@saasontools/strauss-kb 0.1.4 → 0.1.6
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 +42 -3
- package/dist/{chunk-TS26G7TL.js → chunk-BVF7X5VO.js} +2 -2
- package/dist/{chunk-EDH43Z7J.js → chunk-PNSRTKYN.js} +123 -16
- package/dist/chunk-PNSRTKYN.js.map +1 -0
- package/dist/{chunk-Y5C7Z2HG.js → chunk-V7TRZ2ER.js} +2 -2
- package/dist/cli-main.cjs +119 -15
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +124 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -3
- package/dist/index.d.ts +41 -3
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +119 -15
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-EDH43Z7J.js.map +0 -1
- /package/dist/{chunk-TS26G7TL.js.map → chunk-BVF7X5VO.js.map} +0 -0
- /package/dist/{chunk-Y5C7Z2HG.js.map → chunk-V7TRZ2ER.js.map} +0 -0
package/dist/cli-main.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(index_exports, {
|
|
|
52
52
|
KbPinsMalformedError: () => KbPinsMalformedError,
|
|
53
53
|
KbRecordAlreadyExistsError: () => KbRecordAlreadyExistsError,
|
|
54
54
|
KbRecordNotFoundError: () => KbRecordNotFoundError,
|
|
55
|
+
KbSelfVerificationError: () => KbSelfVerificationError,
|
|
55
56
|
KbStore: () => KbStore,
|
|
56
57
|
KbWriteConflictError: () => KbWriteConflictError,
|
|
57
58
|
LOG_FILE: () => LOG_FILE,
|
|
@@ -82,6 +83,7 @@ __export(index_exports, {
|
|
|
82
83
|
kbLogEntrySchema: () => kbLogEntrySchema,
|
|
83
84
|
kbRecordFrontmatterSchema: () => kbRecordFrontmatterSchema,
|
|
84
85
|
kbSourceSchema: () => kbSourceSchema,
|
|
86
|
+
kbVerifiedEventSchema: () => kbVerifiedEventSchema,
|
|
85
87
|
listPins: () => listPins,
|
|
86
88
|
loadQmd: () => loadQmd,
|
|
87
89
|
matchToDiff: () => matchToDiff,
|
|
@@ -154,6 +156,11 @@ var kbActorStampSchema = import_zod.z.object({
|
|
|
154
156
|
by: import_zod.z.string().min(1),
|
|
155
157
|
at: import_zod.z.string().min(1)
|
|
156
158
|
}).passthrough();
|
|
159
|
+
var kbVerifiedEventSchema = kbActorStampSchema.extend({
|
|
160
|
+
note: import_zod.z.string().refine((s) => s.trim().length > 0, {
|
|
161
|
+
message: "note must say what the check found"
|
|
162
|
+
})
|
|
163
|
+
});
|
|
157
164
|
var kbAnchorSchema = import_zod.z.object({
|
|
158
165
|
file: import_zod.z.string().min(1),
|
|
159
166
|
symbol: import_zod.z.string().min(1).optional()
|
|
@@ -238,6 +245,7 @@ var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
|
238
245
|
ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
|
|
239
246
|
ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
|
|
240
247
|
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
248
|
+
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
241
249
|
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
242
250
|
return ErrorTypes2;
|
|
243
251
|
})(ErrorTypes || {});
|
|
@@ -306,6 +314,25 @@ var KbWriteConflictError = class extends BaseError {
|
|
|
306
314
|
}
|
|
307
315
|
conceptId;
|
|
308
316
|
};
|
|
317
|
+
var KbSelfVerificationError = class extends BaseError {
|
|
318
|
+
constructor(conceptId2, actor, generatedBy) {
|
|
319
|
+
super({
|
|
320
|
+
message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
|
|
321
|
+
errorType: "KbSelfVerification" /* KbSelfVerification */,
|
|
322
|
+
code: 400,
|
|
323
|
+
fault: "User" /* User */,
|
|
324
|
+
retriable: false,
|
|
325
|
+
reportToUser: true,
|
|
326
|
+
details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
|
|
327
|
+
});
|
|
328
|
+
this.conceptId = conceptId2;
|
|
329
|
+
this.actor = actor;
|
|
330
|
+
this.generatedBy = generatedBy;
|
|
331
|
+
}
|
|
332
|
+
conceptId;
|
|
333
|
+
actor;
|
|
334
|
+
generatedBy;
|
|
335
|
+
};
|
|
309
336
|
var KbInvalidConceptIdError = class extends BaseError {
|
|
310
337
|
constructor(message, details) {
|
|
311
338
|
super({
|
|
@@ -749,6 +776,40 @@ var KbStore = class {
|
|
|
749
776
|
{ operation: `status:${status}`, by: actor }
|
|
750
777
|
);
|
|
751
778
|
}
|
|
779
|
+
/**
|
|
780
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
781
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
782
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
783
|
+
*
|
|
784
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
785
|
+
* human: the generator re-reading its own output is not an independent
|
|
786
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
787
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
788
|
+
* logs what it publishes.
|
|
789
|
+
*/
|
|
790
|
+
async verify(bundlePath2, conceptId2, note, actor = "unknown", at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
791
|
+
const event = kbVerifiedEventSchema.parse({ by: actor, at, note });
|
|
792
|
+
const existing = await this.read(bundlePath2, conceptId2);
|
|
793
|
+
if (!existing) throw new KbRecordNotFoundError(conceptId2);
|
|
794
|
+
const generatedBy = existing.frontmatter.generated?.by;
|
|
795
|
+
if (generatedBy !== void 0 && actor.toLowerCase() === generatedBy.toLowerCase() && !normalizeActor(actor).startsWith("human:")) {
|
|
796
|
+
await this.record(this.root(bundlePath2), {
|
|
797
|
+
operation: "verify:refused",
|
|
798
|
+
conceptId: conceptId2,
|
|
799
|
+
by: actor
|
|
800
|
+
});
|
|
801
|
+
throw new KbSelfVerificationError(conceptId2, actor, generatedBy);
|
|
802
|
+
}
|
|
803
|
+
return this.mutate(
|
|
804
|
+
bundlePath2,
|
|
805
|
+
conceptId2,
|
|
806
|
+
(frontmatter) => ({
|
|
807
|
+
...frontmatter,
|
|
808
|
+
verified: [...frontmatter.verified ?? [], event]
|
|
809
|
+
}),
|
|
810
|
+
{ operation: "verify", by: actor }
|
|
811
|
+
);
|
|
812
|
+
}
|
|
752
813
|
/**
|
|
753
814
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
754
815
|
*
|
|
@@ -852,6 +913,10 @@ ${answer}
|
|
|
852
913
|
* Refuses rather than truncates when the base is too large. A truncated base
|
|
853
914
|
* is indistinguishable from a complete one, so a caller would answer "that
|
|
854
915
|
* was never decided" from a slice it did not know was a slice.
|
|
916
|
+
*
|
|
917
|
+
* That refusal is the default guardrail. `all` bypasses it outright and
|
|
918
|
+
* always hands back the whole bundle: an explicit, never-accidental escape
|
|
919
|
+
* hatch for an operator who has the budget to spend, not a wider default.
|
|
855
920
|
*/
|
|
856
921
|
async load(bundlePath2, options = {}) {
|
|
857
922
|
const budgetTokens = options.budgetTokens ?? DEFAULT_LOAD_BUDGET;
|
|
@@ -861,7 +926,7 @@ ${answer}
|
|
|
861
926
|
const records = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
862
927
|
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
863
928
|
const approxTokens2 = records.reduce((total, hit) => total + estimateTokens(hit.record), 0) + superseded.reduce((total, entry) => total + estimateStubTokens(entry), 0);
|
|
864
|
-
if (approxTokens2 > budgetTokens) {
|
|
929
|
+
if (!options.all && approxTokens2 > budgetTokens) {
|
|
865
930
|
return {
|
|
866
931
|
loaded: false,
|
|
867
932
|
recordCount: wanted.length,
|
|
@@ -872,8 +937,8 @@ ${answer}
|
|
|
872
937
|
return {
|
|
873
938
|
loaded: true,
|
|
874
939
|
recordCount: wanted.length,
|
|
875
|
-
|
|
876
|
-
budgetTokens,
|
|
940
|
+
tokensLoaded: approxTokens2,
|
|
941
|
+
budgetTokens: options.all ? null : budgetTokens,
|
|
877
942
|
records,
|
|
878
943
|
superseded
|
|
879
944
|
};
|
|
@@ -1079,6 +1144,11 @@ function matches(record, needle) {
|
|
|
1079
1144
|
(field) => field?.toLowerCase().includes(needle)
|
|
1080
1145
|
);
|
|
1081
1146
|
}
|
|
1147
|
+
function normalizeActor(id) {
|
|
1148
|
+
const colon = id.indexOf(":");
|
|
1149
|
+
if (colon === -1) return id.toLowerCase();
|
|
1150
|
+
return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
|
|
1151
|
+
}
|
|
1082
1152
|
function digest(contents) {
|
|
1083
1153
|
return (0, import_node_crypto.createHash)("sha256").update(contents).digest("hex");
|
|
1084
1154
|
}
|
|
@@ -2058,25 +2128,32 @@ var import_zod11 = require("zod");
|
|
|
2058
2128
|
var loadCommand = define({
|
|
2059
2129
|
name: "load",
|
|
2060
2130
|
tool: "kb_load",
|
|
2061
|
-
usage: "load [type] [--budget N]",
|
|
2062
|
-
description: "Load the whole knowledge base at once, each record with its standing. Prefer this over searching: these bases run to a few thousand tokens, and a reader holding all of it has perfect recall and knows why it is asking, which no ranker does. Superseded records arrive under `superseded` as name, replacement and date only \u2014 their bodies no longer hold, and reading one later in a long session is the mistake this prevents; pass the id to kb_trace when you need the history. Rejected and unresolved records arrive whole: what was turned down, and what is still open, is the part a diff cannot show you. Refuses with a count rather than truncating when the base is too large \u2014 a truncated base is indistinguishable from a complete one, and would have you conclude something was never decided from a slice you did not know was a slice. Call at the point of use, not once per session: a base loaded early is summarised away by compaction, so if the visible context holds no records from this base and the question at hand is one it might govern, load before answering \u2014 never conclude nothing was decided from a context with no KB content in it. This tool (with 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.",
|
|
2131
|
+
usage: "load [type] [--budget N | --all]",
|
|
2132
|
+
description: "Load the whole knowledge base at once, each record with its standing. Prefer this over searching: these bases run to a few thousand tokens, and a reader holding all of it has perfect recall and knows why it is asking, which no ranker does. Superseded records arrive under `superseded` as name, replacement and date only \u2014 their bodies no longer hold, and reading one later in a long session is the mistake this prevents; pass the id to kb_trace when you need the history. Rejected and unresolved records arrive whole: what was turned down, and what is still open, is the part a diff cannot show you. Refuses with a count rather than truncating when the base is too large \u2014 a truncated base is indistinguishable from a complete one, and would have you conclude something was never decided from a slice you did not know was a slice. Call at the point of use, not once per session: a base loaded early is summarised away by compaction, so if the visible context holds no records from this base and the question at hand is one it might govern, load before answering \u2014 never conclude nothing was decided from a context with no KB content in it. This tool (with 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.\n\nThat refusal is the default guardrail, meant for an agent that would otherwise burn its whole context on one call. `all` bypasses it and loads everything regardless of size: a deliberate operator with the budget to spend, not something to reach for automatically. It is mutually exclusive with `budgetTokens`. When the reader does not need everything, kb_query or a narrower `type` filter is the better fit than either.",
|
|
2063
2133
|
input: import_zod11.z.object({
|
|
2064
2134
|
bundlePath,
|
|
2065
2135
|
type: import_zod11.z.enum(KB_RECORD_TYPES).optional(),
|
|
2066
|
-
budgetTokens: import_zod11.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000.")
|
|
2136
|
+
budgetTokens: import_zod11.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
|
|
2137
|
+
all: import_zod11.z.boolean().optional().describe(
|
|
2138
|
+
"Load the entire base regardless of size. The deliberate-operator escape hatch; mutually exclusive with budgetTokens."
|
|
2139
|
+
)
|
|
2140
|
+
}).refine((value) => !(value.all && value.budgetTokens !== void 0), {
|
|
2141
|
+
message: "all and budgetTokens are mutually exclusive: pass a ceiling or none, not both."
|
|
2067
2142
|
}),
|
|
2068
2143
|
fromArgv: (argv, path) => {
|
|
2069
2144
|
const budget = argvFlag(argv, "--budget");
|
|
2070
2145
|
return {
|
|
2071
2146
|
bundlePath: path,
|
|
2072
|
-
...argv[1] && argv[1]
|
|
2073
|
-
...budget ? { budgetTokens: Number(budget) } : {}
|
|
2147
|
+
...argv[1] && !argv[1].startsWith("--") ? { type: argv[1] } : {},
|
|
2148
|
+
...budget ? { budgetTokens: Number(budget) } : {},
|
|
2149
|
+
...argv.includes("--all") ? { all: true } : {}
|
|
2074
2150
|
};
|
|
2075
2151
|
},
|
|
2076
|
-
run: async ({ store }, { bundlePath: path, type, budgetTokens }) => {
|
|
2152
|
+
run: async ({ store }, { bundlePath: path, type, budgetTokens, all }) => {
|
|
2077
2153
|
const result = await store.load(path, {
|
|
2078
2154
|
...type ? { type } : {},
|
|
2079
|
-
...budgetTokens ? { budgetTokens } : {}
|
|
2155
|
+
...budgetTokens ? { budgetTokens } : {},
|
|
2156
|
+
...all ? { all } : {}
|
|
2080
2157
|
});
|
|
2081
2158
|
if (!result.loaded) return result;
|
|
2082
2159
|
return {
|
|
@@ -2389,8 +2466,37 @@ var validateCommand = define({
|
|
|
2389
2466
|
failsWhen: (result) => Array.isArray(result) && result.length > 0
|
|
2390
2467
|
});
|
|
2391
2468
|
|
|
2392
|
-
// src/commands/
|
|
2469
|
+
// src/commands/verify.ts
|
|
2393
2470
|
var import_zod26 = require("zod");
|
|
2471
|
+
var verifyCommand = define({
|
|
2472
|
+
name: "verify",
|
|
2473
|
+
tool: "kb_verify",
|
|
2474
|
+
usage: "verify <concept-id> --note <text>",
|
|
2475
|
+
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.",
|
|
2476
|
+
input: import_zod26.z.object({
|
|
2477
|
+
bundlePath,
|
|
2478
|
+
conceptId,
|
|
2479
|
+
note: import_zod26.z.string().refine((s) => s.trim().length > 0, {
|
|
2480
|
+
message: "note must say what the check found"
|
|
2481
|
+
})
|
|
2482
|
+
}),
|
|
2483
|
+
fromArgv: (argv, path) => ({
|
|
2484
|
+
bundlePath: path,
|
|
2485
|
+
conceptId: argv[1],
|
|
2486
|
+
note: argvFlag(argv, "--note")
|
|
2487
|
+
}),
|
|
2488
|
+
run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, note }) => {
|
|
2489
|
+
await assertBaseNotFrozen(process.cwd(), path);
|
|
2490
|
+
const record = await store.verify(path, id, note, actor, now());
|
|
2491
|
+
return {
|
|
2492
|
+
conceptId: record.conceptId,
|
|
2493
|
+
verified: record.frontmatter.verified?.length ?? 0
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
|
|
2498
|
+
// src/commands/write.ts
|
|
2499
|
+
var import_zod27 = require("zod");
|
|
2394
2500
|
var writeCommand = define({
|
|
2395
2501
|
name: "write",
|
|
2396
2502
|
tool: "kb_write",
|
|
@@ -2404,9 +2510,9 @@ var writeCommand = define({
|
|
|
2404
2510
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
2405
2511
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
2406
2512
|
].join("\n"),
|
|
2407
|
-
input:
|
|
2513
|
+
input: import_zod27.z.object({
|
|
2408
2514
|
bundlePath,
|
|
2409
|
-
type:
|
|
2515
|
+
type: import_zod27.z.enum(KB_RECORD_TYPES),
|
|
2410
2516
|
input: composeInputSchema
|
|
2411
2517
|
}),
|
|
2412
2518
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -2430,7 +2536,7 @@ var writeCommand = define({
|
|
|
2430
2536
|
});
|
|
2431
2537
|
|
|
2432
2538
|
// src/commands/write-decision.ts
|
|
2433
|
-
var
|
|
2539
|
+
var import_zod28 = require("zod");
|
|
2434
2540
|
var writeDecisionCommand = define({
|
|
2435
2541
|
name: "write-decision",
|
|
2436
2542
|
tool: "kb_write_decision",
|
|
@@ -2443,7 +2549,7 @@ var writeDecisionCommand = define({
|
|
|
2443
2549
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
2444
2550
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
2445
2551
|
].join("\n"),
|
|
2446
|
-
input:
|
|
2552
|
+
input: import_zod28.z.object({ bundlePath, input: decisionInputSchema }),
|
|
2447
2553
|
fromArgv: async (_argv, path, stdin) => ({
|
|
2448
2554
|
bundlePath: path,
|
|
2449
2555
|
input: JSON.parse(await stdin())
|
|
@@ -2471,6 +2577,7 @@ var KB_COMMANDS = [
|
|
|
2471
2577
|
statusCommand,
|
|
2472
2578
|
supersedeCommand,
|
|
2473
2579
|
answerCommand,
|
|
2580
|
+
verifyCommand,
|
|
2474
2581
|
loadCommand,
|
|
2475
2582
|
queryCommand,
|
|
2476
2583
|
traceCommand,
|
|
@@ -2634,6 +2741,7 @@ function usage() {
|
|
|
2634
2741
|
KbPinsMalformedError,
|
|
2635
2742
|
KbRecordAlreadyExistsError,
|
|
2636
2743
|
KbRecordNotFoundError,
|
|
2744
|
+
KbSelfVerificationError,
|
|
2637
2745
|
KbStore,
|
|
2638
2746
|
KbWriteConflictError,
|
|
2639
2747
|
LOG_FILE,
|
|
@@ -2664,6 +2772,7 @@ function usage() {
|
|
|
2664
2772
|
kbLogEntrySchema,
|
|
2665
2773
|
kbRecordFrontmatterSchema,
|
|
2666
2774
|
kbSourceSchema,
|
|
2775
|
+
kbVerifiedEventSchema,
|
|
2667
2776
|
listPins,
|
|
2668
2777
|
loadQmd,
|
|
2669
2778
|
matchToDiff,
|