brainclaw 1.17.0 → 1.19.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 (97) hide show
  1. package/README.md +5 -5
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/commands/code-map.js +4 -1
  4. package/dist/commands/codev.js +61 -30
  5. package/dist/commands/doctor.js +14 -1
  6. package/dist/commands/harvest.js +223 -43
  7. package/dist/commands/inbox.js +10 -4
  8. package/dist/commands/install-hooks.js +184 -27
  9. package/dist/commands/loop.js +2 -2
  10. package/dist/commands/loops-handlers.js +82 -1
  11. package/dist/commands/mcp-catalog.js +12 -4
  12. package/dist/commands/mcp-read-handlers.js +90 -7
  13. package/dist/commands/mcp-schemas.generated.js +3 -0
  14. package/dist/commands/mcp-write-claims.js +57 -0
  15. package/dist/commands/mcp-write-coordination.js +216 -57
  16. package/dist/commands/mcp-write-entities.js +11 -0
  17. package/dist/commands/mcp.js +29 -2
  18. package/dist/commands/session-end.js +15 -0
  19. package/dist/commands/session-start.js +19 -0
  20. package/dist/core/agentrun-reconciler.js +171 -7
  21. package/dist/core/agentruns.js +6 -1
  22. package/dist/core/claim-conformity.js +193 -0
  23. package/dist/core/claim-scope.js +155 -0
  24. package/dist/core/claims.js +127 -2
  25. package/dist/core/code-map/aggregate.js +473 -0
  26. package/dist/core/code-map/backend.js +36 -10
  27. package/dist/core/code-map/freshness.js +36 -1
  28. package/dist/core/code-map/lang/c/imports.scm +12 -0
  29. package/dist/core/code-map/lang/c/index.js +150 -0
  30. package/dist/core/code-map/lang/c/tags.scm +68 -0
  31. package/dist/core/code-map/lang/cpp/imports.scm +14 -0
  32. package/dist/core/code-map/lang/cpp/index.js +149 -0
  33. package/dist/core/code-map/lang/cpp/tags.scm +87 -0
  34. package/dist/core/code-map/lang/csharp/imports.scm +20 -0
  35. package/dist/core/code-map/lang/csharp/index.js +224 -0
  36. package/dist/core/code-map/lang/csharp/tags.scm +63 -0
  37. package/dist/core/code-map/lang/go/imports.scm +13 -0
  38. package/dist/core/code-map/lang/go/index.js +139 -0
  39. package/dist/core/code-map/lang/go/tags.scm +36 -0
  40. package/dist/core/code-map/lang/providers.js +12 -1
  41. package/dist/core/code-map/lang/ruby/imports.scm +24 -0
  42. package/dist/core/code-map/lang/ruby/index.js +198 -0
  43. package/dist/core/code-map/lang/ruby/tags.scm +49 -0
  44. package/dist/core/code-map/lang/rust/imports.scm +44 -0
  45. package/dist/core/code-map/lang/rust/index.js +136 -0
  46. package/dist/core/code-map/lang/rust/tags.scm +47 -0
  47. package/dist/core/code-map/query.js +229 -80
  48. package/dist/core/code-map/types.js +18 -0
  49. package/dist/core/code-map/work-section.js +8 -7
  50. package/dist/core/codev-responses.js +16 -0
  51. package/dist/core/dispatcher.js +176 -22
  52. package/dist/core/execution-adapters.js +29 -3
  53. package/dist/core/facade-schema.js +32 -0
  54. package/dist/core/guidance-telemetry.js +197 -0
  55. package/dist/core/ideation-loop-close.js +152 -0
  56. package/dist/core/instruction-templates.js +11 -3
  57. package/dist/core/loops/artifact-resolver.js +197 -0
  58. package/dist/core/loops/attempt-reservation.js +576 -0
  59. package/dist/core/loops/commit-intent.js +494 -0
  60. package/dist/core/loops/facade-schema.js +48 -0
  61. package/dist/core/loops/impl-bind.js +144 -0
  62. package/dist/core/loops/index.js +1 -1
  63. package/dist/core/loops/iteration-engine.js +29 -0
  64. package/dist/core/loops/lock.js +14 -0
  65. package/dist/core/loops/project-resolution.js +157 -0
  66. package/dist/core/loops/reconcile-turn.js +369 -0
  67. package/dist/core/loops/result-reducers.js +88 -0
  68. package/dist/core/loops/store.js +46 -7
  69. package/dist/core/loops/types.js +139 -11
  70. package/dist/core/loops/verbs.js +49 -4
  71. package/dist/core/loops/verify-command.js +209 -0
  72. package/dist/core/messaging.js +58 -5
  73. package/dist/core/next-actions.js +157 -0
  74. package/dist/core/review-loop-close.js +27 -6
  75. package/dist/core/review-loop-turn-dispatch.js +290 -28
  76. package/dist/core/runtime-signals.js +68 -0
  77. package/dist/core/schema.js +64 -0
  78. package/dist/core/surface-freshness.js +150 -0
  79. package/dist/core/warnings.js +98 -0
  80. package/dist/core/worktree.js +24 -0
  81. package/dist/facts.js +9 -9
  82. package/dist/facts.json +8 -8
  83. package/dist/wasm/tree-sitter-c.wasm +0 -0
  84. package/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
  85. package/dist/wasm/tree-sitter-cpp.wasm +0 -0
  86. package/dist/wasm/tree-sitter-go.wasm +0 -0
  87. package/dist/wasm/tree-sitter-ruby.wasm +0 -0
  88. package/dist/wasm/tree-sitter-rust.wasm +0 -0
  89. package/docs/cli.md +1 -1
  90. package/docs/code-map.md +22 -6
  91. package/docs/concepts/loop-engine.md +24 -0
  92. package/docs/concepts/observer-protocol.md +22 -0
  93. package/docs/concepts/plans-and-claims.md +57 -0
  94. package/docs/integrations/claude-code.md +53 -0
  95. package/docs/integrations/mcp.md +45 -0
  96. package/docs/mcp-schema-changelog.md +118 -2
  97. package/package.json +1 -1
@@ -479,6 +479,13 @@ export const InboxMessageSchema = z.object({
479
479
  read_at: z.string().optional(),
480
480
  /** When the message was acknowledged */
481
481
  ack_at: z.string().optional(),
482
+ /** True when the body was truncated at WRITE time because it exceeded the
483
+ * inline size cap (pln#627 Phase B). Unlike read-time previews, the omitted
484
+ * tail is NOT stored inline — the full artifact belongs in a dedicated store
485
+ * (e.g. ideation responses), with the message carrying only a pointer. */
486
+ truncated_at_write: z.boolean().optional(),
487
+ /** Original body length in characters before write-time truncation. */
488
+ original_text_length: z.number().int().nonnegative().optional(),
482
489
  created_at: z.string(),
483
490
  updated_at: z.string(),
484
491
  author: z.string(),
@@ -708,6 +715,28 @@ export const ClaimSchema = z.object({
708
715
  assignment_message_id: z.string().optional(),
709
716
  /** Assignment ID from the Agent SDK runtime protocol. Links claim to its Assignment lifecycle entity. */
710
717
  assignment_id: z.string().optional(),
718
+ /**
719
+ * pln#636 C0-b — commit the claim's work started FROM, recorded at creation.
720
+ *
721
+ * This is the immutable baseline any "what did this claim actually touch?"
722
+ * comparison needs. The design review settled the question by rejecting both
723
+ * options it offered: neither `git diff` against HEAD nor the worktree's dirty
724
+ * set is authoritative, because a lane that commits mid-work moves the ground
725
+ * under both. A fixed point recorded up front is the only honest basis.
726
+ *
727
+ * Optional and never backfilled: the 613 claims that predate this field simply
728
+ * have no baseline, and a conformity check must treat that as `unverifiable`
729
+ * rather than guessing one (see core/claim-scope.ts on the inverted default).
730
+ */
731
+ base_sha: z.string().optional(),
732
+ /**
733
+ * pln#636 C0-b — file footprint the claim DECLARES, when its creator knows it.
734
+ *
735
+ * Raises conformity coverage above what classifying a free-string `scope` can
736
+ * reach (57.6% of the live corpus is path-resolvable). Purely additive: absent
737
+ * means "fall back to classifying `scope`", never "no files allowed".
738
+ */
739
+ paths: z.array(z.string()).optional(),
711
740
  });
712
741
  // --- Assignment schemas (Agent SDK runtime protocol) ---
713
742
  export const AssignmentStatusSchema = z.enum([
@@ -956,6 +985,9 @@ export const RuntimeEventTypeSchema = z.enum([
956
985
  'candidate_harvested',
957
986
  'lane_result_harvested',
958
987
  'lane_integrated',
988
+ // pln#521 P4 — a turn-owned loop artifact was harvested + integrated into the loop
989
+ // by reconcileTurn (observability for the harvest path).
990
+ 'loop_artifact_harvested',
959
991
  ]);
960
992
  /**
961
993
  * pln#526 — LANE-RESULT convention. A dispatched worker writes a single
@@ -964,8 +996,24 @@ export const RuntimeEventTypeSchema = z.enum([
964
996
  * environment, e.g. a genuinely MCP-less agent). The coordinator ingests it with
965
997
  * `brainclaw harvest <assignment_id>`.
966
998
  */
999
+ /**
1000
+ * Largest inline worker body accepted in a LANE-RESULT. This is deliberately
1001
+ * larger than a loop artifact body: harvest persists the original body in its
1002
+ * durable runtime event before a loop closer applies its smaller display cap.
1003
+ */
1004
+ export const LANE_RESULT_BODY_MAX_BYTES = 64 * 1024;
967
1005
  export const LaneResultSchema = z.object({
968
1006
  assignment_id: z.string(),
1007
+ /**
1008
+ * pln#630 PR2b-a (§13 R2/R3) — turn-attempt correlation keys. Optional for
1009
+ * backward compat (legacy lanes are assignment-keyed only); a loop-dispatched
1010
+ * lane echoes all three so the read-strict acceptance path can prove WHICH
1011
+ * attempt+generation produced this result. `nonce` == the consumed launch
1012
+ * token (the epoch-unique generation id), NOT a turn_id-bound value.
1013
+ */
1014
+ turn_id: z.string().optional(),
1015
+ run_id: z.string().optional(),
1016
+ nonce: z.string().optional(),
969
1017
  status: z.enum(['completed', 'blocked', 'failed']),
970
1018
  summary: z.string(),
971
1019
  /** Paths or refs the worker produced (commits, files, docs). */
@@ -974,6 +1022,18 @@ export const LaneResultSchema = z.object({
974
1022
  files_changed: z.array(z.string()).optional(),
975
1023
  /** Free-form notes (blockers, follow-ups). */
976
1024
  notes: z.string().optional(),
1025
+ /**
1026
+ * Full worker reasoning or review content. Unlike `summary`, this is the
1027
+ * durable handoff payload and is copied into the coordinator-side harvest
1028
+ * event, so it survives worktree cleanup. Optional for legacy workers.
1029
+ */
1030
+ body: z.string().refine((body) => Buffer.byteLength(body, 'utf8') <= LANE_RESULT_BODY_MAX_BYTES, `LANE-RESULT.body must be ≤ ${LANE_RESULT_BODY_MAX_BYTES} bytes`).optional(),
1031
+ /**
1032
+ * Type the worker associated with `body`. Optional because legacy
1033
+ * `artifacts` remains a list of opaque labels/refs. A loop harvester may
1034
+ * reconcile this to its phase's required artifact type.
1035
+ */
1036
+ artifact_type: z.string().min(1).optional(),
977
1037
  /**
978
1038
  * pln#628 Focus 4B — review-loop verdict. A worker running a review-loop turn
979
1039
  * sets this to signal whether the change is good to merge (`approve`) or needs
@@ -999,6 +1059,10 @@ export const RuntimeEventSchema = z.object({
999
1059
  tags: TagsWithDefaultSchema,
1000
1060
  assignment_id: z.string().optional(),
1001
1061
  run_id: z.string().optional(),
1062
+ // pln#630 PR2b-a (§13 R2/R3) — turn-attempt correlation on runtime signals.
1063
+ // `run_id` already present above; `nonce` == launch-generation token.
1064
+ turn_id: z.string().optional(),
1065
+ nonce: z.string().optional(),
1002
1066
  claim_id: z.string().optional(),
1003
1067
  message_id: z.string().optional(),
1004
1068
  plan_id: z.string().optional(),
@@ -0,0 +1,150 @@
1
+ /**
2
+ * pln#638 volet 2b — lazy freshness reconcile for generated guidance surfaces.
3
+ *
4
+ * WHY THIS EXISTS. 2a made the live header HONEST: it stopped claiming
5
+ * "auto-refreshed" and started naming its real triggers (session-end, handoff,
6
+ * `export --write`) plus the version and timestamp that wrote it. Honesty alone
7
+ * does not help an agent tier that never fires any of those triggers, though — it
8
+ * just tells that tier, truthfully, that the file might be arbitrarily old. 2b
9
+ * closes the loop by USING the stamp: compare it against the running version and
10
+ * say so, once, at a path we already visit.
11
+ *
12
+ * NO DAEMON, NO WATCHER — the validated lazy-reconcile pattern. The check is a
13
+ * pure comparison plus a directory scan of a registry that already exists
14
+ * (`AGENT_EXPORT_REGISTRY` / `LIVE_COMPANION_EXPORT_REGISTRY`), so it is DERIVED
15
+ * rather than enumerated. That is review finding F1 applied here: a hand-kept
16
+ * list of generated surfaces would itself be an unguarded generated surface, and
17
+ * would reproduce the exact defect this plan exists to fix.
18
+ *
19
+ * ADVISORY, AND SILENT ON DOUBT. A surface with no stamp is not stale — it is
20
+ * unknown (it may predate the stamp, or be hand-written by the operator). Only a
21
+ * stamp that PARSES and names a DIFFERENT version is reported. Nothing here
22
+ * rewrites a file: regeneration stays the explicit act it always was.
23
+ *
24
+ * @module
25
+ */
26
+ import fs from 'node:fs';
27
+ import path from 'node:path';
28
+ import { AGENT_EXPORT_REGISTRY, LIVE_COMPANION_EXPORT_REGISTRY } from './agent-files.js';
29
+ /**
30
+ * Matches the provenance line emitted by `renderLiveHeader`
31
+ * (instruction-templates.ts) and by the protocol-skill front-matter.
32
+ *
33
+ * Deliberately tolerant about what follows the version: the timestamp format is
34
+ * not what this parser is for, and a stricter pattern would go stale the first
35
+ * time the header gains a field.
36
+ */
37
+ const PROVENANCE_RE = /Written by brainclaw v(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
38
+ /** `brainclaw_version: X` in a generated SKILL.md front-matter. */
39
+ const SKILL_PROVENANCE_RE = /^\s*brainclaw_version:\s*v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\s*$/m;
40
+ /** Read the provenance stamp out of a generated surface's content. Never throws. */
41
+ export function parseSurfaceProvenance(content) {
42
+ const header = PROVENANCE_RE.exec(content);
43
+ if (header?.[1])
44
+ return { version: header[1] };
45
+ const skill = SKILL_PROVENANCE_RE.exec(content);
46
+ if (skill?.[1])
47
+ return { version: skill[1] };
48
+ return {};
49
+ }
50
+ /**
51
+ * Compare one surface's stamp against the running version.
52
+ *
53
+ * An UNKNOWN stamp is never reported as stale. Treating "no stamp" as "out of
54
+ * date" would fire on every hand-written AGENTS.md in every project that ever
55
+ * adopted brainclaw — the false-positive failure mode that teaches agents to
56
+ * ignore a channel.
57
+ */
58
+ export function assessSurfaceFreshness(content, currentVersion) {
59
+ const { version } = parseSurfaceProvenance(content);
60
+ if (!version)
61
+ return { kind: 'unknown', reason: 'no brainclaw provenance stamp' };
62
+ if (version === currentVersion)
63
+ return { kind: 'fresh', version };
64
+ return { kind: 'stale', stampedVersion: version, currentVersion };
65
+ }
66
+ /**
67
+ * The set of surfaces this project could have on disk, derived from the export
68
+ * registries rather than listed here. Deduplicated because several agents share
69
+ * a target (four of them write AGENTS.md).
70
+ */
71
+ function candidateSurfacePaths() {
72
+ return [...new Set([
73
+ ...AGENT_EXPORT_REGISTRY.map((t) => t.relativePath),
74
+ ...LIVE_COMPANION_EXPORT_REGISTRY.map((t) => t.relativePath),
75
+ ])];
76
+ }
77
+ /**
78
+ * Scan the project's generated surfaces and report the ones stamped with a
79
+ * different brainclaw version.
80
+ *
81
+ * Cheap by construction: it only stats/reads files the registries name (~25
82
+ * paths, most absent in any given project), and reads at most the head of each —
83
+ * the stamp is in the header, so there is no reason to pull a whole file into
84
+ * memory. Never throws; an unreadable file is simply not reported.
85
+ */
86
+ export function reconcileSurfaceFreshness(cwd, currentVersion) {
87
+ const result = { stale: [], freshCount: 0, unknownCount: 0 };
88
+ for (const relativePath of candidateSurfacePaths()) {
89
+ const full = path.join(cwd, relativePath);
90
+ let head;
91
+ try {
92
+ if (!fs.existsSync(full))
93
+ continue;
94
+ // The stamp lives in the header; 4KB covers it with room to spare.
95
+ const fd = fs.openSync(full, 'r');
96
+ try {
97
+ const buf = Buffer.alloc(4096);
98
+ const read = fs.readSync(fd, buf, 0, buf.length, 0);
99
+ head = buf.subarray(0, read).toString('utf-8');
100
+ }
101
+ finally {
102
+ fs.closeSync(fd);
103
+ }
104
+ }
105
+ catch {
106
+ continue; // unreadable → not reported, never a crash
107
+ }
108
+ const verdict = assessSurfaceFreshness(head, currentVersion);
109
+ if (verdict.kind === 'stale')
110
+ result.stale.push({ relativePath, stampedVersion: verdict.stampedVersion });
111
+ else if (verdict.kind === 'fresh')
112
+ result.freshCount += 1;
113
+ else
114
+ result.unknownCount += 1;
115
+ }
116
+ return result;
117
+ }
118
+ /**
119
+ * Build the advisory for a stale-surface scan, or `undefined` when there is
120
+ * nothing to say.
121
+ *
122
+ * NO `next_actions`, deliberately. The recovery is `brainclaw export --write`,
123
+ * and there is no MCP tool that performs it — `bclaw_setup` is the onboarding
124
+ * wizard and takes no write flag. Pointing at it anyway would ship a next_action
125
+ * whose args the engine rejects, which is the precise class of drift this plan
126
+ * exists to eliminate; and per pln#634's own rule, a builder with no genuine
127
+ * follow-up returns nothing rather than inventing one. The command therefore
128
+ * travels in the message, where it is true.
129
+ */
130
+ export function staleSurfaceWarning(result, currentVersion) {
131
+ if (result.stale.length === 0)
132
+ return undefined;
133
+ const shown = result.stale.slice(0, 8);
134
+ const overflow = result.stale.length - shown.length;
135
+ return {
136
+ code: 'generated_surfaces_stale',
137
+ message: `${result.stale.length} generated guidance surface(s) were written by an older brainclaw than v${currentVersion}: `
138
+ + shown.map((s) => `${s.relativePath} (v${s.stampedVersion})`).join(', ')
139
+ + (overflow > 0 ? ` (+${overflow} more)` : '')
140
+ + '. An agent tier that never triggers a regeneration is reading them as-is.'
141
+ + ' Run `brainclaw export --write` to refresh them.',
142
+ data: {
143
+ current_version: currentVersion,
144
+ stale_surfaces: shown.map((s) => ({ path: s.relativePath, stamped_version: s.stampedVersion })),
145
+ ...(overflow > 0 ? { stale_surfaces_omitted: overflow } : {}),
146
+ refresh_command: 'brainclaw export --write',
147
+ },
148
+ };
149
+ }
150
+ //# sourceMappingURL=surface-freshness.js.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Codes that historically shipped as a JSON blob keep shipping that exact blob,
3
+ * so no existing consumer sees a changed string. The set is enumerated rather
4
+ * than inferred so a NEW code cannot accidentally start emitting JSON at a
5
+ * consumer that only ever saw prose.
6
+ */
7
+ const LEGACY_JSON_CODES = new Set([
8
+ 'agent_validation_failed',
9
+ 'plan_already_assigned',
10
+ 'scope_already_claimed',
11
+ ]);
12
+ /** Derive the legacy `warnings` string for a structured warning. */
13
+ export function renderLegacyWarning(detail) {
14
+ if (LEGACY_JSON_CODES.has(detail.code)) {
15
+ return JSON.stringify({ warning: detail.code, ...(detail.data ?? {}) });
16
+ }
17
+ return detail.message;
18
+ }
19
+ /**
20
+ * Build the structured record without touching any legacy channel.
21
+ *
22
+ * Used by surfaces that have NO historical `warnings: string[]` to stay
23
+ * compatible with — a field introduced already-structured (pln#636 C2's
24
+ * `LaneHarvestResult.warnings`, for one) should not have to invent a throwaway
25
+ * string array just to reach this shape.
26
+ */
27
+ export function toWarningDetail(input) {
28
+ return {
29
+ code: input.code,
30
+ message: input.message,
31
+ ...(input.data ? { data: input.data } : {}),
32
+ ...(input.next_actions?.length ? { next_actions: input.next_actions } : {}),
33
+ };
34
+ }
35
+ /**
36
+ * Record a structured warning into BOTH channels at once.
37
+ *
38
+ * Taking the two arrays as parameters (rather than owning them) is what keeps
39
+ * this additive: the caller's `warnings: string[]` stays the same object it
40
+ * already passes by reference to its own helpers.
41
+ */
42
+ export function pushStructuredWarning(warnings, details, input) {
43
+ const detail = toWarningDetail(input);
44
+ details.push(detail);
45
+ warnings.push(renderLegacyWarning(detail));
46
+ }
47
+ // ── Builders for the migrated sites ─────────────────────────────────────────
48
+ // Each owns its recovery path, which is the entire point of the structured
49
+ // channel: `scope_already_claimed` used to be a dead-end string; now it names
50
+ // the two calls that resolve it.
51
+ export function agentValidationFailedWarning(input) {
52
+ return {
53
+ code: 'agent_validation_failed',
54
+ message: `Agent '${input.agent}' cannot be dispatched to${input.reason ? `: ${input.reason}` : ''}.`,
55
+ data: { agent: input.agent, code: input.code, reason: input.reason },
56
+ next_actions: [{
57
+ tool: 'bclaw_find',
58
+ args: { entity: 'agent', filter: { scope: 'global' } },
59
+ when: 'list the dispatchable agents and pick a target that is actually spawnable',
60
+ }],
61
+ };
62
+ }
63
+ export function planAlreadyAssignedWarning(input) {
64
+ return {
65
+ code: 'plan_already_assigned',
66
+ message: `'${input.planId}' already has an active assignment for ${input.existingAgent} — this call adds a second one.`,
67
+ data: { plan_id: input.planId, existing_agent: input.existingAgent },
68
+ next_actions: [{
69
+ tool: 'bclaw_find',
70
+ args: { entity: 'assignment', filter: { agent: input.existingAgent, status: 'offered' } },
71
+ when: 'inspect the existing assignment before letting two agents work the same scope',
72
+ }],
73
+ };
74
+ }
75
+ export function scopeAlreadyClaimedWarning(input) {
76
+ return {
77
+ code: 'scope_already_claimed',
78
+ message: `Scope '${input.scope}' is already claimed by ${input.existingAgent} (${input.existingClaimId}).`,
79
+ data: {
80
+ scope: input.scope,
81
+ existing_agent: input.existingAgent,
82
+ existing_claim_id: input.existingClaimId,
83
+ },
84
+ next_actions: [
85
+ {
86
+ tool: 'bclaw_get',
87
+ args: { entity: 'claim', id: input.existingClaimId },
88
+ when: 'see who holds the scope and since when before creating a second claim on it',
89
+ },
90
+ {
91
+ tool: 'bclaw_coordinate',
92
+ args: { intent: 'reroute', task: `Reassign work on ${input.scope}`, scope: input.scope },
93
+ when: 'hand the existing claim to another agent instead of double-claiming the scope',
94
+ },
95
+ ],
96
+ };
97
+ }
98
+ //# sourceMappingURL=warnings.js.map
@@ -1360,6 +1360,30 @@ export function isBranchMergedByContent(mainWorktreePath, branchName, baseRef =
1360
1360
  }
1361
1361
  return true;
1362
1362
  }
1363
+ /**
1364
+ * True when a LOCAL git branch of this exact name exists (pln#529). Lets the
1365
+ * gated-sequence base selector distinguish "predecessor branch gone (merged +
1366
+ * cleaned → code is on HEAD)" from "branch present but not yet integrated →
1367
+ * fork the dependent lane from it". Returns false on any git failure.
1368
+ */
1369
+ export function localBranchExists(mainWorktreePath, branchName) {
1370
+ return probeLocalBranch(mainWorktreePath, branchName) === 'present';
1371
+ }
1372
+ export function probeLocalBranch(mainWorktreePath, branchName) {
1373
+ const r = runGit(['rev-parse', '--verify', '--quiet', `refs/heads/${branchName}`], mainWorktreePath);
1374
+ if (r.ok)
1375
+ return 'present';
1376
+ return r.stderr.trim() === '' ? 'absent' : 'unknown';
1377
+ }
1378
+ /**
1379
+ * True when `cwd` is inside a git work tree. pln#529 uses this to distinguish a
1380
+ * NON-git project (where branch/worktree propagation is inapplicable — fall back
1381
+ * to the legacy HEAD base) from a git repo whose branch probe transiently failed
1382
+ * (which must fail SAFE, not silently assume HEAD).
1383
+ */
1384
+ export function isGitRepo(cwd) {
1385
+ return runGit(['rev-parse', '--is-inside-work-tree'], cwd).ok;
1386
+ }
1363
1387
  /**
1364
1388
  * Removes worktrees whose branch has been fully merged into the current branch
1365
1389
  * (typically master/main after a merge). Also removes brainclaw-managed
package/dist/facts.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // Generated by scripts/emit-site-facts.mjs at build time. Do not edit manually.
2
- // Source: brainclaw v1.17.0 on 2026-07-19T20:01:41.172Z
2
+ // Source: brainclaw v1.19.0 on 2026-08-01T21:36:53.526Z
3
3
  export const FACTS = {
4
- "version": "1.17.0",
5
- "generated_at": "2026-07-19T20:01:41.172Z",
4
+ "version": "1.19.0",
5
+ "generated_at": "2026-08-01T21:36:53.526Z",
6
6
  "tools": {
7
7
  "count": 67,
8
8
  "published_count": 65,
@@ -474,7 +474,7 @@ export const FACTS = {
474
474
  },
475
475
  "bench": {
476
476
  "schema": "brainclaw.bench.v1",
477
- "generated_at": "2026-07-19T20:01:39.038Z",
477
+ "generated_at": "2026-08-01T21:36:51.459Z",
478
478
  "node_version": "v24.18.0",
479
479
  "platform": "linux-x64",
480
480
  "repeats": 3,
@@ -483,7 +483,7 @@ export const FACTS = {
483
483
  "name": "cold_onboard",
484
484
  "volume": "empty",
485
485
  "description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
486
- "duration_ms_median": 76,
486
+ "duration_ms_median": 74,
487
487
  "payload_chars_median": 1640,
488
488
  "payload_tokens_est_median": 410
489
489
  },
@@ -491,7 +491,7 @@ export const FACTS = {
491
491
  "name": "warm_work",
492
492
  "volume": "medium",
493
493
  "description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
494
- "duration_ms_median": 135,
494
+ "duration_ms_median": 124,
495
495
  "payload_chars_median": 2626,
496
496
  "payload_tokens_est_median": 657
497
497
  },
@@ -499,9 +499,9 @@ export const FACTS = {
499
499
  "name": "first_edit",
500
500
  "volume": "medium",
501
501
  "description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
502
- "duration_ms_median": 7,
503
- "payload_chars_median": 442,
504
- "payload_tokens_est_median": 111
502
+ "duration_ms_median": 14,
503
+ "payload_chars_median": 499,
504
+ "payload_tokens_est_median": 125
505
505
  }
506
506
  ]
507
507
  }
package/dist/facts.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.17.0",
3
- "generated_at": "2026-07-19T20:01:41.172Z",
2
+ "version": "1.19.0",
3
+ "generated_at": "2026-08-01T21:36:53.526Z",
4
4
  "tools": {
5
5
  "count": 67,
6
6
  "published_count": 65,
@@ -472,7 +472,7 @@
472
472
  },
473
473
  "bench": {
474
474
  "schema": "brainclaw.bench.v1",
475
- "generated_at": "2026-07-19T20:01:39.038Z",
475
+ "generated_at": "2026-08-01T21:36:51.459Z",
476
476
  "node_version": "v24.18.0",
477
477
  "platform": "linux-x64",
478
478
  "repeats": 3,
@@ -481,7 +481,7 @@
481
481
  "name": "cold_onboard",
482
482
  "volume": "empty",
483
483
  "description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
484
- "duration_ms_median": 76,
484
+ "duration_ms_median": 74,
485
485
  "payload_chars_median": 1640,
486
486
  "payload_tokens_est_median": 410
487
487
  },
@@ -489,7 +489,7 @@
489
489
  "name": "warm_work",
490
490
  "volume": "medium",
491
491
  "description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
492
- "duration_ms_median": 135,
492
+ "duration_ms_median": 124,
493
493
  "payload_chars_median": 2626,
494
494
  "payload_tokens_est_median": 657
495
495
  },
@@ -497,9 +497,9 @@
497
497
  "name": "first_edit",
498
498
  "volume": "medium",
499
499
  "description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
500
- "duration_ms_median": 7,
501
- "payload_chars_median": 442,
502
- "payload_tokens_est_median": 111
500
+ "duration_ms_median": 14,
501
+ "payload_chars_median": 499,
502
+ "payload_tokens_est_median": 125
503
503
  }
504
504
  ]
505
505
  }
Binary file
Binary file
Binary file
Binary file
Binary file
package/docs/cli.md CHANGED
@@ -2011,7 +2011,7 @@ The default catalog is intentionally small and centred on the canonical grammar.
2011
2011
  |---|---|
2012
2012
  | `bclaw_coordinate(intent)` | Assign, consult, review, reroute, or summarize across agents. Pass `open_loop: true` on `intent="review"` to also dispatch the reviewer turn. |
2013
2013
  | `bclaw_dispatch(intent)` | Parallelize execute across a sequence's lanes (analysis / execute / review). |
2014
- | `bclaw_loop(intent)` | Drive a turn in an existing multi-turn loop (`turn`, `complete_turn`, `advance`, `close`). Do not call `bclaw_loop(intent="open")` directly without dispatch — use `bclaw_coordinate(intent="review", open_loop: true)` instead. |
2014
+ | `bclaw_loop(intent)` | Drive a turn in an existing multi-turn loop (`turn`, `complete_turn`, `advance`, `close`; implementation loops add `bind` to dispatch the linked sequence and `verify` to run the opener-configured `command_green` check). Do not call `bclaw_loop(intent="open")` directly without dispatch — use `bclaw_coordinate(intent="review", open_loop: true)` instead. |
2015
2015
 
2016
2016
  **Sequences**:
2017
2017
 
package/docs/code-map.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Code Map
2
2
 
3
- Code Map is a per-project structural index of your JavaScript / TypeScript / JSX /
4
- TSX, Python, PHP, and Java codebase. It parses each supported file with Tree-sitter and records the
3
+ Code Map is a per-project structural index of your codebase across 11 languages:
4
+ JavaScript / TypeScript (including JSX / TSX), Python, PHP, Java, Go, Rust, C#,
5
+ Ruby, C, and C++. It parses each supported file with Tree-sitter and records the
5
6
  symbols it defines (functions, classes, types, interfaces, React components and
6
7
  hooks), what it imports and exports, and how files relate — then answers fast
7
8
  "what should I read before I edit this?" questions for both human operators and
@@ -201,11 +202,24 @@ single-project repos ignore the flag entirely.
201
202
  which nested projects have a built index vs `missing_index`, plus an aggregate
202
203
  count — so you can see workspace-wide freshness from the root.
203
204
 
205
+ ### Workspace-wide `find` / `brief`
206
+
207
+ Once the per-child indexes exist (built by `--cascade`), `find` and `brief` run
208
+ at a multi-project workspace **root** automatically aggregate across every child
209
+ project's store — no flag needed. Matches are project-tagged with
210
+ workspace-relative paths, and the freshness badge merges per-store status (worst
211
+ status wins) plus coverage (how many projects are indexed, listing any unindexed
212
+ children). An aggregated `brief` also surfaces **cross-package reverse
213
+ dependents**: sibling packages that import the defining package's public name
214
+ rank into the reading list, flagged `cross_package`.
215
+
216
+ From **inside** a child project, reads stay single-store by default (locality).
217
+ An explicit `traversal: "workspace"` (backend option) walks up to the nearest
218
+ enclosing multi-project root and aggregates from there, with the caller's own
219
+ package ranked first (`local: true` on its rows).
220
+
204
221
  **Not yet supported** (roadmap):
205
222
 
206
- - A single **federated query** at the root that fans out across the per-child
207
- indexes and merges the results (today, `--cascade` builds the per-child indexes;
208
- `find` / `brief` still run against one store at a time).
209
223
  - **Cross-service edges** — e.g. linking an API call to the route that defines it in
210
224
  another service. Code Map indexes language *symbols* and *module imports*, not
211
225
  framework routes or runtime HTTP calls, so it does not (today) map "service A calls
@@ -215,7 +229,9 @@ count — so you can see workspace-wide freshness from the root.
215
229
 
216
230
  The parser is [Tree-sitter](https://tree-sitter.github.io/) compiled to
217
231
  WebAssembly. The engine glue (`web-tree-sitter`) and the prebuilt grammar `.wasm`
218
- files (JavaScript / TypeScript / JSX / TSX, Python, PHP, Java) are **bundled into the package** during the
232
+ files — 12 grammars covering the 11 supported languages: `javascript` (also
233
+ handles JSX), `typescript`, `tsx`, `python`, `php`, `java`, `go`, `rust`,
234
+ `c_sharp`, `ruby`, `c`, `cpp` — are **bundled into the package** during the
219
235
  build (`scripts/copy-code-map-wasm.mjs` copies them into `dist/wasm/` and vendors
220
236
  the engine glue into `dist/vendor/web-tree-sitter/`).
221
237
 
@@ -486,6 +486,29 @@ The three rules are independent: `hard_deadline` bounds pathological "heartbeat
486
486
  - Execution loops (`implementation`) route by `claim_id` — preserved from the claim-routed model already in use.
487
487
  - `session_id` is not a routing key; it remains observability-only. This is consistent with `architecture_session_centric_identity` in memory.
488
488
 
489
+ ### Project resolution gate (pln#521 P1)
490
+
491
+ `bclaw_coordinate(intent='review', open_loop=true)` resolves WHICH project the
492
+ loop belongs to before it writes anything. A loop that lands in the wrong store
493
+ persists a candidate, claim, assignment and loop where nobody is watching, and
494
+ spawns the reviewer against the wrong repo.
495
+
496
+ The ladder, in order: an explicit `project` argument; then any selector that
497
+ already won upstream (`--cwd`, `BRAINCLAW_PROJECT`, a session switch, the
498
+ physical child store, the workspace `active-project.json`); then the bare cwd
499
+ fallback. The fallback is accepted in a single-project store — there is exactly
500
+ one answer — and **refused** with `needs_project_selection` when the store can
501
+ host several projects (`project_mode: multi-project`, or a `store_type: workspace`
502
+ parent with nested project stores). The error lists the candidates and creates
503
+ nothing; fix it by passing `project='<name>'` or by making the choice sticky with
504
+ `bclaw_switch`. Ref, scope and path are never used to guess the project (B3
505
+ rejected in `art_e29e88878209`: a wrong guess costs more than an explicit choice).
506
+
507
+ Both `bclaw_coordinate` (open_loop reviews) and `bclaw_dispatch_status` echo the
508
+ decision as `project_name` / `project_cwd`. `dispatch_status` additionally carries
509
+ `_resolution_trace` (`source_cwd`, `effective_cwd`, `active_source`, `project_arg`)
510
+ so a misroute can be diagnosed without reverse-engineering cwd and store state.
511
+
489
512
  ## Open questions (resolved / deferred)
490
513
 
491
514
  Status after Codex schema review (cnd#574 / `dec_be66ccbf`, verdict `needs_revision` → addressed in v8):
@@ -519,6 +542,7 @@ Status after Codex schema review (cnd#574 / `dec_be66ccbf`, verdict `needs_revis
519
542
  The loop surface exposed over MCP is intentionally narrow:
520
543
 
521
544
  - **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).
545
+ - **Turn-owned exactly-once fix cycle (default, pln#630).** The autonomous `request_changes` fix-cycle re-dispatch runs through the turn-owned attempt state machine (immutable attempt record + atomic launch fence → spawned at most once; `reconcileTurn` finalizes from read-strict, turn-keyed evidence — the ack-wrapper's completion sentinel). It falls back to the legacy closer when a reviewer resolves to inbox/manual (no sentinel) so the loop still converges. **Kill-switch:** set `BRAINCLAW_TURN_OWNED_REVIEW=0` (also `false`/`off`/`no`) to revert review finalization to the legacy presence-based closer.
522
546
  - **Ideation loops** — `bclaw_coordinate(intent="ideate", preset="bootstrap")` opens an ideation loop from a preset.
523
547
 
524
548
  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.
@@ -213,6 +213,28 @@ the projection rule).
213
213
  > from the seed otherwise. `agents`/`sessions` are never journaled → always seed.
214
214
  > A store that has NOT run the supplement keeps the seed (no regression).
215
215
  >
216
+ > **Section CONTENT cutover (pln#560 completion):** once `registryAuthoritative()`
217
+ > is set, the registry/coordination sections (ATTENTION, IN_PROGRESS, SPRINTS,
218
+ > and the flat claims/assignments/runs/actions/candidates drill-downs) serve
219
+ > their entity content from the projection too — zero MCP display fetches on
220
+ > expand. The non-journaled extras on the composites (server-computed
221
+ > `workflow_hints`, loops via `bclaw_loop(intent='list')`, and the
222
+ > `bclaw_dispatch_status` evidence digests of §6/§7) remain best-effort reads
223
+ > through the observer-flagged client: when no client resolves, the section
224
+ > still renders its entities. SYSTEM keeps its MCP fetch regardless — it mixes
225
+ > private/machine runtime_notes (never journaled, visibility boundary) and
226
+ > cross-project config, neither derivable from the shared journal.
227
+ >
228
+ > Two parity notes: (a) sections whose MCP fetch pre-filtered `status:
229
+ > 'pending'` server-side (actions, candidates) apply the equivalent pure
230
+ > filter on the projection, because renderers admit broader statuses; (b)
231
+ > journal-served sections are **legacy-inclusive** — the genesis backfill
232
+ > journals `provenance.kind='legacy'` records that the MCP default read
233
+ > filter excludes, and the projection trim drops the nested `provenance`
234
+ > object, so parity with the MCP default is not reconstructable client-side.
235
+ > Accepted deliberately: the operator tree already passes `includeLegacy:
236
+ > true` wherever it fetches explicitly.
237
+ >
216
238
  > The historical (pre-pln#568) description below is kept for context.
217
239
 
218
240
  The journal classifies records into five classes (§2). In phase 1 / `dual`