From d607861c6a631397b3dddc4c4f06a8274447d343 Mon Sep 17 00:00:00 2001 From: zhuyongxin Date: Wed, 16 Sep 2026 20:06:09 +0800 Subject: [PATCH] Introduce dev-flow: lightweight 3-phase flow with decision archive --- .agents/skills/dev-flow/SKILL.md | 187 ++++++ .../dev-flow/references/decision-note.md | 230 +++++++ .agents/skills/dev-flow/references/phases.md | 130 ++++ .../docs/dev-flow/background-and-evolution.md | 572 ++++++++++++++++++ 4 files changed, 1119 insertions(+) create mode 100644 .agents/skills/dev-flow/SKILL.md create mode 100644 .agents/skills/dev-flow/references/decision-note.md create mode 100644 .agents/skills/dev-flow/references/phases.md create mode 100644 skill-workbench/docs/dev-flow/background-and-evolution.md diff --git a/.agents/skills/dev-flow/SKILL.md b/.agents/skills/dev-flow/SKILL.md new file mode 100644 index 0000000..a852f92 --- /dev/null +++ b/.agents/skills/dev-flow/SKILL.md @@ -0,0 +1,187 @@ +--- +name: dev-flow +description: 轻量开发流程:想清楚 → 做出来 → 收好尾。编排 OpenSpec 的 propose / apply / archive 三个能力,并为每个非平凡变更留下 decision.md——记录为什么这么做、否决了哪些备选、代价是什么。在用户显式调用 /dev-flow,或明确要求走开发流程时使用。 +--- + +# Dev Flow + +三个阶段,两个确认点,不分档。 + +```text +Think 想清楚 → Build 做出来 → Close 收好尾 + ⏸ 确认点 1 ⏸ 确认点 2 + 授权进入实现 是否归档 +``` + +**每个非平凡变更留下一份 `decision.md`**——记录代码和规格承载不了的东西:**为什么这么做,以及放弃了什么。** + +## 能力来源 + +dev-flow 编排已有能力,**不重复实现它们**: + +| 阶段 | 能力 | 用途 | +|---|---|---| +| Think | `grill-with-docs` | 澄清:一次一问、能查代码的自己查、术语沉淀进 `CONTEXT.md` | +| Think | `openspec-propose` | 生成 `proposal.md` / `design.md` / `specs/` / `tasks.md` | +| Build | `openspec-apply-change` | 按规格实现 | +| Build | `diagnose` / `tdd` | 按需:bug 排查 / 测试驱动 | +| Close | `openspec-archive-change` | 归档 | + +**按能力绑定,不按路径绑定**:优先用平台原生 skill;不可用时读本地 `SKILL.md` 并按其协议执行;仍不可用时按最小等价协议直接产出文件。**不可用时在 `decision.md` 里注明。** + +**不使用 `to-prd`**:它把 PRD 发布到 issue tracker(本仓库没有),且内容与 `proposal.md` + `specs/` 重复。 +**不设 `audit` 阶段**:`zoom-out` 保留为按需能力(不熟悉代码区域时拉高视角),但它不是一个必须走的关卡。 + +### 冲突裁决 + +被组合的子 skill 与本流程会冲突——这是组合的固有代价。**靠规则裁决,不靠内置。** + +> **子 skill 管"这一步怎么做";dev-flow 管"做多少、结果放哪"。** +> **冲突时,流程赢。** + +当前组合里,由 dev-flow 覆盖子 skill 默认的有三处: + +| 子 skill 的默认 | dev-flow 的覆盖 | +|---|---| +| `grill-with-docs` 要求 relentlessly 追问 | **范围收窄**:只问答案会改变产物的问题 | +| ADR 写入 `docs/adr/`,用 `ADR-FORMAT.md` | **落点改为 `decision.md`**,格式用 `decision-note.md`,**不创建 `docs/adr/`** | +| 假设根目录 `CONTEXT.md` | 用 **`devflow/glossary/CONTEXT.md`** | + +**归属规则解决不了的冲突,不要靠内置解决,而是不组合它。** + +> 理由:内置只在被组合 skill **消失**时才消除冲突。只要它还装在目录里(用户可以直接调用), +> 内置就只是让 dev-flow 多出一份**会漂移的私有副本**——重复实现加上版本漂移,两个问题。 +> `to-prd` 就是这么处理的:直接排除,而不是改造成 dev-flow 的版本。 + +## Think — 想清楚 + +**进入**:用户给出需求——粗略想法、issue、草稿、已有 PRD 都算。 + +**动作**: + +1. **澄清** —— 用 `grill-with-docs` 的方法,但按上面的 **「冲突裁决」** 覆盖它的默认: + 范围收窄(只问答案会改变产物的问题)、ADR 落点为 `decision.md`、`CONTEXT.md` 用 `devflow/glossary/` 下那份。 +2. **读上下文**:`devflow/glossary/CONTEXT.md`,并搜索 `devflow/rejected/` 与已归档的 `decision.md`—— + **避免重新提出已经被否决过的方案。** +3. 写 `openspec/changes//proposal.md`,并用 OpenSpec 补全 `design.md` / `specs/` / `tasks.md`。 +4. **写下 `decision.md` 的 `## Problem` 和 `## Alternatives considered`。** + 理由在这一刻最新鲜;留到事后补写会变成回忆录。 + +**退出**:proposal 能说清做什么、范围、非目标;`decision.md` 有 `## Problem` 和至少一条备选(或用 `` 显式声明没有)。 + +**⏸ 确认点 1** —— 向用户汇报方案方向、关键假设、主要风险,然后**等待明确的实现授权**。 + +> 未获授权不得进入 Build。 +> **方案讨论中的任何一次确认,都不等于实现授权。** + +## Build — 做出来 + +**进入**:确认点 1 已过。 + +**动作**: + +- 按 `tasks.md` 的纵向切片实现,按 `specs/` 验收。 +- **分步验证**:每完成一层再继续,不要一次写完再验。 +- **冲突先分类再处理**: + +| 分类 | 处理 | +|---|---| +| 规格不准(遗漏、边界未覆盖、验收口径缺失) | 暂停,修正 OpenSpec,再继续 | +| 代码偏离(实现没按规格做) | 修正代码,不改 OpenSpec | +| 不确定、或涉及设计方向 | **暂停,问用户** | + +- 遇到 bug 或行为不明 → 用 `diagnose`;需要测试驱动 → 用 `tdd`。两者都不强制。 + +**退出**:tasks 完成或剩余已明确记录;已运行验证,或记录了未验证的原因;已知限制已列出。 + +Build 结束**自动进入 Close**,中间不设确认点。 + +## Close — 收好尾 + +**进入**:实现达到可交接状态。 + +**动作**: + +1. **补齐 `decision.md`**——把 `## Decision` 校准为**实际发布的做法**(现在时),补 `## Consequences` 和 `## Verification`。 +2. **执行收尾检查**(见下)。 +3. 有新术语 → 更新 `devflow/glossary/CONTEXT.md`。 +4. 有被否决的提案 → 写入 `devflow/rejected//`。 +5. **⏸ 确认点 2** —— 汇报产物、验证情况、剩余风险,询问是否归档。 + +**退出**:`decision.md` 五节齐全;收尾检查通过;用户已被询问是否归档。 + +## 核心规则 + +| 规则 | 说明 | +|---|---| +| **必须写 `decision.md`** | 每个非平凡变更 | +| **`## Alternatives considered` 强制** | 没有记录"打败了什么"的决策会招来重新争论 | +| **备选是记录的,不是编造的** | 当时没记录就写 `` | +| **实现前必须有授权** | 确认点 1 | +| **冲突先分类再处理** | 三分法 | +| **一个事实只有一个权威** | 见下表 | + +**"非平凡"的判据**:改变行为、架构、跨文件或跨包的契约、流程或工具、测试策略、磁盘/线缆/配置格式,或其他维护者可能重新考察的决策。 +**豁免**:纯机械或局部编辑。 + +### 事实的唯一权威 + +| 内容 | 权威 | +|---|---| +| 为什么这么做、放弃了什么、代价 | `decision.md` | +| 实现设计、接口影响 | `design.md` | +| 可观察行为、验收场景 | `specs/` | +| 执行切片与完成状态 | `tasks.md` | +| 跨项目术语 | `devflow/glossary/CONTEXT.md` | +| 未实施的提案 | `devflow/rejected/` | + +**任何一句话只能在一个地方是权威。出现第二处,就是错**——两边会各自腐化,而且没人知道以哪边为准。 + +## 为什么这么轻 + +这三条是有意为之。**不要"补全"它们。** + +1. **义务挂在已有动作上。** 写规划产物的时刻,就是写 `decision.md` 的时刻。不设独立的归档阶段,不做事后提取。 +2. **确认点只有两个。** 进入实现、是否归档。不新增。 +3. **"是否该写"是判断。** 规则 + 收尾看一眼。机器只校验**已经写下**的东西是否合规,不用脚本强制这件事本身。 + +## 收尾检查 + +1. `openspec/changes//decision.md` 存在 +2. 含 `## Alternatives considered`,或显式的 `` +3. `## Verification` 里没有"已确认 X 存在"这类**不可重跑的结论** + +纯机械改动可以豁免。**豁免必须显式,但可以廉价**——目的是让"跳过"变成有意识的选择,而不是静默遗忘。 + +> **计划中的自动化**:`scripts/check-dev-flow.sh`,遵循仓库现有脚本约定(`scripts/*.sh`)。 +> 在它落盘之前,上面三步手动执行。 + +## 目录 + +```text +openspec/changes// +├── proposal.md design.md specs/ tasks.md +└── decision.md ← 人向:为什么、放弃了什么、代价 + ↓ 随归档一起冻存 + +devflow/ +├── glossary/CONTEXT.md ← 跨项目术语 +├── rejected// ← 未实施的提案 +└── index.md ← 由脚本扫描归档生成,不手写 +``` + +`rejected/` 的保留条件:它**仍能防止一个诱人的、有意义的错误**。否则整体删除。 + +## 触发规则 + +- 用户输入 `/dev-flow`。 +- 用户明确要求走开发流程。 + +不要因为任务涉及 OpenSpec、跨模块、架构决策或 devflow 目录就自动触发。 +未被这样要求之前,按普通工程任务处理。 + +## 相关文档 + +- `references/phases.md` — 三阶段契约与判断细则 +- `references/decision-note.md` — `decision.md` 格式规范、校准案例、反模式 +- `skill-workbench/docs/dev-flow/background-and-evolution.md` — 背景、技术演进、设计取舍 diff --git a/.agents/skills/dev-flow/references/decision-note.md b/.agents/skills/dev-flow/references/decision-note.md new file mode 100644 index 0000000..5ae26bb --- /dev/null +++ b/.agents/skills/dev-flow/references/decision-note.md @@ -0,0 +1,230 @@ +# 决策档案格式 + +本文件定义 `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 + +``` + +宁可承认"当时没记录",也不许发明一个假备选。**读者读到一条备选时,必须能相信它真的发生过。** + +### `## 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 行原生 `