Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .github/workflows/publish.yml

This file was deleted.

79 changes: 79 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Release

# Tag-driven: pushing a bare semver tag publishes to PyPI, creates the matching
# GitHub Release, and floats the `v0` action tag. Replaces the old
# `release: published` publish.yml (deleted) and folds in tag-major.yml (deleted).
#
# The tag is the sole, deliberate entry point. semvertag.yml dogfoods in
# DRY-RUN, so it never pushes a tag — only a maintainer's manual `git push` of
# a tag reaches here (GitHub suppresses workflow triggers from
# GITHUB_TOKEN-pushed refs, so even an auto-push would not fire this). By
# convention a tag is cut only off green main, so there is no in-workflow CI gate.
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # stable: 0.9.0
- '[0-9]+.[0-9]+.[0-9]+[a-z]+[0-9]+' # pre-release: 0.9.0rc1, 1.0.0a2

# Needed for softprops/action-gh-release to create the Release and for the
# v0 force-push.
permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: extractions/setup-just@v4
- uses: astral-sh/setup-uv@v7

# PyPI is irreversible, so it runs FIRST: if it fails the job stops and no
# GitHub Release or v0 move advertises a version that never reached PyPI.
# `just publish` derives the version from $GITHUB_REF_NAME (the tag name).
- run: just publish
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

# Description source: planning/releases/<tag>.md if present (verbatim, no
# auto-changelog appended); otherwise GitHub's generated notes. A tag with
# a letter (0.9.0rc1) is a pre-release -> flagged so GitHub won't mark it
# "Latest" and so the v0 float below is skipped.
- name: Resolve release metadata
id: meta
run: |
set -euo pipefail
notes="planning/releases/${GITHUB_REF_NAME}.md"
if [ -f "$notes" ]; then
echo "body_path=$notes" >> "$GITHUB_OUTPUT"
echo "generate_notes=false" >> "$GITHUB_OUTPUT"
else
echo "generate_notes=true" >> "$GITHUB_OUTPUT"
fi
if [[ "$GITHUB_REF_NAME" =~ [a-z] ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish GitHub Release
uses: softprops/action-gh-release@v3
with:
body_path: ${{ steps.meta.outputs.body_path }}
generate_release_notes: ${{ steps.meta.outputs.generate_notes }}
prerelease: ${{ steps.meta.outputs.prerelease }}
draft: false

# Floating major tag (folded-in tag-major.yml): consumers pin
# `uses: modern-python/semvertag@v0` and ride minor bumps. Skipped on
# pre-releases so a 0.9.0rc1 doesn't drag v0 ahead of the latest stable.
# References HEAD (the tag commit), so no fetch-depth: 0 is needed.
- name: Float major tag
if: steps.meta.outputs.prerelease == 'false'
run: |
set -euo pipefail
major="v${GITHUB_REF_NAME%%.*}" # 0.9.0 -> v0
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git tag -fa "$major" -m "Update $major to $GITHUB_REF_NAME"
git push -f origin "$major"
26 changes: 14 additions & 12 deletions .github/workflows/semvertag.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
name: semvertag

# Dogfood the local composite action against this repo. Auto-tags on
# push to main when the latest commit is a merge from `feat/...` (minor
# bump) or `bugfix/`/`hotfix/...` (patch). This repo's branch
# convention uses `feat/...`, so SEMVERTAG_BRANCH_PREFIX__MINOR
# overrides the default `feature/` mapping.
# Dogfood the local composite action against this repo in DRY-RUN: on every
# push to main it computes the planned bump (exercising action.yml + the
# published semvertag) but never pushes a tag. This keeps action.yml honest —
# a breaking change fails the run before it can affect external users.
#
# The workflow only creates a tag — it does NOT trigger publish.yml,
# which fires on GitHub release creation. To publish to PyPI, create a
# GitHub release pointed at the auto-tagged commit.
# Releases are NOT cut here. A release is a maintainer pushing a bare semver
# tag by hand, which triggers .github/workflows/release.yml (PyPI + GitHub
# Release + v0). Dry-run is load-bearing: it guarantees the only tags in the
# repo are deliberate release tags — do not give this job a push token or
# remove `dry-run: true`.
#
# `uses: ./` exercises the action.yml in the current checkout, so any
# breaking change to action.yml fails the dogfood run before it can
# affect external users.
# This repo's branch convention uses `feat/...`, so SEMVERTAG_BRANCH_PREFIX__MINOR
# overrides the default `feature/` mapping.

on:
push:
branches: [main]

permissions:
contents: write
contents: read

concurrency:
group: semvertag
Expand All @@ -33,5 +33,7 @@ jobs:
with:
fetch-depth: 0
- uses: ./
with:
dry-run: true
env:
SEMVERTAG_BRANCH_PREFIX__MINOR: '["feat/"]'
43 changes: 0 additions & 43 deletions .github/workflows/tag-major.yml

This file was deleted.

24 changes: 20 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ excluded from the mkdocs site automatically). When superpowers skills default to
`docs/superpowers/specs/` or `docs/superpowers/plans/`, use the change bundle
under `planning/changes/` here instead.

**Cutting a release (maintainers)** is tag-driven via
[`.github/workflows/release.yml`](.github/workflows/release.yml): write the
notes at `planning/releases/<version>.md` (used verbatim as the GitHub Release
body), then push a bare semver tag off green `main` —
`git tag 0.9.0 && git push origin 0.9.0`. The workflow runs `just publish` (the
tag sets the version via `uv version $GITHUB_REF_NAME`; no `pyproject.toml`
bump) to PyPI, then creates the GitHub Release, then floats the `v0` action tag
— PyPI first, so a failed publish creates no Release. Pre-releases use the
PEP 440 form (`0.9.0rc1`, not `0.9.0-rc1`). PyPI is irreversible; there is no
CI gate (a tag is the commitment point). The dogfood (`semvertag.yml`) runs in
dry-run and never auto-tags, so the tag you push is the only tag. If `just
publish` succeeds but a later step fails (Release or `v0`), do **not** re-push
the tag — PyPI rejects re-uploading an existing version. Create the GitHub
Release and move `v0` by hand, or cut a new patch tag.

## Commit messages

Imperative present-tense, scoped where helpful:
Expand Down Expand Up @@ -95,8 +110,9 @@ Two distinct tag conventions coexist — confusing them is easy:
strategy emits bare-semver tags by default; release URLs are
`releases/tag/0.4.0`. When touching the CLI / `Justfile` / publish flow, think
bare semver — `$GITHUB_REF_NAME` is `0.4.0`, not `v0.4.0`.
- **Action floating tag: `v`-prefixed** (`v0`). `.github/workflows/tag-major.yml`
strips any leading `v` from the release tag then prepends `v` to the major
segment (`0.4.0` → `v0`), so consumers can pin `uses: modern-python/semvertag@v0`
per the GHA ecosystem convention. When touching `tag-major.yml` or
- **Action floating tag: `v`-prefixed** (`v0`). The `Float major tag` step in
[`.github/workflows/release.yml`](.github/workflows/release.yml) prepends `v`
to the release tag's major segment (`0.4.0` → `v0`) and force-updates the
floating tag, so consumers can pin `uses: modern-python/semvertag@v0` per the
GHA ecosystem convention. Skipped on pre-releases. When touching that step or
action-consumer docs, think `v`-prefix.
121 changes: 0 additions & 121 deletions docs/contributing/release.md

This file was deleted.

5 changes: 0 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,3 @@ semvertag ships with two bump-decision strategies:

Both strategies are configurable via environment variables — see the
strategy pages for the full configuration surface.

## Contributing

- [Release runbook](contributing/release.md) — for maintainers cutting
a new release of semvertag itself.
2 changes: 0 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ nav:
- Strategies:
- Branch prefix: strategies/branch-prefix.md
- Conventional commits: strategies/conventional-commits.md
- Contributing:
- Release runbook: contributing/release.md

theme:
name: material
Expand Down
Loading