Clean Commits
Overview
Every commit is atomic, descriptive, and leaves code in a working state.
Core principle: Anyone should be able to checkout any commit and have working code.
Announce at use: "I'm committing with a descriptive message following clean-commits standards."
Commit Message Format
Structure
code
[type](scope): Short description (max 72 chars) [Optional body - what and why, not how] [Optional footer - issue references, breaking changes] Refs: #[ISSUE_NUMBER]
Types
| Type | Use For |
|---|---|
feat | New feature |
fix | Bug fix |
docs | Documentation only |
style | Formatting, no code change |
refactor | Code restructuring |
test | Adding/fixing tests |
chore | Maintenance, dependencies |
Examples
code
feat(auth): Add user registration endpoint Implement POST /api/users/register with email validation, password hashing, and duplicate detection. - Validates email format and uniqueness - Hashes password with bcrypt - Returns user object without password Refs: #123
code
fix(auth): Prevent redirect loop on expired session Session expiry was triggering redirect to login, which checked session, found expired, and redirected again. Now clears session cookie before redirecting. Refs: #456
code
test(auth): Add integration tests for registration Cover success case, duplicate email, invalid format, and weak password scenarios. Refs: #123
Atomic Commits
What Makes a Commit Atomic
| Atomic | Not Atomic |
|---|---|
| One logical change | Multiple unrelated changes |
| Passes all tests | Breaks tests |
| Complete feature slice | Half-implemented feature |
| Can be reverted cleanly | Reverts would break things |
Signs of Non-Atomic Commits
- •Commit message uses "and" to describe multiple things
- •Diff includes unrelated files
- •Some tests fail after commit
- •"WIP" in commit message
Splitting Large Changes
If you have multiple changes, commit them separately:
bash
# Stage specific files git add src/auth/register.ts git add src/auth/register.test.ts git commit -m "feat(auth): Add registration endpoint" # Stage next logical unit git add src/auth/login.ts git add src/auth/login.test.ts git commit -m "feat(auth): Add login endpoint"
Working State Requirement
Every commit must leave the codebase in a state where:
- • All tests pass
- • Build succeeds
- • Application runs
- • No TypeScript errors
- • No linting errors
Before committing:
bash
# Run tests pnpm test # Check build pnpm build # Check types pnpm typecheck # Check lint pnpm lint
If any fail, fix before committing.
Commit Frequency
Commit Often
- •After each passing test in TDD cycle
- •After each refactoring step
- •After completing a logical unit
Don't Wait Too Long
| Too Infrequent | Just Right |
|---|---|
| "Implement entire feature" | "Add user model" |
| "Fix all bugs" | "Fix session expiry redirect" |
| "Update everything" | "Update auth dependencies" |
Small is Good
Smaller commits are:
- •Easier to review
- •Easier to revert
- •Easier to bisect
- •Easier to understand
The Commit Process
1. Stage Selectively
bash
# Review what changed git diff # Stage specific files git add [specific files] # Or stage interactively git add -p
2. Review Staged Changes
bash
# See what will be committed git diff --staged
3. Write Descriptive Message
bash
# Short message (if simple) git commit -m "fix(auth): Handle null user in session check" # Long message (if complex) git commit # Opens editor for full message
4. Verify After Commit
bash
# Check commit looks right git show --stat # Verify tests still pass pnpm test
Commit Message Body
When to include a body:
- •Why the change was made (not just what)
- •Context that isn't obvious from code
- •Trade-offs or alternatives considered
- •Breaking changes if any
Body Examples
code
refactor(api): Extract validation middleware Validation logic was duplicated across 12 endpoints. Extracted to reusable middleware that can be composed. Alternative considered: validation library. Rejected because our rules are domain-specific.
code
fix(data): Use optimistic locking for updates Race condition was causing lost updates when two users edited the same record simultaneously. BREAKING CHANGE: Update operations now require version field in request body.
Issue References
Always reference the issue:
bash
# In commit message Refs: #123 # Or if commit closes the issue Closes: #123
Amending Commits
When to Amend
- •Typo in message (if not pushed)
- •Forgot to stage a file (if not pushed)
bash
# Amend last commit (before push only!) git add forgotten-file.ts git commit --amend
When NOT to Amend
- •After pushing to remote
- •Changing commits others have based work on
Revert, Don't Delete
If a commit was wrong:
bash
# Create a new commit that undoes the change git revert [commit-sha] # DON'T rewrite history on shared branches # DON'T force push to fix mistakes
Checklist
Before each commit:
- • Tests pass
- • Build succeeds
- • Change is atomic (one logical unit)
- • Message follows format
- • Message describes why, not just what
- • Issue is referenced
- • No "WIP" or placeholder messages
Integration
This skill is called by:
- •
issue-driven-development- Throughout development - •
pr-creation- Before creating PR
This skill enforces:
- •Reviewable history
- •Revertible changes
- •Clear project narrative