@aiready/context-analyzer 0.21.5 → 0.21.6
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/.aiready/aiready-report-20260314-222254.json +39216 -0
- package/.aiready/aiready-report-20260314-223947.json +3413 -0
- package/.aiready/aiready-report-20260314-224112.json +3413 -0
- package/.aiready/aiready-report-20260314-224302.json +2973 -0
- package/.aiready/aiready-report-20260314-224939.json +3092 -0
- package/.aiready/aiready-report-20260314-225154.json +3092 -0
- package/.turbo/turbo-build.log +26 -24
- package/.turbo/turbo-test.log +41 -119
- package/dist/__tests__/analyzer.test.js +55 -14
- package/dist/__tests__/analyzer.test.js.map +1 -1
- package/dist/__tests__/cluster-detector.test.d.ts +2 -0
- package/dist/__tests__/cluster-detector.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-detector.test.js +121 -0
- package/dist/__tests__/cluster-detector.test.js.map +1 -0
- package/dist/__tests__/contract.test.d.ts +2 -0
- package/dist/__tests__/contract.test.d.ts.map +1 -0
- package/dist/__tests__/contract.test.js +59 -0
- package/dist/__tests__/contract.test.js.map +1 -0
- package/dist/__tests__/enhanced-cohesion.test.js +12 -2
- package/dist/__tests__/enhanced-cohesion.test.js.map +1 -1
- package/dist/__tests__/file-classification.test.d.ts +2 -0
- package/dist/__tests__/file-classification.test.d.ts.map +1 -0
- package/dist/__tests__/file-classification.test.js +749 -0
- package/dist/__tests__/file-classification.test.js.map +1 -0
- package/dist/__tests__/fragmentation-advanced.test.js +2 -8
- package/dist/__tests__/fragmentation-advanced.test.js.map +1 -1
- package/dist/__tests__/fragmentation-coupling.test.js +2 -2
- package/dist/__tests__/fragmentation-coupling.test.js.map +1 -1
- package/dist/__tests__/fragmentation-log.test.js +3 -7
- package/dist/__tests__/fragmentation-log.test.js.map +1 -1
- package/dist/__tests__/provider.test.d.ts +2 -0
- package/dist/__tests__/provider.test.d.ts.map +1 -0
- package/dist/__tests__/provider.test.js +72 -0
- package/dist/__tests__/provider.test.js.map +1 -0
- package/dist/__tests__/remediation.test.d.ts +2 -0
- package/dist/__tests__/remediation.test.d.ts.map +1 -0
- package/dist/__tests__/remediation.test.js +61 -0
- package/dist/__tests__/remediation.test.js.map +1 -0
- package/dist/__tests__/scoring.test.js +196 -16
- package/dist/__tests__/scoring.test.js.map +1 -1
- package/dist/__tests__/structural-cohesion.test.js +8 -2
- package/dist/__tests__/structural-cohesion.test.js.map +1 -1
- package/dist/analyzer.d.ts +31 -94
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +260 -678
- package/dist/analyzer.js.map +1 -1
- package/dist/analyzers/python-context.d.ts.map +1 -1
- package/dist/analyzers/python-context.js +10 -8
- package/dist/analyzers/python-context.js.map +1 -1
- package/dist/ast-utils.d.ts +16 -0
- package/dist/ast-utils.d.ts.map +1 -0
- package/dist/ast-utils.js +81 -0
- package/dist/ast-utils.js.map +1 -0
- package/dist/chunk-64U3PNO3.mjs +94 -0
- package/dist/chunk-CDIVYADN.mjs +2110 -0
- package/dist/chunk-D3SIHB2V.mjs +2118 -0
- package/dist/chunk-FNPSK3CG.mjs +1760 -0
- package/dist/chunk-GXTGOLZT.mjs +92 -0
- package/dist/chunk-LERPI33Y.mjs +2060 -0
- package/dist/chunk-MZP3G7TF.mjs +2118 -0
- package/dist/chunk-NOHK5DLU.mjs +2173 -0
- package/dist/chunk-ORLC5Y4J.mjs +1787 -0
- package/dist/chunk-OTCQL7DY.mjs +2045 -0
- package/dist/chunk-SFK6XTJE.mjs +2110 -0
- package/dist/chunk-U5R2FTCR.mjs +1803 -0
- package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
- package/dist/chunk-WKOZOHOU.mjs +2060 -0
- package/dist/chunk-XIXAWCMS.mjs +1760 -0
- package/dist/classifier.d.ts +114 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +439 -0
- package/dist/classifier.js.map +1 -0
- package/dist/cli.js +590 -1071
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +63 -533
- package/dist/cluster-detector.d.ts +8 -0
- package/dist/cluster-detector.d.ts.map +1 -0
- package/dist/cluster-detector.js +70 -0
- package/dist/cluster-detector.js.map +1 -0
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +54 -0
- package/dist/defaults.js.map +1 -0
- package/dist/graph-builder.d.ts +33 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +225 -0
- package/dist/graph-builder.js.map +1 -0
- package/dist/index.d.mts +24 -31
- package/dist/index.d.ts +24 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +788 -569
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +265 -8
- package/dist/metrics.d.ts +34 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +170 -0
- package/dist/metrics.js.map +1 -0
- package/dist/provider.d.ts +6 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +48 -0
- package/dist/provider.js.map +1 -0
- package/dist/python-context-3GZKN3LR.mjs +162 -0
- package/dist/python-context-O2EN3M6Z.mjs +162 -0
- package/dist/remediation.d.ts +25 -0
- package/dist/remediation.d.ts.map +1 -0
- package/dist/remediation.js +98 -0
- package/dist/remediation.js.map +1 -0
- package/dist/scoring.d.ts +3 -7
- package/dist/scoring.d.ts.map +1 -1
- package/dist/scoring.js +57 -48
- package/dist/scoring.js.map +1 -1
- package/dist/semantic-analysis.d.ts +12 -23
- package/dist/semantic-analysis.d.ts.map +1 -1
- package/dist/semantic-analysis.js +172 -110
- package/dist/semantic-analysis.js.map +1 -1
- package/dist/summary.d.ts +6 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +92 -0
- package/dist/summary.js.map +1 -0
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/output-formatter.d.ts +14 -0
- package/dist/utils/output-formatter.d.ts.map +1 -0
- package/dist/utils/output-formatter.js +338 -0
- package/dist/utils/output-formatter.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +1 -1
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/contract.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +1 -1
- package/src/__tests__/file-classification.test.ts +1 -1
- package/src/__tests__/fragmentation-advanced.test.ts +1 -1
- package/src/__tests__/fragmentation-coupling.test.ts +1 -1
- package/src/__tests__/fragmentation-log.test.ts +1 -1
- package/src/__tests__/provider.test.ts +1 -1
- package/src/__tests__/structural-cohesion.test.ts +1 -1
- package/src/analyzer.ts +96 -309
- package/src/analyzers/python-context.ts +7 -76
- package/src/cli-action.ts +103 -0
- package/src/cli.ts +12 -693
- package/src/cluster-detector.ts +1 -1
- package/src/graph-builder.ts +9 -85
- package/src/index.ts +6 -0
- package/src/issue-analyzer.ts +143 -0
- package/src/semantic-analysis.ts +1 -14
- package/src/summary.ts +62 -106
- package/src/utils/dependency-graph-utils.ts +126 -0
- package/src/utils/output-formatter.ts +411 -0
- package/src/utils/string-utils.ts +17 -0
package/src/cli.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import { analyzeContext
|
|
4
|
+
import { analyzeContext } from './analyzer';
|
|
5
|
+
import { generateSummary } from './summary';
|
|
6
|
+
import {
|
|
7
|
+
displayConsoleReport,
|
|
8
|
+
generateHTMLReport,
|
|
9
|
+
runInteractiveSetup,
|
|
10
|
+
} from './utils/output-formatter';
|
|
5
11
|
import chalk from 'chalk';
|
|
6
|
-
import { writeFileSync
|
|
7
|
-
import { join, dirname } from 'path';
|
|
12
|
+
import { writeFileSync } from 'fs';
|
|
8
13
|
import {
|
|
9
14
|
loadMergedConfig,
|
|
10
15
|
handleJSONOutput,
|
|
@@ -12,7 +17,8 @@ import {
|
|
|
12
17
|
getElapsedTime,
|
|
13
18
|
resolveOutputPath,
|
|
14
19
|
} from '@aiready/core';
|
|
15
|
-
|
|
20
|
+
|
|
21
|
+
import { contextActionHandler } from './cli-action';
|
|
16
22
|
|
|
17
23
|
const program = new Command();
|
|
18
24
|
|
|
@@ -56,693 +62,6 @@ program
|
|
|
56
62
|
'--interactive',
|
|
57
63
|
'Run interactive setup to suggest excludes and focus areas'
|
|
58
64
|
)
|
|
59
|
-
.action(
|
|
60
|
-
console.log(chalk.blue('🔍 Analyzing context window costs...\n'));
|
|
61
|
-
|
|
62
|
-
const startTime = Date.now();
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
// Define defaults
|
|
66
|
-
const defaults = {
|
|
67
|
-
maxDepth: 5,
|
|
68
|
-
maxContextBudget: 10000,
|
|
69
|
-
minCohesion: 0.6,
|
|
70
|
-
maxFragmentation: 0.5,
|
|
71
|
-
focus: 'all',
|
|
72
|
-
includeNodeModules: false,
|
|
73
|
-
include: undefined,
|
|
74
|
-
exclude: undefined,
|
|
75
|
-
maxResults: 10,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Load and merge config with CLI options
|
|
79
|
-
let finalOptions = (await loadMergedConfig(directory, defaults, {
|
|
80
|
-
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : undefined,
|
|
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,
|
|
93
|
-
includeNodeModules: options.includeNodeModules,
|
|
94
|
-
include: options.include?.split(','),
|
|
95
|
-
exclude: options.exclude?.split(','),
|
|
96
|
-
maxResults: options.maxResults
|
|
97
|
-
? parseInt(options.maxResults)
|
|
98
|
-
: undefined,
|
|
99
|
-
})) as any;
|
|
100
|
-
|
|
101
|
-
// Optional: interactive setup to refine options for first-time users
|
|
102
|
-
if (options.interactive) {
|
|
103
|
-
finalOptions = await runInteractiveSetup(directory, finalOptions);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const results = await analyzeContext(finalOptions);
|
|
107
|
-
|
|
108
|
-
const elapsedTime = getElapsedTime(startTime);
|
|
109
|
-
const summary = generateSummary(results);
|
|
110
|
-
|
|
111
|
-
if (options.output === 'json') {
|
|
112
|
-
const jsonOutput = {
|
|
113
|
-
summary,
|
|
114
|
-
results,
|
|
115
|
-
timestamp: new Date().toISOString(),
|
|
116
|
-
analysisTime: elapsedTime,
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const outputPath = resolveOutputPath(
|
|
120
|
-
options.outputFile,
|
|
121
|
-
`context-report-${new Date().toISOString().split('T')[0]}.json`,
|
|
122
|
-
directory
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
handleJSONOutput(
|
|
126
|
-
jsonOutput,
|
|
127
|
-
outputPath,
|
|
128
|
-
`\n✓ JSON report saved to ${outputPath}`
|
|
129
|
-
);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (options.output === 'html') {
|
|
134
|
-
const html = generateHTMLReport(summary, results);
|
|
135
|
-
const outputPath = resolveOutputPath(
|
|
136
|
-
options.outputFile,
|
|
137
|
-
`context-report-${new Date().toISOString().split('T')[0]}.html`,
|
|
138
|
-
directory
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const dir = dirname(outputPath);
|
|
142
|
-
if (!existsSync(dir)) {
|
|
143
|
-
mkdirSync(dir, { recursive: true });
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
writeFileSync(outputPath, html);
|
|
147
|
-
console.log(chalk.green(`\n✓ HTML report saved to ${outputPath}`));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Console output
|
|
152
|
-
displayConsoleReport(
|
|
153
|
-
summary,
|
|
154
|
-
results,
|
|
155
|
-
elapsedTime,
|
|
156
|
-
finalOptions.maxResults
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
// Show tuning guidance after results
|
|
160
|
-
displayTuningGuidance(results, finalOptions);
|
|
161
|
-
} catch (error) {
|
|
162
|
-
handleCLIError(error, 'Analysis');
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
program.parse();
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Display tuning guidance to help users adjust thresholds
|
|
170
|
-
*/
|
|
171
|
-
function displayTuningGuidance(
|
|
172
|
-
results: Awaited<ReturnType<typeof analyzeContext>>,
|
|
173
|
-
options: any
|
|
174
|
-
): void {
|
|
175
|
-
const issueCount = results.filter((r) => r.severity !== 'info').length;
|
|
176
|
-
|
|
177
|
-
if (issueCount === 0) {
|
|
178
|
-
console.log(
|
|
179
|
-
chalk.green(
|
|
180
|
-
'\n✨ No optimization opportunities found! Your code is well-structured for AI context usage.\n'
|
|
181
|
-
)
|
|
182
|
-
);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
console.log(chalk.cyan('\n━'.repeat(60)));
|
|
187
|
-
console.log(chalk.bold.white(' TUNING GUIDANCE'));
|
|
188
|
-
console.log(chalk.cyan('━'.repeat(60) + '\n'));
|
|
189
|
-
|
|
190
|
-
if (issueCount < 5) {
|
|
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
|
-
);
|
|
224
|
-
} else if (issueCount > 20) {
|
|
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
|
-
);
|
|
258
|
-
} else {
|
|
259
|
-
console.log(
|
|
260
|
-
chalk.green(
|
|
261
|
-
'📊 Good balance of optimization opportunities (showing ' +
|
|
262
|
-
issueCount +
|
|
263
|
-
' areas)\n'
|
|
264
|
-
)
|
|
265
|
-
);
|
|
266
|
-
console.log(chalk.dim(' 💡 Tip: Adjust thresholds if needed:'));
|
|
267
|
-
console.log(
|
|
268
|
-
chalk.dim(
|
|
269
|
-
' aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n'
|
|
270
|
-
)
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
console.log(chalk.dim(' 📖 See README for detailed tuning guide\n'));
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Display formatted console report
|
|
279
|
-
*/
|
|
280
|
-
function displayConsoleReport(
|
|
281
|
-
summary: ReturnType<typeof generateSummary>,
|
|
282
|
-
results: Awaited<ReturnType<typeof analyzeContext>>,
|
|
283
|
-
elapsedTime: string,
|
|
284
|
-
maxResults: number = 10
|
|
285
|
-
) {
|
|
286
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
287
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
288
|
-
const divider = '━'.repeat(dividerWidth);
|
|
289
|
-
|
|
290
|
-
console.log(chalk.cyan(divider));
|
|
291
|
-
console.log(chalk.bold.white(' CONTEXT ANALYSIS SUMMARY'));
|
|
292
|
-
console.log(chalk.cyan(divider) + '\n');
|
|
293
|
-
|
|
294
|
-
// Overview
|
|
295
|
-
console.log(
|
|
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
|
-
)
|
|
302
|
-
);
|
|
303
|
-
console.log(
|
|
304
|
-
chalk.yellow(
|
|
305
|
-
`💰 Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
|
|
306
|
-
)
|
|
307
|
-
);
|
|
308
|
-
console.log(
|
|
309
|
-
chalk.white(`⏱ Analysis time: ${chalk.bold(elapsedTime + 's')}\n`)
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
// Issues summary
|
|
313
|
-
const totalIssues =
|
|
314
|
-
summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
315
|
-
if (totalIssues > 0) {
|
|
316
|
-
console.log(chalk.bold('⚠️ Issues Found:\n'));
|
|
317
|
-
if (summary.criticalIssues > 0) {
|
|
318
|
-
console.log(
|
|
319
|
-
chalk.red(` 🔴 Critical: ${chalk.bold(summary.criticalIssues)}`)
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
if (summary.majorIssues > 0) {
|
|
323
|
-
console.log(
|
|
324
|
-
chalk.yellow(` 🟡 Major: ${chalk.bold(summary.majorIssues)}`)
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
if (summary.minorIssues > 0) {
|
|
328
|
-
console.log(
|
|
329
|
-
chalk.blue(` 🔵 Minor: ${chalk.bold(summary.minorIssues)}`)
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
console.log(
|
|
333
|
-
chalk.green(
|
|
334
|
-
`\n 💡 Potential savings: ${chalk.bold(summary.totalPotentialSavings.toLocaleString())} tokens\n`
|
|
335
|
-
)
|
|
336
|
-
);
|
|
337
|
-
} else {
|
|
338
|
-
console.log(chalk.green('✅ No significant issues found!\n'));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Import depth analysis
|
|
342
|
-
if (summary.deepFiles.length > 0) {
|
|
343
|
-
console.log(chalk.bold('📏 Deep Import Chains:\n'));
|
|
344
|
-
console.log(
|
|
345
|
-
chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
|
|
346
|
-
);
|
|
347
|
-
console.log(chalk.gray(` Maximum depth: ${summary.maxImportDepth}\n`));
|
|
348
|
-
|
|
349
|
-
summary.deepFiles.slice(0, maxResults).forEach((item) => {
|
|
350
|
-
const fileName = item.file.split('/').slice(-2).join('/');
|
|
351
|
-
console.log(
|
|
352
|
-
` ${chalk.cyan('→')} ${chalk.white(fileName)} ${chalk.dim(`(depth: ${item.depth})`)}`
|
|
353
|
-
);
|
|
354
|
-
});
|
|
355
|
-
console.log();
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Fragmentation analysis
|
|
359
|
-
if (summary.fragmentedModules.length > 0) {
|
|
360
|
-
console.log(chalk.bold('🧩 Fragmented Modules:\n'));
|
|
361
|
-
console.log(
|
|
362
|
-
chalk.gray(
|
|
363
|
-
` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%\n`
|
|
364
|
-
)
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
summary.fragmentedModules.slice(0, maxResults).forEach((module) => {
|
|
368
|
-
console.log(
|
|
369
|
-
` ${chalk.yellow('●')} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`
|
|
370
|
-
);
|
|
371
|
-
console.log(
|
|
372
|
-
chalk.dim(
|
|
373
|
-
` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`
|
|
374
|
-
)
|
|
375
|
-
);
|
|
376
|
-
});
|
|
377
|
-
console.log();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Low cohesion files
|
|
381
|
-
if (summary.lowCohesionFiles.length > 0) {
|
|
382
|
-
console.log(chalk.bold('🔀 Low Cohesion Files:\n'));
|
|
383
|
-
console.log(
|
|
384
|
-
chalk.gray(
|
|
385
|
-
` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%\n`
|
|
386
|
-
)
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
summary.lowCohesionFiles.slice(0, maxResults).forEach((item) => {
|
|
390
|
-
const fileName = item.file.split('/').slice(-2).join('/');
|
|
391
|
-
const scorePercent = (item.score * 100).toFixed(0);
|
|
392
|
-
const color = item.score < 0.4 ? chalk.red : chalk.yellow;
|
|
393
|
-
console.log(
|
|
394
|
-
` ${color('○')} ${chalk.white(fileName)} ${chalk.dim(`(${scorePercent}% cohesion)`)}`
|
|
395
|
-
);
|
|
396
|
-
});
|
|
397
|
-
console.log();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Top expensive files
|
|
401
|
-
if (summary.topExpensiveFiles.length > 0) {
|
|
402
|
-
console.log(chalk.bold('💸 Most Expensive Files (Context Budget):\n'));
|
|
403
|
-
|
|
404
|
-
summary.topExpensiveFiles.slice(0, maxResults).forEach((item) => {
|
|
405
|
-
const fileName = item.file.split('/').slice(-2).join('/');
|
|
406
|
-
const severityColor =
|
|
407
|
-
item.severity === 'critical'
|
|
408
|
-
? chalk.red
|
|
409
|
-
: item.severity === 'major'
|
|
410
|
-
? chalk.yellow
|
|
411
|
-
: chalk.blue;
|
|
412
|
-
|
|
413
|
-
console.log(
|
|
414
|
-
` ${severityColor('●')} ${chalk.white(fileName)} ${chalk.dim(`- ${item.contextBudget.toLocaleString()} tokens`)}`
|
|
415
|
-
);
|
|
416
|
-
});
|
|
417
|
-
console.log();
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Recommendations
|
|
421
|
-
if (totalIssues > 0) {
|
|
422
|
-
console.log(chalk.bold('💡 Top Recommendations:\n'));
|
|
423
|
-
|
|
424
|
-
const topFiles = results
|
|
425
|
-
.filter((r) => r.severity === 'critical' || r.severity === 'major')
|
|
426
|
-
.slice(0, 3);
|
|
427
|
-
|
|
428
|
-
topFiles.forEach((result, index) => {
|
|
429
|
-
const fileName = result.file.split('/').slice(-2).join('/');
|
|
430
|
-
console.log(chalk.cyan(` ${index + 1}. ${fileName}`));
|
|
431
|
-
result.recommendations.slice(0, 2).forEach((rec) => {
|
|
432
|
-
console.log(chalk.dim(` • ${rec}`));
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
console.log();
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Footer
|
|
439
|
-
console.log(chalk.cyan(divider));
|
|
440
|
-
console.log(
|
|
441
|
-
chalk.dim(
|
|
442
|
-
'\n⭐ Like aiready? Star us on GitHub: https://github.com/caopengau/aiready-context-analyzer'
|
|
443
|
-
)
|
|
444
|
-
);
|
|
445
|
-
console.log(
|
|
446
|
-
chalk.dim(
|
|
447
|
-
'🐛 Found a bug? Report it: https://github.com/caopengau/aiready-context-analyzer/issues\n'
|
|
448
|
-
)
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Generate HTML report
|
|
454
|
-
*/
|
|
455
|
-
function generateHTMLReport(
|
|
456
|
-
summary: ReturnType<typeof generateSummary>,
|
|
457
|
-
results: Awaited<ReturnType<typeof analyzeContext>>
|
|
458
|
-
): string {
|
|
459
|
-
const totalIssues =
|
|
460
|
-
summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
461
|
-
|
|
462
|
-
// 'results' may be used in templates later; reference to avoid lint warnings
|
|
463
|
-
void results;
|
|
464
|
-
|
|
465
|
-
return `<!DOCTYPE html>
|
|
466
|
-
<html lang="en">
|
|
467
|
-
<head>
|
|
468
|
-
<meta charset="UTF-8">
|
|
469
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
470
|
-
<title>aiready Context Analysis Report</title>
|
|
471
|
-
<style>
|
|
472
|
-
body {
|
|
473
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
474
|
-
line-height: 1.6;
|
|
475
|
-
color: #333;
|
|
476
|
-
max-width: 1200px;
|
|
477
|
-
margin: 0 auto;
|
|
478
|
-
padding: 20px;
|
|
479
|
-
background-color: #f5f5f5;
|
|
480
|
-
}
|
|
481
|
-
h1, h2, h3 { color: #2c3e50; }
|
|
482
|
-
.header {
|
|
483
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
484
|
-
color: white;
|
|
485
|
-
padding: 30px;
|
|
486
|
-
border-radius: 8px;
|
|
487
|
-
margin-bottom: 30px;
|
|
488
|
-
}
|
|
489
|
-
.summary {
|
|
490
|
-
display: grid;
|
|
491
|
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
492
|
-
gap: 20px;
|
|
493
|
-
margin-bottom: 30px;
|
|
494
|
-
}
|
|
495
|
-
.card {
|
|
496
|
-
background: white;
|
|
497
|
-
padding: 20px;
|
|
498
|
-
border-radius: 8px;
|
|
499
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
500
|
-
}
|
|
501
|
-
.metric {
|
|
502
|
-
font-size: 2em;
|
|
503
|
-
font-weight: bold;
|
|
504
|
-
color: #667eea;
|
|
505
|
-
}
|
|
506
|
-
.label {
|
|
507
|
-
color: #666;
|
|
508
|
-
font-size: 0.9em;
|
|
509
|
-
margin-top: 5px;
|
|
510
|
-
}
|
|
511
|
-
.issue-critical { color: #e74c3c; }
|
|
512
|
-
.issue-major { color: #f39c12; }
|
|
513
|
-
.issue-minor { color: #3498db; }
|
|
514
|
-
table {
|
|
515
|
-
width: 100%;
|
|
516
|
-
border-collapse: collapse;
|
|
517
|
-
background: white;
|
|
518
|
-
border-radius: 8px;
|
|
519
|
-
overflow: hidden;
|
|
520
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
521
|
-
}
|
|
522
|
-
th, td {
|
|
523
|
-
padding: 12px;
|
|
524
|
-
text-align: left;
|
|
525
|
-
border-bottom: 1px solid #eee;
|
|
526
|
-
}
|
|
527
|
-
th {
|
|
528
|
-
background-color: #667eea;
|
|
529
|
-
color: white;
|
|
530
|
-
font-weight: 600;
|
|
531
|
-
}
|
|
532
|
-
tr:hover { background-color: #f8f9fa; }
|
|
533
|
-
.footer {
|
|
534
|
-
text-align: center;
|
|
535
|
-
margin-top: 40px;
|
|
536
|
-
padding: 20px;
|
|
537
|
-
color: #666;
|
|
538
|
-
font-size: 0.9em;
|
|
539
|
-
}
|
|
540
|
-
</style>
|
|
541
|
-
</head>
|
|
542
|
-
<body>
|
|
543
|
-
<div class="header">
|
|
544
|
-
<h1>🔍 AIReady Context Analysis Report</h1>
|
|
545
|
-
<p>Generated on ${new Date().toLocaleString()}</p>
|
|
546
|
-
</div>
|
|
547
|
-
|
|
548
|
-
<div class="summary">
|
|
549
|
-
<div class="card">
|
|
550
|
-
<div class="metric">${summary.totalFiles}</div>
|
|
551
|
-
<div class="label">Files Analyzed</div>
|
|
552
|
-
</div>
|
|
553
|
-
<div class="card">
|
|
554
|
-
<div class="metric">${summary.totalTokens.toLocaleString()}</div>
|
|
555
|
-
<div class="label">Total Tokens</div>
|
|
556
|
-
</div>
|
|
557
|
-
<div class="card">
|
|
558
|
-
<div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
|
|
559
|
-
<div class="label">Avg Context Budget</div>
|
|
560
|
-
</div>
|
|
561
|
-
<div class="card">
|
|
562
|
-
<div class="metric ${totalIssues > 0 ? 'issue-major' : ''}">${totalIssues}</div>
|
|
563
|
-
<div class="label">Total Issues</div>
|
|
564
|
-
</div>
|
|
565
|
-
</div>
|
|
566
|
-
|
|
567
|
-
${
|
|
568
|
-
totalIssues > 0
|
|
569
|
-
? `
|
|
570
|
-
<div class="card" style="margin-bottom: 30px;">
|
|
571
|
-
<h2>⚠️ Issues Summary</h2>
|
|
572
|
-
<p>
|
|
573
|
-
<span class="issue-critical">🔴 Critical: ${summary.criticalIssues}</span>
|
|
574
|
-
<span class="issue-major">🟡 Major: ${summary.majorIssues}</span>
|
|
575
|
-
<span class="issue-minor">🔵 Minor: ${summary.minorIssues}</span>
|
|
576
|
-
</p>
|
|
577
|
-
<p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
|
|
578
|
-
</div>
|
|
579
|
-
`
|
|
580
|
-
: ''
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
${
|
|
584
|
-
summary.fragmentedModules.length > 0
|
|
585
|
-
? `
|
|
586
|
-
<div class="card" style="margin-bottom: 30px;">
|
|
587
|
-
<h2>🧩 Fragmented Modules</h2>
|
|
588
|
-
<table>
|
|
589
|
-
<thead>
|
|
590
|
-
<tr>
|
|
591
|
-
<th>Domain</th>
|
|
592
|
-
<th>Files</th>
|
|
593
|
-
<th>Fragmentation</th>
|
|
594
|
-
<th>Token Cost</th>
|
|
595
|
-
</tr>
|
|
596
|
-
</thead>
|
|
597
|
-
<tbody>
|
|
598
|
-
${summary.fragmentedModules
|
|
599
|
-
.map(
|
|
600
|
-
(m) => `
|
|
601
|
-
<tr>
|
|
602
|
-
<td>${m.domain}</td>
|
|
603
|
-
<td>${m.files.length}</td>
|
|
604
|
-
<td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
|
|
605
|
-
<td>${m.totalTokens.toLocaleString()}</td>
|
|
606
|
-
</tr>
|
|
607
|
-
`
|
|
608
|
-
)
|
|
609
|
-
.join('')}
|
|
610
|
-
</tbody>
|
|
611
|
-
</table>
|
|
612
|
-
</div>
|
|
613
|
-
`
|
|
614
|
-
: ''
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
${
|
|
618
|
-
summary.topExpensiveFiles.length > 0
|
|
619
|
-
? `
|
|
620
|
-
<div class="card" style="margin-bottom: 30px;">
|
|
621
|
-
<h2>💸 Most Expensive Files</h2>
|
|
622
|
-
<table>
|
|
623
|
-
<thead>
|
|
624
|
-
<tr>
|
|
625
|
-
<th>File</th>
|
|
626
|
-
<th>Context Budget</th>
|
|
627
|
-
<th>Severity</th>
|
|
628
|
-
</tr>
|
|
629
|
-
</thead>
|
|
630
|
-
<tbody>
|
|
631
|
-
${summary.topExpensiveFiles
|
|
632
|
-
.map(
|
|
633
|
-
(f) => `
|
|
634
|
-
<tr>
|
|
635
|
-
<td>${f.file}</td>
|
|
636
|
-
<td>${f.contextBudget.toLocaleString()} tokens</td>
|
|
637
|
-
<td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
|
|
638
|
-
</tr>
|
|
639
|
-
`
|
|
640
|
-
)
|
|
641
|
-
.join('')}
|
|
642
|
-
</tbody>
|
|
643
|
-
</table>
|
|
644
|
-
</div>
|
|
645
|
-
`
|
|
646
|
-
: ''
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
<div class="footer">
|
|
650
|
-
<p>Generated by <strong>@aiready/context-analyzer</strong></p>
|
|
651
|
-
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-context-analyzer">Star us on GitHub</a></p>
|
|
652
|
-
<p>Found a bug? <a href="https://github.com/caopengau/aiready-context-analyzer/issues">Report it here</a></p>
|
|
653
|
-
</div>
|
|
654
|
-
</body>
|
|
655
|
-
</html>`;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Interactive setup: detect common frameworks and suggest excludes & focus areas
|
|
660
|
-
*/
|
|
661
|
-
async function runInteractiveSetup(
|
|
662
|
-
directory: string,
|
|
663
|
-
current: any
|
|
664
|
-
): Promise<any> {
|
|
665
|
-
console.log(chalk.yellow('🧭 Interactive mode: let’s tailor the analysis.'));
|
|
666
|
-
|
|
667
|
-
const pkgPath = join(directory, 'package.json');
|
|
668
|
-
let deps: Record<string, string> = {};
|
|
669
|
-
if (existsSync(pkgPath)) {
|
|
670
|
-
try {
|
|
671
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
672
|
-
deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
673
|
-
} catch (e) {
|
|
674
|
-
void e;
|
|
675
|
-
// Ignore parse errors, use empty deps
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const hasNextJs = existsSync(join(directory, '.next')) || !!deps['next'];
|
|
680
|
-
const hasCDK =
|
|
681
|
-
existsSync(join(directory, 'cdk.out')) ||
|
|
682
|
-
!!deps['aws-cdk-lib'] ||
|
|
683
|
-
Object.keys(deps).some((d) => d.startsWith('@aws-cdk/'));
|
|
684
|
-
|
|
685
|
-
const recommendedExcludes = new Set<string>(current.exclude || []);
|
|
686
|
-
if (
|
|
687
|
-
hasNextJs &&
|
|
688
|
-
!Array.from(recommendedExcludes).some((p) => p.includes('.next'))
|
|
689
|
-
) {
|
|
690
|
-
recommendedExcludes.add('**/.next/**');
|
|
691
|
-
}
|
|
692
|
-
if (
|
|
693
|
-
hasCDK &&
|
|
694
|
-
!Array.from(recommendedExcludes).some((p) => p.includes('cdk.out'))
|
|
695
|
-
) {
|
|
696
|
-
recommendedExcludes.add('**/cdk.out/**');
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const { applyExcludes } = await prompts({
|
|
700
|
-
type: 'toggle',
|
|
701
|
-
name: 'applyExcludes',
|
|
702
|
-
message: `Detected ${hasNextJs ? 'Next.js ' : ''}${hasCDK ? 'AWS CDK ' : ''}frameworks. Apply recommended excludes?`,
|
|
703
|
-
initial: true,
|
|
704
|
-
active: 'yes',
|
|
705
|
-
inactive: 'no',
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
const nextOptions = { ...current };
|
|
709
|
-
if (applyExcludes) {
|
|
710
|
-
nextOptions.exclude = Array.from(recommendedExcludes);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const { focusArea } = await prompts({
|
|
714
|
-
type: 'select',
|
|
715
|
-
name: 'focusArea',
|
|
716
|
-
message: 'Which areas to focus?',
|
|
717
|
-
choices: [
|
|
718
|
-
{ title: 'Frontend (web app)', value: 'frontend' },
|
|
719
|
-
{ title: 'Backend (API/infra)', value: 'backend' },
|
|
720
|
-
{ title: 'Both', value: 'both' },
|
|
721
|
-
],
|
|
722
|
-
initial: 2,
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
if (focusArea === 'frontend') {
|
|
726
|
-
nextOptions.include = ['**/*.{ts,tsx,js,jsx}'];
|
|
727
|
-
nextOptions.exclude = Array.from(
|
|
728
|
-
new Set([
|
|
729
|
-
...(nextOptions.exclude || []),
|
|
730
|
-
'**/cdk.out/**',
|
|
731
|
-
'**/infra/**',
|
|
732
|
-
'**/server/**',
|
|
733
|
-
'**/backend/**',
|
|
734
|
-
])
|
|
735
|
-
);
|
|
736
|
-
} else if (focusArea === 'backend') {
|
|
737
|
-
nextOptions.include = [
|
|
738
|
-
'**/api/**',
|
|
739
|
-
'**/server/**',
|
|
740
|
-
'**/backend/**',
|
|
741
|
-
'**/infra/**',
|
|
742
|
-
'**/*.{ts,js,py,java}',
|
|
743
|
-
];
|
|
744
|
-
}
|
|
65
|
+
.action(contextActionHandler);
|
|
745
66
|
|
|
746
|
-
|
|
747
|
-
return nextOptions;
|
|
748
|
-
}
|
|
67
|
+
program.parse(process.argv);
|