@hraness/kb 0.17.1

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.
Files changed (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,1018 @@
1
+ import { realpath, stat } from "node:fs/promises";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+
4
+ import {
5
+ inspectAgentContextRepository,
6
+ type AgentContextRepositoryInspection,
7
+ } from "./agent-context.js";
8
+ import {
9
+ MAX_EVALUATION_EVIDENCE_BYTES,
10
+ type EvaluationDiagnostic,
11
+ type EvaluationRawHit,
12
+ type EvaluationRetriever,
13
+ type EvaluationRetrieverResult,
14
+ type RetrievalEvaluationCorpus,
15
+ } from "./evaluation.js";
16
+ import { redactEvaluationMachinePaths } from "./evaluation-redaction.js";
17
+ import {
18
+ runGitCommand,
19
+ type GitCommandProvider,
20
+ type GitHistoryDependencies,
21
+ type GitHistoryForNotesResult,
22
+ type GitHistoryNoteProvenance,
23
+ type GitHistorySearchHit,
24
+ type GitHistorySearchResult,
25
+ } from "./git.js";
26
+ import {
27
+ buildRepositoryMemoryContext,
28
+ MAX_REPOSITORY_MEMORY_DETAIL_LIMIT,
29
+ MAX_REPOSITORY_MEMORY_GROUP_LIMIT,
30
+ repositoryMemoryGroupKeys,
31
+ type RepositoryMemoryContext,
32
+ } from "./repository-memory.js";
33
+ import {
34
+ MAX_SEARCH_RESULTS,
35
+ openKnowledgeBase,
36
+ type KnowledgeBaseDependencies,
37
+ type KnowledgeBaseSearchMode,
38
+ type KnowledgeBaseSearchResult,
39
+ type KnowledgeBaseSession,
40
+ type OpenKnowledgeBaseOptions,
41
+ } from "./sdk.js";
42
+ import { scanVault, type VaultSnapshot } from "./vault.js";
43
+
44
+ const objectIdPattern = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u;
45
+ const SNAPSHOT_GIT_TIMEOUT_MS = 10_000;
46
+ const SNAPSHOT_GIT_OUTPUT_BYTES = 64 * 1_024;
47
+ const DIAGNOSTIC_MESSAGE_BYTES = 16 * 1_024;
48
+
49
+ export const knowledgeBaseEvaluationRetrieverIds = Object.freeze([
50
+ "exact",
51
+ "keyword",
52
+ "semantic",
53
+ "hybrid",
54
+ "metadata",
55
+ "graph",
56
+ "path-context",
57
+ "git",
58
+ ] as const);
59
+
60
+ export type KnowledgeBaseEvaluationRetrieverId =
61
+ typeof knowledgeBaseEvaluationRetrieverIds[number];
62
+
63
+ type CanonicalPathDependencies = {
64
+ readonly realpath?: (path: string) => Promise<string>;
65
+ readonly stat?: (path: string) => Promise<{ readonly isDirectory: () => boolean }>;
66
+ };
67
+
68
+ export type VerifyFrozenEvaluationSnapshotOptions = CanonicalPathDependencies & {
69
+ readonly corpus: Pick<RetrievalEvaluationCorpus, "frozen">;
70
+ readonly root: string;
71
+ readonly repository: string;
72
+ readonly runGit?: GitCommandProvider;
73
+ };
74
+
75
+ export type FrozenEvaluationSnapshot = {
76
+ readonly repository: string;
77
+ readonly root: string;
78
+ readonly vaultRoot: string;
79
+ readonly repositoryCommit: string;
80
+ readonly vaultTree: string;
81
+ };
82
+
83
+ export class FrozenEvaluationSnapshotError extends Error {
84
+ constructor(message: string, options?: ErrorOptions) {
85
+ super(message, options);
86
+ this.name = "FrozenEvaluationSnapshotError";
87
+ }
88
+ }
89
+
90
+ export type OpenKnowledgeBaseEvaluationOptions = CanonicalPathDependencies & {
91
+ readonly corpus: RetrievalEvaluationCorpus;
92
+ readonly root: string;
93
+ readonly repository: string;
94
+ readonly database?: string;
95
+ /** Verified local bytes for the pinned semantic model. */
96
+ readonly embeddingModelFile?: string;
97
+ /** Shared opaque lease for verified local bytes across evaluator readers. */
98
+ readonly embeddingModelLease?: OpenKnowledgeBaseOptions["embeddingModelLease"];
99
+ /** Require measurable store-local query-vector inference. */
100
+ readonly requireStoreLocalVectorBoundary?: boolean;
101
+ readonly runGit?: GitCommandProvider;
102
+ readonly scanVault?: typeof scanVault;
103
+ readonly openKnowledgeBase?: typeof openKnowledgeBase;
104
+ readonly inspectAgentContextRepository?: typeof inspectAgentContextRepository;
105
+ readonly buildRepositoryMemoryContext?: typeof buildRepositoryMemoryContext;
106
+ readonly semantic?: KnowledgeBaseDependencies["semantic"];
107
+ readonly openSemanticSearchSession?: KnowledgeBaseDependencies["openSemanticSearchSession"];
108
+ readonly git?: GitHistoryDependencies;
109
+ readonly indexGitHistory?: KnowledgeBaseDependencies["indexGitHistory"];
110
+ readonly now?: () => number;
111
+ };
112
+
113
+ export type KnowledgeBaseEvaluation = {
114
+ readonly retrievers: readonly EvaluationRetriever[];
115
+ readonly close: () => Promise<void>;
116
+ };
117
+
118
+ function pathIsWithin(root: string, candidate: string): boolean {
119
+ const fromRoot = relative(root, candidate);
120
+ return fromRoot === ""
121
+ || (
122
+ fromRoot !== ".."
123
+ && !fromRoot.startsWith(`..${sep}`)
124
+ && !isAbsolute(fromRoot)
125
+ );
126
+ }
127
+
128
+ function decodedOutput(
129
+ output: string | Uint8Array,
130
+ label: string,
131
+ maximumBytes = SNAPSHOT_GIT_OUTPUT_BYTES,
132
+ ): string {
133
+ const bytes = typeof output === "string" ? Buffer.from(output, "utf8") : Buffer.from(output);
134
+ if (bytes.byteLength > maximumBytes) {
135
+ throw new FrozenEvaluationSnapshotError(
136
+ `${label} exceeded the ${maximumBytes.toLocaleString("en-US")}-byte output limit.`,
137
+ );
138
+ }
139
+ return bytes.toString("utf8");
140
+ }
141
+
142
+ async function gitOutput(
143
+ runGit: GitCommandProvider,
144
+ repository: string,
145
+ args: readonly string[],
146
+ label: string,
147
+ ): Promise<string> {
148
+ const result = await runGit({
149
+ arguments: args,
150
+ cwd: repository,
151
+ timeoutMs: SNAPSHOT_GIT_TIMEOUT_MS,
152
+ maxOutputBytes: SNAPSHOT_GIT_OUTPUT_BYTES,
153
+ });
154
+ if (result.status !== "ok") {
155
+ throw new FrozenEvaluationSnapshotError(
156
+ `${label} could not be verified: ${result.status === "unavailable" ? result.message : result.message}`,
157
+ );
158
+ }
159
+ return decodedOutput(result.stdout, label);
160
+ }
161
+
162
+ function gitObjectId(output: string, label: string): string {
163
+ const value = output.trim();
164
+ if (!objectIdPattern.test(value)) {
165
+ throw new FrozenEvaluationSnapshotError(`${label} returned a malformed Git object ID.`);
166
+ }
167
+ return value;
168
+ }
169
+
170
+ /**
171
+ * Verify that a corpus names the checked-out, clean vault snapshot it will read.
172
+ * All Git work uses bounded argv requests and never invokes a shell.
173
+ */
174
+ export async function verifyFrozenEvaluationSnapshot(
175
+ options: VerifyFrozenEvaluationSnapshotOptions,
176
+ ): Promise<FrozenEvaluationSnapshot> {
177
+ const resolveRealpath = options.realpath ?? realpath;
178
+ const readStat = options.stat ?? stat;
179
+ const repository = await resolveRealpath(resolve(options.repository));
180
+ const repositoryState = await readStat(repository);
181
+ if (!repositoryState.isDirectory()) {
182
+ throw new FrozenEvaluationSnapshotError("The evaluation repository must be a directory.");
183
+ }
184
+
185
+ const root = await resolveRealpath(resolve(options.root));
186
+ const rootState = await readStat(root);
187
+ if (!rootState.isDirectory()) {
188
+ throw new FrozenEvaluationSnapshotError("The evaluation vault root must be a directory.");
189
+ }
190
+ if (!pathIsWithin(repository, root)) {
191
+ throw new FrozenEvaluationSnapshotError(
192
+ "The evaluation vault root must resolve inside the repository.",
193
+ );
194
+ }
195
+
196
+ const declaredRoot = await resolveRealpath(resolve(repository, options.corpus.frozen.vaultRoot));
197
+ if (!pathIsWithin(repository, declaredRoot) || declaredRoot !== root) {
198
+ throw new FrozenEvaluationSnapshotError(
199
+ "The evaluation vault root must resolve to frozen.vaultRoot inside the repository.",
200
+ );
201
+ }
202
+
203
+ const runGit = options.runGit ?? runGitCommand;
204
+ const repositoryPrefix = (await gitOutput(
205
+ runGit,
206
+ repository,
207
+ ["rev-parse", "--show-prefix"],
208
+ "Git repository root",
209
+ )).trim();
210
+ if (repositoryPrefix !== "") {
211
+ throw new FrozenEvaluationSnapshotError(
212
+ `The evaluation repository must be the Git working-tree root; Git reported prefix ${JSON.stringify(repositoryPrefix)}.`,
213
+ );
214
+ }
215
+ const head = gitObjectId(
216
+ await gitOutput(runGit, repository, ["rev-parse", "--verify", "HEAD"], "Git HEAD"),
217
+ "Git HEAD",
218
+ );
219
+ if (head !== options.corpus.frozen.repositoryCommit) {
220
+ throw new FrozenEvaluationSnapshotError(
221
+ `Frozen repository commit ${options.corpus.frozen.repositoryCommit} does not match HEAD ${head}.`,
222
+ );
223
+ }
224
+
225
+ const vaultRevision = options.corpus.frozen.vaultRoot === "."
226
+ ? "HEAD^{tree}"
227
+ : `HEAD:${options.corpus.frozen.vaultRoot}`;
228
+ const vaultTree = gitObjectId(
229
+ await gitOutput(
230
+ runGit,
231
+ repository,
232
+ ["rev-parse", "--verify", vaultRevision],
233
+ "Git vault tree",
234
+ ),
235
+ "Git vault tree",
236
+ );
237
+ if (vaultTree !== options.corpus.frozen.vaultTree) {
238
+ throw new FrozenEvaluationSnapshotError(
239
+ `Frozen vault tree ${options.corpus.frozen.vaultTree} does not match ${vaultRevision} ${vaultTree}.`,
240
+ );
241
+ }
242
+
243
+ const objectType = (await gitOutput(
244
+ runGit,
245
+ repository,
246
+ ["cat-file", "-t", vaultRevision],
247
+ "Git vault object type",
248
+ )).trim();
249
+ if (objectType !== "tree") {
250
+ throw new FrozenEvaluationSnapshotError(
251
+ `${vaultRevision} must identify a Git tree, but Git reported ${JSON.stringify(objectType)}.`,
252
+ );
253
+ }
254
+
255
+ const status = await gitOutput(
256
+ runGit,
257
+ repository,
258
+ [
259
+ "status",
260
+ "--porcelain=v1",
261
+ "--untracked-files=all",
262
+ "--",
263
+ options.corpus.frozen.vaultRoot,
264
+ ],
265
+ "Git vault status",
266
+ );
267
+ if (status.trim() !== "") {
268
+ const firstChange = status.trim().split(/\r?\n/u)[0] ?? "unknown vault change";
269
+ throw new FrozenEvaluationSnapshotError(
270
+ `The frozen evaluation vault has tracked or untracked changes: ${firstChange}.`,
271
+ );
272
+ }
273
+
274
+ return Object.freeze({
275
+ repository,
276
+ root,
277
+ vaultRoot: options.corpus.frozen.vaultRoot,
278
+ repositoryCommit: head,
279
+ vaultTree,
280
+ });
281
+ }
282
+
283
+ function elapsed(startedAt: number, now: () => number): number {
284
+ const duration = now() - startedAt;
285
+ return Number.isFinite(duration) && duration > 0 ? duration : 0;
286
+ }
287
+
288
+ function utf8Prefix(value: string, maximumBytes: number): string {
289
+ let bytes = 0;
290
+ let end = 0;
291
+ for (const character of value) {
292
+ const width = Buffer.byteLength(character, "utf8");
293
+ if (bytes + width > maximumBytes) break;
294
+ bytes += width;
295
+ end += character.length;
296
+ }
297
+ return value.slice(0, end);
298
+ }
299
+
300
+ function diagnosticMessage(parts: readonly string[]): string | undefined {
301
+ const joined = redactEvaluationMachinePaths(parts
302
+ .join(" ")
303
+ .normalize("NFC")
304
+ .replace(/[\0\r\n]+/gu, " ")
305
+ .trim());
306
+ if (joined === "") return undefined;
307
+ return utf8Prefix(joined, DIAGNOSTIC_MESSAGE_BYTES);
308
+ }
309
+
310
+ function evidenceSnapshot(value: unknown): unknown {
311
+ const serialized = JSON.stringify(value, (_key, candidate: unknown) =>
312
+ typeof candidate === "string" ? redactEvaluationMachinePaths(candidate) : candidate);
313
+ if (serialized === undefined) return null;
314
+ const bytes = Buffer.byteLength(serialized, "utf8");
315
+ if (bytes <= MAX_EVALUATION_EVIDENCE_BYTES) {
316
+ return JSON.parse(serialized) as unknown;
317
+ }
318
+ let prefixBytes = MAX_EVALUATION_EVIDENCE_BYTES - 1_024;
319
+ while (prefixBytes > 0) {
320
+ const snapshot = {
321
+ truncated: true,
322
+ originalUtf8Bytes: bytes,
323
+ jsonPrefix: utf8Prefix(serialized, prefixBytes),
324
+ };
325
+ if (Buffer.byteLength(JSON.stringify(snapshot), "utf8") <= MAX_EVALUATION_EVIDENCE_BYTES) {
326
+ return snapshot;
327
+ }
328
+ prefixBytes = Math.floor(prefixBytes * 0.75);
329
+ }
330
+ return { truncated: true, originalUtf8Bytes: bytes, jsonPrefix: "" };
331
+ }
332
+
333
+ function unavailable(
334
+ id: KnowledgeBaseEvaluationRetrieverId,
335
+ requiredInput: string,
336
+ noteCount: number,
337
+ ): EvaluationRetrieverResult {
338
+ return Object.freeze({
339
+ status: "unavailable",
340
+ hits: Object.freeze([]),
341
+ diagnostics: Object.freeze([Object.freeze({
342
+ lane: id,
343
+ status: "unavailable",
344
+ message: `${id} retrieval requires ${requiredInput}.`,
345
+ })]),
346
+ timings: Object.freeze({}),
347
+ resources: Object.freeze({ noteCount }),
348
+ });
349
+ }
350
+
351
+ type LaneEvidenceProvenance = Readonly<{
352
+ readonly targetDocumentId: string;
353
+ readonly evidenceDocumentId: string;
354
+ readonly sourcePath: string;
355
+ readonly locator: Readonly<Record<string, unknown>>;
356
+ }>;
357
+
358
+ function textHitProvenance(
359
+ hit: KnowledgeBaseSearchResult["results"][number],
360
+ ): readonly LaneEvidenceProvenance[] {
361
+ const exactEvidence = hit.evidence.find((evidence) => evidence.kind === "exact");
362
+ const qmdEvidence = hit.evidence.filter((evidence) => evidence.kind === "qmd");
363
+ const explicitLocators = hit.evidence.flatMap((evidence) => evidence.kind !== "exact"
364
+ ? []
365
+ : evidence.matches.flatMap((match): readonly Readonly<Record<string, unknown>>[] => {
366
+ if (match.field === "title") {
367
+ return [Object.freeze({ kind: "title", title: hit.title })];
368
+ }
369
+ if (match.field === "alias") {
370
+ return [Object.freeze({
371
+ kind: "frontmatter-field-any",
372
+ fields: Object.freeze(["aliases", "alias"]),
373
+ })];
374
+ }
375
+ if (match.field === "tag") {
376
+ return [Object.freeze({ kind: "frontmatter-field", field: "tags" })];
377
+ }
378
+ if (match.field === "metadata") {
379
+ return [Object.freeze({ kind: "frontmatter-value", value: match.value })];
380
+ }
381
+ if (match.field === "path") {
382
+ return [Object.freeze({ kind: "source-path", sourcePath: hit.path })];
383
+ }
384
+ return [];
385
+ }));
386
+ const exactContentLocators = hit.line !== undefined
387
+ && exactEvidence?.matches.some(({ field }) => field === "content") === true
388
+ && (exactEvidence.identity || qmdEvidence.length === 0)
389
+ ? [Object.freeze({ kind: "line", line: hit.line })]
390
+ : [];
391
+ const sameDocumentLocators = [...explicitLocators, ...exactContentLocators];
392
+ const sameDocument = sameDocumentLocators.map((locator) => Object.freeze({
393
+ targetDocumentId: hit.id,
394
+ evidenceDocumentId: hit.id,
395
+ sourcePath: hit.path,
396
+ locator,
397
+ }));
398
+ const qmd = qmdEvidence.flatMap((evidence) => evidence.line === undefined || evidence.path === undefined
399
+ ? []
400
+ : [Object.freeze({
401
+ targetDocumentId: hit.id,
402
+ evidenceDocumentId: hit.id,
403
+ sourcePath: evidence.path,
404
+ locator: Object.freeze({ kind: "line", line: evidence.line }),
405
+ })]);
406
+ const unique = [...new Map([...sameDocument, ...qmd].map((provenance) =>
407
+ [JSON.stringify(provenance), provenance])).values()];
408
+ return Object.freeze(unique);
409
+ }
410
+
411
+ function statusFromDiagnostics(
412
+ diagnostics: readonly EvaluationDiagnostic[],
413
+ hits: readonly EvaluationRawHit[],
414
+ ): EvaluationRetrieverResult["status"] {
415
+ if (
416
+ hits.length === 0
417
+ && diagnostics.length > 0
418
+ && diagnostics.every(({ status }) => status === "unavailable")
419
+ ) return "unavailable";
420
+ return diagnostics.some(({ status }) => status !== "ready") ? "degraded" : "ready";
421
+ }
422
+
423
+ function textResult(result: KnowledgeBaseSearchResult): EvaluationRetrieverResult {
424
+ const hits = Object.freeze(result.results.map((hit): EvaluationRawHit => {
425
+ const provenance = textHitProvenance(hit);
426
+ return Object.freeze({
427
+ documentId: hit.id,
428
+ rank: hit.rank,
429
+ score: hit.score,
430
+ evidence: evidenceSnapshot({
431
+ mode: result.mode,
432
+ path: hit.path,
433
+ title: hit.title,
434
+ identity: hit.identity,
435
+ ...(hit.line === undefined ? {} : { line: hit.line }),
436
+ snippet: hit.snippet,
437
+ evidence: hit.evidence,
438
+ contributions: hit.contributions,
439
+ ...(provenance.length === 0 ? {} : { provenance }),
440
+ }),
441
+ });
442
+ }));
443
+ const diagnostics = Object.freeze(result.diagnostics.lanes.map((lane): EvaluationDiagnostic =>
444
+ Object.freeze({
445
+ lane: lane.lane,
446
+ status: lane.status,
447
+ ...(lane.message === undefined
448
+ ? {}
449
+ : { message: diagnosticMessage([lane.message]) ?? "Search diagnostic unavailable." }),
450
+ })));
451
+ const laneResults = new Map(result.diagnostics.lanes.map(({ lane, results }) => [lane, results]));
452
+ return Object.freeze({
453
+ status: statusFromDiagnostics(diagnostics, hits),
454
+ hits,
455
+ diagnostics,
456
+ timings: Object.freeze({ searchMs: result.diagnostics.elapsedMs }),
457
+ resources: Object.freeze({
458
+ noteCount: result.diagnostics.notes,
459
+ resultCount: result.results.length,
460
+ partial: Number(result.partial),
461
+ exactResultCount: laneResults.get("exact") ?? 0,
462
+ qmdResultCount: laneResults.get("qmd") ?? 0,
463
+ ...(result.diagnostics.queryEmbedding == null
464
+ ? {}
465
+ : {
466
+ queryEmbeddingCalls: result.diagnostics.queryEmbedding.calls,
467
+ queryEmbeddingInputTokens: result.diagnostics.queryEmbedding.inputTokens,
468
+ queryEmbeddingDurationMs: result.diagnostics.queryEmbedding.durationMs,
469
+ }),
470
+ }),
471
+ });
472
+ }
473
+
474
+ function stableHits(
475
+ candidates: readonly {
476
+ readonly documentId: string;
477
+ readonly score?: number;
478
+ readonly evidence: unknown;
479
+ }[],
480
+ limit: number,
481
+ ): readonly EvaluationRawHit[] {
482
+ const unique = new Map<string, typeof candidates[number]>();
483
+ for (const candidate of candidates) {
484
+ if (!unique.has(candidate.documentId)) unique.set(candidate.documentId, candidate);
485
+ }
486
+ return Object.freeze([...unique.values()].slice(0, limit).map((candidate, index) =>
487
+ Object.freeze({
488
+ documentId: candidate.documentId,
489
+ rank: index + 1,
490
+ ...(candidate.score === undefined ? {} : { score: candidate.score }),
491
+ evidence: evidenceSnapshot(candidate.evidence),
492
+ })));
493
+ }
494
+
495
+ function metadataRetriever(
496
+ session: KnowledgeBaseSession,
497
+ now: () => number,
498
+ ): EvaluationRetriever {
499
+ return Object.freeze({
500
+ id: "metadata",
501
+ retrieve: ({ query, limit, signal }) => {
502
+ if (query.inputs.metadata === undefined) {
503
+ return Promise.resolve(unavailable("metadata", "query.inputs.metadata", session.noteCount));
504
+ }
505
+ throwIfAborted(signal);
506
+ const startedAt = now();
507
+ const rows = session.list({
508
+ filters: query.inputs.metadata.filters,
509
+ tags: query.inputs.metadata.tags,
510
+ limit,
511
+ });
512
+ throwIfAborted(signal);
513
+ const provenanceFields = [
514
+ ...query.inputs.metadata.filters.map(({ path }) => path.split(".")[0]),
515
+ ...(query.inputs.metadata.tags.length === 0 ? [] : ["tags"]),
516
+ ].filter((field): field is string => field !== undefined && field !== "");
517
+ const provenanceFieldList = [...new Set(provenanceFields)];
518
+ const hits = Object.freeze(rows.map((row, index): EvaluationRawHit => Object.freeze({
519
+ documentId: row.id,
520
+ rank: index + 1,
521
+ evidence: evidenceSnapshot({
522
+ ...row,
523
+ ...(provenanceFieldList.length === 0
524
+ ? {}
525
+ : {
526
+ provenance: provenanceFieldList.map((field) => ({
527
+ targetDocumentId: row.id,
528
+ evidenceDocumentId: row.id,
529
+ sourcePath: row.path,
530
+ locator: { kind: "frontmatter-field", field },
531
+ })),
532
+ }),
533
+ }),
534
+ })));
535
+ return Promise.resolve(Object.freeze({
536
+ status: "ready",
537
+ hits,
538
+ diagnostics: Object.freeze([Object.freeze({
539
+ lane: "metadata",
540
+ status: "ready",
541
+ })]),
542
+ timings: Object.freeze({ listMs: elapsed(startedAt, now) }),
543
+ resources: Object.freeze({ noteCount: session.noteCount, resultCount: rows.length }),
544
+ }));
545
+ },
546
+ });
547
+ }
548
+
549
+ function graphRetriever(
550
+ session: KnowledgeBaseSession,
551
+ now: () => number,
552
+ ): EvaluationRetriever {
553
+ return Object.freeze({
554
+ id: "graph",
555
+ retrieve: ({ query, limit, signal }) => {
556
+ const input = query.inputs.graph;
557
+ if (input === undefined) {
558
+ return Promise.resolve(unavailable("graph", "query.inputs.graph", session.noteCount));
559
+ }
560
+ throwIfAborted(signal);
561
+ const startedAt = now();
562
+ type GraphEvidenceProvenance = Readonly<{
563
+ readonly targetDocumentId: string;
564
+ readonly evidenceDocumentId: string;
565
+ readonly sourcePath: string;
566
+ readonly locator: Readonly<{ readonly kind: "line"; readonly line: number }>;
567
+ }>;
568
+ const matches = new Map<string, {
569
+ readonly documentId: string;
570
+ readonly distance: number;
571
+ readonly seedIndex: number;
572
+ readonly nodeIndex: number;
573
+ readonly evidence: Array<Readonly<{
574
+ readonly seed: string;
575
+ readonly node: unknown;
576
+ readonly connections: readonly unknown[];
577
+ readonly provenance?: readonly GraphEvidenceProvenance[];
578
+ }>>;
579
+ }>();
580
+ const diagnostics: EvaluationDiagnostic[] = [];
581
+ let edges = 0;
582
+ let relations = 0;
583
+ for (const [seedIndex, seed] of input.seeds.entries()) {
584
+ const neighborhood = session.links(seed, {
585
+ direction: "both",
586
+ depth: input.depth,
587
+ limit,
588
+ });
589
+ edges += neighborhood.edges.length;
590
+ relations += neighborhood.relations.length;
591
+ diagnostics.push(Object.freeze({
592
+ lane: "graph",
593
+ status: neighborhood.truncated ? "degraded" : "ready",
594
+ ...(neighborhood.truncated
595
+ ? { message: `Graph traversal from ${JSON.stringify(seed)} reached its result limit.` }
596
+ : {}),
597
+ }));
598
+ const nodeIdByPath = new Map(neighborhood.nodes.map((node) => [node.path, node.id]));
599
+ for (const [nodeIndex, node] of neighborhood.nodes.entries()) {
600
+ const links = neighborhood.edges.filter((edge) =>
601
+ edge.source === node.path || edge.target === node.path);
602
+ const authoredRelations = neighborhood.relations.filter((relation) =>
603
+ relation.source === node.id || relation.target === node.id);
604
+ const connections = Object.freeze([
605
+ ...links.map((edge) => Object.freeze({ kind: "link" as const, edge })),
606
+ ...authoredRelations.map((relation) => Object.freeze({
607
+ kind: "relation" as const,
608
+ relation,
609
+ })),
610
+ ]);
611
+ const provenance = Object.freeze([...new Map([
612
+ ...links.flatMap((link): readonly GraphEvidenceProvenance[] => {
613
+ const evidenceDocumentId = nodeIdByPath.get(link.source);
614
+ return evidenceDocumentId === undefined ? [] : [Object.freeze({
615
+ targetDocumentId: node.id,
616
+ evidenceDocumentId,
617
+ sourcePath: link.source,
618
+ locator: Object.freeze({ kind: "line" as const, line: link.line }),
619
+ })];
620
+ }),
621
+ ...authoredRelations.map((relation): GraphEvidenceProvenance => Object.freeze({
622
+ targetDocumentId: node.id,
623
+ evidenceDocumentId: relation.source,
624
+ sourcePath: relation.provenance.source,
625
+ locator: Object.freeze({
626
+ kind: "line" as const,
627
+ line: relation.provenance.line,
628
+ }),
629
+ })),
630
+ ].map((candidate) => [JSON.stringify(candidate), candidate])).values()]);
631
+ const raw = Object.freeze({
632
+ seed,
633
+ node,
634
+ connections,
635
+ ...(provenance.length === 0 ? {} : { provenance }),
636
+ });
637
+ const existing = matches.get(node.id);
638
+ if (existing === undefined) {
639
+ matches.set(node.id, {
640
+ documentId: node.id,
641
+ distance: node.distance,
642
+ seedIndex,
643
+ nodeIndex,
644
+ evidence: [raw],
645
+ });
646
+ } else {
647
+ existing.evidence.push(raw);
648
+ }
649
+ }
650
+ }
651
+ const candidates = [...matches.values()].toSorted((left, right) =>
652
+ left.distance - right.distance
653
+ || left.seedIndex - right.seedIndex
654
+ || left.nodeIndex - right.nodeIndex
655
+ || left.documentId.localeCompare(right.documentId));
656
+ const hits = stableHits(candidates.map((candidate) => ({
657
+ documentId: candidate.documentId,
658
+ score: 1 / (candidate.distance + 1),
659
+ evidence: {
660
+ neighborhoods: candidate.evidence,
661
+ ...(() => {
662
+ const provenance = [...new Map(candidate.evidence
663
+ .flatMap((entry) => entry.provenance ?? [])
664
+ .map((entry) => [JSON.stringify(entry), entry])).values()];
665
+ return provenance.length === 0 ? {} : { provenance: Object.freeze(provenance) };
666
+ })(),
667
+ },
668
+ })), limit);
669
+ throwIfAborted(signal);
670
+ return Promise.resolve(Object.freeze({
671
+ status: statusFromDiagnostics(diagnostics, hits),
672
+ hits,
673
+ diagnostics: Object.freeze(diagnostics),
674
+ timings: Object.freeze({ linksMs: elapsed(startedAt, now) }),
675
+ resources: Object.freeze({
676
+ noteCount: session.noteCount,
677
+ seedCount: input.seeds.length,
678
+ resultCount: hits.length,
679
+ edgeCount: edges,
680
+ relationCount: relations,
681
+ }),
682
+ }));
683
+ },
684
+ });
685
+ }
686
+
687
+ function pathContextDiagnostics(
688
+ inspection: AgentContextRepositoryInspection,
689
+ memory: RepositoryMemoryContext,
690
+ ): readonly EvaluationDiagnostic[] {
691
+ const inspectionMessage = diagnosticMessage(
692
+ inspection.issues.map(({ message }) => message),
693
+ );
694
+ const memoryMessages = [
695
+ ...memory.invalidRecords.details.flatMap(({ issues }) => issues),
696
+ ...memory.advisories.details.map(({ message }) => message),
697
+ ...(repositoryMemoryGroupKeys.some((key) => memory.groups[key].truncated)
698
+ ? ["Repository-memory results were truncated by the evaluation limit."]
699
+ : []),
700
+ ];
701
+ const memoryMessage = diagnosticMessage(memoryMessages);
702
+ return Object.freeze([
703
+ Object.freeze({
704
+ lane: "agent-context",
705
+ status: inspection.issues.length === 0 ? "ready" : "degraded",
706
+ ...(inspectionMessage === undefined ? {} : { message: inspectionMessage }),
707
+ }),
708
+ Object.freeze({
709
+ lane: "repository-memory",
710
+ status: memoryMessages.length === 0 ? "ready" : "degraded",
711
+ ...(memoryMessage === undefined ? {} : { message: memoryMessage }),
712
+ }),
713
+ ]);
714
+ }
715
+
716
+ function pathContextRetriever(
717
+ session: KnowledgeBaseSession,
718
+ snapshot: VaultSnapshot,
719
+ repository: string,
720
+ inspectContext: typeof inspectAgentContextRepository,
721
+ buildMemory: typeof buildRepositoryMemoryContext,
722
+ now: () => number,
723
+ ): EvaluationRetriever {
724
+ return Object.freeze({
725
+ id: "path-context",
726
+ retrieve: async ({ query, limit, signal }) => {
727
+ const input = query.inputs.context;
728
+ if (input === undefined) {
729
+ return unavailable("path-context", "query.inputs.context", session.noteCount);
730
+ }
731
+ throwIfAborted(signal);
732
+ const inspectionStartedAt = now();
733
+ const inspection = await inspectContext(snapshot.notes, {
734
+ repositoryRoot: repository,
735
+ target: input.repositoryPath,
736
+ targetKind: "auto",
737
+ });
738
+ const inspectionMs = elapsed(inspectionStartedAt, now);
739
+ throwIfAborted(signal);
740
+ const memoryStartedAt = now();
741
+ const memory = await buildMemory(snapshot.notes, {
742
+ repositoryRoot: repository,
743
+ target: input.repositoryPath,
744
+ groupLimit: Math.min(limit, MAX_REPOSITORY_MEMORY_GROUP_LIMIT),
745
+ detailLimit: Math.min(limit, MAX_REPOSITORY_MEMORY_DETAIL_LIMIT),
746
+ });
747
+ const memoryMs = elapsed(memoryStartedAt, now);
748
+ throwIfAborted(signal);
749
+
750
+ const candidates = [
751
+ ...inspection.matchingContexts.map((hub) => ({
752
+ documentId: hub.note.id,
753
+ evidence: {
754
+ kind: "agent-context",
755
+ path: hub.note.path,
756
+ rawScope: hub.rawScope,
757
+ scope: hub.scope,
758
+ canonicalId: hub.canonicalId,
759
+ canonicalPath: hub.canonicalPath,
760
+ guidePath: hub.guidePath,
761
+ canonical: hub.canonical,
762
+ reciprocal: hub.reciprocal,
763
+ valid: hub.valid,
764
+ provenance: [{
765
+ targetDocumentId: hub.note.id,
766
+ evidenceDocumentId: hub.note.id,
767
+ sourcePath: hub.note.path,
768
+ locator: { kind: "frontmatter-field", field: "scope" },
769
+ }],
770
+ },
771
+ })),
772
+ ...repositoryMemoryGroupKeys.flatMap((group) =>
773
+ memory.groups[group].records.map((record) => ({
774
+ documentId: record.id,
775
+ evidence: {
776
+ kind: "repository-memory",
777
+ group,
778
+ record,
779
+ provenance: [{
780
+ targetDocumentId: record.id,
781
+ evidenceDocumentId: record.id,
782
+ sourcePath: record.path,
783
+ locator: { kind: "frontmatter-field", field: "repository_scopes" },
784
+ }],
785
+ },
786
+ }))),
787
+ ];
788
+ const hits = stableHits(candidates, limit);
789
+ const diagnostics = pathContextDiagnostics(inspection, memory);
790
+ return Object.freeze({
791
+ status: statusFromDiagnostics(diagnostics, hits),
792
+ hits,
793
+ diagnostics,
794
+ timings: Object.freeze({ inspectionMs, memoryMs }),
795
+ resources: Object.freeze({
796
+ noteCount: session.noteCount,
797
+ resultCount: hits.length,
798
+ matchingContextCount: inspection.matchingContexts.length,
799
+ inheritedGuideCount: inspection.inheritedGuides.length,
800
+ contextIssueCount: inspection.issues.length,
801
+ memoryMatchedCount: memory.counts.matched,
802
+ memoryReturnedCount: memory.counts.returned,
803
+ memoryInvalidCount: memory.counts.invalid,
804
+ memoryAdvisoryCount: memory.counts.advisories,
805
+ }),
806
+ });
807
+ },
808
+ });
809
+ }
810
+
811
+ function gitDiagnostic(
812
+ lane: "git-search" | "git-history",
813
+ result: GitHistorySearchResult | GitHistoryForNotesResult,
814
+ ): EvaluationDiagnostic {
815
+ if (result.status === "unavailable") {
816
+ return Object.freeze({
817
+ lane,
818
+ status: "unavailable",
819
+ message: diagnosticMessage([result.reason]) ?? "Git retrieval is unavailable.",
820
+ });
821
+ }
822
+ const limited = result.limitedCommits ?? [];
823
+ return Object.freeze({
824
+ lane,
825
+ status: limited.length === 0 ? "ready" : "degraded",
826
+ ...(limited.length === 0
827
+ ? {}
828
+ : { message: `${limited.length} oversized Git commit record(s) have limited evidence.` }),
829
+ });
830
+ }
831
+
832
+ function gitRetriever(
833
+ session: KnowledgeBaseSession,
834
+ frozenCommit: string,
835
+ now: () => number,
836
+ ): EvaluationRetriever {
837
+ return Object.freeze({
838
+ id: "git",
839
+ retrieve: async ({ query, limit, signal }) => {
840
+ const input = query.inputs.history;
841
+ if (input === undefined) {
842
+ return unavailable("git", "query.inputs.history", session.noteCount);
843
+ }
844
+ throwIfAborted(signal);
845
+ const searchStartedAt = now();
846
+ const searched = await session.searchHistory({
847
+ query: input.query,
848
+ limit: Math.min(limit, 100),
849
+ });
850
+ const searchMs = elapsed(searchStartedAt, now);
851
+ throwIfAborted(signal);
852
+ const historyStartedAt = now();
853
+ const history = await session.history(input.noteIds);
854
+ const historyMs = elapsed(historyStartedAt, now);
855
+ throwIfAborted(signal);
856
+
857
+ for (const result of [searched, history]) {
858
+ if (result.status === "ready" && result.head !== frozenCommit) {
859
+ throw new FrozenEvaluationSnapshotError(
860
+ `Git retrieval observed HEAD ${result.head}, expected frozen commit ${frozenCommit}.`,
861
+ );
862
+ }
863
+ }
864
+
865
+ const searchedById = new Map<string, GitHistorySearchHit>(
866
+ searched.status === "ready"
867
+ ? searched.hits.map((hit) => [hit.id, hit])
868
+ : [],
869
+ );
870
+ const historyById = new Map<string, GitHistoryNoteProvenance>(
871
+ history.status === "ready"
872
+ ? history.notes.map((note) => [note.id, note])
873
+ : [],
874
+ );
875
+ const orderedIds = [
876
+ ...input.noteIds.filter((id) => historyById.has(id)),
877
+ ...(searched.status === "ready" ? searched.hits.map(({ id }) => id) : []),
878
+ ];
879
+ const candidates = orderedIds.map((documentId) => {
880
+ const searchedHit = searchedById.get(documentId);
881
+ const provenance = historyById.get(documentId);
882
+ return {
883
+ documentId,
884
+ ...(searchedHit === undefined ? {} : { score: searchedHit.score }),
885
+ evidence: {
886
+ ...(provenance === undefined ? {} : { history: provenance }),
887
+ ...(searchedHit === undefined ? {} : { search: searchedHit }),
888
+ },
889
+ };
890
+ });
891
+ const hits = stableHits(candidates, limit);
892
+ const diagnostics = Object.freeze([
893
+ gitDiagnostic("git-search", searched),
894
+ gitDiagnostic("git-history", history),
895
+ ]);
896
+ const searchedLimited = searched.status === "ready" ? searched.limitedCommits?.length ?? 0 : 0;
897
+ const historyLimited = history.status === "ready" ? history.limitedCommits?.length ?? 0 : 0;
898
+ return Object.freeze({
899
+ status: statusFromDiagnostics(diagnostics, hits),
900
+ hits,
901
+ diagnostics,
902
+ timings: Object.freeze({ searchHistoryMs: searchMs, historyMs }),
903
+ resources: Object.freeze({
904
+ noteCount: session.noteCount,
905
+ resultCount: hits.length,
906
+ searchResultCount: searched.status === "ready" ? searched.hits.length : 0,
907
+ historyResultCount: history.status === "ready" ? history.notes.length : 0,
908
+ limitedCommitCount: searchedLimited + historyLimited,
909
+ }),
910
+ });
911
+ },
912
+ });
913
+ }
914
+
915
+ function throwIfAborted(signal: AbortSignal): void {
916
+ if (!signal.aborted) return;
917
+ if (signal.reason instanceof Error) throw signal.reason;
918
+ throw new Error("Knowledge-base evaluation retrieval was aborted.");
919
+ }
920
+
921
+ function textRetriever(
922
+ id: "exact" | "keyword" | "semantic" | "hybrid",
923
+ session: KnowledgeBaseSession,
924
+ ): EvaluationRetriever {
925
+ return Object.freeze({
926
+ id,
927
+ retrieve: async ({ query, limit, signal }) => {
928
+ const text = query.inputs.text;
929
+ if (text === undefined) return unavailable(id, "query.inputs.text", session.noteCount);
930
+ throwIfAborted(signal);
931
+ const result = await session.search({
932
+ query: text,
933
+ mode: id satisfies KnowledgeBaseSearchMode,
934
+ limit: Math.min(limit, MAX_SEARCH_RESULTS),
935
+ graph: false,
936
+ history: false,
937
+ });
938
+ throwIfAborted(signal);
939
+ return textResult(result);
940
+ },
941
+ });
942
+ }
943
+
944
+ function knowledgeBaseDependencies(
945
+ options: OpenKnowledgeBaseEvaluationOptions,
946
+ snapshot: VaultSnapshot,
947
+ ): KnowledgeBaseDependencies {
948
+ const git = options.git === undefined && options.runGit === undefined
949
+ ? undefined
950
+ : {
951
+ ...options.git,
952
+ ...(options.git?.runGit !== undefined || options.runGit === undefined
953
+ ? {}
954
+ : { runGit: options.runGit }),
955
+ };
956
+ return {
957
+ scanVault: () => Promise.resolve(snapshot),
958
+ ...(options.semantic === undefined ? {} : { semantic: options.semantic }),
959
+ ...(options.openSemanticSearchSession === undefined
960
+ ? {}
961
+ : { openSemanticSearchSession: options.openSemanticSearchSession }),
962
+ ...(git === undefined ? {} : { git }),
963
+ ...(options.indexGitHistory === undefined
964
+ ? {}
965
+ : { indexGitHistory: options.indexGitHistory }),
966
+ };
967
+ }
968
+
969
+ /** Open all built-in evaluation lanes over one verified, immutable vault scan. */
970
+ export async function openKnowledgeBaseEvaluation(
971
+ options: OpenKnowledgeBaseEvaluationOptions,
972
+ ): Promise<KnowledgeBaseEvaluation> {
973
+ const verified = await verifyFrozenEvaluationSnapshot(options);
974
+ const scanner = options.scanVault ?? scanVault;
975
+ const snapshot = await scanner(verified.root, { mentionScope: false });
976
+ const open = options.openKnowledgeBase ?? openKnowledgeBase;
977
+ const session = await open(
978
+ {
979
+ root: verified.root,
980
+ repository: verified.repository,
981
+ ...(options.database === undefined ? {} : { database: options.database }),
982
+ ...(options.embeddingModelFile === undefined
983
+ ? {}
984
+ : { embeddingModelFile: options.embeddingModelFile }),
985
+ ...(options.embeddingModelLease === undefined
986
+ ? {}
987
+ : { embeddingModelLease: options.embeddingModelLease }),
988
+ ...(options.requireStoreLocalVectorBoundary === undefined
989
+ ? {}
990
+ : {
991
+ requireStoreLocalVectorBoundary:
992
+ options.requireStoreLocalVectorBoundary,
993
+ }),
994
+ },
995
+ knowledgeBaseDependencies(options, snapshot),
996
+ );
997
+ const now = options.now ?? performance.now.bind(performance);
998
+ const inspectContext = options.inspectAgentContextRepository ?? inspectAgentContextRepository;
999
+ const buildMemory = options.buildRepositoryMemoryContext ?? buildRepositoryMemoryContext;
1000
+ const retrievers = Object.freeze([
1001
+ textRetriever("exact", session),
1002
+ textRetriever("keyword", session),
1003
+ textRetriever("semantic", session),
1004
+ textRetriever("hybrid", session),
1005
+ metadataRetriever(session, now),
1006
+ graphRetriever(session, now),
1007
+ pathContextRetriever(
1008
+ session,
1009
+ snapshot,
1010
+ verified.repository,
1011
+ inspectContext,
1012
+ buildMemory,
1013
+ now,
1014
+ ),
1015
+ gitRetriever(session, verified.repositoryCommit, now),
1016
+ ] satisfies readonly EvaluationRetriever[]);
1017
+ return Object.freeze({ retrievers, close: session.close });
1018
+ }