- AI 修复:无原生 fix 的 linter 条目走 AI 修复(aiFixEngine/fixPrompt,AI 重检收敛),自定义规则与 AI 审查条目新增 AI 修复按钮(customFixEngine),自定义规则条目展开显示 AI 建议(suggestion 字段)
- 修复预览:面板触发修复先用内置 diff 预览,面板内「应用/取消」两步确认后才写入(fixPreview/fixPending,单条与分 tab 批量均支持)
- 修复面板交互:linter/custom/ai 分 tab「全部修复」、已修复+撤销、pending 按钮状态同步
- bug 修复:修复按钮失败后卡 ⏳ 不恢复;custom/ai 修复不稳定(空修复重试、AI 重检收敛判定放宽、降级接受最后一次有效修复)
- 移除 AI codeDiff 展示块
12 KiB
12 KiB
修复前 Diff 预览确认功能设计书
日期:2026-08-19 状态:已批准(Stage ④ 通过);2026-08-19 修订确认交互为「面板内两步确认」 流程:① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批
1. 背景与目标
当前自动修复(src/fix/)在用户点击「修复 / AI 修复 / 全部修复」后立即写盘,用户无法在写入前确认改动内容,误修后只能依赖撤销功能回退。
本次功能目标:在面板触发的修复应用前,用 VSCode 内置 diff 编辑器展示「修改前 → 修改后」的代码对比,用户确认后才真正写入。
2. 需求澄清结论(Stage ② 共识)
| 决策项 | 结论 |
|---|---|
| 触发时机 | 修复前预览确认,确认后才写入 |
| 展示方式 | VSCode 内置 diff 编辑器(vscode.diff,左旧右新) |
| 预览范围 | 单个修复 + 全部修复都走预览 |
| 全部修复 | 合并成一个 diff,确认后一次性应用 |
| AI 修复 | 原生 fix 与 AI fix 两条路径都预览 |
| 入口范围 | 仅面板按钮预览;hover 快速修复保持直接应用(零行为变化) |
| 确认交互 | 面板内两步确认:点「修复」→ 打开 diff + 面板按钮变「应用/取消」(常驻不消失);回面板点「应用」才写入。「全部修复」同理(「全部应用/取消」) |
修订说明:初版设计为 diff +
showInformationMessage确认对话框,实测发现非模态通知几秒后自动消失(返回值 undefined 被当取消并关闭 diff)。改为面板内两步确认——按钮翻转用 webview 轻量消息完成,不全量重建面板,滚动/标签页不丢失。
3. 现状关键点(已核实)
fixEngine.ts:123-129与aiFixEngine.ts:164-170:多轮循环在内存字符串上计算最终currentText,最后一步才用整文档WorkspaceEdit写入。- 计算与写入天然可分,只需让引擎支持"只算不写"(
dryRun)即可在写入前弹 diff。 - 引擎内部已用
mockDocument对内存文本执行adapter.check,支持对累积文本反复计算,无需改核心算法。
4. 架构概览
面板 fix/fixAll 消息 → codeReviewer.fixIssue / codeReviewer.fixAll
│ (origin === 'panel')
▼
FixEngine/AiFixEngine (dryRun=true) → 返回最终 newText(不写盘)
│
▼
fixPreview.openPreviewDiff() → vscode.diff 内置 diff 编辑器(左原文本 / 右新文本)
│
▼
FixPendingStore.setSingle/setBatch + ReviewPanel.postMessage('pending'/'batchPending')
│ 面板条目按钮翻转:「修复」→「应用 / 取消」
▼
codeReviewer.applyFixPreview / applyAllPreview ← 面板「应用」按钮
│
▼
fixPreview.applyNewText() → 整文档 WorkspaceEdit 一次性写入 + recordFixes + save + refresh
codeReviewer.cancelFixPreview / cancelAllPreview → 关闭 diff + 还原按钮(不写入)
数据流:
fixIssue(panel)/fixAll以dryRun=true调用修复引擎,拿到最终newText,不写盘。openPreviewDiff用TextDocumentContentProvider(schemecodeReviewerPreview)提供新文本,vscode.diff打开内置 diff 编辑器。- 结果存入
FixPendingStore,向面板postMessage翻转按钮(pending单条 /batchPending全部)——「修复」隐藏、「应用/取消」显示,常驻不消失。 - 用户回面板点「应用」→
applyFixPreview/applyAllPreview整文档WorkspaceEdit写入、记录撤销会话、保存、刷新;点「取消」→cancelFixPreview/cancelAllPreview关闭 diff、还原按钮、不写入。
5. 文件变更清单
新建
| 文件 | 职责 |
|---|---|
src/fix/fixPreview.ts |
diff 内容提供者、openPreviewDiff、closePreviewEditor、applyNewText |
src/fix/fixPending.ts |
待确认修复存储(PendingFix / PendingBatch / FixPendingStore) |
docs/superpowers/specs/2026-08-19-fix-preview-design.md |
本设计书 |
修改
| 文件 | 变更 |
|---|---|
src/fix/fixEngine.ts |
FixResult 增 newText?: string;fixDiagnostic 增 dryRun 参数,为真时跳过写盘 |
src/fix/aiFixEngine.ts |
aiFixDiagnostic 增 dryRun 参数,为真时跳过写盘并返回 newText |
src/activation/commands.ts |
fixIssue panel 路径两步确认;fixAll 内存累积 + 合并 diff + 存 pending;新增 applyFixPreview/cancelFixPreview/applyAllPreview/cancelAllPreview 命令 |
src/panel/webview.ts |
消息类型增 applyFix/cancelFix/applyAll/cancelAll;条目与顶部渲染隐藏的应用/取消按钮;postMessage 公开方法 |
src/views/reviewPanel.js |
window.addEventListener('message') 翻转按钮显隐 |
src/extension.ts |
创建/传递 FixPendingStore;文档关闭时清 pending |
src/i18n/messages.ts |
新增 report.fixAllApply;复用 fix.apply / fix.cancel / fix.previewTitle |
6. 关键接口定义
6.1 FixResult(fixEngine.ts)
export interface FixResult {
success: boolean;
attempts: number;
message?: string;
appliedFixes: AppliedFix[];
newText?: string; // 新增:最终整文档文本(dryRun 时返回)
}
6.2 引擎 dryRun
export async function fixDiagnostic(
document: vscode.TextDocument,
workingDir: string,
adapter: LinterAdapter,
diag: LinterDiagnostic,
maxIterations: number,
dryRun?: boolean
): Promise<FixResult>;
export async function aiFixDiagnostic(
document, workingDir, adapter, diag, maxIterations,
provider, options,
dryRun?: boolean
): Promise<FixResult>;
dryRun=true:跳过末尾WorkspaceEdit与applyEdit,返回newText: currentText。dryRun=false(默认):保持现有写盘行为,不返回newText(或一并返回,调用方忽略)。- hover 入口仍走默认路径,行为零变化。
6.3 src/fix/fixPreview.ts
export class FixPreviewContentProvider implements vscode.TextDocumentContentProvider {
provideTextDocumentContent(uri: vscode.Uri): string;
set(uri: vscode.Uri, text: string): void;
clear(uri: vscode.Uri): void;
}
export interface PreviewRequest {
originalText: string;
newText: string;
title: string;
fileName: string;
}
export async function openPreviewDiff(req: PreviewRequest): Promise<vscode.Uri | undefined>;
// 打开内置 diff 编辑器,返回右侧 newUri(供后续关闭)
export async function closePreviewEditor(uri: vscode.Uri): Promise<void>;
// 关闭对应 diff 编辑器标签并清理 provider 内容
export async function applyNewText(document: vscode.TextDocument, newText: string): Promise<boolean>;
// 整文档 WorkspaceEdit 一次性写入(recordFixes 由调用方完成)
6.4 src/fix/fixPending.ts
export interface PendingFix {
key: string; // `${ruleId}@${line}`
ruleId: string;
line: number;
filePath: string;
originalText: string;
newText: string;
appliedFixes: AppliedFix[];
diffUri?: vscode.Uri;
}
export interface PendingBatch {
filePath: string;
originalText: string;
newText: string;
results: { ruleId: string; line: number; appliedFixes: AppliedFix[] }[];
diffUri?: vscode.Uri;
}
export class FixPendingStore {
setSingle(fix: PendingFix): void;
getSingle(filePath: string, key: string): PendingFix | undefined;
deleteSingle(filePath: string, key: string): void;
setBatch(batch: PendingBatch): void;
getBatch(filePath: string): PendingBatch | undefined;
deleteBatch(filePath: string): void;
clear(filePath: string): void;
}
6.5 面板消息协议
- 新增消息:
applyFix/cancelFix(带 line/ruleId)→codeReviewer.applyFixPreview/cancelFixPreview;applyAll/cancelAll→applyAllPreview/cancelAllPreview。 - 扩展 → 面板:
{ type: 'pending', key, on: boolean }(翻转单条按钮)、{ type: 'batchPending', on: boolean }(翻转顶部按钮)。
7. 核心逻辑
7.1 单个修复(fixIssue,panel origin)
document = resolveFixDocument(...)
diag = 命中诊断
result = resolveFix(..., dryRun=true) // 计算,不写盘
if !result.success → 提示失败,return
key = `${ruleId}@${line}`
existing = pendingStore.getSingle(filePath, key) // 重复预览先关旧 diff
diffUri = openPreviewDiff({ originalText, newText, title, fileName })
pendingStore.setSingle({ key, ruleId, line, filePath, originalText, newText, appliedFixes, diffUri })
ReviewPanel.postMessage({ type:'pending', key, on:true }) // 面板按钮 → 「应用 / 取消」
return // 不写盘
用户回面板:
- 点「应用」→
applyFixPreview:applyNewText(document, newText)→recordFixes→ 关 diff → 删 pending →save→refreshAfterFix→ 提示成功。 - 点「取消」→
cancelFixPreview:关 diff → 删 pending → postMessage 还原按钮。
7.2 全部修复(fixAll)
fixables = 可修复诊断列表(含 AI)
let currentText = document.getText()
results: { ruleId, line, appliedFixes }[] = []
for each fixable:
fresh = 对 currentText 构造 mock doc 重跑 check 得 freshDiag
result = fixDiagnostic / aiFixDiagnostic(dryRun=true)
if result.success: results.push(...); currentText = result.newText!
diffUri = openPreviewDiff({ originalText, newText: currentText, title, fileName })
pendingStore.setBatch({ filePath, originalText, newText, results, diffUri })
ReviewPanel.postMessage({ type:'batchPending', on:true }) // 顶部按钮 → 「全部应用 / 取消」
return // 不写盘
- 「全部应用」→
applyAllPreview:applyNewText(document, currentText)→ 逐条recordFixes→ 关 diff → 删 batch →save→refreshAfterFix→ 提示批量完成。 - 「取消全部」→
cancelAllPreview:关 diff → 删 batch → 还原按钮。
说明:多轮引擎已在内部对传入文本反复
adapter.check,因此 fixAll 只需把上一个 fix 的newText作为下一个的输入(用 mockDocument 包装为 TextDocument 再传引擎),即可得到合并后的最终文本;位置漂移由引擎内findClosestFixable按行号就近匹配处理。
7.3 diff 提供者
- 注册
vscode.workspace.registerTextDocumentContentProvider('codeReviewerPreview', provider)(extension.ts 调registerFixPreviewProvider一次)。 - 新文本 URI:
codeReviewerPreview://new/<fileName>-<timestamp>,内容即newText;原文本 URI 同样走 provider(避免脏文档时左屏显示磁盘旧内容与引擎计算不一致)。
7.4 面板按钮翻转(reviewPanel.js)
window.addEventListener('message', e => {
const msg = e.data;
if (msg.type === 'pending') {
// 按 data-fix-key 找到「修复 / 应用 / 取消」三个按钮,按 msg.on 显隐
} else if (msg.type === 'batchPending') {
// 按 data-fix-all-btn 找到「全部修复 / 全部应用 / 取消」三个按钮,按 msg.on 显隐
}
});
- 应用/取消按钮在 buildHtml 时默认
display:none,收到消息后显隐切换,不全量重建面板,滚动位置与标签页保持。
7.5 hover 入口保持不变
codeActionProvider.ts触发的fixIssue传origin: 'hover',命令内直接dryRun=false执行,不弹 diff、不弹确认框。
8. i18n 新增 key
| key | zh-CN | en | ja |
|---|---|---|---|
fix.previewTitle |
修复预览 | Fix preview | 修正プレビュー |
fix.apply |
应用 | Apply | 適用 |
fix.cancel |
取消 | Cancel | キャンセル |
report.fixAllApply |
全部应用 | Apply All | すべて適用 |
9. 配置变更
无新增配置。
10. 测试计划
- 现有
npm test(106 tests)回归通过。 - 引擎 dryRun 路径回归:原有 fixEngine/aiFixEngine 测试走默认(写盘)路径不受影响。
11. 验证顺序
lint → compile → test(npm run lint / npm run compile / npm test)