@agenr/skeln-plugin 3.3.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.
@@ -0,0 +1,2436 @@
1
+ // src/core/recall/cross-encoder.ts
2
+ var DEFAULT_CROSS_ENCODER_TOP_K = 10;
3
+ var DEFAULT_CROSS_ENCODER_ALPHA = 0.6;
4
+ async function applyCrossEncoderRerank(options) {
5
+ const candidates = [...options.candidates];
6
+ const k = resolveTopK(options.topK, candidates.length);
7
+ const alpha = resolveAlpha(options.alpha);
8
+ const startedAt = Date.now();
9
+ const passthrough = (degradedReason) => ({
10
+ applied: false,
11
+ k,
12
+ alpha,
13
+ latencyMs: elapsedMs(startedAt),
14
+ ...degradedReason ? { degradedReason } : {},
15
+ candidates: candidates.map((candidate) => ({
16
+ candidate: candidate.candidate,
17
+ score: candidate.score
18
+ })),
19
+ rescoredIds: []
20
+ });
21
+ if (options.disabled === true) {
22
+ return passthrough("disabled");
23
+ }
24
+ if (!options.port) {
25
+ return passthrough("not_configured");
26
+ }
27
+ if (candidates.length === 0) {
28
+ return passthrough("no_candidates");
29
+ }
30
+ const shortlist = candidates.slice(0, k);
31
+ const tail = candidates.slice(k);
32
+ const query = options.query.trim();
33
+ if (query.length === 0 || shortlist.length === 0) {
34
+ return passthrough("no_candidates");
35
+ }
36
+ let scores;
37
+ try {
38
+ scores = await options.port.rank(
39
+ query,
40
+ shortlist.map((candidate) => ({ id: candidate.id, text: candidate.text }))
41
+ );
42
+ } catch {
43
+ return passthrough("provider_error");
44
+ }
45
+ if (!Array.isArray(scores)) {
46
+ return passthrough("provider_error");
47
+ }
48
+ const scoreById = /* @__PURE__ */ new Map();
49
+ for (const entry of scores) {
50
+ if (!entry || typeof entry.id !== "string" || typeof entry.score !== "number" || !Number.isFinite(entry.score)) {
51
+ continue;
52
+ }
53
+ scoreById.set(entry.id, clampUnit(entry.score));
54
+ }
55
+ if (scoreById.size === 0) {
56
+ return passthrough("provider_error");
57
+ }
58
+ const rescoredIds = [];
59
+ const rescoredShortlist = shortlist.map((candidate) => {
60
+ const crossEncoderScore = scoreById.get(candidate.id);
61
+ if (crossEncoderScore === void 0) {
62
+ return {
63
+ candidate: candidate.candidate,
64
+ score: candidate.score,
65
+ id: candidate.id,
66
+ rescored: false
67
+ };
68
+ }
69
+ const nextScore = clampUnit(alpha * crossEncoderScore + (1 - alpha) * candidate.score);
70
+ if (nextScore !== candidate.score) {
71
+ rescoredIds.push(candidate.id);
72
+ }
73
+ return {
74
+ candidate: candidate.candidate,
75
+ score: nextScore,
76
+ crossEncoderScore,
77
+ id: candidate.id,
78
+ rescored: true
79
+ };
80
+ });
81
+ const reorderedShortlist = [...rescoredShortlist].sort((left, right) => {
82
+ if (left.score !== right.score) {
83
+ return right.score - left.score;
84
+ }
85
+ return shortlist.findIndex((candidate) => candidate.id === left.id) - shortlist.findIndex((candidate) => candidate.id === right.id);
86
+ });
87
+ const shortlistOutput = reorderedShortlist.map(({ candidate, score, crossEncoderScore }) => ({
88
+ candidate,
89
+ score,
90
+ ...typeof crossEncoderScore === "number" ? { crossEncoderScore } : {}
91
+ }));
92
+ const tailOutput = tail.map((candidate) => ({
93
+ candidate: candidate.candidate,
94
+ score: candidate.score
95
+ }));
96
+ return {
97
+ applied: true,
98
+ k,
99
+ alpha,
100
+ latencyMs: elapsedMs(startedAt),
101
+ candidates: [...shortlistOutput, ...tailOutput],
102
+ rescoredIds
103
+ };
104
+ }
105
+ function resolveTopK(value, total) {
106
+ if (total <= 0) {
107
+ return 0;
108
+ }
109
+ const raw = typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : DEFAULT_CROSS_ENCODER_TOP_K;
110
+ return Math.max(1, Math.min(total, raw));
111
+ }
112
+ function resolveAlpha(value) {
113
+ if (typeof value !== "number" || !Number.isFinite(value)) {
114
+ return DEFAULT_CROSS_ENCODER_ALPHA;
115
+ }
116
+ return clampUnit(value);
117
+ }
118
+ function clampUnit(value) {
119
+ if (!Number.isFinite(value) || value <= 0) {
120
+ return 0;
121
+ }
122
+ return value >= 1 ? 1 : value;
123
+ }
124
+ function elapsedMs(startedAt) {
125
+ return Math.max(0, Date.now() - startedAt);
126
+ }
127
+
128
+ // src/core/recall/fusion.ts
129
+ var DEFAULT_RRF_RANK_CONSTANT = 60;
130
+ var DEFAULT_RRF_SMALL_POOL_RANK_CONSTANT = 8;
131
+ var SMALL_POOL_RRF_POOL_SIZE = 4;
132
+ function rrfFuse(channels, rankConstant = DEFAULT_RRF_RANK_CONSTANT) {
133
+ const k = sanitizeRankConstant(rankConstant);
134
+ const nonEmptyChannels = channels.filter((channel) => channel.length > 0);
135
+ const scores = /* @__PURE__ */ new Map();
136
+ if (nonEmptyChannels.length === 0) {
137
+ return scores;
138
+ }
139
+ for (const channel of nonEmptyChannels) {
140
+ const seenInChannel = /* @__PURE__ */ new Set();
141
+ let compactedRank = 0;
142
+ for (const id of channel) {
143
+ if (id === void 0 || seenInChannel.has(id)) {
144
+ continue;
145
+ }
146
+ seenInChannel.add(id);
147
+ const contribution = 1 / (compactedRank + k);
148
+ scores.set(id, (scores.get(id) ?? 0) + contribution);
149
+ compactedRank += 1;
150
+ }
151
+ }
152
+ const normalizationDenominator = nonEmptyChannels.length * (1 / k);
153
+ for (const [id, score] of scores) {
154
+ scores.set(id, clampUnit2(score / normalizationDenominator));
155
+ }
156
+ return scores;
157
+ }
158
+ function rrfFuseVectorLexical(vectorRanks, lexicalRanks, rankConstant = DEFAULT_RRF_RANK_CONSTANT) {
159
+ return rrfFuse([vectorRanks, lexicalRanks], rankConstant);
160
+ }
161
+ function sanitizeRankConstant(value) {
162
+ if (!Number.isFinite(value) || value <= 0) {
163
+ return DEFAULT_RRF_RANK_CONSTANT;
164
+ }
165
+ return value;
166
+ }
167
+ function clampUnit2(value) {
168
+ if (!Number.isFinite(value) || value <= 0) {
169
+ return 0;
170
+ }
171
+ return value >= 1 ? 1 : value;
172
+ }
173
+
174
+ // src/core/recall/lexical.ts
175
+ var STOP_WORDS = /* @__PURE__ */ new Set([
176
+ "the",
177
+ "a",
178
+ "an",
179
+ "is",
180
+ "are",
181
+ "was",
182
+ "were",
183
+ "be",
184
+ "been",
185
+ "being",
186
+ "have",
187
+ "has",
188
+ "had",
189
+ "do",
190
+ "does",
191
+ "did",
192
+ "will",
193
+ "would",
194
+ "could",
195
+ "should",
196
+ "may",
197
+ "might",
198
+ "shall",
199
+ "can",
200
+ "need",
201
+ "must",
202
+ "i",
203
+ "me",
204
+ "my",
205
+ "we",
206
+ "our",
207
+ "you",
208
+ "your",
209
+ "he",
210
+ "him",
211
+ "his",
212
+ "she",
213
+ "her",
214
+ "it",
215
+ "its",
216
+ "they",
217
+ "them",
218
+ "their",
219
+ "this",
220
+ "that",
221
+ "these",
222
+ "those",
223
+ "what",
224
+ "which",
225
+ "who",
226
+ "whom",
227
+ "in",
228
+ "on",
229
+ "at",
230
+ "to",
231
+ "for",
232
+ "of",
233
+ "with",
234
+ "by",
235
+ "from",
236
+ "up",
237
+ "about",
238
+ "into",
239
+ "through",
240
+ "during",
241
+ "before",
242
+ "after",
243
+ "above",
244
+ "below",
245
+ "and",
246
+ "or",
247
+ "but",
248
+ "not",
249
+ "no",
250
+ "nor",
251
+ "so",
252
+ "if",
253
+ "then",
254
+ "else",
255
+ "when",
256
+ "where",
257
+ "how",
258
+ "all",
259
+ "each",
260
+ "every",
261
+ "both",
262
+ "few",
263
+ "more",
264
+ "some",
265
+ "any",
266
+ "other",
267
+ "than"
268
+ ]);
269
+ var FTS_OPERATOR_TOKENS = /* @__PURE__ */ new Set(["or", "not", "near"]);
270
+ var LEXICAL_TOKEN_PATTERN = /[\p{L}\p{N}][\p{L}\p{N}._-]*/gu;
271
+ function tokenize(text) {
272
+ const matches = normalizeLexicalText(text).match(LEXICAL_TOKEN_PATTERN) ?? [];
273
+ return matches.filter((token) => token.length >= 2 && !STOP_WORDS.has(token));
274
+ }
275
+ function buildLexicalPlan(text) {
276
+ const trimmed = text.trim();
277
+ if (trimmed.length === 0) {
278
+ return [];
279
+ }
280
+ const tokens = tokenize(trimmed).filter((token) => !FTS_OPERATOR_TOKENS.has(token));
281
+ if (tokens.length === 0) {
282
+ return [
283
+ {
284
+ tier: "exact",
285
+ text: trimmed
286
+ }
287
+ ];
288
+ }
289
+ if (tokens.length === 1) {
290
+ return [
291
+ {
292
+ tier: "exact",
293
+ text: trimmed
294
+ },
295
+ {
296
+ tier: "all_tokens",
297
+ tokens
298
+ }
299
+ ];
300
+ }
301
+ return [
302
+ {
303
+ tier: "exact",
304
+ text: trimmed
305
+ },
306
+ {
307
+ tier: "all_tokens",
308
+ tokens
309
+ },
310
+ {
311
+ tier: "any_tokens",
312
+ tokens
313
+ }
314
+ ];
315
+ }
316
+ function computeLexicalScore(query, subject, content) {
317
+ const queryTokens = tokenize(query);
318
+ const subjectTokens = tokenize(subject);
319
+ const contentTokens = tokenize(content);
320
+ const subjectTokenSet = new Set(subjectTokens);
321
+ const contentTokenSet = new Set(contentTokens);
322
+ const tokenOverlap = queryTokens.length === 0 ? 0 : queryTokens.filter((token) => subjectTokenSet.has(token) || contentTokenSet.has(token)).length / queryTokens.length;
323
+ const phraseMatches = countPhraseMatches(queryTokens, subjectTokens, contentTokens);
324
+ const phraseBonus = Math.min(0.4, phraseMatches * 0.2);
325
+ const subjectBonus = normalizeText(query) === normalizeText(subject) && normalizeText(query).length > 0 ? 0.3 : 0;
326
+ return Math.min(1, tokenOverlap + phraseBonus + subjectBonus);
327
+ }
328
+ var normalizeText = (text) => normalizeLexicalText(text).trim();
329
+ var normalizeLexicalText = (text) => text.normalize("NFKC").toLocaleLowerCase();
330
+ var countPhraseMatches = (queryTokens, subjectTokens, contentTokens) => {
331
+ if (queryTokens.length < 2) {
332
+ return 0;
333
+ }
334
+ const matchedPhrases = /* @__PURE__ */ new Set();
335
+ for (let size = 2; size <= queryTokens.length; size += 1) {
336
+ for (let index = 0; index + size <= queryTokens.length; index += 1) {
337
+ const phraseTokens = queryTokens.slice(index, index + size);
338
+ if (hasConsecutivePhrase(subjectTokens, phraseTokens) || hasConsecutivePhrase(contentTokens, phraseTokens)) {
339
+ matchedPhrases.add(phraseTokens.join(" "));
340
+ }
341
+ }
342
+ }
343
+ return matchedPhrases.size;
344
+ };
345
+ var hasConsecutivePhrase = (haystack, needle) => {
346
+ if (needle.length === 0 || haystack.length < needle.length) {
347
+ return false;
348
+ }
349
+ for (let index = 0; index + needle.length <= haystack.length; index += 1) {
350
+ let matches = true;
351
+ for (let offset = 0; offset < needle.length; offset += 1) {
352
+ if (haystack[index + offset] !== needle[offset]) {
353
+ matches = false;
354
+ break;
355
+ }
356
+ }
357
+ if (matches) {
358
+ return true;
359
+ }
360
+ }
361
+ return false;
362
+ };
363
+
364
+ // src/core/recall/scoring.ts
365
+ var DAY_IN_MILLISECONDS = 1e3 * 60 * 60 * 24;
366
+ var IMPORTANCE_FLOOR = 0.4;
367
+ var RELEVANCE_WEIGHT = 0.5;
368
+ var RECENCY_WEIGHT = 0.25;
369
+ var IMPORTANCE_WEIGHT = 0.25;
370
+ function recencyScore(createdAt, expiry, now = /* @__PURE__ */ new Date()) {
371
+ if (expiry === "core") {
372
+ return 1;
373
+ }
374
+ const createdDate = asValidDate(createdAt);
375
+ const nowDate = asValidDate(now);
376
+ if (!createdDate || !nowDate) {
377
+ return 0;
378
+ }
379
+ const halfLifeDays = expiry === "permanent" ? 365 : 30;
380
+ const daysOld = Math.max(0, (nowDate.getTime() - createdDate.getTime()) / DAY_IN_MILLISECONDS);
381
+ return clampUnit3(Math.pow(0.5, daysOld / halfLifeDays));
382
+ }
383
+ function gaussianRecency(createdAt, aroundDate, radiusDays) {
384
+ const createdDate = asValidDate(createdAt);
385
+ const anchorDate = asValidDate(aroundDate);
386
+ const normalizedRadius = sanitizeNonNegative(radiusDays);
387
+ if (!createdDate || !anchorDate) {
388
+ return 0;
389
+ }
390
+ if (normalizedRadius <= 0) {
391
+ return createdDate.getTime() === anchorDate.getTime() ? 1 : 0;
392
+ }
393
+ const daysDelta = Math.abs(createdDate.getTime() - anchorDate.getTime()) / DAY_IN_MILLISECONDS;
394
+ return clampUnit3(Math.exp(-0.5 * (daysDelta / normalizedRadius) ** 2));
395
+ }
396
+ function importanceScore(importance) {
397
+ const clampedImportance = clampRange(sanitizeNonNegative(importance), 1, 10);
398
+ return clampUnit3(IMPORTANCE_FLOOR + (clampedImportance - 1) / 9 * (1 - IMPORTANCE_FLOOR));
399
+ }
400
+ function scoreCandidate(params) {
401
+ const vector = clampUnit3(sanitizeNonNegative(params.vectorSim));
402
+ const lexical = clampUnit3(sanitizeNonNegative(params.lexical));
403
+ const recency = clampUnit3(sanitizeNonNegative(params.recency));
404
+ const importance = clampUnit3(sanitizeNonNegative(params.importance));
405
+ const relevance = clampUnit3(sanitizeNonNegative(params.relevance));
406
+ const score = clampUnit3(relevance * RELEVANCE_WEIGHT + recency * RECENCY_WEIGHT + importance * IMPORTANCE_WEIGHT);
407
+ return {
408
+ score,
409
+ scores: {
410
+ relevance,
411
+ vector,
412
+ lexical,
413
+ recency,
414
+ importance
415
+ }
416
+ };
417
+ }
418
+ function cosineSimilarity(left, right) {
419
+ const size = Math.min(left.length, right.length);
420
+ if (size === 0) {
421
+ return 0;
422
+ }
423
+ let dot = 0;
424
+ let leftNorm = 0;
425
+ let rightNorm = 0;
426
+ for (let index = 0; index < size; index += 1) {
427
+ const leftValue = sanitizeFinite(left[index]);
428
+ const rightValue = sanitizeFinite(right[index]);
429
+ dot += leftValue * rightValue;
430
+ leftNorm += leftValue * leftValue;
431
+ rightNorm += rightValue * rightValue;
432
+ }
433
+ if (leftNorm <= 0 || rightNorm <= 0) {
434
+ return 0;
435
+ }
436
+ return clampUnit3(dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm)));
437
+ }
438
+ var asValidDate = (value) => {
439
+ const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
440
+ return Number.isNaN(date.getTime()) ? null : date;
441
+ };
442
+ var clampUnit3 = (value) => clampRange(sanitizeNonNegative(value), 0, 1);
443
+ var clampRange = (value, min, max) => Math.min(max, Math.max(min, value));
444
+ var sanitizeFinite = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
445
+ var sanitizeNonNegative = (value) => Math.max(0, sanitizeFinite(value));
446
+
447
+ // src/core/recall/mmr.ts
448
+ var DEFAULT_MMR_LAMBDA = 0.7;
449
+ var DEFAULT_MMR_MIN_POOL_SIZE = 4;
450
+ var NEAR_DUPLICATE_SIMILARITY = 0.95;
451
+ function maximalMarginalRelevance(options) {
452
+ const lambda = clampUnit4(sanitizeNumber(options.lambda, DEFAULT_MMR_LAMBDA));
453
+ const inputIds = options.candidates.map((candidate) => candidate.id);
454
+ const limit = resolveLimit(options.limit, inputIds.length);
455
+ const minPoolSize = resolveMinPoolSize(options.minPoolSize);
456
+ const embeddedCandidates = options.candidates.filter((candidate) => hasUsableEmbedding(candidate.embedding));
457
+ const unembeddedIds = options.candidates.filter((candidate) => !hasUsableEmbedding(candidate.embedding)).map((candidate) => candidate.id);
458
+ const poolBelowGate = minPoolSize > 0 && options.candidates.length <= minPoolSize;
459
+ const canApplyMmr = !poolBelowGate && options.queryVector.length > 0 && embeddedCandidates.length >= 2;
460
+ if (!canApplyMmr) {
461
+ return {
462
+ applied: false,
463
+ lambda,
464
+ orderedIds: sliceOrDefault(inputIds, limit),
465
+ droppedDuplicateCount: 0,
466
+ reorderedIds: []
467
+ };
468
+ }
469
+ const relevanceById = /* @__PURE__ */ new Map();
470
+ for (const candidate of embeddedCandidates) {
471
+ const overriddenRelevance = sanitizeUnit(candidate.relevance);
472
+ const derivedRelevance = overriddenRelevance ?? cosineSimilarity(options.queryVector, candidate.embedding ?? []);
473
+ relevanceById.set(candidate.id, derivedRelevance);
474
+ }
475
+ const pairwiseMaxById = computePairwiseMaxSimilarity(embeddedCandidates);
476
+ const mmrScoreById = /* @__PURE__ */ new Map();
477
+ for (const candidate of embeddedCandidates) {
478
+ const relevance = relevanceById.get(candidate.id) ?? 0;
479
+ const maxPairwise = pairwiseMaxById.get(candidate.id) ?? 0;
480
+ mmrScoreById.set(candidate.id, lambda * relevance - (1 - lambda) * maxPairwise);
481
+ }
482
+ const rankedEmbeddedIds = [...embeddedCandidates].sort((left, right) => {
483
+ const leftScore = mmrScoreById.get(left.id) ?? 0;
484
+ const rightScore = mmrScoreById.get(right.id) ?? 0;
485
+ if (leftScore !== rightScore) {
486
+ return rightScore - leftScore;
487
+ }
488
+ return inputIds.indexOf(left.id) - inputIds.indexOf(right.id);
489
+ }).map((candidate) => candidate.id);
490
+ const orderedIds = sliceOrDefault([...rankedEmbeddedIds, ...unembeddedIds], limit);
491
+ const reorderedIds = inputIds.filter((id, index) => orderedIds[index] !== id);
492
+ const droppedDuplicateCount = countDroppedDuplicates(rankedEmbeddedIds, inputIds, pairwiseMaxById);
493
+ return {
494
+ applied: true,
495
+ lambda,
496
+ orderedIds,
497
+ droppedDuplicateCount,
498
+ reorderedIds
499
+ };
500
+ }
501
+ function computePairwiseMaxSimilarity(embeddedCandidates) {
502
+ const result = /* @__PURE__ */ new Map();
503
+ for (const candidate of embeddedCandidates) {
504
+ result.set(candidate.id, 0);
505
+ }
506
+ for (let outer = 0; outer < embeddedCandidates.length; outer += 1) {
507
+ for (let inner = outer + 1; inner < embeddedCandidates.length; inner += 1) {
508
+ const left = embeddedCandidates[outer];
509
+ const right = embeddedCandidates[inner];
510
+ const similarity = cosineSimilarity(left.embedding ?? [], right.embedding ?? []);
511
+ if (similarity > (result.get(left.id) ?? 0)) {
512
+ result.set(left.id, similarity);
513
+ }
514
+ if (similarity > (result.get(right.id) ?? 0)) {
515
+ result.set(right.id, similarity);
516
+ }
517
+ }
518
+ }
519
+ return result;
520
+ }
521
+ function countDroppedDuplicates(rankedEmbeddedIds, inputIds, pairwiseMaxById) {
522
+ const inputRankById = /* @__PURE__ */ new Map();
523
+ inputIds.forEach((id, index) => inputRankById.set(id, index));
524
+ let droppedDuplicates = 0;
525
+ rankedEmbeddedIds.forEach((id, mmrRank) => {
526
+ const inputRank = inputRankById.get(id);
527
+ if (inputRank === void 0 || mmrRank <= inputRank) {
528
+ return;
529
+ }
530
+ const maxSimilarity = pairwiseMaxById.get(id) ?? 0;
531
+ if (maxSimilarity >= NEAR_DUPLICATE_SIMILARITY) {
532
+ droppedDuplicates += 1;
533
+ }
534
+ });
535
+ return droppedDuplicates;
536
+ }
537
+ function sliceOrDefault(ids, limit) {
538
+ if (limit === null || limit >= ids.length) {
539
+ return [...ids];
540
+ }
541
+ return ids.slice(0, limit);
542
+ }
543
+ function resolveLimit(value, totalCandidates) {
544
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) {
545
+ return null;
546
+ }
547
+ return Math.min(totalCandidates, Math.floor(value));
548
+ }
549
+ function hasUsableEmbedding(embedding) {
550
+ return Array.isArray(embedding) && embedding.length > 0;
551
+ }
552
+ function clampUnit4(value) {
553
+ if (!Number.isFinite(value) || value <= 0) {
554
+ return 0;
555
+ }
556
+ return value >= 1 ? 1 : value;
557
+ }
558
+ function resolveMinPoolSize(value) {
559
+ if (typeof value !== "number" || !Number.isFinite(value)) {
560
+ return DEFAULT_MMR_MIN_POOL_SIZE;
561
+ }
562
+ if (value < 0) {
563
+ return DEFAULT_MMR_MIN_POOL_SIZE;
564
+ }
565
+ return Math.floor(value);
566
+ }
567
+ function sanitizeNumber(value, fallback) {
568
+ if (typeof value !== "number" || !Number.isFinite(value)) {
569
+ return fallback;
570
+ }
571
+ return value;
572
+ }
573
+ function sanitizeUnit(value) {
574
+ if (typeof value !== "number" || !Number.isFinite(value)) {
575
+ return null;
576
+ }
577
+ return clampUnit4(value);
578
+ }
579
+
580
+ // src/core/recall/neighborhood.ts
581
+ var DEFAULT_NEIGHBORHOOD_BUDGET = 24;
582
+ var DEFAULT_STRONG_SEED_TOP_N = 3;
583
+ var DEFAULT_STRONG_SEED_SCORE_GAP = 0.05;
584
+ var DEFAULT_SEEDED_RERANK_WEIGHT = 0.03;
585
+ function selectStrongSeeds(candidates, options = {}) {
586
+ if (candidates.length === 0) {
587
+ return [];
588
+ }
589
+ const topN = Math.max(1, sanitizeInteger(options.topN, DEFAULT_STRONG_SEED_TOP_N));
590
+ const floor = sanitizeUnit2(options.scoreGapFloor ?? DEFAULT_STRONG_SEED_SCORE_GAP);
591
+ const ordered = [...candidates].sort((left, right) => right.score - left.score);
592
+ const leader = ordered[0];
593
+ if (!leader || leader.score <= 0) {
594
+ return [];
595
+ }
596
+ const followerScore = ordered[topN]?.score ?? 0;
597
+ if (leader.score - followerScore < floor) {
598
+ return [];
599
+ }
600
+ const cutoff = Math.max(leader.score - floor, followerScore);
601
+ return ordered.slice(0, topN).filter((candidate) => candidate.score >= cutoff);
602
+ }
603
+ function seededRerank(candidates, seeds, sharesLineage, options = {}) {
604
+ const boostedIds = [];
605
+ if (candidates.length === 0 || seeds.length === 0) {
606
+ return { candidates: [...candidates], boostedIds };
607
+ }
608
+ const weight = sanitizeUnit2(options.weight ?? DEFAULT_SEEDED_RERANK_WEIGHT);
609
+ if (weight <= 0) {
610
+ return { candidates: [...candidates], boostedIds };
611
+ }
612
+ const seedIds = new Set(seeds.map((seed) => seed.id));
613
+ const reranked = candidates.map((candidate) => {
614
+ if (seedIds.has(candidate.id)) {
615
+ return candidate;
616
+ }
617
+ const match = seeds.find((seed) => seed.id !== candidate.id && sharesLineage(candidate, seed));
618
+ if (!match) {
619
+ return candidate;
620
+ }
621
+ boostedIds.push(candidate.id);
622
+ return {
623
+ ...candidate,
624
+ score: clampUnit5(candidate.score + weight)
625
+ };
626
+ });
627
+ return { candidates: reranked, boostedIds };
628
+ }
629
+ function sharesEntryLineage(candidate, seed) {
630
+ if (candidate.id === seed.id) {
631
+ return false;
632
+ }
633
+ if (candidate.claim_key && seed.claim_key && candidate.claim_key === seed.claim_key) {
634
+ return true;
635
+ }
636
+ if (candidate.superseded_by === seed.id || seed.superseded_by === candidate.id) {
637
+ return true;
638
+ }
639
+ return sharesTopicPrefix(candidate.subject, seed.subject);
640
+ }
641
+ function sharesEpisodeLineage(candidate, seed) {
642
+ if (candidate.id === seed.id) {
643
+ return false;
644
+ }
645
+ if (candidate.source === seed.source && candidate.sourceId !== void 0 && candidate.sourceId === seed.sourceId) {
646
+ return true;
647
+ }
648
+ if (candidate.transcriptHash && seed.transcriptHash && candidate.transcriptHash === seed.transcriptHash) {
649
+ return true;
650
+ }
651
+ return false;
652
+ }
653
+ function sharesProcedureLineage(candidate, seed) {
654
+ if (candidate.id === seed.id) {
655
+ return false;
656
+ }
657
+ return candidate.procedure_key === seed.procedure_key;
658
+ }
659
+ var TOPIC_PREFIX_SHARED_MIN = 2;
660
+ var TOPIC_PREFIX_COVERAGE_MIN = 0.6;
661
+ function sharesTopicPrefix(leftSubject, rightSubject) {
662
+ const leftTokens = tokenize(leftSubject);
663
+ const rightTokens = tokenize(rightSubject);
664
+ if (leftTokens.length === 0 || rightTokens.length === 0) {
665
+ return false;
666
+ }
667
+ const length = Math.min(leftTokens.length, rightTokens.length);
668
+ let shared = 0;
669
+ for (let index = 0; index < length; index += 1) {
670
+ if (leftTokens[index] !== rightTokens[index]) {
671
+ break;
672
+ }
673
+ shared += 1;
674
+ }
675
+ if (shared < TOPIC_PREFIX_SHARED_MIN) {
676
+ return false;
677
+ }
678
+ return shared / length >= TOPIC_PREFIX_COVERAGE_MIN;
679
+ }
680
+ function clampUnit5(value) {
681
+ if (!Number.isFinite(value) || value <= 0) {
682
+ return 0;
683
+ }
684
+ return value >= 1 ? 1 : value;
685
+ }
686
+ function sanitizeUnit2(value) {
687
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
688
+ return 0;
689
+ }
690
+ return Math.min(1, value);
691
+ }
692
+ function sanitizeInteger(value, fallback) {
693
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
694
+ return fallback;
695
+ }
696
+ return Math.floor(value);
697
+ }
698
+
699
+ // src/core/claim-key.ts
700
+ var UNKNOWN_SEGMENT = "unknown";
701
+ var SELF_REFERENTIAL_ENTITIES = /* @__PURE__ */ new Set(["i", "me", "myself", "the_user", "user", "we", "our_team", "the_project", "this_project"]);
702
+ var GENERIC_ENTITIES = /* @__PURE__ */ new Set([
703
+ "app",
704
+ "company",
705
+ "config",
706
+ "data",
707
+ "device",
708
+ "entity",
709
+ "environment",
710
+ "item",
711
+ "organization",
712
+ "person",
713
+ "place",
714
+ "project",
715
+ "service",
716
+ "setting",
717
+ "system",
718
+ "team",
719
+ "thing",
720
+ "user",
721
+ "workspace"
722
+ ]);
723
+ var GENERIC_ATTRIBUTES = /* @__PURE__ */ new Set(["info", "details", "config", "stuff", "thing", "data"]);
724
+ var COMPACTION_RELATION_TOKENS = /* @__PURE__ */ new Set([
725
+ "after",
726
+ "before",
727
+ "depend",
728
+ "depends",
729
+ "follows",
730
+ "follow",
731
+ "keep",
732
+ "keeps",
733
+ "maintain",
734
+ "maintains",
735
+ "need",
736
+ "needs",
737
+ "precede",
738
+ "precedes",
739
+ "preserve",
740
+ "preserves",
741
+ "require",
742
+ "required",
743
+ "requires",
744
+ "retain",
745
+ "retains"
746
+ ]);
747
+ var COMPACTION_BREAK_TOKENS = /* @__PURE__ */ new Set(["about", "across", "and", "between", "during", "for", "from", "into", "onto", "or", "to", "with"]);
748
+ var COMPACTION_WEAK_LEADING_TOKENS = /* @__PURE__ */ new Set(["actual", "authoritative", "canonical", "concrete", "current", "durable", "existing", "real"]);
749
+ var ACTION_CONDITION_TOKENS = /* @__PURE__ */ new Set(["activate", "activation", "apply", "fire", "launch", "run", "start", "trigger"]);
750
+ var TRAILING_OBJECT_COMPACTION_PREPOSITIONS = /* @__PURE__ */ new Set(["about", "for", "from", "into", "onto", "to", "with"]);
751
+ var TRAILING_OBJECT_TRANSFER_HEADS = /* @__PURE__ */ new Set([
752
+ "access",
753
+ "boundary",
754
+ "condition",
755
+ "contract",
756
+ "guide",
757
+ "path",
758
+ "policy",
759
+ "preference",
760
+ "process",
761
+ "rule",
762
+ "schedule",
763
+ "support",
764
+ "surface",
765
+ "window",
766
+ "workflow"
767
+ ]);
768
+ var STABLE_ATTRIBUTE_HEADS = /* @__PURE__ */ new Set([
769
+ "access",
770
+ "boundary",
771
+ "condition",
772
+ "contract",
773
+ "default",
774
+ "dependency",
775
+ "guide",
776
+ "mode",
777
+ "order",
778
+ "path",
779
+ "policy",
780
+ "preference",
781
+ "preservation",
782
+ "process",
783
+ "requirement",
784
+ "rule",
785
+ "schedule",
786
+ "setting",
787
+ "status",
788
+ "strategy",
789
+ "support",
790
+ "surface",
791
+ "timezone",
792
+ "truth",
793
+ "version",
794
+ "window",
795
+ "workflow"
796
+ ]);
797
+ function normalizeClaimKeySegment(value) {
798
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
799
+ }
800
+ function normalizeClaimKey(value) {
801
+ const trimmed = value.trim();
802
+ if (trimmed.length === 0) {
803
+ return { ok: false, reason: "empty" };
804
+ }
805
+ const slashCount = Array.from(trimmed).filter((character) => character === "/").length;
806
+ if (slashCount === 0) {
807
+ return { ok: false, reason: "missing_separator" };
808
+ }
809
+ if (slashCount !== 1) {
810
+ return { ok: false, reason: "too_many_segments" };
811
+ }
812
+ const [rawEntity = "", rawAttribute = ""] = trimmed.split("/");
813
+ const entity = normalizeClaimKeySegment(rawEntity);
814
+ if (entity.length === 0) {
815
+ return { ok: false, reason: "empty_entity" };
816
+ }
817
+ const attribute = normalizeClaimKeySegment(rawAttribute);
818
+ if (attribute.length === 0) {
819
+ return { ok: false, reason: "empty_attribute" };
820
+ }
821
+ if (entity === UNKNOWN_SEGMENT && attribute === UNKNOWN_SEGMENT) {
822
+ return { ok: false, reason: "unknown_pair" };
823
+ }
824
+ return {
825
+ ok: true,
826
+ value: {
827
+ claimKey: `${entity}/${attribute}`,
828
+ entity,
829
+ attribute
830
+ }
831
+ };
832
+ }
833
+ function compactClaimKey(claimKey) {
834
+ const normalized = normalizeClaimKey(claimKey);
835
+ if (!normalized.ok) {
836
+ return null;
837
+ }
838
+ let attributeTokens = normalized.value.attribute.split("_").filter((token) => token.length > 0);
839
+ const entityTokens = normalized.value.entity.split("_").filter((token) => token.length > 0);
840
+ const reasons = [];
841
+ if (entityTokens.length > 0 && startsWithTokens(attributeTokens, entityTokens) && attributeTokens.length > entityTokens.length) {
842
+ attributeTokens = attributeTokens.slice(entityTokens.length);
843
+ reasons.push("removed duplicated entity prefix from attribute");
844
+ }
845
+ if (entityTokens.length > 0 && attributeTokens.length > entityTokens.length + 1 && endsWithTokens(attributeTokens, entityTokens) && TRAILING_OBJECT_COMPACTION_PREPOSITIONS.has(attributeTokens[attributeTokens.length - entityTokens.length - 1] ?? "")) {
846
+ attributeTokens = attributeTokens.slice(0, attributeTokens.length - entityTokens.length - 1);
847
+ reasons.push("removed duplicated entity suffix from attribute");
848
+ }
849
+ const sourceOfTruthCompaction = compactSourceOfTruthAttribute(attributeTokens);
850
+ if (sourceOfTruthCompaction) {
851
+ attributeTokens = sourceOfTruthCompaction.attributeTokens;
852
+ reasons.push(sourceOfTruthCompaction.reason);
853
+ } else {
854
+ const relationCompaction = compactRelationAttribute(attributeTokens);
855
+ if (relationCompaction) {
856
+ attributeTokens = relationCompaction.attributeTokens;
857
+ reasons.push(relationCompaction.reason);
858
+ } else {
859
+ const trailingObjectCompaction = compactTrailingObjectAttribute(attributeTokens);
860
+ if (trailingObjectCompaction) {
861
+ attributeTokens = trailingObjectCompaction.attributeTokens;
862
+ reasons.push(trailingObjectCompaction.reason);
863
+ }
864
+ }
865
+ }
866
+ const attribute = attributeTokens.join("_");
867
+ if (attribute.length === 0) {
868
+ return {
869
+ claimKey: normalized.value.claimKey,
870
+ entity: normalized.value.entity,
871
+ attribute: normalized.value.attribute,
872
+ compactedFrom: null,
873
+ reason: null
874
+ };
875
+ }
876
+ const compactedClaimKey = `${normalized.value.entity}/${attribute}`;
877
+ return {
878
+ claimKey: compactedClaimKey,
879
+ entity: normalized.value.entity,
880
+ attribute,
881
+ compactedFrom: compactedClaimKey !== normalized.value.claimKey ? normalized.value.claimKey : null,
882
+ reason: reasons.length > 0 ? joinCompactionReasons(reasons) : null
883
+ };
884
+ }
885
+ function validateExtractedClaimKey(claimKey) {
886
+ if (SELF_REFERENTIAL_ENTITIES.has(claimKey.entity)) {
887
+ return {
888
+ ok: false,
889
+ reason: "self_referential_entity",
890
+ value: claimKey
891
+ };
892
+ }
893
+ if (GENERIC_ATTRIBUTES.has(claimKey.attribute)) {
894
+ return {
895
+ ok: false,
896
+ reason: "generic_attribute",
897
+ value: claimKey
898
+ };
899
+ }
900
+ if (isValueShapedAttribute(claimKey.attribute)) {
901
+ return {
902
+ ok: false,
903
+ reason: "value_shaped_attribute",
904
+ value: claimKey
905
+ };
906
+ }
907
+ return {
908
+ ok: true,
909
+ value: claimKey
910
+ };
911
+ }
912
+ function inspectClaimKey(value) {
913
+ const rawClaimKey = value.trim();
914
+ const normalized = normalizeClaimKey(rawClaimKey);
915
+ if (!normalized.ok) {
916
+ return {
917
+ rawClaimKey,
918
+ canonical: false,
919
+ normalizationFailure: normalized.reason,
920
+ suspectReasons: []
921
+ };
922
+ }
923
+ const suspectReasons = /* @__PURE__ */ new Set();
924
+ const validation = validateExtractedClaimKey(normalized.value);
925
+ if (!validation.ok) {
926
+ suspectReasons.add(validation.reason);
927
+ }
928
+ if (GENERIC_ENTITIES.has(normalized.value.entity)) {
929
+ suspectReasons.add("generic_entity");
930
+ }
931
+ return {
932
+ rawClaimKey,
933
+ canonical: normalized.value.claimKey === rawClaimKey,
934
+ normalized: normalized.value,
935
+ suspectReasons: [...suspectReasons]
936
+ };
937
+ }
938
+ function isTrustedClaimKeyForCleanup(value) {
939
+ const inspection = inspectClaimKey(value);
940
+ return Boolean(inspection.canonical && inspection.normalized && inspection.suspectReasons.length === 0);
941
+ }
942
+ function describeClaimKeyNormalizationFailure(reason) {
943
+ switch (reason) {
944
+ case "empty":
945
+ return "claim key was empty";
946
+ case "missing_separator":
947
+ return "claim key must contain exactly one '/'";
948
+ case "too_many_segments":
949
+ return "claim key must contain exactly one '/'";
950
+ case "empty_entity":
951
+ return "claim key entity was empty after normalization";
952
+ case "empty_attribute":
953
+ return "claim key attribute was empty after normalization";
954
+ case "unknown_pair":
955
+ return 'claim key "unknown/unknown" is not allowed';
956
+ }
957
+ }
958
+ function describeExtractedClaimKeyRejection(reason, claimKey) {
959
+ switch (reason) {
960
+ case "self_referential_entity":
961
+ return `entity "${claimKey.entity}" is self-referential`;
962
+ case "generic_attribute":
963
+ return `attribute "${claimKey.attribute}" is too generic`;
964
+ case "value_shaped_attribute":
965
+ return `attribute "${claimKey.attribute}" looks value-shaped`;
966
+ }
967
+ }
968
+ function describeClaimKeySuspicion(reason, claimKey) {
969
+ switch (reason) {
970
+ case "generic_entity":
971
+ return `entity "${claimKey.entity}" is too generic`;
972
+ case "self_referential_entity":
973
+ case "generic_attribute":
974
+ case "value_shaped_attribute":
975
+ return describeExtractedClaimKeyRejection(reason, claimKey);
976
+ }
977
+ }
978
+ function isValueShapedAttribute(attribute) {
979
+ return /^\d+(?:_\d+)*$/u.test(attribute) || /^v\d+(?:_\d+)*$/u.test(attribute);
980
+ }
981
+ function compactSourceOfTruthAttribute(attributeTokens) {
982
+ const sourceOfTruthIndex = findSourceOfTruthPhraseIndex(attributeTokens);
983
+ if (sourceOfTruthIndex === -1) {
984
+ return null;
985
+ }
986
+ const normalizedPhrase = ["source", "of", "truth"];
987
+ if (attributeTokens.length === normalizedPhrase.length && startsWithTokens(attributeTokens, normalizedPhrase)) {
988
+ return null;
989
+ }
990
+ const before = attributeTokens.slice(0, sourceOfTruthIndex);
991
+ const after = attributeTokens.slice(sourceOfTruthIndex + normalizedPhrase.length);
992
+ const leadingAllowed = before.every((token) => COMPACTION_WEAK_LEADING_TOKENS.has(token));
993
+ const hasMixedStableFamily = before.some((token) => STABLE_ATTRIBUTE_HEADS.has(token)) || after.some((token) => STABLE_ATTRIBUTE_HEADS.has(token));
994
+ const hasConjunctionNoise = before.includes("and") || before.includes("or") || after.includes("and") || after.includes("or");
995
+ if (!leadingAllowed || hasMixedStableFamily || hasConjunctionNoise) {
996
+ return null;
997
+ }
998
+ return {
999
+ attributeTokens: normalizedPhrase,
1000
+ reason: "collapsed source-of-truth phrasing into the stable canonical slot"
1001
+ };
1002
+ }
1003
+ function compactRelationAttribute(attributeTokens) {
1004
+ const relationIndex = attributeTokens.findIndex((token) => COMPACTION_RELATION_TOKENS.has(token));
1005
+ if (relationIndex === -1) {
1006
+ return null;
1007
+ }
1008
+ const relation = attributeTokens[relationIndex] ?? "";
1009
+ const left = attributeTokens.slice(0, relationIndex);
1010
+ const right = attributeTokens.slice(relationIndex + 1);
1011
+ if (left.length === 0 && right.length === 0) {
1012
+ return null;
1013
+ }
1014
+ if (isRequirementRelation(relation)) {
1015
+ const conditionAction = extractConditionAction(right);
1016
+ if (conditionAction) {
1017
+ return {
1018
+ attributeTokens: [conditionAction, "condition"],
1019
+ reason: `collapsed a sentence-like ${conditionAction} requirement into a stable condition slot`
1020
+ };
1021
+ }
1022
+ const requirementFocus = extractCompactionFocus(right, 2) ?? extractCompactionFocus(left, 2);
1023
+ if (!requirementFocus) {
1024
+ return null;
1025
+ }
1026
+ return {
1027
+ attributeTokens: [...requirementFocus, "requirement"],
1028
+ reason: "collapsed a sentence-like requirement phrase into a stable requirement slot"
1029
+ };
1030
+ }
1031
+ if (isOrderingRelation(relation)) {
1032
+ const orderingFocus = extractCompactionFocus(right, 2) ?? extractCompactionFocus(left, 2);
1033
+ if (!orderingFocus) {
1034
+ return null;
1035
+ }
1036
+ return {
1037
+ attributeTokens: [...orderingFocus, "order"],
1038
+ reason: "collapsed a sentence-like ordering phrase into a stable order slot"
1039
+ };
1040
+ }
1041
+ if (isPreservationRelation(relation)) {
1042
+ const preservationFocus = extractCompactionFocus(right, 2) ?? extractCompactionFocus(left, 2);
1043
+ if (!preservationFocus) {
1044
+ return null;
1045
+ }
1046
+ return {
1047
+ attributeTokens: [...preservationFocus, "preservation"],
1048
+ reason: "collapsed a sentence-like preservation phrase into a stable preservation slot"
1049
+ };
1050
+ }
1051
+ return null;
1052
+ }
1053
+ function compactTrailingObjectAttribute(attributeTokens) {
1054
+ const prepositionIndex = attributeTokens.findIndex((token) => TRAILING_OBJECT_COMPACTION_PREPOSITIONS.has(token));
1055
+ if (prepositionIndex <= 0 || prepositionIndex >= attributeTokens.length - 1) {
1056
+ return null;
1057
+ }
1058
+ const left = trimWeakLeadingTokens(attributeTokens.slice(0, prepositionIndex));
1059
+ const right = attributeTokens.slice(prepositionIndex + 1);
1060
+ if (left.length === 0 || left.length > 3 || left.includes("and") || left.includes("or") || left.some((token) => COMPACTION_RELATION_TOKENS.has(token))) {
1061
+ return null;
1062
+ }
1063
+ const head = left[left.length - 1];
1064
+ if (!head || !TRAILING_OBJECT_TRANSFER_HEADS.has(head)) {
1065
+ return null;
1066
+ }
1067
+ const objectFocus = extractCompactionFocus(right, 2);
1068
+ if (!objectFocus) {
1069
+ return null;
1070
+ }
1071
+ const headCore = extractStableHeadCore(left, 2);
1072
+ if (!headCore) {
1073
+ return null;
1074
+ }
1075
+ return {
1076
+ attributeTokens: [...objectFocus, ...headCore],
1077
+ reason: "collapsed a trailing object phrase into a compact stable slot name"
1078
+ };
1079
+ }
1080
+ function findSourceOfTruthPhraseIndex(tokens) {
1081
+ for (let index = 0; index <= tokens.length - 3; index += 1) {
1082
+ if (tokens[index] === "source" && tokens[index + 1] === "of" && tokens[index + 2] === "truth") {
1083
+ return index;
1084
+ }
1085
+ }
1086
+ return -1;
1087
+ }
1088
+ function extractConditionAction(tokens) {
1089
+ for (let index = tokens.length - 1; index >= 0; index -= 1) {
1090
+ const token = tokens[index];
1091
+ if (token && ACTION_CONDITION_TOKENS.has(token)) {
1092
+ return token;
1093
+ }
1094
+ }
1095
+ return null;
1096
+ }
1097
+ function extractCompactionFocus(tokens, limit) {
1098
+ const compactable = trimWeakLeadingTokens(tokens).filter((token) => token.length > 0);
1099
+ const segments = splitTokensOnBreaks(compactable).filter((segment) => segment.length > 0);
1100
+ const preferredSegment = segments[0];
1101
+ if (!preferredSegment || preferredSegment.length === 0) {
1102
+ return null;
1103
+ }
1104
+ return preferredSegment.slice(0, limit);
1105
+ }
1106
+ function extractStableHeadCore(tokens, limit) {
1107
+ const compactable = trimWeakLeadingTokens(tokens).filter((token) => token.length > 0);
1108
+ const head = compactable[compactable.length - 1];
1109
+ if (!head || !STABLE_ATTRIBUTE_HEADS.has(head)) {
1110
+ return null;
1111
+ }
1112
+ return compactable.slice(Math.max(0, compactable.length - limit));
1113
+ }
1114
+ function splitTokensOnBreaks(tokens) {
1115
+ const segments = [];
1116
+ let current = [];
1117
+ for (const token of tokens) {
1118
+ if (COMPACTION_BREAK_TOKENS.has(token)) {
1119
+ if (current.length > 0) {
1120
+ segments.push(current);
1121
+ current = [];
1122
+ }
1123
+ continue;
1124
+ }
1125
+ current.push(token);
1126
+ }
1127
+ if (current.length > 0) {
1128
+ segments.push(current);
1129
+ }
1130
+ return segments;
1131
+ }
1132
+ function trimWeakLeadingTokens(tokens) {
1133
+ let start = 0;
1134
+ while (start < tokens.length && COMPACTION_WEAK_LEADING_TOKENS.has(tokens[start] ?? "")) {
1135
+ start += 1;
1136
+ }
1137
+ return tokens.slice(start);
1138
+ }
1139
+ function joinCompactionReasons(reasons) {
1140
+ if (reasons.length <= 1) {
1141
+ return reasons[0] ?? "";
1142
+ }
1143
+ return `${reasons.slice(0, -1).join(", ")} and ${reasons[reasons.length - 1]}`;
1144
+ }
1145
+ function isRequirementRelation(token) {
1146
+ return token === "depend" || token === "depends" || token === "need" || token === "needs" || token === "required" || token === "require" || token === "requires";
1147
+ }
1148
+ function isOrderingRelation(token) {
1149
+ return token === "after" || token === "before" || token === "follow" || token === "follows" || token === "precede" || token === "precedes";
1150
+ }
1151
+ function isPreservationRelation(token) {
1152
+ return token === "keep" || token === "keeps" || token === "maintain" || token === "maintains" || token === "preserve" || token === "preserves" || token === "retain" || token === "retains";
1153
+ }
1154
+ function startsWithTokens(tokens, prefix) {
1155
+ return prefix.every((token, index) => tokens[index] === token);
1156
+ }
1157
+ function endsWithTokens(tokens, suffix) {
1158
+ return suffix.every((token, index) => tokens[tokens.length - suffix.length + index] === token);
1159
+ }
1160
+
1161
+ // src/core/claim-slot-policy.ts
1162
+ var MULTIVALUED_ATTRIBUTE_HEADS = /* @__PURE__ */ new Set(["access", "dependency", "guide", "integration", "preference", "requirement", "support"]);
1163
+ function resolveClaimSlotPolicy(claimKey, config) {
1164
+ const normalized = normalizeClaimKey(claimKey ?? "");
1165
+ if (!normalized.ok) {
1166
+ return {
1167
+ policy: "exclusive",
1168
+ reason: "No canonical claim key was available, so the slot policy defaulted to exclusive."
1169
+ };
1170
+ }
1171
+ const { claimKey: canonicalClaimKey, entity, attribute } = normalized.value;
1172
+ const attributeHead = attribute.split("_")[0] ?? attribute;
1173
+ const configuredPolicy = resolveConfiguredAttributeHeadPolicy(attributeHead, config);
1174
+ if (configuredPolicy) {
1175
+ return {
1176
+ claimKey: canonicalClaimKey,
1177
+ entity,
1178
+ attribute,
1179
+ attributeHead,
1180
+ policy: configuredPolicy,
1181
+ reason: `Attribute head "${attributeHead}" is configured as ${configuredPolicy} by runtime policy.`
1182
+ };
1183
+ }
1184
+ if (MULTIVALUED_ATTRIBUTE_HEADS.has(attributeHead)) {
1185
+ return {
1186
+ claimKey: canonicalClaimKey,
1187
+ entity,
1188
+ attribute,
1189
+ attributeHead,
1190
+ policy: "multivalued",
1191
+ reason: `Attribute head "${attributeHead}" is registered as multivalued.`
1192
+ };
1193
+ }
1194
+ return {
1195
+ claimKey: canonicalClaimKey,
1196
+ entity,
1197
+ attribute,
1198
+ attributeHead,
1199
+ policy: "exclusive",
1200
+ reason: `Attribute head "${attributeHead}" defaults to exclusive current-state shaping.`
1201
+ };
1202
+ }
1203
+ function resolveConfiguredAttributeHeadPolicy(attributeHead, config) {
1204
+ const configuredPolicy = config?.attributeHeads?.[attributeHead];
1205
+ if (configuredPolicy) {
1206
+ return configuredPolicy;
1207
+ }
1208
+ const loweredAttributeHead = attributeHead.toLowerCase();
1209
+ if (loweredAttributeHead === attributeHead) {
1210
+ return void 0;
1211
+ }
1212
+ return config?.attributeHeads?.[loweredAttributeHead];
1213
+ }
1214
+
1215
+ // src/core/recall/temporal.ts
1216
+ var DAY_IN_MILLISECONDS2 = 1e3 * 60 * 60 * 24;
1217
+ var MONTH_INDEX = /* @__PURE__ */ new Map([
1218
+ ["january", 0],
1219
+ ["february", 1],
1220
+ ["march", 2],
1221
+ ["april", 3],
1222
+ ["may", 4],
1223
+ ["june", 5],
1224
+ ["july", 6],
1225
+ ["august", 7],
1226
+ ["september", 8],
1227
+ ["october", 9],
1228
+ ["november", 10],
1229
+ ["december", 11]
1230
+ ]);
1231
+ function inferAroundDate(text, now = /* @__PURE__ */ new Date()) {
1232
+ const normalized = text.trim().toLowerCase();
1233
+ const referenceNow = asValidDate2(now);
1234
+ if (normalized.length === 0 || !referenceNow) {
1235
+ return null;
1236
+ }
1237
+ let inferred = null;
1238
+ if (/\byesterday\b/.test(normalized)) {
1239
+ inferred = offsetDays(referenceNow, 1);
1240
+ } else if (/\blast week\b/.test(normalized)) {
1241
+ inferred = offsetDays(referenceNow, 7);
1242
+ } else if (/\blast month\b/.test(normalized)) {
1243
+ inferred = offsetDays(referenceNow, 30);
1244
+ } else if (/\blast year\b/.test(normalized)) {
1245
+ inferred = offsetDays(referenceNow, 365);
1246
+ } else if (/\bthis week\b/.test(normalized)) {
1247
+ inferred = offsetDays(referenceNow, 3);
1248
+ } else if (/\bthis month\b/.test(normalized)) {
1249
+ inferred = offsetDays(referenceNow, 15);
1250
+ } else {
1251
+ const relativeMatch = normalized.match(/\b(\d+)\s+(day|days|week|weeks|month|months)\s+ago\b/);
1252
+ if (relativeMatch) {
1253
+ const amount = Number(relativeMatch[1]);
1254
+ const unit = relativeMatch[2];
1255
+ const multiplier = unit?.startsWith("week") ? 7 : unit?.startsWith("month") ? 30 : 1;
1256
+ inferred = Number.isFinite(amount) ? offsetDays(referenceNow, amount * multiplier) : null;
1257
+ } else {
1258
+ const monthMatch = normalized.match(/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/);
1259
+ if (monthMatch?.[1]) {
1260
+ inferred = inferMonthAnchor(monthMatch[1], referenceNow);
1261
+ }
1262
+ }
1263
+ }
1264
+ if (!inferred) {
1265
+ return null;
1266
+ }
1267
+ return inferred.getTime() > referenceNow.getTime() ? new Date(referenceNow.getTime()) : inferred;
1268
+ }
1269
+ function parseRelativeDate(input, now = /* @__PURE__ */ new Date()) {
1270
+ const trimmed = input.trim();
1271
+ const referenceNow = asValidDate2(now);
1272
+ if (trimmed.length === 0 || !referenceNow) {
1273
+ return null;
1274
+ }
1275
+ const durationMatch = trimmed.match(/^(\d+)d$/i);
1276
+ if (durationMatch?.[1]) {
1277
+ const days = Number(durationMatch[1]);
1278
+ return Number.isFinite(days) ? offsetDays(referenceNow, days) : null;
1279
+ }
1280
+ const parsed = new Date(trimmed);
1281
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
1282
+ }
1283
+ var asValidDate2 = (value) => {
1284
+ const date = new Date(value.getTime());
1285
+ return Number.isNaN(date.getTime()) ? null : date;
1286
+ };
1287
+ var offsetDays = (date, days) => new Date(date.getTime() - days * DAY_IN_MILLISECONDS2);
1288
+ var inferMonthAnchor = (monthName, now) => {
1289
+ const monthIndex = MONTH_INDEX.get(monthName);
1290
+ if (monthIndex === void 0) {
1291
+ return null;
1292
+ }
1293
+ const year = monthIndex <= now.getUTCMonth() ? now.getUTCFullYear() : now.getUTCFullYear() - 1;
1294
+ return new Date(Date.UTC(year, monthIndex, 15));
1295
+ };
1296
+
1297
+ // src/core/recall/trace.ts
1298
+ var NOOP_RECALL_TRACE_SINK = {
1299
+ reportSummary() {
1300
+ }
1301
+ };
1302
+ function createNoopRecallTraceSink() {
1303
+ return NOOP_RECALL_TRACE_SINK;
1304
+ }
1305
+
1306
+ // src/core/recall/search.ts
1307
+ var HISTORICAL_NEIGHBORHOOD_FAMILIES = ["supersession_chain", "claim_key_sibling", "topic_family"];
1308
+ var MIN_VECTOR_ONLY_EVIDENCE = 0.3;
1309
+ var HISTORICAL_STATE_FLAT_RECENCY = 0.5;
1310
+ var HISTORICAL_PREDECESSOR_BOOST = 0.08;
1311
+ var HISTORICAL_RETIRED_PREDECESSOR_BOOST = 0.06;
1312
+ var HISTORICAL_OLDER_STATE_BOOST = 0.08;
1313
+ var HISTORICAL_LINEAGE_GAP_MARGIN = 0.02;
1314
+ var HISTORICAL_LINEAGE_MAX_BONUS = 0.45;
1315
+ var HISTORICAL_TOPIC_SHARED_PREFIX_MIN = 2;
1316
+ var HISTORICAL_TOPIC_PREFIX_OF_CANDIDATE_MIN = 0.6;
1317
+ var CLAIM_KEY_TENTATIVE_CURRENT_PENALTY = 0.08;
1318
+ var CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_PENALTY = 0.05;
1319
+ var CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_MAX_PENALTY = 0.15;
1320
+ var QUERY_EMBEDDING_FAILURE_NOTICE = "Embeddings failed during recall, so Agenr fell back to lexical-only entry ranking.";
1321
+ var VECTOR_SEARCH_FAILURE_NOTICE = "Vector search failed during recall, so Agenr continued with lexical entry candidates only.";
1322
+ var ENTITY_ATTRIBUTE_IDENTITY_WRAPPERS = /* @__PURE__ */ new Set(["identity", "profile", "bio", "biography", "summary"]);
1323
+ var WEAK_QUERY_GROUNDING_TOKENS = /* @__PURE__ */ new Set([
1324
+ "earlier",
1325
+ "last",
1326
+ "mention",
1327
+ "mentioned",
1328
+ "number",
1329
+ "order",
1330
+ "remember",
1331
+ "remind",
1332
+ "reminder",
1333
+ "run",
1334
+ "runs",
1335
+ "thing",
1336
+ "time",
1337
+ "use",
1338
+ "uses",
1339
+ "using"
1340
+ ]);
1341
+ var WEAKLY_GROUNDED_REMINDER_PATTERN = /\b(earlier|last time|mention(?:ed)?|remember|remind(?:er)?)\b/iu;
1342
+ var MIN_VECTOR_WITHOUT_GROUNDED_LEXICAL_SUPPORT = 0.45;
1343
+ var GROUNDING_SORT_MAX_SCORE_GAP = 0.03;
1344
+ async function recall(query, ports, options = {}) {
1345
+ const text = query.text.trim();
1346
+ const limit = normalizeLimit(query.limit);
1347
+ const threshold = normalizeThreshold(query.threshold);
1348
+ const budget = normalizeBudget(query.budget);
1349
+ const asOfDate = query.asOf ? parseAroundDate(query.asOf) : null;
1350
+ const aroundDate = query.around !== void 0 ? parseAroundDate(query.around) : inferAroundDate(text);
1351
+ const since = query.since ? parseRelativeDate(query.since) : null;
1352
+ const until = query.until ? parseRelativeDate(query.until) : null;
1353
+ const filters = buildEntryFilters(query.types, query.tags, since, until);
1354
+ const trace = options.trace ?? createNoopRecallTraceSink();
1355
+ const slotPolicyConfig = options.slotPolicyConfig;
1356
+ const summary = buildRecallTraceSummary({
1357
+ filters,
1358
+ limit,
1359
+ threshold,
1360
+ budget,
1361
+ asOfDate,
1362
+ aroundDate,
1363
+ aroundSource: query.around !== void 0 ? "explicit" : "inferred",
1364
+ aroundRadius: aroundDate ? normalizeAroundRadius(query.aroundRadius) : void 0
1365
+ });
1366
+ let traceReported = false;
1367
+ const reportTrace = (noResultReason) => {
1368
+ if (traceReported) {
1369
+ return;
1370
+ }
1371
+ traceReported = true;
1372
+ finishRecallTrace(summary, trace, noResultReason);
1373
+ };
1374
+ if (text.length === 0) {
1375
+ reportTrace("empty_query");
1376
+ return [];
1377
+ }
1378
+ if (limit === 0) {
1379
+ reportTrace("limit_zero");
1380
+ return [];
1381
+ }
1382
+ try {
1383
+ let queryEmbedding = [];
1384
+ try {
1385
+ queryEmbedding = await ports.embed(text);
1386
+ } catch {
1387
+ markRecallDegraded(summary, "query_embedding_failed", QUERY_EMBEDDING_FAILURE_NOTICE);
1388
+ }
1389
+ const vectorSearchLimit = limit * 4;
1390
+ const lexicalSearchLimit = limit * 2;
1391
+ const [vectorCandidates, ftsCandidates] = queryEmbedding.length > 0 ? await Promise.all([
1392
+ ports.vectorSearch({
1393
+ embedding: queryEmbedding,
1394
+ limit: vectorSearchLimit,
1395
+ filters
1396
+ }).catch(() => {
1397
+ markRecallDegraded(summary, "vector_search_failed", VECTOR_SEARCH_FAILURE_NOTICE);
1398
+ return [];
1399
+ }),
1400
+ ports.ftsSearch({
1401
+ text,
1402
+ limit: lexicalSearchLimit,
1403
+ filters
1404
+ })
1405
+ ]) : [
1406
+ [],
1407
+ await ports.ftsSearch({
1408
+ text,
1409
+ limit: lexicalSearchLimit,
1410
+ filters
1411
+ })
1412
+ ];
1413
+ summary.degraded.lexicalOnly = summary.degraded.active && queryEmbedding.length === 0;
1414
+ const mergeStartedAt = Date.now();
1415
+ const mergeOutcome = mergeCandidates(vectorCandidates, ftsCandidates);
1416
+ const neighborhoodEnabled = options.rankingPolicy?.neighborhood !== "disabled";
1417
+ const expansionRanks = neighborhoodEnabled ? await expandEntryNeighborhood(mergeOutcome.merged, queryEmbedding, ports, {
1418
+ rankingProfile: query.rankingProfile,
1419
+ neighborhoodTrace: summary.neighborhood
1420
+ }) : [];
1421
+ const relevanceByEntryId = resolveEntryRelevance({
1422
+ vectorRanks: mergeOutcome.vectorRanks,
1423
+ ftsRanks: mergeOutcome.ftsRanks,
1424
+ expansionRanks,
1425
+ policy: options.rankingPolicy,
1426
+ trace: summary.rrf
1427
+ });
1428
+ summary.candidateCounts.merged = mergeOutcome.merged.size;
1429
+ summary.timings.mergeCandidatesMs = elapsedMs2(mergeStartedAt);
1430
+ const scoreStartedAt = Date.now();
1431
+ const historicallyBoosted = applyHistoricalLineageBoosts(
1432
+ Array.from(mergeOutcome.merged.values()).map(
1433
+ (candidate) => scoreMergedCandidate(candidate, text, queryEmbedding, relevanceByEntryId.get(candidate.entry.id) ?? 0, {
1434
+ asOfDate,
1435
+ aroundDate,
1436
+ aroundRadius: query.aroundRadius,
1437
+ rankingProfile: query.rankingProfile
1438
+ })
1439
+ ),
1440
+ {
1441
+ aroundDate,
1442
+ rankingProfile: query.rankingProfile
1443
+ },
1444
+ summary.claimKey,
1445
+ slotPolicyConfig
1446
+ );
1447
+ const rerankedCandidates = neighborhoodEnabled ? applySeededEntryRerank(historicallyBoosted, summary.neighborhood) : historicallyBoosted;
1448
+ const shaped = applyClaimKeyResultShaping(rerankedCandidates, summary.claimKey, slotPolicyConfig).sort((left, right) => right.score - left.score);
1449
+ const diversified = applyMmrDiversification(shaped, queryEmbedding, options.rankingPolicy, summary.mmr);
1450
+ const scored = await applyEntryCrossEncoderRerank(diversified, text, ports.crossEncoder, options.rankingPolicy, summary.crossEncoder);
1451
+ summary.timings.scoreCandidatesMs = elapsedMs2(scoreStartedAt);
1452
+ const thresholdStartedAt = Date.now();
1453
+ const thresholded = scored.filter((result) => hasSufficientReturnEvidence(result, query) && result.score >= threshold);
1454
+ summary.candidateCounts.thresholdQualified = thresholded.length;
1455
+ summary.timings.thresholdMs = elapsedMs2(thresholdStartedAt);
1456
+ if (thresholded.length === 0) {
1457
+ reportTrace(resolveNoResultReason(summary, scored.length === 0 ? "no_candidates" : "below_threshold"));
1458
+ return [];
1459
+ }
1460
+ const budgetStartedAt = Date.now();
1461
+ const budgeted = budget === null ? thresholded : applyBudget(thresholded, budget);
1462
+ summary.candidateCounts.budgetAccepted = budgeted.length;
1463
+ summary.timings.budgetMs = budget === null ? 0 : elapsedMs2(budgetStartedAt);
1464
+ const ranked = sortAcceptedCandidates(budgeted.slice(0, limit), text, query.rankingProfile);
1465
+ summary.candidateCounts.finalRanked = ranked.length;
1466
+ if (ranked.length === 0) {
1467
+ reportTrace("limit_zero");
1468
+ return [];
1469
+ }
1470
+ const hydratedEntries = await ports.hydrateEntries(ranked.map((result) => result.entry.id));
1471
+ const shapeStartedAt = Date.now();
1472
+ const hydratedById = new Map(hydratedEntries.map((entry) => [entry.id, entry]));
1473
+ const results = ranked.flatMap((result) => {
1474
+ const entry = hydratedById.get(result.entry.id);
1475
+ if (!entry) {
1476
+ return [];
1477
+ }
1478
+ return [
1479
+ {
1480
+ entry,
1481
+ score: result.score,
1482
+ scores: result.scores
1483
+ }
1484
+ ];
1485
+ });
1486
+ summary.candidateCounts.returned = results.length;
1487
+ summary.timings.shapeResultsMs = elapsedMs2(shapeStartedAt);
1488
+ if (results.length === 0) {
1489
+ reportTrace(resolveNoResultReason(summary, "hydrate_missing"));
1490
+ return [];
1491
+ }
1492
+ if (results.length > 0) {
1493
+ await ports.recordRecallEvents({
1494
+ entryIds: results.map((result) => result.entry.id),
1495
+ query: text,
1496
+ sessionKey: query.sessionKey
1497
+ }).catch(() => void 0);
1498
+ }
1499
+ reportTrace();
1500
+ return results;
1501
+ } catch (error) {
1502
+ reportTrace();
1503
+ throw error;
1504
+ }
1505
+ }
1506
+ function buildRecallTraceSummary(params) {
1507
+ return {
1508
+ filtering: {
1509
+ types: params.filters?.types ?? [],
1510
+ tags: params.filters?.tags ?? [],
1511
+ since: params.filters?.since?.toISOString(),
1512
+ until: params.filters?.until?.toISOString(),
1513
+ around: params.aroundDate ? {
1514
+ source: params.aroundSource,
1515
+ anchor: params.aroundDate.toISOString(),
1516
+ radiusDays: params.aroundRadius ?? 14
1517
+ } : void 0,
1518
+ ...params.asOfDate ? {
1519
+ asOf: {
1520
+ anchor: params.asOfDate.toISOString()
1521
+ }
1522
+ } : {}
1523
+ },
1524
+ ranking: {
1525
+ limit: params.limit,
1526
+ threshold: params.threshold,
1527
+ budget: params.budget
1528
+ },
1529
+ candidateCounts: {
1530
+ merged: 0,
1531
+ thresholdQualified: 0,
1532
+ budgetAccepted: 0,
1533
+ finalRanked: 0,
1534
+ returned: 0
1535
+ },
1536
+ claimKey: {
1537
+ historicalBoosted: 0,
1538
+ tentativeLineageSuppressed: 0,
1539
+ trustPenalized: 0,
1540
+ redundancyPenalized: 0
1541
+ },
1542
+ rrf: {
1543
+ applied: false,
1544
+ channelCount: 0,
1545
+ rankConstant: DEFAULT_RRF_RANK_CONSTANT,
1546
+ fusedCandidateCount: 0,
1547
+ maxFusedScore: 0
1548
+ },
1549
+ neighborhood: {
1550
+ expansionRequested: false,
1551
+ expansionAvailable: false,
1552
+ familiesRequested: [],
1553
+ includeRetired: false,
1554
+ seedIds: [],
1555
+ expansionCandidates: 0,
1556
+ strongSeedIds: [],
1557
+ rerankBoostedIds: []
1558
+ },
1559
+ mmr: {
1560
+ applied: false,
1561
+ lambda: DEFAULT_MMR_LAMBDA,
1562
+ droppedDuplicateCount: 0,
1563
+ reorderedIds: []
1564
+ },
1565
+ crossEncoder: {
1566
+ applied: false,
1567
+ k: 0,
1568
+ alpha: DEFAULT_CROSS_ENCODER_ALPHA,
1569
+ latencyMs: 0,
1570
+ rescoredIds: []
1571
+ },
1572
+ timings: {
1573
+ mergeCandidatesMs: 0,
1574
+ scoreCandidatesMs: 0,
1575
+ thresholdMs: 0,
1576
+ budgetMs: 0,
1577
+ shapeResultsMs: 0
1578
+ },
1579
+ degraded: {
1580
+ active: false,
1581
+ reasons: [],
1582
+ lexicalOnly: false,
1583
+ notices: []
1584
+ }
1585
+ };
1586
+ }
1587
+ function finishRecallTrace(summary, trace, noResultReason) {
1588
+ if (noResultReason) {
1589
+ summary.ranking.noResultReason = noResultReason;
1590
+ }
1591
+ trace.reportSummary(summary);
1592
+ }
1593
+ function markRecallDegraded(summary, reason, notice) {
1594
+ summary.degraded.active = true;
1595
+ if (!summary.degraded.reasons.includes(reason)) {
1596
+ summary.degraded.reasons.push(reason);
1597
+ }
1598
+ if (!summary.degraded.notices.includes(notice)) {
1599
+ summary.degraded.notices.push(notice);
1600
+ }
1601
+ }
1602
+ function resolveNoResultReason(summary, reason) {
1603
+ if (!summary.degraded.active) {
1604
+ return reason;
1605
+ }
1606
+ if (reason === "no_candidates") {
1607
+ return "degraded_no_candidates";
1608
+ }
1609
+ if (reason === "below_threshold") {
1610
+ return "degraded_below_threshold";
1611
+ }
1612
+ return reason;
1613
+ }
1614
+ function scoreMergedCandidate(candidate, queryText, queryEmbedding, rrfScore, params) {
1615
+ const vector = candidate.vectorSim ?? cosineSimilarity(candidate.entry.embedding ?? [], queryEmbedding);
1616
+ const lexical = computeLexicalScore(queryText, candidate.entry.subject, candidate.entry.content);
1617
+ const recency = resolveRecencyScore(candidate.entry, params);
1618
+ const importance = importanceScore(candidate.entry.importance);
1619
+ const scored = scoreCandidate({
1620
+ relevance: rrfScore,
1621
+ vectorSim: vector,
1622
+ lexical,
1623
+ recency,
1624
+ importance
1625
+ });
1626
+ return {
1627
+ entry: candidate.entry,
1628
+ score: scored.score,
1629
+ scores: {
1630
+ ...scored.scores,
1631
+ // `rrf` mirrors `relevance` and makes the reciprocal rank fusion source
1632
+ // explicit for trace summaries and cross-stage reasoning in later phases.
1633
+ rrf: scored.scores.relevance,
1634
+ historicalLineage: 0,
1635
+ neighborhoodBoost: 0,
1636
+ claimKeyTrustPenalty: 0,
1637
+ claimKeyRedundancyPenalty: 0
1638
+ }
1639
+ };
1640
+ }
1641
+ function resolveEntryRelevance(params) {
1642
+ const { vectorRanks, ftsRanks, expansionRanks, policy, trace } = params;
1643
+ if (policy?.rrf === "disabled") {
1644
+ const fallbackChannel = vectorRanks.length > 0 ? vectorRanks : ftsRanks;
1645
+ const fallback = /* @__PURE__ */ new Map();
1646
+ fallbackChannel.forEach((id, index) => {
1647
+ if (!fallback.has(id)) {
1648
+ fallback.set(id, 1 / (index + 1));
1649
+ }
1650
+ });
1651
+ trace.applied = false;
1652
+ trace.channelCount = fallbackChannel.length > 0 ? 1 : 0;
1653
+ trace.rankConstant = resolveRrfRankConstant(policy, fallback.size);
1654
+ trace.fusedCandidateCount = fallback.size;
1655
+ trace.maxFusedScore = fallback.size > 0 ? Math.max(...fallback.values()) : 0;
1656
+ return fallback;
1657
+ }
1658
+ const channels = [Array.from(vectorRanks), Array.from(ftsRanks), Array.from(expansionRanks)];
1659
+ const activeChannels = channels.filter((channel) => channel.length > 0);
1660
+ const uniqueFusedIds = /* @__PURE__ */ new Set();
1661
+ for (const channel of channels) {
1662
+ for (const id of channel) {
1663
+ uniqueFusedIds.add(id);
1664
+ }
1665
+ }
1666
+ const rankConstant = resolveRrfRankConstant(policy, uniqueFusedIds.size);
1667
+ trace.rankConstant = rankConstant;
1668
+ const fused = rrfFuse(channels, rankConstant);
1669
+ trace.applied = fused.size > 0;
1670
+ trace.channelCount = activeChannels.length;
1671
+ trace.fusedCandidateCount = fused.size;
1672
+ trace.maxFusedScore = fused.size > 0 ? Math.max(...fused.values()) : 0;
1673
+ return fused;
1674
+ }
1675
+ function resolveRrfRankConstant(policy, fusedPoolSize) {
1676
+ const rawGeneral = policy?.rrfRankConstant;
1677
+ const hasExplicitGeneral = typeof rawGeneral === "number" && Number.isFinite(rawGeneral) && rawGeneral > 0;
1678
+ const generalConstant = hasExplicitGeneral ? rawGeneral : DEFAULT_RRF_RANK_CONSTANT;
1679
+ const isSmallPool = Number.isFinite(fusedPoolSize) && fusedPoolSize > 0 && fusedPoolSize <= SMALL_POOL_RRF_POOL_SIZE;
1680
+ if (!isSmallPool) {
1681
+ return generalConstant;
1682
+ }
1683
+ const rawSmall = policy?.rrfSmallPoolRankConstant;
1684
+ if (typeof rawSmall === "number" && Number.isFinite(rawSmall) && rawSmall > 0) {
1685
+ return rawSmall;
1686
+ }
1687
+ if (hasExplicitGeneral) {
1688
+ return generalConstant;
1689
+ }
1690
+ return DEFAULT_RRF_SMALL_POOL_RANK_CONSTANT;
1691
+ }
1692
+ async function expandEntryNeighborhood(mergedCandidates, queryEmbedding, ports, params) {
1693
+ const trace = params.neighborhoodTrace;
1694
+ trace.expansionAvailable = Boolean(ports.expandNeighborhood);
1695
+ if (mergedCandidates.size === 0 || !ports.expandNeighborhood || params.rankingProfile !== "historical_state") {
1696
+ return [];
1697
+ }
1698
+ const families = HISTORICAL_NEIGHBORHOOD_FAMILIES;
1699
+ const includeRetired = true;
1700
+ const seedIds = Array.from(mergedCandidates.keys());
1701
+ trace.expansionRequested = true;
1702
+ trace.familiesRequested = [...families];
1703
+ trace.includeRetired = includeRetired;
1704
+ trace.seedIds = seedIds;
1705
+ const expanded = await ports.expandNeighborhood({
1706
+ seedIds,
1707
+ budget: DEFAULT_NEIGHBORHOOD_BUDGET,
1708
+ families,
1709
+ includeRetired
1710
+ });
1711
+ const ranked = expanded.filter((entry) => !mergedCandidates.has(entry.id)).map((entry) => ({
1712
+ entry,
1713
+ vectorSim: cosineSimilarity(entry.embedding ?? [], queryEmbedding)
1714
+ })).sort((left, right) => right.vectorSim - left.vectorSim || left.entry.id.localeCompare(right.entry.id));
1715
+ for (const candidate of ranked) {
1716
+ mergedCandidates.set(candidate.entry.id, {
1717
+ entry: candidate.entry,
1718
+ vectorSim: candidate.vectorSim
1719
+ });
1720
+ }
1721
+ trace.expansionCandidates = ranked.length;
1722
+ return ranked.map((candidate) => candidate.entry.id);
1723
+ }
1724
+ function applySeededEntryRerank(candidates, trace) {
1725
+ if (candidates.length === 0) {
1726
+ return candidates;
1727
+ }
1728
+ const seeds = selectStrongSeeds(
1729
+ candidates.map((candidate) => ({ id: candidate.entry.id, score: candidate.score, entry: candidate.entry })),
1730
+ {
1731
+ topN: DEFAULT_STRONG_SEED_TOP_N,
1732
+ scoreGapFloor: DEFAULT_STRONG_SEED_SCORE_GAP
1733
+ }
1734
+ );
1735
+ if (seeds.length === 0) {
1736
+ return candidates;
1737
+ }
1738
+ trace.strongSeedIds = seeds.map((seed) => seed.id);
1739
+ const payloads = candidates.map((candidate) => ({
1740
+ id: candidate.entry.id,
1741
+ score: candidate.score,
1742
+ entry: candidate.entry
1743
+ }));
1744
+ const reranked = seededRerank(payloads, seeds, (candidate, seed) => sharesEntryLineage(candidate.entry, seed.entry), {
1745
+ weight: DEFAULT_SEEDED_RERANK_WEIGHT
1746
+ });
1747
+ trace.rerankBoostedIds = reranked.boostedIds;
1748
+ const scoreById = new Map(reranked.candidates.map((candidate) => [candidate.id, candidate.score]));
1749
+ return candidates.map((candidate) => {
1750
+ const nextScore = scoreById.get(candidate.entry.id) ?? candidate.score;
1751
+ const delta = nextScore - candidate.score;
1752
+ if (delta <= 0) {
1753
+ return candidate;
1754
+ }
1755
+ return {
1756
+ ...candidate,
1757
+ score: nextScore,
1758
+ scores: {
1759
+ ...candidate.scores,
1760
+ neighborhoodBoost: candidate.scores.neighborhoodBoost + delta
1761
+ }
1762
+ };
1763
+ });
1764
+ }
1765
+ function resolveRecencyScore(entry, params) {
1766
+ if (params.asOfDate) {
1767
+ return resolveAsOfScore(entry, params.asOfDate);
1768
+ }
1769
+ if (params.aroundDate) {
1770
+ return gaussianRecency(entry.created_at, params.aroundDate, normalizeAroundRadius(params.aroundRadius));
1771
+ }
1772
+ if (params.rankingProfile === "historical_state") {
1773
+ return HISTORICAL_STATE_FLAT_RECENCY;
1774
+ }
1775
+ return recencyScore(entry.created_at, entry.expiry);
1776
+ }
1777
+ function resolveAsOfScore(entry, asOfDate) {
1778
+ const validFrom = parseTimestamp(entry.valid_from);
1779
+ const validTo = parseTimestamp(entry.valid_to);
1780
+ if (validFrom || validTo) {
1781
+ const startMs = validFrom?.getTime() ?? Number.NEGATIVE_INFINITY;
1782
+ const endMs = validTo?.getTime() ?? Number.POSITIVE_INFINITY;
1783
+ const asOfMs = asOfDate.getTime();
1784
+ if (asOfMs >= startMs && asOfMs <= endMs) {
1785
+ return 1;
1786
+ }
1787
+ const nearestBoundaryMs = asOfMs < startMs ? startMs : endMs;
1788
+ return Math.max(0.1, gaussianRecency(new Date(nearestBoundaryMs).toISOString(), asOfDate, 21) * 0.65);
1789
+ }
1790
+ const observedAt = parseTimestamp(entry.claim_support_observed_at);
1791
+ if (observedAt) {
1792
+ const observedBeforeAsOf = observedAt.getTime() <= asOfDate.getTime();
1793
+ const proximity = gaussianRecency(observedAt.toISOString(), asOfDate, 30);
1794
+ return observedBeforeAsOf ? Math.max(0.45, proximity * 0.8) : Math.max(0.05, proximity * 0.2);
1795
+ }
1796
+ const createdAt = parseTimestamp(entry.created_at);
1797
+ if (createdAt) {
1798
+ const createdBeforeAsOf = createdAt.getTime() <= asOfDate.getTime();
1799
+ const proximity = gaussianRecency(createdAt.toISOString(), asOfDate, 45);
1800
+ return createdBeforeAsOf ? Math.max(0.35, proximity * 0.7) : Math.max(0.05, proximity * 0.15);
1801
+ }
1802
+ return HISTORICAL_STATE_FLAT_RECENCY;
1803
+ }
1804
+ function applyHistoricalLineageBoosts(candidates, params, claimKeyTrace, slotPolicyConfig) {
1805
+ if (params.rankingProfile !== "historical_state") {
1806
+ return candidates;
1807
+ }
1808
+ const entries = candidates.map((candidate) => candidate.entry);
1809
+ const scoresById = new Map(candidates.map((candidate) => [candidate.entry.id, candidate.score]));
1810
+ return candidates.map((candidate) => {
1811
+ const decision = resolveHistoricalLineageBonus(candidate.entry, entries, scoresById, candidate.score, params.aroundDate, slotPolicyConfig);
1812
+ if (decision.tentativeLineageSuppressed) {
1813
+ claimKeyTrace.tentativeLineageSuppressed += 1;
1814
+ }
1815
+ const bonus = decision.bonus;
1816
+ if (bonus <= 0) {
1817
+ return candidate;
1818
+ }
1819
+ claimKeyTrace.historicalBoosted += 1;
1820
+ return {
1821
+ ...candidate,
1822
+ score: clampRecallScore(candidate.score + bonus),
1823
+ scores: {
1824
+ ...candidate.scores,
1825
+ historicalLineage: candidate.scores.historicalLineage + bonus
1826
+ }
1827
+ };
1828
+ });
1829
+ }
1830
+ function resolveHistoricalLineageBonus(entry, entries, scoresById, candidateScore, aroundDate, slotPolicyConfig) {
1831
+ const directSuccessor = entries.find((peer) => peer.id !== entry.id && entry.superseded_by === peer.id);
1832
+ if (directSuccessor) {
1833
+ const successorScore = scoresById.get(directSuccessor.id) ?? 0;
1834
+ return {
1835
+ bonus: shapeHistoricalLineageBonus(HISTORICAL_PREDECESSOR_BOOST, candidateScore, successorScore),
1836
+ tentativeLineageSuppressed: false
1837
+ };
1838
+ }
1839
+ if (aroundDate) {
1840
+ return {
1841
+ bonus: 0,
1842
+ tentativeLineageSuppressed: false
1843
+ };
1844
+ }
1845
+ let tentativeLineageSuppressed = false;
1846
+ let bestPeerScore = 0;
1847
+ let peerMatched = false;
1848
+ for (const peer of entries) {
1849
+ if (peer.id === entry.id || !isPotentialCurrentPeer(peer) || createdAtMs(entry.created_at) >= createdAtMs(peer.created_at)) {
1850
+ continue;
1851
+ }
1852
+ const relation = resolveHistoricalPeerRelation(entry, peer, entries, slotPolicyConfig);
1853
+ if (relation === "tentative_claim_key_suppressed") {
1854
+ tentativeLineageSuppressed = true;
1855
+ continue;
1856
+ }
1857
+ if (relation === null) {
1858
+ continue;
1859
+ }
1860
+ peerMatched = true;
1861
+ const peerScore = scoresById.get(peer.id) ?? 0;
1862
+ if (peerScore > bestPeerScore) {
1863
+ bestPeerScore = peerScore;
1864
+ }
1865
+ }
1866
+ if (!peerMatched) {
1867
+ return {
1868
+ bonus: 0,
1869
+ tentativeLineageSuppressed
1870
+ };
1871
+ }
1872
+ const base = entry.retired ? HISTORICAL_RETIRED_PREDECESSOR_BOOST : HISTORICAL_OLDER_STATE_BOOST;
1873
+ return {
1874
+ bonus: shapeHistoricalLineageBonus(base, candidateScore, bestPeerScore),
1875
+ tentativeLineageSuppressed
1876
+ };
1877
+ }
1878
+ function shapeHistoricalLineageBonus(base, candidateScore, successorScore) {
1879
+ const gap = successorScore - candidateScore;
1880
+ const needed = gap > 0 ? gap + HISTORICAL_LINEAGE_GAP_MARGIN : 0;
1881
+ return Math.min(HISTORICAL_LINEAGE_MAX_BONUS, Math.max(base, needed));
1882
+ }
1883
+ function isPotentialCurrentPeer(entry) {
1884
+ return !entry.retired && entry.superseded_by === void 0;
1885
+ }
1886
+ function resolveHistoricalPeerRelation(left, right, entries, slotPolicyConfig) {
1887
+ if (left.claim_key && right.claim_key && left.claim_key === right.claim_key) {
1888
+ if (resolveClaimSlotPolicy(left.claim_key, slotPolicyConfig).policy === "multivalued") {
1889
+ return null;
1890
+ }
1891
+ return canUseClaimKeyLineage(left, entries, slotPolicyConfig) ? "claim_key" : "tentative_claim_key_suppressed";
1892
+ }
1893
+ return sharesHistoricalTopic(left, right) ? "topic" : null;
1894
+ }
1895
+ function canUseClaimKeyLineage(entry, entries, slotPolicyConfig) {
1896
+ if (!entry.claim_key) {
1897
+ return false;
1898
+ }
1899
+ if (resolveClaimSlotPolicy(entry.claim_key, slotPolicyConfig).policy === "multivalued") {
1900
+ return false;
1901
+ }
1902
+ if (!hasTrustedClaimKeyEvidence(entries, entry.claim_key)) {
1903
+ return true;
1904
+ }
1905
+ return entry.claim_key_status === "trusted";
1906
+ }
1907
+ function hasTrustedClaimKeyEvidence(entries, claimKey) {
1908
+ return entries.some((entry) => entry.claim_key === claimKey && entry.claim_key_status === "trusted");
1909
+ }
1910
+ function sharesHistoricalTopic(left, right) {
1911
+ const leftTokens = tokenize(left.subject);
1912
+ const rightTokens = tokenize(right.subject);
1913
+ if (leftTokens.length === 0 || rightTokens.length === 0) {
1914
+ return false;
1915
+ }
1916
+ const sharedPrefixCount = countSharedPrefixTokens(leftTokens, rightTokens);
1917
+ return sharedPrefixCount >= HISTORICAL_TOPIC_SHARED_PREFIX_MIN && sharedPrefixCount / leftTokens.length >= HISTORICAL_TOPIC_PREFIX_OF_CANDIDATE_MIN;
1918
+ }
1919
+ function createdAtMs(value) {
1920
+ return parseTimestamp(value)?.getTime() ?? 0;
1921
+ }
1922
+ function parseTimestamp(value) {
1923
+ const normalized = value?.trim();
1924
+ if (!normalized) {
1925
+ return null;
1926
+ }
1927
+ const timestamp = new Date(normalized);
1928
+ return Number.isFinite(timestamp.getTime()) ? timestamp : null;
1929
+ }
1930
+ function countSharedPrefixTokens(leftTokens, rightTokens) {
1931
+ const length = Math.min(leftTokens.length, rightTokens.length);
1932
+ let sharedPrefixCount = 0;
1933
+ for (let index = 0; index < length; index += 1) {
1934
+ if (leftTokens[index] !== rightTokens[index]) {
1935
+ break;
1936
+ }
1937
+ sharedPrefixCount += 1;
1938
+ }
1939
+ return sharedPrefixCount;
1940
+ }
1941
+ function applyMmrDiversification(candidates, queryEmbedding, policy, trace) {
1942
+ if (candidates.length < 2 || policy?.mmr === "disabled") {
1943
+ trace.applied = false;
1944
+ trace.lambda = resolveMmrLambda(policy);
1945
+ return candidates;
1946
+ }
1947
+ const reorder = maximalMarginalRelevance({
1948
+ queryVector: queryEmbedding,
1949
+ candidates: candidates.map((candidate) => ({
1950
+ id: candidate.entry.id,
1951
+ relevance: candidate.score,
1952
+ ...candidate.entry.embedding ? { embedding: candidate.entry.embedding } : {}
1953
+ })),
1954
+ lambda: resolveMmrLambda(policy),
1955
+ minPoolSize: resolveMmrMinPoolSize(policy)
1956
+ });
1957
+ trace.applied = reorder.applied;
1958
+ trace.lambda = reorder.lambda;
1959
+ trace.droppedDuplicateCount = reorder.droppedDuplicateCount;
1960
+ trace.reorderedIds = reorder.reorderedIds;
1961
+ if (!reorder.applied) {
1962
+ return candidates;
1963
+ }
1964
+ const candidatesById = new Map(candidates.map((candidate) => [candidate.entry.id, candidate]));
1965
+ return reorder.orderedIds.flatMap((id) => {
1966
+ const candidate = candidatesById.get(id);
1967
+ return candidate ? [candidate] : [];
1968
+ });
1969
+ }
1970
+ async function applyEntryCrossEncoderRerank(candidates, query, crossEncoder, policy, trace) {
1971
+ const result = await applyCrossEncoderRerank({
1972
+ query,
1973
+ candidates: candidates.map((candidate) => ({
1974
+ id: candidate.entry.id,
1975
+ text: buildCrossEncoderPassageText(candidate.entry),
1976
+ score: candidate.score,
1977
+ candidate
1978
+ })),
1979
+ port: crossEncoder,
1980
+ disabled: policy?.crossEncoder === "disabled",
1981
+ topK: policy?.crossEncoderTopK ?? DEFAULT_CROSS_ENCODER_TOP_K,
1982
+ alpha: policy?.crossEncoderAlpha ?? DEFAULT_CROSS_ENCODER_ALPHA
1983
+ });
1984
+ trace.applied = result.applied;
1985
+ trace.k = result.k;
1986
+ trace.alpha = result.alpha;
1987
+ trace.latencyMs = result.latencyMs;
1988
+ trace.rescoredIds = [...result.rescoredIds];
1989
+ if (result.degradedReason) {
1990
+ trace.degradedReason = result.degradedReason;
1991
+ } else {
1992
+ delete trace.degradedReason;
1993
+ }
1994
+ return result.candidates.map((entry) => {
1995
+ const scoredCandidate = entry.candidate;
1996
+ const nextScore = entry.score;
1997
+ if (typeof entry.crossEncoderScore !== "number" && nextScore === scoredCandidate.score) {
1998
+ return scoredCandidate;
1999
+ }
2000
+ return {
2001
+ ...scoredCandidate,
2002
+ score: nextScore,
2003
+ scores: {
2004
+ ...scoredCandidate.scores,
2005
+ ...typeof entry.crossEncoderScore === "number" ? { crossEncoder: entry.crossEncoderScore } : {}
2006
+ }
2007
+ };
2008
+ });
2009
+ }
2010
+ function buildCrossEncoderPassageText(entry) {
2011
+ const subject = entry.subject.trim();
2012
+ const content = entry.content.trim();
2013
+ if (subject.length === 0) {
2014
+ return content;
2015
+ }
2016
+ if (content.length === 0) {
2017
+ return subject;
2018
+ }
2019
+ return `${subject}
2020
+
2021
+ ${content}`;
2022
+ }
2023
+ function resolveMmrLambda(policy) {
2024
+ const rawLambda = policy?.mmrLambda;
2025
+ if (typeof rawLambda !== "number" || !Number.isFinite(rawLambda)) {
2026
+ return DEFAULT_MMR_LAMBDA;
2027
+ }
2028
+ return Math.max(0, Math.min(1, rawLambda));
2029
+ }
2030
+ function resolveMmrMinPoolSize(policy) {
2031
+ const raw = policy?.mmrMinPoolSize;
2032
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) {
2033
+ return DEFAULT_MMR_MIN_POOL_SIZE;
2034
+ }
2035
+ return Math.floor(raw);
2036
+ }
2037
+ function applyClaimKeyResultShaping(candidates, claimKeyTrace, slotPolicyConfig) {
2038
+ if (candidates.length === 0) {
2039
+ return candidates;
2040
+ }
2041
+ const trustedActiveClaimKeys = new Set(
2042
+ candidates.map((candidate) => candidate.entry).filter(
2043
+ (entry) => isPotentialCurrentPeer(entry) && entry.claim_key && entry.claim_key_status === "trusted" && resolveClaimSlotPolicy(entry.claim_key, slotPolicyConfig).policy === "exclusive"
2044
+ ).map((entry) => entry.claim_key)
2045
+ );
2046
+ const trustedSlotRankById = rankTrustedSlotSiblings(candidates, slotPolicyConfig);
2047
+ return candidates.map((candidate) => {
2048
+ const trustPenalty = shouldPenalizeTentativeCurrentSibling(candidate.entry, trustedActiveClaimKeys) ? CLAIM_KEY_TENTATIVE_CURRENT_PENALTY : 0;
2049
+ const redundancyPenalty = resolveTrustedSlotRedundancyPenalty(candidate.entry.id, trustedSlotRankById);
2050
+ if (trustPenalty <= 0 && redundancyPenalty <= 0) {
2051
+ return candidate;
2052
+ }
2053
+ if (trustPenalty > 0) {
2054
+ claimKeyTrace.trustPenalized += 1;
2055
+ }
2056
+ if (redundancyPenalty > 0) {
2057
+ claimKeyTrace.redundancyPenalized += 1;
2058
+ }
2059
+ return {
2060
+ ...candidate,
2061
+ score: clampRecallScore(candidate.score - trustPenalty - redundancyPenalty),
2062
+ scores: {
2063
+ ...candidate.scores,
2064
+ claimKeyTrustPenalty: trustPenalty,
2065
+ claimKeyRedundancyPenalty: redundancyPenalty
2066
+ }
2067
+ };
2068
+ });
2069
+ }
2070
+ function rankTrustedSlotSiblings(candidates, slotPolicyConfig) {
2071
+ const candidatesById = new Map(candidates.map((candidate) => [candidate.entry.id, candidate]));
2072
+ const trustedByClaimKey = /* @__PURE__ */ new Map();
2073
+ for (const candidate of candidates) {
2074
+ const claimKey = candidate.entry.claim_key;
2075
+ if (!claimKey || candidate.entry.claim_key_status !== "trusted" || !isPotentialCurrentPeer(candidate.entry) || resolveClaimSlotPolicy(claimKey, slotPolicyConfig).policy !== "exclusive") {
2076
+ continue;
2077
+ }
2078
+ const siblings = trustedByClaimKey.get(claimKey) ?? [];
2079
+ siblings.push(candidate);
2080
+ trustedByClaimKey.set(claimKey, siblings);
2081
+ }
2082
+ const ranks = /* @__PURE__ */ new Map();
2083
+ for (const siblings of trustedByClaimKey.values()) {
2084
+ siblings.slice().sort(compareCandidatesForTrustedSlotRank).forEach((candidate, index) => {
2085
+ if (candidatesById.has(candidate.entry.id)) {
2086
+ ranks.set(candidate.entry.id, index);
2087
+ }
2088
+ });
2089
+ }
2090
+ return ranks;
2091
+ }
2092
+ function compareCandidatesForTrustedSlotRank(left, right) {
2093
+ return right.score - left.score || createdAtMs(right.entry.created_at) - createdAtMs(left.entry.created_at) || left.entry.id.localeCompare(right.entry.id);
2094
+ }
2095
+ function shouldPenalizeTentativeCurrentSibling(entry, trustedActiveClaimKeys) {
2096
+ return isPotentialCurrentPeer(entry) && entry.claim_key !== void 0 && entry.claim_key_status !== "trusted" && trustedActiveClaimKeys.has(entry.claim_key);
2097
+ }
2098
+ function resolveTrustedSlotRedundancyPenalty(entryId, trustedSlotRankById) {
2099
+ const rank = trustedSlotRankById.get(entryId) ?? 0;
2100
+ if (rank <= 0) {
2101
+ return 0;
2102
+ }
2103
+ return Math.min(CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_MAX_PENALTY, rank * CLAIM_KEY_REDUNDANT_TRUSTED_SLOT_PENALTY);
2104
+ }
2105
+ function clampRecallScore(value) {
2106
+ return Math.max(0, Math.min(1, value));
2107
+ }
2108
+ function hasSufficientReturnEvidence(candidate, query) {
2109
+ if (query.rankingProfile === "entity_attribute") {
2110
+ return hasEntityAttributeEvidence(candidate.entry, query.queryShape);
2111
+ }
2112
+ const groundedLexicalSupport = hasGroundedLexicalSupport(candidate.entry, query.text);
2113
+ if (candidate.scores.lexical > 0) {
2114
+ if (groundedLexicalSupport) {
2115
+ return true;
2116
+ }
2117
+ return candidate.scores.vector >= MIN_VECTOR_WITHOUT_GROUNDED_LEXICAL_SUPPORT;
2118
+ }
2119
+ if (isWeaklyGroundedReminderQuery(query.text) && !groundedLexicalSupport) {
2120
+ return false;
2121
+ }
2122
+ return candidate.scores.vector >= MIN_VECTOR_ONLY_EVIDENCE;
2123
+ }
2124
+ function hasGroundedLexicalSupport(entry, queryText) {
2125
+ const groundingTokens = getGroundingTokens(queryText);
2126
+ if (groundingTokens.length === 0) {
2127
+ return false;
2128
+ }
2129
+ const candidateTokens = new Set(tokenize(`${entry.subject} ${entry.content}`).map(canonicalizeRecallToken));
2130
+ return groundingTokens.some((token) => candidateTokens.has(token));
2131
+ }
2132
+ function isWeaklyGroundedReminderQuery(queryText) {
2133
+ return WEAKLY_GROUNDED_REMINDER_PATTERN.test(queryText);
2134
+ }
2135
+ function hasEntityAttributeEvidence(entry, queryShape) {
2136
+ if (queryShape?.kind !== "entity_attribute") {
2137
+ return false;
2138
+ }
2139
+ const normalizedSubject = normalizeEntityAttributeText(entry.subject);
2140
+ const normalizedContent = normalizeEntityAttributeText(entry.content);
2141
+ const combinedTokens = new Set(tokenize(`${entry.subject} ${entry.content}`));
2142
+ const entityTokenMatches = countTokenMatches(queryShape.entityTokens, combinedTokens);
2143
+ const attributeTokenMatches = countTokenMatches(queryShape.attributeTokens, combinedTokens);
2144
+ if (queryShape.attributeKind === "identity") {
2145
+ if (normalizedSubject === queryShape.normalizedEntity || isIdentityWrapperSubject(normalizedSubject, queryShape.normalizedEntity)) {
2146
+ return true;
2147
+ }
2148
+ }
2149
+ if ((containsNormalizedPhrase(normalizedSubject, queryShape.normalizedEntity) || containsNormalizedPhrase(normalizedContent, queryShape.normalizedEntity)) && (queryShape.entityTokens.length >= 2 || attributeTokenMatches >= 1)) {
2150
+ return true;
2151
+ }
2152
+ return entityTokenMatches >= 2 && attributeTokenMatches >= 1;
2153
+ }
2154
+ function countTokenMatches(expectedTokens, availableTokens) {
2155
+ let matches = 0;
2156
+ for (const token of expectedTokens) {
2157
+ if (availableTokens.has(token)) {
2158
+ matches += 1;
2159
+ }
2160
+ }
2161
+ return matches;
2162
+ }
2163
+ function isIdentityWrapperSubject(normalizedSubject, normalizedEntity) {
2164
+ return Array.from(ENTITY_ATTRIBUTE_IDENTITY_WRAPPERS).some((wrapper) => normalizedSubject === `${normalizedEntity} ${wrapper}`);
2165
+ }
2166
+ function containsNormalizedPhrase(normalizedText, normalizedPhrase) {
2167
+ return normalizedPhrase.length > 0 && normalizedText.includes(normalizedPhrase);
2168
+ }
2169
+ function normalizeEntityAttributeText(text) {
2170
+ return text.replace(/\s+/gu, " ").trim().normalize("NFKC").toLocaleLowerCase();
2171
+ }
2172
+ function mergeCandidates(vectorCandidates, ftsCandidates) {
2173
+ const merged = /* @__PURE__ */ new Map();
2174
+ const vectorRanks = [];
2175
+ const ftsRanks = [];
2176
+ for (const candidate of vectorCandidates) {
2177
+ if (!merged.has(candidate.entry.id)) {
2178
+ vectorRanks.push(candidate.entry.id);
2179
+ }
2180
+ merged.set(candidate.entry.id, {
2181
+ entry: candidate.entry,
2182
+ vectorSim: candidate.vectorSim
2183
+ });
2184
+ }
2185
+ for (const candidate of ftsCandidates) {
2186
+ ftsRanks.push(candidate.entry.id);
2187
+ const existing = merged.get(candidate.entry.id);
2188
+ if (existing) {
2189
+ existing.entry = existing.entry.embedding ? existing.entry : candidate.entry;
2190
+ continue;
2191
+ }
2192
+ merged.set(candidate.entry.id, {
2193
+ entry: candidate.entry
2194
+ });
2195
+ }
2196
+ return {
2197
+ merged,
2198
+ vectorRanks,
2199
+ ftsRanks
2200
+ };
2201
+ }
2202
+ function buildEntryFilters(types, tags, since, until) {
2203
+ const filters = {};
2204
+ if (types && types.length > 0) {
2205
+ filters.types = types;
2206
+ }
2207
+ if (tags && tags.length > 0) {
2208
+ filters.tags = tags;
2209
+ }
2210
+ if (since) {
2211
+ filters.since = since;
2212
+ }
2213
+ if (until) {
2214
+ filters.until = until;
2215
+ }
2216
+ return Object.keys(filters).length > 0 ? filters : void 0;
2217
+ }
2218
+ function applyBudget(results, budget) {
2219
+ if (results.length === 0) {
2220
+ return [];
2221
+ }
2222
+ const accepted = [results[0]];
2223
+ let consumed = estimateTokens(results[0].entry);
2224
+ for (const result of results.slice(1)) {
2225
+ const estimate = estimateTokens(result.entry);
2226
+ if (consumed + estimate > budget) {
2227
+ continue;
2228
+ }
2229
+ accepted.push(result);
2230
+ consumed += estimate;
2231
+ }
2232
+ return accepted;
2233
+ }
2234
+ function sortAcceptedCandidates(candidates, queryText, rankingProfile) {
2235
+ if (rankingProfile === "historical_state" || rankingProfile === "entity_attribute") {
2236
+ return candidates.map((candidate, index) => ({ candidate, index })).sort((left, right) => right.candidate.score - left.candidate.score || left.index - right.index).map(({ candidate }) => candidate);
2237
+ }
2238
+ const groundingTokens = getGroundingTokens(queryText);
2239
+ return candidates.map((candidate, index) => ({
2240
+ candidate,
2241
+ index,
2242
+ grounding: computeGroundingSupport(candidate.entry, groundingTokens)
2243
+ })).sort((left, right) => {
2244
+ const scoreGap = Math.abs(left.candidate.score - right.candidate.score);
2245
+ if (scoreGap > GROUNDING_SORT_MAX_SCORE_GAP || hasStructuralScoreShaping(left.candidate) || hasStructuralScoreShaping(right.candidate)) {
2246
+ if (left.candidate.score !== right.candidate.score) {
2247
+ return right.candidate.score - left.candidate.score;
2248
+ }
2249
+ return left.index - right.index;
2250
+ }
2251
+ if (left.grounding.phraseMatches !== right.grounding.phraseMatches) {
2252
+ return right.grounding.phraseMatches - left.grounding.phraseMatches;
2253
+ }
2254
+ if (left.grounding.coverage !== right.grounding.coverage) {
2255
+ return right.grounding.coverage - left.grounding.coverage;
2256
+ }
2257
+ if (left.candidate.scores.lexical !== right.candidate.scores.lexical) {
2258
+ return right.candidate.scores.lexical - left.candidate.scores.lexical;
2259
+ }
2260
+ if (left.candidate.score !== right.candidate.score) {
2261
+ return right.candidate.score - left.candidate.score;
2262
+ }
2263
+ if (left.candidate.scores.vector !== right.candidate.scores.vector) {
2264
+ return right.candidate.scores.vector - left.candidate.scores.vector;
2265
+ }
2266
+ return left.index - right.index;
2267
+ }).map(({ candidate }) => candidate);
2268
+ }
2269
+ function hasStructuralScoreShaping(candidate) {
2270
+ return candidate.scores.historicalLineage > 0 || candidate.scores.neighborhoodBoost > 0 || candidate.scores.claimKeyTrustPenalty > 0 || candidate.scores.claimKeyRedundancyPenalty > 0;
2271
+ }
2272
+ function getGroundingTokens(queryText) {
2273
+ const seen = /* @__PURE__ */ new Set();
2274
+ const groundingTokens = [];
2275
+ for (const token of tokenize(queryText)) {
2276
+ if (WEAK_QUERY_GROUNDING_TOKENS.has(token)) {
2277
+ continue;
2278
+ }
2279
+ const canonical = canonicalizeRecallToken(token);
2280
+ if (seen.has(canonical)) {
2281
+ continue;
2282
+ }
2283
+ seen.add(canonical);
2284
+ groundingTokens.push(canonical);
2285
+ }
2286
+ return groundingTokens;
2287
+ }
2288
+ function canonicalizeRecallToken(token) {
2289
+ const normalized = token.normalize("NFKC").toLocaleLowerCase();
2290
+ if (normalized === "db" || normalized === "database" || normalized === "databases") {
2291
+ return "db";
2292
+ }
2293
+ if (normalized === "resolve" || normalized === "resolves" || normalized === "resolved" || normalized === "resolving" || normalized === "resolution") {
2294
+ return "resolve";
2295
+ }
2296
+ if (normalized === "branches") {
2297
+ return "branch";
2298
+ }
2299
+ if (normalized === "prefix" || normalized === "prefixes") {
2300
+ return "prefix";
2301
+ }
2302
+ if (normalized.endsWith("ies") && normalized.length > 4) {
2303
+ return `${normalized.slice(0, -3)}y`;
2304
+ }
2305
+ if (normalized.endsWith("es") && normalized.length > 4) {
2306
+ return normalized.slice(0, -2);
2307
+ }
2308
+ if (normalized.endsWith("s") && normalized.length > 3) {
2309
+ return normalized.slice(0, -1);
2310
+ }
2311
+ return normalized;
2312
+ }
2313
+ function computeGroundingSupport(entry, groundingTokens) {
2314
+ if (groundingTokens.length === 0) {
2315
+ return {
2316
+ phraseMatches: 0,
2317
+ coverage: 0
2318
+ };
2319
+ }
2320
+ const subjectTokens = tokenize(entry.subject).map(canonicalizeRecallToken);
2321
+ const contentTokens = tokenize(entry.content).map(canonicalizeRecallToken);
2322
+ const candidateTokens = /* @__PURE__ */ new Set([...subjectTokens, ...contentTokens]);
2323
+ const matchedTokens = groundingTokens.filter((token) => candidateTokens.has(token));
2324
+ return {
2325
+ phraseMatches: countCanonicalPhraseMatches(groundingTokens, subjectTokens, contentTokens),
2326
+ coverage: matchedTokens.length / groundingTokens.length
2327
+ };
2328
+ }
2329
+ function countCanonicalPhraseMatches(queryTokens, subjectTokens, contentTokens) {
2330
+ if (queryTokens.length < 2) {
2331
+ return 0;
2332
+ }
2333
+ const matchedPhrases = /* @__PURE__ */ new Set();
2334
+ for (let size = 2; size <= queryTokens.length; size += 1) {
2335
+ for (let index = 0; index + size <= queryTokens.length; index += 1) {
2336
+ const phraseTokens = queryTokens.slice(index, index + size);
2337
+ if (hasCanonicalConsecutivePhrase(subjectTokens, phraseTokens) || hasCanonicalConsecutivePhrase(contentTokens, phraseTokens)) {
2338
+ matchedPhrases.add(phraseTokens.join(" "));
2339
+ }
2340
+ }
2341
+ }
2342
+ return matchedPhrases.size;
2343
+ }
2344
+ function hasCanonicalConsecutivePhrase(haystack, needle) {
2345
+ if (needle.length === 0 || haystack.length < needle.length) {
2346
+ return false;
2347
+ }
2348
+ for (let index = 0; index + needle.length <= haystack.length; index += 1) {
2349
+ let matches = true;
2350
+ for (let offset = 0; offset < needle.length; offset += 1) {
2351
+ if (haystack[index + offset] !== needle[offset]) {
2352
+ matches = false;
2353
+ break;
2354
+ }
2355
+ }
2356
+ if (matches) {
2357
+ return true;
2358
+ }
2359
+ }
2360
+ return false;
2361
+ }
2362
+ function estimateTokens(entry) {
2363
+ return (entry.subject.length + entry.content.length) / 4;
2364
+ }
2365
+ function parseAroundDate(value) {
2366
+ return parseRelativeDate(value) ?? inferAroundDate(value);
2367
+ }
2368
+ function normalizeLimit(value) {
2369
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2370
+ return 10;
2371
+ }
2372
+ return Math.max(0, Math.floor(value));
2373
+ }
2374
+ function normalizeThreshold(value) {
2375
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2376
+ return 0;
2377
+ }
2378
+ return Math.min(1, Math.max(0, value));
2379
+ }
2380
+ function normalizeBudget(value) {
2381
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2382
+ return null;
2383
+ }
2384
+ return Math.max(0, value);
2385
+ }
2386
+ function normalizeAroundRadius(value) {
2387
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
2388
+ return 14;
2389
+ }
2390
+ return value;
2391
+ }
2392
+ function elapsedMs2(startedAt) {
2393
+ return Math.max(0, Date.now() - startedAt);
2394
+ }
2395
+
2396
+ export {
2397
+ recencyScore,
2398
+ gaussianRecency,
2399
+ importanceScore,
2400
+ scoreCandidate,
2401
+ cosineSimilarity,
2402
+ normalizeClaimKeySegment,
2403
+ normalizeClaimKey,
2404
+ compactClaimKey,
2405
+ validateExtractedClaimKey,
2406
+ inspectClaimKey,
2407
+ isTrustedClaimKeyForCleanup,
2408
+ describeClaimKeyNormalizationFailure,
2409
+ describeExtractedClaimKeyRejection,
2410
+ describeClaimKeySuspicion,
2411
+ tokenize,
2412
+ buildLexicalPlan,
2413
+ computeLexicalScore,
2414
+ inferAroundDate,
2415
+ parseRelativeDate,
2416
+ resolveClaimSlotPolicy,
2417
+ DEFAULT_CROSS_ENCODER_TOP_K,
2418
+ DEFAULT_CROSS_ENCODER_ALPHA,
2419
+ applyCrossEncoderRerank,
2420
+ DEFAULT_RRF_RANK_CONSTANT,
2421
+ rrfFuse,
2422
+ rrfFuseVectorLexical,
2423
+ DEFAULT_MMR_LAMBDA,
2424
+ NEAR_DUPLICATE_SIMILARITY,
2425
+ maximalMarginalRelevance,
2426
+ DEFAULT_NEIGHBORHOOD_BUDGET,
2427
+ DEFAULT_STRONG_SEED_TOP_N,
2428
+ DEFAULT_STRONG_SEED_SCORE_GAP,
2429
+ DEFAULT_SEEDED_RERANK_WEIGHT,
2430
+ selectStrongSeeds,
2431
+ seededRerank,
2432
+ sharesEntryLineage,
2433
+ sharesEpisodeLineage,
2434
+ sharesProcedureLineage,
2435
+ recall
2436
+ };