# Release New Version

> 手动触发的发版流程：更新版本文件 → 提交 → 生成 release notes → 打 annotated tag → push。

- Skill: `maguowei/release-new-version` (Agent Skill)
- Install (CLI): `npx skillmds@latest add maguowei/release-new-version`
- Raw SKILL.md: https://api.skillmd.com/api/skills/maguowei/release-new-version/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: maguowei (https://skillmd.com/u/maguowei)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/maguowei/release-new-version

---


# Release Skill

给定版本号，自动完成完整发布流程：更新版本文件 → 提交 → 生成 release notes → 打 annotated git tag（附带完整变更日志）→ push。

## 工作流

接收版本号（如 `0.17.0` 或 `v0.17.0`），可选接收对比基准版本（如 `from v0.15.0`、`base=v0.15.0`、`对比 v0.15.0`）。若未提供基准，自动取前一个 SemVer tag。按顺序执行以下步骤：

### 步骤 0：前置条件——切换到 main 分支

发布必须在 `main` 分支上执行（版本 bump 提交与 tag 都落在 main），不要在 `dev` 或功能分支上发版。

```bash
git fetch origin --tags
git rev-parse --abbrev-ref HEAD          # 当前分支
git status --short                        # 工作区是否干净
git rev-list --count main..origin/main    # 本地 main 是否落后远端
```

处理规则：

- **工作区必须干净**（`git status --short` 为空）；有无关改动先提示用户处理，不要裹挟进发布提交。
- 切到 main 并与远端对齐：`git checkout main && git merge --ff-only origin/main`。
- **确认 main 已包含本次要发布的内容**：发布内容通常通过 PR 合入 main。若内容仍在 `dev`/功能分支未合入（`git rev-list --count main..<分支>` > 0），先提示用户走 PR 或 fast-forward 合并，**不要在 main 上直接拉取未评审代码**。
- 若已在 `dev`/功能分支误建了 bump 提交、且其父提交正是 main 当前位置，可 `git checkout main && git merge --ff-only <分支>` 把 bump 并入 main，保持两分支一致（避免分叉）。

**⏸️ 用户确认点**（当前不在 main、main 落后远端、或发布内容尚未合入 main 时）：展示上述分支状态，说明将如何对齐，用户确认后再切换/合并。

### 步骤 1：确定并规范化版本号

**1a. 解析用户输入**

- 用户提供了版本号（如 `0.17.0` 或 `v0.17.0`）→ 去掉前缀 `v`，使用纯 semver 格式。
- 用户未提供版本号 → 进入自动推断。

**1b. 自动推断版本号（当用户未提供时）**

```bash
# 同步远程 tags，确保基于最新数据推断
git fetch --tags

# 取最新 tag
LATEST=$(git tag --sort=-v:refname | head -1)
# 若无历史 tag，提示用户手动输入版本号
```

推断规则：minor + 1，patch 重置为 0。例如：
- `v0.16.0` → `0.17.0`
- `v1.0.0` → `1.1.0`
- `v1.2.3` → `1.3.0`

若无历史 tag，提示用户手动输入版本号。

### 步骤 2：更新版本文件

**⏸️ 用户确认点**：展示推断或用户指定的版本号，提示用户确认。用户确认后再执行。

用 Edit 工具依次更新以下三个文件：

1. **`src-tauri/tauri.conf.json`** — 顶层 `version` 字段
2. **`package.json`** — 顶层 `version` 字段
3. **`src-tauri/Cargo.toml`** — `[package]` 段的第一个 `version = "..."` 行

### 步骤 3：同步 Cargo.lock

执行以下命令更新锁文件：

```bash
cargo update --manifest-path src-tauri/Cargo.toml --package code-manager
```

如命令失败（包名不匹配等），跳过并记录。

### 步骤 4：提交版本变更

**⏸️ 用户确认点**：展示将要提交的文件列表和 commit message，提示用户确认。用户确认后再执行。

仅暂存版本相关文件，提交：

```bash
git add src-tauri/tauri.conf.json package.json src-tauri/Cargo.toml src-tauri/Cargo.lock
git commit -m "chore(release): bump version to {VERSION}"
```

### 步骤 5：确定对比基准 tag

确定用于生成 release notes 的基准版本，规则如下：

1. **用户显式指定优先**：若用户消息中包含基准版本（如 `from v0.15.0`、`base=v0.15.0`、`对比 v0.15.0`），将其规范化为 `vX.Y.Z` 格式后直接使用。
2. **自动推断**：否则执行以下命令获取上一个 tag：
   ```bash
   git tag --sort=-v:refname | grep -v "^v{VERSION}$" | head -1
   ```
3. **首次发布**：若仓库无任何历史 tag，将基准设为 `INITIAL`，release notes 标题写 "Initial release"。

校验基准 tag 是否真实存在（`git rev-parse {BASE_TAG}` 不报错）；不存在则中止并询问用户。

**⏸️ 用户确认点**：展示确定的版本号和对比基准 tag，提示用户确认。用户确认后再继续。

### 步骤 6：生成 release notes 并写入临时文件

执行以下流程生成结构化变更日志：

#### 6.1 收集 commits

```bash
git log --pretty=format:'%h %s' {BASE_TAG}..HEAD
```

若基准为 `INITIAL`，改用 `git log --pretty=format:'%h %s'`（取全部历史）。

若 `{BASE_TAG}..HEAD` 区间无 commit（少见，如重打 tag），release notes 为空，需提示用户确认是否继续。

#### 6.2 过滤噪音 commit

移除匹配 `chore(release): bump version to` 的行（版本升级 commit 本身）。

#### 6.3 按 Conventional Commits 类型归类

将剩余 commit 按以下前缀分组，无对应类型则省略该段：

| 类型前缀                  | 中文标题   |
|--------------------------|-----------|
| `feat`                   | 新功能     |
| `fix`                    | 缺陷修复   |
| `perf`                   | 性能优化   |
| `refactor`               | 重构       |
| `docs`                   | 文档       |
| `test`                   | 测试       |
| `build` / `ci`           | 构建与 CI  |
| `chore` / `style` / 其他 | 其他       |

同类型内按原始 commit 顺序排列（即时间从早到晚）。

#### 6.4 解析 GitHub compare URL

```bash
git remote get-url origin
```

同时兼容两种格式：
- `git@github.com:owner/repo.git` → `https://github.com/owner/repo/compare/{BASE_TAG}...v{VERSION}`
- `https://github.com/owner/repo.git` → 同上

无法解析则省略 compare 链接行。

#### 6.5 生成版本总结

在 commit 分类列表之前，生成一段版本总结。规则：

- 通读本版本所有 commit，识别最重要的功能（feat）和修复（fix）
- 用中文写 3-5 句话，面向终端用户，不暴露技术细节（不提 commit hash、不提变量名）
- 优先介绍新功能，其次重要修复，最后性能优化
- 若无显著变更（只有 docs/chore），写"本次为维护性更新，无功能变更"

#### 6.6 写入临时文件

将生成的 release notes 写入临时文件，模板如下（当基准为 INITIAL 时省略「对比基准」和「完整变更」行）：

```markdown
Release v{VERSION} ({YYYY-MM-DD})

对比基准：{BASE_TAG}

**版本总结**：{本版本重要变更的总结，3-5 句话}

## 新功能
- feat(xxx): ... ({short_sha})

## 缺陷修复
- fix(xxx): ... ({short_sha})

（其他类型按需出现，无该类型则省略整段）

完整变更：https://github.com/{owner}/{repo}/compare/{BASE_TAG}...v{VERSION}
```

**⏸️ 用户确认点**：展示生成的完整 release notes 内容（含版本总结），提示用户审核。用户确认后才写入临时文件。

使用 `mktemp` 创建临时文件，将上述内容写入其中，记录文件路径为 `$NOTES_FILE`。

### 步骤 7：打 annotated tag 并推送

**⏸️ 用户确认点**：提示用户即将执行不可逆操作（打 tag + push），展示即将执行的命令摘要，请求最终确认。用户确认后才执行 push。

在 main 上（见步骤 0）执行：

```bash
git tag -a v{VERSION} -F "$NOTES_FILE"
git push origin main
git push origin v{VERSION}
# 如需保持 dev 与 main 一致（bump 已并入 main），一并推送：git push origin dev
rm -f "$NOTES_FILE"
```

tag 必须用 annotated（`-a`）：lightweight tag 不保存 message，`git show v{VERSION}` 将看不到变更内容。

### 步骤 8：完成确认

- 汇报已完成的步骤，展示生成的 release notes 内容（供用户确认）。
- 提示用户：tag 推送后 GitHub Actions 的 release 工作流将自动触发，构建产物文件名将使用正确的版本号。
- 提示：GitHub Release 页面的默认 body 仍是 `release.yml` 中的占位文案「查看 Assets 下载并安装此版本」。若希望 Release 页直接展示此 notes，可后续修改 `release.yml` 的 `releaseBody` 改为从 tag message 读取。
- **提醒用户手动发布草稿 Release**：workflow 以 `releaseDraft: true` 创建的是草稿，不会发出 `published` 事件。构建完成后需用户在 GitHub Releases 页面 review 产物，点击 **Publish release** 发布为正式版。只有发布后才会触发：① 应用自更新（`latest.json` 对用户可见）；② `update-homebrew-cask.yml`（监听 `release: published`）自动 bump cask。停在草稿态则二者都不触发（cask 仅能靠 `workflow_dispatch` 手动补跑）。

## 注意事项

跨步骤的约定与环境依赖（单步内的规则已就近写在对应步骤里）：

- 版本文件中始终使用纯 semver（如 `0.17.0`），只有 git tag 才加 `v` 前缀（`v0.17.0`）。
- 不手动修改 `pnpm-lock.yaml`，不手动编辑 `Cargo.lock`（步骤 3 用 `cargo update` 同步）。
- **应用自更新依赖签名与发布**：release workflow 需配置 `TAURI_SIGNING_PRIVATE_KEY` 与 `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` 两个 GitHub secret，否则不会生成 `.sig` 与 `latest.json`，自更新不可用；`src-tauri/tauri.conf.json` 的 `plugins.updater.pubkey` 必须是对应公钥，不能留占位符。

