Explicit permission to compute the next version, generate the changelog, open a release pull request, and — once it is squash-merged — tag the release and publish it.
-
Preflight.
-
Confirm git and gh are installed and authenticated.
-
Detect the changelog mechanism: git-cliff with a cliff.toml (default), the project's configured release/changelog tool, or a hand-maintained changelog. Confirm the relevant tool is available and print its version.
-
Resolve the base branch from the remote default (do not hardcode main):
BASE_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
-
Ensure a clean working tree and an up-to-date base:
git fetch --tags origin
git switch "$BASE_BRANCH" && git pull origin "$BASE_BRANCH"
-
Confirm there is something to release. If there are no releasable commits since the last tag, stop and say so. With git-cliff: git cliff --unreleased is empty. Otherwise: git log <last-tag>..HEAD has nothing release-worthy.
-
Determine the version.
- If the user supplied a version (a
/create-release argument or in the request), use it. Validate that it is well-formed and ahead of the last tag.
- Otherwise, recommend and confirm. Compute a recommended version from the conventional commits since the last tag, then present it together with those commits and ask the user to confirm or choose another. Proceed only once the user has decided — do not auto-pick.
- With git-cliff (default):
VERSION=$(git cliff --bumped-version) for the recommendation; list the commits with git cliff --unreleased (or git log <last-tag>..HEAD).
- Otherwise: derive the recommendation from the project's release tool or the conventional-commit bump rules by hand.
-
Create the release branch off the fresh base, per git-workflow naming:
git switch -c "release/$VERSION"
-
Generate the changelog for that version into the project's changelog file. The intent is to render the section for the unreleased commits under the new version.
- With git-cliff (default):
git cliff --tag "$VERSION" -o CHANGELOG.md regenerates the whole file idempotently; for a large existing file, prepend only the new section with git cliff --tag "$VERSION" --unreleased --prepend CHANGELOG.md.
- Otherwise: produce the same section with the project's changelog tool, or write it by hand following the repo's changelog convention (e.g. Keep a Changelog) from the commits since the last tag.
-
Bump the version wherever it is declared, using the project's own tooling so derived files stay consistent — e.g. cargo set-version "$VERSION" (Rust), npm version "$VERSION" --no-git-tag-version (Node/TypeScript), the pyproject.toml bumper (Python), the gem's version.rb (Ruby), or the manifest field the project's language uses. If the project derives its version from the git tag (e.g. Go modules, setuptools-scm), there is no manifest to bump — the tag is the version, and the release commit carries only the changelog.
-
Commit the release with the type the generator skips:
git commit -m "chore(release): $VERSION"
Let the pre-commit hooks run (markdownlint on the changelog, etc.); do not skip them.
-
Open the release PR via /create-pr. Title chore(release): $VERSION; body = the new changelog section (with git-cliff: git cliff --unreleased --tag "$VERSION" --strip all; otherwise the section you just wrote). It opens as a draft — mark it ready once the diff looks right.
-
Wait for the release PR to be merged, then verify it. Do not create the tag while the PR is still open — wait for the merge and confirm it before continuing. The PR must be squash-merged (per git-workflow), with the squash message chore(release): $VERSION, placing the changelog and version bump on the base branch as one commit; delete the release branch after the merge.
- Confirm the merge by polling
gh pr view <pr> --json state,mergedAt,mergeCommit until state is MERGED, or by pausing until the user confirms they have merged it.
- The merge is normally left to review/CI and performed by the user; squash-merge it yourself here only if the user has authorized the agent to merge.
-
Sync and tag the merge commit. This is the only place a tag is created, and it happens after the changelog is on the base branch:
git switch "$BASE_BRANCH"
git pull origin "$BASE_BRANCH"
git tag -a "$VERSION" -m "$VERSION"
If other commits landed on the base branch after the release PR merged, tag the squash-merge commit by its SHA rather than HEAD. Sanity-check that the tagged commit contains the changelog: git show "$VERSION":CHANGELOG.md | head.
-
Push the tag (plain push, never force):
git push origin "$VERSION"
-
Wait for CI to publish the GitHub release. The tag push triggers
.github/workflows/release.yml, which builds and packages every supported
target, pauses at the protected release environment, creates the GitHub
Release from the committed changelog, and runs the install smoke test.
Never run gh release create for LWPT: publication and release artifacts
are CI-owned.
-
Resolve the tag commit and capture the matching tag-triggered run:
TAG_COMMIT=$(git rev-list -n 1 "$VERSION")
RUN_ID=$(gh run list --workflow release.yml --commit "$TAG_COMMIT" \
--event push --limit 1 --json databaseId \
--jq '.[0].databaseId // empty')
if [ -z "$RUN_ID" ]; then
echo "No release.yml run found for $VERSION ($TAG_COMMIT)" >&2
exit 1
fi
-
Watch that exact run with gh run watch "$RUN_ID" --exit-status. Wait
for the build jobs to pass, approve the release environment when
prompted, and do not proceed unless both publish and install-smoke
succeed.
-
Retrieve the published URL with
gh release view "$VERSION" --json url --jq .url.
-
Report: the version, the tag and the commit SHA it points at, the release PR URL, the GitHub release URL, and confirmation that the tagged commit contains the changelog.