/ scripts / analyze-coverage.js
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');