Idempotent, adaptive release process.By default, automatically run setup + publish。
By default, use changesets, unless the user explicitly chooses to keep semantic-release.
Step 0: Opening joke 🎭
Before starting the release, tell a programmer joke to lighten the mood:
Randomly choose one of the following jokes to tell:
-
Why do programmers always confuse Halloween and Christmas? Because Oct 31 = Dec 25
-
What is the number programmers hate most? 2.0—because it means a rewrite
-
"My code runs!" "Great, commit it." "Wait, let me first see why it runs..."
-
Product manager: This requirement is simple. Programmer: That sentence itself is very complex.
-
Why do programmers like dark mode? Because bugs are afraid of light
-
git commit -m "final version" → git commit -m "final version 2" → git commit -m "this time it's really the final version"
-
99 bugs in the code, 99 bugs~ fix one, compile it, 127 bugs in the code...
After telling the joke, continue executing the release process.
Parameters
无参数 → setup + publish(默认,自动模式)
setup → 仅检查/修复配置
publish → 仅执行发布
--keep-semantic-release → 保留现有 semantic-release 配置
patch|minor|major → 指定版本类型
Automatic mode behavior
Principle: execute automatically unless very uncertain
-
Auto commit: when there are uncommitted changes, automatically
git add -A && git commit- Commit message inferred from diff content (e.g. "fix: update xxx" or "feat: add xxx")
- Only ask when the changes are complex and the intent cannot be inferred
-
Auto version: default patch, unless the user explicitly specifies otherwise
- First release (no tag) →
0.1.0(not 1.0.0! 1.0+ requires explicit user specification) - Current version
0.x.y→ keep the0.xprefix, increment by patch/minor - Default → patch (the safest choice)
- User parameter
minor→ minor - User parameter
major→ major - User explicitly specifies
1.0.0ormajorand current ≥ 0.x → only then can it be upgraded to 1.0+ - Detected
BREAKINGchange → ask the user whether to use major
Version number philosophy: 0.x means "features are continuously evolving", 1.0 means "stable API commitment". Never jump to 1.0+ on your own without user guidance. If there is already an incorrect 1.0+ tag, propose a rewrite (delete the old tag/release, restart from 0.x).
- First release (no tag) →
-
Conditions for asking: only ask the user in the following cases
- The workspace has multiple unrelated changes
- The changes involve sensitive files (e.g. .env, secrets)
- The version type cannot be automatically inferred (e.g. refactor may be patch or minor)
-
Automatic branch handling: if on a feature branch
- Automatically commit the current changes
- Automatically push to remote
- Automatically switch to main and merge the feature branch
- Use
--no-ffto preserve branch history
-
Automatic Issue handling: detect related issues from branch name/commit
- Branch name format:
*/issue-<number>*or*/<number>-* - Commit format:
Closes #<number>/Fixes #<number>/Resolves #<number> - Automatically comment + close after successful publish
- Branch name format:
Step 1: Automatic project detection
类型: Tauri (src-tauri/) | Monorepo (pnpm-workspace.yaml) | Node (package.json) | Shell (无 package.json)
目标: npm | GitHub Release | 二进制
子类型:
- Obsidian 插件 → GitHub Release Only(tag 触发,无 npm)
- Vite/前端项目 (private: true) → GitHub Release + dist.zip
发布工具:
- 检测到 semantic-release → 询问是否迁移到 changesets(推荐)
- 检测到 changesets → 继续使用
- 未配置 → 自动配置 changesets
Shell project: Only needs workflow + tag + CHANGELOG.md
Vite/frontend project: GitHub Release + upload {project}-{version}.zip
All project types: CHANGELOG.md must be maintained; before publishing, automatically add the new version's change record at the top, and the workflow extracts release notes from it. Do not use generate_release_notes: true。
semantic-release migration detection
Detect whether semantic-release is used:
# 检查 package.json 是否有 semantic-release 相关配置
grep -q "semantic-release" package.json && echo "semantic-release detected"
# 检查 workflow 是否调用 semantic-release
grep -rq "semantic-release" .github/workflows/ && echo "workflow uses semantic-release"
If semantic-release is detected and --keep-semantic-release:
- Use AskUserQuestion to ask the user whether to migrate to changesets
- Recommend migration (changesets is more flexible and supports Monorepo)
- After the user agrees, perform the migration (see "Migration steps" below)
Steps to migrate to changesets
# 1. 初始化 changesets
pnpm add -D @changesets/cli
pnpm changeset init
# 2. 更新 .changeset/config.json
cat > .changeset/config.json << 'EOF'
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
EOF
# 3. 移除 semantic-release 配置
# - 从 package.json 删除 "release" 配置块
# - 从 devDependencies 删除 semantic-release 相关包
# 4. 更新 workflow(见下方 Workflow 模板)
Setup stage
Check and report
✓/✗ .github/workflows/release.yml
✓/✗ .github/workflows/release.yml 包含 `permissions: contents: write`
✓/✗ Repo workflow permissions (write)
✓/✗ [Node] package.json (packageManager, scripts)
✓/✗ [Node] package.json 包含 packageManager 字段
✓/✗ [Node] .changeset/config.json(推荐)
⚠️ [Node] semantic-release 检测(建议迁移到 changesets)
✓/✗ [npm] NPM_TOKEN secret
✓/✗ CHANGELOG.md 存在且格式正确(## x.y.z 格式)
✓/✗ [Tauri] Cargo.toml 版本同步
Auto-fix
Repo permissions:
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
gh api "repos/${REPO}/actions/permissions/workflow" -X PUT \
-f default_workflow_permissions=write -F can_approve_pull_request_reviews=true
packageManager field(if missing):
# 按 lockfile/现有字段检测,不要强行改成 pnpm
if node -e "process.exit(require('./package.json').packageManager ? 0 : 1)" 2>/dev/null; then
node -p "require('./package.json').packageManager"
elif [ -f bun.lock ] || [ -f bun.lockb ]; then
BUN_VERSION=$(bun --version)
# 在 package.json 中添加 "packageManager": "bun@${BUN_VERSION}"
elif [ -f pnpm-lock.yaml ]; then
PNPM_VERSION=$(pnpm --version)
# 在 package.json 中添加 "packageManager": "pnpm@${PNPM_VERSION}"
elif [ -f yarn.lock ]; then
YARN_VERSION=$(yarn --version)
# 在 package.json 中添加 "packageManager": "yarn@${YARN_VERSION}"
else
NPM_VERSION=$(npm --version)
# 在 package.json 中添加 "packageManager": "npm@${NPM_VERSION}"
fi
Shell project Workflow:
Template see references/general-release-playbooks.md. Shell projects must also support workflow_dispatch, and extract release notes from CHANGELOG.md .
Node project principles:
- Must support
workflow_dispatch - Tauri uses job chaining (GITHUB_TOKEN limitation)
- npm packages need
NPM_TOKEN - The workflow must choose bun/pnpm/yarn/npm according to
packageManager; do not hardcode pnpm - Bun projects use
oven-sh/setup-bun@v2、bun install --frozen-lockfile、bun run build - pnpm projects use
pnpm/action-setup@v4,Do not specify version(read the packageManager field) - Tauri macOS cross-compilation requires adding Rust targets:
aarch64-apple-darwin,x86_64-apple-darwin
CHANGELOG.md integration:
- When publishing, extract the corresponding version content from
CHANGELOG.mdas release notes - Fallback: Use GitHub auto-generation when CHANGELOG.md has no content
- Avoid
generate_release_notesbeing repeated in multiple jobs
Publish stage
Pre-checks
git status --porcelain # 不干净 → 自动提交(见自动模式)
git branch --show-current # 非 main/master → 自动合并(见分支处理)
git pull --rebase
# CHANGELOG.md 检查:不存在 → 自动创建(从 git log 生成历史记录)
# 发布前必须在 CHANGELOG.md 顶部添加新版本变更记录
Prevent drift in long workflows:
- Before creating the release commit and tag, must confirm that all diffs to be published in this round have been committed.
git tagthen recordRELEASE_COMMIT=$(git rev-parse HEAD); the workflow, release, and verification all correspond only to this commit.- If the user continues to modify the workspace while the workflow is running, after publishing is complete only do a
git statusreport; do not add new dirty changes to the already tagged version. - If these new changes must be included, bump a new version again; do not silently move an already published/announced tag.
Automatic branch merge
BRANCH=$(git branch --show-current)
MAIN_BRANCH="main" # 或 master,自动检测
# 如果不在 main 分支
if [ "$BRANCH" != "$MAIN_BRANCH" ]; then
# 1. 提交并推送当前分支
git add -A && git commit -m "<auto message>" || true
git push origin "$BRANCH"
# 2. 切换到 main 并拉取最新
git checkout "$MAIN_BRANCH"
git pull origin "$MAIN_BRANCH"
# 3. 合并 feature 分支(保留历史)
git merge "$BRANCH" --no-ff -m "Merge branch '$BRANCH' into $MAIN_BRANCH
<changeset description>
Closes #<issue_number>" # 如果检测到关联 issue
fi
Automatic issue detection and handling
# 从分支名提取 issue 号
BRANCH=$(git branch --show-current)
ISSUE_NUM=""
# 匹配模式:issue-123, 123-feature, feature/issue-123
if [[ "$BRANCH" =~ issue-([0-9]+) ]]; then
ISSUE_NUM="${BASH_REMATCH[1]}"
elif [[ "$BRANCH" =~ ^([0-9]+)- ]]; then
ISSUE_NUM="${BASH_REMATCH[1]}"
elif [[ "$BRANCH" =~ /([0-9]+)- ]]; then
ISSUE_NUM="${BASH_REMATCH[1]}"
fi
# 从 commit message 提取(作为补充)
if [ -z "$ISSUE_NUM" ]; then
COMMITS=$(git log "$MAIN_BRANCH"..HEAD --format=%s 2>/dev/null)
ISSUE_NUM=$(echo "$COMMITS" | grep -oE '(Closes|Fixes|Resolves) #[0-9]+' | head -1 | grep -oE '[0-9]+')
fi
# 验证 issue 存在且未关闭
if [ -n "$ISSUE_NUM" ]; then
STATE=$(gh issue view "$ISSUE_NUM" --json state -q '.state' 2>/dev/null || echo "")
if [ "$STATE" = "OPEN" ]; then
echo "检测到关联 Issue #$ISSUE_NUM"
else
ISSUE_NUM="" # 忽略已关闭的 issue
fi
fi
Automatically close issues after publishing
# 在 workflow 成功后执行
if [ -n "$ISSUE_NUM" ]; then
VERSION="v${VERSION}"
RELEASE_URL="https://github.com/${REPO}/releases/tag/${VERSION}"
# 添加评论
gh issue comment "$ISSUE_NUM" --body "已在 ${VERSION} 中修复。
Release: ${RELEASE_URL}"
# 关闭 issue
gh issue close "$ISSUE_NUM" --reason completed
echo "✓ Issue #$ISSUE_NUM 已关闭"
fi
Auto-commit logic
# 1. 检查变更
CHANGES=$(git status --porcelain)
if [ -z "$CHANGES" ]; then exit; fi
# 2. 分析变更推断提交类型
# - 修改现有文件 → fix
# - 添加新文件 → feat
# - 删除文件 → chore
# - 配置文件 → chore
# 3. 推断提交描述(从文件名/diff 内容)
# - 单文件:直接用文件名
# - 多文件同类:归纳共同点
# - 复杂变更:才询问用户
# 4. 自动提交
git add -A && git commit -m "${TYPE}: ${DESC}"
Automatic version inference
# 版本类型优先级:
# 1. 用户显式参数 (patch/minor/major/具体版本号)
# 2. 检测到 BREAKING 变更 → 询问用户
# 3. 无 tag(首次发布)→ v0.1.0(不是 v1.0.0!)
# 4. 当前 0.x → 保持 0.x,按 patch/minor 递增
# 5. 默认 patch(最安全)
#
# ⚠️ 1.0+ 需要用户明确指定,绝不自行跳到 1.0+
LATEST=$(git tag -l 'v*' | sort -V | tail -1)
if [ -z "$LATEST" ]; then
NEXT="v0.1.0" # 首次发布从 0.1.0 开始
else
# 解析当前版本并递增
# 0.1.0 + patch → 0.1.1
# 0.1.1 + minor → 0.2.0
# 只有用户显式指定 major 或 1.0.0 才跳到 1.x
fi
# 检查是否有 BREAKING 变更
LAST_MSG=$(git log -1 --format=%s)
if [[ "$LAST_MSG" =~ ^BREAKING ]] || [[ "$LAST_MSG" =~ ^major: ]]; then
echo "检测到可能的 BREAKING CHANGE: $LAST_MSG"
echo "是否使用 major 版本?(y/N)"
# 使用 AskUserQuestion 工具询问
fi
VERSION_TYPE="${USER_SPECIFIED_VERSION:-patch}"
Shell projects
# 获取最新 tag 并递增
LATEST=$(git tag -l 'v*' | sort -V | tail -1)
NEXT="v0.1.0" # 无 tag 时默认 0.1.0(不是 1.0.0!)
# 自动递增 patch(0.1.0 → 0.1.1),minor 需用户指定
git tag "$NEXT" && git push --tags
# workflow 自动触发
Node projects
Automatic mode(default):
- Version: automatically inferred from commit type (see automatic version inference)
- Method: default
local(fastest)
Ask mode(only when inference is not possible):
版本: [patch] / [minor] / [major]
方式: [local] 快速本地 | [ci] 通过 PR | [ci-auto] PR+自动合并
Local path (Tauri):
# 1. 创建 changeset(如缺失)
cat > .changeset/<name>.md << 'EOF'
---
"<package>": patch
---
<description>
EOF
# 2. Bump version
pnpm changeset version # 或项目自定义的 changeset:version 脚本
git add . && git commit -m "chore: release v${VERSION}"
git push
# 3. 创建并推送 tag(workflow checkout 需要 tag 存在)
git tag v${VERSION} && git push origin v${VERSION}
# 4. 触发构建
gh workflow run release.yml -f tag=v${VERSION}
sleep 3
RUN_ID=$(gh run list -w release.yml -L 1 --json databaseId -q '.[0].databaseId')
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
echo "Workflow: https://github.com/$REPO/actions/runs/$RUN_ID"
# 5. **必须**等待 workflow 完成(指数退避轮询)
gh_view_json() {
local run_id="$1"
local json="$2"
local query="$3"
local attempt=1
while [ "$attempt" -le 5 ]; do
if gh run view "$run_id" --json "$json" -q "$query"; then
return 0
fi
sleep $((attempt * 2))
attempt=$((attempt + 1))
done
return 1
}
DELAY=5
MAX_DELAY=60
while true; do
STATUS=$(gh_view_json "$RUN_ID" status '.status') || {
echo "GitHub API 暂时不可用,下一轮继续重试..."
sleep "$DELAY"
continue
}
if [ "$STATUS" = "completed" ]; then
CONCLUSION=$(gh_view_json "$RUN_ID" conclusion '.conclusion' || echo "unknown")
echo "Workflow $CONCLUSION"
break
fi
echo "Status: $STATUS, waiting ${DELAY}s..."
sleep $DELAY
DELAY=$((DELAY * 2 > MAX_DELAY ? MAX_DELAY : DELAY * 2))
done
Local path (pure Node):
pnpm build && pnpm changeset publish
git push --follow-tags
NOTES=$(awk -v ver="${VERSION}" '/^## / { if (found) exit; if ($2 == ver) { found=1; next } } found { print }' CHANGELOG.md)
gh release create "v${VERSION}" --notes "${NOTES:-Release v${VERSION}}" --latest
CI path:
git add -A && git commit -m "chore: add changeset" && git push
# 等待 Version Packages PR
gh pr merge ... --squash --delete-branch # ci-auto
References
- Tauri/macOS signing/notarization/Windows fallback:
references/tauri-release-workflow.md - Shell, Vite, monorepo templates, workflow monitoring, README/Vercel audits, issue automation, and failure recovery:
references/general-release-playbooks.md
When a referenced template conflicts with this SKILL.md, follow the stricter rule: maintain CHANGELOG.md, preserve package manager choice, do not print secrets, wait for workflow completion with retry, and verify final release assets before reporting success.