Key Workflows
Day-to-day development
Add a change interactively
Run add without arguments to get a guided prompt:
changelogmanager add
You will be asked to choose a change type and type your message, then confirm before the file is written.
Add a change non-interactively
Suitable for scripts and CI:
changelogmanager add --change-type fixed --message "Prevent crash on empty input"
Valid change types are: added, changed, deprecated, removed, fixed, security.
Preview without writing
Every command accepts --dry-run. It runs all validation and prints what would happen, but does not modify any files:
changelogmanager add --change-type added --message "New feature" --dry-run
changelogmanager release --dry-run
Maintaining [Unreleased]
List entries before editing
changelogmanager remove --list
This prints each [Unreleased] entry as [change-type] index: message, which is the index format used by both edit and remove.
Edit an existing entry
Update the text in place:
changelogmanager edit --change-type added --index 0 --message "Document the non-interactive release flow"
Recategorise an entry:
changelogmanager edit --change-type changed --index 1 --new-change-type fixed
You can combine --message and --new-change-type in the same command.
Remove an entry
changelogmanager remove --change-type fixed --index 0
If removing the last entry in a section empties that change type, the section is removed automatically.
Seed entries from commit history
changelogmanager from-commits
By default the command starts from the most recent git tag, then parses commit subjects using Conventional Commit-style prefixes:
| Commit prefix | Changelog bucket |
|---|---|
feat, feature |
added |
fix, bug |
fixed |
deprecate |
deprecated |
remove |
removed |
security, sec |
security |
docs, refactor, test, chore, etc. |
changed |
Breaking commits like feat!: are treated as removed, which produces a major version bump.
Useful variants:
changelogmanager from-commits --since v1.2.0
changelogmanager from-commits --all-history
changelogmanager from-commits --strict
--strict skips subjects that do not match the selected commit schema. Without it, unmatched subjects are added as
changed.
Backfilling historical releases
Backfill missing version sections from local history
changelogmanager backfill --source all --dry-run
changelogmanager backfill --source all
changelogmanager backfill --source tags --dry-run
changelogmanager backfill --source tags
This is aimed at repositories that already have release tags but either no CHANGELOG.md yet or gaps in the released sections. The command discovers local git tags, normalizes a leading v, filters them through the changelog's active versioning scheme, and adds only versions that are missing. With --source all or --source commits, it also reads commit subjects between tag intervals before falling back to tag placeholders.
Commit parsing supports --commit-schema auto, conventional, gitmoji, and keepachangelog. Auto tries all built-in schemas, so subjects like feat: add export, :bug: fix parser, and Fixed: restore ordering can all become typed changelog entries.
For each imported version, the tool uses an intentionally honest placeholder:
### Changed
- Release notes unavailable; backfilled from tag `v1.2.3`.
That keeps the generated changelog valid without inventing release notes.
Limit the range
changelogmanager backfill --source tags --since v1.0.0 --until v2.0.0
--since and --until accept either the exact tag name or the normalized version string.
What happens today
--source tagsis the implemented path--source commitsand--source allare local-only and use commits grouped by tag interval- existing versions are skipped by default via
--missing-only --strategy merge --no-missing-onlyadditively backfills entries into existing versions while preserving their text; it is idempotent on re-runs--include-unreleasedseeds[Unreleased]from commits since the latest release tag- non-version tags are reported and skipped
--strategy replaceand the remote backfill sources (github-releases,github-prs,pypi) are not implemented;replaceis intentionally unsupported because changelog entries have no stable identity
Releasing
Where your version number lives — an important design choice
Before using release, decide where your project's authoritative version number lives.
This choice changes which release workflow you need.
Option A — changelog is the single source of truth.
pyproject.toml (and any __version__ string in your source) is never checked by your
build tool, or your build tool is told to read the version dynamically from the installed
package metadata. In this case the release command alone is sufficient: it promotes
[Unreleased] to a version entry, and nothing else needs to change before you build.
Option B — version appears in multiple places.
Your pyproject.toml has version = "x.y.z" and/or your source has __version__ = "x.y.z".
These must match the released version before you run uv build or the package will carry
the wrong version number. You have two sub-options:
- Guess the version first: compute the next version yourself (e.g. from
changelogmanager version --reference future), set it inpyproject.tomlmanually, push that change, then trigger the release job. This is error-prone — if your guess is wrong, or a new commit changes the bump level between your edit and the job running, the versions drift. - Use
--bump-versions: let thereleasecommand bumppyproject.toml(and Python source__version__strings) to the released version atomically, in the same step. This is the recommended approach when you need version strings in multiple places. See Syncing version strings with--bump-versionsbelow.
Automatic version bump
release inspects the change types in [Unreleased] and bumps the version according to the configured scheme (semver, pep440, or calver):
| Change type present | Bump |
|---|---|
removed |
Major |
added or security |
Minor |
changed, deprecated, fixed only |
Patch/micro |
changelogmanager release
Override the version
changelogmanager release --override-version 2.0.0
The v prefix is accepted and stripped automatically (v2.0.0 becomes 2.0.0).
Non-interactive releases
For scripts, CI, or any non-interactive run, add --yes:
changelogmanager release --yes
Without --yes, non-interactive release runs are refused. Pair it with --dry-run first if you want a preview.
Syncing version strings with --bump-versions
If your project stores a version number outside the changelog — in pyproject.toml,
in a __version__ variable, or both — use --bump-versions to keep them in sync.
Prerequisite: install the jiggle optional extra:
# uv project dependency
uv add "keepachangelog-manager-fork[jiggle]"
# or standalone tool install
uv tool install "keepachangelog-manager-fork[jiggle]"
# or pip
pip install "keepachangelog-manager-fork[jiggle]"
Release and sync in one step:
changelogmanager release --bump-versions --yes
This does, in order:
- Promotes
[Unreleased]to the inferred next version inCHANGELOG.md. - Writes that same version string into
[project] versioninpyproject.toml. - Updates any
__version__ = "..."variables found in your Python source tree.
Limit to pyproject.toml only (skip Python source files):
changelogmanager release --bump-versions --pyproject-only --yes
Preview without writing anything:
changelogmanager release --bump-versions --dry-run
JSON output includes the list of modified files:
changelogmanager --json release --bump-versions --yes
{
"released": "1.3.0",
"bumped_version": "1.3.0",
"bumped_files": ["pyproject.toml", "mypackage/__about__.py"]
}
The typical build sequence after running --bump-versions is:
changelogmanager release --bump-versions --yes
uv build
uv publish
Querying versions
# most recently released version
changelogmanager version
# the version before that
changelogmanager version --reference previous
# what the next release would be, based on Unreleased changes
changelogmanager version --reference future
Validation
Basic validation
changelogmanager validate
The validator checks:
- Heading depth (maximum 3 levels)
- Version headings follow
## [version] - yyyy-mm-dd, whereversionmatches the configured scheme - Change headings are one of the six allowed types
- Entries do not use sub-lists, numbered lists, or block quotes
- Versions are in descending order
[Unreleased]is at the top
Warnings are also reported for:
- Empty version sections
- Empty change-type sections
- Duplicate entries within the same change-type section
Autofix common issues
changelogmanager validate --fix
This can:
- repair safe layout issues before parsing, such as
## Unreleased,## Added, miscased or near-miss change headings, simple entry wrappers, a leadingvin release headings, and ISO date separator variants - reorder released versions into descending configured-version order
- lowercase change-type headings such as
Added->added - remove empty change-type sections
- deduplicate identical entries within a section
Validate all configured components
changelogmanager --config changelogmanager.toml validate --all
changelogmanager --config changelogmanager.toml validate --all --changed-only
--changed-only uses git status --porcelain and skips configured components whose changelog files are unchanged.
Initialize or update config interactively
changelogmanager config
changelogmanager config init
config shows the effective config plus where it came from. config init writes changelogmanager.toml or
pyproject.toml using interactive prompts, defaulting to pyproject.toml and semver. Re-running it updates the
active config with the current answers.
Export the bundled CLI skill
changelogmanager skill export
changelogmanager skill export --path .github/skills
Without --path, the CLI prompts for a common Copilot or Claude skills location and writes the keepachangelog-manager-cli folder there.
Enforce the canonical preamble
You can require the standard Keep a Changelog preamble from configuration:
[versioning]
scheme = "semver"
[validation]
enforce_preamble = true
If versioning.scheme is set to pep440 or calver, create writes that scheme into the changelog preamble and validation expects the same wording.
GitHub Actions format
changelogmanager --error-format github validate
Errors are printed in GitHub Actions annotation format (::error file=...), making them appear inline in pull request diffs.
GitHub releases
Create a draft release
changelogmanager github-release \
--repository owner/repo
This deletes any existing draft releases and creates a new draft from the [Unreleased] section. The release tag is set to the inferred future version.
If --github-token is omitted, the command falls back to the GITHUB_TOKEN environment variable.
Publish the release immediately
changelogmanager github-release \
--github-token "$GITHUB_TOKEN" \
--repository owner/repo \
--release
Typical CI pattern
- name: Create GitHub release
run: |
changelogmanager github-release \
--repository "${{ github.repository }}" \
--release
Run github-release while [Unreleased] still exists. If you also want to rewrite CHANGELOG.md, do that in a later step or workflow with changelogmanager release --override-version "$TAG" after the GitHub release tag is known.
GitLab releases
changelogmanager gitlab-release --project group/project
GitLab has no draft-release state, so the command is an upsert: it updates the release if the computed tag already exists, otherwise it creates it and lets GitLab create the tag from --ref.
Useful variants:
changelogmanager gitlab-release --project group/project --ref "$CI_COMMIT_SHA"
changelogmanager gitlab-release --project group/project --gitlab-url https://gitlab.example.com
The command looks for credentials in --gitlab-token, then GITLAB_TOKEN, then CI_JOB_TOKEN. In practice the default CI_JOB_TOKEN usually cannot create releases, so CI jobs should normally supply a project/group/personal access token via GITLAB_TOKEN.
See GitLab automation for a concrete .gitlab-ci.yml example.
GitHub release PR automation
changelogmanager github-pr \
--repository owner/repo \
--head release/bump-123 \
--base main \
--title "chore: release 1.2.3"
This opens a pull request for a release branch, or updates the title/body if the PR already exists. It is mainly intended for GitHub Actions workflows that cut a release branch and then want an auditable PR back to the target branch.
See GitHub automation for the repository's end-to-end release workflow.
Exports
changelogmanager to-json
changelogmanager to-json --schema-version v1
changelogmanager to-html
Default output files:
| Command | Default output |
|---|---|
to-json |
CHANGELOG.json |
to-html |
CHANGELOG.html |
to-json writes one object per release. Example output:
[
{
"metadata": {
"version": "1.2.0",
"release_date": "2024-05-01",
"semantic_version": {
"major": 1,
"minor": 2,
"patch": 0,
"prerelease": null,
"buildmetadata": null
}
},
"added": [
"New export command"
],
"fixed": [
"Handle missing release date gracefully"
]
}
]
Use a custom filename:
changelogmanager to-json --file-name changelog-export.json
changelogmanager to-html --file-name changelog-export.html
to-json also accepts --schema-version so automation can pin the expected export contract.
Multi-component repositories
When a single repository contains multiple packages, each with its own CHANGELOG.md, create a configuration file:
[versioning]
scheme = "pep440"
[[components]]
name = "Service Component"
changelog = "service/CHANGELOG.md"
match = ["service/**"]
[[components]]
name = "Client Interface"
changelog = "client/CHANGELOG.md"
match = ["client/**"]
[[components]]
name = "default"
changelog = "CHANGELOG.md"
Then pass --config and --component to any command:
changelogmanager --config changelogmanager.toml --component "Client Interface" version
changelogmanager --config changelogmanager.toml --component "Service Component" release
from-commits --all uses each component's optional match globs to route commits by touched files. A component with
no match acts as the fallback bucket for commits that do not match any explicit component.
If --config is omitted, the CLI auto-detects changelogmanager.toml, .changelogmanager.toml, or
[tool.changelogmanager] in pyproject.toml from the current directory.
Specifying a changelog file directly
If you do not use a config file, you can point at any file with --input-file:
changelogmanager --input-file packages/api/CHANGELOG.md validate
Automation-friendly output
Suppress human-friendly output:
changelogmanager --quiet validate
Emit a single JSON object to stdout:
changelogmanager --json version --reference future
changelogmanager --json remove --list
--json is useful for CI or wrapper scripts that need structured output. For destructive non-interactive workflows such as release, combine it with the command's explicit confirmation flags, for example changelogmanager --json release --yes.