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