/ scripts / deep-code-analysis.js
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  });