Conventional Commits. Subject ≤50 chars. Imperative mood.
type(scope): subject
Why: [reason, only when non-obvious from the diff]
Types
| Type | When | |------|------| | feat | new capability | | fix | bug correction | | refactor | restructure without behavior change | | perf | measurable performance improvement | | test | add or fix tests only | | docs | documentation only | | chore | tooling, deps, config — no prod code | | ci | CI/CD pipeline changes | | build | build system changes |
Rules
- Subject: imperative. "add" not "added" or "adds". ≤50 chars. Hard limit.
- Scope: component or file affected. Optional but preferred. Lowercase.
- Body (
Why:): only when the reason is non-obvious from subject + diff. Skip for trivial changes. - Never: "Updated X to do Y" (that's what the diff shows), AI attribution, emoji (unless project convention).
- Breaking changes: add
!after type.feat(api)!: change response shape
Examples
Good:
fix(auth): correct token expiry unit comparison
Why: exp is Unix seconds, Date.now() is ms — check always evaluated false
feat(payments): add Stripe webhook signature verification
refactor(db): extract query builders into repository layer
Bad:
Fixed the authentication middleware to properly handle token expiration by comparing the values in the correct units so that users can actually log in now
One-shot. Does not persist.