
SkillCodeFree
- Claude Code
- Codex
- Copilot
- +1
Coding✦ Risemi pick
API Contract Reviewer
Catches breaking REST changes in a PR before merge: diff vs spec, severity table, migration notes.
0 installs
Free5 results

Catches breaking REST changes in a PR before merge: diff vs spec, severity table, migration notes.
Project: fresh `npm init -y` + `npm i -D vitest typescript` (Vitest 5.1.0, TypeScript strict). Every runner output below is pasted from the actual run.
Behaviour list (simplest first): 1. returns empty for blank input · 2. returns the address trimmed and lowercased when valid · 3. returns malformed when there is no @ · 4. returns malformed when there is more than one @ · 5. returns malformed when the domain has no dot · 6. returns too_long when the address is over 254 characters
Cycle 1: returns empty for blank input
```
[TDD] cycle 1/6 step RED behaviour: returns empty for blank input
test: src/validateEmail.test.ts > validateEmail > returns empty for blank input
ran: npx vitest run src/validateEmail.test.ts -t "returns empty for blank input"
result: FAIL Error: Cannot find module './validateEmail' imported from <project>/src/validateEmail.test.ts
reason: right (the unit under test does not exist yet: missing function, not a typo in the test)
changed: src/validateEmail.test.ts (+8, new file)
next: minimum code in src/validateEmail.ts
```
```
[TDD] cycle 1/6 step GREEN behaviour: returns empty for blank input
ran: npx vitest run src/validateEmail.test.ts -t "returns empty for blank input" --reporter=verbose; npx vitest run src/validateEmail.test.ts
result: PASS Tests 1 passed (1)
changed: src/validateEmail.ts (+9, new file: result types + hard-coded `{ ok: false, error: 'empty' }`)
next: refactor
```
Refactor: no refactor (hard-coded return is forced into the general version by cycle 2).
[Cycles 2–6 follow the same shape: each RED shows a real assertion mismatch such as `Tests 1 failed | 1 skipped (2)`, each GREEN the passing count, with refactors in cycles 5 and 6.]
Done
```
[TDD] DONE cycles 6 tests added 6 suite PASS
ran: npm test (vitest run) and npx tsc -p .
result: Test Files 1 passed (1) | Tests 6 passed (6); tsc exit 0
pre-existing failures: none (no tests existed)
found, not fixed: the 254 boundary itself (exactly 254 chars accepted) has no test; it passes today, so a test would be green on first run. 'a@.com' and 'a@b.' are accepted because the spec only asks for a dot in the domain.
```
No-fake-green check before every GREEN: no `.skip`/`.todo`, no assertion weakened, no test-only branch, nothing mocked.Use it to make your coding agent build every change test-first: one failing test, the minimum code, a refactor, and a status block after each step.

Your agent interviews you about a plan until nothing is assumed, then writes the spec and tracer-bullet tickets.
Built. Here is what exists now: - /today — the screen learners live on: today's lesson card, a 7-day streak strip, and a trial banner counting down from 5 days with one coral "Keep my streak" button that routes to /upgrade. - /login — magic-link sign-in by email, no password. - /settings — sign-out only. Data model (created before UI, as asked): users(id, email, created_at, trial_ends_at, plan) lessons(id, day_number, title, duration_min) completions(id, user_id, lesson_id, completed_at) Streak is computed from completions, not stored. Assumptions: one lesson per day; streak resets after a missed day; trial length fixed at 5 days. Left out on purpose: analytics, onboarding tour, dark mode, admin. One question before the next screen: should a learner on day 6 with no plan still see today's lesson, or only the upgrade page?
Who, the one job, the data, the feel — and an app that looks like yours on the first generation.
Restating: submitting the sign-up form throws `column "email" does not exist`, and you expected to be redirected to /dashboard. It worked before the profile table was added.
Ranked causes
1. The sign-up handler now inserts into `profiles`, which has no `email` column — the form was pointed at the new table, not `users`. Check: open the insert in `signup.ts`; if the table is `profiles`, confirmed.
2. A migration added `profiles` without `email` while the ORM model still declares it. Check: compare `schema.sql` to the model.
3. Two databases (local vs. hosted) with different schemas. Check: run the migration list against the hosted DB.
Checking #1: `signup.ts` line 22 inserts `{ email, name }` into `profiles`. Confirmed.
Fix (smallest): insert `email` into `users` as before, and only `{ user_id, name }` into `profiles`.
- await db.from("profiles").insert({ email, name })
+ const { data: u } = await db.from("users").insert({ email }).select("id").single()
+ await db.from("profiles").insert({ user_id: u.id, name })
Verify: sign up with a fresh address; you should land on /dashboard and see one row in each table.
Regression risk: any other code that assumed profiles.email — I found none.A bug report the AI can act on: what you did, what happened, what you expected — with a confirm-first rule.