brainclaw 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +592 -505
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +138 -13
  4. package/dist/commands/add-step.js +1 -1
  5. package/dist/commands/bootstrap.js +2 -26
  6. package/dist/commands/check-security-mcp.js +50 -33
  7. package/dist/commands/check-security.js +86 -43
  8. package/dist/commands/claim.js +22 -21
  9. package/dist/commands/confirm.js +26 -0
  10. package/dist/commands/context-diff.js +1 -1
  11. package/dist/commands/dispatch-watch.js +142 -0
  12. package/dist/commands/doctor.js +113 -2
  13. package/dist/commands/estimation-report.js +115 -16
  14. package/dist/commands/harvest.js +286 -23
  15. package/dist/commands/hooks.js +73 -73
  16. package/dist/commands/init.js +124 -22
  17. package/dist/commands/install-hooks.js +78 -78
  18. package/dist/commands/loops-handlers.js +4 -0
  19. package/dist/commands/mcp-read-handlers.js +253 -41
  20. package/dist/commands/mcp.js +664 -102
  21. package/dist/commands/memory.js +21 -17
  22. package/dist/commands/migrate.js +81 -17
  23. package/dist/commands/prune.js +78 -4
  24. package/dist/commands/reflect.js +26 -20
  25. package/dist/commands/register-agent.js +57 -1
  26. package/dist/commands/repair.js +20 -0
  27. package/dist/commands/session-end.js +15 -6
  28. package/dist/commands/session-start.js +18 -1
  29. package/dist/commands/setup-security.js +39 -18
  30. package/dist/commands/setup.js +26 -27
  31. package/dist/commands/stale.js +16 -2
  32. package/dist/commands/switch.js +26 -5
  33. package/dist/commands/uninstall.js +126 -34
  34. package/dist/commands/update-step.js +6 -0
  35. package/dist/commands/version.js +1 -1
  36. package/dist/commands/worktree.js +60 -0
  37. package/dist/core/actions.js +12 -3
  38. package/dist/core/agent-capability.js +30 -17
  39. package/dist/core/agent-files.js +963 -666
  40. package/dist/core/agent-integrations.js +0 -3
  41. package/dist/core/agent-inventory.js +67 -0
  42. package/dist/core/agent-registry.js +163 -29
  43. package/dist/core/agentrun-reconciler.js +33 -2
  44. package/dist/core/agentruns.js +7 -1
  45. package/dist/core/ai-agent-detection.js +31 -44
  46. package/dist/core/archival.js +15 -9
  47. package/dist/core/assignment-reconciler.js +56 -0
  48. package/dist/core/assignment-sweeper.js +127 -4
  49. package/dist/core/assignments.js +69 -11
  50. package/dist/core/bootstrap.js +233 -67
  51. package/dist/core/brainclaw-version.js +22 -0
  52. package/dist/core/candidates.js +21 -1
  53. package/dist/core/claims.js +313 -150
  54. package/dist/core/codev-prompts.js +38 -38
  55. package/dist/core/config.js +6 -1
  56. package/dist/core/context-diff.js +148 -20
  57. package/dist/core/context.js +129 -8
  58. package/dist/core/coordination.js +22 -3
  59. package/dist/core/default-profiles/doctor.yaml +11 -11
  60. package/dist/core/default-profiles/janitor.yaml +11 -11
  61. package/dist/core/default-profiles/onboarder.yaml +11 -11
  62. package/dist/core/default-profiles/reviewer.yaml +13 -13
  63. package/dist/core/dispatch-status.js +79 -5
  64. package/dist/core/dispatcher.js +65 -12
  65. package/dist/core/entity-operations.js +74 -27
  66. package/dist/core/entity-registry.js +31 -5
  67. package/dist/core/event-log.js +138 -21
  68. package/dist/core/events/checkpoint.js +258 -0
  69. package/dist/core/events/genesis.js +220 -0
  70. package/dist/core/events/journal.js +507 -0
  71. package/dist/core/events/materialize.js +126 -0
  72. package/dist/core/events/registry-post-image.js +110 -0
  73. package/dist/core/events/verify.js +109 -0
  74. package/dist/core/execution-adapters.js +23 -0
  75. package/dist/core/execution.js +1 -1
  76. package/dist/core/facade-schema.js +38 -0
  77. package/dist/core/gc-semantic.js +130 -5
  78. package/dist/core/handoff-snapshot.js +68 -0
  79. package/dist/core/ids.js +19 -8
  80. package/dist/core/instruction-templates.js +34 -115
  81. package/dist/core/io.js +39 -3
  82. package/dist/core/json-store.js +10 -1
  83. package/dist/core/lock.js +153 -28
  84. package/dist/core/loops/bootstrap-acquire.js +25 -1
  85. package/dist/core/loops/facade-schema.js +2 -0
  86. package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
  87. package/dist/core/loops/index.js +1 -0
  88. package/dist/core/loops/presets/bootstrap.js +7 -0
  89. package/dist/core/loops/store.js +17 -0
  90. package/dist/core/loops/verbs.js +24 -2
  91. package/dist/core/markdown.js +8 -76
  92. package/dist/core/mcp-command-resolution.js +245 -0
  93. package/dist/core/memory-compactor.js +5 -3
  94. package/dist/core/memory-lifecycle.js +282 -0
  95. package/dist/core/merge-risk.js +150 -0
  96. package/dist/core/messaging.js +10 -3
  97. package/dist/core/migration.js +11 -1
  98. package/dist/core/observer-mode.js +26 -0
  99. package/dist/core/operations/memory-mutation.js +90 -65
  100. package/dist/core/operations/plan.js +27 -1
  101. package/dist/core/protocol-skills.js +210 -0
  102. package/dist/core/reflection-safety.js +6 -7
  103. package/dist/core/reputation.js +84 -2
  104. package/dist/core/runtime-signals.js +72 -10
  105. package/dist/core/runtime.js +84 -1
  106. package/dist/core/schema.js +114 -0
  107. package/dist/core/search.js +19 -2
  108. package/dist/core/security-detectors.js +125 -0
  109. package/dist/core/security-extract.js +189 -0
  110. package/dist/core/security-guard.js +217 -139
  111. package/dist/core/security-packages.js +121 -0
  112. package/dist/core/security-scoring.js +76 -9
  113. package/dist/core/security.js +34 -2
  114. package/dist/core/sequence.js +11 -2
  115. package/dist/core/setup-flow.js +141 -13
  116. package/dist/core/spawn-check.js +16 -2
  117. package/dist/core/staleness.js +73 -2
  118. package/dist/core/state.js +250 -54
  119. package/dist/core/store-resolution.js +45 -12
  120. package/dist/core/worktree.js +90 -26
  121. package/dist/facts.js +8 -8
  122. package/dist/facts.json +7 -7
  123. package/docs/PROTOCOL.md +223 -0
  124. package/docs/adapters/openclaw.md +43 -43
  125. package/docs/architecture/project-refs.md +328 -328
  126. package/docs/cli.md +2097 -2096
  127. package/docs/concepts/coordination.md +52 -52
  128. package/docs/concepts/coordinator-runbook.md +129 -0
  129. package/docs/concepts/dispatch-lifecycle.md +245 -245
  130. package/docs/concepts/event-log-store.md +928 -0
  131. package/docs/concepts/ideation-loop.md +317 -317
  132. package/docs/concepts/loop-engine.md +520 -511
  133. package/docs/concepts/mcp-governance.md +268 -268
  134. package/docs/concepts/memory.md +89 -88
  135. package/docs/concepts/multi-agent-workflows.md +167 -167
  136. package/docs/concepts/observer-protocol.md +361 -0
  137. package/docs/concepts/parallel-merge-protocol.md +71 -0
  138. package/docs/concepts/plans-and-claims.md +217 -174
  139. package/docs/concepts/project-md-convention.md +35 -35
  140. package/docs/concepts/runtime-notes.md +38 -38
  141. package/docs/concepts/skills.md +78 -0
  142. package/docs/concepts/troubleshooting.md +254 -254
  143. package/docs/concepts/workspace-bootstrapping.md +142 -81
  144. package/docs/context-format-changelog.md +35 -35
  145. package/docs/context-format.md +48 -48
  146. package/docs/index.md +65 -65
  147. package/docs/integrations/agents.md +162 -162
  148. package/docs/integrations/claude-code.md +23 -23
  149. package/docs/integrations/cline.md +87 -88
  150. package/docs/integrations/codex.md +2 -2
  151. package/docs/integrations/continue.md +60 -60
  152. package/docs/integrations/copilot.md +82 -80
  153. package/docs/integrations/cursor.md +23 -23
  154. package/docs/integrations/kilocode.md +72 -72
  155. package/docs/integrations/mcp.md +377 -377
  156. package/docs/integrations/mistral-vibe.md +122 -122
  157. package/docs/integrations/openclaw.md +99 -98
  158. package/docs/integrations/opencode.md +84 -84
  159. package/docs/integrations/overview.md +122 -122
  160. package/docs/integrations/roo.md +74 -74
  161. package/docs/integrations/windsurf.md +83 -83
  162. package/docs/mcp-schema-changelog.md +360 -329
  163. package/docs/playbooks/integration/index.md +121 -121
  164. package/docs/playbooks/orchestration.md +37 -0
  165. package/docs/playbooks/productivity/index.md +99 -99
  166. package/docs/playbooks/team/index.md +117 -117
  167. package/docs/product/agent-first-model.md +184 -184
  168. package/docs/product/entity-model-audit.md +462 -462
  169. package/docs/product/positioning.md +86 -86
  170. package/docs/quickstart-existing-project.md +107 -107
  171. package/docs/quickstart.md +148 -147
  172. package/docs/release-maintenance.md +79 -79
  173. package/docs/reputation.md +52 -52
  174. package/docs/review.md +45 -45
  175. package/docs/security.md +212 -53
  176. package/docs/server-operations.md +118 -118
  177. package/docs/storage.md +110 -108
  178. package/package.json +86 -69
Binary file
package/dist/cli.js CHANGED
@@ -63,7 +63,7 @@ import { runCheckConstraints } from './commands/check-constraints.js';
63
63
  import { runCheckPolicy } from './commands/check-policy.js';
64
64
  import { runCheckSecurity } from './commands/check-security.js';
65
65
  import { runSetupSecurity } from './commands/setup-security.js';
66
- import { runRegisterAgent } from './commands/register-agent.js';
66
+ import { runRegisterAgent, runRemoveAgent } from './commands/register-agent.js';
67
67
  import { runEnableAgent } from './commands/enable-agent.js';
68
68
  import { runVersion } from './commands/version.js';
69
69
  import { runReleaseNotes } from './commands/release-notes.js';
@@ -80,6 +80,7 @@ import { runExport, runRefresh } from './commands/export.js';
80
80
  import { runHooks } from './commands/hooks.js';
81
81
  import { runWatch } from './commands/watch.js';
82
82
  import { runDispatchAnalysis, runDispatch, runDispatchReview } from './commands/dispatch.js';
83
+ import { runDispatchWatch } from './commands/dispatch-watch.js';
83
84
  import { runInboxList, runInboxAck, runInboxArchive, runInboxSend, runInboxThread } from './commands/inbox.js';
84
85
  import { runMetrics } from './commands/metrics.js';
85
86
  import { runRollback } from './commands/rollback.js';
@@ -98,7 +99,7 @@ import { initLogLevel, logger } from './core/logger.js';
98
99
  import { resolveEffectiveCwd } from './core/store-resolution.js';
99
100
  import { resolveProjectCwd } from './core/cross-project.js';
100
101
  import { runSwitch } from './commands/switch.js';
101
- import { runWorktreeCreate, runWorktreeList, runWorktreeRemove, runWorktreePrune, runWorktreeClean, runWorktreeMerge } from './commands/worktree.js';
102
+ import { runWorktreeCreate, runWorktreeList, runWorktreeRemove, runWorktreePrune, runWorktreeClean, runWorktreeMerge, runWorktreeCheck } from './commands/worktree.js';
102
103
  import { runCheckEvents } from './commands/check-events.js';
103
104
  import { runDiscover } from './commands/discover.js';
104
105
  import { runMigrate } from './commands/migrate.js';
@@ -148,11 +149,72 @@ function parseLeadingGlobalOptions(argv) {
148
149
  }
149
150
  return result;
150
151
  }
152
+ function trailingGlobalOptionError(argv, actionCommand) {
153
+ let firstCommandIndex = -1;
154
+ for (let i = 0; i < argv.length; i++) {
155
+ const token = argv[i];
156
+ if (token === '--')
157
+ break;
158
+ if (!token.startsWith('-')) {
159
+ firstCommandIndex = i;
160
+ break;
161
+ }
162
+ if (token === '--cwd' || token === '--project')
163
+ i++;
164
+ }
165
+ if (firstCommandIndex < 0)
166
+ return undefined;
167
+ for (let i = firstCommandIndex + 1; i < argv.length; i++) {
168
+ const token = argv[i];
169
+ if (token === '--')
170
+ break;
171
+ const long = token.includes('=') ? token.slice(0, token.indexOf('=')) : token;
172
+ if (!['--cwd', '--project', '--verbose', '--debug'].includes(long))
173
+ continue;
174
+ const isLocalOption = actionCommand.options.some((option) => option.long === long);
175
+ if (!isLocalOption) {
176
+ const valueHint = token === long && (long === '--cwd' || long === '--project') ? ' <value>' : '';
177
+ return `Global option ${long} must appear before the subcommand. Use: brainclaw ${long}${valueHint} <command> ...`;
178
+ }
179
+ }
180
+ return undefined;
181
+ }
182
+ /**
183
+ * Resolve the (possibly nested) subcommand named in argv without parsing.
184
+ * Used to run the trailing-global-option guard BEFORE Commander parses:
185
+ * with positional options enabled, Commander would otherwise reject a
186
+ * trailing --cwd/--project as "unknown option" before any hook runs.
187
+ */
188
+ function findCommandFromArgv(argv) {
189
+ let current = program;
190
+ let found;
191
+ for (let i = 0; i < argv.length; i++) {
192
+ const token = argv[i];
193
+ if (token === '--')
194
+ break;
195
+ if (token.startsWith('-')) {
196
+ if (!found && (token === '--cwd' || token === '--project'))
197
+ i++;
198
+ continue;
199
+ }
200
+ const sub = current.commands.find((c) => c.name() === token || c.aliases().includes(token));
201
+ if (!sub)
202
+ break;
203
+ found = sub;
204
+ current = sub;
205
+ }
206
+ return found;
207
+ }
151
208
  function isCodevEnabled() {
152
209
  return process.env.BRAINCLAW_ENABLE_CODEV === '1';
153
210
  }
154
211
  program
155
212
  .name('brainclaw')
213
+ // Stop the program-level parser from consuming options that appear AFTER the
214
+ // subcommand: `instruction … --project auth` must reach the subcommand's own
215
+ // --project, not the global routing flag (which is leading-only by contract —
216
+ // see parseLeadingGlobalOptions + trailingGlobalOptionError).
217
+ .enablePositionalOptions()
156
218
  .description('Shared project memory for humans and coding agents.')
157
219
  .version(getInstalledBrainclawVersion())
158
220
  .option('--verbose', 'Show info-level log messages on stderr')
@@ -570,8 +632,10 @@ program
570
632
  .command('add-step <planId> <text>')
571
633
  .description('Add an optional step to a plan item')
572
634
  .option('--assign <assignee>', 'Assign this step to an agent or person')
635
+ .option('--estimate <minutes>', 'Step-level estimate (minutes, or a duration like "2h"/"30m")')
636
+ .option('--actual-effort <effort>', 'Step-level actual effort (e.g. "45m", "2h")')
573
637
  .action((planId, text, options) => {
574
- runAddStep(planId, text, { assignee: options.assign });
638
+ runAddStep(planId, text, { assignee: options.assign, estimatedEffort: options.estimate, actualEffort: options.actualEffort });
575
639
  });
576
640
  // --- complete-step ---
577
641
  program
@@ -583,10 +647,12 @@ program
583
647
  // --- update-step ---
584
648
  program
585
649
  .command('update-step <planId> <stepId>')
586
- .description('Update a plan step (status, text, assignee)')
650
+ .description('Update a plan step (status, text, assignee, effort)')
587
651
  .option('--status <status>', 'New status: todo, in_progress, testing, done, blocked')
588
652
  .option('--text <text>', 'Replace step description')
589
653
  .option('--assign <assignee>', 'Assign the step (empty string to unassign)')
654
+ .option('--estimate <minutes>', 'Step-level estimate (minutes, or a duration like "2h"/"30m")')
655
+ .option('--actual-effort <effort>', 'Step-level actual effort (e.g. "45m", "2h")')
590
656
  .action((planId, stepId, options) => {
591
657
  runUpdateStep(planId, stepId, options);
592
658
  });
@@ -603,8 +669,13 @@ program
603
669
  .description('Show estimation accuracy report for completed plans')
604
670
  .option('--agent <name>', 'Filter by agent/author name')
605
671
  .option('--json', 'Output as JSON')
672
+ .option('--outlier-threshold <minutes>', 'Drop plan-wallclock actuals over N minutes from the stats (default 1440 = 24h; 0 disables)')
606
673
  .action((options) => {
607
- runEstimationReport(options);
674
+ runEstimationReport({
675
+ agent: options.agent,
676
+ json: options.json,
677
+ outlierThresholdMinutes: options.outlierThreshold !== undefined ? Number(options.outlierThreshold) : undefined,
678
+ });
608
679
  });
609
680
  // --- update-plan ---
610
681
  program
@@ -681,6 +752,7 @@ program
681
752
  .option('--repair', 'Rebuild dist/ when the MCP runtime is missing or stale')
682
753
  .option('--after-migration', 'Run the v1.0 post-migration health check only (exits non-zero on any failure)')
683
754
  .option('--dispatch', 'Run dispatch-health diagnostic only: reconcile open agent_runs and report stuck/unverified/silent failures (pln#496 step stp_8c072d75)')
755
+ .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')
684
756
  .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')
685
757
  .option('--spawn-check-timeout <ms>', 'Per-agent timeout for --spawn-check (default 15000)', parseInt)
686
758
  .action(async (options) => {
@@ -688,7 +760,7 @@ program
688
760
  await runDoctorSpawnCheck({ cwd: options.cwd, json: options.json, timeoutMs: options.spawnCheckTimeout });
689
761
  return;
690
762
  }
691
- runDoctor({ ...options, afterMigration: options.afterMigration, dispatch: options.dispatch });
763
+ runDoctor({ ...options, afterMigration: options.afterMigration, dispatch: options.dispatch, verifyJournal: options.verifyJournal });
692
764
  });
693
765
  // --- repair (Phase 4 Sprint 2 Lane C / pln#397) ---
694
766
  program
@@ -901,8 +973,14 @@ program
901
973
  .option('--generate-fingerprint', 'Generate or rotate a local public identity fingerprint for this agent')
902
974
  .option('--set-current', 'Set this identity as the current agent in config')
903
975
  .option('--curator', 'Register this agent as a curator (project owner with direct-write access)')
976
+ .option('--remove', 'Remove this identity instead of registering (guarded: debris identities only unless --force)')
977
+ .option('--force', 'With --remove: allow removing a non-debris identity')
904
978
  .option('--json', 'Output as JSON')
905
979
  .action((name, options) => {
980
+ if (options.remove) {
981
+ runRemoveAgent(name, { force: options.force, json: options.json });
982
+ return;
983
+ }
906
984
  runRegisterAgent(name, options);
907
985
  });
908
986
  // --- enable-agent ---
@@ -1015,6 +1093,8 @@ program
1015
1093
  .description('Harvest a worker LANE-RESULT.json from its worktree into the project (pass an assignment id, or --all)')
1016
1094
  .option('--all', 'Harvest every lane result found across worktrees')
1017
1095
  .option('--integrate', 'Worktree-as-contract (pln#534): commit the worktree diff on behalf of a sandboxed worker, lifecycle the assignment, and release the claim')
1096
+ .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')
1097
+ .option('--base <ref>', 'Base ref for --orphaned commits-ahead comparison (default: master)')
1018
1098
  .option('--dry-run', 'Preview without writing events/markers')
1019
1099
  .option('--worktree <path>', 'Explicit worktree path to scan (repeatable)', collect, [])
1020
1100
  .option('--json', 'Output as JSON')
@@ -1237,13 +1317,19 @@ program
1237
1317
  program
1238
1318
  .command('check-security')
1239
1319
  .description('Check supply chain security scores for packages via Socket.dev')
1240
- .requiredOption('--packages <names>', 'Comma-separated package names (e.g. "axios,express" or "axios@1.14.1")')
1320
+ .option('--packages <names>', 'Comma-separated package names (e.g. "axios,express" or "axios@1.14.1")')
1321
+ .option('--requirements <file>', 'Path to a pip-style requirements.txt to scan')
1322
+ .option('--lockfile <file>', 'Path to a package-lock.json (npm) to scan top-level deps')
1241
1323
  .option('--ecosystem <type>', 'Package ecosystem: npm or pypi', 'npm')
1324
+ .option('--mode <mode>', 'Override security mode: advisory or enforced (defaults to config)')
1242
1325
  .option('--json', 'Output as JSON')
1243
1326
  .action(async (options) => {
1244
1327
  await runCheckSecurity({
1245
1328
  packages: options.packages,
1329
+ requirements: options.requirements,
1330
+ lockfile: options.lockfile,
1246
1331
  ecosystem: options.ecosystem,
1332
+ mode: options.mode,
1247
1333
  json: options.json,
1248
1334
  });
1249
1335
  });
@@ -1339,7 +1425,7 @@ program
1339
1425
  .option('--reflect-handoff', 'Materialize an open handoff from git commits since session start')
1340
1426
  .option('--dispatch-review', 'When used with --reflect-handoff, auto-dispatch a code review if the handoff is reviewable')
1341
1427
  .option('--reviewer <name>', 'Explicit reviewer to route the reflected handoff review to')
1342
- .option('--reflect', 'Include structured reflection questions for the agent to answer')
1428
+ .option('--no-reflect', 'Suppress the dogfooding reflection prompt (project + your surfaces/skills/tools), shown by default')
1343
1429
  .option('--json', 'Output as JSON')
1344
1430
  .action(async (options) => {
1345
1431
  await runSessionEnd({
@@ -1388,7 +1474,7 @@ program
1388
1474
  program
1389
1475
  .command('export')
1390
1476
  .description('Export memory as instructions for IDE/AI tools')
1391
- .option('--format <format>', 'Format: copilot-instructions, cursor-rules, agents-md, claude-md, gemini-md, windsurf, cline, roo, continue')
1477
+ .option('--format <format>', 'Format: copilot-instructions, cursor-rules, agents-md, claude-md, gemini-md, windsurf, cline, roo, continue, openclaw, nanoclaw, nemoclaw, picoclaw, zeroclaw')
1392
1478
  .option('--detect', 'Auto-detect agent environment and write to its native file')
1393
1479
  .option('--all', 'Write all known agent instruction files at once (claude-md, agents-md, copilot-instructions, cursor-rules, etc.)')
1394
1480
  .option('--write', 'Write to canonical file path instead of stdout (when --format is given); local files are gitignored by default')
@@ -1466,8 +1552,8 @@ dispatchCmd
1466
1552
  .option('--spawn', 'Autonomously launch CLI agents with invoke templates')
1467
1553
  .option('--agent <name>', 'Dispatcher agent name')
1468
1554
  .option('--json', 'Output as JSON')
1469
- .action((options) => {
1470
- runDispatch({
1555
+ .action(async (options) => {
1556
+ await runDispatch({
1471
1557
  agents: options.agents,
1472
1558
  lanes: options.lanes,
1473
1559
  max: options.max,
@@ -1479,6 +1565,21 @@ dispatchCmd
1479
1565
  json: options.json,
1480
1566
  });
1481
1567
  });
1568
+ dispatchCmd
1569
+ .command('watch <target>')
1570
+ .description('Block until a dispatched worker reaches a terminal state (asgn_/clm_/run_ id) — sentinels, lane-result, committed-clean and worker-process-gone heuristics')
1571
+ .option('--interval <seconds>', 'Poll interval in seconds (default 60)', parseInt)
1572
+ .option('--timeout <minutes>', 'Give up after N minutes (default 90, exit code 2)', parseInt)
1573
+ .option('--base <ref>', 'Base ref for commits-ahead evidence (default master)')
1574
+ .option('--json', 'One JSON object per poll line')
1575
+ .action(async (target, options) => {
1576
+ await runDispatchWatch(target, {
1577
+ intervalSeconds: options.interval,
1578
+ timeoutMinutes: options.timeout,
1579
+ base: options.base,
1580
+ json: options.json,
1581
+ });
1582
+ });
1482
1583
  dispatchCmd
1483
1584
  .command('review')
1484
1585
  .description('Dispatch code reviews for completed handoffs')
@@ -1698,9 +1799,10 @@ program
1698
1799
  .command('migrate')
1699
1800
  .description('Migrate memory items between stores (e.g. promote machine-scoped items to user store)')
1700
1801
  .option('--promote-machine-items', 'Move items with scope:machine from project store to user store (~/.brainclaw/)')
1701
- .option('--dry-run', 'Show what would be moved without actually moving')
1802
+ .option('--enable-journal', 'Turn on the event journal (mode=dual) for this existing store and backfill it (pln#567)')
1803
+ .option('--dry-run', 'Show what would be done without writing')
1702
1804
  .action((options) => {
1703
- runMigrate({ promoteMachineItems: options.promoteMachineItems, dryRun: options.dryRun });
1805
+ runMigrate({ promoteMachineItems: options.promoteMachineItems, enableJournal: options.enableJournal, dryRun: options.dryRun });
1704
1806
  });
1705
1807
  program
1706
1808
  .command('switch [project]')
@@ -1781,6 +1883,15 @@ worktreeCmd
1781
1883
  const globalOpts = program.opts();
1782
1884
  runWorktreeMerge({ branch, message: options.message, dryRun: options.dryRun, cwd: globalOpts.cwd });
1783
1885
  });
1886
+ worktreeCmd
1887
+ .command('check')
1888
+ .description('Pre-merge conflict detection: which parallel lanes touch overlapping files, and who owns them (exit 3 if overlaps found)')
1889
+ .option('--base <ref>', 'Base ref each lane is diffed against (default: current branch)')
1890
+ .option('--json', 'Emit the full risk report as JSON')
1891
+ .action((options) => {
1892
+ const globalOpts = program.opts();
1893
+ runWorktreeCheck({ baseRef: options.base, json: options.json, cwd: globalOpts.cwd });
1894
+ });
1784
1895
  // --- federation cloud ---
1785
1896
  const federationCmd = program
1786
1897
  .command('federation')
@@ -2023,6 +2134,20 @@ program
2023
2134
  const globalOpts = program.opts();
2024
2135
  runRunProfile(profileName, { ...options, cwd: globalOpts.cwd });
2025
2136
  });
2137
+ {
2138
+ // Friendly trailing-global-option error must run before Commander parses:
2139
+ // with positional options enabled, Commander itself would reject a trailing
2140
+ // --cwd/--project as a bare "unknown option" otherwise.
2141
+ const argvTail = process.argv.slice(2);
2142
+ const guardCommand = findCommandFromArgv(argvTail);
2143
+ if (guardCommand) {
2144
+ const trailingError = trailingGlobalOptionError(argvTail, guardCommand);
2145
+ if (trailingError) {
2146
+ console.error(`Error: ${trailingError}`);
2147
+ process.exit(1);
2148
+ }
2149
+ }
2150
+ }
2026
2151
  program.parseAsync(process.argv).catch((err) => {
2027
2152
  console.error(err);
2028
2153
  process.exit(1);
@@ -5,7 +5,7 @@ export function runAddStep(planId, text, options = {}) {
5
5
  requireInitialized(process.cwd());
6
6
  validateCliInput(text);
7
7
  try {
8
- const result = addStep({ planId, text, assignee: options.assignee });
8
+ const result = addStep({ planId, text, assignee: options.assignee, estimatedEffort: options.estimatedEffort, actualEffort: options.actualEffort });
9
9
  console.log(`✔ Step added: [${result.stepId}] ${text}`);
10
10
  console.log(` Plan [${result.planId}] progress: ${result.doneSteps}/${result.totalSteps} steps done`);
11
11
  }
@@ -1,9 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import readline from 'node:readline/promises';
3
- import { stdin as input, stdout as output } from 'node:process';
4
2
  import { memoryExists } from '../core/io.js';
5
3
  import { applyBootstrapImport, renderBootstrapInterview, renderBootstrapSummary, runBootstrapProfile, uninstallBootstrapImport, } from '../core/bootstrap.js';
6
4
  import { BootstrapInterviewAnswerSchema } from '../core/schema.js';
5
+ import { confirmAction } from './confirm.js';
7
6
  export async function runBootstrap(options = {}) {
8
7
  const cwd = options.cwd ?? process.cwd();
9
8
  if (!memoryExists(cwd)) {
@@ -92,28 +91,5 @@ function loadBootstrapInterviewAnswers(filepath) {
92
91
  }
93
92
  return parsed.map((entry) => BootstrapInterviewAnswerSchema.parse(entry));
94
93
  }
95
- async function confirmBootstrapAction(question, yes) {
96
- if (yes) {
97
- return;
98
- }
99
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
100
- console.error(`Error: ${question} Re-run with --yes in non-interactive mode.`);
101
- process.exit(1);
102
- }
103
- const rl = readline.createInterface({ input, output });
104
- try {
105
- const answer = await rl.question(`${question} [y/N] `);
106
- if (answer.trim().toLowerCase() !== 'y') {
107
- console.error('Cancelled.');
108
- process.exit(1);
109
- }
110
- }
111
- catch (error) {
112
- console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
113
- process.exit(1);
114
- }
115
- finally {
116
- rl.close();
117
- }
118
- }
94
+ const confirmBootstrapAction = confirmAction;
119
95
  //# sourceMappingURL=bootstrap.js.map
@@ -2,7 +2,9 @@ import { memoryExists, memoryPath } from '../core/io.js';
2
2
  import { loadConfig } from '../core/config.js';
3
3
  import { SecurityCache } from '../core/security-cache.js';
4
4
  import { querySocketScores } from '../core/socket-client.js';
5
- import { evaluateBatch, worstDecision } from '../core/security-scoring.js';
5
+ import { applyMode, evaluateBatch, worstDecision, } from '../core/security-scoring.js';
6
+ import { parsePackageSpec } from '../core/security-packages.js';
7
+ import { collectPackages } from '../core/security-extract.js';
6
8
  import { createTrap } from '../core/operations/memory-write.js';
7
9
  export async function handleCheckSecurity(args, cwd) {
8
10
  if (!memoryExists(cwd)) {
@@ -13,20 +15,35 @@ export async function handleCheckSecurity(args, cwd) {
13
15
  if (!preinstall?.enabled) {
14
16
  return { content: [{ type: 'text', text: 'Security preinstall checks are not enabled. Set security.preinstall.enabled: true in config.yaml.' }] };
15
17
  }
16
- const packagesStr = String(args.packages ?? '').trim();
17
- if (!packagesStr) {
18
- return { content: [{ type: 'text', text: 'Error: missing required argument: packages' }] };
19
- }
20
18
  const ecosystem = (String(args.ecosystem ?? 'npm').trim());
21
- const packageNames = packagesStr.split(',').map(p => p.trim()).filter(Boolean);
19
+ const modeArg = args.mode ? String(args.mode).trim() : undefined;
20
+ const effectiveMode = (modeArg === 'enforced' || modeArg === 'advisory')
21
+ ? modeArg
22
+ : (preinstall.mode ?? 'advisory');
23
+ let packageSpecs;
24
+ try {
25
+ packageSpecs = collectPackages({
26
+ packages: args.packages ? String(args.packages) : undefined,
27
+ requirements: args.requirements ? String(args.requirements) : undefined,
28
+ lockfile: args.lockfile ? String(args.lockfile) : undefined,
29
+ defaultEcosystem: ecosystem,
30
+ });
31
+ }
32
+ catch (err) {
33
+ const msg = err instanceof Error ? err.message : String(err);
34
+ return { content: [{ type: 'text', text: `Error: ${msg}` }] };
35
+ }
36
+ if (packageSpecs.length === 0) {
37
+ return { content: [{ type: 'text', text: 'Error: no packages specified (pass --packages, --requirements, or --lockfile).' }] };
38
+ }
22
39
  // Build cache
23
40
  const cachePath = memoryPath('security/cache.json', cwd);
24
41
  const cache = new SecurityCache(cachePath, preinstall.cache_ttl_hours);
25
42
  // Separate cached vs uncached
26
43
  const queries = [];
27
44
  const cachedScores = [];
28
- for (const name of packageNames) {
29
- const [depname, version] = parsePackageName(name);
45
+ for (const spec of packageSpecs) {
46
+ const { depname, version } = parsePackageSpec(spec);
30
47
  const cached = cache.get(ecosystem, depname, version);
31
48
  if (cached) {
32
49
  cachedScores.push(cached);
@@ -55,25 +72,34 @@ export async function handleCheckSecurity(args, cwd) {
55
72
  const allScores = [...cachedScores, ...fetchedScores];
56
73
  if (allScores.length === 0 && fetchError) {
57
74
  const fallback = preinstall.fallback_on_error;
75
+ const fallbackVerdict = fallback === 'block' ? 'block' : fallback === 'warn' ? 'warn' : 'pass';
76
+ const effective = applyMode(fallbackVerdict, effectiveMode);
58
77
  return {
59
- content: [{ type: 'text', text: `Socket MCP error: ${fetchError}\nFallback policy: ${fallback}` }],
60
- structuredContent: { error: fetchError, fallback, decision: fallback === 'block' ? 'block' : fallback === 'warn' ? 'warn' : 'pass' },
78
+ content: [{ type: 'text', text: `Socket MCP error: ${fetchError}\nFallback policy: ${fallback} (mode=${effectiveMode}) → ${effective.toUpperCase()}` }],
79
+ structuredContent: {
80
+ error: fetchError,
81
+ fallback,
82
+ mode: effectiveMode,
83
+ decision: fallbackVerdict,
84
+ effective_decision: effective,
85
+ },
61
86
  };
62
87
  }
63
88
  const verdicts = evaluateBatch(allScores, preinstall);
64
- const worst = worstDecision(verdicts);
89
+ const intrinsic = worstDecision(verdicts);
90
+ const effective = applyMode(intrinsic, effectiveMode);
65
91
  // Build text output
66
92
  const lines = [];
67
93
  if (fetchError) {
68
- lines.push(`\u26A0 Socket MCP partial error: ${fetchError} (${cachedScores.length} results from cache)`);
94
+ lines.push(`⚠ Socket MCP partial error: ${fetchError} (${cachedScores.length} results from cache)`);
69
95
  lines.push('');
70
96
  }
71
97
  for (const v of verdicts) {
72
- const icon = v.decision === 'pass' ? '\u2705' : v.decision === 'warn' ? '\u26A0\uFE0F' : '\uD83D\uDED1';
73
- lines.push(`${icon} ${v.ecosystem}/${v.package}@${v.version} \u2014 composite=${v.composite} [${v.decision.toUpperCase()}]`);
98
+ const icon = v.decision === 'pass' ? '' : v.decision === 'warn' ? '⚠️' : '🛑';
99
+ lines.push(`${icon} ${v.ecosystem}/${v.package}@${v.version} composite=${v.composite} [${v.decision.toUpperCase()}]`);
74
100
  lines.push(` SC=${v.scores.supplyChain} vuln=${v.scores.vulnerability} qual=${v.scores.quality} maint=${v.scores.maintenance} lic=${v.scores.license}`);
75
101
  for (const r of v.reasons) {
76
- lines.push(` \u2192 ${r}`);
102
+ lines.push(` ${r}`);
77
103
  }
78
104
  }
79
105
  // Auto-create traps for WARN and BLOCK verdicts
@@ -102,7 +128,12 @@ export async function handleCheckSecurity(args, cwd) {
102
128
  lines.push(`Created ${trapsCreated.length} security trap(s): ${trapsCreated.join(', ')}`);
103
129
  }
104
130
  lines.push('');
105
- lines.push(`Overall decision: ${worst.toUpperCase()}`);
131
+ if (intrinsic !== effective) {
132
+ lines.push(`Verdict: ${intrinsic.toUpperCase()} — downgraded to ${effective.toUpperCase()} by mode=${effectiveMode}.`);
133
+ }
134
+ else {
135
+ lines.push(`Verdict: ${intrinsic.toUpperCase()} (mode=${effectiveMode})`);
136
+ }
106
137
  return {
107
138
  content: [{ type: 'text', text: lines.join('\n') }],
108
139
  structuredContent: {
@@ -121,25 +152,11 @@ export async function handleCheckSecurity(args, cwd) {
121
152
  license: v.scores.license,
122
153
  },
123
154
  })),
124
- decision: worst,
155
+ decision: intrinsic,
156
+ effective_decision: effective,
157
+ mode: effectiveMode,
125
158
  ...(fetchError ? { fetch_error: fetchError } : {}),
126
159
  },
127
160
  };
128
161
  }
129
- function parsePackageName(name) {
130
- // Handle scoped packages: @scope/pkg@version
131
- if (name.startsWith('@')) {
132
- const lastAt = name.lastIndexOf('@', name.length - 1);
133
- if (lastAt > 0) {
134
- return [name.slice(0, lastAt), name.slice(lastAt + 1)];
135
- }
136
- return [name, 'latest'];
137
- }
138
- // Regular: pkg@version
139
- if (name.includes('@')) {
140
- const [depname, version] = name.split('@');
141
- return [depname, version || 'latest'];
142
- }
143
- return [name, 'latest'];
144
- }
145
162
  //# sourceMappingURL=check-security-mcp.js.map