SDD:把模糊需求编译成 Coding Agent 能执行的开发流程

Spec-Driven Development 用 Spec、Plan、Tasks 和 Converge 管理 AI 编程中的意图、技术方案与验收证据。本文通过一个博客搜索功能,完整演示从需求澄清到实现收敛的流程。

本文使用humanizerdocumd-visuals

我以前使用 Coding Agent 时,经常直接给一句需求:给博客加搜索、把登录改成 JWT、给接口补缓存。模型很快开始读代码,也很快给出修改。麻烦通常出现在后半段。它做到一半才发现搜索范围没有定义,缓存一致性没有约定,或者登录方案与现有权限模型冲突。

人类开发者面对模糊需求也会犯错,Coding Agent 只是把这个过程加速了。它擅长生成代码,却无法替我们决定产品边界。信息缺失时,模型会根据训练数据和当前仓库补全空白。每个补全都可能合理,组合起来未必是我们要的系统。

Spec-Driven Development,简称 SDD,试图在 Agent 动手前把这些空白显式化。需求先写成可验证的规格,再转成技术方案和任务列表。实现过程中产生的新发现会回写到相应文档,最后用代码、测试和运行结果检查规格是否兑现。

GitHub Spec Kit把核心过程组织为 Specify → Plan → Tasks → Implement → Converge。这套命令很有代表性,文章会借它解释流程,但重点不在某个工具。只要团队保存了同等含义的工件,用 Markdown、Issue 或自己的 Skill 都能运行 SDD。

SDD 从开发意图到实现收敛的完整循环

一、SDD 管理的是开发意图

一次代码修改至少包含四种信息:

需求:用户最终能够做什么
约束:哪些边界不能突破
方案:准备怎样修改系统
证据:怎样证明实现已经完成

聊天记录也能保存这些信息,只是它不够稳定。对话越长,早期决定越容易被压缩;中途切换会话,新 Agent 需要重新理解;多人协作时,每个人看到的上下文可能不同。

SDD 把意图保存在版本库里。典型目录类似:

specs/
└── 001-blog-search/
    ├── spec.md
    ├── plan.md
    ├── tasks.md
    ├── contracts/
    └── checklists/

.specify/
└── memory/
    └── constitution.md

这些文件承担不同职责。spec.md 说明要交付的行为,plan.md 记录技术路径,tasks.md 保存执行顺序,测试和检查记录提供证据。它们共同构成 Agent 的开发上下文。

开发过程中最常见的失败不是模型不会写某段代码,而是它在错误目标上写出了正确代码。SDD 优先控制这个风险。

二、Constitution:项目长期遵守的原则

Feature Spec 只约束一个功能,项目还需要一组长期规则。Spec Kit 将这类规则保存在 Constitution 中,可以理解成项目宪章。

我们的 Astro 博客可以有下面这些原则:

# 项目原则

1. 所有页面必须支持桌面端和移动端。
2. 内容分类统一来自 src/config/sections.ts。
3. 不在客户端代码中保存密钥和敏感配置。
4. 新页面保持现有排版与颜色体系。
5. 提交前必须运行 npm run build。
6. 用户可见交互必须支持键盘操作。

Constitution 适合保存跨任务不变的决定:技术栈、目录边界、安全要求、测试底线、兼容范围和提交规范。它不应该包含“本次搜索支持标题和标签”这样的功能细节。

项目原则越多不一定越好。过期规则会让 Agent 在错误约束下工作,相互冲突的规则则会把决定重新推给模型。每条原则最好附带原因或验证方式,并且有人负责维护。

三、Specify:先定义用户能够观察到的结果

现在给博客增加搜索功能。原始需求只有一句:

给博客增加搜索。

Agent 无法从中确定搜索范围、交互方式和完成条件。Specify 阶段把这句话展开成 spec.md:

# 博客文章搜索

## 目标

读者可以在博客中搜索已经发布的文章,
并从结果直接进入文章页面。

## 用户故事

作为博客读者,
我希望用标题、摘要或标签查找文章,
以便快速找到某个技术主题。

## 功能要求

1. 顶部导航提供搜索入口。
2. 搜索范围包含标题、摘要和标签。
3. 结果展示标题、摘要与所属专栏。
4. 搜索支持中文和英文关键词。
5. 没有匹配内容时显示空状态。
6. 草稿文章不能进入搜索结果。

## 范围之外

- 暂不搜索文章正文;
- 不保存用户搜索历史;
- 不接入外部搜索服务;
- 不提供拼写纠正和搜索推荐。

## 验收场景

- 搜索“RAG”可以找到 RAG 相关文章;
- 搜索不存在的词时显示空状态;
- 键盘可以打开、选择和关闭搜索框;
- 手机端可以完成输入与跳转。

Spec 关注外部行为,尽量避免提前规定技术实现。这里没有要求使用 Fuse.js,也没有决定索引生成文件放在哪里。技术选择将在 Plan 阶段完成。

一份可用的 Spec 通常包含:

  • 目标和使用者;
  • 用户故事或使用场景;
  • 功能要求;
  • 非功能约束;
  • 明确排除的范围;
  • 可验证的验收条件。

“搜索体验良好”“页面性能足够快”不能直接验收。需要进一步写成可观察的行为或量化条件。暂时无法决定的内容也要标出来,避免模型悄悄选择一个答案。

四、Clarify:把隐藏问题提到编码之前

完成初稿后,Agent 应当检查模糊项,而不是马上设计方案。

搜索功能还有一批未决问题:

结果按照相关度还是发布日期排序?
英文搜索是否区分大小写?
多个关键词使用 AND 还是 OR?
搜索状态是否写入 URL?
弹窗是否允许点击遮罩关闭?
文章数增加后是否需要分页?

这些问题并非全部需要用户逐项决定。Agent 可以读取现有产品和代码,给出带依据的默认建议。会改变用户体验、数据模型、兼容性或者工作量的问题,应该在进入 Plan 前确认。

Clarify 的输出应当回到 Spec,而不是单独留在聊天记录中。例如确认“结果优先按匹配分排序,同分时按发布日期倒序”,就把这条规则写入功能要求或验收场景。

Spec Kit 的 Agentic SDD 流程把 Clarify 作为推荐的可选步骤,位置在 Specify 和 Plan 之间。是否单独执行命令并不重要,重要的是方案设计前处理那些会引发多种实现的空白。

五、Plan:把规格映射到现有系统

Plan 回答技术问题。Agent 要先检查仓库结构、现有约定、依赖和测试方式,再决定怎样实现 Spec。

博客搜索的 plan.md 可以写成:

# 技术方案

## 当前系统

- Astro 静态站点;
- 文章来自 content collection;
- 没有运行时后端和数据库;
- 顶部导航由 BaseLayout 渲染。

## 方案

构建时读取已发布文章,生成静态 JSON 索引。
浏览器按需加载索引并在本地完成匹配。

## 数据结构

SearchDocument {
  title: string
  description: string
  tags: string[]
  category: string
  subcategory?: string
  publishedAt: string
  url: string
}

## 修改范围

- 新增搜索索引端点;
- 新增搜索匹配函数;
- 新增搜索弹窗组件;
- 修改顶部导航;
- 增加移动端和键盘交互样式;
- 增加搜索逻辑测试。

## 约束

- 索引排除 draft;
- 不增加服务端运行时;
- 搜索组件初始不下载索引;
- 复用现有颜色和排版变量。

## 验证

- 单元测试覆盖匹配、排序和草稿过滤;
- npm run build;
- 桌面端与移动端手动检查;
- 键盘操作检查。

Plan 应该显示它如何利用现有系统。脱离仓库生成的通用方案没有多少价值。一个已有项目还需要记录复用点、受影响模块、迁移方式和回滚策略。

接口、事件和数据结构可以作为 contracts/ 下的独立工件。多服务协作时,先确定提供方和消费方共同遵守的契约,再分别实现。Spec Kit 的契约驱动开发指南也强调,提供方与消费方应引用同一份权威契约,并为双方安排验证。

六、Tasks:把方案切成可交付的增量

一份写得很详细的 Plan 仍然可能让 Agent 一口气修改十几个文件。Tasks 将方案拆成可执行单元,并明确依赖关系与验收方法。

# 任务列表

- [ ] T01 定义 SearchDocument 类型
  - 文件:src/lib/search.ts
  - 验证:类型检查通过

- [ ] T02 生成已发布文章索引
  - 依赖:T01
  - 文件:src/pages/search-index.json.ts
  - 验证:索引不包含 draft

- [ ] T03 实现关键词匹配与排序
  - 依赖:T01
  - 文件:src/lib/search.ts
  - 验证:运行搜索单元测试

- [ ] T04 实现搜索弹窗
  - 依赖:T02、T03
  - 验证:鼠标和键盘均可操作

- [ ] T05 接入顶部导航并适配移动端
  - 依赖:T04
  - 验证:检查两种断点

- [ ] T06 执行全量验证
  - 依赖:T01-T05
  - 验证:测试、构建、页面检查

好的任务有清楚的完成边界。它可以独立执行,完成后能验证,并且不会让 Agent 在实现过程中重新设计整个功能。

任务也不必机械地按文件拆分。一个垂直切片可以同时修改数据、逻辑和界面,只要它能形成可验证的增量。按照技术层批量完成“所有后端任务”再做“所有前端任务”,很容易把集成问题拖到最后。

七、Checklist 与 Analyze:实现前再做一次静态检查

进入代码阶段前,可以对文档本身做检查。

Checklist 检查规格质量:

每条需求是否可以验证?
空状态和失败状态是否定义?
权限、兼容性和性能边界是否覆盖?
范围外内容是否明确?

Analyze 检查工件之间的一致性:

每条 Spec 是否有 Plan 对应?
每项 Plan 是否被 Tasks 覆盖?
Tasks 是否包含验证工作?
Constitution 与 Plan 是否冲突?
接口契约是否被提供方和消费方共同引用?

这一步很像对自然语言做静态分析。它无法证明方案正确,却能提前发现遗漏、重复和矛盾。Spec Kit 将 Checklist 和 Analyze 都设计为可选步骤,大功能和跨团队变更更值得使用。

八、Implement:按任务推进,也允许证据改变计划

Implement 阶段才开始修改代码。Agent 每次领取一个或一组相邻任务,读取相关 Spec 与 Plan,检查真实代码后完成实现和验证。

一个稳妥的执行循环是:

选择未完成任务
  ↓
读取需求、方案和相关代码
  ↓
实现最小改动
  ↓
运行该任务的局部验证
  ↓
记录结果并更新任务状态
  ↓
进入下一任务

实现会暴露计划阶段无法知道的信息。比如浏览器端加载搜索索引后体积过大,或者 Astro 的路由行为与方案假设不同。此时应先判断这是实现细节还是方案变化。

实现细节可以直接记录在任务中。影响数据结构、用户行为或验收标准的发现,需要更新 Plan 或 Spec,再继续编码。否则仓库会出现两套事实:文档描述一种系统,代码运行另一种系统。

SDD 给 Agent 提供了稳定计划,也必须允许工程证据修正计划。把初稿视为不可修改的合同,只会让错误更有秩序地执行下去。

九、Converge:代码完成后检查意图是否兑现

所有任务打勾,只能说明任务列表执行完了。Converge 会回到最初意图,对比五类材料:

Spec       承诺了什么行为
Plan       选择了什么方案
Tasks      安排了哪些工作
Code       实际实现了什么
Evidence   测试和运行结果证明了什么

搜索功能完成后,可以逐项检查:

  • Spec 要求搜索标题、摘要和标签,匹配逻辑是否全部覆盖;
  • Spec 排除了草稿,索引生成和测试是否共同保证;
  • Plan 要求按需加载,页面首次加载是否真的没有下载索引;
  • Constitution 要求键盘可用,焦点移动和 Escape 关闭是否验证;
  • Tasks 要求移动端检查,是否留下了检查结果。

发现缺口后不要只写一段总结。把剩余工作追加回 Tasks,继续实现并验证,直到没有影响验收的差异。Spec Kit 当前将 converge 放在核心流程末端,用它评估代码库与 Spec、Plan、Tasks 的差异并补充任务。

这一步为 SDD 闭环。没有 Converge,流程容易退化成“开发前多写几份文档”。

SDD 各类工件分别约束什么

十、规格应该保留多久

功能上线后,团队还要决定这些文件的生命周期。Spec Kit 的 Spec Persistence 文档列出了几种常见选择。

Spec-first

Spec 用于本次开发,完成后可以归档。它适合一次性功能或低维护成本项目。优点是负担小,缺点是下一次修改可能重新从代码推断意图。

Spec-anchored

Spec 在实现后继续保留,后续修改需要参考它。旧 Spec 记录功能为何这样设计,新需求可以创建新的变更规格。这种方式适合希望保留决策历史的团队。

Spec-as-source

Spec 成为长期权威来源,Plan、Tasks 甚至部分实现都由它派生。需求变化先修改 Spec,再同步下游工件。它提供更强一致性,也要求团队投入持续维护。

还有一个实际问题:实现发现是否反向更新已有工件。小团队常采用 Flow-back,允许 Spec、Plan、Tasks 和代码互相修正,最后人工对齐。重视审计的团队可能采用 Flow-forward,保留已经完成的工件,通过新目录记录下一次变更。

没有一种模式适合所有项目。重要的是明确哪份材料在冲突时拥有更高权威,以及谁负责消除漂移。

十一、大功能需要拆成多个 Spec

如果一个 Spec 需要 Agent 连续执行几十个任务,模型仍然会在中途失去重点。大功能可以先建立 Roadmap,再把每个切片运行一遍完整 SDD 流程。

R1 生成搜索索引
R2 完成搜索交互
R3 增加正文搜索
R4 增加搜索分析

每个切片拥有独立的 spec.md、plan.md 和 tasks.md,Roadmap 只保存边界、依赖和进度。GitHub 将这种方法称为 Spec of Specs。它适合单个 Feature 已经超过上下文和审查能力的情况。

切分时要保证每一部分能够独立验收。只把一个大任务按代码层拆成数据库、后端、前端三个 Spec,会让每个 Spec 都缺少可见结果。按照用户能力或完整业务路径切分通常更容易收敛。

十二、SDD 与 TDD、BDD、Skills 的关系

它们处理不同层次的问题,可以组合使用。

方法 主要管理什么 常见产物
SDD 需求、边界、技术方案和任务一致性 Spec、Plan、Tasks、验证记录
BDD 用户可观察的业务行为 Given / When / Then 场景
TDD 小步实现和代码设计反馈 先失败再通过的自动化测试
Skills Agent 完成某类任务时遵守的操作方法 触发条件、步骤、工具和验收规则

一个项目可以用 SDD 确定“博客搜索应该提供哪些行为”,用 BDD 写出搜索与空状态场景,用 TDD 实现匹配和排序,再用 Skill 要求 Agent 每次修改搜索功能都运行指定测试与页面检查。

Skills 还可以把 SDD 流程本身封装起来。例如一个 feature-development Skill 规定:先检查 Constitution,再生成 Spec,列出未决问题,得到确认后生成 Plan 与 Tasks,完成实现后运行 Converge。这样流程从团队约定变成 Agent 可以重复执行的操作。

十三、SDD 与知识库的关系

知识库告诉 Agent 项目里有什么,SDD 告诉它这次准备改变什么。

开发一个已有系统时,Plan 阶段需要查询代码知识库:现有入口在哪、哪些接口可以复用、调用链经过哪些模块。Repo Map 和 FastCode 帮助定位代码,LLM Wiki 提供架构与业务说明,RAG 找到历史决策和故障记录。

检索到的知识进入 Plan 后,会变成本次开发的明确约束。例如知识库找到“优惠券返还必须幂等”,Plan 就应该写出幂等方案,Tasks 则需要安排重复消息测试。

两套系统可以这样配合:

知识库提供项目事实
        ↓
SDD 把事实与新需求整理成开发计划
        ↓
Coding Agent 按计划修改并验证
        ↓
实现结果和新决策回到知识库

这也是我们从“RAG 与知识库”转向“AI Coding”后很自然的一步。前者管理模型能够获得的知识,后者管理人怎样利用这些知识驱动 Agent 开发。

十四、哪些任务值得使用完整 SDD

完整流程适合:

  • 跨越多个文件和模块的功能;
  • 会改变 API、数据结构或用户行为的修改;
  • 有多种技术方案,需要先做取舍;
  • 需要多人或多个 Agent 交接;
  • 实现周期超过一次对话;
  • 上线前需要明确审计和验收证据。

改错别字、调整一个确定的 CSS 值、修复原因明确且影响局部的 Bug,没有必要生成完整工件。可以使用缩短版:

目标与边界
  → 修改计划
  → 实现
  → 验证

流程长度应当与不确定性匹配。任务越小、相关代码越明确,前置工件越轻;需求越模糊、影响面越大,越需要先固定意图。

十五、几个常见失败方式

1. 把 Spec 写成愿望清单

“体验流畅”“架构合理”“保证高性能”很难验证。每条要求都应该能够被测试、观察或评审。

2. 在 Spec 中提前写死实现

Spec 规定用户行为,Plan 选择实现方式。过早指定框架、类名和目录,会让后续调研失去意义,也会把技术细节误当成产品要求。

3. 一次生成全部文档,不做人工确认

Agent 可以连续生成 Spec、Plan 和 Tasks,也能把同一个错误复制三遍。关键边界和技术取舍需要在阶段之间确认。

4. Tasks 只有动作,没有验证

“实现搜索组件”没有明确终点。任务需要写清对应需求、修改范围和完成证据。

5. 实现变化没有回写

代码因为工程现实改变,Plan 仍保留旧方案。后续 Agent 会把旧文档当成事实。Converge 必须处理这类漂移。

6. 把流程当成固定仪式

所有任务都走同样长度的流程,会产生大量无人维护的 Markdown。SDD 的价值来自减少不确定性,不来自文件数量。

十六、一套适合个人项目的最小流程

个人项目不需要一开始就安装完整工具。可以在仓库里创建:

docs/features/<feature-name>/
├── spec.md
├── plan.md
└── tasks.md

然后给 Coding Agent 一份稳定指令:

处理新功能时:

1. 阅读项目规则和相关代码。
2. 先生成 spec.md,只写行为、边界和验收条件。
3. 列出需要用户确认的问题。
4. 确认后生成 plan.md,说明影响范围、方案和验证方法。
5. 将计划拆成带依赖和验证步骤的 tasks.md。
6. 每次只执行少量任务,并更新状态。
7. 完成后对照 Spec、Plan、Tasks 和测试结果检查差异。
8. 有缺口就追加任务,全部验收后再提交。

这套最小流程已经具备 SDD 的主要价值。等项目和协作规模扩大,再引入 Spec Kit 的 Constitution、Checklist、Analyze、Converge、扩展和预设机制。

十七、我对 SDD 的理解

SDD 可以看成开发意图的编译过程。

自然语言需求含有大量省略。Specify 把它整理成外部行为,Plan 将行为映射到当前系统,Tasks 把方案切成可执行增量,Implement 产生代码与测试,Converge 再用证据检查实现是否兑现意图。

模型生成代码的速度越快,这层约束越重要。以前需求模糊可能让开发者浪费几天,现在 Agent 可以在半小时内生成一套方向错误却结构完整的实现。SDD 没有让模型变得确定,它只是把关键决定从模型的临时推断中拿出来,放进可以检查、修改和追踪的工件里。

对于个人开发者,我认为它最实用的价值是跨会话连续性。一次会话把需求澄清并完成 Plan,下一次会话读取 Tasks 继续实现,几天后仍能知道某个决定从哪里来。聊天不再承担项目记忆,代码也不再是唯一能够解释系统的材料。

参考资料