3 Commits
Author SHA1 Message Date
zhuyongxin fddefab0c9 Add value-scan and value-dig skills for reverse-engineering value points
- value-scan: read-only breadth inventory of mechanisms in delivered code,

  stopping at the human selection gate

- value-dig: depth write-up of chosen points (feature list, design review,

  refactor plan) with templates and mechanical checkers

- Add skill-workbench design doc for the pair
2026-09-18 18:23:23 +08:00
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
zhuyongxin d607861c6a Introduce dev-flow: lightweight 3-phase flow with decision archive 2026-09-16 20:06:09 +08:00
24 changed files with 3377 additions and 13 deletions
+192
View File
@@ -0,0 +1,192 @@
---
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/` 下那份。
**被否决的答案当场记进 `decision.md` 的 `## Alternatives considered`**(一行一条:问过什么、为什么输)——这是 Q&A 形态,见 `references/decision-note.md`)。
2. **读上下文**:`devflow/glossary/CONTEXT.md`,并搜索 `devflow/rejected/` 与已归档的 `decision.md`——
**避免重新提出已经被否决过的方案。**
3. 写 `openspec/changes/<slug>/proposal.md`,并用 OpenSpec 补全 `design.md` / `specs/` / `tasks.md`。
4. **写下 `decision.md` 的 `## Problem` 和 `## Alternatives considered`。**
理由在这一刻最新鲜;留到事后补写会变成回忆录。
**退出**:proposal 能说清做什么、范围、非目标;`decision.md` 有 `## Problem` 和至少一条备选(或用 `<!-- alternatives-not-recorded -->` 显式声明没有)。
**⏸ 确认点 1** —— 向用户汇报方案方向、关键假设、主要风险,然后**等待明确的实现授权**。
> 未获授权不得进入 Build。
> **方案讨论中的任何一次确认,都不等于实现授权。**
> 快节奏交付下可以预授权,但必须显式:Think 退出时在 `decision.md` 写下
> `<!-- pre-authorized: 日期 + 范围/原因 -->`。静默跳过不允许——旁路是有意识的选择,不是遗忘。
> 授权状态以这行标记为准,恢复会话时读它,不靠对话记忆。
## Build — 做出来
**进入**:确认点 1 已过。
**动作**:
- 按 `tasks.md` 的纵向切片实现,按 `specs/` 验收。
- **分步验证**:每完成一层再继续,不要一次写完再验。
- **tasks/design 里提到"参考 XXX 实现"的,实现前先完整读它**——设计文档点名参考而实现时没看,是返工最常见的来源。
- **冲突先分类再处理**:
| 分类 | 处理 |
|---|---|
| 规格不准(遗漏、边界未覆盖、验收口径缺失) | 暂停,修正 OpenSpec,再继续 |
| 代码偏离(实现没按规格做) | 修正代码,不改 OpenSpec |
| 不确定、或涉及设计方向 | **暂停,问用户** |
- 遇到 bug 或行为不明 → 用 `diagnose`;需要测试驱动 → 用 `tdd`。两者都不强制。
**退出**:tasks 完成或剩余已明确记录;已运行验证,或记录了未验证的原因;已知限制已列出。
Build 结束**自动进入 Close**,中间不设确认点。
## Close — 收好尾
**进入**:实现达到可交接状态。
**动作**:
1. **补齐 `decision.md`**——把 `## Decision` 校准为**实际发布的做法**(现在时),补 `## Consequences` 和 `## Verification`。
2. **执行收尾检查**(见下)。
3. 检查澄清阶段的新术语**已**沉淀进 `devflow/glossary/CONTEXT.md`(写入时机在术语确认的当场,见「冲突裁决」;这里只查漏)。
4. 有被否决的提案 → 确认**已在否决发生时**写入 `devflow/rejected/<class>/`(这里只兜底检查)。
**退出**:`decision.md` 五节齐全;收尾检查通过;用户已被询问是否归档。
## 核心规则
| 规则 | 说明 |
|---|---|
| **必须写 `decision.md`** | 每个非平凡变更 |
| **`## Alternatives considered` 强制** | 没有记录"打败了什么"的决策会招来重新争论 |
| **备选是记录的,不是编造的** | 当时没记录就写 `<!-- alternatives-not-recorded -->` |
| **实现前必须有授权** | 确认点 1 |
| **冲突先分类再处理** | 三分法 |
| **一个事实只有一个权威** | 见下表 |
**"非平凡"的判据**:改变行为、架构、跨文件或跨包的契约、流程或工具、测试策略、磁盘/线缆/配置格式,或其他维护者可能重新考察的决策。
**豁免**:纯机械或局部编辑。**小修不值得触发本流程**——dev-flow 是显式触发的 opt-in 流程,轻的第一道防线是不进来;后来发现重要时,允许事后补录 `decision.md`(retrofit 已验证)。
### 事实的唯一权威
| 内容 | 权威 |
|---|---|
| 为什么这么做、放弃了什么、代价 | `decision.md` |
| 实现设计、接口影响 | `design.md` |
| 可观察行为、验收场景 | `specs/` |
| 执行切片与完成状态 | `tasks.md` |
| 跨项目术语 | `devflow/glossary/CONTEXT.md` |
| 未实施的提案 | `devflow/rejected/` |
**任何一句话只能在一个地方是权威。出现第二处,就是错**——两边会各自腐化,而且没人知道以哪边为准。
## 为什么这么轻
这三条是有意为之。**不要"补全"它们。**
1. **义务挂在已有动作上。** 写规划产物的时刻,就是写 `decision.md` 的时刻。不设独立的归档阶段,不做事后提取。
2. **确认点只有两个。** 进入实现、是否归档。不新增。
3. **"是否该写"是判断。** 规则 + 收尾看一眼。机器只校验**已经写下**的东西是否合规,不用脚本强制这件事本身。
## 收尾检查
1. `openspec/changes/<slug>/decision.md` 存在
2. 含 `## Alternatives considered`,或显式的 `<!-- alternatives-not-recorded -->`
3. `## Verification` 里没有"已确认 X 存在"这类**不可重跑的结论**
纯机械改动可以豁免。**豁免必须显式,但可以廉价**——目的是让"跳过"变成有意识的选择,而不是静默遗忘。
> **已落地**:`scripts/check-dev-flow.sh <slug>` 执行上述三项检查;`--index` 扫描归档派生 `devflow/index.md`(不手写);`--all` 检查全部活跃 change。
## 目录
```text
openspec/changes/<slug>/
├── proposal.md design.md specs/ tasks.md
└── decision.md ← 人向:为什么、放弃了什么、代价
↓ 随归档一起冻存
devflow/
├── glossary/CONTEXT.md ← 跨项目术语
├── rejected/<class>/ ← 未实施的提案(否决发生时写入,不是 Close 时)
└── index.md ← scripts/check-dev-flow.sh --index 派生,不手写
```
`rejected/` 的保留条件:它**仍能防止一个诱人的、有意义的错误**。否则整体删除。
**边界**:`decision.md` 的 Alternatives 记"变更内"的方案备选;`rejected/` 收"整个提案被否决"的方向。提案在确认点 1 被整个否决时,当场写入 `rejected/`——那时流程已终止,Close 不会执行。
## 触发规则
- 用户输入 `/dev-flow`。
- 用户明确要求走开发流程。
不要因为任务涉及 OpenSpec、跨模块、架构决策或 devflow 目录就自动触发。
未被这样要求之前,按普通工程任务处理。
**`decision.md` 义务只覆盖经本流程的非平凡变更**——不经 dev-flow 的普通工程任务没有这个义务。
## 相关文档
- `references/phases.md` — 三阶段契约与判断细则
- `references/decision-note.md` — `decision.md` 格式规范、校准案例、反模式
- `skill-workbench/docs/dev-flow/background-and-evolution.md` — 背景、技术演进、设计取舍
@@ -0,0 +1,259 @@
# 决策档案格式
本文件定义 `decision.md` 的格式、每节的要求、校准案例和反模式。
## 骨架
```markdown
# 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 问答中被否决的答案,就是备选;当场一行记下,转写成本接近零:
```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
<!-- 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 篇档案里**唯一**记录了备选的一篇——也是唯一读完之后还记得住内容的一篇。
**注意它的形式**:两条备选,每条一句被拒理由,没有废话。**短不等于差。**
### 值得写深的备选(真实)
SuperBizAgent 的 `phase1-infrastructure/decisions.md` ADR-001(Flyway):
```markdown
### 后果
- 表结构修改必须通过 SQL 迁移脚本
- 开发环境首次启动需要执行 Flyway 迁移
- 生产环境部署自动执行未执行的迁移脚本
```
难以逆转的基础设施决策,后果写得具体到"改表必须走迁移脚本"——三个月后最需要读的就是这几行。
**这是 Q&A 一行记不住的那种备选,值得整段。**
### 应当补录备选(真实)
`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 分钟内重跑吗?
- [ ] 有没有一句话在两个地方都是权威?(有就是错)
@@ -0,0 +1,139 @@
# 三阶段契约
本文件是三阶段的判断细则。**它只规定"怎么判断",不新增检查点**——检查只有收尾那一次。
---
## Think — 想清楚
### 澄清:借用 grill-with-docs,但收窄范围
**方法来自 `grill-with-docs`** —— 一次只问一个问题并等待回答、能通过探索代码回答的自己去探索、术语确认后立即写入 `CONTEXT.md`。这些不在这里重复。
**范围由本流程收窄**:
> `grill-with-docs` 自身的取向是 "interview me **relentlessly** … until we reach a shared understanding"。
> **relentless 是对问题质量的,不是对数量的。**
> 只问**答案会改变** `proposal.md` / `specs/` / `tasks.md` 的问题;不会改变产物的疑问,自己判断即可。
**先自己查证,再问用户。** 查证时格外看四处——前两处专门用来避免重复劳动:
1. 搜 `devflow/rejected/` —— 这个方案是不是已经被否决过?
2. 搜已归档的 `decision.md` —— 相关决策的历史理由是什么?
3. 读相关代码 / 配置 / 测试;看 `openspec/specs/` 有没有既有规格
4. **能复现的问题先复现**——一次成功的复现比一轮追问更有信息量
**什么时候算够**:你能写出一份 proposal,别人读了知道要做什么、不做什么。**没有"至少 N 个问题"的要求**——问题数不是质量指标。
### 上下文读取顺序
```text
devflow/glossary/CONTEXT.md ← 术语,先对齐语言
devflow/rejected/ ← 有没有被否决过的相似方案
已归档的 decision.md ← 有没有相关的历史决策
openspec/specs/ ← 相关的既有能力规格
```
**第二步和第三步最重要。** 一个已经被否决过的方案,如果你不知道,就会重新提出一遍、重新争论一遍——这正是决策档案存在的理由。
### 确认点 1 的汇报格式
**向用户汇报时不超过 10 行:**
- 方案方向(1–2 句)
- 关键假设(没有验证、但方案依赖它的部分)
- 主要风险
- **放弃的备选**(1–2 条,连同为什么)
- 请求实现授权(或记录 `<!-- pre-authorized: 日期 + 范围/原因 -->`)
**不要**复述 proposal、不要列产物清单、不要贴对齐表格。用户要的是判断依据,不是工作汇报。
**pre-authorized 是显式旁路,不是默认。** 它适合用户已在对话中明确说"直接做"的快节奏场景。写它 = 承认确认点被有意识地跳过;不写而直接进 Build = 违规。授权状态的唯一载体是这行标记——恢复会话时以它为准。
---
## Build — 做出来
### 分步实现与验证
按 `tasks.md` 的纵向切片推进。**每完成一层先验证,再进下一层**——一次写完再验,出了问题无法定位是哪一层。
第一个切片完成后,对照 `specs/` 检查一次:验收场景是否真的成立。**规格写得含糊会在这里第一次暴露。**
### 参考实现先读,再动手
`tasks.md` / `design.md` 点名"参考 XXX 实现"的,**实现前完整读它**。路径不明确时按类名/模式 Grep 定位。
设计文档点名参考而实现时没看,是返工最常见的来源(SuperBizAgent 复盘:山东商客接口因此返工 4-5 次)。
第一次在某区域写代码、或要用项目现有基础设施(MQ/统一请求结构/工具类)时,同此处理。
### 冲突三分法的判断细则
先分类,再动手。分类的错误比不分类更糟。
**怎么区分"规格不准"和"代码偏离"**:问一句——**"如果完全按规格做出来,行为对不对?"**
| 回答 | 分类 | 处理 |
|---|---|---|
| 对,但我没按规格做 | **代码偏离** | 改代码,不动 OpenSpec |
| 不对,规格本身就写错了 | **规格不准** | 暂停,改 OpenSpec,再继续 |
| 说不清 / 涉及设计方向 | **不确定** | 暂停,问用户 |
**"不确定"这一类必须真的停下来问。** 判断设计方向是用户的权力,不是 agent 可以代劳的——
把设计问题当成实现问题自行消化,是返工最常见的来源。
冲突的分类、证据和处置结果写进 `decision.md` 的正文。
---
## Close — 收好尾
### 补齐 decision.md
Think 阶段已经写下了 `## Problem` 和 `## Alternatives considered`。Close 要补的是后三节:
**`## Decision`** —— 校准为**实际发布的做法**(现在时)。
> Think 时写的是"打算怎么做",Close 要把它改成"实际做了什么"。
> **如果两者不一致,不要偷偷改成后者**——把差异写出来,它本身就是有价值的决策记录。
**`## Consequences`** —— 必须同时写**收获**和**代价**,包括已知限制。
只写收获的是营销文案,不是档案。未来维护者最需要判断的,恰恰是"这个代价现在还接受得了吗"。
**`## Verification`** —— 写成**可重跑的命令**或**明确的人工步骤**。
| 不写 | 写 |
|---|---|
| 已确认年份筛选控件存在 | `grep -c 'year-filter' knowledge-index.html` → ≥1 |
| 手动测试一下 | 选择 2026,只显示 date 以 2026 开头的条目 |
判据:换一个人拿着这一节,能不能在 5 分钟内重跑一遍?
格式细则与校准案例见 `decision-note.md`。
### 收尾检查
三步,一次做完:
1. `decision.md` 存在
2. 含 `## Alternatives considered`,或显式的 `<!-- alternatives-not-recorded -->`
3. `## Verification` 里没有不可重跑的结论
**豁免**:纯机械或局部编辑(不改变行为、契约、结构、流程或理由)。豁免要**显式**——在 `decision.md` 或提交信息里写出来,让"跳过"是有意识的选择。
---
## 中断与恢复
**流程状态全部在文件里,不依赖对话记忆。** 换会话、隔天继续时,按下面的顺序读一下就知道停在哪:
| 观察到 | 当前在 |
|---|---|
| 没有 `openspec/changes/<slug>/` | Think 之前 |
| 有 `proposal.md`,但 `decision.md` 缺 `## Problem` | Think 进行中 |
| 有 `proposal.md` 和 `decision.md`,但没有 `<!-- pre-authorized` 也没有对话中的明确授权 | **确认点 1** |
| 有 `<!-- pre-authorized: ... -->` 或已获授权,实现未完成 | Build 进行中 |
| 实现完成,`decision.md` 缺 `## Decision` / `## Consequences` / `## Verification` | Close 进行中 |
| `decision.md` 五节齐全 | 收尾检查 + 确认点 2 |
**恢复时不重新提问已经解决的问题。** 先读 `decision.md` 和 `proposal.md`——已经确认过的内容就在里面。
+1 -1
View File
@@ -16,7 +16,7 @@
### 真理源 (Source of Truth)
- **日期**和**短标识符 (slug)**:由目录名 `knowledge_YYYYMMDD_Slug/` 提取。
- **显示标题**、**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)
目录名中 `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-19 | add-clear-filters | knowledge-index | clear-filters, search, tags, year-filter | `openspec/changes/archive/2026-05-19-add-clear-filters/` | archived |
| 2026-05-19 | add-year-filter | knowledge-index | year-filter, index-panel, frontmatter | `openspec/changes/add-year-filter/` | active |
| 2026-05-19 | dev-flow-skill-evaluation | sm-flow | dev-flow, sm-flow, skill-evaluation, fallback, phase-contracts | 未关联 | accepted-unarchived |
| 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-05-22 | sm-flow-execution-hardening | sm-flow | execution-hardening, checklist, phase-gate, cross-artifact, question-pool | 未关联 | active |
| 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 |
| 日期 | 标题 | 归档位置 |
|---|---|---|
| 2026-05-25 | knowledge-index-sort Proposal | openspec/changes/archive/2026-05-25-knowledge-index-sort |
| 2026-09-18 | Decision: 建立旧 devflow 档案的迁移规则 | openspec/changes/archive/2026-09-18-migrate-devflow-archives |
| 2026-05-19 | 2026-05-19-add-clear-filters | openspec/archive/2026-05-19-add-clear-filters |
| 2026-05-21 | 2026-05-21-sm-flow-v3-1-upgrade | openspec/archive/2026-05-21-sm-flow-v3-1-upgrade |
| 2026-07-05 | Validate SM Flow Explicit Trigger | openspec/archive/2026-07-05-validate-sm-flow-explicit-trigger |
| 2026-07-05 | Validate SM Flow Standard Change | openspec/archive/2026-07-05-validate-sm-flow-standard-change |
@@ -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
@@ -0,0 +1,584 @@
# Dev Flow:背景与技术演进
**状态**:设计中
**日期**:2026-07-05
**范围**:说明 `dev-flow` skill 为什么存在、从哪来、为什么是现在这个形态
---
## 1. 这份文档解决什么
`dev-flow` 的核心只有一条规则:**每个非平凡变更必须留下 `decision.md`**。
这个规则看起来很轻,但它是从五次设计迭代、一次完整协议重写、以及和三个外部实现的对比里收敛出来的。**如果只看结论,很容易觉得它"太简单了,不够"**——所以这份文档把推理链完整留下来。
配套文档:
- `.agents/skills/dev-flow/SKILL.md` — 运行协议
- `.agents/skills/dev-flow/references/decision-note.md` — 格式规范与校准案例
---
## 2. 背景:为什么需要一层"决策档案"
### 2.1 原始动机(2026-05-18 前后)
第一次完整跑通一个 AI 驱动的开发流程后,暴露的问题是**产物散落**:
```text
CONTEXT.md ← 根目录
docs/adr/ ← 架构决策
docs/agents/ ← PRD
openspec/changes/ ← 提案与规格
knowledge/entries/ ← 知识条目
```
三个月后想回溯"这个项目到底做了什么决策",需要同时翻 4 个目录。**产物按"工具来源"组织,而不是按"项目"组织。**
这是 `devflow/` 聚合层的直接起因,设计上借鉴了 CodeStable 的**单一聚合根**。
### 2.2 当时确立的两层分工
| | `openspec/changes/` | `devflow/projects/` |
|---|---|---|
| 角色 | 工具工作区(WAL) | **人类可读档案层(Tables)** |
| 谁读 | 机器 | **人** |
| 生命周期 | 活跃变更期 | 永久保留 |
| 组织方式 | 按变更名 | 按日期 + 项目 |
**"给人看"是明确的设计目标**,不是副产品。这一点在后来的讨论中被反复确认,也是最终形态的关键约束。
### 2.3 但有一个前提,后来被证伪
原始设计文档里写着:
> `openspec/changes/` 是工具工作区(WAL),**archive 后清空**;`devflow/projects/` 永久保留。
**在真实仓库里这个前提不成立。** 实测:
```text
openspec/archive/2026-05-19-add-clear-filters/
openspec/archive/2026-05-21-sm-flow-v3-1-upgrade/
openspec/archive/2026-07-05-validate-sm-flow-explicit-trigger/
openspec/changes/archive/2026-05-25-knowledge-index-sort/
```
归档内容**没有被清空**,而且历史上出现过两个归档位置(`openspec/archive/` 与 `openspec/changes/archive/`)。
后来设计文档自己把真正丢失的东西说清楚了:
> 旧:`openspec archive` 只归档需求,项目的**术语、业务规则、架构决策**散落在对话中,下次新对话全丢
关键点:**丢的不是变更产物,是 `why`。** 术语有 `devflow/glossary/CONTEXT.md` 承载;而"架构决策"就是决策档案本身。
**结论:"需要一层新目录"这个判断,建立在错误的前提上。**
---
## 3. 技术演进
### 3.1 v1 — `dev-flow` 骨架
`.claude/skills/dev-flow/` 的最早形态:Phase 1-4 流水线(手动 PRD → openspec propose → to-prd → grill → apply),外加一个 `devflow/` 目录约定。
### 3.2 v2.0.0 — 产物聚合层(现存 213 行)
现行 `.claude/skills/dev-flow/SKILL.md` 的版本。核心贡献:
```text
devflow/
├── projects/YYYY-MM-DD-{slug}/
│ ├── {slug}-prd.md {slug}-research.md
│ ├── {slug}-design.md {slug}-tasks.md
│ ├── {slug}-acceptance.md
│ └── adr/
├── glossary/CONTEXT.md
├── compound/
└── reference/
```
确立了两层架构与"Phase 4 从 OpenSpec 提取到 devflow"的回填模式。
**它已经埋下了后来的两个问题**:
1. **数量不封顶**——一个项目 5 个文件起,`adr/` 与 `compound/` 还可再长
2. **提取 = 有损转换**——`{slug}-tasks.md` 是从 openspec `tasks.md` **提炼**出来的,同一事实两个权威
### 3.3 sm-flow v2 — 产品化
`dev-flow` 被重构为 `.agents/skills/sm-flow/`,把设计说明书拆成代理可执行的 skill 结构(`SKILL.md` + `references/`)。
### 3.4 sm-flow v3 / v3.1 — 真理源分层与产物瘦身
v3 解决的问题:**devflow 产物越来越完整,agent 开始直接拿 devflow 写代码,OpenSpec 被架空。**
确立分层:
```text
devflow = 上下文真理源 OpenSpec = 执行真理源 代码 = 结果
```
v3.1 进一步明确 **devflow 不复制 OpenSpec**,只保存它不擅长表达的人类上下文、证据、决策和验收归档。默认产物收敛为 `brief` / `evidence` / `decisions` / `acceptance`。
**这一步方向是对的,但执行没有一致性保障**——见第 5 节的实测。
### 3.5 sm-flow v4.0 — 协议层 harness 重设计
一次设计评审后做了一次**结构性重写**(不是加规则):
| 改动 | 性质 |
|---|---|
| 19 条规则 → 6 条硬约束 | 删减 |
| `SKILL.md` ~85 行 → ~50 行 | 删减 |
| **grill 提到 specify 之前** | 消除"细化 → 澄清 → 回写"的必然返工 |
| devflow 延迟写入:过程只维护 `decisions.md` | 消除双重记录 |
| 阶段改英文动词命名,4 个用户命令 | 接口收窄 |
设计文档自己写下了这次重写最重要的判断:
> **这不是 agent 执行问题,是流程顺序决定了返工必然存在。**
**v4.0 是这条演进线上唯一一次"减法生效"的版本。**
### 3.6 sm-flow v4.1 / v4.2 — 两次应激加码
| 版本 | 触发原因 | 成本 |
|---|---|---|
| v4.1 | 山东商客意向单同步接口返工 4-5 次 | +55 行,门控 4→6 |
| v4.2 | `lookup-knowledge-integration` 跳过 7 个阶段 | +80 行,门控 6→8,新增 `.committed` / `.archive-ready` |
v4.2 的核心主张是**把软性约束变成硬性检查**——但形式上仍然是提示词里的检查清单。
两个值得记录的问题:
1. **v4.0 的验证复盘已经观察到同类跳过行为,并明确决定"暂不改协议"**(原文:"问题 1 和 2 暂不改协议,如果多次验证中反复出现再考虑加约束")。**v4.1/v4.2 违反了自己定下的门槛。**
2. **v4.2 的动机案例按 v4.3 的触发规则根本不算 sm-flow 运行**——优化建议文档自己标注"执行模式:手动跳阶段(用户直接要求修复问题)"。
### 3.7 sm-flow v4.3 — 回到减法
| 改动 | 性质 |
|---|---|
| **只显式触发**(不再按需求类型自动触发) | 砍掉常驻税与假阳性违规 |
| `.agents/skills/sm-flow/references/scales.md` 成为分档**唯一**来源 | 消除重复定义 |
| 新增 `.agents/skills/sm-flow/references/glossary.md` | 统一术语 |
| **拒绝**引入 `.sm-flow-state` 状态文件 | 主动不做 |
| 删掉固定投入指标 | 去掉会腐化的经验值 |
**v4.3 是自 v4.0 以来第一个走简化方向的版本。** 它证明这条演进线是活的。
### 3.8 现状实测(截至本文)
`sm-flow` skill 本体:
| 指标 | 实测 |
|---|---|
| 文件数 / 体积 | 8 个 / 58,597 B |
| 总行数 | **1,253 行** |
| "必须 \| 不得" | **52 处** |
| 列表项 | **454 个** |
| 首次加载(SKILL.md + phase-contracts) | ≈ 8.5k tokens(**占全量一半**) |
| 单次 standard 变更产物 | OpenSpec 7 文件 14.8 KB + devflow 5 文件 20.3 KB = **35 KB** |
设计文档自己给出的成本表:
| 规模 | 代码工作量 | 流程开销 | 占比 | 自评 |
|---|---|---|---|---|
| micro | 30 min | 40–60 min | **~60%** | **太重** |
| standard | 2 h | 1–1.5 h | ~40% | 还行 |
| complex | 1–2 d | 2–3 h | ~20% | 值得 |
---
## 4. 对照外部实现
为了让判断不建立在自我推演上,对三个外部实现做了一手对照。
### 4.1 三个外部实现的做法
| | 状态持有者 | 强制力在哪 | 约束对象 |
|---|---|---|---|
| **OpenSpec**(v1.13) | 产物依赖图,**CLI 计算** readiness | `status --json` 与 schema | 一次变更的产物 |
| **Trellis**(0.7) | `task.json.status` + **hook 每轮注入** | 平台 hook | 一轮该做什么 |
| **DSH Agent Notes** | **文件路径** | **CI 脚本 + PR 原子性** | 一个决策必须留下理由 |
| *sm-flow* | *agent 的 checkpoint 陈述* | *提示词自查* | *阶段顺序* |
**三家都把强制力移出了提示词,sm-flow 是唯一没移的。**
### 4.2 关键发现一:DSH 并不强制"必须写"
DSH 有 19 个 CI workflow、110+ 个 `verify-*` 脚本,其中与 Agent Notes 相关的只有三个:
| 脚本 | 管什么 |
|---|---|
| `verify-agent-note-classification.ts` | 路径是否在关闭集内 |
| `verify-agent-note-format.ts` | 三行头 + 骨架 + **`## Alternatives considered` 必须存在** |
| `verify-archived-agent-notes.ts` | 冻结三元组 + 哈希 manifest |
检索确认:**没有任何脚本强制"改了代码就必须有笔记"。** 那条规则靠约定与人工 review。
**一个 1,026 篇笔记、110 个校验脚本的项目,把所有机器能查的东西都做成了脚本,却单独把这一条留给了判断。**
原因是"非平凡"本身是判断,机器强制会产生假阳性——正是 pre-commit hook 拦琐碎提交的那个问题。
**结论:任何"三条时机不变量 + hook"的方案都比业界最成熟的实现更重。这个方向的复杂度应当被否决。**
### 4.3 关键发现二:devflow 层的前提被证伪
见第 2.3 节。归档不丢数据,丢的是 `why`。
### 4.4 收敛出的三条原则
**① 不新增检查点,把义务挂在已有的动作上。**
- DSH:写新笔记 → 顺手检查旧笔记是否被取代
- DSH:改代码 → 同一次变更里更新笔记的事实
- `triage`:决定拒绝 → 同时写 `.out-of-scope/`
- *反面*:sm-flow 现在新增 4 个 checkpoint + 8 个门控文件 + 各类自检清单
**② 检查只有一个,放在收尾边界。**
- OpenSpec:archive 时检查完整性
- Trellis:finish 时拒绝在不一致状态下完成
两家独立收敛——不是"全过程多条不变量",而是"收尾时一次检查"。
**③ "是否该写"是判断,不强行机器强制。** 见 4.2。
---
## 5. devflow 现状实测:问题不是"文件太多"
`devflow/` 现状:
| 指标 | 实测 |
|---|---|
| 文件数 / 体积 | **37 个 / 85.0 KB** |
| 项目数 | 9 |
| 每项目文件数 | **2 – 7,无规律** |
| 命名约定 | **4 套并存** |
| **含"替代方案"的文件** | **1 / 37** |
### 5.1 四套命名约定并存
| 世代 | 形态 |
|---|---|
| 早期 | `{slug}-{prd,design,research,tasks,acceptance}.md` |
| 早期 | 自由式(`dev-flow-skill-evaluation.md` + `todo.md`) |
| 早期 | `adr/` 子目录 |
| v3.1 后 | `{brief,evidence,decisions,acceptance}.md` |
**没有任何机制阻止格式漂移**,因为格式从未被校验。
### 5.2 真正的病:同一个"5 个文件",两种质量
| 项目 | 文件数 | 与 OpenSpec 的关系 |
|---|---|---|
| `add-year-filter` | 5 | **严重重复**——`-design.md` 与 `design.md` 同一件事写两遍;`-tasks.md` 与 `tasks.md` 任务核对两遍 |
| `sm-flow-execution-hardening` | 5 | **做对了**——`brief.md` 写的是"OpenSpec 对齐:已覆盖",用引用代替复制 |
**两种质量共存,而文件名看不出区别。** 只能靠打开和回忆。
### 5.3 最稀缺的内容几乎为零
在整个 `devflow/` 里检索 `备选|替代方案|放弃了|代价|alternatives|trade-off|权衡|否决`:
```text
Found 1 match
devflow/projects/2026-05-18-knowledge-index-panel/adr/0001-directory-name-as-truth-source.md
Line 21: ## 替代方案
```
**37 个文件、85 KB,只有一篇文章记录了"我们放弃了什么"。** 而它的骨架恰好就是决策档案的骨架:
```markdown
## 背景 ← Problem
## 决策 ← Decision
## 替代方案 ← Alternatives considered
## 后果 ← Consequences(正面 / 负面分开)
```
**正确答案在 2026-05-18 就被写出过一次,此后 9 个项目再没出现第二次——因为没有机制要求它。**
---
## 6. 决策:三个方案
| | A:devflow 分层 + 同步机械 | B:devflow 只给 agent 读 | **C:取消 devflow 层** |
|---|---|---|---|
| 决策笔记落点 | `devflow/decisions/<lifecycle>/<class>/` | 同 A,但格式机器优先 | **`openspec/changes/<slug>/decision.md`** |
| 受众 | 人 + agent | 只 agent | **人 + agent** |
| 同步问题 | 有,需 3 条不变量 + hook 解 | 有,且对 agent 更严重 | **不存在** |
| 新增机械 | ~200 行 + hook | ~60 行 | **~0 行** |
| 写入时机 | 需要保证 | 需要保证 | **天然正确**(写变更产物的同一时刻) |
| 人的可读性 | 是 | 否 | **是** |
### 为什么否决 A
A 要新增三条时机不变量 + pre-commit hook。**4.2 的发现表明这比 DSH 更重**,而 DSH 是靠约定解决的。
更根本的是:A 是在**解决同步问题**;而 4.3 表明这个同步问题**本来不需要存在**。
### 为什么否决 B
B 的直觉是"只给 agent 读就不用管可读性和索引,复杂度就降了"。但:
- 省掉的只是 `index.md` 和文笔(**便宜**)
- 省不掉时机同步(**昂贵**),而且对 agent 阅读**更关键**——agent 会照着过时的记忆干活
而且 A/B 是超集关系:一份写好的人类可读决策档案**同时**是 agent 能读的;反过来不成立。
**唯一支持 B 的论证是"没人读的档案会腐烂"——而这一点已被 5.3 实测证明:覆盖率 1/37,无人校对。**
### 为什么选 C
1. **它消除问题而不是解决问题**——同步复杂度归零
2. **它纠正了事实错误**:devflow 层的主要理由(archive 后清空)不成立
3. **它符合设计原则 ①**:写入义务挂在"写变更产物"这个已有动作上
4. **它满足原始设计目标**:`decision.md` 是给人看的,而且和它描述的那次变更放在一起,人类读起来上下文最完整
保留下来的是三样最有价值的东西:
- **强制 `## Alternatives considered`**(ADR-001 就是范例)
- **`glossary/CONTEXT.md`**(真正的跨项目知识)
- **`rejected/`**(未实施提案的归宿,DSH 与 `triage` 独立收敛的机制)
放弃的是:devflow 作为独立"人类档案层"的存在感,以及 A 的那套同步机械。
---
## 7. 新 dev-flow 的设计
### 7.1 定位:替代 sm-flow
**dev-flow 是一套轻量开发流程,并且替代 sm-flow。** 这不是"两套正交工具",而是一次替换:
| | sm-flow | dev-flow |
|---|---|---|
| 身份 | 协议层 harness,编排 9 阶段 | **轻量开发流程,3 阶段** |
| 人类确认点 | 4 | **2** |
| 分档 | micro / standard / complex | **无** |
| 协议行数 | 1,253 | **~150** |
| 单次变更产物 | OpenSpec 7 文件 + devflow 5 文件 = 35 KB | **OpenSpec 4 件 + `decision.md`** |
| 强制内容 | "按顺序做 9 件事并逐项自查" | **"放弃了什么"** |
| 强制力 | 提示词 + 8 个自查清单 | **1 个收尾检查** |
**sm-flow 的归档由用户手动执行**,在 dev-flow 实现完成之后。
### 7.2 三阶段流程
```text
Think 想清楚 → Build 做出来 → Close 收好尾
⏸ 确认点 1 ⏸ 确认点 2
授权进入实现 是否归档
```
| 阶段 | 做什么 | 用 OpenSpec 的 |
|---|---|---|
| **Think** | 澄清 → 读上下文 → 写 proposal/design/specs/tasks → **写下 `decision.md` 的 Problem 与 Alternatives** | `openspec-propose` |
| **Build** | 按 tasks 分步实现 + 验证;冲突三分法 | `openspec-apply-change` |
| **Close** | 补齐 Decision / Consequences / Verification → 收尾检查 → 归档确认 | `openspec-archive-change` |
**为什么是 3 个而不是 9 个**:sm-flow 的 9 阶段里有一半是"为了防止 agent 偷懒"而设的编排层。当强制力改由文件与收尾检查承担之后,这些中间阶段失去了存在理由。**Trellis 用 Plan / Execute / Finish 三段跑通,是同一判断的独立佐证。**
**为什么不分档**:sm-flow 的分档设计者自己评分 micro 开销约 60%、"太重"。**流程本身就轻的时候,分档是多余的**——它只是在给一个重流程寻找减负的借口。
### 7.3 产物布局
```text
openspec/changes/<slug>/
├── proposal.md
├── design.md ← 实现设计(执行向)
├── specs/
├── tasks.md
└── decision.md ← why + 备选 + 代价 + 验证(人向)
devflow/
├── glossary/CONTEXT.md ← 跨项目术语
├── rejected/<class>/ ← 未实施的提案
└── index.md ← 脚本扫描归档生成,不手写
```
### 7.4 保留了 sm-flow 的什么
dev-flow 不是全盘重写。三样东西被完整继承:
- **Draft / Committed 思想** → 变成**确认点 1**("未获授权不得进入 Build")。这是三家外部实现独立收敛到同一处的设计。
- **冲突三分法** → 完整保留,是四家对比里唯一没找到对应物的原创资产。
- **显式触发** → 保留,v4.3 唯一一次生效的减法。
放弃的是:9 阶段编排、micro / standard / complex 分档、8 个自查清单、`.committed` / `.archive-ready` 门控文件、每阶段声明 capability 来源的仪式、以及 devflow 的 4–5 件套档案。
### 7.5 三条设计原则
见 `SKILL.md`。**① 义务挂在已有动作上 ② 确认点只有两个 ③ "是否该写"是判断。**
### 7.6 能力来源:编排,而不是重写
dev-flow 和 sm-flow 一样是**元技能**——由多个子 skill 实现。但组合的务实程度不同:
| | sm-flow | dev-flow |
|---|---|---|
| 组合的子 skill | 8 个(3 个 OpenSpec + grill / to-prd / zoom-out / diagnose / tdd) | **5 个**(3 个 OpenSpec + grill-with-docs + diagnose / tdd) |
| 能力声明 | 每个阶段显式声明 capability 来源 | **一张表,不逐阶段声明** |
| 不可用时 | `references/fallbacks.md`,8 个内置协议(49 行) | **一行规则**:按能力绑定;不可用时读本地 `SKILL.md`;仍不可用时按最小等价协议直接产出文件并注明 |
| 不使用 | — | `to-prd`(发布到 issue tracker,且与 `proposal.md` + `specs/` 重复) |
| 不设阶段 | — | `audit`。`zoom-out` 降为按需能力,不再是关卡 |
**设计期间发生的三次修正,值得记录:**
1. **澄清环节一度被重写。** 初版 `phases.md` 把"一次一问、能查代码的自己查、术语沉淀进 `CONTEXT.md`"又写了一遍——而这三条 `grill-with-docs` 已经实现了。
这正是 sm-flow v2 自己修过的错(元技能复述子技能逻辑)。现已改为**引用 + 只保留 dev-flow 特有的部分**。
2. **`grill-with-docs` 的取向与本流程有张力。** 它要求 "interview me **relentlessly** … until we reach a shared understanding",而 dev-flow 要轻。
解法是分层:**relentless 是对问题质量的,不是对数量的**——方法借用,范围收窄。
3. **ADR 与 `decision.md` 原本会重复。** `grill-with-docs` 会提议创建 ADR(需同时满足难以逆转 / 缺少上下文会困惑 / 源自真实权衡),而 dev-flow 要求每个变更写 `decision.md`,两者形态几乎相同。
处置:**`decision.md` 吸收 ADR**,不再另建 `adr/` 目录。ADR 三条件从"要不要写"降级为"值不值得写深"。
(DSH Agent Notes 用 `architecture` class 承担 ADR 角色,是同一思路。)
4. **冲突面比预想的宽。** 逐一实测后,`grill-with-docs` 有**三处**默认与本仓库不符:
| 它的默认 | 本仓库实际 |
|---|---|
| ADR 写入 `docs/adr/` | `docs/adr/` **不存在**;实际在 `devflow/projects/*/adr/`(3 个目录) |
| 用 `ADR-FORMAT.md` | dev-flow 用 `decision-note.md` |
| 假设根目录 `CONTEXT.md` | 用 `devflow/glossary/CONTEXT.md`(根目录是 235 B 重定向 stub) |
#### 裁决:都不内置
理由是结构性的,不是偏好:
> **内置只在被组合 skill "消失"时才消除冲突。** 只要它还装在目录里(用户可以直接调用),
> 内置就只是让 dev-flow 多出一份**会漂移的私有副本**——重复实现加上版本漂移,两个问题。
改成一条**可判定的归属规则**,它能覆盖未来还没组合的 skill:
> **子 skill 管"这一步怎么做";dev-flow 管"做多少、结果放哪"。冲突时,流程赢。**
> 归属规则解决不了的 → **不组合它**(`to-prd` 就是这么处理的)。
| 内容 | 归属 |
|---|---|
| 一次一问、先查代码、场景压测 | `grill-with-docs`(方法,引用不内联) |
| 问多少、何时停、要不要写 ADR | **dev-flow** |
| `CONTEXT.md` 在哪、ADR 落哪、用什么格式 | **dev-flow** |
#### 为什么 `openspec-*` 尤其不能内置
它们由 `openspec update` 生成,同时存在于 `.claude/skills/` 和 `.codex/skills/`,是**外部管理的多副本产物**。只能按能力名引用。
**顺带发现**:`.agents/skills/` 下**没有** `openspec-*`,所以在本环境里"优先用平台原生 skill"会失败,必须降级到"读本地 `SKILL.md`"(`.claude/` 或 `.codex/` 下有)。这正好验证了"能力绑定而非路径绑定"这条规则在**兜住一个真实的不一致**——而不是一条装饰性的设计原则。
---
## 8. 验证记录
### 8.1 `openspec validate` 是否容忍 change 目录内的 `decision.md` —— ✅ 容忍
这是方案 C 的硬前提。**已实测确认。**
| 步骤 | 结果 |
|---|---|
| 基线:`validate knowledge-index-panel` | `is valid` / exit=0 |
| **探针:加入 `decision.md`** | `is valid` / **exit=0** ✅ |
| **反向对照:抽掉 `proposal.md`** | **exit=1** ✅ 证明探针可信 |
| 恢复后复验 | exit=0 |
| 工作区洁净度 | 干净,无残留 |
**反向对照是必须的。** 第一次探测只跑出 exit=0 就下结论,那是假阳性——CLI 当时根本没执行。
### 8.2 环境约束:受限沙箱下无法捕获外部程序输出
本次踩到的坑,记录下来避免重复:
```powershell
$x = & openspec validate foo # ❌ 捕获 -> 命令静默不执行,仍返回 0
& openspec validate foo | Select-Object -First 5 # ❌ 管道 -> 同样失效
& openspec validate foo # ✅ 直连控制台 -> 正常
```
**后果**:所有 CLI 验证必须让输出直连控制台。否则会得到"永远通过"的假象——**而且比没有验证更危险**,因为它会让人相信一个不存在的结论。
### 8.3 顺带发现:`add-year-filter` 通不过校验——根因是 UTF-8 BOM
```text
Change 'add-year-filter' has issues
✗ [ERROR] knowledge-filtering/spec.md: No delta sections found.
✗ [ERROR] file: Change must have at least one delta.
```
**这个报错会误导。** `specs/knowledge-filtering/spec.md` 第 1 行**就是** `## ADDED Requirements`,完全按要求写的。
真正的原因是文件带 **UTF-8 BOM**——解析器把行首标题读成了 `\ufeff## ADDED Requirements`,匹配不上。
字节级实测,相关性完美:
| change | 首 4 字节 | BOM | validate |
|---|---|---|---|
| `add-year-filter`(4 个文件全部) | `EF BB BF 23` | **有** | ❌ 失败 |
| `knowledge-index-panel` | `23 23 20 57`(`## W`) | 无 | ✅ 通过 |
| `sm-flow-execution-hardening` | `23 23 20 57` | 无 | ✅ 通过 |
**这个结论差点写错。** 第一版是从报错**推断**出"它的 spec 用的是普通 `##` 小节",而没有实际读那个文件——
读进去才发现推断是错的。**报错信息描述的是解析器看到的东西,不是文件里实际写的东西。**
本地 CLI 版本 **1.3.1**(官网最新 1.13.0),偏旧,但 BOM 不兼容与版本关系不大——这是一个**可机械修复**的问题(去掉 BOM 即可)。
### 8.4 dev-flow 首次验证(retrofit)
用 `add-year-filter` 做了一次 **retrofit** 验证:Think 与 Build 在历史上已发生,所以本次验证的是 **Close 阶段与产物格式**。
产物:`openspec/changes/add-year-filter/decision.md`(4,198 B)
| 验证项 | 结果 |
|---|---|
| 五节骨架齐备 | ✅ |
| 新文件无 BOM | ✅ |
| **收尾检查 3 项** | ✅ **全部机械通过** |
| 加入 `decision.md` 后 validate 报错集合未变 | ✅ 未引入新问题 |
| **补出被丢弃的备选** | ✅ 第 3 条「只改 HTML 不改生成脚本」原本藏在 `design.md` 的"架构风险"里 |
**未被验证的部分**(本次覆盖不到):Think 的澄清、确认点 1、Build 的冲突三分法,以及**冲突裁决**——retrofit 没有调用 `grill-with-docs`,所以三项覆盖一次都没触发。
**本次暴露的一个格式缺口**:`## Verification` 要求"可重跑的命令",但**最有说服力的那条验证会修改文件**(重建 `knowledge-index.html`),导致收尾时不敢跑,只好把它拆成"已实测"和"需执行"两段。
→ 应补充约定:**区分只读命令与有副作用的命令**。这是格式需要补的第一处。
---
## 9. 尚未解决
| # | 问题 | 处置 |
|---|---|---|
| 1 | 旧的 `.claude/skills/dev-flow/`(2.0.0,213 行)同名但设计相反 | **已删除**(2026-09-18) |
| 2 | `sm-flow` 归档 | **改造完成后归档**。完成定义:`scripts/check-dev-flow.sh` 落盘 ✅ + 一次从 Think 开始的全路径真实运行 + 一条旧档案迁移规则(单样本) |
| 3 | 现有 3 个活跃 change(`knowledge-index-panel` / `add-year-filter` / `sm-flow-execution-hardening`) | **本次忽略** |
| 4 | 现有 9 个项目 37 个 devflow 文件的迁移 | 未定;先做单样本再定规则。SuperBizAgent 实测(46 项目/180 文件)给出处置口径:压缩 Q&A 与 ADR 段落并入对应归档的 decision.md,骨架文件删除 |
| 5 | `rejected/` 是否照搬 DSH 的 6 类关闭集 | 沿用,未验证 |
| 6 | "计划写在 `decision.md` 却从未更新为事实"会不会出现 | 观察到再引入 DSH 的两套骨架 |
| 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 契约加一行:点名参考的实现,动手前先完整读 |
---
## 10. 附录:对照结论一览
| 收敛的设计 | 出现处 |
|---|---|
| 状态必须外化到文件 | 全部四家 |
| **强制力不该放在提示词** | OpenSpec(CLI 计算)/ Trellis(hook 注入)/ DSH(CI 脚本) |
| 保留被否决的方案以防重新争论 | DSH `rejected/` / `triage` `.out-of-scope/` / OpenSpec archive |
| 单一来源,不建中央索引 | DSH(明令禁 INDEX)/ Trellis(hook parser-only,零文案副本) |
| 角色/上下文隔离靠进程 | Trellis(research/implement/check 子代理)/ OpenSpec(规划 skill "Never code") |
| 按需加载 | OpenSpec(optional skills)/ Trellis(JSONL manifest) |
| 不新增检查点,义务挂在已有动作上 | DSH(supersession)/ `triage`(决定即写) |
| 检查只在收尾一次 | OpenSpec(archive)/ Trellis(finish) |
| 用真实案例校准,不用阈值 | DSH("word count is never the test") |
+111
View File
@@ -0,0 +1,111 @@
# value-scan / value-dig · 设计文档(Latest Design)
> **定位**:从**已交付的代码**里逆向萃取「值得讲的价值点」,交人勾选后深挖成可讲、可追问、可被审的文档。
> **适用**:面试/简历素材重建、模块价值盘点、交接文档提级。
> **形态**:两个 skill 接力 —— `value-scan`(广度枚举,停在人工勾选门)→ `value-dig`(深度产出,三件套任选)。
> **战果**(its 仓库实测,2026-09-18):4 轮扫描产出 9 份文档全部通过机械校验;2 条主链路各带 1 个 P0 级实锤缺陷。
---
## 1. 为什么要有这两个 skill
盘点代码价值时,agent 有三个系统性偏差:
| 偏差 | 表现 | 解药 |
|---|---|---|
| **判不了价值** | 把"用了 Redis""有 4 个 handler"当亮点 | 简历行填空测试:`用【机制】解决了【问题】,代价是【取舍】`——机制格填不出就不是价值点 |
| **缺陷导向** | 扫一遍变成挑毛病大会,清单全是负面 | 阶段隔离:S1 只产亮点,缺陷是深挖链路时自然浮现的副产物,归宿在改造方案 |
| **自作主张** | 替用户挑完直接开写 | 门控:产出候选清单后**必须停**,勾选权在人 |
一句话设计哲学:**agent 负责采样与结构化,人负责价值判断**。
## 2. 两段流水线
```
S0 定范围 → S1 枚举 → S2 勾选【硬门:必须停】
↓(人勾选 / 否决改向 A′)
value-dig:①功能点清单 ②设计复盘 ③改造方案(各自独立,可只做其一)
```
### value-scan(广度)
- **三层结构**:域 → 链路 → 机制节点。链路按**触发者**拆(谁在改这行数据),不按业务功能拆——收敛点多半是机制所在。
- **三条硬标准**(全过才进清单):① 核心链路 ② 高级工程师的设计(非框架常识)③ 简历够硬。
- **数量闸**:5–8 条为宜,>12 = 颗粒度掉到实现层,退回重并。
- **两层锚点**:机制级 `路径#方法名`(不写行号,防漂移),深挖级 `文件:行号`(必须读过再写)。
### value-dig(深度)
三份文档三种读者:
| 文档 | 读者 | 灵魂 |
|---|---|---|
| ① 功能点清单 | 自己/接手人 | **主干调用骨架**:整条链压成一段伪代码,每行标方式(`[锁]``[幂等]``[MQ]`),功能点互为索引 |
| ② 设计思路与取舍 | 面试官 | 第一人称复盘 + **"与代码的对照说明"**:判断与代码事实不一致处逐条列出——诚实性是可信度唯一来源 |
| ③ 改造方案 | 架构评审 | 缺陷唯一归属地;**产出门槛**:必须有②加机制/③重构档改造点才立方案,全是补漏档不立(防灌水) |
跨文档资产复用:`tb_order_anomaly` 台账、token 锁工具、巡检 Job——两链路方案共用一套,是"结构必然"不是"省事"。
## 3. 机制设计要点(为什么这样设计)
### 3.1 门控(S2 必停)
价值判断权如果不在人,整个流水线退化成"agent 自嗨产出没人看的文档"。所以:
- 候选清单落盘 + 显式请求勾选("请从 N 条挑 3–6 条");
- 用户禁用提问工具也一样停——请求写成文字;
- **整批否决且未给新目标 = 流程终点**,不强续。
### 3.2 判据外化为填空
"价值"无法定义,但"简历行填得出来吗"可判定。两级:
- **准入**:三条硬标准(反例驱动的剔除表);
- **表达**:机制+问题两格必填,取舍格留给 value-dig(不误杀)。
被剔除的点不丢弃——进「已剔除」表注明未过哪条标准,供复核筛选口径。
### 3.3 机械校验(check.py)与模板外置
- 模板是**活资产**:产出照模板填空,被纠正过就回写模板;
- check.py 十余条规则:引号配对 / mermaid 合法性 / 表格列一致+不缩进 / 章节完整性(模板必备节 ⊆ 产出节)/ scan 专属(无代码围栏、锚点格式、候选数 ≤12)/ dig 专属(骨架行必带方式标记、证据必含行号)/ doc 专属(改造方案必有关键片段);
- FAIL 必须为 0,WARN 允许保留但写明原因。
### 3.4 缺陷的归宿隔离
缺陷只在改造方案出现,且**不是枚举出来的是贴着链路走出来的**——三把刀(按触发者拆链路 / 竞态矩阵 / 幂等键清单+状态流转图)+ 机制模式库(结构性限制 → 可引入机制对照表)+ 三档尺度(补漏⭐/加机制⭐⭐⭐/重构⭐⭐⭐,每缺陷必追问"能否升一档")。
## 4. 实战演化记录(its 仓库 4 轮,2026-09-18)
这轮实战暴露的边界案例与回写(模板活资产的实证):
| # | 事件 | 回写 |
|---|---|---|
| 1 | 用户否决 5 条单点:"都不够硬,直接分析整链路" | value-dig 新增**入口 A′(否决改向)**:否决原因入清单、按 B 重走、产物声明 |
| 2 | 权益域首轮以"机制格填不出"剔除,复评翻案(快照轮询机制实存,藏在 2349 行大类的私有方法群) | "机制格填不出"剔除加**硬门槛**:没读过入口方法向下 ~100 行不得定稿剔除;翻案不删行留痕 |
| 3 | "不够硬"出现两次(单点否决、权益否决) | S2 门控新增**否决即校准**:负反馈记入清单,同域复扫先读否决记录 |
| 4 | 勾选回写两次压列(3 列变 2 列) | 候选清单模板补**回写示例**(3 列不压列);check.py 报错**附行内容摘要** |
| 5 | check.ps1 仅 Windows | 移植 **check.py**(等价回归 8 产物一致);顺带发现初版 `__name__` 损坏静默 exit 0——校验工具自身的静默通过比报错更危险 |
### 实测产出骨架(可作参考样例)
```
docs/
├── 赢客下单-候选价值点.md S1 清单(5 候选 + 5 剔除)
├── 赢客下单-功能点清单.md 7 功能点 + 主干骨架
├── 赢客下单-设计思路与取舍.md 4 不变量 / 7 决策 / 口述版
├── 赢客下单-改造方案.md 6 缺陷(3🔴)→ 3 阶段
├── 全仓其余域-候选价值点.md 二轮 8 候选(含翻案 ★7/★8)
├── 工单全生命周期-{功能点清单,设计思路与取舍,改造方案}.md
```
两个 P0 实锤(深挖才浮现的典型):
- 幂等双关口键不一致:bossOrderId vs requestId,组合穿透 → 重复扣权益;
- `updateConfirmInfo` 条件更新漏 `status=51`:设计是 DB 仲裁,落地是应用层检查 → 人工确认可被自动确认覆盖。
## 5. 已知限制与下一步
| 限制 | 说明 | 候选方向 |
|---|---|---|
| 单域节奏 | 一次一域,全仓扫描靠人反复发起 | 域清单自动切片 + 断点续扫 |
| 判据主观残留 | "简历够硬"仍依赖人对目标岗位的校准 | 按 JD 关键词加权候选排序 |
| 深挖成本 | 一个功能点 ≈ 1–2 份 20KB 文档,人工审读压力大 | 功能点清单先行 + 复盘/方案按勾选再出 |
| 校验器无自检 | check.py 自身损坏会静默通过 | CI 里对已知 BAD 样例断言必 FAIL |
@@ -0,0 +1,115 @@
---
name: value-dig
description: Use when the user has already chosen which mechanisms or value points matter and now wants them written up in depth — "把这个点写成文档", "把这条链路的实现整理成功能点", "写设计思路与取舍", "出改造方案", or when an existing module's design must be reconstructed layer by layer for an interview walkthrough. Read-only — never modifies the scanned project.
---
# value-dig · 深度产出(功能点 / 设计复盘 / 改造方案)
## 一句话
把**已经选定的**机制点写成**能讲、能追问、能被审**的文档:按需产出三件套。
**两条原则**
1. **价值优先 → 深入 → 更优方案。** 缺陷与改造只在「改造方案」里出现;功能点清单与设计复盘以"这个设计为什么好、怎么取舍"为主体。
2. **只读**:不修改被扫描项目的任何文件(新增产物不算)。
## 两种入口
| 入口 | 情形 | 怎么做 |
|---|---|---|
| **A · 有候选清单** | 上游 `value-scan` 已产出且人已勾选 | 直接开工;勾选结果回写候选清单「闸门状态」节(`✅ 已勾选:★N…⟨谁/日期⟩`) |
| **A′ · 候选被否决改向** | 人看了清单说"都不够硬,直接分析整条链路"——否决候选但给出新目标 | 三步:①原清单「闸门状态」记**否决原因原文摘要**(这是筛选口径的校准数据);②按 B 的最小枚举对准新目标重走;③产物头部声明"入口 A′(否决改向)" |
| **B · 直接点名** | 用户直接说"把 XX 链路写成文档",无候选清单 | 只针对该链路按 `value-scan` 的判据做一次**最小枚举**(按触发者拆链路 + 三条硬标准),在产物头部声明"入口 B" |
> **入口 B 不能省粗筛**:跳过判据会把"框架常识/纯业务实现"写成深度文档。
## 三份文档(各自独立,可只做其一)
> 若用户只要"每个点先给个轻理由看看"再决定深挖谁,可用 `assets/价值点报告模板.md` 快速产出一份轻量报告,不必直接进三件套。
| 文档 | 模板 | 内容 | 关键 |
|---|---|---|---|
| ① **功能点清单** | `assets/深度模板/功能点清单模板.md` | 链路总览 · **主干调用骨架** · 各功能点 · 推荐组合 · 附录速查 | **只记功能点,不列缺陷** |
| ② **设计思路与取舍** | `assets/深度模板/设计思路与取舍模板.md` | 第一人称复盘:问题定义 → 不变量 → 怎么拆 → 设计主线 → 逐环节决策表 → 做对的/承接不住的 → 重做改什么 → 面试口述版 | 讲**取舍与代价**,不只讲做法 |
| ③ **改造方案** | `assets/深度模板/改造方案模板.md` | 缺陷(含严重度)→ **改造前链路骨架**(整体调用关系,伪代码)→ 总体思路 → 分阶段(每点含**关键片段**(伪代码/SQL)+ 验收)→ 迁移灰度 → 不变量由谁保证 | **缺陷的唯一归属地**;复用既有资产,不重复建表。**代码讲解要求:交付标准是"照着能讲代码"**——§1 必有改造前链路骨架(写清谁调谁、每步做了什么,可标类名/方法名,**不写路径与行号**)、每个改造点必有「关键片段」(伪代码/SQL,只示形状);只有观点没有片段即不达标。**产出门槛:深挖浮现的缺口中至少存在一个②加机制/③重构档的改造点才立方案;全是①档补漏的(加缓存/加判断/加隔离/改配置类),在设计复盘"重做改什么"里带一句即可,不单独成文——硬写就是灌水** |
### ① 功能点清单的必备件:主干调用骨架
把整条链路的 N 段主干压成**一段连续伪代码**,每行右侧标注方式(`[锁]` `[幂等]` `[策略]` `[MQ]` `[外调]` `[落库]` `[兜底]` `[履历]` `[通知]` `[隔离]` `[验签]`);附方式图例(反向索引)。**每个功能点必须能在骨架上找到**(互为索引)。
### ③ 改造方案的取材工具(深入链路时用)
缺陷**不是枚举出来的,是贴着链路走一遍时浮现的**。深挖时用这三把刀看结构,发现的缺口按「机制模式库」比对出改造方向:
| 刀 | 怎么做 |
|---|---|
| **按触发者拆链路** | 找「同一行数据被几个触发者改」→ 判断收敛/防重是否单点 |
| **竞态矩阵** | 「并发场景 × 后果」穷举表:重复回调 / 回调与兜底并发 / 并发退款… |
| **幂等键清单 + 状态流转图** | 列出链路上所有幂等键并标层级;画状态机——"重复入账"与"状态与事实不符"的发现入口 |
**机制模式库**("结构性限制 → 可引入机制"对照表,改造方案的取材清单;**只有这一份,不复制**):
| 结构性限制(发现) | 可引入的机制 / 重构 |
|---|---|
| 业务写 + 发消息不原子 | 本地事务表 / Outbox + 投递器(业务与消息同事务,投递可重放) |
| 兜底任务不可恢复(poll 即删) | 任务表为权威,队列只做加速(宕机可恢复、积压可观测) |
| 不一致只有日志、无人知 | 一致性异常台账 + 告警 + 处理台(待处理 → 处理中 → 已处理) |
| 状态靠应用层"猜测" | CAS 条件更新 + 影响行数判定(把判断交给数据库) |
| 上游码散落多处、口径不一致 | 码字典化:每码标注「是否受理 / 是否终态 / 可重试」,多处共查一份 |
| "不确定"没有归宿 | 挂起态 + 巡检 + 重放(重放须走同一套分级,因为对方状态可能已变) |
| 对外调用有副作用却被重复调用 | 幂等键(业务唯一键)+ 冲突当命中 |
| 资金出去的方向弱于进来 | 对称化:退款补齐同构的锁 / 短路 / 唯一索引 |
| 重试责任多点(乘法放大) | 重试责任单点 + 显式关闭其他层(应用重试了,MQ 就不重试) |
| 状态写入早于事实 | 中间态 + 以权威回调驱动终态(不以"发起成功"为准) |
| 能力只覆盖一条路径 | 铺开:把一个点的分级铺成一张网(全部写类接口统一) |
| 跨系统没有事务 | 「幂等 + 可重放 + 对账」三件套替代事务 |
| 关键参数散落、取值无解释 | 参数登记与解释:汇总阈值 / 退避 / 分片并说明取值依据 |
### 改进的三档尺度
| 档 | 做法 | 简历硬度 |
|:-:|---|:-:|
| ① 补漏 | 修一个具体的错:加校验、加索引、补 try-catch | ⭐ |
| ② 加机制 | 引入一个新机制,替代"靠人 / 靠自觉"的现状 | ⭐⭐⭐ |
| ③ 重构 / 更合理设计 | 改结构本身:隐式判断变显式判定、口径收敛、方向对称化 | ⭐⭐⭐ |
**硬要求:每个缺陷都追问"能不能上升一档"**;改造方案里 ②③ 档应占多数。改进的固定 5 要素:① 结构性限制 ② 机制/方案 ③ 替代了什么 ④ 代价 ⑤ 为什么不用通用方案。
**缺陷严重度**:🔴 资金/数据错误 · 🟠 静默失败或重复调用 · 🟡 卫生与可维护性。
**边界**:只做系统级结构问题(一致性·幂等·可运营性·可恢复性·职责漂移·口径分散),不做代码风格/坏味道/命名/注释。
## 硬规则(合并版)
| # | 规则 | 为什么 |
|:-:|---|---|
| 1 | **深度闸门**:允许简短的伪代码/关键片段(CAS 语句·锁与短路顺序·状态分支·DDL·Lua 脚本)把机制画实——**行数是参考不是硬限,忠实于机制形状优先**;真正禁止的是大段源码摘录、代码块内行号、逐层讲解。**豁免** = 主干调用骨架(技术地图,压成一行一段) | 禁代码会"太空",贴源码则成"阅读报告" |
| 2 | **证据真实性**:锚点 `路径:行号`,**必须读过再写**;拿不准标【待确认】 | 错锚点毁掉可信度 |
| 3 | **只读**:不改被扫项目任何文件(新增产物不算) | — |
| 4 | **缺陷只进改造方案**:功能点清单与设计复盘不列缺陷表、不标严重度;设计复盘里"承接不住的地方"自然带出即可,不展开成清单 | 价值优先;缺陷集中一处才成体系 |
| 5 | **改进不停在补漏档;不写通用套话**("加监控""上分布式事务");必须贴合业务的具体语义/表/状态,优先复用既有资产 | ②③ 档才是简历上能写硬的 |
| 6 | **诚实性**:"设计对了但落地不完整"要明说;判断与代码事实不一致处逐条列出;文档写的方案必须回代码验是否落地 | 深度文档可信度的唯一来源 |
| 7 | **只做被点单的 1–3 个点**;每点先想清楚归哪份文档,不为凑齐三件套而写 | 实测一个功能点 ≈ 1–2 份 20KB 文档 |
## 格式保证
1. **模板外置**:读 `assets/` 对应模板填空。
2. **机械校验**(与 `value-scan` 共用 `../value-scan/assets/check.ps1`):
```powershell
$ck = .agents/skills/value-scan/assets/check.py
# ① 功能点清单
python $ck dig <产物> --template .agents/skills/value-dig/assets/深度模板/功能点清单模板.md
# ② 设计思路与取舍 / ③ 改造方案
python $ck doc <产物> --template .agents/skills/value-dig/assets/深度模板/<对应模板>.md
```
**FAIL 必须为 0**;WARN 允许保留但写明原因。
3. **偏差回写**:被纠正过就回写模板。
## 触发词与落盘
**触发词**:「把这个点写成文档」「把这条链路整理成功能点」「写设计思路与取舍」「出改造方案」。
**落盘**:`<项目根>/docs/`,与候选清单同目录(可被用户覆盖)。
**上游**:候选清单由 `value-scan` 产出。
@@ -0,0 +1,76 @@
# ⟨域⟩ · 价值点报告(S3 产物)
> **用途**:为 S2 勾选出的每个点写**可讲述的轻理由**(默认交付深度)。
> **上游**:`⟨域⟩-候选价值点.md`(S1)· **勾选来源**:⟨候选清单「闸门状态」节 / 用户消息直接点名⟩
> **证据快照**:基于 ⟨仓库名⟩ `⟨commit 短号 / 分支⟩`,⟨读取日期⟩。**换版本要重核**。
> **判据**:**简历行测试**——填不出机制的退回「附录·待定」
> **路径简写**:`⟨简写⟩/` = `⟨真实路径⟩`(锚点一律用简写前缀)
---
## 每个点 = 7 类内容
> **7 类不是"行数上限"**。关键片段与偏差附录**另有归属,不占这 7 类**。
> **点编号沿用 S1 的 ★ 编号**(便于与候选清单对照)。
### ★⟨N⟩ · ⟨机制名⟩
| # | 行 | 内容 |
|:-:|---|---|
| 1 | **简历行** | 用【⟨机制⟩】解决了【⟨具体问题/场景⟩】,代价是【⟨取舍⟩】 |
| 2 | **Problem** | ⟨不做会怎样 / 原来的做法会出什么问题⟩ |
| 3 | **Pattern** | ⟨机制是什么、怎么起作用⟩ |
| 4 | **Alternatives** | ⟨当时还能怎么做 / 为什么没选⟩ |
| 5 | **Tradeoffs** | ⟨代价:新增复杂度 · 依赖 · 运维负担⟩ |
| 6 | **Evidence** | `⟨简写⟩/⟨路径⟩.java:⟨行号⟩` · ⟨可核对的图/表/配置⟩ |
| 7 | **追问预判** | **问**:⟨高概率追问 1⟩ **答**:⟨一句话⟩<br/>**问**:⟨高概率追问 2(可选,最多 2 条)⟩ **答**:⟨一句话⟩ |
**关键片段 · ⟨片段名⟩**(≤10 行伪代码,把"只有名词"的地方画实)
```
⟨CAS 语句 / 锁与短路顺序 / 状态分支 / DDL⟩
```
**事实强度**(可选,用于区分"已核实"与"推断")
| 结论 | 强度 |
|---|---|
| ⟨…⟩ | **已核实**(读过代码) |
| ⟨…⟩ | **推断**(未核实,需人工确认) |
⟨重复上面 3 块,每点一节⟩
---
# 附录 · 与候选清单的偏差
> **实读代码后必须回头核对 S1 的表述**。S1 是"候选"不是"事实"——**偏差本身就是高价值材料**(往往是简历上最硬的一条)。
| # | S1 怎么写的 | 代码事实 | 处理 |
|:-:|---|---|---|
| 1 | ⟨S1 的原文⟩ | ⟨实读结果 + `路径:行号`⟩ | ⟨纠正 / 标【待确认】/ 升级为独立价值点⟩ |
---
# 报告尾 · 推荐组合
> **先讲哪 3 个**——给"追问密度最高 / 最能体现设计能力"的组合。**内容列必须是上面已写的 ★ 编号。**
| 组合 | 内容(★ 编号) | 适合 |
|---|---|---|
| **主线(推荐)** | ⟨★N + ★M⟩ | ⟨最能体现什么能力,追问密度最高⟩ |
| **完整版** | ⟨★1 + ★2 + ★3 + ★4 + ★5⟩ | ⟨讲清完整链路⟩ |
| **差异化** | ⟨★K⟩ | ⟨少数人会讲的点⟩ |
**建议讲述顺序**:⟨★N → ★M → ★K,并说明为什么这个顺序⟩
---
# 附录 · 待定
> 简历行**填不出机制**的点落在这里(**不删除**——防灌水,也不误杀)。
> ⚠️ **闸门二只在本阶段生效**:进入 S4 的点若机制仍未定,改为在其小节头部标 **【待确认:机制未定】**,**不在 S4 新建「待定」节**。
| # | 点 | 为什么填不出 | 需要补什么才能定 |
|:-:|---|---|---|
| 1 | | | |
@@ -0,0 +1,174 @@
# ⟨域⟩ 域 · 功能点清单
> **用途**:把「⟨链路主题⟩」这条链路按其设计文档与代码实读整理成功能点
> **流程**(与上游一致):**先记录功能点 → 定稿文档 → 再讨论最佳设计与取舍**
> **上游**:`⟨域⟩-候选价值点.md`(S1)+ 勾选结果(S2)
> **素材来源**:`⟨wiki/xxx-spec.md⟩`(⟨N⟩ 行)+ `⟨仓库名⟩` 代码实读
> **路径简写**:`⟨简写1⟩/` = `⟨真实路径⟩`;`⟨简写2⟩/` = `⟨真实路径⟩`
---
# 一、链路总览
**一句话**:⟨统领句——给出这条链路的**主线判断**,不是复述流程⟩
```mermaid
flowchart TD
A["⟨起点⟩"] --> B["⟨★ 机制节点⟩"]
B --> C{"⟨分支⟩"}
C -->|"⟨路径⟩"| D["⟨汇合点 / 分级发生地⟩"]
C -->|"⟨路径⟩"| D
D --> E{"⟨结果类型⟩"}
E -->|"⟨可逆⟩"| F["⟨处置⟩"]
E -->|"⟨不确定⟩"| G["⟨处置⟩"]
```
⟨可选⟩**关键差异表(设计的核心)**
| ⟨维度⟩ | ⟨取值1⟩ | ⟨取值2⟩ | ⟨取值3⟩ |
|---|---|---|---|
| ⟨例:对方结果⟩ | | | |
| ⟨例:可逆性⟩ | | | |
| ⟨例:处置⟩ | | | |
---
## 主干调用骨架(一眼看清哪一步用了什么方式)
> **这是"整条链路的技术地图"**——把 N 段主干压成**一段连续伪代码**,每行标注**用了什么方式**(`[锁]` `[幂等]` `[策略]` `[MQ]` `[兜底]` …)。
> 局部片段回答"一个机制长什么样",这一段回答"**哪些地方用了什么**",以及**哪些地方本该有兜底却没有**。
```java
// ═══ ① ⟨阶段名⟩ ═══
⟨方法名⟩(): ⟨动作⟩ // [锁]
⟨动作⟩ // [幂等]
⟨动作⟩ // [策略]
⟨动作⟩ // [外调]
// ═══ ② ⟨阶段名⟩ ═══
⟨方法名⟩(): ⟨动作⟩ // [验签]
⟨动作⟩ // [MQ]
⟨动作⟩ // [落库]
// ═══ ③ ⟨兜底 / 失败处置⟩ ═══
⟨方法名⟩(): ⟨动作⟩ // [兜底]
⟨动作⟩ // [重试] 只重超时
⟨动作⟩ // [履历]
```
**方式图例(反向索引:一种方式出现在哪些地方)**
| 标记 | 方式 | 出现在 |
|---|---|---|
| `[锁]` | ⟨分布式锁(注解式 / Redisson)⟩ | ⟨…⟩ · ⟨…⟩ |
| `[幂等]` | ⟨状态短路 + CAS + 一次性消费⟩ | ⟨…⟩ · ⟨…⟩ |
| `[策略]` | ⟨策略模式 + 工厂⟩ | ⟨…⟩ |
| `[MQ]` | ⟨Kafka⟩ | ⟨…⟩ |
| `[兜底]` | ⟨延时队列 + XXL-Job⟩ | ⟨…⟩ |
| `[重试]` | ⟨`@Retryable`(**只重超时**)⟩ | ⟨…⟩ |
| `[履历]` | ⟨DB 台账⟩ | ⟨…⟩ |
| `[验签]` | ⟨网关签名⟩ | ⟨…⟩ |
| `[隔离]` | ⟨try-catch / 线程池⟩ | ⟨…⟩ |
| `[外调]` / `[落库]` / `[通知]` | ⟨外部系统调用 / DB 写 / 站内信⟩ | 全链路 |
> **这张骨架的两个用处**:① **评审时一眼看清技术分布**(⟨锁 3 处、幂等 5 处、兜底 4 处⟩);② **横向对比**"同样的机制在别处有没有用"。
---
# 二、功能点(⟨N⟩ 个)
> 每个点:**核心内容** / **这个点在讲什么** / **一句话价值** / **图** / **关键片段(≤10 行伪代码)** / **关键表与字段** / **关键做法与证据**。
> **段落式**,不要压成表格("做了什么"常是 5–7 条,塞进单元格必然被简化)。
## ① ⟨功能点名⟩ ⭐⭐⭐⭐⭐
**核心内容**:⟨1 句,把"形状"说清(一把锁 + 三级短路 + 一条条件更新)⟩
**这个点在讲什么**
- **业务场景**:⟨谁在什么时刻触发了什么,为什么这事难;点出"同一时刻还有谁在改同一行数据"⟩
- **做了什么**(⟨用一句话概括做法⟩):
1. ⟨…⟩;
2. ⟨…⟩;
3. ⟨…⟩。
- **只把 fail 留给"我真的还没处理"**:⟨边界在哪⟩(若适用)
- **临界场景**:⟨并发 / 乱序 / 迟到时的行为⟩(若适用)
- **解决了什么问题**:⟨不做会怎样⟩
**一句话价值**:⟨说清"判断权 / 控制权"落在谁手里⟩
**图 · ⟨图名⟩**
```mermaid
flowchart TD
A["⟨入口⟩"] --> B{"⟨判断⟩"}
B -->|"⟨…⟩"| B1["⟨抢不到锁:直接回 success,让给持锁方⟩"]
B -->|"⟨…⟩"| C["⟨动作⟩"]
C --> D["⟨落库⟩"]
```
**关键片段 · ⟨片段名⟩**(伪代码,只示形状)
```
⟨CAS 语句 / 锁与短路顺序 / 状态分支 / DDL,≤10 行⟩
```
**关键表与字段**
| 表 | 关键字段 | 说明 |
|---|---|---|
| `⟨表名⟩` | `⟨字段⟩` · **`⟨关键字段⟩`** | ⟨说明⟩ |
**关键做法与证据**
| 做法 | 证据 |
|---|---|
| ⟨做法⟩ | `⟨简写⟩/⟨路径⟩.java:⟨行号⟩` |
| ⟨做法⟩ | 同上 `:⟨行号⟩` |
⟨重复 ① 的结构,每个功能点一节⟩
---
# 三、推荐组合
| 组合 | 内容 | 适合 |
|---|---|---|
| **主线(推荐)** | ⟨① + ⑤⟩ | ⟨最能体现什么能力,追问密度最高⟩ |
| **完整版** | ⟨① + ② + ③ + ④ + ⑤⟩ | ⟨讲清完整链路⟩ |
| **差异化** | ⟨④⟩ | ⟨少数候选人会讲的点⟩ |
---
# 四、设计亮点
## 4.1 亮点(面试可直接讲)
| # | 亮点 | 价值 |
|:-:|---|---|
| 1 | ⟨亮点⟩ | ⟨价值——**逐条对着旧实现的病灶**,不是堆框架⟩ |
| 2 | | |
> **本模板不设缺陷表**:缺陷、严重度与改进方案统一在《⟨主题⟩-改造方案.md》中呈现。深挖过程中发现的结构缺口先记到工作笔记,写改造方案时再展开。
---
# 附录 A · ⟨状态 / 结果类型⟩速查
| ⟨类型⟩ | 含义 | 终态? | ⟨处置1⟩ | ⟨处置2⟩ | 重试 |
|---|---|:-:|:-:|:-:|---|
| `⟨枚举值⟩` | | ✅ / ❌ | | | |
# 附录 B · 参数速查
| 参数 | 值 | 出处 |
|---|---|---|
| ⟨锁 leaseTime⟩ | | `⟨路径⟩.java:⟨行号⟩` |
| ⟨重试次数⟩ | | |
| ⟨延时 / 超时⟩ | | |
# 附录 C · 关键类索引
| 类 | 角色 |
|---|---|
| `⟨类名⟩` | ⟨在链路里的角色⟩ |
@@ -0,0 +1,233 @@
# ⟨主题⟩ · 改造方案
> **背景**:现状问题登记在本文第 0 节(**缺陷唯一归属地**);复盘视角的"承接不住"见《⟨主题⟩-设计思路与取舍.md》第 5 节
> **目标**:⟨把「X」从**一条路的设计**,变成**覆盖全部写接口的事实**⟩
> **边界**:⟨不改省侧协议、不引入分布式事务(改造点全在本服务内);DDL 只列关键字段,完整建表脚本另出⟩
> **与其它方案的关系**:⟨一致性异常台账**复用**《⟨其它⟩-改造方案.md》的 `⟨表名⟩`,**不重复建表**,只新增 `⟨列/取值⟩`⟩
> **依据**:全部改造点均**对应代码中已核实的缺陷,非推测**
> **取材**:深挖链路时发现的结构性缺口,按 `value-dig` SKILL 的「机制模式库」(结构性限制 → 可引入机制)比对出改造方向;每个缺陷追问"能否从补漏上升到加机制/重构"
> **代码讲解要求(必读)**:本文交付标准是"**照着能讲代码**"——① §1 先给**改造前链路骨架**(整体调用关系,用伪代码写清"谁调谁、每一步做了什么",可标类名/方法名,**不写文件路径与行号**);② **每个改造点**必带「**关键片段**」(伪代码 / SQL,只示形状、不贴源码)。密度基准:`docs/order-改造方案.md`
> **产出门槛**:缺口中至少存在一个**②加机制 / ③重构**档的改造点(更好设计、重构、引入中间件级)才立本文档;全是①档补漏(加缓存/加判断/加隔离/改配置)的**不产出**——在设计复盘"重做改什么"小节记录即可
---
## 0. 现状问题登记(缺陷唯一归属地)
> 功能点清单与设计复盘**不承载缺陷表**;深挖中发现的问题全部登记在此,后文逐条消化。
**严重度**:🔴 资金/数据错误 · 🟠 静默失败或重复调用 · 🟡 卫生与可维护性
| # | 缺陷 | 证据 | 后果 | 严重度 |
|:-:|---|---|---|:-:|
| 1 | ⟨缺陷⟩ | ⟨可核对的事实:行为 / 字段 / 日志⟩ | ⟨后果⟩ | 🔴 |
| 2 | ⟨…⟩ | | | 🟠 |
| 3 | ⟨…⟩ | | | 🟡 |
**归纳**:这些不是孤立的 bug,而是 **⟨N⟩ 处结构性缺口**:
1. **⟨缺口一⟩** —— ⟨…⟩;
2. **⟨缺口二⟩** —— ⟨…⟩;
3. **⟨缺口三⟩** —— ⟨…⟩。
---
## 1. 总体思路
**改造前链路骨架**(先定位"改的是链路的哪一段"——用伪代码写清谁调谁、每一步做了什么):
```java
⟨入口类#方法⟩()
→ ⟨A类#方法⟩() // 这一步做了什么(对应缺陷 #1)
→ ⟨B类#方法⟩()
→ ⟨C类#方法⟩() // 对应缺陷 #3
```
所以改造是做 N 件事,**而不是调参数**:
1. **⟨把一个点的分级 → 铺成一张网⟩**
2. **⟨把「不确定」从日志提升为一等状态⟩**
3. **⟨把状态推进的时机对齐到权威事实⟩**
```mermaid
flowchart TB
subgraph BEFORE["改造前"]
B1["⟨…⟩"] --> B2["⟨…⟩"]
B3["⟨…⟩"] --> B4["⟨…⟩"]
end
subgraph AFTER["改造后"]
A1["⟨…⟩"] --> A2["⟨…⟩"]
A2 --> A3["⟨…⟩"]
A4["⟨…⟩"] --> A5["⟨…⟩"]
end
```
---
## 2. 阶段一 · ⟨阶段主题:先说止血/可见⟩
> 成本最低、收益最大,**优先做**。⟨N⟩ 处改动都只动本服务内部。
### 2.1 ⟨改造点名⟩(P0,最重要)
- **现状**:⟨…⟩(对应第 0 节缺陷 #⟨N⟩)。
- **后果(真金白银)**:⟨…⟩ 这**违反了 ⟨不变量一⟩**。
- **改造**:⟨…⟩
**关键片段 · ⟨片段名⟩**(伪代码 / SQL,只示形状)
```sql
⟨改造后的语句或结构;与改造前的旧写法形成对照⟩
```
| ⟨对象⟩ | ⟨写/读⟩ | 可逆性 | 处置 |
|---|---|---|---|
| ⟨接口/动作⟩ | 写 | ⟨拒绝可逆、超时不可逆⟩ | ⟨…⟩ |
- **注意**:⟨切换后**必须同步补"超时不可逆"的判断**,否则只是把"归档超时 → 退款"换成"归档超时 → 抛异常 → 同样退款",问题原地不动⟩。
```mermaid
flowchart TD
A["⟨入口⟩"] --> B{"⟨判断⟩"}
B -->|"改造后"| C{"分类"}
B -->|"现状"| Z["⟨旧行为⟩"]
C -->|"⟨可逆⟩"| D["⟨处置⟩"]
C -->|"⟨不确定⟩"| E["⟨处置⟩"]
```
### 2.⟨N⟩ ⟨改造点名⟩
- **现状**:⟨…⟩(对应第 0 节缺陷 #⟨N⟩)
- **改造**:⟨…⟩ **原则:⟨异常类型就是后果分类的载体,不能在中间被抹平⟩。**
- **验收**:⟨人为造一次 ⟨场景⟩,应出现 ⟨可观测结果⟩⟩。
**关键片段 · ⟨片段名⟩**
```java
⟨改造后的分支形状,只示形状⟩
```
### 2.⟨N⟩ 阶段一验收标准
| 指标 | 目标 |
|---|---|
| ⟨…⟩ | ⟨100% 不产生 ⟨错误动作⟩,转 ⟨正确处置⟩⟩ |
| ⟨人为造 X⟩ | ⟨出现可观测的 ⟨证据⟩⟩ |
---
## 3. 阶段二 · ⟨阶段主题:再说可靠/对称/一等状态⟩
> 阶段一保证"不再做错",阶段二保证"⟨挂起能被收走⟩"。
### 3.1 ⟨幂等键改造⟩
- **现状**:⟨没有幂等键 → 可被重复执行;MQ `maxRetry=3` 在异常路径上会重复调用外部⟩(对应缺陷 #⟨N⟩)
- **改造**:⟨唯一索引 · 冲突当幂等命中 · 语义:"同一 X + 同一动作 = 只允许一次对外调用"⟩
- **迁移**:⟨先查存量重复,治理后再加索引⟩
**关键片段 · 索引 + 冲突即命中**
```sql
ALTER TABLE ⟨表名⟩ ADD UNIQUE KEY ⟨uk名⟩ (⟨列⟩, ⟨列⟩);
```
```java
try { ⟨对外调用⟩; }
catch (DuplicateKeyException e) { return; } // 已执行过 → 直接返回,不再调外部
```
### 3.2 ⟨挂起态 + 巡检⟩
- **现状**:⟨…⟩(对应缺陷 #⟨N⟩)
- **改造**:
1. **⟨新增挂起态⟩**,与"失败""成功"并列,**不占用失败的语义**;
2. **巡检任务**(⟨XXL-Job⟩)按"距离挂起时长"分级:⟨<1h 自动重放一次 / 1–24h 告警 / >24h 人工队列⟩;
3. 重放走**同一套分级链路**(不新开代码路径)。
**关键片段 · ⟨片段名⟩**
```java
⟨挂起态写入 / 巡检取单的形状⟩
```
```mermaid
flowchart TD
A["⟨不确定⟩"] --> B["⟨挂起 + 台账⟩"]
B --> C["告警"]
B --> D["巡检任务"]
D --> E{"挂起时长"}
E -->|"⟨短⟩"| F["自动重放"]
E -->|"⟨中⟩"| G["升级告警"]
E -->|"⟨长⟩"| H["人工队列"]
```
**关键设计:重放不是"再调一次接口",而是"重新走一遍分级"。** 因为对方的状态可能已经变了,重放必须能得出"成功"这个结论。
### 3.⟨N⟩ ⟨改造点名⟩
| `⟨anomaly_type⟩` 取值 | 触发场景 | 处置方向 |
|---|---|---|
| `⟨…⟩` | | |
- **收益**:⟨两侧的失败**进同一张台账、走同一套告警和处理台**——运维只需要看一个地方。**这是"复用"而不是"新建"的价值。**⟩
### 3.⟨N⟩ 阶段二验收标准
| 指标 | 目标 |
|---|---|
| ⟨重复调用外部⟩ | 0 次 |
| ⟨挂起单⟩ | 100% 台账可见 + 100% 被巡检捞到 |
---
## 4. 阶段三 · ⟨阶段主题:最后收口卫生问题⟩
| # | 改什么 | 现状证据(缺陷 #⟨N⟩) | 成本 |
|:-:|---|---|---|
| 1 | ⟨…⟩ | ⟨可核对的事实⟩ | 低 |
| 2 | ⟨…⟩ | | 低 |
**⟨某条不是文档工作,是防错工作⟩**:⟨文档漂移会让后来人**按错误的图改代码**,成本远高于改文档。⟩
---
## 5. 实施顺序与成本收益
| 阶段 | 内容 | 成本 | 解决的根问题 | 关键收益 |
|:-:|---|---|---|---|
| 一 | ⟨…⟩ | 低 | ⟨…⟩ | **不再出错** |
| 二 | ⟨…⟩ | 中 | ⟨…⟩ | **不确定可运营** |
| 三 | ⟨…⟩ | 低 | 卫生与可维护性 | 可观测与可维护 |
**顺序逻辑**:先修**会花错钱**的(阶段一),再修**会重复调用外部系统 / 卡住看不见**的(阶段二),最后才是卫生(阶段三)。
---
## 6. 迁移与灰度
1. **⟨逐项切⟩**:先切**风险最高、问题最实**的 ⟨X⟩,观察一周无异常再切 ⟨Y⟩;每次切换**只改一个调用点**,便于回滚。
2. **⟨新增状态先"只记录不流转"⟩**:先写台账 + 告警**观察一周**,确认没有误报,再打开自动重放。
3. **⟨索引先查存量⟩**:先排查存量重复;加索引后**观测冲突命中次数**——这个数就是"原设计漏掉的重复调用次数"。
4. **回滚**:全部改造以"**新增状态 + 新增列 + 开关**"方式落地,回滚只需关开关/停调度,**不动存量数据**。
---
## 7. 改造后:⟨N⟩ 条不变量由谁保证
| 不变量 | 改造前 | 改造后 |
|---|---|---|
| **⟨不变量一⟩** | ⟨只有一条路成立;某路径会被错退⟩ | ⟨全部写类接口统一分类⟩ |
| **⟨不变量二⟩** | ⟨只有一行日志,库里无痕⟩ | ⟨挂起态 + 台账 + 巡检 + 重放⟩ |
| **⟨不变量三⟩** | ⟨实际只有两态(超时被降级抹平)⟩ | ⟨异常类型不被中间层改写⟩ |
---
## 附:与《⟨其它⟩-改造方案.md》的关系
| 项 | ⟨其它侧⟩ | ⟨本文⟩ |
|---|---|---|
| ⟨一致性异常台账⟩ | **建** `⟨表名⟩` | **复用**,只加取值 |
| ⟨Outbox⟩ | **建** `⟨表名⟩` | **复用同表**,`⟨biz_type⟩` 区分 |
| ⟨幂等⟩ | ⟨…⟩ | ⟨…⟩ |
**两边的根因是同一个**:⟨设计了机制,但**没有为"需要人介入"这个信号建自动出口**⟩。所以两套改造共用一套台账与告警,是**结构上的必然,而不是为了省事**。
@@ -0,0 +1,206 @@
# ⟨主题⟩ · 设计思路与取舍(复盘)(S4 产物 ②)
> **视角**:第一人称复盘——**我拿到「⟨需求名⟩」这个需求时,是怎么想、怎么设计、怎么取舍、预判会遇到什么问题的**。贴合现有实现,末尾给改进建议与可直接口述的版本。
> **配套文档**:功能点见同目录《⟨主题⟩-功能点清单.md》;⟨可选的关联文档⟩
> **说明**:文中「我会这样想」是设计时的推理;「实际做法」是代码里的真实实现,**两者不一致处必须标出**。
> **路径简写**:`⟨简写⟩/` = `⟨真实路径⟩`
---
## 0. 我拿到需求,先不写代码:把问题定义清楚
**需求的一句话**:⟨…⟩
**我第一件事是承认 N 个事实**,它们决定了后面所有设计:
1. **⟨例:对方的回答不是二元的⟩。** ⟨…⟩
2. **⟨例:几种失败的"可逆性"不同⟩。** ⟨…⟩
3. **⟨例:我没法从"失败"这个现象本身推出可逆性⟩。** ⟨…⟩
**由此我定下 N 条不变量(当成验收标准)**:
- **不变量一**:⟨例:只有确定对方没接单才退款⟩——⟨为什么⟩;
- **不变量二**:⟨例:不确定的失败不能当成失败处理⟩——⟨为什么⟩;
- **不变量三**:⟨例:每一种失败都必须有归属⟩——⟨为什么⟩。
**再看 N 个我不能改变的前提**:⟨省侧是权威 · 对外调用有副作用 · 没有跨系统事务 · …⟩
**结论**:⟨这不是"加个 try-catch 再套个重试框架"的需求,而是「把异常翻译成后果」的需求。⟩——**这句是我做取舍时反复回到的锚点。**
---
## 1. 我怎么拆这个需求
**第一刀,我按「⟨拆解维度⟩」拆,不按「⟨被否决的维度⟩」拆。**
| 为什么否决另一个维度 | ⟨例:按商品类型拆会得到三块,但三种商品的前置动作不同、汇合点却是同一个;各写一套会得到三份可能走偏的代码⟩ |
|---|---|
拆完是 ⟨N⟩ 条路 + ⟨N⟩ 个汇合点:
| 路径 | ⟨前置动作⟩ | ⟨性质⟩ | 风险 |
|---|---|---|---|
| ⟨…⟩ | | 异步 / 同步 | |
**第二刀,我找「⟨关键定位决策⟩」,这是本需求最关键的一个决策。**
**我的判断是**:⟨…⟩。理由:⟨只有那一层同时拿得到 X 和 Y;再往上一层只能看到一个已被包装过的异常,原始信息已经丢了⟩。
**实际做法与我的判断是否一致**:⟨是 / 否⟩,证据 `⟨路径:行号⟩`。
**这个决策的代价**:⟨例:调用层要维护两套调用方式;推广不全会造成覆盖面缺口(实际就漏了)⟩。
---
## 2. 我的设计主线:⟨一句话概括主线⟩
⟨这是我做的最核心的一个决定:不给 A/B/C 各写一套处理,而是让它们沿一条固定的链往下走,每层只做一件事。⟩
```
① ⟨层名(职责)⟩ → ② ⟨层名(职责)⟩ → ③ ⟨层名(职责)⟩ → ④ ⟨层名(职责)⟩
```
**⟨N⟩ 层不是我拍脑袋凑的,是从"上一版为什么会漏"反推出来的**——每层都对着一个旧实现的具体病灶:
| 层 | 旧实现的病灶 | 我的做法 | 为什么必须在这一层做 |
|:-:|---|---|---|
| ① | | `⟨路径:行号⟩` | ⟨信息只在最底层存在,越往上越不可恢复⟩ |
| ② | | | |
| ③ | | | |
| ④ | | | |
```mermaid
flowchart TD
A["⟨入口⟩"] --> B["① ⟨层⟩"]
B --> C{"② ⟨层⟩"}
C -->|"⟨…⟩"| D["③ ⟨层⟩"]
C -->|"⟨…⟩"| E["③ ⟨层⟩"]
D --> F["④ ⟨归集⟩"]
E --> F
F -->|"⟨可逆⟩"| G["⟨处置⟩"]
F -->|"⟨不确定⟩"| H["⟨处置⟩"]
```
### 2.1 ⟨N⟩ 重判断,才是这个设计的真正内容(标题按实际重数写,如「三重判断…」)
> 比"N 层"更重要的是**层里做的判断**——它们才是经验,层只是载体。
**判断一:⟨…⟩**
⟨用收益 × 概率算的:…⟩ **关键点是"不做什么"比"做什么"更难做对**——⟨因为框架默认什么都做⟩。
**判断二:⟨…⟩**
⟨…⟩ **代价必须自己扛**:⟨你关掉了 X,就必须自己承担 Y 的责任。⟩
**判断三:⟨…⟩**
⟨…⟩ **为什么"⟨反面做法⟩"是错的**:⟨…⟩
**还有一条我特意保留的例外**:⟨…⟩——因为 ⟨…⟩。
### 2.2 ⟨独立工具/组件名⟩ 为什么要写成一个独立⟨工具/组件⟩(若无可删本节)
⟨它看着简单,但它是整个 ⟨机制⟩ 的地基——判错一次,方向就错,后面全走偏。⟩
1. **精确匹配** ⟨…⟩;
2. **兜底模糊匹配** ⟨…⟩;
3. **逐层剥 `cause`**——⟨因为异常常被框架层层包装,真实原因往往在第三、四层⟩。
**这里的取舍我很清楚**:⟨选了"宁可多试一次"——因为重试的成本(多一次调用)远低于漏重试的成本(一单卡死)⟩。
---
## 3. 逐环节决策复盘
| # | 当时的问题 | 我的选择 | 为什么 | 代价 / 风险 |
|:-:|---|---|---|---|
| 1 | ⟨…⟩ | | | |
| 2 | | | | |
| 3 | | | | |
| … | | | | |
> **代价/风险列不可空**——只讲选择不讲代价,就等于没做取舍。
---
## 4. 我预判会遇到的问题(拿到需求时就能想到的)
1. **⟨…⟩。** ⟨…⟩
2. **⟨…⟩。** ⟨…⟩
3. **⟨…⟩。** ⟨这是我设计时预判到了却仍然漏掉的一条⟩
---
## 5. 我做对的 ⟨N⟩ 件事 / 承接不住的 ⟨N⟩ 件事
**做对的**
1. **⟨…⟩**——⟨为什么它最有价值;它是从业务语义推出来的,不是从技术模式套出来的⟩。
2. **⟨…⟩**——⟨…⟩
3. **⟨…⟩**——⟨方向是对的,只是推广范围不够⟩
**承接不住的**
1. **⟨…(最严重)⟩**——⟨后果;这是"设计对了但落地不完整"的典型案例⟩。
2. **⟨…⟩**
3. **⟨…⟩**
---
## 6. 如果让我重做,我会改什么(按性价比排序)
> 复盘视角只给**优先级判断**;分阶段展开、验收标准与灰度见《⟨主题⟩-改造方案.md》,此处不重复。
| 优先级 | 改什么 | 为什么 | 成本 | 收益 |
|:-:|---|---|---|---|
| **P0** | | ⟨**真金白银 / 会花错钱**⟩ | 低 | |
| **P1** | | | 中 | |
| **P2** | | | | |
| **P3** | | ⟨卫生问题⟩ | | |
**一句话排序逻辑**:先修**会花错钱和会重复调用外部系统的**(P0/P1),再做**让"不确定"可运营**(P1/P2),最后才是卫生问题(P3)。**因为前两类是"静默地把事情做错",后者只是"做得不够漂亮"。**
---
## 7. 这套设计里可以复用的方法论
1. **⟨先判"可逆性",再决定"要不要补偿"。⟩** ⟨任何跨系统写操作都适用…⟩
2. **⟨异常分层翻译:真相层 → 重试层 → 履历层 → 归集层。⟩** ⟨每层只做一件事,且必须在能拿到信息的最高层把信息捞住⟩
3. **⟨重试责任单点。⟩** ⟨否则是乘法关系,不是加法⟩
4. **⟨"不确定"必须是一等状态。⟩** ⟨否则会退化成一行日志,等于不存在⟩
5. **⟨分级的上限取决于覆盖面,不是取决于精细度。⟩** ⟨一条路上做四层分级,不如四条路上各做一层⟩
---
## 8. 附:面试口述版
### 8.1 一分钟版(先讲骨架)
> 「⟨需求一句话⟩。我的判断是:⟨核心判断⟩。所以我不能只写 try-catch,得**把 ⟨X⟩ 翻译成 ⟨Y⟩**。
>
> 设计上是 N 层:⟨逐层一句话⟩。调用方只做一件事:⟨…⟩。」
### 8.2 三分钟版(加取舍与代价)
在上一段基础上补三段:⟨取舍一(最看重的一点)+ 它的代价⟩ · ⟨取舍二 + 难在哪⟩ · ⟨要坦白的一点(覆盖面 / 落地不完整)⟩。
### 8.3 追问预判(问题 → 一句话答)
| 追问 | 答 |
|---|---|
| ⟨为什么…?⟩ | ⟨一句话⟩ |
| ⟨既然…,那…?⟩ | ⟨一句话,含"坦白说没有"这类诚实回答⟩ |
| ⟨这套设计最大的风险?⟩ | ⟨一句话⟩ |
---
## 附:与代码的对照说明
| 本文的判断 | 代码事实 | 是否一致 |
|---|---|---|
| ⟨分级放最底层⟩ | `⟨路径:行号⟩` | ✅ 一致,但**未推广** |
| ⟨只重超时⟩ | `⟨路径:行号⟩` | ✅ 一致 |
| ⟨…⟩ | | ⚠️ 部分一致 |
> **诚实性要求**:本文的判断与代码事实**不一致处必须逐条列出**——这是这份文档可信度的来源。
@@ -0,0 +1,112 @@
---
name: value-scan
description: Use when the user wants to inventory the mechanisms worth talking about in code that is already delivered — "盘点这个域/模块的价值点", "扫一下 order 域有什么值得讲的", "提取功能点", "这个项目有什么值得讲的设计", or when interview/resume material must be reconstructed backwards from an existing module. Read-only — never modifies the scanned project. Stops at the human selection gate; deep write-up is a separate skill.
---
# value-scan · 价值点盘点(广度枚举)
## 一句话
从**已交付的代码**里,逆向枚举出**值得讲的机制点**,交人勾选后**停下**。
**三条核心原则**
1. **agent 判不了"价值"**——判据不是分类体系,而是**简历行填空测试**。
2. **价值优先,本阶段不找缺陷。** 缺陷是深入链路时自然浮现的副产物,归 `value-dig`。
3. **只读。** 不修改被扫描项目的任何文件。
## 流程
```
S0 定范围 → S1 枚举 → S2 勾选【终点:产出清单后必须停】
↓(人勾选后)
value-dig(轻理由 → 设计复盘 / 功能点 / 改造方案)
```
| 需求 | 该用 |
|---|---|
| 盘点 / 扫一遍 / 提取候选价值点 | **本 skill** |
| 把选中的点写成深度文档 | `value-dig` |
| 找 bug、修缺陷 | `diagnose` |
## S0 · 定范围
- **快路**:仓库已有域清单(`.aspirecode/sdd/rules.md` §9、`pom.xml`、`wiki/` 目录)→ 直接读。
- **慢路**:陌生仓库 → 按接口路径前缀、`-api` Feign 接口名、controller 清单切出能力面,一次一个 package。
- 范围由**人给定**(一个域/模块),不一次扫全库。
**域是入口锚点,不是物理边界。** 完整链路往往跨模块,用两头夹的办法拼:
- **聚合层(web)定链路形状**:controller / Kafka consumer / XxlJob / callback 四类入口全在聚合层,且分包与路径自带业务语义(`/consumer/fulfillment/`、topic 名、请求路径)——从这里正向切出"有哪些触发者、哪些链路"。MQ 消费、定时任务这类**非 Feign 入口**靠反推找不到。
- **原子域挖机制内容**:锁/幂等/状态机等机制本体大多在原子域的 service 实现,聚合层只有编排——读实现要去原子域。
- **中间用调用图接**(`gitnexus_route_map` / `gitnexus_cypher`);被驱动型域也可用"谁 import 了我的 Feign 接口"反查调用方作兜底。
- 只读链路经过的路径,不通读途径模块。
## S1 · 枚举
### 取材顺序(信噪比递减)
| 顺序 | 来源 | 备注 |
|:-:|---|---|
| 1 | `wiki/*.md`、`.claude/docs/**`、`devflow/projects/**` | 先读「设计说明类」,后读「问题分析类」——先读问题分析会把清单带成缺陷导向 |
| 2 | `git log`:`feat(...)` / `refactor(...)`、带单号、单次大改动 | 有意识的改动藏着设计意图 |
| 3 | `gitnexus_query` / `gitnexus_route_map` | 用图查结构,不读全文 |
| 4 | 代码结构统计 + 关键字 grep(锁/重试/MQ/Job/条件更新/状态机/延时/对账) | 只做清单式定位,不通读代码 |
> ⚠️ 读完文档必须回代码验一遍"文档写的方案是否落地"。**"文档 vs 代码分叉"本身就是高价值点**。
### 怎么看结构:按「触发者」拆链路
不按业务功能拆,按**谁触发**拆:找出「**同一行数据被几个触发者改**」(如回调/查单/取消都改同一行支付流水)——收敛点多半就是机制所在。通常 1–4 条链路,被驱动型模块可到 5–6 条(超过要写一句为什么)。
每条链路挂 1–4 个机制节点。**候选颗粒度 = 机制/能力**,不是注解、类、配置项——"用了 `@LockAction`、有 4 个 handler"是实现清单;跨 ≥2 个写类入口或 ≥2 张表的才够"机制"。
### 判据 · 简历行测试
**一级 · 准入(三条硬标准,全过才进清单)**
| 标准 | 反例(据此剔除) |
|---|---|
| ① **核心链路**(资金/履约/交付这类业务闭环) | 购物车 key 命名、查询接口 |
| ② **高级工程师的设计**(设计决策,非框架常识) | Redis `GETDEL`、Spring 自注入修事务自调用 |
| ③ **简历够硬**(能写成"我设计了 X 机制解决 Y") | 定长文件 + 签名 + SFTP 的对接实现 |
**二级 · 表达**:`用【机制】解决了【具体问题/场景】,代价是【取舍】`——本阶段只试填"机制+问题"两格;填不出机制 → 并入「已剔除」表(标 `机制格填不出`,待复核);填不出取舍**不误杀**(取舍归 `value-dig`)。
> ⚠️ **`机制格填不出` 的剔除门槛(防误杀,实测翻案教训)**:机制本体常藏在实现大类(2000+ 行)的私有方法群里,入口类名/Job 类名只是壳——**没读过入口方法向下 ~100 行,不得以此理由定稿剔除**。剔除非此理由的照常。被剔除后复评翻案的,在剔除表原行标注撤销原因与日期(如"2026-09-18 复评撤销:快照轮询机制实存,升级为 ★N"),不删行——翻案记录本身就是筛选口径的进化证据。
被剔除的点进「已剔除」表(注明未过哪条标准),不丢弃。
### 数量
亮点 **5–8 条**为宜。超过 12 条 = 颗粒度掉到实现层,退回重并。
## S2 · 门控(终点,不可跳过)
1. 候选清单**已落盘**(每条含「一句话价值」与锚点),头部「闸门状态」节写明"S1 已完成 · 待勾选";
2. 回复里**显式请求勾选**("请从这 N 条里挑 3–6 条")。用户禁用提问时也一样停——把请求写成文字,**不是**替他挑完继续写文档。
3. **否决即校准**:人否决候选("不够硬"/"一般")时,把否决原因记入清单(闸门状态或剔除表)——下次扫同域/同仓先读上次的否决记录,同类点不再重复上报;整批否决且未给新目标 = 流程终点,不强续。
## 硬规则(合并版,替代旧的硬规则/反模式/Gotchas 三张表)
| # | 规则 | 为什么 |
|:-:|---|---|
| 1 | **深度闸门**:无围栏代码块(Mermaid 除外,行内反引号可用);不贴源码、不写行号、不逐层拆解、不展开取舍 | 本阶段是广度层 |
| 2 | **不找缺陷、不读"坏味道报告"**;把克制当设计("manager 只有 3 个"可能是有意的) | 缺陷导向会摧毁清单 |
| 3 | **证据真实性**:锚点 `⟨简写⟩/路径#方法名`(机制级可锚到类;不写行号),**读过再写**,拿不准标【待确认】,每条 ≤2 个 | 错锚点毁掉可信度 |
| 4 | **每条链路(含未入选的)都要写「是什么 + 为什么入选/未入选」**;0 机制先问"真没有,还是采样不到位" | 链路是骨架,不因无亮点而省略 |
| 5 | **三层结构**:域 → 链路 → 机制节点,机制必须挂在链路图上 | 并列清单会显得"都是散的" |
| 6 | **只读 + 门控**:不改被扫项目文件;产出后必停 | — |
## 格式保证
1. **模板外置**:读 `assets/候选清单模板.md` 填空,不照印象写。
2. **机械校验**:产出后跑 `python <skills>/value-scan/assets/check.py scan -Product <产物> -Template <skills>/value-scan/assets/候选清单模板.md`(跨平台 Python 版;Windows 下若中文乱码先设 `PYTHONIOENCODING=utf-8`。旧 `check.ps1` 保留但不再维护)。FAIL 必须为 0;WARN 允许保留但写明原因。
3. **偏差回写**:被纠正过就回写模板。模板是活资产。
## 触发词与落盘
**触发词**:「盘点这个模块的价值点」「扫一下 X 域有什么值得讲的」「提取功能点」。
**落盘**:`<项目根>/docs/{域}-候选价值点.md`(可被用户覆盖)。
**下一步**:用户勾选后,用 `value-dig` 接手。
@@ -0,0 +1,268 @@
<#
value-scan / value-dig 产物机械校验(替代人工核对)
------------------------------------------------------------------
用法:
# S1 候选清单
powershell -ExecutionPolicy Bypass -File check.ps1 -Mode scan `
-Product <产物路径> -Template <skills>/value-scan/assets/候选清单模板.md
# S4 功能点清单
powershell -ExecutionPolicy Bypass -File check.ps1 -Mode dig `
-Product <产物路径> -Template <skills>/value-dig/assets/深度模板/功能点清单模板.md
# S4 设计思路与取舍 / 改造方案(整篇级文档,只跑通用校验)
powershell -ExecutionPolicy Bypass -File check.ps1 -Mode doc -Product <path> -Template <path>
退出码:0 = 无 FAIL;1 = 有 FAIL
说明:FAIL = 必错;WARN = 需人判断
#>
param(
[Parameter(Mandatory = $true)]
[ValidateSet('scan', 'dig', 'doc')]
[string]$Mode,
[Parameter(Mandatory = $true)]
[string]$Product,
[string]$Template
)
$ErrorActionPreference = 'Stop'
$script:fail = New-Object System.Collections.Generic.List[string]
$script:warn = New-Object System.Collections.Generic.List[string]
$script:pass = New-Object System.Collections.Generic.List[string]
function Fail($m) { $script:fail.Add($m) }
function Warn($m) { $script:warn.Add($m) }
function Pass($m) { $script:pass.Add($m) }
$prodPath = (Resolve-Path -LiteralPath $Product).Path
$text = [System.IO.File]::ReadAllText($prodPath, [System.Text.Encoding]::UTF8)
if ($text.Length -gt 0 -and [int][char]$text[0] -eq 0xFEFF) { $text = $text.Substring(1) }
$lines = $text -split "`r?`n"
# ---------------------------------------------------------------- 围栏代码块
$fences = @()
$inFence = $false; $fStart = 0; $fLang = ''
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^\s*```') {
if (-not $inFence) {
$inFence = $true; $fStart = $i; $fLang = ($lines[$i] -replace '^\s*```', '').Trim()
}
else {
$inFence = $false
$fences += [pscustomobject]@{ Start = $fStart; End = $i; Lang = $fLang; Body = ($i - $fStart - 1) }
}
}
}
if ($inFence) { Fail "存在未闭合的代码围栏(起始行 $($fStart + 1))" }
# ---------------------------------------------------------------- 通用 1:引号逐行配对
$oddQuoteLines = @()
for ($i = 0; $i -lt $lines.Count; $i++) {
if ((([regex]::Matches($lines[$i], '"')).Count % 2) -ne 0) { $oddQuoteLines += ($i + 1) }
}
if ($oddQuoteLines.Count -eq 0) { Pass '引号逐行配对' }
else { Fail ("引号未配对的行: " + ($oddQuoteLines -join ', ')) }
# ---------------------------------------------------------------- 通用 2:mermaid 合法性
$mermaidFences = @($fences | Where-Object { $_.Lang -eq 'mermaid' })
$mmBad = 0
foreach ($f in $mermaidFences) {
$body = ($lines[($f.Start + 1)..($f.End - 1)] -join "`n")
$firstLine = (($body -split "`r?`n") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1)
$endCount = ([regex]::Matches($body, '(?m)^\s*end\s*$')).Count
if ($firstLine -match 'sequenceDiagram') {
# 时序图:end 收的是 alt/opt/loop/par/critical/break/rect
$blk = ([regex]::Matches($body, '(?m)^\s*(alt|opt|loop|par|critical|break|rect)\b')).Count
if ($blk -ne $endCount) { Fail "mermaid 时序图(第 $($f.Start + 1) 行起)alt/opt/loop 等 $blk 个但 end=$endCount"; $mmBad++ }
}
else {
$sg = ([regex]::Matches($body, '(?m)^\s*subgraph\s')).Count
if ($sg -ne $endCount) { Fail "mermaid 流程图(第 $($f.Start + 1) 行起)subgraph=$sg 但 end=$endCount"; $mmBad++ }
if ($body -match '(?m)^\s*subgraph\s+\S+\s*\[' -and $body -notmatch '(?m)^\s*subgraph\s+\S+\s*\["') {
Fail "mermaid(第 $($f.Start + 1) 行起)subgraph 缺引号标题,必须写 subgraph id[`"标题`"]"; $mmBad++
}
if ($body -match '(?m)^\s*subgraph\s+\S+\s*\[\(') {
Fail "mermaid(第 $($f.Start + 1) 行起)用了 subgraph xxx[(...)],会解析失败"; $mmBad++
}
}
}
if ($mermaidFences.Count -gt 0 -and $mmBad -eq 0) { Pass "mermaid 块 $($mermaidFences.Count) 个通过" }
# ---------------------------------------------------------------- 通用 3:表格列数一致
$ti = 0
while ($ti -lt $lines.Count) {
if ($lines[$ti] -match '^\s*\|') {
$blk = @(); $bs = $ti
while ($ti -lt $lines.Count -and $lines[$ti] -match '^\s*\|') { $blk += $lines[$ti]; $ti++ }
$counts = @($blk | ForEach-Object { ([regex]::Matches($_, '\|')).Count } | Sort-Object -Unique)
if ($counts.Count -gt 1) { Fail "表格列数不一致(第 $($bs + 1) 行起):pipe 数 = $($counts -join '/')" }
}
else { $ti++ }
}
# ---------------------------------------------------------------- 通用 3b:表格不得缩进(嵌套在列表内的表多数渲染器不显示)
$indentedTable = @()
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^[ \t]+\|') { $indentedTable += ($i + 1) }
}
if ($indentedTable.Count -eq 0) { Pass '表格均顶格(无嵌套缩进表)' }
else { Fail ('表格存在缩进(第 ' + ($indentedTable -join ', ') + ' 行起)——嵌套在列表里的表多数渲染器不显示,必须顶格') }
# ---------------------------------------------------------------- 通用 4:章节完整性(模板必备节 ⊆ 产出节)
if ($Template) {
$tPath = (Resolve-Path -LiteralPath $Template).Path
$tText = [System.IO.File]::ReadAllText($tPath, [System.Text.Encoding]::UTF8)
if ($tText.Length -gt 0 -and [int][char]$tText[0] -eq 0xFEFF) { $tText = $tText.Substring(1) }
$tLines = $tText -split "`r?`n"
$tHeads = $tLines | Where-Object { $_ -match '^\s*#+\s+\S' }
$missingLiteral = @(); $missingPlaceholder = @()
foreach ($h in $tHeads) {
$t = ($h -replace '^\s*#+\s+', '') -replace '\s+$', ''
$hasPh = $t.Contains([string][char]0x27E8)
$sentinel = '@@PH@@'
$phRx = [string][char]0x27E8 + '[^' + [string][char]0x27E9 + ']*' + [string][char]0x27E9
$rx = [regex]::Escape([regex]::Replace($t, $phRx, $sentinel)).Replace($sentinel, '.*')
$hit = $false
foreach ($pl in $lines) { if ($pl -match ('^\s*#+\s+' + $rx + '\s*$')) { $hit = $true; break } }
if (-not $hit) {
if ($hasPh) { $missingPlaceholder += $t } else { $missingLiteral += $t }
}
}
if ($missingLiteral.Count -eq 0) { Pass '章节完整性:模板必备节全部存在' }
else { Fail ('缺章节(字面量,必错): ' + ($missingLiteral -join ' | ')) }
if ($missingPlaceholder.Count -gt 0) { Warn ('示例性章节未匹配(可能条数不同,需人判): ' + ($missingPlaceholder -join ' | ')) }
}
# ---------------------------------------------------------------- 定位辅助
function Get-HeadIndex($pattern) {
for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match $pattern) { return $i } }
return -1
}
function Get-NextHeadIndex($from) {
for ($i = $from + 1; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*#+\s+\S') { return $i } }
return $lines.Count
}
# ---------------------------------------------------------------- scan 专属
if ($Mode -eq 'scan') {
# 1) 禁止围栏代码块(mermaid 除外)
$nonMermaid = @($fences | Where-Object { $_.Lang -ne 'mermaid' })
if ($nonMermaid.Count -eq 0) { Pass '深度闸门:S1 无围栏代码块(仅 mermaid)' }
else { Fail ("S1 不允许围栏代码块,发现 $($nonMermaid.Count) 个(第 " + (($nonMermaid | ForEach-Object { $_.Start + 1 }) -join ', ') + ' 行起)') }
# 2) 锚点格式:路径#方法名(不带行号,每条最多 2 个)
$badAnchor = @()
$anchorCount = 0
foreach ($l in $lines) {
if ($l -match '^\s*\*\*锚点\*\*\s*[::]\s*(.+?)\s*$') {
$anchorCount++
$v = $Matches[1]
$items = @($v -split '[、,,]' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
if ($items.Count -gt 2) { $badAnchor += ("锚点超过 2 个($($items.Count) 个): " + $l.Trim()) }
foreach ($it in $items) {
$a = $it.Trim().Trim('`')
if ($a -match ':\d') { $badAnchor += ("S1 锚点不写行号 -> $a") }
elseif ($a -notmatch '\w[/\\]\w') { $badAnchor += ("S1 锚点须为 路径#方法名(机制级可到类名) -> $a") }
}
}
}
if ($anchorCount -eq 0) { Warn '未找到 **锚点** 行(若产物为空则忽略)' }
elseif ($badAnchor.Count -eq 0) { Pass "锚点格式合规($anchorCount 处,路径#方法名)" }
else { Fail ('锚点格式不合规: ' + ($badAnchor -join ' ; ')) }
# 3) 闸门状态节
if ((Get-HeadIndex '^\s*#+\s*闸门状态') -ge 0) { Pass '有「闸门状态」节(S2 门控有证据)' }
else { Fail '缺「闸门状态」节 —— S2 门控没有证据' }
# 4) 候选条数(提示性:超 12 才 FAIL,无下限硬卡)
$starCount = ($lines | Where-Object { $_ -match '^\s*#+\s*★' }).Count
if ($starCount -le 12) { Pass "候选条数 $starCount(≤12)" }
else { Fail "候选条数 $starCount 超过 12(颗粒度掉到实现层,需重并)" }
if ($starCount -lt 5 -and $starCount -gt 0) { Warn "候选条数 $starCount 少于 5——先确认是否采样不到位,而非真没有" }
}
# ---------------------------------------------------------------- dig 专属
if ($Mode -eq 'dig') {
$skelIdx = Get-HeadIndex '^\s*#+\s*主干调用骨架'
$skelEnd = if ($skelIdx -ge 0) { Get-NextHeadIndex $skelIdx } else { -1 }
if ($skelIdx -ge 0) { Pass '有「主干调用骨架」节' }
else { Warn '未找到「主干调用骨架」节(仅 S4 ① 功能点清单必需)' }
# 1) 局部代码块 ≤10 行(骨架块豁免)
$exempt = 0
foreach ($f in $fences) {
if ($f.Lang -eq 'mermaid') { continue }
$isSkel = ($skelIdx -ge 0 -and $f.Start -gt $skelIdx -and $f.Start -lt $skelEnd)
if ($isSkel) { $exempt++; continue }
if ($f.Body -gt 10) { Warn "局部代码块超过 10 行(第 $($f.Start + 1) 行起,$($f.Body) 行)——行数非硬限,请人判断是关键片段还是源码摘录" }
}
Pass ("局部代码块行数检查完成(豁免骨架块 $exempt 个)")
# 2) 骨架每行必须带方式标记
if ($skelIdx -ge 0) {
$skelFence = @($fences | Where-Object { $_.Start -gt $skelIdx -and $_.Start -lt $skelEnd }) | Select-Object -First 1
if (-not $skelFence) { Warn '骨架节里没有代码块' }
else {
$missTag = @()
foreach ($bl in ($lines[($skelFence.Start + 1)..($skelFence.End - 1)])) {
if ([string]::IsNullOrWhiteSpace($bl)) { continue }
if ($bl -match '^\s*//') { continue } # 分段注释行
if ($bl -match ':\s*$') { continue } # 块头行(xxx(): ),标"谁"不标"怎么做"
if ($bl -notmatch '[(\[]') { continue } # 连接词行(↓ 三路汇合)
if ($bl -notmatch '//') { $missTag += $bl.Trim() } # 其余动作行必须有方式标记
}
if ($missTag.Count -eq 0) { Pass '骨架每行都带方式标记' }
else { Fail "骨架有 $($missTag.Count) 行缺方式标记: " + (($missTag | Select-Object -First 3) -join ' ; ') }
}
}
# 3) 证据须含 行号
if ($text -notmatch '\.(java|xml|yaml|yml|sql):\d+') { Fail '未找到任何 `文件:行号` 证据(本阶段必须有可核对的锚点)' }
else { Pass '存在 `文件:行号` 形式的证据' }
}
# ---------------------------------------------------------------- doc 专属
if ($Mode -eq 'doc') {
# 改造方案识别:含「现状问题登记」节(设计思路与取舍模板不含此节)
$isPlan = (Get-HeadIndex '^\s*#+\s*0\.\s*现状问题登记') -ge 0
if ($isPlan) {
# 1) 必有非 mermaid 代码片段(伪代码 / SQL)
$codeFences = @($fences | Where-Object { $_.Lang -ne 'mermaid' })
if ($codeFences.Count -ge 1) { Pass "改造方案含关键片段 $($codeFences.Count) 个" }
else { Fail '改造方案缺「关键片段」代码块——必须"照着能讲代码"(伪代码/SQL 均可,只禁大段源码摘录)' }
# 2) 片段应覆盖"骨架 + 改造点",只给一块多半只在开头充数
if ($codeFences.Count -ge 2) { Pass "关键片段 $($codeFences.Count) 个(骨架 + 改造点)" }
elseif ($codeFences.Count -eq 1) { Warn '只有 1 个关键片段——可能只有链路骨架,各改造点未给伪代码,需人判' }
}
}
# ---------------------------------------------------------------- 输出
Write-Output ""
Write-Output ("=" * 68)
Write-Output "机械校验 Mode=$Mode"
Write-Output (" product : " + $prodPath)
if ($Template) { Write-Output (" template : " + (Resolve-Path -LiteralPath $Template).Path) }
Write-Output ("=" * 68)
if ($script:pass.Count -gt 0) {
Write-Output ""
Write-Output "[PASS]"
foreach ($m in $script:pass) { Write-Output (" + " + $m) }
}
if ($script:warn.Count -gt 0) {
Write-Output ""
Write-Output "[WARN] 需人判断"
foreach ($m in $script:warn) { Write-Output (" ! " + $m) }
}
if ($script:fail.Count -gt 0) {
Write-Output ""
Write-Output "[FAIL] 必错"
foreach ($m in $script:fail) { Write-Output (" x " + $m) }
}
Write-Output ""
Write-Output ("结果:PASS {0} / WARN {1} / FAIL {2}" -f $script:pass.Count, $script:warn.Count, $script:fail.Count)
if ($script:fail.Count -gt 0) { exit 1 } else { exit 0 }
@@ -0,0 +1,307 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
value-scan / value-dig 产物机械校验(Python 版,等价移植自 check.ps1,跨平台)
------------------------------------------------------------------
用法:
# S1 候选清单
python check.py scan <产物路径> --template <skills>/value-scan/assets/候选清单模板.md
# S4 功能点清单
python check.py dig <产物路径> --template <skills>/value-dig/assets/深度模板/功能点清单模板.md
# S4 设计思路与取舍 / 改造方案(整篇级文档,只跑通用校验)
python check.py doc <产物路径> --template <路径>
退出码:0 = 无 FAIL;1 = 有 FAIL;2 = 用法/IO 错误
说明:FAIL = 必错;WARN = 需人判断
"""
import argparse
import re
import sys
from pathlib import Path
FAIL, WARN, PASS = [], [], []
# ⟨ ⟩ 占位符(U+27E8 / U+27E9)
PH_L, PH_R = '\u27e8', '\u27e9'
def fail(m):
FAIL.append(m)
def warn(m):
WARN.append(m)
def pass_(m):
PASS.append(m)
def read_text(path_str):
p = Path(path_str)
if not p.is_file():
print(f"[ERROR] 文件不存在: {path_str}", file=sys.stderr)
sys.exit(2)
text = p.read_text(encoding='utf-8-sig') # 自动剥 BOM
return text, text.splitlines()
def head_index(lines, pattern):
for i, line in enumerate(lines):
if re.search(pattern, line):
return i
return -1
def next_head_index(lines, start):
for i in range(start + 1, len(lines)):
if re.match(r'^\s*#+\s+\S', lines[i]):
return i
return len(lines)
def line_snip(line, width=40):
"""报错附带的行内容摘要(改进:报错不指内容曾导致误诊)"""
s = line.strip().replace('|', '\\|')
return s[:width] + ('…' if len(s) > width else '')
def main():
ap = argparse.ArgumentParser(description='value-scan/value-dig 产物机械校验')
ap.add_argument('mode', choices=['scan', 'dig', 'doc'])
ap.add_argument('product', help='产物路径')
ap.add_argument('--template', '-t', help='模板路径')
ap.add_argument('-Mode', '-Product', '-Template', dest='legacy', help=argparse.SUPPRESS)
args = ap.parse_args()
prod_path = Path(args.product).resolve()
text, lines = read_text(args.product)
# ------------------------------------------------ 围栏代码块
fences = []
in_fence = False
f_start, f_lang = 0, ''
for i, line in enumerate(lines):
if re.match(r'^\s*```', line):
if not in_fence:
in_fence, f_start = True, i
f_lang = re.sub(r'^\s*```', '', line).strip()
else:
in_fence = False
fences.append({'start': f_start, 'end': i, 'lang': f_lang,
'body': i - f_start - 1})
if in_fence:
fail(f'存在未闭合的代码围栏(起始行 {f_start + 1})')
# ------------------------------------------------ 通用 1:引号逐行配对
odd_quote = [i + 1 for i, line in enumerate(lines)
if line.count('"') % 2 != 0]
if not odd_quote:
pass_('引号逐行配对')
else:
fail('引号未配对的行: ' + ', '.join(map(str, odd_quote)))
# ------------------------------------------------ 通用 2:mermaid 合法性
mermaid_fences = [f for f in fences if f['lang'] == 'mermaid']
mm_bad = 0
for f in mermaid_fences:
body_lines = lines[f['start'] + 1:f['end']]
body = '\n'.join(body_lines)
first = next((l for l in body_lines if l.strip()), '')
end_count = len(re.findall(r'(?m)^\s*end\s*$', body))
if 'sequenceDiagram' in first:
blk = len(re.findall(r'(?m)^\s*(alt|opt|loop|par|critical|break|rect)\b', body))
if blk != end_count:
fail(f"mermaid 时序图(第 {f['start'] + 1} 行起)alt/opt/loop 等 {blk} 个但 end={end_count}")
mm_bad += 1
else:
sg = len(re.findall(r'(?m)^\s*subgraph\s', body))
if sg != end_count:
fail(f"mermaid 流程图(第 {f['start'] + 1} 行起)subgraph={sg} 但 end={end_count}")
mm_bad += 1
if (re.search(r'(?m)^\s*subgraph\s+\S+\s*\[', body)
and not re.search(r'(?m)^\s*subgraph\s+\S+\s*\["', body)):
fail(f'mermaid(第 {f["start"] + 1} 行起)subgraph 缺引号标题,必须写 subgraph id["标题"]')
mm_bad += 1
if re.search(r'(?m)^\s*subgraph\s+\S+\s*\[\(', body):
fail(f'mermaid(第 {f["start"] + 1} 行起)用了 subgraph xxx[(...)],会解析失败')
mm_bad += 1
if mermaid_fences and mm_bad == 0:
pass_(f'mermaid 块 {len(mermaid_fences)} 个通过')
# ------------------------------------------------ 通用 3:表格列数一致(含行内容摘要)
ti = 0
while ti < len(lines):
if re.match(r'^\s*\|', lines[ti]):
bs = ti
blk = []
while ti < len(lines) and re.match(r'^\s*\|', lines[ti]):
blk.append(lines[ti])
ti += 1
counts = sorted({b.count('|') for b in blk})
if len(counts) > 1:
fail(f"表格列数不一致(第 {bs + 1} 行起):pipe 数 = {'/'.join(map(str, counts))}"
f"|首行内容: {line_snip(blk[0])}")
else:
ti += 1
# ------------------------------------------------ 通用 3b:表格不得缩进
indented = [i + 1 for i, line in enumerate(lines) if re.match(r'^[ \t]+\|', line)]
if not indented:
pass_('表格均顶格(无嵌套缩进表)')
else:
fail('表格存在缩进(第 ' + ', '.join(map(str, indented)) + ' 行起)——嵌套在列表里的表多数渲染器不显示,必须顶格')
# ------------------------------------------------ 通用 4:章节完整性
if args.template:
_, t_lines = read_text(args.template)
t_heads = [l for l in t_lines if re.match(r'^\s*#+\s+\S', l)]
missing_literal, missing_ph = [], []
for h in t_heads:
t = re.sub(r'\s+$', '', re.sub(r'^\s*#+\s+', '', h))
has_ph = PH_L in t
ph_rx = re.escape(PH_L) + '[^' + re.escape(PH_R) + ']*' + re.escape(PH_R)
rx = re.escape(re.sub(ph_rx, '@@PH@@', t)).replace('@@PH@@', '.*')
hit = any(re.match(r'^\s*#+\s+' + rx + r'\s*$', pl) for pl in lines)
if not hit:
(missing_ph if has_ph else missing_literal).append(t)
if not missing_literal:
pass_('章节完整性:模板必备节全部存在')
else:
fail('缺章节(字面量,必错): ' + ' | '.join(missing_literal))
if missing_ph:
warn('示例性章节未匹配(可能条数不同,需人判): ' + ' | '.join(missing_ph))
# ------------------------------------------------ scan 专属
if args.mode == 'scan':
non_mermaid = [f for f in fences if f['lang'] != 'mermaid']
if not non_mermaid:
pass_('深度闸门:S1 无围栏代码块(仅 mermaid)')
else:
fail('S1 不允许围栏代码块,发现 {} 个(第 {} 行起)'.format(
len(non_mermaid),
', '.join(str(f['start'] + 1) for f in non_mermaid)))
bad_anchor, anchor_count = [], 0
for line in lines:
m = re.match(r'^\s*\*\*锚点\*\*\s*[::]\s*(.+?)\s*$', line)
if not m:
continue
anchor_count += 1
items = [s for s in re.split(r'[、,,]', m.group(1)) if s.strip()]
if len(items) > 2:
bad_anchor.append(f'锚点超过 2 个({len(items)} 个): {line.strip()}')
for it in items:
a = it.strip().strip('`')
if re.search(r':\d', a):
bad_anchor.append(f'S1 锚点不写行号 -> {a}')
elif not re.search(r'\w[/\\]\w', a):
bad_anchor.append(f'S1 锚点须为 路径#方法名(机制级可到类名) -> {a}')
if anchor_count == 0:
warn('未找到 **锚点** 行(若产物为空则忽略)')
elif not bad_anchor:
pass_(f'锚点格式合规({anchor_count} 处,路径#方法名)')
else:
fail('锚点格式不合规: ' + ' ; '.join(bad_anchor))
if head_index(lines, r'^\s*#+\s*闸门状态') >= 0:
pass_('有「闸门状态」节(S2 门控有证据)')
else:
fail('缺「闸门状态」节 —— S2 门控没有证据')
star_count = sum(1 for l in lines if re.match(r'^\s*#+\s*★', l))
if star_count <= 12:
pass_(f'候选条数 {star_count}(≤12)')
else:
fail(f'候选条数 {star_count} 超过 12(颗粒度掉到实现层,需重并)')
if 0 < star_count < 5:
warn(f'候选条数 {star_count} 少于 5——先确认是否采样不到位,而非真没有')
# ------------------------------------------------ dig 专属
if args.mode == 'dig':
skel_idx = head_index(lines, r'^\s*#+\s*主干调用骨架')
skel_end = next_head_index(lines, skel_idx) if skel_idx >= 0 else -1
if skel_idx >= 0:
pass_('有「主干调用骨架」节')
else:
warn('未找到「主干调用骨架」节(仅 S4 ① 功能点清单必需)')
exempt = 0
for f in fences:
if f['lang'] == 'mermaid':
continue
if skel_idx >= 0 and skel_idx < f['start'] < skel_end:
exempt += 1
continue
if f['body'] > 10:
warn(f"局部代码块超过 10 行(第 {f['start'] + 1} 行起,{f['body']} 行)"
f"——行数非硬限,请人判断是关键片段还是源码摘录")
pass_(f'局部代码块行数检查完成(豁免骨架块 {exempt} 个)')
if skel_idx >= 0:
skel_fences = [f for f in fences if skel_idx < f['start'] < skel_end]
if not skel_fences:
warn('骨架节里没有代码块')
else:
sf = skel_fences[0]
miss_tag = []
for bl in lines[sf['start'] + 1:sf['end']]:
if not bl.strip():
continue
if re.match(r'^\s*//', bl):
continue
if re.search(r':\s*$', bl):
continue
if not re.search(r'[(\[]', bl):
continue
if '//' not in bl:
miss_tag.append(bl.strip())
if not miss_tag:
pass_('骨架每行都带方式标记')
else:
fail(f"骨架有 {len(miss_tag)} 行缺方式标记: "
+ ' ; '.join(miss_tag[:3]))
if not re.search(r'\.(java|xml|yaml|yml|sql):\d+', text):
fail('未找到任何 `文件:行号` 证据(本阶段必须有可核对的锚点)')
else:
pass_('存在 `文件:行号` 形式的证据')
# ------------------------------------------------ doc 专属
if args.mode == 'doc':
is_plan = head_index(lines, r'^\s*#+\s*0\.\s*现状问题登记') >= 0
if is_plan:
code_fences = [f for f in fences if f['lang'] != 'mermaid']
if len(code_fences) >= 1:
pass_(f'改造方案含关键片段 {len(code_fences)} 个')
else:
fail('改造方案缺「关键片段」代码块——必须"照着能讲代码"(伪代码/SQL 均可,只禁大段源码摘录)')
if len(code_fences) >= 2:
pass_(f"关键片段 {len(code_fences)} 个(骨架 + 改造点)")
elif len(code_fences) == 1:
warn('只有 1 个关键片段——可能只有链路骨架,各改造点未给伪代码,需人判')
# ------------------------------------------------ 输出
print()
print('=' * 68)
print(f'机械校验 Mode={args.mode}')
print(f' product : {prod_path}')
if args.template:
print(f" template : {Path(args.template).resolve()}")
print('=' * 68)
for tag, bucket, mark in (('[PASS]', PASS, '+ '), ('[WARN] 需人判断', WARN, '! '), ('[FAIL] 必错', FAIL, 'x ')):
if bucket:
print()
print(tag)
for m in bucket:
print(f' {mark}{m}')
print()
print(f'结果:PASS {len(PASS)} / WARN {len(WARN)} / FAIL {len(FAIL)}')
sys.exit(1 if FAIL else 0)
if __name__ == '__main__':
main()
@@ -0,0 +1,146 @@
# ⟨域⟩ 域 · 候选价值点(S1 产物)
> **阶段**:S1 枚举(**只产出亮点**;缺陷不在此阶段——它是深入链路时自然浮现的副产物)
> **域**:`⟨模块名⟩`(走 S0 ⟨快路 / 慢路⟩:⟨域清单来源⟩)
> **结构**:**域 → 链路 → 链路上的机制节点** 三层
> **筛选标准(三条,全过才进)**:**① 是否核心链路 ② 是否符合高级工程师的设计 ③ 写到简历上够不够硬**
> **深度闸门**:允许「**这个点在讲什么**」的业务语言说明;**禁止围栏代码块(行内反引号可用)、禁止逐层拆解、禁止取舍复盘**(那些属深度阶段)
> **取材**:① 设计说明类文档 ② `git log` ③ 图查询 / 结构统计 ④ 关键字 grep
---
# 闸门状态
> **本节是 S2 门控的证据**——没有它,"停在第几段"无法核查。
| 段 | 状态 | 说明 |
|:-:|---|---|
| S0 定范围 | ✅ | ⟨范围声明一句话⟩ |
| **S1 枚举** | ✅ 已完成 | 候选 ⟨N⟩ 条(**亮点**)+ 剔除 ⟨M⟩ 条 |
| **S2 勾选** | ⏸ **待人工勾选** | **请从下面挑 3–6 条**;本文件到此为止,**未做深度产出** |
> **勾选/否决后回写本表(保持 3 列不压列)**,两种回写形状:
>
> ```
> | **S2 勾选** | ✅ 已勾选 | ⟨勾了哪些(★N…)/ 谁日期⟩ → 产出《⟨产物名⟩》 |
> | **S2 勾选** | ↩️ 被否决改向 | 否决原因:⟨用户原话摘要⟩ → 改挖 ⟨新目标⟩(入口 A′) |
> ```
---
## 一、链路总览
**一句话**:⟨统领句——不是复述流程,而是给出这条链路的**主线判断**。例:"这条链路的每一段都在把外部系统给的不确定,收敛成我方的确定状态"⟩
**读法**:⟨一条端到端链路 = …→…→…;下面每个机制都挂在这张图的某个位置上⟩
**图例**:`★` = 入选的机制节点;**未标 ★ 的链路仍属本链路的一环**(见第二、三节),只是未列为独立机制。
### 端到端链路图(★ = 入选的机制节点)
```mermaid
flowchart LR
subgraph PA["链路 A · ⟨链路名⟩"]
A1["⟨起点⟩"] --> A2["⟨机制节点⟩ ★1"]
A3["⟨兜底 / 分支⟩ ★2"]
end
subgraph PB["链路 B · ⟨链路名⟩"]
B1["⟨起点⟩"] --> B2["⟨机制节点⟩ ★3"]
B2 --> B3["⟨机制节点⟩ ★4"]
end
subgraph PC["链路 C · ⟨链路名⟩"]
C1["⟨起点⟩"] --> C2["⟨终点⟩"]
end
A2 --> B1
A3 -.-> A2
B3 --> C1
C2 -.-> A2
```
⟨可选⟩**关键差异表**(若这条链路的核心是"几种结果后果完全不同",用一张表压住)
| ⟨维度⟩ | ⟨取值1⟩ | ⟨取值2⟩ | ⟨取值3⟩ |
|---|---|---|---|
| ⟨例:对方结果⟩ | | | |
| ⟨例:可逆性⟩ | | | |
| ⟨例:处置⟩ | | | |
---
## 二、链路上的机制节点(⟨N⟩ 个 ★)
> **按链路分组**。每点 4 段,**段落式**(不要压成表格——"做了什么"常是 5–7 条,塞进单元格必然被简化)。
### 链路 ⟨A⟩ · ⟨链路名⟩
## ★1 · ⟨机制名⟩
**核心内容**:⟨1 句:这个机制是什么,把它的"形状"说清(一把锁 + 三级短路 + 一条条件更新)⟩
**这个点在讲什么**
- **业务场景**:⟨谁在什么时刻触发了什么,为什么这事难;点出"同一时刻还有谁在改同一行数据"⟩
- **做了什么**(⟨用一句话概括做法⟩):
1. ⟨…⟩;
2. ⟨…⟩;
3. ⟨…⟩。
- **解决了什么问题**:⟨不做会怎样⟩
**一句话价值**:⟨1 句,可讲述;说清"判断权 / 控制权"落在谁手里⟩
**锚点**:`⟨简写⟩/⟨路径⟩#⟨方法名⟩`
---
## ★2 · ⟨机制名⟩
⟨同 ★1 的 4 段结构⟩
---
### 链路 ⟨B⟩ · ⟨链路名⟩
## ★3 · ⟨机制名⟩
⟨同 ★1 的 4 段结构⟩
---
### 链路 ⟨C⟩ · ⟨链路名⟩(⟨依附型,未列为独立机制 / 未入选⟩)
> **本链路 0 个独立机制,但说明不可省**——否则图上有个框、没人知道里面在干嘛。
**核心内容**:⟨它是什么⟩
**这个点在讲什么**
- **业务场景**:⟨…⟩
- **做了什么**:⟨…⟩
- **依附关系**:⟨它的哪几个设计决策其实是 ★N 在另一个方向 / 另一个域上的复用;判不清就按独立节点处理,不要为了凑数强行依附⟩
- **为什么未入选 / 为什么依附**:⟨★ 必须写清⟩
**锚点**:`⟨…⟩`
---
## 三、已剔除(附理由,供复核筛选口径)
> **被剔除的点不丢弃**——注明**未过哪条标准**。
**「未过的标准」建议取值**:`①核心链路` · `②非设计决策(框架常识 / 通用工程质量)` · `③简历不硬` · `机制格填不出` · `示例数据 / 字典表 / 配置装配`
| # | 被剔除的点 | 未过的标准 | 理由 |
|:-:|---|---|---|
| 1 | ⟨例:key 命名约定⟩ | `①核心链路` | 不落在业务闭环上 |
| 2 | ⟨例:Redis `GETDEL` 用法⟩ | `②非设计决策` | 框架常识用法,非设计决策 |
| 3 | ⟨例:契约模式 / CI / Checkstyle⟩ | `②非设计决策` | 通用工程质量,无设计含量 |
| 4 | ⟨例:表 CRUD / 单表封装⟩ | `机制格填不出` | 弱候选,并入本表待复核 |
---
## 四、待确认 / 覆盖缺口
| # | 项 | 说明 |
|:-:|---|---|
| 1 | **⟨链路名⟩ 0 个节点** | ⟨先问"真没亮点,还是采样不到位"(回看该链路的触发者与写类接口);判不清就写明理由⟩ |
| 2 | `⟨路径⟩#⟨方法名⟩` | 【待确认】⟨拿不准的锚点必须标出,禁止猜⟩ |
| 3 | **文档 vs 代码分叉** | ⟨设计文档写的方案在代码里是否落地?不落地本身就是高价值点⟩ |