---
name: release-prep
description: cc-router 发版准备一条龙：升版本号 → 根据上一个 tag 以来的提交写 release-notes/<版本>/ 的中英日三份更新内容 → 校验 → 给用户审 → 本地提交「Bump version to X.Y.Z」，停在打 tag 之前。当用户说「准备发版」「发个版」「发 6.1.0」「写发版说明 / 更新内容 / release notes」「bump 版本」时必须走本 skill；即便用户只说了一个版本号，只要意图是发新版，就走本流程，不要只跑 pnpm version:set 了事。绝不打 tag、绝不推送。
---

# 发版准备（release-prep）

## 这个 skill 在做什么

cc-router 每个版本的更新内容写在仓库根目录 `release-notes/<版本>/`，编译期内嵌进 app（升级后自动弹窗展示，侧栏底部礼花可重开），CI 也用同一份文件生成 GitHub Release 正文。本 skill 把「升版本号 + 写三语更新内容 + 校验 + 本地提交」串起来，**停在打 tag 之前**。

完整设计见 `docs/superpowers/specs/2026-09-27-release-notes-popup-design.md`（本地文件，被 gitignore）。

## 铁律

- **绝不 `git tag`、绝不 `git push`、绝不 `gh release …`**。结束时只打印用户要执行的命令。
- 写之前先问、不猜：拿不准的条目（是否对用户可见、属于哪一节、要不要写）列出来问用户。
- 中文是准绳，英日从中文译出；三份结构逐条对应。
- 提交说明里**不提任何其他开源项目的名字**，**不写用户私有配置值**（示例一律中性化），**不写 OSS / 国内镜像源的运维细节**（bucket、ACL、密钥之类）。

## 流程

### Step 1：前置检查与版本号

```bash
git status --short          # 必须干净（允许的只有与本次发版无关、用户明确说不管的文件）
git rev-parse --abbrev-ref HEAD   # 应在 main；不在就问用户
git describe --tags --abbrev=0 --match 'v*'   # 上一个版本 tag，记为 PREV
node -p "require('./package.json').version"   # 当前版本号
```

- 用户给了版本号：确认它比 PREV 新（semver）。
- 没给：按 Step 2 的素材判断——有「新功能」→ 升 minor；只有修复 → 升 patch；有破坏性变化或用户说是大版本 → 升 major。**给出建议版本号请用户确认后再继续。**
- 预发布版本（含 `-`，如 `6.1.0-beta.1`）：先问用户要不要写更新内容。不写的话 CI 会放行，预发布说明本来也不会展示给正式版用户；这种情况跳过 Step 4–5。
- 顺便问一句**版本代号**（如 6.0.0 的 `Sketchbook`），可以不填。

### Step 2：收集素材

```bash
git log --no-merges --reverse --format='--- %h %an%n%B' PREV..HEAD
```

默认只读提交信息（标题 + 正文）——这个仓库的提交正文大多写得很完整，够用。**以下情况必须看改动本身**：标题含糊（如 `update providers`、`fix`、`wip`）、没有正文、或正文与标题对不上。先 `git show --stat <sha>` 看动了哪些文件，再只看相关文件的 diff（`git show <sha> -- <path>`），弄清它实际包含几处用户可感知的变化。

整理规则：

0. **排除只动 `release-notes/` 的提交**：它们是在补写 / 修订**已发布版本**的说明（例如 PREV 打 tag 之后才补齐的上一版说明），不是本版本的变化。
1. **按用户可感知的变化归类**，不按 conventional-commit 前缀机械归类：
   - `## 新功能`：用户能用到的新能力、新界面、新入口、行为上的新选项。
   - `## 修复`：**上一个版本里已经存在**的问题被修好。
   - `## 其他`：用户能感知但不算功能 / 修复的变化（打包、日志、依赖升级带来的可见影响、文档）。
2. **同一版本里新功能的修补提交并进那条新功能**，不单列进「修复」。判断方法：修的东西是在 PREV 之后才加的（同一 scope、提交时间在 PREV 之后）。
3. **默认不写**：纯重构、测试、CI 流程、内部文档、代码风格、只影响开发者的改动。例外：它带来了用户可见的影响（例如「安装目录不再附带 yaml 文件」）。
4. **合并同类、拆分混装**：同一功能的多个提交写成一条，子要点用二级列表；反过来，**一个提交里混了几处不相干的变化**（常见于标题含糊的提交，比如一个「修复 + 新端点 + 改名」的厂商更新），按变化拆到各自的分节里。
5. **外部贡献者的 PR**（提交作者不是维护者）：问用户要不要致谢，不要自己决定。PR 编号从合并提交的标题找（`Merge pull request #48 from …`），找不到再用只读命令 `gh pr list --state merged --search <sha>`。用户同意的话，写在要点名后面的编号里：
   - zh：`**新增 Requesty 服务商**（#48，感谢 @作者）：…`
   - en：`**Requesty provider** (#48, thanks @author): …`
   - ja：`**Requesty プロバイダーを追加**（#48、@author さんに感謝）：…`
6. 拿不准的条目汇总成一个列表问用户（一次问完，不要一条一条问）。

### Step 3：升版本号

```bash
pnpm version:set X.Y.Z
```

它同步 `package.json` / `tauri.conf.json` / 两个 `Cargo.toml` / `Cargo.lock`，并在 `release-notes/X.Y.Z/` 下生成 `meta.json`（日期是今天）和只有分节标题的 `zh.md` 骨架。有代号就把 `"codename": "…"` 加进 `meta.json`。

### Step 4：写三份说明

路径：`release-notes/X.Y.Z/zh.md`、`en.md`、`ja.md`（覆盖骨架）。

#### 允许的 Markdown 子集（严格，写错 release 构建会失败）

| 语法 | 用途 |
|---|---|
| 第一个 `## ` 之前的段落，**一行一段** | 摘要 |
| `## 标题` | 分节 |
| `- 文字` | 列表项（分节里只允许列表项，不允许段落） |
| 两个空格 + `- 文字` | 二级列表项，只允许一层 |
| `**粗体**` | 列表项开头的要点名 |
| `` `代码` `` | 字段名、命令、路径（三份要一致：要么都用，要么都不用） |
| `[文字](https://…)` | 链接，只允许 http(s) |

禁止：图片、HTML（`<` 后面紧跟字母）、`#` / `###` 标题、有序列表、表格、引用、`*` / `+` 列表符号、三层嵌套、空分节。

#### 分节标题（固定）

| zh | en | ja |
|---|---|---|
| `## 新功能` | `## Features` | `## 新機能` |
| `## 修复` | `## Fixes` | `## 修正` |
| `## 其他` | `## Other` | `## その他` |

某一节没有内容就**整节删掉**（空分节会被解析器拒绝）。

#### 文风（照 `release-notes/6.0.0/zh.md`）

- **摘要**：大版本 / 功能较多的版本写一段摘要，说清这一版的主题和「升级后默认行为是否变化、新功能默认开还是关」。纯修复的小版本可以不写摘要。
- **列表项**：`- **要点名**：说明。`——要点名是用户在界面上能认出来的功能名，说明写「做了什么、在哪里打开、有什么限制」。
- 不写指向弹窗本身的话（如「也就是你现在看到的这份说明」）——同一份正文也会原样出现在 GitHub Release 页面上。
- 写事实，不写营销话术（不用「全新」「极致」「强大」）；不写实现细节（函数名、文件名、crate 名），除非用户要用到它（命令行参数、环境变量、配置字段）。
- 需要重启 app 才生效、默认关闭、只在某个平台生效——都要写明。
- issue 编号写在要点名后面：`**自定义厂商自动获取模型列表**（#44）：…`（英日同样位置用半角 `(#44)`）。
- 界面上的名称与设置路径**从 locale 文件里取原词**：中文查 `src/i18n/locales/zh.json`，英文查 `en.json`，日文查 `ja.json`（例：「设置 → 安全与访问 → 终端界面」对应的 en / ja 路径）。找不到对应词再自己译。

参考片段（6.0.0 真实内容的节选）：

```markdown
这是一个大版本：桌面端换上与官网一致的「手绘速写本」外观；新增终端界面 cc-router-tui，不开窗口也能管理 cc-router。升级后默认行为不变，终端界面默认关闭。

## 新功能
- **终端界面 cc-router-tui**（默认关闭）：在终端里管理正在运行的 cc-router。打开方式：设置 → 安全与访问 → 终端界面，打开「启用终端界面」。
  - 共五个标签：总览、订阅、虚拟模型、实时路由、请求日志。
  - 只接受本机连接，不需要打开网页界面。

## 修复
- **数据库体积上限真正生效**：这个设置以前不起作用。现在超过上限（默认 500 MB）会从最旧的请求日志和事件开始删除；设为 0 表示关闭。
```

#### 英日翻译要求

- 结构与 zh.md **逐条对应**：摘要段数、列表项与二级项的数量和顺序、加粗位置、`#44` 编号、反引号用法都一致。
- 英文：简洁的产品说明语气，句首大写，要点名用 Sentence case。
- 日文：です・ます体，全角标点（`：`「」）；括号不要全半角混用；数字与英文单词两侧的空格按日文习惯处理（`3 言語`、`500 MB`）。

### Step 5：校验

```bash
node scripts/release-body.mjs --check vX.Y.Z          # CI 第一个 job 用的同一条检查
cd src-tauri && cargo test every_embedded_release_note_is_valid   # 与 app 同一个解析器
cd .. && node scripts/release-body.mjs vX.Y.Z         # 预览 GitHub Release 正文
```

守卫测试报错会给出文件和行号，按上面的子集改格式后重跑。三份都过了再进 Step 6。

### Step 6：给用户审

把三份全文贴给用户（不要只贴摘要），附上：
- Step 2 里**决定不写**的提交清单（一行一个，写原因），方便用户捞回。
- 仍未确认的问题（代号、致谢、拿不准的条目）。

用户改了中文 → 把改动同步到 en / ja（保持逐条对应）→ 重跑 Step 5。
用户直接改了英文或日文 → 只改那一份，不要反向改中文。
反复到用户明确说「可以 / 提交」为止。

### Step 7：本地提交并停下

```bash
git add -u && git add release-notes/X.Y.Z
git commit -m "Bump version to X.Y.Z"   # 按当前环境要求附上 Co-Authored-By 尾行
git status -sb                          # 报告领先 origin 几个提交
```

然后**停下**，告诉用户接下来由他本人执行：

```bash
git tag vX.Y.Z
```

```bash
git push && git push --tags
```

并提醒：推 tag 后 CI 会先检查 `zh.md`，构建通过后用这三份文件生成 Release 正文并自动发布。

## 常见坑

- **忘了删空分节**：只有「修复」的小版本，`## 新功能` / `## 其他` 骨架没删 → 解析器报「分节下没有列表项」，release 构建失败。
- **分节里写了段落**：分节里的每一行都必须是 `- ` 或两空格 `- `。想补充说明就写成二级列表项。
- **把同版本新功能的修补写进了「修复」**：用户会看到「修复了一个自己从没见过的功能」。
- **英日漏条 / 多条**：审之前逐节数一遍条目数。
- **行内出现 `<`**：比如 `<版本>`、`<token>` 会被当成 HTML 拒绝，改成「版本号」这类文字或放进反引号。
- **在 `pnpm tauri dev` 里关掉了弹窗**：dev 与生产共用数据目录，会把「已看过」写成新版本，你自己的生产版之后就不会再弹这一版。想在本机看效果，先备份 `~/Library/Application Support/com.cc-router.desktop/settings.json`。
