devcompass 2.3.1 → 2.5.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.
@@ -1,54 +1,125 @@
1
1
  // src/alerts/predictive.js
2
+ const { checkGitHubIssues } = require('./github-tracker');
2
3
 
3
4
  /**
4
- * Analyze package health trends
5
- * NOTE: This is a simplified version without GitHub API
6
- * For production, integrate with GitHub Issues API
5
+ * Generate predictive warnings based on GitHub activity
6
+ * OPTIMIZED: Only checks packages that are actually installed
7
7
  */
8
- async function analyzeTrends(packageName) {
9
- // Placeholder for future GitHub API integration
10
- // For now, return basic analysis
11
-
12
- return {
13
- package: packageName,
14
- recentIssues: 0,
15
- trend: 'stable',
16
- recommendation: null
17
- };
8
+ async function generatePredictiveWarnings(packages) {
9
+ try {
10
+ // Only check packages that are actually installed
11
+ const installedPackages = Object.keys(packages);
12
+
13
+ if (installedPackages.length === 0) {
14
+ return [];
15
+ }
16
+
17
+ // Pass only installed packages to GitHub checker
18
+ const githubData = await checkGitHubIssues(packages);
19
+
20
+ const warnings = [];
21
+
22
+ for (const data of githubData) {
23
+ // High risk: many recent issues
24
+ if (data.riskScore >= 3) {
25
+ warnings.push({
26
+ package: data.package,
27
+ severity: 'high',
28
+ type: 'predictive',
29
+ title: 'High bug activity detected',
30
+ description: `${data.last7Days} new issues in last 7 days`,
31
+ recommendation: 'Consider delaying upgrade or monitoring closely',
32
+ data: {
33
+ totalIssues: data.totalIssues,
34
+ recentIssues: data.last7Days,
35
+ criticalIssues: data.criticalIssues,
36
+ trend: data.trend,
37
+ repoUrl: data.repoUrl
38
+ }
39
+ });
40
+ }
41
+
42
+ // Medium risk: increasing trend
43
+ else if (data.riskScore >= 2 || data.trend === 'increasing') {
44
+ warnings.push({
45
+ package: data.package,
46
+ severity: 'medium',
47
+ type: 'predictive',
48
+ title: 'Increased issue activity',
49
+ description: `${data.last7Days} issues opened recently`,
50
+ recommendation: 'Monitor for stability',
51
+ data: {
52
+ totalIssues: data.totalIssues,
53
+ recentIssues: data.last7Days,
54
+ criticalIssues: data.criticalIssues,
55
+ trend: data.trend,
56
+ repoUrl: data.repoUrl
57
+ }
58
+ });
59
+ }
60
+
61
+ // Low risk: stable but worth noting
62
+ else if (data.riskScore >= 1) {
63
+ warnings.push({
64
+ package: data.package,
65
+ severity: 'low',
66
+ type: 'predictive',
67
+ title: 'Minor issue activity',
68
+ description: `${data.last7Days} issues in last week`,
69
+ recommendation: 'No immediate action needed',
70
+ data: {
71
+ totalIssues: data.totalIssues,
72
+ recentIssues: data.last7Days,
73
+ trend: data.trend,
74
+ repoUrl: data.repoUrl
75
+ }
76
+ });
77
+ }
78
+ }
79
+
80
+ return warnings;
81
+ } catch (error) {
82
+ console.error('Error generating predictive warnings:', error.message);
83
+ return [];
84
+ }
18
85
  }
19
86
 
20
87
  /**
21
- * Calculate risk score based on trends
88
+ * Calculate risk score for a package
22
89
  */
23
- function calculateRiskScore(trends) {
24
- let risk = 0;
90
+ function calculateRiskScore(githubData) {
91
+ let score = 0;
25
92
 
26
- // High issue activity = higher risk
27
- if (trends.recentIssues > 20) {
28
- risk += 3;
29
- } else if (trends.recentIssues > 10) {
30
- risk += 2;
31
- } else if (trends.recentIssues > 5) {
32
- risk += 1;
33
- }
93
+ // High recent activity
94
+ if (githubData.last7Days > 20) score += 3;
95
+ else if (githubData.last7Days > 10) score += 2;
96
+ else if (githubData.last7Days > 5) score += 1;
97
+
98
+ // Critical issues
99
+ if (githubData.criticalIssues > 5) score += 2;
100
+ else if (githubData.criticalIssues > 2) score += 1;
34
101
 
35
- return risk;
102
+ // Trend
103
+ if (githubData.trend === 'increasing') score += 1;
104
+
105
+ return score;
36
106
  }
37
107
 
38
108
  /**
39
- * Generate predictive warnings
109
+ * Get statistics about predictive warnings
40
110
  */
41
- function generatePredictiveWarnings(packages) {
42
- const warnings = [];
43
-
44
- // This is a placeholder
45
- // In production, this would analyze GitHub activity
46
-
47
- return warnings;
111
+ function getPredictiveStats(warnings) {
112
+ return {
113
+ total: warnings.length,
114
+ high: warnings.filter(w => w.severity === 'high').length,
115
+ medium: warnings.filter(w => w.severity === 'medium').length,
116
+ low: warnings.filter(w => w.severity === 'low').length,
117
+ packages: warnings.map(w => w.package)
118
+ };
48
119
  }
49
120
 
50
121
  module.exports = {
51
- analyzeTrends,
122
+ generatePredictiveWarnings,
52
123
  calculateRiskScore,
53
- generatePredictiveWarnings
124
+ getPredictiveStats
54
125
  };
@@ -161,7 +161,7 @@ async function analyze(options) {
161
161
  }
162
162
  }
163
163
 
164
- // Check security vulnerabilities (NEW)
164
+ // Check security vulnerabilities
165
165
  spinner.text = 'Checking security vulnerabilities...';
166
166
  let securityData = { vulnerabilities: [], metadata: { total: 0, critical: 0, high: 0, moderate: 0, low: 0 } };
167
167
 
@@ -184,7 +184,43 @@ async function analyze(options) {
184
184
  }
185
185
  }
186
186
 
187
- // Analyze bundle sizes (NEW)
187
+ // Check for predictive warnings (GitHub Issues) - UPDATED for v2.5.0
188
+ const { getTrackedPackageCount, TRACKED_REPOS } = require('../alerts/github-tracker');
189
+ const totalTracked = getTrackedPackageCount();
190
+
191
+ // Count how many installed packages are tracked
192
+ const installedTrackedCount = Object.keys(dependencies).filter(pkg => TRACKED_REPOS[pkg]).length;
193
+
194
+ if (installedTrackedCount > 0) {
195
+ spinner.text = `Checking GitHub activity (${installedTrackedCount}/${totalTracked} tracked packages)...`;
196
+ } else {
197
+ spinner.text = 'Checking GitHub activity...';
198
+ }
199
+
200
+ let predictiveWarnings = [];
201
+
202
+ if (config.cache) {
203
+ predictiveWarnings = getCached(projectPath, 'predictive');
204
+ }
205
+
206
+ if (!predictiveWarnings) {
207
+ try {
208
+ const { generatePredictiveWarnings } = require('../alerts/predictive');
209
+ predictiveWarnings = await generatePredictiveWarnings(dependencies);
210
+
211
+ if (config.cache && predictiveWarnings.length > 0) {
212
+ setCache(projectPath, 'predictive', predictiveWarnings);
213
+ }
214
+ } catch (error) {
215
+ if (outputMode !== 'silent') {
216
+ console.log(chalk.yellow('\nāš ļø Could not check GitHub activity'));
217
+ console.log(chalk.gray(` Error: ${error.message}\n`));
218
+ }
219
+ predictiveWarnings = [];
220
+ }
221
+ }
222
+
223
+ // Analyze bundle sizes
188
224
  spinner.text = 'Analyzing bundle sizes...';
189
225
  let bundleSizes = [];
190
226
 
@@ -204,7 +240,7 @@ async function analyze(options) {
204
240
  }
205
241
  }
206
242
 
207
- // Check licenses (NEW)
243
+ // Check licenses
208
244
  spinner.text = 'Checking licenses...';
209
245
  let licenses = [];
210
246
 
@@ -224,7 +260,7 @@ async function analyze(options) {
224
260
  }
225
261
  }
226
262
 
227
- // Calculate score (UPDATED)
263
+ // Calculate score
228
264
  const alertPenalty = calculateAlertPenalty(alerts);
229
265
  const securityPenalty = calculateSecurityPenalty(securityData.metadata);
230
266
 
@@ -241,15 +277,15 @@ async function analyze(options) {
241
277
 
242
278
  // Handle different output modes
243
279
  if (outputMode === 'json') {
244
- const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
280
+ const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
245
281
  console.log(jsonOutput);
246
282
  } else if (outputMode === 'ci') {
247
- displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
283
+ displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
248
284
  handleCiMode(score, config, alerts, unusedDeps);
249
285
  } else if (outputMode === 'silent') {
250
286
  // Silent mode - no output
251
287
  } else {
252
- displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
288
+ displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
253
289
  }
254
290
 
255
291
  } catch (error) {
@@ -262,10 +298,10 @@ async function analyze(options) {
262
298
  }
263
299
  }
264
300
 
265
- function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses) {
301
+ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings) {
266
302
  logDivider();
267
303
 
268
- // SECURITY VULNERABILITIES (NEW SECTION)
304
+ // SECURITY VULNERABILITIES
269
305
  if (securityData.metadata.total > 0) {
270
306
  const criticalCount = securityData.metadata.critical;
271
307
  const highCount = securityData.metadata.high;
@@ -335,6 +371,39 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
335
371
 
336
372
  logDivider();
337
373
 
374
+ // PREDICTIVE WARNINGS (UPDATED for v2.5.0)
375
+ if (predictiveWarnings.length > 0) {
376
+ const { getTrackedPackageCount } = require('../alerts/github-tracker');
377
+ const totalTracked = getTrackedPackageCount();
378
+
379
+ logSection('šŸ”® PREDICTIVE WARNINGS', predictiveWarnings.length);
380
+
381
+ log(chalk.gray(` Based on recent GitHub activity (${totalTracked}+ packages monitored):\n`));
382
+
383
+ predictiveWarnings.forEach(warning => {
384
+ const display = getSeverityDisplay(warning.severity);
385
+
386
+ log(`${display.emoji} ${display.color(warning.package)}`);
387
+ log(` ${chalk.yellow(warning.title)}`);
388
+ log(` ${warning.description}`);
389
+ log(` ${chalk.cyan('→')} ${warning.recommendation}`);
390
+
391
+ if (warning.data && warning.data.repoUrl) {
392
+ log(chalk.gray(` GitHub: ${warning.data.repoUrl}`));
393
+ }
394
+
395
+ log('');
396
+ });
397
+ } else {
398
+ const { getTrackedPackageCount } = require('../alerts/github-tracker');
399
+ const totalTracked = getTrackedPackageCount();
400
+
401
+ logSection('āœ… PREDICTIVE ANALYSIS');
402
+ log(chalk.green(` No unusual activity detected (${totalTracked}+ packages monitored)!\n`));
403
+ }
404
+
405
+ logDivider();
406
+
338
407
  // UNUSED DEPENDENCIES
339
408
  if (unusedDeps.length > 0) {
340
409
  logSection('šŸ”“ UNUSED DEPENDENCIES', unusedDeps.length);
@@ -376,7 +445,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
376
445
 
377
446
  logDivider();
378
447
 
379
- // BUNDLE SIZE (NEW SECTION)
448
+ // BUNDLE SIZE
380
449
  const heavyPackages = findHeavyPackages(bundleSizes);
381
450
  if (heavyPackages.length > 0) {
382
451
  logSection('šŸ“¦ HEAVY PACKAGES', heavyPackages.length);
@@ -396,7 +465,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
396
465
  logDivider();
397
466
  }
398
467
 
399
- // LICENSE WARNINGS (NEW SECTION - ALWAYS SHOW)
468
+ // LICENSE WARNINGS
400
469
  const problematicLicenses = findProblematicLicenses(licenses);
401
470
  if (problematicLicenses.length > 0) {
402
471
  logSection('āš–ļø LICENSE WARNINGS', problematicLicenses.length);
@@ -431,6 +500,10 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
431
500
  log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
432
501
  }
433
502
 
503
+ if (predictiveWarnings.length > 0) {
504
+ log(` Predictive Warnings: ${chalk.yellow(predictiveWarnings.length)}`);
505
+ }
506
+
434
507
  log(` Unused: ${chalk.red(unusedDeps.length)}`);
435
508
  log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
436
509
 
@@ -3,11 +3,13 @@ const chalk = require('chalk');
3
3
  const ora = require('ora');
4
4
  const { execSync } = require('child_process');
5
5
  const readline = require('readline');
6
+ const path = require('path');
6
7
 
7
8
  const { findUnusedDeps } = require('../analyzers/unused-deps');
8
9
  const { findOutdatedDeps } = require('../analyzers/outdated');
9
10
  const { checkEcosystemAlerts } = require('../alerts');
10
11
  const { getSeverityDisplay } = require('../alerts/formatter');
12
+ const { clearCache } = require('../cache/manager');
11
13
 
12
14
  async function fix(options) {
13
15
  const projectPath = options.path || process.cwd();
@@ -22,7 +24,6 @@ async function fix(options) {
22
24
 
23
25
  try {
24
26
  const fs = require('fs');
25
- const path = require('path');
26
27
  const packageJsonPath = path.join(projectPath, 'package.json');
27
28
 
28
29
  if (!fs.existsSync(packageJsonPath)) {
@@ -52,7 +53,7 @@ async function fix(options) {
52
53
  spinner.succeed(chalk.green('Analysis complete!\n'));
53
54
 
54
55
  // Show what will be fixed
55
- await showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options);
56
+ await showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options, projectPath);
56
57
 
57
58
  } catch (error) {
58
59
  spinner.fail(chalk.red('Analysis failed'));
@@ -61,7 +62,7 @@ async function fix(options) {
61
62
  }
62
63
  }
63
64
 
64
- async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options) {
65
+ async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options, projectPath) {
65
66
  const actions = [];
66
67
 
67
68
  // Critical alerts
@@ -160,19 +161,19 @@ async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options) {
160
161
 
161
162
  // Confirm
162
163
  if (options.yes) {
163
- await applyFixes(actions);
164
+ await applyFixes(actions, projectPath);
164
165
  } else {
165
166
  const confirmed = await askConfirmation('\nā“ Apply these fixes?');
166
167
 
167
168
  if (confirmed) {
168
- await applyFixes(actions);
169
+ await applyFixes(actions, projectPath);
169
170
  } else {
170
171
  console.log(chalk.yellow('\nāš ļø Fix cancelled. No changes made.\n'));
171
172
  }
172
173
  }
173
174
  }
174
175
 
175
- async function applyFixes(actions) {
176
+ async function applyFixes(actions, projectPath) {
176
177
  console.log(chalk.cyan.bold('\nšŸ”§ Applying fixes...\n'));
177
178
 
178
179
  const spinner = ora('Processing...').start();
@@ -222,6 +223,11 @@ async function applyFixes(actions) {
222
223
  console.log(chalk.green.bold('\n✨ All fixes applied successfully!\n'));
223
224
  console.log(chalk.cyan('šŸ’” Run') + chalk.bold(' devcompass analyze ') + chalk.cyan('to see the new health score.\n'));
224
225
 
226
+ // Clear cache after fixes - ADDED
227
+ spinner.text = 'Clearing cache...';
228
+ clearCache(projectPath);
229
+ spinner.succeed(chalk.gray('Cache cleared'));
230
+
225
231
  } catch (error) {
226
232
  spinner.fail(chalk.red('Fix failed'));
227
233
  console.log(chalk.red(`\nāŒ Error: ${error.message}\n`));
@@ -244,4 +250,4 @@ function askConfirmation(question) {
244
250
  });
245
251
  }
246
252
 
247
- module.exports = { fix };
253
+ module.exports = { fix };
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Format analysis results as JSON
5
5
  */
6
- function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses) {
6
+ function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings = []) {
7
7
  const problematicLicenses = licenses.filter(l => l.type === 'restrictive' || l.type === 'unknown');
8
8
  const heavyPackages = bundleSizes.filter(p => p.size > 1024);
9
9
 
@@ -15,6 +15,7 @@ function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securi
15
15
  totalDependencies: totalDeps,
16
16
  securityVulnerabilities: securityData.metadata.total,
17
17
  ecosystemAlerts: alerts.length,
18
+ predictiveWarnings: predictiveWarnings.length,
18
19
  unusedDependencies: unusedDeps.length,
19
20
  outdatedPackages: outdatedDeps.length,
20
21
  heavyPackages: heavyPackages.length,
@@ -44,6 +45,14 @@ function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securi
44
45
  source: alert.source,
45
46
  reported: alert.reported
46
47
  })),
48
+ predictiveWarnings: predictiveWarnings.map(warning => ({
49
+ package: warning.package,
50
+ severity: warning.severity,
51
+ title: warning.title,
52
+ description: warning.description,
53
+ recommendation: warning.recommendation,
54
+ githubData: warning.data
55
+ })),
47
56
  unusedDependencies: unusedDeps.map(dep => ({
48
57
  name: dep.name
49
58
  })),