@aiready/context-analyzer 0.21.1 → 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.
Files changed (153) hide show
  1. package/.aiready/aiready-report-20260314-222254.json +39216 -0
  2. package/.aiready/aiready-report-20260314-223947.json +3413 -0
  3. package/.aiready/aiready-report-20260314-224112.json +3413 -0
  4. package/.aiready/aiready-report-20260314-224302.json +2973 -0
  5. package/.aiready/aiready-report-20260314-224939.json +3092 -0
  6. package/.aiready/aiready-report-20260314-225154.json +3092 -0
  7. package/.turbo/turbo-build.log +13 -12
  8. package/.turbo/turbo-test.log +32 -341
  9. package/dist/__tests__/analyzer.test.js +55 -14
  10. package/dist/__tests__/analyzer.test.js.map +1 -1
  11. package/dist/__tests__/cluster-detector.test.d.ts +2 -0
  12. package/dist/__tests__/cluster-detector.test.d.ts.map +1 -0
  13. package/dist/__tests__/cluster-detector.test.js +121 -0
  14. package/dist/__tests__/cluster-detector.test.js.map +1 -0
  15. package/dist/__tests__/contract.test.d.ts +2 -0
  16. package/dist/__tests__/contract.test.d.ts.map +1 -0
  17. package/dist/__tests__/contract.test.js +59 -0
  18. package/dist/__tests__/contract.test.js.map +1 -0
  19. package/dist/__tests__/enhanced-cohesion.test.js +12 -2
  20. package/dist/__tests__/enhanced-cohesion.test.js.map +1 -1
  21. package/dist/__tests__/file-classification.test.d.ts +2 -0
  22. package/dist/__tests__/file-classification.test.d.ts.map +1 -0
  23. package/dist/__tests__/file-classification.test.js +749 -0
  24. package/dist/__tests__/file-classification.test.js.map +1 -0
  25. package/dist/__tests__/fragmentation-advanced.test.js +2 -8
  26. package/dist/__tests__/fragmentation-advanced.test.js.map +1 -1
  27. package/dist/__tests__/fragmentation-coupling.test.js +2 -2
  28. package/dist/__tests__/fragmentation-coupling.test.js.map +1 -1
  29. package/dist/__tests__/fragmentation-log.test.js +3 -7
  30. package/dist/__tests__/fragmentation-log.test.js.map +1 -1
  31. package/dist/__tests__/provider.test.d.ts +2 -0
  32. package/dist/__tests__/provider.test.d.ts.map +1 -0
  33. package/dist/__tests__/provider.test.js +72 -0
  34. package/dist/__tests__/provider.test.js.map +1 -0
  35. package/dist/__tests__/remediation.test.d.ts +2 -0
  36. package/dist/__tests__/remediation.test.d.ts.map +1 -0
  37. package/dist/__tests__/remediation.test.js +61 -0
  38. package/dist/__tests__/remediation.test.js.map +1 -0
  39. package/dist/__tests__/scoring.test.js +196 -16
  40. package/dist/__tests__/scoring.test.js.map +1 -1
  41. package/dist/__tests__/structural-cohesion.test.js +8 -2
  42. package/dist/__tests__/structural-cohesion.test.js.map +1 -1
  43. package/dist/analyzer.d.ts +31 -94
  44. package/dist/analyzer.d.ts.map +1 -1
  45. package/dist/analyzer.js +260 -678
  46. package/dist/analyzer.js.map +1 -1
  47. package/dist/analyzers/python-context.d.ts.map +1 -1
  48. package/dist/analyzers/python-context.js +10 -8
  49. package/dist/analyzers/python-context.js.map +1 -1
  50. package/dist/ast-utils.d.ts +16 -0
  51. package/dist/ast-utils.d.ts.map +1 -0
  52. package/dist/ast-utils.js +81 -0
  53. package/dist/ast-utils.js.map +1 -0
  54. package/dist/chunk-64U3PNO3.mjs +94 -0
  55. package/dist/chunk-CDIVYADN.mjs +2110 -0
  56. package/dist/chunk-D3SIHB2V.mjs +2118 -0
  57. package/dist/chunk-FNPSK3CG.mjs +1760 -0
  58. package/dist/chunk-GXTGOLZT.mjs +92 -0
  59. package/dist/chunk-LERPI33Y.mjs +2060 -0
  60. package/dist/chunk-MZP3G7TF.mjs +2118 -0
  61. package/dist/chunk-NOHK5DLU.mjs +2173 -0
  62. package/dist/chunk-ORLC5Y4J.mjs +1787 -0
  63. package/dist/chunk-OTCQL7DY.mjs +2045 -0
  64. package/dist/chunk-OUYSZZ7X.mjs +1846 -0
  65. package/dist/chunk-SFK6XTJE.mjs +2110 -0
  66. package/dist/chunk-U5R2FTCR.mjs +1803 -0
  67. package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
  68. package/dist/chunk-W2KNBN6W.mjs +1849 -0
  69. package/dist/chunk-WKOZOHOU.mjs +2060 -0
  70. package/dist/chunk-XIXAWCMS.mjs +1760 -0
  71. package/dist/classifier.d.ts +114 -0
  72. package/dist/classifier.d.ts.map +1 -0
  73. package/dist/classifier.js +439 -0
  74. package/dist/classifier.js.map +1 -0
  75. package/dist/cli.js +591 -1057
  76. package/dist/cli.js.map +1 -1
  77. package/dist/cli.mjs +63 -533
  78. package/dist/cluster-detector.d.ts +8 -0
  79. package/dist/cluster-detector.d.ts.map +1 -0
  80. package/dist/cluster-detector.js +70 -0
  81. package/dist/cluster-detector.js.map +1 -0
  82. package/dist/defaults.d.ts +7 -0
  83. package/dist/defaults.d.ts.map +1 -0
  84. package/dist/defaults.js +54 -0
  85. package/dist/defaults.js.map +1 -0
  86. package/dist/graph-builder.d.ts +33 -0
  87. package/dist/graph-builder.d.ts.map +1 -0
  88. package/dist/graph-builder.js +225 -0
  89. package/dist/graph-builder.js.map +1 -0
  90. package/dist/index.d.mts +24 -31
  91. package/dist/index.d.ts +24 -31
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +822 -588
  94. package/dist/index.js.map +1 -1
  95. package/dist/index.mjs +265 -8
  96. package/dist/metrics.d.ts +34 -0
  97. package/dist/metrics.d.ts.map +1 -0
  98. package/dist/metrics.js +170 -0
  99. package/dist/metrics.js.map +1 -0
  100. package/dist/provider.d.ts +6 -0
  101. package/dist/provider.d.ts.map +1 -0
  102. package/dist/provider.js +48 -0
  103. package/dist/provider.js.map +1 -0
  104. package/dist/python-context-3GZKN3LR.mjs +162 -0
  105. package/dist/python-context-O2EN3M6Z.mjs +162 -0
  106. package/dist/remediation.d.ts +25 -0
  107. package/dist/remediation.d.ts.map +1 -0
  108. package/dist/remediation.js +98 -0
  109. package/dist/remediation.js.map +1 -0
  110. package/dist/scoring.d.ts +3 -7
  111. package/dist/scoring.d.ts.map +1 -1
  112. package/dist/scoring.js +57 -48
  113. package/dist/scoring.js.map +1 -1
  114. package/dist/semantic-analysis.d.ts +12 -23
  115. package/dist/semantic-analysis.d.ts.map +1 -1
  116. package/dist/semantic-analysis.js +172 -110
  117. package/dist/semantic-analysis.js.map +1 -1
  118. package/dist/summary.d.ts +6 -0
  119. package/dist/summary.d.ts.map +1 -0
  120. package/dist/summary.js +92 -0
  121. package/dist/summary.js.map +1 -0
  122. package/dist/types.d.ts +9 -2
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/utils/output-formatter.d.ts +14 -0
  125. package/dist/utils/output-formatter.d.ts.map +1 -0
  126. package/dist/utils/output-formatter.js +338 -0
  127. package/dist/utils/output-formatter.js.map +1 -0
  128. package/package.json +2 -2
  129. package/src/__tests__/analyzer.test.ts +1 -1
  130. package/src/__tests__/auto-detection.test.ts +1 -1
  131. package/src/__tests__/contract.test.ts +1 -1
  132. package/src/__tests__/enhanced-cohesion.test.ts +1 -1
  133. package/src/__tests__/file-classification.test.ts +1 -1
  134. package/src/__tests__/fragmentation-advanced.test.ts +1 -1
  135. package/src/__tests__/fragmentation-coupling.test.ts +1 -1
  136. package/src/__tests__/fragmentation-log.test.ts +1 -1
  137. package/src/__tests__/provider.test.ts +1 -1
  138. package/src/__tests__/scoring.test.ts +217 -9
  139. package/src/__tests__/structural-cohesion.test.ts +1 -1
  140. package/src/analyzer.ts +96 -309
  141. package/src/analyzers/python-context.ts +7 -76
  142. package/src/cli-action.ts +103 -0
  143. package/src/cli.ts +12 -693
  144. package/src/cluster-detector.ts +1 -1
  145. package/src/graph-builder.ts +9 -85
  146. package/src/index.ts +6 -0
  147. package/src/issue-analyzer.ts +143 -0
  148. package/src/scoring.ts +40 -20
  149. package/src/semantic-analysis.ts +1 -14
  150. package/src/summary.ts +62 -106
  151. package/src/utils/dependency-graph-utils.ts +126 -0
  152. package/src/utils/output-formatter.ts +411 -0
  153. package/src/utils/string-utils.ts +17 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Shared dependency graph utilities used by context analyzers.
3
+ */
4
+
5
+ export function calculateImportDepthFromEdges(
6
+ file: string,
7
+ edges: Map<string, Set<string>>,
8
+ visited = new Set<string>(),
9
+ depth = 0
10
+ ): number {
11
+ if (visited.has(file)) return depth;
12
+
13
+ const dependencies = edges.get(file);
14
+ if (!dependencies || dependencies.size === 0) return depth;
15
+
16
+ const nextVisited = new Set(visited);
17
+ nextVisited.add(file);
18
+
19
+ let maxDepth = depth;
20
+ for (const dep of dependencies) {
21
+ maxDepth = Math.max(
22
+ maxDepth,
23
+ calculateImportDepthFromEdges(dep, edges, nextVisited, depth + 1)
24
+ );
25
+ }
26
+
27
+ return maxDepth;
28
+ }
29
+
30
+ export function getTransitiveDependenciesFromEdges(
31
+ file: string,
32
+ edges: Map<string, Set<string>>,
33
+ visited = new Set<string>()
34
+ ): string[] {
35
+ if (visited.has(file)) return [];
36
+
37
+ const nextVisited = new Set(visited);
38
+ nextVisited.add(file);
39
+
40
+ const dependencies = edges.get(file);
41
+ if (!dependencies || dependencies.size === 0) return [];
42
+
43
+ const allDeps: string[] = [];
44
+ for (const dep of dependencies) {
45
+ allDeps.push(dep);
46
+ allDeps.push(
47
+ ...getTransitiveDependenciesFromEdges(dep, edges, nextVisited)
48
+ );
49
+ }
50
+
51
+ return [...new Set(allDeps)];
52
+ }
53
+
54
+ export function detectGraphCycles(edges: Map<string, Set<string>>): string[][] {
55
+ const cycles: string[][] = [];
56
+ const visited = new Set<string>();
57
+ const recursionStack = new Set<string>();
58
+
59
+ function dfs(file: string, path: string[]): void {
60
+ if (recursionStack.has(file)) {
61
+ const cycleStart = path.indexOf(file);
62
+ if (cycleStart !== -1) {
63
+ cycles.push([...path.slice(cycleStart), file]);
64
+ }
65
+ return;
66
+ }
67
+
68
+ if (visited.has(file)) return;
69
+
70
+ visited.add(file);
71
+ recursionStack.add(file);
72
+
73
+ const dependencies = edges.get(file);
74
+ if (dependencies) {
75
+ for (const dep of dependencies) {
76
+ dfs(dep, [...path, file]);
77
+ }
78
+ }
79
+
80
+ recursionStack.delete(file);
81
+ }
82
+
83
+ for (const file of edges.keys()) {
84
+ if (!visited.has(file)) {
85
+ dfs(file, []);
86
+ }
87
+ }
88
+
89
+ return cycles;
90
+ }
91
+
92
+ export function detectGraphCyclesFromFile(
93
+ file: string,
94
+ edges: Map<string, Set<string>>
95
+ ): string[][] {
96
+ const cycles: string[][] = [];
97
+ const visited = new Set<string>();
98
+ const recursionStack = new Set<string>();
99
+
100
+ function dfs(current: string, path: string[]): void {
101
+ if (recursionStack.has(current)) {
102
+ const cycleStart = path.indexOf(current);
103
+ if (cycleStart !== -1) {
104
+ cycles.push([...path.slice(cycleStart), current]);
105
+ }
106
+ return;
107
+ }
108
+
109
+ if (visited.has(current)) return;
110
+
111
+ visited.add(current);
112
+ recursionStack.add(current);
113
+
114
+ const dependencies = edges.get(current);
115
+ if (dependencies) {
116
+ for (const dep of dependencies) {
117
+ dfs(dep, [...path, current]);
118
+ }
119
+ }
120
+
121
+ recursionStack.delete(current);
122
+ }
123
+
124
+ dfs(file, []);
125
+ return cycles;
126
+ }
@@ -0,0 +1,411 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import prompts from 'prompts';
5
+ import { analyzeContext } from '../analyzer';
6
+ import { generateSummary } from '../summary';
7
+
8
+ /**
9
+ * Display analysis report in console
10
+ */
11
+ export function displayConsoleReport(
12
+ summary: ReturnType<typeof generateSummary>,
13
+ results: Awaited<ReturnType<typeof analyzeContext>>,
14
+ maxResults: number = 10
15
+ ): void {
16
+ const divider =
17
+ '──────────────────────────────────────────────────────────────────';
18
+ const totalIssues =
19
+ summary.criticalIssues + summary.majorIssues + summary.minorIssues;
20
+
21
+ console.log(chalk.bold('📊 Context Analysis Summary:\n'));
22
+ console.log(` • Total Files: ${chalk.cyan(summary.totalFiles)}`);
23
+ console.log(
24
+ ` • Total Tokens: ${chalk.cyan(summary.totalTokens.toLocaleString())}`
25
+ );
26
+ console.log(
27
+ ` • Avg Budget: ${chalk.cyan(summary.avgContextBudget.toFixed(0))} tokens`
28
+ );
29
+ console.log(
30
+ ` • Potential Saving: ${chalk.green(summary.totalPotentialSavings.toLocaleString())} tokens`
31
+ );
32
+ console.log();
33
+
34
+ if (totalIssues > 0) {
35
+ console.log(chalk.bold('⚠️ Issues Detected:\n'));
36
+ console.log(` • ${chalk.red('🔴 Critical:')} ${summary.criticalIssues}`);
37
+ console.log(` • ${chalk.yellow('🟡 Major:')} ${summary.majorIssues}`);
38
+ console.log(` • ${chalk.blue('🔵 Minor:')} ${summary.minorIssues}`);
39
+ console.log();
40
+ } else {
41
+ console.log(chalk.green('✅ No significant context issues detected!\n'));
42
+ }
43
+
44
+ // Fragmented modules
45
+ if (summary.fragmentedModules.length > 0) {
46
+ console.log(chalk.bold('🧩 Top Fragmented Modules:\n'));
47
+
48
+ summary.fragmentedModules.slice(0, maxResults).forEach((mod) => {
49
+ const scoreColor =
50
+ mod.fragmentationScore > 0.7
51
+ ? chalk.red
52
+ : mod.fragmentationScore > 0.4
53
+ ? chalk.yellow
54
+ : chalk.green;
55
+
56
+ console.log(
57
+ ` ${scoreColor('■')} ${chalk.white(mod.domain)} ${chalk.dim(`(${mod.files.length} files, ${(mod.fragmentationScore * 100).toFixed(0)}% frag)`)}`
58
+ );
59
+ });
60
+ console.log();
61
+ }
62
+
63
+ // Top expensive files
64
+ if (summary.topExpensiveFiles.length > 0) {
65
+ console.log(chalk.bold('💸 Most Expensive Files (Context Budget):\n'));
66
+
67
+ summary.topExpensiveFiles.slice(0, maxResults).forEach((item) => {
68
+ const fileName = item.file.split('/').slice(-2).join('/');
69
+ const severityColor =
70
+ item.severity === 'critical'
71
+ ? chalk.red
72
+ : item.severity === 'major'
73
+ ? chalk.yellow
74
+ : chalk.blue;
75
+
76
+ console.log(
77
+ ` ${severityColor('●')} ${chalk.white(fileName)} ${chalk.dim(`- ${item.contextBudget.toLocaleString()} tokens`)}`
78
+ );
79
+ });
80
+ console.log();
81
+ }
82
+
83
+ // Recommendations
84
+ if (totalIssues > 0) {
85
+ console.log(chalk.bold('💡 Top Recommendations:\n'));
86
+
87
+ const topFiles = results
88
+ .filter((r) => r.severity === 'critical' || r.severity === 'major')
89
+ .slice(0, 3);
90
+
91
+ topFiles.forEach((result, index) => {
92
+ const fileName = result.file.split('/').slice(-2).join('/');
93
+ console.log(chalk.cyan(` ${index + 1}. ${fileName}`));
94
+ result.recommendations.slice(0, 2).forEach((rec) => {
95
+ console.log(chalk.dim(` • ${rec}`));
96
+ });
97
+ });
98
+ console.log();
99
+ }
100
+
101
+ // Footer
102
+ console.log(chalk.cyan(divider));
103
+ console.log(
104
+ chalk.dim(
105
+ '\n⭐ Like aiready? Star us on GitHub: https://github.com/caopengau/aiready-context-analyzer'
106
+ )
107
+ );
108
+ console.log(
109
+ chalk.dim(
110
+ '🐛 Found a bug? Report it: https://github.com/caopengau/aiready-context-analyzer/issues\n'
111
+ )
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Generate HTML report
117
+ */
118
+ export function generateHTMLReport(
119
+ summary: ReturnType<typeof generateSummary>,
120
+ results: Awaited<ReturnType<typeof analyzeContext>>
121
+ ): string {
122
+ const totalIssues =
123
+ summary.criticalIssues + summary.majorIssues + summary.minorIssues;
124
+
125
+ // 'results' may be used in templates later; reference to avoid lint warnings
126
+ void results;
127
+
128
+ return `<!DOCTYPE html>
129
+ <html lang="en">
130
+ <head>
131
+ <meta charset="UTF-8">
132
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
133
+ <title>aiready Context Analysis Report</title>
134
+ <style>
135
+ body {
136
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
137
+ line-height: 1.6;
138
+ color: #333;
139
+ max-width: 1200px;
140
+ margin: 0 auto;
141
+ padding: 20px;
142
+ background-color: #f5f5f5;
143
+ }
144
+ h1, h2, h3 { color: #2c3e50; }
145
+ .header {
146
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
147
+ color: white;
148
+ padding: 30px;
149
+ border-radius: 8px;
150
+ margin-bottom: 30px;
151
+ }
152
+ .summary {
153
+ display: grid;
154
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
155
+ gap: 20px;
156
+ margin-bottom: 30px;
157
+ }
158
+ .card {
159
+ background: white;
160
+ padding: 20px;
161
+ border-radius: 8px;
162
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
163
+ }
164
+ .metric {
165
+ font-size: 2em;
166
+ font-weight: bold;
167
+ color: #667eea;
168
+ }
169
+ .label {
170
+ color: #666;
171
+ font-size: 0.9em;
172
+ margin-top: 5px;
173
+ }
174
+ .issue-critical { color: #e74c3c; }
175
+ .issue-major { color: #f39c12; }
176
+ .issue-minor { color: #3498db; }
177
+ table {
178
+ width: 100%;
179
+ border-collapse: collapse;
180
+ background: white;
181
+ border-radius: 8px;
182
+ overflow: hidden;
183
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
184
+ }
185
+ th, td {
186
+ padding: 12px;
187
+ text-align: left;
188
+ border-bottom: 1px solid #eee;
189
+ }
190
+ th {
191
+ background-color: #667eea;
192
+ color: white;
193
+ font-weight: 600;
194
+ }
195
+ tr:hover { background-color: #f8f9fa; }
196
+ .footer {
197
+ text-align: center;
198
+ margin-top: 40px;
199
+ padding: 20px;
200
+ color: #666;
201
+ font-size: 0.9em;
202
+ }
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div class="header">
207
+ <h1>🔍 AIReady Context Analysis Report</h1>
208
+ <p>Generated on ${new Date().toLocaleString()}</p>
209
+ </div>
210
+
211
+ <div class="summary">
212
+ <div class="card">
213
+ <div class="metric">${summary.totalFiles}</div>
214
+ <div class="label">Files Analyzed</div>
215
+ </div>
216
+ <div class="card">
217
+ <div class="metric">${summary.totalTokens.toLocaleString()}</div>
218
+ <div class="label">Total Tokens</div>
219
+ </div>
220
+ <div class="card">
221
+ <div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
222
+ <div class="label">Avg Context Budget</div>
223
+ </div>
224
+ <div class="card">
225
+ <div class="metric ${totalIssues > 0 ? 'issue-major' : ''}">${totalIssues}</div>
226
+ <div class="label">Total Issues</div>
227
+ </div>
228
+ </div>
229
+
230
+ ${
231
+ totalIssues > 0
232
+ ? `
233
+ <div class="card" style="margin-bottom: 30px;">
234
+ <h2>⚠️ Issues Summary</h2>
235
+ <p>
236
+ <span class="issue-critical">🔴 Critical: ${summary.criticalIssues}</span> &nbsp;
237
+ <span class="issue-major">🟡 Major: ${summary.majorIssues}</span> &nbsp;
238
+ <span class="issue-minor">🔵 Minor: ${summary.minorIssues}</span>
239
+ </p>
240
+ <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
241
+ </div>
242
+ `
243
+ : ''
244
+ }
245
+
246
+ ${
247
+ summary.fragmentedModules.length > 0
248
+ ? `
249
+ <div class="card" style="margin-bottom: 30px;">
250
+ <h2>🧩 Fragmented Modules</h2>
251
+ <table>
252
+ <thead>
253
+ <tr>
254
+ <th>Domain</th>
255
+ <th>Files</th>
256
+ <th>Fragmentation</th>
257
+ <th>Token Cost</th>
258
+ </tr>
259
+ </thead>
260
+ <tbody>
261
+ ${summary.fragmentedModules
262
+ .map(
263
+ (m) => `
264
+ <tr>
265
+ <td>${m.domain}</td>
266
+ <td>${m.files.length}</td>
267
+ <td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
268
+ <td>${m.totalTokens.toLocaleString()}</td>
269
+ </tr>
270
+ `
271
+ )
272
+ .join('')}
273
+ </tbody>
274
+ </table>
275
+ </div>
276
+ `
277
+ : ''
278
+ }
279
+
280
+ ${
281
+ summary.topExpensiveFiles.length > 0
282
+ ? `
283
+ <div class="card" style="margin-bottom: 30px;">
284
+ <h2>💸 Most Expensive Files</h2>
285
+ <table>
286
+ <thead>
287
+ <tr>
288
+ <th>File</th>
289
+ <th>Context Budget</th>
290
+ <th>Severity</th>
291
+ </tr>
292
+ </thead>
293
+ <tbody>
294
+ ${summary.topExpensiveFiles
295
+ .map(
296
+ (f) => `
297
+ <tr>
298
+ <td>${f.file}</td>
299
+ <td>${f.contextBudget.toLocaleString()} tokens</td>
300
+ <td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
301
+ </tr>
302
+ `
303
+ )
304
+ .join('')}
305
+ </tbody>
306
+ </table>
307
+ </div>
308
+ `
309
+ : ''
310
+ }
311
+
312
+ <div class="footer">
313
+ <p>Generated by <strong>@aiready/context-analyzer</strong></p>
314
+ <p>Like AIReady? <a href="https://github.com/caopengau/aiready-context-analyzer">Star us on GitHub</a></p>
315
+ <p>Found a bug? <a href="https://github.com/caopengau/aiready-context-analyzer/issues">Report it here</a></p>
316
+ </div>
317
+ </body>
318
+ </html>`;
319
+ }
320
+
321
+ /**
322
+ * Interactive setup: detect common frameworks and suggest excludes & focus areas
323
+ */
324
+ export async function runInteractiveSetup(
325
+ directory: string,
326
+ current: any
327
+ ): Promise<any> {
328
+ console.log(chalk.yellow('🧭 Interactive mode: let’s tailor the analysis.'));
329
+
330
+ const pkgPath = join(directory, 'package.json');
331
+ let deps: Record<string, string> = {};
332
+ if (existsSync(pkgPath)) {
333
+ try {
334
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
335
+ deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
336
+ } catch (e) {
337
+ void e;
338
+ // Ignore parse errors, use empty deps
339
+ }
340
+ }
341
+
342
+ const hasNextJs = existsSync(join(directory, '.next')) || !!deps['next'];
343
+ const hasCDK =
344
+ existsSync(join(directory, 'cdk.out')) ||
345
+ !!deps['aws-cdk-lib'] ||
346
+ Object.keys(deps).some((d) => d.startsWith('@aws-cdk/'));
347
+
348
+ const recommendedExcludes = new Set<string>(current.exclude || []);
349
+ if (
350
+ hasNextJs &&
351
+ !Array.from(recommendedExcludes).some((p) => p.includes('.next'))
352
+ ) {
353
+ recommendedExcludes.add('**/.next/**');
354
+ }
355
+ if (
356
+ hasCDK &&
357
+ !Array.from(recommendedExcludes).some((p) => p.includes('cdk.out'))
358
+ ) {
359
+ recommendedExcludes.add('**/cdk.out/**');
360
+ }
361
+
362
+ const { applyExcludes } = await prompts({
363
+ type: 'toggle',
364
+ name: 'applyExcludes',
365
+ message: `Detected ${hasNextJs ? 'Next.js ' : ''}${hasCDK ? 'AWS CDK ' : ''}frameworks. Apply recommended excludes?`,
366
+ initial: true,
367
+ active: 'yes',
368
+ inactive: 'no',
369
+ });
370
+
371
+ const nextOptions = { ...current };
372
+ if (applyExcludes) {
373
+ nextOptions.exclude = Array.from(recommendedExcludes);
374
+ }
375
+
376
+ const { focusArea } = await prompts({
377
+ type: 'select',
378
+ name: 'focusArea',
379
+ message: 'Which areas to focus?',
380
+ choices: [
381
+ { title: 'Frontend (web app)', value: 'frontend' },
382
+ { title: 'Backend (API/infra)', value: 'backend' },
383
+ { title: 'Both', value: 'both' },
384
+ ],
385
+ initial: 2,
386
+ });
387
+
388
+ if (focusArea === 'frontend') {
389
+ nextOptions.include = ['**/*.{ts,tsx,js,jsx}'];
390
+ nextOptions.exclude = Array.from(
391
+ new Set([
392
+ ...(nextOptions.exclude || []),
393
+ '**/cdk.out/**',
394
+ '**/infra/**',
395
+ '**/server/**',
396
+ '**/backend/**',
397
+ ])
398
+ );
399
+ } else if (focusArea === 'backend') {
400
+ nextOptions.include = [
401
+ '**/api/**',
402
+ '**/server/**',
403
+ '**/backend/**',
404
+ '**/infra/**',
405
+ '**/*.{ts,js,py,java}',
406
+ ];
407
+ }
408
+
409
+ console.log(chalk.green('✓ Interactive configuration applied.'));
410
+ return nextOptions;
411
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Simple singularization for common English plurals.
3
+ * Shared across graph-builder and semantic-analysis.
4
+ */
5
+ export function singularize(word: string): string {
6
+ const irregulars: Record<string, string> = {
7
+ people: 'person',
8
+ children: 'child',
9
+ men: 'man',
10
+ women: 'woman',
11
+ };
12
+ if (irregulars[word]) return irregulars[word];
13
+ if (word.endsWith('ies')) return word.slice(0, -3) + 'y';
14
+ if (word.endsWith('ses')) return word.slice(0, -2);
15
+ if (word.endsWith('s') && word.length > 3) return word.slice(0, -1);
16
+ return word;
17
+ }