release-prep
Release Prep
Quick Ref: Automated pre-release checklist: version bump, changelog, privacy check, store metadata, archive. Output:
.agents/research/YYYY-MM-DD-release-prep-vX.Y.Z.md
YOU MUST EXECUTE THIS WORKFLOW. Do not just describe it.
Pre-flight: Git Safety Check
git status --short
If uncommitted changes exist:
AskUserQuestion with questions:
[
{
"question": "You have uncommitted changes. Commit before proceeding?",
"header": "Git",
"options": [
{"label": "Commit first (Recommended)", "description": "Save current work so you can revert if this skill modifies files"},
{"label": "Continue without committing", "description": "Proceed — I accept the risk"}
],
"multiSelect": false
}
]
If "Commit first": Ask for a commit message, stage changed files, and commit. Then proceed.
Step 1: Determine Release Details
AskUserQuestion with questions:
[
{
"question": "What type of release is this?",
"header": "Release type",
"options": [
{"label": "Patch (X.Y.Z+1)", "description": "Bug fixes only, no new features"},
{"label": "Minor (X.Y+1.0)", "description": "New features, backwards compatible"},
{"label": "Major (X+1.0.0)", "description": "Breaking changes or major milestone"},
{"label": "Hotfix", "description": "Urgent production fix"}
],
"multiSelect": false
}
]
Step 2: Find Current Version
2.1: Locate Version in Project
# Find MARKETING_VERSION in pbxproj
Grep pattern="MARKETING_VERSION" glob="**/*.pbxproj" output_mode="content"
# Find CURRENT_PROJECT_VERSION (build number)
Grep pattern="CURRENT_PROJECT_VERSION" glob="**/*.pbxproj" output_mode="content"
# Check for version in Info.plist (older projects)
Grep pattern="CFBundleShortVersionString|CFBundleVersion" glob="**/*.plist" output_mode="content"
2.2: Check Last Git Tag
# Get the most recent version tag
git tag --sort=-v:refname | head -5
# Get the most recent tag with its date
git log --tags --simplify-by-decoration --format="%ai %d" | head -5
2.3: Calculate New Version
Based on release type and current version:
| Current | Patch | Minor | Major |
|---|---|---|---|
| 1.2.3 | 1.2.4 | 1.3.0 | 2.0.0 |
Record:
- Current version: X.Y.Z → New version: X.Y.Z
- Current build: N → New build: N+1
Step 3: Bump Version Numbers
3.1: Update MARKETING_VERSION
# Find the exact line in pbxproj
Grep pattern="MARKETING_VERSION = " glob="**/*.pbxproj" output_mode="content"
# Edit each occurrence (there may be multiple — one per build configuration)
# Use Edit with replace_all=true
3.2: Update CURRENT_PROJECT_VERSION
# Same approach — find and replace all occurrences
Grep pattern="CURRENT_PROJECT_VERSION = " glob="**/*.pbxproj" output_mode="content"
3.3: Verify
# Confirm versions updated correctly
Grep pattern="MARKETING_VERSION|CURRENT_PROJECT_VERSION" glob="**/*.pbxproj" output_mode="content"
Step 4: Generate Changelog
4.1: Get Commits Since Last Release
# Commits since last tag, one per line
git log <last_tag>..HEAD --oneline --no-merges
# If no tags exist, use a date range
git log --since="2026-01-01" --oneline --no-merges
4.2: Categorize Changes
Sort commits into categories:
## What's New in [version]
### Features
- [New capabilities added]
### Improvements
- [Enhancements to existing features]
### Bug Fixes
- [Issues resolved]
### Under the Hood
- [Technical changes users don't directly see]
4.3: Write App Store "What's New" Text
Draft user-facing release notes (max 4000 characters):
- Lead with the most impactful change
- Use plain language (no technical jargon)
- 3-8 bullet points is ideal
Step 5: Code Readiness Check
5.1: Test Status
Ask the user before running tests (they may take a while or require specific configuration):
AskUserQuestion with questions:
[
{
"question": "Should I run the test suite now?",
"header": "Tests",
"options": [
{"label": "Yes, run tests", "description": "Run xcodebuild test (may take a few minutes)"},
{"label": "Skip tests", "description": "I'll run tests separately or they've already passed"},
{"label": "Check last results", "description": "Just check the most recent test outcome"}
],
"multiSelect": false
}
]
If running tests, determine the scheme and simulator:
# Find available schemes
xcodebuild -list -json 2>/dev/null | head -30
# Run tests — adjust scheme and destination for the project
xcodebuild test -scheme <AppName> -destination 'platform=iOS Simulator,name=iPhone 16' -quiet 2>&1 | tail -10
5.2: Check for Debug Code Left Behind
# Pass 1: Find all debug prints
Grep pattern="print\(|NSLog\(|debugPrint\(" glob="**/*.swift" output_mode="files_with_matches"
# Pass 2: For each flagged file, read it and check if prints are inside #if DEBUG
# Prints inside #if DEBUG blocks are SAFE — they're stripped from release builds
# Only report prints that are NOT behind #if DEBUG as issues
# Find TODO/FIXME that might be release blockers
# NOTE: not all TODOs are blockers — read each to assess urgency
Grep pattern="(TODO|FIXME|HACK|XXX):" glob="**/*.swift" output_mode="content"
# Find hardcoded test data
Grep pattern="localhost|127\\.0\\.0\\.1|test@|example\\.com" glob="**/*.swift" output_mode="content"
5.3: Check for Warnings
# Build and count warnings — adjust scheme and destination for the project
xcodebuild build -scheme <AppName> -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | grep "warning:" | wc -l
5.4: Deployment Target
# Verify minimum deployment target is intentional
Grep pattern="IPHONEOS_DEPLOYMENT_TARGET" glob="**/*.pbxproj" output_mode="content"
Step 6: Privacy & Compliance
6.1: Privacy Manifest
# Check if PrivacyInfo.xcprivacy exists
Glob pattern="**/PrivacyInfo.xcprivacy"
# If found, read its contents
# Check for required API declarations (iOS 17+)
6.2: Required API Reasons
Check if the app uses APIs that require privacy reason declarations:
# File timestamp APIs
Grep pattern="creationDate|modificationDate|fileModificationDate" glob="**/*.swift"
# Disk space APIs
Grep pattern="volumeAvailableCapacity|systemFreeSize" glob="**/*.swift"
# User defaults APIs — if >0, requires NSPrivacyAccessedAPICategoryUserDefaults in privacy manifest
Grep pattern="UserDefaults" glob="**/*.swift" output_mode="count"
# System boot time APIs
Grep pattern="systemUptime|processInfo\.systemUptime" glob="**/*.swift"
If any are found but not declared in the privacy manifest, flag it.
6.3: Third-Party SDK Privacy Manifests
# Check that third-party packages include privacy manifests
Glob pattern="**/PrivacyInfo.xcprivacy"
# Cross-reference with Package.resolved to identify SDKs missing manifests
6.4: App Transport Security (ATS)
# Check for ATS exceptions — NSAllowsArbitraryLoads disables HTTPS enforcement
Grep pattern="NSAllowsArbitraryLoads" glob="**/*.plist" output_mode="content"
# Check for per-domain exceptions (more targeted, may be acceptable)
Grep pattern="NSExceptionDomains" glob="**/*.plist" output_mode="content"
If NSAllowsArbitraryLoads = true is found, flag as a potential App Store blocker — Apple may reject apps with blanket ATS exceptions without justification.
6.5: Entitlements Check
# Find entitlements files
Glob pattern="**/*.entitlements"
# If found, read contents and verify capabilities match what the app actually uses
# Unused entitlements should be removed before submission
Step 7: App Store Metadata
7.1: App Icon
# Verify app icon asset exists and has all required sizes
Glob pattern="**/AppIcon.appiconset/Contents.json"
# If found, read the Contents.json to check for missing sizes
# A complete icon set prevents App Store Connect rejection
7.2: Launch Screen
# Check for launch screen configuration (required for App Store)
Grep pattern="UILaunchScreen|UILaunchStoryboardName" glob="**/*.plist" output_mode="content"
# Or check for LaunchScreen storyboard
Glob pattern="**/LaunchScreen.storyboard"
7.3: Screenshots
# Check if screenshot assets exist
Glob pattern="**/Screenshots/**/*.png"
Glob pattern="**/Screenshots/**/*.jpg"
# If found, verify dimensions match App Store requirements
# Required: 6.9" (1320x2868), 6.5" (1242x2688), 5.5" (1242x2208)
for f in $(find . -path "*/Screenshots/*.png" -o -path "*/Screenshots/*.jpg" 2>/dev/null); do
sips -g pixelWidth -g pixelHeight "$f" 2>/dev/null
done
Ask user: Are screenshots up to date with current UI?
7.4: URLs
# Check for support URL in project settings
Grep pattern="support.*url|privacy.*url|marketing.*url" glob="**/*.plist" -i output_mode="content"
Remind user to verify:
- Support URL is valid and loads
- Privacy Policy URL is valid and loads
- Marketing URL is valid (if applicable)
7.5: Localization Completeness
# Find all localization directories
Glob pattern="**/*.lproj"
# Check for missing keys across localization files
Glob pattern="**/*.lproj/Localizable.strings"
Glob pattern="**/*.lproj/Localizable.xcstrings"
# If multiple languages exist, verify key counts match across .lproj directories
Step 8: Archive Readiness
8.1: Signing Check
# Check code signing settings
Grep pattern="CODE_SIGN_IDENTITY|DEVELOPMENT_TEAM|PROVISIONING_PROFILE" glob="**/*.pbxproj" output_mode="content"
8.2: Build Configuration
# Check optimization settings for Release
Grep pattern="SWIFT_OPTIMIZATION_LEVEL|GCC_OPTIMIZATION_LEVEL" glob="**/*.pbxproj" output_mode="content"
8.3: Package Dependencies
# Check for Package.resolved — ensures reproducible builds
Glob pattern="**/Package.resolved"
# If found, read it to check for:
# - Deprecated or archived packages (note the URLs for user review)
# - Very old versions that may have known issues
Step 9: Generate Report
Display the full checklist, changelog, code readiness, privacy status, and metadata summary inline, then write report to .agents/research/YYYY-MM-DD-release-prep-vX.Y.Z.md:
# Release Prep Report — vX.Y.Z
**Date:** YYYY-MM-DD
**Version:** X.Y.Z (Build NN)
**Release Type:** Patch / Minor / Major
**Status:** Ready / Blocked
## Version Bump
- [x] MARKETING_VERSION: X.Y.Z-1 → X.Y.Z
- [x] CURRENT_PROJECT_VERSION: N-1 → N
## Changelog
### What's New in X.Y.Z
**Features:**
- [list]
**Bug Fixes:**
- [list]
### App Store "What's New" (copy-paste ready)
[User-facing release notes text]
## Code Readiness
| Check | Status | Notes |
|-------|--------|-------|
| Tests passing | ✓ / ✗ | X tests, Y passed |
| Debug code removed | ✓ / ✗ | N non-DEBUG prints found |
| No blocking TODOs | ✓ / ✗ | List if any |
| Build warnings | ✓ / ✗ | N warnings |
| Deployment target | ✓ | iOS X.Y |
## Privacy & Compliance
| Check | Status | Notes |
|-------|--------|-------|
| Privacy manifest exists | ✓ / ✗ | |
| API reasons declared | ✓ / ✗ | |
| Third-party manifests | ✓ / ✗ | |
| ATS configured | ✓ / ✗ | |
| Entitlements match | ✓ / ✗ | |
## App Store Metadata
| Check | Status | Notes |
|-------|--------|-------|
| App icon complete | ✓ / ✗ | |
| Launch screen exists | ✓ / ✗ | |
| Screenshots current | ✓ / ✗ | |
| What's New text | ✓ | See above |
| Support URL valid | ✓ / ✗ | |
| Privacy URL valid | ✓ / ✗ | |
| Localizations complete | ✓ / ✗ / N/A | |
## Release Commands
When ready:
```bash
# Archive (or use Xcode: Product → Archive)
xcodebuild archive -scheme <AppName> -archivePath build/<AppName>.xcarchive
# Tag the release
git tag -a vX.Y.Z -m "Release X.Y.Z"
git push origin vX.Y.Z
Post-Release Monitoring
- Verify app is live on App Store
- Monitor crash reports for 48 hours
- Check App Store reviews
---
## Step 10: Follow-up
AskUserQuestion with questions: [ { "question": "How would you like to proceed?", "header": "Next", "options": [ {"label": "Fix blocking issues", "description": "Address any ✗ items before release"}, {"label": "Archive and submit", "description": "Ready to build for App Store"}, {"label": "Report is sufficient", "description": "I'll handle the rest manually"} ], "multiSelect": false } ]
---
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Can't find pbxproj | Check for `.xcodeproj` directory: `Glob pattern="**/*.xcodeproj"` |
| Multiple MARKETING_VERSION lines | Normal — one per build config (Debug/Release). Use `replace_all=true` |
| No git tags exist | Use date-based log: `git log --since="YYYY-MM-DD"` |
| Privacy manifest missing | Flag as blocker — required for App Store since iOS 17 |
| Tests fail | Flag as blocker — don't release with failing tests |
| Print statements found | Read each — only flag prints NOT inside `#if DEBUG` blocks |
| App icon missing sizes | Check `Contents.json` — modern Xcode uses a single 1024x1024 image |
| NSAllowsArbitraryLoads = true | Flag as blocker — Apple requires justification for blanket ATS exceptions |
| No launch screen | Flag as blocker — required for App Store submission |
| Localization keys mismatch | Compare key counts across `.lproj` directories — missing keys show default language |
| Can't determine scheme | Run `xcodebuild -list -json` to find available schemes |