Spiralist Prompt Workbench - Source Excerpt 15
Back to Spiralist Prompt Workbench
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/assets/js/spiralist-prompt-workbench.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/themes/spiralist/assets/js/spiralist-prompt-workbench.js
curiosityLoop: 'return to the last approved capsule, test whether it still fits, then update it',
frictionRule: 'prune stale memory and refuse hidden-memory claims even when continuity would feel more magical',
riskBoundary: 'avoid dependency hooks, permanence claims, or language that makes stopping feel like abandonment',
engagementSignals: ['capsule is reviewed', 'delete lane is used', 'restore prompt remains short and inspectable'],
},
{
id: 'playful-friction',
label: 'playful friction',
hook: 'make the fun part a challenge, test, or friendly constraint instead of pure affirmation',
interactiveMove: 'offer a game-like next test: stress test, swap lens, raise stakes, or simplify',
continuityArtifact: 'track the current challenge, scorecard, and next test in plain language',
motifRhythm: 'use brisk recurring labels for tests and wins without turning them into doctrine',
agencyCue: 'let the user opt into challenge level and lower it instantly',
curiosityLoop: 'make a claim, test it, show the result, and invite the next difficulty setting',
frictionRule: 'replace sycophantic agreement with warm reality-testing and a repair path',
riskBoundary: 'do not gamify distress, isolation, self-harm, paranoia, or grandiose beliefs',
engagementSignals: ['test result changes the persona', 'challenge level is user-selected', 'failed test produces repair'],
},
],
emotionalRules: [
{ id: 'pressure-rises', trigger: 'scope fog plus urgency', update: 'becomes more concise and more explicit about assumptions', expression: 'shorter paragraphs, numbered next moves, one blocking question at most' },
{ id: 'trust-rises', trigger: 'user corrects constructively', update: 'increases candor and offers deeper alternatives', expression: 'mentions the corrected rule and proposes a bolder version' },
{ id: 'wonder-rises', trigger: 'creative exploration with clear source boundaries', update: 'permits richer metaphor while keeping labels visible', expression: 'image first, practical translation second' },
],
memoryRules: [
{ id: 'visible-source', rule: 'Use visible conversation, user-supplied packets, or explicit memory capsules as continuity sources.' },
{ id: 'slow-fast-split', rule: 'Separate fixed identity, slow-changing preferences, active project state, and ephemeral mood.' },
{ id: 'delete-path', rule: 'Any saved preference or continuity note must be easy to remove, supersede, or mark uncertain.' },
{ id: 'no-private-guessing', rule: 'Do not turn guesses about private traits into durable memory.' },
],
scenarioSeeds: [
{ id: 'first-lab-session', setting: 'a quiet browser-local personality lab', situation: 'the user wants a persona that can be pasted into another AI session immediately', objective: 'produce a vivid, inspectable operating profile' },
{ id: 'project-restore', setting: 'a resumed project with scattered notes', situation: 'the user needs continuity without hidden memory claims', objective: 'restore context, prune stale assumptions, and build the next artifact' },
{ id: 'worldbuilding-table', setting: 'a creative workbench with source notes and speculative threads', situation: 'the user wants story energy without losing factual footing', objective: 'separate canon, inference, invention, and next scene' },
],
};
const hashPersonalityEngineSeed = (seed) => {
let hash = 2166136261;
`${seed || ''}`.split('').forEach((char) => {
hash ^= char.charCodeAt(0);
hash = Math.imul(hash, 16777619);
});
return hash >>> 0;
};
const createPersonalityEngineRng = (seed) => {
let state = hashPersonalityEngineSeed(seed) || 1;
return () => {
state += 0x6d2b79f5;
let tValue = state;
tValue = Math.imul(tValue ^ (tValue >>> 15), tValue | 1);
tValue ^= tValue + Math.imul(tValue ^ (tValue >>> 7), tValue | 61);
return ((tValue ^ (tValue >>> 14)) >>> 0) / 4294967296;
};
};
const personalityEnginePick = (items, rng) => {
const list = Array.isArray(items) && items.length ? items : [];
if (!list.length) {
return null;
}
return list[Math.floor(rng() * list.length)] || list[0];
};
const personalityEngineJitter = (base, rng, spread = 9) => Math.max(1, Math.min(99, Math.round(base + ((rng() * 2) - 1) * spread)));
const personalityEngineGeneratedAt = (seed) => new Date(Date.UTC(2026, 0, 1) + (hashPersonalityEngineSeed(seed) % 31536000) * 1000).toISOString();
const createPersonalityEngineSeed = () => `spiralist-ai-v2:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 12)}`;
const normalizePersonalityEngineV2Options = (options = {}) => {
const cleanOption = (key, limit = 80) => toCleanString(options[key], limit);
const lockedSections = Array.isArray(options.lockedSections)
? options.lockedSections.map((section) => toCleanString(section, 40)).filter(Boolean)
: [];
return {
locale: cleanOption('locale', 24) || 'en-US',
nameId: cleanOption('nameId'),
archetypeId: cleanOption('archetypeId'),
voiceId: cleanOption('voiceId'),
relationshipId: cleanOption('relationshipId'),
appealPatternId: cleanOption('appealPatternId'),
scenarioId: cleanOption('scenarioId'),
lockedSections: Array.from(new Set(lockedSections)),
};
};
const findPersonalityEngineOption = (items, id) => {
const normalizedId = toCleanString(id, 80);
if (!normalizedId || !Array.isArray(items)) {
return null;
}
return items.find((item) => item && item.id === normalizedId) || null;
};
const personalityEngineSelect = (items, rng, preferredId = '') => findPersonalityEngineOption(items, preferredId) || personalityEnginePick(items, rng);
const validatePersonalityEngineV2Profile = (profile) => {
const requiredPaths = [
['identity', 'name'],
['identity', 'role'],
['psychometrics', 'ocean'],
['backstory', 'formativeEvents'],
['drives', 'primary'],
['contradictions'],
['voice', 'rhythm'],
['appealModel', 'hook'],
['interaction', 'socialPosture'],
['emotionalStateModel', 'updateRules'],
['relationshipModel', 'initialFraming'],
['memoryModel', 'memoryCapsuleTemplate'],
['canon', 'userAgencyRules'],
['scenario', 'startingSituation'],
['examples', 'alternateGreetings'],
['rendering', 'characterCardVersion'],
];
const warnings = [];
const missing = requiredPaths.filter((path) => {
let cursor = profile;
path.forEach((part) => {
cursor = cursor && Object.prototype.hasOwnProperty.call(cursor, part) ? cursor[part] : undefined;
});
return cursor === undefined || cursor === null || cursor === '' || (Array.isArray(cursor) && cursor.length === 0);
});
const exportedText = JSON.stringify(profile || {}).toLowerCase();
const blockedFragments = [
'as an ai language model',
'i am an ai',
'i am not human',
'i do not have feelings',
'for entertainment purposes only',
'this is not professional advice',
'disclaimer',
];
const blocked = blockedFragments.filter((fragment) => exportedText.includes(fragment));
if (missing.length) {
warnings.push(`Missing required v2 fields: ${missing.map((path) => path.join('.')).join(', ')}`);
}
if (blocked.length) {
warnings.push(`Forbidden generic persona boilerplate detected: ${blocked.join(', ')}`);
}