analyze-coverage.js
1 #!/usr/bin/env node 2 3 /** 4 * Analyze code coverage and categorize files for Phase 0 remediation 5 * 6 * Categories: 7 * - Easy wins: <100 lines, <80% coverage, simple logic 8 * - Medium complexity: 100-300 lines, <80% coverage 9 * - High complexity: >300 lines, <80% coverage 10 * - Good coverage: >=80% coverage (no action needed) 11 */ 12 13 import fs from 'fs'; 14 import path from 'path'; 15 16 const coverageFile = './coverage/coverage-summary.json'; 17 18 if (!fs.existsSync(coverageFile)) { 19 console.error('ā Coverage file not found. Run: npm test'); 20 process.exit(1); 21 } 22 23 const coverage = JSON.parse(fs.readFileSync(coverageFile, 'utf8')); 24 25 // Skip total and non-src files 26 const srcFiles = Object.entries(coverage).filter( 27 ([file]) => 28 file !== 'total' && 29 (file.includes('/src/') || file.includes('/scripts/')) && 30 !file.includes('/tests/') && 31 !file.includes('node_modules') 32 ); 33 34 // Categorize files 35 const easyWins = []; 36 const medium = []; 37 const complex = []; 38 const good = []; 39 40 for (const [file, data] of srcFiles) { 41 const linePct = data.lines.pct; 42 const totalLines = data.lines.total; 43 const relativePath = file.replace(/^.*\/333Method\//, ''); 44 45 const fileInfo = { 46 file: relativePath, 47 lines: totalLines, 48 coverage: linePct, 49 uncovered: data.lines.total - data.lines.covered, 50 functions: data.functions.pct, 51 branches: data.branches.pct, 52 }; 53 54 if (linePct >= 80) { 55 good.push(fileInfo); 56 } else if (totalLines < 100) { 57 easyWins.push(fileInfo); 58 } else if (totalLines < 300) { 59 medium.push(fileInfo); 60 } else { 61 complex.push(fileInfo); 62 } 63 } 64 65 // Sort by uncovered lines (priority) 66 easyWins.sort((a, b) => b.uncovered - a.uncovered); 67 medium.sort((a, b) => b.uncovered - a.uncovered); 68 complex.sort((a, b) => b.uncovered - a.uncovered); 69 70 console.log('\nš Coverage Analysis for Phase 0 Remediation\n'); 71 console.log(`Overall: ${coverage.total.lines.pct.toFixed(1)}% coverage\n`); 72 73 console.log('šÆ EASY WINS (<100 lines, <80% coverage)'); 74 console.log('File'.padEnd(60), 'Lines', 'Coverage', 'Uncov'); 75 console.log('ā'.repeat(85)); 76 easyWins.forEach(f => { 77 console.log( 78 f.file.padEnd(60), 79 f.lines.toString().padEnd(5), 80 `${f.coverage.toFixed(1)}%`.padEnd(8), 81 f.uncovered 82 ); 83 }); 84 console.log(`\nTotal: ${easyWins.length} files\n`); 85 86 console.log('š MEDIUM COMPLEXITY (100-300 lines, <80% coverage)'); 87 console.log('File'.padEnd(60), 'Lines', 'Coverage', 'Uncov'); 88 console.log('ā'.repeat(85)); 89 medium.slice(0, 15).forEach(f => { 90 console.log( 91 f.file.padEnd(60), 92 f.lines.toString().padEnd(5), 93 `${f.coverage.toFixed(1)}%`.padEnd(8), 94 f.uncovered 95 ); 96 }); 97 if (medium.length > 15) { 98 console.log(`... and ${medium.length - 15} more`); 99 } 100 console.log(`\nTotal: ${medium.length} files\n`); 101 102 console.log('š„ HIGH COMPLEXITY (>300 lines, <80% coverage)'); 103 console.log('File'.padEnd(60), 'Lines', 'Coverage', 'Uncov'); 104 console.log('ā'.repeat(85)); 105 complex.forEach(f => { 106 console.log( 107 f.file.padEnd(60), 108 f.lines.toString().padEnd(5), 109 `${f.coverage.toFixed(1)}%`.padEnd(8), 110 f.uncovered 111 ); 112 }); 113 console.log(`\nTotal: ${complex.length} files\n`); 114 115 console.log(`ā GOOD COVERAGE (>=80%):`); 116 console.log(`Total: ${good.length} files\n`); 117 118 // Calculate impact 119 const totalUncovered = coverage.total.lines.total - coverage.total.lines.covered; 120 const easyWinUncovered = easyWins.reduce((sum, f) => sum + f.uncovered, 0); 121 const mediumUncovered = medium.reduce((sum, f) => sum + f.uncovered, 0); 122 const complexUncovered = complex.reduce((sum, f) => sum + f.uncovered, 0); 123 124 console.log('š ESTIMATED IMPACT'); 125 console.log(`Total uncovered lines: ${totalUncovered}`); 126 console.log( 127 `Easy wins cover: ${easyWinUncovered} lines (${((easyWinUncovered / totalUncovered) * 100).toFixed(1)}%)` 128 ); 129 console.log( 130 `Medium cover: ${mediumUncovered} lines (${((mediumUncovered / totalUncovered) * 100).toFixed(1)}%)` 131 ); 132 console.log( 133 `Complex cover: ${complexUncovered} lines (${((complexUncovered / totalUncovered) * 100).toFixed(1)}%)` 134 ); 135 136 // Recommendations 137 console.log('\nš” RECOMMENDATIONS'); 138 console.log( 139 `1. Focus on easy wins first (${easyWins.length} files, ${((easyWinUncovered / totalUncovered) * 100).toFixed(1)}% of gap)` 140 ); 141 console.log(`2. Use QA agent to generate tests for easy wins`); 142 console.log(`3. Manually write tests for medium complexity files`); 143 console.log(`4. Consider excluding or splitting high complexity files`); 144 145 // Export categorized lists for QA agent 146 const output = { 147 timestamp: new Date().toISOString(), 148 overall_coverage: coverage.total.lines.pct, 149 target: 80, 150 gap: 80 - coverage.total.lines.pct, 151 easy_wins: easyWins, 152 medium: medium.slice(0, 10), // Top 10 by impact 153 complex, 154 summary: { 155 total_files: srcFiles.length, 156 easy_wins: easyWins.length, 157 medium: medium.length, 158 complex: complex.length, 159 good: good.length, 160 }, 161 }; 162 163 fs.writeFileSync('./.analysis-reports/coverage-phase0.json', JSON.stringify(output, null, 2)); 164 console.log('\nā Analysis saved to .analysis-reports/coverage-phase0.json');