| name | release |
| version | 1.0 |
| description | Cut a new gflow-cli release — bump version, update CHANGELOG, tag, push, and back-merge.
|
/gflow:release — Cut a new release
Follow this sequence verbatim. Every step matters.
Branch-protection note: main blocks direct pushes. The release commit travels
via a chore/release-vX.Y.Z branch PR. The signed tag is pushed independently
(tag pushes bypass branch protection and trigger the CI release workflow immediately).
Source-branch note (read first): develop is the integration branch — it carries
ALL unreleased work and main usually lags it. The release branch is cut from
develop, NOT main. The PR chore/release-vX.Y.Z → main then brings the full
integration history onto main. Do not expect the work to already be on main.
Inputs
Ask the user (if not already provided):
- Version — the new version (e.g.
0.4.0, 0.4.0a3, 1.0.0rc1). Use PEP 440 prerelease suffixes (aN, bN, rcN). If they don't know, run /gflow:changelog first and propose the next bump (PATCH for fixes only, MINOR for new features, MAJOR for breaks).
- Pre-release? — prerelease versions stay marked as GitHub prereleases. Only the user can say when a release line is ready for the stable tag.
Sequence
1. Review what's queued.
Run /gflow:changelog — confirm the [Unreleased] block is non-empty and accurate before proceeding.
2. Verify (or triage) a clean working tree.
git status --short
If empty, continue. If not, triage before aborting — do not blindly stop:
- Auto-injected boilerplate (e.g. a context-mode routing block appended to
CLAUDE.md by an MCP plugin's SessionStart hook): this is plugin-injected, not
project content — git restore it. Confirm with the user if unsure.
- Build/temp artifacts (e.g. a stray
tmp*.tar.gz sdist at repo root): delete them.
- Genuine uncommitted work: STOP and tell the user to commit or stash on the
appropriate branch (never commit straight to
develop).
The tree must be clean before you create the release branch.
3. Verify develop is the release source and up-to-date.
The release is cut from develop, NOT main (see Source-branch note above).
Confirm direction explicitly — a backwards divergence means a prior back-merge was skipped.
git fetch origin
git rev-parse --abbrev-ref HEAD
git rev-list --count HEAD..origin/develop
git rev-list --count origin/main..origin/develop
git rev-list --count origin/develop..origin/main
If not on develop: git checkout develop && git pull origin develop.
If local is behind origin: git pull origin develop.
If main is AHEAD of develop (last count > 0): STOP. A prior release skipped its
main → develop back-merge — recover first (see the release-back-merge-gap-recovery
memory) or the release branch will hit conflicts on pyproject.toml / __init__.py / CHANGELOG.md.
4. Run quality gates.
Run /gflow:check — all gates must pass. Abort if any fail.
4b. Live-verify the release's user-facing features (REQUIRED gate).
For every new/changed user-facing feature in this release, exercise it against
live Flow (credit-free wherever possible — image gen, entity attach, upscale, and
scene/timeline ops cost no Veo credits) and write the evidence to
docs/LIVE_VERIFICATION_v<NEW_VERSION>.md using the 5-layer ledger (file count +
magic bytes + dimensions/shape + structlog invariants + a user-confirmable
artifact). Add it to the "what was live-verified" entry in docs/INDEX.md. This
doc shipped for every release v0.7.0→v0.13.0, then lapsed for v0.14.0–v0.15.1 —
which is why it is now an explicit gate. If a feature genuinely cannot be verified
this cycle, record that and the reason in the doc; never silently omit it. Stage
the doc into the release-prep commit (step 11).
5. Create a release branch off develop.
git checkout develop
git checkout -b chore/release-v<NEW_VERSION>
This branch now contains all of develop (⊇ main) plus your release prep. All
release prep commits live here; the PR into main (step 14) carries the full
integration history forward.
6. Bump version in pyproject.toml:
[project]
version = "<NEW_VERSION>"
7. Bump package version in src/gflow_cli/__init__.py:
__version__ = "<NEW_VERSION>"
8. Update version assertion tests if present:
rg -n "__version__|<OLD_VERSION>|version assertion" tests src pyproject.toml
9. Migrate CHANGELOG.
10. Run the documentation review gate.
Run /gflow:doc-review — audit all version refs, INDEX completeness, evidence files, skill files, CHANGELOG footer, and memory files. Fix every FAIL before continuing. Fold all discovered fixes into the release prep commit.
Also consolidate shipped planning artifacts here: extract any durable patterns
into auto-memory, then remove the now-shipped docs/superpowers/ plan / spec /
verification files (keep only in-flight work). check_repo_hygiene.py enforces the
root-doc allowlist, so a stray review doc or session marker left at the repo root
will fail the gate.
11. Commit the release prep.
git add pyproject.toml src/gflow_cli/__init__.py uv.lock CHANGELOG.md
git add docs/ .claude/commands/gflow/
git add README.md PLAN.md KNOWN_ISSUES.md AGENTS.md llms.txt 2>/dev/null || true
git status --short
git commit -m "chore(release): v<NEW_VERSION>"
uv.lock changes on every version bump (the editable package version is
pinned in the lockfile) — it is easy to forget and must ship in this commit.
- The release-prep commit must NOT carry a
Co-Authored-By trailer (see reminders).
12. Tag the release commit. Use -s for a signed annotated tag so GitHub shows "Verified" AND .github/workflows/release.yml passes the signed-tag gate (unsigned or lightweight tags are rejected by CI).
git tag -s v<NEW_VERSION> -m "v<NEW_VERSION>"
Signing requirements:
- SSH signing (preferred):
git config --global gpg.format ssh + user.signingkey pointing at your public key.
- GPG: any registered GPG key works.
- Run
git config --global user.signingkey to confirm a key is configured.
Confirm the tag actually carries a signature (this is what CI checks):
git cat-file -p v<NEW_VERSION> | grep -c "BEGIN SSH SIGNATURE"
Benign local-verify error: git tag -v v<NEW_VERSION> may fail with
gpg.ssh.allowedSignersFile needs to be configured. This is a local
verification-config gap only — the tag IS validly signed and CI still passes
(CI greps for the signature header, above). To make local verify work once and
for all, create an allowed-signers file (<your-email> ssh-rsa AAAA...) and run
git config --global gpg.ssh.allowedSignersFile <path>. See the release-signing
memory for the exact recipe. Do NOT treat this error as a signing failure.
13. Push the tag first (bypasses branch protection; triggers the CI release workflow immediately):
⚠ POINT OF NO RETURN — confirm with the user before this push. Pushing the
tag immediately triggers .github/workflows/release.yml → PyPI publish +
public GitHub Release. A pushed release tag must NOT be force-replaced (ship a
PATCH instead). Get an explicit go-ahead, then push.
git push origin v<NEW_VERSION>
CI will start building the release. Watch https://github.com/ffroliva/gflow-cli/actions.
Wait for the Release run to report completed / success and confirm the GitHub
Release published before continuing (gh release view v<NEW_VERSION>).
14. Push the release branch and open the PR.
git push -u origin chore/release-v<NEW_VERSION>
gh pr create --base main --head chore/release-v<NEW_VERSION> \
--title "chore(release): v<NEW_VERSION>"
Wait for PR CI to go green (gh pr checks <N> --watch). The SonarCloud analysis
check must be green (gate passed) — not just the test matrix. If it is red or you
want the verdict, run /gflow:sonar <N> and drive it to zero before merging. Then
merge with a merge commit — never squash:
gh pr merge <N> --merge --delete-branch
NEVER --squash this PR. Because the branch was cut from develop, the PR
carries the entire batch of unreleased integration commits. A squash collapses
them into one opaque commit on main and destroys that history. --merge
preserves it. (The release workflow already ran from the tag push in step 13 —
this PR is to bring the bump commit + integration history onto main.)
15. Back-merge main into develop.
After the release PR is merged, bring the bump commit back to develop so branches stay aligned:
git checkout develop
git pull origin develop
git fetch origin main
git merge origin/main --no-ff -m "chore: back-merge main (v<NEW_VERSION>) into develop"
git push origin develop
If there are conflicts (rare — only if develop has commits that touched the same lines as the bump), resolve them, keeping develop's unreleased work and main's version bump.
16. Report.
Tell the user:
- Tag push triggered
.github/workflows/release.yml.
- Watch https://github.com/ffroliva/gflow-cli/actions for the release workflow.
- On success: PyPI publish + GitHub Release with auto-generated notes.
- On failure (most common: PyPI Trusted Publishing not yet configured): point to https://pypi.org/manage/account/publishing/.
develop is now synced with main (back-merge done in step 15).
- Next development cycle starts on
develop — open ## [Unreleased] in CHANGELOG is ready.
Critical reminders
- ALWAYS cut the release branch from
develop, not main — develop carries the work.
- NEVER
--squash the release PR into main — it destroys the integration history the branch carries. Use --merge.
- NEVER skip the
main → develop back-merge (step 15) — skipping it guarantees conflicts at the next release.
- NEVER add
Co-Authored-By: Claude (or any AI co-author) to the release commit.
- NEVER force-push a release tag once it's on GitHub. Ship a PATCH fix instead.
- NEVER
--no-verify past hooks. Fix the underlying issue.
- NEVER push directly to
main — branch protection will reject it. Always use a PR.
- A
git tag -v allowedSignersFile error is benign (verify-only) — the tag is still signed and CI passes. Don't treat it as a failure.
- CONFIRM with the user before the step 13 tag push — it's the irreversible PyPI + public Release trigger.
- If quality gates fail at step 4, STOP. Surface the failures to the user.
- If doc-review fails at step 10, STOP. Fix before committing.
See also