Skip to content

Conventional Commits & categorisation

changelog is opinionated about one thing: commit messages follow the Conventional Commits specification. That convention is what lets it turn a flat list of commits into a structured, categorised changelog without any manual curation. This page explains how the mapping works and why.

From commit type to category

Each commit is parsed with the leodido/go-conventionalcommits parser (in best-effort mode, so a non-conforming message degrades gracefully instead of failing the run). The commit type selects a Category:

Commit type Category
feat CategoryFeature
fix CategoryFix
perf CategoryPerformance
anything else (refactor, docs, chore, build, …) CategoryOther
test, ci dropped

test and ci are dropped deliberately: they describe changes to the project's own scaffolding, not to the thing being released, so they add noise to a user-facing changelog.

The commit scope (feat(cli):) and description are carried onto the Entry as Scope and Description; the raw subject line is preserved in Raw.

Detecting breaking changes

A change is breaking if either signal is present:

  • the ! marker on the type — feat!: or feat(api)!:; or
  • a BREAKING CHANGE: footer (its hyphenated synonym BREAKING-CHANGE: is accepted too).

Breaking entries are surfaced through HasBreakingChanges() and BreakingChanges(), and FormatSummary lists them first under a warning banner — a breaking change is the one thing a reader must not miss.

Why releases are semver-ordered

Tags are validated and ordered with golang.org/x/mod/semver rather than by commit date or tag-creation order. Semver ordering is the correct model for a changelog: v1.10.0 comes after v1.9.0 even though "10" sorts before "9" lexically, and re-tagging or importing history out of order does not scramble the result. A tag that is not valid semver is skipped for ordering.

Commits newer than the most recent tag form an unreleased section at the top, so a changelog generated mid-cycle reflects work already merged but not yet tagged.

Generation vs parsing use the same model

Both directions produce or consume the same ChangelogReleaseEntry structure. Generation classifies commits into it; parsing reconstructs it from existing markdown. That symmetry is why a changelog this package generated can be parsed back and queried with the same category helpers — the categories are the stable interface, not the markdown text.