Release simctlhouse
Publishes version X.Y.Z of this repository: a GitHub release with the notarized tarball, and the formula in the tap.
scripts/release.sh does the build, signing, notarization and packaging; this skill does everything around it.
docs/how-to.md ("Make a release") and D27 in docs/decisions.md explain the design.
Fixed names:
| What | Value |
|---|---|
| GitHub repository | juraj-blahunka/simctlhouse (remote origin) |
| Tap checkout | ../homebrew-tap, GitHub juraj-blahunka/homebrew-tap, used as juraj-blahunka/tap |
| Formula in the tap | Formula/simctlhouse.rb |
| Tag | vX.Y.Z |
| Tarball | dist/simctlhouse-X.Y.Z-macos.tar.gz |
| Release notes | dist/release-notes-X.Y.Z.md (dist/ is ignored by git) |
Run every command from the repository root unless a step says otherwise.
Stop at the first failure, report what failed with its output, and do not work around it (no --skip-notarize, --allow-dirty, --force or --no-verify).
1. Pick the version
Use the version the user gave.
Otherwise propose one and ask: list the changes since the last tag with git log --oneline $(git describe --tags --abbrev=0 2>/dev/null)..HEAD (all commits if there is no tag), then bump minor for feat: commits and patch for fixes only.
2. Check preconditions
Check all of these before changing anything, and report every failure at once:
git rev-parse --abbrev-ref HEAD # main
git status --porcelain --untracked-files=no # empty
git fetch origin && git status -sb | head -1 # not behind origin/main
git ls-remote --tags origin "refs/tags/vX.Y.Z" # empty: the version is unreleased
gh auth status # logged in
git -C ../homebrew-tap status --porcelain # tap exists and is clean
git -C ../homebrew-tap pull --ff-only # tap is up to date
If origin or the tap is missing, stop and tell the user what to create; never create GitHub repositories yourself.
A new tap needs the GitHub repository juraj-blahunka/homebrew-tap, cloned to ../homebrew-tap, with a Formula/ directory.
3. Bump the version
If version: in Sources/simctlhouse/SimctlHouse.swift is not X.Y.Z, change it there, run swift build and swift test, and commit only that file:
git commit -m "chore: release X.Y.Z" Sources/simctlhouse/SimctlHouse.swift
4. Write the release notes with the user
Draft the notes from what changed since the last release:
git log --format='%h %s%n%b' $(git describe --tags --abbrev=0 2>/dev/null)..HEAD
git diff --stat $(git describe --tags --abbrev=0 2>/dev/null)..HEAD
Read the commits and, where a subject is unclear, the diff itself; the notes describe what users of the CLI and the skill notice, not how the code changed.
-
Start with one or two sentences on what the release is about.
-
Then sections, leaving out empty ones:
## New,## Changed,## Fixed, and## Breakingfirst if anything breaks scripts or agents (renamed commands or flags, changed output, changed exit codes). -
One bullet per change, with the command or flag in backticks, e.g.
simctlhouse skill install. -
Leave out
chore:,refactor:,test:and docs-only commits unless users would notice them. -
End with how to get it, exactly like this:
## Install ```sh brew install juraj-blahunka/tap/simctlhouse # or: brew upgrade simctlhouse simctlhouse skill install # once, to link the agent skill ```
Write the draft to dist/release-notes-X.Y.Z.md and show the user the full text.
Ask them to accept it or say what to change; apply their changes to the file and show the whole text again.
Repeat until they accept it explicitly, and do not go on to the build before that.
The file is published verbatim, so it must not contain anything addressed to the user, such as questions or TODOs.
5. Build the release
scripts/release.sh X.Y.Z
It takes a few minutes, most of it waiting for Apple's notarization; allow up to 10 minutes.
It refuses to run on a version mismatch, uncommitted sources, or a tag pointing elsewhere, runs the tests and lint, builds through scripts/build.sh, checks that Apple published the notarization ticket, and verifies its own tarball.
It ends by printing the tarball path, its sha256, and dist/simctlhouse.rb.
6. Smoke test the tarball
Run the exact bytes you are about to publish against real simulators:
scripts/smoke-test.sh dist/simctlhouse-X.Y.Z-macos.tar.gz
It unpacks the tarball into a temporary directory, links the skill there, acquires a device in a throwaway SIMCTLHOUSE_HOME, checks that the daemon started and can be stopped and started again, releases the device and waits for the daemon to shut it down and exit on its own, deletes it, and checks that every simulator that existed before still exists.
A first boot takes about 30 seconds; allow up to 5 minutes on a loaded machine.
If it reports missing simulators, show the list to the user: other agents may have deleted their own devices meanwhile, and only the user can tell.
7. Confirm before publishing
Everything from here is public and hard to undo.
Show the user the version, the commit (git log --oneline -1), the sha256, the smoke test result, the accepted release notes file, and the steps below, and wait for an explicit go-ahead.
8. Publish
In this order, so that every published step points at something that already exists:
git push origin main
git tag -a vX.Y.Z -m "simctlhouse X.Y.Z"
git push origin vX.Y.Z
gh release create vX.Y.Z dist/simctlhouse-X.Y.Z-macos.tar.gz \
--repo juraj-blahunka/simctlhouse --verify-tag --title "simctlhouse X.Y.Z" --notes-file dist/release-notes-X.Y.Z.md
Check that the uploaded tarball is the one the formula describes; a mismatch means scripts/release.sh ran again after the upload:
gh release download vX.Y.Z --repo juraj-blahunka/simctlhouse --pattern '*.tar.gz' --output - | shasum -a 256
grep sha256 dist/simctlhouse.rb
Then publish the formula:
mkdir -p ../homebrew-tap/Formula
cp dist/simctlhouse.rb ../homebrew-tap/Formula/simctlhouse.rb
git -C ../homebrew-tap add Formula/simctlhouse.rb
git -C ../homebrew-tap commit -m "simctlhouse X.Y.Z"
git -C ../homebrew-tap push
9. Verify through Homebrew
The push starts the tap's tests workflow, which audits the formula, installs it on clean macOS 15 and 26 machines, and runs its test block.
That is the only real install of the published formula before users get it, so wait for it to pass:
commit="$(git -C ../homebrew-tap rev-parse HEAD)"
gh run list --repo juraj-blahunka/homebrew-tap --commit "$commit" --json databaseId,url # empty for a few seconds after the push
gh run watch <databaseId> --repo juraj-blahunka/homebrew-tap --exit-status
Then check the formula from this machine.
Homebrew refuses to load formulae from a tap nobody trusted, even for brew info, so trust this one formula first; that changes the user's Homebrew configuration, as brew install juraj-blahunka/tap/simctlhouse would.
The rest only reads and downloads; it installs nothing:
brew tap juraj-blahunka/tap 2>/dev/null; brew update
brew trust --formula juraj-blahunka/tap/simctlhouse
brew info juraj-blahunka/tap/simctlhouse # shows X.Y.Z
brew fetch --force juraj-blahunka/tap/simctlhouse # downloads and checks the sha256
brew audit --strict --online juraj-blahunka/tap/simctlhouse
If the user already has simctlhouse installed from the tap, offer brew upgrade simctlhouse and then simctlhouse --version and simctlhouse skill status.
10. Report
Give the user the release URL (gh release view vX.Y.Z --repo juraj-blahunka/simctlhouse --json url -q .url), the tap commit, the URL of its passing tests run, and the install command brew install juraj-blahunka/tap/simctlhouse.
Recovering from a failed release
- Failure before step 8: nothing is public. Fix the cause and start again; re-running
scripts/release.shis fine. - Tag pushed, release missing: run the
gh release createstep again. - Release created, tap not updated: run the tap steps again.
- The tap's
testsrun fails: show the user the failing step's log (gh run view <databaseId> --repo juraj-blahunka/homebrew-tap --log-failed), because users installing now hit the same failure. Fix forward with a new tap commit; never force-push the tap, since users'brew updatepulls it. - Notes need fixing after publishing: edit
dist/release-notes-X.Y.Z.mdwith the user, thengh release edit vX.Y.Z --repo juraj-blahunka/simctlhouse --notes-file dist/release-notes-X.Y.Z.md. - Never re-run
scripts/release.shfor a version whose tarball is uploaded, because every build has a new sha256. To replace a broken upload, ask the user first, thengh release upload vX.Y.Z <tarball> --clobberwith a fresh build and publish the matching formula. - Never delete or move a pushed tag or a published release without the user's explicit approval.