| name | tdd-workflow |
| description | Complete Test-Driven Development (TDD) workflow guide. Use when implementing new features, adding functionality, or when user mentions "test first", "TDD", or "red-green-refactor". Ensures compliance with Critical Invariant |
Test-Driven Development (TDD) Workflow
This skill enforces Critical Invariant #7: Always write tests BEFORE implementation.
When to Use This Skill
ALWAYS use for:
- New features or functionality
- New API endpoints or models
- New UI components with logic
- Bug fixes (write test that reproduces bug first)
Trigger words:
- "implement", "add feature", "create new"
- "TDD", "test first", "test-driven"
- "red-green-refactor"
The TDD Cycle: RED-GREEN-REFACTOR
🔴 Phase 1: RED (Write Failing Test)
Step 1: Create test file
touch test/my-feature.test.js
Step 2: Write the test FIRST
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { myFeature } from '../lib/my-feature.js'
describe('myFeature', () => {
it('should return expected result when given valid input', () => {
const result = myFeature({ input: 'test' })
assert.strictEqual(result.success, true)
assert.strictEqual(result.output, 'processed: test')
})
})
Step 3: Run test - MUST FAIL
npm test
✅ Success criteria:
- Test fails for the RIGHT reason
- Error message is clear
- You understand what needs to be implemented
❌ If test passes already:
- You're not writing a new test
- Test doesn't actually test the feature
- Implementation already exists (not TDD)
🟢 Phase 2: GREEN (Make Test Pass)
Step 4: Implement MINIMAL code
Write the SIMPLEST code that makes the test pass:
export function myFeature({ input }) {
return {
success: true,
output: `processed: ${input}`
}
}
Don't add:
- Extra features not tested
- Edge case handling not in test
- Optimizations or clever code
- Multiple responsibilities
Step 5: Run test - MUST PASS
npm test
✅ Success criteria:
- All tests pass (including existing ones)
- Implementation is simple and clear
- Code does ONLY what tests require
🔵 Phase 3: REFACTOR (Improve While Green)
Step 6: Make it better
Now that tests pass, improve the code:
const PREFIX = 'processed: '
export function myFeature({ input }) {
if (!input || typeof input !== 'string') {
throw new Error('Input must be a non-empty string')
}
return {
success: true,
output: `${PREFIX}${input.trim()}`
}
}
Step 7: Add tests for edge cases
describe('myFeature', () => {
it('should return expected result when given valid input', () => {
})
it('should throw error for invalid input', () => {
assert.throws(
() => myFeature({ input: '' }),
{ message: 'Input must be a non-empty string' }
)
})
it('should trim whitespace from input', () => {
const result = myFeature({ input: ' test ' })
assert.strictEqual(result.output, 'processed: test')
})
})
Step 8: Run tests - MUST STILL PASS
npm test
✅ Success criteria:
- Code is cleaner/more maintainable
- All tests still pass
- No new functionality without tests
Full Workflow Example
Scenario: Add a user validation function
1. 🔴 RED: Write failing test
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { validateUser } from '../lib/validate-user.js'
describe('validateUser', () => {
it('should return valid:true for user with required fields', () => {
const user = { name: 'Alice', email: 'alice@example.com' }
const result = validateUser(user)
assert.strictEqual(result.valid, true)
assert.strictEqual(result.errors, undefined)
})
})
Run: npm test → ❌ Fails (module doesn't exist)
2. 🟢 GREEN: Minimal implementation
export function validateUser(user) {
return { valid: true }
}
Run: npm test → ✅ Passes
3. 🔵 REFACTOR: Add more tests + improve
describe('validateUser', () => {
it('should return valid:true for user with required fields', () => {
})
it('should return valid:false when name is missing', () => {
const user = { email: 'alice@example.com' }
const result = validateUser(user)
assert.strictEqual(result.valid, false)
assert.deepStrictEqual(result.errors, ['Name is required'])
})
it('should return valid:false when email is invalid', () => {
const user = { name: 'Alice', email: 'not-an-email' }
const result = validateUser(user)
assert.strictEqual(result.valid, false)
assert.ok(result.errors.some(e => e.includes('email')))
})
})
Run: npm test → ❌ Fails (need to implement validation)
export function validateUser(user) {
const errors = []
if (!user.name || user.name.trim() === '') {
errors.push('Name is required')
}
if (!user.email || !isValidEmail(user.email)) {
errors.push('Valid email is required')
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined
}
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
Run: npm test → ✅ Passes
Common Pitfalls
❌ Writing tests after implementation
Problem: Tests become "verification tests" not "design tests"
Solution: Delete implementation, start over with test first
❌ Writing too many tests at once
Problem: Overwhelmed, unclear what to implement
Solution: One test at a time, make it pass, then next test
❌ Implementing more than needed to pass test
Problem: Scope creep, untested code paths
Solution: Implement ONLY what makes current test pass
❌ Skipping RED phase
Problem: Test might always pass (false positive)
Solution: ALWAYS watch test fail before implementing
❌ Not refactoring
Problem: Code works but is messy
Solution: After GREEN, improve code while tests stay green
TDD Benefits
Design Benefits
- Forces thinking about API before implementation
- Creates minimal, focused interfaces
- Reveals coupling and dependencies early
Confidence Benefits
- Every line of code has a test
- Refactoring is safe (tests catch regressions)
- Documentation through examples
Workflow Benefits
- Clear progress (count passing tests)
- Natural stopping points
- Easy to resume work (look at failing test)
Integration with Project Workflow
Before Starting Feature
cat .github/skills/tdd-workflow/SKILL.md
touch test/my-feature.test.js
During Implementation
Before Committing
npm test
git add lib/my-feature.js test/my-feature.test.js
git commit -m "feat: add my-feature (TDD)"
Self-Audit (from AGENTS.md Step 3½)
Enforcement Mechanisms
This project has 4 layers of TDD enforcement:
- AGENTS.md - Pre-work checklist (Step 3)
- feature-implementation skill - Phase 0 TDD requirement
- Git hook -
.git-hooks/pre-commit checks for test changes
- GitHub Actions -
.github/workflows/tdd-compliance.yml PR check
To install git hook:
./scripts/install-git-hooks.sh
Quick Reference
RED-GREEN-REFACTOR Cycle:
- 🔴 Write failing test
- Run test (must fail)
- 🟢 Write minimal code
- Run test (must pass)
- 🔵 Refactor code
- Run test (must still pass)
- Repeat
Golden Rules:
- Test FIRST, code second
- One test at a time
- Simplest code to pass
- All tests must pass before commit
- Refactor only when green
This skill enforces Critical Invariant #7 from CODEBASE_ESSENTIALS.md