Files
2026Technology-Competition/docs/superpowers/specs/implementation-steps/14-phase4.6-report-export.md
T

4.1 KiB
Raw Blame History

Step 14 — Phase 4.6: 报告导出

依赖: Step 12Merger
参考设计: §14.2

目标

MergedReport 导出为 Markdown 格式报告。

新建文件

# 文件 说明
1 src/utils/report.ts reportToMarkdown()

src/utils/report.ts

import { MergedReport } from '../merger/merger';

function severityEmoji(severity: string): string {
  switch (severity) {
    case 'error': return '🔴';
    case 'warning': return '🟡';
    case 'info': return '🔵';
    default: return '⚪';
  }
}

function formatLine(line: number): string {
  return `L${line + 1}`;
}

export function reportToMarkdown(report: MergedReport): string {
  const lines: string[] = [];

  lines.push('# 代码审查报告');
  lines.push('');
  lines.push(`**文件:** \`${report.filePath}\``);
  lines.push(`**语言:** ${report.language}`);
  lines.push(`**耗时:** ${(report.duration / 1000).toFixed(1)}s`);
  if (report.adapterNames.length > 0) {
    lines.push(`**分析工具:** ${report.adapterNames.join(', ')}`);
  }
  if (report.degraded) {
    lines.push('');
    lines.push('> ⚠️ 部分 AI 功能不可用,报告已降级');
  }
  if (report.errors.length > 0) {
    lines.push('');
    lines.push('## 错误');
    for (const err of report.errors) {
      lines.push(`- ${err}`);
    }
  }

  lines.push('');
  lines.push('---');
  lines.push('');

  const total = report.linterCount + report.customRuleCount + report.aiCount;
  const errors = report.linterDiagnostics.filter(d => d.severity === 'error').length
    + report.customRuleDiagnostics.filter(d => d.severity === 'error').length
    + report.aiFindings.filter(f => f.severity === 'error').length;
  const warnings = report.linterDiagnostics.filter(d => d.severity === 'warning').length
    + report.customRuleDiagnostics.filter(d => d.severity === 'warning').length
    + report.aiFindings.filter(f => f.severity === 'warning').length;
  const infos = total - errors - warnings;

  lines.push(`**总计:** ${total} | **错误:** ${errors} | **警告:** ${warnings} | **建议:** ${infos}`);
  lines.push('');

  if (report.linterDiagnostics.length > 0) {
    lines.push(`## 🔧 静态分析 · ${report.linterCount} 个问题`);
    lines.push('');
    for (const diag of report.linterDiagnostics) {
      lines.push(`- ${severityEmoji(diag.severity)} \`${diag.ruleId}\` ${formatLine(diag.range.start.line)}`);
      lines.push(`  ${diag.message}`);
      if (diag.suggestion) {
        lines.push(`  建议: ${diag.suggestion}`);
      }
    }
    lines.push('');
  }

  if (report.customRuleDiagnostics.length > 0) {
    lines.push(`## 📋 自定义规则 · ${report.customRuleCount} 个问题`);
    lines.push('');
    for (const diag of report.customRuleDiagnostics) {
      lines.push(`- ${severityEmoji(diag.severity)} \`${diag.ruleId}\` ${formatLine(diag.range.start.line)}`);
      lines.push(`  ${diag.message}`);
    }
    lines.push('');
  }

  if (report.aiFindings.length > 0) {
    lines.push(`## 🤖 AI 审查 · ${report.aiCount} 条建议`);
    lines.push('');
    for (const finding of report.aiFindings) {
      lines.push(`- ${severityEmoji(finding.severity)} [AI] [${finding.category}] \`${finding.ruleId}\` ${formatLine(finding.line)}`);
      lines.push(`  **${finding.title}**`);
      lines.push(`  ${finding.description}`);
      if (finding.suggestion) {
        lines.push(`  建议: ${finding.suggestion}`);
      }
      if (finding.codeDiff) {
        lines.push('  ```diff');
        lines.push(`  ${finding.codeDiff.split('\n').join('\n  ')}`);
        lines.push('  ```');
      }
    }
    lines.push('');
  }

  if (total === 0) {
    lines.push('✅ 未发现问题');
    lines.push('');
  }

  return lines.join('\n');
}

关键逻辑

  • 报告格式与设计 §14.2 一致
  • 按来源分三个段:静态分析 / 自定义规则 / AI 审查
  • 统计卡片:总计 + 按严重级别拆分
  • AI 审查结果包含 category、codeDiffdiff 代码块)
  • 降级和错误信息在报告顶部单独显示

验收

  • 文件创建完成
  • npm run compile 通过
  • npm run lint 通过