@aiready/context-analyzer 0.9.34 → 0.9.36
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/.github/FUNDING.yml +2 -2
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +19 -19
- package/CONTRIBUTING.md +10 -2
- package/dist/chunk-7LUSCLGR.mjs +2058 -0
- package/dist/cli.js +346 -83
- package/dist/cli.mjs +137 -33
- package/dist/index.js +231 -56
- package/dist/index.mjs +1 -1
- package/dist/python-context-GOH747QU.mjs +202 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +69 -17
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +19 -7
- package/src/__tests__/file-classification.test.ts +188 -53
- package/src/__tests__/fragmentation-advanced.test.ts +2 -11
- package/src/__tests__/fragmentation-coupling.test.ts +8 -2
- package/src/__tests__/fragmentation-log.test.ts +9 -9
- package/src/__tests__/scoring.test.ts +19 -7
- package/src/__tests__/structural-cohesion.test.ts +33 -21
- package/src/analyzer.ts +724 -376
- package/src/analyzers/python-context.ts +33 -10
- package/src/cli.ts +223 -59
- package/src/index.ts +112 -55
- package/src/scoring.ts +53 -43
- package/src/semantic-analysis.ts +73 -55
- package/src/types.ts +12 -13
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Python Context Analyzer
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Analyzes Python code for:
|
|
5
5
|
* - Import chain depth
|
|
6
6
|
* - Context budget (tokens needed)
|
|
@@ -54,34 +54,52 @@ export async function analyzePythonContext(
|
|
|
54
54
|
return results;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const pythonFiles = files.filter(f => f.toLowerCase().endsWith('.py'));
|
|
57
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith('.py'));
|
|
58
|
+
|
|
59
|
+
// Some path helpers are imported for future use; reference them to avoid lint warnings
|
|
60
|
+
void relative;
|
|
61
|
+
void join;
|
|
58
62
|
|
|
59
63
|
// Build dependency graph first
|
|
60
|
-
const dependencyGraph = await buildPythonDependencyGraph(
|
|
64
|
+
const dependencyGraph = await buildPythonDependencyGraph(
|
|
65
|
+
pythonFiles,
|
|
66
|
+
rootDir
|
|
67
|
+
);
|
|
61
68
|
|
|
62
69
|
for (const file of pythonFiles) {
|
|
63
70
|
try {
|
|
64
71
|
const code = await fs.promises.readFile(file, 'utf-8');
|
|
65
72
|
const result = parser.parse(code, file);
|
|
66
73
|
|
|
67
|
-
const imports: PythonImportInfo[] = result.imports.map(imp => ({
|
|
74
|
+
const imports: PythonImportInfo[] = result.imports.map((imp) => ({
|
|
68
75
|
source: imp.source,
|
|
69
76
|
specifiers: imp.specifiers,
|
|
70
77
|
isRelative: imp.source.startsWith('.'),
|
|
71
78
|
resolvedPath: resolvePythonImport(file, imp.source, rootDir),
|
|
72
79
|
}));
|
|
73
80
|
|
|
74
|
-
const exports: PythonExportInfo[] = result.exports.map(exp => ({
|
|
81
|
+
const exports: PythonExportInfo[] = result.exports.map((exp) => ({
|
|
75
82
|
name: exp.name,
|
|
76
83
|
type: exp.type,
|
|
77
84
|
}));
|
|
78
85
|
|
|
79
86
|
// Calculate metrics
|
|
80
87
|
const linesOfCode = code.split('\n').length;
|
|
81
|
-
const importDepth = await calculatePythonImportDepth(
|
|
82
|
-
|
|
88
|
+
const importDepth = await calculatePythonImportDepth(
|
|
89
|
+
file,
|
|
90
|
+
dependencyGraph,
|
|
91
|
+
new Set()
|
|
92
|
+
);
|
|
93
|
+
const contextBudget = estimateContextBudget(
|
|
94
|
+
code,
|
|
95
|
+
imports,
|
|
96
|
+
dependencyGraph
|
|
97
|
+
);
|
|
83
98
|
const cohesion = calculatePythonCohesion(exports, imports);
|
|
84
|
-
const circularDependencies = detectCircularDependencies(
|
|
99
|
+
const circularDependencies = detectCircularDependencies(
|
|
100
|
+
file,
|
|
101
|
+
dependencyGraph
|
|
102
|
+
);
|
|
85
103
|
|
|
86
104
|
results.push({
|
|
87
105
|
file,
|
|
@@ -133,6 +151,7 @@ async function buildPythonDependencyGraph(
|
|
|
133
151
|
|
|
134
152
|
graph.set(file, dependencies);
|
|
135
153
|
} catch (error) {
|
|
154
|
+
void error;
|
|
136
155
|
// Skip files with errors
|
|
137
156
|
}
|
|
138
157
|
}
|
|
@@ -143,7 +162,11 @@ async function buildPythonDependencyGraph(
|
|
|
143
162
|
/**
|
|
144
163
|
* Resolve Python import to file path
|
|
145
164
|
*/
|
|
146
|
-
function resolvePythonImport(
|
|
165
|
+
function resolvePythonImport(
|
|
166
|
+
fromFile: string,
|
|
167
|
+
importPath: string,
|
|
168
|
+
rootDir: string
|
|
169
|
+
): string | undefined {
|
|
147
170
|
const dir = dirname(fromFile);
|
|
148
171
|
|
|
149
172
|
// Handle relative imports
|
|
@@ -244,7 +267,7 @@ function estimateContextBudget(
|
|
|
244
267
|
|
|
245
268
|
/**
|
|
246
269
|
* Calculate cohesion for a Python module
|
|
247
|
-
*
|
|
270
|
+
*
|
|
248
271
|
* Cohesion = How related are the exports to each other?
|
|
249
272
|
* Higher cohesion = better (single responsibility)
|
|
250
273
|
*/
|
package/src/cli.ts
CHANGED
|
@@ -5,7 +5,13 @@ import { analyzeContext, generateSummary } from './index';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs';
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
loadMergedConfig,
|
|
10
|
+
handleJSONOutput,
|
|
11
|
+
handleCLIError,
|
|
12
|
+
getElapsedTime,
|
|
13
|
+
resolveOutputPath,
|
|
14
|
+
} from '@aiready/core';
|
|
9
15
|
import prompts from 'prompts';
|
|
10
16
|
|
|
11
17
|
const program = new Command();
|
|
@@ -14,7 +20,10 @@ program
|
|
|
14
20
|
.name('aiready-context')
|
|
15
21
|
.description('Analyze AI context window cost and code structure')
|
|
16
22
|
.version('0.1.0')
|
|
17
|
-
.addHelpText(
|
|
23
|
+
.addHelpText(
|
|
24
|
+
'after',
|
|
25
|
+
'\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings'
|
|
26
|
+
)
|
|
18
27
|
.argument('<directory>', 'Directory to analyze')
|
|
19
28
|
.option('--max-depth <number>', 'Maximum acceptable import depth')
|
|
20
29
|
.option(
|
|
@@ -33,14 +42,20 @@ program
|
|
|
33
42
|
.option('--include-node-modules', 'Include node_modules in analysis')
|
|
34
43
|
.option('--include <patterns>', 'File patterns to include (comma-separated)')
|
|
35
44
|
.option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
|
|
36
|
-
.option(
|
|
45
|
+
.option(
|
|
46
|
+
'--max-results <number>',
|
|
47
|
+
'Maximum number of results to show in console output'
|
|
48
|
+
)
|
|
37
49
|
.option(
|
|
38
50
|
'-o, --output <format>',
|
|
39
51
|
'Output format: console, json, html',
|
|
40
52
|
'console'
|
|
41
53
|
)
|
|
42
54
|
.option('--output-file <path>', 'Output file path (for json/html)')
|
|
43
|
-
.option(
|
|
55
|
+
.option(
|
|
56
|
+
'--interactive',
|
|
57
|
+
'Run interactive setup to suggest excludes and focus areas'
|
|
58
|
+
)
|
|
44
59
|
.action(async (directory, options) => {
|
|
45
60
|
console.log(chalk.blue('🔍 Analyzing context window costs...\n'));
|
|
46
61
|
|
|
@@ -61,17 +76,27 @@ program
|
|
|
61
76
|
};
|
|
62
77
|
|
|
63
78
|
// Load and merge config with CLI options
|
|
64
|
-
let finalOptions = await loadMergedConfig(directory, defaults, {
|
|
79
|
+
let finalOptions = (await loadMergedConfig(directory, defaults, {
|
|
65
80
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : undefined,
|
|
66
|
-
maxContextBudget: options.maxContext
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
maxContextBudget: options.maxContext
|
|
82
|
+
? parseInt(options.maxContext)
|
|
83
|
+
: undefined,
|
|
84
|
+
minCohesion: options.minCohesion
|
|
85
|
+
? parseFloat(options.minCohesion)
|
|
86
|
+
: undefined,
|
|
87
|
+
maxFragmentation: options.maxFragmentation
|
|
88
|
+
? parseFloat(options.maxFragmentation)
|
|
89
|
+
: undefined,
|
|
90
|
+
focus:
|
|
91
|
+
(options.focus as 'fragmentation' | 'cohesion' | 'depth' | 'all') ||
|
|
92
|
+
undefined,
|
|
70
93
|
includeNodeModules: options.includeNodeModules,
|
|
71
94
|
include: options.include?.split(','),
|
|
72
95
|
exclude: options.exclude?.split(','),
|
|
73
|
-
maxResults: options.maxResults
|
|
74
|
-
|
|
96
|
+
maxResults: options.maxResults
|
|
97
|
+
? parseInt(options.maxResults)
|
|
98
|
+
: undefined,
|
|
99
|
+
})) as any;
|
|
75
100
|
|
|
76
101
|
// Optional: interactive setup to refine options for first-time users
|
|
77
102
|
if (options.interactive) {
|
|
@@ -96,8 +121,12 @@ program
|
|
|
96
121
|
`context-report-${new Date().toISOString().split('T')[0]}.json`,
|
|
97
122
|
directory
|
|
98
123
|
);
|
|
99
|
-
|
|
100
|
-
handleJSONOutput(
|
|
124
|
+
|
|
125
|
+
handleJSONOutput(
|
|
126
|
+
jsonOutput,
|
|
127
|
+
outputPath,
|
|
128
|
+
`\n✓ JSON report saved to ${outputPath}`
|
|
129
|
+
);
|
|
101
130
|
return;
|
|
102
131
|
}
|
|
103
132
|
|
|
@@ -108,20 +137,25 @@ program
|
|
|
108
137
|
`context-report-${new Date().toISOString().split('T')[0]}.html`,
|
|
109
138
|
directory
|
|
110
139
|
);
|
|
111
|
-
|
|
140
|
+
|
|
112
141
|
const dir = dirname(outputPath);
|
|
113
142
|
if (!existsSync(dir)) {
|
|
114
143
|
mkdirSync(dir, { recursive: true });
|
|
115
144
|
}
|
|
116
|
-
|
|
145
|
+
|
|
117
146
|
writeFileSync(outputPath, html);
|
|
118
147
|
console.log(chalk.green(`\n✓ HTML report saved to ${outputPath}`));
|
|
119
148
|
return;
|
|
120
149
|
}
|
|
121
150
|
|
|
122
151
|
// Console output
|
|
123
|
-
displayConsoleReport(
|
|
124
|
-
|
|
152
|
+
displayConsoleReport(
|
|
153
|
+
summary,
|
|
154
|
+
results,
|
|
155
|
+
elapsedTime,
|
|
156
|
+
finalOptions.maxResults
|
|
157
|
+
);
|
|
158
|
+
|
|
125
159
|
// Show tuning guidance after results
|
|
126
160
|
displayTuningGuidance(results, finalOptions);
|
|
127
161
|
} catch (error) {
|
|
@@ -138,35 +172,105 @@ function displayTuningGuidance(
|
|
|
138
172
|
results: Awaited<ReturnType<typeof analyzeContext>>,
|
|
139
173
|
options: any
|
|
140
174
|
): void {
|
|
141
|
-
const issueCount = results.filter(r => r.severity !== 'info').length;
|
|
142
|
-
|
|
175
|
+
const issueCount = results.filter((r) => r.severity !== 'info').length;
|
|
176
|
+
|
|
143
177
|
if (issueCount === 0) {
|
|
144
|
-
console.log(
|
|
178
|
+
console.log(
|
|
179
|
+
chalk.green(
|
|
180
|
+
'\n✨ No optimization opportunities found! Your code is well-structured for AI context usage.\n'
|
|
181
|
+
)
|
|
182
|
+
);
|
|
145
183
|
return;
|
|
146
184
|
}
|
|
147
|
-
|
|
185
|
+
|
|
148
186
|
console.log(chalk.cyan('\n━'.repeat(60)));
|
|
149
187
|
console.log(chalk.bold.white(' TUNING GUIDANCE'));
|
|
150
188
|
console.log(chalk.cyan('━'.repeat(60) + '\n'));
|
|
151
|
-
|
|
189
|
+
|
|
152
190
|
if (issueCount < 5) {
|
|
153
|
-
console.log(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
191
|
+
console.log(
|
|
192
|
+
chalk.yellow(
|
|
193
|
+
'📊 Showing few optimization opportunities. To find more areas to improve:\n'
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
console.log(
|
|
197
|
+
chalk.dim(
|
|
198
|
+
' • Lower --max-depth (currently: ' +
|
|
199
|
+
options.maxDepth +
|
|
200
|
+
') to catch shallower import chains'
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
console.log(
|
|
204
|
+
chalk.dim(
|
|
205
|
+
' • Lower --max-context (currently: ' +
|
|
206
|
+
options.maxContextBudget.toLocaleString() +
|
|
207
|
+
') to catch smaller files'
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
console.log(
|
|
211
|
+
chalk.dim(
|
|
212
|
+
' • Raise --min-cohesion (currently: ' +
|
|
213
|
+
(options.minCohesion * 100).toFixed(0) +
|
|
214
|
+
'%) to be stricter about mixed concerns'
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
console.log(
|
|
218
|
+
chalk.dim(
|
|
219
|
+
' • Lower --max-fragmentation (currently: ' +
|
|
220
|
+
(options.maxFragmentation * 100).toFixed(0) +
|
|
221
|
+
'%) to catch scattered code\n'
|
|
222
|
+
)
|
|
223
|
+
);
|
|
158
224
|
} else if (issueCount > 20) {
|
|
159
|
-
console.log(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
225
|
+
console.log(
|
|
226
|
+
chalk.yellow(
|
|
227
|
+
'📊 Showing many opportunities. To focus on highest-impact areas:\n'
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
console.log(
|
|
231
|
+
chalk.dim(
|
|
232
|
+
' • Raise --max-depth (currently: ' +
|
|
233
|
+
options.maxDepth +
|
|
234
|
+
') to only catch very deep chains'
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
console.log(
|
|
238
|
+
chalk.dim(
|
|
239
|
+
' • Raise --max-context (currently: ' +
|
|
240
|
+
options.maxContextBudget.toLocaleString() +
|
|
241
|
+
') to focus on largest files'
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
console.log(
|
|
245
|
+
chalk.dim(
|
|
246
|
+
' • Lower --min-cohesion (currently: ' +
|
|
247
|
+
(options.minCohesion * 100).toFixed(0) +
|
|
248
|
+
'%) to only flag severe mixed concerns'
|
|
249
|
+
)
|
|
250
|
+
);
|
|
251
|
+
console.log(
|
|
252
|
+
chalk.dim(
|
|
253
|
+
' • Raise --max-fragmentation (currently: ' +
|
|
254
|
+
(options.maxFragmentation * 100).toFixed(0) +
|
|
255
|
+
'%) to only flag highly scattered code\n'
|
|
256
|
+
)
|
|
257
|
+
);
|
|
164
258
|
} else {
|
|
165
|
-
console.log(
|
|
259
|
+
console.log(
|
|
260
|
+
chalk.green(
|
|
261
|
+
'📊 Good balance of optimization opportunities (showing ' +
|
|
262
|
+
issueCount +
|
|
263
|
+
' areas)\n'
|
|
264
|
+
)
|
|
265
|
+
);
|
|
166
266
|
console.log(chalk.dim(' 💡 Tip: Adjust thresholds if needed:'));
|
|
167
|
-
console.log(
|
|
267
|
+
console.log(
|
|
268
|
+
chalk.dim(
|
|
269
|
+
' aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n'
|
|
270
|
+
)
|
|
271
|
+
);
|
|
168
272
|
}
|
|
169
|
-
|
|
273
|
+
|
|
170
274
|
console.log(chalk.dim(' 📖 See README for detailed tuning guide\n'));
|
|
171
275
|
}
|
|
172
276
|
|
|
@@ -188,9 +292,13 @@ function displayConsoleReport(
|
|
|
188
292
|
console.log(chalk.cyan(divider) + '\n');
|
|
189
293
|
|
|
190
294
|
// Overview
|
|
191
|
-
console.log(chalk.white(`📁 Files analyzed: ${chalk.bold(summary.totalFiles)}`));
|
|
192
295
|
console.log(
|
|
193
|
-
chalk.white(
|
|
296
|
+
chalk.white(`📁 Files analyzed: ${chalk.bold(summary.totalFiles)}`)
|
|
297
|
+
);
|
|
298
|
+
console.log(
|
|
299
|
+
chalk.white(
|
|
300
|
+
`📊 Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`
|
|
301
|
+
)
|
|
194
302
|
);
|
|
195
303
|
console.log(
|
|
196
304
|
chalk.yellow(
|
|
@@ -217,7 +325,9 @@ function displayConsoleReport(
|
|
|
217
325
|
);
|
|
218
326
|
}
|
|
219
327
|
if (summary.minorIssues > 0) {
|
|
220
|
-
console.log(
|
|
328
|
+
console.log(
|
|
329
|
+
chalk.blue(` 🔵 Minor: ${chalk.bold(summary.minorIssues)}`)
|
|
330
|
+
);
|
|
221
331
|
}
|
|
222
332
|
console.log(
|
|
223
333
|
chalk.green(
|
|
@@ -297,8 +407,8 @@ function displayConsoleReport(
|
|
|
297
407
|
item.severity === 'critical'
|
|
298
408
|
? chalk.red
|
|
299
409
|
: item.severity === 'major'
|
|
300
|
-
|
|
301
|
-
|
|
410
|
+
? chalk.yellow
|
|
411
|
+
: chalk.blue;
|
|
302
412
|
|
|
303
413
|
console.log(
|
|
304
414
|
` ${severityColor('●')} ${chalk.white(fileName)} ${chalk.dim(`- ${item.contextBudget.toLocaleString()} tokens`)}`
|
|
@@ -333,7 +443,9 @@ function displayConsoleReport(
|
|
|
333
443
|
)
|
|
334
444
|
);
|
|
335
445
|
console.log(
|
|
336
|
-
chalk.dim(
|
|
446
|
+
chalk.dim(
|
|
447
|
+
'🐛 Found a bug? Report it: https://github.com/caopengau/aiready-context-analyzer/issues\n'
|
|
448
|
+
)
|
|
337
449
|
);
|
|
338
450
|
}
|
|
339
451
|
|
|
@@ -347,6 +459,9 @@ function generateHTMLReport(
|
|
|
347
459
|
const totalIssues =
|
|
348
460
|
summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
349
461
|
|
|
462
|
+
// 'results' may be used in templates later; reference to avoid lint warnings
|
|
463
|
+
void results;
|
|
464
|
+
|
|
350
465
|
return `<!DOCTYPE html>
|
|
351
466
|
<html lang="en">
|
|
352
467
|
<head>
|
|
@@ -449,7 +564,9 @@ function generateHTMLReport(
|
|
|
449
564
|
</div>
|
|
450
565
|
</div>
|
|
451
566
|
|
|
452
|
-
${
|
|
567
|
+
${
|
|
568
|
+
totalIssues > 0
|
|
569
|
+
? `
|
|
453
570
|
<div class="card" style="margin-bottom: 30px;">
|
|
454
571
|
<h2>⚠️ Issues Summary</h2>
|
|
455
572
|
<p>
|
|
@@ -459,9 +576,13 @@ function generateHTMLReport(
|
|
|
459
576
|
</p>
|
|
460
577
|
<p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
|
|
461
578
|
</div>
|
|
462
|
-
`
|
|
579
|
+
`
|
|
580
|
+
: ''
|
|
581
|
+
}
|
|
463
582
|
|
|
464
|
-
${
|
|
583
|
+
${
|
|
584
|
+
summary.fragmentedModules.length > 0
|
|
585
|
+
? `
|
|
465
586
|
<div class="card" style="margin-bottom: 30px;">
|
|
466
587
|
<h2>🧩 Fragmented Modules</h2>
|
|
467
588
|
<table>
|
|
@@ -474,20 +595,28 @@ function generateHTMLReport(
|
|
|
474
595
|
</tr>
|
|
475
596
|
</thead>
|
|
476
597
|
<tbody>
|
|
477
|
-
${summary.fragmentedModules
|
|
598
|
+
${summary.fragmentedModules
|
|
599
|
+
.map(
|
|
600
|
+
(m) => `
|
|
478
601
|
<tr>
|
|
479
602
|
<td>${m.domain}</td>
|
|
480
603
|
<td>${m.files.length}</td>
|
|
481
604
|
<td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
|
|
482
605
|
<td>${m.totalTokens.toLocaleString()}</td>
|
|
483
606
|
</tr>
|
|
484
|
-
`
|
|
607
|
+
`
|
|
608
|
+
)
|
|
609
|
+
.join('')}
|
|
485
610
|
</tbody>
|
|
486
611
|
</table>
|
|
487
612
|
</div>
|
|
488
|
-
`
|
|
613
|
+
`
|
|
614
|
+
: ''
|
|
615
|
+
}
|
|
489
616
|
|
|
490
|
-
${
|
|
617
|
+
${
|
|
618
|
+
summary.topExpensiveFiles.length > 0
|
|
619
|
+
? `
|
|
491
620
|
<div class="card" style="margin-bottom: 30px;">
|
|
492
621
|
<h2>💸 Most Expensive Files</h2>
|
|
493
622
|
<table>
|
|
@@ -499,17 +628,23 @@ function generateHTMLReport(
|
|
|
499
628
|
</tr>
|
|
500
629
|
</thead>
|
|
501
630
|
<tbody>
|
|
502
|
-
${summary.topExpensiveFiles
|
|
631
|
+
${summary.topExpensiveFiles
|
|
632
|
+
.map(
|
|
633
|
+
(f) => `
|
|
503
634
|
<tr>
|
|
504
635
|
<td>${f.file}</td>
|
|
505
636
|
<td>${f.contextBudget.toLocaleString()} tokens</td>
|
|
506
637
|
<td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
|
|
507
638
|
</tr>
|
|
508
|
-
`
|
|
639
|
+
`
|
|
640
|
+
)
|
|
641
|
+
.join('')}
|
|
509
642
|
</tbody>
|
|
510
643
|
</table>
|
|
511
644
|
</div>
|
|
512
|
-
`
|
|
645
|
+
`
|
|
646
|
+
: ''
|
|
647
|
+
}
|
|
513
648
|
|
|
514
649
|
<div class="footer">
|
|
515
650
|
<p>Generated by <strong>@aiready/context-analyzer</strong></p>
|
|
@@ -523,7 +658,10 @@ function generateHTMLReport(
|
|
|
523
658
|
/**
|
|
524
659
|
* Interactive setup: detect common frameworks and suggest excludes & focus areas
|
|
525
660
|
*/
|
|
526
|
-
async function runInteractiveSetup(
|
|
661
|
+
async function runInteractiveSetup(
|
|
662
|
+
directory: string,
|
|
663
|
+
current: any
|
|
664
|
+
): Promise<any> {
|
|
527
665
|
console.log(chalk.yellow('🧭 Interactive mode: let’s tailor the analysis.'));
|
|
528
666
|
|
|
529
667
|
const pkgPath = join(directory, 'package.json');
|
|
@@ -532,17 +670,29 @@ async function runInteractiveSetup(directory: string, current: any): Promise<any
|
|
|
532
670
|
try {
|
|
533
671
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
534
672
|
deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
535
|
-
} catch {
|
|
673
|
+
} catch (e) {
|
|
674
|
+
void e;
|
|
675
|
+
// Ignore parse errors, use empty deps
|
|
676
|
+
}
|
|
536
677
|
}
|
|
537
678
|
|
|
538
679
|
const hasNextJs = existsSync(join(directory, '.next')) || !!deps['next'];
|
|
539
|
-
const hasCDK =
|
|
680
|
+
const hasCDK =
|
|
681
|
+
existsSync(join(directory, 'cdk.out')) ||
|
|
682
|
+
!!deps['aws-cdk-lib'] ||
|
|
683
|
+
Object.keys(deps).some((d) => d.startsWith('@aws-cdk/'));
|
|
540
684
|
|
|
541
685
|
const recommendedExcludes = new Set<string>(current.exclude || []);
|
|
542
|
-
if (
|
|
686
|
+
if (
|
|
687
|
+
hasNextJs &&
|
|
688
|
+
!Array.from(recommendedExcludes).some((p) => p.includes('.next'))
|
|
689
|
+
) {
|
|
543
690
|
recommendedExcludes.add('**/.next/**');
|
|
544
691
|
}
|
|
545
|
-
if (
|
|
692
|
+
if (
|
|
693
|
+
hasCDK &&
|
|
694
|
+
!Array.from(recommendedExcludes).some((p) => p.includes('cdk.out'))
|
|
695
|
+
) {
|
|
546
696
|
recommendedExcludes.add('**/cdk.out/**');
|
|
547
697
|
}
|
|
548
698
|
|
|
@@ -555,7 +705,7 @@ async function runInteractiveSetup(directory: string, current: any): Promise<any
|
|
|
555
705
|
inactive: 'no',
|
|
556
706
|
});
|
|
557
707
|
|
|
558
|
-
|
|
708
|
+
const nextOptions = { ...current };
|
|
559
709
|
if (applyExcludes) {
|
|
560
710
|
nextOptions.exclude = Array.from(recommendedExcludes);
|
|
561
711
|
}
|
|
@@ -574,9 +724,23 @@ async function runInteractiveSetup(directory: string, current: any): Promise<any
|
|
|
574
724
|
|
|
575
725
|
if (focusArea === 'frontend') {
|
|
576
726
|
nextOptions.include = ['**/*.{ts,tsx,js,jsx}'];
|
|
577
|
-
nextOptions.exclude = Array.from(
|
|
727
|
+
nextOptions.exclude = Array.from(
|
|
728
|
+
new Set([
|
|
729
|
+
...(nextOptions.exclude || []),
|
|
730
|
+
'**/cdk.out/**',
|
|
731
|
+
'**/infra/**',
|
|
732
|
+
'**/server/**',
|
|
733
|
+
'**/backend/**',
|
|
734
|
+
])
|
|
735
|
+
);
|
|
578
736
|
} else if (focusArea === 'backend') {
|
|
579
|
-
nextOptions.include = [
|
|
737
|
+
nextOptions.include = [
|
|
738
|
+
'**/api/**',
|
|
739
|
+
'**/server/**',
|
|
740
|
+
'**/backend/**',
|
|
741
|
+
'**/infra/**',
|
|
742
|
+
'**/*.{ts,js,py,java}',
|
|
743
|
+
];
|
|
580
744
|
}
|
|
581
745
|
|
|
582
746
|
console.log(chalk.green('✓ Interactive configuration applied.'));
|