@rce-mcp/retrieval-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,4338 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import type { EnhancePromptInput, EnhancePromptOutput, SearchContextInput, SearchContextOutput } from "@rce-mcp/contracts";
3
+ import {
4
+ buildQueryCacheKey,
5
+ type CandidateScoreWeights,
6
+ type IndexRepository,
7
+ type QueryCache,
8
+ type RankedChunkCandidate,
9
+ type WorkspaceRecord
10
+ } from "@rce-mcp/data-plane";
11
+ import { InMemoryQueryCache } from "@rce-mcp/data-plane";
12
+ import { getObservability, type Observability } from "@rce-mcp/observability";
13
+ import {
14
+ buildChunksForFile,
15
+ getChunkingParserAvailabilitySnapshot,
16
+ type ChunkingFallbackReason,
17
+ type ChunkingStrategy
18
+ } from "./chunking.js";
19
+
20
+ type RetrievalMode = SearchContextOutput["search_metadata"]["retrieval_mode"];
21
+ type ContextRef = EnhancePromptOutput["context_refs"][number];
22
+
23
+ const MAX_FILE_SIZE_BYTES = 1_000_000;
24
+ const MAX_CHUNKS_PER_FILE = 300;
25
+ const TARGET_CHUNK_TOKENS = 220;
26
+ const CHUNK_OVERLAP_TOKENS = 40;
27
+ const MAX_TOP_K = 20;
28
+ const MAX_CONTEXT_BUDGET_TOKENS = 12_000;
29
+ export const DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_BASE_URL = "https://router.tumuer.me/v1";
30
+ export const DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_MODEL = "Qwen/Qwen3-Embedding-4B";
31
+ export const DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_DIMENSIONS = 2560;
32
+ export const DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_TIMEOUT_MS = 10_000;
33
+ export const DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_BATCH_SIZE = 64;
34
+ export const DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_MAX_RETRIES = 2;
35
+
36
+ const DEFAULT_CANDIDATE_SCORE_WEIGHTS: CandidateScoreWeights = {
37
+ lexical_weight: 0.6,
38
+ vector_weight: 0.4,
39
+ path_match_boost: 0.2,
40
+ recency_boost: 0.1,
41
+ generated_penalty: 0.2
42
+ };
43
+
44
+ export interface RetrievalPathBiasConfig {
45
+ source_path_boost: number;
46
+ low_priority_prefix_penalty: number;
47
+ low_priority_substring_penalty: number;
48
+ low_priority_asset_penalty: number;
49
+ lockfile_penalty: number;
50
+ doc_intent_docs_boost: number;
51
+ doc_intent_markdown_boost: number;
52
+ docs_without_doc_intent_penalty: number;
53
+ non_doc_markdown_penalty: number;
54
+ code_intent_docs_penalty: number;
55
+ doc_intent_source_penalty: number;
56
+ workspace_manifest_root_boost: number;
57
+ workspace_manifest_nested_boost: number;
58
+ ui_component_tsx_boost: number;
59
+ ui_component_css_penalty: number;
60
+ docs_archive_penalty: number;
61
+ public_path_penalty: number;
62
+ test_path_penalty: number;
63
+ example_path_penalty: number;
64
+ declaration_file_penalty: number;
65
+ test_intent_test_boost: number;
66
+ example_intent_example_boost: number;
67
+ filename_token_match_boost: number;
68
+ negation_avoid_docs_penalty: number;
69
+ negation_avoid_tests_penalty: number;
70
+ negation_avoid_examples_penalty: number;
71
+ negation_avoid_archive_penalty: number;
72
+ min_total_bias: number;
73
+ max_total_bias: number;
74
+ }
75
+
76
+ export interface RetrievalRerankConfig {
77
+ low_information_penalty: number;
78
+ max_chunks_per_path_default: number;
79
+ max_chunks_per_path_file_lookup: number;
80
+ same_directory_penalty: number;
81
+ same_extension_penalty: number;
82
+ }
83
+
84
+ export interface RetrievalScoringConfig {
85
+ candidate_weights: CandidateScoreWeights;
86
+ path_bias: RetrievalPathBiasConfig;
87
+ rerank: RetrievalRerankConfig;
88
+ }
89
+
90
+ export interface RetrievalEnhancerConfig {
91
+ max_expansion_hints: number;
92
+ max_candidates_pre_rerank: number;
93
+ rerank_timeout_ms: number;
94
+ }
95
+
96
+ export interface RetrievalChunkingConfig {
97
+ strategy: ChunkingStrategy;
98
+ fallback_strategy: "sliding";
99
+ parse_timeout_ms: number;
100
+ enabled_languages: string[];
101
+ }
102
+
103
+ export type RetrievalScoringConfigInput = Partial<{
104
+ candidate_weights: Partial<CandidateScoreWeights>;
105
+ path_bias: Partial<RetrievalPathBiasConfig>;
106
+ rerank: Partial<RetrievalRerankConfig>;
107
+ }>;
108
+
109
+ export type RetrievalEnhancerConfigInput = Partial<RetrievalEnhancerConfig>;
110
+ export type RetrievalChunkingConfigInput = Partial<{
111
+ strategy: ChunkingStrategy;
112
+ fallback_strategy: "sliding";
113
+ parse_timeout_ms: number;
114
+ enabled_languages: string[];
115
+ }>;
116
+
117
+ export const BASELINE_RETRIEVAL_SCORING_CONFIG: RetrievalScoringConfig = {
118
+ candidate_weights: { ...DEFAULT_CANDIDATE_SCORE_WEIGHTS },
119
+ path_bias: {
120
+ source_path_boost: 0.12,
121
+ low_priority_prefix_penalty: 0.15,
122
+ low_priority_substring_penalty: 0.15,
123
+ low_priority_asset_penalty: 0.2,
124
+ lockfile_penalty: 0.2,
125
+ doc_intent_docs_boost: 0.18,
126
+ doc_intent_markdown_boost: 0.08,
127
+ docs_without_doc_intent_penalty: 0.2,
128
+ non_doc_markdown_penalty: 0.15,
129
+ code_intent_docs_penalty: 0.08,
130
+ doc_intent_source_penalty: 0.08,
131
+ workspace_manifest_root_boost: 0.26,
132
+ workspace_manifest_nested_boost: 0.16,
133
+ ui_component_tsx_boost: 0.18,
134
+ ui_component_css_penalty: 0.2,
135
+ docs_archive_penalty: 0.15,
136
+ public_path_penalty: 0.08,
137
+ test_path_penalty: 0.35,
138
+ example_path_penalty: 0.2,
139
+ declaration_file_penalty: 0.12,
140
+ test_intent_test_boost: 0.08,
141
+ example_intent_example_boost: 0.06,
142
+ filename_token_match_boost: 0.06,
143
+ negation_avoid_docs_penalty: 0.35,
144
+ negation_avoid_tests_penalty: 0.35,
145
+ negation_avoid_examples_penalty: 0.3,
146
+ negation_avoid_archive_penalty: 0.35,
147
+ min_total_bias: -0.45,
148
+ max_total_bias: 0.35
149
+ },
150
+ rerank: {
151
+ low_information_penalty: 0.15,
152
+ max_chunks_per_path_default: 2,
153
+ max_chunks_per_path_file_lookup: 1,
154
+ same_directory_penalty: 0,
155
+ same_extension_penalty: 0
156
+ }
157
+ };
158
+
159
+ export const CONSERVATIVE_RETRIEVAL_SCORING_CONFIG: RetrievalScoringConfig = {
160
+ candidate_weights: {
161
+ lexical_weight: 0.55,
162
+ vector_weight: 0.45,
163
+ path_match_boost: 0.1,
164
+ recency_boost: 0.05,
165
+ generated_penalty: 0.15
166
+ },
167
+ path_bias: {
168
+ source_path_boost: 0.08,
169
+ low_priority_prefix_penalty: 0.08,
170
+ low_priority_substring_penalty: 0.08,
171
+ low_priority_asset_penalty: 0.12,
172
+ lockfile_penalty: 0.12,
173
+ doc_intent_docs_boost: 0.1,
174
+ doc_intent_markdown_boost: 0.05,
175
+ docs_without_doc_intent_penalty: 0.08,
176
+ non_doc_markdown_penalty: 0.08,
177
+ code_intent_docs_penalty: 0.05,
178
+ doc_intent_source_penalty: 0.05,
179
+ workspace_manifest_root_boost: 0.14,
180
+ workspace_manifest_nested_boost: 0.1,
181
+ ui_component_tsx_boost: 0.1,
182
+ ui_component_css_penalty: 0.1,
183
+ docs_archive_penalty: 0.1,
184
+ public_path_penalty: 0.05,
185
+ test_path_penalty: 0.12,
186
+ example_path_penalty: 0.06,
187
+ declaration_file_penalty: 0.08,
188
+ test_intent_test_boost: 0.04,
189
+ example_intent_example_boost: 0.04,
190
+ filename_token_match_boost: 0.04,
191
+ negation_avoid_docs_penalty: 0.2,
192
+ negation_avoid_tests_penalty: 0.2,
193
+ negation_avoid_examples_penalty: 0.16,
194
+ negation_avoid_archive_penalty: 0.2,
195
+ min_total_bias: -0.25,
196
+ max_total_bias: 0.2
197
+ },
198
+ rerank: {
199
+ low_information_penalty: 0.1,
200
+ max_chunks_per_path_default: 2,
201
+ max_chunks_per_path_file_lookup: 1,
202
+ same_directory_penalty: 0,
203
+ same_extension_penalty: 0
204
+ }
205
+ };
206
+
207
+ export const DEFAULT_RETRIEVAL_ENHANCER_CONFIG: RetrievalEnhancerConfig = {
208
+ max_expansion_hints: 24,
209
+ max_candidates_pre_rerank: 4,
210
+ rerank_timeout_ms: 40
211
+ };
212
+
213
+ export const DEFAULT_RETRIEVAL_CHUNKING_CONFIG: RetrievalChunkingConfig = {
214
+ strategy: "sliding",
215
+ fallback_strategy: "sliding",
216
+ parse_timeout_ms: 80,
217
+ enabled_languages: ["typescript", "javascript", "python", "go"]
218
+ };
219
+
220
+ const BUILTIN_RETRIEVAL_SCORING_PROFILES = {
221
+ baseline: BASELINE_RETRIEVAL_SCORING_CONFIG,
222
+ conservative: CONSERVATIVE_RETRIEVAL_SCORING_CONFIG
223
+ } as const;
224
+
225
+ export type BuiltinRetrievalScoringProfileId = keyof typeof BUILTIN_RETRIEVAL_SCORING_PROFILES;
226
+
227
+ function deepCloneScoringConfig(config: RetrievalScoringConfig): RetrievalScoringConfig {
228
+ return {
229
+ candidate_weights: { ...config.candidate_weights },
230
+ path_bias: { ...config.path_bias },
231
+ rerank: { ...config.rerank }
232
+ };
233
+ }
234
+
235
+ function assertFiniteNumber(value: unknown, label: string): number {
236
+ if (typeof value !== "number" || !Number.isFinite(value)) {
237
+ throw new Error(`invalid retrieval scoring config: ${label} must be a finite number`);
238
+ }
239
+ return value;
240
+ }
241
+
242
+ function validateScoringConfig(config: RetrievalScoringConfig): void {
243
+ const weights = config.candidate_weights;
244
+ const pathBias = config.path_bias;
245
+ const rerank = config.rerank;
246
+
247
+ assertFiniteNumber(weights.lexical_weight, "candidate_weights.lexical_weight");
248
+ assertFiniteNumber(weights.vector_weight, "candidate_weights.vector_weight");
249
+ assertFiniteNumber(weights.path_match_boost, "candidate_weights.path_match_boost");
250
+ assertFiniteNumber(weights.recency_boost, "candidate_weights.recency_boost");
251
+ assertFiniteNumber(weights.generated_penalty, "candidate_weights.generated_penalty");
252
+
253
+ const weightSum = weights.lexical_weight + weights.vector_weight;
254
+ if (Math.abs(weightSum - 1) > 0.001) {
255
+ throw new Error("invalid retrieval scoring config: lexical_weight + vector_weight must equal 1");
256
+ }
257
+
258
+ for (const [key, value] of Object.entries(pathBias)) {
259
+ assertFiniteNumber(value, `path_bias.${key}`);
260
+ }
261
+
262
+ if (pathBias.min_total_bias > pathBias.max_total_bias) {
263
+ throw new Error("invalid retrieval scoring config: path_bias.min_total_bias must be <= max_total_bias");
264
+ }
265
+
266
+ assertFiniteNumber(rerank.low_information_penalty, "rerank.low_information_penalty");
267
+ if (!Number.isInteger(rerank.max_chunks_per_path_default) || rerank.max_chunks_per_path_default <= 0) {
268
+ throw new Error("invalid retrieval scoring config: rerank.max_chunks_per_path_default must be a positive integer");
269
+ }
270
+ if (!Number.isInteger(rerank.max_chunks_per_path_file_lookup) || rerank.max_chunks_per_path_file_lookup <= 0) {
271
+ throw new Error(
272
+ "invalid retrieval scoring config: rerank.max_chunks_per_path_file_lookup must be a positive integer"
273
+ );
274
+ }
275
+ assertFiniteNumber(rerank.same_directory_penalty, "rerank.same_directory_penalty");
276
+ assertFiniteNumber(rerank.same_extension_penalty, "rerank.same_extension_penalty");
277
+ if (rerank.same_directory_penalty < 0) {
278
+ throw new Error("invalid retrieval scoring config: rerank.same_directory_penalty must be >= 0");
279
+ }
280
+ if (rerank.same_extension_penalty < 0) {
281
+ throw new Error("invalid retrieval scoring config: rerank.same_extension_penalty must be >= 0");
282
+ }
283
+ }
284
+
285
+ export function resolveRetrievalScoringProfile(profile_id: string | undefined): {
286
+ profile_id: BuiltinRetrievalScoringProfileId;
287
+ config: RetrievalScoringConfig;
288
+ } {
289
+ const normalized = (profile_id ?? "baseline").trim().toLowerCase() as BuiltinRetrievalScoringProfileId;
290
+ if (!(normalized in BUILTIN_RETRIEVAL_SCORING_PROFILES)) {
291
+ throw new Error(
292
+ `unknown retrieval scoring profile "${profile_id ?? ""}". Expected one of: ${Object.keys(
293
+ BUILTIN_RETRIEVAL_SCORING_PROFILES
294
+ ).join(", ")}`
295
+ );
296
+ }
297
+ return {
298
+ profile_id: normalized,
299
+ config: deepCloneScoringConfig(BUILTIN_RETRIEVAL_SCORING_PROFILES[normalized])
300
+ };
301
+ }
302
+
303
+ export function mergeRetrievalScoringConfig(
304
+ base: RetrievalScoringConfig,
305
+ overrides?: RetrievalScoringConfigInput
306
+ ): RetrievalScoringConfig {
307
+ const next = deepCloneScoringConfig(base);
308
+ if (overrides?.candidate_weights) {
309
+ next.candidate_weights = {
310
+ ...next.candidate_weights,
311
+ ...overrides.candidate_weights
312
+ };
313
+ }
314
+ if (overrides?.path_bias) {
315
+ next.path_bias = {
316
+ ...next.path_bias,
317
+ ...overrides.path_bias
318
+ };
319
+ }
320
+ if (overrides?.rerank) {
321
+ next.rerank = {
322
+ ...next.rerank,
323
+ ...overrides.rerank
324
+ };
325
+ }
326
+ validateScoringConfig(next);
327
+ return next;
328
+ }
329
+
330
+ function validateEnhancerConfig(config: RetrievalEnhancerConfig): void {
331
+ if (!Number.isInteger(config.max_expansion_hints) || config.max_expansion_hints <= 0) {
332
+ throw new Error("invalid retrieval enhancer config: max_expansion_hints must be a positive integer");
333
+ }
334
+ if (!Number.isInteger(config.max_candidates_pre_rerank) || config.max_candidates_pre_rerank <= 0) {
335
+ throw new Error("invalid retrieval enhancer config: max_candidates_pre_rerank must be a positive integer");
336
+ }
337
+ if (!Number.isInteger(config.rerank_timeout_ms) || config.rerank_timeout_ms <= 0) {
338
+ throw new Error("invalid retrieval enhancer config: rerank_timeout_ms must be a positive integer");
339
+ }
340
+ }
341
+
342
+ export function mergeRetrievalEnhancerConfig(
343
+ base: RetrievalEnhancerConfig,
344
+ overrides?: RetrievalEnhancerConfigInput
345
+ ): RetrievalEnhancerConfig {
346
+ const next: RetrievalEnhancerConfig = {
347
+ ...base,
348
+ ...(overrides ?? {})
349
+ };
350
+ validateEnhancerConfig(next);
351
+ return next;
352
+ }
353
+
354
+ function normalizeChunkingLanguageList(value: string[]): string[] {
355
+ const deduped = new Set<string>();
356
+ for (const language of value) {
357
+ const normalized = language.trim().toLowerCase();
358
+ if (normalized.length === 0) {
359
+ continue;
360
+ }
361
+ deduped.add(normalized);
362
+ }
363
+ return [...deduped];
364
+ }
365
+
366
+ function validateChunkingConfig(config: RetrievalChunkingConfig): void {
367
+ if (!["language_aware", "sliding"].includes(config.strategy)) {
368
+ throw new Error("invalid retrieval chunking config: strategy must be language_aware|sliding");
369
+ }
370
+ if (config.fallback_strategy !== "sliding") {
371
+ throw new Error("invalid retrieval chunking config: fallback_strategy must be sliding");
372
+ }
373
+ if (!Number.isInteger(config.parse_timeout_ms) || config.parse_timeout_ms <= 0) {
374
+ throw new Error("invalid retrieval chunking config: parse_timeout_ms must be a positive integer");
375
+ }
376
+ if (!Array.isArray(config.enabled_languages) || config.enabled_languages.length === 0) {
377
+ throw new Error("invalid retrieval chunking config: enabled_languages must include at least one language");
378
+ }
379
+ for (const language of config.enabled_languages) {
380
+ if (typeof language !== "string" || language.trim().length === 0) {
381
+ throw new Error("invalid retrieval chunking config: enabled_languages must contain non-empty strings");
382
+ }
383
+ }
384
+ }
385
+
386
+ export function mergeRetrievalChunkingConfig(
387
+ base: RetrievalChunkingConfig,
388
+ overrides?: RetrievalChunkingConfigInput
389
+ ): RetrievalChunkingConfig {
390
+ const next: RetrievalChunkingConfig = {
391
+ ...base,
392
+ ...(overrides ?? {}),
393
+ enabled_languages: normalizeChunkingLanguageList(overrides?.enabled_languages ?? base.enabled_languages)
394
+ };
395
+ validateChunkingConfig(next);
396
+ return next;
397
+ }
398
+
399
+ function stableSerialize(value: unknown): string {
400
+ if (Array.isArray(value)) {
401
+ return `[${value.map((entry) => stableSerialize(entry)).join(",")}]`;
402
+ }
403
+ if (value && typeof value === "object") {
404
+ const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b));
405
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableSerialize(v)}`).join(",")}}`;
406
+ }
407
+ return JSON.stringify(value);
408
+ }
409
+
410
+ function scoringConfigChecksum(config: RetrievalScoringConfig): string {
411
+ return sha256(stableSerialize(config)).slice(0, 12);
412
+ }
413
+
414
+ const REASON_STRINGS = [
415
+ "semantic match",
416
+ "exact symbol match",
417
+ "path and token overlap",
418
+ "recently modified relevant module"
419
+ ] as const;
420
+
421
+ export type RetrievalReason = (typeof REASON_STRINGS)[number];
422
+
423
+ export class RetrievalError extends Error {
424
+ readonly code: "INVALID_ARGUMENT" | "NOT_FOUND" | "UPSTREAM_FAILURE";
425
+
426
+ constructor(code: "INVALID_ARGUMENT" | "NOT_FOUND" | "UPSTREAM_FAILURE", message: string) {
427
+ super(message);
428
+ this.code = code;
429
+ }
430
+ }
431
+
432
+ export interface RawFile {
433
+ path: string;
434
+ content: string;
435
+ language?: string;
436
+ updated_at?: string;
437
+ generated?: boolean;
438
+ binary?: boolean;
439
+ }
440
+
441
+ export interface IndexUploadArtifact {
442
+ tenant_id: string;
443
+ workspace_id: string;
444
+ index_version: string;
445
+ files: RawFile[];
446
+ manifest?: {
447
+ object_key: string;
448
+ checksum: string;
449
+ };
450
+ }
451
+
452
+ export interface IndexDeltaArtifact {
453
+ tenant_id: string;
454
+ workspace_id: string;
455
+ index_version: string;
456
+ base_index_version?: string;
457
+ upsert_files: RawFile[];
458
+ deleted_paths: string[];
459
+ }
460
+
461
+ interface IndexedChunk {
462
+ path: string;
463
+ start_line: number;
464
+ end_line: number;
465
+ snippet: string;
466
+ language?: string;
467
+ generated?: boolean;
468
+ updated_at: string;
469
+ hash: string;
470
+ embedding?: number[];
471
+ }
472
+
473
+ interface ReadyIndex {
474
+ index_id: string;
475
+ tenant_id: string;
476
+ workspace_id: string;
477
+ index_version: string;
478
+ status: "indexing" | "ready" | "failed";
479
+ created_at: string;
480
+ updated_at: string;
481
+ }
482
+
483
+ interface PersistedFile {
484
+ file_id: string;
485
+ repo_path: string;
486
+ content_hash: string;
487
+ language?: string;
488
+ }
489
+
490
+ export interface IndexingWarning {
491
+ path: string;
492
+ reason: string;
493
+ category: "secret_exclusion" | "filter_exclusion";
494
+ }
495
+
496
+ export interface IndexingReport {
497
+ workspace_id: string;
498
+ index_version: string;
499
+ status: "ready";
500
+ counts: {
501
+ added: number;
502
+ modified: number;
503
+ deleted: number;
504
+ unchanged: number;
505
+ skipped: number;
506
+ };
507
+ skipped_files: Array<{ path: string; reason: string }>;
508
+ warnings: IndexingWarning[];
509
+ }
510
+
511
+ export interface RetrievalCoreOptions {
512
+ cacheTtlSeconds?: number;
513
+ embeddingProvider?: EmbeddingProvider;
514
+ embeddingDescriptor?: EmbeddingDescriptor;
515
+ observability?: Observability;
516
+ scoringProfile?: BuiltinRetrievalScoringProfileId;
517
+ scoringProfileId?: string;
518
+ scoringConfig?: RetrievalScoringConfigInput;
519
+ enhancerConfig?: RetrievalEnhancerConfigInput;
520
+ chunkingConfig?: RetrievalChunkingConfigInput;
521
+ enhancerDecisionTraceEnabled?: boolean;
522
+ }
523
+
524
+ export interface EmbeddingDescriptor {
525
+ provider: string;
526
+ model?: string;
527
+ dimensions: number;
528
+ version?: string;
529
+ }
530
+
531
+ export type EmbeddingPurpose = "index" | "query";
532
+
533
+ export interface EmbeddingProvider {
534
+ embed(input: { texts: string[]; purpose: EmbeddingPurpose }): Promise<number[][]>;
535
+ describe?(): EmbeddingDescriptor;
536
+ }
537
+
538
+ export interface DeterministicEmbeddingProviderOptions {
539
+ dimensions?: number;
540
+ model?: string;
541
+ version?: string;
542
+ }
543
+
544
+ export interface OpenAICompatibleEmbeddingProviderOptions {
545
+ base_url: string;
546
+ api_key: string;
547
+ model?: string;
548
+ dimensions?: number;
549
+ timeout_ms?: number;
550
+ batch_size?: number;
551
+ max_retries?: number;
552
+ observability?: Observability;
553
+ }
554
+
555
+ class EmbeddingProviderRequestError extends Error {
556
+ constructor(
557
+ readonly reason: string,
558
+ readonly retryable: boolean,
559
+ message: string
560
+ ) {
561
+ super(message);
562
+ }
563
+ }
564
+
565
+ const SECRET_PATTERNS: Array<{ label: string; pattern: RegExp }> = [
566
+ { label: "aws_access_key", pattern: /AKIA[0-9A-Z]{16}/g },
567
+ { label: "private_key", pattern: /-----BEGIN (?:RSA|EC|OPENSSH) PRIVATE KEY-----/g },
568
+ { label: "generic_secret_key", pattern: /(?:api|secret|access)[_-]?key\s*[:=]\s*["']?[A-Za-z0-9_\-]{16,}["']?/gi },
569
+ { label: "bearer_token", pattern: /Bearer\s+[A-Za-z0-9._\-]{10,}/gi }
570
+ ];
571
+
572
+ function normalizePath(path: string): string {
573
+ return path.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\.\//, "").trim();
574
+ }
575
+
576
+ function normalizeQuery(query: string): string {
577
+ return query.normalize("NFKC").trim();
578
+ }
579
+
580
+ function sha256(value: string): string {
581
+ return createHash("sha256").update(value).digest("hex");
582
+ }
583
+
584
+ function pluralizeToken(token: string): string | undefined {
585
+ if (!/^[a-z][a-z0-9]{2,}$/.test(token)) {
586
+ return undefined;
587
+ }
588
+ if (token.endsWith("s")) {
589
+ return undefined;
590
+ }
591
+ if (token.endsWith("y") && token.length > 3) {
592
+ return `${token.slice(0, -1)}ies`;
593
+ }
594
+ return `${token}s`;
595
+ }
596
+
597
+ function singularizeToken(token: string): string | undefined {
598
+ if (!/^[a-z][a-z0-9]{2,}$/.test(token)) {
599
+ return undefined;
600
+ }
601
+ if (token.endsWith("ies") && token.length > 4) {
602
+ return `${token.slice(0, -3)}y`;
603
+ }
604
+ if (token.endsWith("es") && token.length > 4) {
605
+ return token.slice(0, -2);
606
+ }
607
+ if (token.endsWith("s") && !token.endsWith("ss") && token.length > 3) {
608
+ return token.slice(0, -1);
609
+ }
610
+ return undefined;
611
+ }
612
+
613
+ function tokenize(text: string): string[] {
614
+ const coarseTokens = text
615
+ .split(/[^a-z0-9_./-]+/)
616
+ .map((token) => token.trim())
617
+ .filter(Boolean);
618
+
619
+ const expandedTokens = new Set<string>();
620
+ const addToken = (value: string): void => {
621
+ const normalized = value.trim().toLowerCase();
622
+ if (!normalized) {
623
+ return;
624
+ }
625
+ expandedTokens.add(normalized);
626
+ const singular = singularizeToken(normalized);
627
+ if (singular) {
628
+ expandedTokens.add(singular);
629
+ }
630
+ const plural = pluralizeToken(normalized);
631
+ if (plural) {
632
+ expandedTokens.add(plural);
633
+ }
634
+ };
635
+
636
+ for (const token of coarseTokens) {
637
+ addToken(token);
638
+ for (const part of token.split(/[./_-]+/).filter(Boolean)) {
639
+ addToken(part);
640
+ const camelSplit = part
641
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
642
+ .split(/\s+/)
643
+ .map((segment) => segment.trim().toLowerCase())
644
+ .filter(Boolean);
645
+ for (const segment of camelSplit) {
646
+ addToken(segment);
647
+ }
648
+ }
649
+ }
650
+
651
+ return [...expandedTokens];
652
+ }
653
+
654
+ function lexicalScore(query: string, haystack: string): number {
655
+ const q = new Set(tokenize(query));
656
+ if (q.size === 0) {
657
+ return 0;
658
+ }
659
+ const h = new Set(tokenize(haystack));
660
+ let overlap = 0;
661
+ for (const token of q) {
662
+ if (h.has(token)) {
663
+ overlap += 1;
664
+ }
665
+ }
666
+ return overlap / q.size;
667
+ }
668
+
669
+ function cosineSimilarity(a: number[], b: number[]): number {
670
+ if (a.length === 0 || b.length === 0) {
671
+ return 0;
672
+ }
673
+
674
+ const max = Math.min(a.length, b.length);
675
+ let dot = 0;
676
+ let normA = 0;
677
+ let normB = 0;
678
+ for (let i = 0; i < max; i += 1) {
679
+ dot += (a[i] ?? 0) * (b[i] ?? 0);
680
+ normA += (a[i] ?? 0) * (a[i] ?? 0);
681
+ normB += (b[i] ?? 0) * (b[i] ?? 0);
682
+ }
683
+
684
+ if (normA === 0 || normB === 0) {
685
+ return 0;
686
+ }
687
+
688
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
689
+ }
690
+
691
+ function looksLowInformation(snippet: string): boolean {
692
+ const trimmed = snippet.trim();
693
+ if (trimmed.length < 12) {
694
+ return true;
695
+ }
696
+ const noWhitespace = trimmed.replace(/\s+/g, "");
697
+ return /^(.)\1+$/.test(noWhitespace);
698
+ }
699
+
700
+ function chooseReason(input: {
701
+ lexical: number;
702
+ path_match: boolean;
703
+ recency_boosted: boolean;
704
+ }): RetrievalReason {
705
+ if (input.path_match) {
706
+ return "exact symbol match";
707
+ }
708
+ if (input.recency_boosted) {
709
+ return "recently modified relevant module";
710
+ }
711
+ if (input.lexical > 0.3) {
712
+ return "path and token overlap";
713
+ }
714
+ return "semantic match";
715
+ }
716
+
717
+ const DOC_INTENT_TOKENS = new Set([
718
+ "adr",
719
+ "architecture",
720
+ "design",
721
+ "doc",
722
+ "docs",
723
+ "documentation",
724
+ "milestone",
725
+ "readme",
726
+ "spec",
727
+ "specification"
728
+ ]);
729
+
730
+ const CODE_INTENT_TOKENS = new Set([
731
+ "api",
732
+ "bug",
733
+ "bugfix",
734
+ "cli",
735
+ "code",
736
+ "command",
737
+ "component",
738
+ "error",
739
+ "entrypoint",
740
+ "feature",
741
+ "fix",
742
+ "function",
743
+ "handler",
744
+ "harden",
745
+ "hook",
746
+ "hooks",
747
+ "implement",
748
+ "implementation",
749
+ "middleware",
750
+ "module",
751
+ "optimize",
752
+ "pipeline",
753
+ "react",
754
+ "refactor",
755
+ "root",
756
+ "route",
757
+ "routing",
758
+ "source",
759
+ "struct",
760
+ "ts",
761
+ "type",
762
+ "typescript",
763
+ "tsx",
764
+ "ui"
765
+ ]);
766
+
767
+ const WORKSPACE_MANIFEST_TOKENS = new Set(["cargo", "members", "resolver", "workspace", "rust"]);
768
+ const UI_COMPONENT_TOKENS = new Set(["component", "layout", "react", "tsx", "ui"]);
769
+ const FILE_LOOKUP_TOKENS = new Set(["entrypoint", "file", "locate", "path", "where", "which"]);
770
+ const TEST_INTENT_TOKENS = new Set(["assert", "coverage", "e2e", "integration", "spec", "test", "tests", "unit"]);
771
+ const EXAMPLE_INTENT_TOKENS = new Set(["demo", "example", "examples", "sample", "tutorial"]);
772
+
773
+ const SOURCE_PATH_PREFIXES = ["src/", "app/", "apps/", "crates/", "internal/", "lib/", "package/", "packages/"];
774
+ const LOW_PRIORITY_PATH_PREFIXES = [
775
+ ".next/",
776
+ ".nuxt/",
777
+ ".svelte-kit/",
778
+ "build/",
779
+ "coverage/",
780
+ "dist/",
781
+ "node_modules/",
782
+ "out/",
783
+ "target/"
784
+ ];
785
+ const LOW_PRIORITY_PATH_SUBSTRINGS = ["/docs/_archive/", "/node_modules/", "/target/"];
786
+ const LOW_PRIORITY_ASSET_EXTENSIONS = [".gif", ".ico", ".jpeg", ".jpg", ".pdf", ".png", ".svg", ".webp"];
787
+ const LOW_PRIORITY_LOCK_FILES = [
788
+ "bun.lock",
789
+ "bun.lockb",
790
+ "cargo.lock",
791
+ "composer.lock",
792
+ "gemfile.lock",
793
+ "package-lock.json",
794
+ "pipfile.lock",
795
+ "pnpm-lock.yaml",
796
+ "yarn.lock"
797
+ ];
798
+
799
+ interface NegativePathPreferences {
800
+ avoid_docs: boolean;
801
+ avoid_tests: boolean;
802
+ avoid_examples: boolean;
803
+ avoid_archive: boolean;
804
+ }
805
+
806
+ function hasNegationNearTarget(normalized: string, targetPattern: string): boolean {
807
+ const negationPrefix =
808
+ "(?:avoid|avoiding|exclude|excluding|without|skip|omit|ignore|deprioriti(?:s|z)e|not|no|leave\\s+out|keep\\s+[^\\n]{0,40}?\\s+out\\s+of)";
809
+ return (
810
+ new RegExp(`\\b${negationPrefix}\\b[^\\n,.!?;]{0,48}\\b(?:${targetPattern})\\b`, "i").test(normalized) ||
811
+ new RegExp(`\\b(?:${targetPattern})\\b[^\\n,.!?;]{0,24}\\b(?:not|avoid|exclude|without|skip|omit)\\b`, "i").test(
812
+ normalized
813
+ )
814
+ );
815
+ }
816
+
817
+ function detectNegativePathPreferences(query: string): NegativePathPreferences {
818
+ const normalized = query.toLowerCase();
819
+ return {
820
+ avoid_docs: hasNegationNearTarget(normalized, "docs?|documentation|readme|specs?|guides?|markdown"),
821
+ avoid_tests: hasNegationNearTarget(normalized, "tests?|specs?|fixtures?|__tests__"),
822
+ avoid_examples: hasNegationNearTarget(normalized, "examples?|samples?|demos?|tutorials?"),
823
+ avoid_archive: hasNegationNearTarget(normalized, "archive|archived|_archive")
824
+ };
825
+ }
826
+
827
+ function hasDocIntent(tokens: string[]): boolean {
828
+ return tokens.some((token) => DOC_INTENT_TOKENS.has(token));
829
+ }
830
+
831
+ function hasCodeIntent(tokens: string[]): boolean {
832
+ return tokens.some((token) => CODE_INTENT_TOKENS.has(token));
833
+ }
834
+
835
+ function hasWorkspaceManifestIntent(tokens: string[]): boolean {
836
+ return tokens.some((token) => WORKSPACE_MANIFEST_TOKENS.has(token));
837
+ }
838
+
839
+ function hasUiComponentIntent(tokens: string[]): boolean {
840
+ return tokens.some((token) => UI_COMPONENT_TOKENS.has(token));
841
+ }
842
+
843
+ function hasFileLookupIntent(tokens: string[]): boolean {
844
+ return tokens.some((token) => FILE_LOOKUP_TOKENS.has(token));
845
+ }
846
+
847
+ function hasTestIntent(tokens: string[]): boolean {
848
+ return tokens.some((token) => TEST_INTENT_TOKENS.has(token));
849
+ }
850
+
851
+ function hasExampleIntent(tokens: string[]): boolean {
852
+ return tokens.some((token) => EXAMPLE_INTENT_TOKENS.has(token));
853
+ }
854
+
855
+ function pathQualityBias(path: string, queryTokens: string[], config: RetrievalScoringConfig, queryText?: string): number {
856
+ const normalizedPath = path.toLowerCase();
857
+ const docIntent = hasDocIntent(queryTokens);
858
+ const codeIntent = hasCodeIntent(queryTokens);
859
+ const docsPreferred = docIntent && !codeIntent;
860
+ const negativePreferences = detectNegativePathPreferences(queryText ?? queryTokens.join(" "));
861
+ const workspaceManifestIntent = hasWorkspaceManifestIntent(queryTokens);
862
+ const uiComponentIntent = hasUiComponentIntent(queryTokens);
863
+ const testIntent = hasTestIntent(queryTokens);
864
+ const exampleIntent = hasExampleIntent(queryTokens);
865
+ let bias = 0;
866
+ const pathBias = config.path_bias;
867
+
868
+ const isSourcePath = SOURCE_PATH_PREFIXES.some(
869
+ (prefix) => normalizedPath.startsWith(prefix) || normalizedPath.includes(`/${prefix}`)
870
+ );
871
+ const isDocsPath = normalizedPath.startsWith("docs/") || normalizedPath.includes("/docs/");
872
+ const isMarkdown = normalizedPath.endsWith(".md");
873
+ const isTestPath =
874
+ normalizedPath.startsWith("test/") ||
875
+ normalizedPath.startsWith("tests/") ||
876
+ normalizedPath.startsWith("__tests__/") ||
877
+ normalizedPath.includes("/test/") ||
878
+ normalizedPath.includes("/tests/") ||
879
+ normalizedPath.includes("/__tests__/") ||
880
+ normalizedPath.endsWith(".test.js") ||
881
+ normalizedPath.endsWith(".test.ts") ||
882
+ normalizedPath.endsWith(".spec.js") ||
883
+ normalizedPath.endsWith(".spec.ts");
884
+ const isExamplePath =
885
+ normalizedPath.startsWith("example/") ||
886
+ normalizedPath.startsWith("examples/") ||
887
+ normalizedPath.includes("/example/") ||
888
+ normalizedPath.includes("/examples/");
889
+ const isLibPath = normalizedPath === "lib" || normalizedPath.startsWith("lib/") || normalizedPath.includes("/lib/");
890
+ const isChangelogLikeDoc = /(?:^|\/)(?:history|changelog|changes|readme)\.md$/.test(normalizedPath);
891
+
892
+ if (isSourcePath) {
893
+ bias += pathBias.source_path_boost;
894
+ }
895
+ if (codeIntent && isLibPath) {
896
+ bias += pathBias.source_path_boost;
897
+ }
898
+
899
+ if (LOW_PRIORITY_PATH_PREFIXES.some((prefix) => normalizedPath.startsWith(prefix))) {
900
+ bias -= pathBias.low_priority_prefix_penalty;
901
+ }
902
+ if (LOW_PRIORITY_PATH_SUBSTRINGS.some((pattern) => normalizedPath.includes(pattern))) {
903
+ bias -= pathBias.low_priority_substring_penalty;
904
+ }
905
+ if (LOW_PRIORITY_ASSET_EXTENSIONS.some((ext) => normalizedPath.endsWith(ext))) {
906
+ bias -= pathBias.low_priority_asset_penalty;
907
+ }
908
+ if (LOW_PRIORITY_LOCK_FILES.some((name) => normalizedPath.endsWith(name))) {
909
+ bias -= pathBias.lockfile_penalty;
910
+ }
911
+
912
+ if (docsPreferred && isDocsPath) {
913
+ bias += pathBias.doc_intent_docs_boost;
914
+ }
915
+ if (docsPreferred && isMarkdown) {
916
+ bias += pathBias.doc_intent_markdown_boost;
917
+ }
918
+
919
+ if (!docsPreferred && isDocsPath) {
920
+ bias -= pathBias.docs_without_doc_intent_penalty;
921
+ }
922
+ if (!docsPreferred && isMarkdown) {
923
+ bias -= pathBias.non_doc_markdown_penalty;
924
+ }
925
+ if (codeIntent && isDocsPath) {
926
+ bias -= pathBias.code_intent_docs_penalty;
927
+ }
928
+ if (codeIntent && isMarkdown) {
929
+ bias -= pathBias.non_doc_markdown_penalty;
930
+ }
931
+ if (codeIntent && isChangelogLikeDoc) {
932
+ bias -= pathBias.docs_without_doc_intent_penalty;
933
+ }
934
+ if (docsPreferred && isSourcePath) {
935
+ bias -= pathBias.doc_intent_source_penalty;
936
+ }
937
+
938
+ if (workspaceManifestIntent && normalizedPath === "cargo.toml") {
939
+ bias += pathBias.workspace_manifest_root_boost;
940
+ }
941
+ if (workspaceManifestIntent && normalizedPath.endsWith("/cargo.toml")) {
942
+ bias += pathBias.workspace_manifest_nested_boost;
943
+ }
944
+
945
+ if (uiComponentIntent && (normalizedPath.endsWith(".tsx") || normalizedPath.endsWith(".jsx"))) {
946
+ bias += pathBias.ui_component_tsx_boost;
947
+ }
948
+ if (uiComponentIntent && normalizedPath.endsWith(".css")) {
949
+ bias -= pathBias.ui_component_css_penalty;
950
+ }
951
+
952
+ if (normalizedPath.startsWith("docs/_archive/")) {
953
+ bias -= pathBias.docs_archive_penalty;
954
+ }
955
+
956
+ if (normalizedPath.includes("/public/") && !queryTokens.some((token) => token.includes("public"))) {
957
+ bias -= pathBias.public_path_penalty;
958
+ }
959
+
960
+ if (!testIntent && isTestPath) {
961
+ bias -= pathBias.test_path_penalty;
962
+ }
963
+ if (testIntent && isTestPath) {
964
+ bias += pathBias.test_intent_test_boost;
965
+ }
966
+
967
+ if (!exampleIntent && isExamplePath) {
968
+ bias -= pathBias.example_path_penalty;
969
+ }
970
+ if (exampleIntent && isExamplePath) {
971
+ bias += pathBias.example_intent_example_boost;
972
+ }
973
+
974
+ if (codeIntent && normalizedPath.endsWith(".d.ts")) {
975
+ bias -= pathBias.declaration_file_penalty;
976
+ }
977
+
978
+ if (negativePreferences.avoid_docs && (isDocsPath || isMarkdown)) {
979
+ bias -= pathBias.negation_avoid_docs_penalty;
980
+ }
981
+ if (negativePreferences.avoid_tests && isTestPath) {
982
+ bias -= pathBias.negation_avoid_tests_penalty;
983
+ }
984
+ if (negativePreferences.avoid_examples && isExamplePath) {
985
+ bias -= pathBias.negation_avoid_examples_penalty;
986
+ }
987
+ if (negativePreferences.avoid_archive && normalizedPath.includes("archive")) {
988
+ bias -= pathBias.negation_avoid_archive_penalty;
989
+ }
990
+
991
+ const filename = normalizedPath.split("/").pop() ?? normalizedPath;
992
+ const filenameStem = filename.replace(/\.[a-z0-9]+$/, "");
993
+ for (const token of queryTokens) {
994
+ if (token === filename || token === filenameStem) {
995
+ bias += pathBias.filename_token_match_boost;
996
+ }
997
+ }
998
+
999
+ return Math.max(pathBias.min_total_bias, Math.min(pathBias.max_total_bias, bias));
1000
+ }
1001
+
1002
+ interface ChunkBuildReport {
1003
+ chunks: IndexedChunk[];
1004
+ strategy: ChunkingStrategy;
1005
+ fallback_reason?: ChunkingFallbackReason;
1006
+ parse_latency_ms?: number;
1007
+ language_aware_attempt_latency_ms?: number;
1008
+ fallback_path_latency_ms?: number;
1009
+ language?: string;
1010
+ }
1011
+
1012
+ function buildChunks(file: RawFile, chunkingConfig: RetrievalChunkingConfig): ChunkBuildReport {
1013
+ const normalizedPath = normalizePath(file.path);
1014
+ const chunkingResult = buildChunksForFile({
1015
+ file: {
1016
+ path: normalizedPath,
1017
+ content: file.content,
1018
+ language: file.language
1019
+ },
1020
+ config: {
1021
+ strategy: chunkingConfig.strategy,
1022
+ fallback_strategy: chunkingConfig.fallback_strategy,
1023
+ target_chunk_tokens: TARGET_CHUNK_TOKENS,
1024
+ chunk_overlap_tokens: CHUNK_OVERLAP_TOKENS,
1025
+ max_chunks_per_file: MAX_CHUNKS_PER_FILE,
1026
+ parse_timeout_ms: chunkingConfig.parse_timeout_ms,
1027
+ enabled_languages: chunkingConfig.enabled_languages
1028
+ },
1029
+ tokenize
1030
+ });
1031
+
1032
+ return {
1033
+ chunks: chunkingResult.chunks.map((chunk) => ({
1034
+ path: normalizedPath,
1035
+ start_line: chunk.start_line,
1036
+ end_line: chunk.end_line,
1037
+ snippet: chunk.snippet,
1038
+ language: file.language,
1039
+ generated: file.generated,
1040
+ updated_at: file.updated_at ?? new Date().toISOString(),
1041
+ hash: sha256(`${normalizedPath}:${chunk.start_line}:${chunk.end_line}:${chunk.snippet}`)
1042
+ })),
1043
+ strategy: chunkingResult.strategy,
1044
+ fallback_reason: chunkingResult.fallback_reason,
1045
+ parse_latency_ms: chunkingResult.parse_latency_ms,
1046
+ language_aware_attempt_latency_ms: chunkingResult.language_aware_attempt_latency_ms,
1047
+ fallback_path_latency_ms: chunkingResult.fallback_path_latency_ms,
1048
+ language: chunkingResult.language
1049
+ };
1050
+ }
1051
+
1052
+ function pseudoEmbedding(input: string, dimensions = 24): number[] {
1053
+ const safeDimensions = Math.max(1, dimensions);
1054
+ let source = sha256(input);
1055
+ let salt = 1;
1056
+ while (source.length < safeDimensions * 2) {
1057
+ source += sha256(`${input}:${salt}`);
1058
+ salt += 1;
1059
+ }
1060
+ const out: number[] = [];
1061
+ for (let i = 0; i < safeDimensions; i += 1) {
1062
+ const offset = i * 2;
1063
+ const segment = source.slice(offset, offset + 2);
1064
+ out.push(parseInt(segment, 16) / 255);
1065
+ }
1066
+ return out;
1067
+ }
1068
+
1069
+ function isFiniteNumberArray(value: unknown): value is number[] {
1070
+ return Array.isArray(value) && value.every((cell) => typeof cell === "number" && Number.isFinite(cell));
1071
+ }
1072
+
1073
+ function sleep(ms: number): Promise<void> {
1074
+ return new Promise((resolve) => {
1075
+ setTimeout(resolve, ms);
1076
+ });
1077
+ }
1078
+
1079
+ export class DeterministicEmbeddingProvider implements EmbeddingProvider {
1080
+ private readonly dimensions: number;
1081
+ private readonly model: string;
1082
+ private readonly version: string;
1083
+
1084
+ constructor(options: DeterministicEmbeddingProviderOptions = {}) {
1085
+ this.dimensions = options.dimensions ?? 24;
1086
+ this.model = options.model ?? "pseudo-sha256";
1087
+ this.version = options.version ?? "v1";
1088
+ }
1089
+
1090
+ async embed(input: { texts: string[]; purpose: EmbeddingPurpose }): Promise<number[][]> {
1091
+ return input.texts.map((text) => pseudoEmbedding(text, this.dimensions));
1092
+ }
1093
+
1094
+ describe(): EmbeddingDescriptor {
1095
+ return {
1096
+ provider: "deterministic",
1097
+ model: this.model,
1098
+ dimensions: this.dimensions,
1099
+ version: this.version
1100
+ };
1101
+ }
1102
+ }
1103
+
1104
+ export class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
1105
+ private readonly baseUrl: string;
1106
+ private readonly endpoint: string;
1107
+ private readonly apiKey: string;
1108
+ private readonly model: string;
1109
+ private readonly dimensions: number;
1110
+ private readonly timeoutMs: number;
1111
+ private readonly batchSize: number;
1112
+ private readonly maxRetries: number;
1113
+ private readonly observability: Observability;
1114
+
1115
+ constructor(options: OpenAICompatibleEmbeddingProviderOptions) {
1116
+ const baseUrl = options.base_url.trim().replace(/\/+$/, "");
1117
+ if (baseUrl.length === 0) {
1118
+ throw new Error("invalid openai-compatible embedding config: base_url must be non-empty");
1119
+ }
1120
+ const apiKey = options.api_key.trim();
1121
+ if (apiKey.length === 0) {
1122
+ throw new Error("invalid openai-compatible embedding config: api_key must be non-empty");
1123
+ }
1124
+
1125
+ this.baseUrl = baseUrl;
1126
+ this.endpoint = `${this.baseUrl}/embeddings`;
1127
+ this.apiKey = apiKey;
1128
+ this.model = options.model?.trim() || DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_MODEL;
1129
+ this.dimensions = options.dimensions ?? DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_DIMENSIONS;
1130
+ this.timeoutMs = options.timeout_ms ?? DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_TIMEOUT_MS;
1131
+ this.batchSize = options.batch_size ?? DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_BATCH_SIZE;
1132
+ this.maxRetries = options.max_retries ?? DEFAULT_OPENAI_COMPATIBLE_EMBEDDING_MAX_RETRIES;
1133
+ this.observability = options.observability ?? getObservability("retrieval-core");
1134
+
1135
+ if (!Number.isInteger(this.dimensions) || this.dimensions <= 0) {
1136
+ throw new Error("invalid openai-compatible embedding config: dimensions must be a positive integer");
1137
+ }
1138
+ if (!Number.isInteger(this.timeoutMs) || this.timeoutMs <= 0) {
1139
+ throw new Error("invalid openai-compatible embedding config: timeout_ms must be a positive integer");
1140
+ }
1141
+ if (!Number.isInteger(this.batchSize) || this.batchSize <= 0) {
1142
+ throw new Error("invalid openai-compatible embedding config: batch_size must be a positive integer");
1143
+ }
1144
+ if (!Number.isInteger(this.maxRetries) || this.maxRetries < 0) {
1145
+ throw new Error("invalid openai-compatible embedding config: max_retries must be a non-negative integer");
1146
+ }
1147
+ }
1148
+
1149
+ describe(): EmbeddingDescriptor {
1150
+ return {
1151
+ provider: "openai_compatible",
1152
+ model: this.model,
1153
+ dimensions: this.dimensions
1154
+ };
1155
+ }
1156
+
1157
+ async embed(input: { texts: string[]; purpose: EmbeddingPurpose }): Promise<number[][]> {
1158
+ if (input.texts.length === 0) {
1159
+ return [];
1160
+ }
1161
+
1162
+ const output: number[][] = [];
1163
+ for (let offset = 0; offset < input.texts.length; offset += this.batchSize) {
1164
+ const batch = input.texts.slice(offset, offset + this.batchSize);
1165
+ const vectors = await this.embedBatchWithRetries(batch, input.purpose);
1166
+ output.push(...vectors);
1167
+ }
1168
+ return output;
1169
+ }
1170
+
1171
+ private async embedBatchWithRetries(texts: string[], purpose: EmbeddingPurpose): Promise<number[][]> {
1172
+ const labels = {
1173
+ provider: "openai_compatible",
1174
+ model: this.model,
1175
+ purpose
1176
+ } as const;
1177
+
1178
+ for (let attempt = 0; attempt <= this.maxRetries; attempt += 1) {
1179
+ const startedAt = Date.now();
1180
+ this.observability.metrics.increment("retrieval_embedding_provider_requests_total", 1, labels);
1181
+ try {
1182
+ return await this.embedBatchOnce(texts);
1183
+ } catch (error) {
1184
+ const failure = this.toProviderFailure(error);
1185
+ this.observability.metrics.increment("retrieval_embedding_provider_failures_total", 1, {
1186
+ ...labels,
1187
+ reason: failure.reason
1188
+ });
1189
+
1190
+ const shouldRetry = failure.retryable && attempt < this.maxRetries;
1191
+ this.observability.logger.warn("embedding provider request failed", {
1192
+ provider: "openai_compatible",
1193
+ model: this.model,
1194
+ purpose,
1195
+ reason: failure.reason,
1196
+ retryable: failure.retryable,
1197
+ retrying: shouldRetry,
1198
+ attempt: attempt + 1,
1199
+ max_attempts: this.maxRetries + 1
1200
+ });
1201
+
1202
+ if (shouldRetry) {
1203
+ await sleep(this.retryDelayMs(attempt));
1204
+ continue;
1205
+ }
1206
+
1207
+ throw new RetrievalError(
1208
+ "UPSTREAM_FAILURE",
1209
+ `embedding provider request failed (${failure.reason}); ${failure.message}`
1210
+ );
1211
+ } finally {
1212
+ this.observability.metrics.observe("retrieval_embedding_provider_latency_ms", Date.now() - startedAt, labels);
1213
+ }
1214
+ }
1215
+
1216
+ throw new RetrievalError("UPSTREAM_FAILURE", "embedding provider retries exhausted");
1217
+ }
1218
+
1219
+ private async embedBatchOnce(texts: string[]): Promise<number[][]> {
1220
+ const controller = new AbortController();
1221
+ const timeoutId = setTimeout(() => {
1222
+ controller.abort();
1223
+ }, this.timeoutMs);
1224
+
1225
+ let response: Response;
1226
+ try {
1227
+ response = await fetch(this.endpoint, {
1228
+ method: "POST",
1229
+ headers: {
1230
+ authorization: `Bearer ${this.apiKey}`,
1231
+ "content-type": "application/json"
1232
+ },
1233
+ body: JSON.stringify({
1234
+ model: this.model,
1235
+ input: texts,
1236
+ encoding_format: "float"
1237
+ }),
1238
+ signal: controller.signal
1239
+ });
1240
+ } catch (error) {
1241
+ if (error && typeof error === "object" && "name" in error && (error as { name?: string }).name === "AbortError") {
1242
+ throw new EmbeddingProviderRequestError("timeout", true, `request timed out after ${this.timeoutMs}ms`);
1243
+ }
1244
+ throw new EmbeddingProviderRequestError(
1245
+ "network_error",
1246
+ true,
1247
+ error instanceof Error ? error.message : String(error)
1248
+ );
1249
+ } finally {
1250
+ clearTimeout(timeoutId);
1251
+ }
1252
+
1253
+ if (!response.ok) {
1254
+ const details = await safeResponseText(response);
1255
+ if (response.status === 429) {
1256
+ throw new EmbeddingProviderRequestError("rate_limited", true, `HTTP 429 ${details}`.trim());
1257
+ }
1258
+ if (response.status >= 500) {
1259
+ throw new EmbeddingProviderRequestError("http_5xx", true, `HTTP ${response.status} ${details}`.trim());
1260
+ }
1261
+ if (response.status === 401 || response.status === 403) {
1262
+ throw new EmbeddingProviderRequestError("auth_error", false, `HTTP ${response.status} ${details}`.trim());
1263
+ }
1264
+ if (response.status === 404) {
1265
+ throw new EmbeddingProviderRequestError("endpoint_not_found", false, `HTTP 404 ${details}`.trim());
1266
+ }
1267
+ throw new EmbeddingProviderRequestError("http_4xx", false, `HTTP ${response.status} ${details}`.trim());
1268
+ }
1269
+
1270
+ let payload: unknown;
1271
+ try {
1272
+ payload = await response.json();
1273
+ } catch {
1274
+ throw new EmbeddingProviderRequestError("invalid_json", false, "provider returned non-JSON response");
1275
+ }
1276
+
1277
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
1278
+ throw new EmbeddingProviderRequestError("invalid_response", false, "provider response missing data field");
1279
+ }
1280
+
1281
+ const rawData = (payload as { data: unknown }).data;
1282
+ if (!Array.isArray(rawData)) {
1283
+ throw new EmbeddingProviderRequestError("invalid_response", false, "provider response data must be an array");
1284
+ }
1285
+ if (rawData.length !== texts.length) {
1286
+ throw new EmbeddingProviderRequestError(
1287
+ "count_mismatch",
1288
+ false,
1289
+ `provider returned ${rawData.length} embeddings for ${texts.length} inputs`
1290
+ );
1291
+ }
1292
+
1293
+ const hasIndexField = rawData.every(
1294
+ (entry) => entry && typeof entry === "object" && Number.isInteger((entry as { index?: unknown }).index)
1295
+ );
1296
+
1297
+ const ordered = hasIndexField
1298
+ ? [...rawData].sort((a, b) => {
1299
+ const left = (a as { index: number }).index;
1300
+ const right = (b as { index: number }).index;
1301
+ return left - right;
1302
+ })
1303
+ : rawData;
1304
+
1305
+ const vectors: number[][] = [];
1306
+ for (let i = 0; i < ordered.length; i += 1) {
1307
+ const row = ordered[i];
1308
+ if (!row || typeof row !== "object") {
1309
+ throw new EmbeddingProviderRequestError("invalid_response", false, "embedding row must be an object");
1310
+ }
1311
+ if (hasIndexField && (row as { index: number }).index !== i) {
1312
+ throw new EmbeddingProviderRequestError("invalid_response", false, "embedding row indexes are not contiguous");
1313
+ }
1314
+ const embedding = (row as { embedding?: unknown }).embedding;
1315
+ if (!isFiniteNumberArray(embedding)) {
1316
+ throw new EmbeddingProviderRequestError("invalid_vector", false, "embedding must be a numeric array");
1317
+ }
1318
+ if (embedding.length !== this.dimensions) {
1319
+ throw new EmbeddingProviderRequestError(
1320
+ "dimension_mismatch",
1321
+ false,
1322
+ `expected ${this.dimensions} dimensions, received ${embedding.length}`
1323
+ );
1324
+ }
1325
+ vectors.push([...embedding]);
1326
+ }
1327
+
1328
+ return vectors;
1329
+ }
1330
+
1331
+ private retryDelayMs(attempt: number): number {
1332
+ const base = 100 * (attempt + 1);
1333
+ const jitter = Math.floor(Math.random() * 75);
1334
+ return base + jitter;
1335
+ }
1336
+
1337
+ private toProviderFailure(error: unknown): EmbeddingProviderRequestError {
1338
+ if (error instanceof EmbeddingProviderRequestError) {
1339
+ return error;
1340
+ }
1341
+ if (error instanceof RetrievalError) {
1342
+ return new EmbeddingProviderRequestError("upstream_failure", false, error.message);
1343
+ }
1344
+ if (error instanceof Error) {
1345
+ return new EmbeddingProviderRequestError("unknown_error", false, error.message);
1346
+ }
1347
+ return new EmbeddingProviderRequestError("unknown_error", false, String(error));
1348
+ }
1349
+ }
1350
+
1351
+ async function safeResponseText(response: Response): Promise<string> {
1352
+ try {
1353
+ const text = await response.text();
1354
+ return text.slice(0, 512);
1355
+ } catch {
1356
+ return "";
1357
+ }
1358
+ }
1359
+
1360
+ function resolveEmbeddingDescriptor(provider: EmbeddingProvider): EmbeddingDescriptor {
1361
+ const described = provider.describe?.();
1362
+ if (!described) {
1363
+ return {
1364
+ provider: "custom",
1365
+ dimensions: 24
1366
+ };
1367
+ }
1368
+ return {
1369
+ provider: described.provider,
1370
+ ...(described.model ? { model: described.model } : {}),
1371
+ dimensions: described.dimensions,
1372
+ ...(described.version ? { version: described.version } : {})
1373
+ };
1374
+ }
1375
+
1376
+ function normalizeEmbeddingDescriptor(descriptor: EmbeddingDescriptor): EmbeddingDescriptor {
1377
+ const provider = descriptor.provider.trim();
1378
+ if (provider.length === 0) {
1379
+ throw new Error("invalid embedding descriptor: provider must be non-empty");
1380
+ }
1381
+ if (!Number.isInteger(descriptor.dimensions) || descriptor.dimensions <= 0) {
1382
+ throw new Error("invalid embedding descriptor: dimensions must be a positive integer");
1383
+ }
1384
+ return {
1385
+ provider: provider.toLowerCase(),
1386
+ ...(descriptor.model ? { model: descriptor.model.trim() } : {}),
1387
+ dimensions: descriptor.dimensions,
1388
+ ...(descriptor.version ? { version: descriptor.version.trim() } : {})
1389
+ };
1390
+ }
1391
+
1392
+ function classifyIntent(prompt: string): "bugfix" | "feature" | "refactor" | "docs" | "tests" | "unknown" {
1393
+ const p = prompt.toLowerCase();
1394
+ if (/fix|bug|error|crash|regression/.test(p)) {
1395
+ return "bugfix";
1396
+ }
1397
+ if (/add|implement|new|support/.test(p)) {
1398
+ return "feature";
1399
+ }
1400
+ if (/refactor|cleanup|restructure/.test(p)) {
1401
+ return "refactor";
1402
+ }
1403
+ if (/readme|docs|documentation/.test(p)) {
1404
+ return "docs";
1405
+ }
1406
+ if (/test|coverage|assert/.test(p)) {
1407
+ return "tests";
1408
+ }
1409
+ return "unknown";
1410
+ }
1411
+
1412
+ function detectDominantLanguage(prompt: string, history: EnhancePromptInput["conversation_history"]): "en" | "es" | "zh" {
1413
+ const latestUser = [...history].reverse().find((m) => m.role === "user")?.content ?? prompt;
1414
+ const sample = `${prompt}\n${latestUser}`.toLowerCase();
1415
+ if (/[\u3400-\u9fff]/.test(sample)) {
1416
+ return "zh";
1417
+ }
1418
+ if (/[áéíóúñ¿¡]/.test(sample) || /\b(implementar|arreglar|prueba|archivo|código)\b/.test(sample)) {
1419
+ return "es";
1420
+ }
1421
+ return "en";
1422
+ }
1423
+
1424
+ interface EnhancerFrameworkHintRule {
1425
+ id: string;
1426
+ token_triggers: string[];
1427
+ phrase_triggers: string[];
1428
+ symbol_hints: string[];
1429
+ path_hints: string[];
1430
+ }
1431
+
1432
+ const ENHANCER_FRAMEWORK_HINT_RULES: EnhancerFrameworkHintRule[] = [
1433
+ {
1434
+ id: "flask",
1435
+ token_triggers: ["flask", "jinja", "template", "templates", "render_template", "render", "templating"],
1436
+ phrase_triggers: ["render template", "render_template", "jinja loader", "before render template"],
1437
+ symbol_hints: [
1438
+ "render_template",
1439
+ "render_template_string",
1440
+ "before_render_template",
1441
+ "DispatchingJinjaLoader",
1442
+ "register_error_handler"
1443
+ ],
1444
+ path_hints: [
1445
+ "src/flask/templating.py",
1446
+ "src/flask/views.py",
1447
+ "src/flask/signals.py",
1448
+ "src/flask/sansio/scaffold.py",
1449
+ "src/flask/sansio/app.py"
1450
+ ]
1451
+ },
1452
+ {
1453
+ id: "fastify",
1454
+ token_triggers: [
1455
+ "fastify",
1456
+ "hook",
1457
+ "hooks",
1458
+ "addhook",
1459
+ "seterrorhandler",
1460
+ "onrequest",
1461
+ "onerror",
1462
+ "plugin"
1463
+ ],
1464
+ phrase_triggers: ["error handler", "seterrorhandler", "addhook", "request lifecycle"],
1465
+ symbol_hints: ["addHook", "setErrorHandler", "onRequest", "onError", "preHandler", "setNotFoundHandler"],
1466
+ path_hints: ["fastify.js", "lib/hooks.js", "lib/error-handler.js", "lib/route.js", "lib/handle-request.js"]
1467
+ },
1468
+ {
1469
+ id: "node_web_runtime",
1470
+ token_triggers: [
1471
+ "express",
1472
+ "koa",
1473
+ "middleware",
1474
+ "router",
1475
+ "route",
1476
+ "handler",
1477
+ "request",
1478
+ "response",
1479
+ "pipeline"
1480
+ ],
1481
+ phrase_triggers: ["request pipeline", "error handler", "middleware chain", "route handler"],
1482
+ symbol_hints: [
1483
+ "middleware",
1484
+ "router",
1485
+ "handleRequest",
1486
+ "setErrorHandler",
1487
+ "onRequest",
1488
+ "response.send",
1489
+ "onerror",
1490
+ "app.onerror"
1491
+ ],
1492
+ path_hints: ["lib/application.js", "lib/request.js", "lib/response.js", "lib/context.js", "lib/router", "src/server.ts"]
1493
+ },
1494
+ {
1495
+ id: "http_client_runtime",
1496
+ token_triggers: ["axios", "request", "adapter", "dispatch", "url", "fetch", "http", "client"],
1497
+ phrase_triggers: ["http adapter", "request dispatch", "url builder", "request pipeline"],
1498
+ symbol_hints: ["dispatchRequest", "mergeConfig", "buildURL", "combineURLs", "adapter"],
1499
+ path_hints: ["lib/core", "lib/helpers", "lib/adapters", "src/client", "src/http"]
1500
+ }
1501
+ ];
1502
+
1503
+ const SYMBOL_STOPWORDS = new Set([
1504
+ "about",
1505
+ "after",
1506
+ "before",
1507
+ "behavior",
1508
+ "change",
1509
+ "compatibility",
1510
+ "enhance",
1511
+ "flow",
1512
+ "keep",
1513
+ "logic",
1514
+ "module",
1515
+ "preserve",
1516
+ "refactor",
1517
+ "request",
1518
+ "response",
1519
+ "should",
1520
+ "stable",
1521
+ "support",
1522
+ "while"
1523
+ ]);
1524
+
1525
+ const ENHANCER_IMPLEMENTATION_INTENT_HINTS = [
1526
+ "implementation",
1527
+ "source",
1528
+ "runtime",
1529
+ "module",
1530
+ "handler",
1531
+ "function",
1532
+ "class",
1533
+ "codepath",
1534
+ "src",
1535
+ "lib"
1536
+ ];
1537
+
1538
+ const ENHANCER_DOCS_INTENT_TOKENS = new Set([
1539
+ "adr",
1540
+ "architecture",
1541
+ "doc",
1542
+ "docs",
1543
+ "documentation",
1544
+ "readme",
1545
+ "spec",
1546
+ "specification"
1547
+ ]);
1548
+
1549
+ const ENHANCER_TEST_INTENT_TOKENS = new Set(["assert", "coverage", "integration", "spec", "test", "tests", "unit"]);
1550
+ const ENHANCER_CONCEPTUAL_INTENT_TOKENS = new Set([
1551
+ "architecture",
1552
+ "concept",
1553
+ "design",
1554
+ "explain",
1555
+ "guidance",
1556
+ "how",
1557
+ "overview",
1558
+ "reason",
1559
+ "tradeoff",
1560
+ "why"
1561
+ ]);
1562
+
1563
+ type EnhancerQueryIntent = "symbol-heavy" | "impl-focused" | "conceptual";
1564
+
1565
+ interface EnhancerIntentPolicy {
1566
+ max_expansion_hints: number;
1567
+ max_candidates_pre_rerank: number;
1568
+ max_candidates_per_directory_pre_rerank: number;
1569
+ strict_impl_only_filtering: boolean;
1570
+ }
1571
+
1572
+ function classifyEnhancerQueryIntent(prompt: string, history: EnhancePromptInput["conversation_history"]): EnhancerQueryIntent {
1573
+ const recentMessages = [...history]
1574
+ .reverse()
1575
+ .filter((message) => message.role !== "system")
1576
+ .map((message) => message.content.trim())
1577
+ .filter((content) => content.length > 0 && content !== prompt.trim())
1578
+ .slice(0, 4)
1579
+ .reverse();
1580
+ const combined = normalizeQuery([prompt, ...recentMessages].join("\n").trim());
1581
+ const tokens = tokenize(combined);
1582
+ const extractedSymbols = extractLikelyCodeSymbols(combined, 20);
1583
+ const extractedPaths = extractPathLikeSymbols(combined);
1584
+ const quotedSymbols = (combined.match(/`[^`]+`/g) ?? []).length;
1585
+ const symbolSignal = extractedSymbols.length + extractedPaths.length * 2 + quotedSymbols * 2;
1586
+
1587
+ if (symbolSignal >= 8 || (extractedSymbols.length >= 4 && (extractedPaths.length > 0 || quotedSymbols > 0))) {
1588
+ return "symbol-heavy";
1589
+ }
1590
+
1591
+ let implSignal = 0;
1592
+ let conceptualSignal = 0;
1593
+ for (const token of tokens) {
1594
+ if (CODE_INTENT_TOKENS.has(token) || ENHANCER_IMPLEMENTATION_INTENT_HINTS.includes(token)) {
1595
+ implSignal += 1;
1596
+ }
1597
+ if (ENHANCER_DOCS_INTENT_TOKENS.has(token) || ENHANCER_CONCEPTUAL_INTENT_TOKENS.has(token)) {
1598
+ conceptualSignal += 1;
1599
+ }
1600
+ }
1601
+
1602
+ const negativePreferences = detectNegativePathPreferences(combined);
1603
+ if (negativePreferences.avoid_docs || negativePreferences.avoid_tests || negativePreferences.avoid_examples) {
1604
+ implSignal += 2;
1605
+ }
1606
+
1607
+ if (implSignal >= conceptualSignal + 1) {
1608
+ return "impl-focused";
1609
+ }
1610
+ return "conceptual";
1611
+ }
1612
+
1613
+ function resolveEnhancerIntentPolicy(input: {
1614
+ query_intent: EnhancerQueryIntent;
1615
+ enhancer_config: RetrievalEnhancerConfig;
1616
+ negative_preferences: NegativePathPreferences;
1617
+ }): EnhancerIntentPolicy {
1618
+ const strictImplOnlyFiltering =
1619
+ input.query_intent !== "conceptual" &&
1620
+ (input.negative_preferences.avoid_docs ||
1621
+ input.negative_preferences.avoid_tests ||
1622
+ input.negative_preferences.avoid_examples);
1623
+
1624
+ if (input.query_intent === "symbol-heavy") {
1625
+ return {
1626
+ max_expansion_hints: Math.min(input.enhancer_config.max_expansion_hints, 12),
1627
+ max_candidates_pre_rerank: Math.max(1, Math.min(input.enhancer_config.max_candidates_pre_rerank, 3)),
1628
+ max_candidates_per_directory_pre_rerank: 1,
1629
+ strict_impl_only_filtering: strictImplOnlyFiltering
1630
+ };
1631
+ }
1632
+ if (input.query_intent === "impl-focused") {
1633
+ return {
1634
+ max_expansion_hints: Math.min(input.enhancer_config.max_expansion_hints, 18),
1635
+ max_candidates_pre_rerank: Math.max(1, Math.min(input.enhancer_config.max_candidates_pre_rerank, 4)),
1636
+ max_candidates_per_directory_pre_rerank: 2,
1637
+ strict_impl_only_filtering: strictImplOnlyFiltering
1638
+ };
1639
+ }
1640
+ return {
1641
+ max_expansion_hints: Math.min(input.enhancer_config.max_expansion_hints, 22),
1642
+ max_candidates_pre_rerank: Math.max(1, Math.min(input.enhancer_config.max_candidates_pre_rerank, 4)),
1643
+ max_candidates_per_directory_pre_rerank: 3,
1644
+ strict_impl_only_filtering: false
1645
+ };
1646
+ }
1647
+
1648
+ function looksLikeCodeSymbol(value: string): boolean {
1649
+ if (value.length < 3 || value.length > 96) {
1650
+ return false;
1651
+ }
1652
+ if (!/^[A-Za-z_][A-Za-z0-9_./-]*$/.test(value)) {
1653
+ return false;
1654
+ }
1655
+ const normalized = value.toLowerCase();
1656
+ if (SYMBOL_STOPWORDS.has(normalized)) {
1657
+ return false;
1658
+ }
1659
+
1660
+ return (
1661
+ value.includes("_") ||
1662
+ value.includes(".") ||
1663
+ value.includes("/") ||
1664
+ /[a-z][A-Z]/.test(value) ||
1665
+ /[A-Z]{2,}/.test(value) ||
1666
+ /^(?:get|set|add|create|render|handle|parse|build|validate|register|process|update|remove|is|has)[A-Z_]/.test(
1667
+ value
1668
+ )
1669
+ );
1670
+ }
1671
+
1672
+ function extractPathLikeSymbols(text: string): string[] {
1673
+ const out: string[] = [];
1674
+ for (const match of text.matchAll(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\.[A-Za-z0-9]+\b/g)) {
1675
+ const value = (match[0] ?? "").trim();
1676
+ if (value) {
1677
+ out.push(value);
1678
+ }
1679
+ }
1680
+ return out;
1681
+ }
1682
+
1683
+ function extractLikelyCodeSymbols(text: string, limit = 12): string[] {
1684
+ const symbols: string[] = [];
1685
+ const seen = new Set<string>();
1686
+
1687
+ const add = (candidate: string): void => {
1688
+ const normalized = candidate.trim().replace(/^[`"'([{]+|[`"')\]}:;,.]+$/g, "");
1689
+ if (!normalized || seen.has(normalized) || !looksLikeCodeSymbol(normalized)) {
1690
+ return;
1691
+ }
1692
+ seen.add(normalized);
1693
+ symbols.push(normalized);
1694
+ };
1695
+
1696
+ for (const match of text.matchAll(/`([^`]+)`/g)) {
1697
+ const quoted = match[1] ?? "";
1698
+ for (const part of quoted.split(/[\s,;(){}[\]]+/g).filter(Boolean)) {
1699
+ add(part);
1700
+ if (symbols.length >= limit) {
1701
+ return symbols;
1702
+ }
1703
+ }
1704
+ }
1705
+
1706
+ for (const value of extractPathLikeSymbols(text)) {
1707
+ add(value);
1708
+ if (symbols.length >= limit) {
1709
+ return symbols;
1710
+ }
1711
+ }
1712
+
1713
+ for (const match of text.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) {
1714
+ add(match[1] ?? "");
1715
+ if (symbols.length >= limit) {
1716
+ return symbols;
1717
+ }
1718
+ }
1719
+
1720
+ for (const match of text.matchAll(/\b[A-Za-z_][A-Za-z0-9_]*(?:[./-][A-Za-z_][A-Za-z0-9_]*)*\b/g)) {
1721
+ add(match[0] ?? "");
1722
+ if (symbols.length >= limit) {
1723
+ return symbols;
1724
+ }
1725
+ }
1726
+
1727
+ return symbols;
1728
+ }
1729
+
1730
+ function extractPathFormHints(text: string, limit = 12): string[] {
1731
+ const words = (text.toLowerCase().match(/\b[a-z][a-z0-9_]{1,48}\b/g) ?? [])
1732
+ .map((token) => token.trim())
1733
+ .filter(Boolean)
1734
+ .filter((token) => !SYMBOL_STOPWORDS.has(token));
1735
+ const out: string[] = [];
1736
+ const seen = new Set<string>();
1737
+ for (let i = 0; i < words.length - 1; i += 1) {
1738
+ const left = words[i];
1739
+ const right = words[i + 1];
1740
+ if (!left || !right || left.length < 3 || right.length < 3) {
1741
+ continue;
1742
+ }
1743
+ const forms = [
1744
+ `${left}/${right}`,
1745
+ `${singularizeToken(left) ?? left}/${singularizeToken(right) ?? right}`,
1746
+ `${pluralizeToken(left) ?? left}/${right}`
1747
+ ];
1748
+ for (const form of forms) {
1749
+ if (seen.has(form)) {
1750
+ continue;
1751
+ }
1752
+ seen.add(form);
1753
+ out.push(form);
1754
+ if (out.length >= limit) {
1755
+ return out;
1756
+ }
1757
+ }
1758
+ }
1759
+ return out;
1760
+ }
1761
+
1762
+ function buildEnhancerRetrievalQuery(
1763
+ prompt: string,
1764
+ history: EnhancePromptInput["conversation_history"],
1765
+ options?: { maxExpansionHints?: number; queryIntent?: EnhancerQueryIntent }
1766
+ ): { query: string; expanded_hint_count: number; query_intent: EnhancerQueryIntent } {
1767
+ const maxExpansionHints = options?.maxExpansionHints ?? DEFAULT_RETRIEVAL_ENHANCER_CONFIG.max_expansion_hints;
1768
+ const queryIntent = options?.queryIntent ?? classifyEnhancerQueryIntent(prompt, history);
1769
+ const recentMessages = [...history]
1770
+ .reverse()
1771
+ .filter((message) => message.role !== "system")
1772
+ .map((message) => message.content.trim())
1773
+ .filter((content) => content.length > 0 && content !== prompt.trim())
1774
+ .slice(0, 4)
1775
+ .reverse();
1776
+ const combined = normalizeQuery([prompt, ...recentMessages].join("\n").trim());
1777
+ const lower = combined.toLowerCase();
1778
+ const queryTokens = tokenize(combined);
1779
+ const queryTokenSet = new Set(queryTokens);
1780
+ const docsIntent = queryTokens.some((token) => ENHANCER_DOCS_INTENT_TOKENS.has(token));
1781
+ const negativePreferences = detectNegativePathPreferences(combined);
1782
+ const testsIntent = queryTokens.some((token) => ENHANCER_TEST_INTENT_TOKENS.has(token));
1783
+ const implementationIntent = queryIntent !== "conceptual";
1784
+
1785
+ const hintSet = new Set<string>();
1786
+ const hints: string[] = [];
1787
+ const addHint = (value: string): void => {
1788
+ if (hints.length >= maxExpansionHints) {
1789
+ return;
1790
+ }
1791
+ const normalized = value.trim();
1792
+ if (!normalized || hintSet.has(normalized)) {
1793
+ return;
1794
+ }
1795
+ hintSet.add(normalized);
1796
+ hints.push(normalized);
1797
+ };
1798
+
1799
+ const extractedSymbols = extractLikelyCodeSymbols(combined, 12);
1800
+ for (const symbol of extractedSymbols) {
1801
+ addHint(symbol);
1802
+ }
1803
+
1804
+ const extractedPathForms = extractPathFormHints(combined, 12);
1805
+ for (const pathForm of extractedPathForms) {
1806
+ addHint(pathForm);
1807
+ }
1808
+ const symbolHeavyPrompt = queryIntent === "symbol-heavy" || extractedSymbols.length >= 4 || extractedPathForms.length >= 4;
1809
+ const genericHintBudget =
1810
+ queryIntent === "symbol-heavy"
1811
+ ? Math.max(2, Math.floor(maxExpansionHints / 3))
1812
+ : queryIntent === "impl-focused"
1813
+ ? Math.max(4, Math.floor(maxExpansionHints * 0.8))
1814
+ : maxExpansionHints;
1815
+ const frameworkHintBudget =
1816
+ queryIntent === "symbol-heavy"
1817
+ ? Math.max(2, Math.floor(genericHintBudget / 2))
1818
+ : queryIntent === "impl-focused"
1819
+ ? Math.max(3, Math.floor(genericHintBudget * 0.8))
1820
+ : Math.max(4, Math.floor(genericHintBudget * 0.85));
1821
+ let genericHintCount = 0;
1822
+ let frameworkHintCount = 0;
1823
+ const addGenericHint = (value: string): void => {
1824
+ if (genericHintCount >= genericHintBudget) {
1825
+ return;
1826
+ }
1827
+ const beforeCount = hints.length;
1828
+ addHint(value);
1829
+ if (hints.length > beforeCount) {
1830
+ genericHintCount += 1;
1831
+ }
1832
+ };
1833
+ const addFrameworkHint = (value: string): void => {
1834
+ if (frameworkHintCount >= frameworkHintBudget) {
1835
+ return;
1836
+ }
1837
+ const beforeCount = hints.length;
1838
+ addGenericHint(value);
1839
+ if (hints.length > beforeCount) {
1840
+ frameworkHintCount += 1;
1841
+ }
1842
+ };
1843
+
1844
+ if (/\b(cli|subcommand|command|flag)\b/.test(lower)) {
1845
+ addGenericHint("main.rs");
1846
+ addGenericHint("clap");
1847
+ addGenericHint("subcommand");
1848
+ addGenericHint("command");
1849
+ }
1850
+ if (/\b(react|tsx|ui|component|layout|panel)\b/.test(lower)) {
1851
+ addGenericHint("app.tsx");
1852
+ addGenericHint("component");
1853
+ addGenericHint("ui");
1854
+ }
1855
+ if (queryIntent === "conceptual" || (!implementationIntent && /\b(adr|readme|spec|docs|documentation|milestone)\b/.test(lower))) {
1856
+ addGenericHint("docs");
1857
+ addGenericHint("spec");
1858
+ addGenericHint("markdown");
1859
+ }
1860
+ if (/\b(python|django|flask|jinja|pydantic)\b/.test(lower)) {
1861
+ addGenericHint("py");
1862
+ addGenericHint("src");
1863
+ addGenericHint("views.py");
1864
+ addGenericHint("handlers");
1865
+ }
1866
+ if (/\b(node|javascript|typescript|ts|js|express|koa|fastify)\b/.test(lower)) {
1867
+ addGenericHint("src");
1868
+ addGenericHint("lib");
1869
+ addGenericHint("handler");
1870
+ addGenericHint("middleware");
1871
+ }
1872
+ const prefersRuntimeOnly =
1873
+ negativePreferences.avoid_docs || negativePreferences.avoid_tests || negativePreferences.avoid_examples;
1874
+ if (implementationIntent && (!docsIntent || prefersRuntimeOnly)) {
1875
+ for (const hint of ENHANCER_IMPLEMENTATION_INTENT_HINTS) {
1876
+ addGenericHint(hint);
1877
+ }
1878
+ }
1879
+ if (prefersRuntimeOnly) {
1880
+ addGenericHint("runtime");
1881
+ addGenericHint("src");
1882
+ addGenericHint("lib");
1883
+ addGenericHint("implementation");
1884
+ }
1885
+ if (testsIntent) {
1886
+ addGenericHint("test");
1887
+ addGenericHint("assert");
1888
+ }
1889
+
1890
+ for (const rule of ENHANCER_FRAMEWORK_HINT_RULES) {
1891
+ const hasTokenTrigger = rule.token_triggers.some((token) => queryTokenSet.has(token));
1892
+ const hasPhraseTrigger = rule.phrase_triggers.some((phrase) => lower.includes(phrase));
1893
+ if (!hasTokenTrigger && !hasPhraseTrigger) {
1894
+ continue;
1895
+ }
1896
+ for (const symbol of rule.symbol_hints) {
1897
+ addFrameworkHint(symbol);
1898
+ }
1899
+ for (const pathHint of rule.path_hints) {
1900
+ addFrameworkHint(pathHint);
1901
+ }
1902
+ }
1903
+
1904
+ const sections = [combined];
1905
+ sections.push(`query_intent: ${queryIntent}`);
1906
+ if (extractedSymbols.length > 0) {
1907
+ sections.push(`symbol_candidates: ${extractedSymbols.join(" ")}`);
1908
+ }
1909
+ if (hints.length > 0) {
1910
+ sections.push(`retrieval_hints: ${hints.join(" ")}`);
1911
+ }
1912
+
1913
+ return {
1914
+ query: sections.join("\n").trim(),
1915
+ expanded_hint_count: hints.length,
1916
+ query_intent: queryIntent
1917
+ };
1918
+ }
1919
+
1920
+ const ENHANCER_LOW_CONFIDENCE_WARNING = "Low retrieval confidence; narrowed context refs and added clarification questions.";
1921
+
1922
+ const ENHANCER_CONFIDENCE_OVERLAP_STOPWORDS = new Set([
1923
+ "a",
1924
+ "about",
1925
+ "an",
1926
+ "and",
1927
+ "avoid",
1928
+ "behavior",
1929
+ "change",
1930
+ "doc",
1931
+ "docs",
1932
+ "documentation",
1933
+ "example",
1934
+ "examples",
1935
+ "for",
1936
+ "from",
1937
+ "in",
1938
+ "is",
1939
+ "not",
1940
+ "of",
1941
+ "on",
1942
+ "or",
1943
+ "readme",
1944
+ "spec",
1945
+ "tests",
1946
+ "test",
1947
+ "the",
1948
+ "to",
1949
+ "without",
1950
+ "with"
1951
+ ]);
1952
+
1953
+ function parentDirectory(path: string): string {
1954
+ const normalized = normalizePath(path);
1955
+ const idx = normalized.lastIndexOf("/");
1956
+ if (idx <= 0) {
1957
+ return ".";
1958
+ }
1959
+ return normalized.slice(0, idx);
1960
+ }
1961
+
1962
+ function fileExtension(path: string): string {
1963
+ const normalized = normalizePath(path).toLowerCase();
1964
+ const filename = normalized.split("/").pop() ?? normalized;
1965
+ const idx = filename.lastIndexOf(".");
1966
+ if (idx < 0) {
1967
+ return "";
1968
+ }
1969
+ return filename.slice(idx);
1970
+ }
1971
+
1972
+ function average(values: number[]): number {
1973
+ if (values.length === 0) {
1974
+ return 0;
1975
+ }
1976
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
1977
+ }
1978
+
1979
+ function percentile(values: number[], p: number): number {
1980
+ if (values.length === 0) {
1981
+ return 0;
1982
+ }
1983
+ const sorted = [...values].sort((a, b) => a - b);
1984
+ const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * p) - 1));
1985
+ return sorted[idx] ?? 0;
1986
+ }
1987
+
1988
+ function clamp01(value: number): number {
1989
+ return Math.max(0, Math.min(1, value));
1990
+ }
1991
+
1992
+ function normalizeEnhancerOverlapTokens(text: string): string[] {
1993
+ return tokenize(text).filter((token) => token.length >= 3 && !ENHANCER_CONFIDENCE_OVERLAP_STOPWORDS.has(token));
1994
+ }
1995
+
1996
+ function overlapRatio(queryTokens: string[], result: SearchContextOutput["results"][number]): number {
1997
+ if (queryTokens.length === 0) {
1998
+ return 0;
1999
+ }
2000
+ const haystack = new Set(tokenize(`${result.path}\n${result.reason}\n${result.snippet}`));
2001
+ let overlap = 0;
2002
+ for (const token of queryTokens) {
2003
+ if (haystack.has(token)) {
2004
+ overlap += 1;
2005
+ }
2006
+ }
2007
+ return overlap / queryTokens.length;
2008
+ }
2009
+
2010
+ function pathDiversity(results: SearchContextOutput["results"]): number {
2011
+ if (results.length === 0) {
2012
+ return 0;
2013
+ }
2014
+ const uniqueDirs = new Set(results.map((result) => parentDirectory(result.path).toLowerCase()));
2015
+ return uniqueDirs.size / results.length;
2016
+ }
2017
+
2018
+ function isDocsLikePath(path: string): boolean {
2019
+ const normalized = normalizePath(path).toLowerCase();
2020
+ return (
2021
+ normalized.startsWith("docs/") ||
2022
+ normalized.includes("/docs/") ||
2023
+ normalized.endsWith(".md") ||
2024
+ normalized.endsWith(".rst") ||
2025
+ normalized.endsWith(".txt")
2026
+ );
2027
+ }
2028
+
2029
+ function isTestLikePath(path: string): boolean {
2030
+ const normalized = normalizePath(path).toLowerCase();
2031
+ return (
2032
+ normalized.startsWith("test/") ||
2033
+ normalized.startsWith("tests/") ||
2034
+ normalized.startsWith("__tests__/") ||
2035
+ normalized.includes("/test/") ||
2036
+ normalized.includes("/tests/") ||
2037
+ normalized.includes("/__tests__/") ||
2038
+ normalized.endsWith(".test.js") ||
2039
+ normalized.endsWith(".test.ts") ||
2040
+ normalized.endsWith(".spec.js") ||
2041
+ normalized.endsWith(".spec.ts") ||
2042
+ normalized.endsWith(".spec.py")
2043
+ );
2044
+ }
2045
+
2046
+ function isExampleLikePath(path: string): boolean {
2047
+ const normalized = normalizePath(path).toLowerCase();
2048
+ return (
2049
+ normalized.startsWith("example/") ||
2050
+ normalized.startsWith("examples/") ||
2051
+ normalized.includes("/example/") ||
2052
+ normalized.includes("/examples/")
2053
+ );
2054
+ }
2055
+
2056
+ function isArchiveLikePath(path: string): boolean {
2057
+ const normalized = normalizePath(path).toLowerCase();
2058
+ return (
2059
+ normalized.includes("/archive/") ||
2060
+ normalized.includes("/_archive/") ||
2061
+ normalized.startsWith("archive/") ||
2062
+ normalized.startsWith("_archive/")
2063
+ );
2064
+ }
2065
+
2066
+ function shouldAvoidPathFromNegation(path: string, preferences: NegativePathPreferences): boolean {
2067
+ if (preferences.avoid_archive && isArchiveLikePath(path)) {
2068
+ return true;
2069
+ }
2070
+ if (preferences.avoid_docs && isDocsLikePath(path)) {
2071
+ return true;
2072
+ }
2073
+ if (preferences.avoid_tests && isTestLikePath(path)) {
2074
+ return true;
2075
+ }
2076
+ if (preferences.avoid_examples && isExampleLikePath(path)) {
2077
+ return true;
2078
+ }
2079
+ return false;
2080
+ }
2081
+
2082
+ function isRiskyEnhancerPath(path: string, intent: ReturnType<typeof classifyIntent>): boolean {
2083
+ if (intent === "docs") {
2084
+ return false;
2085
+ }
2086
+ if (intent === "tests") {
2087
+ return isDocsLikePath(path) || isExampleLikePath(path);
2088
+ }
2089
+ return isDocsLikePath(path) || isTestLikePath(path) || isExampleLikePath(path);
2090
+ }
2091
+
2092
+ function applyEnhancerIntentPathFiltering(
2093
+ results: SearchContextOutput["results"],
2094
+ input: {
2095
+ intent: ReturnType<typeof classifyIntent>;
2096
+ negative_preferences: NegativePathPreferences;
2097
+ strict_impl_only_filtering: boolean;
2098
+ }
2099
+ ): SearchContextOutput["results"] {
2100
+ if (results.length === 0) {
2101
+ return [];
2102
+ }
2103
+ const preferred = results.filter(
2104
+ (result) => !isRiskyEnhancerPath(result.path, input.intent) && !shouldAvoidPathFromNegation(result.path, input.negative_preferences)
2105
+ );
2106
+ if (preferred.length > 0) {
2107
+ return preferred;
2108
+ }
2109
+ if (input.strict_impl_only_filtering) {
2110
+ const implOnly = results.filter(
2111
+ (result) =>
2112
+ !isDocsLikePath(result.path) &&
2113
+ !isTestLikePath(result.path) &&
2114
+ !isExampleLikePath(result.path) &&
2115
+ !isArchiveLikePath(result.path)
2116
+ );
2117
+ if (implOnly.length > 0) {
2118
+ return implOnly;
2119
+ }
2120
+ }
2121
+ const tolerated = results.filter((result) => !shouldAvoidPathFromNegation(result.path, input.negative_preferences));
2122
+ return tolerated.length > 0 ? tolerated : results;
2123
+ }
2124
+
2125
+ function compareSearchResults(a: SearchContextOutput["results"][number], b: SearchContextOutput["results"][number]): number {
2126
+ const scoreDiff = b.score - a.score;
2127
+ if (Math.abs(scoreDiff) > 1e-9) {
2128
+ return scoreDiff;
2129
+ }
2130
+ if (a.path !== b.path) {
2131
+ return a.path.localeCompare(b.path);
2132
+ }
2133
+ if (a.start_line !== b.start_line) {
2134
+ return a.start_line - b.start_line;
2135
+ }
2136
+ return a.end_line - b.end_line;
2137
+ }
2138
+
2139
+ function dedupeEnhancerCandidatesByPath(results: SearchContextOutput["results"]): SearchContextOutput["results"] {
2140
+ const byPath = new Map<string, SearchContextOutput["results"][number]>();
2141
+ for (const result of results) {
2142
+ const key = normalizePath(result.path).toLowerCase();
2143
+ const existing = byPath.get(key);
2144
+ if (!existing || compareSearchResults(result, existing) < 0) {
2145
+ byPath.set(key, result);
2146
+ }
2147
+ }
2148
+ return [...byPath.values()].sort(compareSearchResults);
2149
+ }
2150
+
2151
+ function collapseEnhancerCandidatesByDirectory(
2152
+ results: SearchContextOutput["results"],
2153
+ maxPerDirectory: number
2154
+ ): SearchContextOutput["results"] {
2155
+ if (maxPerDirectory <= 0) {
2156
+ return [];
2157
+ }
2158
+ const output: SearchContextOutput["results"] = [];
2159
+ const directoryCounts = new Map<string, number>();
2160
+ for (const result of [...results].sort(compareSearchResults)) {
2161
+ const directory = parentDirectory(result.path).toLowerCase();
2162
+ const count = directoryCounts.get(directory) ?? 0;
2163
+ if (count >= maxPerDirectory) {
2164
+ continue;
2165
+ }
2166
+ directoryCounts.set(directory, count + 1);
2167
+ output.push(result);
2168
+ }
2169
+ return output;
2170
+ }
2171
+
2172
+ function extractEnhancerAnchors(text: string): string[] {
2173
+ const anchors = new Set<string>();
2174
+ for (const symbol of extractLikelyCodeSymbols(text, 10)) {
2175
+ const normalized = symbol.trim().toLowerCase();
2176
+ if (normalized.length >= 4) {
2177
+ anchors.add(normalized);
2178
+ }
2179
+ }
2180
+ for (const rawPath of extractPathLikeSymbols(text).slice(0, 10)) {
2181
+ const normalizedPath = normalizePath(rawPath).toLowerCase();
2182
+ if (normalizedPath.length >= 4) {
2183
+ anchors.add(normalizedPath);
2184
+ }
2185
+ const leaf = normalizedPath.split("/").pop();
2186
+ if (leaf && leaf.length >= 4) {
2187
+ anchors.add(leaf);
2188
+ }
2189
+ }
2190
+ return [...anchors];
2191
+ }
2192
+
2193
+ function hasStrongEnhancerAnchorMatch(input: {
2194
+ prompt: string;
2195
+ history: EnhancePromptInput["conversation_history"];
2196
+ results: SearchContextOutput["results"];
2197
+ }): boolean {
2198
+ const top = dedupeEnhancerCandidatesByPath(input.results).slice(0, 3);
2199
+ if (top.length === 0) {
2200
+ return false;
2201
+ }
2202
+
2203
+ const topScore = top[0]?.score ?? 0;
2204
+ const runnerUpScore = top[1]?.score ?? Number.NEGATIVE_INFINITY;
2205
+ const strongScoreMargin = top.length === 1 || topScore - runnerUpScore >= 0.08;
2206
+ const hasTopExactSymbolMatch = top.some((result) => result.reason === "exact symbol match");
2207
+ if (hasTopExactSymbolMatch && strongScoreMargin && topScore >= 0.55) {
2208
+ return true;
2209
+ }
2210
+
2211
+ const anchorSource = `${input.prompt}\n${input.history.map((entry) => entry.content).join("\n")}`;
2212
+ const anchors = extractEnhancerAnchors(anchorSource);
2213
+ if (anchors.length === 0) {
2214
+ return false;
2215
+ }
2216
+
2217
+ let anchorMatches = 0;
2218
+ for (const result of top) {
2219
+ const normalizedPath = normalizePath(result.path).toLowerCase();
2220
+ const normalizedSnippet = result.snippet.toLowerCase();
2221
+ const matched = anchors.some(
2222
+ (anchor) => normalizedPath.includes(anchor) || (anchor.length >= 5 && normalizedSnippet.includes(anchor))
2223
+ );
2224
+ if (matched) {
2225
+ anchorMatches += 1;
2226
+ }
2227
+ }
2228
+
2229
+ return anchorMatches >= 2 && strongScoreMargin;
2230
+ }
2231
+
2232
+ function shouldBypassEnhancerRerankForAnchors(input: {
2233
+ query_intent: EnhancerQueryIntent;
2234
+ prompt: string;
2235
+ history: EnhancePromptInput["conversation_history"];
2236
+ results: SearchContextOutput["results"];
2237
+ negative_preferences: NegativePathPreferences;
2238
+ }): boolean {
2239
+ if (input.query_intent === "conceptual") {
2240
+ return false;
2241
+ }
2242
+ const hasStrongAnchors = hasStrongEnhancerAnchorMatch({
2243
+ prompt: input.prompt,
2244
+ history: input.history,
2245
+ results: input.results
2246
+ });
2247
+ if (!hasStrongAnchors) {
2248
+ return false;
2249
+ }
2250
+ if (input.query_intent === "symbol-heavy") {
2251
+ return true;
2252
+ }
2253
+ if (
2254
+ input.negative_preferences.avoid_docs ||
2255
+ input.negative_preferences.avoid_tests ||
2256
+ input.negative_preferences.avoid_examples ||
2257
+ input.negative_preferences.avoid_archive
2258
+ ) {
2259
+ return true;
2260
+ }
2261
+ const top = dedupeEnhancerCandidatesByPath(input.results).slice(0, 2);
2262
+ if (top.length < 2) {
2263
+ return true;
2264
+ }
2265
+ const lead = (top[0]?.score ?? 0) - (top[1]?.score ?? 0);
2266
+ return lead >= 0.04;
2267
+ }
2268
+
2269
+ interface EnhancerConfidenceSignals {
2270
+ score_spread: number;
2271
+ token_overlap: number;
2272
+ path_diversity: number;
2273
+ confidence_score: number;
2274
+ confidence_threshold: number;
2275
+ failed_signals: string[];
2276
+ low_confidence: boolean;
2277
+ }
2278
+
2279
+ function evaluateEnhancerConfidence(input: {
2280
+ prompt: string;
2281
+ retrieval_query: string;
2282
+ query_intent: EnhancerQueryIntent;
2283
+ results: SearchContextOutput["results"];
2284
+ }): EnhancerConfidenceSignals {
2285
+ const top = input.results.slice(0, 5);
2286
+ if (top.length === 0) {
2287
+ return {
2288
+ score_spread: 0,
2289
+ token_overlap: 0,
2290
+ path_diversity: 0,
2291
+ confidence_score: 0,
2292
+ confidence_threshold: 0.5,
2293
+ failed_signals: ["empty_results"],
2294
+ low_confidence: true
2295
+ };
2296
+ }
2297
+
2298
+ const topScores = top.map((result) => result.score);
2299
+ const topScore = topScores[0] ?? 0;
2300
+ const runnerUpScore = topScores[1] ?? topScore;
2301
+ const scoreSpread = (topScores[0] ?? 0) - average(topScores);
2302
+ const scoreP25 = percentile(topScores, 0.25);
2303
+ const scoreP50 = percentile(topScores, 0.5);
2304
+ const scoreP75 = percentile(topScores, 0.75);
2305
+ const scoreIqr = Math.max(1e-6, scoreP75 - scoreP25);
2306
+ const leadStrength = clamp01((topScore - runnerUpScore) / (Math.abs(topScore - runnerUpScore) + scoreIqr));
2307
+ const spreadStrength = clamp01((topScore - scoreP50) / (Math.abs(topScore - scoreP50) + scoreIqr));
2308
+ const distributionStrength = average([leadStrength, spreadStrength]);
2309
+
2310
+ const overlapTokens = normalizeEnhancerOverlapTokens(`${input.prompt}\n${input.retrieval_query}`);
2311
+ const topOverlap = average(top.slice(0, 3).map((result) => overlapRatio(overlapTokens, result)));
2312
+ const topPathDiversity = pathDiversity(top);
2313
+ const targetPathDiversity = input.query_intent === "conceptual" ? 0.6 : 0.45;
2314
+ const diversityStrength = clamp01(1 - Math.abs(topPathDiversity - targetPathDiversity) / Math.max(0.2, targetPathDiversity));
2315
+ const confidenceComponents = [distributionStrength, topOverlap, diversityStrength];
2316
+ const confidenceScore = average(confidenceComponents);
2317
+ const thresholdQuantile =
2318
+ input.query_intent === "symbol-heavy" ? 0.5 : input.query_intent === "impl-focused" ? 0.55 : 0.6;
2319
+ const confidenceThreshold = percentile(confidenceComponents, thresholdQuantile);
2320
+
2321
+ const failedSignals: string[] = [];
2322
+ if (distributionStrength < confidenceThreshold) {
2323
+ failedSignals.push("score_spread");
2324
+ }
2325
+ if (topOverlap < confidenceThreshold) {
2326
+ failedSignals.push("token_overlap");
2327
+ }
2328
+ if (diversityStrength < confidenceThreshold) {
2329
+ failedSignals.push("path_diversity");
2330
+ }
2331
+ const strongSymbolOrPathSignal = top.some((result) => result.reason === "exact symbol match") && topOverlap >= 0.16;
2332
+ const lowConfidence = !strongSymbolOrPathSignal && confidenceScore + 0.01 < confidenceThreshold;
2333
+
2334
+ return {
2335
+ score_spread: scoreSpread,
2336
+ token_overlap: topOverlap,
2337
+ path_diversity: topPathDiversity,
2338
+ confidence_score: confidenceScore,
2339
+ confidence_threshold: confidenceThreshold,
2340
+ failed_signals: failedSignals,
2341
+ low_confidence: lowConfidence
2342
+ };
2343
+ }
2344
+
2345
+ function rankEnhancerResultsForConfidence(input: {
2346
+ results: SearchContextOutput["results"];
2347
+ intent: ReturnType<typeof classifyIntent>;
2348
+ query_intent: EnhancerQueryIntent;
2349
+ negative_preferences: NegativePathPreferences;
2350
+ prompt: string;
2351
+ retrieval_query: string;
2352
+ }): SearchContextOutput["results"] {
2353
+ const overlapTokens = normalizeEnhancerOverlapTokens(`${input.prompt}\n${input.retrieval_query}`);
2354
+ const anchors = extractEnhancerAnchors(`${input.prompt}\n${input.retrieval_query}`);
2355
+ const anchorScore = (result: SearchContextOutput["results"][number]): number => {
2356
+ const normalizedPath = normalizePath(result.path).toLowerCase();
2357
+ const normalizedSnippet = result.snippet.toLowerCase();
2358
+ let score = result.reason === "exact symbol match" ? 2 : 0;
2359
+ for (const anchor of anchors) {
2360
+ if (normalizedPath.includes(anchor)) {
2361
+ score += 2;
2362
+ continue;
2363
+ }
2364
+ if (anchor.length >= 5 && normalizedSnippet.includes(anchor)) {
2365
+ score += 1;
2366
+ }
2367
+ }
2368
+ return score;
2369
+ };
2370
+ return [...input.results].sort((a, b) => {
2371
+ const aExcludedByNegation = shouldAvoidPathFromNegation(a.path, input.negative_preferences) ? 1 : 0;
2372
+ const bExcludedByNegation = shouldAvoidPathFromNegation(b.path, input.negative_preferences) ? 1 : 0;
2373
+ if (aExcludedByNegation !== bExcludedByNegation) {
2374
+ return aExcludedByNegation - bExcludedByNegation;
2375
+ }
2376
+
2377
+ const aRisk = isRiskyEnhancerPath(a.path, input.intent) ? 1 : 0;
2378
+ const bRisk = isRiskyEnhancerPath(b.path, input.intent) ? 1 : 0;
2379
+ if (aRisk !== bRisk) {
2380
+ return aRisk - bRisk;
2381
+ }
2382
+
2383
+ if (input.query_intent !== "conceptual") {
2384
+ const aRuntimePenalty =
2385
+ isDocsLikePath(a.path) || isTestLikePath(a.path) || isExampleLikePath(a.path) || isArchiveLikePath(a.path) ? 1 : 0;
2386
+ const bRuntimePenalty =
2387
+ isDocsLikePath(b.path) || isTestLikePath(b.path) || isExampleLikePath(b.path) || isArchiveLikePath(b.path) ? 1 : 0;
2388
+ if (aRuntimePenalty !== bRuntimePenalty) {
2389
+ return aRuntimePenalty - bRuntimePenalty;
2390
+ }
2391
+ }
2392
+
2393
+ const anchorDiff = anchorScore(b) - anchorScore(a);
2394
+ if (anchorDiff !== 0) {
2395
+ return anchorDiff;
2396
+ }
2397
+
2398
+ const overlapDiff = overlapRatio(overlapTokens, b) - overlapRatio(overlapTokens, a);
2399
+ if (Math.abs(overlapDiff) > 1e-6) {
2400
+ return overlapDiff;
2401
+ }
2402
+
2403
+ const scoreDiff = b.score - a.score;
2404
+ if (Math.abs(scoreDiff) > 1e-6) {
2405
+ return scoreDiff;
2406
+ }
2407
+
2408
+ if (a.path !== b.path) {
2409
+ return a.path.localeCompare(b.path);
2410
+ }
2411
+ if (a.start_line !== b.start_line) {
2412
+ return a.start_line - b.start_line;
2413
+ }
2414
+ return a.end_line - b.end_line;
2415
+ });
2416
+ }
2417
+
2418
+ async function runWithTimeout<T>(input: { timeout_ms: number; fn: () => Promise<T> | T }): Promise<T> {
2419
+ return await new Promise<T>((resolve, reject) => {
2420
+ let settled = false;
2421
+ const timer = setTimeout(() => {
2422
+ if (settled) {
2423
+ return;
2424
+ }
2425
+ settled = true;
2426
+ reject(new Error(`timeout_after_${input.timeout_ms}ms`));
2427
+ }, input.timeout_ms);
2428
+
2429
+ Promise.resolve()
2430
+ .then(() => input.fn())
2431
+ .then((value) => {
2432
+ if (settled) {
2433
+ return;
2434
+ }
2435
+ settled = true;
2436
+ clearTimeout(timer);
2437
+ resolve(value);
2438
+ })
2439
+ .catch((error) => {
2440
+ if (settled) {
2441
+ return;
2442
+ }
2443
+ settled = true;
2444
+ clearTimeout(timer);
2445
+ reject(error);
2446
+ });
2447
+ });
2448
+ }
2449
+
2450
+ function deterministicEnhancerFallbackRanking(input: {
2451
+ results: SearchContextOutput["results"];
2452
+ intent: ReturnType<typeof classifyIntent>;
2453
+ negative_preferences: NegativePathPreferences;
2454
+ }): SearchContextOutput["results"] {
2455
+ const preferred = input.results.filter(
2456
+ (result) => !isRiskyEnhancerPath(result.path, input.intent) && !shouldAvoidPathFromNegation(result.path, input.negative_preferences)
2457
+ );
2458
+ const tolerated = input.results.filter(
2459
+ (result) => !preferred.includes(result) && !shouldAvoidPathFromNegation(result.path, input.negative_preferences)
2460
+ );
2461
+ const avoided = input.results.filter((result) => !preferred.includes(result) && !tolerated.includes(result));
2462
+ return [...preferred, ...tolerated, ...avoided];
2463
+ }
2464
+
2465
+ function localizeLowConfidenceQuestion(input: {
2466
+ language: "en" | "es" | "zh";
2467
+ kind: "scope" | "symbol" | "source_priority";
2468
+ symbol?: string;
2469
+ }): string {
2470
+ if (input.kind === "symbol") {
2471
+ if (input.language === "es") {
2472
+ return input.symbol
2473
+ ? `¿Puedes confirmar si el cambio debe centrarse en el símbolo "${input.symbol}"?`
2474
+ : "¿Qué función, clase o archivo exacto debe modificarse primero?";
2475
+ }
2476
+ if (input.language === "zh") {
2477
+ return input.symbol
2478
+ ? `请确认这次改动是否应优先围绕符号“${input.symbol}”展开?`
2479
+ : "请明确首先要修改的函数、类或文件路径。";
2480
+ }
2481
+ return input.symbol
2482
+ ? `Can you confirm whether "${input.symbol}" is the primary symbol to change?`
2483
+ : "Which exact function, class, or file should be edited first?";
2484
+ }
2485
+
2486
+ if (input.kind === "source_priority") {
2487
+ if (input.language === "es") {
2488
+ return "¿Debemos priorizar archivos de implementación en src/lib y dejar docs/tests/examples fuera de alcance?";
2489
+ }
2490
+ if (input.language === "zh") {
2491
+ return "是否应优先修改 src/lib 下的实现代码,并排除 docs/tests/examples?";
2492
+ }
2493
+ return "Should we prioritize runtime implementation files (src/lib) and exclude docs/tests/examples from scope?";
2494
+ }
2495
+
2496
+ if (input.language === "es") {
2497
+ return "¿Cuál es el alcance mínimo y el comportamiento que no debe cambiar?";
2498
+ }
2499
+ if (input.language === "zh") {
2500
+ return "这次改动的最小范围是什么?哪些行为必须保持不变?";
2501
+ }
2502
+ return "What is the minimal scope, and which behavior must remain unchanged?";
2503
+ }
2504
+
2505
+ function trimToContextBudget(results: SearchContextOutput["results"]): SearchContextOutput["results"] {
2506
+ let total = 0;
2507
+ const out: SearchContextOutput["results"] = [];
2508
+ for (const result of results) {
2509
+ total += tokenize(result.snippet).length;
2510
+ if (total > MAX_CONTEXT_BUDGET_TOKENS) {
2511
+ break;
2512
+ }
2513
+ out.push(result);
2514
+ }
2515
+ return out;
2516
+ }
2517
+
2518
+ function formatEnhancedPrompt(input: {
2519
+ intent: ReturnType<typeof classifyIntent>;
2520
+ language: "en" | "es" | "zh";
2521
+ original_prompt: string;
2522
+ refs: ContextRef[];
2523
+ }): string {
2524
+ const emptyRefsByLanguage = {
2525
+ en: "- (no file context available)",
2526
+ es: "- (no hay contexto de archivos disponible)",
2527
+ zh: "- (暂无可用文件上下文)"
2528
+ } as const;
2529
+ const likelyFiles =
2530
+ input.refs.length > 0 ? input.refs.map((r) => `- ${r.path}:${r.start_line}`).join("\n") : emptyRefsByLanguage[input.language];
2531
+
2532
+ if (input.language === "zh") {
2533
+ return [
2534
+ "目标",
2535
+ input.original_prompt,
2536
+ "",
2537
+ "当前状态",
2538
+ `- 识别意图: ${input.intent}`,
2539
+ "",
2540
+ "约束",
2541
+ "- 保持 v1 合约兼容和严格校验。",
2542
+ "",
2543
+ "可能涉及的文件",
2544
+ likelyFiles,
2545
+ "",
2546
+ "实现清单",
2547
+ "- 在改动前确认请求/响应合约。",
2548
+ "- 最小化改动并保持 tenant/workspace 隔离。",
2549
+ "",
2550
+ "边界情况",
2551
+ "- Workspace 没有可用索引。",
2552
+ "- 搜索过滤后结果为空。",
2553
+ "",
2554
+ "验证与测试",
2555
+ "- 运行 typecheck 和合约/工具测试。",
2556
+ "",
2557
+ "完成定义",
2558
+ "- 测试通过且行为符合 v1 规范。"
2559
+ ].join("\n");
2560
+ }
2561
+
2562
+ if (input.language === "es") {
2563
+ return [
2564
+ "Objetivo",
2565
+ input.original_prompt,
2566
+ "",
2567
+ "Estado actual",
2568
+ `- Intención clasificada: ${input.intent}`,
2569
+ "",
2570
+ "Restricciones",
2571
+ "- Mantener compatibilidad con contratos v1 y validación estricta.",
2572
+ "",
2573
+ "Archivos probables a editar",
2574
+ likelyFiles,
2575
+ "",
2576
+ "Checklist de implementación",
2577
+ "- Confirmar entradas/salidas del contrato antes de modificar lógica.",
2578
+ "- Aplicar cambios mínimos y mantener aislamiento por tenant/workspace.",
2579
+ "",
2580
+ "Casos límite",
2581
+ "- Workspace sin índice listo.",
2582
+ "- Filtros de búsqueda que no devuelven resultados.",
2583
+ "",
2584
+ "Validación y pruebas",
2585
+ "- Ejecutar typecheck y pruebas de contratos/herramientas.",
2586
+ "",
2587
+ "Definición de terminado",
2588
+ "- Los tests pasan y el comportamiento coincide con el spec."
2589
+ ].join("\n");
2590
+ }
2591
+
2592
+ return [
2593
+ "Goal",
2594
+ input.original_prompt,
2595
+ "",
2596
+ "Current state",
2597
+ `- Classified intent: ${input.intent}`,
2598
+ "",
2599
+ "Constraints",
2600
+ "- Keep v1 contract compatibility and strict schema validation.",
2601
+ "",
2602
+ "Likely files to edit",
2603
+ likelyFiles,
2604
+ "",
2605
+ "Implementation checklist",
2606
+ "- Confirm request/response contract assumptions before code edits.",
2607
+ "- Apply smallest safe changes while preserving tenant/workspace isolation.",
2608
+ "",
2609
+ "Edge cases",
2610
+ "- Workspace has no ready index.",
2611
+ "- Search filters produce empty result sets.",
2612
+ "",
2613
+ "Validation and tests",
2614
+ "- Run typecheck and contract/tool tests.",
2615
+ "",
2616
+ "Definition of done",
2617
+ "- Tests pass and behavior matches the v1 spec."
2618
+ ].join("\n");
2619
+ }
2620
+
2621
+ function detectSecretMatches(content: string): string[] {
2622
+ const matches = new Set<string>();
2623
+ for (const pattern of SECRET_PATTERNS) {
2624
+ if (pattern.pattern.test(content)) {
2625
+ matches.add(pattern.label);
2626
+ }
2627
+ pattern.pattern.lastIndex = 0;
2628
+ }
2629
+ return [...matches];
2630
+ }
2631
+
2632
+ export class InMemoryIndexStore implements IndexRepository {
2633
+ private readonly workspaces = new Map<string, WorkspaceRecord>();
2634
+ private readonly workspacesByPath = new Map<string, WorkspaceRecord>();
2635
+ private readonly indexes = new Map<string, ReadyIndex>();
2636
+ private readonly workspaceIndexes = new Map<string, string[]>();
2637
+ private readonly filesByIndex = new Map<string, PersistedFile[]>();
2638
+ private readonly chunksByFile = new Map<string, IndexedChunk[]>();
2639
+ private readonly indexMetadata = new Map<
2640
+ string,
2641
+ {
2642
+ tenant_id: string;
2643
+ embedding_provider: string;
2644
+ embedding_model?: string;
2645
+ embedding_dimensions: number;
2646
+ embedding_version?: string;
2647
+ chunking_strategy: "language_aware" | "sliding";
2648
+ chunking_fallback_strategy: "sliding";
2649
+ created_at: string;
2650
+ }
2651
+ >();
2652
+
2653
+ async migrate(): Promise<void> {
2654
+ return;
2655
+ }
2656
+
2657
+ async upsertWorkspace(input: WorkspaceRecord): Promise<void> {
2658
+ this.workspaces.set(input.workspace_id, input);
2659
+ this.workspacesByPath.set(`${input.tenant_id}:${input.project_root_path}`, input);
2660
+ }
2661
+
2662
+ async resolveWorkspaceByProjectRoot(tenant_id: string, project_root_path: string): Promise<WorkspaceRecord | undefined> {
2663
+ return this.workspacesByPath.get(`${tenant_id}:${project_root_path}`);
2664
+ }
2665
+
2666
+ async resolveWorkspaceByWorkspaceId(tenant_id: string, workspace_id: string): Promise<WorkspaceRecord | undefined> {
2667
+ const workspace = this.workspaces.get(workspace_id);
2668
+ if (!workspace || workspace.tenant_id !== tenant_id) {
2669
+ return undefined;
2670
+ }
2671
+ return workspace;
2672
+ }
2673
+
2674
+ async createIndexVersion(input: {
2675
+ tenant_id: string;
2676
+ workspace_id: string;
2677
+ index_version: string;
2678
+ status?: "indexing" | "ready" | "failed";
2679
+ }): Promise<ReadyIndex> {
2680
+ const indexId = `idx_${randomUUID()}`;
2681
+ const index: ReadyIndex = {
2682
+ index_id: indexId,
2683
+ workspace_id: input.workspace_id,
2684
+ tenant_id: input.tenant_id,
2685
+ index_version: input.index_version,
2686
+ status: input.status ?? "indexing",
2687
+ created_at: new Date().toISOString(),
2688
+ updated_at: new Date().toISOString()
2689
+ };
2690
+ this.indexes.set(indexId, index);
2691
+
2692
+ const existing = this.workspaceIndexes.get(input.workspace_id) ?? [];
2693
+ existing.push(indexId);
2694
+ this.workspaceIndexes.set(input.workspace_id, existing);
2695
+
2696
+ this.filesByIndex.set(indexId, []);
2697
+ return index;
2698
+ }
2699
+
2700
+ async markIndexStatus(input: {
2701
+ tenant_id: string;
2702
+ workspace_id: string;
2703
+ index_id: string;
2704
+ status: "indexing" | "ready" | "failed";
2705
+ }): Promise<void> {
2706
+ const index = this.indexes.get(input.index_id);
2707
+ if (!index) {
2708
+ return;
2709
+ }
2710
+
2711
+ if (index.tenant_id !== input.tenant_id || index.workspace_id !== input.workspace_id) {
2712
+ return;
2713
+ }
2714
+
2715
+ index.status = input.status;
2716
+ index.updated_at = new Date().toISOString();
2717
+ }
2718
+
2719
+ async getIndexByVersion(input: {
2720
+ tenant_id: string;
2721
+ workspace_id: string;
2722
+ index_version: string;
2723
+ }): Promise<ReadyIndex | undefined> {
2724
+ for (const index of this.indexes.values()) {
2725
+ if (
2726
+ index.tenant_id === input.tenant_id &&
2727
+ index.workspace_id === input.workspace_id &&
2728
+ index.index_version === input.index_version
2729
+ ) {
2730
+ return index;
2731
+ }
2732
+ }
2733
+ return undefined;
2734
+ }
2735
+
2736
+ async resetIndexContent(input: {
2737
+ tenant_id: string;
2738
+ index_id: string;
2739
+ }): Promise<void> {
2740
+ const index = this.indexes.get(input.index_id);
2741
+ if (!index || index.tenant_id !== input.tenant_id) {
2742
+ return;
2743
+ }
2744
+
2745
+ const files = this.filesByIndex.get(input.index_id) ?? [];
2746
+ for (const file of files) {
2747
+ this.chunksByFile.delete(file.file_id);
2748
+ }
2749
+ this.filesByIndex.set(input.index_id, []);
2750
+ }
2751
+
2752
+ async getLatestReadyIndex(input: { tenant_id: string; workspace_id: string }): Promise<ReadyIndex | undefined> {
2753
+ const indexIds = this.workspaceIndexes.get(input.workspace_id) ?? [];
2754
+ const readyIndexes = indexIds
2755
+ .map((id) => this.indexes.get(id))
2756
+ .filter((index): index is ReadyIndex => index !== undefined)
2757
+ .filter(
2758
+ (index) => index.tenant_id === input.tenant_id && index.workspace_id === input.workspace_id && index.status === "ready"
2759
+ );
2760
+
2761
+ readyIndexes.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
2762
+ return readyIndexes[0];
2763
+ }
2764
+
2765
+ async getFilesByIndex(input: { tenant_id: string; index_id: string }): Promise<PersistedFile[]> {
2766
+ const index = this.indexes.get(input.index_id);
2767
+ if (!index || index.tenant_id !== input.tenant_id) {
2768
+ return [];
2769
+ }
2770
+
2771
+ return [...(this.filesByIndex.get(input.index_id) ?? [])];
2772
+ }
2773
+
2774
+ async copyFileFromIndex(input: {
2775
+ tenant_id: string;
2776
+ source_index_id: string;
2777
+ target_index_id: string;
2778
+ repo_path: string;
2779
+ }): Promise<void> {
2780
+ const sourceFiles = this.filesByIndex.get(input.source_index_id) ?? [];
2781
+ const sourceFile = sourceFiles.find((file) => file.repo_path === input.repo_path);
2782
+ if (!sourceFile) {
2783
+ return;
2784
+ }
2785
+
2786
+ const targetFiles = this.filesByIndex.get(input.target_index_id) ?? [];
2787
+ const targetFile: PersistedFile = {
2788
+ file_id: `fil_${randomUUID()}`,
2789
+ repo_path: sourceFile.repo_path,
2790
+ content_hash: sourceFile.content_hash,
2791
+ language: sourceFile.language
2792
+ };
2793
+ targetFiles.push(targetFile);
2794
+ this.filesByIndex.set(input.target_index_id, targetFiles);
2795
+
2796
+ const sourceChunks = this.chunksByFile.get(sourceFile.file_id) ?? [];
2797
+ this.chunksByFile.set(
2798
+ targetFile.file_id,
2799
+ sourceChunks.map((chunk) => ({
2800
+ ...chunk,
2801
+ hash: sha256(`${targetFile.file_id}:${chunk.start_line}:${chunk.end_line}:${chunk.snippet}`)
2802
+ }))
2803
+ );
2804
+ }
2805
+
2806
+ async upsertFile(input: {
2807
+ tenant_id: string;
2808
+ index_id: string;
2809
+ repo_path: string;
2810
+ content_hash: string;
2811
+ size_bytes: number;
2812
+ language?: string;
2813
+ }): Promise<{ file_id: string }> {
2814
+ const files = this.filesByIndex.get(input.index_id) ?? [];
2815
+ const existing = files.find((file) => file.repo_path === input.repo_path);
2816
+ if (existing) {
2817
+ existing.content_hash = input.content_hash;
2818
+ existing.language = input.language;
2819
+ return { file_id: existing.file_id };
2820
+ }
2821
+
2822
+ const file_id = `fil_${randomUUID()}`;
2823
+ files.push({
2824
+ file_id,
2825
+ repo_path: input.repo_path,
2826
+ content_hash: input.content_hash,
2827
+ language: input.language
2828
+ });
2829
+ this.filesByIndex.set(input.index_id, files);
2830
+ return { file_id };
2831
+ }
2832
+
2833
+ async replaceFileChunks(input: {
2834
+ tenant_id: string;
2835
+ file_id: string;
2836
+ repo_path: string;
2837
+ chunks: Array<{
2838
+ start_line: number;
2839
+ end_line: number;
2840
+ snippet: string;
2841
+ embedding: number[];
2842
+ generated?: boolean;
2843
+ updated_at?: string;
2844
+ }>;
2845
+ }): Promise<void> {
2846
+ this.chunksByFile.set(
2847
+ input.file_id,
2848
+ input.chunks.map((chunk) => ({
2849
+ path: input.repo_path,
2850
+ start_line: chunk.start_line,
2851
+ end_line: chunk.end_line,
2852
+ snippet: chunk.snippet,
2853
+ generated: chunk.generated,
2854
+ updated_at: chunk.updated_at ?? new Date().toISOString(),
2855
+ hash: sha256(`${input.file_id}:${chunk.start_line}:${chunk.end_line}:${chunk.snippet}`),
2856
+ embedding: [...chunk.embedding]
2857
+ }))
2858
+ );
2859
+ }
2860
+
2861
+ async saveManifest(): Promise<void> {
2862
+ return;
2863
+ }
2864
+
2865
+ async saveIndexMetadata(input: {
2866
+ tenant_id: string;
2867
+ index_id: string;
2868
+ embedding_provider: string;
2869
+ embedding_model?: string;
2870
+ embedding_dimensions: number;
2871
+ embedding_version?: string;
2872
+ chunking_strategy: "language_aware" | "sliding";
2873
+ chunking_fallback_strategy: "sliding";
2874
+ }): Promise<void> {
2875
+ this.indexMetadata.set(input.index_id, {
2876
+ tenant_id: input.tenant_id,
2877
+ embedding_provider: input.embedding_provider,
2878
+ ...(input.embedding_model ? { embedding_model: input.embedding_model } : {}),
2879
+ embedding_dimensions: input.embedding_dimensions,
2880
+ ...(input.embedding_version ? { embedding_version: input.embedding_version } : {}),
2881
+ chunking_strategy: input.chunking_strategy,
2882
+ chunking_fallback_strategy: input.chunking_fallback_strategy,
2883
+ created_at: new Date().toISOString()
2884
+ });
2885
+ }
2886
+
2887
+ async getIndexMetadata(input: { tenant_id: string; index_id: string }): Promise<{
2888
+ embedding_provider: string;
2889
+ embedding_model?: string;
2890
+ embedding_dimensions: number;
2891
+ embedding_version?: string;
2892
+ chunking_strategy: "language_aware" | "sliding";
2893
+ chunking_fallback_strategy: "sliding";
2894
+ created_at: string;
2895
+ } | undefined> {
2896
+ const metadata = this.indexMetadata.get(input.index_id);
2897
+ if (!metadata || metadata.tenant_id !== input.tenant_id) {
2898
+ return undefined;
2899
+ }
2900
+ return {
2901
+ embedding_provider: metadata.embedding_provider,
2902
+ ...(metadata.embedding_model ? { embedding_model: metadata.embedding_model } : {}),
2903
+ embedding_dimensions: metadata.embedding_dimensions,
2904
+ ...(metadata.embedding_version ? { embedding_version: metadata.embedding_version } : {}),
2905
+ chunking_strategy: metadata.chunking_strategy,
2906
+ chunking_fallback_strategy: metadata.chunking_fallback_strategy,
2907
+ created_at: metadata.created_at
2908
+ };
2909
+ }
2910
+
2911
+ async listChunksByIndex(input: {
2912
+ tenant_id: string;
2913
+ index_id: string;
2914
+ filters?: {
2915
+ language?: string;
2916
+ path_prefix?: string;
2917
+ glob?: string;
2918
+ };
2919
+ }): Promise<
2920
+ Array<{
2921
+ chunk_id: string;
2922
+ file_id: string;
2923
+ path: string;
2924
+ start_line: number;
2925
+ end_line: number;
2926
+ snippet: string;
2927
+ language?: string;
2928
+ generated?: boolean;
2929
+ updated_at: string;
2930
+ embedding: number[];
2931
+ }>
2932
+ > {
2933
+ const index = this.indexes.get(input.index_id);
2934
+ if (!index || index.tenant_id !== input.tenant_id) {
2935
+ return [];
2936
+ }
2937
+
2938
+ const files = this.filesByIndex.get(input.index_id) ?? [];
2939
+ const globRegex = input.filters?.glob ? compileGlob(input.filters.glob) : undefined;
2940
+
2941
+ const output: Array<{
2942
+ chunk_id: string;
2943
+ file_id: string;
2944
+ path: string;
2945
+ start_line: number;
2946
+ end_line: number;
2947
+ snippet: string;
2948
+ language?: string;
2949
+ generated?: boolean;
2950
+ updated_at: string;
2951
+ embedding: number[];
2952
+ }> = [];
2953
+
2954
+ for (const file of files) {
2955
+ if (input.filters?.language && file.language !== input.filters.language) {
2956
+ continue;
2957
+ }
2958
+
2959
+ const chunks = this.chunksByFile.get(file.file_id) ?? [];
2960
+ for (const chunk of chunks) {
2961
+ if (input.filters?.path_prefix && !chunk.path.startsWith(input.filters.path_prefix)) {
2962
+ continue;
2963
+ }
2964
+ if (globRegex && !globRegex.test(chunk.path)) {
2965
+ continue;
2966
+ }
2967
+
2968
+ output.push({
2969
+ chunk_id: `chk_${sha256(`${file.file_id}:${chunk.hash}`)}`,
2970
+ file_id: file.file_id,
2971
+ path: chunk.path,
2972
+ start_line: chunk.start_line,
2973
+ end_line: chunk.end_line,
2974
+ snippet: chunk.snippet,
2975
+ ...(file.language ? { language: file.language } : {}),
2976
+ ...(chunk.generated ? { generated: chunk.generated } : {}),
2977
+ updated_at: chunk.updated_at,
2978
+ embedding: [...(chunk.embedding ?? pseudoEmbedding(chunk.snippet))]
2979
+ });
2980
+ }
2981
+ }
2982
+
2983
+ return output;
2984
+ }
2985
+ }
2986
+
2987
+ function compileGlob(glob: string): RegExp {
2988
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2989
+ const pattern = `^${escaped.replace(/\*/g, ".*").replace(/\?/g, ".")}$`;
2990
+ return new RegExp(pattern);
2991
+ }
2992
+
2993
+ export class RetrievalCore {
2994
+ private readonly cacheTtlSeconds: number;
2995
+ private readonly embeddingProvider: EmbeddingProvider;
2996
+ private readonly embeddingDescriptor: EmbeddingDescriptor;
2997
+ private readonly observability: Observability;
2998
+ private readonly scoringConfig: RetrievalScoringConfig;
2999
+ private readonly scoringProfileId: string;
3000
+ private readonly scoringConfigChecksum: string;
3001
+ private readonly enhancerConfig: RetrievalEnhancerConfig;
3002
+ private readonly chunkingConfig: RetrievalChunkingConfig;
3003
+ private readonly enhancerDecisionTraceEnabled: boolean;
3004
+ private cacheHits = 0;
3005
+ private cacheMisses = 0;
3006
+
3007
+ constructor(
3008
+ private readonly store: IndexRepository,
3009
+ private readonly cache: QueryCache,
3010
+ options?: RetrievalCoreOptions
3011
+ ) {
3012
+ this.cacheTtlSeconds = options?.cacheTtlSeconds ?? 60;
3013
+ this.embeddingProvider = options?.embeddingProvider ?? new DeterministicEmbeddingProvider();
3014
+ this.embeddingDescriptor = normalizeEmbeddingDescriptor(
3015
+ options?.embeddingDescriptor ?? resolveEmbeddingDescriptor(this.embeddingProvider)
3016
+ );
3017
+ this.observability = options?.observability ?? getObservability("retrieval-core");
3018
+ const baseProfile = resolveRetrievalScoringProfile(options?.scoringProfile);
3019
+ this.scoringConfig = mergeRetrievalScoringConfig(baseProfile.config, options?.scoringConfig);
3020
+ this.scoringProfileId = options?.scoringProfileId ?? baseProfile.profile_id;
3021
+ this.scoringConfigChecksum = scoringConfigChecksum(this.scoringConfig);
3022
+ this.enhancerConfig = mergeRetrievalEnhancerConfig(DEFAULT_RETRIEVAL_ENHANCER_CONFIG, options?.enhancerConfig);
3023
+ this.chunkingConfig = mergeRetrievalChunkingConfig(DEFAULT_RETRIEVAL_CHUNKING_CONFIG, options?.chunkingConfig);
3024
+ this.enhancerDecisionTraceEnabled = Boolean(options?.enhancerDecisionTraceEnabled);
3025
+ }
3026
+
3027
+ async indexArtifact(artifact: IndexUploadArtifact): Promise<IndexingReport> {
3028
+ const existingIndex = await this.store.getIndexByVersion({
3029
+ tenant_id: artifact.tenant_id,
3030
+ workspace_id: artifact.workspace_id,
3031
+ index_version: artifact.index_version
3032
+ });
3033
+
3034
+ if (existingIndex?.status === "ready") {
3035
+ return {
3036
+ workspace_id: artifact.workspace_id,
3037
+ index_version: artifact.index_version,
3038
+ status: "ready",
3039
+ counts: {
3040
+ added: 0,
3041
+ modified: 0,
3042
+ deleted: 0,
3043
+ unchanged: 0,
3044
+ skipped: 0
3045
+ },
3046
+ skipped_files: [],
3047
+ warnings: []
3048
+ };
3049
+ }
3050
+
3051
+ const previousReadyIndex = await this.store.getLatestReadyIndex({
3052
+ tenant_id: artifact.tenant_id,
3053
+ workspace_id: artifact.workspace_id
3054
+ });
3055
+
3056
+ const nextIndex = existingIndex
3057
+ ? existingIndex
3058
+ : await this.store.createIndexVersion({
3059
+ tenant_id: artifact.tenant_id,
3060
+ workspace_id: artifact.workspace_id,
3061
+ index_version: artifact.index_version,
3062
+ status: "indexing"
3063
+ });
3064
+
3065
+ if (existingIndex) {
3066
+ await this.store.markIndexStatus({
3067
+ tenant_id: artifact.tenant_id,
3068
+ workspace_id: artifact.workspace_id,
3069
+ index_id: existingIndex.index_id,
3070
+ status: "indexing"
3071
+ });
3072
+ await this.store.resetIndexContent({
3073
+ tenant_id: artifact.tenant_id,
3074
+ index_id: existingIndex.index_id
3075
+ });
3076
+ }
3077
+
3078
+ const normalizedFiles = artifact.files.map((file) => ({
3079
+ ...file,
3080
+ path: normalizePath(file.path)
3081
+ }));
3082
+
3083
+ const skipped: Array<{ path: string; reason: string }> = [];
3084
+ const warnings: IndexingWarning[] = [];
3085
+
3086
+ const candidateFiles = normalizedFiles.filter((file) => {
3087
+ const sizeBytes = Buffer.byteLength(file.content, "utf8");
3088
+ if (file.binary) {
3089
+ skipped.push({ path: file.path, reason: "binary file" });
3090
+ return false;
3091
+ }
3092
+ if (sizeBytes > MAX_FILE_SIZE_BYTES) {
3093
+ skipped.push({ path: file.path, reason: "max file size exceeded" });
3094
+ return false;
3095
+ }
3096
+
3097
+ const secretMatches = detectSecretMatches(file.content);
3098
+ if (secretMatches.length > 0) {
3099
+ skipped.push({ path: file.path, reason: `secret scan matched (${secretMatches.join(", ")})` });
3100
+ warnings.push({
3101
+ path: file.path,
3102
+ reason: `excluded by default secret scanner: ${secretMatches.join(", ")}`,
3103
+ category: "secret_exclusion"
3104
+ });
3105
+ return false;
3106
+ }
3107
+
3108
+ return true;
3109
+ });
3110
+
3111
+ const previousHashes = new Map<string, string>();
3112
+ if (previousReadyIndex) {
3113
+ const previousFiles = await this.store.getFilesByIndex({
3114
+ tenant_id: artifact.tenant_id,
3115
+ index_id: previousReadyIndex.index_id
3116
+ });
3117
+ for (const file of previousFiles) {
3118
+ previousHashes.set(file.repo_path, file.content_hash);
3119
+ }
3120
+ }
3121
+
3122
+ const nextHashes = new Map<string, string>();
3123
+ const changedFiles: RawFile[] = [];
3124
+
3125
+ let added = 0;
3126
+ let modified = 0;
3127
+ let unchanged = 0;
3128
+
3129
+ for (const file of candidateFiles) {
3130
+ const hash = sha256(file.content);
3131
+ nextHashes.set(file.path, hash);
3132
+ const old = previousHashes.get(file.path);
3133
+ if (!old) {
3134
+ added += 1;
3135
+ changedFiles.push(file);
3136
+ } else if (old !== hash) {
3137
+ modified += 1;
3138
+ changedFiles.push(file);
3139
+ } else {
3140
+ unchanged += 1;
3141
+ }
3142
+ }
3143
+
3144
+ let deleted = 0;
3145
+ for (const oldPath of previousHashes.keys()) {
3146
+ if (!nextHashes.has(oldPath)) {
3147
+ deleted += 1;
3148
+ }
3149
+ }
3150
+
3151
+ try {
3152
+ await this.store.saveIndexMetadata?.({
3153
+ tenant_id: artifact.tenant_id,
3154
+ index_id: nextIndex.index_id,
3155
+ embedding_provider: this.embeddingDescriptor.provider,
3156
+ embedding_model: this.embeddingDescriptor.model,
3157
+ embedding_dimensions: this.embeddingDescriptor.dimensions,
3158
+ embedding_version: this.embeddingDescriptor.version,
3159
+ chunking_strategy: this.chunkingConfig.strategy,
3160
+ chunking_fallback_strategy: this.chunkingConfig.fallback_strategy
3161
+ });
3162
+ this.observability.logger.info("index embedding metadata persisted", {
3163
+ tenant_id: artifact.tenant_id,
3164
+ workspace_id: artifact.workspace_id,
3165
+ index_id: nextIndex.index_id,
3166
+ embedding_provider: this.embeddingDescriptor.provider,
3167
+ embedding_model: this.embeddingDescriptor.model ?? "unknown",
3168
+ embedding_dimensions: this.embeddingDescriptor.dimensions
3169
+ });
3170
+
3171
+ if (previousReadyIndex) {
3172
+ for (const [path, hash] of nextHashes) {
3173
+ const old = previousHashes.get(path);
3174
+ if (old && old === hash) {
3175
+ await this.store.copyFileFromIndex({
3176
+ tenant_id: artifact.tenant_id,
3177
+ source_index_id: previousReadyIndex.index_id,
3178
+ target_index_id: nextIndex.index_id,
3179
+ repo_path: path
3180
+ });
3181
+ }
3182
+ }
3183
+ }
3184
+
3185
+ if (this.chunkingConfig.strategy === "language_aware") {
3186
+ const parserAvailabilitySnapshot = getChunkingParserAvailabilitySnapshot({
3187
+ enabled_languages: this.chunkingConfig.enabled_languages
3188
+ });
3189
+ for (const availability of parserAvailabilitySnapshot) {
3190
+ this.observability.metrics.gauge(
3191
+ "index_chunking_parser_availability",
3192
+ availability.status === "available" ? 1 : 0,
3193
+ {
3194
+ tenant_id: artifact.tenant_id,
3195
+ language: availability.language,
3196
+ status: availability.status
3197
+ }
3198
+ );
3199
+ }
3200
+ this.observability.logger.info("chunking parser availability snapshot", {
3201
+ tenant_id: artifact.tenant_id,
3202
+ workspace_id: artifact.workspace_id,
3203
+ index_id: nextIndex.index_id,
3204
+ snapshot: parserAvailabilitySnapshot
3205
+ });
3206
+ }
3207
+
3208
+ for (const file of changedFiles) {
3209
+ const chunkBuild = buildChunks(file, this.chunkingConfig);
3210
+ const chunks = chunkBuild.chunks;
3211
+ const chunkLanguage = chunkBuild.language ?? file.language ?? "unknown";
3212
+ this.observability.metrics.increment("index_chunking_strategy_total", 1, {
3213
+ tenant_id: artifact.tenant_id,
3214
+ strategy: chunkBuild.strategy,
3215
+ language: chunkLanguage,
3216
+ reason: chunkBuild.fallback_reason ?? "none"
3217
+ });
3218
+ if (chunkBuild.fallback_reason) {
3219
+ this.observability.metrics.increment("index_chunking_fallback_total", 1, {
3220
+ tenant_id: artifact.tenant_id,
3221
+ reason: chunkBuild.fallback_reason,
3222
+ language: chunkLanguage
3223
+ });
3224
+ }
3225
+ if (typeof chunkBuild.parse_latency_ms === "number") {
3226
+ this.observability.metrics.observe("index_chunk_parse_latency_ms", chunkBuild.parse_latency_ms, {
3227
+ tenant_id: artifact.tenant_id,
3228
+ language: chunkLanguage
3229
+ });
3230
+ }
3231
+ if (typeof chunkBuild.language_aware_attempt_latency_ms === "number") {
3232
+ this.observability.metrics.observe(
3233
+ "index_chunk_language_aware_attempt_latency_ms",
3234
+ chunkBuild.language_aware_attempt_latency_ms,
3235
+ {
3236
+ tenant_id: artifact.tenant_id,
3237
+ language: chunkLanguage,
3238
+ outcome: chunkBuild.fallback_reason ? "fallback" : "success"
3239
+ }
3240
+ );
3241
+ }
3242
+ if (typeof chunkBuild.fallback_path_latency_ms === "number" && chunkBuild.fallback_reason) {
3243
+ this.observability.metrics.observe("index_chunk_fallback_path_latency_ms", chunkBuild.fallback_path_latency_ms, {
3244
+ tenant_id: artifact.tenant_id,
3245
+ language: chunkLanguage,
3246
+ reason: chunkBuild.fallback_reason
3247
+ });
3248
+ }
3249
+ const estimatedEmbeddingTokens = chunks.reduce((sum, chunk) => sum + tokenize(chunk.snippet).length, 0);
3250
+ this.observability.metrics.increment("index_embedding_tokens_total", estimatedEmbeddingTokens, {
3251
+ tenant_id: artifact.tenant_id
3252
+ });
3253
+ const embeddings =
3254
+ chunks.length === 0
3255
+ ? []
3256
+ : await this.embeddingProvider.embed({
3257
+ texts: chunks.map((chunk) => chunk.snippet),
3258
+ purpose: "index"
3259
+ });
3260
+ if (embeddings.length !== chunks.length) {
3261
+ throw new RetrievalError(
3262
+ "UPSTREAM_FAILURE",
3263
+ `embedding provider returned ${embeddings.length} vectors for ${chunks.length} indexed chunks`
3264
+ );
3265
+ }
3266
+ const contentHash = sha256(file.content);
3267
+ const sizeBytes = Buffer.byteLength(file.content, "utf8");
3268
+ const chunkEmbeddings = chunks.map((chunk, index) => {
3269
+ const embedding = embeddings[index];
3270
+ if (!isFiniteNumberArray(embedding)) {
3271
+ throw new RetrievalError(
3272
+ "UPSTREAM_FAILURE",
3273
+ `embedding provider returned a non-numeric vector for ${file.path}:${chunk.start_line}-${chunk.end_line}`
3274
+ );
3275
+ }
3276
+ if (embedding.length !== this.embeddingDescriptor.dimensions) {
3277
+ this.observability.metrics.increment("index_embedding_dimension_mismatch_total", 1, {
3278
+ tenant_id: artifact.tenant_id,
3279
+ expected_dimensions: this.embeddingDescriptor.dimensions,
3280
+ actual_dimensions: embedding.length
3281
+ });
3282
+ this.observability.logger.warn("embedding dimensions mismatch provider descriptor; failing index build", {
3283
+ tenant_id: artifact.tenant_id,
3284
+ workspace_id: artifact.workspace_id,
3285
+ path: file.path,
3286
+ expected_dimensions: this.embeddingDescriptor.dimensions,
3287
+ actual_dimensions: embedding.length
3288
+ });
3289
+ throw new RetrievalError(
3290
+ "UPSTREAM_FAILURE",
3291
+ `embedding dimension mismatch for ${file.path}; expected ${this.embeddingDescriptor.dimensions}, received ${embedding.length}`
3292
+ );
3293
+ }
3294
+ return embedding;
3295
+ });
3296
+
3297
+ const fileRow = await this.store.upsertFile({
3298
+ tenant_id: artifact.tenant_id,
3299
+ index_id: nextIndex.index_id,
3300
+ repo_path: file.path,
3301
+ content_hash: contentHash,
3302
+ size_bytes: sizeBytes,
3303
+ language: file.language,
3304
+ updated_at: file.updated_at ?? new Date().toISOString()
3305
+ });
3306
+
3307
+ await this.store.replaceFileChunks({
3308
+ tenant_id: artifact.tenant_id,
3309
+ file_id: fileRow.file_id,
3310
+ repo_path: file.path,
3311
+ chunks: chunks.map((chunk, index) => ({
3312
+ start_line: chunk.start_line,
3313
+ end_line: chunk.end_line,
3314
+ snippet: chunk.snippet,
3315
+ embedding: chunkEmbeddings[index]!,
3316
+ generated: chunk.generated,
3317
+ updated_at: chunk.updated_at
3318
+ }))
3319
+ });
3320
+ }
3321
+
3322
+ if (artifact.manifest) {
3323
+ await this.store.saveManifest({
3324
+ index_id: nextIndex.index_id,
3325
+ object_key: artifact.manifest.object_key,
3326
+ checksum: artifact.manifest.checksum
3327
+ });
3328
+ }
3329
+
3330
+ await this.store.markIndexStatus({
3331
+ tenant_id: artifact.tenant_id,
3332
+ workspace_id: artifact.workspace_id,
3333
+ index_id: nextIndex.index_id,
3334
+ status: "ready"
3335
+ });
3336
+
3337
+ await this.cache.invalidateWorkspace(artifact.workspace_id);
3338
+
3339
+ return {
3340
+ workspace_id: artifact.workspace_id,
3341
+ index_version: artifact.index_version,
3342
+ status: "ready",
3343
+ counts: {
3344
+ added,
3345
+ modified,
3346
+ deleted,
3347
+ unchanged,
3348
+ skipped: skipped.length
3349
+ },
3350
+ skipped_files: skipped,
3351
+ warnings
3352
+ };
3353
+ } catch (error) {
3354
+ await this.store.markIndexStatus({
3355
+ tenant_id: artifact.tenant_id,
3356
+ workspace_id: artifact.workspace_id,
3357
+ index_id: nextIndex.index_id,
3358
+ status: "failed"
3359
+ });
3360
+
3361
+ throw error;
3362
+ }
3363
+ }
3364
+
3365
+ async indexArtifactDelta(artifact: IndexDeltaArtifact): Promise<IndexingReport> {
3366
+ const existingIndex = await this.store.getIndexByVersion({
3367
+ tenant_id: artifact.tenant_id,
3368
+ workspace_id: artifact.workspace_id,
3369
+ index_version: artifact.index_version
3370
+ });
3371
+
3372
+ if (existingIndex?.status === "ready") {
3373
+ return {
3374
+ workspace_id: artifact.workspace_id,
3375
+ index_version: artifact.index_version,
3376
+ status: "ready",
3377
+ counts: {
3378
+ added: 0,
3379
+ modified: 0,
3380
+ deleted: 0,
3381
+ unchanged: 0,
3382
+ skipped: 0
3383
+ },
3384
+ skipped_files: [],
3385
+ warnings: []
3386
+ };
3387
+ }
3388
+
3389
+ const baseIndex = artifact.base_index_version
3390
+ ? await this.store.getIndexByVersion({
3391
+ tenant_id: artifact.tenant_id,
3392
+ workspace_id: artifact.workspace_id,
3393
+ index_version: artifact.base_index_version
3394
+ })
3395
+ : await this.store.getLatestReadyIndex({
3396
+ tenant_id: artifact.tenant_id,
3397
+ workspace_id: artifact.workspace_id
3398
+ });
3399
+
3400
+ if (artifact.base_index_version && (!baseIndex || baseIndex.status !== "ready")) {
3401
+ throw new RetrievalError(
3402
+ "INVALID_ARGUMENT",
3403
+ `base index version ${artifact.base_index_version} is not ready for workspace ${artifact.workspace_id}`
3404
+ );
3405
+ }
3406
+
3407
+ const nextIndex = existingIndex
3408
+ ? existingIndex
3409
+ : await this.store.createIndexVersion({
3410
+ tenant_id: artifact.tenant_id,
3411
+ workspace_id: artifact.workspace_id,
3412
+ index_version: artifact.index_version,
3413
+ status: "indexing"
3414
+ });
3415
+
3416
+ if (existingIndex) {
3417
+ await this.store.markIndexStatus({
3418
+ tenant_id: artifact.tenant_id,
3419
+ workspace_id: artifact.workspace_id,
3420
+ index_id: existingIndex.index_id,
3421
+ status: "indexing"
3422
+ });
3423
+ await this.store.resetIndexContent({
3424
+ tenant_id: artifact.tenant_id,
3425
+ index_id: existingIndex.index_id
3426
+ });
3427
+ }
3428
+
3429
+ const dedupedUpsertFiles = new Map<string, RawFile>();
3430
+ for (const file of artifact.upsert_files) {
3431
+ const normalizedPath = normalizePath(file.path);
3432
+ dedupedUpsertFiles.set(normalizedPath, {
3433
+ ...file,
3434
+ path: normalizedPath
3435
+ });
3436
+ }
3437
+ const normalizedUpsertFiles = [...dedupedUpsertFiles.values()];
3438
+
3439
+ const skipped: Array<{ path: string; reason: string }> = [];
3440
+ const warnings: IndexingWarning[] = [];
3441
+ const candidateFiles = normalizedUpsertFiles.filter((file) => {
3442
+ const sizeBytes = Buffer.byteLength(file.content, "utf8");
3443
+ if (file.binary) {
3444
+ skipped.push({ path: file.path, reason: "binary file" });
3445
+ return false;
3446
+ }
3447
+ if (sizeBytes > MAX_FILE_SIZE_BYTES) {
3448
+ skipped.push({ path: file.path, reason: "max file size exceeded" });
3449
+ return false;
3450
+ }
3451
+
3452
+ const secretMatches = detectSecretMatches(file.content);
3453
+ if (secretMatches.length > 0) {
3454
+ skipped.push({ path: file.path, reason: `secret scan matched (${secretMatches.join(", ")})` });
3455
+ warnings.push({
3456
+ path: file.path,
3457
+ reason: `excluded by default secret scanner: ${secretMatches.join(", ")}`,
3458
+ category: "secret_exclusion"
3459
+ });
3460
+ return false;
3461
+ }
3462
+
3463
+ return true;
3464
+ });
3465
+
3466
+ const baseHashes = new Map<string, string>();
3467
+ if (baseIndex?.status === "ready") {
3468
+ const baseFiles = await this.store.getFilesByIndex({
3469
+ tenant_id: artifact.tenant_id,
3470
+ index_id: baseIndex.index_id
3471
+ });
3472
+ for (const file of baseFiles) {
3473
+ baseHashes.set(file.repo_path, file.content_hash);
3474
+ }
3475
+ }
3476
+
3477
+ const changedFiles: RawFile[] = [];
3478
+ let added = 0;
3479
+ let modified = 0;
3480
+
3481
+ for (const file of candidateFiles) {
3482
+ const hash = sha256(file.content);
3483
+ const old = baseHashes.get(file.path);
3484
+ if (!old) {
3485
+ added += 1;
3486
+ changedFiles.push(file);
3487
+ } else if (old !== hash) {
3488
+ modified += 1;
3489
+ changedFiles.push(file);
3490
+ }
3491
+ }
3492
+
3493
+ const changedPaths = new Set(changedFiles.map((file) => file.path));
3494
+ const deletedPaths = new Set(
3495
+ artifact.deleted_paths
3496
+ .map((path) => normalizePath(path))
3497
+ .filter((path) => baseHashes.has(path) && !changedPaths.has(path))
3498
+ );
3499
+
3500
+ const copyPaths: string[] = [];
3501
+ for (const path of baseHashes.keys()) {
3502
+ if (deletedPaths.has(path) || changedPaths.has(path)) {
3503
+ continue;
3504
+ }
3505
+ copyPaths.push(path);
3506
+ }
3507
+
3508
+ const unchanged = copyPaths.length;
3509
+ const deleted = deletedPaths.size;
3510
+
3511
+ try {
3512
+ await this.store.saveIndexMetadata?.({
3513
+ tenant_id: artifact.tenant_id,
3514
+ index_id: nextIndex.index_id,
3515
+ embedding_provider: this.embeddingDescriptor.provider,
3516
+ embedding_model: this.embeddingDescriptor.model,
3517
+ embedding_dimensions: this.embeddingDescriptor.dimensions,
3518
+ embedding_version: this.embeddingDescriptor.version,
3519
+ chunking_strategy: this.chunkingConfig.strategy,
3520
+ chunking_fallback_strategy: this.chunkingConfig.fallback_strategy
3521
+ });
3522
+ this.observability.logger.info("index embedding metadata persisted", {
3523
+ tenant_id: artifact.tenant_id,
3524
+ workspace_id: artifact.workspace_id,
3525
+ index_id: nextIndex.index_id,
3526
+ embedding_provider: this.embeddingDescriptor.provider,
3527
+ embedding_model: this.embeddingDescriptor.model ?? "unknown",
3528
+ embedding_dimensions: this.embeddingDescriptor.dimensions
3529
+ });
3530
+
3531
+ if (baseIndex?.status === "ready") {
3532
+ for (const path of copyPaths) {
3533
+ await this.store.copyFileFromIndex({
3534
+ tenant_id: artifact.tenant_id,
3535
+ source_index_id: baseIndex.index_id,
3536
+ target_index_id: nextIndex.index_id,
3537
+ repo_path: path
3538
+ });
3539
+ }
3540
+ }
3541
+
3542
+ if (this.chunkingConfig.strategy === "language_aware") {
3543
+ const parserAvailabilitySnapshot = getChunkingParserAvailabilitySnapshot({
3544
+ enabled_languages: this.chunkingConfig.enabled_languages
3545
+ });
3546
+ for (const availability of parserAvailabilitySnapshot) {
3547
+ this.observability.metrics.gauge(
3548
+ "index_chunking_parser_availability",
3549
+ availability.status === "available" ? 1 : 0,
3550
+ {
3551
+ tenant_id: artifact.tenant_id,
3552
+ language: availability.language,
3553
+ status: availability.status
3554
+ }
3555
+ );
3556
+ }
3557
+ this.observability.logger.info("chunking parser availability snapshot", {
3558
+ tenant_id: artifact.tenant_id,
3559
+ workspace_id: artifact.workspace_id,
3560
+ index_id: nextIndex.index_id,
3561
+ snapshot: parserAvailabilitySnapshot
3562
+ });
3563
+ }
3564
+
3565
+ for (const file of changedFiles) {
3566
+ const chunkBuild = buildChunks(file, this.chunkingConfig);
3567
+ const chunks = chunkBuild.chunks;
3568
+ const chunkLanguage = chunkBuild.language ?? file.language ?? "unknown";
3569
+ this.observability.metrics.increment("index_chunking_strategy_total", 1, {
3570
+ tenant_id: artifact.tenant_id,
3571
+ strategy: chunkBuild.strategy,
3572
+ language: chunkLanguage,
3573
+ reason: chunkBuild.fallback_reason ?? "none"
3574
+ });
3575
+ if (chunkBuild.fallback_reason) {
3576
+ this.observability.metrics.increment("index_chunking_fallback_total", 1, {
3577
+ tenant_id: artifact.tenant_id,
3578
+ reason: chunkBuild.fallback_reason,
3579
+ language: chunkLanguage
3580
+ });
3581
+ }
3582
+ if (typeof chunkBuild.parse_latency_ms === "number") {
3583
+ this.observability.metrics.observe("index_chunk_parse_latency_ms", chunkBuild.parse_latency_ms, {
3584
+ tenant_id: artifact.tenant_id,
3585
+ language: chunkLanguage
3586
+ });
3587
+ }
3588
+ if (typeof chunkBuild.language_aware_attempt_latency_ms === "number") {
3589
+ this.observability.metrics.observe(
3590
+ "index_chunk_language_aware_attempt_latency_ms",
3591
+ chunkBuild.language_aware_attempt_latency_ms,
3592
+ {
3593
+ tenant_id: artifact.tenant_id,
3594
+ language: chunkLanguage,
3595
+ outcome: chunkBuild.fallback_reason ? "fallback" : "success"
3596
+ }
3597
+ );
3598
+ }
3599
+ if (typeof chunkBuild.fallback_path_latency_ms === "number" && chunkBuild.fallback_reason) {
3600
+ this.observability.metrics.observe("index_chunk_fallback_path_latency_ms", chunkBuild.fallback_path_latency_ms, {
3601
+ tenant_id: artifact.tenant_id,
3602
+ language: chunkLanguage,
3603
+ reason: chunkBuild.fallback_reason
3604
+ });
3605
+ }
3606
+ const estimatedEmbeddingTokens = chunks.reduce((sum, chunk) => sum + tokenize(chunk.snippet).length, 0);
3607
+ this.observability.metrics.increment("index_embedding_tokens_total", estimatedEmbeddingTokens, {
3608
+ tenant_id: artifact.tenant_id
3609
+ });
3610
+ const embeddings =
3611
+ chunks.length === 0
3612
+ ? []
3613
+ : await this.embeddingProvider.embed({
3614
+ texts: chunks.map((chunk) => chunk.snippet),
3615
+ purpose: "index"
3616
+ });
3617
+ if (embeddings.length !== chunks.length) {
3618
+ throw new RetrievalError(
3619
+ "UPSTREAM_FAILURE",
3620
+ `embedding provider returned ${embeddings.length} vectors for ${chunks.length} indexed chunks`
3621
+ );
3622
+ }
3623
+ const contentHash = sha256(file.content);
3624
+ const sizeBytes = Buffer.byteLength(file.content, "utf8");
3625
+ const chunkEmbeddings = chunks.map((chunk, index) => {
3626
+ const embedding = embeddings[index];
3627
+ if (!isFiniteNumberArray(embedding)) {
3628
+ throw new RetrievalError(
3629
+ "UPSTREAM_FAILURE",
3630
+ `embedding provider returned a non-numeric vector for ${file.path}:${chunk.start_line}-${chunk.end_line}`
3631
+ );
3632
+ }
3633
+ if (embedding.length !== this.embeddingDescriptor.dimensions) {
3634
+ this.observability.metrics.increment("index_embedding_dimension_mismatch_total", 1, {
3635
+ tenant_id: artifact.tenant_id,
3636
+ expected_dimensions: this.embeddingDescriptor.dimensions,
3637
+ actual_dimensions: embedding.length
3638
+ });
3639
+ this.observability.logger.warn("embedding dimensions mismatch provider descriptor; failing index build", {
3640
+ tenant_id: artifact.tenant_id,
3641
+ workspace_id: artifact.workspace_id,
3642
+ path: file.path,
3643
+ expected_dimensions: this.embeddingDescriptor.dimensions,
3644
+ actual_dimensions: embedding.length
3645
+ });
3646
+ throw new RetrievalError(
3647
+ "UPSTREAM_FAILURE",
3648
+ `embedding dimension mismatch for ${file.path}; expected ${this.embeddingDescriptor.dimensions}, received ${embedding.length}`
3649
+ );
3650
+ }
3651
+ return embedding;
3652
+ });
3653
+
3654
+ const fileRow = await this.store.upsertFile({
3655
+ tenant_id: artifact.tenant_id,
3656
+ index_id: nextIndex.index_id,
3657
+ repo_path: file.path,
3658
+ content_hash: contentHash,
3659
+ size_bytes: sizeBytes,
3660
+ language: file.language,
3661
+ updated_at: file.updated_at ?? new Date().toISOString()
3662
+ });
3663
+
3664
+ await this.store.replaceFileChunks({
3665
+ tenant_id: artifact.tenant_id,
3666
+ file_id: fileRow.file_id,
3667
+ repo_path: file.path,
3668
+ chunks: chunks.map((chunk, index) => ({
3669
+ start_line: chunk.start_line,
3670
+ end_line: chunk.end_line,
3671
+ snippet: chunk.snippet,
3672
+ embedding: chunkEmbeddings[index]!,
3673
+ generated: chunk.generated,
3674
+ updated_at: chunk.updated_at
3675
+ }))
3676
+ });
3677
+ }
3678
+
3679
+ await this.store.markIndexStatus({
3680
+ tenant_id: artifact.tenant_id,
3681
+ workspace_id: artifact.workspace_id,
3682
+ index_id: nextIndex.index_id,
3683
+ status: "ready"
3684
+ });
3685
+
3686
+ await this.cache.invalidateWorkspace(artifact.workspace_id);
3687
+
3688
+ return {
3689
+ workspace_id: artifact.workspace_id,
3690
+ index_version: artifact.index_version,
3691
+ status: "ready",
3692
+ counts: {
3693
+ added,
3694
+ modified,
3695
+ deleted,
3696
+ unchanged,
3697
+ skipped: skipped.length
3698
+ },
3699
+ skipped_files: skipped,
3700
+ warnings
3701
+ };
3702
+ } catch (error) {
3703
+ await this.store.markIndexStatus({
3704
+ tenant_id: artifact.tenant_id,
3705
+ workspace_id: artifact.workspace_id,
3706
+ index_id: nextIndex.index_id,
3707
+ status: "failed"
3708
+ });
3709
+
3710
+ throw error;
3711
+ }
3712
+ }
3713
+
3714
+ async getIndexVersion(input: {
3715
+ tenant_id: string;
3716
+ workspace_id: string;
3717
+ index_version: string;
3718
+ }): Promise<
3719
+ | {
3720
+ index_id: string;
3721
+ status: "indexing" | "ready" | "failed";
3722
+ }
3723
+ | undefined
3724
+ > {
3725
+ const existing = await this.store.getIndexByVersion({
3726
+ tenant_id: input.tenant_id,
3727
+ workspace_id: input.workspace_id,
3728
+ index_version: input.index_version
3729
+ });
3730
+ if (!existing) {
3731
+ return undefined;
3732
+ }
3733
+ return {
3734
+ index_id: existing.index_id,
3735
+ status: existing.status
3736
+ };
3737
+ }
3738
+
3739
+ async searchContext(input: {
3740
+ trace_id: string;
3741
+ tenant_id: string;
3742
+ workspace_id: string;
3743
+ request: SearchContextInput;
3744
+ }): Promise<SearchContextOutput> {
3745
+ const searchStartedAt = Date.now();
3746
+ const index = await this.store.getLatestReadyIndex({
3747
+ tenant_id: input.tenant_id,
3748
+ workspace_id: input.workspace_id
3749
+ });
3750
+
3751
+ if (!index) {
3752
+ throw new RetrievalError("NOT_FOUND", "No ready index found for workspace. Run initial batch-upload push.");
3753
+ }
3754
+
3755
+ const indexMetadata = await this.store.getIndexMetadata?.({
3756
+ tenant_id: input.tenant_id,
3757
+ index_id: index.index_id
3758
+ });
3759
+
3760
+ const topK = Math.min(input.request.top_k ?? 8, MAX_TOP_K);
3761
+ const candidatePoolTopK = Math.min(MAX_TOP_K, Math.max(topK * 4, 12));
3762
+ const query = normalizeQuery(input.request.query);
3763
+
3764
+ if (!indexMetadata) {
3765
+ this.observability.metrics.increment("retrieval_embedding_metadata_mismatch_total", 1, {
3766
+ reason: "metadata_missing"
3767
+ });
3768
+ this.observability.logger.warn("index metadata missing embedding configuration; reindex required", {
3769
+ tenant_id: input.tenant_id,
3770
+ workspace_id: input.workspace_id,
3771
+ index_id: index.index_id,
3772
+ reindex_required: true
3773
+ });
3774
+ throw new RetrievalError(
3775
+ "INVALID_ARGUMENT",
3776
+ "index embedding metadata missing; reindex required with current embedding model and dimensions."
3777
+ );
3778
+ }
3779
+
3780
+ const expectedProvider = this.embeddingDescriptor.provider;
3781
+ const expectedModel = this.embeddingDescriptor.model ?? "";
3782
+ const expectedDimensions = this.embeddingDescriptor.dimensions;
3783
+ const actualProvider = indexMetadata.embedding_provider;
3784
+ const actualModel = indexMetadata.embedding_model ?? "";
3785
+ const actualDimensions = indexMetadata.embedding_dimensions;
3786
+
3787
+ let mismatchReason: "provider_mismatch" | "model_mismatch" | "dimension_mismatch" | undefined;
3788
+ if (actualProvider !== expectedProvider) {
3789
+ mismatchReason = "provider_mismatch";
3790
+ } else if (actualModel !== expectedModel) {
3791
+ mismatchReason = "model_mismatch";
3792
+ } else if (actualDimensions !== expectedDimensions) {
3793
+ mismatchReason = "dimension_mismatch";
3794
+ }
3795
+
3796
+ if (mismatchReason) {
3797
+ this.observability.metrics.increment("retrieval_embedding_metadata_mismatch_total", 1, {
3798
+ reason: mismatchReason
3799
+ });
3800
+ this.observability.logger.warn("embedding compatibility mismatch detected; reindex required", {
3801
+ tenant_id: input.tenant_id,
3802
+ workspace_id: input.workspace_id,
3803
+ index_id: index.index_id,
3804
+ reason: mismatchReason,
3805
+ expected_provider: expectedProvider,
3806
+ expected_model: expectedModel || "unknown",
3807
+ expected_dimensions: expectedDimensions,
3808
+ actual_provider: actualProvider,
3809
+ actual_model: actualModel || "unknown",
3810
+ actual_dimensions: actualDimensions,
3811
+ reindex_required: true
3812
+ });
3813
+ throw new RetrievalError(
3814
+ "INVALID_ARGUMENT",
3815
+ "embedding configuration mismatch; reindex required with current embedding model and dimensions."
3816
+ );
3817
+ }
3818
+
3819
+ const queryEmbeddings = await this.embeddingProvider.embed({
3820
+ texts: [query],
3821
+ purpose: "query"
3822
+ });
3823
+ if (queryEmbeddings.length !== 1 || !isFiniteNumberArray(queryEmbeddings[0])) {
3824
+ throw new RetrievalError("UPSTREAM_FAILURE", "embedding provider returned an invalid query embedding response");
3825
+ }
3826
+ const queryEmbedding = queryEmbeddings[0];
3827
+ if (queryEmbedding.length !== this.embeddingDescriptor.dimensions) {
3828
+ throw new RetrievalError(
3829
+ "UPSTREAM_FAILURE",
3830
+ `embedding provider returned query embedding dimensions ${queryEmbedding.length}; expected ${this.embeddingDescriptor.dimensions}`
3831
+ );
3832
+ }
3833
+ const queryTokens = tokenize(query);
3834
+
3835
+ const cacheKey = buildQueryCacheKey({
3836
+ workspace_id: input.workspace_id,
3837
+ index_version: index.index_version,
3838
+ query,
3839
+ top_k: topK,
3840
+ filters: input.request.filters
3841
+ });
3842
+
3843
+ const cached = await this.cache.get(cacheKey);
3844
+ if (cached) {
3845
+ this.cacheHits += 1;
3846
+ const ratio = this.cacheHits / Math.max(1, this.cacheHits + this.cacheMisses);
3847
+ this.observability.metrics.gauge("retrieval_cache_hit_ratio", ratio, {});
3848
+ return { ...cached, trace_id: input.trace_id };
3849
+ }
3850
+ this.cacheMisses += 1;
3851
+ const ratio = this.cacheHits / Math.max(1, this.cacheHits + this.cacheMisses);
3852
+ this.observability.metrics.gauge("retrieval_cache_hit_ratio", ratio, {});
3853
+
3854
+ const candidates = await this.observability.tracing.withSpan(
3855
+ "retrieval.candidate_generation",
3856
+ {
3857
+ trace_id: input.trace_id,
3858
+ tenant_id: input.tenant_id,
3859
+ workspace_id: input.workspace_id
3860
+ },
3861
+ async () => {
3862
+ let ranked: RankedChunkCandidate[] | undefined;
3863
+ if (this.store.rankChunksByIndex) {
3864
+ ranked = await this.store.rankChunksByIndex({
3865
+ tenant_id: input.tenant_id,
3866
+ index_id: index.index_id,
3867
+ query,
3868
+ query_embedding: queryEmbedding,
3869
+ query_tokens: queryTokens,
3870
+ top_k: candidatePoolTopK,
3871
+ candidate_weights: this.scoringConfig.candidate_weights,
3872
+ filters: input.request.filters
3873
+ });
3874
+ }
3875
+
3876
+ const output =
3877
+ ranked && ranked.length > 0
3878
+ ? ranked
3879
+ .map((candidate) => {
3880
+ let score = candidate.score;
3881
+ score += pathQualityBias(candidate.path, queryTokens, this.scoringConfig, query);
3882
+ if (looksLowInformation(candidate.snippet)) {
3883
+ score -= this.scoringConfig.rerank.low_information_penalty;
3884
+ }
3885
+ const reason = chooseReason({
3886
+ lexical: candidate.lexical_score,
3887
+ path_match: candidate.path_match,
3888
+ recency_boosted: candidate.recency_boosted
3889
+ });
3890
+ return {
3891
+ path: candidate.path,
3892
+ start_line: candidate.start_line,
3893
+ end_line: candidate.end_line,
3894
+ snippet: candidate.snippet,
3895
+ score,
3896
+ reason
3897
+ };
3898
+ })
3899
+ .filter((candidate) => candidate.end_line >= candidate.start_line)
3900
+ .sort((a, b) => b.score - a.score)
3901
+ : (
3902
+ await this.store.listChunksByIndex({
3903
+ tenant_id: input.tenant_id,
3904
+ index_id: index.index_id,
3905
+ filters: input.request.filters
3906
+ })
3907
+ )
3908
+ .map((chunk) => {
3909
+ const haystack = `${chunk.path}\n${chunk.snippet}`;
3910
+ const l = lexicalScore(query, haystack);
3911
+ const v = cosineSimilarity(queryEmbedding, chunk.embedding);
3912
+ const pathMatch = queryTokens.some((token) => chunk.path.toLowerCase().includes(token));
3913
+ const recencyBoost = Date.now() - new Date(chunk.updated_at).getTime() < 14 * 24 * 3600 * 1000;
3914
+ const candidateWeights = this.scoringConfig.candidate_weights;
3915
+
3916
+ let score = l * candidateWeights.lexical_weight + v * candidateWeights.vector_weight;
3917
+ if (pathMatch) {
3918
+ score += candidateWeights.path_match_boost;
3919
+ }
3920
+ if (recencyBoost) {
3921
+ score += candidateWeights.recency_boost;
3922
+ }
3923
+ if (chunk.generated) {
3924
+ score -= candidateWeights.generated_penalty;
3925
+ }
3926
+ score += pathQualityBias(chunk.path, queryTokens, this.scoringConfig, query);
3927
+ if (looksLowInformation(chunk.snippet)) {
3928
+ score -= this.scoringConfig.rerank.low_information_penalty;
3929
+ }
3930
+
3931
+ const reason = chooseReason({ lexical: l, path_match: pathMatch, recency_boosted: recencyBoost });
3932
+
3933
+ return {
3934
+ path: chunk.path,
3935
+ start_line: chunk.start_line,
3936
+ end_line: chunk.end_line,
3937
+ snippet: chunk.snippet,
3938
+ score,
3939
+ reason
3940
+ };
3941
+ })
3942
+ .filter((candidate) => candidate.end_line >= candidate.start_line)
3943
+ .sort((a, b) => b.score - a.score);
3944
+
3945
+ this.observability.metrics.observe("retrieval_candidates_count", output.length, {
3946
+ channel: "hybrid",
3947
+ retrieval_profile_id: this.scoringProfileId
3948
+ });
3949
+ return output;
3950
+ }
3951
+ );
3952
+
3953
+ const deduped = await this.observability.tracing.withSpan("retrieval.rerank", { trace_id: input.trace_id }, async () => {
3954
+ const output: SearchContextOutput["results"] = [];
3955
+ const seen = new Set<string>();
3956
+ const pathCounts = new Map<string, number>();
3957
+ const directoryCounts = new Map<string, number>();
3958
+ const extensionCounts = new Map<string, number>();
3959
+ const maxChunksPerPath = hasFileLookupIntent(queryTokens)
3960
+ ? this.scoringConfig.rerank.max_chunks_per_path_file_lookup
3961
+ : this.scoringConfig.rerank.max_chunks_per_path_default;
3962
+
3963
+ const available = [...candidates];
3964
+ while (output.length < topK && available.length > 0) {
3965
+ let bestIndex = -1;
3966
+ let bestAdjustedScore = Number.NEGATIVE_INFINITY;
3967
+ let bestRawScore = Number.NEGATIVE_INFINITY;
3968
+
3969
+ for (let i = 0; i < available.length; i += 1) {
3970
+ const candidate = available[i];
3971
+ if (!candidate) {
3972
+ continue;
3973
+ }
3974
+ const key = `${candidate.path}:${candidate.start_line}:${candidate.end_line}`;
3975
+ if (seen.has(key)) {
3976
+ continue;
3977
+ }
3978
+
3979
+ const pathCount = pathCounts.get(candidate.path) ?? 0;
3980
+ if (pathCount >= maxChunksPerPath) {
3981
+ continue;
3982
+ }
3983
+
3984
+ const directoryKey = parentDirectory(candidate.path).toLowerCase();
3985
+ const extensionKey = fileExtension(candidate.path);
3986
+ const adjustedScore =
3987
+ candidate.score -
3988
+ (directoryCounts.get(directoryKey) ?? 0) * this.scoringConfig.rerank.same_directory_penalty -
3989
+ (extensionCounts.get(extensionKey) ?? 0) * this.scoringConfig.rerank.same_extension_penalty;
3990
+
3991
+ const currentBest = bestIndex >= 0 ? available[bestIndex] : undefined;
3992
+ const isBetter =
3993
+ adjustedScore > bestAdjustedScore + 1e-9 ||
3994
+ (Math.abs(adjustedScore - bestAdjustedScore) <= 1e-9 &&
3995
+ (candidate.score > bestRawScore + 1e-9 ||
3996
+ (Math.abs(candidate.score - bestRawScore) <= 1e-9 &&
3997
+ (!currentBest ||
3998
+ candidate.path.localeCompare(currentBest.path) < 0 ||
3999
+ (candidate.path === currentBest.path &&
4000
+ (candidate.start_line < currentBest.start_line ||
4001
+ (candidate.start_line === currentBest.start_line &&
4002
+ candidate.end_line < currentBest.end_line)))))));
4003
+
4004
+ if (isBetter) {
4005
+ bestAdjustedScore = adjustedScore;
4006
+ bestRawScore = candidate.score;
4007
+ bestIndex = i;
4008
+ }
4009
+ }
4010
+
4011
+ if (bestIndex < 0) {
4012
+ break;
4013
+ }
4014
+
4015
+ const selected = available.splice(bestIndex, 1)[0];
4016
+ if (!selected) {
4017
+ break;
4018
+ }
4019
+ const selectedKey = `${selected.path}:${selected.start_line}:${selected.end_line}`;
4020
+ seen.add(selectedKey);
4021
+ pathCounts.set(selected.path, (pathCounts.get(selected.path) ?? 0) + 1);
4022
+ const selectedDirectory = parentDirectory(selected.path).toLowerCase();
4023
+ const selectedExtension = fileExtension(selected.path);
4024
+ directoryCounts.set(selectedDirectory, (directoryCounts.get(selectedDirectory) ?? 0) + 1);
4025
+ extensionCounts.set(selectedExtension, (extensionCounts.get(selectedExtension) ?? 0) + 1);
4026
+ output.push(selected);
4027
+ }
4028
+ return output;
4029
+ });
4030
+
4031
+ const output: SearchContextOutput = {
4032
+ trace_id: input.trace_id,
4033
+ results: deduped,
4034
+ search_metadata: {
4035
+ latency_ms: Date.now() - searchStartedAt,
4036
+ retrieval_mode: "hybrid" satisfies RetrievalMode,
4037
+ index_version: index.index_version
4038
+ }
4039
+ };
4040
+
4041
+ this.observability.metrics.observe("retrieval_topk_hit_proxy", deduped.length > 0 ? 1 : 0, {
4042
+ retrieval_profile_id: this.scoringProfileId
4043
+ });
4044
+ this.observability.logger.info("search_context completed", {
4045
+ trace_id: input.trace_id,
4046
+ tenant_id: input.tenant_id,
4047
+ workspace_id: input.workspace_id,
4048
+ latency_ms: output.search_metadata.latency_ms,
4049
+ result_count: output.results.length,
4050
+ retrieval_profile_id: this.scoringProfileId,
4051
+ retrieval_profile_checksum: this.scoringConfigChecksum
4052
+ });
4053
+
4054
+ await this.cache.set(cacheKey, output, this.cacheTtlSeconds);
4055
+ return output;
4056
+ }
4057
+
4058
+ async enhancePrompt(input: {
4059
+ trace_id: string;
4060
+ tenant_id: string;
4061
+ workspace_id?: string;
4062
+ request: EnhancePromptInput;
4063
+ }): Promise<EnhancePromptOutput> {
4064
+ const startedAt = Date.now();
4065
+ const warnings: string[] = [];
4066
+ const questions: string[] = [];
4067
+ const addQuestion = (value: string): void => {
4068
+ if (!questions.includes(value)) {
4069
+ questions.push(value);
4070
+ }
4071
+ };
4072
+
4073
+ const intent = classifyIntent(input.request.prompt);
4074
+ const queryIntent = classifyEnhancerQueryIntent(input.request.prompt, input.request.conversation_history);
4075
+ const language = detectDominantLanguage(input.request.prompt, input.request.conversation_history);
4076
+ const negativePreferences = detectNegativePathPreferences(
4077
+ `${input.request.prompt}\n${input.request.conversation_history.map((entry) => entry.content).join("\n")}`
4078
+ );
4079
+ const intentPolicy = resolveEnhancerIntentPolicy({
4080
+ query_intent: queryIntent,
4081
+ enhancer_config: this.enhancerConfig,
4082
+ negative_preferences: negativePreferences
4083
+ });
4084
+ const rerankTimeoutMs =
4085
+ queryIntent === "symbol-heavy"
4086
+ ? Math.max(20, Math.floor(this.enhancerConfig.rerank_timeout_ms * 0.75))
4087
+ : this.enhancerConfig.rerank_timeout_ms;
4088
+
4089
+ let searchResults: SearchContextOutput["results"] = [];
4090
+ let retrievalQuery = "";
4091
+ let lowConfidenceTriggered = false;
4092
+ let fallbackTriggered = false;
4093
+ let fallbackReason: string | null = null;
4094
+ let confidenceSignals: EnhancerConfidenceSignals | undefined;
4095
+ let queryExpansionMs = 0;
4096
+ let expandedHintCount = 0;
4097
+ let candidateCountPreRerank = 0;
4098
+ let candidateCountPostRerank = 0;
4099
+ let rerankMs = 0;
4100
+ let strongAnchorBypassUsed = false;
4101
+ let rerankUsed = false;
4102
+
4103
+ if (!input.request.project_root_path || !input.workspace_id) {
4104
+ warnings.push("Workspace context missing. Running text-only enhancement mode.");
4105
+ fallbackTriggered = true;
4106
+ fallbackReason = "workspace_context_missing";
4107
+ } else {
4108
+ try {
4109
+ const expansionStartedAt = Date.now();
4110
+ const expandedQuery = buildEnhancerRetrievalQuery(input.request.prompt, input.request.conversation_history, {
4111
+ maxExpansionHints: intentPolicy.max_expansion_hints,
4112
+ queryIntent
4113
+ });
4114
+ queryExpansionMs = Date.now() - expansionStartedAt;
4115
+ retrievalQuery = expandedQuery.query;
4116
+ expandedHintCount = expandedQuery.expanded_hint_count;
4117
+ const retrieval = await this.searchContext({
4118
+ trace_id: input.trace_id,
4119
+ tenant_id: input.tenant_id,
4120
+ workspace_id: input.workspace_id,
4121
+ request: {
4122
+ project_root_path: input.request.project_root_path,
4123
+ query: retrievalQuery,
4124
+ top_k: MAX_TOP_K
4125
+ }
4126
+ });
4127
+ const budgetedResults = trimToContextBudget(retrieval.results);
4128
+ const dedupedByPath = dedupeEnhancerCandidatesByPath(budgetedResults);
4129
+ const collapsedByDirectory = collapseEnhancerCandidatesByDirectory(
4130
+ dedupedByPath,
4131
+ intentPolicy.max_candidates_per_directory_pre_rerank
4132
+ );
4133
+ const filteredCandidates = applyEnhancerIntentPathFiltering(collapsedByDirectory, {
4134
+ intent,
4135
+ negative_preferences: negativePreferences,
4136
+ strict_impl_only_filtering: intentPolicy.strict_impl_only_filtering
4137
+ });
4138
+ searchResults = filteredCandidates.slice(0, intentPolicy.max_candidates_pre_rerank);
4139
+ candidateCountPreRerank = searchResults.length;
4140
+
4141
+ const shouldSkipRerankForStrongAnchors = shouldBypassEnhancerRerankForAnchors({
4142
+ query_intent: queryIntent,
4143
+ prompt: input.request.prompt,
4144
+ history: input.request.conversation_history,
4145
+ results: searchResults,
4146
+ negative_preferences: negativePreferences
4147
+ });
4148
+ strongAnchorBypassUsed = shouldSkipRerankForStrongAnchors;
4149
+
4150
+ confidenceSignals = evaluateEnhancerConfidence({
4151
+ prompt: input.request.prompt,
4152
+ retrieval_query: retrievalQuery,
4153
+ query_intent: queryIntent,
4154
+ results: filteredCandidates.slice(0, Math.max(intentPolicy.max_candidates_pre_rerank * 2, 6))
4155
+ });
4156
+
4157
+ if (!shouldSkipRerankForStrongAnchors && confidenceSignals.low_confidence) {
4158
+ lowConfidenceTriggered = true;
4159
+ rerankUsed = true;
4160
+ warnings.push(ENHANCER_LOW_CONFIDENCE_WARNING);
4161
+
4162
+ const rerankStartedAt = Date.now();
4163
+ let reranked: SearchContextOutput["results"];
4164
+ try {
4165
+ reranked = await runWithTimeout({
4166
+ timeout_ms: rerankTimeoutMs,
4167
+ fn: () =>
4168
+ rankEnhancerResultsForConfidence({
4169
+ results: searchResults,
4170
+ intent,
4171
+ query_intent: queryIntent,
4172
+ negative_preferences: negativePreferences,
4173
+ prompt: input.request.prompt,
4174
+ retrieval_query: retrievalQuery
4175
+ })
4176
+ });
4177
+ } catch {
4178
+ fallbackTriggered = true;
4179
+ fallbackReason = "rerank_timeout";
4180
+ warnings.push("Enhancer rerank timeout; applied deterministic fallback ranking.");
4181
+ reranked = deterministicEnhancerFallbackRanking({
4182
+ results: searchResults,
4183
+ intent,
4184
+ negative_preferences: negativePreferences
4185
+ });
4186
+ } finally {
4187
+ rerankMs = Date.now() - rerankStartedAt;
4188
+ }
4189
+
4190
+ searchResults = dedupeEnhancerCandidatesByPath(
4191
+ applyEnhancerIntentPathFiltering(reranked, {
4192
+ intent,
4193
+ negative_preferences: negativePreferences,
4194
+ strict_impl_only_filtering: intentPolicy.strict_impl_only_filtering
4195
+ })
4196
+ );
4197
+ searchResults = collapseEnhancerCandidatesByDirectory(
4198
+ searchResults,
4199
+ intentPolicy.max_candidates_per_directory_pre_rerank
4200
+ ).slice(0, intentPolicy.max_candidates_pre_rerank);
4201
+
4202
+ const symbolCandidates = extractLikelyCodeSymbols(
4203
+ `${input.request.prompt}\n${input.request.conversation_history.map((entry) => entry.content).join("\n")}`,
4204
+ 3
4205
+ );
4206
+ if (confidenceSignals.failed_signals.includes("score_spread")) {
4207
+ addQuestion(localizeLowConfidenceQuestion({ language, kind: "scope" }));
4208
+ }
4209
+ if (confidenceSignals.failed_signals.includes("token_overlap")) {
4210
+ addQuestion(localizeLowConfidenceQuestion({ language, kind: "symbol", symbol: symbolCandidates[0] }));
4211
+ }
4212
+ if (confidenceSignals.failed_signals.includes("path_diversity")) {
4213
+ addQuestion(localizeLowConfidenceQuestion({ language, kind: "source_priority" }));
4214
+ }
4215
+ } else {
4216
+ searchResults = dedupeEnhancerCandidatesByPath(searchResults);
4217
+ searchResults = collapseEnhancerCandidatesByDirectory(
4218
+ searchResults,
4219
+ intentPolicy.max_candidates_per_directory_pre_rerank
4220
+ ).slice(0, intentPolicy.max_candidates_pre_rerank);
4221
+ }
4222
+ candidateCountPostRerank = searchResults.length;
4223
+ } catch (error) {
4224
+ warnings.push("Context retrieval unavailable; enhancement generated with limited confidence.");
4225
+ fallbackTriggered = true;
4226
+ fallbackReason = "context_retrieval_unavailable";
4227
+ if (error instanceof RetrievalError && error.code === "NOT_FOUND") {
4228
+ warnings.push("No ready index found for workspace.");
4229
+ fallbackReason = "no_ready_index";
4230
+ }
4231
+ }
4232
+ }
4233
+
4234
+ if (intent === "unknown") {
4235
+ addQuestion(
4236
+ language === "es"
4237
+ ? "¿Cuál es el resultado esperado exacto y el alcance del cambio?"
4238
+ : language === "zh"
4239
+ ? "这次变更的精确目标和范围是什么?"
4240
+ : "What exact outcome and scope should this change target?"
4241
+ );
4242
+ }
4243
+
4244
+ const contextRefs: ContextRef[] = searchResults.map((result) => ({
4245
+ path: result.path,
4246
+ start_line: result.start_line,
4247
+ end_line: result.end_line,
4248
+ reason: result.reason
4249
+ }));
4250
+
4251
+ const enhancedPrompt = formatEnhancedPrompt({
4252
+ intent,
4253
+ language,
4254
+ original_prompt: input.request.prompt,
4255
+ refs: contextRefs
4256
+ });
4257
+
4258
+ const output: EnhancePromptOutput = {
4259
+ trace_id: input.trace_id,
4260
+ enhanced_prompt: enhancedPrompt,
4261
+ context_refs: contextRefs,
4262
+ warnings,
4263
+ questions
4264
+ };
4265
+
4266
+ const latency_ms = Date.now() - startedAt;
4267
+ this.observability.metrics.observe("enhancer_latency_ms", latency_ms, {});
4268
+ this.observability.metrics.observe("enhancer_context_refs_count", output.context_refs.length, {});
4269
+ this.observability.metrics.observe("enhancer_questions_count", output.questions.length, {});
4270
+ this.observability.metrics.observe("enhancer_query_expansion_ms", queryExpansionMs, {
4271
+ retrieval_profile_id: this.scoringProfileId
4272
+ });
4273
+ this.observability.metrics.observe("enhancer_expanded_hint_count", expandedHintCount, {
4274
+ retrieval_profile_id: this.scoringProfileId
4275
+ });
4276
+ this.observability.metrics.observe("enhancer_candidate_count_pre_rerank", candidateCountPreRerank, {
4277
+ retrieval_profile_id: this.scoringProfileId
4278
+ });
4279
+ this.observability.metrics.observe("enhancer_candidate_count_post_rerank", candidateCountPostRerank, {
4280
+ retrieval_profile_id: this.scoringProfileId
4281
+ });
4282
+ this.observability.metrics.observe("enhancer_rerank_ms", rerankMs, {
4283
+ retrieval_profile_id: this.scoringProfileId
4284
+ });
4285
+ this.observability.metrics.observe("enhancer_low_confidence_triggered", lowConfidenceTriggered ? 1 : 0, {});
4286
+ this.observability.metrics.observe("enhancer_fallback_triggered", fallbackTriggered ? 1 : 0, {});
4287
+ if (lowConfidenceTriggered) {
4288
+ this.observability.metrics.increment("enhancer_low_confidence_total", 1, {
4289
+ retrieval_profile_id: this.scoringProfileId
4290
+ });
4291
+ }
4292
+ if (fallbackTriggered) {
4293
+ this.observability.metrics.increment("enhancer_fallback_total", 1, {
4294
+ retrieval_profile_id: this.scoringProfileId,
4295
+ reason: fallbackReason ?? "unknown"
4296
+ });
4297
+ }
4298
+ this.observability.logger.info("enhance_prompt completed", {
4299
+ trace_id: input.trace_id,
4300
+ tenant_id: input.tenant_id,
4301
+ workspace_id: input.workspace_id ?? "none",
4302
+ latency_ms,
4303
+ context_refs: output.context_refs.length,
4304
+ query_expansion_ms: queryExpansionMs,
4305
+ expanded_hint_count: expandedHintCount,
4306
+ candidate_count_pre_rerank: candidateCountPreRerank,
4307
+ candidate_count_post_rerank: candidateCountPostRerank,
4308
+ rerank_ms: rerankMs,
4309
+ low_confidence_triggered: lowConfidenceTriggered,
4310
+ fallback_triggered: fallbackTriggered,
4311
+ fallback_reason: fallbackReason,
4312
+ query_intent: queryIntent,
4313
+ confidence_score_spread: confidenceSignals?.score_spread ?? null,
4314
+ confidence_token_overlap: confidenceSignals?.token_overlap ?? null,
4315
+ confidence_path_diversity: confidenceSignals?.path_diversity ?? null,
4316
+ ...(this.enhancerDecisionTraceEnabled
4317
+ ? {
4318
+ intent_class: intent,
4319
+ strong_anchor_bypass_used: strongAnchorBypassUsed,
4320
+ rerank_used: rerankUsed,
4321
+ confidence_score: confidenceSignals?.confidence_score ?? null,
4322
+ confidence_threshold: confidenceSignals?.confidence_threshold ?? null
4323
+ }
4324
+ : {})
4325
+ });
4326
+ return output;
4327
+ }
4328
+ }
4329
+
4330
+ export function createDefaultRetrievalCore(): RetrievalCore {
4331
+ return new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache());
4332
+ }
4333
+
4334
+ export async function seedWorkspaceIndex(core: RetrievalCore, artifact: IndexUploadArtifact): Promise<IndexingReport> {
4335
+ return core.indexArtifact(artifact);
4336
+ }
4337
+
4338
+ export * from "./remote-sync.js";