@untangle-code/search 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +433 -2
  4. package/THIRD_PARTY_LICENSES.md +29 -0
  5. package/dist/benchmark-integrity.d.ts +12 -0
  6. package/dist/benchmark-integrity.d.ts.map +1 -0
  7. package/dist/benchmark-integrity.js +10 -0
  8. package/dist/benchmark-integrity.js.map +1 -0
  9. package/dist/bm25.d.ts +46 -0
  10. package/dist/bm25.d.ts.map +1 -0
  11. package/dist/bm25.js +105 -0
  12. package/dist/bm25.js.map +1 -0
  13. package/dist/deadline.d.ts +4 -0
  14. package/dist/deadline.d.ts.map +1 -0
  15. package/dist/deadline.js +54 -0
  16. package/dist/deadline.js.map +1 -0
  17. package/dist/dedupe.d.ts +48 -0
  18. package/dist/dedupe.d.ts.map +1 -0
  19. package/dist/dedupe.js +58 -0
  20. package/dist/dedupe.js.map +1 -0
  21. package/dist/diversify.d.ts +45 -0
  22. package/dist/diversify.d.ts.map +1 -0
  23. package/dist/diversify.js +30 -0
  24. package/dist/diversify.js.map +1 -0
  25. package/dist/engine.d.ts +157 -0
  26. package/dist/engine.d.ts.map +1 -0
  27. package/dist/engine.js +1498 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/enrichment.d.ts +16 -0
  30. package/dist/enrichment.d.ts.map +1 -0
  31. package/dist/enrichment.js +92 -0
  32. package/dist/enrichment.js.map +1 -0
  33. package/dist/filter.d.ts +86 -0
  34. package/dist/filter.d.ts.map +1 -0
  35. package/dist/filter.js +166 -0
  36. package/dist/filter.js.map +1 -0
  37. package/dist/findability.d.ts +144 -0
  38. package/dist/findability.d.ts.map +1 -0
  39. package/dist/findability.js +393 -0
  40. package/dist/findability.js.map +1 -0
  41. package/dist/format.d.ts +28 -0
  42. package/dist/format.d.ts.map +1 -0
  43. package/dist/format.js +172 -0
  44. package/dist/format.js.map +1 -0
  45. package/dist/fusion.d.ts +47 -0
  46. package/dist/fusion.d.ts.map +1 -0
  47. package/dist/fusion.js +84 -0
  48. package/dist/fusion.js.map +1 -0
  49. package/dist/glob.d.ts +50 -0
  50. package/dist/glob.d.ts.map +1 -0
  51. package/dist/glob.js +144 -0
  52. package/dist/glob.js.map +1 -0
  53. package/dist/grep.d.ts +76 -0
  54. package/dist/grep.d.ts.map +1 -0
  55. package/dist/grep.js +475 -0
  56. package/dist/grep.js.map +1 -0
  57. package/dist/index.d.ts +42 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +63 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/ledger.d.ts +45 -0
  62. package/dist/ledger.d.ts.map +1 -0
  63. package/dist/ledger.js +39 -0
  64. package/dist/ledger.js.map +1 -0
  65. package/dist/payload.d.ts +108 -0
  66. package/dist/payload.d.ts.map +1 -0
  67. package/dist/payload.js +263 -0
  68. package/dist/payload.js.map +1 -0
  69. package/dist/port.d.ts +112 -0
  70. package/dist/port.d.ts.map +1 -0
  71. package/dist/port.js +2 -0
  72. package/dist/port.js.map +1 -0
  73. package/dist/product-complexity.d.ts +89 -0
  74. package/dist/product-complexity.d.ts.map +1 -0
  75. package/dist/product-complexity.js +1367 -0
  76. package/dist/product-complexity.js.map +1 -0
  77. package/dist/regex-safety.d.ts +75 -0
  78. package/dist/regex-safety.d.ts.map +1 -0
  79. package/dist/regex-safety.js +902 -0
  80. package/dist/regex-safety.js.map +1 -0
  81. package/dist/relevance-prerequisites.d.ts +21 -0
  82. package/dist/relevance-prerequisites.d.ts.map +1 -0
  83. package/dist/relevance-prerequisites.js +47 -0
  84. package/dist/relevance-prerequisites.js.map +1 -0
  85. package/dist/relevance.d.ts +107 -0
  86. package/dist/relevance.d.ts.map +1 -0
  87. package/dist/relevance.js +199 -0
  88. package/dist/relevance.js.map +1 -0
  89. package/dist/retrieval.d.ts +46 -0
  90. package/dist/retrieval.d.ts.map +1 -0
  91. package/dist/retrieval.js +182 -0
  92. package/dist/retrieval.js.map +1 -0
  93. package/dist/scoring.d.ts +102 -0
  94. package/dist/scoring.d.ts.map +1 -0
  95. package/dist/scoring.js +175 -0
  96. package/dist/scoring.js.map +1 -0
  97. package/dist/sort.d.ts +21 -0
  98. package/dist/sort.d.ts.map +1 -0
  99. package/dist/sort.js +53 -0
  100. package/dist/sort.js.map +1 -0
  101. package/dist/symbol-match.d.ts +21 -0
  102. package/dist/symbol-match.d.ts.map +1 -0
  103. package/dist/symbol-match.js +72 -0
  104. package/dist/symbol-match.js.map +1 -0
  105. package/dist/tokenize.d.ts +41 -0
  106. package/dist/tokenize.d.ts.map +1 -0
  107. package/dist/tokenize.js +202 -0
  108. package/dist/tokenize.js.map +1 -0
  109. package/dist/types.d.ts +173 -0
  110. package/dist/types.d.ts.map +1 -0
  111. package/dist/types.js +42 -0
  112. package/dist/types.js.map +1 -0
  113. package/package.json +61 -3
  114. package/src/benchmark-integrity.ts +25 -0
  115. package/src/bm25.ts +135 -0
  116. package/src/deadline.ts +59 -0
  117. package/src/dedupe.ts +99 -0
  118. package/src/diversify.ts +71 -0
  119. package/src/engine.ts +1957 -0
  120. package/src/enrichment.ts +115 -0
  121. package/src/filter.ts +267 -0
  122. package/src/findability.ts +580 -0
  123. package/src/format.ts +228 -0
  124. package/src/fusion.ts +121 -0
  125. package/src/glob.ts +151 -0
  126. package/src/grep.ts +618 -0
  127. package/src/index.ts +83 -0
  128. package/src/ledger.ts +72 -0
  129. package/src/payload.ts +382 -0
  130. package/src/port.ts +133 -0
  131. package/src/product-complexity.ts +2168 -0
  132. package/src/regex-safety.ts +1002 -0
  133. package/src/relevance-prerequisites.ts +83 -0
  134. package/src/relevance.ts +328 -0
  135. package/src/retrieval.ts +299 -0
  136. package/src/scoring.ts +321 -0
  137. package/src/sort.ts +86 -0
  138. package/src/symbol-match.ts +87 -0
  139. package/src/tokenize.ts +213 -0
  140. package/src/types.ts +206 -0
package/dist/bm25.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Deterministisches Okapi BM25 ueber den Kandidatenpool des Abrufkerns.
3
+ *
4
+ * WARUM EIGEN UND NICHT AUS DEM SPEICHER: Der `VectorStore` liefert im
5
+ * spaerlichen Zweig einen Rang, den der Speicher berechnet hat (bei Qdrant
6
+ * mit seiner eigenen IDF-Statistik ueber die ganze Collection). Dieser Rang
7
+ * ist ausgezeichnet fuer den ABRUF (Recall), aber er ist nicht in diesem
8
+ * Prozess nachrechenbar und aendert sich mit dem Speicherstand. Die
9
+ * Score-Zerlegung soll erklaeren, WARUM ein Treffer rankt — dafuer muss der
10
+ * lexikalische Anteil hier berechenbar sein. Deshalb:
11
+ *
12
+ * - der Speicher liefert KANDIDATEN (dichter und spaerlicher Zweig),
13
+ * - der lexikalische RANG entsteht in diesem Modul, mit den Statistiken des
14
+ * Kandidatenpools.
15
+ *
16
+ * Der Preis dieser Entscheidung ist benannt und nicht wegdiskutiert: IDF
17
+ * ueber den Kandidatenpool ist nicht IDF ueber das ganze Korpus. Bei kleinen
18
+ * Pools kann ein Wort dadurch seltener aussehen, als es ist. Der Pool ist
19
+ * `candidateLimit` gross (Default 50, Maximum 200) und damit gross genug fuer
20
+ * eine stabile Ordnung INNERHALB des Pools — und nur diese Ordnung geht in
21
+ * die Fusion ein, nicht der absolute Wert. Siehe README „Bekannte Grenzen".
22
+ *
23
+ * Konstanten: k1 = 1.2, b = 0.75 — die Standardwerte aus Robertson/Sparck
24
+ * Jones. Sie sind hier keine „magischen Gewichte", sondern die dokumentierte
25
+ * Voreinstellung des Verfahrens; abweichende Werte muessten begruendet werden.
26
+ */
27
+ import { compareCodepoints } from "@untangle-code/domain";
28
+ import { informativeQueryTokens, termFrequencies, tokenizeIdentifiers } from "./tokenize.js";
29
+ /** Saettigungskonstante der Termhaeufigkeit (Standardwert). */
30
+ export const BM25_K1 = 1.2;
31
+ /** Laengennormalisierung (Standardwert). */
32
+ export const BM25_B = 0.75;
33
+ /**
34
+ * BM25 ueber eine feste Dokumentmenge. Die Statistiken werden EINMAL im
35
+ * Konstruktor gebildet; danach ist `score()` eine reine Funktion.
36
+ */
37
+ export class Bm25Ranker {
38
+ k1;
39
+ b;
40
+ documents = [];
41
+ documentFrequency = new Map();
42
+ averageLength;
43
+ constructor(documents, k1 = BM25_K1, b = BM25_B) {
44
+ this.k1 = k1;
45
+ this.b = b;
46
+ let totalLength = 0;
47
+ for (const document of documents) {
48
+ const tokens = tokenizeIdentifiers(document.text);
49
+ const frequencies = termFrequencies(tokens);
50
+ this.documents.push({ id: document.id, frequencies, length: tokens.length });
51
+ totalLength += tokens.length;
52
+ for (const term of frequencies.keys()) {
53
+ this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
54
+ }
55
+ }
56
+ this.averageLength = this.documents.length === 0 ? 1 : totalLength / this.documents.length;
57
+ }
58
+ get documentCount() {
59
+ return this.documents.length;
60
+ }
61
+ /**
62
+ * Inverse Dokumenthaeufigkeit in der geglaetteten Form
63
+ * `ln(1 + (N - df + 0.5) / (df + 0.5))` — immer positiv, damit ein in jedem
64
+ * Dokument vorkommender Term den Score nicht negativ macht.
65
+ */
66
+ inverseDocumentFrequency(term) {
67
+ const df = this.documentFrequency.get(term) ?? 0;
68
+ const n = this.documents.length;
69
+ return Math.log(1 + (n - df + 0.5) / (df + 0.5));
70
+ }
71
+ scoreDocument(queryTerms, document) {
72
+ let total = 0;
73
+ for (const term of queryTerms) {
74
+ const tf = document.frequencies.get(term) ?? 0;
75
+ if (tf === 0) {
76
+ continue;
77
+ }
78
+ const numerator = tf * (this.k1 + 1);
79
+ const denominator = tf + this.k1 * (1 - this.b + this.b * (document.length / this.averageLength));
80
+ total += this.inverseDocumentFrequency(term) * (numerator / denominator);
81
+ }
82
+ return total;
83
+ }
84
+ /**
85
+ * Rangliste zu einem Suchtext. Dokumente mit Score 0 (kein Term getroffen)
86
+ * fallen heraus — sie haben lexikalisch nichts beigetragen und duerfen die
87
+ * Fusion nicht mit einem Scheinrang beeinflussen.
88
+ *
89
+ * Gleichstand wird deterministisch nach Codepoints der ID aufgeloest, nie
90
+ * nach Einfuegereihenfolge und nie ueber `localeCompare`.
91
+ */
92
+ rank(queryText) {
93
+ const queryTerms = [...termFrequencies(informativeQueryTokens(queryText)).keys()];
94
+ const scored = [];
95
+ for (const document of this.documents) {
96
+ const score = this.scoreDocument(queryTerms, document);
97
+ if (score > 0) {
98
+ scored.push({ id: document.id, score });
99
+ }
100
+ }
101
+ scored.sort((a, b) => a.score === b.score ? compareCodepoints(a.id, b.id) : b.score - a.score);
102
+ return scored.map((entry, index) => ({ id: entry.id, score: entry.score, rank: index + 1 }));
103
+ }
104
+ }
105
+ //# sourceMappingURL=bm25.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bm25.js","sourceRoot":"","sources":["../src/bm25.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type PortCallOptions } from "@untangle-code/domain";
2
+ /** Bound hybrid branches and propagate outer cancellation even if an adapter hangs. */
3
+ export declare function withSearchBranchDeadline<T>(branch: "dense" | "sparse" | "symbol", timeoutMs: number | undefined, retrieve: (options: PortCallOptions) => Promise<T>, options?: PortCallOptions, storeFailureCode?: "QDRANT_UNAVAILABLE" | "LANCEDB_UNAVAILABLE"): Promise<T>;
4
+ //# sourceMappingURL=deadline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../src/deadline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE5E,uFAAuF;AACvF,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,EACrC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,CAAC,CAAC,EAClD,OAAO,CAAC,EAAE,eAAe,EACzB,gBAAgB,GAAE,oBAAoB,GAAG,qBAA4C,GACpF,OAAO,CAAC,CAAC,CAAC,CAiDZ"}
@@ -0,0 +1,54 @@
1
+ import { UntangleError } from "@untangle-code/domain";
2
+ /** Bound hybrid branches and propagate outer cancellation even if an adapter hangs. */
3
+ export async function withSearchBranchDeadline(branch, timeoutMs, retrieve, options, storeFailureCode = "QDRANT_UNAVAILABLE") {
4
+ options?.signal?.throwIfAborted();
5
+ const controller = new AbortController();
6
+ let timeoutError;
7
+ let timer;
8
+ let onAbort;
9
+ const interrupted = new Promise((_, reject) => {
10
+ onAbort = () => {
11
+ const reason = options?.signal?.reason;
12
+ reject(reason);
13
+ controller.abort(reason);
14
+ };
15
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
16
+ if (timeoutMs !== undefined) {
17
+ timer = setTimeout(() => {
18
+ timeoutError = new UntangleError(branch === "dense" ? "EMBEDDING_UNAVAILABLE" : storeFailureCode, `retrieval branch "${branch}" exceeded its ${String(timeoutMs)}ms deadline`, {
19
+ operation: "search_branch",
20
+ retryable: true,
21
+ context: { branch, timeoutMs, timedOut: true },
22
+ });
23
+ // Reject before notifying the adapter: an adapter's JOB_CANCELLED is
24
+ // caused by this local deadline and must not cancel successful peers.
25
+ reject(timeoutError);
26
+ controller.abort(timeoutError);
27
+ }, timeoutMs);
28
+ }
29
+ });
30
+ try {
31
+ // Promise.race installs handlers on both promises, including a late
32
+ // adapter rejection after a deadline. It cannot become unhandled.
33
+ return await Promise.race([
34
+ Promise.resolve().then(() => {
35
+ controller.signal.throwIfAborted();
36
+ return retrieve({ ...options, signal: controller.signal });
37
+ }),
38
+ interrupted,
39
+ ]);
40
+ }
41
+ catch (error) {
42
+ options?.signal?.throwIfAborted();
43
+ if (timeoutError !== undefined)
44
+ throw timeoutError;
45
+ throw error;
46
+ }
47
+ finally {
48
+ if (timer !== undefined)
49
+ clearTimeout(timer);
50
+ if (onAbort !== undefined)
51
+ options?.signal?.removeEventListener("abort", onAbort);
52
+ }
53
+ }
54
+ //# sourceMappingURL=deadline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadline.js","sourceRoot":"","sources":["../src/deadline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAwB,MAAM,uBAAuB,CAAC;AAE5E,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAqC,EACrC,SAA6B,EAC7B,QAAkD,EAClD,OAAyB,EACzB,mBAAiE,oBAAoB;IAErF,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,IAAI,YAAuC,CAAC;IAC5C,IAAI,KAAgD,CAAC;IACrD,IAAI,OAAiC,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QACnD,OAAO,GAAG,GAAG,EAAE;YACb,MAAM,MAAM,GAAY,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC;YAChD,MAAM,CAAC,MAAM,CAAC,CAAC;YACf,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,CAAC,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBACtB,YAAY,GAAG,IAAI,aAAa,CAC9B,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,gBAAgB,EAC/D,qBAAqB,MAAM,kBAAkB,MAAM,CAAC,SAAS,CAAC,aAAa,EAC3E;oBACE,SAAS,EAAE,eAAe;oBAC1B,SAAS,EAAE,IAAI;oBACf,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC/C,CACF,CAAC;gBACF,qEAAqE;gBACrE,sEAAsE;gBACtE,MAAM,CAAC,YAAY,CAAC,CAAC;gBACrB,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACjC,CAAC,EAAE,SAAS,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,oEAAoE;QACpE,kEAAkE;QAClE,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC;YACxB,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC1B,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;gBACnC,OAAO,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7D,CAAC,CAAC;YACF,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QAClC,IAAI,YAAY,KAAK,SAAS;YAAE,MAAM,YAAY,CAAC;QACnD,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,KAAK,KAAK,SAAS;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACpF,CAAC;AACH,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Deduplizierung (spec.md §13.5, architecture.md §18.4).
3
+ *
4
+ * Mehrere Fragmente desselben Symbols duerfen nicht alle Top-Plaetze belegen.
5
+ * Der Dedupe-Schluessel ist woertlich der aus architecture.md §18.4:
6
+ *
7
+ * workspace_id + repo_id + ref_id + metrics_scope_id
8
+ *
9
+ * Der Ref gehoert bewusst hinein: derselbe Code in zwei Branches oder zwei
10
+ * Worktrees sind ZWEI Antworten, nicht eine — wer ueber mehrere Refs sucht,
11
+ * will beide sehen, jeweils mit ihrer Herkunft (vision.md §1a „Branch- und
12
+ * Worktree-Bewusstsein").
13
+ *
14
+ * Verbunden werden die Bestandteile mit `ID_COMPONENT_SEPARATOR` (U+001F),
15
+ * derselben Trennzeichenkonvention wie bei allen Untangle-IDs — ein Pfad oder
16
+ * Name kann so keinen Schluessel „ueberlaufen" lassen.
17
+ */
18
+ import type { CodeChunkPayloadV1, RelatedFragmentV1 } from "@untangle-code/domain";
19
+ import type { SearchCandidate } from "./payload.js";
20
+ /** Baut den Dedupe-Schluessel eines Chunk-Payloads. */
21
+ export declare function buildDedupeKey(payload: CodeChunkPayloadV1): string;
22
+ /** Ein Kandidat mit seinem Basisscore, wie ihn die Fusion geliefert hat. */
23
+ export interface ScoredCandidate {
24
+ candidate: SearchCandidate;
25
+ /** Normalisierter Basisscore aus der Fusion, in [0, 1]. */
26
+ baseScore: number;
27
+ }
28
+ /** Ergebnis der Deduplizierung einer Gruppe. */
29
+ export interface DedupedCandidate {
30
+ primary: ScoredCandidate;
31
+ dedupeKey: string;
32
+ relatedFragments: RelatedFragmentV1[];
33
+ /** Die verdraengten Kandidaten — fuer den Skip-Ledger (E4). */
34
+ absorbed: ScoredCandidate[];
35
+ }
36
+ /**
37
+ * Fasst Kandidaten mit gleichem Dedupe-Schluessel zusammen.
38
+ *
39
+ * Primaer bleibt der bestbewertete; bei Gleichstand entscheidet die
40
+ * Codepoint-Ordnung der Chunk-ID — nie die Reihenfolge, in der der Speicher
41
+ * geantwortet hat.
42
+ *
43
+ * Die Eingabereihenfolge bleibt fuer die Ausgabe massgeblich: die Gruppen
44
+ * erscheinen in der Reihenfolge ihres ersten Vorkommens, damit die Rangfolge
45
+ * der Fusion erhalten bleibt.
46
+ */
47
+ export declare function dedupeCandidates(scored: readonly ScoredCandidate[]): DedupedCandidate[];
48
+ //# sourceMappingURL=dedupe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAIlE;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,eAAe,CAAC;IAC3B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;IACtC,+DAA+D;IAC/D,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,gBAAgB,EAAE,CAuBvF"}
package/dist/dedupe.js ADDED
@@ -0,0 +1,58 @@
1
+ import { ID_COMPONENT_SEPARATOR, compareCodepoints } from "@untangle-code/domain";
2
+ /** Baut den Dedupe-Schluessel eines Chunk-Payloads. */
3
+ export function buildDedupeKey(payload) {
4
+ return [payload.workspace_id, payload.repo_id, payload.ref_id, payload.metrics_scope_id].join(ID_COMPONENT_SEPARATOR);
5
+ }
6
+ /**
7
+ * Fasst Kandidaten mit gleichem Dedupe-Schluessel zusammen.
8
+ *
9
+ * Primaer bleibt der bestbewertete; bei Gleichstand entscheidet die
10
+ * Codepoint-Ordnung der Chunk-ID — nie die Reihenfolge, in der der Speicher
11
+ * geantwortet hat.
12
+ *
13
+ * Die Eingabereihenfolge bleibt fuer die Ausgabe massgeblich: die Gruppen
14
+ * erscheinen in der Reihenfolge ihres ersten Vorkommens, damit die Rangfolge
15
+ * der Fusion erhalten bleibt.
16
+ */
17
+ export function dedupeCandidates(scored) {
18
+ const groups = new Map();
19
+ for (const entry of scored) {
20
+ const key = buildDedupeKey(entry.candidate.payload);
21
+ const existing = groups.get(key);
22
+ if (existing === undefined) {
23
+ groups.set(key, [entry]);
24
+ }
25
+ else {
26
+ existing.push(entry);
27
+ }
28
+ }
29
+ return [...groups].map(([key, group]) => {
30
+ const members = [...group].sort(compareScored);
31
+ const primary = members[0];
32
+ const absorbed = members.slice(1);
33
+ return {
34
+ primary,
35
+ dedupeKey: key,
36
+ absorbed,
37
+ relatedFragments: absorbed.map((member) => toRelatedFragment(member)),
38
+ };
39
+ });
40
+ }
41
+ function compareScored(a, b) {
42
+ if (a.baseScore !== b.baseScore) {
43
+ return b.baseScore - a.baseScore;
44
+ }
45
+ return compareCodepoints(a.candidate.payload.chunk_id, b.candidate.payload.chunk_id);
46
+ }
47
+ function toRelatedFragment(entry) {
48
+ const { payload } = entry.candidate;
49
+ return {
50
+ chunkId: payload.chunk_id,
51
+ chunkKind: payload.chunk_kind,
52
+ ...(payload.fragment_index === undefined ? {} : { fragmentIndex: payload.fragment_index }),
53
+ startLine: payload.start_line,
54
+ endLine: payload.end_line,
55
+ score: entry.baseScore,
56
+ };
57
+ }
58
+ //# sourceMappingURL=dedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAIlF,uDAAuD;AACvD,MAAM,UAAU,cAAc,CAAC,OAA2B;IACxD,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAC3F,sBAAsB,CACvB,CAAC;AACJ,CAAC;AAkBD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkC;IACjE,MAAM,MAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;IACpD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACtC,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAoB,CAAC;QAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClC,OAAO;YACL,OAAO;YACP,SAAS,EAAE,GAAG;YACd,QAAQ;YACR,gBAAgB,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;SACtE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,CAAkB,EAAE,CAAkB;IAC3D,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;IACnC,CAAC;IACD,OAAO,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAsB;IAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC;IACpC,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,SAAS,EAAE,OAAO,CAAC,UAAU;QAC7B,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;QAC1F,SAAS,EAAE,OAAO,CAAC,UAAU;QAC7B,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,KAAK,EAAE,KAAK,CAAC,SAAS;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Diversifizierung (spec.md §13.2 Schritt 10, architecture.md §18.5).
3
+ *
4
+ * Ziel ist der Fall „zehn nahezu identische Treffer derselben Datei". Nach der
5
+ * Deduplizierung ueber `metrics_scope_id` bleiben immer noch mehrere Symbole
6
+ * derselben Datei uebrig — und wer nach „Funktion X" sucht, will nicht
7
+ * zehnmal dieselbe Datei sehen, sondern die zehn besten STELLEN.
8
+ *
9
+ * Verfahren (bewusst ohne Inhaltsvektoren): Die Liste wird in Rangfolge
10
+ * durchlaufen; jeder Treffer bekommt eine Strafe, die davon abhaengt, wie oft
11
+ * seine Datei (und, schwaecher, sein Repository) vorher schon vertreten war:
12
+ *
13
+ * strafe(n) = 1 - 1 / (1 + n)
14
+ *
15
+ * mit `n` = Zahl der vorher schon vertretenen Treffer derselben Datei. Der
16
+ * erste Treffer einer Datei bekommt also 0, der zweite 0.5, der dritte 0.667 —
17
+ * eine saettigende Kurve, die nie 1 erreicht. Damit verschwindet keine Datei
18
+ * vollstaendig aus dem Ergebnis; sie wird nur immer teurer.
19
+ *
20
+ * Warum keine Content-MMR: dafuer braeuchte es die dichten Vektoren aller
21
+ * Kandidaten. Der `VectorStore`-Port liefert sie beim Suchen nicht mit
22
+ * (`hybridQuery` gibt Payload, keine Vektoren), und sie nachzuladen kostet
23
+ * eine zweite Runde ueber den Speicher. Die datei-/repo-basierte Streuung ist
24
+ * die Dimension, die architecture.md §18.5 zuerst nennt, und sie kostet nichts.
25
+ * Der Verzicht auf die Content-Dimension steht in der README unter „Bekannte
26
+ * Grenzen".
27
+ */
28
+ import type { CodeChunkPayloadV1 } from "@untangle-code/domain";
29
+ /** Anteil der Datei-Strafe, den ein reiner Repo-Wiederholer bekommt. */
30
+ export declare const REPO_REPETITION_SHARE = 0.25;
31
+ /** Ein Eintrag der Diversifizierung: Gegenstand plus zugeteilte Strafe. */
32
+ export interface DiversificationEntry<T> {
33
+ item: T;
34
+ /** Rohwert der `duplicate_penalty` in [0, 1). */
35
+ penaltyRawValue: number;
36
+ }
37
+ /**
38
+ * Berechnet die Duplikatsstrafen in Rangfolge.
39
+ *
40
+ * Die Funktion sortiert NICHT um — sie bewertet nur. Die Neuordnung passiert
41
+ * spaeter ueber den fertigen Endscore, damit jede Verschiebung in der
42
+ * Score-Zerlegung sichtbar ist statt in einem unsichtbaren Sortierschritt.
43
+ */
44
+ export declare function assignDuplicatePenalties<T>(items: readonly T[], payloadOf: (item: T) => CodeChunkPayloadV1): DiversificationEntry<T>[];
45
+ //# sourceMappingURL=diversify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diversify.d.ts","sourceRoot":"","sources":["../src/diversify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,IAAI,EAAE,CAAC,CAAC;IACR,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;CACzB;AAMD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,kBAAkB,GACzC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAgB3B"}
@@ -0,0 +1,30 @@
1
+ import { ID_COMPONENT_SEPARATOR } from "@untangle-code/domain";
2
+ /** Anteil der Datei-Strafe, den ein reiner Repo-Wiederholer bekommt. */
3
+ export const REPO_REPETITION_SHARE = 0.25;
4
+ function saturatingPenalty(previousOccurrences) {
5
+ return 1 - 1 / (1 + previousOccurrences);
6
+ }
7
+ /**
8
+ * Berechnet die Duplikatsstrafen in Rangfolge.
9
+ *
10
+ * Die Funktion sortiert NICHT um — sie bewertet nur. Die Neuordnung passiert
11
+ * spaeter ueber den fertigen Endscore, damit jede Verschiebung in der
12
+ * Score-Zerlegung sichtbar ist statt in einem unsichtbaren Sortierschritt.
13
+ */
14
+ export function assignDuplicatePenalties(items, payloadOf) {
15
+ const fileCounts = new Map();
16
+ const repoCounts = new Map();
17
+ return items.map((item) => {
18
+ const payload = payloadOf(item);
19
+ const repoKey = [payload.repo_id, payload.ref_id].join(ID_COMPONENT_SEPARATOR);
20
+ const fileKey = [repoKey, payload.file_id].join(ID_COMPONENT_SEPARATOR);
21
+ const previousFile = fileCounts.get(fileKey) ?? 0;
22
+ const previousRepo = repoCounts.get(repoKey) ?? 0;
23
+ fileCounts.set(fileKey, previousFile + 1);
24
+ repoCounts.set(repoKey, previousRepo + 1);
25
+ const filePenalty = saturatingPenalty(previousFile);
26
+ const repoPenalty = previousFile === 0 ? saturatingPenalty(previousRepo) * REPO_REPETITION_SHARE : 0;
27
+ return { item, penaltyRawValue: Math.min(1, filePenalty + repoPenalty) };
28
+ });
29
+ }
30
+ //# sourceMappingURL=diversify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diversify.js","sourceRoot":"","sources":["../src/diversify.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAS1C,SAAS,iBAAiB,CAAC,mBAA2B;IACpD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAmB,EACnB,SAA0C;IAE1C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAC/E,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACxE,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;QAC1C,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,WAAW,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,WAAW,GACf,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,YAAY,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;QACnF,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC,EAAE,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Der hybride Abrufkern (spec.md §13.2; architecture.md §18; vision.md §1d).
3
+ *
4
+ * ABRUF UND RANGFOLGE SIND ZWEI VERSCHIEDENE DINGE — das ist die zentrale
5
+ * Bauentscheidung dieses Moduls und der Grund, warum sich der Suchmodus so
6
+ * einfach einschraenken laesst:
7
+ *
8
+ * - ABRUF (Recall) holt Kandidaten aus dem Speicher. Quellen: der dichte
9
+ * Zweig (`hybridQuery` mit Query-Embedding), der spaerliche Zweig
10
+ * (`hybridQuery` mit spaerlichem Vektor, sofern ein `SparseVectorizer`
11
+ * gestellt wurde) und der Symbolzweig (`scroll` mit Filter auf
12
+ * `symbol_name`).
13
+ * - RANGFOLGE entsteht danach IN DIESEM PROZESS: der dichte Rang aus den
14
+ * Cosine-Scores des Speichers, der lexikalische Rang aus dem eigenen BM25
15
+ * ueber den Kandidatenpool, der Symbolrang aus dem Namensvergleich. Diese
16
+ * drei Ranglisten werden per RRF fusioniert (`fusion.ts`).
17
+ *
18
+ * Der Suchmodus waehlt Abruf UND Rangfolge: `semantic` fragt nur den
19
+ * dichten Zweig, `lexical` nur den spaerlichen, `symbol` nur den
20
+ * Symbolspeicher. Im Hybridmodus laufen die drei Zweige unabhaengig;
21
+ * ausgefallene Zweige duerfen nutzbare Antworten anderer Zweige nicht
22
+ * verhindern. Jede solche Teilsuche bleibt als Degradation sichtbar.
23
+ *
24
+ * E4 durchgaengig: jeder verworfene Kandidat — Nachfilter, `minScore`,
25
+ * Dedupe, `limit`, unbrauchbarer Payload, ausgefallenes Ziel — bekommt einen
26
+ * Eintrag im Skip-Ledger des Ergebnisses. Kein `catch`, das schweigt.
27
+ */
28
+ import type { SearchConfig } from "@untangle-code/config";
29
+ import type { Clock, LoggerPort, PayloadFilter, PortCallOptions, SearchQueryDefaults, SearchResultSetV1, VectorStore } from "@untangle-code/domain";
30
+ import type { BranchWeights } from "./fusion.js";
31
+ import type { FindabilityLookup, GraphProximityLookup, ProfileTargetV1, SearchGraphEnrichmentPort, SearchPort, SparseVectorizer } from "./port.js";
32
+ import type { RelevancePolicyBindingV1 } from "./relevance.js";
33
+ import type { GrepRequestV1, GrepResultSetV1, SearchQueryExtensionsV1, UntangleSearchRequestV1 } from "./types.js";
34
+ /** Halbwertszeit des Recency-Boosts, wenn keine gesetzt ist. */
35
+ export declare const DEFAULT_RECENCY_HALF_LIFE_DAYS = 30;
36
+ /** Harte Grenze fuer freie Providerwarnungen und Batch-Diagnose-IDs. */
37
+ export declare const SEARCH_GRAPH_DIAGNOSTIC_LIMIT = 32;
38
+ /** Providerwarnungen sind eine eigene Ausgabeachse und deshalb separat gedeckelt. */
39
+ export declare const SEARCH_GRAPH_PROVIDER_WARNING_LIMIT = 16;
40
+ export declare const DEFAULT_SEARCH_BRANCH_TIMEOUT_MS = 10000;
41
+ /** Alles, was der Abrufkern braucht — reine Ports und Daten, keine Adapter. */
42
+ export interface HybridSearchEngineDeps {
43
+ vectorStore: VectorStore;
44
+ /** Ein Ziel je Embedding-Profil (spec.md §13.4). */
45
+ targets: ProfileTargetV1[];
46
+ /** Vorgaben aus der Konfiguration fuer `normalizeSearchQuery`. */
47
+ defaults: SearchQueryDefaults;
48
+ searchConfig?: SearchConfig;
49
+ /** Nur fuer den Recency-Boost noetig; ohne Clock entfaellt der Bestandteil. */
50
+ clock?: Clock;
51
+ logger?: LoggerPort;
52
+ /** Erzeugt spaerliche Anfragevektoren; siehe {@link SparseVectorizer}. */
53
+ sparseVectorizer?: SparseVectorizer;
54
+ /** Repo-Prioritaeten in [0, 1] fuer den `repo_priority_boost`. */
55
+ repoPriorities?: Record<string, number>;
56
+ graphProximity?: GraphProximityLookup;
57
+ /** Post-Ranking-Anreicherung; kennt als Port keine konkreten Graphpakete. */
58
+ graphEnrichment?: SearchGraphEnrichmentPort;
59
+ findability?: FindabilityLookup;
60
+ recencyHalfLifeDays?: number;
61
+ branchWeights?: BranchWeights;
62
+ /** Hybrid-only deadline per independent branch; single-mode uses caller cancellation. */
63
+ branchTimeoutMs?: number;
64
+ /**
65
+ * Profilgebundene Abwesenheitsentscheidungen. Eine leere Map behaelt das
66
+ * bisherige ungefilterte Verhalten. Ist die Map konfiguriert, aber fuer ein
67
+ * aktives Profil fehlt der Eintrag, bleibt die Trefferliste ebenfalls
68
+ * unveraendert und die Engine meldet die nicht ausgewertete Policy sichtbar.
69
+ */
70
+ relevancePolicies?: Readonly<Record<string, RelevancePolicyBindingV1>>;
71
+ /** Display-only legacy fallback; never accepted as persisted provenance. */
72
+ indexGeneration?: string;
73
+ }
74
+ /**
75
+ * Gibt dem PRAEZISIONSzweig bei einer zeichengleichen Symbolanfrage genug
76
+ * Basisgewicht, dass ein exakter Treffer selbst unter den erlaubten
77
+ * Boost-Grenzfaellen vor einem nicht-exakten Treffer bleibt.
78
+ *
79
+ * Bei MAX_BOOST_SHARE=0.5 kann ein nicht-exakter Basisscore hoechstens auf
80
+ * 1.5x steigen und ein exakter durch Strafen hoechstens auf 0.5x fallen. Das
81
+ * benoetigte Basisverhaeltnis ist daher (1+s)/(1-s)=3. Der kleine vorhandene
82
+ * Symbolanteil oberhalb dieser Grenze macht die Ungleichung strikt. Der
83
+ * Boost-Deckel selbst bleibt vollkommen unveraendert.
84
+ */
85
+ export declare function exactSymbolIntentBranchWeights(weights: BranchWeights, active: {
86
+ dense: boolean;
87
+ lexical: boolean;
88
+ symbol: boolean;
89
+ }): BranchWeights;
90
+ /** Zahl dichter Spitzenplaetze, die einen lexikalischen Spitzenkandidaten bestaetigen. */
91
+ export declare const GERMAN_LEXICAL_CORROBORATION_DEPTH = 10;
92
+ /**
93
+ * Nur eine als deutsch erkannte Frage mit einem lexikalischen Spitzenreiter,
94
+ * der NICHT unter den zehn dicht besten Kandidaten vorkommt, verliert im
95
+ * Hybridmodus den lokalen BM25-Rang. Stimmen beide unabhaengigen Zweige
96
+ * ueberein, bleibt das nuetzliche lexikalische Signal erhalten.
97
+ */
98
+ export declare function shouldOmitUncorroboratedGermanLexicalBranch(queryText: string, denseRankedIds: readonly string[], lexicalRankedIds: readonly string[]): boolean;
99
+ /** Normalisiert die Zusatzfelder dieses Pakets (Gegenstueck zu `SearchQueryV1`). */
100
+ export declare function normalizeExtensions(request: UntangleSearchRequestV1): SearchQueryExtensionsV1;
101
+ /**
102
+ * Die Zusatzangaben, die den Fingerabdruck der Anfrage veraendern MUESSEN.
103
+ *
104
+ * Der Fingerabdruck beantwortet „ist das dieselbe Frage?". `mode`,
105
+ * `pathExcludes`, `complexityBias` und `sort` aendern die Treffermenge bzw.
106
+ * die Rangfolge — zwei Anfragen, die sich nur darin unterscheiden, sind NICHT
107
+ * dieselbe Frage. `includeContent` bleibt draussen: es aendert nur die
108
+ * Darstellung, genau wie `limit` und `candidateLimit` (siehe
109
+ * `buildSearchQueryFingerprint`).
110
+ *
111
+ * Nur ABWEICHUNGEN vom Standard gehen ein. Eine Anfrage ohne
112
+ * Paket-Erweiterungen behaelt damit exakt den Fingerabdruck, den sie ohne
113
+ * dieses Feld haette — Findability-Datensaetze aelterer Laeufe bleiben
114
+ * vergleichbar.
115
+ */
116
+ export declare function fingerprintRankingOptions(extensions: SearchQueryExtensionsV1): Record<string, unknown> | undefined;
117
+ /**
118
+ * Die Referenzimplementierung des {@link SearchPort}.
119
+ *
120
+ * Sie ist speicher-agnostisch: alles laeuft ueber den `VectorStore`-Port, also
121
+ * gegen Qdrant genauso wie gegen den In-Memory-Speicher des testkits.
122
+ */
123
+ export declare class HybridSearchEngine implements SearchPort {
124
+ private readonly deps;
125
+ private readonly config;
126
+ private readonly branchWeights;
127
+ private readonly branchTimeoutMs;
128
+ private readonly relevancePolicies;
129
+ constructor(deps: HybridSearchEngineDeps);
130
+ search(request: UntangleSearchRequestV1, options?: PortCallOptions): Promise<SearchResultSetV1>;
131
+ private rankingContextFingerprint;
132
+ private enrichFinalHits;
133
+ /**
134
+ * grep laeuft im selben Geltungsbereich wie `search`: Workspace aus den
135
+ * Vorgaben, wenn die Anfrage keinen nennt, und nur die Repositories der
136
+ * verdrahteten Ziele. Sonst faende dieselbe Oberflaeche mit demselben
137
+ * Aufrufer je nach Werkzeug verschiedene Mengen.
138
+ */
139
+ grep(request: GrepRequestV1, options?: PortCallOptions): Promise<GrepResultSetV1>;
140
+ private searchTarget;
141
+ /** Uebernimmt Speicherpunkte in den Kandidatenpool; unbrauchbare landen im Ledger. */
142
+ private collect;
143
+ /** Pfad-Globs und Findability — die Filter, die der Speicher nicht kennt. */
144
+ private passesResidualFilters;
145
+ private buildProximityMap;
146
+ private buildHit;
147
+ private indexGenerationOf;
148
+ /** Persisted candidate provenance; display fallbacks are deliberately excluded. */
149
+ private rawIndexGenerationOf;
150
+ private indexContextProvenance;
151
+ private complexityOf;
152
+ }
153
+ /** Kleiner Bequemlichkeits-Konstruktor. */
154
+ export declare function createHybridSearchEngine(deps: HybridSearchEngineDeps): HybridSearchEngine;
155
+ /** Wiederexport, damit Aufrufer den Filtertyp nicht aus zwei Paketen ziehen muessen. */
156
+ export type { PayloadFilter };
157
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}