avatar
原创2026/03/20

OpenSpec:AI 时代的规格标准化

AI 总结

介绍 OpenSpec 的核心理念——一种面向 AI 工具的机器可读规格格式,剖析其文档结构、与 OpenAPI 和 JSON Schema 的关系、AI 工具如何解析和消费 OpenSpec,以及它如何将 Spec Coding 从个人实践提升为可协作的工程标准。

Spec Coding 解决了一个核心问题:在让 AI 写代码之前,先把规格写清楚。但它还有一个遗留问题——规格本身没有统一的格式。每个人写的 Spec 长相各异,AI 能读懂,但不同格式给 AI 的"信号密度"差异很大,也无法在工具之间共享和复用。

OpenSpec 是对这个问题的回答:一种面向 AI 工具设计的、标准化的机器可读规格格式。

为什么需要标准化

现有的规格标准如 OpenAPI、JSON Schema 并不是为 AI 消费设计的。它们的目的是文档化和验证,而不是指导生成。OpenAPI 描述的是一个 API 已经长什么样,而 OpenSpec 描述的是一个功能应该长什么样——包含约束、边界条件、验收标准和明确的不在范围内的部分。

OpenSpec 的文档结构

一个 OpenSpec 文档是一个 YAML 文件,包含以下顶层字段:

openspec: "1.0"
 
info:
  title: User Login
  description: 用户通过邮箱 + 密码登录,获取访问 token
  version: "1.0.0"
  author: benson
 
constraints:
  - id: C001
    description: 密码使用 argon2id 哈希存储,不得使用 MD5 或 SHA1
  - id: C002
    description: JWT 存储在 httpOnly Cookie,不得出现在响应 body 中
  - id: C003
    description: 连续失败 5 次后锁定账户 15 分钟
 
interface:
  inputs:
    - name: email
      type: string
      format: email
      required: true
    - name: password
      type: string
      minLength: 8
      required: true
  outputs:
    success:
      status: 200
      schema:
        user:
          id: string
          email: string
          name: string
    errors:
      - status: 401
        condition: 凭据错误或账户不存在
        schema:
          error: "Invalid credentials"
      - status: 429
        condition: 账户已锁定
        schema:
          error: "Account locked"
          retryAfter: number
 
acceptance_criteria:
  - id: AC001
    description: 正确凭据可以登录并在响应中设置 httpOnly Cookie
    testable: true
  - id: AC002
    description: 错误凭据返回 401,响应内容不区分"账户不存在"和"密码错误"
    testable: true
  - id: AC003
    description: 第 5 次失败后返回 429,retryAfter 为 900(秒)
    testable: true
  - id: AC004
    description: 登录成功后,该账户的失败计数归零
    testable: true
 
out_of_scope:
  - OAuth / 第三方登录
  - 多因素认证(MFA)
  - 记住我(Remember Me)功能
 
tech_stack:
  language: typescript
  framework: nextjs
  database: postgresql
  orm: prisma

每个字段都有明确的语义,AI 工具在解析时无需做任何猜测。

AI 工具如何消费 OpenSpec

AI 编码助手在接收到 OpenSpec 文件后会经历三个阶段:

结构解析

工具将 YAML 解析为内部数据结构,提取关键信息:

interface ParsedSpec {
  constraints: Constraint[]       // 硬性限制,不可违反
  interface: InterfaceDefinition  // 输入输出的类型约束
  acceptanceCriteria: AC[]        // 验收条件,直接对应测试用例
  outOfScope: string[]            // 排除范围,防止过度实现
  techStack: TechStack            // 技术选型,决定生成代码的风格
}

约束注入

解析结果被转化为结构化的 System Prompt 注入 LLM 上下文。constraints 被标注为最高优先级,out_of_scope 被明确列为禁止实现,防止模型"顺手"多做:

[CONSTRAINTS - MUST NOT VIOLATE]
- C001: Use argon2id for password hashing. MD5 and SHA1 are forbidden.
- C002: JWT must be set via httpOnly Cookie only.
- C003: Lock account for 900 seconds after 5 consecutive failures.
 
[INTERFACE CONTRACT]
Input:  { email: string(email), password: string(min:8) }
Output: 200 → { user: {id, email, name} } + Set-Cookie header
        401 → { error: "Invalid credentials" }
        429 → { error: "Account locked", retryAfter: number }
 
[OUT OF SCOPE - DO NOT IMPLEMENT]
- OAuth / third-party login, MFA, Remember Me
 
[TECH STACK]
TypeScript + Next.js App Router + Prisma + PostgreSQL

验收测试生成

acceptance_criteria 字段直接被转化为测试骨架,每个 testable: true 的条目对应一个 test 块:

// 由 OpenSpec AC001~AC004 自动生成
describe('POST /auth/login', () => {
  test('AC001: 正确凭据设置 httpOnly Cookie', async () => {
    const res = await request(app).post('/auth/login')
      .send({ email: 'user@example.com', password: 'correct-password' })
    expect(res.status).toBe(200)
    expect(res.headers['set-cookie']).toEqual(
      expect.arrayContaining([expect.stringContaining('HttpOnly')])
    )
  })
 
  test('AC002: 错误凭据返回统一错误信息', async () => {
    const res = await request(app).post('/auth/login')
      .send({ email: 'user@example.com', password: 'wrong-password' })
    expect(res.status).toBe(401)
    expect(res.body.error).toBe('Invalid credentials')
  })
 
  test('AC003: 连续失败 5 次后锁定账户', async () => {
    for (let i = 0; i < 5; i++) {
      await request(app).post('/auth/login')
        .send({ email: 'user@example.com', password: 'wrong' })
    }
    const res = await request(app).post('/auth/login')
      .send({ email: 'user@example.com', password: 'wrong' })
    expect(res.status).toBe(429)
    expect(res.body.retryAfter).toBe(900)
  })
 
  test('AC004: 登录成功后失败计数归零', async () => {
    // ...
  })
})

这个过程不需要 AI 猜测应该测什么——测什么完全由 Spec 决定。

OpenSpec 与 Agentic Loop

OpenSpec 和 Agentic Loop 的结合是自然的。一个 AI Agent 在拿到 OpenSpec 文件后可以自主执行:

读取 OpenSpec
    │
    ▼
生成实现代码(遵循 constraints + interface)
    │
    ▼
生成测试代码(基于 acceptance_criteria)
    │
    ▼
运行测试
    │
    ├── 全部通过 ──> 输出完成信号,loop 结束
    │
    └── 存在失败 ──> 分析失败原因,修改实现代码,重新运行测试

OpenSpec 在这里扮演了"事实来源"的角色——Agent 在每轮循环中都可以回头对照 Spec 检查自己的实现是否偏离了约束。

安装与使用

OpenSpec 提供了官方的 CLI 工具,将整个 spec-driven 工作流封装为一组命令。

安装

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

初始化项目

Terminal
cd your-project
openspec init

这会在项目里生成 openspec/ 目录和对应的 AI 指令文件。

核心工作流

第一步:探索阶段(可选)

/opsx:explore

AI 会读取代码,分析现有架构,帮你权衡方案。

第二步:创建 Spec 提案

/opsx:propose add-dark-mode

AI 自动在 openspec/changes/add-dark-mode/ 目录下生成四份文件:

openspec/changes/add-dark-mode/
├── proposal.md   # 做什么、为什么做
├── specs/        # 需求与验收场景
├── design.md     # 技术方案
└── tasks.md      # 实现清单

审查并修改这些文件,确认规格正确后再进入实现。

第三步:按 Spec 实现

/opsx:apply

AI 按 tasks.md 中的清单逐条实现,每完成一项打勾。

第四步:归档

/opsx:archive

变更记录被移入 openspec/changes/archive/,保留完整的决策历史。

OpenSpec 官方建议搭配高推理能力模型使用,推荐使用 Opus 4.7 或同等水平的模型。每次开始 /opsx:apply 前最好清空 context window,避免历史对话噪音干扰 AI 对 Spec 的理解。

总结

OpenSpec 的本质是给 Spec Coding 加上一层协议。自然语言 Spec 依赖 AI 的理解能力,不同写法给模型的信号强度不同,也难以在工具之间复用。OpenSpec 通过标准化的 YAML 结构将约束、接口、验收标准和范围边界分离到独立字段,让 AI 工具能精确消费每一类信息。

从 Vibe Coding 到 Spec Coding,再到 OpenSpec,本质上是同一件事的三个层次:把对 AI 的输入从"随意"变成"有结构",从"有结构"变成"可标准化"。

点击播放