OpenGeno开源库:Spec 持续腐烂?我靠一棵树 + 一个 hook 治好了它

作者:袖梨 2026-07-29

前言

最近 SDD(Spec-Driven Development)讨论很多,OpenSpec、Kiro 这一派工具的核心思路是:先写一份 spec,AI 按 spec 实现。

我连续实践几个项目后,发现每次都会卡在同一个环节:

问题并不属于某个工具的 bug,而是来自流程本身:spec 记录的是“计划如何做”,code 呈现的是“最终实际做了什么”。只要 code 持续迭代,spec 就可能落后。让 AI 读取过期 spec 后再编写代码,甚至比完全不读更危险,因为它会依据错误的“事实”作出判断。

因此,我尝试从相反方向打开突破口,提出 GDD(Geno-Driven Development),对应的开源仓库是 OpenGeno。本文将拆解其设计逻辑、与 SDD 的差异,以及真正落地后才理解的一些取舍。

一、spec 为何迅速失鲜:SDD 的失败模式

可以把 SDD 的工作循环抽象为:

1. 人写 spec2. AI 读 spec → 写代码3. (代码合入)4. 下次任务 → AI 再读 spec → 写代码

第 3 步和第 4 步之间,没有任何机制保证 spec 还和代码一致。维护 spec 这件事被默默推给了"AI 应该顺手做"或"reviewer 应该提醒"。这两个都是软约束,软约束扛不住时间。

更麻烦的是,spec 通常是一整块文档。一份 200 行的 spec 即使只修改 5 行,diff 看起来变化很小,语义却可能已经彻底失真,而 AI 读取时无法识别这种问题。

二、GDD 押注的方向:真理之源交给 code,文档改做可验证索引

GDD 建立在一个简单的反向假设上:

  • code 是唯一会被强制保持一致的事实,因为 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,再读那一篇。改 50 个 feature 的项目和改 5 个 feature 的项目,单次任务读的 token 量几乎一样。

2. 一个完成"已对账"的 SHA 随每个 L3 保存

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 表达的并非“最后编辑时间”,而是“最后验证时间”。这一差异非常关键:仅修改文档不能视为完成对账,只有阅读代码并确认二者一致后才可以 bump。

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 先于 codecode 已经存在,由文档跟随变化
文档形态单份 spec / 大块 markdownL1 / L2 / L3 三层树
AI 加载方式一次读取完整 spec沿 L1→L2→L3 按需加载
维护机制依靠人或 AI 主动保持同步last_synced_commit + Stop hook 强制对账
适用阶段更偏向新项目适合任意阶段,包括遗留代码
失败模式spec 腐烂、AI 读错漂移能够被检测,最差情况也会收到一次提醒
介入复杂度预先编写 specinit 仅执行一次,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 → 改文档 → bump SHA │ ▼最终报告:哪些已对齐、哪些跳过

整个系统就两个 skill(/geno-init/geno-sync)+ 两个 hook(PostToolUse 提醒、Stop 检漂移)。没有第三个。每多一个命令都是用户要记的事。

五、stub / full:两种初始化策略

/geno-init 生成模式的选择问题,会在 Step 3 出现: stub 还是 full

stub 模式(默认)

该模式只创建文档骨架,所有 section body 都是 TODO / 待补充。日常修改到哪个 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 采用的句式值得注意:

这种 hedged tone 是对 AI 的刻意要求:无法确定的信息就不应伪装成已经确认。AI 没有把握时,不要猜测一个表面合理的值,而应直接保留 待补充 并交由人工填写。

六、.feat-tree.json:项目根目录中的运行时配置

完成 init 后,会在项目根目录写入一个:

{"version": 1,"tree_path": "feat-tree","drift_mode": "warn","gen_mode": "stub"}

其中四个字段分别承担不同作用:

  • tree_path:指定树所在的目录(默认为 feat-tree/,允许修改);
  • drift_modewarn 还是 block,供 Stop hook 读取;
  • gen_modestub 还是 full,由 /geno-sync 读取,用来判定空 SHA 代表“待写”还是“待审”;
  • version:用于未来升级的 schema 版本。

七、后续 session 从哪里获得规则

GDD 真正可以 work,关键在于规则并非放进 README 等用户记住,而会在 init 阶段写入项目的 CLAUDE.md

/geno-init 最后一步会把一段规则文字追加至 CLAUDE.md;如果文件不存在就新建,并使用 <!-- BEGIN OpenGeno --> / <!-- END OpenGeno --> 外面包裹起来。未来每个 session 的 AI 都会从这段文字获知:

  • 修改代码前,应按照 L1→L2→L3 的顺序阅读相应文档
  • 同步更新 L3、bump SHA,都是改完代码后的必要动作
  • 只有真正阅读过代码才能 bump SHA,单独编辑文档不符合条件

Claude Code(包括其他读 CLAUDE.md / AGENTS.md 的工具)每次启动时都会读取该文件。因此规则只需注入一次便会持续生效,用户不必在每个 session 中反复说明。

八、上手

快速安装

npx skills add web-abin/OpenGeno

手动安装

# 1. 把 skill 装到 Claude Codegit clone [email protected]: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 并非银弹

我承认它仍存在几个明显限制:

  1. 首次接入需要投入时间梳理模块。stub 模式虽然能降低工作量,但模块边界依旧要由人工确定。
  2. AI 有时仍会忘记 bump SHA。Stop hook 可以作为兜底机制,但 hook 报错后依然需要人工处理。
  3. 多人共同协作时,漂移会更加频繁。这有利于暴露团队成员之间的同步问题,但在刚接入的阶段也会带来阵痛。
  4. 验证工作目前仅覆盖 Claude Code。虽然 AGENTS.md 已准备完毕,但 Cursor / Aider 等工具中的打磨还未开展。
  5. 它无法彻底取代 spec。在需求评审和架构设计等描述“计划做什么”的环节,spec 依然更合适;GDD 只负责“已经完成了什么”的部分。

完整的设计动机和权衡记录在仓库的 docs/motivation.md,而几个具体决策,包括为何只有两个 skill、为何通过 CLAUDE.md 注入以及为何采用三层结构,则写在 docs/decisions/

结语

把“先写 spec”调整为“先阅读代码,再让文档跟随代码”,虽然违背直觉,实际使用后却越来越顺手。这有点像从“编写注释”转向“编写测试”:前者依靠自觉维护,后者则有机器负责兜底。

如果你也一直受 spec 腐烂困扰,可以尝试使用 OpenGeno。

  • 觉得有用请点个 Star??? — GitHub:github.com/web-abin/Op…
  • 欢迎提交 Issue / PR 或参与讨论,尤其是仍在完善的漂移规则部分
  • 三连:点赞 / 收藏 / 关注,就是对有启发内容最有力的支持

下一篇将详细讨论“为什么选择 SHA 而不是 mtime”,以及“为何采用三层而非两层或四层”。当时在这两个决策上确实权衡了很久。

相关文章

精彩推荐