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.
- package/README.md +193 -170
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +683 -23
- package/dist/commands/accept.js +3 -0
- package/dist/commands/add-step.js +11 -26
- package/dist/commands/agent-board.js +70 -3
- package/dist/commands/audit.js +19 -0
- package/dist/commands/check-policy.js +54 -0
- package/dist/commands/check-security-mcp.js +145 -0
- package/dist/commands/check-security.js +106 -0
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/codev.js +672 -0
- package/dist/commands/compact.js +74 -0
- package/dist/commands/complete-step.js +16 -26
- package/dist/commands/constraint.js +8 -20
- package/dist/commands/decision.js +9 -20
- package/dist/commands/delete-plan.js +10 -12
- package/dist/commands/delete-step.js +16 -0
- package/dist/commands/dispatch.js +163 -0
- package/dist/commands/doctor.js +1122 -49
- package/dist/commands/enable-agent.js +1 -0
- package/dist/commands/export.js +280 -22
- package/dist/commands/handoff.js +33 -0
- package/dist/commands/harvest.js +189 -0
- package/dist/commands/hooks.js +82 -25
- package/dist/commands/inbox.js +169 -0
- package/dist/commands/init.js +38 -31
- package/dist/commands/install-hooks.js +71 -44
- package/dist/commands/link.js +89 -0
- package/dist/commands/list-claims.js +48 -3
- package/dist/commands/list-plans.js +129 -25
- package/dist/commands/loops-handlers.js +409 -0
- package/dist/commands/mcp-read-handlers.js +1628 -0
- package/dist/commands/mcp-schemas.generated.js +74 -0
- package/dist/commands/mcp.js +4244 -1475
- package/dist/commands/plan-resource.js +64 -0
- package/dist/commands/plan.js +12 -26
- package/dist/commands/prune.js +37 -2
- package/dist/commands/reflect.js +20 -7
- package/dist/commands/release-claim.js +11 -6
- package/dist/commands/release-notes.js +170 -0
- package/dist/commands/repair.js +210 -0
- package/dist/commands/run-profile.js +57 -0
- package/dist/commands/sequence.js +113 -0
- package/dist/commands/session-end.js +423 -14
- package/dist/commands/session-start.js +214 -41
- package/dist/commands/setup-security.js +103 -0
- package/dist/commands/setup.js +42 -4
- package/dist/commands/stale.js +109 -0
- package/dist/commands/switch.js +131 -10
- package/dist/commands/trap.js +14 -31
- package/dist/commands/update-handoff.js +63 -4
- package/dist/commands/update-plan.js +21 -28
- package/dist/commands/update-step.js +37 -0
- package/dist/commands/upgrade.js +313 -6
- package/dist/commands/usage.js +102 -0
- package/dist/commands/version.js +20 -0
- package/dist/commands/who.js +124 -0
- package/dist/commands/worktree.js +105 -0
- package/dist/core/actions.js +315 -0
- package/dist/core/agent-capability.js +610 -17
- package/dist/core/agent-context.js +7 -1
- package/dist/core/agent-files.js +1169 -85
- package/dist/core/agent-integrations.js +160 -5
- package/dist/core/agent-inventory.js +2 -0
- package/dist/core/agent-profiles.js +93 -0
- package/dist/core/agent-registry.js +162 -30
- package/dist/core/agentrun-reconciler.js +345 -0
- package/dist/core/agentruns.js +424 -0
- package/dist/core/ai-agent-detection.js +31 -10
- package/dist/core/archival.js +77 -0
- package/dist/core/assignment-sweeper.js +82 -0
- package/dist/core/assignments.js +367 -0
- package/dist/core/audit.js +30 -0
- package/dist/core/bootstrap.js +61 -10
- package/dist/core/brainclaw-version.js +94 -2
- package/dist/core/candidates.js +93 -2
- package/dist/core/claims.js +419 -0
- package/dist/core/codev-metrics.js +77 -0
- package/dist/core/codev-personas.js +31 -0
- package/dist/core/codev-plan-gen.js +35 -0
- package/dist/core/codev-prompts.js +74 -0
- package/dist/core/codev-responses.js +62 -0
- package/dist/core/codev-rounds.js +218 -0
- package/dist/core/config.js +4 -0
- package/dist/core/context.js +454 -34
- package/dist/core/coordination.js +201 -6
- package/dist/core/cross-project.js +230 -16
- package/dist/core/default-profiles/doctor.yaml +11 -0
- package/dist/core/default-profiles/janitor.yaml +11 -0
- package/dist/core/default-profiles/onboarder.yaml +11 -0
- package/dist/core/default-profiles/reviewer.yaml +13 -0
- package/dist/core/dispatcher.js +1189 -0
- package/dist/core/duplicates.js +2 -2
- package/dist/core/entity-operations.js +450 -0
- package/dist/core/entity-registry.js +344 -0
- package/dist/core/event-log.js +1 -0
- package/dist/core/events.js +106 -2
- package/dist/core/execution-adapters.js +154 -0
- package/dist/core/execution-context.js +63 -0
- package/dist/core/execution-profile.js +270 -0
- package/dist/core/execution.js +255 -0
- package/dist/core/facade-schema.js +81 -0
- package/dist/core/federation-cloud.js +99 -0
- package/dist/core/federation-message.js +52 -0
- package/dist/core/federation-transport.js +65 -0
- package/dist/core/gc-semantic.js +482 -0
- package/dist/core/governance.js +247 -0
- package/dist/core/guards.js +19 -0
- package/dist/core/ideation.js +72 -0
- package/dist/core/identity.js +252 -28
- package/dist/core/ids.js +6 -0
- package/dist/core/input-validation.js +2 -2
- package/dist/core/instruction-templates.js +344 -136
- package/dist/core/io.js +90 -11
- package/dist/core/lock.js +6 -2
- package/dist/core/loops/brief-assembly.js +213 -0
- package/dist/core/loops/facade-schema.js +148 -0
- package/dist/core/loops/index.js +7 -0
- package/dist/core/loops/iteration-engine.js +139 -0
- package/dist/core/loops/lock.js +385 -0
- package/dist/core/loops/store.js +201 -0
- package/dist/core/loops/types.js +403 -0
- package/dist/core/loops/verbs.js +534 -0
- package/dist/core/markdown.js +15 -3
- package/dist/core/memory-compactor.js +432 -0
- package/dist/core/memory-git.js +152 -8
- package/dist/core/messaging.js +278 -0
- package/dist/core/migration.js +32 -1
- package/dist/core/mutation-pipeline.js +4 -2
- package/dist/core/operations/memory-mutation.js +129 -0
- package/dist/core/operations/memory-write.js +78 -0
- package/dist/core/operations/plan.js +190 -0
- package/dist/core/policy.js +169 -0
- package/dist/core/repo-analysis.js +67 -0
- package/dist/core/reputation.js +9 -3
- package/dist/core/schema.js +546 -21
- package/dist/core/search.js +21 -2
- package/dist/core/security-cache.js +71 -0
- package/dist/core/security-guard.js +152 -0
- package/dist/core/security-scoring.js +86 -0
- package/dist/core/sequence.js +130 -0
- package/dist/core/socket-client.js +113 -0
- package/dist/core/staleness.js +246 -0
- package/dist/core/state.js +98 -22
- package/dist/core/store-resolution.js +54 -12
- package/dist/core/toml-writer.js +76 -0
- package/dist/core/upgrades/backup.js +232 -0
- package/dist/core/upgrades/health-check.js +169 -0
- package/dist/core/upgrades/patches/candidate-archive.js +145 -0
- package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
- package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
- package/dist/core/upgrades/schema-version.js +97 -0
- package/dist/core/worktree.js +606 -0
- package/dist/facts.js +114 -0
- package/dist/facts.json +111 -0
- package/docs/architecture/project-refs.md +5 -1
- package/docs/cli.md +690 -43
- package/docs/concepts/ideation-loop.md +317 -0
- package/docs/concepts/loop-engine.md +456 -0
- package/docs/concepts/mcp-governance.md +268 -0
- package/docs/concepts/memory-staleness.md +122 -0
- package/docs/concepts/multi-agent-workflows.md +166 -0
- package/docs/concepts/plans-and-claims.md +31 -6
- package/docs/concepts/project-md-convention.md +35 -0
- package/docs/concepts/troubleshooting.md +220 -0
- package/docs/concepts/upgrade-cli.md +202 -0
- package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
- package/docs/context-format-changelog.md +2 -2
- package/docs/context-format.md +2 -2
- package/docs/index.md +68 -0
- package/docs/integrations/agents.md +15 -16
- package/docs/integrations/cline.md +88 -0
- package/docs/integrations/codex.md +75 -23
- package/docs/integrations/continue.md +60 -0
- package/docs/integrations/copilot.md +67 -9
- package/docs/integrations/kilocode.md +72 -0
- package/docs/integrations/mcp.md +304 -21
- package/docs/integrations/mistral-vibe.md +122 -0
- package/docs/integrations/opencode.md +84 -0
- package/docs/integrations/overview.md +23 -8
- package/docs/integrations/roo.md +74 -0
- package/docs/integrations/windsurf.md +83 -0
- package/docs/mcp-schema-changelog.md +191 -1
- package/docs/playbooks/integration/index.md +121 -0
- package/docs/playbooks/productivity/index.md +102 -0
- package/docs/playbooks/team/index.md +122 -0
- package/docs/product/agent-first-model.md +184 -0
- package/docs/product/entity-model-audit.md +462 -0
- package/docs/quickstart-existing-project.md +135 -0
- package/docs/quickstart.md +124 -37
- package/docs/release-maintenance.md +79 -0
- package/docs/review.md +2 -0
- package/docs/server-operations.md +118 -0
- package/package.json +20 -12
- package/dist/commands/claude-desktop-extension.js +0 -18
- package/dist/commands/diff.js +0 -99
- 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,
|
package/dist/commands/export.js
CHANGED
|
@@ -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 (
|
|
45
|
-
|
|
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 (
|
|
81
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
package/dist/commands/handoff.js
CHANGED
|
@@ -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
|