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. 阶段 1(Spec 合规性) 确保 AI 实现的代码忠实于 spec 的语义,不多做、不少做、不偏做
  2. 阶段 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 说它应该做的事。

posted @ 2026-03-25 16:09  唐宋元明清2188  阅读(7)  评论(0)    收藏  举报