系列目录(4 篇已发布)
第一季 · 开始之前 · 第 2 篇
先立规矩,再让 AI 写代码
spec → contract → plan → tasks → verify,五份文档一条流水线。
用 AI 写代码,第一周会很爽。
你说“加一个投递看板”,它给你页面、接口、数据库表,一下午能跑起来。
第三周开始出问题。
改了一个字段,另一个页面白了;接口的返回形状和前端对不上;数据库里多了一个字段,但迁移没写;同一个状态值,在三个文件里各有一份文案。
这些问题不是模型笨。它们有一个共同原因:你没有定义“什么算完成”,也没有定义“什么不能改”。
我的解决办法是把开发过程拆成五份文档。听起来很重,但它把返工的次数降下来了。
一、五份文档
| 文档 | 回答的问题 | 位置 |
|---|---|---|
| Feature Spec | 做什么、不做什么、怎么算做完 | specs/features/F-XXX-*.md |
| Contract | 数据、接口、插件、Agent 的边界长什么样 | specs/contracts/*.md |
| Plan | 怎么实现、按什么顺序、有什么风险 | plans/F-XXX-*.plan.md |
| Tasks | 拆成一次只做一件、可独立验证的事 | tasks/F-XXX-*.tasks.md |
| Verify | 对照验收标准逐条检查 | 命令 + 执行记录 |
契约里写什么
契约不是给人看的规范文档,它是 AI 每次开工前要读的上下文。所以只写三类东西:
一、数据的归属。 比如“Company 和 Job 是全局共享的事实,Application 和 JobDecision 按用户隔离”。这一句决定了 AI 会不会给 Job 加 userId。
二、接口的形状。 比如统一响应:
{ "data": {}, "meta": { "requestId": "..." } }
错误也用同一种外壳,前端按 error.code 分支,不解析中文文案。
三、不许做的事。 比如插件契约里写死:只填空白字段、不覆盖已有内容、不点提交、不上传文件。
这三类写清楚,AI 生成的代码才有一致的形状。
Spec 和 Contract 先写,代码最后写。顺序不能反。
原因很简单:AI 会把你给的任何模糊之处,用最省事的方式补上。 你没说不许软删除,它就加 deletedAt;你没说岗位是共享的,它就加 userId。
二、把它变成四个命令
仓库根目录有四个命令文件,内容很短。
/spec:
1. 分配下一个 F-XXX 编号。
2. 写入 specs/features/F-XXX-<slug>.md。
3. 必须包含目标、用户场景、范围、不做什么、验收标准、受影响契约与开放问题。
4. 不明确且会改变范围、数据、隐私、成本的事项必须先提问。
5. 完成后只请求确认,不实现代码。
/plan:
1. 先阅读 Feature Spec 与所有受影响契约。
2. 写入 plans/F-XXX-<slug>.plan.md。
3. 说明架构选择、模块边界、数据/API/Agent 变更、实现顺序、风险与验证方案。
4. 发现 Spec 与契约冲突时,先修订文档,不写代码。
5. 完成后只请求确认,不拆任务或实现。
/tasks:
1. 写入 tasks/F-XXX-<slug>.tasks.md。
2. 每项任务使用 T-XXX 编号,描述单一结果与验证方法。
3. 明确依赖关系;一次只允许一个任务处于进行中。
4. 任务必须覆盖测试、文档或契约更新。
5. 完成后请求确认,再开始实现。
/verify:
1. 对照 Feature Spec 的每条验收标准。
2. 运行计划中声明的自动化检查和必要的手动检查。
3. 记录通过、失败、未验证及原因。
4. 如果实现改变了产品行为,更新 Spec、Plan、Tasks 或契约。
5. 未通过验证不得宣称功能完成。
最后一条是重点。AI 很愿意说“已完成”。你必须给它一个明确的、可以反驳这句话的检查表。
三、一个真实例子
项目里最早的一批功能之一,是岗位评估。
它的 Spec 有 10 条验收标准。其中三条是这样的:
5. 每项匹配结论至少包含来自 JD 或用户简历的可识别依据;
不得虚构用户经历、技能、项目、数据或职位要求。
7. 系统不持久化完整简历文本;保存的 AgentRun 不含可还原完整简历的正文。
8. Agent 不会创建投递、改变投递状态、改写简历或执行任何外部副作用。
注意这三条都不是“实现什么”,而是“不许做什么”。
AI 天然倾向于把能拿到的数据都存下来,把能做的操作都做掉。验收标准里的否定句,是给它划红线。
Plan 里还有一张风险表。比如这一行:
| 将完整简历持久化到 AgentRun | 服务层只接受内存中的 resumeText;
持久化前调用摘要函数,测试断言数据库输出不包含原文。 |
风险表不是为了好看。它决定了测试写什么。
四、执行记录:别让 AI 只说“已完成”
Tasks 文件里,每条任务都有“验证”和“执行记录”。
这是 T-003 的真实记录:
T-003:定义 Zod 契约与服务层……确保完整 resumeText 不进入 Prisma 写入数据。
验证:服务层测试断言 AgentRun 不含完整简历原文;同岗位运行两次产生两条记录。
执行记录(2026-08-09):resumeText 仅作为函数入参参与内存摘要,
未出现在任何 Prisma data 写入或返回契约中;lint 与 typecheck 通过。
端到端行为将在 T-005/T-007 API 验证中覆盖。
这段记录有三个作用。
第一,它承认了什么没验证。“端到端行为将在后面覆盖”比“已完成”诚实。
第二,它给了后面的我一个入口。三天后回来,我知道该从哪继续。
第三,它逼 AI 把“怎么验证的”写出来。只写“已实现”是不合格的。
整个项目留下了 56 份 Spec、56 份 Plan、57 份任务清单。任务清单里最多的一天有 31 条执行记录。
五、代价
先看一个反例。
有一次我让它写风险表,它给了三行:
| 兼容性 | 注意兼容性 |
| 性能 | 注意性能 |
| 安全 | 注意安全 |
形式上完全符合模板,内容等于没说。
好的风险表应该长这样:
| 将完整简历持久化到 AgentRun | 服务层只接受内存中的 resumeText;
持久化前调用摘要函数,测试断言数据库输出不包含原文。 |
区别在于:后者能变成一个测试,前者不能。
这套流程不是免费的。
写文档的时间大约占总时间的 20% 到 30%。 一个中等功能,spec 加 plan 加 tasks 要一两个小时。
模板会僵化。 有些小改动,硬套 spec 会显得很滑稽。
AI 会“形式上遵守”。 它会写满所有小节,但内容是套话。比如风险表里写“注意兼容性”,没有任何具体的兼容性风险。
所以模板只能保证下限,不能保证质量。质量还是靠人读。
六、我现在怎么分层
不是所有改动都走全套。我按影响面分三级。
轻量:改文案、调样式、加一个只读字段。 写一条可验证的断言,直接做,不写 spec。
中等:新增一个接口、改一个页面的交互。 写 spec 和 tasks,plan 可以合并进 spec。必须有测试或手动验证步骤。
重量:涉及数据模型、权限、外部副作用、AI 写入。 全套走完。因为这类改动的错误成本最高,而且最难回滚。
判断标准可以简化成一个问题:这个改动如果错了,我能不能在十分钟内发现?
能,就走轻量。不能,就走全套。
七、小结
AI 不会因为你不写文档就停下来。它会在信息不足时自己补全,而且补得很自信。
Spec 和契约的作用,不是“让 AI 更聪明”,而是让它没有机会替你决定那些应该由你决定的事。
下一篇讲一个具体的例子:数据模型。第一版只有一张表,两周后就崩了。
上一篇:我用 AI 从零做了一个全栈产品
下一篇:把需求写成 AI 能执行的样子(待发布)