Spec腐烂解决方案:OpenGeno树+Hook开源库

2026-06-20阅读 0热度 0
开源

引言

最近 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 先于 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 → 修改文档 → 更新 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_modewarnblock,由 Stop hook 读取;
  • gen_modestubfull,由 /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 并非银弹

需要承认几个明显的局限性:

  1. 首次接入需要花些时间梳理模块边界。stub 模式能减轻负担,但模块划分仍需人工确定。
  2. AI 偶尔会忘记更新 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。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策