# 决策档案格式 本文件定义 `decision.md` 的格式、每节的要求、校准案例和反模式。 ## 骨架 ```markdown # Decision: <一句话标题> ## Problem ## Decision ## Alternatives considered ## Consequences ## Verification ``` 小节顺序固定。固定顺序不是为了机器,是为了**人能跳转**——任何时候你想知道"代价是什么",往下翻到 `## Consequences` 就行。 节名是规范名,不要用近义词替代(`## 背景` / `## 方案` / `## 风险`)。 需要额外内容时,在 `## Decision` 和 `## Alternatives considered` 之间插入自定义小节。 ### 各节在哪个阶段写下 | 节 | 写下时机 | 原因 | |---|---|---| | `## Problem` | **Think** | 动机在需求刚澄清时最准确 | | `## Alternatives considered` | **Think**,grill 问答当场 | 备选是在方案讨论中被否决的,当时记最真实;事后补写会变成回忆录 | | `` | **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` —— 备选(强制) **要求**:每个**真实存在过**的备选一段,加粗开头,说明它**为什么输了**。 **首选形态是压缩 Q&A——一行一条**。grill 问答中被否决的答案,就是备选;当场一行记下,转写成本接近零: ```markdown ## Alternatives considered - Q: 上完整 BM25 schema? A: 不——先 sparse-lite + RRF,schema 重建是后续变更。 - Q: 默认用 hybrid? A: 不——dense 默认,hybrid 显式 opt-in。 ``` (SuperBizAgent 实测:180 个档案文件里,段落式备选记录率为 0;唯一自然发生的高质量备选记录就是这个 Q&A 形态。) **值得写深时**(难以逆转的架构决策)才展开成段落,并要求写出推理链: ```markdown **新增 year 字段到条目数据。** 冗余且需要同步维护——`date` 已是唯一来源, 派生成本为零,多一个字段就多一处会不一致的地方。 ``` **反模式**: | 反模式 | 为什么不行 | |---|---| | **稻草人备选** | 编一个明显更差的选项来凑数。这会让真实读者对整个文档失去信任 | | 只列备选,不说为什么输 | 等于没写——读者无法判断它现在是否仍然输 | | 把"什么都不做"当默认备选 | 除非当时真的认真考虑过维持现状,否则它只是修辞 | | 备选写成一堆标签 | 没有推理链,未来无法复用 | | **段落式凑字数** | Q&A 一行能说清的,不要为了"看起来正式"展开成三段 | **"什么都不做"什么时候是真的备选**:当维持现状确实是一个被权衡过的选项时(例如"暂不修复这个边界,先观察")。 **没有可记录的备选时**,写这一行注释而不是编造: ```markdown ``` 宁可承认"当时没记录",也不许发明一个假备选。**读者读到一条备选时,必须能相信它真的发生过。** ### `## 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 篇档案里**唯一**记录了备选的一篇——也是唯一读完之后还记得住内容的一篇。 **注意它的形式**:两条备选,每条一句被拒理由,没有废话。**短不等于差。** ### 值得写深的备选(真实) SuperBizAgent 的 `phase1-infrastructure/decisions.md` ADR-001(Flyway): ```markdown ### 后果 - 表结构修改必须通过 SQL 迁移脚本 - 开发环境首次启动需要执行 Flyway 迁移 - 生产环境部署自动执行未执行的迁移脚本 ``` 难以逆转的基础设施决策,后果写得具体到"改表必须走迁移脚本"——三个月后最需要读的就是这几行。 **这是 Q&A 一行记不住的那种备选,值得整段。** ### 应当补录备选(真实) `openspec/changes/add-year-filter/` 那次变更,实际发生过两条备选,但一条都没记: ```markdown ## Alternatives considered **引入第三方筛选组件(Select2 等)。** 为 4 行原生 `