brainclaw 1.15.0 → 1.17.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 (59) hide show
  1. package/README.md +25 -4
  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 -2183
  13. package/dist/commands/dispatch-watch.js +25 -2
  14. package/dist/commands/harvest.js +107 -20
  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 -5584
  27. package/dist/commands/update-handoff.js +28 -42
  28. package/dist/core/agent-capability.js +38 -16
  29. package/dist/core/agent-files.js +54 -3
  30. package/dist/core/agent-integrations.js +1 -0
  31. package/dist/core/coordination.js +5 -2
  32. package/dist/core/cross-project.js +35 -1
  33. package/dist/core/dispatcher.js +67 -27
  34. package/dist/core/entity-operations.js +335 -12
  35. package/dist/core/entity-registry.js +72 -9
  36. package/dist/core/execution.js +28 -4
  37. package/dist/core/facade-schema.js +18 -4
  38. package/dist/core/handoff-review.js +35 -0
  39. package/dist/core/protocol-tool-policy.js +113 -0
  40. package/dist/core/review-loop-close.js +184 -0
  41. package/dist/core/review-loop-turn-dispatch.js +183 -0
  42. package/dist/core/schema.js +24 -2
  43. package/dist/core/security-detectors.js +35 -6
  44. package/dist/core/security.js +32 -12
  45. package/dist/core/worktree.js +274 -12
  46. package/dist/facts.js +13 -11
  47. package/dist/facts.json +12 -10
  48. package/docs/PROTOCOL.md +7 -3
  49. package/docs/concepts/coordinator-runbook.md +3 -0
  50. package/docs/concepts/dispatch-lifecycle.md +4 -4
  51. package/docs/concepts/loop-engine.md +6 -2
  52. package/docs/concepts/troubleshooting.md +1 -1
  53. package/docs/integrations/codex.md +22 -6
  54. package/docs/integrations/overview.md +1 -1
  55. package/docs/mcp-schema-changelog.md +137 -2
  56. package/docs/playbooks/orchestration.md +1 -1
  57. package/docs/product/entity-model-audit.md +3 -2
  58. package/docs/security.md +22 -1
  59. package/package.json +3 -1
@@ -0,0 +1,472 @@
1
+ import { runClaimResource } from '../commands/claim-resource.js';
2
+ import { runAssignmentResource } from '../commands/assignment-resource.js';
3
+ import { runListClaims } from '../commands/list-claims.js';
4
+ import { runReleaseClaim } from '../commands/release-claim.js';
5
+ import { runReleaseClaims } from '../commands/release-claims.js';
6
+ import { runAgentBoard } from '../commands/agent-board.js';
7
+ import { runWatch } from '../commands/watch.js';
8
+ import { runDispatchAnalysis, runDispatch, runDispatchReview } from '../commands/dispatch.js';
9
+ import { runDispatchWatch } from '../commands/dispatch-watch.js';
10
+ import { runInboxList, runInboxAck, runInboxArchive, runInboxSend, runInboxThread } from '../commands/inbox.js';
11
+ import { runCheckEvents } from '../commands/check-events.js';
12
+ import { runWorktreeCreate, runWorktreeList, runWorktreeRemove, runWorktreePrune, runWorktreeClean, runWorktreeMerge, runWorktreeCheck } from '../commands/worktree.js';
13
+ import { runQuestionsCommand } from '../commands/questions.js';
14
+ import { runReplyCommand } from '../commands/reply.js';
15
+ import { runRunProfile } from '../commands/run-profile.js';
16
+ function isCodevEnabled() {
17
+ return process.env.BRAINCLAW_ENABLE_CODEV === '1';
18
+ }
19
+ export function registerCoordinationCommands(program) {
20
+ // --- claim ---
21
+ program
22
+ .command('claim <subcommand> [args...]')
23
+ .description('Manage work claims (create, list, release)')
24
+ .option('--agent <agent>', 'Agent or person name; defaults to the configured current agent')
25
+ .option('--scope <scope>', 'Scope being claimed (e.g. file path, module)')
26
+ .option('--project <project>', 'Optional project namespace for this claim')
27
+ .option('--plan <id>', 'Optional linked plan item ID')
28
+ .option('--ttl <duration>', 'Auto-expire after duration: 30m, 2h, 8h, 1d')
29
+ .option('--all', 'Include released claims in list')
30
+ .option('--json', 'Output as JSON for list')
31
+ .option('--plan-status <status>', 'Optional linked plan status when releasing: todo, in_progress, blocked, done, dropped')
32
+ .option('--coordinator-override', 'Trusted+ only: release a claim owned by another agent')
33
+ .option('--store <target>', 'Target store level: local (default), repo, workspace')
34
+ .option('--local-only', 'Read from local store only for list (skip parent stores in chain)')
35
+ .action((subcommand, args, options) => {
36
+ runClaimResource(subcommand, args, {
37
+ ...options,
38
+ planStatus: options.planStatus,
39
+ coordinatorOverride: options.coordinatorOverride,
40
+ localOnly: options.localOnly,
41
+ });
42
+ });
43
+ // --- assignment ---
44
+ program
45
+ .command('assignment <subcommand> [args...]')
46
+ .description('Manage work assignments (list, show, update, cancel)')
47
+ .option('--json', 'Output as JSON for list/show')
48
+ .option('--all', 'Include terminal assignments in list')
49
+ .option('--status <status>', 'Status filter for list or target status for update')
50
+ .option('--agent <agent>', 'Filter by agent name')
51
+ .option('--claim <id>', 'Filter by linked claim ID')
52
+ .option('--plan <id>', 'Filter by linked plan ID')
53
+ .option('--sequence <id>', 'Filter by linked sequence ID')
54
+ .option('--reason <text>', 'Optional status reason for update/cancel')
55
+ .action((subcommand, args, options) => {
56
+ runAssignmentResource(subcommand, args, {
57
+ ...options,
58
+ claim: options.claim,
59
+ plan: options.plan,
60
+ sequence: options.sequence,
61
+ });
62
+ });
63
+ // --- list-claims ---
64
+ program
65
+ .command('list-claims')
66
+ .description('List work claims')
67
+ .option('--json', 'Output as JSON')
68
+ .option('--all', 'Include released claims')
69
+ .option('--project <project>', 'Filter by project namespace')
70
+ .option('--plan <id>', 'Filter by linked plan item')
71
+ .option('--agent <agent>', 'Filter by agent name')
72
+ .option('--local-only', 'Read from local store only (skip parent stores in chain)')
73
+ .action((options) => {
74
+ runListClaims({ ...options, localOnly: options.localOnly });
75
+ });
76
+ // --- release-claim ---
77
+ program
78
+ .command('release-claim <id>')
79
+ .description('Release a work claim')
80
+ .option('--plan-status <status>', 'Optional linked plan status: todo, in_progress, blocked, done, dropped')
81
+ .option('--coordinator-override', 'Trusted+ only: release a claim owned by another agent')
82
+ .action((id, options) => {
83
+ runReleaseClaim(id, { ...options, coordinatorOverride: options.coordinatorOverride });
84
+ });
85
+ // --- release-claims ---
86
+ program
87
+ .command('release-claims')
88
+ .description('Bulk-release claims whose scope overlaps with git-changed files')
89
+ .option('--from-git-diff', 'Use ORIG_HEAD..HEAD diff to detect changed files (post-merge)')
90
+ .option('--ref1 <ref>', 'First git ref (default: ORIG_HEAD)')
91
+ .option('--ref2 <ref>', 'Second git ref (default: HEAD)')
92
+ .action((options) => {
93
+ runReleaseClaims({ fromGitDiff: options.fromGitDiff, ref1: options.ref1, ref2: options.ref2 });
94
+ });
95
+ // --- agent-board ---
96
+ program
97
+ .command('agent-board')
98
+ .description('Show a coordination board for agents, plans, claims, handoffs, and instructions')
99
+ .option('--agent <agent>', 'Filter by agent name')
100
+ .option('--project <project>', 'Filter by project namespace')
101
+ .option('--for <target>', 'Infer project from target path')
102
+ .option('--host <host>', 'Include machine-local runtime notes for a specific host')
103
+ .option('--all-hosts', 'Include machine-local runtime notes from all hosts')
104
+ .option('--json', 'Output as JSON')
105
+ .option('--with-reputation', 'Include bounded reputation summaries when available')
106
+ .option('--capabilities', 'List all registered agents with their declared capabilities')
107
+ .option('--suggest <query>', 'Suggest agents whose capabilities match a query string')
108
+ .option('--include-session-meta', 'Include session_start/session_end runtime notes (hidden by default)')
109
+ .option('--all-agents', 'Show unfiltered board (supervisor mode — all claims, all agents)')
110
+ .action((options) => {
111
+ runAgentBoard(options);
112
+ });
113
+ // --- watch ---
114
+ program
115
+ .command('watch')
116
+ .description('Watch for memory changes and emit NDJSON events on stdout')
117
+ .option('--interval <seconds>', 'Poll interval in seconds', parseInt)
118
+ .option('--auto-claim', 'Auto-create advisory claims on first write to workspace files')
119
+ .option('--agent <name>', 'Agent name for auto-claim')
120
+ .action((options) => {
121
+ runWatch({ ...options, autoClaim: options.autoClaim });
122
+ });
123
+ // --- dispatch ---
124
+ const dispatchCmd = program
125
+ .command('dispatch')
126
+ .description('Local agent dispatcher — analyze lanes and assign work');
127
+ dispatchCmd
128
+ .command('analysis')
129
+ .description('Analyze the active sequence: show ready, active, blocked, and done lanes')
130
+ .option('--json', 'Output as JSON')
131
+ .action((options) => {
132
+ runDispatchAnalysis({ json: options.json });
133
+ });
134
+ dispatchCmd
135
+ .command('run')
136
+ .description('Run a dispatch cycle: assign ready lanes to available agents')
137
+ .option('--agents <names>', 'Comma-separated list of agents to dispatch to')
138
+ .option('--lanes <names>', 'Comma-separated list of lanes to dispatch')
139
+ .option('--max <n>', 'Maximum assignments', parseInt)
140
+ .option('--max-concurrency <n>', 'Opt-in cap on concurrent instances per host-binary (default: unlimited)', parseInt)
141
+ .option('--model <name>', 'Model to run, decoupled from agent identity (e.g. --model sonnet)')
142
+ .option('--dry', 'Preview assignments without sending messages')
143
+ .option('--spawn', 'Autonomously launch CLI agents with invoke templates')
144
+ .option('--agent <name>', 'Dispatcher agent name')
145
+ .option('--json', 'Output as JSON')
146
+ .action(async (options) => {
147
+ await runDispatch({
148
+ agents: options.agents,
149
+ lanes: options.lanes,
150
+ max: options.max,
151
+ maxConcurrency: options.maxConcurrency,
152
+ model: options.model,
153
+ dry: options.dry,
154
+ spawn: options.spawn,
155
+ agent: options.agent,
156
+ json: options.json,
157
+ });
158
+ });
159
+ dispatchCmd
160
+ .command('watch <target>')
161
+ .description('Block until a dispatched worker reaches a terminal state (asgn_/clm_/run_ id) — sentinels, lane-result, committed-clean and worker-process-gone heuristics')
162
+ .option('--interval <seconds>', 'Poll interval in seconds (default 60)', parseInt)
163
+ .option('--timeout <minutes>', 'Give up after N minutes (default 90, exit code 2)', parseInt)
164
+ .option('--base <ref>', 'Base ref for commits-ahead evidence (default master)')
165
+ .option('--json', 'One JSON object per poll line')
166
+ .action(async (target, options) => {
167
+ await runDispatchWatch(target, {
168
+ intervalSeconds: options.interval,
169
+ timeoutMinutes: options.timeout,
170
+ base: options.base,
171
+ json: options.json,
172
+ });
173
+ });
174
+ dispatchCmd
175
+ .command('review')
176
+ .description('Dispatch code reviews for completed handoffs')
177
+ .option('--handoff <id>', 'Specific handoff ID to review')
178
+ .option('--reviewer <name>', 'Specific reviewer agent')
179
+ .option('--spawn', 'Launch the reviewer CLI agent')
180
+ .option('--dry', 'Preview without sending')
181
+ .option('--agent <name>', 'Dispatcher agent name')
182
+ .option('--json', 'Output as JSON')
183
+ .action((options) => {
184
+ runDispatchReview({
185
+ handoff: options.handoff,
186
+ reviewer: options.reviewer,
187
+ spawn: options.spawn,
188
+ dry: options.dry,
189
+ agent: options.agent,
190
+ json: options.json,
191
+ });
192
+ });
193
+ // --- inbox ---
194
+ const inboxCmd = program
195
+ .command('inbox')
196
+ .description('Inter-agent messaging inbox');
197
+ inboxCmd
198
+ .command('list')
199
+ .description('List inbox messages (default: pending only)')
200
+ .option('--agent <name>', 'Agent name')
201
+ .option('--status <status>', 'Filter by status: pending, read, acknowledged, archived')
202
+ .option('--type <type>', 'Filter by type: assign, review, rfc, info, reply')
203
+ .option('--thread <id>', 'Filter by thread ID')
204
+ .option('--all', 'Show all messages, not just pending')
205
+ .option('--json', 'Output as JSON')
206
+ .option('--local-only', 'Read from local store only (skip parent stores in chain)')
207
+ .action((options) => {
208
+ runInboxList({ ...options, localOnly: options.localOnly });
209
+ });
210
+ inboxCmd
211
+ .command('ack <id>')
212
+ .description('Acknowledge a message')
213
+ .option('--agent <name>', 'Agent name')
214
+ .option('--json', 'Output as JSON')
215
+ .action((id, options) => {
216
+ runInboxAck(id, options);
217
+ });
218
+ inboxCmd
219
+ .command('archive <id>')
220
+ .description('Archive a message')
221
+ .option('--agent <name>', 'Agent name')
222
+ .option('--json', 'Output as JSON')
223
+ .action((id, options) => {
224
+ runInboxArchive(id, options);
225
+ });
226
+ inboxCmd
227
+ .command('send <to> <text>')
228
+ .description('Send a message to another agent')
229
+ .option('--type <type>', 'Message type: assign, review, rfc, info, reply (default: info)')
230
+ .option('--ref <id>', 'Reference to a plan, sequence, or other entity')
231
+ .option('--scope <path>', 'File scope')
232
+ .option('--thread <id>', 'Thread ID for conversations')
233
+ .option('--ack', 'Require acknowledgment')
234
+ .option('--agent <name>', 'Sender agent name')
235
+ .option('--json', 'Output as JSON')
236
+ .action((to, text, options) => {
237
+ runInboxSend(to, text, options);
238
+ });
239
+ inboxCmd
240
+ .command('thread <id>')
241
+ .description('Show all messages in a thread')
242
+ .option('--json', 'Output as JSON')
243
+ .action((id, options) => {
244
+ runInboxThread(id, options);
245
+ });
246
+ // --- check-events ---
247
+ program
248
+ .command('check-events')
249
+ .description('Show unseen events from the event bus (events.jsonl) for the current agent')
250
+ .option('--agent <name>', 'Agent name for cursor lookup (default: auto-detected)')
251
+ .option('--json', 'Output as JSON')
252
+ .action((options) => {
253
+ runCheckEvents(options);
254
+ });
255
+ const worktreeCmd = program
256
+ .command('worktree')
257
+ .description('Manage git worktrees for parallel agent isolation');
258
+ worktreeCmd
259
+ .command('create <branch>')
260
+ .description('Create a linked git worktree for a given branch')
261
+ .option('--session-id <id>', 'Associate this worktree with a brainclaw session')
262
+ .option('--agent <name>', 'Associate this worktree with an agent name')
263
+ .action((branch, options) => {
264
+ const globalOpts = program.opts();
265
+ runWorktreeCreate({ branch, sessionId: options.sessionId, agent: options.agent, cwd: globalOpts.cwd });
266
+ });
267
+ worktreeCmd
268
+ .command('list')
269
+ .description('List all git worktrees for this project')
270
+ .action(() => {
271
+ const globalOpts = program.opts();
272
+ runWorktreeList({ cwd: globalOpts.cwd });
273
+ });
274
+ worktreeCmd
275
+ .command('remove <path>')
276
+ .description('Remove a linked git worktree')
277
+ .option('--force', 'Force removal even with uncommitted changes')
278
+ .action((worktreePath, options) => {
279
+ const globalOpts = program.opts();
280
+ runWorktreeRemove({ path: worktreePath, force: options.force, cwd: globalOpts.cwd });
281
+ });
282
+ worktreeCmd
283
+ .command('prune')
284
+ .description('Prune stale worktree administrative files')
285
+ .action(() => {
286
+ const globalOpts = program.opts();
287
+ runWorktreePrune({ cwd: globalOpts.cwd });
288
+ });
289
+ worktreeCmd
290
+ .command('clean')
291
+ .description('Remove worktrees whose branch is fully merged and orphan worktree directories')
292
+ .option('--force', 'Force removal even with uncommitted changes')
293
+ .option('--dry-run', 'Show what would be removed without actually removing')
294
+ .action((options) => {
295
+ const globalOpts = program.opts();
296
+ runWorktreeClean({ force: options.force, dryRun: options.dryRun, cwd: globalOpts.cwd });
297
+ });
298
+ worktreeCmd
299
+ .command('merge <branch>')
300
+ .description('Merge a worktree branch with auto-restoration of parasitic deletions')
301
+ .option('-m, --message <message>', 'Merge commit message')
302
+ .option('--dry-run', 'Show what would be merged without committing')
303
+ .action((branch, options) => {
304
+ const globalOpts = program.opts();
305
+ runWorktreeMerge({ branch, message: options.message, dryRun: options.dryRun, cwd: globalOpts.cwd });
306
+ });
307
+ worktreeCmd
308
+ .command('check')
309
+ .description('Pre-merge conflict detection: which parallel lanes touch overlapping files, and who owns them (exit 3 if overlaps found)')
310
+ .option('--base <ref>', 'Base ref each lane is diffed against (default: current branch)')
311
+ .option('--json', 'Emit the full risk report as JSON')
312
+ .action((options) => {
313
+ const globalOpts = program.opts();
314
+ runWorktreeCheck({ baseRef: options.base, json: options.json, cwd: globalOpts.cwd });
315
+ });
316
+ // --- codev (legacy experimental) ---
317
+ if (isCodevEnabled()) {
318
+ program
319
+ .command('codev [topic]')
320
+ .description('Experimental legacy ideation session using persona-based consultation')
321
+ .option('--personas <tier>', 'Persona tier: tier1 (default), tier2, or list', 'tier1')
322
+ .option('--checkpoint', 'Pause after clarification for human input')
323
+ .option('--spawn', 'Spawn each consultant as an agent CLI instance')
324
+ .option('--fresh', 'Clear cached responses before starting a new run')
325
+ .option('--agents <list>', 'Comma-separated agent names for spawn (e.g. claude-code,codex,antigravity). Default: auto-detect')
326
+ .option('--rounds <N>', 'Number of discussion rounds in spawn mode (default 3, min 2)', '3')
327
+ .option('--target-duration <seconds>', 'Target duration per round indicated to agents (default 120)', '120')
328
+ .option('--quorum <N>', 'Advance to next round after N agent responses (default: all)')
329
+ .option('--model-map <map>', 'Per-persona model overrides, e.g. simplificateur:sonnet,stratege:opus')
330
+ .option('--metrics', 'Display response timing metrics at end of session')
331
+ .option('--json', 'Output as JSON')
332
+ .action(async (topic, options) => {
333
+ const globalOpts = program.opts();
334
+ const { runCodev } = await import('../commands/codev.js');
335
+ runCodev(topic, {
336
+ ...options,
337
+ rounds: parseInt(options.rounds, 10),
338
+ targetDuration: parseInt(options.targetDuration, 10),
339
+ quorum: options.quorum != null ? parseInt(options.quorum, 10) : undefined,
340
+ cwd: globalOpts.cwd,
341
+ });
342
+ });
343
+ program
344
+ .command('codev-metrics <thread>')
345
+ .description('Show per-agent avg/p95 response metrics for an experimental CoDev thread')
346
+ .option('--json', 'Output as JSON')
347
+ .action(async (thread, options) => {
348
+ const globalOpts = program.opts();
349
+ const { runCodevMetrics } = await import('../commands/codev.js');
350
+ runCodevMetrics(thread, { ...options, cwd: globalOpts.cwd });
351
+ });
352
+ }
353
+ // --- questions (operator-question artifacts across loops; pln#508 step 4) ---
354
+ program
355
+ .command('questions')
356
+ .description('List pending operator_question artifacts across loops in the current project')
357
+ .option('--loop <loop_id>', 'Filter to a single loop')
358
+ .option('--status <status>', 'Filter by status: awaiting (default), answered, timed_out', 'awaiting')
359
+ .option('--mine', 'Filter to questions targeted at the current agent (v1 heuristic: humans see all awaiting)')
360
+ .option('--json', 'Output as JSON')
361
+ .action((options) => {
362
+ const globalOpts = program.opts();
363
+ const status = options.status;
364
+ if (!['awaiting', 'answered', 'timed_out'].includes(status)) {
365
+ console.error(`Error: --status must be one of awaiting|answered|timed_out (got "${options.status}")`);
366
+ process.exit(1);
367
+ }
368
+ runQuestionsCommand({
369
+ loop: options.loop,
370
+ status,
371
+ mine: options.mine,
372
+ json: options.json,
373
+ }, globalOpts.cwd);
374
+ });
375
+ // --- bootstrap-loop (open/join/status/cancel a bootstrap loop; pln#513 step 3) ---
376
+ program
377
+ .command('bootstrap-loop')
378
+ .description('Open or join a bootstrap loop on the current project, or query its status')
379
+ .option('--status', 'Report current state')
380
+ .option('--cancel', 'Cancel the active bootstrap loop')
381
+ .option('--yes', 'Skip confirmation prompts')
382
+ .option('--json', 'Machine-readable output')
383
+ .action(async (options) => {
384
+ const globalOpts = program.opts();
385
+ const { runBootstrapLoopCommand } = await import('../commands/bootstrap-loop.js');
386
+ await runBootstrapLoopCommand(options, globalOpts.cwd);
387
+ });
388
+ // --- loop (drive loop turn verbs; pln#517 step 2) ---
389
+ const loopCmd = program
390
+ .command('loop')
391
+ .description('Drive a loop turn (turn / complete-turn / advance / add-artifact)');
392
+ loopCmd
393
+ .command('turn <loop_id>')
394
+ .description('Issue a turn assignment on a slot')
395
+ .requiredOption('--slot <slot_id>', 'Target slot id (lsl_...)')
396
+ .option('--input <text>', 'Free-form input passed to the slot')
397
+ .option('--role <role>', 'Slot role (resolves the first non-done slot with that role)')
398
+ .option('--assignment-id <id>', 'Dispatcher-provided assignment id to record on the slot')
399
+ .option('--json', 'Machine-readable output')
400
+ .action(async (loop_id, options) => {
401
+ const globalOpts = program.opts();
402
+ const { runLoopCommand } = await import('../commands/loop.js');
403
+ await runLoopCommand('turn', { loop_id }, options, globalOpts.cwd);
404
+ });
405
+ loopCmd
406
+ .command('complete-turn <loop_id>')
407
+ .description('Complete a slot turn')
408
+ .requiredOption('--slot <slot_id>', 'Target slot id (lsl_...)')
409
+ .requiredOption('--outcome <outcome>', 'Turn outcome: done, failed, or cancelled')
410
+ .option('--failure-reason <text>', 'Reason when outcome is failed')
411
+ .option('--artifact <json>', 'JSON object payload for an artifact to attach')
412
+ .option('--json', 'Machine-readable output')
413
+ .action(async (loop_id, options) => {
414
+ const globalOpts = program.opts();
415
+ const { runLoopCommand } = await import('../commands/loop.js');
416
+ await runLoopCommand('complete-turn', { loop_id }, options, globalOpts.cwd);
417
+ });
418
+ loopCmd
419
+ .command('advance <loop_id>')
420
+ .description('Advance a loop to its next phase')
421
+ .option('--to-phase <name>', 'Explicit target phase')
422
+ .option('--force', 'Bypass phase gate checks')
423
+ .option('--reason <text>', 'Reason to record on the phase advance event')
424
+ .option('--json', 'Machine-readable output')
425
+ .action(async (loop_id, options) => {
426
+ const globalOpts = program.opts();
427
+ const { runLoopCommand } = await import('../commands/loop.js');
428
+ await runLoopCommand('advance', { loop_id }, options, globalOpts.cwd);
429
+ });
430
+ loopCmd
431
+ .command('add-artifact <loop_id>')
432
+ .description('Attach an artifact to a loop')
433
+ .requiredOption('--phase <phase>', 'Artifact phase')
434
+ .requiredOption('--type <type>', 'Artifact type')
435
+ .requiredOption('--body <json-or-text>', 'Artifact body as JSON or text')
436
+ .option('--produced-by <agent>', 'Agent that produced the artifact')
437
+ .option('--ref <ref>', 'JSON ref object, e.g. {"kind":"plan","id":"pln_..."}')
438
+ .option('--json', 'Machine-readable output')
439
+ .action(async (loop_id, options) => {
440
+ const globalOpts = program.opts();
441
+ const { runLoopCommand } = await import('../commands/loop.js');
442
+ await runLoopCommand('add-artifact', { loop_id }, options, globalOpts.cwd);
443
+ });
444
+ // --- reply (provide_input to an operator_question; pln#508 step 4) ---
445
+ program
446
+ .command('reply <qst_id>')
447
+ .description('Resolve an operator_question artifact (wraps bclaw_loop.provide_input)')
448
+ .option('--answer <text>', 'Free-form answer text')
449
+ .option('--choose <option_id>', 'Pick one of the question\'s structured options[].id')
450
+ .option('--skip', 'Materialize the question\'s suggested_default')
451
+ .option('--json', 'Output as JSON')
452
+ .action((qstId, options) => {
453
+ const globalOpts = program.opts();
454
+ runReplyCommand(qstId, {
455
+ answer: options.answer,
456
+ choose: options.choose,
457
+ skip: options.skip,
458
+ json: options.json,
459
+ }, globalOpts.cwd);
460
+ });
461
+ // --- run (agent profiles) ---
462
+ program
463
+ .command('run [profile-name]')
464
+ .description('Run an agent profile (list profiles if no name given)')
465
+ .option('--dry', 'Print the resolved command without executing')
466
+ .option('--agent <agent>', 'Override the invoke template with a known agent')
467
+ .action((profileName, options) => {
468
+ const globalOpts = program.opts();
469
+ runRunProfile(profileName, { ...options, cwd: globalOpts.cwd });
470
+ });
471
+ }
472
+ //# sourceMappingURL=register-coordination.js.map