dual-brain 0.2.30 → 0.3.0
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/task-classifier.mjs +328 -0
- package/hooks/vibe-router.mjs +387 -0
- 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/dist/src/head.js
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
// @ts-ignore — diagnostic-companion.mjs not yet migrated
|
|
4
|
+
import { readDiagnosticNoticings } from '../hooks/diagnostic-companion.mjs';
|
|
5
|
+
const STATE_DIR = join(process.cwd(), '.dualbrain');
|
|
6
|
+
const STATE_FILE = join(STATE_DIR, 'head-state.json');
|
|
7
|
+
// ── Values: these shape judgment, not rules to check ────────────────────────
|
|
8
|
+
export const HEAD_VALUES = {
|
|
9
|
+
selfHonesty: 'Say what you don\'t know. Never dress up guesses as facts.',
|
|
10
|
+
materialCare: 'The user\'s code, context, and time are precious. Don\'t waste them.',
|
|
11
|
+
curiosity: 'Notice what\'s off. Ask what you\'re not seeing.',
|
|
12
|
+
strategicPace: 'Know when to act fast and when to slow down.',
|
|
13
|
+
proactivity: 'Surface things the user should know, but only when it matters.',
|
|
14
|
+
restraint: 'Can do ≠ should do. Permission ≠ wisdom.',
|
|
15
|
+
honesty: 'Be honest about the material — its quality, risks, and gaps.',
|
|
16
|
+
consideration: 'Think about the user\'s actual situation, not the abstract task.',
|
|
17
|
+
};
|
|
18
|
+
const DEPTH_SIGNALS = {
|
|
19
|
+
ambiguity: { weight: 3, test: (s) => s.ambiguity },
|
|
20
|
+
risk: { weight: 4, test: (s) => s.risk },
|
|
21
|
+
irreversibility: { weight: 4, test: (s) => s.reversibility === 'hard' ? 'high' : s.reversibility === 'moderate' ? 'medium' : 'low' },
|
|
22
|
+
scope: { weight: 2, test: (s) => s.scope === 'large' ? 'high' : s.scope === 'medium' ? 'medium' : 'low' },
|
|
23
|
+
priorFailures: { weight: 3, test: (s) => (s.priorFailures || 0) >= 2 ? 'high' : (s.priorFailures || 0) >= 1 ? 'medium' : 'low' },
|
|
24
|
+
novelty: { weight: 2, test: (s) => s.novelty },
|
|
25
|
+
materialValue: { weight: 3, test: (s) => s.materialValue },
|
|
26
|
+
userStress: { weight: 2, test: (s) => s.userStress },
|
|
27
|
+
contextVolatility: { weight: 1, test: (s) => s.contextVolatility },
|
|
28
|
+
};
|
|
29
|
+
const LEVEL_SCORES = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
30
|
+
/**
|
|
31
|
+
* Assess how much deliberation this situation deserves.
|
|
32
|
+
* Returns 'reflexive' | 'light' | 'full' | 'deep'
|
|
33
|
+
*/
|
|
34
|
+
export function assessDepth(signals) {
|
|
35
|
+
let score = 0;
|
|
36
|
+
for (const [, cfg] of Object.entries(DEPTH_SIGNALS)) {
|
|
37
|
+
const level = cfg.test(signals) || 'low';
|
|
38
|
+
score += (LEVEL_SCORES[level] || 0) * cfg.weight;
|
|
39
|
+
}
|
|
40
|
+
// Task type floor: work requests are never reflexive
|
|
41
|
+
const taskType = signals.taskShape?.type || signals.type;
|
|
42
|
+
const isWorkRequest = ['edit', 'debug', 'review', 'research'].includes(taskType || '');
|
|
43
|
+
if (isWorkRequest && score < 3)
|
|
44
|
+
score = 3;
|
|
45
|
+
if (score <= 2)
|
|
46
|
+
return 'reflexive';
|
|
47
|
+
if (score <= 8)
|
|
48
|
+
return 'light';
|
|
49
|
+
if (score <= 18)
|
|
50
|
+
return 'full';
|
|
51
|
+
return 'deep';
|
|
52
|
+
}
|
|
53
|
+
// ── SituationModel: what's happening ────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Build a situation model from user input and context.
|
|
56
|
+
*/
|
|
57
|
+
export function perceive(message, context = {}) {
|
|
58
|
+
const words = message.trim().split(/\s+/);
|
|
59
|
+
const isQuestion = /\?\s*$/.test(message.trim());
|
|
60
|
+
const isShort = words.length <= 5;
|
|
61
|
+
const taskShape = _inferTaskShape(message, context);
|
|
62
|
+
const inferredGoal = _inferGoal(message, context);
|
|
63
|
+
const urgency = _assessUrgency(message, context);
|
|
64
|
+
const material = _assessMaterial(message, context);
|
|
65
|
+
const relationship = _assessRelationship(message, context, taskShape);
|
|
66
|
+
const mode = detectMode(message, context);
|
|
67
|
+
return {
|
|
68
|
+
raw: message,
|
|
69
|
+
explicitAsk: message.trim(),
|
|
70
|
+
inferredGoal,
|
|
71
|
+
urgency,
|
|
72
|
+
isQuestion,
|
|
73
|
+
isShort,
|
|
74
|
+
taskShape,
|
|
75
|
+
material,
|
|
76
|
+
relationship,
|
|
77
|
+
mode,
|
|
78
|
+
ambiguity: taskShape.ambiguity,
|
|
79
|
+
risk: taskShape.risk,
|
|
80
|
+
reversibility: taskShape.reversibility,
|
|
81
|
+
scope: taskShape.scope,
|
|
82
|
+
novelty: context.novelty || 'low',
|
|
83
|
+
materialValue: material.value,
|
|
84
|
+
userStress: urgency === 'high' ? 'high' : 'low',
|
|
85
|
+
contextVolatility: context.volatility || 'low',
|
|
86
|
+
priorFailures: context.priorFailures || 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ── Mode Sensing ────────────────────────────────────────────────────────────
|
|
90
|
+
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;
|
|
91
|
+
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;
|
|
92
|
+
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;
|
|
93
|
+
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;
|
|
94
|
+
const MODE_WORK_SIGNALS = /(`[^`]+`|\.mjs|\.ts|\.js|\.py|src\/|hooks\/|bin\/|\bfunction\b|\bclass\b|\bimport\b)/;
|
|
95
|
+
/**
|
|
96
|
+
* Detect user's conversational mode from message signals.
|
|
97
|
+
*/
|
|
98
|
+
export function detectMode(message, context = {}) {
|
|
99
|
+
const scores = { execute: 0, ideate: 0, work: 0, explore: 0, discuss: 0 };
|
|
100
|
+
const signals = [];
|
|
101
|
+
const words = message.trim().split(/\s+/);
|
|
102
|
+
const len = words.length;
|
|
103
|
+
// ── Length signal
|
|
104
|
+
if (len <= 4) {
|
|
105
|
+
scores.execute += 3;
|
|
106
|
+
signals.push('very-short');
|
|
107
|
+
}
|
|
108
|
+
else if (len <= 10) {
|
|
109
|
+
scores.execute += 1;
|
|
110
|
+
scores.work += 1;
|
|
111
|
+
}
|
|
112
|
+
else if (len >= 80) {
|
|
113
|
+
scores.ideate += 2;
|
|
114
|
+
signals.push('long-message');
|
|
115
|
+
}
|
|
116
|
+
else if (len >= 40) {
|
|
117
|
+
scores.ideate += 1;
|
|
118
|
+
scores.discuss += 1;
|
|
119
|
+
}
|
|
120
|
+
// ── Lexical signals
|
|
121
|
+
if (MODE_EXECUTE_WORDS.test(message.trim())) {
|
|
122
|
+
scores.execute += 4;
|
|
123
|
+
signals.push('execute-word');
|
|
124
|
+
}
|
|
125
|
+
if (MODE_IDEATE_WORDS.test(message)) {
|
|
126
|
+
scores.ideate += 3;
|
|
127
|
+
signals.push('ideate-word');
|
|
128
|
+
}
|
|
129
|
+
if (MODE_EXPLORE_WORDS.test(message)) {
|
|
130
|
+
scores.explore += 3;
|
|
131
|
+
signals.push('explore-word');
|
|
132
|
+
}
|
|
133
|
+
if (MODE_DISCUSS_WORDS.test(message)) {
|
|
134
|
+
scores.discuss += 4;
|
|
135
|
+
signals.push('discuss-word');
|
|
136
|
+
}
|
|
137
|
+
// ── Specificity signal
|
|
138
|
+
const specificityMatches = message.match(MODE_WORK_SIGNALS);
|
|
139
|
+
if (specificityMatches) {
|
|
140
|
+
scores.work += 2;
|
|
141
|
+
signals.push('has-specifics');
|
|
142
|
+
if (/\b(add|change|update|refactor|fix|remove|rename|move)\b/i.test(message)) {
|
|
143
|
+
scores.work += 2;
|
|
144
|
+
signals.push('specific-action');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ── Punctuation signal
|
|
148
|
+
const questionMarks = (message.match(/\?/g) || []).length;
|
|
149
|
+
if (questionMarks >= 2) {
|
|
150
|
+
scores.discuss += 2;
|
|
151
|
+
scores.explore += 1;
|
|
152
|
+
signals.push('multi-question');
|
|
153
|
+
}
|
|
154
|
+
else if (questionMarks === 1 && len > 5) {
|
|
155
|
+
scores.explore += 1;
|
|
156
|
+
scores.discuss += 1;
|
|
157
|
+
}
|
|
158
|
+
if (/\.{3}|—|–/.test(message)) {
|
|
159
|
+
scores.ideate += 1;
|
|
160
|
+
signals.push('ellipsis-dash');
|
|
161
|
+
}
|
|
162
|
+
if (/\b(maybe|might|could|perhaps|wonder)\b/i.test(message)) {
|
|
163
|
+
scores.ideate += 1;
|
|
164
|
+
scores.discuss += 1;
|
|
165
|
+
signals.push('hedging');
|
|
166
|
+
}
|
|
167
|
+
// ── Contextual signals
|
|
168
|
+
if (context._priorWasProposal && len <= 15) {
|
|
169
|
+
scores.execute += 2;
|
|
170
|
+
signals.push('post-proposal-short');
|
|
171
|
+
}
|
|
172
|
+
if (context._isFirstTurn) {
|
|
173
|
+
scores.explore += 1;
|
|
174
|
+
scores.discuss += 1;
|
|
175
|
+
}
|
|
176
|
+
// ── Anti-signals
|
|
177
|
+
if (/^go on\b|^keep going/i.test(message.trim())) {
|
|
178
|
+
scores.execute -= 3;
|
|
179
|
+
scores.discuss += 2;
|
|
180
|
+
}
|
|
181
|
+
if (/^(actually|wait|hold on|hang on)/i.test(message.trim()) && questionMarks > 0) {
|
|
182
|
+
scores.execute -= 2;
|
|
183
|
+
scores.discuss += 3;
|
|
184
|
+
signals.push('pumping-brakes');
|
|
185
|
+
}
|
|
186
|
+
if (/what if.*(break|fail|crash|error)/i.test(message)) {
|
|
187
|
+
scores.ideate -= 2;
|
|
188
|
+
scores.work += 2;
|
|
189
|
+
}
|
|
190
|
+
if (/could we.*(refactor|change|update)/i.test(message) && specificityMatches) {
|
|
191
|
+
scores.ideate -= 2;
|
|
192
|
+
scores.work += 2;
|
|
193
|
+
}
|
|
194
|
+
// ── Resolve
|
|
195
|
+
for (const k of Object.keys(scores)) {
|
|
196
|
+
if (scores[k] < 0)
|
|
197
|
+
scores[k] = 0;
|
|
198
|
+
}
|
|
199
|
+
const total = Object.values(scores).reduce((a, b) => a + b, 0) || 1;
|
|
200
|
+
const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
201
|
+
const [primary, primaryScore] = sorted[0];
|
|
202
|
+
const confidence = primaryScore / total;
|
|
203
|
+
if (confidence < 0.35 && scores.execute > 0 && scores.discuss <= 1 && scores.ideate <= 1) {
|
|
204
|
+
return { primary: 'execute', confidence: 0.4, scores, signals: [...signals, 'low-confidence-action-bias'] };
|
|
205
|
+
}
|
|
206
|
+
return { primary, confidence, scores, signals };
|
|
207
|
+
}
|
|
208
|
+
function _inferTaskShape(message, context) {
|
|
209
|
+
const lower = message.toLowerCase();
|
|
210
|
+
const files = context.files || [];
|
|
211
|
+
const dirCount = files.filter(f => f.endsWith('/') || !f.includes('.')).length;
|
|
212
|
+
const fileCount = files.length + (dirCount * 4);
|
|
213
|
+
const scope = fileCount > 5 ? 'large' : fileCount > 2 ? 'medium' : lower.length > 500 ? 'medium' : 'small';
|
|
214
|
+
const riskSignals = [];
|
|
215
|
+
if (/\b(auth|secret|token|credential|password|key|session|permission)\b/i.test(message))
|
|
216
|
+
riskSignals.push('security-adjacent');
|
|
217
|
+
if (/\b(delete|remove|drop|destroy|reset|force|wipe)\b/i.test(message))
|
|
218
|
+
riskSignals.push('destructive-language');
|
|
219
|
+
if (/\b(deploy|publish|push|release|ship|migrate)\b/i.test(message))
|
|
220
|
+
riskSignals.push('external-effect');
|
|
221
|
+
if (/\b(database|db|schema|migration|table)\b/i.test(message))
|
|
222
|
+
riskSignals.push('data-mutation');
|
|
223
|
+
if ((context.priorFailures || 0) >= 2)
|
|
224
|
+
riskSignals.push('repeated-failure');
|
|
225
|
+
const risk = riskSignals.length >= 3 ? 'critical'
|
|
226
|
+
: riskSignals.length >= 2 ? 'high'
|
|
227
|
+
: riskSignals.length >= 1 ? 'medium'
|
|
228
|
+
: 'low';
|
|
229
|
+
const hasDestructive = riskSignals.includes('destructive-language') || riskSignals.includes('external-effect');
|
|
230
|
+
const reversibility = hasDestructive ? 'hard' : riskSignals.includes('data-mutation') ? 'moderate' : 'easy';
|
|
231
|
+
const ambiguitySignals = [];
|
|
232
|
+
if (/\b(maybe|might|could|should we|not sure|thinking about|what if|somehow)\b/i.test(message))
|
|
233
|
+
ambiguitySignals.push('hedging-language');
|
|
234
|
+
if (/\b(or|versus|vs|either|option|alternative)\b/i.test(message))
|
|
235
|
+
ambiguitySignals.push('considering-alternatives');
|
|
236
|
+
if (message.split('?').length > 2)
|
|
237
|
+
ambiguitySignals.push('multiple-questions');
|
|
238
|
+
if (!context.files?.length && /\b(it|this|that|these|those)\b/i.test(message) && !context.recentFiles?.length && !/\?\s*$/.test(message.trim()))
|
|
239
|
+
ambiguitySignals.push('vague-reference');
|
|
240
|
+
if (/\b(everything|all|entire|whole|every)\b/i.test(message))
|
|
241
|
+
ambiguitySignals.push('unbounded-scope');
|
|
242
|
+
if (/\b(better|improve|enhance|optimize|clean up)\b/i.test(message) && !context.files?.length)
|
|
243
|
+
ambiguitySignals.push('vague-goal');
|
|
244
|
+
const ambiguity = ambiguitySignals.length >= 2 ? 'high' : ambiguitySignals.length >= 1 ? 'medium' : 'low';
|
|
245
|
+
let type = 'unknown';
|
|
246
|
+
if (/\b(what|where|which|how many|show|list|find|search|explain|tell me)\b/i.test(message) || /\?\s*$/.test(message.trim()))
|
|
247
|
+
type = 'answer';
|
|
248
|
+
if (/\b(fix|bug|error|broken|crash|fail|issue|wrong)\b/i.test(message))
|
|
249
|
+
type = 'debug';
|
|
250
|
+
if (/\b(build|create|add|implement|write|make|new)\b/i.test(message))
|
|
251
|
+
type = 'edit';
|
|
252
|
+
if (/\b(review|check|audit|look at|inspect)\b/i.test(message))
|
|
253
|
+
type = 'review';
|
|
254
|
+
if (/\b(research|investigate|explore|understand|dig into)\b/i.test(message))
|
|
255
|
+
type = 'research';
|
|
256
|
+
if (/\b(plan|design|architect|strategy|approach|think about|brainstorm)\b/i.test(message))
|
|
257
|
+
type = 'plan';
|
|
258
|
+
if (/\b(refactor|clean|reorganize|restructure|simplify)\b/i.test(message))
|
|
259
|
+
type = 'edit';
|
|
260
|
+
return { type, scope, risk, reversibility, ambiguity, riskSignals, ambiguitySignals };
|
|
261
|
+
}
|
|
262
|
+
function _inferGoal(message, context) {
|
|
263
|
+
if (/fix.*(test|spec)/i.test(message) && context.recentFailures?.length) {
|
|
264
|
+
return 'May need to fix source code, not just tests';
|
|
265
|
+
}
|
|
266
|
+
if (/\b(make it work|just work|get it working)\b/i.test(message)) {
|
|
267
|
+
return 'Vague success criteria — needs clarification on what "working" means';
|
|
268
|
+
}
|
|
269
|
+
if (/\b(do everything|all of it|everything)\b/i.test(message)) {
|
|
270
|
+
return 'Unbounded scope — needs prioritization';
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
function _assessUrgency(message, context) {
|
|
275
|
+
if (/\b(asap|urgent|now|immediately|hurry|quick|fast)\b/i.test(message))
|
|
276
|
+
return 'high';
|
|
277
|
+
if (/\b(when you get a chance|no rush|whenever|eventually)\b/i.test(message))
|
|
278
|
+
return 'low';
|
|
279
|
+
if ((context.priorFailures || 0) >= 2)
|
|
280
|
+
return 'high';
|
|
281
|
+
return 'medium';
|
|
282
|
+
}
|
|
283
|
+
function _assessMaterial(message, context) {
|
|
284
|
+
const touchedFiles = context.files || [];
|
|
285
|
+
const fragileAreas = [];
|
|
286
|
+
const existingPatterns = context.patterns || [];
|
|
287
|
+
for (const f of touchedFiles) {
|
|
288
|
+
if (/auth|session|token|secret|credential/i.test(f))
|
|
289
|
+
fragileAreas.push({ file: f, reason: 'security-sensitive' });
|
|
290
|
+
if (/migration|schema|database/i.test(f))
|
|
291
|
+
fragileAreas.push({ file: f, reason: 'data-layer' });
|
|
292
|
+
if (/config|env|settings/i.test(f))
|
|
293
|
+
fragileAreas.push({ file: f, reason: 'configuration' });
|
|
294
|
+
}
|
|
295
|
+
const value = fragileAreas.length >= 2 ? 'high'
|
|
296
|
+
: fragileAreas.length >= 1 ? 'medium'
|
|
297
|
+
: touchedFiles.length > 5 ? 'medium'
|
|
298
|
+
: 'low';
|
|
299
|
+
return {
|
|
300
|
+
touchedFiles,
|
|
301
|
+
fragileAreas,
|
|
302
|
+
existingPatterns,
|
|
303
|
+
value,
|
|
304
|
+
userOwnedChanges: context.uncommittedFiles || [],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function _assessRelationship(message, context, taskShape) {
|
|
308
|
+
const shouldAsk = taskShape.ambiguity === 'high'
|
|
309
|
+
|| taskShape.risk === 'critical'
|
|
310
|
+
|| taskShape.reversibility === 'hard'
|
|
311
|
+
|| (taskShape.scope === 'large' && taskShape.ambiguity !== 'low');
|
|
312
|
+
const likelyMismatch = !!((taskShape.type === 'debug' && (context.priorFailures || 0) >= 2)
|
|
313
|
+
|| (taskShape.ambiguity === 'high' && taskShape.risk !== 'low'));
|
|
314
|
+
const wrongAssumption = !!(context.staleContext
|
|
315
|
+
|| ((context.priorFailures || 0) >= 2 && taskShape.type === 'debug'));
|
|
316
|
+
return { shouldAsk, likelyMismatch, wrongAssumption };
|
|
317
|
+
}
|
|
318
|
+
// ── UncertaintyLedger: what HEAD knows vs doesn't ──────────────────────────
|
|
319
|
+
/**
|
|
320
|
+
* Build an uncertainty ledger from the situation model.
|
|
321
|
+
*/
|
|
322
|
+
export function assessUncertainty(situation, context = {}) {
|
|
323
|
+
let score = 0.8;
|
|
324
|
+
let blocker = null;
|
|
325
|
+
let shouldVerify = false;
|
|
326
|
+
if (situation.taskShape.ambiguity === 'high')
|
|
327
|
+
score -= 0.3;
|
|
328
|
+
else if (situation.taskShape.ambiguity === 'medium')
|
|
329
|
+
score -= 0.1;
|
|
330
|
+
if (situation.priorFailures >= 2) {
|
|
331
|
+
score -= 0.3;
|
|
332
|
+
blocker = 'Repeated failures suggest wrong approach';
|
|
333
|
+
}
|
|
334
|
+
else if (situation.priorFailures >= 1)
|
|
335
|
+
score -= 0.15;
|
|
336
|
+
if (situation.material.fragileAreas.length > 0) {
|
|
337
|
+
score -= 0.15;
|
|
338
|
+
shouldVerify = true;
|
|
339
|
+
}
|
|
340
|
+
if (situation.taskShape.scope === 'large') {
|
|
341
|
+
score -= 0.1;
|
|
342
|
+
shouldVerify = true;
|
|
343
|
+
}
|
|
344
|
+
if (!situation.material.touchedFiles.length && situation.taskShape.type === 'edit') {
|
|
345
|
+
score -= 0.2;
|
|
346
|
+
blocker = blocker || 'No files identified for edit task';
|
|
347
|
+
}
|
|
348
|
+
if (context.contextAge === 'stale')
|
|
349
|
+
score -= 0.2;
|
|
350
|
+
if (situation.inferredGoal)
|
|
351
|
+
score -= 0.15;
|
|
352
|
+
score = Math.max(0.1, Math.min(1.0, score));
|
|
353
|
+
return [{
|
|
354
|
+
claim: blocker || 'Task assessment',
|
|
355
|
+
confidence: score,
|
|
356
|
+
basis: `score=${score.toFixed(2)}`,
|
|
357
|
+
wouldChangeIf: blocker ? 'Different approach tried' : 'n/a',
|
|
358
|
+
}];
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Overall confidence from the uncertainty ledger.
|
|
362
|
+
*/
|
|
363
|
+
export function summarizeConfidence(ledger) {
|
|
364
|
+
if (ledger.length === 0)
|
|
365
|
+
return { level: 'sufficient', score: 0.8, gaps: [] };
|
|
366
|
+
const avg = ledger.reduce((sum, e) => sum + e.confidence, 0) / ledger.length;
|
|
367
|
+
const gaps = ledger.filter(e => e.confidence < 0.5);
|
|
368
|
+
const blockers = ledger.filter(e => e.confidence < 0.3);
|
|
369
|
+
return {
|
|
370
|
+
level: blockers.length > 0 ? 'insufficient' : gaps.length > 0 ? 'partial' : 'sufficient',
|
|
371
|
+
score: Math.round(avg * 100) / 100,
|
|
372
|
+
gaps: gaps.map(g => g.claim),
|
|
373
|
+
blockers: blockers.map(b => ({ claim: b.claim, wouldResolve: b.wouldChangeIf })),
|
|
374
|
+
entryCount: ledger.length,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
// ── CareObligations: what HEAD is responsible for ──────────────────────────
|
|
378
|
+
const OBLIGATION_TYPES = {
|
|
379
|
+
preserveWork: { priority: 'critical', description: 'Don\'t destroy the user\'s uncommitted work' },
|
|
380
|
+
respectPatterns: { priority: 'high', description: 'Follow the codebase\'s existing patterns and conventions' },
|
|
381
|
+
minimizeBlast: { priority: 'high', description: 'Keep changes as small and focused as possible' },
|
|
382
|
+
verifyBeforeClaim: { priority: 'high', description: 'Don\'t claim success without evidence' },
|
|
383
|
+
askBeforeIrreversi: { priority: 'critical', description: 'Get permission before irreversible actions' },
|
|
384
|
+
distinguishIntent: { priority: 'medium', description: 'Separate what the user asked from what might also be useful' },
|
|
385
|
+
protectSecrets: { priority: 'critical', description: 'Never expose, log, or transmit secrets' },
|
|
386
|
+
honestLimits: { priority: 'high', description: 'Admit when you don\'t know or aren\'t sure' },
|
|
387
|
+
contextCare: { priority: 'medium', description: 'Be economical with context — don\'t waste tokens on ceremony' },
|
|
388
|
+
timingAwareness: { priority: 'medium', description: 'Sense whether now is the right time to surface something' },
|
|
389
|
+
};
|
|
390
|
+
/**
|
|
391
|
+
* Derive which care obligations are active given the current situation.
|
|
392
|
+
*/
|
|
393
|
+
export function deriveObligations(situation) {
|
|
394
|
+
const active = [];
|
|
395
|
+
// Always active
|
|
396
|
+
active.push({ ...OBLIGATION_TYPES.protectSecrets, type: 'protectSecrets', trigger: 'always' });
|
|
397
|
+
active.push({ ...OBLIGATION_TYPES.honestLimits, type: 'honestLimits', trigger: 'always' });
|
|
398
|
+
active.push({ ...OBLIGATION_TYPES.contextCare, type: 'contextCare', trigger: 'always' });
|
|
399
|
+
if (situation.material.userOwnedChanges?.length > 0) {
|
|
400
|
+
active.push({ ...OBLIGATION_TYPES.preserveWork, type: 'preserveWork', trigger: `${situation.material.userOwnedChanges.length} uncommitted files` });
|
|
401
|
+
}
|
|
402
|
+
if (situation.material.existingPatterns?.length > 0) {
|
|
403
|
+
active.push({ ...OBLIGATION_TYPES.respectPatterns, type: 'respectPatterns', trigger: `${situation.material.existingPatterns.length} existing patterns detected` });
|
|
404
|
+
}
|
|
405
|
+
if (situation.taskShape.scope !== 'small' || situation.taskShape.risk !== 'low') {
|
|
406
|
+
active.push({ ...OBLIGATION_TYPES.minimizeBlast, type: 'minimizeBlast', trigger: `scope=${situation.taskShape.scope}, risk=${situation.taskShape.risk}` });
|
|
407
|
+
}
|
|
408
|
+
if (situation.taskShape.type === 'edit' || situation.taskShape.type === 'debug') {
|
|
409
|
+
active.push({ ...OBLIGATION_TYPES.verifyBeforeClaim, type: 'verifyBeforeClaim', trigger: `task type: ${situation.taskShape.type}` });
|
|
410
|
+
}
|
|
411
|
+
if (situation.taskShape.reversibility === 'hard' || situation.taskShape.risk === 'critical') {
|
|
412
|
+
active.push({ ...OBLIGATION_TYPES.askBeforeIrreversi, type: 'askBeforeIrreversi', trigger: `reversibility=${situation.taskShape.reversibility}, risk=${situation.taskShape.risk}` });
|
|
413
|
+
}
|
|
414
|
+
if (situation.inferredGoal) {
|
|
415
|
+
active.push({ ...OBLIGATION_TYPES.distinguishIntent, type: 'distinguishIntent', trigger: `inferred goal differs: "${situation.inferredGoal}"` });
|
|
416
|
+
}
|
|
417
|
+
return active;
|
|
418
|
+
}
|
|
419
|
+
// ── Turn history query ────────────────────────────────────────────────────────
|
|
420
|
+
export function queryRecentTurns(state, n = 3) {
|
|
421
|
+
if (!state.turns?.length)
|
|
422
|
+
return { count: 0, lastActions: [], failureStreak: 0, sameActionCount: 0 };
|
|
423
|
+
const recent = state.turns.slice(-n);
|
|
424
|
+
const lastActions = recent.map(t => t.action);
|
|
425
|
+
const lastAction = lastActions[lastActions.length - 1];
|
|
426
|
+
const sameActionCount = lastActions.filter(a => a === lastAction).length;
|
|
427
|
+
const failureStreak = [...recent].reverse().findIndex(t => t.confidence > 0.6);
|
|
428
|
+
return {
|
|
429
|
+
count: state.turns.length,
|
|
430
|
+
lastActions,
|
|
431
|
+
failureStreak: failureStreak === -1 ? recent.length : failureStreak,
|
|
432
|
+
sameActionCount,
|
|
433
|
+
avgConfidence: recent.reduce((s, t) => s + (t.confidence || 0), 0) / recent.length,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
// ── Noticings: what HEAD observes passively ─────────────────────────────────
|
|
437
|
+
/**
|
|
438
|
+
* Passive observation layer.
|
|
439
|
+
*/
|
|
440
|
+
export function notice(situation, state, context = {}) {
|
|
441
|
+
const noticings = [];
|
|
442
|
+
const turnHistory = queryRecentTurns(state);
|
|
443
|
+
if (turnHistory.sameActionCount >= 3) {
|
|
444
|
+
noticings.push({
|
|
445
|
+
type: 'self-awareness',
|
|
446
|
+
severity: 'high',
|
|
447
|
+
observation: `Same action "${turnHistory.lastActions[turnHistory.lastActions.length - 1]}" repeated ${turnHistory.sameActionCount} times — may be stuck`,
|
|
448
|
+
shouldSurface: true,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (turnHistory.failureStreak >= 2) {
|
|
452
|
+
noticings.push({
|
|
453
|
+
type: 'self-awareness',
|
|
454
|
+
severity: 'medium',
|
|
455
|
+
observation: `${turnHistory.failureStreak} turns with low confidence — consider changing approach`,
|
|
456
|
+
shouldSurface: true,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (state.declaredGoal && situation.inferredGoal && state.declaredGoal !== situation.inferredGoal) {
|
|
460
|
+
noticings.push({
|
|
461
|
+
type: 'drift',
|
|
462
|
+
severity: 'medium',
|
|
463
|
+
observation: `Started with "${state.declaredGoal}" but current request implies "${situation.inferredGoal}"`,
|
|
464
|
+
shouldSurface: true,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (situation.priorFailures >= 2) {
|
|
468
|
+
noticings.push({
|
|
469
|
+
type: 'pattern',
|
|
470
|
+
severity: 'high',
|
|
471
|
+
observation: `${situation.priorFailures} prior failures — the approach may be wrong, not just the execution`,
|
|
472
|
+
shouldSurface: true,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
for (const area of situation.material.fragileAreas) {
|
|
476
|
+
if (!context.hasTests?.[area.file]) {
|
|
477
|
+
noticings.push({
|
|
478
|
+
type: 'risk',
|
|
479
|
+
severity: 'medium',
|
|
480
|
+
observation: `${area.file} is ${area.reason} but has no test coverage`,
|
|
481
|
+
shouldSurface: situation.taskShape.type === 'edit',
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (state.contextEstimate?.estimatedTokens > 120_000) {
|
|
486
|
+
const pct = Math.round((state.contextEstimate.estimatedTokens / 200_000) * 100);
|
|
487
|
+
noticings.push({
|
|
488
|
+
type: 'resource',
|
|
489
|
+
severity: state.contextEstimate.estimatedTokens > 160_000 ? 'high' : 'medium',
|
|
490
|
+
observation: `Context is at ~${pct}% capacity — consider wrapping up or handing off`,
|
|
491
|
+
shouldSurface: true,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
if (state.originalScope && situation.material.touchedFiles.length > state.originalScope * 2) {
|
|
495
|
+
noticings.push({
|
|
496
|
+
type: 'scope',
|
|
497
|
+
severity: 'medium',
|
|
498
|
+
observation: `Task has grown from ${state.originalScope} to ${situation.material.touchedFiles.length} files`,
|
|
499
|
+
shouldSurface: true,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
if (context.contextAge === 'stale') {
|
|
503
|
+
noticings.push({
|
|
504
|
+
type: 'staleness',
|
|
505
|
+
severity: 'medium',
|
|
506
|
+
observation: 'Working from potentially outdated context — files may have changed',
|
|
507
|
+
shouldSurface: situation.taskShape.risk !== 'low',
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (context.opportunities?.length) {
|
|
511
|
+
for (const opp of context.opportunities.slice(0, 3)) {
|
|
512
|
+
noticings.push({
|
|
513
|
+
type: 'opportunity',
|
|
514
|
+
severity: 'low',
|
|
515
|
+
observation: opp,
|
|
516
|
+
shouldSurface: false,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (state.dispatches?.length >= 2) {
|
|
521
|
+
const recent = state.dispatches.slice(-3);
|
|
522
|
+
const failures = recent.filter(d => d.outcome === 'failure');
|
|
523
|
+
if (failures.length >= 2) {
|
|
524
|
+
noticings.push({
|
|
525
|
+
type: 'self-awareness',
|
|
526
|
+
severity: 'high',
|
|
527
|
+
observation: `${failures.length} of last ${recent.length} dispatches failed — approach may need to change`,
|
|
528
|
+
shouldSurface: true,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// Diagnostic companion
|
|
533
|
+
try {
|
|
534
|
+
const diagnosticNoticings = readDiagnosticNoticings();
|
|
535
|
+
for (const dn of diagnosticNoticings) {
|
|
536
|
+
noticings.push({
|
|
537
|
+
type: 'diagnostic',
|
|
538
|
+
severity: dn.severity || 'medium',
|
|
539
|
+
observation: dn.observation,
|
|
540
|
+
shouldSurface: dn.severity === 'high',
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch { /* non-fatal */ }
|
|
545
|
+
return noticings;
|
|
546
|
+
}
|
|
547
|
+
// ── Deliberation: what HEAD decides to do ──────────────────────────────────
|
|
548
|
+
/**
|
|
549
|
+
* Full deliberation pipeline.
|
|
550
|
+
*/
|
|
551
|
+
export function deliberate(situation, uncertaintyLedger, obligations, noticings, state) {
|
|
552
|
+
const depth = assessDepth(situation);
|
|
553
|
+
const confidence = summarizeConfidence(uncertaintyLedger);
|
|
554
|
+
if (depth === 'reflexive' && confidence.level === 'sufficient') {
|
|
555
|
+
return {
|
|
556
|
+
depth,
|
|
557
|
+
action: _reflexiveAction(situation),
|
|
558
|
+
rationale: 'Simple request with sufficient confidence',
|
|
559
|
+
confidence,
|
|
560
|
+
obligations: obligations.filter(o => o.priority === 'critical'),
|
|
561
|
+
surfaceNoticings: [],
|
|
562
|
+
shouldAskUser: false,
|
|
563
|
+
uncertainties: [],
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const surfaceNoticings = noticings.filter(n => {
|
|
567
|
+
if (!n.shouldSurface)
|
|
568
|
+
return false;
|
|
569
|
+
if (n.type === 'opportunity')
|
|
570
|
+
return situation.taskShape.type === 'plan';
|
|
571
|
+
if (n.severity === 'high')
|
|
572
|
+
return true;
|
|
573
|
+
if (n.severity === 'medium' && depth !== 'light')
|
|
574
|
+
return true;
|
|
575
|
+
return false;
|
|
576
|
+
});
|
|
577
|
+
const shouldAskUser = _shouldAsk(situation, confidence, obligations, depth);
|
|
578
|
+
const candidates = _generateCandidates(situation, confidence, obligations);
|
|
579
|
+
const chosen = _selectAction(candidates, obligations, confidence, situation);
|
|
580
|
+
const rationale = _buildRationale(chosen, situation, confidence, obligations, surfaceNoticings);
|
|
581
|
+
return {
|
|
582
|
+
depth,
|
|
583
|
+
action: chosen,
|
|
584
|
+
rationale,
|
|
585
|
+
confidence,
|
|
586
|
+
obligations: obligations.filter(o => o.priority === 'critical' || o.priority === 'high'),
|
|
587
|
+
surfaceNoticings,
|
|
588
|
+
shouldAskUser,
|
|
589
|
+
uncertainties: confidence.gaps,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function _reflexiveAction(situation) {
|
|
593
|
+
if (situation.isQuestion && situation.taskShape.type === 'answer') {
|
|
594
|
+
return { type: 'respond', mode: 'direct' };
|
|
595
|
+
}
|
|
596
|
+
if (situation.isShort && /^(yes|y|ok|go|do it|sure|approved)\s*$/i.test(situation.raw.trim())) {
|
|
597
|
+
return { type: 'proceed', mode: 'approved' };
|
|
598
|
+
}
|
|
599
|
+
if (situation.isShort && /^(no|stop|wait|hold)\s*$/i.test(situation.raw.trim())) {
|
|
600
|
+
return { type: 'pause', mode: 'correction' };
|
|
601
|
+
}
|
|
602
|
+
return { type: 'respond', mode: 'direct' };
|
|
603
|
+
}
|
|
604
|
+
function _shouldAsk(situation, confidence, obligations, depth) {
|
|
605
|
+
if (obligations.some(o => o.type === 'askBeforeIrreversi'))
|
|
606
|
+
return true;
|
|
607
|
+
if (confidence.blockers && confidence.blockers.length > 0)
|
|
608
|
+
return true;
|
|
609
|
+
if (situation.relationship.shouldAsk)
|
|
610
|
+
return true;
|
|
611
|
+
if (depth === 'deep' && confidence.level !== 'sufficient')
|
|
612
|
+
return true;
|
|
613
|
+
if (situation.relationship.likelyMismatch)
|
|
614
|
+
return true;
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
function _generateCandidates(situation, confidence, obligations) {
|
|
618
|
+
const candidates = [];
|
|
619
|
+
if (situation.taskShape.type === 'answer' || situation.taskShape.type === 'research') {
|
|
620
|
+
candidates.push({ type: 'respond', mode: 'direct', fitness: situation.isQuestion ? 0.9 : 0.6 });
|
|
621
|
+
}
|
|
622
|
+
if (['edit', 'debug', 'review'].includes(situation.taskShape.type)) {
|
|
623
|
+
candidates.push({ type: 'dispatch', mode: situation.taskShape.type, fitness: confidence.level === 'sufficient' ? 0.85 : 0.4 });
|
|
624
|
+
}
|
|
625
|
+
if (situation.taskShape.scope !== 'small' || situation.taskShape.ambiguity !== 'low') {
|
|
626
|
+
candidates.push({ type: 'plan', mode: 'structured', fitness: situation.taskShape.ambiguity === 'high' ? 0.9 : 0.6 });
|
|
627
|
+
}
|
|
628
|
+
if (confidence.level === 'insufficient' || situation.relationship.wrongAssumption) {
|
|
629
|
+
candidates.push({ type: 'clarify', mode: 'question', fitness: confidence.level === 'insufficient' ? 0.95 : 0.7 });
|
|
630
|
+
}
|
|
631
|
+
if (situation.taskShape.type === 'plan') {
|
|
632
|
+
candidates.push({ type: 'think', mode: 'architecture', fitness: 0.85 });
|
|
633
|
+
}
|
|
634
|
+
if (situation.isShort && /^(yes|y|ok|go|do it|sure|approved)\s*$/i.test(situation.raw.trim())) {
|
|
635
|
+
candidates.push({ type: 'proceed', mode: 'approved', fitness: 0.95 });
|
|
636
|
+
}
|
|
637
|
+
return candidates;
|
|
638
|
+
}
|
|
639
|
+
function _selectAction(candidates, obligations, confidence, situation) {
|
|
640
|
+
if (candidates.length === 0)
|
|
641
|
+
return { type: 'clarify', mode: 'no-candidates' };
|
|
642
|
+
for (const c of candidates) {
|
|
643
|
+
if (c.type === 'dispatch' && confidence.level !== 'sufficient') {
|
|
644
|
+
c.fitness = (c.fitness || 0) * 0.5;
|
|
645
|
+
}
|
|
646
|
+
if (c.type === 'dispatch' && obligations.some(o => o.type === 'askBeforeIrreversi')) {
|
|
647
|
+
c.fitness = (c.fitness || 0) * 0.3;
|
|
648
|
+
}
|
|
649
|
+
if (c.type === 'plan' && situation.taskShape.scope === 'large') {
|
|
650
|
+
c.fitness = (c.fitness || 0) * 1.3;
|
|
651
|
+
}
|
|
652
|
+
if (c.type === 'clarify' && situation.relationship.likelyMismatch) {
|
|
653
|
+
c.fitness = (c.fitness || 0) * 1.5;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
candidates.sort((a, b) => (b.fitness || 0) - (a.fitness || 0));
|
|
657
|
+
return candidates[0];
|
|
658
|
+
}
|
|
659
|
+
function _buildRationale(action, situation, confidence, obligations, noticings) {
|
|
660
|
+
const parts = [];
|
|
661
|
+
parts.push(`Action: ${action.type} (${action.mode})`);
|
|
662
|
+
if (confidence.level !== 'sufficient') {
|
|
663
|
+
parts.push(`Confidence: ${confidence.level} (${confidence.score}) — ${confidence.gaps.length} gap(s)`);
|
|
664
|
+
}
|
|
665
|
+
const criticalObligations = obligations.filter(o => o.priority === 'critical');
|
|
666
|
+
if (criticalObligations.length > 0) {
|
|
667
|
+
parts.push(`Critical obligations: ${criticalObligations.map(o => o.type).join(', ')}`);
|
|
668
|
+
}
|
|
669
|
+
if (noticings.length > 0) {
|
|
670
|
+
parts.push(`Surfacing ${noticings.length} noticing(s)`);
|
|
671
|
+
}
|
|
672
|
+
if (situation.inferredGoal) {
|
|
673
|
+
parts.push(`Note: inferred goal may differ — "${situation.inferredGoal}"`);
|
|
674
|
+
}
|
|
675
|
+
return parts.join('. ');
|
|
676
|
+
}
|
|
677
|
+
// ── Full turn processor ─────────────────────────────────────────────────────
|
|
678
|
+
/**
|
|
679
|
+
* Process a complete turn through the cognitive pipeline.
|
|
680
|
+
*/
|
|
681
|
+
export function processTurn(state, userMessage, context = {}) {
|
|
682
|
+
const situation = perceive(userMessage, context);
|
|
683
|
+
const depth = assessDepth(situation);
|
|
684
|
+
const uncertainties = assessUncertainty(situation, context);
|
|
685
|
+
const obligations = deriveObligations(situation);
|
|
686
|
+
const noticings = notice(situation, state, context);
|
|
687
|
+
const result = deliberate(situation, uncertainties, obligations, noticings, state);
|
|
688
|
+
// Update state
|
|
689
|
+
state.lastActivity = Date.now();
|
|
690
|
+
if (!state.declaredGoal && situation.taskShape.type !== 'answer') {
|
|
691
|
+
state.declaredGoal = situation.explicitAsk.slice(0, 200);
|
|
692
|
+
state.originalScope = situation.material.touchedFiles.length || 1;
|
|
693
|
+
}
|
|
694
|
+
if (!state.turns)
|
|
695
|
+
state.turns = [];
|
|
696
|
+
state.turns.push({
|
|
697
|
+
timestamp: Date.now(),
|
|
698
|
+
depth: result.depth,
|
|
699
|
+
action: result.action.type,
|
|
700
|
+
confidence: result.confidence.score,
|
|
701
|
+
obligationCount: result.obligations.length,
|
|
702
|
+
noticingCount: result.surfaceNoticings.length,
|
|
703
|
+
});
|
|
704
|
+
saveState(state);
|
|
705
|
+
return {
|
|
706
|
+
situation,
|
|
707
|
+
depth,
|
|
708
|
+
uncertainties,
|
|
709
|
+
obligations,
|
|
710
|
+
noticings,
|
|
711
|
+
result,
|
|
712
|
+
shouldAskUser: result.shouldAskUser,
|
|
713
|
+
shouldDispatch: result.action.type === 'dispatch' || result.action.type === 'proceed',
|
|
714
|
+
shouldClarify: result.action.type === 'clarify',
|
|
715
|
+
shouldThink: result.action.type === 'think' || result.action.type === 'plan',
|
|
716
|
+
action: result.action,
|
|
717
|
+
rationale: result.rationale,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
// ── State persistence ───────────────────────────────────────────────────────
|
|
721
|
+
export function loadState() {
|
|
722
|
+
try {
|
|
723
|
+
if (existsSync(STATE_FILE)) {
|
|
724
|
+
const data = JSON.parse(readFileSync(STATE_FILE, 'utf8'));
|
|
725
|
+
if (Date.now() - (data.lastActivity || 0) > 30 * 60 * 1000) {
|
|
726
|
+
return freshState();
|
|
727
|
+
}
|
|
728
|
+
return data;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
catch { /* non-fatal */ }
|
|
732
|
+
return freshState();
|
|
733
|
+
}
|
|
734
|
+
export function freshState() {
|
|
735
|
+
return {
|
|
736
|
+
sessionId: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
737
|
+
declaredGoal: null,
|
|
738
|
+
originalScope: null,
|
|
739
|
+
turns: [],
|
|
740
|
+
dispatches: [],
|
|
741
|
+
contextEstimate: { messages: 0, estimatedTokens: 0 },
|
|
742
|
+
lastActivity: Date.now(),
|
|
743
|
+
created: Date.now(),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
export function saveState(state) {
|
|
747
|
+
state.lastActivity = Date.now();
|
|
748
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
749
|
+
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
750
|
+
}
|
|
751
|
+
export function recordDispatchOutcome(state, outcome) {
|
|
752
|
+
if (!state.dispatches)
|
|
753
|
+
state.dispatches = [];
|
|
754
|
+
state.dispatches.push({
|
|
755
|
+
ts: Date.now(),
|
|
756
|
+
type: outcome.type || 'unknown',
|
|
757
|
+
objective: (outcome.objective || '').slice(0, 100),
|
|
758
|
+
outcome: outcome.status || 'unknown',
|
|
759
|
+
durationMs: outcome.durationMs || 0,
|
|
760
|
+
});
|
|
761
|
+
if (state.dispatches.length > 10)
|
|
762
|
+
state.dispatches = state.dispatches.slice(-10);
|
|
763
|
+
saveState(state);
|
|
764
|
+
}
|
|
765
|
+
//# sourceMappingURL=head.js.map
|