Conventional Commits and SemVer
Conventional Commits is a commit-message format that machines can parse: a type(scope): description first line plus optional footers. Semantic Versioning is a numbering contract, MAJOR.MINOR.PATCH, that tells consumers what a version bump means. Together they let tooling derive the next version and the changelog directly from your commit history.
The payoff is that releases stop being a manual judgment call. Once every commit declares its kind, a tool can read the log, decide whether the change is a patch, a feature, or a break, tag the version, and write the notes — no human deciding "is this a minor or a major?"
The Conventional Commits Format
The first line is a type, an optional scope in parentheses, and a short description. Common types are feat, fix, chore, and docs. A breaking change is flagged with a ! after the type or a BREAKING CHANGE: footer in the body.
feat(auth): add OAuth login BREAKING CHANGE: removes the legacy token endpoint
The structure is the whole point — a parser can read the type and the breaking-change marker without understanding the prose, which is what makes the downstream automation possible.
Semantic Versioning Rules
SemVer maps change kinds to version components: a fix bumps the patch number, a feat bumps the minor, and a breaking change bumps the major. A major bump is a promise to consumers that something they depend on has changed in an incompatible way.
That promise runs both directions. Bumping major when nothing broke tells everyone to brace for a migration that does not exist; leaving it at minor when something did break sends the break out silently.
Deriving Versions Automatically
Because the mapping is mechanical, tooling can compute the next version from the commits since the last release: any feat means at least a minor, any breaking change means a major, otherwise a patch. The one caveat is 0.x — under SemVer, anything below 1.0.0 is allowed to break in a minor bump, so the usual guarantees do not yet apply.
Automated Changelogs and Releases
Tools like semantic-release and release-please read the commit history, decide the version, tag it, and generate release notes grouped by type. The changelog becomes a byproduct of how you already commit, not a document someone maintains by hand and forgets to update.
Enforcement
None of this works if half the team forgets the format. A commitlint check in a commit-msg hook, or a required PR-title check, makes the convention non-optional — malformed messages are rejected before they reach main, so the automation never silently skips a change.
- Marking a breaking change as
featwithout!or aBREAKING CHANGE:footer, so automation cuts a minor release and consumers' caret ranges silently pull the break. - Bumping the major version for a bugfix "to feel safe", which tells every consumer to expect breaking changes that do not exist.
- Treating
0.xas stable, when under SemVer anything below1.0.0can break in a minor bump. - Squash-merging without curating the squash message, so the auto-generated changelog inherits "fix stuff" from the WIP commits.
- Adopting Conventional Commits with no lint check, so half the team forgets the format and automated versioning skips their changes.
- Enforce Conventional Commits with
commitlintin acommit-msghook or a required PR-title check. - Automate versioning and changelogs with
semantic-releaseorrelease-pleasedriven off commit types. - Mark breaking changes explicitly with
!and aBREAKING CHANGE:footer so automation cuts a major. - Curate the squash-merge commit message into a valid Conventional Commit so the changelog stays accurate.
- Reach
1.0.0before promising stability, and treat every0.xminor as potentially breaking.
Knowledge Check
Under SemVer, which component does a feat commit bump?
- The minor version — a new backward-compatible feature
- The patch version, treated the same as a
fixsince both ship small incremental changes - The major version, because any new code added to the public surface counts as a breaking change
- None — features never change the version number
You ship a breaking change labeled feat with no ! or footer. What happens to consumers on caret ranges?
- Automation cuts a minor release, and their
^ranges silently pull in the breaking change - Their build refuses to install the new version and pins them to the last release before the break
- A major version is cut automatically regardless of the label, because the tool detects the break itself
- Nothing — caret ranges never upgrade across any change, so consumers stay on the version they first locked
Why is a 0.x release not considered stable under SemVer?
- Below
1.0.0, even a minor bump is allowed to break, so the normal compatibility guarantees do not apply - Because
0.xversions cannot be published to a public registry until they first reach the1.0.0milestone - Because
0.xonly allows patch releases, never minor ones, until the major is manually promoted to1.0.0 - It is stable — the leading
0just signals the package is free to use rather than commercial or paid
How does an uncurated squash-merge message hurt an automated changelog?
- The changelog inherits the squash commit's message, so noise like "fix stuff" from WIP commits ends up in the release notes
- It causes the released version to skip a number, leaving a visible gap in the published version sequence
- It deletes the previous changelog entirely and regenerates it from scratch using only the latest squash subject line as input
- It has no effect, since changelog tools read the diff and the changed file names rather than the commit message subject text
You got correct