Propose the test(s) FIRST and get a human to sign off on the behaviour before writing the code — the test analog of /schema-review and /ui-proposal. For hand-written LOGIC in packages/* (apps opt-in, pocs exempt). Renders the proposed failing test as a reviewable contract, holds for lgtm/approve, then you write the code to make it green. Use when an agent is about to implement packages/* logic, or when asked to "write the test first", "TDD this", "agree on the test", run /test-review.
Installs into .claude/skills of the current project.
Are you the author of Test Review?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/friendlyinternet-test-review)
---
name: test-review
layer: stage
description: Propose the test(s) FIRST and get a human to sign off on the behaviour before writing the code — the test analog of /schema-review and /ui-proposal. For hand-written LOGIC in packages/* (apps opt-in, pocs exempt). Renders the proposed failing test as a reviewable contract, holds for lgtm/approve, then you write the code to make it green. Use when an agent is about to implement packages/* logic, or when asked to "write the test first", "TDD this", "agree on the test", run /test-review.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
---
# Test Review — agree on the test before you write the code
Turns "I'm about to write some logic" into a **reviewable contract**: the proposed *failing*
test(s) — the cases being asserted, in plain language plus the actual test code — so a human can
sign off on the **behaviour** *before* any implementation exists. The agreed test is the
definition of done: "done" = that test passes.
This is the third sign-off gate (epic #774), alongside **`schema-review`** (#314, the data model)
and **`ui-proposal`** (#307, the look). Pick the gate by *what the change is*: a data model → schema
gate; how it looks → UI gate; **hand-written logic → here**. It reuses the same revision/approval
loop and signal as the other two (#310/#572).
> **Why test-first?** The test is cheap to change while it's just a proposal and expensive to change
> after the code is built around the wrong assumption. Agreeing on the cases first ("no
> double-booking; adjacent slots OK; buffer respected") means the code has one job — go green —
> instead of being a moving target.
## Step 0 — Self-scope (skip when out of scope). This gate is deliberately narrow.
Decide by **where the code currently lives (#779)**, then by **what kind of change** it is. Skip
loudly (one line saying why + which gate, if any, fits instead) rather than gating everything.
**By location — resolved from the stage model.** Run `node scripts/harness-stages.mjs <path>`
(or `gateMode(path, 'test-first')`) for the verdict (`on` / `opt-in` / `off`) rather than matching
the folder by hand; the canonical source is `harness.config.mjs` (epic #952). The table below is
the **default profile** rendered for humans:
| Current home | Stage | Action |
|---|---|---|
| `packages/*` | `package` | **Proceed** (default — what we maintain; every consuming app inherits its correctness) |
| `apps/*` | `app` | **Skip unless the app has explicitly opted in** (may be another user's app — not ours to impose) |
| `pocs/*` | `poc` | **Skip** — the incubator must stay fast and safe-to-fail; a POC graduating to `packages/*` is the checkpoint to backfill its tests |
**By change kind (only once location is in scope):**
- Collection schema / data model → **skip**, use `/schema-review` (+ the e2e fixture smoke covers generated CRUD).
- `.vue` component / layout / theme — how it *looks* → **skip**, use `/ui-proposal`.
- Deterministic **generated** code (CLI output, templated CRUD) → **skip**, the e2e smoke harness owns it.
- **Hand-written logic** — a composable rule, server util, permission check, calculation, layout-engine transform, a generator's own logic → **proceed.**
> Rule of thumb: `packages/*` *logic* almost always proceeds; everything else almost always skips.
> When unsure whether something is "logic", ask: *can a human just eyeball it for correctness?* If
> yes, it probably doesn't need a test-first contract.
## Step 1 — Pin down the behaviour
Before writing any test, state — in one or two plain sentences — what the logic must do, and
**enumerate the edge cases** that matter. This list IS the thing being agreed on; the test code is
just its executable form. Example (a slot-availability rule):
- a booking that overlaps an existing one is rejected
- two back-to-back bookings (end == next start) are both allowed
- a booking inside another's buffer window is rejected
- times are compared in the resource's timezone, not the server's
## Step 2 — Write the proposed failing test(s)
Author the test next to the code it will cover, following the package's existing test layout
(`*.test.ts` beside the source, or its `__tests__/` dir — match the neighbours; see
`packages/crouton-core` for examples). Vitest is the runner (`pnpm test`).
- One `describe` per unit; one `it` per edge case from Step 1, named in plain language.
- The test must **fail first** — it asserts against code that doesn't exist yet (or asserts the
new behaviour the current code lacks). Red before green is the point; surface that.
- Keep it a **general** contract (all valid inputs), never assertions reverse-engineered to pass a
specific implementation.
## Step 3 — Hand off for sign-off (review happens on the test itself)
**The proposed test is the review surface.** Present both the plain-language case list (Step 1) and
the test code, so the reviewer agrees on *behaviour*, not syntax.
- **Interactive session:** show the case list + test in chat and hold. The reviewer says
`lgtm`/`approve` to proceed, or asks for changes (add a case, fix an expectation) → revise and
re-show.
- **Agent pipeline / PR:** commit the failing test (via `/commit`, scope = the package, e.g.
`test(crouton-core): …`) so it lands in the PR's "Files changed". The reviewer comments inline on
any `it(...)` to change a case. Set the issue to `status:blocked` while holding. Approval is a
**comment** containing `lgtm`/`approve` — **not** a reaction or label (#572) — and it unblocks
implementation.
Iterate Step 1–3 until approved. Do **not** write implementation code before the test is agreed.
## Step 4 — Go green (only after approval)
Write the implementation to make the agreed test pass — and **only** that. Run `pnpm test` (and
`pnpm typecheck`) until green. The agreed test is now the regression guard; it stays in the diff and
ships with the code. Resume the normal workflow (typecheck → update issue → `/commit`).
## Conventions
- The case list (Step 1) is the contract; keep the test code honest to it — never quietly drop or
weaken a case to make implementation easier (that's a re-negotiation → go back to Step 3).
- One concern per test file, mirroring the source it covers.
- Red first, then green. If the proposed test passes *before* you write any code, the behaviour
already exists — say so and skip (nothing to build).
- This is about *order* (test before code), not *enforcement* — the CI `test` job already hard-gates
`pnpm test`.