| autoload | true |
|---|---|
| maturity | beta |
All implementation follows strict TDD. The cycle is RED → GREEN → REFACTOR → VERIFY.
- Read the spec's acceptance criteria and user test cases
- Write test(s) that assert the expected behavior
- Run the tests — they MUST fail
- If tests pass before implementation, the tests are wrong (testing existing behavior, not new)
- Write the MINIMUM code to make failing tests pass
- No extra features, no "while I'm here" additions
- No optimization — just make it work
- Run tests — they MUST pass
- Clean up code without changing behavior
- Extract functions, rename variables, remove duplication
- Run tests after EVERY refactor — they must still pass
- Apply project naming conventions and patterns
- Run the FULL test suite, not just new tests
- Run linter (ruff/eslint depending on language)
- Run type checker (mypy/tsc depending on language)
- Verify spec compliance — do the changes satisfy the acceptance criteria?
- If any gate fails, fix before proceeding
- NEVER write implementation before tests exist and FAIL
- NEVER skip the RED phase — "I'll add tests later" is not allowed
- NEVER commit with failing tests on the branch
- When a sub-agent implements code, the orchestrator MUST run tests independently
- Each TDD cycle should be a single, atomic commit
Tests added during RED MUST exist (passing) at the end of GREEN. Test deletion during
the cycle is forbidden without --allow-test-rewrite and explicit human approval.
Test renames are permitted (same normalized body, different function name); test
replacements (same name, rewritten body beyond the similarity threshold) require
approval.
This invariant is enforced at three points:
- End of RED —
/add:test-writerwrites a snapshot at.add/cycles/cycle-{N}/tdd-{slug}-red.jsoncapturing every test function's path, name, and normalized body hash. The snapshot is committed (test(red): snapshot N tests for {slug}). Failure mode: if RED produces zero new tests, the cycle halts — RED with no new tests is itself a TDD violation. - End of GREEN —
/add:implementerre-runs discovery against the same files and writes.add/cycles/cycle-{N}/tdd-{slug}-green.jsonwith identical schema. - Gate 3.5 in
/add:verify— runsscripts/check-test-count.py gate --red ... --green .... Iftests_removed > 0without an override, ortests_replaced > 0without--allow-test-rewrite, the gate fails with a structured error listing each removed or replaced test. The cycle does not advance to Gate 4.
A test deletion or replacement is authorized by either:
-
A commit trailer in the range
base..HEADof the form[ADD-TEST-DELETE: <AC-id or reason>]. Used for out-of-cycle rewrites or small maintenance changes. -
A file at
.add/cycles/cycle-{N}/overrides.jsonof shape:{ "kind": "test-rewrite", "approved_by": "human", "timestamp": "2026-04-22T14:32:00Z", "affected_tests": ["tests/path.py::function_name"] }
Either form is recorded in telemetry and surfaced in /add:retro for review.
The genie doesn't want to do TDD (Kent Beck, 2026) — the path of least resistance for a coding agent is to remove the failing test rather than satisfy it. The TDAD paper (arXiv 2603.17973) observed naive TDD-prompting increased regression rate to 9.94% because agents silently deleted tests they couldn't satisfy. ADD's separation of concerns (test-writer vs implementer) only matters if the tests written in RED survive GREEN. This invariant enforces that.
Some TDD-cycle runs are fully scripted (--allow-test-rewrite with an overrides.json
approval); others are ad-hoc developer work where a commit trailer is the lighter-weight
signal. Both land in the same telemetry channel so retros can review legitimacy.
Tests must reference the spec:
# Backend (pytest)
def test_ac001_user_can_login_with_valid_credentials():
def test_ac002_invalid_password_shows_error():
def test_tc001_login_success_flow():// Frontend (vitest/playwright)
describe('AC-001: User login', () => {
it('should authenticate with valid credentials', ...);
});
describe('TC-001: Login success flow', () => {
it('step 1: navigate to /login', ...);
it('step 2: enter credentials and submit', ...);
it('step 3: see dashboard with username', ...);
});Coverage targets are set in .add/config.json during project init. Defaults:
- Unit tests: 80% line coverage
- Integration tests: Critical paths covered
- E2E tests: All user test cases from specs have corresponding tests