@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/src/cli.ts ADDED
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { analyzeContext, generateSummary } from './index';
5
+ import chalk from 'chalk';
6
+ import { writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('aiready-context')
13
+ .description('Analyze AI context window cost and code structure')
14
+ .version('0.1.0')
15
+ .argument('<directory>', 'Directory to analyze')
16
+ .option('--max-depth <number>', 'Maximum acceptable import depth', '5')
17
+ .option(
18
+ '--max-context <number>',
19
+ 'Maximum acceptable context budget (tokens)',
20
+ '10000'
21
+ )
22
+ .option('--min-cohesion <number>', 'Minimum acceptable cohesion score (0-1)', '0.6')
23
+ .option(
24
+ '--max-fragmentation <number>',
25
+ 'Maximum acceptable fragmentation (0-1)',
26
+ '0.5'
27
+ )
28
+ .option(
29
+ '--focus <type>',
30
+ 'Analysis focus: fragmentation, cohesion, depth, all',
31
+ 'all'
32
+ )
33
+ .option('--include-node-modules', 'Include node_modules in analysis', false)
34
+ .option('--include <patterns>', 'File patterns to include (comma-separated)')
35
+ .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
36
+ .option(
37
+ '-o, --output <format>',
38
+ 'Output format: console, json, html',
39
+ 'console'
40
+ )
41
+ .option('--output-file <path>', 'Output file path (for json/html)')
42
+ .action(async (directory, options) => {
43
+ console.log(chalk.blue('šŸ” Analyzing context window costs...\n'));
44
+
45
+ const startTime = Date.now();
46
+
47
+ try {
48
+ const results = await analyzeContext({
49
+ rootDir: directory,
50
+ maxDepth: parseInt(options.maxDepth),
51
+ maxContextBudget: parseInt(options.maxContext),
52
+ minCohesion: parseFloat(options.minCohesion),
53
+ maxFragmentation: parseFloat(options.maxFragmentation),
54
+ focus: options.focus as any,
55
+ includeNodeModules: options.includeNodeModules,
56
+ include: options.include?.split(','),
57
+ exclude: options.exclude?.split(','),
58
+ });
59
+
60
+ const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
61
+ const summary = generateSummary(results);
62
+
63
+ if (options.output === 'json') {
64
+ const jsonOutput = {
65
+ summary,
66
+ results,
67
+ timestamp: new Date().toISOString(),
68
+ analysisTime: elapsedTime,
69
+ };
70
+
71
+ if (options.outputFile) {
72
+ writeFileSync(
73
+ options.outputFile,
74
+ JSON.stringify(jsonOutput, null, 2)
75
+ );
76
+ console.log(
77
+ chalk.green(`\nāœ“ JSON report saved to ${options.outputFile}`)
78
+ );
79
+ } else {
80
+ console.log(JSON.stringify(jsonOutput, null, 2));
81
+ }
82
+ return;
83
+ }
84
+
85
+ if (options.output === 'html') {
86
+ const html = generateHTMLReport(summary, results);
87
+ const outputPath =
88
+ options.outputFile || join(process.cwd(), 'context-report.html');
89
+ writeFileSync(outputPath, html);
90
+ console.log(chalk.green(`\nāœ“ HTML report saved to ${outputPath}`));
91
+ return;
92
+ }
93
+
94
+ // Console output
95
+ displayConsoleReport(summary, results, elapsedTime);
96
+ } catch (error) {
97
+ console.error(chalk.red('\nāŒ Analysis failed:'));
98
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
99
+ process.exit(1);
100
+ }
101
+ });
102
+
103
+ program.parse();
104
+
105
+ /**
106
+ * Display formatted console report
107
+ */
108
+ function displayConsoleReport(
109
+ summary: ReturnType<typeof generateSummary>,
110
+ results: Awaited<ReturnType<typeof analyzeContext>>,
111
+ elapsedTime: string
112
+ ) {
113
+ const terminalWidth = process.stdout.columns || 80;
114
+ const dividerWidth = Math.min(60, terminalWidth - 2);
115
+ const divider = '━'.repeat(dividerWidth);
116
+
117
+ console.log(chalk.cyan(divider));
118
+ console.log(chalk.bold.white(' CONTEXT ANALYSIS SUMMARY'));
119
+ console.log(chalk.cyan(divider) + '\n');
120
+
121
+ // Overview
122
+ console.log(chalk.white(`šŸ“ Files analyzed: ${chalk.bold(summary.totalFiles)}`));
123
+ console.log(
124
+ chalk.white(`šŸ“Š Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`)
125
+ );
126
+ console.log(
127
+ chalk.yellow(
128
+ `šŸ’° Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
129
+ )
130
+ );
131
+ console.log(
132
+ chalk.white(`ā± Analysis time: ${chalk.bold(elapsedTime + 's')}\n`)
133
+ );
134
+
135
+ // Issues summary
136
+ const totalIssues =
137
+ summary.criticalIssues + summary.majorIssues + summary.minorIssues;
138
+ if (totalIssues > 0) {
139
+ console.log(chalk.bold('āš ļø Issues Found:\n'));
140
+ if (summary.criticalIssues > 0) {
141
+ console.log(
142
+ chalk.red(` šŸ”“ Critical: ${chalk.bold(summary.criticalIssues)}`)
143
+ );
144
+ }
145
+ if (summary.majorIssues > 0) {
146
+ console.log(
147
+ chalk.yellow(` 🟔 Major: ${chalk.bold(summary.majorIssues)}`)
148
+ );
149
+ }
150
+ if (summary.minorIssues > 0) {
151
+ console.log(chalk.blue(` šŸ”µ Minor: ${chalk.bold(summary.minorIssues)}`));
152
+ }
153
+ console.log(
154
+ chalk.green(
155
+ `\n šŸ’” Potential savings: ${chalk.bold(summary.totalPotentialSavings.toLocaleString())} tokens\n`
156
+ )
157
+ );
158
+ } else {
159
+ console.log(chalk.green('āœ… No significant issues found!\n'));
160
+ }
161
+
162
+ // Import depth analysis
163
+ if (summary.deepFiles.length > 0) {
164
+ console.log(chalk.bold('šŸ“ Deep Import Chains:\n'));
165
+ console.log(
166
+ chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
167
+ );
168
+ console.log(chalk.gray(` Maximum depth: ${summary.maxImportDepth}\n`));
169
+
170
+ summary.deepFiles.slice(0, 5).forEach((item) => {
171
+ const fileName = item.file.split('/').slice(-2).join('/');
172
+ console.log(
173
+ ` ${chalk.cyan('→')} ${chalk.white(fileName)} ${chalk.dim(`(depth: ${item.depth})`)}`
174
+ );
175
+ });
176
+ console.log();
177
+ }
178
+
179
+ // Fragmentation analysis
180
+ if (summary.fragmentedModules.length > 0) {
181
+ console.log(chalk.bold('🧩 Fragmented Modules:\n'));
182
+ console.log(
183
+ chalk.gray(
184
+ ` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%\n`
185
+ )
186
+ );
187
+
188
+ summary.fragmentedModules.slice(0, 5).forEach((module) => {
189
+ console.log(
190
+ ` ${chalk.yellow('ā—')} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`
191
+ );
192
+ console.log(
193
+ chalk.dim(
194
+ ` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`
195
+ )
196
+ );
197
+ });
198
+ console.log();
199
+ }
200
+
201
+ // Low cohesion files
202
+ if (summary.lowCohesionFiles.length > 0) {
203
+ console.log(chalk.bold('šŸ”€ Low Cohesion Files:\n'));
204
+ console.log(
205
+ chalk.gray(
206
+ ` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%\n`
207
+ )
208
+ );
209
+
210
+ summary.lowCohesionFiles.slice(0, 5).forEach((item) => {
211
+ const fileName = item.file.split('/').slice(-2).join('/');
212
+ const scorePercent = (item.score * 100).toFixed(0);
213
+ const color = item.score < 0.4 ? chalk.red : chalk.yellow;
214
+ console.log(
215
+ ` ${color('ā—‹')} ${chalk.white(fileName)} ${chalk.dim(`(${scorePercent}% cohesion)`)}`
216
+ );
217
+ });
218
+ console.log();
219
+ }
220
+
221
+ // Top expensive files
222
+ if (summary.topExpensiveFiles.length > 0) {
223
+ console.log(chalk.bold('šŸ’ø Most Expensive Files (Context Budget):\n'));
224
+
225
+ summary.topExpensiveFiles.slice(0, 5).forEach((item) => {
226
+ const fileName = item.file.split('/').slice(-2).join('/');
227
+ const severityColor =
228
+ item.severity === 'critical'
229
+ ? chalk.red
230
+ : item.severity === 'major'
231
+ ? chalk.yellow
232
+ : chalk.blue;
233
+
234
+ console.log(
235
+ ` ${severityColor('ā—')} ${chalk.white(fileName)} ${chalk.dim(`- ${item.contextBudget.toLocaleString()} tokens`)}`
236
+ );
237
+ });
238
+ console.log();
239
+ }
240
+
241
+ // Recommendations
242
+ if (totalIssues > 0) {
243
+ console.log(chalk.bold('šŸ’” Top Recommendations:\n'));
244
+
245
+ const topFiles = results
246
+ .filter((r) => r.severity === 'critical' || r.severity === 'major')
247
+ .slice(0, 3);
248
+
249
+ topFiles.forEach((result, index) => {
250
+ const fileName = result.file.split('/').slice(-2).join('/');
251
+ console.log(chalk.cyan(` ${index + 1}. ${fileName}`));
252
+ result.recommendations.slice(0, 2).forEach((rec) => {
253
+ console.log(chalk.dim(` • ${rec}`));
254
+ });
255
+ });
256
+ console.log();
257
+ }
258
+
259
+ // Footer
260
+ console.log(chalk.cyan(divider));
261
+ console.log(
262
+ chalk.dim(
263
+ '\nšŸ’Ž Want historical trends and refactoring plans? → aiready.dev/pro'
264
+ )
265
+ );
266
+ console.log(
267
+ chalk.dim('šŸ’¼ Enterprise: CI/CD integration → aiready.dev/demo\n')
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Generate HTML report
273
+ */
274
+ function generateHTMLReport(
275
+ summary: ReturnType<typeof generateSummary>,
276
+ results: Awaited<ReturnType<typeof analyzeContext>>
277
+ ): string {
278
+ const totalIssues =
279
+ summary.criticalIssues + summary.majorIssues + summary.minorIssues;
280
+
281
+ return `<!DOCTYPE html>
282
+ <html lang="en">
283
+ <head>
284
+ <meta charset="UTF-8">
285
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
286
+ <title>AIReady Context Analysis Report</title>
287
+ <style>
288
+ body {
289
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
290
+ line-height: 1.6;
291
+ color: #333;
292
+ max-width: 1200px;
293
+ margin: 0 auto;
294
+ padding: 20px;
295
+ background-color: #f5f5f5;
296
+ }
297
+ h1, h2, h3 { color: #2c3e50; }
298
+ .header {
299
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
300
+ color: white;
301
+ padding: 30px;
302
+ border-radius: 8px;
303
+ margin-bottom: 30px;
304
+ }
305
+ .summary {
306
+ display: grid;
307
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
308
+ gap: 20px;
309
+ margin-bottom: 30px;
310
+ }
311
+ .card {
312
+ background: white;
313
+ padding: 20px;
314
+ border-radius: 8px;
315
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
316
+ }
317
+ .metric {
318
+ font-size: 2em;
319
+ font-weight: bold;
320
+ color: #667eea;
321
+ }
322
+ .label {
323
+ color: #666;
324
+ font-size: 0.9em;
325
+ margin-top: 5px;
326
+ }
327
+ .issue-critical { color: #e74c3c; }
328
+ .issue-major { color: #f39c12; }
329
+ .issue-minor { color: #3498db; }
330
+ table {
331
+ width: 100%;
332
+ border-collapse: collapse;
333
+ background: white;
334
+ border-radius: 8px;
335
+ overflow: hidden;
336
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
337
+ }
338
+ th, td {
339
+ padding: 12px;
340
+ text-align: left;
341
+ border-bottom: 1px solid #eee;
342
+ }
343
+ th {
344
+ background-color: #667eea;
345
+ color: white;
346
+ font-weight: 600;
347
+ }
348
+ tr:hover { background-color: #f8f9fa; }
349
+ .footer {
350
+ text-align: center;
351
+ margin-top: 40px;
352
+ padding: 20px;
353
+ color: #666;
354
+ font-size: 0.9em;
355
+ }
356
+ </style>
357
+ </head>
358
+ <body>
359
+ <div class="header">
360
+ <h1>šŸ” AIReady Context Analysis Report</h1>
361
+ <p>Generated on ${new Date().toLocaleString()}</p>
362
+ </div>
363
+
364
+ <div class="summary">
365
+ <div class="card">
366
+ <div class="metric">${summary.totalFiles}</div>
367
+ <div class="label">Files Analyzed</div>
368
+ </div>
369
+ <div class="card">
370
+ <div class="metric">${summary.totalTokens.toLocaleString()}</div>
371
+ <div class="label">Total Tokens</div>
372
+ </div>
373
+ <div class="card">
374
+ <div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
375
+ <div class="label">Avg Context Budget</div>
376
+ </div>
377
+ <div class="card">
378
+ <div class="metric ${totalIssues > 0 ? 'issue-major' : ''}">${totalIssues}</div>
379
+ <div class="label">Total Issues</div>
380
+ </div>
381
+ </div>
382
+
383
+ ${totalIssues > 0 ? `
384
+ <div class="card" style="margin-bottom: 30px;">
385
+ <h2>āš ļø Issues Summary</h2>
386
+ <p>
387
+ <span class="issue-critical">šŸ”“ Critical: ${summary.criticalIssues}</span> &nbsp;
388
+ <span class="issue-major">🟔 Major: ${summary.majorIssues}</span> &nbsp;
389
+ <span class="issue-minor">šŸ”µ Minor: ${summary.minorIssues}</span>
390
+ </p>
391
+ <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
392
+ </div>
393
+ ` : ''}
394
+
395
+ ${summary.fragmentedModules.length > 0 ? `
396
+ <div class="card" style="margin-bottom: 30px;">
397
+ <h2>🧩 Fragmented Modules</h2>
398
+ <table>
399
+ <thead>
400
+ <tr>
401
+ <th>Domain</th>
402
+ <th>Files</th>
403
+ <th>Fragmentation</th>
404
+ <th>Token Cost</th>
405
+ </tr>
406
+ </thead>
407
+ <tbody>
408
+ ${summary.fragmentedModules.map(m => `
409
+ <tr>
410
+ <td>${m.domain}</td>
411
+ <td>${m.files.length}</td>
412
+ <td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
413
+ <td>${m.totalTokens.toLocaleString()}</td>
414
+ </tr>
415
+ `).join('')}
416
+ </tbody>
417
+ </table>
418
+ </div>
419
+ ` : ''}
420
+
421
+ ${summary.topExpensiveFiles.length > 0 ? `
422
+ <div class="card" style="margin-bottom: 30px;">
423
+ <h2>šŸ’ø Most Expensive Files</h2>
424
+ <table>
425
+ <thead>
426
+ <tr>
427
+ <th>File</th>
428
+ <th>Context Budget</th>
429
+ <th>Severity</th>
430
+ </tr>
431
+ </thead>
432
+ <tbody>
433
+ ${summary.topExpensiveFiles.map(f => `
434
+ <tr>
435
+ <td>${f.file}</td>
436
+ <td>${f.contextBudget.toLocaleString()} tokens</td>
437
+ <td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
438
+ </tr>
439
+ `).join('')}
440
+ </tbody>
441
+ </table>
442
+ </div>
443
+ ` : ''}
444
+
445
+ <div class="footer">
446
+ <p>Generated by <strong>@aiready/context-analyzer</strong></p>
447
+ <p>Want historical trends? Visit <a href="https://aiready.dev">aiready.dev</a></p>
448
+ </div>
449
+ </body>
450
+ </html>`;
451
+ }