@aiready/context-analyzer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +24 -0
- package/CONTRIBUTING.md +134 -0
- package/LICENSE +21 -0
- package/README.md +412 -0
- package/dist/chunk-K6U64EL3.mjs +517 -0
- package/dist/chunk-T6ZCOPPI.mjs +538 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +942 -0
- package/dist/cli.mjs +387 -0
- package/dist/index.d.mts +79 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +563 -0
- package/dist/index.mjs +8 -0
- package/package.json +72 -0
- package/src/__tests__/analyzer.test.ts +175 -0
- package/src/analyzer.ts +426 -0
- package/src/cli.ts +451 -0
- package/src/index.ts +396 -0
- package/src/types.ts +104 -0
- package/tsconfig.json +8 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { scanFiles, readFileContent } from '@aiready/core';
|
|
2
|
+
import type { ScanOptions } from '@aiready/core';
|
|
3
|
+
import {
|
|
4
|
+
buildDependencyGraph,
|
|
5
|
+
calculateImportDepth,
|
|
6
|
+
getTransitiveDependencies,
|
|
7
|
+
calculateContextBudget,
|
|
8
|
+
detectCircularDependencies,
|
|
9
|
+
calculateCohesion,
|
|
10
|
+
calculateFragmentation,
|
|
11
|
+
detectModuleClusters,
|
|
12
|
+
} from './analyzer';
|
|
13
|
+
import type {
|
|
14
|
+
ContextAnalyzerOptions,
|
|
15
|
+
ContextAnalysisResult,
|
|
16
|
+
ContextSummary,
|
|
17
|
+
ModuleCluster,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export type { ContextAnalyzerOptions, ContextAnalysisResult, ContextSummary, ModuleCluster };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Analyze AI context window cost for a codebase
|
|
24
|
+
*/
|
|
25
|
+
export async function analyzeContext(
|
|
26
|
+
options: ContextAnalyzerOptions
|
|
27
|
+
): Promise<ContextAnalysisResult[]> {
|
|
28
|
+
const {
|
|
29
|
+
maxDepth = 5,
|
|
30
|
+
maxContextBudget = 10000,
|
|
31
|
+
minCohesion = 0.6,
|
|
32
|
+
maxFragmentation = 0.5,
|
|
33
|
+
focus = 'all',
|
|
34
|
+
includeNodeModules = false,
|
|
35
|
+
...scanOptions
|
|
36
|
+
} = options;
|
|
37
|
+
|
|
38
|
+
// Scan files
|
|
39
|
+
const files = await scanFiles({
|
|
40
|
+
...scanOptions,
|
|
41
|
+
exclude: includeNodeModules
|
|
42
|
+
? scanOptions.exclude
|
|
43
|
+
: [...(scanOptions.exclude || []), '**/node_modules/**'],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Read all file contents
|
|
47
|
+
const fileContents = await Promise.all(
|
|
48
|
+
files.map(async (file) => ({
|
|
49
|
+
file,
|
|
50
|
+
content: await readFileContent(file),
|
|
51
|
+
}))
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Build dependency graph
|
|
55
|
+
const graph = buildDependencyGraph(fileContents);
|
|
56
|
+
|
|
57
|
+
// Detect circular dependencies
|
|
58
|
+
const circularDeps = detectCircularDependencies(graph);
|
|
59
|
+
|
|
60
|
+
// Detect module clusters for fragmentation analysis
|
|
61
|
+
const clusters = detectModuleClusters(graph);
|
|
62
|
+
const fragmentationMap = new Map<string, number>();
|
|
63
|
+
for (const cluster of clusters) {
|
|
64
|
+
for (const file of cluster.files) {
|
|
65
|
+
fragmentationMap.set(file, cluster.fragmentationScore);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Analyze each file
|
|
70
|
+
const results: ContextAnalysisResult[] = [];
|
|
71
|
+
|
|
72
|
+
for (const { file } of fileContents) {
|
|
73
|
+
const node = graph.nodes.get(file);
|
|
74
|
+
if (!node) continue;
|
|
75
|
+
|
|
76
|
+
// Calculate metrics based on focus
|
|
77
|
+
const importDepth =
|
|
78
|
+
focus === 'depth' || focus === 'all'
|
|
79
|
+
? calculateImportDepth(file, graph)
|
|
80
|
+
: 0;
|
|
81
|
+
|
|
82
|
+
const dependencyList =
|
|
83
|
+
focus === 'depth' || focus === 'all'
|
|
84
|
+
? getTransitiveDependencies(file, graph)
|
|
85
|
+
: [];
|
|
86
|
+
|
|
87
|
+
const contextBudget =
|
|
88
|
+
focus === 'all' ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
89
|
+
|
|
90
|
+
const cohesionScore =
|
|
91
|
+
focus === 'cohesion' || focus === 'all'
|
|
92
|
+
? calculateCohesion(node.exports)
|
|
93
|
+
: 1;
|
|
94
|
+
|
|
95
|
+
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
96
|
+
|
|
97
|
+
// Find related files (files in same domain cluster)
|
|
98
|
+
const relatedFiles: string[] = [];
|
|
99
|
+
for (const cluster of clusters) {
|
|
100
|
+
if (cluster.files.includes(file)) {
|
|
101
|
+
relatedFiles.push(...cluster.files.filter((f) => f !== file));
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Determine severity and generate issues/recommendations
|
|
107
|
+
const { severity, issues, recommendations, potentialSavings } =
|
|
108
|
+
analyzeIssues({
|
|
109
|
+
file,
|
|
110
|
+
importDepth,
|
|
111
|
+
contextBudget,
|
|
112
|
+
cohesionScore,
|
|
113
|
+
fragmentationScore,
|
|
114
|
+
maxDepth,
|
|
115
|
+
maxContextBudget,
|
|
116
|
+
minCohesion,
|
|
117
|
+
maxFragmentation,
|
|
118
|
+
circularDeps,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Get domains from exports
|
|
122
|
+
const domains = [
|
|
123
|
+
...new Set(node.exports.map((e) => e.inferredDomain || 'unknown')),
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
results.push({
|
|
127
|
+
file,
|
|
128
|
+
tokenCost: node.tokenCost,
|
|
129
|
+
linesOfCode: node.linesOfCode,
|
|
130
|
+
importDepth,
|
|
131
|
+
dependencyCount: dependencyList.length,
|
|
132
|
+
dependencyList,
|
|
133
|
+
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
134
|
+
cohesionScore,
|
|
135
|
+
domains,
|
|
136
|
+
exportCount: node.exports.length,
|
|
137
|
+
contextBudget,
|
|
138
|
+
fragmentationScore,
|
|
139
|
+
relatedFiles,
|
|
140
|
+
severity,
|
|
141
|
+
issues,
|
|
142
|
+
recommendations,
|
|
143
|
+
potentialSavings,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Sort by severity and context budget
|
|
148
|
+
return results.sort((a, b) => {
|
|
149
|
+
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
150
|
+
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
151
|
+
if (severityDiff !== 0) return severityDiff;
|
|
152
|
+
return b.contextBudget - a.contextBudget;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generate summary of context analysis results
|
|
158
|
+
*/
|
|
159
|
+
export function generateSummary(
|
|
160
|
+
results: ContextAnalysisResult[]
|
|
161
|
+
): ContextSummary {
|
|
162
|
+
if (results.length === 0) {
|
|
163
|
+
return {
|
|
164
|
+
totalFiles: 0,
|
|
165
|
+
totalTokens: 0,
|
|
166
|
+
avgContextBudget: 0,
|
|
167
|
+
maxContextBudget: 0,
|
|
168
|
+
avgImportDepth: 0,
|
|
169
|
+
maxImportDepth: 0,
|
|
170
|
+
deepFiles: [],
|
|
171
|
+
avgFragmentation: 0,
|
|
172
|
+
fragmentedModules: [],
|
|
173
|
+
avgCohesion: 0,
|
|
174
|
+
lowCohesionFiles: [],
|
|
175
|
+
criticalIssues: 0,
|
|
176
|
+
majorIssues: 0,
|
|
177
|
+
minorIssues: 0,
|
|
178
|
+
totalPotentialSavings: 0,
|
|
179
|
+
topExpensiveFiles: [],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const totalFiles = results.length;
|
|
184
|
+
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
185
|
+
const totalContextBudget = results.reduce(
|
|
186
|
+
(sum, r) => sum + r.contextBudget,
|
|
187
|
+
0
|
|
188
|
+
);
|
|
189
|
+
const avgContextBudget = totalContextBudget / totalFiles;
|
|
190
|
+
const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
|
|
191
|
+
|
|
192
|
+
const avgImportDepth =
|
|
193
|
+
results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
|
|
194
|
+
const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
|
|
195
|
+
|
|
196
|
+
const deepFiles = results
|
|
197
|
+
.filter((r) => r.importDepth >= 5)
|
|
198
|
+
.map((r) => ({ file: r.file, depth: r.importDepth }))
|
|
199
|
+
.sort((a, b) => b.depth - a.depth)
|
|
200
|
+
.slice(0, 10);
|
|
201
|
+
|
|
202
|
+
const avgFragmentation =
|
|
203
|
+
results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
|
|
204
|
+
|
|
205
|
+
// Get unique module clusters
|
|
206
|
+
const moduleMap = new Map<string, ContextAnalysisResult[]>();
|
|
207
|
+
for (const result of results) {
|
|
208
|
+
for (const domain of result.domains) {
|
|
209
|
+
if (!moduleMap.has(domain)) {
|
|
210
|
+
moduleMap.set(domain, []);
|
|
211
|
+
}
|
|
212
|
+
moduleMap.get(domain)!.push(result);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const fragmentedModules: ModuleCluster[] = [];
|
|
217
|
+
for (const [domain, files] of moduleMap.entries()) {
|
|
218
|
+
if (files.length < 2) continue;
|
|
219
|
+
|
|
220
|
+
const fragmentationScore =
|
|
221
|
+
files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
|
|
222
|
+
if (fragmentationScore < 0.3) continue; // Skip well-organized modules
|
|
223
|
+
|
|
224
|
+
const totalTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
225
|
+
const avgCohesion =
|
|
226
|
+
files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
|
|
227
|
+
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
228
|
+
|
|
229
|
+
fragmentedModules.push({
|
|
230
|
+
domain,
|
|
231
|
+
files: files.map((f) => f.file),
|
|
232
|
+
totalTokens,
|
|
233
|
+
fragmentationScore,
|
|
234
|
+
avgCohesion,
|
|
235
|
+
suggestedStructure: {
|
|
236
|
+
targetFiles,
|
|
237
|
+
consolidationPlan: [
|
|
238
|
+
`Consolidate ${files.length} ${domain} files into ${targetFiles} cohesive file(s)`,
|
|
239
|
+
`Current token cost: ${totalTokens.toLocaleString()}`,
|
|
240
|
+
`Estimated savings: ${Math.floor(totalTokens * 0.3).toLocaleString()} tokens (30%)`,
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
|
|
247
|
+
|
|
248
|
+
const avgCohesion =
|
|
249
|
+
results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
250
|
+
|
|
251
|
+
const lowCohesionFiles = results
|
|
252
|
+
.filter((r) => r.cohesionScore < 0.6)
|
|
253
|
+
.map((r) => ({ file: r.file, score: r.cohesionScore }))
|
|
254
|
+
.sort((a, b) => a.score - b.score)
|
|
255
|
+
.slice(0, 10);
|
|
256
|
+
|
|
257
|
+
const criticalIssues = results.filter((r) => r.severity === 'critical').length;
|
|
258
|
+
const majorIssues = results.filter((r) => r.severity === 'major').length;
|
|
259
|
+
const minorIssues = results.filter((r) => r.severity === 'minor').length;
|
|
260
|
+
|
|
261
|
+
const totalPotentialSavings = results.reduce(
|
|
262
|
+
(sum, r) => sum + r.potentialSavings,
|
|
263
|
+
0
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const topExpensiveFiles = results
|
|
267
|
+
.sort((a, b) => b.contextBudget - a.contextBudget)
|
|
268
|
+
.slice(0, 10)
|
|
269
|
+
.map((r) => ({
|
|
270
|
+
file: r.file,
|
|
271
|
+
contextBudget: r.contextBudget,
|
|
272
|
+
severity: r.severity,
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
totalFiles,
|
|
277
|
+
totalTokens,
|
|
278
|
+
avgContextBudget,
|
|
279
|
+
maxContextBudget,
|
|
280
|
+
avgImportDepth,
|
|
281
|
+
maxImportDepth,
|
|
282
|
+
deepFiles,
|
|
283
|
+
avgFragmentation,
|
|
284
|
+
fragmentedModules: fragmentedModules.slice(0, 10),
|
|
285
|
+
avgCohesion,
|
|
286
|
+
lowCohesionFiles,
|
|
287
|
+
criticalIssues,
|
|
288
|
+
majorIssues,
|
|
289
|
+
minorIssues,
|
|
290
|
+
totalPotentialSavings,
|
|
291
|
+
topExpensiveFiles,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Analyze issues for a single file
|
|
297
|
+
*/
|
|
298
|
+
function analyzeIssues(params: {
|
|
299
|
+
file: string;
|
|
300
|
+
importDepth: number;
|
|
301
|
+
contextBudget: number;
|
|
302
|
+
cohesionScore: number;
|
|
303
|
+
fragmentationScore: number;
|
|
304
|
+
maxDepth: number;
|
|
305
|
+
maxContextBudget: number;
|
|
306
|
+
minCohesion: number;
|
|
307
|
+
maxFragmentation: number;
|
|
308
|
+
circularDeps: string[][];
|
|
309
|
+
}): {
|
|
310
|
+
severity: ContextAnalysisResult['severity'];
|
|
311
|
+
issues: string[];
|
|
312
|
+
recommendations: string[];
|
|
313
|
+
potentialSavings: number;
|
|
314
|
+
} {
|
|
315
|
+
const {
|
|
316
|
+
file,
|
|
317
|
+
importDepth,
|
|
318
|
+
contextBudget,
|
|
319
|
+
cohesionScore,
|
|
320
|
+
fragmentationScore,
|
|
321
|
+
maxDepth,
|
|
322
|
+
maxContextBudget,
|
|
323
|
+
minCohesion,
|
|
324
|
+
maxFragmentation,
|
|
325
|
+
circularDeps,
|
|
326
|
+
} = params;
|
|
327
|
+
|
|
328
|
+
const issues: string[] = [];
|
|
329
|
+
const recommendations: string[] = [];
|
|
330
|
+
let severity: ContextAnalysisResult['severity'] = 'info';
|
|
331
|
+
let potentialSavings = 0;
|
|
332
|
+
|
|
333
|
+
// Check circular dependencies (CRITICAL)
|
|
334
|
+
if (circularDeps.length > 0) {
|
|
335
|
+
severity = 'critical';
|
|
336
|
+
issues.push(
|
|
337
|
+
`Part of ${circularDeps.length} circular dependency chain(s)`
|
|
338
|
+
);
|
|
339
|
+
recommendations.push('Break circular dependencies by extracting interfaces or using dependency injection');
|
|
340
|
+
potentialSavings += contextBudget * 0.2; // Estimate 20% savings
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Check import depth
|
|
344
|
+
if (importDepth > maxDepth * 1.5) {
|
|
345
|
+
severity = severity === 'critical' ? 'critical' : 'critical';
|
|
346
|
+
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
347
|
+
recommendations.push('Flatten dependency tree or use facade pattern');
|
|
348
|
+
potentialSavings += contextBudget * 0.3; // Estimate 30% savings
|
|
349
|
+
} else if (importDepth > maxDepth) {
|
|
350
|
+
severity = severity === 'critical' ? 'critical' : 'major';
|
|
351
|
+
issues.push(`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`);
|
|
352
|
+
recommendations.push('Consider reducing dependency depth');
|
|
353
|
+
potentialSavings += contextBudget * 0.15;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check context budget
|
|
357
|
+
if (contextBudget > maxContextBudget * 1.5) {
|
|
358
|
+
severity = severity === 'critical' ? 'critical' : 'critical';
|
|
359
|
+
issues.push(`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`);
|
|
360
|
+
recommendations.push('Split into smaller modules or reduce dependency tree');
|
|
361
|
+
potentialSavings += contextBudget * 0.4; // Significant savings possible
|
|
362
|
+
} else if (contextBudget > maxContextBudget) {
|
|
363
|
+
severity = severity === 'critical' || severity === 'major' ? severity : 'major';
|
|
364
|
+
issues.push(`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`);
|
|
365
|
+
recommendations.push('Reduce file size or dependencies');
|
|
366
|
+
potentialSavings += contextBudget * 0.2;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check cohesion
|
|
370
|
+
if (cohesionScore < minCohesion * 0.5) {
|
|
371
|
+
severity = severity === 'critical' ? 'critical' : 'major';
|
|
372
|
+
issues.push(`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`);
|
|
373
|
+
recommendations.push('Split file by domain - separate unrelated functionality');
|
|
374
|
+
potentialSavings += contextBudget * 0.25;
|
|
375
|
+
} else if (cohesionScore < minCohesion) {
|
|
376
|
+
severity = severity === 'critical' || severity === 'major' ? severity : 'minor';
|
|
377
|
+
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
378
|
+
recommendations.push('Consider grouping related exports together');
|
|
379
|
+
potentialSavings += contextBudget * 0.1;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Check fragmentation
|
|
383
|
+
if (fragmentationScore > maxFragmentation) {
|
|
384
|
+
severity = severity === 'critical' || severity === 'major' ? severity : 'minor';
|
|
385
|
+
issues.push(`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`);
|
|
386
|
+
recommendations.push('Consolidate with related files in same domain');
|
|
387
|
+
potentialSavings += contextBudget * 0.3;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (issues.length === 0) {
|
|
391
|
+
issues.push('No significant issues detected');
|
|
392
|
+
recommendations.push('File is well-structured for AI context usage');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return { severity, issues, recommendations, potentialSavings: Math.floor(potentialSavings) };
|
|
396
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ScanOptions } from '@aiready/core';
|
|
2
|
+
|
|
3
|
+
export interface ContextAnalyzerOptions extends ScanOptions {
|
|
4
|
+
maxDepth?: number; // Maximum acceptable import depth, default 5
|
|
5
|
+
maxContextBudget?: number; // Maximum acceptable token budget, default 10000
|
|
6
|
+
minCohesion?: number; // Minimum acceptable cohesion score (0-1), default 0.6
|
|
7
|
+
maxFragmentation?: number; // Maximum acceptable fragmentation (0-1), default 0.5
|
|
8
|
+
focus?: 'fragmentation' | 'cohesion' | 'depth' | 'all'; // Analysis focus, default 'all'
|
|
9
|
+
includeNodeModules?: boolean; // Include node_modules in analysis, default false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ContextAnalysisResult {
|
|
13
|
+
file: string;
|
|
14
|
+
|
|
15
|
+
// Basic metrics
|
|
16
|
+
tokenCost: number; // Total tokens in this file
|
|
17
|
+
linesOfCode: number;
|
|
18
|
+
|
|
19
|
+
// Dependency analysis
|
|
20
|
+
importDepth: number; // Max depth of import tree
|
|
21
|
+
dependencyCount: number; // Total transitive dependencies
|
|
22
|
+
dependencyList: string[]; // All files in dependency tree
|
|
23
|
+
circularDeps: string[][]; // Circular dependency chains if any
|
|
24
|
+
|
|
25
|
+
// Cohesion analysis
|
|
26
|
+
cohesionScore: number; // 0-1, how related are exports (1 = perfect cohesion)
|
|
27
|
+
domains: string[]; // Detected domain categories (e.g., ['user', 'auth'])
|
|
28
|
+
exportCount: number;
|
|
29
|
+
|
|
30
|
+
// AI context impact
|
|
31
|
+
contextBudget: number; // Total tokens to understand this file (includes all deps)
|
|
32
|
+
fragmentationScore: number; // 0-1, how scattered is this domain (0 = well-grouped)
|
|
33
|
+
relatedFiles: string[]; // Files that should be loaded together
|
|
34
|
+
|
|
35
|
+
// Recommendations
|
|
36
|
+
severity: 'critical' | 'major' | 'minor' | 'info';
|
|
37
|
+
issues: string[]; // List of specific problems
|
|
38
|
+
recommendations: string[]; // Actionable suggestions
|
|
39
|
+
potentialSavings: number; // Estimated token savings if fixed
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ModuleCluster {
|
|
43
|
+
domain: string; // e.g., "user-management", "auth"
|
|
44
|
+
files: string[];
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
fragmentationScore: number; // 0-1, higher = more scattered
|
|
47
|
+
avgCohesion: number; // Average cohesion across files in cluster
|
|
48
|
+
suggestedStructure: {
|
|
49
|
+
targetFiles: number; // Recommended number of files
|
|
50
|
+
consolidationPlan: string[]; // Step-by-step suggestions
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ContextSummary {
|
|
55
|
+
totalFiles: number;
|
|
56
|
+
totalTokens: number;
|
|
57
|
+
avgContextBudget: number;
|
|
58
|
+
maxContextBudget: number;
|
|
59
|
+
|
|
60
|
+
// Depth metrics
|
|
61
|
+
avgImportDepth: number;
|
|
62
|
+
maxImportDepth: number;
|
|
63
|
+
deepFiles: Array<{ file: string; depth: number }>; // Files exceeding maxDepth
|
|
64
|
+
|
|
65
|
+
// Fragmentation metrics
|
|
66
|
+
avgFragmentation: number;
|
|
67
|
+
fragmentedModules: ModuleCluster[];
|
|
68
|
+
|
|
69
|
+
// Cohesion metrics
|
|
70
|
+
avgCohesion: number;
|
|
71
|
+
lowCohesionFiles: Array<{ file: string; score: number }>;
|
|
72
|
+
|
|
73
|
+
// Issues summary
|
|
74
|
+
criticalIssues: number;
|
|
75
|
+
majorIssues: number;
|
|
76
|
+
minorIssues: number;
|
|
77
|
+
totalPotentialSavings: number;
|
|
78
|
+
|
|
79
|
+
// Top offenders
|
|
80
|
+
topExpensiveFiles: Array<{
|
|
81
|
+
file: string;
|
|
82
|
+
contextBudget: number;
|
|
83
|
+
severity: string;
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DependencyGraph {
|
|
88
|
+
nodes: Map<string, DependencyNode>;
|
|
89
|
+
edges: Map<string, Set<string>>; // file -> dependencies
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface DependencyNode {
|
|
93
|
+
file: string;
|
|
94
|
+
imports: string[]; // Direct imports
|
|
95
|
+
exports: ExportInfo[];
|
|
96
|
+
tokenCost: number;
|
|
97
|
+
linesOfCode: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ExportInfo {
|
|
101
|
+
name: string;
|
|
102
|
+
type: 'function' | 'class' | 'const' | 'type' | 'interface' | 'default';
|
|
103
|
+
inferredDomain?: string; // Inferred from name/usage
|
|
104
|
+
}
|