@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
@@ -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,
@@ -140,11 +148,14 @@ import {
140
148
  MAX_SEARCH_RELATED_SEEDS,
141
149
  MAX_SEARCH_RESULTS,
142
150
  openKnowledgeBase,
151
+ searchEvidenceRank,
143
152
  type KnowledgeBaseGraphOptions,
144
153
  type KnowledgeBaseSearchMode,
145
154
  type KnowledgeBaseSearchOrdering,
146
155
  type KnowledgeBaseSearchResult,
147
156
  } from "./sdk.js";
157
+ import { MAX_RERANK_CANDIDATES, type SearchReranker } from "./rerank.js";
158
+ import { createCliTypeSafeReranker } from "./rerank-credentials.js";
148
159
  import {
149
160
  refreshVault,
150
161
  scanVault,
@@ -215,7 +226,16 @@ async function loadSearchRulesFile(path: string): Promise<SearchRulesV1> {
215
226
  return parseSearchRules(input);
216
227
  }
217
228
 
218
- export const usage = `wordcell — auditable capture and derived links for Markdown vaults
229
+ export const usage = `wordcell — a local knowledge base for coding agents
230
+
231
+ Start here (no account or model needed):
232
+ wordcell init kb
233
+ wordcell note create notes/decision --title "A decision" --body "Keep retries bounded." --root kb
234
+ wordcell search "retries" --root kb --mode exact
235
+
236
+ Already have Markdown? Search it with --root <your-notes-directory>.
237
+ Use --mode exact for model-free search; optional hybrid search needs a local index.
238
+ Quick start and examples: https://wordcell.io/docs
219
239
 
220
240
  Usage:
221
241
  wordcell init [directory] [--json]
@@ -243,12 +263,14 @@ Usage:
243
263
  wordcell percolate [note] [--proofs] [--root <directory>] [--min-support <count>] [--limit <count>] [--json]
244
264
  wordcell list [--root <directory>] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--sort <field>] [--order <asc|desc>] [--limit <count>] [--json]
245
265
  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]
266
+ 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
267
  wordcell history <note> [--root <directory>] [--repo <repository>] [--limit <count>] [--cochanged-limit <count>] [--json]
248
268
  wordcell history search <query-or-path> [--root <directory>] [--repo <repository>] [--limit <count>] [--commit-limit <count>] [--cochanged-limit <count>] [--json]
249
269
  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
270
  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
271
  wordcell portfolio audit --registry <file> --workspace <directory> (--all | --shared | --vault <owner/id>...) [--strict] [--json]
272
+ 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]
273
+ wordcell serve --root <directory> [--host <host>] [--port <port>] [--json]
252
274
  wordcell inbox [--root <directory>] [--source-prefix <directory>] [--limit <count>] [--json]
253
275
  wordcell context <repository-path> [--root <vault>] [--repo <repository>] [--kind <auto|file|directory>] [--json]
254
276
  wordcell agents identity <repository-scope> [--json]
@@ -256,6 +278,12 @@ Usage:
256
278
  wordcell agents audit [--root <vault>] [--repo <repository>] [--json]
257
279
  wordcell doctor [--json]
258
280
  wordcell adapters [--json]
281
+ wordcell support [--json | protocol --json | offer --json | shown <id> | release <id> | dismiss | snooze | enable | status --json]
282
+
283
+ Optional support uses separate local preferences. HRANESS_SUPPORT=off or
284
+ HRANESS_SUPPORT_AUDIENCE=off keeps invitations quiet; explicit support remains available.
285
+ Agents receive discovery on stderr after useful standalone work. Human mode requires
286
+ HRANESS_SUPPORT_AUDIENCE=human and an interactive stderr. No command signs up or pays.
259
287
 
260
288
  Run \`wordcell clip --help\` for web capture options or \`wordcell pdf --help\` for PDF conversion options.
261
289
  `;
@@ -353,6 +381,8 @@ type ParsedCommand =
353
381
  readonly limit?: number;
354
382
  readonly candidateLimit?: number;
355
383
  readonly minScore?: number;
384
+ readonly rerank?: "typesafe";
385
+ readonly rerankLimit?: number;
356
386
  readonly query: string;
357
387
  readonly json: boolean;
358
388
  }
@@ -440,6 +470,31 @@ type ParsedCommand =
440
470
  readonly minSupport: number;
441
471
  readonly limit: number;
442
472
  readonly json: boolean;
473
+ }
474
+ | {
475
+ readonly kind: "publish";
476
+ readonly root: string;
477
+ readonly out: string;
478
+ readonly index?: string;
479
+ readonly title?: string;
480
+ readonly description?: string;
481
+ readonly basePath?: string;
482
+ readonly baseUrl?: string;
483
+ readonly noindex: boolean;
484
+ readonly indexContent: boolean;
485
+ readonly deterministic: boolean;
486
+ readonly dryRun: boolean;
487
+ readonly listLimit: number;
488
+ readonly force: boolean;
489
+ readonly selection: PublishSelectionInput;
490
+ readonly json: boolean;
491
+ }
492
+ | {
493
+ readonly kind: "serve";
494
+ readonly root: string;
495
+ readonly host: string;
496
+ readonly port: number;
497
+ readonly json: boolean;
443
498
  };
444
499
 
445
500
  type ParseResult =
@@ -462,6 +517,8 @@ type CliDependencies = GraphCliDependencies & {
462
517
  readonly indexSemanticVault?: typeof indexSemanticVault;
463
518
  readonly openKnowledgeBase?: typeof openKnowledgeBase;
464
519
  readonly openKnowledgeBaseEvaluation?: typeof openKnowledgeBaseEvaluation;
520
+ /** Test seam; production wiring builds the TypeSafe reranker lazily per flag. */
521
+ readonly rerankers?: readonly SearchReranker[];
465
522
  readonly digestEvaluationModel?: (path: string) => Promise<string>;
466
523
  readonly evaluationNow?: () => Date;
467
524
  readonly createNote?: typeof createNote;
@@ -473,6 +530,8 @@ type CliDependencies = GraphCliDependencies & {
473
530
  readonly buildRepositoryMemoryContext?: typeof buildRepositoryMemoryContext;
474
531
  readonly auditAgentGuideRepository?: typeof auditAgentGuideRepository;
475
532
  readonly validateMarkdownAttachments?: typeof validateMarkdownAttachments;
533
+ readonly publishVault?: typeof publishVault;
534
+ readonly serveSite?: typeof serveSite;
476
535
  };
477
536
 
478
537
  function safe(value: string): string {
@@ -860,6 +919,195 @@ function parseListCommand(arguments_: readonly string[]): ParseResult {
860
919
  };
861
920
  }
862
921
 
922
+ function parsePublishCommand(arguments_: readonly string[]): ParseResult {
923
+ let root = ".";
924
+ let out: string | undefined;
925
+ let index: string | undefined;
926
+ let title: string | undefined;
927
+ let description: string | undefined;
928
+ let basePath: string | undefined;
929
+ let baseUrl: string | undefined;
930
+ let noindex = false;
931
+ let indexContent = true;
932
+ let deterministic = false;
933
+ let dryRun = false;
934
+ let listLimit = DEFAULT_PUBLISH_LIST_LIMIT;
935
+ let force = false;
936
+ let json = false;
937
+ let from: string | undefined;
938
+ let depth = 1;
939
+ let direction: LinkDirection = "both";
940
+ const includes: string[] = [];
941
+ const excludes: string[] = [];
942
+ const includeGlobs: string[] = [];
943
+ const excludeGlobs: string[] = [];
944
+ const filters: MetadataFilter[] = [];
945
+ const tags: string[] = [];
946
+ const repositoryScopes: string[] = [];
947
+
948
+ for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
949
+ const argument = arguments_[cursor];
950
+ if (argument === undefined) continue;
951
+ if (argument === "--json") { json = true; continue; }
952
+ if (argument === "--noindex") { noindex = true; continue; }
953
+ if (argument === "--no-index-content") { indexContent = false; continue; }
954
+ if (argument === "--deterministic") { deterministic = true; continue; }
955
+ if (argument === "--dry-run") { dryRun = true; continue; }
956
+ if (argument === "--force") { force = true; continue; }
957
+ if (
958
+ argument === "--root" || argument === "--out" || argument === "--index"
959
+ || argument === "--title" || argument === "--description"
960
+ || argument === "--base-path" || argument === "--base-url"
961
+ || argument === "--include" || argument === "--exclude"
962
+ || argument === "--include-glob" || argument === "--exclude-glob"
963
+ || argument === "--list-limit"
964
+ || argument === "--where" || argument === "--has" || argument === "--tag"
965
+ || argument === "--scope" || argument === "--repository-scope"
966
+ || argument === "--from" || argument === "--depth" || argument === "--direction"
967
+ ) {
968
+ const value = readValue(arguments_, cursor);
969
+ if (value === null) return { ok: false, message: `${argument} requires a value` };
970
+ if (argument === "--root") root = value;
971
+ else if (argument === "--out") out = value;
972
+ else if (argument === "--index") index = value;
973
+ else if (argument === "--title") title = value;
974
+ else if (argument === "--description") description = value;
975
+ else if (argument === "--base-path") basePath = value;
976
+ else if (argument === "--base-url") baseUrl = value;
977
+ else if (argument === "--include") includes.push(value);
978
+ else if (argument === "--exclude") excludes.push(value);
979
+ else if (argument === "--include-glob") includeGlobs.push(value);
980
+ else if (argument === "--exclude-glob") excludeGlobs.push(value);
981
+ else if (argument === "--list-limit") {
982
+ if (!/^(0|[1-9][0-9]*)$/u.test(value) || Number(value) > MAX_PUBLISH_LIST_LIMIT) {
983
+ return { ok: false, message: `--list-limit must be an integer from 0 through ${MAX_PUBLISH_LIST_LIMIT}` };
984
+ }
985
+ listLimit = Number(value);
986
+ }
987
+ else if (argument === "--from") from = value;
988
+ else if (argument === "--depth") {
989
+ const parsed = Number(value);
990
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 10) {
991
+ return { ok: false, message: "--depth must be an integer from 1 through 10" };
992
+ }
993
+ depth = parsed;
994
+ } else if (argument === "--direction") {
995
+ if (value !== "in" && value !== "out" && value !== "both") {
996
+ return { ok: false, message: "--direction must be in, out, or both" };
997
+ }
998
+ direction = value;
999
+ } else if (argument === "--tag") {
1000
+ if (tags.length >= MAX_QUERY_TAGS) {
1001
+ return { ok: false, message: `Query tags may contain at most ${MAX_QUERY_TAGS} entries.` };
1002
+ }
1003
+ tags.push(value);
1004
+ } else if (argument === "--scope" || argument === "--repository-scope") {
1005
+ if (repositoryScopes.length >= MAX_REPOSITORY_SCOPES) {
1006
+ return { ok: false, message: `Repository scope filters may contain at most ${MAX_REPOSITORY_SCOPES} entries.` };
1007
+ }
1008
+ repositoryScopes.push(value);
1009
+ } else if (argument === "--has") {
1010
+ if (value.trim() === "") return { ok: false, message: "--has requires a metadata path" };
1011
+ if (filters.length >= MAX_QUERY_FILTERS) {
1012
+ return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
1013
+ }
1014
+ filters.push({ kind: "exists", path: value });
1015
+ } else {
1016
+ const equals = value.indexOf("=");
1017
+ const path = equals === -1 ? "" : value.slice(0, equals).trim();
1018
+ if (path === "") return { ok: false, message: "--where requires path=value" };
1019
+ const scalar = metadataScalar(value.slice(equals + 1));
1020
+ if (!scalar.ok) return scalar;
1021
+ if (filters.length >= MAX_QUERY_FILTERS) {
1022
+ return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
1023
+ }
1024
+ filters.push({ kind: "equals", path, value: scalar.value });
1025
+ }
1026
+ cursor += 1;
1027
+ continue;
1028
+ }
1029
+ return {
1030
+ ok: false,
1031
+ message: argument.startsWith("--")
1032
+ ? "unknown publish option"
1033
+ : "publish does not accept positional arguments",
1034
+ };
1035
+ }
1036
+
1037
+ if (out === undefined) return { ok: false, message: "publish requires --out <directory>" };
1038
+ try {
1039
+ validateQueryOptions({ filters, tags, repositoryScopes });
1040
+ } catch (error: unknown) {
1041
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
1042
+ }
1043
+ const selection: PublishSelectionInput = {
1044
+ includes,
1045
+ excludes,
1046
+ includeGlobs,
1047
+ excludeGlobs,
1048
+ filters,
1049
+ tags,
1050
+ repositoryScopes,
1051
+ ...(from === undefined ? {} : { from: { note: from, depth, direction } }),
1052
+ };
1053
+ return {
1054
+ ok: true,
1055
+ value: {
1056
+ kind: "publish",
1057
+ root,
1058
+ out,
1059
+ noindex,
1060
+ indexContent,
1061
+ deterministic,
1062
+ dryRun,
1063
+ listLimit,
1064
+ force,
1065
+ selection,
1066
+ json,
1067
+ ...(index === undefined ? {} : { index }),
1068
+ ...(title === undefined ? {} : { title }),
1069
+ ...(description === undefined ? {} : { description }),
1070
+ ...(basePath === undefined ? {} : { basePath }),
1071
+ ...(baseUrl === undefined ? {} : { baseUrl }),
1072
+ },
1073
+ };
1074
+ }
1075
+
1076
+ function parseServeCommand(arguments_: readonly string[]): ParseResult {
1077
+ let root: string | undefined;
1078
+ let host = "127.0.0.1";
1079
+ let port = 8080;
1080
+ let json = false;
1081
+ for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
1082
+ const argument = arguments_[cursor];
1083
+ if (argument === undefined) continue;
1084
+ if (argument === "--json") { json = true; continue; }
1085
+ if (argument === "--root" || argument === "--host" || argument === "--port") {
1086
+ const value = readValue(arguments_, cursor);
1087
+ if (value === null) return { ok: false, message: `${argument} requires a value` };
1088
+ if (argument === "--root") root = value;
1089
+ else if (argument === "--host") host = value;
1090
+ else {
1091
+ const parsed = Number(value);
1092
+ if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > 65_535) {
1093
+ return { ok: false, message: "--port must be an integer from 0 through 65535" };
1094
+ }
1095
+ port = parsed;
1096
+ }
1097
+ cursor += 1;
1098
+ continue;
1099
+ }
1100
+ return {
1101
+ ok: false,
1102
+ message: argument.startsWith("--")
1103
+ ? "unknown serve option"
1104
+ : "serve does not accept positional arguments",
1105
+ };
1106
+ }
1107
+ if (root === undefined) return { ok: false, message: "serve requires --root <directory>" };
1108
+ return { ok: true, value: { kind: "serve", root, host, port, json } };
1109
+ }
1110
+
863
1111
  function parseInboxCommand(arguments_: readonly string[]): ParseResult {
864
1112
  let root = ".";
865
1113
  let index: string | undefined;
@@ -936,6 +1184,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
936
1184
  let limit: number | undefined;
937
1185
  let candidateLimit: number | undefined;
938
1186
  let minScore: number | undefined;
1187
+ let rerank: "typesafe" | undefined;
1188
+ let rerankLimit: number | undefined;
939
1189
  let graphDepth: number | undefined;
940
1190
  let noGraph = false;
941
1191
  let history = false;
@@ -997,6 +1247,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
997
1247
  || argument === "--limit"
998
1248
  || argument === "--candidate-limit"
999
1249
  || argument === "--min-score"
1250
+ || argument === "--rerank"
1251
+ || argument === "--rerank-limit"
1000
1252
  || argument === "--where"
1001
1253
  || argument === "--has"
1002
1254
  || argument === "--tag"
@@ -1012,6 +1264,17 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1012
1264
  return { ok: false, message: "--mode must be hybrid, exact, keyword, or semantic" };
1013
1265
  }
1014
1266
  mode = value;
1267
+ } else if (argument === "--rerank") {
1268
+ if (value !== "typesafe") {
1269
+ return { ok: false, message: "--rerank must be typesafe" };
1270
+ }
1271
+ rerank = value;
1272
+ } else if (argument === "--rerank-limit") {
1273
+ const parsed = Number(value);
1274
+ if (!Number.isSafeInteger(parsed) || parsed < 2 || parsed > MAX_RERANK_CANDIDATES) {
1275
+ return { ok: false, message: `--rerank-limit must be an integer from 2 through ${MAX_RERANK_CANDIDATES}` };
1276
+ }
1277
+ rerankLimit = parsed;
1015
1278
  } else if (argument === "--where") {
1016
1279
  const equals = value.indexOf("=");
1017
1280
  const path = equals === -1 ? "" : value.slice(0, equals).trim();
@@ -1141,6 +1404,9 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1141
1404
  message: "Search minimum score applies only to hybrid, keyword, or semantic mode.",
1142
1405
  };
1143
1406
  }
1407
+ if (rerankLimit !== undefined && rerank === undefined) {
1408
+ return { ok: false, message: "--rerank-limit requires --rerank typesafe" };
1409
+ }
1144
1410
  if (priority && rulesPath === undefined) {
1145
1411
  return { ok: false, message: "--priority requires --rules" };
1146
1412
  }
@@ -1179,6 +1445,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1179
1445
  ...(limit === undefined ? {} : { limit }),
1180
1446
  ...(candidateLimit === undefined ? {} : { candidateLimit }),
1181
1447
  ...(minScore === undefined ? {} : { minScore }),
1448
+ ...(rerank === undefined ? {} : { rerank }),
1449
+ ...(rerankLimit === undefined ? {} : { rerankLimit }),
1182
1450
  query,
1183
1451
  json,
1184
1452
  },
@@ -1932,6 +2200,8 @@ export function parseArguments(arguments_: readonly string[]): ParseResult {
1932
2200
  if (command === "note") return parseNoteCommand(arguments_.slice(1));
1933
2201
  if (command === "relation") return parseRelationCommand(arguments_.slice(1));
1934
2202
  if (command === "percolate") return parsePercolateCommand(arguments_.slice(1));
2203
+ if (command === "publish") return parsePublishCommand(arguments_.slice(1));
2204
+ if (command === "serve") return parseServeCommand(arguments_.slice(1));
1935
2205
  return { ok: false, message: "unknown command" };
1936
2206
  }
1937
2207
 
@@ -1954,10 +2224,28 @@ function renderKnowledgeBaseSearch(result: KnowledgeBaseSearchResult): string {
1954
2224
  const lines = [
1955
2225
  `${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for “${safe(result.query)}” (${result.results.length})${result.partial ? " [partial]" : ""}`,
1956
2226
  ];
2227
+ const rerankLane = result.diagnostics.lanes.find(({ lane }) => lane === "rerank");
2228
+ if (rerankLane !== undefined) {
2229
+ lines.push(
2230
+ ` Rerank: typesafe over ${rerankLane.results} candidates (${rerankLane.status})`
2231
+ + (rerankLane.message === undefined ? "" : ` — ${safe(rerankLane.message)}`),
2232
+ );
2233
+ const receipt = rerankLane.rerank;
2234
+ if (receipt?.accounting !== undefined) {
2235
+ const { attempted, candidates, completed, elapsedMs, usageComplete } = receipt.accounting;
2236
+ lines.push(` Rerank requests: ${attempted}/${candidates} attempted, ${completed} settled; ${elapsedMs.toFixed(0)} ms`
2237
+ + (usageComplete ? "; usage complete" : "; usage incomplete, additional charges may be unknown"));
2238
+ if (rerankLane.status !== "ready" && receipt.usage !== undefined) {
2239
+ lines.push(` Known rerank usage: ${receipt.usage.inputTokens ?? 0} input tokens, ${receipt.usage.outputTokens ?? 0} output tokens`);
2240
+ }
2241
+ }
2242
+ }
1957
2243
  if (result.results.length === 0) lines.push(" None.");
1958
2244
  for (const hit of result.results) {
1959
2245
  const location = `${safe(hit.path)}${hit.line === undefined ? "" : `:${hit.line}`}`;
1960
- const evidence = hit.evidence.map((item) => `${item.kind}#${item.rank}`).join(", ");
2246
+ const evidence = hit.evidence
2247
+ .map((item) => `${item.kind}#${searchEvidenceRank(item)}`)
2248
+ .join(", ");
1961
2249
  lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} — ${safe(hit.title)} [${safe(evidence)}]`);
1962
2250
  if (hit.snippet !== "") lines.push(` ${safe(hit.snippet)}`);
1963
2251
  }
@@ -1993,12 +2281,17 @@ async function runSemantic(
1993
2281
  const searchRules = command.rulesPath === undefined
1994
2282
  ? undefined
1995
2283
  : await loadSearchRulesFile(command.rulesPath);
1996
- const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)({
1997
- root: command.root,
1998
- repository: command.repository,
1999
- ...(command.database === undefined ? {} : { database: command.database }),
2000
- ...(searchRules === undefined ? {} : { searchRules }),
2001
- });
2284
+ const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)(
2285
+ {
2286
+ root: command.root,
2287
+ repository: command.repository,
2288
+ ...(command.database === undefined ? {} : { database: command.database }),
2289
+ ...(searchRules === undefined ? {} : { searchRules }),
2290
+ },
2291
+ ...(command.rerank === undefined
2292
+ ? []
2293
+ : [{ rerankers: dependencies.rerankers ?? [await createCliTypeSafeReranker()] }]),
2294
+ );
2002
2295
  try {
2003
2296
  const result = await kb.search({
2004
2297
  query: command.query,
@@ -2014,6 +2307,12 @@ async function runSemantic(
2014
2307
  ? {}
2015
2308
  : { candidateLimit: command.candidateLimit }),
2016
2309
  ...(command.minScore === undefined ? {} : { minScore: command.minScore }),
2310
+ ...(command.rerank === undefined ? {} : {
2311
+ rerank: {
2312
+ engine: command.rerank,
2313
+ ...(command.rerankLimit === undefined ? {} : { limit: command.rerankLimit }),
2314
+ },
2315
+ }),
2017
2316
  });
2018
2317
  output.stdout(command.json
2019
2318
  ? terminalSafeJson(result)
@@ -2828,6 +3127,49 @@ async function runList(
2828
3127
  return 0;
2829
3128
  }
2830
3129
 
3130
+ async function runPublish(
3131
+ command: Extract<ParsedCommand, { readonly kind: "publish" }>,
3132
+ output: Output,
3133
+ dependencies: CliDependencies,
3134
+ ): Promise<number> {
3135
+ const result = await (dependencies.publishVault ?? publishVault)({
3136
+ root: command.root,
3137
+ out: command.out,
3138
+ noindex: command.noindex,
3139
+ indexContent: command.indexContent,
3140
+ deterministic: command.deterministic,
3141
+ dryRun: command.dryRun,
3142
+ listLimit: command.listLimit,
3143
+ force: command.force,
3144
+ selection: command.selection,
3145
+ ...(command.index === undefined ? {} : { index: command.index }),
3146
+ ...(command.title === undefined ? {} : { title: command.title }),
3147
+ ...(command.description === undefined ? {} : { description: command.description }),
3148
+ ...(command.basePath === undefined ? {} : { basePath: command.basePath }),
3149
+ ...(command.baseUrl === undefined ? {} : { baseUrl: command.baseUrl }),
3150
+ });
3151
+ output.stdout(command.json
3152
+ ? terminalSafeJson(result.report)
3153
+ : sanitizeTerminalText(renderPublishReportText(result.report, command.dryRun)));
3154
+ return 0;
3155
+ }
3156
+
3157
+ async function runServe(
3158
+ command: Extract<ParsedCommand, { readonly kind: "serve" }>,
3159
+ output: Output,
3160
+ dependencies: CliDependencies,
3161
+ ): Promise<number> {
3162
+ const site = await (dependencies.serveSite ?? serveSite)({
3163
+ root: command.root,
3164
+ host: command.host,
3165
+ port: command.port,
3166
+ });
3167
+ output.stdout(command.json
3168
+ ? terminalSafeJson({ root: site.root, host: site.host, port: site.port, url: site.url })
3169
+ : `Serving ${safe(site.root)} at ${safe(site.url)}\n`);
3170
+ return 0;
3171
+ }
3172
+
2831
3173
  async function runCatalog(
2832
3174
  command: Extract<ParsedCommand, { readonly kind: "catalog" }>,
2833
3175
  output: Output,
@@ -3574,6 +3916,8 @@ export async function main(
3574
3916
  }
3575
3917
  if (command.kind === "percolate") return await runPercolate(command, output, dependencies);
3576
3918
  if (command.kind === "list") return await runList(command, output, dependencies);
3919
+ if (command.kind === "publish") return await runPublish(command, output, dependencies);
3920
+ if (command.kind === "serve") return await runServe(command, output, dependencies);
3577
3921
  if (command.kind === "inbox") return await runInbox(command, output, dependencies);
3578
3922
  if (command.kind === "catalog") return await runCatalog(command, output, dependencies);
3579
3923
  return await runVault(command, output, dependencies);
package/src/cli.ts CHANGED
@@ -3,4 +3,15 @@ import { runExecutable } from "./cli-program.js";
3
3
 
4
4
  export * from "./cli-program.js";
5
5
 
6
- if (import.meta.main) process.exitCode = await runExecutable();
6
+ if (import.meta.main) {
7
+ const { standaloneSupportEnvironment, isUsefulSupportResult, runProductSupportCommand, showProductSupportInvitation } = await import("./support.js");
8
+ const env = standaloneSupportEnvironment();
9
+ const args = process.argv.slice(2);
10
+ if (args[0] === "support") {
11
+ process.exitCode = await runProductSupportCommand(args.slice(1), { env });
12
+ } else {
13
+ const exitCode = await runExecutable(args);
14
+ process.exitCode = exitCode;
15
+ if (exitCode === 0 && isUsefulSupportResult(args, env)) await showProductSupportInvitation({ env });
16
+ }
17
+ }
@@ -146,6 +146,8 @@ async function runCommand(
146
146
  stdin?: string,
147
147
  ): Promise<CommandResult> {
148
148
  const child = Bun.spawn([...command], {
149
+ // Bun otherwise inherits its launch-time environment instead of current CLI preferences.
150
+ env: process.env,
149
151
  stdin: stdin === undefined ? "ignore" : new Blob([stdin]),
150
152
  stdout: "pipe",
151
153
  stderr: "pipe",
@@ -172,6 +172,8 @@ async function readBoundedStream(stream: ReadableStream<Uint8Array>, maxBytes: n
172
172
 
173
173
  export const runDiagnosticCommand: DiagnosticCommandRunner = async (specification) => {
174
174
  const child = Bun.spawn([...specification.command], {
175
+ // Bun otherwise inherits its launch-time environment instead of current CLI preferences.
176
+ env: process.env,
175
177
  stdin: "ignore",
176
178
  stdout: "pipe",
177
179
  stderr: "pipe",
package/src/clip/media.ts CHANGED
@@ -274,6 +274,8 @@ export const runMediaCommand: MediaCommandRunner = async (specification) => {
274
274
  if (executable === undefined) throw new Error("media command is empty");
275
275
  const useProcessGroup = process.platform !== "win32";
276
276
  const child = spawn(executable, specification.command.slice(1), {
277
+ // Bun otherwise inherits its launch-time environment instead of current CLI preferences.
278
+ env: process.env,
277
279
  ...(specification.cwd === undefined ? {} : { cwd: specification.cwd }),
278
280
  detached: useProcessGroup,
279
281
  stdio: ["pipe", "pipe", "pipe"],
package/src/index.ts CHANGED
@@ -11,7 +11,11 @@ export * from "./init.js";
11
11
  export * from "./navigation.js";
12
12
  export * from "./oh-adoption.js";
13
13
  export * from "./percolate.js";
14
+ export * from "./publish.js";
15
+ export * from "./publish-model.js";
14
16
  export * from "./query.js";
17
+ export * from "./rerank.js";
18
+ export * from "./rerank-typesafe.js";
15
19
  export * from "./repository-memory.js";
16
20
  export * from "./search.js";
17
21
  export * from "./semantic.js";
@@ -3,7 +3,7 @@ import { isProxy } from "node:util/types";
3
3
  import { canonicalJson, canonicalSha256, type KnowledgeGraphChangeV1, type KnowledgeGraphRecordV1 } from "@hraness/oh";
4
4
  import {
5
5
  createOhProjectionDatasetV1, createOhProjectionFactV1, createOhProjectionSnapshotV1,
6
- evaluateOhProjectionV1, parseOhProjectionResultV1, type OhProjectionProofV1,
6
+ parseOhProjectionResultV1, type OhProjectionProofV1,
7
7
  } from "@hraness/oh/projection";
8
8
  import { createOhSqliteStoreAuthorityV1 } from "@hraness/oh/sqlite";
9
9
  import { createOhStoreProfileV1, OH_WORKING_STORE_PROFILE_V1, type OhStoreV1 } from "@hraness/oh/store";
@@ -11,6 +11,7 @@ import {
11
11
  GRAPH_LIMITS, GraphAuthorityError, type GraphAuthority, type GraphProof,
12
12
  type GraphQueryLimits, type GraphQueryRequest, type GraphQueryResult, type GraphSnapshot,
13
13
  } from "../graph-authority-model";
14
+ import { evaluateOhProjectionWithFallbackV1, loadProjectionRustEngineV1 } from "./projection-rust";
14
15
  import { compileGraphProgram, normalizeGraphQueryRequest } from "./programs";
15
16
  import { GRAPH_EXTRACTOR_SHA256, GRAPH_MANIFEST_KEY, manifestGraphRecord, parseGraphSnapshot, sourceGraphRecord } from "./snapshot";
16
17
  import { deepFreeze, detachedData, exactKeys } from "./validation";
@@ -95,6 +96,7 @@ export async function openOhGraphAdapter(input: GraphSnapshot, options: OhGraphA
95
96
  const materialized = await selectedStore.snapshot({ maximumRecords: GRAPH_LIMITS.notes + 1 });
96
97
  if (canonicalJson(materialized.records) !== canonicalJson(expected)) throw new GraphAuthorityError("corrupt-cache", "Graph reconciliation did not retain exact source records.");
97
98
  const projectionSnapshot = createOhProjectionSnapshotV1({ ...materialized, spaceId });
99
+ const rustEngine = await loadProjectionRustEngineV1();
98
100
  const recordMap = new Map(records.map((record, index) => [record.key, { record, source: snapshot.records[index]! }]));
99
101
  const assertOpen = () => { if (closed) throw new GraphAuthorityError("closed", "The graph authority is closed."); };
100
102
  const assertCurrent = async () => {
@@ -141,10 +143,17 @@ export async function openOhGraphAdapter(input: GraphSnapshot, options: OhGraphA
141
143
  });
142
144
  try {
143
145
  const dataset = createOhProjectionDatasetV1({ extractorSha256: GRAPH_EXTRACTOR_SHA256, factPackId: "wordcell.graph-facts", factPackRevision: 1, facts, snapshot: projectionSnapshot });
144
- const result = evaluateOhProjectionV1({ ...program, dataset, snapshot: projectionSnapshot, options: {
145
- maximumDerivedTuples: limits.derivedTuples, maximumRounds: limits.rounds, maximumWorkUnits: limits.workUnits,
146
- maximumProofDepth: limits.proofDepth, maximumProofNodes: limits.proofNodes, maximumTotalProofNodes: limits.totalProofNodes, maximumResultBytes: limits.resultBytes,
147
- } });
146
+ const result = evaluateOhProjectionWithFallbackV1({
147
+ dataset,
148
+ engine: rustEngine,
149
+ rulePack: program.rulePack,
150
+ query: program.query,
151
+ snapshot: projectionSnapshot,
152
+ options: {
153
+ maximumDerivedTuples: limits.derivedTuples, maximumRounds: limits.rounds, maximumWorkUnits: limits.workUnits,
154
+ maximumProofDepth: limits.proofDepth, maximumProofNodes: limits.proofNodes, maximumTotalProofNodes: limits.totalProofNodes, maximumResultBytes: limits.resultBytes,
155
+ },
156
+ });
148
157
  if (parseOhProjectionResultV1(result, result.identity.projectionSha256) === null) throw new GraphAuthorityError("corrupt-cache", "Oh returned an invalid bounded projection result.");
149
158
  await assertCurrent();
150
159
  const proofBudget = { remaining: limits.resultBytes };