一、故事背景 (虚拟背景)
某天我看到新同事在做 “扣费灰度” 功能,但他没有复用项目里沉淀好的 GrayReleaseUtil,而是直接让 AI 生成了一套全新的灰度逻辑。
我心里很清楚:
“这样下去,项目里会有N套灰度工具,最后没人敢改。”
正好我在研究AI相关的一些东西,于是我决定,用 SDD 来规范这件事。
二、SDD / OpenSpec 极简解析
| 概念 | 一句话解释 |
|---|---|
| SDD | Spec-Driven Development(规格驱动开发):把需求、规则和系统现状写成可追踪的 Spec,不让 AI 瞎写。 |
| OpenSpec | 管理这些文件的工具。 |
| 规则层 (skills/) | 项目长期稳定的规范(比如:灰度必须用 GrayReleaseUtil)。 |
| 变更层 (changes/) | 某一次具体需求的记录。 |
| 全局唯一真相 (openspec/specs/) | 系统当前状态的唯一权威描述(Source of Truth)。 |
三、操作流程
第 1 步:建立规则层
看到同事乱写后,我意识到项目缺了“规矩”文档。
我先在项目根目录创建了 skills/ 目录,把项目缺乏的规矩写成文件。
操作:
mkdir -p skills/gray-release
touch skills/gray-release/SKILL.md
编辑 skills/gray-release/SKILL.md:
---
name: gray-release
description: 新功能需要灰度发布 / 流量分桶 / AB 测试
allowed-tools: [Read, Edit, Bash, Grep]
---
# Gray Release · iOS 项目规范
## 项目事实(必须遵守)
- 统一使用 `GrayReleaseUtil` 类
- 路径:`Sources/Utils/GrayReleaseUtil.swift`
- 已稳定运行 2 年,禁止自研
## 标准用法
```swift
let isInGray = GrayReleaseUtil.route(featureKey: "feature_key")
if isInGray {
// 新逻辑
} else {
// 老逻辑
}
```
✅ 做完这一步,AI 才知道“灰度”在我们项目里意味着什么。
第 2 步:启动变更(New)
有了规矩,我开始做需求。
我执行 /opsx:new 命令,告诉 OpenSpec 我要开始一个新的变更了。
操作:
/opsx:new add-deduction-grey-release
或者是
/opsx:propose add-deduction-grey-release
// 这个命令会自动帮你把 openspec/changes/add-deduction-grey-release/目录
// 以及里面的 proposal.md, design.md, tasks.md全部建好
结果:
OpenSpec 自动创建了变更目录结构(此时 openspec/specs/ 还是旧内容)。
第 3 步:编写 Proposal 和 Design
我打开 openspec/changes/add-deduction-grey-release/ 目录,开始写文档。
3.1 Proposal.md(定方向)
## Why
新扣费算法涉及资金安全,需灰度开关以降低全量风险。
## What Changes
- 在 `BillingService.swift` 的 `processDeduction()` 入口接入灰度判断。
- 新增配置中心开关 `deduction_v2_ios_20260115`。
- 新逻辑走 `DeductionEngineV2`,老逻辑保留。
## Capabilities
### Modified Capabilities
- `billing-calculation`: 扣费入口新增灰度分支。
## Impact
- **Reuses**: `skills/gray-release/SKILL.md`
- **依赖**: `GrayReleaseUtil`
✅ 关键点: 我在 Impact 里显式引用了刚才创建的 Skill。
3.2 Design.md(定方案)
## 技术选型
- 灰度工具:`GrayReleaseUtil`
- 配置中心:Firebase Remote Config
## 逻辑设计
- App 启动时拉取灰度配置并缓存。
- 扣费前判断灰度状态,分发至对应引擎。
- 新引擎 `DeductionEngineV2` 独立封装。
第 4 步:AI 自动加载与写代码(Apply)
文档写完后,我让 AI 开始干活。
因为我在 Proposal 里引用了 Skill,AI 会自动加载 skills/gray-release/SKILL.md。
操作:
/opsx:apply
发生了什么:
- AI 读取 proposal.md 和 design.md。
- AI 自动加载 skills/gray-release/SKILL.md。
- AI 知道必须用 GrayReleaseUtil,没有瞎写新逻辑。
- AI 按 tasks.md 清单修改 BillingService.swift。
- 生成 execute.log 记录过程。
第 5 步:归档与完善规则(Archive)
代码上线后,一切顺利。我执行归档,把这次变更合并到全局真相,并完善了规则层。
操作:
/opsx:archive
结果:
- 更新全局唯一真相:openspec/specs/billing-calculation/spec.md 变成了包含灰度逻辑的新版本。
- 归档变更目录:OpenSpec 会把本次 change 从 active changes 移动到
openspec/changes/archive/YYYY-MM-DD-add-deduction-grey-release/。 - 完善规则层:基于这次的实现经验,我补充了 skills/gray-release/SKILL.md 的细节(v1.1),让规则更清晰。
补充说明:
-
/opsx:sync负责把 change 里的增量 spec 同步到openspec/specs/,但 change 仍然是 active 状态。 -
/opsx:archive是收尾动作:如果主 spec 还没同步,它会提示或处理同步,然后把 change 移入 archive 历史目录。
完善后的 skills/gray-release/SKILL.md:
## 最佳实践
- ✅ 灰度状态在 ViewModel 初始化时判断一次,缓存结果。
- ✅ 支付按钮需处理重复点击,确保单次操作唯一。
✅ 规则层更完善了,下次新同事做灰度,AI 会自动用对。
四、目录变化(全流程)
1️⃣ 开发前(初始状态)
MyApp/
├── Sources/
│ └── Billing/
│ └── BillingService.swift # 只有老扣费逻辑
│
├── skills/ # ❌ 还没有灰度规范
├── openspec/
│ ├── specs/ # 全局唯一真相(旧版)
│ │ └── billing-calculation/
│ │ └── spec.md
│ └── changes/ # 空
├── AGENTS.md
└── Package.swift
2️⃣ 开发中(执行 /opsx:new 后)
MyApp/
├── Sources/
│ └── Billing/
│ └── BillingService.swift # 正在改
│
├── skills/ # ✅ 规则层(我第一步创建的)
│ └── gray-release/
│ └── SKILL.md # 规定:必须用 GrayReleaseUtil
│
├── openspec/
│ ├── specs/ # 仍是旧真相
│ │ └── billing-calculation/
│ │ └── spec.md
│ │
│ └── changes/ # ✅ 新增变更目录
│ └── add-deduction-grey-release/
│ ├── proposal.md # 为什么做
│ ├── design.md # 怎么做
│ ├── tasks.md # 具体任务
│ ├── execute.log # AI 执行记录
│ └── specs/ # 本次增量改动
│ └── billing-calculation/ ✅ Capability 同名
│ └── spec.md # 只写新增灰度规则
├── AGENTS.md
└── Package.swift
3️⃣ 开发后(归档完成)
MyApp/
├── Sources/
│ └── Billing/
│ └── BillingService.swift # ✅ 已合入灰度
│
├── skills/
│ └── gray-release/
│ └── SKILL.md # ✅ 规则沉淀(v1.1,更完善)
│
├── openspec/
│ ├── specs/ # ✅ 已更新为全局唯一真相
│ │ └── billing-calculation/
│ │ └── spec.md
│ │
│ └── changes/
│ └── archive/ # ✅ 历史档案
│ └── 2026-05-13-add-deduction-grey-release/
│ ├── proposal.md
│ ├── design.md
│ ├── tasks.md
│ ├── execute.log
│ └── specs/
│ └── billing-calculation/
│ └── spec.md
├── AGENTS.md
└── Package.swift
五、SDD 的 8 步
- 先有规则层
把项目规范(比如开关功能)写成 SKILL.md。 - 启动 change
执行 /opsx:new add-deduction-grey-release。 - AI 自动加载 skill
AI 读到 SKILL.md,知道不能乱写,因为 description (描述)匹配 - 写 proposal (提案)
显式引用 skills/gray-release/SKILL.md。 - Capability 同名是关键
billing-calculation 在 proposal 声明处、change specs 目录处、主 spec 目录处 这三处必须同名。 - AI 按 tasks 写代码
执行 /opsx:apply,AI 严格复用 GrayReleaseUtil。 - Archive 回写 skill
执行 /opsx:archive,规则层从 v1.0 升到 v1.1(更完善)。 - AI 自动用对
新同事下次做灰度,AI 自动用对。
六、特别说明
这里的 Skill,说的是「规矩」不是「本事」
SDD / OpenSpec 里写在 SKILL.md 里的 Skill,指的是团队定死的规矩:这件事必须怎么做、哪些做法一律不许。