231 lines
9.5 KiB
Markdown
231 lines
9.5 KiB
Markdown
# 决策档案格式
|
||
|
||
本文件定义 `decision.md` 的格式、每节的要求、校准案例和反模式。
|
||
|
||
## 骨架
|
||
|
||
```markdown
|
||
# Decision: <一句话标题>
|
||
|
||
## Problem
|
||
## Decision
|
||
## Alternatives considered
|
||
## Consequences
|
||
## Verification
|
||
```
|
||
|
||
小节顺序固定。固定顺序不是为了机器,是为了**人能跳转**——任何时候你想知道"代价是什么",往下翻到 `## Consequences` 就行。
|
||
|
||
节名是规范名,不要用近义词替代(`## 背景` / `## 方案` / `## 风险`)。
|
||
需要额外内容时,在 `## Decision` 和 `## Alternatives considered` 之间插入自定义小节。
|
||
|
||
### 各节在哪个阶段写下
|
||
|
||
| 节 | 写下时机 | 原因 |
|
||
|---|---|---|
|
||
| `## Problem` | **Think** | 动机在需求刚澄清时最准确 |
|
||
| `## Alternatives considered` | **Think** | 备选是在方案讨论中被否决的,当时记最真实;事后补写会变成回忆录 |
|
||
| `## Decision` | Think 记意图,**Close 校准为事实** | 见下 |
|
||
| `## Consequences` | **Close** | 代价要等实现完才知道 |
|
||
| `## Verification` | **Close** | 验证要跑过才能写成可重跑的命令 |
|
||
|
||
**`## Decision` 的两段式**:Think 时写"打算怎么做",Close 时校准为"实际做了什么"。
|
||
|
||
**如果两者不一致,不要静默改写。** 把差异写进正文——计划与现实的偏差,本身往往是最有价值的一条决策信息。
|
||
|
||
---
|
||
|
||
## 逐节要求
|
||
|
||
### `## Problem` —— 动机
|
||
|
||
**要求**:必须能**脱离方案独立成立**。读者只读这一节,就该明白为什么值得做。
|
||
|
||
**反模式**:
|
||
|
||
| 反模式 | 为什么不行 |
|
||
|---|---|
|
||
| 在 Problem 里提到方案名或实现手段 | 循环论证——说明动机没想清,只是把方案重述了一遍 |
|
||
| 写成"当前代码的缺陷清单" | 那是 bug 报告,不是动机 |
|
||
| 一句话带过 | 读者无法判断这个决策是否还成立 |
|
||
|
||
**自检**:把 `## Decision` 整节删掉,`## Problem` 还读得通吗?读不通就重写。
|
||
|
||
### `## Decision` —— 做法
|
||
|
||
**要求**:现在时,描述**已经采用的做法**。如果变更还在进行中,写"将要采用的做法"并保持它随后更新为事实。
|
||
|
||
**反模式**:
|
||
|
||
- 写成任务清单(那是 `tasks.md`)
|
||
- 复述 `design.md` 的实现细节(那是 `design.md`)
|
||
- 用"我们计划"而不说"我们决定"
|
||
|
||
### `## Alternatives considered` —— 备选(强制)
|
||
|
||
**要求**:每个**真实存在过**的备选一段,加粗开头,说明它**为什么输了**。
|
||
|
||
```markdown
|
||
**新增 year 字段到条目数据。** 冗余且需要同步维护——`date` 已是唯一来源,
|
||
派生成本为零,多一个字段就多一处会不一致的地方。
|
||
```
|
||
|
||
**反模式**:
|
||
|
||
| 反模式 | 为什么不行 |
|
||
|---|---|
|
||
| **稻草人备选** | 编一个明显更差的选项来凑数。这会让真实读者对整个文档失去信任 |
|
||
| 只列备选,不说为什么输 | 等于没写——读者无法判断它现在是否仍然输 |
|
||
| 把"什么都不做"当默认备选 | 除非当时真的认真考虑过维持现状,否则它只是修辞 |
|
||
| 备选写成一堆标签 | 没有推理链,未来无法复用 |
|
||
|
||
**"什么都不做"什么时候是真的备选**:当维持现状确实是一个被权衡过的选项时(例如"暂不修复这个边界,先观察")。
|
||
|
||
**没有可记录的备选时**,写这一行注释而不是编造:
|
||
|
||
```markdown
|
||
<!-- alternatives-not-recorded -->
|
||
```
|
||
|
||
宁可承认"当时没记录",也不许发明一个假备选。**读者读到一条备选时,必须能相信它真的发生过。**
|
||
|
||
### `## Consequences` —— 代价
|
||
|
||
**要求**:同时写清**买到了什么**和**付出了什么**。包含已知限制。
|
||
|
||
**反模式**:
|
||
|
||
- 只写收获(那是营销文案,不是档案)
|
||
- 不写已知限制("目前不支持 X")
|
||
- 不写被放弃的能力
|
||
|
||
**这是全文第二有价值的一节。** 未来维护者最常需要判断的正是"这个代价现在还能接受吗"。
|
||
|
||
### `## Verification` —— 验证
|
||
|
||
**要求**:写**可重跑的命令**,或**明确的人工步骤**。并且**标明哪些是只读的、哪些有副作用**。
|
||
|
||
```markdown
|
||
**只读(可直接跑)**
|
||
- `grep -c 'id="year-filter"' knowledge-index.html` → 1
|
||
- `bash -n scripts/update-knowledge-index.sh` → exit=0
|
||
|
||
**有副作用(会重写文件,收尾时不必跑)**
|
||
- `bash scripts/update-knowledge-index.sh`,然后复跑上面两条
|
||
|
||
**人工**
|
||
- 选择 2026,只显示 date 以 2026 开头的条目
|
||
```
|
||
|
||
**为什么必须分**:收尾检查发生在变更即将落地的时候,**那时最不愿意再改文件**。如果把最有说服力的验证写成有副作用的命令,执行者只有两个选择——跑(有风险)或者跳(失去验证)。
|
||
分开之后,只读的那部分**每次都能跑**。
|
||
|
||
**反模式**:
|
||
|
||
| 反模式 | 为什么不行 |
|
||
|---|---|
|
||
| `已确认年份筛选控件存在` | **结论不可重跑**。三个月后没人知道当时确认了什么 |
|
||
| `手动测试一下` | 不是步骤,是托付 |
|
||
| 只写"测试通过" | 哪个测试?覆盖率多少? |
|
||
| 只读命令与有副作用命令混在一起 | 执行者要么全跑(有风险),要么全不跑(失去验证) |
|
||
|
||
**判据**:换一个人拿着这一节,能不能在 5 分钟内重跑一遍——**而且不必担心它改坏什么?**
|
||
|
||
---
|
||
|
||
## 校准案例
|
||
|
||
校准判断用真实案例,**不用字数阈值**。字数从来不是标准。
|
||
|
||
### 值得写的备选(真实)
|
||
|
||
来自 `devflow/projects/2026-05-18-knowledge-index-panel/adr/0001-directory-name-as-truth-source.md`:
|
||
|
||
```markdown
|
||
## 替代方案
|
||
|
||
| 方案 | 被拒原因 |
|
||
|------|---------|
|
||
| YAML 全优先 | 字段名不一致(date vs created) |
|
||
| 目录名全优先 | 标题中的语义信息在下划线转空格后丢失 |
|
||
```
|
||
|
||
只有 39 行,但它是本仓库 37 篇档案里**唯一**记录了备选的一篇——也是唯一读完之后还记得住内容的一篇。
|
||
|
||
**注意它的形式**:两条备选,每条一句被拒理由,没有废话。**短不等于差。**
|
||
|
||
### 应当补录备选(真实)
|
||
|
||
`openspec/changes/add-year-filter/` 那次变更,实际发生过两条备选,但一条都没记:
|
||
|
||
```markdown
|
||
## Alternatives considered
|
||
|
||
**引入第三方筛选组件(Select2 等)。** 为 4 行原生 `<select>` 代码引入依赖,
|
||
与仓库"单文件静态 HTML"的约束冲突。
|
||
|
||
**只改 knowledge-index.html,不改生成脚本。** 更快,但重建即丢功能——
|
||
生成脚本才是真理源,这条诱惑在本次实现中差点导致返工。
|
||
```
|
||
|
||
第二条特别值得记:**它在现有的 `design.md` 里其实被写成了"架构审计:主要风险是……"**。
|
||
风险的痕迹在,但它没有被记成一条可查、可复用的决策。**这就是备选被浪费的典型形态。**
|
||
|
||
### 备选的粒度
|
||
|
||
一条备选**值得记录**当且仅当:**它现在仍然可能被重新提出。**
|
||
|
||
- "用 X 库"—— 如果 X 库还在、还流行,值得记
|
||
- "用某个已下线的内部服务"—— 除非它是诱人的错误,否则不必记
|
||
|
||
---
|
||
|
||
## 与 ADR 的关系:`decision.md` 就是它
|
||
|
||
**本仓库不另建 `adr/` 目录。`decision.md` 承担 ADR 的角色**——它的骨架(Problem / Decision / Alternatives considered / Consequences)本来就是 ADR 的形态。
|
||
|
||
`grill-with-docs` 会在同时满足三个条件时提议创建 ADR:
|
||
|
||
1. **难以逆转** —— 以后改主意的代价可观
|
||
2. **缺少上下文会令人困惑** —— 未来读者会想"他们为什么这么做?"
|
||
3. **源自真实权衡** —— 确实存在备选,并且有具体理由选了其中一个
|
||
|
||
**在本流程里,这个提议的落点是 `decision.md`,不是 `docs/adr/` 或 `devflow/*/adr/`。**
|
||
|
||
> **不要同时产出两份。** 同一决策只有一个权威——两份文档必然各自腐化,而且没人知道以哪边为准。
|
||
|
||
当一个变更**只**满足部分条件(很日常、并不难逆转),仍然要写 `decision.md`——它是每个非平凡变更的义务。
|
||
**ADR 三条件在这里的作用,是判断这篇笔记值不值得写深,而不是判断要不要写。**
|
||
|
||
---
|
||
|
||
## 与 DSH Agent Notes 的差异
|
||
|
||
本格式参考了 DeepSeek Harness 的 `.agents/notes/` 范式,但有三处**简化**,原因是本仓库的约束不同:
|
||
|
||
| | DSH Agent Notes | 本协议 | 为什么 |
|
||
|---|---|---|---|
|
||
| 生命周期 | 路径含 `{lifecycle}/{class}/`,需手动移动 | **路径由 OpenSpec 表达**(在 `changes/` = 进行中,在 `archive/` = 已完成) | 文件随 change 一起走,不需要自己维护生命周期 |
|
||
| 词汇表 | `proposed/` 与 `implemented/` 有**两套不同的合法小节名**,由 gate 强制 | **单一骨架** | 两套骨架需要一次"翻转"动作,而翻转要靠记得;单骨架零维护 |
|
||
| 多语言 | en + zh + `i18n.yaml` 三元组 + 哈希 | **单语** | 那是公开仓库的需求;本仓库少两个文件 |
|
||
| `Status:` 行 | 强制,且与目录交叉校验 | **不需要** | 路径已经表达了状态,不引入需要同步的冗余字段 |
|
||
|
||
**保留自 DSH 的**:强制备选、反稻草人规则("recorded, never invented")、以真实案例校准而非阈值、冻结归档不当作当前权威。
|
||
|
||
**可选的升级路径**:如果将来发现"计划写在 `decision.md` 里但从未更新为事实"这个问题反复出现,
|
||
可以引入 DSH 的两套骨架(`## Proposal` → `## Decision`),代价是需要一个收尾步骤负责翻转。
|
||
在观察到这个问题之前不引入。
|
||
|
||
---
|
||
|
||
## 交付前自检
|
||
|
||
写完 `decision.md` 后,逐条对照:
|
||
|
||
- [ ] 删掉 `## Decision`,`## Problem` 还读得通吗?
|
||
- [ ] `## Alternatives considered` 里的每一条,**当时真的被考虑过**吗?
|
||
- [ ] 每条备选都写了**为什么输**吗?
|
||
- [ ] `## Consequences` 同时写了收获和代价吗?
|
||
- [ ] `## Verification` 里的每一项,换个人能在 5 分钟内重跑吗?
|
||
- [ ] 有没有一句话在两个地方都是权威?(有就是错)
|