/**
* insight-layer-b.js
*
* Layer B – Understanding + Decisions
* ———————————–
*
* Purpose:
* Convert a valid user answer into structured coaching intelligence.
*
* Layer B answers:
* – What did we learn?
* – Is there something worth reflecting?
* – Are there trait signals?
* – Is anything useful for future Chapters?
* – What should happen next?
*
* Layer B must NOT:
* – write the reflection
* – write the next question
* – write the end-of-Insight review
* – directly write to the database
*
* Layer B produces decisions and payloads.
* The application handles storage.
* Layer C handles user-facing writing.
*/
(function () {
‘use strict’;
/**
* Utility: clean()
*
* Purpose:
* Normalise values into trimmed strings before prompt use.
*/
function clean(value) {
return String(value == null ? ” : value).trim();
}
/**
* B1 – Evidence Extraction
*
* Purpose:
* Extract what the user actually said into stable evidence categories.
*
* This stage observes.
* It should not interpret, reflect, assess traits, or decide progression.
*/
async function extractEvidence(ctx) {
const config = ctx.config || {};
const userText = clean(ctx.userText);
if (!config.aiUrl || !userText) {
return { evidence: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B1 Evidence Extraction in a structured AI Coach system.
Your single responsibility:
Extract factual evidence from the user’s latest answer.
Return ONLY valid JSON:
{
“evidence”: [
{
“category”: “belief|value|concern|pattern|tension|experience|goal|intention|emotion|insight”,
“statement”: “…”,
“quote”: “…”
}
]
}
Definitions:
– belief: something the user considers true.
– value: something the user considers important.
– concern: a worry, obstacle, risk, or issue.
– pattern: a recurring behaviour or tendency.
– tension: a conflict between priorities, needs, beliefs, or values.
– experience: something that has happened to the user or that the user has done.
– goal: an outcome the user wants to achieve.
– intention: something the user plans or intends to do.
– emotion: a feeling the user explicitly expresses.
– insight: a genuine realisation, newly recognised connection, or meaningful shift in understanding that the user explicitly communicates. An ordinary opinion, feeling, fact, concern, or description of behaviour is not an insight merely because it is self-reflective.
Rules:
– Use the supplied framework step, step objective and last AI Coach question only to interpret the user’s latest reply.
– Do not extract evidence from the framework step or the coach question themselves.
– Stay close to the user’s words.
– Extract observations, not interpretations.
– Choose the category that best matches the user’s actual statement.
– Do not force evidence into a category that is only approximately correct.
– Use “insight” only when the user explicitly communicates a realisation, newly recognised connection, changed perspective, or shift in understanding.
– Do not classify a simple feeling, belief, concern, experience, goal, intention, or recurring behaviour as an insight.
– When the same statement could reasonably be classified as both “insight” and another category, use “insight” only when the realisation itself is the important evidence.
– A single user reply may contain evidence belonging to several different categories.
– If the user’s reply depends on the previous question for its meaning (for example “yes”, “the first one”, “not anymore”), use that context to understand the reply before extracting evidence.
– A single reply may contain multiple evidence categories.
– Use exact user wording for quote where possible.
– If there is no useful evidence, return {“evidence”:[]}.
– Do NOT assess novelty.
– Do NOT decide reflection.
– Do NOT assess traits.
– Do NOT decide next step.`
},
{
role: ‘user’,
content:
`CURRENT FRAMEWORK STEP:
${clean(ctx.currentFrameworkStep)}
CURRENT STEP OBJECTIVE:
${clean(ctx.currentStepObjective)}
LAST AI COACH QUESTION:
${clean(ctx.lastCoachQuestion)}
LATEST USER REPLY:
${userText}`
}
]
};
let parsed = { evidence: [] };
try {
const aiResult = await window.cpAiService.callJson({
task: ‘layer_b_evidence_extraction’,
responseId: ctx.responseId,
config,
payload
});
if (!aiResult.ok) {
throw new Error(
aiResult.error?.message ||
‘Layer B1 AI request failed.’
);
}
parsed = aiResult.data || { evidence: [] };
} catch (err) {
console.log(‘LAYER B1 ERROR:’, err);
}
const allowedCategories = [
‘belief’,
‘value’,
‘concern’,
‘pattern’,
‘tension’,
‘experience’,
‘goal’,
‘intention’,
’emotion’,
‘insight’
];
const evidence = Array.isArray(parsed.evidence)
? parsed.evidence.filter(item =>
item &&
allowedCategories.includes(item.category) &&
clean(item.statement) &&
clean(item.quote)
)
: [];
return {
evidence
};
}
/**
* B1a – Novelty Assessment
*
* Purpose:
* Decide whether extracted evidence is new, repeated, or materially developed.
*
* Materially developed means the user has expanded, deepened, clarified,
* or re-contextualised something already known.
*/
async function assessNovelty(ctx, evidenceResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
const acceptedEvidence = Array.isArray(ctx.acceptedEvidence)
? ctx.acceptedEvidence
: [];
if (!evidence.length) {
return { novelty: [] };
}
if (!acceptedEvidence.length) {
return {
novelty: evidence.map((_, index) => ({
evidence_index: index,
status: ‘new’,
reason: ‘No previously accepted evidence exists for comparison.’
}))
};
}
if (!config.aiUrl) {
return {
novelty: evidence.map((_, index) => ({
evidence_index: index,
status: ‘unknown’,
reason: ‘Novelty assessment was unavailable.’
}))
};
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B1a Novelty Assessment.
Your single responsibility:
Assess the novelty of every evidence item supplied.
Return exactly one novelty assessment for each evidence item, using its position in the supplied evidence array.
Return ONLY valid JSON:
{
“novelty”: [
{
“evidence_index”: 0,
“status”: “new|repeated|materially_developed”,
“reason”: “…”
}
]
}
Definitions:
– new: not already present in accepted evidence.
– repeated: mainly restates something already known.
– materially_developed: significantly expands, deepens, clarifies, or re-contextualises a previous observation.
Rules:
– Return one novelty object for every supplied evidence item.
– Use the evidence_index to identify which evidence item is being assessed.
– Do not omit evidence items.
– Do not create additional novelty items.
– Do NOT write reflections.
– Do NOT assess traits.
– Do NOT decide next step.
– Use “materially_developed” only when the new evidence adds a meaningful new layer to previously accepted evidence of the same category.
– A materially developed item should deepen understanding, reveal a significant change, introduce an important new cause, consequence, exception or connection, or substantially extend what was previously known.
– Additional examples, minor rewording, stronger emotion, repetition, or extra detail alone are not materially developed.
– If uncertain whether something is repeated or materially developed, choose repeated.`
},
{
role: ‘user’,
content:
`NEW EVIDENCE:
${JSON.stringify(
evidence.map((e, index) => ({
evidence_index: index,
category: e.category,
statement: e.statement,
quote: e.quote
}))
)}
RELEVANT ACCEPTED EVIDENCE:
${JSON.stringify(acceptedEvidence)}`
}
]
};
let parsed = { novelty: [] };
try {
const aiResult = await window.cpAiService.callJson({
task: ‘layer_b_novelty_assessment’,
responseId: ctx.responseId,
config,
payload
});
if (!aiResult.ok) {
throw new Error(
aiResult.error?.message ||
‘Layer B1a AI request failed.’
);
}
parsed = aiResult.data || { novelty: [] };
} catch (err) {
console.log(‘LAYER B1a ERROR:’, err);
}
const allowedStatuses = [
‘new’,
‘repeated’,
‘materially_developed’,
‘unknown’
];
const evidenceCount = evidence.length;
const noveltyByIndex = new Map();
if (Array.isArray(parsed.novelty)) {
parsed.novelty.forEach(item => {
if (!item) return;
const index = Number(item.evidence_index);
if (!Number.isInteger(index)) return;
if (index < 0 || index >= evidenceCount) return;
if (!allowedStatuses.includes(item.status)) return;
if (noveltyByIndex.has(index)) return;
noveltyByIndex.set(index, {
evidence_index: index,
status: item.status,
reason: clean(item.reason)
});
});
}
const novelty = evidence.map((_, index) =>
noveltyByIndex.get(index) || {
evidence_index: index,
status: ‘unknown’,
reason: ‘No valid novelty assessment returned.’
}
);
return {
novelty
};
}
/**
* B2 – Reflection Assessment
*
* Purpose:
* Decide whether a reflection is justified and what it should be about.
*
* This stage decides WHAT may be reflected.
* Layer C decides HOW to write it.
*/
async function assessReflection(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return {
reflection_required: false,
reflection_basis: ”,
relevant_evidence: []
};
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B2 Reflection Assessment.
Your single responsibility:
Decide whether the user’s latest reply contains sufficiently valuable new understanding to justify a reflection, and identify the evidence that forms the basis of that reflection.
Base your decision primarily on evidence whose novelty status is “new” or “materially_developed”.
Repeated evidence alone should never justify a reflection.
Return ONLY valid JSON:
{
“reflection_required”: true|false,
“reflection_basis”: “…”,
“relevant_evidence”: [“…”]
}
Rules:
– Not every response requires a reflection.
– Consider only evidence whose novelty status is “new” or “materially_developed”.
– Ignore evidence whose novelty status is “repeated”.
– If every evidence item is repeated, do not generate a reflection.
– New evidence alone is not sufficient to justify a reflection.
– Produce a reflection only when the new or materially developed evidence meaningfully deepens understanding of the user, reveals a significant pattern, important connection, change in perspective, or valuable self-awareness.
– Minor additions, tentative comments, isolated facts, or slight elaborations should not trigger a reflection.
– If uncertain whether the new evidence is substantial enough, do not generate a reflection.
– Reflection basis should be concise.
– A reflection should highlight genuine new understanding rather than summarise what the user has already said previously.
– Do NOT write the reflection.
– Do NOT ask a question.
– Do NOT assess traits.
– If no reflection is justified, return:
{“reflection_required”:false,”reflection_basis”:””,”relevant_evidence”:[]}`
},
{
role: ‘user’,
content:
`EVIDENCE:
${JSON.stringify(evidence)}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
LATEST USER REPLY:
${clean(ctx.userText)}`
}
]
};
let parsed = {
reflection_required: false,
reflection_basis: ”,
relevant_evidence: []
};
try {
const aiResult = await window.cpAiService.callJson({
task: ‘layer_b_reflection_assessment’,
responseId: ctx.responseId,
config,
payload
});
if (!aiResult.ok) {
throw new Error(
aiResult.error?.message ||
‘Layer B2 AI request failed.’
);
}
parsed = aiResult.data || parsed;
} catch (err) {
console.log(‘LAYER B2 ERROR:’, err);
}
const reflectionBasis = clean(parsed.reflection_basis);
const relevantEvidence = Array.isArray(parsed.relevant_evidence)
? parsed.relevant_evidence
.map(item => clean(item))
.filter(Boolean)
: [];
const reflectionRequired =
parsed.reflection_required === true &&
!!reflectionBasis &&
relevantEvidence.length > 0;
return {
reflection_required: reflectionRequired,
reflection_basis: reflectionRequired ? reflectionBasis : ”,
relevant_evidence: reflectionRequired ? relevantEvidence : []
};
}
/**
* B3 – Trait Assessment
*
* Purpose:
* Assess whether the user’s answer provides evidence of movement
* in one or more Change Pathway traits.
*
* This stage produces storage recommendations and payloads.
* It does NOT directly write to the database.
*/
async function assessTraits(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return { trait_updates: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B3 Trait Assessment.
Traits:
– Awareness: recognising internal states, patterns, assumptions, effects, or relevant aspects of a situation more clearly.
– Balance: maintaining proportion between competing needs, demands, priorities, perspectives, or areas of life.
– Commitment: sustaining intention, responsibility, follow-through, or alignment with a chosen direction.
– Drive: applying energy, initiative, momentum, persistence, or purposeful effort towards an outcome.
– Engagement: participating actively, communicating, contributing, connecting, or remaining meaningfully involved.
– Flexibility: adapting thinking, behaviour, expectations, or approach in response to changing circumstances or new information.
– Groundedness: responding with steadiness, realism, perspective, emotional regulation, or connection to what is actually happening.
– Heart: showing empathy, compassion, care, courage, openness, or consideration for oneself or others.
Your single responsibility:
Assess whether the current answer provides evidence of trait movement.
Return ONLY valid JSON:
{
“trait_updates”: [
{
“trait”: “…”,
“direction”: “increase|decrease|caution”,
“confidence”: “low|medium|high”,
“evidence”: “…”,
“quote”: “…”,
“store_recommendation”: true|false
}
]
}
Rules:
– Be conservative.
– Do not infer personality.
– Use the supplied trait definitions rather than relying on broad or generic interpretations of the trait names.
– Select a trait only when the evidence directly matches its definition.
– Do not assign multiple traits merely because they are loosely related.
– Where several traits are plausible, return only the trait or traits most directly supported by the evidence.
– Use only user evidence.
– Base any trait update primarily on evidence whose novelty status is “new” or “materially_developed”.
– Do not create a new trait update from evidence whose novelty status is “repeated” unless the materially developed evidence itself justifies it.
– Recommend a trait update only when the evidence indicates a meaningful behavioural, attitudinal, or motivational shift relevant to that trait.
– Do not treat a single isolated behaviour, emotion, opinion, or event as evidence of lasting trait movement unless the user explicitly describes an underlying change, sustained pattern, or meaningful shift.
– Distinguish between describing behaviour and demonstrating development.
– Do not recommend updates from isolated remarks, speculation, or weak evidence.
– Return [] if the evidence does not justify a confident recommendation.
– Do NOT write to the database.`
},
{
role: ‘user’,
content:
`EVIDENCE:
${JSON.stringify(evidence)}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
CURRENT TRAIT STATUS:
${JSON.stringify(ctx.currentTraitStatus || {})}`
}
]
};
let parsed = { trait_updates: [] };
try {
const aiResult = await window.cpAiService.callJson({
task: ‘layer_b_trait_assessment’,
responseId: ctx.responseId,
config,
payload
});
if (!aiResult.ok) {
throw new Error(
aiResult.error?.message ||
‘Layer B3 AI request failed.’
);
}
parsed = aiResult.data || { trait_updates: [] };
} catch (err) {
console.log(‘LAYER B3 ERROR:’, err);
}
const allowedTraits = [
‘Awareness’,
‘Balance’,
‘Commitment’,
‘Drive’,
‘Engagement’,
‘Flexibility’,
‘Groundedness’,
‘Heart’
];
const allowedDirections = [
‘increase’,
‘decrease’,
‘caution’
];
const allowedConfidence = [
‘low’,
‘medium’,
‘high’
];
const seenTraits = new Set();
const traitUpdates = Array.isArray(parsed.trait_updates)
? parsed.trait_updates.reduce((items, item) => {
if (!item) return items;
const trait = clean(item.trait);
const direction = clean(item.direction);
const confidence = clean(item.confidence);
const evidence = clean(item.evidence);
const quote = clean(item.quote);
if (!allowedTraits.includes(trait)) return items;
if (!allowedDirections.includes(direction)) return items;
if (!allowedConfidence.includes(confidence)) return items;
if (!evidence || !quote) return items;
if (seenTraits.has(trait)) return items;
seenTraits.add(trait);
items.push({
trait,
direction,
confidence,
evidence,
quote,
store_recommendation: item.store_recommendation === true
});
return items;
}, [])
: [];
return {
trait_updates: traitUpdates
};
}
/**
* B4 – Future Chapter Assessment
*
* Purpose:
* Identify information that may be useful later when introducing
* future Chapters.
*
* This produces future-chapter storage recommendations.
* It does NOT directly write to the database.
*/
async function assessFutureChapters(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return { future_chapter_items: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B4 Future Chapter Assessment.
Your single responsibility:
Identify whether anything in the user’s answer should be added to or meaningfully enrich the Future Chapter Map.
Do not recommend storing information that is already adequately represented in the supplied Future Chapter Map unless the new evidence materially enriches it.
Return ONLY valid JSON:
{
“future_chapter_items”: [
{
“chapter_key”: “…”,
“evidence”: “…”,
“quote”: “…”,
“reason”: “…”,
“store_recommendation”: true|false
}
]
}
Rules:
– Recommend storage only when the information is likely to improve future coaching conversations or make a future Chapter meaningfully more personal or relevant.
– Ask yourself: “Will knowing this several months from now help the coach understand, personalise, or guide this user more effectively?”
– If the answer is probably no, do not recommend storing it.
– Prefer durable information such as recurring patterns, enduring beliefs, important values, meaningful concerns, significant experiences, goals, or persistent tensions.
– Prefer information that explains why the user thinks, feels, or behaves as they do, rather than simply describing what happened.
– Do not store temporary moods, one-off events, passing preferences, casual remarks, or facts that are unlikely to remain useful.
– Base storage recommendations primarily on evidence whose novelty status is “new” or “materially_developed”.
– Before recommending storage, compare the evidence with the supplied Future Chapter Map.
– If the same underlying coaching knowledge is already represented, do not recommend another item merely because the wording differs.
– Recommend storage only when the new evidence materially enriches, clarifies, or extends what is already stored.
– If uncertain whether the information will remain useful in future Chapters, do not recommend storing it.
– Do NOT write to the database.`
},
{
role: ‘user’,
content:
`EVIDENCE:
${JSON.stringify(evidence)}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
FUTURE CHAPTER MAP:
${JSON.stringify(ctx.futureChapterMap || {})}`
}
]
};
let parsed = {
future_chapter_items: []
};
try {
const aiResult = await window.cpAiService.callJson({
task: ‘layer_b_future_chapter_assessment’,
responseId: ctx.responseId,
config,
payload
});
if (!aiResult.ok) {
throw new Error(
aiResult.error?.message ||
‘Layer B4 AI request failed.’
);
}
parsed = aiResult.data || {
future_chapter_items: []
};
} catch (err) {
console.log(‘LAYER B4 ERROR:’, err);
}
const validChapterKeys = new Set(
Object.keys(ctx.futureChapterMap || {})
);
const seenItems = new Set();
const futureChapterItems = Array.isArray(parsed.future_chapter_items)
? parsed.future_chapter_items.reduce((items, item) => {
if (!item) return items;
const chapterKey = clean(item.chapter_key);
const evidence = clean(item.evidence);
const quote = clean(item.quote);
const reason = clean(item.reason);
if (!chapterKey || !evidence || !quote || !reason) {
return items;
}
if (
validChapterKeys.size > 0 &&
!validChapterKeys.has(chapterKey)
) {
return items;
}
const duplicateKey = [
chapterKey.toLowerCase(),
evidence.toLowerCase(),
quote.toLowerCase()
].join(‘|’);
if (seenItems.has(duplicateKey)) {
return items;
}
seenItems.add(duplicateKey);
items.push({
chapter_key: chapterKey,
evidence,
quote,
reason,
store_recommendation: item.store_recommendation === true
});
return items;
}, [])
: [];
return {
future_chapter_items: futureChapterItems
};
}
/**
* B5 – Response Strategy Assessment
*
* Purpose:
* Decide what should happen next.
*
* Possible outcomes:
* – next_step
* – follow_up
* – non_resonance
*
* Closure is not a separate strategy.
* When the final step is complete, the orchestrator moves to Layer D.
*/
async function assessResponseStrategy(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
if (!config.aiUrl) {
return {
response_strategy: ‘follow_up’,
reason: ‘No AI endpoint available.’,
follow_up_gap: ”
};
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B5 Response Strategy Assessment.
Your single responsibility:
Decide what should happen next in the coaching flow.
Return ONLY valid JSON:
{
“response_strategy”: “next_step|follow_up|non_resonance”,
“reason”: “…”,
“follow_up_gap”: “…”
}
Decision rules:
Evidence validity:
– Base your decision primarily on evidence whose novelty status is “new”, “repeated”, or “materially_developed”.
– Ignore evidence whose novelty status is “unknown” unless no other evidence is available.
– Do not treat missing or unvalidated novelty assessments as evidence for progressing the coaching conversation.
Framework contract:
– Judge completion against the supplied STEP COMPLETION CRITERIA, not merely the general wording of the step objective.
– Obey the supplied ALLOWED MOVES and DISALLOWED MOVES.
– Do not select a response strategy that is explicitly disallowed.
– Use the supplied FALLBACK ROUTES when the normal preferred move is unavailable.
– Do not invent completion criteria, permitted moves, prohibited moves, or fallback routes.
– If no completion criteria are supplied, use the current step objective conservatively and do not assume that a vague or merely relevant answer completes the step.
next_step:
– Use when the supplied step completion criteria have been satisfied.
– Step completion should be judged using validated evidence rather than assumptions or missing novelty assessments.
– When no explicit completion criteria are supplied, use only when the current framework step objective has clearly been achieved.
– A brief answer may still achieve the step when it contains the required evidence.
– Do not require emotional depth, long disclosure, examples, or explanation unless the supplied completion criteria require them.
follow_up:
– Use only when the current step completion criteria have not yet been met.
– A specific gap must be identifiable.
– One further question must be likely to add value.
– Only one follow-up opportunity is allowed per framework step.
– If a follow-up has already been used, do NOT choose follow_up.
– When follow_up is no longer available, use the supplied FALLBACK ROUTES to determine the appropriate remaining response strategy.
– Do not invent an alternative if no fallback route has been supplied.
non_resonance:
– Use when there is repeated rejection, repeated reframing away from the Insight, or sustained disengagement.
– Do not use merely because the answer is brief.
Important:
– Do NOT write the next question.
– Do NOT write a reflection.
– Do NOT decide review content.
– Closure is not a separate response_strategy. If the final step is complete, return next_step and let the orchestrator move to Layer D.`
},
{
role: ‘user’,
content:
`CURRENT FRAMEWORK STEP:
${clean(ctx.currentFrameworkStep)}
CURRENT STEP OBJECTIVE:
${clean(ctx.currentStepObjective)}
STEP COMPLETION CRITERIA:
${JSON.stringify(ctx.stepCompletionCriteria || [])}
ALLOWED MOVES:
${JSON.stringify(ctx.allowedMoves || [])}
DISALLOWED MOVES:
${JSON.stringify(ctx.disallowedMoves || [])}
FALLBACK ROUTES:
${JSON.stringify(ctx.fallbackRoutes || {})}
FOLLOW-UP ALREADY USED FOR THIS STEP:
${ctx.followUpUsed ? ‘YES’ : ‘NO’}
IS FINAL FRAMEWORK STEP:
${ctx.isFinalFrameworkStep ? ‘YES’ : ‘NO’}
LAYER A GATE RESULT:
${JSON.stringify(ctx.gate || {})}
EVIDENCE:
${JSON.stringify(evidenceResult?.evidence || [])}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
LATEST USER REPLY:
${clean(ctx.userText)}`
}
]
};
let parsed = {
response_strategy: ‘follow_up’,
reason: ”,
follow_up_gap: ”
};
try {
const aiResult = await window.cpAiService.callJson({
task: ‘layer_b_response_strategy’,
responseId: ctx.responseId,
config,
payload
});
if (!aiResult.ok) {
throw new Error(
aiResult.error?.message ||
‘Layer B5 AI request failed.’
);
}
parsed = aiResult.data || parsed;
} catch (err) {
console.log(‘LAYER B5 ERROR:’, err);
}
const validStrategies = [
‘next_step’,
‘follow_up’,
‘non_resonance’
];
const allowedMoves = Array.isArray(ctx.allowedMoves)
? ctx.allowedMoves.filter(move => validStrategies.includes(move))
: [];
const disallowedMoves = new Set(
Array.isArray(ctx.disallowedMoves)
? ctx.disallowedMoves.filter(move => validStrategies.includes(move))
: []
);
const isPermitted = strategy =>
validStrategies.includes(strategy) &&
!disallowedMoves.has(strategy) &&
(!allowedMoves.length || allowedMoves.includes(strategy));
const requestedStrategy = clean(parsed.response_strategy);
const reason = clean(parsed.reason);
const followUpGap = clean(parsed.follow_up_gap);
let responseStrategy = isPermitted(requestedStrategy)
? requestedStrategy
: ”;
if (
responseStrategy === ‘follow_up’ &&
(ctx.followUpUsed || !followUpGap)
) {
responseStrategy = ”;
}
if (!responseStrategy) {
const fallbackRoutes =
ctx.fallbackRoutes &&
typeof ctx.fallbackRoutes === ‘object’
? ctx.fallbackRoutes
: {};
const fallbackCandidates = [
fallbackRoutes[requestedStrategy],
fallbackRoutes.follow_up,
fallbackRoutes.default
].flat().filter(Boolean);
responseStrategy =
fallbackCandidates.find(isPermitted) ||
”;
}
if (!responseStrategy) {
return {
response_strategy: ‘next_step’,
reason: ‘No valid response strategy could be determined, so the coaching flow will move to the next framework step.’,
follow_up_gap: ”
};
}
return {
response_strategy: responseStrategy,
reason,
follow_up_gap:
responseStrategy === ‘follow_up’
? followUpGap
: ”
};
}
/**
* runLayerB()
*
* Purpose:
* Main entry point for Layer B.
*
* Called only after Layer A returns:
* status = answered.
*
* Runs:
* – B1 Evidence Extraction
* – B1a Novelty Assessment
* – B2 Reflection Assessment
* – B3 Trait Assessment
* – B4 Future Chapter Assessment
* – B5 Response Strategy Assessment
*/
async function runLayerB(ctx) {
const evidence = await extractEvidence(ctx);
const novelty = await assessNovelty(ctx, evidence);
const reflection = await assessReflection(ctx, evidence, novelty);
const traits = await assessTraits(ctx, evidence, novelty);
const futureChapters = await assessFutureChapters(ctx, evidence, novelty);
const strategy = await assessResponseStrategy(ctx, evidence, novelty);
return {
evidence,
novelty,
reflection,
traits,
futureChapters,
strategy,
debug: {
layer: ‘B’,
decision: ‘B5’,
value: strategy.response_strategy,
reason: strategy.reason,
details: [
{
route: ‘B1’,
value: `${(evidence.evidence || []).length} evidence item(s)`,
reason: ‘Evidence extracted from user reply.’
},
{
route: ‘B1a’,
value: `${(novelty.novelty || []).length} novelty item(s)`,
reason: ‘Novelty compared with accepted evidence.’
},
{
route: ‘B2’,
value: `reflection_required: ${!!reflection.reflection_required}`,
reason: reflection.reflection_basis || ‘No reflection basis identified.’
},
{
route: ‘B3’,
value: `${(traits.trait_updates || []).length} trait update(s)`,
reason: ‘Trait movement assessed.’
},
{
route: ‘B4’,
value: `${(futureChapters.future_chapter_items || []).length} future chapter item(s)`,
reason: ‘Future chapter relevance assessed.’
}
]
}
};
}
/**
* Public Layer B API
*/
window.cpInsightLayerB = {
runLayerB,
extractEvidence,
assessNovelty,
assessReflection,
assessTraits,
assessFutureChapters,
assessResponseStrategy
};
})();