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/awareness.mjs DELETED
@@ -1,425 +0,0 @@
1
- /**
2
- * awareness.mjs — Environment awareness layer for dual-brain.
3
- * Scans runtime environment once on startup and caches results with TTL.
4
- */
5
-
6
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
7
- import { execSync } from 'node:child_process';
8
- import { join, resolve } from 'node:path';
9
- import { homedir } from 'node:os';
10
-
11
- let _cache = null;
12
- let _cacheTime = 0;
13
-
14
- function safeExec(cmd, timeoutMs = 2000) {
15
- try {
16
- return execSync(cmd, {
17
- encoding: 'utf8',
18
- stdio: ['ignore', 'pipe', 'ignore'],
19
- timeout: timeoutMs,
20
- }).trim();
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- function extractVersion(output) {
27
- if (!output) return null;
28
- const m = output.match(/(\d+\.\d+[\.\d]*)/);
29
- return m ? m[1] : null;
30
- }
31
-
32
- function probeToolAvailability(name) {
33
- const path = safeExec(`which ${name}`);
34
- if (!path) return { available: false, version: null };
35
- if (name === 'rg' || name === 'replit' || name === 'gh') {
36
- return { available: true };
37
- }
38
- const versionOutput = safeExec(`${name} --version`);
39
- return { available: true, version: extractVersion(versionOutput) };
40
- }
41
-
42
- function detectContainerType() {
43
- const env = process.env;
44
- if (env.REPL_ID || env.REPL_SLUG) return 'replit';
45
- if (env.CODESPACES) return 'codespace';
46
- if (env.CI || env.GITHUB_ACTIONS || env.GITLAB_CI || env.JENKINS_URL) return 'ci';
47
- return 'local';
48
- }
49
-
50
- function scanSecrets() {
51
- const keys = [
52
- 'NPM_TOKEN',
53
- 'DATABASE_URL',
54
- 'GITHUB_TOKEN',
55
- 'REPLIT_DB_URL',
56
- ];
57
- const result = {};
58
- for (const key of keys) {
59
- result[key] = Boolean(process.env[key]);
60
- }
61
- return result;
62
- }
63
-
64
- function scanReplitTools() {
65
- const home = homedir();
66
- const candidates = [
67
- join(home, '.replit-tools'),
68
- join('/home/runner/workspace', '.replit-tools'),
69
- join(process.cwd(), '.replit-tools'),
70
- ];
71
-
72
- let toolsDir = null;
73
- for (const c of candidates) {
74
- if (existsSync(c)) {
75
- toolsDir = c;
76
- break;
77
- }
78
- }
79
-
80
- if (!toolsDir) {
81
- return { installed: false, version: null, sessionArchivePath: null, capabilities: [] };
82
- }
83
-
84
- let version = null;
85
- const versionFile = join(toolsDir, '.version');
86
- if (existsSync(versionFile)) {
87
- try { version = readFileSync(versionFile, 'utf8').trim() || null; } catch { /* skip */ }
88
- }
89
-
90
- const persistentBase = join(toolsDir, '.claude-persistent');
91
- const projectDir = join(persistentBase, 'projects');
92
- let sessionArchivePath = null;
93
- if (existsSync(projectDir)) {
94
- sessionArchivePath = projectDir;
95
- } else if (existsSync(persistentBase)) {
96
- sessionArchivePath = persistentBase;
97
- }
98
-
99
- const capabilities = [];
100
- if (existsSync(join(toolsDir, '.claude-persistent'))) capabilities.push('sessions');
101
- const hasSearch = existsSync(join(toolsDir, 'search')) || existsSync(join(toolsDir, 'search.mjs'));
102
- if (hasSearch) capabilities.push('search');
103
- const hasContext = existsSync(join(toolsDir, 'context')) || existsSync(join(toolsDir, 'context.mjs'));
104
- if (hasContext) capabilities.push('context');
105
- const hasMcp = existsSync(join(toolsDir, 'mcp-server')) || existsSync(join(toolsDir, 'mcp'));
106
- if (hasMcp) capabilities.push('mcp');
107
-
108
- return { installed: true, version, sessionArchivePath, capabilities };
109
- }
110
-
111
- /**
112
- * Basic Replit environment scan used for the awareness report.
113
- *
114
- * NOTE: Detailed Replit integration (auth status, replit-tools capabilities,
115
- * session archive, secrets listing, config planning, init flow) lives in
116
- * src/replit.mjs. When that module is available, prefer its
117
- * detectReplitEnvironment() over duplicating detection logic here.
118
- *
119
- * Usage (fail-safe):
120
- * try {
121
- * const { detectReplitEnvironment } = await import('./replit.mjs');
122
- * const rich = detectReplitEnvironment(cwd);
123
- * // rich has .isReplit, .replId, .version, .authStatus, .capabilities …
124
- * } catch {
125
- * // fall back to this scanReplit() result from scanEnvironment()
126
- * }
127
- */
128
- function scanReplit(cwd) {
129
- const env = process.env;
130
- const isReplit = Boolean(env.REPL_ID || env.REPL_SLUG);
131
-
132
- let hasDeployments = false;
133
- const replitConfigPath = join(cwd, '.replit');
134
- if (existsSync(replitConfigPath)) {
135
- try {
136
- const content = readFileSync(replitConfigPath, 'utf8');
137
- hasDeployments = content.includes('[deployment]');
138
- } catch { /* skip */ }
139
- }
140
-
141
- return {
142
- isReplit,
143
- replId: env.REPL_ID || null,
144
- replSlug: env.REPL_SLUG || null,
145
- hasDatabase: Boolean(env.DATABASE_URL),
146
- hasKV: Boolean(env.REPLIT_DB_URL),
147
- hasObjectStorage: Boolean(env.REPLIT_BUCKET_URL || env.OBJECT_STORAGE_URL),
148
- hasAuth: existsSync(join(cwd, '.replit-auth')) || Boolean(env.REPLIT_AUTH),
149
- hasDeployments,
150
- };
151
- }
152
-
153
- function scanClaudeCode(cwd) {
154
- const claudeDir = join(cwd, '.claude');
155
- const homeClaudeDir = join(homedir(), '.claude');
156
- const isInsideClaude = Boolean(
157
- process.env.CLAUDE_CODE || process.env.CLAUDE_AGENT || process.env.ANTHROPIC_CLAUDE_CODE
158
- );
159
-
160
- let hooksDir = null;
161
- const localHooks = join(claudeDir, 'hooks');
162
- const rootHooks = join(cwd, 'hooks');
163
- if (existsSync(localHooks)) {
164
- hooksDir = localHooks;
165
- } else if (existsSync(rootHooks)) {
166
- hooksDir = rootHooks;
167
- }
168
-
169
- let mcpConfigured = false;
170
- const mcpPaths = [
171
- join(claudeDir, 'mcp.json'),
172
- join(claudeDir, 'mcp_servers.json'),
173
- join(homeClaudeDir, 'mcp.json'),
174
- ];
175
- for (const p of mcpPaths) {
176
- if (existsSync(p)) { mcpConfigured = true; break; }
177
- }
178
-
179
- let settingsPath = null;
180
- const settingsCandidates = [
181
- join(claudeDir, 'settings.json'),
182
- join(homeClaudeDir, 'settings.json'),
183
- ];
184
- for (const p of settingsCandidates) {
185
- if (existsSync(p)) { settingsPath = p; break; }
186
- }
187
-
188
- return { isInsideClaude, hooksDir, mcpConfigured, settingsPath };
189
- }
190
-
191
- function scanDualBrain(cwd) {
192
- let version = '0.0.0';
193
- try {
194
- const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
195
- version = pkg.version ?? '0.0.0';
196
- } catch { /* skip */ }
197
-
198
- const livingDocsDir = join(cwd, '.dual-brain');
199
- const livingDocsInit = existsSync(livingDocsDir);
200
-
201
- let sessionCount = 0;
202
- const sessionDir = join(cwd, '.dualbrain', 'sessions');
203
- if (existsSync(sessionDir)) {
204
- try {
205
- sessionCount = readdirSync(sessionDir).filter(f => f.endsWith('.jsonl')).length;
206
- } catch { /* skip */ }
207
- }
208
-
209
- const hasLedger = existsSync(join(cwd, '.dualbrain', 'ledger.jsonl'));
210
- const hasFailureMemory = existsSync(join(cwd, '.dualbrain', 'failures.jsonl'));
211
-
212
- return { version, livingDocsInit, sessionCount, hasLedger, hasFailureMemory };
213
- }
214
-
215
- export function scanEnvironment(cwd, options = {}) {
216
- const ttl = options.ttl ?? 300000;
217
- if (_cache && Date.now() - _cacheTime < ttl && !options.force) return _cache;
218
-
219
- const resolvedCwd = resolve(cwd || process.cwd());
220
-
221
- const container = {
222
- type: detectContainerType(),
223
- hostname: process.env.HOSTNAME || process.env.REPL_ID || 'unknown',
224
- nodeVersion: process.version,
225
- platform: process.platform,
226
- arch: process.arch,
227
- };
228
-
229
- const toolNames = ['git', 'node', 'npm', 'codex', 'claude'];
230
- const flagOnlyTools = ['rg', 'replit', 'gh'];
231
- const tools = {};
232
-
233
- for (const name of toolNames) {
234
- tools[name] = probeToolAvailability(name);
235
- }
236
- for (const name of flagOnlyTools) {
237
- const path = safeExec(`which ${name}`);
238
- tools[name] = { available: Boolean(path) };
239
- }
240
-
241
- const secrets = scanSecrets();
242
- const replitTools = scanReplitTools();
243
- const replit = scanReplit(resolvedCwd);
244
- const claudeCode = scanClaudeCode(resolvedCwd);
245
- const dualBrain = scanDualBrain(resolvedCwd);
246
-
247
- const report = {
248
- scannedAt: Date.now(),
249
- ttl,
250
- container,
251
- tools,
252
- secrets,
253
- replitTools,
254
- replit,
255
- claudeCode,
256
- dualBrain,
257
- };
258
-
259
- _cache = report;
260
- _cacheTime = Date.now();
261
- return report;
262
- }
263
-
264
- export function formatEnvironment(report) {
265
- const { container, tools, secrets, replitTools, replit, dualBrain } = report;
266
-
267
- const nodeShort = container.nodeVersion.replace(/^v/, '').split('.')[0];
268
- const containerLabel = container.type === 'replit'
269
- ? 'Replit'
270
- : container.type.charAt(0).toUpperCase() + container.type.slice(1);
271
-
272
- const lines = [];
273
-
274
- lines.push(`Environment: ${containerLabel} (node ${nodeShort}.x)`);
275
-
276
- const toolEntries = [];
277
- for (const [name, info] of Object.entries(tools)) {
278
- if (info.available) toolEntries.push(`${name} ✓`);
279
- }
280
- if (toolEntries.length) lines.push(`Tools: ${toolEntries.join(' ')}`);
281
-
282
- const secretMap = {
283
- NPM_TOKEN: 'npm',
284
- GITHUB_TOKEN: 'GitHub',
285
- DATABASE_URL: 'PostgreSQL',
286
- REPLIT_DB_URL: 'KV',
287
- };
288
- const secretEntries = [];
289
- for (const [key, label] of Object.entries(secretMap)) {
290
- if (secrets[key]) secretEntries.push(`${label} ✓`);
291
- }
292
- if (secretEntries.length) lines.push(`Secrets: ${secretEntries.join(' ')}`);
293
-
294
- const platformParts = [];
295
- if (replit.hasDatabase) platformParts.push('PostgreSQL ✓');
296
- if (replit.hasKV) platformParts.push('KV ✓');
297
- if (replit.hasObjectStorage) platformParts.push('ObjectStorage ✓');
298
- if (replit.hasAuth) platformParts.push('Auth ✓');
299
- if (replit.hasDeployments) platformParts.push('Deployments ✓');
300
- if (platformParts.length) lines.push(`Platform: ${platformParts.join(' ')}`);
301
-
302
- if (replitTools.installed) {
303
- const ver = replitTools.version ? `v${replitTools.version}` : 'installed';
304
- const caps = replitTools.capabilities.join(', ');
305
- lines.push(`replit-tools: ${ver}${caps ? ` (${caps})` : ''}`);
306
- }
307
-
308
- const dbFlag = dualBrain.hasLedger ? ', ledger ✓' : '';
309
- const docsFlag = dualBrain.livingDocsInit ? 'living docs ✓' : 'living docs ✗';
310
- lines.push(`dual-brain: v${dualBrain.version} (${docsFlag}${dbFlag})`);
311
-
312
- return lines.join('\n');
313
- }
314
-
315
- export function getCapabilitySummary(report) {
316
- const caps = [];
317
-
318
- if (report.container.type !== 'unknown') {
319
- caps.push(`${report.container.type}-container`);
320
- }
321
-
322
- if (report.replit.hasDatabase) caps.push('postgresql');
323
- if (report.replit.hasKV) caps.push('replit-kv');
324
- if (report.replit.hasObjectStorage) caps.push('object-storage');
325
- if (report.replit.hasAuth) caps.push('replit-auth');
326
- if (report.replit.hasDeployments) caps.push('replit-deployments');
327
-
328
- if (report.tools.codex?.available) caps.push('codex-cli');
329
- if (report.tools.claude?.available) caps.push('claude-cli');
330
- if (report.tools.git?.available) caps.push('git');
331
- if (report.tools.gh?.available) caps.push('github-cli');
332
- if (report.tools.rg?.available) caps.push('ripgrep');
333
-
334
-
335
- if (report.replitTools.installed) {
336
- for (const c of report.replitTools.capabilities) {
337
- caps.push(`replit-tools-${c}`);
338
- }
339
- }
340
-
341
- if (report.claudeCode.mcpConfigured) caps.push('mcp-configured');
342
- if (report.claudeCode.hooksDir) caps.push('claude-hooks');
343
-
344
- if (report.dualBrain.hasLedger) caps.push('dual-brain-ledger');
345
- if (report.dualBrain.hasFailureMemory) caps.push('dual-brain-failure-memory');
346
- if (report.dualBrain.livingDocsInit) caps.push('dual-brain-living-docs');
347
-
348
- return caps;
349
- }
350
-
351
- export function invalidateCache() {
352
- _cache = null;
353
- _cacheTime = 0;
354
- }
355
-
356
- // ─── Ambiguity Detection ──────────────────────────────────────────────────────
357
-
358
- const TECHNICAL_TERMS = /\b(fix|bug|error|test|deploy|refactor|import|export|function|class|module|api|endpoint|auth|token|database|query|schema|migration|build|lint|type|interface|component|route|handler|middleware|config|env|secret|key|file|path|directory|repo|branch|commit|merge|pull|push|install|upgrade|package|dependency|version|release|publish|log|trace|debug|stack|exception|undefined|null|async|await|promise|fetch|request|response|status|server|client|socket|cache|session)\b/i;
359
-
360
- /**
361
- * Detect whether a prompt is ambiguous and needs clarification before dispatch.
362
- *
363
- * A prompt is considered ambiguous when ALL of the following are true:
364
- * 1. It is very short (under 4 words)
365
- * 2. No file context is provided
366
- * 3. It lacks specific technical terms that narrow the intent
367
- *
368
- * @param {string} prompt — the user's raw prompt
369
- * @param {{ files?: string[] }} [context] — optional context (e.g. file paths)
370
- * @returns {{ isAmbiguous: boolean, reason: string|null }}
371
- */
372
- export function detectAmbiguity(prompt, context = {}) {
373
- if (!prompt || typeof prompt !== 'string') {
374
- return { isAmbiguous: true, reason: 'missing context: empty prompt' };
375
- }
376
-
377
- const words = prompt.trim().split(/\s+/).filter(Boolean);
378
- const isTooShort = words.length < 4;
379
- const hasFileContext = Array.isArray(context?.files) && context.files.length > 0;
380
- const hasTechnicalTerms = TECHNICAL_TERMS.test(prompt);
381
-
382
- if (isTooShort && !hasFileContext && !hasTechnicalTerms) {
383
- return {
384
- isAmbiguous: true,
385
- reason: `unclear: prompt is vague ("${prompt.trim()}") — missing context about what to change and where`,
386
- };
387
- }
388
-
389
- return { isAmbiguous: false, reason: null };
390
- }
391
-
392
- /**
393
- * Detect whether a user prompt is too vague to act on confidently.
394
- *
395
- * Checks for:
396
- * - Very short prompts (under 10 chars)
397
- * - No file paths, function names, or specific identifiers
398
- * - Pronoun-only references without antecedents ("fix that thing", "change it")
399
- *
400
- * @param {string} prompt — the user's raw prompt
401
- * @returns {{ ambiguous: boolean, reason: string|null, confidence: number }}
402
- */
403
- export function isAmbiguous(prompt) {
404
- if (!prompt || typeof prompt !== 'string') return { ambiguous: true, reason: 'empty-prompt', confidence: 1.0 };
405
-
406
- const trimmed = prompt.trim();
407
- if (trimmed.length < 10) return { ambiguous: true, reason: 'too-short', confidence: 0.9 };
408
-
409
- // Check for file paths, function names, specific identifiers
410
- const hasSpecifics = /[a-zA-Z_]\w*\.(mjs|js|ts|tsx|jsx|py|go|rs|java|rb|css|html|json|yaml|yml|md|sh)/.test(trimmed)
411
- || /[a-zA-Z_]\w*\(\)/.test(trimmed) // function calls
412
- || /`[^`]+`/.test(trimmed) // backtick-quoted identifiers
413
- || /"[^"]{3,}"/.test(trimmed) // quoted strings
414
- || /\b(line|function|class|method|variable|module|component|endpoint|route|table|column)\s+\w+/i.test(trimmed);
415
-
416
- // Vague pronoun patterns
417
- const vaguePatterns = /^(fix|change|update|do|make|help|look at|check)\s+(this|that|it|the thing|stuff|things?)$/i;
418
- if (vaguePatterns.test(trimmed)) return { ambiguous: true, reason: 'vague-reference', confidence: 0.85 };
419
-
420
- if (!hasSpecifics && trimmed.split(/\s+/).length < 5) {
421
- return { ambiguous: true, reason: 'lacks-specifics', confidence: 0.7 };
422
- }
423
-
424
- return { ambiguous: false, reason: null, confidence: 0.1 };
425
- }
package/src/brief.mjs DELETED
@@ -1,266 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * brief.mjs — Delegation brief generator for the Dual-Brain Orchestrator HEAD.
4
- *
5
- * Generates typed delegation prompts from role-based templates. The HEAD's
6
- * primary skill is writing great agent briefs. Pure string construction —
7
- * no imports from sibling modules.
8
- *
9
- * Exports: generateBrief, compressPriorResults, listRoles
10
- */
11
-
12
- // ─── Brief templates (role-based) ────────────────────────────────────────────
13
-
14
- const TEMPLATES = {
15
- researcher: {
16
- prefix: 'You are a code researcher. READ ONLY — do NOT edit any files.',
17
- outputFormat: 'Return: { findings: string, files: string[], lineRefs: string[], confidence: "high"|"medium"|"low" }',
18
- constraints: 'Max 50 lines of quoted code. Focus on structure and relationships, not implementation details.',
19
- },
20
- implementer: {
21
- prefix: 'You are a code implementer. Edit ONLY files in your ownership list.',
22
- outputFormat: 'Return: { filesChanged: string[], testsRun: string[], decisions: string[], risks: string[] }',
23
- constraints: 'Run tests after changes. Make minimal edits. Do not refactor beyond scope.',
24
- },
25
- reviewer: {
26
- prefix: 'You are a code reviewer. READ ONLY — do NOT edit any files.',
27
- outputFormat: 'Return: { issues: { severity: string, file: string, line: number, description: string }[], approved: boolean, risks: string[] }',
28
- constraints: 'Focus on correctness, security, and behavior preservation. Ignore style.',
29
- },
30
- verifier: {
31
- prefix: 'You are a test verifier. Run the test suite and report results.',
32
- outputFormat: 'Return: { passed: boolean, failCount: number, failures: string[], rootCause: string | null }',
33
- constraints: 'If tests fail, identify root cause but DO NOT fix. Report only.',
34
- },
35
- };
36
-
37
- // ─── Exported: listRoles ──────────────────────────────────────────────────────
38
-
39
- /**
40
- * Return available role names and their short descriptions.
41
- * @returns {{ role: string, description: string }[]}
42
- */
43
- export function listRoles() {
44
- return [
45
- { role: 'researcher', description: 'Read-only exploration and mapping. Returns findings, file refs, and confidence.' },
46
- { role: 'implementer', description: 'Makes edits within ownership scope. Returns changed files, test results, and decisions.' },
47
- { role: 'reviewer', description: 'Read-only code review. Returns issues by severity and an approval verdict.' },
48
- { role: 'verifier', description: 'Runs the test suite and identifies failures. Does not fix anything.' },
49
- ];
50
- }
51
-
52
- // ─── Exported: compressPriorResults ──────────────────────────────────────────
53
-
54
- /**
55
- * Take an array of prior wave results and return a compact string suitable for
56
- * injection into agent briefs. Strips code blocks, keeps decisions and file paths.
57
- *
58
- * @param {Array<{ stepId?: string, taskId?: string, summary?: string, output?: string, filesChanged?: string[], decisions?: string[], findings?: string }>} results
59
- * @returns {string}
60
- */
61
- export function compressPriorResults(results) {
62
- if (!results || results.length === 0) return '';
63
-
64
- const lines = [];
65
- for (const r of results) {
66
- const id = r.stepId ?? r.taskId ?? '?';
67
- const parts = [];
68
-
69
- // Extract summary or findings
70
- const rawSummary = r.summary ?? r.findings ?? r.output ?? '';
71
- if (rawSummary) {
72
- // Strip code blocks
73
- const cleaned = String(rawSummary)
74
- .replace(/```[\s\S]*?```/g, '[code block]')
75
- .replace(/`[^`]+`/g, (m) => m.replace(/\n/g, ' '))
76
- .replace(/\n{3,}/g, '\n\n')
77
- .trim();
78
- // Keep only first 200 chars of text
79
- const snippet = cleaned.length > 200 ? cleaned.slice(0, 197) + '...' : cleaned;
80
- if (snippet) parts.push(snippet);
81
- }
82
-
83
- // Append file lists compactly
84
- if (Array.isArray(r.filesChanged) && r.filesChanged.length > 0) {
85
- parts.push(`files: ${r.filesChanged.join(', ')}`);
86
- }
87
-
88
- // Append key decisions
89
- if (Array.isArray(r.decisions) && r.decisions.length > 0) {
90
- parts.push(`decisions: ${r.decisions.slice(0, 3).join('; ')}`);
91
- }
92
-
93
- if (parts.length > 0) {
94
- lines.push(`[${id}] ${parts.join(' | ')}`);
95
- }
96
- }
97
-
98
- return lines.join('\n');
99
- }
100
-
101
- // ─── Section builders ─────────────────────────────────────────────────────────
102
-
103
- function buildOwnershipSection(owns) {
104
- if (!owns || owns.length === 0) return null;
105
- return `File ownership (ONLY edit these):\n ${owns.join('\n ')}`;
106
- }
107
-
108
- function buildPriorResultsSection(priorResults) {
109
- const compressed = compressPriorResults(priorResults);
110
- if (!compressed) return null;
111
- return `Prior wave results (context only — do not repeat this work):\n${compressed}`;
112
- }
113
-
114
- function buildAcceptanceCriteria(task) {
115
- const lines = [];
116
-
117
- if (task.role === 'researcher') {
118
- lines.push('- Return a structured findings object with file paths and line references');
119
- lines.push('- Report confidence level based on coverage of the codebase you explored');
120
- } else if (task.role === 'implementer') {
121
- lines.push('- All edits must stay within the ownership list above');
122
- lines.push('- Run existing tests; report any failures');
123
- lines.push('- Document decisions made during implementation');
124
- } else if (task.role === 'reviewer') {
125
- lines.push('- Report every issue with severity (critical / high / medium / low)');
126
- lines.push('- Provide a clear approved: true/false verdict');
127
- lines.push('- Do not edit any files');
128
- } else if (task.role === 'verifier') {
129
- lines.push('- Run the full test suite');
130
- lines.push('- If tests fail, identify root cause but do NOT fix');
131
- lines.push('- Return pass/fail with failure details');
132
- }
133
-
134
- if (task.consensus) {
135
- lines.push('- This task requires dual-brain consensus — be thorough and explicit in your reasoning');
136
- }
137
-
138
- return lines.length > 0 ? `Acceptance criteria:\n${lines.join('\n')}` : null;
139
- }
140
-
141
- // ─── Exported: generateBrief ──────────────────────────────────────────────────
142
-
143
- /**
144
- * Generate a full delegation prompt for a single agent task.
145
- *
146
- * @param {{
147
- * id: string,
148
- * title: string,
149
- * goal: string,
150
- * tier: string,
151
- * role: 'researcher'|'implementer'|'reviewer'|'verifier',
152
- * owns: string[],
153
- * consensus: boolean,
154
- * risk: string,
155
- * }} task — from decompose output
156
- * @param {{
157
- * prompt?: string,
158
- * priorResults?: object[],
159
- * repo?: { projectType?: string, branch?: string, testCmd?: string, lintCmd?: string },
160
- * cwd?: string,
161
- * }} [context]
162
- * @returns {string} Full delegation prompt string
163
- */
164
- export function generateBrief(task, context = {}) {
165
- const { prompt = '', priorResults = [], repo = {} } = context;
166
- const role = task.role ?? 'implementer';
167
- const template = TEMPLATES[role] ?? TEMPLATES.implementer;
168
-
169
- const sections = [];
170
-
171
- // 1. Role header
172
- sections.push(template.prefix);
173
- sections.push('');
174
-
175
- // 2. Task identity
176
- sections.push(`Task: ${task.title}`);
177
- if (task.id) sections.push(`Task ID: ${task.id}`);
178
- if (task.tier) sections.push(`Tier: ${task.tier}`);
179
- sections.push('');
180
-
181
- // 3. Goal
182
- sections.push(`Goal:\n${task.goal}`);
183
- sections.push('');
184
-
185
- // 4. Original user request (for context)
186
- if (prompt && prompt !== task.goal) {
187
- const snippet = prompt.length > 300 ? prompt.slice(0, 297) + '...' : prompt;
188
- sections.push(`Original request (for context):\n${snippet}`);
189
- sections.push('');
190
- }
191
-
192
- // 5. File ownership
193
- const ownershipSection = buildOwnershipSection(task.owns);
194
- if (ownershipSection) {
195
- sections.push(ownershipSection);
196
- sections.push('');
197
- }
198
-
199
- // 6. Repository context (if available)
200
- const repoLines = [];
201
- if (repo.projectType) repoLines.push(`Project type: ${repo.projectType}`);
202
- if (repo.branch) repoLines.push(`Branch: ${repo.branch}`);
203
- if (repo.testCmd) repoLines.push(`Test command: ${repo.testCmd}`);
204
- if (repo.lintCmd) repoLines.push(`Lint command: ${repo.lintCmd}`);
205
- if (repoLines.length > 0) {
206
- sections.push(`Repository context:\n ${repoLines.join('\n ')}`);
207
- sections.push('');
208
- }
209
-
210
- // 7. Prior results
211
- const priorSection = buildPriorResultsSection(priorResults);
212
- if (priorSection) {
213
- sections.push(priorSection);
214
- sections.push('');
215
- }
216
-
217
- // 8. Acceptance criteria
218
- const criteriaSection = buildAcceptanceCriteria(task);
219
- if (criteriaSection) {
220
- sections.push(criteriaSection);
221
- sections.push('');
222
- }
223
-
224
- // 9. Constraints
225
- sections.push(`Constraints:\n${template.constraints}`);
226
- sections.push('');
227
-
228
- // 10. Output format requirement
229
- sections.push(`Required output format:\n${template.outputFormat}`);
230
-
231
- return sections.join('\n').trimEnd();
232
- }
233
-
234
- // ─── CLI ──────────────────────────────────────────────────────────────────────
235
-
236
- if (process.argv[1] && new URL(import.meta.url).pathname === process.argv[1]) {
237
- const args = process.argv.slice(2);
238
- const cmd = args[0];
239
-
240
- if (cmd === 'roles') {
241
- console.log(JSON.stringify(listRoles(), null, 2));
242
- process.exit(0);
243
- }
244
-
245
- if (cmd === 'generate') {
246
- const role = args[1] ?? 'implementer';
247
- const goal = args[2] ?? 'No goal provided.';
248
- const task = {
249
- id: 'task-1',
250
- title: goal.slice(0, 60),
251
- goal,
252
- tier: 'execute',
253
- role,
254
- owns: [],
255
- consensus: false,
256
- risk: 'medium',
257
- };
258
- console.log(generateBrief(task, { prompt: goal }));
259
- process.exit(0);
260
- }
261
-
262
- console.error('Usage:');
263
- console.error(' node src/brief.mjs roles');
264
- console.error(' node src/brief.mjs generate <role> "<goal>"');
265
- process.exit(1);
266
- }