@codeledger/selector 0.1.1 → 0.2.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.
Files changed (55) hide show
  1. package/dist/blast-radius.d.ts +7 -0
  2. package/dist/blast-radius.d.ts.map +1 -0
  3. package/dist/blast-radius.js +105 -0
  4. package/dist/blast-radius.js.map +1 -0
  5. package/dist/bundle.d.ts +10 -0
  6. package/dist/bundle.d.ts.map +1 -1
  7. package/dist/bundle.js +83 -9
  8. package/dist/bundle.js.map +1 -1
  9. package/dist/candidates.d.ts +12 -1
  10. package/dist/candidates.d.ts.map +1 -1
  11. package/dist/candidates.js +147 -22
  12. package/dist/candidates.js.map +1 -1
  13. package/dist/confidence.d.ts.map +1 -1
  14. package/dist/confidence.js +31 -0
  15. package/dist/confidence.js.map +1 -1
  16. package/dist/exemplars.d.ts +8 -0
  17. package/dist/exemplars.d.ts.map +1 -0
  18. package/dist/exemplars.js +122 -0
  19. package/dist/exemplars.js.map +1 -0
  20. package/dist/index.d.ts +11 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +9 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/layer-ordering.d.ts +37 -0
  25. package/dist/layer-ordering.d.ts.map +1 -0
  26. package/dist/layer-ordering.js +93 -0
  27. package/dist/layer-ordering.js.map +1 -0
  28. package/dist/near-miss.d.ts +9 -0
  29. package/dist/near-miss.d.ts.map +1 -0
  30. package/dist/near-miss.js +78 -0
  31. package/dist/near-miss.js.map +1 -0
  32. package/dist/scorer.d.ts +2 -2
  33. package/dist/scorer.d.ts.map +1 -1
  34. package/dist/scorer.js +23 -6
  35. package/dist/scorer.js.map +1 -1
  36. package/dist/stubs.d.ts.map +1 -1
  37. package/dist/stubs.js +4 -3
  38. package/dist/stubs.js.map +1 -1
  39. package/dist/task-type.d.ts +28 -0
  40. package/dist/task-type.d.ts.map +1 -0
  41. package/dist/task-type.js +115 -0
  42. package/dist/task-type.js.map +1 -0
  43. package/dist/todo-scan.d.ts +31 -0
  44. package/dist/todo-scan.d.ts.map +1 -0
  45. package/dist/todo-scan.js +63 -0
  46. package/dist/todo-scan.js.map +1 -0
  47. package/dist/token-calibration.d.ts +27 -0
  48. package/dist/token-calibration.d.ts.map +1 -0
  49. package/dist/token-calibration.js +113 -0
  50. package/dist/token-calibration.js.map +1 -0
  51. package/dist/validation.d.ts +7 -0
  52. package/dist/validation.d.ts.map +1 -0
  53. package/dist/validation.js +108 -0
  54. package/dist/validation.js.map +1 -0
  55. package/package.json +2 -2
@@ -0,0 +1,7 @@
1
+ import type { BlastRadiusEntry, BundleFile, DepGraph, TestMapping } from '@codeledger/types';
2
+ /**
3
+ * Compute blast radius for each non-stub, non-exemplar file in the bundle.
4
+ * Returns entries sorted by total impact (direct + transitive count).
5
+ */
6
+ export declare function computeBlastRadius(files: BundleFile[], depGraph: DepGraph, testMap: TestMapping[]): BlastRadiusEntry[];
7
+ //# sourceMappingURL=blast-radius.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blast-radius.d.ts","sourceRoot":"","sources":["../src/blast-radius.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqB7F;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,WAAW,EAAE,GACrB,gBAAgB,EAAE,CAgEpB"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Blast Radius Annotation
3
+ *
4
+ * For each selected file in the bundle, computes the set of files that would
5
+ * be affected if that file changes:
6
+ * - Direct dependents (files that import it)
7
+ * - Transitive dependents (full dependency chain up to a configurable depth)
8
+ * - Impacted tests (test files mapped to affected source files)
9
+ *
10
+ * This is a safety annotation — it helps agents understand the ripple effects
11
+ * of their changes without altering the scoring algorithm.
12
+ */
13
+ /** Maximum depth for transitive dependent traversal */
14
+ const MAX_TRANSITIVE_DEPTH = 3;
15
+ /** Maximum number of transitive dependents to track per file */
16
+ const MAX_TRANSITIVE_PER_FILE = 20;
17
+ /**
18
+ * Compute blast radius for each non-stub, non-exemplar file in the bundle.
19
+ * Returns entries sorted by total impact (direct + transitive count).
20
+ */
21
+ export function computeBlastRadius(files, depGraph, testMap) {
22
+ const entries = [];
23
+ const selectedPaths = new Set(files.map((f) => f.path));
24
+ // Build reverse test map: source_file → test_files
25
+ const sourceToTests = new Map();
26
+ for (const mapping of testMap) {
27
+ const tests = sourceToTests.get(mapping.source_file) ?? [];
28
+ tests.push(mapping.test_file);
29
+ sourceToTests.set(mapping.source_file, tests);
30
+ }
31
+ for (const file of files) {
32
+ if (file.is_stub || file.is_exemplar)
33
+ continue;
34
+ const directDeps = (depGraph.dependents[file.path] ?? [])
35
+ .filter((d) => !selectedPaths.has(d));
36
+ const transitiveDeps = computeTransitiveDependents(file.path, depGraph, selectedPaths);
37
+ // Impacted tests: tests for the file itself + tests for its direct dependents
38
+ const impactedTests = new Set();
39
+ // Tests for this file
40
+ const ownTests = sourceToTests.get(file.path) ?? [];
41
+ for (const t of ownTests)
42
+ impactedTests.add(t);
43
+ // Tests for direct dependents
44
+ for (const dep of directDeps) {
45
+ const depTests = sourceToTests.get(dep) ?? [];
46
+ for (const t of depTests)
47
+ impactedTests.add(t);
48
+ }
49
+ // Tests for transitive dependents
50
+ for (const dep of transitiveDeps) {
51
+ const depTests = sourceToTests.get(dep) ?? [];
52
+ for (const t of depTests)
53
+ impactedTests.add(t);
54
+ }
55
+ // Only include if there's meaningful blast radius
56
+ if (directDeps.length === 0 && transitiveDeps.length === 0 && impactedTests.size === 0) {
57
+ continue;
58
+ }
59
+ entries.push({
60
+ path: file.path,
61
+ direct_dependents: directDeps,
62
+ transitive_dependents: transitiveDeps,
63
+ impacted_tests: [...impactedTests],
64
+ });
65
+ }
66
+ // Sort by total impact descending
67
+ entries.sort((a, b) => {
68
+ const aTotal = a.direct_dependents.length + a.transitive_dependents.length + a.impacted_tests.length;
69
+ const bTotal = b.direct_dependents.length + b.transitive_dependents.length + b.impacted_tests.length;
70
+ return bTotal - aTotal;
71
+ });
72
+ return entries;
73
+ }
74
+ /**
75
+ * BFS to find transitive dependents up to MAX_TRANSITIVE_DEPTH.
76
+ * Excludes already-selected files (they're in the bundle, so the agent
77
+ * will handle them directly).
78
+ */
79
+ function computeTransitiveDependents(startPath, depGraph, excludePaths) {
80
+ const visited = new Set([startPath]);
81
+ const result = [];
82
+ let frontier = [startPath];
83
+ for (let depth = 0; depth < MAX_TRANSITIVE_DEPTH; depth++) {
84
+ const nextFrontier = [];
85
+ for (const path of frontier) {
86
+ const deps = depGraph.dependents[path] ?? [];
87
+ for (const dep of deps) {
88
+ if (visited.has(dep))
89
+ continue;
90
+ visited.add(dep);
91
+ if (!excludePaths.has(dep)) {
92
+ result.push(dep);
93
+ if (result.length >= MAX_TRANSITIVE_PER_FILE)
94
+ return result;
95
+ }
96
+ nextFrontier.push(dep);
97
+ }
98
+ }
99
+ frontier = nextFrontier;
100
+ if (frontier.length === 0)
101
+ break;
102
+ }
103
+ return result;
104
+ }
105
+ //# sourceMappingURL=blast-radius.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blast-radius.js","sourceRoot":"","sources":["../src/blast-radius.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,uDAAuD;AACvD,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B,gEAAgE;AAChE,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAEnC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAmB,EACnB,QAAkB,EAClB,OAAsB;IAEtB,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAExD,mDAAmD;IACnD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAoB,CAAC;IAClD,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC9B,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW;YAAE,SAAS;QAE/C,MAAM,UAAU,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aACtD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAExC,MAAM,cAAc,GAAG,2BAA2B,CAChD,IAAI,CAAC,IAAI,EACT,QAAQ,EACR,aAAa,CACd,CAAC;QAEF,8EAA8E;QAC9E,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;QAExC,sBAAsB;QACtB,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpD,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAE/C,8BAA8B;QAC9B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9C,KAAK,MAAM,CAAC,IAAI,QAAQ;gBAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,kCAAkC;QAClC,KAAK,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9C,KAAK,MAAM,CAAC,IAAI,QAAQ;gBAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,kDAAkD;QAClD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvF,SAAS;QACX,CAAC;QAED,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,iBAAiB,EAAE,UAAU;YAC7B,qBAAqB,EAAE,cAAc;YACrC,cAAc,EAAE,CAAC,GAAG,aAAa,CAAC;SACnC,CAAC,CAAC;IACL,CAAC;IAED,kCAAkC;IAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpB,MAAM,MAAM,GAAG,CAAC,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,qBAAqB,CAAC,MAAM,GAAG,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC;QACrG,MAAM,MAAM,GAAG,CAAC,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,qBAAqB,CAAC,MAAM,GAAG,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC;QACrG,OAAO,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAS,2BAA2B,CAClC,SAAiB,EACjB,QAAkB,EAClB,YAAyB;IAEzB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,QAAQ,GAAG,CAAC,SAAS,CAAC,CAAC;IAE3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,oBAAoB,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACjB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBACjB,IAAI,MAAM,CAAC,MAAM,IAAI,uBAAuB;wBAAE,OAAO,MAAM,CAAC;gBAC9D,CAAC;gBACD,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QACD,QAAQ,GAAG,YAAY,CAAC;QACxB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM;IACnC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/bundle.d.ts CHANGED
@@ -9,6 +9,16 @@ export interface BuildBundleOptions {
9
9
  ledgerStats?: LedgerStats;
10
10
  /** When true, include per-file scoring breakdown in the bundle */
11
11
  explain?: boolean;
12
+ /** Scope restriction: only consider files under these path prefixes */
13
+ scope?: string[];
14
+ /** Files changed on the current branch (for work-in-progress scoring boost) */
15
+ branchChangedFiles?: Set<string>;
16
+ /** When true, include near-miss files in the bundle output */
17
+ includeNearMisses?: boolean;
18
+ /** When true, compute blast radius for each selected file */
19
+ includeBlastRadius?: boolean;
20
+ /** When true, sort bundle files by architectural layer instead of score */
21
+ layerOrder?: boolean;
12
22
  }
13
23
  export declare function buildBundle(opts: BuildBundleOptions): ContextBundle;
14
24
  //# sourceMappingURL=bundle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EAEN,aAAa,EAEb,SAAS,EACT,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAOnE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAoLnE"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EAEN,aAAa,EAGb,SAAS,EACT,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAenE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAiRnE"}
package/dist/bundle.js CHANGED
@@ -1,18 +1,33 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { generateCandidates, tokenizeTask } from './candidates.js';
3
3
  import { scoreAllCandidates } from './scorer.js';
4
- import { shouldStop, estimateTokens } from './stop-rule.js';
4
+ import { shouldStop } from './stop-rule.js';
5
5
  import { extractExcerpt } from './excerpt.js';
6
6
  import { assessConfidence } from './confidence.js';
7
7
  import { generateInterfaceStubs } from './stubs.js';
8
8
  import { scanDeprecations } from './deprecation.js';
9
+ import { generateExemplars } from './exemplars.js';
10
+ import { validateBundle } from './validation.js';
11
+ import { computeNearMisses } from './near-miss.js';
12
+ import { computeBlastRadius } from './blast-radius.js';
13
+ import { inferTaskType, applyTaskTypeWeights } from './task-type.js';
14
+ import { sortByLayer } from './layer-ordering.js';
15
+ import { estimateTokensByExtension } from './token-calibration.js';
16
+ import { scanTodos } from './todo-scan.js';
9
17
  export function buildBundle(opts) {
10
- const { taskText, repoIndex, selectorConfig, budget = selectorConfig.default_budget, sufficiencyThreshold = selectorConfig.sufficiency_threshold, ledgerStats, explain = false, } = opts;
18
+ const { taskText, repoIndex, selectorConfig, budget = selectorConfig.default_budget, sufficiencyThreshold = selectorConfig.sufficiency_threshold, ledgerStats, explain = false, scope, branchChangedFiles, includeNearMisses = false, includeBlastRadius = false, layerOrder = false, } = opts;
19
+ // Auto-infer task type and apply weight adjustments
20
+ const taskType = inferTaskType(taskText);
21
+ const typeAdjustedWeights = applyTaskTypeWeights(selectorConfig.weights, taskType);
22
+ // Enable branch_changed weight when branch files are provided
23
+ const effectiveWeights = branchChangedFiles && branchChangedFiles.size > 0
24
+ ? { ...typeAdjustedWeights, branch_changed: typeAdjustedWeights.branch_changed ?? 0.15 }
25
+ : typeAdjustedWeights;
11
26
  const keywords = tokenizeTask(taskText);
12
- // Step 1: Generate candidates (includes IDF token weights + fan-out)
13
- const { candidates: candidateSet, tokenWeights, fanoutFiles } = generateCandidates(taskText, repoIndex, selectorConfig);
14
- // Step 2: Score all candidates (with IDF weights + fan-out bonus)
15
- const scored = scoreAllCandidates(candidateSet, keywords, repoIndex, selectorConfig.weights, ledgerStats, tokenWeights, fanoutFiles);
27
+ // Step 1: Generate candidates (includes IDF token weights + fan-out + surfaces)
28
+ const { candidates: candidateSet, tokenWeights, fanoutFiles, surfaceFiles } = generateCandidates(taskText, repoIndex, selectorConfig, scope);
29
+ // Step 2: Score all candidates (with IDF weights + fan-out bonus + branch awareness)
30
+ const scored = scoreAllCandidates(candidateSet, keywords, repoIndex, effectiveWeights, ledgerStats, tokenWeights, fanoutFiles, branchChangedFiles, surfaceFiles);
16
31
  // Step 3: Compute max cumulative score for threshold
17
32
  const maxCumulative = scored.reduce((sum, f) => sum + Math.max(0, f.score), 0);
18
33
  // Step 3.5: Reserve slots for test pairing under tight budgets.
@@ -22,7 +37,9 @@ export function buildBundle(opts) {
22
37
  const reservedTestSlots = tightBudget ? 2 : 0;
23
38
  const mainBudget = {
24
39
  ...budget,
25
- max_files: budget.max_files ? budget.max_files - reservedTestSlots : undefined,
40
+ max_files: budget.max_files
41
+ ? Math.max(1, budget.max_files - reservedTestSlots)
42
+ : undefined,
26
43
  };
27
44
  // Step 4: Iterate with stop rule (using reduced budget to leave test slots)
28
45
  const state = {
@@ -38,7 +55,11 @@ export function buildBundle(opts) {
38
55
  }
39
56
  // Extract excerpt
40
57
  const excerpt = extractExcerpt(repoIndex.root, scoredFile.path, keywords, selectorConfig.excerpt_full_file_max_lines, selectorConfig.excerpt_window_lines);
41
- const tokenEst = estimateTokens(excerpt.lineCount);
58
+ // Language-aware token estimation (Feature 17)
59
+ const fileExt = scoredFile.path.includes('.')
60
+ ? '.' + scoredFile.path.split('.').pop()
61
+ : '';
62
+ const tokenEst = estimateTokensByExtension(excerpt.lineCount, fileExt);
42
63
  // Check if adding this file would blow the budget
43
64
  if (budget.tokens && state.totalTokens + tokenEst > budget.tokens * 1.1) {
44
65
  // Allow 10% overshoot, then stop
@@ -47,6 +68,8 @@ export function buildBundle(opts) {
47
68
  }
48
69
  // Scan for deprecated API patterns
49
70
  const deprecationWarnings = scanDeprecations(excerpt.content, selectorConfig.deprecation_rules);
71
+ // Scan for TODO/FIXME markers (Feature 14)
72
+ const todoResult = scanTodos(excerpt.content);
50
73
  const bundleFile = {
51
74
  path: scoredFile.path,
52
75
  score: Math.round(scoredFile.score * 1000) / 1000,
@@ -55,6 +78,7 @@ export function buildBundle(opts) {
55
78
  content: excerpt.content,
56
79
  token_estimate: tokenEst,
57
80
  deprecation_warnings: deprecationWarnings.length > 0 ? deprecationWarnings : undefined,
81
+ todo_count: todoResult.count > 0 ? todoResult.count : undefined,
58
82
  };
59
83
  state.files.push(bundleFile);
60
84
  state.totalTokens += tokenEst;
@@ -84,7 +108,8 @@ export function buildBundle(opts) {
84
108
  if (budget.max_files && state.files.length >= budget.max_files)
85
109
  break;
86
110
  const excerpt = extractExcerpt(repoIndex.root, testPath, keywords, selectorConfig.excerpt_full_file_max_lines, selectorConfig.excerpt_window_lines);
87
- const tokenEst = estimateTokens(excerpt.lineCount);
111
+ const testExt = testPath.includes('.') ? '.' + testPath.split('.').pop() : '';
112
+ const tokenEst = estimateTokensByExtension(excerpt.lineCount, testExt);
88
113
  if (budget.tokens && state.totalTokens + tokenEst > budget.tokens * 1.1)
89
114
  continue;
90
115
  // Find the scored entry if it exists, otherwise create a minimal one
@@ -115,6 +140,51 @@ export function buildBundle(opts) {
115
140
  }
116
141
  // Step 7: Assess bundle confidence
117
142
  const confidence = assessConfidence(state.files, state.cumulativeScore, keywords.length);
143
+ // Step 8: Build file relationship map from dep_graph edges between selected files
144
+ const allSelectedPaths = new Set(state.files.map((f) => f.path));
145
+ const fileRelationships = [];
146
+ for (const filePath of allSelectedPaths) {
147
+ const imports = repoIndex.dep_graph.imports[filePath] ?? [];
148
+ for (const imp of imports) {
149
+ if (allSelectedPaths.has(imp)) {
150
+ fileRelationships.push({ from: filePath, to: imp });
151
+ }
152
+ }
153
+ }
154
+ // Step 9: Pattern exemplars — include structural siblings for "add new" tasks
155
+ const exemplarBudget = (budget.tokens ?? Infinity) - state.totalTokens;
156
+ const maxExemplars = Math.max(0, (budget.max_files ?? 25) - state.files.length);
157
+ if (maxExemplars > 0 && exemplarBudget > 0) {
158
+ const exemplars = generateExemplars(repoIndex.root, state.files, repoIndex, keywords, Math.min(maxExemplars, 2), // cap at 2 exemplars
159
+ Math.min(exemplarBudget, 2000));
160
+ for (const ex of exemplars) {
161
+ state.files.push(ex);
162
+ state.totalTokens += ex.token_estimate;
163
+ }
164
+ }
165
+ // Step 10: Layer ordering — optionally sort files by architectural layer
166
+ if (layerOrder) {
167
+ // Separate stubs/exemplars, sort main files by layer, recombine
168
+ const mainFiles = state.files.filter((f) => !f.is_stub && !f.is_exemplar);
169
+ const otherFiles = state.files.filter((f) => f.is_stub || f.is_exemplar);
170
+ const sorted = sortByLayer(mainFiles);
171
+ state.files = [...sorted, ...otherFiles];
172
+ }
173
+ // Step 11: Bundle validation — pre-flight warnings
174
+ const warnings = validateBundle(state.files, keywords, scored, taskText);
175
+ // Step 12: Near-miss explanation
176
+ const nearMisses = includeNearMisses
177
+ ? (() => {
178
+ // Build line count map from repo index so near-miss estimation
179
+ // uses actual line counts (avoids size_penalty cap at 1000 lines).
180
+ const fileLineCounts = new Map(repoIndex.files.map((f) => [f.path, f.lines]));
181
+ return computeNearMisses(scored, state.files, state.totalTokens, budget.tokens, undefined, fileLineCounts);
182
+ })()
183
+ : undefined;
184
+ // Step 13: Blast radius annotation
185
+ const blastRadius = includeBlastRadius
186
+ ? computeBlastRadius(state.files, repoIndex.dep_graph, repoIndex.test_map)
187
+ : undefined;
118
188
  return {
119
189
  bundle_id: `bnd_${randomUUID().slice(0, 12)}`,
120
190
  task: taskText,
@@ -126,6 +196,10 @@ export function buildBundle(opts) {
126
196
  generated_at: new Date().toISOString(),
127
197
  confidence,
128
198
  explain: explain ? explainData : undefined,
199
+ file_relationships: fileRelationships.length > 0 ? fileRelationships : undefined,
200
+ warnings: warnings.length > 0 ? warnings : undefined,
201
+ near_misses: nearMisses && nearMisses.length > 0 ? nearMisses : undefined,
202
+ blast_radius: blastRadius && blastRadius.length > 0 ? blastRadius : undefined,
129
203
  };
130
204
  }
131
205
  //# sourceMappingURL=bundle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,19 @@ export interface CandidateResult {
3
3
  candidates: Set<string>;
4
4
  tokenWeights: Map<string, number>;
5
5
  fanoutFiles: Set<string>;
6
+ /** Files injected via surface-aware auto-inclusion (low-priority, reason: surface_auto_include) */
7
+ surfaceFiles: Set<string>;
6
8
  }
7
9
  export declare function tokenizeTask(taskText: string): string[];
10
+ /**
11
+ * Split a compound task into independent clauses for separate keyword extraction.
12
+ *
13
+ * "Fix auth middleware and add payment tests" →
14
+ * [["auth", "middleware"], ["payment", "tests"]]
15
+ *
16
+ * Falls back to a single clause (identical to tokenizeTask) for simple tasks.
17
+ */
18
+ export declare function decomposeTask(taskText: string): string[][];
8
19
  /**
9
20
  * Compute IDF-like token weights based on how many files each keyword matches.
10
21
  *
@@ -16,5 +27,5 @@ export declare function tokenizeTask(taskText: string): string[];
16
27
  * "service" appear everywhere and shouldn't dominate scoring.
17
28
  */
18
29
  export declare function computeTokenWeights(keywords: string[], repoIndex: RepoIndex, hotZoneCount: number): Map<string, number>;
19
- export declare function generateCandidates(taskText: string, repoIndex: RepoIndex, config: SelectorConfig): CandidateResult;
30
+ export declare function generateCandidates(taskText: string, repoIndex: RepoIndex, config: SelectorConfig, scope?: string[]): CandidateResult;
20
31
  //# sourceMappingURL=candidates.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../src/candidates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAoE7E,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAcvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAwCrB;AAyCD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,cAAc,GACrB,eAAe,CAoHjB"}
1
+ {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../src/candidates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAkG7E,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,mGAAmG;IACnG,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAcvD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAkB1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAwCrB;AAyCD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,MAAM,EAAE,GACf,eAAe,CAkMjB"}
@@ -57,6 +57,30 @@ const DEFAULT_CONTRACT_PATTERNS = [
57
57
  const CONTRACT_PATH_INDICATORS = [
58
58
  'openapi', 'swagger', 'schema', 'migrations',
59
59
  ];
60
+ /** Keyword sets for surface-aware auto-inclusion */
61
+ const CONFIG_SURFACE_KEYWORDS = new Set([
62
+ 'config', 'configuration', 'script', 'dependency', 'dependencies', 'setup',
63
+ 'package', 'tsconfig', 'eslint', 'prettier', 'webpack', 'vite', 'rollup',
64
+ ]);
65
+ const ACCEPTANCE_SURFACE_KEYWORDS = new Set([
66
+ 'test', 'tests', 'testing', 'ci', 'pipeline', 'failing', 'pass', 'coverage',
67
+ 'jest', 'vitest', 'mocha', 'cypress', 'playwright',
68
+ ]);
69
+ const CONTRACT_SURFACE_KEYWORDS = new Set([
70
+ 'schema', 'api', 'endpoint', 'migration', 'contract', 'proto', 'graphql',
71
+ 'openapi', 'swagger', 'prisma', 'database',
72
+ ]);
73
+ /** Path patterns for surface-aware auto-inclusion */
74
+ const CONFIG_SURFACE_PATTERNS = [
75
+ 'package.json', 'tsconfig', 'pnpm-workspace', '.env.example',
76
+ 'eslint', 'prettier', 'webpack.config', 'vite.config', 'rollup.config',
77
+ ];
78
+ const ACCEPTANCE_SURFACE_PATTERNS = [
79
+ 'vitest.config', 'jest.config', '.github/workflows', 'ci.yml', 'ci.yaml',
80
+ 'test-setup', 'test-helper', 'test-utils',
81
+ ];
82
+ /** Conjunction / separator patterns for task decomposition */
83
+ const CLAUSE_SPLITTERS = /\b(?:and then|and also|and|then|also|plus)\b|[;,]/i;
60
84
  export function tokenizeTask(taskText) {
61
85
  const lower = taskText.toLowerCase();
62
86
  const isBroadTask = BROAD_TASK_PHRASES.some((re) => re.test(lower));
@@ -69,6 +93,30 @@ export function tokenizeTask(taskText) {
69
93
  .filter((w) => w.length > 1 && !effectiveStopWords.has(w));
70
94
  return [...new Set(words)];
71
95
  }
96
+ /**
97
+ * Split a compound task into independent clauses for separate keyword extraction.
98
+ *
99
+ * "Fix auth middleware and add payment tests" →
100
+ * [["auth", "middleware"], ["payment", "tests"]]
101
+ *
102
+ * Falls back to a single clause (identical to tokenizeTask) for simple tasks.
103
+ */
104
+ export function decomposeTask(taskText) {
105
+ const clauses = taskText.split(CLAUSE_SPLITTERS)
106
+ .map((c) => c.trim())
107
+ .filter((c) => c.length > 0);
108
+ if (clauses.length <= 1) {
109
+ return [tokenizeTask(taskText)];
110
+ }
111
+ const result = [];
112
+ for (const clause of clauses) {
113
+ const keywords = tokenizeTask(clause);
114
+ if (keywords.length > 0) {
115
+ result.push(keywords);
116
+ }
117
+ }
118
+ return result.length > 0 ? result : [tokenizeTask(taskText)];
119
+ }
72
120
  /**
73
121
  * Compute IDF-like token weights based on how many files each keyword matches.
74
122
  *
@@ -144,24 +192,39 @@ function findStrongTargets(keywords, repoIndex) {
144
192
  }
145
193
  return targets;
146
194
  }
147
- export function generateCandidates(taskText, repoIndex, config) {
195
+ export function generateCandidates(taskText, repoIndex, config, scope) {
148
196
  const candidates = new Set();
149
197
  const fanoutFiles = new Set();
150
- const keywords = tokenizeTask(taskText);
198
+ const surfaceFiles = new Set();
199
+ // Resolve effective scope: CLI flag > config default > no scope
200
+ const effectiveScope = scope ?? config.default_scope;
201
+ // Task decomposition: split compound tasks into clauses
202
+ const clauses = decomposeTask(taskText);
203
+ // Flat keyword list for IDF + backward compat (union of all clauses)
204
+ const keywords = [...new Set(clauses.flat())];
151
205
  // Compute IDF token weights
152
206
  const tokenWeights = computeTokenWeights(keywords, repoIndex, config.hot_zone_count);
153
- // 1. Keyword match: file paths + names + content keywords
154
- for (const file of repoIndex.files) {
155
- const pathLower = file.path.toLowerCase();
156
- for (const kw of keywords) {
157
- if (pathLower.includes(kw)) {
158
- candidates.add(file.path);
159
- break;
160
- }
161
- // Content-aware matching: check identifiers extracted during scanning
162
- if (file.content_keywords?.some((ck) => ck === kw || ck === kw + 's' || kw === ck + 's')) {
163
- candidates.add(file.path);
164
- break;
207
+ // Build scope-filtered file list
208
+ const scopedFiles = effectiveScope
209
+ ? repoIndex.files.filter((f) => effectiveScope.some((s) => f.path.startsWith(s)))
210
+ : repoIndex.files;
211
+ // 1. Keyword match: run per-clause to preserve clause-specific file discovery,
212
+ // then union the results. A file matching any clause's keywords is included.
213
+ for (const clauseKeywords of clauses) {
214
+ for (const file of scopedFiles) {
215
+ if (candidates.has(file.path))
216
+ continue;
217
+ const pathLower = file.path.toLowerCase();
218
+ for (const kw of clauseKeywords) {
219
+ if (pathLower.includes(kw)) {
220
+ candidates.add(file.path);
221
+ break;
222
+ }
223
+ // Content-aware matching: check identifiers extracted during scanning
224
+ if (file.content_keywords?.some((ck) => ck === kw || ck === kw + 's' || kw === ck + 's')) {
225
+ candidates.add(file.path);
226
+ break;
227
+ }
165
228
  }
166
229
  }
167
230
  }
@@ -170,18 +233,21 @@ export function generateCandidates(taskText, repoIndex, config) {
170
233
  // because churn-heavy files crowd out task-specific ones. In that case
171
234
  // hot-zone only contributes files that already have a keyword hit.
172
235
  const tightMode = (config.default_budget.max_files ?? Infinity) <= 15;
236
+ const scopedPathSet = new Set(scopedFiles.map((f) => f.path));
173
237
  const hotZone = repoIndex.churn.slice(0, config.hot_zone_count);
174
238
  for (const churnEntry of hotZone) {
175
- if (!repoIndex.files.some((f) => f.path === churnEntry.path))
239
+ if (!scopedPathSet.has(churnEntry.path))
176
240
  continue;
177
241
  if (tightMode && !candidates.has(churnEntry.path))
178
242
  continue;
179
243
  candidates.add(churnEntry.path);
180
244
  }
181
245
  // 3. Dependency neighborhood: expand from keyword-matched files
246
+ // Scope-aware: only add dependencies that fall within the effective scope.
247
+ const scopedPathSet2 = effectiveScope ? scopedPathSet : undefined;
182
248
  const keywordMatched = [...candidates];
183
249
  for (const seed of keywordMatched) {
184
- expandDependencies(seed, repoIndex, candidates, config.dependency_depth, config.dependency_cap_per_seed);
250
+ expandDependencies(seed, repoIndex, candidates, config.dependency_depth, config.dependency_cap_per_seed, scopedPathSet2);
185
251
  }
186
252
  // 4. Fan-out expansion: when task describes a cross-cutting change AND a
187
253
  // strong target file is identified, expand ALL direct dependents (depth=1)
@@ -193,11 +259,13 @@ export function generateCandidates(taskText, repoIndex, config) {
193
259
  break;
194
260
  // Depth 1: all direct dependents — always mark as fanout even if
195
261
  // already discovered by normal dependency expansion, so they receive
196
- // the fan-out scoring bonus.
262
+ // the fan-out scoring bonus. Scope-aware: skip out-of-scope deps.
197
263
  const dependents = repoIndex.dep_graph.dependents[target] ?? [];
198
264
  for (const dep of dependents) {
199
265
  if (candidates.size >= FANOUT_CEILING)
200
266
  break;
267
+ if (scopedPathSet2 && !scopedPathSet2.has(dep))
268
+ continue;
201
269
  candidates.add(dep);
202
270
  fanoutFiles.add(dep);
203
271
  }
@@ -206,10 +274,14 @@ export function generateCandidates(taskText, repoIndex, config) {
206
274
  for (const dep of dependents) {
207
275
  if (candidates.size >= FANOUT_CEILING)
208
276
  break;
277
+ if (scopedPathSet2 && !scopedPathSet2.has(dep))
278
+ continue;
209
279
  const depth2Deps = repoIndex.dep_graph.dependents[dep] ?? [];
210
280
  for (const dep2 of depth2Deps) {
211
281
  if (candidates.size >= FANOUT_CEILING)
212
282
  break;
283
+ if (scopedPathSet2 && !scopedPathSet2.has(dep2))
284
+ continue;
213
285
  candidates.add(dep2);
214
286
  fanoutFiles.add(dep2);
215
287
  }
@@ -217,14 +289,18 @@ export function generateCandidates(taskText, repoIndex, config) {
217
289
  }
218
290
  }
219
291
  }
220
- // 5. Test neighborhood: add test files mapped to candidates
292
+ // 5. Test neighborhood: add test files mapped to candidates (scope-aware)
221
293
  const currentCandidates = [...candidates];
222
294
  for (const mapping of repoIndex.test_map) {
223
295
  if (currentCandidates.includes(mapping.source_file)) {
224
- candidates.add(mapping.test_file);
296
+ if (!scopedPathSet2 || scopedPathSet2.has(mapping.test_file)) {
297
+ candidates.add(mapping.test_file);
298
+ }
225
299
  }
226
300
  if (currentCandidates.includes(mapping.test_file)) {
227
- candidates.add(mapping.source_file);
301
+ if (!scopedPathSet2 || scopedPathSet2.has(mapping.source_file)) {
302
+ candidates.add(mapping.source_file);
303
+ }
228
304
  }
229
305
  }
230
306
  // 6. Contract/schema auto-inclusion: when the bundle touches DB models,
@@ -246,7 +322,52 @@ export function generateCandidates(taskText, repoIndex, config) {
246
322
  }
247
323
  }
248
324
  }
249
- return { candidates, tokenWeights, fanoutFiles };
325
+ // 7. Surface-aware auto-inclusion: detect task intent and include
326
+ // config, acceptance, or contract surface files that wouldn't be found
327
+ // by keyword matching (rarely edited, rarely named in tasks).
328
+ const taskWords = new Set(keywords);
329
+ const wantsConfig = [...CONFIG_SURFACE_KEYWORDS].some((k) => taskWords.has(k));
330
+ const wantsAcceptance = [...ACCEPTANCE_SURFACE_KEYWORDS].some((k) => taskWords.has(k));
331
+ const wantsContract = [...CONTRACT_SURFACE_KEYWORDS].some((k) => taskWords.has(k));
332
+ if (wantsConfig || wantsAcceptance || wantsContract) {
333
+ // For contract surfaces, reuse the directory-proximity check from step 6
334
+ // to avoid pulling in unrelated contract files from distant directories.
335
+ const candidateDirsForSurface = wantsContract
336
+ ? new Set([...candidates].map((p) => p.split('/').slice(0, -1).join('/')))
337
+ : undefined;
338
+ for (const file of scopedFiles) {
339
+ if (candidates.has(file.path))
340
+ continue;
341
+ const pathLower = file.path.toLowerCase();
342
+ if (wantsConfig && CONFIG_SURFACE_PATTERNS.some((p) => pathLower.includes(p))) {
343
+ candidates.add(file.path);
344
+ surfaceFiles.add(file.path);
345
+ continue;
346
+ }
347
+ if (wantsAcceptance && ACCEPTANCE_SURFACE_PATTERNS.some((p) => pathLower.includes(p))) {
348
+ candidates.add(file.path);
349
+ surfaceFiles.add(file.path);
350
+ continue;
351
+ }
352
+ if (wantsContract && candidateDirsForSurface) {
353
+ const ext = file.extension.toLowerCase();
354
+ const basename = pathLower.split('/').pop() ?? '';
355
+ const isContract = DEFAULT_CONTRACT_PATTERNS.includes(ext) ||
356
+ CONTRACT_PATH_INDICATORS.some((ind) => basename.startsWith(ind));
357
+ if (isContract) {
358
+ // Only include if the contract shares a directory with an existing candidate
359
+ const cfDir = file.path.split('/').slice(0, -1).join('/');
360
+ const isRelated = candidateDirsForSurface.has(cfDir) ||
361
+ [...candidateDirsForSurface].some((d) => d.startsWith(cfDir + '/') || cfDir.startsWith(d + '/'));
362
+ if (isRelated) {
363
+ candidates.add(file.path);
364
+ surfaceFiles.add(file.path);
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+ return { candidates, tokenWeights, fanoutFiles, surfaceFiles };
250
371
  }
251
372
  /**
252
373
  * Identify contract/schema files in the repo based on extension and path patterns.
@@ -274,7 +395,7 @@ function findContractFiles(files, customPatterns) {
274
395
  }
275
396
  return results;
276
397
  }
277
- function expandDependencies(seed, repoIndex, candidates, maxDepth, capPerSeed) {
398
+ function expandDependencies(seed, repoIndex, candidates, maxDepth, capPerSeed, scopedPaths) {
278
399
  let frontier = [seed];
279
400
  let added = 0;
280
401
  for (let depth = 0; depth < maxDepth && added < capPerSeed; depth++) {
@@ -283,6 +404,8 @@ function expandDependencies(seed, repoIndex, candidates, maxDepth, capPerSeed) {
283
404
  // Add imports
284
405
  const imports = repoIndex.dep_graph.imports[file] ?? [];
285
406
  for (const dep of imports) {
407
+ if (scopedPaths && !scopedPaths.has(dep))
408
+ continue;
286
409
  if (!candidates.has(dep) && added < capPerSeed) {
287
410
  candidates.add(dep);
288
411
  nextFrontier.push(dep);
@@ -292,6 +415,8 @@ function expandDependencies(seed, repoIndex, candidates, maxDepth, capPerSeed) {
292
415
  // Add dependents (reverse)
293
416
  const dependents = repoIndex.dep_graph.dependents[file] ?? [];
294
417
  for (const dep of dependents) {
418
+ if (scopedPaths && !scopedPaths.has(dep))
419
+ continue;
295
420
  if (!candidates.has(dep) && added < capPerSeed) {
296
421
  candidates.add(dep);
297
422
  nextFrontier.push(dep);