Runs the full release workflow for the current project. Commits any uncommitted changes, pushes to remote, creates and merges a PR if on a feature branch, determines the next semver version from conventional commits, creates an annotated git tag and GitHub release with generated notes, cleans up merged branches, and returns to a clean main. Use when the user says promote, ship, release, commit and push, tag and release, or get back to main.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill promote --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Promote?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-promote-tons-of-skills-marketplace)More formats (shields.io, HTML) on the badges page.
---
name: promote
description: Runs the full release workflow for the current project. Commits any uncommitted changes, pushes to remote, creates and merges a PR if on a feature branch, determines the next semver version from conventional commits, creates an annotated git tag and GitHub release with generated notes, cleans up merged branches, and returns to a clean main. Use when the user says promote, ship, release, commit and push, tag and release, or get back to main.
allowed-tools: Bash
disable-model-invocation: true
---
# Promote Changes
Runs the full release workflow from current working state to a tagged GitHub release on main.
Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"`
Git status: !`git status --short 2>/dev/null || echo "not a git repo"`
Last tag: !`git describe --tags --abbrev=0 2>/dev/null || echo "none"`
Recent commits since last tag:
```text
!`git log $(git describe --tags --abbrev=0 2>/dev/null || echo "")..HEAD --oneline -10 2>/dev/null || git log --oneline -10`
```
Next version (calculated from commits above):
```text
!`(last=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"); IFS='.' read -r ma mi pa <<< "${last#v}"; log=$(git log "${last}..HEAD" --format="%s" 2>/dev/null); if echo "$log" | grep -qE "^[a-z]+(\([^)]+\))?!:"; then echo "v$((ma+1)).0.0"; elif echo "$log" | grep -qE "^feat"; then echo "v${ma}.$((mi+1)).0"; else echo "v${ma}.${mi}.$((pa+1))"; fi)`
```
## Step 0: Pre-flight checks
```bash
gh auth status 2>&1 || { echo "ERROR: gh is not authenticated. Run: gh auth login"; exit 1; }
```
Check for untracked `.env` files that could be accidentally staged:
```bash
git status --short | grep -E '^\?\? .*\.env' && echo "WARNING: untracked .env files detected — review before staging"
cat .gitignore 2>/dev/null | grep -q '\.env' || echo "WARNING: .gitignore does not exclude .env files"
```
If any `.env` files would be staged:
```bash
if [ -t 0 ]; then
# Interactive — ask for confirmation before continuing
read -p "WARNING: .env files detected. Continue? (y/N) " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
else
# Non-interactive — hard stop; too risky to proceed without human review
echo "ERROR: untracked .env files detected in non-interactive mode. Aborting."
exit 1
fi
```
## Step 1: Commit any uncommitted changes
If the git status above shows uncommitted or untracked changes:
1. Review the diff: `git diff` and `git diff --cached`
2. Stage changes selectively — prefer tracked files: `git add -u`, then review and add any
intentional new files individually. Avoid `git add -A` unless the user explicitly confirms.
3. Draft a conventional commit message from the changes — lead with a type prefix
(`feat:`, `fix:`, `docs:`, `chore:`, etc.) and a concise summary
4. Commit using a heredoc so multi-line messages format correctly
If there is nothing uncommitted, skip to Step 2.
## Step 2: Push
Push the current branch to remote:
```bash
git push -u origin HEAD
```
## Step 3: Merge to main (feature branch only)
Skip this step if already on `main` (or the repo's default branch).
If on a feature branch:
1. Scan commits since the last tag for issue references:
```bash
git log $(git describe --tags --abbrev=0 2>/dev/null || echo "")..HEAD --format="%B"
```
Look for `Closes #N`, `Fixes #N`, `Resolves #N` (case-insensitive). Collect all issue numbers
found. If none are found:
```bash
if [ -t 0 ]; then
# Interactive — ask the user
read -p "Any GitHub issues this resolves? (e.g. 12 15 — or enter to skip) " issues
else
# Non-interactive — skip silently
issues=""
fi
```
2. Create a PR targeting main, including closing keywords in the body so GitHub closes the issues
automatically on merge:
```bash
gh pr create --title "<type>: <summary>" --body "$(cat <<'EOF'
## Summary
<bullet points from commits>
Closes #N, Closes #N
🤖 Generated with [claude-workflow-skills:promote](https://github.com/ali5ter/claude-workflow-skills) on behalf of [Alister](https://github.com/ali5ter)
EOF
)"
```
Omit the `Closes` lines if no issues were identified.
3. Enable auto-merge (squash preferred):
```bash
gh pr merge --auto --squash
```
4. Poll until merged:
```bash
gh pr view --json state --jq '.state'
```
5. Switch to main and pull:
```bash
git checkout main && git pull
```
## Step 4: Confirm next version
The next version is pre-calculated in the context block above using these semver rules:
- Commit subject with `!:` (e.g. `feat!:`, `fix!:`) → **major** bump
- Commit subject beginning with `feat` → **minor** bump
- All other commits → **patch** bump
Confirm the calculated version is correct given the commit list. If no previous tag exists, use
`v1.0.0`. Override only if the calculated version is clearly wrong (e.g. the injection returned
an error or empty output).
## Step 5: Sync plugin manifest (if present)
If `.claude-plugin/plugin.json` exists, update its `version` field to `<next-version>` (no `v`
prefix — plugin manifests use bare semver like `1.2.3`):
```bash
node -e "
const fs = require('fs');
const f = '.claude-plugin/plugin.json';
const d = JSON.parse(fs.readFileSync(f, 'utf8'));
d.version = '<next-version>';
fs.writeFileSync(f, JSON.stringify(d, null, 2) + '\n');
"
```
Then commit and push:
```bash
git add .claude-plugin/plugin.json
git commit -m "chore: sync plugin.json version to v<next-version>"
git push
```
Skip this step entirely if `.claude-plugin/plugin.json` does not exist.
## Step 6: Tag and release
```bash
git tag -a v<next-version> -m "Release v<next-version>"
git push origin v<next-version>
gh release create v<next-version> \
--generate-notes \
--title "v<next-version>"
```
## Step 7: Close resolved issues (main branch only)
Skip this step if a PR was created in Step 3 — GitHub will close the issues automatically when
the PR merges via the `Closes #N` keywords in the PR body.
If the promotion was directly on `main` (no PR), close any identified issues now:
```bash
gh issue close <N> --comment "Resolved in $(gh release view v<next-version> --json url --jq '.url')"
```
## Step 8: Clean up merged feature branch
If a feature branch was merged in Step 3, delete it locally and remotely:
```bash
git branch -d <feature-branch>
git push origin --delete <feature-branch>
```
## Step 9: Confirm clean state
```bash
git status
git log --oneline -5
```
Report the GitHub release URL from `gh release view v<next-version> --json url --jq '.url'`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!