# 审核时预生成修复 + 展开问题即显示 diff 日期:2026-08-22 状态:已批准 ## 目标 1. 审核时 AI 为三类问题(custom 规则问题、AI 深度审查发现、linter 无 autofix 诊断)预生成可直接应用的修复片段 `{ originalText, newText }`。 2. 点击面板"修复"按钮直接从报告取片段匹配应用,无需再调 LLM;匹配失败自动回退实时 AI 修复。 3. 面板展开问题时,直接以行级 `-/+` 对比显示修复前后代码(所有可修复问题,含 linter 自带 fix)。 4. `fixAll` 采用"预生成片段优先,失败回退实时"策略,不做偏移修正。 ## 数据流 ``` 审核(发现 + 预生成修复片段) → 面板 ├─ 点修复 → 直接匹配应用(免等待) → 失败 → 回退实时 LLM └─ 展开问题 → 行级 -/+ diff 立即渲染 ``` ## 关键决策 | 决策点 | 结论 | |---|---| | 预生成覆盖 | custom + AI findings + linter 无 autofix 诊断 | | 匹配失败 | 回退实时 AI 修复 | | 点击修复 | 保留 diff 预览确认,内容来自预生成片段 | | 展开 diff 形式 | 行级 -/+ 对比(红删/绿增) | | 展开 diff 范围 | 所有可修复问题(含 linter 自带 fix) | | fixAll 策略 | 预生成片段优先,失败回退实时,不做偏移修正 | | 兼容性 | `fix`/`aiFix` 字段可选,缺失则该问题无预生成修复/diff,行为不变 | ## 文件变更 | 文件 | 变更 | |---|---| | `src/types.ts` | 新增 `AiFixSnippet { originalText; newText }`;`LinterFix` 加 `originalText?`;`LinterDiagnostic` 加 `aiFix?: AiFixSnippet` | | `src/ai/schema.ts` | `CustomRuleResult`、`AIFinding`、`TranslatedDiagnostic` 加可选 `fix?: AiFixSnippet` | | `src/ai/engine.ts` | 三语 prompt(zh/en/ja)的 `customRuleResults`/`findings`/`translatedDiagnostics` 输出格式加 `fix` 字段(含"原文逐字、不含行号前缀"说明);`runAIReview`/`runMethodReview` 解析容错保留 `fix` | | `src/merger/merger.ts` | `MergeInput` 加 `code?`;映射 `staticDiagnostics` 时从 `code` 按 `fix.range` 切片补 `fix.originalText`;映射 `customRuleResults` 时带 `aiFix` | | `src/activation/commands.ts` | 所有 `mergeResults` 调用点传 `document.getText()` 作为 `code`;`refreshAfterFix` 重建时保留 `aiFix`;`findCustomIssue`/`findAIIssue`/`fixAll` 带 `fix` | | `src/fix/fixPrompt.ts` | `ReviewIssueInput` 加 `fix?: AiFixSnippet` | | `src/fix/customFixEngine.ts` | `aiFixReviewIssue` 开头尝试预生成片段直接应用,失败进实时循环 | | `src/fix/aiFixEngine.ts` | `aiFixDiagnostic` 开头尝试预生成片段直接应用 | | `src/utils/diff.ts`(新建) | 轻量行级 diff(LCS),返回 `{type:'del'\|'add'\|'same', text}[]` | | `src/panel/webview.ts` | 展开区渲染行级 diff(`.diff-del` 红 / `.diff-add` 绿 + CSS);三列表构造 detailHtml 时并入 diff;suggestion 与 diff 同时展示 | ## 核心逻辑 ### 预生成片段直接应用(两个修复引擎开头共用模式) ```ts const p = diag.fix ?? diag.aiFix; if (p?.originalText && p?.newText) { const i = currentText.indexOf(p.originalText); if (i !== -1) { const next = currentText.slice(0, i) + p.newText + currentText.slice(i + p.originalText.length); if (next !== currentText) { appliedFixes.push({ originalText: p.originalText, newText: p.newText, line }); return dryRun ? { success: true, appliedFixes, newText: next } : applyWorkspaceEdit(document, next, appliedFixes); } } } // 匹配失败 → 继续现有 for 循环(实时 LLM) ``` ### 展开 diff 渲染 ```ts // 提取每条问题的修复片段 linter: d.fix?.originalText + d.fix?.text // 或 d.aiFix custom: d.aiFix ai: f.fix // 生成行级 diff HTML diffHtml = computeLineDiff(originalText, newText) .map(l => `