@hraness/wordcell 0.21.3 → 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.
- package/README.md +170 -64
- package/dist/{authority-4sp9d552.js → authority-1pgftmwq.js} +55 -10
- package/dist/benchmark.js +3 -3
- package/dist/capture.js +6 -5
- package/dist/cli.js +37 -31
- package/dist/clip/acquire.js +3 -2
- package/dist/clip/cli.js +6 -5
- package/dist/clip/doctor.js +4 -3
- package/dist/clip/jobs.js +3 -3
- package/dist/clip/refresh.js +1 -1
- package/dist/evaluation-builder.js +18 -17
- package/dist/evaluation-kb.js +14 -13
- package/dist/graph-authority.js +3 -3
- package/dist/graph-percolation.js +5 -5
- package/dist/index-0dqfnrz9.js +1985 -0
- package/dist/{index-0k2x4nn9.js → index-1tm7bgx7.js} +16 -4
- package/dist/{index-vdw5yjfa.js → index-2hf27mws.js} +108 -18
- package/dist/index-3agn8scn.js +306 -0
- package/dist/{index-0rfcqab6.js → index-4ag64rc1.js} +555 -59
- package/dist/index-4knsp9qj.js +34 -0
- package/dist/index-66pshdtx.js +341 -0
- package/dist/index-9rf81m0p.js +368 -0
- package/dist/{index-59ssmqqm.js → index-a6kbv718.js} +1 -1
- package/dist/{index-51bap0mq.js → index-avbce90h.js} +9 -9
- package/dist/{index-9zcpdbjg.js → index-de2w8crk.js} +13 -13
- package/dist/{index-pbr6rv47.js → index-e6p9e926.js} +6 -6
- package/dist/{index-bcfn9xah.js → index-famy7fhs.js} +5 -5
- package/dist/{index-van48n3g.js → index-fc4dr114.js} +4 -2
- package/dist/{index-jzcws0zn.js → index-g5vsqmdy.js} +27 -57
- package/dist/{index-115b07ap.js → index-hcw140eb.js} +5 -5
- package/dist/index-j70m75wd.js +217 -0
- package/dist/{index-3ehtt59c.js → index-qwgsmtsz.js} +1 -1
- package/dist/{index-bgfzwt4h.js → index-t2bs9xdr.js} +4 -4
- package/dist/index.js +285 -93
- package/dist/portfolio.js +14 -13
- package/dist/publish-model.js +36 -0
- package/dist/publish-reader/reader.css +1042 -0
- package/dist/publish-reader/reader.js +1534 -0
- package/dist/publish-reader/theme.js +92 -0
- package/dist/publish-search.js +52 -0
- package/dist/publish.js +36 -0
- package/dist/rerank-typesafe.js +13 -0
- package/dist/rerank.js +14 -0
- package/dist/sdk.js +14 -11
- package/dist/search.js +2 -2
- package/dist/semantic.js +4 -4
- package/dist/{support-vm4mkw0k.js → support-mywv1em0.js} +38 -32
- package/dist/workflows/decision-context.js +13 -12
- package/dist/workflows/index.js +13 -12
- package/package.json +63 -5
- package/skills/wordcell/AGENTS.md +1 -0
- package/skills/wordcell/SKILL.md +2 -1
- package/skills/wordcell/agents/openai.yaml +1 -1
- package/skills/wordcell/references/publish.md +93 -0
- package/skills/wordcell/references/query.md +22 -0
- package/src/cli-program.ts +347 -9
- package/src/index.ts +4 -0
- package/src/oh/authority.ts +14 -5
- package/src/oh/canonical-rust.ts +127 -0
- package/src/oh/projection-rust.ts +63 -0
- package/src/oh-adoption.ts +7 -1
- package/src/publish-graph.ts +0 -0
- package/src/publish-index.ts +195 -0
- package/src/publish-markdown.ts +617 -0
- package/src/publish-model.ts +599 -0
- package/src/publish-nav.ts +204 -0
- package/src/publish-pages.ts +382 -0
- package/src/publish-reader/reader.css +1042 -0
- package/src/publish-reader/reader.ts +1017 -0
- package/src/publish-reader/theme.ts +97 -0
- package/src/publish-search.ts +418 -0
- package/src/publish-select.ts +419 -0
- package/src/publish-theme.ts +106 -0
- package/src/publish.ts +904 -0
- package/src/rerank-credentials.ts +56 -0
- package/src/rerank-typesafe.ts +472 -0
- package/src/rerank.ts +389 -0
- package/src/sdk.ts +157 -6
- package/src/serve.ts +180 -0
- package/src/vault.ts +23 -9
- 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
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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-
|
|
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-
|
|
22
|
+
} from "./index-t2bs9xdr.js";
|
|
18
23
|
import {
|
|
19
24
|
openGraphAuthority
|
|
20
|
-
} from "./index-
|
|
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-
|
|
44
|
+
} from "./index-gm9t95d9.js";
|
|
43
45
|
import {
|
|
44
|
-
|
|
45
|
-
} from "./index-
|
|
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
|
|
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 =
|
|
552
|
+
let results = rankedResults.slice(0, limit);
|
|
463
553
|
if (shouldApplyPriority && searchRules !== null) {
|
|
464
|
-
const prioritized = prioritizeSearchHits(
|
|
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
|
|
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 };
|