---
name: xcode-release
description: >
  Automate Xcode TestFlight releases: bump build number, generate changelog,
  run pre-release checks with multi-agent review, commit, and push to trigger
  CI. Use when the user says "release build", "new build", "push to TestFlight",
  "bump build", or invokes /xcode-release.
allowed-tools:
  - Bash
  - Read
  - Edit
  - Grep
  - Glob
  - Skill
---

# Release Build

Automate the full TestFlight release preparation: bump build number, generate
changelog, commit, and push.

## Procedure

Follow these steps in order. Do NOT ask for confirmation — proceed through all
steps automatically. Only stop if a check fails and cannot be auto-fixed.

### Step 1: Pre-flight

Before any work, verify the workspace is ready:

1. **Clean working tree**: Run `git status --porcelain`. If there are modified or
   untracked files, stop and ask the user how to proceed. Do not stash or
   discard silently.
2. **Correct branch**: Check `git branch --show-current` is `master`. If not,
   stop.
3. **Remote is up to date**: Run `git fetch` then compare local and remote HEAD.
   If behind, stop and suggest `git pull --rebase`.

### Step 2: Read current state

Run these commands to gather context:

```
agvtool what-version
```

Extract `MARKETING_VERSION` from the Xcode project:

```
grep -m1 'MARKETING_VERSION' *.xcodeproj/project.pbxproj
```

Read the top of `CHANGELOG.md` to find the last build entry (version, build
number, and date).

### Step 3: Identify NET changes since last build

Find the commit that last bumped the build number:

```
git log --oneline --all --grep="bump build" -1
```

Then review commits AND the actual diff between that commit and HEAD:

```
git log --oneline <last-bump-commit>..HEAD
git diff --stat <last-bump-commit>..HEAD
```

If no bump commit is found, use the last 20 commits as context.

**Critical: only include NET changes.** Read the full commit history carefully.
If a feature was added in one commit but reverted or removed in a later commit,
do NOT include it in the changelog. If a bug was introduced and then fixed, do
NOT mention either. The changelog must reflect only the difference between the
previous build and this one — what a user upgrading from the last build will
actually experience. Compare the actual code diff, not just commit messages.

### Step 4: Release readiness debate

Review whether the release contains potential issues BEFORE making any changes
to the working tree. This ensures a no-go leaves the tree clean.

Scale the debate depth to the complexity of the release. Assess complexity by
reading the diff (`git diff --stat <last-bump-commit>..HEAD`) and evaluating:

- Does it touch data models, schema, storage configuration, or migrations?
- Does it change business logic, scheduling, sync, or payment flows?
- Does it affect multiple platforms or introduce new user-facing behavior?
- Could it affect existing users upgrading from the current App Store version?

Use `depth:quick` for low-complexity releases (cosmetic changes, copy updates,
minor bug fixes with no behavioral risk). Use default depth for moderate
complexity. Use `depth:deep` for high-complexity releases (data layer changes,
new features with edge cases, multi-system changes).

Invoke with:

```
/debate <depth based on complexity> — Review the changes going into this
release build for potential issues. The diff since last build is: `git diff
<last-bump-commit>..HEAD`. Reference the project's CLAUDE.md for
project-specific release requirements. Focus on: (1) data migration safety —
do any changes affect persisted models, schema, store configuration, or
migration logic? Could upgrading users lose data? (2) bugs or regressions —
do the changes introduce edge cases, missing error handling, or behavioral
changes that could break existing functionality? (3) release blockers — are
there any incomplete features, missing localizations, broken platform parity,
or other issues that should prevent this build from shipping? Produce a
go/no-go recommendation with specific concerns if any.
```

If the debate produces a **no-go** recommendation, stop and present the
concerns to the user. Do NOT proceed — the working tree is still clean.

If the debate produces a **go** recommendation (possibly with warnings), note
any warnings and proceed to Step 5.

The debate skill is a required dependency. If it is not installed, stop and
inform the user to install it before proceeding.

### Step 5: Pre-release gate

These checks validate the ENTIRE app state, not just recent changes.

Run every check target defined in the project's Makefile or CI configuration:

```
make format
make lint
make build
make test
```

If any check fails, fix the issue (or ask the user for guidance) and re-run
before continuing. Do NOT proceed with failing checks.

After checks pass, check whether `make format` modified any files:

```
git diff --name-only
```

If source files were modified by formatting, they must be staged in Step 8.

### Step 6: Bump the build number

Calculate the new build number (current + 1), then run:

```
agvtool new-version -all <NEW_BUILD_NUMBER>
```

Verify the new build number is strictly greater than the number in the last
`CHANGELOG.md` entry. If not, stop — this indicates a duplicate or regression.

### Step 7: Generate the changelog entry

Write a changelog entry following this exact format:

```
Version <MARKETING_VERSION> (Build <NEW_BUILD_NUMBER>) - <YYYY-MM-DD>

- <Platform>: <What changed — technical detail and user benefit>
- <Platform>: <What changed — technical detail and user benefit>
```

Rules for changelog entries:
- Each bullet starts with the platform scope (iOS, macOS, watchOS, or omit if
  cross-platform)
- Describe both the technical change AND the user-facing benefit in one line
- Use present tense, imperative mood
- Keep each bullet to one line
- Order: user-facing changes first, then internal/infra changes
- Cross-check: each bullet should correspond to actual changes visible in
  `git diff --stat`

Prepend the new entry to `CHANGELOG.md` directly after the `# Changelog` heading,
followed by a `---` separator before the previous entry.

Verify that `MARKETING_VERSION` in the changelog matches the value in
`project.pbxproj`.

### Step 8: Commit

Stage ALL modified files — this includes changelog, project files, plists
changed by agvtool, AND any source files modified by formatting in Step 5:

```
git add CHANGELOG.md *.xcodeproj/project.pbxproj
git add $(git diff --name-only -- '*.plist')
git add $(git diff --name-only -- '*.swift')
git add $(git diff --name-only -- '*.xcstrings')
```

Verify nothing unexpected is staged with `git diff --cached --stat`. If
unexpected files appear, stop and ask the user.

Commit message format:

```
chore: bump build to <N>, update changelog
```

Include the Co-Authored-By trailer.

### Step 9: Push and monitor

Push immediately after committing:

```
git push origin master
```

After pushing, wait up to 30 seconds for the CI run to appear, then print the
run URL:

```
gh run list --workflow=build.yml --limit 1
```

Inform the user they can monitor with `gh run watch <RUN_ID>`.

## Error handling

- If `agvtool` fails, check that the working directory contains an `.xcodeproj`
- If there are uncommitted changes before starting, warn the user and ask how to
  proceed
- If the push fails, show the error and suggest `git pull --rebase` first
- Never force-push

## Recovery after CI failure

If CI fails after push:

### Lint or test failure

The local checks diverged from CI. Fix the issue, then:
- Do NOT bump the build number again — the failed build was never uploaded
- Commit the fix with: `fix: resolve CI failure for build <N>`
- Push. CI will re-run and archive the same build number

### Archive or upload failure

This is an infrastructure issue (signing, provisioning, App Store Connect):
- Do NOT revert or re-bump — the code is correct
- Fix the CI configuration and re-run: `gh run rerun <RUN_ID> --failed`

### Never revert a build bump commit

Reverting creates confusion: the build number goes backwards, but agvtool and
App Store Connect have already seen the number. Always fix forward.
