gh-release 是做什么的?
升级版本、变更日志、打标签、推送、观察。
下面的命令完整列出了 ~/.claude/skills/gh-release/scripts/ 路径。请完全按照所写内容执行——保留开头的 ~,不要展开它。 权限规则匹配的是字面的命令字符串,因此 /home/<你>/.claude/...(波浪号展开后)、python3 ~/...、相对路径以及先 cd 再执行,每个都是不同的字符串,每个都会重新触发权限提示。波浪号路径并不是“非正式书写的绝对路径”——对权限规则而言,它就是路径本身。
发布版本出错的原因有两个,并且都发生在标签创建之前:
- 重复做仓库已经自动化的版本升级。 手动编辑会改动仓库自身任务本应处理的文件,遗漏它已知的文件,并跳过它的门控。
- 创建的标签不触发任何操作。 它看起来就像 CI 队列很慢,二十分钟后你才会发现。
步骤 1 和步骤 3 正是为了解决这些问题。
0. 确定版本
读取标签(git tag --sort=-v:refname | head -5)来了解仓库的格式(v1.2.3 还是 1.2.3)以及当前版本。如果用户指定了版本,就使用它。否则,根据自上个标签以来的提交记录提议一个版本——破坏性变更为 major,feat: 为 minor,否则为 patch——并说明具体原因。
如果工作区有未提交的修改、HEAD 处于分离状态或分支落后于远程分支,则拒绝继续操作。发布未提交的工作会生成一个其他人无法重现的标签。
1. 使用仓库自身的发布机制
在手动做任何事情之前,始终先检查:
~/.claude/skills/gh-release/scripts/versiontool.py discover
它会报告项目 skill、mise 任务、npm 脚本、Make target、just recipe、scripts/-bin/ 目录下的条目以及工具配置(cargo-release、bumpversion、release-please、git-cliff)。优先级:
kind=project-skill —— 停止阅读本 skill,转而遵循那个 skill。这是仓库自身的过程,它知道一些本通用 skill 无法得知的信息。
任务或脚本(kind=mise-task|npm-script|make-target|just-recipe|script)
- 先阅读再运行,然后执行它。你需要知道它覆盖了哪些内容,避免重复或遗漏。典型任务会执行以下部分操作:版本升级、变更日志推进、门控、提交、打标签、推送。记下哪些已经做了,然后只做剩下的事情。
许多任务接受不带前缀的版本号,并自行添加前缀(例如 mise run release 0.13.0 会打上 v0.13.0 标签),所以不要在需要版本号的地方传递标签。如果任务支持 --dry-run,先使用它并向用户展示计划。
只有工具配置没有任务 —— 使用该工具自身的命令(cargo release <version>、bump-my-version bump ...),而不是手动编辑。
result=none —— 只有在这种情况下才退回到步骤 2。
2. 后备方案:找到所有存在版本号的地方
仅在步骤 1 没有找到任何内容时才执行。
~/.claude/skills/gh-release/scripts/versiontool.py locations <current-version>
它搜索的是字面上的版本号字符串,而不是单词“version”。在真实仓库上测试:搜索 version 键找到了 5 个文件,而搜索字面字符串找到了 8 个。遗漏的部分使用了键 "version-name"(GNOME 元数据)和 "Version"(Plasma)。任何遗漏都会导致发布的二进制文件报告错误的版本。
输出结果分类为:edit(修改它)、regenerate(锁文件——运行提示中的命令,切勿手动编辑,否则 --locked 会在 CI 中失败)、changelog(步骤 4)。
然后也检查上一个版本——那里有匹配项意味着上次发布时遗漏了一个文件,并且从那以后一直过时。在这次发布中修复它。
3. 预检标签
~/.claude/skills/gh-release/scripts/relwatch.py preflight <tag>
它会读取 .github/workflows/*.yml,提取所有 on.push.tags 模式,并告诉你你的标签是否匹配其中任何一个,以及该工作流是否看起来会创建一个 release。result=NO_MATCH 意味着推送标签什么也不会触发。
在提交之前运行此步骤,此时更改标签没有任何代价。
另外,肉眼确认工作流的 release 步骤使用的是你即将推送的标签。许多工作流并不会验证标签是否与代码中的版本号匹配,因此不匹配会产生一个绿色 CI 但报告错误版本的 release,静默且无害地出现。
4. 变更日志
如果步骤 1 的任务会自动推进变更日志,就让它做,然后跳到步骤 5。
Keep a Changelog 格式(存在 ## [Unreleased] 标题):推进它。
## [Unreleased]变为## [X.Y.Z] - YYYY-MM-DD- 在其上方添加一个新的空
## [Unreleased] - 更新底部的链接定义,包括
[Unreleased]比较链接的新基准
没有变更日志,或没有 Unreleased 部分:根据自上个标签以来的提交内容编写条目,按文件已有的分组方式分组。完全匹配现有的风格——如果条目是一行式的,就写一行式;如果是有加粗标题的散文风格,就写那种风格。
无论如何:描述的是对项目用户而言什么发生了变化,而不是你对仓库做了什么。
对每一条条目应用 unslop skill。 变更日志会被阅读多年,供人们决定是否升级。“各种改进和 bug 修复”比没有条目更糟糕;同样糟糕的是用更长的话重述提交主题的条目。文档特有的注意点在 docs-update/SKILL.md 步骤 5 中列出,这里同样适用:不要开场白、不要空洞的强调词、在答案明确时不要含糊其辞。
5. 在打标签前验证
~/.claude/skills/gh-release/scripts/versiontool.py verify <old-version> # 必须输出 result=clean
即使仓库任务已经执行了版本升级,也要运行此步骤。 六个月前编写的升级器并不知道上周有人添加的文件,这正是版本号在树的一个角落过时的原因。变更日志历史被忽略;只有可编辑的文件才计入。
然后运行项目门控(构建、测试、lint)。指向红色提交的标签必须被删除并重新创建,而删除已推送的标签对任何已经拉取它的人来说都很麻烦。
6. 提交、打标签、推送
跳过步骤 1 的任务已经做过的部分。如果它已经提交、打标签并推送了,直接跳到步骤 7。
否则:为发布创建一个提交,提交标题按 ~/.claude/CLAUDE.md 的要求以分支名作为前缀。版本升级和变更日志应该放在同一个提交中;它们属于同一个变更。
git commit -m "[<branch>] chore(release): <version>"
git tag -a <tag> -m "<tag>"
git push && git push origin <tag>
先推送提交,再推送标签。 先到达的标签可能会针对远程还没有的提交开始构建。
记下推送时间;它将限制观察的时长。
7. 监视直到 release 存在
~/.claude/skills/gh-release/scripts/relwatch.py watch <tag> --timeout 1800
| 结果 | 含义 |
|---|---|
released url=... | 完成 |
ci_failed run=... conclusion=... | 转到步骤 8 |
no_run | 没有触发任何操作。标签没有匹配任何模式 —— 运行 preflight |
still_running | 不是失败。再次观察 |
绿色的 CI 并不代表 release 已完成:发布步骤可能比构建慢,所以观察器会持续等待直到 Release 对象实际存在。
与 babysit-pr 相同的子 agent 规则:在单个 Bash 调用中使用 --timeout 540 的观察更便宜,适合内联;仅当等待跨越多次调用时才委托。不要在没有发布完成或遇到阻塞时停止。
8. 当 CI 在标签推送后失败
标签是错误的,所以收回它而不是修补它:
git push --delete origin <tag>
git tag -d <tag>
修复,然后从步骤 5 重新开始,使用相同的版本号。不要为了跳过失败而升级版本号:没有人发布过的版本不值得浪费,而且版本号空缺会诱使人们去寻找丢失的 release。
如果标签已经公开了一段时间,或者已经存在部分 release,请说明情况并让用户选择是删除它还是在其上推送一个补丁。
9. 报告
PR/比较链接、标签、release URL、变更内容以及每个已升级的文件。如果有任何留给用户处理的事情,请明确说明。