Skip to content
Agentmart
Submit a tool

release

Cut a Ducko release: update CHANGELOG.md on main, then push the X.Y.Z tag that triggers the release.yml CI workflow (build, sign, notarize, DMG, Sparkle…

Skill by ducko-im

IndexedGPL-3.0★ 7
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/ducko-im-release

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Cut a Ducko release: update CHANGELOG.md on main, then push the X.Y.Z tag that triggers the release.yml CI workflow (build, sign, notarize, DMG, Sparkle appcast, GitHub Release). Use when the user asks to "cut a release", "publish a new version", "prepare a release", "tag a release", or "ship it".

Use cases: devops, productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/release

What's inside

Skill name
release
Description
Cut a Ducko release: update CHANGELOG.md on main, then push the X.Y.Z tag that triggers the release.yml CI workflow (build, sign, notarize, DMG, Sparkle appcast, GitHub Release). Use when the user asks to "cut a release", "publish a new version", "prepare a release", "tag a release", or "ship it".
License
GPL-3.0
Instructions
90 lines in SKILL.md · view on GitHub

Files (2)

  • SKILL.md
  • local-release.md · references/

SKILL.md (first lines)

# Release

Cut a release straight from `main`: update the changelog, then push an `X.Y.Z` tag. CI does the build — the tag triggers `.github/workflows/release.yml`, which builds, signs, notarizes, packages a DMG, regenerates the Sparkle appcast, publishes a GitHub Release whose body comes from `CHANGELOG.md`, and asks ducko.im to rebuild. The signing, Sparkle and site rebuild secrets are already configured in CI. Releases are built exclusively via GitHub Actions — never locally.

There is no version file to bump — the marketing version comes from the tag, and `CHANGELOG.md` is the only prep artifact (`version.env` holds names, not the version; the build number is the commit count).

## Step 1: Determine the version

If the user did not give one, infer the next `X.Y.Z` from the latest tag and propose a patch/minor/major bump, then confirm:

```bash
git tag --sort=-v:refname | head -1
```

## Step 2: Update the changelog

Make sure `main` is clean and current (`git checkout main && git pull origin main`). `CHANGELOG.md` keeps a running `## [Unreleased]` section, so a release completes that section and then promotes it to a version heading.

**Scope: user-facing only, framed as a net delta.** `CHANGELOG.md` becomes the GitHub Release notes and the Sparkle update notes, so it must describe only what a user experiences between releases. Before promoting, review every `[Unreleased]` entry and:

- **Drop non-user changes.** CI, scripts, skills, tests, and refactors don't ship to users. `DuckoCLI`-only changes belong only when the CLI is part of what the release ships.
- **State the net delta from the last released version, not the development history.** The `[Unreleased]` section accumulates commit-by-commit, so it collects entries that only make sense relative to an intermediate unreleased build (e.g. "no longer does X" / "removed the Y glitch" where X or the Y bug never shipped). Rewrite or drop those so each entry reads as a change the previous release's users will actually notice.
- **Check each entry's subject against the last tag, don't just scan the phrasing.** A positively-phrased entry hides the same trap: a fix for a feature that arrived in this same cycle reads like something users would notice, but nobody on the previous release ever hit that bug. For every entry, confirm the thing it changes or fixes existed at the last tag — `git show <last-tag>:<path>`, plus `git log --follow -- <path>` when the file moved. When it did not, fold the entry into whatever introduced the feature, or drop it. One entry per net user-visible change, not one per commit.
- **Describe the experience, not the mechanism.** "Rooms remember their bookmark autojoin flag" is what a user sees; the stanza or storage detail belongs in the commit, not the release notes.

1. **Complete `[Unreleased]` via `/update-changelog`.** Run it to capture anything missing, then double-check completeness against `git log <last-tag>..HEAD --oneline` — that range always includes the prior `Update appcast.xml for <last>` commit (CI pushes it to `main` after the tag) as noise, and real changes can land *after* it, so don't stop scanning there.
2. **Promote** by inserting the version heading (`## [X.Y.Z] - YYYY-MM-DD`, today's date) under the kept-empty `## [Unreleased]` heading so the accumulated entries fall under the new version, add the `[X.Y.Z]: https://github.com/ducko-im/ducko/compare/<last-tag>...X.Y.Z` link reference (`.../releases/tag/X.Y.Z` for the first release), and repoint `[Unreleased]` to `compare/X.Y.Z...HEAD` (mirror the previous `Prepare release X.Y.Z` commit's changelog diff). `release.yml` extracts this version section as the GitHub Release notes.

## Step 3: Commit and push to main

```bash
git add CHANGELOG.md
git commit -m "Prepare release X.Y.Z"
git push origin main
```

The tag must land on a `main` commit: `release.yml` signs only commits reachable from `origin/main`, and it reads `CHANGELOG.md` at the tagged commit.

## Step 4: Tag and trigger the release

Dispatch a dry run and wait for it to pass before tagging when the signing or packaging inputs changed since the last tag:

```bash
git diff --stat <last-tag>..HEAD -- Package.swift Resources/Entitlements.plist Scripts/package_app.sh Scripts/create_dmg.sh Scripts/release.sh .github/workflows/release.yml
```

**Skip** the dry run when that diff is empty. It is the only check that signs, notarizes, and staples the real arm64 build, since CI's release smoke test stubs those tools:

```bash
gh workflow run release.yml --ref main -f version=X.Y.Z -f dry_run=true
gh run list --workflow release.yml --limit 1   # run ID, when the dispatch prints no URL
gh run watch <run-id> --exit-status
```

If the dry run fails, fix the cause on `main`, push, and dispatch again. Tag only after a dry run passes. A passing dry run uploads its artifacts for inspection.

Tag the latest `main` commit (the changelog commit, or a later dry-run fix) and push the tag — this is what starts CI:

```bash
git tag -a X.Y.Z -m "X.Y.Z"
git push origin X.Y.Z
```

`release.yml` then builds, signs, notarizes, generates the appcast, creates the `X.Y.Z` GitHub Release (notes from the changelog section), commits the updated `appcast.xml` back to `main`, and asks ducko.im to rebuild for the new version.

## Step 5: Finish

```bash
git pull origin main    # pick up the appcast commit CI pushed
gh release view X.Y.Z   # verify the Release and its assets
gh run list --commit "$(git rev-parse X.Y.Z^{commit})"   # every workflow on the tagged commit
```

`ci.yml` runs on the tagged commit from its push to `main`, separately from `release.yml`, so a passing release run does not cover it. Report the release as done only once both have passed, waiting on an unfinished run with `gh run watch <run-id> --exit-status`. When the CI run failed, name the failure and fix it on `main`.

When only the release run's last step, `Rebuild ducko.im`, failed, the release is already published, so don't re-run the release. That step authenticates with `SITE_DISPATCH_TOKEN`, a fine-grained token with Actions: Read and write on `ducko-im/ducko-im.github.io`. Renew it when it has expired, then ask for the rebuild by hand:

```bash
gh workflow run deploy.yml -R ducko-im/ducko-im.github.io --ref main -f version=X.Y.Z
```

Without that, the site picks up the release on its next daily build.

## Notes

- Reserve each `X.Y.Z` for one set of artifacts — re-tagging a published version reuses the Release and appcast URLs for different content.
- The Sparkle EdDSA private key lives in the login Keychain under `generate_keys --account ducko` and is backed up in 1Password. `.build/artifacts/sparkle/Sparkle/bin/generate_keys --account ducko -p` must print the `SUPublicEDKey` in `Scripts/package_app.sh`. GitHub cannot show the `SPARKLE_PRIVATE_KEY` secret, so when its contents are in doubt, re-set it from that key (`generate_keys --account ducko -x <file>`, then `gh secret set SPARKLE_PRIVATE_KEY < <file>` and delete the file) rather than generating a new pair. A mismatched pair still publishes a release, but every later update fails signature verification for its users.
- If CI is unavailable, see [references/local-release.md](references/local-release.md) for the manual release procedure.

From ducko-im/ducko, GPL-3.0.

More from ducko-im/ducko

Signals

Stars
7
Forks
0
Open issues
4
Last commit (this folder)
Oct 18, 2026 (10 days ago)
Last release
Oct 23, 2026 (6 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
GPL-3.0
Website
ducko.im
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from ducko-im

Cite this page

release skill. Agent Mart. https://agentmart.dev/skills/ducko-im-release (last updated 2026-10-24)