Tasks 003 — CI wiring
Execution skill: superpowers:subagent-driven-development — one implementer per task, then a two-stage review (spec compliance, then code quality). superpowers:test-driven-development applies inside every task: no production code before a failing test that demands it. Reach for superpowers:systematic-debugging on any surprise rather than guessing.
Derived from plan.md (approved). Each task is small, independently verifiable, and reviewed as its own diff. Split where a reviewer could reject one task while approving its neighbour — not where the work merely changes subject. A task is done only when it satisfies the definition of done in CLAUDE.md.
Global Constraints in plan.md apply to every task and are not repeated per task.
T1 — The CI workflow, guarded by a structural test
Satisfies: R1, R2, R3, R4, R5, R6, R7, R8, R9, R11, P1 #1, P1 #5, P2 #1, P2 #2, P2 #3, SC4, SC5.
The test is written first and drives the workflow: it fails because .github/workflows/ci.yml does not exist, then passes once the workflow is created exactly as plan.md § Approach specifies.
[ ] Add the parser dependency:
pnpm add -D -w yaml(root workspace). Confirmpnpm installstays non-interactive and the lockfile updates;yamlis pure JS with no build script, sostrictDepBuildsis not engaged.[ ] RED: create
tests/ci/workflow.test.tsthat reads.github/workflows/ci.yml, parses it withimport { parse } from 'yaml', and asserts — oneitper criterion, named for it:```ts import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { parse } from 'yaml'; import { describe, expect, it } from 'vitest'; const repoRoot = fileURLToPath(new URL('../..', import.meta.url)); const workflow = parse( readFileSync(join(repoRoot, '.github/workflows/ci.yml'), 'utf8'), ); const pkg = JSON.parse( readFileSync(join(repoRoot, 'package.json'), 'utf8'), ); // The single job, whatever it is named. const job = Object.values(workflow.jobs)[0]; const steps = job.steps as Array<Record<string, unknown>>; const runs = steps .map((s) => (typeof s.run === 'string' ? s.run : null)) .filter((r): r is string => r !== null); const stepUsing = (prefix: string) => steps.find( (s) => typeof s.uses === 'string' && s.uses.startsWith(prefix), ); const withOf = (step: Record<string, unknown> | undefined) => (step?.with ?? {}) as Record<string, unknown>; const majorOf = (spec: string) => { const m = /(\d+)/.exec(spec); return m ? Number(m[1]) : 0; }; ``` Cases (`SC4`/`P1 #1`): `workflow.on.pull_request` is a defined key; `workflow.on.push.branches` includes `'main'`; the four `pnpm` checks appear as `run` commands **in order** — `runs.filter(r => r.startsWith('pnpm ') && !r.includes('install'))` deep-equals `['pnpm lint', 'pnpm typecheck', 'pnpm test', 'pnpm docs:build']`. (`P1 #5`) `workflow.concurrency['cancel-in-progress']` is `true`. (`SC4`) `withOf(stepUsing('actions/setup-node')).cache` is `'pnpm'`. (`P2 #2`/`SC5`) `majorOf(String(withOf(stepUsing('actions/setup-node'))['node-version']))` is `>=` `majorOf(pkg.engines.node)`; and `withOf(stepUsing('pnpm/action-setup')).version` is either `undefined` or equal to the pnpm version parsed from `pkg.packageManager`. (`P2 #3`) a helper that the four check commands are all present so a removed step fails an assertion naming it. Run `pnpm exec vitest run tests/ci/workflow.test.ts` — watch it FAIL (file missing).[ ] GREEN: create
.github/workflows/ci.ymlexactly as inplan.md§ Approach (nameCI;on:pull_request+push.branches: [main];concurrencywithcancel-in-progress: true; onecheckjob onubuntu-latest; stepsactions/checkout@v4→pnpm/action-setup@v6(noversion) →actions/setup-node@v4withnode-version: '22'andcache: 'pnpm'→pnpm install --frozen-lockfile→pnpm lint→pnpm typecheck→pnpm test→pnpm docs:build). Pin each action to the current major at implementation time. Rerun — PASS.[ ] REFACTOR: none expected.
[ ] Confirm the test has teeth — reorder two checks in
ci.yml(e.g.testbeforetypecheck), watch the ordered-checks case fail; delete theconcurrencyblock, watch that case fail; restore both.[ ]
pnpm typecheckexits 0 (the new test typechecks, noany, no!);pnpm lintexits 0 (pnpm formatif needed);pnpm testgreen.[ ] Commit (
ci: add GitHub Actions workflow and its structural guard), including the updatedpnpm-lock.yamlso--frozen-lockfilewill pass on the runner.
Verified by: tests/ci/workflow.test.ts (all cases); pnpm typecheck/pnpm lint/pnpm test green.
T2 — Document the enforcement rule and the CI facts
Satisfies: R10, R12; creates the dated-verification home the observational criteria (SC1–SC3, SC6, P1 #2–#4, P3) are recorded in at step 5.
Docs, not code, so the RED step guards existence and linkage rather than behaviour.
- [ ] RED: add to
tests/ci/workflow.test.tsadescribe('the CI enforcement is documented')with a case assertingdocs/architecture/ci.mdexists, contains a heading mentioning branch protection, and thatCLAUDE.mdreferencesdocs/architecture/ci.md. Run — watch it FAIL (doc missing). - [ ] GREEN: create
docs/architecture/ci.mdcapturing: - The workflow — the shape fromplan.md(two triggers, one fail-fast job, four checks, pnpm cache, concurrency), in prose a future reader can act on. - Branch protection (R10) — the exact rule onmain: require thecheckjob's status check to pass before merging (Settings → Branches → add rule formain, orgh api -X PUT repos/:owner/:repo/branches/main/protection …with the required check). State plainly why it is not a committed artifact (a GitHub-side setting) and that it is set by the human once and verified by hand. - Standing guards —strictDepBuilds: truemeans any future build-script dependency must be added toallowBuildsor CI's install fails (research.mdV1); mise will own a single Node version for every environment later, at which point CI reads that pin instead of22(research.mdV3). - A "Verification log" section with placeholder rows for the dated observations filled at step 5 (SC1–SC3,SC6). Add a line toCLAUDE.md's Reference section linkingdocs/architecture/ci.md. Rerun — PASS. - [ ] Confirm teeth — remove the
CLAUDE.mdlink, watch the case fail, restore. - [ ]
pnpm testgreen (confirm theCLAUDE.mdedit did not breakconstitution.test.ts). - [ ] Commit (
docs: capture CI shape and the manual branch-protection rule).
Verified by: tests/ci/workflow.test.ts › the CI enforcement is documented; pnpm test green.
T3 — Fill traceability and lock SC8
Satisfies: SC8, SC7; final in-repo verification.
- [ ] RED: add to
tests/ci/workflow.test.tsa caseSC8: the spec 003 traceability table is complete and its named tests existthat parsesspecs/003-ci-wiring/spec.md's traceability table (rows| criterion | … |), asserts no cell is empty or a(…to be named…)placeholder, and that every backtick-quoted test name in a cell is found as a string in some file undertests/. Manual-record cells (no backticks) are allowed as notes. Run — watch it FAIL (cells still say "to be named"). - [ ] GREEN: fill the Test/verification column of
spec.md's traceability table fromplan.md§ Test strategy — automated rows cite`tests/ci/workflow.test.ts`(with the specific case name), observational rows cite the dated record indocs/architecture/ci.md. Rerun — PASS. - [ ] Confirm teeth — blank one table cell, watch the case fail, restore.
- [ ] Full in-repo verification:
pnpm typecheckexits 0;pnpm lintexits 0;pnpm testfully green includingtests/workflow/*(specs 001/002 intact —SC7);find docs/superpowers -type f | wc -lprints0(R12/SC7). - [ ] Commit (
specs: fill spec 003 traceability and lock SC8).
Verified by: tests/ci/workflow.test.ts › SC8: …; clean pnpm typecheck/pnpm lint/pnpm test; zero files under docs/superpowers/.
Notes
Staging area for decisions and surprises found during implementation — including anything that turned out differently from what plan.md assumed. Move each one into spec.md, research.md, or docs/ before closing the feature; this section is not a home.
- Environment quirk (this sandbox):
node/pnpmare not on the defaultPATH; they live at/opt/homebrew/bin. Prefixexport PATH="/opt/homebrew/bin:$PATH"when a command reportscommand not found. - After each task, run
pnpm formatbefore committing sopnpm lintstays green throughout. - Verification at step 5 (not a coding task — the observational criteria). These are proven only on the real run and in GitHub's settings, and recorded with their dates in
docs/architecture/ci.md:SC1/P1 #2— open the 003 PR with one check deliberately broken; confirm a red run whose failed step names the check, and that later steps did not run. Then fix and confirm green.SC2/P1 #3— with every check passing, confirm a green run.SC3/P1 #4— after merge, confirm thepush:mainrun reports a status on the commit.SC6/P3— the human sets the branch-protection rule onmain(perci.md), then confirms a red/pending PR cannot be merged and a green one can (R10).- Record each with its date; the traceability rows for these cite that log.
- Graduation (step 6):
research.md§ Graduation lists the CI facts to consolidate intodocs/architecture/ci.md— much of it lands in T2; confirm nothing is left only inresearch.mdbefore closing the branch (superpowers:finishing-a-development-branch). - Isolation (step 3.5): implement in a worktree (
superpowers:using-git-worktrees) so a long run cannot disturb the working tree, per the constitution.