Prepare Release

SkillFiles & storage

prepare-release is a skill that lets an AI agent prepare a software release. It collects commits since the last published release, generates bilingual release notes in English and Chinese, updates version files, and creates a release branch. It stops early if anything looks wrong.

Use Prepare Release in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Prepare Release and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Prepare Release skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Have a repository with a commit history and version files.

Prepare ReleaseStart free

What your AI can do with it

  • Verify the repository state before starting a release
  • Pick the right version number
  • Collect commits since the last published release
  • Generate English and Chinese release notes
  • Update version files
  • Create a release branch

Getting started

  1. Have a repository with a commit history and version files.
  2. Make the skill available to your agent.
  3. Ask the agent to prepare a release, bump the version, or run /prepare-release.
  4. Review the generated release notes and version changes before publishing.

What this skill tells your AI

The instructions your AI receives, as published by cherryhq/cherry-studio in .agents/skills/prepare-release/SKILL.md and read by ahel’s review.

Automate the Cherry Studio release workflow: collect changes → generate bilingual release notes → update files → create release branch → trigger CI/CD.

Arguments

Parse the version intent from the user's message. Accept any of these forms:

  • Bump type keyword: patch, minor, major
  • Exact version: strict x.y.z or x.y.z-<prerelease> without build metadata (e.g. 1.8.0, 1.8.0-beta.1, 1.8.0-rc.1)
  • Natural language: "prepare a beta release", "bump to 1.8.0-rc.2", etc.

Defaults to patch if no version is specified. Always echo the resolved target version back to the user before proceeding with any file edits.

  • --dry-run: Preview only, do not create a release branch.

Workflow

Step 1: Determine Version

  1. For an interactive local run, fetch origin/main and all tags, then verify that the checkout is a clean main at exactly origin/main:
    git fetch origin refs/heads/main:refs/remotes/origin/main --tags
    test "$(git branch --show-current)" = main
    test -z "$(git status --porcelain)"
    test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
    
    Stop before editing files if any check fails. This prevents a standalone run from creating a release branch from an arbitrary or stale checkout. In GitHub Actions, use the workflow's frozen dispatch SHA and leave checkout validation to the workflow. Do not fetch or compare the later origin/main head.
  2. Read the current version from package.json. Use it for version increments even if that release has since been withdrawn; never reuse the withdrawn version.
  3. Select the latest published, non-draft GitHub Release whose tag is strict v<semver> as the release-note baseline. Non-semver preview releases and drafts are never a baseline:
    gh release list --limit 1000 --json isDraft,publishedAt,tagName --jq '[.[] | select(.isDraft == false and (.tagName | test("^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$")))] | sort_by(.publishedAt) | last | .tagName // empty'
    
    Stop if no published baseline exists or its Git tag is missing (git rev-parse --verify refs/tags/{baseline-tag}). Compare its version with the current package version using semver: if main is behind, stop and require the latest Post Release metadata PR to be merged. If main is ahead, allow preparation using the published baseline for release notes and the package version for version increments. For example, after withdrawing 2.1.1, main at 2.1.1 with a published baseline of 2.1.0 prepares 2.1.2 and includes changes since 2.1.0. In GitHub Actions, use the workflow-provided baseline tag and collection base without repeating these checks.
  4. Compute the new version based on the argument:
    • patch / minor / major: bump from the current version.
    • An exact version must match ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ and pass semver.valid; build metadata such as +build.1 is not accepted.
    • In both cases, require the result to be strictly greater than the current version according to semver precedence. Reject equal versions and downgrades.

Step 2: Collect Commits

  1. Determine the release-note collection base from the published baseline selected in Step 1, not from the package version:
    • If the baseline tag is an ancestor of HEAD, use the tag.
    • Otherwise, use the latest commit whose full message contains the exact marker release-metadata-boundary: <baseline-tag>. This machine marker is added to the Post Release pull request body and survives the required squash merge.
    • For metadata pull requests created before the machine marker existed, accept a subject exactly equal to chore(release): sync <baseline-tag> metadata or that subject followed only by GitHub's squash suffix (#<PR-number>).
    • Stop with an error if the tag is not an ancestor and its metadata sync commit is missing; otherwise already-released hotfixes could be included again.
  2. List all commits since that base:
    git log <collection-base>..HEAD --format="%H %s" --no-merges
    
  3. For each commit, get the full body:
    git log <hash> -1 --format="%B"
    
  4. Extract the content inside ```release-note code blocks from each commit body.
  5. Extract the conventional commit type from the title (feat, fix, refactor, perf, docs, etc.).
  6. Skip these commits as standalone release-note candidates, but still inspect their effects when reconciling candidates with the final code in Step 3:
    • Titles starting with 🤖 Daily Auto I18N
    • Titles starting with Merge
    • Titles starting with chore(deps)
    • Titles starting with chore: release
    • Titles starting with chore(release)
    • Commits where the release-note block says NONE

Step 3: Generate Bilingual Release Notes

Generate release notes in both English and Chinese from the final user-visible changes relative to the published baseline. Commit titles and release-note blocks are candidate descriptions, not proof that a change will ship.

Reconcile net changes before drafting:

  1. Inspect git diff --name-status <baseline-tag> <release-head>, then read the relevant patches and code at both endpoints. <release-head> is the source HEAD before release preparation (the frozen dispatch SHA in CI). Compare the two endpoint trees, not a three-dot merge-base diff. The collection base is only for discovering commits; when it differs from the published tag, also inspect user-visible differences missing from that commit range.
  2. Group candidates by user-visible behavior and trace related patches in chronological order, including commits excluded in Step 2. Detect explicit reverts, manual undoing, replacements, partial reversals, and reintroductions from the code; do not rely on commit wording or matching hashes alone.
  3. Omit a change introduced and fully undone during this cycle, including fixes that only addressed that temporary change. For partial reversals, replacements, or reintroductions, describe only the final outcome that differs from the published baseline, once per distinct user-visible change.
  4. If a reversal removes or changes behavior that already existed in the published baseline, describe the resulting user-visible removal or restoration. Do not discard all revert commits indiscriminately. An implementation rewrite that preserves the same user-visible behavior does not by itself justify a release-note entry.
  5. Verify each proposed item against the endpoint diff and final code, and ensure the English and Chinese versions describe the same outcome. If the claimed effect cannot be substantiated, omit it and report the uncertainty in the preparation summary rather than inventing a release-note claim.

Recommended format:

<!--LANG:en-->
Cherry Studio {version} - {Brief English Title}

✨ New Features
- [Component] Description

🐛 Bug Fixes
- [Component] Description

💄 Improvements
- [Component] Description

⚡ Performance
- [Component] Description

<!--LANG:zh-CN-->
Cherry Studio {version} - {简短中文标题}

✨ 新功能
- [组件] 描述

🐛 问题修复
- [组件] 描述

💄 改进
- [组件] 描述

⚡ 性能优化
- [组件] 描述
<!--LANG:END-->

The language markers are the machine-readable contract: include each marker once, keep them in order, and provide non-empty English and Chinese sections. Titles and surrounding explanatory text are presentation choices, not validation requirements.

Rules:

  • Only include categories that have entries (omit empty categories).
  • Each distinct surviving user-visible change appears once in the appropriate category; combine related commits and omit canceled or superseded claims.
  • Prefer wording from the release-note field, or otherwise the commit title, only when it matches the verified final outcome.
  • Component tags should be short: [Chat], [Models], [Agent], [MCP], [Settings], [Data], [Build], etc.
  • Chinese translations should be natural, not machine-literal.
  • Do NOT include commit hashes or PR numbers.
  • Read the existing release notes in electron-builder.yml as a style reference before writing.

IMPORTANT: User-Focused Content Only

Release notes are for end users, not developers. Exclude anything users don't care about:

  • EXCLUDE internal refactoring, code cleanup, or architecture changes
  • EXCLUDE CI/CD, build tooling, or test infrastructure changes
  • EXCLUDE dependency updates (unless they add user-visible features)
  • EXCLUDE documentation updates
  • EXCLUDE developer experience improvements
  • EXCLUDE technical debt fixes with no user-visible impact
  • EXCLUDE overly technical descriptions (e.g., "fix race condition in Redux middleware")

INCLUDE only changes that users will notice:

  • New features they can use
  • Bug fixes that affected their workflow
  • UI/UX improvements they can see
  • Performance improvements they can feel
  • Security fixes (simplified, without implementation details)

Keep descriptions simple and non-technical:

  • ❌ "Fix streaming race condition causing partial tool response status in Redux state"
  • ✅ "Fix tool status not stopping when aborting"
  • ❌ "Auto-convert reasoning_effort to reasoningEffort for OpenAI-compatible providers"
  • ✅ "Fix deep thinking mode not working with some providers"

Step 4: Update Files

  1. package.json: Update the "version" field to the new version.
  2. electron-builder.yml: Replace the content under releaseInfo.releaseNotes: | with the generated notes. Preserve the 4-space YAML indentation for the block scalar content.
  3. resources/cherry-studio/release-history.json: Never edit by hand; the notes must match electron-builder.yml byte for byte. Run node scripts/release/sync-release-history.js --target-version {version}, which prepends (or replaces) the entry for a stable release and leaves the file untouched for a prerelease. In GitHub Actions, the workflow runs this itself after the Claude step.
  4. Validate source metadata: For an interactive local run, run node scripts/release/validate-prepared-release.js --target-version {version} before generating the product manifest, and stop if it rejects the changed paths, version ordering, bilingual sections, or stable history. In GitHub Actions, leave validation to the workflow step that runs after Claude.
  5. Built-in knowledge: For an interactive local run, run pnpm build:builtin-knowledge after validation. This refreshes resources/builtin-agents/cherry-assistant/product-manifest.json with the new package version. Never edit the generated manifest by hand. In GitHub Actions, do not run the generator: the workflow runs the same validator first, then runs the trusted generator itself.

Step 5: Present for Review

Show the user:

  • The new version number.
  • The full generated release notes.
  • A summary of which files were modified.

If --dry-run was specified, stop here.

Otherwise, ask the user to confirm before proceeding to Step 6.

Step 6: Create Release Branch

  1. For an interactive local run, repeat Step 1 items 1-3 immediately before creating the branch, reading the original package version from HEAD:package.json. Require the published baseline to still match the one used to collect release notes; otherwise regenerate the notes before proceeding. Because Step 4 has intentionally prepared and validated release metadata, replace Step 1's clean-worktree assertion with git status --short and stop unless every listed path is one of the four allowed release metadata files. Then create and push a signed, DCO-compliant release commit:
    git fetch origin refs/heads/main:refs/remotes/origin/main --tags
    test "$(git branch --show-current)" = main
    test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
    test "$(node -p "require('./package.json').version")" = "{version}"
    CURRENT_VERSION="$(git show HEAD:package.json | jq -r .version)"
    LATEST_PUBLISHED="$(gh release list --limit 1000 --json isDraft,publishedAt,tagName --jq '[.[] | select(.isDraft == false and (.tagName | test("^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$")))] | sort_by(.publishedAt) | last | .tagName // empty')"
    test "$LATEST_PUBLISHED" = "{baseline-tag}"
    git rev-parse --verify "refs/tags/$LATEST_PUBLISHED"
    node -e "const semver = require('semver'); process.exit(semver.gte(process.argv[1], process.argv[2]) ? 0 : 1)" "$CURRENT_VERSION" "${LATEST_PUBLISHED#v}"
    REPO="$(gh repo view --json nameWithOwner --jq .nameWithOwner)"
    gh api --paginate --slurp "repos/$REPO/releases?per_page=100" | TAG="v{version}" node scripts/release/validate-release-state.js prepare
    test -z "$(git ls-remote --heads origin refs/heads/release/v{version})"
    git status --short
    UNEXPECTED_RELEASE_PATHS="$(git status --porcelain | cut -c4- | grep -Ev '^(package\.json|electron-builder\.yml|resources/cherry-studio/release-history\.json|resources/builtin-agents/cherry-assistant/product-manifest\.json)$' || true)"
    test -z "$UNEXPECTED_RELEASE_PATHS"
    git checkout -b release/v{version}
    git add package.json electron-builder.yml resources/cherry-studio/release-history.json resources/builtin-agents/cherry-assistant/product-manifest.json
    git commit -S --signoff -m "chore(release): prepare v{version}"
    git cat-file commit HEAD | grep -q '^gpgsig '
    git log -1 --format=%B | grep -q '^Signed-off-by: '
    git push -u origin release/v{version}
    
  2. In GitHub Actions, stop after updating package.json and electron-builder.yml. Temporary helper files and local Git operations are allowed; the workflow extracts those two file changes, restores the frozen source SHA, and discards everything else. It then derives the release history, validates, generates the product manifest, creates the branch, and uses GitHub's API to create and verify the signed, DCO-compliant commit. Never push from the Claude step.
  3. Report the release branch and next steps. Do not create a PR yet: the release must be built and published from this branch first.

CI Trigger Chain

  • Wait for the CI push run on the new release/v{version} commit to succeed. auto-release-build.yml revalidates that exact live branch head and dispatches release.yml with all; it builds macOS, Windows, and Linux and creates or updates the draft GitHub Release. Use release.yml manually only to retry a failed all-platform build or one platform for the unchanged tagged commit.
  • While a single draft semantic-version release is active, backport-release-fixes.yml opens a backport PR for the first merged hotfix: <description> or hotfix(<kebab-case-scope>): <description> PR from main, applies any optional bilingual release note, then appends consecutive hotfixes and source markers to that same open topic branch. It manages every source PR's hotfix and backport-status labels and reports failures on the source PR; never merge main into the release branch.
  • Review the backport PR, wait for its CI, and merge it. After the resulting release-branch push passes CI, the exact-head all-platform draft rebuild starts automatically.
  • A successful exact-head all-platform build starts publish-release.yml. Approve the release Environment deployment after inspecting the draft. Publication then acquires the release-state lock, revalidates the approved run, release branch, tag, draft, artifacts, open PRs, and pending hotfixes, and publishes only if they still agree. The draft body contains the bilingual electron-builder.yml notes followed by GitHub's generated changes. The final fetched main SHA is the hotfix cutoff; a hotfix merged after that snapshot belongs to the next release. Publication triggers post-release.yml, which uses the published tag as its source, applies only the release metadata delta to the latest main, and creates a release-sync/v{version} metadata-only PR.
  • The metadata PR synchronizes only package.json, electron-builder.yml, release history, and the generated product manifest. It triggers ci.yml; merge it only after CI passes.
  • When squash-merging the metadata PR, set the commit title to exactly chore(release): sync v{version} metadata with only GitHub's optional PR-number suffix, and keep release-metadata-boundary: v{version} on its own line in the squash commit body so the next release can find the boundary reliably.

Constraints

  • Always read electron-builder.yml before modifying it to understand the current format.
  • Never retain changes outside package.json, electron-builder.yml, resources/cherry-studio/release-history.json, and the generated resources/builtin-agents/cherry-assistant/product-manifest.json.
  • Never push directly to main.
  • Never create the release metadata PR before the GitHub Release is published; post-release.yml owns that step.
  • Always show the generated release notes to the user before creating the release branch (unless running in CI with no interactive user).

Signals

GitHub stars
52k
Forks
5k
Last commit
Sep 2026

Questions

What does prepare-release do?
It collects commits since the last published release, generates bilingual release notes, updates version files, and creates a release branch, stopping early if anything looks wrong.
When should I use prepare-release?
Use it when asked to prepare or create a release, bump a version, or run /prepare-release.
Does prepare-release publish the release?
No. It prepares the release by creating a branch and updating files, but it does not publish the release.
What languages are the release notes in?
The release notes are generated in English and Chinese.
What happens if something looks wrong?
The skill stops early if anything looks wrong.
Advanced
Item type
skill
Key
prepare-release-cherryhq
Source
github.com/cherryhq/cherry-studio