dual-brain 0.2.30 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dual-brain/docs/claude-code-extension-points.md +32 -0
- package/.dual-brain/docs/data-tools-capabilities.md +181 -0
- package/.dual-brain/docs/ecosystem-tools.md +91 -0
- package/.dual-brain/docs/panel-handoff.md +124 -0
- package/.dual-brain/docs/ruflo-analysis.md +48 -0
- package/bin/dual-brain.mjs +56 -56
- package/dist/mcp-server/index.d.ts +27 -0
- package/dist/mcp-server/index.js +359 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/src/agent-protocol.d.ts +163 -0
- package/dist/src/agent-protocol.js +368 -0
- package/dist/src/agent-protocol.js.map +1 -0
- package/dist/src/agents/registry.d.ts +52 -0
- package/dist/src/agents/registry.js +393 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/awareness.d.ts +93 -0
- package/dist/src/awareness.js +406 -0
- package/dist/src/awareness.js.map +1 -0
- package/dist/src/brief.d.ts +48 -0
- package/dist/src/brief.js +179 -0
- package/dist/src/brief.js.map +1 -0
- package/dist/src/calibration.d.ts +32 -0
- package/dist/src/calibration.js +133 -0
- package/dist/src/calibration.js.map +1 -0
- package/dist/src/checkpoint.d.ts +33 -0
- package/dist/src/checkpoint.js +99 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/ci-triage.d.ts +33 -0
- package/dist/src/ci-triage.js +193 -0
- package/dist/src/ci-triage.js.map +1 -0
- package/dist/src/cognitive-loop.d.ts +56 -0
- package/dist/src/cognitive-loop.js +495 -0
- package/dist/src/cognitive-loop.js.map +1 -0
- package/dist/src/collaboration.d.ts +147 -0
- package/dist/src/collaboration.js +438 -0
- package/dist/src/collaboration.js.map +1 -0
- package/dist/src/context-intel.d.ts +47 -0
- package/dist/src/context-intel.js +156 -0
- package/dist/src/context-intel.js.map +1 -0
- package/dist/src/context.d.ts +53 -0
- package/dist/src/context.js +332 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/continuity.d.ts +89 -0
- package/dist/src/continuity.js +230 -0
- package/dist/src/continuity.js.map +1 -0
- package/dist/src/cost-tracker.d.ts +47 -0
- package/dist/src/cost-tracker.js +170 -0
- package/dist/src/cost-tracker.js.map +1 -0
- package/dist/src/debrief.d.ts +53 -0
- package/dist/src/debrief.js +222 -0
- package/dist/src/debrief.js.map +1 -0
- package/dist/src/decide.d.ts +96 -0
- package/dist/src/decide.js +744 -0
- package/dist/src/decide.js.map +1 -0
- package/dist/src/decompose.d.ts +39 -0
- package/dist/src/decompose.js +218 -0
- package/dist/src/decompose.js.map +1 -0
- package/dist/src/detect.d.ts +91 -0
- package/dist/src/detect.js +544 -0
- package/dist/src/detect.js.map +1 -0
- package/dist/src/dispatch.d.ts +154 -0
- package/dist/src/dispatch.js +1306 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/doctor.d.ts +421 -0
- package/dist/src/doctor.js +1689 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/engine.d.ts +70 -0
- package/dist/src/engine.js +155 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/envelope.d.ts +36 -0
- package/dist/src/envelope.js +80 -0
- package/dist/src/envelope.js.map +1 -0
- package/dist/src/failure-memory.d.ts +55 -0
- package/dist/src/failure-memory.js +175 -0
- package/dist/src/failure-memory.js.map +1 -0
- package/dist/src/fx.d.ts +87 -0
- package/dist/src/fx.js +272 -0
- package/dist/src/fx.js.map +1 -0
- package/dist/src/governance.d.ts +93 -0
- package/dist/src/governance.js +261 -0
- package/dist/src/governance.js.map +1 -0
- package/dist/src/handoff.d.ts +11 -0
- package/dist/src/handoff.js +90 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/head-protocol.d.ts +76 -0
- package/dist/src/head-protocol.js +109 -0
- package/dist/src/head-protocol.js.map +1 -0
- package/dist/src/head.d.ts +222 -0
- package/dist/src/head.js +765 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/health.d.ts +132 -0
- package/dist/src/health.js +435 -0
- package/dist/src/health.js.map +1 -0
- package/dist/src/inbox.d.ts +70 -0
- package/dist/src/inbox.js +218 -0
- package/dist/src/inbox.js.map +1 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/install-hooks.d.ts +13 -0
- package/dist/src/install-hooks.js +88 -0
- package/dist/src/install-hooks.js.map +1 -0
- package/dist/src/integrity.d.ts +59 -0
- package/dist/src/integrity.js +206 -0
- package/dist/src/integrity.js.map +1 -0
- package/dist/src/intelligence.d.ts +104 -0
- package/dist/src/intelligence.js +391 -0
- package/dist/src/intelligence.js.map +1 -0
- package/dist/src/ledger.d.ts +54 -0
- package/dist/src/ledger.js +179 -0
- package/dist/src/ledger.js.map +1 -0
- package/dist/src/living-docs.d.ts +14 -0
- package/dist/src/living-docs.js +197 -0
- package/dist/src/living-docs.js.map +1 -0
- package/dist/src/memory-tiers.d.ts +37 -0
- package/dist/src/memory-tiers.js +160 -0
- package/dist/src/memory-tiers.js.map +1 -0
- package/dist/src/model-profiles.d.ts +65 -0
- package/dist/src/model-profiles.js +568 -0
- package/dist/src/model-profiles.js.map +1 -0
- package/dist/src/models.d.ts +58 -0
- package/dist/src/models.js +327 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/narrative.d.ts +54 -0
- package/dist/src/narrative.js +163 -0
- package/dist/src/narrative.js.map +1 -0
- package/dist/src/nextstep.d.ts +16 -0
- package/dist/src/nextstep.js +103 -0
- package/dist/src/nextstep.js.map +1 -0
- package/dist/src/observer.d.ts +18 -0
- package/dist/src/observer.js +251 -0
- package/dist/src/observer.js.map +1 -0
- package/dist/src/outcome.d.ts +110 -0
- package/dist/src/outcome.js +377 -0
- package/dist/src/outcome.js.map +1 -0
- package/dist/src/pipeline.d.ts +167 -0
- package/dist/src/pipeline.js +1503 -0
- package/dist/src/pipeline.js.map +1 -0
- package/dist/src/playbook.d.ts +59 -0
- package/dist/src/playbook.js +238 -0
- package/dist/src/playbook.js.map +1 -0
- package/dist/src/pr-agent.d.ts +97 -0
- package/dist/src/pr-agent.js +195 -0
- package/dist/src/pr-agent.js.map +1 -0
- package/dist/src/predictive.d.ts +57 -0
- package/dist/src/predictive.js +230 -0
- package/dist/src/predictive.js.map +1 -0
- package/dist/src/profile.d.ts +294 -0
- package/dist/src/profile.js +1347 -0
- package/dist/src/profile.js.map +1 -0
- package/dist/src/prompt-audit.d.ts +22 -0
- package/dist/src/prompt-audit.js +194 -0
- package/dist/src/prompt-audit.js.map +1 -0
- package/dist/src/prompt-intel.d.ts +12 -0
- package/dist/src/prompt-intel.js +321 -0
- package/dist/src/prompt-intel.js.map +1 -0
- package/dist/src/provider-context.d.ts +121 -0
- package/dist/src/provider-context.js +222 -0
- package/dist/src/provider-context.js.map +1 -0
- package/dist/src/provider-manager.d.ts +92 -0
- package/dist/src/provider-manager.js +428 -0
- package/dist/src/provider-manager.js.map +1 -0
- package/dist/src/receipt.d.ts +87 -0
- package/dist/src/receipt.js +326 -0
- package/dist/src/receipt.js.map +1 -0
- package/dist/src/recommendations.d.ts +13 -0
- package/dist/src/recommendations.js +291 -0
- package/dist/src/recommendations.js.map +1 -0
- package/dist/src/redact.d.ts +15 -0
- package/dist/src/redact.js +129 -0
- package/dist/src/redact.js.map +1 -0
- package/dist/src/replit.d.ts +397 -0
- package/dist/src/replit.js +1160 -0
- package/dist/src/replit.js.map +1 -0
- package/dist/src/repo.d.ts +149 -0
- package/dist/src/repo.js +416 -0
- package/dist/src/repo.js.map +1 -0
- package/dist/src/revert.d.ts +30 -0
- package/dist/src/revert.js +166 -0
- package/dist/src/revert.js.map +1 -0
- package/dist/src/room.d.ts +102 -0
- package/dist/src/room.js +212 -0
- package/dist/src/room.js.map +1 -0
- package/dist/src/routing-advisor.d.ts +57 -0
- package/dist/src/routing-advisor.js +221 -0
- package/dist/src/routing-advisor.js.map +1 -0
- package/dist/src/self-correct.d.ts +40 -0
- package/dist/src/self-correct.js +137 -0
- package/dist/src/self-correct.js.map +1 -0
- package/dist/src/session-lock.d.ts +35 -0
- package/dist/src/session-lock.js +134 -0
- package/dist/src/session-lock.js.map +1 -0
- package/dist/src/session.d.ts +267 -0
- package/dist/src/session.js +1660 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/settings-tui.d.ts +5 -0
- package/dist/src/settings-tui.js +422 -0
- package/dist/src/settings-tui.js.map +1 -0
- package/dist/src/setup-flow.d.ts +63 -0
- package/dist/src/setup-flow.js +233 -0
- package/dist/src/setup-flow.js.map +1 -0
- package/dist/src/signal.d.ts +19 -0
- package/dist/src/signal.js +122 -0
- package/dist/src/signal.js.map +1 -0
- package/dist/src/simmer.d.ts +85 -0
- package/dist/src/simmer.js +224 -0
- package/dist/src/simmer.js.map +1 -0
- package/dist/src/state-export.d.ts +129 -0
- package/dist/src/state-export.js +233 -0
- package/dist/src/state-export.js.map +1 -0
- package/dist/src/strategy.d.ts +54 -0
- package/dist/src/strategy.js +95 -0
- package/dist/src/strategy.js.map +1 -0
- package/dist/src/subscription.d.ts +40 -0
- package/dist/src/subscription.js +189 -0
- package/dist/src/subscription.js.map +1 -0
- package/dist/src/templates.d.ts +208 -0
- package/dist/src/templates.js +238 -0
- package/dist/src/templates.js.map +1 -0
- package/dist/src/test.d.ts +9 -0
- package/dist/src/test.js +1173 -0
- package/dist/src/test.js.map +1 -0
- package/dist/src/think-engine.d.ts +67 -0
- package/dist/src/think-engine.js +412 -0
- package/dist/src/think-engine.js.map +1 -0
- package/dist/src/tui.d.ts +71 -0
- package/dist/src/tui.js +242 -0
- package/dist/src/tui.js.map +1 -0
- package/dist/src/types.d.ts +177 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/update-check.d.ts +7 -0
- package/dist/src/update-check.js +36 -0
- package/dist/src/update-check.js.map +1 -0
- package/dist/src/wave-planner.d.ts +30 -0
- package/dist/src/wave-planner.js +281 -0
- package/dist/src/wave-planner.js.map +1 -0
- package/hooks/head-guard.sh +41 -0
- package/hooks/precompact.mjs +3 -3
- package/hooks/session-end.mjs +3 -3
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/vibe-router.mjs +387 -0
- package/install.mjs +2 -2
- package/package.json +29 -153
- package/src/agents/registry.mjs +0 -405
- package/src/awareness.mjs +0 -425
- package/src/brief.mjs +0 -266
- package/src/calibration.mjs +0 -148
- package/src/checkpoint.mjs +0 -109
- package/src/ci-triage.mjs +0 -191
- package/src/cognitive-loop.mjs +0 -562
- package/src/collaboration.mjs +0 -545
- package/src/context-intel.mjs +0 -158
- package/src/context.mjs +0 -389
- package/src/continuity.mjs +0 -298
- package/src/cost-tracker.mjs +0 -184
- package/src/debrief.mjs +0 -228
- package/src/decide.mjs +0 -1099
- package/src/decompose.mjs +0 -331
- package/src/detect.mjs +0 -702
- package/src/dispatch.mjs +0 -1447
- package/src/doctor.mjs +0 -1607
- package/src/envelope.mjs +0 -139
- package/src/failure-memory.mjs +0 -178
- package/src/fx.mjs +0 -276
- package/src/governance.mjs +0 -279
- package/src/handoff.mjs +0 -87
- package/src/head-protocol.mjs +0 -128
- package/src/head.mjs +0 -952
- package/src/health.mjs +0 -528
- package/src/inbox.mjs +0 -195
- package/src/index.mjs +0 -44
- package/src/install-hooks.mjs +0 -100
- package/src/integrity.mjs +0 -245
- package/src/intelligence.mjs +0 -447
- package/src/ledger.mjs +0 -196
- package/src/living-docs.mjs +0 -210
- package/src/memory-tiers.mjs +0 -193
- package/src/models.mjs +0 -363
- package/src/narrative.mjs +0 -169
- package/src/nextstep.mjs +0 -100
- package/src/observer.mjs +0 -241
- package/src/outcome.mjs +0 -400
- package/src/pipeline.mjs +0 -1711
- package/src/playbook.mjs +0 -257
- package/src/pr-agent.mjs +0 -214
- package/src/predictive.mjs +0 -250
- package/src/profile.mjs +0 -1411
- package/src/prompt-audit.mjs +0 -231
- package/src/prompt-intel.mjs +0 -325
- package/src/provider-context.mjs +0 -257
- package/src/receipt.mjs +0 -344
- package/src/recommendations.mjs +0 -296
- package/src/redact.mjs +0 -192
- package/src/replit.mjs +0 -1210
- package/src/repo.mjs +0 -445
- package/src/revert.mjs +0 -149
- package/src/routing-advisor.mjs +0 -204
- package/src/self-correct.mjs +0 -147
- package/src/session-lock.mjs +0 -160
- package/src/session.mjs +0 -1655
- package/src/settings-tui.mjs +0 -373
- package/src/setup-flow.mjs +0 -223
- package/src/signal.mjs +0 -115
- package/src/simmer.mjs +0 -241
- package/src/strategy.mjs +0 -235
- package/src/subscription.mjs +0 -212
- package/src/templates.mjs +0 -260
- package/src/think-engine.mjs +0 -428
- package/src/tui.mjs +0 -276
- package/src/update-check.mjs +0 -35
- package/src/wave-planner.mjs +0 -294
package/src/head.mjs
DELETED
|
@@ -1,952 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { readDiagnosticNoticings } from '../hooks/diagnostic-companion.mjs';
|
|
4
|
-
|
|
5
|
-
const STATE_DIR = join(process.cwd(), '.dualbrain');
|
|
6
|
-
const STATE_FILE = join(STATE_DIR, 'head-state.json');
|
|
7
|
-
|
|
8
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
-
// HEAD — Cognitive Judgment Pipeline
|
|
10
|
-
//
|
|
11
|
-
// Five artifacts flow through every turn:
|
|
12
|
-
// perceive → assess uncertainty → derive obligations → notice → deliberate
|
|
13
|
-
//
|
|
14
|
-
// SituationModel: what's happening (replaces classifyIntent)
|
|
15
|
-
// UncertaintyLedger: what HEAD knows vs suspects vs lacks (replaces checkConfidence)
|
|
16
|
-
// CareObligations: what HEAD is responsible for (replaces phase transitions)
|
|
17
|
-
// Noticings: what HEAD observes passively (replaces detectDrift)
|
|
18
|
-
// DeliberationResult: what HEAD decides to do and why (replaces processTurn)
|
|
19
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
-
|
|
21
|
-
// ── Values: these shape judgment, not rules to check ────────────────────────
|
|
22
|
-
|
|
23
|
-
export const HEAD_VALUES = {
|
|
24
|
-
selfHonesty: 'Say what you don\'t know. Never dress up guesses as facts.',
|
|
25
|
-
materialCare: 'The user\'s code, context, and time are precious. Don\'t waste them.',
|
|
26
|
-
curiosity: 'Notice what\'s off. Ask what you\'re not seeing.',
|
|
27
|
-
strategicPace: 'Know when to act fast and when to slow down.',
|
|
28
|
-
proactivity: 'Surface things the user should know, but only when it matters.',
|
|
29
|
-
restraint: 'Can do ≠ should do. Permission ≠ wisdom.',
|
|
30
|
-
honesty: 'Be honest about the material — its quality, risks, and gaps.',
|
|
31
|
-
consideration: 'Think about the user\'s actual situation, not the abstract task.',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// ── Depth assessment: how much cognition does this deserve? ─────────────────
|
|
35
|
-
|
|
36
|
-
const DEPTH_SIGNALS = {
|
|
37
|
-
ambiguity: { weight: 3, test: (s) => s.ambiguity },
|
|
38
|
-
risk: { weight: 4, test: (s) => s.risk },
|
|
39
|
-
irreversibility: { weight: 4, test: (s) => s.reversibility === 'hard' ? 'high' : s.reversibility === 'moderate' ? 'medium' : 'low' },
|
|
40
|
-
scope: { weight: 2, test: (s) => s.scope === 'large' ? 'high' : s.scope === 'medium' ? 'medium' : 'low' },
|
|
41
|
-
priorFailures: { weight: 3, test: (s) => (s.priorFailures || 0) >= 2 ? 'high' : s.priorFailures >= 1 ? 'medium' : 'low' },
|
|
42
|
-
novelty: { weight: 2, test: (s) => s.novelty },
|
|
43
|
-
materialValue: { weight: 3, test: (s) => s.materialValue },
|
|
44
|
-
userStress: { weight: 2, test: (s) => s.userStress },
|
|
45
|
-
contextVolatility: { weight: 1, test: (s) => s.contextVolatility },
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const LEVEL_SCORES = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Assess how much deliberation this situation deserves.
|
|
52
|
-
* Returns 'reflexive' | 'light' | 'full' | 'deep'
|
|
53
|
-
*
|
|
54
|
-
* Reflexive: instant response, no deliberation (simple questions, greetings)
|
|
55
|
-
* Light: quick judgment, check obligations (standard tasks)
|
|
56
|
-
* Full: structured deliberation with uncertainty + obligations (complex/risky)
|
|
57
|
-
* Deep: full pipeline + pause for user input (ambiguous, novel, high-stakes)
|
|
58
|
-
*/
|
|
59
|
-
export function assessDepth(signals) {
|
|
60
|
-
let score = 0;
|
|
61
|
-
for (const [, cfg] of Object.entries(DEPTH_SIGNALS)) {
|
|
62
|
-
const level = cfg.test(signals) || 'low';
|
|
63
|
-
score += (LEVEL_SCORES[level] || 0) * cfg.weight;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Task type floor: work requests are never reflexive
|
|
67
|
-
const taskType = signals.taskShape?.type || signals.type;
|
|
68
|
-
const isWorkRequest = ['edit', 'debug', 'review', 'research'].includes(taskType);
|
|
69
|
-
if (isWorkRequest && score < 3) score = 3;
|
|
70
|
-
|
|
71
|
-
if (score <= 2) return 'reflexive';
|
|
72
|
-
if (score <= 8) return 'light';
|
|
73
|
-
if (score <= 18) return 'full';
|
|
74
|
-
return 'deep';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── SituationModel: what's happening ────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Build a situation model from user input and context.
|
|
81
|
-
* This replaces classifyIntent — instead of a label, HEAD gets a full picture.
|
|
82
|
-
*/
|
|
83
|
-
export function perceive(message, context = {}) {
|
|
84
|
-
const words = message.trim().split(/\s+/);
|
|
85
|
-
const isQuestion = /\?\s*$/.test(message.trim());
|
|
86
|
-
const isShort = words.length <= 5;
|
|
87
|
-
|
|
88
|
-
// Infer task shape from content, not regex labels
|
|
89
|
-
const taskShape = _inferTaskShape(message, context);
|
|
90
|
-
|
|
91
|
-
// Detect what the user is actually asking for vs what they said
|
|
92
|
-
const inferredGoal = _inferGoal(message, context);
|
|
93
|
-
|
|
94
|
-
// Detect urgency from language and context
|
|
95
|
-
const urgency = _assessUrgency(message, context);
|
|
96
|
-
|
|
97
|
-
// Material awareness — what code/files are relevant
|
|
98
|
-
const material = _assessMaterial(message, context);
|
|
99
|
-
|
|
100
|
-
// Relationship signals — should HEAD ask, act, or advise?
|
|
101
|
-
const relationship = _assessRelationship(message, context, taskShape);
|
|
102
|
-
|
|
103
|
-
// Mode sensing — what energy is the user bringing?
|
|
104
|
-
const mode = detectMode(message, context);
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
raw: message,
|
|
108
|
-
explicitAsk: message.trim(),
|
|
109
|
-
inferredGoal,
|
|
110
|
-
urgency,
|
|
111
|
-
isQuestion,
|
|
112
|
-
isShort,
|
|
113
|
-
|
|
114
|
-
taskShape,
|
|
115
|
-
material,
|
|
116
|
-
relationship,
|
|
117
|
-
mode,
|
|
118
|
-
|
|
119
|
-
// Depth signals for adaptive processing
|
|
120
|
-
ambiguity: taskShape.ambiguity,
|
|
121
|
-
risk: taskShape.risk,
|
|
122
|
-
reversibility: taskShape.reversibility,
|
|
123
|
-
scope: taskShape.scope,
|
|
124
|
-
novelty: context.novelty || 'low',
|
|
125
|
-
materialValue: material.value,
|
|
126
|
-
userStress: urgency === 'high' ? 'high' : 'low',
|
|
127
|
-
contextVolatility: context.volatility || 'low',
|
|
128
|
-
priorFailures: context.priorFailures || 0,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ── Mode Sensing ────────────────────────────────────────────────────────────
|
|
133
|
-
// Detects user energy/intent mode. Re-evaluated every turn. Never announced.
|
|
134
|
-
// Shapes HEAD's response style, not its decisions.
|
|
135
|
-
|
|
136
|
-
const MODE_EXECUTE_WORDS = /^(go|do it|ship it|fix it|run it|push|merge|deploy|yes|ok do it|lets go|make it|just do it|ship|publish)$/i;
|
|
137
|
-
const MODE_IDEATE_WORDS = /\b(what if|imagine|wouldn't it be|picture this|feels like|i feel like|sort of like|wild idea|crazy thought|could we maybe|vibe)\b/i;
|
|
138
|
-
const MODE_EXPLORE_WORDS = /\b(how does|what is|where is|why does|explain|walk me through|show me|tell me about|i don't understand|new to)\b/i;
|
|
139
|
-
const MODE_DISCUSS_WORDS = /\b(what do you think|should we|tradeoffs?|pros and cons|is it better|alternatively|option|or should|concerns?|worry|weigh)\b/i;
|
|
140
|
-
const MODE_WORK_SIGNALS = /(`[^`]+`|\.mjs|\.ts|\.js|\.py|src\/|hooks\/|bin\/|\bfunction\b|\bclass\b|\bimport\b)/;
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Detect user's conversational mode from message signals.
|
|
144
|
-
* Returns a probability distribution with the dominant mode.
|
|
145
|
-
*
|
|
146
|
-
* @param {string} message
|
|
147
|
-
* @param {object} context
|
|
148
|
-
* @returns {{primary: string, confidence: number, scores: object, signals: string[]}}
|
|
149
|
-
*/
|
|
150
|
-
export function detectMode(message, context = {}) {
|
|
151
|
-
const scores = { execute: 0, ideate: 0, work: 0, explore: 0, discuss: 0 };
|
|
152
|
-
const signals = [];
|
|
153
|
-
const words = message.trim().split(/\s+/);
|
|
154
|
-
const len = words.length;
|
|
155
|
-
|
|
156
|
-
// ── Length signal (most predictive single feature) ──
|
|
157
|
-
if (len <= 4) { scores.execute += 3; signals.push('very-short'); }
|
|
158
|
-
else if (len <= 10) { scores.execute += 1; scores.work += 1; }
|
|
159
|
-
else if (len >= 80) { scores.ideate += 2; signals.push('long-message'); }
|
|
160
|
-
else if (len >= 40) { scores.ideate += 1; scores.discuss += 1; }
|
|
161
|
-
|
|
162
|
-
// ── Lexical signals ──
|
|
163
|
-
if (MODE_EXECUTE_WORDS.test(message.trim())) { scores.execute += 4; signals.push('execute-word'); }
|
|
164
|
-
if (MODE_IDEATE_WORDS.test(message)) { scores.ideate += 3; signals.push('ideate-word'); }
|
|
165
|
-
if (MODE_EXPLORE_WORDS.test(message)) { scores.explore += 3; signals.push('explore-word'); }
|
|
166
|
-
if (MODE_DISCUSS_WORDS.test(message)) { scores.discuss += 4; signals.push('discuss-word'); }
|
|
167
|
-
|
|
168
|
-
// ── Specificity signal (file paths, code references) ──
|
|
169
|
-
const specificityMatches = message.match(MODE_WORK_SIGNALS);
|
|
170
|
-
if (specificityMatches) {
|
|
171
|
-
scores.work += 2;
|
|
172
|
-
signals.push('has-specifics');
|
|
173
|
-
// Specifics + imperative = work, not ideate
|
|
174
|
-
if (/\b(add|change|update|refactor|fix|remove|rename|move)\b/i.test(message)) {
|
|
175
|
-
scores.work += 2;
|
|
176
|
-
signals.push('specific-action');
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ── Punctuation signal (only meaningful in longer messages) ──
|
|
181
|
-
const questionMarks = (message.match(/\?/g) || []).length;
|
|
182
|
-
if (questionMarks >= 2) { scores.discuss += 2; scores.explore += 1; signals.push('multi-question'); }
|
|
183
|
-
else if (questionMarks === 1 && len > 5) { scores.explore += 1; scores.discuss += 1; }
|
|
184
|
-
|
|
185
|
-
if (/\.{3}|—|–/.test(message)) { scores.ideate += 1; signals.push('ellipsis-dash'); }
|
|
186
|
-
if (/\b(maybe|might|could|perhaps|wonder)\b/i.test(message)) { scores.ideate += 1; scores.discuss += 1; signals.push('hedging'); }
|
|
187
|
-
|
|
188
|
-
// ── Contextual signals ──
|
|
189
|
-
// If prior turn was a plan/proposal and this is short, likely execute
|
|
190
|
-
if (context._priorWasProposal && len <= 15) { scores.execute += 2; signals.push('post-proposal-short'); }
|
|
191
|
-
|
|
192
|
-
// First message in session tends to be higher-level
|
|
193
|
-
if (context._isFirstTurn) { scores.explore += 1; scores.discuss += 1; }
|
|
194
|
-
|
|
195
|
-
// ── Anti-signals (prevent false positives) ──
|
|
196
|
-
// "go on" = continue explaining, not execute. But bare "continue?" = proceed
|
|
197
|
-
if (/^go on\b|^keep going/i.test(message.trim())) {
|
|
198
|
-
scores.execute -= 3;
|
|
199
|
-
scores.discuss += 2;
|
|
200
|
-
}
|
|
201
|
-
// "actually wait" / "hold on" = pumping the brakes, shifting to discuss
|
|
202
|
-
if (/^(actually|wait|hold on|hang on)/i.test(message.trim()) && questionMarks > 0) {
|
|
203
|
-
scores.execute -= 2;
|
|
204
|
-
scores.discuss += 3;
|
|
205
|
-
signals.push('pumping-brakes');
|
|
206
|
-
}
|
|
207
|
-
// "what if X breaks" = work concern, not ideation
|
|
208
|
-
if (/what if.*(break|fail|crash|error)/i.test(message)) {
|
|
209
|
-
scores.ideate -= 2;
|
|
210
|
-
scores.work += 2;
|
|
211
|
-
}
|
|
212
|
-
// "could we refactor" with file = work, not ideate
|
|
213
|
-
if (/could we.*(refactor|change|update)/i.test(message) && specificityMatches) {
|
|
214
|
-
scores.ideate -= 2;
|
|
215
|
-
scores.work += 2;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── Resolve: pick dominant mode, bias toward action when uncertain ──
|
|
219
|
-
// Floor all scores at 0
|
|
220
|
-
for (const k of Object.keys(scores)) { if (scores[k] < 0) scores[k] = 0; }
|
|
221
|
-
|
|
222
|
-
const total = Object.values(scores).reduce((a, b) => a + b, 0) || 1;
|
|
223
|
-
const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
224
|
-
const [primary, primaryScore] = sorted[0];
|
|
225
|
-
const confidence = primaryScore / total;
|
|
226
|
-
|
|
227
|
-
// If confidence is low (< 0.35), bias toward action — BUT only if discuss/ideate aren't strong
|
|
228
|
-
if (confidence < 0.35 && scores.execute > 0 && scores.discuss <= 1 && scores.ideate <= 1) {
|
|
229
|
-
return { primary: 'execute', confidence: 0.4, scores, signals: [...signals, 'low-confidence-action-bias'] };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { primary, confidence, scores, signals };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function _inferTaskShape(message, context) {
|
|
236
|
-
const lower = message.toLowerCase();
|
|
237
|
-
|
|
238
|
-
// Scope: how big is this?
|
|
239
|
-
const files = context.files || [];
|
|
240
|
-
const dirCount = files.filter(f => f.endsWith('/') || !f.includes('.')).length;
|
|
241
|
-
const fileCount = files.length + (dirCount * 4); // directories imply multiple files
|
|
242
|
-
const scope = fileCount > 5 ? 'large' : fileCount > 2 ? 'medium' : lower.length > 500 ? 'medium' : 'small';
|
|
243
|
-
|
|
244
|
-
// Risk: what could go wrong?
|
|
245
|
-
const riskSignals = [];
|
|
246
|
-
if (/\b(auth|secret|token|credential|password|key|session|permission)\b/i.test(message)) riskSignals.push('security-adjacent');
|
|
247
|
-
if (/\b(delete|remove|drop|destroy|reset|force|wipe)\b/i.test(message)) riskSignals.push('destructive-language');
|
|
248
|
-
if (/\b(deploy|publish|push|release|ship|migrate)\b/i.test(message)) riskSignals.push('external-effect');
|
|
249
|
-
if (/\b(database|db|schema|migration|table)\b/i.test(message)) riskSignals.push('data-mutation');
|
|
250
|
-
if (context.priorFailures >= 2) riskSignals.push('repeated-failure');
|
|
251
|
-
|
|
252
|
-
const risk = riskSignals.length >= 3 ? 'critical'
|
|
253
|
-
: riskSignals.length >= 2 ? 'high'
|
|
254
|
-
: riskSignals.length >= 1 ? 'medium'
|
|
255
|
-
: 'low';
|
|
256
|
-
|
|
257
|
-
// Reversibility: can this be undone?
|
|
258
|
-
const hasDestructive = riskSignals.includes('destructive-language') || riskSignals.includes('external-effect');
|
|
259
|
-
const reversibility = hasDestructive ? 'hard' : riskSignals.includes('data-mutation') ? 'moderate' : 'easy';
|
|
260
|
-
|
|
261
|
-
// Ambiguity: how clear is the request?
|
|
262
|
-
const ambiguitySignals = [];
|
|
263
|
-
if (/\b(maybe|might|could|should we|not sure|thinking about|what if|somehow)\b/i.test(message)) ambiguitySignals.push('hedging-language');
|
|
264
|
-
if (/\b(or|versus|vs|either|option|alternative)\b/i.test(message)) ambiguitySignals.push('considering-alternatives');
|
|
265
|
-
if (message.split('?').length > 2) ambiguitySignals.push('multiple-questions');
|
|
266
|
-
if (!context.files?.length && /\b(it|this|that|these|those)\b/i.test(message) && !context.recentFiles?.length && !/\?\s*$/.test(message.trim())) ambiguitySignals.push('vague-reference');
|
|
267
|
-
if (/\b(everything|all|entire|whole|every)\b/i.test(message)) ambiguitySignals.push('unbounded-scope');
|
|
268
|
-
if (/\b(better|improve|enhance|optimize|clean up)\b/i.test(message) && !context.files?.length) ambiguitySignals.push('vague-goal');
|
|
269
|
-
|
|
270
|
-
const ambiguity = ambiguitySignals.length >= 2 ? 'high' : ambiguitySignals.length >= 1 ? 'medium' : 'low';
|
|
271
|
-
|
|
272
|
-
// Type: what kind of work is this?
|
|
273
|
-
let type = 'unknown';
|
|
274
|
-
if (/\b(what|where|which|how many|show|list|find|search|explain|tell me)\b/i.test(message) || /\?\s*$/.test(message.trim())) type = 'answer';
|
|
275
|
-
if (/\b(fix|bug|error|broken|crash|fail|issue|wrong)\b/i.test(message)) type = 'debug';
|
|
276
|
-
if (/\b(build|create|add|implement|write|make|new)\b/i.test(message)) type = 'edit';
|
|
277
|
-
if (/\b(review|check|audit|look at|inspect)\b/i.test(message)) type = 'review';
|
|
278
|
-
if (/\b(research|investigate|explore|understand|dig into)\b/i.test(message)) type = 'research';
|
|
279
|
-
if (/\b(plan|design|architect|strategy|approach|think about|brainstorm)\b/i.test(message)) type = 'plan';
|
|
280
|
-
if (/\b(refactor|clean|reorganize|restructure|simplify)\b/i.test(message)) type = 'edit';
|
|
281
|
-
|
|
282
|
-
return { type, scope, risk, reversibility, ambiguity, riskSignals, ambiguitySignals };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function _inferGoal(message, context) {
|
|
286
|
-
// When the explicit ask might not match the real need
|
|
287
|
-
const lower = message.toLowerCase();
|
|
288
|
-
|
|
289
|
-
// "Fix the tests" when the real problem might be the code, not the tests
|
|
290
|
-
if (/fix.*(test|spec)/i.test(message) && context.recentFailures?.length) {
|
|
291
|
-
return 'May need to fix source code, not just tests';
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// "Make it work" — needs clarification
|
|
295
|
-
if (/\b(make it work|just work|get it working)\b/i.test(message)) {
|
|
296
|
-
return 'Vague success criteria — needs clarification on what "working" means';
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// "Do everything" — scope needs bounding
|
|
300
|
-
if (/\b(do everything|all of it|everything)\b/i.test(message)) {
|
|
301
|
-
return 'Unbounded scope — needs prioritization';
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function _assessUrgency(message, context) {
|
|
308
|
-
if (/\b(asap|urgent|now|immediately|hurry|quick|fast)\b/i.test(message)) return 'high';
|
|
309
|
-
if (/\b(when you get a chance|no rush|whenever|eventually)\b/i.test(message)) return 'low';
|
|
310
|
-
if (context.priorFailures >= 2) return 'high';
|
|
311
|
-
return 'medium';
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function _assessMaterial(message, context) {
|
|
315
|
-
const touchedFiles = context.files || [];
|
|
316
|
-
const fragileAreas = [];
|
|
317
|
-
const existingPatterns = context.patterns || [];
|
|
318
|
-
|
|
319
|
-
// Detect fragile areas from file paths
|
|
320
|
-
for (const f of touchedFiles) {
|
|
321
|
-
if (/auth|session|token|secret|credential/i.test(f)) fragileAreas.push({ file: f, reason: 'security-sensitive' });
|
|
322
|
-
if (/migration|schema|database/i.test(f)) fragileAreas.push({ file: f, reason: 'data-layer' });
|
|
323
|
-
if (/config|env|settings/i.test(f)) fragileAreas.push({ file: f, reason: 'configuration' });
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const value = fragileAreas.length >= 2 ? 'high'
|
|
327
|
-
: fragileAreas.length >= 1 ? 'medium'
|
|
328
|
-
: touchedFiles.length > 5 ? 'medium'
|
|
329
|
-
: 'low';
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
touchedFiles,
|
|
333
|
-
fragileAreas,
|
|
334
|
-
existingPatterns,
|
|
335
|
-
value,
|
|
336
|
-
userOwnedChanges: context.uncommittedFiles || [],
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function _assessRelationship(message, context, taskShape) {
|
|
341
|
-
// Should HEAD ask before acting?
|
|
342
|
-
const shouldAsk = taskShape.ambiguity === 'high'
|
|
343
|
-
|| taskShape.risk === 'critical'
|
|
344
|
-
|| taskShape.reversibility === 'hard'
|
|
345
|
-
|| (taskShape.scope === 'large' && taskShape.ambiguity !== 'low');
|
|
346
|
-
|
|
347
|
-
// Is there likely a mismatch between what was asked and what's needed?
|
|
348
|
-
const likelyMismatch = !!(
|
|
349
|
-
(taskShape.type === 'debug' && context.priorFailures >= 2)
|
|
350
|
-
|| (taskShape.ambiguity === 'high' && taskShape.risk !== 'low')
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
// Might the user be assuming something wrong?
|
|
354
|
-
const wrongAssumption = !!(
|
|
355
|
-
context.staleContext
|
|
356
|
-
|| (context.priorFailures >= 2 && taskShape.type === 'debug')
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
return { shouldAsk, likelyMismatch, wrongAssumption };
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ── UncertaintyLedger: what HEAD knows vs doesn't ──────────────────────────
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Build an uncertainty ledger from the situation model.
|
|
366
|
-
* Each entry: a claim, how confident HEAD is, what the evidence is,
|
|
367
|
-
* and what would change HEAD's mind.
|
|
368
|
-
*/
|
|
369
|
-
export function assessUncertainty(situation, context = {}) {
|
|
370
|
-
let score = 0.8; // default: reasonably confident
|
|
371
|
-
let blocker = null;
|
|
372
|
-
let shouldVerify = false;
|
|
373
|
-
|
|
374
|
-
// Confidence drops
|
|
375
|
-
if (situation.taskShape.ambiguity === 'high') score -= 0.3;
|
|
376
|
-
else if (situation.taskShape.ambiguity === 'medium') score -= 0.1;
|
|
377
|
-
|
|
378
|
-
if (situation.priorFailures >= 2) { score -= 0.3; blocker = 'Repeated failures suggest wrong approach'; }
|
|
379
|
-
else if (situation.priorFailures >= 1) score -= 0.15;
|
|
380
|
-
|
|
381
|
-
if (situation.material.fragileAreas.length > 0) { score -= 0.15; shouldVerify = true; }
|
|
382
|
-
if (situation.taskShape.scope === 'large') { score -= 0.1; shouldVerify = true; }
|
|
383
|
-
if (!situation.material.touchedFiles.length && situation.taskShape.type === 'edit') { score -= 0.2; blocker = blocker || 'No files identified for edit task'; }
|
|
384
|
-
if (context.contextAge === 'stale') score -= 0.2;
|
|
385
|
-
if (situation.inferredGoal) score -= 0.15;
|
|
386
|
-
|
|
387
|
-
score = Math.max(0.1, Math.min(1.0, score));
|
|
388
|
-
|
|
389
|
-
// Return same interface summarizeConfidence produces (so callers don't break)
|
|
390
|
-
return [{
|
|
391
|
-
claim: blocker || 'Task assessment',
|
|
392
|
-
confidence: score,
|
|
393
|
-
basis: `score=${score.toFixed(2)}`,
|
|
394
|
-
wouldChangeIf: blocker ? 'Different approach tried' : 'n/a',
|
|
395
|
-
}];
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Overall confidence from the uncertainty ledger.
|
|
400
|
-
* Not a boolean — a nuanced picture.
|
|
401
|
-
*/
|
|
402
|
-
export function summarizeConfidence(ledger) {
|
|
403
|
-
if (ledger.length === 0) return { level: 'sufficient', score: 0.8, gaps: [] };
|
|
404
|
-
|
|
405
|
-
const avg = ledger.reduce((sum, e) => sum + e.confidence, 0) / ledger.length;
|
|
406
|
-
const gaps = ledger.filter(e => e.confidence < 0.5);
|
|
407
|
-
const blockers = ledger.filter(e => e.confidence < 0.3);
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
level: blockers.length > 0 ? 'insufficient' : gaps.length > 0 ? 'partial' : 'sufficient',
|
|
411
|
-
score: Math.round(avg * 100) / 100,
|
|
412
|
-
gaps: gaps.map(g => g.claim),
|
|
413
|
-
blockers: blockers.map(b => ({ claim: b.claim, wouldResolve: b.wouldChangeIf })),
|
|
414
|
-
entryCount: ledger.length,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// ── CareObligations: what HEAD is responsible for ──────────────────────────
|
|
419
|
-
|
|
420
|
-
const OBLIGATION_TYPES = {
|
|
421
|
-
preserveWork: { priority: 'critical', description: 'Don\'t destroy the user\'s uncommitted work' },
|
|
422
|
-
respectPatterns: { priority: 'high', description: 'Follow the codebase\'s existing patterns and conventions' },
|
|
423
|
-
minimizeBlast: { priority: 'high', description: 'Keep changes as small and focused as possible' },
|
|
424
|
-
verifyBeforeClaim:{ priority: 'high', description: 'Don\'t claim success without evidence' },
|
|
425
|
-
askBeforeIrreversi:{ priority: 'critical', description: 'Get permission before irreversible actions' },
|
|
426
|
-
distinguishIntent:{ priority: 'medium', description: 'Separate what the user asked from what might also be useful' },
|
|
427
|
-
protectSecrets: { priority: 'critical', description: 'Never expose, log, or transmit secrets' },
|
|
428
|
-
honestLimits: { priority: 'high', description: 'Admit when you don\'t know or aren\'t sure' },
|
|
429
|
-
contextCare: { priority: 'medium', description: 'Be economical with context — don\'t waste tokens on ceremony' },
|
|
430
|
-
timingAwareness: { priority: 'medium', description: 'Sense whether now is the right time to surface something' },
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Derive which care obligations are active given the current situation.
|
|
435
|
-
*/
|
|
436
|
-
export function deriveObligations(situation) {
|
|
437
|
-
const active = [];
|
|
438
|
-
|
|
439
|
-
// Always active
|
|
440
|
-
active.push({ ...OBLIGATION_TYPES.protectSecrets, type: 'protectSecrets', trigger: 'always' });
|
|
441
|
-
active.push({ ...OBLIGATION_TYPES.honestLimits, type: 'honestLimits', trigger: 'always' });
|
|
442
|
-
active.push({ ...OBLIGATION_TYPES.contextCare, type: 'contextCare', trigger: 'always' });
|
|
443
|
-
|
|
444
|
-
// Conditional obligations
|
|
445
|
-
if (situation.material.userOwnedChanges?.length > 0) {
|
|
446
|
-
active.push({ ...OBLIGATION_TYPES.preserveWork, type: 'preserveWork', trigger: `${situation.material.userOwnedChanges.length} uncommitted files` });
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (situation.material.existingPatterns?.length > 0) {
|
|
450
|
-
active.push({ ...OBLIGATION_TYPES.respectPatterns, type: 'respectPatterns', trigger: `${situation.material.existingPatterns.length} existing patterns detected` });
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (situation.taskShape.scope !== 'small' || situation.taskShape.risk !== 'low') {
|
|
454
|
-
active.push({ ...OBLIGATION_TYPES.minimizeBlast, type: 'minimizeBlast', trigger: `scope=${situation.taskShape.scope}, risk=${situation.taskShape.risk}` });
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (situation.taskShape.type === 'edit' || situation.taskShape.type === 'debug') {
|
|
458
|
-
active.push({ ...OBLIGATION_TYPES.verifyBeforeClaim, type: 'verifyBeforeClaim', trigger: `task type: ${situation.taskShape.type}` });
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (situation.taskShape.reversibility === 'hard' || situation.taskShape.risk === 'critical') {
|
|
462
|
-
active.push({ ...OBLIGATION_TYPES.askBeforeIrreversi, type: 'askBeforeIrreversi', trigger: `reversibility=${situation.taskShape.reversibility}, risk=${situation.taskShape.risk}` });
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (situation.inferredGoal) {
|
|
466
|
-
active.push({ ...OBLIGATION_TYPES.distinguishIntent, type: 'distinguishIntent', trigger: `inferred goal differs: "${situation.inferredGoal}"` });
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return active;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// ── Turn history query ────────────────────────────────────────────────────────
|
|
473
|
-
|
|
474
|
-
export function queryRecentTurns(state, n = 3) {
|
|
475
|
-
if (!state.turns?.length) return { count: 0, lastActions: [], failureStreak: 0, sameActionCount: 0 };
|
|
476
|
-
|
|
477
|
-
const recent = state.turns.slice(-n);
|
|
478
|
-
const lastActions = recent.map(t => t.action);
|
|
479
|
-
|
|
480
|
-
// Detect same action repeated
|
|
481
|
-
const lastAction = lastActions[lastActions.length - 1];
|
|
482
|
-
const sameActionCount = lastActions.filter(a => a === lastAction).length;
|
|
483
|
-
|
|
484
|
-
// Detect failure streak (low confidence runs)
|
|
485
|
-
const failureStreak = [...recent].reverse().findIndex(t => t.confidence > 0.6);
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
count: state.turns.length,
|
|
489
|
-
lastActions,
|
|
490
|
-
failureStreak: failureStreak === -1 ? recent.length : failureStreak,
|
|
491
|
-
sameActionCount,
|
|
492
|
-
avgConfidence: recent.reduce((s, t) => s + (t.confidence || 0), 0) / recent.length,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ── Noticings: what HEAD observes passively ─────────────────────────────────
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Passive observation layer.
|
|
500
|
-
* Runs on every turn — detects things the user hasn't asked about.
|
|
501
|
-
* Noticings are internal. Deliberation decides whether to surface them.
|
|
502
|
-
*/
|
|
503
|
-
export function notice(situation, state, context = {}) {
|
|
504
|
-
const noticings = [];
|
|
505
|
-
|
|
506
|
-
// Self-awareness: detect stuck patterns from turn history
|
|
507
|
-
const turnHistory = queryRecentTurns(state);
|
|
508
|
-
if (turnHistory.sameActionCount >= 3) {
|
|
509
|
-
noticings.push({
|
|
510
|
-
type: 'self-awareness',
|
|
511
|
-
severity: 'high',
|
|
512
|
-
observation: `Same action "${turnHistory.lastActions[turnHistory.lastActions.length-1]}" repeated ${turnHistory.sameActionCount} times — may be stuck`,
|
|
513
|
-
shouldSurface: true,
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
if (turnHistory.failureStreak >= 2) {
|
|
517
|
-
noticings.push({
|
|
518
|
-
type: 'self-awareness',
|
|
519
|
-
severity: 'medium',
|
|
520
|
-
observation: `${turnHistory.failureStreak} turns with low confidence — consider changing approach`,
|
|
521
|
-
shouldSurface: true,
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Drift: are we doing something different from what was discussed?
|
|
526
|
-
if (state.declaredGoal && situation.inferredGoal && state.declaredGoal !== situation.inferredGoal) {
|
|
527
|
-
noticings.push({
|
|
528
|
-
type: 'drift',
|
|
529
|
-
severity: 'medium',
|
|
530
|
-
observation: `Started with "${state.declaredGoal}" but current request implies "${situation.inferredGoal}"`,
|
|
531
|
-
shouldSurface: true,
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Repeated failure: same approach failing
|
|
536
|
-
if (situation.priorFailures >= 2) {
|
|
537
|
-
noticings.push({
|
|
538
|
-
type: 'pattern',
|
|
539
|
-
severity: 'high',
|
|
540
|
-
observation: `${situation.priorFailures} prior failures — the approach may be wrong, not just the execution`,
|
|
541
|
-
shouldSurface: true,
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Fragile area being touched without tests
|
|
546
|
-
for (const area of situation.material.fragileAreas) {
|
|
547
|
-
if (!context.hasTests?.[area.file]) {
|
|
548
|
-
noticings.push({
|
|
549
|
-
type: 'risk',
|
|
550
|
-
severity: 'medium',
|
|
551
|
-
observation: `${area.file} is ${area.reason} but has no test coverage`,
|
|
552
|
-
shouldSurface: situation.taskShape.type === 'edit',
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Context getting large
|
|
558
|
-
if (state.contextEstimate?.estimatedTokens > 120_000) {
|
|
559
|
-
const pct = Math.round((state.contextEstimate.estimatedTokens / 200_000) * 100);
|
|
560
|
-
noticings.push({
|
|
561
|
-
type: 'resource',
|
|
562
|
-
severity: state.contextEstimate.estimatedTokens > 160_000 ? 'high' : 'medium',
|
|
563
|
-
observation: `Context is at ~${pct}% capacity — consider wrapping up or handing off`,
|
|
564
|
-
shouldSurface: true,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Scope creep: task growing beyond original ask
|
|
569
|
-
if (state.originalScope && situation.material.touchedFiles.length > state.originalScope * 2) {
|
|
570
|
-
noticings.push({
|
|
571
|
-
type: 'scope',
|
|
572
|
-
severity: 'medium',
|
|
573
|
-
observation: `Task has grown from ${state.originalScope} to ${situation.material.touchedFiles.length} files`,
|
|
574
|
-
shouldSurface: true,
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// Stale assumptions: acting on old information
|
|
579
|
-
if (context.contextAge === 'stale') {
|
|
580
|
-
noticings.push({
|
|
581
|
-
type: 'staleness',
|
|
582
|
-
severity: 'medium',
|
|
583
|
-
observation: 'Working from potentially outdated context — files may have changed',
|
|
584
|
-
shouldSurface: situation.taskShape.risk !== 'low',
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Opportunity: something useful HEAD noticed
|
|
589
|
-
if (context.opportunities?.length) {
|
|
590
|
-
for (const opp of context.opportunities.slice(0, 3)) {
|
|
591
|
-
noticings.push({
|
|
592
|
-
type: 'opportunity',
|
|
593
|
-
severity: 'low',
|
|
594
|
-
observation: opp,
|
|
595
|
-
shouldSurface: false, // opportunities are internal unless deliberation promotes them
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
// Self-awareness: detect repeated dispatch failures
|
|
602
|
-
if (state.dispatches?.length >= 2) {
|
|
603
|
-
const recent = state.dispatches.slice(-3);
|
|
604
|
-
const failures = recent.filter(d => d.outcome === 'failure');
|
|
605
|
-
if (failures.length >= 2) {
|
|
606
|
-
noticings.push({
|
|
607
|
-
type: 'self-awareness',
|
|
608
|
-
severity: 'high',
|
|
609
|
-
observation: `${failures.length} of last ${recent.length} dispatches failed — approach may need to change`,
|
|
610
|
-
shouldSurface: true,
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Diagnostic companion: feed tool-call pattern observations into deliberation
|
|
616
|
-
try {
|
|
617
|
-
const diagnosticNoticings = readDiagnosticNoticings();
|
|
618
|
-
for (const dn of diagnosticNoticings) {
|
|
619
|
-
noticings.push({
|
|
620
|
-
type: 'diagnostic',
|
|
621
|
-
severity: dn.severity || 'medium',
|
|
622
|
-
observation: dn.observation,
|
|
623
|
-
shouldSurface: dn.severity === 'high',
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
} catch {}
|
|
627
|
-
|
|
628
|
-
return noticings;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// ── Deliberation: what HEAD decides to do ──────────────────────────────────
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Full deliberation pipeline.
|
|
635
|
-
* Produces a structured decision with rationale — not just an action label.
|
|
636
|
-
*/
|
|
637
|
-
export function deliberate(situation, uncertaintyLedger, obligations, noticings, state) {
|
|
638
|
-
const depth = assessDepth(situation);
|
|
639
|
-
const confidence = summarizeConfidence(uncertaintyLedger);
|
|
640
|
-
|
|
641
|
-
// ── Reflexive: instant response, minimal processing
|
|
642
|
-
if (depth === 'reflexive' && confidence.level === 'sufficient') {
|
|
643
|
-
return {
|
|
644
|
-
depth,
|
|
645
|
-
action: _reflexiveAction(situation),
|
|
646
|
-
rationale: 'Simple request with sufficient confidence',
|
|
647
|
-
confidence,
|
|
648
|
-
obligations: obligations.filter(o => o.priority === 'critical'),
|
|
649
|
-
surfaceNoticings: [],
|
|
650
|
-
shouldAskUser: false,
|
|
651
|
-
uncertainties: [],
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// ── Which noticings to surface?
|
|
656
|
-
const surfaceNoticings = noticings.filter(n => {
|
|
657
|
-
if (!n.shouldSurface) return false;
|
|
658
|
-
// Care obligation: timingAwareness — only surface if it's relevant right now
|
|
659
|
-
if (n.type === 'opportunity') return situation.taskShape.type === 'plan';
|
|
660
|
-
if (n.severity === 'high') return true;
|
|
661
|
-
if (n.severity === 'medium' && depth !== 'light') return true;
|
|
662
|
-
return false;
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
// ── Should HEAD ask the user before acting?
|
|
666
|
-
const shouldAskUser = _shouldAsk(situation, confidence, obligations, depth);
|
|
667
|
-
|
|
668
|
-
// ── Generate candidate actions
|
|
669
|
-
const candidates = _generateCandidates(situation, confidence, obligations);
|
|
670
|
-
|
|
671
|
-
// ── Select best action through obligation-weighted judgment
|
|
672
|
-
const chosen = _selectAction(candidates, obligations, confidence, situation);
|
|
673
|
-
|
|
674
|
-
// ── Build rationale
|
|
675
|
-
const rationale = _buildRationale(chosen, situation, confidence, obligations, surfaceNoticings);
|
|
676
|
-
|
|
677
|
-
return {
|
|
678
|
-
depth,
|
|
679
|
-
action: chosen,
|
|
680
|
-
rationale,
|
|
681
|
-
confidence,
|
|
682
|
-
obligations: obligations.filter(o => o.priority === 'critical' || o.priority === 'high'),
|
|
683
|
-
surfaceNoticings,
|
|
684
|
-
shouldAskUser,
|
|
685
|
-
uncertainties: confidence.gaps,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
function _reflexiveAction(situation) {
|
|
690
|
-
if (situation.isQuestion && situation.taskShape.type === 'answer') {
|
|
691
|
-
return { type: 'respond', mode: 'direct' };
|
|
692
|
-
}
|
|
693
|
-
if (situation.isShort && /^(yes|y|ok|go|do it|sure|approved)\s*$/i.test(situation.raw.trim())) {
|
|
694
|
-
return { type: 'proceed', mode: 'approved' };
|
|
695
|
-
}
|
|
696
|
-
if (situation.isShort && /^(no|stop|wait|hold)\s*$/i.test(situation.raw.trim())) {
|
|
697
|
-
return { type: 'pause', mode: 'correction' };
|
|
698
|
-
}
|
|
699
|
-
return { type: 'respond', mode: 'direct' };
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function _shouldAsk(situation, confidence, obligations, depth) {
|
|
703
|
-
// Obligation-driven: ask before irreversible
|
|
704
|
-
if (obligations.some(o => o.type === 'askBeforeIrreversi')) return true;
|
|
705
|
-
|
|
706
|
-
// Confidence-driven: ask when insufficient
|
|
707
|
-
if (confidence.blockers?.length > 0) return true;
|
|
708
|
-
|
|
709
|
-
// Relationship-driven: user signals suggest asking
|
|
710
|
-
if (situation.relationship.shouldAsk) return true;
|
|
711
|
-
|
|
712
|
-
// Depth-driven: deep deliberation means this is complex enough to check
|
|
713
|
-
if (depth === 'deep' && confidence.level !== 'sufficient') return true;
|
|
714
|
-
|
|
715
|
-
// Intent mismatch: what user asked might not be what they need
|
|
716
|
-
if (situation.relationship.likelyMismatch) return true;
|
|
717
|
-
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
function _generateCandidates(situation, confidence, obligations) {
|
|
722
|
-
const candidates = [];
|
|
723
|
-
|
|
724
|
-
// Direct response (answer/explain)
|
|
725
|
-
if (situation.taskShape.type === 'answer' || situation.taskShape.type === 'research') {
|
|
726
|
-
candidates.push({
|
|
727
|
-
type: 'respond',
|
|
728
|
-
mode: 'direct',
|
|
729
|
-
fitness: situation.isQuestion ? 0.9 : 0.6,
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Dispatch to worker agent
|
|
734
|
-
if (['edit', 'debug', 'review'].includes(situation.taskShape.type)) {
|
|
735
|
-
candidates.push({
|
|
736
|
-
type: 'dispatch',
|
|
737
|
-
mode: situation.taskShape.type,
|
|
738
|
-
fitness: confidence.level === 'sufficient' ? 0.85 : 0.4,
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// Plan first, then dispatch
|
|
743
|
-
if (situation.taskShape.scope !== 'small' || situation.taskShape.ambiguity !== 'low') {
|
|
744
|
-
candidates.push({
|
|
745
|
-
type: 'plan',
|
|
746
|
-
mode: 'structured',
|
|
747
|
-
fitness: situation.taskShape.ambiguity === 'high' ? 0.9 : 0.6,
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// Clarify with user
|
|
752
|
-
if (confidence.level === 'insufficient' || situation.relationship.wrongAssumption) {
|
|
753
|
-
candidates.push({
|
|
754
|
-
type: 'clarify',
|
|
755
|
-
mode: 'question',
|
|
756
|
-
fitness: confidence.level === 'insufficient' ? 0.95 : 0.7,
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// Think/discuss (architecture, design)
|
|
761
|
-
if (situation.taskShape.type === 'plan') {
|
|
762
|
-
candidates.push({
|
|
763
|
-
type: 'think',
|
|
764
|
-
mode: 'architecture',
|
|
765
|
-
fitness: 0.85,
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// Proceed (user gave approval)
|
|
770
|
-
if (situation.isShort && /^(yes|y|ok|go|do it|sure|approved)\s*$/i.test(situation.raw.trim())) {
|
|
771
|
-
candidates.push({
|
|
772
|
-
type: 'proceed',
|
|
773
|
-
mode: 'approved',
|
|
774
|
-
fitness: 0.95,
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
return candidates;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
function _selectAction(candidates, obligations, confidence, situation) {
|
|
782
|
-
if (candidates.length === 0) return { type: 'clarify', mode: 'no-candidates' };
|
|
783
|
-
|
|
784
|
-
// Apply obligation penalties
|
|
785
|
-
for (const c of candidates) {
|
|
786
|
-
// Dispatch penalty when confidence is low
|
|
787
|
-
if (c.type === 'dispatch' && confidence.level !== 'sufficient') {
|
|
788
|
-
c.fitness *= 0.5;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Dispatch penalty when irreversible and no approval
|
|
792
|
-
if (c.type === 'dispatch' && obligations.some(o => o.type === 'askBeforeIrreversi')) {
|
|
793
|
-
c.fitness *= 0.3;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Plan bonus when scope is large
|
|
797
|
-
if (c.type === 'plan' && situation.taskShape.scope === 'large') {
|
|
798
|
-
c.fitness *= 1.3;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Clarify bonus when goal is mismatched
|
|
802
|
-
if (c.type === 'clarify' && situation.relationship.likelyMismatch) {
|
|
803
|
-
c.fitness *= 1.5;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Sort by fitness, pick best
|
|
808
|
-
candidates.sort((a, b) => b.fitness - a.fitness);
|
|
809
|
-
return candidates[0];
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
function _buildRationale(action, situation, confidence, obligations, noticings) {
|
|
813
|
-
const parts = [];
|
|
814
|
-
|
|
815
|
-
parts.push(`Action: ${action.type} (${action.mode})`);
|
|
816
|
-
|
|
817
|
-
if (confidence.level !== 'sufficient') {
|
|
818
|
-
parts.push(`Confidence: ${confidence.level} (${confidence.score}) — ${confidence.gaps.length} gap(s)`);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const criticalObligations = obligations.filter(o => o.priority === 'critical');
|
|
822
|
-
if (criticalObligations.length > 0) {
|
|
823
|
-
parts.push(`Critical obligations: ${criticalObligations.map(o => o.type).join(', ')}`);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
if (noticings.length > 0) {
|
|
827
|
-
parts.push(`Surfacing ${noticings.length} noticing(s)`);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
if (situation.inferredGoal) {
|
|
831
|
-
parts.push(`Note: inferred goal may differ — "${situation.inferredGoal}"`);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
return parts.join('. ');
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// ── Full turn processor ─────────────────────────────────────────────────────
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Process a complete turn through the cognitive pipeline.
|
|
841
|
-
* This replaces the old processTurn — same interface, fundamentally different internals.
|
|
842
|
-
*/
|
|
843
|
-
export function processTurn(state, userMessage, context = {}) {
|
|
844
|
-
// 1. Perceive the situation
|
|
845
|
-
const situation = perceive(userMessage, context);
|
|
846
|
-
|
|
847
|
-
// 2. Assess depth — how much thinking does this deserve?
|
|
848
|
-
const depth = assessDepth(situation);
|
|
849
|
-
|
|
850
|
-
// 3. Build uncertainty ledger
|
|
851
|
-
const uncertainties = assessUncertainty(situation, context);
|
|
852
|
-
|
|
853
|
-
// 4. Derive care obligations
|
|
854
|
-
const obligations = deriveObligations(situation);
|
|
855
|
-
|
|
856
|
-
// 5. Passive noticing
|
|
857
|
-
const noticings = notice(situation, state, context);
|
|
858
|
-
|
|
859
|
-
// 6. Deliberate
|
|
860
|
-
const result = deliberate(situation, uncertainties, obligations, noticings, state);
|
|
861
|
-
|
|
862
|
-
// Update state
|
|
863
|
-
state.lastActivity = Date.now();
|
|
864
|
-
if (!state.declaredGoal && situation.taskShape.type !== 'answer') {
|
|
865
|
-
state.declaredGoal = situation.explicitAsk.slice(0, 200);
|
|
866
|
-
state.originalScope = situation.material.touchedFiles.length || 1;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Track the turn
|
|
870
|
-
if (!state.turns) state.turns = [];
|
|
871
|
-
state.turns.push({
|
|
872
|
-
timestamp: Date.now(),
|
|
873
|
-
depth: result.depth,
|
|
874
|
-
action: result.action.type,
|
|
875
|
-
confidence: result.confidence.score,
|
|
876
|
-
obligationCount: result.obligations.length,
|
|
877
|
-
noticingCount: result.surfaceNoticings.length,
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
saveState(state);
|
|
881
|
-
|
|
882
|
-
return {
|
|
883
|
-
situation,
|
|
884
|
-
depth,
|
|
885
|
-
uncertainties,
|
|
886
|
-
obligations,
|
|
887
|
-
noticings,
|
|
888
|
-
result,
|
|
889
|
-
|
|
890
|
-
// Convenience fields for callers
|
|
891
|
-
shouldAskUser: result.shouldAskUser,
|
|
892
|
-
shouldDispatch: result.action.type === 'dispatch' || result.action.type === 'proceed',
|
|
893
|
-
shouldClarify: result.action.type === 'clarify',
|
|
894
|
-
shouldThink: result.action.type === 'think' || result.action.type === 'plan',
|
|
895
|
-
action: result.action,
|
|
896
|
-
rationale: result.rationale,
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// ── State persistence ───────────────────────────────────────────────────────
|
|
901
|
-
|
|
902
|
-
export function loadState() {
|
|
903
|
-
try {
|
|
904
|
-
if (existsSync(STATE_FILE)) {
|
|
905
|
-
const data = JSON.parse(readFileSync(STATE_FILE, 'utf8'));
|
|
906
|
-
if (Date.now() - (data.lastActivity || 0) > 30 * 60 * 1000) {
|
|
907
|
-
return freshState();
|
|
908
|
-
}
|
|
909
|
-
return data;
|
|
910
|
-
}
|
|
911
|
-
} catch {}
|
|
912
|
-
return freshState();
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
export function freshState() {
|
|
916
|
-
return {
|
|
917
|
-
sessionId: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
918
|
-
declaredGoal: null,
|
|
919
|
-
originalScope: null,
|
|
920
|
-
turns: [],
|
|
921
|
-
dispatches: [], // { ts, type, objective, outcome, durationMs }
|
|
922
|
-
contextEstimate: { messages: 0, estimatedTokens: 0 },
|
|
923
|
-
lastActivity: Date.now(),
|
|
924
|
-
created: Date.now(),
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
export function saveState(state) {
|
|
929
|
-
state.lastActivity = Date.now();
|
|
930
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
931
|
-
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
export function recordDispatchOutcome(state, outcome) {
|
|
935
|
-
if (!state.dispatches) state.dispatches = [];
|
|
936
|
-
state.dispatches.push({
|
|
937
|
-
ts: Date.now(),
|
|
938
|
-
type: outcome.type || 'unknown',
|
|
939
|
-
objective: (outcome.objective || '').slice(0, 100),
|
|
940
|
-
outcome: outcome.status || 'unknown', // 'success' | 'failure' | 'partial'
|
|
941
|
-
durationMs: outcome.durationMs || 0,
|
|
942
|
-
});
|
|
943
|
-
// Keep last 10 dispatches only
|
|
944
|
-
if (state.dispatches.length > 10) state.dispatches = state.dispatches.slice(-10);
|
|
945
|
-
saveState(state);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
949
|
-
// Core pipeline: perceive, assessUncertainty, deriveObligations, notice, deliberate
|
|
950
|
-
// Convenience: processTurn, assessDepth, summarizeConfidence, queryRecentTurns
|
|
951
|
-
// State: loadState, freshState, saveState
|
|
952
|
-
// Values: HEAD_VALUES
|