Conventional Commits turn messages into a build artifact
Free-form commit messages are fine for two-person repos. At team scale, every commit becomes input to release tooling, changelogs, and triage. Conventional Commits is the format that lets that tooling work without a human curator: a small grammar in the subject line that release bots and changelog generators can parse mechanically. The cost is one prefix per commit; the benefit is automated changelog and version bump.
The grammar: <type>(<optional scope>): <subject>. Types include feat, fix, docs, style, refactor, perf, test, chore, build, ci, revert. Scope is an optional area marker like feat(auth): or fix(api):. Add ! after type/scope to mark a breaking change: feat(api)!: drop /v1/legacy. The body and footer follow the same rules as any good commit message — explain why, reference issues, note breaking-change details.
Once enforced, the format unlocks semantic-release and conventional-changelog tooling. Each push to main can compute the next version automatically: any fix: bumps patch, any feat: bumps minor, any !/BREAKING CHANGE: bumps major. The same tools generate a categorized CHANGELOG.md from the commit messages — features, fixes, breaking changes — without anyone hand-writing release notes. Releases become a side effect of merge.
Enforcement is where the format becomes durable. commitlint as a Git hook rejects malformed messages locally; commitizen provides an interactive prompt that builds correctly-formatted messages; GitHub Action checks reject malformed PRs. None of this works if the team treats the format as guidance and breaks it casually. Treat it as code: malformed = rejected, like any lint failure.