@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,2728 @@
1
+ // @bun
2
+ import {
3
+ fuseRankedCandidates,
4
+ validateSearchQuery
5
+ } from "./index-cv6fh7z5.js";
6
+ import {
7
+ MAX_ANALYZED_NOTES,
8
+ analyzeVault,
9
+ isCanonicalNoteId,
10
+ lookupNote,
11
+ normalizeVaultPath,
12
+ parseNote,
13
+ renderCatalog,
14
+ replaceCatalog
15
+ } from "./index-cxfrakt7.js";
16
+
17
+ // src/semantic.ts
18
+ import { createHash as createHash2 } from "crypto";
19
+ import { constants as constants3 } from "fs";
20
+ import {
21
+ chmod,
22
+ lstat as lstat3,
23
+ mkdir as mkdir2,
24
+ mkdtemp,
25
+ open as open3,
26
+ realpath as realpath3,
27
+ rm as rm3,
28
+ stat
29
+ } from "fs/promises";
30
+ import { homedir, tmpdir } from "os";
31
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, relative as relative3, resolve as resolve3, sep as sep3 } from "path";
32
+
33
+ // src/semantic-runtime.ts
34
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
35
+ import {
36
+ constants as constants2
37
+ } from "fs";
38
+ import {
39
+ lstat as lstat2,
40
+ mkdir,
41
+ open as open2,
42
+ opendir,
43
+ realpath as realpath2,
44
+ rename as rename2,
45
+ rm as rm2,
46
+ writeFile
47
+ } from "fs/promises";
48
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
49
+
50
+ // src/vault.ts
51
+ import { randomUUID } from "crypto";
52
+ import { constants } from "fs";
53
+ import {
54
+ lstat,
55
+ open,
56
+ readdir,
57
+ realpath,
58
+ rename,
59
+ rm
60
+ } from "fs/promises";
61
+ import { basename, dirname, join, relative, resolve, sep } from "path";
62
+ var MAX_SCANNED_NOTES = MAX_ANALYZED_NOTES;
63
+ var MAX_NOTE_UTF8_BYTES = 16 * 1024 * 1024;
64
+ var MAX_VAULT_UTF8_BYTES = 256 * 1024 * 1024;
65
+
66
+ class VaultScanBudgetError extends RangeError {
67
+ kind;
68
+ limit;
69
+ constructor(kind, limit, message) {
70
+ super(message);
71
+ this.name = "VaultScanBudgetError";
72
+ this.kind = kind;
73
+ this.limit = limit;
74
+ }
75
+ }
76
+ var defaultIgnoredDirectories = new Set([
77
+ ".git",
78
+ ".next",
79
+ ".turbo",
80
+ ".vercel",
81
+ "coverage",
82
+ "dist",
83
+ "node_modules"
84
+ ]);
85
+ async function markdownFiles(directory, ignoredDirectories = defaultIgnoredDirectories) {
86
+ const files = [];
87
+ const entries = await readdir(directory, { withFileTypes: true });
88
+ for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
89
+ if (entry.name.startsWith("."))
90
+ continue;
91
+ const entryPath = join(directory, entry.name);
92
+ if (entry.isDirectory()) {
93
+ if (ignoredDirectories.has(entry.name))
94
+ continue;
95
+ files.push(...await markdownFiles(entryPath, ignoredDirectories));
96
+ continue;
97
+ }
98
+ if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "AGENTS.md") {
99
+ files.push(entryPath);
100
+ }
101
+ }
102
+ return files;
103
+ }
104
+ function checkedScanLimit(value, hardMaximum, option) {
105
+ const limit = value ?? hardMaximum;
106
+ if (!Number.isSafeInteger(limit) || limit < 0 || limit > hardMaximum) {
107
+ throw new RangeError(`${option} must be a safe integer from 0 through ${hardMaximum}.`);
108
+ }
109
+ return limit;
110
+ }
111
+ function normalizedRawNoteId(rawId) {
112
+ return normalizeVaultPath(rawId).normalize("NFC");
113
+ }
114
+ function validateScannedNotePaths(root, paths) {
115
+ const files = paths.map((absolutePath) => {
116
+ const vaultPath = relative(root, absolutePath).split(sep).join("/");
117
+ return {
118
+ absolutePath,
119
+ vaultPath,
120
+ rawId: vaultPath.slice(0, -3)
121
+ };
122
+ });
123
+ const pathByNormalizedId = new Map;
124
+ for (const file of files) {
125
+ const normalizedId = normalizedRawNoteId(file.rawId);
126
+ const collision = pathByNormalizedId.get(normalizedId);
127
+ if (collision !== undefined && collision !== file.vaultPath) {
128
+ throw new Error(`Vault note paths ${JSON.stringify(collision)} and ` + `${JSON.stringify(file.vaultPath)} normalize to the same note ID ` + `${JSON.stringify(normalizedId)}.`);
129
+ }
130
+ pathByNormalizedId.set(normalizedId, file.vaultPath);
131
+ }
132
+ for (const file of files) {
133
+ if (isCanonicalNoteId(file.rawId))
134
+ continue;
135
+ if (file.rawId !== file.rawId.normalize("NFC")) {
136
+ throw new Error(`Vault note path ${JSON.stringify(file.vaultPath)} is not NFC; ` + `its extensionless note ID must be exactly ` + `${JSON.stringify(file.rawId.normalize("NFC"))}.`);
137
+ }
138
+ if (file.rawId.includes("\\")) {
139
+ throw new Error(`Vault note path ${JSON.stringify(file.vaultPath)} contains a backslash; ` + "note IDs must use exact vault-root directory separators.");
140
+ }
141
+ throw new Error(`Vault note path ${JSON.stringify(file.vaultPath)} must have an exact ` + "canonical extensionless vault-root note ID.");
142
+ }
143
+ return files;
144
+ }
145
+ function assertScannableNoteFile(vaultPath, metadata) {
146
+ if (metadata.isSymbolicLink()) {
147
+ throw new Error(`Vault note ${JSON.stringify(vaultPath)} must not be a symbolic link.`);
148
+ }
149
+ if (!metadata.isFile()) {
150
+ throw new Error(`Vault note ${JSON.stringify(vaultPath)} must be a regular file.`);
151
+ }
152
+ if (metadata.nlink !== 1n) {
153
+ throw new Error(`Vault note ${JSON.stringify(vaultPath)} must not be hard-linked.`);
154
+ }
155
+ }
156
+ function noteBytesError(vaultPath, limit) {
157
+ return new VaultScanBudgetError("note-bytes", limit, `Vault note ${JSON.stringify(vaultPath)} exceeds the ${limit}-byte UTF-8 limit.`);
158
+ }
159
+ function totalBytesError(limit) {
160
+ return new VaultScanBudgetError("total-bytes", limit, `Vault scan exceeds the ${limit}-byte cumulative UTF-8 limit.`);
161
+ }
162
+ async function readBoundedNote(handle, vaultPath, maxNoteBytes, remainingTotalBytes, maxTotalBytes) {
163
+ const chunks = [];
164
+ let bytes = 0;
165
+ for (;; ) {
166
+ const remaining = Math.min(maxNoteBytes - bytes, remainingTotalBytes - bytes);
167
+ const buffer = new Uint8Array(Math.min(64 * 1024, Math.max(1, remaining + 1)));
168
+ const result = await handle.read(buffer, 0, buffer.byteLength, null);
169
+ if (result.bytesRead === 0)
170
+ break;
171
+ bytes += result.bytesRead;
172
+ if (bytes > maxNoteBytes)
173
+ throw noteBytesError(vaultPath, maxNoteBytes);
174
+ if (bytes > remainingTotalBytes) {
175
+ throw totalBytesError(maxTotalBytes);
176
+ }
177
+ chunks.push(buffer.slice(0, result.bytesRead));
178
+ }
179
+ const joined = new Uint8Array(bytes);
180
+ let offset = 0;
181
+ for (const chunk of chunks) {
182
+ joined.set(chunk, offset);
183
+ offset += chunk.byteLength;
184
+ }
185
+ try {
186
+ return {
187
+ content: new TextDecoder("utf-8", { fatal: true }).decode(joined),
188
+ bytes
189
+ };
190
+ } catch (error) {
191
+ throw new Error(`Vault note ${JSON.stringify(vaultPath)} is not valid UTF-8.`, { cause: error });
192
+ }
193
+ }
194
+ async function readVaultNotes(root, ignoredDirectories = defaultIgnoredDirectories, limits = {}) {
195
+ const maxNotes = checkedScanLimit(limits.maxNotes, MAX_SCANNED_NOTES, "maxNotes");
196
+ const maxNoteBytes = checkedScanLimit(limits.maxNoteBytes, MAX_NOTE_UTF8_BYTES, "maxNoteBytes");
197
+ const maxTotalBytes = checkedScanLimit(limits.maxTotalBytes, MAX_VAULT_UTF8_BYTES, "maxTotalBytes");
198
+ const paths = await markdownFiles(root, ignoredDirectories);
199
+ if (paths.length > maxNotes) {
200
+ throw new VaultScanBudgetError("notes", maxNotes, `Vault scan exceeds the ${maxNotes} Markdown note limit.`);
201
+ }
202
+ const files = validateScannedNotePaths(root, paths);
203
+ let declaredTotal = 0n;
204
+ const preflight = [];
205
+ for (const file of files) {
206
+ const metadata = await lstat(file.absolutePath, { bigint: true });
207
+ assertScannableNoteFile(file.vaultPath, metadata);
208
+ if (metadata.size > BigInt(maxNoteBytes)) {
209
+ throw noteBytesError(file.vaultPath, maxNoteBytes);
210
+ }
211
+ declaredTotal += metadata.size;
212
+ if (declaredTotal > BigInt(maxTotalBytes)) {
213
+ throw totalBytesError(maxTotalBytes);
214
+ }
215
+ preflight.push({
216
+ ...file,
217
+ device: metadata.dev,
218
+ inode: metadata.ino
219
+ });
220
+ }
221
+ const notes = [];
222
+ let observedTotal = 0;
223
+ for (const file of preflight) {
224
+ const handle = await open(file.absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
225
+ try {
226
+ const beforeRead = await handle.stat({ bigint: true });
227
+ assertScannableNoteFile(file.vaultPath, beforeRead);
228
+ if (beforeRead.dev !== file.device || beforeRead.ino !== file.inode) {
229
+ throw new Error(`Vault note ${JSON.stringify(file.vaultPath)} changed during scan; retry.`);
230
+ }
231
+ if (beforeRead.size > BigInt(maxNoteBytes)) {
232
+ throw noteBytesError(file.vaultPath, maxNoteBytes);
233
+ }
234
+ if (BigInt(observedTotal) + beforeRead.size > BigInt(maxTotalBytes)) {
235
+ throw totalBytesError(maxTotalBytes);
236
+ }
237
+ const read = await readBoundedNote(handle, file.vaultPath, maxNoteBytes, maxTotalBytes - observedTotal, maxTotalBytes);
238
+ const afterRead = await handle.stat({ bigint: true });
239
+ if (afterRead.dev !== file.device || afterRead.ino !== file.inode || afterRead.size !== beforeRead.size || afterRead.size !== BigInt(read.bytes)) {
240
+ throw new Error(`Vault note ${JSON.stringify(file.vaultPath)} changed during scan; retry.`);
241
+ }
242
+ observedTotal += read.bytes;
243
+ notes.push(parseNote(file.vaultPath, read.content));
244
+ } finally {
245
+ await handle.close();
246
+ }
247
+ }
248
+ return notes;
249
+ }
250
+ function confined(root, path) {
251
+ const fromRoot = relative(root, path);
252
+ return fromRoot !== "" && fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`);
253
+ }
254
+ async function assertConfinedIndexParents(root, path) {
255
+ if (!confined(root, path))
256
+ throw new Error("The configured index must be a file inside the vault root.");
257
+ const parent = dirname(path);
258
+ const segments = relative(root, parent).split(sep).filter((segment) => segment !== "");
259
+ let current = root;
260
+ for (const segment of segments) {
261
+ current = join(current, segment);
262
+ const metadata = await lstat(current);
263
+ if (metadata.isSymbolicLink()) {
264
+ throw new Error("The configured index path must not traverse a symbolic link.");
265
+ }
266
+ if (!metadata.isDirectory()) {
267
+ throw new Error("Every configured index parent must be a directory.");
268
+ }
269
+ }
270
+ const canonicalParent = await realpath(parent);
271
+ if (!confined(root, join(canonicalParent, basename(path)))) {
272
+ throw new Error("The configured index parent resolves outside the vault root.");
273
+ }
274
+ }
275
+ async function readIndexRevision(root, path, maxNoteBytes = MAX_NOTE_UTF8_BYTES) {
276
+ await assertConfinedIndexParents(root, path);
277
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
278
+ try {
279
+ const metadata = await handle.stat({ bigint: true });
280
+ if (!metadata.isFile())
281
+ throw new Error("The configured index must be a regular file.");
282
+ if (metadata.nlink !== 1n)
283
+ throw new Error("The configured index must not be hard-linked.");
284
+ const canonicalPath = await realpath(path);
285
+ if (!confined(root, canonicalPath)) {
286
+ throw new Error("The configured index resolves outside the vault root.");
287
+ }
288
+ const vaultPath = relative(root, path).split(sep).join("/");
289
+ if (metadata.size > BigInt(maxNoteBytes)) {
290
+ throw noteBytesError(vaultPath, maxNoteBytes);
291
+ }
292
+ const read = await readBoundedNote(handle, vaultPath, maxNoteBytes, maxNoteBytes, maxNoteBytes);
293
+ const afterRead = await handle.stat({ bigint: true });
294
+ if (afterRead.dev !== metadata.dev || afterRead.ino !== metadata.ino || afterRead.size !== metadata.size || afterRead.size !== BigInt(read.bytes)) {
295
+ throw new Error("The configured index changed during scan; retry.");
296
+ }
297
+ return {
298
+ content: read.content,
299
+ device: metadata.dev,
300
+ inode: metadata.ino,
301
+ mode: Number(metadata.mode & 0o777n)
302
+ };
303
+ } finally {
304
+ await handle.close();
305
+ }
306
+ }
307
+ function sameRevision(left, right) {
308
+ return left.device === right.device && left.inode === right.inode && left.content === right.content;
309
+ }
310
+ function parsedCatalogMode(value, source) {
311
+ if (value === undefined)
312
+ return;
313
+ if (value === "managed" || value === "authored")
314
+ return value;
315
+ throw new Error(`${source} must be exactly "managed" or "authored".`);
316
+ }
317
+ function declaredCatalogMode(indexNote) {
318
+ const declaration = Object.entries(indexNote.metadata).find(([name]) => name.toLocaleLowerCase("en-US") === "kb_catalog");
319
+ return parsedCatalogMode(declaration?.[1], `The configured index frontmatter property "kb_catalog"`);
320
+ }
321
+ async function atomicReplace(root, path, content, expected) {
322
+ const beforeWrite = await readIndexRevision(root, path);
323
+ if (!sameRevision(beforeWrite, expected)) {
324
+ throw new Error("The configured index changed during refresh; retry without overwriting the editor's changes.");
325
+ }
326
+ const directory = dirname(path);
327
+ const temporaryPath = join(directory, `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
328
+ await assertConfinedIndexParents(root, path);
329
+ const handle = await open(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, expected.mode);
330
+ let closed = false;
331
+ try {
332
+ await handle.writeFile(content, "utf8");
333
+ await handle.sync();
334
+ await handle.close();
335
+ closed = true;
336
+ const beforeRename = await readIndexRevision(root, path);
337
+ if (!sameRevision(beforeRename, expected)) {
338
+ throw new Error("The configured index changed during refresh; retry without overwriting the editor's changes.");
339
+ }
340
+ await assertConfinedIndexParents(root, path);
341
+ await rename(temporaryPath, path);
342
+ } catch (error) {
343
+ if (!closed)
344
+ await handle.close().catch(() => {
345
+ return;
346
+ });
347
+ await rm(temporaryPath, { force: true }).catch(() => {
348
+ return;
349
+ });
350
+ throw error;
351
+ }
352
+ }
353
+ async function snapshot(rootInput, options, writeIndex) {
354
+ const requestedRoot = resolve(rootInput);
355
+ const root = await realpath(requestedRoot);
356
+ const rootMetadata = await lstat(root);
357
+ if (!rootMetadata.isDirectory())
358
+ throw new Error("The vault root must be a directory.");
359
+ const indexPath = resolve(root, options.index ?? "index.md");
360
+ const relativeIndex = relative(root, indexPath);
361
+ if (!confined(root, indexPath)) {
362
+ throw new Error("The configured index must be a file inside the vault root.");
363
+ }
364
+ if (!indexPath.toLowerCase().endsWith(".md")) {
365
+ throw new Error("The configured index must be a Markdown file.");
366
+ }
367
+ const vaultIndexPath = relativeIndex.split(sep).join("/");
368
+ const catalogNoteId = vaultIndexPath.toLowerCase().endsWith(".md") ? vaultIndexPath.slice(0, -3) : vaultIndexPath;
369
+ const notes = await readVaultNotes(root, options.ignoredDirectories, {
370
+ ...options.maxNotes === undefined ? {} : { maxNotes: options.maxNotes },
371
+ ...options.maxNoteBytes === undefined ? {} : { maxNoteBytes: options.maxNoteBytes },
372
+ ...options.maxTotalBytes === undefined ? {} : { maxTotalBytes: options.maxTotalBytes }
373
+ });
374
+ const indexRevision = await readIndexRevision(root, indexPath, options.maxNoteBytes ?? MAX_NOTE_UTF8_BYTES);
375
+ const currentIndex = indexRevision.content;
376
+ const indexNote = parseNote(vaultIndexPath, currentIndex);
377
+ const catalogMode = parsedCatalogMode(options.catalogMode, "ScanVaultOptions.catalogMode") ?? declaredCatalogMode(indexNote) ?? "managed";
378
+ let index = "authored";
379
+ if (catalogMode === "managed") {
380
+ const expectedIndex = replaceCatalog(currentIndex, renderCatalog(notes, catalogNoteId));
381
+ const stale = currentIndex !== expectedIndex;
382
+ index = stale ? "stale" : "current";
383
+ if (writeIndex && stale) {
384
+ await atomicReplace(root, indexPath, expectedIndex, indexRevision);
385
+ index = "updated";
386
+ const parsed = parseNote(vaultIndexPath, expectedIndex);
387
+ const noteIndex = notes.findIndex((note) => note.path === vaultIndexPath);
388
+ if (noteIndex === -1)
389
+ notes.push(parsed);
390
+ else
391
+ notes[noteIndex] = parsed;
392
+ }
393
+ }
394
+ const mentionScope = options.mentionScope;
395
+ const mentionIds = new Set;
396
+ if (typeof mentionScope === "string") {
397
+ const lookup = lookupNote(notes, mentionScope);
398
+ if (lookup.kind === "found")
399
+ mentionIds.add(lookup.note.id);
400
+ else if (lookup.kind === "ambiguous") {
401
+ for (const note of lookup.candidates)
402
+ mentionIds.add(note.id);
403
+ }
404
+ }
405
+ const mentionScopePredicate = mentionScope === undefined ? undefined : (note) => mentionScope !== false && mentionIds.has(note.id);
406
+ return {
407
+ root,
408
+ indexPath,
409
+ catalogMode,
410
+ index,
411
+ notes,
412
+ analysis: analyzeVault(notes, {
413
+ catalogNoteId,
414
+ ...options.includeInSuggestions === undefined ? {} : { includeInSuggestions: options.includeInSuggestions },
415
+ ...mentionScopePredicate === undefined ? {} : { mentionScope: mentionScopePredicate },
416
+ ...options.maxNotes === undefined ? {} : { maxNotes: options.maxNotes },
417
+ ...options.maxConnectionObservations === undefined ? {} : { maxConnectionObservations: options.maxConnectionObservations },
418
+ ...options.maxMentionPairs === undefined ? {} : { maxMentionPairs: options.maxMentionPairs },
419
+ ...options.maxMentions === undefined ? {} : { maxMentions: options.maxMentions }
420
+ })
421
+ };
422
+ }
423
+ async function scanVault(root = ".", options = {}) {
424
+ return snapshot(root, options, false);
425
+ }
426
+ async function refreshVault(root = ".", options = {}) {
427
+ return snapshot(root, options, true);
428
+ }
429
+
430
+ // src/semantic-runtime.ts
431
+ var LEASE_VERSION = 1;
432
+ var PROJECTION_VERSION = 2;
433
+ var DEFAULT_LEASE_WAIT_MS = 30000;
434
+ var DEFAULT_LEASE_POLL_MS = 25;
435
+ var MAX_LEASE_WAIT_MS = 10 * 60000;
436
+ var MAX_LEASE_POLL_MS = 1000;
437
+ var GENERATION_PREFIX = "generation-";
438
+ var READER_PREFIX = ".reader-";
439
+ var MANIFEST_NAME = "manifest.json";
440
+ var OWNER_NAME = "owner.json";
441
+ var SNAPSHOT_OWNER_NAME = ".hraness-kb-semantic-cache.json";
442
+ var SNAPSHOT_OWNER_VERSION = 1;
443
+ var SNAPSHOT_OWNER_KIND = "@hraness/kb/semantic-projection-cache";
444
+ var MAX_OWNER_BYTES = 4 * 1024;
445
+ var MAX_MANIFEST_BYTES = 64 * 1024 * 1024;
446
+ var MAX_CACHE_DIRECTORY_ENTRIES = 1024;
447
+ var MAX_INDEX_IDENTITY_BYTES = 16 * 1024;
448
+ var MAX_INDEX_IDENTITY_STRING_BYTES = 4 * 1024;
449
+ var MAX_INDEX_IDENTITY_ENTRIES = 64;
450
+ function errorCode(error) {
451
+ if (typeof error !== "object" || error === null || !("code" in error))
452
+ return;
453
+ const code = Reflect.get(error, "code");
454
+ return typeof code === "string" ? code : undefined;
455
+ }
456
+ function checkedLeaseBound(value, fallback, maximum, label) {
457
+ const result = value ?? fallback;
458
+ if (!Number.isSafeInteger(result) || result < 1 || result > maximum) {
459
+ throw new RangeError(`${label} must be an integer from 1 through ${maximum}.`);
460
+ }
461
+ return result;
462
+ }
463
+ function isRecord(value) {
464
+ return typeof value === "object" && value !== null && !Array.isArray(value);
465
+ }
466
+ function checkedIdentityString(value, label) {
467
+ const bytes = Buffer.byteLength(value, "utf8");
468
+ if (value === "" || bytes > MAX_INDEX_IDENTITY_STRING_BYTES) {
469
+ throw new RangeError(`${label} must contain from 1 through ${MAX_INDEX_IDENTITY_STRING_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
470
+ }
471
+ return value;
472
+ }
473
+ function validatedIndexIdentity(identity) {
474
+ if (!Number.isSafeInteger(identity.producer.schema) || identity.producer.schema < 1) {
475
+ throw new RangeError("Semantic index producer schema must be a positive safe integer.");
476
+ }
477
+ if (identity.collection.ignore.length > MAX_INDEX_IDENTITY_ENTRIES || identity.collection.pathContexts.length > MAX_INDEX_IDENTITY_ENTRIES) {
478
+ throw new RangeError(`Semantic index identity collections are limited to ${MAX_INDEX_IDENTITY_ENTRIES} entries.`);
479
+ }
480
+ const validated = {
481
+ producer: {
482
+ package: checkedIdentityString(identity.producer.package, "Semantic index producer package"),
483
+ schema: identity.producer.schema
484
+ },
485
+ indexer: {
486
+ package: checkedIdentityString(identity.indexer.package, "Semantic indexer package"),
487
+ version: checkedIdentityString(identity.indexer.version, "Semantic indexer version")
488
+ },
489
+ collection: {
490
+ name: checkedIdentityString(identity.collection.name, "Semantic collection name"),
491
+ pattern: checkedIdentityString(identity.collection.pattern, "Semantic collection pattern"),
492
+ ignore: identity.collection.ignore.map((pattern) => checkedIdentityString(pattern, "Semantic collection ignore pattern")),
493
+ globalContext: checkedIdentityString(identity.collection.globalContext, "Semantic collection global context"),
494
+ pathContexts: identity.collection.pathContexts.map((entry) => ({
495
+ path: checkedIdentityString(entry.path, "Semantic collection context path"),
496
+ context: checkedIdentityString(entry.context, "Semantic collection path context")
497
+ }))
498
+ },
499
+ embedding: {
500
+ model: checkedIdentityString(identity.embedding.model, "Semantic embedding model"),
501
+ chunkStrategy: checkedIdentityString(identity.embedding.chunkStrategy, "Semantic embedding chunk strategy")
502
+ }
503
+ };
504
+ if (Buffer.byteLength(JSON.stringify(validated), "utf8") > MAX_INDEX_IDENTITY_BYTES) {
505
+ throw new RangeError(`Semantic index identity exceeds the ${MAX_INDEX_IDENTITY_BYTES.toLocaleString("en-US")}-byte limit.`);
506
+ }
507
+ return validated;
508
+ }
509
+ async function boundedMetadataText(path, maximum, label) {
510
+ const pathBefore = await lstat2(path, { bigint: true });
511
+ if (pathBefore.isSymbolicLink() || !pathBefore.isFile() || pathBefore.nlink !== 1n) {
512
+ throw new Error(`${label} must be a regular, singly linked file.`);
513
+ }
514
+ const handle = await open2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
515
+ try {
516
+ const before = await handle.stat({ bigint: true });
517
+ if (!before.isFile() || before.nlink !== 1n || before.dev !== pathBefore.dev || before.ino !== pathBefore.ino) {
518
+ throw new Error(`${label} must be a regular, singly linked file.`);
519
+ }
520
+ if (before.size > BigInt(maximum)) {
521
+ throw new RangeError(`${label} exceeds the ${maximum.toLocaleString("en-US")}-byte metadata limit.`);
522
+ }
523
+ const chunks = [];
524
+ let bytes = 0;
525
+ for (;; ) {
526
+ const remaining = maximum - bytes;
527
+ const buffer = new Uint8Array(Math.min(64 * 1024, remaining + 1));
528
+ const result = await handle.read(buffer, 0, buffer.byteLength, null);
529
+ if (result.bytesRead === 0)
530
+ break;
531
+ bytes += result.bytesRead;
532
+ if (bytes > maximum) {
533
+ throw new RangeError(`${label} exceeds the ${maximum.toLocaleString("en-US")}-byte metadata limit.`);
534
+ }
535
+ chunks.push(buffer.slice(0, result.bytesRead));
536
+ }
537
+ const after = await handle.stat({ bigint: true });
538
+ let pathAfter;
539
+ try {
540
+ pathAfter = await lstat2(path, { bigint: true });
541
+ } catch {
542
+ throw new Error(`${label} changed while it was being read; retry.`);
543
+ }
544
+ if (!after.isFile() || after.nlink !== 1n || after.dev !== before.dev || after.ino !== before.ino || after.size !== BigInt(bytes) || pathAfter.dev !== before.dev || pathAfter.ino !== before.ino) {
545
+ throw new Error(`${label} changed while it was being read; retry.`);
546
+ }
547
+ const joined = new Uint8Array(bytes);
548
+ let offset = 0;
549
+ for (const chunk of chunks) {
550
+ joined.set(chunk, offset);
551
+ offset += chunk.byteLength;
552
+ }
553
+ return new TextDecoder("utf-8", { fatal: true }).decode(joined);
554
+ } finally {
555
+ await handle.close();
556
+ }
557
+ }
558
+ async function safeDirectory(path, label) {
559
+ try {
560
+ const metadata = await lstat2(path);
561
+ if (metadata.isSymbolicLink())
562
+ throw new Error(`${label} must not be a symbolic link.`);
563
+ if (!metadata.isDirectory())
564
+ throw new Error(`${label} must be a directory.`);
565
+ return "directory";
566
+ } catch (error) {
567
+ if (errorCode(error) === "ENOENT")
568
+ return "absent";
569
+ throw error;
570
+ }
571
+ }
572
+ async function canonicalProspectiveDirectory(path) {
573
+ const missing = [];
574
+ let candidate = resolve2(path);
575
+ for (;; ) {
576
+ try {
577
+ return resolve2(await realpath2(candidate), ...missing.toReversed());
578
+ } catch (error) {
579
+ if (errorCode(error) !== "ENOENT")
580
+ throw error;
581
+ const parent = dirname2(candidate);
582
+ if (parent === candidate)
583
+ throw error;
584
+ missing.push(basename2(candidate));
585
+ candidate = parent;
586
+ }
587
+ }
588
+ }
589
+ async function validateSemanticDatabaseFile(path) {
590
+ let metadata;
591
+ try {
592
+ metadata = await lstat2(path, { bigint: true });
593
+ } catch (error) {
594
+ if (errorCode(error) === "ENOENT")
595
+ return;
596
+ throw error;
597
+ }
598
+ if (metadata.isSymbolicLink()) {
599
+ throw new Error("Semantic database must not be a symbolic link.");
600
+ }
601
+ if (!metadata.isFile() || metadata.nlink !== 1n) {
602
+ throw new Error("Semantic database must be a regular, singly linked file.");
603
+ }
604
+ const handle = await open2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
605
+ try {
606
+ const current = await handle.stat({ bigint: true });
607
+ if (!current.isFile() || current.nlink !== 1n || current.dev !== metadata.dev || current.ino !== metadata.ino) {
608
+ throw new Error("Semantic database must be a regular, singly linked file.");
609
+ }
610
+ } finally {
611
+ await handle.close();
612
+ }
613
+ }
614
+ async function canonicalSemanticDatabasePath(database) {
615
+ const requested = resolve2(database);
616
+ const canonicalParent = await canonicalProspectiveDirectory(dirname2(requested));
617
+ const canonical = join2(canonicalParent, basename2(requested));
618
+ await validateSemanticDatabaseFile(canonical);
619
+ try {
620
+ return await realpath2(canonical);
621
+ } catch (error) {
622
+ if (errorCode(error) === "ENOENT")
623
+ return canonical;
624
+ throw error;
625
+ }
626
+ }
627
+ async function settledSemanticDatabasePath(database) {
628
+ const canonical = await canonicalSemanticDatabasePath(database);
629
+ await mkdir(dirname2(canonical), { recursive: true, mode: 448 });
630
+ const settledParent = await realpath2(dirname2(canonical));
631
+ if (settledParent !== dirname2(canonical)) {
632
+ throw new Error("Semantic database parent changed while settling its identity; retry.");
633
+ }
634
+ let created;
635
+ try {
636
+ created = await open2(canonical, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW | constants2.O_NONBLOCK, 384);
637
+ } catch (error) {
638
+ if (errorCode(error) !== "EEXIST")
639
+ throw error;
640
+ } finally {
641
+ await created?.close();
642
+ }
643
+ await validateSemanticDatabaseFile(canonical);
644
+ return await realpath2(canonical);
645
+ }
646
+ function pathIsWithin(root, candidate) {
647
+ return candidate === root || confinedPath(root, candidate);
648
+ }
649
+ function assertSemanticCacheOutsideVault(root, database) {
650
+ const ownedPaths = [
651
+ database,
652
+ `${database}.writer-lease`,
653
+ `${database}.snapshot`
654
+ ];
655
+ if (ownedPaths.some((path) => pathIsWithin(root, path) || pathIsWithin(path, root))) {
656
+ throw new Error("Semantic database, lease, and snapshot paths must not overlap the vault root.");
657
+ }
658
+ }
659
+ async function resolveSemanticDatabase(database, root) {
660
+ const [canonicalDatabase, canonicalRoot] = await Promise.all([
661
+ canonicalSemanticDatabasePath(database),
662
+ realpath2(root)
663
+ ]);
664
+ assertSemanticCacheOutsideVault(canonicalRoot, canonicalDatabase);
665
+ return canonicalDatabase;
666
+ }
667
+ function parsedOwner(value) {
668
+ if (!isRecord(value) || value.version !== LEASE_VERSION || !Number.isSafeInteger(value.pid) || value.pid < 1 || typeof value.token !== "string" || value.token.length < 16 || value.token.length > 128 || typeof value.acquiredAt !== "string") {
669
+ return null;
670
+ }
671
+ return {
672
+ version: LEASE_VERSION,
673
+ pid: value.pid,
674
+ token: value.token,
675
+ acquiredAt: value.acquiredAt
676
+ };
677
+ }
678
+ async function readOwner(path) {
679
+ try {
680
+ return parsedOwner(JSON.parse(await boundedMetadataText(join2(path, OWNER_NAME), MAX_OWNER_BYTES, "Semantic lease owner")));
681
+ } catch (error) {
682
+ if (error instanceof RangeError)
683
+ throw error;
684
+ return null;
685
+ }
686
+ }
687
+ function processIsAlive(pid) {
688
+ try {
689
+ process.kill(pid, 0);
690
+ return true;
691
+ } catch (error) {
692
+ return errorCode(error) !== "ESRCH";
693
+ }
694
+ }
695
+ async function recoverDeadLease(path) {
696
+ if (await safeDirectory(path, "Semantic writer lease") === "absent")
697
+ return false;
698
+ const owner = await readOwner(path);
699
+ if (owner === null || processIsAlive(owner.pid))
700
+ return false;
701
+ const confirmed = await readOwner(path);
702
+ if (confirmed?.pid !== owner.pid || confirmed.token !== owner.token)
703
+ return false;
704
+ const tombstone = `${path}.dead-${owner.token}-${randomUUID2()}`;
705
+ try {
706
+ await rename2(path, tombstone);
707
+ } catch (error) {
708
+ if (["ENOENT", "EEXIST", "ENOTEMPTY"].includes(errorCode(error) ?? ""))
709
+ return false;
710
+ throw error;
711
+ }
712
+ await rm2(tombstone, { recursive: true, force: true });
713
+ return true;
714
+ }
715
+ async function acquireWriterLeaseState(database, options) {
716
+ const waitMs = checkedLeaseBound(options.waitMs, DEFAULT_LEASE_WAIT_MS, MAX_LEASE_WAIT_MS, "Semantic writer lease wait");
717
+ const pollMs = checkedLeaseBound(options.pollMs, DEFAULT_LEASE_POLL_MS, MAX_LEASE_POLL_MS, "Semantic writer lease poll interval");
718
+ const canonicalDatabase = await settledSemanticDatabasePath(database);
719
+ const path = `${canonicalDatabase}.writer-lease`;
720
+ const owner = {
721
+ version: LEASE_VERSION,
722
+ pid: process.pid,
723
+ token: randomUUID2(),
724
+ acquiredAt: new Date().toISOString()
725
+ };
726
+ const startedAt = Date.now();
727
+ for (;; ) {
728
+ await safeDirectory(path, "Semantic writer lease");
729
+ const claim = `${path}.claim-${process.pid}-${owner.token}`;
730
+ await mkdir(claim, { mode: 448 });
731
+ try {
732
+ await writeFile(join2(claim, OWNER_NAME), `${JSON.stringify(owner)}
733
+ `, {
734
+ encoding: "utf8",
735
+ flag: "wx",
736
+ mode: 384
737
+ });
738
+ try {
739
+ await rename2(claim, path);
740
+ const lease = { path, owner, database: canonicalDatabase };
741
+ try {
742
+ await validateSemanticDatabaseFile(canonicalDatabase);
743
+ return lease;
744
+ } catch (error) {
745
+ await releaseWriterLease(lease);
746
+ throw error;
747
+ }
748
+ } catch (error) {
749
+ if (!["EEXIST", "ENOTEMPTY"].includes(errorCode(error) ?? ""))
750
+ throw error;
751
+ }
752
+ } finally {
753
+ await rm2(claim, { recursive: true, force: true });
754
+ }
755
+ if (await recoverDeadLease(path))
756
+ continue;
757
+ const elapsed = Date.now() - startedAt;
758
+ if (elapsed >= waitMs) {
759
+ throw new Error(`Timed out after ${waitMs}ms waiting for the semantic writer lease for ${JSON.stringify(resolve2(database))}.`);
760
+ }
761
+ await Bun.sleep(Math.min(pollMs, waitMs - elapsed));
762
+ }
763
+ }
764
+ async function releaseWriterLease(lease) {
765
+ if (await safeDirectory(lease.path, "Semantic writer lease") === "absent")
766
+ return;
767
+ const current = await readOwner(lease.path);
768
+ if (current?.pid !== lease.owner.pid || current.token !== lease.owner.token)
769
+ return;
770
+ const tombstone = `${lease.path}.release-${lease.owner.token}`;
771
+ try {
772
+ await rename2(lease.path, tombstone);
773
+ } catch (error) {
774
+ if (errorCode(error) === "ENOENT")
775
+ return;
776
+ throw error;
777
+ }
778
+ await rm2(tombstone, { recursive: true, force: true });
779
+ }
780
+ async function acquireSemanticWriterLease(database, options = {}) {
781
+ const lease = await acquireWriterLeaseState(database, options);
782
+ let released = false;
783
+ return {
784
+ release: async () => {
785
+ if (released)
786
+ return;
787
+ released = true;
788
+ await releaseWriterLease(lease);
789
+ }
790
+ };
791
+ }
792
+ function confinedPath(root, path) {
793
+ const candidate = relative2(root, path);
794
+ return candidate !== "" && candidate !== ".." && !candidate.startsWith(`..${sep2}`) && !isAbsolute(candidate);
795
+ }
796
+ function validatedProjectionNotes(notes) {
797
+ if (notes.length > MAX_SCANNED_NOTES) {
798
+ throw new VaultScanBudgetError("notes", MAX_SCANNED_NOTES, `Vault scan exceeds the ${MAX_SCANNED_NOTES} Markdown note limit.`);
799
+ }
800
+ const paths = new Set;
801
+ const validated = [];
802
+ let totalBytes = 0;
803
+ for (const note of notes) {
804
+ const path = note.path;
805
+ const segments = path.split("/");
806
+ if (path === "" || isAbsolute(path) || path.includes("\\") || !path.endsWith(".md") || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
807
+ throw new Error(`Semantic projection note path ${JSON.stringify(path)} is invalid.`);
808
+ }
809
+ if (paths.has(path)) {
810
+ throw new Error(`Semantic projection note path ${JSON.stringify(path)} is duplicated.`);
811
+ }
812
+ paths.add(path);
813
+ const bytes = Buffer.byteLength(note.content, "utf8");
814
+ if (bytes > MAX_NOTE_UTF8_BYTES) {
815
+ throw new VaultScanBudgetError("note-bytes", MAX_NOTE_UTF8_BYTES, `Vault note ${JSON.stringify(path)} exceeds the ${MAX_NOTE_UTF8_BYTES}-byte UTF-8 limit.`);
816
+ }
817
+ totalBytes += bytes;
818
+ if (totalBytes > MAX_VAULT_UTF8_BYTES) {
819
+ throw new VaultScanBudgetError("total-bytes", MAX_VAULT_UTF8_BYTES, `Vault scan exceeds the ${MAX_VAULT_UTF8_BYTES}-byte cumulative UTF-8 limit.`);
820
+ }
821
+ validated.push({
822
+ path,
823
+ sha256: createHash("sha256").update(note.content).digest("hex"),
824
+ bytes
825
+ });
826
+ }
827
+ return { notes: validated, totalBytes };
828
+ }
829
+ async function atomicWrite(path, content) {
830
+ const temporary = `${path}.${process.pid}.${randomUUID2()}.tmp`;
831
+ let handle;
832
+ try {
833
+ handle = await open2(temporary, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
834
+ await handle.writeFile(content, "utf8");
835
+ await handle.sync();
836
+ await handle.close();
837
+ handle = undefined;
838
+ await rename2(temporary, path);
839
+ } finally {
840
+ await handle?.close().catch(() => {
841
+ return;
842
+ });
843
+ await rm2(temporary, { force: true }).catch(() => {
844
+ return;
845
+ });
846
+ }
847
+ }
848
+ function snapshotCacheOwner(database) {
849
+ return {
850
+ version: SNAPSHOT_OWNER_VERSION,
851
+ kind: SNAPSHOT_OWNER_KIND,
852
+ databaseIdentity: resolve2(database)
853
+ };
854
+ }
855
+ async function assertOwnedSnapshotCache(database, snapshotDirectory) {
856
+ if (await safeDirectory(snapshotDirectory, "Semantic snapshot cache") === "absent") {
857
+ return "absent";
858
+ }
859
+ const expected = snapshotCacheOwner(database);
860
+ let actual;
861
+ try {
862
+ actual = JSON.parse(await boundedMetadataText(join2(snapshotDirectory, SNAPSHOT_OWNER_NAME), MAX_OWNER_BYTES, "Semantic snapshot cache owner"));
863
+ } catch {
864
+ throw new Error(`Semantic snapshot cache ${JSON.stringify(snapshotDirectory)} is unowned or has an incompatible ownership marker; remove this disposable directory explicitly before retrying.`);
865
+ }
866
+ if (!isRecord(actual) || actual.version !== expected.version || actual.kind !== expected.kind || actual.databaseIdentity !== expected.databaseIdentity) {
867
+ throw new Error(`Semantic snapshot cache ${JSON.stringify(snapshotDirectory)} is unowned or has an incompatible ownership marker; remove this disposable directory explicitly before retrying.`);
868
+ }
869
+ return "owned";
870
+ }
871
+ async function ensureOwnedSnapshotCache(database, snapshotDirectory) {
872
+ if (await assertOwnedSnapshotCache(database, snapshotDirectory) === "owned")
873
+ return;
874
+ const temporary = `${snapshotDirectory}.initialize-${process.pid}-${randomUUID2()}`;
875
+ await mkdir(temporary, { mode: 448 });
876
+ try {
877
+ await writeFile(join2(temporary, SNAPSHOT_OWNER_NAME), `${JSON.stringify(snapshotCacheOwner(database))}
878
+ `, { encoding: "utf8", flag: "wx", mode: 384 });
879
+ try {
880
+ await rename2(temporary, snapshotDirectory);
881
+ return;
882
+ } catch (error) {
883
+ if (!["EEXIST", "ENOTEMPTY"].includes(errorCode(error) ?? ""))
884
+ throw error;
885
+ }
886
+ } finally {
887
+ await rm2(temporary, { recursive: true, force: true });
888
+ }
889
+ await assertOwnedSnapshotCache(database, snapshotDirectory);
890
+ }
891
+ async function existingGenerationMatches(generationPath, manifest, manifestText) {
892
+ try {
893
+ if (await safeDirectory(generationPath, "Semantic projection generation") === "absent") {
894
+ return false;
895
+ }
896
+ if (await boundedMetadataText(join2(generationPath, MANIFEST_NAME), MAX_MANIFEST_BYTES, "Semantic projection manifest") !== manifestText) {
897
+ return false;
898
+ }
899
+ for (const entry of manifest.notes) {
900
+ const path = resolve2(generationPath, ...entry.path.split("/"));
901
+ if (!confinedPath(generationPath, path))
902
+ return false;
903
+ const content = await boundedMetadataText(path, MAX_NOTE_UTF8_BYTES, `Semantic projection note ${JSON.stringify(entry.path)}`);
904
+ if (Buffer.byteLength(content, "utf8") !== entry.bytes || createHash("sha256").update(content).digest("hex") !== entry.sha256) {
905
+ return false;
906
+ }
907
+ }
908
+ const expectedNotes = new Set(manifest.notes.map(({ path }) => join2(...path.split("/"))));
909
+ const expectedDirectories = new Set;
910
+ for (const path of expectedNotes) {
911
+ let parent = dirname2(path);
912
+ while (parent !== ".") {
913
+ expectedDirectories.add(parent);
914
+ parent = dirname2(parent);
915
+ }
916
+ }
917
+ const pending = [generationPath];
918
+ let entries = 0;
919
+ const maximumEntries = manifest.notes.length + expectedDirectories.size + MAX_CACHE_DIRECTORY_ENTRIES + 1;
920
+ while (pending.length > 0) {
921
+ const directoryPath = pending.pop();
922
+ if (directoryPath === undefined)
923
+ break;
924
+ const directory = await opendir(directoryPath);
925
+ try {
926
+ for await (const entry of directory) {
927
+ entries += 1;
928
+ if (entries > maximumEntries || entry.isSymbolicLink())
929
+ return false;
930
+ const absolute = join2(directoryPath, entry.name);
931
+ const path = relative2(generationPath, absolute);
932
+ if (entry.isDirectory()) {
933
+ if (!expectedDirectories.has(path))
934
+ return false;
935
+ pending.push(absolute);
936
+ continue;
937
+ }
938
+ if (!entry.isFile())
939
+ return false;
940
+ if (path === MANIFEST_NAME || expectedNotes.has(path))
941
+ continue;
942
+ if (dirname2(path) === "." && entry.name.startsWith(READER_PREFIX) && entry.name.endsWith(".json")) {
943
+ continue;
944
+ }
945
+ return false;
946
+ }
947
+ } finally {
948
+ try {
949
+ await directory.close();
950
+ } catch {}
951
+ }
952
+ }
953
+ return true;
954
+ } catch (error) {
955
+ if (error instanceof RangeError || error instanceof Error && error.message.includes("symbolic link")) {
956
+ throw error;
957
+ }
958
+ return false;
959
+ }
960
+ }
961
+ async function materializeGeneration(snapshotDirectory, generationPath, manifest, manifestText, notesByPath) {
962
+ if (await existingGenerationMatches(generationPath, manifest, manifestText))
963
+ return;
964
+ if (await safeDirectory(generationPath, "Semantic projection generation") === "directory") {
965
+ if (await activeGenerationReaders(generationPath) > 0) {
966
+ throw new Error("Semantic projection cache verification failed while this generation still has active readers; close those sessions and retry the repair.");
967
+ }
968
+ await rm2(generationPath, { recursive: true, force: true });
969
+ }
970
+ const temporary = join2(snapshotDirectory, `.temporary-${process.pid}-${randomUUID2()}`);
971
+ await mkdir(temporary, { recursive: true, mode: 448 });
972
+ try {
973
+ for (const entry of manifest.notes) {
974
+ const note = notesByPath.get(entry.path);
975
+ if (note === undefined) {
976
+ throw new Error(`Semantic projection lost note ${JSON.stringify(entry.path)}.`);
977
+ }
978
+ const bytes = Buffer.byteLength(note.content, "utf8");
979
+ const sha256 = createHash("sha256").update(note.content).digest("hex");
980
+ if (bytes !== entry.bytes || sha256 !== entry.sha256) {
981
+ throw new Error(`Semantic projection note ${JSON.stringify(entry.path)} changed after validation.`);
982
+ }
983
+ const destination = resolve2(temporary, ...entry.path.split("/"));
984
+ if (!confinedPath(temporary, destination)) {
985
+ throw new Error(`Semantic projection path ${JSON.stringify(entry.path)} escapes its generation.`);
986
+ }
987
+ await mkdir(dirname2(destination), { recursive: true, mode: 448 });
988
+ await writeFile(destination, note.content, {
989
+ encoding: "utf8",
990
+ flag: "wx",
991
+ mode: 384
992
+ });
993
+ }
994
+ await atomicWrite(join2(temporary, MANIFEST_NAME), manifestText);
995
+ await rename2(temporary, generationPath);
996
+ } catch (error) {
997
+ await rm2(temporary, { recursive: true, force: true });
998
+ throw error;
999
+ }
1000
+ }
1001
+ function readerOwnerName(owner) {
1002
+ return `${READER_PREFIX}${owner.pid}-${owner.token}.json`;
1003
+ }
1004
+ async function activeGenerationReaders(generation) {
1005
+ let directory;
1006
+ try {
1007
+ directory = await opendir(generation);
1008
+ } catch (error) {
1009
+ if (errorCode(error) === "ENOENT")
1010
+ return 0;
1011
+ throw error;
1012
+ }
1013
+ let active = 0;
1014
+ let readerEntries = 0;
1015
+ try {
1016
+ for await (const entry of directory) {
1017
+ if (!entry.isFile() || !entry.name.startsWith(READER_PREFIX) || !entry.name.endsWith(".json")) {
1018
+ continue;
1019
+ }
1020
+ readerEntries += 1;
1021
+ if (readerEntries > MAX_CACHE_DIRECTORY_ENTRIES) {
1022
+ throw new RangeError("Semantic projection generation has too many reader entries.");
1023
+ }
1024
+ const path = join2(generation, entry.name);
1025
+ let owner;
1026
+ try {
1027
+ owner = parsedOwner(JSON.parse(await boundedMetadataText(path, MAX_OWNER_BYTES, "Semantic projection reader")));
1028
+ } catch (error) {
1029
+ if (error instanceof RangeError)
1030
+ throw error;
1031
+ owner = null;
1032
+ }
1033
+ if (owner !== null && processIsAlive(owner.pid)) {
1034
+ active += 1;
1035
+ } else {
1036
+ await rm2(path, { force: true });
1037
+ }
1038
+ }
1039
+ } finally {
1040
+ try {
1041
+ await directory.close();
1042
+ } catch {}
1043
+ }
1044
+ return active;
1045
+ }
1046
+ async function activeReaderGenerations(database) {
1047
+ const canonicalDatabase = await canonicalSemanticDatabasePath(database);
1048
+ const snapshotDirectory = `${canonicalDatabase}.snapshot`;
1049
+ if (await assertOwnedSnapshotCache(canonicalDatabase, snapshotDirectory) === "absent") {
1050
+ return new Set;
1051
+ }
1052
+ const active = new Set;
1053
+ const directory = await opendir(snapshotDirectory);
1054
+ let entries = 0;
1055
+ try {
1056
+ for await (const entry of directory) {
1057
+ entries += 1;
1058
+ if (entries > MAX_CACHE_DIRECTORY_ENTRIES) {
1059
+ throw new RangeError("Semantic snapshot cache has too many metadata entries.");
1060
+ }
1061
+ if (!entry.isDirectory() || !entry.name.startsWith(GENERATION_PREFIX))
1062
+ continue;
1063
+ if (await activeGenerationReaders(join2(snapshotDirectory, entry.name)) > 0) {
1064
+ active.add(entry.name);
1065
+ }
1066
+ }
1067
+ } finally {
1068
+ try {
1069
+ await directory.close();
1070
+ } catch {}
1071
+ }
1072
+ return active;
1073
+ }
1074
+ async function withSemanticGenerationWriterLease(database, generation, operation, options = {}) {
1075
+ if (!generation.startsWith(GENERATION_PREFIX)) {
1076
+ throw new Error("Semantic projection generation is invalid.");
1077
+ }
1078
+ if (options.excludeReaders !== undefined && typeof options.excludeReaders !== "boolean") {
1079
+ throw new TypeError("Semantic writer excludeReaders must be a boolean.");
1080
+ }
1081
+ const waitMs = checkedLeaseBound(options.waitMs, DEFAULT_LEASE_WAIT_MS, MAX_LEASE_WAIT_MS, "Semantic writer lease wait");
1082
+ const pollMs = checkedLeaseBound(options.pollMs, DEFAULT_LEASE_POLL_MS, MAX_LEASE_POLL_MS, "Semantic writer lease poll interval");
1083
+ const readerDescription = options.excludeReaders === true ? "semantic projection readers" : "readers of an older semantic projection";
1084
+ const startedAt = Date.now();
1085
+ for (;; ) {
1086
+ const elapsed = Date.now() - startedAt;
1087
+ if (elapsed >= waitMs) {
1088
+ throw new Error(`Timed out after ${waitMs}ms waiting for ${readerDescription} to close.`);
1089
+ }
1090
+ const lease = await acquireSemanticWriterLease(database, {
1091
+ waitMs: Math.max(1, waitMs - elapsed),
1092
+ pollMs
1093
+ });
1094
+ let compatible = false;
1095
+ try {
1096
+ const active = await activeReaderGenerations(database);
1097
+ compatible = options.excludeReaders === true ? active.size === 0 : [...active].every((candidate) => candidate === generation);
1098
+ if (compatible)
1099
+ return await operation();
1100
+ } finally {
1101
+ await lease.release();
1102
+ }
1103
+ const waited = Date.now() - startedAt;
1104
+ if (waited >= waitMs) {
1105
+ throw new Error(`Timed out after ${waitMs}ms waiting for ${readerDescription} to close.`);
1106
+ }
1107
+ await Bun.sleep(Math.min(pollMs, waitMs - waited));
1108
+ }
1109
+ }
1110
+ async function cleanUnusedGenerations(snapshotDirectory, currentGeneration) {
1111
+ const directory = await opendir(snapshotDirectory);
1112
+ let entries = 0;
1113
+ try {
1114
+ for await (const entry of directory) {
1115
+ entries += 1;
1116
+ if (entries > MAX_CACHE_DIRECTORY_ENTRIES) {
1117
+ throw new RangeError("Semantic snapshot cache has too many metadata entries.");
1118
+ }
1119
+ if (!entry.isDirectory())
1120
+ continue;
1121
+ const path = join2(snapshotDirectory, entry.name);
1122
+ if (entry.name.startsWith(".temporary-")) {
1123
+ await rm2(path, { recursive: true, force: true });
1124
+ continue;
1125
+ }
1126
+ if (!entry.name.startsWith(GENERATION_PREFIX) || entry.name === currentGeneration)
1127
+ continue;
1128
+ if (await activeGenerationReaders(path) === 0) {
1129
+ await rm2(path, { recursive: true, force: true });
1130
+ }
1131
+ }
1132
+ } finally {
1133
+ try {
1134
+ await directory.close();
1135
+ } catch {}
1136
+ }
1137
+ }
1138
+ async function describeSemanticProjection(database, root, notes, indexIdentity) {
1139
+ const [canonicalRoot, prospectiveDatabase] = await Promise.all([
1140
+ realpath2(root),
1141
+ canonicalSemanticDatabasePath(database)
1142
+ ]);
1143
+ assertSemanticCacheOutsideVault(canonicalRoot, prospectiveDatabase);
1144
+ const validated = validatedProjectionNotes(notes);
1145
+ const validatedIdentity = validatedIndexIdentity(indexIdentity);
1146
+ const canonicalDatabase = await settledSemanticDatabasePath(prospectiveDatabase);
1147
+ assertSemanticCacheOutsideVault(canonicalRoot, canonicalDatabase);
1148
+ const identity = createHash("sha256").update(JSON.stringify({
1149
+ version: PROJECTION_VERSION,
1150
+ indexIdentity: validatedIdentity,
1151
+ root: canonicalRoot,
1152
+ notes: validated.notes,
1153
+ totalBytes: validated.totalBytes
1154
+ })).digest("hex");
1155
+ const generation = `${GENERATION_PREFIX}${identity.slice(0, 32)}`;
1156
+ const snapshotDirectory = `${canonicalDatabase}.snapshot`;
1157
+ const generationPath = join2(snapshotDirectory, generation);
1158
+ const manifest = {
1159
+ version: PROJECTION_VERSION,
1160
+ indexIdentity: validatedIdentity,
1161
+ root: canonicalRoot,
1162
+ generation,
1163
+ notes: validated.notes,
1164
+ totalBytes: validated.totalBytes
1165
+ };
1166
+ const manifestText = `${JSON.stringify(manifest)}
1167
+ `;
1168
+ if (Buffer.byteLength(manifestText) > MAX_MANIFEST_BYTES) {
1169
+ throw new RangeError(`Semantic projection manifest exceeds the ${MAX_MANIFEST_BYTES}-byte metadata limit.`);
1170
+ }
1171
+ return {
1172
+ database: canonicalDatabase,
1173
+ snapshotDirectory,
1174
+ generationPath,
1175
+ manifest,
1176
+ manifestText
1177
+ };
1178
+ }
1179
+ async function prepareSemanticProjection(description, notes) {
1180
+ const { database, snapshotDirectory, generationPath, manifest, manifestText } = description;
1181
+ const validated = validatedProjectionNotes(notes);
1182
+ if (validated.totalBytes !== manifest.totalBytes || JSON.stringify(validated.notes) !== JSON.stringify(manifest.notes)) {
1183
+ throw new Error("Semantic projection notes changed after validation.");
1184
+ }
1185
+ const notesByPath = new Map(notes.map((note) => [note.path, note]));
1186
+ await ensureOwnedSnapshotCache(database, snapshotDirectory);
1187
+ await materializeGeneration(snapshotDirectory, generationPath, manifest, manifestText, notesByPath);
1188
+ const reader = {
1189
+ version: LEASE_VERSION,
1190
+ pid: process.pid,
1191
+ token: randomUUID2(),
1192
+ acquiredAt: new Date().toISOString()
1193
+ };
1194
+ const readerPath = join2(generationPath, readerOwnerName(reader));
1195
+ await writeFile(readerPath, `${JSON.stringify(reader)}
1196
+ `, {
1197
+ encoding: "utf8",
1198
+ flag: "wx",
1199
+ mode: 384
1200
+ });
1201
+ try {
1202
+ await atomicWrite(join2(snapshotDirectory, MANIFEST_NAME), manifestText);
1203
+ await cleanUnusedGenerations(snapshotDirectory, manifest.generation);
1204
+ } catch (error) {
1205
+ await rm2(readerPath, { force: true });
1206
+ throw error;
1207
+ }
1208
+ let released = false;
1209
+ return {
1210
+ root: generationPath,
1211
+ manifest,
1212
+ release: async () => {
1213
+ if (released)
1214
+ return;
1215
+ released = true;
1216
+ await rm2(readerPath, { force: true });
1217
+ }
1218
+ };
1219
+ }
1220
+
1221
+ // src/semantic.ts
1222
+ var recommendedEmbeddingModel = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf#0f741b5a6585bd53aeb15cd1372c56f2a0f65e12";
1223
+ var recommendedEmbeddingModelSha256 = "b5ce9d77a3fc4b3b39ccb5643c36777911cc4eb46a66962eadfa3f5f60490d63";
1224
+ var MAX_EMBEDDING_MODEL_BYTES = 2 * 1024 * 1024 * 1024;
1225
+ var MAX_SEMANTIC_DATABASE_IDENTITY_BYTES = 16 * 1024;
1226
+ var MAX_SEMANTIC_READ_SNAPSHOT_BYTES = 16 * 1024 * 1024 * 1024;
1227
+ var SHA256 = /^[0-9a-f]{64}$/u;
1228
+ var semanticIndexSchema = 1;
1229
+ var qmdIndexerVersion = "2.5.3+hraness.aa993dceb3ef8cfb71d470554ca437570f5a2b3c";
1230
+ var collectionName = "kb";
1231
+ var markdownPattern = "**/*.md";
1232
+ var ignoredPatterns = ["index.md", "**/AGENTS.md"];
1233
+ var embeddingChunkStrategy = "regex";
1234
+ var globalContext = "A Markdown knowledge base. Source records preserve evidence; maintained notes contain current synthesis; explicit wikilinks define structural relationships.";
1235
+ var collectionContext = {
1236
+ "/": "Knowledge-base notes, clipped sources, plans, reports, and explicit contextual links.",
1237
+ "/articles": "Captured source records and their acquisition provenance.",
1238
+ "/notes": "Maintained concepts, comparisons, and current synthesis.",
1239
+ "/plans": "Decisions, constraints, execution state, and verification evidence.",
1240
+ "/riffs": "Voice-preserving first-person source thought."
1241
+ };
1242
+ var recommendedEmbeddingModelIdentity = `${recommendedEmbeddingModel}@sha256:${recommendedEmbeddingModelSha256}`;
1243
+ var semanticIndexIdentity = {
1244
+ producer: { package: "@hraness/kb", schema: semanticIndexSchema },
1245
+ indexer: { package: "@tobilu/qmd", version: qmdIndexerVersion },
1246
+ collection: {
1247
+ name: collectionName,
1248
+ pattern: markdownPattern,
1249
+ ignore: ignoredPatterns,
1250
+ globalContext,
1251
+ pathContexts: Object.entries(collectionContext).map(([path, context]) => ({ path, context }))
1252
+ },
1253
+ embedding: {
1254
+ model: recommendedEmbeddingModelIdentity,
1255
+ chunkStrategy: embeddingChunkStrategy
1256
+ }
1257
+ };
1258
+ var qmdModuleSpecifier = "@tobilu/qmd";
1259
+ async function sha256EmbeddingModelFile(path) {
1260
+ const handle = await open3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
1261
+ try {
1262
+ const metadata = await handle.stat();
1263
+ if (!metadata.isFile())
1264
+ throw new TypeError("The embedding model must be a regular file.");
1265
+ if (metadata.size > MAX_EMBEDDING_MODEL_BYTES) {
1266
+ throw new RangeError(`The embedding model exceeds ${MAX_EMBEDDING_MODEL_BYTES.toLocaleString("en-US")} bytes.`);
1267
+ }
1268
+ const hash = createHash2("sha256");
1269
+ const buffer = new Uint8Array(1024 * 1024);
1270
+ let observed = 0;
1271
+ while (true) {
1272
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
1273
+ if (bytesRead === 0)
1274
+ break;
1275
+ observed += bytesRead;
1276
+ if (observed > MAX_EMBEDDING_MODEL_BYTES) {
1277
+ throw new RangeError(`The embedding model exceeds ${MAX_EMBEDDING_MODEL_BYTES.toLocaleString("en-US")} bytes.`);
1278
+ }
1279
+ hash.update(buffer.subarray(0, bytesRead));
1280
+ }
1281
+ const after = await handle.stat();
1282
+ if (after.size !== metadata.size || observed !== metadata.size) {
1283
+ throw new Error("The embedding model changed while its digest was computed; retry.");
1284
+ }
1285
+ return hash.digest("hex");
1286
+ } finally {
1287
+ await handle.close();
1288
+ }
1289
+ }
1290
+ var verifiedEmbeddingModelLeaseStates = new WeakMap;
1291
+ async function writeEmbeddingModelBytes(destination, bytes) {
1292
+ let offset = 0;
1293
+ while (offset < bytes.byteLength) {
1294
+ const written = await destination.write(bytes, offset, bytes.byteLength - offset, null);
1295
+ if (written.bytesWritten < 1)
1296
+ throw new Error("The embedding model snapshot write stalled.");
1297
+ offset += written.bytesWritten;
1298
+ }
1299
+ }
1300
+ async function verifiedIndexEmbeddingModelSource(path, dependencies) {
1301
+ if (path === undefined) {
1302
+ return Object.freeze({ source: recommendedEmbeddingModel, release: () => Promise.resolve() });
1303
+ }
1304
+ const sourcePath = resolve3(path);
1305
+ const directory = await mkdtemp(join3(tmpdir(), "hraness-kb-embedding-model-"));
1306
+ const destinationPath = join3(directory, "pinned-model.gguf");
1307
+ let source;
1308
+ let destination;
1309
+ try {
1310
+ source = await open3(sourcePath, constants3.O_RDONLY | constants3.O_NOFOLLOW);
1311
+ destination = await open3(destinationPath, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 256);
1312
+ const before = await source.stat();
1313
+ if (!before.isFile())
1314
+ throw new TypeError("The embedding model must be a regular file.");
1315
+ if (before.size > MAX_EMBEDDING_MODEL_BYTES) {
1316
+ throw new RangeError(`The embedding model exceeds ${MAX_EMBEDDING_MODEL_BYTES.toLocaleString("en-US")} bytes.`);
1317
+ }
1318
+ const hash = createHash2("sha256");
1319
+ const buffer = new Uint8Array(1024 * 1024);
1320
+ let observed = 0;
1321
+ for (;; ) {
1322
+ const { bytesRead } = await source.read(buffer, 0, buffer.byteLength, null);
1323
+ if (bytesRead === 0)
1324
+ break;
1325
+ observed += bytesRead;
1326
+ if (observed > MAX_EMBEDDING_MODEL_BYTES) {
1327
+ throw new RangeError(`The embedding model exceeds ${MAX_EMBEDDING_MODEL_BYTES.toLocaleString("en-US")} bytes.`);
1328
+ }
1329
+ const bytes = buffer.subarray(0, bytesRead);
1330
+ hash.update(bytes);
1331
+ await writeEmbeddingModelBytes(destination, bytes);
1332
+ }
1333
+ const after = await source.stat();
1334
+ const copied = await destination.stat();
1335
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || observed !== before.size || copied.size !== observed) {
1336
+ throw new Error("The embedding model changed while its private snapshot was created; retry.");
1337
+ }
1338
+ await destination.sync();
1339
+ await destination.close();
1340
+ destination = undefined;
1341
+ await source.close();
1342
+ source = undefined;
1343
+ const digest = dependencies.digestEmbeddingModelFile === undefined ? hash.digest("hex") : await dependencies.digestEmbeddingModelFile(destinationPath);
1344
+ if (digest !== recommendedEmbeddingModelSha256) {
1345
+ throw new Error("The local embedding model does not match the pinned recommended model SHA-256.");
1346
+ }
1347
+ await chmod(destinationPath, 256);
1348
+ let released = false;
1349
+ return Object.freeze({
1350
+ source: destinationPath,
1351
+ release: async () => {
1352
+ if (released)
1353
+ return;
1354
+ released = true;
1355
+ await rm3(directory, { recursive: true, force: true });
1356
+ }
1357
+ });
1358
+ } catch (error) {
1359
+ await destination?.close().catch(() => {
1360
+ return;
1361
+ });
1362
+ await source?.close().catch(() => {
1363
+ return;
1364
+ });
1365
+ await rm3(directory, { recursive: true, force: true });
1366
+ throw error;
1367
+ }
1368
+ }
1369
+ function releaseEmbeddingModelReference(state) {
1370
+ if (state.references < 1) {
1371
+ throw new Error("Verified embedding-model lease reference accounting underflowed.");
1372
+ }
1373
+ state.references -= 1;
1374
+ if (state.references !== 0)
1375
+ return Promise.resolve();
1376
+ state.cleanupPromise ??= state.cleanup();
1377
+ return state.cleanupPromise;
1378
+ }
1379
+ function retainVerifiedEmbeddingModelLease(lease) {
1380
+ const state = verifiedEmbeddingModelLeaseStates.get(lease);
1381
+ if (state === undefined) {
1382
+ throw new TypeError("embeddingModelLease must be created by createVerifiedEmbeddingModelLease.");
1383
+ }
1384
+ if (!state.acceptingReaders) {
1385
+ throw new Error("The verified embedding-model lease is closed.");
1386
+ }
1387
+ state.references += 1;
1388
+ let released = false;
1389
+ return Object.freeze({
1390
+ source: state.source,
1391
+ release: () => {
1392
+ if (released)
1393
+ return Promise.resolve();
1394
+ released = true;
1395
+ return releaseEmbeddingModelReference(state);
1396
+ }
1397
+ });
1398
+ }
1399
+ async function createVerifiedEmbeddingModelLease(embeddingModelFile, dependencies = {}) {
1400
+ if (typeof embeddingModelFile !== "string" || embeddingModelFile.trim() === "") {
1401
+ throw new TypeError("embeddingModelFile must be a non-empty path string.");
1402
+ }
1403
+ const copied = await verifiedIndexEmbeddingModelSource(embeddingModelFile, dependencies);
1404
+ const state = {
1405
+ source: copied.source,
1406
+ cleanup: copied.release,
1407
+ references: 1,
1408
+ acceptingReaders: true
1409
+ };
1410
+ let ownerClosePromise;
1411
+ const lease = Object.freeze({
1412
+ model: recommendedEmbeddingModel,
1413
+ close: () => {
1414
+ if (ownerClosePromise !== undefined)
1415
+ return ownerClosePromise;
1416
+ state.acceptingReaders = false;
1417
+ ownerClosePromise = releaseEmbeddingModelReference(state);
1418
+ return ownerClosePromise;
1419
+ }
1420
+ });
1421
+ verifiedEmbeddingModelLeaseStates.set(lease, state);
1422
+ return lease;
1423
+ }
1424
+ function requiredStoreLocalVectorBoundary(value) {
1425
+ if (value === undefined)
1426
+ return false;
1427
+ if (typeof value !== "boolean") {
1428
+ throw new TypeError("requireStoreLocalVectorBoundary must be a boolean.");
1429
+ }
1430
+ return value;
1431
+ }
1432
+ async function sessionEmbeddingModelSource(options, dependencies) {
1433
+ if (options.embeddingModelFile !== undefined && options.embeddingModelLease !== undefined) {
1434
+ throw new TypeError("embeddingModelFile and embeddingModelLease are mutually exclusive.");
1435
+ }
1436
+ if (options.embeddingModelLease !== undefined) {
1437
+ return retainVerifiedEmbeddingModelLease(options.embeddingModelLease);
1438
+ }
1439
+ if (options.embeddingModelFile === undefined) {
1440
+ return Object.freeze({
1441
+ source: recommendedEmbeddingModel,
1442
+ release: () => Promise.resolve()
1443
+ });
1444
+ }
1445
+ const owner = await createVerifiedEmbeddingModelLease(options.embeddingModelFile, dependencies);
1446
+ try {
1447
+ const retained = retainVerifiedEmbeddingModelLease(owner);
1448
+ await owner.close();
1449
+ return retained;
1450
+ } catch (error) {
1451
+ await owner.close().catch(() => {
1452
+ return;
1453
+ });
1454
+ throw error;
1455
+ }
1456
+ }
1457
+ function cacheHome(dependencies) {
1458
+ const configured = dependencies.cacheHome ?? process.env.XDG_CACHE_HOME;
1459
+ if (configured !== undefined && configured.trim() !== "") {
1460
+ return isAbsolute2(configured) ? configured : resolve3(configured);
1461
+ }
1462
+ return join3(homedir(), ".cache");
1463
+ }
1464
+ function semanticDatabasePath(root, dependencies = {}) {
1465
+ const identity = createHash2("sha256").update(resolve3(root)).digest("hex").slice(0, 20);
1466
+ return join3(cacheHome(dependencies), "hraness-kb", "indexes", `${identity}.sqlite`);
1467
+ }
1468
+ async function resolvedDirectory(path) {
1469
+ const root = await realpath3(resolve3(path));
1470
+ if (!(await stat(root)).isDirectory())
1471
+ throw new Error("Knowledge-base root must be a directory.");
1472
+ return root;
1473
+ }
1474
+ function isRecord2(value) {
1475
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1476
+ }
1477
+ function boundaryRecord(value, label) {
1478
+ if (!isRecord2(value))
1479
+ throw new Error(`${label} must be an object.`);
1480
+ return value;
1481
+ }
1482
+ function boundaryString(value, label) {
1483
+ if (typeof value !== "string")
1484
+ throw new Error(`${label} must be a string.`);
1485
+ return value;
1486
+ }
1487
+ function boundaryNumber(value, label) {
1488
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1489
+ throw new Error(`${label} must be a finite number.`);
1490
+ }
1491
+ return value;
1492
+ }
1493
+ function boundaryCount(value, label) {
1494
+ const number = boundaryNumber(value, label);
1495
+ if (!Number.isSafeInteger(number) || number < 0) {
1496
+ throw new Error(`${label} must be a non-negative safe integer.`);
1497
+ }
1498
+ return number;
1499
+ }
1500
+ function boundaryArray(value, label) {
1501
+ if (!Array.isArray(value))
1502
+ throw new Error(`${label} must be an array.`);
1503
+ return value;
1504
+ }
1505
+ function parseUpdateResult(value) {
1506
+ const result = boundaryRecord(value, "QMD update result");
1507
+ return {
1508
+ collections: boundaryCount(result.collections, "QMD update result.collections"),
1509
+ indexed: boundaryCount(result.indexed, "QMD update result.indexed"),
1510
+ updated: boundaryCount(result.updated, "QMD update result.updated"),
1511
+ unchanged: boundaryCount(result.unchanged, "QMD update result.unchanged"),
1512
+ removed: boundaryCount(result.removed, "QMD update result.removed"),
1513
+ needsEmbedding: boundaryCount(result.needsEmbedding, "QMD update result.needsEmbedding")
1514
+ };
1515
+ }
1516
+ function parseEmbeddingFailure(value, index) {
1517
+ const label = `QMD embedding result.failures[${index}]`;
1518
+ const failure = boundaryRecord(value, label);
1519
+ return {
1520
+ path: boundaryString(failure.path, `${label}.path`),
1521
+ hash: boundaryString(failure.hash, `${label}.hash`),
1522
+ seq: boundaryCount(failure.seq, `${label}.seq`),
1523
+ attempts: boundaryCount(failure.attempts, `${label}.attempts`),
1524
+ reason: boundaryString(failure.reason, `${label}.reason`)
1525
+ };
1526
+ }
1527
+ function parseEmbeddingResult(value) {
1528
+ const result = boundaryRecord(value, "QMD embedding result");
1529
+ const failures = result.failures === undefined ? undefined : boundaryArray(result.failures, "QMD embedding result.failures").map((failure, index) => parseEmbeddingFailure(failure, index));
1530
+ return {
1531
+ docsProcessed: boundaryCount(result.docsProcessed, "QMD embedding result.docsProcessed"),
1532
+ chunksEmbedded: boundaryCount(result.chunksEmbedded, "QMD embedding result.chunksEmbedded"),
1533
+ errors: boundaryCount(result.errors, "QMD embedding result.errors"),
1534
+ ...failures === undefined ? {} : { failures },
1535
+ durationMs: boundaryNumber(result.durationMs, "QMD embedding result.durationMs")
1536
+ };
1537
+ }
1538
+ function parseSearchDocument(value, index) {
1539
+ const label = `QMD search result[${index}]`;
1540
+ const result = boundaryRecord(value, label);
1541
+ const source = result.source;
1542
+ if (source !== "fts" && source !== "vec") {
1543
+ throw new Error(`${label}.source must be "fts" or "vec".`);
1544
+ }
1545
+ const chunkPos = result.chunkPos === undefined ? undefined : boundaryCount(result.chunkPos, `${label}.chunkPos`);
1546
+ return {
1547
+ filepath: boundaryString(result.filepath, `${label}.filepath`),
1548
+ title: boundaryString(result.title, `${label}.title`),
1549
+ hash: boundaryString(result.hash, `${label}.hash`),
1550
+ docid: boundaryString(result.docid, `${label}.docid`),
1551
+ modifiedAt: boundaryString(result.modifiedAt, `${label}.modifiedAt`),
1552
+ score: boundaryNumber(result.score, `${label}.score`),
1553
+ source,
1554
+ ...chunkPos === undefined ? {} : { chunkPos }
1555
+ };
1556
+ }
1557
+ function boundedResultArray(value, label, maximum) {
1558
+ const results = boundaryArray(value, label);
1559
+ if (results.length > maximum) {
1560
+ throw new Error(`${label} returned more than the requested ${maximum} results.`);
1561
+ }
1562
+ return results;
1563
+ }
1564
+ function parseSearchResults(value, maximum) {
1565
+ return boundedResultArray(value, "QMD search results", maximum).map((result, index) => parseSearchDocument(result, index));
1566
+ }
1567
+ function boundUnknownMethod(owner, name, label) {
1568
+ const method = owner[name];
1569
+ if (typeof method !== "function")
1570
+ throw new Error(`${label}.${name} must be a function.`);
1571
+ return async (...arguments_) => {
1572
+ const returned = Reflect.apply(method, owner, arguments_);
1573
+ return await returned;
1574
+ };
1575
+ }
1576
+ function measuredDuration(startedAt, finishedAt) {
1577
+ if (!Number.isFinite(startedAt) || !Number.isFinite(finishedAt) || finishedAt < startedAt) {
1578
+ throw new Error("The query-embedding monotonic clock returned an invalid interval.");
1579
+ }
1580
+ const duration = finishedAt - startedAt;
1581
+ return duration;
1582
+ }
1583
+ function internalVectorBoundary(store, modelIdentity, now) {
1584
+ if (store.internal === undefined)
1585
+ return null;
1586
+ const internal = boundaryRecord(store.internal, "QMD store.internal");
1587
+ const llm = boundaryRecord(internal.llm, "QMD store.internal.llm");
1588
+ const getHashesNeedingEmbedding = boundUnknownMethod(internal, "getHashesNeedingEmbedding", "QMD store.internal");
1589
+ const searchVec = boundUnknownMethod(internal, "searchVec", "QMD store.internal");
1590
+ const embed = boundUnknownMethod(llm, "embed", "QMD store.internal.llm");
1591
+ const countTokens = boundUnknownMethod(llm, "countTokens", "QMD store.internal.llm");
1592
+ return {
1593
+ pendingEmbeddingCount: async () => boundaryCount(await getHashesNeedingEmbedding(modelIdentity), "QMD store.internal.getHashesNeedingEmbedding result"),
1594
+ searchVector: async (query, options) => {
1595
+ let calls = 0;
1596
+ let inputTokens = 0;
1597
+ let durationMs = 0;
1598
+ const session = Object.freeze({
1599
+ countTokens: async (text) => boundaryCount(await countTokens(boundaryString(text, "QMD query embedding text")), "QMD store.internal.llm.countTokens result"),
1600
+ embed: async (text, embedOptions) => {
1601
+ const exactText = boundaryString(text, "QMD query embedding text");
1602
+ if (calls !== 0) {
1603
+ throw new Error("QMD query-vector search must perform exactly one query embedding.");
1604
+ }
1605
+ inputTokens = boundaryCount(await countTokens(exactText), "QMD store.internal.llm.countTokens result");
1606
+ calls = 1;
1607
+ const startedAt = now();
1608
+ try {
1609
+ return await embed(exactText, embedOptions);
1610
+ } finally {
1611
+ durationMs = measuredDuration(startedAt, now());
1612
+ }
1613
+ }
1614
+ });
1615
+ const results = parseSearchResults(await searchVec(query, modelIdentity, options.limit, options.collection, session), options.limit);
1616
+ if (calls !== 1) {
1617
+ throw new Error(`QMD query-vector search must perform exactly one query embedding; observed ${calls}.`);
1618
+ }
1619
+ return {
1620
+ results,
1621
+ accounting: Object.freeze({ calls, inputTokens, durationMs })
1622
+ };
1623
+ }
1624
+ };
1625
+ }
1626
+ function parseSearchStore(value, modelIdentity, options) {
1627
+ const store = boundaryRecord(value, "QMD store");
1628
+ const close = boundUnknownMethod(store, "close", "QMD store");
1629
+ const embed = boundUnknownMethod(store, "embed", "QMD store");
1630
+ const searchLex = boundUnknownMethod(store, "searchLex", "QMD store");
1631
+ const searchVector = boundUnknownMethod(store, "searchVector", "QMD store");
1632
+ const update = boundUnknownMethod(store, "update", "QMD store");
1633
+ let internalVector = null;
1634
+ try {
1635
+ internalVector = internalVectorBoundary(store, modelIdentity, options.now);
1636
+ } catch (error) {
1637
+ if (options.requireStoreLocalVectorBoundary)
1638
+ throw error;
1639
+ }
1640
+ if (options.requireStoreLocalVectorBoundary && internalVector === null) {
1641
+ throw new Error("QMD store-local vector search is required, but store.internal.searchVec with its LLM boundary is unavailable.");
1642
+ }
1643
+ return {
1644
+ close: async () => {
1645
+ await close();
1646
+ },
1647
+ embed: async (options2) => parseEmbeddingResult(await embed(options2)),
1648
+ searchLex: async (query, options2) => parseSearchResults(await searchLex(query, options2), options2.limit),
1649
+ searchVector: internalVector?.searchVector ?? (async (query, vectorOptions) => ({
1650
+ results: parseSearchResults(await searchVector(query, vectorOptions), vectorOptions.limit),
1651
+ accounting: null
1652
+ })),
1653
+ update: async (options2) => {
1654
+ const result = parseUpdateResult(await update(options2));
1655
+ if (internalVector === null)
1656
+ return result;
1657
+ return {
1658
+ ...result,
1659
+ needsEmbedding: await internalVector.pendingEmbeddingCount()
1660
+ };
1661
+ }
1662
+ };
1663
+ }
1664
+ function parseWarmSearchStore(value, now) {
1665
+ const store = boundaryRecord(value, "QMD warm search store");
1666
+ const close = boundUnknownMethod(store, "close", "QMD warm search store");
1667
+ const searchLex = boundUnknownMethod(store, "searchLex", "QMD warm search store");
1668
+ const internalVector = internalVectorBoundary(store, recommendedEmbeddingModel, now);
1669
+ if (internalVector === null) {
1670
+ throw new Error("QMD warm search requires store.internal.searchVec with its store-local LLM boundary.");
1671
+ }
1672
+ return Object.freeze({
1673
+ close: async () => {
1674
+ await close();
1675
+ },
1676
+ pendingEmbeddingCount: internalVector.pendingEmbeddingCount,
1677
+ searchLex: async (query, options) => parseSearchResults(await searchLex(query, options), options.limit),
1678
+ searchVector: internalVector.searchVector
1679
+ });
1680
+ }
1681
+ async function closeMalformedStore(value) {
1682
+ if (!isRecord2(value))
1683
+ return;
1684
+ const close = value.close;
1685
+ if (typeof close !== "function")
1686
+ return;
1687
+ try {
1688
+ const returned = Reflect.apply(close, value, []);
1689
+ await returned;
1690
+ } catch {}
1691
+ }
1692
+ async function openedSearchStore(value, modelIdentity, options) {
1693
+ try {
1694
+ return parseSearchStore(value, modelIdentity, options);
1695
+ } catch (error) {
1696
+ await closeMalformedStore(value);
1697
+ throw error;
1698
+ }
1699
+ }
1700
+ async function openedWarmSearchStore(value, now) {
1701
+ try {
1702
+ return parseWarmSearchStore(value, now);
1703
+ } catch (error) {
1704
+ await closeMalformedStore(value);
1705
+ throw error;
1706
+ }
1707
+ }
1708
+ function sameStableFileMetadata(left, right) {
1709
+ return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode && left.nlink === right.nlink && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
1710
+ }
1711
+ function missingFile(error) {
1712
+ return isRecord2(error) && error.code === "ENOENT";
1713
+ }
1714
+ async function writeAll(handle, bytes, length, position) {
1715
+ let written = 0;
1716
+ while (written < length) {
1717
+ const result = await handle.write(bytes, written, length - written, position + written);
1718
+ if (result.bytesWritten === 0) {
1719
+ throw new Error("The isolated QMD snapshot stopped accepting bytes.");
1720
+ }
1721
+ written += result.bytesWritten;
1722
+ }
1723
+ }
1724
+ async function copyStableSnapshotFile(source, destination, label, maximumBytes, expected) {
1725
+ if (!Number.isSafeInteger(expected.bytes) || expected.bytes < 0 || expected.bytes > maximumBytes || !SHA256.test(expected.sha256))
1726
+ throw new TypeError(`${label} seal is invalid.`);
1727
+ const pathBefore = await lstat3(source, { bigint: true });
1728
+ if (pathBefore.isSymbolicLink() || !pathBefore.isFile() || pathBefore.nlink !== 1n || pathBefore.size > BigInt(maximumBytes) || pathBefore.size !== BigInt(expected.bytes)) {
1729
+ throw new Error(`${label} must be one bounded, singly linked regular file.`);
1730
+ }
1731
+ const sourceHandle = await open3(source, constants3.O_RDONLY | constants3.O_NOFOLLOW);
1732
+ let destinationHandle;
1733
+ try {
1734
+ const before = await sourceHandle.stat({ bigint: true });
1735
+ if (!sameStableFileMetadata(pathBefore, before)) {
1736
+ throw new Error(`${label} changed before its isolated read snapshot was opened.`);
1737
+ }
1738
+ destinationHandle = await open3(destination, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
1739
+ const buffer = new Uint8Array(1024 * 1024);
1740
+ const hash = createHash2("sha256");
1741
+ let copied = 0;
1742
+ while (copied < Number(before.size)) {
1743
+ const requested = Math.min(buffer.byteLength, Number(before.size) - copied);
1744
+ const { bytesRead } = await sourceHandle.read(buffer, 0, requested, copied);
1745
+ if (bytesRead === 0) {
1746
+ throw new Error(`${label} ended before its declared size.`);
1747
+ }
1748
+ await writeAll(destinationHandle, buffer, bytesRead, copied);
1749
+ hash.update(buffer.subarray(0, bytesRead));
1750
+ copied += bytesRead;
1751
+ }
1752
+ await destinationHandle.sync();
1753
+ const [after, pathAfter] = await Promise.all([
1754
+ sourceHandle.stat({ bigint: true }),
1755
+ lstat3(source, { bigint: true })
1756
+ ]);
1757
+ if (copied !== Number(before.size) || !sameStableFileMetadata(before, after) || !sameStableFileMetadata(after, pathAfter)) {
1758
+ throw new Error(`${label} changed while its isolated read snapshot was copied.`);
1759
+ }
1760
+ const observed = Object.freeze({ bytes: copied, sha256: hash.digest("hex") });
1761
+ if (observed.bytes !== expected.bytes || observed.sha256 !== expected.sha256) {
1762
+ throw new Error(`${label} does not match its sealed byte commitment.`);
1763
+ }
1764
+ return observed;
1765
+ } finally {
1766
+ await Promise.allSettled([
1767
+ sourceHandle.close(),
1768
+ destinationHandle?.close() ?? Promise.resolve()
1769
+ ]);
1770
+ }
1771
+ }
1772
+ async function assertAbsentSnapshotSidecar(path, label) {
1773
+ try {
1774
+ await lstat3(path);
1775
+ } catch (error) {
1776
+ if (missingFile(error))
1777
+ return;
1778
+ throw error;
1779
+ }
1780
+ throw new Error(`${label} appeared while the isolated read snapshot was copied.`);
1781
+ }
1782
+ async function createIsolatedQmdDatabaseSnapshot(database, seal) {
1783
+ const directory = await mkdtemp(join3(tmpdir(), "hraness-kb-qmd-reader."));
1784
+ await chmod(directory, 448);
1785
+ let cleaned = false;
1786
+ const cleanup = async () => {
1787
+ if (cleaned)
1788
+ return;
1789
+ cleaned = true;
1790
+ await rm3(directory, { recursive: true, force: true });
1791
+ };
1792
+ try {
1793
+ const isolatedDatabase = join3(directory, "snapshot.sqlite");
1794
+ if (seal.wal !== null || seal.shm !== null || seal.journal !== null) {
1795
+ throw new TypeError("Strict warm semantic snapshots require checkpointed SQLite state.");
1796
+ }
1797
+ const sourceWal = `${database}-wal`;
1798
+ const sourceShm = `${database}-shm`;
1799
+ const sourceJournal = `${database}-journal`;
1800
+ await Promise.all([
1801
+ assertAbsentSnapshotSidecar(sourceWal, "Semantic database WAL"),
1802
+ assertAbsentSnapshotSidecar(sourceShm, "Semantic database SHM"),
1803
+ assertAbsentSnapshotSidecar(sourceJournal, "Semantic database rollback journal")
1804
+ ]);
1805
+ const databaseSeal = await copyStableSnapshotFile(database, isolatedDatabase, "Semantic database", MAX_SEMANTIC_READ_SNAPSHOT_BYTES, seal.database);
1806
+ await Promise.all([
1807
+ assertAbsentSnapshotSidecar(sourceWal, "Semantic database WAL"),
1808
+ assertAbsentSnapshotSidecar(sourceShm, "Semantic database SHM"),
1809
+ assertAbsentSnapshotSidecar(sourceJournal, "Semantic database rollback journal")
1810
+ ]);
1811
+ if (databaseSeal.bytes > MAX_SEMANTIC_READ_SNAPSHOT_BYTES) {
1812
+ throw new RangeError("Semantic database exceeds the read-snapshot byte bound.");
1813
+ }
1814
+ return Object.freeze({ database: isolatedDatabase, cleanup });
1815
+ } catch (error) {
1816
+ await cleanup();
1817
+ throw error;
1818
+ }
1819
+ }
1820
+ function aggregateCloseFailures(settlements, label) {
1821
+ const failures = settlements.filter((result) => result.status === "rejected");
1822
+ if (failures.length > 0) {
1823
+ throw new AggregateError(failures.map(({ reason }) => reason), `${label} did not close cleanly.`);
1824
+ }
1825
+ }
1826
+ async function closeIsolatedStore(close, snapshot2, label) {
1827
+ const settlements = [];
1828
+ try {
1829
+ await close();
1830
+ settlements.push({ status: "fulfilled", value: undefined });
1831
+ } catch (reason) {
1832
+ settlements.push({ status: "rejected", reason });
1833
+ }
1834
+ try {
1835
+ await snapshot2.cleanup();
1836
+ settlements.push({ status: "fulfilled", value: undefined });
1837
+ } catch (reason) {
1838
+ settlements.push({ status: "rejected", reason });
1839
+ }
1840
+ aggregateCloseFailures(settlements, label);
1841
+ }
1842
+ function isolatedWarmSearchStore(store, snapshot2) {
1843
+ let closePromise;
1844
+ return Object.freeze({
1845
+ ...store,
1846
+ close: () => {
1847
+ closePromise ??= closeIsolatedStore(store.close, snapshot2, "Isolated QMD warm store");
1848
+ return closePromise;
1849
+ }
1850
+ });
1851
+ }
1852
+ function isolatedAttestationStore(store, snapshot2) {
1853
+ let closePromise;
1854
+ return Object.freeze({
1855
+ ...store,
1856
+ close: () => {
1857
+ closePromise ??= closeIsolatedStore(store.close, snapshot2, "Isolated QMD attestation store");
1858
+ return closePromise;
1859
+ }
1860
+ });
1861
+ }
1862
+ function storeConfig(root, embeddingModelSource) {
1863
+ return {
1864
+ global_context: globalContext,
1865
+ collections: {
1866
+ [collectionName]: {
1867
+ path: root,
1868
+ pattern: markdownPattern,
1869
+ ignore: ignoredPatterns,
1870
+ context: collectionContext
1871
+ }
1872
+ },
1873
+ models: { embed: embeddingModelSource }
1874
+ };
1875
+ }
1876
+ async function defaultCreateStore(options) {
1877
+ const loaded = await import(qmdModuleSpecifier);
1878
+ const module = boundaryRecord(loaded, "QMD module");
1879
+ const createStore = boundUnknownMethod(module, "createStore", "QMD module");
1880
+ return await createStore(options);
1881
+ }
1882
+ async function defaultCreateWarmSearchStore(options) {
1883
+ const loaded = await import(qmdModuleSpecifier);
1884
+ const module = boundaryRecord(loaded, "QMD module");
1885
+ const createStore = boundUnknownMethod(module, "createStore", "QMD module");
1886
+ const created = await createStore({ dbPath: options.dbPath });
1887
+ let localLlm;
1888
+ try {
1889
+ const store = boundaryRecord(created, "QMD warm search store");
1890
+ const internal = boundaryRecord(store.internal, "QMD warm search store.internal");
1891
+ const previousLlm = boundaryRecord(internal.llm, "QMD warm search store.internal.llm");
1892
+ const previousDispose = boundUnknownMethod(previousLlm, "dispose", "QMD warm search store.internal.llm");
1893
+ const llmSpecifier = new URL("./llm.js", import.meta.resolve(qmdModuleSpecifier)).href;
1894
+ const loadedLlm = await import(llmSpecifier);
1895
+ const llmModule = boundaryRecord(loadedLlm, "QMD LLM module");
1896
+ if (typeof llmModule.LlamaCpp !== "function") {
1897
+ throw new Error("QMD LLM module.LlamaCpp must be a constructor.");
1898
+ }
1899
+ const constructed = Reflect.construct(llmModule.LlamaCpp, [{
1900
+ embedModel: options.embeddingModelSource,
1901
+ inactivityTimeoutMs: 300000,
1902
+ disposeModelsOnInactivity: true
1903
+ }]);
1904
+ localLlm = boundaryRecord(constructed, "QMD warm local LLM");
1905
+ if (localLlm.embedModelName !== options.embeddingModelSource) {
1906
+ throw new Error("QMD warm local LLM did not retain the verified model source.");
1907
+ }
1908
+ const localDispose = boundUnknownMethod(localLlm, "dispose", "QMD warm local LLM");
1909
+ if (!Reflect.set(internal, "llm", localLlm)) {
1910
+ throw new Error("QMD warm store rejected its verified local LLM boundary.");
1911
+ }
1912
+ await previousDispose();
1913
+ const close = boundUnknownMethod(store, "close", "QMD warm search store");
1914
+ let closed = false;
1915
+ return Object.freeze({
1916
+ ...store,
1917
+ internal,
1918
+ close: async () => {
1919
+ if (closed)
1920
+ return;
1921
+ closed = true;
1922
+ const settlements = await Promise.allSettled([localDispose(), close()]);
1923
+ const failures = settlements.filter((result) => result.status === "rejected");
1924
+ if (failures.length > 0) {
1925
+ throw new AggregateError(failures.map(({ reason }) => reason), "QMD warm store did not close cleanly.");
1926
+ }
1927
+ }
1928
+ });
1929
+ } catch (error) {
1930
+ if (localLlm !== undefined) {
1931
+ const dispose = localLlm.dispose;
1932
+ if (typeof dispose === "function") {
1933
+ await Promise.resolve(Reflect.apply(dispose, localLlm, [])).catch(() => {
1934
+ return;
1935
+ });
1936
+ }
1937
+ }
1938
+ await closeMalformedStore(created);
1939
+ throw error;
1940
+ }
1941
+ }
1942
+ async function defaultCreateAttestationStore(options) {
1943
+ const loaded = await import(qmdModuleSpecifier);
1944
+ const module = boundaryRecord(loaded, "QMD module");
1945
+ const createStore = boundUnknownMethod(module, "createStore", "QMD module");
1946
+ return await createStore(options);
1947
+ }
1948
+ async function defaultOpenCheckpointDatabase(database) {
1949
+ const moduleSpecifier = "bun:sqlite";
1950
+ const loaded = await import(moduleSpecifier);
1951
+ const module = boundaryRecord(loaded, "Bun SQLite module");
1952
+ if (typeof module.Database !== "function") {
1953
+ throw new TypeError("Bun SQLite module.Database must be a constructor.");
1954
+ }
1955
+ const opened = boundaryRecord(Reflect.construct(module.Database, [database, { create: false, strict: true }]), "Bun SQLite checkpoint database");
1956
+ const query = boundUnknownMethod(opened, "query", "Bun SQLite checkpoint database");
1957
+ const close = boundUnknownMethod(opened, "close", "Bun SQLite checkpoint database");
1958
+ return Object.freeze({
1959
+ checkpoint: async () => {
1960
+ const walStatement = boundaryRecord(await query("PRAGMA wal_checkpoint(TRUNCATE)"), "Bun SQLite WAL checkpoint statement");
1961
+ const walGet = boundUnknownMethod(walStatement, "get", "Bun SQLite WAL checkpoint statement");
1962
+ const modeStatement = boundaryRecord(await query("PRAGMA journal_mode = DELETE"), "Bun SQLite journal-mode statement");
1963
+ const modeGet = boundUnknownMethod(modeStatement, "get", "Bun SQLite journal-mode statement");
1964
+ return Object.freeze({ wal: await walGet(), mode: await modeGet() });
1965
+ },
1966
+ close: async () => {
1967
+ await close();
1968
+ }
1969
+ });
1970
+ }
1971
+ async function openedCheckpointDatabase(value) {
1972
+ try {
1973
+ const database = boundaryRecord(value, "Semantic checkpoint database");
1974
+ const checkpoint = boundUnknownMethod(database, "checkpoint", "Semantic checkpoint database");
1975
+ const close = boundUnknownMethod(database, "close", "Semantic checkpoint database");
1976
+ return Object.freeze({
1977
+ checkpoint: async () => {
1978
+ return await checkpoint();
1979
+ },
1980
+ close: async () => {
1981
+ await close();
1982
+ }
1983
+ });
1984
+ } catch (error) {
1985
+ await closeMalformedStore(value);
1986
+ throw error;
1987
+ }
1988
+ }
1989
+ function parseSemanticAttestationStore(value) {
1990
+ const store = boundaryRecord(value, "QMD attestation store");
1991
+ const close = boundUnknownMethod(store, "close", "QMD attestation store");
1992
+ const internal = boundaryRecord(store.internal, "QMD attestation store.internal");
1993
+ const llm = boundaryRecord(internal.llm, "QMD attestation store.internal.llm");
1994
+ const getHashesNeedingEmbedding = boundUnknownMethod(internal, "getHashesNeedingEmbedding", "QMD attestation store.internal");
1995
+ boundUnknownMethod(internal, "searchVec", "QMD attestation store.internal");
1996
+ boundUnknownMethod(llm, "countTokens", "QMD attestation store.internal.llm");
1997
+ boundUnknownMethod(llm, "embed", "QMD attestation store.internal.llm");
1998
+ return Object.freeze({
1999
+ close: async () => {
2000
+ await close();
2001
+ },
2002
+ pendingEmbeddingCount: async () => boundaryCount(await getHashesNeedingEmbedding(recommendedEmbeddingModel), "QMD attestation store.internal.getHashesNeedingEmbedding result")
2003
+ });
2004
+ }
2005
+ async function openedSemanticAttestationStore(value) {
2006
+ try {
2007
+ return parseSemanticAttestationStore(value);
2008
+ } catch (error) {
2009
+ await closeMalformedStore(value);
2010
+ throw error;
2011
+ }
2012
+ }
2013
+ async function openStore(root, database, embeddingModelSource, dependencies, requireStoreLocalVectorBoundary = false) {
2014
+ await mkdir2(dirname3(database), { recursive: true });
2015
+ const created = await (dependencies.createStore ?? defaultCreateStore)({
2016
+ dbPath: database,
2017
+ config: storeConfig(root, embeddingModelSource)
2018
+ });
2019
+ return await openedSearchStore(created, recommendedEmbeddingModel, {
2020
+ requireStoreLocalVectorBoundary,
2021
+ now: dependencies.now ?? performance.now.bind(performance)
2022
+ });
2023
+ }
2024
+ async function assertExactWarmProjectionFile(path, expected, label) {
2025
+ const handle = await open3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
2026
+ try {
2027
+ const before = await handle.stat();
2028
+ if (!before.isFile() || before.nlink !== 1 || before.size !== expected.byteLength) {
2029
+ throw new Error(`${label} does not match the immutable warm projection.`);
2030
+ }
2031
+ const observed = await handle.readFile();
2032
+ const after = await handle.stat();
2033
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || !observed.equals(expected)) {
2034
+ throw new Error(`${label} does not match the immutable warm projection.`);
2035
+ }
2036
+ } finally {
2037
+ await handle.close();
2038
+ }
2039
+ }
2040
+ async function assertExistingWarmProjection(description, notes) {
2041
+ let canonicalGeneration;
2042
+ try {
2043
+ canonicalGeneration = await realpath3(description.generationPath);
2044
+ } catch (error) {
2045
+ throw new Error("The immutable warm semantic projection is absent.", { cause: error });
2046
+ }
2047
+ if (canonicalGeneration !== description.generationPath) {
2048
+ throw new Error("The immutable warm semantic projection changed identity.");
2049
+ }
2050
+ await assertExactWarmProjectionFile(join3(canonicalGeneration, "manifest.json"), Buffer.from(description.manifestText, "utf8"), "Semantic projection manifest");
2051
+ const notesByPath = new Map(notes.map((note) => [note.path, note]));
2052
+ if (notesByPath.size !== description.manifest.notes.length) {
2053
+ throw new Error("The immutable warm semantic projection note population drifted.");
2054
+ }
2055
+ for (const entry of description.manifest.notes) {
2056
+ const note = notesByPath.get(entry.path);
2057
+ if (note === undefined) {
2058
+ throw new Error(`Semantic projection note ${JSON.stringify(entry.path)} is absent.`);
2059
+ }
2060
+ const expected = Buffer.from(note.content, "utf8");
2061
+ if (expected.byteLength !== entry.bytes || createHash2("sha256").update(expected).digest("hex") !== entry.sha256) {
2062
+ throw new Error(`Semantic projection note ${JSON.stringify(entry.path)} drifted.`);
2063
+ }
2064
+ await assertExactWarmProjectionFile(resolve3(canonicalGeneration, ...entry.path.split("/")), expected, `Semantic projection note ${JSON.stringify(entry.path)}`);
2065
+ }
2066
+ }
2067
+ async function openWarmSearchStore(database, databaseSnapshotSeal, embeddingModelSource, dependencies) {
2068
+ const snapshot2 = await createIsolatedQmdDatabaseSnapshot(database, databaseSnapshotSeal);
2069
+ try {
2070
+ const created = await (dependencies.createWarmSearchStore ?? defaultCreateWarmSearchStore)({
2071
+ dbPath: snapshot2.database,
2072
+ embeddingModelSource
2073
+ });
2074
+ const store = await openedWarmSearchStore(created, dependencies.now ?? performance.now.bind(performance));
2075
+ return isolatedWarmSearchStore(store, snapshot2);
2076
+ } catch (error) {
2077
+ await snapshot2.cleanup();
2078
+ throw error;
2079
+ }
2080
+ }
2081
+ async function openAttestationStore(database, databaseSnapshotSeal, dependencies) {
2082
+ const snapshot2 = await createIsolatedQmdDatabaseSnapshot(database, databaseSnapshotSeal);
2083
+ try {
2084
+ const created = await (dependencies.createAttestationStore ?? defaultCreateAttestationStore)({ dbPath: snapshot2.database });
2085
+ const store = await openedSemanticAttestationStore(created);
2086
+ return isolatedAttestationStore(store, snapshot2);
2087
+ } catch (error) {
2088
+ await snapshot2.cleanup();
2089
+ throw error;
2090
+ }
2091
+ }
2092
+ async function checkpointSemanticWarmCache(options, dependencies = {}) {
2093
+ const root = await resolvedDirectory(options.root);
2094
+ const database = await resolveSemanticDatabase(databaseFor(root, options.database, dependencies), root);
2095
+ const before = await lstat3(database);
2096
+ if (before.isSymbolicLink() || !before.isFile() || before.nlink !== 1) {
2097
+ throw new TypeError("The semantic database to checkpoint must be a singly linked regular file.");
2098
+ }
2099
+ const opened = await (dependencies.openCheckpointDatabase ?? defaultOpenCheckpointDatabase)(database);
2100
+ const checkpoint = await openedCheckpointDatabase(opened);
2101
+ try {
2102
+ const outcome = boundaryRecord(await checkpoint.checkpoint(), "Semantic checkpoint result");
2103
+ const wal = boundaryRecord(outcome.wal, "Semantic checkpoint result.wal");
2104
+ const busy = boundaryCount(wal.busy, "Semantic checkpoint result.wal.busy");
2105
+ const log = boundaryCount(wal.log, "Semantic checkpoint result.wal.log");
2106
+ const checkpointed = boundaryCount(wal.checkpointed, "Semantic checkpoint result.wal.checkpointed");
2107
+ const mode = boundaryRecord(outcome.mode, "Semantic checkpoint result.mode");
2108
+ if (busy !== 0 || log !== checkpointed) {
2109
+ throw new Error("Semantic WAL checkpoint did not copy every committed frame.");
2110
+ }
2111
+ if (boundaryString(mode.journal_mode, "Semantic checkpoint result.mode.journal_mode") !== "delete") {
2112
+ throw new Error("Semantic database did not leave WAL journal mode.");
2113
+ }
2114
+ } catch (error) {
2115
+ try {
2116
+ await checkpoint.close();
2117
+ } catch (cleanupError) {
2118
+ throw new AggregateError([error, cleanupError], "Semantic database checkpoint and cleanup both failed.", { cause: error });
2119
+ }
2120
+ throw error;
2121
+ }
2122
+ await checkpoint.close();
2123
+ for (const sidecar of [`${database}-wal`, `${database}-shm`]) {
2124
+ let metadata;
2125
+ try {
2126
+ metadata = await lstat3(sidecar);
2127
+ } catch (error) {
2128
+ if (missingFile(error))
2129
+ continue;
2130
+ throw error;
2131
+ }
2132
+ if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.nlink !== 1) {
2133
+ throw new TypeError("Semantic checkpoint sidecars must be singly linked regular files.");
2134
+ }
2135
+ await rm3(sidecar);
2136
+ }
2137
+ await Promise.all([
2138
+ assertAbsentSnapshotSidecar(`${database}-wal`, "Semantic database WAL"),
2139
+ assertAbsentSnapshotSidecar(`${database}-shm`, "Semantic database SHM"),
2140
+ assertAbsentSnapshotSidecar(`${database}-journal`, "Semantic database rollback journal")
2141
+ ]);
2142
+ return Object.freeze({ database, wal: null, shm: null, journal: null });
2143
+ }
2144
+ async function attestSemanticWarmCache(options, dependencies = {}) {
2145
+ const embeddingModel = retainVerifiedEmbeddingModelLease(options.embeddingModelLease);
2146
+ try {
2147
+ const root = await resolvedDirectory(options.root);
2148
+ const database = await resolveSemanticDatabase(databaseFor(root, options.database, dependencies), root);
2149
+ if (Buffer.byteLength(database, "utf8") > MAX_SEMANTIC_DATABASE_IDENTITY_BYTES) {
2150
+ throw new RangeError(`Semantic database identity exceeds ${MAX_SEMANTIC_DATABASE_IDENTITY_BYTES.toLocaleString("en-US")} UTF-8 bytes.`);
2151
+ }
2152
+ let databaseState;
2153
+ try {
2154
+ databaseState = await stat(database);
2155
+ } catch (error) {
2156
+ throw new Error("The warm semantic database must already exist.", { cause: error });
2157
+ }
2158
+ if (!databaseState.isFile()) {
2159
+ throw new Error("The warm semantic database must already be a regular file.");
2160
+ }
2161
+ const store = await openAttestationStore(database, options.databaseSnapshotSeal, dependencies);
2162
+ try {
2163
+ const pendingEmbeddings = await store.pendingEmbeddingCount();
2164
+ if (pendingEmbeddings !== 0) {
2165
+ throw new Error(`Warm semantic cache is not ready: ${pendingEmbeddings} embedding input(s) remain pending.`);
2166
+ }
2167
+ return Object.freeze({
2168
+ model: recommendedEmbeddingModel,
2169
+ database,
2170
+ pendingEmbeddings: 0
2171
+ });
2172
+ } finally {
2173
+ await store.close();
2174
+ }
2175
+ } finally {
2176
+ await embeddingModel.release();
2177
+ }
2178
+ }
2179
+ async function openSemanticWarmSearchSession(options, dependencies = {}) {
2180
+ const embeddingModel = retainVerifiedEmbeddingModelLease(options.embeddingModelLease);
2181
+ let store;
2182
+ try {
2183
+ const root = await resolvedDirectory(options.root);
2184
+ const database = await resolveSemanticDatabase(databaseFor(root, options.database, dependencies), root);
2185
+ const databaseState = await stat(database).catch((error) => {
2186
+ throw new Error("The warm semantic database must already exist.", { cause: error });
2187
+ });
2188
+ if (!databaseState.isFile()) {
2189
+ throw new Error("The warm semantic database must already be a regular file.");
2190
+ }
2191
+ const snapshot2 = await semanticSnapshot(root, dependencies);
2192
+ const description = await describeSemanticProjection(database, root, snapshot2.notes, semanticIndexIdentity);
2193
+ await assertExistingWarmProjection(description, snapshot2.notes);
2194
+ store = await openWarmSearchStore(database, options.databaseSnapshotSeal, embeddingModel.source, dependencies);
2195
+ const pendingEmbeddings = await store.pendingEmbeddingCount();
2196
+ if (pendingEmbeddings !== 0) {
2197
+ throw new Error(`Warm semantic cache is not ready: ${pendingEmbeddings} embedding input(s) remain pending.`);
2198
+ }
2199
+ const notesByPath = new Map(snapshot2.notes.map((note) => [note.path, note]));
2200
+ const contentHashesByPath = new Map(description.manifest.notes.map(({ path, sha256 }) => [path, sha256]));
2201
+ const notesByQmdPath = qmdNoteLookup(snapshot2.notes, contentHashesByPath);
2202
+ const connectionsById = new Map(snapshot2.analysis.noteConnections.map((connection) => [connection.id, connection]));
2203
+ const update = Object.freeze({
2204
+ collections: 1,
2205
+ indexed: 0,
2206
+ updated: 0,
2207
+ unchanged: snapshot2.notes.length,
2208
+ removed: 0,
2209
+ needsEmbedding: 0
2210
+ });
2211
+ const context = {
2212
+ root,
2213
+ projectionRoot: description.generationPath,
2214
+ database,
2215
+ store,
2216
+ update,
2217
+ ensureEmbedding: () => Promise.resolve(null),
2218
+ notesByPath,
2219
+ notesByQmdPath,
2220
+ contentHashesByPath,
2221
+ connectionsById
2222
+ };
2223
+ let tail = Promise.resolve();
2224
+ let closeRequested = false;
2225
+ let closePromise;
2226
+ const serialize = (operation) => {
2227
+ const result = tail.then(operation);
2228
+ tail = result.then(() => {
2229
+ return;
2230
+ }, () => {
2231
+ return;
2232
+ });
2233
+ return result;
2234
+ };
2235
+ const ownedStore = store;
2236
+ store = undefined;
2237
+ return Object.freeze({
2238
+ root,
2239
+ database,
2240
+ model: recommendedEmbeddingModel,
2241
+ update,
2242
+ search: (searchOptions) => {
2243
+ if (closeRequested) {
2244
+ return Promise.reject(new Error("Semantic warm search session is closed."));
2245
+ }
2246
+ return serialize(() => executeSemanticSearch(context, searchOptions));
2247
+ },
2248
+ close: () => {
2249
+ if (closePromise !== undefined)
2250
+ return closePromise;
2251
+ closeRequested = true;
2252
+ closePromise = serialize(async () => {
2253
+ try {
2254
+ await ownedStore.close();
2255
+ } finally {
2256
+ await embeddingModel.release();
2257
+ }
2258
+ });
2259
+ return closePromise;
2260
+ }
2261
+ });
2262
+ } catch (error) {
2263
+ try {
2264
+ await store?.close();
2265
+ } finally {
2266
+ await embeddingModel.release();
2267
+ }
2268
+ throw error;
2269
+ }
2270
+ }
2271
+ function databaseFor(root, requested, dependencies) {
2272
+ if (requested === undefined)
2273
+ return semanticDatabasePath(root, dependencies);
2274
+ return resolve3(requested);
2275
+ }
2276
+ async function embedChanged(store, update, force) {
2277
+ if (!force && update.needsEmbedding === 0)
2278
+ return null;
2279
+ return await store.embed({
2280
+ collection: collectionName,
2281
+ force,
2282
+ model: recommendedEmbeddingModel,
2283
+ chunkStrategy: embeddingChunkStrategy
2284
+ });
2285
+ }
2286
+ async function semanticSnapshot(root, dependencies) {
2287
+ return await (dependencies.scanVault ?? ((vaultRoot) => scanVault(vaultRoot, { mentionScope: false })))(root);
2288
+ }
2289
+ async function indexSemanticVault(options, dependencies = {}) {
2290
+ const embeddingModel = await verifiedIndexEmbeddingModelSource(options.embeddingModelFile, dependencies);
2291
+ try {
2292
+ const root = await resolvedDirectory(options.root);
2293
+ const databaseCandidate = await resolveSemanticDatabase(databaseFor(root, options.database, dependencies), root);
2294
+ const snapshot2 = await semanticSnapshot(root, dependencies);
2295
+ const description = await describeSemanticProjection(databaseCandidate, root, snapshot2.notes, semanticIndexIdentity);
2296
+ const database = description.database;
2297
+ return await withSemanticGenerationWriterLease(database, description.manifest.generation, async () => {
2298
+ const projection = await prepareSemanticProjection(description, snapshot2.notes);
2299
+ let store;
2300
+ try {
2301
+ store = await openStore(projection.root, database, embeddingModel.source, dependencies);
2302
+ const update = await store.update({ collections: [collectionName] });
2303
+ const embedding = await embedChanged(store, update, options.force ?? false);
2304
+ return { root, database, model: recommendedEmbeddingModel, update, embedding };
2305
+ } finally {
2306
+ try {
2307
+ await store?.close();
2308
+ } finally {
2309
+ await projection.release();
2310
+ }
2311
+ }
2312
+ }, {
2313
+ ...dependencies.writerLease,
2314
+ excludeReaders: options.force === true
2315
+ });
2316
+ } finally {
2317
+ await embeddingModel.release();
2318
+ }
2319
+ }
2320
+ function qmdEmojiToHex(value) {
2321
+ return value.replace(/(?:\p{So}\p{Mn}?|\p{Sk})+/gu, (run) => [...run].filter((character) => /\p{So}|\p{Sk}/u.test(character)).map((character) => character.codePointAt(0)?.toString(16) ?? "").join("-"));
2322
+ }
2323
+ function qmdHandelize(path) {
2324
+ if (path.trim() === "")
2325
+ return null;
2326
+ const segments = path.split("/").filter((segment) => segment !== "");
2327
+ const lastSegment = segments.at(-1) ?? "";
2328
+ const filenameWithoutExtension = lastSegment.replace(/\.[^.]+$/u, "");
2329
+ if (!/[\p{L}\p{N}\p{So}\p{Sk}$]/u.test(filenameWithoutExtension))
2330
+ return null;
2331
+ const result = path.replaceAll("___", "/").split("/").map((rawSegment, index, allSegments) => {
2332
+ const segment = qmdEmojiToHex(rawSegment);
2333
+ if (index === allSegments.length - 1) {
2334
+ const extension = segment.match(/(\.[a-z0-9]+)$/iu)?.[1] ?? "";
2335
+ const name = extension === "" ? segment : segment.slice(0, -extension.length);
2336
+ return name.replace(/[^\p{L}\p{N}$]+/gu, "-").replace(/^-+|-+$/gu, "") + extension;
2337
+ }
2338
+ return segment.replace(/[^\p{L}\p{N}$]+/gu, "-").replace(/^-+|-+$/gu, "");
2339
+ }).filter((segment) => segment !== "").join("/");
2340
+ return result === "" ? null : result;
2341
+ }
2342
+ function qmdNoteLookup(notes, contentHashesByPath) {
2343
+ const lookup = new Map;
2344
+ for (const note of notes) {
2345
+ const qmdPath = qmdHandelize(note.path);
2346
+ if (qmdPath === null)
2347
+ continue;
2348
+ const contentHash = contentHashesByPath.get(note.path);
2349
+ if (contentHash === undefined) {
2350
+ throw new Error(`Semantic projection lost the hash for ${JSON.stringify(note.path)}.`);
2351
+ }
2352
+ const byHash = lookup.get(qmdPath) ?? new Map;
2353
+ const candidates = byHash.get(contentHash) ?? [];
2354
+ candidates.push(note);
2355
+ byHash.set(contentHash, candidates);
2356
+ lookup.set(qmdPath, byHash);
2357
+ }
2358
+ return lookup;
2359
+ }
2360
+ function qmdVirtualNotePath(filepath) {
2361
+ if (!filepath.startsWith("qmd://"))
2362
+ return;
2363
+ const prefix = `qmd://${collectionName}/`;
2364
+ if (!filepath.startsWith(prefix))
2365
+ return null;
2366
+ const path = filepath.slice(prefix.length);
2367
+ const segments = path.split("/");
2368
+ const hasControlCharacter = [...path].some((character) => {
2369
+ const codePoint = character.codePointAt(0) ?? 0;
2370
+ return codePoint <= 31 || codePoint === 127;
2371
+ });
2372
+ if (path === "" || path.includes("\\") || path.includes("?") || path.includes("#") || path.includes("%") || hasControlCharacter || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
2373
+ return null;
2374
+ }
2375
+ return path;
2376
+ }
2377
+ async function resolvedSearchNote(projectionRoot, result, notesByPath, notesByQmdPath, contentHashesByPath) {
2378
+ const virtualPath = qmdVirtualNotePath(result.filepath);
2379
+ if (virtualPath !== undefined) {
2380
+ if (virtualPath === null)
2381
+ return null;
2382
+ const candidates = notesByQmdPath.get(virtualPath)?.get(result.hash) ?? [];
2383
+ const candidate2 = candidates[0];
2384
+ return candidates.length === 1 && candidate2 !== undefined && contentHashesByPath.get(candidate2.path) === result.hash ? candidate2 : null;
2385
+ }
2386
+ if (!isAbsolute2(result.filepath))
2387
+ return null;
2388
+ let filepath;
2389
+ try {
2390
+ filepath = await realpath3(resolve3(result.filepath));
2391
+ } catch {
2392
+ return null;
2393
+ }
2394
+ const candidate = relative3(projectionRoot, filepath);
2395
+ if (candidate === "" || candidate === ".." || candidate.startsWith(`..${sep3}`) || isAbsolute2(candidate)) {
2396
+ return null;
2397
+ }
2398
+ const note = notesByPath.get(candidate.split(sep3).join("/"));
2399
+ return note !== undefined && contentHashesByPath.get(note.path) === result.hash ? note : null;
2400
+ }
2401
+ function queryOffset(body, query, suggested) {
2402
+ if (suggested !== undefined && Number.isSafeInteger(suggested) && suggested >= 0 && suggested <= body.length) {
2403
+ return suggested;
2404
+ }
2405
+ const terms = query.toLocaleLowerCase("en-US").match(/[\p{L}\p{N}]{2,}/gu) ?? [];
2406
+ const lowerBody = body.toLocaleLowerCase("en-US");
2407
+ for (const term of terms.toSorted((left, right) => right.length - left.length)) {
2408
+ const offset = lowerBody.indexOf(term);
2409
+ if (offset !== -1)
2410
+ return offset;
2411
+ }
2412
+ return 0;
2413
+ }
2414
+ function boundedSnippet(body, offset) {
2415
+ const normalized = body.replaceAll(`\r
2416
+ `, `
2417
+ `).replaceAll("\r", `
2418
+ `);
2419
+ const maximum = 600;
2420
+ const start = Math.max(0, Math.min(normalized.length, offset) - 180);
2421
+ const end = Math.min(normalized.length, start + maximum);
2422
+ const value = normalized.slice(start, end).replace(/\s+/gu, " ").trim();
2423
+ return `${start > 0 ? "\u2026" : ""}${value}${end < normalized.length ? "\u2026" : ""}`;
2424
+ }
2425
+ async function searchHit(projectionRoot, query, result, notesByPath, notesByQmdPath, contentHashesByPath, connectionsById) {
2426
+ const note = await resolvedSearchNote(projectionRoot, result, notesByPath, notesByQmdPath, contentHashesByPath);
2427
+ if (note === null)
2428
+ return null;
2429
+ const connection = connectionsById.get(note.id);
2430
+ const body = note.content;
2431
+ const offset = queryOffset(body, query, result.chunkPos);
2432
+ return {
2433
+ path: note.path,
2434
+ title: note.title,
2435
+ score: result.score,
2436
+ source: result.source,
2437
+ docid: result.docid,
2438
+ modifiedAt: result.modifiedAt,
2439
+ ...body === "" ? {} : { line: body.slice(0, offset).split(`
2440
+ `).length },
2441
+ snippet: boundedSnippet(body, offset),
2442
+ tags: note.tags,
2443
+ metadata: note.metadata,
2444
+ inboundContextualCount: connection?.inboundContextualCount ?? 0,
2445
+ outboundContextualCount: connection?.outboundContextualCount ?? 0,
2446
+ backlinks: connection?.backlinks ?? []
2447
+ };
2448
+ }
2449
+ function semanticDocumentKey(document) {
2450
+ return JSON.stringify([document.filepath, document.hash]);
2451
+ }
2452
+ function firstDocumentsByKey(documents) {
2453
+ const byKey = new Map;
2454
+ for (const document of documents) {
2455
+ const key = semanticDocumentKey(document);
2456
+ if (!byKey.has(key))
2457
+ byKey.set(key, document);
2458
+ }
2459
+ return byKey;
2460
+ }
2461
+ function fusedHybridDocuments(lexical, vector, candidateLimit) {
2462
+ const lexicalByKey = firstDocumentsByKey(lexical);
2463
+ const vectorByKey = firstDocumentsByKey(vector);
2464
+ return fuseRankedCandidates([
2465
+ { name: "keyword", weight: 1, ids: lexical.map(semanticDocumentKey) },
2466
+ { name: "semantic", weight: 1, ids: vector.map(semanticDocumentKey) }
2467
+ ]).slice(0, candidateLimit).map((candidate) => {
2468
+ const lexicalDocument = lexicalByKey.get(candidate.id);
2469
+ const vectorDocument = vectorByKey.get(candidate.id);
2470
+ const document = vectorDocument ?? lexicalDocument;
2471
+ if (document === undefined) {
2472
+ throw new Error("Fused QMD candidate lost its source document.");
2473
+ }
2474
+ return {
2475
+ document,
2476
+ score: candidate.score,
2477
+ signals: {
2478
+ keyword: candidate.contributions.some(({ lane }) => lane === "keyword"),
2479
+ semantic: candidate.contributions.some(({ lane }) => lane === "semantic")
2480
+ }
2481
+ };
2482
+ });
2483
+ }
2484
+ async function hybridSearchHit(projectionRoot, query, result, notesByPath, notesByQmdPath, contentHashesByPath, connectionsById) {
2485
+ const hit = await searchHit(projectionRoot, query, result.document, notesByPath, notesByQmdPath, contentHashesByPath, connectionsById);
2486
+ if (hit === null)
2487
+ return null;
2488
+ return {
2489
+ ...hit,
2490
+ score: result.score,
2491
+ source: "hybrid",
2492
+ signals: result.signals
2493
+ };
2494
+ }
2495
+ function boundedLimit(value, maximum) {
2496
+ if (value === undefined)
2497
+ return 10;
2498
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
2499
+ throw new Error(`Search limit must be an integer from 1 through ${maximum}.`);
2500
+ }
2501
+ return value;
2502
+ }
2503
+ function boundedCandidateLimit(value, resultLimit) {
2504
+ if (value === undefined)
2505
+ return Math.max(40, resultLimit * 4);
2506
+ if (!Number.isSafeInteger(value) || value < resultLimit || value > 500) {
2507
+ throw new Error(`Search candidate limit must be an integer from ${resultLimit} through 500.`);
2508
+ }
2509
+ return value;
2510
+ }
2511
+ function boundedScore(value) {
2512
+ if (value === undefined)
2513
+ return 0;
2514
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
2515
+ throw new Error("Minimum score must be a number from 0 through 1.");
2516
+ }
2517
+ return value;
2518
+ }
2519
+ function boundedMode(value) {
2520
+ const mode = value ?? "semantic";
2521
+ if (mode !== "hybrid" && mode !== "keyword" && mode !== "semantic") {
2522
+ throw new Error('Search mode must be "hybrid", "keyword", or "semantic".');
2523
+ }
2524
+ return mode;
2525
+ }
2526
+ async function executeSemanticSearch(context, options) {
2527
+ const query = validateSearchQuery(options.query).query;
2528
+ const mode = boundedMode(options.mode);
2529
+ const limit = boundedLimit(options.limit, 500);
2530
+ const candidateLimit = boundedCandidateLimit(options.candidateLimit, limit);
2531
+ const minScore = boundedScore(options.minScore);
2532
+ const embedding = mode === "keyword" ? null : await context.ensureEmbedding();
2533
+ let queryEmbedding = mode === "keyword" ? Object.freeze({ calls: 0, inputTokens: 0, durationMs: 0 }) : null;
2534
+ let hits;
2535
+ let rawRequested;
2536
+ let rawReturned;
2537
+ let rawDiscarded;
2538
+ let rawThresholdRejected;
2539
+ let rawExhausted;
2540
+ if (mode === "hybrid") {
2541
+ const lexical = await context.store.searchLex(query, {
2542
+ collection: collectionName,
2543
+ limit: candidateLimit
2544
+ });
2545
+ const vectorSearch = await context.store.searchVector(query, {
2546
+ collection: collectionName,
2547
+ limit: candidateLimit
2548
+ });
2549
+ queryEmbedding = vectorSearch.accounting;
2550
+ const fused = fusedHybridDocuments(lexical, vectorSearch.results, candidateLimit);
2551
+ const considered = fused.filter(({ score }) => score >= minScore);
2552
+ rawRequested = candidateLimit;
2553
+ rawReturned = fused.length;
2554
+ rawThresholdRejected = fused.length - considered.length;
2555
+ rawExhausted = fused.length < candidateLimit && lexical.length < candidateLimit && vectorSearch.results.length < candidateLimit;
2556
+ hits = await Promise.all(considered.map((result) => hybridSearchHit(context.projectionRoot, query, result, context.notesByPath, context.notesByQmdPath, context.contentHashesByPath, context.connectionsById)));
2557
+ rawDiscarded = considered.length - hits.filter((hit) => hit !== null).length;
2558
+ } else {
2559
+ const vectorSearch = mode === "semantic" ? await context.store.searchVector(query, {
2560
+ collection: collectionName,
2561
+ limit: candidateLimit
2562
+ }) : null;
2563
+ const matches = vectorSearch === null ? await context.store.searchLex(query, {
2564
+ collection: collectionName,
2565
+ limit: candidateLimit
2566
+ }) : vectorSearch.results;
2567
+ if (vectorSearch !== null)
2568
+ queryEmbedding = vectorSearch.accounting;
2569
+ rawRequested = candidateLimit;
2570
+ rawReturned = matches.length;
2571
+ const considered = matches.filter(({ score }) => score >= minScore);
2572
+ rawThresholdRejected = matches.length - considered.length;
2573
+ rawExhausted = matches.length < candidateLimit;
2574
+ hits = await Promise.all(considered.map((result) => searchHit(context.projectionRoot, query, result, context.notesByPath, context.notesByQmdPath, context.contentHashesByPath, context.connectionsById)));
2575
+ rawDiscarded = considered.length - hits.filter((hit) => hit !== null).length;
2576
+ }
2577
+ const verified = hits.filter((hit) => hit !== null);
2578
+ return {
2579
+ root: context.root,
2580
+ database: context.database,
2581
+ model: recommendedEmbeddingModel,
2582
+ mode,
2583
+ query,
2584
+ update: context.update,
2585
+ embedding,
2586
+ queryEmbedding,
2587
+ rawWindow: {
2588
+ requested: rawRequested,
2589
+ returned: rawReturned,
2590
+ discarded: rawDiscarded,
2591
+ thresholdRejected: rawThresholdRejected,
2592
+ exhausted: rawExhausted
2593
+ },
2594
+ results: verified.slice(0, limit)
2595
+ };
2596
+ }
2597
+ async function openSemanticSearchSession(options, dependencies = {}) {
2598
+ const root = await resolvedDirectory(options.root);
2599
+ const databaseCandidate = await resolveSemanticDatabase(databaseFor(root, options.database, dependencies), root);
2600
+ const snapshot2 = await semanticSnapshot(root, dependencies);
2601
+ const description = await describeSemanticProjection(databaseCandidate, root, snapshot2.notes, semanticIndexIdentity);
2602
+ const notesByPath = new Map(snapshot2.notes.map((note) => [note.path, note]));
2603
+ const contentHashesByPath = new Map(description.manifest.notes.map(({ path, sha256 }) => [path, sha256]));
2604
+ const notesByQmdPath = qmdNoteLookup(snapshot2.notes, contentHashesByPath);
2605
+ const connectionsById = new Map(snapshot2.analysis.noteConnections.map((connection) => [connection.id, connection]));
2606
+ const requireStoreLocalVectorBoundary = requiredStoreLocalVectorBoundary(options.requireStoreLocalVectorBoundary);
2607
+ const embeddingModel = await sessionEmbeddingModelSource(options, dependencies);
2608
+ const database = description.database;
2609
+ let retained;
2610
+ let initialized;
2611
+ try {
2612
+ initialized = await withSemanticGenerationWriterLease(database, description.manifest.generation, async () => {
2613
+ const projection2 = await prepareSemanticProjection(description, snapshot2.notes);
2614
+ let store2;
2615
+ try {
2616
+ store2 = await openStore(projection2.root, database, embeddingModel.source, dependencies, requireStoreLocalVectorBoundary);
2617
+ retained = { store: store2, projection: projection2 };
2618
+ const update2 = await store2.update({ collections: [collectionName] });
2619
+ return { store: store2, projection: projection2, update: update2 };
2620
+ } catch (error) {
2621
+ try {
2622
+ await store2?.close();
2623
+ } finally {
2624
+ await projection2.release();
2625
+ retained = undefined;
2626
+ }
2627
+ throw error;
2628
+ }
2629
+ }, dependencies.writerLease);
2630
+ } catch (error) {
2631
+ await retained?.store.close().catch(() => {
2632
+ return;
2633
+ });
2634
+ await retained?.projection.release().catch(() => {
2635
+ return;
2636
+ });
2637
+ await embeddingModel.release().catch(() => {
2638
+ return;
2639
+ });
2640
+ throw error;
2641
+ }
2642
+ const { store, projection, update } = initialized;
2643
+ let embeddingPromise;
2644
+ const ensureEmbedding = () => {
2645
+ embeddingPromise ??= update.needsEmbedding === 0 ? Promise.resolve(null) : withSemanticGenerationWriterLease(database, projection.manifest.generation, async () => {
2646
+ const refreshed = await store.update({ collections: [collectionName] });
2647
+ return await embedChanged(store, refreshed, false);
2648
+ }, dependencies.writerLease);
2649
+ return embeddingPromise;
2650
+ };
2651
+ let tail = Promise.resolve();
2652
+ let closeRequested = false;
2653
+ let closePromise;
2654
+ const serialize = (operation) => {
2655
+ const result = tail.then(operation);
2656
+ tail = result.then(() => {
2657
+ return;
2658
+ }, () => {
2659
+ return;
2660
+ });
2661
+ return result;
2662
+ };
2663
+ const context = {
2664
+ root,
2665
+ projectionRoot: projection.root,
2666
+ database,
2667
+ store,
2668
+ update,
2669
+ ensureEmbedding,
2670
+ notesByPath,
2671
+ notesByQmdPath,
2672
+ contentHashesByPath,
2673
+ connectionsById
2674
+ };
2675
+ return {
2676
+ root,
2677
+ database,
2678
+ model: recommendedEmbeddingModel,
2679
+ update,
2680
+ search: (searchOptions) => {
2681
+ if (closeRequested) {
2682
+ return Promise.reject(new Error("Semantic search session is closed."));
2683
+ }
2684
+ return serialize(() => executeSemanticSearch(context, searchOptions));
2685
+ },
2686
+ close: () => {
2687
+ if (closePromise !== undefined)
2688
+ return closePromise;
2689
+ closeRequested = true;
2690
+ closePromise = serialize(async () => {
2691
+ try {
2692
+ await store.close();
2693
+ } finally {
2694
+ try {
2695
+ await projection.release();
2696
+ } finally {
2697
+ await embeddingModel.release();
2698
+ }
2699
+ }
2700
+ });
2701
+ return closePromise;
2702
+ }
2703
+ };
2704
+ }
2705
+ async function searchSemanticVault(options, dependencies = {}) {
2706
+ const query = validateSearchQuery(options.query).query;
2707
+ const mode = boundedMode(options.mode);
2708
+ const limit = boundedLimit(options.limit, 100);
2709
+ const candidateLimit = boundedCandidateLimit(options.candidateLimit, limit);
2710
+ const minScore = boundedScore(options.minScore);
2711
+ const session = await openSemanticSearchSession({
2712
+ root: options.root,
2713
+ ...options.database === undefined ? {} : { database: options.database }
2714
+ }, dependencies);
2715
+ try {
2716
+ return await session.search({
2717
+ query,
2718
+ mode,
2719
+ limit,
2720
+ candidateLimit,
2721
+ minScore
2722
+ });
2723
+ } finally {
2724
+ await session.close();
2725
+ }
2726
+ }
2727
+
2728
+ export { MAX_SCANNED_NOTES, MAX_NOTE_UTF8_BYTES, MAX_VAULT_UTF8_BYTES, VaultScanBudgetError, defaultIgnoredDirectories, markdownFiles, readVaultNotes, scanVault, refreshVault, recommendedEmbeddingModel, recommendedEmbeddingModelSha256, MAX_EMBEDDING_MODEL_BYTES, MAX_SEMANTIC_DATABASE_IDENTITY_BYTES, qmdIndexerVersion, sha256EmbeddingModelFile, createVerifiedEmbeddingModelLease, semanticDatabasePath, checkpointSemanticWarmCache, attestSemanticWarmCache, openSemanticWarmSearchSession, indexSemanticVault, openSemanticSearchSession, searchSemanticVault };