-
Record the release target, then sync and validate: Name the PR or merge commit(s) this release must ship. A bootstrap release made only to restore CI or review does not complete a separate pending change; that change needs its own release after it merges. Fetch and bring the long-lived release branch to origin/main (git merge origin/main, or git reset --hard origin/main when it has no commits to preserve), then verify every target with git merge-base --is-ancestor <commit> HEAD. Run wt test and uv tool run pre-commit run --all-files. Record HEAD as the cut-from commit for step 9.
-
Check current version: Read version in generator/pyproject.toml
-
Review commits: git log <last-version>..origin/main --oneline to understand scope — against origin/main (not HEAD), so the range is the full set of commits this release ships even if step 1 was skipped
-
Confirm version with user: Present changes summary and proposed version
-
Bump version: Edit version in generator/pyproject.toml, then uv lock at the repo root (the workspace's only lockfile)
-
Update CHANGELOG: Add a ## X.Y.Z section at the top of CHANGELOG.md (see "CHANGELOG" below). The release workflow publishes this section verbatim as the GitHub Release notes and fails the GitHub Release job if the section is missing (PyPI publish has already happened by then; recovery is a manual gh release create), so it must land in the release commit — before the tag.
-
Commit on the current branch: chore: release X.Y.Z (version bump, lockfile, and CHANGELOG). Don't create a new branch — this worktree is already on the release branch, and the PR opens from it to main.
-
Merge to main: Push, create PR via gh pr create, wait for CI, merge with gh pr merge --squash
-
Verify the changelog covers main, then tag and push: the tag decides what ships — pypi-release.yaml publishes to PyPI and builds the GitHub Release from the ## X.Y.Z section at the tag, so everything reachable from it is in the release. The merge squashes onto whatever main tip exists at merge time, so a commit that lands during the PR's CI wait is already an ancestor of the release commit and ships whether or not the changelog mentions it. A direct push to main is the easy miss: it never appears in gh pr list, so a PR-based cross-check won't find it. List what reached main since the cut-from tip (step 1):
git fetch origin
git log --oneline <cut-from-commit>..origin/main
Clean means the changelog at origin/main documents every user-facing commit listed. With no drift the list is one line, the chore: release X.Y.Z (#NNNN) squash commit. Fold anything else that's user-facing into the changelog with a follow-up squash PR, then re-fetch and re-run. The list only grows across passes — the drifted commits stay, joined by the follow-up's own squash commit — so each pass re-checks coverage over a longer list.
Both the tag and the PyPI version are immutable, so this is the last point where a miss is cheap to fix. Once clean, tag origin/main, so the check and the tag name the same ref:
git tag X.Y.Z origin/main && git push origin X.Y.Z
-
Wait for publication: Require the tag workflow to succeed, uvx tend@X.Y.Z --help to resolve the exact version, and gh release view X.Y.Z to show the release.
-
Deploy the release to tend: Stay on the release branch. Fetch and git reset --hard origin/main, then regenerate with the exact published package: uvx tend@X.Y.Z init. Follow running-tend's Nightly: restamp the hand-maintained workflow refs in the same commit. Push and open a PR titled chore: regenerate workflows with tend X.Y.Z; wait for CI and review, then squash-merge it. Opening the PR is not deployment.
-
Exercise changed integration surfaces: If the release changes an action, harness, authentication, model selection, plugin installation, or generated workflow behavior, dispatch a representative workflow for every affected harness from the updated main. Inspect the run, not just its conclusion: verify it invokes @X.Y.Z, exhibits the intended configuration, and completes a real agent task. The release is complete only after publication, the deployment PR merge, and these required live checks.