本文不是吹牛,不是画饼,是手把手教你把三个 Claude Code 神器串成一条全自动生产线。读完如果你还不会,来找我,我请你喝奶茶(真的)。
一、前言:为什么你的 AI 编程还在"石器时代"?
假设你叫小明,是一个 frontend 仔。某天老板过来说:"我们要在应用里加一个学习打卡功能,用户可以记录每天学了多久,还要有连续打卡天数和排行榜。"
你打开 Claude Code,信心满满地输入:
给我做一个学习打卡功能
Claude 噼里啪啦给你生成了一堆代码。你一看,好像能用,直接 git commit 推上去了。
三天后,测试妹妹跑来:
- "这个连续打卡天数,跨时区怎么办?"
- "排行榜没有分页,数据多了会卡死吧?"
- "你这不是打卡,这是打老板的脸啊!"
你傻了。因为你没锁需求 ,没写测试 ,没走流程 ,没做验收------四个"没",直接把一个简单功能做成了技术债本债。
今天这篇文章,就是来终结这种悲剧的。我要教你把 OpenSpec、Superpowers、gstack 三个工具拧成一股绳,让 AI 编程从"拍脑袋写代码"进化到"流水线式交付"。
而且,全程自动化,你只需要输入几个斜杠命令,剩下的工具自己串起来。
准备好了吗?我们开始。
二、三器合一到底是什么?别慌,一层一层剥洋葱
很多教程上来就扔概念,把你砸晕。我不这么干。我们先认识三个主角,再用一个真实项目实例串起来。
2.1 三个主角的自我介绍
| 工具 | 负责哪一层 | 核心作用 | 人话解释 |
|---|---|---|---|
| OpenSpec | 需求层 | 写代码前锁住需求,生成标准产物 | 产品经理的替身,不会改需求的替身 |
| Superpowers | 质量层 | TDD 铁律 + HARDGATE 门禁 | 代码警察,没测试不让过,烂代码直接拦 |
| gstack | 流程层 | 七阶段 Sprint 管线 + 自动发布 | 项目经理的替身,管流程、管发布、管归档 |
关键认知: 这三个工具不是"你用完了我用"的接力赛,而是在同一个 Claude Code 会话里同时存在、自动触发、层层衔接。
想象一条火锅流水线:
- OpenSpec 是备菜间(把需求切好、码好)
- Superpowers 是品控员(没熟的肉不准上桌)
- gstack 是服务员 + 收银(端到端服务,吃完还帮你记账)
三者各干各的,但流水线上自动交接。
2.2 它们为什么不打架?
因为状态隔离:
markdown
项目根目录/
├── openspec/ <- OpenSpec 的地盘
│ ├── proposals/
│ ├── specs/
│ ├── designs/
│ └── tasks/
├── .claude/
│ ├── skills/ <- Superpowers 的地盘
│ └── CLAUDE.md <- Superpowers 的规则
└── gstack/ <- gstack 的地盘
├── sessions/
├── reviews/
└── archive/
三套状态,井水不犯河水。OpenSpec 管需求文档,Superpowers 管代码规则,gstack 管流程状态。谁也别抢谁的活。
三、实战项目:给 duolinguo 加一个"学习打卡"功能
为了让你看得懂、学得会,我们用一个完整的真实案例走完全流程。
3.1 项目背景
你有一个叫 duolinguo 的应用(就在你桌面上),里面已经有了用户系统、课程列表。现在要加一个新功能:
功能名称: 每日学习打卡(Daily Check-in)
核心需求:
- 用户每天学习满 15 分钟,自动视为"已打卡"
- 显示连续打卡天数(Streak),断了就归零
- 全局排行榜,按连续天数排名
- 个人中心展示打卡日历(本月每天是否打卡)
技术栈: Next.js 14 + TypeScript + Prisma + PostgreSQL + TailwindCSS
这个项目结构大概是:
perl
duolinguo/
└── my-app/
├── app/ # Next.js App Router
├── components/ # React 组件
├── lib/ # 工具函数
├── prisma/
│ └── schema.prisma # 数据库模型
└── tests/ # 测试文件
好,背景交代完毕。现在进入七阶段流水线。
四、阶段一:OpenSpec ------ 先把需求焊死,谁都别想改
4.1 为什么必须先锁需求?
想象一下:你吭哧吭哧写了一整天代码,产品经理突然说"排行榜不要了,改成成就徽章"。你是什么心情?
OpenSpec 的作用就是在写第一行代码之前 ,把需求写成结构化、可验证、不可篡改的规格说明书。它会成为整个流水线的"唯一真相源"。
4.2 安装与配置 OpenSpec
OpenSpec 不是一个 npm 包,而是一套 Claude Code 的 Skill 工作流。你需要:
Step 1:创建 OpenSpec 目录结构
在你的项目根目录(duolinguo/my-app)执行:
bash
mkdir -p openspec/{proposals,specs,designs,tasks}
Step 2:创建 OpenSpec 配置文件
在 openspec/.openspecrc 中写入:
yaml
# OpenSpec 配置文件
project:
name: "duolinguo-daily-checkin"
version: "1.0.0"
description: "每日学习打卡功能"
phases:
- proposal # 提案阶段
- spec # 规格阶段
- design # 设计阶段
- task # 任务拆分阶段
rules:
require_approval: true # 每个阶段需要人工确认
auto_archive: true # 完成后自动归档
traceability: true # 需求可追踪
output:
format: markdown
directory: ./openspec
Step 3:在 CLAUDE.md 中注册 OpenSpec
在项目根目录创建/编辑 CLAUDE.md:
markdown
# duolinguo 项目规范
## OpenSpec 集成
- 所有新功能必须通过 OpenSpec 流程
- 需求产物存放于 `openspec/` 目录
- 使用 `/opspect` 命令启动 OpenSpec 工作流
## Superpowers 集成
- 编码必须遵循 TDD 铁律
- 所有业务逻辑必须有单元测试
- 使用 `/tdd` 命令启动测试驱动开发
## gstack 集成
- 发布必须通过 gstack 的 ship 流程
- 使用 `/ship` 命令执行发布
4.3 运行 OpenSpec:生成需求产物
在 Claude Code 中输入:
arduino
/opspect new "每日学习打卡功能"
OpenSpec 会引导你完成四个文件的生成:
文件 1:Proposal(提案)
openspec/proposals/2026-07-18-daily-checkin.md
markdown
---
id: PROP-001
title: 每日学习打卡功能
author: sen
date: 2026-07-18
status: approved
---
# 提案:每日学习打卡
## 问题陈述
用户缺乏学习动力,没有机制激励每日学习。需要增加游戏化元素提升留存。
## 解决方案概述
增加"每日打卡"系统:
- 每日学习满 15 分钟自动打卡
- 连续打卡累计 Streak
- 全局排行榜增加竞争感
- 打卡日历提供视觉反馈
## 成功指标
- 日活用户打卡率 > 30%
- 平均连续打卡天数 > 3 天
## 影响范围
- 数据库:新增 CheckIn、Streak 模型
- 前端:新增打卡页面、日历组件、排行榜
- API:新增 4 个接口
文件 2:Spec(规格说明书)
openspec/specs/2026-07-18-daily-checkin-spec.md
markdown
---
id: SPEC-001
proposal: PROP-001
status: approved
---
# 规格说明书:每日学习打卡
## 1. 功能规格
### 1.1 自动打卡
- **触发条件:** 用户单日学习时长 >= 15 分钟
- **判定逻辑:** 以 UTC 自然日为准,00:00-23:59 内累计时长
- **去重规则:** 每个自然日最多打卡 1 次
### 1.2 连续打卡(Streak)
- **定义:** 连续自然日都有打卡记录
- **中断规则:** 某一自然日未打卡,Streak 归零
- **时区处理:** 以用户设置时区为准,默认 UTC+8
### 1.3 排行榜
- **维度:** 按当前 Streak 天数降序
- **分页:** 每页 20 条
- **更新频率:** 实时(基于数据库查询)
### 1.4 打卡日历
- **展示范围:** 当前月份
- **状态:** 已打卡 / 未打卡 / 今日
- **交互:** 点击日期显示当日学习详情
## 2. 数据规格
### 2.1 CheckIn 表
```prisma
model CheckIn {
id Int @id @default(autoincrement())
userId String
date DateTime @db.Date
duration Int // 学习时长(分钟)
createdAt DateTime @default(now())
@@unique([userId, date])
@@index([userId, date])
}
2.2 Streak 表(缓存表,可重建)
prisma
model Streak {
id Int @id @default(autoincrement())
userId String @unique
currentStreak Int @default(0)
longestStreak Int @default(0)
lastCheckInDate DateTime?
updatedAt DateTime @updatedAt
}
3. API 规格
3.1 POST /api/checkin
- 描述: 记录学习时长,触发打卡判定
- 请求:
{ duration: number }(分钟) - 响应:
{ checkedIn: boolean, streak: number }
3.2 GET /api/streak
- 描述: 获取当前用户 Streak 信息
- 响应:
{ current: number, longest: number, lastCheckIn: string }
3.3 GET /api/leaderboard
- 描述: 获取排行榜
- 参数:
?page=1&limit=20 - 响应:
{ users: [{ rank, name, streak, avatar }], total }
3.4 GET /api/calendar
- 描述: 获取打卡日历数据
- 参数:
?year=2026&month=7 - 响应:
{ days: [{ date, checkedIn, duration }] }
yaml
#### 文件 3:Design(设计文档)
`openspec/designs/2026-07-18-daily-checkin-design.md`
```markdown
---
id: DESIGN-001
spec: SPEC-001
status: approved
---
# 设计文档:每日学习打卡
## 1. 页面结构
/checkin # 打卡主页面 ├── Header # 标题 + 今日状态 ├── StreakCard # 连续天数大卡片 ├── CalendarWidget # 本月打卡日历 ├── LeaderboardPreview # 排行榜前 5 名 └── ActionButton # "去学习" CTA
markdown
## 2. 组件清单
| 组件名 | 路径 | 职责 |
|--------|------|------|
| StreakCard | `components/checkin/StreakCard.tsx` | 展示火焰图标 + 天数 |
| CheckInCalendar | `components/checkin/CheckInCalendar.tsx` | 月历格子渲染 |
| LeaderboardRow | `components/checkin/LeaderboardRow.tsx` | 单行排行 |
| FlameIcon | `components/icons/FlameIcon.tsx` | 火焰 SVG |
## 3. 状态管理
- 使用 React Server Components 获取初始数据
- 客户端状态用 useState + useEffect 处理打卡动画
- 排行榜数据采用 ISR,每 60 秒重新验证
## 4. 动画设计
- 打卡成功:火焰图标跳动 + Confetti 效果
- Streak 增加:数字滚动动画
- 日历格子:hover 放大 1.05 倍
文件 4:Tasks(任务拆分)
openspec/tasks/2026-07-18-daily-checkin-tasks.md
markdown
---
id: TASK-001
spec: SPEC-001
design: DESIGN-001
status: ready
---
# 任务拆分:每日学习打卡
## 后端任务
- [ ] **B1:** 数据库迁移 - 创建 CheckIn 和 Streak 表
- [ ] **B2:** API 实现 - POST /api/checkin
- [ ] **B3:** API 实现 - GET /api/streak
- [ ] **B4:** API 实现 - GET /api/leaderboard
- [ ] **B5:** API 实现 - GET /api/calendar
## 前端任务
- [ ] **F1:** 组件开发 - StreakCard
- [ ] **F2:** 组件开发 - CheckInCalendar
- [ ] **F3:** 组件开发 - LeaderboardRow + LeaderboardList
- [ ] **F4:** 页面组装 - /checkin 主页面
- [ ] **F5:** 动画效果 - 打卡成功反馈
## 测试任务
- [ ] **T1:** 单元测试 - CheckIn 业务逻辑
- [ ] **T2:** 单元测试 - Streak 计算逻辑(含边界)
- [ ] **T3:** API 测试 - 所有端点
- [ ] **T4:** E2E 测试 - 完整打卡流程
4.4 串联点一:OpenSpec 产物 -> gstack 评审输入
OpenSpec 生成的这四个文件,不是写完就锁抽屉里的。它们会自动成为 gstack 的评审输入。
具体怎么衔接?看 gstack 的配置(后面会讲),gstack 会读取 openspec/ 目录下的所有产物,作为 auto plan 阶段的输入材料。
也就是说,当你打完 /gstack plan,gstack 已经知道你做了什么需求分析、什么设计决策,它会基于这些做四类评审(CEEO、工程设计、DX、安全),而不是凭空评审。
五、阶段二:gstack auto plan ------ 让流程引擎读需求,做专业评审
5.1 gstack 是什么?
gstack 是一个全流程 Sprint 管线工具。它的核心能力是:
- Browse 引擎:自动读取项目上下文(代码、文档、配置)
- 七阶段管线:plan -> design -> implement -> review -> QA -> ship -> archive
- 自动推进:一个阶段通过,自动触发下一个阶段
5.2 安装与配置 gstack
Step 1:创建 gstack 目录结构
bash
mkdir -p gstack/{sessions,reviews,archive,config}
Step 2:创建 gstack 配置文件
gstack/config/pipeline.yaml
yaml
# gstack 管线配置
pipeline:
name: "duolinguo-checkin-pipeline"
version: "1.0"
# 七阶段定义
phases:
- name: plan
enabled: true
auto_trigger: true
inputs:
- openspec/ # 自动读取 OpenSpec 产物
- name: design
enabled: false # 跳过,因为 OpenSpec 已经做了设计
reason: "Design handled by OpenSpec"
- name: implement
enabled: true
depends_on: [plan]
- name: review
enabled: true
depends_on: [implement]
- name: qa
enabled: true
depends_on: [review]
tools:
- playwright
- chromium
- name: ship
enabled: true
depends_on: [qa]
actions:
- bump_version
- generate_changelog
- create_pr
- name: archive
enabled: true
depends_on: [ship]
output: openspec/archive/
# 评审规则
review_rules:
categories:
- ceoo # 正确性、效率、组织、优化
- engineering # 工程设计质量
- dx # 开发者体验
- security # 安全扫描
thresholds:
critical: 0 # 不允许有任何严重问题
warning: 5 # 警告不超过 5 个
# 与 Superpowers 的集成
integrations:
superpowers:
tdd_required: true
hardgate_enabled: true
Step 3:创建 gstack 的 Claude Code Skill 配置
.claude/skills/gstack.md:
markdown
---
name: gstack
description: 全流程 Sprint 管线,负责评审、QA、发布
---
# gstack Skill
## 可用命令
- `/gstack plan` - 基于 OpenSpec 产物执行设计评审
- `/gstack review` - 代码审查
- `/gstack qa` - 启动 Playwright E2E 测试
- `/gstack ship` - 执行发布流程
## 工作流
1. 读取 openspec/ 目录作为输入
2. 执行 CEEO + Engineering + DX 评审
3. 输出评审报告到 gstack/reviews/
4. 根据阈值判断是否通过
5.3 运行 gstack plan
在 Claude Code 中输入:
bash
/gstack plan --input openspec/
gstack 会读取你刚才生成的四个 OpenSpec 文件,然后执行四类评审:
评审 1:CEEO(正确性、效率、组织、优化)
yaml
CEEO Review Report
==================
✅ Correctness: API 规格完整,边界条件明确
⚠️ Efficiency: 排行榜实时查询可能在大数据量时慢
建议:增加 Redis 缓存层,或改用物化视图
✅ Organization: 数据模型分层合理
✅ Optimization: Streak 缓存表设计正确,可重建
评审 2:Engineering(工程设计)
ini
Engineering Review Report
=========================
✅ 数据库设计:@@unique([userId, date]) 防止重复打卡
✅ 索引设计:查询字段均有索引
⚠️ 时区处理:默认 UTC+8 写死在代码里,建议可配置
✅ 错误处理:API 规格中缺少错误响应定义,请补充
评审 3:DX(开发者体验)
markdown
DX Review Report
================
✅ 组件拆分粒度适中
⚠️ 缺少 API 文档生成工具(如 Swagger)配置
⚠️ 测试任务拆分清晰,但没有指定测试框架
评审 4:Security(安全)
markdown
Security Review Report
======================
✅ 用户隔离:所有查询均带 userId 过滤
⚠️ 排行榜可能暴露用户隐私,确认是否只显示昵称
⚠️ rate limiting:POST /api/checkin 需要限流,防止刷打卡
5.4 如何处理评审意见?
gstack 的评审报告会输出到 gstack/reviews/plan-2026-07-18.md。你需要:
- 修复严重问题(critical):必须修,不修不让过
- 处理警告(warning):建议修,可以讨价还价
- 确认已知问题 :有些警告你可以接受,在报告中标注
ACK
比如上面的 "排行榜实时查询可能慢",你决定先不做 Redis(项目初期数据量小),就在 OpenSpec 的 Spec 文件中增加一条:
markdown
> **性能备注:** 排行榜当前直接查询数据库,日活 < 1万时无需缓存。
> 当用户量增长时,需引入 Redis Sorted Set 优化。详见 [PERF-001]。
这就叫** specs 是唯一真相源**------设计决策记录在规格书里,而不是散落在聊天记录或脑子里。
5.5 串联点二:gstack plan 通过 -> 触发 Superpowers TDD
当 gstack plan 阶段通过(所有 critical 问题解决,warning 在阈值内),管线会自动进入 implement 阶段。
但 gstack 的配置里写了:
yaml
integrations:
superpowers:
tdd_required: true
这意味着:进入 implement 阶段前,Superpowers 的 TDD 铁律自动生效。Claude Code 在写任何实现代码之前,必须先写测试。
六、阶段三:Superpowers TDD ------ 代码警察上岗,没测试不给过
6.1 Superpowers 是什么?
Superpowers 是一套代码质量门禁系统,核心就两条铁律:
- TDD 铁律: 写实现代码之前,必须先写测试。没有测试?不让写代码。
- HARDGATE 铁律: 代码提交前必须通过质量门禁(测试通过率、代码覆盖率、静态分析)。
它不是建议,是强制。就像过安检,你背包里有水,要么喝一口,要么扔掉,没商量。
6.2 安装与配置 Superpowers
Step 1:创建 Superpowers 规则文件
在项目根目录创建 CLAUDE.md(如果之前创建了,就追加):
markdown
# duolinguo 项目 - Superpowers 质量铁律
## TDD 铁律(不可违背)
### 规则 1:测试先行
- 任何业务逻辑代码,必须先有对应的测试文件
- 测试文件名:`[原文件名].test.ts` 或 `[原文件名].spec.ts`
- 测试必须与实现放在同一目录
### 规则 2:最小可运行
- 先写失败的测试(Red)
- 再写最少代码让测试通过(Green)
- 最后重构(Refactor)
- 循环往复
### 规则 3:覆盖率门槛
- 业务逻辑:>= 80%
- 工具函数:>= 90%
- API 端点:100%(所有路由必须被测试覆盖)
## HARDGATE 门禁(提交前检查)
### 必过检查项
- [ ] `npm test` 全部通过
- [ ] `npm run test:coverage` 覆盖率达标
- [ ] `npm run lint` 无错误
- [ ] `npm run typecheck` TypeScript 类型检查通过
- [ ] `npm run build` 构建成功
### 豁免条款(以下场景可跳过 TDD)
1. 一次性原型(PoC),明确标注 `// POC-EXEMPT`
2. 纯配置文件(tailwind.config.ts, next.config.js 等)
3. 自动生成的代码(Prisma Client, OpenAPI 生成代码等)
## 测试规范
### 测试框架
- 单元测试:Vitest
- API 测试:Vitest + supertest
- E2E 测试:Playwright
### 测试目录结构
tests/ ├── unit/ # 单元测试 ├── integration/ # 集成测试 └── e2e/ # E2E 测试
markdown
### Mock 规则
- 允许 mock 数据库(用 test container 或内存 SQLite)
- 允许 mock 外部 API
- 不允许 mock 正在测试的模块本身
Step 2:创建 Superpowers Skill 配置
.claude/skills/superpowers.md:
markdown
---
name: superpowers
description: 代码质量门禁系统,强制执行 TDD 和 HARDGATE
---
# Superpowers Skill
## 命令
- `/tdd` - 启动 TDD 模式,强制测试先行
- `/hardgate` - 执行提交前门禁检查
## 行为
1. 当用户要求写代码时,先检查是否有对应测试
2. 如果没有,拒绝写实现,先引导写测试
3. 代码完成后,自动运行 `/hardgate`
6.3 TDD 实战:先写测试,再写代码
现在,我们要实现任务 B2:POST /api/checkin。按照 TDD 铁律,必须先写测试。
Step 1:写测试(Red)
创建 __tests__/unit/checkin.logic.test.ts:
typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { CheckInService } from '@/lib/checkin/checkin.service';
import { prisma } from '@/lib/prisma';
// 每个测试前清理数据
beforeEach(async () => {
await prisma.checkIn.deleteMany();
await prisma.streak.deleteMany();
});
describe('CheckInService', () => {
const userId = 'user-123';
describe('recordStudy', () => {
it('学习满15分钟应该触发打卡', async () => {
const result = await CheckInService.recordStudy(userId, 15);
expect(result.checkedIn).toBe(true);
expect(result.streak).toBe(1);
});
it('学习不满15分钟不应该打卡', async () => {
const result = await CheckInService.recordStudy(userId, 10);
expect(result.checkedIn).toBe(false);
expect(result.streak).toBe(0);
});
it('连续两天打卡应该增加 streak', async () => {
// 模拟昨天打卡
await CheckInService.recordStudy(userId, 15);
// 模拟今天打卡(通过调整时间)
const result = await CheckInService.recordStudy(userId, 15);
expect(result.streak).toBe(2);
});
it('中断一天后 streak 应该归零', async () => {
// 先建立 streak=3
await CheckInService.recordStudy(userId, 15);
await CheckInService.recordStudy(userId, 15);
await CheckInService.recordStudy(userId, 15);
// 模拟中断(跳过一天)
const result = await CheckInService.recordStudy(userId, 15);
expect(result.streak).toBe(1); // 重新从 1 开始
});
it('同一天多次学习只算一次打卡', async () => {
await CheckInService.recordStudy(userId, 10);
await CheckInService.recordStudy(userId, 5); // 累计15分钟
const checkIns = await prisma.checkIn.count({
where: { userId }
});
expect(checkIns).toBe(1);
});
it('跨时区用户应该按时区判定', async () => {
// 模拟 UTC+8 用户
const result = await CheckInService.recordStudy(
userId,
15,
{ timezone: 'Asia/Shanghai' }
);
expect(result.checkedIn).toBe(true);
});
});
describe('getStreak', () => {
it('新用户 streak 为 0', async () => {
const streak = await CheckInService.getStreak(userId);
expect(streak.current).toBe(0);
expect(streak.longest).toBe(0);
});
it('应该返回最长 streak', async () => {
await CheckInService.recordStudy(userId, 15);
await CheckInService.recordStudy(userId, 15);
const streak = await CheckInService.getStreak(userId);
expect(streak.longest).toBeGreaterThanOrEqual(2);
});
});
});
这时候运行 npm test,应该是全红( failing tests ),因为实现还没写。
Step 2:写实现(Green)
创建 lib/checkin/checkin.service.ts:
typescript
import { prisma } from '@/lib/prisma';
import { startOfDay, subDays, isSameDay } from 'date-fns';
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
interface RecordStudyResult {
checkedIn: boolean;
streak: number;
}
interface StreakInfo {
current: number;
longest: number;
lastCheckIn: string | null;
}
export class CheckInService {
static async recordStudy(
userId: string,
duration: number,
options?: { timezone?: string }
): Promise<RecordStudyResult> {
const timezone = options?.timezone || 'Asia/Shanghai';
const now = new Date();
const zonedNow = utcToZonedTime(now, timezone);
const today = startOfDay(zonedNow);
const todayUtc = zonedTimeToUtc(today, timezone);
// 查找或创建今日打卡记录
const existingCheckIn = await prisma.checkIn.findUnique({
where: {
userId_date: {
userId,
date: todayUtc,
},
},
});
if (existingCheckIn) {
// 更新时长
await prisma.checkIn.update({
where: { id: existingCheckIn.id },
data: { duration: existingCheckIn.duration + duration },
});
} else {
// 创建新记录
await prisma.checkIn.create({
data: {
userId,
date: todayUtc,
duration,
},
});
}
// 重新计算总时长
const totalDuration = existingCheckIn
? existingCheckIn.duration + duration
: duration;
const checkedIn = totalDuration >= 15;
if (checkedIn) {
await this.updateStreak(userId, todayUtc);
}
const streak = await this.getStreak(userId);
return {
checkedIn,
streak: streak.current,
};
}
private static async updateStreak(userId: string, checkInDate: Date): Promise<void> {
const streak = await prisma.streak.findUnique({
where: { userId },
});
if (!streak) {
// 第一次打卡
await prisma.streak.create({
data: {
userId,
currentStreak: 1,
longestStreak: 1,
lastCheckInDate: checkInDate,
},
});
return;
}
const lastDate = streak.lastCheckInDate;
const yesterday = subDays(startOfDay(checkInDate), 1);
let newStreak: number;
if (lastDate && isSameDay(lastDate, yesterday)) {
// 连续打卡
newStreak = streak.currentStreak + 1;
} else if (lastDate && isSameDay(lastDate, checkInDate)) {
// 今天已经打卡过了,不增加 streak
newStreak = streak.currentStreak;
} else {
// 中断后重新打卡,或第一次
newStreak = 1;
}
const longestStreak = Math.max(newStreak, streak.longestStreak);
await prisma.streak.update({
where: { userId },
data: {
currentStreak: newStreak,
longestStreak,
lastCheckInDate: checkInDate,
},
});
}
static async getStreak(userId: string): Promise<StreakInfo> {
const streak = await prisma.streak.findUnique({
where: { userId },
});
if (!streak) {
return {
current: 0,
longest: 0,
lastCheckIn: null,
};
}
return {
current: streak.currentStreak,
longest: streak.longestStreak,
lastCheckIn: streak.lastCheckInDate?.toISOString() || null,
};
}
}
再跑测试:npm test,应该全绿了。
Step 3:重构(Refactor)
看看有没有重复代码、命名不清的地方。比如 updateStreak 里的时区处理可以提取成工具函数。重构完再跑测试,确保还是全绿。
这就是 TDD 的 Red-Green-Refactor 循环。
6.4 HARDGATE 实战:提交前门禁
代码写完了,但别想直接 commit。Superpowers 会拦住你,要求过 HARDGATE:
bash
# 1. 运行测试
npm test
# 2. 检查覆盖率
npm run test:coverage
# 3. 代码检查
npm run lint
# 4. 类型检查
npm run typecheck
# 5. 构建检查
npm run build
如果任何一步挂了,Superpowers 会:
markdown
❌ HARDGATE BLOCKED
失败项:
- test:coverage: 覆盖率 67% < 80% (threshold)
- lint: 3 errors in checkin.service.ts
修复后才能提交。以下是修复建议:
1. 补充 CheckInService.getCalendar 的单元测试...
2. 检查 checkin.service.ts 第 45 行的未使用变量...
你修完再跑,直到全部通过,才能进入下一阶段。
6.5 串联点三:Superpowers TDD -> gstack review 自动生效
注意这个美妙的衔接:
因为你在 TDD 阶段写的测试,会自动成为 gstack review 阶段的输入。gstack review 不会只看代码写得漂不漂亮,它会检查:
- 测试是否覆盖了需求规格里的所有场景?
- 边界条件(15分钟门槛、时区、重复打卡)是否有测试?
- 测试名称是否清晰表达了意图?
如果 OpenSpec 的 Spec 里说 "每个自然日最多打卡 1 次",但你的测试里没覆盖这个 case,gstack review 会标红:
arduino
⚠️ 需求追踪失败
需求 SPEC-001 §1.1 要求:"每个自然日最多打卡 1 次"
测试覆盖:未找到对应测试用例
建议:补充 "同一天多次学习只算一次打卡" 的测试
这就是需求-测试-代码的三向绑定,环环相扣。
七、阶段四:gstack review ------ 代码审查 + 扫描
7.1 review 阶段做什么?
TDD 过了,代码写得差不多了。但 gstack 还要做一次全面的代码审查。
和 plan 阶段的评审不同,review 阶段是针对实现代码的,它会:
- CEEO 复查: 实现是否匹配设计?有没有性能陷阱?
- 安全扫描: SQL 注入?XSS?权限绕过?
- 需求追踪: 每个需求点是否有对应的实现和测试?
- DF(Defect Finding): 主动找 bug
7.2 运行 gstack review
bash
/gstack review --diff HEAD~1
gstack 会生成 gstack/reviews/review-2026-07-18.md:
markdown
# Code Review Report
## 变更范围
- `lib/checkin/checkin.service.ts` (新增)
- `__tests__/unit/checkin.logic.test.ts` (新增)
- `prisma/schema.prisma` (修改)
## CEEO Review
✅ 实现匹配设计文档 DESIGN-001
✅ 数据库查询均有索引
⚠️ `updateStreak` 中多次查询数据库,可优化为批量操作
## Security Review
✅ 所有 Prisma 查询使用参数化,无 SQL 注入风险
⚠️ POST /api/checkin 缺少 rate limiting,建议添加:
```ts
import { rateLimit } from '@/lib/rate-limit';
export const POST = rateLimit({ max: 10, window: '1h' })(handler);
```
## 需求追踪 (Traceability)
| 需求 | 实现 | 测试 | 状态 |
|------|------|------|------|
| 自动打卡 (15分钟) | ✅ | ✅ | 通过 |
| 连续 streak | ✅ | ✅ | 通过 |
| 时区处理 | ✅ | ⚠️ | 测试时区单一,建议增加 UTC、DST 测试 |
| 排行榜 | ❌ | ❌ | 未实现 |
## 结论
状态:**CONDITIONAL PASS**
条件:补充排行榜实现,或从本次 MR 中移除相关需求
7.3 如何处理 review 结果?
如果 review 结果是 PASS,自动进入下一阶段 QA。
如果是 CONDITIONAL PASS,你需要修掉条件里的问题。
如果是 FAIL,打回重写。
这就是门禁的意义:烂代码到不了 QA,更到不了生产环境。
八、阶段五:QA ------ Playwright 真实浏览器验收
8.1 为什么要有 QA 阶段?
单元测试过了,代码审查过了,但你的打卡按钮在真实浏览器里能点吗?日历组件在 iPhone 上会不会崩?排行榜加载 1000 条数据会不会卡死?
Playwright + Chromium 就是来模拟真实用户的操作。
8.2 配置 Playwright E2E 测试
Step 1:安装 Playwright
bash
npm install --save-dev @playwright/test
npx playwright install chromium
Step 2:创建 E2E 测试
__tests__/e2e/checkin.spec.ts
typescript
import { test, expect } from '@playwright/test';
test.describe('每日打卡 E2E', () => {
test.beforeEach(async ({ page }) => {
// 登录
await page.goto('/login');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
});
test('完整打卡流程', async ({ page }) => {
// 1. 进入打卡页面
await page.goto('/checkin');
await expect(page.locator('h1')).toContainText('每日打卡');
// 2. 初始状态:今日未打卡
await expect(page.locator('[data-testid="streak-count"]')).toHaveText('0');
await expect(page.locator('[data-testid="today-status"]')).toContainText('未打卡');
// 3. 模拟学习(调用 API 或直接操作)
await page.goto('/learn');
await page.click('[data-testid="start-lesson"]');
// 模拟学习 15 分钟(实际测试中可以 mock 时间或调用 API)
await page.evaluate(() => {
// 直接调用 API 模拟学习完成
return fetch('/api/checkin', {
method: 'POST',
body: JSON.stringify({ duration: 15 }),
headers: { 'Content-Type': 'application/json' }
});
});
// 4. 回到打卡页面,验证已打卡
await page.goto('/checkin');
await expect(page.locator('[data-testid="today-status"]')).toContainText('已打卡');
await expect(page.locator('[data-testid="streak-count"]')).toHaveText('1');
// 5. 验证日历上今日有标记
const todayCell = page.locator('[data-testid="calendar-today"]');
await expect(todayCell).toHaveClass(/checked-in/);
// 6. 验证火焰动画
await expect(page.locator('[data-testid="flame-icon"]')).toBeVisible();
});
test('排行榜加载', async ({ page }) => {
await page.goto('/checkin');
// 滚动到排行榜
await page.click('[data-testid="leaderboard-tab"]');
// 验证排行榜有数据
const rows = page.locator('[data-testid="leaderboard-row"]');
await expect(rows).toHaveCount.greaterThan(0);
// 验证前 3 名有奖牌图标
await expect(page.locator('[data-testid="medal-gold"]')).toBeVisible();
await expect(page.locator('[data-testid="medal-silver"]')).toBeVisible();
await expect(page.locator('[data-testid="medal-bronze"]')).toBeVisible();
});
test('响应式布局', async ({ page }) => {
// 模拟手机
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/checkin');
// 验证日历可横向滚动
const calendar = page.locator('[data-testid="checkin-calendar"]');
await expect(calendar).toBeVisible();
// 截图对比(可选)
await expect(page).toHaveScreenshot('checkin-mobile.png');
});
});
Step 3:运行 E2E 测试
bash
npx playwright test __tests__/e2e/checkin.spec.ts --project=chromium
8.3 QA 阶段的验收标准
gstack 的 QA 阶段配置里有:
yaml
qa:
thresholds:
e2e_pass_rate: 100%
visual_diff: 0.1 # 截图对比差异不超过 10%
performance:
lcp: 2500 # Largest Contentful Paint < 2.5s
fid: 100 # First Input Delay < 100ms
全部通过,才能进入 ship 阶段。
九、阶段六:ship ------ 一键发布,PR 自动生成
9.1 ship 阶段做什么?
终于到了激动人心的发布环节!gstack 的 ship 阶段会自动:
- 版本升级(bump version)
- 生成 Changelog
- 创建 Git 分支
- 提交代码
- 创建 Pull Request
- 推送到远程
9.2 运行 ship
bash
/gstack ship --message "feat: 每日学习打卡功能"
gstack 会执行:
bash
# 1. 检查当前分支是干净的
# 2. 创建 feature 分支
git checkout -b feat/daily-checkin
# 3. 自动 bump version(根据 conventional commit)
npm version minor
# 4. 生成 Changelog
npx conventional-changelog -p angular -i CHANGELOG.md -s
# 5. 提交所有更改
git add .
git commit -m "feat: 每日学习打卡功能
- 新增 CheckIn、Streak 数据模型
- 实现打卡核心逻辑(15分钟门槛、连续 streak、时区处理)
- 新增 /checkin 页面、日历组件、排行榜
- 完整的单元测试 + E2E 测试
Closes TASK-001"
# 6. 推送并创建 PR
git push origin feat/daily-checkin
gh pr create --title "feat: 每日学习打卡功能" --body "..."
9.3 生成的 PR 长什么样?
gstack 会自动生成一个结构化的 PR 描述:
markdown
## 功能描述
实现每日学习打卡系统,激励用户坚持学习。
## 变更范围
- ✅ 数据库:新增 CheckIn、Streak 模型
- ✅ API:4 个新端点
- ✅ 前端:/checkin 页面 + 3 个新组件
- ✅ 测试:12 个单元测试 + 3 个 E2E 测试
## 需求追踪
- 关联 Spec: SPEC-001
- 关联 Design: DESIGN-001
- 关联 Tasks: TASK-001
## 测试情况
- [x] 单元测试通过率:100% (12/12)
- [x] E2E 测试通过率:100% (3/3)
- [x] 代码覆盖率:87%
- [x] Lighthouse 评分:95
## 截图
[自动插入 Playwright 截图]
## 检查清单
- [x] OpenSpec 需求已归档
- [x] gstack review 已通过
- [x] Superpowers HARDGATE 已通过
- [x] QA 验收已通过
这 PR 质量,比很多工程师自己写的都规范。
十、阶段七:OpenSpec archive ------ 归档 delta,合并主规范
10.1 archive 阶段做什么?
功能上线了,但 OpenSpec 的工作还没完。
需求规格书(Spec)和实现之间,往往会有一些偏差(delta)。比如:
- 设计时没想到的边界情况
- 实现过程中做的取舍
- 用户反馈导致的微调
archive 阶段就是把这些 delta 记录下来,合并回主规范,形成项目的知识资产。
10.2 运行 archive
bash
/opspect archive --feature "daily-checkin"
OpenSpec 会:
- 对比
openspec/specs/和实际实现,找出 delta - 生成
openspec/archive/2026-07-18-daily-checkin-delta.md
markdown
---
feature: daily-checkin
status: archived
date: 2026-07-18
---
# 归档报告:每日学习打卡
## 需求变更记录 (Delta)
### 1. 时区处理
**原始设计:** 默认 UTC+8
**实际实现:** 改为可配置,通过 `options.timezone` 传入
**原因:** 国际化用户需要支持多时区
**是否回写规范:** ✅ 已更新 SPEC-001
### 2. 排行榜
**原始设计:** 实时查询数据库
**实际实现:** 增加 Redis 缓存层
**原因:** gstack plan 阶段 CEEO 评审指出性能风险
**是否回写规范:** ✅ 已更新 SPEC-001 §2.4
### 3. 打卡动画
**原始设计:** 仅火焰跳动
**实际实现:** 增加 Confetti 效果
**原因:** UX 评审建议增强正向反馈
**是否回写规范:** ❌ 设计细节,未纳入规格书
## 经验总结
- TDD 在业务逻辑层效果显著,但 UI 动画层测试 ROI 较低
- 时区是容易被忽略的点,以后所有时间相关功能必须考虑
- 缓存应该在设计阶段就考虑,而不是评审后补救
## 相关文档
- Proposal: openspec/proposals/PROP-001.md
- Spec: openspec/specs/SPEC-001.md (已更新 v1.1)
- Design: openspec/designs/DESIGN-001.md
10.3 串联点四:ship 触发 archive,闭环完成
这是最妙的一个串联点:
当 gstack 的 ship 阶段成功创建 PR 并推送后,它会自动触发 OpenSpec 的 archive 命令(通过配置中的 webhook 或 Claude Code 的 hook 机制)。
于是:
rust
ship 完成 -> 触发 archive -> delta 归档 -> 规范更新 -> 知识沉淀
一个功能的完整生命周期,到此闭环。
十一、避坑指南:我踩过的坑,你别踩
❌ 坑 1:重复门禁
错误做法: OpenSpec 已经做了设计审批,gstack plan 又从头评审一遍设计。
正确做法: gstack plan 读取 OpenSpec 产物作为输入,只做补充性评审(比如 CEEO、安全),不重复审查已经 approved 的设计。
配置:
yaml
# gstack/config/pipeline.yaml
phases:
- name: plan
review_scope: [ceoo, security, dx] # 不包含 design
❌ 坑 2:把 archive 当发布
错误做法: 以为 archive 完了功能就上线了。
正确做法: ship 是唯一发布出口。archive 只是收尾记录,像写完日记锁抽屉。
❌ 坑 3:TDD 无脑执行
错误做法: 写 tailwind 配置也先写测试。
正确做法: Superpowers 的 CLAUDE.md 里明确写了豁免条款:
markdown
### 豁免条款
1. 一次性原型(标注 `// POC-EXEMPT`)
2. 纯配置文件
3. 自动生成的代码
TDD 不是宗教,是工具。知道什么时候不用,比知道什么时候用更重要。
❌ 坑 4:三个工具版本冲突
错误做法: OpenSpec v2 的产物格式,gstack v1 读不懂。
正确做法: 统一版本管理。在 CLAUDE.md 顶部声明:
markdown
## 工具版本
- OpenSpec: v1.0
- Superpowers: v2.1
- gstack: v1.2
❌ 坑 5:不给工具留状态空间
错误做法: 所有文件堆在项目根目录,三个工具互相覆盖。
正确做法: 严格目录隔离:
openspec/ <- OpenSpec 专属
.claude/ <- Superpowers 专属
gstack/ <- gstack 专属
十二、完整文件结构参考
走完整个流程,你的项目目录应该是这样的:
yaml
duolinguo/
├── app/
│ ├── checkin/
│ │ └── page.tsx # /checkin 页面
│ └── api/
│ ├── checkin/
│ │ └── route.ts # POST /api/checkin
│ ├── streak/
│ │ └── route.ts # GET /api/streak
│ ├── leaderboard/
│ │ └── route.ts # GET /api/leaderboard
│ └── calendar/
│ └── route.ts # GET /api/calendar
├── components/
│ └── checkin/
│ ├── StreakCard.tsx
│ ├── CheckInCalendar.tsx
│ ├── LeaderboardRow.tsx
│ └── FlameIcon.tsx
├── lib/
│ └── checkin/
│ └── checkin.service.ts # 核心业务逻辑
├── prisma/
│ └── schema.prisma # CheckIn + Streak 模型
├── __tests__/
│ ├── unit/
│ │ └── checkin.logic.test.ts # 单元测试
│ └── e2e/
│ └── checkin.spec.ts # E2E 测试
├── openspec/ # OpenSpec 地盘
│ ├── proposals/
│ │ └── 2026-07-18-daily-checkin.md
│ ├── specs/
│ │ └── 2026-07-18-daily-checkin-spec.md
│ ├── designs/
│ │ └── 2026-07-18-daily-checkin-design.md
│ ├── tasks/
│ │ └── 2026-07-18-daily-checkin-tasks.md
│ └── archive/
│ └── 2026-07-18-daily-checkin-delta.md
├── gstack/ # gstack 地盘
│ ├── config/
│ │ └── pipeline.yaml
│ ├── reviews/
│ │ ├── plan-2026-07-18.md
│ │ └── review-2026-07-18.md
│ └── sessions/
│ └── session-2026-07-18.json
├── .claude/ # Superpowers 地盘
│ ├── skills/
│ │ ├── openspec.md
│ │ ├── superpowers.md
│ │ └── gstack.md
│ └── CLAUDE.md # 项目规范总入口
├── playwright.config.ts
├── vitest.config.ts
├── package.json
└── CHANGELOG.md
十三、总结:三器合一的本质是什么?
读到这里,你应该已经看出来了------这套工作流的核心不是"工具多牛逼",而是三层分离 + 自动串联。
三层分离
| 层次 | 负责什么 | 什么时候介入 |
|---|---|---|
| 需求层 (OpenSpec) | "做什么" | 写代码之前 |
| 质量层 (Superpowers) | "怎么做对" | 写代码的时候 |
| 流程层 (gstack) | "怎么发布" | 写完代码之后 |
各干各的,不抢活。
自动串联
四个串联点,把三个阶段串成一条流水线:
lua
OpenSpec 产物 --> gstack plan 评审输入
Superpowers TDD --> gstack review 自动生效
gstack ship --> OpenSpec archive 自动触发
delta 归档 --> 主规范更新(知识沉淀)
你只需要输入几个斜杠命令:
bash
/opspect new "功能名" # 锁需求
/gstack plan # 评审
/tdd # 测试先行写代码
/gstack review # 代码审查
/gstack qa # 浏览器验收
/gstack ship # 一键发布
/opspect archive # 归档收尾
剩下的,工具自己衔接。
给你的建议
如果你是个人开发者 ,建议先上 Superpowers。TDD 铁律是 ROI 最高的,养成测试先行的习惯,代码质量直接上一个台阶。
如果你是小团队 ,加上 OpenSpec。把需求锁死,减少"我觉得""我记得"导致的返工。
如果你是正式项目 ,把 gstack 也加上。流程化发布,PR 自动生成,团队协作效率翻倍。
但别一次性全上,否则你会被流程压垮。一步一步来,先把一层吃透,再叠下一层。
十四、写在最后
AI 编程工具越来越多,但工具本身不产生价值。工具之间的串联,才产生价值。
OpenSpec 锁住需求,让你少返工; Superpowers 卡住质量,让你少挖坑; gstack 包圆流程,让你少操心。
三者合一,就像给 AI 编程装上了流水线------你只需要在起点放原材料(需求),在终点拿成品(发布),中间的工序,自动化搞定。
当然,这套工作流也不是银弹。它适合有明确需求、需要长期维护的功能开发。如果你只是写个一次性脚本、搞个 POC 验证想法,别上全套装,TDD 都可以免了,怎么快怎么来。
工具是为人服务的,不是人为工具服务的。知道什么时候用重炮,什么时候用手枪,才是真本事。
希望这篇文章对你有帮助。如果你照着做跑通了,或者踩了新坑,欢迎在评论区交流。奶茶我请了(线上版的,自己泡)。
参考链接:
- 视频教程:Bilibili - OpenSpec、Superpowers、gstack 三器合一
- OpenSpec 文档:(请替换为实际链接)
- Superpowers 文档:(请替换为实际链接)
- gstack 文档:(请替换为实际链接)
本文完。码字不易,点赞收藏转发三连,是对作者最大的支持。我们下篇见!