# 规格契约自检机制

> 创建：2026-06-13 | 阶段：设计定稿，待落地
> 性质：设计说明 + 可执行规格基准。后续 `spec-verifier` agent、`/spec-check` skill、自检 hook 均从本文档推导。

---

## 0. 背景与问题

全程 AI coding 下，开发节奏快（实测 ~4 Sprint/天），产生两个痛点：

1. **规格无锚点 → 漂移 → 不敢当契约用**：写下的 spec 没人验证，与代码逐渐脱节，最终失去参考价值。
2. **done/todo/decision 散落 → 记不住**：做了什么、没做什么、做了哪些决策，事后查不清。

**错误方向**：新建独立的「需求规格」文档。静态 spec 必漂移；本项目无外部契约/验收需求，spec 的核心价值（沟通契约/验收基准）不成立；独立文档违反 SSOT，成为第三处真相源。

**正确方向**：**活契约（living contract）**——契约跟决策一起演进，由 AI 自检维持与代码一致。不新建文档，改造现有功能决策记录。

---

## 1. 核心方向：活契约

契约不单独成文，而是挂在功能决策记录的每条决策上。一条 `✅ 已落地` 的决策，就是一条当前生效的契约。

```
决策记录（活契约载体）
  ├─ 决策三要素：决策 / 原因 / 状态
  ├─ 代码锚点：让契约可被验证（机制 A）
  └─ 状态字段：聚合出完成度（机制 B）
        ↓
AI 自检维持契约与代码一致（机制 C/D/E）
```

---

## 2. 机制 A：代码锚点（防漂移）

给 `✅ 已落地` 的决策加一个**代码锚点**。锚点以**符号名 + grep 关键词为主锚**（跨修改稳定），**行号为辅锚**（最近定位，可漂，AI 自愈）。规格真相留在代码里，文档只留索引 + 意图。

### 形态

```markdown
### connect_timeout 不设总 timeout [Sprint 6]
- 决策：reqwest Client 加 connect_timeout(30s)，不设总 timeout
- 原因：连接阶段防无限 hang；总 timeout 会误砍流式长生成任务
- 状态：✅ 已落地
- 锚点：符号 `Client::new` | grep `connect_timeout` @ client.rs:142（行可漂，AI 自愈）
- 自检：PostEdit 触发锚点一致性 | 上次：2026-06-13 ✅
```

- **锚点**：符号 + grep 为主（真相），行号为辅（快照）。人补一次，AI 维护行号。
- **自检行**：AI 验证后回写时间戳与结果，人扫一眼即知近期是否验过。

### 主辅分明：为什么行号不是主体

| 锚组成部分 | 稳定性 | 角色 |
|------------|--------|------|
| 符号名（函数/类/常量） | 高（重构才改） | 主锚 |
| grep 关键词 | 中高 | 主锚（定位调用点） |
| 行号 | 低（加删几行就漂） | 辅锚（最近定位，可漂） |

代码高频修改下，行号必然漂；行号作主体 = 锚点必然失效。符号 + grep 才是跨修改稳定的真相。

### 三层抗漂

1. **行号漂（最常见）**：grep 不受影响，重新定位。AI 自愈（行号漂但 grep 在附近 → 自动修）；机制未跑时 grep 命中也能秒级定位。人无感。
2. **符号/grep 漂（罕见，如重命名）**：必伴随决策变更 → grep 失效 = 正确的报警信号，触发决策同步（命中第 5 节"信息不足"上报条件）。
3. **功能删除**：grep 全失效 → 报警 → 人确认废弃或误删。

维护靠 AI 不靠人：人只在新增决策时补一次锚点；之后行号漂由 AI 在自检环节自愈，人改代码时无需动锚点。

### 状态阀门：锚点只绑稳定态

锚点验证只对相对稳定的契约有效。剧烈重构期契约本身不稳，强行锚是噪音。

| 状态 | 是否锚 | 原因 |
|------|--------|------|
| `✅ 已落地` | 锚定 | 稳定，可验证 |
| `🚧 待实测` | 不锚/暂锚 | 不稳定，重构中 |
| `📐 设计未实施` | 不锚 | 未实现，无代码可锚 |

重构完成、代码稳了，`🚧→✅` 再锚定。状态字段是防锚点失效的阀门。

---

## 3. 机制 B：完成度聚合表

完成情况已编码在状态字段里（✅/🚧/📐）。缺的是按状态聚合的视图。在功能决策记录头部维护一张索引表：

```markdown
## 完成度总览

| 状态 | 数量 | 代表条目 |
|------|------|----------|
| ✅ 已落地 | 38 | connect_timeout、provider 路由、知识库 Tier 分层 |
| 🚧 待实测 | 5 | 审计回写、… |
| 📐 设计未实施（TODO） | 12 | 向量检索、count_any 否定检测、IdeaPromoter 接线 |
```

- `✅` = 做了什么；`📐` = 没做什么；决策条目 = 做了哪些决策。三问一表答完。
- 增量维护：新增决策更新计数；`📐→✅` 迁移挪列。按状态聚合，不按时间，比 PROGRESS 流水好查。

---

## 4. AI 自检：三级验证

| 层级 | 验证内容 | 可靠性 | 谁验 |
|------|----------|--------|------|
| **L1 锚点存在性** | 文件:行 + grep 关键词命中 | ✅ 高（确定性） | 主代理 grep |
| **L2 取值一致性** | 具体数值/标志是否如 spec 所述 | ⚠️ 中（范围窄，误读低） | 主代理读码 |
| **L3 行为契约** | 代码逻辑是否遵守 spec 意图 | ❌ 低（主观） | 子代理最小上下文 |

**核心贡献**：AI 把脆弱的行号锚点变成自愈的语义锚点——
- 行号漂但 grep 关键词在附近 N 行 → **AI 自动修正锚点行号**（可逆，自处理）。
- 关键词消失 → **真报警**，语义变了，需人决策。

---

## 5. 分流规则：AI 能做 vs 人必须做

目标：让 AI 机械吞掉确定性/低风险/可逆的 80%，只把真正需要人脑的推到人面前。

### 5.1 两轴判定矩阵

| | 客观唯一（确定） | 主观/多解 |
|---|---|---|
| **只读/可逆** | ✅ AI 全权自处理 | ⚠️ AI 给候选 → 人定 |
| **有后果/不可逆** | ⚠️ 报告 → 人定 | 🔴 必须人定 |

### 5.2 两条机械判定

**AI 自处理（不报人）的充要条件**：`确定性 = 客观 AND 动作 = 可逆`。
（锚点行号自愈、数值核对一致、自检时间戳回写。）

**必须上报人的条件（任一命中即报）**：
1. **主观**——验证答案不唯一（行为契约、意图符合性）。
2. **有后果**——动作不可逆（改决策语义、标记契约被破坏、回滚代码）。
3. **信息不足**——AI 无法判定（锚点关键词消失，但不知是否故意改的）。

### 5.3 兜底安全阀

规则未覆盖的场景，**默认上报人**，不擅自自处理。宁可多报，不可漏报关键。

### 5.4 高后果判定（决定是否触发子代理）

| 判为高后果（满足任一） | 例 |
|------------------------|-----|
| 数据完整性 | 写库、迁移、状态机流转 |
| 并发安全 | 锁、共享状态、异步竞态 |
| 安全 | 鉴权、注入、凭据处理 |
| 外部契约 | API/协议、第三方对接 |
| 不可逆操作 | 删除、覆盖、发布 |

高后果决策即使主代理自检报绿，仍触发子代理第二意见。

---

## 6. 反馈规格：五字段决策单元

上报给人的每条，必须是**可点的闭合决策**，不是要调查的谜题。AI 把上下文打包进去，人只回答 yes/no 或选 A/B。

```markdown
🔴 [决策名] connect_timeout 不设总 timeout
锚点：client.rs:142 | 实际：行号漂至 158，且 grep "timeout" 消失
证据：预期 .connect_timeout(30s) 无 .timeout() | 实际代码已加 .timeout(60s)
为何上报：命中「信息不足」——无法判定是故意改回总 timeout，还是误改
候选：A. 故意改 → 更新决策记录(状态/原因)
      B. 误改 → 回滚代码（AI 推断 B 更可能：总 timeout 会误砍流式）
需你定：A 还是 B？
```

「为何上报」显式化分流规则，使判定可审计。

---

## 7. 子代理隔离验证（L3 / 高后果层）

### 7.1 为什么用 agent 不用 skill

| | Skill | Agent |
|---|---|---|
| 上下文 | 复用主对话，**不隔离** | 独立窗口，**隔离** |
| 偏误 | 主代理带作者偏误执行（白搭） | 消除作者偏误 |

主代理验证有结构性确认偏误：决策是它记的、代码是它改的，倾向支持自己对。**隔离验证必须 agent。**

### 7.2 零上下文 → 最小必要上下文

完全零上下文是双刃剑：子代理无领域知识会误判（局外人偏误）。正确形态是**给事实，不给立场**——子代理是陪审员，只看证据下判断。

**卷宗格式**（由编排方构造，传入 agent）：

```
断言：此函数应"丢弃残缺响应，不入库"
证据：<精确代码片段>
任务：判断代码行为是否符合断言
```

**不给**：决策原因字段、对话历史、是否刚改的、当初怎么定的。

### 7.3 分歧才报人

子代理不替代人，是在「上报人」前加第二意见：

```
主代理自检（带上下文判一次）
  ├─ L1/L2 确定性 → 自处理
  └─ L3/高后果 → 起 spec-verifier agent（最小上下文判一次）
        ├─ 两代理一致（都绿/都红）→ 按结论走
        └─ 两代理分歧 → 🔴 报人（分歧暴露主观性，只有人能定）
```

人的事件面从「所有主观项」压缩到「主观项中的分歧项」。

---

## 8. 触发环节：何时拉起 Agent

起 agent ⟺ 命中下列环节之一 AND 验证项是主观层(L3)或高后果。

| 环节 | 时机 | 起 agent? | 验证范围 | 频率控制 |
|------|------|-----------|----------|----------|
| **A 改代码** | PostEdit hook，改到挂锚文件 | 改到高后果锚点才起；L1/L2 主代理自验 | 仅被改那条契约 | 每次相关编辑，单条 |
| **B 回合结束** | Stop hook（降频） | 本回合涉及的高后果/主观项 | 本回合动过的 | 抽样，≥10 轮/≥10 min |
| **C 主动审计** | 手动 `/spec-check` | 全部 L3/高后果 | 所有 ✅ 决策 | 人触发，全量并行 |
| **D 记录决策** | decision-record 标 ✅/演进时 | 新记或 📐→✅ 的高后果项 | 该单条 | 每次 ✅ 迁移 |

- **A 最值钱**：在「可能制造漂移的时刻」拦截，单条，便宜。优先级最高。
- **B 兜底**：catch A 漏的（一处改多处）。
- **C 体检**：清历史漂移，最贵，人触发。
- **D 防脱节**：决策记了但代码没跟上，✅ 迁移时必验。

全程 AI coding 下，A/B 自动跑零摩擦，C 人按需，D 跟 decision-record 自然触发。无需人记「该验证了」。

---

## 9. 三层架构与组件骨架

```
hook（时机）→ /spec-check skill（编排）→ spec-verifier agent（隔离验证）
   PostEdit/Stop     读记录+分流+封装卷宗        最小上下文判定
                     L1/L2 自处理                 L3/高后果
                     收集+分歧上报                返回：判定+置信+分歧点
```

### spec-verifier agent（`.claude/agents/spec-verifier.md`）

```
你是独立契约验证者。只依据调用方给你的【断言+证据】判断。
不假设意图，不参考对话历史，不信任任何"应该是什么"的预设。
输出：判定（符合/违反/无法判定）+ 置信度 + 关键分歧点（一句话）。
无法判定时必须明说，禁止凑结论。
```

### /spec-check skill（`.claude/skills/spec-check/`）

```
1. 读功能决策记录，提取所有 ✅ 条目（锚点+断言）
2. 分流：L1/L2（确定性）→ 自己 grep 验，自处理
         L3/高后果（主观）→ 调 spec-verifier agent（传断言+代码片段，不传决策原因）
3. 主代理自己也判一次 L3（带上下文）
4. 比对：分歧项 → 按五字段格式化上报；一致项 → 按结论走
5. 锚点行号漂移 → 自愈（可逆，自处理）
```

基建复用：devflow 已在用 hook（Stop 降频）+ skill（/review、decision-record）。三层机制全是同构基建，不引入新依赖。

---

## 10. 落地顺序

1. **本文档定稿**（当前）——后续所有实现的规格基准。
2. **功能决策记录瘦身 + 补锚点**——删微决策膨胀（730→~300），给 ✅ 条目补代码锚点。
3. **主代理自检 hook**——PostEdit（环节 A）+ Stop 降频（环节 B），覆盖 L1/L2 确定性层。
4. **spec-verifier agent + /spec-check skill**——覆盖 L3/高后果，四环节（A/B/C/D）主观层。
5. **分歧上报机制**——五字段决策单元，接入 Stop hook 通知。

---

## 11. 用户操作指南

> 本节是人视角的操作手册。机制细节见 2-9 节，这里只讲「你做什么」。

### 心智模型：3 按钮 + 1 屏

整个机制里，人只做三件事，看一块屏。其余全是 AI 自动。

| | 人的动作 | 时机 |
|---|----------|------|
| 🔘 记决策 | 做取舍时，让 AI 用 decision-record 记下（决策/原因/状态） | 每次开发有取舍 |
| 🔘 裁决分歧 | AI 上报时，在候选里选 A 或 B | 子代理与主代理打架时（偶发） |
| 🔘 跑体检 | 执行 `/spec-check` 全量扫描 | 大版本前 / 重构后 |
| 🖥 完成度表 | 翻功能决策记录头部「完成度总览」表 | 想看进度时 |

### 人 vs AI 分工

| 动作 | 归属 | 频率 |
|------|------|------|
| 做开发取舍（选 A 不选 B） | 人 | 每次开发 |
| 记决策 + 补锚点 | AI 做，人确认 | 决策落地时 |
| 维护锚点行号（自愈） | AI | 自动 |
| 验证代码符合契约 | AI | 自动 |
| 起 hook / 子代理自检 | AI | 自动 |
| 裁决 AI 分歧 | 人 | 上报时 |
| 查进度 | 人（看表） | 随时 |

人只做两件：**记决策 + 裁决分歧**。验证、维护、检查全归 AI。

### 看的入口与时机

| 想知道 | 看哪里 | 时机 |
|--------|--------|------|
| 做了/没做/做了哪些决策 | 功能决策记录头部「完成度总览」表 | 随时 |
| AI 发现的契约冲突 | 上报条目（五字段：锚点/证据/为何上报/候选/需你定） | 被动收（偶发） |
| 全量漂移体检 | `/spec-check` 红项报告 | 主动（大版本前） |

### 做的节奏

- **记决策**：开发中一有取舍，当场记。齿轮转起来的起点，零额外成本。
- **裁决**：收到上报 → 选 A/B → AI 执行。
- **体检**：每 Sprint 末或重构后跑一次 `/spec-check`，清历史漂移。
- **迭代机制**：规则不准（误报/漏报）→ 改本文档第 5 节分流规则。机制文档是活的。

### 一天的工作流

```
开发中做取舍 ──→ 🔘记决策（AI 补锚点，人不管）
       │
       │  AI 后台：hook 自检 / 子代理验证 / 行号自愈
       │
   AI 打架？─是─→ 🔘裁决（选 A/B）
       │否
       ▼
  想看进度 ────→ 🖥翻完成度表
       │
  大版本前 ────→ 🔘跑 /spec-check 体检
```

### 当前态：能做什么

机制尚未落地（落地链 ②-⑤）。当前能力边界：

| 能力 | 现在 | 建成后 |
|------|------|--------|
| 🔘 记决策 | ✅ 已有（decision-record） | ✅ |
| 🖥 完成度表 | ❌ 需先补锚点 + 建表（②） | ✅ |
| 🔘 裁决上报 | ❌ 需 ③④⑤ | ✅ |
| 🔘 /spec-check | ❌ 需 ④ | ✅ |

解锁其余能力的起点是落地链 ②：补锚点 + 建完成度表。

---

## 12. 诚实边界

- **AI 自检降低漂移，不消除。** 行为级契约仍需人盯。别因「AI 验过」就放心改语义。
- **同一 AI 的盲区贯穿写与验。** 全程 AI coding 下，当初记录漏掉的约束，验证时 AI 也想不到查。关键决策的 spec，人过一眼。
- **一致 ≠ 正确。** 两代理一致时仍可能共享同一盲区（spec 本身写错，两代理按错的理解一致）。一致只代表「无分歧可上报」。
- **selective 用子代理。** 全用 = 成本爆炸。只 L3 + 高后果。
