理解 Spec-Driven-Development:Kiro、spec-kit 和 Tessl
Birgitta 是 Thoughtworks 的杰出工程师(Distinguished Engineer)和 AI 辅助交付专家。她拥有超过 20 年的软件开发、架构师和技术领导者经验。
本文是”探索生成式 AI(Exploring Gen AI)“系列的一部分。该系列记录了 Thoughtworks 技术专家探索将生成式 AI(gen ai)技术用于软件开发的历程。
2025 年 10 月 15 日
我一直在尝试理解最新的 AI 编码流行语之一:Spec-driven development(SDD,规格驱动开发)。我研究了三个将自己标榜为 SDD 工具的工具,并尝试梳理截至目前它意味着什么。
与这个快节奏领域中的许多新兴术语一样,“spec-driven development”(SDD,规格驱动开发)的定义仍在变化中。以下是我从目前所见的使用方式中收集到的信息:Spec-driven development 意味着在使用 AI 编写代码之前先编写一个”spec”(规格)(“文档优先”)。该 spec 成为人类和 AI 的真实来源(source of truth)。
GitHub:“在这个新世界中,维护软件意味着演进规格。[…] 开发的通用语言(lingua franca)转移到了更高的层次,代码是最后一英里(last-mile)的方法。”
Tessl:“一种开发方法,其中规格(specs)——而非代码——是主要产物(artifact)。规格用结构化、可测试的语言描述意图,AI 代理(agents)生成代码来匹配它们。”
在研究了该术语的用法以及一些声称实现 SDD 的工具后,在我看来,实际上存在多个实现层次:
- Spec-first(规格优先):先编写一个经过深思熟虑的 spec,然后在 AI 辅助开发工作流中用于当前任务。
- Spec-anchored(规格锚定):任务完成后仍保留 spec,继续用于相应功能的演进和维护。
- Spec-as-source(规格即源码):随着时间的推移,spec 是主要的源文件,只有 spec 由人类编辑,人类从不触碰代码。
我找到的所有 SDD 方法和定义都是 spec-first,但并非所有都力求成为 spec-anchored 或 spec-as-source。而且,随着时间的推移,spec 维护策略应该是什么,往往被模糊处理或完全开放。
什么是 spec?
Section titled “什么是 spec?”就定义而言,关键问题当然是:什么是 spec?似乎没有一个通用的定义,我见过的最接近一致定义的是将 spec 比作”产品需求文档(Product Requirements Document)”。
目前这个术语被过度使用(overloaded)了,以下是我尝试定义 spec 的内容:
spec 是一个结构化的、面向行为的产物(artifact)——或一组相关的产物——用自然语言编写,表达软件功能并作为 AI 编码代理(AI coding agents)的指导。每种 spec-driven development 的变体都定义了它们对 spec 结构、详细程度以及这些产物如何在项目中组织的方法。
我认为 specs 与代码库更通用的上下文文档之间存在有用的区别。那些通用上下文是诸如规则文件,或产品和代码库的高级描述之类的内容。一些工具称这种上下文为 memory bank(记忆库),所以我在这里就这么称呼。这些文件与代码库中的所有 AI 编码会话都相关,而 specs 仅与实际上创建或更改特定功能的任务相关。
评估 SDD 工具的挑战
Section titled “评估 SDD 工具的挑战”事实证明,以接近实际使用的方式评估 SDD 工具和方法非常耗时。你必须用不同规模的问题来尝试它们,包括绿地(greenfield,全新项目)和棕地(brownfield,现有项目),并真正花时间审查和修订中间产物,而不仅仅是粗略浏览。因为正如 GitHub 关于 spec-kit 的博客文章所说:“至关重要的是,你的角色不仅仅是引导。而是验证。在每个阶段,你都要反思和完善。”
对于我尝试的三个工具中的两个,将它们引入现有代码库似乎需要更多的工作,因此使得评估它们对棕地代码库的有用性变得更加困难。在我听到人们在”真实”代码库上使用它们一段时间的使用报告之前,我仍然有很多关于这在现实生活中如何运作的开放性问题。
话虽如此——让我们来看看这三个工具。我将首先分享它们如何工作的描述(或者更确切地说,我认为它们如何工作),并将我的观察和问题留到最后。请注意,这些工具发展非常迅速,所以自从我在 9 月份使用它们以来,它们可能已经发生了变化。
Kiro 是我尝试的三个工具中最简单(或最轻量级)的一个。它似乎主要是 spec-first,我找到的所有示例都将其用于一个任务或用户故事(user story),没有提到如何在多个任务中随着时间的推移以 spec-anchored 方式使用需求文档。
工作流:需求(Requirements)→ 设计(Design)→ 任务(Tasks)
每个工作流步骤由一个 markdown 文档表示,Kiro 在其基于 VS Code 的分发版本中引导你完成这 3 个工作流步骤。
需求(Requirements):结构化为需求列表,其中每个需求代表一个”用户故事(User Story)“(采用”As a…”格式),带有验收标准(acceptance criteria,采用”GIVEN… WHEN… THEN…”格式)
设计(Design):在我的尝试中,设计文档包含下图中看到的部分。我只保留了一次尝试的结果,所以我不确定这是否是一个一致的结构,还是它会根据任务而变化。
任务(Tasks):一个任务列表,可以追溯回需求编号,并获得一些额外的 UI 元素来逐个运行任务,并审查每个任务的更改。
Kiro 还有一个 memory bank(记忆库)的概念,他们称之为”steering”(引导)。其内容是灵活的,他们的工作流似乎不依赖于任何特定文件的存在(我在发现 steering 部分之前就进行了使用尝试)。当你要求 Kiro 生成 steering 文档时,它创建的默认拓扑是 product.md、structure.md、tech.md。
Spec-kit
Section titled “Spec-kit”Spec-kit 是 GitHub 的 SDD 版本。它作为 CLI(命令行界面)分发,可以为各种常见的编码助手创建工作区设置。一旦设置了该结构,你就可以通过编码助手中的斜杠命令(slash commands)与 spec-kit 交互。因为它的所有产物都直接放入你的工作区,所以这是此处讨论的三个工具中可定制性最强的一个。
工作流:章程(Constitution)→ 𝒢 指定(Specify)→ 计划(Plan)→ 任务(Tasks)𝒢
Spec-kit 的 memory bank(记忆库)概念是 spec-driven 方法的前提条件。他们称之为 constitution(章程)。章程应该包含”不可变的(immutable)“高级原则,并且应该始终应用于每个更改。它基本上是一个非常强大的规则文件,被工作流大量使用。
在每个工作流步骤(指定、计划、任务)中,spec-kit 借助 bash 脚本和一些模板实例化一组文件和提示(prompts)。然后工作流大量使用文件内的检查清单(checklists),来跟踪必要的用户澄清、章程违规、研究任务等。它们就像是每个工作流步骤的”完成定义(definition of done)“(尽管由 AI 解释,所以不能 100% 保证它们会被遵守)。
下面是我用来概述我在 spec-kit 中看到的文件拓扑的概览。注意一个 spec 是如何由许多文件组成的。
乍一看,GitHub 似乎致力于 spec-anchored 方法(“这就是为什么我们正在重新思考规格——不是作为静态文档,而是作为与项目一起演进的、活的可执行产物。规格成为共享的真实来源。当某些事情没有意义时,你回到 spec;当项目变得复杂时,你完善它;当任务感觉太大时,你分解它们。“)然而,spec-kit 为创建的每个 spec 创建一个分支,这似乎表明他们将 spec 视为变更请求生命周期内的活产物,而不是功能生命周期内的活产物。这个社区讨论正在谈论这种困惑。这让我认为 spec-kit 仍然是我所说的仅 spec-first,而不是随着时间的推移 spec-anchored。
Tessl Framework
Section titled “Tessl Framework”(仍处于私有测试阶段)
与 spec-kit 一样,Tessl Framework 作为 CLI 分发,可以为各种编码助手创建所有工作区和配置结构。CLI 命令还兼作 MCP 服务器。
Tessl 是这三个工具中唯一一个明确致力于 spec-anchored 方法,甚至正在探索 SDD 的 spec-as-source 级别的工具。Tessl spec 可以作为正在维护和编辑的主要产物,代码甚至在顶部用注释标记为 // GENERATED FROM SPEC - DO NOT EDIT(从规格生成——请勿编辑)。目前这是 spec 和代码文件之间的 1:1 映射,即一个 spec 转换为代码库中的一个文件。但 Tessl 仍处于测试阶段,他们正在尝试不同的版本,所以我可以想象这种方法也可以在一个 spec 映射到具有多个文件的代码组件的层面上采用。alpha 产品将支持什么还有待观察。(Tessl 团队自己认为他们的框架是比他们当前的公开产品 Tessl Registry 更未来的东西。)
// GENERATED FROM SPEC - DO NOT EDIT以下是我让 Tessl CLI 从现有代码库中的 JavaScript 文件逆向工程(tessl document —code …js)的一个 spec 示例:
tessl document --code ...js像 @generate 或 @tests 这样的标签似乎告诉 Tessl 要生成什么。API 部分展示了至少在 spec 中定义暴露给代码库其他部分的接口的想法,据推测是为了确保生成的组件的这些更关键部分完全在维护者的控制之下。为此 spec 运行 tessl build 会生成相应的 JavaScript 代码文件。
@generate@testtessl build将 spec-as-source 的 spec 放在相当低的抽象级别,每个代码文件,可能会减少 LLM 必须执行的步骤和解释数量,从而减少错误的机会。即使在这个低抽象级别,我也看到了非确定性(non-determinism)的实际作用,当我从同一个 spec 多次生成代码时。迭代 spec 并使其越来越具体以提高代码生成的可重复性是一个有趣的练习。这个过程让我想起了编写明确且完整的规范的一些陷阱和挑战。
这三个工具都将自己标榜为 spec-driven development 的实现,但它们彼此之间差异很大。所以在谈论 SDD 时要记住的第一件事是,它不是单一的东西。
一个工作流适合所有规模?
Section titled “一个工作流适合所有规模?”Kiro 和 spec-kit 各自提供一个有明确观点的工作流,但我相当确定它们都不适合大多数现实生活中的编码问题。特别是,我不太清楚它们如何适应足够多的不同问题规模以具有普遍适用性。
当我让 Kiro 修复一个小 bug(这是我过去用来尝试 Codex 的同一个 bug)时,很快就清楚这个工作流就像用大锤砸坚果。需求文档将这个小 bug 变成了 4 个”用户故事”,总共有 16 个验收标准,其中包括这样的宝石:“用户故事:作为一名开发人员,我希望转换函数能够优雅地处理边缘情况,以便在引入新的类别格式时系统保持稳健。”
我在使用 spec-kit 时也遇到了类似的挑战,我不太确定应该用它来解决什么规模的问题。可用的教程通常基于从头创建应用程序,因为这对教程来说最容易。我最终尝试的用例之一是一个功能,在我过去的团队中这将是一个 3-5 点的故事。该功能依赖于许多已经存在的代码,它应该构建一个概述模态框(overview modal),总结现有仪表板中的一堆数据。随着 spec-kit 采取的步骤数量,以及它为我创建的要审查的 markdown 文件数量,这再次感觉对于问题的规模来说是过度杀伤(overkill)。它比我用 Kiro 的问题更大,但也是一个更复杂的工作流。我甚至没有完成完整的实现,但我认为在运行和审查 spec-kit 结果所花的相同时间内,我本可以用”普通”AI 辅助编码实现该功能,而且我会感觉更有控制权。
一个有效的 SDD 工具至少必须为几种不同的核心工作流提供灵活性,以适应不同规模和类型的更改。
审查 markdown 而不是审查代码?
Section titled “审查 markdown 而不是审查代码?”正如刚才提到的,正如你在上面的工具描述中看到的那样,spec-kit 为我创建了大量的 markdown 文件来审查。它们是重复的,彼此之间以及与已经存在的代码之间都是如此。有些已经包含了代码。总的来说,它们非常冗长,审查起来很乏味。在 Kiro 中稍微容易一些,因为你只得到 3 个文件,而且更容易理解”需求 > 设计 > 任务”的心智模型(mental model)。然而,如前所述,Kiro 对于我要求它修复的小 bug 来说也太冗长了。
老实说,我宁愿审查代码也不愿审查所有这些 markdown 文件。一个有效的 SDD 工具必须提供非常好的 spec 审查体验。
虚假的控制感?
Section titled “虚假的控制感?”即使有所有这些文件、模板、提示、工作流和检查清单,我也经常看到代理最终没有遵循所有指令。是的,上下文窗口(context windows)现在更大了,这经常被提及为 spec-driven development 的推动因素之一。但仅仅因为窗口更大,并不意味着 AI 会正确理解其中的所有内容。
例如:Spec-kit 在计划期间的某个地方有一个研究步骤,它对现有代码和已经存在的内容做了很多研究,这很好,因为我要求它添加一个基于现有代码的功能。但最终代理忽略了这些是现有类描述的注释,它只是把它们当作新的规格并重新生成它们,创建了重复项。但我不仅看到了忽略指令的例子,我还看到代理因为过于热切地遵循指令而做得过火(例如,章程文章之一)。
过去表明,我们保持对所构建内容控制的最佳方式是小的、迭代的步骤,所以我非常怀疑大量的前期 spec 设计是否是一个好主意,特别是当它过于冗长时。一个有效的 SDD 工具必须适应迭代方法,但小的工作包几乎似乎与 SDD 的理念相悖。
如何有效地将功能规格与技术规格分离?
Section titled “如何有效地将功能规格与技术规格分离?”在 SDD 中,有意分离功能规格(functional spec)和技术实现(technical implementation)是一个常见的想法。我想潜在的追求是,最终我们可以让 AI 填充所有的解决方案和细节,并使用相同的规格切换到不同的技术栈。
在现实中,当我尝试 spec-kit 时,我经常困惑何时应该保持在功能级别,何时应该添加技术细节。教程和文档在这方面也不完全一致,似乎对”纯功能(purely functional)“的真正含义有不同的解释。当我回想起在我的职业生涯中读过的许多许多用户故事时,它们没有正确地分离需求和实现,我认为我们作为一个专业在这方面没有良好的记录。
目标用户是谁?
Section titled “目标用户是谁?”许多 spec-driven development 工具的演示和教程包括定义产品和功能目标之类的内容,它们甚至纳入了”用户故事(user story)“等术语。这里的想法可能是将 AI 作为跨技能(cross-skilling)的推动者,让开发人员更多地参与需求分析?或者让开发人员与产品人员配对当他们处理这个工作流时?然而,这些都没有明确说明,它被呈现为理所当然的,即开发人员将完成所有这些分析。
在这种情况下,我会再次问自己,SDD 旨在解决什么规模和问题类型?可能不是针对仍然非常不明确的大型功能,因为那肯定需要更专业的产品和需求技能,以及许多其他步骤,如研究和利益相关者参与?
Spec-anchored 和 spec-as-source:我们是否从过去吸取了教训?
Section titled “Spec-anchored 和 spec-as-source:我们是否从过去吸取了教训?”虽然许多人将 SDD 与 TDD 或 BDD 进行类比,但我认为对于特别是 spec-as-source 来说,另一个重要的并行是 MDD(model-driven development,模型驱动开发)。我在职业生涯初期参与过一些大量使用 MDD 的项目,当我尝试 Tessl Framework 时,我一直被提醒起这一点。MDD 中的模型基本上就是 specs,尽管不是用自然语言,而是用例如自定义 UML 或文本 DSL 表达。我们构建了自定义代码生成器,将这些 specs 转换为代码。
最终,MDD 从未在业务应用程序中取得成功,它处于尴尬的抽象级别,只是产生了太多的开销和约束。但 LLM 消除了 MDD 的一些开销和约束,所以有了新的希望,我们现在终于可以专注于编写 specs 并从中生成代码。使用 LLM,我们不再受预定义和可解析的 spec 语言的限制,也不必构建复杂的代码生成器。当然,这样做的代价是 LLM 的非确定性(non-determinism)。而且可解析的结构也有我们正在失去的优势:我们可以为 spec 作者提供大量的工具支持来编写有效、完整和一致的 specs。我想知道 spec-as-source,甚至 spec-anchoring,是否最终会得到 MDD 和 LLM 的缺点:不灵活性(inflexibility)和非确定性(non-determinism)。
要明确的是,我并不是怀念我过去的 MDD 经历并说”我们不妨把它带回来”。但当我们在今天探索 spec-driven 时,我们应该回顾过去的从规格生成代码的尝试,从中吸取教训。
在我个人使用 AI 辅助编码时,我也经常花时间仔细制作某种形式的 spec 首先提供给编码代理。所以 spec-first 的一般原则在许多情况下绝对是有价值的,关于如何构建该 spec 的不同方法是非常受欢迎的。它们是我目前从从业者那里听到的最常被问到的问题之一:“我如何构建我的 memory bank?”,“我如何为 AI 编写一个好的规格和设计文档?”。
但术语”spec-driven development”还没有很好的定义,而且它已经语义扩散(semantically diffused)。我最近甚至听到人们基本上将”spec”用作”detailed prompt”(详细提示)的同义词。
关于我已经尝试过的工具,我在这里列出了许多关于它们现实世界有用性的问题。我想知道它们中的一些是否过于字面地将我们现有的工作流喂给 AI 代理,最终放大了现有的挑战,如审查过载和幻觉(hallucinations)。特别是对于创建大量文件的更复杂的方法,我忍不住想起德语复合词”Verschlimmbesserung”:我们是否在试图改进某事时让它变得更糟?
最新文章(3 月 04 日):
Humans and Agents in Software Engineering Loops(软件工程循环中的人类和代理)
上一篇文章:
Anchoring AI to a reference application(将 AI 锚定到参考应用程序)
下一篇文章:
Assessing internal quality while coding with an agent(使用代理编码时评估内部质量)