@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.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 = {
|
|
@@ -213,12 +225,11 @@ declare function resolveHeads(from: KbRecord, byId: Map<string, KbRecord>): {
|
|
|
213
225
|
};
|
|
214
226
|
|
|
215
227
|
/**
|
|
216
|
-
* Edges a trace may follow.
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* so a question's resolution lives
|
|
220
|
-
*
|
|
221
|
-
* pass this package does not yet do.
|
|
228
|
+
* Edges a trace may follow — the shared kb-edges.ts definitions, minus
|
|
229
|
+
* `body-link`: body links can reach most of a bundle from anywhere, which
|
|
230
|
+
* suits a bounded pack but floods a timeline. Also absent by design:
|
|
231
|
+
* `strauss_answered` carries no target id, so a question's resolution lives
|
|
232
|
+
* in its own body rather than in another record.
|
|
222
233
|
*/
|
|
223
234
|
declare const TRACE_EDGES: readonly ["supersession", "anchor", "source"];
|
|
224
235
|
type KbTraceEdge = (typeof TRACE_EDGES)[number];
|
|
@@ -248,6 +259,58 @@ type KbTraceOptions = {
|
|
|
248
259
|
*/
|
|
249
260
|
declare function trace(seedId: string, bundle: KbRecord[], options?: KbTraceOptions): KbTraceStep[];
|
|
250
261
|
|
|
262
|
+
declare const DEFAULT_PACK_HOPS = 2;
|
|
263
|
+
declare const DEFAULT_PACK_MAX_NODES = 20;
|
|
264
|
+
type KbPackOptions = {
|
|
265
|
+
/** How far from the root the walk may reach. */
|
|
266
|
+
hops?: number;
|
|
267
|
+
/** How many records the pack may hold, root included. */
|
|
268
|
+
maxNodes?: number;
|
|
269
|
+
/** Approximate token ceiling over what is actually emitted. */
|
|
270
|
+
budgetTokens?: number;
|
|
271
|
+
};
|
|
272
|
+
/** One record as the pack emits it — the same mapping `kb_load` hands back. */
|
|
273
|
+
type KbPackedRecord = {
|
|
274
|
+
conceptId: string;
|
|
275
|
+
title: string | null;
|
|
276
|
+
standing: KbStanding;
|
|
277
|
+
supersededBy: string[];
|
|
278
|
+
warnings: KbWarning[];
|
|
279
|
+
anchors: KbAnchor[];
|
|
280
|
+
body: string;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Deliberately timestamp-free: the same bundle and root must produce the same
|
|
284
|
+
* bytes on every run, so a caller can diff two packs and trust that a changed
|
|
285
|
+
* byte means changed knowledge.
|
|
286
|
+
*/
|
|
287
|
+
type KbPackResult = {
|
|
288
|
+
root: string;
|
|
289
|
+
records: KbPackedRecord[];
|
|
290
|
+
/** Named only, exactly as `load` stubs them. Bodies reachable via trace. */
|
|
291
|
+
superseded: KbSupersededStub[];
|
|
292
|
+
/** Every reachable record the hop and node limits cut, never summarized. */
|
|
293
|
+
excluded: string[];
|
|
294
|
+
recordCount: number;
|
|
295
|
+
tokensLoaded: number;
|
|
296
|
+
budgetTokens: number;
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* A bounded, progressively disclosed neighbourhood around one record.
|
|
300
|
+
*
|
|
301
|
+
* Where `load` hands over a whole base and `trace` a timeline, a pack is the
|
|
302
|
+
* subgraph a reader needs to act near one record: everything within `hops`
|
|
303
|
+
* of the root, cut to `maxNodes` by ranking, with every cut id listed —
|
|
304
|
+
* a named gap is knowable, a silent one is not.
|
|
305
|
+
*
|
|
306
|
+
* Adjudication runs against the whole bundle, not the reached slice, so a
|
|
307
|
+
* record's standing cannot depend on whether its replacement happened to be
|
|
308
|
+
* within reach. Superseded records become the same stubs `load` emits, costed
|
|
309
|
+
* as stubs, and the budget is a refusal rather than a truncation: a partial
|
|
310
|
+
* pack looks complete to its reader.
|
|
311
|
+
*/
|
|
312
|
+
declare function pack(bundle: KbRecord[], rootId: string, options?: KbPackOptions): KbPackResult;
|
|
313
|
+
|
|
251
314
|
declare const LOG_FILE = "log.jsonl";
|
|
252
315
|
declare const kbLogEntrySchema: z.ZodObject<{
|
|
253
316
|
at: z.ZodString;
|
|
@@ -298,6 +361,8 @@ type KbLogger = {
|
|
|
298
361
|
info?(entry: Record<string, unknown>): void;
|
|
299
362
|
warn?(entry: Record<string, unknown>): void;
|
|
300
363
|
};
|
|
364
|
+
/** Roughly an eighth of a large context window — generous, and overridable. */
|
|
365
|
+
declare const DEFAULT_LOAD_BUDGET = 25000;
|
|
301
366
|
/**
|
|
302
367
|
* A superseded record, named but not spelled out.
|
|
303
368
|
*
|
|
@@ -386,6 +451,18 @@ declare class KbStore {
|
|
|
386
451
|
* timeouts.
|
|
387
452
|
*/
|
|
388
453
|
setStatus(bundlePath: string, conceptId: string, status: KbRecordStatus, actor?: string): Promise<KbRecord>;
|
|
454
|
+
/**
|
|
455
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
456
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
457
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
458
|
+
*
|
|
459
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
460
|
+
* human: the generator re-reading its own output is not an independent
|
|
461
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
462
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
463
|
+
* logs what it publishes.
|
|
464
|
+
*/
|
|
465
|
+
verify(bundlePath: string, conceptId: string, note: string, actor?: string, at?: string): Promise<KbRecord>;
|
|
389
466
|
/**
|
|
390
467
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
391
468
|
*
|
|
@@ -443,6 +520,8 @@ declare class KbStore {
|
|
|
443
520
|
}): Promise<KbLoadResult>;
|
|
444
521
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
445
522
|
trace(bundlePath: string, seedId: string, options?: KbTraceOptions): Promise<KbTraceStep[]>;
|
|
523
|
+
/** A bounded neighbourhood around one record. See `pack.ts`. */
|
|
524
|
+
pack(bundlePath: string, rootId: string, options?: KbPackOptions): Promise<KbPackResult>;
|
|
446
525
|
/**
|
|
447
526
|
* The stored index, rebuilt if it disagrees with the records.
|
|
448
527
|
*
|
|
@@ -521,7 +600,9 @@ declare enum Fault {
|
|
|
521
600
|
declare enum ErrorTypes {
|
|
522
601
|
KbRecordAlreadyExists = "KbRecordAlreadyExists",
|
|
523
602
|
KbInvalidConceptId = "KbInvalidConceptId",
|
|
603
|
+
KbPackBudgetExceeded = "KbPackBudgetExceeded",
|
|
524
604
|
KbRecordNotFound = "KbRecordNotFound",
|
|
605
|
+
KbSelfVerification = "KbSelfVerification",
|
|
525
606
|
KbWriteConflict = "KbWriteConflict"
|
|
526
607
|
}
|
|
527
608
|
type ErrorDetails = Record<string, string | boolean | number | string[] | boolean[] | number[]>;
|
|
@@ -564,6 +645,27 @@ declare class KbWriteConflictError extends BaseError {
|
|
|
564
645
|
readonly conceptId: string;
|
|
565
646
|
constructor(conceptId: string);
|
|
566
647
|
}
|
|
648
|
+
/** A generator confirming its own record adds no independent check. */
|
|
649
|
+
declare class KbSelfVerificationError extends BaseError {
|
|
650
|
+
readonly conceptId: string;
|
|
651
|
+
readonly actor: string;
|
|
652
|
+
readonly generatedBy: string;
|
|
653
|
+
constructor(conceptId: string, actor: string, generatedBy: string);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* A pack that will not fit its token budget. Refusal, not truncation: a
|
|
657
|
+
* partial pack is indistinguishable from a complete one, so the caller gets
|
|
658
|
+
* the full picture — how many records, how many tokens, and every id the
|
|
659
|
+
* walk's own limits already cut — and decides whether to raise the budget or
|
|
660
|
+
* tighten the walk.
|
|
661
|
+
*/
|
|
662
|
+
declare class KbPackBudgetExceededError extends BaseError {
|
|
663
|
+
readonly recordCount: number;
|
|
664
|
+
readonly approxTokens: number;
|
|
665
|
+
readonly budgetTokens: number;
|
|
666
|
+
readonly excluded: string[];
|
|
667
|
+
constructor(recordCount: number, approxTokens: number, budgetTokens: number, excluded: string[]);
|
|
668
|
+
}
|
|
567
669
|
declare class KbInvalidConceptIdError extends BaseError {
|
|
568
670
|
constructor(message: string, details: Record<string, string>);
|
|
569
671
|
}
|
|
@@ -986,6 +1088,33 @@ type MatchOptions = {
|
|
|
986
1088
|
};
|
|
987
1089
|
declare function matchToDiff(files: DiffFile[], records: KbRecord[], options?: MatchOptions): DiffMatch[];
|
|
988
1090
|
|
|
1091
|
+
/**
|
|
1092
|
+
* The edges between records in one bundle, defined once.
|
|
1093
|
+
*
|
|
1094
|
+
* Both walks — `trace` and `pack` — consume this module, so they cannot drift
|
|
1095
|
+
* into disagreeing about what makes two records neighbours, and a diagnostic
|
|
1096
|
+
* pass over the graph can reuse the same definition.
|
|
1097
|
+
*
|
|
1098
|
+
* There is no separate `related` kind: compose.ts renders `relatedConceptIds`
|
|
1099
|
+
* as body links (`Relates to [id](id.md).`), so in stored form a related edge
|
|
1100
|
+
* IS a body link, and a distinct kind would count the same markdown twice.
|
|
1101
|
+
*/
|
|
1102
|
+
declare const KB_EDGE_KINDS: readonly ["body-link", "supersession", "anchor", "source"];
|
|
1103
|
+
type KbEdgeKind = (typeof KB_EDGE_KINDS)[number];
|
|
1104
|
+
type KbNeighbour = {
|
|
1105
|
+
record: KbRecord;
|
|
1106
|
+
/** Every edge kind that connects it to the record asked about. */
|
|
1107
|
+
via: KbEdgeKind[];
|
|
1108
|
+
};
|
|
1109
|
+
/**
|
|
1110
|
+
* Every record `from` touches, each carrying the full set of edge kinds that
|
|
1111
|
+
* connect the pair. Order is deterministic: bundle order per kind, kinds in
|
|
1112
|
+
* the order given.
|
|
1113
|
+
*/
|
|
1114
|
+
declare function neighbours(from: KbRecord, bundle: KbRecord[], kinds?: readonly KbEdgeKind[]): KbNeighbour[];
|
|
1115
|
+
/** The records one edge kind connects `from` to, in bundle order. */
|
|
1116
|
+
declare function edgeNeighbours(from: KbRecord, bundle: KbRecord[], kind: KbEdgeKind): KbRecord[];
|
|
1117
|
+
|
|
989
1118
|
type KbValidationProblem = {
|
|
990
1119
|
check: string;
|
|
991
1120
|
conceptId: string;
|
|
@@ -1265,4 +1394,4 @@ declare function parseMarkdownWithFrontmatter<S extends z.ZodType>(text: string,
|
|
|
1265
1394
|
frontmatter: ReturnType<S["safeParse"]>;
|
|
1266
1395
|
};
|
|
1267
1396
|
|
|
1268
|
-
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 };
|
|
1397
|
+
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, DEFAULT_LOAD_BUDGET, DEFAULT_PACK_HOPS, DEFAULT_PACK_MAX_NODES, 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_EDGE_KINDS, 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, type KbEdgeKind, KbInvalidConceptIdError, type KbLoadResult, type KbLogEntry, type KbLogReadResult, type KbLogger, type KbMergedPin, type KbMergedPins, type KbNeighbour, KbPackBudgetExceededError, type KbPackOptions, type KbPackResult, type KbPackedRecord, 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, edgeNeighbours, indexIsStale, isKbRecordType, isNoDecisionRecord, kbActorStampSchema, kbAnchorSchema, kbConceptIdSchema, kbJsonSchemas, kbLogEntrySchema, kbRecordFrontmatterSchema, kbSourceSchema, kbVerifiedEventSchema, listPins, loadQmd, matchToDiff, mergedContextBudgets, neighbours, pack, 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 = {
|
|
@@ -213,12 +225,11 @@ declare function resolveHeads(from: KbRecord, byId: Map<string, KbRecord>): {
|
|
|
213
225
|
};
|
|
214
226
|
|
|
215
227
|
/**
|
|
216
|
-
* Edges a trace may follow.
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* so a question's resolution lives
|
|
220
|
-
*
|
|
221
|
-
* pass this package does not yet do.
|
|
228
|
+
* Edges a trace may follow — the shared kb-edges.ts definitions, minus
|
|
229
|
+
* `body-link`: body links can reach most of a bundle from anywhere, which
|
|
230
|
+
* suits a bounded pack but floods a timeline. Also absent by design:
|
|
231
|
+
* `strauss_answered` carries no target id, so a question's resolution lives
|
|
232
|
+
* in its own body rather than in another record.
|
|
222
233
|
*/
|
|
223
234
|
declare const TRACE_EDGES: readonly ["supersession", "anchor", "source"];
|
|
224
235
|
type KbTraceEdge = (typeof TRACE_EDGES)[number];
|
|
@@ -248,6 +259,58 @@ type KbTraceOptions = {
|
|
|
248
259
|
*/
|
|
249
260
|
declare function trace(seedId: string, bundle: KbRecord[], options?: KbTraceOptions): KbTraceStep[];
|
|
250
261
|
|
|
262
|
+
declare const DEFAULT_PACK_HOPS = 2;
|
|
263
|
+
declare const DEFAULT_PACK_MAX_NODES = 20;
|
|
264
|
+
type KbPackOptions = {
|
|
265
|
+
/** How far from the root the walk may reach. */
|
|
266
|
+
hops?: number;
|
|
267
|
+
/** How many records the pack may hold, root included. */
|
|
268
|
+
maxNodes?: number;
|
|
269
|
+
/** Approximate token ceiling over what is actually emitted. */
|
|
270
|
+
budgetTokens?: number;
|
|
271
|
+
};
|
|
272
|
+
/** One record as the pack emits it — the same mapping `kb_load` hands back. */
|
|
273
|
+
type KbPackedRecord = {
|
|
274
|
+
conceptId: string;
|
|
275
|
+
title: string | null;
|
|
276
|
+
standing: KbStanding;
|
|
277
|
+
supersededBy: string[];
|
|
278
|
+
warnings: KbWarning[];
|
|
279
|
+
anchors: KbAnchor[];
|
|
280
|
+
body: string;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Deliberately timestamp-free: the same bundle and root must produce the same
|
|
284
|
+
* bytes on every run, so a caller can diff two packs and trust that a changed
|
|
285
|
+
* byte means changed knowledge.
|
|
286
|
+
*/
|
|
287
|
+
type KbPackResult = {
|
|
288
|
+
root: string;
|
|
289
|
+
records: KbPackedRecord[];
|
|
290
|
+
/** Named only, exactly as `load` stubs them. Bodies reachable via trace. */
|
|
291
|
+
superseded: KbSupersededStub[];
|
|
292
|
+
/** Every reachable record the hop and node limits cut, never summarized. */
|
|
293
|
+
excluded: string[];
|
|
294
|
+
recordCount: number;
|
|
295
|
+
tokensLoaded: number;
|
|
296
|
+
budgetTokens: number;
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* A bounded, progressively disclosed neighbourhood around one record.
|
|
300
|
+
*
|
|
301
|
+
* Where `load` hands over a whole base and `trace` a timeline, a pack is the
|
|
302
|
+
* subgraph a reader needs to act near one record: everything within `hops`
|
|
303
|
+
* of the root, cut to `maxNodes` by ranking, with every cut id listed —
|
|
304
|
+
* a named gap is knowable, a silent one is not.
|
|
305
|
+
*
|
|
306
|
+
* Adjudication runs against the whole bundle, not the reached slice, so a
|
|
307
|
+
* record's standing cannot depend on whether its replacement happened to be
|
|
308
|
+
* within reach. Superseded records become the same stubs `load` emits, costed
|
|
309
|
+
* as stubs, and the budget is a refusal rather than a truncation: a partial
|
|
310
|
+
* pack looks complete to its reader.
|
|
311
|
+
*/
|
|
312
|
+
declare function pack(bundle: KbRecord[], rootId: string, options?: KbPackOptions): KbPackResult;
|
|
313
|
+
|
|
251
314
|
declare const LOG_FILE = "log.jsonl";
|
|
252
315
|
declare const kbLogEntrySchema: z.ZodObject<{
|
|
253
316
|
at: z.ZodString;
|
|
@@ -298,6 +361,8 @@ type KbLogger = {
|
|
|
298
361
|
info?(entry: Record<string, unknown>): void;
|
|
299
362
|
warn?(entry: Record<string, unknown>): void;
|
|
300
363
|
};
|
|
364
|
+
/** Roughly an eighth of a large context window — generous, and overridable. */
|
|
365
|
+
declare const DEFAULT_LOAD_BUDGET = 25000;
|
|
301
366
|
/**
|
|
302
367
|
* A superseded record, named but not spelled out.
|
|
303
368
|
*
|
|
@@ -386,6 +451,18 @@ declare class KbStore {
|
|
|
386
451
|
* timeouts.
|
|
387
452
|
*/
|
|
388
453
|
setStatus(bundlePath: string, conceptId: string, status: KbRecordStatus, actor?: string): Promise<KbRecord>;
|
|
454
|
+
/**
|
|
455
|
+
* Appends one `verified[]` event: who checked the record, when, and what the
|
|
456
|
+
* check found. Append-only — prior events are history, and are spread into
|
|
457
|
+
* the new array untouched rather than reshaped through the write schema.
|
|
458
|
+
*
|
|
459
|
+
* A record's generator cannot verify its own record unless the actor is
|
|
460
|
+
* human: the generator re-reading its own output is not an independent
|
|
461
|
+
* check. The rule runs before the mutation so a refusal never publishes,
|
|
462
|
+
* and the refusal is logged under its own operation name — `mutate` only
|
|
463
|
+
* logs what it publishes.
|
|
464
|
+
*/
|
|
465
|
+
verify(bundlePath: string, conceptId: string, note: string, actor?: string, at?: string): Promise<KbRecord>;
|
|
389
466
|
/**
|
|
390
467
|
* Marks `conceptId` superseded by `replacementId`, and links both directions.
|
|
391
468
|
*
|
|
@@ -443,6 +520,8 @@ declare class KbStore {
|
|
|
443
520
|
}): Promise<KbLoadResult>;
|
|
444
521
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
445
522
|
trace(bundlePath: string, seedId: string, options?: KbTraceOptions): Promise<KbTraceStep[]>;
|
|
523
|
+
/** A bounded neighbourhood around one record. See `pack.ts`. */
|
|
524
|
+
pack(bundlePath: string, rootId: string, options?: KbPackOptions): Promise<KbPackResult>;
|
|
446
525
|
/**
|
|
447
526
|
* The stored index, rebuilt if it disagrees with the records.
|
|
448
527
|
*
|
|
@@ -521,7 +600,9 @@ declare enum Fault {
|
|
|
521
600
|
declare enum ErrorTypes {
|
|
522
601
|
KbRecordAlreadyExists = "KbRecordAlreadyExists",
|
|
523
602
|
KbInvalidConceptId = "KbInvalidConceptId",
|
|
603
|
+
KbPackBudgetExceeded = "KbPackBudgetExceeded",
|
|
524
604
|
KbRecordNotFound = "KbRecordNotFound",
|
|
605
|
+
KbSelfVerification = "KbSelfVerification",
|
|
525
606
|
KbWriteConflict = "KbWriteConflict"
|
|
526
607
|
}
|
|
527
608
|
type ErrorDetails = Record<string, string | boolean | number | string[] | boolean[] | number[]>;
|
|
@@ -564,6 +645,27 @@ declare class KbWriteConflictError extends BaseError {
|
|
|
564
645
|
readonly conceptId: string;
|
|
565
646
|
constructor(conceptId: string);
|
|
566
647
|
}
|
|
648
|
+
/** A generator confirming its own record adds no independent check. */
|
|
649
|
+
declare class KbSelfVerificationError extends BaseError {
|
|
650
|
+
readonly conceptId: string;
|
|
651
|
+
readonly actor: string;
|
|
652
|
+
readonly generatedBy: string;
|
|
653
|
+
constructor(conceptId: string, actor: string, generatedBy: string);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* A pack that will not fit its token budget. Refusal, not truncation: a
|
|
657
|
+
* partial pack is indistinguishable from a complete one, so the caller gets
|
|
658
|
+
* the full picture — how many records, how many tokens, and every id the
|
|
659
|
+
* walk's own limits already cut — and decides whether to raise the budget or
|
|
660
|
+
* tighten the walk.
|
|
661
|
+
*/
|
|
662
|
+
declare class KbPackBudgetExceededError extends BaseError {
|
|
663
|
+
readonly recordCount: number;
|
|
664
|
+
readonly approxTokens: number;
|
|
665
|
+
readonly budgetTokens: number;
|
|
666
|
+
readonly excluded: string[];
|
|
667
|
+
constructor(recordCount: number, approxTokens: number, budgetTokens: number, excluded: string[]);
|
|
668
|
+
}
|
|
567
669
|
declare class KbInvalidConceptIdError extends BaseError {
|
|
568
670
|
constructor(message: string, details: Record<string, string>);
|
|
569
671
|
}
|
|
@@ -986,6 +1088,33 @@ type MatchOptions = {
|
|
|
986
1088
|
};
|
|
987
1089
|
declare function matchToDiff(files: DiffFile[], records: KbRecord[], options?: MatchOptions): DiffMatch[];
|
|
988
1090
|
|
|
1091
|
+
/**
|
|
1092
|
+
* The edges between records in one bundle, defined once.
|
|
1093
|
+
*
|
|
1094
|
+
* Both walks — `trace` and `pack` — consume this module, so they cannot drift
|
|
1095
|
+
* into disagreeing about what makes two records neighbours, and a diagnostic
|
|
1096
|
+
* pass over the graph can reuse the same definition.
|
|
1097
|
+
*
|
|
1098
|
+
* There is no separate `related` kind: compose.ts renders `relatedConceptIds`
|
|
1099
|
+
* as body links (`Relates to [id](id.md).`), so in stored form a related edge
|
|
1100
|
+
* IS a body link, and a distinct kind would count the same markdown twice.
|
|
1101
|
+
*/
|
|
1102
|
+
declare const KB_EDGE_KINDS: readonly ["body-link", "supersession", "anchor", "source"];
|
|
1103
|
+
type KbEdgeKind = (typeof KB_EDGE_KINDS)[number];
|
|
1104
|
+
type KbNeighbour = {
|
|
1105
|
+
record: KbRecord;
|
|
1106
|
+
/** Every edge kind that connects it to the record asked about. */
|
|
1107
|
+
via: KbEdgeKind[];
|
|
1108
|
+
};
|
|
1109
|
+
/**
|
|
1110
|
+
* Every record `from` touches, each carrying the full set of edge kinds that
|
|
1111
|
+
* connect the pair. Order is deterministic: bundle order per kind, kinds in
|
|
1112
|
+
* the order given.
|
|
1113
|
+
*/
|
|
1114
|
+
declare function neighbours(from: KbRecord, bundle: KbRecord[], kinds?: readonly KbEdgeKind[]): KbNeighbour[];
|
|
1115
|
+
/** The records one edge kind connects `from` to, in bundle order. */
|
|
1116
|
+
declare function edgeNeighbours(from: KbRecord, bundle: KbRecord[], kind: KbEdgeKind): KbRecord[];
|
|
1117
|
+
|
|
989
1118
|
type KbValidationProblem = {
|
|
990
1119
|
check: string;
|
|
991
1120
|
conceptId: string;
|
|
@@ -1265,4 +1394,4 @@ declare function parseMarkdownWithFrontmatter<S extends z.ZodType>(text: string,
|
|
|
1265
1394
|
frontmatter: ReturnType<S["safeParse"]>;
|
|
1266
1395
|
};
|
|
1267
1396
|
|
|
1268
|
-
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 };
|
|
1397
|
+
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, DEFAULT_LOAD_BUDGET, DEFAULT_PACK_HOPS, DEFAULT_PACK_MAX_NODES, 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_EDGE_KINDS, 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, type KbEdgeKind, KbInvalidConceptIdError, type KbLoadResult, type KbLogEntry, type KbLogReadResult, type KbLogger, type KbMergedPin, type KbMergedPins, type KbNeighbour, KbPackBudgetExceededError, type KbPackOptions, type KbPackResult, type KbPackedRecord, 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, edgeNeighbours, indexIsStale, isKbRecordType, isNoDecisionRecord, kbActorStampSchema, kbAnchorSchema, kbConceptIdSchema, kbJsonSchemas, kbLogEntrySchema, kbRecordFrontmatterSchema, kbSourceSchema, kbVerifiedEventSchema, listPins, loadQmd, matchToDiff, mergedContextBudgets, neighbours, pack, 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,16 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runKbCli
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GKCG4P3L.js";
|
|
4
4
|
import {
|
|
5
5
|
createKbMcpServer,
|
|
6
6
|
runKbMcpServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LCQKARFK.js";
|
|
8
8
|
import {
|
|
9
9
|
BaseError,
|
|
10
10
|
CONTEXT_BEGIN,
|
|
11
11
|
CONTEXT_END,
|
|
12
12
|
CONTEXT_PROFILES,
|
|
13
13
|
DECISION_TYPE,
|
|
14
|
+
DEFAULT_LOAD_BUDGET,
|
|
15
|
+
DEFAULT_PACK_HOPS,
|
|
16
|
+
DEFAULT_PACK_MAX_NODES,
|
|
14
17
|
ErrorTypes,
|
|
15
18
|
Fault,
|
|
16
19
|
INDEX_FILE,
|
|
@@ -19,15 +22,18 @@ import {
|
|
|
19
22
|
KB_CONCEPT_ID_PATTERN,
|
|
20
23
|
KB_CONFIDENCES,
|
|
21
24
|
KB_DIR,
|
|
25
|
+
KB_EDGE_KINDS,
|
|
22
26
|
KB_MATERIALITIES,
|
|
23
27
|
KB_RECORD_STATUSES,
|
|
24
28
|
KB_RECORD_TYPES,
|
|
25
29
|
KB_SLUG_PATTERN,
|
|
26
30
|
KbBaseFrozenError,
|
|
27
31
|
KbInvalidConceptIdError,
|
|
32
|
+
KbPackBudgetExceededError,
|
|
28
33
|
KbPinsMalformedError,
|
|
29
34
|
KbRecordAlreadyExistsError,
|
|
30
35
|
KbRecordNotFoundError,
|
|
36
|
+
KbSelfVerificationError,
|
|
31
37
|
KbStore,
|
|
32
38
|
KbWriteConflictError,
|
|
33
39
|
LOG_FILE,
|
|
@@ -47,6 +53,7 @@ import {
|
|
|
47
53
|
composeRecord,
|
|
48
54
|
contextProfileBudgets,
|
|
49
55
|
decisionInputSchema,
|
|
56
|
+
edgeNeighbours,
|
|
50
57
|
indexIsStale,
|
|
51
58
|
isKbRecordType,
|
|
52
59
|
isNoDecisionRecord,
|
|
@@ -57,9 +64,12 @@ import {
|
|
|
57
64
|
kbLogEntrySchema,
|
|
58
65
|
kbRecordFrontmatterSchema,
|
|
59
66
|
kbSourceSchema,
|
|
67
|
+
kbVerifiedEventSchema,
|
|
60
68
|
listPins,
|
|
61
69
|
loadQmd,
|
|
62
70
|
mergedContextBudgets,
|
|
71
|
+
neighbours,
|
|
72
|
+
pack,
|
|
63
73
|
parseLog,
|
|
64
74
|
parseMarkdownWithFrontmatter,
|
|
65
75
|
pinBase,
|
|
@@ -80,7 +90,7 @@ import {
|
|
|
80
90
|
trace,
|
|
81
91
|
unpinBase,
|
|
82
92
|
validateBundle
|
|
83
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-GKUQOJEK.js";
|
|
84
94
|
|
|
85
95
|
// src/match-diff.ts
|
|
86
96
|
function matchToDiff(files, records, options = {}) {
|
|
@@ -167,6 +177,9 @@ export {
|
|
|
167
177
|
CONTEXT_END,
|
|
168
178
|
CONTEXT_PROFILES,
|
|
169
179
|
DECISION_TYPE,
|
|
180
|
+
DEFAULT_LOAD_BUDGET,
|
|
181
|
+
DEFAULT_PACK_HOPS,
|
|
182
|
+
DEFAULT_PACK_MAX_NODES,
|
|
170
183
|
ErrorTypes,
|
|
171
184
|
Fault,
|
|
172
185
|
INDEX_FILE,
|
|
@@ -175,15 +188,18 @@ export {
|
|
|
175
188
|
KB_CONCEPT_ID_PATTERN,
|
|
176
189
|
KB_CONFIDENCES,
|
|
177
190
|
KB_DIR,
|
|
191
|
+
KB_EDGE_KINDS,
|
|
178
192
|
KB_MATERIALITIES,
|
|
179
193
|
KB_RECORD_STATUSES,
|
|
180
194
|
KB_RECORD_TYPES,
|
|
181
195
|
KB_SLUG_PATTERN,
|
|
182
196
|
KbBaseFrozenError,
|
|
183
197
|
KbInvalidConceptIdError,
|
|
198
|
+
KbPackBudgetExceededError,
|
|
184
199
|
KbPinsMalformedError,
|
|
185
200
|
KbRecordAlreadyExistsError,
|
|
186
201
|
KbRecordNotFoundError,
|
|
202
|
+
KbSelfVerificationError,
|
|
187
203
|
KbStore,
|
|
188
204
|
KbWriteConflictError,
|
|
189
205
|
LOG_FILE,
|
|
@@ -204,6 +220,7 @@ export {
|
|
|
204
220
|
contextProfileBudgets,
|
|
205
221
|
createKbMcpServer,
|
|
206
222
|
decisionInputSchema,
|
|
223
|
+
edgeNeighbours,
|
|
207
224
|
indexIsStale,
|
|
208
225
|
isKbRecordType,
|
|
209
226
|
isNoDecisionRecord,
|
|
@@ -214,10 +231,13 @@ export {
|
|
|
214
231
|
kbLogEntrySchema,
|
|
215
232
|
kbRecordFrontmatterSchema,
|
|
216
233
|
kbSourceSchema,
|
|
234
|
+
kbVerifiedEventSchema,
|
|
217
235
|
listPins,
|
|
218
236
|
loadQmd,
|
|
219
237
|
matchToDiff,
|
|
220
238
|
mergedContextBudgets,
|
|
239
|
+
neighbours,
|
|
240
|
+
pack,
|
|
221
241
|
parseLog,
|
|
222
242
|
parseMarkdownWithFrontmatter,
|
|
223
243
|
pinBase,
|
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":[]}
|