Agent skill
releaserator
Generate semantic versioned releases with changelog and GitHub releases
Install this agent skill to your Project
npx add-skill https://github.com/pborenstein/plinth/tree/main/skills/releaserator
SKILL.md
Releaserator Skill
Automate the release process with semantic versioning, Keep A Changelog format, and GitHub release creation.
Implementation Steps
Step 1: Pre-flight Checks
Task: Verify repository state is clean and ready for release
Run these checks:
# Check git status (must be clean)
git status --porcelain
# Check current branch
git branch --show-current
# Detect version file (check in priority order)
for f in .claude-plugin/plugin.json package.json pyproject.toml Cargo.toml; do
test -f "$f" && echo "VERSION_FILE=$f" && break
done
# Check for gh CLI
command -v gh >/dev/null 2>&1 && echo "INSTALLED" || echo "NOT_FOUND"
Validation:
- Working directory MUST be clean (no uncommitted changes)
- If dirty: Error with message: "Working directory has uncommitted changes. Please commit or stash changes before creating a release."
- Current branch SHOULD be main/master
- If not: Ask user to confirm creating release from current branch
- A supported version file MUST exist (one of the following, checked in order):
.claude-plugin/plugin.json— Claude Code pluginpackage.json— Node.js projectpyproject.toml— Python project (version in[project]or[tool.poetry])Cargo.toml— Rust project (version in[package])- If none found: Error with message: "No supported version file found. Releaserator supports: plugin.json, package.json, pyproject.toml, Cargo.toml"
- gh CLI MUST be installed
- If missing: Error with message: "GitHub CLI (gh) not found. Install with: brew install gh"
Store:
VERSION_FILE= path to the detected version file
Optional check: Verify docs/CONTEXT.md is recent (< 24 hours old)
- If stale: Warn user and suggest running
/session-wrapupfirst
Step 2: Determine Current Version and Last Release
Task: Read current version and find last release tag
# Read current version from VERSION_FILE (use Read tool)
# Parse the version field based on file type (see below)
# Find last tag (if any)
git describe --tags --abbrev=0 2>/dev/null || echo "NO_TAGS"
# List all tags for reference
git tag --list --sort=-version:refname
Version parsing by file type:
| Version File | How to read version |
|---|---|
.claude-plugin/plugin.json |
JSON field "version" |
package.json |
JSON field "version" |
pyproject.toml |
[project] version = "X.Y.Z" or [tool.poetry] version = "X.Y.Z" |
Cargo.toml |
[package] version = "X.Y.Z" |
Logic:
- Read and parse version from VERSION_FILE
- If no tags exist, this is first release (baseline: use all commits)
- If tags exist, use latest tag as baseline for commit collection
Store:
CURRENT_VERSION= version from VERSION_FILELAST_TAG= latest git tag (or "NO_TAGS")IS_FIRST_RELEASE= true if no tags exist
Step 3: Collect Commits Since Last Release
Task: Get all commits since last release for changelog generation
# If tags exist: commits since last tag
git log LAST_TAG..HEAD --oneline --no-merges
# If no tags: all commits (first release)
git log --oneline --no-merges
Parse each commit:
For each commit line, extract:
- Commit hash (first 7 characters)
- Commit type (feat, fix, docs, refactor, perf, chore, test, ci, build, style)
- Scope (optional, in parentheses)
- Breaking change indicator (exclamation mark before colon)
- Description (after colon)
- PR reference (look for PR number pattern)
Example parsing:
abc1234 feat(auth): add OAuth support (#42)
→ hash: abc1234
→ type: feat
→ scope: auth
→ breaking: false
→ description: add OAuth support
→ pr: 42
Check commit body for BREAKING CHANGE:
For commits that might have breaking changes, also check the full commit message:
git log LAST_TAG..HEAD --format="%H %s%n%b%n---" --no-merges
Look for BREAKING CHANGE: in commit body.
Store parsed commits in a data structure:
commits = [
{hash, type, scope, breaking, description, pr},
...
]
Step 4: Determine Version Bump
Task: Analyze commits to determine MAJOR.MINOR.PATCH bump
Rules (Conventional Commits):
-
MAJOR bump if ANY commit has:
- BREAKING CHANGE in commit body/footer, OR
- Exclamation mark before colon (e.g., feat!, fix!)
-
MINOR bump if (and no MAJOR):
- ANY
feat:commits exist
- ANY
-
PATCH bump if (and no MAJOR/MINOR):
- ANY
fix:orperf:commits exist
- ANY
-
No bump if only:
docs:,refactor:,chore:,test:,ci:,build:,style:
Calculate new version:
# Pseudocode for version bump
def bump_version(current, bump_type):
major, minor, patch = map(int, current.split('.'))
if bump_type == 'MAJOR':
return f"{major + 1}.0.0"
elif bump_type == 'MINOR':
return f"{major}.{minor + 1}.0"
elif bump_type == 'PATCH':
return f"{major}.{minor}.{patch + 1}"
else:
return current # No bump
Edge case: If no version bump needed (only docs/chore commits), ask user:
⚠️ No version bump needed (only documentation/chore commits).
Do you want to create a release anyway? (y/N)
Store:
BUMP_TYPE= "MAJOR" | "MINOR" | "PATCH" | "NONE"NEW_VERSION= calculated new version
Step 5: Generate Changelog Entry
Task: Create Keep A Changelog formatted entry for this release
Group commits by section:
| Commit Type | Changelog Section | Variable |
|---|---|---|
feat: |
Added | ADDED |
refactor: (behavior change) |
Changed | CHANGED |
| Mentions "deprecat" | Deprecated | DEPRECATED |
| Mentions "remov" | Removed | REMOVED |
fix: |
Fixed | FIXED |
| Mentions "security" or "CVE" | Security | SECURITY |
Format each commit line:
- Description ([#PR](https://github.com/OWNER/REPO/pull/PR)) ([hash](https://github.com/OWNER/REPO/commit/hash))
If no PR reference, omit PR link. Always include commit hash link.
Breaking changes:
Collect all breaking changes (commits with exclamation mark or BREAKING CHANGE) and add to the top of the relevant section with ⚠️:
- ⚠️ **BREAKING**: Description of breaking change
Extract repo info:
# Get remote URL and parse owner/repo
git remote get-url origin
# Parse: git@github.com:owner/repo.git → owner/repo
# Or: https://github.com/owner/repo.git → owner/repo
Load template: Read skills/releaserator/templates/changelog-entry.md
Substitute variables:
{{VERSION}}→ NEW_VERSION{{DATE}}→ today's date (ISO format: YYYY-MM-DD){{ADDED}}→ formatted commit lines (or empty if none){{CHANGED}}→ formatted commit lines (or empty if none){{DEPRECATED}}→ formatted commit lines (or empty if none){{REMOVED}}→ formatted commit lines (or empty if none){{FIXED}}→ formatted commit lines (or empty if none){{SECURITY}}→ formatted commit lines (or empty if none){{BREAKING}}→ breaking changes summary (or omit section if none)
Remove empty sections: If a section has no commits, remove the entire section heading and content.
Store: CHANGELOG_ENTRY = generated markdown
Step 6: Update or Create CHANGELOG.md
Task: Prepend new version entry to CHANGELOG.md
Check if CHANGELOG.md exists:
test -f CHANGELOG.md && echo "EXISTS" || echo "MISSING"
If CHANGELOG.md exists:
- Read existing CHANGELOG.md
- Find insertion point:
- If
## [Unreleased]exists, insert after it - Otherwise, insert after the header (before first version)
- If
- Insert CHANGELOG_ENTRY
- Update comparison links at bottom:
- Add new version link:
[VERSION]: https://github.com/OWNER/REPO/releases/tag/vVERSION - Update Unreleased link:
[Unreleased]: https://github.com/OWNER/REPO/compare/vVERSION...HEAD
- Add new version link:
If CHANGELOG.md doesn't exist:
- Load template:
skills/releaserator/templates/CHANGELOG.md - Substitute variables:
{{VERSION}}→ NEW_VERSION{{DATE}}→ today's date{{REPO}}→ owner/repo from git remote{{ADDED}},{{CHANGED}}, etc. → from CHANGELOG_ENTRY
- Write new CHANGELOG.md
Use Write tool to create/update CHANGELOG.md
Step 7: Update Version File
Task: Write new version to VERSION_FILE
By file type:
| Version File | How to update |
|---|---|
.claude-plugin/plugin.json |
Update JSON "version" field. Use 2-space indentation, trailing newline. |
package.json |
Update JSON "version" field. Preserve existing indentation and trailing newline. |
pyproject.toml |
Replace version = "OLD" with version = "NEW" in the correct section. |
Cargo.toml |
Replace version = "OLD" with version = "NEW" in the [package] section. |
Use Read and Edit tools for precise replacement.
Step 8: Commit Version Bump
Task: Create commit for version and changelog changes
# Stage files (VERSION_FILE is the detected version file from Step 1)
git add VERSION_FILE CHANGELOG.md
# Create commit
git commit -m "chore: bump version to NEW_VERSION"
Commit message format: chore: bump version to X.Y.Z
This follows conventional commits (type: chore, as it's a release task).
Store commit hash for later reference:
git rev-parse HEAD
Step 9: Create Git Tag
Task: Create annotated git tag for release
# Create annotated tag (not lightweight)
git tag -a vNEW_VERSION -m "Release vNEW_VERSION"
Tag format: v prefix + semantic version (e.g., v1.2.3)
Annotated tags are used (not lightweight) so they contain:
- Tagger name and email
- Tag date
- Tag message
Step 10: Push Changes and Tag
Task: Push commit and tag to remote
Ask user for confirmation:
Ready to push release v1.2.3 to remote?
- Commit: abc1234 "chore: bump version to 1.2.3"
- Tag: v1.2.3
This will:
1. Push commit to main branch
2. Push tag v1.2.3
3. Trigger any CI/CD workflows
Continue? (y/N)
If confirmed:
# Push commit to current branch
git push origin $(git branch --show-current)
# Push tag
git push origin vNEW_VERSION
If not confirmed: Stop here and report that release is ready locally but not pushed.
Step 11: Create GitHub Release
Task: Create GitHub release using gh CLI
Prepare release notes file (temporary file):
- Load template:
skills/releaserator/templates/release-notes.md - Substitute variables:
{{VERSION}}→ NEW_VERSION{{CHANGELOG_ENTRY}}→ the changelog entry content{{REPO}}→ owner/repo
- Write to temporary file (e.g., /tmp/release-notes-NEW_VERSION.md)
Create GitHub release:
gh release create vNEW_VERSION --title "vNEW_VERSION" --notes-file /tmp/release-notes-NEW_VERSION.md
Flags explained:
vNEW_VERSION: Tag name (must already exist from step 9)--title: Release title shown on GitHub--notes-file: Release body (our changelog entry + links)
Capture release URL:
# Get release URL
gh release view vNEW_VERSION --json url --jq .url
Store: RELEASE_URL = GitHub release URL
Step 12: Report Success
Task: Display release information to user
Output a summary:
✅ Release vNEW_VERSION created successfully!
**Changes**:
- Updated VERSION_FILE (OLD_VERSION → NEW_VERSION)
- Created/updated CHANGELOG.md with entry for vNEW_VERSION
- Committed changes (abc1234: "chore: bump version to NEW_VERSION")
- Created git tag vNEW_VERSION
- Pushed commit and tag to remote
- Created GitHub release: RELEASE_URL
**Release includes**:
- X features added
- Y bugs fixed
- Z changes made
**Next steps**:
- Announce release to users
- Update plugin registry (if applicable)
- Monitor for issues in new release
- Continue development
View release: RELEASE_URL
View changelog: https://github.com/OWNER/REPO/blob/main/CHANGELOG.md
Template Variables
Templates use {{VARIABLE}} substitution syntax:
| Variable | Description | Example |
|---|---|---|
{{VERSION}} |
New version number | "1.2.3" |
{{VERSION_TAG}} |
Version with v prefix | "v1.2.3" |
{{OLD_VERSION}} |
Previous version | "1.2.2" |
{{DATE}} |
Release date (ISO) | "2026-01-11" |
{{REPO}} |
GitHub owner/repo | "owner/repo" |
{{ADDED}} |
Added section content | "- New feature\n- Another feature" |
{{CHANGED}} |
Changed section content | "- Updated behavior" |
{{DEPRECATED}} |
Deprecated section | "- Old API deprecated" |
{{REMOVED}} |
Removed section | "- Dropped support for X" |
{{FIXED}} |
Fixed section | "- Bug fix" |
{{SECURITY}} |
Security section | "- CVE fix" |
{{BREAKING}} |
Breaking changes summary | "⚠️ BREAKING: API changed" |
{{CHANGELOG_ENTRY}} |
Full changelog entry | (entire entry content) |
Error Handling
Dirty Working Directory
Error: Git status shows uncommitted changes
Message:
❌ Working directory has uncommitted changes.
Please commit or stash changes before creating a release.
Run: git status
Action: Exit without making any changes
No gh CLI
Error: gh command not found
Message:
❌ GitHub CLI (gh) not found.
Install with: brew install gh
After installing, run: gh auth login
Action: Exit without making any changes
Version Conflict
Error: Tag vX.Y.Z already exists
Message:
❌ Version X.Y.Z already exists as git tag.
Current version in VERSION_FILE: X.Y.Z
Existing tag: vX.Y.Z
Please update the version manually in VERSION_FILE,
or delete the tag if it was created in error:
git tag -d vX.Y.Z
git push origin :refs/tags/vX.Y.Z
Action: Exit without making any changes
No Commits Since Last Release
Error: No new commits found
Message:
⚠️ No commits found since last release vX.Y.Z
Current HEAD: abc1234
Last release tag: vX.Y.Z (abc1234)
These are the same commit. Nothing to release.
Action: Exit without making any changes
No Version Bump Needed
Warning: Only docs/chore commits
Message:
⚠️ No version bump needed (only documentation/chore commits).
Commits since v1.2.3:
- docs: update README
- chore: fix typo in comment
Do you want to create a release anyway? This will keep version at 1.2.3. (y/N)
Action: Ask user to confirm, proceed if yes
Plugin Not in GitHub
Error: Remote URL is not GitHub
Message:
❌ This plugin is not hosted on GitHub.
Remote URL: https://gitlab.com/owner/repo.git
The releaserator currently only supports GitHub.
GitLab and Gitea support coming soon!
Action: Exit without making any changes
Platform Abstraction
Current implementation: GitHub-only
GitHub-specific code is isolated in:
skills/releaserator/platforms/github.md
Platform interface (for future expansion):
- detect_platform() - Detect hosting platform from git remote
- create_release(version, notes) - Create platform release
- get_pr_link(number) - Format PR link URL
- get_commit_link(hash) - Format commit link URL
- check_cli() - Check for platform CLI tool
Platform detection logic:
REMOTE_URL=$(git remote get-url origin 2>/dev/null)
if echo "$REMOTE_URL" | grep -q "github.com"; then
PLATFORM="github"
elif echo "$REMOTE_URL" | grep -q "gitlab.com"; then
PLATFORM="gitlab"
elif echo "$REMOTE_URL" | grep -qE "gitea|codeberg"; then
PLATFORM="gitea"
else
PLATFORM="unknown"
fi
To add GitLab support:
- Create
skills/releaserator/platforms/gitlab.md - Implement same interface methods for GitLab
- Update SKILL.md to load platform-specific code based on detection
- No changes needed to core logic (commit parsing, version bumping, changelog generation)
Benefits:
- GitHub implementation doesn't need refactoring when adding platforms
- Each platform has dedicated documentation file
- Core logic (steps 1-7, 9-12) stays unchanged
- Only step 8 (create release) and link formatting is platform-specific
Common Issues and Solutions
Issue: Tag already exists
Cause: Version bump calculation matched an existing tag
Solution: This shouldn't happen if logic is correct. If it does:
- Check if plugin.json version was manually changed
- Verify last tag detection works correctly
- Ask user if they want to skip tagging or delete old tag
Issue: Commit parsing fails
Cause: Commit messages don't follow Conventional Commits format
Solution:
- Parse commits that do follow format
- Group unparsed commits under "Other changes" section
- Warn user about non-standard commits
Issue: Push fails (no permission)
Cause: User doesn't have push access to remote
Solution:
- Check git push output for authentication errors
- Suggest running
gh auth loginor setting up SSH keys - Report that release is ready locally but not pushed
Issue: GitHub release fails
Cause: gh CLI not authenticated
Solution:
❌ GitHub CLI authentication failed.
Run: gh auth login
Then try creating the release again.
Issue: Can't determine repo owner/name
Cause: Git remote URL in unexpected format
Solution:
- Try multiple parsing patterns:
git@github.com:owner/repo.githttps://github.com/owner/repo.githttps://github.com/owner/repo
- Ask user to manually provide owner/repo if parsing fails
Success Criteria
After running releaserator, verify:
- ✅ CHANGELOG.md created or updated with new entry
- ✅ CHANGELOG.md follows Keep A Changelog format
- ✅ Version file updated correctly (plugin.json, package.json, pyproject.toml, or Cargo.toml)
- ✅ Git commit created with conventional commit message
- ✅ Annotated git tag created (not lightweight)
- ✅ Tag pushed to remote
- ✅ GitHub release created with correct notes
- ✅ Release URL accessible and displays correctly
- ✅ All links in changelog work (commit hashes, PR numbers)
- ✅ Changelog sections correctly categorized (Added/Fixed/etc)
- ✅ Breaking changes marked with ⚠️ if present
- ✅ No errors or warnings during execution
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
python-project-init
Initialize complete Python project with comprehensive documentation, development environment, and tooling. Use when creating a new Python project from scratch.
fastapi-sweetener
Add FastAPI server capabilities to an existing Python project with uvicorn, OpenAPI docs, and configuration management
macos-launchd-service
Set up macOS launchd service for auto-starting Python applications
git-workflow-hooks
Install git hooks to prevent common workflow mistakes
edit-article
Edit and improve articles by restructuring sections, improving clarity, and tightening prose. Use when user wants to edit, revise, or improve an article draft.
handoff
Compact the current conversation into a handoff document for another agent to pick up.
Didn't find tool you were looking for?