just build # uv sync: pytest, ruff, ty, prek, git-cliff, shellcheck
just lint # every hook: ruff, ty, actionlint (+ shellcheck), workflow and action schemas
just test # the release package, cliff.toml, the actions, and that the examples matchtests/test_release.pycovers each command of thereleasepackage (tests/test_cli.pyits entry points and help);tests/test_release_e2e.pydrives whole release flows (two series; alphas, betas, candidates and two release lines with the changelog onmain) the wayrelease.ymlandbackport.ymldo, against a scratch repository with a local bareoriginand a stand-in forgh;tests/test_release_pr.pyruns itsrelease-prcommand;tests/test_workflows.pychecks that the workflows only read outputs the package writes.tests/test_cliff.pyruns git-cliff 2.14.2 on scratch histories: gitmoji stripping, migration notes, blank lines, ignored pre-releases, two release lines,--current,--prepend.tests/test_actions.pyshellchecks the composite actions and runs them with stand-ins forpipxandgh.tests/test_examples.pychecks that docs/adopting.md, theexamples/and the dogfooding stubs agree, that the stubs pass what the reusable workflows declare, and that every reference uses the same release-tools tag and git-cliff version.tests/test_leaks.pychecks that no tracked file holds an AWS account ID, ARN or endpoint, or names anAlgorithmiq/repository other than the public ones.tests/test_repository_setup.pyruns the jq of docs/repository-setup.md, step 8, on sample rulesets: it adds only what is missing, so a second run changes nothing.tests/test_skill.pychecks the agent skill inskills/adopt-release-tools/: its frontmatter, the major version it is written for, that every release-tools path and heading it reads exists, and that it stops for the admin and hands over only literal commands.
On Nix, nix develop (or direnv with the tracked .envrc) gives a
shell with Python 3.12, uv, just and git; run the same commands inside it. The tools
still come from uv.lock, so on NixOS their binary wheels need
nix-ld. Refresh the pinned nixpkgs with
nix flake update.
Requires Python 3.12 or later: the release package runs on the runner's
system Python (python3 path/to/release <command>) and only uses the standard
library. python3 release --help lists its commands.
release-tools is public; the organisation's private and internal repositories must not
be named in it. just hooks installs the git hooks, among them a pre-push check
(scripts/check_internal_names.py)
that lists the non-public repositories with gh and refuses a push in which any commit's
added lines, paths or message name one. Each commit is checked on its own, so a name
that a later commit of the push removes is still caught. A name that is fine to publish goes in
.git/info/allowed-names, one per line; that file is never committed. PR titles and
descriptions are not checked: read them before opening the PR.
release-tools follows the same process with its own workflows (dogfooding):
self-release.yml,
self-backport.yml and
self-pr-checks.yml are the
consumer stubs with uses: ./.github/workflows/<file> instead of a pinned tag, and
self-publish.yml publishes the GitHub release with
./actions/*. A local reference runs the workflow from the same commit as the event, so a
change is exercised by release-tools itself (release candidates included) before any
consumer pins it. Nothing is built or uploaded: the release is the tag and its notes.
It has no version files (version-files: ""). Set it up like any other repository
(Per-repository setup, without PyPI), tag v0.0.0 on the first
commit, and start the first release with major to get v1.0.0rc1 and then v1.0.0.
The pages in docs/ are the source of https://algorithmiq.github.io/release-tools/,
built with Zensical (zensical.toml). just docs previews it
with live reload; just docs-build builds it the way CI does (--strict: broken links
fail). Links between pages are relative .md links, so they work on GitHub too.
Each release deploys its version with Zensical's fork of mike
(the docs job of .github/workflows/self-publish.yml), which commits to the
gh-pages branch:
| Tag | Deployed as |
|---|---|
vX.Y.Z, newest final line |
X.Y, and latest moves to it |
vX.Y.Z, an older line |
X.Y (latest stays) |
vX.Y.0aN, bN, rcN |
X.Y, titled "X.Y (pre-release)", until its final replaces it |
The site root redirects to latest after the first final; until then, it redirects to
the first pre-release's line. If a docs deploy is skipped because several tags were
pushed close together, re-run that tag's docs job (the deploy logic doesn't depend on
order, so it's safe in any order). mike is transitional: when Zensical ships native
versioning, only the deploy step changes.
One-time setup, by a repository admin: Settings → Pages → Source: Deploy from a
branch, branch gh-pages, folder / (root). The branch appears with the first
deploy; set the source after it. No ruleset may block pushes of the Actions token to
gh-pages.