首页
看点啥
插画图片
首页 看点啥 OpenGeno开源库:Spec 总在腐烂?我用一棵树 + 一个 hook 治好了它

OpenGeno开源库:Spec 总在腐烂?我用一棵树 + 一个 hook 治好了它

2026-07-29 0

前言

最近关于 SDD(Spec-Driven Development)的讨论很多。OpenSpec、Kiro 等工具采用的核心思路,都是先准备一份 spec,再由 AI 根据 spec 完成实现。

我亲自实践了几个项目,却发现每一次最终都会卡在同一个问题上:

问题并不属于某个工具的 bug,而是来自流程本身:spec 记录的是“准备做什么”,code 体现的是“实际做了什么”。只要 code 持续迭代,spec 就会逐渐落后。AI 根据过期 spec 生成代码甚至比完全不读更危险,因为它会把错误的“事实”作为决策依据。

因此,我尝试打开一条相反的路径:GDD(Geno-Driven Development),对应的开源仓库是 OpenGeno。本文将拆解其设计,比较它与 SDD 的差异,并说明实际应用后才理清的一些取舍。

一、SDD 的失效方式:spec 的鲜活期过短

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

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

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

更棘手的是,spec 通常是一整块文档。一份 200 行的 spec 即使只调整 5 行,diff 看起来变化不大,语义却可能已经完全偏离;而 AI 阅读时并不知道它已经失真。

二、GDD 的核心赌注:以 code 为事实来源,让文档成为可验证索引

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

具体实现只包含三件事:

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. 每个 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---

两个关键字段:

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——每次会话结束自动跑。两种模式:

由此,软约束被提升为硬约束。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 读错漂移能够被检测,最差结果也只是收到一次提醒
介入复杂度编写 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 → 改文档 → 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"}

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

七、规则如何传递至后续 session

这正是 GDD 真正能够 work 的关键:规则不是放进 README 等用户记住,而是在 init 阶段注入项目的 CLAUDE.md

/geno-init 最后一步会将一段规则文字追加至 CLAUDE.md,若文件不存在则新建,并使用 / 包裹起来。这段内容会告诉以后每个 session 中的 AI:

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。

下一篇准备详细讨论“为什么选择 SHA 而非 mtime”,以及“为什么使用三层,而不是两层或四层”。这两个决策当时考虑了很长时间。

喜欢(0)

上一篇

一天一个开源项目(第64篇):OpenCLI - 用统一 CLI 驱动任意网站、Electron 应用与本地工具

一天一个开源项目(第64篇):OpenCLI - 用统一 CLI 驱动任意网站、Electron 应用与本地工具

下一篇

AI时代,程序员保持竞争力的核心学习路径

AI时代,程序员保持竞争力的核心学习路径
猜你喜欢