| name | releasing-lungfish |
| description | Use when asked to prepare, publish, recover, promote, or validate a Lungfish macOS release, DMG, GitHub release, or Sparkle preview/stable update. |
Releasing Lungfish
Produce a reproducible release from current main. Treat a missing provenance, signature, notarization, or verification result as a blocking defect.
Channels and Release History
Use one suffix-free YYYY.M.PATCH version line across preview and stable builds. Select the channel explicitly when invoking the builder:
--channel preview publishes a GitHub prerelease whose app polls appcast-beta.xml at sparkle-beta. Its signed bundle must use CFBundleDisplayName Lungfish Genome Explorer Preview, CFBundleName Lungfish Preview, and LungfishReleaseChannel preview. It may also update the sparkle-alpha legacy bridge and prune old preview release records. Preview is publicly downloadable and must carry this exact caveat in release notes and the About window: “Preview builds are under rapid iterative development. Features may be incomplete, change quickly, or require additional feedback.”
--channel stable publishes a full GitHub release whose app polls appcast-stable.xml at sparkle-stable. Its signed bundle must use CFBundleDisplayName Lungfish Genome Explorer, CFBundleName Lungfish, and LungfishReleaseChannel stable. It does not relabel or reuse a preview DMG; the feed URL and identity are baked into the signed app.
Explicit appcast flags override channel defaults and are for deliberate recovery or migration only. Channel state belongs to the baked Sparkle feed, the GitHub prerelease flag, and visible bundle metadata, never the version or tag. Both channels use the literal Lungfish.app wrapper and bundle identifier, so manually installing one channel's DMG replaces the other; there is no side-by-side installation or in-app channel toggle.
Every docs/release-notes/<version>.md starts with these audit fields:
Channel: Preview or Stable.
Previous versioned release: the latest immutable versioned release of either channel.
Stable baseline: the latest full versioned GitHub release, or None before the first stable release.
Dependency set: the bundled manifest set.
Preview notes describe the delta from the previous versioned release. Stable notes are aggregate notes: compare the latest full versioned GitHub release tag to HEAD, read every intervening committed versioned note, and reconcile both against the Git diff. Before the first stable release, use the explicitly recorded bootstrap aggregation baseline (currently v0.5.0-beta29), never the repository root. Include ## Included preview releases listing the intervening preview versions. Aggregate and deduplicate user-visible workflows, correctness/stability, scientific provenance, storage/migrations, dependency and database pins, platform/toolchain compatibility, updater/release infrastructure, and known issues. Git tags, GitHub release state, and committed per-version notes are the ledger; do not create a second mutable channel/version registry.
Debug Test Builds
A debug test build is NOT a release and must never be signed, notarized, tagged, uploaded, or attached to a GitHub release. Produce one whenever the user asks to "try", "test", or "smoke" a fix before release, and do it from the feature branch, not main.
-
Run the unit tier first: bash scripts/full-suite-gate.sh --tier unit must print PASS (serialize it with any other swift invocation; SwiftPM holds one .build/.lock per checkout).
-
Build the bundle with bash scripts/build-app.sh --debug (add --skip-build only when .build/arm64-apple-macosx/debug/Lungfish is already current for the exact commit under test). The script reads the version from Lungfish.xcodeproj so the debug bundle and the notarized build never diverge.
-
The result is build/Debug/Lungfish.app with bundle id com.lungfish.browser.debug, CFBundleName Lungfish Debug, and display name Lungfish Debug. It registers as a separate app from the installed release copy, so Computer Use, screen-capture, and Accessibility grants for the release app do not cover it; request them for the debug id explicitly.
-
Launch it with open build/Debug/Lungfish.app for the user, or run build/Debug/Lungfish.app/Contents/MacOS/Lungfish from a shell when LUNGFISH_* environment overrides are needed (environment variables only reach the app from a direct shell launch). Never point LUNGFISH_STORAGE_ROOT at the real ~/.lungfish in a throwaway smoke run.
-
Report the commit hash, the branch, the absolute .app path, and the unit-tier PASS line. Say plainly that the build is unsigned and for local testing only.
-
A SwiftPM debug bundle is NOT self-contained: the generated Bundle.module accessors resolve each module's resource bundle either at the .app root or at the absolute .build/arm64-apple-macosx/debug/<name>.bundle path of the checkout that compiled it, never under Contents/Resources. The app therefore crashes at first use of a bundled resource (NSBundle.module assertion, seen 2026-08-23 as a lungfishTeal palette crash) if that .build directory is deleted or the app is copied away from a checkout whose .build is gone. Build debug apps from the primary checkout when they must outlive a worktree, never delete a worktree or its .build while a debug app built from it is installed or running, and rebuild /Applications/Lungfish Debug.app from the primary checkout after removing a worktree.
Do not reuse build/Release/ or build-notarized-dmg.sh for a debug build, and do not delete build/Debug/Lungfish.app when cleaning up a release run unless the user asks.
Load Current Authority
Before acting, read all of:
bash scripts/release/build-notarized-dmg.sh --help
docs/release/sparkle-updates.md
.codex/agents/release-agent.md
SKILLS.md
- relevant files under
scripts/tests/
Run <skill-root>/scripts/validate.py --repo-root "$PWD". If repository interfaces drifted, update the skill or stop; never guess obsolete flags.
Choose Reasoning Strength
- Use a balanced model for read-only inventory and mechanical checks.
- Use the strongest available coding/reasoning model at high effort for branch classification, merge conflict resolution, release-delta analysis, narrative notes, publication recovery, cleanup decisions, and the final audit.
- Keep pushes, tags, releases, feed mutations, and deletion of branches/worktrees under the root agent.
- If model switching is unavailable, use the strongest active model and add an independent verification pass.
Release Gates
- Fetch
origin and tags. Inventory every worktree, branch, and dirty path. Classify each as release work, already merged, unrelated active work, or unresolved. Preserve unrelated work. Any dirty or ambiguous item blocks deletion. When using the coordinator, pass --approved-agent-branch once for each branch explicitly classified as release work; unlisted agent branches are not integrated or cleaned.
- Integrate approved release work into a clean checkout of current
origin/main. Test, commit, and push main; do not publish from a feature branch or silently discard changes.
- Name every new release with canonical CalVer
YYYY.M.PATCH, such as 2026.8.1; never add alpha, beta, preview, stable, or other channel suffixes. Capture the release machine's local calendar date once for the run. Set YYYY and M from that date without leading zeroes. Set PATCH to one more than the highest positive patch for that year and month across both remote Git tags and GitHub releases; start at 1 when the month has none. Ignore mutable sparkle-beta, sparkle-alpha, and sparkle-stable feed tags and all legacy-version tags when computing the counter. When resuming preparation, exclude the planned/current version itself from baseline selection. Fail if an existing CalVer release is future-dated relative to the captured month. A user-supplied version must be canonical, match the captured year/month, and be collision-free. Use tag v<version> and release notes docs/release-notes/<version>.md. Recheck Git tags and GitHub releases immediately before tagging and publication. Recompute after a concurrent collision; never overwrite a tag or versioned release.
- Determine both baselines before writing notes. The previous versioned release is the latest immutable versioned tag/release of either channel. The stable baseline is the latest full versioned GitHub release; exclude drafts and mutable
sparkle-* feed containers. Harmonize every visible app/CLI/version declaration. Write the channel-appropriate notes and audit fields above, with a detailed narrative and complete pinned dependency versions. Verify every claim against commits and changed files.
- Run the channel's test tiers with
scripts/full-suite-gate.sh (tier definitions live in that script; the opt-in pre-push hook covers only the unit tier, so releases must run their tiers explicitly). For a preview release, run the focused release tests plus bash scripts/full-suite-gate.sh --tier unit and bash scripts/full-suite-gate.sh --tier integration; a preview may ship without the conformance and full tiers. For a stable release, run the focused release tests plus and . The XCUI suite () is an attended diagnostic, not a release gate: macOS binds its automation permission to each rebuilt runner binary and may re-prompt, so it can block unattended runs indefinitely; run it on demand when investigating UI problems, with an operator present to answer the prompt. Read each verdict from the gate's PASS/FAIL line, never from the console tail alone. For both channels, also run , old-version scans, and the skill validator. Before tagging, preflight , Developer ID identity and Team ID agreement, the notarytool profile, the Sparkle generator, and signing-key access. Parse the selected channel's live appcast and require the planned ( or ) to exceed its .
Evidence Report
Report the channel, release URL, version/tag/commit, DMG absolute path and SHA-256, archive/app paths, notarization and signature results, selected Sparkle feed (plus preview bridge when applicable), local tests per tier (naming each gate tier run and its PASS/FAIL line; XCUI only if an attended diagnostic run was performed), automatic CI appropriate to the channel, cleanup performed, and anything retained or unresolved. State only what commands verified.
Install and Maintain
Run scripts/install.sh to link this repository-owned skill into ~/.codex/skills. The symlink keeps the installed skill current with the repository. Re-run scripts/validate.py whenever release tooling changes.