Release Guide¶
This repository publishes one PyPI distribution, abi-agent. A release is built from a verified
master commit and keeps the Git tag, package version, GitHub Release, and PyPI artifacts tied to
the same identity.
Pre-Release Checks¶
Start with the consolidated release check:
scripts/release_check.sh
Before pushing a candidate, confirm that CHANGELOG.md contains the exact project.version from
pyproject.toml. CI enforces this through scripts/check_release_identity.py, which also compares
the Claude Code and Codex plugin manifests with the package version.
Choose a version that is absent from both PyPI and remote Git tags. Tags and
PyPI versions are immutable identities: never move or reuse a tag after it has
been pushed. If a tag points to mismatched package metadata, abandon that
version and increment again. Historical example: 1.5.4 was abandoned because
its tag was created against 1.5.3 metadata. Later versions must still be
checked independently; never infer availability from that example.
The release policy requires the tag to point at the verified master commit.
This is a release-operator precondition: release.yml validates tag/package
identity and reuses CI, but it does not itself prove that the tagged commit is
the current master tip. Verify ancestry and remote tag absence before pushing.
The script creates a POSIX temporary directory under /tmp by default and
exports TMPDIR, TMP, and TEMP before running tests. This keeps
permission-sensitive checks off WSL/Windows-mounted temporary directories. Use
ABI_RELEASE_TMPDIR or ABI_RELEASE_TMP_ROOT to override the location.
The script runs the local Python/package subset below:
ruff check src/ tests/
ruff format --check src/ tests/
mypy src/abi/ --ignore-missing-imports
python -m pytest tests/ src/abi/sciplot/tests/ -v --tb=short \
--strict-markers -m "not requires_tools" --capture=no \
--cov=src/abi --cov-branch --cov-report=term-missing:skip-covered \
--cov-report=xml --cov-report=json:coverage.json --cov-fail-under=75
python scripts/check_module_coverage.py --coverage coverage.json
python -m build
abi query --type metagenomic_plasmid --what stages
It does not replace every release-surface gate. Run bash docs/build_docs.sh,
docker compose -f docker/docker-compose.yml config --quiet, the Docker
configuration regression test, strict contract lint for all seven plugins, and
python -m twine check dist/* separately. Run applicable real-tool acceptance
checks, and run container acceptance only when container inputs changed or an
image will be published. The GitHub release workflow reruns the reusable CI
gate before creating a Release.
After building a wheel, install it with its [mcp] extra and smoke-test the
installed commands in a clean environment when possible:
abi list-types
abi query --type metagenomic_plasmid --what stages
abi query --type rnaseq_expression --what tools
autoplasm --help
abi dry-run --type metagenomic_plasmid --config examples/config_minimal.yaml --profile dry_run
abi doctor-agent --type metatranscriptomics
abi export-openai-tools --type metatranscriptomics --format json
abi install-skills --target /tmp/abi-smoke-skills
abi-mcp --help 2>/dev/null || python -m abi.mcp.server --help 2>/dev/null || true
for platform in claude-code opencode codex; do
abi agent install "$platform" --scope project --project-dir "/tmp/abi-release-agent-$platform"
abi agent doctor "$platform" --scope project --project-dir "/tmp/abi-release-agent-$platform"
done
Run this with the clean wheel environment’s bin directory on PATH, because
the doctor verifies the installed abi-mcp entry point. integrations/ is a
release input: it must be present in both distributions and every Docker /app
context. When it or another container input changes, a manual Docker workflow
run is recommended before a container release.
GitHub Actions¶
ci.ymlruns lint, format check, mypy, tests, default builds and installed wheel checks on Python 3.10–3.13, native arm64 validation, and the shared installed-wheel capability verifier.docker.ymlis manual-only. It builds and smoke-tests a selected plugin (or all plugins) and publishes only when the operator explicitly enablespush. Registry pushes include provenance and SBOM; non-push validation loads a stable local image without attestations. Published images are multi-platform except RNA-seq, which remainslinux/amd64-only until its R/DESeq2 environment passes a native arm64 build and smoke test.release.ymlbuilds distributions, runs the wheel smoke outside the source checkout, attachesabi-linux-capability-v<version>.jsongenerated by that installed wheel, creates a GitHub Release forv*tags, and emits the published event.publish-pypi.ymldownloads those exact Release artifacts and publishes the*.whland*.tar.gzdistributions through PyPI Trusted Publishing. The capability JSON remains GitHub Release evidence and is not uploaded to PyPI. PyPI binds the OIDC identity to this filename, so it remains required.
No optional bot or duplicate publishing workflow belongs in .github/workflows/.
The required workflow set is exactly ci.yml, docker.yml, release.yml, and
publish-pypi.yml.
The canonical automatic chain is:
verified master commit → v<version> tag → reusable CI quality gate
→ build and smoke-test wheel/sdist → GitHub Release with exact distributions
and Linux capability evidence
→ top-level release.published event starts publish-pypi.yml
→ download Release artifacts → PyPI Trusted Publishing
Do not call publish-pypi.yml as a reusable workflow: PyPI does not support the
parent workflow’s OIDC Build Config URI. release.published is the single
automatic publication trigger. Recovery uses workflow_dispatch with the
existing GitHub Release tag and never rebuilds locally. Renaming the publisher
requires updating the trusted publisher configuration on PyPI first.
This chain combines policy and automation. The v* filter is enforced by
release.yml; the separate PyPI workflow reacts to a published GitHub Release
and manual recovery input. Release operators must therefore publish only the
verified v<version> Release and must not use the manual path to bypass the
identity checks.
Before merging packaging changes, require a successful default sdist-to-wheel
build. Docker is not a strict PR or PyPI release gate. When container inputs
change, a manual Docker workflow run is recommended; before publishing an image,
that run must cover build, local load, and abi list-types. A successful
BuildKit setup or Conda solve alone is not sufficient. Select the plasmid image
explicitly because of its size.
gh workflow run docker.yml --ref master \
-f plugin=<plugin> -f push=false -f push_to_dockerhub=false
The Docker workflow is never started by a tag or GitHub Release. Container
publication is a separate, intentional operator action and is outside the
automatic GitHub Release → PyPI chain. To publish, select the exact verified
v<version> tag as the manual workflow ref, first record a successful
push=false run, and then rerun the same tag with push=true. The workflow
rejects branch and non-version-tag publication and repeats the local smoke test
before pushing registry tags.
Post-Release Verification¶
Verify that the GitHub Release includes
abi-linux-capability-v<version>.json, that its matrix has exactly 21
environments and the declared x86_64/aarch64 cells, and that unsupported cells
remain explicit. PyPI should contain only the wheel and sdist.
After publication, verify the GitHub Release and PyPI version, confirm Trusted
Publishing provenance and file hashes, install the wheel in a clean environment,
and run abi list-types, autoplasm --help, and representative plugin dry-runs.
For container tags, verify the GHCR image can be pulled and runs
abi list-types. Record links to the Release, PyPI project, release workflow,
publish job, and container workflow in the release handoff.