brainclaw 1.14.0 → 1.16.0

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 (63) hide show
  1. package/README.md +16 -263
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli/register-capture.js +209 -0
  4. package/dist/cli/register-code-map.js +19 -0
  5. package/dist/cli/register-coordination.js +472 -0
  6. package/dist/cli/register-federation.js +258 -0
  7. package/dist/cli/register-lifecycle.js +436 -0
  8. package/dist/cli/register-memory-context.js +502 -0
  9. package/dist/cli/register-planning.js +167 -0
  10. package/dist/cli/register-review.js +149 -0
  11. package/dist/cli/shared.js +5 -0
  12. package/dist/cli.js +212 -2015
  13. package/dist/commands/dispatch-watch.js +25 -2
  14. package/dist/commands/harvest.js +31 -6
  15. package/dist/commands/mcp-catalog.js +1438 -0
  16. package/dist/commands/mcp-contract.js +33 -0
  17. package/dist/commands/mcp-presentation.js +27 -0
  18. package/dist/commands/mcp-read-handlers.js +72 -36
  19. package/dist/commands/mcp-write-admin.js +328 -0
  20. package/dist/commands/mcp-write-claims.js +864 -0
  21. package/dist/commands/mcp-write-coordination.js +1825 -0
  22. package/dist/commands/mcp-write-entities.js +620 -0
  23. package/dist/commands/mcp-write-memory.js +451 -0
  24. package/dist/commands/mcp-write-sequences.js +116 -0
  25. package/dist/commands/mcp-write-support.js +367 -0
  26. package/dist/commands/mcp.js +261 -5570
  27. package/dist/commands/update-handoff.js +28 -42
  28. package/dist/core/agent-capability.js +31 -14
  29. package/dist/core/agent-files.js +1 -1
  30. package/dist/core/agent-registry.js +51 -3
  31. package/dist/core/claims.js +18 -0
  32. package/dist/core/coordination.js +5 -2
  33. package/dist/core/cross-project.js +35 -1
  34. package/dist/core/dispatcher.js +34 -20
  35. package/dist/core/entity-operations.js +335 -12
  36. package/dist/core/entity-registry.js +72 -9
  37. package/dist/core/execution.js +28 -4
  38. package/dist/core/facade-schema.js +30 -4
  39. package/dist/core/federation-cloud.js +142 -11
  40. package/dist/core/federation-outbox.js +292 -0
  41. package/dist/core/federation-signing.js +115 -0
  42. package/dist/core/handoff-review.js +35 -0
  43. package/dist/core/io.js +6 -0
  44. package/dist/core/protocol-tool-policy.js +113 -0
  45. package/dist/core/review-loop-close.js +115 -0
  46. package/dist/core/schema.js +25 -2
  47. package/dist/core/security-detectors.js +35 -6
  48. package/dist/core/security.js +32 -12
  49. package/dist/core/worktree.js +98 -9
  50. package/dist/facts.js +13 -11
  51. package/dist/facts.json +12 -10
  52. package/docs/PROTOCOL.md +7 -3
  53. package/docs/concepts/coordinator-runbook.md +3 -0
  54. package/docs/concepts/dispatch-lifecycle.md +4 -4
  55. package/docs/concepts/loop-engine.md +3 -1
  56. package/docs/concepts/troubleshooting.md +1 -1
  57. package/docs/integrations/codex.md +3 -3
  58. package/docs/integrations/overview.md +1 -1
  59. package/docs/mcp-schema-changelog.md +153 -2
  60. package/docs/playbooks/orchestration.md +1 -1
  61. package/docs/product/entity-model-audit.md +3 -2
  62. package/docs/security.md +22 -1
  63. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -1,120 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { Command } from 'commander';
4
- import { runInit } from './commands/init.js';
5
- import { runSetup, runSetupMachine } from './commands/setup.js';
6
- import { runUpgrade } from './commands/upgrade.js';
7
- import { patchAllMcpConfigs } from './core/agent-files.js';
8
- import { runReconcile } from './commands/reconcile.js';
9
- import { getMemoryLog, rollbackMemory, hasMemoryRepo } from './core/memory-git.js';
10
- import { buildMachineProfile, saveMachineProfile, loadMachineProfile, renderMachineProfileSummary } from './core/machine-profile.js';
11
- import { buildAgentInventory, saveAgentInventory, loadAgentInventory, renderAgentInventorySummary } from './core/agent-inventory.js';
12
- import { scanAndRegister, scanProject, upsertProject, loadGlobalRegistry, renderGlobalRegistrySummary } from './core/global-registry.js';
13
- import { runStatus } from './commands/status.js';
14
- import { runDecision } from './commands/decision.js';
15
- import { runConstraint } from './commands/constraint.js';
16
- import { runTrap } from './commands/trap.js';
17
- import { runHandoff } from './commands/handoff.js';
18
- import { runListPlans } from './commands/list-plans.js';
19
- import { runUpdatePlan } from './commands/update-plan.js';
20
- import { runDeletePlan } from './commands/delete-plan.js';
21
- import { runPlanResource } from './commands/plan-resource.js';
22
- import { runCodeMap } from './commands/code-map.js';
23
- import { runMove } from './commands/move.js';
24
- import { runSequenceResource } from './commands/sequence.js';
25
- import { runAddStep } from './commands/add-step.js';
26
- import { runDeleteStep } from './commands/delete-step.js';
27
- import { runUpdateStep } from './commands/update-step.js';
28
- import { runEstimationReport } from './commands/estimation-report.js';
29
- import { runCompleteStep } from './commands/complete-step.js';
30
- import { runUpdateHandoff } from './commands/update-handoff.js';
31
- import { runInstruction } from './commands/instruction.js';
32
- import { runListAgents } from './commands/list-agents.js';
33
- import { runSurfaceTaskResource } from './commands/surface-task-resource.js';
34
- import { runListInstructions } from './commands/list-instructions.js';
35
- import { runDoctor, runDoctorSpawnCheck } from './commands/doctor.js';
36
- import { runRepair } from './commands/repair.js';
37
- import { runStale } from './commands/stale.js';
38
- import { runRebuild } from './commands/rebuild.js';
39
- import { runReflect } from './commands/reflect.js';
40
- import { runReflectRuntimeNote } from './commands/reflect-runtime-note.js';
41
- import { runReview } from './commands/review.js';
42
- import { runShowCandidate } from './commands/show-candidate.js';
43
- import { runStarCandidate } from './commands/star-candidate.js';
44
- import { runUseCandidate } from './commands/use-candidate.js';
45
- import { runAccept } from './commands/accept.js';
46
- import { runReject } from './commands/reject.js';
47
- import { runPruneCandidates } from './commands/prune-candidates.js';
48
- import { cleanupStaleCandidates } from './core/candidates.js';
49
- import { runListClaims } from './commands/list-claims.js';
50
- import { runReleaseClaim } from './commands/release-claim.js';
51
- import { runClaimResource } from './commands/claim-resource.js';
52
- import { runAssignmentResource } from './commands/assignment-resource.js';
53
- import { runMemoryCommand } from './commands/memory.js';
54
- import { runReleaseClaims } from './commands/release-claims.js';
55
- import { runAgentBoard } from './commands/agent-board.js';
56
- import { runRuntimeNote } from './commands/runtime-note.js';
57
- import { runRuntimeStatus } from './commands/runtime-status.js';
58
- import { runSync } from './commands/sync.js';
59
- import { runContext } from './commands/context.js';
60
- import { runBootstrap } from './commands/bootstrap.js';
61
- import { runEnv } from './commands/env.js';
62
- import { runAdapterOpenclawImport } from './commands/adapter-openclaw-import.js';
63
- import { runInstallHooks } from './commands/install-hooks.js';
64
- import { runCheckConstraints } from './commands/check-constraints.js';
65
- import { runCheckPolicy } from './commands/check-policy.js';
66
- import { runCheckSecurity } from './commands/check-security.js';
67
- import { runSetupSecurity } from './commands/setup-security.js';
68
- import { runRegisterAgent, runRemoveAgent } from './commands/register-agent.js';
69
- import { runEnableAgent } from './commands/enable-agent.js';
70
- import { runVersion } from './commands/version.js';
71
- import { runReleaseNotes } from './commands/release-notes.js';
72
- import { runDiff } from './commands/changes.js';
73
- import { runPrune } from './commands/prune.js';
74
- import { runMcp } from './commands/mcp.js';
75
- import { runSetTrust } from './commands/set-trust.js';
76
- import { runSessionStart } from './commands/session-start.js';
77
- import { runSessionEnd } from './commands/session-end.js';
78
- import { runWhoami } from './commands/whoami.js';
79
- import { runUsage } from './commands/usage.js';
80
- import { runSearch } from './commands/search.js';
81
- import { runExport, runRefresh } from './commands/export.js';
82
- import { runHooks } from './commands/hooks.js';
83
- import { runWatch } from './commands/watch.js';
84
- import { runDispatchAnalysis, runDispatch, runDispatchReview } from './commands/dispatch.js';
85
- import { runDispatchWatch } from './commands/dispatch-watch.js';
86
- import { runInboxList, runInboxAck, runInboxArchive, runInboxSend, runInboxThread } from './commands/inbox.js';
87
- import { runMetrics } from './commands/metrics.js';
88
- import { runRollback } from './commands/rollback.js';
89
- import { runPull } from './commands/pull.js';
90
- import { runPush } from './commands/push.js';
91
- import { runAuditCommand } from './commands/audit.js';
92
- import { runHistory } from './commands/history.js';
93
- import { runContextDiff } from './commands/context-diff.js';
94
- import { runCapability } from './commands/capability.js';
95
- import { runLink } from './commands/link.js';
96
- import { runTool } from './commands/tool.js';
97
- import { runExplore } from './commands/explore.js';
98
5
  import { getInstalledBrainclawVersion } from './core/brainclaw-version.js';
99
- import { cleanOrphanFiles, memoryDir, memoryExists } from './core/io.js';
6
+ import { cleanOrphanFiles, memoryDir } from './core/io.js';
100
7
  import { initLogLevel, logger } from './core/logger.js';
101
8
  import { resolveEffectiveCwd } from './core/store-resolution.js';
102
9
  import { resolveProjectCwd } from './core/cross-project.js';
103
- import { runSwitch } from './commands/switch.js';
104
- import { runWorktreeCreate, runWorktreeList, runWorktreeRemove, runWorktreePrune, runWorktreeClean, runWorktreeMerge, runWorktreeCheck } from './commands/worktree.js';
105
- import { runCheckEvents } from './commands/check-events.js';
106
- import { runDiscover } from './commands/discover.js';
107
- import { runMigrate } from './commands/migrate.js';
108
- import { runRunProfile } from './commands/run-profile.js';
109
- import { runCompact } from './commands/compact.js';
110
- import { runHarvestCandidates, runHarvestLane } from './commands/harvest.js';
111
- import { runQuestionsCommand } from './commands/questions.js';
112
- import { runReplyCommand } from './commands/reply.js';
113
- import { requireRegisteredAgentIdentity } from './core/agent-registry.js';
10
+ import { registerLifecycleCommands } from './cli/register-lifecycle.js';
11
+ import { registerCaptureCommands } from './cli/register-capture.js';
12
+ import { registerPlanningCommands } from './cli/register-planning.js';
13
+ import { registerCoordinationCommands } from './cli/register-coordination.js';
14
+ import { registerReviewCommands } from './cli/register-review.js';
15
+ import { registerMemoryContextCommands } from './cli/register-memory-context.js';
16
+ import { registerFederationCommands } from './cli/register-federation.js';
17
+ import { registerCodeMapCommands } from './cli/register-code-map.js';
114
18
  const program = new Command();
115
- function collect(value, previous) {
116
- return [...previous, value];
117
- }
118
19
  function parseLeadingGlobalOptions(argv) {
119
20
  const result = {};
120
21
  for (let i = 0; i < argv.length; i++) {
@@ -207,9 +108,6 @@ function findCommandFromArgv(argv) {
207
108
  }
208
109
  return found;
209
110
  }
210
- function isCodevEnabled() {
211
- return process.env.BRAINCLAW_ENABLE_CODEV === '1';
212
- }
213
111
  program
214
112
  .name('brainclaw')
215
113
  // Stop the program-level parser from consuming options that appear AFTER the
@@ -267,1914 +165,213 @@ program
267
165
  process.chdir(path.resolve(explicitCwd));
268
166
  }
269
167
  });
270
- // --- init ---
271
- program
272
- .command('init')
273
- .description('Initialize or refresh project memory in .brainclaw/ storage directory')
274
- .option('-y, --yes', 'Skip interactive wizard and use defaults')
275
- .option('--force', 'Rebuild managed Brainclaw config and generated files from defaults')
276
- .option('--compact', 'Enable compact markdown mode')
277
- .option('--topology <mode>', 'Topology mode: embedded, sidecar, local-only')
278
- .option('--project-mode <mode>', 'Project mode: single-project, multi-project, auto')
279
- .option('--project-strategy <strategy>', 'Project strategy for multi-project mode: manual, folder')
280
- .option('--no-analyze-repo', 'Skip repository analysis when suggesting a project mode')
281
- .option('--no-ai-scan', 'Skip AI surface scan during init')
282
- .option('--scan', 'Scan subdirectories for service boundaries and suggest init targets')
283
- .option('--cwd <path>', 'Override working directory for init scaffolding (parity with other CLI commands)')
284
- .action(async (options) => {
285
- // pln#515 step 1: commander binds --cwd to the program-level option even
286
- // when it appears after `init`, so resolve via program.opts() and feed
287
- // runInit's existing options.cwd plumb.
288
- const programCwd = program.opts().cwd;
289
- await runInit({ ...options, cwd: options.cwd ?? programCwd });
290
- });
291
- // --- setup ---
292
- program
293
- .command('setup')
294
- .description('Interactive onboarding wizard — machine bootstrap plus multi-repo init')
295
- .option('--roots <paths>', 'Comma-separated root directories to scan (skips interactive prompt)')
296
- .option('--agents <agents>', 'Agents to configure: all, detected, or comma-separated names')
297
- .option('--repos <mode>', 'Repo selection: all, current, or comma-separated numbers')
298
- .option('-y, --yes', 'Accept all defaults non-interactively')
299
- .action(async (options) => {
300
- await runSetup(options);
301
- });
302
- // --- setup-machine ---
303
- program
304
- .command('setup-machine')
305
- .description('Machine-only onboarding — detect/configure agents and MCP without scanning or initializing repositories')
306
- .option('--agents <agents>', 'Agents to configure: all, detected, or comma-separated names')
307
- .option('-y, --yes', 'Accept all defaults non-interactively')
308
- .action(async (options) => {
309
- await runSetupMachine(options);
310
- });
311
- // --- memory-log ---
312
- program
313
- .command('memory-log')
314
- .description('Show recent memory change history (from internal git repo)')
315
- .option('-n, --limit <count>', 'Number of entries to show', '20')
316
- .action((options) => {
317
- const entries = getMemoryLog(parseInt(options.limit, 10));
318
- if (entries.length === 0) {
319
- console.log('No memory history available. Run `brainclaw init --force` to enable memory versioning.');
320
- return;
321
- }
322
- console.log(`Memory history (${entries.length} entries):\n`);
323
- for (const entry of entries) {
324
- console.log(` ${entry}`);
325
- }
326
- });
327
- // --- memory-rollback ---
328
- program
329
- .command('memory-rollback <ref>')
330
- .description('Restore live project memory from a previous git snapshot without deleting audit or archive artifacts')
331
- .option('--actor <name>', 'Registered human identity required to authorize the rollback')
332
- .action((ref, options) => {
333
- const cwd = process.cwd();
334
- if (!hasMemoryRepo()) {
335
- console.error('Error: no memory git repo. Run `brainclaw init --force` to enable.');
336
- process.exit(1);
337
- }
338
- let actor;
339
- try {
340
- actor = requireRegisteredAgentIdentity({
341
- agentName: options.actor,
342
- allowCurrent: true,
343
- allowEnv: true,
344
- cwd,
168
+ registerLifecycleCommands(program);
169
+ registerCaptureCommands(program);
170
+ registerPlanningCommands(program);
171
+ registerCoordinationCommands(program);
172
+ registerReviewCommands(program);
173
+ registerMemoryContextCommands(program);
174
+ registerFederationCommands(program);
175
+ registerCodeMapCommands(program);
176
+ // ─── Command-order shim (pln#622 PR5) ────────────────────────────────────────
177
+ // Commander renders `--help` in registration order. The per-family register
178
+ // modules above would otherwise reorder the help output relative to the
179
+ // pre-split monolithic cli.ts — a visible surface change for a pure refactor.
180
+ // This manifest freezes the pre-split order (git show of the monolith); the
181
+ // stable sort below restores it on the live registry. Any NEW registration not
182
+ // listed here is appended at the end in its relative registration order —
183
+ // changing that (or editing this manifest) is an explicit product decision,
184
+ // not a refactor side effect.
185
+ const ORIGINAL_COMMAND_ORDER = [
186
+ 'init',
187
+ 'setup',
188
+ 'setup-machine',
189
+ 'memory-log',
190
+ 'memory-rollback',
191
+ 'upgrade',
192
+ 'patch-configs',
193
+ 'machine-profile',
194
+ 'agent-inventory',
195
+ 'projects',
196
+ 'decision',
197
+ 'constraint',
198
+ 'trap',
199
+ 'handoff',
200
+ 'status',
201
+ 'plan',
202
+ 'code-map',
203
+ 'move',
204
+ 'list-plans',
205
+ 'sequence',
206
+ 'add-step',
207
+ 'complete-step',
208
+ 'update-step',
209
+ 'delete-step',
210
+ 'estimation-report',
211
+ 'update-plan',
212
+ 'surface-task',
213
+ 'delete-plan',
214
+ 'update-handoff',
215
+ 'doctor',
216
+ 'repair',
217
+ 'stale',
218
+ 'version',
219
+ 'release-notes',
220
+ 'uninstall',
221
+ 'rebuild',
222
+ 'reflect',
223
+ 'reflect-runtime-note',
224
+ 'context',
225
+ 'bootstrap',
226
+ 'env',
227
+ 'memory',
228
+ 'instruction',
229
+ 'list-instructions',
230
+ 'register-agent',
231
+ 'enable-agent',
232
+ 'list-agents',
233
+ 'review',
234
+ 'show-candidate',
235
+ 'star-candidate',
236
+ 'use-candidate',
237
+ 'accept',
238
+ 'adapter-openclaw-import',
239
+ 'reject',
240
+ 'harvest-candidates',
241
+ 'harvest',
242
+ 'prune-candidates',
243
+ 'cleanup-candidates',
244
+ 'claim',
245
+ 'assignment',
246
+ 'list-claims',
247
+ 'release-claim',
248
+ 'release-claims',
249
+ 'agent-board',
250
+ 'runtime-note',
251
+ 'note',
252
+ 'runtime-status',
253
+ 'sync',
254
+ 'check-constraints',
255
+ 'check-policy',
256
+ 'check-security',
257
+ 'setup-security',
258
+ 'install-hooks',
259
+ 'diff',
260
+ 'prune',
261
+ 'compact',
262
+ 'mcp',
263
+ 'set-trust',
264
+ 'session-start',
265
+ 'session-end',
266
+ 'whoami',
267
+ 'usage',
268
+ 'search',
269
+ 'export',
270
+ 'refresh',
271
+ 'reconcile',
272
+ 'hooks',
273
+ 'watch',
274
+ 'dispatch',
275
+ 'inbox',
276
+ 'check-events',
277
+ 'metrics',
278
+ 'rollback',
279
+ 'pull',
280
+ 'push',
281
+ 'audit',
282
+ 'history',
283
+ 'context-diff',
284
+ 'capability',
285
+ 'link',
286
+ 'tool',
287
+ 'explore',
288
+ 'discover',
289
+ 'migrate',
290
+ 'switch',
291
+ 'who',
292
+ 'worktree',
293
+ 'federation',
294
+ // Gated by BRAINCLAW_ENABLE_CODEV: listed here so the order is right in both
295
+ // modes — when not registered they are simply absent from the live array and
296
+ // the sort skips them naturally.
297
+ 'codev',
298
+ 'codev-metrics',
299
+ 'questions',
300
+ 'bootstrap-loop',
301
+ 'loop',
302
+ 'reply',
303
+ 'run',
304
+ ];
305
+ {
306
+ const rank = new Map(ORIGINAL_COMMAND_ORDER.map((name, index) => [name, index]));
307
+ // Localized cast: Commander types `commands` as readonly, but reordering the
308
+ // live array is exactly the point of this shim. Array.prototype.sort is
309
+ // stable (ES2019+), so unlisted commands keep their relative registration
310
+ // order after every listed one.
311
+ program.commands.sort((a, b) => (rank.get(a.name()) ?? Number.MAX_SAFE_INTEGER) -
312
+ (rank.get(b.name()) ?? Number.MAX_SAFE_INTEGER));
313
+ }
314
+ // ─────────────────────────────────────────────────────────────────────────────
315
+ // CAMPAIGN SCAFFOLDING (pln#622 PR0c) — TEMPORARY, removed at end of campaign.
316
+ // When BRAINCLAW_DUMP_REGISTRY is set, emit a normalized JSON snapshot of
317
+ // the fully-built Commander registry and exit BEFORE parsing: value '1'
318
+ // prints to stdout (manual debugging); any other value is a FILE PATH the
319
+ // JSON is written to — module side effects can interleave writes on stdout
320
+ // (observed on Linux CI: corrupted JSON mid-stream), a file write is not
321
+ // subject to that race. This
322
+ // adds zero visible CLI surface: no new command, option, or help text — it is
323
+ // reachable only through an env var that regular users never set. It exists
324
+ // solely so tests/unit/cli-registry-snapshot.test.ts can freeze the command
325
+ // surface while cli.ts is decomposed (PR1→PR5); the branch goes away in PR6.
326
+ // ─────────────────────────────────────────────────────────────────────────────
327
+ if (process.env.BRAINCLAW_DUMP_REGISTRY) {
328
+ // Codepoint comparison (NOT localeCompare) so the committed snapshot is
329
+ // byte-identical across machines/locales.
330
+ const byCodepoint = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
331
+ const commands = [];
332
+ const walk = (cmd, prefix) => {
333
+ const pathTokens = [...prefix, cmd.name()];
334
+ commands.push({
335
+ aliases: [...cmd.aliases()].sort(byCodepoint),
336
+ arguments: cmd.registeredArguments.map((arg) => ({
337
+ name: arg.name(),
338
+ required: arg.required,
339
+ variadic: arg.variadic,
340
+ })),
341
+ options: cmd.options
342
+ .map((opt) => ({
343
+ ...(opt.defaultValue !== undefined ? { defaultValue: opt.defaultValue } : {}),
344
+ flags: opt.flags,
345
+ long: opt.long ?? null,
346
+ mandatory: opt.mandatory,
347
+ negate: opt.negate,
348
+ short: opt.short ?? null,
349
+ valueOptional: opt.optional,
350
+ valueRequired: opt.required,
351
+ variadic: opt.variadic,
352
+ }))
353
+ .sort((a, b) => byCodepoint(a.flags, b.flags)),
354
+ path: pathTokens.join(' '),
345
355
  });
346
- }
347
- catch (err) {
348
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
349
- process.exit(1);
350
- }
351
- if (actor.kind !== 'human') {
352
- console.error(`Error: memory-rollback is reserved to registered human identities. Resolved actor '${actor.agent_name}' is kind='${actor.kind}'.`);
353
- process.exit(1);
354
- }
355
- const success = rollbackMemory(ref, cwd);
356
- if (success) {
357
- console.log(`✔ Live project memory restored to ${ref} (audit, archives, backups preserved)`);
358
- }
359
- else {
360
- console.error(`Error: failed to rollback to '${ref}'. Check memory-log for valid refs.`);
361
- process.exit(1);
362
- }
363
- });
364
- // --- upgrade ---
365
- program
366
- .command('upgrade')
367
- .description('Upgrade project memory structure and refresh managed workspace agent files without losing data')
368
- .option('--json', 'Output as JSON')
369
- .option('--dry-run', 'Show what would be done without making changes')
370
- .option('--self-update', 'Check for a newer brainclaw package version and install it before upgrading memory')
371
- .option('--to <version>', 'One-shot target schema version (e.g. --to=1.0). Real runs require a backup.')
372
- .option('--backup', 'Create a timestamped backup of .brainclaw/ before any write (always on for --to runs)')
373
- .option('--no-backup', 'Disable the automatic backup for housekeeping-only upgrade runs')
374
- .option('--rollback', 'Restore the most recent backup, park the current live store, exit')
375
- .option('--yes', 'Skip interactive confirmations (reserved for later prompt additions)')
376
- .action((options) => {
377
- runUpgrade({
378
- json: options.json,
379
- dryRun: options.dryRun,
380
- selfUpdate: options.selfUpdate,
381
- to: options.to,
382
- backup: options.backup,
383
- rollback: options.rollback,
384
- yes: options.yes,
385
- });
386
- });
387
- // --- patch-configs ---
388
- program
389
- .command('patch-configs')
390
- .description('Patch all MCP config files to use the current brainclaw binary path')
391
- .option('--json', 'Output as JSON')
392
- .action((options) => {
393
- const cwd = process.env.BRAINCLAW_CWD ?? process.cwd();
394
- const results = patchAllMcpConfigs(cwd);
395
- if (options.json) {
396
- console.log(JSON.stringify(results, null, 2));
397
- }
398
- else if (results.length === 0) {
399
- console.log('✔ All MCP configs are already up to date.');
400
- }
401
- else {
402
- for (const r of results) {
403
- const tag = r.created ? 'created' : 'updated';
404
- console.log(`✔ ${r.filePath} (${tag}) — ${r.label}`);
405
- }
406
- console.log(`\n${results.length} MCP config(s) patched.`);
407
- }
408
- });
409
- // --- machine-profile ---
410
- program
411
- .command('machine-profile')
412
- .description('Detect and persist machine capabilities (OS, shells, git users, SSH keys, toolchains, WSL, AI surfaces)')
413
- .option('--refresh', 'Force regeneration even if profile exists')
414
- .option('--json', 'Output as JSON')
415
- .action(async (options) => {
416
- const existing = loadMachineProfile();
417
- if (existing && !options.refresh) {
418
- if (options.json) {
419
- console.log(JSON.stringify(existing, null, 2));
420
- }
421
- else {
422
- console.log(renderMachineProfileSummary(existing));
423
- console.log('\nUse --refresh to regenerate.');
424
- }
425
- return;
426
- }
427
- console.log('Detecting machine capabilities...');
428
- const profile = buildMachineProfile();
429
- const filePath = saveMachineProfile(profile);
430
- if (options.json) {
431
- console.log(JSON.stringify(profile, null, 2));
432
- }
433
- else {
434
- console.log(renderMachineProfileSummary(profile));
435
- console.log(`\n✔ Profile saved to ${filePath}`);
436
- }
437
- });
438
- // --- agent-inventory ---
439
- program
440
- .command('agent-inventory')
441
- .description('Detect all installed AI coding agents and their capabilities')
442
- .option('--refresh', 'Force regeneration even if inventory exists')
443
- .option('--json', 'Output as JSON')
444
- .action(async (options) => {
445
- const existing = loadAgentInventory();
446
- if (existing && !options.refresh) {
447
- if (options.json) {
448
- console.log(JSON.stringify(existing, null, 2));
449
- }
450
- else {
451
- console.log(renderAgentInventorySummary(existing));
452
- console.log('\nUse --refresh to regenerate.');
453
- }
454
- return;
455
- }
456
- console.log('Detecting installed agents...');
457
- const inventory = buildAgentInventory();
458
- const filePath = saveAgentInventory(inventory);
459
- if (options.json) {
460
- console.log(JSON.stringify(inventory, null, 2));
461
- }
462
- else {
463
- console.log(renderAgentInventorySummary(inventory));
464
- console.log(`\n✔ Inventory saved to ${filePath}`);
465
- }
466
- });
467
- // --- projects ---
468
- program
469
- .command('projects')
470
- .description('List all brainclaw-initialized projects on this machine')
471
- .option('--scan <roots>', 'Comma-separated directories to scan for projects')
472
- .option('--register', 'Register the current project in the global registry')
473
- .option('--json', 'Output as JSON')
474
- .action(async (options) => {
475
- if (options.register) {
476
- const entry = scanProject(process.cwd());
477
- if (!entry) {
478
- console.error('No brainclaw project found in current directory. Run brainclaw init first.');
479
- process.exit(1);
480
- }
481
- upsertProject(entry);
482
- console.log(`✔ Registered ${entry.project_name} (${entry.project_id})`);
483
- return;
484
- }
485
- if (options.scan) {
486
- const roots = options.scan.split(',').map((r) => r.trim());
487
- console.log(`Scanning ${roots.join(', ')}...`);
488
- const registry = scanAndRegister(roots);
489
- if (options.json) {
490
- console.log(JSON.stringify(registry, null, 2));
491
- }
492
- else {
493
- console.log(renderGlobalRegistrySummary(registry));
494
- }
495
- return;
496
- }
497
- // Default: show existing registry
498
- const registry = loadGlobalRegistry();
499
- if (!registry || registry.projects.length === 0) {
500
- console.log('No projects registered. Use --scan <roots> or --register to add projects.');
501
- return;
502
- }
503
- if (options.json) {
504
- console.log(JSON.stringify(registry, null, 2));
356
+ for (const sub of cmd.commands)
357
+ walk(sub, pathTokens);
358
+ };
359
+ walk(program, []);
360
+ // topLevelOrder captures the LIVE registration order (what `--help` renders,
361
+ // after the command-order shim above) before the path sort below erases it.
362
+ // The snapshot test asserts it still matches the pre-split monolith order.
363
+ const topLevelOrder = program.commands.map((cmd) => cmd.name());
364
+ commands.sort((a, b) => byCodepoint(a.path, b.path));
365
+ const dump = { commands, topLevelOrder };
366
+ const dumpTarget = process.env.BRAINCLAW_DUMP_REGISTRY;
367
+ if (dumpTarget === '1') {
368
+ console.log(JSON.stringify(dump, null, 2));
505
369
  }
506
370
  else {
507
- console.log(renderGlobalRegistrySummary(registry));
371
+ fs.writeFileSync(dumpTarget, JSON.stringify(dump, null, 2), 'utf-8');
508
372
  }
509
- });
510
- // --- decision ---
511
- program
512
- .command('decision <text>')
513
- .description('Add a recent decision')
514
- .option('--outcome <outcome>', 'Outcome: approved, rejected, deferred, pending')
515
- .option('--tag <tags...>', 'Tags for this decision')
516
- .option('--path <paths...>', 'Related file paths')
517
- .option('--author <author>', 'Author name')
518
- .option('--plan <id>', 'Optional linked plan item ID')
519
- .option('--store <target>', 'Target store level: local (default), repo, workspace, user')
520
- .action((text, options) => {
521
- runDecision(text, options);
522
- });
523
- // --- constraint ---
524
- program
525
- .command('constraint <text>')
526
- .description('Add an active constraint')
527
- .option('--category <category>', 'Category: architecture, performance, security, reliability, compatibility, process, other')
528
- .option('--tag <tags...>', 'Tags for this constraint')
529
- .option('--path <paths...>', 'Related file paths')
530
- .option('--author <author>', 'Author name')
531
- .option('--store <target>', 'Target store level: local (default), repo, workspace, user')
532
- .action((text, options) => {
533
- runConstraint(text, options);
534
- });
535
- // --- trap ---
536
- program
537
- .command('trap <text>')
538
- .description('Add a known trap')
539
- .option('--status <status>', 'Status: active, resolved, expired', 'active')
540
- .option('--severity <severity>', 'Severity: low, medium, high', 'medium')
541
- .option('--visibility <visibility>', 'Visibility: shared, machine, private', 'shared')
542
- .option('--host <host>', 'Optional host identifier override for machine/private traps')
543
- .option('--tag <tags...>', 'Tags for this trap')
544
- .option('--path <paths...>', 'Related file paths')
545
- .option('--author <author>', 'Author name')
546
- .option('--ttl <duration>', 'Time-to-live: 30m, 2h, 7d (trap auto-expires after this duration)')
547
- .option('--store <target>', 'Target store level: local (default), repo, workspace, user')
548
- .action((text, options) => {
549
- runTrap(text, options);
550
- });
551
- // --- handoff ---
552
- program
553
- .command('handoff <text>')
554
- .description('Create a handoff')
555
- .requiredOption('--from <from>', 'Source of the handoff')
556
- .requiredOption('--to <to>', 'Destination of the handoff')
557
- .option('--tag <tags...>', 'Tags for this handoff')
558
- .option('--path <paths...>', 'Related file paths')
559
- .option('--project <project>', 'Optional project namespace')
560
- .option('--plan <id>', 'Optional linked plan item ID')
561
- .option('--author <author>', 'Author name')
562
- .option('--capture-diff', 'Capture current git diff into the handoff snapshot')
563
- .option('--files <files...>', 'Files touched in this handoff')
564
- .option('--pre-condition <conditions...>', 'Pre-conditions for the receiving agent')
565
- .option('--post-condition <conditions...>', 'Post-conditions the receiving agent must satisfy')
566
- .option('--test <tests...>', 'Tests the receiving agent should verify')
567
- .option('--linked-plan <plans...>', 'Linked plan IDs')
568
- .action((text, options) => {
569
- runHandoff(text, options);
570
- });
571
- // --- status ---
572
- program
573
- .command('status')
574
- .description('Show project memory status')
575
- .option('--json', 'Output as JSON')
576
- .option('--markdown', 'Output as Markdown')
577
- .action((options) => {
578
- runStatus(options);
579
- });
580
- // --- plan ---
581
- program
582
- .command('plan <subcommand> [args...]')
583
- .description('Manage shared plan items (create, list, update, delete)')
584
- .option('--json', 'Output as JSON for list')
585
- .option('--all', 'Include done and dropped plan items in list')
586
- .option('--type <type>', 'Plan type or filter: feat, fix, chore, spike, doc')
587
- .option('--status <status>', 'Status filter/update: todo, in_progress, blocked, done, dropped')
588
- .option('--priority <priority>', 'Priority: low, medium, high', 'medium')
589
- .option('--assignee <assignee>', 'Assignee for this plan item')
590
- .option('--project <project>', 'Optional project namespace')
591
- .option('--tag <tags...>', 'Tags for this plan item')
592
- .option('--path <paths...>', 'Related file paths')
593
- .option('--depends-on <ids...>', 'Dependency IDs for this plan item')
594
- .option('--author <author>', 'Author name')
595
- .option('--estimate <minutes>', 'Estimated effort in minutes (positive integer, e.g. --estimate 30)')
596
- .option('--actual-effort <effort>', 'Actual effort spent (e.g. "20min", "1h30m")')
597
- .option('--store <target>', 'Target store level: local (default), repo, workspace, user')
598
- .option('--recursive', 'Include plans from descendant brainclaw projects (for list)')
599
- .option('--local-only', 'Read from local store only for list (skip parent stores in chain)')
600
- .action((subcommand, args, options) => {
601
- runPlanResource(subcommand, args, { ...options, actualEffort: options.actualEffort, localOnly: options.localOnly });
602
- });
603
- // --- code-map ---
604
- program
605
- .command('code-map <subcommand> [args...]')
606
- .description('Query the per-project Code Map (status, refresh, find, brief)')
607
- .option('--json', 'Output as JSON')
608
- .option('--all', 'For refresh: enumerate all supported files (full refresh)')
609
- .option('--changed', 'For refresh: only changed files (default)')
610
- .option('--cascade', 'For refresh/status in a multi-project workspace: cascade across every nested project (each gets its own store; the root store is scoped to files no child owns)')
611
- .option('--limit <n>', 'Max results for find/brief', (v) => parseInt(v, 10))
612
- .action((subcommand, args, options) => {
613
- void runCodeMap(subcommand, args, options).catch((err) => {
614
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
615
- process.exit(1);
616
- });
617
- });
618
- // --- move (cross-project relocation, pln#595) ---
619
- program
620
- .command('move <entity> <id>')
621
- .description('Relocate a brainclaw item to another project, id-preserving (multi-project workspaces). Relocatable: plan, decision, constraint, trap, handoff, sequence.')
622
- .requiredOption('--to <project>', 'Target project (name, path, or basename)')
623
- .option('--from <project>', 'Source project (defaults to the current project)')
624
- .option('--force', 'Move even if an active claim references the item')
625
- .option('--json', 'Output as JSON')
626
- .action((entity, id, options) => runMove(entity, id, options));
627
- // --- list-plans ---
628
- program
629
- .command('list-plans')
630
- .description('List shared plan items')
631
- .option('--json', 'Output as JSON')
632
- .option('--status <status>', 'Status: todo, in_progress, blocked, done, dropped')
633
- .option('--type <type>', 'Filter by type: feat, fix, chore, spike, doc')
634
- .option('--assignee <assignee>', 'Filter by assignee')
635
- .option('--project <project>', 'Filter by project namespace')
636
- .option('--all', 'Include done and dropped plan items')
637
- .option('--recursive', 'Include plans from descendant brainclaw projects')
638
- .option('--local-only', 'Read from local store only (skip parent stores in chain)')
639
- .action((options) => {
640
- runListPlans({ ...options, localOnly: options.localOnly });
641
- });
642
- program
643
- .command('sequence <subcommand> [args...]')
644
- .description('Manage coordination sequences (create, list, show, update)')
645
- .option('--json', 'Output as JSON')
646
- .option('--description <text>', 'Optional sequence description')
647
- .option('--status <status>', 'Sequence status: draft, active, archived')
648
- .option('--owner <owner>', 'Optional sequence owner')
649
- .option('--items <json>', 'Sequence items JSON array')
650
- .option('--name <name>', 'Optional sequence name for update')
651
- .option('--tag <tags...>', 'Tags for this sequence')
652
- .option('--author <author>', 'Author name')
653
- .action((subcommand, args, options) => {
654
- runSequenceResource(subcommand, args, options);
655
- });
656
- // --- add-step ---
657
- program
658
- .command('add-step <planId> <text>')
659
- .description('Add an optional step to a plan item')
660
- .option('--assign <assignee>', 'Assign this step to an agent or person')
661
- .option('--estimate <minutes>', 'Step-level estimate (minutes, or a duration like "2h"/"30m")')
662
- .option('--actual-effort <effort>', 'Step-level actual effort (e.g. "45m", "2h")')
663
- .action((planId, text, options) => {
664
- runAddStep(planId, text, { assignee: options.assign, estimatedEffort: options.estimate, actualEffort: options.actualEffort });
665
- });
666
- // --- complete-step ---
667
- program
668
- .command('complete-step <planId> <stepId>')
669
- .description('Mark a plan step as done')
670
- .action((planId, stepId) => {
671
- runCompleteStep(planId, stepId);
672
- });
673
- // --- update-step ---
674
- program
675
- .command('update-step <planId> <stepId>')
676
- .description('Update a plan step (status, text, assignee, effort)')
677
- .option('--status <status>', 'New status: todo, in_progress, testing, done, blocked')
678
- .option('--text <text>', 'Replace step description')
679
- .option('--assign <assignee>', 'Assign the step (empty string to unassign)')
680
- .option('--estimate <minutes>', 'Step-level estimate (minutes, or a duration like "2h"/"30m")')
681
- .option('--actual-effort <effort>', 'Step-level actual effort (e.g. "45m", "2h")')
682
- .action((planId, stepId, options) => {
683
- runUpdateStep(planId, stepId, options);
684
- });
685
- // --- delete-step ---
686
- program
687
- .command('delete-step <planId> <stepId>')
688
- .description('Remove a step from a plan')
689
- .action((planId, stepId) => {
690
- runDeleteStep(planId, stepId);
691
- });
692
- // --- estimation-report ---
693
- program
694
- .command('estimation-report')
695
- .description('Show estimation accuracy report for completed plans')
696
- .option('--agent <name>', 'Filter by agent/author name')
697
- .option('--json', 'Output as JSON')
698
- .option('--outlier-threshold <minutes>', 'Drop plan-wallclock actuals over N minutes from the stats (default 1440 = 24h; 0 disables)')
699
- .action((options) => {
700
- runEstimationReport({
701
- agent: options.agent,
702
- json: options.json,
703
- outlierThresholdMinutes: options.outlierThreshold !== undefined ? Number(options.outlierThreshold) : undefined,
704
- });
705
- });
706
- // --- update-plan ---
707
- program
708
- .command('update-plan <id>')
709
- .description('Update a shared plan item')
710
- .option('--status <status>', 'Status: todo, in_progress, blocked, done, dropped')
711
- .option('--assignee <assignee>', 'Assign a user or agent to this plan item')
712
- .option('--project <project>', 'Set or change project namespace')
713
- .option('--priority <priority>', 'Priority: low, medium, high')
714
- .option('--actual-effort <effort>', 'Actual effort spent (e.g. "20min", "1h30m")')
715
- .action((id, options) => {
716
- runUpdatePlan(id, { ...options, actualEffort: options.actualEffort });
717
- });
718
- // --- surface-task ---
719
- program
720
- .command('surface-task <subcommand> [args...]')
721
- .description('Manage queued tasks for desktop AI surfaces such as ChatGPT Desktop or Claude Desktop')
722
- .option('--json', 'Output as JSON for list')
723
- .option('--all', 'Include completed, cancelled, and failed tasks in list')
724
- .option('--status <status>', 'Status filter/update: queued, in_progress, completed, cancelled, failed')
725
- .option('--target <surface>', 'Target surface, e.g. chatgpt, claude, gemini')
726
- .option('--kind <kind>', 'Task kind: visual_asset, draft, summary, analysis, research, custom')
727
- .option('--instructions <text>', 'Detailed instructions for the target surface')
728
- .option('--output <paths...>', 'Expected output paths')
729
- .option('--result <text>', 'Optional result note when updating a task')
730
- .option('--tag <tags...>', 'Tags for this task')
731
- .option('--path <paths...>', 'Related file paths')
732
- .option('--agent <agent>', 'Author agent name')
733
- .option('--agent-id <agentId>', 'Author agent id')
734
- .action((subcommand, args, options) => {
735
- runSurfaceTaskResource(subcommand, args, {
736
- ...options,
737
- agentId: options.agentId,
738
- });
739
- });
740
- // --- delete-plan ---
741
- program
742
- .command('delete-plan <id>')
743
- .description('Delete a shared plan item')
744
- .action((id) => {
745
- runDeletePlan(id);
746
- });
747
- // --- update-handoff ---
748
- program
749
- .command('update-handoff <id>')
750
- .description('Update the status, recipient, or review state of a handoff')
751
- .option('--status <status>', 'Status: open, accepted, closed')
752
- .option('--to <agent>', 'Change the receiving agent')
753
- .option('--narrative <text>', 'Update the narrative attached to the handoff')
754
- .option('--reviewer <agent>', 'Set or override the assigned reviewer')
755
- .option('--review-verdict <verdict>', 'Set review verdict: approve or request_changes')
756
- .option('--reviewed-by <agent>', 'Set the reviewer identity that produced the verdict')
757
- .option('--review-summary <text>', 'Attach a short review summary')
758
- .option('--blocking-issue <text>', 'Add a blocking review issue (repeatable)', collect, [])
759
- .option('--suggestion <text>', 'Add a non-blocking review suggestion (repeatable)', collect, [])
760
- .action((id, options) => {
761
- runUpdateHandoff(id, {
762
- ...options,
763
- review_verdict: options.reviewVerdict,
764
- reviewed_by: options.reviewedBy,
765
- review_summary: options.reviewSummary,
766
- blocking_issues: options.blockingIssue,
767
- suggestions: options.suggestion,
768
- });
769
- });
770
- // --- doctor ---
771
- program
772
- .command('doctor')
773
- .description('Check memory health and security')
774
- .option('--json', 'Output as JSON dashboard')
775
- .option('--migration-check', 'Report versioned documents that need schema migration')
776
- .option('--fix-agent-ignore', 'Add missing .gitignore entries for generated local Brainclaw agent files')
777
- .option('--fix-hooks', 'Purge stale/broken/duplicate brainclaw session hooks across all Claude Code settings scopes (user + cwd) and rewrite the canonical ones')
778
- .option('--fix', 'Fix auto-resolvable issues (e.g. drifting MCP configs)')
779
- .option('--repair', 'Rebuild dist/ when the MCP runtime is missing or stale')
780
- .option('--after-migration', 'Run the v1.0 post-migration health check only (exits non-zero on any failure)')
781
- .option('--dispatch', 'Run dispatch-health diagnostic only: reconcile open agent_runs and report stuck/unverified/silent failures (pln#496 step stp_8c072d75)')
782
- .option('--verify-journal', 'Phase-2 cutover gate (pln#565): rebuild state from the event journal and diff vs live projections; exits non-zero on any drift')
783
- .option('--spawn-check', 'Real spawn round-trip per installed agent before dispatch (pln#520 step 2): validates delivery + handshake on this host, exits non-zero on any installed-agent failure')
784
- .option('--spawn-check-timeout <ms>', 'Per-agent timeout for --spawn-check (default 15000)', parseInt)
785
- .option('--hygiene', 'Coordination-hygiene snapshot (pln#602): counts per family, park candidates, serve-count aging stats. Read-only.')
786
- .action(async (options) => {
787
- if (options.spawnCheck) {
788
- await runDoctorSpawnCheck({ cwd: options.cwd, json: options.json, timeoutMs: options.spawnCheckTimeout });
789
- return;
790
- }
791
- runDoctor({ ...options, afterMigration: options.afterMigration, dispatch: options.dispatch, verifyJournal: options.verifyJournal, hygiene: options.hygiene });
792
- });
793
- // --- repair (Phase 4 Sprint 2 Lane C / pln#397) ---
794
- program
795
- .command('repair')
796
- .description('Apply safe, non-destructive fixes for the repair candidates surfaced by doctor')
797
- .option('--dry-run', 'Print the plan without executing anything')
798
- .option('--include-unsafe', 'Also apply candidates flagged unsafe (preserves data but requires confirmation)')
799
- .option('--json', 'Output as JSON')
800
- .action((options) => {
801
- runRepair({
802
- dryRun: options.dryRun,
803
- includeUnsafe: options.includeUnsafe,
804
- json: options.json,
805
- });
806
- });
807
- // --- stale (Phase 4 Sprint 1 Lane A / pln#390) ---
808
- program
809
- .command('stale [subcommand] [id]')
810
- .description('List or resolve stale memory items (plans, traps, handoffs, candidates, runtime notes). Subcommands: list (default), resolve <id>.')
811
- .option('--json', 'Output as JSON')
812
- .action((subcommand, id, options) => {
813
- runStale(subcommand, id, { json: options.json });
814
- });
815
- // --- version ---
816
- program
817
- .command('version')
818
- .description('Show the installed brainclaw version and the project version policy')
819
- .option('--check', 'Check the configured installable update source')
820
- .option('--publish-local', 'Create/update the local installable .releases channel via npm pack')
821
- .option('--release-notes <text>', 'Attach plain-text release notes to the generated local-pack manifest')
822
- .option('--agent-release-notes <json>', 'Attach structured agent-first release notes (JSON) to the generated local-pack manifest')
823
- .option('--auto-release-notes', 'Auto-generate agent-first release notes from git log (use with --publish-local)')
824
- .option('--json', 'Output as JSON')
825
- .action((options) => {
826
- runVersion(options);
827
- });
828
- // --- release-notes ---
829
- program
830
- .command('release-notes')
831
- .description('Show or generate agent-first release notes')
832
- .option('--generate', 'Generate release notes from git log instead of showing configured ones')
833
- .option('--since <ref>', 'Git ref to generate from (default: last version tag)')
834
- .option('--json', 'Output as JSON')
835
- .action((options) => {
836
- runReleaseNotes(options);
837
- });
838
- // --- uninstall ---
839
- import { runUninstall } from './commands/uninstall.js';
840
- program
841
- .command('uninstall')
842
- .description('Remove brainclaw from a project and/or machine')
843
- .option('--project', 'Remove brainclaw from the current project (.brainclaw/, agent files, configs)')
844
- .option('--machine', 'Remove brainclaw global config (~/.brainclaw/)')
845
- .option('-y, --yes', 'Skip confirmation prompts')
846
- .action(async (options) => {
847
- await runUninstall(options);
848
- });
849
- // --- rebuild ---
850
- program
851
- .command('rebuild')
852
- .description('Rebuild project.md from canonical memory state')
853
- .action(() => {
854
- runRebuild();
855
- });
856
- // --- reflect ---
857
- program
858
- .command('reflect [text]')
859
- .description('Create a memory candidate for review')
860
- .option('--type <type>', 'Type: constraint, decision, trap, handoff')
861
- .option('--batch <file>', 'Import runtime events from JSON file')
862
- .option('--session <id>', 'Import runtime events by session id from .brainclaw/runtime/')
863
- .option('--tag <tags...>', 'Tags')
864
- .option('--author <author>', 'Author name')
865
- .option('--source <source>', 'Source context (e.g. agent name)')
866
- .option('--severity <severity>', 'Severity for traps: low, medium, high')
867
- .option('--from <from>', 'Handoff source')
868
- .option('--to <to>', 'Handoff destination')
869
- .option('--path <paths...>', 'Related file paths')
870
- .action((text, options) => {
871
- runReflect(text, options);
872
- });
873
- // --- reflect-runtime-note ---
874
- program
875
- .command('reflect-runtime-note <id> [text]')
876
- .description('Turn a visible runtime note into a shared review candidate')
877
- .option('--type <type>', 'Type: constraint, decision, trap, handoff')
878
- .option('--host <host>', 'Look up machine-local runtime note for a specific host')
879
- .option('--all-hosts', 'Look up runtime notes across all hosts')
880
- .option('--suggest', 'Show candidate type suggestions before or instead of creation')
881
- .option('--json', 'Output suggestions as JSON when used with --suggest or without --type')
882
- .option('--tag <tags...>', 'Additional tags to merge with the runtime note tags')
883
- .option('--author <author>', 'Author name for the candidate')
884
- .option('--source <source>', 'Source context for the candidate')
885
- .option('--severity <severity>', 'Severity for traps: low, medium, high')
886
- .option('--from <from>', 'Handoff source')
887
- .option('--to <to>', 'Handoff destination')
888
- .option('--path <paths...>', 'Related file paths')
889
- .action((id, text, options) => {
890
- runReflectRuntimeNote(id, text, options);
891
- });
892
- // --- context ---
893
- program
894
- .command('context')
895
- .description('Generate compact context for agents')
896
- .option('--for <target>', 'Task/path/goal to prioritize')
897
- .option('--project <project>', 'Explicit project namespace for layered instructions')
898
- .option('--agent <agent>', 'Agent name for agent-layer instructions')
899
- .option('--host <host>', 'Include machine-local runtime notes for a specific host')
900
- .option('--all-hosts', 'Include machine-local runtime notes from all hosts')
901
- .option('--profile <profile>', 'Profile: dev, openclaw, ops, research')
902
- .option('--include-pending', 'Include pending candidates')
903
- .option('--max-items <count>', 'Limit number of items', parseInt)
904
- .option('--max-chars <count>', 'Approximate output budget for selected item content', parseInt)
905
- .option('--digest', 'Include a short deterministic digest ahead of the detailed context')
906
- .option('--since-session <id>', 'Include a compact memory diff since the given session started')
907
- .option('--no-bootstrap', 'Disable brownfield bootstrap fallback when canonical memory is sparse')
908
- .option('--refresh-bootstrap', 'Refresh brownfield bootstrap profile before building context')
909
- .option('--template', 'Output prompt-ready context template')
910
- .option('--compact-template', 'Use compact template format (default for openclaw profile)')
911
- .option('--explain', 'Show ranking reasons in human-readable output')
912
- .option('--json', 'Output as JSON')
913
- .action((options) => {
914
- runContext(options);
915
- });
916
- // --- bootstrap ---
917
- program
918
- .command('bootstrap')
919
- .description('Derive brownfield bootstrap signals and optionally import them into canonical memory')
920
- .option('--for <target>', 'Target path or scope to tailor the bootstrap')
921
- .option('--json', 'Output as JSON')
922
- .option('--refresh', 'Force a fresh bootstrap scan instead of reusing the current profile')
923
- .option('--interview', 'Render the adaptive interview prompts instead of the bootstrap summary')
924
- .option('--audience <audience>', 'Target interview prompts for cli, ide_chat, or any')
925
- .option('--answers-file <path>', 'Load structured bootstrap interview answers from a JSON file')
926
- .option('--apply', 'Import the current bootstrap proposal into canonical memory')
927
- .option('--uninstall', 'Deactivate the last bootstrap import managed by this workspace')
928
- .option('-y, --yes', 'Skip confirmation prompts for apply/uninstall')
929
- .action(async (options) => {
930
- await runBootstrap(options);
931
- });
932
- // --- env ---
933
- program
934
- .command('env')
935
- .description('Show the local execution context and optionally detected agent tooling')
936
- .option('--json', 'Output as JSON')
937
- .option('--agent-tooling', 'Include AGENTS.md, local skills, and local MCP inventory')
938
- .action((options) => {
939
- runEnv({ ...options, agentTooling: options.agentTooling });
940
- });
941
- // --- memory ---
942
- program
943
- .command('memory <subcommand> [args...]')
944
- .description('Manage canonical memory items (create, list, update, delete)')
945
- .option('--json', 'Output as JSON for list')
946
- .option('--type <type>', 'Memory type/filter: decision, constraint, trap, handoff')
947
- .option('--text <text>', 'Replacement text for memory update')
948
- .option('--tag <tags...>', 'Tags')
949
- .option('--path <paths...>', 'Related file paths')
950
- .option('--author <author>', 'Author name')
951
- .option('--outcome <outcome>', 'Decision outcome: approved, rejected, deferred, pending')
952
- .option('--category <category>', 'Constraint category: architecture, performance, security, reliability, compatibility, process, other')
953
- .option('--status <status>', 'Constraint, trap, or handoff status')
954
- .option('--severity <severity>', 'Trap severity: low, medium, high')
955
- .option('--project <project>', 'Optional project namespace')
956
- .option('--plan <id>', 'Optional linked plan item ID')
957
- .option('--from <from>', 'Handoff source')
958
- .option('--to <to>', 'Handoff destination')
959
- .option('--visibility <visibility>', 'Trap visibility: shared, machine, private', 'shared')
960
- .option('--host <host>', 'Optional host identifier override for machine/private traps')
961
- .option('--ttl <duration>', 'Time-to-live: 30m, 2h, 7d')
962
- .option('--store <target>', 'Target store level: local (default), repo, workspace, user')
963
- .action((subcommand, args, options) => {
964
- runMemoryCommand(subcommand, args, options);
965
- });
966
- // --- instruction ---
967
- program
968
- .command('instruction <text>')
969
- .description('Add a layered shared instruction')
970
- .option('--layer <layer>', 'Instruction layer: global, project, agent', 'global')
971
- .option('--project <project>', 'Project namespace when --layer project is used')
972
- .option('--agent <agent>', 'Agent name when --layer agent is used')
973
- .option('--tag <tags...>', 'Tags for this instruction')
974
- .option('--author <author>', 'Author name')
975
- .option('--supersedes <id>', 'Supersede an older instruction entry')
976
- .option('--store <target>', 'Target store level: local (default), repo, workspace, user')
977
- .action((text, options) => {
978
- runInstruction(text, options);
979
- });
980
- // --- list-instructions ---
981
- program
982
- .command('list-instructions')
983
- .description('List raw or resolved shared instructions')
984
- .option('--json', 'Output as JSON')
985
- .option('--layer <layer>', 'Filter by layer: global, project, agent')
986
- .option('--project <project>', 'Project namespace filter')
987
- .option('--agent <agent>', 'Agent name filter')
988
- .option('--active', 'Only show active entries')
989
- .option('--resolved', 'Resolve effective instructions for the given scope')
990
- .option('--for <target>', 'Infer project namespace from target path when strategy=folder')
991
- .action((options) => {
992
- runListInstructions(options);
993
- });
994
- // --- register-agent ---
995
- program
996
- .command('register-agent <name>')
997
- .description('Register an agent or human identity in project memory')
998
- .option('--kind <kind>', 'Identity kind: agent, human, unknown', 'unknown')
999
- .option('--capability <value>', 'Declare a capability on the agent profile (repeatable)', collect, [])
1000
- .option('--replace-capabilities', 'Replace existing capabilities instead of merging')
1001
- .option('--generate-fingerprint', 'Generate or rotate a local public identity fingerprint for this agent')
1002
- .option('--set-current', 'Set this identity as the current agent in config')
1003
- .option('--curator', 'Register this agent as a curator (project owner with direct-write access)')
1004
- .option('--remove', 'Remove this identity instead of registering (guarded: debris identities only unless --force)')
1005
- .option('--force', 'With --remove: allow removing a non-debris identity')
1006
- .option('--json', 'Output as JSON')
1007
- .action((name, options) => {
1008
- if (options.remove) {
1009
- runRemoveAgent(name, { force: options.force, json: options.json });
1010
- return;
1011
- }
1012
- runRegisterAgent(name, options);
1013
- });
1014
- // --- enable-agent ---
1015
- program
1016
- .command('enable-agent <name>')
1017
- .description('Activate a supported coding agent on an already initialized project')
1018
- .option('--kind <kind>', 'Identity kind: agent, human, unknown', 'agent')
1019
- .option('--context-profile <profile>', 'Default context profile: dev, dense, compact, copilot, quick, openclaw, ops, research')
1020
- .option('--capability <value>', 'Declare a capability on the agent profile (repeatable)', collect, [])
1021
- .option('--replace-capabilities', 'Replace existing capabilities instead of merging')
1022
- .option('--generate-fingerprint', 'Generate or rotate a local public identity fingerprint for this agent')
1023
- .option('--set-current', 'Set this identity as the current agent in config')
1024
- .option('--json', 'Output as JSON')
1025
- .action((name, options) => {
1026
- runEnableAgent(name, options);
1027
- });
1028
- // --- list-agents ---
1029
- program
1030
- .command('list-agents')
1031
- .description('List registered agent identities')
1032
- .option('--json', 'Output as JSON')
1033
- .option('--with-reputation', 'Include bounded reputation summaries when available')
1034
- .action((options) => {
1035
- runListAgents(options);
1036
- });
1037
- // --- review ---
1038
- program
1039
- .command('review')
1040
- .description('List pending memory candidates')
1041
- .option('--json', 'Output as JSON')
1042
- .option('--type <type>', 'Filter by type')
1043
- .option('--prioritized', 'Sort by review priority and show SLA')
1044
- .option('--only-overdue', 'Show only candidates overdue review SLA')
1045
- .option('--assignee <assignee>', 'Filter by assignee tag (assignee:<name>)')
1046
- .option('--for-curator <curator>', 'Shortcut assignee filter for curator queue')
1047
- .option('--take <count>', 'Limit number of reviewed items', parseInt)
1048
- .option('--claim <curator>', 'Atomically assign selected candidates to a curator')
1049
- .option('--auto', 'Auto-promote candidates meeting the configured score threshold')
1050
- .option('--auto-by <actor>', 'Actor identity for auto-promotion (defaults to current user)')
1051
- .action((options) => {
1052
- runReview({ ...options, autoBy: options.autoBy });
1053
- });
1054
- // --- show-candidate ---
1055
- program
1056
- .command('show-candidate <id>')
1057
- .description('Show details of a memory candidate')
1058
- .option('--related', 'Show related candidates by shared tags/paths')
1059
- .action((id, options) => {
1060
- runShowCandidate(id, { related: options.related });
1061
- });
1062
- // --- star-candidate ---
1063
- program
1064
- .command('star-candidate <id>')
1065
- .description('Add an adoption star to a pending candidate')
1066
- .option('--by <actor>', 'Agent or person adding the star')
1067
- .action((id, options) => {
1068
- runStarCandidate(id, options);
1069
- });
1070
- // --- use-candidate ---
1071
- program
1072
- .command('use-candidate <id>')
1073
- .description('Record that a pending candidate was reused in a concrete work context')
1074
- .option('--by <actor>', 'Agent or person using the candidate')
1075
- .requiredOption('--context <context>', 'Work context where the candidate was reused')
1076
- .action((id, options) => {
1077
- runUseCandidate(id, options);
1078
- });
1079
- // --- accept ---
1080
- program
1081
- .command('accept <id>')
1082
- .description('Accept a candidate into canonical memory')
1083
- .option('--by <reviewer>', 'Reviewer identity used for governance checks')
1084
- .action((id, options) => {
1085
- runAccept(id, options.by);
1086
- });
1087
- // --- adapter-openclaw-import ---
1088
- program
1089
- .command('adapter-openclaw-import [file]')
1090
- .description('Import OpenClaw runtime events file as memory candidates')
1091
- .option('--session <id>', 'Import runtime events from a session id in .brainclaw/runtime/')
1092
- .option('--dry-run', 'Preview import without creating candidates')
1093
- .option('--source <source>', 'Source label for imported candidates', 'openclaw')
1094
- .option('--author <author>', 'Author for imported candidates')
1095
- .action((file, options) => {
1096
- runAdapterOpenclawImport(file, options);
1097
- });
1098
- // --- reject ---
1099
- program
1100
- .command('reject <id>')
1101
- .description('Reject a memory candidate')
1102
- .option('--by <reviewer>', 'Reviewer identity used for rejection attribution')
1103
- .option('--reason <reason>', 'Reason for rejection')
1104
- .action((id, options) => {
1105
- runReject(id, options.reason, options.by);
1106
- });
1107
- // --- harvest-candidates ---
1108
- program
1109
- .command('harvest-candidates')
1110
- .description('Harvest candidates from worktree inboxes into the main project store (codex sandbox bridge)')
1111
- .option('--dry-run', 'Preview what would be imported without writing anything')
1112
- .option('--worktree <path>', 'Explicit worktree path to scan (repeatable)', collect, [])
1113
- .option('--json', 'Output as JSON')
1114
- .action((options) => {
1115
- const globalOpts = program.opts();
1116
- runHarvestCandidates({ ...options, cwd: globalOpts.cwd });
1117
- });
1118
- // --- harvest (lane results, pln#526) ---
1119
- program
1120
- .command('harvest [assignment_id]')
1121
- .description('Harvest a worker LANE-RESULT.json from its worktree into the project (pass an assignment id, or --all)')
1122
- .option('--all', 'Harvest every lane result found across worktrees')
1123
- .option('--integrate', 'Worktree-as-contract (pln#534): commit the worktree diff on behalf of a sandboxed worker, lifecycle the assignment, and release the claim')
1124
- .option('--orphaned', 'Recover a dead worker that left NO lane-result (pln#554): typecheck + commit the worktree on behalf, lifecycle, release. Never deletes or resets anything')
1125
- .option('--base <ref>', 'Base ref for --orphaned commits-ahead comparison (default: master)')
1126
- .option('--dry-run', 'Preview without writing events/markers')
1127
- .option('--worktree <path>', 'Explicit worktree path to scan (repeatable)', collect, [])
1128
- .option('--json', 'Output as JSON')
1129
- .action((assignmentId, options) => {
1130
- const globalOpts = program.opts();
1131
- runHarvestLane(assignmentId, { ...options, cwd: globalOpts.cwd });
1132
- });
1133
- // --- prune-candidates ---
1134
- program
1135
- .command('prune-candidates')
1136
- .description('Remove old rejected candidates')
1137
- .option('--days <days>', 'Max age in days', parseInt)
1138
- .option('--dry-run', 'Preview without deleting')
1139
- .action((options) => {
1140
- runPruneCandidates(options);
1141
- });
1142
- // --- cleanup-candidates ---
1143
- program
1144
- .command('cleanup-candidates')
1145
- .description('Remove stale auto-generated pending candidates')
1146
- .option('--max-age <days>', 'Max age in days before cleanup', parseInt)
1147
- .option('--dry-run', 'Preview without deleting')
1148
- .action((options) => {
1149
- if (!memoryExists()) {
1150
- console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
1151
- process.exit(1);
1152
- }
1153
- const maxAgeDays = options.maxAge ?? 30;
1154
- const result = cleanupStaleCandidates({
1155
- maxAgeDays,
1156
- dryRun: options.dryRun,
1157
- });
1158
- if (result.matched === 0) {
1159
- console.log(`No stale auto-generated candidates older than ${maxAgeDays} days found.`);
1160
- return;
1161
- }
1162
- if (options.dryRun) {
1163
- console.log(`Would remove ${result.matched} stale auto-generated candidate(s):`);
1164
- for (const candidate of result.candidates) {
1165
- console.log(` [${candidate.id}] ${candidate.text}`);
1166
- }
1167
- return;
1168
- }
1169
- console.log(`✔ Removed ${result.deleted} stale auto-generated candidate(s) older than ${maxAgeDays} days.`);
1170
- });
1171
- // --- claim ---
1172
- program
1173
- .command('claim <subcommand> [args...]')
1174
- .description('Manage work claims (create, list, release)')
1175
- .option('--agent <agent>', 'Agent or person name; defaults to the configured current agent')
1176
- .option('--scope <scope>', 'Scope being claimed (e.g. file path, module)')
1177
- .option('--project <project>', 'Optional project namespace for this claim')
1178
- .option('--plan <id>', 'Optional linked plan item ID')
1179
- .option('--ttl <duration>', 'Auto-expire after duration: 30m, 2h, 8h, 1d')
1180
- .option('--all', 'Include released claims in list')
1181
- .option('--json', 'Output as JSON for list')
1182
- .option('--plan-status <status>', 'Optional linked plan status when releasing: todo, in_progress, blocked, done, dropped')
1183
- .option('--coordinator-override', 'Trusted+ only: release a claim owned by another agent')
1184
- .option('--store <target>', 'Target store level: local (default), repo, workspace')
1185
- .option('--local-only', 'Read from local store only for list (skip parent stores in chain)')
1186
- .action((subcommand, args, options) => {
1187
- runClaimResource(subcommand, args, {
1188
- ...options,
1189
- planStatus: options.planStatus,
1190
- coordinatorOverride: options.coordinatorOverride,
1191
- localOnly: options.localOnly,
1192
- });
1193
- });
1194
- // --- assignment ---
1195
- program
1196
- .command('assignment <subcommand> [args...]')
1197
- .description('Manage work assignments (list, show, update, cancel)')
1198
- .option('--json', 'Output as JSON for list/show')
1199
- .option('--all', 'Include terminal assignments in list')
1200
- .option('--status <status>', 'Status filter for list or target status for update')
1201
- .option('--agent <agent>', 'Filter by agent name')
1202
- .option('--claim <id>', 'Filter by linked claim ID')
1203
- .option('--plan <id>', 'Filter by linked plan ID')
1204
- .option('--sequence <id>', 'Filter by linked sequence ID')
1205
- .option('--reason <text>', 'Optional status reason for update/cancel')
1206
- .action((subcommand, args, options) => {
1207
- runAssignmentResource(subcommand, args, {
1208
- ...options,
1209
- claim: options.claim,
1210
- plan: options.plan,
1211
- sequence: options.sequence,
1212
- });
1213
- });
1214
- // --- list-claims ---
1215
- program
1216
- .command('list-claims')
1217
- .description('List work claims')
1218
- .option('--json', 'Output as JSON')
1219
- .option('--all', 'Include released claims')
1220
- .option('--project <project>', 'Filter by project namespace')
1221
- .option('--plan <id>', 'Filter by linked plan item')
1222
- .option('--agent <agent>', 'Filter by agent name')
1223
- .option('--local-only', 'Read from local store only (skip parent stores in chain)')
1224
- .action((options) => {
1225
- runListClaims({ ...options, localOnly: options.localOnly });
1226
- });
1227
- // --- release-claim ---
1228
- program
1229
- .command('release-claim <id>')
1230
- .description('Release a work claim')
1231
- .option('--plan-status <status>', 'Optional linked plan status: todo, in_progress, blocked, done, dropped')
1232
- .option('--coordinator-override', 'Trusted+ only: release a claim owned by another agent')
1233
- .action((id, options) => {
1234
- runReleaseClaim(id, { ...options, coordinatorOverride: options.coordinatorOverride });
1235
- });
1236
- // --- release-claims ---
1237
- program
1238
- .command('release-claims')
1239
- .description('Bulk-release claims whose scope overlaps with git-changed files')
1240
- .option('--from-git-diff', 'Use ORIG_HEAD..HEAD diff to detect changed files (post-merge)')
1241
- .option('--ref1 <ref>', 'First git ref (default: ORIG_HEAD)')
1242
- .option('--ref2 <ref>', 'Second git ref (default: HEAD)')
1243
- .action((options) => {
1244
- runReleaseClaims({ fromGitDiff: options.fromGitDiff, ref1: options.ref1, ref2: options.ref2 });
1245
- });
1246
- // --- agent-board ---
1247
- program
1248
- .command('agent-board')
1249
- .description('Show a coordination board for agents, plans, claims, handoffs, and instructions')
1250
- .option('--agent <agent>', 'Filter by agent name')
1251
- .option('--project <project>', 'Filter by project namespace')
1252
- .option('--for <target>', 'Infer project from target path')
1253
- .option('--host <host>', 'Include machine-local runtime notes for a specific host')
1254
- .option('--all-hosts', 'Include machine-local runtime notes from all hosts')
1255
- .option('--json', 'Output as JSON')
1256
- .option('--with-reputation', 'Include bounded reputation summaries when available')
1257
- .option('--capabilities', 'List all registered agents with their declared capabilities')
1258
- .option('--suggest <query>', 'Suggest agents whose capabilities match a query string')
1259
- .option('--include-session-meta', 'Include session_start/session_end runtime notes (hidden by default)')
1260
- .option('--all-agents', 'Show unfiltered board (supervisor mode — all claims, all agents)')
1261
- .action((options) => {
1262
- runAgentBoard(options);
1263
- });
1264
- // --- runtime-note ---
1265
- program
1266
- .command('runtime-note <text>')
1267
- .description('Add a runtime note for an agent')
1268
- .option('--agent <agent>', 'Agent name; defaults to the configured current agent')
1269
- .option('--project <project>', 'Optional project namespace')
1270
- .option('--plan <id>', 'Optional linked plan item ID')
1271
- .option('--visibility <visibility>', 'Visibility: shared, machine, private', 'shared')
1272
- .option('--host <host>', 'Optional host identifier override for machine/private runtime notes')
1273
- .option('--tag <tags...>', 'Tags')
1274
- .option('--ttl <duration>', 'Time-to-live: 30m, 2h, 7d (note auto-expires after this duration)')
1275
- .option('--auto-reflect', 'Attempt to turn this runtime note into durable memory immediately')
1276
- .action((text, options) => {
1277
- runRuntimeNote(text, { ...options, autoReflect: options.autoReflect });
1278
- });
1279
- // --- note create ---
1280
- const noteCommand = program
1281
- .command('note')
1282
- .description('Manage runtime notes');
1283
- noteCommand
1284
- .command('create <text>')
1285
- .description('Alias for runtime-note')
1286
- .option('--agent <agent>', 'Agent name; defaults to the configured current agent')
1287
- .option('--project <project>', 'Optional project namespace')
1288
- .option('--plan <id>', 'Optional linked plan item ID')
1289
- .option('--visibility <visibility>', 'Visibility: shared, machine, private', 'shared')
1290
- .option('--host <host>', 'Optional host identifier override for machine/private runtime notes')
1291
- .option('--tag <tags...>', 'Tags')
1292
- .option('--ttl <duration>', 'Time-to-live: 30m, 2h, 7d (note auto-expires after this duration)')
1293
- .option('--auto-reflect', 'Attempt to turn this runtime note into durable memory immediately')
1294
- .action((text, options) => {
1295
- runRuntimeNote(text, { ...options, autoReflect: options.autoReflect });
1296
- });
1297
- // --- runtime-status ---
1298
- program
1299
- .command('runtime-status')
1300
- .description('Show runtime notes')
1301
- .option('--agent <agent>', 'Filter by agent')
1302
- .option('--plan <id>', 'Filter by linked plan item')
1303
- .option('--visibility <visibility>', 'Visibility filter: shared, machine, private, all')
1304
- .option('--host <host>', 'Include machine-local notes for a specific host')
1305
- .option('--all-hosts', 'Include machine-local notes from all hosts')
1306
- .option('--json', 'Output as JSON')
1307
- .action((options) => {
1308
- runRuntimeStatus(options);
1309
- });
1310
- // --- sync ---
1311
- program
1312
- .command('sync')
1313
- .description('Summarize memory changes and optionally commit')
1314
- .option('--commit', 'Create a local git commit')
1315
- .option('--message <message>', 'Custom commit message')
1316
- .option('--summary-only', 'Print summary only; skip git status and commit checks')
1317
- .option('--scope <scope>', 'Scope: all, state, config, project, inbox, archive, claims, runtime, runtime-local, trap-local')
1318
- .option('--include-machine-runtime', 'Include machine-local runtime memory in sync scope for all')
1319
- .option('--remote', 'Pull + push from/to remote memory repo in one step')
1320
- .action((options) => {
1321
- runSync({ ...options, remote: options.remote });
1322
- });
1323
- // --- check-constraints ---
1324
- program
1325
- .command('check-constraints')
1326
- .description('Check if staged files (or given files) violate active constraints')
1327
- .option('--staged', 'Check git staged files (git diff --cached --name-only)')
1328
- .option('--files <files...>', 'Explicit list of files to check')
1329
- .option('--json', 'Output as JSON')
1330
- .action((options) => {
1331
- runCheckConstraints({ staged: options.staged, files: options.files, json: options.json });
1332
- });
1333
- // --- check-policy ---
1334
- program
1335
- .command('check-policy')
1336
- .description('Pre-execution policy check: verify claims, constraints, traps and instructions for a scope')
1337
- .requiredOption('--scope <path>', 'File or directory scope to check')
1338
- .option('--agent <name>', 'Agent name to check claims for')
1339
- .option('--agent-id <id>', 'Agent id to check claims for')
1340
- .option('--action <action>', 'Intended action: edit, create, delete')
1341
- .option('--json', 'Output as JSON')
1342
- .action((options) => {
1343
- runCheckPolicy({
1344
- scope: options.scope,
1345
- agent: options.agent,
1346
- agentId: options.agentId,
1347
- action: options.action,
1348
- json: options.json,
1349
- });
1350
- });
1351
- // --- check-security ---
1352
- program
1353
- .command('check-security')
1354
- .description('Check supply chain security scores for packages via Socket.dev')
1355
- .option('--packages <names>', 'Comma-separated package names (e.g. "axios,express" or "axios@1.14.1")')
1356
- .option('--requirements <file>', 'Path to a pip-style requirements.txt to scan')
1357
- .option('--lockfile <file>', 'Path to a package-lock.json (npm) to scan top-level deps')
1358
- .option('--ecosystem <type>', 'Package ecosystem: npm or pypi', 'npm')
1359
- .option('--mode <mode>', 'Override security mode: advisory or enforced (defaults to config)')
1360
- .option('--json', 'Output as JSON')
1361
- .action(async (options) => {
1362
- await runCheckSecurity({
1363
- packages: options.packages,
1364
- requirements: options.requirements,
1365
- lockfile: options.lockfile,
1366
- ecosystem: options.ecosystem,
1367
- mode: options.mode,
1368
- json: options.json,
1369
- });
1370
- });
1371
- // --- setup-security ---
1372
- program
1373
- .command('setup-security')
1374
- .description('Enable supply chain security gate: generate wrapper scripts and configure preinstall checks')
1375
- .option('--mode <mode>', 'Security mode: advisory (default) or enforced', 'advisory')
1376
- .action((options) => {
1377
- runSetupSecurity({ mode: options.mode });
1378
- });
1379
- // --- install-hooks ---
1380
- program
1381
- .command('install-hooks')
1382
- .description('Install a Git pre-commit hook that blocks sensitive content in .brainclaw/ and checks active constraints')
1383
- .option('--force', 'Overwrite existing pre-commit hook')
1384
- .action((options) => {
1385
- runInstallHooks(options);
1386
- });
1387
- // --- diff ---
1388
- program
1389
- .command('diff')
1390
- .description('Show what changed in .brainclaw/ since a timestamp or last context read')
1391
- .option('--since <timestamp>', 'ISO 8601 timestamp to diff from')
1392
- .option('--json', 'Output as JSON')
1393
- .action((options) => {
1394
- runDiff(options);
1395
- });
1396
- // --- prune ---
1397
- program
1398
- .command('prune')
1399
- .description('Prune expired constraints and stale memory')
1400
- .option('--expired', 'Also prune expired runtime notes and traps')
1401
- .option('--archive', 'Archive done plans and closed handoffs (>30 days) to cold storage JSONL')
1402
- .option('--semantic', 'Detect near-duplicate clusters and stale items via semantic analysis')
1403
- .option('--dry-run', 'Preview compaction without applying (use with --semantic)')
1404
- .action((options) => {
1405
- runPrune(options);
1406
- });
1407
- // --- compact ---
1408
- program
1409
- .command('compact')
1410
- .description('LLM-driven semantic memory compaction — archive old items and get a summary template')
1411
- .option('--assess', 'Show pressure assessment and compaction template without archiving')
1412
- .option('--dry-run', 'Preview eligible items without archiving')
1413
- .option('--max-items <n>', 'Maximum items to compact (default: 20)', parseInt)
1414
- .option('--min-age <days>', 'Minimum age in days for eligibility (default: 7)', parseInt)
1415
- .option('--no-dedup-handoffs', 'Skip deduplication of auto-generated session-end handoffs')
1416
- .option('--no-purge-claims', 'Skip archival of released claims')
1417
- .option('--no-purge-session-notes', 'Skip archival of session-lifecycle runtime_notes')
1418
- .action((options) => {
1419
- runCompact(options);
1420
- });
1421
- // --- mcp ---
1422
- program
1423
- .command('mcp')
1424
- .description('Start the standalone MCP server over stdio')
1425
- .action(() => {
1426
- runMcp();
1427
- });
1428
- // --- set-trust ---
1429
- program
1430
- .command('set-trust <agent>')
1431
- .description('Set the trust level for a registered agent or manage circuit-breaker state')
1432
- .option('--level <level>', 'Trust level: observer, contributor, trusted, curator')
1433
- .option('--reset-breaker', 'Reset circuit-breaker suspension for the agent (restores auto-promote)')
1434
- .option('--json', 'Output as JSON')
1435
- .action((agent, options) => {
1436
- runSetTrust(agent, { level: options.level, resetBreaker: options.resetBreaker, json: options.json });
1437
- });
1438
- // --- session-start ---
1439
- program
1440
- .command('session-start')
1441
- .description('Start a memory session and capture initial context')
1442
- .option('--agent <agent>', 'Agent name (defaults to current configured agent)')
1443
- .option('--context <path>', 'Context target path for initial hash capture')
1444
- .option('--model <id>', 'Model identifier (e.g. claude-sonnet-4-6)')
1445
- .option('--maintenance-mode <mode>', 'Maintenance mode: full (default) or fast')
1446
- .option('--include-context', 'Output full project context after starting session (replaces separate context call)')
1447
- .option('--hook', 'Hook mode: degrade to exit 0 + ~/.brainclaw/hook.log on failure (advisory session hooks)')
1448
- .option('--json', 'Output as JSON')
1449
- .action(async (options) => {
1450
- await runSessionStart(options);
1451
- });
1452
- // --- session-end ---
1453
- program
1454
- .command('session-end')
1455
- .description('End a memory session and optionally auto-reflect observations')
1456
- .option('--session <id>', 'Session ID (defaults to BRAINCLAW_SESSION_ID env var)')
1457
- .option('--agent <agent>', 'Agent name')
1458
- .option('--summary <text>', 'Session summary text')
1459
- .option('--auto-reflect', 'Auto-reflect session notes as pending candidates')
1460
- .option('--auto-release', 'Auto-release any active claims at session end')
1461
- .option('--reflect-handoff', 'Materialize an open handoff from git commits since session start')
1462
- .option('--dispatch-review', 'When used with --reflect-handoff, auto-dispatch a code review if the handoff is reviewable')
1463
- .option('--reviewer <name>', 'Explicit reviewer to route the reflected handoff review to')
1464
- .option('--no-reflect', 'Suppress the dogfooding reflection prompt (project + your surfaces/skills/tools), shown by default')
1465
- .option('--hook', 'Hook mode: degrade to exit 0 + ~/.brainclaw/hook.log on failure (advisory Stop hook)')
1466
- .option('--json', 'Output as JSON')
1467
- .action(async (options) => {
1468
- await runSessionEnd({
1469
- ...options,
1470
- autoReflect: options.autoReflect,
1471
- autoRelease: options.autoRelease,
1472
- reflectHandoff: options.reflectHandoff,
1473
- dispatchReview: options.dispatchReview,
1474
- reviewer: options.reviewer,
1475
- reflect: options.reflect,
1476
- hook: options.hook,
1477
- });
1478
- });
1479
- // --- whoami ---
1480
- program
1481
- .command('whoami')
1482
- .description('Show the current resolved agent identity and trust level')
1483
- .option('--json', 'Output as JSON')
1484
- .action((options) => {
1485
- runWhoami(options);
1486
- });
1487
- // --- usage ---
1488
- program
1489
- .command('usage')
1490
- .description('Show brainclaw context volume stats (tokens injected per agent/tool)')
1491
- .option('--agent <name>', 'Filter by agent name')
1492
- .option('--tool <name>', 'Filter by tool name')
1493
- .option('--days <n>', 'Limit to last N days', parseInt)
1494
- .option('--json', 'Output as JSON')
1495
- .action((options) => {
1496
- runUsage(options);
1497
- });
1498
- // --- search ---
1499
- program
1500
- .command('search <query>')
1501
- .description('Full-text search across project memory')
1502
- .option('--section <section>', 'Filter by section: constraints, decisions, traps, handoffs, plans')
1503
- .option('--since <timestamp>', 'ISO timestamp filter')
1504
- .option('--tag <tags...>', 'Filter by tags')
1505
- .option('--pending', 'Include pending candidates')
1506
- .option('--max-results <count>', 'Maximum results to return', parseInt)
1507
- .option('--json', 'Output as JSON')
1508
- .action((query, options) => {
1509
- runSearch(query, options);
1510
- });
1511
- // --- export ---
1512
- program
1513
- .command('export')
1514
- .description('Export memory as instructions for IDE/AI tools')
1515
- .option('--format <format>', 'Format: copilot-instructions, cursor-rules, agents-md, claude-md, gemini-md, windsurf, cline, roo, continue, openclaw, nanoclaw, nemoclaw, picoclaw, zeroclaw')
1516
- .option('--detect', 'Auto-detect agent environment and write to its native file')
1517
- .option('--all', 'Write all known agent instruction files at once (claude-md, agents-md, copilot-instructions, cursor-rules, etc.)')
1518
- .option('--write', 'Write to canonical file path instead of stdout (when --format is given); local files are gitignored by default')
1519
- .option('--include-live', 'Also write the native live companion file when the target agent supports one')
1520
- .option('--shared', 'Keep the main exported instruction file versionable instead of auto-ignoring it (companions remain local)')
1521
- .option('--output <file>', 'Write to a specific file path instead of stdout')
1522
- .option('--project <project>', 'Project namespace filter')
1523
- .option('--agent <agent>', 'Agent name for agent-layer instructions')
1524
- .action((options) => {
1525
- runExport(options);
1526
- });
1527
- program
1528
- .command('refresh')
1529
- .description('Refresh live companion files with current state (plans, claims, traps, sequences). Gitignored, safe to run frequently.')
1530
- .action(() => {
1531
- runRefresh();
1532
- });
1533
- program
1534
- .command('reconcile')
1535
- .description('Refresh machine and workspace bootstrap state after updates or onboarding on complex installs')
1536
- .option('--json', 'Output as JSON')
1537
- .option('--dry-run', 'Preview the reconciliation plan without writing machine or bootstrap state')
1538
- .option('--apply-bootstrap', 'Apply bootstrap suggestions across all selected stores after refresh')
1539
- .option('-y, --yes', 'Skip confirmation prompts for multi-store bootstrap apply')
1540
- .option('--skip-machine-profile', 'Skip machine-profile refresh')
1541
- .option('--skip-agent-inventory', 'Skip agent-inventory refresh')
1542
- .action(async (options) => {
1543
- await runReconcile({
1544
- json: options.json,
1545
- dryRun: options.dryRun,
1546
- applyBootstrap: options.applyBootstrap,
1547
- yes: options.yes,
1548
- skipMachineProfile: options.skipMachineProfile,
1549
- skipAgentInventory: options.skipAgentInventory,
1550
- });
1551
- });
1552
- // --- hooks ---
1553
- program
1554
- .command('hooks')
1555
- .description('Write deterministic session-trigger hooks for Cursor, Windsurf, and Claude Code (PostToolUse event check)')
1556
- .option('--target <target>', 'Which hooks to write: cursor, windsurf, claude-code, all (default: all)')
1557
- .action((options) => {
1558
- runHooks(options);
1559
- });
1560
- // --- watch ---
1561
- program
1562
- .command('watch')
1563
- .description('Watch for memory changes and emit NDJSON events on stdout')
1564
- .option('--interval <seconds>', 'Poll interval in seconds', parseInt)
1565
- .option('--auto-claim', 'Auto-create advisory claims on first write to workspace files')
1566
- .option('--agent <name>', 'Agent name for auto-claim')
1567
- .action((options) => {
1568
- runWatch({ ...options, autoClaim: options.autoClaim });
1569
- });
1570
- // --- dispatch ---
1571
- const dispatchCmd = program
1572
- .command('dispatch')
1573
- .description('Local agent dispatcher — analyze lanes and assign work');
1574
- dispatchCmd
1575
- .command('analysis')
1576
- .description('Analyze the active sequence: show ready, active, blocked, and done lanes')
1577
- .option('--json', 'Output as JSON')
1578
- .action((options) => {
1579
- runDispatchAnalysis({ json: options.json });
1580
- });
1581
- dispatchCmd
1582
- .command('run')
1583
- .description('Run a dispatch cycle: assign ready lanes to available agents')
1584
- .option('--agents <names>', 'Comma-separated list of agents to dispatch to')
1585
- .option('--lanes <names>', 'Comma-separated list of lanes to dispatch')
1586
- .option('--max <n>', 'Maximum assignments', parseInt)
1587
- .option('--max-concurrency <n>', 'Opt-in cap on concurrent instances per host-binary (default: unlimited)', parseInt)
1588
- .option('--model <name>', 'Model to run, decoupled from agent identity (e.g. --model sonnet)')
1589
- .option('--dry', 'Preview assignments without sending messages')
1590
- .option('--spawn', 'Autonomously launch CLI agents with invoke templates')
1591
- .option('--agent <name>', 'Dispatcher agent name')
1592
- .option('--json', 'Output as JSON')
1593
- .action(async (options) => {
1594
- await runDispatch({
1595
- agents: options.agents,
1596
- lanes: options.lanes,
1597
- max: options.max,
1598
- maxConcurrency: options.maxConcurrency,
1599
- model: options.model,
1600
- dry: options.dry,
1601
- spawn: options.spawn,
1602
- agent: options.agent,
1603
- json: options.json,
1604
- });
1605
- });
1606
- dispatchCmd
1607
- .command('watch <target>')
1608
- .description('Block until a dispatched worker reaches a terminal state (asgn_/clm_/run_ id) — sentinels, lane-result, committed-clean and worker-process-gone heuristics')
1609
- .option('--interval <seconds>', 'Poll interval in seconds (default 60)', parseInt)
1610
- .option('--timeout <minutes>', 'Give up after N minutes (default 90, exit code 2)', parseInt)
1611
- .option('--base <ref>', 'Base ref for commits-ahead evidence (default master)')
1612
- .option('--json', 'One JSON object per poll line')
1613
- .action(async (target, options) => {
1614
- await runDispatchWatch(target, {
1615
- intervalSeconds: options.interval,
1616
- timeoutMinutes: options.timeout,
1617
- base: options.base,
1618
- json: options.json,
1619
- });
1620
- });
1621
- dispatchCmd
1622
- .command('review')
1623
- .description('Dispatch code reviews for completed handoffs')
1624
- .option('--handoff <id>', 'Specific handoff ID to review')
1625
- .option('--reviewer <name>', 'Specific reviewer agent')
1626
- .option('--spawn', 'Launch the reviewer CLI agent')
1627
- .option('--dry', 'Preview without sending')
1628
- .option('--agent <name>', 'Dispatcher agent name')
1629
- .option('--json', 'Output as JSON')
1630
- .action((options) => {
1631
- runDispatchReview({
1632
- handoff: options.handoff,
1633
- reviewer: options.reviewer,
1634
- spawn: options.spawn,
1635
- dry: options.dry,
1636
- agent: options.agent,
1637
- json: options.json,
1638
- });
1639
- });
1640
- // --- inbox ---
1641
- const inboxCmd = program
1642
- .command('inbox')
1643
- .description('Inter-agent messaging inbox');
1644
- inboxCmd
1645
- .command('list')
1646
- .description('List inbox messages (default: pending only)')
1647
- .option('--agent <name>', 'Agent name')
1648
- .option('--status <status>', 'Filter by status: pending, read, acknowledged, archived')
1649
- .option('--type <type>', 'Filter by type: assign, review, rfc, info, reply')
1650
- .option('--thread <id>', 'Filter by thread ID')
1651
- .option('--all', 'Show all messages, not just pending')
1652
- .option('--json', 'Output as JSON')
1653
- .option('--local-only', 'Read from local store only (skip parent stores in chain)')
1654
- .action((options) => {
1655
- runInboxList({ ...options, localOnly: options.localOnly });
1656
- });
1657
- inboxCmd
1658
- .command('ack <id>')
1659
- .description('Acknowledge a message')
1660
- .option('--agent <name>', 'Agent name')
1661
- .option('--json', 'Output as JSON')
1662
- .action((id, options) => {
1663
- runInboxAck(id, options);
1664
- });
1665
- inboxCmd
1666
- .command('archive <id>')
1667
- .description('Archive a message')
1668
- .option('--agent <name>', 'Agent name')
1669
- .option('--json', 'Output as JSON')
1670
- .action((id, options) => {
1671
- runInboxArchive(id, options);
1672
- });
1673
- inboxCmd
1674
- .command('send <to> <text>')
1675
- .description('Send a message to another agent')
1676
- .option('--type <type>', 'Message type: assign, review, rfc, info, reply (default: info)')
1677
- .option('--ref <id>', 'Reference to a plan, sequence, or other entity')
1678
- .option('--scope <path>', 'File scope')
1679
- .option('--thread <id>', 'Thread ID for conversations')
1680
- .option('--ack', 'Require acknowledgment')
1681
- .option('--agent <name>', 'Sender agent name')
1682
- .option('--json', 'Output as JSON')
1683
- .action((to, text, options) => {
1684
- runInboxSend(to, text, options);
1685
- });
1686
- inboxCmd
1687
- .command('thread <id>')
1688
- .description('Show all messages in a thread')
1689
- .option('--json', 'Output as JSON')
1690
- .action((id, options) => {
1691
- runInboxThread(id, options);
1692
- });
1693
- // --- check-events ---
1694
- program
1695
- .command('check-events')
1696
- .description('Show unseen events from the event bus (events.jsonl) for the current agent')
1697
- .option('--agent <name>', 'Agent name for cursor lookup (default: auto-detected)')
1698
- .option('--json', 'Output as JSON')
1699
- .action((options) => {
1700
- runCheckEvents(options);
1701
- });
1702
- // --- metrics ---
1703
- program
1704
- .command('metrics')
1705
- .description('Show memory health metrics dashboard')
1706
- .option('--json', 'Output as JSON')
1707
- .option('--since <timestamp>', 'Filter audit log stats from this ISO timestamp')
1708
- .action((options) => {
1709
- runMetrics(options);
1710
- });
1711
- // --- rollback ---
1712
- program
1713
- .command('rollback')
1714
- .description('Restore a memory item to a previous state from audit log')
1715
- .option('--audit-id <timestamp>', 'Audit log entry timestamp to roll back')
1716
- .option('--item-id <id>', 'Memory item ID to roll back (uses most recent audit entry with before-state)')
1717
- .option('--dry-run', 'Preview rollback without applying changes')
1718
- .option('--json', 'Output as JSON')
1719
- .action((options) => {
1720
- runRollback({ auditId: options.auditId, itemId: options.itemId, dryRun: options.dryRun, json: options.json });
1721
- });
1722
- // --- pull ---
1723
- program
1724
- .command('pull')
1725
- .description('Pull memory updates from a git remote')
1726
- .option('--remote <remote>', 'Remote name (defaults to origin)')
1727
- .option('--json', 'Output as JSON')
1728
- .action((options) => {
1729
- runPull(options);
1730
- });
1731
- // --- push ---
1732
- program
1733
- .command('push')
1734
- .description('Push memory updates to a git remote')
1735
- .option('--remote <remote>', 'Remote name (defaults to origin)')
1736
- .option('--message <message>', 'Custom commit message')
1737
- .option('--json', 'Output as JSON')
1738
- .action((options) => {
1739
- runPush(options);
1740
- });
1741
- // --- audit ---
1742
- program
1743
- .command('audit')
1744
- .description('View the append-only audit log, or generate a governance posture report with --governance')
1745
- .option('--since <date>', 'Show entries since this ISO date')
1746
- .option('--actor <agent>', 'Filter by actor name or agent ID')
1747
- .option('--action <action>', 'Filter by action type (create, accept, reject, etc.)')
1748
- .option('--limit <n>', 'Show last N entries', parseInt)
1749
- .option('--json', 'Output as JSON')
1750
- .option('--governance', 'Generate a governance posture report (aggregated view of claims, constraints, traps, instructions)')
1751
- .option('--scope <path>', 'Filter governance report by scope (used with --governance)')
1752
- .action((options) => {
1753
- runAuditCommand({ since: options.since, actor: options.actor, action: options.action, limit: options.limit, json: options.json, governance: options.governance, scope: options.scope });
1754
- });
1755
- // --- history ---
1756
- program
1757
- .command('history <id>')
1758
- .description('Show full mutation history of a memory item from the audit log')
1759
- .action((id) => {
1760
- runHistory(id);
1761
- });
1762
- // --- context-diff ---
1763
- program
1764
- .command('context-diff')
1765
- .description('Show what changed in memory since last context read, a session start, or a given timestamp')
1766
- .option('--since <date>', 'Show changes since this ISO date')
1767
- .option('--session <id>', 'Show changes since the start of this session')
1768
- .option('--hook', 'Hook mode: exit 0 silently when there is no diff baseline (advisory session hooks)')
1769
- .option('--json', 'Output as JSON')
1770
- .action((options) => {
1771
- runContextDiff({ since: options.since, session: options.session, json: options.json, hook: options.hook });
1772
- });
1773
- program
1774
- .command('capability <subcommand> [args...]')
1775
- .description('Manage project capabilities (list, add, describe)')
1776
- .option('--tag <tag>', 'Tag for categorization (repeatable)', (val, prev) => [...(prev || []), val])
1777
- .option('--author <name>', 'Author name')
1778
- .option('--store <target>', 'Store level: local (default), repo, workspace, user')
1779
- .action((subcommand, args, options) => {
1780
- runCapability(subcommand, args, {
1781
- tag: options.tag,
1782
- author: options.author,
1783
- store: options.store,
1784
- });
1785
- });
1786
- program
1787
- .command('link <subcommand> [args...]')
1788
- .description('Manage cross-project federation links (add, list, remove)')
1789
- .option('--name <slug>', 'Override the auto-derived link name')
1790
- .option('--role <role>', 'Link role: publisher (push signals out) or subscriber (default)')
1791
- .option('--channels <list>', 'Comma-separated allow-list of channels: candidate,handoff,runtime_note', (val) => val.split(',').map((s) => s.trim()).filter(Boolean))
1792
- .option('--force', 'Replace an existing link of the same name/path')
1793
- .option('--json', 'Output as JSON')
1794
- .option('--store <target>', 'Store level: local (default), repo, workspace, user')
1795
- .action((subcommand, args, options) => {
1796
- runLink(subcommand, args, {
1797
- name: options.name,
1798
- role: options.role,
1799
- channels: options.channels,
1800
- force: options.force,
1801
- json: options.json,
1802
- store: options.store,
1803
- });
1804
- });
1805
- program
1806
- .command('tool <subcommand> [args...]')
1807
- .description('Manage project tools (list, add, describe, search)')
1808
- .option('--tag <tag>', 'Tag for categorization (repeatable)', (val, prev) => [...(prev || []), val])
1809
- .option('--type <type>', 'Tool type: workflow, validator, generator, utility, explorer')
1810
- .option('--author <name>', 'Author name')
1811
- .option('--store <target>', 'Store level: local (default), repo, workspace, user')
1812
- .action((subcommand, args, options) => {
1813
- runTool(subcommand, args, {
1814
- tag: options.tag,
1815
- type: options.type,
1816
- author: options.author,
1817
- store: options.store,
1818
- });
1819
- });
1820
- program
1821
- .command('explore')
1822
- .description('Explore project capabilities and available tools')
1823
- .option('--query <q>', 'Search for specific capability or tool')
1824
- .action((options) => {
1825
- runExplore({ query: options.query });
1826
- });
1827
- // --- discover ---
1828
- program
1829
- .command('discover')
1830
- .description('Scan workspace for MCP configs, instruction files, skills, hooks, and agent integrations')
1831
- .option('--json', 'Output as JSON')
1832
- .option('--no-save', 'Do not persist discovery profile to .brainclaw/discovery/')
1833
- .action((options) => {
1834
- runDiscover({ json: options.json, save: options.save });
1835
- });
1836
- // --- migrate ---
1837
- program
1838
- .command('migrate')
1839
- .description('Migrate memory items between stores (e.g. promote machine-scoped items to user store)')
1840
- .option('--promote-machine-items', 'Move items with scope:machine from project store to user store (~/.brainclaw/)')
1841
- .option('--enable-journal', 'Turn on the event journal (mode=dual) for this existing store and backfill it (pln#567)')
1842
- .option('--dry-run', 'Show what would be done without writing')
1843
- .action((options) => {
1844
- runMigrate({ promoteMachineItems: options.promoteMachineItems, enableJournal: options.enableJournal, dryRun: options.dryRun });
1845
- });
1846
- program
1847
- .command('switch [project]')
1848
- .description('Set the active project for subsequent commands (session-scoped by default)')
1849
- .option('--list', 'List available projects in the workspace')
1850
- .option('--clear', 'Clear the active project (revert to cwd)')
1851
- .option('--global', 'Set/clear the SHARED workspace default for ALL agents (writes active-project.json). Without it, switch is session-scoped and isolated.')
1852
- .option('--json', 'Output as JSON')
1853
- .action((project, options) => {
1854
- const globalOpts = options.parent?.parent ? program.opts() : {};
1855
- runSwitch(project, {
1856
- list: options.list,
1857
- clear: options.clear,
1858
- global: options.global,
1859
- json: options.json,
1860
- cwd: globalOpts.cwd,
1861
- });
1862
- });
1863
- program
1864
- .command('who')
1865
- .description('Show active agent sessions on this workspace')
1866
- .option('--json', 'Output as JSON')
1867
- .option('--all', 'Include stale sessions')
1868
- .option('--gc', 'Remove stale sessions')
1869
- .option('--local-only', 'Read claims from local store only (skip parent stores in chain)')
1870
- .action(async (options) => {
1871
- const globalOpts = program.opts();
1872
- const { runWho } = await import('./commands/who.js');
1873
- runWho({ json: options.json, all: options.all, gc: options.gc, cwd: globalOpts.cwd, localOnly: options.localOnly });
1874
- });
1875
- const worktreeCmd = program
1876
- .command('worktree')
1877
- .description('Manage git worktrees for parallel agent isolation');
1878
- worktreeCmd
1879
- .command('create <branch>')
1880
- .description('Create a linked git worktree for a given branch')
1881
- .option('--session-id <id>', 'Associate this worktree with a brainclaw session')
1882
- .option('--agent <name>', 'Associate this worktree with an agent name')
1883
- .action((branch, options) => {
1884
- const globalOpts = program.opts();
1885
- runWorktreeCreate({ branch, sessionId: options.sessionId, agent: options.agent, cwd: globalOpts.cwd });
1886
- });
1887
- worktreeCmd
1888
- .command('list')
1889
- .description('List all git worktrees for this project')
1890
- .action(() => {
1891
- const globalOpts = program.opts();
1892
- runWorktreeList({ cwd: globalOpts.cwd });
1893
- });
1894
- worktreeCmd
1895
- .command('remove <path>')
1896
- .description('Remove a linked git worktree')
1897
- .option('--force', 'Force removal even with uncommitted changes')
1898
- .action((worktreePath, options) => {
1899
- const globalOpts = program.opts();
1900
- runWorktreeRemove({ path: worktreePath, force: options.force, cwd: globalOpts.cwd });
1901
- });
1902
- worktreeCmd
1903
- .command('prune')
1904
- .description('Prune stale worktree administrative files')
1905
- .action(() => {
1906
- const globalOpts = program.opts();
1907
- runWorktreePrune({ cwd: globalOpts.cwd });
1908
- });
1909
- worktreeCmd
1910
- .command('clean')
1911
- .description('Remove worktrees whose branch is fully merged and orphan worktree directories')
1912
- .option('--force', 'Force removal even with uncommitted changes')
1913
- .option('--dry-run', 'Show what would be removed without actually removing')
1914
- .action((options) => {
1915
- const globalOpts = program.opts();
1916
- runWorktreeClean({ force: options.force, dryRun: options.dryRun, cwd: globalOpts.cwd });
1917
- });
1918
- worktreeCmd
1919
- .command('merge <branch>')
1920
- .description('Merge a worktree branch with auto-restoration of parasitic deletions')
1921
- .option('-m, --message <message>', 'Merge commit message')
1922
- .option('--dry-run', 'Show what would be merged without committing')
1923
- .action((branch, options) => {
1924
- const globalOpts = program.opts();
1925
- runWorktreeMerge({ branch, message: options.message, dryRun: options.dryRun, cwd: globalOpts.cwd });
1926
- });
1927
- worktreeCmd
1928
- .command('check')
1929
- .description('Pre-merge conflict detection: which parallel lanes touch overlapping files, and who owns them (exit 3 if overlaps found)')
1930
- .option('--base <ref>', 'Base ref each lane is diffed against (default: current branch)')
1931
- .option('--json', 'Emit the full risk report as JSON')
1932
- .action((options) => {
1933
- const globalOpts = program.opts();
1934
- runWorktreeCheck({ baseRef: options.base, json: options.json, cwd: globalOpts.cwd });
1935
- });
1936
- // --- federation cloud ---
1937
- const federationCmd = program
1938
- .command('federation')
1939
- .description('Cloud federation — sync signals with app.brainclaw.dev');
1940
- federationCmd
1941
- .command('push <message>')
1942
- .description('Push a test signal to the cloud')
1943
- .option('--type <type>', 'Signal type', 'runtime_note')
1944
- .option('--to-project <project>', 'Target project name')
1945
- .option('--to-agent <agent>', 'Target agent name')
1946
- .action(async (message, options) => {
1947
- const { pushSignalToCloud, isCloudConfigured } = await import('./core/federation-cloud.js');
1948
- const { createFederationMessage } = await import('./core/federation-message.js');
1949
- const { loadConfig } = await import('./core/config.js');
1950
- const { resolveCurrentAgentName } = await import('./core/agent-registry.js');
1951
- if (!isCloudConfigured()) {
1952
- console.error('Error: cloud not configured. Set BRAINCLAW_CLOUD_API_KEY env var.');
1953
- process.exit(1);
1954
- }
1955
- const config = loadConfig();
1956
- const agent = resolveCurrentAgentName() ?? 'unknown';
1957
- const msg = createFederationMessage({
1958
- version: 1,
1959
- from: { project_name: config.project_name, project_path: process.cwd(), agent_name: agent },
1960
- to: { project_name: options.toProject ?? 'broadcast', project_path: '' },
1961
- type: options.type,
1962
- payload: { text: message },
1963
- });
1964
- const ok = await pushSignalToCloud(msg);
1965
- if (ok) {
1966
- console.log(`✔ Signal pushed to cloud: [${msg.id}] ${message}`);
1967
- }
1968
- else {
1969
- console.error('Error: failed to push signal to cloud.');
1970
- process.exit(1);
1971
- }
1972
- });
1973
- federationCmd
1974
- .command('pull')
1975
- .description('Pull signals from the cloud inbox')
1976
- .option('--agent <name>', 'Agent name to pull for')
1977
- .option('--since <date>', 'Only pull signals after this ISO date')
1978
- .option('--limit <n>', 'Max signals to pull', '20')
1979
- .action(async (options) => {
1980
- const { pullSignalsFromCloud, isCloudConfigured } = await import('./core/federation-cloud.js');
1981
- const { resolveCurrentAgentName } = await import('./core/agent-registry.js');
1982
- if (!isCloudConfigured()) {
1983
- console.error('Error: cloud not configured. Set BRAINCLAW_CLOUD_API_KEY env var.');
1984
- process.exit(1);
1985
- }
1986
- const agent = options.agent ?? resolveCurrentAgentName() ?? 'unknown';
1987
- const signals = await pullSignalsFromCloud(agent, {
1988
- since: options.since,
1989
- limit: parseInt(options.limit, 10),
1990
- });
1991
- if (signals.length === 0) {
1992
- console.log('No signals in cloud inbox.');
1993
- return;
1994
- }
1995
- console.log(`${signals.length} signal(s) from cloud:\n`);
1996
- for (const s of signals) {
1997
- const payload = typeof s.payload === 'object' && s.payload !== null ? s.payload.text ?? JSON.stringify(s.payload) : String(s.payload);
1998
- console.log(` [${s.id}] ${s.type} from ${s.from.project_name}/${s.from.agent_name}`);
1999
- console.log(` ${String(payload).slice(0, 120)}`);
2000
- console.log(` ${s.created_at}\n`);
2001
- }
2002
- });
2003
- federationCmd
2004
- .command('status')
2005
- .description('Check cloud federation configuration')
2006
- .action(async () => {
2007
- const { isCloudConfigured } = await import('./core/federation-cloud.js');
2008
- const url = process.env.BRAINCLAW_CLOUD_URL ?? 'https://app.brainclaw.dev';
2009
- console.log(`Cloud URL: ${url}`);
2010
- console.log(`API Key: ${process.env.BRAINCLAW_CLOUD_API_KEY ? '***configured***' : 'NOT SET'}`);
2011
- console.log(`Configured: ${isCloudConfigured() ? 'yes' : 'no'}`);
2012
- if (isCloudConfigured()) {
2013
- try {
2014
- const res = await fetch(`${url}/api/v1/health`);
2015
- const data = await res.json();
2016
- console.log(`Cloud status: ${data.status} (v${data.version})`);
2017
- }
2018
- catch (e) {
2019
- console.error(`Cloud unreachable: ${e.message}`);
2020
- }
2021
- }
2022
- });
2023
- // --- codev (legacy experimental) ---
2024
- if (isCodevEnabled()) {
2025
- program
2026
- .command('codev [topic]')
2027
- .description('Experimental legacy ideation session using persona-based consultation')
2028
- .option('--personas <tier>', 'Persona tier: tier1 (default), tier2, or list', 'tier1')
2029
- .option('--checkpoint', 'Pause after clarification for human input')
2030
- .option('--spawn', 'Spawn each consultant as an agent CLI instance')
2031
- .option('--fresh', 'Clear cached responses before starting a new run')
2032
- .option('--agents <list>', 'Comma-separated agent names for spawn (e.g. claude-code,codex,antigravity). Default: auto-detect')
2033
- .option('--rounds <N>', 'Number of discussion rounds in spawn mode (default 3, min 2)', '3')
2034
- .option('--target-duration <seconds>', 'Target duration per round indicated to agents (default 120)', '120')
2035
- .option('--quorum <N>', 'Advance to next round after N agent responses (default: all)')
2036
- .option('--model-map <map>', 'Per-persona model overrides, e.g. simplificateur:sonnet,stratege:opus')
2037
- .option('--metrics', 'Display response timing metrics at end of session')
2038
- .option('--json', 'Output as JSON')
2039
- .action(async (topic, options) => {
2040
- const globalOpts = program.opts();
2041
- const { runCodev } = await import('./commands/codev.js');
2042
- runCodev(topic, {
2043
- ...options,
2044
- rounds: parseInt(options.rounds, 10),
2045
- targetDuration: parseInt(options.targetDuration, 10),
2046
- quorum: options.quorum != null ? parseInt(options.quorum, 10) : undefined,
2047
- cwd: globalOpts.cwd,
2048
- });
2049
- });
2050
- program
2051
- .command('codev-metrics <thread>')
2052
- .description('Show per-agent avg/p95 response metrics for an experimental CoDev thread')
2053
- .option('--json', 'Output as JSON')
2054
- .action(async (thread, options) => {
2055
- const globalOpts = program.opts();
2056
- const { runCodevMetrics } = await import('./commands/codev.js');
2057
- runCodevMetrics(thread, { ...options, cwd: globalOpts.cwd });
2058
- });
373
+ process.exit(0);
2059
374
  }
2060
- // --- questions (operator-question artifacts across loops; pln#508 step 4) ---
2061
- program
2062
- .command('questions')
2063
- .description('List pending operator_question artifacts across loops in the current project')
2064
- .option('--loop <loop_id>', 'Filter to a single loop')
2065
- .option('--status <status>', 'Filter by status: awaiting (default), answered, timed_out', 'awaiting')
2066
- .option('--mine', 'Filter to questions targeted at the current agent (v1 heuristic: humans see all awaiting)')
2067
- .option('--json', 'Output as JSON')
2068
- .action((options) => {
2069
- const globalOpts = program.opts();
2070
- const status = options.status;
2071
- if (!['awaiting', 'answered', 'timed_out'].includes(status)) {
2072
- console.error(`Error: --status must be one of awaiting|answered|timed_out (got "${options.status}")`);
2073
- process.exit(1);
2074
- }
2075
- runQuestionsCommand({
2076
- loop: options.loop,
2077
- status,
2078
- mine: options.mine,
2079
- json: options.json,
2080
- }, globalOpts.cwd);
2081
- });
2082
- // --- bootstrap-loop (open/join/status/cancel a bootstrap loop; pln#513 step 3) ---
2083
- program
2084
- .command('bootstrap-loop')
2085
- .description('Open or join a bootstrap loop on the current project, or query its status')
2086
- .option('--status', 'Report current state')
2087
- .option('--cancel', 'Cancel the active bootstrap loop')
2088
- .option('--yes', 'Skip confirmation prompts')
2089
- .option('--json', 'Machine-readable output')
2090
- .action(async (options) => {
2091
- const globalOpts = program.opts();
2092
- const { runBootstrapLoopCommand } = await import('./commands/bootstrap-loop.js');
2093
- await runBootstrapLoopCommand(options, globalOpts.cwd);
2094
- });
2095
- // --- loop (drive loop turn verbs; pln#517 step 2) ---
2096
- const loopCmd = program
2097
- .command('loop')
2098
- .description('Drive a loop turn (turn / complete-turn / advance / add-artifact)');
2099
- loopCmd
2100
- .command('turn <loop_id>')
2101
- .description('Issue a turn assignment on a slot')
2102
- .requiredOption('--slot <slot_id>', 'Target slot id (lsl_...)')
2103
- .option('--input <text>', 'Free-form input passed to the slot')
2104
- .option('--role <role>', 'Slot role (resolves the first non-done slot with that role)')
2105
- .option('--assignment-id <id>', 'Dispatcher-provided assignment id to record on the slot')
2106
- .option('--json', 'Machine-readable output')
2107
- .action(async (loop_id, options) => {
2108
- const globalOpts = program.opts();
2109
- const { runLoopCommand } = await import('./commands/loop.js');
2110
- await runLoopCommand('turn', { loop_id }, options, globalOpts.cwd);
2111
- });
2112
- loopCmd
2113
- .command('complete-turn <loop_id>')
2114
- .description('Complete a slot turn')
2115
- .requiredOption('--slot <slot_id>', 'Target slot id (lsl_...)')
2116
- .requiredOption('--outcome <outcome>', 'Turn outcome: done, failed, or cancelled')
2117
- .option('--failure-reason <text>', 'Reason when outcome is failed')
2118
- .option('--artifact <json>', 'JSON object payload for an artifact to attach')
2119
- .option('--json', 'Machine-readable output')
2120
- .action(async (loop_id, options) => {
2121
- const globalOpts = program.opts();
2122
- const { runLoopCommand } = await import('./commands/loop.js');
2123
- await runLoopCommand('complete-turn', { loop_id }, options, globalOpts.cwd);
2124
- });
2125
- loopCmd
2126
- .command('advance <loop_id>')
2127
- .description('Advance a loop to its next phase')
2128
- .option('--to-phase <name>', 'Explicit target phase')
2129
- .option('--force', 'Bypass phase gate checks')
2130
- .option('--reason <text>', 'Reason to record on the phase advance event')
2131
- .option('--json', 'Machine-readable output')
2132
- .action(async (loop_id, options) => {
2133
- const globalOpts = program.opts();
2134
- const { runLoopCommand } = await import('./commands/loop.js');
2135
- await runLoopCommand('advance', { loop_id }, options, globalOpts.cwd);
2136
- });
2137
- loopCmd
2138
- .command('add-artifact <loop_id>')
2139
- .description('Attach an artifact to a loop')
2140
- .requiredOption('--phase <phase>', 'Artifact phase')
2141
- .requiredOption('--type <type>', 'Artifact type')
2142
- .requiredOption('--body <json-or-text>', 'Artifact body as JSON or text')
2143
- .option('--produced-by <agent>', 'Agent that produced the artifact')
2144
- .option('--ref <ref>', 'JSON ref object, e.g. {"kind":"plan","id":"pln_..."}')
2145
- .option('--json', 'Machine-readable output')
2146
- .action(async (loop_id, options) => {
2147
- const globalOpts = program.opts();
2148
- const { runLoopCommand } = await import('./commands/loop.js');
2149
- await runLoopCommand('add-artifact', { loop_id }, options, globalOpts.cwd);
2150
- });
2151
- // --- reply (provide_input to an operator_question; pln#508 step 4) ---
2152
- program
2153
- .command('reply <qst_id>')
2154
- .description('Resolve an operator_question artifact (wraps bclaw_loop.provide_input)')
2155
- .option('--answer <text>', 'Free-form answer text')
2156
- .option('--choose <option_id>', 'Pick one of the question\'s structured options[].id')
2157
- .option('--skip', 'Materialize the question\'s suggested_default')
2158
- .option('--json', 'Output as JSON')
2159
- .action((qstId, options) => {
2160
- const globalOpts = program.opts();
2161
- runReplyCommand(qstId, {
2162
- answer: options.answer,
2163
- choose: options.choose,
2164
- skip: options.skip,
2165
- json: options.json,
2166
- }, globalOpts.cwd);
2167
- });
2168
- // --- run (agent profiles) ---
2169
- program
2170
- .command('run [profile-name]')
2171
- .description('Run an agent profile (list profiles if no name given)')
2172
- .option('--dry', 'Print the resolved command without executing')
2173
- .option('--agent <agent>', 'Override the invoke template with a known agent')
2174
- .action((profileName, options) => {
2175
- const globalOpts = program.opts();
2176
- runRunProfile(profileName, { ...options, cwd: globalOpts.cwd });
2177
- });
2178
375
  {
2179
376
  // Friendly trailing-global-option error must run before Commander parses:
2180
377
  // with positional options enabled, Commander itself would reject a trailing