Releasing
A release is a version bump, and everything else is automation. This page assumes you can run the gate from Development setup and that your change is already green. For whether a deployment is fit to upgrade, which is a different question, The release gate owns that.
The version drives everything
Section titled “The version drives everything”There are no release branches and no manual tagging. The single source of truth is version in
pyproject.toml, and .github/workflows/publish.yml compares it against the existing tags.
push to main │ ▼ ci.yml lint · lint-imports · typecheck · test │ ├─ red ──▶ nothing else runs │ └─ green ─▶ workflow_dispatch? │ ├─ no ──▶ stop here, the gate ran and that is all │ └─ yes ─▶ v<version> already a tag? │ ├─ yes ─▶ already released, no-op │ └─ no ──▶ uv build │ ▼ publish to PyPI trusted publishing, skip-existing │ ▼ tag v<version> │ ▼ GitHub release with generated notesTwo details in that flow are deliberate. The publish step runs before the tag, so a failed
upload leaves no tag behind and the next attempt simply retries. And skip-existing is on, so
re-running after a partial failure is idempotent rather than an error.
The CI job is reused rather than reimplemented. publish.yml calls ci.yml through
workflow_call, so the gate a release passes is byte for byte the gate a pull request passes.
Why publishing is manual right now
Section titled “Why publishing is manual right now”The release job carries if: github.event_name == 'workflow_dispatch', so an ordinary push to
main runs the full gate and stops. Two things block an unattended upload.
The first is the rls dependency. It is a direct git reference, because the PyPI name belongs to the
upstream fork base rather than to the house package, and PyPI rejects any distribution that carries a
direct-URL dependency. The second is SQLAlchemy. aizk pins the 2.1 beta, and expressing that needs a
resolver override that plain pip has no way to state.
Neither is permanent. Once the fork publishes under its own PyPI name and pyproject.toml can name
it normally, the if line comes out and a version bump becomes the whole release again. Until then
somebody triggers the run.
Checklist
Section titled “Checklist”- Bump
versioninpyproject.toml. - Move the
Unreleasedsection ofCHANGELOG.mdunder the new version with today’s date. - Update
README.mdand these docs if the change is user-visible, in the same commit. - Run the local gate.
- Merge to
mainand confirm CI is green. - Trigger
publish.ymlfrom the Actions tab or withgh workflow run publish.yml. - Check the PyPI project page and the docs site.
Commands
Section titled “Commands”Everything goes through chefe run from the monorepo root. Bare uv run, pip, python, and
pytest are not the environment the gate uses.
chefe run lint # ruff check, ruff format --check, and the pre-commit stackchefe run lint-imports-aizk # the layered and SQL import contractschefe run typecheck-aizk # pyrefly and tychefe run test-aizk-cov # the suite plus the 100 percent coverage gatechefe run docs-aizk # build the site and run the page gateBuilding the wheel is the one step CI owns rather than you, using uv build on a clean checkout
inside the workflow.
The docs are a separate workflow
Section titled “The docs are a separate workflow”.github/workflows/docs.yml builds the Astro site on any change under docs/ and runs the page gate
over the result, checking the reading budget, the diagram rule, and every internal link. It publishes
nothing. The site is served from the deployment itself through the docs Compose service, so this
job only proves the build is green.
Writing these docs is the contract it enforces.
One-time setup
Section titled “One-time setup”PyPI publishing uses trusted publishing over OIDC, so
there is no API token anywhere. The publisher is registered against this repository, the workflow
file path publish.yml, and the pypi environment.
- The release gate is the operational question of whether to upgrade.
- Upgrades covers moving a running deployment forward.
- Writing these docs is what the docs workflow checks.