feat: AI 修复链路扩展 + 修复预览两步确认 + 自定义规则/AI 审查条目支持 AI 修复

- 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 展示块
This commit is contained in:
范智鹏
2026-08-20 22:26:30 +08:00
parent 3d8119d9c9
commit d16d680f0f
20 changed files with 1655 additions and 85 deletions
+7
View File
@@ -212,3 +212,10 @@
| 2026-08-18 20:28 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复审查面板点击「修复」无反应:根因是 webview.ts 审查面板用内联 <script>,被 VS Code 默认 CSP 屏蔽(与 setupView 2026-07-15 历史 bug 相同),send/switchTab/toggleItem 全部未定义——修复按钮点击到 this.textContent='⏳...' 后 send() 抛 ReferenceError 消息从未发到扩展;症状为按钮卡 ⏳、无弹窗、代码不变,且 L1 行号跳转/tab 切换同样失效(用户确认)。修复:新建 src/views/reviewPanel.js(提取 send/switchTab/toggleItemcurly 花括号写法过 lint);webview.ts 面板 options localResourceRoots 改 [extensionUri]、构造函数 asWebviewUri 计算 scriptUri 存字段、buildHtml 内联 script 改 <script src>copy-webview-js.mjs 与 build.mjs 的 webview 拷贝 files 数组加 reviewPanel.jscompile 与生产打包均拷到 out/webview/)。防御性改进:commands.ts fixIssue/fixAll 主体包 try/catch + console.error + showErrorMessage,静默 return 处补 console.log 便于排查。npm test 99 passing / lint 0 error / compile 通过,out/webview/reviewPanel.js 已生成 | 中间产物:根因排查多轮——先疑 fixDiagnostic 静默返回/挂起,再疑 fixSession 缓存缺失,最后用户确认「L1 跳转也失效」锁定 webview 内联 JS 被 CSP 屏蔽;reviewPanel.js 初版单行 if 无花括号会被 eslint curly 警告,改花括号 | src/views/reviewPanel.js(新建) src/panel/webview.ts src/activation/commands.ts scripts/copy-webview-js.mjs scripts/build.mjs | deepseek-v4-flash |
| 2026-08-18 20:58 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复面板点「修复」必须先聚焦文件才生效:根因是两层活动编辑器依赖——commands.ts fixIssue/fixAll/undoFix 用 vscode.window.activeTextEditor 取文档,面板有焦点时活动编辑器未必是审查文件;fixEngine.ts fixDiagnostic 应用修复前又强制校验 activeTextEditor 必须等于目标文档(no-active-editor 分支)。改动:commands.ts 新增 resolveFixDocument(report, active, origin)——hover 用活动编辑器、面板(panel/fixAll/undo)用 currentReport.filePath 在 vscode.workspace.textDocuments 中定位文档、无报告回退活动编辑器;fixIssue/fixAll/undoFix 全部改用它取 documentfixEngine.ts 删除 no-active-editor 强校验分支直接 applyEditWorkspaceEdit 对任何打开文档生效无需焦点);webview.ts handleMessage 改 asyncnavigate 用 currentReport 的 Uri 经 showTextDocument({preview:true}) 打开/跳转。npm test 99 passing / lint 0 error / compile 通过 | 中间产物:resolveFixDocument 面板分支初版带「报告文件未打开时回退活动编辑器」会被误修当前活动文件,改为面板来源仅用报告文件、找不到返回 undefined 记日志;fixEngine.test.ts 仍保留 no-active-editor 断言分支但该路径已不再产生(不影响) | src/activation/commands.ts src/fix/fixEngine.ts src/panel/webview.ts | deepseek-v4-flash |
| 2026-08-18 21:28 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 三个问题合并修复:①修复后不保存文件——commands.ts fixIssue 成功、fixAll 循环结束、undoFix 成功后各加 await document.save() 自动写盘。②点第一个修复连带修相邻同规则问题——根因 fixEngine.ts issueStillExists 用 ±2 行窗口把相邻实例误判为同一问题;改为按修复后文本区域重叠判断(重 lint 诊断的 fix.range 是否与被修复区间 [start, start+fix.text.length] 重叠),不重叠即另一处实例停止,只修被点击那一处;findClosestFixable/prevLine 保留用于定位目标。③并排窗口点行号在面板列新开文件副本——根因 webview.ts navigate 的 showTextDocument({preview:true}) 未指定列,默认在活动列(面板所在列)打开;改为先从 vscode.window.visibleTextEditors 找该文件已打开的编辑器取其 viewColumn 传入 showTextDocument,未打开才 preview:true 打开;修复 TS2531 用局部变量 report 收窄 currentReport。fixEngine.test.ts 新增用例(相邻两行 no-var 点第一个只产出 1 个 appliedFix)。npm test 100 passing / lint 0 error / compile 通过 | 中间产物:webview navigate 编译报 TS2531find 回调内 this.currentReport 未收窄)→ 局部变量 const report=this.currentReport 解决 | src/activation/commands.ts src/fix/fixEngine.ts src/panel/webview.ts src/test/fixEngine.test.ts | deepseek-v4-flash |
| 2026-08-18 22:07 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 静态分析修复能力扩展(AI 修复 fallback):为无原生 fix 的静态分析条目(PMD/ESLint·Stylelint 不可自动修项)增加 AI 修复。新建 src/fix/aiFixEngine.ts——aiFixDiagnostic:按规则类别取 ±6 行上下文(含已有 suggestion 作为参考建议注入 prompt,三语系统提示词,AI 返回 {originalText,newText})→ 全文 indexOf 匹配 → 替换 → adapter.check 重 lint 用「修复区域重叠」验证收敛(≤maxIterations,不消除带反馈重试)→ 单次 WorkspaceEdit 提交,复用 FixResult/AppliedFix(撤销、自动保存链路原样生效)。merger.tsMergeInput/MergedReport 增 aiFixAvailable,新增 aiFixableLinterIndices(无 native fix 且 aiFixAvailable 且 ruleId 非 sqlfluff: 前缀)。commands.ts:新增 createFixProvider(复用 ai/factory+ resolveFixdiag.fix→native,否则 AIAI 未配置→ai-unavailable);review 计算 aiFixAvailable 传入 mergeResultsrefreshAfterFix 透传;fixIssue 无 fix 时 withProgress 走 AI 并区分报错(fix.noAI/fix.aiFailed);fixAll 的 fixables 扩展为 native+AI 可修项、freshDiag 按 ruleId+line 匹配。webview.tslinter 列表按 fixable/aiFixable 分渲染 🔧 修复 / 🤖 AI 修复按钮,fixAll 按钮条件含 aiFixable。i18n 新增 fix.aiRunning/aiFailed/noAI 与 report.fixAILabel 三语。测试:ai-fix-engine.test.ts 4 用例(mock provider+adapter:成功收敛/匹配失败/空修复/重试收敛)+ merger 2 用例。SQLFluff/JSP 本期排除。npm test 106 passing / lint 0 error / compile 通过 | 中间产物:AI 修复验证收敛初版考虑「按目标行 ±3 窗口」会误判相邻实例为未消除导致再次修复(重蹈 over-fix),最终沿用 native 的修复区域重叠判断;fixAll freshDiag 初版保留 ?? diag 回退会重修已消失问题,改为查不到即 skipped++ | src/fix/aiFixEngine.ts(新建) src/merger/merger.ts src/activation/commands.ts src/panel/webview.ts src/i18n/messages.ts src/test/ai-fix-engine.test.ts(新建) src/test/merger.test.ts | deepseek-v4-flash |
| 2026-08-19 21:29 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复前 Diff 预览确认功能:面板触发的修复应用前用 VSCode 内置 diff 编辑器展示修改前后对比,确认后才写入。fixEngine.ts/aiFixEngine.tsFixResult 增 newText?fixDiagnostic/aiFixDiagnostic 增 dryRun 参数(为真时跳过 WorkspaceEdit 返回 newText)。新建 src/fix/fixPreview.tsTextDocumentContentProvider(scheme codeReviewerPreviewdiff 两侧均走 provider 避免脏文档左屏显示磁盘旧内容) + previewAndConfirmvscode.diff 打开内置 diff + showInformationMessage 应用/取消对话框,选完统一关 diff 编辑器再清理 provider+ applyNewText(整文档 WorkspaceEdit)。commands.tsresolveFix 透传 dryRunfixIssue 面板 origin 走 dryRun→diff 预览→确认后 applyNewText+recordFixeshover 保持直接应用零变化;fixAll 改为内存累积(mockDocument 逐条 dryRun 计算 currentText 累积)→合并单 diff 预览→确认后一次性 applyNewText+逐条 recordFixes(原逐个写盘+多次刷新改为一次写入)。i18n 新增 fix.confirmApply/fix.apply/fix.cancel/fix.previewTitle 三语。extension.ts 注册 registerFixPreviewProvider。specdocs/superpowers/specs/2026-08-19-fix-preview-design.md。npm test 106 passing / lint 0 error(仅 mockDocument.ts 2 个既有 curly warning/ compile 通过 | 中间产物:①applyNewText 初版签名含 fixSession/ruleId/line/appliedFixes/record 五参(把记录职责内聚),实现时发现 fixAll 需逐条 recordFixes、单条预览也需在 diff 模块外记录,简化为仅 (document,newText) 纯写入,记录回归调用方;②fixAll 的 results 类型初版写成 typeof fixSession extends never 类型体操编译报错,改为显式 { ruleId; line; appliedFixes: AppliedFix[] }[] 并补 AppliedFix import;③自审查发现修复 bug——预览路径(isPreview)下原 recordFixes 条件 (origin!=='hover' && !isPreview) 恒假,撤销会话丢失,改为 if(isPreview){...记录} else if(origin!=='hover'){...记录};④freshDiag 行号匹配初版写 Math.abs(diff)<=0 语义即严格相等,简化为 ===;⑤previewAndConfirm 初版仅取消时关 diff,应用时残留 diff 编辑器会因 provider.clear 变空白,改为无论应用/取消都先 closePreviewEditors 再 clear | docs/superpowers/specs/2026-08-19-fix-preview-design.md(新建) src/fix/fixPreview.ts(新建) src/fix/fixEngine.ts src/fix/aiFixEngine.ts src/activation/commands.ts src/extension.ts src/i18n/messages.ts | deepseek-v4-flash |
| 2026-08-20 19:21 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复实测问题:确认按钮不保持。实测发现 previewAndConfirm 用 showInformationMessage({modal:false}) 的右下角通知几秒后自动消失(返回 undefined 被当取消并关闭 diff)。经用户决策改为「面板内两步确认」:新建 src/fix/fixPending.tsPendingFix/PendingBatch/FixPendingStore 待确认存储,按 filePath+key 索引,含 diffUri);fixPreview.ts 删 previewAndConfirm,新增 openPreviewDiff(返回 newUri/closePreviewEditor(关 diff 标签+清理 provider/applyNewText 简化;commands.ts 新增 4 命令 applyFixPreview/cancelFixPreview/applyAllPreview/cancelAllPreview(应用=写入+recordFixes+关 diff+删 pending+save+refresh;取消=关 diff+删 pending+postMessage 还原按钮),fixIssue panel 路径只存 pending+开 diff+postMessage 翻转按钮,fixAll 只存 batch+开 diff+postMessagereview 与文档关闭时 pendingStore.clearwebview.ts PanelMessage 增 applyFix/cancelFix/applyAll/cancelAllbuildIssueItem 对可修条目渲染隐藏的 btn-apply/btn-canceldata-fix-key=ruleId@line),buildLinterList 顶部渲染隐藏 btn-apply-all/btn-cancel-allhandleMessage 路由到新命令,ReviewPanel 增公开 postMessage 方法;reviewPanel.js 增 window message 监听按 key/class 翻转按钮显隐(不全量重建面板,滚动/标签页保持);extension.ts 创建/传递 FixPendingStorei18n 新增 report.fixAllApply 三语(复用 fix.apply/fix.cancel/fix.previewTitle)。spec 同步更新确认交互为两步确认。npm test 106 passing / lint 0 error / compile 通过。问题2(diff 左右结构)按用户要求暂缓 | 中间产物:①初版考虑 modal:true 居中模态框(按钮保持但挡 diff)用户弃选改面板两步确认;②ReviewPanel.currentPanel.webview 私有属性 4 处编译报错 TS2339,加公开 postMessage() 方法并用 PowerShell 批量替换调用点;③reviewPanel.js 翻转初版用 inline-flex 恢复显示,CSS 无此值改回清空 display 由默认样式接管 | src/fix/fixPending.ts(新建) src/fix/fixPreview.ts src/activation/commands.ts src/panel/webview.ts src/views/reviewPanel.js src/extension.ts src/i18n/messages.ts docs/superpowers/specs/2026-08-19-fix-preview-design.md | deepseek-v4-flash |
| 2026-08-20 19:47 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 自定义规则条目支持展开显示 AI 修复建议:用户反馈自定义规则(custom tab)条目不可展开(无 suggestion、无修复意见),linter 条目可展开带 AI 建议。澄清后确定为「展开显示 AI 建议文本(💡)」,不加 AI 修复按钮。改动:schema.ts CustomRuleResult 加 suggestion?: stringengine.ts buildCustomRuleSystemPrompt(完整审查 zh/en/ja 三段)输出 JSON 格式加 "suggestion" 并要求每条违规必给可执行建议,buildMethodSystemPromptEn/Zh/Ja(方法审查)customRuleResults 输出段加 "suggestion"runAIReview/runMethodReview 已用 ...r 展开映射自动透传无需改);merger.ts custom 诊断映射补 suggestion: r.suggestionwebview.ts 无需改(buildCustomList 已传 d.suggestion,非空即触发 buildIssueItem 展开详情复用 detail-suggestion 渲染)。specdocs/superpowers/specs/2026-08-20-custom-rule-suggestion-design.md。npm test 106 passing / lint 0 error / compile 通过 | 中间产物:澄清阶段在「仅展开建议文本」与「展开+AI 修复按钮」间选择,用户选前者(自定义规则无 linter 可验证,修复按钮需额外适配,暂缓) | src/ai/schema.ts src/ai/engine.ts src/merger/merger.ts docs/superpowers/specs/2026-08-20-custom-rule-suggestion-design.md(新建) | deepseek-v4-flash |
| 2026-08-20 20:18 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复功能扩展到自定义规则与 AI 审查:面板 custom/ai 条目标题支持 AI 修复,分 tab 批量(各 tab 各自「全部修复」),两步确认复用,各 tab 显示已修复+撤销。新建 src/fix/fixPrompt.ts(共享修复/重检 prompt builder 三语 + ReviewIssueInput 接口,从 aiFixEngine 提取)+ src/fix/customFixEngine.tsaiFixReviewIssueAI 生成修复→indexOf 匹配替换→AI 重检 {fixed,reason} 收敛,未消除带反馈重试 ≤maxIterations,无 adapter,支持 dryRun+ src/test/customFixEngine.test.ts4 用例)。aiFixEngine.ts 改用共享 fixPromptrequestFix 内构造 ReviewIssueInput 适配 range→line)。fixSession.ts FixedEntry 加 source:'linter'|'custom'|'ai'recordFixes 加 source 参数;fixPending.ts PendingFix/PendingBatch 加 source。commands.ts 新增 resolveReviewIssueFix/findCustomIssue/findAIIssuefixIssue 按 payload.source 分流(custom/ai 走 AI 重检),fixAll 分 tab 批量(payload.source),applyFixPreview/applyAllPreview recordFixes 传 sourcerefreshAfterFix 保留 custom suggestion(原映射丢 suggestion)。webview.ts custom/ai 列表启用 aiFixable 渲染修复按钮+各 tab fixAll 按钮传 source+已修复区块按 source 过滤与徽章+FixedEntryView 接口。删除 codeDiffschema.ts 两字段、engine.ts 完整+方法审查 prompt 全部 codeDiff 行、report.ts ```diff``` 展示块。specdocs/superpowers/specs/2026-08-20-custom-ai-fix-design.md。npm test 110 passing / lint 0 error / compile 通过 | 中间产物:①澄清阶段用户在「AI 生成后不验证」与「AI 重检收敛」间两轮选择,最终定为 AI 重检收敛(无 linter 可重 lint,用 AI 重检替代);②codeDiff 去留讨论——用户问「带着有需要吗」,说明其现状仅报告展示用,用户决定删除;③refreshAfterFix 的 customRuleResults 映射初版未含 suggestion,审查中发现会破坏上轮 custom 展开功能,补上;④aiFixEngine requestFix 传入 LinterDiagnostic 与 fixPrompt.ReviewIssueInput 类型不匹配(line vs range)编译报错 TS2345,改为构造 ReviewIssueInput | src/fix/fixPrompt.ts(新建) src/fix/customFixEngine.ts(新建) src/fix/aiFixEngine.ts src/fix/fixSession.ts src/fix/fixPending.ts src/activation/commands.ts src/panel/webview.ts src/ai/schema.ts src/ai/engine.ts src/utils/report.ts src/test/customFixEngine.test.ts(新建) docs/superpowers/specs/2026-08-20-custom-ai-fix-design.md(新建) | deepseek-v4-flash |
| 2026-08-20 21:56 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复面板「修复」按钮卡 ⏳ 不恢复的 bug(实测:custom 修复失败 ai-no-fix 后按钮永久卡 ⏳、点取消后也卡 ⏳)。根因双处:①reviewPanel.js pending/batchPending 消息翻转只切 display,从不重置「修复」按钮的 textContent/disabled——点击时 JS 设 disabled=true+textContent='⏳...'webview.ts buildIssueItem onclick),取消(on:false)恢复显示后仍停留在 ⏳+disabled;②fixIssue/fixAll 命令层失败路径(!result.success、success===0、catch)只 showWarningMessage 就 return,从不 postMessage 恢复按钮。修复:webview.ts buildIssueItem 修复按钮加 data-label 存原始文案(🔧 修复/🤖 AI 修复,esc 转义);reviewPanel.js pending 消息 on:false 时对非 confirm 按钮(data-label)恢复 textContent+disabled=falsecommands.ts fixIssue custom/linter 分支失败+catch 补 postMessage {type:'pending',key,on:false}key=ruleId@line),fixAll 两处 success===0+catch 补 {type:'batchPending',on:false}。npm test 110 passing / lint 0 error / compile 通过。ai-no-fix 与「聚焦文件才生效」的关系仍在排查(用户复现:不聚焦连点 3 次必现失败;聚焦后成功,但代码逻辑 panel 来源取文档不依赖焦点,待进一步定位) | 中间产物:①初版考虑在失败弹窗后靠 refreshAfterFix 全量重建面板恢复按钮,发现失败路径根本不走 refreshAfterFix,改显式 postMessage;②data-label 文案初版直接拼 emoji+t(),审查确认 esc() 转义 & < > " 后 dataset 读取自动解码安全;③是否给 catch 也补恢复曾有犹豫,确认异常路径同样需恢复故一并补上 | src/panel/webview.ts src/views/reviewPanel.js src/activation/commands.ts | deepseek-v4-flash |
| 2026-08-20 22:20 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复 custom/ai 修复不稳定(用户实测「有时能修有时不能」,排除聚焦因素后确认是 AI 链路稳定性问题)。根因三处:①fixPrompt.buildFixSystemPrompt 只要求「无法修复就输出空」,给了 AI 轻易放弃的空间(→ ai-no-fix);②customFixEngine.verifyFixed 用 parsed.fixed === true 严格相等,AI 返回字符串 "true" 永不收敛(→ max-iterations);③收敛失败整体回滚,一次不确定的 AI 重检否定已生成的有效修复。修复(按用户确认的 C1 方案):①prompt 三语加硬性要求「必须输出修复片段,禁止输出空修复;即使无法完全消除也要给缓解/改善的最小片段」;②requestFix 对空修复/解析失败自动重试 1 次(customFixEngine 与 aiFixEngine 同步,前者重试后仍空才 null);③verifyFixed 宽松判定 f===true || String(f)==='true';④C1 收敛降级——循环结束未收敛时若 appliedFixes.length>0 则接受最后一次修复(返回 success+newText 走正常 diff 预览),仅从未生成出可匹配修复才失败;⑤每轮加 console.log('[code-reviewer] review-fix', ruleId, round, {ai-no-fix/ai-match-failed/no-change/applied/verify}) 诊断日志。测试:customFixEngine.test.ts 原 max-iterations 用例改为「accepts last fix when verify never passes」(断言 success=true+newText),新增「retries empty fix once then fails with ai-no-fix」用例(两次空响应)。npm test 111 passing / lint 0 error / compile 通过 | 中间产物:①TS2367 类型不重叠——parsed.fixed 类型 boolean|undefined 与 'true' 字符串比较报错,用 & { fixed?: unknown } 断言 + String(f) 收窄解决;②aiFixEngine 的 ai-no-fix 测试用例只给 1 次空响应,重试后取默认 '{}'(无 originalText)仍返回 ai-no-fix,无需改断言;③C1 实现位置纠结——先想在循环外统一处理,实际把降级分支写在 !converged 内并与 dryRun/apply 复用收尾 | src/fix/fixPrompt.ts src/fix/customFixEngine.ts src/fix/aiFixEngine.ts src/test/customFixEngine.test.ts | deepseek-v4-flash |