brainclaw 1.7.5 → 1.9.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 (143) hide show
  1. package/README.md +28 -11
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +139 -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 +502 -16
  15. package/dist/commands/init.js +123 -21
  16. package/dist/commands/loops-handlers.js +4 -0
  17. package/dist/commands/mcp-read-handlers.js +198 -29
  18. package/dist/commands/mcp.js +615 -92
  19. package/dist/commands/memory.js +21 -17
  20. package/dist/commands/migrate.js +81 -17
  21. package/dist/commands/prune.js +78 -4
  22. package/dist/commands/reflect.js +26 -20
  23. package/dist/commands/register-agent.js +57 -1
  24. package/dist/commands/repair.js +20 -0
  25. package/dist/commands/session-end.js +15 -6
  26. package/dist/commands/session-start.js +18 -1
  27. package/dist/commands/setup-security.js +39 -18
  28. package/dist/commands/setup.js +26 -27
  29. package/dist/commands/stale.js +16 -2
  30. package/dist/commands/uninstall.js +126 -34
  31. package/dist/commands/update-step.js +6 -0
  32. package/dist/commands/worktree.js +60 -0
  33. package/dist/core/actions.js +12 -3
  34. package/dist/core/agent-capability.js +11 -13
  35. package/dist/core/agent-files.js +844 -547
  36. package/dist/core/agent-integrations.js +0 -3
  37. package/dist/core/agent-inventory.js +67 -0
  38. package/dist/core/agent-registry.js +163 -29
  39. package/dist/core/agentrun-reconciler.js +33 -2
  40. package/dist/core/agentruns.js +7 -1
  41. package/dist/core/ai-agent-detection.js +31 -44
  42. package/dist/core/archival.js +15 -9
  43. package/dist/core/assignment-reconciler.js +56 -0
  44. package/dist/core/assignment-sweeper.js +127 -4
  45. package/dist/core/assignments.js +69 -11
  46. package/dist/core/bootstrap.js +233 -67
  47. package/dist/core/brainclaw-version.js +22 -0
  48. package/dist/core/candidates.js +21 -1
  49. package/dist/core/claims.js +313 -150
  50. package/dist/core/config.js +6 -1
  51. package/dist/core/context-diff.js +148 -20
  52. package/dist/core/context.js +129 -8
  53. package/dist/core/coordination.js +22 -3
  54. package/dist/core/dispatch-status.js +109 -5
  55. package/dist/core/dispatcher.js +65 -11
  56. package/dist/core/entity-operations.js +45 -24
  57. package/dist/core/entity-registry.js +31 -5
  58. package/dist/core/event-log.js +138 -21
  59. package/dist/core/events/checkpoint.js +258 -0
  60. package/dist/core/events/genesis.js +220 -0
  61. package/dist/core/events/journal.js +507 -0
  62. package/dist/core/events/materialize.js +126 -0
  63. package/dist/core/events/registry-post-image.js +110 -0
  64. package/dist/core/events/verify.js +109 -0
  65. package/dist/core/execution-adapters.js +23 -0
  66. package/dist/core/execution.js +25 -0
  67. package/dist/core/facade-schema.js +48 -0
  68. package/dist/core/gc-semantic.js +130 -5
  69. package/dist/core/handoff-snapshot.js +68 -0
  70. package/dist/core/ids.js +19 -8
  71. package/dist/core/instruction-templates.js +34 -115
  72. package/dist/core/io.js +39 -3
  73. package/dist/core/json-store.js +10 -1
  74. package/dist/core/lock.js +153 -28
  75. package/dist/core/loops/bootstrap-acquire.js +25 -1
  76. package/dist/core/loops/facade-schema.js +2 -0
  77. package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
  78. package/dist/core/loops/index.js +1 -0
  79. package/dist/core/loops/presets/bootstrap.js +7 -0
  80. package/dist/core/loops/store.js +17 -0
  81. package/dist/core/loops/verbs.js +24 -1
  82. package/dist/core/markdown.js +8 -76
  83. package/dist/core/mcp-command-resolution.js +245 -0
  84. package/dist/core/memory-compactor.js +5 -3
  85. package/dist/core/memory-lifecycle.js +282 -0
  86. package/dist/core/merge-risk.js +150 -0
  87. package/dist/core/messaging.js +8 -1
  88. package/dist/core/migration.js +11 -1
  89. package/dist/core/observer-mode.js +26 -0
  90. package/dist/core/operations/memory-mutation.js +90 -65
  91. package/dist/core/operations/plan.js +27 -1
  92. package/dist/core/protocol-skills.js +210 -0
  93. package/dist/core/reflection-safety.js +6 -7
  94. package/dist/core/reputation.js +84 -2
  95. package/dist/core/runtime-signals.js +71 -9
  96. package/dist/core/runtime.js +84 -1
  97. package/dist/core/schema.js +125 -0
  98. package/dist/core/security-detectors.js +125 -0
  99. package/dist/core/security-extract.js +189 -0
  100. package/dist/core/security-guard.js +107 -29
  101. package/dist/core/security-packages.js +121 -0
  102. package/dist/core/security-scoring.js +76 -9
  103. package/dist/core/security.js +34 -2
  104. package/dist/core/sequence.js +11 -2
  105. package/dist/core/setup-flow.js +141 -13
  106. package/dist/core/spawn-check.js +110 -4
  107. package/dist/core/staleness.js +109 -1
  108. package/dist/core/state.js +250 -54
  109. package/dist/core/store-resolution.js +19 -5
  110. package/dist/core/worktree.js +169 -7
  111. package/dist/facts.js +8 -8
  112. package/dist/facts.json +7 -7
  113. package/docs/PROTOCOL.md +223 -0
  114. package/docs/cli.md +11 -10
  115. package/docs/concepts/coordinator-runbook.md +129 -0
  116. package/docs/concepts/dispatch-lifecycle.md +17 -0
  117. package/docs/concepts/event-log-store-critique-A.md +333 -0
  118. package/docs/concepts/event-log-store-critique-B.md +353 -0
  119. package/docs/concepts/event-log-store-phase0-measurements.md +58 -0
  120. package/docs/concepts/event-log-store-proposal-A.md +365 -0
  121. package/docs/concepts/event-log-store-proposal-B.md +404 -0
  122. package/docs/concepts/event-log-store.md +928 -0
  123. package/docs/concepts/identity-model-proposal.md +371 -0
  124. package/docs/concepts/memory.md +5 -4
  125. package/docs/concepts/observer-protocol.md +361 -0
  126. package/docs/concepts/parallel-merge-protocol.md +71 -0
  127. package/docs/concepts/plans-and-claims.md +43 -0
  128. package/docs/concepts/skills.md +78 -0
  129. package/docs/concepts/workspace-bootstrapping.md +61 -0
  130. package/docs/integrations/agents.md +4 -4
  131. package/docs/integrations/cline.md +10 -11
  132. package/docs/integrations/codex.md +2 -2
  133. package/docs/integrations/continue.md +5 -5
  134. package/docs/integrations/copilot.md +14 -12
  135. package/docs/integrations/openclaw.md +7 -6
  136. package/docs/integrations/overview.md +7 -7
  137. package/docs/integrations/roo.md +3 -3
  138. package/docs/integrations/windsurf.md +6 -6
  139. package/docs/mcp-schema-changelog.md +51 -20
  140. package/docs/quickstart.md +48 -47
  141. package/docs/security.md +174 -15
  142. package/docs/storage.md +4 -2
  143. package/package.json +8 -6
package/README.md CHANGED
@@ -65,17 +65,17 @@ brainclaw is designed to sit alongside the coding agents teams are already using
65
65
  | Logo | Agent | Tier | What brainclaw configures |
66
66
  |---|---|---|---|
67
67
  | [![Claude Code](https://img.shields.io/badge/Claude_Code-111111?logo=anthropic&logoColor=white)](https://github.com/anthropics/claude-code) | **[Claude Code](https://github.com/anthropics/claude-code)** | A | MCP + CLAUDE.md + hooks + auto-approve + permissions + /brainclaw skill |
68
- | [![Codex](https://img.shields.io/badge/Codex-111111?logo=openai&logoColor=white)](https://openai.com/codex/) | **[Codex](https://openai.com/codex/)** | A | MCP + AGENTS.md + hooks + skills |
68
+ | [![Codex](https://img.shields.io/badge/Codex-111111?logo=openai&logoColor=white)](https://openai.com/codex/) | **[Codex](https://openai.com/codex/)** | A | MCP + AGENTS.md + skills |
69
69
  | [![Cursor](https://img.shields.io/badge/Cursor-1F2430?logo=cursor&logoColor=white)](https://cursor.com/en-US) | **[Cursor](https://cursor.com/en-US)** | A | MCP (machine) + .cursor/rules/ + hooks + skills |
70
- | [![Windsurf](https://img.shields.io/badge/Windsurf-0B1220?logo=codeium&logoColor=white)](https://windsurf.com/) | **[Windsurf](https://windsurf.com/)** | A | MCP (machine) + .windsurfrules + hooks + skills |
71
- | [![Cline](https://img.shields.io/badge/Cline-0F766E?logoColor=white)](https://github.com/cline/cline) | **[Cline](https://github.com/cline/cline)** | A | MCP + auto-approve + .clinerules/ + hooks + skills |
70
+ | [![Windsurf](https://img.shields.io/badge/Windsurf-0B1220?logo=codeium&logoColor=white)](https://windsurf.com/) | **[Windsurf](https://windsurf.com/)** | A | MCP (machine) + .windsurfrules + .windsurf/rules/ |
71
+ | [![Cline](https://img.shields.io/badge/Cline-0F766E?logoColor=white)](https://github.com/cline/cline) | **[Cline](https://github.com/cline/cline)** | A | MCP + auto-approve + .clinerules/ |
72
72
  | [![GitHub Copilot](https://img.shields.io/badge/GitHub_Copilot-181717?logo=githubcopilot&logoColor=white)](https://github.com/features/copilot) | **[GitHub Copilot](https://github.com/features/copilot)** | A | MCP + copilot-instructions.md + hooks + skills |
73
73
  | [![Roo](https://img.shields.io/badge/Roo-7C3AED?logoColor=white)](https://github.com/RooCodeInc/Roo-Code) | **[Roo](https://github.com/RooCodeInc/Roo-Code)** | B | MCP + auto-approve + .roo/rules/ |
74
74
  | [![Continue](https://img.shields.io/badge/Continue-2563EB?logoColor=white)](https://github.com/continuedev/continue) | **[Continue](https://github.com/continuedev/continue)** | B | MCP + .continue/rules/ |
75
75
  | [![OpenCode](https://img.shields.io/badge/OpenCode-0F172A?logoColor=white)](https://github.com/opencode-ai/opencode) | **[OpenCode](https://github.com/opencode-ai/opencode)** | B | MCP + AGENTS.md |
76
76
  | [![Gemini CLI](https://img.shields.io/badge/Gemini_CLI-1A73E8?logo=googlegemini&logoColor=white)](https://github.com/google-gemini/gemini-cli) | **[Antigravity / Gemini CLI](https://github.com/google-gemini/gemini-cli)** | B | MCP + GEMINI.md |
77
77
 
78
- **Tier A** = MCP + hooks + skills (context injected dynamically, lightweight instruction files). **Tier B** = MCP only, no hooks (richer static instruction files with architecture + top traps). Tier can degrade at runtime if integration surfaces are missing.
78
+ **Tier A** = strongest supported integration for that agent family (usually MCP plus native files, and hooks/skills where the agent exposes them). **Tier B** = MCP/native-file integration with fewer automation surfaces. Tier can degrade at runtime if integration surfaces are missing.
79
79
 
80
80
  ### Autonomous Agents
81
81
 
@@ -96,11 +96,11 @@ brainclaw is most effective today when one agent works at a time in a given chec
96
96
 
97
97
  ## Platform Support
98
98
 
99
- brainclaw declares support for Node.js 20+ in `package.json`, and CI actively exercises Node 20, 22, and 24 across the main Linux path (Windows runs on Node 24). Real-world support is still not perfectly even yet.
99
+ brainclaw declares support for Node.js 20+ in `package.json` (`engines.node = ">=20.0.0"`). CI actively exercises Node 22 (Active LTS) and Node 24 (current LTS) on Linux; Windows runs on Node 24. Node 20 still works as a minimum runtime but is no longer CI-verified — it reached EOL in April 2026 and was removed from GitHub-hosted runners. The recommended runtime is Node 22 LTS or Node 24 LTS. Real-world support is still not perfectly even yet.
100
100
 
101
101
  | Logo | Platform | Status today | Notes |
102
102
  |---|---|---|---|
103
- | [![Linux](https://img.shields.io/badge/Linux-111111?logo=linux&logoColor=white)](https://www.kernel.org/) | **[Linux](https://www.kernel.org/)** | Recommended | best-supported environment today; GitHub CI runs on Ubuntu with Node 20, 22, and 24 |
103
+ | [![Linux](https://img.shields.io/badge/Linux-111111?logo=linux&logoColor=white)](https://www.kernel.org/) | **[Linux](https://www.kernel.org/)** | Recommended | best-supported environment today; GitHub CI runs on Ubuntu with Node 22 and 24 |
104
104
  | [![macOS](https://img.shields.io/badge/macOS-000000?logo=apple&logoColor=white)](https://www.apple.com/macos/) | **[macOS](https://www.apple.com/macos/)** | Likely supported | Unix-like path and shell model should map well, but it is less exercised than Linux |
105
105
  | [![Windows](https://img.shields.io/badge/Windows-0078D4?logo=windows&logoColor=white)](https://www.microsoft.com/windows/) | **[Windows](https://www.microsoft.com/windows/)** | Supported with caveats | native support exists, but PATH, npm, SSH, and PowerShell quoting still create more friction than on Unix systems |
106
106
  | [![Windows + WSL2](https://img.shields.io/badge/Windows%20%2B%20WSL2-0078D4?logo=windows&logoColor=white)](https://learn.microsoft.com/windows/wsl/) | **[Windows + WSL2](https://learn.microsoft.com/windows/wsl/)** | Important, still maturing | Brainclaw detects this setup explicitly, but setup/install/store parity across Windows and WSL is not fully seamless yet |
@@ -232,7 +232,7 @@ Still sharp:
232
232
  1. **Same-checkout concurrent edits** — running two agents in the *same* working tree (no per-claim worktree) is still the wrong answer. Use the dispatch path (auto-worktree per claim) instead of raw concurrent CLI sessions.
233
233
  2. **Cross-machine sync** — federation across machines is on the roadmap, not in v1.x. Today brainclaw's store is local and one-machine-per-project.
234
234
  3. **Spawn-and-forget assumptions** — spawned workers don't always commit their work cleanly. The brief-ack file confirms the spawn started; in the worst case the coordinator harvests open changes.
235
- 4. **Live state for hook-less agents** — Tier B/C agents without lifecycle hooks (Cursor, Cline, Windsurf, Copilot, Continue, Kilocode, Mistral Vibe, Hermes) get live context via `.live.md` companions regenerated on session-end and handoff, not via real-time push.
235
+ 4. **Live state for hook-less agents** — supported hook-less file surfaces such as Cline, Windsurf, Continue, Antigravity/Gemini CLI, and Mistral Vibe can get live context via `.live.md` companions regenerated on session-end and handoff, not via real-time push.
236
236
 
237
237
  Recommended use today:
238
238
 
@@ -334,9 +334,10 @@ If you are integrating Brainclaw into an agent workflow, start with the agent-fa
334
334
  Contributor note: the commands below are for developing Brainclaw itself, not for normal agent usage inside a target repo.
335
335
 
336
336
  ```bash
337
- npm test # unit + smoke (fast path)
338
- npm run test:e2e # full suite
339
- npm run test:coverage # with coverage report
337
+ npm test # unit + smoke (fast path)
338
+ npm run test:e2e # end-to-end suite
339
+ npm run test:all # full suite
340
+ npm run test:coverage # with coverage report
340
341
  ```
341
342
 
342
343
  ---
@@ -345,6 +346,22 @@ npm run test:coverage # with coverage report
345
346
 
346
347
  For older releases (v0.x and the early v1.0 launch series), `git log` on `master` is the source of truth — every release commit follows the `chore(release): bump version to <semver>` convention, and the matching feature/fix commits reference their plan id (e.g. `feat(mcp): self-heal ... (pln#478)`).
347
348
 
349
+ ### v1.8.0
350
+
351
+ - **Multi-agent dispatch convergence — "worktree-as-contract"** (from a real
352
+ cross-project field session where a sandboxed worker could neither commit nor
353
+ reach MCP). The worker's contract shrinks to "edit files in this worktree +
354
+ drop `LANE-RESULT.json`": `brainclaw harvest --integrate` commits the worktree
355
+ diff on behalf of a worker that can't self-commit (hard-guarded to the linked
356
+ worktree, never the main repo), then completes the assignment and releases the
357
+ claim with plan cascade. A `LANE-RESULT.json` is now the #1 verdict signal in
358
+ `bclaw_dispatch_status` (worker FINISHED, even without self-update); the
359
+ dispatcher refuses to spawn without an isolated worktree; `open_loop` reviews
360
+ pre-flight each reviewer agent with a trivial validation spawn (clear
361
+ boot-failure reason instead of a generic loop timeout); and decisions/traps
362
+ gain `verified_at`/`verify_cmd` so perishable facts can be flagged stale.
363
+ Additive + opt-in throughout. (pln#530, pln#531, pln#532, pln#533, pln#534, trp#468)
364
+
348
365
  ### v1.7.5
349
366
 
350
367
  - **Security patch (recommended upgrade)** — fixes a git command-injection / RCE
@@ -443,7 +460,7 @@ For older releases (v0.x and the early v1.0 launch series), `git log` on `master
443
460
 
444
461
  ### v1.1.0
445
462
 
446
- - **Node 20+ baseline** (pln#485) — `engines.node` is now `>=20.0.0` (Node 18 reached EOL in April 2025). CI matrix runs Node 20, 22, and 24 on Linux; Windows on Node 24.
463
+ - **Node 20+ baseline** (pln#485) — `engines.node` is now `>=20.0.0` (Node 18 reached EOL in April 2025). CI matrix runs Node 22 and 24 on Linux; Windows on Node 24. Node 20 remains the minimum installable runtime but is no longer CI-verified.
447
464
  - **commander 13 → 14** (requires Node 20+).
448
465
  - **@types/node 22 → 24** (LTS-aligned).
449
466
 
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 ---
@@ -1014,6 +1092,9 @@ program
1014
1092
  .command('harvest [assignment_id]')
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')
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)')
1017
1098
  .option('--dry-run', 'Preview without writing events/markers')
1018
1099
  .option('--worktree <path>', 'Explicit worktree path to scan (repeatable)', collect, [])
1019
1100
  .option('--json', 'Output as JSON')
@@ -1236,13 +1317,19 @@ program
1236
1317
  program
1237
1318
  .command('check-security')
1238
1319
  .description('Check supply chain security scores for packages via Socket.dev')
1239
- .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')
1240
1323
  .option('--ecosystem <type>', 'Package ecosystem: npm or pypi', 'npm')
1324
+ .option('--mode <mode>', 'Override security mode: advisory or enforced (defaults to config)')
1241
1325
  .option('--json', 'Output as JSON')
1242
1326
  .action(async (options) => {
1243
1327
  await runCheckSecurity({
1244
1328
  packages: options.packages,
1329
+ requirements: options.requirements,
1330
+ lockfile: options.lockfile,
1245
1331
  ecosystem: options.ecosystem,
1332
+ mode: options.mode,
1246
1333
  json: options.json,
1247
1334
  });
1248
1335
  });
@@ -1338,7 +1425,7 @@ program
1338
1425
  .option('--reflect-handoff', 'Materialize an open handoff from git commits since session start')
1339
1426
  .option('--dispatch-review', 'When used with --reflect-handoff, auto-dispatch a code review if the handoff is reviewable')
1340
1427
  .option('--reviewer <name>', 'Explicit reviewer to route the reflected handoff review to')
1341
- .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')
1342
1429
  .option('--json', 'Output as JSON')
1343
1430
  .action(async (options) => {
1344
1431
  await runSessionEnd({
@@ -1387,7 +1474,7 @@ program
1387
1474
  program
1388
1475
  .command('export')
1389
1476
  .description('Export memory as instructions for IDE/AI tools')
1390
- .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')
1391
1478
  .option('--detect', 'Auto-detect agent environment and write to its native file')
1392
1479
  .option('--all', 'Write all known agent instruction files at once (claude-md, agents-md, copilot-instructions, cursor-rules, etc.)')
1393
1480
  .option('--write', 'Write to canonical file path instead of stdout (when --format is given); local files are gitignored by default')
@@ -1465,8 +1552,8 @@ dispatchCmd
1465
1552
  .option('--spawn', 'Autonomously launch CLI agents with invoke templates')
1466
1553
  .option('--agent <name>', 'Dispatcher agent name')
1467
1554
  .option('--json', 'Output as JSON')
1468
- .action((options) => {
1469
- runDispatch({
1555
+ .action(async (options) => {
1556
+ await runDispatch({
1470
1557
  agents: options.agents,
1471
1558
  lanes: options.lanes,
1472
1559
  max: options.max,
@@ -1478,6 +1565,21 @@ dispatchCmd
1478
1565
  json: options.json,
1479
1566
  });
1480
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
+ });
1481
1583
  dispatchCmd
1482
1584
  .command('review')
1483
1585
  .description('Dispatch code reviews for completed handoffs')
@@ -1697,9 +1799,10 @@ program
1697
1799
  .command('migrate')
1698
1800
  .description('Migrate memory items between stores (e.g. promote machine-scoped items to user store)')
1699
1801
  .option('--promote-machine-items', 'Move items with scope:machine from project store to user store (~/.brainclaw/)')
1700
- .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')
1701
1804
  .action((options) => {
1702
- runMigrate({ promoteMachineItems: options.promoteMachineItems, dryRun: options.dryRun });
1805
+ runMigrate({ promoteMachineItems: options.promoteMachineItems, enableJournal: options.enableJournal, dryRun: options.dryRun });
1703
1806
  });
1704
1807
  program
1705
1808
  .command('switch [project]')
@@ -1780,6 +1883,15 @@ worktreeCmd
1780
1883
  const globalOpts = program.opts();
1781
1884
  runWorktreeMerge({ branch, message: options.message, dryRun: options.dryRun, cwd: globalOpts.cwd });
1782
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
+ });
1783
1895
  // --- federation cloud ---
1784
1896
  const federationCmd = program
1785
1897
  .command('federation')
@@ -2022,6 +2134,20 @@ program
2022
2134
  const globalOpts = program.opts();
2023
2135
  runRunProfile(profileName, { ...options, cwd: globalOpts.cwd });
2024
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
+ }
2025
2151
  program.parseAsync(process.argv).catch((err) => {
2026
2152
  console.error(err);
2027
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