@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/index.d.cts
CHANGED
|
@@ -31,6 +31,17 @@ declare const kbActorStampSchema: z.ZodObject<{
|
|
|
31
31
|
by: z.ZodString;
|
|
32
32
|
at: z.ZodString;
|
|
33
33
|
}, z.core.$loose>;
|
|
34
|
+
/**
|
|
35
|
+
* A `verified[]` event as this package writes one: the actor stamp plus a
|
|
36
|
+
* required note saying what the check found. Write-side only — the frontmatter
|
|
37
|
+
* keeps reading `verified` with `kbActorStampSchema`, because OKF-native
|
|
38
|
+
* entries carry no note and a consumer must not reject conformant records.
|
|
39
|
+
*/
|
|
40
|
+
declare const kbVerifiedEventSchema: z.ZodObject<{
|
|
41
|
+
by: z.ZodString;
|
|
42
|
+
at: z.ZodString;
|
|
43
|
+
note: z.ZodString;
|
|
44
|
+
}, z.core.$loose>;
|
|
34
45
|
/**
|
|
35
46
|
* Where a record attaches in the code.
|
|
36
47
|
*
|
|
@@ -124,6 +135,7 @@ declare const kbRecordFrontmatterSchema: z.ZodObject<{
|
|
|
124
135
|
}, z.core.$loose>;
|
|
125
136
|
type KbSource = z.infer<typeof kbSourceSchema>;
|
|
126
137
|
type KbActorStamp = z.infer<typeof kbActorStampSchema>;
|
|
138
|
+
type KbVerifiedEvent = z.infer<typeof kbVerifiedEventSchema>;
|
|
127
139
|
type KbAnchor = z.infer<typeof kbAnchorSchema>;
|
|
128
140
|
type KbRecordFrontmatter = z.infer<typeof kbRecordFrontmatterSchema>;
|
|
129
141
|
type KbRecord = {
|
|
@@ -318,8 +330,9 @@ type KbLoadResult = {
|
|
|
318
330
|
/** Named only. Their bodies are reachable through `trace`. */
|
|
319
331
|
superseded: KbSupersededStub[];
|
|
320
332
|
recordCount: number;
|
|
321
|
-
|
|
322
|
-
|
|
333
|
+
tokensLoaded: number;
|
|
334
|
+
/** `null` when loaded via `all`: no ceiling was applied. */
|
|
335
|
+
budgetTokens: number | null;
|
|
323
336
|
} | {
|
|
324
337
|
loaded: false;
|
|
325
338
|
recordCount: number;
|
|
@@ -385,6 +398,18 @@ declare class KbStore {
|
|
|
385
398
|
* timeouts.
|
|
386
399
|
*/
|
|
387
400
|
setStatus(bundlePath: string, conceptId: string, status: KbRecordStatus, actor?: string): Promise<KbRecord>;
|
|
401
|
+
/**
|
|
402
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
403
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
404
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
405
|
+
*
|
|
406
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
407
|
+
* human: the generator re-reading its own output is not an independent
|
|
408
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
409
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
410
|
+
* logs what it publishes.
|
|
411
|
+
*/
|
|
412
|
+
verify(bundlePath: string, conceptId: string, note: string, actor?: string, at?: string): Promise<KbRecord>;
|
|
388
413
|
/**
|
|
389
414
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
390
415
|
*
|
|
@@ -430,10 +455,15 @@ declare class KbStore {
|
|
|
430
455
|
* Refuses rather than truncates when the base is too large. A truncated base
|
|
431
456
|
* is indistinguishable from a complete one, so a caller would answer "that
|
|
432
457
|
* was never decided" from a slice it did not know was a slice.
|
|
458
|
+
*
|
|
459
|
+
* That refusal is the default guardrail. `all` bypasses it outright and
|
|
460
|
+
* always hands back the whole bundle: an explicit, never-accidental escape
|
|
461
|
+
* hatch for an operator who has the budget to spend, not a wider default.
|
|
433
462
|
*/
|
|
434
463
|
load(bundlePath: string, options?: {
|
|
435
464
|
budgetTokens?: number;
|
|
436
465
|
type?: string;
|
|
466
|
+
all?: boolean;
|
|
437
467
|
}): Promise<KbLoadResult>;
|
|
438
468
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
439
469
|
trace(bundlePath: string, seedId: string, options?: KbTraceOptions): Promise<KbTraceStep[]>;
|
|
@@ -516,6 +546,7 @@ declare enum ErrorTypes {
|
|
|
516
546
|
KbRecordAlreadyExists = "KbRecordAlreadyExists",
|
|
517
547
|
KbInvalidConceptId = "KbInvalidConceptId",
|
|
518
548
|
KbRecordNotFound = "KbRecordNotFound",
|
|
549
|
+
KbSelfVerification = "KbSelfVerification",
|
|
519
550
|
KbWriteConflict = "KbWriteConflict"
|
|
520
551
|
}
|
|
521
552
|
type ErrorDetails = Record<string, string | boolean | number | string[] | boolean[] | number[]>;
|
|
@@ -558,6 +589,13 @@ declare class KbWriteConflictError extends BaseError {
|
|
|
558
589
|
readonly conceptId: string;
|
|
559
590
|
constructor(conceptId: string);
|
|
560
591
|
}
|
|
592
|
+
/** A generator confirming its own record adds no independent check. */
|
|
593
|
+
declare class KbSelfVerificationError extends BaseError {
|
|
594
|
+
readonly conceptId: string;
|
|
595
|
+
readonly actor: string;
|
|
596
|
+
readonly generatedBy: string;
|
|
597
|
+
constructor(conceptId: string, actor: string, generatedBy: string);
|
|
598
|
+
}
|
|
561
599
|
declare class KbInvalidConceptIdError extends BaseError {
|
|
562
600
|
constructor(message: string, details: Record<string, string>);
|
|
563
601
|
}
|
|
@@ -1259,4 +1297,4 @@ declare function parseMarkdownWithFrontmatter<S extends z.ZodType>(text: string,
|
|
|
1259
1297
|
frontmatter: ReturnType<S["safeParse"]>;
|
|
1260
1298
|
};
|
|
1261
1299
|
|
|
1262
|
-
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, type DecisionInput, type DiffFile, type DiffHunk, type DiffMatch, type ErrorDetails, type ErrorProps, ErrorTypes, Fault, INDEX_FILE, KB_COMMANDS, KB_COMMANDS_BY_NAME, KB_CONCEPT_ID_PATTERN, KB_CONFIDENCES, KB_DIR, KB_MATERIALITIES, KB_RECORD_STATUSES, KB_RECORD_TYPES, KB_SLUG_PATTERN, type KbActorStamp, type KbAdjudicated, type KbAnchor, KbBaseFrozenError, type KbCommand, type KbCommandContext, type KbContextBudgets, type KbContextOptions, type KbContextResult, KbInvalidConceptIdError, type KbLoadResult, type KbLogEntry, type KbLogReadResult, type KbLogger, type KbMergedPin, type KbMergedPins, type KbPin, type KbPinLayer, type KbPinOptions, type KbPinResult, type KbPinStatus, KbPinsMalformedError, type KbPinsManifest, type KbRecord, KbRecordAlreadyExistsError, type KbRecordFrontmatter, KbRecordNotFoundError, type KbRecordStatus, type KbRecordType, type KbRecordTypeSpec, type KbSearchLogger, type KbSource, type KbStanding, KbStore, type KbSupersededStub, type KbSyncResult, type KbTraceEdge, type KbTraceOptions, type KbTraceStep, type KbValidationProblem, type KbWarning, KbWriteConflictError, type KbWriteInput, LOG_FILE, type MatchOptions, NO_DECISION_SLUG, PINS_FILE, PINS_LOCAL_FILE, PIN_LAYERS, type QmdModule, RECORD_TYPES, SEARCH_INDEX_FILE, type SearchHit, type SearchOptions, type SymbolRange, TRACE_EDGES, adjudicate, assertBaseNotFrozen, buildContext, composeDecisionRecord, composeInputSchema, composeNoDecisionRecord, composeRecord, contextProfileBudgets, createKbMcpServer, decisionInputSchema, indexIsStale, isKbRecordType, isNoDecisionRecord, kbActorStampSchema, kbAnchorSchema, kbConceptIdSchema, kbJsonSchemas, kbLogEntrySchema, kbRecordFrontmatterSchema, kbSourceSchema, listPins, loadQmd, matchToDiff, mergedContextBudgets, parseLog, parseMarkdownWithFrontmatter, pinBase, readMergedPins, readPinsLayer, renderIndex, renderIndexLine, renderLogEntry, resolveHeads, resolveHits, resolvePinPath, runKbCli, runKbMcpServer, searchBase, selectDecisions, splitMarkdownFrontmatter, stringifyMarkdownWithFrontmatter, syncInstructions, toHookJson, trace, unpinBase, validateBundle };
|
|
1300
|
+
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, type DecisionInput, type DiffFile, type DiffHunk, type DiffMatch, type ErrorDetails, type ErrorProps, ErrorTypes, Fault, INDEX_FILE, KB_COMMANDS, KB_COMMANDS_BY_NAME, KB_CONCEPT_ID_PATTERN, KB_CONFIDENCES, KB_DIR, KB_MATERIALITIES, KB_RECORD_STATUSES, KB_RECORD_TYPES, KB_SLUG_PATTERN, type KbActorStamp, type KbAdjudicated, type KbAnchor, KbBaseFrozenError, type KbCommand, type KbCommandContext, type KbContextBudgets, type KbContextOptions, type KbContextResult, KbInvalidConceptIdError, type KbLoadResult, type KbLogEntry, type KbLogReadResult, type KbLogger, type KbMergedPin, type KbMergedPins, type KbPin, type KbPinLayer, type KbPinOptions, type KbPinResult, type KbPinStatus, KbPinsMalformedError, type KbPinsManifest, type KbRecord, KbRecordAlreadyExistsError, type KbRecordFrontmatter, KbRecordNotFoundError, type KbRecordStatus, type KbRecordType, type KbRecordTypeSpec, type KbSearchLogger, KbSelfVerificationError, type KbSource, type KbStanding, KbStore, type KbSupersededStub, type KbSyncResult, type KbTraceEdge, type KbTraceOptions, type KbTraceStep, type KbValidationProblem, type KbVerifiedEvent, type KbWarning, KbWriteConflictError, type KbWriteInput, LOG_FILE, type MatchOptions, NO_DECISION_SLUG, PINS_FILE, PINS_LOCAL_FILE, PIN_LAYERS, type QmdModule, RECORD_TYPES, SEARCH_INDEX_FILE, type SearchHit, type SearchOptions, type SymbolRange, TRACE_EDGES, adjudicate, assertBaseNotFrozen, buildContext, composeDecisionRecord, composeInputSchema, composeNoDecisionRecord, composeRecord, contextProfileBudgets, createKbMcpServer, decisionInputSchema, indexIsStale, isKbRecordType, isNoDecisionRecord, kbActorStampSchema, kbAnchorSchema, kbConceptIdSchema, kbJsonSchemas, kbLogEntrySchema, kbRecordFrontmatterSchema, kbSourceSchema, kbVerifiedEventSchema, listPins, loadQmd, matchToDiff, mergedContextBudgets, parseLog, parseMarkdownWithFrontmatter, pinBase, readMergedPins, readPinsLayer, renderIndex, renderIndexLine, renderLogEntry, resolveHeads, resolveHits, resolvePinPath, runKbCli, runKbMcpServer, searchBase, selectDecisions, splitMarkdownFrontmatter, stringifyMarkdownWithFrontmatter, syncInstructions, toHookJson, trace, unpinBase, validateBundle };
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,17 @@ declare const kbActorStampSchema: z.ZodObject<{
|
|
|
31
31
|
by: z.ZodString;
|
|
32
32
|
at: z.ZodString;
|
|
33
33
|
}, z.core.$loose>;
|
|
34
|
+
/**
|
|
35
|
+
* A `verified[]` event as this package writes one: the actor stamp plus a
|
|
36
|
+
* required note saying what the check found. Write-side only — the frontmatter
|
|
37
|
+
* keeps reading `verified` with `kbActorStampSchema`, because OKF-native
|
|
38
|
+
* entries carry no note and a consumer must not reject conformant records.
|
|
39
|
+
*/
|
|
40
|
+
declare const kbVerifiedEventSchema: z.ZodObject<{
|
|
41
|
+
by: z.ZodString;
|
|
42
|
+
at: z.ZodString;
|
|
43
|
+
note: z.ZodString;
|
|
44
|
+
}, z.core.$loose>;
|
|
34
45
|
/**
|
|
35
46
|
* Where a record attaches in the code.
|
|
36
47
|
*
|
|
@@ -124,6 +135,7 @@ declare const kbRecordFrontmatterSchema: z.ZodObject<{
|
|
|
124
135
|
}, z.core.$loose>;
|
|
125
136
|
type KbSource = z.infer<typeof kbSourceSchema>;
|
|
126
137
|
type KbActorStamp = z.infer<typeof kbActorStampSchema>;
|
|
138
|
+
type KbVerifiedEvent = z.infer<typeof kbVerifiedEventSchema>;
|
|
127
139
|
type KbAnchor = z.infer<typeof kbAnchorSchema>;
|
|
128
140
|
type KbRecordFrontmatter = z.infer<typeof kbRecordFrontmatterSchema>;
|
|
129
141
|
type KbRecord = {
|
|
@@ -318,8 +330,9 @@ type KbLoadResult = {
|
|
|
318
330
|
/** Named only. Their bodies are reachable through `trace`. */
|
|
319
331
|
superseded: KbSupersededStub[];
|
|
320
332
|
recordCount: number;
|
|
321
|
-
|
|
322
|
-
|
|
333
|
+
tokensLoaded: number;
|
|
334
|
+
/** `null` when loaded via `all`: no ceiling was applied. */
|
|
335
|
+
budgetTokens: number | null;
|
|
323
336
|
} | {
|
|
324
337
|
loaded: false;
|
|
325
338
|
recordCount: number;
|
|
@@ -385,6 +398,18 @@ declare class KbStore {
|
|
|
385
398
|
* timeouts.
|
|
386
399
|
*/
|
|
387
400
|
setStatus(bundlePath: string, conceptId: string, status: KbRecordStatus, actor?: string): Promise<KbRecord>;
|
|
401
|
+
/**
|
|
402
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
403
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
404
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
405
|
+
*
|
|
406
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
407
|
+
* human: the generator re-reading its own output is not an independent
|
|
408
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
409
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
410
|
+
* logs what it publishes.
|
|
411
|
+
*/
|
|
412
|
+
verify(bundlePath: string, conceptId: string, note: string, actor?: string, at?: string): Promise<KbRecord>;
|
|
388
413
|
/**
|
|
389
414
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
390
415
|
*
|
|
@@ -430,10 +455,15 @@ declare class KbStore {
|
|
|
430
455
|
* Refuses rather than truncates when the base is too large. A truncated base
|
|
431
456
|
* is indistinguishable from a complete one, so a caller would answer "that
|
|
432
457
|
* was never decided" from a slice it did not know was a slice.
|
|
458
|
+
*
|
|
459
|
+
* That refusal is the default guardrail. `all` bypasses it outright and
|
|
460
|
+
* always hands back the whole bundle: an explicit, never-accidental escape
|
|
461
|
+
* hatch for an operator who has the budget to spend, not a wider default.
|
|
433
462
|
*/
|
|
434
463
|
load(bundlePath: string, options?: {
|
|
435
464
|
budgetTokens?: number;
|
|
436
465
|
type?: string;
|
|
466
|
+
all?: boolean;
|
|
437
467
|
}): Promise<KbLoadResult>;
|
|
438
468
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
439
469
|
trace(bundlePath: string, seedId: string, options?: KbTraceOptions): Promise<KbTraceStep[]>;
|
|
@@ -516,6 +546,7 @@ declare enum ErrorTypes {
|
|
|
516
546
|
KbRecordAlreadyExists = "KbRecordAlreadyExists",
|
|
517
547
|
KbInvalidConceptId = "KbInvalidConceptId",
|
|
518
548
|
KbRecordNotFound = "KbRecordNotFound",
|
|
549
|
+
KbSelfVerification = "KbSelfVerification",
|
|
519
550
|
KbWriteConflict = "KbWriteConflict"
|
|
520
551
|
}
|
|
521
552
|
type ErrorDetails = Record<string, string | boolean | number | string[] | boolean[] | number[]>;
|
|
@@ -558,6 +589,13 @@ declare class KbWriteConflictError extends BaseError {
|
|
|
558
589
|
readonly conceptId: string;
|
|
559
590
|
constructor(conceptId: string);
|
|
560
591
|
}
|
|
592
|
+
/** A generator confirming its own record adds no independent check. */
|
|
593
|
+
declare class KbSelfVerificationError extends BaseError {
|
|
594
|
+
readonly conceptId: string;
|
|
595
|
+
readonly actor: string;
|
|
596
|
+
readonly generatedBy: string;
|
|
597
|
+
constructor(conceptId: string, actor: string, generatedBy: string);
|
|
598
|
+
}
|
|
561
599
|
declare class KbInvalidConceptIdError extends BaseError {
|
|
562
600
|
constructor(message: string, details: Record<string, string>);
|
|
563
601
|
}
|
|
@@ -1259,4 +1297,4 @@ declare function parseMarkdownWithFrontmatter<S extends z.ZodType>(text: string,
|
|
|
1259
1297
|
frontmatter: ReturnType<S["safeParse"]>;
|
|
1260
1298
|
};
|
|
1261
1299
|
|
|
1262
|
-
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, type DecisionInput, type DiffFile, type DiffHunk, type DiffMatch, type ErrorDetails, type ErrorProps, ErrorTypes, Fault, INDEX_FILE, KB_COMMANDS, KB_COMMANDS_BY_NAME, KB_CONCEPT_ID_PATTERN, KB_CONFIDENCES, KB_DIR, KB_MATERIALITIES, KB_RECORD_STATUSES, KB_RECORD_TYPES, KB_SLUG_PATTERN, type KbActorStamp, type KbAdjudicated, type KbAnchor, KbBaseFrozenError, type KbCommand, type KbCommandContext, type KbContextBudgets, type KbContextOptions, type KbContextResult, KbInvalidConceptIdError, type KbLoadResult, type KbLogEntry, type KbLogReadResult, type KbLogger, type KbMergedPin, type KbMergedPins, type KbPin, type KbPinLayer, type KbPinOptions, type KbPinResult, type KbPinStatus, KbPinsMalformedError, type KbPinsManifest, type KbRecord, KbRecordAlreadyExistsError, type KbRecordFrontmatter, KbRecordNotFoundError, type KbRecordStatus, type KbRecordType, type KbRecordTypeSpec, type KbSearchLogger, type KbSource, type KbStanding, KbStore, type KbSupersededStub, type KbSyncResult, type KbTraceEdge, type KbTraceOptions, type KbTraceStep, type KbValidationProblem, type KbWarning, KbWriteConflictError, type KbWriteInput, LOG_FILE, type MatchOptions, NO_DECISION_SLUG, PINS_FILE, PINS_LOCAL_FILE, PIN_LAYERS, type QmdModule, RECORD_TYPES, SEARCH_INDEX_FILE, type SearchHit, type SearchOptions, type SymbolRange, TRACE_EDGES, adjudicate, assertBaseNotFrozen, buildContext, composeDecisionRecord, composeInputSchema, composeNoDecisionRecord, composeRecord, contextProfileBudgets, createKbMcpServer, decisionInputSchema, indexIsStale, isKbRecordType, isNoDecisionRecord, kbActorStampSchema, kbAnchorSchema, kbConceptIdSchema, kbJsonSchemas, kbLogEntrySchema, kbRecordFrontmatterSchema, kbSourceSchema, listPins, loadQmd, matchToDiff, mergedContextBudgets, parseLog, parseMarkdownWithFrontmatter, pinBase, readMergedPins, readPinsLayer, renderIndex, renderIndexLine, renderLogEntry, resolveHeads, resolveHits, resolvePinPath, runKbCli, runKbMcpServer, searchBase, selectDecisions, splitMarkdownFrontmatter, stringifyMarkdownWithFrontmatter, syncInstructions, toHookJson, trace, unpinBase, validateBundle };
|
|
1300
|
+
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, type DecisionInput, type DiffFile, type DiffHunk, type DiffMatch, type ErrorDetails, type ErrorProps, ErrorTypes, Fault, INDEX_FILE, KB_COMMANDS, KB_COMMANDS_BY_NAME, KB_CONCEPT_ID_PATTERN, KB_CONFIDENCES, KB_DIR, KB_MATERIALITIES, KB_RECORD_STATUSES, KB_RECORD_TYPES, KB_SLUG_PATTERN, type KbActorStamp, type KbAdjudicated, type KbAnchor, KbBaseFrozenError, type KbCommand, type KbCommandContext, type KbContextBudgets, type KbContextOptions, type KbContextResult, KbInvalidConceptIdError, type KbLoadResult, type KbLogEntry, type KbLogReadResult, type KbLogger, type KbMergedPin, type KbMergedPins, type KbPin, type KbPinLayer, type KbPinOptions, type KbPinResult, type KbPinStatus, KbPinsMalformedError, type KbPinsManifest, type KbRecord, KbRecordAlreadyExistsError, type KbRecordFrontmatter, KbRecordNotFoundError, type KbRecordStatus, type KbRecordType, type KbRecordTypeSpec, type KbSearchLogger, KbSelfVerificationError, type KbSource, type KbStanding, KbStore, type KbSupersededStub, type KbSyncResult, type KbTraceEdge, type KbTraceOptions, type KbTraceStep, type KbValidationProblem, type KbVerifiedEvent, type KbWarning, KbWriteConflictError, type KbWriteInput, LOG_FILE, type MatchOptions, NO_DECISION_SLUG, PINS_FILE, PINS_LOCAL_FILE, PIN_LAYERS, type QmdModule, RECORD_TYPES, SEARCH_INDEX_FILE, type SearchHit, type SearchOptions, type SymbolRange, TRACE_EDGES, adjudicate, assertBaseNotFrozen, buildContext, composeDecisionRecord, composeInputSchema, composeNoDecisionRecord, composeRecord, contextProfileBudgets, createKbMcpServer, decisionInputSchema, indexIsStale, isKbRecordType, isNoDecisionRecord, kbActorStampSchema, kbAnchorSchema, kbConceptIdSchema, kbJsonSchemas, kbLogEntrySchema, kbRecordFrontmatterSchema, kbSourceSchema, kbVerifiedEventSchema, listPins, loadQmd, matchToDiff, mergedContextBudgets, parseLog, parseMarkdownWithFrontmatter, pinBase, readMergedPins, readPinsLayer, renderIndex, renderIndexLine, renderLogEntry, resolveHeads, resolveHits, resolvePinPath, runKbCli, runKbMcpServer, searchBase, selectDecisions, splitMarkdownFrontmatter, stringifyMarkdownWithFrontmatter, syncInstructions, toHookJson, trace, unpinBase, validateBundle };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runKbCli
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V7TRZ2ER.js";
|
|
4
4
|
import {
|
|
5
5
|
createKbMcpServer,
|
|
6
6
|
runKbMcpServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BVF7X5VO.js";
|
|
8
8
|
import {
|
|
9
9
|
BaseError,
|
|
10
10
|
CONTEXT_BEGIN,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
KbPinsMalformedError,
|
|
29
29
|
KbRecordAlreadyExistsError,
|
|
30
30
|
KbRecordNotFoundError,
|
|
31
|
+
KbSelfVerificationError,
|
|
31
32
|
KbStore,
|
|
32
33
|
KbWriteConflictError,
|
|
33
34
|
LOG_FILE,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
kbLogEntrySchema,
|
|
58
59
|
kbRecordFrontmatterSchema,
|
|
59
60
|
kbSourceSchema,
|
|
61
|
+
kbVerifiedEventSchema,
|
|
60
62
|
listPins,
|
|
61
63
|
loadQmd,
|
|
62
64
|
mergedContextBudgets,
|
|
@@ -80,7 +82,7 @@ import {
|
|
|
80
82
|
trace,
|
|
81
83
|
unpinBase,
|
|
82
84
|
validateBundle
|
|
83
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-PNSRTKYN.js";
|
|
84
86
|
|
|
85
87
|
// src/match-diff.ts
|
|
86
88
|
function matchToDiff(files, records, options = {}) {
|
|
@@ -184,6 +186,7 @@ export {
|
|
|
184
186
|
KbPinsMalformedError,
|
|
185
187
|
KbRecordAlreadyExistsError,
|
|
186
188
|
KbRecordNotFoundError,
|
|
189
|
+
KbSelfVerificationError,
|
|
187
190
|
KbStore,
|
|
188
191
|
KbWriteConflictError,
|
|
189
192
|
LOG_FILE,
|
|
@@ -214,6 +217,7 @@ export {
|
|
|
214
217
|
kbLogEntrySchema,
|
|
215
218
|
kbRecordFrontmatterSchema,
|
|
216
219
|
kbSourceSchema,
|
|
220
|
+
kbVerifiedEventSchema,
|
|
217
221
|
listPins,
|
|
218
222
|
loadQmd,
|
|
219
223
|
matchToDiff,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/match-diff.ts"],"sourcesContent":["import { adjudicate, type KbAdjudicated } from \"./adjudicate.js\";\nimport type { KbAnchor, KbRecord } from \"./kb-record.schema.js\";\n\n/**\n * Which records apply to which part of a change.\n *\n * Takes a structural description of a diff rather than a patch, so this package\n * carries no diff parser: callers already have one, and a knowledge base has no\n * business preferring a particular flavour of unified diff.\n *\n * Deterministic on purpose. Every step here is mechanical — the one judgment,\n * whether a matched record is worth showing a reviewer, is deliberately absent.\n * A model placed here would sit between the reviewer and their diff on every\n * review, to answer a question nobody has yet shown needs asking.\n *\n * Distinct from `load()`, which hands a reader the whole base. That answers\n * \"does anything address this question\"; this answers \"what is attached to this\n * code\", and an anchor is the author's own statement rather than an inference\n * from one. A reader guessing which record relates to a hunk would be guessing\n * at something already written down — and a diff has dozens of hunks, which is\n * dozens of reader calls against microseconds of matching. Where they compose:\n * this narrows a hunk to a few records, and a reader asked to explain them gets\n * those, not the base.\n */\nexport type DiffHunk = {\n /** 1-based, inclusive, in the file's post-change line numbering. */\n startLine: number;\n endLine: number;\n};\n\nexport type DiffFile = {\n /** Repo-relative, matching how anchors are written. */\n filePath: string;\n hunks: DiffHunk[];\n};\n\n/**\n * A symbol resolved to lines. Supplied by whatever the caller uses to index\n * symbols; absence is tolerated — see `place()`.\n */\nexport type SymbolRange = {\n file: string;\n symbol: string;\n startLine: number;\n endLine: number;\n};\n\nexport type DiffMatch = {\n filePath: string;\n hunk: DiffHunk;\n /** Current records first — what still holds should be read before what does not. */\n records: KbAdjudicated[];\n /**\n * `symbol` when every record here was placed by a resolved symbol range,\n * `file` when at least one fell back to the whole file. Reported rather than\n * hidden: a caller showing a file-level match as though it were pinned to\n * these lines is claiming a precision it does not have.\n */\n precision: \"symbol\" | \"file\";\n};\n\nexport type MatchOptions = {\n /** Without these, symbol anchors degrade to file level rather than vanishing. */\n symbolRanges?: SymbolRange[];\n now?: Date;\n};\n\nexport function matchToDiff(\n files: DiffFile[],\n records: KbRecord[],\n options: MatchOptions = {},\n): DiffMatch[] {\n const ranges = indexRanges(options.symbolRanges ?? []);\n const anchored = records.filter(\n (record) => (record.frontmatter.strauss_anchors ?? []).length > 0,\n );\n const matches: DiffMatch[] = [];\n\n for (const file of files) {\n const candidates = anchored\n .map((record) => ({\n record,\n anchors: (record.frontmatter.strauss_anchors ?? []).filter(\n (anchor) => normalize(anchor.file) === normalize(file.filePath),\n ),\n }))\n .filter(({ anchors }) => anchors.length > 0);\n if (!candidates.length) continue;\n\n for (const hunk of file.hunks) {\n const hits: KbRecord[] = [];\n let precision: DiffMatch[\"precision\"] = \"symbol\";\n\n for (const { record, anchors } of candidates) {\n const placement = place(anchors, file.filePath, hunk, ranges);\n if (placement === \"miss\") continue;\n if (placement === \"file\") precision = \"file\";\n hits.push(record);\n }\n\n if (!hits.length) continue;\n matches.push({\n filePath: file.filePath,\n hunk,\n records: order(adjudicate(hits, records, options.now)),\n precision,\n });\n }\n }\n\n return matches;\n}\n\n/**\n * Whether any of a record's anchors puts it on this hunk.\n *\n * An anchor naming only a file is about the whole file, so it lands on every\n * hunk in it. One naming a symbol lands only where that symbol's lines overlap\n * — unless nothing resolved the symbol, in which case it falls back to the file\n * rather than disappearing. A record silently absent because a resolver was\n * unavailable is worse than one shown imprecisely and labelled as such.\n */\nfunction place(\n anchors: KbAnchor[],\n filePath: string,\n hunk: DiffHunk,\n ranges: Map<string, SymbolRange[]>,\n): \"symbol\" | \"file\" | \"miss\" {\n let fallback: \"file\" | \"miss\" = \"miss\";\n\n for (const anchor of anchors) {\n if (!anchor.symbol) return \"file\";\n\n const resolved = ranges.get(key(filePath, anchor.symbol));\n if (!resolved?.length) {\n fallback = \"file\";\n continue;\n }\n if (resolved.some((range) => overlaps(range, hunk))) return \"symbol\";\n }\n\n return fallback;\n}\n\nfunction overlaps(range: SymbolRange, hunk: DiffHunk): boolean {\n return range.startLine <= hunk.endLine && hunk.startLine <= range.endLine;\n}\n\n/** Current before superseded, then oldest first, so an arc reads in order. */\nfunction order(records: KbAdjudicated[]): KbAdjudicated[] {\n const rank: Record<string, number> = {\n current: 0,\n unsettled: 1,\n open: 2,\n superseded: 3,\n rejected: 4,\n };\n return [...records].sort(\n (left, right) =>\n (rank[left.standing] ?? 9) - (rank[right.standing] ?? 9) ||\n (left.record.frontmatter.generated?.at ?? \"\").localeCompare(\n right.record.frontmatter.generated?.at ?? \"\",\n ),\n );\n}\n\nfunction indexRanges(ranges: SymbolRange[]): Map<string, SymbolRange[]> {\n const byKey = new Map<string, SymbolRange[]>();\n for (const range of ranges) {\n const id = key(range.file, range.symbol);\n byKey.set(id, [...(byKey.get(id) ?? []), range]);\n }\n return byKey;\n}\n\nfunction key(file: string, symbol: string): string {\n return `${normalize(file)}#${symbol}`;\n}\n\n/** Anchors are written by hand often enough that `./` shows up. */\nfunction normalize(path: string): string {\n return path.replace(/^\\.\\//, \"\");\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/match-diff.ts"],"sourcesContent":["import { adjudicate, type KbAdjudicated } from \"./adjudicate.js\";\nimport type { KbAnchor, KbRecord } from \"./kb-record.schema.js\";\n\n/**\n * Which records apply to which part of a change.\n *\n * Takes a structural description of a diff rather than a patch, so this package\n * carries no diff parser: callers already have one, and a knowledge base has no\n * business preferring a particular flavour of unified diff.\n *\n * Deterministic on purpose. Every step here is mechanical — the one judgment,\n * whether a matched record is worth showing a reviewer, is deliberately absent.\n * A model placed here would sit between the reviewer and their diff on every\n * review, to answer a question nobody has yet shown needs asking.\n *\n * Distinct from `load()`, which hands a reader the whole base. That answers\n * \"does anything address this question\"; this answers \"what is attached to this\n * code\", and an anchor is the author's own statement rather than an inference\n * from one. A reader guessing which record relates to a hunk would be guessing\n * at something already written down — and a diff has dozens of hunks, which is\n * dozens of reader calls against microseconds of matching. Where they compose:\n * this narrows a hunk to a few records, and a reader asked to explain them gets\n * those, not the base.\n */\nexport type DiffHunk = {\n /** 1-based, inclusive, in the file's post-change line numbering. */\n startLine: number;\n endLine: number;\n};\n\nexport type DiffFile = {\n /** Repo-relative, matching how anchors are written. */\n filePath: string;\n hunks: DiffHunk[];\n};\n\n/**\n * A symbol resolved to lines. Supplied by whatever the caller uses to index\n * symbols; absence is tolerated — see `place()`.\n */\nexport type SymbolRange = {\n file: string;\n symbol: string;\n startLine: number;\n endLine: number;\n};\n\nexport type DiffMatch = {\n filePath: string;\n hunk: DiffHunk;\n /** Current records first — what still holds should be read before what does not. */\n records: KbAdjudicated[];\n /**\n * `symbol` when every record here was placed by a resolved symbol range,\n * `file` when at least one fell back to the whole file. Reported rather than\n * hidden: a caller showing a file-level match as though it were pinned to\n * these lines is claiming a precision it does not have.\n */\n precision: \"symbol\" | \"file\";\n};\n\nexport type MatchOptions = {\n /** Without these, symbol anchors degrade to file level rather than vanishing. */\n symbolRanges?: SymbolRange[];\n now?: Date;\n};\n\nexport function matchToDiff(\n files: DiffFile[],\n records: KbRecord[],\n options: MatchOptions = {},\n): DiffMatch[] {\n const ranges = indexRanges(options.symbolRanges ?? []);\n const anchored = records.filter(\n (record) => (record.frontmatter.strauss_anchors ?? []).length > 0,\n );\n const matches: DiffMatch[] = [];\n\n for (const file of files) {\n const candidates = anchored\n .map((record) => ({\n record,\n anchors: (record.frontmatter.strauss_anchors ?? []).filter(\n (anchor) => normalize(anchor.file) === normalize(file.filePath),\n ),\n }))\n .filter(({ anchors }) => anchors.length > 0);\n if (!candidates.length) continue;\n\n for (const hunk of file.hunks) {\n const hits: KbRecord[] = [];\n let precision: DiffMatch[\"precision\"] = \"symbol\";\n\n for (const { record, anchors } of candidates) {\n const placement = place(anchors, file.filePath, hunk, ranges);\n if (placement === \"miss\") continue;\n if (placement === \"file\") precision = \"file\";\n hits.push(record);\n }\n\n if (!hits.length) continue;\n matches.push({\n filePath: file.filePath,\n hunk,\n records: order(adjudicate(hits, records, options.now)),\n precision,\n });\n }\n }\n\n return matches;\n}\n\n/**\n * Whether any of a record's anchors puts it on this hunk.\n *\n * An anchor naming only a file is about the whole file, so it lands on every\n * hunk in it. One naming a symbol lands only where that symbol's lines overlap\n * — unless nothing resolved the symbol, in which case it falls back to the file\n * rather than disappearing. A record silently absent because a resolver was\n * unavailable is worse than one shown imprecisely and labelled as such.\n */\nfunction place(\n anchors: KbAnchor[],\n filePath: string,\n hunk: DiffHunk,\n ranges: Map<string, SymbolRange[]>,\n): \"symbol\" | \"file\" | \"miss\" {\n let fallback: \"file\" | \"miss\" = \"miss\";\n\n for (const anchor of anchors) {\n if (!anchor.symbol) return \"file\";\n\n const resolved = ranges.get(key(filePath, anchor.symbol));\n if (!resolved?.length) {\n fallback = \"file\";\n continue;\n }\n if (resolved.some((range) => overlaps(range, hunk))) return \"symbol\";\n }\n\n return fallback;\n}\n\nfunction overlaps(range: SymbolRange, hunk: DiffHunk): boolean {\n return range.startLine <= hunk.endLine && hunk.startLine <= range.endLine;\n}\n\n/** Current before superseded, then oldest first, so an arc reads in order. */\nfunction order(records: KbAdjudicated[]): KbAdjudicated[] {\n const rank: Record<string, number> = {\n current: 0,\n unsettled: 1,\n open: 2,\n superseded: 3,\n rejected: 4,\n };\n return [...records].sort(\n (left, right) =>\n (rank[left.standing] ?? 9) - (rank[right.standing] ?? 9) ||\n (left.record.frontmatter.generated?.at ?? \"\").localeCompare(\n right.record.frontmatter.generated?.at ?? \"\",\n ),\n );\n}\n\nfunction indexRanges(ranges: SymbolRange[]): Map<string, SymbolRange[]> {\n const byKey = new Map<string, SymbolRange[]>();\n for (const range of ranges) {\n const id = key(range.file, range.symbol);\n byKey.set(id, [...(byKey.get(id) ?? []), range]);\n }\n return byKey;\n}\n\nfunction key(file: string, symbol: string): string {\n return `${normalize(file)}#${symbol}`;\n}\n\n/** Anchors are written by hand often enough that `./` shows up. */\nfunction normalize(path: string): string {\n return path.replace(/^\\.\\//, \"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEO,SAAS,YACd,OACA,SACA,UAAwB,CAAC,GACZ;AACb,QAAM,SAAS,YAAY,QAAQ,gBAAgB,CAAC,CAAC;AACrD,QAAM,WAAW,QAAQ;AAAA,IACvB,CAAC,YAAY,OAAO,YAAY,mBAAmB,CAAC,GAAG,SAAS;AAAA,EAClE;AACA,QAAM,UAAuB,CAAC;AAE9B,aAAW,QAAQ,OAAO;AACxB,UAAM,aAAa,SAChB,IAAI,CAAC,YAAY;AAAA,MAChB;AAAA,MACA,UAAU,OAAO,YAAY,mBAAmB,CAAC,GAAG;AAAA,QAClD,CAAC,WAAW,UAAU,OAAO,IAAI,MAAM,UAAU,KAAK,QAAQ;AAAA,MAChE;AAAA,IACF,EAAE,EACD,OAAO,CAAC,EAAE,QAAQ,MAAM,QAAQ,SAAS,CAAC;AAC7C,QAAI,CAAC,WAAW,OAAQ;AAExB,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,OAAmB,CAAC;AAC1B,UAAI,YAAoC;AAExC,iBAAW,EAAE,QAAQ,QAAQ,KAAK,YAAY;AAC5C,cAAM,YAAY,MAAM,SAAS,KAAK,UAAU,MAAM,MAAM;AAC5D,YAAI,cAAc,OAAQ;AAC1B,YAAI,cAAc,OAAQ,aAAY;AACtC,aAAK,KAAK,MAAM;AAAA,MAClB;AAEA,UAAI,CAAC,KAAK,OAAQ;AAClB,cAAQ,KAAK;AAAA,QACX,UAAU,KAAK;AAAA,QACf;AAAA,QACA,SAAS,MAAM,WAAW,MAAM,SAAS,QAAQ,GAAG,CAAC;AAAA,QACrD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAWA,SAAS,MACP,SACA,UACA,MACA,QAC4B;AAC5B,MAAI,WAA4B;AAEhC,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,UAAM,WAAW,OAAO,IAAI,IAAI,UAAU,OAAO,MAAM,CAAC;AACxD,QAAI,CAAC,UAAU,QAAQ;AACrB,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,KAAK,CAAC,UAAU,SAAS,OAAO,IAAI,CAAC,EAAG,QAAO;AAAA,EAC9D;AAEA,SAAO;AACT;AAEA,SAAS,SAAS,OAAoB,MAAyB;AAC7D,SAAO,MAAM,aAAa,KAAK,WAAW,KAAK,aAAa,MAAM;AACpE;AAGA,SAAS,MAAM,SAA2C;AACxD,QAAM,OAA+B;AAAA,IACnC,SAAS;AAAA,IACT,WAAW;AAAA,IACX,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,UAAU;AAAA,EACZ;AACA,SAAO,CAAC,GAAG,OAAO,EAAE;AAAA,IAClB,CAAC,MAAM,WACJ,KAAK,KAAK,QAAQ,KAAK,MAAM,KAAK,MAAM,QAAQ,KAAK,OACrD,KAAK,OAAO,YAAY,WAAW,MAAM,IAAI;AAAA,MAC5C,MAAM,OAAO,YAAY,WAAW,MAAM;AAAA,IAC5C;AAAA,EACJ;AACF;AAEA,SAAS,YAAY,QAAmD;AACtE,QAAM,QAAQ,oBAAI,IAA2B;AAC7C,aAAW,SAAS,QAAQ;AAC1B,UAAM,KAAK,IAAI,MAAM,MAAM,MAAM,MAAM;AACvC,UAAM,IAAI,IAAI,CAAC,GAAI,MAAM,IAAI,EAAE,KAAK,CAAC,GAAI,KAAK,CAAC;AAAA,EACjD;AACA,SAAO;AACT;AAEA,SAAS,IAAI,MAAc,QAAwB;AACjD,SAAO,GAAG,UAAU,IAAI,CAAC,IAAI,MAAM;AACrC;AAGA,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,SAAS,EAAE;AACjC;","names":[]}
|
package/dist/mcp-main.cjs
CHANGED
|
@@ -46,6 +46,11 @@ var kbActorStampSchema = import_zod.z.object({
|
|
|
46
46
|
by: import_zod.z.string().min(1),
|
|
47
47
|
at: import_zod.z.string().min(1)
|
|
48
48
|
}).passthrough();
|
|
49
|
+
var kbVerifiedEventSchema = kbActorStampSchema.extend({
|
|
50
|
+
note: import_zod.z.string().refine((s) => s.trim().length > 0, {
|
|
51
|
+
message: "note must say what the check found"
|
|
52
|
+
})
|
|
53
|
+
});
|
|
49
54
|
var kbAnchorSchema = import_zod.z.object({
|
|
50
55
|
file: import_zod.z.string().min(1),
|
|
51
56
|
symbol: import_zod.z.string().min(1).optional()
|
|
@@ -1074,25 +1079,32 @@ var import_zod9 = require("zod");
|
|
|
1074
1079
|
var loadCommand = define({
|
|
1075
1080
|
name: "load",
|
|
1076
1081
|
tool: "kb_load",
|
|
1077
|
-
usage: "load [type] [--budget N]",
|
|
1078
|
-
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.",
|
|
1082
|
+
usage: "load [type] [--budget N | --all]",
|
|
1083
|
+
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.",
|
|
1079
1084
|
input: import_zod9.z.object({
|
|
1080
1085
|
bundlePath,
|
|
1081
1086
|
type: import_zod9.z.enum(KB_RECORD_TYPES).optional(),
|
|
1082
|
-
budgetTokens: import_zod9.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000.")
|
|
1087
|
+
budgetTokens: import_zod9.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
|
|
1088
|
+
all: import_zod9.z.boolean().optional().describe(
|
|
1089
|
+
"Load the entire base regardless of size. The deliberate-operator escape hatch; mutually exclusive with budgetTokens."
|
|
1090
|
+
)
|
|
1091
|
+
}).refine((value) => !(value.all && value.budgetTokens !== void 0), {
|
|
1092
|
+
message: "all and budgetTokens are mutually exclusive: pass a ceiling or none, not both."
|
|
1083
1093
|
}),
|
|
1084
1094
|
fromArgv: (argv, path) => {
|
|
1085
1095
|
const budget = argvFlag(argv, "--budget");
|
|
1086
1096
|
return {
|
|
1087
1097
|
bundlePath: path,
|
|
1088
|
-
...argv[1] && argv[1]
|
|
1089
|
-
...budget ? { budgetTokens: Number(budget) } : {}
|
|
1098
|
+
...argv[1] && !argv[1].startsWith("--") ? { type: argv[1] } : {},
|
|
1099
|
+
...budget ? { budgetTokens: Number(budget) } : {},
|
|
1100
|
+
...argv.includes("--all") ? { all: true } : {}
|
|
1090
1101
|
};
|
|
1091
1102
|
},
|
|
1092
|
-
run: async ({ store }, { bundlePath: path, type, budgetTokens }) => {
|
|
1103
|
+
run: async ({ store }, { bundlePath: path, type, budgetTokens, all }) => {
|
|
1093
1104
|
const result = await store.load(path, {
|
|
1094
1105
|
...type ? { type } : {},
|
|
1095
|
-
...budgetTokens ? { budgetTokens } : {}
|
|
1106
|
+
...budgetTokens ? { budgetTokens } : {},
|
|
1107
|
+
...all ? { all } : {}
|
|
1096
1108
|
});
|
|
1097
1109
|
if (!result.loaded) return result;
|
|
1098
1110
|
return {
|
|
@@ -1570,8 +1582,37 @@ var validateCommand = define({
|
|
|
1570
1582
|
failsWhen: (result) => Array.isArray(result) && result.length > 0
|
|
1571
1583
|
});
|
|
1572
1584
|
|
|
1573
|
-
// src/commands/
|
|
1585
|
+
// src/commands/verify.ts
|
|
1574
1586
|
var import_zod26 = require("zod");
|
|
1587
|
+
var verifyCommand = define({
|
|
1588
|
+
name: "verify",
|
|
1589
|
+
tool: "kb_verify",
|
|
1590
|
+
usage: "verify <concept-id> --note <text>",
|
|
1591
|
+
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.",
|
|
1592
|
+
input: import_zod26.z.object({
|
|
1593
|
+
bundlePath,
|
|
1594
|
+
conceptId,
|
|
1595
|
+
note: import_zod26.z.string().refine((s) => s.trim().length > 0, {
|
|
1596
|
+
message: "note must say what the check found"
|
|
1597
|
+
})
|
|
1598
|
+
}),
|
|
1599
|
+
fromArgv: (argv, path) => ({
|
|
1600
|
+
bundlePath: path,
|
|
1601
|
+
conceptId: argv[1],
|
|
1602
|
+
note: argvFlag(argv, "--note")
|
|
1603
|
+
}),
|
|
1604
|
+
run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, note }) => {
|
|
1605
|
+
await assertBaseNotFrozen(process.cwd(), path);
|
|
1606
|
+
const record = await store.verify(path, id, note, actor, now());
|
|
1607
|
+
return {
|
|
1608
|
+
conceptId: record.conceptId,
|
|
1609
|
+
verified: record.frontmatter.verified?.length ?? 0
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
// src/commands/write.ts
|
|
1615
|
+
var import_zod27 = require("zod");
|
|
1575
1616
|
var writeCommand = define({
|
|
1576
1617
|
name: "write",
|
|
1577
1618
|
tool: "kb_write",
|
|
@@ -1585,9 +1626,9 @@ var writeCommand = define({
|
|
|
1585
1626
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
1586
1627
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
1587
1628
|
].join("\n"),
|
|
1588
|
-
input:
|
|
1629
|
+
input: import_zod27.z.object({
|
|
1589
1630
|
bundlePath,
|
|
1590
|
-
type:
|
|
1631
|
+
type: import_zod27.z.enum(KB_RECORD_TYPES),
|
|
1591
1632
|
input: composeInputSchema
|
|
1592
1633
|
}),
|
|
1593
1634
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -1611,7 +1652,7 @@ var writeCommand = define({
|
|
|
1611
1652
|
});
|
|
1612
1653
|
|
|
1613
1654
|
// src/commands/write-decision.ts
|
|
1614
|
-
var
|
|
1655
|
+
var import_zod28 = require("zod");
|
|
1615
1656
|
var writeDecisionCommand = define({
|
|
1616
1657
|
name: "write-decision",
|
|
1617
1658
|
tool: "kb_write_decision",
|
|
@@ -1624,7 +1665,7 @@ var writeDecisionCommand = define({
|
|
|
1624
1665
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
1625
1666
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
1626
1667
|
].join("\n"),
|
|
1627
|
-
input:
|
|
1668
|
+
input: import_zod28.z.object({ bundlePath, input: decisionInputSchema }),
|
|
1628
1669
|
fromArgv: async (_argv, path, stdin) => ({
|
|
1629
1670
|
bundlePath: path,
|
|
1630
1671
|
input: JSON.parse(await stdin())
|
|
@@ -1652,6 +1693,7 @@ var KB_COMMANDS = [
|
|
|
1652
1693
|
statusCommand,
|
|
1653
1694
|
supersedeCommand,
|
|
1654
1695
|
answerCommand,
|
|
1696
|
+
verifyCommand,
|
|
1655
1697
|
loadCommand,
|
|
1656
1698
|
queryCommand,
|
|
1657
1699
|
traceCommand,
|
|
@@ -1767,6 +1809,25 @@ var KbWriteConflictError = class extends BaseError {
|
|
|
1767
1809
|
}
|
|
1768
1810
|
conceptId;
|
|
1769
1811
|
};
|
|
1812
|
+
var KbSelfVerificationError = class extends BaseError {
|
|
1813
|
+
constructor(conceptId2, actor, generatedBy) {
|
|
1814
|
+
super({
|
|
1815
|
+
message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
|
|
1816
|
+
errorType: "KbSelfVerification" /* KbSelfVerification */,
|
|
1817
|
+
code: 400,
|
|
1818
|
+
fault: "User" /* User */,
|
|
1819
|
+
retriable: false,
|
|
1820
|
+
reportToUser: true,
|
|
1821
|
+
details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
|
|
1822
|
+
});
|
|
1823
|
+
this.conceptId = conceptId2;
|
|
1824
|
+
this.actor = actor;
|
|
1825
|
+
this.generatedBy = generatedBy;
|
|
1826
|
+
}
|
|
1827
|
+
conceptId;
|
|
1828
|
+
actor;
|
|
1829
|
+
generatedBy;
|
|
1830
|
+
};
|
|
1770
1831
|
var KbInvalidConceptIdError = class extends BaseError {
|
|
1771
1832
|
constructor(message, details) {
|
|
1772
1833
|
super({
|
|
@@ -1985,6 +2046,40 @@ var KbStore = class {
|
|
|
1985
2046
|
{ operation: `status:${status}`, by: actor }
|
|
1986
2047
|
);
|
|
1987
2048
|
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
2051
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
2052
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
2053
|
+
*
|
|
2054
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
2055
|
+
* human: the generator re-reading its own output is not an independent
|
|
2056
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
2057
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
2058
|
+
* logs what it publishes.
|
|
2059
|
+
*/
|
|
2060
|
+
async verify(bundlePath2, conceptId2, note, actor = "unknown", at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2061
|
+
const event = kbVerifiedEventSchema.parse({ by: actor, at, note });
|
|
2062
|
+
const existing = await this.read(bundlePath2, conceptId2);
|
|
2063
|
+
if (!existing) throw new KbRecordNotFoundError(conceptId2);
|
|
2064
|
+
const generatedBy = existing.frontmatter.generated?.by;
|
|
2065
|
+
if (generatedBy !== void 0 && actor.toLowerCase() === generatedBy.toLowerCase() && !normalizeActor(actor).startsWith("human:")) {
|
|
2066
|
+
await this.record(this.root(bundlePath2), {
|
|
2067
|
+
operation: "verify:refused",
|
|
2068
|
+
conceptId: conceptId2,
|
|
2069
|
+
by: actor
|
|
2070
|
+
});
|
|
2071
|
+
throw new KbSelfVerificationError(conceptId2, actor, generatedBy);
|
|
2072
|
+
}
|
|
2073
|
+
return this.mutate(
|
|
2074
|
+
bundlePath2,
|
|
2075
|
+
conceptId2,
|
|
2076
|
+
(frontmatter) => ({
|
|
2077
|
+
...frontmatter,
|
|
2078
|
+
verified: [...frontmatter.verified ?? [], event]
|
|
2079
|
+
}),
|
|
2080
|
+
{ operation: "verify", by: actor }
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
1988
2083
|
/**
|
|
1989
2084
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
1990
2085
|
*
|
|
@@ -2088,6 +2183,10 @@ ${answer}
|
|
|
2088
2183
|
* Refuses rather than truncates when the base is too large. A truncated base
|
|
2089
2184
|
* is indistinguishable from a complete one, so a caller would answer "that
|
|
2090
2185
|
* was never decided" from a slice it did not know was a slice.
|
|
2186
|
+
*
|
|
2187
|
+
* That refusal is the default guardrail. `all` bypasses it outright and
|
|
2188
|
+
* always hands back the whole bundle: an explicit, never-accidental escape
|
|
2189
|
+
* hatch for an operator who has the budget to spend, not a wider default.
|
|
2091
2190
|
*/
|
|
2092
2191
|
async load(bundlePath2, options = {}) {
|
|
2093
2192
|
const budgetTokens = options.budgetTokens ?? DEFAULT_LOAD_BUDGET;
|
|
@@ -2097,7 +2196,7 @@ ${answer}
|
|
|
2097
2196
|
const records = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
2098
2197
|
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
2099
2198
|
const approxTokens2 = records.reduce((total, hit) => total + estimateTokens(hit.record), 0) + superseded.reduce((total, entry) => total + estimateStubTokens(entry), 0);
|
|
2100
|
-
if (approxTokens2 > budgetTokens) {
|
|
2199
|
+
if (!options.all && approxTokens2 > budgetTokens) {
|
|
2101
2200
|
return {
|
|
2102
2201
|
loaded: false,
|
|
2103
2202
|
recordCount: wanted.length,
|
|
@@ -2108,8 +2207,8 @@ ${answer}
|
|
|
2108
2207
|
return {
|
|
2109
2208
|
loaded: true,
|
|
2110
2209
|
recordCount: wanted.length,
|
|
2111
|
-
|
|
2112
|
-
budgetTokens,
|
|
2210
|
+
tokensLoaded: approxTokens2,
|
|
2211
|
+
budgetTokens: options.all ? null : budgetTokens,
|
|
2113
2212
|
records,
|
|
2114
2213
|
superseded
|
|
2115
2214
|
};
|
|
@@ -2315,6 +2414,11 @@ function matches(record, needle) {
|
|
|
2315
2414
|
(field) => field?.toLowerCase().includes(needle)
|
|
2316
2415
|
);
|
|
2317
2416
|
}
|
|
2417
|
+
function normalizeActor(id) {
|
|
2418
|
+
const colon = id.indexOf(":");
|
|
2419
|
+
if (colon === -1) return id.toLowerCase();
|
|
2420
|
+
return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
|
|
2421
|
+
}
|
|
2318
2422
|
function digest(contents) {
|
|
2319
2423
|
return (0, import_node_crypto.createHash)("sha256").update(contents).digest("hex");
|
|
2320
2424
|
}
|