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

GitHub:github.com/web-abin/Op...

适合在 Claude Code / 类似 Agent 工具里使用。中文 / 英文项目都支持。


前言

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

我自己跑了几个项目下来,每次都卡在同一个地方:

spec 第一周很鲜活,第三周开始过期,第二个月只能当占位符。

这不是某个工具的 bug,是流程层面的问题:spec 描述的是"打算做",code 描述的是"实际做"。只要后者还在迭代,前者就会被甩开。AI 读了一份过期 spec 写出来的代码,比没读还危险------它会基于错的"事实"做决策。

所以我开了一个反方向的口子:GDD(Geno-Driven Development),开源仓库叫 OpenGeno。这篇文章拆解一下它的设计、和 SDD 的对比、以及一些落地后才想明白的取舍。


一、SDD 的失败模式:spec 鲜活期太短

抽象一下 SDD 的工作循环:

markdown 复制代码
1. 人写 spec
2. 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. 三层结构,按需懒加载

r 复制代码
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 长这样:

yaml 复制代码
---
type: og-feature
kind: ui
feature: sign-in
module: auth
schema: 1
code:
  - lib/features/auth/sign_in_page.dart
  - lib/features/auth/sign_in_controller.dart
  - lib/api/auth_service.dart
last_synced_commit: a1b2c3d
last_reviewed: 2026-05-06
---

两个关键字段:

  • code: 这个 feature 依赖哪些代码文件;
  • last_synced_commit: 这份文档上次被人/AI 真正对照代码核对过的 git SHA。

SHA 的语义不是"被编辑的时间",是"被验证的时间"。这个区分很重要:单纯改了文档不算对账,必须读了代码、确认一致才能 bump。

3. 漂移检测:把"忘记更新"变成机器能发现的事

有了 code:last_synced_commit:,漂移检测就是一个简单的脚本:

bash 复制代码
# 伪码
for doc in feat-tree/**/*.md:
    last_sha = doc.frontmatter.last_synced_commit
    for 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 一次性初始化

text 复制代码
┌─────────────────────────────────────────────────┐
│  User: /geno-init                               │
└────────────────────┬────────────────────────────┘
                     │
                     ▼
        ┌────────────────────────┐
        │ ① 选语言(中文 / 英文)│
        └────────────┬───────────┘
                     ▼
     ┌──────────────────────────────┐
     │ ② 选漂移模式(warn / block) │
     └──────────────┬───────────────┘
                    ▼
     ┌──────────────────────────────┐
     │ ③ 选生成模式(stub / full)  │
     └──────────────┬───────────────┘
                    ▼
        ┌────────────────────────┐
        │ ④ 扫描代码 → 提议模块  │
        └────────────┬───────────┘
                     ▼
       ┌──────────────────────────┐
       │ ⑤ 写 L1 / L2 / L3 文档   │
       │   写 .feat-tree.json     │
       └────────────┬─────────────┘
                    ▼
     ┌──────────────────────────────┐
     │ ⑥ 把工作流契约注入 CLAUDE.md │
     │    (此后规则自动传递)      │
     └──────────────────────────────┘

4.2 日常改功能(不需要任何命令)

text 复制代码
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 漂移发现后

text 复制代码
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 再现写哪个的内容。

yaml 复制代码
---
type: og-feature
kind: ui
feature: sign-in
last_synced_commit: ""
last_reviewed: 2026-05-06
---

# Sign in

## Wireframe
TODO

## Entry points
TODO

## Interactions
TODO

适合:大项目、想增量推进、不希望初始化阶段花太多 token。

full 模式

扫描更深一层,AI 一次性把所有 L3 都尽力写出来。但关键设计last_synced_commit: 留空。

意思是:内容有了,但没人验证过

yaml 复制代码
last_synced_commit: ""    # 哪怕全文都填了,SHA 也必须是空的

为什么?因为 SHA 的语义是"被验证过",full 模式只是"被生成过"。这两件事必须区分开。/geno-sync 看到 gen_mode: "full" + 空 SHA,会知道这是"待审稿"而不是"待写",给出不同的处理建议。

仓库里 examples/todo-app-full/ 有完整的 full 模式产物 demo。注意里面 AI 写的句式:

"Validates on blur. Regex used: ^[^@]+@[^@]+\.[^@]+$ (literal from sign_in_controller.dart)"

"Remember-me checkbox --- visible in the build method, exact placement uncertain --- confirm during review"

这种 hedged tone 是故意要求 AI 这么写的------没法确认的事就不要假装确认 。AI 拿不准的时候,不是猜一个看起来合理的值,而是直接留 待补充 让人来填。


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

init 完成会在项目根写一个:

json 复制代码
{
  "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 重复说。


八、上手

快速安装

csharp 复制代码
npx skills add web-abin/OpenGeno

手动安装

bash 复制代码
# 1. 把 skill 装到 Claude Code
git clone git@github.com:web-abin/OpenGeno.git
cp -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。

  • GitHubgithub.com/web-abin/Op... --- 觉得有用请点个 Star🌟🌟🌟
  • Issue / PR / 讨论:都欢迎,尤其是漂移规则那块还在打磨
  • 三连 :如果文章对你有启发,点赞 / 收藏 / 关注 是最大的支持

下次写一篇细的,专门聊"为什么 SHA 而不是 mtime"和"为什么三层而不是两层或四层"------这两个决策当时纠结了挺久。

相关推荐
海上日出1 小时前
这本"Transformer书",我读了3遍才敢写评测:从"会调用"到"懂原理"的思维跃迁
ai编程
木昆子1 小时前
基于LangChain DeepAgents的Skills应用实践
ai编程
三秋树1 小时前
豆包 Agent Harness 工程师入门 | 第 8 章 后台任务
ai编程
Hello-Mr.Wang2 小时前
【保姆级教程】MasterGo MCP + Cursor 一键实现 UI 设计稿还原
前端·javascript·vue.js·ai编程
Peter·Pan爱编程2 小时前
第四篇:Cursor 深度评测 —— Composer 模式下的全栈 vibe 体验
人工智能·ai编程·composer
好运的阿财2 小时前
OpenClaw工具拆解之memory_search+memory_get
人工智能·python·ai编程·openclaw·openclaw工具
奔跑吧树袋熊3 小时前
Opus 4.7 + GPT-5.5“双核驱动”——2026最强AI编程工作流实测
gpt·ai编程
_Evan_Yao3 小时前
一文搞懂:AI编程辅助工具——从GitHub Copilot到通义灵码,不同人群如何驾驭AI编程助手?
人工智能·后端·copilot·ai编程