Meaningfy Release Lifecycle
Overview
How a Meaningfy artifact goes from merged commits to a versioned, published, documented release.
This skill owns the release lifecycle; it cites the neighbours that already own their slice and
never restates them.
| Concern |
Authority |
Here |
| Commit/branch/PR conventions |
meaningfy-git-workflow |
cite; add only the release/hotfix branch lifecycle |
| GHCR image build/tag/push, deploy, secrets/Vaultwarden |
ci-cd-delivery |
cite for deployable repos; never restate the image flow |
| CI + scaffolding/projection |
project-setup |
cite; it projects the release workflow + SECURITY.md |
| Versioning, changelog, release notes, PyPI publish, supply-chain, yank/deprecation |
this skill |
own |
1. Versioning policy
- SemVer is a contract, not a vibe: MAJOR = backward-incompatible change to the public API;
MINOR = backward-compatible addition; PATCH = backward-compatible fix. See semver.org.
- Public API = what a consumer can import/call without reaching into a private name (leading
_)
or an undocumented internal module. If it's public, changing it incompatibly is a MAJOR. Decide and
document a module's public surface so "breaking" is decidable, not arguable.
- Pre-releases use dotted identifiers:
1.5.0-rc.1, 1.5.0-rc.2, 1.5.0 (final). Use -rc.N for
release candidates, -beta.N/-alpha.N earlier. A pre-release sorts below its final.
- Version of record = the git tag (
vX.Y.Z), mirrored into pyproject.toml. One bumper keeps them
in sync (§5) — never hand-edit both.
2. Release & hotfix branches
GitFlow for public/client repos (a simpler flow internally — that policy lives in
meaningfy-git-workflow). The branch mechanics (rebase/merge,
force-push etiquette) are owned there; the release-specific lifecycle is owned here and detailed in
references/gitflow-release-runbook.md:
release/X.Y.Z — cut from the integration branch; stabilisation-only commits; tag on merge.
hotfix/X.Y.Z — cut from the released tag, patch, tag, then back-merge to all long-lived lines.
3. Changelog & release notes
- Maintain
CHANGELOG.md in Keep a Changelog format with an
Unreleased section that fills as commits land (project-setup ships the stub).
- Auto-generation is the starting point, not the product. The release engine drafts the entry from
Conventional Commits; a human adds breaking-change callouts and migration steps.
- CHANGELOG vs GitHub Release body: the CHANGELOG is the durable in-repo history; the GitHub Release
body is the published announcement for that tag (link back to the CHANGELOG section).
4. The release engine — release-please (semi-automated)
We don't release by hand and we don't auto-release on every merge. Conventional Commits drive a
release proposal that a human approves:
feat:/fix: commits land on the release line
│
▼
release-please opens a "release PR" → version bump + CHANGELOG generated
│ (human reviews — four-eyes; free-tier needs no draft PR)
▼ merge the release PR
tag vX.Y.Z → GitHub Release drafted → publish job runs (§5)
This honours GitFlow + four-eyes and is free-tier-safe (no draft PRs needed — see the GitHub
constraints in meaningfy-git-workflow).
5. Publishing
- Library → PyPI via Trusted Publishing (OIDC), no API tokens. Full setup in
references/pypi-publishing.md: a release GitHub environment,
id-token: write, pypa/gh-action-pypi-publish, and a TestPyPI dry-run gate before the real
upload.
- Deployable service → GHCR image — owned by
ci-cd-delivery
(semver + git sha → registry). This skill does not restate it; the tag from §4 is its version source.
6. Supply-chain hardening (recommended, not required)
Documented target, adopted per-repo and incrementally — no repo is blocked from releasing without it.
Detail in references/supply-chain.md: PyPI attestations,
Sigstore/cosign keyless signing, SLSA Level 2 provenance, and an SBOM (Syft) attached to the
GitHub Release.
7. Governance
SECURITY.md + coordinated disclosure via GitHub Security Advisories (private report → fix → CVE
→ publish). Required for public repos; projected by project-setup.
- Yank, don't delete. A broken release already on PyPI is yanked (stays installable for existing
pins, hidden from new resolves) and superseded by a PATCH. Deleting breaks downstream pinned installs.
- Deprecate before you break. A MAJOR bump that removes public API is preceded by a deprecation
cycle (warn in a MINOR, remove in the next MAJOR), recorded in the CHANGELOG.
Error / decision matrix
| Situation |
❌ Wrong move |
✅ Right move |
| Backward-incompatible change |
ship it in a MINOR/PATCH |
bump MAJOR; run a deprecation cycle first (§7) |
| Bad release already on PyPI |
delete the release |
yank + ship a PATCH (delete breaks pinned installs) |
| Authenticating the PyPI upload |
store an API token in GitHub Secrets |
Trusted Publishing OIDC, no token (§5) |
Tag and pyproject.toml disagree |
hand-edit both to match |
let release-please be the single bumper (§1, §4) |
| Releasing a deployable service |
publish a PyPI package |
GHCR image via ci-cd-delivery (§5) |
| Pre-release going to production |
ship -rc.N as the final |
promote: drop the pre-release identifier to X.Y.Z (§1) |
Boundary & Related Skills
Owns: the release lifecycle — versioning policy, the release/hotfix branch lifecycle, changelog +
GitHub release notes, the release-please engine, PyPI Trusted Publishing, opt-in supply-chain, and
release governance (SECURITY.md, yank, deprecation).
Does NOT own: commit/branch/PR mechanics (meaningfy-git-workflow),
the GHCR image + deploy + secrets flow (ci-cd-delivery), or CI +
scaffolding/projection (project-setup).
Related: meaningfy-git-workflow,
ci-cd-delivery, project-setup.
1---2name: meaningfy-release3description: The Meaningfy release lifecycle — semantic versioning policy (MAJOR/MINOR/PATCH + -rc.N pre-releases), GitFlow release/hotfix branches, changelog + GitHub release notes, semi-automated releases via release-please, publishing Python libraries to PyPI with Trusted Publishing (OIDC, no tokens), opt-in supply-chain hardening (signing/provenance/SBOM), and release governance (SECURITY.md, yanking, deprecation). Use when cutting, versioning, publishing, or documenting a release. Trigger on "cut a release", "bump the version", "publish to PyPI", "write release notes", "release branch / hotfix", "yank a bad release", "how do we version this", "set up the release workflow".4license: Apache 2.05---67# Meaningfy Release Lifecycle89## Overview1011How a Meaningfy artifact goes from merged commits to a versioned, published, documented release.12This skill owns the **release lifecycle**; it cites the neighbours that already own their slice and13never restates them.1415| Concern | Authority | Here |16|---------|-----------|------|17| Commit/branch/PR conventions | [`meaningfy-git-workflow`](../meaningfy-git-workflow/SKILL.md) | cite; add only the release/hotfix branch *lifecycle* |18| GHCR image build/tag/push, deploy, secrets/Vaultwarden | [`ci-cd-delivery`](../ci-cd-delivery/SKILL.md) | cite for deployable repos; never restate the image flow |19| CI + scaffolding/projection | [`project-setup`](../project-setup/SKILL.md) | cite; it projects the release workflow + `SECURITY.md` |20| **Versioning, changelog, release notes, PyPI publish, supply-chain, yank/deprecation** | **this skill** | **own** |2122## 1. Versioning policy2324- **SemVer is a contract**, not a vibe: **MAJOR** = backward-incompatible change to the public API;25 **MINOR** = backward-compatible addition; **PATCH** = backward-compatible fix. See [semver.org](https://semver.org/).26- **Public API** = what a consumer can import/call without reaching into a private name (leading `_`)27 or an undocumented internal module. If it's public, changing it incompatibly is a MAJOR. Decide and28 document a module's public surface so "breaking" is decidable, not arguable.29- **Pre-releases** use dotted identifiers: `1.5.0-rc.1`, `1.5.0-rc.2`, `1.5.0` (final). Use `-rc.N` for30 release candidates, `-beta.N`/`-alpha.N` earlier. A pre-release sorts *below* its final.31- **Version of record = the git tag** (`vX.Y.Z`), mirrored into `pyproject.toml`. One bumper keeps them32 in sync (§5) — never hand-edit both.3334## 2. Release & hotfix branches3536GitFlow for public/client repos (a simpler flow internally — that policy lives in37[`meaningfy-git-workflow`](../meaningfy-git-workflow/SKILL.md)). The branch *mechanics* (rebase/merge,38force-push etiquette) are owned there; the *release-specific lifecycle* is owned here and detailed in39[`references/gitflow-release-runbook.md`](references/gitflow-release-runbook.md):4041- `release/X.Y.Z` — cut from the integration branch; **stabilisation-only** commits; tag on merge.42- `hotfix/X.Y.Z` — cut **from the released tag**, patch, tag, then back-merge to all long-lived lines.4344## 3. Changelog & release notes4546- Maintain `CHANGELOG.md` in **[Keep a Changelog](https://keepachangelog.com/)** format with an47 `Unreleased` section that fills as commits land (`project-setup` ships the stub).48- **Auto-generation is the starting point, not the product.** The release engine drafts the entry from49 Conventional Commits; a human adds breaking-change callouts and migration steps.50- **CHANGELOG vs GitHub Release body:** the CHANGELOG is the durable in-repo history; the GitHub Release51 body is the published announcement for that tag (link back to the CHANGELOG section).5253## 4. The release engine — release-please (semi-automated)5455We **don't release by hand and we don't auto-release on every merge.** Conventional Commits drive a56release *proposal* that a human approves:5758```59feat:/fix: commits land on the release line60 │61 ▼62release-please opens a "release PR" → version bump + CHANGELOG generated63 │ (human reviews — four-eyes; free-tier needs no draft PR)64 ▼ merge the release PR65tag vX.Y.Z → GitHub Release drafted → publish job runs (§5)66```6768This honours GitFlow + four-eyes and is free-tier-safe (no draft PRs needed — see the GitHub69constraints in [`meaningfy-git-workflow`](../meaningfy-git-workflow/SKILL.md#free-tier-github-constraints)).7071## 5. Publishing7273- **Library → PyPI** via **Trusted Publishing (OIDC), no API tokens.** Full setup in74 [`references/pypi-publishing.md`](references/pypi-publishing.md): a `release` GitHub environment,75 `id-token: write`, `pypa/gh-action-pypi-publish`, and a **TestPyPI dry-run gate** before the real76 upload.77- **Deployable service → GHCR image** — owned by [`ci-cd-delivery`](../ci-cd-delivery/SKILL.md#3-release--image-standard-r5)78 (semver + git sha → registry). This skill does not restate it; the tag from §4 is its version source.7980## 6. Supply-chain hardening (recommended, not required)8182Documented target, adopted per-repo and incrementally — **no repo is blocked from releasing without it.**83Detail in [`references/supply-chain.md`](references/supply-chain.md): PyPI **attestations**,84**Sigstore/cosign** keyless signing, **SLSA Level 2** provenance, and an **SBOM** (Syft) attached to the85GitHub Release.8687## 7. Governance8889- **`SECURITY.md` + coordinated disclosure** via GitHub Security Advisories (private report → fix → CVE90 → publish). Required for public repos; projected by [`project-setup`](../project-setup/SKILL.md).91- **Yank, don't delete.** A broken release already on PyPI is **yanked** (stays installable for existing92 pins, hidden from new resolves) and superseded by a PATCH. Deleting breaks downstream pinned installs.93- **Deprecate before you break.** A MAJOR bump that removes public API is preceded by a deprecation94 cycle (warn in a MINOR, remove in the next MAJOR), recorded in the CHANGELOG.9596## Error / decision matrix9798| Situation | ❌ Wrong move | ✅ Right move |99|-----------|--------------|-------------|100| Backward-incompatible change | ship it in a MINOR/PATCH | bump MAJOR; run a deprecation cycle first (§7) |101| Bad release already on PyPI | delete the release | **yank** + ship a PATCH (delete breaks pinned installs) |102| Authenticating the PyPI upload | store an API token in GitHub Secrets | Trusted Publishing OIDC, **no token** (§5) |103| Tag and `pyproject.toml` disagree | hand-edit both to match | let release-please be the single bumper (§1, §4) |104| Releasing a deployable service | publish a PyPI package | GHCR image via `ci-cd-delivery` (§5) |105| Pre-release going to production | ship `-rc.N` as the final | promote: drop the pre-release identifier to `X.Y.Z` (§1) |106107## Boundary & Related Skills108109**Owns:** the release lifecycle — versioning policy, the release/hotfix branch lifecycle, changelog +110GitHub release notes, the release-please engine, PyPI Trusted Publishing, opt-in supply-chain, and111release governance (`SECURITY.md`, yank, deprecation).112**Does NOT own:** commit/branch/PR mechanics ([`meaningfy-git-workflow`](../meaningfy-git-workflow/SKILL.md)),113the GHCR image + deploy + secrets flow ([`ci-cd-delivery`](../ci-cd-delivery/SKILL.md)), or CI +114scaffolding/projection ([`project-setup`](../project-setup/SKILL.md)).115**Related:** [`meaningfy-git-workflow`](../meaningfy-git-workflow/SKILL.md),116[`ci-cd-delivery`](../ci-cd-delivery/SKILL.md), [`project-setup`](../project-setup/SKILL.md).