@saasontools/strauss-kb 0.1.17 → 0.1.18
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/ARCHITECTURE.md +26 -0
- package/README.md +31 -3
- package/dist/{chunk-ZKIQOBHT.js → chunk-GSOTMWZZ.js} +905 -147
- package/dist/chunk-GSOTMWZZ.js.map +1 -0
- package/dist/{chunk-RMJUGTAQ.js → chunk-IOIUS26S.js} +2 -2
- package/dist/{chunk-SA3A2SPY.js → chunk-ROGVYSMV.js} +2 -2
- package/dist/cli-main.cjs +937 -184
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +914 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +291 -5
- package/dist/index.d.ts +291 -5
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +930 -177
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-ZKIQOBHT.js.map +0 -1
- /package/dist/{chunk-RMJUGTAQ.js.map → chunk-IOIUS26S.js.map} +0 -0
- /package/dist/{chunk-SA3A2SPY.js.map → chunk-ROGVYSMV.js.map} +0 -0
package/dist/cli-main.cjs
CHANGED
|
@@ -78,6 +78,14 @@ var kbAnchorSchema = import_zod.z.object({
|
|
|
78
78
|
hash: import_zod.z.string().regex(/^sha256:[0-9a-f]{64}$/, {
|
|
79
79
|
message: "hash must be sha256:<64 hex chars>"
|
|
80
80
|
}).optional(),
|
|
81
|
+
/**
|
|
82
|
+
* What `hash` was taken over: the span's raw text, or the normalised token
|
|
83
|
+
* stream a parser sees (`ast`). Absent means `raw`, which is what every
|
|
84
|
+
* anchor stamped before this field carries, so old hashes keep comparing
|
|
85
|
+
* the way they were written. An `ast` hash is blind to whitespace and
|
|
86
|
+
* comments, so reformatting the anchored code is not drift.
|
|
87
|
+
*/
|
|
88
|
+
hash_kind: import_zod.z.enum(["raw", "ast"]).optional(),
|
|
81
89
|
/** ISO 8601 timestamp of the last successful resolution. */
|
|
82
90
|
resolved_at: import_zod.z.string().min(1).optional(),
|
|
83
91
|
/** Line count of the text the hash was taken over. */
|
|
@@ -468,9 +476,9 @@ async function mapLimit(items, limit, fn) {
|
|
|
468
476
|
{ length: Math.min(limit, items.length) },
|
|
469
477
|
async () => {
|
|
470
478
|
while (!failed && next < items.length) {
|
|
471
|
-
const
|
|
479
|
+
const at2 = next++;
|
|
472
480
|
try {
|
|
473
|
-
out[
|
|
481
|
+
out[at2] = await fn(items[at2], at2);
|
|
474
482
|
} catch (error) {
|
|
475
483
|
failed = true;
|
|
476
484
|
throw error;
|
|
@@ -586,8 +594,8 @@ function safeSegment(value) {
|
|
|
586
594
|
function revRef(rev) {
|
|
587
595
|
const safe = rev.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 64);
|
|
588
596
|
let hash = 5381;
|
|
589
|
-
for (let
|
|
590
|
-
hash = (hash * 33 ^ rev.charCodeAt(
|
|
597
|
+
for (let at2 = 0; at2 < rev.length; at2++) {
|
|
598
|
+
hash = (hash * 33 ^ rev.charCodeAt(at2)) >>> 0;
|
|
591
599
|
}
|
|
592
600
|
return `refs/strauss/${safe}-${hash.toString(16)}`;
|
|
593
601
|
}
|
|
@@ -696,8 +704,8 @@ function repoUrlIsSafe(repo) {
|
|
|
696
704
|
if (!scheme?.[1]) return false;
|
|
697
705
|
if (!allowed.includes(scheme[1].toLowerCase())) return false;
|
|
698
706
|
const authority = url.slice(scheme[0].length).split("/")[0] ?? "";
|
|
699
|
-
const
|
|
700
|
-
return
|
|
707
|
+
const at2 = authority.lastIndexOf("@");
|
|
708
|
+
return at2 < 0 || !authority.slice(0, at2).includes(":");
|
|
701
709
|
}
|
|
702
710
|
function protocolArgs() {
|
|
703
711
|
const allowed = allowedProtocols();
|
|
@@ -788,7 +796,7 @@ async function readOneRepo(repo, url, declared, context) {
|
|
|
788
796
|
return new Map([
|
|
789
797
|
...rejected2,
|
|
790
798
|
...wants.map(
|
|
791
|
-
(want,
|
|
799
|
+
(want, at2) => [wantKey(repo, want.ref, want.file), reads[at2]]
|
|
792
800
|
)
|
|
793
801
|
]);
|
|
794
802
|
}
|
|
@@ -999,7 +1007,7 @@ async function readAnchorFiles(files, read, concurrency = DEFAULT_IO_CONCURRENCY
|
|
|
999
1007
|
return { ok: false, reason: "file-unreadable" };
|
|
1000
1008
|
}
|
|
1001
1009
|
});
|
|
1002
|
-
return new Map(wanted.map((file,
|
|
1010
|
+
return new Map(wanted.map((file, at2) => [file, results[at2]]));
|
|
1003
1011
|
}
|
|
1004
1012
|
|
|
1005
1013
|
// src/anchor-resolver/resolver.ts
|
|
@@ -1189,8 +1197,8 @@ async function ensureGrammar(language, options = {}) {
|
|
|
1189
1197
|
return miss(language, `grammar tree-sitter-${language}`, grammar);
|
|
1190
1198
|
const parts = [];
|
|
1191
1199
|
const total = pack2.tags.length;
|
|
1192
|
-
for (const [
|
|
1193
|
-
const name = `${language} tags${total > 1 ? ` part ${
|
|
1200
|
+
for (const [at2, part] of pack2.tags.entries()) {
|
|
1201
|
+
const name = `${language} tags${total > 1 ? ` part ${at2 + 1}/${total}` : ""}`;
|
|
1194
1202
|
const path = grammarCachePath(root, language, part.sha256, "scm");
|
|
1195
1203
|
const held = await ensurePart(path, name, part, options);
|
|
1196
1204
|
if (held !== true) return miss(language, name, held);
|
|
@@ -1335,8 +1343,8 @@ function typeNameIn(receiver) {
|
|
|
1335
1343
|
while (stack.length) {
|
|
1336
1344
|
const node = stack.pop();
|
|
1337
1345
|
if (node.type === "type_identifier") return node.text;
|
|
1338
|
-
for (let
|
|
1339
|
-
const child = node.child(
|
|
1346
|
+
for (let at2 = 0; at2 < node.childCount; at2++) {
|
|
1347
|
+
const child = node.child(at2);
|
|
1340
1348
|
if (child) stack.push(child);
|
|
1341
1349
|
}
|
|
1342
1350
|
}
|
|
@@ -1345,7 +1353,7 @@ function typeNameIn(receiver) {
|
|
|
1345
1353
|
function endsWith(chain, wanted) {
|
|
1346
1354
|
if (wanted.length > chain.length) return false;
|
|
1347
1355
|
const offset = chain.length - wanted.length;
|
|
1348
|
-
return wanted.every((segment,
|
|
1356
|
+
return wanted.every((segment, at2) => chain[offset + at2] === segment);
|
|
1349
1357
|
}
|
|
1350
1358
|
function width(node) {
|
|
1351
1359
|
return node.endIndex - node.startIndex;
|
|
@@ -1371,6 +1379,26 @@ function spanOf(definition, source) {
|
|
|
1371
1379
|
};
|
|
1372
1380
|
}
|
|
1373
1381
|
|
|
1382
|
+
// src/tree-sitter-resolver/tokens.ts
|
|
1383
|
+
function tokens(root) {
|
|
1384
|
+
const out = [];
|
|
1385
|
+
const stack = [root];
|
|
1386
|
+
while (stack.length) {
|
|
1387
|
+
const node = stack.pop();
|
|
1388
|
+
if (node.type.includes("comment")) continue;
|
|
1389
|
+
if (node.childCount === 0) {
|
|
1390
|
+
const text = node.text.trim();
|
|
1391
|
+
if (text) out.push(text);
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
for (let at2 = node.childCount - 1; at2 >= 0; at2--) {
|
|
1395
|
+
const child = node.child(at2);
|
|
1396
|
+
if (child) stack.push(child);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return out;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1374
1402
|
// src/tree-sitter-resolver/resolver.ts
|
|
1375
1403
|
var TREE_CACHE_LIMIT = 32;
|
|
1376
1404
|
var TreeSitterResolver = class {
|
|
@@ -1416,7 +1444,7 @@ var TreeSitterResolver = class {
|
|
|
1416
1444
|
(language) => this.load(language)
|
|
1417
1445
|
);
|
|
1418
1446
|
languages.forEach(
|
|
1419
|
-
(language,
|
|
1447
|
+
(language, at2) => this.loaded.set(language, loaded[at2] ?? null)
|
|
1420
1448
|
);
|
|
1421
1449
|
}
|
|
1422
1450
|
/**
|
|
@@ -1504,6 +1532,60 @@ var TreeSitterResolver = class {
|
|
|
1504
1532
|
this.trees.set(key, parsed);
|
|
1505
1533
|
return parsed;
|
|
1506
1534
|
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Every definition this file declares, as dotted symbol and span.
|
|
1537
|
+
*
|
|
1538
|
+
* The inverse of `attempt`: that asks "where is this name", this asks "what
|
|
1539
|
+
* names are here". `moved` needs the second — the stored hash has to be
|
|
1540
|
+
* looked for at every definition in the repository, and there is no name to
|
|
1541
|
+
* ask about, since the whole question is which name now carries that code.
|
|
1542
|
+
*/
|
|
1543
|
+
spans(source, file) {
|
|
1544
|
+
const language = languageForFile(file);
|
|
1545
|
+
if (!language) return [];
|
|
1546
|
+
const loaded = this.loaded.get(language);
|
|
1547
|
+
if (!loaded) return [];
|
|
1548
|
+
const parsed = this.parse(language, loaded, source);
|
|
1549
|
+
if (!parsed) return [];
|
|
1550
|
+
return parsed.definitions.filter((definition) => definition.target).map((definition) => ({
|
|
1551
|
+
symbol: chainOf(definition, parsed.byNodeId).join("."),
|
|
1552
|
+
span: spanOf(definition, source)
|
|
1553
|
+
}));
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* The token stream of a span: every leaf the parser sees, comments dropped,
|
|
1557
|
+
* joined by single spaces.
|
|
1558
|
+
*
|
|
1559
|
+
* This is what makes a reformat not be drift. Hashing it rather than the raw
|
|
1560
|
+
* text means indentation, line breaks, trailing commas the formatter moved,
|
|
1561
|
+
* and every comment above or inside the definition are outside the hash —
|
|
1562
|
+
* and a renamed identifier or a changed literal is still inside it, because
|
|
1563
|
+
* those are leaves.
|
|
1564
|
+
*
|
|
1565
|
+
* `null` when the file has no grammar, the grammar would not load, or the
|
|
1566
|
+
* text will not parse: no normalisation is better than a guessed one.
|
|
1567
|
+
*/
|
|
1568
|
+
normalize(text, file) {
|
|
1569
|
+
const language = file ? languageForFile(file) : void 0;
|
|
1570
|
+
if (!language) return null;
|
|
1571
|
+
const loaded = this.loaded.get(language);
|
|
1572
|
+
if (!loaded) return null;
|
|
1573
|
+
const parser = this.parser;
|
|
1574
|
+
if (!parser) return null;
|
|
1575
|
+
let tree;
|
|
1576
|
+
try {
|
|
1577
|
+
parser.setLanguage(loaded.language);
|
|
1578
|
+
tree = parser.parse(text);
|
|
1579
|
+
} catch {
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
if (!tree) return null;
|
|
1583
|
+
try {
|
|
1584
|
+
return tokens(tree.rootNode).join(" ");
|
|
1585
|
+
} finally {
|
|
1586
|
+
tree.delete();
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1507
1589
|
/** Drops cached trees. Grammars stay loaded — they are immutable. */
|
|
1508
1590
|
reset() {
|
|
1509
1591
|
for (const parsed of this.trees.values()) parsed.tree.delete();
|
|
@@ -1657,7 +1739,7 @@ var regexResolver = {
|
|
|
1657
1739
|
);
|
|
1658
1740
|
const nearest = Math.min(...distances);
|
|
1659
1741
|
if (Number.isFinite(nearest)) {
|
|
1660
|
-
candidates = candidates.filter((_,
|
|
1742
|
+
candidates = candidates.filter((_, at2) => distances[at2] === nearest);
|
|
1661
1743
|
}
|
|
1662
1744
|
}
|
|
1663
1745
|
if (candidates.length !== 1) return null;
|
|
@@ -1672,8 +1754,8 @@ function escapeRegExp(value) {
|
|
|
1672
1754
|
}
|
|
1673
1755
|
function distanceToParent(lines, index2, parent) {
|
|
1674
1756
|
const floor = Math.max(0, index2 - PARENT_SCOPE_LINES);
|
|
1675
|
-
for (let
|
|
1676
|
-
if (parent.test(lines[
|
|
1757
|
+
for (let at2 = index2; at2 >= floor; at2--) {
|
|
1758
|
+
if (parent.test(lines[at2] ?? "")) return index2 - at2;
|
|
1677
1759
|
}
|
|
1678
1760
|
return Number.POSITIVE_INFINITY;
|
|
1679
1761
|
}
|
|
@@ -1701,10 +1783,12 @@ function resolveAnchorSpan(source, anchor, resolvers = [regexResolver]) {
|
|
|
1701
1783
|
if (attempt.reason === "symbol-not-found") continue;
|
|
1702
1784
|
return { ok: false, reason: attempt.reason };
|
|
1703
1785
|
}
|
|
1786
|
+
const tokens2 = resolver.normalize?.(attempt.span.text, anchor.file);
|
|
1704
1787
|
return {
|
|
1705
1788
|
ok: true,
|
|
1706
1789
|
span: attempt.span,
|
|
1707
|
-
...isResolverName(resolver.name) ? { resolver: resolver.name } : {}
|
|
1790
|
+
...isResolverName(resolver.name) ? { resolver: resolver.name } : {},
|
|
1791
|
+
...tokens2 ? { normalized: tokens2 } : {}
|
|
1708
1792
|
};
|
|
1709
1793
|
}
|
|
1710
1794
|
return { ok: false, reason: "symbol-not-found" };
|
|
@@ -1732,6 +1816,11 @@ function resolverChanged(source, anchor, produced) {
|
|
|
1732
1816
|
);
|
|
1733
1817
|
return before !== null && hashAnchorText(before.text) === anchor.hash;
|
|
1734
1818
|
}
|
|
1819
|
+
function anchorHashOf(anchor, outcome) {
|
|
1820
|
+
const stored = anchor.hash ? anchor.hash_kind ?? "raw" : void 0;
|
|
1821
|
+
const wanted = stored ?? (outcome.normalized ? "ast" : "raw");
|
|
1822
|
+
return wanted === "ast" && outcome.normalized ? { hash: hashAnchorText(outcome.normalized), kind: "ast" } : { hash: hashAnchorText(outcome.span.text), kind: "raw" };
|
|
1823
|
+
}
|
|
1735
1824
|
|
|
1736
1825
|
// src/anchor-resolver/drift.ts
|
|
1737
1826
|
async function detectAnchorDrift(records, options = {}) {
|
|
@@ -1811,16 +1900,29 @@ function unresolved(anchor, reason, repo) {
|
|
|
1811
1900
|
state: "unresolved",
|
|
1812
1901
|
diffSize: null,
|
|
1813
1902
|
...reason ? { reason } : {},
|
|
1814
|
-
...repo ? { repo } : {}
|
|
1903
|
+
...repo ? { repo } : {},
|
|
1904
|
+
...classOf(reason)
|
|
1815
1905
|
};
|
|
1816
1906
|
}
|
|
1907
|
+
function provisionalDriftClass(entry) {
|
|
1908
|
+
if (entry.state === "unresolved") {
|
|
1909
|
+
return entry.reason === "file-missing" || entry.reason === "symbol-not-found" ? "gone" : void 0;
|
|
1910
|
+
}
|
|
1911
|
+
return entry.state === "drifted" ? "changed" : void 0;
|
|
1912
|
+
}
|
|
1913
|
+
function classOf(reason) {
|
|
1914
|
+
const settled = provisionalDriftClass({ state: "unresolved", reason });
|
|
1915
|
+
return settled ? { class: settled } : {};
|
|
1916
|
+
}
|
|
1817
1917
|
function hashIn(source, anchor, resolvers) {
|
|
1818
1918
|
const outcome = resolveAnchorSpan(source, anchor, resolvers);
|
|
1819
1919
|
if (!outcome.ok) return { ok: false, reason: outcome.reason };
|
|
1920
|
+
const { hash, kind } = anchorHashOf(anchor, outcome);
|
|
1820
1921
|
return {
|
|
1821
1922
|
ok: true,
|
|
1822
1923
|
current: {
|
|
1823
|
-
hash
|
|
1924
|
+
hash,
|
|
1925
|
+
kind,
|
|
1824
1926
|
lines: outcome.span.endLine - outcome.span.startLine + 1,
|
|
1825
1927
|
...outcome.resolver ? { resolver: outcome.resolver } : {}
|
|
1826
1928
|
}
|
|
@@ -1833,11 +1935,14 @@ function resolverExtras(source, anchor, current) {
|
|
|
1833
1935
|
};
|
|
1834
1936
|
}
|
|
1835
1937
|
function compared(anchor, current, extra = {}) {
|
|
1938
|
+
const matched = current.hash === anchor.hash;
|
|
1836
1939
|
return {
|
|
1837
1940
|
...base(anchor),
|
|
1838
|
-
state:
|
|
1941
|
+
state: matched ? "match" : "drifted",
|
|
1839
1942
|
currentHash: current.hash,
|
|
1943
|
+
hashKind: current.kind,
|
|
1840
1944
|
diffSize: anchor.lines === void 0 ? null : Math.abs(current.lines - anchor.lines),
|
|
1945
|
+
...matched ? {} : { class: "changed" },
|
|
1841
1946
|
...extra
|
|
1842
1947
|
};
|
|
1843
1948
|
}
|
|
@@ -2279,7 +2384,7 @@ async function listPins(store, workspaceDir) {
|
|
|
2279
2384
|
}
|
|
2280
2385
|
|
|
2281
2386
|
// src/kb-pins/pin.ts
|
|
2282
|
-
async function pinBase(store, workspaceDir, bundlePath2,
|
|
2387
|
+
async function pinBase(store, workspaceDir, bundlePath2, at2, options = {}) {
|
|
2283
2388
|
const layer = options.layer ?? "project";
|
|
2284
2389
|
const root = layerRoot(workspaceDir, layer);
|
|
2285
2390
|
const manifest = await readPinsLayer(workspaceDir, layer);
|
|
@@ -2307,7 +2412,7 @@ async function pinBase(store, workspaceDir, bundlePath2, at, options = {}) {
|
|
|
2307
2412
|
return {
|
|
2308
2413
|
path: existing.path,
|
|
2309
2414
|
layer,
|
|
2310
|
-
pinnedAt: existing.pinnedAt ??
|
|
2415
|
+
pinnedAt: existing.pinnedAt ?? at2,
|
|
2311
2416
|
alreadyPinned: true,
|
|
2312
2417
|
...updated.mode ? { mode: updated.mode } : {},
|
|
2313
2418
|
...updated.profiles ? { profiles: updated.profiles } : {},
|
|
@@ -2317,7 +2422,7 @@ async function pinBase(store, workspaceDir, bundlePath2, at, options = {}) {
|
|
|
2317
2422
|
}
|
|
2318
2423
|
const entry = {
|
|
2319
2424
|
path: storablePath(root, bundlePath2),
|
|
2320
|
-
pinnedAt:
|
|
2425
|
+
pinnedAt: at2,
|
|
2321
2426
|
...fields
|
|
2322
2427
|
};
|
|
2323
2428
|
await writePinsLayer(workspaceDir, layer, {
|
|
@@ -2327,7 +2432,7 @@ async function pinBase(store, workspaceDir, bundlePath2, at, options = {}) {
|
|
|
2327
2432
|
return {
|
|
2328
2433
|
path: entry.path,
|
|
2329
2434
|
layer,
|
|
2330
|
-
pinnedAt:
|
|
2435
|
+
pinnedAt: at2,
|
|
2331
2436
|
alreadyPinned: false,
|
|
2332
2437
|
...fields,
|
|
2333
2438
|
...warning ? { warning } : {}
|
|
@@ -2379,9 +2484,9 @@ function argvFlag(argv, name) {
|
|
|
2379
2484
|
if (!value2) throw new KbMissingFlagValueError(name);
|
|
2380
2485
|
return value2;
|
|
2381
2486
|
}
|
|
2382
|
-
const
|
|
2383
|
-
if (
|
|
2384
|
-
const value = argv[
|
|
2487
|
+
const at2 = argv.indexOf(name);
|
|
2488
|
+
if (at2 === -1) return void 0;
|
|
2489
|
+
const value = argv[at2 + 1];
|
|
2385
2490
|
if (value === void 0 || value.startsWith("--")) {
|
|
2386
2491
|
throw new KbMissingFlagValueError(name);
|
|
2387
2492
|
}
|
|
@@ -2474,11 +2579,14 @@ var anchorResolveCommand = define({
|
|
|
2474
2579
|
}
|
|
2475
2580
|
const resolved = outcome.span;
|
|
2476
2581
|
const producedBy = outcome.resolver;
|
|
2477
|
-
const currentHash =
|
|
2582
|
+
const { hash: currentHash, kind } = anchorHashOf(anchor, outcome);
|
|
2478
2583
|
const currentLines = resolved.endLine - resolved.startLine + 1;
|
|
2584
|
+
const stampedKind = outcome.normalized ? "ast" : "raw";
|
|
2585
|
+
const stampedHash = outcome.normalized ? anchorHashOf({ ...anchor, hash: void 0 }, outcome).hash : currentHash;
|
|
2479
2586
|
const stamped = {
|
|
2480
2587
|
...anchor,
|
|
2481
|
-
hash:
|
|
2588
|
+
hash: stampedHash,
|
|
2589
|
+
hash_kind: stampedKind,
|
|
2482
2590
|
lines: currentLines,
|
|
2483
2591
|
resolved_at: now(),
|
|
2484
2592
|
...producedBy ? { resolver: producedBy } : {}
|
|
@@ -2488,7 +2596,8 @@ var anchorResolveCommand = define({
|
|
|
2488
2596
|
results.push({
|
|
2489
2597
|
...base2,
|
|
2490
2598
|
state: "stamped",
|
|
2491
|
-
currentHash,
|
|
2599
|
+
currentHash: stampedHash,
|
|
2600
|
+
hashKind: stampedKind,
|
|
2492
2601
|
...producedBy ? { resolver: producedBy } : {}
|
|
2493
2602
|
});
|
|
2494
2603
|
updated.push(stamped);
|
|
@@ -2500,6 +2609,7 @@ var anchorResolveCommand = define({
|
|
|
2500
2609
|
...base2,
|
|
2501
2610
|
state: "drifted",
|
|
2502
2611
|
currentHash,
|
|
2612
|
+
hashKind: kind,
|
|
2503
2613
|
diffSize: lineDelta(anchor, currentLines),
|
|
2504
2614
|
...producedBy ? { resolver: producedBy } : {},
|
|
2505
2615
|
// A regex-stamped anchor re-read by tree-sitter drifts because the
|
|
@@ -2528,6 +2638,7 @@ var anchorResolveCommand = define({
|
|
|
2528
2638
|
...base2,
|
|
2529
2639
|
state: "match",
|
|
2530
2640
|
currentHash,
|
|
2641
|
+
hashKind: kind,
|
|
2531
2642
|
...producedBy ? { resolver: producedBy } : {},
|
|
2532
2643
|
...pinned ? { remoteState: "matches-ref" } : {}
|
|
2533
2644
|
});
|
|
@@ -2760,7 +2871,8 @@ function warningAnchor(entry) {
|
|
|
2760
2871
|
diffSize,
|
|
2761
2872
|
...reason !== void 0 ? { reason } : {},
|
|
2762
2873
|
...repo !== void 0 ? { repo } : {},
|
|
2763
|
-
...remoteState !== void 0 ? { remoteState } : {}
|
|
2874
|
+
...remoteState !== void 0 ? { remoteState } : {},
|
|
2875
|
+
...entry.class !== void 0 ? { class: entry.class } : {}
|
|
2764
2876
|
};
|
|
2765
2877
|
}
|
|
2766
2878
|
function resolveHeads(from, byId) {
|
|
@@ -3236,7 +3348,406 @@ var contextCommand = define({
|
|
|
3236
3348
|
});
|
|
3237
3349
|
|
|
3238
3350
|
// src/commands/doctor.ts
|
|
3239
|
-
var
|
|
3351
|
+
var import_zod13 = require("zod");
|
|
3352
|
+
|
|
3353
|
+
// src/drift/git.ts
|
|
3354
|
+
var import_node_child_process3 = require("child_process");
|
|
3355
|
+
var import_node_util3 = require("util");
|
|
3356
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
3357
|
+
var MAX_GIT_OUTPUT_BYTES = 1048576;
|
|
3358
|
+
var GIT_TIMEOUT_MS = 5e3;
|
|
3359
|
+
async function git2(cwd, args) {
|
|
3360
|
+
const env = { ...process.env };
|
|
3361
|
+
delete env["GIT_DIR"];
|
|
3362
|
+
delete env["GIT_WORK_TREE"];
|
|
3363
|
+
delete env["GIT_INDEX_FILE"];
|
|
3364
|
+
try {
|
|
3365
|
+
const { stdout } = await execFileAsync3("git", ["-C", cwd, ...args], {
|
|
3366
|
+
timeout: GIT_TIMEOUT_MS,
|
|
3367
|
+
maxBuffer: MAX_GIT_OUTPUT_BYTES,
|
|
3368
|
+
env
|
|
3369
|
+
});
|
|
3370
|
+
return { ok: true, stdout };
|
|
3371
|
+
} catch {
|
|
3372
|
+
return { ok: false };
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
async function listRepoFiles(repoRoot) {
|
|
3376
|
+
const result = await git2(repoRoot, ["ls-files", "-z", "--cached"]);
|
|
3377
|
+
if (!result.ok) return [];
|
|
3378
|
+
return result.stdout.split("\0").filter(Boolean);
|
|
3379
|
+
}
|
|
3380
|
+
async function readOldSource(repoRoot, anchor) {
|
|
3381
|
+
if (!filePathIsSafe(anchor.file))
|
|
3382
|
+
return { ok: false, reason: "unrecoverable" };
|
|
3383
|
+
if (anchor.ref && refShapeIsSafe(anchor.ref)) {
|
|
3384
|
+
const shown2 = await showFile(repoRoot, anchor.ref, anchor.file);
|
|
3385
|
+
if (shown2 !== null) {
|
|
3386
|
+
return {
|
|
3387
|
+
ok: true,
|
|
3388
|
+
source: shown2,
|
|
3389
|
+
origin: { kind: "ref", ref: anchor.ref }
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
const at2 = anchor.resolved_at;
|
|
3394
|
+
if (!at2 || Number.isNaN(Date.parse(at2))) {
|
|
3395
|
+
return { ok: false, reason: "unrecoverable" };
|
|
3396
|
+
}
|
|
3397
|
+
const found = await git2(repoRoot, [
|
|
3398
|
+
"log",
|
|
3399
|
+
"-1",
|
|
3400
|
+
"--format=%H",
|
|
3401
|
+
`--before=${at2}`,
|
|
3402
|
+
"--end-of-options",
|
|
3403
|
+
"HEAD",
|
|
3404
|
+
"--",
|
|
3405
|
+
anchor.file
|
|
3406
|
+
]);
|
|
3407
|
+
const sha = found.ok ? found.stdout.trim() : "";
|
|
3408
|
+
if (!sha || !refShapeIsSafe(sha))
|
|
3409
|
+
return { ok: false, reason: "unrecoverable" };
|
|
3410
|
+
const shown = await showFile(repoRoot, sha, anchor.file);
|
|
3411
|
+
if (shown === null) return { ok: false, reason: "unrecoverable" };
|
|
3412
|
+
return { ok: true, source: shown, origin: { kind: "history", ref: sha } };
|
|
3413
|
+
}
|
|
3414
|
+
async function showFile(repoRoot, ref, file) {
|
|
3415
|
+
const path = file.replace(/^\.\//, "");
|
|
3416
|
+
const result = await git2(repoRoot, [
|
|
3417
|
+
"show",
|
|
3418
|
+
"--end-of-options",
|
|
3419
|
+
`${ref}:${path}`
|
|
3420
|
+
]);
|
|
3421
|
+
return result.ok ? result.stdout : null;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
// src/drift/moved.ts
|
|
3425
|
+
var import_promises7 = require("fs/promises");
|
|
3426
|
+
var MAX_MOVED_SEARCH_FILES = 2e3;
|
|
3427
|
+
var SEARCH_BATCH = 64;
|
|
3428
|
+
function movedSearch(repoRoot, options = {}) {
|
|
3429
|
+
const read = options.reader ?? anchorFileReader(repoRoot);
|
|
3430
|
+
const sizeOf = options.sizeOf ?? diskSize(repoRoot);
|
|
3431
|
+
const resolver = new TreeSitterResolver();
|
|
3432
|
+
let repoFiles;
|
|
3433
|
+
const prepared = /* @__PURE__ */ new Set();
|
|
3434
|
+
const filesForLanguage = async (language) => {
|
|
3435
|
+
repoFiles ??= listRepoFiles(repoRoot);
|
|
3436
|
+
return (await repoFiles).filter((file) => languageForFile(file) === language).slice(0, MAX_MOVED_SEARCH_FILES);
|
|
3437
|
+
};
|
|
3438
|
+
return {
|
|
3439
|
+
async find(anchor) {
|
|
3440
|
+
const stored = anchor.hash;
|
|
3441
|
+
if (!stored) return void 0;
|
|
3442
|
+
const language = languageForFile(anchor.file);
|
|
3443
|
+
if (!language) return sameFileWindow(anchor, read, stored);
|
|
3444
|
+
const candidates = await filesForLanguage(language);
|
|
3445
|
+
if (!prepared.has(language)) {
|
|
3446
|
+
await resolver.prepare(candidates.length ? candidates : [anchor.file]);
|
|
3447
|
+
prepared.add(language);
|
|
3448
|
+
}
|
|
3449
|
+
const floor = anchor.lines ?? 0;
|
|
3450
|
+
for (let at2 = 0; at2 < candidates.length; at2 += SEARCH_BATCH) {
|
|
3451
|
+
const batch = candidates.slice(at2, at2 + SEARCH_BATCH);
|
|
3452
|
+
const hits = await mapLimit(
|
|
3453
|
+
batch,
|
|
3454
|
+
DEFAULT_IO_CONCURRENCY,
|
|
3455
|
+
async (file) => {
|
|
3456
|
+
const size2 = await sizeOf(file);
|
|
3457
|
+
if (size2 !== null && size2 < floor) return void 0;
|
|
3458
|
+
return matchIn(resolver, read, anchor, stored, file);
|
|
3459
|
+
}
|
|
3460
|
+
);
|
|
3461
|
+
const found = hits.find((hit) => hit !== void 0);
|
|
3462
|
+
if (found) return found;
|
|
3463
|
+
}
|
|
3464
|
+
return void 0;
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
async function matchIn(resolver, read, anchor, stored, file) {
|
|
3469
|
+
const source = await read(file);
|
|
3470
|
+
if (!source.ok) return void 0;
|
|
3471
|
+
const normalized = source.source.replace(/\r\n/g, "\n");
|
|
3472
|
+
for (const found of resolver.spans(normalized, file)) {
|
|
3473
|
+
const text = anchor.hash_kind === "ast" ? resolver.normalize(found.span.text, file) : found.span.text;
|
|
3474
|
+
if (text === null || hashAnchorText(text) !== stored) continue;
|
|
3475
|
+
if (file === anchor.file && found.symbol === anchor.symbol) continue;
|
|
3476
|
+
return {
|
|
3477
|
+
file,
|
|
3478
|
+
symbol: found.symbol,
|
|
3479
|
+
startLine: found.span.startLine,
|
|
3480
|
+
endLine: found.span.endLine
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
return void 0;
|
|
3484
|
+
}
|
|
3485
|
+
function diskSize(repoRoot) {
|
|
3486
|
+
return async (file) => {
|
|
3487
|
+
const path = anchorFilePath(repoRoot, file);
|
|
3488
|
+
if (path === null) return null;
|
|
3489
|
+
try {
|
|
3490
|
+
return (await (0, import_promises7.stat)(path)).size;
|
|
3491
|
+
} catch {
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
async function sameFileWindow(anchor, read, stored) {
|
|
3497
|
+
const height = anchor.lines;
|
|
3498
|
+
if (!height || anchor.hash_kind === "ast") return void 0;
|
|
3499
|
+
const source = await read(anchor.file);
|
|
3500
|
+
if (!source.ok) return void 0;
|
|
3501
|
+
const lines = source.source.replace(/\r\n/g, "\n").split("\n");
|
|
3502
|
+
for (let at2 = 0; at2 + height <= lines.length; at2++) {
|
|
3503
|
+
if (hashAnchorText(lines.slice(at2, at2 + height).join("\n")) !== stored) {
|
|
3504
|
+
continue;
|
|
3505
|
+
}
|
|
3506
|
+
return {
|
|
3507
|
+
file: anchor.file,
|
|
3508
|
+
...anchor.symbol ? { symbol: anchor.symbol } : {},
|
|
3509
|
+
startLine: at2 + 1,
|
|
3510
|
+
endLine: at2 + height
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
return void 0;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
// src/drift/classify.ts
|
|
3517
|
+
async function classifyDrift(repoRoot, record, entries, options = {}) {
|
|
3518
|
+
const anchors = (record.frontmatter.strauss_anchors ?? []).filter(
|
|
3519
|
+
(anchor) => anchor.hash
|
|
3520
|
+
);
|
|
3521
|
+
const reader = options.reader ?? anchorFileReader(repoRoot);
|
|
3522
|
+
const treeSitter = new TreeSitterResolver();
|
|
3523
|
+
const resolvers = [treeSitter, regexResolver];
|
|
3524
|
+
const search = options.search ?? movedSearch(repoRoot, { ...options.reader ? { reader } : {} });
|
|
3525
|
+
const wanted = [];
|
|
3526
|
+
entries.forEach((entry, at2) => {
|
|
3527
|
+
const anchor = anchors[at2];
|
|
3528
|
+
if (!anchor) return;
|
|
3529
|
+
if (entry.state === "match" || isUncheckedReason(entry.reason)) return;
|
|
3530
|
+
wanted.push({ anchor, entry });
|
|
3531
|
+
});
|
|
3532
|
+
if (!wanted.length) return [];
|
|
3533
|
+
await prepareResolvers(
|
|
3534
|
+
resolvers,
|
|
3535
|
+
wanted.map(({ anchor }) => anchor.file)
|
|
3536
|
+
);
|
|
3537
|
+
const out = [];
|
|
3538
|
+
for (const { anchor, entry } of wanted) {
|
|
3539
|
+
const movedTo = await search.find(anchor);
|
|
3540
|
+
if (movedTo) {
|
|
3541
|
+
out.push({
|
|
3542
|
+
anchor,
|
|
3543
|
+
entry: { ...entry, class: "moved", movedTo },
|
|
3544
|
+
class: "moved"
|
|
3545
|
+
});
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
const newText = await currentText(reader, anchor, resolvers);
|
|
3549
|
+
const old = options.withHistory === false ? { ok: false, reason: "unrecoverable" } : await readOldSource(repoRoot, anchor);
|
|
3550
|
+
const oldText = old.ok ? spanIn(old.source, anchor, resolvers) : void 0;
|
|
3551
|
+
const settled = newText !== void 0 && oldText !== void 0 && sameTokens(treeSitter, anchor.file, oldText, newText) ? "cosmetic" : entry.class ?? "changed";
|
|
3552
|
+
out.push({
|
|
3553
|
+
anchor,
|
|
3554
|
+
entry: { ...entry, class: settled },
|
|
3555
|
+
class: settled,
|
|
3556
|
+
...newText !== void 0 ? { newText } : {},
|
|
3557
|
+
...oldText !== void 0 ? { oldText } : {},
|
|
3558
|
+
...old.ok ? { oldOrigin: old.origin } : {}
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
return out;
|
|
3562
|
+
}
|
|
3563
|
+
function sameTokens(resolver, file, before, after) {
|
|
3564
|
+
if (before === after) return false;
|
|
3565
|
+
const left = resolver.normalize(before, file);
|
|
3566
|
+
const right = resolver.normalize(after, file);
|
|
3567
|
+
return left !== null && left === right;
|
|
3568
|
+
}
|
|
3569
|
+
async function currentText(reader, anchor, resolvers) {
|
|
3570
|
+
const read = await reader(anchor.file);
|
|
3571
|
+
if (!read.ok) return void 0;
|
|
3572
|
+
return spanIn(read.source, anchor, resolvers);
|
|
3573
|
+
}
|
|
3574
|
+
function spanIn(source, anchor, resolvers) {
|
|
3575
|
+
const outcome = resolveAnchorSpan(source, anchor, resolvers);
|
|
3576
|
+
return outcome.ok ? outcome.span.text : void 0;
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
// src/drift/diff.ts
|
|
3580
|
+
var MAX_ANCHOR_DIFF_LINES = 200;
|
|
3581
|
+
var PACKET_DIFF_LINE_BUDGET = 200;
|
|
3582
|
+
var MIN_ANCHOR_DIFF_LINES = 12;
|
|
3583
|
+
function diffBudget(anchors) {
|
|
3584
|
+
if (anchors <= 0) return MAX_ANCHOR_DIFF_LINES;
|
|
3585
|
+
return Math.min(
|
|
3586
|
+
MAX_ANCHOR_DIFF_LINES,
|
|
3587
|
+
Math.max(
|
|
3588
|
+
MIN_ANCHOR_DIFF_LINES,
|
|
3589
|
+
Math.floor(PACKET_DIFF_LINE_BUDGET / anchors)
|
|
3590
|
+
)
|
|
3591
|
+
);
|
|
3592
|
+
}
|
|
3593
|
+
function unifiedDiff(before, after, options = {}) {
|
|
3594
|
+
const max = options.maxLines ?? MAX_ANCHOR_DIFF_LINES;
|
|
3595
|
+
const left = before.replace(/\r\n/g, "\n").split("\n");
|
|
3596
|
+
const right = after.replace(/\r\n/g, "\n").split("\n");
|
|
3597
|
+
const body = [];
|
|
3598
|
+
let added = 0;
|
|
3599
|
+
let removed = 0;
|
|
3600
|
+
for (const edit of edits(left, right)) {
|
|
3601
|
+
if (edit.kind === "same") body.push(` ${edit.line}`);
|
|
3602
|
+
else if (edit.kind === "remove") {
|
|
3603
|
+
body.push(`-${edit.line}`);
|
|
3604
|
+
removed += 1;
|
|
3605
|
+
} else {
|
|
3606
|
+
body.push(`+${edit.line}`);
|
|
3607
|
+
added += 1;
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
const truncated = body.length > max;
|
|
3611
|
+
const shown = truncated ? body.slice(0, max) : body;
|
|
3612
|
+
const header = `@@ -1,${left.length} +1,${right.length} @@${options.oldLabel ? ` ${options.oldLabel} \u2192 ${options.newLabel ?? ""}`.trimEnd() : ""}`;
|
|
3613
|
+
const lines = [header, ...shown];
|
|
3614
|
+
if (truncated) lines.push(`\u2026 ${body.length - max} more diff lines`);
|
|
3615
|
+
return { text: lines.join("\n"), added, removed, truncated };
|
|
3616
|
+
}
|
|
3617
|
+
function edits(left, right) {
|
|
3618
|
+
const rows = left.length;
|
|
3619
|
+
const cols = right.length;
|
|
3620
|
+
const table2 = Array.from(
|
|
3621
|
+
{ length: rows + 1 },
|
|
3622
|
+
() => new Array(cols + 1).fill(0)
|
|
3623
|
+
);
|
|
3624
|
+
for (let row2 = rows - 1; row2 >= 0; row2--) {
|
|
3625
|
+
for (let col2 = cols - 1; col2 >= 0; col2--) {
|
|
3626
|
+
table2[row2][col2] = left[row2] === right[col2] ? table2[row2 + 1][col2 + 1] + 1 : Math.max(
|
|
3627
|
+
table2[row2 + 1][col2],
|
|
3628
|
+
table2[row2][col2 + 1]
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
const out = [];
|
|
3633
|
+
let row = 0;
|
|
3634
|
+
let col = 0;
|
|
3635
|
+
while (row < rows && col < cols) {
|
|
3636
|
+
if (left[row] === right[col]) {
|
|
3637
|
+
out.push({ kind: "same", line: left[row] });
|
|
3638
|
+
row += 1;
|
|
3639
|
+
col += 1;
|
|
3640
|
+
} else if (table2[row + 1][col] >= table2[row][col + 1]) {
|
|
3641
|
+
out.push({ kind: "remove", line: left[row] });
|
|
3642
|
+
row += 1;
|
|
3643
|
+
} else {
|
|
3644
|
+
out.push({ kind: "add", line: right[col] });
|
|
3645
|
+
col += 1;
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
for (; row < rows; row++)
|
|
3649
|
+
out.push({ kind: "remove", line: left[row] });
|
|
3650
|
+
for (; col < cols; col++)
|
|
3651
|
+
out.push({ kind: "add", line: right[col] });
|
|
3652
|
+
return out;
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
// src/drift/packet.ts
|
|
3656
|
+
var PRESUMED_INVALID = [
|
|
3657
|
+
"fact",
|
|
3658
|
+
"constraint",
|
|
3659
|
+
"contract"
|
|
3660
|
+
];
|
|
3661
|
+
var RATIONALE_SURVIVES = ["decision", "risk"];
|
|
3662
|
+
var DEFAULT_NOTES = {
|
|
3663
|
+
"presumed-invalidated": "the code this claim was taken from changed; presume it no longer holds until re-read",
|
|
3664
|
+
"rationale-may-survive": "the reasoning may outlive the code that implemented it; check whether it does",
|
|
3665
|
+
review: "re-read the record against the new code"
|
|
3666
|
+
};
|
|
3667
|
+
async function reassessPacket(repoRoot, record, entries, options = {}) {
|
|
3668
|
+
const classified = await classifyDrift(repoRoot, record, entries, {
|
|
3669
|
+
...options.reader ? { reader: options.reader } : {},
|
|
3670
|
+
...options.search ? { search: options.search } : {},
|
|
3671
|
+
withHistory: options.withDiff !== false
|
|
3672
|
+
});
|
|
3673
|
+
const open = classified.filter(
|
|
3674
|
+
(found) => found.class === "changed" || found.class === "gone"
|
|
3675
|
+
);
|
|
3676
|
+
if (!open.length) return { packet: null, classified };
|
|
3677
|
+
const budget = diffBudget(open.length);
|
|
3678
|
+
const anchors = open.map(
|
|
3679
|
+
(found) => anchorPacket(found, options.withDiff === true, budget)
|
|
3680
|
+
);
|
|
3681
|
+
const type = record.frontmatter.type;
|
|
3682
|
+
const fallback = isKbRecordType(type) ? PRESUMED_INVALID.includes(type) ? "presumed-invalidated" : RATIONALE_SURVIVES.includes(type) ? "rationale-may-survive" : "review" : "review";
|
|
3683
|
+
return {
|
|
3684
|
+
classified,
|
|
3685
|
+
packet: {
|
|
3686
|
+
conceptId: record.conceptId,
|
|
3687
|
+
title: record.frontmatter.title ?? null,
|
|
3688
|
+
type,
|
|
3689
|
+
standing: options.standing ?? "unsettled",
|
|
3690
|
+
why: record.frontmatter.description ?? null,
|
|
3691
|
+
claim: claimOf(record),
|
|
3692
|
+
anchors,
|
|
3693
|
+
impact: (options.impact?.impacted ?? []).map((entry) => ({
|
|
3694
|
+
conceptId: entry.conceptId,
|
|
3695
|
+
title: entry.title,
|
|
3696
|
+
standing: entry.standing,
|
|
3697
|
+
depth: entry.depth
|
|
3698
|
+
})),
|
|
3699
|
+
impactTruncated: options.impact?.truncated ?? false,
|
|
3700
|
+
default: fallback,
|
|
3701
|
+
defaultNote: DEFAULT_NOTES[fallback]
|
|
3702
|
+
}
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
function anchorPacket(found, withDiff, maxLines) {
|
|
3706
|
+
const { entry } = found;
|
|
3707
|
+
const base2 = {
|
|
3708
|
+
file: entry.file,
|
|
3709
|
+
...entry.symbol ? { symbol: entry.symbol } : {},
|
|
3710
|
+
class: found.class,
|
|
3711
|
+
...entry.reason ? { reason: entry.reason } : {},
|
|
3712
|
+
storedHash: entry.storedHash,
|
|
3713
|
+
...entry.currentHash ? { currentHash: entry.currentHash } : {},
|
|
3714
|
+
diffSize: entry.diffSize,
|
|
3715
|
+
...entry.movedTo ? { movedTo: entry.movedTo } : {}
|
|
3716
|
+
};
|
|
3717
|
+
if (!withDiff) return base2;
|
|
3718
|
+
if (found.oldText === void 0 || !found.oldOrigin) {
|
|
3719
|
+
return { ...base2, diff: { status: "unrecoverable" } };
|
|
3720
|
+
}
|
|
3721
|
+
const rendered = unifiedDiff(found.oldText, found.newText ?? "", {
|
|
3722
|
+
maxLines
|
|
3723
|
+
});
|
|
3724
|
+
return {
|
|
3725
|
+
...base2,
|
|
3726
|
+
diff: {
|
|
3727
|
+
status: "ok",
|
|
3728
|
+
source: found.oldOrigin.kind,
|
|
3729
|
+
ref: found.oldOrigin.ref,
|
|
3730
|
+
unified: rendered.text,
|
|
3731
|
+
added: rendered.added,
|
|
3732
|
+
removed: rendered.removed,
|
|
3733
|
+
truncated: rendered.truncated
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
function claimOf(record) {
|
|
3738
|
+
const type = record.frontmatter.type;
|
|
3739
|
+
const section = isKbRecordType(type) ? RECORD_TYPES[type].sections[0] : void 0;
|
|
3740
|
+
if (!section) return null;
|
|
3741
|
+
const lines = record.body.replace(/\r\n/g, "\n").split("\n");
|
|
3742
|
+
const start = lines.findIndex(
|
|
3743
|
+
(line) => line.trim().toLowerCase() === `## ${section}`.toLowerCase()
|
|
3744
|
+
);
|
|
3745
|
+
if (start < 0) return null;
|
|
3746
|
+
const rest = lines.slice(start + 1);
|
|
3747
|
+
const end = rest.findIndex((line) => line.startsWith("## "));
|
|
3748
|
+
const text = (end < 0 ? rest : rest.slice(0, end)).join("\n").trim();
|
|
3749
|
+
return text ? { section, text } : null;
|
|
3750
|
+
}
|
|
3240
3751
|
|
|
3241
3752
|
// src/kb-edges.ts
|
|
3242
3753
|
var KB_EDGE_KINDS = [
|
|
@@ -3486,18 +3997,18 @@ function expired(hits, now) {
|
|
|
3486
3997
|
for (const hit of hits) {
|
|
3487
3998
|
const raw = hit.record.frontmatter.stale_after;
|
|
3488
3999
|
if (!raw) continue;
|
|
3489
|
-
const
|
|
3490
|
-
if (Number.isNaN(
|
|
4000
|
+
const at2 = Date.parse(raw);
|
|
4001
|
+
if (Number.isNaN(at2)) {
|
|
3491
4002
|
findings.push(
|
|
3492
4003
|
finding(hit.record, `stale_after "${raw}" is not a readable date`)
|
|
3493
4004
|
);
|
|
3494
4005
|
continue;
|
|
3495
4006
|
}
|
|
3496
|
-
if (
|
|
4007
|
+
if (at2 < now.getTime()) {
|
|
3497
4008
|
findings.push(
|
|
3498
4009
|
finding(
|
|
3499
4010
|
hit.record,
|
|
3500
|
-
`stale since ${raw} (${daysBetween(
|
|
4011
|
+
`stale since ${raw} (${daysBetween(at2, now.getTime())} days ago)`
|
|
3501
4012
|
)
|
|
3502
4013
|
);
|
|
3503
4014
|
}
|
|
@@ -3510,12 +4021,12 @@ function expiring(hits, now, withinDays) {
|
|
|
3510
4021
|
for (const hit of hits) {
|
|
3511
4022
|
const raw = hit.record.frontmatter.stale_after;
|
|
3512
4023
|
if (!raw) continue;
|
|
3513
|
-
const
|
|
3514
|
-
if (Number.isNaN(
|
|
4024
|
+
const at2 = Date.parse(raw);
|
|
4025
|
+
if (Number.isNaN(at2) || at2 < now.getTime() || at2 > horizon) continue;
|
|
3515
4026
|
findings.push(
|
|
3516
4027
|
finding(
|
|
3517
4028
|
hit.record,
|
|
3518
|
-
`goes stale ${raw} (in ${daysBetween(now.getTime(),
|
|
4029
|
+
`goes stale ${raw} (in ${daysBetween(now.getTime(), at2)} days)`
|
|
3519
4030
|
)
|
|
3520
4031
|
);
|
|
3521
4032
|
}
|
|
@@ -3685,13 +4196,16 @@ function anchorFindings(hits, kind, headline) {
|
|
|
3685
4196
|
);
|
|
3686
4197
|
}
|
|
3687
4198
|
function describeAnchor(anchor) {
|
|
3688
|
-
const
|
|
3689
|
-
if (anchor.
|
|
4199
|
+
const at2 = anchor.symbol ? `${anchor.file}:${anchor.symbol}` : anchor.file;
|
|
4200
|
+
if (anchor.class === "gone") {
|
|
4201
|
+
return `${at2} gone${anchor.reason ? ` (${anchor.reason})` : ""}`;
|
|
4202
|
+
}
|
|
4203
|
+
if (anchor.reason) return `${at2} (${anchor.reason})`;
|
|
3690
4204
|
if (anchor.remoteState === "drifted-on-default") {
|
|
3691
|
-
return `${
|
|
4205
|
+
return `${at2} (matches ref, moved on the default branch)`;
|
|
3692
4206
|
}
|
|
3693
|
-
if (anchor.diffSize === null) return `${
|
|
3694
|
-
return anchor.diffSize === 0 ? `${
|
|
4207
|
+
if (anchor.diffSize === null) return `${at2} (changed, size unrecorded)`;
|
|
4208
|
+
return anchor.diffSize === 0 ? `${at2} (content changed, same line count)` : `${at2} (${anchor.diffSize} line${anchor.diffSize === 1 ? "" : "s"} apart)`;
|
|
3695
4209
|
}
|
|
3696
4210
|
function replaces(later, earlier) {
|
|
3697
4211
|
return (later.frontmatter.strauss_supersedes ?? []).includes(earlier.conceptId) || earlier.frontmatter.strauss_superseded_by === later.conceptId;
|
|
@@ -3708,21 +4222,173 @@ function daysBetween(from, to) {
|
|
|
3708
4222
|
return Math.max(0, Math.floor((to - from) / DAY_MS));
|
|
3709
4223
|
}
|
|
3710
4224
|
function ageInDays(record, now) {
|
|
3711
|
-
const
|
|
3712
|
-
if (!
|
|
3713
|
-
const written = Date.parse(
|
|
4225
|
+
const at2 = record.frontmatter.generated?.at;
|
|
4226
|
+
if (!at2) return null;
|
|
4227
|
+
const written = Date.parse(at2);
|
|
3714
4228
|
if (Number.isNaN(written)) return null;
|
|
3715
4229
|
return daysBetween(written, now.getTime());
|
|
3716
4230
|
}
|
|
3717
4231
|
|
|
4232
|
+
// src/commands/reassess.ts
|
|
4233
|
+
var import_zod12 = require("zod");
|
|
4234
|
+
var reassessCommand = define({
|
|
4235
|
+
name: "reassess",
|
|
4236
|
+
tool: "kb_reassess",
|
|
4237
|
+
usage: "reassess <concept-id> [--repo-root <path>] [--with-diff]",
|
|
4238
|
+
description: "One drifted record, as something to judge: its claim, each anchor's drift class, the old-vs-new span diff, and the records that depend on it. Formatting-only drift is dropped. Empty when there is nothing to reassess. Writes: relocates moved anchors, keeping their hash; never verifies, supersedes, or changes standing.",
|
|
4239
|
+
input: import_zod12.z.object({
|
|
4240
|
+
bundlePath,
|
|
4241
|
+
conceptId,
|
|
4242
|
+
repoRoot: REPO_ROOT,
|
|
4243
|
+
withDiff: import_zod12.z.boolean().optional().describe(
|
|
4244
|
+
"Recover each anchor's committed span and render the diff. Reads git history."
|
|
4245
|
+
)
|
|
4246
|
+
}),
|
|
4247
|
+
fromArgv: (argv, path) => {
|
|
4248
|
+
const repoRoot = argvFlag(argv, "--repo-root");
|
|
4249
|
+
return {
|
|
4250
|
+
bundlePath: path,
|
|
4251
|
+
conceptId: argv[1],
|
|
4252
|
+
...repoRoot !== void 0 ? { repoRoot } : {},
|
|
4253
|
+
...argv.includes("--with-diff") ? { withDiff: true } : {}
|
|
4254
|
+
};
|
|
4255
|
+
},
|
|
4256
|
+
run: async ({ store, actor }, { bundlePath: path, conceptId: id, repoRoot, withDiff }) => {
|
|
4257
|
+
const root = repoRoot ?? process.cwd();
|
|
4258
|
+
const bundle = await store.list(path);
|
|
4259
|
+
const record = bundle.find((entry) => entry.conceptId === id);
|
|
4260
|
+
if (!record) throw new KbRecordNotFoundError(id);
|
|
4261
|
+
const drift = await store.detectDrift([record], repoRoot);
|
|
4262
|
+
const entries = drift?.get(id) ?? [];
|
|
4263
|
+
if (!entries.some((entry) => entry.state !== "match")) {
|
|
4264
|
+
return { conceptId: id, packet: null, rebaselined: [], cosmetic: 0 };
|
|
4265
|
+
}
|
|
4266
|
+
const standing = adjudicate(bundle, bundle).find(
|
|
4267
|
+
(hit) => hit.record.conceptId === id
|
|
4268
|
+
)?.standing;
|
|
4269
|
+
const impact2 = await store.impact(path, id);
|
|
4270
|
+
const { packet, classified } = await reassessPacket(root, record, entries, {
|
|
4271
|
+
...withDiff ? { withDiff: true } : {},
|
|
4272
|
+
impact: impact2,
|
|
4273
|
+
...standing ? { standing } : {}
|
|
4274
|
+
});
|
|
4275
|
+
const moves = classified.filter((found) => found.class === "moved");
|
|
4276
|
+
let frozen = false;
|
|
4277
|
+
const rebaselined = [];
|
|
4278
|
+
if (moves.length) {
|
|
4279
|
+
const relocated = /* @__PURE__ */ new Map();
|
|
4280
|
+
for (const found of moves) {
|
|
4281
|
+
const to = found.entry.movedTo;
|
|
4282
|
+
if (!to) continue;
|
|
4283
|
+
relocated.set(found.anchor, {
|
|
4284
|
+
...found.anchor,
|
|
4285
|
+
file: to.file,
|
|
4286
|
+
...to.symbol ? { symbol: to.symbol } : {}
|
|
4287
|
+
});
|
|
4288
|
+
rebaselined.push({
|
|
4289
|
+
file: found.anchor.file,
|
|
4290
|
+
...found.anchor.symbol ? { symbol: found.anchor.symbol } : {},
|
|
4291
|
+
toFile: to.file,
|
|
4292
|
+
...to.symbol ? { toSymbol: to.symbol } : {}
|
|
4293
|
+
});
|
|
4294
|
+
}
|
|
4295
|
+
try {
|
|
4296
|
+
await assertBaseNotFrozen(process.cwd(), path);
|
|
4297
|
+
} catch (error) {
|
|
4298
|
+
if (!(error instanceof KbBaseFrozenError)) throw error;
|
|
4299
|
+
frozen = true;
|
|
4300
|
+
}
|
|
4301
|
+
if (!frozen) {
|
|
4302
|
+
await store.updateAnchors(
|
|
4303
|
+
path,
|
|
4304
|
+
id,
|
|
4305
|
+
(record.frontmatter.strauss_anchors ?? []).map(
|
|
4306
|
+
(anchor) => relocated.get(anchor) ?? anchor
|
|
4307
|
+
),
|
|
4308
|
+
actor
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
return {
|
|
4313
|
+
conceptId: id,
|
|
4314
|
+
packet,
|
|
4315
|
+
rebaselined: frozen ? [] : rebaselined,
|
|
4316
|
+
cosmetic: classified.filter((found) => found.class === "cosmetic").length,
|
|
4317
|
+
...frozen ? {
|
|
4318
|
+
frozen: true,
|
|
4319
|
+
note: "base is frozen: nothing was rebaselined"
|
|
4320
|
+
} : {}
|
|
4321
|
+
};
|
|
4322
|
+
},
|
|
4323
|
+
render: (result) => renderReassess(result)
|
|
4324
|
+
});
|
|
4325
|
+
function renderReassess(result) {
|
|
4326
|
+
const lines = [];
|
|
4327
|
+
for (const move of result.rebaselined) {
|
|
4328
|
+
lines.push(
|
|
4329
|
+
`rebaselined: ${at(move.file, move.symbol)} \u2192 ${at(move.toFile, move.toSymbol)} (same code, new address)`
|
|
4330
|
+
);
|
|
4331
|
+
}
|
|
4332
|
+
if (result.cosmetic) {
|
|
4333
|
+
lines.push(
|
|
4334
|
+
`${result.cosmetic} anchor${result.cosmetic === 1 ? "" : "s"} changed formatting only.`
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
if (result.note) lines.push(result.note);
|
|
4338
|
+
const packet = result.packet;
|
|
4339
|
+
if (!packet) {
|
|
4340
|
+
lines.push(`${result.conceptId}: nothing to reassess.`);
|
|
4341
|
+
return lines.join("\n");
|
|
4342
|
+
}
|
|
4343
|
+
lines.push(
|
|
4344
|
+
"",
|
|
4345
|
+
`# ${packet.conceptId}${packet.title ? ` \u2014 ${packet.title}` : ""}`,
|
|
4346
|
+
`type: ${packet.type} standing: ${packet.standing}`,
|
|
4347
|
+
...packet.why ? [`why: ${packet.why}`] : [],
|
|
4348
|
+
...packet.claim ? ["", `## ${packet.claim.section}`, packet.claim.text] : [],
|
|
4349
|
+
"",
|
|
4350
|
+
`## Anchors (${packet.anchors.length})`
|
|
4351
|
+
);
|
|
4352
|
+
for (const anchor of packet.anchors) {
|
|
4353
|
+
lines.push(
|
|
4354
|
+
`- ${at(anchor.file, anchor.symbol)} \u2014 ${anchor.class}${anchor.reason ? ` (${anchor.reason})` : ""}`
|
|
4355
|
+
);
|
|
4356
|
+
if (!anchor.diff) continue;
|
|
4357
|
+
if (anchor.diff.status === "unrecoverable") {
|
|
4358
|
+
lines.push(
|
|
4359
|
+
" diff: unrecoverable \u2014 no committed span to compare against"
|
|
4360
|
+
);
|
|
4361
|
+
continue;
|
|
4362
|
+
}
|
|
4363
|
+
lines.push(
|
|
4364
|
+
` diff vs ${anchor.diff.ref} (${anchor.diff.source}): +${anchor.diff.added} \u2212${anchor.diff.removed}`,
|
|
4365
|
+
...anchor.diff.unified.split("\n").map((line) => ` ${line}`)
|
|
4366
|
+
);
|
|
4367
|
+
}
|
|
4368
|
+
if (packet.impact.length) {
|
|
4369
|
+
lines.push("", `## Impact (${packet.impact.length})`);
|
|
4370
|
+
for (const entry of packet.impact) {
|
|
4371
|
+
lines.push(
|
|
4372
|
+
`- ${entry.conceptId} [${entry.standing}]${entry.title ? ` \u2014 ${entry.title}` : ""}`
|
|
4373
|
+
);
|
|
4374
|
+
}
|
|
4375
|
+
if (packet.impactTruncated) lines.push("- \u2026 walk truncated");
|
|
4376
|
+
}
|
|
4377
|
+
lines.push("", `Default: ${packet.default} \u2014 ${packet.defaultNote}.`);
|
|
4378
|
+
return lines.join("\n");
|
|
4379
|
+
}
|
|
4380
|
+
function at(file, symbol) {
|
|
4381
|
+
return symbol ? `${file}:${symbol}` : file;
|
|
4382
|
+
}
|
|
4383
|
+
|
|
3718
4384
|
// src/commands/doctor.ts
|
|
3719
|
-
var days = (what, fallback) =>
|
|
4385
|
+
var days = (what, fallback) => import_zod13.z.number().int().positive().optional().describe(`${what} Defaults to ${fallback}.`);
|
|
3720
4386
|
var doctorCommand = define({
|
|
3721
4387
|
name: "doctor",
|
|
3722
4388
|
tool: "kb_doctor",
|
|
3723
|
-
usage: "doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--repo-root PATH] [--offline] [--strict]",
|
|
3724
|
-
description: "Read-only health sweep: expired, expiring, unverified, aging, orphaned, broken-supersession, superseded-but-cited, drifted and unchecked anchors. Every group is reported even when empty; nothing is written or re-stamped.
|
|
3725
|
-
input:
|
|
4389
|
+
usage: "doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--repo-root PATH] [--offline] [--strict] [--drifted [--with-diff]]",
|
|
4390
|
+
description: "Read-only health sweep: expired, expiring, unverified, aging, orphaned, broken-supersession, superseded-but-cited, drifted and unchecked anchors. Every group is reported even when empty; nothing is written or re-stamped. `drifted` narrows it to a reassessment packet per drifted record, `with_diff` adding each anchor's old-vs-new span.",
|
|
4391
|
+
input: import_zod13.z.object({
|
|
3726
4392
|
bundlePath,
|
|
3727
4393
|
repoRoot: REPO_ROOT,
|
|
3728
4394
|
expiringDays: days(
|
|
@@ -3737,11 +4403,17 @@ var doctorCommand = define({
|
|
|
3737
4403
|
"How long a record may stay `open` or `proposed` before `aging` reports it, in days.",
|
|
3738
4404
|
DEFAULT_AGING_DAYS
|
|
3739
4405
|
),
|
|
3740
|
-
offline:
|
|
4406
|
+
offline: import_zod13.z.boolean().optional().describe(
|
|
3741
4407
|
"Read foreign anchors from the local repo cache only, never fetching."
|
|
3742
4408
|
),
|
|
3743
|
-
strict:
|
|
4409
|
+
strict: import_zod13.z.boolean().optional().describe(
|
|
3744
4410
|
"Turn an expired record into a non-zero exit for the CLI. No effect on the report itself."
|
|
4411
|
+
),
|
|
4412
|
+
drifted: import_zod13.z.boolean().optional().describe(
|
|
4413
|
+
"Report only drift, as a reassessment packet per record: claim, per-anchor class, and what depends on it."
|
|
4414
|
+
),
|
|
4415
|
+
withDiff: import_zod13.z.boolean().optional().describe(
|
|
4416
|
+
"With `drifted`: recover each anchor's committed span and render the old-vs-new diff. Reads git history."
|
|
3745
4417
|
)
|
|
3746
4418
|
}),
|
|
3747
4419
|
// Presence, not truthiness: `--expiring-days ""` is a caller who meant
|
|
@@ -3760,7 +4432,9 @@ var doctorCommand = define({
|
|
|
3760
4432
|
...unverified2 !== void 0 ? { unverifiedDays: Number(unverified2) } : {},
|
|
3761
4433
|
...agingDays !== void 0 ? { agingDays: Number(agingDays) } : {},
|
|
3762
4434
|
...argv.includes("--offline") ? { offline: true } : {},
|
|
3763
|
-
...argv.includes("--strict") ? { strict: true } : {}
|
|
4435
|
+
...argv.includes("--strict") ? { strict: true } : {},
|
|
4436
|
+
...argv.includes("--drifted") ? { drifted: true } : {},
|
|
4437
|
+
...argv.includes("--with-diff") ? { withDiff: true } : {}
|
|
3764
4438
|
};
|
|
3765
4439
|
},
|
|
3766
4440
|
run: async ({ store, now }, {
|
|
@@ -3769,7 +4443,9 @@ var doctorCommand = define({
|
|
|
3769
4443
|
unverifiedDays,
|
|
3770
4444
|
agingDays,
|
|
3771
4445
|
repoRoot,
|
|
3772
|
-
offline
|
|
4446
|
+
offline,
|
|
4447
|
+
drifted: drifted2,
|
|
4448
|
+
withDiff
|
|
3773
4449
|
}) => {
|
|
3774
4450
|
const checkedAt = now();
|
|
3775
4451
|
const records = await store.list(path);
|
|
@@ -3784,10 +4460,51 @@ var doctorCommand = define({
|
|
|
3784
4460
|
now: new Date(checkedAt)
|
|
3785
4461
|
});
|
|
3786
4462
|
const hints = grammarHints();
|
|
4463
|
+
if (!drifted2) {
|
|
4464
|
+
return {
|
|
4465
|
+
bundlePath: path,
|
|
4466
|
+
checkedAt,
|
|
4467
|
+
...report,
|
|
4468
|
+
...hints.length ? { hints } : {}
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4471
|
+
const standings = new Map(
|
|
4472
|
+
adjudicate(records, records, new Date(checkedAt)).map((hit) => [
|
|
4473
|
+
hit.record.conceptId,
|
|
4474
|
+
hit.standing
|
|
4475
|
+
])
|
|
4476
|
+
);
|
|
4477
|
+
const packets = [];
|
|
4478
|
+
const rebaselinable = [];
|
|
4479
|
+
const search = movedSearch(repoRoot ?? process.cwd());
|
|
4480
|
+
for (const found of report.groups.find((g) => g.check === "drifted")?.findings ?? []) {
|
|
4481
|
+
const record = records.find(
|
|
4482
|
+
(entry) => entry.conceptId === found.conceptId
|
|
4483
|
+
);
|
|
4484
|
+
if (!record) continue;
|
|
4485
|
+
const standing = standings.get(record.conceptId);
|
|
4486
|
+
const built = await reassessPacket(
|
|
4487
|
+
repoRoot ?? process.cwd(),
|
|
4488
|
+
record,
|
|
4489
|
+
anchorDrift?.get(record.conceptId) ?? [],
|
|
4490
|
+
{
|
|
4491
|
+
...withDiff ? { withDiff: true } : {},
|
|
4492
|
+
impact: await store.impact(path, record.conceptId),
|
|
4493
|
+
...standing ? { standing } : {},
|
|
4494
|
+
search
|
|
4495
|
+
}
|
|
4496
|
+
);
|
|
4497
|
+
if (built.packet) packets.push(built.packet);
|
|
4498
|
+
if (built.classified.some((entry) => entry.class === "moved")) {
|
|
4499
|
+
rebaselinable.push(record.conceptId);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
3787
4502
|
return {
|
|
3788
4503
|
bundlePath: path,
|
|
3789
4504
|
checkedAt,
|
|
3790
4505
|
...report,
|
|
4506
|
+
packets,
|
|
4507
|
+
rebaselinable,
|
|
3791
4508
|
...hints.length ? { hints } : {}
|
|
3792
4509
|
};
|
|
3793
4510
|
},
|
|
@@ -3801,6 +4518,7 @@ var doctorCommand = define({
|
|
|
3801
4518
|
failsWhen: (result, input) => input.strict === true && result.counts.expired > 0
|
|
3802
4519
|
});
|
|
3803
4520
|
function render2(result) {
|
|
4521
|
+
if (result.packets) return renderPackets(result);
|
|
3804
4522
|
const { thresholds } = result;
|
|
3805
4523
|
const lines = [
|
|
3806
4524
|
`# KB Doctor \u2014 ${result.bundlePath}`,
|
|
@@ -3834,21 +4552,45 @@ function render2(result) {
|
|
|
3834
4552
|
);
|
|
3835
4553
|
return lines.join("\n");
|
|
3836
4554
|
}
|
|
4555
|
+
function renderPackets(result) {
|
|
4556
|
+
const packets = result.packets ?? [];
|
|
4557
|
+
const lines = [
|
|
4558
|
+
`# KB Drift \u2014 ${result.bundlePath}`,
|
|
4559
|
+
`checked: ${result.checkedAt}`,
|
|
4560
|
+
`${packets.length} record${packets.length === 1 ? "" : "s"} need a reading; ${result.counts.drifted} drifted in all.`
|
|
4561
|
+
];
|
|
4562
|
+
if (result.rebaselinable?.length) {
|
|
4563
|
+
lines.push(
|
|
4564
|
+
`moved, rebaseline with \`kb_reassess\`: ${result.rebaselinable.join(", ")}`
|
|
4565
|
+
);
|
|
4566
|
+
}
|
|
4567
|
+
for (const packet of packets) {
|
|
4568
|
+
lines.push(
|
|
4569
|
+
renderReassess({
|
|
4570
|
+
conceptId: packet.conceptId,
|
|
4571
|
+
packet,
|
|
4572
|
+
rebaselined: [],
|
|
4573
|
+
cosmetic: 0
|
|
4574
|
+
})
|
|
4575
|
+
);
|
|
4576
|
+
}
|
|
4577
|
+
return lines.join("\n");
|
|
4578
|
+
}
|
|
3837
4579
|
|
|
3838
4580
|
// src/commands/impact.ts
|
|
3839
|
-
var
|
|
4581
|
+
var import_zod14 = require("zod");
|
|
3840
4582
|
var impactCommand = define({
|
|
3841
4583
|
name: "impact",
|
|
3842
4584
|
tool: "kb_impact",
|
|
3843
4585
|
usage: "impact <concept-id> [--depth N] [--rels a,b]",
|
|
3844
4586
|
description: "What breaks if this record changes: its transitive set of dependants, each with its standing. Each rel declares which of its ends depends on the other, and the walk follows each rel in its own direction. Naming `related_to` or an unknown rel in `rels` is an error. kb_backlinks gives one flat hop.",
|
|
3845
|
-
input:
|
|
4587
|
+
input: import_zod14.z.object({
|
|
3846
4588
|
bundlePath,
|
|
3847
4589
|
conceptId,
|
|
3848
|
-
depth:
|
|
4590
|
+
depth: import_zod14.z.number().int().positive().optional().describe(
|
|
3849
4591
|
"Hops out from the record. Unbounded when omitted; a walk this cuts reports truncated: true."
|
|
3850
4592
|
),
|
|
3851
|
-
rels:
|
|
4593
|
+
rels: import_zod14.z.array(import_zod14.z.enum(KB_CAUSAL_LINK_RELS)).optional().describe(
|
|
3852
4594
|
"Narrow which rels the walk follows. Defaults to every rel that carries a dependence \u2014 all but related_to."
|
|
3853
4595
|
)
|
|
3854
4596
|
}),
|
|
@@ -3869,13 +4611,13 @@ var impactCommand = define({
|
|
|
3869
4611
|
});
|
|
3870
4612
|
|
|
3871
4613
|
// src/commands/list.ts
|
|
3872
|
-
var
|
|
4614
|
+
var import_zod15 = require("zod");
|
|
3873
4615
|
var listCommand = define({
|
|
3874
4616
|
name: "list",
|
|
3875
4617
|
tool: "kb_list",
|
|
3876
4618
|
usage: "list [type]",
|
|
3877
4619
|
description: "Every record, optionally one type. For enumerating; use kb_query for a question.",
|
|
3878
|
-
input:
|
|
4620
|
+
input: import_zod15.z.object({ bundlePath, type: import_zod15.z.enum(KB_RECORD_TYPES).optional() }),
|
|
3879
4621
|
fromArgv: (argv, path) => ({ bundlePath: path, type: argv[1] }),
|
|
3880
4622
|
run: async ({ store }, { bundlePath: path, type }) => (await store.list(path, type)).map((record) => ({
|
|
3881
4623
|
conceptId: record.conceptId,
|
|
@@ -3887,17 +4629,17 @@ var listCommand = define({
|
|
|
3887
4629
|
});
|
|
3888
4630
|
|
|
3889
4631
|
// src/commands/load.ts
|
|
3890
|
-
var
|
|
4632
|
+
var import_zod16 = require("zod");
|
|
3891
4633
|
var loadCommand = define({
|
|
3892
4634
|
name: "load",
|
|
3893
4635
|
tool: "kb_load",
|
|
3894
4636
|
usage: "load [type] [--budget N | --all] [--repo-root PATH]",
|
|
3895
4637
|
description: "Load the whole base, each record with its standing \u2014 call it first, at the point of use, since compaction drops it. Superseded records arrive as stubs; kb_trace has the history. Over budget it refuses: kb_catalog, then kb_pack, or narrow with `type`; `all` bypasses. Never read record files directly \u2014 only kb_* tools resolve supersession. `digest` stamps the base's content, so hooks know when to reload.",
|
|
3896
|
-
input:
|
|
4638
|
+
input: import_zod16.z.object({
|
|
3897
4639
|
bundlePath,
|
|
3898
|
-
type:
|
|
3899
|
-
budgetTokens:
|
|
3900
|
-
all:
|
|
4640
|
+
type: import_zod16.z.enum(KB_RECORD_TYPES).optional(),
|
|
4641
|
+
budgetTokens: import_zod16.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
|
|
4642
|
+
all: import_zod16.z.boolean().optional().describe(
|
|
3901
4643
|
"Loads the entire base regardless of size, bypassing the token budget; mutually exclusive with budgetTokens."
|
|
3902
4644
|
),
|
|
3903
4645
|
repoRoot: REPO_ROOT
|
|
@@ -3939,25 +4681,25 @@ var loadCommand = define({
|
|
|
3939
4681
|
});
|
|
3940
4682
|
|
|
3941
4683
|
// src/commands/log.ts
|
|
3942
|
-
var
|
|
4684
|
+
var import_zod17 = require("zod");
|
|
3943
4685
|
var logCommand = define({
|
|
3944
4686
|
name: "log",
|
|
3945
4687
|
tool: "kb_log",
|
|
3946
4688
|
usage: "log",
|
|
3947
4689
|
description: "Who touched what, and when. Append-only; malformed lines are reported, never repaired.",
|
|
3948
|
-
input:
|
|
4690
|
+
input: import_zod17.z.object({ bundlePath }),
|
|
3949
4691
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3950
4692
|
run: ({ store }, { bundlePath: path }) => store.readLog(path)
|
|
3951
4693
|
});
|
|
3952
4694
|
|
|
3953
4695
|
// src/commands/no-decision.ts
|
|
3954
|
-
var
|
|
4696
|
+
var import_zod18 = require("zod");
|
|
3955
4697
|
var noDecisionCommand = define({
|
|
3956
4698
|
name: "no-decision",
|
|
3957
4699
|
tool: "kb_no_decision",
|
|
3958
4700
|
usage: "no-decision <reason...>",
|
|
3959
4701
|
description: "Record in one sentence that a piece of work had nothing to decide. Idempotent.",
|
|
3960
|
-
input:
|
|
4702
|
+
input: import_zod18.z.object({ bundlePath, reason: import_zod18.z.string().min(1) }),
|
|
3961
4703
|
fromArgv: (argv, path) => ({
|
|
3962
4704
|
bundlePath: path,
|
|
3963
4705
|
reason: argv.slice(1).join(" ").trim()
|
|
@@ -3974,20 +4716,20 @@ var noDecisionCommand = define({
|
|
|
3974
4716
|
});
|
|
3975
4717
|
|
|
3976
4718
|
// src/commands/pack.ts
|
|
3977
|
-
var
|
|
4719
|
+
var import_zod19 = require("zod");
|
|
3978
4720
|
var packCommand = define({
|
|
3979
4721
|
name: "pack",
|
|
3980
4722
|
tool: "kb_pack",
|
|
3981
4723
|
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
3982
4724
|
description: "Bounded neighbourhood around one record: within `hops`, ranked, cut to `maxNodes`, with every cut record named under Excluded. Use when the base is over kb_load's budget and the work centres on a record you can name. Refuses over budget rather than truncating. Everything below the header is byte-stable across runs. Resolves supersession like kb_load.",
|
|
3983
|
-
input:
|
|
4725
|
+
input: import_zod19.z.object({
|
|
3984
4726
|
bundlePath,
|
|
3985
4727
|
conceptId,
|
|
3986
|
-
hops:
|
|
3987
|
-
maxNodes:
|
|
4728
|
+
hops: import_zod19.z.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
|
|
4729
|
+
maxNodes: import_zod19.z.number().int().positive().optional().describe(
|
|
3988
4730
|
"How many records the pack may hold, root included. Defaults to 20."
|
|
3989
4731
|
),
|
|
3990
|
-
budgetTokens:
|
|
4732
|
+
budgetTokens: import_zod19.z.number().int().positive().optional().describe(
|
|
3991
4733
|
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
3992
4734
|
)
|
|
3993
4735
|
}),
|
|
@@ -4012,12 +4754,12 @@ var packCommand = define({
|
|
|
4012
4754
|
return render3(result, path, now());
|
|
4013
4755
|
}
|
|
4014
4756
|
});
|
|
4015
|
-
function render3(result, bundle,
|
|
4757
|
+
function render3(result, bundle, at2) {
|
|
4016
4758
|
const lines = [
|
|
4017
4759
|
`# KB Pack \u2014 ${result.root}`,
|
|
4018
4760
|
`bundle: ${bundle}`,
|
|
4019
4761
|
`budget: ~${result.tokensLoaded} of ${result.budgetTokens} tokens, ${result.recordCount} records`,
|
|
4020
|
-
`packed: ${
|
|
4762
|
+
`packed: ${at2}`,
|
|
4021
4763
|
"",
|
|
4022
4764
|
`## Records (${result.records.length})`
|
|
4023
4765
|
];
|
|
@@ -4074,22 +4816,22 @@ function warningLabel(warning) {
|
|
|
4074
4816
|
}
|
|
4075
4817
|
|
|
4076
4818
|
// src/commands/pin.ts
|
|
4077
|
-
var
|
|
4819
|
+
var import_zod20 = require("zod");
|
|
4078
4820
|
var pinCommand = define({
|
|
4079
4821
|
name: "pin",
|
|
4080
4822
|
tool: "kb_pin",
|
|
4081
4823
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
4082
4824
|
description: "Pin a base into a workspace manifest so kb_context surfaces it. Layers, nearest wins: project `.strauss/kb-pins.json` (default), `--local` (personal, gitignored), `--user` (`~/.strauss`). Idempotent; `--mode full|index`, `--profiles`, `--frozen`/`--unfreeze` update only those fields. A path with no records pins with a warning. Never touches the base itself.",
|
|
4083
|
-
input:
|
|
4825
|
+
input: import_zod20.z.object({
|
|
4084
4826
|
bundlePath,
|
|
4085
|
-
mode:
|
|
4827
|
+
mode: import_zod20.z.enum(["full", "index"]).optional().describe(
|
|
4086
4828
|
"full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
|
|
4087
4829
|
),
|
|
4088
|
-
profiles:
|
|
4089
|
-
layer:
|
|
4830
|
+
profiles: import_zod20.z.array(import_zod20.z.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
|
|
4831
|
+
layer: import_zod20.z.enum(["project", "local", "user"]).optional().describe(
|
|
4090
4832
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
4091
4833
|
),
|
|
4092
|
-
frozen:
|
|
4834
|
+
frozen: import_zod20.z.boolean().optional().describe(
|
|
4093
4835
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
4094
4836
|
)
|
|
4095
4837
|
}),
|
|
@@ -4118,29 +4860,29 @@ var pinCommand = define({
|
|
|
4118
4860
|
});
|
|
4119
4861
|
|
|
4120
4862
|
// src/commands/pins.ts
|
|
4121
|
-
var
|
|
4863
|
+
var import_zod21 = require("zod");
|
|
4122
4864
|
var pinsCommand = define({
|
|
4123
4865
|
name: "pins",
|
|
4124
4866
|
tool: "kb_pins",
|
|
4125
4867
|
usage: "pins",
|
|
4126
4868
|
description: "Every pinned base across the manifest layers, with its layer and whether it resolves to records. Takes no bundlePath.",
|
|
4127
|
-
input:
|
|
4869
|
+
input: import_zod21.z.object({}),
|
|
4128
4870
|
fromArgv: () => ({}),
|
|
4129
4871
|
run: ({ store }) => listPins(store, process.cwd())
|
|
4130
4872
|
});
|
|
4131
4873
|
|
|
4132
4874
|
// src/commands/query.ts
|
|
4133
|
-
var
|
|
4875
|
+
var import_zod22 = require("zod");
|
|
4134
4876
|
var queryCommand = define({
|
|
4135
4877
|
name: "query",
|
|
4136
4878
|
tool: "kb_query",
|
|
4137
4879
|
usage: "query <text...> [--repo-root PATH]",
|
|
4138
4880
|
description: "Search; every hit carries its standing. Flagged, never filtered: a superseded hit returns with its replacement, a rejected one is marked. Prefer kb_load when the base fits its budget \u2014 a full read beats search. Results are volatile: place them at the tail, not the cached prefix. Never read record files directly.",
|
|
4139
|
-
input:
|
|
4881
|
+
input: import_zod22.z.object({
|
|
4140
4882
|
bundlePath,
|
|
4141
|
-
text:
|
|
4142
|
-
type:
|
|
4143
|
-
includeNonCurrent:
|
|
4883
|
+
text: import_zod22.z.string().optional(),
|
|
4884
|
+
type: import_zod22.z.enum(KB_RECORD_TYPES).optional(),
|
|
4885
|
+
includeNonCurrent: import_zod22.z.boolean().optional(),
|
|
4144
4886
|
repoRoot: REPO_ROOT
|
|
4145
4887
|
}),
|
|
4146
4888
|
// `--repo-root` is a flag, so its value must not fall into the search text.
|
|
@@ -4172,27 +4914,27 @@ var queryCommand = define({
|
|
|
4172
4914
|
});
|
|
4173
4915
|
|
|
4174
4916
|
// src/commands/read-index.ts
|
|
4175
|
-
var
|
|
4917
|
+
var import_zod23 = require("zod");
|
|
4176
4918
|
var readIndexCommand = define({
|
|
4177
4919
|
name: "index",
|
|
4178
4920
|
tool: "kb_index",
|
|
4179
4921
|
usage: "index",
|
|
4180
4922
|
description: "The index \u2014 title, type, status, description per record \u2014 rebuilt if stale. Cheapest re-orientation after compaction: call it (or kb_context) first, then kb_load or fetch by id.",
|
|
4181
|
-
input:
|
|
4923
|
+
input: import_zod23.z.object({ bundlePath }),
|
|
4182
4924
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
4183
4925
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
4184
4926
|
});
|
|
4185
4927
|
|
|
4186
4928
|
// src/commands/schema.ts
|
|
4187
|
-
var
|
|
4929
|
+
var import_zod26 = require("zod");
|
|
4188
4930
|
|
|
4189
4931
|
// src/json-schema.ts
|
|
4190
|
-
var
|
|
4932
|
+
var import_zod25 = require("zod");
|
|
4191
4933
|
|
|
4192
4934
|
// src/kb-log.ts
|
|
4193
|
-
var
|
|
4935
|
+
var import_zod24 = require("zod");
|
|
4194
4936
|
var LOG_FILE = "log.jsonl";
|
|
4195
|
-
var kbLogEntrySchema =
|
|
4937
|
+
var kbLogEntrySchema = import_zod24.z.object({
|
|
4196
4938
|
// Validated, not just `min(1)`: `at` is a sort key (see `parseLog`
|
|
4197
4939
|
// below), and a value that isn't actually chronological — a Unix
|
|
4198
4940
|
// timestamp, a human-typed date, garbage — would sort wrong without
|
|
@@ -4201,12 +4943,12 @@ var kbLogEntrySchema = import_zod23.z.object({
|
|
|
4201
4943
|
// and rejects everything else, including a non-`Z` offset — so a
|
|
4202
4944
|
// malformed `at` is reported the same way a malformed line already is,
|
|
4203
4945
|
// rather than silently sorting into the wrong place.
|
|
4204
|
-
at:
|
|
4205
|
-
by:
|
|
4206
|
-
operation:
|
|
4207
|
-
conceptId:
|
|
4946
|
+
at: import_zod24.z.iso.datetime(),
|
|
4947
|
+
by: import_zod24.z.string().min(1),
|
|
4948
|
+
operation: import_zod24.z.string().min(1),
|
|
4949
|
+
conceptId: import_zod24.z.string().min(1),
|
|
4208
4950
|
/** Second concept id, where the operation relates two — supersession. */
|
|
4209
|
-
target:
|
|
4951
|
+
target: import_zod24.z.string().min(1).optional()
|
|
4210
4952
|
}).strict();
|
|
4211
4953
|
function renderLogEntry(entry) {
|
|
4212
4954
|
return `${JSON.stringify(kbLogEntrySchema.parse(entry))}
|
|
@@ -4244,11 +4986,11 @@ function parseLog(raw) {
|
|
|
4244
4986
|
// src/json-schema.ts
|
|
4245
4987
|
function kbJsonSchemas() {
|
|
4246
4988
|
return {
|
|
4247
|
-
recordFrontmatter:
|
|
4989
|
+
recordFrontmatter: import_zod25.z.toJSONSchema(kbRecordFrontmatterSchema, {
|
|
4248
4990
|
io: "input"
|
|
4249
4991
|
}),
|
|
4250
|
-
composeInput:
|
|
4251
|
-
logEntry:
|
|
4992
|
+
composeInput: import_zod25.z.toJSONSchema(composeInputSchema, { io: "input" }),
|
|
4993
|
+
logEntry: import_zod25.z.toJSONSchema(kbLogEntrySchema, { io: "input" })
|
|
4252
4994
|
};
|
|
4253
4995
|
}
|
|
4254
4996
|
|
|
@@ -4258,25 +5000,25 @@ var schemaCommand = define({
|
|
|
4258
5000
|
tool: "kb_schema",
|
|
4259
5001
|
usage: "schema",
|
|
4260
5002
|
description: "JSON Schema for frontmatter, write input, and log entries, generated from the enforcing code.",
|
|
4261
|
-
input:
|
|
5003
|
+
input: import_zod26.z.object({}),
|
|
4262
5004
|
fromArgv: () => ({}),
|
|
4263
5005
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
4264
5006
|
});
|
|
4265
5007
|
|
|
4266
5008
|
// src/commands/stamp.ts
|
|
4267
|
-
var
|
|
4268
|
-
var
|
|
5009
|
+
var import_promises8 = require("fs/promises");
|
|
5010
|
+
var import_zod27 = require("zod");
|
|
4269
5011
|
var DIGEST = /^[0-9a-f]{64}$/;
|
|
4270
5012
|
var stampCommand = define({
|
|
4271
5013
|
name: "stamp",
|
|
4272
5014
|
tool: "kb_stamp",
|
|
4273
5015
|
usage: "stamp [--bundle PATH] [--since DIGEST|FILE]",
|
|
4274
|
-
description: "Content stamp of a base \u2014 `load`'s digest, record counts, per-record digests \u2014 without any bodies. Takes no bundlePath to stamp every pinned base. With `since`, reports only the bases that moved, naming the changed ids
|
|
4275
|
-
input:
|
|
4276
|
-
bundlePath:
|
|
5016
|
+
description: "Content stamp of a base \u2014 `load`'s digest, record counts, per-record digests, how many records have drifted anchors \u2014 without any bodies. Takes no bundlePath to stamp every pinned base. With `since`, reports only the bases that moved, naming the changed ids. Reads, never writes.",
|
|
5017
|
+
input: import_zod27.z.object({
|
|
5018
|
+
bundlePath: import_zod27.z.string().min(1).optional().describe(
|
|
4277
5019
|
"Absolute path to one knowledge base. Omit to stamp every pinned base."
|
|
4278
5020
|
),
|
|
4279
|
-
since:
|
|
5021
|
+
since: import_zod27.z.string().min(1).optional().describe(
|
|
4280
5022
|
"Prior digest, or path to a prior `stamp --json`; only moved bases return, with changed ids when the baseline is a file."
|
|
4281
5023
|
)
|
|
4282
5024
|
}),
|
|
@@ -4315,7 +5057,7 @@ var stampCommand = define({
|
|
|
4315
5057
|
return reports;
|
|
4316
5058
|
},
|
|
4317
5059
|
render: (result) => result.map((report) => {
|
|
4318
|
-
const counts = `${report.recordCount} record(s), ${report.superseded} superseded`;
|
|
5060
|
+
const counts = `${report.recordCount} record(s), ${report.superseded} superseded${report.drifted ? `, ${report.drifted} drifted` : ""}`;
|
|
4319
5061
|
const head = `${report.path} ${report.digest} ${counts}${report.newestAt ? ` newest ${report.newestAt}` : ""}`;
|
|
4320
5062
|
return report.changed?.length ? `${head}
|
|
4321
5063
|
changed: ${report.changed.join(", ")}` : head;
|
|
@@ -4338,7 +5080,7 @@ async function readBaseline(since) {
|
|
|
4338
5080
|
if (DIGEST.test(since)) return { digest: since, byPath: /* @__PURE__ */ new Map() };
|
|
4339
5081
|
let parsed;
|
|
4340
5082
|
try {
|
|
4341
|
-
parsed = JSON.parse(await (0,
|
|
5083
|
+
parsed = JSON.parse(await (0, import_promises8.readFile)(since, "utf8"));
|
|
4342
5084
|
} catch {
|
|
4343
5085
|
throw new KbStampBaselineError(since);
|
|
4344
5086
|
}
|
|
@@ -4362,16 +5104,16 @@ async function readBaseline(since) {
|
|
|
4362
5104
|
}
|
|
4363
5105
|
|
|
4364
5106
|
// src/commands/status.ts
|
|
4365
|
-
var
|
|
5107
|
+
var import_zod28 = require("zod");
|
|
4366
5108
|
var statusCommand = define({
|
|
4367
5109
|
name: "status",
|
|
4368
5110
|
tool: "kb_status",
|
|
4369
5111
|
usage: "status <concept-id> <status>",
|
|
4370
5112
|
description: "Move a record's status. Compare-and-swap: a concurrent change fails instead of being overwritten.",
|
|
4371
|
-
input:
|
|
5113
|
+
input: import_zod28.z.object({
|
|
4372
5114
|
bundlePath,
|
|
4373
5115
|
conceptId,
|
|
4374
|
-
status:
|
|
5116
|
+
status: import_zod28.z.enum(KB_RECORD_STATUSES)
|
|
4375
5117
|
}),
|
|
4376
5118
|
fromArgv: (argv, path) => ({
|
|
4377
5119
|
bundlePath: path,
|
|
@@ -4386,13 +5128,13 @@ var statusCommand = define({
|
|
|
4386
5128
|
});
|
|
4387
5129
|
|
|
4388
5130
|
// src/commands/supersede.ts
|
|
4389
|
-
var
|
|
5131
|
+
var import_zod29 = require("zod");
|
|
4390
5132
|
var supersedeCommand = define({
|
|
4391
5133
|
name: "supersede",
|
|
4392
5134
|
tool: "kb_supersede",
|
|
4393
5135
|
usage: "supersede <concept-id> <replacement-id>",
|
|
4394
5136
|
description: "Mark a record superseded by another, linked in both directions. Use instead of editing a record whose meaning changed.",
|
|
4395
|
-
input:
|
|
5137
|
+
input: import_zod29.z.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
4396
5138
|
fromArgv: (argv, path) => ({
|
|
4397
5139
|
bundlePath: path,
|
|
4398
5140
|
conceptId: argv[1],
|
|
@@ -4406,16 +5148,16 @@ var supersedeCommand = define({
|
|
|
4406
5148
|
});
|
|
4407
5149
|
|
|
4408
5150
|
// src/commands/sync-instructions.ts
|
|
4409
|
-
var
|
|
5151
|
+
var import_zod30 = require("zod");
|
|
4410
5152
|
var syncInstructionsCommand = define({
|
|
4411
5153
|
name: "sync-instructions",
|
|
4412
5154
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
4413
5155
|
description: "CLI-only: plant the kb_context block between sentinel comments in AGENTS.md or CLAUDE.md, idempotently.",
|
|
4414
|
-
input:
|
|
4415
|
-
file:
|
|
4416
|
-
budgetTokens:
|
|
4417
|
-
fullUnderTokens:
|
|
4418
|
-
profile:
|
|
5156
|
+
input: import_zod30.z.object({
|
|
5157
|
+
file: import_zod30.z.string().min(1).describe("The instruction file to edit in place."),
|
|
5158
|
+
budgetTokens: import_zod30.z.number().int().positive().optional(),
|
|
5159
|
+
fullUnderTokens: import_zod30.z.number().int().positive().optional(),
|
|
5160
|
+
profile: import_zod30.z.string().optional()
|
|
4419
5161
|
}),
|
|
4420
5162
|
fromArgv: (argv) => {
|
|
4421
5163
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -4441,7 +5183,7 @@ var syncInstructionsCommand = define({
|
|
|
4441
5183
|
});
|
|
4442
5184
|
|
|
4443
5185
|
// src/commands/trace.ts
|
|
4444
|
-
var
|
|
5186
|
+
var import_zod31 = require("zod");
|
|
4445
5187
|
|
|
4446
5188
|
// src/trace.ts
|
|
4447
5189
|
var TRACE_EDGES = [
|
|
@@ -4487,8 +5229,8 @@ function trace(seedId, bundle, options = {}) {
|
|
|
4487
5229
|
return [...reached.values()].sort(byGeneratedAt);
|
|
4488
5230
|
}
|
|
4489
5231
|
function byGeneratedAt(left, right) {
|
|
4490
|
-
const
|
|
4491
|
-
return
|
|
5232
|
+
const at2 = (step) => step.record.frontmatter.generated?.at ?? "";
|
|
5233
|
+
return at2(left).localeCompare(at2(right)) || left.depth - right.depth;
|
|
4492
5234
|
}
|
|
4493
5235
|
|
|
4494
5236
|
// src/commands/trace.ts
|
|
@@ -4497,11 +5239,11 @@ var traceCommand = define({
|
|
|
4497
5239
|
tool: "kb_trace",
|
|
4498
5240
|
usage: "trace <concept-id> [edges...]",
|
|
4499
5241
|
description: 'Timeline of how a position was reached, ordered by write time, following supersession, shared anchors and shared sources. Includes rejected, draft and superseded records \u2014 in a history they are the content. For "why is it like this"; kb_load answers "what holds now".',
|
|
4500
|
-
input:
|
|
5242
|
+
input: import_zod31.z.object({
|
|
4501
5243
|
bundlePath,
|
|
4502
5244
|
conceptId,
|
|
4503
|
-
edges:
|
|
4504
|
-
depth:
|
|
5245
|
+
edges: import_zod31.z.array(import_zod31.z.enum(TRACE_EDGES)).optional(),
|
|
5246
|
+
depth: import_zod31.z.number().int().positive().optional()
|
|
4505
5247
|
}),
|
|
4506
5248
|
fromArgv: (argv, path) => ({
|
|
4507
5249
|
bundlePath: path,
|
|
@@ -4523,37 +5265,37 @@ var traceCommand = define({
|
|
|
4523
5265
|
});
|
|
4524
5266
|
|
|
4525
5267
|
// src/commands/types.ts
|
|
4526
|
-
var
|
|
5268
|
+
var import_zod32 = require("zod");
|
|
4527
5269
|
var typesCommand = define({
|
|
4528
5270
|
name: "types",
|
|
4529
5271
|
tool: "kb_types",
|
|
4530
5272
|
usage: "types",
|
|
4531
5273
|
description: "The twelve record types with their purpose, body sections, and starting status. Read this before writing rather than guessing headings \u2014 a section the type does not define is rejected.",
|
|
4532
|
-
input:
|
|
5274
|
+
input: import_zod32.z.object({}),
|
|
4533
5275
|
fromArgv: () => ({}),
|
|
4534
5276
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
4535
5277
|
});
|
|
4536
5278
|
|
|
4537
5279
|
// src/commands/unpin.ts
|
|
4538
|
-
var
|
|
5280
|
+
var import_zod33 = require("zod");
|
|
4539
5281
|
var unpinCommand = define({
|
|
4540
5282
|
name: "unpin",
|
|
4541
5283
|
tool: "kb_unpin",
|
|
4542
5284
|
usage: "unpin [bundle-path]",
|
|
4543
5285
|
description: "Remove a base from every manifest layer that holds it. Reports the layers touched.",
|
|
4544
|
-
input:
|
|
5286
|
+
input: import_zod33.z.object({ bundlePath }),
|
|
4545
5287
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
4546
5288
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
4547
5289
|
});
|
|
4548
5290
|
|
|
4549
5291
|
// src/commands/validate.ts
|
|
4550
|
-
var
|
|
5292
|
+
var import_zod34 = require("zod");
|
|
4551
5293
|
var validateCommand = define({
|
|
4552
5294
|
name: "validate",
|
|
4553
5295
|
tool: "kb_validate",
|
|
4554
5296
|
usage: "validate",
|
|
4555
5297
|
description: "Check pointers no single record can see: supersession links that disagree between the two records, typed causal links, and assumptions that cite sources. Each finding carries a severity: errors fail the exit code, warnings do not.",
|
|
4556
|
-
input:
|
|
5298
|
+
input: import_zod34.z.object({ bundlePath }),
|
|
4557
5299
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
4558
5300
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
4559
5301
|
// Warnings never fail the exit code; every other severity does.
|
|
@@ -4563,16 +5305,16 @@ var validateCommand = define({
|
|
|
4563
5305
|
});
|
|
4564
5306
|
|
|
4565
5307
|
// src/commands/verify.ts
|
|
4566
|
-
var
|
|
5308
|
+
var import_zod35 = require("zod");
|
|
4567
5309
|
var verifyCommand = define({
|
|
4568
5310
|
name: "verify",
|
|
4569
5311
|
tool: "kb_verify",
|
|
4570
5312
|
usage: "verify <concept-id> --note <text>",
|
|
4571
5313
|
description: "Append a verified[] event: who checked, when, and what was found. Append-only. A record's own generator is refused unless the actor is `human:`-prefixed.",
|
|
4572
|
-
input:
|
|
5314
|
+
input: import_zod35.z.object({
|
|
4573
5315
|
bundlePath,
|
|
4574
5316
|
conceptId,
|
|
4575
|
-
note:
|
|
5317
|
+
note: import_zod35.z.string().refine((s) => s.trim().length > 0, {
|
|
4576
5318
|
message: "note must say what the check found"
|
|
4577
5319
|
})
|
|
4578
5320
|
}),
|
|
@@ -4592,15 +5334,15 @@ var verifyCommand = define({
|
|
|
4592
5334
|
});
|
|
4593
5335
|
|
|
4594
5336
|
// src/commands/write.ts
|
|
4595
|
-
var
|
|
5337
|
+
var import_zod36 = require("zod");
|
|
4596
5338
|
var writeCommand = define({
|
|
4597
5339
|
name: "write",
|
|
4598
5340
|
tool: "kb_write",
|
|
4599
5341
|
usage: "write <type> < record.json",
|
|
4600
5342
|
description: "Write one record. Search first \u2014 a duplicate concept id is rejected, not overwritten; kb_types lists each type's sections. An unsourced claim is an `assumption` with assumption: true, never a vague `fact`. Conflicting records get a `risk`, `open-question`, or superseding `decision`. Prefer a new short record over overloading one. Never delete; supersede.",
|
|
4601
|
-
input:
|
|
5343
|
+
input: import_zod36.z.object({
|
|
4602
5344
|
bundlePath,
|
|
4603
|
-
type:
|
|
5345
|
+
type: import_zod36.z.enum(KB_RECORD_TYPES),
|
|
4604
5346
|
input: composeInputSchema
|
|
4605
5347
|
}),
|
|
4606
5348
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -4624,13 +5366,13 @@ var writeCommand = define({
|
|
|
4624
5366
|
});
|
|
4625
5367
|
|
|
4626
5368
|
// src/commands/write-decision.ts
|
|
4627
|
-
var
|
|
5369
|
+
var import_zod37 = require("zod");
|
|
4628
5370
|
var writeDecisionCommand = define({
|
|
4629
5371
|
name: "write-decision",
|
|
4630
5372
|
tool: "kb_write_decision",
|
|
4631
5373
|
usage: "write-decision < decision.json",
|
|
4632
5374
|
description: "Write a decision, with `alternative` (what was rejected and why) and `impact` as fields. Record one when a later reader would otherwise simplify the constraint away; skip when the diff already answers it. `sources` for material read, `anchors` for code, `relatedConceptIds` for records.",
|
|
4633
|
-
input:
|
|
5375
|
+
input: import_zod37.z.object({ bundlePath, input: decisionInputSchema }),
|
|
4634
5376
|
fromArgv: async (_argv, path, stdin) => ({
|
|
4635
5377
|
bundlePath: path,
|
|
4636
5378
|
input: JSON.parse(await stdin())
|
|
@@ -4660,6 +5402,7 @@ var KB_COMMANDS = [
|
|
|
4660
5402
|
answerCommand,
|
|
4661
5403
|
verifyCommand,
|
|
4662
5404
|
anchorResolveCommand,
|
|
5405
|
+
reassessCommand,
|
|
4663
5406
|
loadCommand,
|
|
4664
5407
|
catalogCommand,
|
|
4665
5408
|
packCommand,
|
|
@@ -4686,7 +5429,7 @@ var KB_COMMANDS_BY_NAME = new Map(
|
|
|
4686
5429
|
);
|
|
4687
5430
|
|
|
4688
5431
|
// src/kb-store.ts
|
|
4689
|
-
var
|
|
5432
|
+
var import_promises10 = require("fs/promises");
|
|
4690
5433
|
var import_node_path11 = require("path");
|
|
4691
5434
|
|
|
4692
5435
|
// src/markdown.ts
|
|
@@ -4747,7 +5490,7 @@ function bundleDigest(records, superseded) {
|
|
|
4747
5490
|
}
|
|
4748
5491
|
|
|
4749
5492
|
// src/search-index.ts
|
|
4750
|
-
var
|
|
5493
|
+
var import_promises9 = require("fs/promises");
|
|
4751
5494
|
var import_node_path10 = require("path");
|
|
4752
5495
|
var SEARCH_INDEX_FILE = ".index.sqlite";
|
|
4753
5496
|
var COLLECTION = "kb";
|
|
@@ -4792,7 +5535,7 @@ async function searchBase(bundlePath2, query, options = {}) {
|
|
|
4792
5535
|
}
|
|
4793
5536
|
}
|
|
4794
5537
|
async function isStale(bundlePath2) {
|
|
4795
|
-
const indexAt = await (0,
|
|
5538
|
+
const indexAt = await (0, import_promises9.stat)((0, import_node_path10.join)(bundlePath2, SEARCH_INDEX_FILE)).then((s) => s.mtimeMs).catch(() => 0);
|
|
4796
5539
|
if (!indexAt) return true;
|
|
4797
5540
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
4798
5541
|
const names = (await readdir2(bundlePath2).catch(() => [])).filter(
|
|
@@ -4801,8 +5544,8 @@ async function isStale(bundlePath2) {
|
|
|
4801
5544
|
let stale = false;
|
|
4802
5545
|
await mapLimit(names, DEFAULT_IO_CONCURRENCY, async (name) => {
|
|
4803
5546
|
if (stale) return;
|
|
4804
|
-
const
|
|
4805
|
-
if (
|
|
5547
|
+
const at2 = await (0, import_promises9.stat)((0, import_node_path10.join)(bundlePath2, name)).then((s) => s.mtimeMs).catch(() => 0);
|
|
5548
|
+
if (at2 > indexAt) stale = true;
|
|
4806
5549
|
});
|
|
4807
5550
|
return stale;
|
|
4808
5551
|
}
|
|
@@ -5110,7 +5853,7 @@ var KbStore = class {
|
|
|
5110
5853
|
const conceptId2 = `${input.type}.${input.slug}`;
|
|
5111
5854
|
const root = this.root(bundlePath2);
|
|
5112
5855
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
5113
|
-
await (0,
|
|
5856
|
+
await (0, import_promises10.mkdir)(root, { recursive: true });
|
|
5114
5857
|
await this.publish(
|
|
5115
5858
|
target,
|
|
5116
5859
|
stringifyMarkdownWithFrontmatter(input.body, frontmatter),
|
|
@@ -5149,7 +5892,7 @@ var KbStore = class {
|
|
|
5149
5892
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
5150
5893
|
let raw;
|
|
5151
5894
|
try {
|
|
5152
|
-
raw = await (0,
|
|
5895
|
+
raw = await (0, import_promises10.readFile)(target, "utf8");
|
|
5153
5896
|
} catch {
|
|
5154
5897
|
return null;
|
|
5155
5898
|
}
|
|
@@ -5166,7 +5909,7 @@ var KbStore = class {
|
|
|
5166
5909
|
const root = this.root(bundlePath2);
|
|
5167
5910
|
let names;
|
|
5168
5911
|
try {
|
|
5169
|
-
names = await (0,
|
|
5912
|
+
names = await (0, import_promises10.readdir)(root);
|
|
5170
5913
|
} catch {
|
|
5171
5914
|
return [];
|
|
5172
5915
|
}
|
|
@@ -5174,7 +5917,7 @@ var KbStore = class {
|
|
|
5174
5917
|
const records = await mapLimit(
|
|
5175
5918
|
wanted,
|
|
5176
5919
|
DEFAULT_IO_CONCURRENCY,
|
|
5177
|
-
async ({ name, conceptId: conceptId2 }) => this.parse(conceptId2, await (0,
|
|
5920
|
+
async ({ name, conceptId: conceptId2 }) => this.parse(conceptId2, await (0, import_promises10.readFile)((0, import_node_path11.join)(root, name), "utf8"))
|
|
5178
5921
|
);
|
|
5179
5922
|
return records.filter((record) => record !== null);
|
|
5180
5923
|
}
|
|
@@ -5222,8 +5965,8 @@ var KbStore = class {
|
|
|
5222
5965
|
* and the refusal is logged under its own operation name — `mutate` only
|
|
5223
5966
|
* logs what it publishes.
|
|
5224
5967
|
*/
|
|
5225
|
-
async verify(bundlePath2, conceptId2, note, actor = "unknown",
|
|
5226
|
-
const event = kbVerifiedEventSchema.parse({ by: actor, at, note });
|
|
5968
|
+
async verify(bundlePath2, conceptId2, note, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
5969
|
+
const event = kbVerifiedEventSchema.parse({ by: actor, at: at2, note });
|
|
5227
5970
|
const existing = await this.read(bundlePath2, conceptId2);
|
|
5228
5971
|
if (!existing) throw new KbRecordNotFoundError(conceptId2);
|
|
5229
5972
|
const generatedBy = existing.frontmatter.generated?.by;
|
|
@@ -5275,14 +6018,14 @@ var KbStore = class {
|
|
|
5275
6018
|
return superseded;
|
|
5276
6019
|
}
|
|
5277
6020
|
/** Resolves an open question, stamping who answered and when. */
|
|
5278
|
-
async answer(bundlePath2, conceptId2, answer, actor = "unknown",
|
|
6021
|
+
async answer(bundlePath2, conceptId2, answer, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
5279
6022
|
return this.mutate(
|
|
5280
6023
|
bundlePath2,
|
|
5281
6024
|
conceptId2,
|
|
5282
6025
|
(frontmatter) => ({
|
|
5283
6026
|
...frontmatter,
|
|
5284
6027
|
strauss_status: "resolved",
|
|
5285
|
-
strauss_answered: { by: actor, at }
|
|
6028
|
+
strauss_answered: { by: actor, at: at2 }
|
|
5286
6029
|
}),
|
|
5287
6030
|
{ operation: "answer", by: actor },
|
|
5288
6031
|
(body) => `${body.trimEnd()}
|
|
@@ -5451,24 +6194,34 @@ ${answer}
|
|
|
5451
6194
|
}
|
|
5452
6195
|
/**
|
|
5453
6196
|
* `load`'s digest without `load`'s bodies — the same records, adjudicated
|
|
5454
|
-
* the same way, handed back as a stamp.
|
|
5455
|
-
*
|
|
5456
|
-
*
|
|
6197
|
+
* the same way, handed back as a stamp.
|
|
6198
|
+
*
|
|
6199
|
+
* Drift is counted but kept out of the digest, which is what lets the reload
|
|
6200
|
+
* hook ask one question and get two answers: whether the base moved, and
|
|
6201
|
+
* whether the code under it did. A `load` and a `stamp` of the same base
|
|
6202
|
+
* still agree on the digest, because no warning has ever reached it.
|
|
5457
6203
|
*/
|
|
5458
|
-
async stamp(bundlePath2) {
|
|
6204
|
+
async stamp(bundlePath2, options = {}) {
|
|
5459
6205
|
const bundle = await this.list(bundlePath2);
|
|
5460
6206
|
const adjudicated = adjudicate(bundle, bundle, /* @__PURE__ */ new Date());
|
|
5461
6207
|
const current = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
5462
6208
|
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
5463
6209
|
const stamped = bundleStamp(current, superseded);
|
|
5464
|
-
const dates = bundle.map((record) => record.frontmatter.generated?.at ?? null).filter((
|
|
6210
|
+
const dates = bundle.map((record) => record.frontmatter.generated?.at ?? null).filter((at2) => typeof at2 === "string").sort();
|
|
6211
|
+
const drift = await this.detectDrift(bundle, options.repoRoot);
|
|
6212
|
+
const drifted2 = drift === void 0 ? null : [...drift.values()].filter(
|
|
6213
|
+
(entries) => entries.some(
|
|
6214
|
+
(entry) => entry.state !== "match" && !isUncheckedReason(entry.reason)
|
|
6215
|
+
)
|
|
6216
|
+
).length;
|
|
5465
6217
|
return {
|
|
5466
6218
|
path: bundlePath2,
|
|
5467
6219
|
digest: stamped.digest,
|
|
5468
6220
|
recordCount: bundle.length,
|
|
5469
6221
|
superseded: superseded.length,
|
|
5470
6222
|
newestAt: dates.at(-1) ?? null,
|
|
5471
|
-
records: stamped.records
|
|
6223
|
+
records: stamped.records,
|
|
6224
|
+
drifted: drifted2
|
|
5472
6225
|
};
|
|
5473
6226
|
}
|
|
5474
6227
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
@@ -5501,7 +6254,7 @@ ${answer}
|
|
|
5501
6254
|
async readIndex(bundlePath2) {
|
|
5502
6255
|
const root = this.root(bundlePath2);
|
|
5503
6256
|
const expected = renderIndex(await this.list(bundlePath2));
|
|
5504
|
-
const stored = await (0,
|
|
6257
|
+
const stored = await (0, import_promises10.readFile)((0, import_node_path11.join)(root, INDEX_FILE), "utf8").catch(
|
|
5505
6258
|
() => null
|
|
5506
6259
|
);
|
|
5507
6260
|
if (indexIsStale(stored, expected)) {
|
|
@@ -5522,7 +6275,7 @@ ${answer}
|
|
|
5522
6275
|
* knows which agent touched what. So a bad line is surfaced and left alone.
|
|
5523
6276
|
*/
|
|
5524
6277
|
async readLog(bundlePath2) {
|
|
5525
|
-
const raw = await (0,
|
|
6278
|
+
const raw = await (0, import_promises10.readFile)(
|
|
5526
6279
|
(0, import_node_path11.join)(this.root(bundlePath2), LOG_FILE),
|
|
5527
6280
|
"utf8"
|
|
5528
6281
|
).catch(() => "");
|
|
@@ -5574,14 +6327,14 @@ ${answer}
|
|
|
5574
6327
|
}
|
|
5575
6328
|
async mutate(bundlePath2, conceptId2, change, entry, changeBody = (body) => body) {
|
|
5576
6329
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
5577
|
-
const before = await (0,
|
|
6330
|
+
const before = await (0, import_promises10.readFile)(target, "utf8").catch(() => null);
|
|
5578
6331
|
if (before === null) throw new KbRecordNotFoundError(conceptId2);
|
|
5579
6332
|
const parsed = this.parse(conceptId2, before);
|
|
5580
6333
|
if (!parsed) throw new KbRecordNotFoundError(conceptId2);
|
|
5581
6334
|
const frontmatter = change(parsed.frontmatter);
|
|
5582
6335
|
const body = changeBody(parsed.body);
|
|
5583
6336
|
const contents = stringifyMarkdownWithFrontmatter(body, frontmatter);
|
|
5584
|
-
const witness = await (0,
|
|
6337
|
+
const witness = await (0, import_promises10.readFile)(target, "utf8").catch(() => null);
|
|
5585
6338
|
if (witness === null || sha2563(witness) !== sha2563(before)) {
|
|
5586
6339
|
throw new KbWriteConflictError(conceptId2);
|
|
5587
6340
|
}
|
|
@@ -5607,20 +6360,20 @@ ${answer}
|
|
|
5607
6360
|
*/
|
|
5608
6361
|
async publish(target, contents, overwrite, conceptId2) {
|
|
5609
6362
|
const staging = `${target}.${process.pid}.tmp`;
|
|
5610
|
-
await (0,
|
|
6363
|
+
await (0, import_promises10.writeFile)(staging, contents, "utf8");
|
|
5611
6364
|
try {
|
|
5612
6365
|
if (overwrite) {
|
|
5613
|
-
await (0,
|
|
6366
|
+
await (0, import_promises10.rename)(staging, target);
|
|
5614
6367
|
return;
|
|
5615
6368
|
}
|
|
5616
|
-
await (0,
|
|
6369
|
+
await (0, import_promises10.link)(staging, target);
|
|
5617
6370
|
} catch (error) {
|
|
5618
6371
|
if (error.code === "EEXIST") {
|
|
5619
6372
|
throw new KbRecordAlreadyExistsError(conceptId2);
|
|
5620
6373
|
}
|
|
5621
6374
|
throw error;
|
|
5622
6375
|
} finally {
|
|
5623
|
-
await (0,
|
|
6376
|
+
await (0, import_promises10.unlink)(staging).catch(() => void 0);
|
|
5624
6377
|
}
|
|
5625
6378
|
}
|
|
5626
6379
|
/**
|
|
@@ -5668,14 +6421,14 @@ ${answer}
|
|
|
5668
6421
|
try {
|
|
5669
6422
|
let existing;
|
|
5670
6423
|
try {
|
|
5671
|
-
existing = await (0,
|
|
6424
|
+
existing = await (0, import_promises10.readFile)(target, "utf8");
|
|
5672
6425
|
} catch (error) {
|
|
5673
6426
|
if (error.code !== "ENOENT") throw error;
|
|
5674
6427
|
existing = null;
|
|
5675
6428
|
}
|
|
5676
6429
|
if (existing === null) {
|
|
5677
6430
|
try {
|
|
5678
|
-
await (0,
|
|
6431
|
+
await (0, import_promises10.writeFile)(target, appendUnionMergeLine(""), {
|
|
5679
6432
|
encoding: "utf8",
|
|
5680
6433
|
flag: "wx"
|
|
5681
6434
|
});
|
|
@@ -5696,7 +6449,7 @@ ${answer}
|
|
|
5696
6449
|
return;
|
|
5697
6450
|
}
|
|
5698
6451
|
if (!hasMergeDeclaration(existing)) {
|
|
5699
|
-
await (0,
|
|
6452
|
+
await (0, import_promises10.appendFile)(target, appendUnionMergeLine(existing), "utf8");
|
|
5700
6453
|
this.logger.info?.({
|
|
5701
6454
|
operation: "kb.gitattributes.ensure",
|
|
5702
6455
|
bundlePath: root,
|
|
@@ -5715,7 +6468,7 @@ ${answer}
|
|
|
5715
6468
|
async record(root, entry) {
|
|
5716
6469
|
await this.ensureGitattributes(root);
|
|
5717
6470
|
const line = renderLogEntry({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
5718
|
-
await (0,
|
|
6471
|
+
await (0, import_promises10.appendFile)((0, import_node_path11.join)(root, LOG_FILE), line, "utf8").catch((error) => {
|
|
5719
6472
|
this.logger.warn?.({
|
|
5720
6473
|
operation: "kb.log.append",
|
|
5721
6474
|
outcome: "failed",
|
|
@@ -5792,7 +6545,7 @@ function normalizeActor(id) {
|
|
|
5792
6545
|
}
|
|
5793
6546
|
|
|
5794
6547
|
// src/version.ts
|
|
5795
|
-
var VERSION = true ? "0.1.
|
|
6548
|
+
var VERSION = true ? "0.1.18" : "0.0.0-dev";
|
|
5796
6549
|
|
|
5797
6550
|
// src/cli.ts
|
|
5798
6551
|
async function runKbCli(argv) {
|
|
@@ -5849,21 +6602,21 @@ async function runKbCli(argv) {
|
|
|
5849
6602
|
`);
|
|
5850
6603
|
}
|
|
5851
6604
|
function takeLiteral(argv) {
|
|
5852
|
-
const
|
|
5853
|
-
if (
|
|
5854
|
-
return { flags: argv.slice(0,
|
|
6605
|
+
const at2 = argv.indexOf("--");
|
|
6606
|
+
if (at2 === -1) return { flags: argv, literal: [] };
|
|
6607
|
+
return { flags: argv.slice(0, at2), literal: argv.slice(at2 + 1) };
|
|
5855
6608
|
}
|
|
5856
6609
|
function takeBundle(argv) {
|
|
5857
|
-
const
|
|
5858
|
-
if (
|
|
6610
|
+
const at2 = argv.indexOf("--bundle");
|
|
6611
|
+
if (at2 === -1) {
|
|
5859
6612
|
return { bundle: (0, import_node_path12.join)(process.cwd(), KB_DIR), explicit: false, rest: argv };
|
|
5860
6613
|
}
|
|
5861
|
-
const bundle = argv[
|
|
6614
|
+
const bundle = argv[at2 + 1];
|
|
5862
6615
|
if (!bundle) die("--bundle requires a path");
|
|
5863
6616
|
return {
|
|
5864
6617
|
bundle,
|
|
5865
6618
|
explicit: true,
|
|
5866
|
-
rest: [...argv.slice(0,
|
|
6619
|
+
rest: [...argv.slice(0, at2), ...argv.slice(at2 + 2)]
|
|
5867
6620
|
};
|
|
5868
6621
|
}
|
|
5869
6622
|
function readStdin() {
|