一、什么是OpenSpec?

OpenSpec 是 Fission AI 团队发起的轻量级开源规范驱动开发(SDD)框架与CLI工具,专为AI辅助编程场景设计,核心理念是先写规格(Spec),再写代码,通过结构化文档锁定需求,让人类开发者主导、AI严格执行开发任务。

核心定位与特点

  • 规范驱动:以openspec/specs/(项目真实行为规范,事实来源)、openspec/changes/(变更提案)双文件夹模型管理需求。
  • 轻量无侵入:无需API密钥、极简配置,兼容Cursor、Claude Code、GitHub Copilot等主流AI编码工具。
  • 全生命周期管理:覆盖提案→规范→实现→验证→归档的完整变更流程。
  • 语言无关:一套Spec可驱动Python、Java、Go等多语言实现,保证业务逻辑一致性。

二、OpenSpec有什么作用?

1. 解决AI编程核心痛点

  • 需求对齐:避免AI因模糊提示生成不符合需求的代码、遗漏功能或添加冗余逻辑。
  • 可追溯可控:所有变更文档化、版本化,需求与代码一一对应,便于审计与回溯。
  • 降低沟通成本:Spec作为人与AI、团队成员间的契约,减少反复沟通与返工。
  • 上下文优化:变更独立归档,AI仅读取相关文档,减少token消耗与上下文丢失。

2. 核心价值

  • 确定性开发:先锁定“做什么”,再实现“怎么做”,产出可预测、可审查的代码。
  • 存量项目友好:无需重构现有代码,可逐步引入规范,适配新旧项目。
  • 知识沉淀:归档的Spec自动更新项目主规范,形成可复用的技术知识库。

三、OpenSpec怎么使用?(核心工作流)

1. 环境准备

  • 安装Node.js(v16+),执行CLI安装命令:
    npm install -g @fission-ai/openspec@latest
  • 验证安装:openspec --version

2. 项目初始化

在项目根目录执行,生成openspec/核心目录:

openspec init

目录结构:
your-project/
├── openspec/
│ ├── specs/ # 项目主规范(事实来源)
│ └── changes/ # 变更提案(待实现/已归档)
└── ... # 项目源码

3. 标准开发流程(5步)

(1)创建变更提案(Propose)

发起新功能/修复,生成变更文件夹与初始文档:

# 命令行方式
openspec new change add-user-login-otp
# AI工具内(如Cursor)使用斜杠命令
/opsx:propose 新增用户登录OTP二次验证功能

自动生成文件:

  • proposal.md:需求背景、目标、收益
  • specs/:功能规范(接口、数据模型、约束)
  • design.md:技术方案、架构设计
  • tasks.md:可执行任务清单(待办/已完成)

(2)编写与审核规范(Spec)

细化specs/下的文档,明确功能需求、接口定义、数据约束、验收标准,人类开发者审核通过后进入实现阶段。

(3)AI实现代码(Apply)

AI基于审核后的Spec生成/修改代码,严格遵循规范:

# 命令行
openspec apply add-user-login-otp
# AI工具内
/opsx:apply

(4)测试验证(Verify)

运行测试用例,验证代码是否符合Spec,修复不一致问题:

openspec verify add-user-login-otp

(5)归档变更(Archive)

验证通过后,将变更合并到主规范specs/,完成开发:

openspec archive add-user-login-otp

四、Python应用程序实战案例(FastAPI + Pydantic)

场景:基于OpenSpec实现“用户登录OTP验证”功能

1. 初始化Python项目

mkdir python-openspec-demo && cd python-openspec-demo
openspec init
# 安装依赖
pip install fastapi pydantic uvicorn pytest

2. 创建变更提案

openspec new change add-user-otp-login

编辑openspec/changes/add-user-otp-login/specs/auth/spec.md(核心规范):

# 用户登录OTP验证规范
## 功能需求
1. 用户提交手机号+密码,验证通过后返回OTP挑战
2. 提交OTP验证码,验证通过后返回登录Token
3. OTP有效期5分钟,最多尝试3次

## 接口定义
### POST /api/auth/login
- 请求:`{"phone": "13800138000", "password": "xxx"}`
- 响应(200):`{"challenge_id": "uuid", "message": "OTP已发送"}`
- 响应(400):`{"error": "invalid_credentials"}`

### POST /api/auth/verify-otp
- 请求:`{"challenge_id": "uuid", "otp_code": "123456"}`
- 响应(200):`{"token": "jwt_token", "user_id": "1"}`
- 响应(400):`{"error": "invalid_otp"}`
- 响应(400):`{"error": "otp_expired"}`

## 数据模型
- UserLoginRequest: phone(str, 11位), password(str, 6-20位)
- OTPVerifyRequest: challenge_id(str, uuid), otp_code(str, 6位数字)
- LoginResponse: token(str), user_id(str)

3. AI生成代码(Apply)

执行openspec apply add-user-otp-login,AI生成Python代码:

src/auth/models.py(数据模型)

from pydantic import BaseModel, Field, constr
from uuid import UUID

class UserLoginRequest(BaseModel):
phone: constr(regex=r'^1[3-9]\d{9}$') = Field(..., description="11位手机号")
password: constr(min_length=6, max_length=20) = Field(..., description="密码")

class OTPVerifyRequest(BaseModel):
challenge_id: UUID = Field(..., description="挑战ID")
otp_code: constr(regex=r'^\d{6}$') = Field(..., description="6位OTP验证码")

class LoginResponse(BaseModel):
token: str = Field(..., description="JWT登录令牌")
user_id: str = Field(..., description="用户ID")

src/auth/service.py(业务逻辑)

from typing import Dict, Optional
from uuid import uuid4, UUID
from datetime import datetime, timedelta
from .models import UserLoginRequest, OTPVerifyRequest, LoginResponse

# 模拟存储
_challenges: Dict[UUID, Dict] = {}
_users = {"13800138000": {"user_id": "1", "password": "123456"}}

class AuthService:
def generate_otp(self, req: UserLoginRequest) -> Dict:
# 验证用户密码
user = _users.get(req.phone)
if not user or user["password"] != req.password:
raise ValueError("invalid_credentials")
# 生成OTP挑战
challenge_id = uuid4()
_challenges[challenge_id] = {
"user_id": user["user_id"],
"otp": "123456", # 模拟OTP
"expires_at": datetime.now() + timedelta(minutes=5),
"attempts": 0
}
return {"challenge_id": str(challenge_id), "message": "OTP已发送"}

def verify_otp(self, req: OTPVerifyRequest) -> LoginResponse:
challenge = _challenges.get(req.challenge_id)
if not challenge:
raise ValueError("invalid_challenge")
# 验证OTP与有效期
if challenge["attempts"] >= 3:
raise ValueError("otp_locked")
if datetime.now() > challenge["expires_at"]:
raise ValueError("otp_expired")
if req.otp_code != challenge["otp"]:
challenge["attempts"] += 1
raise ValueError("invalid_otp")
# 生成Token
return LoginResponse(token=f"jwt_{challenge['user_id']}", user_id=challenge["user_id"])

src/main.py(FastAPI入口)

from fastapi import FastAPI, HTTPException
from src.auth.models import UserLoginRequest, OTPVerifyRequest, LoginResponse
from src.auth.service import AuthService

app = FastAPI(title="OpenSpec Python Demo")
auth_service = AuthService()

@app.post("/api/auth/login", response_model=Dict)
def login(req: UserLoginRequest):
try:
return auth_service.generate_otp(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))

@app.post("/api/auth/verify-otp", response_model=LoginResponse)
def verify_otp(req: OTPVerifyRequest):
try:
return auth_service.verify_otp(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

4. 验证与归档

# 运行测试(需补充pytest用例)
pytest
# 归档变更
openspec archive add-user-otp-login

五、SpringBoot应用程序实战案例

场景:基于OpenSpec实现“优惠券验证”功能

1. 初始化SpringBoot项目

mkdir springboot-openspec-demo && cd springboot-openspec-demo
openspec init
# 创建SpringBoot项目(Spring Initializr),添加Web、Lombok、Validation依赖

2. 创建变更提案

openspec new change add-coupon-validation

编辑openspec/changes/add-coupon-validation/specs/coupon/spec.md

# 优惠券验证规范
## 功能需求
1. 验证优惠券是否存在、未过期、未使用
2. 返回验证结果与折扣金额
3. 优惠券码唯一,有效期不超过30天

## 接口定义
### GET /api/coupon/validate
- 参数:code(必填, String), user_id(必填, String)
- 响应(200):`{"valid": true, "discount_amount": 100, "message": "验证通过"}`
- 响应(400):`{"valid": false, "error": "coupon_not_found"}`
- 响应(400):`{"valid": false, "error": "coupon_expired"}`
- 响应(400):`{"valid": false, "error": "coupon_used"}`

## 数据模型
- Coupon: id(Long), code(String), amount(Integer), expire_at(LocalDateTime), is_used(Boolean)
- CouponValidateResponse: valid(Boolean), discount_amount(Integer), error(String), message(String)

3. AI生成代码(Apply)

执行openspec apply add-coupon-validation,AI生成Java代码:

src/main/java/com/demo/model/Coupon.java

package com.demo.model;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class Coupon {
private Long id;
private String code;
private Integer discountAmount;
private LocalDateTime expireAt;
private Boolean isUsed;
}

src/main/java/com/demo/dto/CouponValidateResponse.java

package com.demo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class CouponValidateResponse {
private Boolean valid;
private Integer discountAmount;
private String error;
private String message;
}

src/main/java/com/demo/service/CouponService.java

package com.demo.service;

import com.demo.dto.CouponValidateResponse;
import com.demo.model.Coupon;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Service
public class CouponService {
// 模拟存储
private final Map<String, Coupon> couponMap = new HashMap<>();
private final Map<String, String> usedCoupons = new HashMap<>();

public CouponService() {
// 初始化测试优惠券
Coupon coupon = new Coupon();
coupon.setId(1L);
coupon.setCode("TEST100");
coupon.setDiscountAmount(100);
coupon.setExpireAt(LocalDateTime.now().plusDays(7));
coupon.setIsUsed(false);
couponMap.put("TEST100", coupon);
}

public CouponValidateResponse validateCoupon(String code, String userId) {
Coupon coupon = couponMap.get(code);
// 验证优惠券存在
if (coupon == null) {
return new CouponValidateResponse(false, 0, "coupon_not_found", "优惠券不存在");
}
// 验证有效期
if (LocalDateTime.now().isAfter(coupon.getExpireAt())) {
return new CouponValidateResponse(false, 0, "coupon_expired", "优惠券已过期");
}
// 验证是否已使用
if (usedCoupons.containsKey(code) && usedCoupons.get(code).equals(userId)) {
return new CouponValidateResponse(false, 0, "coupon_used", "优惠券已使用");
}
// 验证通过
usedCoupons.put(code, userId);
return new CouponValidateResponse(true, coupon.getDiscountAmount(), null, "验证通过");
}
}

src/main/java/com/demo/controller/CouponController.java

package com.demo.controller;

import com.demo.dto.CouponValidateResponse;
import com.demo.service.CouponService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/coupon")
public class CouponController {

@Autowired
private CouponService couponService;

@GetMapping("/validate")
public CouponValidateResponse validateCoupon(
@RequestParam String code,
@RequestParam String userId) {
return couponService.validateCoupon(code, userId);
}
}

4. 验证与归档

# 启动SpringBoot项目,测试接口
# 归档变更
openspec archive add-coupon-validation

六、总结

OpenSpec通过规范先行、AI执行、全程可控的模式,彻底解决AI辅助开发中的需求模糊、代码不可控问题,同时实现多语言一致性与知识沉淀。无论是Python快速开发还是SpringBoot企业级应用,均可通过标准化流程落地,提升开发效率与代码质量。