@promptwheel/core 0.7.13 → 0.7.17
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/codebase-index/ast-analysis.d.ts +57 -0
- package/dist/codebase-index/ast-analysis.d.ts.map +1 -0
- package/dist/codebase-index/ast-analysis.js +325 -0
- package/dist/codebase-index/ast-analysis.js.map +1 -0
- package/dist/codebase-index/ast-cache.d.ts +29 -0
- package/dist/codebase-index/ast-cache.d.ts.map +1 -0
- package/dist/codebase-index/ast-cache.js +75 -0
- package/dist/codebase-index/ast-cache.js.map +1 -0
- package/dist/codebase-index/dead-code.d.ts +41 -0
- package/dist/codebase-index/dead-code.d.ts.map +1 -0
- package/dist/codebase-index/dead-code.js +158 -0
- package/dist/codebase-index/dead-code.js.map +1 -0
- package/dist/codebase-index/format-analysis.d.ts +18 -0
- package/dist/codebase-index/format-analysis.d.ts.map +1 -0
- package/dist/codebase-index/format-analysis.js +191 -0
- package/dist/codebase-index/format-analysis.js.map +1 -0
- package/dist/codebase-index/index.d.ts +7 -3
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +114 -5
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/shared.d.ts +92 -0
- package/dist/codebase-index/shared.d.ts.map +1 -1
- package/dist/codebase-index/shared.js +137 -1
- package/dist/codebase-index/shared.js.map +1 -1
- package/dist/sectors/shared.d.ts +25 -4
- package/dist/sectors/shared.d.ts.map +1 -1
- package/dist/sectors/shared.js +51 -8
- package/dist/sectors/shared.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead code detection and structural issue analysis.
|
|
3
|
+
*
|
|
4
|
+
* Cross-module export-import matching to find unused exports and
|
|
5
|
+
* structural anti-patterns. Pure functions — no I/O.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Dead export detection
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/**
|
|
11
|
+
* Detect potentially dead exports by comparing each module's exported names
|
|
12
|
+
* against what other modules import. Uses name-based matching (no type
|
|
13
|
+
* resolution) — sufficient for high-confidence identification.
|
|
14
|
+
*
|
|
15
|
+
* Returns at most `maxResults` entries to keep prompt token count bounded.
|
|
16
|
+
*/
|
|
17
|
+
export function detectDeadExports(modules, edges, exportsByModule, importsByModule, maxResults = 30) {
|
|
18
|
+
// Build a set of all imported names across all modules
|
|
19
|
+
const allImportedNames = new Set();
|
|
20
|
+
for (const imports of Object.values(importsByModule)) {
|
|
21
|
+
for (const spec of imports) {
|
|
22
|
+
// Extract the imported name from the specifier
|
|
23
|
+
// For relative imports like './foo', the actual imported names come from
|
|
24
|
+
// import { X } statements — we only have specifiers, not destructured names.
|
|
25
|
+
// So we use a heuristic: mark the last segment of the path as "used"
|
|
26
|
+
const parts = spec.split('/');
|
|
27
|
+
const lastPart = parts[parts.length - 1];
|
|
28
|
+
if (lastPart)
|
|
29
|
+
allImportedNames.add(lastPart);
|
|
30
|
+
// Also add the full specifier for exact matches
|
|
31
|
+
allImportedNames.add(spec);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// For each module with exports, check if any export name is never imported
|
|
35
|
+
const dead = [];
|
|
36
|
+
for (const mod of modules) {
|
|
37
|
+
if (dead.length >= maxResults)
|
|
38
|
+
break;
|
|
39
|
+
const exports = exportsByModule[mod.path];
|
|
40
|
+
if (!exports || exports.length === 0)
|
|
41
|
+
continue;
|
|
42
|
+
// Skip if this module has no dependents (everything would be "dead")
|
|
43
|
+
// — the whole module might be an entrypoint
|
|
44
|
+
const hasImporters = Object.values(edges).some(deps => deps.includes(mod.path));
|
|
45
|
+
if (!hasImporters)
|
|
46
|
+
continue;
|
|
47
|
+
for (const exp of exports) {
|
|
48
|
+
if (dead.length >= maxResults)
|
|
49
|
+
break;
|
|
50
|
+
// Skip 'default' — too noisy, often used implicitly
|
|
51
|
+
if (exp.name === 'default')
|
|
52
|
+
continue;
|
|
53
|
+
// Skip type exports — they're erased at runtime and aren't a real concern
|
|
54
|
+
if (exp.kind === 'type' || exp.kind === 'interface')
|
|
55
|
+
continue;
|
|
56
|
+
// Check if this name appears in any import specifier
|
|
57
|
+
if (!allImportedNames.has(exp.name)) {
|
|
58
|
+
dead.push({ module: mod.path, name: exp.name, kind: exp.kind });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return dead;
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Structural issue detection
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/**
|
|
68
|
+
* Detect structural anti-patterns from dependency graph topology.
|
|
69
|
+
*
|
|
70
|
+
* Patterns detected:
|
|
71
|
+
* - god-module: fan_in > 5 AND file_count > 20
|
|
72
|
+
* - excessive-fan-out: fan_out > 8
|
|
73
|
+
* - barrel-only: 1 file that exists only to re-export
|
|
74
|
+
* - orphan: zero in/out edges, not an entrypoint
|
|
75
|
+
*
|
|
76
|
+
* Returns at most `maxResults` entries.
|
|
77
|
+
*/
|
|
78
|
+
export function detectStructuralIssues(modules, edges, reverseEdges, cycles, entrypoints = [], maxResults = 15) {
|
|
79
|
+
const issues = [];
|
|
80
|
+
const entrypointDirs = new Set(entrypoints.map(e => e.split('/').slice(0, -1).join('/')));
|
|
81
|
+
// Circular dependencies (from pre-computed cycles)
|
|
82
|
+
for (const cycle of cycles) {
|
|
83
|
+
if (issues.length >= maxResults)
|
|
84
|
+
break;
|
|
85
|
+
const display = cycle.join(' → ');
|
|
86
|
+
issues.push({
|
|
87
|
+
kind: 'circular-dep',
|
|
88
|
+
module: cycle[0],
|
|
89
|
+
detail: display,
|
|
90
|
+
severity: 'warning',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
for (const mod of modules) {
|
|
94
|
+
if (issues.length >= maxResults)
|
|
95
|
+
break;
|
|
96
|
+
const fanIn = (reverseEdges[mod.path] ?? []).length;
|
|
97
|
+
const fanOut = (edges[mod.path] ?? []).length;
|
|
98
|
+
// God module: many dependents + many files
|
|
99
|
+
if (fanIn > 5 && mod.file_count > 20) {
|
|
100
|
+
issues.push({
|
|
101
|
+
kind: 'god-module',
|
|
102
|
+
module: mod.path,
|
|
103
|
+
detail: `fan-in: ${fanIn}, ${mod.file_count} files`,
|
|
104
|
+
severity: 'warning',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Excessive fan-out
|
|
108
|
+
if (fanOut > 8) {
|
|
109
|
+
issues.push({
|
|
110
|
+
kind: 'excessive-fan-out',
|
|
111
|
+
module: mod.path,
|
|
112
|
+
detail: `imports ${fanOut} modules`,
|
|
113
|
+
severity: 'info',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Barrel-only: 1 file, has exports but is just re-exporting
|
|
117
|
+
if (mod.file_count === 1 && fanOut > 0 && fanIn > 0 && (mod.export_count ?? 0) > 0) {
|
|
118
|
+
// Heuristic: barrel if export_count > 0 and it imports from multiple modules
|
|
119
|
+
if (fanOut >= 2) {
|
|
120
|
+
issues.push({
|
|
121
|
+
kind: 'barrel-only',
|
|
122
|
+
module: mod.path,
|
|
123
|
+
detail: `1 file, re-exports from ${fanOut} modules`,
|
|
124
|
+
severity: 'info',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Orphan: no edges, not an entrypoint
|
|
129
|
+
if (fanIn === 0 && fanOut === 0 && !entrypointDirs.has(mod.path) && mod.production) {
|
|
130
|
+
issues.push({
|
|
131
|
+
kind: 'orphan',
|
|
132
|
+
module: mod.path,
|
|
133
|
+
detail: 'no dependencies',
|
|
134
|
+
severity: 'info',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return issues;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Compute Robert C. Martin's instability metric per module.
|
|
142
|
+
* I = Ce / (Ca + Ce) where:
|
|
143
|
+
* - Ca (afferent coupling) = fan_in (who depends on me)
|
|
144
|
+
* - Ce (efferent coupling) = fan_out (who I depend on)
|
|
145
|
+
* - I = 0: maximally stable (many dependents, no dependencies)
|
|
146
|
+
* - I = 1: maximally unstable (no dependents, many dependencies)
|
|
147
|
+
*/
|
|
148
|
+
export function computeCouplingMetrics(modules, edges, reverseEdges) {
|
|
149
|
+
const instability = {};
|
|
150
|
+
for (const mod of modules) {
|
|
151
|
+
const ca = (reverseEdges[mod.path] ?? []).length; // afferent
|
|
152
|
+
const ce = (edges[mod.path] ?? []).length; // efferent
|
|
153
|
+
const total = ca + ce;
|
|
154
|
+
instability[mod.path] = total > 0 ? ce / total : 0;
|
|
155
|
+
}
|
|
156
|
+
return { instability };
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=dead-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-code.js","sourceRoot":"","sources":["../../src/codebase-index/dead-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format analysis data (dead exports, structural issues, graph insights,
|
|
3
|
+
* TS analysis) into a compact prompt block for the scout.
|
|
4
|
+
*
|
|
5
|
+
* Rotates sections by cycle number to stay within ~800 token budget per cycle.
|
|
6
|
+
* Pure function — no I/O.
|
|
7
|
+
*/
|
|
8
|
+
import type { CodebaseIndex } from './shared.js';
|
|
9
|
+
/**
|
|
10
|
+
* Format analysis data for scout prompt injection.
|
|
11
|
+
*
|
|
12
|
+
* Rotates which sections are included based on `scoutCycle` to keep token
|
|
13
|
+
* count bounded (~800 tokens max). Each cycle shows 2-3 sections.
|
|
14
|
+
*
|
|
15
|
+
* Returns `null` if no analysis data is available.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatAnalysisForPrompt(index: CodebaseIndex, scoutCycle: number): string | null;
|
|
18
|
+
//# sourceMappingURL=format-analysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-analysis.d.ts","sourceRoot":"","sources":["../../src/codebase-index/format-analysis.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,aAAa,EAId,MAAM,aAAa,CAAC;AAuIrB;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CA0Df"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format analysis data (dead exports, structural issues, graph insights,
|
|
3
|
+
* TS analysis) into a compact prompt block for the scout.
|
|
4
|
+
*
|
|
5
|
+
* Rotates sections by cycle number to stay within ~800 token budget per cycle.
|
|
6
|
+
* Pure function — no I/O.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Section renderers
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
function formatDeadExports(dead) {
|
|
12
|
+
if (dead.length === 0)
|
|
13
|
+
return '';
|
|
14
|
+
// Group by module
|
|
15
|
+
const byModule = new Map();
|
|
16
|
+
for (const d of dead) {
|
|
17
|
+
const names = byModule.get(d.module) ?? [];
|
|
18
|
+
names.push(d.name);
|
|
19
|
+
byModule.set(d.module, names);
|
|
20
|
+
}
|
|
21
|
+
const lines = [`### Dead Exports (${dead.length} potentially unused)`];
|
|
22
|
+
for (const [mod, names] of byModule) {
|
|
23
|
+
lines.push(`${mod}: ${names.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
27
|
+
function formatStructuralIssues(issues) {
|
|
28
|
+
if (issues.length === 0)
|
|
29
|
+
return '';
|
|
30
|
+
const lines = ['### Structural Issues'];
|
|
31
|
+
for (const issue of issues) {
|
|
32
|
+
lines.push(`- ${issue.kind}: ${issue.module} — ${issue.detail} (${issue.severity})`);
|
|
33
|
+
}
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
36
|
+
function formatGraphInsights(index) {
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (index.graph_metrics) {
|
|
39
|
+
const { hub_modules, orphan_modules, leaf_modules } = index.graph_metrics;
|
|
40
|
+
if (hub_modules.length > 0) {
|
|
41
|
+
parts.push(`Hub modules (3+ dependents): ${hub_modules.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
if (leaf_modules.length > 0) {
|
|
44
|
+
parts.push(`Leaf modules (no dependents): ${leaf_modules.slice(0, 8).join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
if (orphan_modules.length > 0) {
|
|
47
|
+
parts.push(`Orphan modules: ${orphan_modules.slice(0, 5).join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (index.dependency_cycles && index.dependency_cycles.length > 0) {
|
|
51
|
+
const formatted = index.dependency_cycles
|
|
52
|
+
.slice(0, 5)
|
|
53
|
+
.map(c => c.join(' → '));
|
|
54
|
+
parts.push(`Circular dependencies: ${formatted.join('; ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (parts.length === 0)
|
|
57
|
+
return '';
|
|
58
|
+
return ['### Graph Topology', ...parts].join('\n');
|
|
59
|
+
}
|
|
60
|
+
function formatCouplingInsights(index) {
|
|
61
|
+
// Extract instability extremes from modules
|
|
62
|
+
const modules = index.modules;
|
|
63
|
+
const edges = index.dependency_edges;
|
|
64
|
+
const reverse = index.reverse_edges ?? {};
|
|
65
|
+
const unstable = [];
|
|
66
|
+
const stable = [];
|
|
67
|
+
for (const mod of modules) {
|
|
68
|
+
if (!mod.production)
|
|
69
|
+
continue;
|
|
70
|
+
const ca = (reverse[mod.path] ?? []).length;
|
|
71
|
+
const ce = (edges[mod.path] ?? []).length;
|
|
72
|
+
const total = ca + ce;
|
|
73
|
+
if (total === 0)
|
|
74
|
+
continue;
|
|
75
|
+
const instability = ce / total;
|
|
76
|
+
if (instability >= 0.9)
|
|
77
|
+
unstable.push(mod.path);
|
|
78
|
+
else if (instability <= 0.1 && ca >= 2)
|
|
79
|
+
stable.push(mod.path);
|
|
80
|
+
}
|
|
81
|
+
if (unstable.length === 0 && stable.length === 0)
|
|
82
|
+
return '';
|
|
83
|
+
const parts = ['### Coupling Analysis'];
|
|
84
|
+
if (unstable.length > 0) {
|
|
85
|
+
parts.push(`Most unstable (easy to change, few dependents): ${unstable.slice(0, 5).join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
if (stable.length > 0) {
|
|
88
|
+
parts.push(`Most stable (many dependents, hard to change safely): ${stable.slice(0, 5).join(', ')}`);
|
|
89
|
+
}
|
|
90
|
+
return parts.join('\n');
|
|
91
|
+
}
|
|
92
|
+
function formatTypeScriptAnalysis(ts) {
|
|
93
|
+
const parts = ['### TypeScript Quality'];
|
|
94
|
+
parts.push(`any-count: ${ts.any_count}, type assertions: ${ts.unchecked_type_assertions}`);
|
|
95
|
+
if (ts.any_propagation_paths.length > 0) {
|
|
96
|
+
const worst = ts.any_propagation_paths
|
|
97
|
+
.sort((a, b) => b.length - a.length)
|
|
98
|
+
.slice(0, 3);
|
|
99
|
+
for (const p of worst) {
|
|
100
|
+
parts.push(`any propagation: ${p.source} → ${p.reaches.join(', ')} (${p.length} hop${p.length !== 1 ? 's' : ''})`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// API surface summary — just top 5 by export count
|
|
104
|
+
const surfaceEntries = Object.entries(ts.api_surface)
|
|
105
|
+
.sort(([, a], [, b]) => b - a)
|
|
106
|
+
.slice(0, 5);
|
|
107
|
+
if (surfaceEntries.length > 0) {
|
|
108
|
+
parts.push(`Largest API surfaces: ${surfaceEntries.map(([m, c]) => `${m} (${c})`).join(', ')}`);
|
|
109
|
+
}
|
|
110
|
+
return parts.join('\n');
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Public API
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* All available analysis sections, in display priority order.
|
|
117
|
+
*/
|
|
118
|
+
const SECTIONS = [
|
|
119
|
+
'dead-exports',
|
|
120
|
+
'structural',
|
|
121
|
+
'graph',
|
|
122
|
+
'coupling',
|
|
123
|
+
'typescript',
|
|
124
|
+
];
|
|
125
|
+
/**
|
|
126
|
+
* Format analysis data for scout prompt injection.
|
|
127
|
+
*
|
|
128
|
+
* Rotates which sections are included based on `scoutCycle` to keep token
|
|
129
|
+
* count bounded (~800 tokens max). Each cycle shows 2-3 sections.
|
|
130
|
+
*
|
|
131
|
+
* Returns `null` if no analysis data is available.
|
|
132
|
+
*/
|
|
133
|
+
export function formatAnalysisForPrompt(index, scoutCycle) {
|
|
134
|
+
// Render all sections
|
|
135
|
+
const rendered = new Map();
|
|
136
|
+
if (index.dead_exports && index.dead_exports.length > 0) {
|
|
137
|
+
const s = formatDeadExports(index.dead_exports);
|
|
138
|
+
if (s)
|
|
139
|
+
rendered.set('dead-exports', s);
|
|
140
|
+
}
|
|
141
|
+
if (index.structural_issues && index.structural_issues.length > 0) {
|
|
142
|
+
const s = formatStructuralIssues(index.structural_issues);
|
|
143
|
+
if (s)
|
|
144
|
+
rendered.set('structural', s);
|
|
145
|
+
}
|
|
146
|
+
{
|
|
147
|
+
const s = formatGraphInsights(index);
|
|
148
|
+
if (s)
|
|
149
|
+
rendered.set('graph', s);
|
|
150
|
+
}
|
|
151
|
+
{
|
|
152
|
+
const s = formatCouplingInsights(index);
|
|
153
|
+
if (s)
|
|
154
|
+
rendered.set('coupling', s);
|
|
155
|
+
}
|
|
156
|
+
if (index.typescript_analysis && (index.typescript_analysis.any_count > 0 || index.typescript_analysis.unchecked_type_assertions > 0)) {
|
|
157
|
+
const s = formatTypeScriptAnalysis(index.typescript_analysis);
|
|
158
|
+
if (s)
|
|
159
|
+
rendered.set('typescript', s);
|
|
160
|
+
}
|
|
161
|
+
if (rendered.size === 0)
|
|
162
|
+
return null;
|
|
163
|
+
// If 3 or fewer sections, show all (fits in budget)
|
|
164
|
+
if (rendered.size <= 3) {
|
|
165
|
+
const parts = ['## Codebase Analysis'];
|
|
166
|
+
for (const key of SECTIONS) {
|
|
167
|
+
const content = rendered.get(key);
|
|
168
|
+
if (content)
|
|
169
|
+
parts.push(content);
|
|
170
|
+
}
|
|
171
|
+
return parts.join('\n\n');
|
|
172
|
+
}
|
|
173
|
+
// Rotate: show 3 sections per cycle
|
|
174
|
+
const available = SECTIONS.filter(k => rendered.has(k));
|
|
175
|
+
const windowSize = 3;
|
|
176
|
+
const start = (scoutCycle * windowSize) % available.length;
|
|
177
|
+
const selected = new Set();
|
|
178
|
+
for (let i = 0; i < windowSize && i < available.length; i++) {
|
|
179
|
+
selected.add(available[(start + i) % available.length]);
|
|
180
|
+
}
|
|
181
|
+
const parts = ['## Codebase Analysis'];
|
|
182
|
+
for (const key of SECTIONS) {
|
|
183
|
+
if (selected.has(key)) {
|
|
184
|
+
const content = rendered.get(key);
|
|
185
|
+
if (content)
|
|
186
|
+
parts.push(content);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return parts.join('\n\n');
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=format-analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-analysis.js","sourceRoot":"","sources":["../../src/codebase-index/format-analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Single source of truth — imported by both @promptwheel/cli and @promptwheel/mcp.
|
|
11
11
|
*/
|
|
12
|
-
export { type
|
|
12
|
+
export { type AstGrepModule } from './ast-analysis.js';
|
|
13
|
+
export { mapExtensionToLang, analyzeFileAst } from './ast-analysis.js';
|
|
14
|
+
export { type CodebaseIndex, type ClassificationConfidence, type ModuleEntry, type LargeFileEntry, type ClassifyResult, type GraphMetrics, type ExportEntry, type AstAnalysisResult, type DeadExportEntry, type StructuralIssue, type TypeScriptAnalysis, SOURCE_EXTENSIONS, PURPOSE_HINT, NON_PRODUCTION_PURPOSES, CHUNK_SIZE, purposeHintFromDirName, sampleEvenly, countNonProdFiles, classifyModule, extractImports, resolveImportToModule, formatIndexForPrompt, computeReverseEdges, detectCycles, computeGraphMetrics, } from './shared.js';
|
|
13
15
|
import type { CodebaseIndex } from './shared.js';
|
|
16
|
+
import type { AstGrepModule as AstGrepModuleType } from './ast-analysis.js';
|
|
17
|
+
export { formatAnalysisForPrompt } from './format-analysis.js';
|
|
14
18
|
/**
|
|
15
19
|
* Use `git ls-files` to discover which top-level directories contain tracked
|
|
16
20
|
* (or unignored) files. Returns null if git is unavailable or the project
|
|
17
21
|
* is not a git repo — callers should fall back to hardcoded excludes.
|
|
18
22
|
*/
|
|
19
23
|
export declare function getTrackedDirectories(projectRoot: string): Set<string> | null;
|
|
20
|
-
export declare function buildCodebaseIndex(projectRoot: string, excludeDirs?: string[], useGitTracking?: boolean): CodebaseIndex;
|
|
21
|
-
export declare function refreshCodebaseIndex(_existing: CodebaseIndex, projectRoot: string, excludeDirs?: string[], useGitTracking?: boolean): CodebaseIndex;
|
|
24
|
+
export declare function buildCodebaseIndex(projectRoot: string, excludeDirs?: string[], useGitTracking?: boolean, astGrepModule?: AstGrepModuleType | null): CodebaseIndex;
|
|
25
|
+
export declare function refreshCodebaseIndex(_existing: CodebaseIndex, projectRoot: string, excludeDirs?: string[], useGitTracking?: boolean, astGrepModule?: AstGrepModuleType | null): CodebaseIndex;
|
|
22
26
|
export declare function hasStructuralChanges(index: CodebaseIndex, projectRoot: string): boolean;
|
|
23
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/codebase-index/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,iBAAiB,EACjB,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/codebase-index/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGvE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,iBAAiB,EACjB,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,aAAa,EAA4C,MAAM,aAAa,CAAC;AAW3F,OAAO,KAAK,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAkC/D;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAe7E;AAMD,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAM,EAAO,EAC1B,cAAc,UAAO,EACrB,aAAa,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACvC,aAAa,CA6Vf;AAMD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,aAAa,EACxB,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAM,EAAO,EAC1B,cAAc,UAAO,EACrB,aAAa,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACvC,aAAa,CAEf;AAMD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAuCT"}
|
|
@@ -12,9 +12,15 @@
|
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
|
+
export { mapExtensionToLang, analyzeFileAst } from './ast-analysis.js';
|
|
15
16
|
// Re-export everything from shared (pure algorithms + types + constants)
|
|
16
|
-
export { SOURCE_EXTENSIONS, PURPOSE_HINT, NON_PRODUCTION_PURPOSES, CHUNK_SIZE, purposeHintFromDirName, sampleEvenly, countNonProdFiles, classifyModule, extractImports, resolveImportToModule, formatIndexForPrompt, } from './shared.js';
|
|
17
|
-
import { SOURCE_EXTENSIONS, sampleEvenly, classifyModule, extractImports, resolveImportToModule, } from './shared.js';
|
|
17
|
+
export { SOURCE_EXTENSIONS, PURPOSE_HINT, NON_PRODUCTION_PURPOSES, CHUNK_SIZE, purposeHintFromDirName, sampleEvenly, countNonProdFiles, classifyModule, extractImports, resolveImportToModule, formatIndexForPrompt, computeReverseEdges, detectCycles, computeGraphMetrics, } from './shared.js';
|
|
18
|
+
import { SOURCE_EXTENSIONS, sampleEvenly, classifyModule, extractImports, resolveImportToModule, computeReverseEdges, detectCycles, computeGraphMetrics, } from './shared.js';
|
|
19
|
+
import { analyzeFileAst, mapExtensionToLang } from './ast-analysis.js';
|
|
20
|
+
import { loadAstCache, saveAstCache, isEntryCurrent } from './ast-cache.js';
|
|
21
|
+
import { detectDeadExports, detectStructuralIssues } from './dead-code.js';
|
|
22
|
+
// Re-export format-analysis
|
|
23
|
+
export { formatAnalysisForPrompt } from './format-analysis.js';
|
|
18
24
|
// ---------------------------------------------------------------------------
|
|
19
25
|
// I/O helpers
|
|
20
26
|
// ---------------------------------------------------------------------------
|
|
@@ -72,7 +78,7 @@ export function getTrackedDirectories(projectRoot) {
|
|
|
72
78
|
// ---------------------------------------------------------------------------
|
|
73
79
|
// buildCodebaseIndex
|
|
74
80
|
// ---------------------------------------------------------------------------
|
|
75
|
-
export function buildCodebaseIndex(projectRoot, excludeDirs = [], useGitTracking = true) {
|
|
81
|
+
export function buildCodebaseIndex(projectRoot, excludeDirs = [], useGitTracking = true, astGrepModule) {
|
|
76
82
|
const excludeSet = new Set(excludeDirs.map(d => d.toLowerCase()));
|
|
77
83
|
// Git-aware filtering: only walk directories that contain tracked files
|
|
78
84
|
const trackedDirs = useGitTracking ? getTrackedDirectories(projectRoot) : null;
|
|
@@ -198,6 +204,73 @@ export function buildCodebaseIndex(projectRoot, excludeDirs = [], useGitTracking
|
|
|
198
204
|
mod.production_file_count = productionFileCount;
|
|
199
205
|
mod.classification_confidence = confidence;
|
|
200
206
|
}
|
|
207
|
+
// Step 2c: AST analysis — when ast-grep is available, extract exports and complexity
|
|
208
|
+
let analysisBackend = 'regex';
|
|
209
|
+
if (astGrepModule) {
|
|
210
|
+
analysisBackend = 'ast-grep';
|
|
211
|
+
const astCache = loadAstCache(projectRoot);
|
|
212
|
+
const updatedCache = { ...astCache };
|
|
213
|
+
const allCurrentFiles = new Set();
|
|
214
|
+
// Per-module: aggregate exports and complexity from individual files
|
|
215
|
+
for (const mod of modules) {
|
|
216
|
+
const files = sourceFilesByModule.get(mod.path) ?? [];
|
|
217
|
+
const moduleExports = [];
|
|
218
|
+
let totalComplexity = 0;
|
|
219
|
+
let filesAnalyzed = 0;
|
|
220
|
+
for (const filePath of files) {
|
|
221
|
+
const relFile = path.relative(projectRoot, filePath);
|
|
222
|
+
allCurrentFiles.add(relFile);
|
|
223
|
+
const ext = path.extname(filePath);
|
|
224
|
+
const langKey = mapExtensionToLang(ext);
|
|
225
|
+
if (!langKey)
|
|
226
|
+
continue;
|
|
227
|
+
// Check cache first
|
|
228
|
+
let stat;
|
|
229
|
+
try {
|
|
230
|
+
stat = fs.statSync(filePath);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const cached = updatedCache[relFile];
|
|
236
|
+
if (isEntryCurrent(cached, stat.mtimeMs, stat.size)) {
|
|
237
|
+
moduleExports.push(...cached.exports);
|
|
238
|
+
totalComplexity += cached.complexity;
|
|
239
|
+
filesAnalyzed++;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
// Read full file content for AST analysis
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
245
|
+
const result = analyzeFileAst(content, filePath, langKey, astGrepModule);
|
|
246
|
+
if (result) {
|
|
247
|
+
moduleExports.push(...result.exports);
|
|
248
|
+
totalComplexity += result.complexity;
|
|
249
|
+
filesAnalyzed++;
|
|
250
|
+
// Update cache
|
|
251
|
+
updatedCache[relFile] = {
|
|
252
|
+
mtime: stat.mtimeMs,
|
|
253
|
+
size: stat.size,
|
|
254
|
+
imports: result.imports,
|
|
255
|
+
exports: result.exports,
|
|
256
|
+
complexity: result.complexity,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// File read error — skip this file for AST analysis
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Populate module-level AST metrics
|
|
265
|
+
if (filesAnalyzed > 0) {
|
|
266
|
+
mod.export_count = moduleExports.length;
|
|
267
|
+
mod.exported_names = moduleExports.slice(0, 20).map(e => e.name);
|
|
268
|
+
mod.avg_complexity = totalComplexity / filesAnalyzed;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Save updated cache (pruning stale entries)
|
|
272
|
+
saveAstCache(projectRoot, updatedCache, allCurrentFiles);
|
|
273
|
+
}
|
|
201
274
|
// Step 3: Test coverage — find untested modules
|
|
202
275
|
const untestedModules = [];
|
|
203
276
|
for (const mod of modules) {
|
|
@@ -284,21 +357,57 @@ export function buildCodebaseIndex(projectRoot, excludeDirs = [], useGitTracking
|
|
|
284
357
|
}
|
|
285
358
|
}
|
|
286
359
|
}
|
|
360
|
+
// Step 6: Graph analysis — reverse edges, cycle detection, topology metrics
|
|
361
|
+
const reverseEdges = computeReverseEdges(dependencyEdges);
|
|
362
|
+
const dependencyCycles = detectCycles(dependencyEdges);
|
|
363
|
+
const graphMetrics = computeGraphMetrics(modules, dependencyEdges, reverseEdges);
|
|
364
|
+
// Populate per-module fan_in / fan_out
|
|
365
|
+
for (const mod of modules) {
|
|
366
|
+
mod.fan_in = (reverseEdges[mod.path] ?? []).length;
|
|
367
|
+
mod.fan_out = (dependencyEdges[mod.path] ?? []).length;
|
|
368
|
+
}
|
|
369
|
+
// Step 7: Dead code + structural analysis
|
|
370
|
+
// Dead exports require AST data (exportsByModule). Structural issues use graph metrics.
|
|
371
|
+
let deadExports;
|
|
372
|
+
if (analysisBackend === 'ast-grep') {
|
|
373
|
+
// Build per-module export/import maps from AST cache
|
|
374
|
+
const exportsByModule = {};
|
|
375
|
+
const importsByModule = {};
|
|
376
|
+
for (const mod of modules) {
|
|
377
|
+
if (mod.exported_names && mod.exported_names.length > 0) {
|
|
378
|
+
// Reconstruct ExportEntry from module data (kind is lost, use 'other')
|
|
379
|
+
exportsByModule[mod.path] = mod.exported_names.map(name => ({ name, kind: 'other' }));
|
|
380
|
+
}
|
|
381
|
+
// Use dependency edges as import proxy
|
|
382
|
+
const deps = dependencyEdges[mod.path];
|
|
383
|
+
if (deps)
|
|
384
|
+
importsByModule[mod.path] = deps;
|
|
385
|
+
}
|
|
386
|
+
deadExports = detectDeadExports(modules, dependencyEdges, exportsByModule, importsByModule);
|
|
387
|
+
}
|
|
388
|
+
// Structural issues only need graph data (no AST required)
|
|
389
|
+
const structuralIssues = detectStructuralIssues(modules, dependencyEdges, reverseEdges, dependencyCycles, entrypoints);
|
|
287
390
|
return {
|
|
288
391
|
built_at: new Date().toISOString(),
|
|
289
392
|
modules,
|
|
290
393
|
dependency_edges: dependencyEdges,
|
|
394
|
+
reverse_edges: reverseEdges,
|
|
395
|
+
dependency_cycles: dependencyCycles,
|
|
396
|
+
graph_metrics: graphMetrics,
|
|
291
397
|
untested_modules: untestedModules,
|
|
292
398
|
large_files: largeFiles,
|
|
293
399
|
entrypoints,
|
|
294
400
|
sampled_file_mtimes: sampledFileMtimes,
|
|
401
|
+
analysis_backend: analysisBackend,
|
|
402
|
+
dead_exports: deadExports,
|
|
403
|
+
structural_issues: structuralIssues,
|
|
295
404
|
};
|
|
296
405
|
}
|
|
297
406
|
// ---------------------------------------------------------------------------
|
|
298
407
|
// refreshCodebaseIndex
|
|
299
408
|
// ---------------------------------------------------------------------------
|
|
300
|
-
export function refreshCodebaseIndex(_existing, projectRoot, excludeDirs = [], useGitTracking = true) {
|
|
301
|
-
return buildCodebaseIndex(projectRoot, excludeDirs, useGitTracking);
|
|
409
|
+
export function refreshCodebaseIndex(_existing, projectRoot, excludeDirs = [], useGitTracking = true, astGrepModule) {
|
|
410
|
+
return buildCodebaseIndex(projectRoot, excludeDirs, useGitTracking, astGrepModule);
|
|
302
411
|
}
|
|
303
412
|
// ---------------------------------------------------------------------------
|
|
304
413
|
// hasStructuralChanges
|