brainclaw 1.20.0 → 1.20.1

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.
Binary file
@@ -6,7 +6,7 @@ import { loadConfig, saveConfig } from '../core/config.js';
6
6
  import { isAgentIntegrationName, upsertAgentIntegrationDeclaration } from '../core/agent-integrations.js';
7
7
  import { resolveInstructions, loadInstructions } from '../core/instructions.js';
8
8
  import { detectAiAgent } from '../core/ai-agent-detection.js';
9
- import { AGENT_EXPORT_REGISTRY, resolveExportTarget, resolveExportTargetByFormat, writeExportFile, writeLiveCompanionFile, buildHygieneSection, describeAutoConfigWrite, writeExportCompanionFiles, collectExportGitignoreEntries, ensureGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
9
+ import { AGENT_EXPORT_REGISTRY, resolveExportTarget, resolveExportTargetByFormat, resolveLiveCompanionPath, writeExportFile, writeLiveCompanionFile, buildHygieneSection, describeAutoConfigWrite, writeExportCompanionFiles, collectExportGitignoreEntries, ensureGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
10
10
  import { buildCoordinationSnapshot } from '../core/coordination.js';
11
11
  import { listClaims } from '../core/claims.js';
12
12
  import { listCandidates } from '../core/candidates.js';
@@ -187,18 +187,23 @@ export function refreshLiveCompanions(cwd) {
187
187
  const instructions = getInstructionText({ project: undefined, agent: undefined }, effectiveCwd);
188
188
  const activeClaims = listClaims(effectiveCwd).filter((c) => c.status === 'active');
189
189
  const pendingCandidates = listCandidates('pending', effectiveCwd);
190
- const seen = new Set();
191
- const targets = AGENT_EXPORT_REGISTRY.filter((t) => {
192
- if (seen.has(t.format))
193
- return false;
194
- seen.add(t.format);
195
- return true;
196
- });
197
190
  let written = 0;
198
191
  const errors = [];
199
192
  const liveGitignoreEntries = [];
200
- for (const target of targets) {
193
+ // trp_6a49f976 F1 (codex review): dedupe by RESOLVED LIVE PATH, never by
194
+ // stable export format. Format-dedup dropped every agents-md agent after
195
+ // codex — including mistral-vibe, whose REGISTERED live companion
196
+ // (.vibe/live.md) then never got refreshed, while the stale-surface
197
+ // advisory told the operator `brainclaw refresh` would fix exactly that
198
+ // file. A path counts as taken only once something was actually RENDERED
199
+ // for it, so a tier with no live companion (codex, Tier A) does not shadow
200
+ // a rendering agent that shares its default path (hermes → AGENTS.live.md).
201
+ const renderedLivePaths = new Set();
202
+ for (const target of AGENT_EXPORT_REGISTRY) {
201
203
  try {
204
+ const livePath = resolveLiveCompanionPath(target.agentName, target.relativePath);
205
+ if (renderedLivePaths.has(livePath))
206
+ continue;
202
207
  const profile = getAgentCapabilityProfile(target.agentName);
203
208
  if (!profile)
204
209
  continue;
@@ -214,7 +219,8 @@ export function refreshLiveCompanions(cwd) {
214
219
  };
215
220
  const live = renderLiveSection(input);
216
221
  if (!live)
217
- continue; // Tier A — no live companion needed
222
+ continue; // no live companion for this tier
223
+ renderedLivePaths.add(livePath);
218
224
  const writeResult = writeLiveCompanionFile(live.content, target.agentName, target.relativePath, effectiveCwd);
219
225
  if (writeResult.created || writeResult.updated) {
220
226
  written++;
@@ -220,15 +220,18 @@ function renderHeader(input) {
220
220
  function renderLiveHeader(input) {
221
221
  // pln#638 volet 2a — HONESTY FIX. This header used to say "auto-refreshed",
222
222
  // but regeneration is EXPLICIT: it happens on session-end, handoff, and
223
- // `export --write`. An agent tier that never fires those events (no hooks, no
224
- // MCP) read a file claiming to be fresh while being arbitrarily stale. A claim
225
- // that is false for half the tiers is worse than no claim, so the header now
223
+ // `brainclaw refresh`. An agent tier that never fires those events (no hooks,
224
+ // no MCP) read a file claiming to be fresh while being arbitrarily stale. A
225
+ // claim that is false for half the tiers is worse than no claim, so the header
226
226
  // names the actual triggers and tells the reader how to force a refresh.
227
+ // trp_6a49f976: the 1.20.0 header cited `brainclaw export --write`, which the
228
+ // CLI rejects outright AND which never wrote a live companion — the honest
229
+ // recovery for THIS file is `brainclaw refresh`.
227
230
  // Guarded by tests/unit/guidance-engine-consistency.test.ts.
228
231
  return [
229
- `> Brainclaw live state — do not edit. Regenerated on: session-end, handoff, \`brainclaw export --write\`.`,
232
+ `> Brainclaw live state — do not edit. Regenerated on: session-end, handoff, \`brainclaw refresh\`.`,
230
233
  `> Written by brainclaw v${input.brainclawVersion} at ${new Date().toISOString().slice(0, 19)}`,
231
- `> Older than your last session? It is stale — run \`brainclaw export --write\` to refresh.`,
234
+ `> Older than your last session? It is stale — run \`brainclaw refresh\`.`,
232
235
  ].join('\n');
233
236
  }
234
237
  // Kept deliberately small (pln#542): entry point + grammar + escalation
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * WHY THIS EXISTS. 2a made the live header HONEST: it stopped claiming
5
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
6
+ * `brainclaw refresh`) plus the version and timestamp that wrote it. Honesty alone
7
7
  * does not help an agent tier that never fires any of those triggers, though — it
8
8
  * just tells that tier, truthfully, that the file might be arbitrarily old. 2b
9
9
  * closes the loop by USING the stamp: compare it against the running version and
@@ -63,16 +63,23 @@ export function assessSurfaceFreshness(content, currentVersion) {
63
63
  return { kind: 'fresh', version };
64
64
  return { kind: 'stale', stampedVersion: version, currentVersion };
65
65
  }
66
+ /** The command that regenerates every stable surface. `export --write` alone is rejected by the CLI (a mode flag is required — see runExport). */
67
+ export const STABLE_SURFACE_REFRESH_COMMAND = 'brainclaw export --all --write';
68
+ /** The command that regenerates every live companion. */
69
+ export const LIVE_SURFACE_REFRESH_COMMAND = 'brainclaw refresh';
66
70
  /**
67
71
  * The set of surfaces this project could have on disk, derived from the export
68
72
  * registries rather than listed here. Deduplicated because several agents share
69
- * a target (four of them write AGENTS.md).
73
+ * a target (four of them write AGENTS.md); a path claimed by both registries
74
+ * counts as stable, since `export` regenerates it.
70
75
  */
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
+ function candidateSurfaces() {
77
+ const stable = new Set(AGENT_EXPORT_REGISTRY.map((t) => t.relativePath));
78
+ const live = new Set(LIVE_COMPANION_EXPORT_REGISTRY.map((t) => t.relativePath));
79
+ return [
80
+ ...[...stable].map((relativePath) => ({ relativePath, kind: 'stable' })),
81
+ ...[...live].filter((p) => !stable.has(p)).map((relativePath) => ({ relativePath, kind: 'live' })),
82
+ ];
76
83
  }
77
84
  /**
78
85
  * Scan the project's generated surfaces and report the ones stamped with a
@@ -85,7 +92,7 @@ function candidateSurfacePaths() {
85
92
  */
86
93
  export function reconcileSurfaceFreshness(cwd, currentVersion) {
87
94
  const result = { stale: [], freshCount: 0, unknownCount: 0 };
88
- for (const relativePath of candidateSurfacePaths()) {
95
+ for (const { relativePath, kind } of candidateSurfaces()) {
89
96
  const full = path.join(cwd, relativePath);
90
97
  let head;
91
98
  try {
@@ -107,7 +114,7 @@ export function reconcileSurfaceFreshness(cwd, currentVersion) {
107
114
  }
108
115
  const verdict = assessSurfaceFreshness(head, currentVersion);
109
116
  if (verdict.kind === 'stale')
110
- result.stale.push({ relativePath, stampedVersion: verdict.stampedVersion });
117
+ result.stale.push({ relativePath, stampedVersion: verdict.stampedVersion, kind });
111
118
  else if (verdict.kind === 'fresh')
112
119
  result.freshCount += 1;
113
120
  else
@@ -119,31 +126,48 @@ export function reconcileSurfaceFreshness(cwd, currentVersion) {
119
126
  * Build the advisory for a stale-surface scan, or `undefined` when there is
120
127
  * nothing to say.
121
128
  *
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
+ * NO `next_actions`, deliberately. The recovery is a CLI command, and there is
130
+ * no MCP tool that performs it — `bclaw_setup` is the onboarding wizard and
131
+ * takes no write flag. Pointing at it anyway would ship a next_action whose
132
+ * args the engine rejects, which is the precise class of drift this plan exists
133
+ * to eliminate; and per pln#634's own rule, a builder with no genuine follow-up
134
+ * returns nothing rather than inventing one. The command therefore travels in
135
+ * the message, where it is true.
136
+ *
137
+ * WHICH command depends on what is stale (trp_6a49f976): this advisory shipped
138
+ * in 1.20.0 recommending `brainclaw export --write`, which the CLI rejects
139
+ * (a mode flag is required) and which — even corrected to `--all` — never
140
+ * touches live companions, the very files the first real-world firing listed.
141
+ * The recovery must be per kind, and only for the kinds actually stale.
129
142
  */
130
143
  export function staleSurfaceWarning(result, currentVersion) {
131
144
  if (result.stale.length === 0)
132
145
  return undefined;
133
146
  const shown = result.stale.slice(0, 8);
134
147
  const overflow = result.stale.length - shown.length;
148
+ const kinds = new Set(result.stale.map((s) => s.kind));
149
+ const commands = [
150
+ ...(kinds.has('stable') ? [STABLE_SURFACE_REFRESH_COMMAND] : []),
151
+ ...(kinds.has('live') ? [LIVE_SURFACE_REFRESH_COMMAND] : []),
152
+ ];
153
+ const recovery = kinds.size === 2
154
+ ? ` Run \`${STABLE_SURFACE_REFRESH_COMMAND}\` (stable surfaces) and \`${LIVE_SURFACE_REFRESH_COMMAND}\` (live companions) to refresh them.`
155
+ : ` Run \`${commands[0]}\` to refresh them.`;
135
156
  return {
136
157
  code: 'generated_surfaces_stale',
137
158
  message: `${result.stale.length} generated guidance surface(s) were written by an older brainclaw than v${currentVersion}: `
138
159
  + shown.map((s) => `${s.relativePath} (v${s.stampedVersion})`).join(', ')
139
160
  + (overflow > 0 ? ` (+${overflow} more)` : '')
140
161
  + '. An agent tier that never triggers a regeneration is reading them as-is.'
141
- + ' Run `brainclaw export --write` to refresh them.',
162
+ + recovery,
142
163
  data: {
143
164
  current_version: currentVersion,
144
- stale_surfaces: shown.map((s) => ({ path: s.relativePath, stamped_version: s.stampedVersion })),
165
+ stale_surfaces: shown.map((s) => ({ path: s.relativePath, stamped_version: s.stampedVersion, kind: s.kind })),
145
166
  ...(overflow > 0 ? { stale_surfaces_omitted: overflow } : {}),
146
- refresh_command: 'brainclaw export --write',
167
+ // Kept as a single runnable string for consumers that shipped against
168
+ // 1.20.0; refresh_commands is the structured form.
169
+ refresh_command: commands.join(' && '),
170
+ refresh_commands: commands,
147
171
  },
148
172
  };
149
173
  }
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.20.0 on 2026-08-02T17:17:41.989Z
2
+ // Source: brainclaw v1.20.1 on 2026-08-02T21:44:51.056Z
3
3
  export const FACTS = {
4
- "version": "1.20.0",
5
- "generated_at": "2026-08-02T17:17:41.989Z",
4
+ "version": "1.20.1",
5
+ "generated_at": "2026-08-02T21:44:51.056Z",
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-08-02T17:17:40.300Z",
477
+ "generated_at": "2026-08-02T21:44:48.979Z",
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": 65,
486
+ "duration_ms_median": 76,
487
487
  "payload_chars_median": 1640,
488
488
  "payload_tokens_est_median": 410
489
489
  },
@@ -491,15 +491,15 @@ 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": 101,
495
- "payload_chars_median": 2625,
496
- "payload_tokens_est_median": 656
494
+ "duration_ms_median": 121,
495
+ "payload_chars_median": 2626,
496
+ "payload_tokens_est_median": 657
497
497
  },
498
498
  {
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": 10,
502
+ "duration_ms_median": 12,
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.20.0",
3
- "generated_at": "2026-08-02T17:17:41.989Z",
2
+ "version": "1.20.1",
3
+ "generated_at": "2026-08-02T21:44:51.056Z",
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-08-02T17:17:40.300Z",
475
+ "generated_at": "2026-08-02T21:44:48.979Z",
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": 65,
484
+ "duration_ms_median": 76,
485
485
  "payload_chars_median": 1640,
486
486
  "payload_tokens_est_median": 410
487
487
  },
@@ -489,15 +489,15 @@
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": 101,
493
- "payload_chars_median": 2625,
494
- "payload_tokens_est_median": 656
492
+ "duration_ms_median": 121,
493
+ "payload_chars_median": 2626,
494
+ "payload_tokens_est_median": 657
495
495
  },
496
496
  {
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": 10,
500
+ "duration_ms_median": 12,
501
501
  "payload_chars_median": 499,
502
502
  "payload_tokens_est_median": 125
503
503
  }
@@ -8,6 +8,21 @@ guarantees this changelog follows.
8
8
 
9
9
  ---
10
10
 
11
+ ## [1.20.1] — 2026-08-02
12
+
13
+ **Changed — `generated_surfaces_stale` recovery data is now true, and structured (#163)**
14
+ - The VALUE of `data.refresh_command` changes: it was `brainclaw export --write`,
15
+ which the CLI rejects (a mode flag is required), and is now a runnable command
16
+ derived from what is actually stale — `brainclaw export --all --write` (stable
17
+ surfaces), `brainclaw refresh` (live companions), or both joined with ` && `
18
+ when both kinds are stale. The field keeps its type (string); consumers that
19
+ displayed it verbatim now display something that works.
20
+ - Additive fields on the same warning's `data`: `refresh_commands: string[]`
21
+ (the structured form of the above) and `kind: "stable" | "live"` on each
22
+ `stale_surfaces[]` entry.
23
+ - Read contract only: no tool added/removed/renamed, no inputSchema change,
24
+ no surface-fingerprint movement.
25
+
11
26
  ## [1.20.0] — 2026-08-02
12
27
 
13
28
  **Fixed — 1.19.0 contract entries now actually emitted (#158)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainclaw",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "description": "Shared project memory for humans and coding agents.",
5
5
  "type": "module",
6
6
  "repository": {