Spec腐烂解决方案:OpenGeno树+Hook开源库
引言
最近 Spec-Driven Development(SDD)成为热门话题,OpenSpec、Kiro 这类工具的核心思想是:先编写一份规范(spec),再由 AI 依据规范来实现代码。
实际在多个项目中实践后,发现每次都在同一环节受阻:
这并非某个工具的缺陷,而是流程层面的问题——spec 描述的是“计划要做”,而代码描述的是“实际已完成”。只要后者仍在迭代,前者就必然落后。AI 基于过期 spec 生成的代码,比完全没有 spec 更危险——它会根据错误的事实做出决策。
因此,我尝试了一条反向路径:GDD(Geno-Driven Development),开源仓库名为 OpenGeno。本文拆解它的设计、与 SDD 的对比,以及在实际落地后才领悟的取舍。
一、SDD 的失败模式:spec 有效周期过短
抽象一下 SDD 的工作循环:
1. 人写 spec2. AI 读 spec → 写代码3. (代码合入)4. 下次任务 → AI 再读 spec → 写代码
第 3 步与第 4 步之间,没有任何机制确保 spec 仍与代码一致。维护 spec 的责任被默默推给了“AI 顺手更新”或“审核者提醒”。这两者都是软约束,而软约束无法抵抗时间侵蚀。
更严峻的是,spec 本身是大型文档:一份 200 行的 spec 只改了 5 行,diff 看似很小,但语义可能已经完全偏离——而 AI 读取时毫无察觉。
二、GDD 的赌注:以代码为真理之源,将文档作为可验证的索引
GDD 的逆向假设很简单:
- 代码是唯一会被强制同步的事实(CI 持续运行、用户持续使用);
- 文档的作用不是“驱动开发”,而是为 AI 提供加载入口和语义注解;
- 文档与代码的一致性不能依赖人的自律,必须依靠机器自动核对。
落实到实现只有三件事:
1. 三层结构,按需懒加载
feat-tree/├── index.md# L1:项目根索引,列模块├── auth/│ ├── index.md# L2:模块索引,列 feature│ ├── sign-in.md# L3:单个 feature 的详情│ └── sign-out.md└── tasks/├── index.md└── list-view.md
AI 修改 sign-in 时无需读取 tasks 模块的 50 个 feature。它从 L1 定位到目标在 auth/,进入 auth/index.md 看到 sign-in.md,再读取那一篇。无论项目有 5 个还是 50 个 feature,单次任务消耗的 token 量几乎相同。
2. 每个 L3 都附带“已对账”的 SHA
L3 的 frontmatter 结构如下:
---type: og-featurekind: uifeature: sign-inmodule: authschema: 1code:- lib/features/auth/sign_in_page.dart- lib/features/auth/sign_in_controller.dart- lib/api/auth_service.dartlast_synced_commit: a1b2c3dlast_reviewed: 2026-05-06---
两个关键字段:
code:该 feature 依赖哪些代码文件;last_synced_commit:该文档最近一次被人或 AI 对照代码验证过的 git SHA。
SHA 的语义不是“被编辑的时间”,而是“被验证的时间”。这个区别至关重要:单纯修改文档不算对账,必须读取代码、确认一致后才能更新。
3. 漂移检测:将“忘记更新”转化为机器可发现的问题
有了 code: 和 last_synced_commit:,漂移检测只需一个简单脚本:
# 伪码for doc in feat-tree/**/*.md:last_sha = doc.frontmatter.last_synced_commitfor code_path in doc.frontmatter.code:if git_log(code_path, since=last_sha) is not empty:mark doc as DRIFT
该脚本被注册为 Claude Code 的 Stop hook——每次会话结束时自动运行。两种模式:
warn(默认):打印漂移摘要,session 正常结束;block:退出码 1,session 不允许结束,除非漂移被处理。
软约束变为硬约束。AI 忘记更新文档不再是“下次再说”,而是“立即解决”。
三、SDD vs GDD:对比表
| 维度 | SDD(OpenSpec / Kiro 等) | GDD(OpenGeno) |
|---|---|---|
| 起点 | spec 先于 code | code 已存在,文档跟随 |
| 文档形态 | 单份 spec / 大块 markdown | L1 / L2 / L3 三层树 |
| AI 加载方式 | 一次性读完整 spec | 沿 L1→L2→L3 按需加载 |
| 维护机制 | 靠人/AI 自觉同步 | last_synced_commit + Stop hook 强制对账 |
| 适用阶段 | 偏新项目 | 任意阶段(含遗留代码) |
| 失败模式 | spec 腐烂、AI 读错 | 漂移会被检测,最差只是被提醒一次 |
| 介入复杂度 | 写 spec 是前置任务 | 一次 init,之后规则注入 CLAUDE.md 自传递 |
它们并非替代关系。SDD 解决“从零到一如何让 AI 写出正确代码”,GDD 解决“从一到无穷如何让 AI 持续写对”。新项目可以先使用 SDD 生成第一版,再用 GDD 维护。
四、整体流程图
整个系统分为三个阶段,分开来看更清晰。
4.1 一次性初始化
┌─────────────────────────────────────────────────┐│User: /geno-init │└────────────────────┬────────────────────────────┘ │ ▼┌────────────────────────┐│ ① 选择语言(中文 / 英文)│└────────────┬───────────┘ ▼ ┌──────────────────────────────┐ │ ② 选择漂移模式(warn / block) │ └──────────────┬───────────────┘▼ ┌──────────────────────────────┐ │ ③ 选择生成模式(stub / full)│ └──────────────┬───────────────┘▼┌────────────────────────┐│ ④ 扫描代码 → 提议模块│└────────────┬───────────┘ ▼ ┌──────────────────────────┐ │ ⑤ 写 L1 / L2 / L3 文档 │ │ 写 .feat-tree.json │ └────────────┬─────────────┘▼ ┌──────────────────────────────┐ │ ⑥ 将工作流契约注入 CLAUDE.md │ │(此后规则自动传递)│ └──────────────────────────────┘
4.2 日常修改功能(无需任何命令)
User: 改 sign-in 的逻辑 │ ▼[AI 读 CLAUDE.md] ── 已注入的规则告知下一步 │ ▼[L1 index.md] ──► 看到 auth 模块 │ ▼[L2 auth/index.md] ──► 看到 sign-in feature │ ▼[L3 auth/sign-in.md] ──► 读取详情 │ ▼[AI 改代码] │ ▼[AI 同步更新 L3 并 bump last_synced_commit] │ ▼[Stop hook 自动执行 drift-check] │ ├─► 无漂移 ──► session 正常结束 └─► 有漂移 ──► warn 提醒 / block 拒绝结束
4.3 漂移发现后
Stop hook 报告漂移 │ ▼User: /geno-sync │ ▼列出五类问题: ├─ 红:明确漂移(code 改了,doc 未更新) ├─ 黄:可疑(提交中包含 "refactor" 等关键词) ├─ 灰:从未对账过(stub / 待审 full 草稿) ├─ 坏链:code 路径已不存在 └─ 陈旧 SHA:记录的 SHA 已不在 git 历史中 │ ▼用户选择从哪类开始 │ ▼逐篇:读取 diff → 修改文档 → 更新 SHA │ ▼最终报告:哪些已对齐、哪些跳过
整个系统只有两个 skill(/geno-init 和 /geno-sync)+ 两个 hook(PostToolUse 提醒、Stop 检测漂移)。没有第三个。每增加一个命令都是用户需要记忆的负担。
五、stub / full:两种初始化策略
/geno-init 在 Step 3 会询问:生成模式选择 stub 还是 full?
stub 模式(默认)
仅生成文档骨架,section body 全部为 TODO / 待补充。日常修改到哪个 feature,再实时填写该 feature 的内容。
---type: og-featurekind: uifeature: sign-inlast_synced_commit: ""last_reviewed: 2026-05-06---# Sign in## WireframeTODO## Entry pointsTODO## InteractionsTODO
适合:大型项目、希望增量推进、不想在初始化阶段消耗过多 token。
full 模式
扫描更深一层,AI 一次性将所有 L3 尽力填充完整。但关键设计:last_synced_commit: 留空。
含义是:内容已生成,但未经任何人验证。
last_synced_commit: ""# 即使全文填满,SHA 也必须为空
为什么?因为 SHA 的语义是“已被验证”,full 模式只是“已被生成”。这两件事必须区分。/geno-sync 识别到 gen_mode: "full" + 空 SHA 时,会将其视为“待审稿”而非“待写”,并给出不同的处理建议。
仓库中 examples/todo-app-full/ 提供了完整的 full 模式产物 demo。注意其中 AI 的句式:
这种谨慎措辞是刻意要求 AI 如此书写——无法确认的信息不要假装确认。AI 不确定时,不是猜测一个看似合理的值,而是直接保留 待补充 等待人工填写。
六、.feat-tree.json:项目根的运行时配置
init 完成后,在项目根目录生成以下文件:
{"version": 1,"tree_path": "feat-tree","drift_mode": "warn","gen_mode": "stub"}
四个字段各有用途:
tree_path:文档树存放目录(默认feat-tree/,可修改);drift_mode:warn或block,由 Stop hook 读取;gen_mode:stub或full,由/geno-sync读取(决定空 SHA 的语义是“待写”还是“待审”);version:schema 版本,用于未来升级。
七、规则如何传递给后续 session
这是 GDD 真正生效的关键——规则不是写在 README 中让用户记住,而是在 init 时注入到项目的 CLAUDE.md。
/geno-init 最后一步会将一段规则文本追加到 CLAUDE.md(若不存在则新建),并用 / 包裹。这段文本通知未来每一个 session 的 AI:
- 修改代码前必须遵循 L1→L2→L3 顺序读取对应文档
- 修改代码后必须同步更新 L3 并更新 SHA
- 更新 SHA 的前提是确实阅读了代码——仅编辑文档不算
Claude Code(以及其他读取 CLAUDE.md / AGENTS.md 的工具)每次启动都会读取此文件。规则一次注入、永久生效,用户无需在每个 session 中重复说明。
八、上手操作
快速安装
npx skills add web-abin/OpenGeno
手动安装
# 1. 将 skill 安装到 Claude Codegit clone git@github.com:web-abin/OpenGeno.gitcp -r OpenGeno/skills/geno-init ~/.claude/skills/cp -r OpenGeno/skills/geno-sync ~/.claude/skills/# 2. 在您的项目下运行cd your-project/geno-init
/geno-init 会与您交互三个问题(语言 / 漂移模式 / 生成模式),然后扫描代码、提议模块、确认后生成文档树。整个过程不会修改您的代码,只会创建 feat-tree/、写入 .feat-tree.json、追加 CLAUDE.md。
运行完成后,日常使用完全无需再手动调用命令——AI 将根据 CLAUDE.md 中的规则自动执行流程。
九、坦诚地说,GDD 并非银弹
需要承认几个明显的局限性:
- 首次接入需要花些时间梳理模块边界。stub 模式能减轻负担,但模块划分仍需人工确定。
- AI 偶尔会忘记更新 SHA。Stop hook 是兜底方案,但 hook 报错时仍需人工修复。
- 多人协作时漂移会更频繁。这是好事——能暴露团队成员之间的同步问题——但接入初期会有阵痛。
- 目前仅在 Claude Code 上经过验证。
AGENTS.md已准备好,但尚未在 Cursor / Aider 等工具中打磨完善。 - 不能完全取代 spec。需求评审、架构设计等“计划要做”的环节,spec 仍然更合适——GDD 仅接管“已经做了什么”这段。
完整的设计动机和取舍详见仓库的 docs/motivation.md,几个具体决策(为什么只有两个 skill、为什么使用 CLAUDE.md 注入、为什么设计三层结构)记录在 docs/decisions/。
结语
将“先写 spec”替换为“先看代码、再让文档跟着代码走”,是一个反直觉但越用越顺手的思维转向。这种感觉有点像从“写注释”切换到“写测试”——前者依赖自律,后者由机器兜底。
如果您也曾被 spec 腐烂所困扰,欢迎尝试 OpenGeno。