ADR 0008 — Adopt release-please¶
Status: Accepted
Date: 2026-07-06
Supersedes: the versioning + release mechanism of ADR 0007 (and 0006). ADR 0004 (SBOM), PyPI OIDC, and the PR-title guard still hold.
Reusable blueprint. Per-repo variables:
{{DIST}}= PyPI name,{{IMPORT}}= import package,{{REPO}}=owner/repo. Here:aces-scenario-packs,aces_scenario_packs,Brad-Edwards/aces-scenario-packs.
Context¶
Every home-grown approach so far (hatch-vcs tags, a committed literal + a custom
tools/release.py, towncrier collation) left a manual step: someone had to run a
script or hand-collate the changelog to cut a release. The requirement is that a
release is produced by merges only — no scripts to remember. That is exactly
what release-please does.
Decision¶
Use release-please as the release manager.
1. Version = a static committed literal, bumped by release-please¶
pyproject.toml has [project].version = "X.Y.Z" (no dynamic, no hatch-vcs).
release-please rewrites that string on release. {{IMPORT}}/__init__.py derives
__version__ = importlib.metadata.version("{{DIST}}") so there’s one source.
2. Changelog = release-please¶
release-please owns CHANGELOG.md. Feature PRs never touch it (no collisions).
towncrier and changelog.d/ are removed.
3. Commit contract (what release-please reads)¶
Squash-merge feature PRs; the PR title is a Conventional Commit:
feat:→minor, fix:/perf:→patch, feat!:/BREAKING CHANGE:→major (pre-1.0
demotes major→minor); docs/chore/refactor/test/ci/build→no release.
The required PR-title guard keeps this enforced on dev and main.
4. Config¶
.release-please-manifest.json:{ ".": "X.Y.Z" }(current released version;0.0.0until the first release).release-please-config.json:release-type: python,package-name: {{DIST}}..github/workflows/release-please.yml(on: push: [main]):job release-please (
googleapis/release-please-action, SHA-pinned) → outputsrelease_created,tag_name.job publish (
needs: release-please,if: release_created == 'true',environment: pypi,id-token: write): build → CycloneDX SBOM (ADR 0004) →pypa/gh-action-pypi-publish(OIDC) → attach dist + SBOM to the release.All third-party actions SHA-pinned.
5. PyPI (one-time, per repo)¶
Register a pending trusted publisher: project {{DIST}}, owner, repo, workflow
release-please.yml, environment pypi.
Flow¶
Features land on main (via dev). release-please auto-maintains a
chore(main): release X.Y.Z PR with the version bump + CHANGELOG.md. Merge
that PR → it tags and publishes. Nothing is hand-run; feature PRs never edit
CHANGELOG.md.
Caveat¶
The release PR is opened by GITHUB_TOKEN, so required CI checks don’t auto-run
on it — admin-merge it, or give release-please a stored PAT so its checks run.
Consequences¶
Releases are merge-only; no scripts, no towncrier, no hand-collation.
The version has one source (
pyproject.toml), rewritten by release-please;__version__reads it from metadata.Ordering for dependents still applies (publish a dependency before its consumer).