agentsclimarketplace

Semantic release automation

Skill stealth-engine/skills/skills/semantic-release-automation

Agent skills we use daily in the studio — reusable, on-demand context for Claude Code, Cursor & other skills.sh-compatible AI agents. Install: npx skills add stealth-engine/skills

Install
npx -y skills add stealth-engine/skills --skill semantic-release-automation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Automate versioning, changelog, tags, GitHub Releases, and npm publishing from Conventional Commits with semantic-release. Use when setting up or debugging automated releases, wiring a `.releaserc` / `release` config and the plugin pipeline (commit-analyzer, release-notes-generator, changelog, npm, git, github), making `main` cut a version on merge, generating CHANGELOG.md, publishing to npm or creating a GitHub Release per release, doing per-package releases in a monorepo (per-package tags + paths-filter matrix), pooling commits into a less-frequent release, or fixing a release that didn't fire / a CI loop from the release commit. Covers the single-package and monorepo flavors and the GitHub Actions workflow.

SKILL.md

14.5 KB, as published. Nobody here has run it

semantic-release automation

semantic-release reads Conventional Commits, computes the next semver version, writes the changelog, tags, and registers a GitHub Release (and optionally publishes to npm) — all in CI, no manual version bumps. This skill is the tooling that consumes the commit format; read conventional-commits first for how the bump is decided.

Copy-paste configs: templates/ — a single-package config, a monorepo per-package config, and the GitHub Actions workflow.

How a release happens

  1. You merge a PR to main (squash; the PR title is the Conventional Commit).
  2. The workflow runs semantic-release, which:
    • commit-analyzer → reads commits since the last tag, decides major/minor/patch (or no release).
    • release-notes-generator → builds the notes from those commits.
    • changelog → writes/updates CHANGELOG.md.
    • npm (optional) → bumps package.json and publishes to npm.
    • git → commits CHANGELOG.md/package.json back as chore(release): X.Y.Z (the version tag itself is created by semantic-release core, not this plugin).
    • github → creates the GitHub Release (the canonical record of what shipped).

If no commit since the last release warrants a bump (only chore/docs/…), it does nothing — correct, not a failure.

Outputs are à la carte — pick any subset

Those six steps read like one bundle, but the outputs are independent: keep only the plugins for what you actually want. commit-analyzer + release-notes-generator are the baseline (they compute the version and notes) — keep them; everything below is opt-in.

OutputPlugin(s)Needs
git tag(semantic-release core — no plugin)— (created on every release)
GitHub Release@semantic-release/githubworkflow grants write permissions — the template sets contents + issues + pull-requests (the plugin's default success and failure issue/PR comments need the latter two; see the plugin docs before trimming them); default GITHUB_TOKEN then suffices — a PAT/bot token only if the Release must trigger a downstream on: release workflow
CHANGELOG.md committed to the repo@semantic-release/changelog + @semantic-release/gitrelease bot can commit to main (bypass branch protection)
package.json version bump (no publish)@semantic-release/npm ("npmPublish": false) + @semantic-release/git to commit it backwithout @semantic-release/git the bump is made in CI then discardedpackage.json in the repo stays unchanged
npm publish@semantic-release/npmNPM_TOKEN; libraries only

The git tag always happens (core); each other output appears only if its plugin is present — drop @semantic-release/npm and package.json is never bumped; drop @semantic-release/git and there's no in-repo CHANGELOG.md/version commit (tag-only).

  • A deployed app typically wants CHANGELOG.md + GitHub Release but not npm publish — drop @semantic-release/npm (or "npmPublish": false to still bump package.json). A library adds npm publish on top.
  • If your deploy gate fires on the published GitHub Release — both the release-event-driven deploy and the Promotion Branch gate do (on: release with types: [published], see production-release-gating) — then @semantic-release/github is required: no Release, no deploy. (Only Vercel's build-skip ignoreCommand gate needs no Release — but it matches on the chore(release): X.Y.Z commit, so it requires @semantic-release/git instead; a tag-only config leaves it nothing to detect.)

Where config lives

Either a .releaserc.json at the repo/package root, or a "release" key in package.json. Both are equivalent; pick one. Plugin order matters — it's the execution pipeline, and npm must run before git so the bumped package.json is what gets committed.

Flavor 1 — single package (npm or app)

Use templates/releaserc.single-package.json.

  • Publishing to npm: keep @semantic-release/npm.
  • Not publishing (a deployed app, or a private package): drop @semantic-release/npm (or set ["@semantic-release/npm", { "npmPublish": false }] to still bump package.json without publishing).

Flavor 2 — monorepo, per-package releases

Each package gets its own .releaserc.json with a package-scoped tagFormat (my-app-v${version}) so versions/tags don't collide — see templates/releaserc.monorepo-package.json. The workflow detects which packages changed with dorny/paths-filter and runs semantic-release once per changed package (a matrix), max-parallel: 1 with a git pull --rebase retry so concurrent tag pushes don't collide.

  • Replace my-app in both tagFormat and the git commit message with the real package name — otherwise every package shares one tag and the deploy gate can't match the scope. The template's exec step bumps package.json inline (no external script to create); swap in a script only if you need extra prepare steps. (It reserialises package.json with 2-space indent — if your repo uses other formatting, use a script or a targeted replace to avoid a noisy diff.)

  • The shipped templates/release.yml is single-package. For a monorepo, wrap that same semantic-release call in a dorny/paths-filter → matrix job (max-parallel: 1 + a git pull --rebase retry so concurrent tag pushes don't collide):

    jobs:
      detect: # which packages changed?
        runs-on: ubuntu-latest
        outputs:
          changed: ${{ steps.f.outputs.changes }}
        steps:
          - uses: actions/checkout@v7
          - id: f
            uses: dorny/paths-filter@v4
            with:
              filters: | # name each key after the package's directory
                apps/web: ['apps/web/**']
                packages/lib: ['packages/lib/**']
      release:
        needs: detect
        if: ${{ needs.detect.outputs.changed != '[]' }}
        strategy:
          max-parallel: 1
          fail-fast: false
          matrix:
            pkg: ${{ fromJson(needs.detect.outputs.changed) }}
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v7
            with: { fetch-depth: 0 } # add persist-credentials: false to use GH_TOKEN below (private repos then need it wired into the git pull, e.g. a URL with the token)
          - uses: actions/setup-node@v6
            with: { node-version: lts/*, cache: npm }
          - run: npm ci
          - working-directory: ${{ matrix.pkg }} # filter key == package dir
            env: { GITHUB_TOKEN: '${{ secrets.GH_TOKEN || github.token }}' }
            # An earlier matrix job may have pushed its release commit; rebase + retry
            # so this job isn't behind main when it pushes its own tag.
            # --repository-url (as in the single-package template) guards against an
            # org/repo rename desyncing package.json's repository field (EMISMATCHGITHUBURL).
            run: |
              R="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
              # checkout defaults to detached HEAD on push — land on the branch so the
              # rebase-retry has a branch to rebase onto (and to push the tag from).
              git checkout "$GITHUB_REF_NAME"
              git pull --rebase origin "$GITHUB_REF_NAME" || true
              npx semantic-release --repository-url "$R" || { git pull --rebase origin "$GITHUB_REF_NAME"; npx semantic-release --repository-url "$R"; }
    

    dorny/paths-filter emits changes as a JSON array of the matched filter keys; naming each key after the package dir lets working-directory: ${{ matrix.pkg }} resolve. Each package uses its own .releaserc (above).

  • Which package releases comes from changed file paths (paths-filter), and the bump from the commit/PR-title type. Keep a PR to one package so the squash commit maps cleanly. For strict per-package commit attribution, add semantic-release-monorepo (it filters commits to those touching the package); plain semantic-release reads repo-wide history.

  • The monorepo template's git message carries [skip ci] and uses a [skip ci]- aware deploy gate — see production-release-gating.

The GitHub Actions workflow

Use templates/release.yml. Non-negotiables:

  • fetch-depth: 0 — semantic-release needs full history + tags.
  • Don't loop: the chore(release): … commit it pushes would re-trigger the workflow. Guard with if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} (this template — the ${{ }} wrapper is required; a bare leading ! is invalid YAML) or put [skip ci] in the release commit message (the monorepo template) if your CI honours it. The template's guard also restricts the workflow_dispatch (manual / pooled) path to the default branch, so a manual run can't accidentally cut a release from a feature branch.
  • Token: the built-in GITHUB_TOKEN works for tags/Releases, but commits it makes won't trigger other workflows. If a release must kick off a downstream deploy via on: push/on: release, use a PAT/bot GH_TOKEN. (See production-release-gating for the on: release (types: [published]) pattern, which sidesteps this.)

Pool commits into fewer releases

Don't want a release on every merge? Keep merging to main continuously (trunk), but change the trigger: drop on: push and run the release on workflow_dispatch (a manual "cut a release" button) and/or a schedule:. semantic-release batches every commit since the last tag into one larger release — no release branch needed. For continuous prereleases, add a next/beta branch to branches (channel releases) and fast-forward to main for the stable cut.

This is the short version. For the full treatment — the manual / scheduled / prerelease-channel models, when a release branch is (rarely) worth it, the gotchas, and copy-paste workflow + channel-config templates — use the dedicated pooled-release skill (it reuses this exact pipeline; only the trigger changes).

Gotchas

  • Plugin order is the pipeline. commit-analyzernoteschangelognpmgitgithub. changelog, npm, and (in the monorepo flavor) the exec bump must all come before gitgit commits the files they produce/bump, so a wrong order commits a stale CHANGELOG.md/package.json.
  • EMISMATCHGITHUBURL after an org/repo renamepackage.json's repository field desyncs from the live URL. Pass --repository-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" (the template does — GITHUB_SERVER_URL rather than a hard-coded github.com keeps it working on GitHub Enterprise Server).
  • Nothing published? Check: was the merged commit/PR-title a releasable type (feat/fix/breaking, not chore)? Is fetch-depth: 0 set? Is the branch in branches? A non-conventional title silently yields no release.
  • NPM_TOKEN needs publish rights (and 2FA set to "automation"/auth-token, not OTP) for @semantic-release/npm.
  • Don't hand-write chore(release): commits — they're the bot's output.
  • The git plugin pushes the release commit straight to main. That's by design — release automation is the one sanctioned committer to main (it bypasses the feature-branch + PR rule that applies to humans). If main has branch protection requiring PRs/reviews, give the release token (a bot/PAT) bypass permission, or drop @semantic-release/git and run tag-only (no changelog/version commit back — you lose the in-repo CHANGELOG.md bump).

Verify

npx semantic-release --dry-run prints the next version and release notes without publishing — the fastest way to confirm your config and that the commits produce the bump you expect. Run it on a branch listed in branches (e.g. main); on any other branch semantic-release logs "skipping" and prints no version — pass --branches "$(git branch --show-current)" to force it on a feature branch.

See also

  • conventional-commits — the input format that decides the version bump.
  • pooled-release — want fewer, batched releases instead of one per merge? The "release train" variant — same pipeline, the trigger changes (on-demand / scheduled / prerelease channels).
  • production-release-gating — deploy only on a real release (the GitHub Release / chore(release): commit this produces).
  • git-trunk-branch-and-pr-automation — squash + semantic PR title that becomes the commit analysed here.

Sources

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.