Files
DevFlow/docs/02-架构设计/规格契约自检机制-2026-06-14.md
绝尘 f3c0967f41 修复: AR-3 审批 reason 查项目名拼对象名(用户再反馈 P0)
build_approval_reason 改 async + 接收 db,对 delete/restore/purge/update/bind/create_task 的 id/project_id 查 ProjectRepo.get_by_id 拼「项目名」(id=x)(原只拼裸 id,用户反馈'只返回 ID 不知道是什么数据')
新增 resolve_project_label helper;process_tool_calls 调用改 await
来源 aichat审查报告 第二章 + 用户 2026-06-14 再反馈;cargo 0 err
2026-06-14 17:16:28 +08:00

360 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 规格契约自检机制
> 创建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 已在用 hookStop 降频)+ 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 + 高后果。