indexer-cli 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,735 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContextPackBuilder = void 0;
4
+ exports.normalizeContextPackIntent = normalizeContextPackIntent;
5
+ exports.buildContextPackProfile = buildContextPackProfile;
6
+ exports.inferContextPackConfidenceBand = inferContextPackConfidenceBand;
7
+ exports.scoreContextPackModules = scoreContextPackModules;
8
+ const architecture_js_1 = require("./architecture.js");
9
+ const PROFILE_DEFAULTS = {
10
+ routing: {
11
+ budget: 800,
12
+ maxModules: 2,
13
+ maxFiles: 4,
14
+ maxSnippets: 1,
15
+ evidenceThreshold: 0.55,
16
+ searchTopK: 6,
17
+ },
18
+ balanced: {
19
+ budget: 1500,
20
+ maxModules: 3,
21
+ maxFiles: 6,
22
+ maxSnippets: 2,
23
+ evidenceThreshold: 0.68,
24
+ searchTopK: 10,
25
+ },
26
+ deep: {
27
+ budget: 2500,
28
+ maxModules: 4,
29
+ maxFiles: 8,
30
+ maxSnippets: 3,
31
+ evidenceThreshold: 0.8,
32
+ searchTopK: 14,
33
+ },
34
+ };
35
+ const PROFILE_BY_BUDGET = {
36
+ 800: "routing",
37
+ 1500: "balanced",
38
+ 2500: "deep",
39
+ };
40
+ function clamp(value, min, max) {
41
+ return Math.min(max, Math.max(min, value));
42
+ }
43
+ function estimateTokens(data) {
44
+ return Math.ceil(JSON.stringify(data).length / 4);
45
+ }
46
+ function normalizePath(value) {
47
+ return value.replace(/\\/g, "/").replace(/^\.\//, "");
48
+ }
49
+ function tokenizeTask(task) {
50
+ return task
51
+ .toLowerCase()
52
+ .split(/[^a-z0-9]+/)
53
+ .map((token) => token.trim())
54
+ .filter((token) => token.length >= 3);
55
+ }
56
+ function normalizeContextPackIntent(task) {
57
+ const input = task.toLowerCase();
58
+ if (/(fix|bug|broken|error|fail|failing|regress|issue)/.test(input)) {
59
+ return "bugfix";
60
+ }
61
+ if (/(add|implement|feature|support|enable|create|introduce)/.test(input)) {
62
+ return "feature";
63
+ }
64
+ if (/(refactor|cleanup|clean up|rename|restructure|simplify)/.test(input)) {
65
+ return "refactor";
66
+ }
67
+ if (/(investigate|investigation|look into|understand|explore|why|how|trace)/.test(input)) {
68
+ return "investigation";
69
+ }
70
+ return "unknown";
71
+ }
72
+ function parseScope(scope) {
73
+ const value = scope?.trim() || "all";
74
+ if (value === "all") {
75
+ return { kind: "all" };
76
+ }
77
+ if (value === "changed") {
78
+ return { kind: "changed" };
79
+ }
80
+ if (value.startsWith("relevant-to:")) {
81
+ const scopeValue = normalizePath(value.slice("relevant-to:".length).trim());
82
+ if (!scopeValue) {
83
+ throw new Error("--scope relevant-to:<path> requires a non-empty path.");
84
+ }
85
+ return {
86
+ kind: "relevant-to",
87
+ value: scopeValue,
88
+ };
89
+ }
90
+ if (value.startsWith("path-prefix:")) {
91
+ const scopeValue = normalizePath(value.slice("path-prefix:".length).trim());
92
+ if (!scopeValue) {
93
+ throw new Error("--scope path-prefix:<path> requires a non-empty path.");
94
+ }
95
+ return {
96
+ kind: "path-prefix",
97
+ value: scopeValue,
98
+ };
99
+ }
100
+ throw new Error("--scope must be one of: all, changed, relevant-to:<path>, path-prefix:<path>.");
101
+ }
102
+ function buildContextPackProfile(options = {}) {
103
+ const budgetProfile = typeof options.budget === "number"
104
+ ? PROFILE_BY_BUDGET[options.budget]
105
+ : undefined;
106
+ if (options.profile && budgetProfile && options.profile !== budgetProfile) {
107
+ throw new Error(`--budget ${options.budget} only matches the ${budgetProfile} profile. Remove one of the options or use a matching pair.`);
108
+ }
109
+ const profile = options.profile ?? budgetProfile ?? "balanced";
110
+ const defaults = PROFILE_DEFAULTS[profile];
111
+ return {
112
+ budget: options.budget ?? defaults.budget,
113
+ profile,
114
+ scope: parseScope(options.scope),
115
+ maxModules: Math.max(1, options.maxModules ?? defaults.maxModules),
116
+ maxFiles: Math.max(1, options.maxFiles ?? defaults.maxFiles),
117
+ maxSnippets: Math.max(1, options.maxSnippets ?? defaults.maxSnippets),
118
+ minScore: options.minScore,
119
+ explainSymbols: Boolean(options.explainSymbols),
120
+ excludePathPatterns: options.excludePathPatterns ?? [],
121
+ includeFixtures: Boolean(options.includeFixtures),
122
+ evidenceThreshold: defaults.evidenceThreshold,
123
+ searchTopK: Math.max(defaults.searchTopK, (options.maxFiles ?? defaults.maxFiles) * 2),
124
+ };
125
+ }
126
+ function isExplicitScope(scope) {
127
+ return scope.kind !== "all";
128
+ }
129
+ function inferContextPackConfidenceBand(confidence) {
130
+ if (confidence >= 0.75) {
131
+ return "high";
132
+ }
133
+ if (confidence >= 0.45) {
134
+ return "medium";
135
+ }
136
+ return "low";
137
+ }
138
+ function scoreContextPackModules(metrics) {
139
+ return metrics
140
+ .map((metric) => {
141
+ const density = clamp(metric.hitCount / 3, 0, 1);
142
+ const score = clamp(metric.maxSemanticScore * 0.35 +
143
+ density * 0.15 +
144
+ metric.symbolOverlap * 0.15 +
145
+ metric.dependencyProximity * 0.15 +
146
+ metric.pathPrior * 0.1 +
147
+ metric.changedBoost * 0.1, 0, 1);
148
+ return {
149
+ module: metric.module,
150
+ score,
151
+ reasons: metric.reasons,
152
+ metrics: {
153
+ maxSemanticScore: metric.maxSemanticScore,
154
+ hitCount: metric.hitCount,
155
+ symbolOverlap: metric.symbolOverlap,
156
+ dependencyProximity: metric.dependencyProximity,
157
+ pathPrior: metric.pathPrior,
158
+ changedBoost: metric.changedBoost,
159
+ fileCount: metric.fileCount,
160
+ },
161
+ };
162
+ })
163
+ .sort((left, right) => right.score - left.score);
164
+ }
165
+ function getPathPrior(moduleKey, tokens, intent) {
166
+ const lower = moduleKey.toLowerCase();
167
+ let prior = 0;
168
+ if (tokens.some((token) => ["cli", "command", "commands"].includes(token))) {
169
+ prior = Math.max(prior, lower.includes("cli") ? 0.9 : 0);
170
+ }
171
+ if (tokens.some((token) => [
172
+ "search",
173
+ "context",
174
+ "architecture",
175
+ "dependency",
176
+ "deps",
177
+ "index",
178
+ ].includes(token))) {
179
+ prior = Math.max(prior, lower.includes("engine") ? 0.9 : 0);
180
+ }
181
+ if (tokens.some((token) => ["config", "types", "logger"].includes(token))) {
182
+ prior = Math.max(prior, lower.includes("core") ? 0.85 : 0);
183
+ }
184
+ if (tokens.some((token) => ["storage", "sqlite", "vector", "db"].includes(token))) {
185
+ prior = Math.max(prior, lower.includes("storage") ? 0.9 : 0);
186
+ }
187
+ if (intent === "bugfix" && /test|fixture/.test(lower)) {
188
+ prior = Math.max(prior, 0.3);
189
+ }
190
+ return prior;
191
+ }
192
+ function scoreSymbolOverlap(symbols, tokens) {
193
+ if (tokens.length === 0 || symbols.length === 0) {
194
+ return 0;
195
+ }
196
+ const haystack = symbols
197
+ .map((symbol) => `${symbol.name} ${symbol.signature ?? ""}`.toLowerCase())
198
+ .join(" ");
199
+ const overlapCount = tokens.filter((token) => haystack.includes(token)).length;
200
+ return clamp(overlapCount / Math.min(tokens.length, 4), 0, 1);
201
+ }
202
+ function inferModuleGoal(moduleKey, symbols, hasSemanticHits) {
203
+ const lower = moduleKey.toLowerCase();
204
+ const evidenceSources = ["path layout"];
205
+ if (hasSemanticHits) {
206
+ evidenceSources.push("semantic hits");
207
+ }
208
+ if (lower.includes("cli") && lower.includes("command")) {
209
+ evidenceSources.push("command path heuristic");
210
+ return {
211
+ goal: "CLI command surface and orchestration",
212
+ evidenceSources,
213
+ };
214
+ }
215
+ if (lower.includes("engine")) {
216
+ evidenceSources.push("engine path heuristic");
217
+ return {
218
+ goal: "Engine and discovery orchestration logic",
219
+ evidenceSources,
220
+ };
221
+ }
222
+ if (lower.includes("core")) {
223
+ evidenceSources.push("core path heuristic");
224
+ return {
225
+ goal: "Shared core types, config, and support utilities",
226
+ evidenceSources,
227
+ };
228
+ }
229
+ if (lower.includes("storage")) {
230
+ evidenceSources.push("storage path heuristic");
231
+ return {
232
+ goal: "Storage and persistence layer",
233
+ evidenceSources,
234
+ };
235
+ }
236
+ const exportedSymbol = symbols.find((symbol) => symbol.exported);
237
+ if (exportedSymbol) {
238
+ evidenceSources.push("exported symbols");
239
+ return {
240
+ goal: `Module centered on ${exportedSymbol.name}`,
241
+ evidenceSources,
242
+ };
243
+ }
244
+ return {
245
+ goal: `Implementation area for ${moduleKey}`,
246
+ evidenceSources,
247
+ };
248
+ }
249
+ function unique(values) {
250
+ return Array.from(new Set(values));
251
+ }
252
+ function pickSelectedModules(scoredModules, maxModules) {
253
+ const top = scoredModules.slice(0, Math.max(1, maxModules));
254
+ if (top.length <= 1) {
255
+ return top;
256
+ }
257
+ const bestScore = top[0]?.score ?? 0;
258
+ return top.filter((entry, index) => index === 0 || bestScore - entry.score <= 0.08);
259
+ }
260
+ function createFallbackScores(moduleFiles, tokens, intent) {
261
+ return Object.entries(moduleFiles)
262
+ .map(([module, filePaths]) => ({
263
+ module,
264
+ score: clamp(getPathPrior(module, tokens, intent) +
265
+ Math.min(filePaths.length / 10, 0.25), 0.05, 0.45),
266
+ reasons: ["weak semantic signal; falling back to broad module coverage"],
267
+ metrics: {
268
+ maxSemanticScore: 0,
269
+ hitCount: 0,
270
+ symbolOverlap: 0,
271
+ dependencyProximity: 0,
272
+ pathPrior: getPathPrior(module, tokens, intent),
273
+ changedBoost: 0,
274
+ fileCount: filePaths.length,
275
+ },
276
+ }))
277
+ .sort((left, right) => right.score - left.score);
278
+ }
279
+ function resolveRelevantScopeFiles(value, architecture) {
280
+ const target = normalizePath(value);
281
+ const moduleFiles = architecture.module_files ?? {};
282
+ const dependencyMap = architecture.dependency_map?.internal ?? {};
283
+ const targetModule = Object.entries(moduleFiles).find(([, filePaths]) => filePaths.some((filePath) => filePath === target || filePath.startsWith(`${target}/`)))?.[0];
284
+ if (!targetModule) {
285
+ return {
286
+ filePaths: null,
287
+ why: [`No module matched ${target}; using global routing instead`],
288
+ };
289
+ }
290
+ const relatedModules = new Set([
291
+ targetModule,
292
+ ...(dependencyMap[targetModule] ?? []),
293
+ ...Object.entries(dependencyMap)
294
+ .filter(([, dependencies]) => dependencies.includes(targetModule))
295
+ .map(([fromModule]) => fromModule),
296
+ ]);
297
+ return {
298
+ filePaths: new Set(Array.from(relatedModules).flatMap((moduleKey) => moduleFiles[moduleKey] ?? [])),
299
+ why: [
300
+ `Expanded relevant-to scope around ${targetModule}`,
301
+ "Included immediate dependency neighborhood for better routing",
302
+ ],
303
+ };
304
+ }
305
+ function buildArchitectureSlice(selectedModuleKeys, architecture, fileToModuleKey, maxModules) {
306
+ const internalMap = architecture.dependency_map?.internal ?? {};
307
+ const related = new Set(selectedModuleKeys);
308
+ for (const moduleKey of selectedModuleKeys) {
309
+ for (const toModule of internalMap[moduleKey] ?? []) {
310
+ if (related.size >= maxModules + 2) {
311
+ break;
312
+ }
313
+ related.add(toModule);
314
+ }
315
+ for (const [fromModule, toModules] of Object.entries(internalMap)) {
316
+ if (toModules.includes(moduleKey) && related.size < maxModules + 2) {
317
+ related.add(fromModule);
318
+ }
319
+ }
320
+ }
321
+ const relatedModules = Array.from(related).sort();
322
+ return {
323
+ entrypoints: (architecture.entrypoints ?? [])
324
+ .filter((filePath) => related.has(fileToModuleKey.get(filePath) ?? ""))
325
+ .slice(0, 5),
326
+ relatedModules,
327
+ dependencies: relatedModules
328
+ .flatMap((from) => (internalMap[from] ?? [])
329
+ .filter((to) => related.has(to))
330
+ .map((to) => ({ from, to })))
331
+ .slice(0, maxModules * 3),
332
+ };
333
+ }
334
+ function buildStructureSlice(args) {
335
+ const selectedModules = new Set(args.selectedModuleKeys);
336
+ const fileByPath = new Map(args.visibleFiles.map((file) => [file.path, file]));
337
+ const selectedFiles = args.visibleFiles.filter((file) => selectedModules.has(args.fileToModuleKey.get(file.path) ?? ""));
338
+ const exportedFiles = new Set(args.visibleSymbols
339
+ .filter((symbol) => symbol.exported)
340
+ .map((symbol) => symbol.filePath));
341
+ const filePaths = unique([
342
+ ...args.hitFilePaths,
343
+ ...selectedFiles
344
+ .filter((file) => exportedFiles.has(file.path))
345
+ .map((file) => file.path),
346
+ ...selectedFiles.map((file) => file.path),
347
+ ]);
348
+ const files = filePaths
349
+ .slice(0, args.maxFiles)
350
+ .map((filePath) => {
351
+ const file = fileByPath.get(filePath);
352
+ if (!file) {
353
+ return null;
354
+ }
355
+ return {
356
+ path: file.path,
357
+ module: args.fileToModuleKey.get(file.path) ?? file.path,
358
+ language: file.languageId,
359
+ };
360
+ })
361
+ .filter((file) => file !== null);
362
+ const returnedFilePaths = new Set(files.map((file) => file.path));
363
+ const tokenizedSymbols = args.visibleSymbols
364
+ .filter((symbol) => returnedFilePaths.has(symbol.filePath))
365
+ .map((symbol) => {
366
+ const text = `${symbol.name} ${symbol.signature ?? ""}`.toLowerCase();
367
+ const overlap = args.tokens.filter((token) => text.includes(token)).length;
368
+ return {
369
+ symbol,
370
+ score: overlap + (symbol.exported ? 1 : 0),
371
+ };
372
+ })
373
+ .sort((left, right) => right.score - left.score ||
374
+ left.symbol.filePath.localeCompare(right.symbol.filePath))
375
+ .slice(0, args.maxFiles * 3)
376
+ .map(({ symbol }) => ({
377
+ file: symbol.filePath,
378
+ name: symbol.name,
379
+ kind: symbol.kind,
380
+ ...(args.explainSymbols && symbol.signature
381
+ ? { signature: symbol.signature }
382
+ : {}),
383
+ }));
384
+ return {
385
+ files,
386
+ keySymbols: tokenizedSymbols,
387
+ };
388
+ }
389
+ function buildSemanticHits(hits, maxSnippets, includeSnippets) {
390
+ return hits.slice(0, maxSnippets).map((hit, index) => ({
391
+ filePath: hit.filePath,
392
+ score: hit.score,
393
+ ...(hit.primarySymbol ? { primarySymbol: hit.primarySymbol } : {}),
394
+ reason: index === 0
395
+ ? "best semantic match in the selected scope"
396
+ : "supporting semantic evidence for the selected area",
397
+ ...(includeSnippets && hit.content ? { snippet: hit.content } : {}),
398
+ }));
399
+ }
400
+ function createEmptyPack(args) {
401
+ const result = {
402
+ task: args.task,
403
+ intent: args.intent,
404
+ selected_scope: {
405
+ pathPrefixes: [],
406
+ confidence: 0.1,
407
+ why: args.why,
408
+ },
409
+ module_goals: [],
410
+ architecture_slice: {
411
+ entrypoints: [],
412
+ relatedModules: [],
413
+ dependencies: [],
414
+ },
415
+ structure_slice: {
416
+ files: [],
417
+ keySymbols: [],
418
+ },
419
+ semantic_hits: [],
420
+ next_reads: [],
421
+ _meta: {
422
+ estimatedTokens: 0,
423
+ budget: args.budget,
424
+ profile: args.profile,
425
+ confidenceBand: "low",
426
+ omitted: [
427
+ "full-file content",
428
+ "long dependency traces",
429
+ "evidence snippets",
430
+ "symbol signatures",
431
+ ],
432
+ },
433
+ };
434
+ result._meta.estimatedTokens = estimateTokens(result);
435
+ return result;
436
+ }
437
+ function buildNextReads(args) {
438
+ const nextReads = new Map();
439
+ for (const hit of args.semanticHits) {
440
+ if (!nextReads.has(hit.filePath)) {
441
+ nextReads.set(hit.filePath, {
442
+ file: hit.filePath,
443
+ reason: "highest-ranked semantic hit for the task",
444
+ });
445
+ }
446
+ }
447
+ for (const file of args.structureFiles) {
448
+ if (!nextReads.has(file.path)) {
449
+ nextReads.set(file.path, {
450
+ file: file.path,
451
+ reason: `representative file in selected module ${file.module}`,
452
+ });
453
+ }
454
+ }
455
+ for (const file of args.entrypoints) {
456
+ if (!nextReads.has(file)) {
457
+ nextReads.set(file, {
458
+ file,
459
+ reason: "entrypoint touching the selected dependency neighborhood",
460
+ });
461
+ }
462
+ }
463
+ return Array.from(nextReads.values()).slice(0, 4);
464
+ }
465
+ function buildMeta(selectedScope, options, includeEvidence) {
466
+ const omitted = ["full-file content", "long dependency traces"];
467
+ if (!includeEvidence) {
468
+ omitted.push("evidence snippets");
469
+ }
470
+ if (!options.explainSymbols) {
471
+ omitted.push("symbol signatures");
472
+ }
473
+ return {
474
+ estimatedTokens: 0,
475
+ budget: options.budget,
476
+ profile: options.profile,
477
+ confidenceBand: inferContextPackConfidenceBand(selectedScope.confidence),
478
+ omitted,
479
+ };
480
+ }
481
+ function buildModuleMetrics(args) {
482
+ const symbolsByModule = new Map();
483
+ for (const symbol of args.visibleSymbols) {
484
+ const moduleKey = args.fileToModuleKey.get(symbol.filePath);
485
+ if (!moduleKey) {
486
+ continue;
487
+ }
488
+ const current = symbolsByModule.get(moduleKey) ?? [];
489
+ current.push(symbol);
490
+ symbolsByModule.set(moduleKey, current);
491
+ }
492
+ const hitModules = new Set();
493
+ const hitStats = new Map();
494
+ for (const hit of args.hits) {
495
+ const moduleKey = args.fileToModuleKey.get(hit.filePath);
496
+ if (!moduleKey) {
497
+ continue;
498
+ }
499
+ hitModules.add(moduleKey);
500
+ const current = hitStats.get(moduleKey) ?? {
501
+ maxSemanticScore: 0,
502
+ hitCount: 0,
503
+ };
504
+ current.maxSemanticScore = Math.max(current.maxSemanticScore, hit.score);
505
+ current.hitCount += 1;
506
+ hitStats.set(moduleKey, current);
507
+ }
508
+ const dependencyMap = args.architecture.dependency_map?.internal ?? {};
509
+ return Object.entries(args.moduleFiles)
510
+ .filter(([moduleKey]) => args.allowedModuleKeys === undefined ||
511
+ args.allowedModuleKeys.has(moduleKey))
512
+ .map(([moduleKey, filePaths]) => {
513
+ const stats = hitStats.get(moduleKey) ?? {
514
+ maxSemanticScore: 0,
515
+ hitCount: 0,
516
+ };
517
+ const changedBoost = filePaths.some((filePath) => args.changedFiles.has(filePath))
518
+ ? 1
519
+ : 0;
520
+ const dependencyProximity = hitModules.has(moduleKey)
521
+ ? 1
522
+ : (dependencyMap[moduleKey] ?? []).some((neighbor) => hitModules.has(neighbor)) ||
523
+ Object.entries(dependencyMap).some(([fromModule, neighbors]) => hitModules.has(fromModule) && neighbors.includes(moduleKey))
524
+ ? 0.6
525
+ : 0;
526
+ const reasons = [];
527
+ if (stats.hitCount > 0) {
528
+ reasons.push(`semantic hits clustered in ${moduleKey}`);
529
+ }
530
+ if (changedBoost > 0) {
531
+ reasons.push("working tree changes intersect this module");
532
+ }
533
+ if (dependencyProximity > 0 && stats.hitCount === 0) {
534
+ reasons.push("dependency neighborhood touches top semantic matches");
535
+ }
536
+ return {
537
+ module: moduleKey,
538
+ maxSemanticScore: stats.maxSemanticScore,
539
+ hitCount: stats.hitCount,
540
+ symbolOverlap: scoreSymbolOverlap(symbolsByModule.get(moduleKey) ?? [], args.tokens),
541
+ dependencyProximity,
542
+ pathPrior: getPathPrior(moduleKey, args.tokens, args.intent),
543
+ changedBoost,
544
+ fileCount: filePaths.length,
545
+ reasons,
546
+ };
547
+ });
548
+ }
549
+ class ContextPackBuilder {
550
+ metadata;
551
+ search;
552
+ git;
553
+ repoRoot;
554
+ constructor(metadata, search, git, repoRoot) {
555
+ this.metadata = metadata;
556
+ this.search = search;
557
+ this.git = git;
558
+ this.repoRoot = repoRoot;
559
+ }
560
+ async build(projectId, snapshotId, task, options = {}) {
561
+ const resolved = buildContextPackProfile(options);
562
+ const intent = normalizeContextPackIntent(task);
563
+ const tokens = tokenizeTask(task);
564
+ const [files, symbols, artifact, gitDiff] = await Promise.all([
565
+ this.metadata.listFiles(projectId, snapshotId),
566
+ this.metadata.listSymbols(projectId, snapshotId),
567
+ this.metadata.getArtifact(projectId, snapshotId, "architecture_snapshot", "project"),
568
+ this.git.getWorkingTreeChanges(this.repoRoot),
569
+ ]);
570
+ if (!artifact) {
571
+ throw new Error("Architecture snapshot unavailable after indexing.");
572
+ }
573
+ const rawArchitecture = JSON.parse(artifact.dataJson);
574
+ const architecture = resolved.includeFixtures
575
+ ? rawArchitecture
576
+ : (0, architecture_js_1.filterArchitectureSnapshot)(rawArchitecture, resolved.excludePathPatterns);
577
+ const visibleFilePathSet = new Set((architecture.files ?? []).map((file) => file.path));
578
+ const visibleFiles = files.filter((file) => visibleFilePathSet.has(file.path));
579
+ const visibleSymbols = symbols.filter((symbol) => visibleFilePathSet.has(symbol.filePath));
580
+ const moduleFiles = architecture.module_files ?? {};
581
+ const fileToModuleKey = new Map();
582
+ for (const [moduleKey, filePaths] of Object.entries(moduleFiles)) {
583
+ for (const filePath of filePaths) {
584
+ fileToModuleKey.set(filePath, moduleKey);
585
+ }
586
+ }
587
+ let scopeResolution;
588
+ if (resolved.scope.kind === "changed") {
589
+ scopeResolution = {
590
+ filePaths: new Set([...gitDiff.added, ...gitDiff.modified]),
591
+ why: ["Scoped to current uncommitted changes"],
592
+ };
593
+ }
594
+ else if (resolved.scope.kind === "relevant-to") {
595
+ scopeResolution = resolveRelevantScopeFiles(resolved.scope.value, architecture);
596
+ }
597
+ else if (resolved.scope.kind === "path-prefix") {
598
+ const scopeValue = resolved.scope.value;
599
+ scopeResolution = {
600
+ filePaths: new Set(visibleFiles
601
+ .filter((file) => file.path.startsWith(scopeValue))
602
+ .map((file) => file.path)),
603
+ pathPrefix: scopeValue,
604
+ why: [`Scoped search to path prefix ${scopeValue}`],
605
+ };
606
+ }
607
+ else {
608
+ scopeResolution = {
609
+ filePaths: null,
610
+ why: ["Used full-repo routing to infer the best area"],
611
+ };
612
+ }
613
+ const scopedFilePaths = scopeResolution.filePaths === null
614
+ ? null
615
+ : new Set(Array.from(scopeResolution.filePaths).filter((filePath) => visibleFilePathSet.has(filePath)));
616
+ if (isExplicitScope(resolved.scope) && (scopedFilePaths?.size ?? 0) === 0) {
617
+ return createEmptyPack({
618
+ task,
619
+ intent,
620
+ profile: resolved.profile,
621
+ budget: resolved.budget,
622
+ why: [
623
+ ...scopeResolution.why,
624
+ "No indexed files matched the requested explicit scope.",
625
+ ],
626
+ });
627
+ }
628
+ const effectiveModuleFiles = scopedFilePaths === null
629
+ ? moduleFiles
630
+ : Object.fromEntries(Object.entries(moduleFiles)
631
+ .map(([moduleKey, filePaths]) => [
632
+ moduleKey,
633
+ filePaths.filter((filePath) => scopedFilePaths.has(filePath)),
634
+ ])
635
+ .filter(([, filePaths]) => filePaths.length > 0));
636
+ const effectiveModuleKeys = new Set(Object.keys(effectiveModuleFiles));
637
+ const effectiveVisibleFiles = scopedFilePaths === null
638
+ ? visibleFiles
639
+ : visibleFiles.filter((file) => scopedFilePaths.has(file.path));
640
+ const effectiveVisibleSymbols = scopedFilePaths === null
641
+ ? visibleSymbols
642
+ : visibleSymbols.filter((symbol) => scopedFilePaths.has(symbol.filePath));
643
+ const lightweightHits = await this.search.search(projectId, snapshotId, task, {
644
+ topK: resolved.searchTopK,
645
+ pathPrefix: scopeResolution.pathPrefix,
646
+ includeContent: false,
647
+ minScore: resolved.minScore,
648
+ });
649
+ const scopedHits = lightweightHits.filter((hit) => visibleFilePathSet.has(hit.filePath) &&
650
+ (scopedFilePaths === null || scopedFilePaths.has(hit.filePath)));
651
+ const changedFiles = new Set([...gitDiff.added, ...gitDiff.modified]);
652
+ const effectiveChangedFiles = scopedFilePaths === null
653
+ ? changedFiles
654
+ : new Set(Array.from(changedFiles).filter((filePath) => scopedFilePaths.has(filePath)));
655
+ const scoredModules = scoreContextPackModules(buildModuleMetrics({
656
+ moduleFiles: effectiveModuleFiles,
657
+ fileToModuleKey,
658
+ visibleSymbols: effectiveVisibleSymbols,
659
+ hits: scopedHits,
660
+ architecture,
661
+ changedFiles: effectiveChangedFiles,
662
+ tokens,
663
+ intent,
664
+ allowedModuleKeys: effectiveModuleKeys,
665
+ }));
666
+ const selectedModules = pickSelectedModules(scoredModules.length > 0 && (scoredModules[0]?.score ?? 0) > 0
667
+ ? scoredModules
668
+ : createFallbackScores(effectiveModuleFiles, tokens, intent), resolved.maxModules);
669
+ const selectedScope = {
670
+ pathPrefixes: unique(selectedModules.map((entry) => entry.module)),
671
+ confidence: clamp(selectedModules[0]?.score ?? 0.2, 0.05, 0.99),
672
+ why: unique([
673
+ ...scopeResolution.why,
674
+ ...selectedModules.flatMap((entry) => entry.reasons),
675
+ ]).slice(0, 4),
676
+ };
677
+ const includeEvidence = resolved.profile === "deep" ||
678
+ selectedScope.confidence < resolved.evidenceThreshold;
679
+ const evidenceHits = includeEvidence
680
+ ? (await this.search.search(projectId, snapshotId, task, {
681
+ topK: Math.max(resolved.maxSnippets * 2, 3),
682
+ pathPrefix: scopeResolution.pathPrefix,
683
+ includeContent: true,
684
+ minScore: resolved.minScore,
685
+ })).filter((hit) => visibleFilePathSet.has(hit.filePath) &&
686
+ (scopedFilePaths === null || scopedFilePaths.has(hit.filePath)))
687
+ : scopedHits;
688
+ const architectureSlice = buildArchitectureSlice(selectedModules.map((entry) => entry.module), architecture, fileToModuleKey, resolved.maxModules);
689
+ const structureSlice = resolved.profile === "routing"
690
+ ? { files: [], keySymbols: [] }
691
+ : buildStructureSlice({
692
+ selectedModuleKeys: selectedModules.map((entry) => entry.module),
693
+ fileToModuleKey,
694
+ visibleFiles: effectiveVisibleFiles,
695
+ visibleSymbols: effectiveVisibleSymbols,
696
+ hitFilePaths: scopedHits.map((hit) => hit.filePath),
697
+ maxFiles: resolved.maxFiles,
698
+ explainSymbols: resolved.explainSymbols,
699
+ tokens,
700
+ });
701
+ const moduleGoals = selectedModules.map((entry) => {
702
+ const goal = inferModuleGoal(entry.module, effectiveVisibleSymbols.filter((symbol) => fileToModuleKey.get(symbol.filePath) === entry.module), entry.metrics.hitCount > 0);
703
+ return {
704
+ module: entry.module,
705
+ goal: goal.goal,
706
+ confidence: clamp(entry.score, 0.1, 0.99),
707
+ evidenceSources: unique(goal.evidenceSources),
708
+ };
709
+ });
710
+ const semanticHits = buildSemanticHits(resolved.profile === "routing" ? [] : includeEvidence ? evidenceHits : [], resolved.maxSnippets, includeEvidence);
711
+ const nextReads = buildNextReads({
712
+ structureFiles: structureSlice.files,
713
+ semanticHits,
714
+ entrypoints: architectureSlice.entrypoints,
715
+ });
716
+ const meta = buildMeta(selectedScope, resolved, includeEvidence);
717
+ const result = {
718
+ task,
719
+ intent,
720
+ selected_scope: selectedScope,
721
+ module_goals: moduleGoals,
722
+ architecture_slice: resolved.profile === "routing"
723
+ ? { entrypoints: [], relatedModules: [], dependencies: [] }
724
+ : architectureSlice,
725
+ structure_slice: structureSlice,
726
+ semantic_hits: semanticHits,
727
+ next_reads: nextReads,
728
+ _meta: meta,
729
+ };
730
+ meta.estimatedTokens = estimateTokens(result);
731
+ return result;
732
+ }
733
+ }
734
+ exports.ContextPackBuilder = ContextPackBuilder;
735
+ //# sourceMappingURL=context-pack.js.map