All files / src/report html-report.ts

100% Statements 17/17
100% Branches 8/8
100% Functions 3/3
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93                                  11x     11x   11x   11x                     11x           11x 10x                     11x 1x           1x           11x 2x         1x           11x             11x              
import { analyzeContext } from '../analyzer';
import { generateSummary } from '../summary';
import {
  generateReportHead,
  generateStatCards,
  generateTable,
  generateReportFooter,
} from '@aiready/core';
 
/**
 * Generate HTML report
 */
export function generateHTMLReport(
  summary: ReturnType<typeof generateSummary>,
  results: Awaited<ReturnType<typeof analyzeContext>>
): string {
  const totalIssues =
    summary.criticalIssues + summary.majorIssues + summary.minorIssues;
 
  // 'results' may be used in templates later; reference to avoid lint warnings
  void results;
 
  const head = generateReportHead('AIReady Context Analysis Report');
 
  const stats = generateStatCards([
    { value: summary.totalFiles, label: 'Files Analyzed' },
    { value: summary.totalTokens.toLocaleString(), label: 'Total Tokens' },
    { value: summary.avgContextBudget.toFixed(0), label: 'Avg Context Budget' },
    {
      value: totalIssues,
      label: 'Total Issues',
      color: totalIssues > 0 ? '#f39c12' : undefined,
    },
  ]);
 
  let body = `<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px;">
    <h1 style="border: none; color: white; margin: 0;">🔍 AIReady Context Analysis Report</h1>
    <p style="margin: 10px 0 0 0;">Generated on ${new Date().toLocaleString()}</p>
  </div>
${stats}`;
 
  if (totalIssues > 0) {
    body += `<div class="card" style="margin-bottom: 30px;">
    <h2>⚠️ Issues Summary</h2>
    <p>
      <span class="critical">🔴 Critical: ${summary.criticalIssues}</span> &nbsp;
      <span class="major">🟡 Major: ${summary.majorIssues}</span> &nbsp;
      <span class="minor">🔵 Minor: ${summary.minorIssues}</span>
    </p>
    <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
  </div>`;
  }
 
  if (summary.fragmentedModules.length > 0) {
    const fragmentedRows = summary.fragmentedModules.map((m) => [
      m.domain,
      String(m.files.length),
      `${(m.fragmentationScore * 100).toFixed(0)}%`,
      m.totalTokens.toLocaleString(),
    ]);
    body += `<div class="card" style="margin-bottom: 30px;">
    <h2>🧩 Fragmented Modules</h2>
    ${generateTable({ headers: ['Domain', 'Files', 'Fragmentation', 'Token Cost'], rows: fragmentedRows })}
  </div>`;
  }
 
  if (summary.topExpensiveFiles.length > 0) {
    const expensiveRows = summary.topExpensiveFiles.map((f) => [
      f.file,
      `${f.contextBudget.toLocaleString()} tokens`,
      `<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`,
    ]);
    body += `<div class="card" style="margin-bottom: 30px;">
    <h2>💸 Most Expensive Files</h2>
    ${generateTable({ headers: ['File', 'Context Budget', 'Severity'], rows: expensiveRows })}
  </div>`;
  }
 
  const footer = generateReportFooter({
    title: 'Context Analysis Report',
    packageName: 'context-analyzer',
    packageUrl: 'https://github.com/caopengau/aiready-context-analyzer',
    bugUrl: 'https://github.com/caopengau/aiready-context-analyzer/issues',
  });
 
  return `${head}
<body>
  ${body}
  ${footer}
</body>
</html>`;
}