@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.
- 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 +13 -12
- package/.turbo/turbo-test.log +32 -341
- 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-OUYSZZ7X.mjs +1846 -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-W2KNBN6W.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 +591 -1057
- 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 +822 -588
- 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__/scoring.test.ts +217 -9
- 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/scoring.ts +40 -20
- 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
|
@@ -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>
|
|
237
|
+
<span class="issue-major">🟡 Major: ${summary.majorIssues}</span>
|
|
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
|
+
}
|