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
@@ -42,6 +42,24 @@ export function evaluateWatchTick(input) {
42
42
  return 'running';
43
43
  }
44
44
  const AGENT_CHILD_NAMES = ['claude', 'codex', 'copilot', 'node'];
45
+ /**
46
+ * Parse `ps -A -o ppid=,comm=` output and return the lowercased command names
47
+ * whose parent pid equals `ppid`. Pure + exported so the ppid-filter logic is
48
+ * unit-tested independently of the platform the test runs on (trp_3b096bf4:
49
+ * BSD `ps` on macOS rejects the GNU `--ppid` flag, so we filter here instead).
50
+ */
51
+ export function parseChildCommsByPpid(psOutput, ppid) {
52
+ const target = Math.floor(ppid);
53
+ return psOutput
54
+ .split('\n')
55
+ .map((line) => {
56
+ const m = /^\s*(\d+)\s+(.*)$/.exec(line);
57
+ if (!m || Number(m[1]) !== target)
58
+ return undefined;
59
+ return m[2].trim().toLowerCase();
60
+ })
61
+ .filter((n) => !!n);
62
+ }
45
63
  /**
46
64
  * Does a real agent process live under the wrapper pid?
47
65
  * Returns undefined when the observation itself fails (never treated as death).
@@ -59,10 +77,15 @@ export function probeAgentChildAlive(wrapperPid) {
59
77
  const names = out.split(/\r?\n/).map((l) => l.trim().toLowerCase()).filter(Boolean);
60
78
  return names.some((n) => AGENT_CHILD_NAMES.some((a) => n.startsWith(a)));
61
79
  }
62
- const out = execFileSync('ps', ['-o', 'comm=', '--ppid', String(wrapperPid)], {
80
+ // BSD `ps` (macOS) does NOT support the GNU `--ppid` flag — it errored on
81
+ // every poll on macOS and broke child-pid discovery there (trp_3b096bf4).
82
+ // List every process with its parent pid + command (portable across Linux
83
+ // and macOS — same `-A -o …=` form already used in ai-surface-inventory.ts)
84
+ // and filter by ppid in parseChildCommsByPpid.
85
+ const out = execFileSync('ps', ['-A', '-o', 'ppid=,comm='], {
63
86
  encoding: 'utf-8', timeout: 15000,
64
87
  });
65
- const names = out.split('\n').map((l) => l.trim().toLowerCase()).filter(Boolean);
88
+ const names = parseChildCommsByPpid(out, wrapperPid);
66
89
  return names.some((n) => AGENT_CHILD_NAMES.some((a) => n.includes(a)));
67
90
  }
68
91
  catch {
@@ -22,6 +22,7 @@ import { loadAssignment, transitionAssignment } from '../core/assignments.js';
22
22
  import { loadClaim, releaseClaimsCascade, logCascadeReleaseResult } from '../core/claims.js';
23
23
  import { getCapabilityProfile, dispatchCanCommit } from '../core/agent-capability.js';
24
24
  import { commitWorktreeOnBehalf, worktreesBaseDir, resolveGitToplevel } from '../core/worktree.js';
25
+ import { closeReviewLoopFromLaneResult } from '../core/review-loop-close.js';
25
26
  /**
26
27
  * Auto-detect all worktree directories under the brainclaw-managed base dir.
27
28
  * Returns subdirectories that exist on disk (may or may not have an inbox).
@@ -112,10 +113,11 @@ function collectWorktreeCandidateFiles(worktreePath) {
112
113
  /**
113
114
  * Harvest candidates from worktree inboxes into the main project store.
114
115
  *
115
- * This is the coordinator-side fix for gap 5 of E2E test n°1: agents
116
- * running under `--sandbox workspace-write` cannot reach the main store
117
- * via MCP. They write candidates directly to their worktree inbox; the
118
- * coordinator calls `harvestCandidates` to sync them.
116
+ * This is the coordinator-side fix for gap 5 of E2E test n°1: a worker that
117
+ * cannot write to the main store — a genuinely MCP-less agent, or (post
118
+ * dec#133) a sandboxed codex whose `.git` is read-only so it cannot commit —
119
+ * leaves candidates in its worktree inbox; the coordinator calls
120
+ * `harvestCandidates` to sync them.
119
121
  *
120
122
  * @returns HarvestResult with counts of harvested, skipped, and errors.
121
123
  */
@@ -231,8 +233,9 @@ export function runHarvestCandidates(options = {}) {
231
233
  //
232
234
  // A dispatched worker writes a single `LANE-RESULT.json` at its worktree root
233
235
  // as its final step. This is the standard, brief-boilerplate-free channel for a
234
- // worker (especially a sandboxed one that cannot reach MCP) to report its
235
- // outcome. The coordinator ingests it with `brainclaw harvest <assignment_id>`.
236
+ // worker (a genuinely MCP-less agent, or a sandboxed codex that cannot git
237
+ // commit) to report its outcome. The coordinator ingests it with
238
+ // `brainclaw harvest <assignment_id>`.
236
239
  // ─────────────────────────────────────────────────────────────────────────────
237
240
  /** Conventional path of a worker's lane-result file at the worktree root. */
238
241
  export function getLaneResultPath(worktreePath) {
@@ -269,6 +272,18 @@ export function harvestLaneResults(options = {}) {
269
272
  // Assignment filter (when harvesting a specific lane).
270
273
  if (options.assignmentId && lane.assignment_id !== options.assignmentId)
271
274
  continue;
275
+ // pln#628 Focus 4B (Codex review of #87 BLOCKING 1) — a review lane must
276
+ // close/advance its loop on the plain report-only harvest path too, not only
277
+ // on `--integrate`. closeReviewLoopFromLaneResult is convergent + idempotent
278
+ // (a terminal loop is a no-op; a stuck approve is resumed), so firing it here
279
+ // AND in integrateLaneResults is safe — and it runs BEFORE the harvested
280
+ // marker short-circuits below, so a re-harvest still resumes a stuck loop.
281
+ try {
282
+ const laneAssignment = loadAssignment(lane.assignment_id, cwd);
283
+ if (laneAssignment)
284
+ closeReviewLoopFromLaneResult(laneAssignment, lane, agent, cwd);
285
+ }
286
+ catch { /* never block harvest on loop-close */ }
272
287
  const marker = laneHarvestedMarkerPath(cwd, lane.assignment_id);
273
288
  if (fs.existsSync(marker)) {
274
289
  result.skipped.push(lane.assignment_id);
@@ -455,6 +470,16 @@ export function integrateLaneResults(options = {}) {
455
470
  if (claimEntry && !claimEntry.released) {
456
471
  reasons.push(`claim release ${claimEntry.reason}${claimEntry.error ? `: ${claimEntry.error}` : ''}`);
457
472
  }
473
+ // pln#628 Focus 4B — if this lane is a review-loop turn carrying a
474
+ // verdict, map it onto the loop: record the verdict artifact + advance,
475
+ // which auto-closes the loop on reviewer_green (approve) without a human
476
+ // driving complete_turn/advance by hand. No-op for non-review lanes or
477
+ // lanes without a verdict; never throws (harvest is not blocked on it).
478
+ const loopClose = closeReviewLoopFromLaneResult(assignment, lane, actor, cwd);
479
+ if (loopClose) {
480
+ entry.review_loop = loopClose;
481
+ reasons.push(`review-loop ${loopClose.loop_id}: ${loopClose.action} — ${loopClose.reason}`);
482
+ }
458
483
  }
459
484
  else {
460
485
  // blocked / failed: best-effort lifecycle (FSM may reject from offered).