Skip to content

spec-driven ​

默认工作流的工件:其顺序、格式,以及它们生成的变更文件夹。

spec-driven 是 OpenSpec 内置的默认 schema。schema.yaml 定义了它所设置的字段。

工件 ​

该工作流起草四个工件:

工件文件用途
proposalproposal.md为什么需要此变更
specsspecs/<capability-path>/spec.md,每个能力一个哪些行为会变更
designdesign.md如何构建它
taskstasks.md实现检查清单

起草顺序 ​

text
             ┌─ specs ──┐
proposal ────┤          ├── tasks ── apply
             └─ design ─┘

Proposal 优先。Specs 和 design 以任意顺序随后起草,而 tasks 需要两者都完成。实现(apply)在 tasks.md 就位后开始。

可以跳过两个产出物:

  • design:当其适用条件均不满足时,代理会将其省略并继续起草 tasks。
  • specs:在变更的 .openspec.yaml 中设置 skip_specs: true。

示例变更文件夹 ​

一个名为 add-user-auth 的变更,包含所有已起草的产出物:

text
openspec/changes/add-user-auth/
├── .openspec.yaml      变更元数据,在变更创建时写入
├── proposal.md
├── specs/
│   └── user-auth/
│       └── spec.md     每个能力对应一个增量规格
├── design.md
└── tasks.md

proposal.md ​

确立变更的必要性。

结构 ​

代理收到的输出格式模板(templates/proposal.md):

md
# Proposal

## Why

<!-- Explain the motivation for this change. What problem does this solve? Why now? -->

## What Changes

<!-- Describe what will change. Be specific about new capabilities, modifications, or removals. -->

## Capabilities

### New Capabilities
<!-- Capabilities being introduced. Name each capability for a cohesive system
     behavior that can own related requirements as the system evolves. Do not name
     implementation tasks or proposal sections. Avoid broad catch-all names. Use
     kebab-case for path segments you introduce (e.g., user-auth or identity/user-auth)
     that follow the project's existing spec organization. Each creates
     specs/<capability-path>/spec.md. -->
- `<capability-path>`: <brief description of what this capability covers>

### Modified Capabilities
<!-- Existing capabilities whose REQUIREMENTS are changing (not just implementation).
     Only list here if spec-level behavior changes. Each needs a delta spec file.
     Use the exact existing path under openspec/specs/. Leave empty if no requirement
     changes. A change with no capabilities at all (pure refactor, tooling, docs)
     must set `skip_specs: true` in its .openspec.yaml - openspec validate rejects
     a zero-delta change without that marker. Do not invent a requirement just to
     satisfy validation. -->
- `<existing-capability-path>`: <what requirement is changing>

## Impact

<!-- Affected code, APIs, dependencies, systems -->

指令 ​

代理在起草此产出物时收到的指令(来自 schema.yaml):

md
Create the proposal document that establishes WHY this change is needed.

Sections:
- **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now?
- **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**.
- **Capabilities**: Identify which specs will be created or modified:
  - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<capability-path>/spec.md`. Name each capability for a durable system behavior (for example, `user-auth`), not the work in this change (for example, `add-login-endpoint`). Choose a cohesive boundary that can own related requirements as the system evolves; avoid broad catch-all capabilities. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization.
  - **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Use the exact existing path under `openspec/specs/`. Leave empty if no requirement changes.
- **Impact**: Affected code, APIs, dependencies, or systems.

IMPORTANT: The Capabilities section is critical. It creates the contract between
proposal and specs phases. Research existing specs before filling this in:
run `openspec list --specs` for the project's capability inventory, then
`openspec show "<spec-id>" --type spec --json --no-scenarios` for any that
look related - that returns a capability's purpose and requirement texts
without pulling whole spec files into context. Append `--store "<id>"` to
both commands only for a registered standalone store, and keep `--type
spec`: a change and a spec sharing a name is otherwise an ambiguous-item
error. `openspec list` without `--specs` lists in-flight changes, not
specs - it never shows what the project already covers. Reuse an existing
capability's exact path instead of introducing a near-duplicate name.
The filtered read is only an overview. Before deciding what is already
covered or what should change, read each relevant spec in full, including
scenarios, with `openspec show "<spec-id>" --type spec` (same `--store` rule).
Each capability listed here will need a corresponding spec file.

Every change must either declare at least one capability (new or
modified) or explicitly opt out of specs: `openspec validate` rejects a
change with zero deltas unless the change's `.openspec.yaml` sets
`skip_specs: true`. Use `skip_specs: true` only when no spec-level
behavior changes (pure refactor, tooling, docs) - specs describe
behavior, so if behavior does not change, no spec should change either.
Do not invent a requirement just to satisfy validation.

Keep it concise (1-2 pages). Focus on the "why" not the "how" -
implementation details belong in design.md.

This is the foundation - specs, design, and tasks all build on this.

增量规格 (spec.md) ​

定义行为变更,proposal 中列出的每个能力对应一个增量规格。

每个增量规格是其能力文件夹中的 spec.md。openspec validate 和 openspec archive 拒绝在 specs/ 下其他任何文件中编写的增量部分,例如 specs/user-auth.md,因为 archive 永远不会合并它们。

结构 ​

代理收到的输出格式模板(templates/spec.md):

md
# Spec Delta

## Purpose
<!-- New capabilities only: one or two sentences (50+ characters) on what this capability is for. Delete this section for an existing capability. -->

## ADDED Requirements

### Requirement: <!-- requirement name -->
<!-- requirement text -->

#### Scenario: <!-- scenario name -->
- **WHEN** <!-- condition -->
- **THEN** <!-- expected outcome -->

指令 ​

代理在起草此产出物时收到的指令(来自 schema.yaml):

md
Create specification files that define WHAT the system should do.

A spec is a behavior contract, not an implementation plan.

Good spec content:
- Observable behavior users or downstream systems rely on
- Inputs, outputs, and error conditions
- External constraints (security, privacy, reliability, compatibility)
- Scenarios that can be tested or explicitly validated

Avoid in specs:
- Internal class/function names
- Library or framework choices
- Step-by-step implementation details
- Detailed execution plans (those belong in design.md or tasks.md)

Quick test: if the implementation can change without changing externally
visible behavior, it likely does not belong in the spec.

Create one spec file per capability listed in the proposal's Capabilities section.
`<capability-path>` is the spec directory relative to `specs/` (for example,
`user-auth` or `identity/user-auth`). Preserve the full path:
- New capabilities: use the exact path from the proposal at `specs/<capability-path>/spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout.
- Modified capabilities: use the exact existing path from `openspec/specs/<capability-path>/` when creating the delta at `specs/<capability-path>/spec.md`. Run `openspec list --specs` to confirm that path before writing the delta, appending `--store "<id>"` only for a registered standalone store - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability.

Write the spec files one at a time, so the user sees progress and an
interrupted run keeps the files it already wrote:
- Before writing anything, compare the capabilities the proposal lists
  with the `specs/<capability-path>/spec.md` files that already exist,
  and start with the first capability that has no file. Do not rewrite
  an existing file unless the user asks you to.
- Before each file, say in one line which capability you are writing.
- Write each file as soon as you have drafted it. Do not plan every file
  before writing the first.
- Stop when every capability the proposal lists has its file.

There must be at least one spec file unless the change's `.openspec.yaml`
sets `skip_specs: true` (no spec-level behavior change) - `openspec validate`
rejects a zero-delta change without that marker. If the proposal lists no
capabilities and `skip_specs` is not set, revisit the proposal first.

Delta operations (use ## headers):
- **ADDED Requirements**: New capabilities
- **MODIFIED Requirements**: Changed behavior - MUST include full updated content
- **REMOVED Requirements**: Deprecated features - MUST include **Reason** and **Migration**
- **RENAMED Requirements**: Name changes only - use FROM:/TO: format

Format requirements:
- Each requirement: `### Requirement: <name>` followed by description
- Use SHALL/MUST for normative requirements (avoid should/may)
- Each scenario: `#### Scenario: <name>` with WHEN/THEN format
- **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently.
- Every requirement MUST have at least one scenario.
- Every scenario MUST have a WHEN/GIVEN step (its trigger) and a THEN step (its outcome). A scenario with only AND lines or plain prose still counts as a scenario, but `openspec validate` flags it as a warning: normal validation still passes, but `openspec validate --strict` fails on it.
- Keep each requirement's description (the text between `### Requirement:` and its first scenario) to 500 characters or fewer. `openspec validate` flags longer descriptions in ADDED requirements and in the main spec. This is a warning: normal validation still passes, but `openspec validate --strict` fails on it. When writing a new requirement, state one behavior per requirement: move examples and edge cases into scenarios, and split a requirement that covers several behaviors into separate `### Requirement:` blocks, each with its own scenarios. Under MODIFIED, keep the existing requirement block whole; never split, trim or rewrite existing text just to meet the length. Split an existing long requirement only when the user asks for it, in a change made for that purpose: under MODIFIED, keep its header and every scenario and cut its description down to one behavior without changing its meaning, then add each behavior you removed as its own ADDED requirement with its own scenarios.

New capabilities only: the delta spec's first section is `## Purpose` -
one or two sentences (50+ characters, or `openspec validate --strict`
reports it as too brief) describing what the capability is for. Archive
copies it into the main spec it creates; without it the new main spec is
left with a `TBD ... Update Purpose after archive` placeholder to fill in
by hand. Do NOT add `## Purpose` to a delta for an existing capability -
that spec already has one and the delta's is ignored. To change an
existing capability's Purpose - including a leftover `TBD` placeholder -
edit `<planningHome.root>/openspec/specs/<capability-path>/spec.md`
directly. `planningHome.root` comes from the `openspec instructions ...
--json` response. Always use it rather than a repo-relative path: it
resolves to the store whenever the change lives in one - whether that
came from `--store`, a project `store:` pointer, or a global default
store - and to the current repository otherwise. Do not try to work out
which case applies; the field already has.

MODIFIED requirements workflow:
1. Locate the existing requirement in `<planningHome.root>/openspec/specs/<capability-path>/spec.md` (the same store-aware root as above)
2. Copy the ENTIRE requirement block (from `### Requirement:` through all scenarios)
3. Paste under `## MODIFIED Requirements` and edit to reflect new behavior
4. Ensure header text matches exactly (whitespace-insensitive)

Common pitfall: Using MODIFIED with partial content loses detail at archive time.
If adding new concerns without changing existing behavior, use ADDED instead.

Example (a new capability, so its first section is `## Purpose`):
```
# Spec Delta

## 目的

让用户能够以可移植格式将数据从产品中导出。

## ADDED 需求

### 需求:用户可以导出数据
系统应允许用户以 CSV 格式导出其数据。

#### 场景:成功导出
- **WHEN** 用户点击"导出"按钮
- **THEN** 系统下载包含所有用户数据的 CSV 文件

## REMOVED 需求

### 需求:旧版导出
**原因**:已被新导出系统取代
**迁移**:使用新导出端点 /api/v2/export
```

规格应可测试——每个场景都是一个潜在的测试用例。

design.md ​

解释如何实现变更。仅在变更需要时起草。

结构 ​

智能体收到的输出格式模板 (templates/design.md):

md
# 设计

## 背景

<!-- 当前状态和约束,用于解释方案。动机见 proposal.md - 不要在此重复 -->

## 目标 / 非目标

**目标:**
<!-- 此设计旨在实现的内容 -->

**非目标:**
<!-- 明确不在范围内的内容 -->

## 决策

<!-- 关键设计决策及其理由和考虑的替代方案 -->

## 风险 / 权衡

<!-- 已知风险和权衡 -->

说明 ​

智能体起草此工件时收到的指令 (来自 schema.yaml):

md
创建解释如何实现变更的设计文档。

何时包含 design.md(仅在以下情况创建):
- 跨领域变更(多个服务/模块)或新架构模式
- 新外部依赖或重大数据模型变更
- 安全性、性能或迁移复杂性
- 在编码前受益于技术决策的模糊性

章节:
- **背景**:仅包含解释方案所需的当前状态和约束。参考提案获取动机,而非在此重复(例如"见 proposal.md - 为什么")。
- **目标 / 非目标**:此设计实现的内容和明确排除的内容。不要重述提案的范围——仅添加设计层面的边界。
- **决策**:关键技术选择及其理由(为什么选 X 而非 Y?)。包含每个决策考虑的替代方案。
- **风险 / 权衡**:已知限制、可能出问题的地方。格式:[风险] → 缓解措施
- **迁移计划**:部署步骤、回滚策略(如适用)
- **开放问题**:可以安全地稍后回答而不会改变规格、方案或任务分解的未知项。如果没有则省略。

开放问题针对真正可推迟的未知项,而非你跳过的决策。如果某个问题会改变规格、所选方案或任务分解,请现在解决——询问用户而非猜测。

专注于架构和方案,而非逐行实现。
提案涵盖为什么和做什么;设计涵盖如何做。参考
提案获取动机,一旦编写完成,参考规格获取需求——
如果某个章节只会重述它们,请指向它们而非重复。

优秀的设计文档解释技术决策背后的"为什么"。

tasks.md ​

将实现分解为可检查的任务。apply 在此跟踪进度。

结构 ​

智能体收到的输出格式模板 (templates/tasks.md):

md
# 任务

## 1. <!-- 任务组名称 -->

- [ ] 1.1 <!-- 任务描述 -->
- [ ] 1.2 <!-- 任务描述 -->

## 2. <!-- 任务组名称 -->

- [ ] 2.1 <!-- 任务描述 -->
- [ ] 2.2 <!-- 任务描述 -->

说明 ​

智能体起草此工件时收到的指令 (来自 schema.yaml):

md
创建分解实现工作的任务列表。

在编写任务之前,检查 design.md 中的开放问题。如果其中任何问题会改变要构建的内容,请先与用户解决——不要将未说明的假设固化到任务列表中。

**重要提示:遵循下方模板用于跟踪任务。** apply 阶段解析复选框格式以跟踪进度。仅包含 `x` 的框计为完成,大小写不限且任何间距均可,因此 `- [ x]` 也算完成。其他任何标记,包括 `- [~]`、`- [-]` 和空的 `- []`,均视为未完成。没有复选框的行完全不被跟踪。

指南:
- 将相关的跟踪任务分组在 ## 编号标题下
- 每个跟踪任务必须是复选框:`- [ ] X.Y 任务描述`
- 任务应足够小,以便在一次会话中完成
- 按依赖关系排序任务(必须先完成什么?)
- 跟踪可在归档之前完成的实现和验证工作。如果请求的工作流包含归档或需要此变更归档的工作,将这些步骤保留为 tasks.md 末尾可选 `## 工作流后续` 部分中的纯项目符号。这些项目符号是跟踪任务进度之外的参考信息。
- 每个任务必须说明如何验证完成(测试、命令、可观察行为或交付工件)。将验证放在该任务的复选框描述中。仅在检查跨多个实现任务的更广泛集成或系统行为时使用单独的验证任务。
- 每个任务组必须交付其自身工作所需的测试和文档。不要将测试或文档收集到最后一组——当后面的组首次执行前面组的工作时,失败会级联回中间的每个组并强制返工。工作不需要测试或文档的组(如脚手架或依赖设置)则不携带。最后一组仅用于集成检查,而非前面组所欠的测试和文档。

示例:
```
# 任务

## 1. 设置

- [ ] 1.1 创建新模块结构并验证预期文件存在
- [ ] 1.2 将依赖添加到 package.json 并验证包安装成功

## 2. 核心实现

- [ ] 2.1 实现数据导出功能并验证导出测试通过
- [ ] 2.2 添加 CSV 格式化工具并验证单元测试覆盖引号和分隔符
- [ ] 2.3 在 docs/export.md 中记录导出 API 并验证文档中的命令按所写运行
```

适当时,将工作流后续作为纯项目符号附加,例如:
```
## 工作流后续

- 在项目审查要求满足后归档变更。
- 验证归档结果。
```

参考规格了解需要构建的内容,参考设计了解如何构建。

Apply ​

从规划到实现的交接。Apply 是处理 tasks.md 的阶段,而非工件。

  • 开始:一旦 tasks.md 存在且列出至少一个任务。
  • 跟踪:tasks.md 中的复选框。勾选它们是进度记录。
  • 结束:每个复选框都被勾选。OpenSpec 随后建议归档变更。

设置 ​

apply 设置 (来自 schema.yaml):

yaml
apply:
  requires: [tasks]
  tracks: tasks.md
  # instruction: shown below

说明 ​

实现开始时发送给智能体的指令 (来自 schema.yaml):

md
阅读上下文文件,处理待办任务,完成时标记为已完成。
遇到阻塞或需要澄清时暂停。

schema.yaml ​

完整的 schema.yaml,其中指令主体被省略。每个指令在上方其各自章节中完整显示。

yaml
name: spec-driven
version: 1
description: 默认 OpenSpec 工作流 - proposal → specs → design → tasks
artifacts:
  - id: proposal
    generates: proposal.md
    description: 概述变更的初始提案文档
    template: proposal.md
    # instruction: 完整显示在上方 proposal.md 章节
    requires: []

  - id: specs
    generates: "specs/**/*.md"
    description: 变更的详细规格
    template: spec.md
    # instruction: 完整显示在上方 Delta specs 章节
    requires:
      - proposal

  - id: design
    generates: design.md
    description: 包含实现细节的技术设计文档
    template: design.md
    # instruction: 完整显示在上方 design.md 章节
    requires:
      - proposal

  - id: tasks
    generates: tasks.md
    description: 包含可跟踪任务的实现检查清单
    template: tasks.md
    # instruction: 完整显示在上方 tasks.md 章节
    requires:
      - specs
      - design

apply:
  requires: [tasks]
  tracks: tasks.md
  # instruction: 完整显示在上方 Apply 章节