SDD 强化代码审查,约束 AI 可能的幻觉
在 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 问题不阻塞流程,记录即可
这套机制的本质是:不要让 AI 既当运动员又当裁判。 即使是同一个 AI,通过角色切换、结构化清单和证据驱动,也能在一定程度上模拟独立审查的效果,显著降低幻觉对最终交付质量的影响。
编译通过只能证明语法正确,Self Code Review 验证的是语义正确——代码真正做了 spec 说它应该做的事。

浙公网安备 33010602011771号