系列目录(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 能执行的样子(待发布)