Release Workflow (12 Phases)
Phase 1: Milestone Check
- List open milestones via
gh api
- Verify 0 open issues in target milestone
- Stop if issues remain (ask user to move/close)
Phase 2: Version Number
- Read current version from pyproject.toml (authoritative)
- Milestone title = target version
- Confirm with user
Phase 3: Create Release Branch
git checkout -b release/vX.Y.Z from main
Phase 4: Bump Version
Update ALL files (pyproject.toml is authoritative):
pyproject.toml — version = "X.Y.Z"
src/apple_mail_fast_mcp/__init__.py — __version__ = "X.Y.Z"
.claude/CLAUDE.md — **Version:** vX.Y.Z
README.md — all version references
mcpb/manifest.json — "version": "X.Y.Z" (the .mcpb bundle; check_version_sync.sh enforces it)
.claude-plugin/marketplace.json + .claude-plugin/plugin.json — every "version": "X.Y.Z" (the Claude Code plugin; check_version_sync.sh enforces all occurrences)
Phase 5: Generate CHANGELOG
- Commits since last tag:
git log v{prev}..HEAD --oneline
- PRs since last release:
gh pr list --state merged --search "merged:>YYYY-MM-DD"
- Keep a Changelog format:
## [X.Y.Z] - YYYY-MM-DD
- Categories: Added, Changed, Fixed, Removed
Phase 6: Test Coverage Review
- Run coverage:
make coverage
- Compare against
fail_under in pyproject.toml
- Audit changed files for adequate coverage
Phase 7: Code Review
- Launch
superpowers:code-reviewer against cumulative diff
- Critical issues block release
Phase 8: Documentation Review
- README, CLAUDE.md, CHANGELOG, docs/**, tool docstrings, skills
- The content-drift gate (
check_docs.sh, Phase 9) automates the tool-set /
removed-name / cross-ref / eval-description-sync parts; this phase is the
human read for things it can't check (accuracy, tone, completeness).
Phase 8.5: Refresh derived artifacts (mandatory, #288)
Derived artifacts rot silently between releases — refresh them against the
release commit so a stale snapshot can't ship:
make eval-descriptions — regenerate the blind-eval tool descriptions;
commit if changed. (check_docs.sh also fails on drift here.)
- Re-capture the benchmark baseline (#216):
MAIL_TEST_MODE=true MAIL_TEST_ACCOUNT=<acct> uv run pytest tests/benchmarks/ --run-benchmark --capture-baseline (needs real Mail.app); commit the refreshed
baseline.json.
- Re-run the blind agent eval (#219) and refresh its scored snapshot (needs
OPENROUTER_API_KEY); commit.
Steps 2 and 3 are enforced by ./scripts/check_release_artifacts.sh (Phase 9):
the benchmark baseline and the eval snapshot are version-stamped, and the gate
fails the release if a stamp is stale. If an artifact genuinely can't be
refreshed for this release (e.g. a CI/docs-only release), don't skip silently —
record a waiver line (with a tracking issue) in release_artifact_waivers.txt.
See docs/guides/RELEASE_ARTIFACTS.md. (#356)
Phase 9: Validation
Run ALL checks (stop on failure):
./scripts/check_version_sync.sh
./scripts/check_client_server_parity.sh
./scripts/check_complexity.sh
make test
make test-e2e — mandatory (requires MAIL_TEST_MODE=true + a test Mail.app account). CI excludes e2e, so this is the only gate that catches a stale e2e failure. A pre-existing failure on main is a release-blocker, not a known issue to ship around (#257).
./scripts/check_dependencies.sh — hard-fails only on advisories in direct deps (fastmcp/imapclient); transitive advisories are warnings (exit 0), surfaced continuously off the release path by .github/workflows/dependency-audit.yml so they don't block a release (#296). A direct-dep advisory still blocks — bump the pin and re-run.
./scripts/check_applescript_safety.sh
./scripts/check_docs.sh — doc/artifact drift gate (tool-set coverage, removed-name, cross-refs, eval-description sync) (#288)
./scripts/check_release_artifacts.sh — fails if the benchmark baseline or eval snapshot isn't version-stamped for this release (Phase 8.5 #2/#3) and isn't waived in release_artifact_waivers.txt (#356)
Phase 10: Commit, Push, PR
- Commit:
"release: vX.Y.Z"
- Push:
-u origin release/vX.Y.Z
- PR to main via
gh pr create
Phase 11: Merge, Tag, Push Tag
- Rebase merge:
gh pr merge NNN --rebase --delete-branch
- Tag on main:
./scripts/create_tag.sh vX.Y.Z
- Push tag:
git push origin vX.Y.Z
- Verify:
git describe --tags --abbrev=0
Phase 12: Close Milestone
gh api -X PATCH repos/{owner}/{repo}/milestones/{number} -f state=closed
Notes
- CHANGELOG only updated on release branches
- Tags created on main AFTER PR merge
- Use rebase merge (linear history required)
- Each phase has explicit stop conditions — if it fails, stop and report
1---2name: release3description: Use when the user wants to release a new version. Orchestrates the full release workflow including milestone check, version bump, changelog generation, validation, tagging, and PR creation. Also use when discussing release planning or version management.4---56# Release Workflow (12 Phases)78## Phase 1: Milestone Check9- List open milestones via `gh api`10- Verify 0 open issues in target milestone11- Stop if issues remain (ask user to move/close)1213## Phase 2: Version Number14- Read current version from pyproject.toml (authoritative)15- Milestone title = target version16- Confirm with user1718## Phase 3: Create Release Branch19- `git checkout -b release/vX.Y.Z` from main2021## Phase 4: Bump Version22Update ALL files (pyproject.toml is authoritative):231. `pyproject.toml` — `version = "X.Y.Z"`242. `src/apple_mail_fast_mcp/__init__.py` — `__version__ = "X.Y.Z"`253. `.claude/CLAUDE.md` — `**Version:** vX.Y.Z`264. `README.md` — all version references275. `mcpb/manifest.json` — `"version": "X.Y.Z"` (the .mcpb bundle; `check_version_sync.sh` enforces it)286. `.claude-plugin/marketplace.json` + `.claude-plugin/plugin.json` — every `"version": "X.Y.Z"` (the Claude Code plugin; `check_version_sync.sh` enforces all occurrences)2930## Phase 5: Generate CHANGELOG31- Commits since last tag: `git log v{prev}..HEAD --oneline`32- PRs since last release: `gh pr list --state merged --search "merged:>YYYY-MM-DD"`33- Keep a Changelog format: `## [X.Y.Z] - YYYY-MM-DD`34- Categories: Added, Changed, Fixed, Removed3536## Phase 6: Test Coverage Review37- Run coverage: `make coverage`38- Compare against `fail_under` in pyproject.toml39- Audit changed files for adequate coverage4041## Phase 7: Code Review42- Launch `superpowers:code-reviewer` against cumulative diff43- Critical issues block release4445## Phase 8: Documentation Review46- README, CLAUDE.md, CHANGELOG, docs/**, tool docstrings, skills47- The content-drift gate (`check_docs.sh`, Phase 9) automates the tool-set /48 removed-name / cross-ref / eval-description-sync parts; this phase is the49 human read for things it can't check (accuracy, tone, completeness).5051## Phase 8.5: Refresh derived artifacts (mandatory, #288)52Derived artifacts rot silently between releases — refresh them against the53release commit so a stale snapshot can't ship:541. `make eval-descriptions` — regenerate the blind-eval tool descriptions;55 commit if changed. (`check_docs.sh` also fails on drift here.)562. Re-capture the benchmark baseline (#216): `MAIL_TEST_MODE=true57 MAIL_TEST_ACCOUNT=<acct> uv run pytest tests/benchmarks/ --run-benchmark58 --capture-baseline` (needs real Mail.app); commit the refreshed59 `baseline.json`.603. Re-run the blind agent eval (#219) and refresh its scored snapshot (needs61 `OPENROUTER_API_KEY`); commit.6263Steps 2 and 3 are **enforced** by `./scripts/check_release_artifacts.sh` (Phase 9):64the benchmark baseline and the eval snapshot are version-stamped, and the gate65fails the release if a stamp is stale. If an artifact genuinely can't be66refreshed for this release (e.g. a CI/docs-only release), don't skip silently —67record a waiver line (with a tracking issue) in `release_artifact_waivers.txt`.68See [docs/guides/RELEASE_ARTIFACTS.md](../../../docs/guides/RELEASE_ARTIFACTS.md). (#356)6970## Phase 9: Validation71Run ALL checks (stop on failure):721. `./scripts/check_version_sync.sh`732. `./scripts/check_client_server_parity.sh`743. `./scripts/check_complexity.sh`754. `make test`765. `make test-e2e` — **mandatory** (requires `MAIL_TEST_MODE=true` + a test Mail.app account). CI excludes e2e, so this is the only gate that catches a stale e2e failure. A pre-existing failure on `main` is a **release-blocker**, not a known issue to ship around (#257).776. `./scripts/check_dependencies.sh` — hard-fails only on advisories in **direct** deps (`fastmcp`/`imapclient`); transitive advisories are warnings (exit 0), surfaced continuously off the release path by `.github/workflows/dependency-audit.yml` so they don't block a release (#296). A direct-dep advisory still blocks — bump the pin and re-run.787. `./scripts/check_applescript_safety.sh`798. `./scripts/check_docs.sh` — doc/artifact drift gate (tool-set coverage, removed-name, cross-refs, eval-description sync) (#288)809. `./scripts/check_release_artifacts.sh` — fails if the benchmark baseline or eval snapshot isn't version-stamped for this release (Phase 8.5 #2/#3) and isn't waived in `release_artifact_waivers.txt` (#356)8182## Phase 10: Commit, Push, PR83- Commit: `"release: vX.Y.Z"`84- Push: `-u origin release/vX.Y.Z`85- PR to main via `gh pr create`8687## Phase 11: Merge, Tag, Push Tag88- Rebase merge: `gh pr merge NNN --rebase --delete-branch`89- Tag on main: `./scripts/create_tag.sh vX.Y.Z`90- Push tag: `git push origin vX.Y.Z`91- Verify: `git describe --tags --abbrev=0`9293## Phase 12: Close Milestone94- `gh api -X PATCH repos/{owner}/{repo}/milestones/{number} -f state=closed`9596## Notes97- CHANGELOG only updated on release branches98- Tags created on main AFTER PR merge99- Use rebase merge (linear history required)100- Each phase has explicit stop conditions — if it fails, stop and report