@saasontools/strauss-kb 0.1.6 → 0.1.7
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/README.md +21 -2
- package/dist/{chunk-V7TRZ2ER.js → chunk-GKCG4P3L.js} +10 -3
- package/dist/chunk-GKCG4P3L.js.map +1 -0
- package/dist/{chunk-PNSRTKYN.js → chunk-GKUQOJEK.js} +343 -79
- package/dist/chunk-GKUQOJEK.js.map +1 -0
- package/dist/{chunk-BVF7X5VO.js → chunk-LCQKARFK.js} +5 -4
- package/dist/chunk-LCQKARFK.js.map +1 -0
- package/dist/cli-main.cjs +348 -88
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +357 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -7
- package/dist/index.d.ts +104 -7
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +343 -89
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-BVF7X5VO.js.map +0 -1
- package/dist/chunk-PNSRTKYN.js.map +0 -1
- package/dist/chunk-V7TRZ2ER.js.map +0 -1
|
@@ -983,41 +983,48 @@ function kbJsonSchemas() {
|
|
|
983
983
|
};
|
|
984
984
|
}
|
|
985
985
|
|
|
986
|
-
// src/
|
|
987
|
-
var
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
for (const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
1006
|
-
existing.via.push(edge);
|
|
1007
|
-
}
|
|
1008
|
-
continue;
|
|
1009
|
-
}
|
|
1010
|
-
reached.set(record.conceptId, { record, depth, via: [edge] });
|
|
1011
|
-
next.push(record);
|
|
1012
|
-
}
|
|
986
|
+
// src/kb-edges.ts
|
|
987
|
+
var KB_EDGE_KINDS = [
|
|
988
|
+
"body-link",
|
|
989
|
+
"supersession",
|
|
990
|
+
"anchor",
|
|
991
|
+
"source"
|
|
992
|
+
];
|
|
993
|
+
var BODY_LINK_TARGET = new RegExp(
|
|
994
|
+
`\\]\\((${KB_CONCEPT_ID_PATTERN.source.replace(/^\^|\$$/g, "")})\\.md\\)`,
|
|
995
|
+
"g"
|
|
996
|
+
);
|
|
997
|
+
function neighbours(from, bundle, kinds = KB_EDGE_KINDS) {
|
|
998
|
+
const found = /* @__PURE__ */ new Map();
|
|
999
|
+
for (const kind of kinds) {
|
|
1000
|
+
for (const record of edgeNeighbours(from, bundle, kind)) {
|
|
1001
|
+
const existing = found.get(record.conceptId);
|
|
1002
|
+
if (existing) {
|
|
1003
|
+
if (!existing.via.includes(kind)) existing.via.push(kind);
|
|
1004
|
+
continue;
|
|
1013
1005
|
}
|
|
1006
|
+
found.set(record.conceptId, { record, via: [kind] });
|
|
1014
1007
|
}
|
|
1015
|
-
frontier = next;
|
|
1016
1008
|
}
|
|
1017
|
-
return [...
|
|
1009
|
+
return [...found.values()];
|
|
1018
1010
|
}
|
|
1019
|
-
function
|
|
1020
|
-
switch (
|
|
1011
|
+
function edgeNeighbours(from, bundle, kind) {
|
|
1012
|
+
switch (kind) {
|
|
1013
|
+
// A link whose target is not in the bundle is legal per compose.ts —
|
|
1014
|
+
// records are routinely written before the ones they point at exist — so
|
|
1015
|
+
// missing targets are skipped, never an error.
|
|
1016
|
+
case "body-link": {
|
|
1017
|
+
const targets = new Set(
|
|
1018
|
+
[...from.body.matchAll(BODY_LINK_TARGET)].map((match) => match[1])
|
|
1019
|
+
);
|
|
1020
|
+
if (!targets.size) return [];
|
|
1021
|
+
return bundle.filter(
|
|
1022
|
+
(candidate) => candidate.conceptId !== from.conceptId && targets.has(candidate.conceptId)
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
// Both directions and both pointers: `supersede()` writes the pair, but a
|
|
1026
|
+
// hand-edit can leave one side behind, and a walk trusting one pointer
|
|
1027
|
+
// would miss a replacement the bundle openly declares.
|
|
1021
1028
|
case "supersession":
|
|
1022
1029
|
return bundle.filter(
|
|
1023
1030
|
(candidate) => candidate.conceptId !== from.conceptId && (candidate.conceptId === from.frontmatter.strauss_superseded_by || from.frontmatter.strauss_supersedes?.includes(
|
|
@@ -1051,6 +1058,40 @@ function anchorsTouch(left, right) {
|
|
|
1051
1058
|
if (!left.symbol || !right.symbol) return true;
|
|
1052
1059
|
return left.symbol === right.symbol;
|
|
1053
1060
|
}
|
|
1061
|
+
|
|
1062
|
+
// src/trace.ts
|
|
1063
|
+
var TRACE_EDGES = ["supersession", "anchor", "source"];
|
|
1064
|
+
function trace(seedId, bundle, options = {}) {
|
|
1065
|
+
const edges = options.edges?.length ? options.edges : TRACE_EDGES;
|
|
1066
|
+
const maxDepth = options.depth ?? 3;
|
|
1067
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
1068
|
+
const seed = byId.get(seedId);
|
|
1069
|
+
if (!seed) return [];
|
|
1070
|
+
const reached = /* @__PURE__ */ new Map([
|
|
1071
|
+
[seedId, { record: seed, depth: 0, via: [] }]
|
|
1072
|
+
]);
|
|
1073
|
+
let frontier = [seed];
|
|
1074
|
+
for (let depth = 1; depth <= maxDepth && frontier.length; depth += 1) {
|
|
1075
|
+
const next = [];
|
|
1076
|
+
for (const from of frontier) {
|
|
1077
|
+
for (const edge of edges) {
|
|
1078
|
+
for (const record of edgeNeighbours(from, bundle, edge)) {
|
|
1079
|
+
const existing = reached.get(record.conceptId);
|
|
1080
|
+
if (existing) {
|
|
1081
|
+
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
1082
|
+
existing.via.push(edge);
|
|
1083
|
+
}
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
reached.set(record.conceptId, { record, depth, via: [edge] });
|
|
1087
|
+
next.push(record);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
frontier = next;
|
|
1092
|
+
}
|
|
1093
|
+
return [...reached.values()].sort(byGeneratedAt);
|
|
1094
|
+
}
|
|
1054
1095
|
function byGeneratedAt(left, right) {
|
|
1055
1096
|
const at = (step) => step.record.frontmatter.generated?.at ?? "";
|
|
1056
1097
|
return at(left).localeCompare(at(right)) || left.depth - right.depth;
|
|
@@ -1281,23 +1322,123 @@ var noDecisionCommand = define({
|
|
|
1281
1322
|
}
|
|
1282
1323
|
});
|
|
1283
1324
|
|
|
1284
|
-
// src/commands/
|
|
1325
|
+
// src/commands/pack.ts
|
|
1285
1326
|
import { z as z14 } from "zod";
|
|
1327
|
+
var packCommand = define({
|
|
1328
|
+
name: "pack",
|
|
1329
|
+
tool: "kb_pack",
|
|
1330
|
+
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
1331
|
+
description: "The bounded neighbourhood around one record: everything within `hops` of the root, ranked and cut to `maxNodes`, with every cut record named under Excluded \u2014 a named gap is knowable, a silent one is not. Prefer this over kb_load when the base is too large to hold whole and the work centres on one record; prefer it over kb_query when the question needs the governed neighbourhood \u2014 what was settled and what binds near this record \u2014 rather than a lookup by wording. Superseded records arrive as name, replacement and date stubs exactly as kb_load emits them: their bodies no longer hold, and kb_trace has the history. Refuses outright rather than truncating when the pack would exceed its token budget \u2014 a partial pack is indistinguishable from a complete one \u2014 reporting the record count and every already-cut id so the caller can lower hops or maxNodes, or raise the budget. The header carries the bundle, root, budget and a timestamp; everything below the header is byte-identical across runs over an unchanged base, so two packs can be diffed and a changed byte means changed knowledge. This tool (with kb_load, kb_query and kb_trace) is the only supported way to read a base; a raw file read bypasses supersession resolution and returns replaced records as if current.",
|
|
1332
|
+
input: z14.object({
|
|
1333
|
+
bundlePath,
|
|
1334
|
+
conceptId,
|
|
1335
|
+
hops: z14.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
|
|
1336
|
+
maxNodes: z14.number().int().positive().optional().describe(
|
|
1337
|
+
"How many records the pack may hold, root included. Defaults to 20."
|
|
1338
|
+
),
|
|
1339
|
+
budgetTokens: z14.number().int().positive().optional().describe(
|
|
1340
|
+
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
1341
|
+
)
|
|
1342
|
+
}),
|
|
1343
|
+
fromArgv: (argv, path) => {
|
|
1344
|
+
const hops = argvFlag(argv, "--hops");
|
|
1345
|
+
const maxNodes = argvFlag(argv, "--max-nodes");
|
|
1346
|
+
const budget = argvFlag(argv, "--budget");
|
|
1347
|
+
return {
|
|
1348
|
+
bundlePath: path,
|
|
1349
|
+
conceptId: argv[1],
|
|
1350
|
+
...hops ? { hops: Number(hops) } : {},
|
|
1351
|
+
...maxNodes ? { maxNodes: Number(maxNodes) } : {},
|
|
1352
|
+
...budget ? { budgetTokens: Number(budget) } : {}
|
|
1353
|
+
};
|
|
1354
|
+
},
|
|
1355
|
+
run: async ({ store, now }, { bundlePath: path, conceptId: root, hops, maxNodes, budgetTokens }) => {
|
|
1356
|
+
const result = await store.pack(path, root, {
|
|
1357
|
+
...hops !== void 0 ? { hops } : {},
|
|
1358
|
+
...maxNodes !== void 0 ? { maxNodes } : {},
|
|
1359
|
+
...budgetTokens !== void 0 ? { budgetTokens } : {}
|
|
1360
|
+
});
|
|
1361
|
+
return render(result, path, now());
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
function render(result, bundle, at) {
|
|
1365
|
+
const lines = [
|
|
1366
|
+
`# KB Pack \u2014 ${result.root}`,
|
|
1367
|
+
`bundle: ${bundle}`,
|
|
1368
|
+
`budget: ~${result.tokensLoaded} of ${result.budgetTokens} tokens, ${result.recordCount} records`,
|
|
1369
|
+
`packed: ${at}`,
|
|
1370
|
+
"",
|
|
1371
|
+
`## Records (${result.records.length})`
|
|
1372
|
+
];
|
|
1373
|
+
for (const record of result.records) {
|
|
1374
|
+
lines.push(
|
|
1375
|
+
"",
|
|
1376
|
+
`### ${record.conceptId}${record.title ? ` \u2014 ${record.title}` : ""} [${record.standing}]`
|
|
1377
|
+
);
|
|
1378
|
+
if (record.warnings.length) {
|
|
1379
|
+
lines.push(`warnings: ${record.warnings.map(warningLabel).join("; ")}`);
|
|
1380
|
+
}
|
|
1381
|
+
if (record.anchors.length) {
|
|
1382
|
+
lines.push(
|
|
1383
|
+
`anchors: ${record.anchors.map(
|
|
1384
|
+
(anchor) => anchor.symbol ? `${anchor.file}#${anchor.symbol}` : anchor.file
|
|
1385
|
+
).join(", ")}`
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
lines.push("", record.body.trimEnd());
|
|
1389
|
+
}
|
|
1390
|
+
if (result.superseded.length) {
|
|
1391
|
+
lines.push("", `## Superseded (${result.superseded.length})`);
|
|
1392
|
+
for (const entry of result.superseded) {
|
|
1393
|
+
lines.push(
|
|
1394
|
+
`- ${entry.conceptId} \u2192 ${entry.supersededBy.join(", ") || "(no surviving head)"}${entry.at ? ` (${entry.at})` : ""}`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (result.excluded.length) {
|
|
1399
|
+
lines.push("", `## Excluded (${result.excluded.length})`);
|
|
1400
|
+
for (const cut of result.excluded) lines.push(`- ${cut}`);
|
|
1401
|
+
}
|
|
1402
|
+
return lines.join("\n");
|
|
1403
|
+
}
|
|
1404
|
+
function warningLabel(warning) {
|
|
1405
|
+
switch (warning.kind) {
|
|
1406
|
+
case "superseded":
|
|
1407
|
+
return `superseded by ${warning.by.join(", ")}`;
|
|
1408
|
+
case "unsettled":
|
|
1409
|
+
return `unsettled (${warning.status})`;
|
|
1410
|
+
case "broken-chain":
|
|
1411
|
+
return `broken chain \u2014 ${warning.missing} is not in the bundle`;
|
|
1412
|
+
case "chain-cycle":
|
|
1413
|
+
return `chain cycle through ${warning.through.join(" \u2192 ")}`;
|
|
1414
|
+
case "forked-chain":
|
|
1415
|
+
return `forked chain \u2014 heads ${warning.heads.join(", ")}`;
|
|
1416
|
+
case "stale":
|
|
1417
|
+
return `stale since ${warning.staleAfter}`;
|
|
1418
|
+
case "unresolved-question":
|
|
1419
|
+
return "unresolved question";
|
|
1420
|
+
default:
|
|
1421
|
+
return warning.kind;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// src/commands/pin.ts
|
|
1426
|
+
import { z as z15 } from "zod";
|
|
1286
1427
|
var pinCommand = define({
|
|
1287
1428
|
name: "pin",
|
|
1288
1429
|
tool: "kb_pin",
|
|
1289
1430
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
1290
1431
|
description: "Pin a base into a workspace pin manifest, so `context` surfaces it at every context birth. Three layers, nearest wins: the committed project manifest (.strauss/kb-pins.json, the default), `--local` (.strauss/kb-pins.local.json, personal and gitignored), and `--user` (~/.strauss/kb-pins.json, every workspace). Idempotent \u2014 re-pinning changes nothing unless --mode, --profiles, or --frozen/--unfreeze are given, which update just those fields. `--mode full` preloads the whole base into the block regardless of the full-under threshold; `--mode index` never upgrades. `--profiles` scopes the pin to named context profiles. `--frozen` marks the base concluded: write commands against it refuse and `context` labels it read-only. A path with no records yet succeeds with a warning; bases are routinely pinned before they are populated. Pins are workspace state: the pinned base itself is never touched.",
|
|
1291
|
-
input:
|
|
1432
|
+
input: z15.object({
|
|
1292
1433
|
bundlePath,
|
|
1293
|
-
mode:
|
|
1434
|
+
mode: z15.enum(["full", "index"]).optional().describe(
|
|
1294
1435
|
"full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
|
|
1295
1436
|
),
|
|
1296
|
-
profiles:
|
|
1297
|
-
layer:
|
|
1437
|
+
profiles: z15.array(z15.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
|
|
1438
|
+
layer: z15.enum(["project", "local", "user"]).optional().describe(
|
|
1298
1439
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
1299
1440
|
),
|
|
1300
|
-
frozen:
|
|
1441
|
+
frozen: z15.boolean().optional().describe(
|
|
1301
1442
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
1302
1443
|
)
|
|
1303
1444
|
}),
|
|
@@ -1326,29 +1467,29 @@ var pinCommand = define({
|
|
|
1326
1467
|
});
|
|
1327
1468
|
|
|
1328
1469
|
// src/commands/pins.ts
|
|
1329
|
-
import { z as
|
|
1470
|
+
import { z as z16 } from "zod";
|
|
1330
1471
|
var pinsCommand = define({
|
|
1331
1472
|
name: "pins",
|
|
1332
1473
|
tool: "kb_pins",
|
|
1333
1474
|
usage: "pins",
|
|
1334
1475
|
description: "Every pinned base across the manifest layers, each with its layer and whether it currently resolves to readable records. Reads the workspace manifests rather than any one base, like kb_context.",
|
|
1335
|
-
input:
|
|
1476
|
+
input: z16.object({}),
|
|
1336
1477
|
fromArgv: () => ({}),
|
|
1337
1478
|
run: ({ store }) => listPins(store, process.cwd())
|
|
1338
1479
|
});
|
|
1339
1480
|
|
|
1340
1481
|
// src/commands/query.ts
|
|
1341
|
-
import { z as
|
|
1482
|
+
import { z as z17 } from "zod";
|
|
1342
1483
|
var queryCommand = define({
|
|
1343
1484
|
name: "query",
|
|
1344
1485
|
tool: "kb_query",
|
|
1345
1486
|
usage: "query <text...>",
|
|
1346
1487
|
description: "Search and return each match with its standing. Results are flagged, never filtered: a superseded record comes back alongside whatever replaced it, and a rejected one is marked as something explicitly not adopted. Prefer kb_load when the base fits its budget: on this package's measurements, a reader holding the whole base answered eight of nine questions whose wording appears in no record, where embedding search answered four. Never read record files directly \u2014 this tool (with kb_load and kb_trace) is the only supported way to read a base; a file read bypasses supersession resolution and returns replaced records as if current.",
|
|
1347
|
-
input:
|
|
1488
|
+
input: z17.object({
|
|
1348
1489
|
bundlePath,
|
|
1349
|
-
text:
|
|
1350
|
-
type:
|
|
1351
|
-
includeNonCurrent:
|
|
1490
|
+
text: z17.string().optional(),
|
|
1491
|
+
type: z17.enum(KB_RECORD_TYPES).optional(),
|
|
1492
|
+
includeNonCurrent: z17.boolean().optional()
|
|
1352
1493
|
}),
|
|
1353
1494
|
fromArgv: (argv, path) => ({
|
|
1354
1495
|
bundlePath: path,
|
|
@@ -1370,40 +1511,40 @@ var queryCommand = define({
|
|
|
1370
1511
|
});
|
|
1371
1512
|
|
|
1372
1513
|
// src/commands/read-index.ts
|
|
1373
|
-
import { z as
|
|
1514
|
+
import { z as z18 } from "zod";
|
|
1374
1515
|
var readIndexCommand = define({
|
|
1375
1516
|
name: "index",
|
|
1376
1517
|
tool: "kb_index",
|
|
1377
1518
|
usage: "index",
|
|
1378
1519
|
description: "The index, rebuilt if it disagrees with the records. One call gives the whole shape of the base: title, type, status, and description per record. The cheap re-orientation call after compaction or deep in a long session \u2014 a few hundred tokens; call it (or kb_context, when bases are pinned) first, then kb_load or fetch by concept id.",
|
|
1379
|
-
input:
|
|
1520
|
+
input: z18.object({ bundlePath }),
|
|
1380
1521
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
1381
1522
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
1382
1523
|
});
|
|
1383
1524
|
|
|
1384
1525
|
// src/commands/schema.ts
|
|
1385
|
-
import { z as
|
|
1526
|
+
import { z as z19 } from "zod";
|
|
1386
1527
|
var schemaCommand = define({
|
|
1387
1528
|
name: "schema",
|
|
1388
1529
|
tool: "kb_schema",
|
|
1389
1530
|
usage: "schema",
|
|
1390
1531
|
description: "JSON Schema for the frontmatter, the write input, and log entries \u2014 generated from the code that enforces them, so it cannot drift from what a write will accept.",
|
|
1391
|
-
input:
|
|
1532
|
+
input: z19.object({}),
|
|
1392
1533
|
fromArgv: () => ({}),
|
|
1393
1534
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
1394
1535
|
});
|
|
1395
1536
|
|
|
1396
1537
|
// src/commands/status.ts
|
|
1397
|
-
import { z as
|
|
1538
|
+
import { z as z20 } from "zod";
|
|
1398
1539
|
var statusCommand = define({
|
|
1399
1540
|
name: "status",
|
|
1400
1541
|
tool: "kb_status",
|
|
1401
1542
|
usage: "status <concept-id> <status>",
|
|
1402
1543
|
description: "Move a record's status, leaving everything else alone. Uses a compare-and-swap, so a concurrent change fails loudly rather than being overwritten.",
|
|
1403
|
-
input:
|
|
1544
|
+
input: z20.object({
|
|
1404
1545
|
bundlePath,
|
|
1405
1546
|
conceptId,
|
|
1406
|
-
status:
|
|
1547
|
+
status: z20.enum(KB_RECORD_STATUSES)
|
|
1407
1548
|
}),
|
|
1408
1549
|
fromArgv: (argv, path) => ({
|
|
1409
1550
|
bundlePath: path,
|
|
@@ -1418,13 +1559,13 @@ var statusCommand = define({
|
|
|
1418
1559
|
});
|
|
1419
1560
|
|
|
1420
1561
|
// src/commands/supersede.ts
|
|
1421
|
-
import { z as
|
|
1562
|
+
import { z as z21 } from "zod";
|
|
1422
1563
|
var supersedeCommand = define({
|
|
1423
1564
|
name: "supersede",
|
|
1424
1565
|
tool: "kb_supersede",
|
|
1425
1566
|
usage: "supersede <concept-id> <replacement-id>",
|
|
1426
1567
|
description: "Mark a record superseded by another, linking both directions. Use this rather than editing a record whose meaning changed \u2014 a record that quietly becomes something else invalidates every reference to it, and the earlier understanding is what a later trace needs.",
|
|
1427
|
-
input:
|
|
1568
|
+
input: z21.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
1428
1569
|
fromArgv: (argv, path) => ({
|
|
1429
1570
|
bundlePath: path,
|
|
1430
1571
|
conceptId: argv[1],
|
|
@@ -1438,16 +1579,16 @@ var supersedeCommand = define({
|
|
|
1438
1579
|
});
|
|
1439
1580
|
|
|
1440
1581
|
// src/commands/sync-instructions.ts
|
|
1441
|
-
import { z as
|
|
1582
|
+
import { z as z22 } from "zod";
|
|
1442
1583
|
var syncInstructionsCommand = define({
|
|
1443
1584
|
name: "sync-instructions",
|
|
1444
1585
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
1445
1586
|
description: "Idempotently plant the `context` block between sentinel comments in an instruction file (AGENTS.md, CLAUDE.md), creating the block when absent and leaving everything outside the sentinels alone. CLI-only: this is file plumbing for runtimes whose instruction files are re-read where their conversations are not, not an agent capability \u2014 the capability is kb_context.",
|
|
1446
|
-
input:
|
|
1447
|
-
file:
|
|
1448
|
-
budgetTokens:
|
|
1449
|
-
fullUnderTokens:
|
|
1450
|
-
profile:
|
|
1587
|
+
input: z22.object({
|
|
1588
|
+
file: z22.string().min(1).describe("The instruction file to edit in place."),
|
|
1589
|
+
budgetTokens: z22.number().int().positive().optional(),
|
|
1590
|
+
fullUnderTokens: z22.number().int().positive().optional(),
|
|
1591
|
+
profile: z22.string().optional()
|
|
1451
1592
|
}),
|
|
1452
1593
|
fromArgv: (argv) => {
|
|
1453
1594
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -1473,17 +1614,17 @@ var syncInstructionsCommand = define({
|
|
|
1473
1614
|
});
|
|
1474
1615
|
|
|
1475
1616
|
// src/commands/trace.ts
|
|
1476
|
-
import { z as
|
|
1617
|
+
import { z as z23 } from "zod";
|
|
1477
1618
|
var traceCommand = define({
|
|
1478
1619
|
name: "trace",
|
|
1479
1620
|
tool: "kb_trace",
|
|
1480
1621
|
usage: "trace <concept-id> [edges...]",
|
|
1481
1622
|
description: 'How a position was arrived at, as a timeline ordered by when each record was written. Deliberately includes rejected, draft, and superseded records \u2014 in a history those are the content, not noise. Follows supersession, shared code anchors, and shared sources. Use when the question is "why is this the way it is" rather than "what do we hold now". This tool (with kb_load and kb_query) is the only supported way to read a base; a raw file read bypasses supersession resolution and returns replaced records as if current.',
|
|
1482
|
-
input:
|
|
1623
|
+
input: z23.object({
|
|
1483
1624
|
bundlePath,
|
|
1484
1625
|
conceptId,
|
|
1485
|
-
edges:
|
|
1486
|
-
depth:
|
|
1626
|
+
edges: z23.array(z23.enum(TRACE_EDGES)).optional(),
|
|
1627
|
+
depth: z23.number().int().positive().optional()
|
|
1487
1628
|
}),
|
|
1488
1629
|
fromArgv: (argv, path) => ({
|
|
1489
1630
|
bundlePath: path,
|
|
@@ -1505,53 +1646,53 @@ var traceCommand = define({
|
|
|
1505
1646
|
});
|
|
1506
1647
|
|
|
1507
1648
|
// src/commands/types.ts
|
|
1508
|
-
import { z as
|
|
1649
|
+
import { z as z24 } from "zod";
|
|
1509
1650
|
var typesCommand = define({
|
|
1510
1651
|
name: "types",
|
|
1511
1652
|
tool: "kb_types",
|
|
1512
1653
|
usage: "types",
|
|
1513
1654
|
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.",
|
|
1514
|
-
input:
|
|
1655
|
+
input: z24.object({}),
|
|
1515
1656
|
fromArgv: () => ({}),
|
|
1516
1657
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
1517
1658
|
});
|
|
1518
1659
|
|
|
1519
1660
|
// src/commands/unpin.ts
|
|
1520
|
-
import { z as
|
|
1661
|
+
import { z as z25 } from "zod";
|
|
1521
1662
|
var unpinCommand = define({
|
|
1522
1663
|
name: "unpin",
|
|
1523
1664
|
tool: "kb_unpin",
|
|
1524
1665
|
usage: "unpin [bundle-path]",
|
|
1525
1666
|
description: "Remove a base from every pin manifest layer that holds it \u2014 project, local, and user \u2014 because unpinned means gone, not still injected from another file. Reports which layers were touched.",
|
|
1526
|
-
input:
|
|
1667
|
+
input: z25.object({ bundlePath }),
|
|
1527
1668
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
1528
1669
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
1529
1670
|
});
|
|
1530
1671
|
|
|
1531
1672
|
// src/commands/validate.ts
|
|
1532
|
-
import { z as
|
|
1673
|
+
import { z as z26 } from "zod";
|
|
1533
1674
|
var validateCommand = define({
|
|
1534
1675
|
name: "validate",
|
|
1535
1676
|
tool: "kb_validate",
|
|
1536
1677
|
usage: "validate",
|
|
1537
1678
|
description: "Check pointers no single record can see: supersession links that disagree between the two records, and assumptions that cite sources. Per-record shape is enforced on every read, so a problem here means someone edited a file by hand.",
|
|
1538
|
-
input:
|
|
1679
|
+
input: z26.object({ bundlePath }),
|
|
1539
1680
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
1540
1681
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
1541
1682
|
failsWhen: (result) => Array.isArray(result) && result.length > 0
|
|
1542
1683
|
});
|
|
1543
1684
|
|
|
1544
1685
|
// src/commands/verify.ts
|
|
1545
|
-
import { z as
|
|
1686
|
+
import { z as z27 } from "zod";
|
|
1546
1687
|
var verifyCommand = define({
|
|
1547
1688
|
name: "verify",
|
|
1548
1689
|
tool: "kb_verify",
|
|
1549
1690
|
usage: "verify <concept-id> --note <text>",
|
|
1550
1691
|
description: "Append one verified[] event \u2014 who checked the record, when, and what the check found. Appends only; prior events are never rewritten. A record's own generator is refused unless the actor is human: re-reading your own output is not an independent check.",
|
|
1551
|
-
input:
|
|
1692
|
+
input: z27.object({
|
|
1552
1693
|
bundlePath,
|
|
1553
1694
|
conceptId,
|
|
1554
|
-
note:
|
|
1695
|
+
note: z27.string().refine((s) => s.trim().length > 0, {
|
|
1555
1696
|
message: "note must say what the check found"
|
|
1556
1697
|
})
|
|
1557
1698
|
}),
|
|
@@ -1571,7 +1712,7 @@ var verifyCommand = define({
|
|
|
1571
1712
|
});
|
|
1572
1713
|
|
|
1573
1714
|
// src/commands/write.ts
|
|
1574
|
-
import { z as
|
|
1715
|
+
import { z as z28 } from "zod";
|
|
1575
1716
|
var writeCommand = define({
|
|
1576
1717
|
name: "write",
|
|
1577
1718
|
tool: "kb_write",
|
|
@@ -1585,9 +1726,9 @@ var writeCommand = define({
|
|
|
1585
1726
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
1586
1727
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
1587
1728
|
].join("\n"),
|
|
1588
|
-
input:
|
|
1729
|
+
input: z28.object({
|
|
1589
1730
|
bundlePath,
|
|
1590
|
-
type:
|
|
1731
|
+
type: z28.enum(KB_RECORD_TYPES),
|
|
1591
1732
|
input: composeInputSchema
|
|
1592
1733
|
}),
|
|
1593
1734
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -1611,7 +1752,7 @@ var writeCommand = define({
|
|
|
1611
1752
|
});
|
|
1612
1753
|
|
|
1613
1754
|
// src/commands/write-decision.ts
|
|
1614
|
-
import { z as
|
|
1755
|
+
import { z as z29 } from "zod";
|
|
1615
1756
|
var writeDecisionCommand = define({
|
|
1616
1757
|
name: "write-decision",
|
|
1617
1758
|
tool: "kb_write_decision",
|
|
@@ -1624,7 +1765,7 @@ var writeDecisionCommand = define({
|
|
|
1624
1765
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
1625
1766
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
1626
1767
|
].join("\n"),
|
|
1627
|
-
input:
|
|
1768
|
+
input: z29.object({ bundlePath, input: decisionInputSchema }),
|
|
1628
1769
|
fromArgv: async (_argv, path, stdin) => ({
|
|
1629
1770
|
bundlePath: path,
|
|
1630
1771
|
input: JSON.parse(await stdin())
|
|
@@ -1654,6 +1795,7 @@ var KB_COMMANDS = [
|
|
|
1654
1795
|
answerCommand,
|
|
1655
1796
|
verifyCommand,
|
|
1656
1797
|
loadCommand,
|
|
1798
|
+
packCommand,
|
|
1657
1799
|
queryCommand,
|
|
1658
1800
|
traceCommand,
|
|
1659
1801
|
listCommand,
|
|
@@ -1707,6 +1849,7 @@ var Fault = /* @__PURE__ */ ((Fault2) => {
|
|
|
1707
1849
|
var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
1708
1850
|
ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
|
|
1709
1851
|
ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
|
|
1852
|
+
ErrorTypes2["KbPackBudgetExceeded"] = "KbPackBudgetExceeded";
|
|
1710
1853
|
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
1711
1854
|
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
1712
1855
|
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
@@ -1796,6 +1939,27 @@ var KbSelfVerificationError = class extends BaseError {
|
|
|
1796
1939
|
actor;
|
|
1797
1940
|
generatedBy;
|
|
1798
1941
|
};
|
|
1942
|
+
var KbPackBudgetExceededError = class extends BaseError {
|
|
1943
|
+
constructor(recordCount, approxTokens2, budgetTokens, excluded) {
|
|
1944
|
+
super({
|
|
1945
|
+
message: `kb: a pack of ${recordCount} records is ~${approxTokens2} tokens against a budget of ${budgetTokens} \u2014 lower hops or maxNodes, or raise the budget`,
|
|
1946
|
+
errorType: "KbPackBudgetExceeded" /* KbPackBudgetExceeded */,
|
|
1947
|
+
code: 400,
|
|
1948
|
+
fault: "User" /* User */,
|
|
1949
|
+
retriable: false,
|
|
1950
|
+
reportToUser: true,
|
|
1951
|
+
details: { recordCount, approxTokens: approxTokens2, budgetTokens, excluded }
|
|
1952
|
+
});
|
|
1953
|
+
this.recordCount = recordCount;
|
|
1954
|
+
this.approxTokens = approxTokens2;
|
|
1955
|
+
this.budgetTokens = budgetTokens;
|
|
1956
|
+
this.excluded = excluded;
|
|
1957
|
+
}
|
|
1958
|
+
recordCount;
|
|
1959
|
+
approxTokens;
|
|
1960
|
+
budgetTokens;
|
|
1961
|
+
excluded;
|
|
1962
|
+
};
|
|
1799
1963
|
var KbInvalidConceptIdError = class extends BaseError {
|
|
1800
1964
|
constructor(message, details) {
|
|
1801
1965
|
super({
|
|
@@ -2197,6 +2361,10 @@ ${answer}
|
|
|
2197
2361
|
async trace(bundlePath2, seedId, options = {}) {
|
|
2198
2362
|
return trace(seedId, await this.list(bundlePath2), options);
|
|
2199
2363
|
}
|
|
2364
|
+
/** A bounded neighbourhood around one record. See `pack.ts`. */
|
|
2365
|
+
async pack(bundlePath2, rootId, options = {}) {
|
|
2366
|
+
return pack(await this.list(bundlePath2), rootId, options);
|
|
2367
|
+
}
|
|
2200
2368
|
/**
|
|
2201
2369
|
* The stored index, rebuilt if it disagrees with the records.
|
|
2202
2370
|
*
|
|
@@ -2403,6 +2571,93 @@ function digest(contents) {
|
|
|
2403
2571
|
return createHash("sha256").update(contents).digest("hex");
|
|
2404
2572
|
}
|
|
2405
2573
|
|
|
2574
|
+
// src/pack.ts
|
|
2575
|
+
var DEFAULT_PACK_HOPS = 2;
|
|
2576
|
+
var DEFAULT_PACK_MAX_NODES = 20;
|
|
2577
|
+
var TYPE_PRIORITY = [
|
|
2578
|
+
"decision",
|
|
2579
|
+
"constraint",
|
|
2580
|
+
"requirement",
|
|
2581
|
+
...KB_RECORD_TYPES.filter(
|
|
2582
|
+
(type) => !["decision", "constraint", "requirement"].includes(type)
|
|
2583
|
+
)
|
|
2584
|
+
];
|
|
2585
|
+
function pack(bundle, rootId, options = {}) {
|
|
2586
|
+
const hops = options.hops ?? DEFAULT_PACK_HOPS;
|
|
2587
|
+
const maxNodes = options.maxNodes ?? DEFAULT_PACK_MAX_NODES;
|
|
2588
|
+
const budgetTokens = options.budgetTokens ?? DEFAULT_LOAD_BUDGET;
|
|
2589
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
2590
|
+
const root = byId.get(rootId);
|
|
2591
|
+
if (!root) throw new KbRecordNotFoundError(rootId);
|
|
2592
|
+
const reached = [{ record: root, depth: 0 }];
|
|
2593
|
+
const seen = /* @__PURE__ */ new Set([rootId]);
|
|
2594
|
+
let frontier = [root];
|
|
2595
|
+
for (let depth = 1; frontier.length; depth += 1) {
|
|
2596
|
+
const next = [];
|
|
2597
|
+
for (const from of frontier) {
|
|
2598
|
+
for (const { record } of neighbours(from, bundle)) {
|
|
2599
|
+
if (seen.has(record.conceptId)) continue;
|
|
2600
|
+
seen.add(record.conceptId);
|
|
2601
|
+
reached.push({ record, depth });
|
|
2602
|
+
next.push(record);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
frontier = next;
|
|
2606
|
+
}
|
|
2607
|
+
reached.sort(byRank);
|
|
2608
|
+
const within = reached.filter((entry) => entry.depth <= hops);
|
|
2609
|
+
const kept = within.slice(0, maxNodes);
|
|
2610
|
+
const excluded = [
|
|
2611
|
+
...within.slice(maxNodes),
|
|
2612
|
+
...reached.filter((entry) => entry.depth > hops)
|
|
2613
|
+
].map((entry) => entry.record.conceptId).sort();
|
|
2614
|
+
const adjudicated = adjudicate(
|
|
2615
|
+
kept.map((entry) => entry.record),
|
|
2616
|
+
bundle
|
|
2617
|
+
);
|
|
2618
|
+
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
2619
|
+
const whole = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
2620
|
+
const tokensLoaded = whole.reduce((total, hit) => total + estimateTokens(hit.record), 0) + superseded.reduce((total, entry) => total + estimateStubTokens(entry), 0);
|
|
2621
|
+
const recordCount = adjudicated.length;
|
|
2622
|
+
if (tokensLoaded > budgetTokens) {
|
|
2623
|
+
throw new KbPackBudgetExceededError(
|
|
2624
|
+
recordCount,
|
|
2625
|
+
tokensLoaded,
|
|
2626
|
+
budgetTokens,
|
|
2627
|
+
excluded
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
return {
|
|
2631
|
+
root: rootId,
|
|
2632
|
+
records: whole.map((hit) => ({
|
|
2633
|
+
conceptId: hit.record.conceptId,
|
|
2634
|
+
title: hit.record.frontmatter.title ?? null,
|
|
2635
|
+
standing: hit.standing,
|
|
2636
|
+
supersededBy: hit.heads.map((head) => head.conceptId),
|
|
2637
|
+
warnings: hit.warnings,
|
|
2638
|
+
anchors: hit.record.frontmatter.strauss_anchors ?? [],
|
|
2639
|
+
body: hit.record.body
|
|
2640
|
+
})),
|
|
2641
|
+
superseded,
|
|
2642
|
+
excluded,
|
|
2643
|
+
recordCount,
|
|
2644
|
+
tokensLoaded,
|
|
2645
|
+
budgetTokens
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function byRank(left, right) {
|
|
2649
|
+
return left.depth - right.depth || typeRank(left.record) - typeRank(right.record) || (left.record.frontmatter.title ?? "").localeCompare(
|
|
2650
|
+
right.record.frontmatter.title ?? ""
|
|
2651
|
+
) || left.record.conceptId.localeCompare(right.record.conceptId);
|
|
2652
|
+
}
|
|
2653
|
+
function typeRank(record) {
|
|
2654
|
+
const index = TYPE_PRIORITY.indexOf(record.frontmatter.type);
|
|
2655
|
+
return index === -1 ? TYPE_PRIORITY.length : index;
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// src/version.ts
|
|
2659
|
+
var VERSION = true ? "0.1.7" : "0.0.0-dev";
|
|
2660
|
+
|
|
2406
2661
|
export {
|
|
2407
2662
|
kbSourceSchema,
|
|
2408
2663
|
kbActorStampSchema,
|
|
@@ -2458,6 +2713,9 @@ export {
|
|
|
2458
2713
|
renderLogEntry,
|
|
2459
2714
|
parseLog,
|
|
2460
2715
|
kbJsonSchemas,
|
|
2716
|
+
KB_EDGE_KINDS,
|
|
2717
|
+
neighbours,
|
|
2718
|
+
edgeNeighbours,
|
|
2461
2719
|
TRACE_EDGES,
|
|
2462
2720
|
trace,
|
|
2463
2721
|
validateBundle,
|
|
@@ -2473,12 +2731,18 @@ export {
|
|
|
2473
2731
|
KbRecordNotFoundError,
|
|
2474
2732
|
KbWriteConflictError,
|
|
2475
2733
|
KbSelfVerificationError,
|
|
2734
|
+
KbPackBudgetExceededError,
|
|
2476
2735
|
KbInvalidConceptIdError,
|
|
2477
2736
|
SEARCH_INDEX_FILE,
|
|
2478
2737
|
searchBase,
|
|
2479
2738
|
resolveHits,
|
|
2480
2739
|
loadQmd,
|
|
2740
|
+
DEFAULT_PACK_HOPS,
|
|
2741
|
+
DEFAULT_PACK_MAX_NODES,
|
|
2742
|
+
pack,
|
|
2481
2743
|
KB_DIR,
|
|
2482
|
-
|
|
2744
|
+
DEFAULT_LOAD_BUDGET,
|
|
2745
|
+
KbStore,
|
|
2746
|
+
VERSION
|
|
2483
2747
|
};
|
|
2484
|
-
//# sourceMappingURL=chunk-
|
|
2748
|
+
//# sourceMappingURL=chunk-GKUQOJEK.js.map
|