brainclaw 1.8.0 → 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.
- package/README.md +12 -11
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +138 -13
- package/dist/commands/add-step.js +1 -1
- package/dist/commands/bootstrap.js +2 -26
- package/dist/commands/check-security-mcp.js +50 -33
- package/dist/commands/check-security.js +86 -43
- package/dist/commands/claim.js +22 -21
- package/dist/commands/confirm.js +26 -0
- package/dist/commands/context-diff.js +1 -1
- package/dist/commands/dispatch-watch.js +142 -0
- package/dist/commands/doctor.js +113 -2
- package/dist/commands/estimation-report.js +115 -16
- package/dist/commands/harvest.js +285 -22
- package/dist/commands/init.js +123 -21
- package/dist/commands/loops-handlers.js +4 -0
- package/dist/commands/mcp-read-handlers.js +198 -29
- package/dist/commands/mcp.js +588 -92
- package/dist/commands/memory.js +21 -17
- package/dist/commands/migrate.js +81 -17
- package/dist/commands/prune.js +78 -4
- package/dist/commands/reflect.js +26 -20
- package/dist/commands/register-agent.js +57 -1
- package/dist/commands/repair.js +20 -0
- package/dist/commands/session-end.js +15 -6
- package/dist/commands/session-start.js +18 -1
- package/dist/commands/setup-security.js +39 -18
- package/dist/commands/setup.js +26 -27
- package/dist/commands/stale.js +16 -2
- package/dist/commands/uninstall.js +126 -34
- package/dist/commands/update-step.js +6 -0
- package/dist/commands/worktree.js +60 -0
- package/dist/core/actions.js +12 -3
- package/dist/core/agent-capability.js +11 -13
- package/dist/core/agent-files.js +844 -547
- package/dist/core/agent-integrations.js +0 -3
- package/dist/core/agent-inventory.js +67 -0
- package/dist/core/agent-registry.js +163 -29
- package/dist/core/agentrun-reconciler.js +33 -2
- package/dist/core/agentruns.js +7 -1
- package/dist/core/ai-agent-detection.js +31 -44
- package/dist/core/archival.js +15 -9
- package/dist/core/assignment-reconciler.js +56 -0
- package/dist/core/assignment-sweeper.js +127 -4
- package/dist/core/assignments.js +69 -11
- package/dist/core/bootstrap.js +233 -67
- package/dist/core/brainclaw-version.js +22 -0
- package/dist/core/candidates.js +21 -1
- package/dist/core/claims.js +313 -150
- package/dist/core/config.js +6 -1
- package/dist/core/context-diff.js +148 -20
- package/dist/core/context.js +129 -8
- package/dist/core/coordination.js +22 -3
- package/dist/core/dispatch-status.js +79 -5
- package/dist/core/dispatcher.js +64 -11
- package/dist/core/entity-operations.js +45 -24
- package/dist/core/entity-registry.js +31 -5
- package/dist/core/event-log.js +138 -21
- package/dist/core/events/checkpoint.js +258 -0
- package/dist/core/events/genesis.js +220 -0
- package/dist/core/events/journal.js +507 -0
- package/dist/core/events/materialize.js +126 -0
- package/dist/core/events/registry-post-image.js +110 -0
- package/dist/core/events/verify.js +109 -0
- package/dist/core/execution-adapters.js +23 -0
- package/dist/core/facade-schema.js +38 -0
- package/dist/core/gc-semantic.js +130 -5
- package/dist/core/handoff-snapshot.js +68 -0
- package/dist/core/ids.js +19 -8
- package/dist/core/instruction-templates.js +34 -115
- package/dist/core/io.js +39 -3
- package/dist/core/json-store.js +10 -1
- package/dist/core/lock.js +153 -28
- package/dist/core/loops/bootstrap-acquire.js +25 -1
- package/dist/core/loops/facade-schema.js +2 -0
- package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
- package/dist/core/loops/index.js +1 -0
- package/dist/core/loops/presets/bootstrap.js +7 -0
- package/dist/core/loops/store.js +17 -0
- package/dist/core/loops/verbs.js +24 -1
- package/dist/core/markdown.js +8 -76
- package/dist/core/mcp-command-resolution.js +245 -0
- package/dist/core/memory-compactor.js +5 -3
- package/dist/core/memory-lifecycle.js +282 -0
- package/dist/core/merge-risk.js +150 -0
- package/dist/core/messaging.js +8 -1
- package/dist/core/migration.js +11 -1
- package/dist/core/observer-mode.js +26 -0
- package/dist/core/operations/memory-mutation.js +90 -65
- package/dist/core/operations/plan.js +27 -1
- package/dist/core/protocol-skills.js +210 -0
- package/dist/core/reflection-safety.js +6 -7
- package/dist/core/reputation.js +84 -2
- package/dist/core/runtime-signals.js +71 -9
- package/dist/core/runtime.js +84 -1
- package/dist/core/schema.js +114 -0
- package/dist/core/security-detectors.js +125 -0
- package/dist/core/security-extract.js +189 -0
- package/dist/core/security-guard.js +107 -29
- package/dist/core/security-packages.js +121 -0
- package/dist/core/security-scoring.js +76 -9
- package/dist/core/security.js +34 -2
- package/dist/core/sequence.js +11 -2
- package/dist/core/setup-flow.js +141 -13
- package/dist/core/staleness.js +72 -1
- package/dist/core/state.js +250 -54
- package/dist/core/store-resolution.js +19 -5
- package/dist/core/worktree.js +72 -8
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/PROTOCOL.md +223 -0
- package/docs/cli.md +11 -10
- package/docs/concepts/coordinator-runbook.md +129 -0
- package/docs/concepts/event-log-store-critique-A.md +333 -0
- package/docs/concepts/event-log-store-critique-B.md +353 -0
- package/docs/concepts/event-log-store-phase0-measurements.md +58 -0
- package/docs/concepts/event-log-store-proposal-A.md +365 -0
- package/docs/concepts/event-log-store-proposal-B.md +404 -0
- package/docs/concepts/event-log-store.md +928 -0
- package/docs/concepts/identity-model-proposal.md +371 -0
- package/docs/concepts/memory.md +5 -4
- package/docs/concepts/observer-protocol.md +361 -0
- package/docs/concepts/parallel-merge-protocol.md +71 -0
- package/docs/concepts/plans-and-claims.md +43 -0
- package/docs/concepts/skills.md +78 -0
- package/docs/concepts/workspace-bootstrapping.md +61 -0
- package/docs/integrations/agents.md +4 -4
- package/docs/integrations/cline.md +10 -11
- package/docs/integrations/codex.md +2 -2
- package/docs/integrations/continue.md +5 -5
- package/docs/integrations/copilot.md +14 -12
- package/docs/integrations/openclaw.md +7 -6
- package/docs/integrations/overview.md +7 -7
- package/docs/integrations/roo.md +3 -3
- package/docs/integrations/windsurf.md +6 -6
- package/docs/mcp-schema-changelog.md +29 -2
- package/docs/quickstart.md +48 -47
- package/docs/security.md +174 -15
- package/docs/storage.md +4 -2
- 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
|
| [](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
|
-
| [](https://openai.com/codex/) | **[Codex](https://openai.com/codex/)** | A | MCP + AGENTS.md +
|
|
68
|
+
| [](https://openai.com/codex/) | **[Codex](https://openai.com/codex/)** | A | MCP + AGENTS.md + skills |
|
|
69
69
|
| [](https://cursor.com/en-US) | **[Cursor](https://cursor.com/en-US)** | A | MCP (machine) + .cursor/rules/ + hooks + skills |
|
|
70
|
-
| [](https://windsurf.com/) | **[Windsurf](https://windsurf.com/)** | A | MCP (machine) + .windsurfrules +
|
|
71
|
-
| [](https://github.com/cline/cline) | **[Cline](https://github.com/cline/cline)** | A | MCP + auto-approve + .clinerules/
|
|
70
|
+
| [](https://windsurf.com/) | **[Windsurf](https://windsurf.com/)** | A | MCP (machine) + .windsurfrules + .windsurf/rules/ |
|
|
71
|
+
| [](https://github.com/cline/cline) | **[Cline](https://github.com/cline/cline)** | A | MCP + auto-approve + .clinerules/ |
|
|
72
72
|
| [](https://github.com/features/copilot) | **[GitHub Copilot](https://github.com/features/copilot)** | A | MCP + copilot-instructions.md + hooks + skills |
|
|
73
73
|
| [](https://github.com/RooCodeInc/Roo-Code) | **[Roo](https://github.com/RooCodeInc/Roo-Code)** | B | MCP + auto-approve + .roo/rules/ |
|
|
74
74
|
| [](https://github.com/continuedev/continue) | **[Continue](https://github.com/continuedev/continue)** | B | MCP + .continue/rules/ |
|
|
75
75
|
| [](https://github.com/opencode-ai/opencode) | **[OpenCode](https://github.com/opencode-ai/opencode)** | B | MCP + AGENTS.md |
|
|
76
76
|
| [](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** =
|
|
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
|
|
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
|
-
| [](https://www.kernel.org/) | **[Linux](https://www.kernel.org/)** | Recommended | best-supported environment today; GitHub CI runs on Ubuntu with Node
|
|
103
|
+
| [](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
|
| [](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
|
| [](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
|
| [](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** —
|
|
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 #
|
|
339
|
-
npm run test:
|
|
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
|
---
|
|
@@ -459,7 +460,7 @@ For older releases (v0.x and the early v1.0 launch series), `git log` on `master
|
|
|
459
460
|
|
|
460
461
|
### v1.1.0
|
|
461
462
|
|
|
462
|
-
- **Node 20+ baseline** (pln#485) — `engines.node` is now `>=20.0.0` (Node 18 reached EOL in April 2025). CI matrix runs Node
|
|
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.
|
|
463
464
|
- **commander 13 → 14** (requires Node 20+).
|
|
464
465
|
- **@types/node 22 → 24** (LTS-aligned).
|
|
465
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(
|
|
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
|
-
.
|
|
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', '
|
|
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('--
|
|
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
|
-
|
|
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
|
|
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
|
|
29
|
-
const
|
|
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: {
|
|
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
|
|
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(
|
|
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' ? '
|
|
73
|
-
lines.push(`${icon} ${v.ecosystem}/${v.package}@${v.version}
|
|
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(`
|
|
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
|
-
|
|
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:
|
|
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
|