@viloforge/vfkb 0.4.0 → 0.5.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/broadcast.js +19 -8
- package/dist/bundles/vfkb-mcp.mjs +15 -1
- package/dist/bundles/vfkb.mjs +223 -112
- package/dist/cli.js +43 -4
- package/dist/docs-committed-brain-files.test.js +416 -0
- package/dist/doctor.js +104 -11
- package/dist/engine.js +52 -1
- package/dist/hook-stdin-failopen.test.js +217 -0
- package/dist/init.js +47 -6
- package/dist/init.test.js +70 -1
- package/dist/manifest.js +40 -4
- package/package.json +1 -1
package/dist/bundles/vfkb.mjs
CHANGED
|
@@ -7,6 +7,9 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/engine.ts
|
|
9
9
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
10
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
11
|
+
import { dirname as dirname3, join as join6, resolve as resolvePath } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
10
13
|
|
|
11
14
|
// src/storage.ts
|
|
12
15
|
import { basename, dirname as dirname2, join as join4, resolve } from "node:path";
|
|
@@ -15496,6 +15499,16 @@ function latestCrossRepo(all = readAll(), today = nowIso().slice(0, 10), superse
|
|
|
15496
15499
|
}
|
|
15497
15500
|
return latest;
|
|
15498
15501
|
}
|
|
15502
|
+
var CLI_FACES = ["vfkb.mjs", "cli.js", "cli.mjs"];
|
|
15503
|
+
function resolveCliFace(engineDir) {
|
|
15504
|
+
return CLI_FACES.map((f) => join6(engineDir, f)).find((p) => existsSync4(p));
|
|
15505
|
+
}
|
|
15506
|
+
function renderCaptureFallback(engineDir = dirname3(fileURLToPath(import.meta.url))) {
|
|
15507
|
+
const cli = resolveCliFace(engineDir);
|
|
15508
|
+
const target = cli ? `node "${cli}"` : `node <vfkb CLI not found beside the engine at ${engineDir} \u2014 locate vfkb.mjs or cli.js>`;
|
|
15509
|
+
return `capture fallback: if the kb_* tools error or disappear, the CLI still writes (same engine, separate process; journaled per ADR-0064) \u2014
|
|
15510
|
+
VFKB_DATA_DIR="${resolvePath(brainDir())}" ${target} add <type> "\u2026" [--tags a,b] [--why "\u2026"]`;
|
|
15511
|
+
}
|
|
15499
15512
|
function renderContextBundle(project = defaultProject(), budget = SESSION_BUDGET_CHARS) {
|
|
15500
15513
|
const all = readAll();
|
|
15501
15514
|
const today = nowIso().slice(0, 10);
|
|
@@ -15534,6 +15547,7 @@ function renderContextBundle(project = defaultProject(), budget = SESSION_BUDGET
|
|
|
15534
15547
|
`;
|
|
15535
15548
|
}
|
|
15536
15549
|
body += renderContextMap() + "\n\n";
|
|
15550
|
+
body += renderCaptureFallback() + "\n\n";
|
|
15537
15551
|
const kept = [];
|
|
15538
15552
|
let keptLen = 0;
|
|
15539
15553
|
let dropped = 0;
|
|
@@ -15741,7 +15755,7 @@ ${bundle}`;
|
|
|
15741
15755
|
|
|
15742
15756
|
// src/export.ts
|
|
15743
15757
|
import {
|
|
15744
|
-
existsSync as
|
|
15758
|
+
existsSync as existsSync5,
|
|
15745
15759
|
mkdirSync as mkdirSync5,
|
|
15746
15760
|
readdirSync as readdirSync2,
|
|
15747
15761
|
readFileSync as readFileSync5,
|
|
@@ -15749,7 +15763,7 @@ import {
|
|
|
15749
15763
|
statSync as statSync2,
|
|
15750
15764
|
writeFileSync as writeFileSync3
|
|
15751
15765
|
} from "node:fs";
|
|
15752
|
-
import { dirname as
|
|
15766
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
15753
15767
|
var GENERATED_MARKER = "generated by vfkb export";
|
|
15754
15768
|
var MARKER_LINE_RE = /^<!-- generated by vfkb export /m;
|
|
15755
15769
|
var EXPORT_BUDGET_CHARS = 4e4;
|
|
@@ -15798,7 +15812,7 @@ function listFiles(dir) {
|
|
|
15798
15812
|
const out = [];
|
|
15799
15813
|
const walk = (d) => {
|
|
15800
15814
|
for (const name of readdirSync2(d)) {
|
|
15801
|
-
const p =
|
|
15815
|
+
const p = join7(d, name);
|
|
15802
15816
|
if (statSync2(p).isDirectory()) walk(p);
|
|
15803
15817
|
else out.push(p);
|
|
15804
15818
|
}
|
|
@@ -15815,7 +15829,7 @@ function isGenerated(path) {
|
|
|
15815
15829
|
}
|
|
15816
15830
|
}
|
|
15817
15831
|
function prepareTree(dir) {
|
|
15818
|
-
if (!
|
|
15832
|
+
if (!existsSync5(dir)) {
|
|
15819
15833
|
mkdirSync5(dir, { recursive: true });
|
|
15820
15834
|
return;
|
|
15821
15835
|
}
|
|
@@ -15831,7 +15845,7 @@ function prepareTree(dir) {
|
|
|
15831
15845
|
const dirs = [];
|
|
15832
15846
|
const collect = (d) => {
|
|
15833
15847
|
for (const name of readdirSync2(d)) {
|
|
15834
|
-
const p =
|
|
15848
|
+
const p = join7(d, name);
|
|
15835
15849
|
if (statSync2(p).isDirectory()) {
|
|
15836
15850
|
collect(p);
|
|
15837
15851
|
dirs.push(p);
|
|
@@ -15847,8 +15861,8 @@ function exportAgentsMd(opts = {}) {
|
|
|
15847
15861
|
const { superseded, asOf, eligible } = projectionInputs();
|
|
15848
15862
|
const project = opts.project ?? defaultProject();
|
|
15849
15863
|
const budget = opts.budget ?? EXPORT_BUDGET_CHARS;
|
|
15850
|
-
const out = opts.out ??
|
|
15851
|
-
if (
|
|
15864
|
+
const out = opts.out ?? join7(process.cwd(), "AGENTS.md");
|
|
15865
|
+
if (existsSync5(out) && !MARKER_LINE_RE.test(readFileSync5(out, "utf8"))) {
|
|
15852
15866
|
throw new Error(
|
|
15853
15867
|
`vfkb export: refusing to overwrite ${out} \u2014 it is not a previous export (no generated marker)`
|
|
15854
15868
|
);
|
|
@@ -15907,7 +15921,7 @@ ${spine.trim()}
|
|
|
15907
15921
|
}
|
|
15908
15922
|
if (dropped > 0) body += `<!-- ${dropped} entries omitted for the ${budget}-char budget -->
|
|
15909
15923
|
`;
|
|
15910
|
-
mkdirSync5(
|
|
15924
|
+
mkdirSync5(dirname4(out), { recursive: true });
|
|
15911
15925
|
writeFileSync3(out, head + body);
|
|
15912
15926
|
return { path: out };
|
|
15913
15927
|
}
|
|
@@ -16005,13 +16019,13 @@ function deriveLog(eligibleIds, records, live) {
|
|
|
16005
16019
|
function exportOkf(opts = {}) {
|
|
16006
16020
|
const { superseded, asOf, eligible } = projectionInputs();
|
|
16007
16021
|
const project = opts.project ?? defaultProject();
|
|
16008
|
-
const dir = opts.out ??
|
|
16022
|
+
const dir = opts.out ?? join7(process.cwd(), ".okf");
|
|
16009
16023
|
prepareTree(dir);
|
|
16010
16024
|
const marker = `<!-- ${GENERATED_MARKER} okf; regenerate with \`vfkb export okf\`, do not hand-edit (as-of ${asOf}) -->`;
|
|
16011
16025
|
const files = [];
|
|
16012
16026
|
const write = (rel, content) => {
|
|
16013
|
-
const p =
|
|
16014
|
-
mkdirSync5(
|
|
16027
|
+
const p = join7(dir, rel);
|
|
16028
|
+
mkdirSync5(dirname4(p), { recursive: true });
|
|
16015
16029
|
writeFileSync3(p, content);
|
|
16016
16030
|
files.push(rel);
|
|
16017
16031
|
};
|
|
@@ -16068,41 +16082,57 @@ function runExport(target, opts = {}) {
|
|
|
16068
16082
|
|
|
16069
16083
|
// src/broadcast.ts
|
|
16070
16084
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
16071
|
-
import { existsSync as
|
|
16072
|
-
import { basename as basename2, join as
|
|
16085
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
16086
|
+
import { basename as basename2, join as join9, resolve as resolve2 } from "node:path";
|
|
16073
16087
|
|
|
16074
16088
|
// src/manifest.ts
|
|
16075
|
-
import { existsSync as
|
|
16076
|
-
import { dirname as
|
|
16089
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
16090
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
16077
16091
|
|
|
16078
16092
|
// src/version.ts
|
|
16079
16093
|
var SCHEMA_VERSION = 1;
|
|
16080
|
-
var ENGINE_VERSION = true ? "0.
|
|
16081
|
-
var ENGINE_COMMIT = true ? "
|
|
16094
|
+
var ENGINE_VERSION = true ? "0.5.0" : ownPackageVersion();
|
|
16095
|
+
var ENGINE_COMMIT = true ? "aeaa5fa" : "dev";
|
|
16082
16096
|
|
|
16083
16097
|
// src/manifest.ts
|
|
16084
16098
|
function manifestPath(brainDir2) {
|
|
16085
|
-
return
|
|
16099
|
+
return join8(brainDir2, "manifest.json");
|
|
16100
|
+
}
|
|
16101
|
+
function isUnknownCommit(commit) {
|
|
16102
|
+
return !commit || commit === "dev";
|
|
16086
16103
|
}
|
|
16087
|
-
function currentManifest() {
|
|
16088
|
-
return {
|
|
16104
|
+
function currentManifest(opts = {}) {
|
|
16105
|
+
return {
|
|
16106
|
+
schema_version: SCHEMA_VERSION,
|
|
16107
|
+
engine_version: opts.engineVersion ?? ENGINE_VERSION,
|
|
16108
|
+
engine_commit: opts.engineCommit ?? ENGINE_COMMIT
|
|
16109
|
+
};
|
|
16089
16110
|
}
|
|
16090
16111
|
function readManifest(brainDir2) {
|
|
16091
16112
|
const p = manifestPath(brainDir2);
|
|
16092
|
-
if (!
|
|
16113
|
+
if (!existsSync6(p)) return void 0;
|
|
16093
16114
|
try {
|
|
16094
16115
|
return JSON.parse(readFileSync6(p, "utf8"));
|
|
16095
16116
|
} catch {
|
|
16096
16117
|
return void 0;
|
|
16097
16118
|
}
|
|
16098
16119
|
}
|
|
16099
|
-
function
|
|
16120
|
+
function manifestNeedsStamp(brainDir2, engineCommit = ENGINE_COMMIT) {
|
|
16121
|
+
const cur = readManifest(brainDir2);
|
|
16122
|
+
if (!cur) return true;
|
|
16123
|
+
return isUnknownCommit(cur.engine_commit) && !isUnknownCommit(engineCommit);
|
|
16124
|
+
}
|
|
16125
|
+
function writeManifest(brainDir2, opts = {}) {
|
|
16100
16126
|
const p = manifestPath(brainDir2);
|
|
16101
|
-
const existed =
|
|
16127
|
+
const existed = existsSync6(p);
|
|
16102
16128
|
const cur = readManifest(brainDir2);
|
|
16103
|
-
const next = currentManifest();
|
|
16129
|
+
const next = currentManifest(opts);
|
|
16130
|
+
if (cur && isUnknownCommit(next.engine_commit) && !isUnknownCommit(cur.engine_commit)) {
|
|
16131
|
+
next.engine_commit = cur.engine_commit;
|
|
16132
|
+
next.engine_version = cur.engine_version;
|
|
16133
|
+
}
|
|
16104
16134
|
if (cur && JSON.stringify(cur) === JSON.stringify(next)) return "skipped";
|
|
16105
|
-
mkdirSync6(
|
|
16135
|
+
mkdirSync6(dirname5(p), { recursive: true });
|
|
16106
16136
|
writeFileSync4(p, JSON.stringify(next, null, 2) + "\n");
|
|
16107
16137
|
return existed ? "updated" : "created";
|
|
16108
16138
|
}
|
|
@@ -16111,7 +16141,7 @@ function writeManifest(brainDir2) {
|
|
|
16111
16141
|
var FORBIDDEN_TAGS = /* @__PURE__ */ new Set(["handoff", "next"]);
|
|
16112
16142
|
function targetBrainDir(target) {
|
|
16113
16143
|
const abs = resolve2(target);
|
|
16114
|
-
return basename2(abs) === ".vfkb" ? abs :
|
|
16144
|
+
return basename2(abs) === ".vfkb" ? abs : join9(abs, ".vfkb");
|
|
16115
16145
|
}
|
|
16116
16146
|
function gitPosture(repoDir) {
|
|
16117
16147
|
const git2 = (...a) => execFileSync2("git", ["-C", repoDir, ...a], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -16152,20 +16182,24 @@ function broadcast(text, targets, opts = {}) {
|
|
|
16152
16182
|
continue;
|
|
16153
16183
|
}
|
|
16154
16184
|
const repoDir = resolve2(brain, "..");
|
|
16155
|
-
const manifestPath2 =
|
|
16185
|
+
const manifestPath2 = join9(brain, "manifest.json");
|
|
16156
16186
|
let healed = false;
|
|
16157
|
-
|
|
16158
|
-
|
|
16187
|
+
let upgraded = false;
|
|
16188
|
+
const needsStamp = manifestNeedsStamp(brain, opts.engineCommit ?? ENGINE_COMMIT);
|
|
16189
|
+
if (needsStamp) {
|
|
16190
|
+
if (!existsSync7(join9(brain, "entries.jsonl"))) {
|
|
16159
16191
|
results.push({ target, ok: false, reason: `no brain (no entries.jsonl in ${brain}) \u2014 never bootstrap a wire-less brain (ADR-0063 \xA73)` });
|
|
16160
16192
|
continue;
|
|
16161
16193
|
}
|
|
16194
|
+
const absent = !existsSync7(manifestPath2);
|
|
16162
16195
|
try {
|
|
16163
|
-
writeManifest(brain);
|
|
16196
|
+
writeManifest(brain, { engineCommit: opts.engineCommit, engineVersion: opts.engineVersion });
|
|
16164
16197
|
} catch (err) {
|
|
16165
|
-
results.push({ target, ok: false, reason: `manifest heal failed: ${err.message}` });
|
|
16198
|
+
results.push({ target, ok: false, reason: `manifest ${absent ? "heal" : "provenance upgrade"} failed: ${err.message}` });
|
|
16166
16199
|
continue;
|
|
16167
16200
|
}
|
|
16168
|
-
healed = true;
|
|
16201
|
+
if (absent) healed = true;
|
|
16202
|
+
else upgraded = true;
|
|
16169
16203
|
}
|
|
16170
16204
|
let schema;
|
|
16171
16205
|
try {
|
|
@@ -16183,9 +16217,9 @@ function broadcast(text, targets, opts = {}) {
|
|
|
16183
16217
|
process.env.VFKB_DATA_DIR = brain;
|
|
16184
16218
|
const e = addEntry("fact", stamped, { role: "executor", tags: [.../* @__PURE__ */ new Set(["cross-repo", ...extraTags])] });
|
|
16185
16219
|
written.add(brain);
|
|
16186
|
-
results.push({ target, ok: true, id: e.id, posture: gitPosture(repoDir), ...healed ? { healed } : {} });
|
|
16220
|
+
results.push({ target, ok: true, id: e.id, posture: gitPosture(repoDir), ...healed ? { healed } : {}, ...upgraded ? { upgraded } : {} });
|
|
16187
16221
|
} catch (err) {
|
|
16188
|
-
results.push({ target, ok: false, reason: err.message, ...healed ? { healed } : {} });
|
|
16222
|
+
results.push({ target, ok: false, reason: err.message, ...healed ? { healed } : {}, ...upgraded ? { upgraded } : {} });
|
|
16189
16223
|
} finally {
|
|
16190
16224
|
if (prev === void 0) delete process.env.VFKB_DATA_DIR;
|
|
16191
16225
|
else process.env.VFKB_DATA_DIR = prev;
|
|
@@ -16419,7 +16453,7 @@ var GATING_REASON = "vfkb: edit the brain via the engine/CLI/MCP, not by writing
|
|
|
16419
16453
|
// src/stop-reminder.ts
|
|
16420
16454
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
16421
16455
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
16422
|
-
import { join as
|
|
16456
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
16423
16457
|
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.';
|
|
16424
16458
|
var HANDOFF_MIN_ENTRIES = 3;
|
|
16425
16459
|
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.';
|
|
@@ -16448,7 +16482,7 @@ function hasUncommittedWork(cwd = process.cwd(), brain = brainDir()) {
|
|
|
16448
16482
|
});
|
|
16449
16483
|
}
|
|
16450
16484
|
function newBrainEntriesSinceHead(brain = brainDir(), cwd = process.cwd()) {
|
|
16451
|
-
const file2 =
|
|
16485
|
+
const file2 = join10(brain, "entries.jsonl");
|
|
16452
16486
|
let current2;
|
|
16453
16487
|
try {
|
|
16454
16488
|
current2 = readFileSync8(file2, "utf8").split("\n").filter(Boolean);
|
|
@@ -16484,13 +16518,13 @@ function gatherStopContext(cwd = process.cwd(), brain = brainDir()) {
|
|
|
16484
16518
|
|
|
16485
16519
|
// src/git.ts
|
|
16486
16520
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
16487
|
-
import { existsSync as
|
|
16488
|
-
import { join as
|
|
16521
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
16522
|
+
import { join as join11 } from "node:path";
|
|
16489
16523
|
function git(args, cwd) {
|
|
16490
16524
|
return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
16491
16525
|
}
|
|
16492
16526
|
function ensureRepo(brain) {
|
|
16493
|
-
if (!
|
|
16527
|
+
if (!existsSync8(join11(brain, ".git"))) {
|
|
16494
16528
|
git(["init", "-q"], brain);
|
|
16495
16529
|
}
|
|
16496
16530
|
}
|
|
@@ -16518,10 +16552,10 @@ function save(message = "vfkb: update", role = "engine", brain = brainDir()) {
|
|
|
16518
16552
|
// src/session-end.ts
|
|
16519
16553
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
16520
16554
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
16521
|
-
import { join as
|
|
16555
|
+
import { join as join12, isAbsolute } from "node:path";
|
|
16522
16556
|
var realGit = (args, cwd) => execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
16523
16557
|
function brainEntriesRelPath(dataDir2) {
|
|
16524
|
-
return
|
|
16558
|
+
return join12(dataDir2, "entries.jsonl").replace(/\\/g, "/");
|
|
16525
16559
|
}
|
|
16526
16560
|
function tryGit(git2, args, cwd) {
|
|
16527
16561
|
try {
|
|
@@ -16622,8 +16656,8 @@ function runSessionEnd(opts = {}) {
|
|
|
16622
16656
|
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).`
|
|
16623
16657
|
};
|
|
16624
16658
|
}
|
|
16625
|
-
const absBrain = isAbsolute(dataDir2) ? dataDir2 :
|
|
16626
|
-
const fresh = newEntriesSinceHead(git2, cwd, entries,
|
|
16659
|
+
const absBrain = isAbsolute(dataDir2) ? dataDir2 : join12(cwd, dataDir2);
|
|
16660
|
+
const fresh = newEntriesSinceHead(git2, cwd, entries, join12(absBrain, "entries.jsonl"));
|
|
16627
16661
|
let autoHandoff = false;
|
|
16628
16662
|
if (fresh.length > 0 && !fresh.some(isHandoff2)) {
|
|
16629
16663
|
try {
|
|
@@ -16643,8 +16677,8 @@ function runSessionEnd(opts = {}) {
|
|
|
16643
16677
|
}
|
|
16644
16678
|
|
|
16645
16679
|
// src/init.ts
|
|
16646
|
-
import { existsSync as
|
|
16647
|
-
import { basename as basename3, join as
|
|
16680
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
16681
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
16648
16682
|
var AGENTS_MARKER = "<!-- vfkb:how-we-track-work -->";
|
|
16649
16683
|
var BOOTSTRAP_REL = ".vfkb/bin/bootstrap.mjs";
|
|
16650
16684
|
function mcpConfig(project) {
|
|
@@ -16720,8 +16754,9 @@ This repo uses **vfkb** as its knowledge substrate (project \`${project}\`). Kno
|
|
|
16720
16754
|
- **Record knowledge** with the \`mcp__vfkb__kb_add\` tool (or \`node .vfkb/bin/bootstrap.mjs cli add \u2026\`):
|
|
16721
16755
|
\`decision\`, \`fact\`, \`gotcha\`, \`pattern\`, \`link\` \u2014 put a decision's rationale in its text.
|
|
16722
16756
|
**Capture load-bearing decisions immediately \u2014 don't defer.**
|
|
16723
|
-
-
|
|
16724
|
-
|
|
16757
|
+
- Committed: \`.vfkb/entries.jsonl\`, \`.vfkb/manifest.json\` (the ADR-0030 engine stamp \u2014
|
|
16758
|
+
**never gitignore \`manifest.json\`**), and \`.vfkb/bin/\`. Derived/gitignored, all five:
|
|
16759
|
+
\`.vfkb/index-meta.json\`, \`.vfkb/.sessions/\`, \`.vfkb/.signals/\`, \`.vfkb/.journal/\`, \`.vfkb/.lock\`.
|
|
16725
16760
|
|
|
16726
16761
|
Two env vars: **\`VFKB_DATA_DIR\`** = this repo's brain (\`.vfkb\`, set by the wiring) \xB7 **\`VFKB_BUNDLE_DIR\`**
|
|
16727
16762
|
= the shared vfkb engine bundles \u2014 set it once per machine, e.g. \`export VFKB_BUNDLE_DIR=/path/to/vfkb/dist/bundles\`.
|
|
@@ -16729,7 +16764,7 @@ If it is unset, a session-start banner tells you; run \`vfkb doctor\` to check.
|
|
|
16729
16764
|
`;
|
|
16730
16765
|
}
|
|
16731
16766
|
function readJson(path) {
|
|
16732
|
-
if (!
|
|
16767
|
+
if (!existsSync9(path)) return void 0;
|
|
16733
16768
|
try {
|
|
16734
16769
|
return JSON.parse(readFileSync10(path, "utf8"));
|
|
16735
16770
|
} catch {
|
|
@@ -16745,9 +16780,9 @@ function eventHasVfkb(arr2) {
|
|
|
16745
16780
|
function initProject(root, opts = {}) {
|
|
16746
16781
|
const project = opts.project || basename3(root) || "project";
|
|
16747
16782
|
const changes = [];
|
|
16748
|
-
const brainDir2 =
|
|
16749
|
-
const entries =
|
|
16750
|
-
if (!
|
|
16783
|
+
const brainDir2 = join13(root, ".vfkb");
|
|
16784
|
+
const entries = join13(brainDir2, "entries.jsonl");
|
|
16785
|
+
if (!existsSync9(entries)) {
|
|
16751
16786
|
mkdirSync7(brainDir2, { recursive: true });
|
|
16752
16787
|
writeFileSync5(entries, "");
|
|
16753
16788
|
changes.push({ path: ".vfkb/entries.jsonl", action: "created" });
|
|
@@ -16756,9 +16791,9 @@ function initProject(root, opts = {}) {
|
|
|
16756
16791
|
}
|
|
16757
16792
|
changes.push({ path: ".vfkb/manifest.json", action: writeManifest(brainDir2) });
|
|
16758
16793
|
{
|
|
16759
|
-
const binDir =
|
|
16760
|
-
const path =
|
|
16761
|
-
const existed =
|
|
16794
|
+
const binDir = join13(brainDir2, "bin");
|
|
16795
|
+
const path = join13(binDir, "bootstrap.mjs");
|
|
16796
|
+
const existed = existsSync9(path);
|
|
16762
16797
|
const same = existed && readFileSync10(path, "utf8") === BOOTSTRAP_SRC;
|
|
16763
16798
|
if (same) {
|
|
16764
16799
|
changes.push({ path: BOOTSTRAP_REL, action: "skipped" });
|
|
@@ -16769,8 +16804,8 @@ function initProject(root, opts = {}) {
|
|
|
16769
16804
|
}
|
|
16770
16805
|
}
|
|
16771
16806
|
{
|
|
16772
|
-
const path =
|
|
16773
|
-
const existed =
|
|
16807
|
+
const path = join13(root, ".mcp.json");
|
|
16808
|
+
const existed = existsSync9(path);
|
|
16774
16809
|
const cfg = readJson(path) ?? {};
|
|
16775
16810
|
cfg.mcpServers = cfg.mcpServers ?? {};
|
|
16776
16811
|
const desired = mcpConfig(project);
|
|
@@ -16784,9 +16819,9 @@ function initProject(root, opts = {}) {
|
|
|
16784
16819
|
}
|
|
16785
16820
|
}
|
|
16786
16821
|
{
|
|
16787
|
-
const dir =
|
|
16788
|
-
const path =
|
|
16789
|
-
const existed =
|
|
16822
|
+
const dir = join13(root, ".claude");
|
|
16823
|
+
const path = join13(dir, "settings.json");
|
|
16824
|
+
const existed = existsSync9(path);
|
|
16790
16825
|
const cfg = readJson(path) ?? {};
|
|
16791
16826
|
cfg.hooks = cfg.hooks ?? {};
|
|
16792
16827
|
const want = settingsHooks(project);
|
|
@@ -16809,16 +16844,37 @@ function initProject(root, opts = {}) {
|
|
|
16809
16844
|
}
|
|
16810
16845
|
}
|
|
16811
16846
|
{
|
|
16812
|
-
const path =
|
|
16813
|
-
const lines = [
|
|
16814
|
-
|
|
16847
|
+
const path = join13(root, ".gitignore");
|
|
16848
|
+
const lines = [
|
|
16849
|
+
".vfkb/index-meta.json",
|
|
16850
|
+
".vfkb/.sessions/",
|
|
16851
|
+
".vfkb/.signals/",
|
|
16852
|
+
".vfkb/.journal/",
|
|
16853
|
+
".vfkb/.lock"
|
|
16854
|
+
];
|
|
16855
|
+
const HEADER = [
|
|
16856
|
+
"# vfkb \u2014 entries.jsonl + manifest.json (the ADR-0030 engine stamp) are committed;",
|
|
16857
|
+
"# the vfkb paths below are derived/operational and stay out of git"
|
|
16858
|
+
];
|
|
16859
|
+
const STALE_HEADER = /^#\s*vfkb\s*—\s*derived\/operational \(only \.vfkb\/entries\.jsonl is committed\)\s*$/;
|
|
16860
|
+
const existed = existsSync9(path);
|
|
16815
16861
|
const cur = existed ? readFileSync10(path, "utf8") : "";
|
|
16816
|
-
const
|
|
16817
|
-
|
|
16862
|
+
const curLines = cur.split(/\r?\n/);
|
|
16863
|
+
const missing = lines.filter((l) => !curLines.includes(l));
|
|
16864
|
+
const staleAt = curLines.findIndex((l) => STALE_HEADER.test(l));
|
|
16865
|
+
if (missing.length === 0 && staleAt === -1) {
|
|
16818
16866
|
changes.push({ path: ".gitignore", action: "skipped" });
|
|
16867
|
+
} else if (staleAt !== -1) {
|
|
16868
|
+
const out = [...curLines];
|
|
16869
|
+
out.splice(staleAt, 1, ...HEADER);
|
|
16870
|
+
let last = staleAt + HEADER.length - 1;
|
|
16871
|
+
for (let i = last + 1; i < out.length && /^\.vfkb\//.test(out[i].trim()); i++) last = i;
|
|
16872
|
+
out.splice(last + 1, 0, ...missing);
|
|
16873
|
+
writeFileSync5(path, out.join("\n"));
|
|
16874
|
+
changes.push({ path: ".gitignore", action: "updated" });
|
|
16819
16875
|
} else {
|
|
16820
16876
|
const prefix = cur && !cur.endsWith("\n") ? "\n" : "";
|
|
16821
|
-
const block = `${prefix}${cur ? "\n" : ""}
|
|
16877
|
+
const block = `${prefix}${cur ? "\n" : ""}${HEADER.join("\n")}
|
|
16822
16878
|
${missing.join("\n")}
|
|
16823
16879
|
`;
|
|
16824
16880
|
writeFileSync5(path, cur + block);
|
|
@@ -16826,9 +16882,9 @@ ${missing.join("\n")}
|
|
|
16826
16882
|
}
|
|
16827
16883
|
}
|
|
16828
16884
|
{
|
|
16829
|
-
const path =
|
|
16885
|
+
const path = join13(root, ".gitattributes");
|
|
16830
16886
|
const line = ".vfkb/entries.jsonl merge=union";
|
|
16831
|
-
const existed =
|
|
16887
|
+
const existed = existsSync9(path);
|
|
16832
16888
|
const cur = existed ? readFileSync10(path, "utf8") : "";
|
|
16833
16889
|
if (cur.split(/\r?\n/).includes(line)) {
|
|
16834
16890
|
changes.push({ path: ".gitattributes", action: "skipped" });
|
|
@@ -16842,8 +16898,8 @@ ${line}
|
|
|
16842
16898
|
}
|
|
16843
16899
|
}
|
|
16844
16900
|
{
|
|
16845
|
-
const path =
|
|
16846
|
-
const existed =
|
|
16901
|
+
const path = join13(root, "AGENTS.md");
|
|
16902
|
+
const existed = existsSync9(path);
|
|
16847
16903
|
const cur = existed ? readFileSync10(path, "utf8") : "";
|
|
16848
16904
|
if (cur.includes(AGENTS_MARKER)) {
|
|
16849
16905
|
changes.push({ path: "AGENTS.md", action: "skipped" });
|
|
@@ -16869,10 +16925,10 @@ function approvalNotice(project) {
|
|
|
16869
16925
|
|
|
16870
16926
|
// src/doctor.ts
|
|
16871
16927
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
16872
|
-
import { existsSync as
|
|
16873
|
-
import { join as
|
|
16928
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, realpathSync } from "node:fs";
|
|
16929
|
+
import { join as join14, dirname as dirname6, relative as relative3, resolve as resolve4, isAbsolute as isAbsolute2 } from "node:path";
|
|
16874
16930
|
function readJson2(path) {
|
|
16875
|
-
if (!
|
|
16931
|
+
if (!existsSync10(path)) return void 0;
|
|
16876
16932
|
try {
|
|
16877
16933
|
return JSON.parse(readFileSync11(path, "utf8"));
|
|
16878
16934
|
} catch {
|
|
@@ -16907,16 +16963,16 @@ function detectPluginWiring(settings, root, pluginsFile) {
|
|
|
16907
16963
|
}
|
|
16908
16964
|
function claudeConfigDir(env) {
|
|
16909
16965
|
if (env.CLAUDE_CONFIG_DIR) return env.CLAUDE_CONFIG_DIR;
|
|
16910
|
-
return env.HOME ?
|
|
16966
|
+
return env.HOME ? join14(env.HOME, ".claude") : void 0;
|
|
16911
16967
|
}
|
|
16912
16968
|
function localHeadSha(cloneDir) {
|
|
16913
|
-
const head = readFileMaybe(
|
|
16969
|
+
const head = readFileMaybe(join14(cloneDir, ".git", "HEAD"));
|
|
16914
16970
|
if (!head) return void 0;
|
|
16915
16971
|
const ref = head.trim().match(/^ref:\s*(\S+)$/)?.[1];
|
|
16916
16972
|
if (!ref) return /^[0-9a-f]{40}$/.test(head.trim()) ? head.trim() : void 0;
|
|
16917
|
-
const loose = readFileMaybe(
|
|
16973
|
+
const loose = readFileMaybe(join14(cloneDir, ".git", ...ref.split("/")));
|
|
16918
16974
|
if (loose) return loose.trim();
|
|
16919
|
-
const packed = readFileMaybe(
|
|
16975
|
+
const packed = readFileMaybe(join14(cloneDir, ".git", "packed-refs"));
|
|
16920
16976
|
for (const line of packed?.split("\n") ?? []) {
|
|
16921
16977
|
const [sha, name] = line.trim().split(/\s+/);
|
|
16922
16978
|
if (name === ref) return sha;
|
|
@@ -16939,14 +16995,14 @@ var realGit2 = (args, cwd) => execFileSync6("git", args, {
|
|
|
16939
16995
|
});
|
|
16940
16996
|
function checkCurrency(plugin, opts, configDir) {
|
|
16941
16997
|
const marketplace = plugin.key.split("@")[1];
|
|
16942
|
-
const kmFile = opts.knownMarketplacesFile ?? (configDir ?
|
|
16998
|
+
const kmFile = opts.knownMarketplacesFile ?? (configDir ? join14(configDir, "plugins", "known_marketplaces.json") : void 0);
|
|
16943
16999
|
const entry = kmFile ? readJson2(kmFile)?.[marketplace] : void 0;
|
|
16944
17000
|
const skip = (detail) => ({ status: "skip", detail });
|
|
16945
17001
|
if (!entry) return skip(`no marketplace "${marketplace}" in ${kmFile ?? "the plugin registry"} \u2014 cannot check currency`);
|
|
16946
17002
|
if (entry.source?.source !== "github") {
|
|
16947
17003
|
return skip(`marketplace "${marketplace}" is a ${entry.source?.source ?? "unknown"}-source \u2014 no clone to compare`);
|
|
16948
17004
|
}
|
|
16949
|
-
if (!entry.installLocation || !
|
|
17005
|
+
if (!entry.installLocation || !existsSync10(join14(entry.installLocation, ".git"))) {
|
|
16950
17006
|
return skip(`marketplace clone not found at ${entry.installLocation ?? "(unset installLocation)"}`);
|
|
16951
17007
|
}
|
|
16952
17008
|
const clone2 = entry.installLocation;
|
|
@@ -16977,7 +17033,7 @@ var NPM_REGISTRY_URL = "https://registry.npmjs.org/@viloforge%2Fvfkb/latest";
|
|
|
16977
17033
|
var NPM_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
16978
17034
|
var NPM_CACHE_FILE = "npm-currency-cache.json";
|
|
16979
17035
|
function npmCacheFilePath(opts) {
|
|
16980
|
-
return opts.cacheFile ??
|
|
17036
|
+
return opts.cacheFile ?? join14(opts.brainDir, ".signals", NPM_CACHE_FILE);
|
|
16981
17037
|
}
|
|
16982
17038
|
function readNpmCache(path) {
|
|
16983
17039
|
const raw = readJson2(path);
|
|
@@ -16987,7 +17043,7 @@ function readNpmCache(path) {
|
|
|
16987
17043
|
}
|
|
16988
17044
|
function writeNpmCache(path, entry) {
|
|
16989
17045
|
try {
|
|
16990
|
-
mkdirSync8(
|
|
17046
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
16991
17047
|
writeFileSync6(path, JSON.stringify(entry));
|
|
16992
17048
|
} catch {
|
|
16993
17049
|
}
|
|
@@ -17065,21 +17121,64 @@ async function checkNpmCurrency(opts) {
|
|
|
17065
17121
|
clearTimeout(timer);
|
|
17066
17122
|
}
|
|
17067
17123
|
}
|
|
17124
|
+
var HOOK_SUBCOMMANDS = ["session-start", "pre-tool-use", "post-tool-use", "stop", "session-end"];
|
|
17125
|
+
function isEngineWiring(hookJson) {
|
|
17126
|
+
const text = hookJson.replace(/\\[tnr]/g, " ").replace(/\\(.)/g, "$1");
|
|
17127
|
+
if (/bootstrap\.mjs/.test(text)) return true;
|
|
17128
|
+
return new RegExp(`\\bhook["'\\s]+(${HOOK_SUBCOMMANDS.join("|")})\\b`).test(text);
|
|
17129
|
+
}
|
|
17130
|
+
function engineDrift(manifestCommit, runningCommit) {
|
|
17131
|
+
if (!manifestCommit || manifestCommit === "dev") return false;
|
|
17132
|
+
if (!runningCommit || runningCommit === "dev") return false;
|
|
17133
|
+
return manifestCommit !== runningCommit;
|
|
17134
|
+
}
|
|
17135
|
+
function repoToplevel(root) {
|
|
17136
|
+
try {
|
|
17137
|
+
return execFileSync6("git", ["-C", root, "rev-parse", "--show-toplevel"], {
|
|
17138
|
+
encoding: "utf8",
|
|
17139
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
17140
|
+
}).trim();
|
|
17141
|
+
} catch {
|
|
17142
|
+
return void 0;
|
|
17143
|
+
}
|
|
17144
|
+
}
|
|
17145
|
+
function isUnder(parent, child) {
|
|
17146
|
+
if (!parent) return false;
|
|
17147
|
+
const real = (p) => {
|
|
17148
|
+
try {
|
|
17149
|
+
return realpathSync(p);
|
|
17150
|
+
} catch {
|
|
17151
|
+
return resolve4(p);
|
|
17152
|
+
}
|
|
17153
|
+
};
|
|
17154
|
+
const rel = relative3(real(parent), real(child));
|
|
17155
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
17156
|
+
}
|
|
17068
17157
|
function runDoctor(opts) {
|
|
17069
17158
|
const { root, brainDir: brainDir2, env } = opts;
|
|
17159
|
+
const runningCommit = opts.engineCommit ?? ENGINE_COMMIT;
|
|
17070
17160
|
const checks = [];
|
|
17071
17161
|
const add = (name, status, detail) => checks.push({ name, status, detail });
|
|
17072
|
-
add("engine", "ok", `version ${ENGINE_VERSION} \xB7 commit ${
|
|
17073
|
-
const settings = readJson2(
|
|
17162
|
+
add("engine", "ok", `version ${ENGINE_VERSION} \xB7 commit ${runningCommit} \xB7 schema v${SCHEMA_VERSION}`);
|
|
17163
|
+
const settings = readJson2(join14(root, ".claude", "settings.json"));
|
|
17074
17164
|
const configDir = claudeConfigDir(env);
|
|
17075
|
-
const pluginsFile = opts.pluginsFile ?? (configDir ?
|
|
17165
|
+
const pluginsFile = opts.pluginsFile ?? (configDir ? join14(configDir, "plugins", "installed_plugins.json") : void 0);
|
|
17076
17166
|
const plugin = detectPluginWiring(settings, root, pluginsFile);
|
|
17077
17167
|
const mf = readManifest(brainDir2);
|
|
17078
17168
|
if (!mf) {
|
|
17079
17169
|
add(
|
|
17080
17170
|
"brain manifest",
|
|
17081
17171
|
"warn",
|
|
17082
|
-
|
|
17172
|
+
// #188 — the ordinary write path (addEntry → appendRecord → backend.append)
|
|
17173
|
+
// never touches manifest.json. `writeManifest` has exactly two callers:
|
|
17174
|
+
// `vfkb init` (init.ts) and the broadcast heal, which fires only when the
|
|
17175
|
+
// manifest is ABSENT (broadcast.ts). Saying "on the next write" claimed
|
|
17176
|
+
// behaviour the code does not have.
|
|
17177
|
+
// On a PLUGIN-wired repo the message must not prescribe `vfkb init` — that
|
|
17178
|
+
// advice is what scaffolds double wiring (issue #77), and the invariant is
|
|
17179
|
+
// asserted in doctor.test.ts. A plugin-born brain simply has no manifest
|
|
17180
|
+
// until a cross-repo broadcast heals it (vfkb#193).
|
|
17181
|
+
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)`
|
|
17083
17182
|
);
|
|
17084
17183
|
} else if (typeof mf.schema_version !== "number") {
|
|
17085
17184
|
add("brain manifest", "warn", "manifest has no numeric schema_version");
|
|
@@ -17088,9 +17187,10 @@ function runDoctor(opts) {
|
|
|
17088
17187
|
} else if (mf.schema_version < SCHEMA_VERSION) {
|
|
17089
17188
|
add("brain\u2194engine compat", "warn", `brain schema v${mf.schema_version} is older than engine v${SCHEMA_VERSION} \u2014 migration may be needed`);
|
|
17090
17189
|
} else {
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17190
|
+
const sentinel = mf.engine_commit === "dev" ? ` \xB7 stamped by a build that did not know its own commit ("dev") \u2014 provenance unknown (#212)` : "";
|
|
17191
|
+
add("brain\u2194engine compat", "ok", `schema v${mf.schema_version} matches${sentinel}`);
|
|
17192
|
+
if (engineDrift(mf.engine_commit, runningCommit)) {
|
|
17193
|
+
add("engine drift", "warn", `brain last stamped by engine ${mf.engine_commit}, running ${runningCommit} \u2014 possible dual-clone drift`);
|
|
17094
17194
|
}
|
|
17095
17195
|
}
|
|
17096
17196
|
{
|
|
@@ -17110,9 +17210,9 @@ function runDoctor(opts) {
|
|
|
17110
17210
|
} else {
|
|
17111
17211
|
add("journal", "ok", `${js.walLines} line(s) in the uncommitted window, nothing to restore`);
|
|
17112
17212
|
}
|
|
17113
|
-
if (
|
|
17213
|
+
if (existsSync10(join14(brainDir2, ".journal")) && isUnder(repoToplevel(root), join14(brainDir2, ".journal"))) {
|
|
17114
17214
|
try {
|
|
17115
|
-
execFileSync6("git", ["-C", root, "check-ignore", "-q",
|
|
17215
|
+
execFileSync6("git", ["-C", root, "check-ignore", "-q", join14(brainDir2, ".journal", "wal.jsonl")], {
|
|
17116
17216
|
stdio: "ignore"
|
|
17117
17217
|
});
|
|
17118
17218
|
} catch {
|
|
@@ -17135,7 +17235,7 @@ function runDoctor(opts) {
|
|
|
17135
17235
|
} else {
|
|
17136
17236
|
add("$VFKB_BUNDLE_DIR", "warn", "unset \u2014 set it once per machine to the vfkb bundles dir (so the wiring resolves the engine)");
|
|
17137
17237
|
}
|
|
17138
|
-
} else if (!
|
|
17238
|
+
} else if (!existsSync10(join14(home, "vfkb.mjs")) || !existsSync10(join14(home, "vfkb-mcp.mjs"))) {
|
|
17139
17239
|
add("$VFKB_BUNDLE_DIR", "warn", `set to ${home} but vfkb.mjs / vfkb-mcp.mjs not found there (run \`npm run build:bundles\`)`);
|
|
17140
17240
|
} else {
|
|
17141
17241
|
add("$VFKB_BUNDLE_DIR", "ok", home);
|
|
@@ -17146,7 +17246,7 @@ function runDoctor(opts) {
|
|
|
17146
17246
|
if (env.VFKB_HOME && !env.VFKB_BUNDLE_DIR) {
|
|
17147
17247
|
add("env (deprecated)", "warn", "VFKB_HOME is a deprecated alias \u2014 rename it to VFKB_BUNDLE_DIR");
|
|
17148
17248
|
}
|
|
17149
|
-
const mcp = readJson2(
|
|
17249
|
+
const mcp = readJson2(join14(root, ".mcp.json"));
|
|
17150
17250
|
const mcpProject = mcp?.mcpServers?.vfkb?.env?.VFKB_PROJECT;
|
|
17151
17251
|
const mcpPresent = Boolean(mcp?.mcpServers?.vfkb);
|
|
17152
17252
|
if (plugin && mcpPresent) {
|
|
@@ -17160,7 +17260,7 @@ function runDoctor(opts) {
|
|
|
17160
17260
|
}
|
|
17161
17261
|
const hooks = settings?.hooks ?? {};
|
|
17162
17262
|
const expected = ["SessionStart", "PreToolUse", "Stop", "SessionEnd"];
|
|
17163
|
-
const have = expected.filter((e) => JSON.stringify(hooks[e] ?? "")
|
|
17263
|
+
const have = expected.filter((e) => isEngineWiring(JSON.stringify(hooks[e] ?? "")));
|
|
17164
17264
|
if (plugin && have.length > 0) {
|
|
17165
17265
|
add(".claude/settings.json", "warn", `vfkb hooks present ALONGSIDE the plugin (double wiring) \u2014 remove them; the plugin's hooks are primary (ADR-0045)`);
|
|
17166
17266
|
} else if (plugin) {
|
|
@@ -17176,7 +17276,7 @@ function runDoctor(opts) {
|
|
|
17176
17276
|
if (!plugin && have.length > 0 && hooksBlob.includes("bootstrap.mjs") && !hooksBlob.includes("CLAUDE_PROJECT_DIR")) {
|
|
17177
17277
|
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)");
|
|
17178
17278
|
}
|
|
17179
|
-
if (
|
|
17279
|
+
if (existsSync10(join14(root, ".vfkb", "bin", "bootstrap.mjs"))) {
|
|
17180
17280
|
add("bootstrap", "ok", ".vfkb/bin/bootstrap.mjs present");
|
|
17181
17281
|
} else if (!plugin && (mcpPresent || have.length > 0)) {
|
|
17182
17282
|
add("bootstrap", "warn", "wiring present but .vfkb/bin/bootstrap.mjs is missing \u2014 run `vfkb init`");
|
|
@@ -17216,9 +17316,9 @@ function renderDoctor(report) {
|
|
|
17216
17316
|
}
|
|
17217
17317
|
|
|
17218
17318
|
// src/import.ts
|
|
17219
|
-
import { existsSync as
|
|
17319
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
|
|
17220
17320
|
import { homedir as homedir2 } from "node:os";
|
|
17221
|
-
import { basename as basename4, extname, join as
|
|
17321
|
+
import { basename as basename4, extname, join as join15 } from "node:path";
|
|
17222
17322
|
var MYKB_FILES = {
|
|
17223
17323
|
"decisions.jsonl": "decision",
|
|
17224
17324
|
"facts.jsonl": "fact",
|
|
@@ -17243,15 +17343,15 @@ function mykbText(type, e) {
|
|
|
17243
17343
|
return parts.filter(Boolean).join("\n\n");
|
|
17244
17344
|
}
|
|
17245
17345
|
function resolveMykbArea(nameOrDir) {
|
|
17246
|
-
if (
|
|
17247
|
-
return
|
|
17346
|
+
if (existsSync11(nameOrDir) && statSync3(nameOrDir).isDirectory()) return nameOrDir;
|
|
17347
|
+
return join15(homedir2(), ".mykb", "areas", nameOrDir);
|
|
17248
17348
|
}
|
|
17249
17349
|
function fromMykb(areaDir) {
|
|
17250
|
-
if (!
|
|
17350
|
+
if (!existsSync11(areaDir)) throw new Error(`mykb area not found: ${areaDir}`);
|
|
17251
17351
|
const out = [];
|
|
17252
17352
|
for (const [file2, type] of Object.entries(MYKB_FILES)) {
|
|
17253
|
-
const path =
|
|
17254
|
-
if (!
|
|
17353
|
+
const path = join15(areaDir, file2);
|
|
17354
|
+
if (!existsSync11(path)) continue;
|
|
17255
17355
|
for (const line of readFileSync12(path, "utf8").split(/\r?\n/)) {
|
|
17256
17356
|
const trimmed = line.trim();
|
|
17257
17357
|
if (!trimmed) continue;
|
|
@@ -17278,17 +17378,17 @@ function mdTitle(path) {
|
|
|
17278
17378
|
return basename4(path, extname(path));
|
|
17279
17379
|
}
|
|
17280
17380
|
function fromAdr(dir = "docs/adr") {
|
|
17281
|
-
if (!
|
|
17381
|
+
if (!existsSync11(dir)) throw new Error(`ADR dir not found: ${dir}`);
|
|
17282
17382
|
const out = [];
|
|
17283
17383
|
for (const file2 of readdirSync3(dir).sort()) {
|
|
17284
17384
|
if (extname(file2) !== ".md" || /readme/i.test(file2)) continue;
|
|
17285
|
-
const rel =
|
|
17385
|
+
const rel = join15(dir, file2);
|
|
17286
17386
|
out.push(stamp("link", `${mdTitle(rel)} \u2192 ${rel}`, ["adr"], false));
|
|
17287
17387
|
}
|
|
17288
17388
|
return out;
|
|
17289
17389
|
}
|
|
17290
17390
|
function fromMarkdown(file2) {
|
|
17291
|
-
if (!
|
|
17391
|
+
if (!existsSync11(file2)) throw new Error(`markdown file not found: ${file2}`);
|
|
17292
17392
|
return [stamp("link", `${mdTitle(file2)} \u2192 ${file2}`, ["doc"], false)];
|
|
17293
17393
|
}
|
|
17294
17394
|
|
|
@@ -17368,25 +17468,35 @@ var DECISION_STATUSES = ["proposed", "accepted", "deprecated", "superseded"];
|
|
|
17368
17468
|
|
|
17369
17469
|
// src/cli.ts
|
|
17370
17470
|
import { readFileSync as readFileSync13 } from "node:fs";
|
|
17371
|
-
import { fileURLToPath } from "node:url";
|
|
17372
|
-
import { dirname as
|
|
17471
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
17472
|
+
import { dirname as dirname7, join as join16 } from "node:path";
|
|
17373
17473
|
function packageVersion() {
|
|
17374
17474
|
try {
|
|
17375
|
-
const here =
|
|
17376
|
-
const pkg = JSON.parse(readFileSync13(
|
|
17475
|
+
const here = dirname7(fileURLToPath2(import.meta.url));
|
|
17476
|
+
const pkg = JSON.parse(readFileSync13(join16(here, "..", "package.json"), "utf8"));
|
|
17377
17477
|
return pkg.version || ENGINE_VERSION;
|
|
17378
17478
|
} catch {
|
|
17379
17479
|
return ENGINE_VERSION;
|
|
17380
17480
|
}
|
|
17381
17481
|
}
|
|
17482
|
+
var STDIN_WATCHDOG_MS = 2e3;
|
|
17382
17483
|
function readStdin() {
|
|
17383
|
-
return new Promise((
|
|
17484
|
+
return new Promise((resolve5) => {
|
|
17384
17485
|
let data = "";
|
|
17385
|
-
if (process.stdin.isTTY) return
|
|
17486
|
+
if (process.stdin.isTTY) return resolve5("");
|
|
17487
|
+
let settled = false;
|
|
17488
|
+
const finish = () => {
|
|
17489
|
+
if (settled) return;
|
|
17490
|
+
settled = true;
|
|
17491
|
+
process.stdin.pause();
|
|
17492
|
+
process.stdin.unref?.();
|
|
17493
|
+
resolve5(data);
|
|
17494
|
+
};
|
|
17386
17495
|
process.stdin.setEncoding("utf8");
|
|
17387
17496
|
process.stdin.on("data", (c) => data += c);
|
|
17388
|
-
process.stdin.on("end",
|
|
17389
|
-
|
|
17497
|
+
process.stdin.on("end", finish);
|
|
17498
|
+
process.stdin.on("error", finish);
|
|
17499
|
+
setTimeout(finish, STDIN_WATCHDOG_MS).unref?.();
|
|
17390
17500
|
});
|
|
17391
17501
|
}
|
|
17392
17502
|
function flag(args, name) {
|
|
@@ -17482,7 +17592,8 @@ async function dispatch() {
|
|
|
17482
17592
|
let failed = 0;
|
|
17483
17593
|
for (const r of results) {
|
|
17484
17594
|
if (r.ok) {
|
|
17485
|
-
|
|
17595
|
+
const repair = r.healed ? " (manifest healed \u2014 brain was wired but manifest-less, vfkb#193)" : r.upgraded ? ' (manifest provenance upgraded \u2014 engine identity was unknown ("dev"), now the running sha, vfkb#212)' : "";
|
|
17596
|
+
process.stdout.write(`written ${r.target} ${r.id} ${r.posture}${repair}
|
|
17486
17597
|
`);
|
|
17487
17598
|
} else {
|
|
17488
17599
|
failed++;
|