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
@@ -1,257 +0,0 @@
1
- import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
2
- import { join } from 'node:path';
3
-
4
- // ── Provider capabilities registry ──────────────────────────────────────────
5
-
6
- const PROVIDER_CAPS = {
7
- claude: {
8
- name: 'Claude Code',
9
- cli: 'claude',
10
- hasNativeCompaction: true,
11
- compactionStrategy: 'automatic',
12
- contextFormat: 'markdown-blocks',
13
- supportsSpecialists: true,
14
- supportsSituationBrief: true,
15
- maxContextTokens: 200_000,
16
- sessionStorage: 'claude-internal',
17
- authCheck: 'claude --version',
18
- resumeSupport: 'receipt + handoff',
19
- },
20
- openai: {
21
- name: 'Codex CLI',
22
- cli: 'codex',
23
- hasNativeCompaction: false,
24
- compactionStrategy: 'none',
25
- contextFormat: 'plain-text',
26
- supportsSpecialists: false,
27
- supportsSituationBrief: true,
28
- maxContextTokens: 128_000,
29
- sessionStorage: '~/.codex/sessions/YYYY/MM/DD/*.jsonl',
30
- authCheck: 'codex --version',
31
- resumeSupport: 'handoff-only',
32
- },
33
- };
34
-
35
- export function getProviderCaps(provider) {
36
- return PROVIDER_CAPS[provider] || PROVIDER_CAPS.claude;
37
- }
38
-
39
- // ── Provider-agnostic context injection ─────────────────────────────────────
40
-
41
- /**
42
- * Build a context block that works for any provider.
43
- * Adapts format based on provider capabilities.
44
- */
45
- export function buildContextBlock(provider, sections) {
46
- const caps = getProviderCaps(provider);
47
- const lines = [];
48
-
49
- if (caps.contextFormat === 'markdown-blocks') {
50
- for (const [label, content] of Object.entries(sections)) {
51
- if (!content) continue;
52
- lines.push(`[${label.toUpperCase()}]`);
53
- lines.push(typeof content === 'string' ? content : JSON.stringify(content));
54
- lines.push(`[/${label.toUpperCase()}]`);
55
- lines.push('');
56
- }
57
- } else {
58
- for (const [label, content] of Object.entries(sections)) {
59
- if (!content) continue;
60
- lines.push(`--- ${label} ---`);
61
- lines.push(typeof content === 'string' ? content : JSON.stringify(content));
62
- lines.push('');
63
- }
64
- }
65
-
66
- return lines.join('\n').trim();
67
- }
68
-
69
- // ── Compaction survival for both providers ───────────────────────────────────
70
-
71
- /**
72
- * Build a compaction survival block tuned for the target provider.
73
- *
74
- * Claude: uses tagged blocks that survive automatic context compression.
75
- * Codex: no native compaction, but we prepend a compact state header that
76
- * stays at the top of the context window and serves as a quick-reference
77
- * for the model when the conversation gets long.
78
- */
79
- export function buildSurvivalBlock(provider, state) {
80
- const caps = getProviderCaps(provider);
81
-
82
- const coreLines = [];
83
- if (state.activeTask) coreLines.push(`TASK: ${state.activeTask}`);
84
- if (state.provider) coreLines.push(`PROVIDER: ${state.provider}/${state.model || 'default'}`);
85
- if (state.tier) coreLines.push(`TIER: ${state.tier}`);
86
- if (state.risk) coreLines.push(`RISK: ${state.risk}`);
87
- if (state.filesInProgress?.length) coreLines.push(`FILES: ${state.filesInProgress.slice(0, 10).join(', ')}`);
88
- if (state.decisions?.length) coreLines.push(`DECISIONS: ${state.decisions.slice(0, 3).join('; ')}`);
89
- if (state.warnings?.length) coreLines.push(`WARNINGS: ${state.warnings.slice(0, 3).join('; ')}`);
90
- if (state.routingRules?.length) coreLines.push(`ROUTING: ${state.routingRules.join('; ')}`);
91
-
92
- if (caps.hasNativeCompaction) {
93
- return `[DUAL-BRAIN CONTINUITY]\n${coreLines.join('\n')}\n[/DUAL-BRAIN CONTINUITY]`;
94
- }
95
-
96
- // Codex: compact header block — placed at prompt start for max visibility
97
- return `## dual-brain state\n${coreLines.join('\n')}\n---`;
98
- }
99
-
100
- // ── Provider-agnostic handoff ───────────────────────────────────────────────
101
-
102
- /**
103
- * Generate a handoff receipt that works for both providers.
104
- * Accounts for Codex's lack of native resume support by writing
105
- * to a shared .dualbrain/handoffs/ directory that both providers can read.
106
- */
107
- export function generateProviderHandoff(sessionState, provider) {
108
- const caps = getProviderCaps(provider);
109
-
110
- const handoff = {
111
- version: 2,
112
- provider,
113
- providerCaps: caps.name,
114
- timestamp: new Date().toISOString(),
115
- task: sessionState.taskDescription || null,
116
- progress: {
117
- filesChanged: (sessionState.filesChanged || []).slice(0, 20),
118
- testsRun: sessionState.testsRun || [],
119
- decisions: (sessionState.decisions || []).slice(0, 5),
120
- },
121
- unresolved: (sessionState.unresolved || []).slice(0, 5),
122
- routing: {
123
- lastProvider: provider,
124
- lastModel: sessionState.routingHistory?.lastModel || null,
125
- failedProviders: sessionState.routingHistory?.failedProviders || [],
126
- },
127
- resumeHint: sessionState.resumeHint || null,
128
- resumeStrategy: caps.resumeSupport,
129
- };
130
-
131
- return handoff;
132
- }
133
-
134
- /**
135
- * Build a resume brief from the latest handoff, adapted for the resuming provider.
136
- * Codex gets a more verbose brief since it has no native session memory.
137
- */
138
- export function buildProviderResumeBrief(cwd, targetProvider) {
139
- const dir = join(cwd || process.cwd(), '.dualbrain', 'handoffs');
140
- if (!existsSync(dir)) return null;
141
-
142
- const files = readdirSync(dir)
143
- .filter(f => f.startsWith('handoff-') && f.endsWith('.json'))
144
- .sort()
145
- .reverse();
146
- if (files.length === 0) return null;
147
-
148
- let handoff;
149
- try {
150
- handoff = JSON.parse(readFileSync(join(dir, files[0]), 'utf8'));
151
- } catch {
152
- return null;
153
- }
154
-
155
- const age = (Date.now() - Date.parse(handoff.timestamp)) / 3600000;
156
- if (age > 48) return null;
157
-
158
- const caps = getProviderCaps(targetProvider);
159
- const lines = [];
160
-
161
- const ageLabel = age < 1 ? 'just now' : age < 24 ? `${Math.round(age)}h ago` : `${Math.round(age / 24)}d ago`;
162
- lines.push(`Resuming from previous session (${ageLabel}, ran on ${handoff.provider || 'unknown'}):`);
163
-
164
- if (handoff.task) lines.push(` Task: ${handoff.task}`);
165
- if (handoff.resumeHint) lines.push(` Next: ${handoff.resumeHint}`);
166
-
167
- if (handoff.progress?.filesChanged?.length) {
168
- const shown = handoff.progress.filesChanged.slice(0, caps.hasNativeCompaction ? 5 : 10);
169
- lines.push(` Changed: ${shown.join(', ')}`);
170
- }
171
-
172
- if (handoff.unresolved?.length) {
173
- lines.push(` Unresolved: ${handoff.unresolved.join('; ')}`);
174
- }
175
-
176
- // Codex gets extra context since it has no native session memory
177
- if (!caps.hasNativeCompaction && handoff.progress?.decisions?.length) {
178
- lines.push(` Prior routing: ${handoff.progress.decisions.map(d => `${d.provider}/${d.model}`).join(', ')}`);
179
- }
180
-
181
- if (handoff.routing?.failedProviders?.length) {
182
- lines.push(` Note: ${handoff.routing.failedProviders.join(', ')} failed last session`);
183
- }
184
-
185
- return lines.join('\n');
186
- }
187
-
188
- // ── Specialist/plugin injection (provider-aware) ────────────────────────────
189
-
190
- /**
191
- * Build a capability hint block for the target provider.
192
- * Claude: specialist prompts from agents/specialists/.
193
- * Codex: plugin hints from matched Codex plugins.
194
- */
195
- export function buildCapabilityHint(provider, prompt, cwd) {
196
- if (provider === 'claude') {
197
- return null; // Handled by dispatch.mjs specialist injection
198
- }
199
-
200
- // Codex: try to match plugins
201
- try {
202
- const { matchPluginsForTask } = require('./replit.mjs');
203
- const matched = matchPluginsForTask(prompt, undefined, cwd);
204
- if (matched.length > 0) {
205
- const names = matched.slice(0, 3).map(m => m.plugin.id).join(', ');
206
- return `[Available Codex plugins: ${names}. Consider using matching plugins for direct API access.]`;
207
- }
208
- } catch {}
209
-
210
- return null;
211
- }
212
-
213
- // ── Context budget tracking (provider-aware) ────────────────────────────────
214
-
215
- /**
216
- * Estimate remaining context budget for a provider.
217
- */
218
- export function estimateContextBudget(provider, usedTokens) {
219
- const caps = getProviderCaps(provider);
220
- const remaining = caps.maxContextTokens - usedTokens;
221
-
222
- return {
223
- provider,
224
- maxTokens: caps.maxContextTokens,
225
- usedTokens,
226
- remainingTokens: Math.max(0, remaining),
227
- utilizationPct: Math.round((usedTokens / caps.maxContextTokens) * 100),
228
- compactionRisk: remaining < 20_000 ? 'critical' : remaining < 50_000 ? 'high' : remaining < 100_000 ? 'medium' : 'low',
229
- hasNativeCompaction: caps.hasNativeCompaction,
230
- action: caps.hasNativeCompaction
231
- ? (remaining < 20_000 ? 'survival-kit-injected' : 'none')
232
- : (remaining < 30_000 ? 'manual-handoff-recommended' : 'none'),
233
- };
234
- }
235
-
236
- // ── Cross-provider compatibility helpers ────────────────────────────────────
237
-
238
- /**
239
- * Get the opposite provider for cross-review.
240
- * Handles the case where the opposite provider isn't available.
241
- */
242
- export function getReviewProvider(workProvider, availableProviders) {
243
- const opposite = workProvider === 'claude' ? 'openai' : 'claude';
244
- if (availableProviders?.includes(opposite)) return opposite;
245
-
246
- // Same-provider review with different model if opposite unavailable
247
- return workProvider;
248
- }
249
-
250
- /**
251
- * Check if both providers are available for true dual-brain operation.
252
- */
253
- export function isDualProviderAvailable(profile) {
254
- const claude = profile?.providers?.claude?.enabled !== false;
255
- const openai = profile?.providers?.openai?.enabled && profile?.providers?.openai?.plan;
256
- return { claude, openai, dual: claude && openai };
257
- }
package/src/receipt.mjs DELETED
@@ -1,344 +0,0 @@
1
- import { mkdirSync, writeFileSync, appendFileSync, readFileSync, existsSync, readdirSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { execSync } from 'node:child_process';
4
-
5
- const DIM = '\x1b[2m';
6
- const GREEN = '\x1b[32m';
7
- const YELLOW= '\x1b[33m';
8
- const RED = '\x1b[31m';
9
- const RESET = '\x1b[0m';
10
-
11
- const SEP = `${DIM}──────────────────────────────────${RESET}`;
12
-
13
- const AUTH_PAT = /\b(auth|credential|secret|token|password|encrypt|permission|oauth|jwt|api.?key)\b/i;
14
-
15
- function classifyRisk(plan, result) {
16
- if (plan.risk) return plan.risk;
17
- const files = result.filesChanged ?? [];
18
- if (files.some(f => AUTH_PAT.test(f))) return 'critical';
19
- if (plan.tier === 'think') return 'high';
20
- if (plan.tier === 'execute') return 'medium';
21
- return 'low';
22
- }
23
-
24
- function classifyChallenger(plan, result) {
25
- const policy = plan.challengerPolicy;
26
- if (!plan.useChallenger && (!policy || policy === 'none')) return 'not used';
27
- if (!result.success) return 'blocked';
28
- if (result.output && /concern|issue|warn|problem/i.test(String(result.output))) return 'concerns raised';
29
- return 'pass';
30
- }
31
-
32
- function nextStep(result, plan, verification) {
33
- const files = result.filesChanged ?? [];
34
- const changed = files.length > 0;
35
- const authFiles = files.some(f => AUTH_PAT.test(f));
36
-
37
- if (!result.success) {
38
- const retry = result.error && /test/i.test(String(result.error));
39
- return retry ? 'fix failing tests' : 'retry with deeper analysis';
40
- }
41
-
42
- if (authFiles) return 'security review recommended';
43
-
44
- if (changed) {
45
- if (!verification.testsRun) return 'run tests to verify';
46
- if (verification.testsPassed === false) return 'fix failing tests';
47
- if (verification.testsPassed === true) return 'commit this patch';
48
- return 'review the diff';
49
- }
50
-
51
- return 'review the output';
52
- }
53
-
54
- export function buildReceipt(result, plan, verification) {
55
- const files = result.filesChanged ?? [];
56
- const changed = files.length > 0 ? files.join(', ') : 'no files changed';
57
-
58
- let verified;
59
- if (verification.testsPassed === true) verified = 'tests passed';
60
- else if (verification.filesVerified) verified = 'files confirmed changed';
61
- else verified = 'not verified';
62
-
63
- return {
64
- changed,
65
- verified,
66
- risk: classifyRisk(plan, result),
67
- challenger: classifyChallenger(plan, result),
68
- next: nextStep(result, plan, verification),
69
- success: result.success ?? false,
70
- };
71
- }
72
-
73
- function colorRisk(risk) {
74
- if (risk === 'low') return `${GREEN}${risk}${RESET}`;
75
- if (risk === 'medium') return `${YELLOW}${risk}${RESET}`;
76
- return `${RED}${risk}${RESET}`;
77
- }
78
-
79
- function colorChallenger(ch) {
80
- if (ch === 'pass') return `${GREEN}${ch}${RESET}`;
81
- if (ch === 'concerns raised') return `${YELLOW}${ch}${RESET}`;
82
- if (ch === 'blocked') return `${RED}${ch}${RESET}`;
83
- return `${DIM}${ch}${RESET}`;
84
- }
85
-
86
- export function formatReceipt(receipt) {
87
- return [
88
- SEP,
89
- ` Changed: ${receipt.changed}`,
90
- ` Verified: ${receipt.verified}`,
91
- ` Risk: ${colorRisk(receipt.risk)}`,
92
- ` Challenger: ${colorChallenger(receipt.challenger)}`,
93
- ` Next: ${receipt.next}`,
94
- SEP,
95
- ].join('\n');
96
- }
97
-
98
- export function formatFailureReceipt(receipt, failureContext) {
99
- const errorLine = failureContext ? ` Error: ${failureContext}` : null;
100
- const lines = [
101
- SEP,
102
- ` Changed: ${receipt.changed}`,
103
- ` Verified: ${receipt.verified}`,
104
- ` Risk: ${colorRisk(receipt.risk)}`,
105
- ` Challenger: ${colorChallenger(receipt.challenger)}`,
106
- ];
107
- if (errorLine) lines.push(errorLine);
108
- lines.push(` Next: ${receipt.next}`, SEP);
109
- return lines.join('\n');
110
- }
111
-
112
- // ─── Persistent session receipt ──────────────────────────────────────────────
113
-
114
- const RECEIPTS_DIR = '.dualbrain/receipts';
115
-
116
- function receiptsDir(cwd) {
117
- return join(cwd, RECEIPTS_DIR);
118
- }
119
-
120
- function gitChangedFiles(cwd) {
121
- try {
122
- const out = execSync('git diff --name-only HEAD', { cwd, stdio: ['ignore', 'pipe', 'pipe'] })
123
- .toString().trim();
124
- if (!out) return [];
125
- return out.split('\n').filter(Boolean);
126
- } catch {
127
- return [];
128
- }
129
- }
130
-
131
- function readDecisionsRecent(cwd, limit = 5) {
132
- try {
133
- const raw = readFileSync(join(cwd, '.dualbrain', 'decisions.jsonl'), 'utf8');
134
- const lines = raw.split('\n').filter(l => l.trim());
135
- return lines.slice(-limit).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
136
- } catch {
137
- return [];
138
- }
139
- }
140
-
141
- function ageLabel(ms) {
142
- const mins = Math.round(ms / 60000);
143
- if (mins < 60) return `${mins}m ago`;
144
- const hours = Math.round(mins / 60);
145
- if (hours < 24) return `${hours}h ago`;
146
- return `${Math.round(hours / 24)}d ago`;
147
- }
148
-
149
- /**
150
- * Generate a persistent session receipt and append it to the receipts store.
151
- * @param {object} run PipelineRun object (or any outcome object with compatible fields)
152
- * @param {string} cwd Working directory
153
- * @returns {object} The receipt object
154
- */
155
- export function generateReceipt(run = {}, cwd = process.cwd()) {
156
- const now = new Date();
157
- const ts = now.toISOString();
158
-
159
- // Derive files changed — prefer run.result, fall back to git diff
160
- const filesChanged = (run.result?.filesChanged?.length > 0)
161
- ? run.result.filesChanged
162
- : gitChangedFiles(cwd);
163
-
164
- // Recent decisions from living docs
165
- const decisionEntries = readDecisionsRecent(cwd, 5);
166
- const decisions = decisionEntries.map(d => d.question || d.decision || '').filter(Boolean).slice(0, 3);
167
-
168
- // Test results
169
- const testsRun = run.verification?.ok !== undefined
170
- ? (run.verification.ok ? 'passed' : 'failed')
171
- : null;
172
-
173
- // Unresolved risks from plan
174
- const risksUnresolved = [];
175
- if (run.plan?.approvalRequired && !run.outcome?.approved) {
176
- risksUnresolved.push('approval required but not obtained');
177
- }
178
- if (run.verification && !run.verification.ok) {
179
- risksUnresolved.push('verification failed');
180
- }
181
- const verNotes = run.verification?.notes ?? [];
182
- for (const note of verNotes) {
183
- if (/warn|risk|unverif|no file changes/i.test(note)) risksUnresolved.push(note.slice(0, 80));
184
- }
185
-
186
- // Blockers — gates that failed
187
- const blockers = [];
188
- for (const [name, g] of Object.entries(run.gates ?? {})) {
189
- if (g && !g.passed) blockers.push(`${name}: ${g.reason?.slice(0, 80)}`);
190
- }
191
- if (run.result?.error) blockers.push(run.result.error.slice(0, 80));
192
-
193
- // Derive status
194
- const success = run.result && !run.result.error && (run.verification?.ok !== false);
195
- const status = !run.result ? 'incomplete'
196
- : blockers.length > 0 ? 'failed'
197
- : success ? 'success'
198
- : 'partial';
199
-
200
- // Next action (reuse existing logic)
201
- let nextAction = 'review the output';
202
- if (status === 'success' && filesChanged.length > 0) {
203
- nextAction = run.verification?.testsRun ? 'commit changes' : 'run tests, then commit';
204
- } else if (status === 'failed') {
205
- nextAction = 'investigate failure, retry with adjusted approach';
206
- } else if (status === 'partial') {
207
- nextAction = 'check partial output, verify manually';
208
- }
209
-
210
- const duration = (run.completedAt && run.startedAt)
211
- ? Math.round((run.completedAt - run.startedAt) / 1000)
212
- : null;
213
-
214
- const receipt = {
215
- timestamp: ts,
216
- goal: (run.prompt ?? '').slice(0, 200),
217
- filesChanged,
218
- decisions,
219
- testsRun,
220
- risksUnresolved,
221
- blockers,
222
- nextAction,
223
- provider: run.plan?.primaryProvider ?? run.result?.provider ?? null,
224
- model: run.plan?.primaryModel ?? run.result?.model ?? null,
225
- duration,
226
- status,
227
- };
228
-
229
- // Store receipt
230
- try {
231
- const dir = receiptsDir(cwd);
232
- mkdirSync(dir, { recursive: true });
233
-
234
- const filename = ts.replace(/[:.]/g, '-').slice(0, 19) + '.json';
235
- writeFileSync(join(dir, filename), JSON.stringify(receipt, null, 2));
236
-
237
- // One-line summary for fast scanning
238
- const summary = {
239
- ts,
240
- goal: receipt.goal.slice(0, 80),
241
- status,
242
- files: filesChanged.length,
243
- next: nextAction.slice(0, 60),
244
- };
245
- appendFileSync(join(dir, 'index.jsonl'), JSON.stringify(summary) + '\n');
246
- } catch {
247
- // Storage failure is non-blocking
248
- }
249
-
250
- return receipt;
251
- }
252
-
253
- /**
254
- * Read the most recent receipt(s) and build a compact resume brief (max 500 chars).
255
- * @param {string} cwd
256
- * @returns {string|null}
257
- */
258
- export function buildResumeBrief(cwd = process.cwd()) {
259
- try {
260
- const dir = receiptsDir(cwd);
261
- if (!existsSync(dir)) return null;
262
-
263
- // Find the most recent receipt JSON file
264
- const files = readdirSync(dir)
265
- .filter(f => f.endsWith('.json') && f !== 'index.json')
266
- .sort()
267
- .reverse();
268
-
269
- if (files.length === 0) return null;
270
-
271
- const raw = readFileSync(join(dir, files[0]), 'utf8');
272
- const r = JSON.parse(raw);
273
-
274
- const age = ageLabel(Date.now() - Date.parse(r.timestamp));
275
- const filesSummary = r.filesChanged?.length > 0
276
- ? r.filesChanged.slice(0, 3).map(f => f.split('/').pop()).join(', ')
277
- + (r.filesChanged.length > 3 ? ` +${r.filesChanged.length - 3}` : '')
278
- : 'no files changed';
279
- const riskLine = r.risksUnresolved?.length > 0
280
- ? `Risk: ${r.risksUnresolved[0].slice(0, 60)}`
281
- : null;
282
-
283
- const lines = [
284
- 'RESUME CONTEXT:',
285
- `Last session: ${age}`,
286
- `Goal: ${(r.goal || 'unknown').slice(0, 80)}`,
287
- `Done: ${filesSummary}`,
288
- `Status: ${r.status}${r.testsRun ? ', tests ' + r.testsRun : ''}`,
289
- ];
290
- if (riskLine) lines.push(riskLine);
291
- lines.push(`Next: ${(r.nextAction || '').slice(0, 80)}`);
292
-
293
- const brief = lines.join('\n');
294
- return brief.length > 500 ? brief.slice(0, 497) + '...' : brief;
295
- } catch {
296
- return null;
297
- }
298
- }
299
-
300
- /**
301
- * Return the most recent receipt object, or null if none exists or the store is empty.
302
- * @param {string} cwd
303
- * @returns {object|null}
304
- */
305
- export function getLatestReceipt(cwd = process.cwd()) {
306
- try {
307
- const dir = receiptsDir(cwd);
308
- if (!existsSync(dir)) return null;
309
- const files = readdirSync(dir)
310
- .filter(f => f.endsWith('.json') && f !== 'index.json')
311
- .sort()
312
- .reverse();
313
- if (files.length === 0) return null;
314
- const raw = readFileSync(join(dir, files[0]), 'utf8');
315
- return JSON.parse(raw);
316
- } catch {
317
- return null;
318
- }
319
- }
320
-
321
- export function buildReceiptFromOutcome(outcome = {}) {
322
- const result = {
323
- success: outcome.success ?? outcome.result?.success ?? false,
324
- filesChanged: outcome.filesChanged ?? outcome.result?.filesChanged ?? [],
325
- error: outcome.error ?? outcome.result?.error ?? null,
326
- duration: outcome.duration ?? outcome.result?.duration ?? 0,
327
- output: outcome.output ?? null,
328
- };
329
- const plan = {
330
- primaryModel: outcome.primaryModel ?? '',
331
- reasoningDepth: outcome.reasoningDepth ?? '',
332
- challengerPolicy: outcome.challengerPolicy ?? 'none',
333
- useChallenger: !!(outcome.challengerPolicy && outcome.challengerPolicy !== 'none'),
334
- tier: outcome.tier ?? '',
335
- workStyle: outcome.workStyle ?? '',
336
- risk: outcome.risk ?? '',
337
- };
338
- const verification = {
339
- filesVerified: outcome.verification?.filesVerified ?? false,
340
- testsRun: outcome.verification?.testsRun ?? false,
341
- testsPassed: outcome.verification?.testsPassed ?? null,
342
- };
343
- return buildReceipt(result, plan, verification);
344
- }