规格驱动开发:人类与AI的合约

目录 [−]

  1. 3.1 规格驱动从何而来
  2. 3.2 OpenSpec:轻量级 SDD 的代表
    1. 3.2.1 设计哲学:五句话
    2. 3.2.2 安装与初始化
    3. 3.2.3 核心工作流:propose → apply → archive
    4. 3.2.4 扩展工作流
    5. 3.2.5 OpenSpec 的边界:它不做什么
    6. 3.2.6 实战:用 OpenSpec 生成贪吃蛇游戏
  3. 3.3 GitHub Spec-Kit:重量级 SDD 的基建
    1. 3.3.1 设计哲学:以 constitution.md 为纲
    2. 3.3.2 核心流程:五步法+三个可选步骤
    3. 3.3.3 Spec-Kit 中文增强版:spec-kit-zh
    4. 3.3.4 实战:用 Spec-Kit 生成贪吃蛇游戏
  4. 3.4 AWS Kiro:SDD 内置的 IDE
    1. 3.4.1 三种工作流:覆盖开发的全场景
    2. 3.4.2 Steering Files:Agent 的持久记忆
    3. 3.4.3 Kiro 的位置:重量级 SDD 的企业端
    4. 3.4.4 实战:用 Kiro 生成贪吃蛇游戏
  5. 3.5 SDD 的关键原则
    1. 3.5.1 规格先行,代码在后
    2. 3.5.2 规格是活文档
    3. 3.5.3 规格粒度适中
    4. 3.5.4 规格必须可验证
    5. 3.5.5 规格应与平台和模型解耦
  6. 3.6 三个工具的对比
  7. 3.7 本章小结

"The specification is the source of truth, and code derives from it — not the other way around."
规格是真理的来源,代码派生自规格——而不是反过来。

——Deepak Babu Piskala, 《Spec-Driven Development: From Code to Contract in the Age of AI Coding Assistants》, 2026 年

Skill 是 AI Agent 的能力单元:一个 Markdown 文件定义一种行为,小而可组合,可复用可迭代。但多个 Skill 组合在一起时,它们之间的"合约"是什么?谁来保证 /tdd 写的测试和 /grill-with-docs 对齐的需求是同一件事?

这就是规格驱动开发(Spec-Driven Development,SDD)要解决的问题。如果 Skill 是原子能力,那么 Spec 就是这些能力之间的接口协议。它不定义"怎么做",而是定义"做成什么样才算对"。

三条线索:SDD 的思想史(根源比 AI 编码工具早得多,但 AI 让它从学院派理想变成了工程必需品);三个代表性工具的逐层分析(OpenSpec、GitHub Spec-Kit、AWS Kiro);跨工具的通用原则。

三个线索汇聚到一句:规格不是在浪费时间写文档——规格是你和 AI 之间最有效率的通信协议。

3.1 规格驱动从何而来

SDD 的思想根源比 AI 编码工具古老得多。它的前身可以追溯到四条工程实践线。

TDD(Kent Beck,1990 年代末)的核心洞见——测试本身就是一个可执行的规格。写完 expect(prefs.theme).toBe('system'),你实际上在声明:程序行为必须满足这个断言。契约式设计(Bertrand Meyer,1986)把模块之间的关系定义成契约——前置条件、后置条件、不变式。精确、可验证、不可含糊,这三个要素至今是 SDD 的质量标准。BDD(Dan North, 2003)把测试语言翻译成行为语言 Given-When-Then,开发者、测试者、业务分析师都能读同一份文档。形式化方法从 Hoare 逻辑到 TLA+,始终在回答一个问题:实现是否满足规格,能不能被证明?它没成为主流工程实践,但钉住了一个标准——好规格必须是可验证的。

这四条线各自独立跑了几十年,直到撞上一个共同的催化剂:AI 编码 Agent 能写代码了,但它不会读心。

2025 年是"Vibe Coding"元年——Karpathy 造出这个词之后,会写自然语言就能生出一个可运行的应用。但解放的另一面是失控。AI 写得极快,质量极不可靠——产物是"看起来能跑、生产环境里一碰就碎"的代码浆糊,不可测试、不可重构、不可理解。

2026 年风向变了。GitHub Spec Kit、OpenSpec、Kiro 三个工具同时爆发,GitHub Universe 和 AWS re:Invent 为 SDD 背书,半年内超过 20 个 AI 编码工具内置了规格驱动工作流。同年 1 月,arXiv 上的一篇论文给出了第一个系统化的 SDD 定义,并引用了受控实验数据:人工提炼过的规格可以让 LLM 生成代码的错误率降低高达 50%。

SDD 从四条各自独立的工程线索,被 AI 催化成了一个完整的范式。

3.2 OpenSpec:轻量级 SDD 的代表

OpenSpec 由 Fission-AI 团队开发,2025 年中首次发布,MIT 协议。GitHub 上 46,000+ Star,25 个以上的 AI 编码工具原生支持——从 Claude Code、Codex 到 Cursor、Windsurf、Copilot。

它的 GitHub 描述行简短:"Spec-driven development for AI coding assistants."

3.2.1 设计哲学:五句话

OpenSpec 的设计哲学可以归结为五句话:

  1. Fluid, not rigid(流动而非僵化)。OpenSpec 不定义刚性阶段门。你不必严格按"需求→设计→任务→实现"的顺序线性推进。你可以在设计到一半时回到需求修改,可以在实现到一半时推翻设计。规格是活的——它和你对问题的理解同步演进。

  2. Iterative, not waterfall(迭代而非瀑布)。OpenSpec 的工作单元是一个"变更"(Change)——对应于一个功能、一个修复、或一个改进。每个变更独立地走 propose → apply → archive 三阶段。没有全局阶段的约束——三个变更可以同时处于不同的阶段。

  3. Easy, not complex(简单而非复杂)。安装只需要 Node.js 20+ 和一句 npm install -g @fission-ai/openspec(详见 3.2.2 节)。没有 Python 环境、没有数据库、没有配置文件。OpenSpec 的全部状态都以 Markdown 文件的形式存储在项目仓库的 openspec/ 目录下。

  4. Built for brownfield, not just greenfield(为存量项目而生,不只为新项目)。这是 OpenSpec 最核心的差异点。大多数规格工具假设你从空项目开始——写一份规格,按规格实现。但现实中的多数项目是存量项目——已经有几万到几十万行代码,现在要往里加功能。你没法为了加一个暗黑模式就重写整个前端的规格。

OpenSpec 用 Delta Specs(增量规格) 应对这个场景。不需要重新描述整个系统的行为——只标记你改变了什么:

1
2
3
4
5
6
7
8
## ADDED Requirements
- Theme system supports `light`, `dark`, and `system` modes

## MODIFIED Requirements
- Navbar component: theme prop is now required instead of optional

## REMOVED Requirements
- (none)

每个变更文件夹只包含相对于当前规格的变化。变更归档时,这些增量自动合并到主规格库中。这个设计让 SDD 可以在存量项目中渐进式地引入——今天为一个新功能写一份变更规格,明天为另一个功能再写一份。不需要一次性铺开。

  1. Scalable(可伸缩)。从个人项目到企业级。单人开发者用三句核心命令(/opsx:propose/opsx:apply/opsx:archive)走完闭环。企业团队用 /opsx:verifyopenspec validate --strict 将规格验证嵌入 CI。

3.2.2 安装与初始化

OpenSpec 是一套发布在 npm 上的 CLI 工具,包名为 @fission-ai/openspec,MIT 协议开源。GitHub 仓库 Fission-AI/OpenSpec,官方网站 openspec.dev

前置要求:Node.js ≥ 20.19.0。 检查当前版本:

1
node --version

安装 CLI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# npm(推荐)
npm install -g @fission-ai/openspec@latest

# pnpm
pnpm add -g @fission-ai/openspec@latest

# yarn
yarn global add @fission-ai/openspec@latest

# bun(仍需 Node.js 20.19.0+ 在 PATH 中)
bun add -g @fission-ai/openspec@latest

# nix(直接运行,无需安装)
nix run github:Fission-AI/OpenSpec -- init

验证安装成功:

1
openspec --version

初始化项目:

进入项目目录,运行 openspec init

1
2
cd your-project
openspec init

openspec init 会交互式引导你完成三件事:

  1. 选择 AI 编码工具——Claude Code、Cursor、Codex、Copilot、Windsurf、Gemini CLI、Cline、RooCode、Amazon Q 等 25+ 工具,OpenSpec 会为选定的工具自动配置对应的 slash 命令
  2. 选择 Profile——默认 core(包含 5 个核心命令);可切换为扩展 profile(包含 11 个命令)
  3. 配置项目上下文——技术栈、编码规范、项目描述等

初始化完成后,项目根目录下生成 openspec/ 目录及对应工具的配置文件。

目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
openspec/
├── specs/ ← 累积的项目规格知识库(系统真值)
│ └── <domain>/
│ └── spec.md
├── changes/ ← 活动变更(每个功能一个文件夹)
│ └── <change-name>/
│ ├── proposal.md
│ ├── design.md
│ ├── tasks.md
│ └── specs/ ← 增量规格(ADDED/MODIFIED/REMOVED)
│ └── <domain>/
│ └── spec.md
└── config.yaml ← 项目配置(可选)

更新 CLI:

1
npm install -g @fission-ai/openspec@latest

更新后,在每个项目中刷新 Agent 指令:

1
openspec update

3.2.3 核心工作流:propose → apply → archive

OpenSpec 是三阶段工作流,足够简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/opsx:propose add-dark-mode


┌─────────────────────────────────┐
│ openspec/ │
│ ├── changes/ │
│ │ └── add-dark-mode/ │
│ │ ├── proposal.md ← 为什么要做、影响什么
│ │ ├── specs/ ← 增量规格(ADDED/MODIFIED/REMOVED)
│ │ ├── design.md ← 技术方案(可选)
│ │ └── tasks.md ← 可逐个验证的任务列表
│ └── specs/ ← 累积的项目规格知识库
│ ├── theme-system.md │
│ └── navigation.md │
└─────────────────────────────────┘


/opsx:apply → 逐任务实现,打勾 tasks.md


/opsx:archive → 归档到 changes/archive/,增量合并到 specs/

/opsx:propose 是生成阶段。你告诉 AI 你要什么——可能是半句话的描述,也可能是一段详细的背景。AI 会向你提问来澄清歧义(类似 /grill-me 的机制,但没有那么穷举式),然后产出一个变更文件夹,包含:

  • proposal.md:为什么做这个变更,影响哪些现有模块,有哪些风险
  • specs/:增量规格,用 ADDED/MODIFIED/REMOVED 标记
  • design.md:技术实现方案(可选)
  • tasks.md:分解为可逐个验证的任务列表

/opsx:apply 是执行阶段。AI 读取 tasks.md,逐个任务实现,完成一个打勾一个。关键是一次只做一个任务——这是 Pocock 在第 2 章中强调的"小而可组合"原则在规格层面的映射。如果一个任务在实现过程中发现需要偏离设计,AI 会停下来向你报告,而不是默默修改导致漂移。

/opsx:archive 是收尾阶段。变更完成并通过验证后,整个变更文件夹被移到 changes/archive/,增量规格自动合并到主规格库中。主规格库永远反映的是系统的当前状态——不是某个时间点的快照。下一次 /opsx:propose 会基于最新的主规格库来生成新变更的增量。

这个工作流的巧思在归档即合并。未归档的变更堆在 changes/ 下,每一项都在提醒"还有未完成的事"。归档不只是移动文件夹——它是变更从"临时提案"切换为"系统真值"。Meyer 的契约式设计在这里以最轻量的方式落地:每次归档,系统的契约被正式更新一次。

3.2.4 扩展工作流

三阶段之外,OpenSpec 提供了一系列可选命令,覆盖更复杂的场景:

命令 功能 使用场景
/opsx:new 全流程初始化 项目首次接入 OpenSpec 时,建立目录结构和初始规格
/opsx:continue 继续未完成的变更 上次 apply 到一半上下文窗口耗尽了,接着做
/opsx:ff 快速跟进(Fast Forward) 小变更不需要走完整流程,一次性生成全部产物
/opsx:verify 验证一致性 检查实现是否与规格一致——对照 tasks.md 中打勾的项逐条验证
/opsx:sync 规格同步 从现有代码反向提取规格,更新规格库(逆向 Spec-First)
/opsx:bulk-archive 批量归档 多个变更并行开发完成,一次性归档并检测冲突
/opsx:onboard 新成员引导 生成项目的规格全景图,帮助新开发者(人或 AI)快速理解系统

/opsx:sync 是 OpenSpec 对"brownfield"承诺的技术兑现:可以先有代码,后有规格。对一个已有几万行代码的存量项目,运行 /opsx:sync 会让 AI 扫描代码库并反向生成初始规格库。这降低了 SDD 的入场成本——不需要为历史代码补写规格,AI 帮你做这件事。

/opsx:verifyopenspec validate --all --strict --json(CLI 版本)是 CI 集成的关键。前者在 Agent 会话中交互式逐任务验证;后者产出结构化的 JSON 和退出码,可以被 GitHub Actions 或 Jenkins 直接消费——实现与规格不符,CI 红灯。

3.2.5 OpenSpec 的边界:它不做什么

理解一个工具,也要理解它选择不做什么。OpenSpec 有四个明确的边界:

不强制阶段门。 OpenSpec 不会用"必须先写 design.md 才能开始实现"拦住开发者。它信任开发者的判断——如果变更简单到可以跳过设计,它不阻止。代价是如果跳过设计然后在实现中走错了方向,需要自己承担返工。

不做平台绑定。 OpenSpec 的规格是纯 Markdown 文件,不依赖任何特定 AI 工具的 API。同一个 openspec/ 目录可以无缝工作在 Claude Code、Codex、Cursor、Windsurf 之间。这是第 2 章中 Pocock 提出的"模型无关"原则在规格层面的表现。

不做自动代码生成。 OpenSpec 帮你定义"做什么"和"怎么做",但它不自己写代码。它把规格输出交给 AI 编码 Agent(通过 /opsx:apply)去执行。OpenSpec 的角色是"合约管理者",不是"代码生成器"。

不解决"人本身不会写规格"的问题。 所有 SDD 工具的终极瓶颈。工具可以提供模板、提出澄清问题、防止格式错误——前提是使用者具备将模糊需求转化为可验证标准的能力。OpenSpec 不会救一个描述不清楚自己需求的人——它会放大你,正如第 1 章中反复出现的那个判断。

这四个边界定义出了 OpenSpec 的定位:它处于轻量自由的一端。GitHub Spec-Kit 则站在另一端。

3.2.6 实战:用 OpenSpec 生成贪吃蛇游戏

下面用一个完整的例子走一遍 OpenSpec 的三阶段闭环。任务:生成一个在浏览器中可玩的贪吃蛇网页游戏。

第一步:/opsx:propose snake-game

你只需要一句话:

1
2
3
/opsx:propose 做一个贪吃蛇网页游戏。用方向键控制蛇的移动,吃到食物
后蛇变长、分数增加。撞到墙壁或自己的身体则游戏结束。纯前端实现,
HTML/CSS/JS 单文件。

Agent 不会直接开始写规格。它会先追问几个澄清问题:

Q1: 游戏网格大小你期望多少?我建议 20×20——适合桌面端,移动端也不会太小。

Q2: 蛇的移动速度?我推荐初始 150ms/步——有挑战但不至于太快。

Q3: 分数系统怎么设计?每个食物 10 分、显示当前分数和最高分?

Q4: 要不要加一个"重新开始"按钮?撞墙或咬到自己后弹出。

你逐一确认。Agent 随后在 openspec/changes/snake-game/ 下生成四个文件:

1
2
3
4
5
6
7
8
openspec/
├── changes/
│ └── snake-game/
│ ├── proposal.md ← 为什么做、影响范围、风险
│ ├── specs/
│ │ └── game-logic.md ← 增量规格(ADDED/MODIFIED/REMOVED)
│ ├── design.md ← 技术方案(可选)
│ └── tasks.md ← 可逐个验证的任务列表

proposal.md 长这样:

1
2
3
4
5
6
7
8
9
10
11
12
# Proposal: 贪吃蛇网页游戏

## Why
提供一个可直接在浏览器中游玩的贪吃蛇游戏,作为项目的前端交互能力验证。

## What Changes
- 新增 `snake-game.html`,单文件包含 HTML/CSS/JS
- 不引入任何外部依赖

## Impact
- 纯新增文件,不影响现有模块
- 无后端依赖,无构建步骤

specs/game-logic.md 中的增量规格只有 ADDED 部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
## ADDED Requirements

### 游戏核心
- 20×20 网格,蛇初始长度 3,位于网格中央
- 方向键控制移动方向(上/下/左/右)
- 蛇不能反向移动(如向右时不能立刻向左)

### 食物系统
- 食物随机出现在空白格子,每次只有一个
- 蛇吃到食物后长度 +1,分数 +10
- 新食物立即在空白位置生成

### 游戏结束
- 蛇头触碰墙壁 → 游戏结束
- 蛇头触碰自身身体 → 游戏结束
- 游戏结束时显示最终得分和"重新开始"按钮

### 非功能需求
- 游戏循环间隔 150ms
- 最高分存储在 localStorage,刷新后保留
- 页面在 500ms 内完成首次渲染

tasks.md 把实现拆成四个独立可验证的任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Tasks

## 1. HTML 结构与画布渲染
- [ ] 创建 20×20 网格的 Canvas 元素
- [ ] 实现 `drawGrid()` 渲染网格线
- [ ] 实现 `drawSnake()` 渲染蛇身(绿色方块)
- [ ] 实现 `drawFood()` 渲染食物(红色方块)
- **验证**:打开页面看到空网格

## 2. 蛇的移动与键盘控制
- [ ] 实现方向状态管理(不允许反向)
- [ ] 实现 150ms 间隔的游戏循环
- [ ] 监听键盘方向键事件
- [ ] 实现蛇身移动逻辑(头前进、尾删除)
- **验证**:方向键控制蛇在网格中移动

## 3. 碰撞检测与食物逻辑
- [ ] 墙壁碰撞检测 → 游戏结束
- [ ] 自身碰撞检测 → 游戏结束
- [ ] 食物碰撞检测 → 蛇变长、分数+10、新食物生成
- [ ] 食物不生成在蛇身上
- **验证**:吃食物蛇变长,撞墙游戏结束

## 4. 分数系统与游戏状态
- [ ] 当前分数和最高分显示
- [ ] localStorage 读写最高分
- [ ] 游戏结束弹窗(显示分数 + 重新开始按钮)
- [ ] 重新开始重置所有状态
- **验证**:完整一局从开始到结束到重新开始

第二步:/opsx:apply

Agent 读取 tasks.md,从任务 1 开始逐个实现。每完成一个任务,它在 tasks.md 中打勾 [x],然后进入下一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Agent: 开始任务 1——HTML 结构与画布渲染。
[生成 snake-game.html,包含 Canvas 元素和 drawGrid/drawSnake/drawFood 函数]
打开浏览器预览 → 网格正常渲染。
tasks.md: 1. [x] HTML 结构与画布渲染

Agent: 开始任务 2——蛇的移动与键盘控制。
[添加方向状态、游戏循环、键盘监听]
测试:方向键控制蛇移动 → 正常。
tasks.md: 2. [x] 蛇的移动与键盘控制

Agent: 开始任务 3——碰撞检测与食物逻辑。
[在游戏循环中加入碰撞检测,实现食物生成与消费]
测试:吃食物蛇变长,撞墙结束 → 正常。
tasks.md: 3. [x] 碰撞检测与食物逻辑

Agent: 开始任务 4——分数系统与游戏状态。
[添加分数面板、localStorage 读写、结束弹窗、重新开始按钮]
测试:完整一局正常 → 正常。最高分刷新后保留 → 正常。
tasks.md: 4. [x] 分数系统与游戏状态

四个任务全部打勾,apply 阶段结束。

第三步:/opsx:archive snake-game

1
/opsx:archive snake-game

Agent 执行三个动作:

  1. changes/snake-game/ 整体移到 changes/archive/snake-game/
  2. specs/game-logic.md 中的 ADDED 增量合并到主规格库 openspec/specs/game-logic.md
  3. 输出归档摘要:"snake-game 已归档。主规格库中的 game-logic.md 已更新,新增 12 条行为规格。"

自此,贪吃蛇游戏的完整规格永久留在了项目的 openspec/specs/ 目录中。六个月后如果有人要重构它——比如把 Canvas 换成 DOM 渲染——规格告诉 TA 游戏应该怎么工作,不管实现层怎么变。

这就是 OpenSpec 的增量变更机制的意义。重构时不需要重新定义"游戏行为"——只需要声明"渲染层实现方式变了,行为不变"。流程如下:

第一步:/opsx:propose snake-dom-render

描述变更内容,明确声明这是一个修改而非新增:

1
2
3
/opsx:propose 将贪吃蛇游戏的渲染层从 Canvas 换为 DOM div 网格。
游戏行为不变——所有游戏逻辑、碰撞检测、分数系统的行为
保持与现有规格一致。仅替换渲染实现。

Agent 检测到主规格库 openspec/specs/ 中已存在 game-logic.md,自动识别被影响的 capability,生成变更文件夹:

1
2
3
4
5
6
openspec/changes/snake-dom-render/
├── proposal.md ← "Why: 提高语义化、可访问性"
├── specs/
│ └── game-logic.md ← 增量规格(只写变更部分)
├── design.md ← "DOM 网格方案替代 Canvas API"
└── tasks.md ← 四个迁移任务

specs/game-logic.md 的增量规格自动标注为 MODIFIED,只写变更部分,保留所有原有行为申明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## MODIFIED Requirements

### 渲染层
- ~~使用 Canvas API 的 drawGrid/drawSnake/drawFood 函数~~
- 使用 DOM div 元素渲染 20×20 网格,每个格子一个 `<div>` 节点
- 蛇身格子使用 `.snake` CSS class,食物格子使用 `.food` CSS class
- 渲染性能:单帧重绘 < 50ms,保持 150ms 游戏循环不受影响

## RETAINED Requirements
以下行为申明保持不变(来自主规格库 game-logic.md):

- 20×20 网格,蛇初始长度 3,位于网格中央
- 方向键控制移动方向(上/下/左/右)
- 蛇不能反向移动
- 食物随机出现在空白格子,每次只有一个
- 蛇吃到食物后长度 +1,分数 +10
- 撞墙或自碰 → 游戏结束
- 最高分存储在 localStorage
- 游戏循环间隔 150ms
- 首次渲染 < 500ms

第二步:/opsx:apply

Agent 逐任务执行。关键约束:每完成一个任务,Agent 对照 MODIFIED + RETAINED 双清单验证——新实现同时满足变更要求和保留行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Agent: 开始任务 1——DOM 网格结构。
将 Canvas 替换为 20×20 div 网格,每格添加 .cell class。
✓ 网格渲染正常,原有行为未受影响

Agent: 开始任务 2——DOM 渲染函数。
实现 renderGridDom()/renderSnakeDom()/renderFoodDom()。
✓ 蛇移动正常,食物生成正常,原有行为未受影响

Agent: 开始任务 3——替换渲染调用。
gameLoop 中 Canvas 调用全部替换为 DOM 调用。
✓ 碰撞检测正常,分数系统正常,原有行为未受影响

Agent: 开始任务 4——无障碍与样式。
添加 aria-label、键盘焦点、网格线样式。
✓ 原有路径测试全部通过,game-logic.md 中的 RETAINED 行为均验证通过

第三步:/opsx:archive snake-dom-render

归档时,OpenSpec 将 MODIFIED 部分合并到主规格库 openspec/specs/game-logic.md——渲染层描述从 Canvas 更新为 DOM,RETAINED 行为不变。规格仍然是系统的准确真值。

归档即合并的威力:每次归档,规格被正式更新一次,而非重新写一次。 重构不是一次重新写规格的过程,而是一次更新规格中一个子集的过程。规格保留了所有未变行为的完整性——六个月前的游戏行为申明,六次重构后仍然可信。

整个流程用了三个命令、四个任务、一次归档。OpenSpec 的轻体现在此:它没有要求你写 constitution、没有强制阶段门、没有生成八份文档。它只做了一件事——在你写代码之前冻结了一份关于"完成意味着什么"的共识。


3.3 GitHub Spec-Kit:重量级 SDD 的基建

2025 年 9 月,GitHub 发布了名为 Spec-Kit 的开源工具包。它的定位简洁明确。GitHub 官方博客的原话:"Specifications don't serve code — code serves specifications."(规格不服务于代码——代码服务于规格。)

这句话翻转了传统的层级:规格才是源,代码是派生品。

Spec-Kit 在不到一年内积累了超过 92,000 个 GitHub Stars,30+ 个 AI 编码工具支持,近 100 个社区扩展。2026 年 5 月,微软在 Build 大会上为它安排了专门的培训模块和主题演讲。Thoughtworks 在 2026 年 4 月的技术雷达中将 Spec-Kit 列入"评估"环——他们的评价精准地总结了它的核心特征:"一种更重量级的方案,拥有刚性阶段门、大量的 Markdown 文档、Python 运行时。"

3.3.1 设计哲学:以 constitution.md 为纲

Spec-Kit 用六条原则定义了它的世界观:

规格是主,代码是仆。 这个表述比 OpenSpec 的"规格先行"更强硬。它隐含的推论是:如果代码和规格不一致,永远是代码错了。

先定宪法,再写代码。 一个名为 constitution.md 的文件是 Spec-Kit 最独特的设计。这个文件不是功能需求,它是项目的不变法则:编码规范、TDD 要求、安全标准、部署约束、不合规的代价。在后续每个阶段中,Agent 都必须检查"我的产出是否符合宪法?"这就像国家在制定法律之前先有宪法的约束——一切下位法不能违宪。

刚性阶段门。 Spec-Kit 定义了五个强制阶段外加三个可选阶段。理论上你可以跳过某个阶段,但框架的设计强烈建议按序执行:Constitution → Specify → Plan → Tasks → Implement。每个阶段有明确的输入和输出。

可扩展。 社区贡献了近 100 个扩展——Jira 集成、Azure 部署、成本追踪、无障碍检查、安全审计、多 Agent 审查。Spec-Kit 本身是一个骨架,扩展是肌肉。

多 Agent 支持。 30+ 个 AI 编码工具原生支持——从 GitHub Copilot 到 Claude Code 到 Amazon Q Developer。Spec-Kit 用同一个 /speckit.* 命令体系跨平台运作。

全生命周期覆盖。 从初始化到退役,Spec-Kit 试图覆盖一个功能在代码库中的完整生命周期——不只是"写代码",还有"理解已有代码"、"变更影响分析"、"退役清理"。

3.3.2 核心流程:五步法+三个可选步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
/speckit.constitution  →  定义项目的不变法则


/speckit.specify → 编写功能规格(用户故事、验收标准、非功能需求)


/speckit.plan → 生成技术方案(架构、数据流、组件树、API 契约)


/speckit.tasks → 将方案分解为可独立验证的任务清单


/speckit.implement → 逐任务实现

五个步骤之间是三个可选命令:

  • /speckit.clarify:在 specifyplan 之间插入一轮需求澄清——类似 /grill-with-docs 的领域建模过程
  • /speckit.analyze:在 plan 之前分析变更的影响范围——哪些现有模块会被触及,风险在哪里
  • /speckit.checklist:在 implement 完成后生成一个验证清单——对照原规格逐条确认

constitution.md 的设计值得深入讨论,因为它是 Spec-Kit 与 OpenSpec 之间最深层的哲学分歧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# constitution.md

## 编码标准
- 语言:TypeScript,严格模式
- 格式:Prettier 默认配置
- 禁止使用 `any` 类型(除非有明确的 ADR 记录偏差理由)

## 测试要求
- 每个功能必须有单元测试,覆盖率不低于 80%
- 每个 API 端点必须有集成测试
- 不满足测试要求不得进入 implement 阶段

## 安全要求
- 所有用户输入必须经过验证
- 所有外部 API 调用必须有超时和重试策略
- 敏感信息(密钥、密码、PII)禁止出现在日志中

## 架构约束
- 遵循 Clean Architecture 的分层模型
- 新增依赖必须过审——优先使用现有依赖
- API 契约变更必须更新对应的 OpenAPI 文档和版本号

## 违规后果
- CI 红灯,阻止 merge
- 违规需要在 ADR 中记录决策理由和批准的例外情况

宪法不告诉你"做什么功能"。它告诉你"在这个仓库里,怎么写代码才算合格"。宪法不可变,或只能通过正式的修正案来修改——Agent 在每一步都知道它的行为边界在哪里。

Pocock 在第 2 章中的批评"GSD、BMAD、Spec-Kit 这类方法通过接管流程来帮助你,但同时夺走了你的控制权",指的主要是这种刚性。在 OpenSpec 中,proposal.md 简单到你手动改一行就能用。在 Spec-Kit 中,流程是一个系统,离开流程的代价是失去了框架提供的验证和宪法检查。

两种选择的优劣没有绝对答案。如果团队在高度监管的行业开发(医疗、金融、政府),如果需要为审计留下完整的决策记录,如果面临多个团队并行开发同一代码库的协调复杂度——Spec-Kit 的刚性的价值远超其成本。如果是一个人在做 startup 的 MVP,每天需要快速迭代验证方向——OpenSpec 的轻量自由更合适。用第 1 章的话说:工具的选择取决于你的"信号需求"——你需要多强的信号来确定代码在做你期望它做的事。

3.3.3 Spec-Kit 中文增强版:spec-kit-zh

Spec-Kit 生态中还有一个给中国开发者的项目:spec-kit-zh。不是简单的汉化——它针对中文开发环境做了几项实际适配:

  • 完整的汉化输出:AI 生成的规格、任务、审查报告全部以中文输出。不是机翻,而是用中文技术写作的惯用表达重构了模板
  • 国内工具链适配:预设了 Gitee、极狐 GitLab、阿里云效 Codeup 的项目模板
  • 10+ 主流 AI 编码工具支持:除了 Claude Code、Codex、Copilot 之外,还包括通义灵码、文心快码等国内 AI 编码工具
  • 中文开发场景适配:在 constitution 模板中预设了符合国内团队习惯的编码规范、commit 规范、CR 流程

这代表了 SDD 生态向中国市场的延伸——它标志着 SDD 不再是一个英语社区的孤立运动,而是正在形成全球化的工具生态。

3.3.4 实战:用 Spec-Kit 生成贪吃蛇游戏

同一个贪吃蛇任务,用 Spec-Kit 走一遍,观察流程感的差异。

第零步:/speckit.constitution

Spec-Kit 要求先定宪法。启动命令后,Agent 引导你逐条确定项目的不变法则:

1
/speckit.constitution

Agent: 我先建议一份宪法草案,你可以逐条确认或修改。

生成的 constitution.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# constitution.md

## 编码标准
- 语言:HTML/CSS/JavaScript(纯前端,无框架)
- 格式:2 空格缩进,单引号,分号结尾
- 禁止使用 var,使用 const/let

## 测试要求
- 每个游戏逻辑函数必须有对应的单元测试
- 使用 Jest 或 Vitest 作为测试运行器
- 关键用户路径(开始→移动→吃食物→死亡→重新开始)必须有集成测试

## 安全要求
- 不使用 eval() 或 innerHTML 注入
- localStorage 读写必须有 try-catch 包裹

## 架构约束
- 游戏逻辑与渲染逻辑分离
- 状态管理集中在一个 gameState 对象中
- 新增 npm 依赖需要过审

## 违规后果
- 代码审查时逐条对照宪法检查
- 违规禁止合入

第一步:/speckit.specify

1
2
/speckit.specify 做一个贪吃蛇网页游戏。方向键控制,吃食物变长得分,
撞墙或撞到自己结束。纯前端单文件。

Agent 基于宪法和需求生成 spec.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Spec: 贪吃蛇网页游戏

## 用户故事

### US-1: 作为玩家,我可以用方向键控制蛇的移动
验收标准:
1. 按下 ↑ 键,蛇向上移动一格
2. 按下 ↓ 键,蛇向下移动一格
3. 按下 ← 键,蛇向左移动一格
4. 按下 → 键,蛇向右移动一格
5. 蛇不能反向移动(如当前向右时,← 键无效)

### US-2: 作为玩家,吃到食物后蛇变长、分数增加
验收标准:
1. 蛇头碰到食物格子时,食物消失,蛇长度 +1
2. 分数显示 +10
3. 新食物立即在空白位置随机生成
4. 新食物不与蛇身位置重叠

### US-3: 作为玩家,撞墙或撞到自己时游戏结束
验收标准:
1. 蛇头坐标超出 20×20 网格边界 → 游戏结束
2. 蛇头坐标与蛇身任一格子重叠 → 游戏结束
3. 游戏结束时显示最终得分
4. 显示"重新开始"按钮,点击后重置游戏

### US-4: 作为玩家,最高分在刷新后保留
验收标准:
1. 游戏结束时如果当前分数 > 历史最高分,更新最高分
2. 最高分存储在 localStorage 中
3. 刷新页面后最高分仍然显示
4. localStorage 不可用时游戏正常运行(仅当前分数可用)

## 非功能需求
- NFR-1: 游戏循环间隔 150ms
- NFR-2: 首次渲染时间 < 500ms
- NFR-3: 不依赖任何外部库或框架

第二步:/speckit.clarify(可选)

执行 /speckit.clarify,Agent 会再扫一轮规格中的模糊点:

Agent: 有一个歧义需要澄清——蛇的初始方向是什么?我建议初始方向为"静止",玩家按下第一个方向键后蛇才开始移动。这样可以避免玩家还没准备好蛇就撞墙了。

用户同意。这条澄清被追加到 US-1 的第 6 条验收标准中。

第三步:/speckit.plan

Agent 读取 spec.mdconstitution.md,生成 plan.md

1
2
3
# Plan: 贪吃蛇网页游戏

## 架构概览

index.html
├── ← 游戏画布(400×400px)
├──

← 分数面板
└──
← 结束弹窗

snake-game.js
├── gameState ← 集中状态管理
│ ├── snake: [{x, y}] ← 蛇身坐标数组
│ ├── food: {x, y} ← 食物坐标
│ ├── direction: 'UP'|'DOWN'|'LEFT'|'RIGHT'
│ ├── score: number
│ └── isGameOver: boolean
├── gameLoop() ← 150ms 间隔主循环
├── moveSnake() ← 蛇身移动逻辑
├── checkCollision() ← 碰撞检测(墙壁 + 自身)
├── checkFoodCollision() ← 食物碰撞检测
├── spawnFood() ← 食物随机生成
├── saveHighScore() / loadHighScore() ← localStorage 读写
└── resetGame() ← 重置状态

game.test.js ← 单元测试

1
2
3
4
5
6
7
8
9

## 数据流
键盘事件 → 更新 gameState.direction
→ gameLoop 每 150ms 触发
→ moveSnake() 计算新蛇头位置
→ checkCollision() 检测墙壁/自身 → 可能设置 isGameOver
→ checkFoodCollision() 检测食物 → 可能更新 score、spawnFood()
→ drawGrid() / drawSnake() / drawFood() 重绘画布
→ 更新 score-board DOM

第四步:/speckit.tasks

1
/speckit.tasks

Agent 将 plan 分解为依赖排序的任务,每个任务标注对应的验收标准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Tasks

## Task 1: 项目骨架与画布渲染(依赖:无)
- 创建 index.html 和 snake-game.js
- 初始化 400×400 Canvas
- 实现 drawGrid / drawSnake / drawFood
- 覆盖:US-1 的前置条件(网格可见)
- 验证:打开页面看到空白网格

## Task 2: 游戏状态管理与蛇的移动(依赖:Task 1)
- 实现 gameState 对象
- 实现 moveSnake() 和方向校验(禁止反向)
- 实现 150ms gameLoop
- 绑定键盘事件
- 覆盖:US-1 AC 1-5
- 验证:方向键控制蛇移动,不能反向

## Task 3: 碰撞检测与食物系统(依赖:Task 2)
- 实现 checkCollision(墙壁 + 自身)
- 实现 checkFoodCollision(蛇头 vs 食物)
- 实现 spawnFood(随机、不与蛇重叠)
- 覆盖:US-2 AC 1-4, US-3 AC 1-2
- 验证:吃食物→变长得分,撞墙→游戏结束

## Task 4: UI 与分数系统(依赖:Task 3)
- 实现分数面板 DOM 更新
- 实现游戏结束弹窗 + 重新开始按钮
- 实现 resetGame()
- 覆盖:US-3 AC 3-4, US-4 AC 1-4
- 验证:完整游戏流程 + 最高分持久化

## Task 5: 测试(依赖:Task 4)
- 编写 game.test.js
- 游戏逻辑函数的单元测试
- 完整用户路径的集成测试
- 覆盖:所有 US 的所有 AC
- 验证:npm test 全部通过

注意 Spec-Kit 和 OpenSpec 的一个关键差异:Spec-Kit 的 Task 5 是独立的测试任务——它把测试从实现中分离出来,要求你在实现完成后专门写一轮测试。而 OpenSpec 的 TDD 式工作流中,测试和实现在同一个垂直切片里交替进行。这是两种哲学的直接体现:Spec-Kit 更接近传统的"实现→验证"线性模型,OpenSpec 更接近 Pocock 的"RED→GREEN→REFACTOR"循环。

第五步:/speckit.implement

Agent 从 Task 1 开始顺序执行。和 OpenSpec 的不同之处在于——每一步实现后,Agent 除了打勾任务之外,还会对照 constitution.md 检查合规性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Agent: 开始 Task 1——项目骨架与画布渲染。
[创建文件,实现 Canvas 和渲染函数]
✓ Task 1 完成
✓ 宪法检查通过:无外部依赖、无 eval、使用 const/let

Agent: 开始 Task 2——游戏状态管理与蛇的移动。
[实现 gameState、moveSnake、gameLoop、键盘监听]
✓ Task 2 完成
✓ 宪法检查通过:状态集中在 gameState 对象中
⚠ 宪法提醒:gameLoop 和 moveSnake 可测试性不足——将在 Task 5 中补测试

Agent: 开始 Task 3——碰撞检测与食物逻辑。
[...]

Agent: 开始 Task 4——UI 与分数系统。
[...]

Agent: 开始 Task 5——测试。
[生成 game.test.js,运行 npm test]
✓ 12 个单元测试通过
✓ 2 个集成测试通过
✓ 宪法检查通过:测试覆盖率 94%,超过 80% 门槛

第六步:/speckit.checklist(可选收尾)

1
/speckit.checklist

Agent 生成最终验证清单,逐条对照原规格:

1
2
3
4
5
6
7
✓ US-1: 方向键控制(AC 1-6 全部满足)
✓ US-2: 食物与分数(AC 1-4 全部满足)
✓ US-3: 游戏结束(AC 1-4 全部满足)
✓ US-4: 最高分持久化(AC 1-4 全部满足)
✓ NFR-1: 游戏循环 150ms — 实测 148-152ms
✓ NFR-2: 首次渲染 320ms < 500ms
✓ NFR-3: 零外部依赖

Spec-Kit 用了六个命令、五个任务、外加宪法检查和 checklist 收尾。流程比 OpenSpec 长,但每一步产出的文档和验证记录更完整。如果你在合规性要求的行业,这些记录就是审计轨迹——谁来审都能追溯"这个功能为什么长这样、谁在什么时间验证了它"。


3.4 AWS Kiro:SDD 内置的 IDE

如果 OpenSpec 和 Spec-Kit 是"在现有 AI 编码工具上加上规格工作流",AWS Kiro 则更进一步——它是一个从头开始以 SDD 为设计中心的完整 IDE。

Kiro 于 2025 年中期发布,架设在 Amazon Bedrock 之上(Claude 推理,Amazon Nova 高吞吐量代码生成),是 AWS 在 AI 编码工具领域的旗舰产品。它的定位很清晰:规格是工作单元(Unit of Work)。 不是聊天记录,不是 prompt,不是随手写的便签——是有结构、有版本、可审计的规格文件夹。

3.4.1 三种工作流:覆盖开发的全场景

Kiro 在 2026 年初将它的规格体系扩展为三种互补的工作流,每一种对应一种真实的开发场景:

标准工作流(Standard):最经典的 SDD 模式——用户描述需求,Kiro 生成三份文档。

  • requirements.md:使用 EARS 格式(Easy Approach to Requirements Syntax)编写,一种受 IEEE 标准启发但大幅简化的需求语法。它强制每个需求写清楚触发条件("当用户点击保存按钮时")、系统响应("系统应将偏好写入 localStorage")、异常处理("如果 localStorage 不可用,系统应回退到默认值并输出 console.warn")
  • design.md:技术设计——组件层次、数据流、序列图、API 契约、数据库 schema
  • tasks.md:依赖排序的任务列表,每个任务绑定对应的测试

设计先行工作流(Design-First):为"我已经想好技术方案了,帮我把它形式化"的场景而设计。开发者从一个已有的架构想法开始——可能是手绘的组件图、伪代码、甚至是一段被否掉的原型代码——Kiro 反向推导出 requirements.mddesign.md,再正向生成 tasks.md。这个工作流降低了 SDD 的入场门槛——不需要从空白需求开始,可以从已有的设计出发。AWS 的 Stephanie Walter 评价说:"这是 Kiro 承认了现实——开发者的行为习惯胜过方法论的说教。先构思再形式化,这种混合策略让 SDD 更容易被团队接受。"

Bugfix 工作流:这是三种工作流中最特别的一个,因为它代表了一种"外科手术式"的精确变更模式。在一个有几十万行代码的存量项目中,为了修一个 bug 写一份完整的功能规格是多余的开销。Bugfix 工作流只要求三样东西:

  • 当前行为(Current Behavior):WHEN-THEN 格式,精确描述 bug 的表现。"WHEN 用户在 Firefox 隐私窗口中打开设置页 THEN 页面白屏,console 报 QuotaExceededError"
  • 期望行为(Expected Behavior):修复后应该发生什么。"WHEN 用户在 Firefox 隐私窗口中打开设置页 THEN 页面正常渲染,主题回退到跟随系统,console 输出警告而非报错"
  • 不变行为(Unchanged Behavior):明确声明"修复这个 bug 时绝对不能破坏什么"——这是最容易被忽略但最重要的一条。"应用主题切换功能的所有其他行为应保持不变。localStorage 可用时的偏好保存逻辑不应被修改。相关的单元测试应继续通过。"

Kiro 基于这三份描述生成两类测试:确认 bug 存在的测试(验证修复前行为)和属性测试(Property-Based Tests)——验证修复后不变行为未被破坏。这种设计将一次性的调试行为转化为持久化的测试资产,与第 2 章中 /diagnose 的 Phase 5(回归测试)一脉相承。

3.4.2 Steering Files:Agent 的持久记忆

Kiro 的另一个原创概念是 Steering Files(引导文件)——放在 .kiro/steering/ 目录下的持久化 Markdown 文件,编码了跨会话的约束和知识。它的逻辑与第 2 章中 Pocock 的 CONTEXT.md 高度同构,但结构性更强:

1
2
3
4
5
6
7
8
9
10
11
12
.kiro/
├── steering/
│ ├── coding-standards.md ← 编码规范(类似 ESLint 配置的精神,但用自然语言)
│ ├── architecture.md ← 架构约束(不可触及的模块边界)
│ ├── security.md ← 安全要求(认证模型、数据加密、敏感信息处理)
│ ├── dependencies.md ← 依赖策略(新增库必须过审、版本锁定规则)
│ └── testing.md ← 测试标准(覆盖率门槛、测试类型要求)
└── specs/
└── add-dark-mode/
├── requirements.md
├── design.md
└── tasks.md

Steering Files 和规格(specs)之间的关系很像第 2 章中的"全局 Skill"和"任务级指令"之间的关系。Steering Files 是"凡是这个项目的东西都要遵守"的跨功能约束,规格是"这个功能要实现的"功能级行为。两者都存放在 .kiro/ 目录中,一起构成了项目的 AI 开发环境。

3.4.3 Kiro 的位置:重量级 SDD 的企业端

Kiro 带来的不只是功能,更是一种工作方式的选择。它的存在主义问题是:为了获得治理能力、审计能力、安全控制,你愿意放弃多少灵活性?

答案取决于场景。Dion Hinchcliffe(The Futurum Group)对此的判断是:"企业要回答的问题不是'这个工具慢不慢',而是'它能不能在可测量的程度上降低变更失败率和平均恢复时间'。"

Kiro 的定价也体现了它的定位:从免费层(50 credits/月)到 Power 层($200/月,10,000 credits)。多模型支持意味着不同复杂度的任务可以使用不同成本的模型——简单的格式化用 Qwen(0.05x),复杂的架构设计用 Claude(1x)。这和 Kiro 的"按场景选择工具"哲学一以贯之。

但 Kiro 有几个局限不全是 bug,一定程度上是 SDD 作为方法论的固有张力:

  • SDD 引入额外开销。 为一句话的改动("把按钮颜色换成蓝色")走完 requirements → design → tasks → implement 全流程是荒谬的。Kiro 官方的回应是"不是所有任务都需要 SDD"——但这个判断需要使用者自己下,Kiro 并没有自动推荐合适的工作流。
  • EARS 格式可能产生"假精度"。 格式良好的需求不等于内容正确的需求。一个需求可以完美地遵循 EARS 语法,同时描述的行为完全是错的。形式不保证实质。
  • 规格漂移仍然存在。 Kiro 的规格是在开发前生成的,实施完成后不会自动更新。除非配置 Agent Hooks 来检测漂移,否则规格和实现之间的鸿沟会默默扩大。

Kiro 在 2026 年代表的是"企业重量级"这一端——与 OpenSpec 的自由轻量、Spec-Kit 的治理完备构成一个从轻到重的连续选择区间。

3.4.4 实战:用 Kiro 生成贪吃蛇游戏

还是同一个贪吃蛇任务。在 Kiro 中,你不需要先安装任何 CLI 或配置目录结构——打开 IDE,创建一个空白项目,直接在 Kiro 的规格面板中开始。

选择工作流:Standard

Kiro 启动后弹出工作流选择:Standard / Design-First / Bugfix。选 Standard——我们从一个自然语言需求开始。

第一步:编写需求,Kiro 生成 EARS 格式规格

在 Kiro 的规格编辑器中输入:

1
2
做一个贪吃蛇网页游戏。方向键控制蛇移动,吃食物后蛇变长、分数增加。
撞墙或撞到自己则游戏结束。纯前端单文件实现,最高分用 localStorage 保存。

Kiro 读取这段描述,生成 requirements.md——使用 EARS 格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Requirements: 贪吃蛇网页游戏

## R1: 游戏初始化
WHEN 玩家打开页面时,
THE SYSTEM SHALL 渲染一个 20×20 的游戏网格,
AND 在网格中央放置一条长度为 3 的蛇,
AND 在随机空白位置生成一个食物,
AND 显示当前分数(初始为 0)和历史最高分。

## R2: 蛇的移动
WHEN 玩家按下方向键(↑ ↓ ← →)时,
THE SYSTEM SHALL 更新蛇的移动方向为对应方向,
UNLESS 新方向与当前方向相反(如当前向右时按 ←),
IN WHICH CASE THE SYSTEM SHALL 忽略该按键输入。

WHEN 游戏循环计时器(间隔 150ms)触发时,
THE SYSTEM SHALL 将蛇头向当前方向移动一格,
AND 从蛇尾移除一格。

## R3: 食物消费
WHEN 蛇头坐标与食物坐标重叠时,
THE SYSTEM SHALL 移除该食物,
AND 将蛇长度增加 1 格,
AND 将当前分数增加 10 分,
AND 在随机空白位置生成一个新食物。

IF 没有空白格子可用(极罕见,蛇填满整个网格),
THEN THE SYSTEM SHALL 判定玩家胜利,显示"你赢了!"并停止游戏循环。

## R4: 碰撞与游戏结束
WHEN 蛇头坐标超出 20×20 网格边界时,
THE SYSTEM SHALL 判定游戏结束。

WHEN 蛇头坐标与蛇身任一格子重叠时,
THE SYSTEM SHALL 判定游戏结束。

WHEN 游戏结束时,
THE SYSTEM SHALL 停止游戏循环,
AND 比较当前分数与 localStorage 中的最高分(若当前分更高则更新),
AND 显示游戏结束弹窗(包含最终得分和"重新开始"按钮)。

## R5: 重新开始
WHEN 玩家点击"重新开始"按钮时,
THE SYSTEM SHALL 重置蛇位置、蛇长度、移动方向、当前分数和食物位置,
AND 重新启动游戏循环,
AND 不重置 localStorage 中的最高分。

## R6: 降级处理
IF localStorage 不可用(隐私模式、存储已满),
THEN THE SYSTEM SHALL 回退到内存存储(最高分仅在当前会话中保留),
AND 在浏览器 console 中输出警告信息。

## 非功能需求
- NFR1: 游戏循环间隔 150ms(±10ms 容差)
- NFR2: 页面首次渲染在 500ms 内完成
- NFR3: 无外部库或框架依赖
- NFR4: 键盘输入响应延迟 < 50ms

EARS 的特征在此清晰:每条需求都以 WHEN/IF/THEN/SHALL/UNLESS 之一开头,触发条件和系统响应被强制分离。这种结构让自然语言需求变得可自动化验证——你可以写一个脚本逐条扫描需求文档,检查每条是否包含 WHEN 和 THEN 子句。

第二步:Kiro 生成 design.md

你确认需求后,Kiro 基于 requirements.md.kiro/steering/ 目录中的 Steering Files 生成 design.md。如果你还没有 Steering Files,Kiro 会提示你:"检测到 .kiro/steering/ 为空。建议在实现前先配置 coding-standards.md 和 testing.md,或跳过此步直接使用默认设置。"

(这里我们假设已经配置了基础的编码规范。)

design.md 的核心内容:

1
2
3
# Design: 贪吃蛇网页游戏

## 组件树

index.html
└──
├──


│ ├──
← 当前分数 / 最高分
│ └── ← 游戏画布 400×400
└──