feat: AI 修复预生成 + 审查面板 diff 预览两步确认 + SQLFluff 行号修复 + PMD 内置规则精简

- AI 修复预生成:静态/custom/AI 条目审查时预生成修复片段(originalText/newText),展开问题即显示行级 diff,无预生成时展示占位提示,匹配失败回退实时 LLM 生成

- 修复前 diff 预览:侧边新建编辑器组打开内置 diff,确认后写入(两步确认),不覆盖当前文件

- fix: sqlfluff 在 jinja 标签位于注释内时 JJ01 JSON 缺失 end_line_no/end_line_pos,适配器 Range 构造产生 NaN 被 VSCode 交换 start/end 导致行号 LNaN;新增 resolveSqlFluffRange 兜底 + 面板/report 行号 Number.isFinite 防御

- fix: 从 PMD 内置 ruleset 移除 5 条实际不可触发规则(AvoidAssertAsIdentifier、AvoidEnumAsIdentifier、AccessorClassGeneration、AccessorMethodGeneration、LoosePackageCoupling),内置配置 274→269 条全部可触发,同步 static-rules.json 与翻译脚本

- docs: README/DESIGN 更新
This commit is contained in:
范智鹏
2026-08-25 21:58:14 +08:00
parent 59e5fcde8c
commit 9c676e46e6
22 changed files with 445 additions and 86 deletions
@@ -0,0 +1,95 @@
# 审核时预生成修复 + 展开问题即显示 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` | 三语 promptzh/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 时并入 diffsuggestion 与 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 => `<div class="diff-${l.type}">${esc(l.text)}</div>`).join('')
```
### fixAll 策略
逐条串行处理:每条问题先尝试预生成片段在当前累积文本 `currentText` 上匹配,命中则直接应用(不调 LLM);失败或无片段则走现有实时修复。完成后保留批量 diff 预览 + 应用确认。
## 影响与风险
- 审核输出变长(三个来源都输出 fix),token/耗时略增——换取点修复零等待 + 展开即见 diff
- linter 原文片段依赖 `code` 全文切片;`refreshAfterFix` 需传当前文本,否则 diff 基于旧文本(匹配失败会回退,体验无损)
- 自写 diff 仅行级对比,不做字符级高亮
## 验证
- `npm run lint``npm run compile``npm test`
- 手动验证:custom / AI findings / linter 无 autofix 三条路径的"展开即见 diff"与"点修复免等待"
- 修改代码后再点修复应回退实时
- fixAll 批量场景多数问题秒过,个别失败回退实时