brainclaw 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +16 -263
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli/register-capture.js +209 -0
  4. package/dist/cli/register-code-map.js +19 -0
  5. package/dist/cli/register-coordination.js +472 -0
  6. package/dist/cli/register-federation.js +258 -0
  7. package/dist/cli/register-lifecycle.js +436 -0
  8. package/dist/cli/register-memory-context.js +502 -0
  9. package/dist/cli/register-planning.js +167 -0
  10. package/dist/cli/register-review.js +149 -0
  11. package/dist/cli/shared.js +5 -0
  12. package/dist/cli.js +212 -2015
  13. package/dist/commands/dispatch-watch.js +25 -2
  14. package/dist/commands/harvest.js +31 -6
  15. package/dist/commands/mcp-catalog.js +1438 -0
  16. package/dist/commands/mcp-contract.js +33 -0
  17. package/dist/commands/mcp-presentation.js +27 -0
  18. package/dist/commands/mcp-read-handlers.js +72 -36
  19. package/dist/commands/mcp-write-admin.js +328 -0
  20. package/dist/commands/mcp-write-claims.js +864 -0
  21. package/dist/commands/mcp-write-coordination.js +1825 -0
  22. package/dist/commands/mcp-write-entities.js +620 -0
  23. package/dist/commands/mcp-write-memory.js +451 -0
  24. package/dist/commands/mcp-write-sequences.js +116 -0
  25. package/dist/commands/mcp-write-support.js +367 -0
  26. package/dist/commands/mcp.js +261 -5570
  27. package/dist/commands/update-handoff.js +28 -42
  28. package/dist/core/agent-capability.js +31 -14
  29. package/dist/core/agent-files.js +1 -1
  30. package/dist/core/agent-registry.js +51 -3
  31. package/dist/core/claims.js +18 -0
  32. package/dist/core/coordination.js +5 -2
  33. package/dist/core/cross-project.js +35 -1
  34. package/dist/core/dispatcher.js +34 -20
  35. package/dist/core/entity-operations.js +335 -12
  36. package/dist/core/entity-registry.js +72 -9
  37. package/dist/core/execution.js +28 -4
  38. package/dist/core/facade-schema.js +30 -4
  39. package/dist/core/federation-cloud.js +142 -11
  40. package/dist/core/federation-outbox.js +292 -0
  41. package/dist/core/federation-signing.js +115 -0
  42. package/dist/core/handoff-review.js +35 -0
  43. package/dist/core/io.js +6 -0
  44. package/dist/core/protocol-tool-policy.js +113 -0
  45. package/dist/core/review-loop-close.js +115 -0
  46. package/dist/core/schema.js +25 -2
  47. package/dist/core/security-detectors.js +35 -6
  48. package/dist/core/security.js +32 -12
  49. package/dist/core/worktree.js +98 -9
  50. package/dist/facts.js +13 -11
  51. package/dist/facts.json +12 -10
  52. package/docs/PROTOCOL.md +7 -3
  53. package/docs/concepts/coordinator-runbook.md +3 -0
  54. package/docs/concepts/dispatch-lifecycle.md +4 -4
  55. package/docs/concepts/loop-engine.md +3 -1
  56. package/docs/concepts/troubleshooting.md +1 -1
  57. package/docs/integrations/codex.md +3 -3
  58. package/docs/integrations/overview.md +1 -1
  59. package/docs/mcp-schema-changelog.md +153 -2
  60. package/docs/playbooks/orchestration.md +1 -1
  61. package/docs/product/entity-model-audit.md +3 -2
  62. package/docs/security.md +22 -1
  63. package/package.json +3 -1
@@ -25,17 +25,38 @@ function gitPath(p) {
25
25
  * landed on the dot before `astro`, yielding `…IntegrationHubPage.` — a trailing
26
26
  * dot git rejects (`fatal: not a valid branch name`). Truncating first, then
27
27
  * stripping, guarantees the cap can never re-introduce an invalid ref.
28
+ *
29
+ * trp#950 (dogfood 2026-07-15): a plain truncation makes two DISTINCT scopes
30
+ * that share a >48-char prefix collapse to the SAME branch → same worktree path
31
+ * → the second claim/assign is refused. When (and only when) the cleaned slug
32
+ * exceeds the cap, a deterministic 8-char digest of the FULL cleaned slug is
33
+ * appended so distinct scopes diverge, while the same scope stays stable
34
+ * (resume/re-assign still resolves its worktree). Short scopes are unchanged.
35
+ * 8 hex chars = 32 bits: comfortably collision-safe for the realistic case
36
+ * (a handful of scopes sharing a deep directory prefix) while keeping a
37
+ * 39-char readable head.
28
38
  */
39
+ const BRANCH_COMPONENT_CAP = 48;
29
40
  export function sanitizeBranchComponent(raw, fallback = 'scope') {
30
- let slug = raw
41
+ const cleaned = raw
31
42
  .replace(/[\s~^:?*[\]\\]/g, '-') // chars forbidden by check-ref-format
32
43
  .replace(/@\{/g, '-') // reflog syntax
33
44
  .replace(/\.\.+/g, '.') // no double dots
34
45
  .replace(/[^a-zA-Z0-9._-]/g, '-') // conservative whitelist for the rest
35
46
  .replace(/-+/g, '-') // collapse dashes
36
- .replace(/^[.-]+/, '') // no leading dot/dash
37
- .slice(0, 48) // length cap BEFORE the trailing strips
38
- .replace(/[.-]+$/, ''); // no trailing dot/dash (cut may have made one)
47
+ .replace(/^[.-]+/, ''); // no leading dot/dash
48
+ let slug;
49
+ if (cleaned.length <= BRANCH_COMPONENT_CAP) {
50
+ slug = cleaned.replace(/[.-]+$/, ''); // no trailing dot/dash
51
+ }
52
+ else {
53
+ // Truncation drops characters → reserve room for a collision-resistant
54
+ // suffix derived from the full cleaned slug (trp#950). The digest is hex, so
55
+ // it can never re-introduce a trailing dot/dash or a `.lock` suffix.
56
+ const suffix = crypto.createHash('sha1').update(cleaned).digest('hex').slice(0, 8);
57
+ const head = cleaned.slice(0, BRANCH_COMPONENT_CAP - suffix.length - 1).replace(/[.-]+$/, '');
58
+ slug = `${head}-${suffix}`;
59
+ }
39
60
  if (/\.lock$/i.test(slug))
40
61
  slug = slug.slice(0, -'.lock'.length).replace(/[.-]+$/, '');
41
62
  if (!slug)
@@ -348,14 +369,42 @@ export function commitWorktreeOnBehalf(worktreePath, message, options = {}) {
348
369
  return { committed: false, files_changed: [], reason: 'worktree clean — nothing to commit' };
349
370
  }
350
371
  // Stage everything, then UNSTAGE the transient files that must never land on
351
- // the lane branch: the worker's own `LANE-RESULT.json` report and any
352
- // `.brainclaw/` coordination state. Committing those would pollute the branch
353
- // (and master, on merge) with non-deliverable artefacts.
372
+ // the lane branch: the worker's own `LANE-RESULT.json` report, any
373
+ // `.brainclaw/` coordination state, and the `.brainclaw-worktree.json` marker.
374
+ // Committing those would pollute the branch (and master, on merge) with
375
+ // non-deliverable artefacts — a field report (Codex on macOS) caught them
376
+ // landing in a lane commit (trp_01a2ba2a). `.brainclaw-worktree.json` sits at
377
+ // the worktree ROOT (NOT inside `.brainclaw/`), so the `.brainclaw` pathspec
378
+ // does not cover it — it needs its own entry. These are ALWAYS transient, so
379
+ // the unstage is unconditional.
354
380
  const add = runGit(['add', '-A'], worktreePath);
355
381
  if (!add.ok) {
356
382
  return { committed: false, files_changed: [], reason: `git add failed: ${add.stderr.trim()}` };
357
383
  }
358
- runGit(['reset', '-q', '--', 'LANE-RESULT.json', '.brainclaw', '.brainclaw-heartbeat-*'], worktreePath);
384
+ runGit([
385
+ 'reset', '-q', '--',
386
+ 'LANE-RESULT.json',
387
+ '.brainclaw',
388
+ '.brainclaw-worktree.json',
389
+ '.brainclaw-heartbeat-*',
390
+ ], worktreePath);
391
+ // node_modules needs a TRACKED-AWARE exclusion (Codex review of #88, BLOCKING).
392
+ // Unstage the links/dirs brainclaw provisions — but a project that VENDORS
393
+ // node_modules tracks those files, and a worker's change to a TRACKED
394
+ // node_modules file is a REAL deliverable; dropping it would silently omit
395
+ // work. Strategy: unstage every node_modules path, then RE-ADD only the ones
396
+ // already tracked at HEAD and modified/deleted (never the fresh provisioned
397
+ // link/dir, which is `A` vs HEAD). The component-bounded pathspecs never match
398
+ // a similarly-named deliverable such as `src/node_modules_helper.ts` — the
399
+ // plain `node_modules` is root-leading-dir only, the `:(glob)` forms match the
400
+ // `node_modules` path component exactly (nested link entry + nested contents).
401
+ const NODE_MODULES_SPECS = ['node_modules', ':(glob)**/node_modules', ':(glob)**/node_modules/**'];
402
+ runGit(['reset', '-q', '--', ...NODE_MODULES_SPECS], worktreePath);
403
+ const trackedNm = runGit(['diff', '--name-only', '--diff-filter=MD', 'HEAD', '--', ...NODE_MODULES_SPECS], worktreePath);
404
+ const keepNm = trackedNm.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
405
+ if (keepNm.length > 0) {
406
+ runGit(['add', '--', ...keepNm], worktreePath);
407
+ }
359
408
  // The files actually staged for this commit (post-exclusion) — also the
360
409
  // truthful files_changed report.
361
410
  const staged = runGit(['diff', '--cached', '--name-only'], worktreePath);
@@ -364,7 +413,7 @@ export function commitWorktreeOnBehalf(worktreePath, message, options = {}) {
364
413
  // Only transient files changed — nothing deliverable to commit. Restore the
365
414
  // index so the worktree is left exactly as the worker left it.
366
415
  runGit(['reset', '-q'], worktreePath);
367
- return { committed: false, files_changed: [], reason: 'no committable changes (only transient LANE-RESULT.json / .brainclaw)' };
416
+ return { committed: false, files_changed: [], reason: 'no committable changes (only transient LANE-RESULT.json / .brainclaw / node_modules links)' };
368
417
  }
369
418
  const authorName = options.authorName ?? 'brainclaw (on behalf)';
370
419
  const authorEmail = options.authorEmail ?? 'brainclaw@on-behalf.local';
@@ -482,6 +531,30 @@ export function findWorktreePathForBranch(worktrees, branchName) {
482
531
  *
483
532
  * Returns the absolute path to the newly created worktree.
484
533
  */
534
+ /**
535
+ * Whether the project looks like a Next.js app — a `next` dependency in
536
+ * package.json or a `next.config.*` at the root. Used to warn that the
537
+ * out-of-root `node_modules` symlink brainclaw provisions is rejected by
538
+ * `next dev` / Turbopack (trp_37b05a15), even though tsc / vitest / build accept
539
+ * it. Best-effort + defensive: any read/parse error → false (never blocks
540
+ * worktree creation over a heuristic).
541
+ */
542
+ export function projectUsesNextjs(projectRoot) {
543
+ try {
544
+ for (const cfg of ['next.config.js', 'next.config.mjs', 'next.config.ts', 'next.config.cjs']) {
545
+ if (fs.existsSync(path.join(projectRoot, cfg)))
546
+ return true;
547
+ }
548
+ const pkgPath = path.join(projectRoot, 'package.json');
549
+ if (!fs.existsSync(pkgPath))
550
+ return false;
551
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
552
+ return Boolean(pkg.dependencies?.next ?? pkg.devDependencies?.next);
553
+ }
554
+ catch {
555
+ return false;
556
+ }
557
+ }
485
558
  export function createWorktree(mainWorktreePath, branchName, options = {}) {
486
559
  // pln#614: resolve the true git toplevel first, so an in-tree project (project
487
560
  // dir ≠ git root) creates its worktree from the real repo root — `git worktree
@@ -610,6 +683,22 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
610
683
  for (const entry of sharedPaths) {
611
684
  trySymlinkSharedPath(entry);
612
685
  }
686
+ // trp_37b05a15 (field report, Next.js 16 / Turbopack) — the node_modules link
687
+ // brainclaw provisions is an out-of-worktree-root symlink to the main repo.
688
+ // tsc / vitest / build follow it fine, but `next dev` (Turbopack) PANICS on a
689
+ // node_modules link that points outside the worktree root. Surface a warning
690
+ // (not a failure — the link is still correct for build/typecheck) so a worker
691
+ // or operator doing dev-server work knows the workaround up front. A full
692
+ // Turbopack-compatible per-worktree dependency mode is a planned follow-up.
693
+ const linkedNodeModules = sharedPaths.some((p) => p === 'node_modules' || p.endsWith('/node_modules'));
694
+ if (linkedNodeModules && projectUsesNextjs(mainWorktreePath)) {
695
+ const msg = 'Next.js detected: node_modules is linked as an out-of-worktree-root symlink, which '
696
+ + '`next dev` / Turbopack rejects (it requires node_modules under the worktree root). '
697
+ + 'tsc / vitest / build are unaffected. For dev-server work in this worktree, run '
698
+ + '`npm install` here (optionally with BRAINCLAW_NO_LINK_DEPS=1), or smoke-test on the merged branch.';
699
+ symlinkWarnings.push(msg);
700
+ logger.warn(`[worktree] ${msg}`);
701
+ }
613
702
  // NOTE: .brainclaw/ is intentionally NOT symlinked.
614
703
  // Symlinking .brainclaw/ causes hooks and session_start to trigger on the
615
704
  // shared store, creating session conflicts and potentially blocking agents
package/dist/facts.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // Generated by scripts/emit-site-facts.mjs at build time. Do not edit manually.
2
- // Source: brainclaw v1.14.0 on 2026-07-05T13:38:40.276Z
2
+ // Source: brainclaw v1.16.0 on 2026-07-18T18:21:20.312Z
3
3
  export const FACTS = {
4
- "version": "1.14.0",
5
- "generated_at": "2026-07-05T13:38:40.276Z",
4
+ "version": "1.16.0",
5
+ "generated_at": "2026-07-18T18:21:20.312Z",
6
6
  "tools": {
7
7
  "count": 67,
8
- "published_count": 66,
8
+ "published_count": 65,
9
9
  "names": [
10
10
  "bclaw_bootstrap",
11
11
  "bclaw_release_notes",
@@ -77,7 +77,7 @@ export const FACTS = {
77
77
  ]
78
78
  },
79
79
  "entities": {
80
- "count": 17,
80
+ "count": 18,
81
81
  "names": [
82
82
  "plan",
83
83
  "step",
@@ -95,6 +95,7 @@ export const FACTS = {
95
95
  "assignment",
96
96
  "agent_run",
97
97
  "action",
98
+ "agent",
98
99
  "cross_project_link"
99
100
  ],
100
101
  "short_label_prefixes": {
@@ -114,6 +115,7 @@ export const FACTS = {
114
115
  "assignment": "asgn",
115
116
  "agent_run": "run",
116
117
  "action": "act",
118
+ "agent": "agt",
117
119
  "cross_project_link": "xpl"
118
120
  }
119
121
  },
@@ -472,7 +474,7 @@ export const FACTS = {
472
474
  },
473
475
  "bench": {
474
476
  "schema": "brainclaw.bench.v1",
475
- "generated_at": "2026-07-05T13:38:38.216Z",
477
+ "generated_at": "2026-07-18T18:21:18.026Z",
476
478
  "node_version": "v24.18.0",
477
479
  "platform": "linux-x64",
478
480
  "repeats": 3,
@@ -481,15 +483,15 @@ export const FACTS = {
481
483
  "name": "cold_onboard",
482
484
  "volume": "empty",
483
485
  "description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
484
- "duration_ms_median": 72,
485
- "payload_chars_median": 1650,
486
- "payload_tokens_est_median": 413
486
+ "duration_ms_median": 82,
487
+ "payload_chars_median": 1640,
488
+ "payload_tokens_est_median": 410
487
489
  },
488
490
  {
489
491
  "name": "warm_work",
490
492
  "volume": "medium",
491
493
  "description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
492
- "duration_ms_median": 126,
494
+ "duration_ms_median": 146,
493
495
  "payload_chars_median": 2626,
494
496
  "payload_tokens_est_median": 657
495
497
  },
@@ -497,7 +499,7 @@ export const FACTS = {
497
499
  "name": "first_edit",
498
500
  "volume": "medium",
499
501
  "description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
500
- "duration_ms_median": 8,
502
+ "duration_ms_median": 9,
501
503
  "payload_chars_median": 442,
502
504
  "payload_tokens_est_median": 111
503
505
  }
package/dist/facts.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.14.0",
3
- "generated_at": "2026-07-05T13:38:40.276Z",
2
+ "version": "1.16.0",
3
+ "generated_at": "2026-07-18T18:21:20.312Z",
4
4
  "tools": {
5
5
  "count": 67,
6
- "published_count": 66,
6
+ "published_count": 65,
7
7
  "names": [
8
8
  "bclaw_bootstrap",
9
9
  "bclaw_release_notes",
@@ -75,7 +75,7 @@
75
75
  ]
76
76
  },
77
77
  "entities": {
78
- "count": 17,
78
+ "count": 18,
79
79
  "names": [
80
80
  "plan",
81
81
  "step",
@@ -93,6 +93,7 @@
93
93
  "assignment",
94
94
  "agent_run",
95
95
  "action",
96
+ "agent",
96
97
  "cross_project_link"
97
98
  ],
98
99
  "short_label_prefixes": {
@@ -112,6 +113,7 @@
112
113
  "assignment": "asgn",
113
114
  "agent_run": "run",
114
115
  "action": "act",
116
+ "agent": "agt",
115
117
  "cross_project_link": "xpl"
116
118
  }
117
119
  },
@@ -470,7 +472,7 @@
470
472
  },
471
473
  "bench": {
472
474
  "schema": "brainclaw.bench.v1",
473
- "generated_at": "2026-07-05T13:38:38.216Z",
475
+ "generated_at": "2026-07-18T18:21:18.026Z",
474
476
  "node_version": "v24.18.0",
475
477
  "platform": "linux-x64",
476
478
  "repeats": 3,
@@ -479,15 +481,15 @@
479
481
  "name": "cold_onboard",
480
482
  "volume": "empty",
481
483
  "description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
482
- "duration_ms_median": 72,
483
- "payload_chars_median": 1650,
484
- "payload_tokens_est_median": 413
484
+ "duration_ms_median": 82,
485
+ "payload_chars_median": 1640,
486
+ "payload_tokens_est_median": 410
485
487
  },
486
488
  {
487
489
  "name": "warm_work",
488
490
  "volume": "medium",
489
491
  "description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
490
- "duration_ms_median": 126,
492
+ "duration_ms_median": 146,
491
493
  "payload_chars_median": 2626,
492
494
  "payload_tokens_est_median": 657
493
495
  },
@@ -495,7 +497,7 @@
495
497
  "name": "first_edit",
496
498
  "volume": "medium",
497
499
  "description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
498
- "duration_ms_median": 8,
500
+ "duration_ms_median": 9,
499
501
  "payload_chars_median": 442,
500
502
  "payload_tokens_est_median": 111
501
503
  }
package/docs/PROTOCOL.md CHANGED
@@ -131,9 +131,13 @@ offered → accepted → started → (progress)* → completed
131
131
  ```
132
132
 
133
133
  Each transition is emitted via `transition('assignment', id, <status>)`.
134
- A worker that cannot reach the protocol transport (sandboxed) MAY drop a
135
- `LANE-RESULT.json` file at the worktree root as the fallback signal — the
136
- coordinator harvests it.
134
+ A worker MAY drop a `LANE-RESULT.json` file at the worktree root and let the
135
+ coordinator harvest it instead of emitting transitions itself. This is the
136
+ coordinator-owned contract for a sandboxed worker: its sandbox makes `.git`
137
+ read-only (so it cannot `git commit`), and the file is the harvestable signal —
138
+ NOT because MCP is unreachable (a sandboxed worker does reach the MCP server,
139
+ which runs out-of-process; dec#133). A genuinely MCP-less agent uses the same
140
+ file as its only channel.
137
141
 
138
142
  ### 5.3 Claim sentinels
139
143
 
@@ -41,6 +41,9 @@ worker presumed dead
41
41
  │
42
42
  ├─ 1. LANE-RESULT.json at the worktree root?
43
43
  │ yes → it FINISHED. `brainclaw harvest <asgn> [--integrate]`. Done.
44
+ │ (review lane: a `review_verdict` in the file is mapped onto the review
45
+ │ loop by harvest — a `verdict` artifact is recorded and the loop
46
+ │ auto-closes on `approve`; no manual bclaw_loop drive needed.)
44
47
  │
45
48
  ├─ 2. git evidence (shared helper: commits_ahead + dirty_tracked,
46
49
  │ surfaced by dispatch-status / dispatch watch):
@@ -152,16 +152,16 @@ You called `bclaw_coordinate(intent="review", open_loop=true, …)` and got back
152
152
 
153
153
  ## Worktree-as-contract harvest
154
154
 
155
- Some dispatched workers cannot self-commit or call MCP. For example, a sandboxed Codex run may have `dispatchCanCommit=false` because its writable root is the linked worktree, while `.git` lives outside that root. In that case the worker contract is intentionally small:
155
+ Some dispatched workers cannot self-commit. For example, a sandboxed Codex run has `dispatchCanCommit=false` because its writable root is the linked worktree, while `.git` lives outside that root — so it cannot `git commit`. (It *can* still call brainclaw MCP — dec#133 — but the file-based contract below is used regardless, so harvesting stays coordinator-owned and does not depend on the worker's MCP writes.) In that case the worker contract is intentionally small:
156
156
 
157
157
  1. Edit files inside the dispatched worktree.
158
- 2. Write `LANE-RESULT.json` at the worktree root.
158
+ 2. Write `LANE-RESULT.json` at the worktree root — `{ assignment_id, status: completed|blocked|failed, summary, files_changed?, artifacts?, notes? }`. For a **review** lane, the worker also sets `review_verdict` (`approve` | `request_changes`) and `review_summary`; harvest maps those onto the review loop and auto-closes it on `approve` (pln#628 Focus 4B — see [loop-engine.md](./loop-engine.md#automation-extending-bclaw_coordinateintentreview)).
159
159
 
160
160
  The worker does not need to commit, call `bclaw_assignment_update`, or release the claim itself. The worktree is the contract.
161
161
 
162
162
  When the coordinator runs `brainclaw harvest <assignment_id> --integrate`, brainclaw reads the worker's `LANE-RESULT.json`, commits the linked worktree diff on the worker's behalf onto the lane branch, then completes the assignment and releases the claim, including the normal plan-status cascade.
163
163
 
164
- The on-behalf commit is guarded by the linked-worktree check (`isLinkedWorktree`): integration only targets the worktree associated with the assignment, never the main repository. This keeps sandboxed-worker harvesting from turning into an accidental main-repo commit path.
164
+ The on-behalf commit is guarded by the linked-worktree check (`isLinkedWorktree`): integration only targets the worktree associated with the assignment, never the main repository. This keeps sandboxed-worker harvesting from turning into an accidental main-repo commit path. It also excludes brainclaw's own transient/provisioned files from the lane commit — `LANE-RESULT.json`, `.brainclaw/`, `.brainclaw-worktree.json`, heartbeat files, and the `node_modules` link(s) brainclaw provisions (top-level + monorepo `**/node_modules`). The `node_modules` exclusion is tracked-aware: a project that *vendors* (commits) `node_modules` keeps a worker's change to a tracked file — only the freshly-provisioned link/dir is dropped.
165
165
 
166
166
  Integration is strictly additive and opt-in. Plain `brainclaw harvest <assignment_id>` remains report-only; it reads and reports the lane result without committing or mutating assignment / claim state. The on-behalf commit and lifecycle completion happen only when the coordinator passes `--integrate`.
167
167
 
@@ -255,7 +255,7 @@ A dead dispatch needs four cleanup steps (no single facade does all of them toda
255
255
 
256
256
  Spawn behaviour varies by agent. The capability profile in `src/core/agent-capability.ts` describes each agent's prompt delivery, sandbox model, and MCP availability. Per-agent caveats:
257
257
 
258
- - [codex.md](../integrations/codex.md#caveats) — `--sandbox workspace-write` required; spawned codex may not have brainclaw MCP wired; stdin_pipe prompt delivery; brief-ack required for headless dispatch detection.
258
+ - [codex.md](../integrations/codex.md#caveats) — `--sandbox workspace-write` required; sandboxed codex reaches MCP but cannot `git commit` (dec#133 — coordinator harvests the worktree diff); stdin_pipe prompt delivery; brief-ack required for headless dispatch detection.
259
259
  - [claude-code.md](../integrations/claude-code.md) — interactive vs `-p` headless modes; tools whitelist.
260
260
  - [copilot.md](../integrations/copilot.md), [windsurf.md](../integrations/windsurf.md), [cline.md](../integrations/cline.md), [opencode.md](../integrations/opencode.md), [roo.md](../integrations/roo.md), [kilocode.md](../integrations/kilocode.md), [continue.md](../integrations/continue.md) — per-agent specifics.
261
261
  - [mistral-vibe.md](../integrations/mistral-vibe.md) — EU/GDPR self-hosted option.
@@ -332,6 +332,8 @@ When `bclaw_coordinate(intent='review', open_loop: true)` is called, it:
332
332
  5. On turn completion with a verdict artifact, auto-advances; `reviewer_green` stop closes.
333
333
  6. On non-green verdict with `iteration_count < max`, advances to `author_response`, dispatches to author.
334
334
 
335
+ **How the verdict reaches the loop (shipped, pln#628 Focus 4B).** A dispatched reviewer worker does not call `bclaw_loop` itself — it writes its outcome to `LANE-RESULT.json` at the worktree root, now including an optional `review_verdict` (`approve` | `request_changes`) and `review_summary`. When the coordinator runs `brainclaw harvest <assignment_id>` (both the report-only path and `--integrate`), a review lane carrying a `review_verdict` is mapped onto its loop: brainclaw records a `verdict` artifact on the reviewer slot (`approve` → an `accepted…` body) and calls `advance`, which **auto-closes the loop on `reviewer_green` for `approve`** — no human driving `complete_turn`/`advance`. `request_changes` records the verdict and advances to `author_response` (the automated fix→re-review cycle is a follow-up). The mapping is idempotent, resolves the reviewer slot strictly by `assignment_id` (so symmetric multi-reviewer loops target the right slot), and runs the `complete_turn`+`advance` pair under the loop lock so an interrupted pass resumes rather than stalls.
336
+
335
337
  ### Symmetric review-AND-fix mode
336
338
 
337
339
  By default, the phases `findings` and `author_response` follow the classical asymmetric split — the reviewer identifies issues, the author applies fixes on the next turn. That doubles the number of round-trips: every issue needs one full turn to be identified, then another to be fixed.
@@ -514,7 +516,7 @@ Status after Codex schema review (cnd#574 / `dec_be66ccbf`, verdict `needs_revis
514
516
 
515
517
  The loop surface exposed over MCP is intentionally narrow:
516
518
 
517
- - **Review loops** — `bclaw_coordinate(intent="review", open_loop=true, review_mode="asymmetric"|"symmetric", targetAgents=[…])` opens the loop and dispatches the first turn. Drive subsequent turns with `bclaw_loop(intent="turn"|"complete_turn"|"advance"|"close")`.
519
+ - **Review loops** — `bclaw_coordinate(intent="review", open_loop=true, review_mode="asymmetric"|"symmetric", targetAgents=[…])` opens the loop and dispatches the first turn. The reviewer's verdict is then harvested from `LANE-RESULT.json` (`review_verdict`) and **auto-advances/closes the loop on approve** — no manual driving needed for the approve path (pln#628 Focus 4B). `bclaw_loop(intent="turn"|"complete_turn"|"advance"|"close")` remains available to drive turns by hand (e.g. the `request_changes` fix cycle, or a human-operated slot).
518
520
  - **Ideation loops** — `bclaw_coordinate(intent="ideate", preset="bootstrap")` opens an ideation loop from a preset.
519
521
 
520
522
  Custom phase lists (`LoopPhase[]`) and bespoke `StopCondition` logic exist in the loop engine internally, but are **not** exposed through the MCP facade today: `CoordinateRequestSchema` accepts only `open_loop`, `review_mode`, `preflight`, `ref`, and `preset` — no `phases` or `stop_condition` — and the standalone `bclaw_loop` tool does not expose an `open` intent. Programmatic construction of ad-hoc loops is therefore internal / future work until the facade is extended.
@@ -206,7 +206,7 @@ brainclaw stale resolve <plan_id> # → dropped (default for stale)
206
206
 
207
207
  **Symptom**: a dispatched assignment shows `running` indefinitely, and `bclaw_assignment_events` shows `run_running` but no further progress.
208
208
 
209
- **Why**: the spawned worker process either (a) crashed before reading its inbox, (b) read the inbox but couldn't acknowledge (e.g., MCP unavailable inside the spawned sandbox — common with codex `--sandbox workspace-write`), or (c) is genuinely still working but slow.
209
+ **Why**: the spawned worker process either (a) crashed before reading its inbox, (b) started but the brief-ack sentinel never fired (spawn wrapper died, wrong CODEX_HOME/auth, or the CLI exited early — NOT because the sandbox blocks MCP: dec#133 shows a sandboxed codex reaches MCP fine), or (c) is genuinely still working but slow.
210
210
 
211
211
  **Diagnostic order**:
212
212
 
@@ -43,7 +43,7 @@ Codex is CLI-spawnable for parallel lanes and dispatched workflows. The canonica
43
43
  codex exec -c approval_policy="never" --sandbox workspace-write "{prompt}"
44
44
  ```
45
45
 
46
- The `--sandbox workspace-write` setting is required, **not `read-only`** — the read-only sandbox blocks the MCP filesystem writes that brainclaw needs (worktree handoffs, session files, etc.) and forces fallback to GitHub connectors which fail for local-only commits.
46
+ The `--sandbox workspace-write` setting is required, **not `read-only`** — the worker needs to write files in its own worktree (the edits it produces, plus its `LANE-RESULT.json`) so the coordinator has a harvestable diff. This is about the worker's *file* writes, not MCP: the brainclaw MCP server runs out-of-sandbox and is reachable under either sandbox mode (dec#133). What the sandbox does block is `git commit` (`.git` is outside the writable root), so the coordinator commits the worktree diff at harvest.
47
47
 
48
48
  ### Prompt delivery: stdin_pipe (preferred)
49
49
 
@@ -53,7 +53,7 @@ When you (or the dispatcher) calls Codex with no positional `[PROMPT]`, Codex re
53
53
 
54
54
  ### Brief-ack handshake
55
55
 
56
- Since pln#476 (1.0.13+), spawned Codex workers are marked `delivered_and_started` once the wrapping shell touches a brief-ack sentinel at `.brainclaw/coordination/runtime/ack/<assignmentId>.ack`. This proves the spawn actually executed and decouples the handshake from MCP availability inside the worker — important because Codex spawned in `--sandbox workspace-write` may not have brainclaw MCP wired in its session.
56
+ Since pln#476 (1.0.13+), spawned Codex workers are marked `delivered_and_started` once the wrapping shell touches a brief-ack sentinel at `.brainclaw/coordination/runtime/ack/<assignmentId>.ack`. This proves the spawn actually executed and decouples the handshake from whatever the worker does next — the spawn is confirmed even before the worker makes its first MCP call. (Note — dec#133: a sandboxed Codex run **does** reach brainclaw MCP; the server runs out-of-sandbox and `approval_policy=never` auto-approves. The sentinel is about confirming the spawn, not about MCP being unavailable.)
57
57
 
58
58
  ## Capability profile
59
59
 
@@ -72,7 +72,7 @@ Since pln#476 (1.0.13+), spawned Codex workers are marked `delivered_and_started
72
72
 
73
73
  ## Caveats
74
74
 
75
- - **Sandbox blocks MCP writes**: when running with strict sandbox, the spawned Codex cannot write to `.brainclaw/` directly. Use the brief-ack file plus filesystem-direct candidate writes (the dispatcher harvests them).
75
+ - **Sandbox blocks `git commit`, not MCP** (dec#133): a sandboxed Codex run reaches brainclaw MCP (the server is a separate out-of-sandbox process; `approval_policy=never` auto-approves). What the sandbox *does* block is direct writes to paths outside the worktree root — notably `.git`, so the worker cannot `git commit`. Leave fixes uncommitted; the coordinator integrates + commits the worktree diff at harvest. A LANE-RESULT.json / filesystem-direct candidate write remains a valid fallback for reporting.
76
76
  - **Windows quoting**: long prompts containing backticks or `#` fail when passed as inline args through `cmd.exe`. The default stdin_pipe path avoids this.
77
77
  - **Sandbox vs review parity**: review runs use the same `workspace-write` sandbox as execution runs (older templates forced `read-only` on reviews; that path blocked PowerShell exec on Windows).
78
78
  - **No always-allow**: each MCP tool call still respects per-call approval policy unless explicitly set with `-c approval_policy="never"`.
@@ -103,7 +103,7 @@ The developer can dial back individual surfaces if needed, but the default is fu
103
103
 
104
104
  brainclaw's store mutations are serialized (MCP single-writer queue + file-based lock), so memory writes are safe even under contention.
105
105
 
106
- For **parallel work**, dispatch a sequence with `bclaw_dispatch(intent="execute")` — each lane gets its own auto-worktree under `~/.brainclaw/worktrees/<project-hash>/`, and the coordinator integrates with an octopus merge. For **review-fix loops**, `bclaw_coordinate(intent="review", open_loop=true, review_mode="symmetric")` runs an alternating review-and-fix conversation across two slots without shared-checkout collisions. These are the supported parallel paths today.
106
+ For **parallel work**, dispatch a sequence with `bclaw_dispatch(intent="execute")` — each lane gets its own auto-worktree under `~/.brainclaw/worktrees/<project-hash>/`, and the coordinator integrates with an octopus merge. For **review-fix loops**, `bclaw_coordinate(intent="review", open_loop=true, review_mode="symmetric")` runs an alternating review-and-fix conversation across two slots without shared-checkout collisions — and the loop **auto-closes on reviewer approve** (the verdict is harvested from `LANE-RESULT.json`, no manual drive). These are the supported parallel paths today.
107
107
 
108
108
  For **sequential work** in the same project, let one agent claim at a time and rely on handoffs to keep continuity across sessions.
109
109
 
@@ -10,6 +10,122 @@ guarantees this changelog follows.
10
10
 
11
11
  ## Unreleased
12
12
 
13
+ **Added — `LaneResultSchema.review_verdict` / `review_summary` (pln#628 Focus 4B)**
14
+ - `LANE-RESULT.json` (the worktree-root file a dispatched worker writes) gains two
15
+ optional fields: `review_verdict` (`approve` | `request_changes`) and
16
+ `review_summary` (one-line rationale). Absent on non-review lanes.
17
+ - Consumed by `brainclaw harvest` (report path + `--integrate`): a review lane
18
+ carrying a `review_verdict` is mapped onto its review loop — a `verdict` artifact
19
+ is recorded and the loop advances, auto-closing on `reviewer_green` for
20
+ `approve`. Additive + backward-compatible; no tool added/removed/renamed and no
21
+ surface-fingerprint change (LaneResultSchema is not part of the read/write
22
+ contract fingerprint).
23
+
24
+ **Added — `bclaw_update(entity='handoff')` incl. review/contract (pln#625 Phase 3)**
25
+ - The handoff update path is now wired. Previously `bclaw_update(entity='handoff')`
26
+ fell through to "not yet wired" (the field check passed for narrative/tags but
27
+ there was no handler case). `handoff.updatable` now also includes `review` and
28
+ `contract`, each validated against `HandoffReviewSchema` / `HandoffContractSchema`
29
+ and merged onto the record.
30
+ - This **restores the review-state write capability lost at v1.0** when
31
+ `bclaw_update_handoff` was removed: an agent can write a review verdict via
32
+ `bclaw_update(entity='handoff', data={ review: { verdict, summary, … } })`.
33
+ A verdict auto-stamps `review.reviewed_at`. The review loop's core
34
+ (`applyHandoffUpdates`) is unchanged — this is the canonical-grammar front door
35
+ onto the same record.
36
+ - Tip guard: a superseded (tombstoned) handoff is refused, pointing at the tip.
37
+ - No tool added/removed/renamed; `updatable` is not part of the surface
38
+ fingerprint, so no fingerprint change.
39
+
40
+ **Removed — `bclaw_list_agents` (pln#625; migrate to `bclaw_find(entity='agent')`)**
41
+ - `bclaw_list_agents` — the last surviving `bclaw_list_*` tool — is retired into
42
+ `REMOVED_IN_V1_TOOLS`: hidden from every `tools/list`, with a direct-call
43
+ deprecation warning pointing at `bclaw_find(entity='agent')`. The handler
44
+ stays as a redacted read escape-hatch (`LEGACY_READ_TOOL_HANDLERS`), same as
45
+ its `list_*` siblings.
46
+ - To preserve its one unique capability, `bclaw_find(entity='agent')` gains an
47
+ agent-only `includeReputation` filter that attaches the public reputation
48
+ summary per agent (same join the CLI `list-agents --with-reputation` uses).
49
+ - Net surface coherence: `agent` reads now flow through one grammar path with a
50
+ single redacted projection (`projectAgentForRead`), closing the divergent
51
+ double-surface (the old tool leaked raw `identity_key`/`invoke.env`).
52
+
53
+ **Changed — governance guard now covers grammar entities AND the filter contract (pln#625)**
54
+ - `tests/unit/mcp-governance.test.ts` folds two free-form parts of the callable
55
+ contract into the public-surface fingerprint: the set of addressable grammar
56
+ entities (`ENTITY_NAMES`) and the find/get filter grammar
57
+ (`GRAMMAR_FILTER_CONTRACT` — accepted keys, entity-scoping, constrained
58
+ values). Both were previously invisible (the `entity` and `filter` args are
59
+ free-form and their enumerating descriptions are stripped), so wiring a new
60
+ `bclaw_find/get(entity='…')` target or adding/re-scoping/re-valuing a filter
61
+ key (e.g. the Phase 2c `scope`) could ship without a changelog entry.
62
+ - `GRAMMAR_FILTER_CONTRACT` (exported from `entity-operations.ts`) is now the
63
+ single source of truth for the handler's filter validation AND the
64
+ fingerprint, so the two can never drift. A mutation test proves the
65
+ fingerprint reacts to an added entity, key, re-scope, and new value.
66
+ - Closes the blind spot surfaced by the Phase 2c ideation loop; the filter-grammar
67
+ extension came from the Codex review of PR #82.
68
+
69
+ **Added — read-only `agent` entity in the canonical grammar (pln#625 Phase 2c)**
70
+ - `bclaw_find/get(entity='agent')` are now wired. They return a REDACTED,
71
+ read-only projection: `id`, `name`, `kind`, `trust_level`, `capabilities`,
72
+ `fingerprint` (full sha256(PEM) — the public canonical key id), `model`,
73
+ `context_profile`, `created_at`. The private key material (`identity_key`,
74
+ `public_key` PEM) and `invoke` (unpopulated dead field; would leak
75
+ `invoke.command`) are never surfaced. Writes (`create/update/remove/transition`)
76
+ return the `SystemManagedError` boundary — agents are managed via
77
+ `bclaw_setup` / `enable-agent`, not the grammar.
78
+ - New agent-only filter `scope`: `bclaw_find(entity='agent')` defaults to the
79
+ current project's registry; `filter.scope='global'` additionally unions the
80
+ static dispatchable catalog (`getSpawnableAgents`) and annotates each entry
81
+ with `dispatchable` (canBeSpawnedCli) + `registered`. `scope` is rejected for
82
+ any other entity, and its value must be `project` (default) or `global`.
83
+ - `bclaw_list_agents` now redacts through the SAME projection (one source of
84
+ truth). It previously spread the raw identity document, leaking
85
+ `identity_key.public_key` and `invoke.env` in the clear — a pre-existing
86
+ disclosure, now closed. `includeReputation` still attaches the reputation
87
+ add-on.
88
+ - This supersedes the Phase 1a stopgap that reported `agent` as "not addressable
89
+ via the canonical grammar" (never released; last tag v1.15.0).
90
+
91
+ **Fixed — `bclaw_transition(entity='handoff')` wired (pln#625 Phase 2a)**
92
+ - The handoff lifecycle (`open→accepted|closed`, `accepted→closed`) is now
93
+ wired. It previously fell to the "not yet wired" default, which also broke
94
+ `brainclaw stale resolve <handoff-id>` (that command routes through the
95
+ canonical transition). A tip guard refuses to transition a handoff carrying
96
+ `superseded_by` (an immutable correction tombstone) and points at the tip.
97
+ - No tool was added, removed, or renamed; no required argument changed.
98
+
99
+ **Fixed — `bclaw_coordinate` published-schema parity (pln#622 PR0b)**
100
+ - The published inputSchema of `bclaw_coordinate` now declares `preset`
101
+ (loop preset selector, valid only with `intent='ideate'`; v1 ships the
102
+ single preset `bootstrap`; unknown names are rejected with
103
+ `unknown_preset`, other intents with `preset_kind_mismatch`) and
104
+ `client_request_id` (caller-minted ULID/UUIDv7 for idempotent retries,
105
+ observed on `intent='review'` + `open_loop=true`, safe elsewhere). Both
106
+ were already accepted by `CoordinateRequestSchema` and used by the
107
+ handler — and `next_actions` literally recommended
108
+ `bclaw_coordinate(intent='ideate', preset='bootstrap')` — but the catalog
109
+ never declared them, so strict MCP clients could not follow the product's
110
+ own recommendation.
111
+ - New guard: `tests/unit/mcp-facade-structural-parity.test.ts` asserts
112
+ bidirectional structural parity (keys + shared enum values) between the
113
+ hand-written facade schemas (`bclaw_work`, `bclaw_coordinate`) and their
114
+ zod sources, with an explicit allowlist for adapter-envelope fields
115
+ (`agent`, `agentId`).
116
+ - No tool was added, removed, or renamed; no required argument changed.
117
+ - Surface fingerprint bumped in the `(current)` section below.
118
+
119
+ **Changed — MCP model selection parity (pln#520/#606)**
120
+ - `bclaw_dispatch` and `bclaw_coordinate` gain an optional `model` string that
121
+ selects the spawned worker's model (e.g. `sonnet`, `gpt-5-codex`), decoupled
122
+ from agent identity — closing the CLI/MCP gap (the CLI `dispatch run --model`
123
+ already existed). Injected only for agents that declare a `model_flag`
124
+ (claude-code / codex / github-copilot); no-op otherwise, and consistent with
125
+ the dispatcher's resolveModel chain.
126
+ - No tool was removed or renamed; no required argument changed.
127
+ - Surface fingerprint bumped in the `(current)` section below.
128
+
13
129
  **Added — `bclaw_move` cross-project relocation (pln#595)**
14
130
  - New canonical-grammar verb `bclaw_move(entity, id, to_project, from_project?, force?)`:
15
131
  relocates a brainclaw item to another project in a multi-project workspace,
@@ -122,8 +238,43 @@ will still succeed. A follow-up PR will strip the dead handler code.
122
238
  changelog records the published MCP surface fingerprint. When a tool
123
239
  name, tier, category, or input schema changes, the test fails until
124
240
  this section is updated.
125
- - MCP public surface fingerprint: `sha256:2b0dfbd62acd71b7`
126
- (updated 2026-07-04 for trp#928: explicit `coordinator_override` boolean added
241
+ - MCP public surface fingerprint: `sha256:468f0103414e97e8`
242
+ (updated 2026-07-18 for pln#625 PR #83 + Codex review: `bclaw_list_agents`
243
+ retired and `bclaw_find(entity='agent')` gains `includeReputation` — now typed
244
+ as a boolean in `GRAMMAR_FILTER_CONTRACT.booleanKeys` and validated at the MCP
245
+ front door, so a non-boolean value is rejected instead of silently coercing to
246
+ a no-op. The added boolean-type declaration moves the fingerprint.)
247
+ Previous: `sha256:be0df1e4cc33936f`
248
+ (updated 2026-07-17 for pln#625 PR #83: `bclaw_list_agents` retired from the
249
+ published surface — migrated to `bclaw_find(entity='agent')` which gains an
250
+ agent-only `includeReputation` filter. Both the removed tool and the new
251
+ filter key move the fingerprint via the guard now covering PUBLISHED_TOOLS +
252
+ ENTITY_NAMES + GRAMMAR_FILTER_CONTRACT.)
253
+ Previous: `sha256:e12fd2f34dae1ac0`
254
+ (updated 2026-07-17 for pln#625 Phase 2c + PR #82: the fingerprint now folds in
255
+ two parts of the callable contract that the tool inputSchema cannot express —
256
+ the set of grammar-addressable entities (`ENTITY_NAMES`) and the find/get
257
+ filter grammar (`GRAMMAR_FILTER_CONTRACT`: accepted keys, entity-scoping, and
258
+ constrained values such as `scope=project|global`). Both were invisible to the
259
+ fingerprint before (`entity` and `filter` are free-form and their enumerating
260
+ descriptions are stripped), so wiring a new addressable entity — or adding /
261
+ re-scoping / re-valuing a filter key like the Phase 2c `scope` — slipped past
262
+ this guard. Additive: no tool added, removed, or renamed.)
263
+ Previous: `sha256:45c02576aff36244`
264
+ (updated 2026-07-15 for pln#622 PR0b: `preset` and `client_request_id` added
265
+ to the published `bclaw_coordinate` input schema. Both were already accepted
266
+ by `CoordinateRequestSchema` and used by the handler — and `next_actions`
267
+ recommended `bclaw_coordinate(intent='ideate', preset='bootstrap')` — but
268
+ the catalog never declared them. Additive: no tool added, removed, or
269
+ renamed; no required argument changed.
270
+ Previous: `sha256:b53eb56d4391b5a6`
271
+ updated 2026-07-15 for pln#520/#606: optional `model` string added to
272
+ `bclaw_dispatch` and `bclaw_coordinate` input schemas — selects the spawned
273
+ worker's model, decoupled from agent identity (CLI/MCP parity with
274
+ `dispatch run --model`). Additive: no tool added, removed, or renamed; no
275
+ required argument changed.
276
+ Previous: `sha256:2b0dfbd62acd71b7`
277
+ updated 2026-07-04 for trp#928: explicit `coordinator_override` boolean added
127
278
  to `bclaw_release_claim` and `bclaw_transition` input schemas — the coordinator
128
279
  path to release/stale a non-owned claim is now opt-in and audited rather than
129
280
  auto-derived from trust level. Additive: no tool added, removed, or renamed; no
@@ -24,7 +24,7 @@ With the plan and sequences established, assign the work to execution agents.
24
24
 
25
25
  - **Sequence-driven execution:** Use `bclaw_dispatch(intent="execute")` to parallelize plans across your defined sequence lanes automatically.
26
26
  - **Direct orchestration:** For ad-hoc delegation, use `bclaw_coordinate(intent="assign")` to assign specific tasks to target agents. This seamlessly generates the necessary claims and dispatch briefs.
27
- - **Reviews & Loops:** To open a structured review process on completed work, use `bclaw_coordinate(intent="review", open_loop=true)`.
27
+ - **Reviews & Loops:** To open a structured review process on completed work, use `bclaw_coordinate(intent="review", open_loop=true)`. The reviewer's verdict is harvested from its `LANE-RESULT.json` (`review_verdict`) and the loop **auto-closes on approve** — no manual `complete_turn`/`advance` round-trip to close the approve path (pln#628 Focus 4B).
28
28
  - Use `bclaw_dispatch_status` to verify worker liveness, examine log tails, and ensure dispatches are progressing healthily.
29
29
 
30
30
  ## Step 4: Manage the Inbox