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.
- package/README.md +5 -5
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/code-map.js +4 -1
- package/dist/commands/codev.js +61 -30
- package/dist/commands/doctor.js +14 -1
- package/dist/commands/harvest.js +223 -43
- package/dist/commands/inbox.js +10 -4
- package/dist/commands/install-hooks.js +184 -27
- package/dist/commands/loop.js +2 -2
- package/dist/commands/loops-handlers.js +82 -1
- package/dist/commands/mcp-catalog.js +12 -4
- package/dist/commands/mcp-read-handlers.js +90 -7
- package/dist/commands/mcp-schemas.generated.js +3 -0
- package/dist/commands/mcp-write-claims.js +57 -0
- package/dist/commands/mcp-write-coordination.js +216 -57
- package/dist/commands/mcp-write-entities.js +11 -0
- package/dist/commands/mcp.js +29 -2
- package/dist/commands/session-end.js +15 -0
- package/dist/commands/session-start.js +19 -0
- package/dist/core/agentrun-reconciler.js +171 -7
- package/dist/core/agentruns.js +6 -1
- package/dist/core/claim-conformity.js +193 -0
- package/dist/core/claim-scope.js +155 -0
- package/dist/core/claims.js +127 -2
- package/dist/core/code-map/aggregate.js +473 -0
- package/dist/core/code-map/backend.js +36 -10
- package/dist/core/code-map/freshness.js +36 -1
- package/dist/core/code-map/lang/c/imports.scm +12 -0
- package/dist/core/code-map/lang/c/index.js +150 -0
- package/dist/core/code-map/lang/c/tags.scm +68 -0
- package/dist/core/code-map/lang/cpp/imports.scm +14 -0
- package/dist/core/code-map/lang/cpp/index.js +149 -0
- package/dist/core/code-map/lang/cpp/tags.scm +87 -0
- package/dist/core/code-map/lang/csharp/imports.scm +20 -0
- package/dist/core/code-map/lang/csharp/index.js +224 -0
- package/dist/core/code-map/lang/csharp/tags.scm +63 -0
- package/dist/core/code-map/lang/go/imports.scm +13 -0
- package/dist/core/code-map/lang/go/index.js +139 -0
- package/dist/core/code-map/lang/go/tags.scm +36 -0
- package/dist/core/code-map/lang/providers.js +12 -1
- package/dist/core/code-map/lang/ruby/imports.scm +24 -0
- package/dist/core/code-map/lang/ruby/index.js +198 -0
- package/dist/core/code-map/lang/ruby/tags.scm +49 -0
- package/dist/core/code-map/lang/rust/imports.scm +44 -0
- package/dist/core/code-map/lang/rust/index.js +136 -0
- package/dist/core/code-map/lang/rust/tags.scm +47 -0
- package/dist/core/code-map/query.js +229 -80
- package/dist/core/code-map/types.js +18 -0
- package/dist/core/code-map/work-section.js +8 -7
- package/dist/core/codev-responses.js +16 -0
- package/dist/core/dispatcher.js +176 -22
- package/dist/core/execution-adapters.js +29 -3
- package/dist/core/facade-schema.js +32 -0
- package/dist/core/guidance-telemetry.js +197 -0
- package/dist/core/ideation-loop-close.js +152 -0
- package/dist/core/instruction-templates.js +11 -3
- package/dist/core/loops/artifact-resolver.js +197 -0
- package/dist/core/loops/attempt-reservation.js +576 -0
- package/dist/core/loops/commit-intent.js +494 -0
- package/dist/core/loops/facade-schema.js +48 -0
- package/dist/core/loops/impl-bind.js +144 -0
- package/dist/core/loops/index.js +1 -1
- package/dist/core/loops/iteration-engine.js +29 -0
- package/dist/core/loops/lock.js +14 -0
- package/dist/core/loops/project-resolution.js +157 -0
- package/dist/core/loops/reconcile-turn.js +369 -0
- package/dist/core/loops/result-reducers.js +88 -0
- package/dist/core/loops/store.js +46 -7
- package/dist/core/loops/types.js +139 -11
- package/dist/core/loops/verbs.js +49 -4
- package/dist/core/loops/verify-command.js +209 -0
- package/dist/core/messaging.js +58 -5
- package/dist/core/next-actions.js +157 -0
- package/dist/core/review-loop-close.js +27 -6
- package/dist/core/review-loop-turn-dispatch.js +290 -28
- package/dist/core/runtime-signals.js +68 -0
- package/dist/core/schema.js +64 -0
- package/dist/core/surface-freshness.js +150 -0
- package/dist/core/warnings.js +98 -0
- package/dist/core/worktree.js +24 -0
- package/dist/facts.js +9 -9
- package/dist/facts.json +8 -8
- package/dist/wasm/tree-sitter-c.wasm +0 -0
- package/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/wasm/tree-sitter-go.wasm +0 -0
- package/dist/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/docs/cli.md +1 -1
- package/docs/code-map.md +22 -6
- package/docs/concepts/loop-engine.md +24 -0
- package/docs/concepts/observer-protocol.md +22 -0
- package/docs/concepts/plans-and-claims.md +57 -0
- package/docs/integrations/claude-code.md +53 -0
- package/docs/integrations/mcp.md +45 -0
- package/docs/mcp-schema-changelog.md +118 -2
- package/package.json +1 -1
package/dist/core/schema.js
CHANGED
|
@@ -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
|
package/dist/core/worktree.js
CHANGED
|
@@ -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.
|
|
2
|
+
// Source: brainclaw v1.19.0 on 2026-08-01T21:36:53.526Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.
|
|
5
|
-
"generated_at": "2026-
|
|
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-
|
|
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":
|
|
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":
|
|
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":
|
|
503
|
-
"payload_chars_median":
|
|
504
|
-
"payload_tokens_est_median":
|
|
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.
|
|
3
|
-
"generated_at": "2026-
|
|
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-
|
|
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":
|
|
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":
|
|
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":
|
|
501
|
-
"payload_chars_median":
|
|
502
|
-
"payload_tokens_est_median":
|
|
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
|
|
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
|
|
4
|
-
TSX, Python, PHP,
|
|
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
|
|
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`
|