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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Wave Planner — Layer 2 cognitive loop
|
|
2
|
+
// Takes HEAD's deliberation output and produces structured wave-based execution plans.
|
|
3
|
+
const TIER_COST = {
|
|
4
|
+
search: { tokens: 5000, time: '~10s' },
|
|
5
|
+
execute: { tokens: 20000, time: '~45s' },
|
|
6
|
+
think: { tokens: 15000, time: '~30s' },
|
|
7
|
+
review: { tokens: 10000, time: '~20s' },
|
|
8
|
+
};
|
|
9
|
+
let waveCounter = 0;
|
|
10
|
+
function nextWaveId() { return `w-${Date.now().toString(36)}-${++waveCounter}`; }
|
|
11
|
+
/** Plan waves from HEAD's deliberation output. */
|
|
12
|
+
export function planWaves(deliberation, context = {}) {
|
|
13
|
+
const { situation = {}, uncertainties = [], result = {} } = deliberation;
|
|
14
|
+
const { files = [], priorDebriefs = [], diagnosticPatterns = [] } = context;
|
|
15
|
+
const depth = result.depth || 'light';
|
|
16
|
+
const blockers = uncertainties.filter((u) => u.confidence < 0.3);
|
|
17
|
+
const hasFragile = situation.risk === 'high' || situation.risk === 'critical';
|
|
18
|
+
const largeScope = situation.scope === 'large';
|
|
19
|
+
const priorBlockers = priorDebriefs.filter((d) => d.blockers?.length);
|
|
20
|
+
const waves = [];
|
|
21
|
+
const contingencies = [];
|
|
22
|
+
// Determine wave structure based on depth
|
|
23
|
+
if (depth === 'reflexive' || depth === 'light') {
|
|
24
|
+
waves.push(makeSingleWave(result, situation, files));
|
|
25
|
+
}
|
|
26
|
+
else if (depth === 'full') {
|
|
27
|
+
if (blockers.length) {
|
|
28
|
+
waves.push(makeReconWave(blockers, situation, files, priorBlockers));
|
|
29
|
+
}
|
|
30
|
+
waves.push(makeImplementWave(result, situation, files, largeScope, waves));
|
|
31
|
+
if (hasFragile) {
|
|
32
|
+
waves.push(makeVerifyWave(situation, files, waves));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (depth === 'deep') {
|
|
36
|
+
// Always start with recon for deep
|
|
37
|
+
waves.push(makeReconWave(blockers.length ? blockers : uncertainties, situation, files, priorBlockers));
|
|
38
|
+
// Plan wave
|
|
39
|
+
waves.push({
|
|
40
|
+
id: nextWaveId(),
|
|
41
|
+
phase: 'synthesize',
|
|
42
|
+
agents: [{
|
|
43
|
+
tier: 'think',
|
|
44
|
+
objective: `Synthesize recon findings into implementation plan for: ${result.action || situation.taskShape || 'task'}`,
|
|
45
|
+
scope: files.slice(0, 10),
|
|
46
|
+
}],
|
|
47
|
+
dependsOn: [waves[0].id],
|
|
48
|
+
gateCondition: 'recon wave completed without escalation',
|
|
49
|
+
parallel: false,
|
|
50
|
+
});
|
|
51
|
+
// Implement wave
|
|
52
|
+
waves.push(makeImplementWave(result, situation, files, largeScope, waves));
|
|
53
|
+
// Verify wave
|
|
54
|
+
waves.push(makeVerifyWave(situation, files, waves));
|
|
55
|
+
}
|
|
56
|
+
// Force recon-first if blockers exist and first wave isn't recon
|
|
57
|
+
if (blockers.length && waves.length && waves[0].phase !== 'recon') {
|
|
58
|
+
const reconWave = makeReconWave(blockers, situation, files, priorBlockers);
|
|
59
|
+
waves.forEach(w => { if (!w.dependsOn.length)
|
|
60
|
+
w.dependsOn.push(reconWave.id); });
|
|
61
|
+
waves.unshift(reconWave);
|
|
62
|
+
}
|
|
63
|
+
// Build contingencies
|
|
64
|
+
if (largeScope) {
|
|
65
|
+
contingencies.push({
|
|
66
|
+
trigger: 'if wave 1 finds scope is larger than expected',
|
|
67
|
+
response: 'add-wave',
|
|
68
|
+
details: 'Split implementation into additional parallel waves by file group',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (hasFragile) {
|
|
72
|
+
contingencies.push({
|
|
73
|
+
trigger: 'if implementation wave introduces regressions',
|
|
74
|
+
response: 'retry-different',
|
|
75
|
+
details: 'Re-approach with smaller incremental changes',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (blockers.length) {
|
|
79
|
+
contingencies.push({
|
|
80
|
+
trigger: 'if recon cannot resolve uncertainty',
|
|
81
|
+
response: 'escalate',
|
|
82
|
+
details: 'Ask user for clarification before proceeding',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const agentCount = waves.reduce((n, w) => n + w.agents.length, 0);
|
|
86
|
+
const dominantTier = agentCount <= 1 ? (waves[0]?.agents[0]?.tier || 'search')
|
|
87
|
+
: waves.flatMap((w) => w.agents.map((a) => a.tier))
|
|
88
|
+
.sort((a, b) => (TIER_COST[b]?.tokens ?? 0) - (TIER_COST[a]?.tokens ?? 0))[0];
|
|
89
|
+
return {
|
|
90
|
+
waves,
|
|
91
|
+
rationale: buildRationale(depth, blockers, hasFragile, largeScope),
|
|
92
|
+
estimatedCost: { waves: waves.length, agents: agentCount, tier: dominantTier },
|
|
93
|
+
contingencies,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Decide if the remaining plan is still valid after a wave debrief. */
|
|
97
|
+
export function shouldReplan(currentPlan, newDebrief) {
|
|
98
|
+
if (!newDebrief)
|
|
99
|
+
return false;
|
|
100
|
+
if (newDebrief.scopeChange === 'larger' || newDebrief.scopeChange === 'different')
|
|
101
|
+
return true;
|
|
102
|
+
if (newDebrief.pivotReason)
|
|
103
|
+
return true;
|
|
104
|
+
if (newDebrief.confidence !== undefined && newDebrief.confidence < 0.4)
|
|
105
|
+
return true;
|
|
106
|
+
// Check if blockers intersect with upcoming wave objectives
|
|
107
|
+
const blockersList = newDebrief.blockers;
|
|
108
|
+
if (blockersList?.length) {
|
|
109
|
+
const planWaves = currentPlan.waves;
|
|
110
|
+
const remaining = (planWaves ?? []).filter((w) => !w.completed);
|
|
111
|
+
for (const w of remaining) {
|
|
112
|
+
for (const agent of w.agents) {
|
|
113
|
+
for (const blocker of blockersList) {
|
|
114
|
+
const bLower = (typeof blocker === 'string' ? blocker : blocker.description || '').toLowerCase();
|
|
115
|
+
if (bLower && agent.objective.toLowerCase().includes(bLower.split(' ')[0]))
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
/** Produce a new plan incorporating what was learned. Preserves completed waves. */
|
|
124
|
+
export function replan(currentPlan, waveSummary, originalDeliberation) {
|
|
125
|
+
const planWavesArr = (currentPlan.waves ?? []);
|
|
126
|
+
const completed = planWavesArr.filter((w) => w.completed);
|
|
127
|
+
const context = {
|
|
128
|
+
files: waveSummary.filesDiscovered || [],
|
|
129
|
+
priorDebriefs: [waveSummary],
|
|
130
|
+
diagnosticPatterns: waveSummary.patterns || [],
|
|
131
|
+
};
|
|
132
|
+
// Merge learning into deliberation
|
|
133
|
+
const updated = { ...originalDeliberation };
|
|
134
|
+
if (waveSummary.scopeChange === 'larger') {
|
|
135
|
+
updated.situation = { ...updated.situation, scope: 'large' };
|
|
136
|
+
}
|
|
137
|
+
if (waveSummary.confidence !== undefined) {
|
|
138
|
+
updated.result = { ...updated.result, confidence: waveSummary.confidence };
|
|
139
|
+
}
|
|
140
|
+
if (waveSummary.newUncertainties) {
|
|
141
|
+
updated.uncertainties = [
|
|
142
|
+
...(updated.uncertainties || []),
|
|
143
|
+
...waveSummary.newUncertainties,
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
const newPlan = planWaves(updated, context);
|
|
147
|
+
// Preserve completed waves at the front
|
|
148
|
+
newPlan.waves = [...completed, ...newPlan.waves];
|
|
149
|
+
newPlan.rationale = `Replanned after wave debrief: ${waveSummary.pivotReason || waveSummary.scopeChange || 'confidence drop'}. ${newPlan.rationale}`;
|
|
150
|
+
return newPlan;
|
|
151
|
+
}
|
|
152
|
+
/** Rough cost estimate for a single wave. */
|
|
153
|
+
export function estimateWaveCost(wave) {
|
|
154
|
+
let tokens = 0;
|
|
155
|
+
for (const agent of wave.agents) {
|
|
156
|
+
tokens += TIER_COST[agent.tier]?.tokens || 10000;
|
|
157
|
+
}
|
|
158
|
+
// Time: parallel agents overlap, sequential add up
|
|
159
|
+
const times = wave.agents.map((a) => parseInt(TIER_COST[a.tier]?.time) || 20);
|
|
160
|
+
const seconds = wave.parallel ? Math.max(...times) : times.reduce((s, t) => s + t, 0);
|
|
161
|
+
return { tokens, time: `~${seconds}s` };
|
|
162
|
+
}
|
|
163
|
+
function makeSingleWave(result, situation, files) {
|
|
164
|
+
const tier = mapActionToTier(result.action);
|
|
165
|
+
return {
|
|
166
|
+
id: nextWaveId(),
|
|
167
|
+
phase: tier === 'search' ? 'recon' : 'implement',
|
|
168
|
+
agents: [{
|
|
169
|
+
tier,
|
|
170
|
+
objective: situation.explicitAsk || situation.raw || (typeof result.action === 'string' ? result.action : result.action?.mode) || 'execute task',
|
|
171
|
+
scope: files.slice(0, 5),
|
|
172
|
+
}],
|
|
173
|
+
dependsOn: [],
|
|
174
|
+
parallel: false,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function makeReconWave(uncertainties, situation, files, priorBlockers) {
|
|
178
|
+
const avoidApproaches = priorBlockers.flatMap((d) => d.blockers?.map((b) => typeof b === 'string' ? b : b.approach) || []);
|
|
179
|
+
const agents = uncertainties.slice(0, 3).map((u) => {
|
|
180
|
+
const spec = {
|
|
181
|
+
tier: 'search',
|
|
182
|
+
objective: `Resolve uncertainty: ${u.claim || u.description || 'unknown'}`,
|
|
183
|
+
scope: files.slice(0, 5),
|
|
184
|
+
};
|
|
185
|
+
if (u.wouldChangeIf) {
|
|
186
|
+
spec.conditionalPivot = { if: u.wouldChangeIf, then: 'report finding and stop' };
|
|
187
|
+
}
|
|
188
|
+
return spec;
|
|
189
|
+
});
|
|
190
|
+
// If no uncertainties provided, add a general recon agent
|
|
191
|
+
if (!agents.length) {
|
|
192
|
+
agents.push({
|
|
193
|
+
tier: 'search',
|
|
194
|
+
objective: `Explore scope and structure for: ${situation.taskShape || 'task'}`,
|
|
195
|
+
scope: files.slice(0, 5),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// Annotate agents to avoid prior failed approaches
|
|
199
|
+
if (avoidApproaches.length) {
|
|
200
|
+
for (const agent of agents) {
|
|
201
|
+
agent.objective += ` (avoid: ${avoidApproaches.join(', ')})`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
id: nextWaveId(),
|
|
206
|
+
phase: 'recon',
|
|
207
|
+
agents,
|
|
208
|
+
dependsOn: [],
|
|
209
|
+
gateCondition: undefined,
|
|
210
|
+
parallel: agents.length > 1,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function makeImplementWave(result, situation, files, largeScope, existingWaves) {
|
|
214
|
+
const dependsOn = existingWaves.length ? [existingWaves[existingWaves.length - 1].id] : [];
|
|
215
|
+
const agents = [];
|
|
216
|
+
if (largeScope && files.length > 3) {
|
|
217
|
+
// Split into parallel agents by file group
|
|
218
|
+
const groupSize = Math.ceil(files.length / 3);
|
|
219
|
+
for (let i = 0; i < files.length; i += groupSize) {
|
|
220
|
+
agents.push({
|
|
221
|
+
tier: 'execute',
|
|
222
|
+
objective: result.action || `Implement changes in file group ${Math.floor(i / groupSize) + 1}`,
|
|
223
|
+
scope: files.slice(i, i + groupSize),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
agents.push({
|
|
229
|
+
tier: 'execute',
|
|
230
|
+
objective: result.action || situation.taskShape || 'implement changes',
|
|
231
|
+
scope: files.slice(0, 10),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
id: nextWaveId(),
|
|
236
|
+
phase: 'implement',
|
|
237
|
+
agents,
|
|
238
|
+
dependsOn,
|
|
239
|
+
gateCondition: existingWaves.length ? 'prior wave completed successfully' : undefined,
|
|
240
|
+
parallel: agents.length > 1,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function makeVerifyWave(situation, files, existingWaves) {
|
|
244
|
+
const dependsOn = existingWaves.length ? [existingWaves[existingWaves.length - 1].id] : [];
|
|
245
|
+
return {
|
|
246
|
+
id: nextWaveId(),
|
|
247
|
+
phase: 'verify',
|
|
248
|
+
agents: [{
|
|
249
|
+
tier: 'review',
|
|
250
|
+
objective: `Verify changes are correct and safe${situation.risk === 'critical' ? ' — critical risk area' : ''}`,
|
|
251
|
+
scope: files.slice(0, 10),
|
|
252
|
+
}],
|
|
253
|
+
dependsOn,
|
|
254
|
+
gateCondition: 'implementation wave completed',
|
|
255
|
+
parallel: false,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function mapActionToTier(action) {
|
|
259
|
+
if (!action)
|
|
260
|
+
return 'execute';
|
|
261
|
+
const actionObj = action;
|
|
262
|
+
const a = (typeof action === 'string' ? action : `${actionObj.type || ''} ${actionObj.mode || ''}`).toLowerCase();
|
|
263
|
+
if (a.includes('search') || a.includes('find') || a.includes('look') || a.includes('explore'))
|
|
264
|
+
return 'search';
|
|
265
|
+
if (a.includes('review') || a.includes('check') || a.includes('verify'))
|
|
266
|
+
return 'review';
|
|
267
|
+
if (a.includes('think') || a.includes('plan') || a.includes('design') || a.includes('architect'))
|
|
268
|
+
return 'think';
|
|
269
|
+
return 'execute';
|
|
270
|
+
}
|
|
271
|
+
function buildRationale(depth, blockers, hasFragile, largeScope) {
|
|
272
|
+
const parts = [`Depth: ${depth}.`];
|
|
273
|
+
if (blockers.length)
|
|
274
|
+
parts.push(`${blockers.length} blocker(s) require recon first.`);
|
|
275
|
+
if (hasFragile)
|
|
276
|
+
parts.push('High-risk area — verification wave added.');
|
|
277
|
+
if (largeScope)
|
|
278
|
+
parts.push('Large scope — parallel agents where possible.');
|
|
279
|
+
return parts.join(' ');
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=wave-planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wave-planner.js","sourceRoot":"","sources":["../../src/wave-planner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# head-guard.sh — DEPRECATED. Replaced by head-guard.mjs.
|
|
3
|
+
#
|
|
4
|
+
# This file is kept for reference only. It never worked correctly because it
|
|
5
|
+
# reads CLAUDE_TOOL_NAME from the environment, but Claude Code delivers tool
|
|
6
|
+
# info via stdin JSON, not environment variables.
|
|
7
|
+
#
|
|
8
|
+
# The replacement (head-guard.mjs) reads stdin JSON, detects HEAD vs subagent
|
|
9
|
+
# via `agent_id`, and returns the correct permissionDecision block format.
|
|
10
|
+
#
|
|
11
|
+
# Do not use this file. See hooks/head-guard.mjs instead.
|
|
12
|
+
|
|
13
|
+
BLOCK_MSG='[dual-brain] HEAD cannot use this tool directly. Dispatch via: dual-brain go "task description"'
|
|
14
|
+
|
|
15
|
+
# ── 1. Role check ────────────────────────────────────────────────────────────
|
|
16
|
+
# Only enforce when the session has been explicitly marked as the HEAD agent.
|
|
17
|
+
# If the env var is unset we allow everything (backward compat for non-dual-brain usage).
|
|
18
|
+
|
|
19
|
+
if [[ -z "${DUAL_BRAIN_ROLE}" ]]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [[ "${DUAL_BRAIN_ROLE}" != "head" ]]; then
|
|
24
|
+
# Work-agent session — no restrictions.
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# ── 2. Tool name check ───────────────────────────────────────────────────────
|
|
29
|
+
TOOL="${CLAUDE_TOOL_NAME:-}"
|
|
30
|
+
|
|
31
|
+
# Block direct file-editing tools and Bash unconditionally for HEAD.
|
|
32
|
+
# HEAD should use Read tool for reading and Agent (via dual-brain go) for all other work.
|
|
33
|
+
case "${TOOL}" in
|
|
34
|
+
Edit|Write|NotebookEdit|Bash)
|
|
35
|
+
echo "${BLOCK_MSG}" >&2
|
|
36
|
+
exit 2
|
|
37
|
+
;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
40
|
+
# ── 3. Default: allow ────────────────────────────────────────────────────────
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* task-classifier.mjs — Analyze work descriptions and return model + effort + mode config.
|
|
4
|
+
*
|
|
5
|
+
* Uses model-registry capabilities to make informed routing decisions:
|
|
6
|
+
* - Which model (per provider) handles this task best
|
|
7
|
+
* - What effort/reasoning level to use
|
|
8
|
+
* - Whether to enable extended thinking, fast mode, extended context, web search
|
|
9
|
+
* - How to dispatch (Claude Agent vs Codex exec)
|
|
10
|
+
*
|
|
11
|
+
* Exports: classifyTask, selectModelEffort, INTENTS
|
|
12
|
+
* CLI: node hooks/task-classifier.mjs "description" [--files a,b] [--budget-pressure 0.8]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { classifyRisk, extractPaths } from './risk-classifier.mjs';
|
|
16
|
+
import {
|
|
17
|
+
MODEL_CAPABILITIES, getCapabilities, getDispatchConfig,
|
|
18
|
+
recommendEffort, shouldUseExtendedContext, shouldUseFastMode,
|
|
19
|
+
getBestModelFor,
|
|
20
|
+
} from './model-registry.mjs';
|
|
21
|
+
|
|
22
|
+
// ─── Intent definitions ───────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const INTENTS = {
|
|
25
|
+
search: /\b(grep|find|locate|where is|where are|list|explore|read|look up|look for|check|what is|show me|display)\b/i,
|
|
26
|
+
explain: /\b(explain|walk me through|what does|how does|describe|summarize|understand|clarify)\b/i,
|
|
27
|
+
compare: /\b(compare|contrast|difference|versus|vs\.?|trade.?off|which is better|pros and cons|benchmark|performance)\b/i,
|
|
28
|
+
document: /\b(document|docs?|readme|jsdoc|typedoc|api docs|write docs|add docs|update docs)\b/i,
|
|
29
|
+
format: /\b(format|lint|prettier|style|indent|whitespace|typo|typos|comment[s]?|reformat)\b/i,
|
|
30
|
+
planning: /\b(plan|roadmap|strategy|prioritize|break down|decompose|prioritise)\b/i,
|
|
31
|
+
architecture: /\b(design|architect|architecture|propose|how should we|system design|system architecture)\b/i,
|
|
32
|
+
security: /\b(auth|credential|secret|token|password|encrypt|permission[s]?|vulnerability|vulnerabilities|CVE|oauth|jwt|api.?key)\b/i,
|
|
33
|
+
review: /\b(review|audit|check for issues|evaluate|assess|inspect code|code review)\b/i,
|
|
34
|
+
debug: /\b(debug|investigate|why (is|does|isn't|doesn't)|trace|diagnose|figure out|broken|not working|failing|regression)\b/i,
|
|
35
|
+
test: /\b(test[s]?|spec[s]?|add test|fix test|test coverage|unit test|e2e|integration test|jest|vitest|mocha)\b/i,
|
|
36
|
+
refactor: /\b(refactor|restructure|reorganize|reorganise|extract|split|consolidate|clean up|cleanup|dedupe|dedup)\b/i,
|
|
37
|
+
edit: /\b(fix|add|update|modify|change|rename|move|replace|write|implement|create|remove|delete|insert)\b/i,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const INTENT_PRIORITY = [
|
|
41
|
+
'security', 'architecture', 'planning', 'compare', 'review',
|
|
42
|
+
'debug', 'refactor', 'test', 'explain', 'document', 'format', 'search', 'edit',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// ─── Risk keyword detection (description-level) ──────────────────────────────
|
|
46
|
+
|
|
47
|
+
const RISK_KEYWORDS = [
|
|
48
|
+
{ level: 'critical', regex: /\b(auth|secret|credential|token|password|encrypt|certificate|oauth|jwt|api.?key|vulnerability|CVE)\b/i },
|
|
49
|
+
{ level: 'high', regex: /\b(billing|payment|migration|deploy|ci.?cd|security|permission|policy|schema|openapi|swagger|production|prod)\b/i },
|
|
50
|
+
{ level: 'medium', regex: /\b(test|spec|config|shared|util|lib|integration|public.?api)\b/i },
|
|
51
|
+
{ level: 'low', regex: /\b(readme|docs?|comment|format|lint|changelog|typo|whitespace)\b/i },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const LEVEL_ORDER = { critical: 3, high: 2, medium: 1, low: 0 };
|
|
55
|
+
|
|
56
|
+
function detectKeywordRisk(description) {
|
|
57
|
+
for (const { level, regex } of RISK_KEYWORDS) {
|
|
58
|
+
if (regex.test(description)) return level;
|
|
59
|
+
}
|
|
60
|
+
return 'low';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function higherRisk(a, b) {
|
|
64
|
+
return LEVEL_ORDER[a] >= LEVEL_ORDER[b] ? a : b;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── classifyTask ─────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function classifyTask(description, options = {}) {
|
|
70
|
+
const { files = [], priorFailures = 0 } = options;
|
|
71
|
+
|
|
72
|
+
// 1. Intent detection
|
|
73
|
+
let intent = 'edit';
|
|
74
|
+
for (const key of INTENT_PRIORITY) {
|
|
75
|
+
if (INTENTS[key].test(description)) {
|
|
76
|
+
intent = key;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. Risk detection
|
|
82
|
+
const allPaths = [...files, ...extractPaths(description)];
|
|
83
|
+
const pathRisk = allPaths.length > 0 ? classifyRisk(allPaths).level : 'low';
|
|
84
|
+
const keywordRisk = detectKeywordRisk(description);
|
|
85
|
+
const risk = higherRisk(pathRisk, keywordRisk);
|
|
86
|
+
|
|
87
|
+
// 3. File count
|
|
88
|
+
const fileCount = files.length;
|
|
89
|
+
|
|
90
|
+
// 4. Complexity detection
|
|
91
|
+
let complexity;
|
|
92
|
+
const isAmbiguous = description.length > 120 || /\b(and also|as well as|plus|additionally|also)\b/i.test(description);
|
|
93
|
+
|
|
94
|
+
if (priorFailures >= 2 || intent === 'architecture' || risk === 'critical' || fileCount >= 6 || isAmbiguous && risk === 'critical') {
|
|
95
|
+
complexity = 'complex';
|
|
96
|
+
} else if (fileCount >= 3 || intent === 'refactor' || intent === 'debug' || risk === 'high' || isAmbiguous) {
|
|
97
|
+
complexity = 'moderate';
|
|
98
|
+
} else if (fileCount <= 2 && (risk === 'low' || risk === 'medium')) {
|
|
99
|
+
if (intent === 'format' || fileCount <= 1 && risk === 'low') {
|
|
100
|
+
complexity = 'trivial';
|
|
101
|
+
} else {
|
|
102
|
+
complexity = 'simple';
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
complexity = 'moderate';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 5. Effort selection
|
|
109
|
+
const baseEffort = { trivial: 'low', simple: 'medium', moderate: 'high', complex: 'high' }[complexity];
|
|
110
|
+
const effortOrder = ['low', 'medium', 'high', 'xhigh'];
|
|
111
|
+
|
|
112
|
+
function bumpEffort(e, n = 1) {
|
|
113
|
+
return effortOrder[Math.min(effortOrder.indexOf(e) + n, effortOrder.length - 1)];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let effort = baseEffort;
|
|
117
|
+
|
|
118
|
+
if (risk === 'critical' && LEVEL_ORDER[effort] < LEVEL_ORDER['high']) effort = 'high';
|
|
119
|
+
|
|
120
|
+
if (priorFailures >= 2) {
|
|
121
|
+
effort = 'xhigh';
|
|
122
|
+
} else if (priorFailures === 1) {
|
|
123
|
+
effort = bumpEffort(effort, 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (intent === 'format' || intent === 'search') {
|
|
127
|
+
if (LEVEL_ORDER[effort] > LEVEL_ORDER['medium']) effort = 'medium';
|
|
128
|
+
}
|
|
129
|
+
if ((intent === 'architecture' || intent === 'security') && LEVEL_ORDER[effort] < LEVEL_ORDER['high']) {
|
|
130
|
+
effort = 'high';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 6. Reason
|
|
134
|
+
const reasons = [];
|
|
135
|
+
if (fileCount > 0) reasons.push(`${fileCount} file(s)`);
|
|
136
|
+
if (risk !== 'low') reasons.push(`${risk} risk`);
|
|
137
|
+
if (priorFailures > 0) reasons.push(`${priorFailures} prior failure(s)`);
|
|
138
|
+
reasons.push(`intent=${intent}, complexity=${complexity}`);
|
|
139
|
+
const reason = reasons.join('; ');
|
|
140
|
+
|
|
141
|
+
return { intent, risk, complexity, fileCount, effort, reason };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── selectModelEffort ────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
function selectModelEffort(taskProfile, options = {}) {
|
|
147
|
+
const { budgetPressure = 0, userBudgetTier = '$100', isIterating = false, estimatedTokens = 0 } = options;
|
|
148
|
+
const { intent, risk, effort, complexity } = taskProfile;
|
|
149
|
+
|
|
150
|
+
// ── Intent classification for routing ──
|
|
151
|
+
const thinkIntents = ['architecture', 'security', 'review', 'planning', 'compare'];
|
|
152
|
+
const searchIntents = ['search', 'format', 'explain'];
|
|
153
|
+
const lightIntents = ['document', 'explain', 'format', 'search'];
|
|
154
|
+
|
|
155
|
+
const needsOpus = thinkIntents.includes(intent)
|
|
156
|
+
|| risk === 'critical'
|
|
157
|
+
|| effort === 'xhigh';
|
|
158
|
+
|
|
159
|
+
const needsHaiku = searchIntents.includes(intent) && effort === 'low';
|
|
160
|
+
|
|
161
|
+
let claudeModel = needsOpus ? 'opus' : needsHaiku ? 'haiku' : 'sonnet';
|
|
162
|
+
|
|
163
|
+
// ── Claude effort (from registry, null-safe for haiku) ──
|
|
164
|
+
const caps = getCapabilities(claudeModel);
|
|
165
|
+
let claudeEffort = caps?.reasoning?.effortLevels
|
|
166
|
+
? (recommendEffort(claudeModel, complexity, risk) || effort)
|
|
167
|
+
: null;
|
|
168
|
+
|
|
169
|
+
// ── Claude modes ──
|
|
170
|
+
const claudeModes = {
|
|
171
|
+
extendedThinking: caps?.reasoning?.extendedThinking
|
|
172
|
+
&& (complexity === 'moderate' || complexity === 'complex')
|
|
173
|
+
&& !lightIntents.includes(intent),
|
|
174
|
+
fastMode: shouldUseFastMode(claudeModel, isIterating),
|
|
175
|
+
extendedContext: shouldUseExtendedContext(claudeModel, estimatedTokens),
|
|
176
|
+
ultrathink: claudeModel === 'opus'
|
|
177
|
+
&& (risk === 'critical' || (complexity === 'complex' && thinkIntents.includes(intent))),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// ── OpenAI model selection (all models reachable) ──
|
|
181
|
+
let openaiModel;
|
|
182
|
+
if (needsOpus) {
|
|
183
|
+
openaiModel = 'gpt-5.5';
|
|
184
|
+
} else if (searchIntents.includes(intent) && effort === 'low') {
|
|
185
|
+
openaiModel = 'gpt-4.1-mini';
|
|
186
|
+
} else if (['edit', 'test', 'document'].includes(intent) && ['simple', 'trivial'].includes(complexity)) {
|
|
187
|
+
openaiModel = 'gpt-4.1';
|
|
188
|
+
} else if (intent === 'explain' && complexity !== 'trivial') {
|
|
189
|
+
openaiModel = 'gpt-5.2';
|
|
190
|
+
} else if (['edit', 'document'].includes(intent) && complexity === 'moderate') {
|
|
191
|
+
openaiModel = 'gpt-5.3-codex';
|
|
192
|
+
} else if (intent === 'test' && complexity === 'moderate') {
|
|
193
|
+
openaiModel = 'gpt-5.4-mini';
|
|
194
|
+
} else if (['refactor', 'debug'].includes(intent)) {
|
|
195
|
+
openaiModel = complexity === 'complex' ? 'gpt-5.4' : 'gpt-5.3-codex';
|
|
196
|
+
} else {
|
|
197
|
+
openaiModel = 'gpt-5.4';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── OpenAI effort (from registry) ──
|
|
201
|
+
let openaiEffort = recommendEffort(openaiModel, complexity, risk) || effort;
|
|
202
|
+
|
|
203
|
+
// ── OpenAI modes ──
|
|
204
|
+
const openaiCaps = getCapabilities(openaiModel);
|
|
205
|
+
const openaiModes = {
|
|
206
|
+
webSearch: openaiCaps?.modes?.webSearch ?? false,
|
|
207
|
+
sandbox: openaiCaps?.modes?.sandbox?.[
|
|
208
|
+
thinkIntents.includes(intent) ? 'think' : searchIntents.includes(intent) ? 'search' : 'execute'
|
|
209
|
+
] ?? 'danger-full-access',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// ── Outcome learning override ──
|
|
213
|
+
// If we have enough empirical data, let it influence model selection
|
|
214
|
+
const empiricalClaude = getBestModelFor(intent, 'claude', { minSamples: 5 });
|
|
215
|
+
if (empiricalClaude && empiricalClaude.successRate !== null && empiricalClaude.successRate > 0.8) {
|
|
216
|
+
const caps = getCapabilities(empiricalClaude.model);
|
|
217
|
+
if (caps && !caps.avoidFor?.includes(intent)) {
|
|
218
|
+
claudeModel = empiricalClaude.model;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const empiricalOpenai = getBestModelFor(intent, 'openai', { minSamples: 5 });
|
|
223
|
+
if (empiricalOpenai && empiricalOpenai.successRate !== null && empiricalOpenai.successRate > 0.8) {
|
|
224
|
+
const caps = getCapabilities(empiricalOpenai.model);
|
|
225
|
+
if (caps && !caps.avoidFor?.includes(intent)) {
|
|
226
|
+
openaiModel = empiricalOpenai.model;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Budget pressure adjustments ──
|
|
231
|
+
const reasons = [];
|
|
232
|
+
const isHighStakes = risk === 'critical' || risk === 'high';
|
|
233
|
+
const openaiModelRank = [
|
|
234
|
+
'gpt-4.1-mini', 'gpt-4.1', 'gpt-5.2', 'gpt-5.4-mini',
|
|
235
|
+
'gpt-5.3-codex', 'gpt-5.3-codex-spark', 'gpt-5.4', 'gpt-5.5',
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
if (budgetPressure > 0.9 && !isHighStakes) {
|
|
239
|
+
claudeModel = claudeModel === 'opus' ? 'sonnet' : 'haiku';
|
|
240
|
+
const oaiIdx = openaiModelRank.indexOf(openaiModel);
|
|
241
|
+
openaiModel = openaiModelRank[Math.max(0, oaiIdx - 2)] || 'gpt-4.1-mini';
|
|
242
|
+
claudeModes.fastMode = false;
|
|
243
|
+
claudeModes.extendedContext = false;
|
|
244
|
+
claudeModes.extendedThinking = false;
|
|
245
|
+
reasons.push('near limit, aggressive downgrade for non-critical task');
|
|
246
|
+
} else if (budgetPressure > 0.7 && !isHighStakes) {
|
|
247
|
+
claudeModel = claudeModel === 'opus' ? 'sonnet' : claudeModel === 'sonnet' ? 'haiku' : 'haiku';
|
|
248
|
+
const oaiIdx = openaiModelRank.indexOf(openaiModel);
|
|
249
|
+
openaiModel = openaiModelRank[Math.max(0, oaiIdx - 1)] || 'gpt-4.1-mini';
|
|
250
|
+
claudeModes.fastMode = false;
|
|
251
|
+
reasons.push('downgraded due to budget pressure');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Recalculate efforts after potential model change
|
|
255
|
+
const newCaps = getCapabilities(claudeModel);
|
|
256
|
+
claudeEffort = newCaps?.reasoning?.effortLevels
|
|
257
|
+
? (recommendEffort(claudeModel, complexity, risk) || effort)
|
|
258
|
+
: null;
|
|
259
|
+
openaiEffort = recommendEffort(openaiModel, complexity, risk) || effort;
|
|
260
|
+
|
|
261
|
+
// ── Preferred provider (think→claude, isolated execute→openai) ──
|
|
262
|
+
const preferred = thinkIntents.includes(intent) ? 'claude' : 'openai';
|
|
263
|
+
|
|
264
|
+
// ── Dual-brain recommendation ──
|
|
265
|
+
const dualBrain = risk === 'critical'
|
|
266
|
+
|| (thinkIntents.includes(intent) && (complexity === 'complex' || complexity === 'moderate'))
|
|
267
|
+
|| intent === 'security'
|
|
268
|
+
|| (intent === 'review' && risk !== 'low')
|
|
269
|
+
|| (intent === 'refactor' && risk === 'critical');
|
|
270
|
+
|
|
271
|
+
if (reasons.length === 0) {
|
|
272
|
+
reasons.push(`${claudeModel}/${openaiModel} matched to ${intent} @ ${complexity} complexity`);
|
|
273
|
+
}
|
|
274
|
+
if (empiricalClaude?.successRate !== null) reasons.push(`claude empirical: ${empiricalClaude.model} ${Math.round(empiricalClaude.successRate * 100)}%`);
|
|
275
|
+
if (empiricalOpenai?.successRate !== null) reasons.push(`openai empirical: ${empiricalOpenai.model} ${Math.round(empiricalOpenai.successRate * 100)}%`);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
claude: {
|
|
279
|
+
model: claudeModel,
|
|
280
|
+
effort: claudeEffort,
|
|
281
|
+
modes: claudeModes,
|
|
282
|
+
dispatch: getDispatchConfig(claudeModel),
|
|
283
|
+
},
|
|
284
|
+
openai: {
|
|
285
|
+
model: openaiModel,
|
|
286
|
+
effort: openaiEffort,
|
|
287
|
+
modes: openaiModes,
|
|
288
|
+
dispatch: getDispatchConfig(openaiModel),
|
|
289
|
+
},
|
|
290
|
+
preferred,
|
|
291
|
+
dualBrain,
|
|
292
|
+
reason: reasons.join('; '),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
if (process.argv[1] && new URL(import.meta.url).pathname === process.argv[1]) {
|
|
299
|
+
const args = process.argv.slice(2);
|
|
300
|
+
const description = args.find(a => !a.startsWith('--')) || '';
|
|
301
|
+
const filesArg = args.find(a => a.startsWith('--files=')) || args[args.indexOf('--files') + 1];
|
|
302
|
+
const budgetArg = args.find(a => a.startsWith('--budget-pressure=')) || args[args.indexOf('--budget-pressure') + 1];
|
|
303
|
+
const failuresArg = args.find(a => a.startsWith('--failures=')) || args[args.indexOf('--failures') + 1];
|
|
304
|
+
|
|
305
|
+
const files = (filesArg && !filesArg.startsWith('--'))
|
|
306
|
+
? filesArg.replace(/^--files=/, '').split(',').map(f => f.trim())
|
|
307
|
+
: [];
|
|
308
|
+
|
|
309
|
+
const budgetPressure = budgetArg
|
|
310
|
+
? parseFloat(budgetArg.replace(/^--budget-pressure=/, ''))
|
|
311
|
+
: 0;
|
|
312
|
+
|
|
313
|
+
const priorFailures = failuresArg
|
|
314
|
+
? parseInt(failuresArg.replace(/^--failures=/, ''), 10)
|
|
315
|
+
: 0;
|
|
316
|
+
|
|
317
|
+
if (!description) {
|
|
318
|
+
console.error('Usage: node hooks/task-classifier.mjs "task description" [--files a,b] [--budget-pressure 0.8] [--failures 1]');
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const profile = classifyTask(description, { files, priorFailures });
|
|
323
|
+
const selection = selectModelEffort(profile, { budgetPressure });
|
|
324
|
+
|
|
325
|
+
console.log(JSON.stringify({ profile, selection }, null, 2));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export { classifyTask, selectModelEffort, INTENTS };
|