@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
package/dist/cli.js ADDED
@@ -0,0 +1,3646 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ import {
4
+ backfillSavedUrlMetadata,
5
+ createRustMetadataSearchProvider
6
+ } from "./index-dycjbhw2.js";
7
+ import {
8
+ main as main2
9
+ } from "./index-kh0pwvdd.js";
10
+ import {
11
+ MAX_AUTHORIZED_VAULTS,
12
+ auditKnowledgePortfolio,
13
+ loadPortfolioRegistry,
14
+ openKnowledgePortfolio,
15
+ snapshotPortfolioRegistry
16
+ } from "./index-jsmvyyvf.js";
17
+ import {
18
+ diffCaptureBundle
19
+ } from "./index-j4zgmzjr.js";
20
+ import {
21
+ verifyCaptureBundle
22
+ } from "./index-npg9z1a4.js";
23
+ import {
24
+ initVault
25
+ } from "./index-mqx4nd6v.js";
26
+ import {
27
+ MAX_SOURCE_INBOX_PREFIXES,
28
+ MAX_SOURCE_INBOX_RESULTS,
29
+ sourceInbox
30
+ } from "./index-pj501bh1.js";
31
+ import {
32
+ MAX_PERCOLATION_MENTIONS,
33
+ MAX_PERCOLATION_MENTION_PAIRS,
34
+ MAX_PERCOLATION_NOTES,
35
+ MAX_SCOPED_PERCOLATION_MENTION_PAIRS,
36
+ percolateVault
37
+ } from "./index-dyqwejk5.js";
38
+ import {
39
+ knowledgeBaseEvaluationRetrieverIds,
40
+ openKnowledgeBaseEvaluation
41
+ } from "./index-n5dd7r0v.js";
42
+ import {
43
+ DEFAULT_SEARCH_RESULTS,
44
+ MAX_SEARCH_CANDIDATES,
45
+ MAX_SEARCH_NOTE_REFERENCE_BYTES,
46
+ MAX_SEARCH_RELATED_SEEDS,
47
+ MAX_SEARCH_RESULTS,
48
+ openKnowledgeBase
49
+ } from "./index-zzhgcwyt.js";
50
+ import {
51
+ MAX_SEARCH_RULE_CONFIG_BYTES,
52
+ parseSearchRules
53
+ } from "./index-adx6khj5.js";
54
+ import {
55
+ indexSemanticVault,
56
+ qmdIndexerVersion,
57
+ recommendedEmbeddingModel,
58
+ recommendedEmbeddingModelSha256,
59
+ refreshVault,
60
+ scanVault,
61
+ sha256EmbeddingModelFile
62
+ } from "./index-zxdy5pby.js";
63
+ import"./index-4j3tt0c3.js";
64
+ import"./index-1gwbassd.js";
65
+ import {
66
+ MAX_EVALUATION_RESULTS_PER_QUERY,
67
+ MAX_EVALUATION_TIMEOUT_MS,
68
+ buildRetrievalEvaluationReport,
69
+ parseRetrievalEvaluationCorpus,
70
+ runRetrievalEvaluation
71
+ } from "./index-b88v3vtm.js";
72
+ import {
73
+ auditAgentGuideRepository
74
+ } from "./index-hya40gb2.js";
75
+ import {
76
+ validateMarkdownAttachments
77
+ } from "./index-x3fthpsc.js";
78
+ import {
79
+ addNoteRelation,
80
+ createNote,
81
+ removeNoteRelation
82
+ } from "./index-01jj6rbv.js";
83
+ import"./index-3rm7cz6h.js";
84
+ import {
85
+ validateSearchQuery
86
+ } from "./index-cv6fh7z5.js";
87
+ import {
88
+ navigateLinks
89
+ } from "./index-d13v9ckt.js";
90
+ import {
91
+ MAX_QUERY_FILTERS,
92
+ MAX_QUERY_TAGS,
93
+ queryVault,
94
+ validateQueryOptions
95
+ } from "./index-48pz4jpc.js";
96
+ import {
97
+ MAX_REPOSITORY_SCOPES,
98
+ buildRepositoryMemoryContext,
99
+ repositoryMemoryGroupKeys
100
+ } from "./index-06c9ctr6.js";
101
+ import {
102
+ agentContextGuidePath,
103
+ agentContextMarkerForScope,
104
+ agentContextNoteId,
105
+ agentContextNotePath,
106
+ analyzeAgentContexts,
107
+ inspectAgentContextRepository,
108
+ normalizeRepositoryScope
109
+ } from "./index-5vwpzb5a.js";
110
+ import {
111
+ lookupNote,
112
+ parseVaultKey,
113
+ renderCatalog
114
+ } from "./index-cxfrakt7.js";
115
+ import {
116
+ main
117
+ } from "./index-0kavxzqj.js";
118
+ import"./index-7fzc32gf.js";
119
+ import"./index-f984hw45.js";
120
+ import"./index-1n418kb9.js";
121
+ import {
122
+ findKbPackageRoot
123
+ } from "./index-qry4vhxk.js";
124
+ import"./index-hgve9rh2.js";
125
+ import"./index-w2zc0vwa.js";
126
+ import"./index-e5fbsywq.js";
127
+ import"./index-6g2pv9d2.js";
128
+ import"./index-gh719d91.js";
129
+ import {
130
+ redactSensitiveText
131
+ } from "./index-mxxxytys.js";
132
+ import"./index-84x0vjjp.js";
133
+ import {
134
+ sanitizeTerminalLine,
135
+ sanitizeTerminalText
136
+ } from "./index-1xxnjn0d.js";
137
+ import"./index-5n05se68.js";
138
+
139
+ // src/cli.ts
140
+ import { open } from "fs/promises";
141
+ import { cpus, release, totalmem } from "os";
142
+ import { relative, resolve as resolve3 } from "path";
143
+ import { format } from "util";
144
+
145
+ // src/clip/url-metadata-cli.ts
146
+ import { resolve as resolve2 } from "path";
147
+
148
+ // src/clip/metadata-search-tool/runner.ts
149
+ import { randomUUID } from "crypto";
150
+ import {
151
+ closeSync,
152
+ constants,
153
+ fchmodSync,
154
+ fstatSync,
155
+ fsyncSync,
156
+ lstatSync,
157
+ mkdirSync,
158
+ mkdtempSync,
159
+ openSync,
160
+ readSync,
161
+ renameSync,
162
+ rmSync,
163
+ writeSync
164
+ } from "fs";
165
+ import { tmpdir } from "os";
166
+ import { basename, dirname, isAbsolute, join, resolve } from "path";
167
+ var TEMPORARY_DIRECTORY_PREFIX = "hraness-kb-metadata-search-tool-";
168
+ var MAX_EXECUTABLE_BYTES = 64 * 1024 * 1024;
169
+ var COPY_BUFFER_BYTES = 64 * 1024;
170
+ function noFollowFlag() {
171
+ return typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
172
+ }
173
+ function directoryFlag() {
174
+ return typeof constants.O_DIRECTORY === "number" ? constants.O_DIRECTORY : 0;
175
+ }
176
+ function errorCode(error) {
177
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : undefined;
178
+ }
179
+ function maybeLstat(path) {
180
+ try {
181
+ return lstatSync(path, { bigint: true });
182
+ } catch (error) {
183
+ if (errorCode(error) === "ENOENT")
184
+ return null;
185
+ throw error;
186
+ }
187
+ }
188
+ function sameDirectoryIdentity(left, right) {
189
+ return left.dev === right.dev && left.ino === right.ino;
190
+ }
191
+ function sameFileIdentity(left, right) {
192
+ return sameDirectoryIdentity(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
193
+ }
194
+ function sameInstalledFile(left, right) {
195
+ return sameDirectoryIdentity(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs;
196
+ }
197
+ function assertOwnedByCurrentUser(stat, label) {
198
+ if (process.platform === "win32" || typeof process.getuid !== "function")
199
+ return;
200
+ if (stat.uid !== BigInt(process.getuid())) {
201
+ throw new Error(`${label} must be owned by the current user`);
202
+ }
203
+ }
204
+ function assertDirectory(stat, label) {
205
+ if (stat === null || stat.isSymbolicLink() || !stat.isDirectory()) {
206
+ throw new Error(`${label} must be a real directory`);
207
+ }
208
+ assertOwnedByCurrentUser(stat, label);
209
+ }
210
+ function openBoundDirectory(path, label) {
211
+ const before = maybeLstat(path);
212
+ assertDirectory(before, label);
213
+ const descriptor = openSync(path, constants.O_RDONLY | directoryFlag() | noFollowFlag());
214
+ try {
215
+ const opened = fstatSync(descriptor, { bigint: true });
216
+ if (!opened.isDirectory() || !sameDirectoryIdentity(before, opened)) {
217
+ throw new Error(`${label} changed while it was opened`);
218
+ }
219
+ return { path, descriptor, identity: opened };
220
+ } catch (error) {
221
+ closeSync(descriptor);
222
+ throw error;
223
+ }
224
+ }
225
+ function assertDirectoryBinding(binding, label) {
226
+ const byPath = maybeLstat(binding.path);
227
+ assertDirectory(byPath, label);
228
+ const byDescriptor = fstatSync(binding.descriptor, { bigint: true });
229
+ if (!sameDirectoryIdentity(binding.identity, byPath) || !sameDirectoryIdentity(binding.identity, byDescriptor)) {
230
+ throw new Error(`${label} changed during installation`);
231
+ }
232
+ }
233
+ function ensurePrivateDirectory(path, label) {
234
+ let created = false;
235
+ try {
236
+ mkdirSync(path, { mode: 448 });
237
+ created = true;
238
+ } catch (error) {
239
+ if (errorCode(error) !== "EEXIST")
240
+ throw error;
241
+ }
242
+ const before = maybeLstat(path);
243
+ assertDirectory(before, label);
244
+ const binding = openBoundDirectory(path, label);
245
+ try {
246
+ if (process.platform !== "win32") {
247
+ fchmodSync(binding.descriptor, 448);
248
+ }
249
+ const identity = fstatSync(binding.descriptor, { bigint: true });
250
+ return { binding: { ...binding, identity }, created };
251
+ } catch (error) {
252
+ closeSync(binding.descriptor);
253
+ throw error;
254
+ }
255
+ }
256
+ function assertExecutableSource(stat, platform) {
257
+ if (!stat.isFile() || stat.isSymbolicLink()) {
258
+ throw new Error("Cargo release output must be a regular file");
259
+ }
260
+ if (stat.nlink !== 1n) {
261
+ throw new Error("Cargo release output must not be hard-linked");
262
+ }
263
+ if (stat.size < 1n || stat.size > BigInt(MAX_EXECUTABLE_BYTES)) {
264
+ throw new Error(`Cargo release output must contain 1-${MAX_EXECUTABLE_BYTES} bytes`);
265
+ }
266
+ if (platform !== "win32" && (stat.mode & 0o111n) === 0n) {
267
+ throw new Error("Cargo release output must be executable");
268
+ }
269
+ assertOwnedByCurrentUser(stat, "Cargo release output");
270
+ }
271
+ function assertExistingDestination(stat, platform) {
272
+ assertExecutableSource(stat, platform);
273
+ if (platform !== "win32" && (stat.mode & 0o777n) !== 0o700n) {
274
+ throw new Error("installed metadata-search executable has unsafe permissions");
275
+ }
276
+ }
277
+ function writeAll(descriptor, buffer, length) {
278
+ let offset = 0;
279
+ while (offset < length) {
280
+ const written = writeSync(descriptor, buffer, offset, length - offset);
281
+ if (written < 1)
282
+ throw new Error("could not write the staged executable");
283
+ offset += written;
284
+ }
285
+ }
286
+ function copyValidatedExecutable(sourcePath, stagingPath, platform) {
287
+ const sourceDescriptor = openSync(sourcePath, constants.O_RDONLY | noFollowFlag());
288
+ let stagingDescriptor = null;
289
+ let staged = null;
290
+ let complete = false;
291
+ try {
292
+ const sourceBefore = fstatSync(sourceDescriptor, { bigint: true });
293
+ assertExecutableSource(sourceBefore, platform);
294
+ stagingDescriptor = openSync(stagingPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | noFollowFlag(), 448);
295
+ if (platform !== "win32")
296
+ fchmodSync(stagingDescriptor, 448);
297
+ const buffer = Buffer.allocUnsafe(COPY_BUFFER_BYTES);
298
+ let copiedBytes = 0;
299
+ const expectedBytes = Number(sourceBefore.size);
300
+ while (copiedBytes < expectedBytes) {
301
+ const bytesRead = readSync(sourceDescriptor, buffer, 0, Math.min(buffer.byteLength, expectedBytes - copiedBytes), null);
302
+ if (bytesRead < 1) {
303
+ throw new Error("Cargo release output changed while it was copied");
304
+ }
305
+ writeAll(stagingDescriptor, buffer, bytesRead);
306
+ copiedBytes += bytesRead;
307
+ }
308
+ if (readSync(sourceDescriptor, buffer, 0, 1, null) !== 0) {
309
+ throw new Error("Cargo release output grew while it was copied");
310
+ }
311
+ const sourceAfter = fstatSync(sourceDescriptor, { bigint: true });
312
+ if (!sameFileIdentity(sourceBefore, sourceAfter)) {
313
+ throw new Error("Cargo release output changed while it was copied");
314
+ }
315
+ const sourceByPath = maybeLstat(sourcePath);
316
+ if (sourceByPath === null || !sameFileIdentity(sourceBefore, sourceByPath)) {
317
+ throw new Error("Cargo release output path changed while it was copied");
318
+ }
319
+ fsyncSync(stagingDescriptor);
320
+ staged = fstatSync(stagingDescriptor, { bigint: true });
321
+ if (!staged.isFile() || staged.nlink !== 1n || staged.size !== sourceBefore.size) {
322
+ throw new Error("staged metadata-search executable failed validation");
323
+ }
324
+ complete = true;
325
+ return staged;
326
+ } finally {
327
+ if (stagingDescriptor !== null) {
328
+ if (!complete)
329
+ staged = fstatSync(stagingDescriptor, { bigint: true });
330
+ closeSync(stagingDescriptor);
331
+ }
332
+ closeSync(sourceDescriptor);
333
+ if (!complete) {
334
+ removeIdentityBoundFile(stagingPath, staged, "incomplete staged executable");
335
+ }
336
+ }
337
+ }
338
+ function assertDestinationUnchanged(destinationPath, expected, platform) {
339
+ const current = maybeLstat(destinationPath);
340
+ if (expected === null) {
341
+ if (current !== null) {
342
+ throw new Error("metadata-search executable destination appeared during installation");
343
+ }
344
+ return;
345
+ }
346
+ if (current === null || !sameFileIdentity(expected, current)) {
347
+ throw new Error("metadata-search executable destination changed during installation");
348
+ }
349
+ assertExistingDestination(current, platform);
350
+ }
351
+ function removeIdentityBoundFile(path, expected, label) {
352
+ const current = maybeLstat(path);
353
+ if (current === null)
354
+ return;
355
+ if (expected === null || current.isDirectory() || !sameFileIdentity(expected, current)) {
356
+ throw new Error(`refusing to remove a replaced ${label}`);
357
+ }
358
+ rmSync(path, { force: true });
359
+ }
360
+ function metadataSearchExecutableName(platform = process.platform) {
361
+ return platform === "win32" ? "kb-url-metadata-search.exe" : "kb-url-metadata-search";
362
+ }
363
+ function metadataSearchToolCommand(action, targetDirectory, toolDirectory = import.meta.dir) {
364
+ if (!isAbsolute(targetDirectory)) {
365
+ throw new Error("metadata-search tool target directory must be absolute");
366
+ }
367
+ if (!isAbsolute(toolDirectory)) {
368
+ throw new Error("metadata-search tool directory must be absolute");
369
+ }
370
+ return Object.freeze([
371
+ "cargo",
372
+ action,
373
+ ...action === "check" ? ["--all-targets"] : ["--release"],
374
+ "--locked",
375
+ "--manifest-path",
376
+ join(toolDirectory, "Cargo.toml"),
377
+ "--target-dir",
378
+ targetDirectory
379
+ ]);
380
+ }
381
+ function installMetadataSearchExecutable(input) {
382
+ const toolDirectory = resolve(input.toolDirectory);
383
+ const platform = input.platform ?? process.platform;
384
+ const executableName = metadataSearchExecutableName(platform);
385
+ const sourcePath = resolve(input.sourcePath);
386
+ const targetDirectory = join(toolDirectory, "target");
387
+ const releaseDirectory = join(targetDirectory, "release");
388
+ const destinationPath = join(releaseDirectory, executableName);
389
+ const stagingName = `.${executableName}.${process.pid}.${randomUUID()}.tmp`;
390
+ const backupName = `.${executableName}.${process.pid}.${randomUUID()}.backup`;
391
+ const stagingPath = join(toolDirectory, stagingName);
392
+ const backupPath = join(toolDirectory, backupName);
393
+ const syncDirectory = input.syncDirectory ?? ((descriptor) => fsyncSync(descriptor));
394
+ const toolBinding = openBoundDirectory(toolDirectory, "metadata-search tool directory");
395
+ let targetBinding = null;
396
+ let releaseBinding = null;
397
+ let stagedIdentity = null;
398
+ let backupIdentity = null;
399
+ let committed = false;
400
+ let backupDestroyed = false;
401
+ let installFailure = null;
402
+ let installedPath = null;
403
+ const cleanupErrors = [];
404
+ try {
405
+ const target = ensurePrivateDirectory(targetDirectory, "metadata-search target directory");
406
+ targetBinding = target.binding;
407
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
408
+ syncDirectory(toolBinding.descriptor, "tool-after-target");
409
+ const release = ensurePrivateDirectory(releaseDirectory, "metadata-search release directory");
410
+ releaseBinding = release.binding;
411
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
412
+ syncDirectory(targetBinding.descriptor, "target-after-release");
413
+ const previousDestination = maybeLstat(destinationPath);
414
+ if (previousDestination !== null) {
415
+ assertExistingDestination(previousDestination, platform);
416
+ backupIdentity = copyValidatedExecutable(destinationPath, backupPath, platform);
417
+ }
418
+ stagedIdentity = copyValidatedExecutable(sourcePath, stagingPath, platform);
419
+ input.beforeInstall?.({
420
+ sourcePath,
421
+ targetDirectory,
422
+ releaseDirectory,
423
+ destinationPath
424
+ });
425
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
426
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
427
+ assertDirectoryBinding(releaseBinding, "metadata-search release directory");
428
+ assertDestinationUnchanged(destinationPath, previousDestination, platform);
429
+ const stagedByPath = maybeLstat(stagingPath);
430
+ if (stagedByPath === null || !sameFileIdentity(stagedIdentity, stagedByPath)) {
431
+ throw new Error("staged metadata-search executable changed before installation");
432
+ }
433
+ if (backupIdentity !== null) {
434
+ const backup = maybeLstat(backupPath);
435
+ if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
436
+ throw new Error("metadata-search executable backup changed before installation");
437
+ }
438
+ }
439
+ input.beforeCommit?.({
440
+ sourcePath,
441
+ targetDirectory,
442
+ releaseDirectory,
443
+ destinationPath
444
+ });
445
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
446
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
447
+ assertDirectoryBinding(releaseBinding, "metadata-search release directory");
448
+ assertDestinationUnchanged(destinationPath, previousDestination, platform);
449
+ const commitStaging = maybeLstat(stagingPath);
450
+ if (commitStaging === null || !sameFileIdentity(stagedIdentity, commitStaging)) {
451
+ throw new Error("staged metadata-search executable changed before commit");
452
+ }
453
+ if (backupIdentity !== null) {
454
+ const commitBackup = maybeLstat(backupPath);
455
+ if (commitBackup === null || !sameFileIdentity(backupIdentity, commitBackup)) {
456
+ throw new Error("metadata-search executable backup changed before commit");
457
+ }
458
+ }
459
+ syncDirectory(toolBinding.descriptor, "tool-before-commit");
460
+ syncDirectory(releaseBinding.descriptor, "release-before-commit");
461
+ renameSync(stagingPath, destinationPath);
462
+ committed = true;
463
+ assertDirectoryBinding(toolBinding, "metadata-search tool directory");
464
+ assertDirectoryBinding(targetBinding, "metadata-search target directory");
465
+ assertDirectoryBinding(releaseBinding, "metadata-search release directory");
466
+ const installed = maybeLstat(destinationPath);
467
+ if (installed === null || !sameInstalledFile(stagedIdentity, installed)) {
468
+ throw new Error("installed metadata-search executable failed identity validation");
469
+ }
470
+ assertExistingDestination(installed, platform);
471
+ syncDirectory(releaseBinding.descriptor, "release-after-commit");
472
+ syncDirectory(toolBinding.descriptor, "tool-after-commit");
473
+ if (backupIdentity !== null) {
474
+ const backup = maybeLstat(backupPath);
475
+ if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
476
+ throw new Error("metadata-search executable backup changed during installation");
477
+ }
478
+ rmSync(backupPath, { force: true });
479
+ backupDestroyed = true;
480
+ syncDirectory(toolBinding.descriptor, "tool-after-backup-removal");
481
+ backupIdentity = null;
482
+ }
483
+ installedPath = destinationPath;
484
+ } catch (error) {
485
+ let reportedError = error;
486
+ if (committed && !backupDestroyed && releaseBinding !== null && stagedIdentity !== null) {
487
+ try {
488
+ const installed = maybeLstat(destinationPath);
489
+ if (installed === null || !sameInstalledFile(stagedIdentity, installed)) {
490
+ throw new Error("refusing to replace an unrecognized failed install");
491
+ }
492
+ if (backupIdentity === null) {
493
+ rmSync(destinationPath, { force: true });
494
+ } else {
495
+ const backup = maybeLstat(backupPath);
496
+ if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
497
+ throw new Error("refusing to restore an unrecognized executable backup");
498
+ }
499
+ renameSync(backupPath, destinationPath);
500
+ backupIdentity = null;
501
+ }
502
+ syncDirectory(releaseBinding.descriptor, "release-after-rollback");
503
+ syncDirectory(toolBinding.descriptor, "tool-after-rollback");
504
+ } catch (rollbackError) {
505
+ reportedError = new AggregateError([error, rollbackError], "metadata-search executable installation and rollback both failed");
506
+ }
507
+ }
508
+ installFailure = { error: reportedError };
509
+ } finally {
510
+ const attemptCleanup = (action) => {
511
+ try {
512
+ action();
513
+ } catch (error) {
514
+ cleanupErrors.push(error);
515
+ }
516
+ };
517
+ attemptCleanup(() => removeIdentityBoundFile(stagingPath, stagedIdentity, "staging executable"));
518
+ attemptCleanup(() => removeIdentityBoundFile(backupPath, backupIdentity, "executable backup"));
519
+ if (releaseBinding !== null) {
520
+ const releaseDescriptor = releaseBinding.descriptor;
521
+ attemptCleanup(() => closeSync(releaseDescriptor));
522
+ }
523
+ if (targetBinding !== null) {
524
+ const targetDescriptor = targetBinding.descriptor;
525
+ attemptCleanup(() => closeSync(targetDescriptor));
526
+ }
527
+ attemptCleanup(() => closeSync(toolBinding.descriptor));
528
+ }
529
+ const errors = [
530
+ ...installFailure === null ? [] : [installFailure.error],
531
+ ...cleanupErrors
532
+ ];
533
+ if (errors.length === 1)
534
+ throw errors[0];
535
+ if (errors.length > 1) {
536
+ const details = errors.map((error) => error instanceof Error ? error.message : "unknown error").join("; ");
537
+ throw new AggregateError(errors, `metadata-search executable installation or cleanup failed: ${details}`);
538
+ }
539
+ if (installedPath === null) {
540
+ throw new Error("metadata-search executable installation produced no outcome");
541
+ }
542
+ return installedPath;
543
+ }
544
+ function defaultTemporaryDirectory() {
545
+ return mkdtempSync(join(tmpdir(), TEMPORARY_DIRECTORY_PREFIX));
546
+ }
547
+ function assertTemporaryDirectoryIdentity(path, expected) {
548
+ const current = maybeLstat(path);
549
+ assertDirectory(current, "Cargo target directory");
550
+ if (!sameDirectoryIdentity(expected, current)) {
551
+ throw new Error("Cargo target directory changed before cleanup");
552
+ }
553
+ }
554
+ function removeOwnedTemporaryDirectory(path, identity) {
555
+ const resolvedPath = resolve(path);
556
+ const resolvedTemporaryRoot = resolve(tmpdir());
557
+ if (dirname(resolvedPath) !== resolvedTemporaryRoot || !basename(resolvedPath).startsWith(TEMPORARY_DIRECTORY_PREFIX)) {
558
+ throw new Error("refusing to remove an unexpected Cargo target directory");
559
+ }
560
+ assertTemporaryDirectoryIdentity(resolvedPath, identity);
561
+ rmSync(resolvedPath, { recursive: true, force: true });
562
+ }
563
+ function defaultCargoRunner(command, options) {
564
+ const result = Bun.spawnSync({
565
+ cmd: [...command],
566
+ cwd: options.cwd,
567
+ env: options.environment,
568
+ stdin: "inherit",
569
+ stdout: "inherit",
570
+ stderr: "inherit"
571
+ });
572
+ return result.exitCode;
573
+ }
574
+ function runMetadataSearchTool(action, dependencies = {}) {
575
+ const toolDirectory = resolve(dependencies.toolDirectory ?? import.meta.dir);
576
+ const platform = dependencies.platform ?? process.platform;
577
+ const createTemporaryDirectory = dependencies.createTemporaryDirectory ?? defaultTemporaryDirectory;
578
+ const removeTemporaryDirectory = dependencies.removeTemporaryDirectory ?? removeOwnedTemporaryDirectory;
579
+ const runCargo = dependencies.runCargo ?? defaultCargoRunner;
580
+ const createdTargetDirectory = createTemporaryDirectory();
581
+ if (!isAbsolute(createdTargetDirectory)) {
582
+ throw new Error("metadata-search tool temporary directory must be absolute");
583
+ }
584
+ const targetDirectory = resolve(createdTargetDirectory);
585
+ const targetIdentity = maybeLstat(targetDirectory);
586
+ assertDirectory(targetIdentity, "Cargo target directory");
587
+ try {
588
+ const command = metadataSearchToolCommand(action, targetDirectory, toolDirectory);
589
+ const exitCode = runCargo(command, {
590
+ cwd: toolDirectory,
591
+ environment: { ...process.env, CARGO_INCREMENTAL: "0" }
592
+ });
593
+ if (exitCode !== 0 || action === "check")
594
+ return exitCode;
595
+ installMetadataSearchExecutable({
596
+ sourcePath: join(targetDirectory, "release", metadataSearchExecutableName(platform)),
597
+ toolDirectory,
598
+ platform,
599
+ beforeInstall: dependencies.beforeInstall,
600
+ beforeCommit: dependencies.beforeCommit,
601
+ syncDirectory: dependencies.syncDirectory
602
+ });
603
+ return 0;
604
+ } finally {
605
+ assertTemporaryDirectoryIdentity(targetDirectory, targetIdentity);
606
+ removeTemporaryDirectory(targetDirectory, targetIdentity);
607
+ }
608
+ }
609
+ if (false) {}
610
+
611
+ // src/clip/url-metadata-cli.ts
612
+ var urlMetadataUsage = `kb url-metadata \u2014 backfill bounded metadata for saved URLs
613
+
614
+ Usage:
615
+ kb url-metadata tool <build|check>
616
+ kb url-metadata backfill [--root <vault>] [--search-binary <path>] [--refresh]
617
+ [--archive | --no-archive] [--delay-ms <milliseconds>]
618
+ [--max-results <count>] [--timeout <milliseconds>] [--json]
619
+
620
+ Build or validate the immutable metadata-search-engine-rs helper directly from
621
+ an installed @hraness/kb package:
622
+ kb url-metadata tool build
623
+ `;
624
+ var defaultOutput = {
625
+ stdout: (value) => process.stdout.write(value),
626
+ stderr: (value) => process.stderr.write(value)
627
+ };
628
+ function integer(value, minimum, maximum) {
629
+ if (!/^(?:0|[1-9]\d*)$/u.test(value))
630
+ return null;
631
+ const parsed = Number(value);
632
+ return Number.isSafeInteger(parsed) && parsed >= minimum && parsed <= maximum ? parsed : null;
633
+ }
634
+ function metadataSearchBinaryPath(packageRoot = findKbPackageRoot(), platform = process.platform) {
635
+ const executable = platform === "win32" ? "kb-url-metadata-search.exe" : "kb-url-metadata-search";
636
+ return resolve2(packageRoot, "src", "clip", "metadata-search-tool", "target", "release", executable);
637
+ }
638
+ function defaultBinaryPath(environment) {
639
+ const configured = environment.HRANESS_KB_METADATA_SEARCH_BINARY;
640
+ if (configured !== undefined && configured.trim() !== "")
641
+ return resolve2(configured);
642
+ return metadataSearchBinaryPath();
643
+ }
644
+ function parseUrlMetadataArguments(arguments_, environment = process.env) {
645
+ const command = arguments_[0];
646
+ const jsonRequested = arguments_.includes("--json");
647
+ if (command === undefined || command === "help" || command === "--help" || command === "-h") {
648
+ return { ok: true, value: { kind: "help" } };
649
+ }
650
+ if (command === "tool") {
651
+ const action = arguments_[1];
652
+ if (arguments_.length !== 2 || action !== "build" && action !== "check") {
653
+ return {
654
+ ok: false,
655
+ message: "url-metadata tool accepts exactly one build or check action",
656
+ json: jsonRequested
657
+ };
658
+ }
659
+ return { ok: true, value: { kind: "tool", action } };
660
+ }
661
+ if (command !== "backfill") {
662
+ return {
663
+ ok: false,
664
+ message: "url-metadata accepts only the tool or backfill subcommand",
665
+ json: jsonRequested
666
+ };
667
+ }
668
+ let root = "kb";
669
+ let binaryPath = defaultBinaryPath(environment);
670
+ let refresh = false;
671
+ let discoverArchives = true;
672
+ let delayMs = 1000;
673
+ let maxResults = 20;
674
+ let timeoutMs = 15000;
675
+ let json = false;
676
+ for (let index = 1;index < arguments_.length; index += 1) {
677
+ const argument = arguments_[index];
678
+ if (argument === "--refresh")
679
+ refresh = true;
680
+ else if (argument === "--archive")
681
+ discoverArchives = true;
682
+ else if (argument === "--no-archive")
683
+ discoverArchives = false;
684
+ else if (argument === "--json")
685
+ json = true;
686
+ else if (argument === "--root" || argument === "--search-binary" || argument === "--delay-ms" || argument === "--max-results" || argument === "--timeout") {
687
+ const value = arguments_[index + 1];
688
+ if (value === undefined || value.startsWith("--")) {
689
+ return { ok: false, message: `${argument} requires a value`, json: jsonRequested };
690
+ }
691
+ index += 1;
692
+ if (argument === "--root")
693
+ root = value;
694
+ else if (argument === "--search-binary")
695
+ binaryPath = resolve2(value);
696
+ else if (argument === "--delay-ms") {
697
+ const parsed = integer(value, 0, 60000);
698
+ if (parsed === null)
699
+ return { ok: false, message: "--delay-ms must be an integer from 0 through 60000", json: jsonRequested };
700
+ delayMs = parsed;
701
+ } else if (argument === "--max-results") {
702
+ const parsed = integer(value, 1, 20);
703
+ if (parsed === null)
704
+ return { ok: false, message: "--max-results must be an integer from 1 through 20", json: jsonRequested };
705
+ maxResults = parsed;
706
+ } else {
707
+ const parsed = integer(value, 500, 15000);
708
+ if (parsed === null)
709
+ return { ok: false, message: "--timeout must be an integer from 500 through 15000", json: jsonRequested };
710
+ timeoutMs = parsed;
711
+ }
712
+ } else {
713
+ return { ok: false, message: `unknown url-metadata option: ${argument ?? ""}`, json: jsonRequested };
714
+ }
715
+ }
716
+ if (root.trim() === "")
717
+ return { ok: false, message: "--root must not be empty", json: jsonRequested };
718
+ return {
719
+ ok: true,
720
+ value: {
721
+ kind: "backfill",
722
+ root,
723
+ binaryPath,
724
+ refresh,
725
+ discoverArchives,
726
+ delayMs,
727
+ maxResults,
728
+ timeoutMs,
729
+ json
730
+ }
731
+ };
732
+ }
733
+ function terminalJson(value) {
734
+ return `${JSON.stringify(value, (_key, candidate) => typeof candidate === "string" ? sanitizeTerminalText(candidate) : candidate, 2)}
735
+ `;
736
+ }
737
+ function renderReport(report) {
738
+ const counts = report.statusCounts;
739
+ return [
740
+ `URL metadata: ${report.processedRecords} processed, ${report.skippedRecords} resumed, ${report.remainingRecords} remaining.`,
741
+ `Writes: ${report.writtenRecords} new or refreshed, ${report.unchangedRecords} unchanged.`,
742
+ `Status: ${counts.matched} matched, ${counts.partial} partial, ${counts.notFound} not found, ${counts.unavailable} unavailable.`
743
+ ].join(`
744
+ `) + `
745
+ `;
746
+ }
747
+ async function main3(rawArguments = process.argv.slice(2), environment = process.env, output = defaultOutput, dependencies = {}) {
748
+ const parsed = parseUrlMetadataArguments(rawArguments, environment);
749
+ if (!parsed.ok) {
750
+ if (parsed.json)
751
+ output.stdout(terminalJson({ ok: false, error: parsed.message }));
752
+ else
753
+ output.stderr(`error: ${sanitizeTerminalText(parsed.message)}
754
+
755
+ ${urlMetadataUsage}`);
756
+ return 2;
757
+ }
758
+ if (parsed.value.kind === "help") {
759
+ output.stdout(urlMetadataUsage);
760
+ return 0;
761
+ }
762
+ try {
763
+ if (parsed.value.kind === "tool") {
764
+ return (dependencies.runTool ?? runMetadataSearchTool)(parsed.value.action, {
765
+ toolDirectory: resolve2(findKbPackageRoot(), "src", "clip", "metadata-search-tool")
766
+ });
767
+ }
768
+ const provider = (dependencies.createSearchProvider ?? ((binaryPath) => createRustMetadataSearchProvider({ binaryPath })))(parsed.value.binaryPath);
769
+ const report = await (dependencies.backfill ?? backfillSavedUrlMetadata)({
770
+ vaultRoot: parsed.value.root,
771
+ refresh: parsed.value.refresh,
772
+ discoverArchives: parsed.value.discoverArchives,
773
+ interRequestDelayMs: parsed.value.delayMs,
774
+ maxResults: parsed.value.maxResults,
775
+ searchTimeoutMs: parsed.value.timeoutMs
776
+ }, { searchProvider: provider });
777
+ if (parsed.value.json)
778
+ output.stdout(terminalJson({ ok: !report.aborted, ...report }));
779
+ else
780
+ output.stdout(renderReport(report));
781
+ if (report.aborted)
782
+ return 130;
783
+ return report.statusCounts.unavailable > 0 ? 3 : 0;
784
+ } catch (error) {
785
+ const message = error instanceof Error ? error.message : String(error);
786
+ if (parsed.value.kind === "backfill" && parsed.value.json) {
787
+ output.stdout(terminalJson({ ok: false, error: message }));
788
+ } else
789
+ output.stderr(`error: ${sanitizeTerminalText(message)}
790
+ `);
791
+ return 1;
792
+ }
793
+ }
794
+ if (false)
795
+ ;
796
+
797
+ // src/cli.ts
798
+ var defaultOutput2 = {
799
+ stdout: (value) => process.stdout.write(value),
800
+ stderr: (value) => process.stderr.write(value)
801
+ };
802
+ async function readBoundedUtf8(path, maximumBytes, label) {
803
+ const handle = await open(path, "r");
804
+ try {
805
+ const bytes = new Uint8Array(maximumBytes + 1);
806
+ let offset = 0;
807
+ while (offset < bytes.byteLength) {
808
+ const { bytesRead } = await handle.read(bytes, offset, bytes.byteLength - offset, null);
809
+ if (bytesRead === 0)
810
+ break;
811
+ offset += bytesRead;
812
+ }
813
+ if (offset > maximumBytes) {
814
+ throw new Error(`${label} exceeds the ${maximumBytes}-byte limit`);
815
+ }
816
+ try {
817
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, offset));
818
+ } catch (error) {
819
+ throw new Error(`${label} is not valid UTF-8`, { cause: error });
820
+ }
821
+ } finally {
822
+ await handle.close();
823
+ }
824
+ }
825
+ async function loadSearchRulesFile(path) {
826
+ const text = await readBoundedUtf8(path, MAX_SEARCH_RULE_CONFIG_BYTES, "search rules file");
827
+ let input;
828
+ try {
829
+ input = JSON.parse(text);
830
+ } catch (error) {
831
+ throw new Error("search rules file is not valid JSON", { cause: error });
832
+ }
833
+ return parseSearchRules(input);
834
+ }
835
+ var usage = `kb \u2014 auditable capture and derived links for Markdown vaults
836
+
837
+ Usage:
838
+ kb init [directory] [--json]
839
+ kb clip <url|current> [capture options]
840
+ kb capture show <bundle> [--verify-assets] [--include-source-html] [--json]
841
+ kb capture verify <bundle> [--verify-assets] [--json]
842
+ kb capture diff <bundle> [--repo <repository>] [--ref <ref>] [--json]
843
+ kb url-metadata tool <build|check>
844
+ kb url-metadata backfill [metadata options]
845
+ kb inspect <url> [capture options]
846
+ kb pdf <file-or-url> [PDF options]
847
+ kb refresh [--root <directory>] [--index <path>] [--json]
848
+ kb check [--root <directory>] [--index <path>] [--no-catalog] [--json]
849
+ kb catalog [--root <directory>] [--index <path>] [--json]
850
+ kb graph [--root <directory>] [--index <path>] [--json]
851
+ kb backlinks <note> [--root <directory>] [--index <path>] [--json]
852
+ kb links <note> [--root <directory>] [--direction <in|out|both>] [--depth <count>] [--limit <count>] [--json]
853
+ kb note create <id> --title <title> [--type <type>] [--tag <tag>] [--body <markdown> | --body-file <path>] [--root <directory>] [--json]
854
+ kb relation add <source> <predicate> <target> [--root <directory>] [--expected-revision <sha256:...>] [--json]
855
+ kb relation remove <source> <predicate> <target> [--root <directory>] [--expected-revision <sha256:...>] [--json]
856
+ kb relation list <note> [--root <directory>] [--json]
857
+ kb percolate [note] [--root <directory>] [--min-support <count>] [--limit <count>] [--json]
858
+ kb list [--root <directory>] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--sort <field>] [--order <asc|desc>] [--limit <count>] [--json]
859
+ kb index [--root <directory>] [--database <path>] [--force] [--json]
860
+ kb search <query> [--root <directory>] [--repo <repository>] [--database <path>] [--mode <hybrid|exact|keyword|semantic>] [--rules <file>] [--priority] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--related <note>] [--graph-depth <1|2>] [--no-graph] [--history | --no-history | --require-history] [--limit <count>] [--candidate-limit <count>] [--min-score <score>] [--json]
861
+ kb history <note> [--root <directory>] [--repo <repository>] [--limit <count>] [--cochanged-limit <count>] [--json]
862
+ kb history search <query-or-path> [--root <directory>] [--repo <repository>] [--limit <count>] [--commit-limit <count>] [--cochanged-limit <count>] [--json]
863
+ kb evaluate <manifest.json> [--root <directory>] [--repo <repository>] [--database <path>] [--retriever <id>] [--split <development|test|all>] [--limit <count>] [--cutoff <count>] [--timeout <milliseconds>] [--baseline <id>] [--model-file <path>] [--cache-state <cold|mixed|warm>] [--json]
864
+ kb portfolio search <query> --registry <file> --workspace <directory> (--shared | --vault <owner/id>...) [--mode <hybrid|exact|keyword|semantic>] [--rules <file>] [--priority] [--limit <count>] [--require-all] [--json]
865
+ kb portfolio audit --registry <file> --workspace <directory> (--all | --shared | --vault <owner/id>...) [--strict] [--json]
866
+ kb inbox [--root <directory>] [--source-prefix <directory>] [--limit <count>] [--json]
867
+ kb context <repository-path> [--root <vault>] [--repo <repository>] [--kind <auto|file|directory>] [--json]
868
+ kb agents identity <repository-scope> [--json]
869
+ kb agents check [--root <vault>] [--repo <repository>] [--json]
870
+ kb agents audit [--root <vault>] [--repo <repository>] [--json]
871
+ kb doctor [--json]
872
+ kb adapters [--json]
873
+
874
+ Run \`kb clip --help\` for web capture options or \`kb pdf --help\` for PDF conversion options.
875
+ `;
876
+ function safe(value) {
877
+ return sanitizeTerminalLine(redactSensitiveText(value));
878
+ }
879
+ function terminalSafeJson(value) {
880
+ return `${JSON.stringify(value, (_key, candidate) => typeof candidate === "string" ? sanitizeTerminalText(redactSensitiveText(candidate)) : candidate, 2)}
881
+ `;
882
+ }
883
+ function readValue(arguments_, index) {
884
+ const value = arguments_[index + 1];
885
+ return value === undefined || value.startsWith("--") ? null : value;
886
+ }
887
+ function parseCaptureBundleCommand(arguments_) {
888
+ const action = arguments_[0];
889
+ if (action !== "show" && action !== "verify") {
890
+ return { ok: false, message: "capture bundle action must be show or verify" };
891
+ }
892
+ let json = false;
893
+ let includeSourceHtml = false;
894
+ let verifyAssets = false;
895
+ const positional = [];
896
+ for (const argument of arguments_.slice(1)) {
897
+ if (argument === "--json")
898
+ json = true;
899
+ else if (argument === "--verify-assets")
900
+ verifyAssets = true;
901
+ else if (argument === "--include-source-html" && action === "show")
902
+ includeSourceHtml = true;
903
+ else if (argument.startsWith("--"))
904
+ return { ok: false, message: `unknown capture ${action} option: ${argument}` };
905
+ else
906
+ positional.push(argument);
907
+ }
908
+ if (positional.length !== 1 || positional[0] === undefined) {
909
+ return { ok: false, message: `capture ${action} requires exactly one bundle path` };
910
+ }
911
+ return {
912
+ ok: true,
913
+ value: {
914
+ kind: "capture-bundle",
915
+ action,
916
+ path: positional[0],
917
+ options: { verifyAssets, ...includeSourceHtml ? { includeSourceHtml: true } : {} },
918
+ json
919
+ }
920
+ };
921
+ }
922
+ function parseCaptureDiffCommand(arguments_) {
923
+ let repository = ".";
924
+ let ref = "HEAD";
925
+ let json = false;
926
+ const positional = [];
927
+ for (let index = 0;index < arguments_.length; index += 1) {
928
+ const argument = arguments_[index];
929
+ if (argument === undefined)
930
+ continue;
931
+ if (argument === "--json")
932
+ json = true;
933
+ else if (argument === "--repo" || argument === "--ref") {
934
+ const value = readValue(arguments_, index);
935
+ if (value === null)
936
+ return { ok: false, message: `${argument} requires a value` };
937
+ if (argument === "--repo")
938
+ repository = value;
939
+ else
940
+ ref = value;
941
+ index += 1;
942
+ } else if (argument.startsWith("--"))
943
+ return { ok: false, message: `unknown capture diff option: ${argument}` };
944
+ else
945
+ positional.push(argument);
946
+ }
947
+ if (positional.length !== 1 || positional[0] === undefined) {
948
+ return { ok: false, message: "capture diff requires exactly one bundle path" };
949
+ }
950
+ return {
951
+ ok: true,
952
+ value: {
953
+ kind: "capture-diff",
954
+ options: { bundle: positional[0], repository, ref },
955
+ json
956
+ }
957
+ };
958
+ }
959
+ function parseVaultCommand(command, arguments_) {
960
+ let root = ".";
961
+ let index;
962
+ let json = false;
963
+ let direction = "both";
964
+ let depth = 1;
965
+ let limit;
966
+ let noCatalog = false;
967
+ const positional = [];
968
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
969
+ const argument = arguments_[cursor];
970
+ if (argument === undefined)
971
+ continue;
972
+ if (argument === "--json") {
973
+ json = true;
974
+ continue;
975
+ }
976
+ if (argument === "--no-catalog" && command === "check") {
977
+ noCatalog = true;
978
+ continue;
979
+ }
980
+ if (argument === "--root" || argument === "--index") {
981
+ const value = readValue(arguments_, cursor);
982
+ if (value === null)
983
+ return { ok: false, message: `${argument} requires a value` };
984
+ if (argument === "--root")
985
+ root = value;
986
+ else
987
+ index = value;
988
+ cursor += 1;
989
+ continue;
990
+ }
991
+ if (command === "links" && (argument === "--direction" || argument === "--depth" || argument === "--limit")) {
992
+ const value = readValue(arguments_, cursor);
993
+ if (value === null)
994
+ return { ok: false, message: `${argument} requires a value` };
995
+ if (argument === "--direction") {
996
+ if (value !== "in" && value !== "out" && value !== "both") {
997
+ return { ok: false, message: "--direction must be in, out, or both" };
998
+ }
999
+ direction = value;
1000
+ } else if (argument === "--depth") {
1001
+ const parsed = Number(value);
1002
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 10) {
1003
+ return { ok: false, message: "--depth must be an integer from 1 through 10" };
1004
+ }
1005
+ depth = parsed;
1006
+ } else {
1007
+ const parsed = Number(value);
1008
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 1000) {
1009
+ return { ok: false, message: "--limit must be an integer from 1 through 1000" };
1010
+ }
1011
+ limit = parsed;
1012
+ }
1013
+ cursor += 1;
1014
+ continue;
1015
+ }
1016
+ if (argument.startsWith("--"))
1017
+ return { ok: false, message: `unknown ${command} option` };
1018
+ positional.push(argument);
1019
+ }
1020
+ if (command === "backlinks" || command === "links") {
1021
+ const note = positional[0];
1022
+ if (positional.length !== 1 || note === undefined) {
1023
+ return { ok: false, message: `${command} requires exactly one note path, title, or alias` };
1024
+ }
1025
+ return {
1026
+ ok: true,
1027
+ value: {
1028
+ kind: command,
1029
+ root,
1030
+ options: index === undefined ? {} : { index },
1031
+ json,
1032
+ note,
1033
+ ...command === "links" ? { direction, depth, ...limit === undefined ? {} : { limit } } : {}
1034
+ }
1035
+ };
1036
+ }
1037
+ if (positional.length !== 0)
1038
+ return { ok: false, message: `${command} does not accept positional arguments` };
1039
+ return {
1040
+ ok: true,
1041
+ value: {
1042
+ kind: command,
1043
+ root,
1044
+ options: index === undefined ? {} : { index },
1045
+ json,
1046
+ ...command === "check" && noCatalog ? { noCatalog: true } : {}
1047
+ }
1048
+ };
1049
+ }
1050
+ function parseCatalogCommand(arguments_) {
1051
+ let root = ".";
1052
+ let index;
1053
+ let json = false;
1054
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1055
+ const argument = arguments_[cursor];
1056
+ if (argument === "--json") {
1057
+ json = true;
1058
+ continue;
1059
+ }
1060
+ if (argument === "--root" || argument === "--index") {
1061
+ const value = readValue(arguments_, cursor);
1062
+ if (value === null)
1063
+ return { ok: false, message: `${argument} requires a value` };
1064
+ if (argument === "--root")
1065
+ root = value;
1066
+ else
1067
+ index = value;
1068
+ cursor += 1;
1069
+ continue;
1070
+ }
1071
+ return {
1072
+ ok: false,
1073
+ message: argument?.startsWith("--") === true ? "unknown catalog option" : "catalog does not accept positional arguments"
1074
+ };
1075
+ }
1076
+ return {
1077
+ ok: true,
1078
+ value: {
1079
+ kind: "catalog",
1080
+ root,
1081
+ options: index === undefined ? { mentionScope: false } : { index, mentionScope: false },
1082
+ json
1083
+ }
1084
+ };
1085
+ }
1086
+ function metadataScalar(raw) {
1087
+ const value = raw.trim();
1088
+ if (value.startsWith('"') || value.endsWith('"')) {
1089
+ try {
1090
+ const parsed = JSON.parse(value);
1091
+ return typeof parsed === "string" ? { ok: true, value: parsed } : { ok: false, message: "quoted --where values must be strings" };
1092
+ } catch {
1093
+ return { ok: false, message: "double-quoted --where values must be valid JSON strings" };
1094
+ }
1095
+ }
1096
+ if (value.startsWith("'") || value.endsWith("'")) {
1097
+ if (!(value.startsWith("'") && value.endsWith("'") && value.length >= 2)) {
1098
+ return { ok: false, message: "single-quoted --where values must have a closing quote" };
1099
+ }
1100
+ return { ok: true, value: value.slice(1, -1).replaceAll("''", "'") };
1101
+ }
1102
+ if (value === "null")
1103
+ return { ok: true, value: null };
1104
+ if (value === "true")
1105
+ return { ok: true, value: true };
1106
+ if (value === "false")
1107
+ return { ok: true, value: false };
1108
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value)) {
1109
+ const number = Number(value);
1110
+ if (Number.isFinite(number)) {
1111
+ if (Number.isInteger(number) && !Number.isSafeInteger(number)) {
1112
+ return { ok: false, message: "numeric --where values must be safe integers; quote large identifiers" };
1113
+ }
1114
+ return { ok: true, value: number };
1115
+ }
1116
+ }
1117
+ return { ok: true, value };
1118
+ }
1119
+ function querySort(raw) {
1120
+ const value = raw.trim();
1121
+ if (value === "title" || value === "path" || value === "inbound" || value === "outbound") {
1122
+ return { kind: "builtin", field: value };
1123
+ }
1124
+ const path = value.replace(/^(?:meta|metadata)\./u, "");
1125
+ return path === "" ? null : { kind: "metadata", path };
1126
+ }
1127
+ function parseListCommand(arguments_) {
1128
+ let root = ".";
1129
+ let index;
1130
+ let json = false;
1131
+ let sort = { kind: "builtin", field: "path" };
1132
+ let direction = "asc";
1133
+ let limit;
1134
+ const filters = [];
1135
+ const tags = [];
1136
+ const repositoryScopes = [];
1137
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1138
+ const argument = arguments_[cursor];
1139
+ if (argument === undefined)
1140
+ continue;
1141
+ if (argument === "--json") {
1142
+ json = true;
1143
+ continue;
1144
+ }
1145
+ if (argument === "--root" || argument === "--index" || argument === "--where" || argument === "--has" || argument === "--tag" || argument === "--scope" || argument === "--repository-scope" || argument === "--sort" || argument === "--order" || argument === "--limit") {
1146
+ const value = readValue(arguments_, cursor);
1147
+ if (value === null)
1148
+ return { ok: false, message: `${argument} requires a value` };
1149
+ if (argument === "--root")
1150
+ root = value;
1151
+ else if (argument === "--index")
1152
+ index = value;
1153
+ else if (argument === "--tag") {
1154
+ if (tags.length >= MAX_QUERY_TAGS) {
1155
+ return {
1156
+ ok: false,
1157
+ message: `Query tags may contain at most ${MAX_QUERY_TAGS} entries.`
1158
+ };
1159
+ }
1160
+ tags.push(value);
1161
+ } else if (argument === "--scope" || argument === "--repository-scope") {
1162
+ if (repositoryScopes.length >= MAX_REPOSITORY_SCOPES) {
1163
+ return {
1164
+ ok: false,
1165
+ message: `Repository scope filters may contain at most ${MAX_REPOSITORY_SCOPES} entries.`
1166
+ };
1167
+ }
1168
+ repositoryScopes.push(value);
1169
+ } else if (argument === "--has") {
1170
+ if (value.trim() === "")
1171
+ return { ok: false, message: "--has requires a metadata path" };
1172
+ if (filters.length >= MAX_QUERY_FILTERS) {
1173
+ return {
1174
+ ok: false,
1175
+ message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.`
1176
+ };
1177
+ }
1178
+ filters.push({ kind: "exists", path: value });
1179
+ } else if (argument === "--where") {
1180
+ const equals = value.indexOf("=");
1181
+ const path = equals === -1 ? "" : value.slice(0, equals).trim();
1182
+ if (path === "")
1183
+ return { ok: false, message: "--where requires path=value" };
1184
+ const scalar = metadataScalar(value.slice(equals + 1));
1185
+ if (!scalar.ok)
1186
+ return scalar;
1187
+ if (filters.length >= MAX_QUERY_FILTERS) {
1188
+ return {
1189
+ ok: false,
1190
+ message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.`
1191
+ };
1192
+ }
1193
+ filters.push({ kind: "equals", path, value: scalar.value });
1194
+ } else if (argument === "--sort") {
1195
+ const parsed = querySort(value);
1196
+ if (parsed === null)
1197
+ return { ok: false, message: "--sort requires a field" };
1198
+ sort = parsed;
1199
+ } else if (argument === "--order") {
1200
+ if (value !== "asc" && value !== "desc") {
1201
+ return { ok: false, message: "--order must be asc or desc" };
1202
+ }
1203
+ direction = value;
1204
+ } else {
1205
+ const parsed = Number(value);
1206
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
1207
+ return { ok: false, message: "--limit must be a non-negative integer" };
1208
+ }
1209
+ limit = parsed;
1210
+ }
1211
+ cursor += 1;
1212
+ continue;
1213
+ }
1214
+ return {
1215
+ ok: false,
1216
+ message: argument.startsWith("--") ? "unknown list option" : "list does not accept positional arguments"
1217
+ };
1218
+ }
1219
+ try {
1220
+ validateQueryOptions({
1221
+ filters,
1222
+ tags,
1223
+ repositoryScopes,
1224
+ sort,
1225
+ direction,
1226
+ ...limit === undefined ? {} : { limit }
1227
+ });
1228
+ } catch (error) {
1229
+ return {
1230
+ ok: false,
1231
+ message: error instanceof Error ? error.message : String(error)
1232
+ };
1233
+ }
1234
+ return {
1235
+ ok: true,
1236
+ value: {
1237
+ kind: "list",
1238
+ root,
1239
+ options: index === undefined ? {} : { index },
1240
+ filters,
1241
+ tags,
1242
+ repositoryScopes,
1243
+ sort,
1244
+ direction,
1245
+ ...limit === undefined ? {} : { limit },
1246
+ json
1247
+ }
1248
+ };
1249
+ }
1250
+ function parseInboxCommand(arguments_) {
1251
+ let root = ".";
1252
+ let index;
1253
+ let json = false;
1254
+ let limit = 100;
1255
+ const sourcePrefixes = [];
1256
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1257
+ const argument = arguments_[cursor];
1258
+ if (argument === undefined)
1259
+ continue;
1260
+ if (argument === "--json") {
1261
+ json = true;
1262
+ continue;
1263
+ }
1264
+ if (argument === "--root" || argument === "--index" || argument === "--limit" || argument === "--source-prefix") {
1265
+ const value = readValue(arguments_, cursor);
1266
+ if (value === null)
1267
+ return { ok: false, message: `${argument} requires a value` };
1268
+ if (argument === "--root")
1269
+ root = value;
1270
+ else if (argument === "--index")
1271
+ index = value;
1272
+ else if (argument === "--source-prefix") {
1273
+ if (sourcePrefixes.length >= MAX_SOURCE_INBOX_PREFIXES) {
1274
+ return {
1275
+ ok: false,
1276
+ message: `Source inbox accepts at most ${MAX_SOURCE_INBOX_PREFIXES} source prefixes.`
1277
+ };
1278
+ }
1279
+ sourcePrefixes.push(value);
1280
+ } else {
1281
+ const parsed = Number(value);
1282
+ if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > MAX_SOURCE_INBOX_RESULTS) {
1283
+ return {
1284
+ ok: false,
1285
+ message: `--limit must be an integer from 0 through ${MAX_SOURCE_INBOX_RESULTS}`
1286
+ };
1287
+ }
1288
+ limit = parsed;
1289
+ }
1290
+ cursor += 1;
1291
+ continue;
1292
+ }
1293
+ return {
1294
+ ok: false,
1295
+ message: argument.startsWith("--") ? "unknown inbox option" : "inbox does not accept positional arguments"
1296
+ };
1297
+ }
1298
+ return {
1299
+ ok: true,
1300
+ value: {
1301
+ kind: "inbox",
1302
+ root,
1303
+ options: index === undefined ? { mentionScope: false } : { index, mentionScope: false },
1304
+ sourcePrefixes,
1305
+ limit,
1306
+ json
1307
+ }
1308
+ };
1309
+ }
1310
+ function parseSemanticCommand(command, arguments_) {
1311
+ let root = ".";
1312
+ let repository = ".";
1313
+ let database;
1314
+ let force = false;
1315
+ let json = false;
1316
+ let mode;
1317
+ let priority = false;
1318
+ let rulesPath;
1319
+ let limit;
1320
+ let candidateLimit;
1321
+ let minScore;
1322
+ let graphDepth;
1323
+ let noGraph = false;
1324
+ let history = false;
1325
+ let noHistory = false;
1326
+ let requireHistory = false;
1327
+ const filters = [];
1328
+ const tags = [];
1329
+ const repositoryScopes = [];
1330
+ const related = [];
1331
+ const positional = [];
1332
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1333
+ const argument = arguments_[cursor];
1334
+ if (argument === undefined)
1335
+ continue;
1336
+ if (argument === "--json") {
1337
+ json = true;
1338
+ continue;
1339
+ }
1340
+ if (argument === "--force" && command === "index") {
1341
+ force = true;
1342
+ continue;
1343
+ }
1344
+ if (argument === "--no-graph" && command === "search") {
1345
+ noGraph = true;
1346
+ continue;
1347
+ }
1348
+ if (argument === "--no-history" && command === "search") {
1349
+ noHistory = true;
1350
+ continue;
1351
+ }
1352
+ if (argument === "--history" && command === "search") {
1353
+ history = true;
1354
+ continue;
1355
+ }
1356
+ if (argument === "--require-history" && command === "search") {
1357
+ requireHistory = true;
1358
+ continue;
1359
+ }
1360
+ if (argument === "--priority" && command === "search") {
1361
+ priority = true;
1362
+ continue;
1363
+ }
1364
+ if (argument === "--root" || argument === "--database" || command === "search" && (argument === "--repo" || argument === "--rules")) {
1365
+ const value = readValue(arguments_, cursor);
1366
+ if (value === null)
1367
+ return { ok: false, message: `${argument} requires a value` };
1368
+ if (argument === "--root")
1369
+ root = value;
1370
+ else if (argument === "--repo")
1371
+ repository = value;
1372
+ else if (argument === "--rules")
1373
+ rulesPath = value;
1374
+ else
1375
+ database = value;
1376
+ cursor += 1;
1377
+ continue;
1378
+ }
1379
+ if (command === "search" && (argument === "--mode" || argument === "--limit" || argument === "--candidate-limit" || argument === "--min-score" || argument === "--where" || argument === "--has" || argument === "--tag" || argument === "--scope" || argument === "--repository-scope" || argument === "--related" || argument === "--graph-depth")) {
1380
+ const value = readValue(arguments_, cursor);
1381
+ if (value === null)
1382
+ return { ok: false, message: `${argument} requires a value` };
1383
+ if (argument === "--mode") {
1384
+ if (value !== "hybrid" && value !== "exact" && value !== "semantic" && value !== "keyword") {
1385
+ return { ok: false, message: "--mode must be hybrid, exact, keyword, or semantic" };
1386
+ }
1387
+ mode = value;
1388
+ } else if (argument === "--where") {
1389
+ const equals = value.indexOf("=");
1390
+ const path = equals === -1 ? "" : value.slice(0, equals).trim();
1391
+ if (path === "")
1392
+ return { ok: false, message: "--where requires path=value" };
1393
+ const scalar = metadataScalar(value.slice(equals + 1));
1394
+ if (!scalar.ok)
1395
+ return scalar;
1396
+ if (filters.length >= MAX_QUERY_FILTERS) {
1397
+ return {
1398
+ ok: false,
1399
+ message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.`
1400
+ };
1401
+ }
1402
+ filters.push({ kind: "equals", path, value: scalar.value });
1403
+ } else if (argument === "--has") {
1404
+ if (value.trim() === "")
1405
+ return { ok: false, message: "--has requires a metadata path" };
1406
+ if (filters.length >= MAX_QUERY_FILTERS) {
1407
+ return {
1408
+ ok: false,
1409
+ message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.`
1410
+ };
1411
+ }
1412
+ filters.push({ kind: "exists", path: value });
1413
+ } else if (argument === "--tag") {
1414
+ if (tags.length >= MAX_QUERY_TAGS) {
1415
+ return {
1416
+ ok: false,
1417
+ message: `Query tags may contain at most ${MAX_QUERY_TAGS} entries.`
1418
+ };
1419
+ }
1420
+ tags.push(value);
1421
+ } else if (argument === "--scope" || argument === "--repository-scope") {
1422
+ if (repositoryScopes.length >= MAX_REPOSITORY_SCOPES) {
1423
+ return {
1424
+ ok: false,
1425
+ message: `Repository scope filters may contain at most ${MAX_REPOSITORY_SCOPES} entries.`
1426
+ };
1427
+ }
1428
+ repositoryScopes.push(value);
1429
+ } else if (argument === "--related") {
1430
+ if (related.length >= MAX_SEARCH_RELATED_SEEDS) {
1431
+ return {
1432
+ ok: false,
1433
+ message: `Hybrid search accepts at most ${MAX_SEARCH_RELATED_SEEDS} explicit related-note seeds.`
1434
+ };
1435
+ }
1436
+ if (Buffer.byteLength(value, "utf8") > MAX_SEARCH_NOTE_REFERENCE_BYTES) {
1437
+ return {
1438
+ ok: false,
1439
+ message: `Search related-note seed ${related.length + 1} must be at most ` + `${MAX_SEARCH_NOTE_REFERENCE_BYTES.toLocaleString("en-US")} UTF-8 bytes.`
1440
+ };
1441
+ }
1442
+ if (value.trim() === "") {
1443
+ return {
1444
+ ok: false,
1445
+ message: `Search related-note seed ${related.length + 1} must not be empty.`
1446
+ };
1447
+ }
1448
+ related.push(value);
1449
+ } else if (argument === "--min-score") {
1450
+ const parsed = Number(value);
1451
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
1452
+ return { ok: false, message: "--min-score must be a number from 0 through 1" };
1453
+ }
1454
+ minScore = parsed;
1455
+ } else {
1456
+ const parsed = Number(value);
1457
+ if (argument === "--limit" || argument === "--candidate-limit") {
1458
+ const maximum = argument === "--limit" ? MAX_SEARCH_RESULTS : MAX_SEARCH_CANDIDATES;
1459
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > maximum) {
1460
+ return {
1461
+ ok: false,
1462
+ message: `${argument} must be an integer from 1 through ${maximum}`
1463
+ };
1464
+ }
1465
+ if (argument === "--limit")
1466
+ limit = parsed;
1467
+ else
1468
+ candidateLimit = parsed;
1469
+ } else if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 2) {
1470
+ return { ok: false, message: "--graph-depth must be 1 or 2" };
1471
+ } else {
1472
+ graphDepth = parsed;
1473
+ }
1474
+ }
1475
+ cursor += 1;
1476
+ continue;
1477
+ }
1478
+ if (argument.startsWith("--"))
1479
+ return { ok: false, message: `unknown ${command} option` };
1480
+ positional.push(argument);
1481
+ }
1482
+ if (command === "index") {
1483
+ if (positional.length > 0)
1484
+ return { ok: false, message: "index does not accept positional arguments" };
1485
+ return {
1486
+ ok: true,
1487
+ value: { kind: "index", root, ...database === undefined ? {} : { database }, force, json }
1488
+ };
1489
+ }
1490
+ if (noHistory && requireHistory) {
1491
+ return {
1492
+ ok: false,
1493
+ message: "--no-history and --require-history cannot be used together"
1494
+ };
1495
+ }
1496
+ if (history && noHistory) {
1497
+ return {
1498
+ ok: false,
1499
+ message: "--history and --no-history cannot be used together"
1500
+ };
1501
+ }
1502
+ if (history && requireHistory) {
1503
+ return {
1504
+ ok: false,
1505
+ message: "--history and --require-history cannot be used together"
1506
+ };
1507
+ }
1508
+ if (candidateLimit !== undefined && candidateLimit < (limit ?? DEFAULT_SEARCH_RESULTS)) {
1509
+ return {
1510
+ ok: false,
1511
+ message: "Search candidate limit must be at least the result limit."
1512
+ };
1513
+ }
1514
+ if (mode === "exact" && minScore !== undefined) {
1515
+ return {
1516
+ ok: false,
1517
+ message: "Search minimum score applies only to hybrid, keyword, or semantic mode."
1518
+ };
1519
+ }
1520
+ if (priority && rulesPath === undefined) {
1521
+ return { ok: false, message: "--priority requires --rules" };
1522
+ }
1523
+ const rawQuery = positional.join(" ");
1524
+ if (rawQuery.trim() === "")
1525
+ return { ok: false, message: "search requires a query" };
1526
+ let query;
1527
+ try {
1528
+ query = validateSearchQuery(rawQuery).query;
1529
+ validateQueryOptions({ filters, tags, repositoryScopes });
1530
+ } catch (error) {
1531
+ return {
1532
+ ok: false,
1533
+ message: error instanceof Error ? error.message : String(error)
1534
+ };
1535
+ }
1536
+ return {
1537
+ ok: true,
1538
+ value: {
1539
+ kind: "search",
1540
+ root,
1541
+ repository,
1542
+ ...database === undefined ? {} : { database },
1543
+ ...mode === undefined ? {} : { mode },
1544
+ ordering: priority ? "priority-then-relevance" : "relevance",
1545
+ ...rulesPath === undefined ? {} : { rulesPath },
1546
+ filters,
1547
+ tags,
1548
+ repositoryScopes,
1549
+ graph: noGraph ? false : {
1550
+ ...related.length === 0 ? {} : { related },
1551
+ ...graphDepth === undefined ? {} : { depth: graphDepth }
1552
+ },
1553
+ history: requireHistory ? "required" : history ? "auto" : false,
1554
+ ...limit === undefined ? {} : { limit },
1555
+ ...candidateLimit === undefined ? {} : { candidateLimit },
1556
+ ...minScore === undefined ? {} : { minScore },
1557
+ query,
1558
+ json
1559
+ }
1560
+ };
1561
+ }
1562
+ function parseContextCommand(arguments_) {
1563
+ let root = ".";
1564
+ let repository = ".";
1565
+ let targetKind = "auto";
1566
+ let json = false;
1567
+ const positional = [];
1568
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1569
+ const argument = arguments_[cursor];
1570
+ if (argument === undefined)
1571
+ continue;
1572
+ if (argument === "--json") {
1573
+ json = true;
1574
+ continue;
1575
+ }
1576
+ if (argument === "--root" || argument === "--repo" || argument === "--kind") {
1577
+ const value = readValue(arguments_, cursor);
1578
+ if (value === null)
1579
+ return { ok: false, message: `${argument} requires a value` };
1580
+ if (argument === "--root")
1581
+ root = value;
1582
+ else if (argument === "--repo")
1583
+ repository = value;
1584
+ else {
1585
+ if (value !== "auto" && value !== "file" && value !== "directory") {
1586
+ return { ok: false, message: "--kind must be auto, file, or directory" };
1587
+ }
1588
+ targetKind = value;
1589
+ }
1590
+ cursor += 1;
1591
+ continue;
1592
+ }
1593
+ if (argument.startsWith("--"))
1594
+ return { ok: false, message: "unknown context option" };
1595
+ positional.push(argument);
1596
+ }
1597
+ const target = positional[0];
1598
+ if (target === undefined || positional.length !== 1) {
1599
+ return { ok: false, message: "context requires exactly one repository path" };
1600
+ }
1601
+ return {
1602
+ ok: true,
1603
+ value: {
1604
+ kind: "context",
1605
+ root,
1606
+ repository,
1607
+ target,
1608
+ targetKind,
1609
+ json
1610
+ }
1611
+ };
1612
+ }
1613
+ var MAX_HISTORY_QUERY_CHARACTERS = 500;
1614
+ var MAX_HISTORY_RESULT_LIMIT = 100;
1615
+ var MAX_HISTORY_COMMIT_LIMIT = 50;
1616
+ var MAX_HISTORY_COCHANGED_LIMIT = 100;
1617
+ function parseHistoryCommand(arguments_) {
1618
+ const search = arguments_[0] === "search";
1619
+ let root = ".";
1620
+ let repository = ".";
1621
+ let limit;
1622
+ let commitLimit;
1623
+ let cochangedLimit;
1624
+ let json = false;
1625
+ const positional = [];
1626
+ for (let cursor = search ? 1 : 0;cursor < arguments_.length; cursor += 1) {
1627
+ const argument = arguments_[cursor];
1628
+ if (argument === undefined)
1629
+ continue;
1630
+ if (argument === "--json") {
1631
+ json = true;
1632
+ continue;
1633
+ }
1634
+ if (argument === "--root" || argument === "--repo" || argument === "--limit" || argument === "--commit-limit" || argument === "--cochanged-limit") {
1635
+ const value = readValue(arguments_, cursor);
1636
+ if (value === null)
1637
+ return { ok: false, message: `${argument} requires a value` };
1638
+ if (argument === "--root")
1639
+ root = value;
1640
+ else if (argument === "--repo")
1641
+ repository = value;
1642
+ else {
1643
+ if (!search && argument === "--commit-limit") {
1644
+ return {
1645
+ ok: false,
1646
+ message: "history <note> uses --limit for its per-note commit limit"
1647
+ };
1648
+ }
1649
+ const maximum = argument === "--cochanged-limit" ? MAX_HISTORY_COCHANGED_LIMIT : argument === "--commit-limit" ? MAX_HISTORY_COMMIT_LIMIT : search ? MAX_HISTORY_RESULT_LIMIT : MAX_HISTORY_COMMIT_LIMIT;
1650
+ const minimum = argument === "--cochanged-limit" ? 0 : 1;
1651
+ const parsed = Number(value);
1652
+ if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
1653
+ return {
1654
+ ok: false,
1655
+ message: `${argument} must be an integer from ${minimum} through ${maximum}`
1656
+ };
1657
+ }
1658
+ if (argument === "--cochanged-limit")
1659
+ cochangedLimit = parsed;
1660
+ else if (argument === "--commit-limit")
1661
+ commitLimit = parsed;
1662
+ else
1663
+ limit = parsed;
1664
+ }
1665
+ cursor += 1;
1666
+ continue;
1667
+ }
1668
+ if (argument.startsWith("--")) {
1669
+ return { ok: false, message: `unknown history ${search ? "search " : ""}option` };
1670
+ }
1671
+ positional.push(argument);
1672
+ }
1673
+ const query = search ? positional.join(" ").trim() : positional[0]?.trim();
1674
+ if (query === undefined || query === "" || !search && positional.length !== 1) {
1675
+ return {
1676
+ ok: false,
1677
+ message: search ? "history search requires a query or repository path" : "history requires exactly one note path, title, or alias"
1678
+ };
1679
+ }
1680
+ if (query.length > MAX_HISTORY_QUERY_CHARACTERS || /[\0\r\n]/u.test(query)) {
1681
+ return {
1682
+ ok: false,
1683
+ message: `history ${search ? "search query" : "note"} must be one to ${MAX_HISTORY_QUERY_CHARACTERS} characters on one line`
1684
+ };
1685
+ }
1686
+ return {
1687
+ ok: true,
1688
+ value: {
1689
+ kind: "history",
1690
+ action: search ? "search" : "note",
1691
+ root,
1692
+ repository,
1693
+ query,
1694
+ ...limit === undefined ? {} : { limit },
1695
+ ...commitLimit === undefined ? {} : { commitLimit },
1696
+ ...cochangedLimit === undefined ? {} : { cochangedLimit },
1697
+ json
1698
+ }
1699
+ };
1700
+ }
1701
+ function parseAgentsCommand(arguments_) {
1702
+ const action = arguments_[0];
1703
+ if (action === "identity") {
1704
+ let json2 = false;
1705
+ const positional = [];
1706
+ for (const argument of arguments_.slice(1)) {
1707
+ if (argument === "--json")
1708
+ json2 = true;
1709
+ else if (argument.startsWith("--")) {
1710
+ return { ok: false, message: "unknown agents identity option" };
1711
+ } else {
1712
+ positional.push(argument);
1713
+ }
1714
+ }
1715
+ const scope = positional[0];
1716
+ if (scope === undefined || positional.length !== 1) {
1717
+ return {
1718
+ ok: false,
1719
+ message: "agents identity requires exactly one repository scope"
1720
+ };
1721
+ }
1722
+ return {
1723
+ ok: true,
1724
+ value: { kind: "agent-identity", scope, json: json2 }
1725
+ };
1726
+ }
1727
+ if (action !== "check" && action !== "audit") {
1728
+ return { ok: false, message: "agents requires identity, check, or audit" };
1729
+ }
1730
+ let root = ".";
1731
+ let repository = ".";
1732
+ let json = false;
1733
+ for (let cursor = 1;cursor < arguments_.length; cursor += 1) {
1734
+ const argument = arguments_[cursor];
1735
+ if (argument === undefined)
1736
+ continue;
1737
+ if (argument === "--json") {
1738
+ json = true;
1739
+ continue;
1740
+ }
1741
+ if (argument === "--root" || argument === "--repo") {
1742
+ const value = readValue(arguments_, cursor);
1743
+ if (value === null)
1744
+ return { ok: false, message: `${argument} requires a value` };
1745
+ if (argument === "--root")
1746
+ root = value;
1747
+ else
1748
+ repository = value;
1749
+ cursor += 1;
1750
+ continue;
1751
+ }
1752
+ return {
1753
+ ok: false,
1754
+ message: argument.startsWith("--") ? `unknown agents ${action} option` : `agents ${action} does not accept positional arguments`
1755
+ };
1756
+ }
1757
+ return {
1758
+ ok: true,
1759
+ value: { kind: "agents", action, root, repository, json }
1760
+ };
1761
+ }
1762
+ function boundedInteger(raw, option, minimum, maximum) {
1763
+ const value = Number(raw);
1764
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
1765
+ return {
1766
+ ok: false,
1767
+ message: `${option} must be an integer from ${minimum} through ${maximum}`
1768
+ };
1769
+ }
1770
+ return value;
1771
+ }
1772
+ function parseEvaluationCommand(arguments_) {
1773
+ let root = ".";
1774
+ let repository = ".";
1775
+ let database;
1776
+ let split = "test";
1777
+ let limit = 20;
1778
+ let cutoff = 10;
1779
+ let timeoutMs = 30000;
1780
+ let baseline;
1781
+ let modelFile;
1782
+ let cacheState = "mixed";
1783
+ let json = false;
1784
+ const retrievers = [];
1785
+ const positional = [];
1786
+ const supported = new Set(knowledgeBaseEvaluationRetrieverIds);
1787
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1788
+ const argument = arguments_[cursor];
1789
+ if (argument === undefined)
1790
+ continue;
1791
+ if (argument === "--json") {
1792
+ json = true;
1793
+ continue;
1794
+ }
1795
+ if (argument === "--root" || argument === "--repo" || argument === "--database" || argument === "--retriever" || argument === "--split" || argument === "--limit" || argument === "--cutoff" || argument === "--timeout" || argument === "--baseline" || argument === "--model-file" || argument === "--cache-state") {
1796
+ const value = readValue(arguments_, cursor);
1797
+ if (value === null)
1798
+ return { ok: false, message: `${argument} requires a value` };
1799
+ if (argument === "--root")
1800
+ root = value;
1801
+ else if (argument === "--repo")
1802
+ repository = value;
1803
+ else if (argument === "--database")
1804
+ database = value;
1805
+ else if (argument === "--retriever") {
1806
+ if (!supported.has(value)) {
1807
+ return {
1808
+ ok: false,
1809
+ message: `--retriever must be one of ${knowledgeBaseEvaluationRetrieverIds.join(", ")}`
1810
+ };
1811
+ }
1812
+ retrievers.push(value);
1813
+ } else if (argument === "--split") {
1814
+ if (value !== "development" && value !== "test" && value !== "all") {
1815
+ return { ok: false, message: "--split must be development, test, or all" };
1816
+ }
1817
+ split = value;
1818
+ } else if (argument === "--limit" || argument === "--cutoff") {
1819
+ const parsed = boundedInteger(value, argument, 1, MAX_CLI_EVALUATION_RESULT_LIMIT);
1820
+ if (typeof parsed !== "number")
1821
+ return parsed;
1822
+ if (argument === "--limit")
1823
+ limit = parsed;
1824
+ else
1825
+ cutoff = parsed;
1826
+ } else if (argument === "--timeout") {
1827
+ const parsed = boundedInteger(value, argument, 1, MAX_EVALUATION_TIMEOUT_MS);
1828
+ if (typeof parsed !== "number")
1829
+ return parsed;
1830
+ timeoutMs = parsed;
1831
+ } else if (argument === "--baseline")
1832
+ baseline = value;
1833
+ else if (argument === "--model-file")
1834
+ modelFile = value;
1835
+ else {
1836
+ if (value !== "cold" && value !== "mixed" && value !== "warm") {
1837
+ return { ok: false, message: "--cache-state must be cold, mixed, or warm" };
1838
+ }
1839
+ cacheState = value;
1840
+ }
1841
+ cursor += 1;
1842
+ continue;
1843
+ }
1844
+ if (argument.startsWith("--")) {
1845
+ return { ok: false, message: "unknown evaluate option" };
1846
+ }
1847
+ positional.push(argument);
1848
+ }
1849
+ const manifest = positional[0];
1850
+ if (manifest === undefined || positional.length !== 1) {
1851
+ return { ok: false, message: "evaluate requires exactly one manifest path" };
1852
+ }
1853
+ const selected = retrievers.length === 0 ? [...knowledgeBaseEvaluationRetrieverIds] : retrievers;
1854
+ if (new Set(selected).size !== selected.length) {
1855
+ return { ok: false, message: "--retriever values must not repeat" };
1856
+ }
1857
+ const selectedBaseline = baseline ?? (selected.includes("exact") ? "exact" : selected[0]);
1858
+ if (selectedBaseline === undefined || !selected.includes(selectedBaseline)) {
1859
+ return { ok: false, message: "--baseline must name a selected retriever" };
1860
+ }
1861
+ if (cutoff > limit) {
1862
+ return { ok: false, message: "--cutoff must not exceed --limit" };
1863
+ }
1864
+ const needsModel = selected.includes("semantic") || selected.includes("hybrid");
1865
+ if (needsModel && modelFile === undefined) {
1866
+ return {
1867
+ ok: false,
1868
+ message: "semantic and hybrid evaluation require --model-file to bind the pinned model bytes"
1869
+ };
1870
+ }
1871
+ if (!needsModel && modelFile !== undefined) {
1872
+ return {
1873
+ ok: false,
1874
+ message: "--model-file is only valid when semantic or hybrid evaluation is selected"
1875
+ };
1876
+ }
1877
+ return {
1878
+ ok: true,
1879
+ value: {
1880
+ kind: "evaluate",
1881
+ manifest,
1882
+ root,
1883
+ repository,
1884
+ ...database === undefined ? {} : { database },
1885
+ retrievers: selected,
1886
+ split,
1887
+ limit,
1888
+ cutoff,
1889
+ timeoutMs,
1890
+ baseline: selectedBaseline,
1891
+ ...modelFile === undefined ? {} : { modelFile },
1892
+ cacheState: needsModel ? cacheState : "not-applicable",
1893
+ json
1894
+ }
1895
+ };
1896
+ }
1897
+ function parseNoteCommand(arguments_) {
1898
+ if (arguments_[0] !== "create") {
1899
+ return { ok: false, message: "note requires create" };
1900
+ }
1901
+ let root = ".";
1902
+ let title;
1903
+ let type = "note";
1904
+ let body;
1905
+ let bodyFile;
1906
+ let json = false;
1907
+ const tags = [];
1908
+ const positional = [];
1909
+ for (let cursor = 1;cursor < arguments_.length; cursor += 1) {
1910
+ const argument = arguments_[cursor];
1911
+ if (argument === undefined)
1912
+ continue;
1913
+ if (argument === "--json") {
1914
+ json = true;
1915
+ continue;
1916
+ }
1917
+ if (argument === "--root" || argument === "--title" || argument === "--type" || argument === "--tag" || argument === "--body" || argument === "--body-file") {
1918
+ const value = readValue(arguments_, cursor);
1919
+ if (value === null)
1920
+ return { ok: false, message: `${argument} requires a value` };
1921
+ if (argument === "--root")
1922
+ root = value;
1923
+ else if (argument === "--title")
1924
+ title = value;
1925
+ else if (argument === "--type")
1926
+ type = value;
1927
+ else if (argument === "--tag")
1928
+ tags.push(value);
1929
+ else if (argument === "--body")
1930
+ body = value;
1931
+ else
1932
+ bodyFile = value;
1933
+ cursor += 1;
1934
+ continue;
1935
+ }
1936
+ if (argument.startsWith("--")) {
1937
+ return { ok: false, message: "unknown note create option" };
1938
+ }
1939
+ positional.push(argument);
1940
+ }
1941
+ const id = positional[0];
1942
+ if (id === undefined || positional.length !== 1) {
1943
+ return { ok: false, message: "note create requires exactly one canonical note ID" };
1944
+ }
1945
+ if (title === undefined)
1946
+ return { ok: false, message: "note create requires --title" };
1947
+ if (body !== undefined && bodyFile !== undefined) {
1948
+ return { ok: false, message: "note create accepts either --body or --body-file, not both" };
1949
+ }
1950
+ return {
1951
+ ok: true,
1952
+ value: {
1953
+ kind: "note-create",
1954
+ root,
1955
+ input: { id, title, type, ...tags.length === 0 ? {} : { tags } },
1956
+ ...body === undefined ? {} : { body },
1957
+ ...bodyFile === undefined ? {} : { bodyFile },
1958
+ json
1959
+ }
1960
+ };
1961
+ }
1962
+ function isNoteRevision(value) {
1963
+ return /^sha256:[0-9a-f]{64}$/u.test(value);
1964
+ }
1965
+ function parseRelationCommand(arguments_) {
1966
+ const action = arguments_[0];
1967
+ if (action !== "add" && action !== "remove" && action !== "list") {
1968
+ return { ok: false, message: "relation requires add, remove, or list" };
1969
+ }
1970
+ let root = ".";
1971
+ let expectedRevision;
1972
+ let json = false;
1973
+ const positional = [];
1974
+ for (let cursor = 1;cursor < arguments_.length; cursor += 1) {
1975
+ const argument = arguments_[cursor];
1976
+ if (argument === undefined)
1977
+ continue;
1978
+ if (argument === "--json") {
1979
+ json = true;
1980
+ continue;
1981
+ }
1982
+ if (argument === "--root" || argument === "--expected-revision") {
1983
+ const value = readValue(arguments_, cursor);
1984
+ if (value === null)
1985
+ return { ok: false, message: `${argument} requires a value` };
1986
+ if (argument === "--root") {
1987
+ root = value;
1988
+ } else {
1989
+ if (!isNoteRevision(value)) {
1990
+ return {
1991
+ ok: false,
1992
+ message: "--expected-revision must be sha256 followed by 64 lowercase hexadecimal characters"
1993
+ };
1994
+ }
1995
+ expectedRevision = value;
1996
+ }
1997
+ cursor += 1;
1998
+ continue;
1999
+ }
2000
+ if (argument.startsWith("--")) {
2001
+ return { ok: false, message: `unknown relation ${action} option` };
2002
+ }
2003
+ positional.push(argument);
2004
+ }
2005
+ const source = positional[0];
2006
+ if (action === "list") {
2007
+ if (source === undefined || positional.length !== 1) {
2008
+ return { ok: false, message: "relation list requires exactly one canonical note ID" };
2009
+ }
2010
+ if (expectedRevision !== undefined) {
2011
+ return { ok: false, message: "relation list does not accept --expected-revision" };
2012
+ }
2013
+ return { ok: true, value: { kind: "relation", action, root, source, json } };
2014
+ }
2015
+ const predicate = positional[1];
2016
+ const target = positional[2];
2017
+ if (source === undefined || predicate === undefined || target === undefined || positional.length !== 3) {
2018
+ return {
2019
+ ok: false,
2020
+ message: `relation ${action} requires exact source, predicate, and target IDs`
2021
+ };
2022
+ }
2023
+ return {
2024
+ ok: true,
2025
+ value: {
2026
+ kind: "relation",
2027
+ action,
2028
+ root,
2029
+ source,
2030
+ predicate,
2031
+ target,
2032
+ ...expectedRevision === undefined ? {} : { expectedRevision },
2033
+ json
2034
+ }
2035
+ };
2036
+ }
2037
+ function parsePercolateCommand(arguments_) {
2038
+ let root = ".";
2039
+ let minSupport = 2;
2040
+ let limit = 25;
2041
+ let json = false;
2042
+ const positional = [];
2043
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
2044
+ const argument = arguments_[cursor];
2045
+ if (argument === undefined)
2046
+ continue;
2047
+ if (argument === "--json") {
2048
+ json = true;
2049
+ continue;
2050
+ }
2051
+ if (argument === "--root" || argument === "--min-support" || argument === "--limit") {
2052
+ const value = readValue(arguments_, cursor);
2053
+ if (value === null)
2054
+ return { ok: false, message: `${argument} requires a value` };
2055
+ if (argument === "--root") {
2056
+ root = value;
2057
+ } else {
2058
+ const parsed = boundedInteger(value, argument, argument === "--min-support" ? 2 : 1, 1000);
2059
+ if (typeof parsed !== "number")
2060
+ return parsed;
2061
+ if (argument === "--min-support")
2062
+ minSupport = parsed;
2063
+ else
2064
+ limit = parsed;
2065
+ }
2066
+ cursor += 1;
2067
+ continue;
2068
+ }
2069
+ if (argument.startsWith("--")) {
2070
+ return { ok: false, message: "unknown percolate option" };
2071
+ }
2072
+ positional.push(argument);
2073
+ }
2074
+ const note = positional[0];
2075
+ if (positional.length > 1) {
2076
+ return { ok: false, message: "percolate accepts at most one note ID" };
2077
+ }
2078
+ return {
2079
+ ok: true,
2080
+ value: {
2081
+ kind: "percolate",
2082
+ root,
2083
+ ...note === undefined ? {} : { note },
2084
+ minSupport,
2085
+ limit,
2086
+ json
2087
+ }
2088
+ };
2089
+ }
2090
+ function parsePortfolioCommand(arguments_) {
2091
+ const action = arguments_[0];
2092
+ if (action !== "search" && action !== "audit") {
2093
+ return { ok: false, message: "portfolio action must be search or audit" };
2094
+ }
2095
+ let registryPath;
2096
+ let workspaceRoot;
2097
+ let shared = false;
2098
+ let all = false;
2099
+ let requireAll = false;
2100
+ let strict = false;
2101
+ let json = false;
2102
+ let mode;
2103
+ let priority = false;
2104
+ let rulesPath;
2105
+ let limit;
2106
+ const vaults = [];
2107
+ const positional = [];
2108
+ for (let cursor = 1;cursor < arguments_.length; cursor += 1) {
2109
+ const argument = arguments_[cursor];
2110
+ if (argument === undefined)
2111
+ continue;
2112
+ if (argument === "--json")
2113
+ json = true;
2114
+ else if (argument === "--shared")
2115
+ shared = true;
2116
+ else if (argument === "--all" && action === "audit")
2117
+ all = true;
2118
+ else if (argument === "--require-all" && action === "search")
2119
+ requireAll = true;
2120
+ else if (argument === "--priority" && action === "search")
2121
+ priority = true;
2122
+ else if (argument === "--strict" && action === "audit")
2123
+ strict = true;
2124
+ else if (argument === "--registry" || argument === "--workspace" || argument === "--vault" || argument === "--rules" && action === "search" || argument === "--mode" && action === "search" || argument === "--limit" && action === "search") {
2125
+ const value = readValue(arguments_, cursor);
2126
+ if (value === null)
2127
+ return { ok: false, message: `${argument} requires a value` };
2128
+ cursor += 1;
2129
+ if (argument === "--registry")
2130
+ registryPath = value;
2131
+ else if (argument === "--workspace")
2132
+ workspaceRoot = value;
2133
+ else if (argument === "--rules")
2134
+ rulesPath = value;
2135
+ else if (argument === "--vault") {
2136
+ try {
2137
+ vaults.push(parseVaultKey(value).key);
2138
+ } catch {
2139
+ return { ok: false, message: "--vault must be a canonical owner/id key" };
2140
+ }
2141
+ } else if (argument === "--mode") {
2142
+ if (value !== "exact" && value !== "hybrid" && value !== "keyword" && value !== "semantic") {
2143
+ return { ok: false, message: "--mode must be exact, hybrid, keyword, or semantic" };
2144
+ }
2145
+ mode = value;
2146
+ } else {
2147
+ const checked = boundedInteger(value, "--limit", 1, MAX_SEARCH_RESULTS);
2148
+ if (typeof checked !== "number")
2149
+ return checked;
2150
+ limit = checked;
2151
+ }
2152
+ } else if (argument.startsWith("--")) {
2153
+ return { ok: false, message: `unknown portfolio ${action} option` };
2154
+ } else
2155
+ positional.push(argument);
2156
+ }
2157
+ if (registryPath === undefined || workspaceRoot === undefined) {
2158
+ return { ok: false, message: `portfolio ${action} requires --registry and --workspace` };
2159
+ }
2160
+ if (new Set(vaults).size !== vaults.length) {
2161
+ return { ok: false, message: "portfolio vault selections must be unique" };
2162
+ }
2163
+ const selectionCount = Number(shared) + Number(all) + Number(vaults.length > 0);
2164
+ if (selectionCount !== 1) {
2165
+ return {
2166
+ ok: false,
2167
+ message: action === "search" ? "portfolio search requires exactly one of --shared or repeated --vault" : "portfolio audit requires exactly one of --all, --shared, or repeated --vault"
2168
+ };
2169
+ }
2170
+ if (action === "search") {
2171
+ if (priority && rulesPath === undefined) {
2172
+ return { ok: false, message: "--priority requires --rules" };
2173
+ }
2174
+ const rawQuery = positional.join(" ");
2175
+ if (rawQuery.trim() === "")
2176
+ return { ok: false, message: "portfolio search requires a query" };
2177
+ let query;
2178
+ try {
2179
+ query = validateSearchQuery(rawQuery).query;
2180
+ } catch (error) {
2181
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
2182
+ }
2183
+ return {
2184
+ ok: true,
2185
+ value: {
2186
+ kind: "portfolio-search",
2187
+ registryPath,
2188
+ workspaceRoot,
2189
+ selection: shared ? "shared" : "explicit",
2190
+ vaults,
2191
+ failurePolicy: requireAll ? "required" : "partial",
2192
+ ...mode === undefined ? {} : { mode },
2193
+ ordering: priority ? "priority-then-relevance" : "relevance",
2194
+ ...rulesPath === undefined ? {} : { rulesPath },
2195
+ ...limit === undefined ? {} : { limit },
2196
+ query,
2197
+ json
2198
+ }
2199
+ };
2200
+ }
2201
+ if (positional.length > 0)
2202
+ return { ok: false, message: "portfolio audit does not accept positional arguments" };
2203
+ return {
2204
+ ok: true,
2205
+ value: {
2206
+ kind: "portfolio-audit",
2207
+ registryPath,
2208
+ workspaceRoot,
2209
+ selection: all ? "all" : shared ? "shared" : "explicit",
2210
+ vaults,
2211
+ strict,
2212
+ json
2213
+ }
2214
+ };
2215
+ }
2216
+ function parseArguments(arguments_) {
2217
+ const command = arguments_[0];
2218
+ if (command === undefined || command === "help" || command === "--help" || command === "-h") {
2219
+ return { ok: true, value: { kind: "help" } };
2220
+ }
2221
+ if (command === "capture" && arguments_[1] === "diff") {
2222
+ return parseCaptureDiffCommand(arguments_.slice(2));
2223
+ }
2224
+ if (command === "capture" && (arguments_[1] === "show" || arguments_[1] === "verify")) {
2225
+ return parseCaptureBundleCommand(arguments_.slice(1));
2226
+ }
2227
+ if (command === "clip" || command === "capture" || command === "inspect") {
2228
+ if (arguments_[1] === "--help" || arguments_[1] === "-h" || arguments_[1] === "help") {
2229
+ return { ok: true, value: { kind: "clip", arguments: ["help"] } };
2230
+ }
2231
+ const delegated = command === "inspect" ? "inspect" : "capture";
2232
+ return { ok: true, value: { kind: "clip", arguments: [delegated, ...arguments_.slice(1)] } };
2233
+ }
2234
+ if (command === "url-metadata") {
2235
+ return { ok: true, value: { kind: "url-metadata", arguments: arguments_.slice(1) } };
2236
+ }
2237
+ if (command === "pdf") {
2238
+ return { ok: true, value: { kind: "pdf", arguments: arguments_.slice(1) } };
2239
+ }
2240
+ if (command === "doctor" || command === "adapters") {
2241
+ return { ok: true, value: { kind: "clip", arguments: arguments_ } };
2242
+ }
2243
+ if (command === "init") {
2244
+ let directory = "kb";
2245
+ let json = false;
2246
+ const positional = [];
2247
+ for (const argument of arguments_.slice(1)) {
2248
+ if (argument === "--json")
2249
+ json = true;
2250
+ else if (argument.startsWith("--"))
2251
+ return { ok: false, message: "unknown init option" };
2252
+ else
2253
+ positional.push(argument);
2254
+ }
2255
+ if (positional.length > 1)
2256
+ return { ok: false, message: "init accepts at most one directory" };
2257
+ if (positional[0] !== undefined)
2258
+ directory = positional[0];
2259
+ return { ok: true, value: { kind: "init", directory, json } };
2260
+ }
2261
+ if (command === "refresh" || command === "check" || command === "graph" || command === "backlinks" || command === "links") {
2262
+ return parseVaultCommand(command, arguments_.slice(1));
2263
+ }
2264
+ if (command === "catalog")
2265
+ return parseCatalogCommand(arguments_.slice(1));
2266
+ if (command === "list" || command === "notes")
2267
+ return parseListCommand(arguments_.slice(1));
2268
+ if (command === "inbox" || command === "source-inbox") {
2269
+ return parseInboxCommand(arguments_.slice(1));
2270
+ }
2271
+ if (command === "index" || command === "search") {
2272
+ return parseSemanticCommand(command, arguments_.slice(1));
2273
+ }
2274
+ if (command === "history")
2275
+ return parseHistoryCommand(arguments_.slice(1));
2276
+ if (command === "portfolio")
2277
+ return parsePortfolioCommand(arguments_.slice(1));
2278
+ if (command === "evaluate")
2279
+ return parseEvaluationCommand(arguments_.slice(1));
2280
+ if (command === "context")
2281
+ return parseContextCommand(arguments_.slice(1));
2282
+ if (command === "agents")
2283
+ return parseAgentsCommand(arguments_.slice(1));
2284
+ if (command === "note")
2285
+ return parseNoteCommand(arguments_.slice(1));
2286
+ if (command === "relation")
2287
+ return parseRelationCommand(arguments_.slice(1));
2288
+ if (command === "percolate")
2289
+ return parsePercolateCommand(arguments_.slice(1));
2290
+ return { ok: false, message: "unknown command" };
2291
+ }
2292
+ function embeddingCount(result) {
2293
+ return result.embedding?.chunksEmbedded ?? 0;
2294
+ }
2295
+ function renderSemanticIndex(result) {
2296
+ const changed = result.update.indexed + result.update.updated;
2297
+ return [
2298
+ `Indexed ${safe(result.root)} with QMD.`,
2299
+ `Documents: ${changed} changed, ${result.update.unchanged} unchanged, ${result.update.removed} removed.`,
2300
+ `Embeddings: ${embeddingCount(result)} chunks; model: ${safe(result.model)}.`,
2301
+ `Database: ${safe(result.database)}`,
2302
+ ""
2303
+ ].join(`
2304
+ `);
2305
+ }
2306
+ function renderKnowledgeBaseSearch(result) {
2307
+ const lines = [
2308
+ `${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for \u201C${safe(result.query)}\u201D (${result.results.length})${result.partial ? " [partial]" : ""}`
2309
+ ];
2310
+ if (result.results.length === 0)
2311
+ lines.push(" None.");
2312
+ for (const hit of result.results) {
2313
+ const location = `${safe(hit.path)}${hit.line === undefined ? "" : `:${hit.line}`}`;
2314
+ const evidence = hit.evidence.map((item) => `${item.kind}#${item.rank}`).join(", ");
2315
+ lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} \u2014 ${safe(hit.title)} [${safe(evidence)}]`);
2316
+ if (hit.snippet !== "")
2317
+ lines.push(` ${safe(hit.snippet)}`);
2318
+ }
2319
+ if ((result.graph?.related.length ?? 0) > 0) {
2320
+ lines.push(` Related graph context: ${result.graph?.related.length ?? 0}`);
2321
+ }
2322
+ if (result.history?.status === "ready") {
2323
+ const limited = result.history.limitedCommits?.length ?? 0;
2324
+ lines.push(` Git provenance: ${result.history.notes.length} notes at ${safe(result.history.head.slice(0, 12))}` + (limited === 0 ? "" : `; ${limited} commit${limited === 1 ? "" : "s"} with incomplete co-change paths`));
2325
+ }
2326
+ return `${lines.join(`
2327
+ `)}
2328
+ `;
2329
+ }
2330
+ async function runSemantic(command, output, dependencies) {
2331
+ if (command.kind === "index") {
2332
+ const result = await (dependencies.indexSemanticVault ?? indexSemanticVault)({
2333
+ root: command.root,
2334
+ ...command.database === undefined ? {} : { database: command.database },
2335
+ force: command.force
2336
+ });
2337
+ output.stdout(command.json ? terminalSafeJson(result) : sanitizeTerminalText(renderSemanticIndex(result)));
2338
+ return 0;
2339
+ }
2340
+ const searchRules = command.rulesPath === undefined ? undefined : await loadSearchRulesFile(command.rulesPath);
2341
+ const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)({
2342
+ root: command.root,
2343
+ repository: command.repository,
2344
+ ...command.database === undefined ? {} : { database: command.database },
2345
+ ...searchRules === undefined ? {} : { searchRules }
2346
+ });
2347
+ try {
2348
+ const result = await kb.search({
2349
+ query: command.query,
2350
+ ...command.mode === undefined ? {} : { mode: command.mode },
2351
+ ordering: command.ordering,
2352
+ filters: command.filters,
2353
+ tags: command.tags,
2354
+ repositoryScopes: command.repositoryScopes,
2355
+ graph: command.graph,
2356
+ history: command.history,
2357
+ ...command.limit === undefined ? {} : { limit: command.limit },
2358
+ ...command.candidateLimit === undefined ? {} : { candidateLimit: command.candidateLimit },
2359
+ ...command.minScore === undefined ? {} : { minScore: command.minScore }
2360
+ });
2361
+ output.stdout(command.json ? terminalSafeJson(result) : sanitizeTerminalText(renderKnowledgeBaseSearch(result)));
2362
+ return 0;
2363
+ } finally {
2364
+ await kb.close();
2365
+ }
2366
+ }
2367
+ function historyIsPartial(result) {
2368
+ return result.status === "unavailable" || (result.limitedCommits?.length ?? 0) > 0;
2369
+ }
2370
+ function renderHistoryAvailability(result) {
2371
+ if (result.status === "unavailable") {
2372
+ return [`Git history unavailable: ${safe(result.reason)}`];
2373
+ }
2374
+ const limited = result.limitedCommits?.length ?? 0;
2375
+ return limited === 0 ? [] : [
2376
+ `Coverage: ${limited} oversized commit${limited === 1 ? "" : "s"} ` + "have incomplete co-change paths."
2377
+ ];
2378
+ }
2379
+ function renderNoteHistory(note, result) {
2380
+ const lines = [`Git history for ${safe(note.path)} \u2014 ${safe(note.title)}`];
2381
+ lines.push(...renderHistoryAvailability(result));
2382
+ if (result.status === "unavailable")
2383
+ return `${lines.join(`
2384
+ `)}
2385
+ `;
2386
+ const provenance = result.notes.find(({ id }) => id === note.id);
2387
+ const commits = provenance?.commits ?? [];
2388
+ lines.push(`Head: ${safe(result.head)}; commits: ${commits.length}.`);
2389
+ if (commits.length === 0)
2390
+ lines.push(" No indexed commits.");
2391
+ for (const commit of commits) {
2392
+ lines.push(` ${safe(commit.hash.slice(0, 12))} ${safe(commit.committedAt)} ${safe(commit.subject)}`);
2393
+ for (const path of commit.cochangedPaths)
2394
+ lines.push(` ${safe(path)}`);
2395
+ if (commit.cochangeDetailsLimited === true) {
2396
+ lines.push(" Co-change paths are incomplete for this oversized commit.");
2397
+ }
2398
+ }
2399
+ return `${lines.join(`
2400
+ `)}
2401
+ `;
2402
+ }
2403
+ function renderHistorySearch(result) {
2404
+ const lines = [
2405
+ result.status === "ready" ? `Git history results for \u201C${safe(result.query)}\u201D (${result.hits.length})` : "Git history search",
2406
+ ...renderHistoryAvailability(result)
2407
+ ];
2408
+ if (result.status === "unavailable")
2409
+ return `${lines.join(`
2410
+ `)}
2411
+ `;
2412
+ if (result.hits.length === 0)
2413
+ lines.push(" None.");
2414
+ for (const hit of result.hits) {
2415
+ lines.push(` ${hit.score.toFixed(3)} ${safe(hit.path)}`);
2416
+ for (const commit of hit.commits) {
2417
+ const matches = commit.matchedPaths.length === 0 ? "" : ` [${commit.matchedPaths.map(safe).join(", ")}]`;
2418
+ lines.push(` ${safe(commit.hash.slice(0, 12))} ${safe(commit.subject)}${matches}`);
2419
+ }
2420
+ }
2421
+ return `${lines.join(`
2422
+ `)}
2423
+ `;
2424
+ }
2425
+ var MAX_EVALUATION_MANIFEST_BYTES = 16 * 1024 * 1024;
2426
+ var MAX_CLI_EVALUATION_RESULT_LIMIT = Math.min(100, MAX_EVALUATION_RESULTS_PER_QUERY);
2427
+ var MAX_CLI_EVALUATION_QUERIES = 500;
2428
+ var MAX_CLI_EVALUATION_RUNS = 4000;
2429
+ function evaluationEnvironment(command, modelSha256, now) {
2430
+ const [modelId, modelRevision] = recommendedEmbeddingModel.split("#", 2);
2431
+ const processor = cpus()[0]?.model.trim() || "unknown processor";
2432
+ const hardware = `${processor}; ${cpus().length} logical CPUs; ` + `${(totalmem() / 1024 ** 3).toFixed(1)} GiB memory`;
2433
+ return {
2434
+ generatedAt: now().toISOString(),
2435
+ runtime: {
2436
+ bun: Bun.version,
2437
+ node: process.versions.node,
2438
+ os: `${process.platform} ${release()}`,
2439
+ arch: process.arch,
2440
+ hardware
2441
+ },
2442
+ model: modelSha256 === null ? {
2443
+ kind: "none",
2444
+ reason: "The selected retrievers do not use local vector embeddings."
2445
+ } : {
2446
+ kind: "local",
2447
+ id: modelId ?? recommendedEmbeddingModel,
2448
+ revision: modelRevision ?? "unversioned",
2449
+ sha256: modelSha256
2450
+ },
2451
+ cache: { state: command.cacheState },
2452
+ retrievers: command.retrievers.map((id) => ({
2453
+ id,
2454
+ version: id === "keyword" || id === "semantic" ? `qmd-${qmdIndexerVersion}/${id}` : id === "hybrid" ? `kb-rrf-v1+qmd-${qmdIndexerVersion}` : `kb-${id}-v1`,
2455
+ configuration: {
2456
+ resultLimit: command.limit,
2457
+ cutoff: command.cutoff,
2458
+ split: command.split
2459
+ }
2460
+ }))
2461
+ };
2462
+ }
2463
+ function metricText(value) {
2464
+ return value === null ? "n/a" : value.toFixed(4);
2465
+ }
2466
+ function renderEvaluationReport(report) {
2467
+ const lines = [
2468
+ `Retrieval evaluation ${safe(report.corpus.id)}: ${report.queryCount} ${safe(report.split)} queries at cutoff ${report.cutoff}.`,
2469
+ `Frozen repository: ${safe(report.corpus.frozen.repositoryCommit)}; vault tree: ${safe(report.corpus.frozen.vaultTree)}.`
2470
+ ];
2471
+ for (const summary of report.summaries) {
2472
+ lines.push(` ${safe(summary.retrieverId)}: ready ${summary.ready}, degraded ${summary.degraded}, unavailable ${summary.unavailable}, failed ${summary.failed}; ` + `recall ${metricText(summary.metrics.recall)}, MRR ${metricText(summary.metrics.reciprocalRank)}, ` + `nDCG ${metricText(summary.metrics.ndcg)}, no-answer ${metricText(summary.metrics.noAnswerAccuracy)}, ` + `p95 ${summary.latencyMs.p95?.toFixed(2) ?? "n/a"} ms.`);
2473
+ }
2474
+ lines.push("These measurements describe only the frozen corpus, selected retrievers, cache state, and recorded machine.");
2475
+ return `${lines.join(`
2476
+ `)}
2477
+ `;
2478
+ }
2479
+ async function runEvaluation(command, output, dependencies) {
2480
+ const source = await readBoundedUtf8(command.manifest, MAX_EVALUATION_MANIFEST_BYTES, "evaluation manifest");
2481
+ let parsed;
2482
+ try {
2483
+ parsed = JSON.parse(source);
2484
+ } catch (error) {
2485
+ throw new TypeError("The evaluation manifest must contain valid JSON.", { cause: error });
2486
+ }
2487
+ const corpus = parseRetrievalEvaluationCorpus(parsed);
2488
+ const queryCount = corpus.queries.filter(({ split }) => command.split === "all" || split === command.split).length;
2489
+ if (queryCount > MAX_CLI_EVALUATION_QUERIES) {
2490
+ throw new RangeError(`CLI evaluation accepts at most ${MAX_CLI_EVALUATION_QUERIES} selected queries.`);
2491
+ }
2492
+ if (queryCount * command.retrievers.length > MAX_CLI_EVALUATION_RUNS) {
2493
+ throw new RangeError(`CLI evaluation accepts at most ${MAX_CLI_EVALUATION_RUNS} retriever/query runs.`);
2494
+ }
2495
+ const embeddingModelFile = command.modelFile === undefined ? undefined : resolve3(command.modelFile);
2496
+ const digestEvaluationModel = dependencies.digestEvaluationModel ?? sha256EmbeddingModelFile;
2497
+ const modelSha256 = embeddingModelFile === undefined ? null : await digestEvaluationModel(embeddingModelFile);
2498
+ if (modelSha256 !== null && modelSha256 !== recommendedEmbeddingModelSha256) {
2499
+ throw new Error("The evaluation model does not match the pinned recommended model SHA-256.");
2500
+ }
2501
+ const evaluation = await (dependencies.openKnowledgeBaseEvaluation ?? openKnowledgeBaseEvaluation)({
2502
+ corpus,
2503
+ root: command.root,
2504
+ repository: command.repository,
2505
+ ...command.database === undefined ? {} : { database: command.database },
2506
+ ...embeddingModelFile === undefined ? {} : { embeddingModelFile }
2507
+ });
2508
+ try {
2509
+ const byId = new Map(evaluation.retrievers.map((retriever) => [retriever.id, retriever]));
2510
+ const retrievers = command.retrievers.map((id) => {
2511
+ const retriever = byId.get(id);
2512
+ if (retriever === undefined)
2513
+ throw new Error(`Evaluation adapter ${id} is unavailable.`);
2514
+ return retriever;
2515
+ });
2516
+ const runs = await runRetrievalEvaluation({
2517
+ corpus,
2518
+ retrievers,
2519
+ split: command.split,
2520
+ limit: command.limit,
2521
+ timeoutMs: command.timeoutMs
2522
+ });
2523
+ if (embeddingModelFile !== undefined) {
2524
+ const afterSha256 = await digestEvaluationModel(embeddingModelFile);
2525
+ if (afterSha256 !== modelSha256) {
2526
+ throw new Error("The evaluation model changed while retrieval was running; retry.");
2527
+ }
2528
+ }
2529
+ const report = buildRetrievalEvaluationReport({
2530
+ corpus,
2531
+ runs,
2532
+ environment: evaluationEnvironment(command, modelSha256, dependencies.evaluationNow ?? (() => new Date)),
2533
+ cutoff: command.cutoff,
2534
+ baselineRetrieverId: command.baseline,
2535
+ bootstrapSeed: 1,
2536
+ bootstrapResamples: 1e4
2537
+ });
2538
+ output.stdout(command.json ? terminalSafeJson(report) : sanitizeTerminalText(renderEvaluationReport(report)));
2539
+ return 0;
2540
+ } finally {
2541
+ await evaluation.close();
2542
+ }
2543
+ }
2544
+ async function runHistory(command, output, dependencies) {
2545
+ const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)({
2546
+ root: command.root,
2547
+ repository: command.repository
2548
+ });
2549
+ try {
2550
+ if (command.action === "note") {
2551
+ const note = kb.read(command.query, { maxBytes: 1 });
2552
+ const history2 = await kb.history([note.id], {
2553
+ ...command.limit === undefined ? {} : { commitsPerNote: command.limit },
2554
+ ...command.cochangedLimit === undefined ? {} : { cochangedPathsPerCommit: command.cochangedLimit }
2555
+ });
2556
+ const payload = {
2557
+ kind: "note",
2558
+ note: { id: note.id, path: note.path, title: note.title },
2559
+ history: history2,
2560
+ partial: historyIsPartial(history2)
2561
+ };
2562
+ output.stdout(command.json ? terminalSafeJson(payload) : sanitizeTerminalText(renderNoteHistory(payload.note, history2)));
2563
+ return 0;
2564
+ }
2565
+ const history = await kb.searchHistory({
2566
+ query: command.query,
2567
+ ...command.limit === undefined ? {} : { limit: command.limit },
2568
+ ...command.commitLimit === undefined ? {} : { commitsPerHit: command.commitLimit },
2569
+ ...command.cochangedLimit === undefined ? {} : { cochangedPathsPerCommit: command.cochangedLimit }
2570
+ });
2571
+ output.stdout(command.json ? terminalSafeJson({
2572
+ kind: "search",
2573
+ history,
2574
+ partial: historyIsPartial(history)
2575
+ }) : sanitizeTerminalText(renderHistorySearch(history)));
2576
+ return 0;
2577
+ } finally {
2578
+ await kb.close();
2579
+ }
2580
+ }
2581
+ function issueJson(issue) {
2582
+ return issue.kind === "broken" ? { kind: issue.kind, source: issue.source, line: issue.line, target: issue.target } : {
2583
+ kind: issue.kind,
2584
+ source: issue.source,
2585
+ line: issue.line,
2586
+ target: issue.target,
2587
+ candidates: issue.candidates
2588
+ };
2589
+ }
2590
+ function relationIssueJson(issue) {
2591
+ return { ...issue };
2592
+ }
2593
+ function summary(snapshot, options = {}) {
2594
+ return {
2595
+ root: snapshot.root,
2596
+ indexPath: snapshot.indexPath,
2597
+ index: snapshot.index,
2598
+ catalogRequired: options.noCatalog !== true,
2599
+ noteCount: snapshot.analysis.noteCount,
2600
+ contextualLinkCount: snapshot.analysis.contextualLinks.length,
2601
+ backlinkCount: snapshot.analysis.backlinks.length,
2602
+ authoredRelationCount: snapshot.analysis.authoredRelations.length,
2603
+ issues: snapshot.analysis.issues.map(issueJson),
2604
+ relationIssues: snapshot.analysis.relationIssues.map(relationIssueJson),
2605
+ orphans: snapshot.analysis.orphans,
2606
+ mentions: snapshot.analysis.mentions,
2607
+ ...options.attachments === undefined ? {} : {
2608
+ attachments: {
2609
+ referenceCount: options.attachments.references.length,
2610
+ validatedCount: options.attachments.attachments.length,
2611
+ truncated: options.attachments.truncated,
2612
+ issues: options.attachments.issues
2613
+ }
2614
+ }
2615
+ };
2616
+ }
2617
+ function renderIssue(issue) {
2618
+ if (issue.kind === "broken") {
2619
+ return `${safe(issue.source)}:${issue.line}: broken wikilink [[${safe(issue.target)}]]`;
2620
+ }
2621
+ return `${safe(issue.source)}:${issue.line}: ambiguous wikilink [[${safe(issue.target)}]] (${issue.candidates.map(safe).join(", ")})`;
2622
+ }
2623
+ function renderRelationIssue(issue) {
2624
+ if (issue.kind === "malformed") {
2625
+ return `${safe(issue.source)}:${issue.line}: malformed relationship${issue.predicate === undefined ? "" : ` ${safe(issue.predicate)}`}: ${safe(issue.message)}`;
2626
+ }
2627
+ if (issue.kind === "broken") {
2628
+ return `${safe(issue.source)}:${issue.line}: broken relationship ${safe(issue.predicate)} \u2192 ${safe(issue.target)}`;
2629
+ }
2630
+ return `${safe(issue.source)}:${issue.line}: ambiguous relationship ${safe(issue.predicate)} \u2192 ${safe(issue.target)} (${issue.candidates.map(safe).join(", ")})`;
2631
+ }
2632
+ function renderAdvisories(analysis) {
2633
+ const lines = [];
2634
+ if (analysis.orphans.length > 0) {
2635
+ lines.push(`Advisory: ${analysis.orphans.length} contextual orphan${analysis.orphans.length === 1 ? "" : "s"}.`);
2636
+ for (const orphan of analysis.orphans)
2637
+ lines.push(` ${safe(orphan)}`);
2638
+ }
2639
+ if (analysis.mentions.length > 0) {
2640
+ lines.push(`Advisory: ${analysis.mentions.length} exact unlinked title or alias mention${analysis.mentions.length === 1 ? "" : "s"}.`);
2641
+ for (const mention of analysis.mentions) {
2642
+ lines.push(` ${safe(mention.source)}:${mention.line} mentions \u201C${safe(mention.phrase)}\u201D (${safe(mention.target)})`);
2643
+ }
2644
+ }
2645
+ return lines;
2646
+ }
2647
+ function checkExitCode(snapshot, noCatalog = false, attachments) {
2648
+ return !noCatalog && snapshot.index === "stale" || snapshot.analysis.issues.length > 0 || snapshot.analysis.relationIssues.length > 0 || (attachments?.issues.length ?? 0) > 0 || attachments?.truncated === true ? 3 : 0;
2649
+ }
2650
+ function renderAttachmentIssue(issue) {
2651
+ const candidates = issue.candidates === undefined ? "" : ` (${issue.candidates.map(safe).join(", ")})`;
2652
+ return `${safe(issue.source)}:${issue.line}: ${safe(issue.kind)} attachment ${safe(issue.target)}: ${safe(issue.message)}${candidates}`;
2653
+ }
2654
+ function renderSnapshot(command, snapshot, noCatalog = false, attachments) {
2655
+ const lines = [
2656
+ `${command === "refresh" ? "Refreshed" : "Checked"} ${safe(snapshot.root)}`,
2657
+ `Index: ${noCatalog ? `not required (${snapshot.index})` : snapshot.index}; notes: ${snapshot.analysis.noteCount}; contextual links: ${snapshot.analysis.contextualLinks.length}; typed relationships: ${snapshot.analysis.authoredRelations.length}.`
2658
+ ];
2659
+ if (!noCatalog && snapshot.index === "stale") {
2660
+ lines.push(`error: generated catalog is stale (${safe(snapshot.indexPath)})`);
2661
+ }
2662
+ for (const issue of snapshot.analysis.issues)
2663
+ lines.push(`error: ${renderIssue(issue)}`);
2664
+ for (const issue of snapshot.analysis.relationIssues) {
2665
+ lines.push(`error: ${renderRelationIssue(issue)}`);
2666
+ }
2667
+ for (const issue of attachments?.issues ?? []) {
2668
+ lines.push(`error: ${renderAttachmentIssue(issue)}`);
2669
+ }
2670
+ if (attachments?.truncated === true && attachments.issues.every(({ kind }) => kind !== "budget")) {
2671
+ lines.push("error: attachment validation was truncated by a resource limit");
2672
+ }
2673
+ lines.push(...renderAdvisories(snapshot.analysis));
2674
+ return `${lines.join(`
2675
+ `)}
2676
+ `;
2677
+ }
2678
+ function graphJson(snapshot) {
2679
+ return { ...summary(snapshot), notes: snapshot.analysis.noteConnections };
2680
+ }
2681
+ function renderGraph(snapshot) {
2682
+ const lines = [
2683
+ `Graph: ${snapshot.analysis.noteCount} notes; ${snapshot.analysis.contextualLinks.length} contextual links; ${snapshot.analysis.authoredRelations.length} typed relationships.`
2684
+ ];
2685
+ for (const note of snapshot.analysis.noteConnections) {
2686
+ lines.push(`${safe(note.path)} \u2190 ${note.inboundContextualCount} \u2192 ${note.outboundContextualCount}`);
2687
+ }
2688
+ if (snapshot.analysis.contextualLinks.length > 0) {
2689
+ lines.push("Contextual edges:");
2690
+ for (const link of snapshot.analysis.contextualLinks) {
2691
+ lines.push(` ${safe(link.source)}:${link.line} \u2192 ${safe(link.target)}`);
2692
+ }
2693
+ }
2694
+ if (snapshot.analysis.authoredRelations.length > 0) {
2695
+ lines.push("Typed relationships:");
2696
+ for (const relation of snapshot.analysis.authoredRelations) {
2697
+ lines.push(` ${safe(relation.source)}:${relation.provenance.line} ${safe(relation.predicate)} \u2192 ${safe(relation.target)}`);
2698
+ }
2699
+ }
2700
+ for (const issue of snapshot.analysis.issues)
2701
+ lines.push(`error: ${renderIssue(issue)}`);
2702
+ for (const issue of snapshot.analysis.relationIssues) {
2703
+ lines.push(`error: ${renderRelationIssue(issue)}`);
2704
+ }
2705
+ lines.push(...renderAdvisories(snapshot.analysis));
2706
+ return `${lines.join(`
2707
+ `)}
2708
+ `;
2709
+ }
2710
+ function backlinkPayload(notePath, backlinks, relationships) {
2711
+ return {
2712
+ note: notePath,
2713
+ count: backlinks.length + relationships.length,
2714
+ backlinkCount: backlinks.length,
2715
+ relationshipCount: relationships.length,
2716
+ backlinks,
2717
+ relationships
2718
+ };
2719
+ }
2720
+ function renderBacklinks(notePath, backlinks, relationships) {
2721
+ const lines = [
2722
+ `Backlinks to ${safe(notePath)} (${backlinks.length} links, ${relationships.length} typed relationships)`
2723
+ ];
2724
+ if (backlinks.length === 0 && relationships.length === 0)
2725
+ lines.push(" None.");
2726
+ else
2727
+ for (const backlink of backlinks)
2728
+ lines.push(` ${safe(backlink.source)}:${backlink.line}`);
2729
+ for (const relation of relationships) {
2730
+ lines.push(` ${safe(relation.source)}:${relation.provenance.line} ${safe(relation.predicate)} \u2192 ${safe(relation.target)}`);
2731
+ }
2732
+ return `${lines.join(`
2733
+ `)}
2734
+ `;
2735
+ }
2736
+ function renderLinks(neighborhood) {
2737
+ const lines = [
2738
+ `Links around ${safe(neighborhood.note)} (${neighborhood.direction}, depth ${neighborhood.depth}, limit ${neighborhood.limit})`
2739
+ ];
2740
+ for (const node of neighborhood.nodes) {
2741
+ lines.push(` ${node.distance} ${safe(node.path)} \u2014 ${safe(node.title)} \u2190 ${node.inboundContextualCount} \u2192 ${node.outboundContextualCount}`);
2742
+ }
2743
+ if (neighborhood.edges.length > 0) {
2744
+ lines.push("Edges:");
2745
+ for (const edge of neighborhood.edges) {
2746
+ lines.push(` ${safe(edge.source)}:${edge.line} \u2192 ${safe(edge.target)}`);
2747
+ }
2748
+ }
2749
+ if (neighborhood.relations.length > 0) {
2750
+ lines.push("Typed relationships:");
2751
+ for (const relation of neighborhood.relations) {
2752
+ lines.push(` ${safe(relation.source)}:${relation.provenance.line} ${safe(relation.predicate)} \u2192 ${safe(relation.target)}`);
2753
+ }
2754
+ }
2755
+ if (neighborhood.truncated) {
2756
+ lines.push("Results were truncated by the node or connection limit; lower the depth or raise --limit.");
2757
+ }
2758
+ return `${lines.join(`
2759
+ `)}
2760
+ `;
2761
+ }
2762
+ function renderList(rows) {
2763
+ const lines = [`Notes (${rows.length})`];
2764
+ if (rows.length === 0)
2765
+ lines.push(" None.");
2766
+ for (const row of rows) {
2767
+ const tags = row.tags.length === 0 ? "" : ` #${row.tags.map(safe).join(" #")}`;
2768
+ lines.push(` ${safe(row.path)} \u2014 ${safe(row.title)} \u2190 ${row.inboundContextualCount} \u2192 ${row.outboundContextualCount}${tags}`);
2769
+ }
2770
+ return `${lines.join(`
2771
+ `)}
2772
+ `;
2773
+ }
2774
+ function renderSourceInbox(report) {
2775
+ const lines = [
2776
+ `Source inbox: ${report.pendingSources} pending of ${report.totalSources} captures; ${report.disposedSources} disposed.`
2777
+ ];
2778
+ if (report.items.length === 0)
2779
+ lines.push(" None.");
2780
+ for (const item of report.items) {
2781
+ const clipped = item.clipped === null ? "undated" : item.clipped;
2782
+ lines.push(` ${safe(clipped)} ${safe(item.path)} \u2014 ${safe(item.title)} (${safe(item.reason)})`);
2783
+ }
2784
+ if (report.truncated) {
2785
+ lines.push(` \u2026 ${report.pendingSources - report.returnedSources} more; raise --limit to inspect them.`);
2786
+ }
2787
+ lines.push("Advisory only: a capture may remain an intentional leaf.");
2788
+ return `${lines.join(`
2789
+ `)}
2790
+ `;
2791
+ }
2792
+ function renderAuthoringResult(verb, result) {
2793
+ return [
2794
+ `${result.changed ? verb : "Unchanged"} ${safe(result.path)}`,
2795
+ `Revision: ${safe(result.revision)}; outbound relationships: ${result.relations.length}.`,
2796
+ ""
2797
+ ].join(`
2798
+ `);
2799
+ }
2800
+ async function runNoteCreate(command, output, dependencies) {
2801
+ const body = command.body ?? (command.bodyFile === undefined ? undefined : await readBoundedUtf8(command.bodyFile, 16 * 1024 * 1024, "note body"));
2802
+ const result = await (dependencies.createNote ?? createNote)(command.root, {
2803
+ ...command.input,
2804
+ ...body === undefined ? {} : { body }
2805
+ });
2806
+ output.stdout(command.json ? terminalSafeJson(result) : sanitizeTerminalText(renderAuthoringResult("Created", result)));
2807
+ return 0;
2808
+ }
2809
+ async function runRelation(command, output, dependencies) {
2810
+ if (command.action === "list") {
2811
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root, { mentionScope: false });
2812
+ const lookup = lookupNote(snapshot.notes, command.source);
2813
+ if (lookup.kind === "missing") {
2814
+ if (command.json) {
2815
+ output.stdout(terminalSafeJson({
2816
+ ok: false,
2817
+ kind: "missing",
2818
+ note: command.source
2819
+ }));
2820
+ } else {
2821
+ output.stderr(`error: note was not found
2822
+ `);
2823
+ }
2824
+ return 3;
2825
+ }
2826
+ if (lookup.kind === "ambiguous") {
2827
+ if (command.json) {
2828
+ output.stdout(terminalSafeJson({
2829
+ ok: false,
2830
+ kind: "ambiguous",
2831
+ candidates: lookup.candidates.map(({ id }) => id)
2832
+ }));
2833
+ } else {
2834
+ output.stderr(`error: note is ambiguous (${lookup.candidates.map(({ id }) => safe(id)).join(", ")})
2835
+ `);
2836
+ }
2837
+ return 3;
2838
+ }
2839
+ const outbound = snapshot.analysis.authoredRelations.filter(({ source }) => source === lookup.note.id);
2840
+ const inbound = snapshot.analysis.authoredRelations.filter(({ target: target2 }) => target2 === lookup.note.id);
2841
+ const payload = {
2842
+ note: lookup.note.id,
2843
+ outboundCount: outbound.length,
2844
+ inboundCount: inbound.length,
2845
+ outbound,
2846
+ inbound
2847
+ };
2848
+ if (command.json) {
2849
+ output.stdout(terminalSafeJson(payload));
2850
+ } else {
2851
+ const lines = [
2852
+ `Relationships for ${safe(lookup.note.id)} (${outbound.length} out, ${inbound.length} in)`
2853
+ ];
2854
+ if (outbound.length === 0 && inbound.length === 0)
2855
+ lines.push(" None.");
2856
+ for (const relation of outbound) {
2857
+ lines.push(` \u2192 ${safe(relation.predicate)} \u2192 ${safe(relation.target)}`);
2858
+ }
2859
+ for (const relation of inbound) {
2860
+ lines.push(` \u2190 ${safe(relation.predicate)} \u2190 ${safe(relation.source)}`);
2861
+ }
2862
+ output.stdout(`${lines.join(`
2863
+ `)}
2864
+ `);
2865
+ }
2866
+ return 0;
2867
+ }
2868
+ const predicate = command.predicate;
2869
+ const target = command.target;
2870
+ if (predicate === undefined || target === undefined) {
2871
+ throw new Error("relation command parser lost its predicate or target");
2872
+ }
2873
+ const options = command.expectedRevision === undefined ? {} : { expectedRevision: command.expectedRevision };
2874
+ const result = command.action === "add" ? await (dependencies.addNoteRelation ?? addNoteRelation)(command.root, command.source, predicate, target, options) : await (dependencies.removeNoteRelation ?? removeNoteRelation)(command.root, command.source, predicate, target, options);
2875
+ output.stdout(command.json ? terminalSafeJson(result) : sanitizeTerminalText(renderAuthoringResult("Updated", result)));
2876
+ return 0;
2877
+ }
2878
+ function renderPercolation(result, note) {
2879
+ const lines = [
2880
+ `Percolation${note === undefined ? "" : ` for ${safe(note)}`}: ${result.candidates.length} candidate${result.candidates.length === 1 ? "" : "s"}${result.truncated ? " (truncated)" : ""}.`
2881
+ ];
2882
+ if (result.candidates.length === 0)
2883
+ lines.push(" None.");
2884
+ for (const candidate of result.candidates) {
2885
+ if (candidate.kind === "missing-concept") {
2886
+ lines.push(` concept #${safe(candidate.tag)} \u2192 ${safe(candidate.suggestedId)} (${candidate.support} supporting notes)` + (candidate.collidesWith === null ? "" : `; natural ID is occupied by ${safe(candidate.collidesWith)}`));
2887
+ } else if (candidate.kind === "missing-relation") {
2888
+ lines.push(` relation ${safe(candidate.source)} ${safe(candidate.suggestedPredicate)} ${safe(candidate.target)} (${candidate.support} shared signals)`);
2889
+ } else if (candidate.kind === "unlinked-mention") {
2890
+ lines.push(` mention ${safe(candidate.source)} \u2192 ${safe(candidate.target)} (${candidate.support})`);
2891
+ } else {
2892
+ lines.push(` hygiene ${safe(candidate.problem)} in ${safe(candidate.source)}${candidate.target === null ? "" : ` \u2192 ${safe(candidate.target)}`}: ${safe(candidate.message)}`);
2893
+ }
2894
+ for (const evidence of candidate.evidence.slice(0, 3)) {
2895
+ if (evidence.kind === "tag") {
2896
+ lines.push(` ${safe(evidence.path)} #${safe(evidence.tag)}`);
2897
+ } else if (evidence.kind === "shared-tag") {
2898
+ lines.push(` ${safe(evidence.path)} shares #${safe(evidence.tag)}`);
2899
+ } else if (evidence.kind === "shared-concept") {
2900
+ lines.push(` ${safe(evidence.path)} shares ${safe(evidence.concept)}`);
2901
+ } else if (evidence.kind === "mention") {
2902
+ lines.push(` ${safe(evidence.source)}:${evidence.line} mentions \u201C${safe(evidence.phrase)}\u201D`);
2903
+ } else if (evidence.kind === "relation") {
2904
+ lines.push(` ${safe(evidence.source)}:${evidence.line} ${safe(evidence.predicate)} \u2192 ${safe(evidence.target)}`);
2905
+ } else {
2906
+ lines.push(` ${safe(evidence.source)}:${evidence.line} ${safe(evidence.message)}`);
2907
+ }
2908
+ }
2909
+ if (candidate.evidence.length > 3) {
2910
+ lines.push(` \u2026 ${candidate.evidence.length - 3} more evidence records`);
2911
+ }
2912
+ }
2913
+ return `${lines.join(`
2914
+ `)}
2915
+ `;
2916
+ }
2917
+ async function runPercolate(command, output, dependencies) {
2918
+ const maxMentionPairs = command.note === undefined ? MAX_PERCOLATION_MENTION_PAIRS : MAX_SCOPED_PERCOLATION_MENTION_PAIRS;
2919
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root, {
2920
+ maxNotes: MAX_PERCOLATION_NOTES,
2921
+ maxMentionPairs,
2922
+ maxMentions: Math.min(MAX_PERCOLATION_MENTIONS, maxMentionPairs),
2923
+ ...command.note === undefined ? {} : { mentionScope: command.note }
2924
+ });
2925
+ const result = (dependencies.percolateVault ?? percolateVault)(snapshot.notes, snapshot.analysis, {
2926
+ ...command.note === undefined ? {} : { note: command.note },
2927
+ minSupport: command.minSupport,
2928
+ limit: command.limit
2929
+ });
2930
+ output.stdout(command.json ? terminalSafeJson({
2931
+ root: snapshot.root,
2932
+ note: command.note ?? null,
2933
+ minSupport: command.minSupport,
2934
+ ...result
2935
+ }) : sanitizeTerminalText(renderPercolation(result, command.note)));
2936
+ return 0;
2937
+ }
2938
+ async function runList(command, output, dependencies) {
2939
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root, command.options);
2940
+ const rows = queryVault(snapshot.notes, snapshot.analysis, {
2941
+ filters: command.filters,
2942
+ tags: command.tags,
2943
+ repositoryScopes: command.repositoryScopes,
2944
+ sort: command.sort,
2945
+ direction: command.direction,
2946
+ ...command.limit === undefined ? {} : { limit: command.limit }
2947
+ });
2948
+ output.stdout(command.json ? terminalSafeJson({ root: snapshot.root, count: rows.length, notes: rows }) : sanitizeTerminalText(renderList(rows)));
2949
+ return 0;
2950
+ }
2951
+ async function runCatalog(command, output, dependencies) {
2952
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root, command.options);
2953
+ const relativeIndex = relative(snapshot.root, snapshot.indexPath).split("\\").join("/");
2954
+ const catalogNoteId = relativeIndex.toLocaleLowerCase("en-US").endsWith(".md") ? relativeIndex.slice(0, -3) : relativeIndex;
2955
+ const catalog = renderCatalog(snapshot.notes, catalogNoteId);
2956
+ output.stdout(command.json ? terminalSafeJson({
2957
+ root: snapshot.root,
2958
+ catalogMode: snapshot.catalogMode,
2959
+ noteCount: snapshot.analysis.noteCount,
2960
+ catalog
2961
+ }) : sanitizeTerminalText(`${catalog}
2962
+ `));
2963
+ return 0;
2964
+ }
2965
+ async function runInbox(command, output, dependencies) {
2966
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root, command.options);
2967
+ const report = sourceInbox(snapshot.notes, snapshot.analysis, {
2968
+ limit: command.limit,
2969
+ ...command.sourcePrefixes.length === 0 ? {} : { sourcePrefixes: command.sourcePrefixes }
2970
+ });
2971
+ output.stdout(command.json ? terminalSafeJson({ root: snapshot.root, ...report }) : sanitizeTerminalText(renderSourceInbox(report)));
2972
+ return 0;
2973
+ }
2974
+ async function runInit(command, output, initialize) {
2975
+ const result = await initialize(command.directory);
2976
+ if (command.json)
2977
+ output.stdout(terminalSafeJson(result));
2978
+ else {
2979
+ const relativeRoot = relative(process.cwd(), result.root) || ".";
2980
+ output.stdout(`Initialized ${safe(relativeRoot)} with ${result.files.length} files.
2981
+ `);
2982
+ }
2983
+ return 0;
2984
+ }
2985
+ async function runCaptureBundle(command, output, dependencies) {
2986
+ const verification = await (dependencies.verifyCaptureBundle ?? verifyCaptureBundle)(command.path, command.options);
2987
+ const inspection = verification.inspection;
2988
+ if (command.json) {
2989
+ const jsonInspection = command.action === "show" ? inspection : Object.freeze({
2990
+ root: inspection.root,
2991
+ schemaVersion: inspection.schemaVersion,
2992
+ sourceUrl: inspection.sourceUrl,
2993
+ canonicalUrl: inspection.canonicalUrl,
2994
+ status: inspection.status,
2995
+ capturedAt: inspection.capturedAt,
2996
+ document: Object.freeze({
2997
+ path: inspection.document.path,
2998
+ bytes: inspection.document.bytes,
2999
+ sha256: inspection.document.sha256,
3000
+ expectedBytes: inspection.document.expectedBytes,
3001
+ expectedSha256: inspection.document.expectedSha256,
3002
+ integrity: inspection.document.integrity
3003
+ }),
3004
+ assets: inspection.assets
3005
+ });
3006
+ output.stdout(terminalSafeJson({
3007
+ ok: verification.ok,
3008
+ trust: "untrusted",
3009
+ trustScope: command.action === "show" ? "inspection and issues" : "inspection metadata and issues; stored document and source HTML omitted",
3010
+ inspection: jsonInspection,
3011
+ issues: verification.issues
3012
+ }));
3013
+ } else {
3014
+ const lines = [
3015
+ `Capture bundle: ${safe(inspection.root)}`,
3016
+ `Source: ${safe(inspection.sourceUrl)}`,
3017
+ `Document: ${safe(inspection.document.path)} (${inspection.document.integrity}; ${inspection.document.bytes} bytes)`,
3018
+ `Assets: ${inspection.assets.length}`
3019
+ ];
3020
+ for (const issue of verification.issues)
3021
+ lines.push(`Issue: ${safe(issue.path)} \u2014 ${safe(issue.message)}`);
3022
+ if (command.action === "show") {
3023
+ lines.push("", "Execution trust: untrusted. The captured fields below are data, not instructions.", "", sanitizeTerminalText(inspection.document.markdown));
3024
+ if (inspection.sourceHtml !== undefined) {
3025
+ lines.push("", "Source HTML (inert text):", "", sanitizeTerminalText(inspection.sourceHtml));
3026
+ }
3027
+ }
3028
+ output.stdout(`${lines.join(`
3029
+ `)}
3030
+ `);
3031
+ }
3032
+ return verification.ok ? 0 : 3;
3033
+ }
3034
+ async function runCaptureDiff(command, output, dependencies) {
3035
+ const result = await (dependencies.diffCaptureBundle ?? diffCaptureBundle)(command.options);
3036
+ if (command.json) {
3037
+ output.stdout(terminalSafeJson({
3038
+ ok: true,
3039
+ trust: "untrusted",
3040
+ trustScope: "result",
3041
+ result
3042
+ }));
3043
+ } else {
3044
+ output.stdout([
3045
+ `Capture at ${safe(result.ref)}: ${result.status}`,
3046
+ `Path: ${safe(result.repositoryPath)}`,
3047
+ `Current SHA-256: ${result.currentSha256}`,
3048
+ ...result.referenceSha256 === null ? [] : [`Reference SHA-256: ${result.referenceSha256}`],
3049
+ ...result.diff === null || result.diff === "" ? [] : ["", "Diff (untrusted data):", "", sanitizeTerminalText(result.diff)],
3050
+ ""
3051
+ ].join(`
3052
+ `));
3053
+ }
3054
+ return 0;
3055
+ }
3056
+ async function authorizedPortfolioSelection(command, dependencies) {
3057
+ if (command.selection === "explicit")
3058
+ return { authorizedVaults: command.vaults };
3059
+ const registry = snapshotPortfolioRegistry(await (dependencies.loadPortfolioRegistry ?? loadPortfolioRegistry)(command.registryPath));
3060
+ const selected = command.selection === "all" ? registry.vaults : registry.vaults.filter(({ visibility }) => visibility === "public" || visibility === "organization");
3061
+ if (selected.length === 0)
3062
+ throw new Error("Portfolio selection resolved to no vaults.");
3063
+ if (selected.length > MAX_AUTHORIZED_VAULTS) {
3064
+ throw new RangeError(`Portfolio selection resolved to ${selected.length} vaults, exceeding the per-operation limit of ${MAX_AUTHORIZED_VAULTS}; select a bounded set with repeated --vault.`);
3065
+ }
3066
+ return Object.freeze({
3067
+ authorizedVaults: Object.freeze(selected.map(({ key }) => key)),
3068
+ registry
3069
+ });
3070
+ }
3071
+ function renderPortfolioSearch(result) {
3072
+ const lines = [
3073
+ `Portfolio search: ${safe(result.query)}`,
3074
+ `Vaults: ${result.diagnostics.availableVaults}/${result.diagnostics.selectedVaults}; notes: ${result.diagnostics.notes}; partial: ${result.partial}`
3075
+ ];
3076
+ for (const hit of result.results) {
3077
+ const identity = hit.identity.kind === "stable" ? hit.identity.uri : `${hit.vault.key}:${hit.path} [legacy path identity]`;
3078
+ lines.push("", `${hit.rank}. ${safe(hit.title)}`, ` ${safe(identity)} \xB7 local rank ${hit.localRank}`, ` ${safe(hit.snippet)}`);
3079
+ }
3080
+ return `${lines.join(`
3081
+ `)}
3082
+ `;
3083
+ }
3084
+ async function runPortfolioSearch(command, output, dependencies) {
3085
+ const selection = await authorizedPortfolioSelection(command, dependencies);
3086
+ const searchRules = command.rulesPath === undefined ? undefined : await loadSearchRulesFile(command.rulesPath);
3087
+ const session = await (dependencies.openKnowledgePortfolio ?? openKnowledgePortfolio)({
3088
+ registryPath: command.registryPath,
3089
+ ...selection.registry === undefined ? {} : { registry: selection.registry },
3090
+ workspaceRoot: command.workspaceRoot,
3091
+ authorizedVaults: selection.authorizedVaults,
3092
+ failurePolicy: command.failurePolicy,
3093
+ ...searchRules === undefined ? {} : { knowledgeBase: { searchRules } }
3094
+ });
3095
+ try {
3096
+ const result = await session.search({
3097
+ query: command.query,
3098
+ ...command.mode === undefined ? {} : { mode: command.mode },
3099
+ ordering: command.ordering,
3100
+ ...command.limit === undefined ? {} : { limit: command.limit },
3101
+ graph: false
3102
+ });
3103
+ output.stdout(command.json ? terminalSafeJson({
3104
+ ok: true,
3105
+ trust: "untrusted",
3106
+ trustScope: "result.results[*].title, snippet, metadata, evidence, and local",
3107
+ result
3108
+ }) : renderPortfolioSearch(result));
3109
+ return 0;
3110
+ } finally {
3111
+ await session.close();
3112
+ }
3113
+ }
3114
+ async function runPortfolioAudit(command, output, dependencies) {
3115
+ const selection = await authorizedPortfolioSelection(command, dependencies);
3116
+ const report = await (dependencies.auditKnowledgePortfolio ?? auditKnowledgePortfolio)({
3117
+ registryPath: command.registryPath,
3118
+ ...selection.registry === undefined ? {} : { registry: selection.registry },
3119
+ workspaceRoot: command.workspaceRoot,
3120
+ authorizedVaults: selection.authorizedVaults
3121
+ });
3122
+ if (command.json) {
3123
+ output.stdout(terminalSafeJson({
3124
+ ok: report.counts.error === 0 && !report.truncated,
3125
+ report
3126
+ }));
3127
+ } else {
3128
+ const lines = [
3129
+ `Portfolio audit: ${report.auditedVaults}/${report.selectedVaults} vaults; ${report.notes} notes`,
3130
+ `Issues: ${report.counts.error} errors, ${report.counts.warning} warnings, ${report.counts.advisory} advisories`
3131
+ ];
3132
+ for (const issue of report.issues) {
3133
+ const location = issue.vault === undefined ? "portfolio" : `${issue.vault.key}${issue.path === undefined ? "" : `:${issue.path}${issue.line === undefined ? "" : `:${issue.line}`}`}`;
3134
+ lines.push(`- ${issue.severity} ${issue.code} ${safe(location)}: ${safe(issue.message)}`);
3135
+ }
3136
+ output.stdout(`${lines.join(`
3137
+ `)}
3138
+ `);
3139
+ }
3140
+ return command.strict && (report.counts.error > 0 || report.truncated) ? 3 : 0;
3141
+ }
3142
+ function contextIssuePayload(issue) {
3143
+ return { ...issue };
3144
+ }
3145
+ function uniqueAgentContextIssues(issues) {
3146
+ const unique = new Map;
3147
+ for (const issue of issues)
3148
+ unique.set(JSON.stringify(issue), issue);
3149
+ return [...unique.values()].toSorted((left, right) => `${left.kind}\x00${left.message}`.localeCompare(`${right.kind}\x00${right.message}`));
3150
+ }
3151
+ function contextPayload(inspection, snapshot, memory) {
3152
+ const connections = new Map(snapshot.analysis.noteConnections.map((connection) => [connection.id, connection]));
3153
+ return {
3154
+ repositoryRoot: inspection.repositoryRoot,
3155
+ vaultRoot: snapshot.root,
3156
+ target: inspection.target,
3157
+ targetScope: inspection.targetScope,
3158
+ guides: inspection.inheritedGuides.map((guide) => ({
3159
+ path: guide.path,
3160
+ scope: guide.scope,
3161
+ context: guide.marker.markers[0]?.noteId
3162
+ })),
3163
+ contexts: inspection.matchingContexts.map((context) => {
3164
+ const connection = connections.get(context.note.id);
3165
+ return {
3166
+ id: context.note.id,
3167
+ path: context.note.path,
3168
+ title: context.note.title,
3169
+ scope: context.scope,
3170
+ summary: context.note.summary,
3171
+ inboundContextualCount: connection?.inboundContextualCount ?? 0,
3172
+ outboundContextualCount: connection?.outboundContextualCount ?? 0
3173
+ };
3174
+ }),
3175
+ records: memory,
3176
+ issues: inspection.issues.map(contextIssuePayload)
3177
+ };
3178
+ }
3179
+ function renderContext(inspection, snapshot, memory) {
3180
+ const lines = [
3181
+ `Agent context for ${safe(inspection.target)} (scope ${safe(inspection.targetScope)})`,
3182
+ "Guides (root \u2192 nearest):"
3183
+ ];
3184
+ if (inspection.inheritedGuides.length === 0)
3185
+ lines.push(" None.");
3186
+ for (const guide of inspection.inheritedGuides) {
3187
+ const context = guide.marker.markers[0]?.noteId;
3188
+ lines.push(` ${safe(guide.path)}${context === undefined ? "" : ` \u2192 ${safe(context)}`}`);
3189
+ }
3190
+ lines.push("KB hubs (nearest \u2192 root):");
3191
+ if (inspection.matchingContexts.length === 0)
3192
+ lines.push(" None.");
3193
+ for (const context of inspection.matchingContexts) {
3194
+ const connection = snapshot.analysis.noteConnections.find(({ id }) => id === context.note.id);
3195
+ lines.push(` ${safe(context.note.id)} \u2014 ${safe(context.note.title)} \u2190 ${connection?.inboundContextualCount ?? 0} \u2192 ${connection?.outboundContextualCount ?? 0}`);
3196
+ if (context.note.summary !== "")
3197
+ lines.push(` ${safe(context.note.summary)}`);
3198
+ }
3199
+ const groupLabels = {
3200
+ maintainedKnowledge: "Maintained knowledge",
3201
+ activePlans: "Active plans",
3202
+ datedResearch: "Dated research",
3203
+ reports: "Reports",
3204
+ historicalPlans: "Historical plans"
3205
+ };
3206
+ lines.push(`Repository memory (${memory.counts.returned} of ${memory.counts.matched} matched records):`);
3207
+ for (const key of repositoryMemoryGroupKeys) {
3208
+ const group = memory.groups[key];
3209
+ lines.push(` ${groupLabels[key]} (${group.returned}/${group.total})`);
3210
+ if (group.records.length === 0)
3211
+ lines.push(" None.");
3212
+ for (const record of group.records) {
3213
+ const scopeState = record.scopeState.status === "present" ? record.scopeState.kind : record.scopeState.status;
3214
+ lines.push(` ${safe(record.path)} \u2014 ${safe(record.title)} [${safe(record.matchedScope)}; ${safe(record.match)}; ${safe(scopeState)}]`);
3215
+ if (record.description !== undefined)
3216
+ lines.push(` ${safe(record.description)}`);
3217
+ else if (record.summary !== "")
3218
+ lines.push(` ${safe(record.summary)}`);
3219
+ }
3220
+ if (group.truncated)
3221
+ lines.push(` \u2026 ${group.total - group.returned} more.`);
3222
+ }
3223
+ if (memory.invalidRecords.total > 0) {
3224
+ lines.push(`Repository-memory errors (${memory.invalidRecords.returned}/${memory.invalidRecords.total}):`);
3225
+ for (const invalid of memory.invalidRecords.details) {
3226
+ lines.push(` ${safe(invalid.path)}: ${invalid.issues.map(safe).join(" ")}`);
3227
+ }
3228
+ }
3229
+ if (memory.advisories.total > 0) {
3230
+ lines.push(`Repository-memory advisories (${memory.advisories.returned}/${memory.advisories.total}):`);
3231
+ for (const advisory of memory.advisories.details)
3232
+ lines.push(` ${safe(advisory.message)}`);
3233
+ }
3234
+ for (const issue of inspection.issues)
3235
+ lines.push(`error: ${safe(issue.message)}`);
3236
+ if (inspection.matchingContexts.length > 0) {
3237
+ lines.push("Open a hub, then use `kb links <hub> --root <vault> --depth 1` for bounded neighboring context.");
3238
+ }
3239
+ return `${lines.join(`
3240
+ `)}
3241
+ `;
3242
+ }
3243
+ async function runContext(command, output, dependencies) {
3244
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root);
3245
+ const inspection = await (dependencies.inspectAgentContextRepository ?? inspectAgentContextRepository)(snapshot.notes, {
3246
+ repositoryRoot: command.repository,
3247
+ target: command.target,
3248
+ targetKind: command.targetKind
3249
+ });
3250
+ const memory = await (dependencies.buildRepositoryMemoryContext ?? buildRepositoryMemoryContext)(snapshot.notes, {
3251
+ repositoryRoot: command.repository,
3252
+ target: inspection.target
3253
+ });
3254
+ output.stdout(command.json ? terminalSafeJson(contextPayload(inspection, snapshot, memory)) : sanitizeTerminalText(renderContext(inspection, snapshot, memory)));
3255
+ return inspection.issues.length === 0 && memory.invalidRecords.total === 0 ? 0 : 3;
3256
+ }
3257
+ function agentIdentityPayload(scopeInput) {
3258
+ const scope = normalizeRepositoryScope(scopeInput);
3259
+ return {
3260
+ scope,
3261
+ noteId: agentContextNoteId(scope),
3262
+ notePath: agentContextNotePath(scope),
3263
+ guidePath: agentContextGuidePath(scope),
3264
+ marker: agentContextMarkerForScope(scope)
3265
+ };
3266
+ }
3267
+ function renderAgentIdentity(identity) {
3268
+ return [
3269
+ `Scope: ${safe(identity.scope ?? "")}`,
3270
+ `Note ID: ${safe(identity.noteId ?? "")}`,
3271
+ `Note path: ${safe(identity.notePath ?? "")}`,
3272
+ `Guide path: ${safe(identity.guidePath ?? "")}`,
3273
+ `Marker: ${safe(identity.marker ?? "")}`,
3274
+ ""
3275
+ ].join(`
3276
+ `);
3277
+ }
3278
+ function runAgentIdentity(command, output) {
3279
+ const identity = agentIdentityPayload(command.scope);
3280
+ output.stdout(command.json ? terminalSafeJson(identity) : sanitizeTerminalText(renderAgentIdentity(identity)));
3281
+ return 0;
3282
+ }
3283
+ function agentCheckErrors(contextIssues, discoveryIssues, audit) {
3284
+ return [
3285
+ ...uniqueAgentContextIssues(contextIssues).map((issue) => ({ kind: "context", issue })),
3286
+ ...discoveryIssues.filter(({ kind }) => kind !== "symlink-directory").map((issue) => ({ kind: "discovery", issue })),
3287
+ ...audit.guides.flatMap((guide) => guide.shapeIssues.map((issue) => ({ kind: "shape", path: guide.path, issue })))
3288
+ ];
3289
+ }
3290
+ function renderAgentCheckError(error) {
3291
+ if (error.kind === "context")
3292
+ return error.issue.message;
3293
+ if (error.kind === "discovery")
3294
+ return error.issue.message;
3295
+ return `${error.path}: ${error.issue.message}`;
3296
+ }
3297
+ function advisoryLabel(advisory) {
3298
+ if (advisory.kind === "contents-budget") {
3299
+ return `${advisory.path}: Contents has ${advisory.actualWords} words / ${advisory.actualBullets} bullets`;
3300
+ }
3301
+ if (advisory.kind === "guidelines-budget") {
3302
+ return `${advisory.path}: Guidelines has ${advisory.actualWords} words / ${advisory.actualBullets} bullets`;
3303
+ }
3304
+ if (advisory.kind === "long-guideline") {
3305
+ return `${advisory.path}:${advisory.line}: guideline has ${advisory.words} words`;
3306
+ }
3307
+ if (advisory.kind === "inherited-budget") {
3308
+ return `${advisory.path}: inherited chain has ${advisory.words} words across ${advisory.guides.length} guides`;
3309
+ }
3310
+ return `${advisory.guides.length} guides repeat a ${advisory.words}-word rule: ${advisory.text}`;
3311
+ }
3312
+ function agentReportPayload(repositoryRoot, vaultRoot, audit, validContexts, errors, discoveryIssues, includeAudit) {
3313
+ return {
3314
+ repositoryRoot,
3315
+ vaultRoot,
3316
+ guideCount: audit.guideCount,
3317
+ mappedGuideCount: audit.mappedGuideCount,
3318
+ validContextCount: validContexts,
3319
+ words: audit.words,
3320
+ contentsWords: audit.contentsWords,
3321
+ guidelineWords: audit.guidelineWords,
3322
+ nonblankLines: audit.nonblankLines,
3323
+ errors,
3324
+ discoveryIssues,
3325
+ ...includeAudit ? {
3326
+ advisories: audit.advisories,
3327
+ duplicates: audit.duplicates,
3328
+ guides: audit.guides.map((guide) => ({
3329
+ path: guide.path,
3330
+ scope: guide.scope,
3331
+ words: guide.words,
3332
+ nonblankLines: guide.nonblankLines,
3333
+ contentsWords: guide.contents.words,
3334
+ guidelineWords: guide.guidelines.words,
3335
+ inheritedWords: guide.inheritedWords,
3336
+ inheritedGuidePaths: guide.inheritedGuidePaths,
3337
+ context: guide.marker.markers[0]?.noteId
3338
+ }))
3339
+ } : {}
3340
+ };
3341
+ }
3342
+ function renderAgentReport(action, audit, validContexts, errors, discoveryIssues) {
3343
+ const lines = [
3344
+ `${action === "check" ? "Checked" : "Audited"} ${audit.guideCount} agent guides; ${audit.mappedGuideCount} markers, ${validContexts} valid KB hubs.`,
3345
+ `Context: ${audit.words} words (${audit.contentsWords} Contents, ${audit.guidelineWords} Guidelines), ${audit.nonblankLines} nonblank lines.`
3346
+ ];
3347
+ if (errors.length === 0)
3348
+ lines.push("Mappings and guide shape: clean.");
3349
+ else
3350
+ for (const error of errors)
3351
+ lines.push(`error: ${safe(renderAgentCheckError(error))}`);
3352
+ const skippedDirectories = discoveryIssues.filter(({ kind }) => kind === "symlink-directory");
3353
+ if (skippedDirectories.length > 0) {
3354
+ lines.push(`Skipped symbolic-link directories (${skippedDirectories.length}):`);
3355
+ for (const issue of skippedDirectories)
3356
+ lines.push(` ${safe(issue.path)}`);
3357
+ }
3358
+ if (action === "audit") {
3359
+ lines.push(`Advisories: ${audit.advisories.length}; exact duplicate rules: ${audit.duplicates.length}.`);
3360
+ const worstChains = audit.guides.toSorted((left, right) => right.inheritedWords - left.inheritedWords || left.path.localeCompare(right.path)).slice(0, 10);
3361
+ lines.push("Largest inherited chains:");
3362
+ for (const guide of worstChains) {
3363
+ lines.push(` ${guide.inheritedWords} words / ${guide.inheritedGuidePaths.length} guides ${safe(guide.path)}`);
3364
+ }
3365
+ const shown = audit.advisories.slice(0, 25);
3366
+ if (shown.length > 0)
3367
+ lines.push("Advisory sample:");
3368
+ for (const advisory of shown)
3369
+ lines.push(` ${safe(advisoryLabel(advisory))}`);
3370
+ if (audit.advisories.length > shown.length) {
3371
+ lines.push(` \u2026 ${audit.advisories.length - shown.length} more; rerun with --json for the complete audit.`);
3372
+ }
3373
+ }
3374
+ return `${lines.join(`
3375
+ `)}
3376
+ `;
3377
+ }
3378
+ async function runAgents(command, output, dependencies) {
3379
+ const snapshot = await (dependencies.scanVault ?? scanVault)(command.root);
3380
+ const repository = await (dependencies.auditAgentGuideRepository ?? auditAgentGuideRepository)(command.repository);
3381
+ const mapping = analyzeAgentContexts(snapshot.notes, repository.guides);
3382
+ const filesystem = await (dependencies.inspectAgentContextRepository ?? inspectAgentContextRepository)(snapshot.notes, {
3383
+ repositoryRoot: command.repository,
3384
+ target: ".",
3385
+ targetKind: "directory",
3386
+ validationMode: "all"
3387
+ });
3388
+ const errors = agentCheckErrors([...mapping.issues, ...filesystem.issues], repository.issues, repository.audit);
3389
+ const validContexts = mapping.contexts.filter(({ valid }) => valid).length;
3390
+ if (command.json) {
3391
+ output.stdout(terminalSafeJson(agentReportPayload(repository.repositoryRoot, snapshot.root, repository.audit, validContexts, errors, repository.issues, command.action === "audit")));
3392
+ } else {
3393
+ output.stdout(sanitizeTerminalText(renderAgentReport(command.action, repository.audit, validContexts, errors, repository.issues)));
3394
+ }
3395
+ return errors.length === 0 ? 0 : 3;
3396
+ }
3397
+ async function runVault(command, output, dependencies) {
3398
+ const snapshot = command.kind === "refresh" ? await (dependencies.refreshVault ?? refreshVault)(command.root, command.options) : await (dependencies.scanVault ?? scanVault)(command.root, command.options);
3399
+ if (command.kind === "refresh" || command.kind === "check") {
3400
+ const noCatalog = command.kind === "check" && command.noCatalog === true;
3401
+ const attachments = command.kind === "check" ? await (dependencies.validateMarkdownAttachments ?? validateMarkdownAttachments)({
3402
+ root: snapshot.root,
3403
+ documents: snapshot.notes.map(({ path, content }) => ({ path, content }))
3404
+ }) : undefined;
3405
+ output.stdout(command.json ? terminalSafeJson(summary(snapshot, { noCatalog, ...attachments === undefined ? {} : { attachments } })) : sanitizeTerminalText(renderSnapshot(command.kind, snapshot, noCatalog, attachments)));
3406
+ return checkExitCode(snapshot, noCatalog, attachments);
3407
+ }
3408
+ if (command.kind === "graph") {
3409
+ output.stdout(command.json ? terminalSafeJson(graphJson(snapshot)) : sanitizeTerminalText(renderGraph(snapshot)));
3410
+ return 0;
3411
+ }
3412
+ const lookup = lookupNote(snapshot.notes, command.note ?? "");
3413
+ if (lookup.kind === "missing") {
3414
+ if (command.json) {
3415
+ output.stdout(terminalSafeJson({
3416
+ ok: false,
3417
+ kind: "missing",
3418
+ note: command.note ?? ""
3419
+ }));
3420
+ } else {
3421
+ output.stderr(`error: note was not found
3422
+ `);
3423
+ }
3424
+ return 3;
3425
+ }
3426
+ if (lookup.kind === "ambiguous") {
3427
+ if (command.json) {
3428
+ output.stdout(terminalSafeJson({ ok: false, kind: "ambiguous", candidates: lookup.candidates.map(({ path }) => path) }));
3429
+ } else {
3430
+ output.stderr(`error: note is ambiguous (${lookup.candidates.map(({ path }) => safe(path)).join(", ")})
3431
+ `);
3432
+ }
3433
+ return 3;
3434
+ }
3435
+ if (command.kind === "links") {
3436
+ const neighborhood = navigateLinks(snapshot.notes, snapshot.analysis, lookup.note, {
3437
+ direction: command.direction ?? "both",
3438
+ depth: command.depth ?? 1,
3439
+ ...command.limit === undefined ? {} : { limit: command.limit }
3440
+ });
3441
+ output.stdout(command.json ? terminalSafeJson(neighborhood) : sanitizeTerminalText(renderLinks(neighborhood)));
3442
+ return 0;
3443
+ }
3444
+ const connection = snapshot.analysis.noteConnections.find(({ id }) => id === lookup.note.id);
3445
+ const backlinks = connection?.backlinks ?? [];
3446
+ const relationBacklinks = connection?.relationBacklinks ?? [];
3447
+ output.stdout(command.json ? terminalSafeJson(backlinkPayload(lookup.note.path, backlinks, relationBacklinks)) : sanitizeTerminalText(renderBacklinks(lookup.note.path, backlinks, relationBacklinks)));
3448
+ return 0;
3449
+ }
3450
+ async function main4(rawArguments = process.argv.slice(2), output = defaultOutput2, dependencies = {}) {
3451
+ const jsonRequested = rawArguments.includes("--json");
3452
+ const parsed = parseArguments(rawArguments);
3453
+ if (!parsed.ok) {
3454
+ if (jsonRequested) {
3455
+ output.stdout(terminalSafeJson({
3456
+ ok: false,
3457
+ error: { kind: "parse", message: parsed.message }
3458
+ }));
3459
+ } else {
3460
+ output.stderr(`error: ${safe(parsed.message)}
3461
+
3462
+ ${sanitizeTerminalText(usage)}`);
3463
+ }
3464
+ return 2;
3465
+ }
3466
+ const command = parsed.value;
3467
+ if (command.kind === "help") {
3468
+ output.stdout(sanitizeTerminalText(usage));
3469
+ return 0;
3470
+ }
3471
+ try {
3472
+ if (command.kind === "clip") {
3473
+ return await (dependencies.runClipCommand ?? main)(command.arguments, process.env, output);
3474
+ }
3475
+ if (command.kind === "capture-bundle")
3476
+ return await runCaptureBundle(command, output, dependencies);
3477
+ if (command.kind === "capture-diff")
3478
+ return await runCaptureDiff(command, output, dependencies);
3479
+ if (command.kind === "portfolio-search")
3480
+ return await runPortfolioSearch(command, output, dependencies);
3481
+ if (command.kind === "portfolio-audit")
3482
+ return await runPortfolioAudit(command, output, dependencies);
3483
+ if (command.kind === "url-metadata") {
3484
+ return await (dependencies.runUrlMetadataCommand ?? main3)(command.arguments, process.env, output);
3485
+ }
3486
+ if (command.kind === "pdf") {
3487
+ return await (dependencies.runPdfCommand ?? main2)(command.arguments, process.env, output);
3488
+ }
3489
+ if (command.kind === "init") {
3490
+ return await runInit(command, output, dependencies.initVault ?? initVault);
3491
+ }
3492
+ if (command.kind === "index" || command.kind === "search") {
3493
+ return await runSemantic(command, output, dependencies);
3494
+ }
3495
+ if (command.kind === "history")
3496
+ return await runHistory(command, output, dependencies);
3497
+ if (command.kind === "evaluate")
3498
+ return await runEvaluation(command, output, dependencies);
3499
+ if (command.kind === "context")
3500
+ return await runContext(command, output, dependencies);
3501
+ if (command.kind === "agent-identity")
3502
+ return runAgentIdentity(command, output);
3503
+ if (command.kind === "agents")
3504
+ return await runAgents(command, output, dependencies);
3505
+ if (command.kind === "note-create")
3506
+ return await runNoteCreate(command, output, dependencies);
3507
+ if (command.kind === "relation")
3508
+ return await runRelation(command, output, dependencies);
3509
+ if (command.kind === "percolate")
3510
+ return await runPercolate(command, output, dependencies);
3511
+ if (command.kind === "list")
3512
+ return await runList(command, output, dependencies);
3513
+ if (command.kind === "inbox")
3514
+ return await runInbox(command, output, dependencies);
3515
+ if (command.kind === "catalog")
3516
+ return await runCatalog(command, output, dependencies);
3517
+ return await runVault(command, output, dependencies);
3518
+ } catch (error) {
3519
+ const message = error instanceof Error ? error.message : String(error);
3520
+ if (jsonRequested) {
3521
+ output.stdout(terminalSafeJson({
3522
+ ok: false,
3523
+ error: { kind: "runtime", message }
3524
+ }));
3525
+ } else {
3526
+ output.stderr(`error: ${safe(message)}
3527
+ `);
3528
+ }
3529
+ return 1;
3530
+ }
3531
+ }
3532
+ var strictJsonTail = Promise.resolve();
3533
+ async function serializeStrictJson(operation) {
3534
+ const previous = strictJsonTail;
3535
+ let release2 = () => {
3536
+ return;
3537
+ };
3538
+ strictJsonTail = new Promise((resolvePromise) => {
3539
+ release2 = resolvePromise;
3540
+ });
3541
+ await previous;
3542
+ try {
3543
+ return await operation();
3544
+ } finally {
3545
+ release2();
3546
+ }
3547
+ }
3548
+ function strictProtocolObject(value) {
3549
+ const parsed = JSON.parse(value);
3550
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
3551
+ throw new TypeError("Machine output must be one JSON object.");
3552
+ }
3553
+ return parsed;
3554
+ }
3555
+ async function runExecutable(rawArguments = process.argv.slice(2), dependencies = {}) {
3556
+ if (!rawArguments.includes("--json")) {
3557
+ return main4(rawArguments, defaultOutput2, dependencies);
3558
+ }
3559
+ return serializeStrictJson(async () => {
3560
+ const stdout = process.stdout;
3561
+ const ownWrite = Object.getOwnPropertyDescriptor(stdout, "write");
3562
+ const rawStdoutWrite = stdout.write.bind(stdout);
3563
+ const rawStderrWrite = process.stderr.write.bind(process.stderr);
3564
+ const originalConsole = {
3565
+ log: console.log,
3566
+ info: console.info,
3567
+ debug: console.debug
3568
+ };
3569
+ const chunks = [];
3570
+ const protocolOutput = {
3571
+ stdout: (value) => chunks.push(value),
3572
+ stderr: (value) => {
3573
+ rawStderrWrite(value);
3574
+ }
3575
+ };
3576
+ const redirectedWrite = (...arguments_) => {
3577
+ Reflect.apply(rawStderrWrite, process.stderr, arguments_);
3578
+ return true;
3579
+ };
3580
+ const redirectedConsole = (...arguments_) => {
3581
+ rawStderrWrite(`${format(...arguments_)}
3582
+ `);
3583
+ };
3584
+ try {
3585
+ Object.defineProperty(stdout, "write", {
3586
+ configurable: true,
3587
+ writable: true,
3588
+ value: redirectedWrite
3589
+ });
3590
+ console.log = redirectedConsole;
3591
+ console.info = redirectedConsole;
3592
+ console.debug = redirectedConsole;
3593
+ } catch (error) {
3594
+ if (ownWrite === undefined)
3595
+ Reflect.deleteProperty(stdout, "write");
3596
+ else
3597
+ Object.defineProperty(stdout, "write", ownWrite);
3598
+ console.log = originalConsole.log;
3599
+ console.info = originalConsole.info;
3600
+ console.debug = originalConsole.debug;
3601
+ const fallback = terminalSafeJson({
3602
+ ok: false,
3603
+ error: {
3604
+ kind: "protocol",
3605
+ message: `Could not guard machine stdout: ${error instanceof Error ? error.message : String(error)}`
3606
+ }
3607
+ });
3608
+ rawStdoutWrite(fallback);
3609
+ return 1;
3610
+ }
3611
+ let exitCode = 1;
3612
+ let protocolValue;
3613
+ try {
3614
+ exitCode = await main4(rawArguments, protocolOutput, dependencies);
3615
+ protocolValue = strictProtocolObject(chunks.join(""));
3616
+ } catch (error) {
3617
+ protocolValue = {
3618
+ ok: false,
3619
+ error: {
3620
+ kind: "protocol",
3621
+ message: error instanceof Error ? error.message : String(error)
3622
+ }
3623
+ };
3624
+ exitCode = 1;
3625
+ } finally {
3626
+ if (ownWrite === undefined) {
3627
+ Reflect.deleteProperty(stdout, "write");
3628
+ } else {
3629
+ Object.defineProperty(stdout, "write", ownWrite);
3630
+ }
3631
+ console.log = originalConsole.log;
3632
+ console.info = originalConsole.info;
3633
+ console.debug = originalConsole.debug;
3634
+ }
3635
+ rawStdoutWrite(terminalSafeJson(protocolValue));
3636
+ return exitCode;
3637
+ });
3638
+ }
3639
+ if (import.meta.main)
3640
+ process.exitCode = await runExecutable();
3641
+ export {
3642
+ usage,
3643
+ runExecutable,
3644
+ parseArguments,
3645
+ main4 as main
3646
+ };