deep-code-analysis.js
1 #!/usr/bin/env node 2 /** 3 * Automated Deep Code Analysis 4 * 5 * Performs comprehensive code analysis covering: 6 * - TODO.md review for outdated tasks 7 * - Stale documentation detection 8 * - Unused exports/imports identification 9 * - Technical debt assessment 10 * - Security vulnerability scanning 11 * 12 * Generates a detailed report with actionable recommendations. 13 */ 14 15 import { execSync } from 'child_process'; 16 import fs from 'fs'; 17 import path from 'path'; 18 import { fileURLToPath } from 'url'; 19 import { addReviewItem, initializeQueue } from '../src/utils/human-review-queue.js'; 20 21 const __filename = fileURLToPath(import.meta.url); 22 const __dirname = path.dirname(__filename); 23 24 const log = { 25 info: msg => console.log(`[INFO] ${msg}`), 26 success: msg => console.log(`[SUCCESS] ${msg}`), 27 warn: msg => console.log(`[WARN] ${msg}`), 28 error: msg => console.error(`[ERROR] ${msg}`), 29 }; 30 31 // Utility to run commands 32 function runCommand(command, options = {}) { 33 const { silent = false, ignoreError = false } = options; 34 35 try { 36 const output = execSync(command, { 37 encoding: 'utf8', 38 stdio: silent ? 'pipe' : 'inherit', 39 cwd: path.join(__dirname, '..'), 40 }); 41 return { success: true, output }; 42 } catch (error) { 43 if (!ignoreError) { 44 log.error(`Command failed: ${command}`); 45 } 46 return { success: false, error, output: error.stdout || '' }; 47 } 48 } 49 50 // Generate timestamp for reports 51 function getTimestamp() { 52 return new Date().toISOString().replace(/[:.]/g, '-').split('T')[0]; 53 } 54 55 // Suppress unhandled rejections from fire-and-forget DB writes (human-review-queue). 56 // PG connection timeouts are non-fatal for this analysis script — the report is already 57 // written to disk by the time these background writes attempt to connect. 58 process.on('unhandledRejection', err => { 59 const msg = err?.message || ''; 60 if (msg.includes('Connection terminated') || msg.includes('pool') || msg.includes('timeout')) { 61 // Non-fatal: background review queue write failed to connect to PG — report still written 62 return; 63 } 64 // Unexpected rejection: log with context but do not exit (let the main catch handle it) 65 console.error('[ERROR] Unhandled rejection in deep-code-analysis:', err); 66 }); 67 68 async function main() { 69 log.info('=== Automated Deep Code Analysis ==='); 70 log.info('Starting comprehensive analysis...\n'); 71 72 // Initialize human review queue (fire-and-forget — PG errors are non-fatal) 73 Promise.resolve(initializeQueue()).catch(err => 74 log.error(`Queue init skipped (non-fatal): ${err.message}`) 75 ); 76 77 const reportDir = path.join(__dirname, '..', '.analysis-reports'); 78 if (!fs.existsSync(reportDir)) { 79 fs.mkdirSync(reportDir, { recursive: true }); 80 } 81 82 const timestamp = getTimestamp(); 83 const reportPath = path.join(reportDir, `deep-analysis-${timestamp}.md`); 84 85 // Initialize report 86 const reportSections = []; 87 reportSections.push('# Deep Code Analysis Report'); 88 reportSections.push(`\nGenerated: ${new Date().toISOString()}\n`); 89 90 // 1. TODO.md Review 91 log.info('1. Reviewing TODO.md for outdated tasks...'); 92 reportSections.push('## 1. TODO.md Review\n'); 93 94 const todoPath = path.join(__dirname, '..', 'docs', 'TODO.md'); 95 if (fs.existsSync(todoPath)) { 96 const todoContent = fs.readFileSync(todoPath, 'utf8'); 97 const completedTasks = todoContent 98 .split('\n') 99 .filter(line => line.includes('✅') || line.includes('[x]')); 100 101 reportSections.push(`- Total completed tasks: ${completedTasks.length}`); 102 reportSections.push( 103 '- **Recommendation**: Review completed tasks and archive them to a separate CHANGELOG.md\n' 104 ); 105 106 // Flag for human review if many completed tasks 107 if (completedTasks.length > 10) { 108 addReviewItem({ 109 file: 'docs/TODO.md', 110 reason: `${completedTasks.length} completed tasks in TODO.md need archiving to CHANGELOG.md`, 111 type: 'maintenance', 112 priority: 'low', 113 }); 114 } 115 } else { 116 reportSections.push('- ⚠️ TODO.md not found\n'); 117 } 118 119 // 2. Stale Documentation Check 120 log.info('2. Checking for stale documentation...'); 121 reportSections.push('## 2. Stale Documentation Check\n'); 122 123 const docFiles = ['README.md', 'CLAUDE.md', 'docs/TODO.md', '.env.example']; 124 const staleThreshold = 30; // days 125 126 for (const docFile of docFiles) { 127 const filePath = path.join(__dirname, '..', docFile); 128 if (fs.existsSync(filePath)) { 129 const stats = fs.statSync(filePath); 130 const daysSinceModified = Math.floor((Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24)); 131 132 if (daysSinceModified > staleThreshold) { 133 reportSections.push(`- ⚠️ ${docFile}: Last modified ${daysSinceModified} days ago`); 134 135 // Flag critical docs for human review if very stale 136 if ( 137 ['README.md', 'CLAUDE.md', '.env.example'].includes(docFile) && 138 daysSinceModified > 60 139 ) { 140 addReviewItem({ 141 file: docFile, 142 reason: `Documentation is ${daysSinceModified} days old and may be outdated. Review for accuracy and update if needed.`, 143 type: 'documentation', 144 priority: daysSinceModified > 90 ? 'high' : 'medium', 145 }); 146 } 147 } else { 148 reportSections.push(`- ✅ ${docFile}: Recently updated (${daysSinceModified} days ago)`); 149 } 150 } else { 151 reportSections.push(`- ⚠️ ${docFile}: File not found`); 152 } 153 } 154 reportSections.push(''); 155 156 // 3. Unused Exports/Imports (via ESLint) 157 log.info('3. Scanning for unused exports/imports...'); 158 reportSections.push('## 3. Unused Code Detection\n'); 159 160 const lintResult = runCommand('npm run lint', { 161 silent: true, 162 ignoreError: true, 163 }); 164 165 if (lintResult.success) { 166 reportSections.push('- ✅ No lint errors detected\n'); 167 } else { 168 const output = lintResult.output || ''; 169 const unusedLines = output 170 .split('\n') 171 .filter(line => line.includes('no-unused-vars') || line.includes('unused-imports')); 172 173 if (unusedLines.length > 0) { 174 reportSections.push(`- ⚠️ Found ${unusedLines.length} potential unused variables/imports`); 175 reportSections.push('- **Recommendation**: Run `npm run lint:fix` to auto-fix\n'); 176 } else { 177 reportSections.push('- ✅ No obvious unused code detected\n'); 178 } 179 } 180 181 // 4. Technical Debt Assessment (via test coverage) 182 log.info('4. Assessing technical debt via test coverage...'); 183 reportSections.push('## 4. Technical Debt Assessment\n'); 184 185 const coveragePath = path.join(__dirname, '..', 'coverage', 'coverage-summary.json'); 186 if (fs.existsSync(coveragePath)) { 187 const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8')); 188 const totalCoverage = coverage.total; 189 190 // Guard against stale/incomplete coverage data where pct may be "Unknown" 191 const getPct = metric => (typeof metric?.pct === 'number' ? metric.pct : null); 192 const linesPct = getPct(totalCoverage.lines); 193 const branchesPct = getPct(totalCoverage.branches); 194 const functionsPct = getPct(totalCoverage.functions); 195 196 if (linesPct === null) { 197 reportSections.push('- ⚠️ Coverage data unavailable (run `npm test` to generate)\n'); 198 } else { 199 reportSections.push(`- Line coverage: ${linesPct.toFixed(1)}%`); 200 reportSections.push(`- Branch coverage: ${branchesPct?.toFixed(1) ?? 'N/A'}%`); 201 reportSections.push(`- Function coverage: ${functionsPct?.toFixed(1) ?? 'N/A'}%`); 202 203 if (linesPct < 70) { 204 reportSections.push('\n- ⚠️ **Critical**: Coverage below 70% threshold'); 205 reportSections.push('- **Recommendation**: Prioritize adding tests for uncovered code\n'); 206 207 // Flag for human review 208 addReviewItem({ 209 file: 'Test Coverage', 210 reason: `Test coverage is critically low at ${linesPct.toFixed(1)}% (target: 70%+). Review coverage report and prioritize adding tests for critical paths.`, 211 type: 'test', 212 priority: 'high', 213 }); 214 } else if (linesPct < 80) { 215 reportSections.push('\n- ⚠️ Coverage below 80% target'); 216 reportSections.push('- **Recommendation**: Continue improving test coverage\n'); 217 218 // Flag for human review (lower priority) 219 addReviewItem({ 220 file: 'Test Coverage', 221 reason: `Test coverage is ${linesPct.toFixed(1)}% (target: 80%+). Consider adding tests for uncovered code paths.`, 222 type: 'test', 223 priority: 'medium', 224 }); 225 } else { 226 reportSections.push('\n- ✅ Coverage meets target threshold\n'); 227 } 228 } // end else (linesPct !== null) 229 } else { 230 reportSections.push('- ⚠️ Coverage report not found. Run `npm test` first.\n'); 231 } 232 233 // 5. Security Vulnerability Scan 234 log.info('5. Running security vulnerability scan...'); 235 reportSections.push('## 5. Security Vulnerability Scan\n'); 236 237 const auditResult = runCommand('npm audit --json', { 238 silent: true, 239 ignoreError: true, 240 }); 241 242 if (auditResult.output) { 243 try { 244 const auditData = JSON.parse(auditResult.output); 245 const { vulnerabilities } = auditData; 246 247 if (vulnerabilities) { 248 const criticalCount = vulnerabilities.critical || 0; 249 const highCount = vulnerabilities.high || 0; 250 const moderateCount = vulnerabilities.moderate || 0; 251 const lowCount = vulnerabilities.low || 0; 252 253 reportSections.push(`- Critical vulnerabilities: ${criticalCount}`); 254 reportSections.push(`- High vulnerabilities: ${highCount}`); 255 reportSections.push(`- Moderate vulnerabilities: ${moderateCount}`); 256 reportSections.push(`- Low vulnerabilities: ${lowCount}`); 257 258 if (criticalCount > 0 || highCount > 0) { 259 reportSections.push('\n- 🚨 **URGENT**: Critical or high vulnerabilities detected'); 260 reportSections.push('- **Recommendation**: Run `npm audit fix` immediately\n'); 261 262 // Flag for immediate human review 263 addReviewItem({ 264 file: 'npm dependencies', 265 reason: `${criticalCount} critical and ${highCount} high severity vulnerabilities detected in npm dependencies. Run \`npm audit fix\` or review \`npm audit\` output for details.`, 266 type: 'security', 267 priority: criticalCount > 0 ? 'critical' : 'high', 268 }); 269 } else if (moderateCount > 0) { 270 reportSections.push('\n- ⚠️ Moderate vulnerabilities detected'); 271 reportSections.push('- **Recommendation**: Review and update affected packages\n'); 272 273 // Flag for human review (lower priority) 274 addReviewItem({ 275 file: 'npm dependencies', 276 reason: `${moderateCount} moderate severity vulnerabilities detected. Review \`npm audit\` output and update affected packages.`, 277 type: 'security', 278 priority: 'medium', 279 }); 280 } else { 281 reportSections.push('\n- ✅ No significant vulnerabilities detected\n'); 282 } 283 } else { 284 reportSections.push('- ✅ No vulnerabilities detected\n'); 285 } 286 } catch { 287 reportSections.push('- ⚠️ Could not parse audit output\n'); 288 } 289 } else { 290 reportSections.push('- ⚠️ Audit failed to run\n'); 291 } 292 293 // 6. Git Status Check 294 log.info('6. Checking git status...'); 295 reportSections.push('## 6. Git Repository Status\n'); 296 297 const statusResult = runCommand('git status --porcelain', { 298 silent: true, 299 ignoreError: true, 300 }); 301 302 if (statusResult.success && statusResult.output.trim() !== '') { 303 const modifiedFiles = statusResult.output.trim().split('\n').length; 304 reportSections.push(`- ⚠️ ${modifiedFiles} uncommitted changes detected`); 305 reportSections.push('- **Recommendation**: Review and commit pending changes\n'); 306 } else { 307 reportSections.push('- ✅ Working directory is clean\n'); 308 } 309 310 // 7. Summary and Recommendations 311 reportSections.push('## Summary\n'); 312 reportSections.push('This automated analysis has identified potential areas for improvement.'); 313 reportSections.push('Review the recommendations above and prioritize based on severity.\n'); 314 reportSections.push('### Quick Actions\n'); 315 reportSections.push('```bash'); 316 reportSections.push('# Fix linting issues'); 317 reportSections.push('npm run lint:fix'); 318 reportSections.push(''); 319 reportSections.push('# Run security audit'); 320 reportSections.push('npm audit fix'); 321 reportSections.push(''); 322 reportSections.push('# Update test coverage'); 323 reportSections.push('npm test'); 324 reportSections.push(''); 325 reportSections.push('# Update dependencies'); 326 reportSections.push('npm run deps:update'); 327 reportSections.push('```\n'); 328 329 // Write report 330 const reportContent = reportSections.join('\n'); 331 fs.writeFileSync(reportPath, reportContent); 332 333 log.success(`\nReport generated: ${reportPath}`); 334 log.info('\nPreview:'); 335 console.log(`\n${reportContent}`); 336 337 log.info('\n=== Analysis Complete ==='); 338 339 } 340 341 main().catch(error => { 342 log.error('Unexpected error:'); 343 console.error(error); 344 process.exit(1); 345 });