@hraness/wordcell 0.21.2 → 0.22.0

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