OpenSpec + Superpowers + gstack 三器合一:你的 AI 编程终于可以从"拍脑袋"进化到"流水线"了

本文不是吹牛,不是画饼,是手把手教你把三个 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)

核心需求:

  1. 用户每天学习满 15 分钟,自动视为"已打卡"
  2. 显示连续打卡天数(Streak),断了就归零
  3. 全局排行榜,按连续天数排名
  4. 个人中心展示打卡日历(本月每天是否打卡)

技术栈: 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 管线工具。它的核心能力是:

  1. Browse 引擎:自动读取项目上下文(代码、文档、配置)
  2. 七阶段管线:plan -> design -> implement -> review -> QA -> ship -> archive
  3. 自动推进:一个阶段通过,自动触发下一个阶段

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。你需要:

  1. 修复严重问题(critical):必须修,不修不让过
  2. 处理警告(warning):建议修,可以讨价还价
  3. 确认已知问题 :有些警告你可以接受,在报告中标注 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 是一套代码质量门禁系统,核心就两条铁律:

  1. TDD 铁律: 写实现代码之前,必须先写测试。没有测试?不让写代码。
  2. 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 阶段是针对实现代码的,它会:

  1. CEEO 复查: 实现是否匹配设计?有没有性能陷阱?
  2. 安全扫描: SQL 注入?XSS?权限绕过?
  3. 需求追踪: 每个需求点是否有对应的实现和测试?
  4. 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 阶段会自动:

  1. 版本升级(bump version)
  2. 生成 Changelog
  3. 创建 Git 分支
  4. 提交代码
  5. 创建 Pull Request
  6. 推送到远程

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 会:

  1. 对比 openspec/specs/ 和实际实现,找出 delta
  2. 生成 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 都可以免了,怎么快怎么来。

工具是为人服务的,不是人为工具服务的。知道什么时候用重炮,什么时候用手枪,才是真本事。

希望这篇文章对你有帮助。如果你照着做跑通了,或者踩了新坑,欢迎在评论区交流。奶茶我请了(线上版的,自己泡)。


参考链接:


本文完。码字不易,点赞收藏转发三连,是对作者最大的支持。我们下篇见!

相关推荐
程序猿乐锅19 小时前
【苍穹外卖 day11|统计报表接口与 Apache ECharts 图表展示】
前端·apache·echarts
yy403319 小时前
【HarmonyOS学习笔记】2026-07-19 | 布局性能实验:百分比vs固定值vs预计算
前端·harmonyos
渣波19 小时前
🚀 全栈AI革命:用 Node.js + LangChain + dotenv 打造你的智能应用基座
前端
程序员Jason19 小时前
Node.js 极简安装指南(Mac / Windows / Linux 通用,含国内镜像)
前端
半个落月19 小时前
Vue 3 如何接住大模型的流式回答:从 ReadableStream 到可靠的 SSE 解析
前端·javascript·人工智能
蓝银草同学19 小时前
Stream 数据统计实战:求和、平均值、分组汇总(AI 辅助学习 Java 8)
java·前端·后端
Dontla19 小时前
Hero Section(首屏大图区 / 英雄区)介绍(Web网页落地页Landing Page最顶部的区域)
前端
问商十三载19 小时前
2026大模型GEO优化体系:3层链路提收录,零成本提34%引用率附工具包
大数据·前端·人工智能