@saasontools/strauss-kb 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -3
- package/dist/{chunk-KQMGKSPZ.js → chunk-GKCG4P3L.js} +10 -3
- package/dist/chunk-GKCG4P3L.js.map +1 -0
- package/dist/{chunk-FZIMFPGR.js → chunk-GKUQOJEK.js} +436 -76
- package/dist/chunk-GKUQOJEK.js.map +1 -0
- package/dist/{chunk-VOJ6D6OX.js → chunk-LCQKARFK.js} +5 -4
- package/dist/chunk-LCQKARFK.js.map +1 -0
- package/dist/cli-main.cjs +438 -85
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +452 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -7
- package/dist/index.d.ts +136 -7
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +433 -86
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-FZIMFPGR.js.map +0 -1
- package/dist/chunk-KQMGKSPZ.js.map +0 -1
- package/dist/chunk-VOJ6D6OX.js.map +0 -1
package/dist/cli-main.cjs
CHANGED
|
@@ -45,6 +45,11 @@ var kbActorStampSchema = import_zod.z.object({
|
|
|
45
45
|
by: import_zod.z.string().min(1),
|
|
46
46
|
at: import_zod.z.string().min(1)
|
|
47
47
|
}).passthrough();
|
|
48
|
+
var kbVerifiedEventSchema = kbActorStampSchema.extend({
|
|
49
|
+
note: import_zod.z.string().refine((s) => s.trim().length > 0, {
|
|
50
|
+
message: "note must say what the check found"
|
|
51
|
+
})
|
|
52
|
+
});
|
|
48
53
|
var kbAnchorSchema = import_zod.z.object({
|
|
49
54
|
file: import_zod.z.string().min(1),
|
|
50
55
|
symbol: import_zod.z.string().min(1).optional()
|
|
@@ -1151,23 +1156,123 @@ var noDecisionCommand = define({
|
|
|
1151
1156
|
}
|
|
1152
1157
|
});
|
|
1153
1158
|
|
|
1154
|
-
// src/commands/
|
|
1159
|
+
// src/commands/pack.ts
|
|
1155
1160
|
var import_zod12 = require("zod");
|
|
1161
|
+
var packCommand = define({
|
|
1162
|
+
name: "pack",
|
|
1163
|
+
tool: "kb_pack",
|
|
1164
|
+
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
1165
|
+
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.",
|
|
1166
|
+
input: import_zod12.z.object({
|
|
1167
|
+
bundlePath,
|
|
1168
|
+
conceptId,
|
|
1169
|
+
hops: import_zod12.z.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
|
|
1170
|
+
maxNodes: import_zod12.z.number().int().positive().optional().describe(
|
|
1171
|
+
"How many records the pack may hold, root included. Defaults to 20."
|
|
1172
|
+
),
|
|
1173
|
+
budgetTokens: import_zod12.z.number().int().positive().optional().describe(
|
|
1174
|
+
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
1175
|
+
)
|
|
1176
|
+
}),
|
|
1177
|
+
fromArgv: (argv, path) => {
|
|
1178
|
+
const hops = argvFlag(argv, "--hops");
|
|
1179
|
+
const maxNodes = argvFlag(argv, "--max-nodes");
|
|
1180
|
+
const budget = argvFlag(argv, "--budget");
|
|
1181
|
+
return {
|
|
1182
|
+
bundlePath: path,
|
|
1183
|
+
conceptId: argv[1],
|
|
1184
|
+
...hops ? { hops: Number(hops) } : {},
|
|
1185
|
+
...maxNodes ? { maxNodes: Number(maxNodes) } : {},
|
|
1186
|
+
...budget ? { budgetTokens: Number(budget) } : {}
|
|
1187
|
+
};
|
|
1188
|
+
},
|
|
1189
|
+
run: async ({ store, now }, { bundlePath: path, conceptId: root, hops, maxNodes, budgetTokens }) => {
|
|
1190
|
+
const result = await store.pack(path, root, {
|
|
1191
|
+
...hops !== void 0 ? { hops } : {},
|
|
1192
|
+
...maxNodes !== void 0 ? { maxNodes } : {},
|
|
1193
|
+
...budgetTokens !== void 0 ? { budgetTokens } : {}
|
|
1194
|
+
});
|
|
1195
|
+
return render(result, path, now());
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
function render(result, bundle, at) {
|
|
1199
|
+
const lines = [
|
|
1200
|
+
`# KB Pack \u2014 ${result.root}`,
|
|
1201
|
+
`bundle: ${bundle}`,
|
|
1202
|
+
`budget: ~${result.tokensLoaded} of ${result.budgetTokens} tokens, ${result.recordCount} records`,
|
|
1203
|
+
`packed: ${at}`,
|
|
1204
|
+
"",
|
|
1205
|
+
`## Records (${result.records.length})`
|
|
1206
|
+
];
|
|
1207
|
+
for (const record of result.records) {
|
|
1208
|
+
lines.push(
|
|
1209
|
+
"",
|
|
1210
|
+
`### ${record.conceptId}${record.title ? ` \u2014 ${record.title}` : ""} [${record.standing}]`
|
|
1211
|
+
);
|
|
1212
|
+
if (record.warnings.length) {
|
|
1213
|
+
lines.push(`warnings: ${record.warnings.map(warningLabel).join("; ")}`);
|
|
1214
|
+
}
|
|
1215
|
+
if (record.anchors.length) {
|
|
1216
|
+
lines.push(
|
|
1217
|
+
`anchors: ${record.anchors.map(
|
|
1218
|
+
(anchor) => anchor.symbol ? `${anchor.file}#${anchor.symbol}` : anchor.file
|
|
1219
|
+
).join(", ")}`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
lines.push("", record.body.trimEnd());
|
|
1223
|
+
}
|
|
1224
|
+
if (result.superseded.length) {
|
|
1225
|
+
lines.push("", `## Superseded (${result.superseded.length})`);
|
|
1226
|
+
for (const entry of result.superseded) {
|
|
1227
|
+
lines.push(
|
|
1228
|
+
`- ${entry.conceptId} \u2192 ${entry.supersededBy.join(", ") || "(no surviving head)"}${entry.at ? ` (${entry.at})` : ""}`
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
if (result.excluded.length) {
|
|
1233
|
+
lines.push("", `## Excluded (${result.excluded.length})`);
|
|
1234
|
+
for (const cut of result.excluded) lines.push(`- ${cut}`);
|
|
1235
|
+
}
|
|
1236
|
+
return lines.join("\n");
|
|
1237
|
+
}
|
|
1238
|
+
function warningLabel(warning) {
|
|
1239
|
+
switch (warning.kind) {
|
|
1240
|
+
case "superseded":
|
|
1241
|
+
return `superseded by ${warning.by.join(", ")}`;
|
|
1242
|
+
case "unsettled":
|
|
1243
|
+
return `unsettled (${warning.status})`;
|
|
1244
|
+
case "broken-chain":
|
|
1245
|
+
return `broken chain \u2014 ${warning.missing} is not in the bundle`;
|
|
1246
|
+
case "chain-cycle":
|
|
1247
|
+
return `chain cycle through ${warning.through.join(" \u2192 ")}`;
|
|
1248
|
+
case "forked-chain":
|
|
1249
|
+
return `forked chain \u2014 heads ${warning.heads.join(", ")}`;
|
|
1250
|
+
case "stale":
|
|
1251
|
+
return `stale since ${warning.staleAfter}`;
|
|
1252
|
+
case "unresolved-question":
|
|
1253
|
+
return "unresolved question";
|
|
1254
|
+
default:
|
|
1255
|
+
return warning.kind;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/commands/pin.ts
|
|
1260
|
+
var import_zod13 = require("zod");
|
|
1156
1261
|
var pinCommand = define({
|
|
1157
1262
|
name: "pin",
|
|
1158
1263
|
tool: "kb_pin",
|
|
1159
1264
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
1160
1265
|
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.",
|
|
1161
|
-
input:
|
|
1266
|
+
input: import_zod13.z.object({
|
|
1162
1267
|
bundlePath,
|
|
1163
|
-
mode:
|
|
1268
|
+
mode: import_zod13.z.enum(["full", "index"]).optional().describe(
|
|
1164
1269
|
"full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
|
|
1165
1270
|
),
|
|
1166
|
-
profiles:
|
|
1167
|
-
layer:
|
|
1271
|
+
profiles: import_zod13.z.array(import_zod13.z.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
|
|
1272
|
+
layer: import_zod13.z.enum(["project", "local", "user"]).optional().describe(
|
|
1168
1273
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
1169
1274
|
),
|
|
1170
|
-
frozen:
|
|
1275
|
+
frozen: import_zod13.z.boolean().optional().describe(
|
|
1171
1276
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
1172
1277
|
)
|
|
1173
1278
|
}),
|
|
@@ -1196,29 +1301,29 @@ var pinCommand = define({
|
|
|
1196
1301
|
});
|
|
1197
1302
|
|
|
1198
1303
|
// src/commands/pins.ts
|
|
1199
|
-
var
|
|
1304
|
+
var import_zod14 = require("zod");
|
|
1200
1305
|
var pinsCommand = define({
|
|
1201
1306
|
name: "pins",
|
|
1202
1307
|
tool: "kb_pins",
|
|
1203
1308
|
usage: "pins",
|
|
1204
1309
|
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.",
|
|
1205
|
-
input:
|
|
1310
|
+
input: import_zod14.z.object({}),
|
|
1206
1311
|
fromArgv: () => ({}),
|
|
1207
1312
|
run: ({ store }) => listPins(store, process.cwd())
|
|
1208
1313
|
});
|
|
1209
1314
|
|
|
1210
1315
|
// src/commands/query.ts
|
|
1211
|
-
var
|
|
1316
|
+
var import_zod15 = require("zod");
|
|
1212
1317
|
var queryCommand = define({
|
|
1213
1318
|
name: "query",
|
|
1214
1319
|
tool: "kb_query",
|
|
1215
1320
|
usage: "query <text...>",
|
|
1216
1321
|
description: "Search and return each match with its standing. Results are flagged, never filtered: a superseded record comes back alongside whatever replaced it, and a rejected one is marked as something explicitly not adopted. Prefer kb_load when the base fits its budget: on this package's measurements, a reader holding the whole base answered eight of nine questions whose wording appears in no record, where embedding search answered four. Never read record files directly \u2014 this tool (with kb_load and kb_trace) is the only supported way to read a base; a file read bypasses supersession resolution and returns replaced records as if current.",
|
|
1217
|
-
input:
|
|
1322
|
+
input: import_zod15.z.object({
|
|
1218
1323
|
bundlePath,
|
|
1219
|
-
text:
|
|
1220
|
-
type:
|
|
1221
|
-
includeNonCurrent:
|
|
1324
|
+
text: import_zod15.z.string().optional(),
|
|
1325
|
+
type: import_zod15.z.enum(KB_RECORD_TYPES).optional(),
|
|
1326
|
+
includeNonCurrent: import_zod15.z.boolean().optional()
|
|
1222
1327
|
}),
|
|
1223
1328
|
fromArgv: (argv, path) => ({
|
|
1224
1329
|
bundlePath: path,
|
|
@@ -1240,33 +1345,33 @@ var queryCommand = define({
|
|
|
1240
1345
|
});
|
|
1241
1346
|
|
|
1242
1347
|
// src/commands/read-index.ts
|
|
1243
|
-
var
|
|
1348
|
+
var import_zod16 = require("zod");
|
|
1244
1349
|
var readIndexCommand = define({
|
|
1245
1350
|
name: "index",
|
|
1246
1351
|
tool: "kb_index",
|
|
1247
1352
|
usage: "index",
|
|
1248
1353
|
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.",
|
|
1249
|
-
input:
|
|
1354
|
+
input: import_zod16.z.object({ bundlePath }),
|
|
1250
1355
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
1251
1356
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
1252
1357
|
});
|
|
1253
1358
|
|
|
1254
1359
|
// src/commands/schema.ts
|
|
1255
|
-
var
|
|
1360
|
+
var import_zod19 = require("zod");
|
|
1256
1361
|
|
|
1257
1362
|
// src/json-schema.ts
|
|
1258
|
-
var
|
|
1363
|
+
var import_zod18 = require("zod");
|
|
1259
1364
|
|
|
1260
1365
|
// src/kb-log.ts
|
|
1261
|
-
var
|
|
1366
|
+
var import_zod17 = require("zod");
|
|
1262
1367
|
var LOG_FILE = "log.jsonl";
|
|
1263
|
-
var kbLogEntrySchema =
|
|
1264
|
-
at:
|
|
1265
|
-
by:
|
|
1266
|
-
operation:
|
|
1267
|
-
conceptId:
|
|
1368
|
+
var kbLogEntrySchema = import_zod17.z.object({
|
|
1369
|
+
at: import_zod17.z.string().min(1),
|
|
1370
|
+
by: import_zod17.z.string().min(1),
|
|
1371
|
+
operation: import_zod17.z.string().min(1),
|
|
1372
|
+
conceptId: import_zod17.z.string().min(1),
|
|
1268
1373
|
/** Second concept id, where the operation relates two — supersession. */
|
|
1269
|
-
target:
|
|
1374
|
+
target: import_zod17.z.string().min(1).optional()
|
|
1270
1375
|
}).strict();
|
|
1271
1376
|
function renderLogEntry(entry) {
|
|
1272
1377
|
return `${JSON.stringify(kbLogEntrySchema.parse(entry))}
|
|
@@ -1297,11 +1402,11 @@ function parseLog(raw) {
|
|
|
1297
1402
|
// src/json-schema.ts
|
|
1298
1403
|
function kbJsonSchemas() {
|
|
1299
1404
|
return {
|
|
1300
|
-
recordFrontmatter:
|
|
1405
|
+
recordFrontmatter: import_zod18.z.toJSONSchema(kbRecordFrontmatterSchema, {
|
|
1301
1406
|
io: "input"
|
|
1302
1407
|
}),
|
|
1303
|
-
composeInput:
|
|
1304
|
-
logEntry:
|
|
1408
|
+
composeInput: import_zod18.z.toJSONSchema(composeInputSchema, { io: "input" }),
|
|
1409
|
+
logEntry: import_zod18.z.toJSONSchema(kbLogEntrySchema, { io: "input" })
|
|
1305
1410
|
};
|
|
1306
1411
|
}
|
|
1307
1412
|
|
|
@@ -1311,22 +1416,22 @@ var schemaCommand = define({
|
|
|
1311
1416
|
tool: "kb_schema",
|
|
1312
1417
|
usage: "schema",
|
|
1313
1418
|
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.",
|
|
1314
|
-
input:
|
|
1419
|
+
input: import_zod19.z.object({}),
|
|
1315
1420
|
fromArgv: () => ({}),
|
|
1316
1421
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
1317
1422
|
});
|
|
1318
1423
|
|
|
1319
1424
|
// src/commands/status.ts
|
|
1320
|
-
var
|
|
1425
|
+
var import_zod20 = require("zod");
|
|
1321
1426
|
var statusCommand = define({
|
|
1322
1427
|
name: "status",
|
|
1323
1428
|
tool: "kb_status",
|
|
1324
1429
|
usage: "status <concept-id> <status>",
|
|
1325
1430
|
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.",
|
|
1326
|
-
input:
|
|
1431
|
+
input: import_zod20.z.object({
|
|
1327
1432
|
bundlePath,
|
|
1328
1433
|
conceptId,
|
|
1329
|
-
status:
|
|
1434
|
+
status: import_zod20.z.enum(KB_RECORD_STATUSES)
|
|
1330
1435
|
}),
|
|
1331
1436
|
fromArgv: (argv, path) => ({
|
|
1332
1437
|
bundlePath: path,
|
|
@@ -1341,13 +1446,13 @@ var statusCommand = define({
|
|
|
1341
1446
|
});
|
|
1342
1447
|
|
|
1343
1448
|
// src/commands/supersede.ts
|
|
1344
|
-
var
|
|
1449
|
+
var import_zod21 = require("zod");
|
|
1345
1450
|
var supersedeCommand = define({
|
|
1346
1451
|
name: "supersede",
|
|
1347
1452
|
tool: "kb_supersede",
|
|
1348
1453
|
usage: "supersede <concept-id> <replacement-id>",
|
|
1349
1454
|
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.",
|
|
1350
|
-
input:
|
|
1455
|
+
input: import_zod21.z.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
1351
1456
|
fromArgv: (argv, path) => ({
|
|
1352
1457
|
bundlePath: path,
|
|
1353
1458
|
conceptId: argv[1],
|
|
@@ -1361,16 +1466,16 @@ var supersedeCommand = define({
|
|
|
1361
1466
|
});
|
|
1362
1467
|
|
|
1363
1468
|
// src/commands/sync-instructions.ts
|
|
1364
|
-
var
|
|
1469
|
+
var import_zod22 = require("zod");
|
|
1365
1470
|
var syncInstructionsCommand = define({
|
|
1366
1471
|
name: "sync-instructions",
|
|
1367
1472
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
1368
1473
|
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.",
|
|
1369
|
-
input:
|
|
1370
|
-
file:
|
|
1371
|
-
budgetTokens:
|
|
1372
|
-
fullUnderTokens:
|
|
1373
|
-
profile:
|
|
1474
|
+
input: import_zod22.z.object({
|
|
1475
|
+
file: import_zod22.z.string().min(1).describe("The instruction file to edit in place."),
|
|
1476
|
+
budgetTokens: import_zod22.z.number().int().positive().optional(),
|
|
1477
|
+
fullUnderTokens: import_zod22.z.number().int().positive().optional(),
|
|
1478
|
+
profile: import_zod22.z.string().optional()
|
|
1374
1479
|
}),
|
|
1375
1480
|
fromArgv: (argv) => {
|
|
1376
1481
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -1396,43 +1501,50 @@ var syncInstructionsCommand = define({
|
|
|
1396
1501
|
});
|
|
1397
1502
|
|
|
1398
1503
|
// src/commands/trace.ts
|
|
1399
|
-
var
|
|
1504
|
+
var import_zod23 = require("zod");
|
|
1400
1505
|
|
|
1401
|
-
// src/
|
|
1402
|
-
var
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
for (const
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
1421
|
-
existing.via.push(edge);
|
|
1422
|
-
}
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
|
-
reached.set(record.conceptId, { record, depth, via: [edge] });
|
|
1426
|
-
next.push(record);
|
|
1427
|
-
}
|
|
1506
|
+
// src/kb-edges.ts
|
|
1507
|
+
var KB_EDGE_KINDS = [
|
|
1508
|
+
"body-link",
|
|
1509
|
+
"supersession",
|
|
1510
|
+
"anchor",
|
|
1511
|
+
"source"
|
|
1512
|
+
];
|
|
1513
|
+
var BODY_LINK_TARGET = new RegExp(
|
|
1514
|
+
`\\]\\((${KB_CONCEPT_ID_PATTERN.source.replace(/^\^|\$$/g, "")})\\.md\\)`,
|
|
1515
|
+
"g"
|
|
1516
|
+
);
|
|
1517
|
+
function neighbours(from, bundle, kinds = KB_EDGE_KINDS) {
|
|
1518
|
+
const found = /* @__PURE__ */ new Map();
|
|
1519
|
+
for (const kind of kinds) {
|
|
1520
|
+
for (const record of edgeNeighbours(from, bundle, kind)) {
|
|
1521
|
+
const existing = found.get(record.conceptId);
|
|
1522
|
+
if (existing) {
|
|
1523
|
+
if (!existing.via.includes(kind)) existing.via.push(kind);
|
|
1524
|
+
continue;
|
|
1428
1525
|
}
|
|
1526
|
+
found.set(record.conceptId, { record, via: [kind] });
|
|
1429
1527
|
}
|
|
1430
|
-
frontier = next;
|
|
1431
1528
|
}
|
|
1432
|
-
return [...
|
|
1529
|
+
return [...found.values()];
|
|
1433
1530
|
}
|
|
1434
|
-
function
|
|
1435
|
-
switch (
|
|
1531
|
+
function edgeNeighbours(from, bundle, kind) {
|
|
1532
|
+
switch (kind) {
|
|
1533
|
+
// A link whose target is not in the bundle is legal per compose.ts —
|
|
1534
|
+
// records are routinely written before the ones they point at exist — so
|
|
1535
|
+
// missing targets are skipped, never an error.
|
|
1536
|
+
case "body-link": {
|
|
1537
|
+
const targets = new Set(
|
|
1538
|
+
[...from.body.matchAll(BODY_LINK_TARGET)].map((match) => match[1])
|
|
1539
|
+
);
|
|
1540
|
+
if (!targets.size) return [];
|
|
1541
|
+
return bundle.filter(
|
|
1542
|
+
(candidate) => candidate.conceptId !== from.conceptId && targets.has(candidate.conceptId)
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
// Both directions and both pointers: `supersede()` writes the pair, but a
|
|
1546
|
+
// hand-edit can leave one side behind, and a walk trusting one pointer
|
|
1547
|
+
// would miss a replacement the bundle openly declares.
|
|
1436
1548
|
case "supersession":
|
|
1437
1549
|
return bundle.filter(
|
|
1438
1550
|
(candidate) => candidate.conceptId !== from.conceptId && (candidate.conceptId === from.frontmatter.strauss_superseded_by || from.frontmatter.strauss_supersedes?.includes(
|
|
@@ -1466,6 +1578,40 @@ function anchorsTouch(left, right) {
|
|
|
1466
1578
|
if (!left.symbol || !right.symbol) return true;
|
|
1467
1579
|
return left.symbol === right.symbol;
|
|
1468
1580
|
}
|
|
1581
|
+
|
|
1582
|
+
// src/trace.ts
|
|
1583
|
+
var TRACE_EDGES = ["supersession", "anchor", "source"];
|
|
1584
|
+
function trace(seedId, bundle, options = {}) {
|
|
1585
|
+
const edges = options.edges?.length ? options.edges : TRACE_EDGES;
|
|
1586
|
+
const maxDepth = options.depth ?? 3;
|
|
1587
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
1588
|
+
const seed = byId.get(seedId);
|
|
1589
|
+
if (!seed) return [];
|
|
1590
|
+
const reached = /* @__PURE__ */ new Map([
|
|
1591
|
+
[seedId, { record: seed, depth: 0, via: [] }]
|
|
1592
|
+
]);
|
|
1593
|
+
let frontier = [seed];
|
|
1594
|
+
for (let depth = 1; depth <= maxDepth && frontier.length; depth += 1) {
|
|
1595
|
+
const next = [];
|
|
1596
|
+
for (const from of frontier) {
|
|
1597
|
+
for (const edge of edges) {
|
|
1598
|
+
for (const record of edgeNeighbours(from, bundle, edge)) {
|
|
1599
|
+
const existing = reached.get(record.conceptId);
|
|
1600
|
+
if (existing) {
|
|
1601
|
+
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
1602
|
+
existing.via.push(edge);
|
|
1603
|
+
}
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
reached.set(record.conceptId, { record, depth, via: [edge] });
|
|
1607
|
+
next.push(record);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
frontier = next;
|
|
1612
|
+
}
|
|
1613
|
+
return [...reached.values()].sort(byGeneratedAt);
|
|
1614
|
+
}
|
|
1469
1615
|
function byGeneratedAt(left, right) {
|
|
1470
1616
|
const at = (step) => step.record.frontmatter.generated?.at ?? "";
|
|
1471
1617
|
return at(left).localeCompare(at(right)) || left.depth - right.depth;
|
|
@@ -1477,11 +1623,11 @@ var traceCommand = define({
|
|
|
1477
1623
|
tool: "kb_trace",
|
|
1478
1624
|
usage: "trace <concept-id> [edges...]",
|
|
1479
1625
|
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.',
|
|
1480
|
-
input:
|
|
1626
|
+
input: import_zod23.z.object({
|
|
1481
1627
|
bundlePath,
|
|
1482
1628
|
conceptId,
|
|
1483
|
-
edges:
|
|
1484
|
-
depth:
|
|
1629
|
+
edges: import_zod23.z.array(import_zod23.z.enum(TRACE_EDGES)).optional(),
|
|
1630
|
+
depth: import_zod23.z.number().int().positive().optional()
|
|
1485
1631
|
}),
|
|
1486
1632
|
fromArgv: (argv, path) => ({
|
|
1487
1633
|
bundlePath: path,
|
|
@@ -1503,31 +1649,31 @@ var traceCommand = define({
|
|
|
1503
1649
|
});
|
|
1504
1650
|
|
|
1505
1651
|
// src/commands/types.ts
|
|
1506
|
-
var
|
|
1652
|
+
var import_zod24 = require("zod");
|
|
1507
1653
|
var typesCommand = define({
|
|
1508
1654
|
name: "types",
|
|
1509
1655
|
tool: "kb_types",
|
|
1510
1656
|
usage: "types",
|
|
1511
1657
|
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.",
|
|
1512
|
-
input:
|
|
1658
|
+
input: import_zod24.z.object({}),
|
|
1513
1659
|
fromArgv: () => ({}),
|
|
1514
1660
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
1515
1661
|
});
|
|
1516
1662
|
|
|
1517
1663
|
// src/commands/unpin.ts
|
|
1518
|
-
var
|
|
1664
|
+
var import_zod25 = require("zod");
|
|
1519
1665
|
var unpinCommand = define({
|
|
1520
1666
|
name: "unpin",
|
|
1521
1667
|
tool: "kb_unpin",
|
|
1522
1668
|
usage: "unpin [bundle-path]",
|
|
1523
1669
|
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.",
|
|
1524
|
-
input:
|
|
1670
|
+
input: import_zod25.z.object({ bundlePath }),
|
|
1525
1671
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
1526
1672
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
1527
1673
|
});
|
|
1528
1674
|
|
|
1529
1675
|
// src/commands/validate.ts
|
|
1530
|
-
var
|
|
1676
|
+
var import_zod26 = require("zod");
|
|
1531
1677
|
|
|
1532
1678
|
// src/validate.ts
|
|
1533
1679
|
function validateBundle(records) {
|
|
@@ -1570,14 +1716,43 @@ var validateCommand = define({
|
|
|
1570
1716
|
tool: "kb_validate",
|
|
1571
1717
|
usage: "validate",
|
|
1572
1718
|
description: "Check pointers no single record can see: supersession links that disagree between the two records, and assumptions that cite sources. Per-record shape is enforced on every read, so a problem here means someone edited a file by hand.",
|
|
1573
|
-
input:
|
|
1719
|
+
input: import_zod26.z.object({ bundlePath }),
|
|
1574
1720
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
1575
1721
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
1576
1722
|
failsWhen: (result) => Array.isArray(result) && result.length > 0
|
|
1577
1723
|
});
|
|
1578
1724
|
|
|
1725
|
+
// src/commands/verify.ts
|
|
1726
|
+
var import_zod27 = require("zod");
|
|
1727
|
+
var verifyCommand = define({
|
|
1728
|
+
name: "verify",
|
|
1729
|
+
tool: "kb_verify",
|
|
1730
|
+
usage: "verify <concept-id> --note <text>",
|
|
1731
|
+
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.",
|
|
1732
|
+
input: import_zod27.z.object({
|
|
1733
|
+
bundlePath,
|
|
1734
|
+
conceptId,
|
|
1735
|
+
note: import_zod27.z.string().refine((s) => s.trim().length > 0, {
|
|
1736
|
+
message: "note must say what the check found"
|
|
1737
|
+
})
|
|
1738
|
+
}),
|
|
1739
|
+
fromArgv: (argv, path) => ({
|
|
1740
|
+
bundlePath: path,
|
|
1741
|
+
conceptId: argv[1],
|
|
1742
|
+
note: argvFlag(argv, "--note")
|
|
1743
|
+
}),
|
|
1744
|
+
run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, note }) => {
|
|
1745
|
+
await assertBaseNotFrozen(process.cwd(), path);
|
|
1746
|
+
const record = await store.verify(path, id, note, actor, now());
|
|
1747
|
+
return {
|
|
1748
|
+
conceptId: record.conceptId,
|
|
1749
|
+
verified: record.frontmatter.verified?.length ?? 0
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1579
1754
|
// src/commands/write.ts
|
|
1580
|
-
var
|
|
1755
|
+
var import_zod28 = require("zod");
|
|
1581
1756
|
var writeCommand = define({
|
|
1582
1757
|
name: "write",
|
|
1583
1758
|
tool: "kb_write",
|
|
@@ -1591,9 +1766,9 @@ var writeCommand = define({
|
|
|
1591
1766
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
1592
1767
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
1593
1768
|
].join("\n"),
|
|
1594
|
-
input:
|
|
1769
|
+
input: import_zod28.z.object({
|
|
1595
1770
|
bundlePath,
|
|
1596
|
-
type:
|
|
1771
|
+
type: import_zod28.z.enum(KB_RECORD_TYPES),
|
|
1597
1772
|
input: composeInputSchema
|
|
1598
1773
|
}),
|
|
1599
1774
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -1617,7 +1792,7 @@ var writeCommand = define({
|
|
|
1617
1792
|
});
|
|
1618
1793
|
|
|
1619
1794
|
// src/commands/write-decision.ts
|
|
1620
|
-
var
|
|
1795
|
+
var import_zod29 = require("zod");
|
|
1621
1796
|
var writeDecisionCommand = define({
|
|
1622
1797
|
name: "write-decision",
|
|
1623
1798
|
tool: "kb_write_decision",
|
|
@@ -1630,7 +1805,7 @@ var writeDecisionCommand = define({
|
|
|
1630
1805
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
1631
1806
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
1632
1807
|
].join("\n"),
|
|
1633
|
-
input:
|
|
1808
|
+
input: import_zod29.z.object({ bundlePath, input: decisionInputSchema }),
|
|
1634
1809
|
fromArgv: async (_argv, path, stdin) => ({
|
|
1635
1810
|
bundlePath: path,
|
|
1636
1811
|
input: JSON.parse(await stdin())
|
|
@@ -1658,7 +1833,9 @@ var KB_COMMANDS = [
|
|
|
1658
1833
|
statusCommand,
|
|
1659
1834
|
supersedeCommand,
|
|
1660
1835
|
answerCommand,
|
|
1836
|
+
verifyCommand,
|
|
1661
1837
|
loadCommand,
|
|
1838
|
+
packCommand,
|
|
1662
1839
|
queryCommand,
|
|
1663
1840
|
traceCommand,
|
|
1664
1841
|
listCommand,
|
|
@@ -1773,6 +1950,46 @@ var KbWriteConflictError = class extends BaseError {
|
|
|
1773
1950
|
}
|
|
1774
1951
|
conceptId;
|
|
1775
1952
|
};
|
|
1953
|
+
var KbSelfVerificationError = class extends BaseError {
|
|
1954
|
+
constructor(conceptId2, actor, generatedBy) {
|
|
1955
|
+
super({
|
|
1956
|
+
message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
|
|
1957
|
+
errorType: "KbSelfVerification" /* KbSelfVerification */,
|
|
1958
|
+
code: 400,
|
|
1959
|
+
fault: "User" /* User */,
|
|
1960
|
+
retriable: false,
|
|
1961
|
+
reportToUser: true,
|
|
1962
|
+
details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
|
|
1963
|
+
});
|
|
1964
|
+
this.conceptId = conceptId2;
|
|
1965
|
+
this.actor = actor;
|
|
1966
|
+
this.generatedBy = generatedBy;
|
|
1967
|
+
}
|
|
1968
|
+
conceptId;
|
|
1969
|
+
actor;
|
|
1970
|
+
generatedBy;
|
|
1971
|
+
};
|
|
1972
|
+
var KbPackBudgetExceededError = class extends BaseError {
|
|
1973
|
+
constructor(recordCount, approxTokens2, budgetTokens, excluded) {
|
|
1974
|
+
super({
|
|
1975
|
+
message: `kb: a pack of ${recordCount} records is ~${approxTokens2} tokens against a budget of ${budgetTokens} \u2014 lower hops or maxNodes, or raise the budget`,
|
|
1976
|
+
errorType: "KbPackBudgetExceeded" /* KbPackBudgetExceeded */,
|
|
1977
|
+
code: 400,
|
|
1978
|
+
fault: "User" /* User */,
|
|
1979
|
+
retriable: false,
|
|
1980
|
+
reportToUser: true,
|
|
1981
|
+
details: { recordCount, approxTokens: approxTokens2, budgetTokens, excluded }
|
|
1982
|
+
});
|
|
1983
|
+
this.recordCount = recordCount;
|
|
1984
|
+
this.approxTokens = approxTokens2;
|
|
1985
|
+
this.budgetTokens = budgetTokens;
|
|
1986
|
+
this.excluded = excluded;
|
|
1987
|
+
}
|
|
1988
|
+
recordCount;
|
|
1989
|
+
approxTokens;
|
|
1990
|
+
budgetTokens;
|
|
1991
|
+
excluded;
|
|
1992
|
+
};
|
|
1776
1993
|
var KbInvalidConceptIdError = class extends BaseError {
|
|
1777
1994
|
constructor(message, details) {
|
|
1778
1995
|
super({
|
|
@@ -1873,6 +2090,90 @@ async function loadQmd(logger) {
|
|
|
1873
2090
|
}
|
|
1874
2091
|
}
|
|
1875
2092
|
|
|
2093
|
+
// src/pack.ts
|
|
2094
|
+
var DEFAULT_PACK_HOPS = 2;
|
|
2095
|
+
var DEFAULT_PACK_MAX_NODES = 20;
|
|
2096
|
+
var TYPE_PRIORITY = [
|
|
2097
|
+
"decision",
|
|
2098
|
+
"constraint",
|
|
2099
|
+
"requirement",
|
|
2100
|
+
...KB_RECORD_TYPES.filter(
|
|
2101
|
+
(type) => !["decision", "constraint", "requirement"].includes(type)
|
|
2102
|
+
)
|
|
2103
|
+
];
|
|
2104
|
+
function pack(bundle, rootId, options = {}) {
|
|
2105
|
+
const hops = options.hops ?? DEFAULT_PACK_HOPS;
|
|
2106
|
+
const maxNodes = options.maxNodes ?? DEFAULT_PACK_MAX_NODES;
|
|
2107
|
+
const budgetTokens = options.budgetTokens ?? DEFAULT_LOAD_BUDGET;
|
|
2108
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
2109
|
+
const root = byId.get(rootId);
|
|
2110
|
+
if (!root) throw new KbRecordNotFoundError(rootId);
|
|
2111
|
+
const reached = [{ record: root, depth: 0 }];
|
|
2112
|
+
const seen = /* @__PURE__ */ new Set([rootId]);
|
|
2113
|
+
let frontier = [root];
|
|
2114
|
+
for (let depth = 1; frontier.length; depth += 1) {
|
|
2115
|
+
const next = [];
|
|
2116
|
+
for (const from of frontier) {
|
|
2117
|
+
for (const { record } of neighbours(from, bundle)) {
|
|
2118
|
+
if (seen.has(record.conceptId)) continue;
|
|
2119
|
+
seen.add(record.conceptId);
|
|
2120
|
+
reached.push({ record, depth });
|
|
2121
|
+
next.push(record);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
frontier = next;
|
|
2125
|
+
}
|
|
2126
|
+
reached.sort(byRank);
|
|
2127
|
+
const within = reached.filter((entry) => entry.depth <= hops);
|
|
2128
|
+
const kept = within.slice(0, maxNodes);
|
|
2129
|
+
const excluded = [
|
|
2130
|
+
...within.slice(maxNodes),
|
|
2131
|
+
...reached.filter((entry) => entry.depth > hops)
|
|
2132
|
+
].map((entry) => entry.record.conceptId).sort();
|
|
2133
|
+
const adjudicated = adjudicate(
|
|
2134
|
+
kept.map((entry) => entry.record),
|
|
2135
|
+
bundle
|
|
2136
|
+
);
|
|
2137
|
+
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
2138
|
+
const whole = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
2139
|
+
const tokensLoaded = whole.reduce((total, hit) => total + estimateTokens(hit.record), 0) + superseded.reduce((total, entry) => total + estimateStubTokens(entry), 0);
|
|
2140
|
+
const recordCount = adjudicated.length;
|
|
2141
|
+
if (tokensLoaded > budgetTokens) {
|
|
2142
|
+
throw new KbPackBudgetExceededError(
|
|
2143
|
+
recordCount,
|
|
2144
|
+
tokensLoaded,
|
|
2145
|
+
budgetTokens,
|
|
2146
|
+
excluded
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
return {
|
|
2150
|
+
root: rootId,
|
|
2151
|
+
records: whole.map((hit) => ({
|
|
2152
|
+
conceptId: hit.record.conceptId,
|
|
2153
|
+
title: hit.record.frontmatter.title ?? null,
|
|
2154
|
+
standing: hit.standing,
|
|
2155
|
+
supersededBy: hit.heads.map((head) => head.conceptId),
|
|
2156
|
+
warnings: hit.warnings,
|
|
2157
|
+
anchors: hit.record.frontmatter.strauss_anchors ?? [],
|
|
2158
|
+
body: hit.record.body
|
|
2159
|
+
})),
|
|
2160
|
+
superseded,
|
|
2161
|
+
excluded,
|
|
2162
|
+
recordCount,
|
|
2163
|
+
tokensLoaded,
|
|
2164
|
+
budgetTokens
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
function byRank(left, right) {
|
|
2168
|
+
return left.depth - right.depth || typeRank(left.record) - typeRank(right.record) || (left.record.frontmatter.title ?? "").localeCompare(
|
|
2169
|
+
right.record.frontmatter.title ?? ""
|
|
2170
|
+
) || left.record.conceptId.localeCompare(right.record.conceptId);
|
|
2171
|
+
}
|
|
2172
|
+
function typeRank(record) {
|
|
2173
|
+
const index = TYPE_PRIORITY.indexOf(record.frontmatter.type);
|
|
2174
|
+
return index === -1 ? TYPE_PRIORITY.length : index;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
1876
2177
|
// src/kb-store.ts
|
|
1877
2178
|
var KB_DIR = (0, import_node_path6.join)(".strauss", "kb");
|
|
1878
2179
|
var STORE_OWNED = /* @__PURE__ */ new Set([INDEX_FILE, LOG_FILE, SEARCH_INDEX_FILE]);
|
|
@@ -1991,6 +2292,40 @@ var KbStore = class {
|
|
|
1991
2292
|
{ operation: `status:${status}`, by: actor }
|
|
1992
2293
|
);
|
|
1993
2294
|
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
2297
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
2298
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
2299
|
+
*
|
|
2300
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
2301
|
+
* human: the generator re-reading its own output is not an independent
|
|
2302
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
2303
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
2304
|
+
* logs what it publishes.
|
|
2305
|
+
*/
|
|
2306
|
+
async verify(bundlePath2, conceptId2, note, actor = "unknown", at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2307
|
+
const event = kbVerifiedEventSchema.parse({ by: actor, at, note });
|
|
2308
|
+
const existing = await this.read(bundlePath2, conceptId2);
|
|
2309
|
+
if (!existing) throw new KbRecordNotFoundError(conceptId2);
|
|
2310
|
+
const generatedBy = existing.frontmatter.generated?.by;
|
|
2311
|
+
if (generatedBy !== void 0 && actor.toLowerCase() === generatedBy.toLowerCase() && !normalizeActor(actor).startsWith("human:")) {
|
|
2312
|
+
await this.record(this.root(bundlePath2), {
|
|
2313
|
+
operation: "verify:refused",
|
|
2314
|
+
conceptId: conceptId2,
|
|
2315
|
+
by: actor
|
|
2316
|
+
});
|
|
2317
|
+
throw new KbSelfVerificationError(conceptId2, actor, generatedBy);
|
|
2318
|
+
}
|
|
2319
|
+
return this.mutate(
|
|
2320
|
+
bundlePath2,
|
|
2321
|
+
conceptId2,
|
|
2322
|
+
(frontmatter) => ({
|
|
2323
|
+
...frontmatter,
|
|
2324
|
+
verified: [...frontmatter.verified ?? [], event]
|
|
2325
|
+
}),
|
|
2326
|
+
{ operation: "verify", by: actor }
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
1994
2329
|
/**
|
|
1995
2330
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
1996
2331
|
*
|
|
@@ -2128,6 +2463,10 @@ ${answer}
|
|
|
2128
2463
|
async trace(bundlePath2, seedId, options = {}) {
|
|
2129
2464
|
return trace(seedId, await this.list(bundlePath2), options);
|
|
2130
2465
|
}
|
|
2466
|
+
/** A bounded neighbourhood around one record. See `pack.ts`. */
|
|
2467
|
+
async pack(bundlePath2, rootId, options = {}) {
|
|
2468
|
+
return pack(await this.list(bundlePath2), rootId, options);
|
|
2469
|
+
}
|
|
2131
2470
|
/**
|
|
2132
2471
|
* The stored index, rebuilt if it disagrees with the records.
|
|
2133
2472
|
*
|
|
@@ -2325,10 +2664,18 @@ function matches(record, needle) {
|
|
|
2325
2664
|
(field) => field?.toLowerCase().includes(needle)
|
|
2326
2665
|
);
|
|
2327
2666
|
}
|
|
2667
|
+
function normalizeActor(id) {
|
|
2668
|
+
const colon = id.indexOf(":");
|
|
2669
|
+
if (colon === -1) return id.toLowerCase();
|
|
2670
|
+
return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
|
|
2671
|
+
}
|
|
2328
2672
|
function digest(contents) {
|
|
2329
2673
|
return (0, import_node_crypto.createHash)("sha256").update(contents).digest("hex");
|
|
2330
2674
|
}
|
|
2331
2675
|
|
|
2676
|
+
// src/version.ts
|
|
2677
|
+
var VERSION = true ? "0.1.7" : "0.0.0-dev";
|
|
2678
|
+
|
|
2332
2679
|
// src/cli.ts
|
|
2333
2680
|
async function runKbCli(argv) {
|
|
2334
2681
|
const { bundle, rest } = takeBundle(argv);
|
|
@@ -2337,6 +2684,11 @@ async function runKbCli(argv) {
|
|
|
2337
2684
|
process.stdout.write(usage());
|
|
2338
2685
|
return;
|
|
2339
2686
|
}
|
|
2687
|
+
if (name === "--version" || name === "-v") {
|
|
2688
|
+
process.stdout.write(`${VERSION}
|
|
2689
|
+
`);
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2340
2692
|
const command = KB_COMMANDS_BY_NAME.get(name);
|
|
2341
2693
|
if (!command) die(`unknown command ${name}`);
|
|
2342
2694
|
const raw = await command.fromArgv(rest, bundle, readStdin);
|
|
@@ -2406,6 +2758,7 @@ function usage() {
|
|
|
2406
2758
|
),
|
|
2407
2759
|
"",
|
|
2408
2760
|
` --bundle PATH defaults to ./${KB_DIR}`,
|
|
2761
|
+
" --version the installed package version",
|
|
2409
2762
|
" STRAUSS_KB_ACTOR names the writer in the log",
|
|
2410
2763
|
""
|
|
2411
2764
|
].join("\n");
|