Parse, query, and validate changelogs in Python and CI.
- reStructuredText changelog parser (
patchnotes.parse(text, format="rst"), or automatic for.rstfiles). Learns the document's own heading hierarchy from section adornments, skips directives and comments, and reduces inline rST markup to plain text. Issue codes match the markdown parser's, so quality is comparable across formats. research/: an ecosystem scan over the 500 most-downloaded PyPI packages, measuring how many keep a changelog a machine can read. Results and method inresearch/FINDINGS.md.
.rstfiles now parse with the reStructuredText parser. Previously the extension matched no format and fell through to the markdown parser, which recovered setext-underlined headers but little else — soparse_file("CHANGES.rst")may now return more releases, different entries, and different issue codes than it did in 2.5.x. Setext-underlined markdown is unaffected: the two shapes are ambiguous, so rST is auto-detected only on content markdown cannot produce.- CI now dogfoods the badge:
ci.ymlpublishes patchnotes' own changelog badge to thegh-pagesbranch on every push to main, and skips publishing on pull requests so fork PRs don't need a write token. - The sdist no longer ships
research/,examples/, or.github/. The wheel was already scoped to the package; the sdist was picking up everything untracked by.gitignore, including ~220 KB of ecosystem-scan data.
__version__now matches the released version. It was left at2.5.0when 2.5.1 shipped, sopatchnotes --versionandpatchnotes.__version__under-reported by a patch release. Thecheck-versioncommand compares the changelog againstpyproject.toml, which was correct, so nothing caught it —patchnotes/__init__.pyis now part of the release checklist.
- Version ordering now follows the precedence rules in the Semantic Versioning specification (section 11). Pre-release suffixes were previously discarded, so
1.0.0-rc.1and1.0.0compared equal andlatest()could return the release candidate instead of the final release. Pre-release identifiers now compare field by field, numeric identifiers order below alphanumeric ones, and build metadata is ignored for precedence. - Corrected the release dates recorded for 1.0.0 and 1.1.0, which predated the repository's own first commit. Both were published on 2026-04-23.
- The source now passes
mypy --strict, whichpyproject.tomlhad declared but the code did not satisfy. The check is clean whether or not the optionalmkdocsextra is installed. Annotation-only change; no runtime behaviour differs. - Corrected the "zero dependencies" claim in the README: PyYAML has been a required dependency since 2.0.1.
- Citation metadata (
CITATION.cff,.zenodo.json) and a contributor guide (CONTRIBUTING.md), so releases are archived to Zenodo with a citable DOI.
- The
badgecommand now reports validation state as well as version: green for a clean parse, yellow with a warning count, red for errors, grey when nothing is released yet.--labelsets the left-hand text and--no-versionreports state alone. - GitHub Action can publish the badge JSON itself —
badge: gist(any repo, needsbadge-gist-idand abadge-token) orbadge: gh-pages(commits to a branch using the built-in token). Newbadge-jsonoutput exposes the generated JSON.
badgeis now exempt from the global--strictguard and always exits0, so a broken changelog produces a red badge instead of aborting the step. Under--strictit treats warnings as invalid.
patchnotes initscaffolds a spec-compliant starter CHANGELOG.md (strict-validation clean out of the box);--workflowalso writes a ready-made.github/workflows/changelog.ymlPR check.dep --requirements old.txt new.txtdiffs two requirements files and runs the breaking/security analysis for every changed pin at once — built for reviewing lockfile bump PRs. With--strict, flagged changes fail CI.- mkdocs plugin: add
patchnotestoplugins:in mkdocs.yml and a<!-- patchnotes -->marker in any docs page renders the styled changelog at build time (pip install patchnotes[mkdocs]).
- An empty [Unreleased] section no longer triggers a PN203 warning — it's the normal state right after a release (and what
bumpleaves behind). Empty versioned releases still warn.
- CI workflow (
.github/workflows/ci.yml) that dogfoods patchnotes: the repo's own changelog is strict-validated by the bundled action (uses: ./installed from source), version-synced againstpyproject.toml, and uploaded as SARIF to GitHub code scanning on every push.
- The publish workflow now uses the bundled action for validation, tag/changelog version sync, and GitHub Release creation with changelog notes — every release exercises the full feature set.
check-versioncommand: fails CI when the latest changelog version doesn't matchpyproject.toml,package.json,Cargo.toml, or a literal version/tag (--against "$GITHUB_REF_NAME"). Auto-discovers the metadata file next to the changelog.- Changelog fragments:
patchnotes fragment add fixed "..."writes a snippet tochangelog.d/, ending [Unreleased] merge conflicts.fragment listshows pending entries;bump --collectfolds them into the release and deletes the files;unreleased --fail-if-empty --collectcounts fragments as unreleased changes. depcommand:patchnotes dep requests 2.30.0 2.32.0resolves a PyPI package to its GitHub repo, fetches its changelog, and flags every breaking/removed/security/deprecated change between two versions — built for reviewing Dependabot bumps.- SARIF output:
validate --format sarifemits SARIF 2.1.0 for GitHub code scanning upload. badgecommand prints a shields.io endpoint JSON for a "latest changelog version" badge.- GitHub Action: new
check-versioninput runs the version sync check, andrelease: "true"creates a GitHub Release with the latest changelog section as notes.
- Release automation:
Changelog.bump("2.1.0")andpatchnotes CHANGELOG.md bump 2.1.0move the [Unreleased] entries into a new dated release, keeping an empty [Unreleased] section and maintaining compare-link footnotes. patchnotes CHANGELOG.md unreleased --fail-if-emptyexits 1 when there are no unreleased entries, so CI can require a changelog entry in every PR.- Write support:
to_markdown()(with optional compare-link generation viarepo_url=) andto_yaml()enable round-tripping and format conversion. patchnotes changelog.yml convert CHANGELOG.mdconverts between markdown and YAML changelogs in either direction.patchnotes CHANGELOG.md fixrewrites an off-spec changelog in normalized form, applying every correction the lenient parser already recovers.- Compare-link footnotes (
[1.2.0]: https://.../compare/v1.1.0...v1.2.0) are now parsed intoChangelog.linksand validated: missing (PN206) and orphaned (PN207) links are reported as warnings. - pre-commit hooks (
.pre-commit-hooks.yaml):patchnotes-validate,patchnotes-validate-strict, andpatchnotes-fix. validateandfixaccept the file after the command (patchnotes validate CHANGELOG.md), matching how pre-commit passes filenames.
- PyYAML is now a required dependency, so YAML changelog support works out of the box with plain
pip install patchnotes. - CI publish workflow installs the package before running tests.
- The internal
patchnotes._parsermodule was split into_models,_dispatch, and thepatchnotes.formatspackage. All public imports (patchnotes.parse,patchnotes.Changelog, ...) are unchanged, andpatchnotes._parserremains as a compatibility shim. - The parser is now lenient by default and recovers from off-standard input (loose dates, bracket-less headers, unknown section names) instead of silently misparsing it. Recovered constructs are reported via
Changelog.validate(), so entry counts and dates may differ from 1.x on malformed files.
- Validation API:
Changelog.validate(),Changelog.is_valid(),patchnotes.validate(), andpatchnotes.validate_file()return structuredValidationIssueobjects with stable codes, severities, and line numbers. - Strict mode:
parse(text, strict=True)andparse_file(path, strict=True)raiseChangelogValidationErroron spec violations — built for CI/CD checks. - Pluggable format registry:
patchnotes.formats.register_format()lets third parties add new changelog formats;format="auto"detects by extension and content. - YAML changelog format via the optional extra
pip install patchnotes[yaml]. - CLI
validatesubcommand with meaningful exit codes (0 valid, 1 invalid, 2 usage error) and a--strictflag that treats warnings as failures. - CLI
--format jsonon every subcommand for shell scripting,-to read from stdin, and--quietfor exit-code-only usage. - GitHub Actions workflow annotations (
::error/::warningwith file and line) — automatic when running inside Actions, or forced with--github. - Composite GitHub Action (
action.yml) so workflows can writeuses: Londopy/patchnotes@v2. - Common off-spec section aliases ("Bug Fixes", "Features", "Improvements", ...) are mapped to their Keep a Changelog equivalents with a warning.
Changelog.from_github()with automaticmasterbranch fallback.to_rss()renderer.
- Initial release: Keep a Changelog parser,
to_html()/to_text()renderers, and thepatchnotesCLI.