@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.
- package/dist/blast-radius.d.ts +7 -0
- package/dist/blast-radius.d.ts.map +1 -0
- package/dist/blast-radius.js +105 -0
- package/dist/blast-radius.js.map +1 -0
- package/dist/bundle.d.ts +10 -0
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +83 -9
- package/dist/bundle.js.map +1 -1
- package/dist/candidates.d.ts +12 -1
- package/dist/candidates.d.ts.map +1 -1
- package/dist/candidates.js +147 -22
- package/dist/candidates.js.map +1 -1
- package/dist/confidence.d.ts.map +1 -1
- package/dist/confidence.js +31 -0
- package/dist/confidence.js.map +1 -1
- package/dist/exemplars.d.ts +8 -0
- package/dist/exemplars.d.ts.map +1 -0
- package/dist/exemplars.js +122 -0
- package/dist/exemplars.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/layer-ordering.d.ts +37 -0
- package/dist/layer-ordering.d.ts.map +1 -0
- package/dist/layer-ordering.js +93 -0
- package/dist/layer-ordering.js.map +1 -0
- package/dist/near-miss.d.ts +9 -0
- package/dist/near-miss.d.ts.map +1 -0
- package/dist/near-miss.js +78 -0
- package/dist/near-miss.js.map +1 -0
- package/dist/scorer.d.ts +2 -2
- package/dist/scorer.d.ts.map +1 -1
- package/dist/scorer.js +23 -6
- package/dist/scorer.js.map +1 -1
- package/dist/stubs.d.ts.map +1 -1
- package/dist/stubs.js +4 -3
- package/dist/stubs.js.map +1 -1
- package/dist/task-type.d.ts +28 -0
- package/dist/task-type.d.ts.map +1 -0
- package/dist/task-type.js +115 -0
- package/dist/task-type.js.map +1 -0
- package/dist/todo-scan.d.ts +31 -0
- package/dist/todo-scan.d.ts.map +1 -0
- package/dist/todo-scan.js +63 -0
- package/dist/todo-scan.js.map +1 -0
- package/dist/token-calibration.d.ts +27 -0
- package/dist/token-calibration.d.ts.map +1 -0
- package/dist/token-calibration.js +113 -0
- package/dist/token-calibration.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +108 -0
- package/dist/validation.js.map +1 -0
- 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":"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"}
|
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
|
package/dist/bundle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EAEN,aAAa,
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
package/dist/bundle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
|
package/dist/candidates.d.ts
CHANGED
|
@@ -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
|
package/dist/candidates.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/candidates.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|