如何通过SDD强化代码审查,有效约束AI的幻觉生成?

摘要:在 Spec-Driven Development(SDD)工作流中,AI 既是 spec 的执行者,也可能是幻觉的制造者。 使用SDD开发已经有一段时间,我发现在上下文被打爆的情况下,rules很容易被稀释,导致最后脱离原来的spec规范
在 Spec-Driven Development(SDD)工作流中,AI 既是 spec 的执行者,也可能是幻觉的制造者。 使用SDD开发已经有一段时间,我发现在上下文被打爆的情况下,rules很容易被稀释,导致最后脱离原来的spec规范。当然最后肯定会有verify检查,但因为介入环节较晚,最后需要多轮的修复才能回到正轨,这导致当前Session烧了更多的Token。 我们也知道历史消息不断累积,占用窗口空间,越到后面越容易"忘记"早期内容,所以更早环节介入代码与spec的对齐审查,在当前Session非常接近或者已经超出上下文窗口的情况下,对减少AI幻觉是非常有必要的 基于这个想法参考superpowers提炼了个self-code-review.skills,提升OpenSpec开发代码质量 本文介绍self-code-review.skills如何通过多重审核机制,在代码实现阶段强制验证 spec 合规性,让 AI 写完代码后"不信任自己",从而显著提高交付质量。 一、问题:AI 编码中的"自我说服"陷阱 当我们用 AI 辅助编码时,一个常见的问题是:AI 在生成代码后,倾向于"自我说服"代码是正确的。 这种"自我说服"体现在几个方面: 幻觉(Hallucination):编造不存在的 API、类名、配置项,看起来合理但实际不存在 过度实现(Overbuilding):实现了 spec 中未要求的功能,引入不必要的复杂性 遗漏实现(Missing):跳过了 spec 中某些不起眼但重要的场景 语义偏差(Drift):代码"大致正确"但与 spec 的精确语义存在微妙差异 编译通过只能证明语法正确,无法证明语义正确——即代码是否真正做了 spec 说它应该做的事。 二、解法:两阶段自审查机制 借鉴 Superpowers 的 Subagent-Driven Development 两阶段审查思想,我们设计了一套Self Code Review机制:让 AI 在完成实现后,强制切换到"审查者"角色,通过结构化 Checklist 逐条验证。 核心设计原则: 原则说明 不信任自己 刚写完代码时最容易自我说服"没问题",必须假设代码可能有错 角色隔离 审查时切换到"审查者"视角,不为"实现者"辩护 证据驱动 每个判断必须引用具体的file:line或 spec 条目,禁止主观臆断 顺序不可颠倒 先确认"做对了",再看"做好了" 整体流程 实现完成 + 编译通过 │ ▼ ┌─────────────────────────────┐ │ 阶段 1:Spec 合规性审查 │ ← 对照 spec.md │ "做对了吗?" │ │ │ │ ✅ 通过 → 进入阶段 2 │ │ ❌ 问题 → 修复 → 重新阶段 1 │ └─────────────────────────────┘ │ ✅ ▼ ┌─────────────────────────────┐ │ 阶段 2:代码质量审查 │ ← 对照 design.md + 编码规则 │ "做好了吗?" │ │ │ │ ✅ 通过 → 标记 task 完成 │ │ ❌ 问题 → 修复 → 重新阶段 2 │ └─────────────────────────────┘ │ ✅ ▼ ✅ 输出审查报告,标记完成 关键约束:阶段 2 必须在阶段 1 通过后才能开始。如果代码做的事情本身就不对,审查代码质量没有意义。 三、阶段 1:Spec 合规性审查——"做对了吗?" 这是约束 AI 幻觉的核心防线。审查者角色设定: 你现在是Spec Compliance Reviewer。你的工作是验证实现是否与 spec 一致。不要为实现者辩护,不要信任实现者的自述。只看代码,只对照 spec。 3.1 四维检查清单 ① 需求覆盖(Requirement Coverage) 逐条对照 spec 中的每个 Requirement,确认是否有对应的代码实现,且实现完整覆盖了 Requirement 的语义。 ### 需求覆盖 | Requirement | 状态 | 证据 | |-------------------|------|-------------------------------| | 支持密码重置 | ✅ | AccountService.cs:42 | | 重置后发送通知 | ❌ | 未找到通知逻辑 | ② 场景覆盖(Scenario Coverage) 对 spec 中每个 Scenario,验证 WHEN 条件是否被代码识别,THEN 结果是否与代码行为一致,边界条件是否被处理。 ### 场景覆盖 | Scenario | WHEN | THEN | 状态 | 证据 | |--------------|----------------|----------------|------|-------------------| | 正常重置 | 用户点击重置 | 密码被更新 | ✅ | ResetHandler:28 | | Token 过期 | Token 已失效 | 返回错误提示 | ❌ | 未处理过期场景 | ③ 过度实现检查(Overbuilding Check) 检测 spec 中未提及的额外行为——这正是 AI 幻觉的高发区。AI 经常会"好心"地添加 spec 未要求的功能,引入不必要的参数、配置或依赖。 ④ 遗漏实现检查(Missing Implementation Check) 确认是否存在 spec 要求但代码未实现的行为,是否有 Scenario 被跳过或忽略。 3.2 偏差处理 发现问题后不是简单标记,而是立即修复 → 编译 → 重新执行全部阶段 1 检查: 发现偏差 → 修复代码 → 编译通过 → 重新阶段 1 全部检查 │ 仍有问题 → 再次修复(最多 3 轮) │ 3 轮未解决 → 暂停,报告用户 修复循环最多 3 轮。这避免了无限循环,同时保证了足够的修复机会。 四、阶段 2:代码质量审查——"做好了吗?" 确认代码"做对了"之后,再审查"做得好不好"。问题按严重程度分三级: 级别含义处理方式典型问题 Critical 必须修复 立即修复 + 重新审查 Bug、安全问题、资源泄露、破坏性变更 Important 应该修复 修复后再通过 错误处理不规范、命名违规、依赖方向错误、未复用已有设施 Minor 建议改进 记录但不阻塞 冗余代码、风格不一致、可读性可提升 每个问题必须给出具体位置和修复建议: #### Critical(必须修复) 1. **未处理空值导致 NullReferenceException** - 位置: `DiskService.cs:85` - 问题: `GetDiskInfo()` 返回 null 时未做判断,直接访问 `.Name` 属性 - 原因: 磁盘不存在时服务端返回 null,会导致运行时崩溃 - 修复: 添加 null 判断,返回 `OperateResult.Fail("磁盘不存在")` 五、多重审核:与上下游机制的协同 Self Code Review 并不是孤立存在的,它是 SDD 工作流中多重审核链的关键一环: ┌─────────────────────────────────────────┐ │ SDD 多重审核链 │ ├─────────────────────────────────────────┤ │ │ Artifact 阶段 │ ① Artifact 一致性验证 │ (设计时) │ proposal → design → specs → tasks │ │ 每个 artifact 生成后对照上游验证 │ │ │ ├─────────────────────────────────────────┤ │ │ 编译阶段 │ ② 自动编译修复循环 │ (第 0 层) │ 修改 → 编译 → 修复 → 再编译 │ │ 语法正确性保证 │ │ │ ├─────────────────────────────────────────┤ │ │ 审查阶段 │ ③ Self Code Review (本文重点) │ (第 1-2 层) │ 阶段 1: Spec 合规 → 阶段 2: 代码质量 │ │ 语义正确性保证 │ │ │ ├─────────────────────────────────────────┤ │ │ 验证阶段 │ ④ 三层完成验证 │ (标记完成前) │ Spec 合规 → Design 一致 → Proposal 范围│ │ 全链路一致性保证 │ │ │ └─────────────────────────────────────────┘ 各层机制的职责分工: 机制验证什么约束什么幻觉 Artifact 一致性验证 design/specs/tasks 是否与 proposal 一致 设计阶段的幻觉——AI 在细化设计时偏离原始目标 自动编译修复 代码语法是否正确 基础幻觉——编造不存在的 API、类名、命名空间 Self Code Review 实现是否符合 spec + 代码质量是否达标 语义幻觉——代码"看起来对"但实际行为与 spec 不一致 三层完成验证 spec → design → proposal 全链路一致 范围幻觉——实现超出或遗漏了 proposal 声明的变更范围 这四层机制层层递进,从设计一致性 → 语法正确性 → 语义正确性 → 全链路一致性,形成完整的质量保障闭环。 六、实际审查报告示例 两阶段均通过后,输出的合并报告如下: ## ✅ Self Code Review 通过 **Task**: Task 3 - 实现磁盘信息查询接口 **变更文件**: DiskInfoController.cs, DiskQueryService.cs, DiskInfoDto.cs ### 阶段 1:Spec 合规性 ✅ - Requirements 覆盖: 4/4 - Scenarios 覆盖: 6/6 - 过度实现: 无 - 遗漏实现: 无 ### 阶段 2:代码质量 ✅ - Critical: 0 - Important: 0 - Minor: 1(已记录:DiskQueryService.cs:32 变量名可更清晰) - 优点: 错误处理规范,复用了 PilotCenters.RbdAdmin,日志标签一致 ### 修复记录 - 阶段 1: 补充了"磁盘不存在"场景的 404 响应处理 - 阶段 2: 修复了未释放的 HttpResponse 资源 如果经过修复才通过,报告中会明确标注修复内容,确保每次修复都有迹可循。 七、为什么这套机制有效? 7.1 对抗"自我确认偏误" 人类和 AI 都有一个共同的弱点:倾向于确认自己已经相信的东西。AI 写完代码后,如果让它自己判断"对不对",它会倾向于说"对"。 通过强制角色切换(从 Implementer 切换到 Reviewer),配合结构化 Checklist(必须逐条对照),我们打破了这种自我确认循环。 7.2 Spec 作为客观锚点 Spec 文件是在实现之前就确定的。用它作为审查基准,相当于有了一个不会被实现过程污染的客观标准。AI 不能说"我觉得这样更好所以改了 spec 的要求"——spec 就是 spec,实现必须符合。 7.3 证据消灭模糊地带 要求每个判断都引用file:line,消灭了"应该没问题""看起来正确"这类模糊表述。要么能指出代码在哪里实现了 spec 的要求,要么就是没实现。没有中间状态。 7.4 修复闭环防止"标记跳过" 发现 Critical/Important 问题必须修复,不能标注为"已知问题"然后跳过。修复后必须重新审查,防止修复引入新问题。最多 3 轮的限制既保证了充分修复,又避免了无限循环。 八、总结 在 SDD 工作流中,Spec 文件既是设计的产物,也是验证的基准。通过引入两阶段自审查机制: 阶段 1(Spec 合规性)确保 AI 实现的代码忠实于 spec 的语义,不多做、不少做、不偏做 阶段 2(代码质量)确保代码在正确的基础上,还写得好、写得规范 配合上游的Artifact 一致性验证和下游的三层完成验证,形成了从设计到实现的完整审核链。 self-code-review.skills,可另存为md然后导入skills即可: --- name: self-code-review description: >- OpenSpec 工作流中的两阶段代码自审查 Skill。在 task 实现完成后触发, 分两阶段审查代码:第一阶段 Spec 合规性审查(做对了吗),第二阶段代码质量审查(做好了吗)。 适用于 apply change 过程中每个 task 完成后的自动审查。 --- # 两阶段代码自审查 Skill(Self Code Review) > 灵感来源:Superpowers 的 Subagent-Driven Development 两阶段审查机制(Spec Compliance → Code Quality)。 > 适配方案:单代理通过**角色切换 + 结构化 Checklist**模拟独立审查视角。 ## 触发条件 当以下条件**全部满足**时激活此 Skill: 1. 正在执行 OpenSpec 工作流(`openspec/changes/<name>/` 下存在 artifacts) 2. 一个 task 的代码实现已完成且编译通过 3. 即将标记 task 为 `[x]`(与 `verification-before-completion` rule 联动) ## 核心原则 - **不信任自己刚写的代码**——刚写完时最容易自我说服"没问题" - **角色隔离**——审查时切换到"审查者"视角,不为"实现者"辩护 - **证据驱动**——每个判断都必须引用具体的 file:line 或 spec 条目 - **两阶段顺序不可颠倒**——先确认"做对了",再看"做好了" ## 两阶段审查流程 ``` 实现完成 + 编译通过 │ ▼ ┌─────────────────────────────┐ │ 阶段 1:Spec 合规性审查 │ ← 对照 specs/<capability>/spec.md │ "做对了吗?" │ │ │ │ ✅ 通过 → 进入阶段 2 │ │ ❌ 问题 → 修复 → 重新阶段 1 │ └─────────────────────────────┘ │ ✅ ▼ ┌─────────────────────────────┐ │ 阶段 2:代码质量审查 │ ← 对照 design.md + rules.mdc │ "做好了吗?" │ │ │ │ ✅ 通过 → 标记 task 完成 │ │ ❌ 问题 → 修复 → 重新阶段 2 │ └─────────────────────────────┘ │ ✅ ▼ ✅ 输出审查报告 ✅ 标记 task [x] ``` **关键约束:阶段 2 必须在阶段 1 通过后才能开始。** 如果代码做的事情本身就不对,审查代码质量没有意义。 --- ## 阶段 1:Spec 合规性审查 ### 角色设定 > 你现在是 **Spec Compliance Reviewer**。你的工作是验证实现是否与 spec 一致。 > 不要为实现者辩护,不要信任实现者的自述。只看代码,只对照 spec。 ### 输入 1. 读取当前 change 的 `specs/<capability>/spec.md` 2. 读取 task 描述(从 `tasks.md` 中提取当前 task 内容) 3. 读取本次 task 变更的所有代码文件 ### 检查清单 逐条检查,每条必须给出 ✅ 或 ❌ 并引用具体证据: #### 1.1 需求覆盖(Requirement Coverage) 对 spec 中每个 Requirement: - [ ] 是否有对应的代码实现? - [ ] 实现是否完整覆盖了 Requirement 的语义? #### 1.2 场景覆盖(Scenario Coverage) 对 spec 中每个 Scenario: - [ ] WHEN 条件是否在代码逻辑中被识别和处理? - [ ] THEN 结果是否与代码行为一致? - [ ] 边界条件是否被处理(如"未注册""已运行""启动失败"等)? #### 1.3 过度实现检查(Overbuilding Check) - [ ] 是否存在 spec 中未提及的额外行为? - [ ] 是否引入了 spec 未要求的新参数、新配置、新依赖? - [ ] 是否存在 YAGNI 违规("将来可能有用"的代码)? #### 1.4 遗漏实现检查(Missing Implementation Check) - [ ] 是否存在 spec 要求但代码未实现的行为? - [ ] 是否有 Scenario 被跳过或忽略? ### 输出格式 ``` ## 阶段 1:Spec 合规性审查 **Spec 文件**: specs/<capability>/spec.md **Task**: <task 编号和描述> ### 需求覆盖 | Requirement | 状态 | 证据 | |-------------|------|------| | <requirement 名> | ✅/❌ | <file:line 或说明> | ### 场景覆盖 | Scenario | WHEN | THEN | 状态 | 证据 | |----------|------|------|------|------| | <scenario 名> | <条件> | <结果> | ✅/❌ | <file:line> | ### 问题 (如有) - ❌ **遗漏**: <具体描述,引用 spec 条目> - ❌ **过度**: <具体描述,引用多余代码位置> - ❌ **偏差**: <spec 要求 vs 实际实现> ### 结论 - ✅ Spec 合规性通过 / ❌ 需要修复(<N> 个问题) ``` ### 偏差处理 - 发现问题 → 立即修复代码 → 编译 → **重新执行阶段 1 全部检查** - 修复循环最多 3 轮。如果 3 轮后仍有问题,暂停并报告给用户 --- ## 阶段 2:代码质量审查 ### 前置条件 **阶段 1 必须已通过。** 如果阶段 1 未通过,禁止进入阶段 2。 ### 角色设定 > 你现在是 **Code Quality Reviewer**。你的工作是审查代码是否写得好。 > Spec 合规性已确认,现在关注实现质量。 ### 输入 1. 读取当前 change 的 `design.md`(关注 Decisions、技术选择) 2. 读取 `.codemaker/rules/rules.mdc`(项目编码规范) 3. 读取本次 task 变更的所有代码文件 ### 检查清单 按严重程度分级检查: #### 2.1 Critical(必须修复) - [ ] **Bug 或逻辑错误**:条件判断、空值处理、异常路径是否正确? - [ ] **安全问题**:是否暴露敏感信息、是否有注入风险? - [ ] **资源泄露**:是否有未释放的资源、未取消的订阅、未停止的定时器? - [ ] **破坏性变更**:是否可能破坏现有功能? #### 2.2 Important(应该修复) - [ ] **错误处理**:是否遵循项目约定(返回 `OperateResult` 而非抛异常)? - [ ] **日志与上报**:关键操作是否有日志?失败是否有 Warn/Error 日志? - [ ] **命名规范**:是否符合项目命名约定(PascalCase/camelCase/_camelCase)? - [ ] **依赖方向**:是否违反项目层级依赖规则? - [ ] **设计一致性**:是否遵循 `design.md` 中的 Decisions? - [ ] **复用已有设施**:是否使用了 `PilotCenters.*` 而非自创全局状态? #### 2.3 Minor(建议改进) - [ ] **代码简洁性**:是否有冗余代码、无用的注释、过长的方法? - [ ] **一致性**:风格是否与周围代码一致(缩进、空行、注释风格)? - [ ] **可读性**:变量名是否清晰表达意图?逻辑是否线性可读? ### 输出格式 ``` ## 阶段 2:代码质量审查 **Task**: <task 编号和描述> **变更文件**: <文件列表> ### 优点 - <具体的正面反馈,引用 file:line> ### 问题 #### Critical(必须修复) (如有) 1. **<问题标题>** - 位置: `<file>:<line>` - 问题: <具体描述> - 原因: <为什么这是个问题> - 修复: <建议的修复方式> #### Important(应该修复) (如有) 1. **<问题标题>** - 位置: `<file>:<line>` - 问题: <具体描述> - 修复: <建议的修复方式> #### Minor(建议改进) (如有) 1. **<问题标题>** - 位置: `<file>:<line>` - 建议: <改进方式> ### 结论 - ✅ 代码质量审查通过 / ❌ 需要修复(Critical: N, Important: M) ``` ### 偏差处理 - **Critical 问题**:必须立即修复 → 编译 → 重新执行阶段 2 - **Important 问题**:必须修复后再通过 → 编译 → 重新执行阶段 2 - **Minor 问题**:记录但不阻塞,可选择修复 - 修复循环最多 3 轮 --- ## 最终审查报告 两阶段均通过后,输出合并报告: ``` ## ✅ Self Code Review 通过 **Task**: <task 编号和描述> **变更文件**: <文件列表> ### 阶段 1:Spec 合规性 ✅ - Requirements 覆盖: <N>/<N> - Scenarios 覆盖: <M>/<M> - 过度实现: 无 - 遗漏实现: 无 ### 阶段 2:代码质量 ✅ - Critical: 0 - Important: 0 - Minor: <K>(已记录/已修复) - 优点: <简要列举> ### 修复记录 (如有修复) - 阶段 1: <修复内容简述> - 阶段 2: <修复内容简述> ``` --- ## 与其他规则/技能的关系 | 规则/技能 | 关系 | 说明 | |-----------|------|------| | `auto-build-fix-rule` | 前置 | 编译通过是审查的前提 | | `verification-before-completion` | 联动 | 本 skill 是 verification rule 三层检查的具体执行手段 | | `rules.mdc` | 输入 | 阶段 2 的检查项来源之一 | | OpenSpec `specs/*.md` | 输入 | 阶段 1 的对照基准 | | OpenSpec `design.md` | 输入 | 阶段 2 设计一致性的对照基准 | ## 行为约束 1. **两阶段顺序不可颠倒** —— 先 Spec 合规,后代码质量 2. **每个判断必须有证据** —— 引用 file:line 或 spec 条目,禁止主观臆断 3. **不为实现辩护** —— 审查时要假设代码可能有问题,而非假设代码正确 4. **修复后必须重新审查** —— 修复可能引入新问题,不能假设修复一定正确 5. **Critical/Important 必须修复** —— 不能标注为"已知问题"然后跳过 6. **不要过度审查** —— Minor 问题不阻塞流程,记录即可 View Code 这套机制的本质是:不要让 AI 既当运动员又当裁判。即使是同一个 AI,通过角色切换、结构化清单和证据驱动,也能在一定程度上模拟独立审查的效果,显著降低幻觉对最终交付质量的影响。 编译通过只能证明语法正确,Self Code Review 验证的是语义正确——代码真正做了 spec 说它应该做的事。