--- name: deploy description: Use when shipping a release of Agent Sessions — bumping version, updating CHANGELOG, building, signing, notarizing, publishing appcast, and creating a GitHub release. --- # Deployment Skill (Agent Sessions) This skill is an agent-facing entrypoint that avoids duplicating the deployment runbook. ## Canonical Sources (Single Source of Truth) - Runbook: `docs/deployment.md` - Unified tool: `tools/release/deploy` (see `tools/release/deploy --help`) - Recommended pre-release QA checklist: `docs/release/pre-release-qa.md` If anything here disagrees with the runbook, follow `docs/deployment.md`. ## Workspace Policy (Hard Rule) - Always run deployment from the user's current local repository checkout. - Do not clone to temporary directories and do not switch to alternate worktrees as a deployment workaround. - If the local worktree is dirty, stop and tell the user to clean the tree first (commit, stash, or discard), then continue in the same local repo. ## QA Gate (Mandatory — Run Automatically Before Deploy) - **Always run QA automatically** before any bump/release/verify step, unless the user explicitly says to skip it (e.g. "skip QA", "no QA"). - Do not ask whether to run QA — just run it. - QA execution order: 1. **Scope** — `git log --oneline --decorate -n 30` and `git diff --name-only ..HEAD`; identify high-risk areas. 2. **Build** — `xcodebuild -project AgentSessions.xcodeproj -scheme AgentSessions -configuration Debug build` 3. **Full test suite** — `./scripts/xcode_test_stable.sh` 4. **Targeted tests** — run suites for touched high-risk areas (session parsing, usage tracking, onboarding, etc.) 5. **Warnings sweep** — flag any new actionable warnings in build output. 6. **Manual smoke reminder** — list the manual steps from `docs/release/pre-release-qa.md` §3–4 and ask the user to confirm GO/NO-GO after completing them. - If automated gates fail → stop, report failure, do not proceed to bump/release. - If user says "skip QA" or "no QA" → proceed without running, note it was skipped. **Test count.** QA reads the authoritative pass count from the `.xcresult` bundle (stdout reports per-bundle totals and this scheme has two — it under-reports) and compares it to `tools/release/test-count-baseline.txt`. A drop **warns and continues**, deliberately: it does not block a release. So the warning has to actually be read — a deleted suite still exits 0 from xcodebuild, and this line is the only thing that says so. When the count changes for a real reason, update the baseline file in the same release. ## Before Starting (Ask the User) 1. Target version (`X.Y` for major/minor releases, `X.Y.Z` only for patch releases; never ship `X.Y.0`) 2. Any headline changes (new agents, major features) that must be reflected in `docs/CHANGELOG.md` 3. Whether this is a major release that requires onboarding updates 4. Public copy updates needed for README/GitHub Pages (major changes to highlight, renamed features, or outdated wording to fix) **Do NOT ask about QA status** — QA always runs automatically as part of pre-deploy (see QA Gate above). ## Public Copy Update (Required for All Releases) ### Always update (every release) - `README.md` download link: `v{VERSION}/AgentSessions-{VERSION}.dmg` and label `Download Agent Sessions {VERSION} (DMG)` - `README.md` Option A download link (second occurrence under Install section) - `docs/index.html` download button URL and label - `docs/index.html` version meta-line (`Version {VERSION} · Free & open source · No telemetry`) ### Do NOT version-pin the site meta descriptions `docs/index.html`'s `description`, `og:description` and `twitter:description` are **deliberately version-agnostic** as of the 2026-08-29 SEO work. This checklist used to demand "mention current version + key change" there, which is how the homepage description reached 541 chars reading "Version 5.0 makes every agent a plug-in adapter…" — past the ~155-char SERP budget, and stale the day after every release. Leave them alone. If the product's positioning genuinely changes, rewrite them on their own merits and keep the budget: **title ≤60, description ≤155.** Adding a version string is the one edit that is always wrong. See the `reference-docs-site-seo-conventions` memory. ### Update for minor/major or user-visible feature releases - `README.md` "What's New in X.Y" section: update heading to new version, rewrite TL;DR and Highlights to reflect this release's key changes (do not keep old version's copy) - `docs/index.html` hero/feature copy if features were renamed or new agents added ### Never add - Versioned "What's New in X.Y" section to `docs/index.html` - Detailed release notes to README or website (those live in `docs/CHANGELOG.md`) ## Never Announce a Bug the User Never Had (Hard Rule) **A fix to a feature that ships in this same release is not a Bug Fix. It is development.** When a feature is new in X.Y, every defect found and fixed in it before X.Y shipped was never in anyone's hands. Listing those under "Bug Fixes" invents a history of breakage users never experienced, and buries the actual feature under a list of things that sound broken. Ship the feature; the fixes are part of it. Before writing any Bug Fix entry, ask: **which released version had this bug?** If the answer is "none — the code is new in this release", the entry does not exist. Fold anything user-visible into the feature's own Highlight instead. Worked example (4.8, Grok CLI's first release): - ❌ "A Grok session shows the title its own sidecar records" — Grok shipped in 4.8; no user ever saw the wrong title. - ❌ "Grok transcripts open with content in them" — same. - ❌ "A Grok session's message count matches its transcript" — same. - ✅ "Grok CLI is the eleventh current agent source…" — the Highlight, which already says transcripts, images, Analytics and resume work. Mixed entries need splitting, not deleting: a fix spanning shipped **and** new sources is real for the shipped ones. Describe it in terms of those, and drop the new source from the list. In 4.8 the CLI PATH-masking fix covered Cursor, Kimi and Pi (all shipped) plus Grok (new) — it stayed, naming only the three. This applies identically to `docs/CHANGELOG.md`, the Sparkle notes, the GitHub release body, and the README "What's New". The changelog is the source all of them derive from, so fix it there first. ### After pushing - Verify GitHub Pages reflects updated `docs/index.html` (check the download button and version meta-line; the meta descriptions should read the same as before the release) ## Pre-Deploy Checklist (Run Before Bump) - [ ] `docs/CHANGELOG.md` `[Unreleased]` section has full, accurate content for this release (this is the only changelog — root `CHANGELOG.md` is a pointer to it, not a copy to sync) - [ ] Every Bug Fix entry names a defect that existed in a **released** version — no fixes to features shipping in this same release (see "Never Announce a Bug the User Never Had") - [ ] In-app What's New has an entry for this version in `AgentSessions/Onboarding/Models/WhatsNewCatalog.swift` — both a `teasers` line and a `bundled` array. `hasContent` goes true on the auto-generated new-provider row alone, so a forgotten release still shows the card, just with one generic line and no teaser. Do not author a row for a new source by hand: `providerHighlights(for:)` generates it from `versionIntroduced`, and authoring it again shows it twice. - [ ] README.md download links updated to new version (both occurrences) - [ ] README.md "What's New" section updated to new version heading + rewritten highlights - [ ] `docs/index.html` download button URL, label, and version meta-line updated - [ ] `docs/index.html` meta descriptions left **unchanged** — they are version-agnostic on purpose (see "Do NOT version-pin the site meta descriptions") - [ ] All above files committed before running `deploy bump` (or bump will overwrite) ## Sparkle Notes Are Short and Fun (Hard Rule) Sparkle notes appear in a small update window. Nobody reads prose there. The 5.0 notes shipped as full changelog paragraphs and had to be republished after the fact — don't repeat that. - **Highlight**: 2–4 sentences, hard max. Lead with the change, keep one concrete number if there is one, cut everything a curious reader can find in the changelog. - **Feature / Bug Fix bullets**: 1–2 sentences. One for what changed, at most one for why it was wrong before. A good bug-fix line can be a single sentence ("Five agents used to skip it silently."). - **Whole notes**: aim under ~250 words. If the preview scrolls, it's too long. - **Tone**: dry fun is welcome — "takes no for an answer", "Bring the agent you use". Personality yes, marketing-speak no, emoji never. - The full detail lives in `docs/CHANGELOG.md`; the notes may compress it freely. If an entry can't be compressed without losing the point, the changelog entry is overweight — tighten it there first (changelog entries also don't need to be essays). - Links in changelog entries that feed the notes must be **absolute URLs** — relative links break in the appcast and the GitHub release body. - To republish notes after a release: edit `docs/CHANGELOG.md`, then `python3 tools/release/sparkle_release_notes.py --version --changelog docs/CHANGELOG.md --appcast docs/appcast.xml --github-url --lint --out-text /tmp/notes.txt`, then `gh release edit v --notes-file /tmp/notes.txt`, commit and push the appcast. ## Sparkle Release Notes (Approval Gate) - The release pipeline generates **structured Sparkle notes** from `docs/CHANGELOG.md`: - Highlights or grouped current-release changes - Other changes (summary) - Reminder from the baseline release (for patch releases: `A.B`) - During `tools/release/deploy release `, the deploy script prints a **Sparkle release notes preview** after build/sign/notarization and appcast validation. - Treat the preview as user-facing product copy, not raw commit history: - Lead with the headline change the user should care about. - Do not include internal cleanup, validation fixes, or pre-release stabilization as “Bug Fixes” if users never received that broken behavior. - If the preview is misleading, stop and edit `docs/CHANGELOG.md` before publishing. - If `SKIP_CONFIRM` is not `1`, it will pause and ask for approval before publishing (pushing appcast, updating Homebrew, updating the GitHub release). - `SKIP_CONFIRM=1` requires `RELEASE_NOTES_REVIEWED=1` at the appcast publish gate; set it only after manually inspecting the Sparkle preview or for a rerun whose notes were already reviewed. - The notes generator fails before publishing if notes contain obvious internal/process wording or put Bug Fixes ahead of a headline section. - GitHub Release notes must use the same curated/linted notes as Sparkle, not raw commit history or raw changelog extraction. - If the current release has no structured bullets, the generator adds a fallback highlight: `Small bug fixes and stability improvements.` ## Standard Workflow (Use the Unified Tool) ```bash tools/release/deploy changelog [FROM_TAG] tools/release/deploy bump [patch|minor|major] git push origin main tools/release/deploy release [--dry-run] tools/release/deploy verify ``` ## Failure Handling - First stop: `docs/deployment.md` → Troubleshooting, logs, and rollback guidance. - Rollback only after reviewing logs: `tools/release/rollback-release.sh `. ### Read the real exit code, and check what actually published Two traps, both hit during 5.1.1: - **Do not wrap the release in `; echo "EXIT: $?"`.** The wrapper's own status is what the caller sees, so a pipeline that correctly exited 4 was reported as 0 and briefly looked like a tooling bug. Run `tools/release/deploy release ` on its own and read its status. - **A completed run is not a published release.** Confirm against the world, not the log: ```bash gh release view v --json tagName,isDraft,assets git ls-remote --tags origin v grep -n "sparkle:shortVersionString" docs/appcast.xml ``` Then `tools/release/deploy verify ` for the full check. A failure before the publish steps leaves nothing public — no release, no tag, appcast unchanged — which is the state to expect after an aborted build. ### Watching a long run The release log embeds the entire QA test output, so a bare keyword grep matches **test names**, not events — "Aborted", "Homebrew" and "rollback" all produced false alarms during 5.1.1. Anchor on the script's own line markers (`^==> `, `^✅ `, `^ERROR: `) and run the pattern against the live log before trusting it. Note also that `ERROR: Release QA stamp is not valid` is expected and self-heals whenever HEAD moved after `deploy qa` — it re-runs QA rather than failing. When matching newest-file-by-glob (`/tmp/notarization--*.log`), guard on mtime or you will read the previous run's artifacts.