Tdd
Use before writing or changing any production code — features, bug fixes, refactors — and whenever the router announces the tdd-fast-path. Invoke via route or explicit command.From its SKILL.md
npx -y skills add nyelonong/galdr --skill tddAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 11 stars11 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.6 KB, as published. Nobody here has run it
TDD
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
This is a hard rule, not a preference. If you are about to write or edit production code and no failing test exists for that change yet, stop and write the test first.
The loop
- Write one failing test for the next smallest behavior you need.
- Run it and read the failure. Three outcomes:
- Fails as expected (the assertion fails for the reason you expect) → continue.
- Fails wrong (compile error, wrong exception type, crash before the assertion) → fix the test or its setup; do not touch production code yet.
- Passes (green with no production change) → the test asserts nothing useful; rewrite it so it fails first.
- Emit
EV [<task> RED] <test cmd> → FAIL (<expected failure>). - Write the minimal production code needed to pass — no more than the test requires.
- Run the test, and the type checker, to green.
- Emit
EV [<task> GREEN] <test cmd> → PASS. - Commit the test and implementation as one atomic commit — one logical change,
conventional prefix (
feat/fix/refactor/test), never mixed. Refactor commits made at the wave gate are separate commits, never folded into this one. - Move to the next test.
Refactoring is not a loop step. It happens once, at the wave gate, as a pass across the whole task's tests — not inside this loop.
Seams
A seam is a declared boundary where a test may substitute a fake or mock for a real dependency. Seams come from the plan. If the plan does not declare a seam for what you're testing, stop and ask — don't invent one.
If code exists before its test
If production code was written before a test for it exists: delete it. Don't keep it as reference, don't adapt it — delete means delete. Then restart the loop with a failing test first.
Tautological-test tells
| Tell | Why it's empty |
|---|---|
| Asserting the mock | Checks that a mock was called, not that real behavior occurred — passes even if the real logic is deleted |
| Asserting the implementation | Restates the code's internals instead of checking an observable outcome |
| Snapshot without intent | Records that something changed, not whether the change is correct |
Mock gate
A mock is allowed only at a named seam, with a stated reason (why the real dependency can't run here — for example, a network call, a non-deterministic clock, cost). No named seam, no mock: use the real dependency, or the test double the plan provides.
Cadence
In the loop: run the type checker and the one focused test for the file you're changing. Do not run the full suite here. Run the full suite at wave gates and before any completion claim.
Fast path
When route dispatches straight into this skill (announced as tdd-fast-path), the
loop and the Iron Law above still apply in full. Fast path skips routing ceremony, not
test-first discipline.
Rationalizations
| Thought | Reality |
|---|---|
| "Too simple to test" | Simple things break; the test is fast — write it |
| "I'll verify at the end" | Stale evidence is not evidence; verify now |
| "The fix is obvious, skip root cause" | Symptom fixes recur; find the cause |
| "The test is hard to write" | Hard test = design feedback; check the seam |
Examples
Go, table-driven with testify:
func TestParseAmount(t *testing.T) {
tests := []struct {
name string
input string
want int64
wantErr bool
}{
{"valid rupiah", "Rp10.000", 10000, false},
{"empty string", "", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseAmount(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
TypeScript, vitest:
import { describe, it, expect } from "vitest";
describe("parseAmount", () => {
it("parses a rupiah string", () => {
expect(parseAmount("Rp10.000")).toBe(10000);
});
it("rejects an empty string", () => {
expect(() => parseAmount("")).toThrow();
});
});
Rust, cargo test with inline tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_valid_rupiah() {
assert_eq!(parse_amount("Rp10.000"), Ok(10000));
}
#[test]
fn rejects_empty_string() {
assert!(parse_amount("").is_err());
}
}
For TypeScript work, load references/ts-conventions.md before writing test doubles or
parsing at a boundary.