Skip to main content

Commit & changelog conventions

CHANGELOG.md is generated from git history at release time, never hand-edited on a branch. Editing it on every branch is what produced the endless rebase and merge conflicts; generating it from commit metadata removes them. Two things drive the generator, and both are enforced on every merge request:

  1. the module prefix — which selects the changelog marker, and
  2. an optional Changelog: trailer — which supplies the bullet text.

Commit subjects

Every commit subject is module: summary with a bare module prefix — the name of a repo subsystem, never a change-type (there is no feat:/fix:) and never the parenthesized module(scope): form. The prefix is not decoration: the generator routes each module to a changelog marker, so an unlisted or mistyped prefix (e.g. serer: for server:) would silently become a user-facing changelog line. make commit-subjects.check rejects anything outside the allowed set.

The full list — each module with a one-line description — is the single source of truth in scripts/commit-prefixes.txt, which is exactly what the check script reads. The module decides the changelog marker:

  • The product modulesserver, templates, pdfrender, services, config — render as a plain bullet (user-facing).
  • tester (the React UI) renders as [tester].
  • The internal modulesweb, examples, ci, build, devenv, e2e — render as INTERNAL.
  • release seals a version and is skipped by the generator.

Merge commits are exempt (their subjects are auto-generated).

Recording a changelog entry

A commit contributes a bullet only if it carries a Changelog: git trailer. Put the terse bullet in the trailer; keep rationale and detail in the body, which never reaches the changelog:

ci: content-address the base images

Free-form rationale and detail live here, in the body. None of it appears in
the changelog.

Changelog: Content-address CI base images so they rebuild only when inputs change

The commit's prefix picks the marker, so the trailer above renders as:

- **INTERNAL** Content-address CI base images so they rebuild only when inputs change
  • Keep the trailer terse — it is the changelog line, read at a glance.
  • Purely mechanical commits (typo fixes, formatting) need no trailer.
  • For a change that is genuinely note-worthy but should produce no bullet, add a Changelog: skip trailer. That is the only opt-out — a conscious, in-history acknowledgment, not a hidden label. make changelog.check fails a merge request in which no commit carries a real Changelog: trailer and none carries Changelog: skip.

One feature split across several commits usually wants one bullet: put the trailer on the commit that best represents the change, and leave the supporting commits (tests, CI wiring, docs) trailer-less.

Running the checks locally

Both gates default to the master..HEAD range, exactly as CI runs them on a merge request:

make commit-subjects.check # every subject uses an allowed bare prefix
make changelog.check # the change records at least one Changelog: trailer
make changelog.preview # preview the bullets the next release will contain

Point the range at a different base with MR_BASE=<ref> (both scripts also honor GitLab's CI_MERGE_REQUEST_DIFF_BASE_SHA when it is set).

Cutting a release

Releasing stays manual; only the changelog step is automated:

make changelog.preview # review what will be included
make changelog VERSION=vX.Y.Z # prepend the generated release section

Review the diff, merge to master, then tag vX.Y.Z on master and push — the tag drives the release pipeline, as before.

Where the pieces live

FileRole
scripts/commit-prefixes.txtSource of truth for the allowed module prefixes
scripts/commit-subject-check.shSubject-prefix gate (make commit-subjects.check)
scripts/changelog-check.shChangelog: trailer gate (make changelog.check)
cliff.tomlgit-cliff config: marker rules + the CHANGELOG.md template
AGENTS.mdThe short version of these rules for coding agents