@viloforge/vfkb 0.2.3 → 0.4.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.
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  import { randomBytes as randomBytes2 } from "node:crypto";
10
10
 
11
11
  // src/storage.ts
12
- import { basename, dirname, join as join3, resolve } from "node:path";
12
+ import { basename, dirname as dirname2, join as join4, resolve } from "node:path";
13
13
  import { homedir } from "node:os";
14
14
  import { createHash } from "node:crypto";
15
15
 
@@ -207,6 +207,134 @@ function storageBackend() {
207
207
  return current;
208
208
  }
209
209
 
210
+ // src/journal.ts
211
+ import { execFileSync } from "node:child_process";
212
+ import { appendFileSync as appendFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
213
+ import { dirname, join as join3, relative, sep } from "node:path";
214
+ var walPath = (brain) => join3(brain, ".journal", "wal.jsonl");
215
+ var suppressedPath = (brain) => join3(brain, ".journal", "suppressed");
216
+ function rewriteWal(wal, keep) {
217
+ const tmp = `${wal}.tmp`;
218
+ writeFileSync2(tmp, keep.length > 0 ? keep.map((l) => l.raw).join("\n") + "\n" : "", "utf8");
219
+ renameSync(tmp, wal);
220
+ }
221
+ var pairOf = (r) => `${String(r.id)}\0${String(r.updated ?? "")}`;
222
+ function parseLines(text) {
223
+ const out = [];
224
+ for (const raw of text.split("\n")) {
225
+ if (raw.trim().length === 0) continue;
226
+ try {
227
+ const rec = JSON.parse(raw);
228
+ if (typeof rec.id !== "string") continue;
229
+ out.push({ raw, pair: pairOf(rec), id: rec.id });
230
+ } catch {
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+ function pairsOfFile(path) {
236
+ if (!existsSync2(path)) return /* @__PURE__ */ new Set();
237
+ return new Set(parseLines(readFileSync3(path, "utf8")).map((l) => l.pair));
238
+ }
239
+ function suppressedPairs(brain) {
240
+ const p = suppressedPath(brain);
241
+ if (!existsSync2(p)) return /* @__PURE__ */ new Set();
242
+ return new Set(
243
+ readFileSync3(p, "utf8").split("\n").filter((l) => l.includes(" ")).map((l) => l.replace(" ", "\0"))
244
+ );
245
+ }
246
+ function journalAppend(brain, rec) {
247
+ if (process.env.VFKB_NO_JOURNAL) return;
248
+ try {
249
+ mkdirSync3(join3(brain, ".journal"), { recursive: true });
250
+ appendFileSync2(walPath(brain), JSON.stringify(rec) + "\n", "utf8");
251
+ } catch (err) {
252
+ process.stderr.write(
253
+ `vfkb: journal mirror failed (primary append proceeds unprotected): ${err.message}
254
+ `
255
+ );
256
+ }
257
+ }
258
+ function pairsAtHead(brain) {
259
+ const repoDir = dirname(brain);
260
+ const git2 = (...a) => execFileSync("git", ["-C", repoDir, ...a], {
261
+ encoding: "utf8",
262
+ stdio: ["ignore", "pipe", "ignore"]
263
+ }).trim();
264
+ try {
265
+ if (git2("rev-parse", "--is-inside-work-tree") !== "true") return "not-git";
266
+ } catch {
267
+ return "not-git";
268
+ }
269
+ try {
270
+ const top = git2("rev-parse", "--show-toplevel");
271
+ const rel = relative(top, join3(brain, "entries.jsonl")).split(sep).join("/");
272
+ const head = execFileSync("git", ["-C", repoDir, "cat-file", "-p", `HEAD:${rel}`], {
273
+ encoding: "utf8",
274
+ stdio: ["ignore", "pipe", "ignore"]
275
+ });
276
+ return new Set(parseLines(head).map((l) => l.pair));
277
+ } catch {
278
+ return "unknown";
279
+ }
280
+ }
281
+ function recoverFromJournal(brain) {
282
+ if (process.env.VFKB_NO_JOURNAL) return { restored: 0, pruned: 0 };
283
+ const wal = walPath(brain);
284
+ if (!existsSync2(wal)) return { restored: 0, pruned: 0 };
285
+ const walLines = parseLines(readFileSync3(wal, "utf8"));
286
+ if (walLines.length === 0) return { restored: 0, pruned: 0 };
287
+ const entriesPath = join3(brain, "entries.jsonl");
288
+ const present = pairsOfFile(entriesPath);
289
+ const suppressed = suppressedPairs(brain);
290
+ const toRestore = walLines.filter((l) => !present.has(l.pair) && !suppressed.has(l.pair));
291
+ if (toRestore.length > 0) {
292
+ let prefix = "";
293
+ if (existsSync2(entriesPath)) {
294
+ const cur = readFileSync3(entriesPath, "utf8");
295
+ if (cur.length > 0 && !cur.endsWith("\n")) prefix = "\n";
296
+ }
297
+ appendFileSync2(entriesPath, prefix + toRestore.map((l) => l.raw).join("\n") + "\n", "utf8");
298
+ }
299
+ const head = pairsAtHead(brain);
300
+ let keep;
301
+ if (head === "unknown") {
302
+ keep = walLines;
303
+ } else if (head === "not-git") {
304
+ keep = walLines.filter((l) => !suppressed.has(l.pair) && !present.has(l.pair) && !toRestore.includes(l));
305
+ } else {
306
+ keep = walLines.filter((l) => !suppressed.has(l.pair) && !head.has(l.pair));
307
+ }
308
+ const pruned = walLines.length - keep.length;
309
+ if (pruned > 0) {
310
+ rewriteWal(wal, keep);
311
+ }
312
+ return { restored: toRestore.length, pruned };
313
+ }
314
+ function purgeJournal(brain, opts) {
315
+ const wal = walPath(brain);
316
+ if (!existsSync2(wal)) return { purged: 0 };
317
+ const walLines = parseLines(readFileSync3(wal, "utf8"));
318
+ const gone = walLines.filter((l) => opts.all ? true : l.id === opts.id);
319
+ if (gone.length === 0) return { purged: 0 };
320
+ const keep = walLines.filter((l) => !gone.includes(l));
321
+ mkdirSync3(join3(brain, ".journal"), { recursive: true });
322
+ appendFileSync2(suppressedPath(brain), gone.map((l) => l.pair.replace("\0", " ")).join("\n") + "\n", "utf8");
323
+ rewriteWal(wal, keep);
324
+ return { purged: gone.length };
325
+ }
326
+ function journalStatus(brain) {
327
+ const wal = walPath(brain);
328
+ const walLines = existsSync2(wal) ? parseLines(readFileSync3(wal, "utf8")) : [];
329
+ const present = pairsOfFile(join3(brain, "entries.jsonl"));
330
+ const suppressed = suppressedPairs(brain);
331
+ return {
332
+ walLines: walLines.length,
333
+ restorable: walLines.filter((l) => !present.has(l.pair) && !suppressed.has(l.pair)).length,
334
+ suppressedInEntries: [...suppressed].filter((p) => present.has(p)).length
335
+ };
336
+ }
337
+
210
338
  // node_modules/zod/v4/classic/external.js
211
339
  var external_exports = {};
212
340
  __export(external_exports, {
@@ -14772,7 +14900,7 @@ function isTombstone(r) {
14772
14900
  return r.deleted === true;
14773
14901
  }
14774
14902
  function brainDir() {
14775
- return process.env.VFKB_DATA_DIR || process.env.VFKB_DIR || join3(homedir(), ".vfkb");
14903
+ return process.env.VFKB_DATA_DIR || process.env.VFKB_DIR || join4(homedir(), ".vfkb");
14776
14904
  }
14777
14905
  function defaultProject() {
14778
14906
  const raw = (() => {
@@ -14781,7 +14909,7 @@ function defaultProject() {
14781
14909
  if (explicit) {
14782
14910
  const abs = resolve(explicit);
14783
14911
  const name = basename(abs);
14784
- return name.startsWith(".") ? basename(dirname(abs)) : name;
14912
+ return name.startsWith(".") ? basename(dirname2(abs)) : name;
14785
14913
  }
14786
14914
  const root = process.env.CLAUDE_PROJECT_DIR;
14787
14915
  if (root) return basename(resolve(root));
@@ -14790,7 +14918,9 @@ function defaultProject() {
14790
14918
  return raw.replace(/["<>&]/g, "") || "spike";
14791
14919
  }
14792
14920
  function appendRecord(rec) {
14793
- storageBackend().append(rec);
14921
+ const be = storageBackend();
14922
+ if (be.name === "jsonl-fs") journalAppend(be.location(), rec);
14923
+ be.append(rec);
14794
14924
  writeMeta();
14795
14925
  }
14796
14926
  function withExclusive(fn) {
@@ -14960,21 +15090,21 @@ function assertNoSecrets(text) {
14960
15090
  }
14961
15091
 
14962
15092
  // src/counters.ts
14963
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
14964
- import { join as join4 } from "node:path";
15093
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, existsSync as existsSync3 } from "node:fs";
15094
+ import { join as join5 } from "node:path";
14965
15095
  function signalsFile() {
14966
- return join4(brainDir(), ".signals", "counters.jsonl");
15096
+ return join5(brainDir(), ".signals", "counters.jsonl");
14967
15097
  }
14968
15098
  function recordSignal(entryId, kind, source) {
14969
15099
  const sig = { entryId, kind, at: (/* @__PURE__ */ new Date()).toISOString(), source };
14970
- mkdirSync3(join4(brainDir(), ".signals"), { recursive: true });
14971
- appendFileSync2(signalsFile(), JSON.stringify(sig) + "\n", "utf8");
15100
+ mkdirSync4(join5(brainDir(), ".signals"), { recursive: true });
15101
+ appendFileSync3(signalsFile(), JSON.stringify(sig) + "\n", "utf8");
14972
15102
  return sig;
14973
15103
  }
14974
15104
  function readSignals() {
14975
15105
  const f = signalsFile();
14976
- if (!existsSync2(f)) return [];
14977
- return readFileSync3(f, "utf8").split("\n").filter((l) => l.trim().length > 0).map((l) => JSON.parse(l));
15106
+ if (!existsSync3(f)) return [];
15107
+ return readFileSync4(f, "utf8").split("\n").filter((l) => l.trim().length > 0).map((l) => JSON.parse(l));
14978
15108
  }
14979
15109
  function tally(entryId, signals = readSignals()) {
14980
15110
  let helpful = 0;
@@ -15350,6 +15480,22 @@ function latestHandoff(all = readAll(), today = nowIso().slice(0, 10), supersede
15350
15480
  }
15351
15481
  return latest;
15352
15482
  }
15483
+ var CROSS_REPO_PIN_CAP_CHARS = 2e3;
15484
+ function latestCrossRepo(all = readAll(), today = nowIso().slice(0, 10), superseded = supersededIds(all)) {
15485
+ let latest = null;
15486
+ for (const e of all) {
15487
+ if (!e.tags.includes("cross-repo")) continue;
15488
+ if (e.tags.includes("handoff") || e.tags.includes("next")) continue;
15489
+ if (!isInjectable(e, today, superseded)) continue;
15490
+ if (!latest) {
15491
+ latest = e;
15492
+ continue;
15493
+ }
15494
+ const cmp = e.updated.localeCompare(latest.updated) || e.created.localeCompare(latest.created);
15495
+ if (cmp >= 0) latest = e;
15496
+ }
15497
+ return latest;
15498
+ }
15353
15499
  function renderContextBundle(project = defaultProject(), budget = SESSION_BUDGET_CHARS) {
15354
15500
  const all = readAll();
15355
15501
  const today = nowIso().slice(0, 10);
@@ -15377,25 +15523,57 @@ function renderContextBundle(project = defaultProject(), budget = SESSION_BUDGET
15377
15523
  body += `## Last handoff
15378
15524
  - [${handoff.type} ${trustGlyph(handoff)}] ${text}
15379
15525
 
15526
+ `;
15527
+ }
15528
+ const crossRepo = latestCrossRepo(all, today, superseded);
15529
+ if (crossRepo && !constitutionalIds.has(crossRepo.id)) {
15530
+ const text = crossRepo.text.length > CROSS_REPO_PIN_CAP_CHARS ? `${crossRepo.text.slice(0, CROSS_REPO_PIN_CAP_CHARS)}\u2026 (truncated \u2014 kb_get ${crossRepo.id} for the rest)` : crossRepo.text;
15531
+ body += `## Cross-repo operations
15532
+ - [${crossRepo.type} ${trustGlyph(crossRepo)}] ${text}
15533
+
15380
15534
  `;
15381
15535
  }
15382
15536
  body += renderContextMap() + "\n\n";
15537
+ const kept = [];
15538
+ let keptLen = 0;
15383
15539
  let dropped = 0;
15540
+ const fixedLen = () => header.length + body.length + footer.length;
15384
15541
  for (const e of injectable) {
15385
15542
  if (constitutionalIds.has(e.id)) continue;
15386
15543
  if (handoff && e.id === handoff.id) continue;
15544
+ if (crossRepo && e.id === crossRepo.id) continue;
15387
15545
  const line = `- [${e.type} ${trustGlyph(e)}] ${e.text}
15388
15546
  `;
15389
- if (header.length + body.length + line.length + footer.length > budget) {
15547
+ if (fixedLen() + keptLen + line.length > budget) {
15390
15548
  dropped++;
15391
15549
  continue;
15392
15550
  }
15393
- body += line;
15551
+ kept.push(line);
15552
+ keptLen += line.length;
15394
15553
  }
15395
15554
  if (dropped > 0) {
15396
- const note = `<!-- ${dropped} lower-ranked entries omitted for the ${budget}-char budget -->
15555
+ const note = () => `(+ ${dropped} lower-ranked entries omitted for the ${budget}-char budget \u2014 kb_search / kb_list pulls them)
15397
15556
  `;
15398
- if (header.length + body.length + note.length + footer.length <= budget) body += note;
15557
+ const evicted = [];
15558
+ while (kept.length > 0 && fixedLen() + keptLen + note().length > budget) {
15559
+ const line = kept.pop();
15560
+ keptLen -= line.length;
15561
+ evicted.push(line);
15562
+ dropped++;
15563
+ }
15564
+ if (fixedLen() + keptLen + note().length <= budget) {
15565
+ body += kept.join("") + note();
15566
+ } else {
15567
+ while (evicted.length > 0) {
15568
+ const line = evicted.pop();
15569
+ kept.push(line);
15570
+ keptLen += line.length;
15571
+ dropped--;
15572
+ }
15573
+ body += kept.join("");
15574
+ }
15575
+ } else {
15576
+ body += kept.join("");
15399
15577
  }
15400
15578
  return header + body + footer;
15401
15579
  }
@@ -15563,15 +15741,15 @@ ${bundle}`;
15563
15741
 
15564
15742
  // src/export.ts
15565
15743
  import {
15566
- existsSync as existsSync3,
15567
- mkdirSync as mkdirSync4,
15744
+ existsSync as existsSync4,
15745
+ mkdirSync as mkdirSync5,
15568
15746
  readdirSync as readdirSync2,
15569
- readFileSync as readFileSync4,
15747
+ readFileSync as readFileSync5,
15570
15748
  rmSync,
15571
15749
  statSync as statSync2,
15572
- writeFileSync as writeFileSync2
15750
+ writeFileSync as writeFileSync3
15573
15751
  } from "node:fs";
15574
- import { dirname as dirname2, join as join5 } from "node:path";
15752
+ import { dirname as dirname3, join as join6 } from "node:path";
15575
15753
  var GENERATED_MARKER = "generated by vfkb export";
15576
15754
  var MARKER_LINE_RE = /^<!-- generated by vfkb export /m;
15577
15755
  var EXPORT_BUDGET_CHARS = 4e4;
@@ -15620,7 +15798,7 @@ function listFiles(dir) {
15620
15798
  const out = [];
15621
15799
  const walk = (d) => {
15622
15800
  for (const name of readdirSync2(d)) {
15623
- const p = join5(d, name);
15801
+ const p = join6(d, name);
15624
15802
  if (statSync2(p).isDirectory()) walk(p);
15625
15803
  else out.push(p);
15626
15804
  }
@@ -15631,14 +15809,14 @@ function listFiles(dir) {
15631
15809
  function isGenerated(path) {
15632
15810
  if (!path.endsWith(".md")) return false;
15633
15811
  try {
15634
- return MARKER_LINE_RE.test(readFileSync4(path, "utf8"));
15812
+ return MARKER_LINE_RE.test(readFileSync5(path, "utf8"));
15635
15813
  } catch {
15636
15814
  return false;
15637
15815
  }
15638
15816
  }
15639
15817
  function prepareTree(dir) {
15640
- if (!existsSync3(dir)) {
15641
- mkdirSync4(dir, { recursive: true });
15818
+ if (!existsSync4(dir)) {
15819
+ mkdirSync5(dir, { recursive: true });
15642
15820
  return;
15643
15821
  }
15644
15822
  const files = listFiles(dir);
@@ -15653,7 +15831,7 @@ function prepareTree(dir) {
15653
15831
  const dirs = [];
15654
15832
  const collect = (d) => {
15655
15833
  for (const name of readdirSync2(d)) {
15656
- const p = join5(d, name);
15834
+ const p = join6(d, name);
15657
15835
  if (statSync2(p).isDirectory()) {
15658
15836
  collect(p);
15659
15837
  dirs.push(p);
@@ -15669,8 +15847,8 @@ function exportAgentsMd(opts = {}) {
15669
15847
  const { superseded, asOf, eligible } = projectionInputs();
15670
15848
  const project = opts.project ?? defaultProject();
15671
15849
  const budget = opts.budget ?? EXPORT_BUDGET_CHARS;
15672
- const out = opts.out ?? join5(process.cwd(), "AGENTS.md");
15673
- if (existsSync3(out) && !MARKER_LINE_RE.test(readFileSync4(out, "utf8"))) {
15850
+ const out = opts.out ?? join6(process.cwd(), "AGENTS.md");
15851
+ if (existsSync4(out) && !MARKER_LINE_RE.test(readFileSync5(out, "utf8"))) {
15674
15852
  throw new Error(
15675
15853
  `vfkb export: refusing to overwrite ${out} \u2014 it is not a previous export (no generated marker)`
15676
15854
  );
@@ -15729,8 +15907,8 @@ ${spine.trim()}
15729
15907
  }
15730
15908
  if (dropped > 0) body += `<!-- ${dropped} entries omitted for the ${budget}-char budget -->
15731
15909
  `;
15732
- mkdirSync4(dirname2(out), { recursive: true });
15733
- writeFileSync2(out, head + body);
15910
+ mkdirSync5(dirname3(out), { recursive: true });
15911
+ writeFileSync3(out, head + body);
15734
15912
  return { path: out };
15735
15913
  }
15736
15914
  function okfDoc(e, asOf, exportedDirs) {
@@ -15827,14 +16005,14 @@ function deriveLog(eligibleIds, records, live) {
15827
16005
  function exportOkf(opts = {}) {
15828
16006
  const { superseded, asOf, eligible } = projectionInputs();
15829
16007
  const project = opts.project ?? defaultProject();
15830
- const dir = opts.out ?? join5(process.cwd(), ".okf");
16008
+ const dir = opts.out ?? join6(process.cwd(), ".okf");
15831
16009
  prepareTree(dir);
15832
16010
  const marker = `<!-- ${GENERATED_MARKER} okf; regenerate with \`vfkb export okf\`, do not hand-edit (as-of ${asOf}) -->`;
15833
16011
  const files = [];
15834
16012
  const write = (rel, content) => {
15835
- const p = join5(dir, rel);
15836
- mkdirSync4(dirname2(p), { recursive: true });
15837
- writeFileSync2(p, content);
16013
+ const p = join6(dir, rel);
16014
+ mkdirSync5(dirname3(p), { recursive: true });
16015
+ writeFileSync3(p, content);
15838
16016
  files.push(rel);
15839
16017
  };
15840
16018
  const exportedDirs = new Map(eligible.map((e) => [e.id, TYPE_DIR[e.type]]));
@@ -15888,6 +16066,134 @@ function runExport(target, opts = {}) {
15888
16066
  throw new Error("usage: vfkb export <agents-md|okf> [--out <path>]");
15889
16067
  }
15890
16068
 
16069
+ // src/broadcast.ts
16070
+ import { execFileSync as execFileSync2 } from "node:child_process";
16071
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
16072
+ import { basename as basename2, join as join8, resolve as resolve2 } from "node:path";
16073
+
16074
+ // src/manifest.ts
16075
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
16076
+ import { dirname as dirname4, join as join7 } from "node:path";
16077
+
16078
+ // src/version.ts
16079
+ var SCHEMA_VERSION = 1;
16080
+ var ENGINE_VERSION = true ? "0.4.1" : ownPackageVersion();
16081
+ var ENGINE_COMMIT = true ? "66ee821" : "dev";
16082
+
16083
+ // src/manifest.ts
16084
+ function manifestPath(brainDir2) {
16085
+ return join7(brainDir2, "manifest.json");
16086
+ }
16087
+ function currentManifest() {
16088
+ return { schema_version: SCHEMA_VERSION, engine_version: ENGINE_VERSION, engine_commit: ENGINE_COMMIT };
16089
+ }
16090
+ function readManifest(brainDir2) {
16091
+ const p = manifestPath(brainDir2);
16092
+ if (!existsSync5(p)) return void 0;
16093
+ try {
16094
+ return JSON.parse(readFileSync6(p, "utf8"));
16095
+ } catch {
16096
+ return void 0;
16097
+ }
16098
+ }
16099
+ function writeManifest(brainDir2) {
16100
+ const p = manifestPath(brainDir2);
16101
+ const existed = existsSync5(p);
16102
+ const cur = readManifest(brainDir2);
16103
+ const next = currentManifest();
16104
+ if (cur && JSON.stringify(cur) === JSON.stringify(next)) return "skipped";
16105
+ mkdirSync6(dirname4(p), { recursive: true });
16106
+ writeFileSync4(p, JSON.stringify(next, null, 2) + "\n");
16107
+ return existed ? "updated" : "created";
16108
+ }
16109
+
16110
+ // src/broadcast.ts
16111
+ var FORBIDDEN_TAGS = /* @__PURE__ */ new Set(["handoff", "next"]);
16112
+ function targetBrainDir(target) {
16113
+ const abs = resolve2(target);
16114
+ return basename2(abs) === ".vfkb" ? abs : join8(abs, ".vfkb");
16115
+ }
16116
+ function gitPosture(repoDir) {
16117
+ const git2 = (...a) => execFileSync2("git", ["-C", repoDir, ...a], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
16118
+ try {
16119
+ const branch = git2("rev-parse", "--abbrev-ref", "HEAD");
16120
+ const parked = branch === "main" || branch === "master";
16121
+ return `uncommitted; target on ${branch}${parked ? " (parked \u2014 rides until a topic-branch brain commit)" : ""}`;
16122
+ } catch {
16123
+ try {
16124
+ if (git2("rev-parse", "--is-inside-work-tree") === "true") {
16125
+ return "uncommitted; target git repository on an unborn branch (no commits yet)";
16126
+ }
16127
+ } catch {
16128
+ }
16129
+ return "uncommitted; target not a git repository";
16130
+ }
16131
+ }
16132
+ function broadcast(text, targets, opts = {}) {
16133
+ const origin = defaultProject();
16134
+ const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
16135
+ const extraTags = opts.tags ?? [];
16136
+ for (const t of extraTags) {
16137
+ if (FORBIDDEN_TAGS.has(t)) {
16138
+ throw new Error(
16139
+ `broadcast: tag '${t}' is forbidden on cross-repo records \u2014 it claims the resident's ADR-0049 continuity pin (ADR-0063 \xA71)`
16140
+ );
16141
+ }
16142
+ }
16143
+ const op = (opts.op || "OPERATION").toUpperCase();
16144
+ const MARKER = /^CROSS-REPO \S+ \(\d{4}-\d{2}-\d{2}, from [^)]+\):/;
16145
+ const stamped = MARKER.test(text) ? text : `CROSS-REPO ${op} (${date5}, from ${origin}): ${text}`;
16146
+ const results = [];
16147
+ const written = /* @__PURE__ */ new Set();
16148
+ for (const target of targets) {
16149
+ const brain = targetBrainDir(target);
16150
+ if (written.has(brain)) {
16151
+ results.push({ target, ok: false, reason: "duplicate target (already written in this broadcast)" });
16152
+ continue;
16153
+ }
16154
+ const repoDir = resolve2(brain, "..");
16155
+ const manifestPath2 = join8(brain, "manifest.json");
16156
+ let healed = false;
16157
+ if (!existsSync6(manifestPath2)) {
16158
+ if (!existsSync6(join8(brain, "entries.jsonl"))) {
16159
+ results.push({ target, ok: false, reason: `no brain (no entries.jsonl in ${brain}) \u2014 never bootstrap a wire-less brain (ADR-0063 \xA73)` });
16160
+ continue;
16161
+ }
16162
+ try {
16163
+ writeManifest(brain);
16164
+ } catch (err) {
16165
+ results.push({ target, ok: false, reason: `manifest heal failed: ${err.message}` });
16166
+ continue;
16167
+ }
16168
+ healed = true;
16169
+ }
16170
+ let schema;
16171
+ try {
16172
+ schema = JSON.parse(readFileSync7(manifestPath2, "utf8")).schema_version;
16173
+ } catch {
16174
+ results.push({ target, ok: false, reason: "unreadable manifest.json" });
16175
+ continue;
16176
+ }
16177
+ if (schema !== SCHEMA_VERSION) {
16178
+ results.push({ target, ok: false, reason: `brain schema ${JSON.stringify(schema)} unsupported by engine schema v${SCHEMA_VERSION}` });
16179
+ continue;
16180
+ }
16181
+ const prev = process.env.VFKB_DATA_DIR;
16182
+ try {
16183
+ process.env.VFKB_DATA_DIR = brain;
16184
+ const e = addEntry("fact", stamped, { role: "executor", tags: [.../* @__PURE__ */ new Set(["cross-repo", ...extraTags])] });
16185
+ written.add(brain);
16186
+ results.push({ target, ok: true, id: e.id, posture: gitPosture(repoDir), ...healed ? { healed } : {} });
16187
+ } catch (err) {
16188
+ results.push({ target, ok: false, reason: err.message, ...healed ? { healed } : {} });
16189
+ } finally {
16190
+ if (prev === void 0) delete process.env.VFKB_DATA_DIR;
16191
+ else process.env.VFKB_DATA_DIR = prev;
16192
+ }
16193
+ }
16194
+ return results;
16195
+ }
16196
+
15891
16197
  // src/curator.ts
15892
16198
  function get(id) {
15893
16199
  const e = readAll().find((x) => x.id === id);
@@ -16086,7 +16392,7 @@ function queryExplained(opts = {}) {
16086
16392
  }
16087
16393
 
16088
16394
  // src/gating.ts
16089
- import { resolve as resolve2 } from "node:path";
16395
+ import { resolve as resolve3 } from "node:path";
16090
16396
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
16091
16397
  "write",
16092
16398
  "edit",
@@ -16104,16 +16410,16 @@ function isBrainWrite(toolName, input, brain = brainDir()) {
16104
16410
  if (!toolName || !WRITE_TOOLS.has(toolName.toLowerCase())) return false;
16105
16411
  const p = extractPath(input);
16106
16412
  if (!p) return false;
16107
- const abs = resolve2(p);
16108
- const root = resolve2(brain);
16413
+ const abs = resolve3(p);
16414
+ const root = resolve3(brain);
16109
16415
  return abs === root || abs.startsWith(root + "/");
16110
16416
  }
16111
16417
  var GATING_REASON = "vfkb: edit the brain via the engine/CLI/MCP, not by writing files directly (keeps the index, freshness, and no-secrets invariants).";
16112
16418
 
16113
16419
  // src/stop-reminder.ts
16114
- import { execFileSync } from "node:child_process";
16115
- import { readFileSync as readFileSync5 } from "node:fs";
16116
- import { join as join6, relative } from "node:path";
16420
+ import { execFileSync as execFileSync3 } from "node:child_process";
16421
+ import { readFileSync as readFileSync8 } from "node:fs";
16422
+ import { join as join9, relative as relative2 } from "node:path";
16117
16423
  var STOP_REMINDER = 'vfkb decision-capture check: this turn changed code/docs but no `decision` was recorded to the brain. If a load-bearing decision was made, capture it now via `mcp__vfkb__kb_add` (type=decision, why=<rationale>, role=human) \u2014 or `vfkb add decision "\u2026" --why "\u2026" --role human` \u2014 and add an ADR under docs/adr/ for anything architectural. If NO decision was made this turn, just finish normally.';
16118
16424
  var HANDOFF_MIN_ENTRIES = 3;
16119
16425
  var HANDOFF_REMINDER = 'vfkb handoff check: this session has recorded knowledge but no `handoff`/`next` entry. If you are WRAPPING UP, record a durable handoff now \u2014 `mcp__vfkb__kb_add` (type=fact, tags=handoff,next, role=human) naming what the NEXT session should pick up (a real "next:", not just a summary). If you are still mid-session, ignore this and finish normally \u2014 the SessionEnd floor will leave a fallback if you never do.';
@@ -16129,11 +16435,11 @@ function decideStop(input, ctx) {
16129
16435
  function hasUncommittedWork(cwd = process.cwd(), brain = brainDir()) {
16130
16436
  let out;
16131
16437
  try {
16132
- out = execFileSync("git", ["status", "--porcelain"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
16438
+ out = execFileSync3("git", ["status", "--porcelain"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
16133
16439
  } catch {
16134
16440
  return false;
16135
16441
  }
16136
- const brainRel = relative(cwd, brain).replace(/\\/g, "/");
16442
+ const brainRel = relative2(cwd, brain).replace(/\\/g, "/");
16137
16443
  return out.split("\n").some((line) => {
16138
16444
  const p = line.slice(3).trim();
16139
16445
  if (!p) return false;
@@ -16142,17 +16448,17 @@ function hasUncommittedWork(cwd = process.cwd(), brain = brainDir()) {
16142
16448
  });
16143
16449
  }
16144
16450
  function newBrainEntriesSinceHead(brain = brainDir(), cwd = process.cwd()) {
16145
- const file2 = join6(brain, "entries.jsonl");
16451
+ const file2 = join9(brain, "entries.jsonl");
16146
16452
  let current2;
16147
16453
  try {
16148
- current2 = readFileSync5(file2, "utf8").split("\n").filter(Boolean);
16454
+ current2 = readFileSync8(file2, "utf8").split("\n").filter(Boolean);
16149
16455
  } catch {
16150
16456
  return [];
16151
16457
  }
16152
- const rel = relative(cwd, file2).replace(/\\/g, "/");
16458
+ const rel = relative2(cwd, file2).replace(/\\/g, "/");
16153
16459
  let headCount = 0;
16154
16460
  try {
16155
- const head = execFileSync("git", ["show", `HEAD:${rel}`], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
16461
+ const head = execFileSync3("git", ["show", `HEAD:${rel}`], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
16156
16462
  headCount = head.split("\n").filter(Boolean).length;
16157
16463
  } catch {
16158
16464
  headCount = 0;
@@ -16177,14 +16483,14 @@ function gatherStopContext(cwd = process.cwd(), brain = brainDir()) {
16177
16483
  }
16178
16484
 
16179
16485
  // src/git.ts
16180
- import { execFileSync as execFileSync2 } from "node:child_process";
16181
- import { existsSync as existsSync4 } from "node:fs";
16182
- import { join as join7 } from "node:path";
16486
+ import { execFileSync as execFileSync4 } from "node:child_process";
16487
+ import { existsSync as existsSync7 } from "node:fs";
16488
+ import { join as join10 } from "node:path";
16183
16489
  function git(args, cwd) {
16184
- return execFileSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
16490
+ return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
16185
16491
  }
16186
16492
  function ensureRepo(brain) {
16187
- if (!existsSync4(join7(brain, ".git"))) {
16493
+ if (!existsSync7(join10(brain, ".git"))) {
16188
16494
  git(["init", "-q"], brain);
16189
16495
  }
16190
16496
  }
@@ -16210,12 +16516,12 @@ function save(message = "vfkb: update", role = "engine", brain = brainDir()) {
16210
16516
  }
16211
16517
 
16212
16518
  // src/session-end.ts
16213
- import { execFileSync as execFileSync3 } from "node:child_process";
16214
- import { readFileSync as readFileSync6 } from "node:fs";
16215
- import { join as join8, isAbsolute } from "node:path";
16216
- var realGit = (args, cwd) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
16519
+ import { execFileSync as execFileSync5 } from "node:child_process";
16520
+ import { readFileSync as readFileSync9 } from "node:fs";
16521
+ import { join as join11, isAbsolute } from "node:path";
16522
+ var realGit = (args, cwd) => execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
16217
16523
  function brainEntriesRelPath(dataDir2) {
16218
- return join8(dataDir2, "entries.jsonl").replace(/\\/g, "/");
16524
+ return join11(dataDir2, "entries.jsonl").replace(/\\/g, "/");
16219
16525
  }
16220
16526
  function tryGit(git2, args, cwd) {
16221
16527
  try {
@@ -16239,7 +16545,7 @@ function countAdded(git2, cwd, path) {
16239
16545
  function newEntriesSinceHead(git2, cwd, repoRelEntries, absEntries) {
16240
16546
  let lines;
16241
16547
  try {
16242
- lines = readFileSync6(absEntries, "utf8").split("\n").filter(Boolean);
16548
+ lines = readFileSync9(absEntries, "utf8").split("\n").filter(Boolean);
16243
16549
  } catch {
16244
16550
  return [];
16245
16551
  }
@@ -16316,8 +16622,8 @@ function runSessionEnd(opts = {}) {
16316
16622
  systemMessage: `vfkb: ${added2} new brain entr${added2 === 1 ? "y" : "ies"} on \`${branch}\` left uncommitted \u2014 branch + commit to preserve continuity (vfkb never auto-commits the default branch).`
16317
16623
  };
16318
16624
  }
16319
- const absBrain = isAbsolute(dataDir2) ? dataDir2 : join8(cwd, dataDir2);
16320
- const fresh = newEntriesSinceHead(git2, cwd, entries, join8(absBrain, "entries.jsonl"));
16625
+ const absBrain = isAbsolute(dataDir2) ? dataDir2 : join11(cwd, dataDir2);
16626
+ const fresh = newEntriesSinceHead(git2, cwd, entries, join11(absBrain, "entries.jsonl"));
16321
16627
  let autoHandoff = false;
16322
16628
  if (fresh.length > 0 && !fresh.some(isHandoff2)) {
16323
16629
  try {
@@ -16337,46 +16643,8 @@ function runSessionEnd(opts = {}) {
16337
16643
  }
16338
16644
 
16339
16645
  // src/init.ts
16340
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
16341
- import { basename as basename2, join as join10 } from "node:path";
16342
-
16343
- // src/manifest.ts
16344
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
16345
- import { dirname as dirname3, join as join9 } from "node:path";
16346
-
16347
- // src/version.ts
16348
- var SCHEMA_VERSION = 1;
16349
- var ENGINE_VERSION = true ? "0.2.3" : ownPackageVersion();
16350
- var ENGINE_COMMIT = true ? "21adfd7" : "dev";
16351
-
16352
- // src/manifest.ts
16353
- function manifestPath(brainDir2) {
16354
- return join9(brainDir2, "manifest.json");
16355
- }
16356
- function currentManifest() {
16357
- return { schema_version: SCHEMA_VERSION, engine_version: ENGINE_VERSION, engine_commit: ENGINE_COMMIT };
16358
- }
16359
- function readManifest(brainDir2) {
16360
- const p = manifestPath(brainDir2);
16361
- if (!existsSync5(p)) return void 0;
16362
- try {
16363
- return JSON.parse(readFileSync7(p, "utf8"));
16364
- } catch {
16365
- return void 0;
16366
- }
16367
- }
16368
- function writeManifest(brainDir2) {
16369
- const p = manifestPath(brainDir2);
16370
- const existed = existsSync5(p);
16371
- const cur = readManifest(brainDir2);
16372
- const next = currentManifest();
16373
- if (cur && JSON.stringify(cur) === JSON.stringify(next)) return "skipped";
16374
- mkdirSync5(dirname3(p), { recursive: true });
16375
- writeFileSync3(p, JSON.stringify(next, null, 2) + "\n");
16376
- return existed ? "updated" : "created";
16377
- }
16378
-
16379
- // src/init.ts
16646
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
16647
+ import { basename as basename3, join as join12 } from "node:path";
16380
16648
  var AGENTS_MARKER = "<!-- vfkb:how-we-track-work -->";
16381
16649
  var BOOTSTRAP_REL = ".vfkb/bin/bootstrap.mjs";
16382
16650
  function mcpConfig(project) {
@@ -16461,48 +16729,48 @@ If it is unset, a session-start banner tells you; run \`vfkb doctor\` to check.
16461
16729
  `;
16462
16730
  }
16463
16731
  function readJson(path) {
16464
- if (!existsSync6(path)) return void 0;
16732
+ if (!existsSync8(path)) return void 0;
16465
16733
  try {
16466
- return JSON.parse(readFileSync8(path, "utf8"));
16734
+ return JSON.parse(readFileSync10(path, "utf8"));
16467
16735
  } catch {
16468
16736
  return void 0;
16469
16737
  }
16470
16738
  }
16471
16739
  function writeJson(path, value) {
16472
- writeFileSync4(path, JSON.stringify(value, null, 2) + "\n");
16740
+ writeFileSync5(path, JSON.stringify(value, null, 2) + "\n");
16473
16741
  }
16474
16742
  function eventHasVfkb(arr2) {
16475
16743
  return JSON.stringify(arr2 ?? "").includes(BOOTSTRAP_REL);
16476
16744
  }
16477
16745
  function initProject(root, opts = {}) {
16478
- const project = opts.project || basename2(root) || "project";
16746
+ const project = opts.project || basename3(root) || "project";
16479
16747
  const changes = [];
16480
- const brainDir2 = join10(root, ".vfkb");
16481
- const entries = join10(brainDir2, "entries.jsonl");
16482
- if (!existsSync6(entries)) {
16483
- mkdirSync6(brainDir2, { recursive: true });
16484
- writeFileSync4(entries, "");
16748
+ const brainDir2 = join12(root, ".vfkb");
16749
+ const entries = join12(brainDir2, "entries.jsonl");
16750
+ if (!existsSync8(entries)) {
16751
+ mkdirSync7(brainDir2, { recursive: true });
16752
+ writeFileSync5(entries, "");
16485
16753
  changes.push({ path: ".vfkb/entries.jsonl", action: "created" });
16486
16754
  } else {
16487
16755
  changes.push({ path: ".vfkb/entries.jsonl", action: "skipped" });
16488
16756
  }
16489
16757
  changes.push({ path: ".vfkb/manifest.json", action: writeManifest(brainDir2) });
16490
16758
  {
16491
- const binDir = join10(brainDir2, "bin");
16492
- const path = join10(binDir, "bootstrap.mjs");
16493
- const existed = existsSync6(path);
16494
- const same = existed && readFileSync8(path, "utf8") === BOOTSTRAP_SRC;
16759
+ const binDir = join12(brainDir2, "bin");
16760
+ const path = join12(binDir, "bootstrap.mjs");
16761
+ const existed = existsSync8(path);
16762
+ const same = existed && readFileSync10(path, "utf8") === BOOTSTRAP_SRC;
16495
16763
  if (same) {
16496
16764
  changes.push({ path: BOOTSTRAP_REL, action: "skipped" });
16497
16765
  } else {
16498
- mkdirSync6(binDir, { recursive: true });
16499
- writeFileSync4(path, BOOTSTRAP_SRC);
16766
+ mkdirSync7(binDir, { recursive: true });
16767
+ writeFileSync5(path, BOOTSTRAP_SRC);
16500
16768
  changes.push({ path: BOOTSTRAP_REL, action: existed ? "updated" : "created" });
16501
16769
  }
16502
16770
  }
16503
16771
  {
16504
- const path = join10(root, ".mcp.json");
16505
- const existed = existsSync6(path);
16772
+ const path = join12(root, ".mcp.json");
16773
+ const existed = existsSync8(path);
16506
16774
  const cfg = readJson(path) ?? {};
16507
16775
  cfg.mcpServers = cfg.mcpServers ?? {};
16508
16776
  const desired = mcpConfig(project);
@@ -16516,9 +16784,9 @@ function initProject(root, opts = {}) {
16516
16784
  }
16517
16785
  }
16518
16786
  {
16519
- const dir = join10(root, ".claude");
16520
- const path = join10(dir, "settings.json");
16521
- const existed = existsSync6(path);
16787
+ const dir = join12(root, ".claude");
16788
+ const path = join12(dir, "settings.json");
16789
+ const existed = existsSync8(path);
16522
16790
  const cfg = readJson(path) ?? {};
16523
16791
  cfg.hooks = cfg.hooks ?? {};
16524
16792
  const want = settingsHooks(project);
@@ -16533,7 +16801,7 @@ function initProject(root, opts = {}) {
16533
16801
  touched = true;
16534
16802
  }
16535
16803
  if (touched) {
16536
- mkdirSync6(dir, { recursive: true });
16804
+ mkdirSync7(dir, { recursive: true });
16537
16805
  writeJson(path, cfg);
16538
16806
  changes.push({ path: ".claude/settings.json", action: existed ? "updated" : "created" });
16539
16807
  } else {
@@ -16541,10 +16809,10 @@ function initProject(root, opts = {}) {
16541
16809
  }
16542
16810
  }
16543
16811
  {
16544
- const path = join10(root, ".gitignore");
16545
- const lines = [".vfkb/index-meta.json", ".vfkb/.sessions/", ".vfkb/.signals/"];
16546
- const existed = existsSync6(path);
16547
- const cur = existed ? readFileSync8(path, "utf8") : "";
16812
+ const path = join12(root, ".gitignore");
16813
+ const lines = [".vfkb/index-meta.json", ".vfkb/.sessions/", ".vfkb/.signals/", ".vfkb/.journal/"];
16814
+ const existed = existsSync8(path);
16815
+ const cur = existed ? readFileSync10(path, "utf8") : "";
16548
16816
  const missing = lines.filter((l) => !cur.split(/\r?\n/).includes(l));
16549
16817
  if (missing.length === 0) {
16550
16818
  changes.push({ path: ".gitignore", action: "skipped" });
@@ -16553,15 +16821,15 @@ function initProject(root, opts = {}) {
16553
16821
  const block = `${prefix}${cur ? "\n" : ""}# vfkb \u2014 derived/operational (only .vfkb/entries.jsonl is committed)
16554
16822
  ${missing.join("\n")}
16555
16823
  `;
16556
- writeFileSync4(path, cur + block);
16824
+ writeFileSync5(path, cur + block);
16557
16825
  changes.push({ path: ".gitignore", action: existed ? "updated" : "created" });
16558
16826
  }
16559
16827
  }
16560
16828
  {
16561
- const path = join10(root, ".gitattributes");
16829
+ const path = join12(root, ".gitattributes");
16562
16830
  const line = ".vfkb/entries.jsonl merge=union";
16563
- const existed = existsSync6(path);
16564
- const cur = existed ? readFileSync8(path, "utf8") : "";
16831
+ const existed = existsSync8(path);
16832
+ const cur = existed ? readFileSync10(path, "utf8") : "";
16565
16833
  if (cur.split(/\r?\n/).includes(line)) {
16566
16834
  changes.push({ path: ".gitattributes", action: "skipped" });
16567
16835
  } else {
@@ -16569,19 +16837,19 @@ ${missing.join("\n")}
16569
16837
  const block = `${prefix}${cur ? "\n" : ""}# vfkb \u2014 the append-only brain unions across branches (ADR-0041)
16570
16838
  ${line}
16571
16839
  `;
16572
- writeFileSync4(path, cur + block);
16840
+ writeFileSync5(path, cur + block);
16573
16841
  changes.push({ path: ".gitattributes", action: existed ? "updated" : "created" });
16574
16842
  }
16575
16843
  }
16576
16844
  {
16577
- const path = join10(root, "AGENTS.md");
16578
- const existed = existsSync6(path);
16579
- const cur = existed ? readFileSync8(path, "utf8") : "";
16845
+ const path = join12(root, "AGENTS.md");
16846
+ const existed = existsSync8(path);
16847
+ const cur = existed ? readFileSync10(path, "utf8") : "";
16580
16848
  if (cur.includes(AGENTS_MARKER)) {
16581
16849
  changes.push({ path: "AGENTS.md", action: "skipped" });
16582
16850
  } else {
16583
- const sep = cur && !cur.endsWith("\n") ? "\n\n" : cur ? "\n" : "";
16584
- writeFileSync4(path, cur + sep + agentsSnippet(project));
16851
+ const sep2 = cur && !cur.endsWith("\n") ? "\n\n" : cur ? "\n" : "";
16852
+ writeFileSync5(path, cur + sep2 + agentsSnippet(project));
16585
16853
  changes.push({ path: "AGENTS.md", action: existed ? "updated" : "created" });
16586
16854
  }
16587
16855
  }
@@ -16600,13 +16868,13 @@ function approvalNotice(project) {
16600
16868
  }
16601
16869
 
16602
16870
  // src/doctor.ts
16603
- import { execFileSync as execFileSync4 } from "node:child_process";
16604
- import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "node:fs";
16605
- import { join as join11, dirname as dirname4 } from "node:path";
16871
+ import { execFileSync as execFileSync6 } from "node:child_process";
16872
+ import { existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, realpathSync } from "node:fs";
16873
+ import { join as join13, dirname as dirname5, relative as relative3, resolve as resolve4, isAbsolute as isAbsolute2 } from "node:path";
16606
16874
  function readJson2(path) {
16607
- if (!existsSync7(path)) return void 0;
16875
+ if (!existsSync9(path)) return void 0;
16608
16876
  try {
16609
- return JSON.parse(readFileSync9(path, "utf8"));
16877
+ return JSON.parse(readFileSync11(path, "utf8"));
16610
16878
  } catch {
16611
16879
  return void 0;
16612
16880
  }
@@ -16639,16 +16907,16 @@ function detectPluginWiring(settings, root, pluginsFile) {
16639
16907
  }
16640
16908
  function claudeConfigDir(env) {
16641
16909
  if (env.CLAUDE_CONFIG_DIR) return env.CLAUDE_CONFIG_DIR;
16642
- return env.HOME ? join11(env.HOME, ".claude") : void 0;
16910
+ return env.HOME ? join13(env.HOME, ".claude") : void 0;
16643
16911
  }
16644
16912
  function localHeadSha(cloneDir) {
16645
- const head = readFileMaybe(join11(cloneDir, ".git", "HEAD"));
16913
+ const head = readFileMaybe(join13(cloneDir, ".git", "HEAD"));
16646
16914
  if (!head) return void 0;
16647
16915
  const ref = head.trim().match(/^ref:\s*(\S+)$/)?.[1];
16648
16916
  if (!ref) return /^[0-9a-f]{40}$/.test(head.trim()) ? head.trim() : void 0;
16649
- const loose = readFileMaybe(join11(cloneDir, ".git", ...ref.split("/")));
16917
+ const loose = readFileMaybe(join13(cloneDir, ".git", ...ref.split("/")));
16650
16918
  if (loose) return loose.trim();
16651
- const packed = readFileMaybe(join11(cloneDir, ".git", "packed-refs"));
16919
+ const packed = readFileMaybe(join13(cloneDir, ".git", "packed-refs"));
16652
16920
  for (const line of packed?.split("\n") ?? []) {
16653
16921
  const [sha, name] = line.trim().split(/\s+/);
16654
16922
  if (name === ref) return sha;
@@ -16657,12 +16925,12 @@ function localHeadSha(cloneDir) {
16657
16925
  }
16658
16926
  function readFileMaybe(path) {
16659
16927
  try {
16660
- return readFileSync9(path, "utf8");
16928
+ return readFileSync11(path, "utf8");
16661
16929
  } catch {
16662
16930
  return void 0;
16663
16931
  }
16664
16932
  }
16665
- var realGit2 = (args, cwd) => execFileSync4("git", args, {
16933
+ var realGit2 = (args, cwd) => execFileSync6("git", args, {
16666
16934
  cwd,
16667
16935
  encoding: "utf8",
16668
16936
  timeout: 5e3,
@@ -16671,14 +16939,14 @@ var realGit2 = (args, cwd) => execFileSync4("git", args, {
16671
16939
  });
16672
16940
  function checkCurrency(plugin, opts, configDir) {
16673
16941
  const marketplace = plugin.key.split("@")[1];
16674
- const kmFile = opts.knownMarketplacesFile ?? (configDir ? join11(configDir, "plugins", "known_marketplaces.json") : void 0);
16942
+ const kmFile = opts.knownMarketplacesFile ?? (configDir ? join13(configDir, "plugins", "known_marketplaces.json") : void 0);
16675
16943
  const entry = kmFile ? readJson2(kmFile)?.[marketplace] : void 0;
16676
16944
  const skip = (detail) => ({ status: "skip", detail });
16677
16945
  if (!entry) return skip(`no marketplace "${marketplace}" in ${kmFile ?? "the plugin registry"} \u2014 cannot check currency`);
16678
16946
  if (entry.source?.source !== "github") {
16679
16947
  return skip(`marketplace "${marketplace}" is a ${entry.source?.source ?? "unknown"}-source \u2014 no clone to compare`);
16680
16948
  }
16681
- if (!entry.installLocation || !existsSync7(join11(entry.installLocation, ".git"))) {
16949
+ if (!entry.installLocation || !existsSync9(join13(entry.installLocation, ".git"))) {
16682
16950
  return skip(`marketplace clone not found at ${entry.installLocation ?? "(unset installLocation)"}`);
16683
16951
  }
16684
16952
  const clone2 = entry.installLocation;
@@ -16709,7 +16977,7 @@ var NPM_REGISTRY_URL = "https://registry.npmjs.org/@viloforge%2Fvfkb/latest";
16709
16977
  var NPM_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
16710
16978
  var NPM_CACHE_FILE = "npm-currency-cache.json";
16711
16979
  function npmCacheFilePath(opts) {
16712
- return opts.cacheFile ?? join11(opts.brainDir, ".signals", NPM_CACHE_FILE);
16980
+ return opts.cacheFile ?? join13(opts.brainDir, ".signals", NPM_CACHE_FILE);
16713
16981
  }
16714
16982
  function readNpmCache(path) {
16715
16983
  const raw = readJson2(path);
@@ -16719,8 +16987,8 @@ function readNpmCache(path) {
16719
16987
  }
16720
16988
  function writeNpmCache(path, entry) {
16721
16989
  try {
16722
- mkdirSync7(dirname4(path), { recursive: true });
16723
- writeFileSync5(path, JSON.stringify(entry));
16990
+ mkdirSync8(dirname5(path), { recursive: true });
16991
+ writeFileSync6(path, JSON.stringify(entry));
16724
16992
  } catch {
16725
16993
  }
16726
16994
  }
@@ -16797,21 +17065,64 @@ async function checkNpmCurrency(opts) {
16797
17065
  clearTimeout(timer);
16798
17066
  }
16799
17067
  }
17068
+ var HOOK_SUBCOMMANDS = ["session-start", "pre-tool-use", "post-tool-use", "stop", "session-end"];
17069
+ function isEngineWiring(hookJson) {
17070
+ const text = hookJson.replace(/\\[tnr]/g, " ").replace(/\\(.)/g, "$1");
17071
+ if (/bootstrap\.mjs/.test(text)) return true;
17072
+ return new RegExp(`\\bhook["'\\s]+(${HOOK_SUBCOMMANDS.join("|")})\\b`).test(text);
17073
+ }
17074
+ function engineDrift(manifestCommit, runningCommit) {
17075
+ if (!manifestCommit || manifestCommit === "dev") return false;
17076
+ if (!runningCommit || runningCommit === "dev") return false;
17077
+ return manifestCommit !== runningCommit;
17078
+ }
17079
+ function repoToplevel(root) {
17080
+ try {
17081
+ return execFileSync6("git", ["-C", root, "rev-parse", "--show-toplevel"], {
17082
+ encoding: "utf8",
17083
+ stdio: ["ignore", "pipe", "ignore"]
17084
+ }).trim();
17085
+ } catch {
17086
+ return void 0;
17087
+ }
17088
+ }
17089
+ function isUnder(parent, child) {
17090
+ if (!parent) return false;
17091
+ const real = (p) => {
17092
+ try {
17093
+ return realpathSync(p);
17094
+ } catch {
17095
+ return resolve4(p);
17096
+ }
17097
+ };
17098
+ const rel = relative3(real(parent), real(child));
17099
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
17100
+ }
16800
17101
  function runDoctor(opts) {
16801
17102
  const { root, brainDir: brainDir2, env } = opts;
17103
+ const runningCommit = opts.engineCommit ?? ENGINE_COMMIT;
16802
17104
  const checks = [];
16803
17105
  const add = (name, status, detail) => checks.push({ name, status, detail });
16804
- add("engine", "ok", `version ${ENGINE_VERSION} \xB7 commit ${ENGINE_COMMIT} \xB7 schema v${SCHEMA_VERSION}`);
16805
- const settings = readJson2(join11(root, ".claude", "settings.json"));
17106
+ add("engine", "ok", `version ${ENGINE_VERSION} \xB7 commit ${runningCommit} \xB7 schema v${SCHEMA_VERSION}`);
17107
+ const settings = readJson2(join13(root, ".claude", "settings.json"));
16806
17108
  const configDir = claudeConfigDir(env);
16807
- const pluginsFile = opts.pluginsFile ?? (configDir ? join11(configDir, "plugins", "installed_plugins.json") : void 0);
17109
+ const pluginsFile = opts.pluginsFile ?? (configDir ? join13(configDir, "plugins", "installed_plugins.json") : void 0);
16808
17110
  const plugin = detectPluginWiring(settings, root, pluginsFile);
16809
17111
  const mf = readManifest(brainDir2);
16810
17112
  if (!mf) {
16811
17113
  add(
16812
17114
  "brain manifest",
16813
17115
  "warn",
16814
- plugin ? `no manifest.json in ${brainDir2} \u2014 it will be stamped on the next write` : `no manifest.json in ${brainDir2} \u2014 run \`vfkb init\` (or it will be stamped on next write)`
17116
+ // #188 the ordinary write path (addEntry appendRecord backend.append)
17117
+ // never touches manifest.json. `writeManifest` has exactly two callers:
17118
+ // `vfkb init` (init.ts) and the broadcast heal, which fires only when the
17119
+ // manifest is ABSENT (broadcast.ts). Saying "on the next write" claimed
17120
+ // behaviour the code does not have.
17121
+ // On a PLUGIN-wired repo the message must not prescribe `vfkb init` — that
17122
+ // advice is what scaffolds double wiring (issue #77), and the invariant is
17123
+ // asserted in doctor.test.ts. A plugin-born brain simply has no manifest
17124
+ // until a cross-repo broadcast heals it (vfkb#193).
17125
+ plugin ? `no manifest.json in ${brainDir2} \u2014 plugin-born brains have none until a cross-repo broadcast heals it (vfkb#193); the ordinary write path never creates one` : `no manifest.json in ${brainDir2} \u2014 run \`vfkb init\` to stamp it (the ordinary write path never creates one)`
16815
17126
  );
16816
17127
  } else if (typeof mf.schema_version !== "number") {
16817
17128
  add("brain manifest", "warn", "manifest has no numeric schema_version");
@@ -16820,9 +17131,45 @@ function runDoctor(opts) {
16820
17131
  } else if (mf.schema_version < SCHEMA_VERSION) {
16821
17132
  add("brain\u2194engine compat", "warn", `brain schema v${mf.schema_version} is older than engine v${SCHEMA_VERSION} \u2014 migration may be needed`);
16822
17133
  } else {
16823
- add("brain\u2194engine compat", "ok", `schema v${mf.schema_version} matches`);
16824
- if (mf.engine_commit && ENGINE_COMMIT !== "dev" && mf.engine_commit !== ENGINE_COMMIT) {
16825
- add("engine drift", "warn", `brain last stamped by engine ${mf.engine_commit}, running ${ENGINE_COMMIT} \u2014 possible dual-clone drift`);
17134
+ const sentinel = mf.engine_commit === "dev" ? ` \xB7 stamped by a build that did not know its own commit ("dev") \u2014 provenance unknown (#212)` : "";
17135
+ add("brain\u2194engine compat", "ok", `schema v${mf.schema_version} matches${sentinel}`);
17136
+ if (engineDrift(mf.engine_commit, runningCommit)) {
17137
+ add("engine drift", "warn", `brain last stamped by engine ${mf.engine_commit}, running ${runningCommit} \u2014 possible dual-clone drift`);
17138
+ }
17139
+ }
17140
+ {
17141
+ const js = journalStatus(brainDir2);
17142
+ if (js.suppressedInEntries > 0) {
17143
+ add(
17144
+ "journal",
17145
+ "warn",
17146
+ `${js.suppressedInEntries} suppressed (purged) pair(s) still present in entries.jsonl \u2014 a redaction is half-done: remove the line(s) from entries.jsonl too (ADR-0064 \xA74)`
17147
+ );
17148
+ } else if (js.restorable > 0) {
17149
+ add(
17150
+ "journal",
17151
+ "warn",
17152
+ `${js.restorable} journaled entr${js.restorable === 1 ? "y" : "ies"} missing from entries.jsonl \u2014 the next session-start restores them (or run \`vfkb hook session-start\` after checking why they vanished; recovery runs ONLY there)`
17153
+ );
17154
+ } else {
17155
+ add("journal", "ok", `${js.walLines} line(s) in the uncommitted window, nothing to restore`);
17156
+ }
17157
+ if (existsSync9(join13(brainDir2, ".journal")) && isUnder(repoToplevel(root), join13(brainDir2, ".journal"))) {
17158
+ try {
17159
+ execFileSync6("git", ["-C", root, "check-ignore", "-q", join13(brainDir2, ".journal", "wal.jsonl")], {
17160
+ stdio: "ignore"
17161
+ });
17162
+ } catch {
17163
+ try {
17164
+ execFileSync6("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
17165
+ add(
17166
+ "journal gitignore",
17167
+ "warn",
17168
+ `.vfkb/.journal/ exists but is NOT gitignored \u2014 add '.vfkb/.journal/' to .gitignore before the next brain commit (a committed journal defeats its purpose and the ADR-0064 \xA74 redaction)`
17169
+ );
17170
+ } catch {
17171
+ }
17172
+ }
16826
17173
  }
16827
17174
  }
16828
17175
  const home = env.VFKB_BUNDLE_DIR || env.VFKB_HOME;
@@ -16832,7 +17179,7 @@ function runDoctor(opts) {
16832
17179
  } else {
16833
17180
  add("$VFKB_BUNDLE_DIR", "warn", "unset \u2014 set it once per machine to the vfkb bundles dir (so the wiring resolves the engine)");
16834
17181
  }
16835
- } else if (!existsSync7(join11(home, "vfkb.mjs")) || !existsSync7(join11(home, "vfkb-mcp.mjs"))) {
17182
+ } else if (!existsSync9(join13(home, "vfkb.mjs")) || !existsSync9(join13(home, "vfkb-mcp.mjs"))) {
16836
17183
  add("$VFKB_BUNDLE_DIR", "warn", `set to ${home} but vfkb.mjs / vfkb-mcp.mjs not found there (run \`npm run build:bundles\`)`);
16837
17184
  } else {
16838
17185
  add("$VFKB_BUNDLE_DIR", "ok", home);
@@ -16843,7 +17190,7 @@ function runDoctor(opts) {
16843
17190
  if (env.VFKB_HOME && !env.VFKB_BUNDLE_DIR) {
16844
17191
  add("env (deprecated)", "warn", "VFKB_HOME is a deprecated alias \u2014 rename it to VFKB_BUNDLE_DIR");
16845
17192
  }
16846
- const mcp = readJson2(join11(root, ".mcp.json"));
17193
+ const mcp = readJson2(join13(root, ".mcp.json"));
16847
17194
  const mcpProject = mcp?.mcpServers?.vfkb?.env?.VFKB_PROJECT;
16848
17195
  const mcpPresent = Boolean(mcp?.mcpServers?.vfkb);
16849
17196
  if (plugin && mcpPresent) {
@@ -16857,7 +17204,7 @@ function runDoctor(opts) {
16857
17204
  }
16858
17205
  const hooks = settings?.hooks ?? {};
16859
17206
  const expected = ["SessionStart", "PreToolUse", "Stop", "SessionEnd"];
16860
- const have = expected.filter((e) => JSON.stringify(hooks[e] ?? "").includes("vfkb"));
17207
+ const have = expected.filter((e) => isEngineWiring(JSON.stringify(hooks[e] ?? "")));
16861
17208
  if (plugin && have.length > 0) {
16862
17209
  add(".claude/settings.json", "warn", `vfkb hooks present ALONGSIDE the plugin (double wiring) \u2014 remove them; the plugin's hooks are primary (ADR-0045)`);
16863
17210
  } else if (plugin) {
@@ -16873,7 +17220,7 @@ function runDoctor(opts) {
16873
17220
  if (!plugin && have.length > 0 && hooksBlob.includes("bootstrap.mjs") && !hooksBlob.includes("CLAUDE_PROJECT_DIR")) {
16874
17221
  add("hooks anchor", "warn", "vfkb hooks use a CWD-relative bootstrap path \u2014 they break when the session cd's out of the repo root; re-run `vfkb init` to anchor them to $CLAUDE_PROJECT_DIR (issue #22)");
16875
17222
  }
16876
- if (existsSync7(join11(root, ".vfkb", "bin", "bootstrap.mjs"))) {
17223
+ if (existsSync9(join13(root, ".vfkb", "bin", "bootstrap.mjs"))) {
16877
17224
  add("bootstrap", "ok", ".vfkb/bin/bootstrap.mjs present");
16878
17225
  } else if (!plugin && (mcpPresent || have.length > 0)) {
16879
17226
  add("bootstrap", "warn", "wiring present but .vfkb/bin/bootstrap.mjs is missing \u2014 run `vfkb init`");
@@ -16913,9 +17260,9 @@ function renderDoctor(report) {
16913
17260
  }
16914
17261
 
16915
17262
  // src/import.ts
16916
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
17263
+ import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
16917
17264
  import { homedir as homedir2 } from "node:os";
16918
- import { basename as basename3, extname, join as join12 } from "node:path";
17265
+ import { basename as basename4, extname, join as join14 } from "node:path";
16919
17266
  var MYKB_FILES = {
16920
17267
  "decisions.jsonl": "decision",
16921
17268
  "facts.jsonl": "fact",
@@ -16940,16 +17287,16 @@ function mykbText(type, e) {
16940
17287
  return parts.filter(Boolean).join("\n\n");
16941
17288
  }
16942
17289
  function resolveMykbArea(nameOrDir) {
16943
- if (existsSync8(nameOrDir) && statSync3(nameOrDir).isDirectory()) return nameOrDir;
16944
- return join12(homedir2(), ".mykb", "areas", nameOrDir);
17290
+ if (existsSync10(nameOrDir) && statSync3(nameOrDir).isDirectory()) return nameOrDir;
17291
+ return join14(homedir2(), ".mykb", "areas", nameOrDir);
16945
17292
  }
16946
17293
  function fromMykb(areaDir) {
16947
- if (!existsSync8(areaDir)) throw new Error(`mykb area not found: ${areaDir}`);
17294
+ if (!existsSync10(areaDir)) throw new Error(`mykb area not found: ${areaDir}`);
16948
17295
  const out = [];
16949
17296
  for (const [file2, type] of Object.entries(MYKB_FILES)) {
16950
- const path = join12(areaDir, file2);
16951
- if (!existsSync8(path)) continue;
16952
- for (const line of readFileSync10(path, "utf8").split(/\r?\n/)) {
17297
+ const path = join14(areaDir, file2);
17298
+ if (!existsSync10(path)) continue;
17299
+ for (const line of readFileSync12(path, "utf8").split(/\r?\n/)) {
16953
17300
  const trimmed = line.trim();
16954
17301
  if (!trimmed) continue;
16955
17302
  let e;
@@ -16968,24 +17315,24 @@ function fromMykb(areaDir) {
16968
17315
  }
16969
17316
  function mdTitle(path) {
16970
17317
  try {
16971
- const heading = readFileSync10(path, "utf8").split(/\r?\n/).find((l) => l.startsWith("# "));
17318
+ const heading = readFileSync12(path, "utf8").split(/\r?\n/).find((l) => l.startsWith("# "));
16972
17319
  if (heading) return heading.replace(/^#\s+/, "").trim();
16973
17320
  } catch {
16974
17321
  }
16975
- return basename3(path, extname(path));
17322
+ return basename4(path, extname(path));
16976
17323
  }
16977
17324
  function fromAdr(dir = "docs/adr") {
16978
- if (!existsSync8(dir)) throw new Error(`ADR dir not found: ${dir}`);
17325
+ if (!existsSync10(dir)) throw new Error(`ADR dir not found: ${dir}`);
16979
17326
  const out = [];
16980
17327
  for (const file2 of readdirSync3(dir).sort()) {
16981
17328
  if (extname(file2) !== ".md" || /readme/i.test(file2)) continue;
16982
- const rel = join12(dir, file2);
17329
+ const rel = join14(dir, file2);
16983
17330
  out.push(stamp("link", `${mdTitle(rel)} \u2192 ${rel}`, ["adr"], false));
16984
17331
  }
16985
17332
  return out;
16986
17333
  }
16987
17334
  function fromMarkdown(file2) {
16988
- if (!existsSync8(file2)) throw new Error(`markdown file not found: ${file2}`);
17335
+ if (!existsSync10(file2)) throw new Error(`markdown file not found: ${file2}`);
16989
17336
  return [stamp("link", `${mdTitle(file2)} \u2192 ${file2}`, ["doc"], false)];
16990
17337
  }
16991
17338
 
@@ -17064,26 +17411,26 @@ var ENTRY_TYPES = ["fact", "decision", "gotcha", "pattern", "link"];
17064
17411
  var DECISION_STATUSES = ["proposed", "accepted", "deprecated", "superseded"];
17065
17412
 
17066
17413
  // src/cli.ts
17067
- import { readFileSync as readFileSync11 } from "node:fs";
17414
+ import { readFileSync as readFileSync13 } from "node:fs";
17068
17415
  import { fileURLToPath } from "node:url";
17069
- import { dirname as dirname5, join as join13 } from "node:path";
17416
+ import { dirname as dirname6, join as join15 } from "node:path";
17070
17417
  function packageVersion() {
17071
17418
  try {
17072
- const here = dirname5(fileURLToPath(import.meta.url));
17073
- const pkg = JSON.parse(readFileSync11(join13(here, "..", "package.json"), "utf8"));
17419
+ const here = dirname6(fileURLToPath(import.meta.url));
17420
+ const pkg = JSON.parse(readFileSync13(join15(here, "..", "package.json"), "utf8"));
17074
17421
  return pkg.version || ENGINE_VERSION;
17075
17422
  } catch {
17076
17423
  return ENGINE_VERSION;
17077
17424
  }
17078
17425
  }
17079
17426
  function readStdin() {
17080
- return new Promise((resolve3) => {
17427
+ return new Promise((resolve5) => {
17081
17428
  let data = "";
17082
- if (process.stdin.isTTY) return resolve3("");
17429
+ if (process.stdin.isTTY) return resolve5("");
17083
17430
  process.stdin.setEncoding("utf8");
17084
17431
  process.stdin.on("data", (c) => data += c);
17085
- process.stdin.on("end", () => resolve3(data));
17086
- setTimeout(() => resolve3(data), 2e3).unref?.();
17432
+ process.stdin.on("end", () => resolve5(data));
17433
+ setTimeout(() => resolve5(data), 2e3).unref?.();
17087
17434
  });
17088
17435
  }
17089
17436
  function flag(args, name) {
@@ -17111,7 +17458,7 @@ ${USAGE}`);
17111
17458
  throw err;
17112
17459
  }
17113
17460
  }
17114
- var USAGE = "usage: vfkb <add|list|search|query|map|context|context init|resume|resume-note|curate|distill|save|export|import|init|doctor|supersede|context-block|context-block-naive|--version|hook session-start|hook pre-tool-use|hook post-tool-use|hook stop|hook session-end>\n";
17461
+ var USAGE = "usage: vfkb <add|broadcast|journal purge|list|search|query|map|context|context init|resume|resume-note|curate|distill|save|export|import|init|doctor|supersede|context-block|context-block-naive|--version|hook session-start|hook pre-tool-use|hook post-tool-use|hook stop|hook session-end>\n";
17115
17462
  async function dispatch() {
17116
17463
  const [cmd, sub, ...rest] = process.argv.slice(2);
17117
17464
  if (cmd === "--help" || cmd === "-h" || cmd === "help") {
@@ -17160,6 +17507,43 @@ async function dispatch() {
17160
17507
  }
17161
17508
  return;
17162
17509
  }
17510
+ if (cmd === "broadcast") {
17511
+ const p = parseArgs("broadcast", argsOf(sub, rest), {
17512
+ to: "value",
17513
+ op: "value",
17514
+ tag: "value"
17515
+ });
17516
+ const text = p.positionals.join(" ").trim();
17517
+ const targets = flagList(p, "to") ?? [];
17518
+ if (!text || targets.length === 0) {
17519
+ throw new UsageError('usage: vfkb broadcast "<text>" --to <dir>[,<dir>\u2026] [--op <name>] [--tag a,b]');
17520
+ }
17521
+ try {
17522
+ const results = broadcast(text, targets, {
17523
+ op: flagValue(p, "op"),
17524
+ tags: flagList(p, "tag")
17525
+ });
17526
+ let failed = 0;
17527
+ for (const r of results) {
17528
+ if (r.ok) {
17529
+ process.stdout.write(`written ${r.target} ${r.id} ${r.posture}${r.healed ? " (manifest healed \u2014 brain was wired but manifest-less, vfkb#193)" : ""}
17530
+ `);
17531
+ } else {
17532
+ failed++;
17533
+ process.stdout.write(`REFUSED ${r.target} ${r.reason}
17534
+ `);
17535
+ }
17536
+ }
17537
+ process.stdout.write(`
17538
+ broadcast: ${results.length - failed}/${results.length} written${failed ? ` \u2014 ${failed} refused (partial broadcast, visible by design)` : ""}
17539
+ `);
17540
+ process.exit(failed > 0 ? 1 : 0);
17541
+ } catch (err) {
17542
+ process.stderr.write(`error: ${err.message}
17543
+ `);
17544
+ process.exit(1);
17545
+ }
17546
+ }
17163
17547
  if (cmd === "export") {
17164
17548
  const p = parseArgs("export", rest, { out: "value" });
17165
17549
  if (p.positionals.length > 0) {
@@ -17465,7 +17849,18 @@ imported ${results.length} entr${results.length === 1 ? "y" : "ies"} (role=impor
17465
17849
  const project = defaultProject();
17466
17850
  const lim = flag(rest, "limit");
17467
17851
  const session = SessionState.load(effectiveSessionId(payloadId));
17468
- const additionalContext = rest.includes("--naive") ? renderNaiveDump(project, void 0, lim ? Number(lim) : void 0) : renderResume(project, session);
17852
+ let restoreNote = "";
17853
+ try {
17854
+ const rec = withExclusive(() => recoverFromJournal(brainDir()));
17855
+ if (rec.restored > 0) {
17856
+ writeMeta();
17857
+ restoreNote = `\u26A0 vfkb restored ${rec.restored} journaled entr${rec.restored === 1 ? "y" : "ies"} lost from entries.jsonl \u2014 likely a destructive git operation on uncommitted brain state (ADR-0064). Verify with kb_list and commit the brain on your next topic branch.
17858
+
17859
+ `;
17860
+ }
17861
+ } catch {
17862
+ }
17863
+ const additionalContext = restoreNote + (rest.includes("--naive") ? renderNaiveDump(project, void 0, lim ? Number(lim) : void 0) : renderResume(project, session));
17469
17864
  session.save();
17470
17865
  process.stdout.write(
17471
17866
  JSON.stringify({
@@ -17582,6 +17977,23 @@ imported ${results.length} entr${results.length === 1 ? "y" : "ies"} (role=impor
17582
17977
  process.stdout.write((r.committed ? "committed: " : "no-op: ") + r.message + "\n");
17583
17978
  return;
17584
17979
  }
17980
+ if (cmd === "journal") {
17981
+ if (sub !== "purge") {
17982
+ throw new UsageError("usage: vfkb journal purge (--id <id> | --all)");
17983
+ }
17984
+ const p = parseArgs("journal purge", rest, { id: "value", all: "boolean" });
17985
+ const id = flagValue(p, "id");
17986
+ const all = p.flags.has("all");
17987
+ if (!!id === all) {
17988
+ throw new UsageError("usage: vfkb journal purge (--id <id> | --all)");
17989
+ }
17990
+ const r = withExclusive(() => purgeJournal(brainDir(), { id, all }));
17991
+ process.stdout.write(
17992
+ r.purged > 0 ? `purged ${r.purged} journal line(s); pair(s) suppressed \u2014 recovery will never restore them (ADR-0064 \xA74). Remember: a redaction of entries.jsonl is complete only with this purge.
17993
+ ` : "no matching journal lines\n"
17994
+ );
17995
+ return;
17996
+ }
17585
17997
  process.stderr.write(USAGE);
17586
17998
  process.exit(1);
17587
17999
  }