dual-brain 0.2.30 → 0.3.1

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 (312) 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/precompact.mjs +3 -3
  240. package/hooks/session-end.mjs +3 -3
  241. package/hooks/task-classifier.mjs +328 -0
  242. package/hooks/vibe-router.mjs +387 -0
  243. package/install.mjs +2 -2
  244. package/package.json +29 -153
  245. package/src/agents/registry.mjs +0 -405
  246. package/src/awareness.mjs +0 -425
  247. package/src/brief.mjs +0 -266
  248. package/src/calibration.mjs +0 -148
  249. package/src/checkpoint.mjs +0 -109
  250. package/src/ci-triage.mjs +0 -191
  251. package/src/cognitive-loop.mjs +0 -562
  252. package/src/collaboration.mjs +0 -545
  253. package/src/context-intel.mjs +0 -158
  254. package/src/context.mjs +0 -389
  255. package/src/continuity.mjs +0 -298
  256. package/src/cost-tracker.mjs +0 -184
  257. package/src/debrief.mjs +0 -228
  258. package/src/decide.mjs +0 -1099
  259. package/src/decompose.mjs +0 -331
  260. package/src/detect.mjs +0 -702
  261. package/src/dispatch.mjs +0 -1447
  262. package/src/doctor.mjs +0 -1607
  263. package/src/envelope.mjs +0 -139
  264. package/src/failure-memory.mjs +0 -178
  265. package/src/fx.mjs +0 -276
  266. package/src/governance.mjs +0 -279
  267. package/src/handoff.mjs +0 -87
  268. package/src/head-protocol.mjs +0 -128
  269. package/src/head.mjs +0 -952
  270. package/src/health.mjs +0 -528
  271. package/src/inbox.mjs +0 -195
  272. package/src/index.mjs +0 -44
  273. package/src/install-hooks.mjs +0 -100
  274. package/src/integrity.mjs +0 -245
  275. package/src/intelligence.mjs +0 -447
  276. package/src/ledger.mjs +0 -196
  277. package/src/living-docs.mjs +0 -210
  278. package/src/memory-tiers.mjs +0 -193
  279. package/src/models.mjs +0 -363
  280. package/src/narrative.mjs +0 -169
  281. package/src/nextstep.mjs +0 -100
  282. package/src/observer.mjs +0 -241
  283. package/src/outcome.mjs +0 -400
  284. package/src/pipeline.mjs +0 -1711
  285. package/src/playbook.mjs +0 -257
  286. package/src/pr-agent.mjs +0 -214
  287. package/src/predictive.mjs +0 -250
  288. package/src/profile.mjs +0 -1411
  289. package/src/prompt-audit.mjs +0 -231
  290. package/src/prompt-intel.mjs +0 -325
  291. package/src/provider-context.mjs +0 -257
  292. package/src/receipt.mjs +0 -344
  293. package/src/recommendations.mjs +0 -296
  294. package/src/redact.mjs +0 -192
  295. package/src/replit.mjs +0 -1210
  296. package/src/repo.mjs +0 -445
  297. package/src/revert.mjs +0 -149
  298. package/src/routing-advisor.mjs +0 -204
  299. package/src/self-correct.mjs +0 -147
  300. package/src/session-lock.mjs +0 -160
  301. package/src/session.mjs +0 -1655
  302. package/src/settings-tui.mjs +0 -373
  303. package/src/setup-flow.mjs +0 -223
  304. package/src/signal.mjs +0 -115
  305. package/src/simmer.mjs +0 -241
  306. package/src/strategy.mjs +0 -235
  307. package/src/subscription.mjs +0 -212
  308. package/src/templates.mjs +0 -260
  309. package/src/think-engine.mjs +0 -428
  310. package/src/tui.mjs +0 -276
  311. package/src/update-check.mjs +0 -35
  312. 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
- }