@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
@@ -3,22 +3,28 @@ import {
3
3
  backfillSavedUrlMetadata,
4
4
  createRustMetadataSearchProvider
5
5
  } from "./index-bcknqxrq.js";
6
+ import {
7
+ main as main2
8
+ } from "./index-054mb7d3.js";
6
9
  import {
7
10
  MAX_AUTHORIZED_VAULTS,
8
11
  auditKnowledgePortfolio,
9
12
  loadPortfolioRegistry,
10
13
  openKnowledgePortfolio,
11
14
  snapshotPortfolioRegistry
12
- } from "./index-51bap0mq.js";
13
- import {
14
- main as main2
15
- } from "./index-054mb7d3.js";
15
+ } from "./index-bqtqeak3.js";
16
16
  import {
17
17
  diffCaptureBundle
18
18
  } from "./index-j4zgmzjr.js";
19
+ import {
20
+ main
21
+ } from "./index-de2w8crk.js";
19
22
  import {
20
23
  verifyCaptureBundle
21
24
  } from "./index-npg9z1a4.js";
25
+ import {
26
+ redactSensitiveText
27
+ } from "./index-mxxxytys.js";
22
28
  import {
23
29
  initVault
24
30
  } from "./index-23z4zxgg.js";
@@ -27,18 +33,22 @@ import {
27
33
  MAX_SOURCE_INBOX_RESULTS,
28
34
  sourceInbox
29
35
  } from "./index-pj501bh1.js";
36
+ import {
37
+ createTypeSafeReranker
38
+ } from "./index-9rf81m0p.js";
30
39
  import {
31
40
  knowledgeBaseEvaluationRetrieverIds,
32
41
  openKnowledgeBaseEvaluation
33
- } from "./index-pbr6rv47.js";
42
+ } from "./index-1k595z6v.js";
34
43
  import {
35
44
  DEFAULT_SEARCH_RESULTS,
36
45
  MAX_SEARCH_CANDIDATES,
37
46
  MAX_SEARCH_NOTE_REFERENCE_BYTES,
38
47
  MAX_SEARCH_RELATED_SEEDS,
39
48
  MAX_SEARCH_RESULTS,
40
- openKnowledgeBase
41
- } from "./index-vdw5yjfa.js";
49
+ openKnowledgeBase,
50
+ searchEvidenceRank
51
+ } from "./index-q7tbg07z.js";
42
52
  import {
43
53
  MAX_SEARCH_RULE_CONFIG_BYTES,
44
54
  parseSearchRules
@@ -49,50 +59,55 @@ import {
49
59
  recommendedEmbeddingModel,
50
60
  recommendedEmbeddingModelSha256,
51
61
  sha256EmbeddingModelFile
52
- } from "./index-115b07ap.js";
62
+ } from "./index-rr9kzq4n.js";
53
63
  import {
54
- percolateWithGraph
55
- } from "./index-bgfzwt4h.js";
64
+ MAX_RERANK_CANDIDATES
65
+ } from "./index-j70m75wd.js";
56
66
  import {
57
- queryGraph,
58
- rebuildGraph,
59
- verifyGraph
60
- } from "./index-bcfn9xah.js";
61
- import {
62
- validateGraphQueryRequest
63
- } from "./index-11621h23.js";
67
+ MAX_EVALUATION_RESULTS_PER_QUERY,
68
+ MAX_EVALUATION_TIMEOUT_MS,
69
+ buildRetrievalEvaluationReport,
70
+ parseRetrievalEvaluationCorpus,
71
+ runRetrievalEvaluation
72
+ } from "./index-b88v3vtm.js";
64
73
  import {
65
- refreshVault,
66
- scanVault
67
- } from "./index-0k2x4nn9.js";
74
+ percolateWithGraph
75
+ } from "./index-djrcc1yf.js";
68
76
  import {
69
77
  MAX_PERCOLATION_MENTIONS,
70
78
  MAX_PERCOLATION_MENTION_PAIRS,
71
79
  MAX_PERCOLATION_NOTES,
72
80
  MAX_SCOPED_PERCOLATION_MENTION_PAIRS,
73
81
  percolateVault
74
- } from "./index-nd6nynv2.js";
82
+ } from "./index-jk1sgx36.js";
75
83
  import {
76
- MAX_EVALUATION_RESULTS_PER_QUERY,
77
- MAX_EVALUATION_TIMEOUT_MS,
78
- buildRetrievalEvaluationReport,
79
- parseRetrievalEvaluationCorpus,
80
- runRetrievalEvaluation
81
- } from "./index-b88v3vtm.js";
84
+ queryGraph,
85
+ rebuildGraph,
86
+ verifyGraph
87
+ } from "./index-xv80vzfp.js";
82
88
  import {
83
- auditAgentGuideRepository
84
- } from "./index-hya40gb2.js";
89
+ validateGraphQueryRequest
90
+ } from "./index-tf4wzjew.js";
85
91
  import {
86
- validateMarkdownAttachments
87
- } from "./index-x3fthpsc.js";
92
+ validateSearchQuery
93
+ } from "./index-vnybgywh.js";
88
94
  import {
89
- addNoteRelation,
90
- createNote,
91
- removeNoteRelation
92
- } from "./index-6sw24nvv.js";
95
+ sanitizeTerminalLine,
96
+ sanitizeTerminalText
97
+ } from "./index-1xxnjn0d.js";
93
98
  import {
94
- validateSearchQuery
95
- } from "./index-why54q5p.js";
99
+ DEFAULT_PUBLISH_LIST_LIMIT,
100
+ MAX_PUBLISH_LIST_LIMIT,
101
+ publishVault,
102
+ renderPublishReportText
103
+ } from "./index-hmw17zaa.js";
104
+ import {
105
+ refreshVault,
106
+ scanVault
107
+ } from "./index-233z9wmn.js";
108
+ import {
109
+ navigateLinks
110
+ } from "./index-d13v9ckt.js";
96
111
  import {
97
112
  MAX_QUERY_FILTERS,
98
113
  MAX_QUERY_TAGS,
@@ -100,13 +115,18 @@ import {
100
115
  validateQueryOptions
101
116
  } from "./index-48pz4jpc.js";
102
117
  import {
118
+ MAX_REPOSITORY_MEMORY_DETAIL_LIMIT,
103
119
  MAX_REPOSITORY_SCOPES,
120
+ auditRepositoryMemoryScopes,
104
121
  buildRepositoryMemoryContext,
105
122
  repositoryMemoryGroupKeys
106
123
  } from "./index-06c9ctr6.js";
107
124
  import {
108
- navigateLinks
109
- } from "./index-d13v9ckt.js";
125
+ findKbPackageRoot
126
+ } from "./index-4knsp9qj.js";
127
+ import {
128
+ auditAgentGuideRepository
129
+ } from "./index-hya40gb2.js";
110
130
  import {
111
131
  agentContextGuidePath,
112
132
  agentContextMarkerForScope,
@@ -116,24 +136,19 @@ import {
116
136
  inspectAgentContextRepository,
117
137
  normalizeRepositoryScope
118
138
  } from "./index-5vwpzb5a.js";
139
+ import {
140
+ validateMarkdownAttachments
141
+ } from "./index-x3fthpsc.js";
142
+ import {
143
+ addNoteRelation,
144
+ createNote,
145
+ removeNoteRelation
146
+ } from "./index-ecqpgr5y.js";
119
147
  import {
120
148
  lookupNote,
121
149
  parseVaultKey,
122
150
  renderCatalog
123
- } from "./index-ekpwvbra.js";
124
- import {
125
- main
126
- } from "./index-9zcpdbjg.js";
127
- import {
128
- findKbPackageRoot
129
- } from "./index-jzcws0zn.js";
130
- import {
131
- redactSensitiveText
132
- } from "./index-mxxxytys.js";
133
- import {
134
- sanitizeTerminalLine,
135
- sanitizeTerminalText
136
- } from "./index-1xxnjn0d.js";
151
+ } from "./index-qbssx940.js";
137
152
 
138
153
  // src/clip/url-metadata-cli.ts
139
154
  import { resolve as resolve2 } from "path";
@@ -800,7 +815,7 @@ function terminalIntro(terminal) {
800
815
  }
801
816
 
802
817
  // src/cli-program.ts
803
- import { open } from "fs/promises";
818
+ import { open as open2 } from "fs/promises";
804
819
  import { cpus, release, totalmem } from "os";
805
820
  import { relative, resolve as resolve3 } from "path";
806
821
  import { format } from "util";
@@ -905,13 +920,197 @@ Snapshot: ${value.revision}
905
920
  };
906
921
  }
907
922
 
923
+ // src/serve.ts
924
+ import { realpath, stat } from "fs/promises";
925
+ import { join as join2, sep } from "path";
926
+ var MAX_PATH_BYTES = 8 * 1024;
927
+ var CONTENT_TYPES = Object.freeze({
928
+ ".avif": "image/avif",
929
+ ".css": "text/css; charset=utf-8",
930
+ ".gif": "image/gif",
931
+ ".html": "text/html; charset=utf-8",
932
+ ".ico": "image/x-icon",
933
+ ".jpeg": "image/jpeg",
934
+ ".jpg": "image/jpeg",
935
+ ".js": "text/javascript; charset=utf-8",
936
+ ".json": "application/json; charset=utf-8",
937
+ ".map": "application/json; charset=utf-8",
938
+ ".mp3": "audio/mpeg",
939
+ ".mp4": "video/mp4",
940
+ ".pdf": "application/pdf",
941
+ ".png": "image/png",
942
+ ".svg": "image/svg+xml",
943
+ ".txt": "text/plain; charset=utf-8",
944
+ ".wasm": "application/wasm",
945
+ ".wav": "audio/wav",
946
+ ".webm": "video/webm",
947
+ ".webp": "image/webp",
948
+ ".woff": "font/woff",
949
+ ".woff2": "font/woff2",
950
+ ".xml": "application/xml; charset=utf-8"
951
+ });
952
+ function contentType(path) {
953
+ const extension = path.slice(path.lastIndexOf("."));
954
+ return CONTENT_TYPES[extension] ?? "application/octet-stream";
955
+ }
956
+ async function regularFileUnder(rootReal, candidate) {
957
+ let resolved;
958
+ try {
959
+ resolved = await realpath(candidate);
960
+ } catch {
961
+ return null;
962
+ }
963
+ if (resolved !== rootReal && !resolved.startsWith(`${rootReal}${sep}`))
964
+ return null;
965
+ try {
966
+ return (await stat(resolved)).isFile() ? resolved : null;
967
+ } catch {
968
+ return null;
969
+ }
970
+ }
971
+ async function resolveRequest(rootReal, pathname) {
972
+ let decoded;
973
+ try {
974
+ decoded = decodeURIComponent(pathname);
975
+ } catch {
976
+ return null;
977
+ }
978
+ if (decoded.includes("\x00") || decoded.includes("\\"))
979
+ return null;
980
+ const segments = decoded.split("/").filter((segment) => segment !== "" && segment !== ".");
981
+ if (segments.some((segment) => segment === ".."))
982
+ return null;
983
+ const relative = segments.join("/");
984
+ const candidates = [
985
+ join2(rootReal, relative),
986
+ join2(rootReal, relative, "index.html")
987
+ ];
988
+ for (const candidate of candidates) {
989
+ const file = await regularFileUnder(rootReal, candidate);
990
+ if (file !== null)
991
+ return file;
992
+ }
993
+ return null;
994
+ }
995
+ async function createServeHandler(root) {
996
+ const rootReal = await realpath(root);
997
+ return async (request) => {
998
+ const method = request.method.toUpperCase();
999
+ if (method !== "GET" && method !== "HEAD") {
1000
+ return new Response("Method not allowed", { status: 405 });
1001
+ }
1002
+ let pathname;
1003
+ try {
1004
+ pathname = new URL(request.url).pathname;
1005
+ } catch {
1006
+ return new Response("Bad request", { status: 400 });
1007
+ }
1008
+ if (pathname.length > MAX_PATH_BYTES) {
1009
+ return new Response("URI too long", { status: 414 });
1010
+ }
1011
+ const head = method === "HEAD";
1012
+ const file = await resolveRequest(rootReal, pathname);
1013
+ if (file !== null) {
1014
+ const body = Bun.file(file);
1015
+ return new Response(head ? null : body, {
1016
+ headers: {
1017
+ "Cache-Control": "no-store",
1018
+ "Content-Length": String(body.size),
1019
+ "Content-Type": contentType(file)
1020
+ }
1021
+ });
1022
+ }
1023
+ const notFound = await regularFileUnder(rootReal, join2(rootReal, "404.html"));
1024
+ if (notFound !== null) {
1025
+ const body = Bun.file(notFound);
1026
+ return new Response(head ? null : body, {
1027
+ status: 404,
1028
+ headers: {
1029
+ "Cache-Control": "no-store",
1030
+ "Content-Length": String(body.size),
1031
+ "Content-Type": "text/html; charset=utf-8"
1032
+ }
1033
+ });
1034
+ }
1035
+ return new Response(head ? null : "Not found", {
1036
+ status: 404,
1037
+ headers: { "Cache-Control": "no-store" }
1038
+ });
1039
+ };
1040
+ }
1041
+ async function serveSite(options, io = {}) {
1042
+ const host = options.host ?? "127.0.0.1";
1043
+ const port = options.port ?? 8080;
1044
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65535) {
1045
+ throw new Error("Port must be an integer from 0 through 65535");
1046
+ }
1047
+ const rootReal = await realpath(options.root);
1048
+ if (!(await stat(rootReal)).isDirectory()) {
1049
+ throw new Error(`Site root is not a directory: ${options.root}`);
1050
+ }
1051
+ const listen = io.listen ?? ((init) => Bun.serve(init));
1052
+ const server = listen({
1053
+ hostname: host,
1054
+ port,
1055
+ fetch: await createServeHandler(rootReal)
1056
+ });
1057
+ const boundPort = server.port ?? port;
1058
+ return Object.freeze({
1059
+ root: rootReal,
1060
+ host,
1061
+ port: boundPort,
1062
+ url: `http://${host}:${String(boundPort)}/`,
1063
+ close: () => server.stop()
1064
+ });
1065
+ }
1066
+
1067
+ // src/rerank-credentials.ts
1068
+ import { constants as constants2 } from "fs";
1069
+ import { open } from "fs/promises";
1070
+ import { homedir } from "os";
1071
+ import { isAbsolute as isAbsolute2, join as join3 } from "path";
1072
+ function unavailable(message) {
1073
+ return { id: "typesafe", rerank: async () => ({ status: "unavailable", message }) };
1074
+ }
1075
+ async function createCliTypeSafeReranker(environment = process.env, homeDirectory = homedir()) {
1076
+ if (Object.hasOwn(environment, "TYPESAFE_API_KEY")) {
1077
+ return createTypeSafeReranker({ environment: { TYPESAFE_API_KEY: environment["TYPESAFE_API_KEY"] } });
1078
+ }
1079
+ const explicitFile = environment["TYPESAFE_API_KEY_FILE"];
1080
+ const configDirectory = environment["XDG_CONFIG_HOME"];
1081
+ if (explicitFile !== undefined && !isAbsolute2(explicitFile) || configDirectory !== undefined && configDirectory !== "" && !isAbsolute2(configDirectory)) {
1082
+ return unavailable("TypeSafe credential file configuration requires absolute paths.");
1083
+ }
1084
+ const path = explicitFile ?? join3(configDirectory || join3(homeDirectory, ".config"), "wordcell", "typesafe-api-key");
1085
+ let handle;
1086
+ try {
1087
+ handle = await open(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
1088
+ const metadata = await handle.stat();
1089
+ const uid = process.getuid?.();
1090
+ if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size < 1 || metadata.size > 514 || process.platform !== "win32" && ((metadata.mode & 63) !== 0 || metadata.uid !== uid)) {
1091
+ return unavailable("TypeSafe credential file must be an owner-only regular file containing one API key.");
1092
+ }
1093
+ const bytes = Buffer.alloc(515);
1094
+ const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
1095
+ const key = bytes.subarray(0, bytesRead).toString("utf8").replace(/\r?\n$/u, "");
1096
+ if (!/^[\x21-\x7e]{1,512}$/u.test(key)) {
1097
+ return unavailable("TypeSafe credential file must contain one valid API key.");
1098
+ }
1099
+ return createTypeSafeReranker({ environment: { TYPESAFE_API_KEY: key } });
1100
+ } catch {
1101
+ return unavailable("TypeSafe credentials are unavailable; set TYPESAFE_API_KEY or an owner-only TYPESAFE_API_KEY_FILE.");
1102
+ } finally {
1103
+ await handle?.close();
1104
+ }
1105
+ }
1106
+
908
1107
  // src/cli-program.ts
909
1108
  var defaultOutput2 = {
910
1109
  stdout: (value) => process.stdout.write(value),
911
1110
  stderr: (value) => process.stderr.write(value)
912
1111
  };
913
1112
  async function readBoundedUtf8(path, maximumBytes, label) {
914
- const handle = await open(path, "r");
1113
+ const handle = await open2(path, "r");
915
1114
  try {
916
1115
  const bytes = new Uint8Array(maximumBytes + 1);
917
1116
  let offset = 0;
@@ -943,7 +1142,16 @@ async function loadSearchRulesFile(path) {
943
1142
  }
944
1143
  return parseSearchRules(input);
945
1144
  }
946
- var usage = `wordcell \u2014 auditable capture and derived links for Markdown vaults
1145
+ var usage = `wordcell \u2014 a local knowledge base for coding agents
1146
+
1147
+ Start here (no account or model needed):
1148
+ wordcell init kb
1149
+ wordcell note create notes/decision --title "A decision" --body "Keep retries bounded." --root kb
1150
+ wordcell search "retries" --root kb --mode exact
1151
+
1152
+ Already have Markdown? Search it with --root <your-notes-directory>.
1153
+ Use --mode exact for model-free search; optional hybrid search needs a local index.
1154
+ Quick start and examples: https://wordcell.io/docs
947
1155
 
948
1156
  Usage:
949
1157
  wordcell init [directory] [--json]
@@ -956,7 +1164,7 @@ Usage:
956
1164
  wordcell inspect <url> [capture options]
957
1165
  wordcell pdf <file-or-url> [PDF options]
958
1166
  wordcell refresh [--root <directory>] [--index <path>] [--json]
959
- wordcell check [--root <directory>] [--index <path>] [--no-catalog] [--json]
1167
+ wordcell check [--root <directory>] [--index <path>] [--no-catalog] [--repo <repository>] [--json]
960
1168
  wordcell catalog [--root <directory>] [--index <path>] [--json]
961
1169
  wordcell graph [--root <directory>] [--index <path>] [--json]
962
1170
  wordcell graph rebuild [--fresh] [--root <directory>] [--index <path>] [--json]
@@ -971,12 +1179,14 @@ Usage:
971
1179
  wordcell percolate [note] [--proofs] [--root <directory>] [--min-support <count>] [--limit <count>] [--json]
972
1180
  wordcell list [--root <directory>] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--sort <field>] [--order <asc|desc>] [--limit <count>] [--json]
973
1181
  wordcell index [--root <directory>] [--database <path>] [--force] [--json]
974
- 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]
1182
+ 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]
975
1183
  wordcell history <note> [--root <directory>] [--repo <repository>] [--limit <count>] [--cochanged-limit <count>] [--json]
976
1184
  wordcell history search <query-or-path> [--root <directory>] [--repo <repository>] [--limit <count>] [--commit-limit <count>] [--cochanged-limit <count>] [--json]
977
1185
  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]
978
1186
  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]
979
1187
  wordcell portfolio audit --registry <file> --workspace <directory> (--all | --shared | --vault <owner/id>...) [--strict] [--json]
1188
+ 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]
1189
+ wordcell serve --root <directory> [--host <host>] [--port <port>] [--json]
980
1190
  wordcell inbox [--root <directory>] [--source-prefix <directory>] [--limit <count>] [--json]
981
1191
  wordcell context <repository-path> [--root <vault>] [--repo <repository>] [--kind <auto|file|directory>] [--json]
982
1192
  wordcell agents identity <repository-scope> [--json]
@@ -1084,6 +1294,7 @@ function parseVaultCommand(command, arguments_) {
1084
1294
  let depth = 1;
1085
1295
  let limit;
1086
1296
  let noCatalog = false;
1297
+ let repository;
1087
1298
  const positional = [];
1088
1299
  for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1089
1300
  const argument = arguments_[cursor];
@@ -1097,6 +1308,14 @@ function parseVaultCommand(command, arguments_) {
1097
1308
  noCatalog = true;
1098
1309
  continue;
1099
1310
  }
1311
+ if (argument === "--repo" && command === "check") {
1312
+ const value = readValue(arguments_, cursor);
1313
+ if (value === null)
1314
+ return { ok: false, message: "--repo requires a value" };
1315
+ repository = value;
1316
+ cursor += 1;
1317
+ continue;
1318
+ }
1100
1319
  if (argument === "--root" || argument === "--index") {
1101
1320
  const value = readValue(arguments_, cursor);
1102
1321
  if (value === null)
@@ -1163,7 +1382,8 @@ function parseVaultCommand(command, arguments_) {
1163
1382
  root,
1164
1383
  options: index === undefined ? {} : { index },
1165
1384
  json,
1166
- ...command === "check" && noCatalog ? { noCatalog: true } : {}
1385
+ ...command === "check" && noCatalog ? { noCatalog: true } : {},
1386
+ ...command === "check" && repository !== undefined ? { repository } : {}
1167
1387
  }
1168
1388
  };
1169
1389
  }
@@ -1367,6 +1587,220 @@ function parseListCommand(arguments_) {
1367
1587
  }
1368
1588
  };
1369
1589
  }
1590
+ function parsePublishCommand(arguments_) {
1591
+ let root = ".";
1592
+ let out;
1593
+ let index;
1594
+ let title;
1595
+ let description;
1596
+ let basePath;
1597
+ let baseUrl;
1598
+ let noindex = false;
1599
+ let indexContent = true;
1600
+ let deterministic = false;
1601
+ let dryRun = false;
1602
+ let listLimit = DEFAULT_PUBLISH_LIST_LIMIT;
1603
+ let force = false;
1604
+ let json = false;
1605
+ let from;
1606
+ let depth = 1;
1607
+ let direction = "both";
1608
+ const includes = [];
1609
+ const excludes = [];
1610
+ const includeGlobs = [];
1611
+ const excludeGlobs = [];
1612
+ const filters = [];
1613
+ const tags = [];
1614
+ const repositoryScopes = [];
1615
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1616
+ const argument = arguments_[cursor];
1617
+ if (argument === undefined)
1618
+ continue;
1619
+ if (argument === "--json") {
1620
+ json = true;
1621
+ continue;
1622
+ }
1623
+ if (argument === "--noindex") {
1624
+ noindex = true;
1625
+ continue;
1626
+ }
1627
+ if (argument === "--no-index-content") {
1628
+ indexContent = false;
1629
+ continue;
1630
+ }
1631
+ if (argument === "--deterministic") {
1632
+ deterministic = true;
1633
+ continue;
1634
+ }
1635
+ if (argument === "--dry-run") {
1636
+ dryRun = true;
1637
+ continue;
1638
+ }
1639
+ if (argument === "--force") {
1640
+ force = true;
1641
+ continue;
1642
+ }
1643
+ 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") {
1644
+ const value = readValue(arguments_, cursor);
1645
+ if (value === null)
1646
+ return { ok: false, message: `${argument} requires a value` };
1647
+ if (argument === "--root")
1648
+ root = value;
1649
+ else if (argument === "--out")
1650
+ out = value;
1651
+ else if (argument === "--index")
1652
+ index = value;
1653
+ else if (argument === "--title")
1654
+ title = value;
1655
+ else if (argument === "--description")
1656
+ description = value;
1657
+ else if (argument === "--base-path")
1658
+ basePath = value;
1659
+ else if (argument === "--base-url")
1660
+ baseUrl = value;
1661
+ else if (argument === "--include")
1662
+ includes.push(value);
1663
+ else if (argument === "--exclude")
1664
+ excludes.push(value);
1665
+ else if (argument === "--include-glob")
1666
+ includeGlobs.push(value);
1667
+ else if (argument === "--exclude-glob")
1668
+ excludeGlobs.push(value);
1669
+ else if (argument === "--list-limit") {
1670
+ if (!/^(0|[1-9][0-9]*)$/u.test(value) || Number(value) > MAX_PUBLISH_LIST_LIMIT) {
1671
+ return { ok: false, message: `--list-limit must be an integer from 0 through ${MAX_PUBLISH_LIST_LIMIT}` };
1672
+ }
1673
+ listLimit = Number(value);
1674
+ } else if (argument === "--from")
1675
+ from = value;
1676
+ else if (argument === "--depth") {
1677
+ const parsed = Number(value);
1678
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 10) {
1679
+ return { ok: false, message: "--depth must be an integer from 1 through 10" };
1680
+ }
1681
+ depth = parsed;
1682
+ } else if (argument === "--direction") {
1683
+ if (value !== "in" && value !== "out" && value !== "both") {
1684
+ return { ok: false, message: "--direction must be in, out, or both" };
1685
+ }
1686
+ direction = value;
1687
+ } else if (argument === "--tag") {
1688
+ if (tags.length >= MAX_QUERY_TAGS) {
1689
+ return { ok: false, message: `Query tags may contain at most ${MAX_QUERY_TAGS} entries.` };
1690
+ }
1691
+ tags.push(value);
1692
+ } else if (argument === "--scope" || argument === "--repository-scope") {
1693
+ if (repositoryScopes.length >= MAX_REPOSITORY_SCOPES) {
1694
+ return { ok: false, message: `Repository scope filters may contain at most ${MAX_REPOSITORY_SCOPES} entries.` };
1695
+ }
1696
+ repositoryScopes.push(value);
1697
+ } else if (argument === "--has") {
1698
+ if (value.trim() === "")
1699
+ return { ok: false, message: "--has requires a metadata path" };
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: "exists", path: value });
1704
+ } else {
1705
+ const equals = value.indexOf("=");
1706
+ const path = equals === -1 ? "" : value.slice(0, equals).trim();
1707
+ if (path === "")
1708
+ return { ok: false, message: "--where requires path=value" };
1709
+ const scalar = metadataScalar(value.slice(equals + 1));
1710
+ if (!scalar.ok)
1711
+ return scalar;
1712
+ if (filters.length >= MAX_QUERY_FILTERS) {
1713
+ return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
1714
+ }
1715
+ filters.push({ kind: "equals", path, value: scalar.value });
1716
+ }
1717
+ cursor += 1;
1718
+ continue;
1719
+ }
1720
+ return {
1721
+ ok: false,
1722
+ message: argument.startsWith("--") ? "unknown publish option" : "publish does not accept positional arguments"
1723
+ };
1724
+ }
1725
+ if (out === undefined)
1726
+ return { ok: false, message: "publish requires --out <directory>" };
1727
+ try {
1728
+ validateQueryOptions({ filters, tags, repositoryScopes });
1729
+ } catch (error) {
1730
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
1731
+ }
1732
+ const selection = {
1733
+ includes,
1734
+ excludes,
1735
+ includeGlobs,
1736
+ excludeGlobs,
1737
+ filters,
1738
+ tags,
1739
+ repositoryScopes,
1740
+ ...from === undefined ? {} : { from: { note: from, depth, direction } }
1741
+ };
1742
+ return {
1743
+ ok: true,
1744
+ value: {
1745
+ kind: "publish",
1746
+ root,
1747
+ out,
1748
+ noindex,
1749
+ indexContent,
1750
+ deterministic,
1751
+ dryRun,
1752
+ listLimit,
1753
+ force,
1754
+ selection,
1755
+ json,
1756
+ ...index === undefined ? {} : { index },
1757
+ ...title === undefined ? {} : { title },
1758
+ ...description === undefined ? {} : { description },
1759
+ ...basePath === undefined ? {} : { basePath },
1760
+ ...baseUrl === undefined ? {} : { baseUrl }
1761
+ }
1762
+ };
1763
+ }
1764
+ function parseServeCommand(arguments_) {
1765
+ let root;
1766
+ let host = "127.0.0.1";
1767
+ let port = 8080;
1768
+ let json = false;
1769
+ for (let cursor = 0;cursor < arguments_.length; cursor += 1) {
1770
+ const argument = arguments_[cursor];
1771
+ if (argument === undefined)
1772
+ continue;
1773
+ if (argument === "--json") {
1774
+ json = true;
1775
+ continue;
1776
+ }
1777
+ if (argument === "--root" || argument === "--host" || argument === "--port") {
1778
+ const value = readValue(arguments_, cursor);
1779
+ if (value === null)
1780
+ return { ok: false, message: `${argument} requires a value` };
1781
+ if (argument === "--root")
1782
+ root = value;
1783
+ else if (argument === "--host")
1784
+ host = value;
1785
+ else {
1786
+ const parsed = Number(value);
1787
+ if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > 65535) {
1788
+ return { ok: false, message: "--port must be an integer from 0 through 65535" };
1789
+ }
1790
+ port = parsed;
1791
+ }
1792
+ cursor += 1;
1793
+ continue;
1794
+ }
1795
+ return {
1796
+ ok: false,
1797
+ message: argument.startsWith("--") ? "unknown serve option" : "serve does not accept positional arguments"
1798
+ };
1799
+ }
1800
+ if (root === undefined)
1801
+ return { ok: false, message: "serve requires --root <directory>" };
1802
+ return { ok: true, value: { kind: "serve", root, host, port, json } };
1803
+ }
1370
1804
  function parseInboxCommand(arguments_) {
1371
1805
  let root = ".";
1372
1806
  let index;
@@ -1439,6 +1873,8 @@ function parseSemanticCommand(command, arguments_) {
1439
1873
  let limit;
1440
1874
  let candidateLimit;
1441
1875
  let minScore;
1876
+ let rerank;
1877
+ let rerankLimit;
1442
1878
  let graphDepth;
1443
1879
  let noGraph = false;
1444
1880
  let history = false;
@@ -1496,7 +1932,7 @@ function parseSemanticCommand(command, arguments_) {
1496
1932
  cursor += 1;
1497
1933
  continue;
1498
1934
  }
1499
- if (command === "search" && (argument === "--mode" || argument === "--limit" || argument === "--candidate-limit" || argument === "--min-score" || argument === "--where" || argument === "--has" || argument === "--tag" || argument === "--scope" || argument === "--repository-scope" || argument === "--related" || argument === "--graph-depth")) {
1935
+ 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")) {
1500
1936
  const value = readValue(arguments_, cursor);
1501
1937
  if (value === null)
1502
1938
  return { ok: false, message: `${argument} requires a value` };
@@ -1505,6 +1941,17 @@ function parseSemanticCommand(command, arguments_) {
1505
1941
  return { ok: false, message: "--mode must be hybrid, exact, keyword, or semantic" };
1506
1942
  }
1507
1943
  mode = value;
1944
+ } else if (argument === "--rerank") {
1945
+ if (value !== "typesafe") {
1946
+ return { ok: false, message: "--rerank must be typesafe" };
1947
+ }
1948
+ rerank = value;
1949
+ } else if (argument === "--rerank-limit") {
1950
+ const parsed = Number(value);
1951
+ if (!Number.isSafeInteger(parsed) || parsed < 2 || parsed > MAX_RERANK_CANDIDATES) {
1952
+ return { ok: false, message: `--rerank-limit must be an integer from 2 through ${MAX_RERANK_CANDIDATES}` };
1953
+ }
1954
+ rerankLimit = parsed;
1508
1955
  } else if (argument === "--where") {
1509
1956
  const equals = value.indexOf("=");
1510
1957
  const path = equals === -1 ? "" : value.slice(0, equals).trim();
@@ -1637,6 +2084,9 @@ function parseSemanticCommand(command, arguments_) {
1637
2084
  message: "Search minimum score applies only to hybrid, keyword, or semantic mode."
1638
2085
  };
1639
2086
  }
2087
+ if (rerankLimit !== undefined && rerank === undefined) {
2088
+ return { ok: false, message: "--rerank-limit requires --rerank typesafe" };
2089
+ }
1640
2090
  if (priority && rulesPath === undefined) {
1641
2091
  return { ok: false, message: "--priority requires --rules" };
1642
2092
  }
@@ -1674,6 +2124,8 @@ function parseSemanticCommand(command, arguments_) {
1674
2124
  ...limit === undefined ? {} : { limit },
1675
2125
  ...candidateLimit === undefined ? {} : { candidateLimit },
1676
2126
  ...minScore === undefined ? {} : { minScore },
2127
+ ...rerank === undefined ? {} : { rerank },
2128
+ ...rerankLimit === undefined ? {} : { rerankLimit },
1677
2129
  query,
1678
2130
  json
1679
2131
  }
@@ -2420,6 +2872,10 @@ function parseArguments(arguments_) {
2420
2872
  return parseRelationCommand(arguments_.slice(1));
2421
2873
  if (command === "percolate")
2422
2874
  return parsePercolateCommand(arguments_.slice(1));
2875
+ if (command === "publish")
2876
+ return parsePublishCommand(arguments_.slice(1));
2877
+ if (command === "serve")
2878
+ return parseServeCommand(arguments_.slice(1));
2423
2879
  return { ok: false, message: "unknown command" };
2424
2880
  }
2425
2881
  function embeddingCount(result) {
@@ -2440,11 +2896,23 @@ function renderKnowledgeBaseSearch(result) {
2440
2896
  const lines = [
2441
2897
  `${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for \u201C${safe(result.query)}\u201D (${result.results.length})${result.partial ? " [partial]" : ""}`
2442
2898
  ];
2899
+ const rerankLane = result.diagnostics.lanes.find(({ lane }) => lane === "rerank");
2900
+ if (rerankLane !== undefined) {
2901
+ lines.push(` Rerank: typesafe over ${rerankLane.results} candidates (${rerankLane.status})` + (rerankLane.message === undefined ? "" : ` \u2014 ${safe(rerankLane.message)}`));
2902
+ const receipt = rerankLane.rerank;
2903
+ if (receipt?.accounting !== undefined) {
2904
+ const { attempted, candidates, completed, elapsedMs, usageComplete } = receipt.accounting;
2905
+ lines.push(` Rerank requests: ${attempted}/${candidates} attempted, ${completed} settled; ${elapsedMs.toFixed(0)} ms` + (usageComplete ? "; usage complete" : "; usage incomplete, additional charges may be unknown"));
2906
+ if (rerankLane.status !== "ready" && receipt.usage !== undefined) {
2907
+ lines.push(` Known rerank usage: ${receipt.usage.inputTokens ?? 0} input tokens, ${receipt.usage.outputTokens ?? 0} output tokens`);
2908
+ }
2909
+ }
2910
+ }
2443
2911
  if (result.results.length === 0)
2444
2912
  lines.push(" None.");
2445
2913
  for (const hit of result.results) {
2446
2914
  const location = `${safe(hit.path)}${hit.line === undefined ? "" : `:${hit.line}`}`;
2447
- const evidence = hit.evidence.map((item) => `${item.kind}#${item.rank}`).join(", ");
2915
+ const evidence = hit.evidence.map((item) => `${item.kind}#${searchEvidenceRank(item)}`).join(", ");
2448
2916
  lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} \u2014 ${safe(hit.title)} [${safe(evidence)}]`);
2449
2917
  if (hit.snippet !== "")
2450
2918
  lines.push(` ${safe(hit.snippet)}`);
@@ -2476,7 +2944,7 @@ async function runSemantic(command, output, dependencies) {
2476
2944
  repository: command.repository,
2477
2945
  ...command.database === undefined ? {} : { database: command.database },
2478
2946
  ...searchRules === undefined ? {} : { searchRules }
2479
- });
2947
+ }, ...command.rerank === undefined ? [] : [{ rerankers: dependencies.rerankers ?? [await createCliTypeSafeReranker()] }]);
2480
2948
  try {
2481
2949
  const result = await kb.search({
2482
2950
  query: command.query,
@@ -2489,7 +2957,13 @@ async function runSemantic(command, output, dependencies) {
2489
2957
  history: command.history,
2490
2958
  ...command.limit === undefined ? {} : { limit: command.limit },
2491
2959
  ...command.candidateLimit === undefined ? {} : { candidateLimit: command.candidateLimit },
2492
- ...command.minScore === undefined ? {} : { minScore: command.minScore }
2960
+ ...command.minScore === undefined ? {} : { minScore: command.minScore },
2961
+ ...command.rerank === undefined ? {} : {
2962
+ rerank: {
2963
+ engine: command.rerank,
2964
+ ...command.rerankLimit === undefined ? {} : { limit: command.rerankLimit }
2965
+ }
2966
+ }
2493
2967
  });
2494
2968
  output.stdout(command.json ? terminalSafeJson(result) : sanitizeTerminalText(renderKnowledgeBaseSearch(result)));
2495
2969
  return 0;
@@ -2744,7 +3218,8 @@ function summary(snapshot, options = {}) {
2744
3218
  truncated: options.attachments.truncated,
2745
3219
  issues: options.attachments.issues
2746
3220
  }
2747
- }
3221
+ },
3222
+ ...options.repositoryScopes === undefined ? {} : { repositoryScopes: options.repositoryScopes }
2748
3223
  };
2749
3224
  }
2750
3225
  function renderIssue(issue) {
@@ -2777,6 +3252,38 @@ function renderAdvisories(analysis) {
2777
3252
  }
2778
3253
  return lines;
2779
3254
  }
3255
+ function renderRepositoryScopeAuditError(error) {
3256
+ if (error.kind === "invalid-record") {
3257
+ return `${safe(error.path)}: ${error.issues.map(safe).join(" ")}`;
3258
+ }
3259
+ const declared = error.recordPaths.map(safe).join(", ") + (error.recordPathsTruncated ? ", \u2026" : "");
3260
+ return `${safe(error.scope)}: ${safe(error.state.reason)} at ${safe(error.state.path)}` + ` (declared by ${declared})`;
3261
+ }
3262
+ function renderRepositoryScopeAudit(audit) {
3263
+ const { counts } = audit;
3264
+ const lines = [
3265
+ `Repository scopes: ${counts.authoredRecords} declaring note${counts.authoredRecords === 1 ? "" : "s"}; ` + `${counts.distinctScopes} distinct scope${counts.distinctScopes === 1 ? "" : "s"} ` + `(${counts.presentScopes} present, ${counts.absentScopes} absent, ${counts.invalidScopes} unusable).`
3266
+ ];
3267
+ if (audit.advisories.total > 0) {
3268
+ lines.push(audit.advisories.total === 1 ? "Advisory: 1 current note declares a repository path that no longer exists." : `Advisory: ${audit.advisories.total} current notes declare repository paths that no longer exist.`);
3269
+ for (const advisory of audit.advisories.details) {
3270
+ lines.push(` ${safe(advisory.path)} declares absent scope ${safe(advisory.scope)}`);
3271
+ }
3272
+ if (audit.advisories.truncated) {
3273
+ lines.push(` \u2026 ${audit.advisories.total - audit.advisories.returned} more; rerun with --json for the complete audit.`);
3274
+ }
3275
+ }
3276
+ if (audit.errors.total > 0) {
3277
+ lines.push(`Advisory: ${audit.errors.total} repository scope declaration problem${audit.errors.total === 1 ? "" : "s"}.`);
3278
+ for (const error of audit.errors.details) {
3279
+ lines.push(` ${renderRepositoryScopeAuditError(error)}`);
3280
+ }
3281
+ if (audit.errors.truncated) {
3282
+ lines.push(` \u2026 ${audit.errors.total - audit.errors.returned} more; rerun with --json for the complete audit.`);
3283
+ }
3284
+ }
3285
+ return lines;
3286
+ }
2780
3287
  function checkExitCode(snapshot, noCatalog = false, attachments) {
2781
3288
  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;
2782
3289
  }
@@ -2784,7 +3291,7 @@ function renderAttachmentIssue(issue) {
2784
3291
  const candidates = issue.candidates === undefined ? "" : ` (${issue.candidates.map(safe).join(", ")})`;
2785
3292
  return `${safe(issue.source)}:${issue.line}: ${safe(issue.kind)} attachment ${safe(issue.target)}: ${safe(issue.message)}${candidates}`;
2786
3293
  }
2787
- function renderSnapshot(command, snapshot, noCatalog = false, attachments) {
3294
+ function renderSnapshot(command, snapshot, noCatalog = false, attachments, repositoryScopes) {
2788
3295
  const lines = [
2789
3296
  `${command === "refresh" ? "Refreshed" : "Checked"} ${safe(snapshot.root)}`,
2790
3297
  `Index: ${noCatalog ? `not required (${snapshot.index})` : snapshot.index}; notes: ${snapshot.analysis.noteCount}; contextual links: ${snapshot.analysis.contextualLinks.length}; typed relationships: ${snapshot.analysis.authoredRelations.length}.`
@@ -2804,6 +3311,8 @@ function renderSnapshot(command, snapshot, noCatalog = false, attachments) {
2804
3311
  lines.push("error: attachment validation was truncated by a resource limit");
2805
3312
  }
2806
3313
  lines.push(...renderAdvisories(snapshot.analysis));
3314
+ if (repositoryScopes !== undefined)
3315
+ lines.push(...renderRepositoryScopeAudit(repositoryScopes));
2807
3316
  return `${lines.join(`
2808
3317
  `)}
2809
3318
  `;
@@ -3104,6 +3613,36 @@ async function runList(command, output, dependencies) {
3104
3613
  output.stdout(command.json ? terminalSafeJson({ root: snapshot.root, count: rows.length, notes: rows }) : sanitizeTerminalText(renderList(rows)));
3105
3614
  return 0;
3106
3615
  }
3616
+ async function runPublish(command, output, dependencies) {
3617
+ const result = await (dependencies.publishVault ?? publishVault)({
3618
+ root: command.root,
3619
+ out: command.out,
3620
+ noindex: command.noindex,
3621
+ indexContent: command.indexContent,
3622
+ deterministic: command.deterministic,
3623
+ dryRun: command.dryRun,
3624
+ listLimit: command.listLimit,
3625
+ force: command.force,
3626
+ selection: command.selection,
3627
+ ...command.index === undefined ? {} : { index: command.index },
3628
+ ...command.title === undefined ? {} : { title: command.title },
3629
+ ...command.description === undefined ? {} : { description: command.description },
3630
+ ...command.basePath === undefined ? {} : { basePath: command.basePath },
3631
+ ...command.baseUrl === undefined ? {} : { baseUrl: command.baseUrl }
3632
+ });
3633
+ output.stdout(command.json ? terminalSafeJson(result.report) : sanitizeTerminalText(renderPublishReportText(result.report, command.dryRun)));
3634
+ return 0;
3635
+ }
3636
+ async function runServe(command, output, dependencies) {
3637
+ const site = await (dependencies.serveSite ?? serveSite)({
3638
+ root: command.root,
3639
+ host: command.host,
3640
+ port: command.port
3641
+ });
3642
+ 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)}
3643
+ `);
3644
+ return 0;
3645
+ }
3107
3646
  async function runCatalog(command, output, dependencies) {
3108
3647
  const snapshot = await (dependencies.scanVault ?? scanVault)(command.root, command.options);
3109
3648
  const relativeIndex = relative(snapshot.root, snapshot.indexPath).split("\\").join("/");
@@ -3558,7 +4097,15 @@ async function runVault(command, output, dependencies) {
3558
4097
  root: snapshot.root,
3559
4098
  documents: snapshot.notes.map(({ path, content }) => ({ path, content }))
3560
4099
  }) : undefined;
3561
- output.stdout(command.json ? terminalSafeJson(summary(snapshot, { noCatalog, ...attachments === undefined ? {} : { attachments } })) : sanitizeTerminalText(renderSnapshot(command.kind, snapshot, noCatalog, attachments)));
4100
+ const repositoryScopes = command.kind === "check" && command.repository !== undefined ? await (dependencies.auditRepositoryMemoryScopes ?? auditRepositoryMemoryScopes)(snapshot.notes, {
4101
+ repositoryRoot: command.repository,
4102
+ detailLimit: MAX_REPOSITORY_MEMORY_DETAIL_LIMIT
4103
+ }) : undefined;
4104
+ output.stdout(command.json ? terminalSafeJson(summary(snapshot, {
4105
+ noCatalog,
4106
+ ...attachments === undefined ? {} : { attachments },
4107
+ ...repositoryScopes === undefined ? {} : { repositoryScopes }
4108
+ })) : sanitizeTerminalText(renderSnapshot(command.kind, snapshot, noCatalog, attachments, repositoryScopes)));
3562
4109
  return checkExitCode(snapshot, noCatalog, attachments);
3563
4110
  }
3564
4111
  if (command.kind === "graph") {
@@ -3673,6 +4220,10 @@ ${sanitizeTerminalText(usage)}`);
3673
4220
  return await runPercolate(command, output, dependencies);
3674
4221
  if (command.kind === "list")
3675
4222
  return await runList(command, output, dependencies);
4223
+ if (command.kind === "publish")
4224
+ return await runPublish(command, output, dependencies);
4225
+ if (command.kind === "serve")
4226
+ return await runServe(command, output, dependencies);
3676
4227
  if (command.kind === "inbox")
3677
4228
  return await runInbox(command, output, dependencies);
3678
4229
  if (command.kind === "catalog")