test-review

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,

SKILL.md

GitHub

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.