--- name: changelog-release description: The user's release methodology - how CHANGELOG.md (or debian/changelog) is written, how its newest untagged entry collects every change, and how release.mjs / release.py commit, tag and push from it. Use this skill before EVERY edit of a CHANGELOG.md or debian/changelog, whenever a change is finished and has to be recorded, when a version has to be chosen (patch or minor), when the user mentions a release, a tag, a version bump, release.mjs or release.py, or when a repository needs a changelog and a release script set up - even for a one-line addition to a changelog. --- # Changelog and release The user releases with a release script (`release.mjs` or `release.py`) that reads the newest changelog entry, commits everything with the entry's summary line as the message, tags the commit with the entry's version and pushes. The changelog is therefore both the history of the project and the source of every commit message and tag. Getting the entry right is what makes a release a one-command step for the user. ## The rule that matters most: check the tag and the commit before every changelog edit The user commits, tags and pushes between your edits, often without telling you. An entry whose version is tagged is released: changing it rewrites history that is already published, and the next release fails because the tag exists. This mistake has happened many times and the user finds it very annoying, so treat the check as part of the edit itself, not as a once-per-session step. Right before each edit of the changelog, also for a one-line addition, run: ```sh git tag --sort=-v:refname | head -5 git log --oneline -3 git status --short ``` Then decide: - **The newest entry's version is not in the tags**: the entry is open. Add your items to it, and raise its version if your change needs a bigger bump (a feature in an open patch entry makes it a minor entry; keep one entry per release). - **The newest entry's version is tagged**: it is released. Add a new entry above it with the next version and your items. Never change the tagged entry, not even its wording. - **The newest commit is a release commit** (its message is the summary line of the newest entry): the user has just released; the same holds as for a tagged entry. A check made earlier in the session does not count, because a release may have happened since. ## CHANGELOG.md format ```markdown # Changelog ## 1.4.1 fix: the export page saves the chosen date range instead of the default one. - The date range of the export page is kept when the export starts. - The file name of an export names the date range. ## 1.4.0 feature: the reports are exported as CSV files from the export page. - ... ``` - `# Changelog` once at the top; entries newest first. - An entry is a `## x.y.z` heading, a blank line, one summary line, a blank line, and the changes as `-` items. The blank lines around the summary are what the release script looks for. - **The summary line is the commit message.** It starts with a type prefix as in the git history, `feature:`, `fix:` or `update:` (check `git log --oneline` of the repository for the prefixes it uses), then one sentence. It must not start with `-` or `#`, or the script finds no summary. When an entry collects several changes, rewrite the summary so it covers all of them. - **Items describe what changed for the user** of the software: behaviour, options, paths, pages, commands. Not the reasoning and not the implementation story. Reasons and design decisions go to `ADR.md`, which describes only the decisions behind the current code; the changelog is the only place for history. - Wrap items at about 100 columns, continuation lines indented by two spaces. Separate the list from the summary line with a blank line. Plain ASCII only. ### Versions - Patch (`x.y.Z`) for fixes, minor (`x.Y.0`) for features; the user decides about major versions. - `x.y.z-n`, `n` from 2, is a later release of the same code with changes that are not code changes (documentation, packaging); `release.mjs` accepts it. - Every change you make goes into the newest untagged entry; when there is none, add one. A repository's `CLAUDE.md` may add rules; follow them. ## debian/changelog format (repositories that build a Debian package) Some repositories keep the changelog in `debian-package/debian/changelog` and release with `release.py`. The same rules apply in Debian's format: ```text example-app (1.4.0-1) unstable; urgency=medium feature: the service reads and changes its settings at /settings. * GET and PUT /settings show and change the settings; a password is never shown, and an empty one keeps it. * The service starts after the database is reachable. -- Maintainer Name Mon, 02 Mar 2026 10:00:00 +0100 ``` - The summary is the first text line, indented by exactly two spaces, between blank lines, not a `*` item. Items are ` * ` with continuation lines indented by four spaces. - The tag is the version without the Debian revision `1`: `(1.4.0-1)` is tagged `1.4.0`, `(1.4.0-2)` is tagged `1.4.0-2`. - The trailer line ` -- name date` follows Debian policy (`date -R` format); keep the maintainer of the previous entries. ## Releasing The user runs the release. Committing, tagging and pushing are the user's decisions, so: - Do not run a release script without `-n` (`--dry-run`), which prints the git commands instead of running them. Use the dry run to show the user what a release would do. - Do not commit, tag or push on your own; only when the user asks. - The script refuses when the tag exists ("add a new CHANGELOG.md entry first"); that means an entry was released and the next change needs a new entry. What the scripts do, in order: read the newest entry's version and summary, refuse when the tag exists, `git add -A`, `git commit -m `, `git tag `, and `git push origin main --tags` unless `--no-push`. `-m` overrides the message. ## Setting up a new repository When a repository needs this setup, copy the script that fits its language and changelog: - `scripts/release.mjs` - Node.js, `CHANGELOG.md` in the repository root. - `scripts/release.py` - Python, `debian-package/debian/changelog`; change `CHANGELOG` for another path. Make it executable, add a `## Releasing` section to the README describing the commands (`./release.mjs`, `-m`, `-n`, `--no-push`), and add a repository `CLAUDE.md` section with the changelog rules above when the user wants one.