How this skill is triggered — by the user, by Claude, or both
Slash command
/plinth:releaseratorThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Automate the release process with semantic versioning, Keep A Changelog format, and GitHub release creation.
Automate the release process with semantic versioning, Keep A Changelog format, and GitHub release creation.
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:
.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])Store:
VERSION_FILE = path to the detected version fileOptional check: Verify docs/CONTEXT.md is recent (< 24 hours old)
/session-wrapup firstTask: 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:
Store:
CURRENT_VERSION = version from VERSION_FILELAST_TAG = latest git tag (or "NO_TAGS")IS_FIRST_RELEASE = true if no tags existTask: 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:
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},
...
]
Task: Analyze commits to determine MAJOR.MINOR.PATCH bump
Rules (Conventional Commits):
MAJOR bump if ANY commit has:
MINOR bump if (and no MAJOR):
feat: commits existPATCH bump if (and no MAJOR/MINOR):
fix: or perf: commits existNo 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 versionTask: 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: [email protected]: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
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:
## [Unreleased] exists, insert after it[VERSION]: https://github.com/OWNER/REPO/releases/tag/vVERSION[Unreleased]: https://github.com/OWNER/REPO/compare/vVERSION...HEADIf CHANGELOG.md doesn't exist:
skills/releaserator/templates/CHANGELOG.md{{VERSION}} → NEW_VERSION{{DATE}} → today's date{{REPO}} → owner/repo from git remote{{ADDED}}, {{CHANGED}}, etc. → from CHANGELOG_ENTRYUse Write tool to create/update CHANGELOG.md
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.
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
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:
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.
Task: Create GitHub release using gh CLI
Prepare release notes file (temporary file):
skills/releaserator/templates/release-notes.md{{VERSION}} → NEW_VERSION{{CHANGELOG_ENTRY}} → the changelog entry content{{REPO}} → owner/repoCreate 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
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
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: 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
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
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
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
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
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
Current implementation: GitHub-only
GitHub-specific code is isolated in:
skills/releaserator/platforms/github.mdPlatform interface (for future expansion):
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:
skills/releaserator/platforms/gitlab.mdBenefits:
Cause: Version bump calculation matched an existing tag
Solution: This shouldn't happen if logic is correct. If it does:
Cause: Commit messages don't follow Conventional Commits format
Solution:
Cause: User doesn't have push access to remote
Solution:
gh auth login or setting up SSH keysCause: gh CLI not authenticated
Solution:
❌ GitHub CLI authentication failed.
Run: gh auth login
Then try creating the release again.
Cause: Git remote URL in unexpected format
Solution:
[email protected]:owner/repo.githttps://github.com/owner/repo.githttps://github.com/owner/repoAfter running releaserator, verify:
npx claudepluginhub pborenstein/plinthGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Synthesizes the current conversation into a structured spec (PRD) and publishes it to the project issue tracker with a ready-for-agent label, without interviewing the user.