@saasontools/strauss-kb 0.1.11 → 0.1.13
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 +15 -0
- package/README.md +49 -4
- package/dist/{chunk-CWWXMD35.js → chunk-WZODZNR6.js} +2 -2
- package/dist/{chunk-OVRQCQ6P.js → chunk-XALWG3EZ.js} +486 -92
- package/dist/chunk-XALWG3EZ.js.map +1 -0
- package/dist/{chunk-I3WW4F6X.js → chunk-ZICKDZGY.js} +2 -2
- package/dist/cli-main.cjs +484 -102
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +562 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +292 -13
- package/dist/index.d.ts +292 -13
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +484 -102
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-OVRQCQ6P.js.map +0 -1
- /package/dist/{chunk-CWWXMD35.js.map → chunk-WZODZNR6.js.map} +0 -0
- /package/dist/{chunk-I3WW4F6X.js.map → chunk-ZICKDZGY.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -40,10 +40,12 @@ __export(index_exports, {
|
|
|
40
40
|
DEFAULT_LOAD_BUDGET: () => DEFAULT_LOAD_BUDGET,
|
|
41
41
|
DEFAULT_PACK_HOPS: () => DEFAULT_PACK_HOPS,
|
|
42
42
|
DEFAULT_PACK_MAX_NODES: () => DEFAULT_PACK_MAX_NODES,
|
|
43
|
+
DEFAULT_TYPED_LINK_RELS: () => DEFAULT_TYPED_LINK_RELS,
|
|
43
44
|
DEFAULT_UNVERIFIED_DAYS: () => DEFAULT_UNVERIFIED_DAYS,
|
|
44
45
|
ErrorTypes: () => ErrorTypes,
|
|
45
46
|
Fault: () => Fault,
|
|
46
47
|
INDEX_FILE: () => INDEX_FILE,
|
|
48
|
+
KB_CAUSAL_LINK_RELS: () => KB_CAUSAL_LINK_RELS,
|
|
47
49
|
KB_COMMANDS: () => KB_COMMANDS,
|
|
48
50
|
KB_COMMANDS_BY_NAME: () => KB_COMMANDS_BY_NAME,
|
|
49
51
|
KB_CONCEPT_ID_PATTERN: () => KB_CONCEPT_ID_PATTERN,
|
|
@@ -51,6 +53,7 @@ __export(index_exports, {
|
|
|
51
53
|
KB_DIR: () => KB_DIR,
|
|
52
54
|
KB_DOCTOR_CHECKS: () => KB_DOCTOR_CHECKS,
|
|
53
55
|
KB_EDGE_KINDS: () => KB_EDGE_KINDS,
|
|
56
|
+
KB_LINK_RELS: () => KB_LINK_RELS,
|
|
54
57
|
KB_MATERIALITIES: () => KB_MATERIALITIES,
|
|
55
58
|
KB_RECORD_STATUSES: () => KB_RECORD_STATUSES,
|
|
56
59
|
KB_RECORD_TYPES: () => KB_RECORD_TYPES,
|
|
@@ -64,7 +67,9 @@ __export(index_exports, {
|
|
|
64
67
|
KbRecordNotFoundError: () => KbRecordNotFoundError,
|
|
65
68
|
KbSelfVerificationError: () => KbSelfVerificationError,
|
|
66
69
|
KbStore: () => KbStore,
|
|
70
|
+
KbUnknownLinkRelError: () => KbUnknownLinkRelError,
|
|
67
71
|
KbWriteConflictError: () => KbWriteConflictError,
|
|
72
|
+
LINK_RELS: () => LINK_RELS,
|
|
68
73
|
LOG_FILE: () => LOG_FILE,
|
|
69
74
|
NO_DECISION_SLUG: () => NO_DECISION_SLUG,
|
|
70
75
|
PINS_FILE: () => PINS_FILE,
|
|
@@ -76,10 +81,12 @@ __export(index_exports, {
|
|
|
76
81
|
adjudicate: () => adjudicate,
|
|
77
82
|
anchorFilePath: () => anchorFilePath,
|
|
78
83
|
assertBaseNotFrozen: () => assertBaseNotFrozen,
|
|
84
|
+
backlinks: () => backlinks,
|
|
79
85
|
buildContext: () => buildContext,
|
|
80
86
|
catalog: () => catalog,
|
|
81
87
|
composeDecisionRecord: () => composeDecisionRecord,
|
|
82
88
|
composeInputSchema: () => composeInputSchema,
|
|
89
|
+
composeLinkSchema: () => composeLinkSchema,
|
|
83
90
|
composeNoDecisionRecord: () => composeNoDecisionRecord,
|
|
84
91
|
composeRecord: () => composeRecord,
|
|
85
92
|
contextProfileBudgets: () => contextProfileBudgets,
|
|
@@ -89,13 +96,17 @@ __export(index_exports, {
|
|
|
89
96
|
doctor: () => doctor,
|
|
90
97
|
edgeNeighbours: () => edgeNeighbours,
|
|
91
98
|
hashAnchorText: () => hashAnchorText,
|
|
99
|
+
impact: () => impact,
|
|
100
|
+
inboundIndex: () => inboundIndex,
|
|
92
101
|
indexIsStale: () => indexIsStale,
|
|
102
|
+
isKbLinkRel: () => isKbLinkRel,
|
|
93
103
|
isKbRecordType: () => isKbRecordType,
|
|
94
104
|
isNoDecisionRecord: () => isNoDecisionRecord,
|
|
95
105
|
kbActorStampSchema: () => kbActorStampSchema,
|
|
96
106
|
kbAnchorSchema: () => kbAnchorSchema,
|
|
97
107
|
kbConceptIdSchema: () => kbConceptIdSchema,
|
|
98
108
|
kbJsonSchemas: () => kbJsonSchemas,
|
|
109
|
+
kbLinkSchema: () => kbLinkSchema,
|
|
99
110
|
kbLogEntrySchema: () => kbLogEntrySchema,
|
|
100
111
|
kbRecordFrontmatterSchema: () => kbRecordFrontmatterSchema,
|
|
101
112
|
kbSourceSchema: () => kbSourceSchema,
|
|
@@ -239,6 +250,10 @@ var kbAnchorSchema = import_zod.z.object({
|
|
|
239
250
|
/** Line count of the text the hash was taken over. */
|
|
240
251
|
lines: import_zod.z.number().int().positive().optional()
|
|
241
252
|
}).strict();
|
|
253
|
+
var kbLinkSchema = import_zod.z.object({
|
|
254
|
+
target: import_zod.z.string().min(1),
|
|
255
|
+
rel: import_zod.z.string().min(1)
|
|
256
|
+
}).passthrough();
|
|
242
257
|
var KB_RECORD_TYPES = [
|
|
243
258
|
"fact",
|
|
244
259
|
"requirement",
|
|
@@ -290,6 +305,10 @@ var kbRecordFrontmatterSchema = import_zod.z.object({
|
|
|
290
305
|
// strauss extensions — see the module comment.
|
|
291
306
|
strauss_anchors: import_zod.z.array(kbAnchorSchema).optional(),
|
|
292
307
|
strauss_verify: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
308
|
+
// Typed causal edges, source → target, living on the source. `A depends_on
|
|
309
|
+
// B` means A needs B, so `kb_impact` walks these inbound: what breaks if B
|
|
310
|
+
// changes is whatever declared a dependence on it.
|
|
311
|
+
strauss_links: import_zod.z.array(kbLinkSchema).optional(),
|
|
293
312
|
// Total after parsing, tolerant before it. Our producers must supply a
|
|
294
313
|
// status — an absent one would leave every reader inventing its own default
|
|
295
314
|
// — but OKF calls a concept carrying only `type` fully conformant, so
|
|
@@ -322,6 +341,7 @@ var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
|
322
341
|
ErrorTypes2["KbPackBudgetExceeded"] = "KbPackBudgetExceeded";
|
|
323
342
|
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
324
343
|
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
344
|
+
ErrorTypes2["KbUnknownLinkRel"] = "KbUnknownLinkRel";
|
|
325
345
|
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
326
346
|
return ErrorTypes2;
|
|
327
347
|
})(ErrorTypes || {});
|
|
@@ -430,6 +450,23 @@ var KbPackBudgetExceededError = class extends BaseError {
|
|
|
430
450
|
budgetTokens;
|
|
431
451
|
excluded;
|
|
432
452
|
};
|
|
453
|
+
var KbUnknownLinkRelError = class extends BaseError {
|
|
454
|
+
constructor(rel, expected) {
|
|
455
|
+
super({
|
|
456
|
+
message: `kb: ${rel} is not a rel a walk can follow \u2014 expected one of ${expected.join(", ")}`,
|
|
457
|
+
errorType: "KbUnknownLinkRel" /* KbUnknownLinkRel */,
|
|
458
|
+
code: 400,
|
|
459
|
+
fault: "User" /* User */,
|
|
460
|
+
retriable: false,
|
|
461
|
+
reportToUser: true,
|
|
462
|
+
details: { rel, expected: expected.join(", ") }
|
|
463
|
+
});
|
|
464
|
+
this.rel = rel;
|
|
465
|
+
this.expected = expected;
|
|
466
|
+
}
|
|
467
|
+
rel;
|
|
468
|
+
expected;
|
|
469
|
+
};
|
|
433
470
|
var KbMissingFlagValueError = class extends BaseError {
|
|
434
471
|
constructor(flag) {
|
|
435
472
|
super({
|
|
@@ -1150,9 +1187,135 @@ async function loadQmd(logger) {
|
|
|
1150
1187
|
}
|
|
1151
1188
|
}
|
|
1152
1189
|
|
|
1190
|
+
// src/record-types.ts
|
|
1191
|
+
var RECORD_TYPES = {
|
|
1192
|
+
fact: {
|
|
1193
|
+
purpose: "Observed or sourced fact",
|
|
1194
|
+
sections: ["Claim", "Evidence", "Implication"],
|
|
1195
|
+
initialStatus: "accepted"
|
|
1196
|
+
},
|
|
1197
|
+
requirement: {
|
|
1198
|
+
purpose: "Required behavior or outcome",
|
|
1199
|
+
sections: ["Claim", "Evidence", "Implication"],
|
|
1200
|
+
initialStatus: "proposed"
|
|
1201
|
+
},
|
|
1202
|
+
constraint: {
|
|
1203
|
+
purpose: "Limitation, compatibility boundary, policy, or restriction",
|
|
1204
|
+
sections: ["Claim", "Evidence", "Implication"],
|
|
1205
|
+
initialStatus: "accepted"
|
|
1206
|
+
},
|
|
1207
|
+
decision: {
|
|
1208
|
+
purpose: "Chosen or proposed direction",
|
|
1209
|
+
sections: ["Decision", "Rationale", "Rejected", "Impact"],
|
|
1210
|
+
initialStatus: "accepted"
|
|
1211
|
+
},
|
|
1212
|
+
assumption: {
|
|
1213
|
+
purpose: "Unsourced or not-yet-confirmed working assumption",
|
|
1214
|
+
sections: ["Claim", "Why we think so", "What would falsify it"],
|
|
1215
|
+
initialStatus: "draft"
|
|
1216
|
+
},
|
|
1217
|
+
"open-question": {
|
|
1218
|
+
purpose: "Question needing resolution",
|
|
1219
|
+
sections: ["Question", "Why it matters", "Default assumption"],
|
|
1220
|
+
initialStatus: "open"
|
|
1221
|
+
},
|
|
1222
|
+
risk: {
|
|
1223
|
+
purpose: "Something that can go wrong",
|
|
1224
|
+
sections: ["Risk", "Why it matters", "Mitigation", "Verification"],
|
|
1225
|
+
initialStatus: "open"
|
|
1226
|
+
},
|
|
1227
|
+
contract: {
|
|
1228
|
+
purpose: "API, data, event, schema, or permission contract",
|
|
1229
|
+
sections: ["Contract", "Producer", "Consumer", "Compatibility"],
|
|
1230
|
+
initialStatus: "proposed"
|
|
1231
|
+
},
|
|
1232
|
+
flow: {
|
|
1233
|
+
purpose: "Sequence, lifecycle, or state behavior",
|
|
1234
|
+
sections: ["Flow", "Trigger", "Steps", "Failure modes"],
|
|
1235
|
+
initialStatus: "accepted"
|
|
1236
|
+
},
|
|
1237
|
+
"affected-system": {
|
|
1238
|
+
purpose: "Component, service, package, integration, or external system",
|
|
1239
|
+
sections: ["System", "How it is affected", "Blast radius"],
|
|
1240
|
+
initialStatus: "accepted"
|
|
1241
|
+
},
|
|
1242
|
+
"test-obligation": {
|
|
1243
|
+
purpose: "Behavior or contract that must be verified",
|
|
1244
|
+
sections: ["Obligation", "Why it matters", "How to verify"],
|
|
1245
|
+
initialStatus: "open"
|
|
1246
|
+
},
|
|
1247
|
+
"source-note": {
|
|
1248
|
+
purpose: "Extracted note from source material",
|
|
1249
|
+
sections: ["Note", "Where it came from"],
|
|
1250
|
+
initialStatus: "accepted"
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
function isKbRecordType(value) {
|
|
1254
|
+
return Object.prototype.hasOwnProperty.call(RECORD_TYPES, value);
|
|
1255
|
+
}
|
|
1256
|
+
var KB_LINK_RELS = [
|
|
1257
|
+
"depends_on",
|
|
1258
|
+
"constrains",
|
|
1259
|
+
"informs",
|
|
1260
|
+
"blocks",
|
|
1261
|
+
"invalidates",
|
|
1262
|
+
"verified_by",
|
|
1263
|
+
"satisfies",
|
|
1264
|
+
"related_to"
|
|
1265
|
+
];
|
|
1266
|
+
var LINK_RELS = {
|
|
1267
|
+
depends_on: {
|
|
1268
|
+
purpose: "The source needs the target to hold; the source breaks if the target changes",
|
|
1269
|
+
phrase: "Depends on",
|
|
1270
|
+
dependant: "source"
|
|
1271
|
+
},
|
|
1272
|
+
constrains: {
|
|
1273
|
+
purpose: "The source bounds what the target may do; the target breaks if the constraint changes",
|
|
1274
|
+
phrase: "Constrains",
|
|
1275
|
+
dependant: "target"
|
|
1276
|
+
},
|
|
1277
|
+
informs: {
|
|
1278
|
+
purpose: "The source shaped the target without binding it; the target is what needs revisiting",
|
|
1279
|
+
phrase: "Informs",
|
|
1280
|
+
dependant: "target"
|
|
1281
|
+
},
|
|
1282
|
+
blocks: {
|
|
1283
|
+
purpose: "The target cannot proceed until the source is settled; the target is what waits",
|
|
1284
|
+
phrase: "Blocks",
|
|
1285
|
+
dependant: "target"
|
|
1286
|
+
},
|
|
1287
|
+
invalidates: {
|
|
1288
|
+
purpose: "The source makes the target no longer hold; the target is what stops holding",
|
|
1289
|
+
phrase: "Invalidates",
|
|
1290
|
+
dependant: "target"
|
|
1291
|
+
},
|
|
1292
|
+
verified_by: {
|
|
1293
|
+
purpose: "The target is the check that confirms the source; the source's confirmation moves with it",
|
|
1294
|
+
phrase: "Verified by",
|
|
1295
|
+
dependant: "source"
|
|
1296
|
+
},
|
|
1297
|
+
satisfies: {
|
|
1298
|
+
purpose: "The source discharges the target's requirement; the source must change if the requirement does",
|
|
1299
|
+
phrase: "Satisfies",
|
|
1300
|
+
dependant: "source"
|
|
1301
|
+
},
|
|
1302
|
+
related_to: {
|
|
1303
|
+
purpose: "A pointer worth following, with no claim of dependence",
|
|
1304
|
+
phrase: "Relates to",
|
|
1305
|
+
dependant: null
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
var KB_CAUSAL_LINK_RELS = KB_LINK_RELS.filter(
|
|
1309
|
+
(rel) => LINK_RELS[rel].dependant !== null
|
|
1310
|
+
);
|
|
1311
|
+
function isKbLinkRel(value) {
|
|
1312
|
+
return Object.prototype.hasOwnProperty.call(LINK_RELS, value);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1153
1315
|
// src/kb-edges.ts
|
|
1154
1316
|
var KB_EDGE_KINDS = [
|
|
1155
1317
|
"body-link",
|
|
1318
|
+
"typed-link",
|
|
1156
1319
|
"supersession",
|
|
1157
1320
|
"anchor",
|
|
1158
1321
|
"source"
|
|
@@ -1161,10 +1324,11 @@ var BODY_LINK_TARGET = new RegExp(
|
|
|
1161
1324
|
`\\]\\((${KB_CONCEPT_ID_PATTERN.source.replace(/^\^|\$$/g, "")})\\.md\\)`,
|
|
1162
1325
|
"g"
|
|
1163
1326
|
);
|
|
1164
|
-
|
|
1327
|
+
var DEFAULT_TYPED_LINK_RELS = KB_LINK_RELS;
|
|
1328
|
+
function neighbours(from, bundle, kinds = KB_EDGE_KINDS, linkRels = DEFAULT_TYPED_LINK_RELS) {
|
|
1165
1329
|
const found = /* @__PURE__ */ new Map();
|
|
1166
1330
|
for (const kind of kinds) {
|
|
1167
|
-
for (const record of edgeNeighbours(from, bundle, kind)) {
|
|
1331
|
+
for (const record of edgeNeighbours(from, bundle, kind, linkRels)) {
|
|
1168
1332
|
const existing = found.get(record.conceptId);
|
|
1169
1333
|
if (existing) {
|
|
1170
1334
|
if (!existing.via.includes(kind)) existing.via.push(kind);
|
|
@@ -1175,7 +1339,7 @@ function neighbours(from, bundle, kinds = KB_EDGE_KINDS) {
|
|
|
1175
1339
|
}
|
|
1176
1340
|
return [...found.values()];
|
|
1177
1341
|
}
|
|
1178
|
-
function edgeNeighbours(from, bundle, kind) {
|
|
1342
|
+
function edgeNeighbours(from, bundle, kind, linkRels = DEFAULT_TYPED_LINK_RELS) {
|
|
1179
1343
|
switch (kind) {
|
|
1180
1344
|
// A link whose target is not in the bundle is legal per compose.ts —
|
|
1181
1345
|
// records are routinely written before the ones they point at exist — so
|
|
@@ -1189,6 +1353,21 @@ function edgeNeighbours(from, bundle, kind) {
|
|
|
1189
1353
|
(candidate) => candidate.conceptId !== from.conceptId && targets.has(candidate.conceptId)
|
|
1190
1354
|
);
|
|
1191
1355
|
}
|
|
1356
|
+
// Outbound only, like `body-link`, and for the same reason: this is what
|
|
1357
|
+
// the record declares about itself. A missing target is legal — the walk
|
|
1358
|
+
// skips it, and `kb_validate` is what reports it as a warning. A rel
|
|
1359
|
+
// outside `linkRels` is skipped too, which is how an unknown rel stays
|
|
1360
|
+
// untraversable everywhere rather than one walk at a time.
|
|
1361
|
+
case "typed-link": {
|
|
1362
|
+
const allowed = new Set(linkRels);
|
|
1363
|
+
const targets = new Set(
|
|
1364
|
+
(from.frontmatter.strauss_links ?? []).filter((link2) => allowed.has(link2.rel)).map((link2) => link2.target)
|
|
1365
|
+
);
|
|
1366
|
+
if (!targets.size) return [];
|
|
1367
|
+
return bundle.filter(
|
|
1368
|
+
(candidate) => candidate.conceptId !== from.conceptId && targets.has(candidate.conceptId)
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1192
1371
|
// Both directions and both pointers: `supersede()` writes the pair, but a
|
|
1193
1372
|
// hand-edit can leave one side behind, and a walk trusting one pointer
|
|
1194
1373
|
// would miss a replacement the bundle openly declares.
|
|
@@ -1227,7 +1406,12 @@ function anchorsTouch(left, right) {
|
|
|
1227
1406
|
}
|
|
1228
1407
|
|
|
1229
1408
|
// src/trace.ts
|
|
1230
|
-
var TRACE_EDGES = [
|
|
1409
|
+
var TRACE_EDGES = [
|
|
1410
|
+
"typed-link",
|
|
1411
|
+
"supersession",
|
|
1412
|
+
"anchor",
|
|
1413
|
+
"source"
|
|
1414
|
+
];
|
|
1231
1415
|
function trace(seedId, bundle, options = {}) {
|
|
1232
1416
|
const edges = options.edges?.length ? options.edges : TRACE_EDGES;
|
|
1233
1417
|
const maxDepth = options.depth ?? 3;
|
|
@@ -1242,7 +1426,12 @@ function trace(seedId, bundle, options = {}) {
|
|
|
1242
1426
|
const next = [];
|
|
1243
1427
|
for (const from of frontier) {
|
|
1244
1428
|
for (const edge of edges) {
|
|
1245
|
-
for (const record of edgeNeighbours(
|
|
1429
|
+
for (const record of edgeNeighbours(
|
|
1430
|
+
from,
|
|
1431
|
+
bundle,
|
|
1432
|
+
edge,
|
|
1433
|
+
KB_CAUSAL_LINK_RELS
|
|
1434
|
+
)) {
|
|
1246
1435
|
const existing = reached.get(record.conceptId);
|
|
1247
1436
|
if (existing) {
|
|
1248
1437
|
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
@@ -1394,6 +1583,141 @@ function renderCatalogLine(entry) {
|
|
|
1394
1583
|
return `- ${parts.join(" \xB7 ")}`;
|
|
1395
1584
|
}
|
|
1396
1585
|
|
|
1586
|
+
// src/kb-links/inbound.ts
|
|
1587
|
+
function inboundIndex(bundle) {
|
|
1588
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1589
|
+
for (const record of bundle) {
|
|
1590
|
+
for (const link2 of record.frontmatter.strauss_links ?? []) {
|
|
1591
|
+
if (link2.target === record.conceptId) continue;
|
|
1592
|
+
const edges = byTarget.get(link2.target) ?? [];
|
|
1593
|
+
if (edges.some(
|
|
1594
|
+
(edge) => edge.from === record.conceptId && edge.rel === link2.rel
|
|
1595
|
+
)) {
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
edges.push({ from: record.conceptId, rel: link2.rel });
|
|
1599
|
+
byTarget.set(link2.target, edges);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return byTarget;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// src/kb-links/backlinks.ts
|
|
1606
|
+
function backlinks(targetId, bundle) {
|
|
1607
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
1608
|
+
if (!byId.has(targetId)) throw new KbRecordNotFoundError(targetId);
|
|
1609
|
+
const standingOf = new Map(
|
|
1610
|
+
adjudicate(bundle, bundle).map((hit) => [hit.record.conceptId, hit])
|
|
1611
|
+
);
|
|
1612
|
+
const rows = [];
|
|
1613
|
+
for (const edge of inboundIndex(bundle).get(targetId) ?? []) {
|
|
1614
|
+
const record = byId.get(edge.from);
|
|
1615
|
+
if (!record) continue;
|
|
1616
|
+
const hit = standingOf.get(edge.from);
|
|
1617
|
+
rows.push({
|
|
1618
|
+
...edge,
|
|
1619
|
+
title: record.frontmatter.title ?? null,
|
|
1620
|
+
standing: hit?.standing ?? "unsettled",
|
|
1621
|
+
warnings: hit?.warnings ?? []
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
target: targetId,
|
|
1626
|
+
backlinks: rows.sort(
|
|
1627
|
+
(left, right) => left.from.localeCompare(right.from) || left.rel.localeCompare(right.rel)
|
|
1628
|
+
)
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/kb-links/impact.ts
|
|
1633
|
+
function impact(targetId, bundle, options = {}) {
|
|
1634
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
1635
|
+
if (!byId.has(targetId)) throw new KbRecordNotFoundError(targetId);
|
|
1636
|
+
const rels = resolveRels(options.rels);
|
|
1637
|
+
const maxDepth = options.depth ?? Number.POSITIVE_INFINITY;
|
|
1638
|
+
const inbound = inboundIndex(bundle);
|
|
1639
|
+
const standingOf = new Map(
|
|
1640
|
+
adjudicate(bundle, bundle).map((hit) => [hit.record.conceptId, hit])
|
|
1641
|
+
);
|
|
1642
|
+
const reached = /* @__PURE__ */ new Map();
|
|
1643
|
+
const stopped = [];
|
|
1644
|
+
let frontier = [targetId];
|
|
1645
|
+
let depth = 0;
|
|
1646
|
+
while (frontier.length && depth < maxDepth) {
|
|
1647
|
+
depth += 1;
|
|
1648
|
+
const next = [];
|
|
1649
|
+
const consider = (dependantId, edge) => {
|
|
1650
|
+
if (dependantId === targetId) return;
|
|
1651
|
+
const existing = reached.get(dependantId);
|
|
1652
|
+
if (existing) {
|
|
1653
|
+
if (!hasEdge(existing.via, edge)) existing.via.push(edge);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const record = byId.get(dependantId);
|
|
1657
|
+
if (!record) return;
|
|
1658
|
+
const hit = standingOf.get(dependantId);
|
|
1659
|
+
const entry = {
|
|
1660
|
+
conceptId: dependantId,
|
|
1661
|
+
title: record.frontmatter.title ?? null,
|
|
1662
|
+
standing: hit?.standing ?? "unsettled",
|
|
1663
|
+
warnings: hit?.warnings ?? [],
|
|
1664
|
+
depth,
|
|
1665
|
+
via: [edge]
|
|
1666
|
+
};
|
|
1667
|
+
reached.set(dependantId, entry);
|
|
1668
|
+
if (entry.standing === "superseded" || entry.standing === "rejected") {
|
|
1669
|
+
stopped.push(dependantId);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
next.push(dependantId);
|
|
1673
|
+
};
|
|
1674
|
+
for (const id of frontier) {
|
|
1675
|
+
for (const edge of inbound.get(id) ?? []) {
|
|
1676
|
+
if (!rels.has(edge.rel)) continue;
|
|
1677
|
+
if (dependantEnd(edge.rel) !== "source") continue;
|
|
1678
|
+
consider(edge.from, { source: edge.from, target: id, rel: edge.rel });
|
|
1679
|
+
}
|
|
1680
|
+
for (const link2 of byId.get(id)?.frontmatter.strauss_links ?? []) {
|
|
1681
|
+
if (!rels.has(link2.rel)) continue;
|
|
1682
|
+
if (dependantEnd(link2.rel) !== "target") continue;
|
|
1683
|
+
if (link2.target === id) continue;
|
|
1684
|
+
consider(link2.target, {
|
|
1685
|
+
source: id,
|
|
1686
|
+
target: link2.target,
|
|
1687
|
+
rel: link2.rel
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
frontier = next;
|
|
1692
|
+
}
|
|
1693
|
+
return {
|
|
1694
|
+
root: targetId,
|
|
1695
|
+
impacted: [...reached.values()].sort(
|
|
1696
|
+
(left, right) => left.depth - right.depth || left.conceptId.localeCompare(right.conceptId)
|
|
1697
|
+
),
|
|
1698
|
+
stopped: stopped.sort(),
|
|
1699
|
+
truncated: frontier.length > 0,
|
|
1700
|
+
unexpanded: [...frontier].sort()
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
function resolveRels(rels) {
|
|
1704
|
+
if (!rels?.length) return new Set(KB_CAUSAL_LINK_RELS);
|
|
1705
|
+
for (const rel of rels) {
|
|
1706
|
+
if (!isKbLinkRel(rel) || LINK_RELS[rel].dependant === null) {
|
|
1707
|
+
throw new KbUnknownLinkRelError(rel, KB_CAUSAL_LINK_RELS);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
return new Set(rels);
|
|
1711
|
+
}
|
|
1712
|
+
function dependantEnd(rel) {
|
|
1713
|
+
return isKbLinkRel(rel) ? LINK_RELS[rel].dependant : null;
|
|
1714
|
+
}
|
|
1715
|
+
function hasEdge(edges, edge) {
|
|
1716
|
+
return edges.some(
|
|
1717
|
+
(existing) => existing.source === edge.source && existing.target === edge.target && existing.rel === edge.rel
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1397
1721
|
// src/kb-gitattributes.ts
|
|
1398
1722
|
var GITATTRIBUTES_FILE = ".gitattributes";
|
|
1399
1723
|
var UNION_MERGE_LINE = `${LOG_FILE} text eol=lf merge=union`;
|
|
@@ -1760,6 +2084,7 @@ ${answer}
|
|
|
1760
2084
|
const records = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
1761
2085
|
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
1762
2086
|
const approxTokens2 = records.reduce((total, hit) => total + estimateTokens(hit.record), 0) + superseded.reduce((total, entry) => total + estimateStubTokens(entry), 0);
|
|
2087
|
+
const bundleDigestValue = bundleDigest(records, superseded);
|
|
1763
2088
|
if (!options.all && approxTokens2 > budgetTokens) {
|
|
1764
2089
|
return {
|
|
1765
2090
|
loaded: false,
|
|
@@ -1770,7 +2095,8 @@ ${answer}
|
|
|
1770
2095
|
approxTokens: approxTokens2,
|
|
1771
2096
|
budgetTokens,
|
|
1772
2097
|
type: options.type
|
|
1773
|
-
})
|
|
2098
|
+
}),
|
|
2099
|
+
digest: bundleDigestValue
|
|
1774
2100
|
};
|
|
1775
2101
|
}
|
|
1776
2102
|
return {
|
|
@@ -1779,7 +2105,8 @@ ${answer}
|
|
|
1779
2105
|
tokensLoaded: approxTokens2,
|
|
1780
2106
|
budgetTokens: options.all ? null : budgetTokens,
|
|
1781
2107
|
records,
|
|
1782
|
-
superseded
|
|
2108
|
+
superseded,
|
|
2109
|
+
digest: bundleDigestValue
|
|
1783
2110
|
};
|
|
1784
2111
|
}
|
|
1785
2112
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
@@ -1794,6 +2121,14 @@ ${answer}
|
|
|
1794
2121
|
async pack(bundlePath2, rootId, options = {}) {
|
|
1795
2122
|
return pack(await this.list(bundlePath2), rootId, options);
|
|
1796
2123
|
}
|
|
2124
|
+
/** What breaks if this record changes. See `kb-links/impact.ts`. */
|
|
2125
|
+
async impact(bundlePath2, targetId, options = {}) {
|
|
2126
|
+
return impact(targetId, await this.list(bundlePath2), options);
|
|
2127
|
+
}
|
|
2128
|
+
/** Who points at this record, one hop. See `kb-links/backlinks.ts`. */
|
|
2129
|
+
async backlinks(bundlePath2, targetId) {
|
|
2130
|
+
return backlinks(targetId, await this.list(bundlePath2));
|
|
2131
|
+
}
|
|
1797
2132
|
/**
|
|
1798
2133
|
* The stored index, rebuilt if it disagrees with the records.
|
|
1799
2134
|
*
|
|
@@ -2096,76 +2431,29 @@ function normalizeActor(id) {
|
|
|
2096
2431
|
function digest(contents) {
|
|
2097
2432
|
return (0, import_node_crypto2.createHash)("sha256").update(contents).digest("hex");
|
|
2098
2433
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
sections: ["Claim", "Evidence", "Implication"],
|
|
2115
|
-
initialStatus: "accepted"
|
|
2116
|
-
},
|
|
2117
|
-
decision: {
|
|
2118
|
-
purpose: "Chosen or proposed direction",
|
|
2119
|
-
sections: ["Decision", "Rationale", "Rejected", "Impact"],
|
|
2120
|
-
initialStatus: "accepted"
|
|
2121
|
-
},
|
|
2122
|
-
assumption: {
|
|
2123
|
-
purpose: "Unsourced or not-yet-confirmed working assumption",
|
|
2124
|
-
sections: ["Claim", "Why we think so", "What would falsify it"],
|
|
2125
|
-
initialStatus: "draft"
|
|
2126
|
-
},
|
|
2127
|
-
"open-question": {
|
|
2128
|
-
purpose: "Question needing resolution",
|
|
2129
|
-
sections: ["Question", "Why it matters", "Default assumption"],
|
|
2130
|
-
initialStatus: "open"
|
|
2131
|
-
},
|
|
2132
|
-
risk: {
|
|
2133
|
-
purpose: "Something that can go wrong",
|
|
2134
|
-
sections: ["Risk", "Why it matters", "Mitigation", "Verification"],
|
|
2135
|
-
initialStatus: "open"
|
|
2136
|
-
},
|
|
2137
|
-
contract: {
|
|
2138
|
-
purpose: "API, data, event, schema, or permission contract",
|
|
2139
|
-
sections: ["Contract", "Producer", "Consumer", "Compatibility"],
|
|
2140
|
-
initialStatus: "proposed"
|
|
2141
|
-
},
|
|
2142
|
-
flow: {
|
|
2143
|
-
purpose: "Sequence, lifecycle, or state behavior",
|
|
2144
|
-
sections: ["Flow", "Trigger", "Steps", "Failure modes"],
|
|
2145
|
-
initialStatus: "accepted"
|
|
2146
|
-
},
|
|
2147
|
-
"affected-system": {
|
|
2148
|
-
purpose: "Component, service, package, integration, or external system",
|
|
2149
|
-
sections: ["System", "How it is affected", "Blast radius"],
|
|
2150
|
-
initialStatus: "accepted"
|
|
2151
|
-
},
|
|
2152
|
-
"test-obligation": {
|
|
2153
|
-
purpose: "Behavior or contract that must be verified",
|
|
2154
|
-
sections: ["Obligation", "Why it matters", "How to verify"],
|
|
2155
|
-
initialStatus: "open"
|
|
2156
|
-
},
|
|
2157
|
-
"source-note": {
|
|
2158
|
-
purpose: "Extracted note from source material",
|
|
2159
|
-
sections: ["Note", "Where it came from"],
|
|
2160
|
-
initialStatus: "accepted"
|
|
2161
|
-
}
|
|
2162
|
-
};
|
|
2163
|
-
function isKbRecordType(value) {
|
|
2164
|
-
return Object.prototype.hasOwnProperty.call(RECORD_TYPES, value);
|
|
2434
|
+
function bundleDigest(records, superseded) {
|
|
2435
|
+
const entries = [
|
|
2436
|
+
...records.map(
|
|
2437
|
+
(hit) => `${hit.record.conceptId}:current:${digest(
|
|
2438
|
+
stringifyMarkdownWithFrontmatter(
|
|
2439
|
+
hit.record.body,
|
|
2440
|
+
hit.record.frontmatter
|
|
2441
|
+
)
|
|
2442
|
+
)}`
|
|
2443
|
+
),
|
|
2444
|
+
...superseded.map(
|
|
2445
|
+
(entry) => `${entry.conceptId}:superseded:${digest(JSON.stringify(entry))}`
|
|
2446
|
+
)
|
|
2447
|
+
].sort();
|
|
2448
|
+
return digest(entries.join("\n"));
|
|
2165
2449
|
}
|
|
2166
2450
|
|
|
2167
2451
|
// src/compose.ts
|
|
2168
2452
|
var import_zod3 = require("zod");
|
|
2453
|
+
var composeLinkSchema = import_zod3.z.object({
|
|
2454
|
+
target: kbConceptIdSchema,
|
|
2455
|
+
rel: import_zod3.z.enum(KB_LINK_RELS)
|
|
2456
|
+
}).strict();
|
|
2169
2457
|
var composeInputSchema = import_zod3.z.object({
|
|
2170
2458
|
slug: import_zod3.z.string().min(1),
|
|
2171
2459
|
/** One line, in the reader's terms. Becomes OKF `title`. */
|
|
@@ -2192,6 +2480,18 @@ var composeInputSchema = import_zod3.z.object({
|
|
|
2192
2480
|
tags: import_zod3.z.array(import_zod3.z.string().min(1)).optional(),
|
|
2193
2481
|
/** Concept ids this record relates to; rendered as body links. */
|
|
2194
2482
|
relatedConceptIds: import_zod3.z.array(kbConceptIdSchema).optional(),
|
|
2483
|
+
/**
|
|
2484
|
+
* Typed causal edges, source → target: `{ target: "fact.b", rel:
|
|
2485
|
+
* "depends_on" }` on record A says A needs B. Stored in frontmatter and
|
|
2486
|
+
* also rendered as one prose sentence each, so the meaning survives a
|
|
2487
|
+
* reader that knows only OKF. The vocabulary goes into the description from
|
|
2488
|
+
* the same table the walk uses, so `kb_schema` emits it.
|
|
2489
|
+
*/
|
|
2490
|
+
links: import_zod3.z.array(composeLinkSchema).max(64).optional().describe(
|
|
2491
|
+
`Typed causal edges, source \u2192 target \u2014 a link on this record says this record <rel> the target. ${KB_LINK_RELS.map(
|
|
2492
|
+
(rel) => `${rel}: ${LINK_RELS[rel].purpose}`
|
|
2493
|
+
).join("; ")}.`
|
|
2494
|
+
),
|
|
2195
2495
|
/** Concept ids this record replaces. The store settles the backlinks. */
|
|
2196
2496
|
supersedes: import_zod3.z.array(kbConceptIdSchema).max(32).optional(),
|
|
2197
2497
|
materiality: import_zod3.z.enum(KB_MATERIALITIES).optional(),
|
|
@@ -2231,6 +2531,15 @@ function composeRecord(type, input, writtenBy, writtenAt) {
|
|
|
2231
2531
|
if (parsed.owner) frontmatter.strauss_owner = parsed.owner;
|
|
2232
2532
|
if (parsed.supersedes?.length)
|
|
2233
2533
|
frontmatter.strauss_supersedes = parsed.supersedes;
|
|
2534
|
+
const selfLink = parsed.links?.find(
|
|
2535
|
+
(link2) => link2.target === `${type}.${parsed.slug}`
|
|
2536
|
+
);
|
|
2537
|
+
if (selfLink) {
|
|
2538
|
+
throw new Error(
|
|
2539
|
+
`kb: ${type}.${parsed.slug} cannot ${selfLink.rel} itself \u2014 a link must name another record`
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
if (parsed.links?.length) frontmatter.strauss_links = parsed.links;
|
|
2234
2543
|
const blocks = [];
|
|
2235
2544
|
for (const heading of spec.sections) {
|
|
2236
2545
|
const text = sections[heading];
|
|
@@ -2242,6 +2551,11 @@ ${text}`);
|
|
|
2242
2551
|
for (const related of parsed.relatedConceptIds ?? []) {
|
|
2243
2552
|
blocks.push(`Relates to [${related}](${related}.md).`);
|
|
2244
2553
|
}
|
|
2554
|
+
for (const link2 of parsed.links ?? []) {
|
|
2555
|
+
blocks.push(
|
|
2556
|
+
`${LINK_RELS[link2.rel].phrase} [${link2.target}](${link2.target}.md).`
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2245
2559
|
if (parsed.sources?.length) {
|
|
2246
2560
|
blocks.push(
|
|
2247
2561
|
parsed.sources.map((source) => `[^${source.id}]: ${source.title ?? source.resource}`).join("\n")
|
|
@@ -2882,7 +3196,7 @@ function normalize(path) {
|
|
|
2882
3196
|
function validateBundle(records) {
|
|
2883
3197
|
const byId = new Map(records.map((record) => [record.conceptId, record]));
|
|
2884
3198
|
const problems = [];
|
|
2885
|
-
const report = (check, conceptId2, note) => problems.push({ check, conceptId: conceptId2, note });
|
|
3199
|
+
const report = (check, conceptId2, note, severity = "error") => problems.push({ check, conceptId: conceptId2, note, severity });
|
|
2886
3200
|
for (const record of records) {
|
|
2887
3201
|
const { conceptId: conceptId2, frontmatter: fm } = record;
|
|
2888
3202
|
if (!isKbRecordType(fm.type)) {
|
|
@@ -2906,6 +3220,36 @@ function validateBundle(records) {
|
|
|
2906
3220
|
report("supersedes", conceptId2, `${old} is not marked superseded`);
|
|
2907
3221
|
}
|
|
2908
3222
|
}
|
|
3223
|
+
for (const link2 of fm.strauss_links ?? []) {
|
|
3224
|
+
if (!isKbLinkRel(link2.rel)) {
|
|
3225
|
+
report(
|
|
3226
|
+
"link_rel",
|
|
3227
|
+
conceptId2,
|
|
3228
|
+
`unknown rel "${link2.rel}" on link to ${link2.target} \u2014 expected one of ${KB_LINK_RELS.join(", ")}`
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
if (!KB_CONCEPT_ID_PATTERN.test(link2.target)) {
|
|
3232
|
+
report(
|
|
3233
|
+
"link_target",
|
|
3234
|
+
conceptId2,
|
|
3235
|
+
`target "${link2.target}" is not a valid concept id \u2014 expected <type>.<slug>, both kebab-case`
|
|
3236
|
+
);
|
|
3237
|
+
} else if (link2.target === conceptId2) {
|
|
3238
|
+
report(
|
|
3239
|
+
"link_target",
|
|
3240
|
+
conceptId2,
|
|
3241
|
+
`links to itself (${link2.rel})`,
|
|
3242
|
+
"warning"
|
|
3243
|
+
);
|
|
3244
|
+
} else if (!byId.has(link2.target)) {
|
|
3245
|
+
report(
|
|
3246
|
+
"link_target",
|
|
3247
|
+
conceptId2,
|
|
3248
|
+
`target ${link2.target} is not in the bundle`,
|
|
3249
|
+
"warning"
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
2909
3253
|
if (fm.strauss_assumption && fm.sources?.length) {
|
|
2910
3254
|
report("assumption", conceptId2, "marked an assumption but cites sources");
|
|
2911
3255
|
}
|
|
@@ -3200,7 +3544,7 @@ var decisionInputSchema = composeInputSchema.omit({ sections: true }).extend({
|
|
|
3200
3544
|
impact: import_zod6.z.string().min(1).optional()
|
|
3201
3545
|
}).strict();
|
|
3202
3546
|
function composeDecisionRecord(input, writtenBy, writtenAt) {
|
|
3203
|
-
const { alternative, impact, ...rest } = input;
|
|
3547
|
+
const { alternative, impact: impact2, ...rest } = input;
|
|
3204
3548
|
return composeRecord(
|
|
3205
3549
|
DECISION_TYPE,
|
|
3206
3550
|
{
|
|
@@ -3209,7 +3553,7 @@ function composeDecisionRecord(input, writtenBy, writtenAt) {
|
|
|
3209
3553
|
Decision: input.title,
|
|
3210
3554
|
Rationale: input.why,
|
|
3211
3555
|
...alternative ? { Rejected: alternative } : {},
|
|
3212
|
-
...
|
|
3556
|
+
...impact2 ? { Impact: impact2 } : {}
|
|
3213
3557
|
}
|
|
3214
3558
|
},
|
|
3215
3559
|
writtenBy,
|
|
@@ -3448,16 +3792,28 @@ var answerCommand = define({
|
|
|
3448
3792
|
}
|
|
3449
3793
|
});
|
|
3450
3794
|
|
|
3451
|
-
// src/commands/
|
|
3795
|
+
// src/commands/backlinks.ts
|
|
3452
3796
|
var import_zod10 = require("zod");
|
|
3797
|
+
var backlinksCommand = define({
|
|
3798
|
+
name: "backlinks",
|
|
3799
|
+
tool: "kb_backlinks",
|
|
3800
|
+
usage: "backlinks <concept-id>",
|
|
3801
|
+
description: "Who points at this record: every inbound typed causal link (`strauss_links`), one hop, every rel including `related_to`, each with its rel and the standing of the record that made it. Use it when you need the exact edges \u2014 reviewing or renaming a record.",
|
|
3802
|
+
input: import_zod10.z.object({ bundlePath, conceptId }),
|
|
3803
|
+
fromArgv: (argv, path) => ({ bundlePath: path, conceptId: argv[1] }),
|
|
3804
|
+
run: async ({ store }, { bundlePath: path, conceptId: id }) => store.backlinks(path, id)
|
|
3805
|
+
});
|
|
3806
|
+
|
|
3807
|
+
// src/commands/catalog.ts
|
|
3808
|
+
var import_zod11 = require("zod");
|
|
3453
3809
|
var catalogCommand = define({
|
|
3454
3810
|
name: "catalog",
|
|
3455
3811
|
tool: "kb_catalog",
|
|
3456
3812
|
usage: "catalog [type]",
|
|
3457
3813
|
description: "Lists every record as one line \u2014 concept id, type, title, standing, and a stale flag \u2014 at roughly thirty tokens each. Pick this over kb_load once kb_load refuses: kb_catalog never refuses. Superseded records show only their replacement; fetch bodies with kb_load, kb_pack, kb_query, or kb_trace.",
|
|
3458
|
-
input:
|
|
3814
|
+
input: import_zod11.z.object({
|
|
3459
3815
|
bundlePath,
|
|
3460
|
-
type:
|
|
3816
|
+
type: import_zod11.z.enum(KB_RECORD_TYPES).optional()
|
|
3461
3817
|
}),
|
|
3462
3818
|
fromArgv: (argv, path) => ({
|
|
3463
3819
|
bundlePath: path,
|
|
@@ -3512,26 +3868,26 @@ function count(value, noun) {
|
|
|
3512
3868
|
}
|
|
3513
3869
|
|
|
3514
3870
|
// src/commands/context.ts
|
|
3515
|
-
var
|
|
3871
|
+
var import_zod12 = require("zod");
|
|
3516
3872
|
var contextCommand = define({
|
|
3517
3873
|
name: "context",
|
|
3518
3874
|
tool: "kb_context",
|
|
3519
3875
|
usage: "context [--profile NAME] [--budget N] [--full-under N] [--format json] [--event NAME]",
|
|
3520
3876
|
description: "The pinned-base index block, for injection at every context birth \u2014 startup, clear, resume, and after compaction. An index, not the content: concept ids, titles and standing, with the bodies left behind kb_load at the point of use. Emits nothing when nothing is pinned. Refuses with the list of bases and their sizes rather than truncating past its budget. Budgets resolve most-specific-first: explicit flags, then the workspace manifests' `context` tables (per profile, over their `default`), then the built-in profile (session-start, compact, turn), then package defaults \u2014 so a repo tunes its own numbers in .strauss/kb-pins.json without touching hook commands. Like kb_schema and kb_types this takes no bundlePath \u2014 it reads the workspace pin manifests, because which bases a session should see is workspace state, not a property of one base.",
|
|
3521
|
-
input:
|
|
3522
|
-
budgetTokens:
|
|
3877
|
+
input: import_zod12.z.object({
|
|
3878
|
+
budgetTokens: import_zod12.z.number().int().positive().optional().describe(
|
|
3523
3879
|
"Ceiling on the whole emitted block; past it the command refuses with a list of bases rather than truncating. Defaults to 4000."
|
|
3524
3880
|
),
|
|
3525
|
-
fullUnderTokens:
|
|
3881
|
+
fullUnderTokens: import_zod12.z.number().int().positive().optional().describe(
|
|
3526
3882
|
"Per-base rendering threshold, applied before the budget: a base whose complete load fits under this arrives as full records instead of index lines, and the whole block still answers to budgetTokens. Off by default \u2014 index-only is the safe default at a context birth, because injected bodies outlive the qualifiers on them; the session-start profile opts tiny bases in at 1500."
|
|
3527
3883
|
),
|
|
3528
|
-
profile:
|
|
3884
|
+
profile: import_zod12.z.string().optional().describe(
|
|
3529
3885
|
"Named budget set: built-ins are session-start (full-under 1500), compact and turn (budget 2500); the manifests' `context` tables override per repo. Unknown names fall through to defaults rather than failing."
|
|
3530
3886
|
),
|
|
3531
|
-
format:
|
|
3887
|
+
format: import_zod12.z.enum(["markdown", "json"]).optional().describe(
|
|
3532
3888
|
"CLI envelope for hook protocols that require strict JSON on stdout. MCP callers omit this \u2014 the block itself is identical."
|
|
3533
3889
|
),
|
|
3534
|
-
event:
|
|
3890
|
+
event: import_zod12.z.string().optional().describe(
|
|
3535
3891
|
"hookEventName stamped into the JSON envelope. Only meaningful with format=json."
|
|
3536
3892
|
)
|
|
3537
3893
|
}),
|
|
@@ -3567,14 +3923,14 @@ var contextCommand = define({
|
|
|
3567
3923
|
});
|
|
3568
3924
|
|
|
3569
3925
|
// src/commands/doctor.ts
|
|
3570
|
-
var
|
|
3571
|
-
var days = (what, fallback) =>
|
|
3926
|
+
var import_zod13 = require("zod");
|
|
3927
|
+
var days = (what, fallback) => import_zod13.z.number().int().positive().optional().describe(`${what} Defaults to ${fallback}.`);
|
|
3572
3928
|
var doctorCommand = define({
|
|
3573
3929
|
name: "doctor",
|
|
3574
3930
|
tool: "kb_doctor",
|
|
3575
3931
|
usage: "doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--repo-root PATH] [--strict]",
|
|
3576
3932
|
description: "Read-only health sweep: expired, expiring, unverified, aging, orphaned, broken-supersession, superseded-but-cited, drifted anchors. Every group is reported even when empty; nothing is written or re-stamped. Use it when picking up a base you have not touched in a while; kb_validate only checks that pointers between records agree.",
|
|
3577
|
-
input:
|
|
3933
|
+
input: import_zod13.z.object({
|
|
3578
3934
|
bundlePath,
|
|
3579
3935
|
repoRoot: REPO_ROOT,
|
|
3580
3936
|
expiringDays: days(
|
|
@@ -3589,7 +3945,7 @@ var doctorCommand = define({
|
|
|
3589
3945
|
"How long a record may stay `open` or `proposed` before `aging` reports it, in days.",
|
|
3590
3946
|
DEFAULT_AGING_DAYS
|
|
3591
3947
|
),
|
|
3592
|
-
strict:
|
|
3948
|
+
strict: import_zod13.z.boolean().optional().describe(
|
|
3593
3949
|
"Turn an expired record into a non-zero exit for the CLI. No effect on the report itself."
|
|
3594
3950
|
)
|
|
3595
3951
|
}),
|
|
@@ -3664,14 +4020,47 @@ function render2(result) {
|
|
|
3664
4020
|
return lines.join("\n");
|
|
3665
4021
|
}
|
|
3666
4022
|
|
|
4023
|
+
// src/commands/impact.ts
|
|
4024
|
+
var import_zod14 = require("zod");
|
|
4025
|
+
var impactCommand = define({
|
|
4026
|
+
name: "impact",
|
|
4027
|
+
tool: "kb_impact",
|
|
4028
|
+
usage: "impact <concept-id> [--depth N] [--rels a,b]",
|
|
4029
|
+
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.",
|
|
4030
|
+
input: import_zod14.z.object({
|
|
4031
|
+
bundlePath,
|
|
4032
|
+
conceptId,
|
|
4033
|
+
depth: import_zod14.z.number().int().positive().optional().describe(
|
|
4034
|
+
"Hops out from the record. Unbounded when omitted; a walk this cuts reports truncated: true."
|
|
4035
|
+
),
|
|
4036
|
+
rels: import_zod14.z.array(import_zod14.z.enum(KB_CAUSAL_LINK_RELS)).optional().describe(
|
|
4037
|
+
"Narrow which rels the walk follows. Defaults to every rel that carries a dependence \u2014 all but related_to."
|
|
4038
|
+
)
|
|
4039
|
+
}),
|
|
4040
|
+
fromArgv: (argv, path) => {
|
|
4041
|
+
const depth = argvFlag(argv, "--depth");
|
|
4042
|
+
const rels = argvFlag(argv, "--rels");
|
|
4043
|
+
return {
|
|
4044
|
+
bundlePath: path,
|
|
4045
|
+
conceptId: argv[1],
|
|
4046
|
+
...depth ? { depth: Number(depth) } : {},
|
|
4047
|
+
...rels ? { rels: rels.split(",").filter(Boolean) } : {}
|
|
4048
|
+
};
|
|
4049
|
+
},
|
|
4050
|
+
run: async ({ store }, { bundlePath: path, conceptId: id, depth, rels }) => store.impact(path, id, {
|
|
4051
|
+
...depth !== void 0 ? { depth } : {},
|
|
4052
|
+
...rels?.length ? { rels } : {}
|
|
4053
|
+
})
|
|
4054
|
+
});
|
|
4055
|
+
|
|
3667
4056
|
// src/commands/list.ts
|
|
3668
|
-
var
|
|
4057
|
+
var import_zod15 = require("zod");
|
|
3669
4058
|
var listCommand = define({
|
|
3670
4059
|
name: "list",
|
|
3671
4060
|
tool: "kb_list",
|
|
3672
4061
|
usage: "list [type]",
|
|
3673
4062
|
description: "Every record, optionally narrowed to one type. Use kb_query when you have a question; this is for enumerating.",
|
|
3674
|
-
input:
|
|
4063
|
+
input: import_zod15.z.object({ bundlePath, type: import_zod15.z.enum(KB_RECORD_TYPES).optional() }),
|
|
3675
4064
|
fromArgv: (argv, path) => ({ bundlePath: path, type: argv[1] }),
|
|
3676
4065
|
run: async ({ store }, { bundlePath: path, type }) => (await store.list(path, type)).map((record) => ({
|
|
3677
4066
|
conceptId: record.conceptId,
|
|
@@ -3683,17 +4072,17 @@ var listCommand = define({
|
|
|
3683
4072
|
});
|
|
3684
4073
|
|
|
3685
4074
|
// src/commands/load.ts
|
|
3686
|
-
var
|
|
4075
|
+
var import_zod16 = require("zod");
|
|
3687
4076
|
var loadCommand = define({
|
|
3688
4077
|
name: "load",
|
|
3689
4078
|
tool: "kb_load",
|
|
3690
4079
|
usage: "load [type] [--budget N | --all] [--repo-root PATH]",
|
|
3691
|
-
description: "Loads the whole knowledge base at once, each record with its standing. Superseded records arrive as stubs
|
|
3692
|
-
input:
|
|
4080
|
+
description: "Loads the whole knowledge base at once, each record with its standing. Superseded records arrive as stubs; rejected and open records arrive whole. Refuses past the token budget \u2014 call kb_catalog, kb_pack on it; `all` bypasses the budget. Never read record files directly. Cache-stable; `digest` is the base's content stamp \u2014 hooks use it to tell you when to reload.",
|
|
4081
|
+
input: import_zod16.z.object({
|
|
3693
4082
|
bundlePath,
|
|
3694
|
-
type:
|
|
3695
|
-
budgetTokens:
|
|
3696
|
-
all:
|
|
4083
|
+
type: import_zod16.z.enum(KB_RECORD_TYPES).optional(),
|
|
4084
|
+
budgetTokens: import_zod16.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
|
|
4085
|
+
all: import_zod16.z.boolean().optional().describe(
|
|
3697
4086
|
"Loads the entire base regardless of size, bypassing the token budget; mutually exclusive with budgetTokens."
|
|
3698
4087
|
),
|
|
3699
4088
|
repoRoot: REPO_ROOT
|
|
@@ -3735,25 +4124,25 @@ var loadCommand = define({
|
|
|
3735
4124
|
});
|
|
3736
4125
|
|
|
3737
4126
|
// src/commands/log.ts
|
|
3738
|
-
var
|
|
4127
|
+
var import_zod17 = require("zod");
|
|
3739
4128
|
var logCommand = define({
|
|
3740
4129
|
name: "log",
|
|
3741
4130
|
tool: "kb_log",
|
|
3742
4131
|
usage: "log",
|
|
3743
4132
|
description: "What touched what, and when. The only artifact here that cannot be reconstructed from the records, so malformed lines are reported rather than repaired.",
|
|
3744
|
-
input:
|
|
4133
|
+
input: import_zod17.z.object({ bundlePath }),
|
|
3745
4134
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3746
4135
|
run: ({ store }, { bundlePath: path }) => store.readLog(path)
|
|
3747
4136
|
});
|
|
3748
4137
|
|
|
3749
4138
|
// src/commands/no-decision.ts
|
|
3750
|
-
var
|
|
4139
|
+
var import_zod18 = require("zod");
|
|
3751
4140
|
var noDecisionCommand = define({
|
|
3752
4141
|
name: "no-decision",
|
|
3753
4142
|
tool: "kb_no_decision",
|
|
3754
4143
|
usage: "no-decision <reason...>",
|
|
3755
4144
|
description: 'Claim in one sentence that there was nothing to decide. Gating on "did you write a decision?" rewards writing a junk one; gating on "did you answer?" does not, so silence has to be expressible. Idempotent \u2014 restating it is not a collision.',
|
|
3756
|
-
input:
|
|
4145
|
+
input: import_zod18.z.object({ bundlePath, reason: import_zod18.z.string().min(1) }),
|
|
3757
4146
|
fromArgv: (argv, path) => ({
|
|
3758
4147
|
bundlePath: path,
|
|
3759
4148
|
reason: argv.slice(1).join(" ").trim()
|
|
@@ -3770,20 +4159,20 @@ var noDecisionCommand = define({
|
|
|
3770
4159
|
});
|
|
3771
4160
|
|
|
3772
4161
|
// src/commands/pack.ts
|
|
3773
|
-
var
|
|
4162
|
+
var import_zod19 = require("zod");
|
|
3774
4163
|
var packCommand = define({
|
|
3775
4164
|
name: "pack",
|
|
3776
4165
|
tool: "kb_pack",
|
|
3777
4166
|
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
3778
4167
|
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.",
|
|
3779
|
-
input:
|
|
4168
|
+
input: import_zod19.z.object({
|
|
3780
4169
|
bundlePath,
|
|
3781
4170
|
conceptId,
|
|
3782
|
-
hops:
|
|
3783
|
-
maxNodes:
|
|
4171
|
+
hops: import_zod19.z.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
|
|
4172
|
+
maxNodes: import_zod19.z.number().int().positive().optional().describe(
|
|
3784
4173
|
"How many records the pack may hold, root included. Defaults to 20."
|
|
3785
4174
|
),
|
|
3786
|
-
budgetTokens:
|
|
4175
|
+
budgetTokens: import_zod19.z.number().int().positive().optional().describe(
|
|
3787
4176
|
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
3788
4177
|
)
|
|
3789
4178
|
}),
|
|
@@ -3870,22 +4259,22 @@ function warningLabel(warning) {
|
|
|
3870
4259
|
}
|
|
3871
4260
|
|
|
3872
4261
|
// src/commands/pin.ts
|
|
3873
|
-
var
|
|
4262
|
+
var import_zod20 = require("zod");
|
|
3874
4263
|
var pinCommand = define({
|
|
3875
4264
|
name: "pin",
|
|
3876
4265
|
tool: "kb_pin",
|
|
3877
4266
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
3878
4267
|
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.",
|
|
3879
|
-
input:
|
|
4268
|
+
input: import_zod20.z.object({
|
|
3880
4269
|
bundlePath,
|
|
3881
|
-
mode:
|
|
4270
|
+
mode: import_zod20.z.enum(["full", "index"]).optional().describe(
|
|
3882
4271
|
"full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
|
|
3883
4272
|
),
|
|
3884
|
-
profiles:
|
|
3885
|
-
layer:
|
|
4273
|
+
profiles: import_zod20.z.array(import_zod20.z.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
|
|
4274
|
+
layer: import_zod20.z.enum(["project", "local", "user"]).optional().describe(
|
|
3886
4275
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
3887
4276
|
),
|
|
3888
|
-
frozen:
|
|
4277
|
+
frozen: import_zod20.z.boolean().optional().describe(
|
|
3889
4278
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
3890
4279
|
)
|
|
3891
4280
|
}),
|
|
@@ -3914,29 +4303,29 @@ var pinCommand = define({
|
|
|
3914
4303
|
});
|
|
3915
4304
|
|
|
3916
4305
|
// src/commands/pins.ts
|
|
3917
|
-
var
|
|
4306
|
+
var import_zod21 = require("zod");
|
|
3918
4307
|
var pinsCommand = define({
|
|
3919
4308
|
name: "pins",
|
|
3920
4309
|
tool: "kb_pins",
|
|
3921
4310
|
usage: "pins",
|
|
3922
4311
|
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.",
|
|
3923
|
-
input:
|
|
4312
|
+
input: import_zod21.z.object({}),
|
|
3924
4313
|
fromArgv: () => ({}),
|
|
3925
4314
|
run: ({ store }) => listPins(store, process.cwd())
|
|
3926
4315
|
});
|
|
3927
4316
|
|
|
3928
4317
|
// src/commands/query.ts
|
|
3929
|
-
var
|
|
4318
|
+
var import_zod22 = require("zod");
|
|
3930
4319
|
var queryCommand = define({
|
|
3931
4320
|
name: "query",
|
|
3932
4321
|
tool: "kb_query",
|
|
3933
4322
|
usage: "query <text...> [--repo-root PATH]",
|
|
3934
|
-
description: "Search
|
|
3935
|
-
input:
|
|
4323
|
+
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.",
|
|
4324
|
+
input: import_zod22.z.object({
|
|
3936
4325
|
bundlePath,
|
|
3937
|
-
text:
|
|
3938
|
-
type:
|
|
3939
|
-
includeNonCurrent:
|
|
4326
|
+
text: import_zod22.z.string().optional(),
|
|
4327
|
+
type: import_zod22.z.enum(KB_RECORD_TYPES).optional(),
|
|
4328
|
+
includeNonCurrent: import_zod22.z.boolean().optional(),
|
|
3940
4329
|
repoRoot: REPO_ROOT
|
|
3941
4330
|
}),
|
|
3942
4331
|
// `--repo-root` is a flag, so its value must not fall into the search text.
|
|
@@ -3968,40 +4357,40 @@ var queryCommand = define({
|
|
|
3968
4357
|
});
|
|
3969
4358
|
|
|
3970
4359
|
// src/commands/read-index.ts
|
|
3971
|
-
var
|
|
4360
|
+
var import_zod23 = require("zod");
|
|
3972
4361
|
var readIndexCommand = define({
|
|
3973
4362
|
name: "index",
|
|
3974
4363
|
tool: "kb_index",
|
|
3975
4364
|
usage: "index",
|
|
3976
4365
|
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.",
|
|
3977
|
-
input:
|
|
4366
|
+
input: import_zod23.z.object({ bundlePath }),
|
|
3978
4367
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3979
4368
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
3980
4369
|
});
|
|
3981
4370
|
|
|
3982
4371
|
// src/commands/schema.ts
|
|
3983
|
-
var
|
|
4372
|
+
var import_zod24 = require("zod");
|
|
3984
4373
|
var schemaCommand = define({
|
|
3985
4374
|
name: "schema",
|
|
3986
4375
|
tool: "kb_schema",
|
|
3987
4376
|
usage: "schema",
|
|
3988
4377
|
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.",
|
|
3989
|
-
input:
|
|
4378
|
+
input: import_zod24.z.object({}),
|
|
3990
4379
|
fromArgv: () => ({}),
|
|
3991
4380
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
3992
4381
|
});
|
|
3993
4382
|
|
|
3994
4383
|
// src/commands/status.ts
|
|
3995
|
-
var
|
|
4384
|
+
var import_zod25 = require("zod");
|
|
3996
4385
|
var statusCommand = define({
|
|
3997
4386
|
name: "status",
|
|
3998
4387
|
tool: "kb_status",
|
|
3999
4388
|
usage: "status <concept-id> <status>",
|
|
4000
4389
|
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.",
|
|
4001
|
-
input:
|
|
4390
|
+
input: import_zod25.z.object({
|
|
4002
4391
|
bundlePath,
|
|
4003
4392
|
conceptId,
|
|
4004
|
-
status:
|
|
4393
|
+
status: import_zod25.z.enum(KB_RECORD_STATUSES)
|
|
4005
4394
|
}),
|
|
4006
4395
|
fromArgv: (argv, path) => ({
|
|
4007
4396
|
bundlePath: path,
|
|
@@ -4016,13 +4405,13 @@ var statusCommand = define({
|
|
|
4016
4405
|
});
|
|
4017
4406
|
|
|
4018
4407
|
// src/commands/supersede.ts
|
|
4019
|
-
var
|
|
4408
|
+
var import_zod26 = require("zod");
|
|
4020
4409
|
var supersedeCommand = define({
|
|
4021
4410
|
name: "supersede",
|
|
4022
4411
|
tool: "kb_supersede",
|
|
4023
4412
|
usage: "supersede <concept-id> <replacement-id>",
|
|
4024
4413
|
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.",
|
|
4025
|
-
input:
|
|
4414
|
+
input: import_zod26.z.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
4026
4415
|
fromArgv: (argv, path) => ({
|
|
4027
4416
|
bundlePath: path,
|
|
4028
4417
|
conceptId: argv[1],
|
|
@@ -4036,16 +4425,16 @@ var supersedeCommand = define({
|
|
|
4036
4425
|
});
|
|
4037
4426
|
|
|
4038
4427
|
// src/commands/sync-instructions.ts
|
|
4039
|
-
var
|
|
4428
|
+
var import_zod27 = require("zod");
|
|
4040
4429
|
var syncInstructionsCommand = define({
|
|
4041
4430
|
name: "sync-instructions",
|
|
4042
4431
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
4043
4432
|
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.",
|
|
4044
|
-
input:
|
|
4045
|
-
file:
|
|
4046
|
-
budgetTokens:
|
|
4047
|
-
fullUnderTokens:
|
|
4048
|
-
profile:
|
|
4433
|
+
input: import_zod27.z.object({
|
|
4434
|
+
file: import_zod27.z.string().min(1).describe("The instruction file to edit in place."),
|
|
4435
|
+
budgetTokens: import_zod27.z.number().int().positive().optional(),
|
|
4436
|
+
fullUnderTokens: import_zod27.z.number().int().positive().optional(),
|
|
4437
|
+
profile: import_zod27.z.string().optional()
|
|
4049
4438
|
}),
|
|
4050
4439
|
fromArgv: (argv) => {
|
|
4051
4440
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -4071,17 +4460,17 @@ var syncInstructionsCommand = define({
|
|
|
4071
4460
|
});
|
|
4072
4461
|
|
|
4073
4462
|
// src/commands/trace.ts
|
|
4074
|
-
var
|
|
4463
|
+
var import_zod28 = require("zod");
|
|
4075
4464
|
var traceCommand = define({
|
|
4076
4465
|
name: "trace",
|
|
4077
4466
|
tool: "kb_trace",
|
|
4078
4467
|
usage: "trace <concept-id> [edges...]",
|
|
4079
4468
|
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.',
|
|
4080
|
-
input:
|
|
4469
|
+
input: import_zod28.z.object({
|
|
4081
4470
|
bundlePath,
|
|
4082
4471
|
conceptId,
|
|
4083
|
-
edges:
|
|
4084
|
-
depth:
|
|
4472
|
+
edges: import_zod28.z.array(import_zod28.z.enum(TRACE_EDGES)).optional(),
|
|
4473
|
+
depth: import_zod28.z.number().int().positive().optional()
|
|
4085
4474
|
}),
|
|
4086
4475
|
fromArgv: (argv, path) => ({
|
|
4087
4476
|
bundlePath: path,
|
|
@@ -4103,53 +4492,56 @@ var traceCommand = define({
|
|
|
4103
4492
|
});
|
|
4104
4493
|
|
|
4105
4494
|
// src/commands/types.ts
|
|
4106
|
-
var
|
|
4495
|
+
var import_zod29 = require("zod");
|
|
4107
4496
|
var typesCommand = define({
|
|
4108
4497
|
name: "types",
|
|
4109
4498
|
tool: "kb_types",
|
|
4110
4499
|
usage: "types",
|
|
4111
4500
|
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.",
|
|
4112
|
-
input:
|
|
4501
|
+
input: import_zod29.z.object({}),
|
|
4113
4502
|
fromArgv: () => ({}),
|
|
4114
4503
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
4115
4504
|
});
|
|
4116
4505
|
|
|
4117
4506
|
// src/commands/unpin.ts
|
|
4118
|
-
var
|
|
4507
|
+
var import_zod30 = require("zod");
|
|
4119
4508
|
var unpinCommand = define({
|
|
4120
4509
|
name: "unpin",
|
|
4121
4510
|
tool: "kb_unpin",
|
|
4122
4511
|
usage: "unpin [bundle-path]",
|
|
4123
4512
|
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.",
|
|
4124
|
-
input:
|
|
4513
|
+
input: import_zod30.z.object({ bundlePath }),
|
|
4125
4514
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
4126
4515
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
4127
4516
|
});
|
|
4128
4517
|
|
|
4129
4518
|
// src/commands/validate.ts
|
|
4130
|
-
var
|
|
4519
|
+
var import_zod31 = require("zod");
|
|
4131
4520
|
var validateCommand = define({
|
|
4132
4521
|
name: "validate",
|
|
4133
4522
|
tool: "kb_validate",
|
|
4134
4523
|
usage: "validate",
|
|
4135
|
-
description: "Check pointers no single record can see: supersession links that disagree between the two records, and assumptions that cite sources.
|
|
4136
|
-
input:
|
|
4524
|
+
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.",
|
|
4525
|
+
input: import_zod31.z.object({ bundlePath }),
|
|
4137
4526
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
4138
4527
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
4139
|
-
|
|
4528
|
+
// Warnings never fail the exit code; every other severity does.
|
|
4529
|
+
failsWhen: (result) => Array.isArray(result) && result.some(
|
|
4530
|
+
(problem) => problem.severity !== "warning"
|
|
4531
|
+
)
|
|
4140
4532
|
});
|
|
4141
4533
|
|
|
4142
4534
|
// src/commands/verify.ts
|
|
4143
|
-
var
|
|
4535
|
+
var import_zod32 = require("zod");
|
|
4144
4536
|
var verifyCommand = define({
|
|
4145
4537
|
name: "verify",
|
|
4146
4538
|
tool: "kb_verify",
|
|
4147
4539
|
usage: "verify <concept-id> --note <text>",
|
|
4148
4540
|
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.",
|
|
4149
|
-
input:
|
|
4541
|
+
input: import_zod32.z.object({
|
|
4150
4542
|
bundlePath,
|
|
4151
4543
|
conceptId,
|
|
4152
|
-
note:
|
|
4544
|
+
note: import_zod32.z.string().refine((s) => s.trim().length > 0, {
|
|
4153
4545
|
message: "note must say what the check found"
|
|
4154
4546
|
})
|
|
4155
4547
|
}),
|
|
@@ -4169,7 +4561,7 @@ var verifyCommand = define({
|
|
|
4169
4561
|
});
|
|
4170
4562
|
|
|
4171
4563
|
// src/commands/write.ts
|
|
4172
|
-
var
|
|
4564
|
+
var import_zod33 = require("zod");
|
|
4173
4565
|
var writeCommand = define({
|
|
4174
4566
|
name: "write",
|
|
4175
4567
|
tool: "kb_write",
|
|
@@ -4183,9 +4575,9 @@ var writeCommand = define({
|
|
|
4183
4575
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
4184
4576
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
4185
4577
|
].join("\n"),
|
|
4186
|
-
input:
|
|
4578
|
+
input: import_zod33.z.object({
|
|
4187
4579
|
bundlePath,
|
|
4188
|
-
type:
|
|
4580
|
+
type: import_zod33.z.enum(KB_RECORD_TYPES),
|
|
4189
4581
|
input: composeInputSchema
|
|
4190
4582
|
}),
|
|
4191
4583
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -4209,7 +4601,7 @@ var writeCommand = define({
|
|
|
4209
4601
|
});
|
|
4210
4602
|
|
|
4211
4603
|
// src/commands/write-decision.ts
|
|
4212
|
-
var
|
|
4604
|
+
var import_zod34 = require("zod");
|
|
4213
4605
|
var writeDecisionCommand = define({
|
|
4214
4606
|
name: "write-decision",
|
|
4215
4607
|
tool: "kb_write_decision",
|
|
@@ -4222,7 +4614,7 @@ var writeDecisionCommand = define({
|
|
|
4222
4614
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
4223
4615
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
4224
4616
|
].join("\n"),
|
|
4225
|
-
input:
|
|
4617
|
+
input: import_zod34.z.object({ bundlePath, input: decisionInputSchema }),
|
|
4226
4618
|
fromArgv: async (_argv, path, stdin) => ({
|
|
4227
4619
|
bundlePath: path,
|
|
4228
4620
|
input: JSON.parse(await stdin())
|
|
@@ -4257,6 +4649,8 @@ var KB_COMMANDS = [
|
|
|
4257
4649
|
packCommand,
|
|
4258
4650
|
queryCommand,
|
|
4259
4651
|
traceCommand,
|
|
4652
|
+
impactCommand,
|
|
4653
|
+
backlinksCommand,
|
|
4260
4654
|
listCommand,
|
|
4261
4655
|
readIndexCommand,
|
|
4262
4656
|
logCommand,
|
|
@@ -4279,7 +4673,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
4279
4673
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4280
4674
|
|
|
4281
4675
|
// src/version.ts
|
|
4282
|
-
var VERSION = true ? "0.1.
|
|
4676
|
+
var VERSION = true ? "0.1.13" : "0.0.0-dev";
|
|
4283
4677
|
|
|
4284
4678
|
// src/mcp.ts
|
|
4285
4679
|
function createKbMcpServer() {
|
|
@@ -4431,10 +4825,12 @@ function usage() {
|
|
|
4431
4825
|
DEFAULT_LOAD_BUDGET,
|
|
4432
4826
|
DEFAULT_PACK_HOPS,
|
|
4433
4827
|
DEFAULT_PACK_MAX_NODES,
|
|
4828
|
+
DEFAULT_TYPED_LINK_RELS,
|
|
4434
4829
|
DEFAULT_UNVERIFIED_DAYS,
|
|
4435
4830
|
ErrorTypes,
|
|
4436
4831
|
Fault,
|
|
4437
4832
|
INDEX_FILE,
|
|
4833
|
+
KB_CAUSAL_LINK_RELS,
|
|
4438
4834
|
KB_COMMANDS,
|
|
4439
4835
|
KB_COMMANDS_BY_NAME,
|
|
4440
4836
|
KB_CONCEPT_ID_PATTERN,
|
|
@@ -4442,6 +4838,7 @@ function usage() {
|
|
|
4442
4838
|
KB_DIR,
|
|
4443
4839
|
KB_DOCTOR_CHECKS,
|
|
4444
4840
|
KB_EDGE_KINDS,
|
|
4841
|
+
KB_LINK_RELS,
|
|
4445
4842
|
KB_MATERIALITIES,
|
|
4446
4843
|
KB_RECORD_STATUSES,
|
|
4447
4844
|
KB_RECORD_TYPES,
|
|
@@ -4455,7 +4852,9 @@ function usage() {
|
|
|
4455
4852
|
KbRecordNotFoundError,
|
|
4456
4853
|
KbSelfVerificationError,
|
|
4457
4854
|
KbStore,
|
|
4855
|
+
KbUnknownLinkRelError,
|
|
4458
4856
|
KbWriteConflictError,
|
|
4857
|
+
LINK_RELS,
|
|
4459
4858
|
LOG_FILE,
|
|
4460
4859
|
NO_DECISION_SLUG,
|
|
4461
4860
|
PINS_FILE,
|
|
@@ -4467,10 +4866,12 @@ function usage() {
|
|
|
4467
4866
|
adjudicate,
|
|
4468
4867
|
anchorFilePath,
|
|
4469
4868
|
assertBaseNotFrozen,
|
|
4869
|
+
backlinks,
|
|
4470
4870
|
buildContext,
|
|
4471
4871
|
catalog,
|
|
4472
4872
|
composeDecisionRecord,
|
|
4473
4873
|
composeInputSchema,
|
|
4874
|
+
composeLinkSchema,
|
|
4474
4875
|
composeNoDecisionRecord,
|
|
4475
4876
|
composeRecord,
|
|
4476
4877
|
contextProfileBudgets,
|
|
@@ -4480,13 +4881,17 @@ function usage() {
|
|
|
4480
4881
|
doctor,
|
|
4481
4882
|
edgeNeighbours,
|
|
4482
4883
|
hashAnchorText,
|
|
4884
|
+
impact,
|
|
4885
|
+
inboundIndex,
|
|
4483
4886
|
indexIsStale,
|
|
4887
|
+
isKbLinkRel,
|
|
4484
4888
|
isKbRecordType,
|
|
4485
4889
|
isNoDecisionRecord,
|
|
4486
4890
|
kbActorStampSchema,
|
|
4487
4891
|
kbAnchorSchema,
|
|
4488
4892
|
kbConceptIdSchema,
|
|
4489
4893
|
kbJsonSchemas,
|
|
4894
|
+
kbLinkSchema,
|
|
4490
4895
|
kbLogEntrySchema,
|
|
4491
4896
|
kbRecordFrontmatterSchema,
|
|
4492
4897
|
kbSourceSchema,
|