11. 规范驱动编程:当需求变成工程产物
周一上午,你在 Cursor 里和 AI 说,给这个 HTTP 客户端加上超时重试。
它第一轮给你写了个固定 3 次重试,每次间隔 1 秒。你看完说不对,要指数退避,第一次 1 秒,第二次 2 秒,第三次 4 秒。它改完之后你又看了眼,说总等待时间不能超过 30 秒,过了就直接失败。它再改。改到第三轮你想起来这个接口是非幂等的,必须加个请求 ID 去重,又让它补上。第五轮你说要打点指标,把每次重试的次数和最终结果埋进去。第六轮看完代码差不多了,合并进主干。
三个月后,另一个同事接手这块代码。他看到的是合并后的完整代码:指数退避、30 秒上限、请求 ID 去重、指标埋点,齐齐整整排列在一起。他看不到的是你那六轮里反复推翻、反复纠正的过程:为什么是指数退避不是固定退避、为什么上限是 30 秒不是 60 秒、为什么要加幂等、那几个指标字段是怎么选的,这些为什么的讨论全部蒸发了。或者更糟的情况:接手的不是同事,是另一个 AI 会话。它看到的也是这段代码,那六轮讨论它同样看不到。下次再有一个相邻的接口要做同样的事,它读完这段代码,会不会按同样的方式写一遍?大概率不会。它会照着自己读到的那部分代码风格,做出一次它觉得合理、但未必和你原意一致的选择。
这看起来很像忘了写文档。可传统软件开发里也没人录音评审里的口头讨论,那些为什么也只在评审者和提交者之间通过对话讨论。过去这样没事,怎么换成和 AI 协作就有事了?
11.1 那段对话去哪了
先把上面这段经验摆得再具体一点。
那六轮对话本身,是没人会单独保存的东西。你不会把它贴到 wiki,也不会把它附在 PR 上。你会做的,是把磨出来的最终结果合进代码,然后关掉那个会话。会话一关,过程也就没了。
它不是真的丢了:代码还在,效果还在,跑起来一切正常。丢的只是那些为什么的讨论过程。你看着这段代码,会觉得它本来就该写成这样,因为它现在的逻辑是合理的。但它能设计的挺合理,是因为你在那六轮里把不合理的选项一个个剔掉了。剔掉的过程没了,剩下的就是一个看起来天然合理的最终态。
传统软件开发里有没有这个过程?有。只是发生的位置不一样。两个人在 PR 评论区你来我往地讨论,最后达成共识,PR 合并,那些讨论就留在了 GitHub/GitLab 的评论区里。它没进代码,但它有一个和那次代码变更绑在一起的存档位置。三个月后想知道当时为什么这么决定,点进那个 PR 还能翻到。
和 AI 协作时现在没有这个存档位置。对话窗口是会话级的临时容器,关掉就没了;代码仓库里只有合并后的最终态;评论区里只有合并这个动作本身的评审,没有那六轮和 AI 讨论的过程。
那能不能事后补一份记录?理论上能。你写完代码,自己再花五分钟把当时为什么这么做打成一段说明,附在 PR 上,或者让AI总结一个摘要。可只要认真试过几次就会发现,这件事在实际工作里几乎跑不起来。事后补和当下记不是一回事,你写完代码已经累了,注意力全在最终代码上,回头总结时记得最清楚的可能只是最近那一两轮对话,前几轮的曲折早就模糊了。这份事后补的说明和代码也没有结构性的绑定关系,它只是 PR 描述里的一段自然语言,下次 AI 来读这段代码时不会去读它。更要命的是,团队里没有人会真的为这件事建立纪律,大家默认代码评审通过了就行。
所以这里的问题是:那六轮的讨论过程,确实蒸发了;事后补的这条路,在工程落地时也很难跑通。但传统开发里也有大量没记录过程的执行结果,以前为什么就没这个问题呢?
11.2 传统开发里,谁在补齐那些没说出口的东西
回头看传统的开发协作。
一个产品经理写一份需求文档扔给开发,文档里写着用户登录后展示个性化推荐。这句话能直接落地吗?根本不能。它没说推荐什么内容、推荐多少条、推荐失败怎么办、用户没有历史数据时显示什么、缓存策略是什么、加载状态怎么处理。这些全都没写。
但开发拿到这份文档,并不会立刻去追着产品问。他会自己补:根据这个产品过去的风格、团队的代码规范、公司在同类场景下的惯例、自己工作五年八年攒下来的工程经验。个性化推荐这句话在他脑子里会自然翻译成一组具体的实现选择:从哪个服务取数据、用什么缓存、加什么降级、错误码怎么定、日志加在哪、几个关键指标埋在哪。这些选择他可能会写文档,也可能直接写进代码里。
模糊的需求和确定的代码之间,隔着的就是这个补齐动作。开发的脑子是个隐形的翻译层,把模糊翻译成确定,翻译的依据是项目背景、工程经验、团队默契、对业务的理解。这层翻译做得越好,需求文档可以写得越粗。一份只有标题的需求扔给一个资深的内部开发,他能把它打磨成一个能上线的东西;同一份需求扔给一个刚入职的新人,可能要往返五六轮才能勉强可用。
所以传统软件协作里有一个隐含的事实:需求的精度,不是靠文档写出来的,是开发用经验补出来的。文档可以模糊,代码依然能确定下来,因为模糊度被开发的脑子吸收了。现在把代码开发这一环换成 AI。
AI 没有项目的历史背景,它不知道半年前那次重构留下了哪些约定。它没有工程经验的厚度,它见过一万种超时重试的写法,但不知道你这个项目里哪一种算合格。它没有团队默契,它不知道你们错误码是几位、不知道日志习惯加在哪一层、不知道你们已经决定不再引入新的依赖。最关键的是,它没有职业判断这件事,同一份模糊需求让它生成十次,能生成十种合理但不一致的东西。今天它选了 A,明天可能选 B,下次有人改的时候又被改成 C。这种补齐是不稳定的。
这不是 AI 不够聪明,它的语言能力、代码能力、推理能力都已经比一个刚入职的新人强很多。它欠缺的是在这个项目里工作了一年的那个人那种背景知识,这种知识没法靠它自己积累出来,因为它每次会话都是从零开始的。
所以过去能容忍的需求模糊度,现在不能容忍了。从模糊的需求到精确的代码,必须有个位置来处理这个转换。要么将模糊遗留到代码里,让代码本身长得歪歪扭扭;要么让需求不再模糊,让需求自己变得精确。
那是不是我把 PRD 写得更细就行了?
11.3 PRD 不够用,但不是因为它不够好
PRD 不是没有努力过。它在过去十几年里被各家公司打磨过、模板化过、流程化过。一份成熟的 PRD 能写得很厚:用户故事、验收标准、状态机图、接口契约、错误场景,能写的都写了。
但把这种 PRD 直接拿来喂给 AI,仍然不够用。原因不在 PRD 写得不够细,在 PRD 这个东西背后的四个底层假设全部是为人作为接收方准备的,搬到和 AI 协作这边有些不适用。
第一个假设是 PRD 写完归档。一份 PRD 的生命周期是写出来、评审、确认、归档,之后开发自己看着办。它不会在写代码的那一刻被读取,因为开发已经把它在脑子里消化过了。但 AI 没有消化这个动作,它写代码的每一刻都在从眼前的上下文里读信息。一份 PRD 如果不在它当下能看到的位置上,对它就等于不存在。归档这个动作,对和 AI 协作来说是直接断电。
第二个假设是 PRD 的颗粒度是一个功能。一份典型的 PRD 描述的是做一个个性化推荐模块或做一个用户认证流程,是一个完整的产品功能。而和 AI 协作的实际单元是一次变更:加一个字段、改一段逻辑、补一个边界条件。这两个颗粒度差了一个数量级。你不会为了给某个函数加个超时参数专门写一份 PRD,但这种变更恰恰是 AI 最常做的事。PRD 顾不到这一层,下面这一层就被裸露在外面,只能靠对话来弥补,也就是回到了开头那个"蒸发"的现场。
第三个假设是 PRD 不参与评审。PRD 改了不需要走 PR,没有 PRD 的改动记录这种东西。它是产品自己维护的,开发只是消费方。但在和 AI 协作的语境里,需求和代码之间的耦合紧到不能这么处理:需求里改一个段话,AI 生成的代码可能改一片。这种需求层的改动如果不能像代码一样被显式评审,它的实际影响就是隐形的:你只看到代码 PR,看不到那个引发代码变化的需求变化。一份不进评审流的需求文档,在和 AI 协作时就是一份永远在背后悄悄漂移的东西。
第四个假设是 PRD 描述做什么,不描述做完了是什么样。一份 PRD 会写用户应该可以登录,但它不会写登录这件事在什么状态下算做完了:表单提交不算、收到 token 不算、token 写入存储且能在下一个请求里读出来才算。做什么对人是够的,因为人会自己补充细节,他知道写完一个登录接口意味着什么。但 AI 不补,AI 会在某个看起来差不多的位置停下来。你不显式告诉它什么叫做完了,它的做完了和你的做完了就不在同一个概念上。
四个假设串起来看,就会发现:PRD 不是一个写得好不好的问题,是一个为谁服务的问题。它是为人这个接收方准备的,假设接收方有职业判断、有项目背景、有团队默契、能自己补齐信息。这些假设对人都成立,对 AI 一个都不成立。
那如果我不用 PRD,我自己写一份格式新一点的文档,把这四个缺口都补上。我把它放在仓库根目录让 AI 能看到、我把它拆成一次次变更的粒度、我让它进评审流、我在里面显式写完细节,是不是就可以了?
11.4 你不是在写文档,你是在和一个概率云签合同
上一节那个补四个缺口的思路里,藏着一个致命的假设:你以为协作对象还是人,只是换了文档的格式。
但协作对象不是人。它是一台按你给的约束多寡来挑词的机器。
第一章和第二章已经讲过它怎么工作。它每写出一个词,背后都是从一大堆候选里抽一次。你给的约束够多,候选空间就被压得很窄,它写起来就像个心里有数的工程师,稳定、可靠;你给的约束不够,候选空间非常大,它就只能在听起来都合理的几个里挑一个,挑的过程是概率性的,今天挑 A,明天就可能挑 B。
它在自己熟悉的那部分(语法、常见 API、通用模式)上非常稳定。它不稳定的地方,是项目内部的合格标准:这个项目里超时重试算合格的样子、错误日志该长什么样、什么算引入新依赖,这些标准它没有内置,必须由你提供。你不提供,它就只能在多个看起来合理的候选里盲猜。
这个区别带来的影响,不是文档写细一点就够了。是规范本身在结构上要设计得不一样。
首先,必须写完成标准。
不是因为 AI 没有职业判断,是做完了这件事在没有显式定义的时候,本身就是一个开放概念。你说加超时重试,做完了可以是代码能编译,可以是单元测试过了,可以是集成测试过了,也可以是指标埋点都接好了。这几个位置都听起来合理,模型只能朝其中一个方向走。把完成标准写出来,意味着把这个开放概念关上:做完意味着代码合并、单测覆盖、错误日志结构化、三个关键指标埋点就位,每一项都可对照。模型采样到我做完了这个判断时,手边有一份明确的对照表,而不是一个模糊的方向感。
其次,必须可验证。
不是因为 AI 没有自我验收能力,是不可勾选的标准在这台挑词机器里无法稳定收敛。代码要写得整洁,这句话每一次被读进去,激活的都是不同的训练样本,整洁在不同样本里指向不同的具体特征。今天它收敛到短函数、少嵌套,明天可能收敛到详尽注释、长命名。它不是反复无常,是这句话本身就在多个解释之间游荡。可验证的标准把游荡空间关上:函数体不超过 40 行、错误返回必须包装上下文,每一条都是闭合的判断,没法被两种方向同时满足。
再其次,必须显式拒绝那些它可能会做的事。
不是因为 AI 不自觉,是没有显式禁区的时候,它的候选里就会包含一些你以为它不会做的动作。删除现有代码、引入新依赖、重命名公开函数,这些动作在它的训练数据里都出现过,而且不少。你没给禁区,它碰到一个重构会更优雅的诱惑点,会有非零的概率走过去。显式禁区不是因为它喜欢乱来,是把这些非零概率压到零的唯一办法。
这几点放在一起,新规范和老 PRD 在结构上就是两种东西了。老 PRD 描述做什么,新规范定义什么是做完了、做完了是什么样、什么是不能碰的。它的形态不是叙事性的产品描述,是一份可对照、可验证、有完标准、有禁区的工程合约。
OpenSpec 这一类工具,第一次出现在这一节里。它们之所以设计成那种样子:强制 spec → tasks → acceptance criteria 的三段式、强制每个任务项用复选框写出来、强制把完成态单独立一个字段,不是某种产品偏好,不是为了显得专业。是被上面这三件事的约束逼出来的。一个产品要承载在结构上长得不一样的规范,就只能设计成这样:你想让规范可勾选,任务项就得是清单;你想让完成态显式,就得有验收标准;你想让两类信息分开,就得有规范和任务的分层。Spec Kit 是类似的思路,目录结构有差异,但骨架一致。
它们看起来像新工具,本质上是这种结构的承载物。挑哪一家的产品并不关键,应该关心的是这种结构本身为什么不得不设计成这样。
11.5 你说的规范到底是哪种规范
回头看你上一节脑子里浮现的那个规范。它指的是哪一种?是上一节那个加超时重试的需求规约,写出来描述这次要做什么、做完什么样,做完了归档?还是一份描述我们这个项目里的重试机制是什么的长期文档?和重试代码一起活、一起改?
很多人没区分过这两个东西,因为日常使用里它们都叫规范,或者都叫 spec。但它们在生命周期上几乎相反。把它们混在一起讨论要不要维护,就会陷入一种永远说不清的循环:你说要维护,对方拿PR 描述写完归档来反驳;你说不维护,对方拿API 文档不能过期来反驳。两边都对,但说的不是同一种东西。
这两种东西,可以分别叫变更级规范和能力级规范。
变更级规范,颗粒度是一次变更。加超时重试这次需求,从开始到合并,整个过程对应一份变更级规范。它在 OpenSpec 里的目录是 changes/,每次新变更建一个新目录,里面是 proposal.md、tasks.md 之类的文件。它描述的是我们这次要把代码从状态 A 推到状态 B,做完了的判定标准是什么。它的生命周期是一次性的:写出来、被执行、被合并、被归档(OpenSpec 里有个归档命令专门做这件事)。归档之后就不再改了。
类比的话,它最像 PR 描述加提交记录的合体,不是一份动态文档,是一个时间锚点上的决策档案。三个月后回头看,看的是当时为什么这么决定,而不仅是现在的逻辑是怎样的。
能力级规范,颗粒度是一个长期能力。重试机制这个能力本身,在你的项目里只要还活着,就对应一份能力级规范。它在 OpenSpec 里的目录是 specs/(和 changes/ 是两个不同的目录)。它描述的是这个能力当前是什么样的:参数有哪些、边界在哪里、哪些东西不能动、什么情况下退化、什么情况下抛错。它的生命周期和这部分代码一样长,代码在演进,它必须同步演进。类比的话,它最像 API 文档加上一份关键约束清单的合体。不是一份写完就静止的东西,是一份必须永远反映现状的活文档。
两种东西的生命周期几乎完全相反。一个一次性、有终点、写完冻结;一个持续演进、和代码同寿、必须永远是现状。把它们混在一起谈要不要维护,自然怎么谈都不对。
OpenSpec、Spec Kit、Kiro 这一类工具,目录结构里其实两类都有,changes/ 和 specs/ 各自服务一种。但因为外面包了一层统一的命名(都叫规范、都叫 spec),用户初接触时很容易混成一件事,然后产生那个写完了就完了吗的困惑。困惑的根源就是把两类放在一起问。
分开看,那个困惑的答案立刻清晰了,而且答案是相反的。把这一对规范的生命周期讨论清楚,还能推出一个直接的工作判断:什么样的代码改动该走规范,什么样的不用。这件事在团队里经常吵,有人觉得每次都该走,有人觉得太重。其实答案不在严格或宽松,在变更影响了哪一类东西。
改一个变量名、修一个 typo、调一个魔法数字,这种变更不影响任何一份能力级规范。它的讨论过程也没什么三个月后还值得重新探讨的,就是顺手改了。这种变更直接改代码就行,不需要走任何规范。
改重试策略、加新参数、改完成态判定,这种变更影响了能力级规范描述的现状。它必须走规范:先改能力级规范说明新状态,再改代码实现新状态,再用一份变更级规范描述我们这次为什么这么改。前者保证现状描述不漂移,后者保留这次决策的档案。
这个边界其实就是后面 11.7 那个协作半衰期判断的另一种说法:这次变更的语义,三个月后是否还需要被理解?如果是,走规范;如果不是,直接改代码。
11.6 为什么这两份合同都必须进 Git,但理由不同
传统软件里有句话被反复说:代码即真相。所有真相最终都长在代码里,文档可以模糊、可以过时、可以丢失。
但在和 AI 协作的时候,这句话就不成立了。下次 AI 来改这段代码的时候,它读的不只是代码,还有描述这段代码的规范。代码当然永远是真相,但只有代码不够:代码可以告诉它现在是这样写的,没法告诉它为什么这样写,也没法告诉它什么是这段代码不能破的那些约束。它需要规范来补这两层。规范不在仓库里,等于这段代码缺了一半的协作信息。
那直接得出两类规范都必须进 Git是不是就行了?方向对,但理由要分开看。两类规范进 Git 是为了截然不同的事。
变更级规范进 Git,是为了把决策档案锚定在历史时刻上。
它最有价值的地方,是和那次代码变更绑在一起。半年后另一个开发(或另一个 AI 会话)读到代码里 retry_delay = 2 ** attempt,他想知道当时为什么用指数退避不是固定退避、为什么底数是 2 不是 1.5、为什么上限是 30 秒。他不会去翻 wiki,他会顺着 Git 历史翻到那次变更对应的 changes/2024-Q3-add-retry/proposal.md,看到当时为什么这么决定。
变更级规范必须进 Git 的核心理由就是这个:只有进 Git 才能和当时的代码状态绑定。一份脱离 Git 的决策档案,比如躺在 Notion 或 Confluence 里。没有时间锚点。它说当时我们决定 X,但当时是什么时候?对应的代码长什么样?没法回放。Git 提供的是这种绑定关系:每一次变更级规范都和一次代码变更同属一个 commit / PR,三个月后回头看,你能精确地把当时的代码状态和当时的决策对应起来。
而它进 Git 之后就不再改了,它像提交记录一样,归档后冻结。它的价值正来自那一刻定下来什么,不来自现在变成了什么。
能力级规范进 Git,是为了和代码一起演进,永远保持同一份真相。
它最有价值的地方,是和当前代码状态完全同步。下次 AI 来改重试机制的代码,它读 specs/retry/spec.md 是为了知道当前的约束:重试的总次数上限是多少、什么样的错误才该重试、幂等性是怎么保证的、关键指标埋了哪几个。这份文档但凡和代码有一点漂移,AI 读到的协作意图就已经是偏的了。
之前提到过代码记忆的过时不是时间维度的,是因果维度的。一条记忆该不该作废,不取决于多久没被访问,取决于它对应的现实有没有变。能力级规范适用同一条规律:它过时不过时,看的不是它写于何时,是它描述的那段约束规范现在还适不适用。
所以能力级规范必须进 Git 的核心理由是:只有进 Git 才能强制和代码一起评审、一起演进。一个 PR 里改了代码却没改规范,评审应该拦下来;一个 PR 里改了规范却没改代码,评审也应该问清楚。这种强制绑定是非确定性协作下保持同一份真相的唯一办法。脱离 Git,靠人自觉去同步 Notion / wiki,最后一定会脱节。
而且能力级规范的修改入口必须是规范先行:先改规范描述新状态,再改代码实现新状态。反过来,代码先改、事后补规范,等于把和 AI 协作变回了传统的补文档模式。补文档为什么不可靠?因为它的成立依赖人的自律,而人的自律并没有机制来保证。规范先行不是为了仪式感,是把先写、再做设计成结构上的强制顺序,让懒惰这件事在结构上被关上。
11.7 两种失败模式
规范驱动不是免费的。它有维护成本,有协作摩擦,而且它真的会跑不下去。失败的模式不止一种。
第一种失败模式是腐化,只发生在能力级规范上。
能力级规范的生命线是和代码同步。一旦同步断了,它就开始腐化。第一周代码改了规范没改,没人提;第一个月十次 PR 里有三次代码动了规范没动,评审没拦下来;半年之后,规范文件还在 Git 里,看起来还像一份认真的文档,但它描述的那个现状已经和真实代码错开了一大截。下次 AI 读这份规范来改代码,读到的协作意图已经是偏的了,它信了一份在说谎的文档。
能力级规范漂移于代码,是协作意图层面的记忆毒化,毒化的是"为什么这么做"和"什么是边界"。它不是一份写得不好的规范,可能它写得很认真,结构清晰、表述准确。它的问题在于没有人为它的持续真实性负责。一份能力级规范不能只被写出来,它必须有一条纪律强制它和代码同生共死,这条纪律就是 11.6 那个规范先行 + PR 同时改规范和代码。这条纪律失守了,它就开始腐化。
变更级规范不存在腐化问题。它本来就是一次性档案,归档后冻结是它的设计意图,不是失败。一份三年前的变更级规范,你回头看它已经和当下代码不一样了,这不是过时,是它的功能就是描述三年前那一刻的决策,当然不会反映当下。把变更级规范也算成腐化,是把两类的生命周期搞混了。
第二种失败模式是形式主义。两类规范都可能,但表现不太一样。
变更级规范的形式主义是:每个 PR 都强制塞一份变更级规范,但内容是事后补的。代码已经写完了,开发顺手在 changes/ 目录里建一个文件,把做了什么再用书面化的语气复述一遍,加几个看起来正式的标题。它和真实的决策过程没有关系,决策过程是那次对话里那六轮磨出来的,事后这份档案只是把最终结果换一种格式再写一次。这种规范看起来是档案,本质上是 PR 模板里的必填字段。三个月后回头去翻,你只会看到"做了 X",看不到"为什么是 X 不是 Y",因为那个为什么被绕过去了。
能力级规范的形式主义是另一种样子。团队为了显得专业或满足合规,把规范写得齐齐整整,章节完备、字段齐全,看起来像一份正经的工程文档。但实际工作里没人按它走。和 AI 协作时不读它(因为没塞进上下文),开发改代码时不改它(因为反正没人查),评审时不审它(因为大家都默认它是装饰品)。它只在合规检查、技术评审、对外汇报的时候被翻出来。这种规范看起来活着,实际上已经死了。
两种形式主义的共同点是:写规范这件事从真的影响协作变成了做样子。它消耗了团队的精力,没换来任何协作精度的提升。
只看到这两种失败模式,很容易冒出来一个反应:那干脆别搞了。但这个反应也不对,前文已经把为什么必须搞挖透了,那些问题不会因为它有失败模式就消失。真正要回答的不是搞不搞,而是什么时候值得搞。
最容易想到的是一份任务类型清单:探索性任务不用搞、一次性任务不用搞、低成熟度领域不用搞,这种清单看起来很专业,实际上没什么用。原因很简单:没人真的会照清单去判断这次该不该写规范。开发拿到一个任务时,他想的不是这是探索性的还是非探索性的,他想的是这件事我心里大概有数还是没数、这件事做完之后还会不会被反复改。任务类型清单是事后的归类,不是事前的判断标准。
更真实的判断标准只有一条:这次变更,三个月后是否还需要被理解。
如果三个月后还会有人(或下一个 AI 会话)回头来看这段代码、改这段代码、依赖这段代码工作,那这次协作的半衰期就长,值得写规范。变更级规范留下为什么这么决定,能力级规范跟着更新现在变成了什么样。三个月后这两份东西会替你在场。如果三个月后这段代码大概率已经被替换、被重写、或者根本没人再碰,那这次协作的半衰期就短,对话窗口里磨完就够了。写规范的成本超过了它能换来的协作精度。
注意这个标准判断的不是任务类型,是这次协作的半衰期。同样是加超时重试,给一个核心支付链路加,半衰期是几年,必须走规范;给一个实习生周末写的 POC 工具加,半衰期是几天,没必要。任务听起来一样,半衰期不一样,规范的必要性也不一样。半衰期这个判断比清单朴素,但落到自己头上反而更难。它要求你对自己当下在做的事有一种工程上的诚实:这件事三个月后还会有人读吗?这是一个不能糊弄自己的问题。
11.8 和记忆是什么关系
读到这里,本章的规范开发和之前章节的记忆边界开始越来越模糊。一边是第八章讲过的 cursorrules、AGENTS.md、CLAUDE.md:一份放在仓库根目录的文件,写着"这个项目用 TypeScript、命名约定是驼峰、错误处理走统一异常基类、不再引入新的依赖",跟着 Git 走,每次 AI 协作都被自动注入。一边是本章 11.5 拆出来的变更级规范:changes/ 目录下,每次变更一份,描述这次要从 A 推到 B、做完什么样,做完归档冻结。
它们都被叫做规范驱动。但显然不是同一个东西,生命周期不一样、修改入口不一样、解决的问题也不一样。
| 维度 | 行为偏好持久化 | 变更级规范 | 能力级规范 |
|---|---|---|---|
| 颗粒度 | 横切:影响所有任务 | 纵切:一次变更 | 纵切:一个能力 |
| 生命周期 | 长期稳定,偶尔更新 | 一次性,归档冻结 | 和代码同寿,持续演进 |
| 回答的问题 | AI 一直应该怎么干活 | 这次要从 A 推到 B、做完什么样 | 这个能力现在长什么样 |
| 典型形态 | .cursorrules、AGENTS.md | OpenSpec changes/ 下的提案 / 任务清单 | OpenSpec specs/ 下的规范 |
| 修改入口 | 偶尔手动评审更新 | 写完冻结,新变更写新档案 | 规范先行,再改代码 |
| 类比 | 工作守则 / 团队公约 | PR 描述 / 提交记录 | API 文档 / 关键约束 |
把三列摆在一起看,会发现它们处的层次完全不同。行为偏好覆盖的是行为底色:项目里所有任务的命名、错误、依赖、日志位置,无论你今天做什么需求,它都在生效。变更级规范覆盖的不是行为底色,是这一次变更里的具体动作,这次要把代码推到哪儿、做完什么样、为什么这么决定,做完归档。能力级规范覆盖的还不是这次动作,是这次动作所影响的那个长期能力:动作完成之后,这个能力新的要求是什么,下次有人来改它,要照着这份描述来实现。
所以同一个项目里,行为偏好持久化 是常驻的底色,变更级规范是每次变更过境时留下来的档案,能力级规范是项目里那些长期能力各自的活文档。三者颗粒度不同、节奏不同、读者也不同。
它们为什么都被叫做规范驱动?因为本质相同:把人脑里的隐性约定,变成工程上的显性产物。行为偏好过去是开发自己脑子里的团队默契,你和老员工合作时,不需要说错误用统一异常基类,他自然就那么写。和 AI 协作时这份默契不存在,所以必须把它变成 .cursorrules 这种显性文件。本章这两类规范,过去是 PR 评论区和聊天里的为什么这么决定,人和人协作时,那些为讨论过程自然在过程里传递。和 AI 协作时没有这种传递,所以必须把它变成 changes/ 和 specs/ 这种显性产物。
三者都是把隐性变显性,但变出来的东西不一样。一个是横切的行为偏好,一个是一次性的决策档案,一个是持续演进的能力真相。
叠加使用时分工很清楚,谁也替代不了谁。一个新需求来了,你写一份变更级规范描述这次要做什么、做完什么样。AI 在执行的时候,背景里同时跑着 .cursorrules 那套行为偏好,它知道命名怎么来、错误怎么处理、依赖能不能引;前台读着变更级规范,它知道这次具体要把代码改到哪个状态。做完之后,能力级规范反映出这个能力现在变成了什么样,下一次 AI 来改这段代码,它读这份规范是为了知道当前的约束。
11.9 当需求变成工程产物
从开始那个"那六轮讨论去哪了"的疑问开始,一条追问链走到这里,可以把全章的观点总结为一句话:
规范驱动编程的目的,是把协作的精度从个人沟通能力变成团队的工程基础设施。
传统软件协作里,精度依赖有经验的开发会自己补齐,那是一种个人能力。同样的模糊需求,给资深的人和给新人,产出的精度差好几倍。这件事在过去几十年里一直被默认为理所当然:团队当然要靠资深开发,新人当然要靠学。和 AI 协作时这条路走不通。模糊不能再被一个看不见的大脑吸收。精度只能往两端走,要么变成代码里的混乱,要么变成需求侧的显性沉淀。规范驱动选择了后者。
你不是在让 AI 按规范干活。你是在为一个不可完全控制的协作者,建立一份可审查、可回放、可演进的工作合约。它是概率预测机器,不是确定性执行机器。规范驱动是和这台机器协作时,把协作意图从口头沉淀成工程产物的尝试。