agentleFS
mir-ammir-am/skills-agents-ai-coding/skills/gh-release/SKILL.md

gh-release

Skill2 starsChanged 42 days agoMITView on GitHub

Create an annotated git tag and GitHub prerelease from the latest versioned CHANGELOG entry

  • Commits and pushes

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

53 rules. About 0 are common, 25 are distinctive, and 28 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈25

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 28

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Create an annotated git tag for the newest versioned release in CHANGELOG.md

    line 13
  2. Distinctive:

    Create a GitHub prerelease with notes copied from that same changelog entry

    line 14
  3. Very specific:

    Treat the first versioned section below ## [Unreleased] as the release to publish

    line 15
  4. Distinctive:

    Use this skill when:

    line 23
  5. Distinctive:

    Do not use this skill when:

    line 28
  6. Distinctive:

    Must be a git repository

    line 36
  7. Very specific:

    Run changelog-bump-ver first so the newest release already exists as a versioned changelog section

    line 46
  8. Very specific:

    Not on default branch -> Error: Releases must be created from the default branch tip.

    line 74
  9. Distinctive:

    If you need to confirm compare-link or release-note behavior during execution, use the WebFetch tool for those URLs.

    line 85
  10. Very specific:

    If this fails -> error: Error: This skill requires a git repository.

    line 94
  11. Very specific:

    If missing -> error: Error: CHANGELOG.md not found.

    line 99
  12. Very specific:

    Prefer gh repo view so repositories with non-main default branches are handled correctly

    line 130
  13. Very specific:

    If GitHub metadata is unavailable, fall back to origin/HEAD

    line 131
  14. Very specific:

    If origin/HEAD is unavailable, fall back to local main, then local master

    line 132
  15. Very specific:

    If empty -> error: Error: Could not determine the default branch.

    line 134
  16. Very specific:

    If current_branch is not equal to default_branch -> error: Error: Releases must be created from the default branch tip.

    line 141
  17. Very specific:

    If this prints any output -> error: Error: Working tree is not clean.

    line 147
  18. Very specific:

    Ensure CHANGELOG.md has exactly one unreleased section:

    line 151
  19. Very specific:

    Treat v1.2.3 and 1.2.3 as the same version internally

    line 172
  20. Very specific:

    Always publish the git tag and GitHub release as v1.2.3

    line 173
  21. Very specific:

    If no such heading exists -> error: Error: No versioned release entry found below ## [Unreleased].

    line 179
  22. Very specific:

    Read the selected release section from its heading line until the next ## heading or end of file.

    line 183
  23. Distinctive:

    Keep blank lines and subsection order intact

    line 188
  24. Very specific:

    Do not invent missing Added, Changed, Fixed, or other subsections

    line 189
  25. Distinctive:

    If there is another versioned release heading after the selected one, that next heading is the previous release version

    line 215
  26. Distinctive:

    If there is no older versioned heading, this is the first release

    line 216
  27. Distinctive:

    Verify that tag exists locally or on origin

    line 220
  28. Very specific:

    If the previous changelog version exists but the tag does not -> error: Error: Previous changelog version exists but matching git tag was …

    line 221
  29. Distinctive:

    Use the first commit on the default branch as the start of the full changelog range

    line 224
  30. Distinctive:

    Use that commit SHA as the start of the compare range.

    line 232
  31. Very specific:

    If either check finds the tag -> error: Error: Tag vX.Y.Z already exists locally or on origin.

    line 243
  32. Very specific:

    Use gh repo view --json url --jq '.url' to get the canonical GitHub repository URL

    line 275
  33. Very specific:

    Do not repeat the release heading line from CHANGELOG.md

    line 279
  34. Distinctive:

    Do not add extra summary prose above the changelog notes

    line 280
  35. Distinctive:

    Keep the exact subsection content from the changelog

    line 281
  36. Very specific:

    Always include the ## Full Changelog section at the end

    line 282
  37. Distinctive:

    Write the assembled prerelease body from Step 6 into a temporary file before creating the release:

    line 286
  38. Very specific:

    Do not pass an empty temp file to gh release create --notes-file

    line 305
  39. Distinctive:

    Remove the temp file after the release command succeeds or fails

    line 306
  40. Very specific:

    Create the release tag at the current HEAD commit:

    line 310
  41. Distinctive:

    Use an annotated tag, not a lightweight tag

    line 317
  42. Distinctive:

    Do not retag an existing version

    line 319
  43. Distinctive:

    Ensure the prerelease is published from the annotated tag you created in Step 8

    line 332
  44. Distinctive:

    If the tag push fails, stop and report the error instead of creating the release

    line 333
  45. Distinctive:

    Create a temporary notes file, then publish the prerelease:

    line 337
  46. Distinctive:

    Use the already-pushed annotated tag from Step 9 rather than relying on GitHub to create a new ref

    line 350
  47. Very specific:

    If gh release create fails after the local tag was created:

    line 352
  48. Distinctive:

    Remove the temp notes file before exiting

    line 355
  49. Distinctive:

    Do not claim release success

    line 356
  50. Distinctive:

    After the release command finishes, clean up the temp file:

    line 358
  51. Distinctive:

    When scanning for the release to publish:

    line 401
  52. Very specific:

    Start at the ## [Unreleased] heading

    line 402
  53. Very specific:

    Use document order, not highest-version sorting, because changelog-bump-ver writes the newest release directly below Unreleased

    line 404

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

What I do

  • Create an annotated git tag for the newest versioned release in CHANGELOG.md
  • Create a GitHub prerelease with notes copied from that same changelog entry
  • Treat the first versioned section below ## [Unreleased] as the release to publish
  • Preserve the release section subsections and bullets exactly as written in the changelog
  • Preserve the repo's ### Misc subsection at the end of release notes when present
  • Append a ## Full Changelog section that compares the previous release to the new tag
  • Handle first-release repositories by comparing the default branch's first commit to the new release tag

When to use me

Use this skill when:

  • You already ran changelog-bump-ver and now want to publish that version as a git tag and GitHub prerelease
  • The repository uses CHANGELOG.md with Keep a Changelog style version headings
  • You want prerelease notes to come directly from the latest versioned changelog section

Do not use this skill when:

  • CHANGELOG.md still only has unreleased notes and no new versioned section yet; run changelog-bump-ver first
  • You want this workflow to rewrite CHANGELOG.md
  • You want to publish a full GitHub release instead of a prerelease

Prerequisites

Required:

  • Must be a git repository
  • CHANGELOG.md exists at the repository root
  • gh is installed and authenticated
  • origin remote exists
  • The working tree is clean
  • HEAD is on the default branch
  • CHANGELOG.md contains exactly one ## [Unreleased] section
  • CHANGELOG.md contains at least one versioned release heading below ## [Unreleased]

Expected workflow:

  • Run changelog-bump-ver first so the newest release already exists as a versioned changelog section
  • Then run gh-release to publish that version as a prerelease

Validation checks:

# Check git repository
git rev-parse --git-dir

# Check changelog exists
[ -f CHANGELOG.md ]

# Check GitHub CLI auth
gh auth status

# Check origin remote exists
git remote get-url origin

# Check working tree is clean
git status --short

# Detect default branch from GitHub metadata
gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'

Error messages:

  • Missing changelog -> Error: CHANGELOG.md not found. Run changelog-bump-ver first.
  • Missing versioned release -> Error: No versioned release entry found below ## [Unreleased]. Run changelog-bump-ver first.
  • Dirty working tree -> Error: Working tree is not clean. Commit or stash changes before creating a release.
  • Not on default branch -> Error: Releases must be created from the default branch tip.
  • Tag already exists -> Error: Tag vX.Y.Z already exists locally or on origin.
  • Missing previous tag for non-first release -> Error: Previous changelog version exists but matching git tag was not found.

References

This skill follows:

If you need to confirm compare-link or release-note behavior during execution, use the WebFetch tool for those URLs.

Workflow

Step 1: Validate Repository State

git rev-parse --git-dir 2>/dev/null

If this fails -> error: Error: This skill requires a git repository.

[ -f CHANGELOG.md ] && echo "found" || echo "missing"

If missing -> error: Error: CHANGELOG.md not found. Run changelog-bump-ver first.

gh auth status

If this fails -> error: Error: GitHub CLI not authenticated. Run: gh auth login

git remote get-url origin >/dev/null

If this fails -> error: Error: Git remote 'origin' not found.

default_branch="$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name' 2>/dev/null)"

if [ -z "$default_branch" ]; then
  default_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')"

  if [ -z "$default_branch" ]; then
    if git show-ref --verify --quiet refs/heads/main; then
      default_branch="main"
    elif git show-ref --verify --quiet refs/heads/master; then
      default_branch="master"
    fi
  fi
fi

printf '%s\n' "$default_branch"

Rules:

  • Prefer gh repo view so repositories with non-main default branches are handled correctly
  • If GitHub metadata is unavailable, fall back to origin/HEAD
  • If origin/HEAD is unavailable, fall back to local main, then local master

If empty -> error: Error: Could not determine the default branch.

current_branch="$(git branch --show-current)"
printf '%s\n' "$current_branch"

If current_branch is not equal to default_branch -> error: Error: Releases must be created from the default branch tip.

git status --short

If this prints any output -> error: Error: Working tree is not clean. Commit or stash changes before creating a release.

Step 2: Validate Changelog Shape

Ensure CHANGELOG.md has exactly one unreleased section:

unreleased_count="$(grep -c '^## \[Unreleased\]$' CHANGELOG.md)"
printf '%s\n' "$unreleased_count"

Rules:

  • 0 -> error: Error: CHANGELOG.md does not contain a ## [Unreleased] section.
  • 1 -> continue
  • >1 -> error: Error: Multiple ## [Unreleased] sections found. Normalize the changelog before releasing.

Then identify the first versioned release heading after ## [Unreleased].

Accepted heading formats:

  • ## [v1.2.3]
  • ## [v1.2.3] - 2026-03-20
  • ## [1.2.3]
  • ## [1.2.3] - 2026-03-20

Normalization rules:

  • Treat v1.2.3 and 1.2.3 as the same version internally
  • Always publish the git tag and GitHub release as v1.2.3
  • Ignore comparison-link lines such as [Unreleased]: ...

Selection rule:

  • Starting from ## [Unreleased], the first versioned ## [...] heading encountered is the release to publish

If no such heading exists -> error: Error: No versioned release entry found below ## [Unreleased]. Run changelog-bump-ver first.

Step 3: Extract Release Version and Notes

Read the selected release section from its heading line until the next ## heading or end of file.

Rules:

  • Exclude the selected heading line from the notes body
  • Preserve all subsection headings and bullets exactly as written
  • Keep blank lines and subsection order intact
  • Do not invent missing Added, Changed, Fixed, or other subsections
  • Allow nonstandard subsections and keep them unchanged
  • Preserve ### Misc exactly as written as the final subsection when the changelog uses the repo's extended template

Example release body extracted from CHANGELOG.md:

### Added
- Support release digests

### Fixed
- Correct changelog parsing for empty sections

### Misc
- Refactor changelog parsing for prerelease note generation

Normalize the release version to a tag value:

  • 1.2.3 -> v1.2.3
  • v1.2.3 -> v1.2.3

Step 4: Resolve Previous Release Context

Determine whether this is the first release.

Rules:

  • If there is another versioned release heading after the selected one, that next heading is the previous release version
  • If there is no older versioned heading, this is the first release

For non-first releases:

  • Normalize the previous version to vX.Y.Z
  • Verify that tag exists locally or on origin
  • If the previous changelog version exists but the tag does not -> error: Error: Previous changelog version exists but matching git tag was not found.

For first releases:

  • Use the first commit on the default branch as the start of the full changelog range

Commands:

git rev-list --max-parents=0 "$default_branch" | tail -n 1

Use that commit SHA as the start of the compare range.

Step 5: Verify the New Tag Does Not Already Exist

tag="vX.Y.Z"

git rev-parse -q --verify "refs/tags/$tag" >/dev/null && echo "local-exists"
git ls-remote --tags origin "refs/tags/$tag" | grep -q . && echo "remote-exists"

If either check finds the tag -> error: Error: Tag vX.Y.Z already exists locally or on origin.

Step 6: Build the GitHub Prerelease Notes

The prerelease title must be exactly the normalized tag:

vX.Y.Z

The prerelease body must be:

  1. The extracted changelog body for the selected release
  2. A blank line
  3. ## Full Changelog
  4. A compare line in one of these forms

Non-first release:

## Full Changelog
https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0

First release:

## Full Changelog
First release: https://github.com/OWNER/REPO/compare/<first-commit-sha>...v0.1.0

Repository URL guidance:

  • Use gh repo view --json url --jq '.url' to get the canonical GitHub repository URL
  • Build the compare link from that URL

Body construction rules:

  • Do not repeat the release heading line from CHANGELOG.md
  • Do not add extra summary prose above the changelog notes
  • Keep the exact subsection content from the changelog
  • Always include the ## Full Changelog section at the end

Step 7: Write the Prerelease Notes File

Write the assembled prerelease body from Step 6 into a temporary file before creating the release:

notes_file="$(mktemp)"

cat > "$notes_file" <<'EOF'
### Added
- Example release note

### Misc
- Example internal refactor note

## Full Changelog
https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0
EOF

Rules:

  • The file content must be the exact assembled release body from Step 6
  • Do not pass an empty temp file to gh release create --notes-file
  • Remove the temp file after the release command succeeds or fails

Step 8: Create the Annotated Tag

Create the release tag at the current HEAD commit:

git tag -a "vX.Y.Z" -m "Release vX.Y.Z"

Rules:

  • Use an annotated tag, not a lightweight tag
  • Tag the current HEAD only after all validation passes
  • Do not retag an existing version

Step 9: Push and Verify the Annotated Tag

Push the annotated tag to origin before creating the GitHub prerelease:

git push origin "vX.Y.Z"
git ls-remote --tags origin "refs/tags/vX.Y.Z"

Rules:

  • Push the tag before running gh release create
  • Ensure the prerelease is published from the annotated tag you created in Step 8
  • If the tag push fails, stop and report the error instead of creating the release

Step 10: Create the GitHub Prerelease

Create a temporary notes file, then publish the prerelease:

gh release create "vX.Y.Z" \
  --prerelease \
  --title "vX.Y.Z" \
  --notes-file "$notes_file"

Behavior rules:

  • The release must be marked with --prerelease
  • The release title must match the tag exactly
  • The release notes must come from the generated body written in Step 7
  • Use the already-pushed annotated tag from Step 9 rather than relying on GitHub to create a new ref

If gh release create fails after the local tag was created:

  • Report the failure clearly
  • Tell the user the local and remote tag now exist and may need cleanup or retry
  • Remove the temp notes file before exiting
  • Do not claim release success

After the release command finishes, clean up the temp file:

rm -f "$notes_file"

Step 11: Report the Result

Report:

  • Published tag
  • Whether the release was first release or had a previous release
  • Compare range used in Full Changelog
  • GitHub prerelease URL

Standard success message:

✓ Created prerelease v0.5.0
  - Tag: v0.5.0
  - Previous release: v0.4.0
  - Full Changelog: https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0
  - GitHub prerelease: https://github.com/OWNER/REPO/releases/tag/v0.5.0

First release success message:

✓ Created prerelease v0.1.0
  - Tag: v0.1.0
  - Previous release: none (first release)
  - Full Changelog: https://github.com/OWNER/REPO/compare/<first-commit-sha>...v0.1.0
  - GitHub prerelease: https://github.com/OWNER/REPO/releases/tag/v0.1.0

Version Parsing Rules

Accept these heading patterns when scanning CHANGELOG.md:

  • ^## \[(v)?([0-9]+\.[0-9]+\.[0-9]+)\]$
  • ^## \[(v)?([0-9]+\.[0-9]+\.[0-9]+)\] - [0-9]{4}-[0-9]{2}-[0-9]{2}$

Ignore:

  • Comparison links such as [Unreleased]: ...
  • Headings with non-SemVer values like vNext, 1.0, or release-1
  • Inline code or prose mentions of versions

When scanning for the release to publish:

  • Start at the ## [Unreleased] heading
  • Select the first later heading that matches the accepted version formats
  • Use document order, not highest-version sorting, because changelog-bump-ver writes the newest release directly below Unreleased

Release Body Example

Given this changelog excerpt:

## [Unreleased]

### Added

### Changed

### Deprecated

### Removed

### Fixed

### Security

### Misc

## [v0.5.0] - 2026-03-20

### Added
- Add `gh-release` skill for annotated tags and GitHub prereleases

### Fixed
- Preserve changelog subsection order in generated release notes

### Misc
- Refactor release note assembly for changelog subsection passthrough

## [v0.4.0] - 2026-03-10

### Added
- Add changelog version bump workflow

Publish v0.5.0 with notes body:

### Added
- Add `gh-release` skill for annotated tags and GitHub prereleases

### Fixed
- Preserve changelog subsection order in generated release notes

### Misc
- Refactor release note assembly for changelog subsection passthrough

## Full Changelog
https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0

Edge Cases

ScenarioBehavior
CHANGELOG.md missingError: CHANGELOG.md not found. Run changelog-bump-ver first.
No ## [Unreleased] sectionError: CHANGELOG.md does not contain a ## [Unreleased] section.
Multiple ## [Unreleased] sectionsError: Multiple ## [Unreleased] sections found. Normalize the changelog before releasing.
No versioned release below UnreleasedError: No versioned release entry found below ## [Unreleased]. Run changelog-bump-ver first.
Release version heading is malformedIgnore it unless it matches the accepted SemVer patterns
Current branch is not the default branchError: Releases must be created from the default branch tip.
Working tree is dirtyError: Working tree is not clean. Commit or stash changes before creating a release.
Tag already exists locally or remotelyError: Tag vX.Y.Z already exists locally or on origin.
Could not determine default branch from GitHub metadataFall back to origin/HEAD, then local main, then local master
Previous changelog version exists but tag is missingError: Previous changelog version exists but matching git tag was not found.
Temp notes file was created but not populatedError: stop before gh release create; do not publish empty notes
Annotated tag was created locally but not pushedPush and verify the tag on origin before creating the release
First release with no older version headingCompare first commit on default branch to new tag
First release with a single commitCompare that same first commit to the new tag
Release section contains ### MiscPreserve it unchanged in the generated notes body
gh release create fails after tag creationReport partial failure and note that the local tag now exists

Complete Workflow Example

Scenario: changelog-bump-ver already created ## [v0.5.0] - 2026-03-20 above v0.4.0

  1. Validate repository state:
    git rev-parse --git-dir
    gh auth status
    git remote get-url origin
    git status --short
    git branch --show-current
  2. Detect default branch:
    gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
    # main
  3. Identify the release heading below Unreleased:
    • Selected heading: ## [v0.5.0] - 2026-03-20
    • Previous heading: ## [v0.4.0] - 2026-03-10
  4. Verify tags:
    git rev-parse -q --verify refs/tags/v0.5.0
    git rev-parse -q --verify refs/tags/v0.4.0
  5. Build notes body:
     ### Added
     - Add `gh-release` skill for annotated tags and GitHub prereleases
    
     ### Fixed
     - Preserve changelog subsection order in generated release notes
    
     ### Misc
     - Refactor release note assembly for changelog subsection passthrough
    
    ## Full Changelog
    https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0
  6. Write notes, push tag, and create prerelease:
     notes_file="$(mktemp)"
    
     cat > "$notes_file" <<'EOF'
     ### Added
     - Add `gh-release` skill for annotated tags and GitHub prereleases
    
     ### Fixed
     - Preserve changelog subsection order in generated release notes
    
     ### Misc
     - Refactor release note assembly for changelog subsection passthrough
    
     ## Full Changelog
     https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0
     EOF
    
    git tag -a "v0.5.0" -m "Release v0.5.0"
    git push origin "v0.5.0"
    
    gh release create "v0.5.0" \
      --prerelease \
      --title "v0.5.0" \
      --notes-file "$notes_file"
    
    rm -f "$notes_file"
  7. Notify the user:
    ✓ Created prerelease v0.5.0
      - Tag: v0.5.0
      - Previous release: v0.4.0
      - Full Changelog: https://github.com/OWNER/REPO/compare/v0.4.0...v0.5.0
      - GitHub prerelease: https://github.com/OWNER/REPO/releases/tag/v0.5.0

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.