devcompass 2.5.0 → 2.7.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.
@@ -26,6 +26,17 @@ const { loadConfig, filterAlerts } = require('../config/loader');
26
26
  const { getCached, setCache } = require('../cache/manager');
27
27
  const { formatAsJson } = require('../utils/json-formatter');
28
28
  const { handleCiMode } = require('../utils/ci-handler');
29
+
30
+ // NEW v2.7.0 imports
31
+ const { analyzeSupplyChain, getSupplyChainStats } = require('../analyzers/supply-chain');
32
+ const { analyzeLicenseRisks, getLicenseRiskScore } = require('../analyzers/license-risk');
33
+ const { analyzePackageQuality } = require('../analyzers/package-quality');
34
+ const {
35
+ generateSecurityRecommendations,
36
+ groupByPriority,
37
+ calculateExpectedImpact
38
+ } = require('../analyzers/security-recommendations');
39
+
29
40
  const packageJson = require('../../package.json');
30
41
 
31
42
  async function analyze(options) {
@@ -184,7 +195,7 @@ async function analyze(options) {
184
195
  }
185
196
  }
186
197
 
187
- // Check for predictive warnings (GitHub Issues) - UPDATED for v2.5.0
198
+ // Check for predictive warnings (GitHub Issues) - v2.6.0
188
199
  const { getTrackedPackageCount, TRACKED_REPOS } = require('../alerts/github-tracker');
189
200
  const totalTracked = getTrackedPackageCount();
190
201
 
@@ -192,12 +203,14 @@ async function analyze(options) {
192
203
  const installedTrackedCount = Object.keys(dependencies).filter(pkg => TRACKED_REPOS[pkg]).length;
193
204
 
194
205
  if (installedTrackedCount > 0) {
195
- spinner.text = `Checking GitHub activity (${installedTrackedCount}/${totalTracked} tracked packages)...`;
206
+ spinner.text = `Checking GitHub activity (0/${installedTrackedCount} packages)...`;
196
207
  } else {
197
208
  spinner.text = 'Checking GitHub activity...';
198
209
  }
199
210
 
200
211
  let predictiveWarnings = [];
212
+ let githubCheckTime = 0;
213
+ let githubData = [];
201
214
 
202
215
  if (config.cache) {
203
216
  predictiveWarnings = getCached(projectPath, 'predictive');
@@ -206,7 +219,31 @@ async function analyze(options) {
206
219
  if (!predictiveWarnings) {
207
220
  try {
208
221
  const { generatePredictiveWarnings } = require('../alerts/predictive');
209
- predictiveWarnings = await generatePredictiveWarnings(dependencies);
222
+ const { checkGitHubIssues } = require('../alerts/github-tracker');
223
+
224
+ // Track performance
225
+ const startTime = Date.now();
226
+
227
+ // Get raw GitHub data for quality analysis
228
+ githubData = await checkGitHubIssues(dependencies, {
229
+ concurrency: 5,
230
+ onProgress: (current, total, packageName) => {
231
+ if (outputMode === 'normal') {
232
+ spinner.text = `Checking GitHub activity (${current}/${total} packages) - ${packageName}`;
233
+ }
234
+ }
235
+ });
236
+
237
+ // Generate predictive warnings
238
+ predictiveWarnings = await generatePredictiveWarnings(dependencies, {
239
+ onProgress: (current, total, packageName) => {
240
+ if (outputMode === 'normal') {
241
+ spinner.text = `Checking GitHub activity (${current}/${total} packages) - ${packageName}`;
242
+ }
243
+ }
244
+ });
245
+
246
+ githubCheckTime = Date.now() - startTime;
210
247
 
211
248
  if (config.cache && predictiveWarnings.length > 0) {
212
249
  setCache(projectPath, 'predictive', predictiveWarnings);
@@ -260,6 +297,75 @@ async function analyze(options) {
260
297
  }
261
298
  }
262
299
 
300
+ // NEW v2.7.0 - Supply Chain Analysis
301
+ spinner.text = 'Analyzing supply chain security...';
302
+ let supplyChainWarnings = [];
303
+
304
+ if (config.cache) {
305
+ supplyChainWarnings = getCached(projectPath, 'supplyChain');
306
+ }
307
+
308
+ if (!supplyChainWarnings || supplyChainWarnings.length === 0) {
309
+ try {
310
+ supplyChainWarnings = await analyzeSupplyChain(projectPath, dependencies);
311
+ if (config.cache) {
312
+ setCache(projectPath, 'supplyChain', supplyChainWarnings);
313
+ }
314
+ } catch (error) {
315
+ if (outputMode !== 'silent') {
316
+ console.log(chalk.yellow('\n⚠️ Could not analyze supply chain'));
317
+ console.log(chalk.gray(` Error: ${error.message}\n`));
318
+ }
319
+ supplyChainWarnings = [];
320
+ }
321
+ }
322
+
323
+ // NEW v2.7.0 - Enhanced License Risk Analysis
324
+ spinner.text = 'Analyzing license risks...';
325
+ let licenseRiskData = { warnings: [], stats: {}, projectLicense: 'MIT' };
326
+
327
+ if (config.cache) {
328
+ const cached = getCached(projectPath, 'licenseRisk');
329
+ if (cached) licenseRiskData = cached;
330
+ }
331
+
332
+ if (!licenseRiskData.warnings || licenseRiskData.warnings.length === 0) {
333
+ try {
334
+ licenseRiskData = await analyzeLicenseRisks(projectPath, licenses);
335
+ if (config.cache) {
336
+ setCache(projectPath, 'licenseRisk', licenseRiskData);
337
+ }
338
+ } catch (error) {
339
+ if (outputMode !== 'silent') {
340
+ console.log(chalk.yellow('\n⚠️ Could not analyze license risks'));
341
+ console.log(chalk.gray(` Error: ${error.message}\n`));
342
+ }
343
+ }
344
+ }
345
+
346
+ // NEW v2.7.0 - Package Quality Analysis
347
+ spinner.text = 'Analyzing package quality...';
348
+ let qualityData = { results: [], stats: {} };
349
+
350
+ if (config.cache) {
351
+ const cached = getCached(projectPath, 'quality');
352
+ if (cached) qualityData = cached;
353
+ }
354
+
355
+ if (!qualityData.results || qualityData.results.length === 0) {
356
+ try {
357
+ qualityData = await analyzePackageQuality(dependencies, githubData);
358
+ if (config.cache) {
359
+ setCache(projectPath, 'quality', qualityData);
360
+ }
361
+ } catch (error) {
362
+ if (outputMode !== 'silent') {
363
+ console.log(chalk.yellow('\n⚠️ Could not analyze package quality'));
364
+ console.log(chalk.gray(` Error: ${error.message}\n`));
365
+ }
366
+ }
367
+ }
368
+
263
369
  // Calculate score
264
370
  const alertPenalty = calculateAlertPenalty(alerts);
265
371
  const securityPenalty = calculateSecurityPenalty(securityData.metadata);
@@ -275,17 +381,76 @@ async function analyze(options) {
275
381
 
276
382
  spinner.succeed(chalk.green(`Scanned ${totalDeps} dependencies in project`));
277
383
 
384
+ // Show performance info if GitHub check was performed
385
+ if (githubCheckTime > 0 && outputMode === 'normal' && installedTrackedCount > 0) {
386
+ const timeInSeconds = (githubCheckTime / 1000).toFixed(2);
387
+ console.log(chalk.gray(`⚡ GitHub check completed in ${timeInSeconds}s (parallel processing)`));
388
+ }
389
+
390
+ // NEW v2.7.0 - Generate Security Recommendations
391
+ const recommendations = generateSecurityRecommendations({
392
+ supplyChainWarnings,
393
+ licenseWarnings: licenseRiskData.warnings,
394
+ qualityResults: qualityData.results,
395
+ securityVulnerabilities: securityData.metadata,
396
+ ecosystemAlerts: alerts,
397
+ unusedDeps,
398
+ outdatedPackages: outdatedDeps
399
+ });
400
+
278
401
  // Handle different output modes
279
402
  if (outputMode === 'json') {
280
- const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
403
+ const jsonOutput = formatAsJson(
404
+ alerts,
405
+ unusedDeps,
406
+ outdatedDeps,
407
+ score,
408
+ totalDeps,
409
+ securityData,
410
+ bundleSizes,
411
+ licenses,
412
+ predictiveWarnings,
413
+ supplyChainWarnings,
414
+ licenseRiskData,
415
+ qualityData,
416
+ recommendations
417
+ );
281
418
  console.log(jsonOutput);
282
419
  } else if (outputMode === 'ci') {
283
- displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
420
+ displayResults(
421
+ alerts,
422
+ unusedDeps,
423
+ outdatedDeps,
424
+ score,
425
+ totalDeps,
426
+ securityData,
427
+ bundleSizes,
428
+ licenses,
429
+ predictiveWarnings,
430
+ supplyChainWarnings,
431
+ licenseRiskData,
432
+ qualityData,
433
+ recommendations
434
+ );
284
435
  handleCiMode(score, config, alerts, unusedDeps);
285
436
  } else if (outputMode === 'silent') {
286
437
  // Silent mode - no output
287
438
  } else {
288
- displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
439
+ displayResults(
440
+ alerts,
441
+ unusedDeps,
442
+ outdatedDeps,
443
+ score,
444
+ totalDeps,
445
+ securityData,
446
+ bundleSizes,
447
+ licenses,
448
+ predictiveWarnings,
449
+ supplyChainWarnings,
450
+ licenseRiskData,
451
+ qualityData,
452
+ recommendations
453
+ );
289
454
  }
290
455
 
291
456
  } catch (error) {
@@ -298,7 +463,21 @@ async function analyze(options) {
298
463
  }
299
464
  }
300
465
 
301
- function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings) {
466
+ function displayResults(
467
+ alerts,
468
+ unusedDeps,
469
+ outdatedDeps,
470
+ score,
471
+ totalDeps,
472
+ securityData,
473
+ bundleSizes,
474
+ licenses,
475
+ predictiveWarnings,
476
+ supplyChainWarnings = [],
477
+ licenseRiskData = {},
478
+ qualityData = {},
479
+ recommendations = []
480
+ ) {
302
481
  logDivider();
303
482
 
304
483
  // SECURITY VULNERABILITIES
@@ -331,6 +510,59 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
331
510
 
332
511
  logDivider();
333
512
 
513
+ // NEW v2.7.0 - SUPPLY CHAIN SECURITY
514
+ if (supplyChainWarnings.length > 0) {
515
+ const stats = getSupplyChainStats(supplyChainWarnings);
516
+
517
+ logSection('🛡️ SUPPLY CHAIN SECURITY', supplyChainWarnings.length);
518
+
519
+ // Group by type
520
+ const malicious = supplyChainWarnings.filter(w => w.type === 'malicious');
521
+ const typosquat = supplyChainWarnings.filter(w => w.type === 'typosquatting' || w.type === 'typosquatting_suspected');
522
+ const scripts = supplyChainWarnings.filter(w => w.type === 'install_script');
523
+
524
+ // Malicious packages (CRITICAL)
525
+ if (malicious.length > 0) {
526
+ log(chalk.red.bold('\n🔴 MALICIOUS PACKAGES DETECTED\n'));
527
+ malicious.forEach(w => {
528
+ log(` ${chalk.red.bold(w.package)}`);
529
+ log(` ${chalk.red(w.message)}`);
530
+ log(` ${chalk.yellow('→')} ${w.recommendation}\n`);
531
+ });
532
+ }
533
+
534
+ // Typosquatting (HIGH)
535
+ if (typosquat.length > 0) {
536
+ log(chalk.red('\n🟠 TYPOSQUATTING RISK\n'));
537
+ typosquat.forEach(w => {
538
+ const display = getSeverityDisplay(w.severity);
539
+ log(` ${display.emoji} ${chalk.bold(w.package)}`);
540
+ log(` Similar to: ${chalk.green(w.official)} (official package)`);
541
+ log(` ${chalk.yellow('→')} ${w.recommendation}\n`);
542
+ });
543
+ }
544
+
545
+ // Install scripts (MEDIUM/HIGH)
546
+ if (scripts.length > 0) {
547
+ log(chalk.yellow('\n🟡 INSTALL SCRIPT WARNINGS\n'));
548
+ scripts.slice(0, 3).forEach(w => {
549
+ log(` ${chalk.bold(w.package)}`);
550
+ log(` Script: ${chalk.gray(w.script)}`);
551
+ log(` Patterns: ${chalk.yellow(w.patterns.join(', '))}`);
552
+ log(` ${chalk.yellow('→')} ${w.recommendation}\n`);
553
+ });
554
+
555
+ if (scripts.length > 3) {
556
+ log(chalk.gray(` ... and ${scripts.length - 3} more install script warnings\n`));
557
+ }
558
+ }
559
+ } else {
560
+ logSection('✅ SUPPLY CHAIN SECURITY');
561
+ log(chalk.green(' No supply chain risks detected!\n'));
562
+ }
563
+
564
+ logDivider();
565
+
334
566
  // ECOSYSTEM ALERTS
335
567
  if (alerts.length > 0) {
336
568
  logSection('🚨 ECOSYSTEM ALERTS', alerts.length);
@@ -371,7 +603,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
371
603
 
372
604
  logDivider();
373
605
 
374
- // PREDICTIVE WARNINGS (UPDATED for v2.5.0)
606
+ // PREDICTIVE WARNINGS
375
607
  if (predictiveWarnings.length > 0) {
376
608
  const { getTrackedPackageCount } = require('../alerts/github-tracker');
377
609
  const totalTracked = getTrackedPackageCount();
@@ -404,6 +636,124 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
404
636
 
405
637
  logDivider();
406
638
 
639
+ // NEW v2.7.0 - LICENSE RISK ANALYSIS
640
+ if (licenseRiskData.warnings && licenseRiskData.warnings.length > 0) {
641
+ logSection('⚖️ LICENSE RISK ANALYSIS', licenseRiskData.warnings.length);
642
+
643
+ const { warnings, projectLicense } = licenseRiskData;
644
+
645
+ log(chalk.gray(` Project License: ${projectLicense}\n`));
646
+
647
+ // Group by severity
648
+ const critical = warnings.filter(w => w.severity === 'critical');
649
+ const high = warnings.filter(w => w.severity === 'high');
650
+ const medium = warnings.filter(w => w.severity === 'medium');
651
+
652
+ if (critical.length > 0) {
653
+ log(chalk.red.bold('🔴 CRITICAL LICENSE RISKS\n'));
654
+ critical.forEach(w => {
655
+ log(` ${chalk.red.bold(w.package)}`);
656
+ log(` License: ${chalk.red(w.license)}`);
657
+ log(` ${chalk.yellow(w.message)}`);
658
+ log(` ${chalk.cyan('→')} ${w.recommendation}\n`);
659
+ });
660
+ }
661
+
662
+ if (high.length > 0) {
663
+ log(chalk.red('\n🟠 HIGH RISK LICENSES\n'));
664
+ high.slice(0, 3).forEach(w => {
665
+ log(` ${chalk.bold(w.package)}`);
666
+ log(` License: ${chalk.yellow(w.license)}`);
667
+ log(` ${chalk.gray(w.message)}`);
668
+ log(` ${chalk.cyan('→')} ${w.recommendation}\n`);
669
+ });
670
+
671
+ if (high.length > 3) {
672
+ log(chalk.gray(` ... and ${high.length - 3} more high-risk licenses\n`));
673
+ }
674
+ }
675
+
676
+ if (medium.length > 0) {
677
+ log(chalk.gray(`\n ${medium.length} medium-risk licenses detected\n`));
678
+ }
679
+ } else {
680
+ logSection('✅ LICENSE COMPLIANCE');
681
+ log(chalk.green(' All licenses are compliant!\n'));
682
+ }
683
+
684
+ logDivider();
685
+
686
+ // NEW v2.7.0 - PACKAGE QUALITY METRICS
687
+ if (qualityData.results && qualityData.results.length > 0) {
688
+ const { results, stats } = qualityData;
689
+
690
+ logSection('📊 PACKAGE QUALITY METRICS', results.length);
691
+
692
+ // Healthy packages
693
+ if (stats.healthy > 0) {
694
+ log(chalk.green(`\n✅ HEALTHY PACKAGES (${stats.healthy})\n`));
695
+ const healthy = results.filter(r => r.status === 'healthy');
696
+ const display = healthy.slice(0, 10).map(r => r.package).join(', ');
697
+ log(chalk.gray(` ${display}${healthy.length > 10 ? '...' : ''}\n`));
698
+ }
699
+
700
+ // Needs attention
701
+ if (stats.needsAttention > 0) {
702
+ log(chalk.yellow(`\n🟡 NEEDS ATTENTION (${stats.needsAttention})\n`));
703
+ const attention = results.filter(r => r.status === 'needs_attention');
704
+ attention.slice(0, 3).forEach(r => {
705
+ log(` ${chalk.bold(r.package)}`);
706
+ log(` Health Score: ${chalk.yellow(r.healthScore + '/10')}`);
707
+ log(` Last Update: ${chalk.gray(r.daysSincePublish)} days ago`);
708
+ if (r.githubMetrics) {
709
+ log(` Open Issues: ${chalk.gray(r.githubMetrics.totalIssues)}`);
710
+ }
711
+ log('');
712
+ });
713
+ }
714
+
715
+ // Stale packages
716
+ if (stats.stale > 0) {
717
+ log(chalk.red(`\n🟠 STALE PACKAGES (${stats.stale})\n`));
718
+ const stale = results.filter(r => r.status === 'stale');
719
+ stale.slice(0, 3).forEach(r => {
720
+ log(` ${chalk.bold(r.package)}`);
721
+ log(` Health Score: ${chalk.red(r.healthScore + '/10')}`);
722
+ log(` Last Update: ${chalk.red(Math.floor(r.daysSincePublish / 30))} months ago`);
723
+ log(` ${chalk.cyan('→')} Consider finding actively maintained alternative\n`);
724
+ });
725
+ }
726
+
727
+ // Abandoned packages
728
+ if (stats.abandoned > 0) {
729
+ log(chalk.red.bold(`\n🔴 ABANDONED PACKAGES (${stats.abandoned})\n`));
730
+ const abandoned = results.filter(r => r.status === 'abandoned');
731
+ abandoned.forEach(r => {
732
+ log(` ${chalk.red.bold(r.package)}`);
733
+ log(` Health Score: ${chalk.red(r.healthScore + '/10')}`);
734
+ log(` Last Update: ${chalk.red(Math.floor(r.daysSincePublish / 365))} years ago`);
735
+ log(` Maintainer: ${chalk.red('Inactive')}`);
736
+ log(` ${chalk.cyan('→')} Migrate to actively maintained alternative\n`);
737
+ });
738
+ }
739
+
740
+ // Deprecated packages
741
+ if (stats.deprecated > 0) {
742
+ log(chalk.red.bold(`\n🔴 DEPRECATED PACKAGES (${stats.deprecated})\n`));
743
+ const deprecated = results.filter(r => r.status === 'deprecated');
744
+ deprecated.forEach(r => {
745
+ log(` ${chalk.red.bold(r.package)}`);
746
+ log(` ${chalk.red('Package is officially deprecated')}`);
747
+ log(` ${chalk.cyan('→')} Find alternative immediately\n`);
748
+ });
749
+ }
750
+ } else {
751
+ logSection('📊 PACKAGE QUALITY');
752
+ log(chalk.green(' Quality analysis in progress...\n'));
753
+ }
754
+
755
+ logDivider();
756
+
407
757
  // UNUSED DEPENDENCIES
408
758
  if (unusedDeps.length > 0) {
409
759
  logSection('🔴 UNUSED DEPENDENCIES', unusedDeps.length);
@@ -465,9 +815,9 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
465
815
  logDivider();
466
816
  }
467
817
 
468
- // LICENSE WARNINGS
818
+ // LICENSE WARNINGS (legacy - for packages not caught by license risk)
469
819
  const problematicLicenses = findProblematicLicenses(licenses);
470
- if (problematicLicenses.length > 0) {
820
+ if (problematicLicenses.length > 0 && (!licenseRiskData.warnings || licenseRiskData.warnings.length === 0)) {
471
821
  logSection('⚖️ LICENSE WARNINGS', problematicLicenses.length);
472
822
 
473
823
  problematicLicenses.forEach(pkg => {
@@ -478,13 +828,9 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
478
828
  });
479
829
 
480
830
  log(chalk.gray('\n Note: Restrictive licenses may require legal review\n'));
481
- } else {
482
- logSection('✅ LICENSE COMPLIANCE');
483
- log(chalk.green(' All licenses are permissive!\n'));
831
+ logDivider();
484
832
  }
485
833
 
486
- logDivider();
487
-
488
834
  // PROJECT HEALTH
489
835
  logSection('📊 PROJECT HEALTH');
490
836
 
@@ -496,6 +842,10 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
496
842
  log(` Security Vulnerabilities: ${chalk.red(securityData.metadata.total)}`);
497
843
  }
498
844
 
845
+ if (supplyChainWarnings.length > 0) {
846
+ log(` Supply Chain Warnings: ${chalk.red(supplyChainWarnings.length)}`);
847
+ }
848
+
499
849
  if (alerts.length > 0) {
500
850
  log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
501
851
  }
@@ -504,15 +854,114 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
504
854
  log(` Predictive Warnings: ${chalk.yellow(predictiveWarnings.length)}`);
505
855
  }
506
856
 
857
+ if (licenseRiskData.warnings && licenseRiskData.warnings.length > 0) {
858
+ log(` License Risks: ${chalk.yellow(licenseRiskData.warnings.length)}`);
859
+ }
860
+
861
+ if (qualityData.stats) {
862
+ const { abandoned, deprecated, stale } = qualityData.stats;
863
+ const total = (abandoned || 0) + (deprecated || 0) + (stale || 0);
864
+ if (total > 0) {
865
+ log(` Quality Issues: ${chalk.yellow(total)}`);
866
+ }
867
+ }
868
+
507
869
  log(` Unused: ${chalk.red(unusedDeps.length)}`);
508
870
  log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
509
871
 
510
872
  logDivider();
511
873
 
512
- // QUICK WINS
513
- displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData);
874
+ // NEW v2.7.0 - SECURITY RECOMMENDATIONS
875
+ if (recommendations.length > 0) {
876
+ displaySecurityRecommendations(recommendations, score.total);
877
+ } else {
878
+ displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData);
879
+ }
880
+ }
881
+
882
+ // NEW v2.7.0 - Display Security Recommendations
883
+ function displaySecurityRecommendations(recommendations, currentScore) {
884
+ const grouped = groupByPriority(recommendations);
885
+ const impact = calculateExpectedImpact(recommendations, currentScore);
886
+
887
+ logSection('💡 SECURITY RECOMMENDATIONS (Prioritized)');
888
+
889
+ // CRITICAL
890
+ if (grouped.critical.length > 0) {
891
+ log(chalk.red.bold('\n🔴 CRITICAL (Fix Immediately)\n'));
892
+ grouped.critical.forEach((rec, index) => {
893
+ log(` ${index + 1}. ${chalk.bold(rec.issue)}`);
894
+ if (rec.package) {
895
+ log(` Package: ${chalk.red(rec.package)}`);
896
+ }
897
+ log(` ${chalk.cyan('Action:')} ${rec.action}`);
898
+ if (rec.command) {
899
+ log(chalk.gray(` $ ${rec.command}`));
900
+ }
901
+ if (rec.alternative) {
902
+ log(chalk.gray(` ${rec.alternative}`));
903
+ }
904
+ log('');
905
+ });
906
+ }
907
+
908
+ // HIGH
909
+ if (grouped.high.length > 0) {
910
+ log(chalk.red('\n🟠 HIGH (Fix Soon)\n'));
911
+ grouped.high.slice(0, 5).forEach((rec, index) => {
912
+ log(` ${index + 1}. ${rec.issue}`);
913
+ if (rec.package) {
914
+ log(` Package: ${chalk.yellow(rec.package)}`);
915
+ }
916
+ log(` ${chalk.cyan('Action:')} ${rec.action}`);
917
+ if (rec.command) {
918
+ log(chalk.gray(` $ ${rec.command}`));
919
+ }
920
+ log('');
921
+ });
922
+
923
+ if (grouped.high.length > 5) {
924
+ log(chalk.gray(` ... and ${grouped.high.length - 5} more high-priority items\n`));
925
+ }
926
+ }
927
+
928
+ // MEDIUM
929
+ if (grouped.medium.length > 0) {
930
+ log(chalk.yellow('\n🟡 MEDIUM (Plan to Fix)\n'));
931
+ grouped.medium.slice(0, 3).forEach((rec, index) => {
932
+ log(` ${index + 1}. ${rec.issue}`);
933
+ if (rec.package) {
934
+ log(` Package: ${chalk.gray(rec.package)}`);
935
+ }
936
+ log(` ${chalk.cyan('Action:')} ${rec.action}`);
937
+ log('');
938
+ });
939
+
940
+ if (grouped.medium.length > 3) {
941
+ log(chalk.gray(` ... and ${grouped.medium.length - 3} more medium-priority items\n`));
942
+ }
943
+ }
944
+
945
+ // Expected Impact
946
+ log(chalk.cyan.bold('\n📈 Expected Impact:\n'));
947
+ log(` ${chalk.green('✓')} Current Health Score: ${chalk.yellow(impact.currentScore + '/10')}`);
948
+ log(` ${chalk.green('✓')} Expected Score: ${chalk.green(impact.expectedScore + '/10')}`);
949
+ log(` ${chalk.green('✓')} Improvement: ${chalk.cyan('+' + impact.improvement)} points (${impact.percentageIncrease}% increase)`);
950
+ log(` ${chalk.green('✓')} Issues Resolved: ${impact.critical + impact.high + impact.medium} critical/high/medium`);
951
+
952
+ if (grouped.critical.length > 0) {
953
+ log(` ${chalk.green('✓')} Eliminate ${impact.critical} critical security risks`);
954
+ }
955
+ if (grouped.high.length > 0) {
956
+ log(` ${chalk.green('✓')} Resolve ${impact.high} high-priority issues`);
957
+ }
958
+
959
+ log(chalk.cyan('\n💡 TIP: Run') + chalk.bold(' devcompass fix ') + chalk.cyan('to apply automated fixes!\n'));
960
+
961
+ logDivider();
514
962
  }
515
963
 
964
+ // Original Quick Wins (fallback)
516
965
  function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData) {
517
966
  const hasCriticalAlerts = alerts.some(a => a.severity === 'critical' || a.severity === 'high');
518
967
  const hasCriticalSecurity = securityData.metadata.critical > 0 || securityData.metadata.high > 0;