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.
Files changed (309) hide show
  1. package/.dual-brain/docs/claude-code-extension-points.md +32 -0
  2. package/.dual-brain/docs/data-tools-capabilities.md +181 -0
  3. package/.dual-brain/docs/ecosystem-tools.md +91 -0
  4. package/.dual-brain/docs/panel-handoff.md +124 -0
  5. package/.dual-brain/docs/ruflo-analysis.md +48 -0
  6. package/bin/dual-brain.mjs +56 -56
  7. package/dist/mcp-server/index.d.ts +27 -0
  8. package/dist/mcp-server/index.js +359 -0
  9. package/dist/mcp-server/index.js.map +1 -0
  10. package/dist/src/agent-protocol.d.ts +163 -0
  11. package/dist/src/agent-protocol.js +368 -0
  12. package/dist/src/agent-protocol.js.map +1 -0
  13. package/dist/src/agents/registry.d.ts +52 -0
  14. package/dist/src/agents/registry.js +393 -0
  15. package/dist/src/agents/registry.js.map +1 -0
  16. package/dist/src/awareness.d.ts +93 -0
  17. package/dist/src/awareness.js +406 -0
  18. package/dist/src/awareness.js.map +1 -0
  19. package/dist/src/brief.d.ts +48 -0
  20. package/dist/src/brief.js +179 -0
  21. package/dist/src/brief.js.map +1 -0
  22. package/dist/src/calibration.d.ts +32 -0
  23. package/dist/src/calibration.js +133 -0
  24. package/dist/src/calibration.js.map +1 -0
  25. package/dist/src/checkpoint.d.ts +33 -0
  26. package/dist/src/checkpoint.js +99 -0
  27. package/dist/src/checkpoint.js.map +1 -0
  28. package/dist/src/ci-triage.d.ts +33 -0
  29. package/dist/src/ci-triage.js +193 -0
  30. package/dist/src/ci-triage.js.map +1 -0
  31. package/dist/src/cognitive-loop.d.ts +56 -0
  32. package/dist/src/cognitive-loop.js +495 -0
  33. package/dist/src/cognitive-loop.js.map +1 -0
  34. package/dist/src/collaboration.d.ts +147 -0
  35. package/dist/src/collaboration.js +438 -0
  36. package/dist/src/collaboration.js.map +1 -0
  37. package/dist/src/context-intel.d.ts +47 -0
  38. package/dist/src/context-intel.js +156 -0
  39. package/dist/src/context-intel.js.map +1 -0
  40. package/dist/src/context.d.ts +53 -0
  41. package/dist/src/context.js +332 -0
  42. package/dist/src/context.js.map +1 -0
  43. package/dist/src/continuity.d.ts +89 -0
  44. package/dist/src/continuity.js +230 -0
  45. package/dist/src/continuity.js.map +1 -0
  46. package/dist/src/cost-tracker.d.ts +47 -0
  47. package/dist/src/cost-tracker.js +170 -0
  48. package/dist/src/cost-tracker.js.map +1 -0
  49. package/dist/src/debrief.d.ts +53 -0
  50. package/dist/src/debrief.js +222 -0
  51. package/dist/src/debrief.js.map +1 -0
  52. package/dist/src/decide.d.ts +96 -0
  53. package/dist/src/decide.js +744 -0
  54. package/dist/src/decide.js.map +1 -0
  55. package/dist/src/decompose.d.ts +39 -0
  56. package/dist/src/decompose.js +218 -0
  57. package/dist/src/decompose.js.map +1 -0
  58. package/dist/src/detect.d.ts +91 -0
  59. package/dist/src/detect.js +544 -0
  60. package/dist/src/detect.js.map +1 -0
  61. package/dist/src/dispatch.d.ts +154 -0
  62. package/dist/src/dispatch.js +1306 -0
  63. package/dist/src/dispatch.js.map +1 -0
  64. package/dist/src/doctor.d.ts +421 -0
  65. package/dist/src/doctor.js +1689 -0
  66. package/dist/src/doctor.js.map +1 -0
  67. package/dist/src/engine.d.ts +70 -0
  68. package/dist/src/engine.js +155 -0
  69. package/dist/src/engine.js.map +1 -0
  70. package/dist/src/envelope.d.ts +36 -0
  71. package/dist/src/envelope.js +80 -0
  72. package/dist/src/envelope.js.map +1 -0
  73. package/dist/src/failure-memory.d.ts +55 -0
  74. package/dist/src/failure-memory.js +175 -0
  75. package/dist/src/failure-memory.js.map +1 -0
  76. package/dist/src/fx.d.ts +87 -0
  77. package/dist/src/fx.js +272 -0
  78. package/dist/src/fx.js.map +1 -0
  79. package/dist/src/governance.d.ts +93 -0
  80. package/dist/src/governance.js +261 -0
  81. package/dist/src/governance.js.map +1 -0
  82. package/dist/src/handoff.d.ts +11 -0
  83. package/dist/src/handoff.js +90 -0
  84. package/dist/src/handoff.js.map +1 -0
  85. package/dist/src/head-protocol.d.ts +76 -0
  86. package/dist/src/head-protocol.js +109 -0
  87. package/dist/src/head-protocol.js.map +1 -0
  88. package/dist/src/head.d.ts +222 -0
  89. package/dist/src/head.js +765 -0
  90. package/dist/src/head.js.map +1 -0
  91. package/dist/src/health.d.ts +132 -0
  92. package/dist/src/health.js +435 -0
  93. package/dist/src/health.js.map +1 -0
  94. package/dist/src/inbox.d.ts +70 -0
  95. package/dist/src/inbox.js +218 -0
  96. package/dist/src/inbox.js.map +1 -0
  97. package/dist/src/index.d.ts +33 -0
  98. package/dist/src/index.js +38 -0
  99. package/dist/src/index.js.map +1 -0
  100. package/dist/src/install-hooks.d.ts +13 -0
  101. package/dist/src/install-hooks.js +88 -0
  102. package/dist/src/install-hooks.js.map +1 -0
  103. package/dist/src/integrity.d.ts +59 -0
  104. package/dist/src/integrity.js +206 -0
  105. package/dist/src/integrity.js.map +1 -0
  106. package/dist/src/intelligence.d.ts +104 -0
  107. package/dist/src/intelligence.js +391 -0
  108. package/dist/src/intelligence.js.map +1 -0
  109. package/dist/src/ledger.d.ts +54 -0
  110. package/dist/src/ledger.js +179 -0
  111. package/dist/src/ledger.js.map +1 -0
  112. package/dist/src/living-docs.d.ts +14 -0
  113. package/dist/src/living-docs.js +197 -0
  114. package/dist/src/living-docs.js.map +1 -0
  115. package/dist/src/memory-tiers.d.ts +37 -0
  116. package/dist/src/memory-tiers.js +160 -0
  117. package/dist/src/memory-tiers.js.map +1 -0
  118. package/dist/src/model-profiles.d.ts +65 -0
  119. package/dist/src/model-profiles.js +568 -0
  120. package/dist/src/model-profiles.js.map +1 -0
  121. package/dist/src/models.d.ts +58 -0
  122. package/dist/src/models.js +327 -0
  123. package/dist/src/models.js.map +1 -0
  124. package/dist/src/narrative.d.ts +54 -0
  125. package/dist/src/narrative.js +163 -0
  126. package/dist/src/narrative.js.map +1 -0
  127. package/dist/src/nextstep.d.ts +16 -0
  128. package/dist/src/nextstep.js +103 -0
  129. package/dist/src/nextstep.js.map +1 -0
  130. package/dist/src/observer.d.ts +18 -0
  131. package/dist/src/observer.js +251 -0
  132. package/dist/src/observer.js.map +1 -0
  133. package/dist/src/outcome.d.ts +110 -0
  134. package/dist/src/outcome.js +377 -0
  135. package/dist/src/outcome.js.map +1 -0
  136. package/dist/src/pipeline.d.ts +167 -0
  137. package/dist/src/pipeline.js +1503 -0
  138. package/dist/src/pipeline.js.map +1 -0
  139. package/dist/src/playbook.d.ts +59 -0
  140. package/dist/src/playbook.js +238 -0
  141. package/dist/src/playbook.js.map +1 -0
  142. package/dist/src/pr-agent.d.ts +97 -0
  143. package/dist/src/pr-agent.js +195 -0
  144. package/dist/src/pr-agent.js.map +1 -0
  145. package/dist/src/predictive.d.ts +57 -0
  146. package/dist/src/predictive.js +230 -0
  147. package/dist/src/predictive.js.map +1 -0
  148. package/dist/src/profile.d.ts +294 -0
  149. package/dist/src/profile.js +1347 -0
  150. package/dist/src/profile.js.map +1 -0
  151. package/dist/src/prompt-audit.d.ts +22 -0
  152. package/dist/src/prompt-audit.js +194 -0
  153. package/dist/src/prompt-audit.js.map +1 -0
  154. package/dist/src/prompt-intel.d.ts +12 -0
  155. package/dist/src/prompt-intel.js +321 -0
  156. package/dist/src/prompt-intel.js.map +1 -0
  157. package/dist/src/provider-context.d.ts +121 -0
  158. package/dist/src/provider-context.js +222 -0
  159. package/dist/src/provider-context.js.map +1 -0
  160. package/dist/src/provider-manager.d.ts +92 -0
  161. package/dist/src/provider-manager.js +428 -0
  162. package/dist/src/provider-manager.js.map +1 -0
  163. package/dist/src/receipt.d.ts +87 -0
  164. package/dist/src/receipt.js +326 -0
  165. package/dist/src/receipt.js.map +1 -0
  166. package/dist/src/recommendations.d.ts +13 -0
  167. package/dist/src/recommendations.js +291 -0
  168. package/dist/src/recommendations.js.map +1 -0
  169. package/dist/src/redact.d.ts +15 -0
  170. package/dist/src/redact.js +129 -0
  171. package/dist/src/redact.js.map +1 -0
  172. package/dist/src/replit.d.ts +397 -0
  173. package/dist/src/replit.js +1160 -0
  174. package/dist/src/replit.js.map +1 -0
  175. package/dist/src/repo.d.ts +149 -0
  176. package/dist/src/repo.js +416 -0
  177. package/dist/src/repo.js.map +1 -0
  178. package/dist/src/revert.d.ts +30 -0
  179. package/dist/src/revert.js +166 -0
  180. package/dist/src/revert.js.map +1 -0
  181. package/dist/src/room.d.ts +102 -0
  182. package/dist/src/room.js +212 -0
  183. package/dist/src/room.js.map +1 -0
  184. package/dist/src/routing-advisor.d.ts +57 -0
  185. package/dist/src/routing-advisor.js +221 -0
  186. package/dist/src/routing-advisor.js.map +1 -0
  187. package/dist/src/self-correct.d.ts +40 -0
  188. package/dist/src/self-correct.js +137 -0
  189. package/dist/src/self-correct.js.map +1 -0
  190. package/dist/src/session-lock.d.ts +35 -0
  191. package/dist/src/session-lock.js +134 -0
  192. package/dist/src/session-lock.js.map +1 -0
  193. package/dist/src/session.d.ts +267 -0
  194. package/dist/src/session.js +1660 -0
  195. package/dist/src/session.js.map +1 -0
  196. package/dist/src/settings-tui.d.ts +5 -0
  197. package/dist/src/settings-tui.js +422 -0
  198. package/dist/src/settings-tui.js.map +1 -0
  199. package/dist/src/setup-flow.d.ts +63 -0
  200. package/dist/src/setup-flow.js +233 -0
  201. package/dist/src/setup-flow.js.map +1 -0
  202. package/dist/src/signal.d.ts +19 -0
  203. package/dist/src/signal.js +122 -0
  204. package/dist/src/signal.js.map +1 -0
  205. package/dist/src/simmer.d.ts +85 -0
  206. package/dist/src/simmer.js +224 -0
  207. package/dist/src/simmer.js.map +1 -0
  208. package/dist/src/state-export.d.ts +129 -0
  209. package/dist/src/state-export.js +233 -0
  210. package/dist/src/state-export.js.map +1 -0
  211. package/dist/src/strategy.d.ts +54 -0
  212. package/dist/src/strategy.js +95 -0
  213. package/dist/src/strategy.js.map +1 -0
  214. package/dist/src/subscription.d.ts +40 -0
  215. package/dist/src/subscription.js +189 -0
  216. package/dist/src/subscription.js.map +1 -0
  217. package/dist/src/templates.d.ts +208 -0
  218. package/dist/src/templates.js +238 -0
  219. package/dist/src/templates.js.map +1 -0
  220. package/dist/src/test.d.ts +9 -0
  221. package/dist/src/test.js +1173 -0
  222. package/dist/src/test.js.map +1 -0
  223. package/dist/src/think-engine.d.ts +67 -0
  224. package/dist/src/think-engine.js +412 -0
  225. package/dist/src/think-engine.js.map +1 -0
  226. package/dist/src/tui.d.ts +71 -0
  227. package/dist/src/tui.js +242 -0
  228. package/dist/src/tui.js.map +1 -0
  229. package/dist/src/types.d.ts +177 -0
  230. package/dist/src/types.js +6 -0
  231. package/dist/src/types.js.map +1 -0
  232. package/dist/src/update-check.d.ts +7 -0
  233. package/dist/src/update-check.js +36 -0
  234. package/dist/src/update-check.js.map +1 -0
  235. package/dist/src/wave-planner.d.ts +30 -0
  236. package/dist/src/wave-planner.js +281 -0
  237. package/dist/src/wave-planner.js.map +1 -0
  238. package/hooks/head-guard.sh +41 -0
  239. package/hooks/task-classifier.mjs +328 -0
  240. package/hooks/vibe-router.mjs +387 -0
  241. package/package.json +29 -153
  242. package/src/agents/registry.mjs +0 -405
  243. package/src/awareness.mjs +0 -425
  244. package/src/brief.mjs +0 -266
  245. package/src/calibration.mjs +0 -148
  246. package/src/checkpoint.mjs +0 -109
  247. package/src/ci-triage.mjs +0 -191
  248. package/src/cognitive-loop.mjs +0 -562
  249. package/src/collaboration.mjs +0 -545
  250. package/src/context-intel.mjs +0 -158
  251. package/src/context.mjs +0 -389
  252. package/src/continuity.mjs +0 -298
  253. package/src/cost-tracker.mjs +0 -184
  254. package/src/debrief.mjs +0 -228
  255. package/src/decide.mjs +0 -1099
  256. package/src/decompose.mjs +0 -331
  257. package/src/detect.mjs +0 -702
  258. package/src/dispatch.mjs +0 -1447
  259. package/src/doctor.mjs +0 -1607
  260. package/src/envelope.mjs +0 -139
  261. package/src/failure-memory.mjs +0 -178
  262. package/src/fx.mjs +0 -276
  263. package/src/governance.mjs +0 -279
  264. package/src/handoff.mjs +0 -87
  265. package/src/head-protocol.mjs +0 -128
  266. package/src/head.mjs +0 -952
  267. package/src/health.mjs +0 -528
  268. package/src/inbox.mjs +0 -195
  269. package/src/index.mjs +0 -44
  270. package/src/install-hooks.mjs +0 -100
  271. package/src/integrity.mjs +0 -245
  272. package/src/intelligence.mjs +0 -447
  273. package/src/ledger.mjs +0 -196
  274. package/src/living-docs.mjs +0 -210
  275. package/src/memory-tiers.mjs +0 -193
  276. package/src/models.mjs +0 -363
  277. package/src/narrative.mjs +0 -169
  278. package/src/nextstep.mjs +0 -100
  279. package/src/observer.mjs +0 -241
  280. package/src/outcome.mjs +0 -400
  281. package/src/pipeline.mjs +0 -1711
  282. package/src/playbook.mjs +0 -257
  283. package/src/pr-agent.mjs +0 -214
  284. package/src/predictive.mjs +0 -250
  285. package/src/profile.mjs +0 -1411
  286. package/src/prompt-audit.mjs +0 -231
  287. package/src/prompt-intel.mjs +0 -325
  288. package/src/provider-context.mjs +0 -257
  289. package/src/receipt.mjs +0 -344
  290. package/src/recommendations.mjs +0 -296
  291. package/src/redact.mjs +0 -192
  292. package/src/replit.mjs +0 -1210
  293. package/src/repo.mjs +0 -445
  294. package/src/revert.mjs +0 -149
  295. package/src/routing-advisor.mjs +0 -204
  296. package/src/self-correct.mjs +0 -147
  297. package/src/session-lock.mjs +0 -160
  298. package/src/session.mjs +0 -1655
  299. package/src/settings-tui.mjs +0 -373
  300. package/src/setup-flow.mjs +0 -223
  301. package/src/signal.mjs +0 -115
  302. package/src/simmer.mjs +0 -241
  303. package/src/strategy.mjs +0 -235
  304. package/src/subscription.mjs +0 -212
  305. package/src/templates.mjs +0 -260
  306. package/src/think-engine.mjs +0 -428
  307. package/src/tui.mjs +0 -276
  308. package/src/update-check.mjs +0 -35
  309. 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
- }