@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/index.cjs
CHANGED
|
@@ -35,6 +35,9 @@ __export(index_exports, {
|
|
|
35
35
|
CONTEXT_END: () => CONTEXT_END,
|
|
36
36
|
CONTEXT_PROFILES: () => CONTEXT_PROFILES,
|
|
37
37
|
DECISION_TYPE: () => DECISION_TYPE,
|
|
38
|
+
DEFAULT_LOAD_BUDGET: () => DEFAULT_LOAD_BUDGET,
|
|
39
|
+
DEFAULT_PACK_HOPS: () => DEFAULT_PACK_HOPS,
|
|
40
|
+
DEFAULT_PACK_MAX_NODES: () => DEFAULT_PACK_MAX_NODES,
|
|
38
41
|
ErrorTypes: () => ErrorTypes,
|
|
39
42
|
Fault: () => Fault,
|
|
40
43
|
INDEX_FILE: () => INDEX_FILE,
|
|
@@ -43,15 +46,18 @@ __export(index_exports, {
|
|
|
43
46
|
KB_CONCEPT_ID_PATTERN: () => KB_CONCEPT_ID_PATTERN,
|
|
44
47
|
KB_CONFIDENCES: () => KB_CONFIDENCES,
|
|
45
48
|
KB_DIR: () => KB_DIR,
|
|
49
|
+
KB_EDGE_KINDS: () => KB_EDGE_KINDS,
|
|
46
50
|
KB_MATERIALITIES: () => KB_MATERIALITIES,
|
|
47
51
|
KB_RECORD_STATUSES: () => KB_RECORD_STATUSES,
|
|
48
52
|
KB_RECORD_TYPES: () => KB_RECORD_TYPES,
|
|
49
53
|
KB_SLUG_PATTERN: () => KB_SLUG_PATTERN,
|
|
50
54
|
KbBaseFrozenError: () => KbBaseFrozenError,
|
|
51
55
|
KbInvalidConceptIdError: () => KbInvalidConceptIdError,
|
|
56
|
+
KbPackBudgetExceededError: () => KbPackBudgetExceededError,
|
|
52
57
|
KbPinsMalformedError: () => KbPinsMalformedError,
|
|
53
58
|
KbRecordAlreadyExistsError: () => KbRecordAlreadyExistsError,
|
|
54
59
|
KbRecordNotFoundError: () => KbRecordNotFoundError,
|
|
60
|
+
KbSelfVerificationError: () => KbSelfVerificationError,
|
|
55
61
|
KbStore: () => KbStore,
|
|
56
62
|
KbWriteConflictError: () => KbWriteConflictError,
|
|
57
63
|
LOG_FILE: () => LOG_FILE,
|
|
@@ -72,6 +78,7 @@ __export(index_exports, {
|
|
|
72
78
|
contextProfileBudgets: () => contextProfileBudgets,
|
|
73
79
|
createKbMcpServer: () => createKbMcpServer,
|
|
74
80
|
decisionInputSchema: () => decisionInputSchema,
|
|
81
|
+
edgeNeighbours: () => edgeNeighbours,
|
|
75
82
|
indexIsStale: () => indexIsStale,
|
|
76
83
|
isKbRecordType: () => isKbRecordType,
|
|
77
84
|
isNoDecisionRecord: () => isNoDecisionRecord,
|
|
@@ -82,10 +89,13 @@ __export(index_exports, {
|
|
|
82
89
|
kbLogEntrySchema: () => kbLogEntrySchema,
|
|
83
90
|
kbRecordFrontmatterSchema: () => kbRecordFrontmatterSchema,
|
|
84
91
|
kbSourceSchema: () => kbSourceSchema,
|
|
92
|
+
kbVerifiedEventSchema: () => kbVerifiedEventSchema,
|
|
85
93
|
listPins: () => listPins,
|
|
86
94
|
loadQmd: () => loadQmd,
|
|
87
95
|
matchToDiff: () => matchToDiff,
|
|
88
96
|
mergedContextBudgets: () => mergedContextBudgets,
|
|
97
|
+
neighbours: () => neighbours,
|
|
98
|
+
pack: () => pack,
|
|
89
99
|
parseLog: () => parseLog,
|
|
90
100
|
parseMarkdownWithFrontmatter: () => parseMarkdownWithFrontmatter,
|
|
91
101
|
pinBase: () => pinBase,
|
|
@@ -154,6 +164,11 @@ var kbActorStampSchema = import_zod.z.object({
|
|
|
154
164
|
by: import_zod.z.string().min(1),
|
|
155
165
|
at: import_zod.z.string().min(1)
|
|
156
166
|
}).passthrough();
|
|
167
|
+
var kbVerifiedEventSchema = kbActorStampSchema.extend({
|
|
168
|
+
note: import_zod.z.string().refine((s) => s.trim().length > 0, {
|
|
169
|
+
message: "note must say what the check found"
|
|
170
|
+
})
|
|
171
|
+
});
|
|
157
172
|
var kbAnchorSchema = import_zod.z.object({
|
|
158
173
|
file: import_zod.z.string().min(1),
|
|
159
174
|
symbol: import_zod.z.string().min(1).optional()
|
|
@@ -237,7 +252,9 @@ var Fault = /* @__PURE__ */ ((Fault2) => {
|
|
|
237
252
|
var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
238
253
|
ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
|
|
239
254
|
ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
|
|
255
|
+
ErrorTypes2["KbPackBudgetExceeded"] = "KbPackBudgetExceeded";
|
|
240
256
|
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
257
|
+
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
241
258
|
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
242
259
|
return ErrorTypes2;
|
|
243
260
|
})(ErrorTypes || {});
|
|
@@ -306,6 +323,46 @@ var KbWriteConflictError = class extends BaseError {
|
|
|
306
323
|
}
|
|
307
324
|
conceptId;
|
|
308
325
|
};
|
|
326
|
+
var KbSelfVerificationError = class extends BaseError {
|
|
327
|
+
constructor(conceptId2, actor, generatedBy) {
|
|
328
|
+
super({
|
|
329
|
+
message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
|
|
330
|
+
errorType: "KbSelfVerification" /* KbSelfVerification */,
|
|
331
|
+
code: 400,
|
|
332
|
+
fault: "User" /* User */,
|
|
333
|
+
retriable: false,
|
|
334
|
+
reportToUser: true,
|
|
335
|
+
details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
|
|
336
|
+
});
|
|
337
|
+
this.conceptId = conceptId2;
|
|
338
|
+
this.actor = actor;
|
|
339
|
+
this.generatedBy = generatedBy;
|
|
340
|
+
}
|
|
341
|
+
conceptId;
|
|
342
|
+
actor;
|
|
343
|
+
generatedBy;
|
|
344
|
+
};
|
|
345
|
+
var KbPackBudgetExceededError = class extends BaseError {
|
|
346
|
+
constructor(recordCount, approxTokens2, budgetTokens, excluded) {
|
|
347
|
+
super({
|
|
348
|
+
message: `kb: a pack of ${recordCount} records is ~${approxTokens2} tokens against a budget of ${budgetTokens} \u2014 lower hops or maxNodes, or raise the budget`,
|
|
349
|
+
errorType: "KbPackBudgetExceeded" /* KbPackBudgetExceeded */,
|
|
350
|
+
code: 400,
|
|
351
|
+
fault: "User" /* User */,
|
|
352
|
+
retriable: false,
|
|
353
|
+
reportToUser: true,
|
|
354
|
+
details: { recordCount, approxTokens: approxTokens2, budgetTokens, excluded }
|
|
355
|
+
});
|
|
356
|
+
this.recordCount = recordCount;
|
|
357
|
+
this.approxTokens = approxTokens2;
|
|
358
|
+
this.budgetTokens = budgetTokens;
|
|
359
|
+
this.excluded = excluded;
|
|
360
|
+
}
|
|
361
|
+
recordCount;
|
|
362
|
+
approxTokens;
|
|
363
|
+
budgetTokens;
|
|
364
|
+
excluded;
|
|
365
|
+
};
|
|
309
366
|
var KbInvalidConceptIdError = class extends BaseError {
|
|
310
367
|
constructor(message, details) {
|
|
311
368
|
super({
|
|
@@ -558,41 +615,48 @@ async function loadQmd(logger) {
|
|
|
558
615
|
}
|
|
559
616
|
}
|
|
560
617
|
|
|
561
|
-
// src/
|
|
562
|
-
var
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
for (const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
581
|
-
existing.via.push(edge);
|
|
582
|
-
}
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
reached.set(record.conceptId, { record, depth, via: [edge] });
|
|
586
|
-
next.push(record);
|
|
587
|
-
}
|
|
618
|
+
// src/kb-edges.ts
|
|
619
|
+
var KB_EDGE_KINDS = [
|
|
620
|
+
"body-link",
|
|
621
|
+
"supersession",
|
|
622
|
+
"anchor",
|
|
623
|
+
"source"
|
|
624
|
+
];
|
|
625
|
+
var BODY_LINK_TARGET = new RegExp(
|
|
626
|
+
`\\]\\((${KB_CONCEPT_ID_PATTERN.source.replace(/^\^|\$$/g, "")})\\.md\\)`,
|
|
627
|
+
"g"
|
|
628
|
+
);
|
|
629
|
+
function neighbours(from, bundle, kinds = KB_EDGE_KINDS) {
|
|
630
|
+
const found = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const kind of kinds) {
|
|
632
|
+
for (const record of edgeNeighbours(from, bundle, kind)) {
|
|
633
|
+
const existing = found.get(record.conceptId);
|
|
634
|
+
if (existing) {
|
|
635
|
+
if (!existing.via.includes(kind)) existing.via.push(kind);
|
|
636
|
+
continue;
|
|
588
637
|
}
|
|
638
|
+
found.set(record.conceptId, { record, via: [kind] });
|
|
589
639
|
}
|
|
590
|
-
frontier = next;
|
|
591
640
|
}
|
|
592
|
-
return [...
|
|
641
|
+
return [...found.values()];
|
|
593
642
|
}
|
|
594
|
-
function
|
|
595
|
-
switch (
|
|
643
|
+
function edgeNeighbours(from, bundle, kind) {
|
|
644
|
+
switch (kind) {
|
|
645
|
+
// A link whose target is not in the bundle is legal per compose.ts —
|
|
646
|
+
// records are routinely written before the ones they point at exist — so
|
|
647
|
+
// missing targets are skipped, never an error.
|
|
648
|
+
case "body-link": {
|
|
649
|
+
const targets = new Set(
|
|
650
|
+
[...from.body.matchAll(BODY_LINK_TARGET)].map((match) => match[1])
|
|
651
|
+
);
|
|
652
|
+
if (!targets.size) return [];
|
|
653
|
+
return bundle.filter(
|
|
654
|
+
(candidate) => candidate.conceptId !== from.conceptId && targets.has(candidate.conceptId)
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
// Both directions and both pointers: `supersede()` writes the pair, but a
|
|
658
|
+
// hand-edit can leave one side behind, and a walk trusting one pointer
|
|
659
|
+
// would miss a replacement the bundle openly declares.
|
|
596
660
|
case "supersession":
|
|
597
661
|
return bundle.filter(
|
|
598
662
|
(candidate) => candidate.conceptId !== from.conceptId && (candidate.conceptId === from.frontmatter.strauss_superseded_by || from.frontmatter.strauss_supersedes?.includes(
|
|
@@ -626,11 +690,129 @@ function anchorsTouch(left, right) {
|
|
|
626
690
|
if (!left.symbol || !right.symbol) return true;
|
|
627
691
|
return left.symbol === right.symbol;
|
|
628
692
|
}
|
|
693
|
+
|
|
694
|
+
// src/trace.ts
|
|
695
|
+
var TRACE_EDGES = ["supersession", "anchor", "source"];
|
|
696
|
+
function trace(seedId, bundle, options = {}) {
|
|
697
|
+
const edges = options.edges?.length ? options.edges : TRACE_EDGES;
|
|
698
|
+
const maxDepth = options.depth ?? 3;
|
|
699
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
700
|
+
const seed = byId.get(seedId);
|
|
701
|
+
if (!seed) return [];
|
|
702
|
+
const reached = /* @__PURE__ */ new Map([
|
|
703
|
+
[seedId, { record: seed, depth: 0, via: [] }]
|
|
704
|
+
]);
|
|
705
|
+
let frontier = [seed];
|
|
706
|
+
for (let depth = 1; depth <= maxDepth && frontier.length; depth += 1) {
|
|
707
|
+
const next = [];
|
|
708
|
+
for (const from of frontier) {
|
|
709
|
+
for (const edge of edges) {
|
|
710
|
+
for (const record of edgeNeighbours(from, bundle, edge)) {
|
|
711
|
+
const existing = reached.get(record.conceptId);
|
|
712
|
+
if (existing) {
|
|
713
|
+
if (existing.depth > 0 && !existing.via.includes(edge)) {
|
|
714
|
+
existing.via.push(edge);
|
|
715
|
+
}
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
reached.set(record.conceptId, { record, depth, via: [edge] });
|
|
719
|
+
next.push(record);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
frontier = next;
|
|
724
|
+
}
|
|
725
|
+
return [...reached.values()].sort(byGeneratedAt);
|
|
726
|
+
}
|
|
629
727
|
function byGeneratedAt(left, right) {
|
|
630
728
|
const at = (step) => step.record.frontmatter.generated?.at ?? "";
|
|
631
729
|
return at(left).localeCompare(at(right)) || left.depth - right.depth;
|
|
632
730
|
}
|
|
633
731
|
|
|
732
|
+
// src/pack.ts
|
|
733
|
+
var DEFAULT_PACK_HOPS = 2;
|
|
734
|
+
var DEFAULT_PACK_MAX_NODES = 20;
|
|
735
|
+
var TYPE_PRIORITY = [
|
|
736
|
+
"decision",
|
|
737
|
+
"constraint",
|
|
738
|
+
"requirement",
|
|
739
|
+
...KB_RECORD_TYPES.filter(
|
|
740
|
+
(type) => !["decision", "constraint", "requirement"].includes(type)
|
|
741
|
+
)
|
|
742
|
+
];
|
|
743
|
+
function pack(bundle, rootId, options = {}) {
|
|
744
|
+
const hops = options.hops ?? DEFAULT_PACK_HOPS;
|
|
745
|
+
const maxNodes = options.maxNodes ?? DEFAULT_PACK_MAX_NODES;
|
|
746
|
+
const budgetTokens = options.budgetTokens ?? DEFAULT_LOAD_BUDGET;
|
|
747
|
+
const byId = new Map(bundle.map((record) => [record.conceptId, record]));
|
|
748
|
+
const root = byId.get(rootId);
|
|
749
|
+
if (!root) throw new KbRecordNotFoundError(rootId);
|
|
750
|
+
const reached = [{ record: root, depth: 0 }];
|
|
751
|
+
const seen = /* @__PURE__ */ new Set([rootId]);
|
|
752
|
+
let frontier = [root];
|
|
753
|
+
for (let depth = 1; frontier.length; depth += 1) {
|
|
754
|
+
const next = [];
|
|
755
|
+
for (const from of frontier) {
|
|
756
|
+
for (const { record } of neighbours(from, bundle)) {
|
|
757
|
+
if (seen.has(record.conceptId)) continue;
|
|
758
|
+
seen.add(record.conceptId);
|
|
759
|
+
reached.push({ record, depth });
|
|
760
|
+
next.push(record);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
frontier = next;
|
|
764
|
+
}
|
|
765
|
+
reached.sort(byRank);
|
|
766
|
+
const within = reached.filter((entry) => entry.depth <= hops);
|
|
767
|
+
const kept = within.slice(0, maxNodes);
|
|
768
|
+
const excluded = [
|
|
769
|
+
...within.slice(maxNodes),
|
|
770
|
+
...reached.filter((entry) => entry.depth > hops)
|
|
771
|
+
].map((entry) => entry.record.conceptId).sort();
|
|
772
|
+
const adjudicated = adjudicate(
|
|
773
|
+
kept.map((entry) => entry.record),
|
|
774
|
+
bundle
|
|
775
|
+
);
|
|
776
|
+
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
777
|
+
const whole = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
778
|
+
const tokensLoaded = whole.reduce((total, hit) => total + estimateTokens(hit.record), 0) + superseded.reduce((total, entry) => total + estimateStubTokens(entry), 0);
|
|
779
|
+
const recordCount = adjudicated.length;
|
|
780
|
+
if (tokensLoaded > budgetTokens) {
|
|
781
|
+
throw new KbPackBudgetExceededError(
|
|
782
|
+
recordCount,
|
|
783
|
+
tokensLoaded,
|
|
784
|
+
budgetTokens,
|
|
785
|
+
excluded
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
return {
|
|
789
|
+
root: rootId,
|
|
790
|
+
records: whole.map((hit) => ({
|
|
791
|
+
conceptId: hit.record.conceptId,
|
|
792
|
+
title: hit.record.frontmatter.title ?? null,
|
|
793
|
+
standing: hit.standing,
|
|
794
|
+
supersededBy: hit.heads.map((head) => head.conceptId),
|
|
795
|
+
warnings: hit.warnings,
|
|
796
|
+
anchors: hit.record.frontmatter.strauss_anchors ?? [],
|
|
797
|
+
body: hit.record.body
|
|
798
|
+
})),
|
|
799
|
+
superseded,
|
|
800
|
+
excluded,
|
|
801
|
+
recordCount,
|
|
802
|
+
tokensLoaded,
|
|
803
|
+
budgetTokens
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function byRank(left, right) {
|
|
807
|
+
return left.depth - right.depth || typeRank(left.record) - typeRank(right.record) || (left.record.frontmatter.title ?? "").localeCompare(
|
|
808
|
+
right.record.frontmatter.title ?? ""
|
|
809
|
+
) || left.record.conceptId.localeCompare(right.record.conceptId);
|
|
810
|
+
}
|
|
811
|
+
function typeRank(record) {
|
|
812
|
+
const index = TYPE_PRIORITY.indexOf(record.frontmatter.type);
|
|
813
|
+
return index === -1 ? TYPE_PRIORITY.length : index;
|
|
814
|
+
}
|
|
815
|
+
|
|
634
816
|
// src/kb-store.ts
|
|
635
817
|
var KB_DIR = (0, import_node_path2.join)(".strauss", "kb");
|
|
636
818
|
var STORE_OWNED = /* @__PURE__ */ new Set([INDEX_FILE, LOG_FILE, SEARCH_INDEX_FILE]);
|
|
@@ -749,6 +931,40 @@ var KbStore = class {
|
|
|
749
931
|
{ operation: `status:${status}`, by: actor }
|
|
750
932
|
);
|
|
751
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
936
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
937
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
938
|
+
*
|
|
939
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
940
|
+
* human: the generator re-reading its own output is not an independent
|
|
941
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
942
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
943
|
+
* logs what it publishes.
|
|
944
|
+
*/
|
|
945
|
+
async verify(bundlePath2, conceptId2, note, actor = "unknown", at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
946
|
+
const event = kbVerifiedEventSchema.parse({ by: actor, at, note });
|
|
947
|
+
const existing = await this.read(bundlePath2, conceptId2);
|
|
948
|
+
if (!existing) throw new KbRecordNotFoundError(conceptId2);
|
|
949
|
+
const generatedBy = existing.frontmatter.generated?.by;
|
|
950
|
+
if (generatedBy !== void 0 && actor.toLowerCase() === generatedBy.toLowerCase() && !normalizeActor(actor).startsWith("human:")) {
|
|
951
|
+
await this.record(this.root(bundlePath2), {
|
|
952
|
+
operation: "verify:refused",
|
|
953
|
+
conceptId: conceptId2,
|
|
954
|
+
by: actor
|
|
955
|
+
});
|
|
956
|
+
throw new KbSelfVerificationError(conceptId2, actor, generatedBy);
|
|
957
|
+
}
|
|
958
|
+
return this.mutate(
|
|
959
|
+
bundlePath2,
|
|
960
|
+
conceptId2,
|
|
961
|
+
(frontmatter) => ({
|
|
962
|
+
...frontmatter,
|
|
963
|
+
verified: [...frontmatter.verified ?? [], event]
|
|
964
|
+
}),
|
|
965
|
+
{ operation: "verify", by: actor }
|
|
966
|
+
);
|
|
967
|
+
}
|
|
752
968
|
/**
|
|
753
969
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
754
970
|
*
|
|
@@ -886,6 +1102,10 @@ ${answer}
|
|
|
886
1102
|
async trace(bundlePath2, seedId, options = {}) {
|
|
887
1103
|
return trace(seedId, await this.list(bundlePath2), options);
|
|
888
1104
|
}
|
|
1105
|
+
/** A bounded neighbourhood around one record. See `pack.ts`. */
|
|
1106
|
+
async pack(bundlePath2, rootId, options = {}) {
|
|
1107
|
+
return pack(await this.list(bundlePath2), rootId, options);
|
|
1108
|
+
}
|
|
889
1109
|
/**
|
|
890
1110
|
* The stored index, rebuilt if it disagrees with the records.
|
|
891
1111
|
*
|
|
@@ -1083,6 +1303,11 @@ function matches(record, needle) {
|
|
|
1083
1303
|
(field) => field?.toLowerCase().includes(needle)
|
|
1084
1304
|
);
|
|
1085
1305
|
}
|
|
1306
|
+
function normalizeActor(id) {
|
|
1307
|
+
const colon = id.indexOf(":");
|
|
1308
|
+
if (colon === -1) return id.toLowerCase();
|
|
1309
|
+
return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
|
|
1310
|
+
}
|
|
1086
1311
|
function digest(contents) {
|
|
1087
1312
|
return (0, import_node_crypto.createHash)("sha256").update(contents).digest("hex");
|
|
1088
1313
|
}
|
|
@@ -2140,23 +2365,123 @@ var noDecisionCommand = define({
|
|
|
2140
2365
|
}
|
|
2141
2366
|
});
|
|
2142
2367
|
|
|
2143
|
-
// src/commands/
|
|
2368
|
+
// src/commands/pack.ts
|
|
2144
2369
|
var import_zod14 = require("zod");
|
|
2370
|
+
var packCommand = define({
|
|
2371
|
+
name: "pack",
|
|
2372
|
+
tool: "kb_pack",
|
|
2373
|
+
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
2374
|
+
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.",
|
|
2375
|
+
input: import_zod14.z.object({
|
|
2376
|
+
bundlePath,
|
|
2377
|
+
conceptId,
|
|
2378
|
+
hops: import_zod14.z.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
|
|
2379
|
+
maxNodes: import_zod14.z.number().int().positive().optional().describe(
|
|
2380
|
+
"How many records the pack may hold, root included. Defaults to 20."
|
|
2381
|
+
),
|
|
2382
|
+
budgetTokens: import_zod14.z.number().int().positive().optional().describe(
|
|
2383
|
+
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
2384
|
+
)
|
|
2385
|
+
}),
|
|
2386
|
+
fromArgv: (argv, path) => {
|
|
2387
|
+
const hops = argvFlag(argv, "--hops");
|
|
2388
|
+
const maxNodes = argvFlag(argv, "--max-nodes");
|
|
2389
|
+
const budget = argvFlag(argv, "--budget");
|
|
2390
|
+
return {
|
|
2391
|
+
bundlePath: path,
|
|
2392
|
+
conceptId: argv[1],
|
|
2393
|
+
...hops ? { hops: Number(hops) } : {},
|
|
2394
|
+
...maxNodes ? { maxNodes: Number(maxNodes) } : {},
|
|
2395
|
+
...budget ? { budgetTokens: Number(budget) } : {}
|
|
2396
|
+
};
|
|
2397
|
+
},
|
|
2398
|
+
run: async ({ store, now }, { bundlePath: path, conceptId: root, hops, maxNodes, budgetTokens }) => {
|
|
2399
|
+
const result = await store.pack(path, root, {
|
|
2400
|
+
...hops !== void 0 ? { hops } : {},
|
|
2401
|
+
...maxNodes !== void 0 ? { maxNodes } : {},
|
|
2402
|
+
...budgetTokens !== void 0 ? { budgetTokens } : {}
|
|
2403
|
+
});
|
|
2404
|
+
return render(result, path, now());
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
function render(result, bundle, at) {
|
|
2408
|
+
const lines = [
|
|
2409
|
+
`# KB Pack \u2014 ${result.root}`,
|
|
2410
|
+
`bundle: ${bundle}`,
|
|
2411
|
+
`budget: ~${result.tokensLoaded} of ${result.budgetTokens} tokens, ${result.recordCount} records`,
|
|
2412
|
+
`packed: ${at}`,
|
|
2413
|
+
"",
|
|
2414
|
+
`## Records (${result.records.length})`
|
|
2415
|
+
];
|
|
2416
|
+
for (const record of result.records) {
|
|
2417
|
+
lines.push(
|
|
2418
|
+
"",
|
|
2419
|
+
`### ${record.conceptId}${record.title ? ` \u2014 ${record.title}` : ""} [${record.standing}]`
|
|
2420
|
+
);
|
|
2421
|
+
if (record.warnings.length) {
|
|
2422
|
+
lines.push(`warnings: ${record.warnings.map(warningLabel).join("; ")}`);
|
|
2423
|
+
}
|
|
2424
|
+
if (record.anchors.length) {
|
|
2425
|
+
lines.push(
|
|
2426
|
+
`anchors: ${record.anchors.map(
|
|
2427
|
+
(anchor) => anchor.symbol ? `${anchor.file}#${anchor.symbol}` : anchor.file
|
|
2428
|
+
).join(", ")}`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
lines.push("", record.body.trimEnd());
|
|
2432
|
+
}
|
|
2433
|
+
if (result.superseded.length) {
|
|
2434
|
+
lines.push("", `## Superseded (${result.superseded.length})`);
|
|
2435
|
+
for (const entry of result.superseded) {
|
|
2436
|
+
lines.push(
|
|
2437
|
+
`- ${entry.conceptId} \u2192 ${entry.supersededBy.join(", ") || "(no surviving head)"}${entry.at ? ` (${entry.at})` : ""}`
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
if (result.excluded.length) {
|
|
2442
|
+
lines.push("", `## Excluded (${result.excluded.length})`);
|
|
2443
|
+
for (const cut of result.excluded) lines.push(`- ${cut}`);
|
|
2444
|
+
}
|
|
2445
|
+
return lines.join("\n");
|
|
2446
|
+
}
|
|
2447
|
+
function warningLabel(warning) {
|
|
2448
|
+
switch (warning.kind) {
|
|
2449
|
+
case "superseded":
|
|
2450
|
+
return `superseded by ${warning.by.join(", ")}`;
|
|
2451
|
+
case "unsettled":
|
|
2452
|
+
return `unsettled (${warning.status})`;
|
|
2453
|
+
case "broken-chain":
|
|
2454
|
+
return `broken chain \u2014 ${warning.missing} is not in the bundle`;
|
|
2455
|
+
case "chain-cycle":
|
|
2456
|
+
return `chain cycle through ${warning.through.join(" \u2192 ")}`;
|
|
2457
|
+
case "forked-chain":
|
|
2458
|
+
return `forked chain \u2014 heads ${warning.heads.join(", ")}`;
|
|
2459
|
+
case "stale":
|
|
2460
|
+
return `stale since ${warning.staleAfter}`;
|
|
2461
|
+
case "unresolved-question":
|
|
2462
|
+
return "unresolved question";
|
|
2463
|
+
default:
|
|
2464
|
+
return warning.kind;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
// src/commands/pin.ts
|
|
2469
|
+
var import_zod15 = require("zod");
|
|
2145
2470
|
var pinCommand = define({
|
|
2146
2471
|
name: "pin",
|
|
2147
2472
|
tool: "kb_pin",
|
|
2148
2473
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
2149
2474
|
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.",
|
|
2150
|
-
input:
|
|
2475
|
+
input: import_zod15.z.object({
|
|
2151
2476
|
bundlePath,
|
|
2152
|
-
mode:
|
|
2477
|
+
mode: import_zod15.z.enum(["full", "index"]).optional().describe(
|
|
2153
2478
|
"full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
|
|
2154
2479
|
),
|
|
2155
|
-
profiles:
|
|
2156
|
-
layer:
|
|
2480
|
+
profiles: import_zod15.z.array(import_zod15.z.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
|
|
2481
|
+
layer: import_zod15.z.enum(["project", "local", "user"]).optional().describe(
|
|
2157
2482
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
2158
2483
|
),
|
|
2159
|
-
frozen:
|
|
2484
|
+
frozen: import_zod15.z.boolean().optional().describe(
|
|
2160
2485
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
2161
2486
|
)
|
|
2162
2487
|
}),
|
|
@@ -2185,29 +2510,29 @@ var pinCommand = define({
|
|
|
2185
2510
|
});
|
|
2186
2511
|
|
|
2187
2512
|
// src/commands/pins.ts
|
|
2188
|
-
var
|
|
2513
|
+
var import_zod16 = require("zod");
|
|
2189
2514
|
var pinsCommand = define({
|
|
2190
2515
|
name: "pins",
|
|
2191
2516
|
tool: "kb_pins",
|
|
2192
2517
|
usage: "pins",
|
|
2193
2518
|
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.",
|
|
2194
|
-
input:
|
|
2519
|
+
input: import_zod16.z.object({}),
|
|
2195
2520
|
fromArgv: () => ({}),
|
|
2196
2521
|
run: ({ store }) => listPins(store, process.cwd())
|
|
2197
2522
|
});
|
|
2198
2523
|
|
|
2199
2524
|
// src/commands/query.ts
|
|
2200
|
-
var
|
|
2525
|
+
var import_zod17 = require("zod");
|
|
2201
2526
|
var queryCommand = define({
|
|
2202
2527
|
name: "query",
|
|
2203
2528
|
tool: "kb_query",
|
|
2204
2529
|
usage: "query <text...>",
|
|
2205
2530
|
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.",
|
|
2206
|
-
input:
|
|
2531
|
+
input: import_zod17.z.object({
|
|
2207
2532
|
bundlePath,
|
|
2208
|
-
text:
|
|
2209
|
-
type:
|
|
2210
|
-
includeNonCurrent:
|
|
2533
|
+
text: import_zod17.z.string().optional(),
|
|
2534
|
+
type: import_zod17.z.enum(KB_RECORD_TYPES).optional(),
|
|
2535
|
+
includeNonCurrent: import_zod17.z.boolean().optional()
|
|
2211
2536
|
}),
|
|
2212
2537
|
fromArgv: (argv, path) => ({
|
|
2213
2538
|
bundlePath: path,
|
|
@@ -2229,40 +2554,40 @@ var queryCommand = define({
|
|
|
2229
2554
|
});
|
|
2230
2555
|
|
|
2231
2556
|
// src/commands/read-index.ts
|
|
2232
|
-
var
|
|
2557
|
+
var import_zod18 = require("zod");
|
|
2233
2558
|
var readIndexCommand = define({
|
|
2234
2559
|
name: "index",
|
|
2235
2560
|
tool: "kb_index",
|
|
2236
2561
|
usage: "index",
|
|
2237
2562
|
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.",
|
|
2238
|
-
input:
|
|
2563
|
+
input: import_zod18.z.object({ bundlePath }),
|
|
2239
2564
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
2240
2565
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
2241
2566
|
});
|
|
2242
2567
|
|
|
2243
2568
|
// src/commands/schema.ts
|
|
2244
|
-
var
|
|
2569
|
+
var import_zod19 = require("zod");
|
|
2245
2570
|
var schemaCommand = define({
|
|
2246
2571
|
name: "schema",
|
|
2247
2572
|
tool: "kb_schema",
|
|
2248
2573
|
usage: "schema",
|
|
2249
2574
|
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.",
|
|
2250
|
-
input:
|
|
2575
|
+
input: import_zod19.z.object({}),
|
|
2251
2576
|
fromArgv: () => ({}),
|
|
2252
2577
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
2253
2578
|
});
|
|
2254
2579
|
|
|
2255
2580
|
// src/commands/status.ts
|
|
2256
|
-
var
|
|
2581
|
+
var import_zod20 = require("zod");
|
|
2257
2582
|
var statusCommand = define({
|
|
2258
2583
|
name: "status",
|
|
2259
2584
|
tool: "kb_status",
|
|
2260
2585
|
usage: "status <concept-id> <status>",
|
|
2261
2586
|
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.",
|
|
2262
|
-
input:
|
|
2587
|
+
input: import_zod20.z.object({
|
|
2263
2588
|
bundlePath,
|
|
2264
2589
|
conceptId,
|
|
2265
|
-
status:
|
|
2590
|
+
status: import_zod20.z.enum(KB_RECORD_STATUSES)
|
|
2266
2591
|
}),
|
|
2267
2592
|
fromArgv: (argv, path) => ({
|
|
2268
2593
|
bundlePath: path,
|
|
@@ -2277,13 +2602,13 @@ var statusCommand = define({
|
|
|
2277
2602
|
});
|
|
2278
2603
|
|
|
2279
2604
|
// src/commands/supersede.ts
|
|
2280
|
-
var
|
|
2605
|
+
var import_zod21 = require("zod");
|
|
2281
2606
|
var supersedeCommand = define({
|
|
2282
2607
|
name: "supersede",
|
|
2283
2608
|
tool: "kb_supersede",
|
|
2284
2609
|
usage: "supersede <concept-id> <replacement-id>",
|
|
2285
2610
|
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.",
|
|
2286
|
-
input:
|
|
2611
|
+
input: import_zod21.z.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
2287
2612
|
fromArgv: (argv, path) => ({
|
|
2288
2613
|
bundlePath: path,
|
|
2289
2614
|
conceptId: argv[1],
|
|
@@ -2297,16 +2622,16 @@ var supersedeCommand = define({
|
|
|
2297
2622
|
});
|
|
2298
2623
|
|
|
2299
2624
|
// src/commands/sync-instructions.ts
|
|
2300
|
-
var
|
|
2625
|
+
var import_zod22 = require("zod");
|
|
2301
2626
|
var syncInstructionsCommand = define({
|
|
2302
2627
|
name: "sync-instructions",
|
|
2303
2628
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
2304
2629
|
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.",
|
|
2305
|
-
input:
|
|
2306
|
-
file:
|
|
2307
|
-
budgetTokens:
|
|
2308
|
-
fullUnderTokens:
|
|
2309
|
-
profile:
|
|
2630
|
+
input: import_zod22.z.object({
|
|
2631
|
+
file: import_zod22.z.string().min(1).describe("The instruction file to edit in place."),
|
|
2632
|
+
budgetTokens: import_zod22.z.number().int().positive().optional(),
|
|
2633
|
+
fullUnderTokens: import_zod22.z.number().int().positive().optional(),
|
|
2634
|
+
profile: import_zod22.z.string().optional()
|
|
2310
2635
|
}),
|
|
2311
2636
|
fromArgv: (argv) => {
|
|
2312
2637
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -2332,17 +2657,17 @@ var syncInstructionsCommand = define({
|
|
|
2332
2657
|
});
|
|
2333
2658
|
|
|
2334
2659
|
// src/commands/trace.ts
|
|
2335
|
-
var
|
|
2660
|
+
var import_zod23 = require("zod");
|
|
2336
2661
|
var traceCommand = define({
|
|
2337
2662
|
name: "trace",
|
|
2338
2663
|
tool: "kb_trace",
|
|
2339
2664
|
usage: "trace <concept-id> [edges...]",
|
|
2340
2665
|
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.',
|
|
2341
|
-
input:
|
|
2666
|
+
input: import_zod23.z.object({
|
|
2342
2667
|
bundlePath,
|
|
2343
2668
|
conceptId,
|
|
2344
|
-
edges:
|
|
2345
|
-
depth:
|
|
2669
|
+
edges: import_zod23.z.array(import_zod23.z.enum(TRACE_EDGES)).optional(),
|
|
2670
|
+
depth: import_zod23.z.number().int().positive().optional()
|
|
2346
2671
|
}),
|
|
2347
2672
|
fromArgv: (argv, path) => ({
|
|
2348
2673
|
bundlePath: path,
|
|
@@ -2364,44 +2689,73 @@ var traceCommand = define({
|
|
|
2364
2689
|
});
|
|
2365
2690
|
|
|
2366
2691
|
// src/commands/types.ts
|
|
2367
|
-
var
|
|
2692
|
+
var import_zod24 = require("zod");
|
|
2368
2693
|
var typesCommand = define({
|
|
2369
2694
|
name: "types",
|
|
2370
2695
|
tool: "kb_types",
|
|
2371
2696
|
usage: "types",
|
|
2372
2697
|
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.",
|
|
2373
|
-
input:
|
|
2698
|
+
input: import_zod24.z.object({}),
|
|
2374
2699
|
fromArgv: () => ({}),
|
|
2375
2700
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
2376
2701
|
});
|
|
2377
2702
|
|
|
2378
2703
|
// src/commands/unpin.ts
|
|
2379
|
-
var
|
|
2704
|
+
var import_zod25 = require("zod");
|
|
2380
2705
|
var unpinCommand = define({
|
|
2381
2706
|
name: "unpin",
|
|
2382
2707
|
tool: "kb_unpin",
|
|
2383
2708
|
usage: "unpin [bundle-path]",
|
|
2384
2709
|
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.",
|
|
2385
|
-
input:
|
|
2710
|
+
input: import_zod25.z.object({ bundlePath }),
|
|
2386
2711
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
2387
2712
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
2388
2713
|
});
|
|
2389
2714
|
|
|
2390
2715
|
// src/commands/validate.ts
|
|
2391
|
-
var
|
|
2716
|
+
var import_zod26 = require("zod");
|
|
2392
2717
|
var validateCommand = define({
|
|
2393
2718
|
name: "validate",
|
|
2394
2719
|
tool: "kb_validate",
|
|
2395
2720
|
usage: "validate",
|
|
2396
2721
|
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.",
|
|
2397
|
-
input:
|
|
2722
|
+
input: import_zod26.z.object({ bundlePath }),
|
|
2398
2723
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
2399
2724
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
2400
2725
|
failsWhen: (result) => Array.isArray(result) && result.length > 0
|
|
2401
2726
|
});
|
|
2402
2727
|
|
|
2728
|
+
// src/commands/verify.ts
|
|
2729
|
+
var import_zod27 = require("zod");
|
|
2730
|
+
var verifyCommand = define({
|
|
2731
|
+
name: "verify",
|
|
2732
|
+
tool: "kb_verify",
|
|
2733
|
+
usage: "verify <concept-id> --note <text>",
|
|
2734
|
+
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.",
|
|
2735
|
+
input: import_zod27.z.object({
|
|
2736
|
+
bundlePath,
|
|
2737
|
+
conceptId,
|
|
2738
|
+
note: import_zod27.z.string().refine((s) => s.trim().length > 0, {
|
|
2739
|
+
message: "note must say what the check found"
|
|
2740
|
+
})
|
|
2741
|
+
}),
|
|
2742
|
+
fromArgv: (argv, path) => ({
|
|
2743
|
+
bundlePath: path,
|
|
2744
|
+
conceptId: argv[1],
|
|
2745
|
+
note: argvFlag(argv, "--note")
|
|
2746
|
+
}),
|
|
2747
|
+
run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, note }) => {
|
|
2748
|
+
await assertBaseNotFrozen(process.cwd(), path);
|
|
2749
|
+
const record = await store.verify(path, id, note, actor, now());
|
|
2750
|
+
return {
|
|
2751
|
+
conceptId: record.conceptId,
|
|
2752
|
+
verified: record.frontmatter.verified?.length ?? 0
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
|
|
2403
2757
|
// src/commands/write.ts
|
|
2404
|
-
var
|
|
2758
|
+
var import_zod28 = require("zod");
|
|
2405
2759
|
var writeCommand = define({
|
|
2406
2760
|
name: "write",
|
|
2407
2761
|
tool: "kb_write",
|
|
@@ -2415,9 +2769,9 @@ var writeCommand = define({
|
|
|
2415
2769
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
2416
2770
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
2417
2771
|
].join("\n"),
|
|
2418
|
-
input:
|
|
2772
|
+
input: import_zod28.z.object({
|
|
2419
2773
|
bundlePath,
|
|
2420
|
-
type:
|
|
2774
|
+
type: import_zod28.z.enum(KB_RECORD_TYPES),
|
|
2421
2775
|
input: composeInputSchema
|
|
2422
2776
|
}),
|
|
2423
2777
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -2441,7 +2795,7 @@ var writeCommand = define({
|
|
|
2441
2795
|
});
|
|
2442
2796
|
|
|
2443
2797
|
// src/commands/write-decision.ts
|
|
2444
|
-
var
|
|
2798
|
+
var import_zod29 = require("zod");
|
|
2445
2799
|
var writeDecisionCommand = define({
|
|
2446
2800
|
name: "write-decision",
|
|
2447
2801
|
tool: "kb_write_decision",
|
|
@@ -2454,7 +2808,7 @@ var writeDecisionCommand = define({
|
|
|
2454
2808
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
2455
2809
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
2456
2810
|
].join("\n"),
|
|
2457
|
-
input:
|
|
2811
|
+
input: import_zod29.z.object({ bundlePath, input: decisionInputSchema }),
|
|
2458
2812
|
fromArgv: async (_argv, path, stdin) => ({
|
|
2459
2813
|
bundlePath: path,
|
|
2460
2814
|
input: JSON.parse(await stdin())
|
|
@@ -2482,7 +2836,9 @@ var KB_COMMANDS = [
|
|
|
2482
2836
|
statusCommand,
|
|
2483
2837
|
supersedeCommand,
|
|
2484
2838
|
answerCommand,
|
|
2839
|
+
verifyCommand,
|
|
2485
2840
|
loadCommand,
|
|
2841
|
+
packCommand,
|
|
2486
2842
|
queryCommand,
|
|
2487
2843
|
traceCommand,
|
|
2488
2844
|
listCommand,
|
|
@@ -2504,8 +2860,13 @@ var KB_COMMANDS_BY_NAME = new Map(
|
|
|
2504
2860
|
// src/mcp.ts
|
|
2505
2861
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
2506
2862
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
2863
|
+
|
|
2864
|
+
// src/version.ts
|
|
2865
|
+
var VERSION = true ? "0.1.7" : "0.0.0-dev";
|
|
2866
|
+
|
|
2867
|
+
// src/mcp.ts
|
|
2507
2868
|
function createKbMcpServer() {
|
|
2508
|
-
const server = new import_mcp.McpServer({ name: "strauss-kb", version:
|
|
2869
|
+
const server = new import_mcp.McpServer({ name: "strauss-kb", version: VERSION });
|
|
2509
2870
|
const store = new KbStore({
|
|
2510
2871
|
warn: (entry) => process.stderr.write(`${JSON.stringify(entry)}
|
|
2511
2872
|
`)
|
|
@@ -2548,6 +2909,11 @@ async function runKbCli(argv) {
|
|
|
2548
2909
|
process.stdout.write(usage());
|
|
2549
2910
|
return;
|
|
2550
2911
|
}
|
|
2912
|
+
if (name === "--version" || name === "-v") {
|
|
2913
|
+
process.stdout.write(`${VERSION}
|
|
2914
|
+
`);
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2551
2917
|
const command = KB_COMMANDS_BY_NAME.get(name);
|
|
2552
2918
|
if (!command) die(`unknown command ${name}`);
|
|
2553
2919
|
const raw = await command.fromArgv(rest, bundle, readStdin);
|
|
@@ -2617,6 +2983,7 @@ function usage() {
|
|
|
2617
2983
|
),
|
|
2618
2984
|
"",
|
|
2619
2985
|
` --bundle PATH defaults to ./${KB_DIR}`,
|
|
2986
|
+
" --version the installed package version",
|
|
2620
2987
|
" STRAUSS_KB_ACTOR names the writer in the log",
|
|
2621
2988
|
""
|
|
2622
2989
|
].join("\n");
|
|
@@ -2628,6 +2995,9 @@ function usage() {
|
|
|
2628
2995
|
CONTEXT_END,
|
|
2629
2996
|
CONTEXT_PROFILES,
|
|
2630
2997
|
DECISION_TYPE,
|
|
2998
|
+
DEFAULT_LOAD_BUDGET,
|
|
2999
|
+
DEFAULT_PACK_HOPS,
|
|
3000
|
+
DEFAULT_PACK_MAX_NODES,
|
|
2631
3001
|
ErrorTypes,
|
|
2632
3002
|
Fault,
|
|
2633
3003
|
INDEX_FILE,
|
|
@@ -2636,15 +3006,18 @@ function usage() {
|
|
|
2636
3006
|
KB_CONCEPT_ID_PATTERN,
|
|
2637
3007
|
KB_CONFIDENCES,
|
|
2638
3008
|
KB_DIR,
|
|
3009
|
+
KB_EDGE_KINDS,
|
|
2639
3010
|
KB_MATERIALITIES,
|
|
2640
3011
|
KB_RECORD_STATUSES,
|
|
2641
3012
|
KB_RECORD_TYPES,
|
|
2642
3013
|
KB_SLUG_PATTERN,
|
|
2643
3014
|
KbBaseFrozenError,
|
|
2644
3015
|
KbInvalidConceptIdError,
|
|
3016
|
+
KbPackBudgetExceededError,
|
|
2645
3017
|
KbPinsMalformedError,
|
|
2646
3018
|
KbRecordAlreadyExistsError,
|
|
2647
3019
|
KbRecordNotFoundError,
|
|
3020
|
+
KbSelfVerificationError,
|
|
2648
3021
|
KbStore,
|
|
2649
3022
|
KbWriteConflictError,
|
|
2650
3023
|
LOG_FILE,
|
|
@@ -2665,6 +3038,7 @@ function usage() {
|
|
|
2665
3038
|
contextProfileBudgets,
|
|
2666
3039
|
createKbMcpServer,
|
|
2667
3040
|
decisionInputSchema,
|
|
3041
|
+
edgeNeighbours,
|
|
2668
3042
|
indexIsStale,
|
|
2669
3043
|
isKbRecordType,
|
|
2670
3044
|
isNoDecisionRecord,
|
|
@@ -2675,10 +3049,13 @@ function usage() {
|
|
|
2675
3049
|
kbLogEntrySchema,
|
|
2676
3050
|
kbRecordFrontmatterSchema,
|
|
2677
3051
|
kbSourceSchema,
|
|
3052
|
+
kbVerifiedEventSchema,
|
|
2678
3053
|
listPins,
|
|
2679
3054
|
loadQmd,
|
|
2680
3055
|
matchToDiff,
|
|
2681
3056
|
mergedContextBudgets,
|
|
3057
|
+
neighbours,
|
|
3058
|
+
pack,
|
|
2682
3059
|
parseLog,
|
|
2683
3060
|
parseMarkdownWithFrontmatter,
|
|
2684
3061
|
pinBase,
|