Files
git-learn/.agents/skills/dev-flow/references/decision-note.md
T
zhuyongxin 24a71e78f1 Harden dev-flow: pre-authorization marker, alternatives log, check script
- Record rejected answers in decision.md at the moment they are rejected

- Allow explicit pre-authorized Build via a pre-authorized marker line

- Land scripts/check-dev-flow.sh and derive devflow/index.md from archives

- Add dev-flow process artifacts (decisions, prd) for open changes
2026-09-18 18:23:18 +08:00

11 KiB

决策档案格式

本文件定义 decision.md 的格式、每节的要求、校准案例和反模式。

骨架

# Decision: <一句话标题>

## Problem
## Decision
## Alternatives considered
## Consequences
## Verification

小节顺序固定。固定顺序不是为了机器,是为了人能跳转——任何时候你想知道"代价是什么",往下翻到 ## Consequences 就行。

节名是规范名,不要用近义词替代(## 背景 / ## 方案 / ## 风险)。 需要额外内容时,在 ## Decision 和 ## Alternatives considered 之间插入自定义小节。

各节在哪个阶段写下

节 写下时机 原因
## Problem Think 动机在需求刚澄清时最准确
## Alternatives considered Think,grill 问答当场 备选是在方案讨论中被否决的,当时记最真实;事后补写会变成回忆录
<!-- pre-authorized: ... --> 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 问答中被否决的答案,就是备选;当场一行记下,转写成本接近零:

## Alternatives considered

- Q: 上完整 BM25 schema? A: 不——先 sparse-lite + RRF,schema 重建是后续变更。
- Q: 默认用 hybrid? A: 不——dense 默认,hybrid 显式 opt-in。

(SuperBizAgent 实测:180 个档案文件里,段落式备选记录率为 0;唯一自然发生的高质量备选记录就是这个 Q&A 形态。)

值得写深时(难以逆转的架构决策)才展开成段落,并要求写出推理链:

**新增 year 字段到条目数据。** 冗余且需要同步维护——`date` 已是唯一来源,
派生成本为零,多一个字段就多一处会不一致的地方。

反模式:

反模式 为什么不行
稻草人备选 编一个明显更差的选项来凑数。这会让真实读者对整个文档失去信任
只列备选,不说为什么输 等于没写——读者无法判断它现在是否仍然输
把"什么都不做"当默认备选 除非当时真的认真考虑过维持现状,否则它只是修辞
备选写成一堆标签 没有推理链,未来无法复用
段落式凑字数 Q&A 一行能说清的,不要为了"看起来正式"展开成三段

"什么都不做"什么时候是真的备选:当维持现状确实是一个被权衡过的选项时(例如"暂不修复这个边界,先观察")。

没有可记录的备选时,写这一行注释而不是编造:

<!-- alternatives-not-recorded -->

宁可承认"当时没记录",也不许发明一个假备选。读者读到一条备选时,必须能相信它真的发生过。

## Consequences —— 代价

要求:同时写清买到了什么和付出了什么。包含已知限制。

反模式:

  • 只写收获(那是营销文案,不是档案)
  • 不写已知限制("目前不支持 X")
  • 不写被放弃的能力

这是全文第二有价值的一节。 未来维护者最常需要判断的正是"这个代价现在还能接受吗"。

## Verification —— 验证

要求:写可重跑的命令,或明确的人工步骤。并且标明哪些是只读的、哪些有副作用。

**只读(可直接跑)**
- `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:

## 替代方案

| 方案 | 被拒原因 |
|------|---------|
| YAML 全优先 | 字段名不一致(date vs created) |
| 目录名全优先 | 标题中的语义信息在下划线转空格后丢失 |

只有 39 行,但它是本仓库 37 篇档案里唯一记录了备选的一篇——也是唯一读完之后还记得住内容的一篇。

注意它的形式:两条备选,每条一句被拒理由,没有废话。短不等于差。

值得写深的备选(真实)

SuperBizAgent 的 phase1-infrastructure/decisions.md ADR-001(Flyway):

### 后果
- 表结构修改必须通过 SQL 迁移脚本
- 开发环境首次启动需要执行 Flyway 迁移
- 生产环境部署自动执行未执行的迁移脚本

难以逆转的基础设施决策,后果写得具体到"改表必须走迁移脚本"——三个月后最需要读的就是这几行。 这是 Q&A 一行记不住的那种备选,值得整段。

应当补录备选(真实)

openspec/changes/add-year-filter/ 那次变更,实际发生过两条备选,但一条都没记:

## 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 分钟内重跑吗?
  • 有没有一句话在两个地方都是权威?(有就是错)