Skip to content

Commit 9bc8321

Browse files
cadamsdotcomclaude
andcommitted
refactor: Rename TDD intent terminology to declaration across codebase
Rename internal state names (red_intent → writing_tests, green_intent → making_tests_pass) and update all user-facing messages, documentation, and tests to use "declaration" instead of "intent". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cd3f8bc commit 9bc8321

10 files changed

Lines changed: 108 additions & 106 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,12 @@ Remember: A test that fails for the wrong reason teaches you nothing about the c
198198
3. Can existing e2e tests be enhanced to cover this work instead of writing new ones?
199199
4. Can planned e2e tests be unit/integration/component tests instead?
200200

201-
**Red Intent Honesty:**
201+
**Honest Test Declarations:**
202202

203-
- **Red intent must describe expected failure.** If you're adding tests that will pass immediately, use `--skip-red` on the Green intent instead. Don't log a Red intent for tests you expect to pass.
203+
- **The writing-tests declaration must describe expected failure.** If you're adding tests that will pass immediately, use `--skip-red` on the making-tests-pass declaration instead. Don't start writing tests for tests you expect to pass.
204204
- **Guard tests (assert unchanged behavior):** When writing tests that expect existing/default behavior to continue (e.g., "blocked stays blocked", "no-op when absent"), these can't fail during a natural Red phase. Use `--skip-red --reason=adding-coverage` for these, even when they're part of a larger feature. Don't bundle them into a Red cycle where they'll pass immediately.
205205
- Do NOT use `--skip-red` to avoid TDD, even if you already have e2e coverage.
206-
- The TDD guard enforces that `tdd_log green` requires a preceding Red cycle (Red intent -> failing test -> Green intent). The `--skip-red` flag is the escape hatch for changes that genuinely don't need a failing test first.
206+
- The TDD guard enforces that `tdd_log green` requires a preceding Red cycle (writing tests -> failing test -> making tests pass). The `--skip-red` flag is the escape hatch for changes that genuinely don't need a failing test first.
207207

208208
**Using --skip-red (requires --reason):**
209209

@@ -238,7 +238,7 @@ The TDD log file name and full usage examples are provided automatically at sess
238238

239239
**Overriding TDD State:**
240240

241-
You can force the TDD state into any other state by logging a Red or Green intent by running tdd_log with your desired arguments as normal. Your override will be logged for later review. Useful if you get stuck in the wrong TDD state (e.g., you logged the wrong expectation or made a mistake).
241+
You can force the TDD state into any other state by logging a Red or Green declaration via tdd_log with your desired arguments as normal. Your override will be logged for later review. Useful if you get stuck in the wrong TDD state (e.g., you logged the wrong expectation or made a mistake).
242242

243243
### Unit Test Performance & Timeouts
244244

docs-site/docs/tdd-guard.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The TDD Guard is a state machine enforced through Claude Code hooks. It ensures
1010
The guard maintains four states:
1111

1212
```
13-
initial ──→ red_intent ──→ red ──→ green_intent ──→ initial
13+
initial ──→ writing_tests ──→ red ──→ making_tests_pass ──→ initial
1414
│ │ │
1515
│ (write (tests (edit prod
1616
│ test) fail) files)
@@ -19,12 +19,12 @@ initial ──→ red_intent ──→ red ──→ green_intent ──→ init
1919
(tests pass)
2020
```
2121

22-
| State | Meaning | Allowed Actions |
23-
| -------------- | --------------------------------------------- | ----------------------------- |
24-
| `initial` | No active TDD cycle | Log Red intent only |
25-
| `red_intent` | Agent declared what test should fail | Edit test files only |
26-
| `red` | Test ran and failed (as expected) | Log Green intent only |
27-
| `green_intent` | Agent declared what to change and which files | Edit declared prod files only |
22+
| State | Meaning | Allowed Actions |
23+
| ------------------- | --------------------------------------------- | ----------------------------- |
24+
| `initial` | No active TDD cycle | Log Red declaration only |
25+
| `writing_tests` | Agent declared what test should fail | Edit test files only |
26+
| `red` | Test ran and failed (as expected) | Log Green declaration only |
27+
| `making_tests_pass` | Agent declared what to change and which files | Edit declared prod files only |
2828

2929
When tests pass after a Green phase, the state returns to `initial`.
3030

@@ -42,14 +42,14 @@ def read_state(log_path: Path) -> str:
4242
if stripped.startswith("[test]") and stripped.endswith("— SUCCEEDED"):
4343
return "initial"
4444
if stripped.startswith("[test]") and "— FAILED" in stripped:
45-
preceding = _find_preceding_intent(lines, len(lines) - 1 - i)
45+
preceding = _find_preceding_declaration(lines, len(lines) - 1 - i)
4646
if preceding == "green":
47-
return "green_intent"
47+
return "making_tests_pass"
4848
return "red"
4949
if stripped.startswith("## Red"):
50-
return "red_intent"
50+
return "writing_tests"
5151
if stripped.startswith("## Green"):
52-
return "green_intent"
52+
return "making_tests_pass"
5353
return "initial"
5454
```
5555

@@ -58,22 +58,22 @@ def read_state(log_path: Path) -> str:
5858
Summary of state derivation rules:
5959

6060
- `[test] ... — SUCCEEDED``initial`
61-
- `[test] ... — FAILED` after a `## Green` header → `green_intent` (test failed during Green)
61+
- `[test] ... — FAILED` after a `## Green` header → `making_tests_pass` (test failed during Green)
6262
- `[test] ... — FAILED` after a `## Red` header → `red` (test failed as expected)
63-
- `## Red ...``red_intent`
64-
- `## Green ...``green_intent`
63+
- `## Red ...``writing_tests`
64+
- `## Green ...``making_tests_pass`
6565

6666
## The CLI: `tdd_log`
6767

6868
Agents interact with the TDD guard through [`scripts/tdd_log.py`](https://github.com/cadamsdotcom/CodeLeash/blob/main/scripts/tdd_log.py), invoked as:
6969

7070
```bash
71-
# Declare Red intent
71+
# Declare Red (writing-tests) phase
7272
uv run python -m scripts.tdd_log --log "tdd-abc123.log" red \
7373
--test "path/to/test_file" \
7474
--expects "test_name fails because ..."
7575

76-
# Declare Green intent
76+
# Declare Green (making-tests-pass) phase
7777
uv run python -m scripts.tdd_log --log "tdd-abc123.log" green \
7878
--change "what you plan to do" \
7979
--file "path/to/file1.py" --file "path/to/file2.py"
@@ -88,12 +88,12 @@ uv run python -m scripts.tdd_log --log "tdd-abc123.log" green --skip-red \
8888

8989
The `green` subcommand enforces prerequisites:
9090

91-
- Without `--skip-red`: requires state to be `red` (test must have failed) or `green_intent` (re-logging)
91+
- Without `--skip-red`: requires state to be `red` (test must have failed) or `making_tests_pass` (re-logging)
9292
- With `--skip-red`: requires a `--reason` from `{refactoring, lint-only, adding-coverage}`
9393

9494
### Overrides
9595

96-
Logging a Red or Green intent at any time overrides the current state. This is useful when the agent gets stuck in the wrong state. Overrides are recorded in the log for later review.
96+
Logging a Red or Green declaration at any time overrides the current state. This is useful when the agent gets stuck in the wrong state. Overrides are recorded in the log for later review.
9797

9898
## Pre-Edit Hook
9999

@@ -124,14 +124,14 @@ PROD_PATTERNS = [
124124

125125
### Permission Table
126126

127-
| State | Test Files | Prod Files |
128-
| -------------- | ----------- | ------------------------- |
129-
| `initial` | Blocked | Blocked |
130-
| `red_intent` | **Allowed** | Blocked |
131-
| `red` | Blocked | Blocked |
132-
| `green_intent` | Blocked\* | Allowed (if in allowlist) |
127+
| State | Test Files | Prod Files |
128+
| ------------------- | ----------- | ------------------------- |
129+
| `initial` | Blocked | Blocked |
130+
| `writing_tests` | **Allowed** | Blocked |
131+
| `red` | Blocked | Blocked |
132+
| `making_tests_pass` | Blocked\* | Allowed (if in allowlist) |
133133

134-
\* Test files are allowed during `green_intent` only if the Green was logged with `--skip-red`.
134+
\* Test files are allowed during `making_tests_pass` only if the Green was logged with `--skip-red`.
135135

136136
### Green Allowlist
137137

@@ -149,7 +149,7 @@ The [`scripts/tdd_post_bash.py`](https://github.com/cadamsdotcom/CodeLeash/blob/
149149
| `npm test*` or `npm run test*` | `test` | Drives state transitions |
150150
| Everything else | `bash` | Logged, no state change |
151151

152-
Test commands tagged as `test` with `SUCCEEDED` status reset the state to `initial`. Test commands that `FAILED` during a Red phase confirm the state as `red`.
152+
Test commands tagged as `test` with `SUCCEEDED` status reset the state to `initial`. Test commands that `FAILED` during a writing-tests phase confirm the state as `red`.
153153

154154
### Example TDD Log
155155

docs-site/src/pages/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function StateStrip(): ReactNode {
4848
<span className={styles.stateArrow}>&rarr;</span>
4949
<span className={styles.stateLabel}>log red</span>
5050
<span className={styles.stateArrow}>&rarr;</span>
51-
<span className={styles.stateNode}>red_intent</span>
51+
<span className={styles.stateNode}>writing_tests</span>
5252
<span className={styles.stateArrow}>&rarr;</span>
5353
<span className={styles.stateLabel}>test fails</span>
5454
<span className={styles.stateArrow}>&rarr;</span>
@@ -59,7 +59,7 @@ function StateStrip(): ReactNode {
5959
<span className={styles.stateLabel}>log green</span>
6060
<span className={styles.stateArrow}>&rarr;</span>
6161
<span className={`${styles.stateNode} ${styles.stateNodeGreen}`}>
62-
green_intent
62+
making_tests_pass
6363
</span>
6464
<span className={styles.stateArrow}>&rarr;</span>
6565
<span className={styles.stateLabel}>tests pass</span>

scripts/tdd_common.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ def is_prod_file(rel_path: str) -> bool:
3434

3535
STATES = {
3636
"initial": "initial",
37-
"red_intent": "red_intent",
37+
"writing_tests": "writing_tests",
3838
"red": "red",
39-
"green_intent": "green_intent",
39+
"making_tests_pass": "making_tests_pass",
4040
}
4141

4242

43-
def _find_preceding_intent(lines: list[str], before_index: int) -> str | None:
44-
"""Scan backwards from before_index to find the preceding Red/Green intent."""
43+
def _find_preceding_declaration(lines: list[str], before_index: int) -> str | None:
44+
"""Scan backwards from before_index to find the preceding Red/Green declaration."""
4545
for i in range(before_index - 1, -1, -1):
4646
stripped = lines[i].rstrip()
4747
if stripped.startswith("## Green"):
@@ -67,15 +67,15 @@ def read_state(log_path: Path) -> str:
6767
if stripped.startswith("[test]") and stripped.endswith("— SUCCEEDED"):
6868
return STATES["initial"]
6969
if stripped.startswith("[test]") and "— FAILED" in stripped:
70-
# Look further back for the preceding intent
71-
preceding = _find_preceding_intent(lines, len(lines) - 1 - i)
70+
# Look further back for the preceding declaration
71+
preceding = _find_preceding_declaration(lines, len(lines) - 1 - i)
7272
if preceding == "green":
73-
return STATES["green_intent"]
73+
return STATES["making_tests_pass"]
7474
return STATES["red"]
7575
if stripped.startswith("## Red"):
76-
return STATES["red_intent"]
76+
return STATES["writing_tests"]
7777
if stripped.startswith("## Green"):
78-
return STATES["green_intent"]
78+
return STATES["making_tests_pass"]
7979
# Skip other lines (Test:, Expects:, Change:, File:, [bash], [edit])
8080

8181
return STATES["initial"]

scripts/tdd_log.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""TDD Guard — CLI for writing Red/Green log entries.
1+
"""TDD Guard — CLI for writing Red/Green log declarations.
22
33
Replaces fragile echo chains with a single script invocation.
44
The --log flag specifies the log filename (resolved relative to project root).
@@ -67,25 +67,25 @@ def cmd_green(args: argparse.Namespace) -> int:
6767
return 1
6868

6969
# Validate state: Green requires a preceding Red cycle (test must have failed)
70-
if not skip_red and state not in ("red", "green_intent"):
71-
if state == "red_intent":
70+
if not skip_red and state not in ("red", "making_tests_pass"):
71+
if state == "writing_tests":
7272
print(
7373
"ERROR: Run your failing test(s) before logging Green.\n"
74-
"You declared a Red intent but haven't seen test(s) fail "
75-
"yet.",
74+
"You declared a writing-tests phase but haven't seen "
75+
"test(s) fail yet.",
7676
file=sys.stderr,
7777
)
7878
else:
7979
print(
8080
"ERROR: Cannot log Green without a preceding Red cycle.\n"
81-
"Log a Red intent first, write failing test(s) or modify "
82-
"existing test(s) to fail, then run them.",
81+
"Start writing tests first, write failing test(s) or "
82+
"modify existing test(s) to fail, then run them.",
8383
file=sys.stderr,
8484
)
8585
return 1
8686

8787
# Determine if this is an override
88-
is_override = state != "initial" if skip_red else state == "green_intent"
88+
is_override = state != "initial" if skip_red else state == "making_tests_pass"
8989

9090
# Build phase label
9191
if skip_red:
@@ -116,11 +116,11 @@ def main() -> None:
116116
)
117117
sub = parser.add_subparsers(dest="phase", required=True)
118118

119-
red = sub.add_parser("red", help="Log Red phase intent")
119+
red = sub.add_parser("red", help="Log Red phase declaration")
120120
red.add_argument("--test", required=True, help="Path to the test file")
121121
red.add_argument("--expects", required=True, help="What you expect to fail and why")
122122

123-
green = sub.add_parser("green", help="Log Green phase intent")
123+
green = sub.add_parser("green", help="Log Green phase declaration")
124124
green.add_argument("--change", required=True, help="What you plan to change")
125125
green.add_argument(
126126
"--file", required=True, action="append", help="File(s) you will edit"

scripts/tdd_pre_edit.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,21 @@ def warn_large_allowlist(allowed: set[str], threshold: int = 5) -> None:
123123

124124
def blocked_initial(log_name: str) -> str:
125125
return f"""\
126-
BLOCKED: Red-Green-Refactor — log your Red intent first.
126+
BLOCKED: Red-Green-Refactor — log your Red declaration first.
127127
Run (fill in the placeholders):
128128
129129
uv run python -m scripts.tdd_log --log "{log_name}" red --test "path/to/test_file" --expects "test_name fails because ..." """
130130

131131

132-
def blocked_red_intent_impl() -> str:
132+
def blocked_writing_tests_impl() -> str:
133133
return """\
134-
BLOCKED: You're in the Red phase — only test files can be edited.
134+
BLOCKED: You're in the writing-tests phase — only test files can be edited.
135135
Write your failing test, then run it."""
136136

137137

138138
def blocked_red(log_name: str) -> str:
139139
return f"""\
140-
BLOCKED: Red confirmed. Log your Green intent before editing.
140+
BLOCKED: Red confirmed. Log your Green declaration before editing.
141141
Run (fill in the placeholders):
142142
143143
uv run python -m scripts.tdd_log --log "{log_name}" green --change "what you plan to do" --file "path/to/file1.py" --file "path/to/file2.py" """
@@ -146,7 +146,7 @@ def blocked_red(log_name: str) -> str:
146146
def blocked_test_in_green(log_name: str) -> str:
147147
return f"""\
148148
BLOCKED: Test edits are not allowed during Green.
149-
If you need to change your test, re-log your Red intent first:
149+
If you need to change your test, re-log your Red declaration first:
150150
151151
uv run python -m scripts.tdd_log --log "{log_name}" red --test "path/to/test_file" --expects "test_name fails because ..." """
152152

@@ -158,7 +158,7 @@ def blocked_green_not_listed(file_path: str, allowed: set[str], log_name: str) -
158158
Declared files:
159159
{listed}
160160
161-
To add it, re-run your green intent including this file:
161+
To add it, re-run your Green declaration including this file:
162162
uv run python -m scripts.tdd_log --log "{log_name}" green --change "..." --file "{file_path}" """
163163

164164

@@ -187,14 +187,14 @@ def _check_agent_logs(file_path: str, rel_path: str, kind: str) -> bool:
187187
if _is_agent_log_finished(agent_log):
188188
continue
189189
agent_state = read_state(agent_log)
190-
if agent_state == "green_intent":
190+
if agent_state == "making_tests_pass":
191191
if kind == "test" and is_skip_red_green(agent_log):
192192
return True
193193
if kind == "prod":
194194
allowed = read_green_allowlist(agent_log)
195195
if rel_path in allowed:
196196
return True
197-
elif agent_state == "red_intent" and kind == "test":
197+
elif agent_state == "writing_tests" and kind == "test":
198198
return True
199199
return False
200200

@@ -242,24 +242,24 @@ def main() -> None:
242242
sys.exit(0)
243243

244244
# Permission table:
245-
# initial → block all (test + prod)
246-
# red_intent → test ok, prod blocked
247-
# red → block all (test + prod)
248-
# green_intent → test blocked (unless skip-red), prod in allowlist
245+
# initial → block all (test + prod)
246+
# writing_tests → test ok, prod blocked
247+
# red → block all (test + prod)
248+
# making_tests_pass → test blocked (unless skip-red), prod in allowlist
249249
if state == "initial":
250250
if _check_agent_logs(file_path, rel_path, kind):
251251
log_edit(log_path, file_path, kind, state, "ALLOWED(agent)")
252252
sys.exit(0)
253253
log_edit(log_path, file_path, kind, state, "BLOCKED")
254254
print(blocked_initial(log_name), file=sys.stderr)
255255
sys.exit(2)
256-
elif state == "red_intent":
256+
elif state == "writing_tests":
257257
if kind == "prod":
258258
if _check_agent_logs(file_path, rel_path, kind):
259259
log_edit(log_path, file_path, kind, state, "ALLOWED(agent)")
260260
sys.exit(0)
261261
log_edit(log_path, file_path, kind, state, "BLOCKED")
262-
print(blocked_red_intent_impl(), file=sys.stderr)
262+
print(blocked_writing_tests_impl(), file=sys.stderr)
263263
sys.exit(2)
264264
# test file → allowed
265265
log_edit(log_path, file_path, kind, state, "ALLOWED")
@@ -271,7 +271,7 @@ def main() -> None:
271271
log_edit(log_path, file_path, kind, state, "BLOCKED")
272272
print(blocked_red(log_name), file=sys.stderr)
273273
sys.exit(2)
274-
elif state == "green_intent":
274+
elif state == "making_tests_pass":
275275
if kind == "test":
276276
if is_skip_red_green(log_path):
277277
log_edit(log_path, file_path, kind, state, "ALLOWED")

scripts/tdd_session_start.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ def main() -> None:
1818
print()
1919
print("TDD Red-Green-Refactor cycle:")
2020
print()
21-
print(" 1. Log Red intent (declare what test you expect to fail):")
21+
print(" 1. Start writing tests (declare what you expect to fail):")
2222
print(f' uv run python -m scripts.tdd_log --log "{log}" red \\')
2323
print(' --test "path/to/test_file" \\')
2424
print(' --expects "test_name fails because ..."')
2525
print()
2626
print(" 2. Write the failing test, then run it to confirm it fails.")
2727
print()
28-
print(" 3. Log Green intent (declare what you will change to make it pass):")
28+
print(" 3. Start making tests pass (declare what you will change):")
2929
print(f' uv run python -m scripts.tdd_log --log "{log}" green \\')
3030
print(' --change "what you plan to do" \\')
3131
print(' --file "path/to/file1.py" --file "path/to/file2.py"')
@@ -37,7 +37,7 @@ def main() -> None:
3737
print(' --reason=refactoring --change "what you plan to do" \\')
3838
print(' --file "path/to/file.py"')
3939
print()
40-
print("Files subject to TDD (edits blocked until Red/Green intent logged):")
40+
print("Files subject to TDD (edits blocked until Red/Green declaration logged):")
4141
print(" Prod: src/ app/ scripts/*.py main.py worker.py")
4242
print(
4343
" Test: *.test.{ts,tsx,js,jsx} test_*.py tests/ src/test-utils/ conftest.py"

0 commit comments

Comments
 (0)