devcompass 1.0.5 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,19 +7,37 @@ const fs = require('fs');
7
7
  const { findUnusedDeps } = require('../analyzers/unused-deps');
8
8
  const { findOutdatedDeps } = require('../analyzers/outdated');
9
9
  const { calculateScore } = require('../analyzers/scoring');
10
+ const { checkEcosystemAlerts } = require('../alerts');
11
+ const {
12
+ formatAlerts,
13
+ getSeverityDisplay,
14
+ calculateAlertPenalty
15
+ } = require('../alerts/formatter');
10
16
  const {
11
17
  log,
12
18
  logSection,
13
19
  logDivider,
14
20
  getScoreColor
15
21
  } = require('../utils/logger');
22
+ const { loadConfig, filterAlerts } = require('../config/loader');
23
+ const { getCached, setCache } = require('../cache/manager');
24
+ const { formatAsJson } = require('../utils/json-formatter');
25
+ const { handleCiMode } = require('../utils/ci-handler');
16
26
  const packageJson = require('../../package.json');
17
27
 
18
28
  async function analyze(options) {
19
29
  const projectPath = options.path || process.cwd();
20
30
 
21
- console.log('\n');
22
- log(chalk.cyan.bold(`🔍 DevCompass v${packageJson.version}`) + ' - Analyzing your project...\n');
31
+ // Load config
32
+ const config = loadConfig(projectPath);
33
+
34
+ // Handle output modes
35
+ const outputMode = options.json ? 'json' : (options.ci ? 'ci' : (options.silent ? 'silent' : 'normal'));
36
+
37
+ if (outputMode !== 'silent') {
38
+ console.log('\n');
39
+ log(chalk.cyan.bold(`🔍 DevCompass v${packageJson.version}`) + ' - Analyzing your project...\n');
40
+ }
23
41
 
24
42
  const spinner = ora({
25
43
  text: 'Loading project...',
@@ -58,35 +76,101 @@ async function analyze(options) {
58
76
  process.exit(0);
59
77
  }
60
78
 
61
- spinner.text = 'Detecting unused dependencies...';
79
+ // Check for ecosystem alerts (with cache)
80
+ spinner.text = 'Checking ecosystem alerts...';
81
+ let alerts = [];
82
+
83
+ if (config.cache) {
84
+ alerts = getCached(projectPath, 'alerts');
85
+ }
62
86
 
87
+ if (!alerts) {
88
+ try {
89
+ alerts = await checkEcosystemAlerts(projectPath, dependencies);
90
+ if (config.cache) {
91
+ setCache(projectPath, 'alerts', alerts);
92
+ }
93
+ } catch (error) {
94
+ if (outputMode !== 'silent') {
95
+ console.log(chalk.yellow('\n⚠️ Could not check ecosystem alerts'));
96
+ console.log(chalk.gray(` Error: ${error.message}\n`));
97
+ }
98
+ alerts = [];
99
+ }
100
+ }
101
+
102
+ // Filter alerts based on config
103
+ alerts = filterAlerts(alerts, config);
104
+
105
+ // Unused dependencies
106
+ spinner.text = 'Detecting unused dependencies...';
63
107
  let unusedDeps = [];
64
- try {
65
- unusedDeps = await findUnusedDeps(projectPath, dependencies);
66
- } catch (error) {
67
- console.log(chalk.yellow('\n⚠️ Could not detect unused dependencies'));
68
- console.log(chalk.gray(` Error: ${error.message}\n`));
108
+
109
+ if (config.cache) {
110
+ unusedDeps = getCached(projectPath, 'unused');
69
111
  }
70
112
 
71
- spinner.text = 'Checking for outdated packages...';
113
+ if (!unusedDeps) {
114
+ try {
115
+ unusedDeps = await findUnusedDeps(projectPath, dependencies);
116
+ if (config.cache) {
117
+ setCache(projectPath, 'unused', unusedDeps);
118
+ }
119
+ } catch (error) {
120
+ if (outputMode !== 'silent') {
121
+ console.log(chalk.yellow('\n⚠️ Could not detect unused dependencies'));
122
+ console.log(chalk.gray(` Error: ${error.message}\n`));
123
+ }
124
+ unusedDeps = [];
125
+ }
126
+ }
72
127
 
128
+ // Outdated packages
129
+ spinner.text = 'Checking for outdated packages...';
73
130
  let outdatedDeps = [];
74
- try {
75
- outdatedDeps = await findOutdatedDeps(projectPath, dependencies);
76
- } catch (error) {
77
- console.log(chalk.yellow('\n⚠️ Could not check for outdated packages'));
78
- console.log(chalk.gray(` Error: ${error.message}\n`));
131
+
132
+ if (config.cache) {
133
+ outdatedDeps = getCached(projectPath, 'outdated');
134
+ }
135
+
136
+ if (!outdatedDeps) {
137
+ try {
138
+ outdatedDeps = await findOutdatedDeps(projectPath, dependencies);
139
+ if (config.cache) {
140
+ setCache(projectPath, 'outdated', outdatedDeps);
141
+ }
142
+ } catch (error) {
143
+ if (outputMode !== 'silent') {
144
+ console.log(chalk.yellow('\n⚠️ Could not check for outdated packages'));
145
+ console.log(chalk.gray(` Error: ${error.message}\n`));
146
+ }
147
+ outdatedDeps = [];
148
+ }
79
149
  }
80
150
 
151
+ const alertPenalty = calculateAlertPenalty(alerts);
81
152
  const score = calculateScore(
82
153
  totalDeps,
83
154
  unusedDeps.length,
84
- outdatedDeps.length
155
+ outdatedDeps.length,
156
+ alerts.length,
157
+ alertPenalty
85
158
  );
86
159
 
87
160
  spinner.succeed(chalk.green(`Scanned ${totalDeps} dependencies in project`));
88
161
 
89
- displayResults(unusedDeps, outdatedDeps, score, totalDeps);
162
+ // Handle different output modes
163
+ if (outputMode === 'json') {
164
+ const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps);
165
+ console.log(jsonOutput);
166
+ } else if (outputMode === 'ci') {
167
+ displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps);
168
+ handleCiMode(score, config, alerts, unusedDeps);
169
+ } else if (outputMode === 'silent') {
170
+ // Silent mode - no output
171
+ } else {
172
+ displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps);
173
+ }
90
174
 
91
175
  } catch (error) {
92
176
  spinner.fail(chalk.red('Analysis failed'));
@@ -98,13 +182,53 @@ async function analyze(options) {
98
182
  }
99
183
  }
100
184
 
101
- function displayResults(unusedDeps, outdatedDeps, score, totalDeps) {
185
+ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
102
186
  logDivider();
103
187
 
188
+ // ECOSYSTEM ALERTS (NEW SECTION)
189
+ if (alerts.length > 0) {
190
+ logSection('🚨 ECOSYSTEM ALERTS', alerts.length);
191
+
192
+ const grouped = formatAlerts(alerts);
193
+
194
+ Object.entries(grouped).forEach(([packageName, packageAlerts]) => {
195
+ const firstAlert = packageAlerts[0];
196
+ const display = getSeverityDisplay(firstAlert.severity);
197
+
198
+ log(`\n${display.emoji} ${display.color(display.label)}`);
199
+ log(` ${chalk.bold(packageName)}${chalk.gray('@' + firstAlert.version)}`);
200
+
201
+ packageAlerts.forEach((alert, index) => {
202
+ if (index > 0) {
203
+ const alertDisplay = getSeverityDisplay(alert.severity);
204
+ log(`\n ${alertDisplay.emoji} ${alertDisplay.color(alertDisplay.label)}`);
205
+ }
206
+
207
+ log(` ${chalk.yellow('Issue:')} ${alert.title}`);
208
+ log(` ${chalk.gray('Affected:')} ${alert.affected}`);
209
+
210
+ if (alert.fix) {
211
+ log(` ${chalk.green('Fix:')} ${alert.fix}`);
212
+ }
213
+
214
+ if (alert.source) {
215
+ log(` ${chalk.gray('Source:')} ${alert.source}`);
216
+ }
217
+ });
218
+ });
219
+
220
+ log('');
221
+ } else {
222
+ logSection('✅ ECOSYSTEM ALERTS');
223
+ log(chalk.green(' No known issues detected!\n'));
224
+ }
225
+
226
+ logDivider();
227
+
228
+ // UNUSED DEPENDENCIES
104
229
  if (unusedDeps.length > 0) {
105
230
  logSection('🔴 UNUSED DEPENDENCIES', unusedDeps.length);
106
231
 
107
- // Show ALL unused deps
108
232
  unusedDeps.forEach(dep => {
109
233
  log(` ${chalk.red('●')} ${dep.name}`);
110
234
  });
@@ -120,10 +244,10 @@ function displayResults(unusedDeps, outdatedDeps, score, totalDeps) {
120
244
 
121
245
  logDivider();
122
246
 
247
+ // OUTDATED PACKAGES
123
248
  if (outdatedDeps.length > 0) {
124
249
  logSection('🟡 OUTDATED PACKAGES', outdatedDeps.length);
125
250
 
126
- // Show ALL outdated packages
127
251
  outdatedDeps.forEach(dep => {
128
252
  const nameCol = dep.name.padEnd(20);
129
253
  const currentVer = chalk.yellow(dep.current);
@@ -142,39 +266,84 @@ function displayResults(unusedDeps, outdatedDeps, score, totalDeps) {
142
266
 
143
267
  logDivider();
144
268
 
269
+ // PROJECT HEALTH
145
270
  logSection('📊 PROJECT HEALTH');
146
271
 
147
272
  const scoreColor = getScoreColor(score.total);
148
273
  log(` Overall Score: ${scoreColor(score.total + '/10')}`);
149
274
  log(` Total Dependencies: ${chalk.cyan(totalDeps)}`);
275
+
276
+ if (alerts.length > 0) {
277
+ log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
278
+ }
279
+
150
280
  log(` Unused: ${chalk.red(unusedDeps.length)}`);
151
281
  log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
152
282
 
153
283
  logDivider();
154
284
 
155
- if (unusedDeps.length > 0) {
156
- logSection('💡 QUICK WIN');
157
- log(' Clean up unused dependencies:\n');
158
-
159
- const packagesToRemove = unusedDeps
160
- .map(d => d.name)
161
- .join(' ');
285
+ // QUICK WINS
286
+ displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps);
287
+ }
288
+
289
+ function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
290
+ const hasCriticalAlerts = alerts.some(a => a.severity === 'critical' || a.severity === 'high');
291
+
292
+ if (hasCriticalAlerts || unusedDeps.length > 0) {
293
+ logSection('💡 QUICK WINS');
162
294
 
163
- log(chalk.cyan(` npm uninstall ${packagesToRemove}\n`));
295
+ // Fix critical alerts first
296
+ if (hasCriticalAlerts) {
297
+ const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
298
+
299
+ log(' 🔴 Fix critical issues:\n');
300
+
301
+ criticalAlerts.forEach(alert => {
302
+ if (alert.fix && alert.fix.includes('.')) {
303
+ // It's a version number
304
+ log(chalk.cyan(` npm install ${alert.package}@${alert.fix}`));
305
+ } else {
306
+ log(chalk.gray(` ${alert.package}: ${alert.fix}`));
307
+ }
308
+ });
309
+
310
+ log('');
311
+ }
164
312
 
165
- log(' Expected impact:');
166
- log(` ${chalk.green('✓')} Remove ${unusedDeps.length} unused package${unusedDeps.length > 1 ? 's' : ''}`);
167
- log(` ${chalk.green('✓')} Reduce node_modules size`);
313
+ // Remove unused deps
314
+ if (unusedDeps.length > 0) {
315
+ log(' 🧹 Clean up unused dependencies:\n');
316
+
317
+ const packagesToRemove = unusedDeps.map(d => d.name).join(' ');
318
+ log(chalk.cyan(` npm uninstall ${packagesToRemove}\n`));
319
+ }
168
320
 
321
+ // Show expected impact
322
+ const alertPenalty = calculateAlertPenalty(alerts.filter(a => a.severity !== 'critical' && a.severity !== 'high'));
169
323
  const improvedScore = calculateScore(
170
324
  totalDeps - unusedDeps.length,
171
325
  0,
172
- outdatedDeps.length
326
+ outdatedDeps.length,
327
+ alerts.length - alerts.filter(a => a.severity === 'critical' || a.severity === 'high').length,
328
+ alertPenalty
173
329
  );
174
330
 
331
+ log(' Expected impact:');
332
+
333
+ if (hasCriticalAlerts) {
334
+ log(` ${chalk.green('✓')} Resolve critical security/stability issues`);
335
+ }
336
+
337
+ if (unusedDeps.length > 0) {
338
+ log(` ${chalk.green('✓')} Remove ${unusedDeps.length} unused package${unusedDeps.length > 1 ? 's' : ''}`);
339
+ log(` ${chalk.green('✓')} Reduce node_modules size`);
340
+ }
341
+
175
342
  const improvedScoreColor = getScoreColor(improvedScore.total);
176
343
  log(` ${chalk.green('✓')} Improve health score → ${improvedScoreColor(improvedScore.total + '/10')}\n`);
177
344
 
345
+ log(chalk.cyan('💡 TIP: Run') + chalk.bold(' devcompass fix ') + chalk.cyan('to apply these fixes automatically!\n'));
346
+
178
347
  logDivider();
179
348
  }
180
349
  }
@@ -0,0 +1,247 @@
1
+ // src/commands/fix.js
2
+ const chalk = require('chalk');
3
+ const ora = require('ora');
4
+ const { execSync } = require('child_process');
5
+ const readline = require('readline');
6
+
7
+ const { findUnusedDeps } = require('../analyzers/unused-deps');
8
+ const { findOutdatedDeps } = require('../analyzers/outdated');
9
+ const { checkEcosystemAlerts } = require('../alerts');
10
+ const { getSeverityDisplay } = require('../alerts/formatter');
11
+
12
+ async function fix(options) {
13
+ const projectPath = options.path || process.cwd();
14
+
15
+ console.log('\n');
16
+ console.log(chalk.cyan.bold('🔧 DevCompass Fix') + ' - Analyzing and fixing your project...\n');
17
+
18
+ const spinner = ora({
19
+ text: 'Analyzing project...',
20
+ color: 'cyan'
21
+ }).start();
22
+
23
+ try {
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const packageJsonPath = path.join(projectPath, 'package.json');
27
+
28
+ if (!fs.existsSync(packageJsonPath)) {
29
+ spinner.fail(chalk.red('No package.json found'));
30
+ process.exit(1);
31
+ }
32
+
33
+ const projectPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
34
+ const dependencies = {
35
+ ...(projectPackageJson.dependencies || {}),
36
+ ...(projectPackageJson.devDependencies || {})
37
+ };
38
+
39
+ // Check for critical alerts first
40
+ spinner.text = 'Checking for critical issues...';
41
+ const alerts = await checkEcosystemAlerts(projectPath, dependencies);
42
+ const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
43
+
44
+ // Find unused dependencies
45
+ spinner.text = 'Finding unused dependencies...';
46
+ const unusedDeps = await findUnusedDeps(projectPath, dependencies);
47
+
48
+ // Find outdated packages
49
+ spinner.text = 'Checking for updates...';
50
+ const outdatedDeps = await findOutdatedDeps(projectPath, dependencies);
51
+
52
+ spinner.succeed(chalk.green('Analysis complete!\n'));
53
+
54
+ // Show what will be fixed
55
+ await showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options);
56
+
57
+ } catch (error) {
58
+ spinner.fail(chalk.red('Analysis failed'));
59
+ console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options) {
65
+ const actions = [];
66
+
67
+ // Critical alerts
68
+ if (criticalAlerts.length > 0) {
69
+ console.log(chalk.red.bold('🔴 CRITICAL ISSUES TO FIX:\n'));
70
+
71
+ criticalAlerts.forEach(alert => {
72
+ const display = getSeverityDisplay(alert.severity);
73
+ console.log(`${display.emoji} ${chalk.bold(alert.package)}@${alert.version}`);
74
+ console.log(` ${chalk.gray('Issue:')} ${alert.title}`);
75
+
76
+ if (alert.fix && /^\d+\.\d+/.test(alert.fix)) {
77
+ console.log(` ${chalk.green('Fix:')} Upgrade to ${alert.fix}\n`);
78
+ actions.push({
79
+ type: 'upgrade',
80
+ package: alert.package,
81
+ version: alert.fix,
82
+ reason: 'Critical security/stability issue'
83
+ });
84
+ } else {
85
+ console.log(` ${chalk.yellow('Fix:')} ${alert.fix}\n`);
86
+ }
87
+ });
88
+
89
+ console.log('━'.repeat(70) + '\n');
90
+ }
91
+
92
+ // Unused dependencies
93
+ if (unusedDeps.length > 0) {
94
+ console.log(chalk.yellow.bold('🧹 UNUSED DEPENDENCIES TO REMOVE:\n'));
95
+
96
+ unusedDeps.forEach(dep => {
97
+ console.log(` ${chalk.red('●')} ${dep.name}`);
98
+ actions.push({
99
+ type: 'uninstall',
100
+ package: dep.name,
101
+ reason: 'Not used in project'
102
+ });
103
+ });
104
+
105
+ console.log('\n' + '━'.repeat(70) + '\n');
106
+ }
107
+
108
+ // Safe updates (patch/minor only)
109
+ const safeUpdates = outdatedDeps.filter(dep =>
110
+ dep.versionsBehind === 'patch update' || dep.versionsBehind === 'minor update'
111
+ );
112
+
113
+ if (safeUpdates.length > 0) {
114
+ console.log(chalk.cyan.bold('⬆️ SAFE UPDATES (patch/minor):\n'));
115
+
116
+ safeUpdates.forEach(dep => {
117
+ console.log(` ${dep.name}: ${chalk.yellow(dep.current)} → ${chalk.green(dep.latest)} ${chalk.gray(`(${dep.versionsBehind})`)}`);
118
+ actions.push({
119
+ type: 'update',
120
+ package: dep.name,
121
+ version: dep.latest,
122
+ reason: dep.versionsBehind
123
+ });
124
+ });
125
+
126
+ console.log('\n' + '━'.repeat(70) + '\n');
127
+ }
128
+
129
+ // Major updates (show but don't auto-apply)
130
+ const majorUpdates = outdatedDeps.filter(dep => dep.versionsBehind === 'major update');
131
+
132
+ if (majorUpdates.length > 0) {
133
+ console.log(chalk.gray.bold('⚠️ MAJOR UPDATES (skipped - may have breaking changes):\n'));
134
+
135
+ majorUpdates.forEach(dep => {
136
+ console.log(` ${chalk.gray(dep.name)}: ${dep.current} → ${dep.latest}`);
137
+ });
138
+
139
+ console.log(chalk.gray('\n Run these manually after reviewing changelog:\n'));
140
+ majorUpdates.forEach(dep => {
141
+ console.log(chalk.gray(` npm install ${dep.name}@${dep.latest}`));
142
+ });
143
+
144
+ console.log('\n' + '━'.repeat(70) + '\n');
145
+ }
146
+
147
+ if (actions.length === 0) {
148
+ console.log(chalk.green('✨ Everything looks good! No fixes needed.\n'));
149
+ return;
150
+ }
151
+
152
+ // Summary
153
+ console.log(chalk.bold('📊 FIX SUMMARY:\n'));
154
+ console.log(` Critical fixes: ${criticalAlerts.length}`);
155
+ console.log(` Remove unused: ${unusedDeps.length}`);
156
+ console.log(` Safe updates: ${safeUpdates.length}`);
157
+ console.log(` Skipped major: ${majorUpdates.length}\n`);
158
+
159
+ console.log('━'.repeat(70) + '\n');
160
+
161
+ // Confirm
162
+ if (options.yes) {
163
+ await applyFixes(actions);
164
+ } else {
165
+ const confirmed = await askConfirmation('\n❓ Apply these fixes?');
166
+
167
+ if (confirmed) {
168
+ await applyFixes(actions);
169
+ } else {
170
+ console.log(chalk.yellow('\n⚠️ Fix cancelled. No changes made.\n'));
171
+ }
172
+ }
173
+ }
174
+
175
+ async function applyFixes(actions) {
176
+ console.log(chalk.cyan.bold('\n🔧 Applying fixes...\n'));
177
+
178
+ const spinner = ora('Processing...').start();
179
+
180
+ try {
181
+ // Group by type
182
+ const toUninstall = actions.filter(a => a.type === 'uninstall').map(a => a.package);
183
+ const toUpgrade = actions.filter(a => a.type === 'upgrade');
184
+ const toUpdate = actions.filter(a => a.type === 'update');
185
+
186
+ // Uninstall unused
187
+ if (toUninstall.length > 0) {
188
+ spinner.text = `Removing ${toUninstall.length} unused packages...`;
189
+
190
+ const cmd = `npm uninstall ${toUninstall.join(' ')}`;
191
+ execSync(cmd, { stdio: 'pipe' });
192
+
193
+ spinner.succeed(chalk.green(`✅ Removed ${toUninstall.length} unused packages`));
194
+ spinner.start();
195
+ }
196
+
197
+ // Upgrade critical packages
198
+ for (const action of toUpgrade) {
199
+ spinner.text = `Fixing ${action.package}@${action.version}...`;
200
+
201
+ const cmd = `npm install ${action.package}@${action.version}`;
202
+ execSync(cmd, { stdio: 'pipe' });
203
+
204
+ spinner.succeed(chalk.green(`✅ Fixed ${action.package}@${action.version}`));
205
+ spinner.start();
206
+ }
207
+
208
+ // Update safe packages
209
+ if (toUpdate.length > 0) {
210
+ spinner.text = `Updating ${toUpdate.length} packages...`;
211
+
212
+ for (const action of toUpdate) {
213
+ const cmd = `npm install ${action.package}@${action.version}`;
214
+ execSync(cmd, { stdio: 'pipe' });
215
+ }
216
+
217
+ spinner.succeed(chalk.green(`✅ Updated ${toUpdate.length} packages`));
218
+ } else {
219
+ spinner.stop();
220
+ }
221
+
222
+ console.log(chalk.green.bold('\n✨ All fixes applied successfully!\n'));
223
+ console.log(chalk.cyan('💡 Run') + chalk.bold(' devcompass analyze ') + chalk.cyan('to see the new health score.\n'));
224
+
225
+ } catch (error) {
226
+ spinner.fail(chalk.red('Fix failed'));
227
+ console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
228
+ console.log(chalk.yellow('💡 You may need to fix this manually.\n'));
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ function askConfirmation(question) {
234
+ const rl = readline.createInterface({
235
+ input: process.stdin,
236
+ output: process.stdout
237
+ });
238
+
239
+ return new Promise(resolve => {
240
+ rl.question(chalk.cyan(question) + chalk.gray(' (y/N): '), answer => {
241
+ rl.close();
242
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
243
+ });
244
+ });
245
+ }
246
+
247
+ module.exports = { fix };
@@ -0,0 +1,72 @@
1
+ // src/config/loader.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ function loadConfig(projectPath) {
6
+ const configPath = path.join(projectPath, 'devcompass.config.json');
7
+
8
+ if (!fs.existsSync(configPath)) {
9
+ return getDefaultConfig();
10
+ }
11
+
12
+ try {
13
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
14
+ return { ...getDefaultConfig(), ...config };
15
+ } catch (error) {
16
+ console.warn('Warning: Could not parse devcompass.config.json, using defaults');
17
+ return getDefaultConfig();
18
+ }
19
+ }
20
+
21
+ function getDefaultConfig() {
22
+ return {
23
+ ignore: [],
24
+ ignoreSeverity: [], // e.g., ["low", "medium"]
25
+ minSeverity: null, // e.g., "medium" - only show medium+ alerts
26
+ minScore: 0,
27
+ cache: true,
28
+ outputMode: 'normal' // normal, json, silent, ci
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Check if alert should be ignored based on config
34
+ */
35
+ function shouldIgnoreAlert(alert, config) {
36
+ // Check if package is in ignore list
37
+ if (config.ignore.includes(alert.package)) {
38
+ return true;
39
+ }
40
+
41
+ // Check if severity is ignored
42
+ if (config.ignoreSeverity.includes(alert.severity)) {
43
+ return true;
44
+ }
45
+
46
+ // Check minimum severity level
47
+ if (config.minSeverity) {
48
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
49
+ const minLevel = severityOrder[config.minSeverity];
50
+ const alertLevel = severityOrder[alert.severity];
51
+
52
+ if (alertLevel > minLevel) {
53
+ return true; // Alert is below minimum severity
54
+ }
55
+ }
56
+
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Filter alerts based on config
62
+ */
63
+ function filterAlerts(alerts, config) {
64
+ return alerts.filter(alert => !shouldIgnoreAlert(alert, config));
65
+ }
66
+
67
+ module.exports = {
68
+ loadConfig,
69
+ getDefaultConfig,
70
+ shouldIgnoreAlert,
71
+ filterAlerts
72
+ };
@@ -0,0 +1,33 @@
1
+ // src/utils/ci-handler.js
2
+ const chalk = require('chalk');
3
+
4
+ /**
5
+ * Handle CI mode - exit with error code if score below threshold
6
+ */
7
+ function handleCiMode(score, config, alerts, unusedDeps) {
8
+ const minScore = config.minScore || 7;
9
+
10
+ if (score.total < minScore) {
11
+ console.log(chalk.red(`\n❌ CI CHECK FAILED`));
12
+ console.log(chalk.red(`Health score ${score.total}/10 is below minimum ${minScore}/10\n`));
13
+
14
+ // Show critical issues
15
+ const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
16
+
17
+ if (criticalAlerts.length > 0) {
18
+ console.log(chalk.red(`Critical issues: ${criticalAlerts.length}`));
19
+ }
20
+
21
+ if (unusedDeps.length > 0) {
22
+ console.log(chalk.yellow(`Unused dependencies: ${unusedDeps.length}`));
23
+ }
24
+
25
+ process.exit(1); // Fail CI
26
+ } else {
27
+ console.log(chalk.green(`\n✅ CI CHECK PASSED`));
28
+ console.log(chalk.green(`Health score ${score.total}/10 meets minimum ${minScore}/10\n`));
29
+ process.exit(0);
30
+ }
31
+ }
32
+
33
+ module.exports = { handleCiMode };