@codeledger/selector 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Intelligent Context AI, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Note: This license applies to the CLI wrapper, types, repository scanning,
26
+ instrumentation, harness, and report packages (the "Plugin"). The scoring
27
+ engine (packages/core-engine/bin/) is licensed separately under LICENSE-CORE.
@@ -0,0 +1,14 @@
1
+ import type { Budget, ContextBundle, RepoIndex, SelectorConfig } from '@codeledger/types';
2
+ import { type LedgerStats } from './scorer.js';
3
+ export interface BuildBundleOptions {
4
+ taskText: string;
5
+ repoIndex: RepoIndex;
6
+ selectorConfig: SelectorConfig;
7
+ budget?: Budget;
8
+ sufficiencyThreshold?: number;
9
+ ledgerStats?: LedgerStats;
10
+ /** When true, include per-file scoring breakdown in the bundle */
11
+ explain?: boolean;
12
+ }
13
+ export declare function buildBundle(opts: BuildBundleOptions): ContextBundle;
14
+ //# sourceMappingURL=bundle.d.ts.map
@@ -0,0 +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"}
package/dist/bundle.js ADDED
@@ -0,0 +1,131 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { generateCandidates, tokenizeTask } from './candidates.js';
3
+ import { scoreAllCandidates } from './scorer.js';
4
+ import { shouldStop, estimateTokens } from './stop-rule.js';
5
+ import { extractExcerpt } from './excerpt.js';
6
+ import { assessConfidence } from './confidence.js';
7
+ import { generateInterfaceStubs } from './stubs.js';
8
+ import { scanDeprecations } from './deprecation.js';
9
+ export function buildBundle(opts) {
10
+ const { taskText, repoIndex, selectorConfig, budget = selectorConfig.default_budget, sufficiencyThreshold = selectorConfig.sufficiency_threshold, ledgerStats, explain = false, } = opts;
11
+ 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);
16
+ // Step 3: Compute max cumulative score for threshold
17
+ const maxCumulative = scored.reduce((sum, f) => sum + Math.max(0, f.score), 0);
18
+ // Step 3.5: Reserve slots for test pairing under tight budgets.
19
+ // Under generous budgets, test pairing uses leftover space. Under tight
20
+ // budgets (max_files <= 15), reserve 2 slots so tests aren't crowded out.
21
+ const tightBudget = (budget.max_files ?? Infinity) <= 15;
22
+ const reservedTestSlots = tightBudget ? 2 : 0;
23
+ const mainBudget = {
24
+ ...budget,
25
+ max_files: budget.max_files ? budget.max_files - reservedTestSlots : undefined,
26
+ };
27
+ // Step 4: Iterate with stop rule (using reduced budget to leave test slots)
28
+ const state = {
29
+ files: [],
30
+ totalTokens: 0,
31
+ cumulativeScore: 0,
32
+ };
33
+ // Collect explain data if requested
34
+ const explainData = {};
35
+ for (const scoredFile of scored) {
36
+ if (shouldStop(state, mainBudget, sufficiencyThreshold, maxCumulative)) {
37
+ break;
38
+ }
39
+ // Extract excerpt
40
+ const excerpt = extractExcerpt(repoIndex.root, scoredFile.path, keywords, selectorConfig.excerpt_full_file_max_lines, selectorConfig.excerpt_window_lines);
41
+ const tokenEst = estimateTokens(excerpt.lineCount);
42
+ // Check if adding this file would blow the budget
43
+ if (budget.tokens && state.totalTokens + tokenEst > budget.tokens * 1.1) {
44
+ // Allow 10% overshoot, then stop
45
+ if (state.files.length > 0)
46
+ break;
47
+ }
48
+ // Scan for deprecated API patterns
49
+ const deprecationWarnings = scanDeprecations(excerpt.content, selectorConfig.deprecation_rules);
50
+ const bundleFile = {
51
+ path: scoredFile.path,
52
+ score: Math.round(scoredFile.score * 1000) / 1000,
53
+ reasons: scoredFile.reasons,
54
+ excerpt_spans: excerpt.spans,
55
+ content: excerpt.content,
56
+ token_estimate: tokenEst,
57
+ deprecation_warnings: deprecationWarnings.length > 0 ? deprecationWarnings : undefined,
58
+ };
59
+ state.files.push(bundleFile);
60
+ state.totalTokens += tokenEst;
61
+ state.cumulativeScore += Math.max(0, scoredFile.score);
62
+ if (explain) {
63
+ explainData[scoredFile.path] = scoredFile.features;
64
+ }
65
+ }
66
+ // Step 5: Deterministic test pairing — if a source file was selected and
67
+ // it has a mapped test, auto-include the test unless we're at the hard cap.
68
+ // Pairs are sorted by source file score so the most relevant tests fill
69
+ // reserved slots first. This prevents test starvation under tight budgets.
70
+ const selectedPaths = new Set(state.files.map((f) => f.path));
71
+ const sourceScoreMap = new Map(state.files.map((f) => [f.path, f.score]));
72
+ const testPairCandidates = [];
73
+ for (const mapping of repoIndex.test_map) {
74
+ if (selectedPaths.has(mapping.source_file) && !selectedPaths.has(mapping.test_file)) {
75
+ testPairCandidates.push({
76
+ testPath: mapping.test_file,
77
+ sourceScore: sourceScoreMap.get(mapping.source_file) ?? 0,
78
+ });
79
+ }
80
+ }
81
+ // Sort by source score descending — pair tests for highest-scored files first
82
+ testPairCandidates.sort((a, b) => b.sourceScore - a.sourceScore);
83
+ for (const { testPath } of testPairCandidates) {
84
+ if (budget.max_files && state.files.length >= budget.max_files)
85
+ break;
86
+ const excerpt = extractExcerpt(repoIndex.root, testPath, keywords, selectorConfig.excerpt_full_file_max_lines, selectorConfig.excerpt_window_lines);
87
+ const tokenEst = estimateTokens(excerpt.lineCount);
88
+ if (budget.tokens && state.totalTokens + tokenEst > budget.tokens * 1.1)
89
+ continue;
90
+ // Find the scored entry if it exists, otherwise create a minimal one
91
+ const scoredEntry = scored.find((s) => s.path === testPath);
92
+ state.files.push({
93
+ path: testPath,
94
+ score: scoredEntry ? Math.round(scoredEntry.score * 1000) / 1000 : 0,
95
+ reasons: scoredEntry?.reasons ?? ['test_relevant'],
96
+ excerpt_spans: excerpt.spans,
97
+ content: excerpt.content,
98
+ token_estimate: tokenEst,
99
+ });
100
+ state.totalTokens += tokenEst;
101
+ }
102
+ // Step 6: Generate interface stubs for unselected dependencies.
103
+ // These give the agent the type signatures of files adjacent to the bundle
104
+ // without the full token cost.
105
+ const finalSelectedPaths = new Set(state.files.map((f) => f.path));
106
+ const tokenBudgetRemaining = (budget.tokens ?? Infinity) - state.totalTokens;
107
+ const maxStubs = Math.max(0, (budget.max_files ?? 25) - state.files.length);
108
+ if (maxStubs > 0 && tokenBudgetRemaining > 0) {
109
+ const stubs = generateInterfaceStubs(repoIndex.root, finalSelectedPaths, repoIndex.dep_graph, Math.min(maxStubs, 5), // cap at 5 stubs
110
+ Math.min(tokenBudgetRemaining, 1000));
111
+ for (const stub of stubs) {
112
+ state.files.push(stub);
113
+ state.totalTokens += stub.token_estimate;
114
+ }
115
+ }
116
+ // Step 7: Assess bundle confidence
117
+ const confidence = assessConfidence(state.files, state.cumulativeScore, keywords.length);
118
+ return {
119
+ bundle_id: `bnd_${randomUUID().slice(0, 12)}`,
120
+ task: taskText,
121
+ budget,
122
+ sufficiency_threshold: sufficiencyThreshold,
123
+ cumulative_score: Math.round(state.cumulativeScore * 1000) / 1000,
124
+ files: state.files,
125
+ total_tokens: state.totalTokens,
126
+ generated_at: new Date().toISOString(),
127
+ confidence,
128
+ explain: explain ? explainData : undefined,
129
+ };
130
+ }
131
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { RepoIndex, SelectorConfig } from '@codeledger/types';
2
+ export interface CandidateResult {
3
+ candidates: Set<string>;
4
+ tokenWeights: Map<string, number>;
5
+ fanoutFiles: Set<string>;
6
+ }
7
+ export declare function tokenizeTask(taskText: string): string[];
8
+ /**
9
+ * Compute IDF-like token weights based on how many files each keyword matches.
10
+ *
11
+ * idf = 1 / sqrt(max(1, matchCount)), clamped to [0.35, 1.0].
12
+ *
13
+ * Additional penalties:
14
+ * - Short tokens (length <= 2): floor to 0.35 — "ts", "db", "id" are noise.
15
+ * - Generic tokens (matchCount > hotZoneCount): floor to 0.35 — "api",
16
+ * "service" appear everywhere and shouldn't dominate scoring.
17
+ */
18
+ 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;
20
+ //# sourceMappingURL=candidates.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,305 @@
1
+ const STOP_WORDS = new Set([
2
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
3
+ 'of', 'with', 'by', 'from', 'is', 'it', 'that', 'this', 'be', 'as',
4
+ 'are', 'was', 'were', 'been', 'being', 'have', 'has', 'had', 'do',
5
+ 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might',
6
+ 'shall', 'can', 'need', 'must', 'not', 'no', 'if', 'then', 'else',
7
+ 'when', 'up', 'out', 'so', 'than', 'too', 'very', 'just', 'about',
8
+ 'into', 'over', 'after', 'before', 'between', 'under', 'above',
9
+ 'all', 'each', 'every', 'both', 'few', 'more', 'most', 'some', 'any',
10
+ 'add', 'fix', 'update', 'change', 'make', 'use', 'get', 'set',
11
+ 'find', 'check', 'look', 'see', 'know', 'think', 'want', 'try',
12
+ 'run', 'show', 'help', 'work', 'take', 'give', 'tell', 'call',
13
+ 'go', 'come', 'keep', 'let', 'begin', 'start', 'end', 'stop',
14
+ 'ready', 'live', 'done', 'good', 'new', 'old', 'big', 'small',
15
+ ]);
16
+ /**
17
+ * Words that are contextual: they act as stop words in broad/assessment tasks
18
+ * but are useful keywords in targeted coding tasks.
19
+ *
20
+ * "test the app for readiness" -> "test" is noise (broad assessment)
21
+ * "fix the test for authService" -> "test" is signal (specific target)
22
+ *
23
+ * Heuristic: if the task contains broad-assessment phrases, demote these
24
+ * words by treating them as stop words.
25
+ */
26
+ const CONTEXTUAL_WORDS = new Set([
27
+ 'test', 'tests', 'testing', 'build', 'lint', 'debug', 'deploy',
28
+ 'review', 'audit', 'evaluate', 'assess', 'analyze', 'examine',
29
+ 'application', 'app', 'project', 'codebase', 'code', 'repo',
30
+ ]);
31
+ /** Phrases indicating a broad assessment task rather than a focused coding task.
32
+ * Matched with word boundaries to avoid false positives
33
+ * (e.g. "full" in "full-text search"). */
34
+ const BROAD_TASK_PHRASES = [
35
+ 'ready for', 'go live', 'production ready', 'readiness',
36
+ 'find out', 'evaluate', 'assess', 'audit', 'review the',
37
+ 'status of', 'state of', 'quality of', 'health of',
38
+ 'overall', 'end to end', 'full', 'comprehensive',
39
+ ].map((phrase) => new RegExp(`\\b${phrase}\\b`));
40
+ /**
41
+ * Phrases that signal cross-cutting fan-out intent: the task wants to touch
42
+ * every file that depends on a target module.
43
+ * Matched with word boundaries to prevent false positives
44
+ * (e.g. "replace" in "irreplaceable").
45
+ */
46
+ const FANOUT_TRIGGERS = [
47
+ 'replace', 'everywhere', 'all files', 'every file',
48
+ 'migrate', 'files that import', 'files that use',
49
+ 'across all', 'update all', 'rename all',
50
+ ].map((phrase) => new RegExp(`\\b${phrase}\\b`));
51
+ const FANOUT_CEILING = 50;
52
+ /** Default contract/schema patterns when not configured */
53
+ const DEFAULT_CONTRACT_PATTERNS = [
54
+ '.sql', '.proto', '.prisma', '.graphql', '.gql',
55
+ ];
56
+ /** Path segments that indicate contract/schema files */
57
+ const CONTRACT_PATH_INDICATORS = [
58
+ 'openapi', 'swagger', 'schema', 'migrations',
59
+ ];
60
+ export function tokenizeTask(taskText) {
61
+ const lower = taskText.toLowerCase();
62
+ const isBroadTask = BROAD_TASK_PHRASES.some((re) => re.test(lower));
63
+ // Build effective stop word set: include contextual words for broad tasks
64
+ const effectiveStopWords = isBroadTask
65
+ ? new Set([...STOP_WORDS, ...CONTEXTUAL_WORDS])
66
+ : STOP_WORDS;
67
+ const words = lower
68
+ .split(/[^\p{L}\p{N}]+/u)
69
+ .filter((w) => w.length > 1 && !effectiveStopWords.has(w));
70
+ return [...new Set(words)];
71
+ }
72
+ /**
73
+ * Compute IDF-like token weights based on how many files each keyword matches.
74
+ *
75
+ * idf = 1 / sqrt(max(1, matchCount)), clamped to [0.35, 1.0].
76
+ *
77
+ * Additional penalties:
78
+ * - Short tokens (length <= 2): floor to 0.35 — "ts", "db", "id" are noise.
79
+ * - Generic tokens (matchCount > hotZoneCount): floor to 0.35 — "api",
80
+ * "service" appear everywhere and shouldn't dominate scoring.
81
+ */
82
+ export function computeTokenWeights(keywords, repoIndex, hotZoneCount) {
83
+ const weights = new Map();
84
+ for (const kw of keywords) {
85
+ let matchCount = 0;
86
+ for (const file of repoIndex.files) {
87
+ // Count matches from both path and content keywords
88
+ if (file.path.toLowerCase().includes(kw)) {
89
+ matchCount++;
90
+ }
91
+ else if (file.content_keywords?.some((ck) => ck === kw || ck === kw + 's' || kw === ck + 's')) {
92
+ matchCount++;
93
+ }
94
+ }
95
+ // Keywords matching zero files are noise — weight 0 so they don't
96
+ // inflate the keyword normalization denominator.
97
+ if (matchCount === 0) {
98
+ weights.set(kw, 0);
99
+ continue;
100
+ }
101
+ let idf = 1 / Math.sqrt(Math.max(1, matchCount));
102
+ // Short tokens (≤2 chars): weight floor
103
+ if (kw.length <= 2) {
104
+ idf = Math.min(idf, 0.35);
105
+ }
106
+ // Generic token penalty: matchCount > hotZoneCount → floor
107
+ if (matchCount > hotZoneCount) {
108
+ idf = Math.min(idf, 0.35);
109
+ }
110
+ // Clamp to [0.35, 1.0]
111
+ idf = Math.max(0.35, Math.min(1.0, idf));
112
+ weights.set(kw, idf);
113
+ }
114
+ return weights;
115
+ }
116
+ /**
117
+ * Detect whether the task text signals cross-cutting fan-out intent.
118
+ */
119
+ function detectFanoutIntent(taskText) {
120
+ const lower = taskText.toLowerCase();
121
+ return FANOUT_TRIGGERS.some((re) => re.test(lower));
122
+ }
123
+ /**
124
+ * Find files whose basename stem matches a keyword exactly (with plural
125
+ * tolerance). These are "strong targets" for fan-out expansion.
126
+ *
127
+ * Stem splitting: basename without extension, split on `-` and `_`.
128
+ * e.g. "rate-limiter.ts" → ["rate", "limiter"]
129
+ * "helpers.ts" → ["helpers"]
130
+ */
131
+ function findStrongTargets(keywords, repoIndex) {
132
+ const targets = [];
133
+ for (const file of repoIndex.files) {
134
+ const pathLower = file.path.toLowerCase();
135
+ const basename = pathLower.split('/').pop()?.replace(/\.\w+$/, '') ?? '';
136
+ const stems = basename.split(/[-_]/);
137
+ for (const kw of keywords) {
138
+ const isStemMatch = stems.some((stem) => stem === kw || stem === kw + 's' || kw === stem + 's');
139
+ if (isStemMatch) {
140
+ targets.push(file.path);
141
+ break;
142
+ }
143
+ }
144
+ }
145
+ return targets;
146
+ }
147
+ export function generateCandidates(taskText, repoIndex, config) {
148
+ const candidates = new Set();
149
+ const fanoutFiles = new Set();
150
+ const keywords = tokenizeTask(taskText);
151
+ // Compute IDF token weights
152
+ 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;
165
+ }
166
+ }
167
+ }
168
+ // 2. Hot zone: top N by churn — only when budget is generous.
169
+ // Under tight budgets (max_files <= 15), hot-zone is a precision killer
170
+ // because churn-heavy files crowd out task-specific ones. In that case
171
+ // hot-zone only contributes files that already have a keyword hit.
172
+ const tightMode = (config.default_budget.max_files ?? Infinity) <= 15;
173
+ const hotZone = repoIndex.churn.slice(0, config.hot_zone_count);
174
+ for (const churnEntry of hotZone) {
175
+ if (!repoIndex.files.some((f) => f.path === churnEntry.path))
176
+ continue;
177
+ if (tightMode && !candidates.has(churnEntry.path))
178
+ continue;
179
+ candidates.add(churnEntry.path);
180
+ }
181
+ // 3. Dependency neighborhood: expand from keyword-matched files
182
+ const keywordMatched = [...candidates];
183
+ for (const seed of keywordMatched) {
184
+ expandDependencies(seed, repoIndex, candidates, config.dependency_depth, config.dependency_cap_per_seed);
185
+ }
186
+ // 4. Fan-out expansion: when task describes a cross-cutting change AND a
187
+ // strong target file is identified, expand ALL direct dependents (depth=1)
188
+ // without per-seed cap. Optionally expand depth=2 if under ceiling.
189
+ if (detectFanoutIntent(taskText)) {
190
+ const strongTargets = findStrongTargets(keywords, repoIndex);
191
+ for (const target of strongTargets) {
192
+ if (candidates.size >= FANOUT_CEILING)
193
+ break;
194
+ // Depth 1: all direct dependents — always mark as fanout even if
195
+ // already discovered by normal dependency expansion, so they receive
196
+ // the fan-out scoring bonus.
197
+ const dependents = repoIndex.dep_graph.dependents[target] ?? [];
198
+ for (const dep of dependents) {
199
+ if (candidates.size >= FANOUT_CEILING)
200
+ break;
201
+ candidates.add(dep);
202
+ fanoutFiles.add(dep);
203
+ }
204
+ // Depth 2: only if under ceiling
205
+ if (candidates.size < FANOUT_CEILING) {
206
+ for (const dep of dependents) {
207
+ if (candidates.size >= FANOUT_CEILING)
208
+ break;
209
+ const depth2Deps = repoIndex.dep_graph.dependents[dep] ?? [];
210
+ for (const dep2 of depth2Deps) {
211
+ if (candidates.size >= FANOUT_CEILING)
212
+ break;
213
+ candidates.add(dep2);
214
+ fanoutFiles.add(dep2);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ // 5. Test neighborhood: add test files mapped to candidates
221
+ const currentCandidates = [...candidates];
222
+ for (const mapping of repoIndex.test_map) {
223
+ if (currentCandidates.includes(mapping.source_file)) {
224
+ candidates.add(mapping.test_file);
225
+ }
226
+ if (currentCandidates.includes(mapping.test_file)) {
227
+ candidates.add(mapping.source_file);
228
+ }
229
+ }
230
+ // 6. Contract/schema auto-inclusion: when the bundle touches DB models,
231
+ // API routes, or services, auto-include related contract files (.sql,
232
+ // .proto, .prisma, .graphql, openapi specs) so the agent sees the
233
+ // actual data model instead of guessing.
234
+ const contractFiles = findContractFiles(repoIndex.files, config.contract_patterns);
235
+ if (contractFiles.length > 0 && candidates.size > 0) {
236
+ // Only include contracts that share a directory prefix with existing candidates
237
+ const candidateDirs = new Set([...candidates].map((p) => p.split('/').slice(0, -1).join('/')));
238
+ for (const cf of contractFiles) {
239
+ const cfDir = cf.split('/').slice(0, -1).join('/');
240
+ // Include if the contract shares a directory with any candidate,
241
+ // or if any candidate is in a subdirectory of the contract's dir
242
+ const isRelated = candidateDirs.has(cfDir) ||
243
+ [...candidateDirs].some((d) => d.startsWith(cfDir + '/') || cfDir.startsWith(d + '/'));
244
+ if (isRelated) {
245
+ candidates.add(cf);
246
+ }
247
+ }
248
+ }
249
+ return { candidates, tokenWeights, fanoutFiles };
250
+ }
251
+ /**
252
+ * Identify contract/schema files in the repo based on extension and path patterns.
253
+ */
254
+ function findContractFiles(files, customPatterns) {
255
+ const results = [];
256
+ for (const file of files) {
257
+ const ext = file.extension.toLowerCase();
258
+ const pathLower = file.path.toLowerCase();
259
+ // Match by extension
260
+ if (DEFAULT_CONTRACT_PATTERNS.includes(ext)) {
261
+ results.push(file.path);
262
+ continue;
263
+ }
264
+ // Match by custom patterns
265
+ if (customPatterns?.some((pat) => pathLower.includes(pat.toLowerCase()))) {
266
+ results.push(file.path);
267
+ continue;
268
+ }
269
+ // Match by path indicators (openapi.yaml, schema.json, etc.)
270
+ const basename = pathLower.split('/').pop() ?? '';
271
+ if (CONTRACT_PATH_INDICATORS.some((ind) => basename.startsWith(ind))) {
272
+ results.push(file.path);
273
+ }
274
+ }
275
+ return results;
276
+ }
277
+ function expandDependencies(seed, repoIndex, candidates, maxDepth, capPerSeed) {
278
+ let frontier = [seed];
279
+ let added = 0;
280
+ for (let depth = 0; depth < maxDepth && added < capPerSeed; depth++) {
281
+ const nextFrontier = [];
282
+ for (const file of frontier) {
283
+ // Add imports
284
+ const imports = repoIndex.dep_graph.imports[file] ?? [];
285
+ for (const dep of imports) {
286
+ if (!candidates.has(dep) && added < capPerSeed) {
287
+ candidates.add(dep);
288
+ nextFrontier.push(dep);
289
+ added++;
290
+ }
291
+ }
292
+ // Add dependents (reverse)
293
+ const dependents = repoIndex.dep_graph.dependents[file] ?? [];
294
+ for (const dep of dependents) {
295
+ if (!candidates.has(dep) && added < capPerSeed) {
296
+ candidates.add(dep);
297
+ nextFrontier.push(dep);
298
+ added++;
299
+ }
300
+ }
301
+ }
302
+ frontier = nextFrontier;
303
+ }
304
+ }
305
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../src/candidates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { BundleConfidence, BundleFile } from '@codeledger/types';
2
+ /**
3
+ * Assess bundle confidence based on signal quality.
4
+ *
5
+ * A high-confidence bundle has strong keyword matches, concentrated scores,
6
+ * and at least one file with a keyword_match reason in the top positions.
7
+ * A low-confidence bundle was driven by churn/centrality noise with weak
8
+ * keyword signal — the developer should refine their task description.
9
+ */
10
+ export declare function assessConfidence(files: BundleFile[], _cumulativeScore: number, taskKeywordCount: number): BundleConfidence;
11
+ //# sourceMappingURL=confidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confidence.d.ts","sourceRoot":"","sources":["../src/confidence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAC;AAEvF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,EAAE,EACnB,gBAAgB,EAAE,MAAM,EACxB,gBAAgB,EAAE,MAAM,GACvB,gBAAgB,CA+ElB"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Assess bundle confidence based on signal quality.
3
+ *
4
+ * A high-confidence bundle has strong keyword matches, concentrated scores,
5
+ * and at least one file with a keyword_match reason in the top positions.
6
+ * A low-confidence bundle was driven by churn/centrality noise with weak
7
+ * keyword signal — the developer should refine their task description.
8
+ */
9
+ export function assessConfidence(files, _cumulativeScore, taskKeywordCount) {
10
+ const reasons = [];
11
+ let score = 0;
12
+ if (files.length === 0) {
13
+ return { level: 'low', score: 0, reasons: ['Empty bundle — no files matched'] };
14
+ }
15
+ // Factor 1: Keyword coverage — do top files have keyword matches?
16
+ const topFiles = files.slice(0, 5);
17
+ const keywordHits = topFiles.filter((f) => f.reasons.includes('keyword_match'));
18
+ const keywordRatio = keywordHits.length / topFiles.length;
19
+ if (keywordRatio >= 0.6) {
20
+ score += 0.35;
21
+ reasons.push(`Strong keyword signal: ${keywordHits.length}/${topFiles.length} top files match task keywords`);
22
+ }
23
+ else if (keywordRatio > 0) {
24
+ score += 0.15;
25
+ reasons.push(`Weak keyword signal: only ${keywordHits.length}/${topFiles.length} top files match task keywords`);
26
+ }
27
+ else {
28
+ reasons.push('No keyword matches in top 5 files — selection driven by churn/centrality');
29
+ }
30
+ // Factor 2: Score concentration — is the top file significantly better?
31
+ if (files.length >= 2) {
32
+ const topScore = files[0].score;
33
+ const secondScore = files[1].score;
34
+ if (topScore > 0 && secondScore > 0) {
35
+ const gap = (topScore - secondScore) / topScore;
36
+ if (gap >= 0.3) {
37
+ score += 0.2;
38
+ reasons.push('Clear top-ranked file with significant score gap');
39
+ }
40
+ else {
41
+ score += 0.1;
42
+ }
43
+ }
44
+ }
45
+ // Factor 3: Reason diversity — files selected for multiple reasons are stronger
46
+ const avgReasons = files.reduce((sum, f) => sum + f.reasons.length, 0) / files.length;
47
+ if (avgReasons >= 2.0) {
48
+ score += 0.2;
49
+ reasons.push('Files selected for multiple independent reasons');
50
+ }
51
+ else if (avgReasons >= 1.5) {
52
+ score += 0.1;
53
+ }
54
+ // Factor 4: Task specificity — more keywords = more discriminating
55
+ if (taskKeywordCount >= 3) {
56
+ score += 0.15;
57
+ }
58
+ else if (taskKeywordCount >= 2) {
59
+ score += 0.1;
60
+ }
61
+ else {
62
+ reasons.push('Task description has few discriminating keywords — consider being more specific');
63
+ }
64
+ // Factor 5: Bundle isn't just test files
65
+ const nonTestFiles = files.filter((f) => !f.reasons.every((r) => r === 'test_relevant'));
66
+ if (nonTestFiles.length >= files.length * 0.5) {
67
+ score += 0.1;
68
+ }
69
+ // Determine level
70
+ let level;
71
+ if (score >= 0.65) {
72
+ level = 'high';
73
+ }
74
+ else if (score >= 0.35) {
75
+ level = 'medium';
76
+ }
77
+ else {
78
+ level = 'low';
79
+ }
80
+ return {
81
+ level,
82
+ score: Math.round(score * 100) / 100,
83
+ reasons,
84
+ };
85
+ }
86
+ //# sourceMappingURL=confidence.js.map