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:
!`git log $(git describe --tags --abbrev=0 2>/dev/null || echo "")..HEAD --oneline -10 2>/dev/null || git log --oneline -10`
Step 0: Pre-flight checks
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:
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:
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:
- Review the diff:
git diffandgit diff --cached - Stage changes selectively — prefer tracked files:
git add -u, then review and add any intentional new files individually. Avoidgit add -Aunless the user explicitly confirms. - Draft a conventional commit message from the changes — lead with a type prefix
(
feat:,fix:,docs:,chore:, etc.) and a concise summary - 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:
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:
Scan commits since the last tag for issue references:
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: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="" fiCreate a PR targeting main, including closing keywords in the body so GitHub closes the issues automatically on merge:
gh pr create --title "<type>: <summary>" --body "$(cat <<'EOF' ## Summary <bullet points from commits> Closes #N, Closes #N 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )"Omit the
Closeslines if no issues were identified.Enable auto-merge (squash preferred):
gh pr merge --auto --squashPoll until merged:
gh pr view --json state --jq '.state'Switch to main and pull:
git checkout main && git pull
Step 4: Determine next version
Analyse the commits since the last tag shown above. Apply semver rules:
- Any commit with
BREAKING CHANGE:in the footer, or a!suffix on the type → major bump - Any commit beginning with
feat:orfeat(scope):→ minor bump - All other commits (
fix:,docs:,chore:,refactor:, etc.) → patch bump
If no previous tag exists, start at v1.0.0.
Calculate <next-version> from the current last tag and the rule above.
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):
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:
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
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:
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:
git branch -d <feature-branch>
git push origin --delete <feature-branch>
Step 9: Confirm clean state
git status
git log --oneline -5
Report the GitHub release URL from gh release view v<next-version> --json url --jq '.url'.