# Release

> 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.

- Skill: `s-morgan-jeffries/release` (Agent Skill)
- Install (CLI): `npx skillmds@latest add s-morgan-jeffries/release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/s-morgan-jeffries/release/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: s-morgan-jeffries (https://skillmd.com/u/s-morgan-jeffries)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/s-morgan-jeffries/release

---


# 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):
1. `pyproject.toml` — `version = "X.Y.Z"`
2. `src/apple_mail_fast_mcp/__init__.py` — `__version__ = "X.Y.Z"`
3. `.claude/CLAUDE.md` — `**Version:** vX.Y.Z`
4. `README.md` — all version references
5. `mcpb/manifest.json` — `"version": "X.Y.Z"` (the .mcpb bundle; `check_version_sync.sh` enforces it)
6. `.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:
1. `make eval-descriptions` — regenerate the blind-eval tool descriptions;
   commit if changed. (`check_docs.sh` also fails on drift here.)
2. 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`.
3. 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](../../../docs/guides/RELEASE_ARTIFACTS.md). (#356)

## Phase 9: Validation
Run ALL checks (stop on failure):
1. `./scripts/check_version_sync.sh`
2. `./scripts/check_client_server_parity.sh`
3. `./scripts/check_complexity.sh`
4. `make test`
5. `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).
6. `./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.
7. `./scripts/check_applescript_safety.sh`
8. `./scripts/check_docs.sh` — doc/artifact drift gate (tool-set coverage, removed-name, cross-refs, eval-description sync) (#288)
9. `./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

