@promptwheel/core 0.7.14 → 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.
@@ -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":"AAAA;;;;;GAKG;AASH,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAsB,EACtB,KAA+B,EAC/B,eAA8C,EAC9C,eAAyC,EACzC,UAAU,GAAG,EAAE;IAEf,uDAAuD;IACvD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3C,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,+CAA+C;YAC/C,yEAAyE;YACzE,6EAA6E;YAC7E,qEAAqE;YACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACzC,IAAI,QAAQ;gBAAE,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7C,gDAAgD;YAChD,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,MAAM,IAAI,GAAsB,EAAE,CAAC;IAEnC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QACrC,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAE/C,qEAAqE;QACrE,4CAA4C;QAC5C,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,YAAY;YAAE,SAAS;QAE5B,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,UAAU;gBAAE,MAAM;YACrC,oDAAoD;YACpD,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;gBAAE,SAAS;YACrC,0EAA0E;YAC1E,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YAE9D,qDAAqD;YACrD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAsB,EACtB,KAA+B,EAC/B,YAAsC,EACtC,MAAkB,EAClB,cAAwB,EAAE,EAC1B,UAAU,GAAG,EAAE;IAEf,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE1F,mDAAmD;IACnD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QACvC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;YAChB,MAAM,EAAE,OAAO;YACf,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QACvC,MAAM,KAAK,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACpD,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QAE9C,2CAA2C;QAC3C,IAAI,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,GAAG,EAAE,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,GAAG,CAAC,IAAI;gBAChB,MAAM,EAAE,WAAW,KAAK,KAAK,GAAG,CAAC,UAAU,QAAQ;gBACnD,QAAQ,EAAE,SAAS;aACpB,CAAC,CAAC;QACL,CAAC;QAED,oBAAoB;QACpB,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,mBAAmB;gBACzB,MAAM,EAAE,GAAG,CAAC,IAAI;gBAChB,MAAM,EAAE,WAAW,MAAM,UAAU;gBACnC,QAAQ,EAAE,MAAM;aACjB,CAAC,CAAC;QACL,CAAC;QAED,4DAA4D;QAC5D,IAAI,GAAG,CAAC,UAAU,KAAK,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YACnF,6EAA6E;YAC7E,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,GAAG,CAAC,IAAI;oBAChB,MAAM,EAAE,2BAA2B,MAAM,UAAU;oBACnD,QAAQ,EAAE,MAAM;iBACjB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,sCAAsC;QACtC,IAAI,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnF,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,GAAG,CAAC,IAAI;gBAChB,MAAM,EAAE,iBAAiB;gBACzB,QAAQ,EAAE,MAAM;aACjB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAsB,EACtB,KAA+B,EAC/B,YAAsC;IAEtC,MAAM,WAAW,GAA2B,EAAE,CAAC;IAE/C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW;QAC7D,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAS,WAAW;QAC9D,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACtB,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,CAAC;AACzB,CAAC"}
@@ -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":"AAAA;;;;;;GAMG;AASH,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,SAAS,iBAAiB,CAAC,IAAuB;IAChD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,kBAAkB;IAClB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,KAAK,GAAa,CAAC,qBAAqB,IAAI,CAAC,MAAM,sBAAsB,CAAC,CAAC;IACjF,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAyB;IACvD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAa,CAAC,uBAAuB,CAAC,CAAC;IAClD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAoB;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACxB,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC,aAAa,CAAC;QAC1E,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,gCAAgC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,iCAAiC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,mBAAmB,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,SAAS,GAAG,KAAK,CAAC,iBAAiB;aACtC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,0BAA0B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,CAAC,oBAAoB,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAoB;IAClD,4CAA4C;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,IAAI,EAAE,CAAC;IAE1C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,UAAU;YAAE,SAAS;QAC9B,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QAC5C,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QAC1C,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACtB,IAAI,KAAK,KAAK,CAAC;YAAE,SAAS;QAC1B,MAAM,WAAW,GAAG,EAAE,GAAG,KAAK,CAAC;QAC/B,IAAI,WAAW,IAAI,GAAG;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;aAC3C,IAAI,WAAW,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE5D,MAAM,KAAK,GAAa,CAAC,uBAAuB,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,mDAAmD,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,yDAAyD,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,wBAAwB,CAAC,EAAsB;IACtD,MAAM,KAAK,GAAa,CAAC,wBAAwB,CAAC,CAAC;IAEnD,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,SAAS,sBAAsB,EAAE,CAAC,yBAAyB,EAAE,CAAC,CAAC;IAE3F,IAAI,EAAE,CAAC,qBAAqB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,EAAE,CAAC,qBAAqB;aACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;aACnC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC;SAClD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;SAC7B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACf,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,yBAAyB,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;GAEG;AACH,MAAM,QAAQ,GAAG;IACf,cAAc;IACd,YAAY;IACZ,OAAO;IACP,UAAU;IACV,YAAY;CACJ,CAAC;AAIX;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACrC,KAAoB,EACpB,UAAkB;IAElB,sBAAsB;IACtB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE/C,IAAI,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAChD,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,CAAC,GAAG,sBAAsB,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC1D,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,CAAC;QACC,MAAM,CAAC,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,CAAC;QACC,MAAM,CAAC,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,KAAK,CAAC,mBAAmB,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,yBAAyB,GAAG,CAAC,CAAC,EAAE,CAAC;QACtI,MAAM,CAAC,GAAG,wBAAwB,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC9D,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,oDAAoD;IACpD,IAAI,QAAQ,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,OAAO;gBAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED,oCAAoC;IACpC,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,CAAC,CAAC;IACrB,MAAM,KAAK,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC;IAC3D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAc,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5D,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,OAAO;gBAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
@@ -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 CodebaseIndex, type ClassificationConfidence, type ModuleEntry, type LargeFileEntry, type ClassifyResult, SOURCE_EXTENSIONS, PURPOSE_HINT, NON_PRODUCTION_PURPOSES, CHUNK_SIZE, purposeHintFromDirName, sampleEvenly, countNonProdFiles, classifyModule, extractImports, resolveImportToModule, formatIndexForPrompt, } from './shared.js';
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,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,aAAa,EAA+B,MAAM,aAAa,CAAC;AAyC9E;;;;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,GACpB,aAAa,CAkPf;AAMD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,aAAa,EACxB,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAM,EAAO,EAC1B,cAAc,UAAO,GACpB,aAAa,CAEf;AAMD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAuCT"}
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