@saasontools/strauss-kb 0.1.12 → 0.1.14
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 +54 -50
- package/README.md +172 -420
- package/dist/{chunk-33ZCBEUV.js → chunk-43KALLFU.js} +482 -122
- package/dist/chunk-43KALLFU.js.map +1 -0
- package/dist/{chunk-F2U2YLWV.js → chunk-MBXNCZ4V.js} +2 -2
- package/dist/{chunk-EXKK2KUN.js → chunk-PYA5E7FL.js} +2 -2
- package/dist/cli-main.cjs +480 -132
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +559 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +279 -13
- package/dist/index.d.ts +279 -13
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +480 -132
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-33ZCBEUV.js.map +0 -1
- /package/dist/{chunk-F2U2YLWV.js.map → chunk-MBXNCZ4V.js.map} +0 -0
- /package/dist/{chunk-EXKK2KUN.js.map → chunk-PYA5E7FL.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`;
|
|
@@ -1797,6 +2121,14 @@ ${answer}
|
|
|
1797
2121
|
async pack(bundlePath2, rootId, options = {}) {
|
|
1798
2122
|
return pack(await this.list(bundlePath2), rootId, options);
|
|
1799
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
|
+
}
|
|
1800
2132
|
/**
|
|
1801
2133
|
* The stored index, rebuilt if it disagrees with the records.
|
|
1802
2134
|
*
|
|
@@ -2116,75 +2448,12 @@ function bundleDigest(records, superseded) {
|
|
|
2116
2448
|
return digest(entries.join("\n"));
|
|
2117
2449
|
}
|
|
2118
2450
|
|
|
2119
|
-
// src/record-types.ts
|
|
2120
|
-
var RECORD_TYPES = {
|
|
2121
|
-
fact: {
|
|
2122
|
-
purpose: "Observed or sourced fact",
|
|
2123
|
-
sections: ["Claim", "Evidence", "Implication"],
|
|
2124
|
-
initialStatus: "accepted"
|
|
2125
|
-
},
|
|
2126
|
-
requirement: {
|
|
2127
|
-
purpose: "Required behavior or outcome",
|
|
2128
|
-
sections: ["Claim", "Evidence", "Implication"],
|
|
2129
|
-
initialStatus: "proposed"
|
|
2130
|
-
},
|
|
2131
|
-
constraint: {
|
|
2132
|
-
purpose: "Limitation, compatibility boundary, policy, or restriction",
|
|
2133
|
-
sections: ["Claim", "Evidence", "Implication"],
|
|
2134
|
-
initialStatus: "accepted"
|
|
2135
|
-
},
|
|
2136
|
-
decision: {
|
|
2137
|
-
purpose: "Chosen or proposed direction",
|
|
2138
|
-
sections: ["Decision", "Rationale", "Rejected", "Impact"],
|
|
2139
|
-
initialStatus: "accepted"
|
|
2140
|
-
},
|
|
2141
|
-
assumption: {
|
|
2142
|
-
purpose: "Unsourced or not-yet-confirmed working assumption",
|
|
2143
|
-
sections: ["Claim", "Why we think so", "What would falsify it"],
|
|
2144
|
-
initialStatus: "draft"
|
|
2145
|
-
},
|
|
2146
|
-
"open-question": {
|
|
2147
|
-
purpose: "Question needing resolution",
|
|
2148
|
-
sections: ["Question", "Why it matters", "Default assumption"],
|
|
2149
|
-
initialStatus: "open"
|
|
2150
|
-
},
|
|
2151
|
-
risk: {
|
|
2152
|
-
purpose: "Something that can go wrong",
|
|
2153
|
-
sections: ["Risk", "Why it matters", "Mitigation", "Verification"],
|
|
2154
|
-
initialStatus: "open"
|
|
2155
|
-
},
|
|
2156
|
-
contract: {
|
|
2157
|
-
purpose: "API, data, event, schema, or permission contract",
|
|
2158
|
-
sections: ["Contract", "Producer", "Consumer", "Compatibility"],
|
|
2159
|
-
initialStatus: "proposed"
|
|
2160
|
-
},
|
|
2161
|
-
flow: {
|
|
2162
|
-
purpose: "Sequence, lifecycle, or state behavior",
|
|
2163
|
-
sections: ["Flow", "Trigger", "Steps", "Failure modes"],
|
|
2164
|
-
initialStatus: "accepted"
|
|
2165
|
-
},
|
|
2166
|
-
"affected-system": {
|
|
2167
|
-
purpose: "Component, service, package, integration, or external system",
|
|
2168
|
-
sections: ["System", "How it is affected", "Blast radius"],
|
|
2169
|
-
initialStatus: "accepted"
|
|
2170
|
-
},
|
|
2171
|
-
"test-obligation": {
|
|
2172
|
-
purpose: "Behavior or contract that must be verified",
|
|
2173
|
-
sections: ["Obligation", "Why it matters", "How to verify"],
|
|
2174
|
-
initialStatus: "open"
|
|
2175
|
-
},
|
|
2176
|
-
"source-note": {
|
|
2177
|
-
purpose: "Extracted note from source material",
|
|
2178
|
-
sections: ["Note", "Where it came from"],
|
|
2179
|
-
initialStatus: "accepted"
|
|
2180
|
-
}
|
|
2181
|
-
};
|
|
2182
|
-
function isKbRecordType(value) {
|
|
2183
|
-
return Object.prototype.hasOwnProperty.call(RECORD_TYPES, value);
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
2451
|
// src/compose.ts
|
|
2187
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();
|
|
2188
2457
|
var composeInputSchema = import_zod3.z.object({
|
|
2189
2458
|
slug: import_zod3.z.string().min(1),
|
|
2190
2459
|
/** One line, in the reader's terms. Becomes OKF `title`. */
|
|
@@ -2211,6 +2480,18 @@ var composeInputSchema = import_zod3.z.object({
|
|
|
2211
2480
|
tags: import_zod3.z.array(import_zod3.z.string().min(1)).optional(),
|
|
2212
2481
|
/** Concept ids this record relates to; rendered as body links. */
|
|
2213
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
|
+
),
|
|
2214
2495
|
/** Concept ids this record replaces. The store settles the backlinks. */
|
|
2215
2496
|
supersedes: import_zod3.z.array(kbConceptIdSchema).max(32).optional(),
|
|
2216
2497
|
materiality: import_zod3.z.enum(KB_MATERIALITIES).optional(),
|
|
@@ -2250,6 +2531,15 @@ function composeRecord(type, input, writtenBy, writtenAt) {
|
|
|
2250
2531
|
if (parsed.owner) frontmatter.strauss_owner = parsed.owner;
|
|
2251
2532
|
if (parsed.supersedes?.length)
|
|
2252
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;
|
|
2253
2543
|
const blocks = [];
|
|
2254
2544
|
for (const heading of spec.sections) {
|
|
2255
2545
|
const text = sections[heading];
|
|
@@ -2261,6 +2551,11 @@ ${text}`);
|
|
|
2261
2551
|
for (const related of parsed.relatedConceptIds ?? []) {
|
|
2262
2552
|
blocks.push(`Relates to [${related}](${related}.md).`);
|
|
2263
2553
|
}
|
|
2554
|
+
for (const link2 of parsed.links ?? []) {
|
|
2555
|
+
blocks.push(
|
|
2556
|
+
`${LINK_RELS[link2.rel].phrase} [${link2.target}](${link2.target}.md).`
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2264
2559
|
if (parsed.sources?.length) {
|
|
2265
2560
|
blocks.push(
|
|
2266
2561
|
parsed.sources.map((source) => `[^${source.id}]: ${source.title ?? source.resource}`).join("\n")
|
|
@@ -2901,7 +3196,7 @@ function normalize(path) {
|
|
|
2901
3196
|
function validateBundle(records) {
|
|
2902
3197
|
const byId = new Map(records.map((record) => [record.conceptId, record]));
|
|
2903
3198
|
const problems = [];
|
|
2904
|
-
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 });
|
|
2905
3200
|
for (const record of records) {
|
|
2906
3201
|
const { conceptId: conceptId2, frontmatter: fm } = record;
|
|
2907
3202
|
if (!isKbRecordType(fm.type)) {
|
|
@@ -2925,6 +3220,36 @@ function validateBundle(records) {
|
|
|
2925
3220
|
report("supersedes", conceptId2, `${old} is not marked superseded`);
|
|
2926
3221
|
}
|
|
2927
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
|
+
}
|
|
2928
3253
|
if (fm.strauss_assumption && fm.sources?.length) {
|
|
2929
3254
|
report("assumption", conceptId2, "marked an assumption but cites sources");
|
|
2930
3255
|
}
|
|
@@ -3219,7 +3544,7 @@ var decisionInputSchema = composeInputSchema.omit({ sections: true }).extend({
|
|
|
3219
3544
|
impact: import_zod6.z.string().min(1).optional()
|
|
3220
3545
|
}).strict();
|
|
3221
3546
|
function composeDecisionRecord(input, writtenBy, writtenAt) {
|
|
3222
|
-
const { alternative, impact, ...rest } = input;
|
|
3547
|
+
const { alternative, impact: impact2, ...rest } = input;
|
|
3223
3548
|
return composeRecord(
|
|
3224
3549
|
DECISION_TYPE,
|
|
3225
3550
|
{
|
|
@@ -3228,7 +3553,7 @@ function composeDecisionRecord(input, writtenBy, writtenAt) {
|
|
|
3228
3553
|
Decision: input.title,
|
|
3229
3554
|
Rationale: input.why,
|
|
3230
3555
|
...alternative ? { Rejected: alternative } : {},
|
|
3231
|
-
...
|
|
3556
|
+
...impact2 ? { Impact: impact2 } : {}
|
|
3232
3557
|
}
|
|
3233
3558
|
},
|
|
3234
3559
|
writtenBy,
|
|
@@ -3453,7 +3778,7 @@ var answerCommand = define({
|
|
|
3453
3778
|
name: "answer",
|
|
3454
3779
|
tool: "kb_answer",
|
|
3455
3780
|
usage: "answer <concept-id> <answer...>",
|
|
3456
|
-
description: "Resolve an open question:
|
|
3781
|
+
description: "Resolve an open question: set status, stamp who and when, append an Answer section. If the answer overturns a decision or assumption, supersede that record explicitly.",
|
|
3457
3782
|
input: import_zod9.z.object({ bundlePath, conceptId, answer: import_zod9.z.string().min(1) }),
|
|
3458
3783
|
fromArgv: (argv, path) => ({
|
|
3459
3784
|
bundlePath: path,
|
|
@@ -3467,16 +3792,28 @@ var answerCommand = define({
|
|
|
3467
3792
|
}
|
|
3468
3793
|
});
|
|
3469
3794
|
|
|
3470
|
-
// src/commands/
|
|
3795
|
+
// src/commands/backlinks.ts
|
|
3471
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");
|
|
3472
3809
|
var catalogCommand = define({
|
|
3473
3810
|
name: "catalog",
|
|
3474
3811
|
tool: "kb_catalog",
|
|
3475
3812
|
usage: "catalog [type]",
|
|
3476
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.",
|
|
3477
|
-
input:
|
|
3814
|
+
input: import_zod11.z.object({
|
|
3478
3815
|
bundlePath,
|
|
3479
|
-
type:
|
|
3816
|
+
type: import_zod11.z.enum(KB_RECORD_TYPES).optional()
|
|
3480
3817
|
}),
|
|
3481
3818
|
fromArgv: (argv, path) => ({
|
|
3482
3819
|
bundlePath: path,
|
|
@@ -3531,26 +3868,26 @@ function count(value, noun) {
|
|
|
3531
3868
|
}
|
|
3532
3869
|
|
|
3533
3870
|
// src/commands/context.ts
|
|
3534
|
-
var
|
|
3871
|
+
var import_zod12 = require("zod");
|
|
3535
3872
|
var contextCommand = define({
|
|
3536
3873
|
name: "context",
|
|
3537
3874
|
tool: "kb_context",
|
|
3538
3875
|
usage: "context [--profile NAME] [--budget N] [--full-under N] [--format json] [--event NAME]",
|
|
3539
|
-
description: "
|
|
3540
|
-
input:
|
|
3541
|
-
budgetTokens:
|
|
3876
|
+
description: "Index block of pinned bases (ids, titles, standing) for injection at context birth. Takes no bundlePath \u2014 reads the workspace pin manifests. Empty when nothing is pinned; refuses over budget rather than truncating. Budget precedence: flags, then the manifest `context[profile]` over `context.default`, then the built-in profile, then package defaults.",
|
|
3877
|
+
input: import_zod12.z.object({
|
|
3878
|
+
budgetTokens: import_zod12.z.number().int().positive().optional().describe(
|
|
3542
3879
|
"Ceiling on the whole emitted block; past it the command refuses with a list of bases rather than truncating. Defaults to 4000."
|
|
3543
3880
|
),
|
|
3544
|
-
fullUnderTokens:
|
|
3881
|
+
fullUnderTokens: import_zod12.z.number().int().positive().optional().describe(
|
|
3545
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."
|
|
3546
3883
|
),
|
|
3547
|
-
profile:
|
|
3884
|
+
profile: import_zod12.z.string().optional().describe(
|
|
3548
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."
|
|
3549
3886
|
),
|
|
3550
|
-
format:
|
|
3887
|
+
format: import_zod12.z.enum(["markdown", "json"]).optional().describe(
|
|
3551
3888
|
"CLI envelope for hook protocols that require strict JSON on stdout. MCP callers omit this \u2014 the block itself is identical."
|
|
3552
3889
|
),
|
|
3553
|
-
event:
|
|
3890
|
+
event: import_zod12.z.string().optional().describe(
|
|
3554
3891
|
"hookEventName stamped into the JSON envelope. Only meaningful with format=json."
|
|
3555
3892
|
)
|
|
3556
3893
|
}),
|
|
@@ -3586,14 +3923,14 @@ var contextCommand = define({
|
|
|
3586
3923
|
});
|
|
3587
3924
|
|
|
3588
3925
|
// src/commands/doctor.ts
|
|
3589
|
-
var
|
|
3590
|
-
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}.`);
|
|
3591
3928
|
var doctorCommand = define({
|
|
3592
3929
|
name: "doctor",
|
|
3593
3930
|
tool: "kb_doctor",
|
|
3594
3931
|
usage: "doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--repo-root PATH] [--strict]",
|
|
3595
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.",
|
|
3596
|
-
input:
|
|
3933
|
+
input: import_zod13.z.object({
|
|
3597
3934
|
bundlePath,
|
|
3598
3935
|
repoRoot: REPO_ROOT,
|
|
3599
3936
|
expiringDays: days(
|
|
@@ -3608,7 +3945,7 @@ var doctorCommand = define({
|
|
|
3608
3945
|
"How long a record may stay `open` or `proposed` before `aging` reports it, in days.",
|
|
3609
3946
|
DEFAULT_AGING_DAYS
|
|
3610
3947
|
),
|
|
3611
|
-
strict:
|
|
3948
|
+
strict: import_zod13.z.boolean().optional().describe(
|
|
3612
3949
|
"Turn an expired record into a non-zero exit for the CLI. No effect on the report itself."
|
|
3613
3950
|
)
|
|
3614
3951
|
}),
|
|
@@ -3683,14 +4020,47 @@ function render2(result) {
|
|
|
3683
4020
|
return lines.join("\n");
|
|
3684
4021
|
}
|
|
3685
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
|
+
|
|
3686
4056
|
// src/commands/list.ts
|
|
3687
|
-
var
|
|
4057
|
+
var import_zod15 = require("zod");
|
|
3688
4058
|
var listCommand = define({
|
|
3689
4059
|
name: "list",
|
|
3690
4060
|
tool: "kb_list",
|
|
3691
4061
|
usage: "list [type]",
|
|
3692
|
-
description: "Every record, optionally
|
|
3693
|
-
input:
|
|
4062
|
+
description: "Every record, optionally one type. For enumerating; use kb_query for a question.",
|
|
4063
|
+
input: import_zod15.z.object({ bundlePath, type: import_zod15.z.enum(KB_RECORD_TYPES).optional() }),
|
|
3694
4064
|
fromArgv: (argv, path) => ({ bundlePath: path, type: argv[1] }),
|
|
3695
4065
|
run: async ({ store }, { bundlePath: path, type }) => (await store.list(path, type)).map((record) => ({
|
|
3696
4066
|
conceptId: record.conceptId,
|
|
@@ -3702,17 +4072,17 @@ var listCommand = define({
|
|
|
3702
4072
|
});
|
|
3703
4073
|
|
|
3704
4074
|
// src/commands/load.ts
|
|
3705
|
-
var
|
|
4075
|
+
var import_zod16 = require("zod");
|
|
3706
4076
|
var loadCommand = define({
|
|
3707
4077
|
name: "load",
|
|
3708
4078
|
tool: "kb_load",
|
|
3709
4079
|
usage: "load [type] [--budget N | --all] [--repo-root PATH]",
|
|
3710
|
-
description: "
|
|
3711
|
-
input:
|
|
4080
|
+
description: "Load the whole base, each record with its standing \u2014 call it first, at the point of use, since compaction drops it. Superseded records arrive as stubs; kb_trace has the history. Over budget it refuses: kb_catalog, then kb_pack, or narrow with `type`; `all` bypasses. Never read record files directly \u2014 only kb_* tools resolve supersession. `digest` stamps the base's content, so hooks know when to reload.",
|
|
4081
|
+
input: import_zod16.z.object({
|
|
3712
4082
|
bundlePath,
|
|
3713
|
-
type:
|
|
3714
|
-
budgetTokens:
|
|
3715
|
-
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(
|
|
3716
4086
|
"Loads the entire base regardless of size, bypassing the token budget; mutually exclusive with budgetTokens."
|
|
3717
4087
|
),
|
|
3718
4088
|
repoRoot: REPO_ROOT
|
|
@@ -3754,25 +4124,25 @@ var loadCommand = define({
|
|
|
3754
4124
|
});
|
|
3755
4125
|
|
|
3756
4126
|
// src/commands/log.ts
|
|
3757
|
-
var
|
|
4127
|
+
var import_zod17 = require("zod");
|
|
3758
4128
|
var logCommand = define({
|
|
3759
4129
|
name: "log",
|
|
3760
4130
|
tool: "kb_log",
|
|
3761
4131
|
usage: "log",
|
|
3762
|
-
description: "
|
|
3763
|
-
input:
|
|
4132
|
+
description: "Who touched what, and when. Append-only; malformed lines are reported, never repaired.",
|
|
4133
|
+
input: import_zod17.z.object({ bundlePath }),
|
|
3764
4134
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3765
4135
|
run: ({ store }, { bundlePath: path }) => store.readLog(path)
|
|
3766
4136
|
});
|
|
3767
4137
|
|
|
3768
4138
|
// src/commands/no-decision.ts
|
|
3769
|
-
var
|
|
4139
|
+
var import_zod18 = require("zod");
|
|
3770
4140
|
var noDecisionCommand = define({
|
|
3771
4141
|
name: "no-decision",
|
|
3772
4142
|
tool: "kb_no_decision",
|
|
3773
4143
|
usage: "no-decision <reason...>",
|
|
3774
|
-
description:
|
|
3775
|
-
input:
|
|
4144
|
+
description: "Record in one sentence that a piece of work had nothing to decide. Idempotent.",
|
|
4145
|
+
input: import_zod18.z.object({ bundlePath, reason: import_zod18.z.string().min(1) }),
|
|
3776
4146
|
fromArgv: (argv, path) => ({
|
|
3777
4147
|
bundlePath: path,
|
|
3778
4148
|
reason: argv.slice(1).join(" ").trim()
|
|
@@ -3789,20 +4159,20 @@ var noDecisionCommand = define({
|
|
|
3789
4159
|
});
|
|
3790
4160
|
|
|
3791
4161
|
// src/commands/pack.ts
|
|
3792
|
-
var
|
|
4162
|
+
var import_zod19 = require("zod");
|
|
3793
4163
|
var packCommand = define({
|
|
3794
4164
|
name: "pack",
|
|
3795
4165
|
tool: "kb_pack",
|
|
3796
4166
|
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
3797
|
-
description: "
|
|
3798
|
-
input:
|
|
4167
|
+
description: "Bounded neighbourhood around one record: within `hops`, ranked, cut to `maxNodes`, with every cut record named under Excluded. Use when the base is over kb_load's budget and the work centres on a record you can name. Refuses over budget rather than truncating. Everything below the header is byte-stable across runs. Resolves supersession like kb_load.",
|
|
4168
|
+
input: import_zod19.z.object({
|
|
3799
4169
|
bundlePath,
|
|
3800
4170
|
conceptId,
|
|
3801
|
-
hops:
|
|
3802
|
-
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(
|
|
3803
4173
|
"How many records the pack may hold, root included. Defaults to 20."
|
|
3804
4174
|
),
|
|
3805
|
-
budgetTokens:
|
|
4175
|
+
budgetTokens: import_zod19.z.number().int().positive().optional().describe(
|
|
3806
4176
|
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
3807
4177
|
)
|
|
3808
4178
|
}),
|
|
@@ -3889,22 +4259,22 @@ function warningLabel(warning) {
|
|
|
3889
4259
|
}
|
|
3890
4260
|
|
|
3891
4261
|
// src/commands/pin.ts
|
|
3892
|
-
var
|
|
4262
|
+
var import_zod20 = require("zod");
|
|
3893
4263
|
var pinCommand = define({
|
|
3894
4264
|
name: "pin",
|
|
3895
4265
|
tool: "kb_pin",
|
|
3896
4266
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
3897
|
-
description: "Pin a base into a workspace
|
|
3898
|
-
input:
|
|
4267
|
+
description: "Pin a base into a workspace manifest so kb_context surfaces it. Layers, nearest wins: project `.strauss/kb-pins.json` (default), `--local` (personal, gitignored), `--user` (`~/.strauss`). Idempotent; `--mode full|index`, `--profiles`, `--frozen`/`--unfreeze` update only those fields. A path with no records pins with a warning. Never touches the base itself.",
|
|
4268
|
+
input: import_zod20.z.object({
|
|
3899
4269
|
bundlePath,
|
|
3900
|
-
mode:
|
|
4270
|
+
mode: import_zod20.z.enum(["full", "index"]).optional().describe(
|
|
3901
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."
|
|
3902
4272
|
),
|
|
3903
|
-
profiles:
|
|
3904
|
-
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(
|
|
3905
4275
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
3906
4276
|
),
|
|
3907
|
-
frozen:
|
|
4277
|
+
frozen: import_zod20.z.boolean().optional().describe(
|
|
3908
4278
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
3909
4279
|
)
|
|
3910
4280
|
}),
|
|
@@ -3933,29 +4303,29 @@ var pinCommand = define({
|
|
|
3933
4303
|
});
|
|
3934
4304
|
|
|
3935
4305
|
// src/commands/pins.ts
|
|
3936
|
-
var
|
|
4306
|
+
var import_zod21 = require("zod");
|
|
3937
4307
|
var pinsCommand = define({
|
|
3938
4308
|
name: "pins",
|
|
3939
4309
|
tool: "kb_pins",
|
|
3940
4310
|
usage: "pins",
|
|
3941
|
-
description: "Every pinned base across the manifest layers,
|
|
3942
|
-
input:
|
|
4311
|
+
description: "Every pinned base across the manifest layers, with its layer and whether it resolves to records. Takes no bundlePath.",
|
|
4312
|
+
input: import_zod21.z.object({}),
|
|
3943
4313
|
fromArgv: () => ({}),
|
|
3944
4314
|
run: ({ store }) => listPins(store, process.cwd())
|
|
3945
4315
|
});
|
|
3946
4316
|
|
|
3947
4317
|
// src/commands/query.ts
|
|
3948
|
-
var
|
|
4318
|
+
var import_zod22 = require("zod");
|
|
3949
4319
|
var queryCommand = define({
|
|
3950
4320
|
name: "query",
|
|
3951
4321
|
tool: "kb_query",
|
|
3952
4322
|
usage: "query <text...> [--repo-root PATH]",
|
|
3953
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.",
|
|
3954
|
-
input:
|
|
4324
|
+
input: import_zod22.z.object({
|
|
3955
4325
|
bundlePath,
|
|
3956
|
-
text:
|
|
3957
|
-
type:
|
|
3958
|
-
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(),
|
|
3959
4329
|
repoRoot: REPO_ROOT
|
|
3960
4330
|
}),
|
|
3961
4331
|
// `--repo-root` is a flag, so its value must not fall into the search text.
|
|
@@ -3987,40 +4357,40 @@ var queryCommand = define({
|
|
|
3987
4357
|
});
|
|
3988
4358
|
|
|
3989
4359
|
// src/commands/read-index.ts
|
|
3990
|
-
var
|
|
4360
|
+
var import_zod23 = require("zod");
|
|
3991
4361
|
var readIndexCommand = define({
|
|
3992
4362
|
name: "index",
|
|
3993
4363
|
tool: "kb_index",
|
|
3994
4364
|
usage: "index",
|
|
3995
|
-
description: "The index
|
|
3996
|
-
input:
|
|
4365
|
+
description: "The index \u2014 title, type, status, description per record \u2014 rebuilt if stale. Cheapest re-orientation after compaction: call it (or kb_context) first, then kb_load or fetch by id.",
|
|
4366
|
+
input: import_zod23.z.object({ bundlePath }),
|
|
3997
4367
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3998
4368
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
3999
4369
|
});
|
|
4000
4370
|
|
|
4001
4371
|
// src/commands/schema.ts
|
|
4002
|
-
var
|
|
4372
|
+
var import_zod24 = require("zod");
|
|
4003
4373
|
var schemaCommand = define({
|
|
4004
4374
|
name: "schema",
|
|
4005
4375
|
tool: "kb_schema",
|
|
4006
4376
|
usage: "schema",
|
|
4007
|
-
description: "JSON Schema for
|
|
4008
|
-
input:
|
|
4377
|
+
description: "JSON Schema for frontmatter, write input, and log entries, generated from the enforcing code.",
|
|
4378
|
+
input: import_zod24.z.object({}),
|
|
4009
4379
|
fromArgv: () => ({}),
|
|
4010
4380
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
4011
4381
|
});
|
|
4012
4382
|
|
|
4013
4383
|
// src/commands/status.ts
|
|
4014
|
-
var
|
|
4384
|
+
var import_zod25 = require("zod");
|
|
4015
4385
|
var statusCommand = define({
|
|
4016
4386
|
name: "status",
|
|
4017
4387
|
tool: "kb_status",
|
|
4018
4388
|
usage: "status <concept-id> <status>",
|
|
4019
|
-
description: "Move a record's status
|
|
4020
|
-
input:
|
|
4389
|
+
description: "Move a record's status. Compare-and-swap: a concurrent change fails instead of being overwritten.",
|
|
4390
|
+
input: import_zod25.z.object({
|
|
4021
4391
|
bundlePath,
|
|
4022
4392
|
conceptId,
|
|
4023
|
-
status:
|
|
4393
|
+
status: import_zod25.z.enum(KB_RECORD_STATUSES)
|
|
4024
4394
|
}),
|
|
4025
4395
|
fromArgv: (argv, path) => ({
|
|
4026
4396
|
bundlePath: path,
|
|
@@ -4035,13 +4405,13 @@ var statusCommand = define({
|
|
|
4035
4405
|
});
|
|
4036
4406
|
|
|
4037
4407
|
// src/commands/supersede.ts
|
|
4038
|
-
var
|
|
4408
|
+
var import_zod26 = require("zod");
|
|
4039
4409
|
var supersedeCommand = define({
|
|
4040
4410
|
name: "supersede",
|
|
4041
4411
|
tool: "kb_supersede",
|
|
4042
4412
|
usage: "supersede <concept-id> <replacement-id>",
|
|
4043
|
-
description: "Mark a record superseded by another,
|
|
4044
|
-
input:
|
|
4413
|
+
description: "Mark a record superseded by another, linked in both directions. Use instead of editing a record whose meaning changed.",
|
|
4414
|
+
input: import_zod26.z.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
4045
4415
|
fromArgv: (argv, path) => ({
|
|
4046
4416
|
bundlePath: path,
|
|
4047
4417
|
conceptId: argv[1],
|
|
@@ -4055,16 +4425,16 @@ var supersedeCommand = define({
|
|
|
4055
4425
|
});
|
|
4056
4426
|
|
|
4057
4427
|
// src/commands/sync-instructions.ts
|
|
4058
|
-
var
|
|
4428
|
+
var import_zod27 = require("zod");
|
|
4059
4429
|
var syncInstructionsCommand = define({
|
|
4060
4430
|
name: "sync-instructions",
|
|
4061
4431
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
4062
|
-
description: "
|
|
4063
|
-
input:
|
|
4064
|
-
file:
|
|
4065
|
-
budgetTokens:
|
|
4066
|
-
fullUnderTokens:
|
|
4067
|
-
profile:
|
|
4432
|
+
description: "CLI-only: plant the kb_context block between sentinel comments in AGENTS.md or CLAUDE.md, idempotently.",
|
|
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()
|
|
4068
4438
|
}),
|
|
4069
4439
|
fromArgv: (argv) => {
|
|
4070
4440
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -4090,17 +4460,17 @@ var syncInstructionsCommand = define({
|
|
|
4090
4460
|
});
|
|
4091
4461
|
|
|
4092
4462
|
// src/commands/trace.ts
|
|
4093
|
-
var
|
|
4463
|
+
var import_zod28 = require("zod");
|
|
4094
4464
|
var traceCommand = define({
|
|
4095
4465
|
name: "trace",
|
|
4096
4466
|
tool: "kb_trace",
|
|
4097
4467
|
usage: "trace <concept-id> [edges...]",
|
|
4098
|
-
description: '
|
|
4099
|
-
input:
|
|
4468
|
+
description: 'Timeline of how a position was reached, ordered by write time, following supersession, shared anchors and shared sources. Includes rejected, draft and superseded records \u2014 in a history they are the content. For "why is it like this"; kb_load answers "what holds now".',
|
|
4469
|
+
input: import_zod28.z.object({
|
|
4100
4470
|
bundlePath,
|
|
4101
4471
|
conceptId,
|
|
4102
|
-
edges:
|
|
4103
|
-
depth:
|
|
4472
|
+
edges: import_zod28.z.array(import_zod28.z.enum(TRACE_EDGES)).optional(),
|
|
4473
|
+
depth: import_zod28.z.number().int().positive().optional()
|
|
4104
4474
|
}),
|
|
4105
4475
|
fromArgv: (argv, path) => ({
|
|
4106
4476
|
bundlePath: path,
|
|
@@ -4122,53 +4492,56 @@ var traceCommand = define({
|
|
|
4122
4492
|
});
|
|
4123
4493
|
|
|
4124
4494
|
// src/commands/types.ts
|
|
4125
|
-
var
|
|
4495
|
+
var import_zod29 = require("zod");
|
|
4126
4496
|
var typesCommand = define({
|
|
4127
4497
|
name: "types",
|
|
4128
4498
|
tool: "kb_types",
|
|
4129
4499
|
usage: "types",
|
|
4130
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.",
|
|
4131
|
-
input:
|
|
4501
|
+
input: import_zod29.z.object({}),
|
|
4132
4502
|
fromArgv: () => ({}),
|
|
4133
4503
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
4134
4504
|
});
|
|
4135
4505
|
|
|
4136
4506
|
// src/commands/unpin.ts
|
|
4137
|
-
var
|
|
4507
|
+
var import_zod30 = require("zod");
|
|
4138
4508
|
var unpinCommand = define({
|
|
4139
4509
|
name: "unpin",
|
|
4140
4510
|
tool: "kb_unpin",
|
|
4141
4511
|
usage: "unpin [bundle-path]",
|
|
4142
|
-
description: "Remove a base from every
|
|
4143
|
-
input:
|
|
4512
|
+
description: "Remove a base from every manifest layer that holds it. Reports the layers touched.",
|
|
4513
|
+
input: import_zod30.z.object({ bundlePath }),
|
|
4144
4514
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
4145
4515
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
4146
4516
|
});
|
|
4147
4517
|
|
|
4148
4518
|
// src/commands/validate.ts
|
|
4149
|
-
var
|
|
4519
|
+
var import_zod31 = require("zod");
|
|
4150
4520
|
var validateCommand = define({
|
|
4151
4521
|
name: "validate",
|
|
4152
4522
|
tool: "kb_validate",
|
|
4153
4523
|
usage: "validate",
|
|
4154
|
-
description: "Check pointers no single record can see: supersession links that disagree between the two records, and assumptions that cite sources.
|
|
4155
|
-
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 }),
|
|
4156
4526
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
4157
4527
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
4158
|
-
|
|
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
|
+
)
|
|
4159
4532
|
});
|
|
4160
4533
|
|
|
4161
4534
|
// src/commands/verify.ts
|
|
4162
|
-
var
|
|
4535
|
+
var import_zod32 = require("zod");
|
|
4163
4536
|
var verifyCommand = define({
|
|
4164
4537
|
name: "verify",
|
|
4165
4538
|
tool: "kb_verify",
|
|
4166
4539
|
usage: "verify <concept-id> --note <text>",
|
|
4167
|
-
description: "Append
|
|
4168
|
-
input:
|
|
4540
|
+
description: "Append a verified[] event: who checked, when, and what was found. Append-only. A record's own generator is refused unless the actor is `human:`-prefixed.",
|
|
4541
|
+
input: import_zod32.z.object({
|
|
4169
4542
|
bundlePath,
|
|
4170
4543
|
conceptId,
|
|
4171
|
-
note:
|
|
4544
|
+
note: import_zod32.z.string().refine((s) => s.trim().length > 0, {
|
|
4172
4545
|
message: "note must say what the check found"
|
|
4173
4546
|
})
|
|
4174
4547
|
}),
|
|
@@ -4188,23 +4561,15 @@ var verifyCommand = define({
|
|
|
4188
4561
|
});
|
|
4189
4562
|
|
|
4190
4563
|
// src/commands/write.ts
|
|
4191
|
-
var
|
|
4564
|
+
var import_zod33 = require("zod");
|
|
4192
4565
|
var writeCommand = define({
|
|
4193
4566
|
name: "write",
|
|
4194
4567
|
tool: "kb_write",
|
|
4195
4568
|
usage: "write <type> < record.json",
|
|
4196
|
-
description:
|
|
4197
|
-
|
|
4198
|
-
"",
|
|
4199
|
-
"Judgment the tool cannot enforce for you:",
|
|
4200
|
-
"- An unsourced claim is an `assumption` record with assumption: true, never a `fact` with a vague source. The distinction is what lets a later reader separate what was established from what was guessed.",
|
|
4201
|
-
"- When two records conflict, say so in a `risk`, an `open-question`, or a superseding `decision`. Quietly picking a winner destroys the disagreement, which is usually the useful part.",
|
|
4202
|
-
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
4203
|
-
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
4204
|
-
].join("\n"),
|
|
4205
|
-
input: import_zod31.z.object({
|
|
4569
|
+
description: "Write one record. Search first \u2014 a duplicate concept id is rejected, not overwritten; kb_types lists each type's sections. An unsourced claim is an `assumption` with assumption: true, never a vague `fact`. Conflicting records get a `risk`, `open-question`, or superseding `decision`. Prefer a new short record over overloading one. Never delete; supersede.",
|
|
4570
|
+
input: import_zod33.z.object({
|
|
4206
4571
|
bundlePath,
|
|
4207
|
-
type:
|
|
4572
|
+
type: import_zod33.z.enum(KB_RECORD_TYPES),
|
|
4208
4573
|
input: composeInputSchema
|
|
4209
4574
|
}),
|
|
4210
4575
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -4228,20 +4593,13 @@ var writeCommand = define({
|
|
|
4228
4593
|
});
|
|
4229
4594
|
|
|
4230
4595
|
// src/commands/write-decision.ts
|
|
4231
|
-
var
|
|
4596
|
+
var import_zod34 = require("zod");
|
|
4232
4597
|
var writeDecisionCommand = define({
|
|
4233
4598
|
name: "write-decision",
|
|
4234
4599
|
tool: "kb_write_decision",
|
|
4235
4600
|
usage: "write-decision < decision.json",
|
|
4236
|
-
description:
|
|
4237
|
-
|
|
4238
|
-
"",
|
|
4239
|
-
"What belongs in one:",
|
|
4240
|
-
'- Record a decision when a later reader would otherwise "simplify" the constraint away. If the diff already answers the question, there is nothing here to write.',
|
|
4241
|
-
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
4242
|
-
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
4243
|
-
].join("\n"),
|
|
4244
|
-
input: import_zod32.z.object({ bundlePath, input: decisionInputSchema }),
|
|
4601
|
+
description: "Write a decision, with `alternative` (what was rejected and why) and `impact` as fields. Record one when a later reader would otherwise simplify the constraint away; skip when the diff already answers it. `sources` for material read, `anchors` for code, `relatedConceptIds` for records.",
|
|
4602
|
+
input: import_zod34.z.object({ bundlePath, input: decisionInputSchema }),
|
|
4245
4603
|
fromArgv: async (_argv, path, stdin) => ({
|
|
4246
4604
|
bundlePath: path,
|
|
4247
4605
|
input: JSON.parse(await stdin())
|
|
@@ -4276,6 +4634,8 @@ var KB_COMMANDS = [
|
|
|
4276
4634
|
packCommand,
|
|
4277
4635
|
queryCommand,
|
|
4278
4636
|
traceCommand,
|
|
4637
|
+
impactCommand,
|
|
4638
|
+
backlinksCommand,
|
|
4279
4639
|
listCommand,
|
|
4280
4640
|
readIndexCommand,
|
|
4281
4641
|
logCommand,
|
|
@@ -4298,7 +4658,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
4298
4658
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4299
4659
|
|
|
4300
4660
|
// src/version.ts
|
|
4301
|
-
var VERSION = true ? "0.1.
|
|
4661
|
+
var VERSION = true ? "0.1.14" : "0.0.0-dev";
|
|
4302
4662
|
|
|
4303
4663
|
// src/mcp.ts
|
|
4304
4664
|
function createKbMcpServer() {
|
|
@@ -4450,10 +4810,12 @@ function usage() {
|
|
|
4450
4810
|
DEFAULT_LOAD_BUDGET,
|
|
4451
4811
|
DEFAULT_PACK_HOPS,
|
|
4452
4812
|
DEFAULT_PACK_MAX_NODES,
|
|
4813
|
+
DEFAULT_TYPED_LINK_RELS,
|
|
4453
4814
|
DEFAULT_UNVERIFIED_DAYS,
|
|
4454
4815
|
ErrorTypes,
|
|
4455
4816
|
Fault,
|
|
4456
4817
|
INDEX_FILE,
|
|
4818
|
+
KB_CAUSAL_LINK_RELS,
|
|
4457
4819
|
KB_COMMANDS,
|
|
4458
4820
|
KB_COMMANDS_BY_NAME,
|
|
4459
4821
|
KB_CONCEPT_ID_PATTERN,
|
|
@@ -4461,6 +4823,7 @@ function usage() {
|
|
|
4461
4823
|
KB_DIR,
|
|
4462
4824
|
KB_DOCTOR_CHECKS,
|
|
4463
4825
|
KB_EDGE_KINDS,
|
|
4826
|
+
KB_LINK_RELS,
|
|
4464
4827
|
KB_MATERIALITIES,
|
|
4465
4828
|
KB_RECORD_STATUSES,
|
|
4466
4829
|
KB_RECORD_TYPES,
|
|
@@ -4474,7 +4837,9 @@ function usage() {
|
|
|
4474
4837
|
KbRecordNotFoundError,
|
|
4475
4838
|
KbSelfVerificationError,
|
|
4476
4839
|
KbStore,
|
|
4840
|
+
KbUnknownLinkRelError,
|
|
4477
4841
|
KbWriteConflictError,
|
|
4842
|
+
LINK_RELS,
|
|
4478
4843
|
LOG_FILE,
|
|
4479
4844
|
NO_DECISION_SLUG,
|
|
4480
4845
|
PINS_FILE,
|
|
@@ -4486,10 +4851,12 @@ function usage() {
|
|
|
4486
4851
|
adjudicate,
|
|
4487
4852
|
anchorFilePath,
|
|
4488
4853
|
assertBaseNotFrozen,
|
|
4854
|
+
backlinks,
|
|
4489
4855
|
buildContext,
|
|
4490
4856
|
catalog,
|
|
4491
4857
|
composeDecisionRecord,
|
|
4492
4858
|
composeInputSchema,
|
|
4859
|
+
composeLinkSchema,
|
|
4493
4860
|
composeNoDecisionRecord,
|
|
4494
4861
|
composeRecord,
|
|
4495
4862
|
contextProfileBudgets,
|
|
@@ -4499,13 +4866,17 @@ function usage() {
|
|
|
4499
4866
|
doctor,
|
|
4500
4867
|
edgeNeighbours,
|
|
4501
4868
|
hashAnchorText,
|
|
4869
|
+
impact,
|
|
4870
|
+
inboundIndex,
|
|
4502
4871
|
indexIsStale,
|
|
4872
|
+
isKbLinkRel,
|
|
4503
4873
|
isKbRecordType,
|
|
4504
4874
|
isNoDecisionRecord,
|
|
4505
4875
|
kbActorStampSchema,
|
|
4506
4876
|
kbAnchorSchema,
|
|
4507
4877
|
kbConceptIdSchema,
|
|
4508
4878
|
kbJsonSchemas,
|
|
4879
|
+
kbLinkSchema,
|
|
4509
4880
|
kbLogEntrySchema,
|
|
4510
4881
|
kbRecordFrontmatterSchema,
|
|
4511
4882
|
kbSourceSchema,
|