前言
近期,Spec-Driven Development(SDD)在开发社区引发了大量讨论。以 OpenSpec、Kiro 为代表的工具核心思路是:先编写一份详尽的规格说明(Spec),再由 AI 依据该规格实现代码。
但实际在多个项目中落地后,我发现每次都会卡在同一个环节:
这并非某个工具的 Bug,而是流程层面的根本问题——Spec 描述的是“打算做什么”,而 Code 描述的是“实际做了什么”。只要代码持续迭代,Spec 就会逐渐脱节。如果 AI 读取了一份过期的 Spec 来生成代码,其结果比不读 Spec 更危险,因为 AI 会基于错误的“事实”做出决策。
因此,我尝试了一个反向思路:Geno-Driven Development(GDD),开源仓库名为 OpenGeno。本文将拆解 GDD 的设计理念、与 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 的反向假设非常简单:
- 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 个,单次任务所消耗的 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 是前置任务 | 一次初始化,之后规则通过 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 在第 3 步会询问:生成模式选择 stub 还是 full?
stub 模式(默认)
仅生成文档骨架,各部分内容全部填充为 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 模式产物示例。注意其中 AI 所写的句式:
这种带有保留语气的表述(hedged tone)是刻意要求 AI 这样写的——无法确认的事项就不要假装确认。当 AI 不确定时,不是猜测一个看似合理的值,而是直接留出 待补充 让人工来填写。
六、.feat-tree.json:项目根的运行时配置
初始化完成后,会在项目根目录生成如下配置文件:
{"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 里让用户记忆,而是在初始化时直接注入到项目的 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。
