Skip to content

Commit 3db2b50

Browse files
feat(rules): tdd-enforcement test-deletion invariant
Adds the Test-Deletion Invariant section to the auto-loaded TDD rule (AC-028): "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; test replacements require approval." Documents both accepted justification-marker formats: 1. Commit trailer: [ADD-TEST-DELETE: <AC-id or reason>] 2. Per-cycle file: .add/cycles/cycle-{N}/overrides.json with kind:test-rewrite Both land in telemetry for retro review. Cross-references the enforcement points in /add:test-writer, /add:implementer, and /add:verify Gate 3.5. Part of M3 pre-GA hardening. Spec: specs/test-deletion-guardrail.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 65124f5 commit 3db2b50

3 files changed

Lines changed: 171 additions & 0 deletions

File tree

core/rules/tdd-enforcement.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,63 @@ All implementation follows strict TDD. The cycle is RED → GREEN → REFACTOR
4646
- When a sub-agent implements code, the orchestrator MUST run tests independently
4747
- Each TDD cycle should be a single, atomic commit
4848

49+
## Test-Deletion Invariant (v0.9.0 / M3)
50+
51+
**Tests added during RED MUST exist (passing) at the end of GREEN.** Test deletion during
52+
the cycle is forbidden without `--allow-test-rewrite` **and** explicit human approval.
53+
Test renames are permitted (same normalized body, different function name); test
54+
replacements (same name, rewritten body beyond the similarity threshold) require
55+
approval.
56+
57+
This invariant is enforced at three points:
58+
59+
1. **End of RED**`/add:test-writer` writes a snapshot at
60+
`.add/cycles/cycle-{N}/tdd-{slug}-red.json` capturing every test function's path,
61+
name, and normalized body hash. The snapshot is committed (`test(red): snapshot N
62+
tests for {slug}`). Failure mode: if RED produces zero new tests, the cycle halts —
63+
RED with no new tests is itself a TDD violation.
64+
2. **End of GREEN**`/add:implementer` re-runs discovery against the same files and
65+
writes `.add/cycles/cycle-{N}/tdd-{slug}-green.json` with identical schema.
66+
3. **Gate 3.5 in `/add:verify`** — runs `scripts/check-test-count.py gate --red ... --green ...`.
67+
If `tests_removed > 0` without an override, or `tests_replaced > 0` without
68+
`--allow-test-rewrite`, the gate fails with a structured error listing each removed
69+
or replaced test. The cycle does not advance to Gate 4.
70+
71+
### Justification markers
72+
73+
A test deletion or replacement is authorized by **either**:
74+
75+
- A commit trailer in the range `base..HEAD` of the form
76+
`[ADD-TEST-DELETE: <AC-id or reason>]`. Used for out-of-cycle rewrites or small
77+
maintenance changes.
78+
- A file at `.add/cycles/cycle-{N}/overrides.json` of shape:
79+
80+
```json
81+
{
82+
"kind": "test-rewrite",
83+
"approved_by": "human",
84+
"timestamp": "2026-04-22T14:32:00Z",
85+
"affected_tests": ["tests/path.py::function_name"]
86+
}
87+
```
88+
89+
Either form is recorded in telemetry and surfaced in `/add:retro` for review.
90+
91+
### Rationale
92+
93+
The genie doesn't want to do TDD (Kent Beck, 2026) — the path of least resistance for a
94+
coding agent is to remove the failing test rather than satisfy it. The TDAD paper
95+
(arXiv 2603.17973) observed naive TDD-prompting *increased* regression rate to 9.94%
96+
because agents silently deleted tests they couldn't satisfy. ADD's separation of concerns
97+
(test-writer vs implementer) only matters if the tests written in RED survive GREEN.
98+
This invariant enforces that.
99+
100+
### Why both markers are accepted
101+
102+
Some TDD-cycle runs are fully scripted (`--allow-test-rewrite` with an `overrides.json`
103+
approval); others are ad-hoc developer work where a commit trailer is the lighter-weight
104+
signal. Both land in the same telemetry channel so retros can review legitimacy.
105+
49106
## Test Naming
50107

51108
Tests must reference the spec:

dist/codex/AGENTS.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2900,6 +2900,63 @@ All implementation follows strict TDD. The cycle is RED → GREEN → REFACTOR
29002900
- When a sub-agent implements code, the orchestrator MUST run tests independently
29012901
- Each TDD cycle should be a single, atomic commit
29022902

2903+
## Test-Deletion Invariant (v0.9.0 / M3)
2904+
2905+
**Tests added during RED MUST exist (passing) at the end of GREEN.** Test deletion during
2906+
the cycle is forbidden without `--allow-test-rewrite` **and** explicit human approval.
2907+
Test renames are permitted (same normalized body, different function name); test
2908+
replacements (same name, rewritten body beyond the similarity threshold) require
2909+
approval.
2910+
2911+
This invariant is enforced at three points:
2912+
2913+
1. **End of RED**`/add:test-writer` writes a snapshot at
2914+
`.add/cycles/cycle-{N}/tdd-{slug}-red.json` capturing every test function's path,
2915+
name, and normalized body hash. The snapshot is committed (`test(red): snapshot N
2916+
tests for {slug}`). Failure mode: if RED produces zero new tests, the cycle halts —
2917+
RED with no new tests is itself a TDD violation.
2918+
2. **End of GREEN**`/add:implementer` re-runs discovery against the same files and
2919+
writes `.add/cycles/cycle-{N}/tdd-{slug}-green.json` with identical schema.
2920+
3. **Gate 3.5 in `/add:verify`** — runs `scripts/check-test-count.py gate --red ... --green ...`.
2921+
If `tests_removed > 0` without an override, or `tests_replaced > 0` without
2922+
`--allow-test-rewrite`, the gate fails with a structured error listing each removed
2923+
or replaced test. The cycle does not advance to Gate 4.
2924+
2925+
### Justification markers
2926+
2927+
A test deletion or replacement is authorized by **either**:
2928+
2929+
- A commit trailer in the range `base..HEAD` of the form
2930+
`[ADD-TEST-DELETE: <AC-id or reason>]`. Used for out-of-cycle rewrites or small
2931+
maintenance changes.
2932+
- A file at `.add/cycles/cycle-{N}/overrides.json` of shape:
2933+
2934+
```json
2935+
{
2936+
"kind": "test-rewrite",
2937+
"approved_by": "human",
2938+
"timestamp": "2026-04-22T14:32:00Z",
2939+
"affected_tests": ["tests/path.py::function_name"]
2940+
}
2941+
```
2942+
2943+
Either form is recorded in telemetry and surfaced in `/add:retro` for review.
2944+
2945+
### Rationale
2946+
2947+
The genie doesn't want to do TDD (Kent Beck, 2026) — the path of least resistance for a
2948+
coding agent is to remove the failing test rather than satisfy it. The TDAD paper
2949+
(arXiv 2603.17973) observed naive TDD-prompting *increased* regression rate to 9.94%
2950+
because agents silently deleted tests they couldn't satisfy. ADD's separation of concerns
2951+
(test-writer vs implementer) only matters if the tests written in RED survive GREEN.
2952+
This invariant enforces that.
2953+
2954+
### Why both markers are accepted
2955+
2956+
Some TDD-cycle runs are fully scripted (`--allow-test-rewrite` with an `overrides.json`
2957+
approval); others are ad-hoc developer work where a commit trailer is the lighter-weight
2958+
signal. Both land in the same telemetry channel so retros can review legitimacy.
2959+
29032960
## Test Naming
29042961

29052962
Tests must reference the spec:

plugins/add/rules/tdd-enforcement.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,63 @@ All implementation follows strict TDD. The cycle is RED → GREEN → REFACTOR
4646
- When a sub-agent implements code, the orchestrator MUST run tests independently
4747
- Each TDD cycle should be a single, atomic commit
4848

49+
## Test-Deletion Invariant (v0.9.0 / M3)
50+
51+
**Tests added during RED MUST exist (passing) at the end of GREEN.** Test deletion during
52+
the cycle is forbidden without `--allow-test-rewrite` **and** explicit human approval.
53+
Test renames are permitted (same normalized body, different function name); test
54+
replacements (same name, rewritten body beyond the similarity threshold) require
55+
approval.
56+
57+
This invariant is enforced at three points:
58+
59+
1. **End of RED**`/add:test-writer` writes a snapshot at
60+
`.add/cycles/cycle-{N}/tdd-{slug}-red.json` capturing every test function's path,
61+
name, and normalized body hash. The snapshot is committed (`test(red): snapshot N
62+
tests for {slug}`). Failure mode: if RED produces zero new tests, the cycle halts —
63+
RED with no new tests is itself a TDD violation.
64+
2. **End of GREEN**`/add:implementer` re-runs discovery against the same files and
65+
writes `.add/cycles/cycle-{N}/tdd-{slug}-green.json` with identical schema.
66+
3. **Gate 3.5 in `/add:verify`** — runs `scripts/check-test-count.py gate --red ... --green ...`.
67+
If `tests_removed > 0` without an override, or `tests_replaced > 0` without
68+
`--allow-test-rewrite`, the gate fails with a structured error listing each removed
69+
or replaced test. The cycle does not advance to Gate 4.
70+
71+
### Justification markers
72+
73+
A test deletion or replacement is authorized by **either**:
74+
75+
- A commit trailer in the range `base..HEAD` of the form
76+
`[ADD-TEST-DELETE: <AC-id or reason>]`. Used for out-of-cycle rewrites or small
77+
maintenance changes.
78+
- A file at `.add/cycles/cycle-{N}/overrides.json` of shape:
79+
80+
```json
81+
{
82+
"kind": "test-rewrite",
83+
"approved_by": "human",
84+
"timestamp": "2026-04-22T14:32:00Z",
85+
"affected_tests": ["tests/path.py::function_name"]
86+
}
87+
```
88+
89+
Either form is recorded in telemetry and surfaced in `/add:retro` for review.
90+
91+
### Rationale
92+
93+
The genie doesn't want to do TDD (Kent Beck, 2026) — the path of least resistance for a
94+
coding agent is to remove the failing test rather than satisfy it. The TDAD paper
95+
(arXiv 2603.17973) observed naive TDD-prompting *increased* regression rate to 9.94%
96+
because agents silently deleted tests they couldn't satisfy. ADD's separation of concerns
97+
(test-writer vs implementer) only matters if the tests written in RED survive GREEN.
98+
This invariant enforces that.
99+
100+
### Why both markers are accepted
101+
102+
Some TDD-cycle runs are fully scripted (`--allow-test-rewrite` with an `overrides.json`
103+
approval); others are ad-hoc developer work where a commit trailer is the lighter-weight
104+
signal. Both land in the same telemetry channel so retros can review legitimacy.
105+
49106
## Test Naming
50107

51108
Tests must reference the spec:

0 commit comments

Comments
 (0)