Conventional Commits
Write commit messages that strictly follow the Conventional Commits 1.0.0 specification: analyze the staged changes and produce a structured, meaningful message.
When to use
- Committing code changes or preparing a pull request
- Generating or maintaining a changelog
- Any git commit operation where the message must follow Conventional Commits
- Determining the semantic impact of a change — commit types feed version bumps, see [[semantic-versioning]]
Commit format
The strict format from the specification:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Rules:
type is required: a noun describing the category of change
scope is optional: a noun in parentheses describing the section of the codebase
! before the colon signals a BREAKING CHANGE
description is required: short summary in imperative mood, lowercase, no period
body is optional: free-form, one blank line after description
footer(s) are optional: one blank line after body, format token: value or token #value
Commit types
Ordered by semantic impact (the type drives the version bump — see [[semantic-versioning]]):
- feat: new feature (triggers MINOR version bump in SemVer)
- fix: bug fix (triggers PATCH version bump in SemVer)
- docs: documentation only
- style: formatting, semicolons, whitespace (no logic change)
- refactor: code restructuring (no behavior change)
- perf: performance improvement
- test: adding or correcting tests
- build: build system or external dependencies
- ci: CI/CD configuration
- chore: maintenance tasks (deps update, config)
Breaking Changes
Two valid ways to signal breaking changes (triggers MAJOR version bump):
- Append
! after type/scope: feat!: remove deprecated endpoint
- Footer:
BREAKING CHANGE: removed /v1/users endpoint
Both can be combined. Breaking changes can apply to any type.
Scope Guidelines
- Use the module, package, or component name:
feat(auth):, fix(parser):
- Keep scopes consistent within a project
- Omit scope when the change is cross-cutting
Analysis process
- Read the staged diff (
git diff --staged)
- Identify the primary intent: is this a feature, fix, refactor, etc.?
- Determine scope from the files/modules changed
- Check if this introduces breaking changes to a public API
- Write the description in imperative mood ("add", not "added" or "adds")
- Add body if the diff is non-trivial or the "why" is not obvious
- Add footer for breaking changes or issue references
Forbidden footers (STRICT)
NEVER add author-attribution trailers to a commit message or to tag release notes:
- No
Co-Authored-By: trailers of any kind.
- No LLM/AI credit whatsoever (e.g.
Co-Authored-By: Claude ..., "Generated with Claude Code", "🤖 Generated with ...").
- Commits and tag release notes MUST contain zero AI/assistant attribution.
Anti-patterns
- Using
update or change as type (not in the spec)
- Past tense in description: "added feature" instead of "add feature"
- Overly vague descriptions: "fix bug", "update code", "misc changes"
- Mixing unrelated changes in one commit (split them)
- Omitting BREAKING CHANGE when a public API changes
- Using commit body to repeat what the diff already shows
- Adding
Co-Authored-By: or any LLM/AI credit trailer (strictly forbidden)
Examples
feat(auth): add JWT token refresh endpoint
fix: prevent null pointer on empty user profile
refactor(db)!: change connection pool from sync to async
BREAKING CHANGE: NewPool() now returns (Pool, error) instead of Pool
docs: update API reference for v2 endpoints
chore(deps): bump go.mod dependencies to latest
feat: add CSV export for reports
Closes #142
Verification
Before committing:
1---2name: conventional-commit3description: Conventional Commits4---5# Conventional Commits67Write commit messages that strictly follow the Conventional Commits 1.0.0 specification: analyze the staged changes and produce a structured, meaningful message.89## When to use1011- Committing code changes or preparing a pull request12- Generating or maintaining a changelog13- Any git commit operation where the message must follow Conventional Commits14- Determining the semantic impact of a change — commit types feed version bumps, see [[semantic-versioning]]1516## Commit format1718The strict format from the specification:1920```21<type>[optional scope]: <description>2223[optional body]2425[optional footer(s)]26```2728Rules:2930- `type` is required: a noun describing the category of change31- `scope` is optional: a noun in parentheses describing the section of the codebase32- `!` before the colon signals a BREAKING CHANGE33- `description` is required: short summary in imperative mood, lowercase, no period34- `body` is optional: free-form, one blank line after description35- `footer(s)` are optional: one blank line after body, format `token: value` or `token #value`3637## Commit types3839Ordered by semantic impact (the type drives the version bump — see [[semantic-versioning]]):4041- **feat**: new feature (triggers MINOR version bump in SemVer)42- **fix**: bug fix (triggers PATCH version bump in SemVer)43- **docs**: documentation only44- **style**: formatting, semicolons, whitespace (no logic change)45- **refactor**: code restructuring (no behavior change)46- **perf**: performance improvement47- **test**: adding or correcting tests48- **build**: build system or external dependencies49- **ci**: CI/CD configuration50- **chore**: maintenance tasks (deps update, config)5152## Breaking Changes5354Two valid ways to signal breaking changes (triggers MAJOR version bump):55561. Append `!` after type/scope: `feat!: remove deprecated endpoint`572. Footer: `BREAKING CHANGE: removed /v1/users endpoint`58 Both can be combined. Breaking changes can apply to any type.5960## Scope Guidelines6162- Use the module, package, or component name: `feat(auth):`, `fix(parser):`63- Keep scopes consistent within a project64- Omit scope when the change is cross-cutting6566## Analysis process67681. Read the staged diff (`git diff --staged`)692. Identify the primary intent: is this a feature, fix, refactor, etc.?703. Determine scope from the files/modules changed714. Check if this introduces breaking changes to a public API725. Write the description in imperative mood ("add", not "added" or "adds")736. Add body if the diff is non-trivial or the "why" is not obvious747. Add footer for breaking changes or issue references7576## Forbidden footers (STRICT)7778NEVER add author-attribution trailers to a commit message or to tag release notes:7980- No `Co-Authored-By:` trailers of any kind.81- No LLM/AI credit whatsoever (e.g. `Co-Authored-By: Claude ...`, "Generated with Claude Code", "🤖 Generated with ...").82- Commits and tag release notes MUST contain zero AI/assistant attribution.8384## Anti-patterns8586- Using `update` or `change` as type (not in the spec)87- Past tense in description: "added feature" instead of "add feature"88- Overly vague descriptions: "fix bug", "update code", "misc changes"89- Mixing unrelated changes in one commit (split them)90- Omitting BREAKING CHANGE when a public API changes91- Using commit body to repeat what the diff already shows92- Adding `Co-Authored-By:` or any LLM/AI credit trailer (strictly forbidden)9394## Examples9596```97feat(auth): add JWT token refresh endpoint9899fix: prevent null pointer on empty user profile100101refactor(db)!: change connection pool from sync to async102103BREAKING CHANGE: NewPool() now returns (Pool, error) instead of Pool104105docs: update API reference for v2 endpoints106107chore(deps): bump go.mod dependencies to latest108109feat: add CSV export for reports110111Closes #142112```113114## Verification115116Before committing:117118- [ ] Type is one of the standard types119- [ ] Description is imperative mood, lowercase, no trailing period120- [ ] Scope (if used) matches an existing module/package121- [ ] Breaking changes are marked with `!` AND/OR `BREAKING CHANGE:` footer122- [ ] Body explains "why" (not "what" — the diff shows "what")123- [ ] Each commit is atomic (one logical change)124- [ ] No `Co-Authored-By:` / LLM / AI attribution trailer is present