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/src/models.mjs
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* models.mjs — Static model intelligence registry for the Dual-Brain Orchestrator.
|
|
3
|
-
*
|
|
4
|
-
* Pure in-memory registry of AI model capabilities used by routing modules.
|
|
5
|
-
* No file I/O, no API calls. Updated with each package release.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export const REGISTRY_VERSION = '2026-05-15';
|
|
9
|
-
export const REGISTRY_UPDATED = '2026-05-15';
|
|
10
|
-
|
|
11
|
-
export const MODEL_REGISTRY = Object.freeze({
|
|
12
|
-
'claude-opus-4-6': {
|
|
13
|
-
provider: 'anthropic',
|
|
14
|
-
name: 'Claude Opus 4.6',
|
|
15
|
-
tier: 'frontier',
|
|
16
|
-
contextWindow: 200000,
|
|
17
|
-
maxOutput: 32000,
|
|
18
|
-
strengths: ['complex reasoning', 'architecture', 'code review', 'long context', 'multi-step planning'],
|
|
19
|
-
weaknesses: ['speed', 'cost'],
|
|
20
|
-
costTier: 'high',
|
|
21
|
-
bestFor: ['think', 'review', 'architecture', 'complex debugging'],
|
|
22
|
-
speed: 'slow',
|
|
23
|
-
reasoning: true,
|
|
24
|
-
vision: true,
|
|
25
|
-
tools: true,
|
|
26
|
-
agentCapable: true,
|
|
27
|
-
},
|
|
28
|
-
'claude-sonnet-4-6': {
|
|
29
|
-
provider: 'anthropic',
|
|
30
|
-
name: 'Claude Sonnet 4.6',
|
|
31
|
-
tier: 'workhorse',
|
|
32
|
-
contextWindow: 200000,
|
|
33
|
-
maxOutput: 16000,
|
|
34
|
-
strengths: ['balanced speed/quality', 'code generation', 'editing', 'testing'],
|
|
35
|
-
weaknesses: ['less depth on architecture decisions'],
|
|
36
|
-
costTier: 'medium',
|
|
37
|
-
bestFor: ['execute', 'implement', 'test', 'fix'],
|
|
38
|
-
speed: 'medium',
|
|
39
|
-
reasoning: true,
|
|
40
|
-
vision: true,
|
|
41
|
-
tools: true,
|
|
42
|
-
agentCapable: true,
|
|
43
|
-
},
|
|
44
|
-
'claude-haiku-4-5-20251001': {
|
|
45
|
-
provider: 'anthropic',
|
|
46
|
-
name: 'Claude Haiku 4.5',
|
|
47
|
-
tier: 'fast',
|
|
48
|
-
contextWindow: 200000,
|
|
49
|
-
maxOutput: 8192,
|
|
50
|
-
strengths: ['speed', 'low cost', 'simple tasks', 'search', 'classification'],
|
|
51
|
-
weaknesses: ['complex reasoning', 'multi-step tasks'],
|
|
52
|
-
costTier: 'low',
|
|
53
|
-
bestFor: ['search', 'classify', 'simple edits', 'grep'],
|
|
54
|
-
speed: 'fast',
|
|
55
|
-
reasoning: false,
|
|
56
|
-
vision: true,
|
|
57
|
-
tools: true,
|
|
58
|
-
agentCapable: true,
|
|
59
|
-
},
|
|
60
|
-
'gpt-5.5': {
|
|
61
|
-
provider: 'openai',
|
|
62
|
-
name: 'GPT-5.5',
|
|
63
|
-
tier: 'frontier',
|
|
64
|
-
contextWindow: 1050000,
|
|
65
|
-
maxOutput: 128000,
|
|
66
|
-
strengths: ['massive context', 'complex professional work', 'reasoning', 'web search', 'code interpreter'],
|
|
67
|
-
weaknesses: ['cost', 'latency on complex reasoning'],
|
|
68
|
-
costTier: 'premium',
|
|
69
|
-
bestFor: ['think', 'review', 'research', 'long-context analysis'],
|
|
70
|
-
speed: 'medium',
|
|
71
|
-
reasoning: true,
|
|
72
|
-
vision: true,
|
|
73
|
-
tools: true,
|
|
74
|
-
agentCapable: true,
|
|
75
|
-
},
|
|
76
|
-
'o3': {
|
|
77
|
-
provider: 'openai',
|
|
78
|
-
name: 'o3',
|
|
79
|
-
tier: 'reasoning',
|
|
80
|
-
contextWindow: 200000,
|
|
81
|
-
maxOutput: 100000,
|
|
82
|
-
strengths: ['deep reasoning', 'math', 'code', 'science', 'multi-step logic'],
|
|
83
|
-
weaknesses: ['speed', 'cost', 'simple tasks overkill'],
|
|
84
|
-
costTier: 'premium',
|
|
85
|
-
bestFor: ['think', 'complex debugging', 'algorithm design', 'security analysis'],
|
|
86
|
-
speed: 'slow',
|
|
87
|
-
reasoning: true,
|
|
88
|
-
vision: true,
|
|
89
|
-
tools: true,
|
|
90
|
-
agentCapable: true,
|
|
91
|
-
},
|
|
92
|
-
'gpt-4o': {
|
|
93
|
-
provider: 'openai',
|
|
94
|
-
name: 'GPT-4o',
|
|
95
|
-
tier: 'workhorse',
|
|
96
|
-
contextWindow: 128000,
|
|
97
|
-
maxOutput: 16384,
|
|
98
|
-
strengths: ['balanced', 'fast', 'multimodal', 'tool use'],
|
|
99
|
-
weaknesses: ['less depth than frontier models'],
|
|
100
|
-
costTier: 'medium',
|
|
101
|
-
bestFor: ['execute', 'implement', 'chat', 'multimodal'],
|
|
102
|
-
speed: 'fast',
|
|
103
|
-
reasoning: false,
|
|
104
|
-
vision: true,
|
|
105
|
-
tools: true,
|
|
106
|
-
agentCapable: true,
|
|
107
|
-
},
|
|
108
|
-
'gpt-4o-mini': {
|
|
109
|
-
provider: 'openai',
|
|
110
|
-
name: 'GPT-4o Mini',
|
|
111
|
-
tier: 'fast',
|
|
112
|
-
contextWindow: 128000,
|
|
113
|
-
maxOutput: 16384,
|
|
114
|
-
strengths: ['very fast', 'very cheap', 'good enough for simple tasks'],
|
|
115
|
-
weaknesses: ['complex reasoning', 'nuanced code review'],
|
|
116
|
-
costTier: 'low',
|
|
117
|
-
bestFor: ['search', 'classify', 'simple tasks', 'formatting'],
|
|
118
|
-
speed: 'fast',
|
|
119
|
-
reasoning: false,
|
|
120
|
-
vision: true,
|
|
121
|
-
tools: true,
|
|
122
|
-
agentCapable: false,
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const COST_TIER_ORDER = ['low', 'medium', 'high', 'premium'];
|
|
127
|
-
const SPEED_ORDER = ['fast', 'medium', 'slow'];
|
|
128
|
-
const TIER_ORDER = ['fast', 'workhorse', 'reasoning', 'frontier'];
|
|
129
|
-
|
|
130
|
-
const MODEL_QUIRKS = {
|
|
131
|
-
'claude-opus-4-6': [
|
|
132
|
-
'Best for architecture decisions',
|
|
133
|
-
'Use for code review when quality matters',
|
|
134
|
-
'Expensive — reserve for high-value tasks',
|
|
135
|
-
],
|
|
136
|
-
'claude-sonnet-4-6': [
|
|
137
|
-
'Best general-purpose worker',
|
|
138
|
-
'Good balance of speed and quality',
|
|
139
|
-
'Use Agent(model: "sonnet") for work dispatch',
|
|
140
|
-
],
|
|
141
|
-
'claude-haiku-4-5-20251001': [
|
|
142
|
-
'Best for fast search and classification',
|
|
143
|
-
'Not reliable for multi-step edits',
|
|
144
|
-
'Use for disposable search agents',
|
|
145
|
-
],
|
|
146
|
-
'gpt-5.5': [
|
|
147
|
-
'Massive 1M+ context window',
|
|
148
|
-
'Good challenger for dual-brain think',
|
|
149
|
-
'Web search and code interpreter available',
|
|
150
|
-
],
|
|
151
|
-
'o3': [
|
|
152
|
-
'Purpose-built for deep reasoning chains',
|
|
153
|
-
'Strong challenger for security analysis',
|
|
154
|
-
'Slow — avoid for quick tasks',
|
|
155
|
-
],
|
|
156
|
-
'gpt-4o': [
|
|
157
|
-
'Fast and multimodal',
|
|
158
|
-
'Solid fallback for execute-tier GPT work',
|
|
159
|
-
'Native tool use support',
|
|
160
|
-
],
|
|
161
|
-
'gpt-4o-mini': [
|
|
162
|
-
'Cheapest GPT option for simple tasks',
|
|
163
|
-
'Not agent-capable — single-shot only',
|
|
164
|
-
'Good for classification and formatting',
|
|
165
|
-
],
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export function getModel(modelId) {
|
|
169
|
-
return MODEL_REGISTRY[modelId] ?? null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function getModelsForTask(taskType, provider = null) {
|
|
173
|
-
const entries = Object.entries(MODEL_REGISTRY);
|
|
174
|
-
|
|
175
|
-
const filtered = entries.filter(([, m]) => {
|
|
176
|
-
if (provider && m.provider !== provider) return false;
|
|
177
|
-
return true;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
filtered.sort(([, a], [, b]) => {
|
|
181
|
-
const aMatch = a.bestFor.includes(taskType) ? 1 : 0;
|
|
182
|
-
const bMatch = b.bestFor.includes(taskType) ? 1 : 0;
|
|
183
|
-
if (bMatch !== aMatch) return bMatch - aMatch;
|
|
184
|
-
|
|
185
|
-
const aTier = TIER_ORDER.indexOf(a.tier);
|
|
186
|
-
const bTier = TIER_ORDER.indexOf(b.tier);
|
|
187
|
-
if (bTier !== aTier) return bTier - aTier;
|
|
188
|
-
|
|
189
|
-
return SPEED_ORDER.indexOf(a.speed) - SPEED_ORDER.indexOf(b.speed);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
return filtered.map(([id, m]) => ({ id, ...m }));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function getBestModel(taskType, constraints = {}) {
|
|
196
|
-
const { provider = null, maxCost = null, preferSpeed = false, requireReasoning = false, minContext = 0 } = constraints;
|
|
197
|
-
|
|
198
|
-
let candidates = getModelsForTask(taskType, provider);
|
|
199
|
-
|
|
200
|
-
if (requireReasoning) {
|
|
201
|
-
candidates = candidates.filter(m => m.reasoning);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (minContext > 0) {
|
|
205
|
-
candidates = candidates.filter(m => m.contextWindow >= minContext);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (maxCost) {
|
|
209
|
-
const maxIdx = COST_TIER_ORDER.indexOf(maxCost);
|
|
210
|
-
if (maxIdx !== -1) {
|
|
211
|
-
candidates = candidates.filter(m => COST_TIER_ORDER.indexOf(m.costTier) <= maxIdx);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (preferSpeed) {
|
|
216
|
-
candidates.sort((a, b) => SPEED_ORDER.indexOf(a.speed) - SPEED_ORDER.indexOf(b.speed));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return candidates[0] ?? null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function compareModels(modelA, modelB) {
|
|
223
|
-
const a = MODEL_REGISTRY[modelA];
|
|
224
|
-
const b = MODEL_REGISTRY[modelB];
|
|
225
|
-
|
|
226
|
-
if (!a || !b) {
|
|
227
|
-
return {
|
|
228
|
-
contextAdvantage: 'tie',
|
|
229
|
-
speedAdvantage: 'tie',
|
|
230
|
-
costAdvantage: 'tie',
|
|
231
|
-
reasoningAdvantage: 'tie',
|
|
232
|
-
recommendation: 'One or both model IDs are unknown.',
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const ctxAdv = a.contextWindow > b.contextWindow ? 'a'
|
|
237
|
-
: b.contextWindow > a.contextWindow ? 'b'
|
|
238
|
-
: 'tie';
|
|
239
|
-
|
|
240
|
-
const spdA = SPEED_ORDER.indexOf(a.speed);
|
|
241
|
-
const spdB = SPEED_ORDER.indexOf(b.speed);
|
|
242
|
-
const spdAdv = spdA < spdB ? 'a' : spdB < spdA ? 'b' : 'tie';
|
|
243
|
-
|
|
244
|
-
const costA = COST_TIER_ORDER.indexOf(a.costTier);
|
|
245
|
-
const costB = COST_TIER_ORDER.indexOf(b.costTier);
|
|
246
|
-
const costAdv = costA < costB ? 'a' : costB < costA ? 'b' : 'tie';
|
|
247
|
-
|
|
248
|
-
const rsnAdv = (a.reasoning && !b.reasoning) ? 'a'
|
|
249
|
-
: (b.reasoning && !a.reasoning) ? 'b'
|
|
250
|
-
: 'tie';
|
|
251
|
-
|
|
252
|
-
const aAdvantages = [ctxAdv, spdAdv, costAdv, rsnAdv].filter(v => v === 'a').length;
|
|
253
|
-
const bAdvantages = [ctxAdv, spdAdv, costAdv, rsnAdv].filter(v => v === 'b').length;
|
|
254
|
-
|
|
255
|
-
let recommendation;
|
|
256
|
-
if (aAdvantages > bAdvantages) {
|
|
257
|
-
recommendation = `Use ${a.name} for ${a.bestFor.slice(0, 2).join('/')}; ${b.name} as a lighter alternative.`;
|
|
258
|
-
} else if (bAdvantages > aAdvantages) {
|
|
259
|
-
recommendation = `Use ${b.name} for ${b.bestFor.slice(0, 2).join('/')}; ${a.name} as a lighter alternative.`;
|
|
260
|
-
} else {
|
|
261
|
-
recommendation = `${a.name} and ${b.name} are comparable — choose based on provider availability.`;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return { contextAdvantage: ctxAdv, speedAdvantage: spdAdv, costAdvantage: costAdv, reasoningAdvantage: rsnAdv, recommendation };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export function getRegistryAge() {
|
|
268
|
-
const updated = new Date(REGISTRY_UPDATED);
|
|
269
|
-
const now = new Date();
|
|
270
|
-
return Math.floor((now - updated) / (1000 * 60 * 60 * 24));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const COST_SYMBOL = { low: '$', medium: '$$', high: '$$$', premium: '$$$$' };
|
|
274
|
-
|
|
275
|
-
function fmtCtx(n) {
|
|
276
|
-
if (n >= 1000000) return `${(n / 1000000).toFixed(2)}M ctx`;
|
|
277
|
-
return `${Math.round(n / 1000)}K ctx`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export function formatModelTable(provider = null) {
|
|
281
|
-
const groups = {};
|
|
282
|
-
for (const [id, m] of Object.entries(MODEL_REGISTRY)) {
|
|
283
|
-
if (provider && m.provider !== provider) continue;
|
|
284
|
-
if (!groups[m.provider]) groups[m.provider] = [];
|
|
285
|
-
groups[m.provider].push({ id, ...m });
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const lines = [];
|
|
289
|
-
const providerLabels = { anthropic: 'Claude Models', openai: 'OpenAI Models' };
|
|
290
|
-
const providerOrder = ['anthropic', 'openai'];
|
|
291
|
-
|
|
292
|
-
for (const prov of providerOrder) {
|
|
293
|
-
if (!groups[prov]) continue;
|
|
294
|
-
lines.push(`${providerLabels[prov] ?? prov}:`);
|
|
295
|
-
for (const m of groups[prov]) {
|
|
296
|
-
const displayName = m.provider === 'anthropic' ? m.name.replace(/^Claude /, '') : m.name;
|
|
297
|
-
const name = displayName.padEnd(12);
|
|
298
|
-
const tier = m.tier.padEnd(10);
|
|
299
|
-
const ctx = fmtCtx(m.contextWindow).padEnd(10);
|
|
300
|
-
const speed = m.speed.padEnd(8);
|
|
301
|
-
const cost = (COST_SYMBOL[m.costTier] ?? '?').padEnd(6);
|
|
302
|
-
const tasks = m.bestFor.slice(0, 2).join('/');
|
|
303
|
-
lines.push(` ${name}${tier}${ctx}${speed}${cost}${tasks}`);
|
|
304
|
-
}
|
|
305
|
-
lines.push('');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return lines.join('\n').trimEnd();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export function getModelQuirks(modelId) {
|
|
312
|
-
return MODEL_QUIRKS[modelId] ?? [];
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export function suggestModel(intent, risk, complexity, availableProviders = ['anthropic', 'openai']) {
|
|
316
|
-
const intentLower = (intent || '').toLowerCase();
|
|
317
|
-
|
|
318
|
-
const TASK_MAP = {
|
|
319
|
-
think: ['architect', 'design', 'plan', 'review', 'security', 'audit', 'analysis', 'analyze'],
|
|
320
|
-
execute: ['implement', 'edit', 'fix', 'refactor', 'test', 'build', 'write', 'update', 'create'],
|
|
321
|
-
search: ['search', 'find', 'grep', 'look', 'explore', 'classify', 'format', 'list'],
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
let taskType = 'execute';
|
|
325
|
-
for (const [type, keywords] of Object.entries(TASK_MAP)) {
|
|
326
|
-
if (keywords.some(kw => intentLower.includes(kw))) {
|
|
327
|
-
taskType = type;
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if ((risk === 'critical' || risk === 'high') && taskType !== 'think') {
|
|
333
|
-
taskType = 'think';
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const requireReasoning = complexity === 'complex' || risk === 'critical';
|
|
337
|
-
const preferSpeed = risk === 'low' && complexity === 'simple';
|
|
338
|
-
const maxCost = risk === 'low' && complexity !== 'complex' ? 'medium' : null;
|
|
339
|
-
|
|
340
|
-
const candidates = getModelsForTask(taskType)
|
|
341
|
-
.filter(m => availableProviders.includes(m.provider))
|
|
342
|
-
.filter(m => !requireReasoning || m.reasoning)
|
|
343
|
-
.filter(m => !maxCost || COST_TIER_ORDER.indexOf(m.costTier) <= COST_TIER_ORDER.indexOf(maxCost));
|
|
344
|
-
|
|
345
|
-
if (preferSpeed) {
|
|
346
|
-
candidates.sort((a, b) => SPEED_ORDER.indexOf(a.speed) - SPEED_ORDER.indexOf(b.speed));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const best = candidates[0] ?? null;
|
|
350
|
-
if (!best) {
|
|
351
|
-
return { model: null, reason: 'No suitable model found for the given constraints.', alternatives: [] };
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const reason = [
|
|
355
|
-
`Selected ${best.name} for ${taskType}-tier work`,
|
|
356
|
-
risk !== 'low' ? `(${risk} risk)` : null,
|
|
357
|
-
requireReasoning ? '— reasoning required' : null,
|
|
358
|
-
].filter(Boolean).join(' ');
|
|
359
|
-
|
|
360
|
-
const alternatives = candidates.slice(1, 3).map(m => ({ id: m.id, name: m.name }));
|
|
361
|
-
|
|
362
|
-
return { model: best.id, reason, alternatives };
|
|
363
|
-
}
|
package/src/narrative.mjs
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// narrative.mjs — HEAD's running narrative: prose it writes to itself between turns.
|
|
2
|
-
// Not structured data. A paragraph or two that captures where we are, what just
|
|
3
|
-
// happened, what's brewing. Loaded at the top of each turn so HEAD is immediately
|
|
4
|
-
// "in the song" without reconstructing from scattered JSON.
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
|
|
9
|
-
const STATE_DIR = join(process.cwd(), '.dualbrain');
|
|
10
|
-
const NARRATIVE_FILE = join(STATE_DIR, 'narrative.md');
|
|
11
|
-
const NARRATIVE_HISTORY = join(STATE_DIR, 'narrative-history.jsonl');
|
|
12
|
-
|
|
13
|
-
const MAX_NARRATIVE_LENGTH = 2000;
|
|
14
|
-
const MAX_HISTORY_ENTRIES = 20;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Load the current running narrative. Returns empty string if none exists.
|
|
18
|
-
* This is meant to be injected at the top of HEAD's context each turn.
|
|
19
|
-
*/
|
|
20
|
-
export function load() {
|
|
21
|
-
try {
|
|
22
|
-
if (existsSync(NARRATIVE_FILE)) {
|
|
23
|
-
return readFileSync(NARRATIVE_FILE, 'utf8').trim();
|
|
24
|
-
}
|
|
25
|
-
} catch {}
|
|
26
|
-
return '';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Write a new narrative, replacing the old one.
|
|
31
|
-
* The old narrative is archived to history before overwrite.
|
|
32
|
-
*
|
|
33
|
-
* @param {string} prose - HEAD's current understanding in prose form.
|
|
34
|
-
* Should answer: Where are we? What just happened? What's brewing?
|
|
35
|
-
* What did the user care about? What should I not forget?
|
|
36
|
-
*/
|
|
37
|
-
export function write(prose) {
|
|
38
|
-
if (!prose || typeof prose !== 'string') return;
|
|
39
|
-
|
|
40
|
-
const trimmed = prose.slice(0, MAX_NARRATIVE_LENGTH).trim();
|
|
41
|
-
if (!trimmed) return;
|
|
42
|
-
|
|
43
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
44
|
-
|
|
45
|
-
// Archive current before overwriting
|
|
46
|
-
const current = load();
|
|
47
|
-
if (current) {
|
|
48
|
-
_appendHistory(current);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
writeFileSync(NARRATIVE_FILE, trimmed + '\n');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Evolve the narrative — append new observations without replacing everything.
|
|
56
|
-
* Used after dispatches return, after user says something illuminating,
|
|
57
|
-
* or after a wave completes.
|
|
58
|
-
*
|
|
59
|
-
* @param {string} addition - New prose to weave into the narrative.
|
|
60
|
-
* @param {object} opts
|
|
61
|
-
* @param {boolean} opts.replace - If true, replace entirely instead of appending.
|
|
62
|
-
*/
|
|
63
|
-
export function evolve(addition, { replace = false } = {}) {
|
|
64
|
-
if (!addition || typeof addition !== 'string') return;
|
|
65
|
-
|
|
66
|
-
if (replace) {
|
|
67
|
-
write(addition);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const current = load();
|
|
72
|
-
const combined = current
|
|
73
|
-
? current + '\n\n' + addition.trim()
|
|
74
|
-
: addition.trim();
|
|
75
|
-
|
|
76
|
-
// If too long, keep the newest portion (recency bias for immersion)
|
|
77
|
-
const final = combined.length > MAX_NARRATIVE_LENGTH
|
|
78
|
-
? combined.slice(-MAX_NARRATIVE_LENGTH)
|
|
79
|
-
: combined;
|
|
80
|
-
|
|
81
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
82
|
-
writeFileSync(NARRATIVE_FILE, final.trim() + '\n');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Generate a narrative excerpt suitable for a dispatch envelope.
|
|
87
|
-
* Shorter than the full narrative — just enough context for a worker
|
|
88
|
-
* to understand the "why" without the full stream of consciousness.
|
|
89
|
-
*
|
|
90
|
-
* @param {number} maxLength - Max chars for the excerpt (default 500)
|
|
91
|
-
* @returns {string}
|
|
92
|
-
*/
|
|
93
|
-
export function excerpt(maxLength = 500) {
|
|
94
|
-
const full = load();
|
|
95
|
-
if (!full) return '';
|
|
96
|
-
if (full.length <= maxLength) return full;
|
|
97
|
-
|
|
98
|
-
// Take the last N chars — most recent context is most relevant for workers
|
|
99
|
-
const trimmed = full.slice(-maxLength);
|
|
100
|
-
// Find the first sentence boundary to avoid mid-thought cuts
|
|
101
|
-
const firstPeriod = trimmed.indexOf('. ');
|
|
102
|
-
if (firstPeriod > 0 && firstPeriod < maxLength * 0.4) {
|
|
103
|
-
return trimmed.slice(firstPeriod + 2);
|
|
104
|
-
}
|
|
105
|
-
return trimmed;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Get recent narrative history entries (for warm memory tier).
|
|
110
|
-
* @param {number} n - Number of recent entries to retrieve
|
|
111
|
-
* @returns {Array<{ts: number, text: string}>}
|
|
112
|
-
*/
|
|
113
|
-
export function recentHistory(n = 5) {
|
|
114
|
-
try {
|
|
115
|
-
if (!existsSync(NARRATIVE_HISTORY)) return [];
|
|
116
|
-
const lines = readFileSync(NARRATIVE_HISTORY, 'utf8').trim().split('\n').filter(Boolean);
|
|
117
|
-
return lines.slice(-n).map(line => {
|
|
118
|
-
try { return JSON.parse(line); } catch { return null; }
|
|
119
|
-
}).filter(Boolean);
|
|
120
|
-
} catch {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Persist the current narrative for precompact survival.
|
|
127
|
-
* Called by the precompact hook before context compression.
|
|
128
|
-
* Returns the narrative that was persisted (for confirmation).
|
|
129
|
-
*/
|
|
130
|
-
export function persist() {
|
|
131
|
-
const current = load();
|
|
132
|
-
if (!current) return '';
|
|
133
|
-
// Narrative is already on disk — this just ensures it's fresh
|
|
134
|
-
// and archives a snapshot with explicit "precompact" marker
|
|
135
|
-
_appendHistory(current, { reason: 'precompact' });
|
|
136
|
-
return current;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Clear the narrative (used in testing or session reset).
|
|
141
|
-
*/
|
|
142
|
-
export function clear() {
|
|
143
|
-
try {
|
|
144
|
-
if (existsSync(NARRATIVE_FILE)) {
|
|
145
|
-
writeFileSync(NARRATIVE_FILE, '');
|
|
146
|
-
}
|
|
147
|
-
} catch {}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── Internal ──────────────────────────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
function _appendHistory(text, meta = {}) {
|
|
153
|
-
try {
|
|
154
|
-
const entry = JSON.stringify({ ts: Date.now(), text: text.slice(0, 800), ...meta });
|
|
155
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
156
|
-
|
|
157
|
-
// Cap history file
|
|
158
|
-
let existing = '';
|
|
159
|
-
if (existsSync(NARRATIVE_HISTORY)) {
|
|
160
|
-
existing = readFileSync(NARRATIVE_HISTORY, 'utf8');
|
|
161
|
-
const lines = existing.trim().split('\n').filter(Boolean);
|
|
162
|
-
if (lines.length >= MAX_HISTORY_ENTRIES) {
|
|
163
|
-
existing = lines.slice(-MAX_HISTORY_ENTRIES + 1).join('\n') + '\n';
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
writeFileSync(NARRATIVE_HISTORY, existing + entry + '\n');
|
|
168
|
-
} catch {}
|
|
169
|
-
}
|
package/src/nextstep.mjs
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { existsSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
const AUTH_PAT = /\b(auth|credential|secret|token|password|encrypt|permission|oauth|jwt|api.?key)\b/i;
|
|
6
|
-
const TEST_PAT = /\b(test|spec|\.test\.|\.spec\.)\b/i;
|
|
7
|
-
|
|
8
|
-
function gitBranch(cwd) {
|
|
9
|
-
try { return execSync('git rev-parse --abbrev-ref HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); }
|
|
10
|
-
catch { return null; }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function packageVersionChanged(cwd, files) {
|
|
14
|
-
if (!files.some(f => f.includes('package.json'))) return false;
|
|
15
|
-
try { return execSync('git diff HEAD~1 HEAD -- package.json', { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString().includes('"version"'); }
|
|
16
|
-
catch { return false; }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function changelogExists(cwd) {
|
|
20
|
-
return ['CHANGELOG.md', 'CHANGELOG', 'changelog.md'].some(f => existsSync(join(cwd, f)));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function step(priority, type, message, command, reason) {
|
|
24
|
-
return { priority, type, message, command, reason };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function dedup(steps) {
|
|
28
|
-
const seen = new Set();
|
|
29
|
-
return steps.filter(s => { if (seen.has(s.type)) return false; seen.add(s.type); return true; });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function suggestNextSteps(completedTask = {}, outcome = {}, cwd = process.cwd()) {
|
|
33
|
-
try {
|
|
34
|
-
const { prompt = '', files = [], trigger } = completedTask;
|
|
35
|
-
const { success = false, filesChanged = [], error = '' } = outcome;
|
|
36
|
-
const steps = [];
|
|
37
|
-
const branch = gitBranch(cwd);
|
|
38
|
-
const onMain = !branch || branch === 'main' || branch === 'master';
|
|
39
|
-
const allFiles = [...files, ...filesChanged];
|
|
40
|
-
const hasAuth = allFiles.some(f => AUTH_PAT.test(f));
|
|
41
|
-
const hasTests = allFiles.some(f => TEST_PAT.test(f));
|
|
42
|
-
const n = filesChanged.length;
|
|
43
|
-
const fs = (count) => `${count} file${count !== 1 ? 's' : ''}`;
|
|
44
|
-
|
|
45
|
-
if (trigger === 'auto-commit') {
|
|
46
|
-
steps.push(!onMain && branch
|
|
47
|
-
? step(4, 'pr', `Open a pull request for branch "${branch}"`, `gh pr create --head ${branch}`, `On feature branch — changes need review before merging`)
|
|
48
|
-
: step(3, 'deploy', 'Deploy or tag a release', null, 'Committed to main — ready to ship or version'));
|
|
49
|
-
if (packageVersionChanged(cwd, filesChanged))
|
|
50
|
-
steps.push(step(4, 'publish', 'Publish the new package version to npm', 'npm publish', 'package.json version changed in this commit'));
|
|
51
|
-
if (changelogExists(cwd) && !filesChanged.some(f => /changelog/i.test(f)))
|
|
52
|
-
steps.push(step(2, 'changelog', 'Update CHANGELOG with this change', null, 'CHANGELOG exists but was not updated'));
|
|
53
|
-
|
|
54
|
-
} else if (trigger === 'review' || trigger === 'think') {
|
|
55
|
-
const issues = error || /issue|problem|fail|error|warn/i.test(prompt);
|
|
56
|
-
steps.push(issues
|
|
57
|
-
? step(5, 'fix', 'Fix the issues identified in the review', `dual-brain go "fix issues identified in review"`, 'Review found problems that need resolution')
|
|
58
|
-
: step(3, 'continue', 'Ship it — the review looks good', null, 'Review completed without critical findings'));
|
|
59
|
-
|
|
60
|
-
} else if (!success) {
|
|
61
|
-
steps.push(step(5, 'fix', 'Retry with higher reasoning depth', `dual-brain go --tier think "${prompt}"`, 'Task failed — escalating tier may resolve it'));
|
|
62
|
-
if (error && /test/i.test(error))
|
|
63
|
-
steps.push(step(4, 'test', 'Look at test output for clues', null, 'Error references tests — check output to understand the failure'));
|
|
64
|
-
steps.push(step(3, 'review', 'Try a different approach — dual-brain think', `node .claude/hooks/dual-brain-think.mjs --question "${prompt}"`, 'GPT perspective may surface a different solution'));
|
|
65
|
-
|
|
66
|
-
} else if (success && n > 0) {
|
|
67
|
-
if (!hasTests)
|
|
68
|
-
steps.push(step(5, 'test', `Run tests to verify the ${fs(n)} changed`, 'npm test', `${fs(n)} changed without test verification`));
|
|
69
|
-
if (hasAuth)
|
|
70
|
-
steps.push(step(5, 'review', 'Run a security review on auth/credential changes', `node .claude/hooks/dual-brain-think.mjs --question "Security review: ${prompt}"`, 'Auth or security-sensitive files were modified'));
|
|
71
|
-
if (n > 3)
|
|
72
|
-
steps.push(step(4, 'review', `Review the ${n}-file diff before committing`, 'git diff', `${n} files changed — quick diff review before committing`));
|
|
73
|
-
if (!onMain && branch)
|
|
74
|
-
steps.push(step(4, 'pr', `Open a pull request for branch "${branch}"`, `gh pr create --head ${branch}`, `Changes are on feature branch "${branch}" — ready for PR`));
|
|
75
|
-
if (hasTests)
|
|
76
|
-
steps.push(step(3, 'commit', 'Commit changes', 'git add -p && git commit', 'Tests passed — safe to commit'));
|
|
77
|
-
steps.push(step(2, 'continue', 'Check for edge cases in the changed code', null, 'Edge cases are often missed during implementation'));
|
|
78
|
-
if (changelogExists(cwd) && !filesChanged.some(f => /changelog/i.test(f)))
|
|
79
|
-
steps.push(step(2, 'changelog', 'Update CHANGELOG with this change', null, 'CHANGELOG exists but was not updated in this batch'));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const sorted = dedup(steps.sort((a, b) => b.priority - a.priority));
|
|
83
|
-
return {
|
|
84
|
-
steps: sorted,
|
|
85
|
-
topSuggestion: sorted.length > 0 ? `→ ${sorted[0].message}` : '→ Nothing urgent — task complete',
|
|
86
|
-
};
|
|
87
|
-
} catch {
|
|
88
|
-
return { steps: [], topSuggestion: '→ Task complete' };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function formatNextSteps(steps, limit = 3) {
|
|
93
|
-
if (!steps?.length) return '';
|
|
94
|
-
return `📋 Next steps\n${steps.slice(0, limit).map((s, i) => ` ${i + 1}. ${s.message}`).join('\n')}`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function getTopSuggestion(steps) {
|
|
98
|
-
if (!steps?.length) return '→ Task complete';
|
|
99
|
-
return `→ ${steps[0].message}`;
|
|
100
|
-
}
|