乐于分享
好东西不私藏

Spec Coding 不是多写一份文档:让 AI Agent 按规格开发、验证与收敛

Spec Coding 不是多写一份文档:让 AI Agent 按规格开发、验证与收敛

你让 Agent 实现一个功能:

给订单列表增加批量导出。

几分钟后,按钮、接口调用和下载逻辑都有了。

但继续问,问题就开始出现:

  • • 没有选择订单时能不能点击?
  • • 用户没有导出权限怎么办?
  • • 一次选择几万条订单怎么办?
  • • 用户连续点击两次会不会创建两个任务?
  • • 网络超时后能不能重试?
  • • 页面刷新后,导出任务还在不在?
  • • 失败时应该回到什么状态?

Agent 不是不会写代码,而是在替你补全那些没有写出来的需求。

这正是 Spec Coding 想解决的问题。

Spec Coding 到底是什么?

先把术语边界说清楚。

“Spec Coding”目前更像一个工程实践中的称呼,并不是一个统一的编程语言、协议或行业标准。相关官方资料更常使用这些词:

  • • Spec-Driven Development;
  • • Specs;
  • • Intent-driven development;
  • • Structured requirements;
  • • Agent workflow。

GitHub 的 Spec Kit 将这种方式定义为一种以规格驱动 AI coding agent 的开发流程。[1] 官方文档给出的默认链路是:

Spec -> Plan -> Tasks -> Implement

当前流程还增加了 Converge,用于检查实现是否重新符合规格、计划和任务。[2]

Kiro 的 Specs 功能也采用了类似思路:用结构化需求、技术设计和实施计划拆解复杂功能,并持续跟踪实施过程。[4][5]

所以,本文所说的 Spec Coding,可以先理解为:

先把模糊意图整理成可评审、可执行、可验证的规格,再让 Agent 按规格推进实现。

它不是“把 Prompt 写长一点”,也不是“再多维护一份文档”。

真正的变化是:规格开始成为开发过程中的中间控制面。

为什么直接让 Agent 写代码容易失控?

需求歧义会被模型自行补全

“支持导出”可能有很多合理解释:

  • • 导出当前页,还是全部筛选结果?
  • • 导出 CSV,还是 Excel?
  • • 空数据时显示什么?
  • • 权限由前端判断,还是服务端判断?
  • • 失败后允许重试几次?
  • • 相同查询条件是否复用已有任务?

人类开发者通常会通过产品讨论、历史代码和团队约定补充这些信息。

Agent 没有这些隐含上下文时,只能根据概率选择一种实现。

它可能给出一份看起来很完整的代码,但这份代码解决的是“它理解的需求”,不一定是团队真正要的需求。

聊天上下文会逐渐失效

一次短对话里,Agent 还能记住你说过的约束。

但当任务变成:

  • • 阅读多个模块;
  • • 调整接口;
  • • 修改状态管理;
  • • 增加测试;
  • • 修复构建错误;
  • • 再回头补边界场景;

原始需求很容易被埋在几十轮对话里。

如果需求、技术决策和任务状态没有被写成稳定文件,后续 Agent 只能不断重新猜测。

代码会和需求逐渐漂移

开发过程中经常会出现这种情况:

  1. 1. 最初需求说需要失败重试;
  2. 2. 技术计划里决定使用异步任务;
  3. 3. 实现阶段先做了同步下载;
  4. 4. 测试只覆盖了成功路径;
  5. 5. 最后大家以为功能已经完成。

代码可以通过构建,甚至可以通过部分测试,但它已经偏离了最初的目标。

GitHub 对 Spec-Driven Development 的描述,核心就是反转“代码是唯一真相”的习惯:规格、计划和实现之间要保持持续关联。[3]

一套完整的 Spec Coding 工作流

第一步:定义项目原则

GitHub Spec Kit 将项目原则作为一个基础阶段,用来定义后续开发必须遵守的规则。[1]

这些规则可以包括:

  • • TypeScript 必须开启严格模式;
  • • 新功能必须有自动化测试;
  • • 权限只能由服务端决定;
  • • 组件必须支持键盘操作;
  • • 不引入没有维护状态的依赖;
  • • 页面错误不能导致整条路由不可恢复。

这一步不是为了写一篇宏大的工程宣言,而是为了告诉 Agent:

这个项目不仅要实现功能,还要按什么标准实现功能。

如果团队已经有 CONTRIBUTING.md、架构规范或代码检查规则,也可以把它们整理成 Agent 能稳定读取的项目原则。

第二步:Specify,先写需求,不急着选技术

以批量导出为例,Spec 可以这样写:

# 批量导出订单

## 用户故事


作为运营人员,我希望选择多个订单并导出,
以便进行线下对账。

## 验收标准


-
 未选择订单时,导出按钮不可提交
-
 请求提交或处理中,不允许重复创建任务
-
 导出失败后可以重新发起
-
 同一批订单的重复点击不能造成重复请求

## 非目标


-
 本次不定义文件格式
-
 本次不实现服务端权限系统
-
 本次不实现后台任务 Worker

这里有两个关键点。

第一,先描述用户行为和可观察结果,不要一开始就写:

使用某个 Hook,创建一个名为 useExport 的文件。

第二,明确写出非目标。

不属于本次需求的内容也应该被记录下来,否则 Agent 很容易顺手扩大任务范围。

第三步:Plan,把需求翻译成技术决策

Plan 阶段回答的是:

  • • 数据流怎么走?
  • • 状态如何组织?
  • • 哪些逻辑属于前端?
  • • 哪些逻辑必须交给服务端?
  • • 如何处理重复请求?
  • • 失败后如何恢复?
  • • 哪些方案明确不采用?

例如:

# 实施计划

-
 使用 idle、submitting、processing、success、failed 表示导出生命周期
-
 在调用 API 前拒绝空选择和进行中的重复提交
-
 为每次请求保留 requestKey
-
 服务端自行决定幂等存储和权限校验
-
 前端状态机不负责生成文件,也不替代服务端授权

Plan 的作用不是提前把所有代码写完,而是让技术选择有理由可查。

如果后续有人问“为什么没有在前端直接生成文件”,可以回到计划中找到边界,而不是重新翻聊天记录。

第四步:Tasks,把计划拆成可执行任务

好的任务应该能被独立完成和验证。

- T-1:实现空选择校验
-
 T-2:实现请求提交中的重复提交保护
-
 T-3:实现失败状态和重新发起
-
 T-4:为每条验收标准增加测试

不推荐写成:

- 完成批量导出功能

后者看似简洁,实际上无法判断进度,也无法知道漏掉了哪些分支。

一个任务至少要能回答三个问题:

  1. 1. 要修改什么?
  2. 2. 完成后如何验证?
  3. 3. 它对应哪条需求?

第五步:Implement,让 Agent 执行任务

到了实现阶段,Agent 不再是“自由发挥的代码生成器”,而是按照规格和任务推进的执行者。

可以给出这样的约束:

请读取 specs/order-export/spec.md、plan.md 和 tasks.md。
只实现当前任务 T-1。
完成后运行对应测试,并报告:
1. 修改了哪些文件;
2. 哪些验收标准已经覆盖;
3. 哪些假设仍未确认;
4. 哪些任务没有执行。

这会比一句“帮我完成批量导出”更容易复核。

当然,Spec 不会让 Agent 自动变得正确。它只是把任务边界、验收标准和未确认事项显式化。

前端最应该关注的:状态和验收标准能否对上

规格不是只给产品经理看的。

对于前端工程师,最有价值的地方往往是把用户行为翻译成状态机。

批量导出可以抽象成:

idle
  -> submitting
  -> processing
  -> success

processing
  -> failed
  -> retrying
  -> processing

前端状态机至少应该覆盖:

  • • 空选择;
  • • 提交中;
  • • 处理中;
  • • 成功;
  • • 失败;
  • • 失败重试;
  • • 重复操作。

对应的 TypeScript 类型可以很简单:

type ExportState =
  | "idle"
  | "submitting"
  | "processing"
  | "success"
  | "failed";

type
 ExportSession = {
  state
: ExportState;
  selectedOrderIds
: readonly string[];
  requestKey
?: string;
  error
?: string;
};

然后把非法转换挡在状态层:

function startExport(
  session
: ExportSession,
  requestKey
: string,
) {
  if
 (session.selectedOrderIds.length === 0) {
    return
 {
      accepted
: false,
      message
: "select-at-least-one-order",
      session,
    };
  }

  if
 (
    session.state === "submitting" ||
    session.state === "processing"
  ) {
    return
 {
      accepted
: false,
      message
: "request-in-flight",
      session,
    };
  }

  return
 {
    accepted
: true,
    session
: {
      state
: "submitting",
      selectedOrderIds
: session.selectedOrderIds,
      requestKey,
    },
  };
}

这里的价值不是代码有多复杂,而是每个分支都可以回到 Spec 中的验收标准。

如果需求里写了“处理中不可重复提交”,测试就应该能直接证明这一点。

Converge:最容易被漏掉的最后一步

很多 AI 编程流程到“代码生成成功”就结束了。

Spec Coding 需要再问一遍:

现在的代码,真的符合原来的 Spec 吗?

收敛检查至少要看五件事。

1. 每条验收标准都有实现吗?

如果 Spec 中有 AC-1AC-2AC-3,就应该能找到对应任务和测试。

2. 每个任务都能回到需求吗?

如果出现一个任务没有对应验收标准,可能是:

  • • 需求漏写;
  • • 任务范围扩大;
  • • Agent 自行增加了功能;
  • • 原需求已经变化但没有更新 Spec。

3. 失败路径真的存在吗?

很多实现只覆盖:

点击 -> 成功

但真实功能还需要考虑:

点击 -> 网络失败
点击 -> 超时
点击 -> 重复点击
点击 -> 页面刷新
点击 -> 权限失效

4. 技术决策有没有被偷偷改掉?

Plan 里写了“服务端负责权限校验”,但实现却在前端根据一个隐藏按钮决定是否允许请求,这就是边界漂移。

5. 测试验证的是需求,还是只是代码分支?

测试覆盖率高,不代表需求覆盖完整。

真正应该检查的是:

验收标准 -> 任务 -> 实现 -> 测试

这是一条追踪链,而不是四份互相独立的文件。

一个可运行的 Spec Coding 实验

我把上面的批量导出场景做成了独立 TypeScript 实验:

https://github.com/xiaogao007/running-404-code-labs/tree/main/spec-coding-workflow

实验目录包含:

spec-coding-workflow/
├── specs/order-export/
│   ├── spec.md
│   ├── plan.md
│   └── tasks.md
├── src/export-workflow.ts
├── test/export-workflow.test.ts
└── README.md

它验证了五条路径:

  • • 空选择不能启动导出;
  • • 请求处理中不能重复提交;
  • • 失败后可以使用新的请求标识重试;
  • • 成功状态是终态;
  • • 验收标准与任务之间缺失链接时可以被发现。

复现命令:

git clone https://github.com/xiaogao007/running-404-code-labs.git
cd
 running-404-code-labs/spec-coding-workflow
npm ci
npm run verify

代码状态:已运行。验证环境为 Node.js 25.8.2、npm 11.9.0,类型检查通过,5 个测试全部通过。

这个实验有意没有调用 AI 模型,也没有安装 GitHub Spec Kit 或 Kiro。它验证的是更基础、也更通用的一层:

规格里的状态和验收标准,能不能真正变成可执行的代码约束?

Spec 应该写到什么粒度?

写得太粗

实现一个好用的导出功能。

Agent 需要自行猜测,团队也无法验收。

写得太细

在第 132 行新增 handleExportClick,
调用某个 Hook 的第三个参数。

这会过早锁死实现,限制设计空间,也可能让规格很快过时。

更合适的粒度

Spec 优先描述:

  • • 用户是谁;
  • • 想完成什么;
  • • 什么情况算成功;
  • • 哪些情况必须失败;
  • • 哪些行为不属于本次范围;
  • • 有哪些安全、性能和一致性约束。

Plan 再描述:

  • • 采用什么技术;
  • • 如何拆模块;
  • • 数据如何流动;
  • • 为什么不选其他方案。

Implement 阶段才进入具体文件、函数和代码结构。

可以用一句话概括:

Spec 写“要观察到什么”,Plan 写“准备怎么实现”,代码写“具体怎么落地”。

Spec Coding 适合所有任务吗?

不适合。

适合使用

  • • 跨多个模块的业务功能;
  • • 有权限、数据一致性或回滚要求的功能;
  • • Agent 需要连续工作较长时间的任务;
  • • 多人协作和代码评审;
  • • 需要长期维护的系统;
  • • 风险较高的 Bug 修复。

Kiro 也将 Bugfix Specs 单独作为一种规格类型,用于系统诊断、修复和防止回归。[6]

不必强行使用

  • • 改一个文案;
  • • 调整一个 CSS 间距;
  • • 一次性脚本;
  • • 已有明确测试的小范围重构;
  • • 只想快速验证一个想法的最初几分钟。

如果一个改动只需要一分钟,却要先写四个文件,流程就已经失去比例感了。

它的真实代价

Spec Coding 不是免费质量。

它会带来额外工作:

  • • 前期要花时间澄清需求;
  • • 规格也需要维护;
  • • 计划可能因为技术限制而变化;
  • • Agent 仍可能误解自然语言;
  • • 代码审查和测试仍然不可省略。

所以,不要把它宣传成“有了 Spec,AI 就不会写错代码”。

更准确的说法是:

Spec Coding 把一部分隐含猜测提前暴露出来,把一部分口头约定变成可以评审和验证的资产。

一份 Spec 是否合格,可以这样检查

提交给 Agent 之前,至少问自己:

  • • 是否明确了用户和目标?
  • • 是否写出了非目标?
  • • 是否有可验证的验收标准?
  • • 是否覆盖空数据、失败、权限和重复操作?
  • • 是否区分了需求和技术方案?
  • • 是否记录了关键技术取舍?
  • • 是否能拆成独立任务?
  • • 是否每个任务都能映射回某条需求?
  • • 是否每条需求都有测试或验证方式?
  • • 是否定义了完成和废弃条件?

再做一个很实用的测试:

如果把这份 Spec 交给另一个 Agent,它能否在不依赖作者记忆的情况下完成大部分任务?

如果不能,说明规格还依赖隐含上下文。

结尾:代码没有消失,只是责任上移了

Spec Coding 的核心,不是让开发者少写几行代码,也不是把所有开发流程变成模板。

它改变的是开发者的工作重心:

  • • 从直接写代码,转向定义边界;
  • • 从一次性 Prompt,转向维护稳定上下文;
  • • 从只检查实现,转向检查需求到代码的链路;
  • • 从“代码能跑”,转向“代码是否符合可验证的意图”。

AI 可以帮助我们把代码写得更快,但它不会自动知道什么应该做、什么不应该做,也不会自动承担需求漂移带来的责任。

真正值得沉淀的,不是某一次对话生成了多少代码,而是团队能否留下这样一条清晰链路:

意图
  -> 规格
  -> 计划
  -> 任务
  -> 实现
  -> 测试
  -> 收敛

Spec Coding 不是多写一份文档。

它是在 Agent 参与开发之后,给“为什么这样做、做到什么算完成、出了偏差如何发现”建立一套可追踪的回答。

参考资料:

[1] GitHub, Spec Kit README

[2] GitHub, Spec Kit Documentation

[3] GitHub, Specification-Driven Development

[4] Kiro, Specs Documentation
https://kiro.dev/docs/specs/

[5] Kiro, Feature Specs
[6] Kiro, Bugfix Specs

[7] rstacruz, Spec mode prompt

互动问题:

你在使用 AI 编程工具时,最容易遗漏的是需求边界、异常路径,还是验收标准?

✨ END

我是404星球的猫

拒绝当工具人,做 AI 时代的代码架构师。

漫游继续,我们下篇见~

👇 关注我,手握这份 AI 时代的全栈漫游指南