brainclaw 1.18.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/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/harvest.js +28 -2
- package/dist/commands/install-hooks.js +184 -27
- package/dist/commands/mcp-write-claims.js +57 -0
- package/dist/commands/mcp-write-coordination.js +57 -17
- package/dist/commands/mcp-write-entities.js +11 -0
- package/dist/commands/mcp.js +18 -0
- package/dist/commands/session-end.js +15 -0
- package/dist/commands/session-start.js +19 -0
- 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/facade-schema.js +32 -0
- package/dist/core/guidance-telemetry.js +197 -0
- package/dist/core/ideation-loop-close.js +32 -4
- package/dist/core/instruction-templates.js +11 -3
- package/dist/core/loops/verbs.js +40 -1
- package/dist/core/next-actions.js +157 -0
- package/dist/core/review-loop-close.js +22 -4
- package/dist/core/schema.js +40 -0
- package/dist/core/surface-freshness.js +150 -0
- package/dist/core/warnings.js +98 -0
- package/dist/facts.js +7 -7
- package/dist/facts.json +6 -6
- 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 +75 -1
- package/package.json +1 -1
package/dist/core/schema.js
CHANGED
|
@@ -715,6 +715,28 @@ export const ClaimSchema = z.object({
|
|
|
715
715
|
assignment_message_id: z.string().optional(),
|
|
716
716
|
/** Assignment ID from the Agent SDK runtime protocol. Links claim to its Assignment lifecycle entity. */
|
|
717
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(),
|
|
718
740
|
});
|
|
719
741
|
// --- Assignment schemas (Agent SDK runtime protocol) ---
|
|
720
742
|
export const AssignmentStatusSchema = z.enum([
|
|
@@ -974,6 +996,12 @@ export const RuntimeEventTypeSchema = z.enum([
|
|
|
974
996
|
* environment, e.g. a genuinely MCP-less agent). The coordinator ingests it with
|
|
975
997
|
* `brainclaw harvest <assignment_id>`.
|
|
976
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;
|
|
977
1005
|
export const LaneResultSchema = z.object({
|
|
978
1006
|
assignment_id: z.string(),
|
|
979
1007
|
/**
|
|
@@ -994,6 +1022,18 @@ export const LaneResultSchema = z.object({
|
|
|
994
1022
|
files_changed: z.array(z.string()).optional(),
|
|
995
1023
|
/** Free-form notes (blockers, follow-ups). */
|
|
996
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(),
|
|
997
1037
|
/**
|
|
998
1038
|
* pln#628 Focus 4B — review-loop verdict. A worker running a review-loop turn
|
|
999
1039
|
* sets this to signal whether the change is good to merge (`approve`) or needs
|
|
@@ -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/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,7 +499,7 @@ 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":
|
|
502
|
+
"duration_ms_median": 14,
|
|
503
503
|
"payload_chars_median": 499,
|
|
504
504
|
"payload_tokens_est_median": 125
|
|
505
505
|
}
|
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,7 +497,7 @@
|
|
|
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":
|
|
500
|
+
"duration_ms_median": 14,
|
|
501
501
|
"payload_chars_median": 499,
|
|
502
502
|
"payload_tokens_est_median": 125
|
|
503
503
|
}
|
|
@@ -166,6 +166,63 @@ Without claims, multiple agents can easily touch the same area at once and gener
|
|
|
166
166
|
Claims are not necessarily hard file locks.
|
|
167
167
|
They are a shared coordination signal.
|
|
168
168
|
|
|
169
|
+
### Scope grammar and conformity (v1.19.0+)
|
|
170
|
+
|
|
171
|
+
A claim's `scope` is a free string, and in practice it is used three ways. Measured
|
|
172
|
+
over the 613 real claims in the dogfood store:
|
|
173
|
+
|
|
174
|
+
| Shape | Share | Example |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| Path-like | 57.6% | `src/core/auth.ts`, `docs/` |
|
|
177
|
+
| Loop reference | 22.8% | `review-loop:lop_…`, `ideate-loop:lop_…:lsl_…` |
|
|
178
|
+
| Free prose | 19.6% | `Loop engine residuals #1-4` |
|
|
179
|
+
|
|
180
|
+
So **42.4% of real scopes cannot be matched to a file path at all** — and the
|
|
181
|
+
non-matchable share is *growing*, because coordinator-created lane claims are the
|
|
182
|
+
ones being minted. Any check built naively on path matching would false-accuse on
|
|
183
|
+
nearly one claim in two.
|
|
184
|
+
|
|
185
|
+
brainclaw therefore classifies a scope into `paths` / `loop_ref` / `prose` / `empty`
|
|
186
|
+
and reports conformity as `in_scope`, `out_of_scope`, or **`unverifiable`** — a
|
|
187
|
+
first-class verdict that every consumer renders as **silence**. Only a
|
|
188
|
+
path-resolvable scope with concrete stray files can ever produce an accusation.
|
|
189
|
+
`.brainclaw/` and `.git/` are never counted as out of scope: every brainclaw call
|
|
190
|
+
rewrites them, so counting them would accuse every agent on every claim.
|
|
191
|
+
|
|
192
|
+
The reserved loop prefixes are **enumerated**, not inferred from shape — so
|
|
193
|
+
`project-resolution: the gate` reads as prose, and a Windows absolute path
|
|
194
|
+
(`C:/Users/…`) stays a path rather than being read as a `C:` prefix.
|
|
195
|
+
|
|
196
|
+
### `base_sha` and declared `paths[]`
|
|
197
|
+
|
|
198
|
+
A new claim records **`base_sha`**, the commit its work started from, resolved once
|
|
199
|
+
at creation and never moved. This is the baseline any "what did this claim actually
|
|
200
|
+
touch?" comparison needs: neither `git diff HEAD` nor the worktree's dirty set is
|
|
201
|
+
authoritative, because a lane that commits mid-work moves the ground under both —
|
|
202
|
+
each would report "touched nothing" the instant it committed.
|
|
203
|
+
|
|
204
|
+
Optionally a creator can declare **`paths[]`**, a machine-readable footprint that
|
|
205
|
+
raises conformity coverage above what classifying a free-string `scope` can reach.
|
|
206
|
+
|
|
207
|
+
Both are additive and never backfilled. A claim with no baseline is `unverifiable`,
|
|
208
|
+
never guessed, and acquiring a claim **never fails or blocks** because a baseline
|
|
209
|
+
could not be computed — outside a git repo the claim is simply created without one.
|
|
210
|
+
|
|
211
|
+
> `paths[]` is currently settable through the core and the CLI, but is not yet
|
|
212
|
+
> exposed in `bclaw_claim`'s published MCP inputSchema.
|
|
213
|
+
|
|
214
|
+
### Liveness: file evidence, not just a session
|
|
215
|
+
|
|
216
|
+
A spawned sandboxed worker cannot reach MCP, so it cannot maintain any server-side
|
|
217
|
+
liveness record — which is why proof-of-life lives in filesystem sentinels the
|
|
218
|
+
worker writes into its own worktree. Since v1.19.0 claim liveness reads that same
|
|
219
|
+
evidence (worktree/project heartbeat plus filesystem activity) **before** consulting
|
|
220
|
+
any session record, with the same 30-minute freshness window an assignment gets.
|
|
221
|
+
|
|
222
|
+
Previously a demonstrably-alive worker kept its *assignment* while its *claim* aged
|
|
223
|
+
out on wall-clock alone — and a coordinator-created claim, which carries no
|
|
224
|
+
`session_id`, fell straight through to `never-adopted`.
|
|
225
|
+
|
|
169
226
|
## Policy checks
|
|
170
227
|
|
|
171
228
|
Before editing a scope, agents can verify governance compliance using `check-policy`:
|
|
@@ -17,6 +17,59 @@ brainclaw export --format claude-md --write
|
|
|
17
17
|
- use `.brainclaw/project.md` as a readable fallback (it is a derived view, regenerated best-effort — run `brainclaw rebuild` if stale)
|
|
18
18
|
- use hooks or workflow checks when a stronger reminder is needed
|
|
19
19
|
|
|
20
|
+
## The advisory PreToolUse hook (v1.19.0+)
|
|
21
|
+
|
|
22
|
+
`brainclaw install-hooks` generates `.git/hooks/claude-pre-tool.sh` **and activates
|
|
23
|
+
it** by merging a `PreToolUse` entry into `.claude/settings.json`. Before v1.19.0 it
|
|
24
|
+
only printed instructions, so the hook was dead even for operators who ran the
|
|
25
|
+
command.
|
|
26
|
+
|
|
27
|
+
It nudges an agent that is editing files without holding a claim of its own. It is
|
|
28
|
+
**advisory and can never block**: `permissionDecision` is always `allow` and the
|
|
29
|
+
exit code is always 0 (trp_5f342186 — a hook cascade once destroyed work).
|
|
30
|
+
|
|
31
|
+
### The channel matters, and it is not stderr
|
|
32
|
+
|
|
33
|
+
Per the Claude Code hook contract:
|
|
34
|
+
|
|
35
|
+
| Exit | stderr goes to | Tool |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| 0 | **nobody** — not the model | proceeds |
|
|
38
|
+
| 2 | the model | **BLOCKED** |
|
|
39
|
+
| other non-zero | the user only | proceeds |
|
|
40
|
+
|
|
41
|
+
So "advisory = exit 0 + write to stderr" is **structurally mute**, and that is
|
|
42
|
+
exactly what brainclaw's generated hook did for an unknown number of releases: even
|
|
43
|
+
once its other defects were fixed, it would still have spoken into the void. The
|
|
44
|
+
only non-blocking channel that reaches the model is JSON on **stdout** at exit 0:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "hookSpecificOutput": {
|
|
48
|
+
"hookEventName": "PreToolUse",
|
|
49
|
+
"permissionDecision": "allow",
|
|
50
|
+
"additionalContext": "[brainclaw] Editing without an active claim of your own…" } }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If you write hooks of your own, this is the shape to copy.
|
|
54
|
+
|
|
55
|
+
### What it matches, and what it deliberately does not
|
|
56
|
+
|
|
57
|
+
The matcher is `Edit|Write|MultiEdit|NotebookEdit` — the tools whose `tool_input`
|
|
58
|
+
exposes a concrete file path. **`Bash` is excluded on purpose**: a shell command's
|
|
59
|
+
file footprint is not statically knowable, so it is `unverifiable`, never guessed.
|
|
60
|
+
Matching it was one source of the noise that made the pre-v1.19 hook worth ignoring.
|
|
61
|
+
|
|
62
|
+
Activation is non-destructive: unknown settings keys are preserved, a pre-existing
|
|
63
|
+
`PreToolUse` array is appended to rather than replaced, and a `settings.json` that
|
|
64
|
+
cannot be parsed is left **byte-identical** with a manual instruction printed —
|
|
65
|
+
that file holds your permission allow-list, and clobbering it would be far worse
|
|
66
|
+
than an unactivated advisory.
|
|
67
|
+
|
|
68
|
+
> **Spawned workers get no hooks.** `.claude/` is gitignored, so a dispatched
|
|
69
|
+
> worker's worktree never receives this hook (nor Codex's `.codex/hooks.json`).
|
|
70
|
+
> Lifecycle parity for dispatched lanes runs through the brief today — see
|
|
71
|
+
> trp#1277.
|
|
72
|
+
|
|
20
73
|
## Key idea
|
|
21
74
|
|
|
22
75
|
Claude Code should not carry all workspace state in static instructions.
|
package/docs/integrations/mcp.md
CHANGED
|
@@ -141,6 +141,51 @@ See [code map](../code-map.md) for the full Code Map reference (CLI, freshness m
|
|
|
141
141
|
| `bclaw_update_memory` | memory | Update a memory item's text or metadata |
|
|
142
142
|
| `bclaw_compact` | memory | LLM-driven semantic memory compaction (two-phase) |
|
|
143
143
|
|
|
144
|
+
### What a response tells you to do next (v1.19.0+)
|
|
145
|
+
|
|
146
|
+
Responses are self-teaching: rather than requiring you to memorise the API, they
|
|
147
|
+
carry the follow-up derived from **what actually happened**.
|
|
148
|
+
|
|
149
|
+
**`next_actions`** — an array of `{tool, args?, when?}`. Present only when there is
|
|
150
|
+
a genuine follow-up, so its presence is meaningful; a handler with nothing to add
|
|
151
|
+
omits the key entirely rather than padding it. It is derived from the outcome, not
|
|
152
|
+
from a static table: releasing a claim proposes something different depending on
|
|
153
|
+
whether the plan cascade fired or refused. Fan-out is capped at 3, with an explicit
|
|
154
|
+
note when more were available.
|
|
155
|
+
|
|
156
|
+
**`warning_details`** — the structured sibling of `warnings: string[]`. Each entry
|
|
157
|
+
carries a stable `code`, human `message`, the `data` the prose mentions, and — the
|
|
158
|
+
part a bare string could never hold — `next_actions` naming the way out.
|
|
159
|
+
|
|
160
|
+
```jsonc
|
|
161
|
+
{
|
|
162
|
+
"code": "wrote_outside_claim_scope",
|
|
163
|
+
"message": "Claim clm_… declared 'src/core' but 2 touched file(s) sit outside it: …",
|
|
164
|
+
"data": { "claim_id": "clm_…", "scope": "src/core", "unexpected_paths": ["docs/x.md"] },
|
|
165
|
+
"next_actions": [{ "tool": "bclaw_update", "args": { "entity": "claim", "…": "…" } }]
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`warnings` keeps its type **and** its byte-identical historical contents, so a
|
|
170
|
+
consumer that ignores `warning_details` sees no change. Read `warnings` for
|
|
171
|
+
completeness and `warning_details` for the codes that carry a recovery path — the
|
|
172
|
+
structured channel is a subset, not a mirror.
|
|
173
|
+
|
|
174
|
+
Codes you may see today:
|
|
175
|
+
|
|
176
|
+
| Code | Emitted by | Meaning |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `scope_already_claimed` | `bclaw_coordinate` | Another agent holds the scope |
|
|
179
|
+
| `plan_already_assigned` | `bclaw_coordinate` | A second assignment on one plan |
|
|
180
|
+
| `agent_validation_failed` | `bclaw_coordinate` | Target is not dispatchable |
|
|
181
|
+
| `wrote_outside_claim_scope` | release / assignment-completed / harvest / session-end | Files were written outside the claim's declared scope. **Advisory** — the write already happened |
|
|
182
|
+
| `generated_surfaces_stale` | `session-start` | Generated guidance on disk was written by an older brainclaw. Recovery is `brainclaw export --write`; no MCP tool performs it, so no `next_actions` is offered rather than one the engine would reject |
|
|
183
|
+
|
|
184
|
+
Conformity warnings are **silent on doubt** by construction: a claim whose scope is
|
|
185
|
+
a loop reference, free prose or a glob — 42.4% of the real corpus — yields
|
|
186
|
+
`unverifiable` and emits nothing. An accuser that is wrong that often teaches
|
|
187
|
+
agents to ignore the channel, which is worse than shipping no check at all.
|
|
188
|
+
|
|
144
189
|
### Canonical grammar (standard tier, v1.0+)
|
|
145
190
|
|
|
146
191
|
Phase 3 shipped a unified grammar that replaces the per-entity tools
|
|
@@ -8,7 +8,81 @@ guarantees this changelog follows.
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## [1.19.0] — 2026-08-01
|
|
12
|
+
|
|
13
|
+
**Added — `warning_details` on the facade response contract (pln#635)**
|
|
14
|
+
- Additive sibling of `warnings: string[]`, which keeps both its type and its
|
|
15
|
+
byte-identical historical contents. Each record carries `code` / `message` /
|
|
16
|
+
optional `data` / optional `next_actions`, so a consumer no longer has to
|
|
17
|
+
sniff-parse a string that may or may not be JSON to recover structure.
|
|
18
|
+
- The legacy string is DERIVED from the record, and only for an **enumerated**
|
|
19
|
+
set of codes that historically shipped a JSON blob (`agent_validation_failed`,
|
|
20
|
+
`plan_already_assigned`, `scope_already_claimed`). Enumerated rather than
|
|
21
|
+
inferred so a NEW code can never start emitting JSON at a consumer that has
|
|
22
|
+
only ever seen prose.
|
|
23
|
+
- Read contract, not input: no tool added/removed/renamed, no inputSchema change,
|
|
24
|
+
**no surface-fingerprint movement**. `warnings` remains the complete channel;
|
|
25
|
+
`warning_details` is a structured subset (see `src/core/warnings.ts` for why).
|
|
26
|
+
|
|
27
|
+
**Added — `next_actions` emitted by the write surfaces (pln#634 PR1)**
|
|
28
|
+
- `FacadeResponseSchema.next_actions` already existed and was optional; the write
|
|
29
|
+
facades simply never populated it. They now do, derived from the OUTCOME rather
|
|
30
|
+
than from a static table — and omit the key entirely when there is no genuine
|
|
31
|
+
follow-up, so its presence stays meaningful. Response-only; no fingerprint move.
|
|
32
|
+
|
|
33
|
+
**Added — new structured warning codes**
|
|
34
|
+
- `wrote_outside_claim_scope` (pln#636 C2) — emitted on `bclaw_release_claim`, on
|
|
35
|
+
assignment→`completed`, at LANE-RESULT harvest ingestion and at `session-end`.
|
|
36
|
+
Carries `claim_id`, `scope`, `declared_pathspecs`, `unexpected_paths`,
|
|
37
|
+
`base_sha`, and two recovery actions. Advisory: the write already happened.
|
|
38
|
+
- `generated_surfaces_stale` (pln#638 2b) — surfaced on `session-start` as
|
|
39
|
+
`stale_surfaces` when a generated guidance surface on disk was stamped by an
|
|
40
|
+
older brainclaw than the running one. Deliberately carries **no**
|
|
41
|
+
`next_actions`: the recovery is `brainclaw export --write` and no MCP tool
|
|
42
|
+
performs it, so the command travels in `message` + `data.refresh_command`
|
|
43
|
+
rather than as an action whose args the engine would reject.
|
|
44
|
+
|
|
45
|
+
**Added — `ClaimSchema.base_sha` / `ClaimSchema.paths` (pln#636 C0-b)**
|
|
46
|
+
- Both optional and never backfilled; legacy claims parse unchanged and a missing
|
|
47
|
+
baseline is treated as `unverifiable`, never guessed.
|
|
48
|
+
- Record shape only. `paths` is NOT exposed in `bclaw_claim`'s published
|
|
49
|
+
inputSchema — settable via the core and CLI, readable by the conformity
|
|
50
|
+
reconcile — so this moves **no** surface fingerprint. Widening the published
|
|
51
|
+
input belongs in its own governed change (tracked as a known gap in
|
|
52
|
+
CHANGELOG 1.19.0).
|
|
53
|
+
|
|
54
|
+
**Added — `SessionEndResult.scope_warnings`, `LaneHarvestResult.warnings`**
|
|
55
|
+
- Both are `WarningDetail[]`, born structured (hence `toWarningDetail`, which
|
|
56
|
+
builds the record without inventing a throwaway legacy string array). Additive
|
|
57
|
+
result fields on non-MCP surfaces; `LaneHarvestResult.warnings` is always
|
|
58
|
+
present (empty when nothing was ingested), which is an exact-shape change for
|
|
59
|
+
any caller asserting `deepEqual` on that result.
|
|
60
|
+
|
|
61
|
+
**Changed — a content-less loop artifact no longer satisfies a gate (pln#639)**
|
|
62
|
+
- Behavioural, not schema: `artifact.body` stays optional (ref-based artifacts
|
|
63
|
+
legitimately have none), but an artifact with neither a non-empty `body` nor a
|
|
64
|
+
`ref` no longer counts toward `min_artifacts_by_type`. The unmet-gate reason
|
|
65
|
+
string now names how many artifacts of that type were discarded as empty.
|
|
66
|
+
- Verified against the live corpus before shipping (219 loops / 321 artifacts,
|
|
67
|
+
zero content-less), so no running loop can be stalled by the stricter rule.
|
|
68
|
+
|
|
69
|
+
**Changed — loop artifacts are attributed to their DISPATCH phase (pln#639)**
|
|
70
|
+
- The ideation and review closers recorded `phase: loop.current_phase` (close
|
|
71
|
+
time); they now use the phase stamped on the slot at dispatch. A lane returning
|
|
72
|
+
after a phase advance is filed under the phase it was asked to work in.
|
|
73
|
+
- No gate in the engine keys on `type: 'verdict'` and `reviewer_green` scans all
|
|
74
|
+
phases, so review-loop outcomes are unaffected — attribution changes, verdicts
|
|
75
|
+
do not.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## [1.18.0] — 2026-07-31
|
|
80
|
+
|
|
81
|
+
> These entries sat under an `Unreleased` heading THROUGH the 1.18.0 release and
|
|
82
|
+
> are rolled retroactively here. pln#630 / pln#627 / pln#628 shipped in 1.18.0
|
|
83
|
+
> (see CHANGELOG.md); the pln#625 Phase 3 entry below predates it and was never
|
|
84
|
+
> rolled either. Rolling the section is part of cutting a release — the 1.19.0
|
|
85
|
+
> prep found it still open.
|
|
12
86
|
|
|
13
87
|
**Added — turn-attempt evidence-correlation fields (pln#630 PR2b-a)**
|
|
14
88
|
- Additive, backward-compatible: `LaneResultSchema` gains optional `turn_id` /
|