@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,18 @@
1
+ [package]
2
+ name = "kb-url-metadata-search"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ publish = false
6
+
7
+ [dependencies]
8
+ metadata-search-engine-rs = { git = "https://github.com/MikeLuu99/searxng-rust", rev = "f40a00ea67a857ee996e1caba1ebab3ee7a14a47" }
9
+ libc = "=0.2.189"
10
+ reqwest = { version = "=0.12.28", features = ["brotli", "cookies", "gzip"] }
11
+ serde = { version = "1", features = ["derive"] }
12
+ serde_json = "1"
13
+ tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
14
+
15
+ [profile.release]
16
+ codegen-units = 1
17
+ lto = "thin"
18
+ strip = true
@@ -0,0 +1,685 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ constants,
5
+ fchmodSync,
6
+ fstatSync,
7
+ fsyncSync,
8
+ lstatSync,
9
+ mkdirSync,
10
+ mkdtempSync,
11
+ openSync,
12
+ readSync,
13
+ renameSync,
14
+ rmSync,
15
+ type BigIntStats,
16
+ writeSync,
17
+ } from "node:fs";
18
+ import { tmpdir } from "node:os";
19
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
20
+
21
+ export type MetadataSearchToolAction = "build" | "check";
22
+
23
+ const TEMPORARY_DIRECTORY_PREFIX = "hraness-kb-metadata-search-tool-";
24
+ const MAX_EXECUTABLE_BYTES = 64 * 1024 * 1024;
25
+ const COPY_BUFFER_BYTES = 64 * 1024;
26
+
27
+ type DirectoryBinding = {
28
+ readonly path: string;
29
+ readonly descriptor: number;
30
+ readonly identity: BigIntStats;
31
+ };
32
+
33
+ type EnsuredDirectory = {
34
+ readonly binding: DirectoryBinding;
35
+ readonly created: boolean;
36
+ };
37
+
38
+ export type MetadataSearchDirectorySyncPhase =
39
+ | "tool-after-target"
40
+ | "target-after-release"
41
+ | "tool-before-commit"
42
+ | "release-before-commit"
43
+ | "release-after-commit"
44
+ | "tool-after-commit"
45
+ | "tool-after-backup-removal"
46
+ | "release-after-rollback"
47
+ | "tool-after-rollback";
48
+
49
+ export type MetadataSearchToolRunnerDependencies = {
50
+ readonly toolDirectory?: string;
51
+ readonly platform?: NodeJS.Platform;
52
+ readonly createTemporaryDirectory?: () => string;
53
+ readonly removeTemporaryDirectory?: (
54
+ path: string,
55
+ identity: BigIntStats,
56
+ ) => void;
57
+ readonly runCargo?: (
58
+ command: readonly string[],
59
+ options: { readonly cwd: string; readonly environment: NodeJS.ProcessEnv },
60
+ ) => number;
61
+ readonly beforeInstall?: (input: {
62
+ readonly sourcePath: string;
63
+ readonly targetDirectory: string;
64
+ readonly releaseDirectory: string;
65
+ readonly destinationPath: string;
66
+ }) => void;
67
+ readonly beforeCommit?: (input: {
68
+ readonly sourcePath: string;
69
+ readonly targetDirectory: string;
70
+ readonly releaseDirectory: string;
71
+ readonly destinationPath: string;
72
+ }) => void;
73
+ readonly syncDirectory?: (
74
+ descriptor: number,
75
+ phase: MetadataSearchDirectorySyncPhase,
76
+ ) => void;
77
+ };
78
+
79
+ function noFollowFlag(): number {
80
+ return typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
81
+ }
82
+
83
+ function directoryFlag(): number {
84
+ return typeof constants.O_DIRECTORY === "number" ? constants.O_DIRECTORY : 0;
85
+ }
86
+
87
+ function errorCode(error: unknown): string | undefined {
88
+ return typeof error === "object"
89
+ && error !== null
90
+ && "code" in error
91
+ && typeof error.code === "string"
92
+ ? error.code
93
+ : undefined;
94
+ }
95
+
96
+ function maybeLstat(path: string): BigIntStats | null {
97
+ try {
98
+ return lstatSync(path, { bigint: true });
99
+ } catch (error) {
100
+ if (errorCode(error) === "ENOENT") return null;
101
+ throw error;
102
+ }
103
+ }
104
+
105
+ function sameDirectoryIdentity(left: BigIntStats, right: BigIntStats): boolean {
106
+ return left.dev === right.dev && left.ino === right.ino;
107
+ }
108
+
109
+ function sameFileIdentity(left: BigIntStats, right: BigIntStats): boolean {
110
+ return sameDirectoryIdentity(left, right)
111
+ && left.size === right.size
112
+ && left.mtimeNs === right.mtimeNs
113
+ && left.ctimeNs === right.ctimeNs;
114
+ }
115
+
116
+ function sameInstalledFile(left: BigIntStats, right: BigIntStats): boolean {
117
+ return sameDirectoryIdentity(left, right)
118
+ && left.size === right.size
119
+ && left.mtimeNs === right.mtimeNs;
120
+ }
121
+
122
+ function assertOwnedByCurrentUser(stat: BigIntStats, label: string): void {
123
+ if (process.platform === "win32" || typeof process.getuid !== "function") return;
124
+ if (stat.uid !== BigInt(process.getuid())) {
125
+ throw new Error(`${label} must be owned by the current user`);
126
+ }
127
+ }
128
+
129
+ function assertDirectory(stat: BigIntStats | null, label: string): asserts stat is BigIntStats {
130
+ if (stat === null || stat.isSymbolicLink() || !stat.isDirectory()) {
131
+ throw new Error(`${label} must be a real directory`);
132
+ }
133
+ assertOwnedByCurrentUser(stat, label);
134
+ }
135
+
136
+ function openBoundDirectory(path: string, label: string): DirectoryBinding {
137
+ const before = maybeLstat(path);
138
+ assertDirectory(before, label);
139
+ const descriptor = openSync(
140
+ path,
141
+ constants.O_RDONLY | directoryFlag() | noFollowFlag(),
142
+ );
143
+ try {
144
+ const opened = fstatSync(descriptor, { bigint: true });
145
+ if (!opened.isDirectory() || !sameDirectoryIdentity(before, opened)) {
146
+ throw new Error(`${label} changed while it was opened`);
147
+ }
148
+ return { path, descriptor, identity: opened };
149
+ } catch (error) {
150
+ closeSync(descriptor);
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ function assertDirectoryBinding(binding: DirectoryBinding, label: string): void {
156
+ const byPath = maybeLstat(binding.path);
157
+ assertDirectory(byPath, label);
158
+ const byDescriptor = fstatSync(binding.descriptor, { bigint: true });
159
+ if (
160
+ !sameDirectoryIdentity(binding.identity, byPath)
161
+ || !sameDirectoryIdentity(binding.identity, byDescriptor)
162
+ ) {
163
+ throw new Error(`${label} changed during installation`);
164
+ }
165
+ }
166
+
167
+ function ensurePrivateDirectory(path: string, label: string): EnsuredDirectory {
168
+ let created = false;
169
+ try {
170
+ mkdirSync(path, { mode: 0o700 });
171
+ created = true;
172
+ } catch (error) {
173
+ if (errorCode(error) !== "EEXIST") throw error;
174
+ }
175
+ const before = maybeLstat(path);
176
+ assertDirectory(before, label);
177
+ const binding = openBoundDirectory(path, label);
178
+ try {
179
+ if (process.platform !== "win32") {
180
+ fchmodSync(binding.descriptor, 0o700);
181
+ }
182
+ const identity = fstatSync(binding.descriptor, { bigint: true });
183
+ return { binding: { ...binding, identity }, created };
184
+ } catch (error) {
185
+ closeSync(binding.descriptor);
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ function assertExecutableSource(
191
+ stat: BigIntStats,
192
+ platform: NodeJS.Platform,
193
+ ): void {
194
+ if (!stat.isFile() || stat.isSymbolicLink()) {
195
+ throw new Error("Cargo release output must be a regular file");
196
+ }
197
+ if (stat.nlink !== 1n) {
198
+ throw new Error("Cargo release output must not be hard-linked");
199
+ }
200
+ if (stat.size < 1n || stat.size > BigInt(MAX_EXECUTABLE_BYTES)) {
201
+ throw new Error(
202
+ `Cargo release output must contain 1-${MAX_EXECUTABLE_BYTES} bytes`,
203
+ );
204
+ }
205
+ if (platform !== "win32" && (stat.mode & 0o111n) === 0n) {
206
+ throw new Error("Cargo release output must be executable");
207
+ }
208
+ assertOwnedByCurrentUser(stat, "Cargo release output");
209
+ }
210
+
211
+ function assertExistingDestination(
212
+ stat: BigIntStats,
213
+ platform: NodeJS.Platform,
214
+ ): void {
215
+ assertExecutableSource(stat, platform);
216
+ if (platform !== "win32" && (stat.mode & 0o777n) !== 0o700n) {
217
+ throw new Error("installed metadata-search executable has unsafe permissions");
218
+ }
219
+ }
220
+
221
+ function writeAll(descriptor: number, buffer: Buffer, length: number): void {
222
+ let offset = 0;
223
+ while (offset < length) {
224
+ const written = writeSync(descriptor, buffer, offset, length - offset);
225
+ if (written < 1) throw new Error("could not write the staged executable");
226
+ offset += written;
227
+ }
228
+ }
229
+
230
+ function copyValidatedExecutable(
231
+ sourcePath: string,
232
+ stagingPath: string,
233
+ platform: NodeJS.Platform,
234
+ ): BigIntStats {
235
+ const sourceDescriptor = openSync(
236
+ sourcePath,
237
+ constants.O_RDONLY | noFollowFlag(),
238
+ );
239
+ let stagingDescriptor: number | null = null;
240
+ let staged: BigIntStats | null = null;
241
+ let complete = false;
242
+ try {
243
+ const sourceBefore = fstatSync(sourceDescriptor, { bigint: true });
244
+ assertExecutableSource(sourceBefore, platform);
245
+ stagingDescriptor = openSync(
246
+ stagingPath,
247
+ constants.O_CREAT
248
+ | constants.O_EXCL
249
+ | constants.O_WRONLY
250
+ | noFollowFlag(),
251
+ 0o700,
252
+ );
253
+ if (platform !== "win32") fchmodSync(stagingDescriptor, 0o700);
254
+
255
+ const buffer = Buffer.allocUnsafe(COPY_BUFFER_BYTES);
256
+ let copiedBytes = 0;
257
+ const expectedBytes = Number(sourceBefore.size);
258
+ while (copiedBytes < expectedBytes) {
259
+ const bytesRead = readSync(
260
+ sourceDescriptor,
261
+ buffer,
262
+ 0,
263
+ Math.min(buffer.byteLength, expectedBytes - copiedBytes),
264
+ null,
265
+ );
266
+ if (bytesRead < 1) {
267
+ throw new Error("Cargo release output changed while it was copied");
268
+ }
269
+ writeAll(stagingDescriptor, buffer, bytesRead);
270
+ copiedBytes += bytesRead;
271
+ }
272
+ if (readSync(sourceDescriptor, buffer, 0, 1, null) !== 0) {
273
+ throw new Error("Cargo release output grew while it was copied");
274
+ }
275
+
276
+ const sourceAfter = fstatSync(sourceDescriptor, { bigint: true });
277
+ if (!sameFileIdentity(sourceBefore, sourceAfter)) {
278
+ throw new Error("Cargo release output changed while it was copied");
279
+ }
280
+ const sourceByPath = maybeLstat(sourcePath);
281
+ if (sourceByPath === null || !sameFileIdentity(sourceBefore, sourceByPath)) {
282
+ throw new Error("Cargo release output path changed while it was copied");
283
+ }
284
+
285
+ fsyncSync(stagingDescriptor);
286
+ staged = fstatSync(stagingDescriptor, { bigint: true });
287
+ if (!staged.isFile() || staged.nlink !== 1n || staged.size !== sourceBefore.size) {
288
+ throw new Error("staged metadata-search executable failed validation");
289
+ }
290
+ complete = true;
291
+ return staged;
292
+ } finally {
293
+ if (stagingDescriptor !== null) {
294
+ if (!complete) staged = fstatSync(stagingDescriptor, { bigint: true });
295
+ closeSync(stagingDescriptor);
296
+ }
297
+ closeSync(sourceDescriptor);
298
+ if (!complete) {
299
+ removeIdentityBoundFile(stagingPath, staged, "incomplete staged executable");
300
+ }
301
+ }
302
+ }
303
+
304
+ function assertDestinationUnchanged(
305
+ destinationPath: string,
306
+ expected: BigIntStats | null,
307
+ platform: NodeJS.Platform,
308
+ ): void {
309
+ const current = maybeLstat(destinationPath);
310
+ if (expected === null) {
311
+ if (current !== null) {
312
+ throw new Error("metadata-search executable destination appeared during installation");
313
+ }
314
+ return;
315
+ }
316
+ if (current === null || !sameFileIdentity(expected, current)) {
317
+ throw new Error("metadata-search executable destination changed during installation");
318
+ }
319
+ assertExistingDestination(current, platform);
320
+ }
321
+
322
+ function removeIdentityBoundFile(
323
+ path: string,
324
+ expected: BigIntStats | null,
325
+ label: string,
326
+ ): void {
327
+ const current = maybeLstat(path);
328
+ if (current === null) return;
329
+ if (
330
+ expected === null
331
+ || current.isDirectory()
332
+ || !sameFileIdentity(expected, current)
333
+ ) {
334
+ throw new Error(`refusing to remove a replaced ${label}`);
335
+ }
336
+ rmSync(path, { force: true });
337
+ }
338
+
339
+ export function metadataSearchExecutableName(
340
+ platform: NodeJS.Platform = process.platform,
341
+ ): string {
342
+ return platform === "win32"
343
+ ? "kb-url-metadata-search.exe"
344
+ : "kb-url-metadata-search";
345
+ }
346
+
347
+ export function metadataSearchToolCommand(
348
+ action: MetadataSearchToolAction,
349
+ targetDirectory: string,
350
+ toolDirectory = import.meta.dir,
351
+ ): readonly string[] {
352
+ if (!isAbsolute(targetDirectory)) {
353
+ throw new Error("metadata-search tool target directory must be absolute");
354
+ }
355
+ if (!isAbsolute(toolDirectory)) {
356
+ throw new Error("metadata-search tool directory must be absolute");
357
+ }
358
+ return Object.freeze([
359
+ "cargo",
360
+ action,
361
+ ...(action === "check" ? ["--all-targets"] : ["--release"]),
362
+ "--locked",
363
+ "--manifest-path",
364
+ join(toolDirectory, "Cargo.toml"),
365
+ "--target-dir",
366
+ targetDirectory,
367
+ ]);
368
+ }
369
+
370
+ export function installMetadataSearchExecutable(input: {
371
+ readonly sourcePath: string;
372
+ readonly toolDirectory: string;
373
+ readonly platform?: NodeJS.Platform;
374
+ readonly beforeInstall?: MetadataSearchToolRunnerDependencies["beforeInstall"];
375
+ readonly beforeCommit?: MetadataSearchToolRunnerDependencies["beforeCommit"];
376
+ readonly syncDirectory?: MetadataSearchToolRunnerDependencies["syncDirectory"];
377
+ }): string {
378
+ const toolDirectory = resolve(input.toolDirectory);
379
+ const platform = input.platform ?? process.platform;
380
+ const executableName = metadataSearchExecutableName(platform);
381
+ const sourcePath = resolve(input.sourcePath);
382
+ const targetDirectory = join(toolDirectory, "target");
383
+ const releaseDirectory = join(targetDirectory, "release");
384
+ const destinationPath = join(releaseDirectory, executableName);
385
+ const stagingName = `.${executableName}.${process.pid}.${randomUUID()}.tmp`;
386
+ const backupName = `.${executableName}.${process.pid}.${randomUUID()}.backup`;
387
+ const stagingPath = join(toolDirectory, stagingName);
388
+ const backupPath = join(toolDirectory, backupName);
389
+ const syncDirectory = input.syncDirectory
390
+ ?? ((descriptor: number) => fsyncSync(descriptor));
391
+
392
+ const toolBinding = openBoundDirectory(toolDirectory, "metadata-search tool directory");
393
+ let targetBinding: DirectoryBinding | null = null;
394
+ let releaseBinding: DirectoryBinding | null = null;
395
+ let stagedIdentity: BigIntStats | null = null;
396
+ let backupIdentity: BigIntStats | null = null;
397
+ let committed = false;
398
+ let backupDestroyed = false;
399
+ let installFailure: { readonly error: unknown } | null = null;
400
+ let installedPath: string | null = null;
401
+ const cleanupErrors: unknown[] = [];
402
+ try {
403
+ const target = ensurePrivateDirectory(
404
+ targetDirectory,
405
+ "metadata-search target directory",
406
+ );
407
+ targetBinding = target.binding;
408
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
409
+ syncDirectory(toolBinding.descriptor, "tool-after-target");
410
+ const release = ensurePrivateDirectory(
411
+ releaseDirectory,
412
+ "metadata-search release directory",
413
+ );
414
+ releaseBinding = release.binding;
415
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
416
+ syncDirectory(targetBinding.descriptor, "target-after-release");
417
+
418
+ const previousDestination = maybeLstat(destinationPath);
419
+ if (previousDestination !== null) {
420
+ assertExistingDestination(previousDestination, platform);
421
+ backupIdentity = copyValidatedExecutable(
422
+ destinationPath,
423
+ backupPath,
424
+ platform,
425
+ );
426
+ }
427
+
428
+ stagedIdentity = copyValidatedExecutable(sourcePath, stagingPath, platform);
429
+ input.beforeInstall?.({
430
+ sourcePath,
431
+ targetDirectory,
432
+ releaseDirectory,
433
+ destinationPath,
434
+ });
435
+
436
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
437
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
438
+ assertDirectoryBinding(releaseBinding, "metadata-search release directory");
439
+ assertDestinationUnchanged(destinationPath, previousDestination, platform);
440
+ const stagedByPath = maybeLstat(stagingPath);
441
+ if (stagedByPath === null || !sameFileIdentity(stagedIdentity, stagedByPath)) {
442
+ throw new Error("staged metadata-search executable changed before installation");
443
+ }
444
+ if (backupIdentity !== null) {
445
+ const backup = maybeLstat(backupPath);
446
+ if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
447
+ throw new Error("metadata-search executable backup changed before installation");
448
+ }
449
+ }
450
+
451
+ input.beforeCommit?.({
452
+ sourcePath,
453
+ targetDirectory,
454
+ releaseDirectory,
455
+ destinationPath,
456
+ });
457
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
458
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
459
+ assertDirectoryBinding(releaseBinding, "metadata-search release directory");
460
+ assertDestinationUnchanged(destinationPath, previousDestination, platform);
461
+ const commitStaging = maybeLstat(stagingPath);
462
+ if (commitStaging === null || !sameFileIdentity(stagedIdentity, commitStaging)) {
463
+ throw new Error("staged metadata-search executable changed before commit");
464
+ }
465
+ if (backupIdentity !== null) {
466
+ const commitBackup = maybeLstat(backupPath);
467
+ if (commitBackup === null || !sameFileIdentity(backupIdentity, commitBackup)) {
468
+ throw new Error("metadata-search executable backup changed before commit");
469
+ }
470
+ }
471
+ syncDirectory(toolBinding.descriptor, "tool-before-commit");
472
+ syncDirectory(releaseBinding.descriptor, "release-before-commit");
473
+ renameSync(stagingPath, destinationPath);
474
+ committed = true;
475
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
476
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
477
+ assertDirectoryBinding(releaseBinding, "metadata-search release directory");
478
+ const installed = maybeLstat(destinationPath);
479
+ if (installed === null || !sameInstalledFile(stagedIdentity, installed)) {
480
+ throw new Error("installed metadata-search executable failed identity validation");
481
+ }
482
+ assertExistingDestination(installed, platform);
483
+ syncDirectory(releaseBinding.descriptor, "release-after-commit");
484
+ syncDirectory(toolBinding.descriptor, "tool-after-commit");
485
+ if (backupIdentity !== null) {
486
+ const backup = maybeLstat(backupPath);
487
+ if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
488
+ throw new Error("metadata-search executable backup changed during installation");
489
+ }
490
+ rmSync(backupPath, { force: true });
491
+ backupDestroyed = true;
492
+ syncDirectory(toolBinding.descriptor, "tool-after-backup-removal");
493
+ backupIdentity = null;
494
+ }
495
+ installedPath = destinationPath;
496
+ } catch (error) {
497
+ let reportedError = error;
498
+ if (
499
+ committed
500
+ && !backupDestroyed
501
+ && releaseBinding !== null
502
+ && stagedIdentity !== null
503
+ ) {
504
+ try {
505
+ const installed = maybeLstat(destinationPath);
506
+ if (installed === null || !sameInstalledFile(stagedIdentity, installed)) {
507
+ throw new Error("refusing to replace an unrecognized failed install");
508
+ }
509
+ if (backupIdentity === null) {
510
+ rmSync(destinationPath, { force: true });
511
+ } else {
512
+ const backup = maybeLstat(backupPath);
513
+ if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
514
+ throw new Error("refusing to restore an unrecognized executable backup");
515
+ }
516
+ renameSync(backupPath, destinationPath);
517
+ backupIdentity = null;
518
+ }
519
+ syncDirectory(releaseBinding.descriptor, "release-after-rollback");
520
+ syncDirectory(toolBinding.descriptor, "tool-after-rollback");
521
+ } catch (rollbackError) {
522
+ reportedError = new AggregateError(
523
+ [error, rollbackError],
524
+ "metadata-search executable installation and rollback both failed",
525
+ );
526
+ }
527
+ }
528
+ installFailure = { error: reportedError };
529
+ } finally {
530
+ const attemptCleanup = (action: () => void): void => {
531
+ try {
532
+ action();
533
+ } catch (error) {
534
+ cleanupErrors.push(error);
535
+ }
536
+ };
537
+ attemptCleanup(() => removeIdentityBoundFile(
538
+ stagingPath,
539
+ stagedIdentity,
540
+ "staging executable",
541
+ ));
542
+ attemptCleanup(() => removeIdentityBoundFile(
543
+ backupPath,
544
+ backupIdentity,
545
+ "executable backup",
546
+ ));
547
+ if (releaseBinding !== null) {
548
+ const releaseDescriptor = releaseBinding.descriptor;
549
+ attemptCleanup(() => closeSync(releaseDescriptor));
550
+ }
551
+ if (targetBinding !== null) {
552
+ const targetDescriptor = targetBinding.descriptor;
553
+ attemptCleanup(() => closeSync(targetDescriptor));
554
+ }
555
+ attemptCleanup(() => closeSync(toolBinding.descriptor));
556
+ }
557
+ const errors = [
558
+ ...(installFailure === null ? [] : [installFailure.error]),
559
+ ...cleanupErrors,
560
+ ];
561
+ if (errors.length === 1) throw errors[0];
562
+ if (errors.length > 1) {
563
+ const details = errors.map((error) => error instanceof Error
564
+ ? error.message
565
+ : "unknown error").join("; ");
566
+ throw new AggregateError(
567
+ errors,
568
+ `metadata-search executable installation or cleanup failed: ${details}`,
569
+ );
570
+ }
571
+ if (installedPath === null) {
572
+ throw new Error("metadata-search executable installation produced no outcome");
573
+ }
574
+ return installedPath;
575
+ }
576
+
577
+ function defaultTemporaryDirectory(): string {
578
+ return mkdtempSync(join(tmpdir(), TEMPORARY_DIRECTORY_PREFIX));
579
+ }
580
+
581
+ function assertTemporaryDirectoryIdentity(
582
+ path: string,
583
+ expected: BigIntStats,
584
+ ): void {
585
+ const current = maybeLstat(path);
586
+ assertDirectory(current, "Cargo target directory");
587
+ if (!sameDirectoryIdentity(expected, current)) {
588
+ throw new Error("Cargo target directory changed before cleanup");
589
+ }
590
+ }
591
+
592
+ function removeOwnedTemporaryDirectory(
593
+ path: string,
594
+ identity: BigIntStats,
595
+ ): void {
596
+ const resolvedPath = resolve(path);
597
+ const resolvedTemporaryRoot = resolve(tmpdir());
598
+ if (
599
+ dirname(resolvedPath) !== resolvedTemporaryRoot
600
+ || !basename(resolvedPath).startsWith(TEMPORARY_DIRECTORY_PREFIX)
601
+ ) {
602
+ throw new Error("refusing to remove an unexpected Cargo target directory");
603
+ }
604
+ assertTemporaryDirectoryIdentity(resolvedPath, identity);
605
+ rmSync(resolvedPath, { recursive: true, force: true });
606
+ }
607
+
608
+ function defaultCargoRunner(
609
+ command: readonly string[],
610
+ options: { readonly cwd: string; readonly environment: NodeJS.ProcessEnv },
611
+ ): number {
612
+ const result = Bun.spawnSync({
613
+ cmd: [...command],
614
+ cwd: options.cwd,
615
+ env: options.environment,
616
+ stdin: "inherit",
617
+ stdout: "inherit",
618
+ stderr: "inherit",
619
+ });
620
+ return result.exitCode;
621
+ }
622
+
623
+ export function runMetadataSearchTool(
624
+ action: MetadataSearchToolAction,
625
+ dependencies: MetadataSearchToolRunnerDependencies = {},
626
+ ): number {
627
+ const toolDirectory = resolve(dependencies.toolDirectory ?? import.meta.dir);
628
+ const platform = dependencies.platform ?? process.platform;
629
+ const createTemporaryDirectory = dependencies.createTemporaryDirectory
630
+ ?? defaultTemporaryDirectory;
631
+ const removeTemporaryDirectory = dependencies.removeTemporaryDirectory
632
+ ?? removeOwnedTemporaryDirectory;
633
+ const runCargo = dependencies.runCargo ?? defaultCargoRunner;
634
+ const createdTargetDirectory = createTemporaryDirectory();
635
+ if (!isAbsolute(createdTargetDirectory)) {
636
+ throw new Error("metadata-search tool temporary directory must be absolute");
637
+ }
638
+ const targetDirectory = resolve(createdTargetDirectory);
639
+ const targetIdentity = maybeLstat(targetDirectory);
640
+ assertDirectory(targetIdentity, "Cargo target directory");
641
+
642
+ try {
643
+ const command = metadataSearchToolCommand(action, targetDirectory, toolDirectory);
644
+ const exitCode = runCargo(command, {
645
+ cwd: toolDirectory,
646
+ environment: { ...process.env, CARGO_INCREMENTAL: "0" },
647
+ });
648
+ if (exitCode !== 0 || action === "check") return exitCode;
649
+
650
+ installMetadataSearchExecutable({
651
+ sourcePath: join(
652
+ targetDirectory,
653
+ "release",
654
+ metadataSearchExecutableName(platform),
655
+ ),
656
+ toolDirectory,
657
+ platform,
658
+ beforeInstall: dependencies.beforeInstall,
659
+ beforeCommit: dependencies.beforeCommit,
660
+ syncDirectory: dependencies.syncDirectory,
661
+ });
662
+ return 0;
663
+ } finally {
664
+ assertTemporaryDirectoryIdentity(targetDirectory, targetIdentity);
665
+ removeTemporaryDirectory(targetDirectory, targetIdentity);
666
+ }
667
+ }
668
+
669
+ if (import.meta.main) {
670
+ const action = Bun.argv[2];
671
+ if (
672
+ (action !== "check" && action !== "build")
673
+ || Bun.argv.length !== 3
674
+ ) {
675
+ process.stderr.write("usage: runner.ts check|build\n");
676
+ process.exit(2);
677
+ }
678
+ try {
679
+ process.exit(runMetadataSearchTool(action));
680
+ } catch (error) {
681
+ const message = error instanceof Error ? error.message : "unknown error";
682
+ process.stderr.write(`metadata-search tool ${action} failed: ${message}\n`);
683
+ process.exit(1);
684
+ }
685
+ }