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/envelope.mjs DELETED
@@ -1,139 +0,0 @@
1
- // envelope.mjs — Dispatch envelopes that carry understanding to workers.
2
- //
3
- // Instead of workers getting bare instructions ("edit file X, add Y"),
4
- // they get an envelope containing:
5
- // 1. Context preamble — narrative excerpt explaining the "why"
6
- // 2. Contract — the typed task (objective, scope, acceptance criteria)
7
- // 3. Preventions — predicted failure modes and how to avoid them
8
- // 4. Debrief format — how to report back
9
- //
10
- // This is the difference between "do this thing" and "here's where we are,
11
- // here's why this matters, here's what to do, here's what to watch for."
12
-
13
- import * as narrative from './narrative.mjs';
14
-
15
- /**
16
- * @typedef {object} Envelope
17
- * @property {string} preamble - Narrative context for the worker
18
- * @property {string} contract - The actual task specification
19
- * @property {string} preventions - Predicted failure modes and mitigations
20
- * @property {string} debriefFormat - How to report back
21
- * @property {string} full - Complete prompt ready to send
22
- */
23
-
24
- /**
25
- * Build a dispatch envelope for a worker agent.
26
- *
27
- * @param {object} agentSpec - From wave-planner
28
- * @param {string} agentSpec.objective - What to accomplish
29
- * @param {string[]} agentSpec.scope - Files/areas in scope
30
- * @param {string} agentSpec.tier - Worker tier (execute, search, review, etc)
31
- * @param {object} opts
32
- * @param {string} opts.preventions - From predictive.mjs
33
- * @param {string} opts.debriefInstruction - From debrief.mjs
34
- * @param {string} opts.inboxBrief - Messages for this worker
35
- * @param {object} opts.contract - Additional contract fields (acceptance criteria, risk, allowed ops)
36
- * @returns {Envelope}
37
- */
38
- export function build(agentSpec, opts = {}) {
39
- const { preventions, debriefInstruction, inboxBrief, contract } = opts;
40
-
41
- // Get narrative excerpt — the "being in the song" piece
42
- const preamble = _buildPreamble(agentSpec);
43
-
44
- // Build contract section
45
- const contractText = _buildContract(agentSpec, contract);
46
-
47
- // Assemble full prompt
48
- const sections = [];
49
-
50
- if (preamble) {
51
- sections.push(`## Context\n${preamble}`);
52
- }
53
-
54
- sections.push(`## Task\n${contractText}`);
55
-
56
- if (inboxBrief) {
57
- sections.push(`## Notes\n${inboxBrief}`);
58
- }
59
-
60
- if (preventions) {
61
- sections.push(`## Watch For\n${preventions}`);
62
- }
63
-
64
- if (debriefInstruction) {
65
- sections.push(`## When Done\n${debriefInstruction}`);
66
- }
67
-
68
- const full = sections.join('\n\n');
69
-
70
- return {
71
- preamble,
72
- contract: contractText,
73
- preventions: preventions || '',
74
- debriefFormat: debriefInstruction || '',
75
- full,
76
- };
77
- }
78
-
79
- /**
80
- * Build a lightweight envelope for simple/fast dispatches.
81
- * Used when the task is straightforward and doesn't need full context.
82
- *
83
- * @param {string} objective
84
- * @param {string[]} scope
85
- * @returns {string} Simple prompt string
86
- */
87
- export function buildLight(objective, scope = []) {
88
- const parts = [objective];
89
- if (scope.length > 0) {
90
- parts.push(`Scope: ${scope.join(', ')}`);
91
- }
92
- return parts.join('\n');
93
- }
94
-
95
- // ── Internal ──────────────────────────────────────────────────────────────────
96
-
97
- function _buildPreamble(agentSpec) {
98
- const narr = narrative.excerpt(400);
99
- if (!narr) return '';
100
-
101
- // Tailor the preamble based on tier
102
- const tier = (agentSpec.tier || '').toLowerCase();
103
-
104
- if (tier === 'search' || tier === 'recon') {
105
- // Search agents need less context, more focus on what to look for
106
- return narr.length > 200 ? narr.slice(-200) : narr;
107
- }
108
-
109
- // Implementation agents get fuller context
110
- return narr;
111
- }
112
-
113
- function _buildContract(agentSpec, extra = {}) {
114
- const parts = [];
115
-
116
- parts.push(agentSpec.objective);
117
-
118
- if (agentSpec.scope?.length) {
119
- parts.push(`\nScope: ${agentSpec.scope.join(', ')}`);
120
- }
121
-
122
- if (extra?.acceptanceCriteria) {
123
- parts.push(`\nDone when: ${extra.acceptanceCriteria}`);
124
- }
125
-
126
- if (extra?.risk) {
127
- parts.push(`\nRisk level: ${extra.risk}`);
128
- }
129
-
130
- if (extra?.allowedOps) {
131
- parts.push(`\nAllowed: ${extra.allowedOps.join(', ')}`);
132
- }
133
-
134
- if (agentSpec.conditionalPivot) {
135
- parts.push(`\nConditional: if ${agentSpec.conditionalPivot.if} → ${agentSpec.conditionalPivot.then}`);
136
- }
137
-
138
- return parts.join('');
139
- }
@@ -1,178 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * failure-memory.mjs — Track task failures and enable automatic escalation.
4
- *
5
- * Exports: recordFailure, checkFailureHistory, formatEscalation,
6
- * clearFailures, getFailureStats
7
- */
8
-
9
- import { readFileSync, appendFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
10
- import { join } from 'path';
11
- import { randomUUID } from 'crypto';
12
-
13
- const STOP_WORDS = new Set(['the','a','an','is','in','on','at','to','for','of','and','or','with','this','that','it','be','was','are','were','has','have','had','do','does','did','not','from','by','as','if','but','we','i','you']);
14
- const WINDOW_48H = 48 * 60 * 60 * 1000;
15
-
16
- const DEPTH_ORDER = ['low', 'medium', 'high', 'ultra'];
17
- const MODEL_ORDER = ['haiku', 'sonnet', 'opus'];
18
-
19
- function failuresPath(cwd) {
20
- const dir = join(cwd, '.dualbrain');
21
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
22
- return join(dir, 'failures.jsonl');
23
- }
24
-
25
- function categorizeError(error = '') {
26
- const e = error.toLowerCase();
27
- if (/test|assert|expect/.test(e)) return 'test-failure';
28
- if (/timeout|timed out/.test(e)) return 'timeout';
29
- if (/syntax|parse|unexpected token/.test(e)) return 'syntax-error';
30
- if (/permission|eacces/.test(e)) return 'permission-error';
31
- if (/not found|enoent/.test(e)) return 'not-found';
32
- return 'unknown';
33
- }
34
-
35
- function tokenize(text = '') {
36
- return text.toLowerCase().split(/\W+/).filter(w => w.length > 2 && !STOP_WORDS.has(w));
37
- }
38
-
39
- function similarity(promptA, promptB, filesA = [], filesB = []) {
40
- const wordsA = new Set(tokenize(promptA));
41
- const wordsB = new Set(tokenize(promptB));
42
- if (!wordsA.size && !wordsB.size) return 0;
43
- const shared = [...wordsA].filter(w => wordsB.has(w)).length;
44
- const wordScore = shared / Math.max(wordsA.size, wordsB.size);
45
- const sharedFiles = filesA.some(f => filesB.includes(f));
46
- return sharedFiles ? Math.max(wordScore, 0.5) : wordScore;
47
- }
48
-
49
- function readFailures(cwd) {
50
- const path = failuresPath(cwd);
51
- if (!existsSync(path)) return [];
52
- return readFileSync(path, 'utf8')
53
- .split('\n')
54
- .filter(Boolean)
55
- .map(line => { try { return JSON.parse(line); } catch { return null; } })
56
- .filter(Boolean);
57
- }
58
-
59
- function writeAll(cwd, records) {
60
- writeFileSync(failuresPath(cwd), records.map(r => JSON.stringify(r)).join('\n') + '\n');
61
- }
62
-
63
- function bumpDepth(depth) {
64
- const idx = DEPTH_ORDER.indexOf(depth);
65
- return idx === -1 || idx >= DEPTH_ORDER.length - 1 ? 'ultra' : DEPTH_ORDER[idx + 1];
66
- }
67
-
68
- function bumpModel(model = '') {
69
- const m = model.toLowerCase();
70
- const match = MODEL_ORDER.find(k => m.includes(k)) ?? 'sonnet';
71
- const idx = MODEL_ORDER.indexOf(match);
72
- return idx >= MODEL_ORDER.length - 1 ? `claude-opus-4-5` : `claude-${MODEL_ORDER[idx + 1]}-4-5`;
73
- }
74
-
75
- // ─── Exports ──────────────────────────────────────────────────────────────────
76
-
77
- export async function recordFailure(prompt, plan = {}, error = '', cwd = process.cwd()) {
78
- const record = {
79
- id: randomUUID(),
80
- timestamp: Date.now(),
81
- prompt,
82
- promptWords: tokenize(prompt),
83
- model: plan.model ?? null,
84
- reasoningDepth: plan.reasoningDepth ?? null,
85
- tier: plan.tier ?? null,
86
- error: String(error),
87
- errorCategory: categorizeError(error),
88
- files: plan.files ?? [],
89
- escalatedFrom: plan.escalatedFrom ?? null,
90
- resolved: false,
91
- };
92
- appendFileSync(failuresPath(cwd), JSON.stringify(record) + '\n');
93
- return record;
94
- }
95
-
96
- export async function checkFailureHistory(prompt, files = [], cwd = process.cwd()) {
97
- const cutoff = Date.now() - WINDOW_48H;
98
- const all = readFailures(cwd);
99
- const recent = all.filter(r => !r.resolved && r.timestamp >= cutoff);
100
- const matches = recent
101
- .map(r => ({ r, score: similarity(prompt, r.prompt, files, r.files ?? []) }))
102
- .filter(({ score }) => score >= 0.4)
103
- .sort((a, b) => b.r.timestamp - a.r.timestamp);
104
-
105
- const count = matches.length;
106
- const last = matches[0]?.r ?? null;
107
-
108
- const escalation = { recommended: false, fromModel: null, toModel: null, fromDepth: null, toDepth: null, useChallenger: false, reason: '' };
109
-
110
- if (count >= 1) {
111
- escalation.recommended = true;
112
- escalation.fromModel = last.model;
113
- escalation.fromDepth = last.reasoningDepth;
114
-
115
- if (count === 1) {
116
- escalation.toDepth = bumpDepth(last.reasoningDepth ?? 'medium');
117
- escalation.toModel = last.model;
118
- escalation.useChallenger = false;
119
- escalation.reason = `1 prior failure on similar task, bumping depth to ${escalation.toDepth}`;
120
- } else if (count === 2) {
121
- escalation.toDepth = 'ultra';
122
- escalation.toModel = last.model?.includes('opus') ? last.model : bumpModel(last.model);
123
- escalation.useChallenger = false;
124
- escalation.reason = `2 prior failures on similar task, escalating to Opus + ultrathink`;
125
- } else {
126
- escalation.toDepth = 'ultra';
127
- escalation.toModel = last.model?.includes('opus') ? last.model : bumpModel(last.model);
128
- escalation.useChallenger = true;
129
- escalation.reason = `${count} prior failures on similar task, forcing dual-brain`;
130
- }
131
- }
132
-
133
- return { hasPriorFailures: count > 0, failureCount: count, lastFailure: last, escalation };
134
- }
135
-
136
- export function formatEscalation(escalation) {
137
- if (!escalation?.recommended) return '';
138
- const prev = [escalation.fromModel, escalation.fromDepth].filter(Boolean).join(', ') || 'unknown';
139
- const next = [escalation.toModel, escalation.toDepth, escalation.useChallenger ? 'GPT challenger' : null].filter(Boolean).join(' + ');
140
- return `⚡ Strategy changed\n Previous: failed (${prev})\n Escalated: ${next}\n Reason: ${escalation.reason}`;
141
- }
142
-
143
- export async function clearFailures(prompt, cwd = process.cwd()) {
144
- const all = readFailures(cwd);
145
- const promptWords = tokenize(prompt);
146
- const fakePrompt = promptWords.join(' ');
147
- let changed = false;
148
- const updated = all.map(r => {
149
- if (!r.resolved && similarity(fakePrompt, r.prompt) >= 0.4) {
150
- changed = true;
151
- return { ...r, resolved: true };
152
- }
153
- return r;
154
- });
155
- if (changed) writeAll(cwd, updated);
156
- }
157
-
158
- export async function getFailureStats(cwd = process.cwd()) {
159
- const all = readFailures(cwd);
160
- const byCategory = {};
161
- let resolved = 0;
162
- let escalationSum = 0;
163
- let escalationCount = 0;
164
-
165
- for (const r of all) {
166
- if (r.resolved) resolved++;
167
- byCategory[r.errorCategory] = (byCategory[r.errorCategory] ?? 0) + 1;
168
- if (r.escalatedFrom) { escalationSum++; escalationCount++; }
169
- }
170
-
171
- return {
172
- total: all.length,
173
- resolved,
174
- unresolved: all.length - resolved,
175
- byCategory,
176
- avgEscalationsToResolve: escalationCount ? +(escalationSum / escalationCount).toFixed(2) : 0,
177
- };
178
- }
package/src/fx.mjs DELETED
@@ -1,276 +0,0 @@
1
- // fx.mjs — zero-dependency animated shell effects for dual-brain CLI
2
-
3
- const isTTY = process.stdout.isTTY && !process.env.CI;
4
- const hasColor = isTTY && !process.env.NO_COLOR;
5
- const isUnicode = process.platform !== 'win32' || process.env.WT_SESSION;
6
-
7
- const c = {
8
- reset: '\x1b[0m',
9
- bold: '\x1b[1m',
10
- dim: '\x1b[2m',
11
- green: '\x1b[32m',
12
- red: '\x1b[31m',
13
- yellow: '\x1b[33m',
14
- blue: '\x1b[34m',
15
- magenta: '\x1b[35m',
16
- cyan: '\x1b[36m',
17
- white: '\x1b[37m',
18
- gray: '\x1b[90m',
19
- bgGreen: '\x1b[42m',
20
- bgRed: '\x1b[41m',
21
- bgYellow: '\x1b[43m',
22
- bgBlue: '\x1b[44m',
23
- bgMagenta: '\x1b[45m',
24
- clearLine: '\x1b[2K',
25
- cursorUp: '\x1b[1A',
26
- cursorHide: '\x1b[?25l',
27
- cursorShow: '\x1b[?25h',
28
- saveCursor: '\x1b[s',
29
- restoreCursor: '\x1b[u'
30
- };
31
-
32
- function color(text, ...styles) {
33
- if (!hasColor) return text;
34
- return styles.join('') + text + c.reset;
35
- }
36
-
37
- export const colors = c;
38
-
39
- export function sleep(ms) {
40
- return new Promise(resolve => setTimeout(resolve, ms));
41
- }
42
-
43
- export function clearScreen() {
44
- if (isTTY) process.stdout.write('\x1b[2J\x1b[H');
45
- }
46
-
47
- export function nl(n = 1) {
48
- process.stdout.write('\n'.repeat(n));
49
- }
50
-
51
- export function getMode() {
52
- if (process.env.CI) return 'ci';
53
- if (!process.stdout.isTTY) return 'plain';
54
- if (process.env.DUAL_BRAIN_FX === 'subtle') return 'subtle';
55
- return 'full';
56
- }
57
-
58
- export function spinner(text) {
59
- const frames = isUnicode ? ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'] : ['|','/','-','\\'];
60
- let i = 0, interval = null, currentText = text;
61
-
62
- return {
63
- start() {
64
- if (!isTTY) { process.stdout.write(currentText + '\n'); return this; }
65
- process.stdout.write(c.cursorHide);
66
- interval = setInterval(() => {
67
- process.stdout.write(`\r${c.clearLine} ${color(frames[i % frames.length], c.cyan)} ${currentText}`);
68
- i++;
69
- }, 80);
70
- return this;
71
- },
72
- update(newText) { currentText = newText; return this; },
73
- succeed(msg) {
74
- if (interval) clearInterval(interval);
75
- const sym = isUnicode ? '✓' : '+';
76
- process.stdout.write(`\r${c.clearLine} ${color(sym, c.green)} ${msg || currentText}\n`);
77
- if (isTTY) process.stdout.write(c.cursorShow);
78
- return this;
79
- },
80
- fail(msg) {
81
- if (interval) clearInterval(interval);
82
- const sym = isUnicode ? '✗' : 'x';
83
- process.stdout.write(`\r${c.clearLine} ${color(sym, c.red)} ${msg || currentText}\n`);
84
- if (isTTY) process.stdout.write(c.cursorShow);
85
- return this;
86
- },
87
- warn(msg) {
88
- if (interval) clearInterval(interval);
89
- const sym = isUnicode ? '⚠' : '!';
90
- process.stdout.write(`\r${c.clearLine} ${color(sym, c.yellow)} ${msg || currentText}\n`);
91
- if (isTTY) process.stdout.write(c.cursorShow);
92
- return this;
93
- },
94
- stop() {
95
- if (interval) clearInterval(interval);
96
- process.stdout.write(`\r${c.clearLine}`);
97
- if (isTTY) process.stdout.write(c.cursorShow);
98
- return this;
99
- }
100
- };
101
- }
102
-
103
- export function progress(current, total, label = '', width = 30) {
104
- const pct = Math.min(1, current / total);
105
- if (!isTTY) {
106
- process.stdout.write(`${Math.round(pct * 100)}% ${label}\n`);
107
- return;
108
- }
109
- const filled = Math.round(pct * width);
110
- const empty = width - filled;
111
- const bar = isUnicode
112
- ? color('█'.repeat(filled) + '░'.repeat(empty), c.cyan)
113
- : color('#'.repeat(filled) + '-'.repeat(empty), c.cyan);
114
- const pctStr = String(Math.round(pct * 100)).padStart(3) + '%';
115
- process.stdout.write(`\r${c.clearLine} ${bar} ${color(pctStr, c.bold)} ${label}`);
116
- if (current >= total) process.stdout.write('\n');
117
- }
118
-
119
- export function success(text) {
120
- const sym = isUnicode ? '✓' : '+';
121
- process.stdout.write(` ${color(sym, c.green)} ${text}\n`);
122
- }
123
-
124
- export function error(text) {
125
- const sym = isUnicode ? '✗' : 'x';
126
- process.stdout.write(` ${color(sym, c.red)} ${text}\n`);
127
- }
128
-
129
- export function warn(text) {
130
- const sym = isUnicode ? '⚠' : '!';
131
- process.stdout.write(` ${color(sym, c.yellow)} ${text}\n`);
132
- }
133
-
134
- export function info(text) {
135
- const sym = isUnicode ? 'ℹ' : 'i';
136
- process.stdout.write(` ${color(sym, c.blue)} ${text}\n`);
137
- }
138
-
139
- export function dim(text) {
140
- process.stdout.write(`${color(text, c.gray)}\n`);
141
- }
142
-
143
- export function step(current, total, text) {
144
- if (!isUnicode) {
145
- process.stdout.write(` [${current}/${total}] ${text}\n`);
146
- return;
147
- }
148
- const dots = [];
149
- for (let i = 1; i <= total; i++) {
150
- if (i < current) dots.push(color('●', c.green));
151
- else if (i === current) dots.push(color('●', c.cyan));
152
- else dots.push(color('○', c.gray));
153
- }
154
- process.stdout.write(` ${dots.join(' ')} ${color(`Step ${current} of ${total}`, c.bold)} · ${text}\n`);
155
- }
156
-
157
- export function banner(text) {
158
- const pkg = 'DUAL-BRAIN';
159
- const inner = ` ${isUnicode ? '🧠' : '**'} ${pkg} ${text} `;
160
- const width = inner.length + 2;
161
- if (!isUnicode || !hasColor) {
162
- process.stdout.write(`\n +${'='.repeat(width - 2)}+\n | ${inner} |\n +${'='.repeat(width - 2)}+\n\n`);
163
- return;
164
- }
165
- const top = ` ╔${'═'.repeat(width)}╗`;
166
- const mid = ` ║${inner}║`;
167
- const bot = ` ╚${'═'.repeat(width)}╝`;
168
- process.stdout.write(`\n${color(top, c.cyan, c.bold)}\n${color(mid, c.cyan, c.bold)}\n${color(bot, c.cyan, c.bold)}\n\n`);
169
- }
170
-
171
- export function box(content, options = {}) {
172
- const { color: colorName = 'cyan', padding = 1, title = '' } = options;
173
- const ansiColor = c[colorName] || c.cyan;
174
- const lines = Array.isArray(content) ? content : content.split('\n');
175
- const innerWidth = Math.max(...lines.map(l => stripAnsi(l).length), title ? stripAnsi(title).length : 0) + padding * 2;
176
-
177
- function draw(text, ansi) {
178
- if (!hasColor) return text;
179
- return ansi + text + c.reset;
180
- }
181
-
182
- const titleStr = title ? ` ${title} ` : '';
183
- const topFill = '─'.repeat(Math.max(0, innerWidth - stripAnsi(titleStr).length));
184
- const top = isUnicode
185
- ? draw(`┌${titleStr}${'─'.repeat(Math.floor(topFill.length / 2))}${'─'.repeat(Math.ceil(topFill.length / 2))}┐`, ansiColor)
186
- : draw(`+${titleStr}${'-'.repeat(topFill.length)}+`, ansiColor);
187
- const bot = isUnicode
188
- ? draw(`└${'─'.repeat(innerWidth)}┘`, ansiColor)
189
- : draw(`+${'-'.repeat(innerWidth)}+`, ansiColor);
190
-
191
- process.stdout.write(` ${top}\n`);
192
- for (const line of lines) {
193
- const pad = ' '.repeat(padding);
194
- const visible = stripAnsi(line).length;
195
- const right = ' '.repeat(Math.max(0, innerWidth - padding - visible));
196
- const border = isUnicode ? draw('│', ansiColor) : draw('|', ansiColor);
197
- process.stdout.write(` ${border}${pad}${line}${right}${border}\n`);
198
- }
199
- process.stdout.write(` ${bot}\n`);
200
- }
201
-
202
- function stripAnsi(str) {
203
- return str.replace(/\x1b\[[0-9;]*m/g, '');
204
- }
205
-
206
- export function gradient(text, fromColor = 196, toColor = 226) {
207
- if (!hasColor) return process.stdout.write(text + '\n');
208
- const chars = [...text];
209
- const result = chars.map((ch, i) => {
210
- const t = chars.length <= 1 ? 0 : i / (chars.length - 1);
211
- const colorIdx = Math.round(fromColor + t * (toColor - fromColor));
212
- return `\x1b[38;5;${colorIdx}m${ch}`;
213
- }).join('') + c.reset;
214
- process.stdout.write(result + '\n');
215
- }
216
-
217
- export async function celebrate(text) {
218
- const sym = isUnicode ? '✨' : '*';
219
- if (!isTTY || getMode() === 'ci' || getMode() === 'plain') {
220
- process.stdout.write(` ${sym} ${text} ${sym}\n`);
221
- return;
222
- }
223
- process.stdout.write(`\r${c.clearLine} ${color(`${sym} ${text} ${sym}`, c.bgGreen, c.bold)}`);
224
- await sleep(100);
225
- process.stdout.write(`\r${c.clearLine} ${color(`${sym} ${text} ${sym}`, c.green, c.bold)}\n`);
226
- }
227
-
228
- export async function loadingSequence(steps) {
229
- for (const s of steps) {
230
- const sp = spinner(s.text).start();
231
- await sleep(s.duration || 800);
232
- sp.succeed(s.successText || s.text);
233
- }
234
- }
235
-
236
- export async function agentDispatch(model, task) {
237
- const mode = getMode();
238
- if (mode === 'ci' || mode === 'plain') {
239
- process.stdout.write(`Dispatching ${model}...\n`);
240
- process.stdout.write(`Agent dispatched: ${task}\n`);
241
- return;
242
- }
243
- const sp = spinner(`Dispatching ${color(model, c.cyan)}...`).start();
244
- await sleep(mode === 'subtle' ? 0 : 600);
245
- sp.succeed(`Agent dispatched: ${task}`);
246
- }
247
-
248
- export async function thinkRound(round, provider, question) {
249
- const mode = getMode();
250
- const providerLabel = color(provider, c.magenta);
251
- const roundLabel = color(`Round ${round}`, c.bold);
252
-
253
- if (mode === 'ci' || mode === 'plain') {
254
- process.stdout.write(`Dual-Brain Think · ${roundLabel} · ${provider} analyzing: ${question}\n`);
255
- return;
256
- }
257
-
258
- const title = `Dual-Brain Think · ${roundLabel}`;
259
- const titleVisible = stripAnsi(title);
260
- const width = Math.max(titleVisible.length + 4, question.length + 4, 36);
261
- const topFill = '─'.repeat(Math.max(0, width - titleVisible.length - 2));
262
-
263
- if (isUnicode && hasColor) {
264
- process.stdout.write(` ${color(`╭─ ${title} ${'─'.repeat(topFill.length)}╮`, c.cyan)}\n`);
265
- process.stdout.write(` ${color('│', c.cyan)} ${isUnicode ? '🤖' : '>>'} ${providerLabel} analyzing...${' '.repeat(Math.max(0, width - 4 - stripAnsi(provider).length - 13))}${color('│', c.cyan)}\n`);
266
- process.stdout.write(` ${color(`╰${'─'.repeat(width)}╯`, c.cyan)}\n`);
267
- } else {
268
- process.stdout.write(` +-- ${title} --+\n`);
269
- process.stdout.write(` | ${provider} analyzing: ${question}\n`);
270
- process.stdout.write(` +${'─'.repeat(width + 2)}+\n`);
271
- }
272
-
273
- const sp = spinner(`${provider} thinking on: ${question}`).start();
274
- await sleep(mode === 'subtle' ? 0 : 900);
275
- sp.succeed(`${provider} analysis complete`);
276
- }