@saasontools/strauss-kb 0.1.7 → 0.1.9
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/ARCHITECTURE.md +89 -0
- package/README.md +132 -5
- package/dist/{chunk-GKCG4P3L.js → chunk-KVEEISYQ.js} +24 -10
- package/dist/chunk-KVEEISYQ.js.map +1 -0
- package/dist/{chunk-LCQKARFK.js → chunk-MWWDD23L.js} +2 -2
- package/dist/{chunk-GKUQOJEK.js → chunk-OFDWRMY6.js} +622 -154
- package/dist/chunk-OFDWRMY6.js.map +1 -0
- package/dist/cli-main.cjs +663 -197
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +559 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -3
- package/dist/index.d.ts +120 -3
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +641 -189
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-GKCG4P3L.js.map +0 -1
- package/dist/chunk-GKUQOJEK.js.map +0 -1
- /package/dist/{chunk-LCQKARFK.js.map → chunk-MWWDD23L.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -313,7 +313,7 @@ declare function pack(bundle: KbRecord[], rootId: string, options?: KbPackOption
|
|
|
313
313
|
|
|
314
314
|
declare const LOG_FILE = "log.jsonl";
|
|
315
315
|
declare const kbLogEntrySchema: z.ZodObject<{
|
|
316
|
-
at: z.
|
|
316
|
+
at: z.ZodISODateTime;
|
|
317
317
|
by: z.ZodString;
|
|
318
318
|
operation: z.ZodString;
|
|
319
319
|
conceptId: z.ZodString;
|
|
@@ -569,6 +569,47 @@ declare class KbStore {
|
|
|
569
569
|
* otherwise try to read it mid-write.
|
|
570
570
|
*/
|
|
571
571
|
private publish;
|
|
572
|
+
/**
|
|
573
|
+
* Declares union merge for the log, so two worktrees writing the same
|
|
574
|
+
* bundle interleave their `log.jsonl` lines on merge rather than one
|
|
575
|
+
* side's appends silently losing to git's ordinary line-level merge.
|
|
576
|
+
*
|
|
577
|
+
* Called from `record` — every path that appends a log line, not just
|
|
578
|
+
* `write` — so a bundle only ever mutated through `setStatus`/`verify`/
|
|
579
|
+
* `supersede` still gets it. There is no cheaper reliable signal for
|
|
580
|
+
* "first write" than checking the file itself, and after the first call
|
|
581
|
+
* the check is a no-op `readFile`.
|
|
582
|
+
*
|
|
583
|
+
* A missing `.gitattributes` is created outright, with `wx` (exclusive
|
|
584
|
+
* create) rather than a plain write: if another process's `write()` won a
|
|
585
|
+
* race and created the file between the `readFile` below and this call,
|
|
586
|
+
* `wx` fails instead of truncating what that writer just wrote, and the
|
|
587
|
+
* failure is swallowed by the catch below same as any other best-effort
|
|
588
|
+
* miss. A file that exists but declares no merge strategy for the log
|
|
589
|
+
* gets the line appended, never a wholesale rewrite; one that already
|
|
590
|
+
* declares any merge strategy — this one or a user's own — is left alone
|
|
591
|
+
* entirely (see `hasMergeDeclaration`).
|
|
592
|
+
*
|
|
593
|
+
* `readFile` failing is `existing === null` only for `ENOENT` — genuinely
|
|
594
|
+
* missing. Any other error (a permission problem, a transient `EMFILE`,
|
|
595
|
+
* the path being a directory) is *not* "missing" and must not fall into
|
|
596
|
+
* the create branch, which would truncate whatever is actually there with
|
|
597
|
+
* just the union-merge line: that is the file-destroying bug this
|
|
598
|
+
* function exists to avoid, not commit. An unreadable existing file is
|
|
599
|
+
* therefore left untouched and reported as a failure like any other.
|
|
600
|
+
*
|
|
601
|
+
* Two processes racing the append branch — both read a file without the
|
|
602
|
+
* line, both append it — is possible and left unguarded: `appendFile` is
|
|
603
|
+
* `O_APPEND`, so the result is two copies of the same line rather than a
|
|
604
|
+
* torn write, and `hasMergeDeclaration` sees a duplicate declaration as
|
|
605
|
+
* "already declared" on the next call. A cheap-to-detect, harmless-to-
|
|
606
|
+
* leave residue, not a reason to add a cross-process lock (see
|
|
607
|
+
* `ARCHITECTURE.md`'s rejection of one for the same trade on records).
|
|
608
|
+
*
|
|
609
|
+
* Best-effort, like the log append it precedes: failing to write this
|
|
610
|
+
* file must not fail the mutation it guards.
|
|
611
|
+
*/
|
|
612
|
+
private ensureGitattributes;
|
|
572
613
|
/** Appends one log line. Failing to log must not fail the mutation. */
|
|
573
614
|
private record;
|
|
574
615
|
private parse;
|
|
@@ -1130,6 +1171,68 @@ type KbValidationProblem = {
|
|
|
1130
1171
|
*/
|
|
1131
1172
|
declare function validateBundle(records: KbRecord[]): KbValidationProblem[];
|
|
1132
1173
|
|
|
1174
|
+
/**
|
|
1175
|
+
* A health sweep over a whole base — read-only, and never a mutation.
|
|
1176
|
+
*
|
|
1177
|
+
* Every other read answers a question a caller already had. This one asks the
|
|
1178
|
+
* questions nobody thinks to: which records the calendar has already retired,
|
|
1179
|
+
* which nobody ever confirmed, which have been open long enough that "open" is
|
|
1180
|
+
* now the answer, and which the graph has quietly dropped on the floor. Those
|
|
1181
|
+
* decay silently, because a stale record reads exactly like a live one and a
|
|
1182
|
+
* question nobody answered reads exactly like one nobody asked.
|
|
1183
|
+
*
|
|
1184
|
+
* Grouped and counted rather than merged into one list: the seven checks are
|
|
1185
|
+
* seven different repairs — re-verify, re-date, answer, link, or supersede —
|
|
1186
|
+
* and a flat list of "problems" would leave the reader sorting them again.
|
|
1187
|
+
*
|
|
1188
|
+
* Every group is emitted even when empty. A check that found nothing and a
|
|
1189
|
+
* check that never ran look identical in a report that only lists findings,
|
|
1190
|
+
* and the difference is the whole value of a sweep.
|
|
1191
|
+
*/
|
|
1192
|
+
declare const DEFAULT_EXPIRING_DAYS = 30;
|
|
1193
|
+
declare const DEFAULT_UNVERIFIED_DAYS = 90;
|
|
1194
|
+
declare const DEFAULT_AGING_DAYS = 90;
|
|
1195
|
+
declare const KB_DOCTOR_CHECKS: readonly ["expired", "expiring", "unverified", "aging", "orphaned", "broken-supersession", "superseded-but-cited"];
|
|
1196
|
+
type KbDoctorCheck = (typeof KB_DOCTOR_CHECKS)[number];
|
|
1197
|
+
type KbDoctorFinding = {
|
|
1198
|
+
conceptId: string;
|
|
1199
|
+
title: string | null;
|
|
1200
|
+
status: KbRecordStatus;
|
|
1201
|
+
/** Why this record is in this group, in one phrase a reader can act on. */
|
|
1202
|
+
note: string;
|
|
1203
|
+
};
|
|
1204
|
+
type KbDoctorGroup = {
|
|
1205
|
+
check: KbDoctorCheck;
|
|
1206
|
+
/** What the check looks for, so a zero count still says something. */
|
|
1207
|
+
headline: string;
|
|
1208
|
+
count: number;
|
|
1209
|
+
findings: KbDoctorFinding[];
|
|
1210
|
+
};
|
|
1211
|
+
type KbDoctorThresholds = {
|
|
1212
|
+
expiringDays: number;
|
|
1213
|
+
unverifiedDays: number;
|
|
1214
|
+
agingDays: number;
|
|
1215
|
+
};
|
|
1216
|
+
type KbDoctorReport = {
|
|
1217
|
+
recordCount: number;
|
|
1218
|
+
thresholds: KbDoctorThresholds;
|
|
1219
|
+
counts: Record<KbDoctorCheck, number>;
|
|
1220
|
+
/** All seven, in `KB_DOCTOR_CHECKS` order, empty ones included. */
|
|
1221
|
+
groups: KbDoctorGroup[];
|
|
1222
|
+
findingCount: number;
|
|
1223
|
+
healthy: boolean;
|
|
1224
|
+
};
|
|
1225
|
+
type KbDoctorOptions = {
|
|
1226
|
+
/** How far ahead `expiring` looks. */
|
|
1227
|
+
expiringDays?: number;
|
|
1228
|
+
/** How old an unconfirmed record must be before `unverified` reports it. */
|
|
1229
|
+
unverifiedDays?: number;
|
|
1230
|
+
/** How long `open` or `proposed` may stand before `aging` reports it. */
|
|
1231
|
+
agingDays?: number;
|
|
1232
|
+
now?: Date;
|
|
1233
|
+
};
|
|
1234
|
+
declare function doctor(bundle: KbRecord[], options?: KbDoctorOptions): KbDoctorReport;
|
|
1235
|
+
|
|
1133
1236
|
/**
|
|
1134
1237
|
* The record written while a change is being made: why it is shaped the way it
|
|
1135
1238
|
* is, anchored to the symbols it touches.
|
|
@@ -1244,12 +1347,26 @@ type KbCommand<Shape extends z.ZodRawShape = z.ZodRawShape> = {
|
|
|
1244
1347
|
/** Positional argv → the same object MCP receives. */
|
|
1245
1348
|
fromArgv(argv: string[], bundlePath: string, stdin: () => Promise<string>): Promise<unknown> | unknown;
|
|
1246
1349
|
run(ctx: KbCommandContext, input: z.infer<z.ZodObject<Shape>>): Promise<unknown>;
|
|
1350
|
+
/**
|
|
1351
|
+
* A human-readable form of the result, for the CLI. Where it exists the CLI
|
|
1352
|
+
* prints it and `--json` asks for the machine shape instead; MCP always gets
|
|
1353
|
+
* the machine shape, since a tool result is parsed rather than read.
|
|
1354
|
+
*
|
|
1355
|
+
* Separate from `run` rather than rendered inside it — as `pack` does, whose
|
|
1356
|
+
* result *is* a document — because a command whose result is a report needs
|
|
1357
|
+
* both forms: the table for a person, and the object for `failsWhen` and for
|
|
1358
|
+
* anything downstream.
|
|
1359
|
+
*/
|
|
1360
|
+
render?(result: unknown): string;
|
|
1247
1361
|
/**
|
|
1248
1362
|
* Turns a result into a non-zero exit for the CLI. A check that reports a
|
|
1249
1363
|
* problem has succeeded as a command and failed as a check, and a shell
|
|
1250
1364
|
* caller can only see the difference through the exit code.
|
|
1365
|
+
*
|
|
1366
|
+
* The input comes too, so a command can make the exit conditional on a flag
|
|
1367
|
+
* the caller passed rather than on the result alone.
|
|
1251
1368
|
*/
|
|
1252
|
-
failsWhen?(result: unknown): boolean;
|
|
1369
|
+
failsWhen?(result: unknown, input: z.infer<z.ZodObject<Shape>>): boolean;
|
|
1253
1370
|
};
|
|
1254
1371
|
|
|
1255
1372
|
/**
|
|
@@ -1394,4 +1511,4 @@ declare function parseMarkdownWithFrontmatter<S extends z.ZodType>(text: string,
|
|
|
1394
1511
|
frontmatter: ReturnType<S["safeParse"]>;
|
|
1395
1512
|
};
|
|
1396
1513
|
|
|
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 };
|
|
1514
|
+
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, DEFAULT_AGING_DAYS, DEFAULT_EXPIRING_DAYS, DEFAULT_LOAD_BUDGET, DEFAULT_PACK_HOPS, DEFAULT_PACK_MAX_NODES, DEFAULT_UNVERIFIED_DAYS, 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_DOCTOR_CHECKS, 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 KbDoctorCheck, type KbDoctorFinding, type KbDoctorGroup, type KbDoctorOptions, type KbDoctorReport, type KbDoctorThresholds, 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, doctor, 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
|
@@ -313,7 +313,7 @@ declare function pack(bundle: KbRecord[], rootId: string, options?: KbPackOption
|
|
|
313
313
|
|
|
314
314
|
declare const LOG_FILE = "log.jsonl";
|
|
315
315
|
declare const kbLogEntrySchema: z.ZodObject<{
|
|
316
|
-
at: z.
|
|
316
|
+
at: z.ZodISODateTime;
|
|
317
317
|
by: z.ZodString;
|
|
318
318
|
operation: z.ZodString;
|
|
319
319
|
conceptId: z.ZodString;
|
|
@@ -569,6 +569,47 @@ declare class KbStore {
|
|
|
569
569
|
* otherwise try to read it mid-write.
|
|
570
570
|
*/
|
|
571
571
|
private publish;
|
|
572
|
+
/**
|
|
573
|
+
* Declares union merge for the log, so two worktrees writing the same
|
|
574
|
+
* bundle interleave their `log.jsonl` lines on merge rather than one
|
|
575
|
+
* side's appends silently losing to git's ordinary line-level merge.
|
|
576
|
+
*
|
|
577
|
+
* Called from `record` — every path that appends a log line, not just
|
|
578
|
+
* `write` — so a bundle only ever mutated through `setStatus`/`verify`/
|
|
579
|
+
* `supersede` still gets it. There is no cheaper reliable signal for
|
|
580
|
+
* "first write" than checking the file itself, and after the first call
|
|
581
|
+
* the check is a no-op `readFile`.
|
|
582
|
+
*
|
|
583
|
+
* A missing `.gitattributes` is created outright, with `wx` (exclusive
|
|
584
|
+
* create) rather than a plain write: if another process's `write()` won a
|
|
585
|
+
* race and created the file between the `readFile` below and this call,
|
|
586
|
+
* `wx` fails instead of truncating what that writer just wrote, and the
|
|
587
|
+
* failure is swallowed by the catch below same as any other best-effort
|
|
588
|
+
* miss. A file that exists but declares no merge strategy for the log
|
|
589
|
+
* gets the line appended, never a wholesale rewrite; one that already
|
|
590
|
+
* declares any merge strategy — this one or a user's own — is left alone
|
|
591
|
+
* entirely (see `hasMergeDeclaration`).
|
|
592
|
+
*
|
|
593
|
+
* `readFile` failing is `existing === null` only for `ENOENT` — genuinely
|
|
594
|
+
* missing. Any other error (a permission problem, a transient `EMFILE`,
|
|
595
|
+
* the path being a directory) is *not* "missing" and must not fall into
|
|
596
|
+
* the create branch, which would truncate whatever is actually there with
|
|
597
|
+
* just the union-merge line: that is the file-destroying bug this
|
|
598
|
+
* function exists to avoid, not commit. An unreadable existing file is
|
|
599
|
+
* therefore left untouched and reported as a failure like any other.
|
|
600
|
+
*
|
|
601
|
+
* Two processes racing the append branch — both read a file without the
|
|
602
|
+
* line, both append it — is possible and left unguarded: `appendFile` is
|
|
603
|
+
* `O_APPEND`, so the result is two copies of the same line rather than a
|
|
604
|
+
* torn write, and `hasMergeDeclaration` sees a duplicate declaration as
|
|
605
|
+
* "already declared" on the next call. A cheap-to-detect, harmless-to-
|
|
606
|
+
* leave residue, not a reason to add a cross-process lock (see
|
|
607
|
+
* `ARCHITECTURE.md`'s rejection of one for the same trade on records).
|
|
608
|
+
*
|
|
609
|
+
* Best-effort, like the log append it precedes: failing to write this
|
|
610
|
+
* file must not fail the mutation it guards.
|
|
611
|
+
*/
|
|
612
|
+
private ensureGitattributes;
|
|
572
613
|
/** Appends one log line. Failing to log must not fail the mutation. */
|
|
573
614
|
private record;
|
|
574
615
|
private parse;
|
|
@@ -1130,6 +1171,68 @@ type KbValidationProblem = {
|
|
|
1130
1171
|
*/
|
|
1131
1172
|
declare function validateBundle(records: KbRecord[]): KbValidationProblem[];
|
|
1132
1173
|
|
|
1174
|
+
/**
|
|
1175
|
+
* A health sweep over a whole base — read-only, and never a mutation.
|
|
1176
|
+
*
|
|
1177
|
+
* Every other read answers a question a caller already had. This one asks the
|
|
1178
|
+
* questions nobody thinks to: which records the calendar has already retired,
|
|
1179
|
+
* which nobody ever confirmed, which have been open long enough that "open" is
|
|
1180
|
+
* now the answer, and which the graph has quietly dropped on the floor. Those
|
|
1181
|
+
* decay silently, because a stale record reads exactly like a live one and a
|
|
1182
|
+
* question nobody answered reads exactly like one nobody asked.
|
|
1183
|
+
*
|
|
1184
|
+
* Grouped and counted rather than merged into one list: the seven checks are
|
|
1185
|
+
* seven different repairs — re-verify, re-date, answer, link, or supersede —
|
|
1186
|
+
* and a flat list of "problems" would leave the reader sorting them again.
|
|
1187
|
+
*
|
|
1188
|
+
* Every group is emitted even when empty. A check that found nothing and a
|
|
1189
|
+
* check that never ran look identical in a report that only lists findings,
|
|
1190
|
+
* and the difference is the whole value of a sweep.
|
|
1191
|
+
*/
|
|
1192
|
+
declare const DEFAULT_EXPIRING_DAYS = 30;
|
|
1193
|
+
declare const DEFAULT_UNVERIFIED_DAYS = 90;
|
|
1194
|
+
declare const DEFAULT_AGING_DAYS = 90;
|
|
1195
|
+
declare const KB_DOCTOR_CHECKS: readonly ["expired", "expiring", "unverified", "aging", "orphaned", "broken-supersession", "superseded-but-cited"];
|
|
1196
|
+
type KbDoctorCheck = (typeof KB_DOCTOR_CHECKS)[number];
|
|
1197
|
+
type KbDoctorFinding = {
|
|
1198
|
+
conceptId: string;
|
|
1199
|
+
title: string | null;
|
|
1200
|
+
status: KbRecordStatus;
|
|
1201
|
+
/** Why this record is in this group, in one phrase a reader can act on. */
|
|
1202
|
+
note: string;
|
|
1203
|
+
};
|
|
1204
|
+
type KbDoctorGroup = {
|
|
1205
|
+
check: KbDoctorCheck;
|
|
1206
|
+
/** What the check looks for, so a zero count still says something. */
|
|
1207
|
+
headline: string;
|
|
1208
|
+
count: number;
|
|
1209
|
+
findings: KbDoctorFinding[];
|
|
1210
|
+
};
|
|
1211
|
+
type KbDoctorThresholds = {
|
|
1212
|
+
expiringDays: number;
|
|
1213
|
+
unverifiedDays: number;
|
|
1214
|
+
agingDays: number;
|
|
1215
|
+
};
|
|
1216
|
+
type KbDoctorReport = {
|
|
1217
|
+
recordCount: number;
|
|
1218
|
+
thresholds: KbDoctorThresholds;
|
|
1219
|
+
counts: Record<KbDoctorCheck, number>;
|
|
1220
|
+
/** All seven, in `KB_DOCTOR_CHECKS` order, empty ones included. */
|
|
1221
|
+
groups: KbDoctorGroup[];
|
|
1222
|
+
findingCount: number;
|
|
1223
|
+
healthy: boolean;
|
|
1224
|
+
};
|
|
1225
|
+
type KbDoctorOptions = {
|
|
1226
|
+
/** How far ahead `expiring` looks. */
|
|
1227
|
+
expiringDays?: number;
|
|
1228
|
+
/** How old an unconfirmed record must be before `unverified` reports it. */
|
|
1229
|
+
unverifiedDays?: number;
|
|
1230
|
+
/** How long `open` or `proposed` may stand before `aging` reports it. */
|
|
1231
|
+
agingDays?: number;
|
|
1232
|
+
now?: Date;
|
|
1233
|
+
};
|
|
1234
|
+
declare function doctor(bundle: KbRecord[], options?: KbDoctorOptions): KbDoctorReport;
|
|
1235
|
+
|
|
1133
1236
|
/**
|
|
1134
1237
|
* The record written while a change is being made: why it is shaped the way it
|
|
1135
1238
|
* is, anchored to the symbols it touches.
|
|
@@ -1244,12 +1347,26 @@ type KbCommand<Shape extends z.ZodRawShape = z.ZodRawShape> = {
|
|
|
1244
1347
|
/** Positional argv → the same object MCP receives. */
|
|
1245
1348
|
fromArgv(argv: string[], bundlePath: string, stdin: () => Promise<string>): Promise<unknown> | unknown;
|
|
1246
1349
|
run(ctx: KbCommandContext, input: z.infer<z.ZodObject<Shape>>): Promise<unknown>;
|
|
1350
|
+
/**
|
|
1351
|
+
* A human-readable form of the result, for the CLI. Where it exists the CLI
|
|
1352
|
+
* prints it and `--json` asks for the machine shape instead; MCP always gets
|
|
1353
|
+
* the machine shape, since a tool result is parsed rather than read.
|
|
1354
|
+
*
|
|
1355
|
+
* Separate from `run` rather than rendered inside it — as `pack` does, whose
|
|
1356
|
+
* result *is* a document — because a command whose result is a report needs
|
|
1357
|
+
* both forms: the table for a person, and the object for `failsWhen` and for
|
|
1358
|
+
* anything downstream.
|
|
1359
|
+
*/
|
|
1360
|
+
render?(result: unknown): string;
|
|
1247
1361
|
/**
|
|
1248
1362
|
* Turns a result into a non-zero exit for the CLI. A check that reports a
|
|
1249
1363
|
* problem has succeeded as a command and failed as a check, and a shell
|
|
1250
1364
|
* caller can only see the difference through the exit code.
|
|
1365
|
+
*
|
|
1366
|
+
* The input comes too, so a command can make the exit conditional on a flag
|
|
1367
|
+
* the caller passed rather than on the result alone.
|
|
1251
1368
|
*/
|
|
1252
|
-
failsWhen?(result: unknown): boolean;
|
|
1369
|
+
failsWhen?(result: unknown, input: z.infer<z.ZodObject<Shape>>): boolean;
|
|
1253
1370
|
};
|
|
1254
1371
|
|
|
1255
1372
|
/**
|
|
@@ -1394,4 +1511,4 @@ declare function parseMarkdownWithFrontmatter<S extends z.ZodType>(text: string,
|
|
|
1394
1511
|
frontmatter: ReturnType<S["safeParse"]>;
|
|
1395
1512
|
};
|
|
1396
1513
|
|
|
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 };
|
|
1514
|
+
export { BaseError, CONTEXT_BEGIN, CONTEXT_END, CONTEXT_PROFILES, type ComposeInput, type ComposedRecord, DECISION_TYPE, DEFAULT_AGING_DAYS, DEFAULT_EXPIRING_DAYS, DEFAULT_LOAD_BUDGET, DEFAULT_PACK_HOPS, DEFAULT_PACK_MAX_NODES, DEFAULT_UNVERIFIED_DAYS, 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_DOCTOR_CHECKS, 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 KbDoctorCheck, type KbDoctorFinding, type KbDoctorGroup, type KbDoctorOptions, type KbDoctorReport, type KbDoctorThresholds, 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, doctor, 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,19 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runKbCli
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KVEEISYQ.js";
|
|
4
4
|
import {
|
|
5
5
|
createKbMcpServer,
|
|
6
6
|
runKbMcpServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MWWDD23L.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_AGING_DAYS,
|
|
15
|
+
DEFAULT_EXPIRING_DAYS,
|
|
14
16
|
DEFAULT_LOAD_BUDGET,
|
|
15
17
|
DEFAULT_PACK_HOPS,
|
|
16
18
|
DEFAULT_PACK_MAX_NODES,
|
|
19
|
+
DEFAULT_UNVERIFIED_DAYS,
|
|
17
20
|
ErrorTypes,
|
|
18
21
|
Fault,
|
|
19
22
|
INDEX_FILE,
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
KB_CONCEPT_ID_PATTERN,
|
|
23
26
|
KB_CONFIDENCES,
|
|
24
27
|
KB_DIR,
|
|
28
|
+
KB_DOCTOR_CHECKS,
|
|
25
29
|
KB_EDGE_KINDS,
|
|
26
30
|
KB_MATERIALITIES,
|
|
27
31
|
KB_RECORD_STATUSES,
|
|
@@ -53,6 +57,7 @@ import {
|
|
|
53
57
|
composeRecord,
|
|
54
58
|
contextProfileBudgets,
|
|
55
59
|
decisionInputSchema,
|
|
60
|
+
doctor,
|
|
56
61
|
edgeNeighbours,
|
|
57
62
|
indexIsStale,
|
|
58
63
|
isKbRecordType,
|
|
@@ -90,7 +95,7 @@ import {
|
|
|
90
95
|
trace,
|
|
91
96
|
unpinBase,
|
|
92
97
|
validateBundle
|
|
93
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-OFDWRMY6.js";
|
|
94
99
|
|
|
95
100
|
// src/match-diff.ts
|
|
96
101
|
function matchToDiff(files, records, options = {}) {
|
|
@@ -177,9 +182,12 @@ export {
|
|
|
177
182
|
CONTEXT_END,
|
|
178
183
|
CONTEXT_PROFILES,
|
|
179
184
|
DECISION_TYPE,
|
|
185
|
+
DEFAULT_AGING_DAYS,
|
|
186
|
+
DEFAULT_EXPIRING_DAYS,
|
|
180
187
|
DEFAULT_LOAD_BUDGET,
|
|
181
188
|
DEFAULT_PACK_HOPS,
|
|
182
189
|
DEFAULT_PACK_MAX_NODES,
|
|
190
|
+
DEFAULT_UNVERIFIED_DAYS,
|
|
183
191
|
ErrorTypes,
|
|
184
192
|
Fault,
|
|
185
193
|
INDEX_FILE,
|
|
@@ -188,6 +196,7 @@ export {
|
|
|
188
196
|
KB_CONCEPT_ID_PATTERN,
|
|
189
197
|
KB_CONFIDENCES,
|
|
190
198
|
KB_DIR,
|
|
199
|
+
KB_DOCTOR_CHECKS,
|
|
191
200
|
KB_EDGE_KINDS,
|
|
192
201
|
KB_MATERIALITIES,
|
|
193
202
|
KB_RECORD_STATUSES,
|
|
@@ -220,6 +229,7 @@ export {
|
|
|
220
229
|
contextProfileBudgets,
|
|
221
230
|
createKbMcpServer,
|
|
222
231
|
decisionInputSchema,
|
|
232
|
+
doctor,
|
|
223
233
|
edgeNeighbours,
|
|
224
234
|
indexIsStale,
|
|
225
235
|
isKbRecordType,
|
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":[]}
|