@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
@@ -334,12 +334,24 @@ async function snapshot(rootInput, options, writeIndex) {
334
334
  ...options.maxNoteBytes === undefined ? {} : { maxNoteBytes: options.maxNoteBytes },
335
335
  ...options.maxTotalBytes === undefined ? {} : { maxTotalBytes: options.maxTotalBytes }
336
336
  });
337
- const indexRevision = await readIndexRevision(root, indexPath, options.maxNoteBytes ?? MAX_NOTE_UTF8_BYTES);
338
- const currentIndex = indexRevision.content;
339
- const indexNote = parseNote(vaultIndexPath, currentIndex);
340
- const catalogMode = parsedCatalogMode(options.catalogMode, "ScanVaultOptions.catalogMode") ?? declaredCatalogMode(indexNote) ?? "managed";
337
+ const configuredCatalogMode = parsedCatalogMode(options.catalogMode, "ScanVaultOptions.catalogMode");
338
+ let indexRevision;
339
+ try {
340
+ indexRevision = await readIndexRevision(root, indexPath, options.maxNoteBytes ?? MAX_NOTE_UTF8_BYTES);
341
+ } catch (error) {
342
+ const absent = error instanceof Error && "code" in error && error.code === "ENOENT";
343
+ if (!absent || options.index !== undefined || configuredCatalogMode === "managed" || notes.some((note) => note.path === vaultIndexPath))
344
+ throw error;
345
+ if (writeIndex) {
346
+ throw new Error("Refresh requires an index.md front door. Add one with kb_catalog: authored to preserve its contents, or catalog markers for a managed catalog. Search and graph commands can read this folder without one.", { cause: error });
347
+ }
348
+ }
349
+ const currentIndex = indexRevision?.content ?? "";
350
+ const catalogMode = configuredCatalogMode ?? (indexRevision === undefined ? "authored" : declaredCatalogMode(parseNote(vaultIndexPath, currentIndex)) ?? "managed");
341
351
  let index = "authored";
342
352
  if (catalogMode === "managed") {
353
+ if (indexRevision === undefined)
354
+ throw new Error("A managed catalog requires an existing index file.");
343
355
  const expectedIndex = replaceCatalog(currentIndex, renderCatalog(notes, catalogNoteId));
344
356
  const stale = currentIndex !== expectedIndex;
345
357
  index = stale ? "stale" : "current";
@@ -1,8 +1,4 @@
1
1
  // @bun
2
- import {
3
- UntrustedContentBudgetError,
4
- createUntrustedToolResult
5
- } from "./index-4j3tt0c3.js";
6
2
  import {
7
3
  expandSearchRequest,
8
4
  parseSearchRules,
@@ -11,20 +7,26 @@ import {
11
7
  import {
12
8
  openSemanticSearchSession,
13
9
  recommendedEmbeddingModel
14
- } from "./index-115b07ap.js";
10
+ } from "./index-hcw140eb.js";
11
+ import {
12
+ UntrustedContentBudgetError,
13
+ createUntrustedToolResult
14
+ } from "./index-4j3tt0c3.js";
15
+ import {
16
+ MAX_RERANK_CANDIDATES,
17
+ MAX_RERANK_SNIPPET_BYTES,
18
+ applyRerank
19
+ } from "./index-j70m75wd.js";
15
20
  import {
16
21
  percolateWithGraph
17
- } from "./index-bgfzwt4h.js";
22
+ } from "./index-t2bs9xdr.js";
18
23
  import {
19
24
  openGraphAuthority
20
- } from "./index-bcfn9xah.js";
25
+ } from "./index-famy7fhs.js";
21
26
  import {
22
27
  validateGraphPercolationOptions,
23
28
  validateGraphQueryRequest
24
29
  } from "./index-11621h23.js";
25
- import {
26
- scanVault
27
- } from "./index-0k2x4nn9.js";
28
30
  import {
29
31
  GitHistoryError,
30
32
  gitHistoryForNotes,
@@ -39,14 +41,17 @@ import {
39
41
  fuseRankedCandidates,
40
42
  searchExactVault,
41
43
  validateSearchQuery
42
- } from "./index-why54q5p.js";
44
+ } from "./index-gm9t95d9.js";
43
45
  import {
44
- queryVault
45
- } from "./index-48pz4jpc.js";
46
+ scanVault
47
+ } from "./index-1tm7bgx7.js";
46
48
  import {
47
49
  NavigationBudgetError,
48
50
  navigateLinks
49
51
  } from "./index-d13v9ckt.js";
52
+ import {
53
+ queryVault
54
+ } from "./index-48pz4jpc.js";
50
55
  import {
51
56
  lookupNote
52
57
  } from "./index-ekpwvbra.js";
@@ -83,6 +88,9 @@ function noteOrThrow(notes, query) {
83
88
  function errorMessage(error) {
84
89
  return error instanceof Error ? error.message : String(error);
85
90
  }
91
+ function searchEvidenceRank(item) {
92
+ return item.kind === "rerank" ? item.rerankRank : item.rank;
93
+ }
86
94
  function utf8Prefix(value, maximumBytes) {
87
95
  let bytes = 0;
88
96
  let end = 0;
@@ -203,6 +211,29 @@ function checkedHistoryRequest(value) {
203
211
  function validateKnowledgeBaseSearchHistory(value) {
204
212
  checkedHistoryRequest(value);
205
213
  }
214
+ function checkedRerankRequest(value, rerankers) {
215
+ if (value === undefined)
216
+ return { enabled: false };
217
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
218
+ throw new TypeError("Search rerank options must be an options object.");
219
+ }
220
+ const options = value;
221
+ if (options.engine !== "typesafe") {
222
+ throw new Error('Search rerank engine must be "typesafe".');
223
+ }
224
+ const reranker = rerankers?.find((entry) => entry.id === options.engine);
225
+ if (reranker === undefined) {
226
+ throw new Error(`Search rerank engine ${JSON.stringify(options.engine)} is not available in this session.`);
227
+ }
228
+ const limit = options.limit ?? MAX_RERANK_CANDIDATES;
229
+ if (!Number.isSafeInteger(limit) || limit < 2 || limit > MAX_RERANK_CANDIDATES) {
230
+ throw new RangeError(`Search rerank limit must be an integer from 2 through ${MAX_RERANK_CANDIDATES}.`);
231
+ }
232
+ if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) {
233
+ throw new TypeError("Search rerank signal must be an AbortSignal.");
234
+ }
235
+ return { enabled: true, limit, reranker, ...options.signal === undefined ? {} : { signal: options.signal } };
236
+ }
206
237
  async function openKnowledgeBase(options, dependencies = {}) {
207
238
  const searchRules = options.searchRules === undefined ? null : parseSearchRules(options.searchRules);
208
239
  if (options.embeddingModelFile !== undefined && options.embeddingModelLease !== undefined) {
@@ -322,6 +353,7 @@ async function openKnowledgeBase(options, dependencies = {}) {
322
353
  }
323
354
  const historyRequest = checkedHistoryRequest(effectiveOptions.history);
324
355
  const graphOptions = checkedGraphOptions(effectiveOptions.graph);
356
+ const rerankRequest = checkedRerankRequest(effectiveOptions.rerank, dependencies.rerankers);
325
357
  const explicitSeeds = graphOptions === null ? [] : resolvedGraphSeeds(snapshot.notes, graphOptions.related);
326
358
  const historyNoteLimit = historyRequest.enabled ? historyRequest.noteLimit : null;
327
359
  const allowedIds = new Set(queryVault(snapshot.notes, snapshot.analysis, {
@@ -415,7 +447,8 @@ async function openKnowledgeBase(options, dependencies = {}) {
415
447
  const fused = fuseRankedCandidates(lanes);
416
448
  const ordered = fused.toSorted((left, right) => Number(exactById.get(right.id)?.hit.identity ?? false) - Number(exactById.get(left.id)?.hit.identity ?? false) || left.rank - right.rank || left.id.localeCompare(right.id));
417
449
  const shouldApplyPriority = ordering === "priority-then-relevance" && searchRules !== null && searchRules.priorityRules.length > 0;
418
- const relevanceResults = (shouldApplyPriority ? ordered : ordered.slice(0, limit)).map((candidate, index) => {
450
+ const resultWindow = rerankRequest.enabled ? Math.max(limit, rerankRequest.limit) : limit;
451
+ const relevanceResults = (shouldApplyPriority ? ordered : ordered.slice(0, resultWindow)).map((candidate, index) => {
419
452
  const note = notesById.get(candidate.id);
420
453
  if (note === undefined) {
421
454
  throw new Error(`Fused retrieval returned unknown note ${JSON.stringify(candidate.id)}.`);
@@ -458,10 +491,67 @@ async function openKnowledgeBase(options, dependencies = {}) {
458
491
  contributions: candidate.contributions
459
492
  };
460
493
  });
494
+ let rankedResults = relevanceResults;
495
+ if (rerankRequest.enabled) {
496
+ const candidates = relevanceResults.slice(0, rerankRequest.limit).map((hit, index) => ({
497
+ id: hit.id,
498
+ baselineRank: index + 1,
499
+ score: hit.score,
500
+ title: hit.title,
501
+ path: hit.path,
502
+ snippet: utf8Prefix(hit.snippet, MAX_RERANK_SNIPPET_BYTES).value
503
+ }));
504
+ const outcome = await Promise.resolve().then(() => rerankRequest.reranker.rerank({
505
+ query: effectiveQuery,
506
+ candidates,
507
+ ...rerankRequest.signal === undefined ? {} : { signal: rerankRequest.signal }
508
+ })).then((result) => ({ ok: true, result }), (error) => ({ ok: false, error }));
509
+ const applied = applyRerank(relevanceResults, outcome.ok ? outcome.result : {
510
+ status: "failed",
511
+ message: "Rerank engine failed before returning a result."
512
+ }, candidates.map(({ id }) => id));
513
+ rankedResults = applied.hits.map((hit) => {
514
+ const placement = applied.placements.get(hit.id);
515
+ if (placement === undefined)
516
+ return hit;
517
+ return {
518
+ ...hit,
519
+ evidence: [
520
+ ...hit.evidence,
521
+ {
522
+ kind: "rerank",
523
+ engine: rerankRequest.reranker.id,
524
+ baselineRank: placement.baselineRank,
525
+ rerankRank: placement.rerankRank,
526
+ ...placement.probability === undefined ? {} : { probability: placement.probability }
527
+ }
528
+ ]
529
+ };
530
+ });
531
+ const resultDetails = {
532
+ ...applied.result.model === undefined ? {} : { model: applied.result.model },
533
+ ...applied.result.usage === undefined ? {} : { usage: applied.result.usage },
534
+ ...applied.result.accounting === undefined ? {} : { accounting: applied.result.accounting }
535
+ };
536
+ const details = [];
537
+ if (resultDetails.model !== undefined)
538
+ details.push(resultDetails.model);
539
+ if (resultDetails.usage !== undefined) {
540
+ details.push(`${(resultDetails.usage.inputTokens ?? 0).toLocaleString("en-US")} input tokens, ` + `${(resultDetails.usage.outputTokens ?? 0).toLocaleString("en-US")} output tokens`);
541
+ }
542
+ const rerankMessage = applied.status === "ready" ? details.length === 0 ? undefined : details.join("; ") : applied.message ?? "Rerank engine did not return a result.";
543
+ diagnostics.push({
544
+ lane: "rerank",
545
+ ...Object.keys(resultDetails).length === 0 ? {} : { rerank: resultDetails },
546
+ status: applied.status === "failed" ? "degraded" : applied.status,
547
+ results: applied.status === "ready" ? applied.placements.size : 0,
548
+ ...rerankMessage === undefined ? {} : { message: rerankMessage }
549
+ });
550
+ }
461
551
  let priorityTrace = null;
462
- let results = relevanceResults.slice(0, limit);
552
+ let results = rankedResults.slice(0, limit);
463
553
  if (shouldApplyPriority && searchRules !== null) {
464
- const prioritized = prioritizeSearchHits(relevanceResults, searchRules, {
554
+ const prioritized = prioritizeSearchHits(rankedResults, searchRules, {
465
555
  ...options.vaultId === undefined ? {} : { vaultId: options.vaultId }
466
556
  });
467
557
  const selectedHits = prioritized.hits.slice(0, limit);
@@ -696,7 +786,7 @@ Evidence: `))
696
786
  return writer.result();
697
787
  if (!append("#"))
698
788
  return writer.result();
699
- if (!append(String(item.rank)))
789
+ if (!append(String(searchEvidenceRank(item))))
700
790
  return writer.result();
701
791
  }
702
792
  if (!append(`
@@ -1015,4 +1105,4 @@ function packUntrustedSearchContext(result, options = {}) {
1015
1105
  });
1016
1106
  }
1017
1107
 
1018
- export { MAX_SEARCH_RESULTS, MAX_SEARCH_CANDIDATES, DEFAULT_SEARCH_RESULTS, MIN_UNTRUSTED_CONTEXT_BYTES, MAX_SEARCH_RELATED_SEEDS, MAX_SEARCH_NOTE_REFERENCE_BYTES, validateKnowledgeBaseSearchHistory, openKnowledgeBase, packSearchContext, packUntrustedSearchContext };
1108
+ export { MAX_SEARCH_RESULTS, MAX_SEARCH_CANDIDATES, DEFAULT_SEARCH_RESULTS, MIN_UNTRUSTED_CONTEXT_BYTES, MAX_SEARCH_RELATED_SEEDS, MAX_SEARCH_NOTE_REFERENCE_BYTES, searchEvidenceRank, validateKnowledgeBaseSearchHistory, openKnowledgeBase, packSearchContext, packUntrustedSearchContext };
@@ -0,0 +1,306 @@
1
+ // @bun
2
+ // src/publish-search.ts
3
+ var PUBLISH_FIELD_TITLE = 1;
4
+ var PUBLISH_FIELD_ALIAS = 2;
5
+ var PUBLISH_FIELD_PATH = 4;
6
+ var PUBLISH_FIELD_TAG = 8;
7
+ var PUBLISH_FIELD_METADATA = 16;
8
+ var PUBLISH_FIELD_CONTENT = 32;
9
+ var PUBLISH_SEARCH_WEIGHTS_V1 = Object.freeze({
10
+ identity: Object.freeze({ title: 1000, alias: 950, path: 900 }),
11
+ phrase: Object.freeze({
12
+ title: 400,
13
+ alias: 350,
14
+ path: 300,
15
+ tag: 250,
16
+ content: 150,
17
+ metadata: 100
18
+ }),
19
+ term: Object.freeze({
20
+ title: 40,
21
+ alias: 35,
22
+ path: 30,
23
+ tag: 30,
24
+ metadata: 10,
25
+ content: 5
26
+ }),
27
+ coverage: 100
28
+ });
29
+ var MAX_PUBLISH_QUERY_BYTES = 16 * 1024;
30
+ var MAX_PUBLISH_QUERY_TERMS = 64;
31
+ var MAX_PUBLISH_PREFIX_EXPANSIONS = 32;
32
+ var TERM_PATTERN = /[\p{L}\p{N}][\p{L}\p{N}._/-]*/gu;
33
+ var textEncoder = new TextEncoder;
34
+ function publishUtf8Bytes(value) {
35
+ return textEncoder.encode(value).byteLength;
36
+ }
37
+ function publishNormalize(value) {
38
+ return value.normalize("NFC").toLocaleLowerCase("en-US");
39
+ }
40
+ var MAX_PUBLISH_QUERY_FILTERS = 8;
41
+ var FILTER_PATTERN = /^(tag|type|path):(\S+)$/u;
42
+ function trimSlashes(value) {
43
+ let start = 0;
44
+ let end = value.length;
45
+ while (start < end && value.charCodeAt(start) === 47)
46
+ start += 1;
47
+ while (end > start && value.charCodeAt(end - 1) === 47)
48
+ end -= 1;
49
+ return value.slice(start, end);
50
+ }
51
+ function publishQueryParts(raw) {
52
+ const tags = [];
53
+ const types = [];
54
+ const paths = [];
55
+ const rest = [];
56
+ for (const token of raw.split(/\s+/u)) {
57
+ if (token === "")
58
+ continue;
59
+ const match = FILTER_PATTERN.exec(token);
60
+ const value = match === null ? "" : publishNormalize(trimSlashes(match[2] ?? ""));
61
+ if (match === null || value === "") {
62
+ rest.push(token);
63
+ continue;
64
+ }
65
+ const bucket = match[1] === "tag" ? tags : match[1] === "type" ? types : paths;
66
+ if (bucket.length < MAX_PUBLISH_QUERY_FILTERS)
67
+ bucket.push(value);
68
+ else
69
+ rest.push(token);
70
+ }
71
+ return { filters: { tags, types, paths }, text: rest.join(" ") };
72
+ }
73
+ function publishDocMatchesFilters(doc, filters, type) {
74
+ if (filters.tags.length > 0) {
75
+ const tags = doc.f.g === "" ? [] : doc.f.g.split(`
76
+ `);
77
+ for (const tag of filters.tags) {
78
+ if (!tags.includes(publishNormalize(tag)))
79
+ return false;
80
+ }
81
+ }
82
+ if (filters.types.length > 0) {
83
+ const docType = publishNormalize(type === undefined || type === "" ? "note" : type);
84
+ for (const wanted of filters.types) {
85
+ if (docType !== publishNormalize(wanted))
86
+ return false;
87
+ }
88
+ }
89
+ if (filters.paths.length > 0) {
90
+ const slug = publishNormalize(doc.s);
91
+ const fields = doc.f.p === "" ? [] : doc.f.p.split(`
92
+ `);
93
+ for (const value of filters.paths) {
94
+ const prefix = publishNormalize(trimSlashes(value));
95
+ const boundary = `${prefix}/`;
96
+ const inSlug = slug === prefix || slug.startsWith(boundary);
97
+ const inFields = fields.some((line) => line === prefix || line.startsWith(boundary));
98
+ if (!inSlug && !inFields)
99
+ return false;
100
+ }
101
+ }
102
+ return true;
103
+ }
104
+ var MAX_PUBLISH_MARK_RANGES = 32;
105
+ function publishMarkRanges(text, terms, maximum = MAX_PUBLISH_MARK_RANGES) {
106
+ const display = text.normalize("NFC");
107
+ let normalized = "";
108
+ const map = [];
109
+ for (let offset = 0;offset < display.length; ) {
110
+ const point = display.codePointAt(offset) ?? 0;
111
+ const character = String.fromCodePoint(point);
112
+ const lowered = character.toLocaleLowerCase("en-US");
113
+ for (let index = 0;index < lowered.length; index += 1)
114
+ map.push(offset);
115
+ normalized += lowered;
116
+ offset += character.length;
117
+ }
118
+ const ranges = [];
119
+ for (const term of terms) {
120
+ const needle = publishNormalize(term);
121
+ if (needle === "")
122
+ continue;
123
+ let from = 0;
124
+ while (ranges.length < maximum) {
125
+ const hit = normalized.indexOf(needle, from);
126
+ if (hit === -1)
127
+ break;
128
+ const start = map[hit] ?? 0;
129
+ const tail = hit + needle.length;
130
+ const end = tail >= map.length ? display.length : map[tail] ?? display.length;
131
+ if (end > start)
132
+ ranges.push({ start, end });
133
+ from = tail === 0 ? 1 : tail;
134
+ }
135
+ if (ranges.length >= maximum)
136
+ break;
137
+ }
138
+ ranges.sort((left, right) => left.start - right.start || left.end - right.end);
139
+ const merged = [];
140
+ for (const range of ranges) {
141
+ const last = merged[merged.length - 1];
142
+ if (last !== undefined && range.start <= last.end) {
143
+ merged[merged.length - 1] = { start: last.start, end: Math.max(last.end, range.end) };
144
+ } else {
145
+ merged.push(range);
146
+ }
147
+ }
148
+ return merged;
149
+ }
150
+ function publishQuery(value) {
151
+ if (typeof value !== "string") {
152
+ throw new TypeError("Search query must be a string.");
153
+ }
154
+ if (publishUtf8Bytes(value) > MAX_PUBLISH_QUERY_BYTES) {
155
+ throw new RangeError(`Search query exceeds the ${MAX_PUBLISH_QUERY_BYTES}-byte limit.`);
156
+ }
157
+ const raw = value.trim();
158
+ const normalized = publishNormalize(raw);
159
+ const terms = [];
160
+ const seen = new Set;
161
+ for (const match of normalized.matchAll(TERM_PATTERN)) {
162
+ if (seen.has(match[0]))
163
+ continue;
164
+ seen.add(match[0]);
165
+ terms.push(match[0]);
166
+ if (terms.length > MAX_PUBLISH_QUERY_TERMS) {
167
+ throw new RangeError(`Search query may contain at most ${MAX_PUBLISH_QUERY_TERMS} unique normalized terms.`);
168
+ }
169
+ }
170
+ return { raw, normalized, terms };
171
+ }
172
+ function publishShardName(term) {
173
+ let hash = 2166136261;
174
+ for (let index = 0;index < term.length; index += 1) {
175
+ hash ^= term.charCodeAt(index);
176
+ hash = Math.imul(hash, 16777619) >>> 0;
177
+ }
178
+ return (hash >>> 24).toString(16).padStart(2, "0");
179
+ }
180
+ function publishPrefixTerms(dictionary, prefix, maximum = MAX_PUBLISH_PREFIX_EXPANSIONS) {
181
+ if (prefix === "")
182
+ return [];
183
+ let low = 0;
184
+ let high = dictionary.length;
185
+ while (low < high) {
186
+ const middle = low + high >> 1;
187
+ if ((dictionary[middle] ?? "") < prefix)
188
+ low = middle + 1;
189
+ else
190
+ high = middle;
191
+ }
192
+ const found = [];
193
+ for (let index = low;index < dictionary.length && found.length < maximum; index += 1) {
194
+ const term = dictionary[index] ?? "";
195
+ if (!term.startsWith(prefix))
196
+ break;
197
+ found.push(term);
198
+ }
199
+ return found;
200
+ }
201
+ function fieldMasks(term, fields) {
202
+ let mask = 0;
203
+ if (fields.t.includes(term))
204
+ mask |= PUBLISH_FIELD_TITLE;
205
+ if (fields.a.includes(term))
206
+ mask |= PUBLISH_FIELD_ALIAS;
207
+ if (fields.p.includes(term))
208
+ mask |= PUBLISH_FIELD_PATH;
209
+ if (fields.g.includes(term))
210
+ mask |= PUBLISH_FIELD_TAG;
211
+ if (fields.m.includes(term))
212
+ mask |= PUBLISH_FIELD_METADATA;
213
+ return mask;
214
+ }
215
+ function scorePublishDocument(input, query) {
216
+ const fields = input.doc.f;
217
+ const phrase = query.normalized;
218
+ const weights = PUBLISH_SEARCH_WEIGHTS_V1;
219
+ let score = 0;
220
+ let identity = false;
221
+ let phraseMatched = false;
222
+ if (phrase !== "") {
223
+ if (fields.t === phrase) {
224
+ identity = true;
225
+ score += weights.identity.title;
226
+ }
227
+ if (fields.a.split(`
228
+ `).includes(phrase)) {
229
+ identity = true;
230
+ score += weights.identity.alias;
231
+ }
232
+ if (fields.p.split(`
233
+ `).includes(phrase)) {
234
+ identity = true;
235
+ score += weights.identity.path;
236
+ }
237
+ const phraseFields = [
238
+ [PUBLISH_FIELD_TITLE, fields.t, weights.phrase.title],
239
+ [PUBLISH_FIELD_ALIAS, fields.a, weights.phrase.alias],
240
+ [PUBLISH_FIELD_PATH, fields.p, weights.phrase.path],
241
+ [PUBLISH_FIELD_TAG, fields.g, weights.phrase.tag],
242
+ [PUBLISH_FIELD_METADATA, fields.m, weights.phrase.metadata]
243
+ ];
244
+ for (const [, value, weight] of phraseFields) {
245
+ if (value.includes(phrase)) {
246
+ phraseMatched = true;
247
+ score += weight;
248
+ }
249
+ }
250
+ if (input.contentText !== undefined && input.contentText.includes(phrase)) {
251
+ phraseMatched = true;
252
+ score += weights.phrase.content;
253
+ }
254
+ }
255
+ let matchedTerms = 0;
256
+ for (const term of query.terms) {
257
+ const mask = fieldMasks(term, fields) | (input.contentTerms.has(term) ? PUBLISH_FIELD_CONTENT : 0);
258
+ if (mask === 0)
259
+ continue;
260
+ matchedTerms += 1;
261
+ if (mask & PUBLISH_FIELD_TITLE)
262
+ score += weights.term.title;
263
+ if (mask & PUBLISH_FIELD_ALIAS)
264
+ score += weights.term.alias;
265
+ if (mask & PUBLISH_FIELD_PATH)
266
+ score += weights.term.path;
267
+ if (mask & PUBLISH_FIELD_TAG)
268
+ score += weights.term.tag;
269
+ if (mask & PUBLISH_FIELD_METADATA)
270
+ score += weights.term.metadata;
271
+ if (mask & PUBLISH_FIELD_CONTENT)
272
+ score += weights.term.content;
273
+ }
274
+ const requiredTerms = query.terms.length <= 1 ? query.terms.length : Math.min(3, Math.ceil(query.terms.length / 2));
275
+ if (!identity && !phraseMatched && matchedTerms < requiredTerms)
276
+ return null;
277
+ if (query.terms.length > 0) {
278
+ score += Math.round(matchedTerms / query.terms.length * weights.coverage);
279
+ }
280
+ if (score === 0)
281
+ return null;
282
+ return { score, identity, phraseMatched, matchedTerms };
283
+ }
284
+ function comparePublishScores(left, right) {
285
+ return Number(right.identity) - Number(left.identity) || right.score - left.score || left.i - right.i;
286
+ }
287
+ function publishSnippet(text, query, fallback, windowBytes = 160) {
288
+ const normalized = publishNormalize(text);
289
+ let offset = query.normalized === "" ? -1 : normalized.indexOf(query.normalized);
290
+ if (offset < 0) {
291
+ for (const term of query.terms) {
292
+ offset = normalized.indexOf(term);
293
+ if (offset >= 0)
294
+ break;
295
+ }
296
+ }
297
+ if (offset < 0)
298
+ return fallback;
299
+ const half = Math.floor(windowBytes / 2);
300
+ const start = Math.max(0, offset - half);
301
+ const end = Math.min(text.length, offset + half);
302
+ const snippet = text.slice(start, end).replace(/\s+/gu, " ").trim();
303
+ return `${start > 0 ? "\u2026" : ""}${snippet}${end < text.length ? "\u2026" : ""}`;
304
+ }
305
+
306
+ export { PUBLISH_FIELD_TITLE, PUBLISH_FIELD_ALIAS, PUBLISH_FIELD_PATH, PUBLISH_FIELD_TAG, PUBLISH_FIELD_METADATA, PUBLISH_FIELD_CONTENT, PUBLISH_SEARCH_WEIGHTS_V1, MAX_PUBLISH_QUERY_BYTES, MAX_PUBLISH_QUERY_TERMS, MAX_PUBLISH_PREFIX_EXPANSIONS, publishUtf8Bytes, publishNormalize, MAX_PUBLISH_QUERY_FILTERS, publishQueryParts, publishDocMatchesFilters, MAX_PUBLISH_MARK_RANGES, publishMarkRanges, publishQuery, publishShardName, publishPrefixTerms, scorePublishDocument, comparePublishScores, publishSnippet };