OpenSpec 学习笔记:客户端研发同学的 Spec Coding 实践参考

这篇文档是我在学习 OpenSpec 过程中,结合客户端研发(Android/iOS)实践的一些理解。不是权威指南,只是分享一些"如果我在Android和iOS工程里写代码,我会怎么用这个工具"的想法,供同学们参考和讨论。


1. 为什么客户端研发需要一个 Spec 层

1.1 Brownfield-first:存量项目友好的核心设计

在讲 OpenSpec 解决什么问题之前,我想先理解它的一个关键设计哲学:Brownfield-first(存量优先)

Brownfield = 已有 3~10 年历史的 Android/iOS 工程(存量代码库)
Greenfield = 从零开始的新项目

Brownfield-first 的意思是:工具/流程的设计优先考虑"如何在已有代码库里落地",而不是"理想状态下新项目怎么用"。

为什么这个理念对客户端特别重要?

假设 OpenSpec 是 Greenfield-first 的设计,它可能会要求你:

使用 OpenSpec 前,必须先把所有模块的历史行为写成 Spec:
├── feed/           ← 先把 Feed 流模块 5 年历史全部翻译
├── comment/        ← 再把评论模块 3 年历史全部翻译
├── user-profile/   ← 再把用户主页模块历史全部翻译
├── search/         ← 再把搜索模块历史全部翻译
├── notification/   ← 再把消息通知模块历史全部翻译
└── ...             ← 还有 20 个模块

结果:团队评估一下工作量,决定"算了,不用了"。

OpenSpec 的做法是允许从 0 开始,每次只写 Delta

第 1 次变更:add-feed-favorite
openspec/changes/add-feed-favorite/
├── proposal.md
├── design.md
├── tasks.md
└── specs/
    └── feed/
        └── spec.md     ← 只写这次改了什么(Delta)

第 1 次归档后:
openspec/changes/archive/2026-04-22-add-feed-favorite/
└── (完整的 change 目录,保留历史)
    ├── proposal.md
    ├── design.md
    ├── tasks.md
    └── specs/
        └── feed/
            └── spec.md ← Delta Spec 随 change 一起归档

第 2 次变更:fix-comment-empty-state
openspec/changes/fix-comment-empty-state/
├── proposal.md
├── design.md
├── tasks.md
└── specs/
    └── comment/
        └── spec.md     ← 只写评论模块的改动(Delta)

半年后(多次归档后):
openspec/changes/archive/
├── 2026-04-22-add-feed-favorite/
│   └── specs/feed/spec.md      ← feed 模块第 1 次变更
├── 2026-04-25-fix-comment-empty-state/
│   └── specs/comment/spec.md   ← comment 模块第 1 次变更
├── 2026-05-10-feed-optimize/
│   └── specs/feed/spec.md      ← feed 模块第 2 次变更
└── ...

**如何查看某个模块的完整 Spec?**
按模块名(如 `feed`)筛选 archive 中所有 `specs/feed/spec.md`,
按时间顺序阅读 Delta Spec,即可拼出该模块的完整行为契约。

核心差异

Greenfield-first Brownfield-first
准入门槛 先交历史债(补齐所有存量 Spec) 零门槛,从第一次变更开始
首次使用成本 高(可能几周) 低(一次变更就能用)
团队接受度 低(“等有空再说”) 高(“这迭代就能试”)
Spec 沉淀方式 一次性翻译 自然生长,每次归档累积

Brownfield-first = 不逼你先补齐 10 年历史 Spec,允许从第一次变更开始,让 Spec 自然生长。

这是 OpenSpec 能在存量客户端工程里落地的根本原因——它不是"理想状态下的最佳实践",而是"现实约束下的可行路径"。

1.2 客户端工程的复杂度本质

客户端工程的实际复杂度从来就不是"功能实现"本身,而是这些东西:

  • 同一份代码要在 OS 版本、机型、渠道、权限、网络等组合里稳定工作;
  • 一次变更要过审核、过灰度、过回滚窗口,不能"写完上线";
  • 需求描述永远是"做一个 XX",但工程上的完成标准是"空态/弱网/权限被拒/账号未登录/冷启动/后台被杀/多进程回来"要全部对;
  • 线上出问题的时候,你得能翻回去说清楚:“当时这个边界是谁确认的、为什么这么做”。

1.3 AI 辅助编码的真正卡点

在这种上下文里,AI 辅助编码真正的卡点不是"生成速度",而是:

  1. 需求边界靠猜:你让 AI 写"在 Feed 卡片加个收藏按钮",它不会主动问你"未登录态能不能点/点击后要不要弹登录/收藏失败怎么提示/动画时长多少/埋点事件叫什么"。
  2. 过程不可追溯:聊天记录里的决策,过一周你自己都找不到;代码 review 时解释不清"为什么选方案 A"。
  3. 协作不可规模化:每个人的提示词与产物结构不一致,最终成本被 Review、联调、回归与维护吞掉。

OpenSpec 要做的事就一件:在 AI 写代码之前,把上面这些东西变成结构化、可进 Git、可 Review、可归档的 Markdown 工件。

它不是又加一层 PRD 模板,而是给你一个人机共同填写的契约


2. OpenSpec 是什么:以变更为中心的 Spec 资产组织

OpenSpec 把一次"功能迭代 / Bug 修复 / 架构优化"视为一个独立的 Change,并用一个目录封装它的全生命周期:

  • proposal.md:为什么要做、要解决什么、有哪些方案与权衡、共识是什么。
  • tasks.md:怎么做、拆成哪些原子任务、优先级与验收点。
  • specs/:本次变更对应的结构化 Spec 增量(定义边界、规则与验收)。

这不是"多写文档",而是把 AI 的输出从"散落在聊天记录里"变成"可以进 Git 的工程资产"。

2.1 三个核心设计点(也是能否规模化的关键)

轻量低侵入:Markdown 驱动,天然适配 Git
  • 全部产物是 Markdown(规范、提案、任务清单),易写、易审、易 diff。
  • 不锁死工具:不绑定特定模型或外部密钥;任何能读项目上下文的 AI 工具都能配合。
  • 接入成本低:通过 CLI 初始化即可落地,对存量工程侵入小。
流动式工作流:随时修改,不回退阶段

与严格的阶段门控模式不同,OpenSpec 采用流动式动作模式——你可以随时修改任何产物(proposal、design、specs、tasks),不需要回退到上一阶段。这种设计的价值在于:

  • 实现中发现方案有问题? 直接改 design.md,不需要重新走 propose 流程;
  • Review 时发现需求漏了场景? 直接补 specs/,不需要撤销已生成的 tasks;
  • 代码写到一半发现任务拆得太粗? 直接细化 tasks.md,AI 接着当前进度继续。

结构始终清晰,灵活性更高,对抗开发过程的天然不确定性。

对比:阶段门控 vs 流动式

维度 阶段门控(如 speckit) 流动式(OpenSpec)
流程特点 必须完成上一阶段才能进入下一阶段 随时可修改任何产物
修改规范 需要回退流程,对抗开发过程 直接修改,不回退
适用场景 流程严谨、变更类型固定的团队 需求变化快、需要灵活调整的团队
客户端适配 修改成本高,容易卡在流程里 随时调整,更符合客户端迭代节奏
存量友好(Brownfield-first):变更历史即 Spec 资产

OpenSpec 的关键设计是变更历史即资产

  • openspec/changes/:正在推进的变更(工作区)
  • openspec/changes/archive/:已完成的变更(历史归档,含完整 Delta Spec)

每个归档的 change 都保留了当时的 proposal、design、tasks 和 Delta Spec。想看某个模块的完整行为契约?去 archive 里按模块名筛选、按时间顺序阅读即可。这让你可以在不扰动主工程结构、不打断稳定迭代节奏的前提下,逐步把 Spec/流程引入存量项目。

2.2 最小目录结构:把"可控交付"的骨架落到仓库

openspec/
  changes/                  # 以变更为中心的工作区(每次变更一个目录)
    add-feed-favorite/      # 正在推进的变更
      proposal.md
      tasks.md
      specs/
        ...                 # 本次变更新增/修改的 Spec
    archive/                # 已完成的变更(历史归档)
      2026-04-22-add-feed-favorite/
        proposal.md
        tasks.md
        specs/
          ...               # 该次变更的 Delta Spec(保留历史)

与"写文档"相比,这个结构更像"工程工作流":每一次变更都有入口、有边界、有拆解、有归档。归档后的 specs/ 分散在各 change 目录里,按模块名筛选、按时间顺序阅读即可拼出完整功能描述,成为新成员理解项目的"功能手册"。


3. OpenSpec 心智模型与数据流

3.1 一张图看懂一次变更

 openspec/
 └── changes/                     ← 变更工作区
      ├── add-feed-favorite/      ← 正在推进的变更
      │    ├── proposal.md        ← 为什么做 / 范围 / 选型
      │    ├── design.md          ← 技术方案 / 架构决策
      │    ├── tasks.md           ← 可勾选的实现清单
      │    └── specs/             ← 本次变更的 Delta Spec(增/改/删)
      │         └── feed/
      │             └── spec.md   ← 只写"相对当前 Spec 的变化"
      │
      └── archive/                ← 已完成的变更(历史归档)
           └── 2026-04-22-add-feed-favorite/
                ├── proposal.md   ← 保留完整历史
                ├── design.md
                ├── tasks.md
                └── specs/
                     └── feed/
                         └── spec.md  ← 该次变更的 Delta Spec

归档(触发 openspec-archive-change skill)的时候,只做一件事:

归档变更文件夹:把 openspec/changes/add-feed-favorite/ 整体挪到 openspec/changes/archive/2026-04-22-add-feed-favorite/ 存档。

Delta Spec 始终保留在各自 change 的目录里,不会合并到某个中心目录。想看某个模块的完整 Spec?去 archive 里按模块名筛选、按时间顺序阅读即可。

一个关键问题:OpenSpec 怎么知道"这次变更对应的上次 Spec 是谁"?

答案是不主动知道——OpenSpec 没有中心化的 Source of Truth。每个 change 的 Delta Spec 都是独立的,模块路径名(如 feed/)只是分类标签,用于你人工筛选和阅读,不参与自动合并。

这意味着:

  • 两个 change 同时改同一个模块时,各自独立归档changes/add-feed-favorite/changes/fix-feed-crash/ 都有 specs/feed/spec.md,它们会分别成为 archive/2026-04-22-add-feed-favorite/archive/2026-04-23-fix-feed-crash/。想看 feed 模块的完整历史?把这两个目录里的 specs/feed/spec.md 按时间顺序读一遍。
  • change 之间不需要互相感知,每个 change 自包含,天然支持并行。

另一个关键问题:feed 这个模块名是怎么来的?

模块名(如 feed)是根据变更影响的业务领域确定的,不是 change 名的一部分,也不是自动生成的随机 ID:

名称 示例 谁定的 作用
Change 名 add-feed-favorite 你在 propose 时指定 工作区目录标识,方便人看
模块名 feed 根据变更影响的业务领域确定 Spec 文件的分类目录,用于归档后按模块筛选

确定模块名的流程

  1. 你提意图:“在 Feed 卡片加个收藏按钮”
  2. AI 判断领域:"收藏按钮"属于Feed 流领域
  3. 生成模块目录:changes/add-feed-favorite/specs/feed/spec.md

模块名的命名约定

  • 业务领域导向,不是技术实现导向:用 auth-system/ 而不是 login-activity/,用 push-service/ 而不是 firebase-messaging/
  • 常见客户端模块名:auth-system/(登录认证)、push-service/(推送)、routing-system/(路由/Deep Link)、webview-container/(WebView 容器)、gps-service/(定位)、player-engine/(音视频)、payment-system/(支付)、share-service/(分享)

3.2 端到端数据流

  人给 AI 写 intent
        │
        ▼
 你:"帮我起一个 OpenSpec change,在 Feed 卡片加个收藏按钮"
        │
        ▼
 ┌────────────────────────────────────────┐
 │  AI 生成四件套(按 schema 依赖顺序)     │
 │                                        │
 │   proposal.md  ──► specs/feed/spec.md  │
 │        │             │                 │
 │        └────► design.md                │
 │                      │                 │
 │                      ▼                 │
 │                  tasks.md              │
 └────────────────────────────────────────┘
        │
        ▼
 人 Review 四件套(Gate 1)  ◄─── 这一步决定了后面 AI 写的代码能不能用
        │
        ▼
 你:"按 tasks.md 开始实现"    ←── AI 按 tasks.md 一步步写代码、打勾
        │
        ▼
 你:"帮我 archive 这个 change"  ←── 目录归档到 archive/,保留完整历史

两张图记住就行:Changes 是工作区,Archive 是历史库,Delta Spec 分散在各 change 里


4. 客户端研发视角下的核心概念对齐

把 OpenSpec 的几个抽象名词,翻译成我们熟悉的语言。

OpenSpec 概念 客户端研发的对应物 说明
Spec (specs/) 模块当前的"行为契约",类似 readme + 验收标准 + 约束条款 不写实现细节(不写"我们用 Kotlin 协程还是 RxJava");只写"外部可观测的行为"(如"Feed 卡片收藏按钮点击后 200ms 内必须显示收藏成功状态")
Change (changes/<name>/) 一次需求/重构/Bug 修复,对应 Git branch/MR 一个 change 里proposal/design/tasks/specs全都有,不需要去别处找上下文
Delta Spec “本次变更对 Spec 的 Diff”(ADDED/MODIFIED/REMOVED),团队内部叫规范增量 每次变更只写改了什么,归档后保留在 change 目录里
Proposal 需求文档 + 技术选型讨论纪要的合集 回答:为什么做、范围是什么、有哪些候选方案;团队内部口径是"背景 / 方案论证 / 共识"
Design 技术方案评审文档 架构决策、数据流、依赖变化、文件级改动清单
Tasks 可勾选的开发任务清单(带 1.1 / 1.2 分级) 每个 task 小到能一次 session 内做完、可独立回滚
Scenario Given/When/Then 验收用例 跟 UI 测试的测试用例一脉相承
Schema 工件模板及依赖关系 默认是 spec-driven,可以 fork 定制成你们团队的节奏
Archive 已完成的变更目录 保留完整历史(proposal/design/tasks/specs),按模块名筛选可拼出完整 Spec

核心心智模型一句话:Spec = 行为契约,Change = 一次交付盒子,Delta = Diff,Tasks = 可勾选清单,Archive = 历史库

4.2 刻意的 50KB 上下文限制

OpenSpec 刻意将规范产物总大小限制在 50KB 以内。这个设计的原因是:

  • 避免大型项目中规范占满 AI 的上下文窗口,挤压代码空间;
  • 确保规范始终是精炼的核心信息,只描述"做什么"和"为什么做",不冗余;
  • 50KB 足够容纳一个完整客户端项目的所有模块规范,对绝大多数场景都够用。

4.3 Delta Spec 的结构示意

Delta Spec 只写"相对当前 Spec 的变化",用 ADDED/MODIFIED/REMOVED 三段式组织:

## ADDED Requirements
### Requirement: xxx
#### Scenario: xxx
- **GIVEN** 前置条件(当前处于什么状态)
- **WHEN** 用户做了什么操作
- **THEN** 系统应该有什么响应

## MODIFIED Requirements
### Requirement: xxx
(Previously: ...)

## REMOVED Requirements
### Requirement: xxx
(Deprecated: ...)

注意几件事:

  • 只写行为,不写实现。实现细节属于 design.md,Spec 只写可被外部观测到的行为

  • SHALL/MUST/SHOULD/MAY 是 RFC 2119 标准词汇,用来明确需求强度,消除"应该/必须/可以"的歧义:

    词汇 强度 客户端场景举例
    MUST / SHALL 绝对要求,必须遵守 “App MUST complete session validation within 500ms” —— 硬性性能指标,做不到就是 Bug
    SHOULD 强烈推荐,有正当理由可以例外 “App SHOULD show retry button on network error” —— 一般情况要有,特殊场景可降级
    MAY 可选,做不做都行 “App MAY support haptic feedback on button tap” —— 加分项,不做不扣分
    MUST NOT 绝对禁止 “App MUST NOT store plaintext password in local storage” —— 安全红线

    Review 时看到 MUST 就知道必须写测试用例,看到 SHOULD 知道可以讨论降级方案,看到 MAY 知道不阻塞上线。

  • Scenario 必须能写成测试用例。任何一个 Scenario 模糊到写不出 UI 自动化 case,就是 Spec 还没对齐。

完整的 Delta Spec 示例见 §6.2(Feed 卡片作者认证标识案例)。


5. 核心机制与上手路径

5.1 环境前置与安装

node -v   # 要求 Node.js ≥ 20.19.0
npm -v

Android/iOS 同学机器上如果没 Node,建议直接用 nvm:

# macOS
brew install nvm
nvm install 20
nvm use 20

全局安装:

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

团队落地现状autolite 项目已经跑过一次 openspec init,可以跳过初始化,直接从 5.2 开始用自然语言触发。只有新仓库需要做这一步。

cd path/to/your-android-or-ios-repo
openspec init

openspec init 会做两件事:

  1. 在仓库根创建 openspec/ 目录(含 specs/changes/、可选的 config.yaml);
  2. 按你选的 AI 工具(Trae/Cursor/Claude Code/Codex/…)生成对应的 skills 和 slash commands 到 .trae/ / .cursor/ / .claude/ 等目录。

对客户端工程来说这一步是零侵入的:它不碰 app/ios/build.gradlePodfile,只是往根目录加个 openspec/ 和一个隐藏目录。.gitignore 里也完全可以决定要不要把 AI 工具的 skills 目录纳入版本控制(通常建议团队共享)。

5.2 用 Trae 跑完一次最小变更(推荐:自然语言触发)

OpenSpec 已经全面 skills 化。在 Trae 里,你直接输入自然语言描述意图(如"帮我起一个 OpenSpec change,在 Feed 卡片加个收藏按钮"),Trae 会识别并调用 .trae/skills/openspec-propose/ 下的 skill 生成工件。Cursor / Claude Code 等工具同理,通过各自目录下的 skill 文件触发。

前置条件:

  • 仓库已运行过 openspec init(或直接用 autolite);
  • .trae/skills/openspec-*/SKILL.md 已生成;
  • 你在 Trae 聊天框里。

典型的自然语言触发示例:

你:帮我起一个 OpenSpec change,叫 add-feed-favorite,
   目标是在 Feed 卡片加个收藏按钮。

Trae:(自动识别到 openspec skill 触发词)
     ✓ 已创建 openspec/changes/add-feed-favorite/
     ✓ proposal.md — 背景 / 范围 / 方案候选
     ✓ specs/      — 新增行为契约与 Scenario
     ✓ design.md   — 技术选型与决策
     ✓ tasks.md    — 可勾选实现清单

你:我看了一下,tasks.md 里 1.2 拆得太粗,再细化一下。

Trae:(继续调用 skill)
     ✓ tasks.md 已更新:1.2 拆成 1.2.1 / 1.2.2 / 1.2.3

你:OK,按 tasks.md 开始实现。

Trae:(逐条打勾实现并自动打卡 tasks)...

你:全部 checklist 都勾完了,帮我 archive 这个 change。

Trae:(触发归档 skill)
     ✓ 已检查所有 tasks 已完成
     ✓ 变更目录已归档至 openspec/changes/archive/2026-04-22-add-feed-favorite/
     ✓ Delta Spec 保留在 archive 目录中,feed 模块历史可追溯

审查四要点:生成产物后,建议按以下清单快速 Review——

  1. proposal.md 有无遗漏或多余内容?
  2. specs/ 里的需求是否覆盖所有完整场景(含空态/错误态)?
  3. design.md 的技术方案是否合理,是否复用了项目现有模式?
  4. tasks.md 的任务分解是否清晰,依赖顺序是否正确?

花 5 分钟审查规范,可以省下至少 2 小时的返工时间。

  • 触发词是"OpenSpec / change / propose / archive"之类的语义关键词;
  • Trae 通过自然语言识别触发 skill,不需要记命令;
  • 对于不熟的同学,直接描述意图是更低的学习门槛——这也是团队选它的重要原因。

新会话怎么继续之前的 change?

Trae 关闭会话后聊天记录会丢失,但 OpenSpec 的工件在仓库里——文件就是跨会话的持久化记忆

# 会话 1:生成了 change,做了部分任务
你:帮我起一个 OpenSpec change,叫 add-feed-favorite...
Trae:✓ 已创建 openspec/changes/add-feed-favorite/
...
(会话 1 关闭)

# 会话 2:直接引用 change 目录继续
你:帮我继续 openspec/changes/add-feed-favorite/ 这个 change,
   当前 tasks.md 做到 1.2,发现收藏动画性能有问题,改成淡入淡出。

Trae:(读取 change 目录下的所有工件)
     ✓ 已加载 proposal.md / design.md / tasks.md / specs/
     ✓ 当前进度:1.1 已完成,1.2 进行中
     ✓ design.md 已更新:Decision: 淡入淡出替代缩放动画
     ✓ tasks.md 已更新:1.2 改为淡入淡出实现
     ✓ 继续实现...

不需要复述之前的讨论,所有上下文都在工件文件里。如果 AI 没有自动读取,可以显式指定:

你:请先读取 openspec/changes/add-feed-favorite/tasks.md,
   告诉我当前哪些 task 已完成、哪些还没做。

5.3 核心操作一览

OpenSpec 的核心操作只有四个,覆盖一次变更的完整生命周期。在 Trae 里通过自然语言触发对应的 skill:

自然语言触发示例 调用的 skill 作用 对应阶段
“帮我起一个 OpenSpec change…” openspec-propose 创建 change 并生成所有 planning 工件 Gate 1
“按 tasks.md 开始实现” openspec-apply-change 按 tasks.md 逐条实现并打卡 Gate 2~4
“帮我 archive 这个 change” openspec-archive-change 归档 change,Delta Spec 保留在 archive 目录中 收尾
“帮我分析一下这个问题” openspec-explore 纯思考模式,不生成代码,只梳理问题 预研

注意propose 会按 schema 依赖顺序一次性生成所有工件(proposal → specs → design → tasks),不需要手动逐个创建。

Trae 没有 /opsx: 命令:Trae 通过自然语言识别触发 skill,不需要输入类似 /opsx:propose 的斜杠命令。Cursor / Claude Code 等工具可能有各自的 slash command 机制,但 Trae 的方式是直接说话

5.4 关键机制 1:Change-centric 与 Delta Specs

每个 changes/<name>/ 目录都是自包含的:proposal/design/tasks/specs 全在一块。它解决了我们最常见的两个痛点:

  • 并行不冲突add-feed-favoritefix-comment-empty-state 可以同时存在,各自一个目录,互不影响。
  • Review 友好:Reviewer 不用翻聊天记录,一个目录读完就知道"这次改什么、为什么、怎么验收"。

Delta Specs 是存量工程的救命稻草。客户端项目普遍有 3~10 年历史,大量模块从来没有过正经 Spec。如果要求"先把存量 Spec 补齐再开始用",没人能落地。Delta Spec 允许 Spec 从 0 开始增长——你第一次用 OpenSpec,直接写本次变更的 Delta;每次归档后 Delta Spec 保留在 archive 目录里。半年以后,archive 里自然会积累 feed/comment/user-profile/search/notification 等模块的变更历史,按模块名筛选即可拼出完整行为契约。

这是 OpenSpec 敢说自己"brownfield-first"的底气——它不要求你先交"历史债"。

5.5 关键机制 2:Schema 与工件依赖关系

默认 schema 叫 spec-driven,定义了四个工件的依赖关系:

              proposal
                 │
        ┌────────┴────────┐
        ▼                 ▼
      specs            design
        │                 │
        └────────┬────────┘
                 ▼
               tasks
                 │
                 ▼
            implement

依赖是"enabler"而不是"gate"——它告诉你"现在可以做 specs 了",但不强制你必须先做 specs 才能做 design。客户端场景下,typical 组合:

  • 纯 UI 变动:跳过 design,proposal → specs → tasks;
  • 架构改造:重点在 design,specs 只写对外行为约束;
  • 修 Bug:proposal 写清"问题现象 + 根因",specs 用 Delta 修改原有 Requirement。

注意:OpenSpec Schema ≠ 客户端 URL Scheme

这是两个完全不同的概念,只是恰好用了同一个英文词:

OpenSpec Schema 客户端 URL Scheme
中文 工件模板/流程模板 URL 协议/跳转链接
作用 定义一次变更要产出哪些文档、它们的依赖顺序 定义 App 能响应哪种 URL、外部如何唤起 App
例子 spec-driven(proposal → specs → design → tasks) dcarnew://weixin://taobao://
自定义方式 openspec schema fork Info.plistAndroidManifest.xml 注册

为什么需要自定义 Schema?

Schema 不是高端用法,是团队规模化后的必然需求——不同变更类型需要不同的工件组合:

变更类型 需要的工件 自定义 Schema 示例
紧急 Bug 修复 proposal(问题+根因)→ tasks(修复+验证) rapid-fix:跳过 specs 和 design
纯 UI 还原 proposal(范围)→ specs(验收标准)→ tasks(逐组件) ui-implementation:跳过 design
架构改造 全量四件套,design 为重点 spec-driven 默认即可
团队有特殊流程 在默认基础上加合规审查/安全评审等工件 dcd-team:fork 后加 compliance

自定义 Schema 的命令:

# 1. fork:基于现有 schema 改一份自己的
openspec schema fork spec-driven client-pragmatic
# 结果:在 openspec/schemas/client-pragmatic/ 生成 schema.yaml

# 2. init:从零创建一个极简 schema
openspec schema init rapid --artifacts "proposal,tasks" --default
# 结果:只要求 proposal 和 tasks 两个工件

# 3. validate:检查你改的 schema 有没有语法错误
openspec schema validate client-pragmatic

注意:Schema 自定义是 CLI 层功能,skill 层仍通过 openspec status --json 读取当前 schema 的依赖顺序,无需感知 schema 的具体定义。

5.6 扩展模式(点到即止)

默认只启用 4 个核心命令(propose/apply/archive/explore)。如果需要更细粒度的控制,可以开启扩展工作流:

openspec config profile   # 交互式勾选需要的命令
openspec update           # 让配置生效

扩展模式下会新增几个操作,典型场景:

自然语言触发示例 调用的 skill 作用 适用场景
“帮我创建一个空的 change 脚手架” openspec-new 仅创建 change 目录,不生成工件 想手动控制每个工件的生成时机
“快速生成所有 planning 工件” openspec-fast-forward 一次性生成 proposal/design/tasks/specs 需求明确,效果和 propose 一致
“帮我验证一下代码是否符合 Spec” openspec-verify 对照规范检查代码实现完整性 归档前自查,提前发现遗漏

建议:团队初期用默认 4 个操作即可,等流程跑顺了再按需开启扩展。扩展模式不是"高级功能",只是给需要更细粒度控制的同学多一个选项。

5.7 关键机制 3:Update vs Start Fresh

这是团队协作里容易吵架的点。OpenSpec 给出的判断矩阵:

情况 动作
目标没变、只是做法微调(发现更合适的方案) 在原 change 里更新
范围缩小(先 MVP,后续再加) 在原 change 里更新
学习驱动的修正(代码库结构跟想象不一样) 在原 change 里更新
问题本身变了(从"加 Apple 登录"变成"重构登录体系") 新 change
范围爆炸(原 proposal 已经不成立) 新 change
原 change 可以独立完结 新 change

口诀:“同一件事就更新,不是同一件事就新开”

5.8 关键机制 4:多 change 合并的正确方式

重要:archive 是单 change 操作,不会自动合并多个 change。

如果你发现多个 change 其实应该合并成一个(比如它们共同构成一个完整功能),正确做法是在归档前手动合并,而不是依赖 archive 自动处理:

Step 1:新建一个合并 change

你:帮我起一个 OpenSpec change,叫 payment-refactor-merge

Step 2:手动整理多个 change 的工件

changes/payment-v1/changes/payment-v2/changes/payment-v3/ 的工件整合到 changes/payment-refactor-merge/

  • 合并 proposal.md:综合多个 change 的 Intent、Scope、Approach
  • 合并 specs/:把多个 Delta Spec 整合成一个完整的 Delta Spec
  • 合并 design.md:整合所有架构决策
  • 合并 tasks.md:重新编号,确保依赖关系正确

Step 3:删除旧 change

rm -rf openspec/changes/payment-v1
rm -rf openspec/changes/payment-v2
rm -rf openspec/changes/payment-v3

Step 4:归档合并后的 change

你:帮我 archive payment-refactor-merge 这个 change

为什么必须在归档前合并?

  • archive 只是将单个 change 目录移动到 archive/,不涉及 Spec 合并
  • 多个 change 的 proposal.md 可能互相矛盾(比如 v1 说"只做 A",v2 说"只做 B")
  • 多个 change 的 tasks.md 可能有依赖关系(v2 依赖 v1 的某个 task)
  • 分散在多个 change 里的 Delta Spec 需要人工整合才能形成完整视图

一句话:archive 是"收尾",不是"合并"。合并是人工决策,必须在 archive 前完成。


6. 客户端落地案例:Feed 卡片新增"作者认证"标识

6.1 背景

Feed 流卡片需要新增一个"作者认证"标识:当作者为平台认证用户时,在昵称旁显示蓝色认证图标。点击图标弹出认证信息浮层。涉及 UI 布局调整、接口字段新增、空态/错误态处理。

这是一个典型的客户端 UI 需求,不涉及服务端大改动,但有多状态组合(认证/未认证/加载失败/接口未返回字段)。

6.2 用 OpenSpec 怎么拆

Step 1. proposal.md(1 屏内写完)

# Proposal: Feed 卡片新增作者认证标识

## Intent
Feed 流作者辨识度不足,用户难以区分官方账号与普通用户。
在作者昵称旁增加认证标识,提升内容可信度。

## Scope
In scope:
- Feed 卡片 UI:昵称右侧新增认证图标(认证用户显示,普通用户不显示)
- 点击认证图标弹出浮层,展示认证类型(官方 / 达人 / 机构)
- 接口:/feed/list 新增 `author.verified` 字段(bool)和 `author.verified_type`(enum)
- 空态:接口未返回字段时,按未认证处理,不显示图标
- 错误态:接口返回 500 时,不影响卡片其他内容展示

Out of scope:
- 作者主页的认证标识(另起 change)
- 认证申请流程(业务方负责)

## Approach
最小侵入:复用现有 Feed 卡片布局,在昵称 TextView 右侧增加 ImageView。
认证信息浮层用 BottomSheetDialog 实现,不复用现有弹窗组件(避免耦合)。

Step 2. specs/feed/spec.md(Delta)

## ADDED Requirements

### Requirement: Feed 卡片作者认证标识
当 `author.verified` 为 true 时,Feed 卡片**MUST**在作者昵称旁显示认证标识。

#### Scenario: 认证作者
- **GIVEN** Feed 数据项中 `author.verified` 为 true
- **WHEN** 卡片渲染时
- **THEN** 昵称右侧立即显示蓝色认证图标
- **AND** 图标尺寸为 16dp × 16dp,与昵称垂直居中对齐

#### Scenario: 未认证作者
- **GIVEN** Feed 数据项中 `author.verified` 为 false 或缺失
- **WHEN** 卡片渲染时
- **THEN** 不显示认证图标
- **AND** 昵称布局不为图标预留空间

#### Scenario: 点击认证标识
- **GIVEN** Feed 卡片上显示了认证标识
- **WHEN** 用户点击该标识
- **THEN** 200ms 内弹出 BottomSheetDialog
- **AND** 弹窗展示 `verified_type` 标签(官方 / 达人 / 机构)
- **AND** 点击弹窗外区域或按返回键可关闭弹窗

#### Scenario: 接口返回 500 错误
- **GIVEN** /feed/list 接口返回 500
- **WHEN** 错误被处理时
- **THEN** Feed 列表展示现有错误状态(不显示认证图标)
- **AND** 错误不会导致应用崩溃或影响其他卡片

Step 3. design.md(关键架构决策)

## Decision: ImageView vs Compose Icon
使用 ImageView 加载 SVG:
- 现有 Feed 卡片是 XML + ViewHolder 体系,引入 Compose 增加编译成本和包体积
- 认证图标是静态资源,不需要动态主题色

## Decision: 接口字段降级策略
如果 `author.verified` 字段缺失(老版本服务端),按 `false` 处理:
- 避免客户端崩溃
- 老服务端上线期间用户体验一致(不显示图标)

## Decision: 浮层不复用现有弹窗
现有弹窗组件耦合了分享,独立实现避免依赖。

Step 4. tasks.md(可勾选 + 可独立回滚)

## 1. UI 布局
- [ ] 1.1 drawable 目录添加 blue_verified_badge.svg
- [ ] 1.2 feed_item_card.xml: 在 nickname TextView 右侧添加 16dp × 16dp ImageView
- [ ] 1.3 FeedViewHolder: bind 方法绑定 author.verified 字段,控制可见性
- [ ] 1.4 点击 badge 触发 BottomSheetDialog 弹出

## 2. 弹窗逻辑
- [ ] 2.1 VerifiedBadgeDialog 布局:icon + 认证类型文字 + 关闭按钮
- [ ] 2.2 显示认证类型:官方/达人/机构,对应不同文案
- [ ] 2.3 点击 outside 或 back 关闭弹窗

## 3. 降级逻辑 & 测试
- [ ] 3.1 接口缺失 `author.verified` 时按 false 处理
- [ ] 3.2 UI 自动化测试:四个 Scenario 覆盖
- [ ] 3.3 埋点:add click 事件(badge_clicked)

6.3 带来的工程收益

  • Review 效率:Reviewer 看一个目录 5 分钟就能知道"做什么、边界在哪、怎么验收";
  • 所有状态都对齐了:认证/未认证、点击弹出、接口缺失、API 错误都在 Scenario 里写清楚了,AI 写代码不会漏;
  • 归档可追溯:3 个月后你改这个 badge 布局,能在 openspec/changes/archive/ 里翻到"为什么选 ImageView 不选 Compose Icon"。

6.4 哪些场景适合这么写?

  • 新增 UI 组件(如认证 badge、新按钮、弹窗):适合用 OpenSpec 对齐所有状态;
  • 接口字段新增:降级策略必须写清楚,避免 crash;
  • 复杂重构:拆分后每个 task 可独立回滚;

6.5 哪些场景不适合这么写?

  • 一行改动的热修(比如改个文案错误):OpenSpec 的 ceremony 比代码还长;
  • 纯视觉 UI 微调(改个颜色、对齐):走正常 MR 就行;
  • 探索性 POC:还没收敛到方案之前,建议先用 openspec-explore skill 纯粹当 thinking partner,别急着生成工件。

7. OpenSpec × AI 工具:Trae / Cursor / Claude Code 怎么共存

OpenSpec 把"谁来执行 Slash Command"这层抽象掉了,所以它能同时支持 25+ AI 工具。客户端同学常见的组合:

工具 Skills 安装路径 Slash Command 路径 备注
Trae .trae/skills/openspec-*/SKILL.md 无独立 command 目录 通过自然语言触发 skill
Cursor .cursor/skills/openspec-*/SKILL.md .cursor/commands/opsx-<id>.md 有 Slash command 机制
Claude Code .claude/skills/openspec-*/SKILL.md .claude/commands/opsx/<id>.md Slash command 直接命中
Codex CLI .codex/skills/openspec-*/SKILL.md $CODEX_HOME/prompts/opsx-<id>.md 注意是全局 prompt 目录
GitHub Copilot .github/skills/openspec-*/SKILL.md .github/prompts/opsx-<id>.prompt.md VS Code 等 IDE 插件识别

一仓库多工具是正常操作:openspec init --tools claude,cursor,trae 就可以同时配好,团队里每个人用自己习惯的工具,但生成的 openspec/changes/<name>/ 工件完全兼容


8. 结语

如果把 AI 辅助编码拆成三层:

  • 底层:模型能力(GPT-5、Claude Opus 4.5 等);
  • 中层:Harness / Context(Trae、Cursor、Claude Code、Skills 等);
  • 上层:Spec 层(OpenSpec 这样的工件协议)。

客户端研发的现实是:前两层我们影响不了,能抓在自己手里的只有第三层

OpenSpec 提供的不是一个新工具,而是一种工程资产化的 AI 协作范式——把人和 AI 的对齐沉到 Git 里,变成 review、传承、复盘都能用的硬资产。对客户端这种讲灰度、讲回滚、讲审计的工程,它比"更快生成代码"重要得多。

当团队开始把"提案/拆解/规格/验收/归档"变成可版本化资产,AI 才真正从"效率工具"升级为"交付系统的一部分"。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐