mirror of
https://github.com/obra/superpowers.git
synced 2026-05-08 18:19:04 +08:00
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.
1336 lines
45 KiB
Python
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)
|