Files
superpowers/evals/setup_helpers/wave.py
Jesse Vincent 3b412a3836 Lift drill into evals/ at 013fcb8b7dbefd6d3fa4653493e5d2ec8e7f985b
rsync of obra/drill@013fcb8b7d into superpowers/evals/, excluding
.git/, .venv/, results/, .env/, __pycache__/, *.egg-info/,
.private-journal/.

The drill repo is unaffected by this commit; archival is a separate
manual step after this PR merges.

Source SHA recorded at evals/.drill-source-sha for divergence
detection.
2026-05-06 15:47:39 -07:00

1336 lines
45 KiB
Python

"""Setup helpers for wave execution drill scenarios.
Each helper creates a test repository with a plan file that exercises a
specific aspect of the wave decomposition algorithm:
- create_wave_test_repo: full 5-task plan spanning 3 waves
- create_wave_test_repo_minimal: smaller 3-task plan for faster runs
- create_waves_file: full 5-task plan pre-decomposed to .waves.md
- create_waves_file_minimal: 3-task plan pre-decomposed to .waves.md
- create_waves_file_with_broken_task: 3-task plan where Task 3 is structurally
impossible (exercises failure escalation)
- create_false_overlap_repo: same filename in different directories
- create_dependency_chain_repo: semantic (import-based) dependencies
- create_conflict_surface_repo: implicit barrel-file conflicts
"""
from __future__ import annotations
from pathlib import Path
from setup_helpers.base import _git
# ----------------------------------------------------------------------------
# Shared fixture content
# ----------------------------------------------------------------------------
PACKAGE_JSON = """\
{
"name": "wave-test-fixture",
"version": "0.1.0",
"private": true,
"scripts": {
"test": "jest",
"lint": "echo 'no lint configured' && exit 0",
"build": "tsc -p tsconfig.json"
},
"devDependencies": {
"typescript": "^5.4.0",
"jest": "^29.7.0",
"@types/jest": "^29.5.12",
"ts-jest": "^29.1.2"
}
}
"""
TSCONFIG_JSON = """\
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"outDir": "dist",
"rootDir": "."
},
"include": ["src/**/*.ts", "tests/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
"""
# jest.config.js uses ts-jest preset so implementers can write TypeScript
# test files that import from src/ without configuring anything themselves.
# This is deliberately provided up-front so the implementer never has to
# diagnose jest/ts-jest interop issues mid-task.
JEST_CONFIG_JS = """\
/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['<rootDir>/tests/**/*.test.ts'],
rootDir: '.',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
};
"""
CLAUDE_MD = """\
# Project Commands
**install**: npm ci
**test**: npm test
**lint**: npm run lint
**build**: npm run build
"""
README_MD = """\
# Wave Test Fixture
Synthetic project used by drill scenarios to exercise the wave decomposition
algorithm. Do not edit by hand — this file is generated by
`setup_helpers/wave.py`.
"""
# ----------------------------------------------------------------------------
# Internal helpers
# ----------------------------------------------------------------------------
def _init_base_repo(workdir: Path) -> None:
"""Create the base TypeScript repo on main with the standard fixture files."""
workdir.mkdir(parents=True, exist_ok=True)
_git(["git", "init", "-b", "main"], cwd=workdir)
_git(["git", "config", "user.email", "drill@test.local"], cwd=workdir)
_git(["git", "config", "user.name", "Drill Test"], cwd=workdir)
(workdir / "package.json").write_text(PACKAGE_JSON)
(workdir / "README.md").write_text(README_MD)
(workdir / "tsconfig.json").write_text(TSCONFIG_JSON)
(workdir / "jest.config.js").write_text(JEST_CONFIG_JS)
(workdir / "CLAUDE.md").write_text(CLAUDE_MD)
_git(
["git", "add", "package.json", "README.md", "tsconfig.json",
"jest.config.js", "CLAUDE.md"],
cwd=workdir,
)
_git(["git", "commit", "-m", "initial commit"], cwd=workdir)
def _write_file(workdir: Path, rel_path: str, content: str) -> None:
"""Write a file, creating parent directories as needed."""
target = workdir / rel_path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
def _ensure_dir(workdir: Path, rel_path: str) -> None:
"""Create a directory and drop a .gitkeep so git can track it."""
d = workdir / rel_path
d.mkdir(parents=True, exist_ok=True)
(d / ".gitkeep").write_text("")
def _commit_all_on_feature_branch(workdir: Path) -> None:
"""Checkout feature/test-implementation and commit every remaining change."""
_git(["git", "checkout", "-b", "feature/test-implementation"], cwd=workdir)
_git(["git", "add", "-A"], cwd=workdir)
_git(["git", "commit", "-m", "add wave test plan and fixtures"], cwd=workdir)
# ----------------------------------------------------------------------------
# Plan bodies
# ----------------------------------------------------------------------------
WAVE_TEST_PLAN = """\
# Wave Decomposition Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Exercise the full wave decomposition algorithm across 3 waves.
**Architecture:** Foundation types feed independent services which are wired
together by an API routes layer. This shape intentionally produces one
sequential task in Wave 1, three parallel tasks in Wave 2, and one
sequential integration task in Wave 3.
**Tech Stack:** TypeScript, Jest.
---
### Task 1: Foundation types
**Files:**
- Create: `src/types/auth.ts`
- Create: `src/types/users.ts`
- Create: `src/types/billing.ts`
- Modify: `src/types/index.ts`
**Acceptance Criteria:**
- `src/types/auth.ts` exports `User` and `Session` interfaces.
- `src/types/users.ts` exports a `UserProfile` interface with `id` and `email`.
- `src/types/billing.ts` exports `Plan` and `Subscription` interfaces.
- `src/types/index.ts` re-exports everything from the three files above.
- `npm run build` succeeds with no type errors.
- [ ] **Step 1: Create src/types/auth.ts with User and Session interfaces.**
- [ ] **Step 2: Create src/types/users.ts with UserProfile interface.**
- [ ] **Step 3: Create src/types/billing.ts with Plan and Subscription interfaces.**
- [ ] **Step 4: Update src/types/index.ts to re-export the three modules.**
- [ ] **Step 5: Run `npm run build` and commit.**
---
### Task 2: Auth service
**Files:**
- Create: `src/services/auth.ts`
- Create: `tests/auth.test.ts`
**Acceptance Criteria:**
- `src/services/auth.ts` exports an `AuthService` class with a `login(email, password)` method.
- `AuthService.login` returns a `Session` imported from `src/types/auth.ts`.
- `tests/auth.test.ts` covers the happy-path login case.
- `tests/auth.test.ts` covers an invalid-credentials failure case.
- `npm test -- tests/auth.test.ts` passes.
- [ ] **Step 1: Write tests/auth.test.ts covering login success and failure.**
- [ ] **Step 2: Implement src/services/auth.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/auth.test.ts` and commit.**
---
### Task 3: Users service
**Files:**
- Create: `src/services/users.ts`
- Create: `tests/users.test.ts`
**Acceptance Criteria:**
- `src/services/users.ts` exports a `UsersService` class with `getProfile(id)`.
- `UsersService.getProfile` returns a `UserProfile` imported from `src/types/users.ts`.
- `tests/users.test.ts` covers the happy-path lookup case.
- `tests/users.test.ts` covers a not-found case.
- `npm test -- tests/users.test.ts` passes.
- [ ] **Step 1: Write tests/users.test.ts covering getProfile success and missing.**
- [ ] **Step 2: Implement src/services/users.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/users.test.ts` and commit.**
---
### Task 4: Billing service
**Files:**
- Create: `src/services/billing.ts`
- Create: `tests/billing.test.ts`
**Acceptance Criteria:**
- `src/services/billing.ts` exports a `BillingService` class with `subscribe(userId, planId)`.
- `BillingService.subscribe` returns a `Subscription` imported from `src/types/billing.ts`.
- `tests/billing.test.ts` covers a successful subscription.
- `tests/billing.test.ts` covers a failed subscription.
- `npm test -- tests/billing.test.ts` passes.
- [ ] **Step 1: Write tests/billing.test.ts covering subscribe success and failure.**
- [ ] **Step 2: Implement src/services/billing.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/billing.test.ts` and commit.**
---
### Task 5: API routes
**Files:**
- Create: `src/api/routes.ts`
- Modify: `src/index.ts`
**Acceptance Criteria:**
- `src/api/routes.ts` imports `AuthService`, `UsersService`, and `BillingService`.
- `src/api/routes.ts` exports a `registerRoutes(app)` function that wires the three services.
- `src/index.ts` imports `registerRoutes` and calls it with the app.
- `npm run build` succeeds.
- `npm test` passes end to end.
- [ ] **Step 1: Create src/api/routes.ts that composes the three services.**
- [ ] **Step 2: Update src/index.ts to register the routes on startup.**
- [ ] **Step 3: Run `npm run build && npm test` and commit.**
"""
FALSE_OVERLAP_PLAN = """\
# False Overlap Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Verify that wave decomposition uses full paths, not bare filenames,
when detecting file overlap between tasks.
**Architecture:** Three fully-independent domains (auth, users, billing) each
define a locally-scoped `types.ts`. A decomposer that keys on filename alone
would serialize these tasks. A correct decomposer keys on full paths and
parallelizes them.
**Tech Stack:** TypeScript, Jest.
---
### Task 1: Auth domain scaffolding
**Files:**
- Create: `src/auth/types.ts`
- Create: `src/auth/service.ts`
**Acceptance Criteria:**
- `src/auth/types.ts` exports an `AuthToken` interface local to the auth domain.
- `src/auth/service.ts` exports an `AuthService` class that uses `AuthToken`.
- Nothing outside `src/auth/` is touched.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/auth/types.ts with AuthToken.**
- [ ] **Step 2: Create src/auth/service.ts importing AuthToken locally.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
### Task 2: Users domain scaffolding
**Files:**
- Create: `src/users/types.ts`
- Create: `src/users/service.ts`
**Acceptance Criteria:**
- `src/users/types.ts` exports a `UserRecord` interface local to the users domain.
- `src/users/service.ts` exports a `UsersService` class that uses `UserRecord`.
- Nothing outside `src/users/` is touched.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/users/types.ts with UserRecord.**
- [ ] **Step 2: Create src/users/service.ts importing UserRecord locally.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
### Task 3: Billing domain scaffolding
**Files:**
- Create: `src/billing/types.ts`
- Create: `src/billing/service.ts`
**Acceptance Criteria:**
- `src/billing/types.ts` exports an `Invoice` interface local to the billing domain.
- `src/billing/service.ts` exports a `BillingService` class that uses `Invoice`.
- Nothing outside `src/billing/` is touched.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/billing/types.ts with Invoice.**
- [ ] **Step 2: Create src/billing/service.ts importing Invoice locally.**
- [ ] **Step 3: Run `npm run build` and commit.**
"""
DEPENDENCY_CHAIN_PLAN = """\
# Dependency Chain Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Verify that wave decomposition detects semantic (import-based)
dependencies, not just file-overlap dependencies.
**Architecture:** Two independent type modules (auth, billing) can be built
in parallel. A session service consumes the auth types but never touches
the billing types — the decomposer should recognize this asymmetric
dependency via the import, even though there is no file overlap.
**Tech Stack:** TypeScript, Jest.
---
### Task 1: Create auth types
**Files:**
- Create: `src/types/auth.ts`
**Acceptance Criteria:**
- `src/types/auth.ts` exports a `User` interface with `id` and `email`.
- `src/types/auth.ts` exports a `Session` interface with `userId` and `token`.
- No other file is modified.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/types/auth.ts with User and Session interfaces.**
- [ ] **Step 2: Run `npm run build` and commit.**
---
### Task 2: Create billing types
**Files:**
- Create: `src/types/billing.ts`
**Acceptance Criteria:**
- `src/types/billing.ts` exports a `Plan` interface with `id` and `price`.
- `src/types/billing.ts` exports a `Subscription` interface with `userId` and `planId`.
- No other file is modified.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/types/billing.ts with Plan and Subscription interfaces.**
- [ ] **Step 2: Run `npm run build` and commit.**
---
### Task 3: Create session service
**Files:**
- Create: `src/services/session.ts`
**Acceptance Criteria:**
- `src/services/session.ts` **imports** `User` and `Session` from `src/types/auth.ts`.
- `src/services/session.ts` does **not** import from `src/types/billing.ts`.
- `src/services/session.ts` does **not** modify `src/types/auth.ts`.
- `src/services/session.ts` exports a `SessionService` class with `create(user: User): Session`.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/services/session.ts importing User and Session from ../types/auth.**
- [ ] **Step 2: Implement SessionService.create.**
- [ ] **Step 3: Run `npm run build` and commit.**
"""
WAVE_TEST_PLAN_MINIMAL = """\
# Wave Execution Minimal Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Exercise wave execution across two waves with the smallest
possible surface — a single foundation task followed by two independent
parallel services.
**Architecture:** Foundation types feed two independent, parallel
utility services. This produces one sequential task in Wave 1 and two
parallel tasks in Wave 2.
**Tech Stack:** TypeScript, Jest.
---
### Task 1: Foundation types
**Files:**
- Create: `src/types/core.ts`
- Modify: `src/types/index.ts`
**Acceptance Criteria:**
- `src/types/core.ts` exports a `User` interface with `id` and `email`.
- `src/types/core.ts` exports a `Session` interface with `userId` and `token`.
- `src/types/index.ts` re-exports everything from `src/types/core.ts`.
- `npm run build` succeeds with no type errors.
- [ ] **Step 1: Create src/types/core.ts with User and Session interfaces.**
- [ ] **Step 2: Update src/types/index.ts to re-export from ./core.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
### Task 2: Logger service
**Files:**
- Create: `src/services/logger.ts`
- Create: `tests/logger.test.ts`
**Acceptance Criteria:**
- `src/services/logger.ts` exports a `Logger` class with an `info(message: string)` method.
- `Logger.info` appends a timestamped entry to an internal buffer.
- `tests/logger.test.ts` covers a happy-path info case.
- `tests/logger.test.ts` covers a repeated-call buffering case.
- `npm test -- tests/logger.test.ts` passes.
- [ ] **Step 1: Write tests/logger.test.ts covering info and buffering.**
- [ ] **Step 2: Implement src/services/logger.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/logger.test.ts` and commit.**
---
### Task 3: Clock service
**Files:**
- Create: `src/services/clock.ts`
- Create: `tests/clock.test.ts`
**Acceptance Criteria:**
- `src/services/clock.ts` exports a `Clock` class with a `now(): number` method.
- `Clock.now` returns the current Unix timestamp in milliseconds.
- `tests/clock.test.ts` covers a happy-path now case.
- `tests/clock.test.ts` covers the return value being a finite number.
- `npm test -- tests/clock.test.ts` passes.
- [ ] **Step 1: Write tests/clock.test.ts covering now success and type.**
- [ ] **Step 2: Implement src/services/clock.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/clock.test.ts` and commit.**
"""
CONFLICT_SURFACE_PLAN = """\
# Conflict Surface Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Verify the conflict-surface heuristic catches implicit barrel-file
modifications that the task file lists intentionally omit.
**Architecture:** `src/services/index.ts` exists as a barrel file before the
plan runs. Each task creates a new service module and needs to add an
export line to `src/services/index.ts`, but the task Files list only
names the new module. A pure file-overlap decomposer would parallelize
these tasks; the conflict-surface heuristic should recognize that every
task needs to touch the barrel file and either serialize them or add the
barrel file to each task's list.
**Tech Stack:** TypeScript, Jest.
---
### Task 1: Create auth service
**Files:**
- Create: `src/services/auth.ts`
**Acceptance Criteria:**
- `src/services/auth.ts` exports an `AuthService` class with a `login` method.
- `AuthService` is re-exported from `src/services/index.ts` (add export to index).
- Importing `AuthService` from `src/services` works at build time.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/services/auth.ts with AuthService.**
- [ ] **Step 2: Add `export * from './auth';` to src/services/index.ts.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
### Task 2: Create users service
**Files:**
- Create: `src/services/users.ts`
**Acceptance Criteria:**
- `src/services/users.ts` exports a `UsersService` class with a `getProfile` method.
- `UsersService` is re-exported from `src/services/index.ts` (add export to index).
- Importing `UsersService` from `src/services` works at build time.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/services/users.ts with UsersService.**
- [ ] **Step 2: Add `export * from './users';` to src/services/index.ts.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
### Task 3: Create billing service
**Files:**
- Create: `src/services/billing.ts`
**Acceptance Criteria:**
- `src/services/billing.ts` exports a `BillingService` class with a `subscribe` method.
- `BillingService` is re-exported from `src/services/index.ts` (add export to index).
- Importing `BillingService` from `src/services` works at build time.
- `npm run build` succeeds.
- [ ] **Step 1: Create src/services/billing.ts with BillingService.**
- [ ] **Step 2: Add `export * from './billing';` to src/services/index.ts.**
- [ ] **Step 3: Run `npm run build` and commit.**
"""
# ----------------------------------------------------------------------------
# Public helpers
# ----------------------------------------------------------------------------
def create_wave_test_repo(workdir: Path) -> None:
"""Create a 5-task plan exercising the full wave decomposition algorithm.
Expected decomposition:
- Wave 1: Task 1 (foundation types)
- Wave 2: Tasks 2, 3, 4 (parallel, independent service implementations)
- Wave 3: Task 5 (API routes integration, depends on services)
"""
workdir = Path(workdir)
_init_base_repo(workdir)
# Pre-create the barrel file and stub directories the plan references.
_write_file(workdir, "src/types/index.ts", "export {};\n")
for d in ("src/auth", "src/users", "src/billing", "src/api", "tests"):
_ensure_dir(workdir, d)
_write_file(workdir, "docs/superpowers/plans/test-plan.md", WAVE_TEST_PLAN)
_commit_all_on_feature_branch(workdir)
def create_false_overlap_repo(workdir: Path) -> None:
"""Create a plan where three tasks share a filename but no full-path overlap.
Expected decomposition:
- Wave 1: Tasks 1, 2, 3 all parallel (no true file overlap)
"""
workdir = Path(workdir)
_init_base_repo(workdir)
for d in ("src/auth", "src/users", "src/billing"):
_ensure_dir(workdir, d)
_write_file(workdir, "docs/superpowers/plans/test-plan.md", FALSE_OVERLAP_PLAN)
_commit_all_on_feature_branch(workdir)
def create_dependency_chain_repo(workdir: Path) -> None:
"""Create a plan where Task 3 semantically depends on Task 1 via imports.
Expected decomposition:
- Wave 1: Tasks 1, 2 (parallel — independent type modules)
- Wave 2: Task 3 (depends on Task 1's src/types/auth.ts)
"""
workdir = Path(workdir)
_init_base_repo(workdir)
_ensure_dir(workdir, "src/types")
_ensure_dir(workdir, "src/services")
_write_file(workdir, "docs/superpowers/plans/test-plan.md", DEPENDENCY_CHAIN_PLAN)
_commit_all_on_feature_branch(workdir)
def create_wave_test_repo_minimal(workdir: Path) -> None:
"""Create a 3-task plan exercising wave execution with minimal surface.
Expected decomposition:
- Wave 1: Task 1 (foundation types)
- Wave 2: Tasks 2, 3 (parallel, independent logger + clock services)
"""
workdir = Path(workdir)
_init_base_repo(workdir)
# Pre-create the barrel file and stub directories the plan references.
_write_file(workdir, "src/types/index.ts", "export {};\n")
for d in ("src/services", "tests"):
_ensure_dir(workdir, d)
_write_file(workdir, "docs/superpowers/plans/test-plan.md", WAVE_TEST_PLAN_MINIMAL)
_commit_all_on_feature_branch(workdir)
# ----------------------------------------------------------------------------
# Pre-decomposed waves files
# ----------------------------------------------------------------------------
WAVE_TEST_SPEC = """\
# Wave Decomposition Test Specification
## Overview
This specification describes a synthetic TypeScript project used to exercise
the full wave execution pipeline. The feature is a small, illustrative API
surface composed of three independent services (auth, users, billing) wired
together behind a thin routes layer. It exists solely so drill scenarios can
verify that an agent correctly runs an already-decomposed waves file from
start to finish.
## Scope
The spec covers:
- A shared types module that declares the core domain interfaces.
- Three independent service classes, each with a small happy-path and
failure-path test suite.
- An API routes module that composes the three services.
## Non-goals
- Real persistence, real HTTP handling, real authentication. The exercise is
purely about wave execution mechanics, not production-quality code.
"""
WAVE_TEST_SPEC_MINIMAL = """\
# Minimal Wave Execution Test Specification
## Overview
This specification describes a minimal TypeScript project used to exercise
the wave execution pipeline with the smallest possible task surface. The
feature is a tiny utility layer composed of two independent services
(logger, clock) built on top of a shared types module.
## Scope
The spec covers:
- A shared types module that declares `User` and `Session` interfaces.
- A logger service with a buffered `info` method.
- A clock service with a `now()` method returning the current Unix
timestamp in milliseconds.
## Non-goals
- Log rotation, log transport, time sources other than `Date.now()`, or
any production-grade concerns. The fixture exists purely to exercise
wave execution over a small set of parallelizable tasks.
"""
WAVE_TEST_WAVES_FULL = """\
---
run_id: testw5
source_plan: docs/superpowers/plans/test-plan.md
spec_path: docs/superpowers/specs/test-spec.md
feature_branch: feature/test-implementation
status: approved
sequential_time: 8h
parallel_time: 4h
savings: 50%
waves:
- {wave: 1, strategy: sequential, tasks: [1], depends_on: []}
- {wave: 2, strategy: parallel, tasks: [2, 3, 4], depends_on: [1]}
- {wave: 3, strategy: sequential, tasks: [5], depends_on: [2, 3, 4]}
---
# Wave Decomposition Test — Waves File
## Waves Overview
| Wave | Strategy | Tasks | Depends On | Notes |
|------|------------|-----------|------------|-----------------------------------------|
| 1 | sequential | 1 | — | Foundation types, must land first |
| 2 | parallel | 2, 3, 4 | 1 | Independent service implementations |
| 3 | sequential | 5 | 2, 3, 4 | API routes integration glue |
**Sequential time estimate:** 8h
**Parallel time estimate:** 4h
**Savings:** 50%
---
## Wave 1 — Foundation (sequential)
Task 1 must land before any service work can begin because every Wave 2
service imports from `src/types/index.ts`.
### Task 1: Foundation types
**Files:**
- Create: `src/types/auth.ts`
- Create: `src/types/users.ts`
- Create: `src/types/billing.ts`
- Modify: `src/types/index.ts`
**Acceptance Criteria:**
- `src/types/auth.ts` exports `User` and `Session` interfaces.
- `src/types/users.ts` exports a `UserProfile` interface with `id` and `email`.
- `src/types/billing.ts` exports `Plan` and `Subscription` interfaces.
- `src/types/index.ts` re-exports everything from the three files above.
- `npm run build` succeeds with no type errors.
- [ ] **Step 1: Create src/types/auth.ts with User and Session interfaces.**
- [ ] **Step 2: Create src/types/users.ts with UserProfile interface.**
- [ ] **Step 3: Create src/types/billing.ts with Plan and Subscription interfaces.**
- [ ] **Step 4: Update src/types/index.ts to re-export the three modules.**
- [ ] **Step 5: Run `npm run build` and commit.**
---
## Wave 2 — Independent services (parallel)
Tasks 2, 3, and 4 have no file overlap and no cross-task imports; they
can be executed in parallel in isolated worktrees and merged at the
wave boundary.
### File ownership
```
Task 2 (auth service):
- src/services/auth.ts [create]
- tests/auth.test.ts [create]
Task 3 (users service):
- src/services/users.ts [create]
- tests/users.test.ts [create]
Task 4 (billing service):
- src/services/billing.ts [create]
- tests/billing.test.ts [create]
```
No two tasks in Wave 2 touch the same path.
### Task 2: Auth service
**Files:**
- Create: `src/services/auth.ts`
- Create: `tests/auth.test.ts`
**Acceptance Criteria:**
- `src/services/auth.ts` exports an `AuthService` class with a `login(email, password)` method.
- `AuthService.login` returns a `Session` imported from `src/types/auth.ts`.
- `tests/auth.test.ts` covers the happy-path login case.
- `tests/auth.test.ts` covers an invalid-credentials failure case.
- `npm test -- tests/auth.test.ts` passes.
- [ ] **Step 1: Write tests/auth.test.ts covering login success and failure.**
- [ ] **Step 2: Implement src/services/auth.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/auth.test.ts` and commit.**
### Task 3: Users service
**Files:**
- Create: `src/services/users.ts`
- Create: `tests/users.test.ts`
**Acceptance Criteria:**
- `src/services/users.ts` exports a `UsersService` class with `getProfile(id)`.
- `UsersService.getProfile` returns a `UserProfile` imported from `src/types/users.ts`.
- `tests/users.test.ts` covers the happy-path lookup case.
- `tests/users.test.ts` covers a not-found case.
- `npm test -- tests/users.test.ts` passes.
- [ ] **Step 1: Write tests/users.test.ts covering getProfile success and missing.**
- [ ] **Step 2: Implement src/services/users.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/users.test.ts` and commit.**
### Task 4: Billing service
**Files:**
- Create: `src/services/billing.ts`
- Create: `tests/billing.test.ts`
**Acceptance Criteria:**
- `src/services/billing.ts` exports a `BillingService` class with `subscribe(userId, planId)`.
- `BillingService.subscribe` returns a `Subscription` imported from `src/types/billing.ts`.
- `tests/billing.test.ts` covers a successful subscription.
- `tests/billing.test.ts` covers a failed subscription.
- `npm test -- tests/billing.test.ts` passes.
- [ ] **Step 1: Write tests/billing.test.ts covering subscribe success and failure.**
- [ ] **Step 2: Implement src/services/billing.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/billing.test.ts` and commit.**
---
## Wave 3 — Integration (sequential)
Task 5 depends on every Wave 2 service being merged; it can only start
once Wave 2 is fully integrated onto the feature branch.
### Task 5: API routes
**Files:**
- Create: `src/api/routes.ts`
- Modify: `src/index.ts`
**Acceptance Criteria:**
- `src/api/routes.ts` imports `AuthService`, `UsersService`, and `BillingService`.
- `src/api/routes.ts` exports a `registerRoutes(app)` function that wires the three services.
- `src/index.ts` imports `registerRoutes` and calls it with the app.
- `npm run build` succeeds.
- `npm test` passes end to end.
- [ ] **Step 1: Create src/api/routes.ts that composes the three services.**
- [ ] **Step 2: Update src/index.ts to register the routes on startup.**
- [ ] **Step 3: Run `npm run build && npm test` and commit.**
"""
WAVE_TEST_WAVES_MINIMAL = """\
---
run_id: testw3
source_plan: docs/superpowers/plans/test-plan.md
spec_path: docs/superpowers/specs/test-spec.md
feature_branch: feature/test-implementation
status: approved
sequential_time: 3h
parallel_time: 2h
savings: 33%
waves:
- {wave: 1, strategy: sequential, tasks: [1], depends_on: []}
- {wave: 2, strategy: parallel, tasks: [2, 3], depends_on: [1]}
---
# Minimal Wave Execution — Waves File
## Waves Overview
| Wave | Strategy | Tasks | Depends On | Notes |
|------|------------|-------|------------|-----------------------------------|
| 1 | sequential | 1 | — | Foundation types, must land first |
| 2 | parallel | 2, 3 | 1 | Independent logger + clock |
**Sequential time estimate:** 3h
**Parallel time estimate:** 2h
**Savings:** 33%
---
## Wave 1 — Foundation (sequential)
### Task 1: Foundation types
**Files:**
- Create: `src/types/core.ts`
- Modify: `src/types/index.ts`
**Acceptance Criteria:**
- `src/types/core.ts` exports a `User` interface with `id` and `email`.
- `src/types/core.ts` exports a `Session` interface with `userId` and `token`.
- `src/types/index.ts` re-exports everything from `src/types/core.ts`.
- `npm run build` succeeds with no type errors.
- [ ] **Step 1: Create src/types/core.ts with User and Session interfaces.**
- [ ] **Step 2: Update src/types/index.ts to re-export from ./core.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
## Wave 2 — Independent services (parallel)
Tasks 2 and 3 have no file overlap and no cross-task imports; they can
be executed in parallel in isolated worktrees and merged at the wave
boundary.
### File ownership
```
Task 2 (logger service):
- src/services/logger.ts [create]
- tests/logger.test.ts [create]
Task 3 (clock service):
- src/services/clock.ts [create]
- tests/clock.test.ts [create]
```
No two tasks in Wave 2 touch the same path.
### Task 2: Logger service
**Files:**
- Create: `src/services/logger.ts`
- Create: `tests/logger.test.ts`
**Acceptance Criteria:**
- `src/services/logger.ts` exports a `Logger` class with an `info(message: string)` method.
- `Logger.info` appends a timestamped entry to an internal buffer.
- `tests/logger.test.ts` covers a happy-path info case.
- `tests/logger.test.ts` covers a repeated-call buffering case.
- `npm test -- tests/logger.test.ts` passes.
- [ ] **Step 1: Write tests/logger.test.ts covering info and buffering.**
- [ ] **Step 2: Implement src/services/logger.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/logger.test.ts` and commit.**
### Task 3: Clock service
**Files:**
- Create: `src/services/clock.ts`
- Create: `tests/clock.test.ts`
**Acceptance Criteria:**
- `src/services/clock.ts` exports a `Clock` class with a `now(): number` method.
- `Clock.now` returns the current Unix timestamp in milliseconds.
- `tests/clock.test.ts` covers a happy-path now case.
- `tests/clock.test.ts` covers the return value being a finite number.
- `npm test -- tests/clock.test.ts` passes.
- [ ] **Step 1: Write tests/clock.test.ts covering now success and type.**
- [ ] **Step 2: Implement src/services/clock.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/clock.test.ts` and commit.**
"""
WAVE_TEST_PLAN_BROKEN_TASK = """\
# Wave Execution Failure Test Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Exercise wave execution's failure escalation path. Tasks 1 and 2
should succeed normally. Task 3 is **structurally impossible** — its
pre-existing test file contains mutually contradictory assertions that
no implementation can satisfy, and the task scope explicitly forbids
modifying the test file.
**Architecture:** Foundation types feed two parallel services. The second
parallel service (Task 3) is wired up so that the orchestrator must
detect a real failure, retry once, and escalate to the user.
**Tech Stack:** TypeScript, Jest.
---
### Task 1: Foundation types
**Files:**
- Create: `src/types/core.ts`
- Modify: `src/types/index.ts`
**Acceptance Criteria:**
- `src/types/core.ts` exports a `User` interface with `id` and `email`.
- `src/types/core.ts` exports a `Session` interface with `userId` and `token`.
- `src/types/index.ts` re-exports everything from `src/types/core.ts`.
- `npm run build` succeeds with no type errors.
- [ ] **Step 1: Create src/types/core.ts with User and Session interfaces.**
- [ ] **Step 2: Update src/types/index.ts to re-export from ./core.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
### Task 2: Logger service
**Files:**
- Create: `src/services/logger.ts`
- Create: `tests/logger.test.ts`
**Acceptance Criteria:**
- `src/services/logger.ts` exports a `Logger` class with an `info(message: string)` method.
- `Logger.info` appends a timestamped entry to an internal buffer.
- `tests/logger.test.ts` covers a happy-path info case.
- `tests/logger.test.ts` covers a repeated-call buffering case.
- `npm test -- tests/logger.test.ts` passes.
- [ ] **Step 1: Write tests/logger.test.ts covering info and buffering.**
- [ ] **Step 2: Implement src/services/logger.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/logger.test.ts` and commit.**
---
### Task 3: Counter service (impossible — DO NOT modify test file)
**Files:**
- Create: `src/services/counter.ts`
- Pre-existing (DO NOT modify): `tests/counter.test.ts`
**Acceptance Criteria:**
- `src/services/counter.ts` exports a `Counter` class with a `compute(n: number): number` method.
- `tests/counter.test.ts` already exists. **You MUST NOT modify or delete it.**
- `npm test -- tests/counter.test.ts` passes against the pre-existing test file.
- `npm run build` succeeds.
- [ ] **Step 1: Read the pre-existing tests/counter.test.ts and understand its assertions.**
- [ ] **Step 2: Implement src/services/counter.ts to satisfy every assertion in the existing test file (without changing the test file).**
- [ ] **Step 3: Run `npm test -- tests/counter.test.ts` and `npm run build`, then commit.**
"""
# Pre-existing test file for the broken Task 3. This file is committed
# to the repo BEFORE the implementer runs. It contains mutually
# contradictory assertions: compute(1) is asserted to equal both 1 AND
# 2 in two separate `it` blocks. No implementation of `compute` can
# satisfy both assertions simultaneously, so `npm test` will always
# report a failing test for one of the two cases, no matter what the
# implementer writes. The implementer cannot modify the test file
# because the task acceptance criteria explicitly forbid it. This
# produces a structural failure that the orchestrator must detect.
COUNTER_FAILING_TEST = """\
import { Counter } from '../src/services/counter';
describe('Counter', () => {
// The two assertions below are mutually contradictory by design.
// No implementation of compute(n) can make both tests pass at once,
// and the task scope forbids modifying this file. The orchestrator
// should detect the failure, retry once, then escalate to the user.
it('compute(1) returns 1', () => {
const counter = new Counter();
expect(counter.compute(1)).toBe(1);
});
it('compute(1) returns 2', () => {
const counter = new Counter();
expect(counter.compute(1)).toBe(2);
});
});
"""
WAVE_TEST_SPEC_BROKEN_TASK = """\
# Wave Execution Failure Test Specification
## Overview
This specification describes a synthetic TypeScript project used to
exercise the wave execution skill's failure-handling and escalation
path. It is intentionally constructed so that one task in a parallel
wave cannot succeed.
## Scope
The spec covers:
- A shared types module that declares `User` and `Session` interfaces.
- A logger service with a buffered `info` method (Task 2 — should pass).
- A counter service whose pre-existing test file contains mutually
contradictory assertions (Task 3 — must fail).
## Non-goals
- A working counter service. Task 3 is a deliberate failure injection,
not a real feature. The fixture exists purely to verify that the
orchestrator detects the failure, retries once per the failure
handling matrix, and escalates to the user instead of silently
proceeding.
"""
WAVE_TEST_WAVES_BROKEN_TASK = """\
---
run_id: testfwf
source_plan: docs/superpowers/plans/test-plan.md
spec_path: docs/superpowers/specs/test-spec.md
feature_branch: feature/test-implementation
status: approved
sequential_time: 3h
parallel_time: 2h
savings: 33%
waves:
- {wave: 1, strategy: sequential, tasks: [1], depends_on: []}
- {wave: 2, strategy: parallel, tasks: [2, 3], depends_on: [1]}
---
# Wave Execution Failure Test — Waves File
## Waves Overview
| Wave | Strategy | Tasks | Depends On | Notes |
|------|------------|-------|------------|------------------------------------------------|
| 1 | sequential | 1 | — | Foundation types, must land first |
| 2 | parallel | 2, 3 | 1 | Logger (passes) + Counter (structurally fails) |
**Sequential time estimate:** 3h
**Parallel time estimate:** 2h
**Savings:** 33%
---
## Wave 1 — Foundation (sequential)
### Task 1: Foundation types
**Files:**
- Create: `src/types/core.ts`
- Modify: `src/types/index.ts`
**Acceptance Criteria:**
- `src/types/core.ts` exports a `User` interface with `id` and `email`.
- `src/types/core.ts` exports a `Session` interface with `userId` and `token`.
- `src/types/index.ts` re-exports everything from `src/types/core.ts`.
- `npm run build` succeeds with no type errors.
- [ ] **Step 1: Create src/types/core.ts with User and Session interfaces.**
- [ ] **Step 2: Update src/types/index.ts to re-export from ./core.**
- [ ] **Step 3: Run `npm run build` and commit.**
---
## Wave 2 — Independent services (parallel)
Tasks 2 and 3 have no file overlap and no cross-task imports; they can
be executed in parallel in isolated worktrees and merged at the wave
boundary.
### File ownership
```
Task 2 (logger service):
- src/services/logger.ts [create]
- tests/logger.test.ts [create]
Task 3 (counter service):
- src/services/counter.ts [create]
- tests/counter.test.ts [pre-existing — DO NOT modify]
```
No two tasks in Wave 2 touch the same path.
### Task 2: Logger service
**Files:**
- Create: `src/services/logger.ts`
- Create: `tests/logger.test.ts`
**Acceptance Criteria:**
- `src/services/logger.ts` exports a `Logger` class with an `info(message: string)` method.
- `Logger.info` appends a timestamped entry to an internal buffer.
- `tests/logger.test.ts` covers a happy-path info case.
- `tests/logger.test.ts` covers a repeated-call buffering case.
- `npm test -- tests/logger.test.ts` passes.
- [ ] **Step 1: Write tests/logger.test.ts covering info and buffering.**
- [ ] **Step 2: Implement src/services/logger.ts to make the tests pass.**
- [ ] **Step 3: Run `npm test -- tests/logger.test.ts` and commit.**
### Task 3: Counter service (impossible — DO NOT modify test file)
**Files:**
- Create: `src/services/counter.ts`
- Pre-existing (DO NOT modify): `tests/counter.test.ts`
**Acceptance Criteria:**
- `src/services/counter.ts` exports a `Counter` class with a `compute(n: number): number` method.
- `tests/counter.test.ts` already exists. **You MUST NOT modify or delete it.**
- `npm test -- tests/counter.test.ts` passes against the pre-existing test file.
- `npm run build` succeeds.
- [ ] **Step 1: Read the pre-existing tests/counter.test.ts and understand its assertions.**
- [ ] **Step 2: Implement src/services/counter.ts to satisfy every assertion in the existing test file (without changing the test file).**
- [ ] **Step 3: Run `npm test -- tests/counter.test.ts` and `npm run build`, then commit.**
"""
def _commit_waves_file(workdir: Path) -> None:
"""Stage and commit the waves file + spec on the feature branch.
Assumes the caller already created the underlying plan repo and is
sitting on feature/test-implementation (the create_wave_test_repo*
helpers leave us there).
"""
_git(["git", "add", "-A"], cwd=workdir)
_git(["git", "commit", "-m", "add pre-decomposed waves file and spec"], cwd=workdir)
def create_waves_file(workdir: Path) -> None:
"""Create the full 5-task repo with a pre-decomposed .waves.md file.
This is the starting point for `executing-waves` scenarios that
want the full 3-wave experience. The waves file is marked
`status: approved` so the executing-waves pre-flight check passes.
"""
workdir = Path(workdir)
create_wave_test_repo(workdir)
_write_file(
workdir,
"docs/superpowers/specs/test-spec.md",
WAVE_TEST_SPEC,
)
_write_file(
workdir,
"docs/superpowers/plans/test-plan.waves.md",
WAVE_TEST_WAVES_FULL,
)
_commit_waves_file(workdir)
def create_waves_file_minimal(workdir: Path) -> None:
"""Create the 3-task minimal repo with a pre-decomposed .waves.md file.
This is the starting point for smaller `executing-waves` scenarios
that exercise the same execution pipeline over 1 sequential task +
2 parallel tasks. The waves file is marked `status: approved` so
the executing-waves pre-flight check passes.
"""
workdir = Path(workdir)
create_wave_test_repo_minimal(workdir)
_write_file(
workdir,
"docs/superpowers/specs/test-spec.md",
WAVE_TEST_SPEC_MINIMAL,
)
_write_file(
workdir,
"docs/superpowers/plans/test-plan.waves.md",
WAVE_TEST_WAVES_MINIMAL,
)
_commit_waves_file(workdir)
def create_waves_file_with_broken_task(workdir: Path) -> None:
"""Create a 3-task waves repo where Task 3 is structurally impossible.
This is the starting point for `executing-waves` failure scenarios.
Layout:
- Wave 1 (sequential): Task 1 — foundation types (passes normally)
- Wave 2 (parallel): Task 2 — logger service (passes normally)
Task 3 — counter service (always fails)
Task 3's failure is structural, not a prompt trick: a pre-existing
`tests/counter.test.ts` file is committed before the implementer
runs and contains two contradictory assertions (`compute(1) === 1`
AND `compute(1) === 2`). The acceptance criteria explicitly forbid
modifying the test file. No implementation can make both tests
pass, so `npm test` always reports a failure for one of the two
cases.
Expected orchestrator behavior (per failure-handling.md):
1. Detect Task 3 failure after the parallel wave runs.
2. Merge Task 2 (the successful task) onto the feature branch.
3. Retry Task 3 once from the updated tip.
4. Retry also fails.
5. Escalate to the user with the standard escalation message.
"""
workdir = Path(workdir)
create_wave_test_repo_minimal(workdir)
# Overwrite the plan with the broken-task variant.
_write_file(
workdir,
"docs/superpowers/plans/test-plan.md",
WAVE_TEST_PLAN_BROKEN_TASK,
)
# Pre-create the failing test fixture for Task 3. The implementer
# must NOT modify it (per the task acceptance criteria), so the
# contradictory assertions guarantee a structural failure.
_write_file(
workdir,
"tests/counter.test.ts",
COUNTER_FAILING_TEST,
)
_write_file(
workdir,
"docs/superpowers/specs/test-spec.md",
WAVE_TEST_SPEC_BROKEN_TASK,
)
_write_file(
workdir,
"docs/superpowers/plans/test-plan.waves.md",
WAVE_TEST_WAVES_BROKEN_TASK,
)
_commit_waves_file(workdir)
def create_conflict_surface_repo(workdir: Path) -> None:
"""Create a plan where three tasks implicitly modify the same barrel file.
The `src/services/index.ts` barrel file is pre-created so the
decomposer sees it during directory scanning. Each task in the plan
lists only its new module file but the steps mention adding an
export to the barrel — the conflict-surface heuristic should notice
this and either add the barrel file to each task's list or serialize
the tasks.
Expected decomposition (under a correct heuristic): either
- all tasks in one wave with `src/services/index.ts` added to each
task's file list, or
- sequential waves (serialized) to avoid the shared barrel.
"""
workdir = Path(workdir)
_init_base_repo(workdir)
# The barrel file MUST exist before the plan runs.
_write_file(workdir, "src/services/index.ts", "export {};\n")
_write_file(workdir, "docs/superpowers/plans/test-plan.md", CONFLICT_SURFACE_PLAN)
_commit_all_on_feature_branch(workdir)