@hraness/wordcell 0.21.3 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +170 -64
  2. package/dist/authoring.js +5 -3
  3. package/dist/{authority-4sp9d552.js → authority-06eqhqam.js} +57 -12
  4. package/dist/benchmark.js +4 -4
  5. package/dist/capture.js +6 -5
  6. package/dist/cli.js +39 -33
  7. package/dist/clip/acquire.js +3 -2
  8. package/dist/clip/cli.js +6 -5
  9. package/dist/clip/doctor.js +4 -3
  10. package/dist/clip/jobs.js +3 -3
  11. package/dist/clip/refresh.js +1 -1
  12. package/dist/evaluation-builder.js +20 -19
  13. package/dist/evaluation-kb.js +16 -15
  14. package/dist/graph-authority.js +5 -5
  15. package/dist/graph-percolation.js +7 -7
  16. package/dist/graph.js +1 -1
  17. package/dist/{index-pbr6rv47.js → index-1k595z6v.js} +6 -6
  18. package/dist/{index-0k2x4nn9.js → index-233z9wmn.js} +17 -5
  19. package/dist/index-3agn8scn.js +306 -0
  20. package/dist/index-4knsp9qj.js +34 -0
  21. package/dist/index-66pshdtx.js +341 -0
  22. package/dist/{index-59ssmqqm.js → index-76dbn6pe.js} +1 -1
  23. package/dist/{index-0rfcqab6.js → index-810nfzdj.js} +617 -66
  24. package/dist/index-9rf81m0p.js +368 -0
  25. package/dist/{index-51bap0mq.js → index-bqtqeak3.js} +10 -10
  26. package/dist/{index-9zcpdbjg.js → index-de2w8crk.js} +13 -13
  27. package/dist/{index-bgfzwt4h.js → index-djrcc1yf.js} +6 -6
  28. package/dist/{index-6sw24nvv.js → index-ecqpgr5y.js} +53 -2
  29. package/dist/{index-van48n3g.js → index-fc4dr114.js} +4 -2
  30. package/dist/{index-jzcws0zn.js → index-g5vsqmdy.js} +27 -57
  31. package/dist/index-hmw17zaa.js +1985 -0
  32. package/dist/index-j70m75wd.js +217 -0
  33. package/dist/{index-nd6nynv2.js → index-jk1sgx36.js} +1 -1
  34. package/dist/{index-3ehtt59c.js → index-n386evxj.js} +1 -1
  35. package/dist/{index-vdw5yjfa.js → index-q7tbg07z.js} +110 -20
  36. package/dist/{index-ekpwvbra.js → index-qbssx940.js} +1 -2
  37. package/dist/{index-115b07ap.js → index-rr9kzq4n.js} +5 -5
  38. package/dist/{index-11621h23.js → index-tf4wzjew.js} +1 -1
  39. package/dist/{index-why54q5p.js → index-vnybgywh.js} +4 -4
  40. package/dist/{index-bcfn9xah.js → index-xv80vzfp.js} +7 -7
  41. package/dist/index.js +289 -95
  42. package/dist/percolate.js +2 -2
  43. package/dist/portfolio.js +16 -15
  44. package/dist/publish-model.js +36 -0
  45. package/dist/publish-reader/reader.css +1042 -0
  46. package/dist/publish-reader/reader.js +1534 -0
  47. package/dist/publish-reader/theme.js +92 -0
  48. package/dist/publish-search.js +52 -0
  49. package/dist/publish.js +36 -0
  50. package/dist/rerank-typesafe.js +13 -0
  51. package/dist/rerank.js +14 -0
  52. package/dist/sdk.js +16 -13
  53. package/dist/search.js +3 -3
  54. package/dist/semantic.js +5 -5
  55. package/dist/{support-vm4mkw0k.js → support-1rh2d7mt.js} +38 -32
  56. package/dist/workflows/decision-context.js +15 -14
  57. package/dist/workflows/index.js +15 -14
  58. package/package.json +65 -6
  59. package/skills/wordcell/AGENTS.md +1 -0
  60. package/skills/wordcell/SKILL.md +2 -1
  61. package/skills/wordcell/agents/openai.yaml +1 -1
  62. package/skills/wordcell/references/publish.md +127 -0
  63. package/skills/wordcell/references/query.md +22 -0
  64. package/skills/wordcell/references/refresh.md +14 -0
  65. package/src/authoring-model.ts +29 -0
  66. package/src/authoring-program.ts +38 -3
  67. package/src/authoring.ts +17 -3
  68. package/src/cli-program.ts +441 -12
  69. package/src/graph.ts +1 -1
  70. package/src/index.ts +4 -0
  71. package/src/oh/authority.ts +14 -5
  72. package/src/oh/canonical-rust.ts +127 -0
  73. package/src/oh/projection-rust.ts +63 -0
  74. package/src/oh-adoption.ts +7 -1
  75. package/src/publish-graph.ts +0 -0
  76. package/src/publish-index.ts +195 -0
  77. package/src/publish-markdown.ts +617 -0
  78. package/src/publish-model.ts +599 -0
  79. package/src/publish-nav.ts +204 -0
  80. package/src/publish-pages.ts +382 -0
  81. package/src/publish-reader/reader.css +1042 -0
  82. package/src/publish-reader/reader.ts +1017 -0
  83. package/src/publish-reader/theme.ts +97 -0
  84. package/src/publish-search.ts +418 -0
  85. package/src/publish-select.ts +419 -0
  86. package/src/publish-theme.ts +106 -0
  87. package/src/publish.ts +904 -0
  88. package/src/rerank-credentials.ts +56 -0
  89. package/src/rerank-typesafe.ts +472 -0
  90. package/src/rerank.ts +389 -0
  91. package/src/sdk.ts +157 -6
  92. package/src/serve.ts +180 -0
  93. package/src/vault.ts +23 -9
@@ -96,6 +96,14 @@ import {
96
96
  openKnowledgePortfolio,
97
97
  type PortfolioSearchResult,
98
98
  } from "./portfolio.js";
99
+ import {
100
+ DEFAULT_PUBLISH_LIST_LIMIT,
101
+ MAX_PUBLISH_LIST_LIMIT,
102
+ publishVault,
103
+ renderPublishReportText,
104
+ type PublishSelectionInput,
105
+ } from "./publish.js";
106
+ import { serveSite } from "./serve.js";
99
107
  import {
100
108
  MAX_AUTHORIZED_VAULTS,
101
109
  loadPortfolioRegistry,
@@ -114,10 +122,14 @@ import {
114
122
  type QuerySort,
115
123
  } from "./query.js";
116
124
  import {
125
+ auditRepositoryMemoryScopes,
117
126
  buildRepositoryMemoryContext,
127
+ MAX_REPOSITORY_MEMORY_DETAIL_LIMIT,
118
128
  MAX_REPOSITORY_SCOPES,
119
129
  repositoryMemoryGroupKeys,
120
130
  type RepositoryMemoryContext,
131
+ type RepositoryMemoryScopeAudit,
132
+ type RepositoryMemoryScopeAuditError,
121
133
  } from "./repository-memory.js";
122
134
  import { validateSearchQuery } from "./search.js";
123
135
  import {
@@ -140,11 +152,14 @@ import {
140
152
  MAX_SEARCH_RELATED_SEEDS,
141
153
  MAX_SEARCH_RESULTS,
142
154
  openKnowledgeBase,
155
+ searchEvidenceRank,
143
156
  type KnowledgeBaseGraphOptions,
144
157
  type KnowledgeBaseSearchMode,
145
158
  type KnowledgeBaseSearchOrdering,
146
159
  type KnowledgeBaseSearchResult,
147
160
  } from "./sdk.js";
161
+ import { MAX_RERANK_CANDIDATES, type SearchReranker } from "./rerank.js";
162
+ import { createCliTypeSafeReranker } from "./rerank-credentials.js";
148
163
  import {
149
164
  refreshVault,
150
165
  scanVault,
@@ -215,7 +230,16 @@ async function loadSearchRulesFile(path: string): Promise<SearchRulesV1> {
215
230
  return parseSearchRules(input);
216
231
  }
217
232
 
218
- export const usage = `wordcell — auditable capture and derived links for Markdown vaults
233
+ export const usage = `wordcell — a local knowledge base for coding agents
234
+
235
+ Start here (no account or model needed):
236
+ wordcell init kb
237
+ wordcell note create notes/decision --title "A decision" --body "Keep retries bounded." --root kb
238
+ wordcell search "retries" --root kb --mode exact
239
+
240
+ Already have Markdown? Search it with --root <your-notes-directory>.
241
+ Use --mode exact for model-free search; optional hybrid search needs a local index.
242
+ Quick start and examples: https://wordcell.io/docs
219
243
 
220
244
  Usage:
221
245
  wordcell init [directory] [--json]
@@ -228,7 +252,7 @@ Usage:
228
252
  wordcell inspect <url> [capture options]
229
253
  wordcell pdf <file-or-url> [PDF options]
230
254
  wordcell refresh [--root <directory>] [--index <path>] [--json]
231
- wordcell check [--root <directory>] [--index <path>] [--no-catalog] [--json]
255
+ wordcell check [--root <directory>] [--index <path>] [--no-catalog] [--repo <repository>] [--json]
232
256
  wordcell catalog [--root <directory>] [--index <path>] [--json]
233
257
  wordcell graph [--root <directory>] [--index <path>] [--json]
234
258
  wordcell graph rebuild [--fresh] [--root <directory>] [--index <path>] [--json]
@@ -243,12 +267,14 @@ Usage:
243
267
  wordcell percolate [note] [--proofs] [--root <directory>] [--min-support <count>] [--limit <count>] [--json]
244
268
  wordcell list [--root <directory>] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--sort <field>] [--order <asc|desc>] [--limit <count>] [--json]
245
269
  wordcell index [--root <directory>] [--database <path>] [--force] [--json]
246
- 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>] [--json]
270
+ 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]
247
271
  wordcell history <note> [--root <directory>] [--repo <repository>] [--limit <count>] [--cochanged-limit <count>] [--json]
248
272
  wordcell history search <query-or-path> [--root <directory>] [--repo <repository>] [--limit <count>] [--commit-limit <count>] [--cochanged-limit <count>] [--json]
249
273
  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]
250
274
  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]
251
275
  wordcell portfolio audit --registry <file> --workspace <directory> (--all | --shared | --vault <owner/id>...) [--strict] [--json]
276
+ 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]
277
+ wordcell serve --root <directory> [--host <host>] [--port <port>] [--json]
252
278
  wordcell inbox [--root <directory>] [--source-prefix <directory>] [--limit <count>] [--json]
253
279
  wordcell context <repository-path> [--root <vault>] [--repo <repository>] [--kind <auto|file|directory>] [--json]
254
280
  wordcell agents identity <repository-scope> [--json]
@@ -359,6 +385,8 @@ type ParsedCommand =
359
385
  readonly limit?: number;
360
386
  readonly candidateLimit?: number;
361
387
  readonly minScore?: number;
388
+ readonly rerank?: "typesafe";
389
+ readonly rerankLimit?: number;
362
390
  readonly query: string;
363
391
  readonly json: boolean;
364
392
  }
@@ -419,6 +447,8 @@ type ParsedCommand =
419
447
  readonly depth?: number;
420
448
  readonly limit?: number;
421
449
  readonly noCatalog?: boolean;
450
+ /** `check --repo` only: repository working tree for the advisory scope lane. */
451
+ readonly repository?: string;
422
452
  }
423
453
  | {
424
454
  readonly kind: "note-create";
@@ -446,6 +476,31 @@ type ParsedCommand =
446
476
  readonly minSupport: number;
447
477
  readonly limit: number;
448
478
  readonly json: boolean;
479
+ }
480
+ | {
481
+ readonly kind: "publish";
482
+ readonly root: string;
483
+ readonly out: string;
484
+ readonly index?: string;
485
+ readonly title?: string;
486
+ readonly description?: string;
487
+ readonly basePath?: string;
488
+ readonly baseUrl?: string;
489
+ readonly noindex: boolean;
490
+ readonly indexContent: boolean;
491
+ readonly deterministic: boolean;
492
+ readonly dryRun: boolean;
493
+ readonly listLimit: number;
494
+ readonly force: boolean;
495
+ readonly selection: PublishSelectionInput;
496
+ readonly json: boolean;
497
+ }
498
+ | {
499
+ readonly kind: "serve";
500
+ readonly root: string;
501
+ readonly host: string;
502
+ readonly port: number;
503
+ readonly json: boolean;
449
504
  };
450
505
 
451
506
  type ParseResult =
@@ -468,6 +523,8 @@ type CliDependencies = GraphCliDependencies & {
468
523
  readonly indexSemanticVault?: typeof indexSemanticVault;
469
524
  readonly openKnowledgeBase?: typeof openKnowledgeBase;
470
525
  readonly openKnowledgeBaseEvaluation?: typeof openKnowledgeBaseEvaluation;
526
+ /** Test seam; production wiring builds the TypeSafe reranker lazily per flag. */
527
+ readonly rerankers?: readonly SearchReranker[];
471
528
  readonly digestEvaluationModel?: (path: string) => Promise<string>;
472
529
  readonly evaluationNow?: () => Date;
473
530
  readonly createNote?: typeof createNote;
@@ -477,8 +534,11 @@ type CliDependencies = GraphCliDependencies & {
477
534
  readonly percolateWithGraph?: typeof percolateWithGraph;
478
535
  readonly inspectAgentContextRepository?: typeof inspectAgentContextRepository;
479
536
  readonly buildRepositoryMemoryContext?: typeof buildRepositoryMemoryContext;
537
+ readonly auditRepositoryMemoryScopes?: typeof auditRepositoryMemoryScopes;
480
538
  readonly auditAgentGuideRepository?: typeof auditAgentGuideRepository;
481
539
  readonly validateMarkdownAttachments?: typeof validateMarkdownAttachments;
540
+ readonly publishVault?: typeof publishVault;
541
+ readonly serveSite?: typeof serveSite;
482
542
  };
483
543
 
484
544
  function safe(value: string): string {
@@ -570,6 +630,7 @@ function parseVaultCommand(command: VaultCommand, arguments_: readonly string[])
570
630
  let depth = 1;
571
631
  let limit: number | undefined;
572
632
  let noCatalog = false;
633
+ let repository: string | undefined;
573
634
  const positional: string[] = [];
574
635
 
575
636
  for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
@@ -583,6 +644,13 @@ function parseVaultCommand(command: VaultCommand, arguments_: readonly string[])
583
644
  noCatalog = true;
584
645
  continue;
585
646
  }
647
+ if (argument === "--repo" && command === "check") {
648
+ const value = readValue(arguments_, cursor);
649
+ if (value === null) return { ok: false, message: "--repo requires a value" };
650
+ repository = value;
651
+ cursor += 1;
652
+ continue;
653
+ }
586
654
  if (argument === "--root" || argument === "--index") {
587
655
  const value = readValue(arguments_, cursor);
588
656
  if (value === null) return { ok: false, message: `${argument} requires a value` };
@@ -647,6 +715,7 @@ function parseVaultCommand(command: VaultCommand, arguments_: readonly string[])
647
715
  options: index === undefined ? {} : { index },
648
716
  json,
649
717
  ...(command === "check" && noCatalog ? { noCatalog: true } : {}),
718
+ ...(command === "check" && repository !== undefined ? { repository } : {}),
650
719
  },
651
720
  };
652
721
  }
@@ -866,6 +935,195 @@ function parseListCommand(arguments_: readonly string[]): ParseResult {
866
935
  };
867
936
  }
868
937
 
938
+ function parsePublishCommand(arguments_: readonly string[]): ParseResult {
939
+ let root = ".";
940
+ let out: string | undefined;
941
+ let index: string | undefined;
942
+ let title: string | undefined;
943
+ let description: string | undefined;
944
+ let basePath: string | undefined;
945
+ let baseUrl: string | undefined;
946
+ let noindex = false;
947
+ let indexContent = true;
948
+ let deterministic = false;
949
+ let dryRun = false;
950
+ let listLimit = DEFAULT_PUBLISH_LIST_LIMIT;
951
+ let force = false;
952
+ let json = false;
953
+ let from: string | undefined;
954
+ let depth = 1;
955
+ let direction: LinkDirection = "both";
956
+ const includes: string[] = [];
957
+ const excludes: string[] = [];
958
+ const includeGlobs: string[] = [];
959
+ const excludeGlobs: string[] = [];
960
+ const filters: MetadataFilter[] = [];
961
+ const tags: string[] = [];
962
+ const repositoryScopes: string[] = [];
963
+
964
+ for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
965
+ const argument = arguments_[cursor];
966
+ if (argument === undefined) continue;
967
+ if (argument === "--json") { json = true; continue; }
968
+ if (argument === "--noindex") { noindex = true; continue; }
969
+ if (argument === "--no-index-content") { indexContent = false; continue; }
970
+ if (argument === "--deterministic") { deterministic = true; continue; }
971
+ if (argument === "--dry-run") { dryRun = true; continue; }
972
+ if (argument === "--force") { force = true; continue; }
973
+ if (
974
+ argument === "--root" || argument === "--out" || argument === "--index"
975
+ || argument === "--title" || argument === "--description"
976
+ || argument === "--base-path" || argument === "--base-url"
977
+ || argument === "--include" || argument === "--exclude"
978
+ || argument === "--include-glob" || argument === "--exclude-glob"
979
+ || argument === "--list-limit"
980
+ || argument === "--where" || argument === "--has" || argument === "--tag"
981
+ || argument === "--scope" || argument === "--repository-scope"
982
+ || argument === "--from" || argument === "--depth" || argument === "--direction"
983
+ ) {
984
+ const value = readValue(arguments_, cursor);
985
+ if (value === null) return { ok: false, message: `${argument} requires a value` };
986
+ if (argument === "--root") root = value;
987
+ else if (argument === "--out") out = value;
988
+ else if (argument === "--index") index = value;
989
+ else if (argument === "--title") title = value;
990
+ else if (argument === "--description") description = value;
991
+ else if (argument === "--base-path") basePath = value;
992
+ else if (argument === "--base-url") baseUrl = value;
993
+ else if (argument === "--include") includes.push(value);
994
+ else if (argument === "--exclude") excludes.push(value);
995
+ else if (argument === "--include-glob") includeGlobs.push(value);
996
+ else if (argument === "--exclude-glob") excludeGlobs.push(value);
997
+ else if (argument === "--list-limit") {
998
+ if (!/^(0|[1-9][0-9]*)$/u.test(value) || Number(value) > MAX_PUBLISH_LIST_LIMIT) {
999
+ return { ok: false, message: `--list-limit must be an integer from 0 through ${MAX_PUBLISH_LIST_LIMIT}` };
1000
+ }
1001
+ listLimit = Number(value);
1002
+ }
1003
+ else if (argument === "--from") from = value;
1004
+ else if (argument === "--depth") {
1005
+ const parsed = Number(value);
1006
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 10) {
1007
+ return { ok: false, message: "--depth must be an integer from 1 through 10" };
1008
+ }
1009
+ depth = parsed;
1010
+ } else if (argument === "--direction") {
1011
+ if (value !== "in" && value !== "out" && value !== "both") {
1012
+ return { ok: false, message: "--direction must be in, out, or both" };
1013
+ }
1014
+ direction = value;
1015
+ } else if (argument === "--tag") {
1016
+ if (tags.length >= MAX_QUERY_TAGS) {
1017
+ return { ok: false, message: `Query tags may contain at most ${MAX_QUERY_TAGS} entries.` };
1018
+ }
1019
+ tags.push(value);
1020
+ } else if (argument === "--scope" || argument === "--repository-scope") {
1021
+ if (repositoryScopes.length >= MAX_REPOSITORY_SCOPES) {
1022
+ return { ok: false, message: `Repository scope filters may contain at most ${MAX_REPOSITORY_SCOPES} entries.` };
1023
+ }
1024
+ repositoryScopes.push(value);
1025
+ } else if (argument === "--has") {
1026
+ if (value.trim() === "") return { ok: false, message: "--has requires a metadata path" };
1027
+ if (filters.length >= MAX_QUERY_FILTERS) {
1028
+ return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
1029
+ }
1030
+ filters.push({ kind: "exists", path: value });
1031
+ } else {
1032
+ const equals = value.indexOf("=");
1033
+ const path = equals === -1 ? "" : value.slice(0, equals).trim();
1034
+ if (path === "") return { ok: false, message: "--where requires path=value" };
1035
+ const scalar = metadataScalar(value.slice(equals + 1));
1036
+ if (!scalar.ok) return scalar;
1037
+ if (filters.length >= MAX_QUERY_FILTERS) {
1038
+ return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
1039
+ }
1040
+ filters.push({ kind: "equals", path, value: scalar.value });
1041
+ }
1042
+ cursor += 1;
1043
+ continue;
1044
+ }
1045
+ return {
1046
+ ok: false,
1047
+ message: argument.startsWith("--")
1048
+ ? "unknown publish option"
1049
+ : "publish does not accept positional arguments",
1050
+ };
1051
+ }
1052
+
1053
+ if (out === undefined) return { ok: false, message: "publish requires --out <directory>" };
1054
+ try {
1055
+ validateQueryOptions({ filters, tags, repositoryScopes });
1056
+ } catch (error: unknown) {
1057
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
1058
+ }
1059
+ const selection: PublishSelectionInput = {
1060
+ includes,
1061
+ excludes,
1062
+ includeGlobs,
1063
+ excludeGlobs,
1064
+ filters,
1065
+ tags,
1066
+ repositoryScopes,
1067
+ ...(from === undefined ? {} : { from: { note: from, depth, direction } }),
1068
+ };
1069
+ return {
1070
+ ok: true,
1071
+ value: {
1072
+ kind: "publish",
1073
+ root,
1074
+ out,
1075
+ noindex,
1076
+ indexContent,
1077
+ deterministic,
1078
+ dryRun,
1079
+ listLimit,
1080
+ force,
1081
+ selection,
1082
+ json,
1083
+ ...(index === undefined ? {} : { index }),
1084
+ ...(title === undefined ? {} : { title }),
1085
+ ...(description === undefined ? {} : { description }),
1086
+ ...(basePath === undefined ? {} : { basePath }),
1087
+ ...(baseUrl === undefined ? {} : { baseUrl }),
1088
+ },
1089
+ };
1090
+ }
1091
+
1092
+ function parseServeCommand(arguments_: readonly string[]): ParseResult {
1093
+ let root: string | undefined;
1094
+ let host = "127.0.0.1";
1095
+ let port = 8080;
1096
+ let json = false;
1097
+ for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
1098
+ const argument = arguments_[cursor];
1099
+ if (argument === undefined) continue;
1100
+ if (argument === "--json") { json = true; continue; }
1101
+ if (argument === "--root" || argument === "--host" || argument === "--port") {
1102
+ const value = readValue(arguments_, cursor);
1103
+ if (value === null) return { ok: false, message: `${argument} requires a value` };
1104
+ if (argument === "--root") root = value;
1105
+ else if (argument === "--host") host = value;
1106
+ else {
1107
+ const parsed = Number(value);
1108
+ if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > 65_535) {
1109
+ return { ok: false, message: "--port must be an integer from 0 through 65535" };
1110
+ }
1111
+ port = parsed;
1112
+ }
1113
+ cursor += 1;
1114
+ continue;
1115
+ }
1116
+ return {
1117
+ ok: false,
1118
+ message: argument.startsWith("--")
1119
+ ? "unknown serve option"
1120
+ : "serve does not accept positional arguments",
1121
+ };
1122
+ }
1123
+ if (root === undefined) return { ok: false, message: "serve requires --root <directory>" };
1124
+ return { ok: true, value: { kind: "serve", root, host, port, json } };
1125
+ }
1126
+
869
1127
  function parseInboxCommand(arguments_: readonly string[]): ParseResult {
870
1128
  let root = ".";
871
1129
  let index: string | undefined;
@@ -942,6 +1200,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
942
1200
  let limit: number | undefined;
943
1201
  let candidateLimit: number | undefined;
944
1202
  let minScore: number | undefined;
1203
+ let rerank: "typesafe" | undefined;
1204
+ let rerankLimit: number | undefined;
945
1205
  let graphDepth: number | undefined;
946
1206
  let noGraph = false;
947
1207
  let history = false;
@@ -1003,6 +1263,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1003
1263
  || argument === "--limit"
1004
1264
  || argument === "--candidate-limit"
1005
1265
  || argument === "--min-score"
1266
+ || argument === "--rerank"
1267
+ || argument === "--rerank-limit"
1006
1268
  || argument === "--where"
1007
1269
  || argument === "--has"
1008
1270
  || argument === "--tag"
@@ -1018,6 +1280,17 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1018
1280
  return { ok: false, message: "--mode must be hybrid, exact, keyword, or semantic" };
1019
1281
  }
1020
1282
  mode = value;
1283
+ } else if (argument === "--rerank") {
1284
+ if (value !== "typesafe") {
1285
+ return { ok: false, message: "--rerank must be typesafe" };
1286
+ }
1287
+ rerank = value;
1288
+ } else if (argument === "--rerank-limit") {
1289
+ const parsed = Number(value);
1290
+ if (!Number.isSafeInteger(parsed) || parsed < 2 || parsed > MAX_RERANK_CANDIDATES) {
1291
+ return { ok: false, message: `--rerank-limit must be an integer from 2 through ${MAX_RERANK_CANDIDATES}` };
1292
+ }
1293
+ rerankLimit = parsed;
1021
1294
  } else if (argument === "--where") {
1022
1295
  const equals = value.indexOf("=");
1023
1296
  const path = equals === -1 ? "" : value.slice(0, equals).trim();
@@ -1147,6 +1420,9 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1147
1420
  message: "Search minimum score applies only to hybrid, keyword, or semantic mode.",
1148
1421
  };
1149
1422
  }
1423
+ if (rerankLimit !== undefined && rerank === undefined) {
1424
+ return { ok: false, message: "--rerank-limit requires --rerank typesafe" };
1425
+ }
1150
1426
  if (priority && rulesPath === undefined) {
1151
1427
  return { ok: false, message: "--priority requires --rules" };
1152
1428
  }
@@ -1185,6 +1461,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1185
1461
  ...(limit === undefined ? {} : { limit }),
1186
1462
  ...(candidateLimit === undefined ? {} : { candidateLimit }),
1187
1463
  ...(minScore === undefined ? {} : { minScore }),
1464
+ ...(rerank === undefined ? {} : { rerank }),
1465
+ ...(rerankLimit === undefined ? {} : { rerankLimit }),
1188
1466
  query,
1189
1467
  json,
1190
1468
  },
@@ -1938,6 +2216,8 @@ export function parseArguments(arguments_: readonly string[]): ParseResult {
1938
2216
  if (command === "note") return parseNoteCommand(arguments_.slice(1));
1939
2217
  if (command === "relation") return parseRelationCommand(arguments_.slice(1));
1940
2218
  if (command === "percolate") return parsePercolateCommand(arguments_.slice(1));
2219
+ if (command === "publish") return parsePublishCommand(arguments_.slice(1));
2220
+ if (command === "serve") return parseServeCommand(arguments_.slice(1));
1941
2221
  return { ok: false, message: "unknown command" };
1942
2222
  }
1943
2223
 
@@ -1960,10 +2240,28 @@ function renderKnowledgeBaseSearch(result: KnowledgeBaseSearchResult): string {
1960
2240
  const lines = [
1961
2241
  `${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for “${safe(result.query)}” (${result.results.length})${result.partial ? " [partial]" : ""}`,
1962
2242
  ];
2243
+ const rerankLane = result.diagnostics.lanes.find(({ lane }) => lane === "rerank");
2244
+ if (rerankLane !== undefined) {
2245
+ lines.push(
2246
+ ` Rerank: typesafe over ${rerankLane.results} candidates (${rerankLane.status})`
2247
+ + (rerankLane.message === undefined ? "" : ` — ${safe(rerankLane.message)}`),
2248
+ );
2249
+ const receipt = rerankLane.rerank;
2250
+ if (receipt?.accounting !== undefined) {
2251
+ const { attempted, candidates, completed, elapsedMs, usageComplete } = receipt.accounting;
2252
+ lines.push(` Rerank requests: ${attempted}/${candidates} attempted, ${completed} settled; ${elapsedMs.toFixed(0)} ms`
2253
+ + (usageComplete ? "; usage complete" : "; usage incomplete, additional charges may be unknown"));
2254
+ if (rerankLane.status !== "ready" && receipt.usage !== undefined) {
2255
+ lines.push(` Known rerank usage: ${receipt.usage.inputTokens ?? 0} input tokens, ${receipt.usage.outputTokens ?? 0} output tokens`);
2256
+ }
2257
+ }
2258
+ }
1963
2259
  if (result.results.length === 0) lines.push(" None.");
1964
2260
  for (const hit of result.results) {
1965
2261
  const location = `${safe(hit.path)}${hit.line === undefined ? "" : `:${hit.line}`}`;
1966
- const evidence = hit.evidence.map((item) => `${item.kind}#${item.rank}`).join(", ");
2262
+ const evidence = hit.evidence
2263
+ .map((item) => `${item.kind}#${searchEvidenceRank(item)}`)
2264
+ .join(", ");
1967
2265
  lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} — ${safe(hit.title)} [${safe(evidence)}]`);
1968
2266
  if (hit.snippet !== "") lines.push(` ${safe(hit.snippet)}`);
1969
2267
  }
@@ -1999,12 +2297,17 @@ async function runSemantic(
1999
2297
  const searchRules = command.rulesPath === undefined
2000
2298
  ? undefined
2001
2299
  : await loadSearchRulesFile(command.rulesPath);
2002
- const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)({
2003
- root: command.root,
2004
- repository: command.repository,
2005
- ...(command.database === undefined ? {} : { database: command.database }),
2006
- ...(searchRules === undefined ? {} : { searchRules }),
2007
- });
2300
+ const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)(
2301
+ {
2302
+ root: command.root,
2303
+ repository: command.repository,
2304
+ ...(command.database === undefined ? {} : { database: command.database }),
2305
+ ...(searchRules === undefined ? {} : { searchRules }),
2306
+ },
2307
+ ...(command.rerank === undefined
2308
+ ? []
2309
+ : [{ rerankers: dependencies.rerankers ?? [await createCliTypeSafeReranker()] }]),
2310
+ );
2008
2311
  try {
2009
2312
  const result = await kb.search({
2010
2313
  query: command.query,
@@ -2020,6 +2323,12 @@ async function runSemantic(
2020
2323
  ? {}
2021
2324
  : { candidateLimit: command.candidateLimit }),
2022
2325
  ...(command.minScore === undefined ? {} : { minScore: command.minScore }),
2326
+ ...(command.rerank === undefined ? {} : {
2327
+ rerank: {
2328
+ engine: command.rerank,
2329
+ ...(command.rerankLimit === undefined ? {} : { limit: command.rerankLimit }),
2330
+ },
2331
+ }),
2023
2332
  });
2024
2333
  output.stdout(command.json
2025
2334
  ? terminalSafeJson(result)
@@ -2344,6 +2653,7 @@ function summary(
2344
2653
  options: {
2345
2654
  readonly noCatalog?: boolean;
2346
2655
  readonly attachments?: AttachmentValidationReport;
2656
+ readonly repositoryScopes?: RepositoryMemoryScopeAudit;
2347
2657
  } = {},
2348
2658
  ): Record<string, unknown> {
2349
2659
  return {
@@ -2369,6 +2679,9 @@ function summary(
2369
2679
  issues: options.attachments.issues,
2370
2680
  },
2371
2681
  }),
2682
+ ...(options.repositoryScopes === undefined
2683
+ ? {}
2684
+ : { repositoryScopes: options.repositoryScopes }),
2372
2685
  };
2373
2686
  }
2374
2687
 
@@ -2404,6 +2717,60 @@ function renderAdvisories(analysis: VaultAnalysis): string[] {
2404
2717
  return lines;
2405
2718
  }
2406
2719
 
2720
+ function renderRepositoryScopeAuditError(error: RepositoryMemoryScopeAuditError): string {
2721
+ if (error.kind === "invalid-record") {
2722
+ return `${safe(error.path)}: ${error.issues.map(safe).join(" ")}`;
2723
+ }
2724
+ const declared = error.recordPaths.map(safe).join(", ")
2725
+ + (error.recordPathsTruncated ? ", …" : "");
2726
+ return `${safe(error.scope)}: ${safe(error.state.reason)} at ${safe(error.state.path)}`
2727
+ + ` (declared by ${declared})`;
2728
+ }
2729
+
2730
+ /**
2731
+ * Advisory repository-state lane for `check --repo`.
2732
+ *
2733
+ * Every line derives from authored `repository_scopes` declarations and the
2734
+ * working tree they name. Nothing here changes the exit code: a current note
2735
+ * that names a path the repository no longer has is reported for a reader to
2736
+ * judge, never treated as a vault correctness failure.
2737
+ */
2738
+ function renderRepositoryScopeAudit(audit: RepositoryMemoryScopeAudit): string[] {
2739
+ const { counts } = audit;
2740
+ const lines = [
2741
+ `Repository scopes: ${counts.authoredRecords} declaring note${counts.authoredRecords === 1 ? "" : "s"}; `
2742
+ + `${counts.distinctScopes} distinct scope${counts.distinctScopes === 1 ? "" : "s"} `
2743
+ + `(${counts.presentScopes} present, ${counts.absentScopes} absent, ${counts.invalidScopes} unusable).`,
2744
+ ];
2745
+ if (audit.advisories.total > 0) {
2746
+ lines.push(audit.advisories.total === 1
2747
+ ? "Advisory: 1 current note declares a repository path that no longer exists."
2748
+ : `Advisory: ${audit.advisories.total} current notes declare repository paths that no longer exist.`);
2749
+ for (const advisory of audit.advisories.details) {
2750
+ lines.push(` ${safe(advisory.path)} declares absent scope ${safe(advisory.scope)}`);
2751
+ }
2752
+ if (audit.advisories.truncated) {
2753
+ lines.push(
2754
+ ` … ${audit.advisories.total - audit.advisories.returned} more; rerun with --json for the complete audit.`,
2755
+ );
2756
+ }
2757
+ }
2758
+ if (audit.errors.total > 0) {
2759
+ lines.push(
2760
+ `Advisory: ${audit.errors.total} repository scope declaration problem${audit.errors.total === 1 ? "" : "s"}.`,
2761
+ );
2762
+ for (const error of audit.errors.details) {
2763
+ lines.push(` ${renderRepositoryScopeAuditError(error)}`);
2764
+ }
2765
+ if (audit.errors.truncated) {
2766
+ lines.push(
2767
+ ` … ${audit.errors.total - audit.errors.returned} more; rerun with --json for the complete audit.`,
2768
+ );
2769
+ }
2770
+ }
2771
+ return lines;
2772
+ }
2773
+
2407
2774
  function checkExitCode(
2408
2775
  snapshot: VaultSnapshot,
2409
2776
  noCatalog = false,
@@ -2430,6 +2797,7 @@ function renderSnapshot(
2430
2797
  snapshot: VaultSnapshot,
2431
2798
  noCatalog = false,
2432
2799
  attachments?: AttachmentValidationReport,
2800
+ repositoryScopes?: RepositoryMemoryScopeAudit,
2433
2801
  ): string {
2434
2802
  const lines = [
2435
2803
  `${command === "refresh" ? "Refreshed" : "Checked"} ${safe(snapshot.root)}`,
@@ -2449,6 +2817,7 @@ function renderSnapshot(
2449
2817
  lines.push("error: attachment validation was truncated by a resource limit");
2450
2818
  }
2451
2819
  lines.push(...renderAdvisories(snapshot.analysis));
2820
+ if (repositoryScopes !== undefined) lines.push(...renderRepositoryScopeAudit(repositoryScopes));
2452
2821
  return `${lines.join("\n")}\n`;
2453
2822
  }
2454
2823
 
@@ -2834,6 +3203,49 @@ async function runList(
2834
3203
  return 0;
2835
3204
  }
2836
3205
 
3206
+ async function runPublish(
3207
+ command: Extract<ParsedCommand, { readonly kind: "publish" }>,
3208
+ output: Output,
3209
+ dependencies: CliDependencies,
3210
+ ): Promise<number> {
3211
+ const result = await (dependencies.publishVault ?? publishVault)({
3212
+ root: command.root,
3213
+ out: command.out,
3214
+ noindex: command.noindex,
3215
+ indexContent: command.indexContent,
3216
+ deterministic: command.deterministic,
3217
+ dryRun: command.dryRun,
3218
+ listLimit: command.listLimit,
3219
+ force: command.force,
3220
+ selection: command.selection,
3221
+ ...(command.index === undefined ? {} : { index: command.index }),
3222
+ ...(command.title === undefined ? {} : { title: command.title }),
3223
+ ...(command.description === undefined ? {} : { description: command.description }),
3224
+ ...(command.basePath === undefined ? {} : { basePath: command.basePath }),
3225
+ ...(command.baseUrl === undefined ? {} : { baseUrl: command.baseUrl }),
3226
+ });
3227
+ output.stdout(command.json
3228
+ ? terminalSafeJson(result.report)
3229
+ : sanitizeTerminalText(renderPublishReportText(result.report, command.dryRun)));
3230
+ return 0;
3231
+ }
3232
+
3233
+ async function runServe(
3234
+ command: Extract<ParsedCommand, { readonly kind: "serve" }>,
3235
+ output: Output,
3236
+ dependencies: CliDependencies,
3237
+ ): Promise<number> {
3238
+ const site = await (dependencies.serveSite ?? serveSite)({
3239
+ root: command.root,
3240
+ host: command.host,
3241
+ port: command.port,
3242
+ });
3243
+ output.stdout(command.json
3244
+ ? terminalSafeJson({ root: site.root, host: site.host, port: site.port, url: site.url })
3245
+ : `Serving ${safe(site.root)} at ${safe(site.url)}\n`);
3246
+ return 0;
3247
+ }
3248
+
2837
3249
  async function runCatalog(
2838
3250
  command: Extract<ParsedCommand, { readonly kind: "catalog" }>,
2839
3251
  output: Output,
@@ -3470,9 +3882,24 @@ async function runVault(
3470
3882
  documents: snapshot.notes.map(({ path, content }) => ({ path, content })),
3471
3883
  })
3472
3884
  : undefined;
3885
+ const repositoryScopes = command.kind === "check" && command.repository !== undefined
3886
+ ? await (dependencies.auditRepositoryMemoryScopes ?? auditRepositoryMemoryScopes)(
3887
+ snapshot.notes,
3888
+ {
3889
+ repositoryRoot: command.repository,
3890
+ detailLimit: MAX_REPOSITORY_MEMORY_DETAIL_LIMIT,
3891
+ },
3892
+ )
3893
+ : undefined;
3473
3894
  output.stdout(command.json
3474
- ? terminalSafeJson(summary(snapshot, { noCatalog, ...(attachments === undefined ? {} : { attachments }) }))
3475
- : sanitizeTerminalText(renderSnapshot(command.kind, snapshot, noCatalog, attachments)));
3895
+ ? terminalSafeJson(summary(snapshot, {
3896
+ noCatalog,
3897
+ ...(attachments === undefined ? {} : { attachments }),
3898
+ ...(repositoryScopes === undefined ? {} : { repositoryScopes }),
3899
+ }))
3900
+ : sanitizeTerminalText(
3901
+ renderSnapshot(command.kind, snapshot, noCatalog, attachments, repositoryScopes),
3902
+ ));
3476
3903
  return checkExitCode(snapshot, noCatalog, attachments);
3477
3904
  }
3478
3905
  if (command.kind === "graph") {
@@ -3580,6 +4007,8 @@ export async function main(
3580
4007
  }
3581
4008
  if (command.kind === "percolate") return await runPercolate(command, output, dependencies);
3582
4009
  if (command.kind === "list") return await runList(command, output, dependencies);
4010
+ if (command.kind === "publish") return await runPublish(command, output, dependencies);
4011
+ if (command.kind === "serve") return await runServe(command, output, dependencies);
3583
4012
  if (command.kind === "inbox") return await runInbox(command, output, dependencies);
3584
4013
  if (command.kind === "catalog") return await runCatalog(command, output, dependencies);
3585
4014
  return await runVault(command, output, dependencies);