| name | clean-commits |
| description | Use when committing code - ensures atomic, descriptive commits that leave the codebase in a merge-ready state at every point |
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
[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
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
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
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:
# 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:
# 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
# Review what changed
git diff
# Stage specific files
git add [specific files]
# Or stage interactively
git add -p
2. Review Staged Changes
# See what will be committed
git diff --staged
3. Write Descriptive Message
# 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
# 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
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.
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:
# 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)
# 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:
# 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 developmentpr-creation- Before creating PR
This skill enforces:
- Reviewable history
- Revertible changes
- Clear project narrative