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
This commit is contained in:
zhuyongxin
2026-09-18 18:23:18 +08:00
parent d607861c6a
commit 24a71e78f1
14 changed files with 528 additions and 31 deletions
+13 -8
View File
@@ -61,6 +61,7 @@ dev-flow 编排已有能力,**不重复实现它们**:
1. **澄清** —— 用 `grill-with-docs` 的方法,但按上面的 **「冲突裁决」** 覆盖它的默认: 1. **澄清** —— 用 `grill-with-docs` 的方法,但按上面的 **「冲突裁决」** 覆盖它的默认:
范围收窄(只问答案会改变产物的问题)、ADR 落点为 `decision.md`、`CONTEXT.md` 用 `devflow/glossary/` 下那份。 范围收窄(只问答案会改变产物的问题)、ADR 落点为 `decision.md`、`CONTEXT.md` 用 `devflow/glossary/` 下那份。
**被否决的答案当场记进 `decision.md` 的 `## Alternatives considered`**(一行一条:问过什么、为什么输)——这是 Q&A 形态,见 `references/decision-note.md`)。
2. **读上下文**:`devflow/glossary/CONTEXT.md`,并搜索 `devflow/rejected/` 与已归档的 `decision.md`—— 2. **读上下文**:`devflow/glossary/CONTEXT.md`,并搜索 `devflow/rejected/` 与已归档的 `decision.md`——
**避免重新提出已经被否决过的方案。** **避免重新提出已经被否决过的方案。**
3. 写 `openspec/changes/<slug>/proposal.md`,并用 OpenSpec 补全 `design.md` / `specs/` / `tasks.md`。 3. 写 `openspec/changes/<slug>/proposal.md`,并用 OpenSpec 补全 `design.md` / `specs/` / `tasks.md`。
@@ -73,6 +74,9 @@ dev-flow 编排已有能力,**不重复实现它们**:
> 未获授权不得进入 Build。 > 未获授权不得进入 Build。
> **方案讨论中的任何一次确认,都不等于实现授权。** > **方案讨论中的任何一次确认,都不等于实现授权。**
> 快节奏交付下可以预授权,但必须显式:Think 退出时在 `decision.md` 写下
> `<!-- pre-authorized: 日期 + 范围/原因 -->`。静默跳过不允许——旁路是有意识的选择,不是遗忘。
> 授权状态以这行标记为准,恢复会话时读它,不靠对话记忆。
## Build — 做出来 ## Build — 做出来
@@ -82,6 +86,7 @@ dev-flow 编排已有能力,**不重复实现它们**:
- 按 `tasks.md` 的纵向切片实现,按 `specs/` 验收。 - 按 `tasks.md` 的纵向切片实现,按 `specs/` 验收。
- **分步验证**:每完成一层再继续,不要一次写完再验。 - **分步验证**:每完成一层再继续,不要一次写完再验。
- **tasks/design 里提到"参考 XXX 实现"的,实现前先完整读它**——设计文档点名参考而实现时没看,是返工最常见的来源。
- **冲突先分类再处理**: - **冲突先分类再处理**:
| 分类 | 处理 | | 分类 | 处理 |
@@ -104,9 +109,8 @@ Build 结束**自动进入 Close**,中间不设确认点。
1. **补齐 `decision.md`**——把 `## Decision` 校准为**实际发布的做法**(现在时),补 `## Consequences` 和 `## Verification`。 1. **补齐 `decision.md`**——把 `## Decision` 校准为**实际发布的做法**(现在时),补 `## Consequences` 和 `## Verification`。
2. **执行收尾检查**(见下)。 2. **执行收尾检查**(见下)。
3. 有新术语 → 更新 `devflow/glossary/CONTEXT.md`。 3. 检查澄清阶段的新术语**已**沉淀进 `devflow/glossary/CONTEXT.md`(写入时机在术语确认的当场,见「冲突裁决」;这里只查漏)。
4. 有被否决的提案 → 写入 `devflow/rejected/<class>/`。 4. 有被否决的提案 → 确认**已在否决发生时**写入 `devflow/rejected/<class>/`(这里只兜底检查)。
5. **⏸ 确认点 2** —— 汇报产物、验证情况、剩余风险,询问是否归档。
**退出**:`decision.md` 五节齐全;收尾检查通过;用户已被询问是否归档。 **退出**:`decision.md` 五节齐全;收尾检查通过;用户已被询问是否归档。
@@ -122,7 +126,7 @@ Build 结束**自动进入 Close**,中间不设确认点。
| **一个事实只有一个权威** | 见下表 | | **一个事实只有一个权威** | 见下表 |
**"非平凡"的判据**:改变行为、架构、跨文件或跨包的契约、流程或工具、测试策略、磁盘/线缆/配置格式,或其他维护者可能重新考察的决策。 **"非平凡"的判据**:改变行为、架构、跨文件或跨包的契约、流程或工具、测试策略、磁盘/线缆/配置格式,或其他维护者可能重新考察的决策。
**豁免**:纯机械或局部编辑。 **豁免**:纯机械或局部编辑。**小修不值得触发本流程**——dev-flow 是显式触发的 opt-in 流程,轻的第一道防线是不进来;后来发现重要时,允许事后补录 `decision.md`(retrofit 已验证)。
### 事实的唯一权威 ### 事实的唯一权威
@@ -153,8 +157,7 @@ Build 结束**自动进入 Close**,中间不设确认点。
纯机械改动可以豁免。**豁免必须显式,但可以廉价**——目的是让"跳过"变成有意识的选择,而不是静默遗忘。 纯机械改动可以豁免。**豁免必须显式,但可以廉价**——目的是让"跳过"变成有意识的选择,而不是静默遗忘。
> **计划中的自动化**:`scripts/check-dev-flow.sh`,遵循仓库现有脚本约定(`scripts/*.sh`)。 > **已落地**:`scripts/check-dev-flow.sh <slug>` 执行上述三项检查;`--index` 扫描归档派生 `devflow/index.md`(不手写);`--all` 检查全部活跃 change。
> 在它落盘之前,上面三步手动执行。
## 目录 ## 目录
@@ -166,11 +169,12 @@ openspec/changes/<slug>/
devflow/ devflow/
├── glossary/CONTEXT.md ← 跨项目术语 ├── glossary/CONTEXT.md ← 跨项目术语
├── rejected/<class>/ ← 未实施的提案 ├── rejected/<class>/ ← 未实施的提案(否决发生时写入,不是 Close 时)
└── index.md ← 由脚本扫描归档生成,不手写 └── index.md ← scripts/check-dev-flow.sh --index 派生,不手写
``` ```
`rejected/` 的保留条件:它**仍能防止一个诱人的、有意义的错误**。否则整体删除。 `rejected/` 的保留条件:它**仍能防止一个诱人的、有意义的错误**。否则整体删除。
**边界**:`decision.md` 的 Alternatives 记"变更内"的方案备选;`rejected/` 收"整个提案被否决"的方向。提案在确认点 1 被整个否决时,当场写入 `rejected/`——那时流程已终止,Close 不会执行。
## 触发规则 ## 触发规则
@@ -179,6 +183,7 @@ devflow/
不要因为任务涉及 OpenSpec、跨模块、架构决策或 devflow 目录就自动触发。 不要因为任务涉及 OpenSpec、跨模块、架构决策或 devflow 目录就自动触发。
未被这样要求之前,按普通工程任务处理。 未被这样要求之前,按普通工程任务处理。
**`decision.md` 义务只覆盖经本流程的非平凡变更**——不经 dev-flow 的普通工程任务没有这个义务。
## 相关文档 ## 相关文档
@@ -24,7 +24,8 @@
| 节 | 写下时机 | 原因 | | 节 | 写下时机 | 原因 |
|---|---|---| |---|---|---|
| `## Problem` | **Think** | 动机在需求刚澄清时最准确 | | `## Problem` | **Think** | 动机在需求刚澄清时最准确 |
| `## Alternatives considered` | **Think** | 备选是在方案讨论中被否决的,当时记最真实;事后补写会变成回忆录 | | `## Alternatives considered` | **Think**,grill 问答当场 | 备选是在方案讨论中被否决的,当时记最真实;事后补写会变成回忆录 |
| `<!-- pre-authorized: ... -->` | **Think 退出时**(仅预授权场景) | 授权状态的文件载体,恢复会话时以它为准 |
| `## Decision` | Think 记意图,**Close 校准为事实** | 见下 | | `## Decision` | Think 记意图,**Close 校准为事实** | 见下 |
| `## Consequences` | **Close** | 代价要等实现完才知道 | | `## Consequences` | **Close** | 代价要等实现完才知道 |
| `## Verification` | **Close** | 验证要跑过才能写成可重跑的命令 | | `## Verification` | **Close** | 验证要跑过才能写成可重跑的命令 |
@@ -65,6 +66,19 @@
**要求**:每个**真实存在过**的备选一段,加粗开头,说明它**为什么输了**。 **要求**:每个**真实存在过**的备选一段,加粗开头,说明它**为什么输了**。
**首选形态是压缩 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 ```markdown
**新增 year 字段到条目数据。** 冗余且需要同步维护——`date` 已是唯一来源, **新增 year 字段到条目数据。** 冗余且需要同步维护——`date` 已是唯一来源,
派生成本为零,多一个字段就多一处会不一致的地方。 派生成本为零,多一个字段就多一处会不一致的地方。
@@ -78,6 +92,7 @@
| 只列备选,不说为什么输 | 等于没写——读者无法判断它现在是否仍然输 | | 只列备选,不说为什么输 | 等于没写——读者无法判断它现在是否仍然输 |
| 把"什么都不做"当默认备选 | 除非当时真的认真考虑过维持现状,否则它只是修辞 | | 把"什么都不做"当默认备选 | 除非当时真的认真考虑过维持现状,否则它只是修辞 |
| 备选写成一堆标签 | 没有推理链,未来无法复用 | | 备选写成一堆标签 | 没有推理链,未来无法复用 |
| **段落式凑字数** | Q&A 一行能说清的,不要为了"看起来正式"展开成三段 |
**"什么都不做"什么时候是真的备选**:当维持现状确实是一个被权衡过的选项时(例如"暂不修复这个边界,先观察")。 **"什么都不做"什么时候是真的备选**:当维持现状确实是一个被权衡过的选项时(例如"暂不修复这个边界,先观察")。
@@ -146,7 +161,7 @@
| 方案 | 被拒原因 | | 方案 | 被拒原因 |
|------|---------| |------|---------|
| YAML 全优先 | 字段名不一致(date vs created) | | YAML 全优先 | 字段名不一致(date vs created) |
| 目录名全优先 | 标题中的语义信息在下划线转空格后丢失 | | 目录名全优先 | 标题中的语义信息在下划线转空格后丢失 |
``` ```
@@ -154,6 +169,20 @@
**注意它的形式**:两条备选,每条一句被拒理由,没有废话。**短不等于差。** **注意它的形式**:两条备选,每条一句被拒理由,没有废话。**短不等于差。**
### 值得写深的备选(真实)
SuperBizAgent 的 `phase1-infrastructure/decisions.md` ADR-001(Flyway):
```markdown
### 后果
- 表结构修改必须通过 SQL 迁移脚本
- 开发环境首次启动需要执行 Flyway 迁移
- 生产环境部署自动执行未执行的迁移脚本
```
难以逆转的基础设施决策,后果写得具体到"改表必须走迁移脚本"——三个月后最需要读的就是这几行。
**这是 Q&A 一行记不住的那种备选,值得整段。**
### 应当补录备选(真实) ### 应当补录备选(真实)
`openspec/changes/add-year-filter/` 那次变更,实际发生过两条备选,但一条都没记: `openspec/changes/add-year-filter/` 那次变更,实际发生过两条备选,但一条都没记:
+13 -4
View File
@@ -16,7 +16,7 @@
> **relentless 是对问题质量的,不是对数量的。** > **relentless 是对问题质量的,不是对数量的。**
> 只问**答案会改变** `proposal.md` / `specs/` / `tasks.md` 的问题;不会改变产物的疑问,自己判断即可。 > 只问**答案会改变** `proposal.md` / `specs/` / `tasks.md` 的问题;不会改变产物的疑问,自己判断即可。
**先自己查证,再问用户。** 查证时格外看两处——它们专门用来避免重复劳动: **先自己查证,再问用户。** 查证时格外看四处——前两处专门用来避免重复劳动:
1. 搜 `devflow/rejected/` —— 这个方案是不是已经被否决过? 1. 搜 `devflow/rejected/` —— 这个方案是不是已经被否决过?
2. 搜已归档的 `decision.md` —— 相关决策的历史理由是什么? 2. 搜已归档的 `decision.md` —— 相关决策的历史理由是什么?
@@ -44,10 +44,12 @@ openspec/specs/ ← 相关的既有能力规格
- 关键假设(没有验证、但方案依赖它的部分) - 关键假设(没有验证、但方案依赖它的部分)
- 主要风险 - 主要风险
- **放弃的备选**(1–2 条,连同为什么) - **放弃的备选**(1–2 条,连同为什么)
- 请求实现授权 - 请求实现授权(或记录 `<!-- pre-authorized: 日期 + 范围/原因 -->`)
**不要**复述 proposal、不要列产物清单、不要贴对齐表格。用户要的是判断依据,不是工作汇报。 **不要**复述 proposal、不要列产物清单、不要贴对齐表格。用户要的是判断依据,不是工作汇报。
**pre-authorized 是显式旁路,不是默认。** 它适合用户已在对话中明确说"直接做"的快节奏场景。写它 = 承认确认点被有意识地跳过;不写而直接进 Build = 违规。授权状态的唯一载体是这行标记——恢复会话时以它为准。
--- ---
## Build — 做出来 ## Build — 做出来
@@ -58,6 +60,12 @@ openspec/specs/ ← 相关的既有能力规格
第一个切片完成后,对照 `specs/` 检查一次:验收场景是否真的成立。**规格写得含糊会在这里第一次暴露。** 第一个切片完成后,对照 `specs/` 检查一次:验收场景是否真的成立。**规格写得含糊会在这里第一次暴露。**
### 参考实现先读,再动手
`tasks.md` / `design.md` 点名"参考 XXX 实现"的,**实现前完整读它**。路径不明确时按类名/模式 Grep 定位。
设计文档点名参考而实现时没看,是返工最常见的来源(SuperBizAgent 复盘:山东商客接口因此返工 4-5 次)。
第一次在某区域写代码、或要用项目现有基础设施(MQ/统一请求结构/工具类)时,同此处理。
### 冲突三分法的判断细则 ### 冲突三分法的判断细则
先分类,再动手。分类的错误比不分类更糟。 先分类,再动手。分类的错误比不分类更糟。
@@ -123,8 +131,9 @@ Think 阶段已经写下了 `## Problem` 和 `## Alternatives considered`。Clos
|---|---| |---|---|
| 没有 `openspec/changes/<slug>/` | Think 之前 | | 没有 `openspec/changes/<slug>/` | Think 之前 |
| 有 `proposal.md`,但 `decision.md` 缺 `## Problem` | Think 进行中 | | 有 `proposal.md`,但 `decision.md` 缺 `## Problem` | Think 进行中 |
| 有 `proposal.md` 和 `decision.md`,但没有实现授权 | **确认点 1** | | 有 `proposal.md` 和 `decision.md`,但没有 `<!-- pre-authorized` 也没有对话中的明确授权 | **确认点 1** |
| 有实现,但 `decision.md` 缺 `## Decision` / `## Consequences` / `## Verification` | Close 进行中 | | 有 `<!-- pre-authorized: ... -->` 或已获授权,实现未完成 | Build 进行中 |
| 实现完成,`decision.md` 缺 `## Decision` / `## Consequences` / `## Verification` | Close 进行中 |
| `decision.md` 五节齐全 | 收尾检查 + 确认点 2 | | `decision.md` 五节齐全 | 收尾检查 + 确认点 2 |
**恢复时不重新提问已经解决的问题。** 先读 `decision.md` 和 `proposal.md`——已经确认过的内容就在里面。 **恢复时不重新提问已经解决的问题。** 先读 `decision.md` 和 `proposal.md`——已经确认过的内容就在里面。
+1 -1
View File
@@ -16,7 +16,7 @@
### 真理源 (Source of Truth) ### 真理源 (Source of Truth)
- **日期**和**短标识符 (slug)**:由目录名 `knowledge_YYYYMMDD_Slug/` 提取。 - **日期**和**短标识符 (slug)**:由目录名 `knowledge_YYYYMMDD_Slug/` 提取。
- **显示标题**、**tags**、**author**:由 YAML frontmatter 提取。 - **显示标题**、**tags**、**author**:由 YAML frontmatter 提取。
详见 [ADR-001](./adr/0001-directory-name-as-truth-source.md)。 详见 [decision.md](../../openspec/changes/knowledge-index-panel/decision.md)(原 ADR-001,2026-09-18 迁移)。
### 短标识符 (Slug) ### 短标识符 (Slug)
目录名中 `YYYYMMDD_` 之后的部分。用于脚本定位目录和构建文件路径。不用于显示(显示标题来自 YAML frontmatter 的 `title` 字段)。 目录名中 `YYYYMMDD_` 之后的部分。用于脚本定位目录和构建文件路径。不用于显示(显示标题来自 YAML frontmatter 的 `title` 字段)。
+11 -12
View File
@@ -1,14 +1,13 @@
# Devflow Index # devflow 索引
`devflow/index.md` 是 Phase 0.5 的默认上下文入口。每个 `devflow/projects/YYYY-MM-DD-{slug}/` 默认维护一行,帮助代理先定位相关项目,再读取具体 brief、acceptance、ADR 或 compound knowledge。 > 本文件由 scripts/check-dev-flow.sh --index 派生生成,**不要手写**。
> 存储层是 openspec/archive/ 下的归档目录;此处只是浏览视图。
| 日期 | slug | 领域 | 关键词 | 关联 OpenSpec | 状态 | | 日期 | 标题 | 归档位置 |
| --- | --- | --- | --- | --- | --- | |---|---|---|
| 2026-05-18 | knowledge-index-panel | knowledge-index | knowledge-index, search, tags, static-html | `openspec/changes/knowledge-index-panel/` | active | | 2026-05-25 | knowledge-index-sort Proposal | openspec/changes/archive/2026-05-25-knowledge-index-sort |
| 2026-05-19 | add-clear-filters | knowledge-index | clear-filters, search, tags, year-filter | `openspec/changes/archive/2026-05-19-add-clear-filters/` | archived | | 2026-09-18 | Decision: 建立旧 devflow 档案的迁移规则 | openspec/changes/archive/2026-09-18-migrate-devflow-archives |
| 2026-05-19 | add-year-filter | knowledge-index | year-filter, index-panel, frontmatter | `openspec/changes/add-year-filter/` | active | | 2026-05-19 | 2026-05-19-add-clear-filters | openspec/archive/2026-05-19-add-clear-filters |
| 2026-05-19 | dev-flow-skill-evaluation | sm-flow | dev-flow, sm-flow, skill-evaluation, fallback, phase-contracts | 未关联 | accepted-unarchived | | 2026-05-21 | 2026-05-21-sm-flow-v3-1-upgrade | openspec/archive/2026-05-21-sm-flow-v3-1-upgrade |
| 2026-05-21 | sm-flow-v3-1-upgrade | sm-flow | interface-impact, commit-gate, devflow-index, apply-conflict, skill-compatibility | `openspec/changes/archive/2026-05-21-sm-flow-v3-1-upgrade/` | archived | | 2026-07-05 | Validate SM Flow Explicit Trigger | openspec/archive/2026-07-05-validate-sm-flow-explicit-trigger |
| 2026-05-22 | sm-flow-execution-hardening | sm-flow | execution-hardening, checklist, phase-gate, cross-artifact, question-pool | 未关联 | active | | 2026-07-05 | Validate SM Flow Standard Change | openspec/archive/2026-07-05-validate-sm-flow-standard-change |
| 2026-07-05 | validate-sm-flow-explicit-trigger | sm-flow | explicit-trigger, scale-source, fallback, validation | `openspec/archive/2026-07-05-validate-sm-flow-explicit-trigger/` | archived |
| 2026-07-05 | validate-sm-flow-standard-change | sm-flow | standard-change, full-artifacts, evidence, validation | `openspec/archive/2026-07-05-validate-sm-flow-standard-change/` | archived |
@@ -0,0 +1,70 @@
# Decision: 年份筛选采用纯前端派生,不新增数据字段
## Problem
知识索引只能按关键词和标签过滤。当条目跨越多个月份和年份时,列表只会越来越长,而用户缺少一个**粗粒度**的收敛手段——想回看某一年沉淀了什么,只能靠肉眼扫或者记住关键词。
而"年份"这个维度其实**已经躺在数据里**了:每个条目的 `date` 字段(格式 `YYYYMMDD`)是渲染日期时的唯一来源。也就是说这不是一个需要新增信息的需求,而是一个**已经存在的信息没有暴露给用户**的问题。
## Decision
年份从 `ENTRIES[].date.slice(0,4)` 派生,**不新增数据字段**。
- 控件用原生 `<select id="year-filter">`,位置在搜索框下方、标签云上方,与搜索、标签同属一组合并过滤。
- 选项从条目里**实际出现的**年份去重生成、倒序排列;只接受能匹配 `/^\d{4}$/` 的值。默认"全部年份"。
- 过滤并入现有 `applyFilters()` 作为**单一过滤入口**,顺序为 `年份 → 标签 → 搜索`。
- 修改在 `scripts/update-knowledge-index.sh` 的 HTML 模板里完成,再重建 `knowledge-index.html`。
## Alternatives considered
**新增 `year` 字段到条目数据。** 冗余——`date` 已是唯一来源,派生成本为零,多一个字段就多一处可能不一致的地方。而且条目是 `knowledge_*/` 下的手写 markdown,新增字段意味着所有历史条目都要回填。
**引入第三方筛选组件(Select2 等)。** 为 4 行原生 `<select>` 代码引入依赖,与仓库"单文件静态 HTML、零依赖"的约束直接冲突。
**只修改生成后的 `knowledge-index.html`,不改生成脚本。** 更快,但 `knowledge-index.html` 是**生成产物**——下一次重建即丢功能,生成脚本才是真理源。这条在实现过程中确实被识别为"主要风险"(见 `design.md` 的架构审计一节),但**当时没有被记成一条备选**。它是一个真实的、有诱惑力的错误选项,所以补录在这里。
## Consequences
**得到**
- 零依赖、无数据迁移,没有新字段需要维护一致性。
- 与既有搜索、标签过滤自然组合,因为三者走同一个 `applyFilters()` 入口。
- 年份选项只列**实际存在**的年份,不会出现空年份。
- 重建索引不会丢功能——修改落在生成脚本的模板里。
**代价**
- **只有年粒度**:不支持月份、日期范围、时间线视图或自定义排序。
- `<select>` 选项数量随年份**线性增长**。以当前条目量完全可接受;多年后可能需要改成可搜索的下拉。
- 过滤在前端全量进行。条目数量级显著变大时需要改为服务端或预建索引方案。
**已知限制**
- `date` 为空或不匹配 `YYYYMMDD` 的条目:不进入年份选项,在"全部年份"下仍显示,选中具体年份时隐藏。
- 年份比较基于**字符串前四位**,不做真实日期解析——因此 `20260230` 这类非法日期会被当作 2026 年处理。这是有意的取舍:真实解析会引入日期库依赖,而收益只覆盖一个不会出现的输入。
## Verification
以下命令均在仓库根目录执行,路径为相对路径。
**只读(可直接跑,已在本次收尾时实测)**
- 控件同时存在于生成产物与模板两处
`grep -c 'id="year-filter"' knowledge-index.html scripts/update-knowledge-index.sh`
→ `knowledge-index.html: 1`,`scripts/update-knowledge-index.sh: 1`
- 年份过滤确实并入唯一入口 `applyFilters()`(函数体起始于第 217 行)
`grep -n 'selectedYear' knowledge-index.html`
→ `219`(读取控件值)、`228`(判断)、`229`(执行过滤)——三处都在函数体内
- 生成脚本语法完整(只做语法检查,不执行)
`bash -n scripts/update-knowledge-index.sh` → `exit=0`
**有副作用(会重写 `knowledge-index.html`,故未在收尾时运行)**
- 重建后功能仍在
`bash scripts/update-knowledge-index.sh`,然后复跑上面两条 `grep`
**人工**
选择 `2026` → 只显示 `date` 以 `2026` 开头的条目;再激活一个标签 → 两者取交集;选"全部年份" → 年份过滤解除,而标签仍然生效。
@@ -0,0 +1,49 @@
# Decision: 建立旧 devflow 档案的迁移规则
<!-- authorized: 2026-09-18 确认点1通过,用户对话授权("继续"),范围:单样本迁移+本change四件套 -->
## Problem
dev-flow 取消了 devflow 作为"人类档案层"的定位(2026-09-18 重设计),决策档案的唯一落点变为 `openspec/changes/<slug>/decision.md`。但历史遗留 9 个项目、37 个文件仍在 `devflow/projects/` 下,按四套并存的命名约定组织,与 OpenSpec 归档内容大量重复。
真实成本:同一次变更的事实存在两处权威(如 `add-year-filter` 的 design 双写),而最稀缺的内容——备选方案——全库仅 1/37 被记录。不迁移,则 dev-flow 的新真理源布局与旧档案层长期并存,"一个事实只有一个权威"持续被违反;全删,则丢失 ADR-0001 等真实决策资产。
## Alternatives considered
- Q: 全部删除 devflow/projects/? A: 不——ADR-0001(knowledge-index-panel)与部分 decisions.md 的关键取舍是真实决策资产,删了就丢。
- Q: 全部原地保留、只加链接? A: 不——治标;活跃 change 缺 decision.md 的检查缺口仍在,重复权威仍在。
- **只迁移值得留的,骨架删除,以单样本先行定规则。** 全量一次迁移风险不可控(37 文件四套约定);单样本验证规则后再批量。
## Decision
**以"寿命规则"处置旧 devflow 档案:跨变更活的并入新真理源,随变更死的不留;单样本先行,批量另立 change。**(2026-09-18 实际执行)
- 单样本 `2026-05-18-knowledge-index-panel`:ADR-0001 五节转译为 `openspec/changes/knowledge-index-panel/decision.md`(备选转 Q&A 形态,`migrated-from` 注释溯源);PRD 收存为 change 内 `prd.md`(冻结);glossary 引用同步更新
- 首次迁移**不删除**源文件——删除等批量规则稳定后统一执行
- 迁移规则五步(评估→转译→收存→改引用→暂不删)沉淀在 `design.md` 的 Migration Plan,批量迁移按此执行
计划与执行一致,无偏差。
## Consequences
**买到了**:
- knowledge-index-panel 由检查 ✗ 转 ✓,决策资产进入新真理源布局
- 迁移规则经一次真实执行验证,批量迁移(其余 8 项目)有了可引用的依据
- dev-flow 全路径(Think→确认点1→Build→Close)首次完整运行,协议自举验证
**付出了**:
- 旧 ADR-0001 与新 decision.md 双存在,直到批量清理——期间旧文件无入边(glossary 已改指新位置),漂移风险低但非零
- PRD 中 Implementation Decisions 小节与 design.md 内容有重叠——PRD 按历史文档冻结收存,接受
- 迁移是**补录**(retrofit):Alternatives 是从 ADR-0001 转译而非 grill 当场记录,形态合规但时机不理想——这正是它作为"历史迁移"而非"新变更"的本性
## Verification
**只读(可直接跑)**
- `bash scripts/check-dev-flow.sh knowledge-index-panel` → 3 项全过,exit=0
- `bash scripts/check-dev-flow.sh migrate-devflow-archives` → 3 项全过,exit=0
- `grep -c 'decision.md' devflow/glossary/CONTEXT.md` → ≥1(引用已指向新位置)
- `head -3 openspec/changes/knowledge-index-panel/decision.md` → 首行为 `# Decision: 目录名作为日期和标题的真理源`
**有副作用(会重写文件,收尾时不必跑)**
- `bash scripts/check-dev-flow.sh --index`(重生成 devflow/index.md)
@@ -0,0 +1,47 @@
## Context
dev-flow 重设计后,决策档案唯一落点为 `openspec/changes/<slug>/decision.md`。旧 `devflow/projects/`(9 项目/37 文件,四套命名约定并存)需按"寿命规则"处置:**跨变更活的留(并入 decision.md 或 glossary),随变更死的不留(骨架删除)**。
本 change 只执行单样本(`2026-05-18-knowledge-index-panel`),验证规则;批量迁移是后续独立 change。
## Goals / Non-Goals
**Goals**
- knowledge-index-panel 获得合规的 decision.md(迁移自 ADR-0001,五节俱全)
- PRD 独有内容(User Stories、测试决策、Out of Scope)不丢失
- glossary 引用不因迁移产生死链
- 迁移规则本身作为 decision 沉淀,可被批量迁移引用
**Non-Goals**
- 不批量处理其余 8 个项目
- 不删除 devflow/projects/ 任何文件(删除动作在批量规则稳定后)
- 不处理 add-year-filter(已有 decision.md)与 sm-flow-execution-hardening(待其自身 Close)
## Decisions
| 决策 | 理由 | 备选 |
|---|---|---|
| ADR-0001 转译为 decision.md 而非原样复制 | 旧格式(背景/决策/替代方案/后果)→ 新五节骨架,Alternatives 转 Q&A 形态;`migrated-from` 注释保留溯源 | 原样复制(格式两套并存) |
| PRD 收存为 change 内 `prd.md` | User Stories/测试决策/Out of Scope 是 proposal 没有的独有内容;v3.1 规则"复杂需求才留 PRD"在此适用 | 删除(丢失独有内容)/留在 devflow(违反唯一权威) |
| 溯源用 HTML 注释而非正文小节 | 注释不渲染不干扰阅读,机器可查 | 正文"来源"小节(视觉噪音) |
| 首次迁移不删源文件 | 删除是破坏性动作;规则未经批量验证前保守 | 迁移即删(不可逆风险) |
## Risks / Trade-offs
- **devflow/projects 旧文件与新 decision.md 短暂双存在**(至批量清理):接受——单样本期间删除更危险;glossary 已指向新位置,旧 ADR 无入边
- **PRD 中 Implementation Decisions 小节与 design.md 可能重复**:接受——PRD 是历史文档,冻结收存,不回改
## Migration Plan
单样本流程(批量迁移按此规则执行):
1. 评估旧项目文件:有独有决策/备选/取舍 → 并入对应 change 的 decision.md;纯骨架/与 OpenSpec 重复 → 标记待删
2. 转译格式:旧四节 → 新五节,备选转 Q&A 形态,加 `migrated-from` 溯源注释
3. PRD 等独有产物收存进 change 目录(冻结,不回改)
4. glossary 等活文件中的路径引用同步更新
5. 源文件暂不删,批量规则稳定后统一清理
## Open Questions
- 其余 8 个项目中,`dev-flow-skill-evaluation` 的评估结论已沉淀进 workflow.md 演进史,其 todo.md 大概率可删——留批量时判断
- `sm-flow-v3-1-upgrade` 等后期项目的 decisions.md 含真实取舍(术语裁决、产物瘦身决策),逐条评估是否值得并入 sm-flow 归档——批量时逐项目处置
@@ -0,0 +1,26 @@
## Why
dev-flow 重设计(2026-09-18)取消了 devflow 作为独立档案层的定位:决策档案唯一落点为 `openspec/changes/<slug>/decision.md`,随归档冻结;devflow/ 收缩为 glossary + rejected + 派生 index。历史遗留 9 个项目、37 个文件需要按新布局处置,否则新旧两种真理源布局长期并存。
SuperBizAgent-java 实测(46 项目/180 文件)已给出处置口径:压缩 Q&A 与 ADR 段落并入对应归档的 decision.md,骨架文件删除。本仓库先以单样本(`2026-05-18-knowledge-index-panel`)验证规则,再定批量执行。
## What Changes
- 为 `openspec/changes/knowledge-index-panel/` 新建 `decision.md`:内容迁移自 `devflow/projects/2026-05-18-knowledge-index-panel/adr/0001-directory-name-as-truth-source.md`(五节俱全),标注迁移来源
- `knowledge-index-panel-prd.md` 含独有内容(User Stories、测试决策),收存为 `openspec/changes/knowledge-index-panel/prd.md`
- 更新 `devflow/glossary/CONTEXT.md` 中对 ADR-0001 旧路径的引用,指向新位置
- 本 change(`migrate-devflow-archives`)自身按 dev-flow 协议执行,其 decision.md 记录迁移规则本身
- 其余 8 个项目:本次不动(见 Non-Goals)
## Non-Goals
- 不批量迁移其余 8 个项目——单样本验证规则后另行执行
- 不删除 `devflow/projects/` 下任何现有文件——删除等批量规则稳定后统一处理
- 不处理 3 个活跃 change 中另外两个(`add-year-filter` 已有 decision.md;`sm-flow-execution-hardening` 待其自身 Close)
## Impact
- `openspec/changes/knowledge-index-panel/` 新增 2 文件(decision.md、prd.md)
- `devflow/glossary/CONTEXT.md` 改 1 处链接
- `scripts/check-dev-flow.sh knowledge-index-panel` 应由 ✗ 转 ✓
- 迁移规则沉淀在 `openspec/changes/migrate-devflow-archives/decision.md`,作为后续批量迁移的依据
@@ -0,0 +1,11 @@
# Tasks: migrate-devflow-archives
- [x] 1. Think:写 proposal.md + decision.md 的 Problem/Alternatives(迁移规则作为备选记录)
- [x] 2. 确认点 1:汇报方案,获得授权并记录
- [x] 3. 单样本:knowledge-index-panel/decision.md 迁移自 ADR-0001(五节转译,Q&A 备选,migrated-from 溯源)
- [x] 4. 单样本:prd.md 收存进 change 目录
- [x] 5. glossary/CONTEXT.md 的 ADR-0001 引用更新为新路径
- [x] 6. design.md(迁移规则细节)与 tasks.md 落盘
- [x] 7. Close:补齐本 change decision.md 的 Decision/Consequences/Verification
- [x] 8. 验证:`check-dev-flow.sh knowledge-index-panel` 与 `check-dev-flow.sh migrate-devflow-archives` 均通过;`--index` 重生成
- [x] 9. 确认点 2:汇报产物与剩余风险,用户确认归档(2026-09-18,"继续")
@@ -0,0 +1,42 @@
# Decision: 目录名作为日期和标题的真理源
<!-- migrated-from: devflow/projects/2026-05-18-knowledge-index-panel/adr/0001-directory-name-as-truth-source.md (2026-09-18, change: migrate-devflow-archives) -->
## Problem
知识吸收器输出目录遵循 `knowledge_YYYYMMDD_Title/` 命名约定。目录内的 `.md` 文件包含 YAML frontmatter,其中也有 `date`/`created` 和 `title` 字段,但实际使用中发现字段名不一致(`date` vs `created`)且格式有差异。
索引重建脚本需要确定日期和标题的权威来源——两个来源都存在且都可能被读到时,不指定权威就必然漂移。
## Decision
**日期和短标识符 (slug) 的真理源是目录名。显示标题、tags、author 的真理源是 YAML frontmatter。**
- 日期从目录名的 `YYYYMMDD` 部分提取
- 短标识符从目录名的 `Slug` 部分提取(用于文件路径构建,不用于显示)
- 显示标题从 YAML frontmatter 的 `title` 字段提取
- `tags` 和 `author` 从 YAML frontmatter 提取
## Alternatives considered
- Q: YAML 字段全优先? A: 不——字段名不一致(date vs created),脚本要处理变体,漂移点更多。
- Q: 目录名全优先? A: 不——标题中的语义信息在下划线转空格后丢失(如 `mattpocock_skills` → `mattpocock skills`,实际标题更长更精确)。
## Consequences
**买到了**:
- 索引脚本不需要处理日期字段名变体(date vs created)
- 目录名是可见的、可审计的——与 `ls` 输出完全一致
- bash 用 glob 匹配 `knowledge_*` 目录,天然获取了日期和 slug
- 显示标题从 YAML 取,支持完整的、精确的标题文本(含标点、中文)
**付出了**:
- 索引脚本需要解析 YAML frontmatter(复杂度比仅取目录名高)
- 如果目录被重命名,slug 会变化但显示标题不受影响(可接受的行为)
- 知识吸收器 skill 必须严格遵守 `knowledge_YYYYMMDD_Slug` 命名约定
## Verification
**只读(可直接跑)**
- `ls knowledge/entries/ | head -3` → 目录名均形如 `knowledge_YYYYMMDD_Slug`
- `grep -n 'date' scripts/update-knowledge-index.sh | head -5` → 日期提取自目录名而非 YAML
@@ -0,0 +1,57 @@
# PRD: 知识库索引面板 (Knowledge Index Panel)
## Problem Statement
用户每次用 `/knowledge-absorber` 学习后,会在项目根目录生成 `knowledge_YYYYMMDD_Title/` 文件夹。目前有 2 个知识条目,预计每月增长 5-10 个。所有条目散落在根目录,没有跨条目的导航或搜索能力。想找之前学过的内容只能手动翻目录,搜索成本随条目数线性上升。
## Solution
在项目根目录生成一个 **纯静态的 `knowledge-index.html`**,浏览器直接打开即可使用。它能自动发现所有 knowledge 条目,提供标签关联和全文搜索。不需要任何服务器、数据库或外部依赖。
## User Stories
1. As a 学习者, I want to see all my knowledge entries on one page with dates, so that I can quickly find what I studied and when
2. As a 学习者, I want to search across all knowledge entries by keyword, so that I can find "that thing about Redis" in 2 seconds instead of manually opening 10 folders
3. As a 学习者, I want to click a tag like "Claude Code" and see all related entries, so that I can review interconnected topics
4. As a 学习者, I want the index to auto-discover new knowledge entries, so that I don't need to manually update anything after running `/knowledge-absorber`
5. As a 学习者, I want the page to work offline by just double-clicking the HTML file, so that I can use it without internet or any setup
6. As a 学习者, I want to see which entries are related to each other via shared tags, so that I can discover connections between topics I've studied
7. As a 学习者, I want the search to highlight matching text, so that I can see at a glance why an entry matched my query
8. As a 学习者, I want to see a count of entries per tag, so that I know which topics I've studied most
## Implementation Decisions
- **Single HTML file**: All CSS and JS inlined in `knowledge-index.html`. No external files or CDN dependencies
- **Manifest-based discovery**: A JavaScript array inside the HTML lists all known knowledge directory names. Browser security sandbox prevents dynamic directory listing via `fetch()`
- **YAML frontmatter parsing**: Regex-based extraction from `.md` files. Simple key:value parsing only — no full YAML spec support
- **Client-side search**: All `.md` content loaded into memory at page init. Real-time filtering with `String.includes()`, case-insensitive. `<mark>` tags for highlighting
- **Tag system**: In-memory `Map<tag, entry[]>` built at load time. Click to filter, click again to deselect
- **Related Content**: Computed from shared tags, displayed per-entry
- **No pagination**: Designed for < 50 entries. Full list rendered at once
- **UTF-8 encoding**: All files assumed UTF-8. `fetch()` handles encoding detection
## Testing Decisions
- **What makes a good test**: Test external behavior — "user types keyword → correct entries appear" — not internal state
- **Test with real data**: Use existing 2 knowledge entries (`knowledge_20260417_Waza`, `knowledge_20260518_mattpocock_skills`) as test fixtures
- **Edge cases to verify**:
- Missing directory (manifest lists a name that doesn't exist) → skip gracefully
- Malformed YAML → display directory name as fallback
- Empty tags array → no tag badges rendered
- No search results → show "没有找到相关条目" message
- Browser CORS when opened via `file://` protocol → document the workaround
## Out of Scope
- Google Drive / cloud sync
- Editing knowledge entries
- Full YAML 1.2 spec compliance
- Pagination or virtual scrolling
- Fuse.js or other fuzzy search libraries (keep it zero-dependency)
- Dark mode toggle (can add later, not in v1)
## Further Notes
- The manifest maintenance problem (adding new entries) can be solved in a future iteration by modifying the `/knowledge-absorber` skill to auto-append to the manifest array
- The `file://` protocol CORS limitation on Chrome means users may need to serve via `python -m http.server` or similar for full functionality
- Tag counts provide a natural "learning heatmap" — most-studied topics bubble to the top
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env bash
set -euo pipefail
# dev-flow 收尾检查 + index 派生
# 检查模式: check-dev-flow.sh <slug> → 对 openspec/changes/<slug>/ 执行三项收尾检查
# 索引模式: check-dev-flow.sh --index → 扫描 openspec/changes/archive/ 与 openspec/archive/ 生成 devflow/index.md
# 全量模式: check-dev-flow.sh --all → 对所有活跃 change 执行检查
#
# 三项收尾检查:
# 1. decision.md 存在(豁免需显式: decision.md 或提交信息中写明)
# 2. 含 ## Alternatives considered 或显式 <!-- alternatives-not-recorded -->
# 3. ## Verification 无"已确认 X"类不可重跑结论
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CHANGES_ROOT="$PROJECT_ROOT/openspec/changes"
fail() { echo "✗ $1" >&2; exit 1; }
check_change() {
local slug="$1"
local dir="$CHANGES_ROOT/$slug"
local decision="$dir/decision.md"
local errors=0
[ -d "$dir" ] || fail "change 目录不存在: openspec/changes/$slug"
echo "== dev-flow 收尾检查: $slug =="
# 1. decision.md 存在
if [ ! -f "$decision" ]; then
echo "✗ [1] 缺少 openspec/changes/$slug/decision.md (纯机械改动可豁免,但豁免必须显式写在提交信息里)"
errors=$((errors+1))
else
echo "✓ [1] decision.md 存在"
# 2. Alternatives 强制
if grep -q '^## Alternatives considered' "$decision" || grep -q '<!-- alternatives-not-recorded -->' "$decision"; then
echo "✓ [2] 备选已记录或显式声明未记录"
else
echo "✗ [2] decision.md 缺少 ## Alternatives considered,也没有 <!-- alternatives-not-recorded --> 显式豁免"
errors=$((errors+1))
fi
# 3. Verification 无不可重跑结论
local vtext
vtext="$(awk '/^## Verification/{flag=1;next} /^## /{flag=0} flag' "$decision")"
if echo "$vtext" | grep -qE '已确认|已验证[^,。;(]|测试通过$'; then
echo "✗ [3] ## Verification 含不可重跑的结论(如\"已确认 X 存在\")——改为可重跑的命令或明确的人工步骤"
errors=$((errors+1))
elif [ -z "$vtext" ]; then
echo "✗ [3] 缺少 ## Verification 小节"
errors=$((errors+1))
else
echo "✓ [3] Verification 通过"
fi
fi
if [ "$errors" -gt 0 ]; then
echo "== $slug: $errors 项未通过 =="
return 1
fi
echo "== $slug: 全部通过 =="
return 0
}
gen_index() {
local out="$PROJECT_ROOT/devflow/index.md"
echo "# devflow 索引" > "$out"
echo "" >> "$out"
echo "> 本文件由 scripts/check-dev-flow.sh --index 派生生成,**不要手写**。" >> "$out"
echo "> 存储层是 openspec/archive/ 下的归档目录;此处只是浏览视图。" >> "$out"
echo "" >> "$out"
local found=0
local rows=()
local arch_dir
# 两个历史归档位置都扫:openspec/changes/archive/ 与 openspec/archive/
for arch_dir in "$CHANGES_ROOT/archive" "$PROJECT_ROOT/openspec/archive"; do
[ -d "$arch_dir" ] || continue
local d
while IFS= read -r d; do
d="${d%/}"
[ -d "$d" ] || continue
local name; name="$(basename "$d")"
local title; title=""
local date; date=""
local rel; rel="${d#"$PROJECT_ROOT"/}"
# 标题取 decision.md 首行,否则 proposal.md 首个 # 标题
if [ -f "$d/decision.md" ]; then
title="$(head -1 "$d/decision.md" | sed 's/^# *//; s/^[[:space:]]*//; s/[[:space:]]*$//' || true)"
elif [ -f "$d/proposal.md" ]; then
title="$(grep -m1 '^# ' "$d/proposal.md" | sed 's/^# *//' || true)"
fi
# 日期取目录名前缀 YYYY-MM-DD
if [[ "$name" =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2}) ]]; then
date="${BASH_REMATCH[1]}"
fi
rows+=("| ${date:-—} | ${title:-$name} | ${rel} |")
found=$((found+1))
done < <(find "$arch_dir" -mindepth 1 -maxdepth 1 -type d | sort)
done
# 写表格(带表头)
{
echo "| 日期 | 标题 | 归档位置 |"
echo "|---|---|---|"
if [ ${#rows[@]} -gt 0 ]; then
printf '%s\n' "${rows[@]}"
fi
} >> "$out"
echo "[index] 生成 $found 个归档条目 → devflow/index.md"
}
case "${1:-}" in
--index)
gen_index
;;
--all)
rc=0
for d in "$CHANGES_ROOT"/*/; do
d="${d%/}"
[ -d "$d" ] || continue
name="$(basename "$d")"
[ "$name" = "archive" ] && continue
check_change "$name" || rc=1
done
exit $rc
;;
"")
echo "用法: check-dev-flow.sh <slug> | --all | --index" >&2
echo " <slug> 对单个活跃 change 执行三项收尾检查" >&2
echo " --all 检查所有活跃 change" >&2
echo " --index 扫描归档生成 devflow/index.md" >&2
exit 2
;;
*)
check_change "$1"
;;
esac
@@ -547,13 +547,25 @@ Change 'add-year-filter' has issues
| # | 问题 | 处置 | | # | 问题 | 处置 |
|---|---|---| |---|---|---|
| 1 | 旧的 `.claude/skills/dev-flow/`(2.0.0,213 行)同名但设计相反 | **用户后续处理** | | 1 | 旧的 `.claude/skills/dev-flow/`(2.0.0,213 行)同名但设计相反 | **已删除**(2026-09-18) |
| 2 | `sm-flow` 归档 | **用户后续处理**(dev-flow 实现完成之后) | | 2 | `sm-flow` 归档 | **改造完成后归档**。完成定义:`scripts/check-dev-flow.sh` 落盘 ✅ + 一次从 Think 开始的全路径真实运行 + 一条旧档案迁移规则(单样本) |
| 3 | 现有 3 个活跃 change(`knowledge-index-panel` / `add-year-filter` / `sm-flow-execution-hardening`) | **本次忽略** | | 3 | 现有 3 个活跃 change(`knowledge-index-panel` / `add-year-filter` / `sm-flow-execution-hardening`) | **本次忽略** |
| 4 | 现有 9 个项目 37 个 devflow 文件的迁移 | 未定;建议先做单样本再定规则 | | 4 | 现有 9 个项目 37 个 devflow 文件的迁移 | 未定;先做单样本再定规则。SuperBizAgent 实测(46 项目/180 文件)给出处置口径:压缩 Q&A 与 ADR 段落并入对应归档的 decision.md,骨架文件删除 |
| 5 | `rejected/` 是否照搬 DSH 的 6 类关闭集 | 沿用,未验证 | | 5 | `rejected/` 是否照搬 DSH 的 6 类关闭集 | 沿用,未验证 |
| 6 | "计划写在 `decision.md` 却从未更新为事实"会不会出现 | 观察到再引入 DSH 的两套骨架 | | 6 | "计划写在 `decision.md` 却从未更新为事实"会不会出现 | 观察到再引入 DSH 的两套骨架 |
| 7 | `devflow/index.md` 与 `scripts/check-dev-flow.sh` 尚未编写 | 脚本待写;index 暂手工 | | 7 | `scripts/check-dev-flow.sh` 与 `devflow/index.md` | **已落盘**(2026-09-18):`<slug>` / `--all` / `--index` 三种模式,三项收尾检查 + index 派生均已实测 |
### 9.1 SuperBizAgent 实证带来的协议修订(2026-09-18)
SuperBizAgent-java 是 sm-flow 唯一一次大规模实践(6 周、46 项目、180 档案文件)。实证结论与据此合入的修订:
| 实证 | 据此修订 |
|---|---|
| 备选记录率 **0/180**(段落式太贵,没人写) | Alternatives 首选形态改为**压缩 Q&A 一行**,寄生在 grill 问答当场 |
| 后期 `apply pre-authorized` 常态化静默绕过确认点 | 确认点 1 增加显式旁路 `<!-- pre-authorized: 日期 + 范围/原因 -->`,兼作授权的文件载体 |
| 手写 index 腐化(死链/自指/状态不符) | index 只由 `check-dev-flow.sh --index` 派生,协议明令不手写 |
| 4 文件配额在小变更上产出 176B 占位文件 | 不加回分档;触发规则补充"小修不进来,事后可补录" |
| "参考 XXX 实现"没读导致返工 4-5 次(v4.1 的真实教训) | Build 契约加一行:点名参考的实现,动手前先完整读 |
--- ---