aico-ai 1.1.4 → 1.1.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/.aicoignore CHANGED
@@ -1,18 +1,53 @@
1
- # CodeGuard AI Ignore
1
+ # .aicoignore files and patterns excluded from aico review, rules, and security scan
2
+ # Same syntax as .gitignore: glob patterns, one per line, # for comments
2
3
 
3
4
  # Dependencies
4
- node_modules/
5
+ node_modules/**
5
6
  package-lock.json
6
7
  yarn.lock
7
8
  pnpm-lock.yaml
9
+ bun.lockb
8
10
 
9
11
  # Build outputs
10
- dist/
11
- build/
12
+ dist/**
13
+ build/**
14
+ out/**
15
+ .next/**
16
+ .nuxt/**
12
17
 
13
- # Secrets
18
+ # Environment / secrets
14
19
  .env
15
20
  .env.*
16
21
 
17
- # Large files
22
+ # Test snapshots & coverage
23
+ **/__snapshots__/**
24
+ *.snap
25
+ coverage/**
26
+ .nyc_output/**
27
+
28
+ # Binary / media assets
29
+ *.png
30
+ *.jpg
31
+ *.jpeg
32
+ *.gif
33
+ *.svg
34
+ *.ico
35
+ *.pdf
36
+ *.zip
37
+ *.gz
38
+ *.tar
39
+ *.wasm
40
+
41
+ # Auto-generated / minified
42
+ *.min.js
43
+ *.bundle.js
44
+ *.min.css
45
+
46
+ # Database migrations
47
+ prisma/migrations/**
48
+
49
+ # Logs
18
50
  *.log
51
+
52
+ # Vendored / third-party code
53
+ vendor/**
@@ -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
@@ -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';
@@ -727,6 +728,7 @@ ${pc.bold('Commands:')}
727
728
  ${pc.dim('init')} Initialize team rules configuration
728
729
  ${pc.dim('list')} List all active rules
729
730
  ${pc.dim('validate')} Validate code against team rules
731
+ ${pc.cyan('metrics')} Generate code metrics dashboard
730
732
  ${pc.cyan('help')} Display this help message
731
733
 
732
734
  ${pc.bold('Options:')}
@@ -738,6 +740,8 @@ ${pc.bold('Options:')}
738
740
  ${pc.cyan('--severity <level>')} Filter by severity: error, warn, info (CI mode)
739
741
  ${pc.cyan('--dependencies')} Check dependencies only (security mode)
740
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)
741
745
  ${pc.cyan('--version, -v')} Display version number
742
746
  ${pc.cyan('--help, -h')} Display this help message
743
747
 
@@ -753,9 +757,85 @@ ${pc.bold('Examples:')}
753
757
  aico rules init
754
758
  aico rules list
755
759
  aico rules validate
760
+ aico metrics
761
+ aico metrics --output dashboard.html --open
756
762
  `);
757
763
  }
758
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
+
759
839
  async function main() {
760
840
  const args = process.argv.slice(2);
761
841
  const command = args[0] || 'review';
@@ -794,6 +874,11 @@ async function main() {
794
874
  return;
795
875
  }
796
876
 
877
+ if (command === 'metrics') {
878
+ await handleMetricsCommand(args);
879
+ return;
880
+ }
881
+
797
882
  if (command === 'pr') {
798
883
  try {
799
884
  const currentBranch = execSync('git branch --show-current').toString().trim();
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
+ }
package/lib/git-utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { simpleGit } from 'simple-git';
2
2
  import pc from 'picocolors';
3
3
  import fs from 'fs';
4
+ import { loadAicoIgnore, toGitExclude } from './ignore-utils.js';
4
5
 
5
6
  const git = simpleGit();
6
7
 
@@ -11,10 +12,10 @@ const IGNORED_FILES = [
11
12
  'pnpm-lock.yaml',
12
13
  'bun.lockb',
13
14
  '.env',
14
- '*.png', '*.jpg', '*.jpeg', '*.gif', '*.svg', '*.ico', // Images
15
- '*.pdf', '*.zip', '*.gz', '*.tar', // Binaries
16
- 'dist/*', 'build/*', '.next/*', 'node_modules/*', // Build artifacts
17
- 'prisma/migrations/*', // Database migrations
15
+ '*.png', '*.jpg', '*.jpeg', '*.gif', '*.svg', '*.ico',
16
+ '*.pdf', '*.zip', '*.gz', '*.tar',
17
+ 'dist/*', 'build/*', '.next/*', 'node_modules/*',
18
+ 'prisma/migrations/*',
18
19
  ];
19
20
 
20
21
  /**
@@ -23,8 +24,9 @@ const IGNORED_FILES = [
23
24
  */
24
25
  export async function getStagedDiff() {
25
26
  try {
26
- // Filter out common large/binary files to save context
27
- const excludeArgs = IGNORED_FILES.map(file => `:(exclude)${file}`);
27
+ const aicoIgnorePatterns = loadAicoIgnore();
28
+ const allIgnored = [...IGNORED_FILES, ...aicoIgnorePatterns];
29
+ const excludeArgs = allIgnored.map(toGitExclude);
28
30
  let diff = await git.diff(['--cached', '--', '.', ...excludeArgs]);
29
31
 
30
32
  return diff;
@@ -0,0 +1,70 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ const AICOIGNORE_PATH = '.aicoignore';
5
+
6
+ /**
7
+ * Reads .aicoignore from the project root and returns an array of patterns.
8
+ * Ignores blank lines and lines starting with #.
9
+ * @returns {string[]}
10
+ */
11
+ export function loadAicoIgnore() {
12
+ try {
13
+ if (!fs.existsSync(AICOIGNORE_PATH)) return [];
14
+
15
+ return fs.readFileSync(AICOIGNORE_PATH, 'utf-8')
16
+ .split('\n')
17
+ .map(line => line.trim())
18
+ .filter(line => line.length > 0 && !line.startsWith('#'));
19
+ } catch {
20
+ return [];
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Converts a .aicoignore / glob-style pattern to a git pathspec exclude string.
26
+ * @param {string} pattern
27
+ * @returns {string}
28
+ */
29
+ export function toGitExclude(pattern) {
30
+ return `:(exclude)${pattern}`;
31
+ }
32
+
33
+ /**
34
+ * Checks whether a file path matches any of the given .aicoignore-style patterns.
35
+ * Supports * (any segment) and ** (any path).
36
+ * @param {string} filePath
37
+ * @param {string[]} patterns
38
+ * @returns {boolean}
39
+ */
40
+ export function matchesAicoIgnore(filePath, patterns) {
41
+ const normalized = filePath.replace(/\\/g, '/');
42
+
43
+ for (const pattern of patterns) {
44
+ if (globMatch(normalized, pattern)) return true;
45
+ }
46
+ return false;
47
+ }
48
+
49
+ /**
50
+ * Minimal glob matcher supporting *, **, and ? wildcards.
51
+ * @param {string} str
52
+ * @param {string} pattern
53
+ * @returns {boolean}
54
+ */
55
+ function globMatch(str, pattern) {
56
+ const regexStr = pattern
57
+ .replace(/\./g, '\\.')
58
+ .replace(/\*\*/g, '\u0001') // placeholder for **
59
+ .replace(/\*/g, '[^/]*') // * = anything except /
60
+ .replace(/\u0001/g, '.*') // ** = anything including /
61
+ .replace(/\?/g, '[^/]');
62
+
63
+ // Match against the full path or just the basename
64
+ const fullMatch = new RegExp(`^${regexStr}$`).test(str);
65
+ const baseMatch = new RegExp(`^${regexStr}$`).test(path.basename(str));
66
+ // Also match if pattern has no slash and matches basename
67
+ const noSlash = !pattern.includes('/');
68
+
69
+ return fullMatch || (noSlash && baseMatch);
70
+ }
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import pc from 'picocolors';
4
+ import { loadAicoIgnore, matchesAicoIgnore } from './ignore-utils.js';
4
5
 
5
6
  const RULES_PATH = '.aico/rules.json';
6
7
  const DEFAULT_RULES_TEMPLATE = {
@@ -136,10 +137,13 @@ export function validateAgainstRules(diff, fileContent, filePath) {
136
137
 
137
138
  const violations = [];
138
139
 
139
- // Check if file should be ignored
140
+ // Check if file should be ignored (rules.json ignore list or .aicoignore)
140
141
  if (shouldIgnoreFile(filePath, rules.ignore)) {
141
142
  return [];
142
143
  }
144
+ if (matchesAicoIgnore(filePath, loadAicoIgnore())) {
145
+ return [];
146
+ }
143
147
 
144
148
  // Check forbidden patterns
145
149
  if (rules.rules.forbidden) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aico-ai",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -57,4 +57,4 @@
57
57
  "picocolors": "^1.1.1",
58
58
  "simple-git": "^3.30.0"
59
59
  }
60
- }
60
+ }