Research: how should the release-orchestration skill cut releases — what do cargo-release, release-please, changesets, and the terminal/COSMIC peers do that DropTerm should adopt?

Date: 2026-11-01 Question: How do comparable projects and tool ecosystems orchestrate releases — Rust release tooling, changelog/release-PR automation, AI-agent release skills with human gates, COSMIC/desktop-app peers, terminal peers — and which of their practices should DropTerm’s release-orchestration skill adopt?

TL;DR

DropTerm’s gated skill already implements the strongest pattern in the survey — cargo-release’s “dry-run by default, confirm every outward step, verify everything before tagging” — and the ecosystems surveyed offer five cheap sharpenings, not a redesign: a behind-remote pre-flight check, a no-op-release warning in Phase 1, spec-exact version-derivation wording, tests inside the release CI (alacritty’s one real precedent), and an optional “Next (unreleased)” accumulator. cargo-release’s --execute/--no-confirm semantics (docs/reference.md: “-x, –execute Actually perform a release. Dry-run mode is the default”; the [y/N] prompt accepts only a literal y, src/ops/shell.rs:9-18) are the closest thing to a primary-source blessing of DropTerm’s gate design, and its pre-flight battery — clean tree, tag absent, monotonic versions, allowed branch, not-behind-remote — runs before the first confirmation, exactly where DropTerm’s Phase 1 sits. The release-PR tools (release-please, changesets) solve a problem DropTerm solved differently and, for a single maintainer with a curated five-file curation, better: they separate preparing a release from publishing via a PR, and DropTerm separates them via gates plus a human push — same separation, less machinery. No AI-agent release skill exists in either anthropics/skills (19 skills, none about releasing) or addyosmani/agent-skills (the 25-skill pack DropTerm already ships; the closest are the generic shipping-and-launch checklist and /ship go/no-go fan-out) — the gated-skill design has no precedent to copy and none contradicting it. The COSMIC peers do less than DropTerm (no GitHub Releases, no automation, no appstream release notes at all), and the terminal peers contribute two conventions worth a line each: alacritty’s “changelog documents the user-perspective, not the technical background” and wezterm’s idempotent gh release view || gh release create. Nothing in the survey argues for adopting a tool; several things argue for tightening the skill’s own derivation and pre-flight wording against the specs it already cites.

Findings

1. cargo-release: dry-run is the default, every step confirms, and the verify battery precedes the first confirmation

cargo-release @ 5f271e4beb11b59f6305fbce1df7f9033ec27042 (cloned 2026-11-01).

Dry-run/confirm semantics. The CLI makes rehearsal the default and execution the opt-in:

The confirmation is a per-step gate, not one global prompt: every step calls super::confirm(...) (src/steps/version.rs:140, replace.rs:150, hook.rs:154, commit.rs:95, publish.rs:160, tag.rs:138, push.rs:124), and the gate only fires when it matters — src/steps/mod.rs:455: if !dry_run && !no_confirm. The prompt itself rejects everything but an explicit yes — src/ops/shell.rs:9-18:

pub fn confirm(prompt: &str) -> bool {
    ...
    console_println(&format!("{prompt} [y/N] "), Style::new());
    ...
    input.trim().to_lowercase() == "y"
}

That is DropTerm’s “Silence, ambiguity, or ‘probably’ is not consent” rule (SKILL.md, The Gates), with cargo-release as the prior art.

The verify battery runs BEFORE the first confirmation. src/steps/release.rs labels it explicitly — line 195: // STEP 0: Help the user make the right decisions. — and lines 197-258 run, in order: verify_dependencies, verify_git_is_clean (src/steps/mod.rs:29: “uncommitted changes detected, please resolve before release”), verify_tags_missing, verify_monotonically_increasing, a double-publish check, warn_changed, verify_git_branch (the allow-branch allowlist), verify_if_behind, verify_metadata, verify_rate_limit. Only then does line 269 run // STEP 1: Release Confirmation. Two of these are directly adaptable:

Sign/tag/push ordering. After the confirmation the steps run (src/steps/release.rs): STEP 2 (line 272) version bump + lock update + pre-release-replacements + pre-release-hook + commit; STEP 3 (line 329) cargo publish; owner (line 332); STEP 5 (line 333) // STEP 5: Tag; STEP 6 (line 336) // STEP 6: git push. So the artifact is published before the tag and push — cargo-release is crates.io-shaped and tags after publish so a failed publish never leaves a tag pointing at an unreleasable commit. DropTerm inverts this by design (the tag push is the publish, because release.yml fires on the tag), which is the right shape for a GitHub-Releases app; the transferable part is only that each outward act is separately gated, which DropTerm already does.

What it refuses to do by default. verify = true by default (docs/reference.md:417-420: “Verify the contents by building them” — via cargo publish’s own build); dirty tree, existing tag, non-monotonic version, or a disallowed branch abort before any mutation (allow-branch default is ["*", "!HEAD"], docs/reference.md:169-170 — detached HEAD is always refused). One deliberate wart worth knowing: the pre-release hook runs even in dry-run mode, receiving DRY_RUN=true so the script can self-skip — src/steps/hook.rs:209-216 sets OsStr::new("DRY_RUN") => ... if dry_run { "true" } else { "false" } and comments “we use dry_run environmental variable to run the script // so here we set dry_run=false and always execute the command.” A hook with side effects therefore runs during rehearsal; DropTerm’s recipes avoid this class of surprise by refusing rather than running.

Changelog handling: deliberately unopinionated. docs/faq.md:31-37: “At the moment, cargo release is unopinionated in its support for CHANGELOGs due to the complexities and the different approaches people might want to take”, suggesting git-cliff as a pre-release-hook for generated logs, and pre-release-replacements for hand-written ones. The replacement mechanism carries occurrence-count guards DropTerm’s recipes will recognize — docs/reference.md:248-251: min (default 1), max, exactly — the same “re-pin exactly once or fail” shape as release-flatpak-pin’s grep -c checks (justfile lines 672-679).

2. release-please: the release-PR pattern — preparation as a reviewable PR, GitHub as the state machine, and a documented resume procedure

release-please @ c589293ca38aee267b329f9bf9b26c9d6516a687 (cloned 2026-11-01).

The pattern. docs/design.md:18-21: “release-please will maintain a pull request (‘release pull request’) which proposes version bumps in your code and appends release notes to your CHANGELOG.md. Releases are not created until after this ‘release pull request’ is merged to your release branch.” The human gate is the PR review itself; the merge is the trigger. Accumulation is native — docs/design.md:23-24: “Maintainers can merge additional commits and release-please will update the existing release pull request.” And the tool stops at the release: docs/design.md:63: “Note: release-please is not responsible for publishing your package or application.” DropTerm’s gates are the same separation with the PR replaced by an interactive review: the Notes gate is the release-PR review, the Publish gate is the merge.

The manifest and the load-bearing PR. State lives in two source-controlled files plus GitHub labels — docs/manifest-releaser.md:3-5: “release-please can be setup to use source-controlled files containing releaser-specific configuration (the release-please-config.json) as well as package version tracking (the .release-please-manifest.json)”. Because “release-please operates without a database of information and so it relies on GitHub as the source of its information. Due to this, the release pull request is heavily formatted and its structure is load-bearing.” (docs/design.md:210-212). The label state machine is autorelease: pending on creation (design.md:75) → autorelease: tagged after the GitHub release is cut (design.md:106; README.md:38-39 repeats both).

Resume after partial failure is a documented human procedure. A stuck autorelease: pending label blocks all new release PRs — README.md:153-161: if the previous release half-finished, “release-please thinks that the previous release is still pending. If you’re certain that there’s no pending release, remove the autorelease: pending … label and re-run Release Please” (force-run via the release-please:force-run label, README.md:174-176; the closed-PR variant is docs/troubleshooting.md:112). DropTerm’s Rules #2 (“Resuming mid-release means first establishing which gates already passed”) is the same idea executed against git state instead of GitHub labels — and DropTerm’s is stronger because each leg’s state is checkable by command (git ls-remote, the manifest grep).

Version derivation and human overrides. docs/design.md:153-157: “a breaking change will increment the semver major version / a feat change will increment the semver minor version / a fix change will increment the semver patch version.” Releasable units are exactly the conventional prefixes — README.md:146-148: “A releasable unit is a commit to the branch with one of the following prefixes: ‘feat’, ‘fix’, and ‘deps’. (A ‘chore’ or ‘build’ commit is not a releasable unit.)” — matching DropTerm Phase 1’s “release-tooling commits … are invisible to the number”. The maintainer override is a commit footer, README.md:106: “When a commit to the main branch has Release-As: x.x.x (case insensitive) in the commit body, Release Please will open a new pull request for the specified version.” Release-note fixes have an escape hatch too — the BEGIN_COMMIT_OVERRIDE block in a merged PR body (README.md:122-132). DropTerm’s Phase 1 gate (“ask the maintainer to confirm or override. An override skips the derivation, never the gate”) is the interactive equivalent of Release-As, and its “revise until approved” Notes gate is the equivalent of editing the release PR before merge.

3. changesets: capture the intent when the change is fresh; version by the maximum bump; review between version and publish

changesets @ b09779a3d503a581df98a81384d5849db969eb03 (cloned 2026-11-01; the repo docs carry a warning that the canonical version is changesets.dev — the concepts below are the repo’s own text).

The rationale is the timestamp of capture, not the machinery. docs/detailed-explanation.md:10-11: “The best time to capture this information is when submitting a PR (when it is fresh in your mind), not when you eventually go to batch and release these changes.” A changeset is “an ‘intent to change’” (:18) — a markdown file in .changeset/ whose YAML front matter carries the bump level and whose body is the future changelog entry. This “breaks versioning into two steps: 1. Adding a changeset - can be done in a PR, by a contributor, while the change is fresh in their mind. 2. Versioning - combines all changesets, creates one version bump for each package based on the maximum version bump of each package” (:52-56) — the max-bump rule being the changesets counterpart of DropTerm’s “any user-facing feat: → minor”.

Human review sits between versioning and publishing. docs/intro-to-using-changesets.md:60-62: “changeset version … consumes all changesets, and updates to the most appropriate semver version … We recommend at this step reviewing both the changelog entries and the version changes for packages. Once you are confident that these are correct, and have made any necessary tweaks to changelogs, you can publish.” The version PR automation keeps that review PR-shaped — docs/automating-changesets.md:44-45: the action “creates a version PR, then keeps it up to date, recreating it when merged. This PR always has an up-to-date run of changeset version”. Publishing is idempotent (intro:73-74: publish runs “npm publish in each package that is of a later version than the one currently listed on npm”), a missing-changeset PR can be gated in CI (changeset status --since=main “will exit with exit code 1 if there are changed packages but no new changesets”, automating-changesets.md:31-33), and release-noop merges carry changeset --empty (:36). For pre-release channels there are snapshot versions 0.0.0-{tag}-DATETIMESTAMP (docs/snapshot-releases.md:6).

The gap for DropTerm: changesets requires npm packaging — docs/versioning-apps.md:8: “The only requirement is that the project has a package.json file to manage the versions and dependencies within the repo.” Not adaptable as tooling; adaptable as one idea (finding 8).

4. AI-agent release skills: none exist in the primary skill repos — DropTerm’s gated design is novel, and cargo-release is its closest validation

Conclusion of this finding: no primary source in the AI-skills ecosystem encodes an agent-cut release with human gates, never-push rules, or verify-don’t-trust remote checks. Those constructs have exactly one mature analog in the survey — cargo-release’s per-step [y/N] confirmations over a dry-run default (finding 1) — which is a CLI tool talking to a human, not an agent talking to a maintainer. DropTerm’s skill is precedent-setting here; the survey validates its individual mechanisms (gates, default-safe, verify-after-act) but cannot be cited for the agent-level composition.

5. COSMIC peers: everything DropTerm already exceeds, plus three conventions it lacks (pre-bumped dev version, FlakeHub rolling, debian lockstep)

Cloned 2026-11-01. All three peers share the epoch model verified in packaging-and-release finding 3.2, still current:

Repo Commit Cargo version Latest tag Workflows
pop-os/cosmic-term 8a05e91 (2026-10-27) 1.11.0 epoch-1.9.0 rust-checks, validate-desktop-files, require-checklist, flakehub-publish-rolling
pop-os/cosmic-files 09bdd82 (2026-10-09) 1.11.0 epoch-1.9.0 ci, require-checklist, validate-desktop-files
pop-os/cosmic-edit ec8375c (2026-10-27) 1.11.0 epoch-1.9.0 ci, require-checklist, validate-desktop-files, flakehub-publish-rolling

What do they do that DropTerm doesn’t? Nothing in the release-orchestration skill’s scope. The peers’ process is a human runbook DropTerm automated past.

6. Terminal peers: alacritty documents a 14-step RC process and a changelog-voice rule; wezterm names releases by date and creates releases idempotently

alacritty @ bbb2cbf (2026-10-26):

wezterm @ e68a8ec (2026-10-26):

7. The specs: Conventional Commits and SemVer — what Phase 1’s derivation can lean on, and the 0.x caveat

Conventional Commits v1.0.0 (conventionalcommits.org/en/v1.0.0/, fetched 2026-11-01):

SemVer 2.0.0 (semver.org, fetched 2026-11-01):

Validation of DropTerm’s Phase 1: “fixes only → patch; any user-facing feat: → minor; at 0.x a breaking change still takes the minor” is exactly Conventional-Commits translation plus the SemVer FAQ 0.x convention, and it is not in tension with the spec because the spec’s MUSTs do not bind at 0.y.z. Two tightenings the specs support: (a) the derivation should also scan for ! / BREAKING CHANGE markers so “name the break at the gate” triggers on evidence rather than memory; (b) “fixes only” is better stated as “no feat:/fix: commit with user-visible effect” — cargo-release’s warn_changed covers the empty case (finding 1). At 1.0.0 the same derivation flips to spec-mandatory behavior with no wording change: breaking → major becomes a MUST.

What DropTerm has today

Read from the skill (.agents/skills/release-orchestration/SKILL.md), the justfile, .github/workflows/release.yml, and docs/development.md#releasing at HEAD (2026-11-01):

Gaps the survey exposes, none currently covered: no behind-remote check before cutting; no nothing-changed-since-tag warning; no !/BREAKING CHANGE scan in Phase 1; release CI builds but does not test; the workflow_dispatch dry-run leg exists but the skill never mentions it as a rehearsal option; releases.md is explicitly release-time-only (“A section is added here at release time, by the release commit; nothing in between”, docs/releases.md:5), so there is no accumulator between releases.

Recommendation

Graded by the precedent strength of the source:

  1. Add a behind-remote pre-flight to Phase 1/3 — STRONG precedent (cargo-release verify_if_behind runs before its first confirmation). One check before Phase 3’s recipe: git fetch origin main and require git rev-parse main..origin/main to be empty (cargo-release’s own help text prescribes git pull --rebase when it fails). This is the only finding where a surveyed tool guards against a failure mode DropTerm’s recipe genuinely lacks.
  2. Add a no-op-release warning to Phase 1 — STRONG precedent (cargo-release warn_changed: “updating X to Y despite no changes made since tag Z”). If git log <tag>..HEAD contains no user-facing commits at all, say so at the Version gate before proposing a number. Cheap, and it makes the “tooling-invisible” rule self-checking.
  3. Tighten Phase 1’s derivation wording to the specs and scan for break markers — STRONG precedent (Conventional Commits v1.0.0 translation rule; SemVer FAQ 0.x convention). State the mapping as the spec does (feat→minor, fix→patch, other types inert), and let the Phase 1 commit scan surface ! / BREAKING CHANGE so “name the break at the gate” fires on evidence. Document that at 1.0.0 the same rule becomes spec-mandatory unchanged.
  4. Run tests in release CI — MEDIUM precedent (alacritty’s release.yml runs cargo test --release in every matrix job before uploading). DropTerm’s build job could run just test --locked (or the cheaper cargo test --workspace --locked) once before staging, closing the gap between the local just check and the artifacts CI publishes.
  5. Make the skill name the existing dry-run rehearsal — MEDIUM precedent (cargo-release’s dry-run default; release.yml already has the workflow_dispatch dry-run input). Before the Phase 4 push, offer the maintainer the dispatch run as a final full-pipeline rehearsal; it assembles artifacts and proves the offline build without publishing. Zero new machinery.
  6. Optionally adopt a “Next (unreleased)” accumulator in docs/releases.md — MEDIUM precedent (changesets’ capture-when-fresh rationale; wezterm’s accumulating changelog; release-please’s evergreen release PR). DropTerm’s skill already consumes such a pointer if present; making it the norm between releases moves curation from recall-at-release-time to capture-at-change-time. This is the one changesets idea worth having without any of its tooling.

Rejected alternatives, and why:

Adaptable to DropTerm, ranked (value × precedent / effort)

  1. Behind-remote pre-flight before Phase 3 — high value, tiny effort (one fetch + one rev-range check, message with the git pull --rebase hint). cargo-release precedent.
  2. No-op-release warning in Phase 1 — medium value, tiny effort (one more question of the commit list Phase 1 already gathers). cargo-release precedent.
  3. Spec-exact derivation wording + !/BREAKING CHANGE scan in Phase 1 — medium value, small effort (wording + one grep pattern in the commit presentation). Conventional Commits + SemVer precedent; future-proofs 1.0.0.
  4. Tests in release CI — medium value, small effort (one step in the build job). alacritty precedent.
  5. Name the workflow_dispatch dry-run as the pre-push rehearsal option — medium value, tiny effort (one sentence in Phase 4). cargo-release dry-run-default precedent; machinery already exists.
  6. “Next (unreleased)” accumulator as the standing norm — medium value, small ongoing effort (one entry per merged feature, consumed at release). changesets/wezterm/release-please precedent; opt-in, since releases.md’s current release-time-only rule is also defensible.
  7. FlakeHub rolling channel — low value today, medium effort (needs a flake first; packaging-and-release already defers nix). pop-os precedent; park it with the nix story.
  8. RC/nightly channels, release-please, changesets, cargo-release adoption — rejected (see above); listed only so the rejection is on record.

Sources

All repositories cloned 2026-11-01 at the commits named; web specs fetched 2026-11-01. Line numbers are into the files at those commits.