brainclaw 0.28.0 → 1.5.3

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 (198) hide show
  1. package/README.md +193 -170
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +683 -23
  4. package/dist/commands/accept.js +3 -0
  5. package/dist/commands/add-step.js +11 -26
  6. package/dist/commands/agent-board.js +70 -3
  7. package/dist/commands/audit.js +19 -0
  8. package/dist/commands/check-policy.js +54 -0
  9. package/dist/commands/check-security-mcp.js +145 -0
  10. package/dist/commands/check-security.js +106 -0
  11. package/dist/commands/claim-resource.js +1 -0
  12. package/dist/commands/codev.js +672 -0
  13. package/dist/commands/compact.js +74 -0
  14. package/dist/commands/complete-step.js +16 -26
  15. package/dist/commands/constraint.js +8 -20
  16. package/dist/commands/decision.js +9 -20
  17. package/dist/commands/delete-plan.js +10 -12
  18. package/dist/commands/delete-step.js +16 -0
  19. package/dist/commands/dispatch.js +163 -0
  20. package/dist/commands/doctor.js +1122 -49
  21. package/dist/commands/enable-agent.js +1 -0
  22. package/dist/commands/export.js +280 -22
  23. package/dist/commands/handoff.js +33 -0
  24. package/dist/commands/harvest.js +189 -0
  25. package/dist/commands/hooks.js +82 -25
  26. package/dist/commands/inbox.js +169 -0
  27. package/dist/commands/init.js +38 -31
  28. package/dist/commands/install-hooks.js +71 -44
  29. package/dist/commands/link.js +89 -0
  30. package/dist/commands/list-claims.js +48 -3
  31. package/dist/commands/list-plans.js +129 -25
  32. package/dist/commands/loops-handlers.js +409 -0
  33. package/dist/commands/mcp-read-handlers.js +1628 -0
  34. package/dist/commands/mcp-schemas.generated.js +74 -0
  35. package/dist/commands/mcp.js +4244 -1475
  36. package/dist/commands/plan-resource.js +64 -0
  37. package/dist/commands/plan.js +12 -26
  38. package/dist/commands/prune.js +37 -2
  39. package/dist/commands/reflect.js +20 -7
  40. package/dist/commands/release-claim.js +11 -6
  41. package/dist/commands/release-notes.js +170 -0
  42. package/dist/commands/repair.js +210 -0
  43. package/dist/commands/run-profile.js +57 -0
  44. package/dist/commands/sequence.js +113 -0
  45. package/dist/commands/session-end.js +423 -14
  46. package/dist/commands/session-start.js +214 -41
  47. package/dist/commands/setup-security.js +103 -0
  48. package/dist/commands/setup.js +42 -4
  49. package/dist/commands/stale.js +109 -0
  50. package/dist/commands/switch.js +131 -10
  51. package/dist/commands/trap.js +14 -31
  52. package/dist/commands/update-handoff.js +63 -4
  53. package/dist/commands/update-plan.js +21 -28
  54. package/dist/commands/update-step.js +37 -0
  55. package/dist/commands/upgrade.js +313 -6
  56. package/dist/commands/usage.js +102 -0
  57. package/dist/commands/version.js +20 -0
  58. package/dist/commands/who.js +124 -0
  59. package/dist/commands/worktree.js +105 -0
  60. package/dist/core/actions.js +315 -0
  61. package/dist/core/agent-capability.js +610 -17
  62. package/dist/core/agent-context.js +7 -1
  63. package/dist/core/agent-files.js +1169 -85
  64. package/dist/core/agent-integrations.js +160 -5
  65. package/dist/core/agent-inventory.js +2 -0
  66. package/dist/core/agent-profiles.js +93 -0
  67. package/dist/core/agent-registry.js +162 -30
  68. package/dist/core/agentrun-reconciler.js +345 -0
  69. package/dist/core/agentruns.js +424 -0
  70. package/dist/core/ai-agent-detection.js +31 -10
  71. package/dist/core/archival.js +77 -0
  72. package/dist/core/assignment-sweeper.js +82 -0
  73. package/dist/core/assignments.js +367 -0
  74. package/dist/core/audit.js +30 -0
  75. package/dist/core/bootstrap.js +61 -10
  76. package/dist/core/brainclaw-version.js +94 -2
  77. package/dist/core/candidates.js +93 -2
  78. package/dist/core/claims.js +419 -0
  79. package/dist/core/codev-metrics.js +77 -0
  80. package/dist/core/codev-personas.js +31 -0
  81. package/dist/core/codev-plan-gen.js +35 -0
  82. package/dist/core/codev-prompts.js +74 -0
  83. package/dist/core/codev-responses.js +62 -0
  84. package/dist/core/codev-rounds.js +218 -0
  85. package/dist/core/config.js +4 -0
  86. package/dist/core/context.js +454 -34
  87. package/dist/core/coordination.js +201 -6
  88. package/dist/core/cross-project.js +230 -16
  89. package/dist/core/default-profiles/doctor.yaml +11 -0
  90. package/dist/core/default-profiles/janitor.yaml +11 -0
  91. package/dist/core/default-profiles/onboarder.yaml +11 -0
  92. package/dist/core/default-profiles/reviewer.yaml +13 -0
  93. package/dist/core/dispatcher.js +1189 -0
  94. package/dist/core/duplicates.js +2 -2
  95. package/dist/core/entity-operations.js +450 -0
  96. package/dist/core/entity-registry.js +344 -0
  97. package/dist/core/event-log.js +1 -0
  98. package/dist/core/events.js +106 -2
  99. package/dist/core/execution-adapters.js +154 -0
  100. package/dist/core/execution-context.js +63 -0
  101. package/dist/core/execution-profile.js +270 -0
  102. package/dist/core/execution.js +255 -0
  103. package/dist/core/facade-schema.js +81 -0
  104. package/dist/core/federation-cloud.js +99 -0
  105. package/dist/core/federation-message.js +52 -0
  106. package/dist/core/federation-transport.js +65 -0
  107. package/dist/core/gc-semantic.js +482 -0
  108. package/dist/core/governance.js +247 -0
  109. package/dist/core/guards.js +19 -0
  110. package/dist/core/ideation.js +72 -0
  111. package/dist/core/identity.js +252 -28
  112. package/dist/core/ids.js +6 -0
  113. package/dist/core/input-validation.js +2 -2
  114. package/dist/core/instruction-templates.js +344 -136
  115. package/dist/core/io.js +90 -11
  116. package/dist/core/lock.js +6 -2
  117. package/dist/core/loops/brief-assembly.js +213 -0
  118. package/dist/core/loops/facade-schema.js +148 -0
  119. package/dist/core/loops/index.js +7 -0
  120. package/dist/core/loops/iteration-engine.js +139 -0
  121. package/dist/core/loops/lock.js +385 -0
  122. package/dist/core/loops/store.js +201 -0
  123. package/dist/core/loops/types.js +403 -0
  124. package/dist/core/loops/verbs.js +534 -0
  125. package/dist/core/markdown.js +15 -3
  126. package/dist/core/memory-compactor.js +432 -0
  127. package/dist/core/memory-git.js +152 -8
  128. package/dist/core/messaging.js +278 -0
  129. package/dist/core/migration.js +32 -1
  130. package/dist/core/mutation-pipeline.js +4 -2
  131. package/dist/core/operations/memory-mutation.js +129 -0
  132. package/dist/core/operations/memory-write.js +78 -0
  133. package/dist/core/operations/plan.js +190 -0
  134. package/dist/core/policy.js +169 -0
  135. package/dist/core/repo-analysis.js +67 -0
  136. package/dist/core/reputation.js +9 -3
  137. package/dist/core/schema.js +546 -21
  138. package/dist/core/search.js +21 -2
  139. package/dist/core/security-cache.js +71 -0
  140. package/dist/core/security-guard.js +152 -0
  141. package/dist/core/security-scoring.js +86 -0
  142. package/dist/core/sequence.js +130 -0
  143. package/dist/core/socket-client.js +113 -0
  144. package/dist/core/staleness.js +246 -0
  145. package/dist/core/state.js +98 -22
  146. package/dist/core/store-resolution.js +54 -12
  147. package/dist/core/toml-writer.js +76 -0
  148. package/dist/core/upgrades/backup.js +232 -0
  149. package/dist/core/upgrades/health-check.js +169 -0
  150. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  151. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  152. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  153. package/dist/core/upgrades/schema-version.js +97 -0
  154. package/dist/core/worktree.js +606 -0
  155. package/dist/facts.js +114 -0
  156. package/dist/facts.json +111 -0
  157. package/docs/architecture/project-refs.md +5 -1
  158. package/docs/cli.md +690 -43
  159. package/docs/concepts/ideation-loop.md +317 -0
  160. package/docs/concepts/loop-engine.md +456 -0
  161. package/docs/concepts/mcp-governance.md +268 -0
  162. package/docs/concepts/memory-staleness.md +122 -0
  163. package/docs/concepts/multi-agent-workflows.md +166 -0
  164. package/docs/concepts/plans-and-claims.md +31 -6
  165. package/docs/concepts/project-md-convention.md +35 -0
  166. package/docs/concepts/troubleshooting.md +220 -0
  167. package/docs/concepts/upgrade-cli.md +202 -0
  168. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  169. package/docs/context-format-changelog.md +2 -2
  170. package/docs/context-format.md +2 -2
  171. package/docs/index.md +68 -0
  172. package/docs/integrations/agents.md +15 -16
  173. package/docs/integrations/cline.md +88 -0
  174. package/docs/integrations/codex.md +75 -23
  175. package/docs/integrations/continue.md +60 -0
  176. package/docs/integrations/copilot.md +67 -9
  177. package/docs/integrations/kilocode.md +72 -0
  178. package/docs/integrations/mcp.md +304 -21
  179. package/docs/integrations/mistral-vibe.md +122 -0
  180. package/docs/integrations/opencode.md +84 -0
  181. package/docs/integrations/overview.md +23 -8
  182. package/docs/integrations/roo.md +74 -0
  183. package/docs/integrations/windsurf.md +83 -0
  184. package/docs/mcp-schema-changelog.md +191 -1
  185. package/docs/playbooks/integration/index.md +121 -0
  186. package/docs/playbooks/productivity/index.md +102 -0
  187. package/docs/playbooks/team/index.md +122 -0
  188. package/docs/product/agent-first-model.md +184 -0
  189. package/docs/product/entity-model-audit.md +462 -0
  190. package/docs/quickstart-existing-project.md +135 -0
  191. package/docs/quickstart.md +124 -37
  192. package/docs/release-maintenance.md +79 -0
  193. package/docs/review.md +2 -0
  194. package/docs/server-operations.md +118 -0
  195. package/package.json +20 -12
  196. package/dist/commands/claude-desktop-extension.js +0 -18
  197. package/dist/commands/diff.js +0 -99
  198. package/dist/core/claude-desktop-extension.js +0 -224
@@ -18,6 +18,7 @@ export function runEnableAgent(agentName, options = {}) {
18
18
  const agent = registerAgentIdentity({
19
19
  agentName,
20
20
  kind: options.kind ?? 'agent',
21
+ contextProfile: options.contextProfile,
21
22
  capabilities: options.capability,
22
23
  replaceCapabilities: options.replaceCapabilities,
23
24
  generateFingerprint: options.generateFingerprint,
@@ -1,15 +1,18 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { memoryExists } from '../core/io.js';
3
+ import { memoryExists, readProjectVision } from '../core/io.js';
4
4
  import { loadState } from '../core/state.js';
5
5
  import { loadConfig, saveConfig } from '../core/config.js';
6
6
  import { isAgentIntegrationName, upsertAgentIntegrationDeclaration } from '../core/agent-integrations.js';
7
7
  import { resolveInstructions, loadInstructions } from '../core/instructions.js';
8
8
  import { detectAiAgent } from '../core/ai-agent-detection.js';
9
- import { AGENT_EXPORT_REGISTRY, resolveExportTarget, resolveExportTargetByFormat, writeExportFile, buildHygieneSection, describeAutoConfigWrite, writeExportCompanionFiles, collectExportGitignoreEntries, ensureGitignoreEntries, } from '../core/agent-files.js';
9
+ import { AGENT_EXPORT_REGISTRY, resolveExportTarget, resolveExportTargetByFormat, writeExportFile, writeLiveCompanionFile, buildHygieneSection, describeAutoConfigWrite, writeExportCompanionFiles, collectExportGitignoreEntries, ensureGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
10
+ import { buildCoordinationSnapshot } from '../core/coordination.js';
11
+ import { listClaims } from '../core/claims.js';
12
+ import { listCandidates } from '../core/candidates.js';
10
13
  import { logger } from '../core/logger.js';
11
14
  import { getAgentCapabilityProfile } from '../core/agent-capability.js';
12
- import { renderBrainclawSection } from '../core/instruction-templates.js';
15
+ import { renderBrainclawSection, renderLiveSection } from '../core/instruction-templates.js';
13
16
  import { getInstalledBrainclawVersion } from '../core/brainclaw-version.js';
14
17
  export function runExport(options) {
15
18
  const cwd = options.cwd ?? process.cwd();
@@ -17,6 +20,10 @@ export function runExport(options) {
17
20
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
18
21
  process.exit(1);
19
22
  }
23
+ if (options.shared && (!options.write || !options.format || options.all || options.detect || options.output)) {
24
+ console.error('Error: --shared requires --format <format> --write and cannot be used with --all, --detect, or --output.');
25
+ process.exit(1);
26
+ }
20
27
  if (options.all) {
21
28
  runExportAll(cwd, options);
22
29
  return;
@@ -29,6 +36,16 @@ export function runExport(options) {
29
36
  runExportDetect(cwd, options);
30
37
  return;
31
38
  }
39
+ if (options.includeLive) {
40
+ if (options.output) {
41
+ console.error('Error: --include-live cannot be used with --output. Use --write, --detect, or --all.');
42
+ process.exit(1);
43
+ }
44
+ if (!options.write) {
45
+ console.error('Error: --include-live requires --write, --detect, or --all.');
46
+ process.exit(1);
47
+ }
48
+ }
32
49
  if (!options.format) {
33
50
  console.error('Error: --format, --detect, or --all is required.');
34
51
  process.exit(1);
@@ -37,15 +54,20 @@ export function runExport(options) {
37
54
  if (options.write) {
38
55
  const target = resolveExportTargetByFormat(options.format);
39
56
  const result = writeExportFile(content, target.relativePath, cwd);
57
+ const liveResult = options.includeLive ? writeLiveCompanionForTarget(target, options, cwd) : undefined;
40
58
  const autoConfigs = writeExportCompanionFiles(options.format, cwd);
41
59
  const gitignoreEntries = collectExportGitignoreEntries(cwd, target.relativePath, autoConfigs, {
42
60
  includeTarget: !options.shared,
43
61
  });
44
- if (gitignoreEntries.length > 0) {
45
- ensureGitignoreEntries(cwd, gitignoreEntries);
46
- }
62
+ if (liveResult)
63
+ gitignoreEntries.push(liveResult.relativePath);
64
+ ensureGitignoreEntries(cwd, [...gitignoreEntries, ...BRAINCLAW_EXCLUSIVE_DIRECTORIES]);
47
65
  declareAgentIntegrationFromTarget(cwd, target.agentName, 'manual');
48
66
  console.log(`✔ Written to ${target.relativePath} (${result.created ? 'created' : 'updated'})`);
67
+ if (liveResult) {
68
+ const status = liveResult.created ? 'created' : liveResult.updated ? 'updated' : 'unchanged';
69
+ console.log(`Written live companion to ${liveResult.relativePath} (${status})`);
70
+ }
49
71
  if (options.shared) {
50
72
  console.log(`✔ Left ${target.relativePath} versionable (--shared); local companion config remains gitignored`);
51
73
  }
@@ -75,15 +97,20 @@ function runExportDetect(cwd, options) {
75
97
  const target = detected ? resolveExportTarget(detected.name) : resolveExportTarget('unknown');
76
98
  const content = generateExport(target.format, options, cwd);
77
99
  const result = writeExportFile(content, target.relativePath, cwd);
100
+ const liveResult = options.includeLive ? writeLiveCompanionForTarget(target, options, cwd) : undefined;
78
101
  const autoConfigs = writeExportCompanionFiles(target.format, cwd);
79
102
  const gitignoreEntries = collectExportGitignoreEntries(cwd, target.relativePath, autoConfigs);
80
- if (gitignoreEntries.length > 0) {
81
- ensureGitignoreEntries(cwd, gitignoreEntries);
82
- }
103
+ if (liveResult)
104
+ gitignoreEntries.push(liveResult.relativePath);
105
+ ensureGitignoreEntries(cwd, [...gitignoreEntries, ...BRAINCLAW_EXCLUSIVE_DIRECTORIES]);
83
106
  declareAgentIntegrationFromTarget(cwd, target.agentName, detected ? 'detected' : 'manual');
84
107
  const source = detected ? `${detected.name} [${detected.detection_source}]` : 'fallback (no agent detected)';
85
108
  console.log(`✔ Detected: ${source}`);
86
109
  console.log(`✔ Written to ${target.relativePath} (${result.created ? 'created' : 'updated'})`);
110
+ if (liveResult) {
111
+ const status = liveResult.created ? 'created' : liveResult.updated ? 'updated' : 'unchanged';
112
+ console.log(`Written live companion to ${liveResult.relativePath} (${status})`);
113
+ }
87
114
  if (gitignoreEntries.length > 0) {
88
115
  console.log('✔ Added generated local agent files to .gitignore');
89
116
  }
@@ -109,11 +136,18 @@ function runExportAll(cwd, options) {
109
136
  try {
110
137
  const content = generateExport(target.format, options, cwd);
111
138
  const result = writeExportFile(content, target.relativePath, cwd);
139
+ const liveResult = options.includeLive ? writeLiveCompanionForTarget(target, options, cwd) : undefined;
112
140
  const autoConfigs = writeExportCompanionFiles(target.format, cwd);
113
141
  const gitignoreEntries = collectExportGitignoreEntries(cwd, target.relativePath, autoConfigs);
114
142
  allGitignoreEntries.push(...gitignoreEntries);
143
+ if (liveResult)
144
+ allGitignoreEntries.push(liveResult.relativePath);
115
145
  declareAgentIntegrationFromTarget(cwd, target.agentName, 'manual');
116
146
  console.log(`✔ ${target.relativePath} (${result.created ? 'created' : 'updated'})`);
147
+ if (liveResult) {
148
+ const status = liveResult.created ? 'created' : liveResult.updated ? 'updated' : 'unchanged';
149
+ console.log(`Written live companion to ${liveResult.relativePath} (${status})`);
150
+ }
117
151
  written++;
118
152
  }
119
153
  catch (err) {
@@ -123,11 +157,104 @@ function runExportAll(cwd, options) {
123
157
  }
124
158
  // Consolidate gitignore entries
125
159
  if (allGitignoreEntries.length > 0) {
126
- ensureGitignoreEntries(cwd, [...new Set(allGitignoreEntries)]);
160
+ ensureGitignoreEntries(cwd, [...new Set([...allGitignoreEntries, ...BRAINCLAW_EXCLUSIVE_DIRECTORIES])]);
127
161
  console.log('✔ Updated .gitignore');
128
162
  }
129
163
  console.log(`✔ Exported ${written} agent file(s)`);
130
164
  }
165
+ /**
166
+ * Refresh live companion files for agents that emit a filesystem live view.
167
+ * These are gitignored files with current state (plans, claims, traps, candidates, handoffs).
168
+ */
169
+ /**
170
+ * Refresh live companion files silently. Returns count of files written.
171
+ * Non-fatal: errors are collected but never thrown. Safe to call from
172
+ * post-mutation hooks where a refresh failure must not break the mutation.
173
+ *
174
+ * Respects `auto_refresh_live` config flag (default: true).
175
+ */
176
+ export function refreshLiveCompanions(cwd) {
177
+ const effectiveCwd = cwd ?? process.cwd();
178
+ if (!memoryExists(effectiveCwd)) {
179
+ return { written: 0, errors: [] };
180
+ }
181
+ try {
182
+ const config = loadConfig(effectiveCwd);
183
+ if (config.auto_refresh_live === false) {
184
+ return { written: 0, errors: [] };
185
+ }
186
+ const state = loadState(effectiveCwd);
187
+ const instructions = getInstructionText({ project: undefined, agent: undefined }, effectiveCwd);
188
+ const activeClaims = listClaims(effectiveCwd).filter((c) => c.status === 'active');
189
+ const pendingCandidates = listCandidates('pending', effectiveCwd);
190
+ const seen = new Set();
191
+ const targets = AGENT_EXPORT_REGISTRY.filter((t) => {
192
+ if (seen.has(t.format))
193
+ return false;
194
+ seen.add(t.format);
195
+ return true;
196
+ });
197
+ let written = 0;
198
+ const errors = [];
199
+ const liveGitignoreEntries = [];
200
+ for (const target of targets) {
201
+ try {
202
+ const profile = getAgentCapabilityProfile(target.agentName);
203
+ if (!profile)
204
+ continue;
205
+ const input = {
206
+ profile,
207
+ state,
208
+ projectName: config.project_name,
209
+ brainclawVersion: getInstalledBrainclawVersion(),
210
+ resolvedInstructions: instructions,
211
+ projectVision: readProjectVision(effectiveCwd),
212
+ activeClaims,
213
+ pendingCandidates,
214
+ };
215
+ const live = renderLiveSection(input);
216
+ if (!live)
217
+ continue; // Tier A — no live companion needed
218
+ const writeResult = writeLiveCompanionFile(live.content, target.agentName, target.relativePath, effectiveCwd);
219
+ if (writeResult.created || writeResult.updated) {
220
+ written++;
221
+ }
222
+ liveGitignoreEntries.push(writeResult.relativePath);
223
+ }
224
+ catch (err) {
225
+ errors.push(`${target.agentName}: ${err instanceof Error ? err.message : String(err)}`);
226
+ }
227
+ }
228
+ if (liveGitignoreEntries.length > 0) {
229
+ ensureGitignoreEntries(effectiveCwd, liveGitignoreEntries);
230
+ }
231
+ return { written, errors };
232
+ }
233
+ catch (err) {
234
+ return { written: 0, errors: [err instanceof Error ? err.message : String(err)] };
235
+ }
236
+ }
237
+ export function runRefresh(cwd) {
238
+ const effectiveCwd = cwd ?? process.cwd();
239
+ if (!memoryExists(effectiveCwd)) {
240
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
241
+ process.exit(1);
242
+ }
243
+ const result = refreshLiveCompanions(effectiveCwd);
244
+ for (const err of result.errors) {
245
+ console.warn(`⚠ ${err}`);
246
+ }
247
+ if (result.written > 0) {
248
+ console.log(`✔ Refreshed ${result.written} live companion file(s)`);
249
+ }
250
+ else {
251
+ console.log('✔ All live companions are up to date.');
252
+ }
253
+ }
254
+ /**
255
+ * Convert a stable export path to its live companion path.
256
+ * e.g. CLAUDE.md → CLAUDE.live.md, .cursor/rules/brainclaw.md → .cursor/rules/brainclaw.live.md
257
+ */
131
258
  export function writeAgentExportForAgent(agentName, cwd) {
132
259
  const rendered = renderAgentExportForAgent(agentName, cwd);
133
260
  if (!rendered) {
@@ -166,19 +293,22 @@ function declareAgentIntegrationFromTarget(cwd, agentName, declarationSource) {
166
293
  saveConfig(config, cwd);
167
294
  }
168
295
  }
296
+ /**
297
+ * Map an export format to the agent name used to look up a capability profile.
298
+ *
299
+ * Derived from AGENT_EXPORT_REGISTRY so new formats registered there
300
+ * automatically get the adaptive generation path. Historically this was a
301
+ * hand-maintained map that drifted: the 5 autonomous-agent formats
302
+ * (openclaw, nanoclaw, nemoclaw, picoclaw, zeroclaw) were in the registry
303
+ * and had capability profiles but were missing here, so `brainclaw export
304
+ * --all --write` skipped them with "Unknown export format".
305
+ */
169
306
  function formatToAgentName(format) {
170
- const map = {
171
- 'claude-md': 'claude-code',
172
- 'cursor-rules': 'cursor',
173
- 'copilot-instructions': 'github-copilot',
174
- 'agents-md': 'codex',
175
- 'gemini-md': 'antigravity',
176
- 'windsurf': 'windsurf',
177
- 'cline': 'cline',
178
- 'roo': 'roo',
179
- 'continue': 'continue',
180
- };
181
- return map[format];
307
+ for (const entry of AGENT_EXPORT_REGISTRY) {
308
+ if (entry.format === format)
309
+ return entry.agentName;
310
+ }
311
+ return undefined;
182
312
  }
183
313
  function generateExport(format, options, cwd) {
184
314
  const agentName = formatToAgentName(format);
@@ -198,6 +328,7 @@ function generateExport(format, options, cwd) {
198
328
  case 'roo': return generateRoo(options, cwd);
199
329
  case 'continue': return generateContinueRules(options, cwd);
200
330
  case 'gemini-md': return generateGeminiMd(options, cwd);
331
+ case 'board-md': return generateBoardMd(options, cwd);
201
332
  default:
202
333
  throw new Error(`Unknown export format: ${format}`);
203
334
  }
@@ -219,9 +350,36 @@ function generateAdaptiveExport(agentName, options, cwd) {
219
350
  projectName: config.project_name,
220
351
  brainclawVersion: getInstalledBrainclawVersion(),
221
352
  resolvedInstructions: instructions,
353
+ projectVision: readProjectVision(cwd),
222
354
  });
223
355
  return result.content;
224
356
  }
357
+ function buildLiveTemplateInput(target, options, cwd) {
358
+ const profile = getAgentCapabilityProfile(target.agentName);
359
+ if (!profile)
360
+ return undefined;
361
+ const config = loadConfig(cwd);
362
+ return {
363
+ profile,
364
+ state: loadState(cwd),
365
+ projectName: config.project_name,
366
+ brainclawVersion: getInstalledBrainclawVersion(),
367
+ resolvedInstructions: getInstructionText(options, cwd),
368
+ projectVision: readProjectVision(cwd),
369
+ activeClaims: listClaims(cwd).filter((claim) => claim.status === 'active'),
370
+ pendingCandidates: listCandidates('pending', cwd),
371
+ };
372
+ }
373
+ function writeLiveCompanionForTarget(target, options, cwd) {
374
+ const input = buildLiveTemplateInput(target, options, cwd);
375
+ if (!input)
376
+ return undefined;
377
+ const live = renderLiveSection(input);
378
+ if (!live)
379
+ return undefined;
380
+ const result = writeLiveCompanionFile(live.content, target.agentName, target.relativePath, cwd);
381
+ return { relativePath: result.relativePath, created: result.created, updated: result.updated };
382
+ }
225
383
  function getInstructionText(options, cwd) {
226
384
  try {
227
385
  const all = loadInstructions(cwd);
@@ -460,4 +618,104 @@ function generateGeminiMd(options, cwd) {
460
618
  lines.push(buildHygieneSection());
461
619
  return lines.join('\n');
462
620
  }
621
+ function generateBoardMd(_options, cwd) {
622
+ const config = loadConfig(cwd);
623
+ const board = buildCoordinationSnapshot({ cwd });
624
+ const state = loadState(cwd);
625
+ const lines = [
626
+ `# BOARD.md — ${config.project_name}`,
627
+ '',
628
+ `> Live agent board. Generated by brainclaw. Regenerate: \`brainclaw export --format board-md --write\``,
629
+ `> Last updated: ${new Date().toISOString()}`,
630
+ '',
631
+ ];
632
+ // Active plans
633
+ if (board.active_plans.length > 0) {
634
+ lines.push(`## Active Plans (${board.active_plans.length})\n`);
635
+ for (const plan of board.active_plans.slice(0, 20)) {
636
+ const tags = plan.tags?.length ? ` [${plan.tags.join(', ')}]` : '';
637
+ const priority = plan.priority ? ` (${plan.priority})` : '';
638
+ const assignee = plan.assignee ? ` @${plan.assignee}` : '';
639
+ const claims = plan.claims?.length ? ` — claimed by: ${plan.claims.map((c) => c.agent ?? 'unknown').join(', ')}` : '';
640
+ lines.push(`- **[${plan.id}]** [${plan.status}]${priority}${assignee} ${plan.text}${tags}${claims}`);
641
+ }
642
+ lines.push('');
643
+ }
644
+ else {
645
+ lines.push('## Active Plans\n\n_No active plans._\n');
646
+ }
647
+ // Active claims
648
+ if (board.active_claims.length > 0) {
649
+ lines.push(`## Active Claims (${board.active_claims.length})\n`);
650
+ for (const claim of board.active_claims.slice(0, 20)) {
651
+ lines.push(`- **[${claim.id}]** ${claim.agent ?? 'unknown'} → \`${claim.scope}\` — ${claim.description ?? ''}`);
652
+ }
653
+ lines.push('');
654
+ }
655
+ // Other agents activity
656
+ if (board.other_agents && board.other_agents.length > 0) {
657
+ lines.push(`## Other Agents Active\n`);
658
+ for (const agent of board.other_agents) {
659
+ const scopes = agent.scopes.length > 0 ? ` (scopes: ${agent.scopes.join(', ')})` : '';
660
+ lines.push(`- **${agent.name}** — ${agent.claim_count} claim(s)${scopes}`);
661
+ }
662
+ lines.push('');
663
+ }
664
+ // Open handoffs
665
+ if (board.open_handoffs.length > 0) {
666
+ lines.push(`## Open Handoffs (${board.open_handoffs.length})\n`);
667
+ for (const handoff of board.open_handoffs.slice(0, 10)) {
668
+ lines.push(`- **[${handoff.id}]** ${handoff.from ?? '?'} → ${handoff.to ?? '?'}: ${handoff.text ?? ''}`);
669
+ }
670
+ lines.push('');
671
+ }
672
+ // Recent decisions
673
+ const decisions = state.recent_decisions.slice(0, 10);
674
+ if (decisions.length > 0) {
675
+ lines.push(`## Recent Decisions (${decisions.length})\n`);
676
+ for (const d of decisions) {
677
+ const tags = d.tags?.length ? ` [${d.tags.join(', ')}]` : '';
678
+ lines.push(`- **[${d.id}]** ${d.text}${tags}`);
679
+ }
680
+ lines.push('');
681
+ }
682
+ // Known traps
683
+ const traps = state.known_traps.filter(t => t.visibility === 'shared' && t.status === 'active');
684
+ if (traps.length > 0) {
685
+ lines.push(`## Known Traps (${traps.length})\n`);
686
+ for (const t of traps.slice(0, 10)) {
687
+ lines.push(`- **[${t.id}]** [${t.severity}] ${t.text}`);
688
+ }
689
+ lines.push('');
690
+ }
691
+ // Active constraints
692
+ const constraints = state.active_constraints.filter(c => c.status === 'active');
693
+ if (constraints.length > 0) {
694
+ lines.push(`## Active Constraints (${constraints.length})\n`);
695
+ for (const c of constraints.slice(0, 10)) {
696
+ lines.push(`- **[${c.id}]** ${c.text}`);
697
+ }
698
+ lines.push('');
699
+ }
700
+ // Instructions
701
+ if (board.resolved_instructions.length > 0) {
702
+ lines.push(`## Active Instructions (${board.resolved_instructions.length})\n`);
703
+ for (const inst of board.resolved_instructions.slice(0, 10)) {
704
+ lines.push(`- **[${inst.id}]** ${inst.text}`);
705
+ }
706
+ lines.push('');
707
+ }
708
+ // Linked projects
709
+ if (board.linked_projects && board.linked_projects.length > 0) {
710
+ lines.push(`## Linked Projects (${board.linked_projects.length})\n`);
711
+ for (const lp of board.linked_projects) {
712
+ const status = lp.available ? 'available' : 'unavailable';
713
+ const agents = lp.agents.length > 0 ? ` — agents: ${lp.agents.join(', ')}` : '';
714
+ lines.push(`- **${lp.name}** (${lp.role}, ${status}) — ${lp.active_plans} plans, ${lp.active_claims} claims${agents}`);
715
+ }
716
+ lines.push('');
717
+ }
718
+ lines.push(buildHygieneSection());
719
+ return lines.join('\n');
720
+ }
463
721
  //# sourceMappingURL=export.js.map
@@ -39,6 +39,7 @@ export function runHandoff(text, options) {
39
39
  diff = "Could not capture git diff.";
40
40
  }
41
41
  }
42
+ const contract = buildContract(options, diff);
42
43
  const entry = {
43
44
  id,
44
45
  short_label,
@@ -52,10 +53,42 @@ export function runHandoff(text, options) {
52
53
  plan_id: options.plan,
53
54
  tags: options.tag ?? [],
54
55
  related_paths: options.path,
56
+ contract,
55
57
  snapshot: diff ? { diff } : undefined,
56
58
  };
57
59
  state.open_handoffs.push(entry);
58
60
  persistState(state, cwd);
59
61
  console.log(`✔ Handoff added: [${id}] ${options.from} → ${options.to}: ${text}`);
60
62
  }
63
+ export function extractFilesFromDiff(diff) {
64
+ const files = new Set();
65
+ for (const line of diff.split('\n')) {
66
+ // Match "diff --git a/path b/path" or "+++ b/path" or "--- a/path"
67
+ const gitDiffMatch = line.match(/^diff --git a\/(.+?) b\//);
68
+ if (gitDiffMatch) {
69
+ files.add(gitDiffMatch[1]);
70
+ continue;
71
+ }
72
+ const plusMatch = line.match(/^\+\+\+ b\/(.+)/);
73
+ if (plusMatch && plusMatch[1] !== '/dev/null') {
74
+ files.add(plusMatch[1]);
75
+ }
76
+ }
77
+ return [...files].sort();
78
+ }
79
+ function buildContract(options, diff) {
80
+ const hasExplicit = options.files?.length || options.preCondition?.length ||
81
+ options.postCondition?.length || options.test?.length || options.linkedPlan?.length;
82
+ const filesFromDiff = diff ? extractFilesFromDiff(diff) : [];
83
+ const allFiles = [...new Set([...(options.files ?? []), ...filesFromDiff])].sort();
84
+ if (!hasExplicit && allFiles.length === 0)
85
+ return undefined;
86
+ return {
87
+ files_touched: allFiles.length > 0 ? allFiles : undefined,
88
+ pre_conditions: options.preCondition?.length ? options.preCondition : undefined,
89
+ post_conditions: options.postCondition?.length ? options.postCondition : undefined,
90
+ tests_to_verify: options.test?.length ? options.test : undefined,
91
+ linked_plans: options.linkedPlan?.length ? options.linkedPlan : undefined,
92
+ };
93
+ }
61
94
  //# sourceMappingURL=handoff.js.map
@@ -0,0 +1,189 @@
1
+ /**
2
+ * CLI command: brainclaw harvest-candidates
3
+ *
4
+ * Scans worktree inboxes for candidates written by agents operating in
5
+ * --sandbox workspace-write mode (e.g. Codex), and copies them into the
6
+ * main project store. This is the coordinator-side of the codex-sandbox
7
+ * bridge: agents write candidates to their worktree's
8
+ * `.brainclaw/coordination/inbox/cnd_*.json` and the coordinator harvests
9
+ * them via this command.
10
+ *
11
+ * @module
12
+ */
13
+ import fs from 'node:fs';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import crypto from 'node:crypto';
17
+ import { CandidateSchema } from '../core/schema.js';
18
+ import { listCandidates, listArchivedCandidates, saveCandidate } from '../core/candidates.js';
19
+ import { createRuntimeEvent } from '../core/events.js';
20
+ import { memoryExists } from '../core/io.js';
21
+ /**
22
+ * Returns the base directory where brainclaw-managed worktrees are stored
23
+ * for the given project root: `~/.brainclaw/worktrees/<sha1-hash>/`.
24
+ */
25
+ function worktreesBaseDir(projectRoot) {
26
+ const hash = crypto.createHash('sha1').update(projectRoot).digest('hex').slice(0, 12);
27
+ return path.join(os.homedir(), '.brainclaw', 'worktrees', hash);
28
+ }
29
+ /**
30
+ * Auto-detect all worktree directories under the brainclaw-managed base dir.
31
+ * Returns subdirectories that exist on disk (may or may not have an inbox).
32
+ */
33
+ function autoDetectWorktreePaths(cwd) {
34
+ const base = worktreesBaseDir(cwd);
35
+ if (!fs.existsSync(base))
36
+ return [];
37
+ return fs.readdirSync(base, { withFileTypes: true })
38
+ .filter((entry) => entry.isDirectory())
39
+ .map((entry) => path.join(base, entry.name));
40
+ }
41
+ /**
42
+ * Collect all `cnd_*.json` files from a worktree's candidate inbox.
43
+ *
44
+ * Checks both the entity-model path (`.brainclaw/coordination/inbox/`) and
45
+ * the legacy flat path (`.brainclaw/inbox/`) for backward compatibility.
46
+ */
47
+ function collectWorktreeCandidateFiles(worktreePath) {
48
+ const dirs = [
49
+ path.join(worktreePath, '.brainclaw', 'coordination', 'inbox'),
50
+ path.join(worktreePath, '.brainclaw', 'inbox'),
51
+ ];
52
+ const files = [];
53
+ for (const dir of dirs) {
54
+ if (!fs.existsSync(dir))
55
+ continue;
56
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
57
+ if (entry.isFile() && entry.name.startsWith('cnd_') && entry.name.endsWith('.json')) {
58
+ files.push(path.join(dir, entry.name));
59
+ }
60
+ }
61
+ }
62
+ // Dedupe: same filename may appear in both paths (entity + legacy)
63
+ const seen = new Set();
64
+ return files.filter((f) => {
65
+ const key = path.basename(f);
66
+ if (seen.has(key))
67
+ return false;
68
+ seen.add(key);
69
+ return true;
70
+ });
71
+ }
72
+ /**
73
+ * Harvest candidates from worktree inboxes into the main project store.
74
+ *
75
+ * This is the coordinator-side fix for gap 5 of E2E test n°1: agents
76
+ * running under `--sandbox workspace-write` cannot reach the main store
77
+ * via MCP. They write candidates directly to their worktree inbox; the
78
+ * coordinator calls `harvestCandidates` to sync them.
79
+ *
80
+ * @returns HarvestResult with counts of harvested, skipped, and errors.
81
+ */
82
+ export function harvestCandidates(options = {}) {
83
+ const cwd = options.cwd ?? process.cwd();
84
+ const agent = options.agent ?? 'coordinator';
85
+ const result = { harvested: [], skipped: [], errors: [] };
86
+ // Resolve which worktrees to scan
87
+ const worktreePaths = (options.worktreePaths && options.worktreePaths.length > 0)
88
+ ? options.worktreePaths
89
+ : autoDetectWorktreePaths(cwd);
90
+ if (worktreePaths.length === 0) {
91
+ return result;
92
+ }
93
+ // Build a set of IDs already present in the main store across ALL archives
94
+ // (pending + accepted + rejected) to prevent re-importing archived items.
95
+ // (Codex review cnd#564: dedup was only checking pending inbox)
96
+ const existingIds = new Set([
97
+ ...listCandidates(undefined, cwd).map((c) => c.id),
98
+ ...listArchivedCandidates('accepted', cwd).map((c) => c.id),
99
+ ...listArchivedCandidates('rejected', cwd).map((c) => c.id),
100
+ ]);
101
+ for (const worktreePath of worktreePaths) {
102
+ // Wrap per-worktree scan so a disappeared worktree records an error
103
+ // instead of aborting the full harvest. (Codex review cnd#564)
104
+ let files;
105
+ try {
106
+ files = collectWorktreeCandidateFiles(worktreePath);
107
+ }
108
+ catch (err) {
109
+ result.errors.push(`Failed to scan worktree ${worktreePath}: ${err instanceof Error ? err.message : String(err)}`);
110
+ continue;
111
+ }
112
+ for (const filePath of files) {
113
+ let candidate;
114
+ try {
115
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
116
+ candidate = CandidateSchema.parse(raw);
117
+ }
118
+ catch (err) {
119
+ result.errors.push(`Failed to parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
120
+ continue;
121
+ }
122
+ if (existingIds.has(candidate.id)) {
123
+ result.skipped.push(candidate.id);
124
+ continue;
125
+ }
126
+ if (!options.dryRun) {
127
+ try {
128
+ saveCandidate(candidate, cwd);
129
+ createRuntimeEvent({
130
+ agent,
131
+ event_type: 'candidate_harvested',
132
+ text: `Harvested candidate [${candidate.id}] from worktree ${path.basename(worktreePath)}`,
133
+ tags: ['harvest', 'bridge', 'codex-sandbox'],
134
+ metadata: {
135
+ candidate_id: candidate.id,
136
+ source_worktree: worktreePath,
137
+ source_file: filePath,
138
+ },
139
+ }, cwd);
140
+ }
141
+ catch (err) {
142
+ result.errors.push(`Failed to save candidate ${candidate.id}: ${err instanceof Error ? err.message : String(err)}`);
143
+ continue;
144
+ }
145
+ }
146
+ existingIds.add(candidate.id);
147
+ result.harvested.push(candidate);
148
+ }
149
+ }
150
+ return result;
151
+ }
152
+ export function runHarvestCandidates(options = {}) {
153
+ const cwd = options.cwd ?? process.cwd();
154
+ if (!memoryExists(cwd)) {
155
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
156
+ process.exit(1);
157
+ }
158
+ const result = harvestCandidates({
159
+ worktreePaths: options.worktree,
160
+ dryRun: options.dryRun,
161
+ cwd,
162
+ });
163
+ if (options.json) {
164
+ console.log(JSON.stringify({
165
+ harvested: result.harvested.length,
166
+ skipped: result.skipped.length,
167
+ errors: result.errors,
168
+ candidates: result.harvested.map((c) => ({ id: c.id, type: c.type, text: c.text.slice(0, 80) })),
169
+ }, null, 2));
170
+ return;
171
+ }
172
+ const dryTag = options.dryRun ? ' (dry-run)' : '';
173
+ if (result.harvested.length === 0 && result.skipped.length === 0 && result.errors.length === 0) {
174
+ console.log('No worktree candidates found.');
175
+ return;
176
+ }
177
+ for (const c of result.harvested) {
178
+ const verb = options.dryRun ? ' (dry-run) Would harvest' : ' ✔ Harvested';
179
+ console.log(`${verb} [${c.id}] ${c.type}: ${c.text.slice(0, 80)}`);
180
+ }
181
+ for (const id of result.skipped) {
182
+ console.log(` ⟳ Skipped (already exists): ${id}`);
183
+ }
184
+ for (const err of result.errors) {
185
+ console.error(` ✗ ${err}`);
186
+ }
187
+ console.log(`\n✔ Harvest complete${dryTag}: ${result.harvested.length} imported, ${result.skipped.length} skipped, ${result.errors.length} error(s).`);
188
+ }
189
+ //# sourceMappingURL=harvest.js.map