总的来说 spec-kit 没有解决 Vibe Coding 中旧的问题, 还让旧问题翻倍出现. 浪费我 34M Token 后, 我就删掉了它拉的屎, 但接下来越想越气, 遂写了这篇小评. 由于代码和 spec 已经删除, 有些细节可能不准确. 前面说了很多废话, 嫌长可以直接跳到 为什么 spec-kit 失败

前言

我很早就开始用 AI 辅助编程了, 比 GPT 更早, 当时是 Kite 和 Tabnine 的时代, 随后的 Github Copilot 我也是最早的一波内测用户, 再然后则是 Cursor. 但我对 AI 的使用长期限于补全我不想写的重复代码, 比如操作坐标时, 当我写完对 x 的操作时, 让 AI 帮我补出来 y 的, 或者写下函数名后, AI 根据当前块里的变量和函数要求的参数自动填入. cursor 的补全是真的漂亮, 但 Agent/Composer 功能我基本不用, 大部分时候写出来的都是垃圾, 这些 AI 总是忍不住写大量的 try、兼容层、回退或者重新实现某些逻辑, 在某个版本之后还总是忍不住写大量的”文档”.

直到前段时间看了大量 GPT5-codex 软文, 试了一下十分惊艳, 指哪打哪, 不废话. 这里面最重要的就是听话, 只要在 AGENTS.md 里写一二百字的要求, 它就照做. 比如用 uv 而不是 pip, 多用 3.12+ 的 Type Hint, 少用 try 等等.

接下来我甚至用它实现了我拖了好久的一个念头(Prisma 的精神续作), 虽然偶尔代码质量不佳, 但我总是能纠回来, 在纠正的过程中, 自然地发现了区分DESIDN.mdTARGET.md的用法──DESIDN写架构细节设计, TARGET写总的待实现目标和分条TODO, 我更新这两个文件后就叫 AI 再看一遍.

这个经历让我对 spec-driven 开发有了一些初始好感, spec-kit 也正是在我实现前面那个念头的时候火起来的, 这两天刚好有空、有需求还有 GPT 5.2 刚发布, 于是开干.

然而 spec-kit 似乎把 GPT5.2 Codex 灌成了傻子.

需求

我的需求很简单, 一个 Web 应用, 后端订阅若干 Binance API Key, 前端将挂单、成交、仓位合在一起看, 展示合并的收益, 允许平仓或取消挂单, 能有个页面录入 Key.

看 spec-kit 给 GPT 灌迷魂汤

按照 spec-kit 的流程, 要先确立原则(speckit.constitution), 然后创建规范(speckit.specify), 规范是指要做什么, 为什么要做. 这个时候幻觉就开始出现了, 它会脑补我没提到的需求, 或者即便我说了要A, 它也要脑补成A+, 在若干次澄清后虽然我的初始需求好了, 但是它也加了一堆没用的需求, 我想要是真能实现的话, 多点功能也没关系, 于是继续.

其实我看到原则和规范的引导词比我之前手写的设计和目标都要长好几倍的时候, 我心里就有点不详的预感了

接下来是计划(speckit.plan), 即技术栈和架构选择, 这部分开始出现需求稀释, 我明确要求某些数据文件定义参考另一个项目的某个文件, GPT 在按照 spec-kit 前摇了很久干了很多文书工作, 然后忽略了这个要求, 之前玩半自动挡的时候我确信不会出现这样的问题. 在澄清后, 它不再按照 spec-kit 的要求工作, 然后改掉了相关文件.

然后是分解任务(speckit.task), 这部分一般般, 有明显的任务重量不均.

然后终于来到了实施(speckit.implement), 我只能说 GPT 似乎被 spec-kit 灌了太多文书内容灌傻了, 幻觉极大加强, 细节经常丢失. 要求数据库使用 ORM, 它不用, 偏要手写 sqlite 连接然后拼 sql, 然后给所有数据接口加了个参数db_file: str, 每次查数据都重新打开数据库然后关闭. 要求用 litestar, 明确告诉它不懂的地方可以查 context7, 然后遇到了警告时, 坚决不查, 非常自信地把警告关掉了. 而我在指定原则的时候已经明确表示不要关掉警告.

前面创建规范时我澄清掉的内容它也没有删, 最终在实现的时候又拉了个大的──把它首次幻觉认为的需求实现了, 实现完我继续澄清, 于是它实现了第二条路径, 又加了个兼容层, 它的目的是当原来的路径出错时切换到我澄清后的路径上, 然而前端对应部分的逻辑压根没动, 前端在幻觉版本, 后端在兼容版本. 嘿, 原则部分我第一句写的就是这是个只有一个人用的项目, 不要兼容过去, 切换写法时要毫不留情地 break, 用不到的代码就删.

然后跟它搏斗了许久, 它终于把前后端同步了, 不用的代码都删掉, 与此同时 spec 里也多出了一堆 不要这个、不要那个, 如果我不手动删的话, 想来它第一回的幻觉会永远存在于这个项目吧. 可是, 明明只需要写要A就够了, 但 GPT 就是在 spec-kit 的要求下, 先写要 B, 然后写 B 中不要甲、乙、丙、丁, 最终通过对整个世界做补集得到 A. 颇有种函数式编程里为了修改一个字段而复制一个世界的感觉. 滑稽.

随后我突然发现之前有一个需求它没加到spec里去, 于是我要求它再加一个需求, 重走了规范、计划后就出 bug 了, spec-kit 要求一类功能有一个数字的前缀和 git 分支, 比如 spec/001-binance-multi-key, spec/002-key-form, 但是spec-kit里的脚本似乎有问题, 允许创建相同前缀的spec(比如两个spec/001), 但后续各种流程都会报错.

虽然脚本文件会报错, 但是 GPT 还是坚持下去了, 手工修改文件又往后走了几步后, 各种 spec-kit 带的 check 都不通过了. 我知道把前缀改改大概率应该能解决眼前问题. 但我想我应该学会放手.

我用半自动挡怎么做

实际上, 全局的AGENTS.md相当于上面的speckit.constitution, 项目里再加的AGENTS.md表示局域的 constitution, 随后是需求和规范描述, 放在TARGET.md的头部, 接下来设计架构和指定技术栈, 对应speckit.specify, 一切都指定好后, 我会在 TARGET.md 下面开始写 TODO, 让 Agent 读完相关文件后一条条开干.

为什么 spec-kit 失败

spec-kit 过度设计又设计缺失.

过度设计在于 spec-kit 写了太多繁复的细节, 太长的自然语言细节要求压制了 GPT 的智商, GPT 没办法走它最舒适的路径, 它每一步都得小心翼翼, 在注意力上与 spec-kit 的提示词产生呼应, 写代码的时候随时可能跳出代码流程开始参考规范. 但是, 很多良好的软件工程设计实际上是早已内化到 GPT 权重中的, 将其拿出来反而占用上下文和注意力.

另外, 大量的引导词看起来把 LLM 当成了确定性的编译器, 而不是只关注开头和结尾的概率模型, 仿佛只要写要求它就能遵守一样. 给定一长串文本, 实际上现在的 LLM 总是会更关注头尾的部分, 而更容易忽视中间那部分. spec-kit 里如此之多的**MUST** 在上下文超过64k后, 还有多少能生效?

设计缺失表现在 spec-kit 没有让项目 break 的机制, 澄清或修改需求的时候, 它只会追加而不是替换. 这迫使开发者在早期给出清晰、简短又不多不少的描述(像不像一份非常重要的随机数种子?), 虽然按照文档, 你可以先模糊地聊聊后面再迭代, 甚至它的提示词还花了很大一部分要求 LLM 避免过早实现细节, 还要强制标注不确定. 然而这正是问题所在.

LLM 否定与肯定的不对称

就像那个「不要想粉红色的大象」测试一样, 现在的 AI 不太好处理否定, 最好的否定是不出现, 明显的例子就是当你让 AI 「画一个卧室, 地面上有许多蓝色和红色球在一起, 但屋子里不要出现任何黄色的东西」, 球可能颜色对了, 但黄色的东西会频繁出现在任何地方, 比如开着的台灯、木桌柜、泰迪熊、木地板. 但如果你写「画一个卧室, 地面上有许多蓝色和红色球在一起, 屋子主体是白色调的, 只出现白色」, 它就可以正常画出来没有黄色的卧室, 台灯会是关的, 地板是白色调的或者干脆被球铺满.

LLM 也差不多是这样, 当你只告诉他「不要甲」的时候, 这个否定很容易失效, 反而让 LLM 在昏了头的时候更倾向于出现甲, 上策是「只要乙丙丁」, 但很多事情不是能只通过正向描述指出来的, 所以中策是「不要甲, 可以用乙丙丁替代」.

而通过 spec-kit 生成的 spec 里存在大量的否定表述, 这些否定表述在上下文加长时, 很容易幻化为正向的东西.

需求是实在界的洞

用户的真实需求是一个有复杂形状的洞, 洞的主体是正向的需求, 边界则由约束确定. 但用户只能用有损压缩的语言在有限的长度内表述(毕竟完整表达就等于写代码了), 因而表达出的洞只是一个形状简单的洞. 良好的软件工程师可以通过有限的语言尽可能反推需求主体和约束, 并基于尽量少的假设开发出严丝合缝的盖子, 不大不小.

未能盖住所有洞的实现明显是不好的, 但比洞更大的盖反而有更大危害, 因为

  1. 洞的边界是软件不该处理的部分, 就像从不同平台订了同一间房的两个客人, 此时酒店的系统不该为此添加兼容层, 而是应该老老实实抛到前台处理. (哎呀, 这里主键冲突了, 让我们添加异常检查并随机指定一个主键)
  2. 来了一个盖后, 用户的需求就会变化, 越大的盖意味着需求变化后越多的重构, 这实际上是技术债的一部分.

LLM 发展与 spec-kit 的冲突

需求包含很多约束. 于是 spec-kit 在生成的 spec 和 principles 里和添加了大量约束, 但 LLM 又不能处理太多约束, 它按照天性输出的才是它能力的上限, 太多约束只会让动作变形.

如果有一天 LLM 可以正确接收一大堆约束而正确实现, 那可能意味着实际上不加约束时, 它的天性就能够给出合适的实现. 那么那个时候还需要复杂的 spec-kit 机制吗? 会不会只要只要简单写一两个文件就够了呢? 甚至这一两个文件还是模型内化来直接生成的?

spec-kit 跟小学阶段算术里的验算很像, 小学生加强算术能力的方法不是加强验算, 而是将计算内化. LLM 在无法有全局观地处理项目时, 大概率也没办法通过读全局观文档来变好, 反而完善的全局观文档带来了太大压力.

所以, LLM 越发展, 就越不需要当下臃肿的 spec-kit, 但 spec-driven 开发总归是没错的.