Chaining OpenSpec, Superpowers, and gstack Turns Claude Code Into an Automated Delivery Pipeline
This article isn't boasting or painting an unrealistic picture; it's a hands-on guide to stringing three Claude Code tools into a fully automated production line. If you still can't do it after reading, come find me, and I'll buy you a milk tea (for real).
I. Foreword: Why is your AI programming still in the "Stone Age"?
Suppose your name is Xiao Ming, and you're a frontend developer. One day, the boss comes over and says: "We need to add a study check-in feature to the app, where users can record how long they study each day, and we also need a streak count and a leaderboard."
You open Claude Code and confidently type:
Make me a study check-in feature
Claude generates a bunch of code for you in a flash. You glance at it, it seems usable, so you git commit and push it up.
Three days later, the QA tester comes running:
- "What about this streak count across time zones?"
- "The leaderboard has no pagination; won't it freeze up when there's a lot of data?"
- "This isn't a check-in; it's a slap in the boss's face!"
You're stunned. Because you didn't lock down the requirements, didn't write tests, didn't follow a process, and didn't do acceptance checks—four "didn'ts" that directly turned a simple feature into technical debt.
This article today is here to end this tragedy. I'm going to teach you how to twist OpenSpec, Superpowers, and gstack into a single rope, evolving AI programming from "coding off the top of your head" to "assembly-line delivery."
And the whole process is automated. You just need to enter a few slash commands, and the tools will chain themselves together.
Ready? Let's begin.
II. What exactly is the Three-in-One? Don't panic, let's peel the onion layer by layer
Many tutorials throw concepts at you right away, knocking you dizzy. I won't do that. Let's first meet the three main characters, then string them together with a real project example.
2.1 Self-introductions of the three main characters
| Tool | Layer Responsible For | Core Function | Plain English Explanation |
|---|---|---|---|
| OpenSpec | Requirements Layer | Locks down requirements before coding, generates standard artifacts | A stand-in for the product manager, one that won't change the requirements |
| Superpowers | Quality Layer | TDD iron law + HARDGATE checkpoint | The code police; no passing without tests, bad code is directly blocked |
| gstack | Process Layer | Seven-stage Sprint pipeline + automatic release | A stand-in for the project manager, managing process, release, and archiving |
Key Insight: These three tools aren't a relay race where "you finish and then I start," but rather exist simultaneously within the same Claude Code session, triggering automatically and connecting layer by layer.
Imagine a hot pot assembly line:
- OpenSpec is the prep kitchen (slicing and arranging the ingredients/requirements)
- Superpowers is the quality inspector (undercooked meat is not allowed on the table)
- gstack is the waiter + cashier (end-to-end service, and it keeps the books after you eat)
All three do their own jobs, but automatically hand off on the assembly line.
2.2 Why don't they conflict?
Because of state isolation:
Project Root/
├── openspec/ <- OpenSpec's territory
│ ├── proposals/
│ ├── specs/
│ ├── designs/
│ └── tasks/
├── .claude/
│ ├── skills/ <- Superpowers' territory
│ └── CLAUDE.md <- Superpowers' rules
└── gstack/ <- gstack's territory
├── sessions/
├── reviews/
└── archive/
Three sets of state, each minding its own business. OpenSpec manages requirement documents, Superpowers manages code rules, and gstack manages process state. No one steals anyone else's job.
III. Hands-on Project: Adding a "Study Check-in" feature to duolinguo
To make sure you can understand and learn, we'll walk through the entire process using a complete real-world case.
3.1 Project Background
You have an app called duolinguo (right on your desktop), which already has a user system and a course list. Now you need to add a new feature:
Feature Name: Daily Study Check-in
Core Requirements:
- If a user studies for a full 15 minutes in a day, it's automatically considered "checked in"
- Display a streak of consecutive check-in days; it resets to zero if broken
- A global leaderboard, ranked by consecutive days
- A personal center displaying a check-in calendar (whether checked in each day of the current month)
Tech Stack: Next.js 14 + TypeScript + Prisma + PostgreSQL + TailwindCSS
The project structure roughly looks like:
duolinguo/
└── my-app/
├── app/ # Next.js App Router
├── components/ # React Components
├── lib/ # Utility Functions
├── prisma/
│ └── schema.prisma # Database Models
└── tests/ # Test Files
Alright, background is set. Now let's enter the seven-stage pipeline.
IV. Stage One: OpenSpec — Weld the requirements shut first, so no one can change them
4.1 Why must requirements be locked down first?
Imagine this: you've been coding your butt off all day, and the product manager suddenly says, "Forget the leaderboard, let's change it to achievement badges." How would you feel?
OpenSpec's role is to, before writing the first line of code, turn the requirements into a structured, verifiable, and immutable specification. This becomes the "single source of truth" for the entire pipeline.
4.2 Installing and Configuring OpenSpec
OpenSpec is not an npm package, but a set of Claude Code Skill workflows. You need to:
Step 1: Create the OpenSpec directory structure
In your project root (duolinguo/my-app), execute:
mkdir -p openspec/{proposals,specs,designs,tasks}
Step 2: Create the OpenSpec configuration file
Write the following in openspec/.openspecrc:
# OpenSpec Configuration File
project:
name: "duolinguo-daily-checkin"
version: "1.0.0"
description: "Daily study check-in feature"
phases:
- proposal # Proposal phase
- spec # Specification phase
- design # Design phase
- task # Task breakdown phase
rules:
require_approval: true # Each phase requires manual confirmation
auto_archive: true # Auto-archive upon completion
traceability: true # Requirements traceability
output:
format: markdown
directory: ./openspec
Step 3: Register OpenSpec in CLAUDE.md
Create/edit CLAUDE.md in the project root:
# duolinguo Project Standards
## OpenSpec Integration
- All new features must go through the OpenSpec process
- Requirement artifacts are stored in the `openspec/` directory
- Use the `/opspect` command to start the OpenSpec workflow
## Superpowers Integration
- Coding must follow the TDD iron law
- All business logic must have unit tests
- Use the `/tdd` command to start test-driven development
## gstack Integration
- Releases must go through gstack's ship process
- Use the `/ship` command to execute a release
4.3 Running OpenSpec: Generating Requirement Artifacts
In Claude Code, enter:
/opspect new "Daily Study Check-in Feature"
OpenSpec will guide you through generating four files:
File 1: Proposal
openspec/proposals/2026-07-18-daily-checkin.md
---
id: PROP-001
title: Daily Study Check-in Feature
author: sen
date: 2026-07-18
status: approved
---
# Proposal: Daily Study Check-in
## Problem Statement
Users lack motivation to study, with no mechanism to incentivize daily learning. Gamification elements are needed to boost retention.
## Solution Overview
Add a "Daily Check-in" system:
- Automatic check-in after 15 minutes of daily study
- Consecutive check-ins accumulate a Streak
- A global leaderboard to increase competitiveness
- A check-in calendar for visual feedback
## Success Metrics
- Daily active user check-in rate > 30%
- Average consecutive check-in days > 3 days
## Scope of Impact
- Database: Add CheckIn, Streak models
- Frontend: Add check-in page, calendar component, leaderboard
- API: Add 4 new endpoints
File 2: Spec (Specification)
openspec/specs/2026-07-18-daily-checkin-spec.md
---
id: SPEC-001
proposal: PROP-001
status: approved
---
# Specification: Daily Study Check-in
## 1. Functional Specifications
### 1.1 Automatic Check-in
- **Trigger Condition:** User's daily study duration >= 15 minutes
- **Judgment Logic:** Based on UTC calendar day, cumulative duration within 00:00-23:59
- **Deduplication Rule:** At most 1 check-in per calendar day
### 1.2 Consecutive Check-in (Streak)
- **Definition:** Check-in records exist for consecutive calendar days
- **Break Rule:** If a calendar day has no check-in, the Streak resets to zero
- **Timezone Handling:** Based on user's set timezone, default UTC+8
### 1.3 Leaderboard
- **Dimension:** Sorted by current Streak days in descending order
- **Pagination:** 20 entries per page
- **Update Frequency:** Real-time (based on database queries)
### 1.4 Check-in Calendar
- **Display Range:** Current month
- **Status:** Checked in / Not checked in / Today
- **Interaction:** Click a date to show that day's study details
## 2. Data Specifications
### 2.1 CheckIn Table
```prisma
model CheckIn {
id Int @id @default(autoincrement())
userId String
date DateTime @db.Date
duration Int // Study duration (minutes)
createdAt DateTime @default(now())
@@unique([userId, date])
@@index([userId, date])
}
2.2 Streak Table (Cache table, rebuildable)
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 Specifications
3.1 POST /api/checkin
- Description: Record study duration, trigger check-in judgment
- Request:
{ duration: number }(minutes) - Response:
{ checkedIn: boolean, streak: number }
3.2 GET /api/streak
- Description: Get current user's Streak info
- Response:
{ current: number, longest: number, lastCheckIn: string }
3.3 GET /api/leaderboard
- Description: Get the leaderboard
- Parameters:
?page=1&limit=20 - Response:
{ users: [{ rank, name, streak, avatar }], total }
3.4 GET /api/calendar
- Description: Get check-in calendar data
- Parameters:
?year=2026&month=7 - Response:
{ days: [{ date, checkedIn, duration }] }
#### File 3: Design (Design Document)
`openspec/designs/2026-07-18-daily-checkin-design.md`
```markdown
---
id: DESIGN-001
spec: SPEC-001
status: approved
---
# Design Document: Daily Study Check-in
## 1. Page Structure
/checkin # Main check-in page ├── Header # Title + Today's status ├── StreakCard # Large streak days card ├── CalendarWidget # This month's check-in calendar ├── LeaderboardPreview # Top 5 leaderboard └── ActionButton # "Go Study" CTA
## 2. Component List
| Component Name | Path | Responsibility |
|--------|------|------|
| StreakCard | `components/checkin/StreakCard.tsx` | Display flame icon + days |
| CheckInCalendar | `components/checkin/CheckInCalendar.tsx` | Monthly calendar grid rendering |
| LeaderboardRow | `components/checkin/LeaderboardRow.tsx` | Single ranking row |
| FlameIcon | `components/icons/FlameIcon.tsx` | Flame SVG |
## 3. State Management
- Use React Server Components to fetch initial data
- Client-side state uses useState + useEffect for check-in animations
- Leaderboard data uses ISR, revalidating every 60 seconds
## 4. Animation Design
- Check-in success: Flame icon bounce + Confetti effect
- Streak increase: Number scrolling animation
- Calendar cell: Scale up 1.05x on hover
File 4: Tasks (Task Breakdown)
openspec/tasks/2026-07-18-daily-checkin-tasks.md
---
id: TASK-001
spec: SPEC-001
design: DESIGN-001
status: ready
---
# Task Breakdown: Daily Study Check-in
## Backend Tasks
- [ ] **B1:** Database Migration - Create CheckIn and Streak tables
- [ ] **B2:** API Implementation - POST /api/checkin
- [ ] **B3:** API Implementation - GET /api/streak
- [ ] **B4:** API Implementation - GET /api/leaderboard
- [ ] **B5:** API Implementation - GET /api/calendar
## Frontend Tasks
- [ ] **F1:** Component Development - StreakCard
- [ ] **F2:** Component Development - CheckInCalendar
- [ ] **F3:** Component Development - LeaderboardRow + LeaderboardList
- [ ] **F4:** Page Assembly - /checkin main page
- [ ] **F5:** Animation Effects - Check-in success feedback
## Testing Tasks
- [ ] **T1:** Unit Tests - CheckIn business logic
- [ ] **T2:** Unit Tests - Streak calculation logic (including edge cases)
- [ ] **T3:** API Tests - All endpoints
- [ ] **T4:** E2E Tests - Complete check-in flow
4.4 Connection Point One: OpenSpec Artifacts -> gstack Review Input
The four files generated by OpenSpec aren't meant to be locked in a drawer after writing. They automatically become input for gstack's review.
How exactly do they connect? Look at gstack's configuration (explained later); gstack reads all artifacts in the openspec/ directory as input material for the auto plan phase.
That is, when you run /gstack plan, gstack already knows what requirements analysis and design decisions you've made. It will conduct four types of reviews (CEEO, Engineering Design, DX, Security) based on this, rather than reviewing from scratch.
V. Stage Two: gstack auto plan — Let the process engine read requirements and perform professional reviews
5.1 What is gstack?
gstack is a full-process Sprint pipeline tool. Its core capabilities are:
- Browse Engine: Automatically reads project context (code, documents, configuration)
- Seven-Stage Pipeline: plan -> design -> implement -> review -> QA -> ship -> archive
- Automatic Progression: Passing one stage automatically triggers the next
5.2 Installing and Configuring gstack
Step 1: Create the gstack directory structure
mkdir -p gstack/{sessions,reviews,archive,config}
Step 2: Create the gstack configuration file
gstack/config/pipeline.yaml
# gstack Pipeline Configuration
pipeline:
name: "duolinguo-checkin-pipeline"
version: "1.0"
# Seven-Stage Definition
phases:
- name: plan
enabled: true
auto_trigger: true
inputs:
- openspec/ # Automatically reads OpenSpec artifacts
- name: design
enabled: false # Skip, because OpenSpec already handled design
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
review_rules:
categories:
- ceoo # Correctness, Efficiency, Organization, Optimization
- engineering # Engineering design quality
- dx # Developer experience
- security # Security scan
thresholds:
critical: 0 # No critical issues allowed
warning: 5 # No more than 5 warnings
# Integration with Superpowers
integrations:
superpowers:
tdd_required: true
hardgate_enabled: true
Step 3: Create gstack's Claude Code Skill configuration
.claude/skills/gstack.md:
---
name: gstack
description: Full-process Sprint pipeline, responsible for review, QA, and release
---
# gstack Skill
## Available Commands
- `/gstack plan` - Execute design review based on OpenSpec artifacts
- `/gstack review` - Code review
- `/gstack qa` - Start Playwright E2E tests
- `/gstack ship` - Execute release process
## Workflow
1. Read the openspec/ directory as input
2. Execute CEEO + Engineering + DX reviews
3. Output review report to gstack/reviews/
4. Determine pass/fail based on thresholds
5.3 Running gstack plan
In Claude Code, enter:
/gstack plan --input openspec/
gstack will read the four OpenSpec files you just generated and then execute four types of reviews:
Review 1: CEEO (Correctness, Efficiency, Organization, Optimization)
CEEO Review Report
==================
✅ Correctness: API specifications are complete, boundary conditions are clear
⚠️ Efficiency: Real-time leaderboard queries might be slow with large data volumes
Suggestion: Add a Redis cache layer, or use a materialized view
✅ Organization: Data model layering is reasonable
✅ Optimization: Streak cache table design is correct and rebuildable
Review 2: Engineering
Engineering Review Report
=========================
✅ Database Design: @@unique([userId, date]) prevents duplicate check-ins
✅ Index Design: All queried fields have indexes
⚠️ Timezone Handling: Default UTC+8 is hardcoded; suggest making it configurable
✅ Error Handling: API specs lack error response definitions, please supplement
Review 3: DX (Developer Experience)
DX Review Report
================
✅ Component granularity is appropriate
⚠️ Missing API documentation generation tool (e.g., Swagger) configuration
⚠️ Test task breakdown is clear, but no testing framework is specified
Review 4: Security
Security Review Report
======================
✅ User Isolation: All queries include userId filtering
⚠️ Leaderboard might expose user privacy; confirm if only nicknames are displayed
⚠️ Rate Limiting: POST /api/checkin needs rate limiting to prevent check-in spamming
5.4 How to handle review comments?
gstack's review report is output to gstack/reviews/plan-2026-07-18.md. You need to:
- Fix critical issues: Must be fixed; won't pass otherwise.
- Handle warnings: Recommended to fix, but negotiable.
- Acknowledge known issues: Some warnings you can accept; mark them
ACKin the report.
For example, regarding the above "real-time leaderboard queries might be slow," you decide not to use Redis for now (data volume is small in the early project stage). Add a note in OpenSpec's Spec file:
> **Performance Note:** The leaderboard currently queries the database directly. No cache is needed when DAU < 10,000.
> When user volume grows, introduce Redis Sorted Set for optimization. See [PERF-001].
This is what specs as the single source of truth means—design decisions are recorded in the spec, not scattered in chat logs or someone's head.
5.5 Connection Point Two: gstack plan passes -> Triggers Superpowers TDD
When the gstack plan phase passes (all critical issues resolved, warnings within threshold), the pipeline automatically enters the implement phase.
But gstack's configuration states:
integrations:
superpowers:
tdd_required: true
This means: Before entering the implement phase, Superpowers' TDD iron law automatically takes effect. Claude Code must write tests before writing any implementation code.
VI. Stage Three: Superpowers TDD — The code police are on duty; no passing without tests
6.1 What is Superpowers?
Superpowers is a code quality gate system, with two core iron laws:
- TDD Iron Law: Before writing implementation code, you must write tests. No tests? No writing code.
- HARDGATE Iron Law: Code must pass quality gates before committing (test pass rate, code coverage, static analysis).
It's not a suggestion; it's mandatory. Like going through airport security—if you have water in your bag, you either drink it or throw it away. No negotiation.
6.2 Installing and Configuring Superpowers
Step 1: Create the Superpowers rules file
Create CLAUDE.md in the project root (or append if already created):
# duolinguo Project - Superpowers Quality Iron Laws
## TDD Iron Law (Inviolable)
### Rule 1: Test First
- Any business logic code must have a corresponding test file first
- Test file naming: `[original_filename].test.ts` or `[original_filename].spec.ts`
- Tests must be placed in the same directory as the implementation
### Rule 2: Minimum Runnable
- First, write a failing test (Red)
- Then, write the minimum code to make the test pass (Green)
- Finally, refactor (Refactor)
- Repeat the cycle
### Rule 3: Coverage Threshold
- Business logic: >= 80%
- Utility functions: >= 90%
- API endpoints: 100% (all routes must be covered by tests)
## HARDGATE Checkpoint (Pre-commit Checks)
### Mandatory Checks
- [ ] `npm test` all pass
- [ ] `npm run test:coverage` meets thresholds
- [ ] `npm run lint` no errors
- [ ] `npm run typecheck` TypeScript type checking passes
- [ ] `npm run build` build succeeds
### Exemption Clauses (TDD can be skipped for the following)
1. One-off prototypes (PoC), explicitly marked `// POC-EXEMPT`
2. Pure configuration files (tailwind.config.ts, next.config.js, etc.)
3. Auto-generated code (Prisma Client, OpenAPI generated code, etc.)
## Testing Standards
### Testing Framework
- Unit tests: Vitest
- API tests: Vitest + supertest
- E2E tests: Playwright
### Test Directory Structure
tests/ ├── unit/ # Unit tests ├── integration/ # Integration tests └── e2e/ # E2E tests
### Mocking Rules
- Mocking the database is allowed (using a test container or in-memory SQLite)
- Mocking external APIs is allowed
- Mocking the module currently under test is NOT allowed
Step 2: Create the Superpowers Skill configuration
.claude/skills/superpowers.md:
---
name: superpowers
description: Code quality gate system, enforcing TDD and HARDGATE
---
# Superpowers Skill
## Commands
- `/tdd` - Start TDD mode, enforcing test-first
- `/hardgate` - Execute pre-commit gate checks
## Behavior
1. When the user asks to write code, first check if a corresponding test exists
2. If not, refuse to write the implementation and guide the user to write a test first
3. After code is complete, automatically run `/hardgate`
6.3 TDD in Practice: Write Tests First, Then Code
Now, we need to implement Task B2: POST /api/checkin. Following the TDD iron law, we must write tests first.
Step 1: Write Tests (Red)
Create __tests__/unit/checkin.logic.test.ts:
import { describe, it, expect, beforeEach } from 'vitest';
import { CheckInService } from '@/lib/checkin/checkin.service';
import { prisma } from '@/lib/prisma';
// Clean up data before each test
beforeEach(async () => {
await prisma.checkIn.deleteMany();
await prisma.streak.deleteMany();
});
describe('CheckInService', () => {
const userId = 'user-123';
describe('recordStudy', () => {
it('should trigger check-in when study duration reaches 15 minutes', async () => {
const result = await CheckInService.recordStudy(userId, 15);
expect(result.checkedIn).toBe(true);
expect(result.streak).toBe(1);
});
it('should not check in if study duration is less than 15 minutes', async () => {
const result = await CheckInService.recordStudy(userId, 10);
expect(result.checkedIn).toBe(false);
expect(result.streak).toBe(0);
});
it('should increase streak for two consecutive days of check-in', async () => {
// Simulate yesterday's check-in
await CheckInService.recordStudy(userId, 15);
// Simulate today's check-in (by adjusting time)
const result = await CheckInService.recordStudy(userId, 15);
expect(result.streak).toBe(2);
});
it('should reset streak to zero after a missed day', async () => {
// First establish a streak of 3
await CheckInService.recordStudy(userId, 15);
await CheckInService.recordStudy(userId, 15);
await CheckInService.recordStudy(userId, 15);
// Simulate a break (skip a day)
const result = await CheckInService.recordStudy(userId, 15);
expect(result.streak).toBe(1); // Restarts from 1
});
it('should count multiple study sessions on the same day as one check-in', async () => {
await CheckInService.recordStudy(userId, 10);
await CheckInService.recordStudy(userId, 5); // Cumulative 15 minutes
const checkIns = await prisma.checkIn.count({
where: { userId }
});
expect(checkIns).toBe(1);
});
it('should determine check-in based on the user\'s timezone', async () => {
// Simulate a UTC+8 user
const result = await CheckInService.recordStudy(
userId,
15,
{ timezone: 'Asia/Shanghai' }
);
expect(result.checkedIn).toBe(true);
});
});
describe('getStreak', () => {
it('should return a streak of 0 for a new user', async () => {
const streak = await CheckInService.getStreak(userId);
expect(streak.current).toBe(0);
expect(streak.longest).toBe(0);
});
it('should return the longest streak', async () => {
await CheckInService.recordStudy(userId, 15);
await CheckInService.recordStudy(userId, 15);
const streak = await CheckInService.getStreak(userId);
expect(streak.longest).toBeGreaterThanOrEqual(2);
});
});
});
Running npm test at this point should be all red (failing tests), because the implementation hasn't been written yet.
Step 2: Write Implementation (Green)
Create lib/checkin/checkin.service.ts:
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);
// Find or create today's check-in record
const existingCheckIn = await prisma.checkIn.findUnique({
where: {
userId_date: {
userId,
date: todayUtc,
},
},
});
if (existingCheckIn) {
// Update duration
await prisma.checkIn.update({
where: { id: existingCheckIn.id },
data: { duration: existingCheckIn.duration + duration },
});
} else {
// Create new record
await prisma.checkIn.create({
data: {
userId,
date: todayUtc,
duration,
},
});
}
// Recalculate total 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) {
// First check-in ever
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)) {
// Consecutive check-in
newStreak = streak.currentStreak + 1;
} else if (lastDate && isSameDay(lastDate, checkInDate)) {
// Already checked in today, don't increase streak
newStreak = streak.currentStreak;
} else {
// Restart after a break, or first time
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,
};
}
}
Run the tests again: npm test, they should be all green now.
Step 3: Refactor
Look for duplicate code or unclear naming. For example, the timezone handling in updateStreak could be extracted into a utility function. After refactoring, run the tests again to ensure they are still all green.
This is the TDD Red-Green-Refactor cycle.
6.4 HARDGATE in Practice: Pre-commit Gate
The code is written, but don't even think about committing directly. Superpowers will stop you, requiring you to pass HARDGATE:
# 1. Run tests
npm test
# 2. Check coverage
npm run test:coverage
# 3. Code linting
npm run lint
# 4. Type checking
npm run typecheck
# 5. Build check
npm run build
If any step fails, Superpowers will output:
❌ HARDGATE BLOCKED
Failed items:
- test:coverage: Coverage 67% < 80% (threshold)
- lint: 3 errors in checkin.service.ts
Fix before committing. Here are suggestions:
1. Supplement unit tests for CheckInService.getCalendar...
2. Check the unused variable on line 45 of checkin.service.ts...
You fix them and run again until all pass, only then can you enter the next stage.
6.5 Connection Point Three: Superpowers TDD -> gstack review automatically takes effect
Notice this beautiful connection:
Because the tests you wrote during the TDD phase automatically become input for the gstack review phase. gstack review doesn't just look at whether the code is pretty; it checks:
- Do the tests cover all scenarios in the requirements specification?
- Are boundary conditions (15-minute threshold, timezones, duplicate check-ins) tested?
- Do the test names clearly express their intent?
If OpenSpec's Spec says "at most 1 check-in per calendar day," but your tests don't cover this case, gstack review will flag it in red:
⚠️ Requirement Traceability Failure
Requirement SPEC-001 §1.1 states: "At most 1 check-in per calendar day"
Test Coverage: Corresponding test case not found
Suggestion: Add a test for "multiple study sessions on the same day count as one check-in"
This is the requirement-test-code three-way binding, interlocking seamlessly.
VII. Stage Four: gstack review — Code Review + Scanning
7.1 What does the review stage do?
TDD has passed, and the code is mostly written. But gstack still needs to perform a comprehensive code review.
Unlike the plan phase review, the review phase targets the implementation code. It will:
- CEEO Re-check: Does the implementation match the design? Any performance traps?
- Security Scan: SQL injection? XSS? Authorization bypass?
- Requirement Traceability: Does each requirement point have a corresponding implementation and test?
- DF (Defect Finding): Proactively find bugs
7.2 Running gstack review
/gstack review --diff HEAD~1
gstack will generate gstack/reviews/review-2026-07-18.md:
# Code Review Report
## Scope of Changes
- `lib/checkin/checkin.service.ts` (new)
- `__tests__/unit/checkin.logic.test.ts` (new)
- `prisma/schema.prisma` (modified)
## CEEO Review
✅ Implementation matches design document DESIGN-001
✅ Database queries all have indexes
⚠️ `updateStreak` queries the database multiple times; could be optimized to a batch operation
## Security Review
✅ All Prisma queries use parameterization, no SQL injection risk
⚠️ POST /api/checkin lacks rate limiting; suggest adding:
```ts
import { rateLimit } from '@/lib/rate-limit';
export const POST = rateLimit({ max: 10, window: '1h' })(handler);
```
## Requirement Traceability
| Requirement | Implementation | Test | Status |
|------|------|------|------|
| Auto check-in (15 min) | ✅ | ✅ | Pass |
| Consecutive streak | ✅ | ✅ | Pass |
| Timezone handling | ✅ | ⚠️ | Single timezone tested; suggest adding UTC, DST tests |
| Leaderboard | ❌ | ❌ | Not implemented |
## Conclusion
Status: **CONDITIONAL PASS**
Condition: Supplement leaderboard implementation, or remove related requirements from this MR
7.3 How to handle review results?
If the review result is PASS, it automatically enters the next stage, QA.
If it's CONDITIONAL PASS, you need to fix the conditional issues.
If it's FAIL, it's sent back for a rewrite.
This is the meaning of a gate: bad code doesn't reach QA, let alone production.
VIII. Stage Five: QA — Playwright Real Browser Acceptance
8.1 Why have a QA stage?
Unit tests passed, code review passed, but can your check-in button actually be clicked in a real browser? Will the calendar component crash on an iPhone? Will the leaderboard freeze loading 1000 entries?
Playwright + Chromium is here to simulate real user operations.
8.2 Configuring Playwright E2E Tests
Step 1: Install Playwright
npm install --save-dev @playwright/test
npx playwright install chromium
Step 2: Create E2E Tests
__tests__/e2e/checkin.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Daily Check-in E2E', () => {
test.beforeEach(async ({ page }) => {
// Login
await page.goto('/login');
await page.fill('[name="email"]', '[email protected]');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
});
test('Complete check-in flow', async ({ page }) => {
// 1. Go to check-in page
await page.goto('/checkin');
await expect(page.locator('h1')).toContainText('Daily Check-in');
// 2. Initial state: not checked in today
await expect(page.locator('[data-testid="streak-count"]')).toHaveText('0');
await expect(page.locator('[data-testid="today-status"]')).toContainText('Not Checked In');
// 3. Simulate studying (call API or direct action)
await page.goto('/learn');
await page.click('[data-testid="start-lesson"]');
// Simulate 15 minutes of study (in real tests, mock time or call API)
await page.evaluate(() => {
// Directly call API to simulate study completion
return fetch('/api/checkin', {
method: 'POST',
body: JSON.stringify({ duration: 15 }),
headers: { 'Content-Type': 'application/json' }
});
});
// 4. Go back to check-in page, verify checked in
await page.goto('/checkin');
await expect(page.locator('[data-testid="today-status"]')).toContainText('Checked In');
await expect(page.locator('[data-testid="streak-count"]')).toHaveText('1');
// 5. Verify today is marked on the calendar
const todayCell = page.locator('[data-testid="calendar-today"]');
await expect(todayCell).toHaveClass(/checked-in/);
// 6. Verify flame animation
await expect(page.locator('[data-testid="flame-icon"]')).toBeVisible();
});
test('Leaderboard loading', async ({ page }) => {
await page.goto('/checkin');
// Scroll to leaderboard
await page.click('[data-testid="leaderboard-tab"]');
// Verify leaderboard has data
const rows = page.locator('[data-testid="leaderboard-row"]');
await expect(rows).toHaveCount.greaterThan(0);
// Verify top 3 have medal icons
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('Responsive layout', async ({ page }) => {
// Simulate mobile
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/checkin');
// Verify calendar can scroll horizontally
const calendar = page.locator('[data-testid="checkin-calendar"]');
await expect(calendar).toBeVisible();
// Screenshot comparison (optional)
await expect(page).toHaveScreenshot('checkin-mobile.png');
});
});
Step 3: Run E2E tests
npx playwright test __tests__/e2e/checkin.spec.ts --project=chromium
8.3 QA Stage Acceptance Criteria
gstack's QA stage configuration includes:
qa:
thresholds:
e2e_pass_rate: 100%
visual_diff: 0.1 # Screenshot comparison difference < 10%
performance:
lcp: 2500 # Largest Contentful Paint < 2.5s
fid: 100 # First Input Delay < 100ms
All must pass to enter the ship stage.
IX. Stage Six: ship — One-click release, PR auto-generated
9.1 What does the ship stage do?
Finally, the exciting release phase! gstack's ship stage will automatically:
- Bump version
- Generate Changelog
- Create a Git branch
- Commit code
- Create a Pull Request
- Push to remote
9.2 Running ship
/gstack ship --message "feat: Daily study check-in feature"
gstack will execute:
# 1. Check that the current branch is clean
# 2. Create a feature branch
git checkout -b feat/daily-checkin
# 3. Auto bump version (based on conventional commit)
npm version minor
# 4. Generate Changelog
npx conventional-changelog -p angular -i CHANGELOG.md -s
# 5. Commit all changes
git add .
git commit -m "feat: Daily study check-in feature
- Add CheckIn, Streak data models
- Implement core check-in logic (15-min threshold, consecutive streak, timezone handling)
- Add /checkin page, calendar component, leaderboard
- Complete unit tests + E2E tests
Closes TASK-001"
# 6. Push and create PR
git push origin feat/daily-checkin
gh pr create --title "feat: Daily study check-in feature" --body "..."
9.3 What does the generated PR look like?
gstack automatically generates a structured PR description:
## Feature Description
Implements a daily study check-in system to motivate users to keep learning.
## Scope of Changes
- ✅ Database: Added CheckIn, Streak models
- ✅ API: 4 new endpoints
- ✅ Frontend: /checkin page + 3 new components
- ✅ Tests: 12 unit tests + 3 E2E tests
## Requirement Traceability
- Related Spec: SPEC-001
- Related Design: DESIGN-001
- Related Tasks: TASK-001
## Test Status
- [x] Unit test pass rate: 100% (12/12)
- [x] E2E test pass rate: 100% (3/3)
- [x] Code coverage: 87%
- [x] Lighthouse score: 95
## Screenshots
[Auto-inserted Playwright screenshots]
## Checklist
- [x] OpenSpec requirements archived
- [x] gstack review passed
- [x] Superpowers HARDGATE passed
- [x] QA acceptance passed
This PR quality is more standardized than what many engineers write themselves.
X. Stage Seven: OpenSpec archive — Archive deltas, merge into main spec
10.1 What does the archive stage do?
The feature is live, but OpenSpec's job isn't done.
Between the requirement specification (Spec) and the implementation, there are often some deviations (deltas). For example:
- Edge cases not considered during design
- Trade-offs made during implementation
- Minor adjustments from user feedback
The archive stage records these deltas and merges them back into the main specification, forming the project's knowledge asset.
10.2 Running archive
/opspect archive --feature "daily-checkin"
OpenSpec will:
- Compare
openspec/specs/with the actual implementation to find deltas - Generate
openspec/archive/2026-07-18-daily-checkin-delta.md
---
feature: daily-checkin
status: archived
date: 2026-07-18
---
# Archive Report: Daily Study Check-in
## Requirement Change Log (Delta)
### 1. Timezone Handling
**Original Design:** Default UTC+8
**Actual Implementation:** Changed to configurable, passed via `options.timezone`
**Reason:** International users need multi-timezone support
**Written back to spec:** ✅ Updated SPEC-001
### 2. Leaderboard
**Original Design:** Real-time database query
**Actual Implementation:** Added Redis cache layer
**Reason:** CEEO review in gstack plan phase flagged performance risk
**Written back to spec:** ✅ Updated SPEC-001 §2.4
### 3. Check-in Animation
**Original Design:** Only flame bounce
**Actual Implementation:** Added Confetti effect
**Reason:** UX review suggested enhancing positive feedback
**Written back to spec:** ❌ Design detail, not included in spec
## Lessons Learned
- TDD is highly effective at the business logic layer, but ROI is lower for UI animation layer testing
- Timezone is an easily overlooked point; all future time-related features must consider it
- Caching should be considered during the design phase, not patched in after review
## Related Documents
- Proposal: openspec/proposals/PROP-001.md
- Spec: openspec/specs/SPEC-001.md (updated to v1.1)
- Design: openspec/designs/DESIGN-001.md
10.3 Connection Point Four: ship triggers archive, closing the loop
This is the most elegant connection point:
When gstack's ship stage successfully creates a PR and pushes, it automatically triggers OpenSpec's archive command (via a webhook in the config or Claude Code's hook mechanism).
Thus:
ship complete -> triggers archive -> delta archived -> spec updated -> knowledge accumulated
The complete lifecycle of a feature is now closed-loop.
XI. Pitfall Avoidance Guide: Don't step on the mines I stepped on
❌ Pitfall 1: Duplicate Gates
Wrong approach: OpenSpec already did design approval, and gstack plan reviews the design from scratch again.
Right approach: gstack plan reads OpenSpec artifacts as input and only performs supplementary reviews (like CEEO, security), not re-reviewing already approved designs.
Configuration:
# gstack/config/pipeline.yaml
phases:
- name: plan
review_scope: [ceoo, security, dx] # Does not include design
❌ Pitfall 2: Treating archive as release
Wrong approach: Thinking the feature is live once archive is done.
Right approach: ship is the only release exit. archive is just a wrap-up record, like locking a diary in a drawer after writing.
❌ Pitfall 3: Blindly executing TDD
Wrong approach: Writing a test first even for a tailwind config file.
Right approach: Superpowers' CLAUDE.md explicitly states exemption clauses:
### Exemption Clauses
1. One-off prototypes (marked `// POC-EXEMPT`)
2. Pure configuration files
3. Auto-generated code
TDD is not a religion; it's a tool. Knowing when not to use it is more important than knowing when to use it.
❌ Pitfall 4: Version conflicts between the three tools
Wrong approach: OpenSpec v2's artifact format is unreadable by gstack v1.
Right approach: Unified version management. Declare at the top of CLAUDE.md:
## Tool Versions
- OpenSpec: v1.0
- Superpowers: v2.1
- gstack: v1.2
❌ Pitfall 5: Not leaving state space for the tools
Wrong approach: All files piled in the project root, with the three tools overwriting each other.
Right approach: Strict directory isolation:
openspec/ <- OpenSpec exclusive
.claude/ <- Superpowers exclusive
gstack/ <- gstack exclusive
XII. Complete File Structure Reference
After walking through the entire process, your project directory should look like this:
duolinguo/
├── app/
│ ├── checkin/
│ │ └── page.tsx # /checkin page
│ └── 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 # Core business logic
├── prisma/
│ └── schema.prisma # CheckIn + Streak models
├── __tests__/
│ ├── unit/
│ │ └── checkin.logic.test.ts # Unit tests
│ └── e2e/
│ └── checkin.spec.ts # E2E tests
├── openspec/ # OpenSpec territory
│ ├── 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 territory
│ ├── config/
│ │ └── pipeline.yaml
│ ├── reviews/
│ │ ├── plan-2026-07-18.md
│ │ └── review-2026-07-18.md
│ └── sessions/
│ └── session-2026-07-18.json
├── .claude/ # Superpowers territory
│ ├── skills/
│ │ ├── openspec.md
│ │ ├── superpowers.md
│ │ └── gstack.md
│ └── CLAUDE.md # Project standards main entry
├── playwright.config.ts
├── vitest.config.ts
├── package.json
└── CHANGELOG.md
XIII. Summary: What is the essence of the Three-in-One?
By now, you should have realized—the core of this workflow isn't "how awesome the tools are," but rather three-layer separation + automatic chaining.
Three-Layer Separation
| Layer | Responsible For | When It Intervenes |
|---|---|---|
| Requirements Layer (OpenSpec) | "What to do" | Before coding |
| Quality Layer (Superpowers) | "How to do it right" | During coding |
| Process Layer (gstack) | "How to release" | After coding |
Each does its own job, no stealing work.
Automatic Chaining
Four connection points string the three phases into one assembly line:
OpenSpec artifacts --> gstack plan review input
Superpowers TDD --> gstack review automatically takes effect
gstack ship --> OpenSpec archive automatically triggered
delta archived --> main spec updated (knowledge accumulation)
You only need to enter a few slash commands:
/opspect new "Feature Name" # Lock requirements
/gstack plan # Review
/tdd # Test-first coding
/gstack review # Code review
/gstack qa # Browser acceptance
/gstack ship # One-click release
/opspect archive # Archive and wrap up
The rest, the tools chain themselves.
Advice for You
If you're a solo developer, I suggest starting with Superpowers. The TDD iron law has the highest ROI; building the habit of test-first directly elevates your code quality.
If you're a small team, add OpenSpec. Locking down requirements reduces rework caused by "I think" or "I remember."
If you're working on a formal project, add gstack too. Process-driven releases and auto-generated PRs double team collaboration efficiency.
But don't adopt them all at once, or you'll be crushed by the process. Take it step by step, master one layer first, then stack the next.
XIV. Final Words
There are more and more AI programming tools, but tools themselves don't generate value. The chaining between tools generates value.
OpenSpec locks requirements, saving you from rework; Superpowers gates quality, saving you from digging pitfalls; gstack wraps up the process, saving you from worry.
The three combined are like installing an assembly line for AI programming—you just put raw materials (requirements) at the start and take the finished product (release) at the end. The intermediate processes are automated.
Of course, this workflow isn't a silver bullet. It suits feature development with clear requirements and long-term maintenance needs. If you're just writing a one-off script or doing a POC to validate an idea, don't put on the full armor; you can even skip TDD and go as fast as you can.
Tools serve people, not the other way around. Knowing when to use a cannon and when to use a pistol is the real skill.
I hope this article helps you. If you follow along and it works, or if you step on new mines, feel free to share in the comments. The milk tea is on me (the online version, brew it yourself).
Reference Links:
- Video Tutorial: Bilibili - OpenSpec, Superpowers, gstack Three-in-One
- OpenSpec Docs: (Please replace with actual link)
- Superpowers Docs: (Please replace with actual link)
- gstack Docs: (Please replace with actual link)
End of article. Writing is hard work; likes, bookmarks, and shares are the greatest support for the author. See you in the next one!