COMMIT: Quality Gates
You’ve reached minute 5:00. Tests pass. Code is clean. Now comes the moment of truth: do quality gates pass?
COMMIT: All gates pass → Accept the work RESET: Any gate fails → Discard everything
No middle ground. No “mostly passing.” This binary decision enforces uncompromising quality standards.
The Quality Gate Philosophy
Quality gates embody Toyota’s Jidoka principle: “Stop the line when defects occur.” If quality standards aren’t met, production halts.
Why Binary?
No Compromise: Quality is non-negotiable. A partially working feature is worse than no feature—it gives false confidence.
Clear Signal: Binary outcomes are unambiguous. You know instantly whether the cycle succeeded.
Forcing Function: Knowing you might RESET motivates you to stay within the 5-minute budget and write clean code from the start.
Continuous Integration: Every commit maintains codebase quality. No “I’ll fix it later” accumulation.
pforge Quality Gates
pforge enforces multiple quality gates via make quality-gate
:
Gate 1: Formatting
cargo fmt --check
What it checks: Code follows Rust style guide (indentation, spacing, line breaks)
Why it matters: Consistent formatting reduces cognitive load and diff noise
Typical failures:
- Inconsistent indentation
- Missing/extra line breaks
- Non-standard brace placement
Fix: Run cargo fmt
before checking
Gate 2: Linting (Clippy)
cargo clippy -- -D warnings
What it checks: Common Rust pitfalls, performance issues, style violations
Why it matters: Clippy catches bugs and code smells automatically
Typical failures:
- Unused variables
- Unnecessary clones
- Redundant pattern matching
- Performance anti-patterns
Fix: Address each warning individually or suppress with #[allow(clippy::...)]
if truly necessary
Gate 3: Tests
cargo test --all
What it checks: All tests (unit, integration, doc tests) pass
Why it matters: Broken tests mean broken behavior
Typical failures:
- New code breaks existing tests (regression)
- New test doesn’t pass (incomplete implementation)
- Flaky tests (non-deterministic behavior)
Fix: Debug failing tests, fix implementation, or fix test expectations
Gate 4: Complexity
pmat analyze complexity --max 20
What it checks: Cyclomatic complexity of each function
Why it matters: Complex functions are bug-prone and hard to maintain
Typical failures:
- Too many conditional branches
- Deeply nested loops
- Long match statements
Fix: Extract functions, simplify conditionals, reduce nesting
Gate 5: Technical Debt
pmat analyze satd --max 0
What it checks: Self-Admitted Technical Debt (SATD) comments like TODO
, FIXME
, HACK
Why it matters: SATD comments indicate code that needs improvement
Typical failures:
- Leftover
TODO
comments FIXME
markersHACK
acknowledgments
Fix: Either address the issue or remove the comment (only if it’s not actual debt)
Exception: Phase markers like TODO(RED)
, TODO(GREEN)
, TODO(REFACTOR)
are allowed during development but must be removed before COMMIT
Gate 6: Coverage
cargo tarpaulin --out Json
What it checks: Test coverage ≥ 80%
Why it matters: Untested code is unverified code
Typical failures:
- New code without tests
- Error paths not tested
- Edge cases not covered
Fix: Add tests for uncovered lines
Gate 7: Technical Debt Grade
pmat analyze tdg --min 0.75
What it checks: Overall technical debt grade (0-1 scale)
Why it matters: Aggregate measure of code health
Typical failures:
- Combination of complexity, SATD, dead code, and low coverage
- Accumulation of small issues
Fix: Address individual issues contributing to low TDG
Running Quality Gates
Fast Check (During REFACTOR)
make quality-gate-fast
Runs subset of gates for quick feedback:
- Formatting
- Clippy
- Unit tests only
Time: < 10 seconds
Use this during REFACTOR to catch issues early.
Full Check (Before COMMIT)
make quality-gate
Runs all gates:
- Formatting
- Clippy
- All tests
- Complexity
- SATD
- Coverage
- TDG
Time: < 30 seconds (for small projects)
Use this at minute 4:30-5:00 before deciding COMMIT or RESET.
The COMMIT Decision
At minute 5:00, run make quality-gate
:
Scenario 1: All Gates Pass
make quality-gate
# ✓ Formatting check passed
# ✓ Clippy check passed
# ✓ Tests passed (15 passed; 0 failed)
# ✓ Complexity check passed (max: 9/20)
# ✓ SATD check passed (0 markers found)
# ✓ Coverage check passed (87.5%)
# ✓ TDG check passed (0.92/0.75)
# All quality gates passed!
Decision: COMMIT
Stage and commit your changes:
git add -A
git commit -m "feat: add division handler with zero check
Implements division operation with validation for zero denominator.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>"
Cycle successful. Start next cycle.
Scenario 2: One or More Gates Fail
make quality-gate
# ✓ Formatting check passed
# ✗ Clippy check failed (3 warnings)
# ✓ Tests passed
# ✓ Complexity check passed
# ✓ SATD check passed
# ✗ Coverage check failed (72.3% < 80%)
# ✓ TDG check passed
# Quality gates FAILED
Decision: RESET
Discard all changes:
git checkout .
git clean -fd
Cycle failed. Reflect, then start next cycle with adjusted scope.
Scenario 3: Timer Expired
# Check time
echo "Minute: 5:30"
Timer expired before running quality gates.
Decision: RESET
No exceptions. Even if you’re “almost done,” RESET.
git checkout .
git clean -fd
The RESET Protocol
When RESET occurs, follow this protocol:
Step 1: Discard Changes
git checkout .
git clean -fd
This removes all uncommitted changes—both tracked and untracked files.
Step 2: Reflect
Don’t immediately start the next cycle. Take 30-60 seconds to reflect:
Why did RESET occur?
- Timer expired → Scope too large
- Tests failed → Implementation incomplete or incorrect
- Complexity too high → Need simpler approach
- Coverage too low → Missing tests
What will I do differently next cycle?
- Smaller scope (fewer features per test)
- Simpler implementation (avoid clever approaches)
- Better planning (think before typing)
- More tests (test error cases too)
Step 3: Log the RESET
Track your RESETs to identify patterns:
echo "$(date) RESET divide_by_zero - complexity too high (cycle 5:30)" >> .tdd-log
Over time, you’ll notice:
- Common failure modes
- Scope estimation improvements
- Decreasing RESET frequency
Step 4: Start Fresh Cycle
Begin a new 5-minute cycle with adjusted scope:
termdown 5m &
vim tests/calculator_test.rs
Apply lessons learned from the RESET.
Common COMMIT Failures
Failure 1: Clippy Warnings
warning: unused variable: `temp`
--> src/handlers/calculate.rs:12:9
|
12 | let temp = input.a + input.b;
| ^^^^ help: if this is intentional, prefix it with an underscore: `_temp`
Why it happens: Leftover variables from implementation iterations
Quick fix (if < 30 seconds to minute 5:00):
// Remove unused variable
// let temp = input.a + input.b; // deleted
Ok(Output { result: input.a + input.b })
Re-run quality gates.
If no time to fix: RESET
Failure 2: Test Regression
test test_add_positive_numbers ... FAILED
failures:
---- test_add_positive_numbers stdout ----
thread 'test_add_positive_numbers' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `6`'
Why it happens: New code broke existing functionality
Quick fix: Unlikely to fix in < 30 seconds
Correct action: RESET
Regression means your change had unintended side effects. You need to rethink the approach.
Failure 3: Low Coverage
Coverage: 72.3% (target: 80%)
Uncovered lines:
src/handlers/divide.rs:15-18 (error handling)
Why it happens: Forgot to test error paths
Quick fix (if close to time limit): Write missing test in next cycle
Correct action: RESET if you want this feature in codebase now
Coverage gates ensure every line is tested. Untested error handling is a bug waiting to happen.
Failure 4: High Complexity
Cyclomatic complexity check failed:
src/handlers/calculate.rs:handle (complexity: 23, max: 20)
Why it happens: Too many conditional branches
Quick fix: Unlikely in remaining time
Correct action: RESET
High complexity indicates the implementation needs redesign. Quick patches won’t fix fundamental complexity.
When to Override Quality Gates
Never.
The strict answer: you should never override quality gates in EXTREME TDD. If gates fail, the cycle fails.
However, in practice, there are rare circumstances where you might git commit --no-verify
:
Acceptable Override Cases
Pre-commit hook not installed yet: First commit setting up the project
External dependency issues: Gate tool unavailable (e.g., CI server down, PMAT not installed)
Emergency hotfix: Production is down, fix needs to deploy immediately
Experimental branch: Explicitly marked WIP branch, not merging to main
Unacceptable Override Cases
“I’m in a hurry”: No. RESET and do it right.
“The gate is wrong”: If the gate is genuinely wrong, fix the gate in a separate cycle. Don’t override.
“It’s just a style issue”: Style issues compound. Fix them.
“I’ll fix it in the next commit”: No. Future you won’t fix it. Fix it now or RESET.
The Pre-Commit Hook
pforge installs a pre-commit hook that runs quality gates automatically:
.git/hooks/pre-commit
Contents:
#!/bin/bash
set -e
echo "Running quality gates..."
make quality-gate
if [ $? -ne 0 ]; then
echo "Quality gates failed. Commit blocked."
exit 1
fi
echo "Quality gates passed. Commit allowed."
exit 0
This hook:
- Runs automatically on
git commit
- Blocks commit if gates fail
- Ensures you never accidentally commit bad code
To bypass (rarely needed):
git commit --no-verify
But this should be exceptional, not routine.
COMMIT Message Conventions
When COMMIT succeeds, write a clear commit message:
Format
<type>: <short summary>
<detailed description>
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Types
feat
: New featurefix
: Bug fixrefactor
: Code restructuring (no behavior change)test
: Add or modify testsdocs
: Documentation changeschore
: Build, dependencies, tooling
Examples
git commit -m "feat: add divide operation to calculator
Implements basic division with f64 precision. Validates denominator is non-zero and returns appropriate error for division by zero.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>"
git commit -m "test: add edge case for negative numbers
Ensures calculator handles negative operands correctly.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>"
git commit -m "refactor: extract validation into helper function
Reduces cyclomatic complexity from 18 to 12 by extracting input validation logic.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>"
Psychology of COMMIT vs RESET
The Joy of COMMIT
When quality gates pass:
✓ All quality gates passed!
There’s a genuine dopamine hit. You’ve:
- Written working code
- Maintained quality standards
- Made progress
This positive reinforcement encourages continuing the discipline.
The Pain of RESET
When quality gates fail:
✗ Quality gates FAILED
There’s genuine disappointment. You’ve:
- Spent 5 minutes
- Produced nothing commitworthy
- Must start over
This negative reinforcement teaches you to:
- Scope smaller
- Write cleaner code upfront
- Respect the time budget
The Learning Curve
First week:
- COMMIT rate: ~50%
- RESET rate: ~50%
- Frequent frustration
Second week:
- COMMIT rate: ~70%
- RESET rate: ~30%
- Pattern recognition forms
Fourth week:
- COMMIT rate: ~90%
- RESET rate: ~10%
- Discipline internalized
The pain of RESETs trains you to succeed. After 30 days, you intuitively scope work to fit 5-minute cycles.
Tracking COMMIT/RESET Ratios
Track your outcomes to measure improvement:
# Simple tracking script
echo "$(date) COMMIT feat_divide_basic (4:45)" >> .tdd-log
echo "$(date) RESET feat_divide_zero (5:30)" >> .tdd-log
Calculate weekly stats:
grep COMMIT .tdd-log | wc -l # 27
grep RESET .tdd-log | wc -l # 3
# Success rate: 27/(27+3) = 90%
Target Metrics
Week 1: 50% COMMIT rate (learning) Week 2: 70% COMMIT rate (improving) Week 4: 85% COMMIT rate (proficient) Week 8: 95% COMMIT rate (expert)
When RESET Happens Repeatedly
If you RESET 3+ times on the same feature:
Stop and Reassess
Problem: Your approach isn’t working
Solutions:
- Break down further: Feature is too large for one cycle
- Research first: You don’t understand the domain well enough
- Spike solution: Take 15 minutes outside TDD to explore approaches
- Pair program: Another developer might see a simpler approach
- Defer feature: Maybe this feature needs more design before implementation
Example: Persistent RESET
# Attempting to implement JWT authentication
09:00 RESET jwt_auth_validate (5:45)
09:06 RESET jwt_auth_validate (5:30)
09:12 RESET jwt_auth_validate (6:00)
After 3 RESETs, stop. Take 15 minutes to:
- Read JWT library documentation
- Write a spike (throwaway code) to understand API
- Identify the smallest incremental step
Then return to TDD with better understanding.
Quality Gates in CI/CD
Quality gates don’t just run locally—they run in CI/CD:
GitHub Actions Example
# .github/workflows/quality.yml
name: Quality Gates
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run Quality Gates
run: make quality-gate
This ensures:
- Every push runs quality gates
- Pull requests can’t merge if gates fail
- Team maintains quality standards
Advanced: Graduated Quality Gates
For larger changes, use graduated quality gates:
Cycle 1: Core Implementation
- Run fast gates (fmt, clippy, unit tests)
- COMMIT if passing
Cycle 2: Integration Tests
- Run integration tests
- COMMIT if passing
Cycle 3: Performance Tests
- Run benchmarks
- COMMIT if no regression
This allows you to make progress in 5-minute increments while building up to full validation.
The Discipline of Binary Outcomes
The hardest part of EXTREME TDD is accepting binary outcomes:
No “Good Enough”: Either all gates pass or they don’t. No subjective judgment.
No “I’ll Fix Later”: Future you won’t fix it. Fix it now or RESET.
No “It’s Just One Warning”: One warning becomes ten warnings becomes unmaintainable code.
This discipline seems harsh, but it’s what maintains quality over hundreds of cycles.
Celebrating COMMITs
Each COMMIT is progress. Celebrate small wins:
# After COMMIT
echo "✓ Feature complete: divide with zero check"
echo "✓ Tests: 12 passing"
echo "✓ Coverage: 87%"
echo "✓ Cycle time: 4:45"
Recognizing progress maintains motivation through the discipline of EXTREME TDD.
Next Steps
You now understand the complete 5-minute EXTREME TDD cycle:
RED (2 min): Write failing test GREEN (2 min): Minimum code to pass REFACTOR (1 min): Clean up COMMIT (instant): Quality gates decide
This cycle, repeated hundreds of times, builds production-quality software with:
- 80%+ test coverage
- Zero technical debt
- Consistent code quality
- Frequent commits (safety net)
The next chapters cover quality gates in detail, testing strategies, and advanced TDD patterns.
Previous: REFACTOR: Clean Up Next: Chapter 8: Quality Gates