From 44b89bcc7c26f7a515b2e0498dc6ea0f8cd9cac4 Mon Sep 17 00:00:00 2001 From: aaron Date: Wed, 30 Sep 2026 19:31:45 -0700 Subject: [PATCH] Add a /release project skill for cutting PyPI releases Co-Authored-By: Claude Sonnet 5.5 --- .claude/skills/release/SKILL.md | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .claude/skills/release/SKILL.md diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md new file mode 100644 index 0000000..1658579 --- /dev/null +++ b/.claude/skills/release/SKILL.md @@ -0,0 +1,71 @@ +--- +name: release +description: Cut a PyPI release of based-models-agentloop-tools by pushing a version tag. Use only when the user invokes /release. +disable-model-invocation: true +--- + +# Release based-models-agentloop-tools to PyPI + +Distribution `based-models-agentloop-tools`, import name `agentloop_tools`, repo `based-models/agent-loop-tools`. + +Pushing a `vX.Y.Z` tag **publishes to PyPI immediately** (Trusted Publishing, no approval +gate), and a PyPI version number can **never be reused**, even after deletion. Everything +before the tag is preparation; the tag is the point of no return. The full process notes live in the `agent-loop` repo's `docs/guides/releasing.md`; this repo +uses the same workflow. + +## 1. Preconditions (stop and report on any failure; do not fix silently) +Run read-only checks first: +- On `main`, up to date with `origin/main` (`git fetch`, then compare). Unrelated local + edits that do not belong in the release: leave them alone and say so. +- CI on the release commit is green: `gh run list --branch main --limit 3`. +- Version `X.Y.Z` is identical in `pyproject.toml` and `src/agentloop_tools/__init__.py` + (`__version__`). The release workflow rejects a mismatch, but catch it earlier. +- `CHANGELOG.md` has a `## [X.Y.Z]` heading for exactly this version. +- No `vX.Y.Z` tag exists locally or on origin (`git ls-remote --tags origin`), and + `https://pypi.org/pypi/based-models-agentloop-tools/X.Y.Z/json` is a 404 (that version is unpublished). +- The `based-models-agentloop` range in `pyproject.toml` is satisfied by a version already on + PyPI (`https://pypi.org/pypi/based-models-agentloop/json`). Its CI installs that dependency + from PyPI, so an unpublished requirement means red CI. Release `agent-loop` first if needed. +- README or doc changes meant for the PyPI page are **merged to `main`**, because the tag + publishes what is on `main`. + +If the version is not bumped yet, offer to do the bump (pyproject, `__init__`, changelog) on a +branch and open a PR. Never push version bumps straight to `main`. + +## 2. Confirm before tagging +Tell the user exactly what will happen and wait for a clear yes in this conversation: +> Tagging `vX.Y.Z` on `agent-loop-tools` at commit `` will publish `based-models-agentloop-tools X.Y.Z` to PyPI +> immediately. That version can never be re-uploaded. Proceed? + +A yes to an earlier question, or to a different release, does not count. + +## 3. Tag (only after the yes) +```bash +git tag vX.Y.Z && git push origin vX.Y.Z # one tag, by name; never `git push --tags` +``` + +## 4. Watch, then verify +- Find the run: `gh run list --workflow release.yml --limit 1`, then wait until it + completes (a background watch, not a sleep loop). Report each job: `build`, + `publish-pypi`, `github-release`. +- Verify from the real index in a throwaway venv outside the repo: + ```bash + python3 -m venv "$TMP/v" && "$TMP/v/bin/pip" install based-models-agentloop-tools "based-models-agentloop[anthropic]" + "$TMP/v/bin/python" -c "import agentloop_tools; print(agentloop_tools.__version__)" + ``` +- Confirm the GitHub Release has the wheel, sdist and `SHA256SUMS`: + `gh release view vX.Y.Z --json assets`. + +## 5. If something fails +- Fails before `publish-pypi` uploads (tests, version check, missing pending publisher): + nothing was published and the version is not used. Fix, then delete and re-push the tag + **only if the user agrees**, since re-pushing re-triggers a publish. +- `publish-pypi` succeeded but the release is bad: **do not** move or delete the tag. + Recommend yanking `X.Y.Z` on PyPI (the user does this in the PyPI web UI) and releasing + `X.Y.(Z+1)`. + +## Never +- Tag without the explicit confirmation in step 2. +- `git push --tags`, force-push a tag, or reuse a version number. +- Add API tokens or `twine upload`: publishing is only via the workflow. +- Release from anything but an up-to-date `main`.