C.W.K.
Stream
Lesson 02 of 04 · published

Conventional Commits and Release Notes

~18 min · commits, automation

Level 0Untracked Rookie
0 XP0/47 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

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.

Code

Format catalogue·text
feat: add OAuth2 GitHub login
feat(auth): add OAuth2 GitHub login            # with scope
fix(api): handle empty array in /search response
docs(readme): document OAUTH_ENABLED flag
refactor(store): replace ad-hoc cache with TTL map
test(api): add property tests for /search edge cases
chore(deps): bump fastapi to 0.115.0
perf(query): index conversations.created_at
revert: "feat(auth): add OAuth2 GitHub login"

# Breaking change marker:
feat(api)!: drop /v1/legacy-search endpoint

# Or with footer:
feat(api): replace /v1/search with /v2/search

BREAKING CHANGE: /v1/search endpoint is removed.
Migrate to /v2/search; response schema changes are
documented in docs/api/v2-migration.md.
Enforce locally and in CI·bash
# commitlint as a Git hook (Husky):
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
npx husky init
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg

# commitizen for interactive correct messages:
npm install --save-dev commitizen cz-conventional-changelog
echo '{"path": "cz-conventional-changelog"}' > .czrc
git cz                       # use this instead of git commit

# GitHub Action to validate PR titles + commits:
# .github/workflows/commitlint.yml — uses wagoid/commitlint-github-action

External links

Exercise

On any repo, draft Conventional Commits messages for your three most recent commits. Then install commitlint + Husky in a scratch repo and try to commit with a malformed message. Read the rejection. Fix and commit. Note in writing one place where Conventional Commits would have helped a release on a real project you work on.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.