aico-ai 1.1.3 → 1.1.5
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/README.md +7 -0
- package/aico-metrics.html +201 -0
- package/index.js +157 -1
- package/lib/ai-service.js +73 -0
- package/lib/metrics-analyzer.js +430 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- **Multi-Provider Support**: Groq, OpenAI, DeepSeek, Gemini, or local Ollama
|
|
10
10
|
- **Auto-Fix Suggestions**: Apply AI-recommended fixes with one click
|
|
11
11
|
- **Parallel Processing**: Fast reviews even for large diffs
|
|
12
|
+
- **Code Explanation**: Get instant explanations for complex files
|
|
12
13
|
|
|
13
14
|
### Team Rules Engine
|
|
14
15
|
- **Custom Standards**: Define your team's code quality rules
|
|
@@ -175,6 +176,12 @@ aico review
|
|
|
175
176
|
# Generate AI commit message
|
|
176
177
|
aico commit
|
|
177
178
|
|
|
179
|
+
# Generate Pull Request description
|
|
180
|
+
aico pr
|
|
181
|
+
|
|
182
|
+
# Explain the code and commit that was generated
|
|
183
|
+
aico explain
|
|
184
|
+
|
|
178
185
|
# Run security scan
|
|
179
186
|
aico security scan
|
|
180
187
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Aico Code Metrics Dashboard</title>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
11
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8fafc; }
|
|
12
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
13
|
+
.header { text-align: center; margin-bottom: 40px; }
|
|
14
|
+
.header h1 { color: #1e293b; font-size: 2.5rem; margin-bottom: 10px; }
|
|
15
|
+
.header p { color: #64748b; font-size: 1.1rem; }
|
|
16
|
+
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px; }
|
|
17
|
+
.metric-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
18
|
+
.metric-value { font-size: 2.5rem; font-weight: bold; color: #3b82f6; margin-bottom: 5px; }
|
|
19
|
+
.metric-label { color: #64748b; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
20
|
+
.charts-section { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 40px; }
|
|
21
|
+
.chart-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
22
|
+
.chart-card h3 { color: #1e293b; margin-bottom: 20px; }
|
|
23
|
+
.hotspots-section { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
|
24
|
+
.hotspot-item { display: flex; justify-content: space-between; align-items: center; padding: 15px; border-left: 4px solid #ef4444; margin-bottom: 10px; background: #fef2f2; }
|
|
25
|
+
.recommendations { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
26
|
+
.recommendation { padding: 15px; margin-bottom: 10px; border-radius: 8px; }
|
|
27
|
+
.recommendation.high { border-left: 4px solid #f59e0b; background: #fffbeb; }
|
|
28
|
+
.recommendation.critical { border-left: 4px solid #ef4444; background: #fef2f2; }
|
|
29
|
+
.recommendation.medium { border-left: 4px solid #3b82f6; background: #eff6ff; }
|
|
30
|
+
.priority { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; }
|
|
31
|
+
.priority.critical { background: #ef4444; color: white; }
|
|
32
|
+
.priority.high { background: #f59e0b; color: white; }
|
|
33
|
+
.priority.medium { background: #3b82f6; color: white; }
|
|
34
|
+
.timestamp { text-align: center; color: #64748b; margin-top: 40px; font-size: 0.9rem; }
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div class="container">
|
|
39
|
+
<div class="header">
|
|
40
|
+
<h1>🛡️ Aico Code Metrics</h1>
|
|
41
|
+
<p>Project health dashboard and code quality insights</p>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="metrics-grid">
|
|
45
|
+
<div class="metric-card">
|
|
46
|
+
<div class="metric-value">10</div>
|
|
47
|
+
<div class="metric-label">Total Files</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="metric-card">
|
|
50
|
+
<div class="metric-value">3.429</div>
|
|
51
|
+
<div class="metric-label">Total Lines</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="metric-card">
|
|
54
|
+
<div class="metric-value">75</div>
|
|
55
|
+
<div class="metric-label">Functions</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="metric-card">
|
|
58
|
+
<div class="metric-value">45.4</div>
|
|
59
|
+
<div class="metric-label">Avg Complexity</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="metric-card">
|
|
62
|
+
<div class="metric-value">0</div>
|
|
63
|
+
<div class="metric-label">Maintainability Index</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="charts-section">
|
|
68
|
+
<div class="chart-card">
|
|
69
|
+
<h3>📊 Complexity Distribution</h3>
|
|
70
|
+
<canvas id="complexityChart"></canvas>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="chart-card">
|
|
73
|
+
<h3>💻 Languages Used</h3>
|
|
74
|
+
<canvas id="languagesChart"></canvas>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
<div class="hotspots-section">
|
|
80
|
+
<h3>🔥 Code Hotspots</h3>
|
|
81
|
+
|
|
82
|
+
<div class="hotspot-item">
|
|
83
|
+
<div>
|
|
84
|
+
<strong>C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\index.js</strong><br>
|
|
85
|
+
<small>High complexity: 164</small>
|
|
86
|
+
</div>
|
|
87
|
+
<button onclick="window.open('C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\index.js', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="hotspot-item">
|
|
91
|
+
<div>
|
|
92
|
+
<strong>C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\ai-service.js</strong><br>
|
|
93
|
+
<small>High complexity: 33</small>
|
|
94
|
+
</div>
|
|
95
|
+
<button onclick="window.open('C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\ai-service.js', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="hotspot-item">
|
|
99
|
+
<div>
|
|
100
|
+
<strong>C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\ci-formatter.js</strong><br>
|
|
101
|
+
<small>High complexity: 58</small>
|
|
102
|
+
</div>
|
|
103
|
+
<button onclick="window.open('C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\ci-formatter.js', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="hotspot-item">
|
|
107
|
+
<div>
|
|
108
|
+
<strong>C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\metrics-analyzer.js</strong><br>
|
|
109
|
+
<small>High complexity: 28</small>
|
|
110
|
+
</div>
|
|
111
|
+
<button onclick="window.open('C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\metrics-analyzer.js', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="hotspot-item">
|
|
115
|
+
<div>
|
|
116
|
+
<strong>C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\rules-engine.js</strong><br>
|
|
117
|
+
<small>High complexity: 74</small>
|
|
118
|
+
</div>
|
|
119
|
+
<button onclick="window.open('C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\rules-engine.js', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="hotspot-item">
|
|
123
|
+
<div>
|
|
124
|
+
<strong>C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\security-scanner.js</strong><br>
|
|
125
|
+
<small>High complexity: 72</small>
|
|
126
|
+
</div>
|
|
127
|
+
<button onclick="window.open('C:\Users\Pichau\Desktop\aico-ai\aico-ai-lib\lib\security-scanner.js', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
<div class="recommendations">
|
|
135
|
+
<h3>💡 Recommendations</h3>
|
|
136
|
+
|
|
137
|
+
<div class="recommendation high">
|
|
138
|
+
<span class="priority high">high</span>
|
|
139
|
+
<strong>High average complexity detected. Consider breaking down complex functions.</strong><br>
|
|
140
|
+
<small>Action: Use aico refactor --suggestions</small>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="recommendation critical">
|
|
144
|
+
<span class="priority critical">critical</span>
|
|
145
|
+
<strong>6 files have very high complexity (>20).</strong><br>
|
|
146
|
+
<small>Action: Review these files immediately</small>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="recommendation medium">
|
|
150
|
+
<span class="priority medium">medium</span>
|
|
151
|
+
<strong>Low maintainability index. Consider refactoring and adding more documentation.</strong><br>
|
|
152
|
+
<small>Action: Increase test coverage and documentation</small>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
<div class="timestamp">
|
|
159
|
+
Generated on 27/03/2026, 10:18:52 by Aico AI
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<script>
|
|
164
|
+
// Complexity Chart
|
|
165
|
+
new Chart(document.getElementById('complexityChart'), {
|
|
166
|
+
type: 'doughnut',
|
|
167
|
+
data: {
|
|
168
|
+
labels: ['Low (1-5)', 'Medium (6-10)', 'High (11-20)', 'Very High (>20)'],
|
|
169
|
+
datasets: [{
|
|
170
|
+
data: [1, 3, 0, 6],
|
|
171
|
+
backgroundColor: ['#10b981', '#f59e0b', '#ef4444', '#991b1b']
|
|
172
|
+
}]
|
|
173
|
+
},
|
|
174
|
+
options: {
|
|
175
|
+
responsive: true,
|
|
176
|
+
plugins: {
|
|
177
|
+
legend: { position: 'bottom' }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Languages Chart
|
|
183
|
+
new Chart(document.getElementById('languagesChart'), {
|
|
184
|
+
type: 'pie',
|
|
185
|
+
data: {
|
|
186
|
+
labels: ["JavaScript"],
|
|
187
|
+
datasets: [{
|
|
188
|
+
data: [10],
|
|
189
|
+
backgroundColor: ['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981', '#ef4444']
|
|
190
|
+
}]
|
|
191
|
+
},
|
|
192
|
+
options: {
|
|
193
|
+
responsive: true,
|
|
194
|
+
plugins: {
|
|
195
|
+
legend: { position: 'bottom' }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
</script>
|
|
200
|
+
</body>
|
|
201
|
+
</html>
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { getStagedDiff, applyFix, getDiffChunks } from './lib/git-utils.js';
|
|
3
|
-
import { reviewCode, generateCommitMessage } from './lib/ai-service.js';
|
|
3
|
+
import { reviewCode, generateCommitMessage, generatePRDescription, explainCode } from './lib/ai-service.js';
|
|
4
4
|
import { parseAIResponse, displayIssues } from './lib/reviewer.js';
|
|
5
5
|
import {
|
|
6
6
|
initializeTeamRules,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
generateSecurityReport,
|
|
28
28
|
detectPackageManager
|
|
29
29
|
} from './lib/security-scanner.js';
|
|
30
|
+
import { calculateMetrics, generateDashboard } from './lib/metrics-analyzer.js';
|
|
30
31
|
import pc from 'picocolors';
|
|
31
32
|
import enquirer from 'enquirer';
|
|
32
33
|
import fs from 'fs';
|
|
@@ -715,7 +716,9 @@ ${pc.bold('Usage:')}
|
|
|
715
716
|
${pc.bold('Commands:')}
|
|
716
717
|
${pc.cyan('review')} Analyze staged changes and suggest improvements (default)
|
|
717
718
|
${pc.cyan('commit')} Generate and apply an AI-suggested commit message
|
|
719
|
+
${pc.cyan('pr')} Generate Pull Request description from diff
|
|
718
720
|
${pc.cyan('ci')} Run in CI/CD mode with machine-readable output
|
|
721
|
+
${pc.cyan('explain <file>')} Explain a specific file
|
|
719
722
|
${pc.cyan('security <subcommand>')} Security vulnerability scanning
|
|
720
723
|
${pc.dim('scan')} Full security scan (dependencies + code + config)
|
|
721
724
|
${pc.dim('check')} Check specific areas (--dependencies or --code)
|
|
@@ -725,6 +728,7 @@ ${pc.bold('Commands:')}
|
|
|
725
728
|
${pc.dim('init')} Initialize team rules configuration
|
|
726
729
|
${pc.dim('list')} List all active rules
|
|
727
730
|
${pc.dim('validate')} Validate code against team rules
|
|
731
|
+
${pc.cyan('metrics')} Generate code metrics dashboard
|
|
728
732
|
${pc.cyan('help')} Display this help message
|
|
729
733
|
|
|
730
734
|
${pc.bold('Options:')}
|
|
@@ -736,12 +740,16 @@ ${pc.bold('Options:')}
|
|
|
736
740
|
${pc.cyan('--severity <level>')} Filter by severity: error, warn, info (CI mode)
|
|
737
741
|
${pc.cyan('--dependencies')} Check dependencies only (security mode)
|
|
738
742
|
${pc.cyan('--code')} Check code only (security mode)
|
|
743
|
+
${pc.cyan('--output <file>')} Save dashboard to HTML file (metrics mode)
|
|
744
|
+
${pc.cyan('--open, -o')} Open dashboard in browser automatically (metrics mode)
|
|
739
745
|
${pc.cyan('--version, -v')} Display version number
|
|
740
746
|
${pc.cyan('--help, -h')} Display this help message
|
|
741
747
|
|
|
742
748
|
${pc.bold('Examples:')}
|
|
743
749
|
aico review --silent
|
|
744
750
|
aico commit
|
|
751
|
+
aico pr
|
|
752
|
+
aico explain src/utils.js
|
|
745
753
|
aico ci --format json --output report.json
|
|
746
754
|
aico security scan
|
|
747
755
|
aico security check --dependencies
|
|
@@ -749,9 +757,85 @@ ${pc.bold('Examples:')}
|
|
|
749
757
|
aico rules init
|
|
750
758
|
aico rules list
|
|
751
759
|
aico rules validate
|
|
760
|
+
aico metrics
|
|
761
|
+
aico metrics --output dashboard.html --open
|
|
752
762
|
`);
|
|
753
763
|
}
|
|
754
764
|
|
|
765
|
+
async function handleMetricsCommand(args) {
|
|
766
|
+
const outputPath = args.find(arg => arg.startsWith('--output='))?.split('=')[1] || 'aico-metrics.html';
|
|
767
|
+
const openDashboard = args.includes('--open') || args.includes('-o');
|
|
768
|
+
|
|
769
|
+
console.log(pc.bold(pc.blue('\n📊 Analyzing Code Metrics...\n')));
|
|
770
|
+
|
|
771
|
+
startSpinner('Scanning files and calculating metrics');
|
|
772
|
+
|
|
773
|
+
try {
|
|
774
|
+
const metrics = calculateMetrics();
|
|
775
|
+
stopSpinner();
|
|
776
|
+
|
|
777
|
+
// Display summary
|
|
778
|
+
console.log(pc.bold('\n📈 Metrics Summary:'));
|
|
779
|
+
console.log(`${pc.cyan('Total Files:')} ${metrics.summary.totalFiles}`);
|
|
780
|
+
console.log(`${pc.cyan('Total Lines:')} ${metrics.summary.totalLines.toLocaleString()}`);
|
|
781
|
+
console.log(`${pc.cyan('Total Functions:')} ${metrics.summary.totalFunctions}`);
|
|
782
|
+
console.log(`${pc.cyan('Avg Complexity:')} ${metrics.summary.avgComplexity.toFixed(1)}`);
|
|
783
|
+
console.log(`${pc.cyan('Maintainability Index:')} ${metrics.summary.maintainabilityIndex}/100`);
|
|
784
|
+
|
|
785
|
+
// Display complexity distribution
|
|
786
|
+
console.log(pc.bold('\n🎯 Complexity Distribution:'));
|
|
787
|
+
console.log(`${pc.green('Low (1-5):')} ${metrics.complexity.low} files`);
|
|
788
|
+
console.log(`${pc.yellow('Medium (6-10):')} ${metrics.complexity.medium} files`);
|
|
789
|
+
console.log(`${pc.yellow('High (11-20):')} ${metrics.complexity.high} files`);
|
|
790
|
+
console.log(`${pc.red('Very High (>20):')} ${metrics.complexity.veryHigh} files`);
|
|
791
|
+
|
|
792
|
+
// Display hotspots if any
|
|
793
|
+
if (metrics.trends.hotspots.length > 0) {
|
|
794
|
+
console.log(pc.bold('\n🔥 Code Hotspots:'));
|
|
795
|
+
metrics.trends.hotspots.slice(0, 5).forEach((hotspot, index) => {
|
|
796
|
+
console.log(`${pc.red(`${index + 1}.`)} ${hotspot.file} (${hotspot.reason}: ${hotspot.value})`);
|
|
797
|
+
});
|
|
798
|
+
if (metrics.trends.hotspots.length > 5) {
|
|
799
|
+
console.log(pc.dim(`... and ${metrics.trends.hotspots.length - 5} more`));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Display recommendations
|
|
804
|
+
if (metrics.trends.recommendations.length > 0) {
|
|
805
|
+
console.log(pc.bold('\n💡 Recommendations:'));
|
|
806
|
+
metrics.trends.recommendations.forEach((rec, index) => {
|
|
807
|
+
const priorityColor = rec.priority === 'critical' ? pc.red :
|
|
808
|
+
rec.priority === 'high' ? pc.yellow : pc.blue;
|
|
809
|
+
console.log(`${index + 1}. ${priorityColor(`[${rec.priority.toUpperCase()}]`)} ${rec.message}`);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Generate dashboard
|
|
814
|
+
startSpinner('Generating dashboard');
|
|
815
|
+
const dashboardPath = generateDashboard(metrics, outputPath);
|
|
816
|
+
stopSpinner();
|
|
817
|
+
|
|
818
|
+
console.log(pc.green(`\n✅ Dashboard generated: ${pc.bold(dashboardPath)}`));
|
|
819
|
+
|
|
820
|
+
if (openDashboard) {
|
|
821
|
+
try {
|
|
822
|
+
const { execSync } = await import('child_process');
|
|
823
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
824
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
825
|
+
execSync(`${start} ${dashboardPath}`);
|
|
826
|
+
console.log(pc.blue('🌐 Dashboard opened in your browser'));
|
|
827
|
+
} catch (error) {
|
|
828
|
+
console.log(pc.yellow(`⚠️ Could not open dashboard automatically. Please open ${dashboardPath} manually.`));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
} catch (error) {
|
|
833
|
+
stopSpinner();
|
|
834
|
+
console.error(pc.red('Error generating metrics:'), error.message);
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
755
839
|
async function main() {
|
|
756
840
|
const args = process.argv.slice(2);
|
|
757
841
|
const command = args[0] || 'review';
|
|
@@ -790,6 +874,78 @@ async function main() {
|
|
|
790
874
|
return;
|
|
791
875
|
}
|
|
792
876
|
|
|
877
|
+
if (command === 'metrics') {
|
|
878
|
+
await handleMetricsCommand(args);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (command === 'pr') {
|
|
883
|
+
try {
|
|
884
|
+
const currentBranch = execSync('git branch --show-current').toString().trim();
|
|
885
|
+
|
|
886
|
+
// Try to detect base branch (main, master, or develop)
|
|
887
|
+
let baseBranch = 'main';
|
|
888
|
+
const branches = ['main', 'master', 'develop', 'dev'];
|
|
889
|
+
for (const branch of branches) {
|
|
890
|
+
try {
|
|
891
|
+
execSync(`git rev-parse --verify origin/${branch}`, { stdio: 'ignore' });
|
|
892
|
+
baseBranch = branch;
|
|
893
|
+
break;
|
|
894
|
+
} catch (e) {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
console.log(pc.dim(`Comparing ${currentBranch} with origin/${baseBranch}...`));
|
|
900
|
+
const diff = execSync(`git diff origin/${baseBranch}...${currentBranch}`).toString();
|
|
901
|
+
|
|
902
|
+
if (!diff || diff.trim() === '') {
|
|
903
|
+
console.log(pc.yellow(`No differences found between ${currentBranch} and origin/${baseBranch}.`));
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
startSpinner('Generating PR description...');
|
|
908
|
+
const description = await generatePRDescription(diff);
|
|
909
|
+
stopSpinner();
|
|
910
|
+
|
|
911
|
+
console.log(pc.bold('\n📝 Suggested PR Description:\n'));
|
|
912
|
+
console.log(description);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
stopSpinner();
|
|
915
|
+
console.error(pc.red('Error generating PR description:'), error.message);
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (command === 'explain') {
|
|
922
|
+
const filePath = args[1];
|
|
923
|
+
if (!filePath) {
|
|
924
|
+
console.error(pc.red('Please provide a file path: aico explain <file>'));
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (!fs.existsSync(filePath)) {
|
|
929
|
+
console.error(pc.red(`File not found: ${filePath}`));
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
try {
|
|
934
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
935
|
+
startSpinner(`Analyzing ${filePath}...`);
|
|
936
|
+
const explanation = await explainCode(content, filePath);
|
|
937
|
+
stopSpinner();
|
|
938
|
+
|
|
939
|
+
console.log(pc.bold(`\n📘 Explanation for ${filePath}:\n`));
|
|
940
|
+
console.log(explanation);
|
|
941
|
+
} catch (error) {
|
|
942
|
+
stopSpinner();
|
|
943
|
+
console.error(pc.red('Error explaining file:'), error.message);
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
793
949
|
if (command !== 'review' && command !== 'commit') {
|
|
794
950
|
console.error(pc.red(`Unknown command: ${command}`));
|
|
795
951
|
console.log(`Run ${pc.cyan('aico help')} to see available commands.`);
|
package/lib/ai-service.js
CHANGED
|
@@ -167,3 +167,76 @@ Do not include any other text, only the commit message itself. Do not use emojis
|
|
|
167
167
|
throw error;
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
+
|
|
171
|
+
export async function explainCode(content, filePath) {
|
|
172
|
+
const ai = new AIClient();
|
|
173
|
+
ai.updateConfig();
|
|
174
|
+
|
|
175
|
+
if (!ai.client) {
|
|
176
|
+
throw new Error('AI provider not configured. Run `aico init` first.');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const systemPrompt = `
|
|
180
|
+
You are an expert code analyst. Explain the provided code in a clear, concise way.
|
|
181
|
+
Focus on:
|
|
182
|
+
- What the code does
|
|
183
|
+
- Key functions and their purposes
|
|
184
|
+
- Important patterns or design decisions
|
|
185
|
+
- Potential issues or improvements
|
|
186
|
+
|
|
187
|
+
Keep explanations under 200 words unless the code is very complex.
|
|
188
|
+
Use simple language that developers of any level can understand.
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const chatCompletion = await ai.createChatCompletion([
|
|
193
|
+
{ role: 'system', content: systemPrompt },
|
|
194
|
+
{ role: 'user', content: `Explain this code from ${filePath}:\n\n${content}` },
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
return chatCompletion.choices[0]?.message?.content?.trim() || 'Unable to generate explanation.';
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Error generating code explanation:', error);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function generatePRDescription(diff) {
|
|
205
|
+
const ai = new AIClient();
|
|
206
|
+
ai.updateConfig();
|
|
207
|
+
|
|
208
|
+
if (!ai.client) {
|
|
209
|
+
throw new Error('AI provider not configured. Run `aico init` first.');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const systemPrompt = `
|
|
213
|
+
You are an expert at writing Pull Request descriptions.
|
|
214
|
+
Analyze the diff and provide a clear, structured PR description.
|
|
215
|
+
|
|
216
|
+
Format:
|
|
217
|
+
## Summary
|
|
218
|
+
Brief description of what this PR does
|
|
219
|
+
|
|
220
|
+
## Changes
|
|
221
|
+
- Key change 1
|
|
222
|
+
- Key change 2
|
|
223
|
+
- Key change 3
|
|
224
|
+
|
|
225
|
+
## Testing
|
|
226
|
+
How to test these changes
|
|
227
|
+
|
|
228
|
+
Keep it concise but informative. No emojis.
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const chatCompletion = await ai.createChatCompletion([
|
|
233
|
+
{ role: 'system', content: systemPrompt },
|
|
234
|
+
{ role: 'user', content: `Write a PR description for this diff:\n\n${diff}` },
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
return chatCompletion.choices[0]?.message?.content?.trim() || '## Summary\nThis PR includes various improvements and bug fixes.';
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error('Error generating PR description:', error);
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calculate code metrics for a project
|
|
8
|
+
*/
|
|
9
|
+
export function calculateMetrics(projectPath = process.cwd()) {
|
|
10
|
+
const metrics = {
|
|
11
|
+
summary: {
|
|
12
|
+
totalFiles: 0,
|
|
13
|
+
totalLines: 0,
|
|
14
|
+
totalFunctions: 0,
|
|
15
|
+
avgComplexity: 0,
|
|
16
|
+
maintainabilityIndex: 0
|
|
17
|
+
},
|
|
18
|
+
files: [],
|
|
19
|
+
languages: {},
|
|
20
|
+
complexity: {
|
|
21
|
+
low: 0,
|
|
22
|
+
medium: 0,
|
|
23
|
+
high: 0,
|
|
24
|
+
veryHigh: 0
|
|
25
|
+
},
|
|
26
|
+
trends: {
|
|
27
|
+
hotspots: [],
|
|
28
|
+
recommendations: []
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Get all source files
|
|
34
|
+
const files = getSourceFiles(projectPath);
|
|
35
|
+
metrics.summary.totalFiles = files.length;
|
|
36
|
+
|
|
37
|
+
let totalComplexity = 0;
|
|
38
|
+
let totalFunctions = 0;
|
|
39
|
+
|
|
40
|
+
files.forEach(filePath => {
|
|
41
|
+
const fileMetrics = analyzeFile(filePath);
|
|
42
|
+
metrics.files.push(fileMetrics);
|
|
43
|
+
|
|
44
|
+
// Update language stats
|
|
45
|
+
const ext = path.extname(filePath);
|
|
46
|
+
const lang = getLanguageFromExtension(ext);
|
|
47
|
+
metrics.languages[lang] = (metrics.languages[lang] || 0) + 1;
|
|
48
|
+
|
|
49
|
+
// Update complexity stats
|
|
50
|
+
totalComplexity += fileMetrics.complexity;
|
|
51
|
+
totalFunctions += fileMetrics.functions;
|
|
52
|
+
|
|
53
|
+
if (fileMetrics.complexity <= 5) metrics.complexity.low++;
|
|
54
|
+
else if (fileMetrics.complexity <= 10) metrics.complexity.medium++;
|
|
55
|
+
else if (fileMetrics.complexity <= 20) metrics.complexity.high++;
|
|
56
|
+
else metrics.complexity.veryHigh++;
|
|
57
|
+
|
|
58
|
+
// Identify hotspots
|
|
59
|
+
if (fileMetrics.complexity > 15 || fileMetrics.lines > 200) {
|
|
60
|
+
metrics.trends.hotspots.push({
|
|
61
|
+
file: filePath,
|
|
62
|
+
reason: fileMetrics.complexity > 15 ? 'High complexity' : 'Large file',
|
|
63
|
+
value: fileMetrics.complexity > 15 ? fileMetrics.complexity : fileMetrics.lines
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
metrics.summary.totalLines = metrics.files.reduce((sum, f) => sum + f.lines, 0);
|
|
69
|
+
metrics.summary.totalFunctions = totalFunctions;
|
|
70
|
+
metrics.summary.avgComplexity = totalComplexity / files.length;
|
|
71
|
+
|
|
72
|
+
// Calculate maintainability index (simplified)
|
|
73
|
+
metrics.summary.maintainabilityIndex = calculateMaintainabilityIndex(metrics);
|
|
74
|
+
|
|
75
|
+
// Generate recommendations
|
|
76
|
+
metrics.trends.recommendations = generateRecommendations(metrics);
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(pc.red('Error calculating metrics:'), error.message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return metrics;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get source files from project
|
|
87
|
+
*/
|
|
88
|
+
function getSourceFiles(projectPath) {
|
|
89
|
+
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.py', '.go', '.rs', '.java', '.cpp', '.c'];
|
|
90
|
+
const excludeDirs = ['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.nuxt'];
|
|
91
|
+
|
|
92
|
+
let files = [];
|
|
93
|
+
|
|
94
|
+
function scanDirectory(dir) {
|
|
95
|
+
const items = fs.readdirSync(dir);
|
|
96
|
+
|
|
97
|
+
for (const item of items) {
|
|
98
|
+
const fullPath = path.join(dir, item);
|
|
99
|
+
const stat = fs.statSync(fullPath);
|
|
100
|
+
|
|
101
|
+
if (stat.isDirectory() && !excludeDirs.includes(item)) {
|
|
102
|
+
scanDirectory(fullPath);
|
|
103
|
+
} else if (stat.isFile() && extensions.includes(path.extname(item))) {
|
|
104
|
+
files.push(fullPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scanDirectory(projectPath);
|
|
110
|
+
return files;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Analyze individual file
|
|
115
|
+
*/
|
|
116
|
+
function analyzeFile(filePath) {
|
|
117
|
+
try {
|
|
118
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
119
|
+
const lines = content.split('\n');
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
file: filePath,
|
|
123
|
+
lines: lines.length,
|
|
124
|
+
functions: countFunctions(content),
|
|
125
|
+
complexity: calculateCyclomaticComplexity(content),
|
|
126
|
+
comments: countComments(content),
|
|
127
|
+
blankLines: lines.filter(line => line.trim() === '').length,
|
|
128
|
+
codeLines: lines.filter(line => line.trim() && !line.trim().startsWith('//') && !line.trim().startsWith('/*')).length
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
file: filePath,
|
|
133
|
+
lines: 0,
|
|
134
|
+
functions: 0,
|
|
135
|
+
complexity: 0,
|
|
136
|
+
comments: 0,
|
|
137
|
+
blankLines: 0,
|
|
138
|
+
codeLines: 0,
|
|
139
|
+
error: error.message
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Count functions in code
|
|
146
|
+
*/
|
|
147
|
+
function countFunctions(content) {
|
|
148
|
+
const patterns = [
|
|
149
|
+
/function\s+\w+/g,
|
|
150
|
+
/const\s+\w+\s*=\s*\(/g,
|
|
151
|
+
/class\s+\w+/g,
|
|
152
|
+
/def\s+\w+/g,
|
|
153
|
+
/func\s+\w+/g,
|
|
154
|
+
/fn\s+\w+/g
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
let count = 0;
|
|
158
|
+
patterns.forEach(pattern => {
|
|
159
|
+
const matches = content.match(pattern);
|
|
160
|
+
if (matches) count += matches.length;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return count;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Calculate cyclomatic complexity
|
|
168
|
+
*/
|
|
169
|
+
function calculateCyclomaticComplexity(content) {
|
|
170
|
+
const patterns = [
|
|
171
|
+
/if\s*\(/g,
|
|
172
|
+
/else\s+if/g,
|
|
173
|
+
/while\s*\(/g,
|
|
174
|
+
/for\s*\(/g,
|
|
175
|
+
/switch\s*\(/g,
|
|
176
|
+
/case\s+/g,
|
|
177
|
+
/catch\s*\(/g,
|
|
178
|
+
/&&/g,
|
|
179
|
+
/\|\|/g
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
let complexity = 1; // Base complexity
|
|
183
|
+
|
|
184
|
+
patterns.forEach(pattern => {
|
|
185
|
+
const matches = content.match(pattern);
|
|
186
|
+
if (matches) complexity += matches.length;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return complexity;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Count comment lines
|
|
194
|
+
*/
|
|
195
|
+
function countComments(content) {
|
|
196
|
+
const singleLineComments = (content.match(/\/\/.*$/gm) || []).length;
|
|
197
|
+
const multiLineComments = (content.match(/\/\*[\s\S]*?\*\//g) || []).length;
|
|
198
|
+
return singleLineComments + multiLineComments;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get language from file extension
|
|
203
|
+
*/
|
|
204
|
+
function getLanguageFromExtension(ext) {
|
|
205
|
+
const languageMap = {
|
|
206
|
+
'.js': 'JavaScript',
|
|
207
|
+
'.jsx': 'React',
|
|
208
|
+
'.ts': 'TypeScript',
|
|
209
|
+
'.tsx': 'React/TS',
|
|
210
|
+
'.py': 'Python',
|
|
211
|
+
'.go': 'Go',
|
|
212
|
+
'.rs': 'Rust',
|
|
213
|
+
'.java': 'Java',
|
|
214
|
+
'.cpp': 'C++',
|
|
215
|
+
'.c': 'C'
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return languageMap[ext] || 'Other';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Calculate maintainability index (simplified version)
|
|
223
|
+
*/
|
|
224
|
+
function calculateMaintainabilityIndex(metrics) {
|
|
225
|
+
const avgComplexity = metrics.summary.avgComplexity;
|
|
226
|
+
const avgLines = metrics.summary.totalLines / metrics.summary.totalFiles;
|
|
227
|
+
const commentRatio = metrics.files.reduce((sum, f) => sum + (f.comments / f.lines), 0) / metrics.files.length;
|
|
228
|
+
|
|
229
|
+
// Simplified maintainability index (0-100)
|
|
230
|
+
let index = 100;
|
|
231
|
+
index -= avgComplexity * 2;
|
|
232
|
+
index -= avgLines * 0.1;
|
|
233
|
+
index += commentRatio * 20;
|
|
234
|
+
|
|
235
|
+
return Math.max(0, Math.min(100, Math.round(index)));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Generate recommendations based on metrics
|
|
240
|
+
*/
|
|
241
|
+
function generateRecommendations(metrics) {
|
|
242
|
+
const recommendations = [];
|
|
243
|
+
|
|
244
|
+
if (metrics.summary.avgComplexity > 10) {
|
|
245
|
+
recommendations.push({
|
|
246
|
+
type: 'complexity',
|
|
247
|
+
priority: 'high',
|
|
248
|
+
message: 'High average complexity detected. Consider breaking down complex functions.',
|
|
249
|
+
action: 'Use aico refactor --suggestions'
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (metrics.complexity.veryHigh > 0) {
|
|
254
|
+
recommendations.push({
|
|
255
|
+
type: 'hotspots',
|
|
256
|
+
priority: 'critical',
|
|
257
|
+
message: `${metrics.complexity.veryHigh} files have very high complexity (>20).`,
|
|
258
|
+
action: 'Review these files immediately'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (metrics.summary.maintainabilityIndex < 60) {
|
|
263
|
+
recommendations.push({
|
|
264
|
+
type: 'maintainability',
|
|
265
|
+
priority: 'medium',
|
|
266
|
+
message: 'Low maintainability index. Consider refactoring and adding more documentation.',
|
|
267
|
+
action: 'Increase test coverage and documentation'
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return recommendations;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Generate HTML dashboard
|
|
276
|
+
*/
|
|
277
|
+
export function generateDashboard(metrics, outputPath = 'aico-metrics.html') {
|
|
278
|
+
const html = `
|
|
279
|
+
<!DOCTYPE html>
|
|
280
|
+
<html lang="en">
|
|
281
|
+
<head>
|
|
282
|
+
<meta charset="UTF-8">
|
|
283
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
284
|
+
<title>Aico Code Metrics Dashboard</title>
|
|
285
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
286
|
+
<style>
|
|
287
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
288
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8fafc; }
|
|
289
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
290
|
+
.header { text-align: center; margin-bottom: 40px; }
|
|
291
|
+
.header h1 { color: #1e293b; font-size: 2.5rem; margin-bottom: 10px; }
|
|
292
|
+
.header p { color: #64748b; font-size: 1.1rem; }
|
|
293
|
+
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px; }
|
|
294
|
+
.metric-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
295
|
+
.metric-value { font-size: 2.5rem; font-weight: bold; color: #3b82f6; margin-bottom: 5px; }
|
|
296
|
+
.metric-label { color: #64748b; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
297
|
+
.charts-section { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 40px; }
|
|
298
|
+
.chart-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
299
|
+
.chart-card h3 { color: #1e293b; margin-bottom: 20px; }
|
|
300
|
+
.hotspots-section { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
|
301
|
+
.hotspot-item { display: flex; justify-content: space-between; align-items: center; padding: 15px; border-left: 4px solid #ef4444; margin-bottom: 10px; background: #fef2f2; }
|
|
302
|
+
.recommendations { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
303
|
+
.recommendation { padding: 15px; margin-bottom: 10px; border-radius: 8px; }
|
|
304
|
+
.recommendation.high { border-left: 4px solid #f59e0b; background: #fffbeb; }
|
|
305
|
+
.recommendation.critical { border-left: 4px solid #ef4444; background: #fef2f2; }
|
|
306
|
+
.recommendation.medium { border-left: 4px solid #3b82f6; background: #eff6ff; }
|
|
307
|
+
.priority { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; }
|
|
308
|
+
.priority.critical { background: #ef4444; color: white; }
|
|
309
|
+
.priority.high { background: #f59e0b; color: white; }
|
|
310
|
+
.priority.medium { background: #3b82f6; color: white; }
|
|
311
|
+
.timestamp { text-align: center; color: #64748b; margin-top: 40px; font-size: 0.9rem; }
|
|
312
|
+
</style>
|
|
313
|
+
</head>
|
|
314
|
+
<body>
|
|
315
|
+
<div class="container">
|
|
316
|
+
<div class="header">
|
|
317
|
+
<h1>🛡️ Aico Code Metrics</h1>
|
|
318
|
+
<p>Project health dashboard and code quality insights</p>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div class="metrics-grid">
|
|
322
|
+
<div class="metric-card">
|
|
323
|
+
<div class="metric-value">${metrics.summary.totalFiles}</div>
|
|
324
|
+
<div class="metric-label">Total Files</div>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="metric-card">
|
|
327
|
+
<div class="metric-value">${metrics.summary.totalLines.toLocaleString()}</div>
|
|
328
|
+
<div class="metric-label">Total Lines</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="metric-card">
|
|
331
|
+
<div class="metric-value">${metrics.summary.totalFunctions}</div>
|
|
332
|
+
<div class="metric-label">Functions</div>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="metric-card">
|
|
335
|
+
<div class="metric-value">${metrics.summary.avgComplexity.toFixed(1)}</div>
|
|
336
|
+
<div class="metric-label">Avg Complexity</div>
|
|
337
|
+
</div>
|
|
338
|
+
<div class="metric-card">
|
|
339
|
+
<div class="metric-value">${metrics.summary.maintainabilityIndex}</div>
|
|
340
|
+
<div class="metric-label">Maintainability Index</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div class="charts-section">
|
|
345
|
+
<div class="chart-card">
|
|
346
|
+
<h3>📊 Complexity Distribution</h3>
|
|
347
|
+
<canvas id="complexityChart"></canvas>
|
|
348
|
+
</div>
|
|
349
|
+
<div class="chart-card">
|
|
350
|
+
<h3>💻 Languages Used</h3>
|
|
351
|
+
<canvas id="languagesChart"></canvas>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
${metrics.trends.hotspots.length > 0 ? `
|
|
356
|
+
<div class="hotspots-section">
|
|
357
|
+
<h3>🔥 Code Hotspots</h3>
|
|
358
|
+
${metrics.trends.hotspots.map(hotspot => `
|
|
359
|
+
<div class="hotspot-item">
|
|
360
|
+
<div>
|
|
361
|
+
<strong>${hotspot.file}</strong><br>
|
|
362
|
+
<small>${hotspot.reason}: ${hotspot.value}</small>
|
|
363
|
+
</div>
|
|
364
|
+
<button onclick="window.open('${hotspot.file}', '_blank')" style="padding: 5px 10px; background: #ef4444; color: white; border: none; border-radius: 4px; cursor: pointer;">View File</button>
|
|
365
|
+
</div>
|
|
366
|
+
`).join('')}
|
|
367
|
+
</div>
|
|
368
|
+
` : ''}
|
|
369
|
+
|
|
370
|
+
${metrics.trends.recommendations.length > 0 ? `
|
|
371
|
+
<div class="recommendations">
|
|
372
|
+
<h3>💡 Recommendations</h3>
|
|
373
|
+
${metrics.trends.recommendations.map(rec => `
|
|
374
|
+
<div class="recommendation ${rec.priority}">
|
|
375
|
+
<span class="priority ${rec.priority}">${rec.priority}</span>
|
|
376
|
+
<strong>${rec.message}</strong><br>
|
|
377
|
+
<small>Action: ${rec.action}</small>
|
|
378
|
+
</div>
|
|
379
|
+
`).join('')}
|
|
380
|
+
</div>
|
|
381
|
+
` : ''}
|
|
382
|
+
|
|
383
|
+
<div class="timestamp">
|
|
384
|
+
Generated on ${new Date().toLocaleString()} by Aico AI
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<script>
|
|
389
|
+
// Complexity Chart
|
|
390
|
+
new Chart(document.getElementById('complexityChart'), {
|
|
391
|
+
type: 'doughnut',
|
|
392
|
+
data: {
|
|
393
|
+
labels: ['Low (1-5)', 'Medium (6-10)', 'High (11-20)', 'Very High (>20)'],
|
|
394
|
+
datasets: [{
|
|
395
|
+
data: [${metrics.complexity.low}, ${metrics.complexity.medium}, ${metrics.complexity.high}, ${metrics.complexity.veryHigh}],
|
|
396
|
+
backgroundColor: ['#10b981', '#f59e0b', '#ef4444', '#991b1b']
|
|
397
|
+
}]
|
|
398
|
+
},
|
|
399
|
+
options: {
|
|
400
|
+
responsive: true,
|
|
401
|
+
plugins: {
|
|
402
|
+
legend: { position: 'bottom' }
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Languages Chart
|
|
408
|
+
new Chart(document.getElementById('languagesChart'), {
|
|
409
|
+
type: 'pie',
|
|
410
|
+
data: {
|
|
411
|
+
labels: ${JSON.stringify(Object.keys(metrics.languages))},
|
|
412
|
+
datasets: [{
|
|
413
|
+
data: ${JSON.stringify(Object.values(metrics.languages))},
|
|
414
|
+
backgroundColor: ['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981', '#ef4444']
|
|
415
|
+
}]
|
|
416
|
+
},
|
|
417
|
+
options: {
|
|
418
|
+
responsive: true,
|
|
419
|
+
plugins: {
|
|
420
|
+
legend: { position: 'bottom' }
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
</script>
|
|
425
|
+
</body>
|
|
426
|
+
</html>`;
|
|
427
|
+
|
|
428
|
+
fs.writeFileSync(outputPath, html);
|
|
429
|
+
return outputPath;
|
|
430
|
+
}
|