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
@@ -1,279 +0,0 @@
1
- // governance.mjs — Model tier enforcement + multi-model collaboration
2
-
3
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
4
- import { join } from 'node:path';
5
-
6
- // ─── Tier Definitions ────────────────────────────────────────────────────────
7
-
8
- export const MODEL_TIERS = Object.freeze({
9
- 1: {
10
- label: 'lightweight',
11
- models: ['claude-haiku-4-5-20251001', 'haiku', 'gpt-4o-mini', 'o4-mini'],
12
- autoApprove: true,
13
- },
14
- 2: {
15
- label: 'standard',
16
- models: ['claude-sonnet-4-6', 'sonnet', 'gpt-4o', 'gpt-4.1'],
17
- autoApprove: true,
18
- },
19
- 3: {
20
- label: 'heavy',
21
- models: ['claude-opus-4-6', 'claude-opus-4-7', 'opus', 'o3'],
22
- autoApprove: false, // requires consent check per profile
23
- },
24
- });
25
-
26
- // Reverse lookup: model ID → tier number
27
- export function getModelTier(modelId) {
28
- if (!modelId) return 2; // default to standard
29
- const normalized = String(modelId).toLowerCase();
30
- for (const [tier, def] of Object.entries(MODEL_TIERS)) {
31
- if (def.models.some(m => normalized.includes(m))) return Number(tier);
32
- }
33
- return 2; // unknown models default to standard
34
- }
35
-
36
- // ─── Task Scoring ────────────────────────────────────────────────────────────
37
-
38
- export function scoreTask(detection) {
39
- // detection comes from detect.mjs — has intent, risk, scope, files, etc.
40
- const scores = {
41
- complexity: 0, // 0-3
42
- risk: 0, // 0-3
43
- creativity: 0, // 0-2
44
- precision: 0, // 0-2
45
- contextVolume: 0, // 0-3
46
- };
47
-
48
- // Complexity from file count / scope
49
- const fileCount = detection?.files?.length || detection?.scope?.fileCount || 0;
50
- if (fileCount >= 6) scores.complexity = 3;
51
- else if (fileCount >= 3) scores.complexity = 2;
52
- else if (fileCount >= 1) scores.complexity = 1;
53
-
54
- // Risk from explicit risk field or keywords
55
- const risk = detection?.risk || detection?.riskLevel || 'low';
56
- const riskMap = { low: 0, medium: 1, high: 2, critical: 3 };
57
- scores.risk = riskMap[risk] ?? 0;
58
-
59
- // Boost risk for security/auth/billing keywords
60
- const text = (detection?.objective || detection?.intent || '').toLowerCase();
61
- if (/\b(auth|security|credential|secret|billing|payment|migration|delete|drop)\b/.test(text)) {
62
- scores.risk = Math.max(scores.risk, 2);
63
- }
64
-
65
- // Creativity from intent type
66
- const intent = (detection?.intent || detection?.type || '').toLowerCase();
67
- if (/\b(architect|design|brainstorm|explore|research)\b/.test(intent)) scores.creativity = 2;
68
- else if (/\b(refactor|plan|decide)\b/.test(intent)) scores.creativity = 1;
69
-
70
- // Precision — one-shot tasks need higher precision
71
- if (/\b(security|deploy|publish|migration)\b/.test(text)) scores.precision = 2;
72
- else if (/\b(implement|build|create)\b/.test(text)) scores.precision = 1;
73
-
74
- // Context volume
75
- const contextSize = detection?.contextTokens || detection?.estimatedContext || 0;
76
- if (contextSize > 200000) scores.contextVolume = 3;
77
- else if (contextSize > 50000) scores.contextVolume = 2;
78
- else if (contextSize > 10000) scores.contextVolume = 1;
79
-
80
- return scores;
81
- }
82
-
83
- export function computeRequiredTier(scores) {
84
- const total = Object.values(scores).reduce((a, b) => a + b, 0);
85
- if (total <= 2) return 1;
86
- if (total <= 6) return 2;
87
- return 3;
88
- }
89
-
90
- // ─── Governance Assessment ───────────────────────────────────────────────────
91
-
92
- // Profile governance defaults
93
- const GOVERNANCE_PERMISSIONS = {
94
- 'auto': { 1: 'auto', 2: 'auto', 3: 'ask' },
95
- 'balanced': { 1: 'auto', 2: 'auto', 3: 'ask' },
96
- 'cost-saver': { 1: 'auto', 2: 'auto', 3: 'deny' },
97
- 'quality-first': { 1: 'auto', 2: 'auto', 3: 'auto' },
98
- };
99
-
100
- // Pricing per million tokens (input/output) for cost estimation
101
- const MODEL_PRICING = {
102
- 'haiku': { input: 1.00, output: 5.00 },
103
- 'sonnet': { input: 3.00, output: 15.00 },
104
- 'opus': { input: 5.00, output: 25.00 },
105
- 'gpt-4o-mini': { input: 0.15, output: 0.60 },
106
- 'gpt-4o': { input: 2.50, output: 10.00 },
107
- 'gpt-4.1': { input: 2.00, output: 8.00 },
108
- 'o3': { input: 2.00, output: 8.00 },
109
- 'o4-mini': { input: 1.10, output: 4.40 },
110
- };
111
-
112
- function estimateCost(modelId, estimatedTokens = 8000) {
113
- const normalized = String(modelId).toLowerCase();
114
- let pricing = MODEL_PRICING['sonnet']; // default
115
- for (const [key, p] of Object.entries(MODEL_PRICING)) {
116
- if (normalized.includes(key)) { pricing = p; break; }
117
- }
118
- // Assume 20% input, 80% output for agent tasks
119
- const inputTokens = estimatedTokens * 0.2;
120
- const outputTokens = estimatedTokens * 0.8;
121
- return (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
122
- }
123
-
124
- export function assessGovernance(model, detection, profile) {
125
- const tier = getModelTier(model);
126
- const scores = scoreTask(detection);
127
- const requiredTier = computeRequiredTier(scores);
128
- const workStyle = profile?.workStyle || profile?.name || 'auto';
129
- const permissions = GOVERNANCE_PERMISSIONS[workStyle] || GOVERNANCE_PERMISSIONS['auto'];
130
- const permission = permissions[tier] || 'ask';
131
- const estimatedCost = estimateCost(model);
132
-
133
- return {
134
- requestedTier: tier,
135
- requiredTier,
136
- overProvisioned: tier > requiredTier,
137
- underProvisioned: tier < requiredTier,
138
- permission, // 'auto' | 'ask' | 'deny'
139
- estimatedCost,
140
- scores,
141
- justification: buildJustification(scores, tier, requiredTier),
142
- };
143
- }
144
-
145
- function buildJustification(scores, requestedTier, requiredTier) {
146
- const parts = [];
147
- if (scores.risk >= 2) parts.push('high-risk');
148
- if (scores.complexity >= 2) parts.push('complex');
149
- if (scores.creativity >= 2) parts.push('creative/architectural');
150
- if (scores.contextVolume >= 2) parts.push('large-context');
151
- if (requestedTier > requiredTier) parts.push('over-provisioned');
152
- if (requestedTier < requiredTier) parts.push('under-provisioned');
153
- return parts.join(', ') || 'standard task';
154
- }
155
-
156
- // ─── Collaboration Assessment ────────────────────────────────────────────────
157
-
158
- export function shouldCollaborate(detection, governance, profile) {
159
- // Never collaborate on tier-1 tasks
160
- if (governance.requiredTier <= 1) return { collaborate: false };
161
-
162
- // Never collaborate in cost-saver mode unless explicitly requested
163
- const workStyle = profile?.workStyle || profile?.name || 'auto';
164
- if (workStyle === 'cost-saver') return { collaborate: false };
165
-
166
- // Check collaboration triggers (need ANY two)
167
- const triggers = [];
168
- const text = (detection?.objective || detection?.intent || '').toLowerCase();
169
-
170
- if (/\b(auth|security|credential|billing|migration)\b/.test(text)) triggers.push('irreversibility');
171
- if (detection?.ambiguity === 'high' || /\b(should we|how to|best approach|tradeoff)\b/.test(text)) triggers.push('ambiguity');
172
- if (detection?.novelty === 'high' || /\b(new|first time|never done|greenfield)\b/.test(text)) triggers.push('novelty');
173
- if ((detection?.files?.length || 0) >= 4 && /\b(security|performance|ux)\b/.test(text)) triggers.push('cross-domain');
174
- if (governance.requestedTier >= 3 && detection?.confidence && detection.confidence < 0.8) triggers.push('low-confidence');
175
-
176
- const shouldDo = triggers.length >= 2;
177
-
178
- return {
179
- collaborate: shouldDo,
180
- triggers,
181
- pattern: shouldDo ? selectPattern(triggers, detection) : null,
182
- estimatedOverhead: shouldDo ? estimateCost('gpt-4.1') : 0, // secondary model cost
183
- };
184
- }
185
-
186
- function selectPattern(triggers, detection) {
187
- const text = (detection?.objective || detection?.intent || '').toLowerCase();
188
-
189
- // Security → adversarial review
190
- if (triggers.includes('irreversibility') && /\b(auth|security|credential)\b/.test(text)) {
191
- return 'adversarial-review';
192
- }
193
-
194
- // Architecture/greenfield → second opinion (perspective rotation reserved for Phase 4)
195
- if (triggers.includes('novelty') || triggers.includes('ambiguity')) {
196
- return 'second-opinion';
197
- }
198
-
199
- // Default
200
- return 'second-opinion';
201
- }
202
-
203
- // ─── Governance State (Session Budget Tracking) ──────────────────────────────
204
-
205
- const STATE_FILE = '.dualbrain/governance-state.json';
206
- const SESSION_GAP_MS = 30 * 60 * 1000; // 30 min gap = new session
207
-
208
- export function loadGovernanceState(cwd) {
209
- const statePath = join(cwd, STATE_FILE);
210
- try {
211
- const raw = JSON.parse(readFileSync(statePath, 'utf8'));
212
- // Check if session is stale
213
- const lastDispatch = raw.dispatches?.[raw.dispatches.length - 1];
214
- if (lastDispatch && (Date.now() - Date.parse(lastDispatch.ts)) > SESSION_GAP_MS) {
215
- // Stale session — reset
216
- return freshState();
217
- }
218
- return raw;
219
- } catch {
220
- return freshState();
221
- }
222
- }
223
-
224
- function freshState() {
225
- return {
226
- sessionStartedAt: new Date().toISOString(),
227
- dispatches: [],
228
- totalEstimatedCost: 0,
229
- tierCounts: { 1: 0, 2: 0, 3: 0 },
230
- };
231
- }
232
-
233
- export function recordDispatch(cwd, tier, model, estimatedCost, approved = true) {
234
- const state = loadGovernanceState(cwd);
235
- state.dispatches.push({
236
- tier,
237
- model: String(model),
238
- estimatedCost,
239
- approved,
240
- ts: new Date().toISOString(),
241
- });
242
- state.totalEstimatedCost += estimatedCost;
243
- state.tierCounts[tier] = (state.tierCounts[tier] || 0) + 1;
244
-
245
- const dir = join(cwd, '.dualbrain');
246
- mkdirSync(dir, { recursive: true });
247
- writeFileSync(join(cwd, STATE_FILE), JSON.stringify(state, null, 2) + '\n');
248
- return state;
249
- }
250
-
251
- export function checkBudget(cwd, orchestratorConfig) {
252
- const state = loadGovernanceState(cwd);
253
- const sessionLimit = orchestratorConfig?.budgets?.session_limit_usd || 10;
254
- const remaining = sessionLimit - state.totalEstimatedCost;
255
-
256
- return {
257
- spent: state.totalEstimatedCost,
258
- remaining,
259
- limit: sessionLimit,
260
- warning: remaining < sessionLimit * 0.2, // <20% remaining
261
- blocked: remaining <= 0,
262
- tierCounts: state.tierCounts,
263
- };
264
- }
265
-
266
- // ─── Format for User Display ─────────────────────────────────────────────────
267
-
268
- export function formatGovernancePrompt(governance, collaboration) {
269
- const tierLabel = MODEL_TIERS[governance.requestedTier]?.label || 'unknown';
270
- const lines = [];
271
-
272
- lines.push(`[governance] Task requires ${tierLabel} model (tier ${governance.requestedTier}, ~$${governance.estimatedCost.toFixed(2)})`);
273
- if (governance.justification) lines.push(` Reason: ${governance.justification}`);
274
- if (collaboration?.collaborate) {
275
- lines.push(` + ${collaboration.pattern} with secondary model (+$${collaboration.estimatedOverhead.toFixed(2)})`);
276
- }
277
-
278
- return lines.join('\n');
279
- }
package/src/handoff.mjs DELETED
@@ -1,87 +0,0 @@
1
- // handoff.mjs — Typed handoffs between pipeline stages
2
- import { writeFileSync, readFileSync, existsSync, mkdirSync, renameSync, unlinkSync, readdirSync } from 'node:fs';
3
- import { join } from 'node:path';
4
-
5
- export const HANDOFF_TYPES = {
6
- 'think-to-work': { required: ['objective', 'files', 'criteria'], optional: ['context', 'confidence'] },
7
- 'work-to-review': { required: ['filesChanged', 'objective'], optional: ['diff', 'criteria', 'testsRun'] },
8
- 'review-to-head': { required: ['pass'], optional: ['findings', 'recommendation', 'severity'] },
9
- };
10
-
11
- const hDir = (cwd) => join(cwd || process.cwd(), '.dualbrain', 'handoffs');
12
- const hPath = (id, f, t, cwd) => join(hDir(cwd), `${id}_${f}_${t}.json`);
13
-
14
- function validate(from, to, data) {
15
- const schema = HANDOFF_TYPES[`${from}-to-${to}`];
16
- if (!schema) return;
17
- for (const f of schema.required) {
18
- if (!(f in data)) process.stderr.write(`[handoff] warn: missing required field '${f}' in ${from}-to-${to}\n`);
19
- }
20
- }
21
-
22
- export function createHandoff(fromStage, toStage, data, runId, cwd) {
23
- try {
24
- validate(fromStage, toStage, data);
25
- const safeRunId = String(runId).replace(/[^a-z0-9_-]/gi, '_').slice(0, 50);
26
- const dir = hDir(cwd);
27
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
28
- const record = { fromStage, toStage, runId: safeRunId, createdAt: new Date().toISOString(), data };
29
- const dest = hPath(safeRunId, fromStage, toStage, cwd); const tmp = dest + '.tmp';
30
- writeFileSync(tmp, JSON.stringify(record, null, 2), 'utf8');
31
- try { renameSync(tmp, dest); } catch { writeFileSync(dest, JSON.stringify(record, null, 2), 'utf8'); }
32
- return record;
33
- } catch { return null; }
34
- }
35
-
36
- export function consumeHandoff(runId, fromStage, toStage, cwd) {
37
- try {
38
- const safeRunId = String(runId).replace(/[^a-z0-9_-]/gi, '_').slice(0, 50);
39
- const p = hPath(safeRunId, fromStage, toStage, cwd);
40
- if (!existsSync(p)) return null;
41
- const record = JSON.parse(readFileSync(p, 'utf8'));
42
- try { unlinkSync(p); } catch { /* best-effort */ }
43
- return record;
44
- } catch { return null; }
45
- }
46
-
47
- export function buildHandoffContext(handoff, targetRole) {
48
- if (!handoff?.data) return '';
49
- const d = handoff.data;
50
- const lines = (...parts) => parts.filter(Boolean).join('\n');
51
- const list = (v) => Array.isArray(v) ? v.join(', ') : (v ?? '');
52
- const items = (v) => Array.isArray(v) ? v.map(x => ` - ${x}`).join('\n') : (v ?? '');
53
-
54
- if (targetRole === 'worker' && handoff.fromStage === 'thinker') return lines(
55
- '## Handoff from Think Stage',
56
- `**Objective:** ${d.objective ?? '(none)'}`,
57
- `**Files in scope:** ${list(d.files) || 'unspecified'}`,
58
- d.criteria ? `**Acceptance criteria:**\n${items(d.criteria)}` : '',
59
- d.context ? `**Context:** ${d.context}` : '',
60
- d.confidence != null ? `**Thinker confidence:** ${d.confidence}` : '',
61
- );
62
- if (targetRole === 'reviewer' && handoff.fromStage === 'worker') return lines(
63
- '## Handoff from Work Stage',
64
- `**Objective:** ${d.objective ?? '(none)'}`,
65
- `**Files changed:** ${list(d.filesChanged) || 'unknown'}`,
66
- d.criteria ? `**Original criteria:** ${Array.isArray(d.criteria) ? d.criteria.join('; ') : d.criteria}` : '',
67
- d.testsRun ? `**Tests run:** ${d.testsRun}` : '',
68
- d.diff ? `**Diff summary:**\n\`\`\`\n${d.diff.slice(0, 1200)}\n\`\`\`` : '',
69
- );
70
- if (targetRole === 'head' && handoff.fromStage === 'reviewer') return lines(
71
- `## Review Result: ${d.pass ? 'PASS' : 'FAIL'}`,
72
- d.findings ? `**Findings:**\n${items(d.findings)}` : '',
73
- d.recommendation ? `**Recommendation:** ${d.recommendation}` : '',
74
- d.severity ? `**Severity:** ${d.severity}` : '',
75
- );
76
- return JSON.stringify(handoff.data, null, 2);
77
- }
78
-
79
- export function cleanupHandoffs(runId, cwd) {
80
- try {
81
- const dir = hDir(cwd);
82
- if (!existsSync(dir)) return;
83
- for (const name of readdirSync(dir)) {
84
- if (name.startsWith(`${runId}_`)) try { unlinkSync(join(dir, name)); } catch { /* best-effort */ }
85
- }
86
- } catch { /* non-throwing */ }
87
- }
@@ -1,128 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { processTurn, loadState } from './head.mjs';
4
-
5
- const DUALBRAIN = join(process.cwd(), '.dualbrain');
6
- const DELIBERATION_FILE = join(DUALBRAIN, 'deliberation.json');
7
-
8
- /**
9
- * Write the deliberation artifact after running HEAD's cognitive pipeline.
10
- * This is the contract between HEAD's thinking and the deliberation-gate hook.
11
- *
12
- * @param {string} userMessage - The user's message to deliberate on
13
- * @param {object} context - Optional context (files, priorFailures, patterns, etc.)
14
- * @returns {object} The full deliberation result from processTurn
15
- */
16
- export function writeDeliberation(userMessage, context = {}) {
17
- const state = loadState();
18
- const result = processTurn(state, userMessage, context);
19
-
20
- // Determine if there are multiple independent sub-tasks that could be parallelized
21
- const dispatchPlan = _deriveDispatchPlan(result, context);
22
-
23
- // Build the artifact
24
- const artifact = {
25
- timestamp: Date.now(),
26
- createdAt: Date.now(),
27
- message: userMessage.slice(0, 500),
28
-
29
- // Core deliberation fields
30
- action: result.action,
31
- result: result.result,
32
- shouldAskUser: result.shouldAskUser,
33
- confidence: result.result?.confidence || null,
34
-
35
- // Obligations and noticings
36
- obligations: result.obligations || [],
37
- surfaceNoticings: result.result?.surfaceNoticings || [],
38
-
39
- // Dispatch plan (parallel-wave support)
40
- dispatchPlan,
41
-
42
- // Metadata
43
- depth: result.depth,
44
- rationale: result.rationale,
45
- situation: {
46
- taskShape: result.situation?.taskShape || null,
47
- urgency: result.situation?.urgency || null,
48
- scope: result.situation?.taskShape?.scope || null,
49
- },
50
- };
51
-
52
- // Write atomically
53
- mkdirSync(DUALBRAIN, { recursive: true });
54
- const tmpFile = DELIBERATION_FILE + '.tmp.' + process.pid;
55
- writeFileSync(tmpFile, JSON.stringify(artifact, null, 2));
56
- renameSync(tmpFile, DELIBERATION_FILE);
57
-
58
- return result;
59
- }
60
-
61
- /**
62
- * Read the current deliberation artifact.
63
- * @returns {object|null} The deliberation artifact, or null if not found/unreadable.
64
- */
65
- export function readDeliberation() {
66
- try {
67
- if (!existsSync(DELIBERATION_FILE)) return null;
68
- return JSON.parse(readFileSync(DELIBERATION_FILE, 'utf8'));
69
- } catch {
70
- return null;
71
- }
72
- }
73
-
74
- /**
75
- * Check if the current deliberation is fresh (within maxAgeMs).
76
- * @param {number} maxAgeMs - Maximum age in milliseconds (default 60000)
77
- * @returns {boolean} True if deliberation exists and is fresh.
78
- */
79
- export function isDeliberationFresh(maxAgeMs = 60_000) {
80
- const delib = readDeliberation();
81
- if (!delib) return false;
82
- const timestamp = delib.timestamp || delib.createdAt || 0;
83
- return (Date.now() - timestamp) <= maxAgeMs;
84
- }
85
-
86
- // ── Internal helpers ─────────────────────────────────────────────────────────
87
-
88
- /**
89
- * Derive a dispatch plan when the situation has multiple independent sub-tasks.
90
- * Returns null if parallel dispatch is not applicable.
91
- */
92
- function _deriveDispatchPlan(result, context) {
93
- const situation = result.situation;
94
- if (!situation) return null;
95
-
96
- // Only generate a parallel plan for multi-file, non-trivial work
97
- const scope = situation.taskShape?.scope;
98
- const actionType = result.action?.type;
99
-
100
- if (scope !== 'large' && scope !== 'medium') return null;
101
- if (actionType !== 'dispatch' && actionType !== 'proceed') return null;
102
-
103
- // Check for independent sub-tasks from context
104
- const subtasks = context.subtasks || [];
105
- if (subtasks.length < 2) {
106
- // Heuristic: large scope with multiple files could be parallel
107
- const fileCount = situation.material?.touchedFiles?.length || 0;
108
- if (fileCount < 3) return null;
109
-
110
- return {
111
- strategy: 'parallel-wave',
112
- id: Date.now().toString(36),
113
- expectedParallel: Math.min(fileCount, 5),
114
- waveSize: Math.min(fileCount, 5),
115
- reason: `${fileCount} independent files detected`,
116
- };
117
- }
118
-
119
- return {
120
- strategy: 'parallel-wave',
121
- id: Date.now().toString(36),
122
- expectedParallel: subtasks.length,
123
- waveSize: subtasks.length,
124
- subtasks: subtasks.map(t => typeof t === 'string' ? t : t.name || t.description),
125
- reason: `${subtasks.length} independent sub-tasks identified`,
126
- };
127
- }
128
-