@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
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Exakte und nahe Symbolnamen-Treffer (dritter Abrufzweig).
3
+ *
4
+ * Der Nutzerfall aus vision.md §1d ist woertlich: „wenn ich nach ‚Funktion X'
5
+ * suche". Ein dichter Vektor kann einen exakten Namen verfehlen, weil er
6
+ * Bedeutung und nicht Zeichen vergleicht; BM25 kann ihn unter langen Rumpf-
7
+ * texten begraben. Der Symbolzweig ist deshalb kein Bonus auf einen anderen
8
+ * Zweig, sondern eine eigene Rangliste mit eigener Begruendung.
9
+ *
10
+ * Vier Stufen, absichtlich grob und vollstaendig dokumentiert (jede feinere
11
+ * Abstufung waere ein magisches Gewicht ohne Beleg):
12
+ *
13
+ * | Staerke | Bedingung |
14
+ * |---|---|
15
+ * | 1.00 | `symbol_name` ist zeichengleich mit dem Suchtext |
16
+ * | 0.80 | zeichengleich bis auf Gross-/Kleinschreibung |
17
+ * | 0.60 | `symbol_fqn` endet auf den Suchtext (`Service.charge`) |
18
+ * | 0.40 | alle Teilwoerter der Anfrage stecken im Namen (`getUser` ⊂ `getUserById`) |
19
+ */
20
+ import { compareCodepoints } from "@untangle-code/domain";
21
+ import type { CodeChunkPayloadV1 } from "@untangle-code/domain";
22
+
23
+ import { identifierParts } from "./tokenize.js";
24
+
25
+ /** Ergebnis eines Namensvergleichs. */
26
+ export interface SymbolMatch {
27
+ strength: number;
28
+ /** Beleg fuer die Score-Zerlegung, z. B. `symbol_name == "charge"`. */
29
+ evidence: string;
30
+ }
31
+
32
+ /** Vergleicht den Suchtext mit den Namensfeldern eines Chunks. */
33
+ export function matchSymbolName(
34
+ payload: CodeChunkPayloadV1,
35
+ queryText: string,
36
+ ): SymbolMatch | null {
37
+ const name = payload.symbol_name;
38
+ const query = queryText.trim();
39
+ if (query === "") {
40
+ return null;
41
+ }
42
+ if (name !== undefined && name === query) {
43
+ return { strength: 1, evidence: `symbol_name == "${name}"` };
44
+ }
45
+ if (name !== undefined && name.toLowerCase() === query.toLowerCase()) {
46
+ return { strength: 0.8, evidence: `symbol_name ~= "${name}" (case-insensitive)` };
47
+ }
48
+ const fqn = payload.symbol_fqn;
49
+ if (
50
+ fqn !== undefined &&
51
+ (fqn === query || fqn.endsWith(`.${query}`) || fqn.endsWith(`::${query}`))
52
+ ) {
53
+ return { strength: 0.6, evidence: `symbol_fqn "${fqn}" ends with "${query}"` };
54
+ }
55
+ if (name === undefined) {
56
+ return null;
57
+ }
58
+ const queryParts = identifierParts(query);
59
+ const nameParts = new Set(identifierParts(name));
60
+ if (queryParts.length > 0 && queryParts.every((part) => nameParts.has(part))) {
61
+ return { strength: 0.4, evidence: `symbol_name "${name}" contains all query parts` };
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Kandidatenwerte fuer den Speicher-Filter auf `symbol_name`.
68
+ *
69
+ * Der Payload-Filter des Speichers kann nur exakte Werte vergleichen. Deshalb
70
+ * werden hier deterministisch die Schreibweisen erzeugt, die derselbe Name
71
+ * ueblicherweise hat: der Suchtext selbst, sein letztes Pfad-/FQN-Glied und —
72
+ * falls der Suchtext genau ein Wort ist — dieses Wort. Mehr NICHT: eine
73
+ * Kombinatorik ueber camelCase/snake_case-Varianten wuerde Treffer erfinden,
74
+ * die im Index gar nicht so heissen.
75
+ */
76
+ export function symbolNameFilterCandidates(queryText: string): string[] {
77
+ const query = queryText.trim();
78
+ if (query === "") {
79
+ return [];
80
+ }
81
+ const candidates = new Set<string>([query]);
82
+ const lastSegment = query.split(/[.:#/]/u).at(-1);
83
+ if (lastSegment !== undefined && lastSegment !== "") {
84
+ candidates.add(lastSegment);
85
+ }
86
+ return [...candidates].sort(compareCodepoints);
87
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Bezeichner-bewusste Tokenisierung fuer den lexikalischen Zweig
3
+ * (spec.md §13.2 Schritt 3: „Query normalisieren, ohne Identifier-Zeichen zu
4
+ * zerstoeren").
5
+ *
6
+ * Eine reine Wortgrenzen-Tokenisierung („alles ausser Buchstaben/Ziffern ist
7
+ * Trenner") zerlegt `get_user_by_id` korrekt, `getUserById` aber ueberhaupt
8
+ * nicht — dabei ist genau das der Fall, den der Nutzer meint, wenn er nach
9
+ * „Funktion X" sucht. Diese Tokenisierung liefert deshalb ZWEI Ebenen:
10
+ *
11
+ * 1. den ganzen, kleingeschriebenen Bezeichner (`getuserbyid`) — damit ein
12
+ * exakter Namenstreffer den hoechsten lexikalischen Gewinn hat;
13
+ * 2. seine Teilwoerter (`get`, `user`, `by`, `id`) — damit ein Treffer auch
14
+ * ueber Teilbegriffe gefunden wird.
15
+ *
16
+ * Beide Ebenen landen im selben Token-Bag. Das ist bewusst: BM25 belohnt
17
+ * dadurch einen Volltreffer zusaetzlich zu seinen Teilen, ohne dass es dafuer
18
+ * ein Sonderregelwerk im Ranker braucht.
19
+ *
20
+ * Alles hier ist reine Arithmetik ueber den Eingabetext — kein Locale, keine
21
+ * Zeit, kein Zufall (docs/BUILD_PLAN.md §3 „Determinismus").
22
+ */
23
+
24
+ import { tokenizeIdentifiers } from "@untangle-code/domain";
25
+
26
+ // Indexing, sparse queries and reranking must use one versioned token space.
27
+ export { identifierParts, splitIdentifierRun, tokenizeIdentifiers } from "@untangle-code/domain";
28
+
29
+ /**
30
+ * Funktionswoerter, die in frei formulierten Suchfragen fast nur die Form der
31
+ * Frage beschreiben. Sie als BM25-Begriffe zu werten bevorzugt lange Prosa,
32
+ * die ebenfalls oft "wo wird"/"how is" enthaelt, vor dem gesuchten Code.
33
+ *
34
+ * Die Liste ist absichtlich klein und enthaelt nur geschlossene grammatische
35
+ * Klassen in Deutsch und Englisch. Fachwoerter, Verben und Bezeichner werden
36
+ * nie als Stopwoerter geraten. Wenn eine Anfrage ausschliesslich daraus
37
+ * besteht, faellt {@link informativeQueryTokens} auf die Originaltokens
38
+ * zurueck, damit beispielsweise eine Symbolsuche nach `is` nicht verschwindet.
39
+ */
40
+ const QUERY_STOP_WORDS = new Set([
41
+ "a",
42
+ "als",
43
+ "an",
44
+ "and",
45
+ "are",
46
+ "as",
47
+ "at",
48
+ "auf",
49
+ "aus",
50
+ "be",
51
+ "bei",
52
+ "by",
53
+ "das",
54
+ "dass",
55
+ "dem",
56
+ "den",
57
+ "der",
58
+ "des",
59
+ "die",
60
+ "does",
61
+ "ein",
62
+ "eine",
63
+ "einem",
64
+ "einen",
65
+ "einer",
66
+ "eines",
67
+ "es",
68
+ "for",
69
+ "from",
70
+ "für",
71
+ "fuer",
72
+ "how",
73
+ "im",
74
+ "in",
75
+ "into",
76
+ "is",
77
+ "ist",
78
+ "it",
79
+ "mit",
80
+ "of",
81
+ "on",
82
+ "or",
83
+ "sich",
84
+ "sind",
85
+ "that",
86
+ "the",
87
+ "this",
88
+ "to",
89
+ "und",
90
+ "von",
91
+ "was",
92
+ "wann",
93
+ "warum",
94
+ "werden",
95
+ "what",
96
+ "when",
97
+ "where",
98
+ "which",
99
+ "wie",
100
+ "wird",
101
+ "with",
102
+ "wo",
103
+ "wofür",
104
+ "wofuer",
105
+ "who",
106
+ "why",
107
+ "welche",
108
+ "welchem",
109
+ "welchen",
110
+ "welcher",
111
+ "welches",
112
+ "wer",
113
+ "zu",
114
+ ]);
115
+
116
+ const UNAMBIGUOUS_GERMAN_QUESTION_OPENERS = new Set([
117
+ "warum",
118
+ "wann",
119
+ "welche",
120
+ "welchem",
121
+ "welchen",
122
+ "welcher",
123
+ "welches",
124
+ "wer",
125
+ "wie",
126
+ "wo",
127
+ "wofür",
128
+ "wofuer",
129
+ ]);
130
+
131
+ const GERMAN_WAS_PREDICATES = new Set([
132
+ "bedeutet",
133
+ "geschieht",
134
+ "ist",
135
+ "passiert",
136
+ "sind",
137
+ "werden",
138
+ "wird",
139
+ ]);
140
+
141
+ const GERMAN_INFLECTED_QUESTION_PRONOUNS = new Set([
142
+ "welche",
143
+ "welchem",
144
+ "welchen",
145
+ "welcher",
146
+ "welches",
147
+ ]);
148
+
149
+ const GERMAN_GRAMMAR_MARKERS = new Set([
150
+ "das",
151
+ "dass",
152
+ "dem",
153
+ "den",
154
+ "der",
155
+ "des",
156
+ "die",
157
+ "ein",
158
+ "eine",
159
+ "einem",
160
+ "einen",
161
+ "einer",
162
+ "eines",
163
+ "ist",
164
+ "sich",
165
+ "und",
166
+ "von",
167
+ "werden",
168
+ "wird",
169
+ "zu",
170
+ ]);
171
+
172
+ /**
173
+ * Tokenisiert eine frei formulierte Anfrage fuer den lexikalischen Rang und
174
+ * entfernt nur grammatische Fragehuelsen. Wiederholungen bleiben erhalten;
175
+ * der Aufrufer entscheidet wie beim normalen Token-Bag selbst, ob er daraus
176
+ * eine Menge oder Haeufigkeiten braucht.
177
+ */
178
+ export function informativeQueryTokens(text: string): string[] {
179
+ const tokens = tokenizeIdentifiers(text);
180
+ const informative = tokens.filter((token) => !QUERY_STOP_WORDS.has(token));
181
+ return informative.length === 0 ? tokens : informative;
182
+ }
183
+
184
+ /**
185
+ * Erkennt die enge Form, fuer die der Benchmark einen systematischen
186
+ * Cross-Language-Konflikt belegt: eine deutsch formulierte Frage vor
187
+ * ueberwiegend englischen Codebezeichnern. Das ist bewusst KEINE allgemeine
188
+ * Spracherkennung. Eindeutige deutsche Fragewoerter reichen; das auch im
189
+ * Englischen vorkommende `was` braucht zwei weitere Grammatikmarker.
190
+ */
191
+ export function isLikelyGermanQuestion(text: string): boolean {
192
+ const tokens = tokenizeIdentifiers(text);
193
+ const first = tokens[0];
194
+ if (first === undefined) {
195
+ return false;
196
+ }
197
+ return (
198
+ UNAMBIGUOUS_GERMAN_QUESTION_OPENERS.has(first) ||
199
+ (first === "in" && GERMAN_INFLECTED_QUESTION_PRONOUNS.has(tokens[1] ?? "")) ||
200
+ (first === "was" &&
201
+ (GERMAN_WAS_PREDICATES.has(tokens[1] ?? "") ||
202
+ new Set(tokens.filter((token) => GERMAN_GRAMMAR_MARKERS.has(token))).size >= 2))
203
+ );
204
+ }
205
+
206
+ /** Token-Haeufigkeiten als Map; Reihenfolge der Einfuegung ist Textreihenfolge. */
207
+ export function termFrequencies(tokens: readonly string[]): Map<string, number> {
208
+ const counts = new Map<string, number>();
209
+ for (const token of tokens) {
210
+ counts.set(token, (counts.get(token) ?? 0) + 1);
211
+ }
212
+ return counts;
213
+ }
package/src/types.ts ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Paket-eigene Vertraege des Abrufkerns.
3
+ *
4
+ * Grundsatz: Alles, was schon in `@untangle-code/domain` steht
5
+ * (`SearchRequestV1`, `SearchQueryV1`, `ScoreBreakdownV1`, `SearchHitV1`,
6
+ * `SearchResultSetV1`, `SkipLedgerEntryV1`, …), wird NICHT neu definiert,
7
+ * sondern verwendet. Hier stehen nur die Erweiterungen, die der Abrufkern
8
+ * zusaetzlich braucht und die die Werkzeuggrenze aus spec.md §13.1 bewusst
9
+ * nicht kennt:
10
+ *
11
+ * - Suchmodus (semantisch / lexikalisch / Symbolname / hybrid),
12
+ * - die grep-artigen Faehigkeiten (Nutzerwunsch A4),
13
+ * - Ausgabeformate und Sortierung,
14
+ * - der Komplexitaets-Bias (vision.md §1b: Komplexitaet ist ein Blickwinkel
15
+ * auf dieselbe Frage, kein zweites Werkzeug).
16
+ */
17
+ import type {
18
+ IndexFreshnessV1,
19
+ SearchAbstention,
20
+ SearchRequestV1,
21
+ SkipLedgerEntryV1,
22
+ ToolWarningV1,
23
+ } from "@untangle-code/domain";
24
+
25
+ /**
26
+ * Welche unabhaengigen Abruf- und Rankingzweige ausgefuehrt werden.
27
+ *
28
+ * - `hybrid` (Default): dicht + lexikalisch + exakter Symbolname, fusioniert;
29
+ * - `semantic`: nur der dichte Zweig;
30
+ * - `lexical`: nur BM25;
31
+ * - `symbol`: nur der exakte Symbolnamen-Abrufzweig.
32
+ *
33
+ * Lexikalische und Symbolsuche erzeugen kein Queryembedding. Ein
34
+ * eingeschraenkter Modus laesst die anderen Abrufzweige weg; Fusion und
35
+ * Score-Zerlegung bleiben dieselben. Genau deshalb
36
+ * bleibt ein `lexical`-Ergebnis mit einem `hybrid`-Ergebnis vergleichbar
37
+ * lesbar — es fehlen nur Bestandteile, es sind keine anderen.
38
+ */
39
+ export const SEARCH_MODES = ["hybrid", "semantic", "lexical", "symbol"] as const;
40
+
41
+ export type SearchMode = (typeof SEARCH_MODES)[number];
42
+
43
+ // Aus dem Domain re-exportiert, damit Aufrufer nur EIN Paket brauchen.
44
+ export { SEARCH_ABSTENTIONS } from "@untangle-code/domain";
45
+ export type { SearchAbstention };
46
+
47
+ /**
48
+ * Ausdrueckliche Bevorzugung einfacher oder komplexer Treffer (vision.md §1b).
49
+ *
50
+ * Default ist `none`. Das ist eine Entscheidung, keine Bequemlichkeit:
51
+ * Komplexitaet DARF die Rangfolge nicht stillschweigend verschieben. Wer sie
52
+ * einbezieht, bekommt dafuer einen sichtbaren `complexity_modifier` in der
53
+ * Score-Zerlegung.
54
+ */
55
+ export const COMPLEXITY_BIASES = ["none", "prefer-simple", "prefer-complex"] as const;
56
+
57
+ export type ComplexityBias = (typeof COMPLEXITY_BIASES)[number];
58
+
59
+ /** Sortierschluessel der Trefferliste (Nutzerwunsch A4: „Sortierung"). */
60
+ export const SEARCH_SORTS = [
61
+ "relevance",
62
+ "path",
63
+ "complexity-desc",
64
+ "complexity-asc",
65
+ "findability-asc",
66
+ ] as const;
67
+
68
+ export type SearchSort = (typeof SEARCH_SORTS)[number];
69
+
70
+ /** Ausgabeformate (Nutzerwunsch A4: „Text, JSON, JSONL"). */
71
+ export const OUTPUT_FORMATS = ["text", "json", "jsonl"] as const;
72
+
73
+ export type OutputFormat = (typeof OUTPUT_FORMATS)[number];
74
+
75
+ /**
76
+ * Suchanfrage des Abrufkerns: die Spezifikationsform aus spec.md §13.1 plus
77
+ * die Erweiterungen dieses Pakets. Additiv — jede gueltige `SearchRequestV1`
78
+ * ist auch eine gueltige `UntangleSearchRequestV1`.
79
+ */
80
+ export interface UntangleSearchRequestV1 extends SearchRequestV1 {
81
+ mode?: SearchMode;
82
+ /**
83
+ * Verhalten bei unzureichender Evidenz. Fehlt es, gilt `strict` — das ist
84
+ * byte-identisch zum Verhalten vor Welle 14, bis in beide Fingerprints.
85
+ */
86
+ abstention?: SearchAbstention;
87
+ /** Pfadmuster, die AUSGESCHLOSSEN werden (Gegenstueck zu `pathGlob`). */
88
+ pathExclude?: string[];
89
+ /** Chunk-Inhalt mitliefern; Default false (tokenarm fuer Agents). */
90
+ includeContent?: boolean;
91
+ complexityBias?: ComplexityBias;
92
+ sort?: SearchSort;
93
+ /** Nur Treffer mit Findability-Score <= diesem Wert (vision.md §1d). */
94
+ findabilityMaxScore?: number;
95
+ /** Nur Treffer mit Findability-Score >= diesem Wert. */
96
+ findabilityMinScore?: number;
97
+ }
98
+
99
+ /**
100
+ * Normalisierte Zusatzangaben — das Gegenstueck zu `SearchQueryV1` fuer die
101
+ * Felder, die dieses Paket ergaenzt. Alle Felder sind gesetzt (keine
102
+ * `undefined`-Faelle mehr), damit der Ausfuehrungspfad keine Defaults mehr
103
+ * erraten muss.
104
+ */
105
+ export interface SearchQueryExtensionsV1 {
106
+ mode: SearchMode;
107
+ abstention: SearchAbstention;
108
+ pathExcludes: string[];
109
+ includeContent: boolean;
110
+ complexityBias: ComplexityBias;
111
+ sort: SearchSort;
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // grep-Modus (Nutzerwunsch A4)
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /** Sortierung der grep-Treffer. */
119
+ export const GREP_SORTS = ["path", "matches-desc"] as const;
120
+
121
+ export type GrepSort = (typeof GREP_SORTS)[number];
122
+
123
+ /**
124
+ * Regex-/Literalsuche ueber den INDEXIERTEN Inhalt.
125
+ *
126
+ * Wichtig fuer das Verstaendnis der Ergebnisse: gesucht wird im Index, nicht
127
+ * auf der Platte. Ein nicht indizierter oder wegen Groesse uebersprungener
128
+ * Inhalt kann hier nicht gefunden werden — deshalb traegt das Ergebnis den
129
+ * Skip-Ledger mit, statt schlicht „nichts gefunden" zu behaupten (E4).
130
+ */
131
+ export interface GrepRequestV1 {
132
+ /** Suchmuster; regulaerer Ausdruck, wenn `regex` true ist, sonst Literal. */
133
+ pattern: string;
134
+ regex?: boolean;
135
+ caseSensitive?: boolean;
136
+ /** Nur ganze Woerter (Bezeichnergrenzen). */
137
+ wholeWord?: boolean;
138
+ workspaceId?: string;
139
+ repositories?: string[];
140
+ refs?: string[];
141
+ languages?: string[];
142
+ pathGlob?: string[];
143
+ pathExclude?: string[];
144
+ /** Kontextzeilen vor bzw. nach der Trefferzeile. */
145
+ contextLinesBefore?: number;
146
+ contextLinesAfter?: number;
147
+ /** Obergrenze der zurueckgegebenen Treffer insgesamt. */
148
+ maxMatches?: number;
149
+ /** Obergrenze je Datei; verhindert, dass eine Datei die Liste fuellt. */
150
+ maxMatchesPerFile?: number;
151
+ /**
152
+ * Obergrenze der durchsuchten Chunks. Schuetzt vor pathologischen
153
+ * Ausdruecken und riesigen Indizes; Ueberschreitung ist ein sichtbarer
154
+ * Abbruchgrund, kein stilles Kuerzen.
155
+ */
156
+ maxScannedChunks?: number;
157
+ sort?: GrepSort;
158
+ }
159
+
160
+ /** Eine Kontextzeile eines grep-Treffers. */
161
+ export interface GrepContextLineV1 {
162
+ /** 1-basierte Zeilennummer in der Datei. */
163
+ line: number;
164
+ text: string;
165
+ }
166
+
167
+ /** Ein einzelner grep-Treffer mit Herkunft und Kontext. */
168
+ export interface GrepMatchV1 {
169
+ repoId: string;
170
+ repoLabel: string;
171
+ refId: string;
172
+ refName: string;
173
+ commitSha?: string;
174
+ fileId: string;
175
+ /** „/"-normalisierter, repo-relativer Pfad. */
176
+ filePath: string;
177
+ language: string;
178
+ chunkId: string;
179
+ symbolName?: string;
180
+ /** 1-basierte Zeilennummer in der Datei. */
181
+ line: number;
182
+ /** 1-basierte Spalte des Treffers innerhalb der Zeile (UTF-16-Einheiten). */
183
+ column: number;
184
+ /** Der getroffene Text selbst. */
185
+ matchText: string;
186
+ /** Die vollstaendige Trefferzeile. */
187
+ lineText: string;
188
+ before: GrepContextLineV1[];
189
+ after: GrepContextLineV1[];
190
+ }
191
+
192
+ /** Ergebnis einer grep-Ausfuehrung. */
193
+ export interface GrepResultSetV1 {
194
+ schemaVersion: 1;
195
+ indexFreshness?: IndexFreshnessV1;
196
+ pattern: string;
197
+ isRegex: boolean;
198
+ matches: GrepMatchV1[];
199
+ totalMatches: number;
200
+ /** True, wenn eine Grenze griff; dann ist `truncationReason` Pflicht (E4). */
201
+ truncated: boolean;
202
+ truncationReason?: "max_matches" | "max_matches_per_file" | "max_scanned_chunks";
203
+ scannedChunks: number;
204
+ skipped: SkipLedgerEntryV1[];
205
+ warnings: ToolWarningV1[];
206
+ }