直接回答

**OpenSpec 是一套把 AI 辅助编码变更放进仓库、用纯文本工件规划和追踪的工作流。**正确用法是:安装 CLI,在仓库初始化,通过当前 AI 工具生成的工作流创建变更,依次审查 proposal.md、delta spec、按需生成的 design.md 和 tasks.md,再实现、验证并归档。

最重要的边界是:OpenSpec 能让意图、范围和变更历史更容易审查,但不会让生成代码自动正确。openspec validate 能发现结构不合规的工件,可选 verify 工作流能发现规格与实现之间的疑似偏差;真正的正确性仍需由编译、测试、安全检查、人工审查和发布控制证明。

目录

先理解模型:当前规格与活动变更

OpenSpec 把团队当前接受的系统行为与待实施的修改分开保存:

text
openspec/
├── config.yaml
├── specs/
│   └── <domain>/spec.md
└── changes/
    ├── <active-change>/
    │   ├── proposal.md
    │   ├── specs/<domain>/spec.md
    │   ├── design.md
    │   └── tasks.md
    └── archive/
  • openspec/specs/ 描述维护中的系统行为。它是经过审查的文档基线,不是自动执行约束的引擎。
  • openspec/changes/<name>/ 保存一个活动变更的意图和增量。
  • proposal.md 说明为什么要改、改什么、明确不改什么。
  • specs/ 保存 delta spec(增量规格),标识新增、修改或删除的需求。
  • design.md 记录有实质影响的技术取舍。小变更或非技术变更可以按当前 Schema 规则不生成它。
  • tasks.md 排列实施与验证任务。勾选框只是一项状态声明,不是完成证据。
  • changes/archive/ 保存已经关闭的变更记录,其增量已经与主规格完成协调。

这些工件构成依赖关系,而不是四份互不相关的文档:

flowchart LR A["proposal:原因与范围"] --> B["delta spec:目标行为"] B --> C["design:技术取舍"] C --> D["tasks:实施计划"] D --> E["代码与可执行证据"] E --> F["sync 或 archive"] E -. 新发现 .-> A E -. 新发现 .-> B E -. 新发现 .-> C

实施中发现遗漏约束时,应修订上游工件和对应任务。只改代码、不改约束,会制造规格漂移。

如果希望先理解与具体工具无关的需求工程、可追踪性和证据模型,可阅读 Spec Coding 核心概念解析。本文专门解决当前 OpenSpec 的安装、命令和执行问题。

安装并初始化 OpenSpec

通过 npm 安装时,OpenSpec 当前要求 Node.js 20.19.0 或更高版本。修改仓库前先检查运行时与 CLI:

bash
node --version
npm install -g @fission-ai/openspec@latest
openspec --version

然后在仓库根目录初始化。老项目建议先创建分支,以便完整审查生成文件:

bash
cd existing-go-service
git switch -c feature/request-body-limit
openspec init
git status --short

openspec init 会询问正在使用的 AI 工具,并写入两类文件:

  1. openspec/ 项目目录;
  2. 目标工具对应的 Skill 和/或命令,例如 .agents/、.claude/、.cursor/ 或 .trae/。

提交前应审查所有生成文件。初始化程序找到代码仓库,不代表它已经理解系统架构。可在 openspec/config.yaml 中维护长期有效的项目上下文和约束,再对照真实代码验证生成工作流。

重复运行 openspec init 可以新增或刷新工具集成。升级 CLI、切换 Profile 或增加 AI 工具后,应刷新项目生成文件:

bash
openspec update

生成的 Skill 和命令不会自动更新。如果新命令没有出现在 AI IDE 中,先检查生成路径,再重启工具。

终端命令与 AI 工作流不是一回事

这是初次使用最容易混淆的地方:

操作界面 示例 作用
终端 openspec init 创建或刷新项目集成
终端 openspec list 列出活动变更
终端 openspec show add-request-body-limit 查看指定变更
终端 openspec validate add-request-body-limit 检查工件结构和 Schema 规则
终端 openspec view 打开交互式项目视图
AI 对话框 propose 工作流 让助手创建变更工件
AI 对话框 apply 工作流 让助手执行任务
AI 对话框 archive 工作流 让助手协调规格并关闭变更

不要在终端里输入 /opsx:propose,也不要把 openspec validate 发给 AI 后就认为执行了确定性的 CLI 校验。

不同工具执行的是同一套工作流,但调用语法不同:

宿主 Propose Apply Archive
Claude Code /opsx:propose /opsx:apply /opsx:archive
Cursor 或 Trae /opsx-propose /opsx-apply /opsx-archive
Codex $openspec-propose $openspec-apply-change $openspec-archive-change

以上是当前生成名称。对本地项目而言,openspec init 的完成提示和实际生成文件才是权威答案。OpenSpec 可以交付 Skill、命令或两者;部分宿主只支持 Skill。

如何选择 Core 与 Expanded 工作流

默认 core Profile 包含六个工作流:

工作流 适用场景
explore 问题或方案仍有歧义,暂时不需要创建变更工件
propose 一次生成 proposal、spec、按需生成的 design 和 tasks
apply 工件已审查,可以开始实施
update 实施中的新发现要求修订已有计划或需求
sync 需要把增量合入主规格,但仍保留活动变更
archive 实现已经验收,可以关闭变更

Expanded 集合还提供 new、continue、ff、verify、bulk-archive 和 onboard。需要逐份审批工件时,使用 new 加 continue;希望一次生成全部规划工件时可用 ff;归档前需要规格与实现对照检查时,可启用 verify。

先修改全局工作流选择,再更新当前项目:

bash
openspec config profile
openspec update

命令更多不等于流程更成熟。如果团队无法稳定审查四份核心工件,再增加六个入口只会提高认知负担。

实战:为 Go API 增加请求体大小限制

假设一个成熟 Go 服务通过 POST /v1/import 接收 JSON 导入数据,大请求可能在校验前耗尽内存。目标变更如下:

  • 请求体超过 1 MiB 时返回 HTTP 413;
  • 正好等于上限的请求必须通过;
  • 没有 Content-Length 的请求也必须受限;
  • 不改变其他路由和鉴权行为;
  • 增加测试和拒绝计数指标;
  • 不修改公开 JSON Schema。

这个例子同时涉及安全、HTTP 语义、内存边界和既有中间件顺序,比“增加一个按钮”更能体现规格驱动流程的价值。

步骤 1:先调查当前行为

如果还不知道限制应插在哪一层,先运行 explore 工作流,并明确要求只调查、不修改:

text
调查 POST /v1/import 及其中间件链。确认请求体第一次被读取的位置、
现有限制、错误响应格式、监控指标、测试以及反向代理限制。不要修改文件,
结论必须附带文件路径、配置键或测试名称。

审查结果至少要回答:

  • 代理层是否已经拒绝大请求,应用层是否仍需独立约束?
  • 请求体是流式读取、整体缓冲、解压后读取,还是被重复解析?
  • 限制应放在鉴权之前还是之后?
  • API 是否使用统一错误信封,而不是 http.Error?
  • 缺失或伪造 Content-Length 时会发生什么?
  • 哪些测试能证明其他路由没有变化?

不要把“助手已经看过”当作证据。缺少文件路径、配置键和测试名称的结论,应回到仓库核验。

步骤 2:提出一个边界清晰的变更

使用当前宿主生成的 propose 语法。例如:

text
/opsx:propose add-request-body-limit

把已确认事实、目标行为、非目标和验证门禁一起提供给助手:

text
为 POST /v1/import 创建 add-request-body-limit 变更。
应用层上限为 1 MiB,超过后使用现有 API 错误信封返回 HTTP 413;
必须覆盖无 Content-Length 请求,拒绝时增加现有指标一次;
保持中间件顺序和其他路由不变。加入 Go 单元测试、仓库 Lint 门禁
以及正好等于上限的回归测试。不要修改鉴权、代理配置或 JSON Schema。

一份合格的计划应足够具体,让评审者在任何代码生成之前就能否决错误方案。

步骤 3:编写可验证的 Delta Spec

新增行为应放入 ## ADDED Requirements:

markdown
# HTTP Import 变更增量

## ADDED Requirements

### Requirement: 导入请求体上限
服务 MUST 在 JSON 解码前拒绝大于 1 MiB 的 `POST /v1/import`
请求体,并且 MUST 使用现有 API 错误信封返回 HTTP `413`。

#### Scenario: 已声明长度的请求超过上限
- **GIVEN** 一个 `Content-Length` 大于 1 MiB 的已鉴权导入请求
- **WHEN** 请求到达导入请求体限制中间件
- **THEN** 服务返回 HTTP `413`
- **AND** 导入 Handler 不得执行
- **AND** 拒绝计数器只增加一次

#### Scenario: 未知长度的请求超过上限
- **GIVEN** 一个没有 `Content-Length` 的分块导入请求
- **WHEN** 读取量超过 1 MiB
- **THEN** 服务返回 HTTP `413`
- **AND** 不得分配无界缓冲区

#### Scenario: 请求体正好达到上限
- **GIVEN** 一个请求体正好为 1 MiB 的已鉴权导入请求
- **WHEN** 服务读取请求体
- **THEN** 导入 Handler 收到完整请求体

只有替换既有需求时才使用 ## MODIFIED Requirements,而且必须放入修改后的完整需求及全部场景,不能只写一句补丁说明。归档时,新版本会替换原需求。只有维护中的行为确实要消失时才使用 ## REMOVED Requirements,并在 proposal 中交代迁移和兼容影响。

Delta spec 是行为契约。包名、辅助函数签名和具体缓冲策略通常属于 design.md;只有成为外部可观察约束时,才应写进需求。

步骤 4:显式记录设计取舍

这次变更的设计审查至少应比较:

决策 方案 影响
限制层 只在反向代理限制 拒绝成本低,但各部署环境可能不一致,本地测试无法覆盖
限制层 只在应用限制 行为契约可移植,但请求会先消耗应用资源
请求体处理 最多读取 limit + 1 字节 适合小型 JSON,内存有界但仍会整体缓冲
请求体处理 使用 http.MaxBytesReader 流式限制 复制更少,但下游解码错误必须可靠映射为 413
中间件顺序 鉴权前限制 能先保护鉴权服务,但可能改变错误优先级
中间件顺序 鉴权后限制 保留鉴权语义,但被拒请求仍会消耗鉴权资源

正确答案取决于现有架构。应记录最终选择、被否决方案、内存上限、状态码映射、指标和回滚方式,不能把这些决定隐藏在生成代码里。

步骤 5:把需求映射成任务

有用的 tasks.md 应同时覆盖实现与证据:

markdown
## 1. 请求限制
- [ ] 1.1 为 POST /v1/import 增加有界读取
- [ ] 1.2 保留当前 API 错误信封与中间件顺序
- [ ] 1.3 拒绝时将现有指标准确增加一次

## 2. 验证
- [ ] 2.1 测试已声明长度且超过上限的请求
- [ ] 2.2 测试未知长度且超过上限的请求
- [ ] 2.3 测试正好为 1 MiB 的请求
- [ ] 2.4 测试其他路由不受影响
- [ ] 2.5 运行 Go 测试、Lint 和仓库安全门禁

任务不应凭空引入 proposal 与需求之外的工作;每个高风险场景也必须有对应的实现或验证任务。

写代码前如何审查工件

按依赖顺序阅读生成工件,一旦上游假设错误就先停止:

工件 核心问题 应退回修改的情况
proposal.md 问题、范围、非目标和影响领域是否正确? 擅自加入无关重构,或遗漏兼容性影响
delta spec 每项目标行为是否可观察、可测试? “大”“快”“安全”等词没有阈值
design.md 风险、备选方案、边界和回滚是否明确? 未评估中间件顺序或内存行为就直接选型
tasks.md 每项任务是否能追溯到已批准行为? 任务没有需求来源,或需求没有证据任务

高风险变更可以在 proposal 或 PR 中维护一张短小的追踪表:

需求 实现位置 证据
拒绝已声明超限请求 import 中间件 Content-Length > limit 单元测试
拒绝未知长度请求 有界读取器 分块请求单元测试
接受正好达到上限的请求 边界判断 精确边界单元测试
其他路由不变 路由级注册 Router 回归测试
指标只增加一次 拒绝分支 指标断言

OpenSpec 不提供审批权、所有权或访问控制。真正的强制机制应由 Code Owner、受保护分支、CI 和发布策略承担。

实现变更并建立证据链

工件审查通过后再运行 apply。助手可以在执行时更新任务勾选状态,但每完成一组有实质影响的修改,都应审查代码差异。

下面的 Go 示例展示小型 JSON 接口的有界读取逻辑。生产服务应复用已有错误信封和指标,不应直接复制示例文案:

go
package bodylimit

import (
	"bytes"
	"io"
	"net/http"
)

func Middleware(maxBytes int64, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if maxBytes < 1 {
			http.Error(w, "invalid body limit", http.StatusInternalServerError)
			return
		}
		if r.ContentLength > maxBytes {
			http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
			return
		}

		body, err := io.ReadAll(io.LimitReader(r.Body, maxBytes+1))
		if err != nil {
			http.Error(w, "invalid request body", http.StatusBadRequest)
			return
		}
		if int64(len(body)) > maxBytes {
			http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
			return
		}

		r.Body = io.NopCloser(bytes.NewReader(body))
		next.ServeHTTP(w, r)
	})
}

配套测试同时覆盖两个拒绝路径,并证明被接受的请求体能原样交给下游:

go
package bodylimit

import (
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestMiddleware(t *testing.T) {
	const limit = int64(8)

	tests := []struct {
		name          string
		body          string
		contentLength int64
		wantStatus    int
		wantBody      string
	}{
		{
			name:          "declared body exceeds limit",
			body:          "123456789",
			contentLength: 9,
			wantStatus:    http.StatusRequestEntityTooLarge,
		},
		{
			name:          "unknown length exceeds limit",
			body:          "123456789",
			contentLength: -1,
			wantStatus:    http.StatusRequestEntityTooLarge,
		},
		{
			name:          "body at exact limit",
			body:          "12345678",
			contentLength: 8,
			wantStatus:    http.StatusNoContent,
			wantBody:      "12345678",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				body, err := io.ReadAll(r.Body)
				if err != nil {
					t.Fatalf("read accepted body: %v", err)
				}
				w.Header().Set("X-Accepted-Body", string(body))
				w.WriteHeader(http.StatusNoContent)
			})

			request := httptest.NewRequest(
				http.MethodPost,
				"/v1/import",
				strings.NewReader(tc.body),
			)
			request.ContentLength = tc.contentLength
			response := httptest.NewRecorder()

			Middleware(limit, next).ServeHTTP(response, request)

			if response.Code != tc.wantStatus {
				t.Fatalf("status = %d, want %d", response.Code, tc.wantStatus)
			}
			if got := response.Header().Get("X-Accepted-Body"); got != tc.wantBody {
				t.Fatalf("accepted body = %q, want %q", got, tc.wantBody)
			}
		})
	}
}

独立于助手运行验证:

bash
openspec validate add-request-body-limit
go test ./...
go vet ./...

再根据风险执行仓库自己的 Lint、Race、安全、契约和集成检查。真实项目还应补充错误信封、指标、路由范围、压缩请求、代理行为以及并发负载下内存上限的测试。

可以按五层证据链检查:

  1. 工件有效性:openspec validate 证明变更符合当前 Schema。
  2. 构建有效性:编译和静态检查发现类型、API 与常见正确性错误。
  3. 行为证据:每个场景都有覆盖边界和失败分支的测试。
  4. 差异审查:人工确认范围、设计、生成文件、依赖和运维影响。
  5. 运行证据:必要时用指标、负载测试、灰度检查和回滚演练证明生产假设。

任务勾选框或模型声称“测试通过”都不是充分证据。必须能看到实际命令结果,并确认测试确实覆盖对应需求。

Update、Sync、Verify 与 Archive 的边界

这四个工作流解决不同的生命周期问题。

Update

实施中的新发现改变活动工件时使用 update。例如,调查发现代理层已经限制为 512 KiB,此时应先决定应用契约是与其一致还是有意不同,再修订 proposal、spec、design 和 tasks,之后重新执行工件校验及受影响测试。

Sync

Sync 把当前变更的 delta spec 合并进 openspec/specs/,但不关闭活动变更。它适用于一个变更横跨多个 PR,而后续开发已经需要引用新行为基线的情况。执行前必须检查冲突,尤其是两个活动变更同时修改同一需求时。

Verify

可选 verify 工作流会从完整性、正确性和一致性三个维度比较工件与实现,可发现未完成任务、缺失场景测试和设计漂移。但它仍是 AI 辅助审查:

  • 除非明确要求且具备权限,否则它不会自动执行所有相关测试;
  • 它无法证明安全或性能属性;
  • 它不能独立审批自己刚生成的实现;
  • 它不会自动阻止 archive。

应把输出当成待核验的问题清单,而不是发布证书。

Archive

Archive 用于关闭变更。OpenSpec 会按增量语义处理:

  • ADDED 需求加入维护中的主规格;
  • MODIFIED 需求以完整新版本替换旧版本;
  • REMOVED 需求从主规格删除;
  • 变更目录移动到 openspec/changes/archive/。

归档前确认:

  • 已批准需求与实际交付行为一致;
  • 最终代码差异运行过全部必需检查;
  • 已知偏差已经解决或显式接受;
  • 重叠活动变更已经协调;
  • 发布和回滚说明存放在团队规定的位置;
  • 合并后的主规格仍准确描述当前行为。

归档是审计记录,不会自动成为未来每个模型都能调用的“长期记忆”。能否发挥作用,取决于检索方式、当前规格、仓库上下文以及评审者是否真正使用它。

团队中如何落地 OpenSpec

渐进式接入

第一次接入前,不要逆向整理整个遗留系统。先选择一个范围受控、行为可观察、风险可验证的变更,让已经验收的增量逐步构建主规格。

把工件放进版本控制

在正常 PR 中审查 openspec/、生成工作流文件和代码。可采用以下顺序:

  1. 起草并审查工件;
  2. 按批准版本实施;
  3. 附上场景到测试的证据;
  4. 一起审查最终代码与工件差异;
  5. 验收后在同一 PR 或受控后续 PR 中归档。

如果规划和实现拆成两个 PR,应记录实施所依据的准确工件 Commit。

区分全局规则与单次变更

AGENTS.md、CLAUDE.md 或工具规则用于描述长期编码约定;OpenSpec 工件用于描述一次行为变更。两者都不是安全边界。关键约束必须通过代码、测试、策略、权限和 CI 强制执行。

衡量结果,而不是工件数量

选择相似变更,比较:

  • 编码前评审发现的问题数;
  • 开始编码后发生的需求变更;
  • 交付周期与评审时间;
  • 线上逃逸缺陷和回滚率;
  • 计划外修改文件数;
  • 过期或冲突规格数;
  • 维护工件消耗的时间。

Markdown 更多不代表工程质量更高。只有可追踪性收益高于评审和维护成本时,才值得保留这套流程。

常见失败模式与排查

现象 常见原因 处理方式
找不到 /opsx:propose 当前宿主使用另一种语法,或未生成命令 查看 init 提示和宿主目录,运行 openspec update,再重启工具
只能看到六个工作流 正在使用默认 core Profile 运行 openspec config profile 选择所需工作流,再更新项目
没有 design.md 当前 Schema 判定无需设计,或工件图尚未完成 检查状态与 Schema,不要为了形式强行创建
openspec validate 通过但代码报错 校验只覆盖工件结构 运行编译、测试、静态分析和场景到代码审查
Apply 修改了无关文件 范围或任务过弱,或 Agent 执行漂移 停止执行,审查差异,收紧非目标与任务,只撤销非预期修改后继续
代码与规格不一致 需求只在对话中改变,或实施自行偏离 使用 update,重新审查工件并运行受影响证据
归档后主规格异常 MODIFIED 增量不完整,或活动变更冲突 补全完整需求,对比主规格与增量,解决后再归档
生成工作流看起来过时 CLI 或 Profile 已变,但项目文件未刷新 运行 openspec update 并审查生成差异

不要直接复制旧教程中的命令。旧命令名和目录结构会长期留在搜索结果中,应以本机当前版本生成的集成为准。

什么场景值得使用 OpenSpec

变更类型 建议流程
错别字或明显的一行修复 使用普通 Issue 与代码审查,完整变更工件可能只是噪声
验收行为清晰的局部功能 Propose、审查、Apply、测试、Archive
充满歧义的老项目改动 先 Explore 获取代码证据,再 Propose
鉴权、支付、隐私、迁移或不可逆数据变更 逐步生成工件,显式设计与威胁/故障审查,执行独立门禁
横跨多个 PR 的功能 Propose,随证据 Update,必要时 Sync,最终验收后 Archive
紧急事故修复 先按事故机制恢复服务,再补齐工件与证据,不能让形式阻碍止损
纯文档或纯工具修改 行为增量没有意义时,选择更轻的 Schema

OpenSpec 最适合“理解错误代价高,而可审查意图能降低风险”的变更。当工件维护成本高于它消除的不确定性时,就不应强行套用。

常见问题

OpenSpec 是什么,应该如何使用?

OpenSpec 是一套面向 AI 编程助手、以文本工件管理变更的开源规格驱动开发工作流。安装 CLI,执行 openspec init,在 AI 对话中调用生成的 explore 或 propose 工作流,审查工件,再执行任务、收集独立证据,并且只在行为与维护中的规格一致后归档。

为什么同一条 OpenSpec 命令在不同 AI 工具中写法不同?

各宿主加载 Skill 和命令的机制不同。Claude Code 可使用 /opsx:propose,Cursor 与 Trae 使用 /opsx-propose,Codex 使用 $openspec-propose。本地实际生成文件和初始化完成提示才是权威来源。修改 Profile 或工具后,应运行 openspec update 并重启宿主。

OpenSpec 可以渐进式接入老项目吗?

可以。先在分支中初始化,从一个边界明确的变更开始,调查并记录受影响领域的现有行为。不要先为整个遗留系统制造一套未经验证的规格;应让后续已经验收的变更逐步扩展主规格。

OpenSpec 能验证 AI 生成的代码正确吗?

不能。CLI validate 检查工件,可选 verify 工作流进行 AI 辅助的规格与实现对照。二者都不能替代构建、测试、安全与性能检查、人工审查和生产控制。失败后果越严重,证据就必须越独立、越接近真实运行环境。

OpenSpec 的 sync 与 archive 有什么区别?

Sync 把 delta spec 合并进主 openspec/specs/,但让变更继续保持活动状态;Archive 协调增量、关闭变更并把目录移入归档。执行两者前,都应检查并发变更冲突,并确认主规格仍描述已验收行为。

总结

可靠的 OpenSpec 流程不只是 Propose、Apply、Archive 三条命令,而是:

text
调查当前行为
  -> Explore 消除歧义
  -> Propose 创建工件
  -> 审查范围与场景
  -> Apply 执行受控任务
  -> 运行独立证据
  -> 协调实施中的新发现
  -> 必要时 Sync
  -> Archive 已验收行为

使用当前 AI 宿主实际生成的命令语法,区分主规格与变更增量,并把每份工件都当成待验证的声明。只有当这些声明能清晰映射到代码、测试、运行证据和受控发布时,规格驱动工作流才真正产生价值。

官方资料

相关资源