关于 ship
ship 是一项技能,可将已完成的分支经过检查、更新变更日志并提交为拉取请求。它为维护者提供与版本发布相关的步骤,并在合并前停止。
主要功能
- 01检查仓库与分支前提
- 02评估发布影响和版本
- 03按版本更新变更日志
- 04按范围刷新文档
- 05推送前运行快速检查
- 06创建或更新拉取请求
适用场景
- 01为已完成的分支准备评审
- 02记录合并对应的发布说明
- 03提交 PR 前核对仓库步骤
- 04刷新现有分支的拉取请求
MCP 相关
当 `gh` CLI 不可用时,技能要求使用等效的 GitHub MCP 操作访问仓库并处理拉取请求。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jwh3times/ skills --skill ship安装后在 Claude Code 中输入/ship确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
settings/human-follow-up.mdissue-tracker.mdREADME.mdreleases.mdship.mdworkspace.md详细介绍
Ship
将当前分支从“代码已完成”推进到“PR 已创建且可通过 CI”,并在变更日志中注明此次合并实际会生成的版本。
开始时宣布:“我正在使用 ship 技能为此分支创建 PR。”
在 bash(Windows 上使用 Git Bash)中运行 shell 片段。如果 gh 不可用,请使用等效的 GitHub MCP 操作:CLI 缺失会改变操作界面,不会改变步骤或完成标准。
ship 在“PR 已创建”时停止。它绝不会合并、推送到 main、强制推送,也不会运行完整测试套件:这些由 CI 负责,在此处运行会拖慢此技能。
0. 设置
此技能会读取 docs/agents/workspace.md、docs/agents/issue-tracker.md、docs/agents/human-follow-up.md、docs/agents/releases.md 和 docs/agents/ship.md。按照 .agents/skills/ship/settings/README.md 的说明读取这些文件,并补全缺失的文件;定义文件位于其旁边。此处解析每个 discover: 值一次,并在下文重复使用。
使用 Scheme: none 时,跳过所有标记为 (release) 的步骤。
1. 前置条件——任一条件不满足时停止
-
当前分支不是
main。 必须在分支上工作。若当前位于main,停止并提出创建一个按Issue link分支命名模式命名的分支(若其值为none,则使用<type>/<slug>)。 -
工作区干净。
git status --porcelain除步骤 0 刚写入且将纳入本次运行提交的设置文件外,不应输出任何内容。否则停止并询问是否提交现有内容;绝不擅自提交。得到答复后,从此步骤重新开始。之后暂存的每项更改都必须由本次运行产生。 -
可访问 GitHub。
gh auth status成功,或 GitHub MCP 能访问此仓库。 -
配套工作区状态正常。 如果已设置
Private companion且其检出目录存在,则运行Workspace status command:配套工作区必须干净,且与其上游既不超前也不落后。检出目录不存在也是有效状态;需报告。 -
与
main同步。git fetch -q --tags origin git merge-base --is-ancestor origin/main HEAD && echo current || echo behind如果当前分支落后,提出现在运行
git merge origin/main,并与用户一起解决冲突。绝不变基已推送的分支。
始终使用 origin/main,绝不使用本地 main:过时的本地 main 会悄然扩大差异范围,把已合并的工作也纳入其中;而只获取了当前分支的克隆则没有本地 main。
2. 评估发布影响(release)
阅读整个分支,而不只是最新提交:
base="$(git merge-base origin/main HEAD)"
git diff --stat "$base"..HEAD
git log --format='%s%n%b' "$base"..HEAD
git diff "$base"..HEAD
stat 和日志只是索引。请先阅读差异,以及受其影响的测试和文档,再作出判断。按对本项目的影响,选择其中最高级别:
- Major — 现有的公开行为、API、URL、持久化数据格式、配置、部署契约或既定用户工作流变得不兼容,用户必须迁移。对于
0.y系列,不兼容变更属于 minor,除非该分支有意发布1.0.0。 - Minor — 向后兼容的功能或行为的实质性扩展,或有意进行的平台或工具链代际变更,从而提高受支持的基线。
- Build — 修复(包括安全修复)、依赖维护、重构、测试、文档、工具:不增加功能,也不提高基线。
判断本项目的契约,而不是依赖项的版本号。发布元数据和生成的镜像不能作为依据。对于 Scheme: per-merge,Unreleased 的累计内容也计入评估。用户要求的级别仍需评估;如果评估结果不同,请同时说明两者并询问用户。
记录 Release level: <major|minor|build> — <one-sentence rationale>。步骤 9 和 10 使用这一整行内容。
应用该级别。 基线取最新 v* 标签(忽略 Skipped tags Ignore 模式)与 origin/main 上的 Version file(git show "origin/main:<file>")两者中的较高版本。
- Build —
Version file保持为origin/main上的值。如果该分支在没有发布意图的情况下修改了它,请恢复。 - Minor — 目标系列为基线版本的
<major>.<minor + 1>.0。 - Major — 目标系列为
<major + 1>.0.0。
对于 major 或 minor,请先遵循 Ask before bumping,然后使用确切的目标版本运行 Bump command,这样重新运行时会落在相同的值,而不会再递增一次。检查版本文件及其旁边的任何 lockfile 是否一致。如果分支已有该目标版本,则保留;如果分支已有不同系列的版本,则表示这是有意递增的:替换前先询问。
3. 计算目标版本(release)
-
per-merge— 首先确认origin/main上最新的合并已打标签:git tag --points-at origin/main -l 'v*'输出为空,表示刚合并的提交尚未完成标签工作流;此时下一个版本会重复已占用的版本号。请等待工作流完成,再次获取标签并重新检查;如果工作流完成后仍未打标签,请询问用户。然后运行
Next-version command。它会输出纯 SemVer,这是唯一可信来源:切勿自行计算 build 编号。检查是否已有其他打开的 PR 占用了该版本:
gh pr list --state open --json number,headRefName --jq '.[] | "\(.number) \(.headRefName)"' gh pr diff <number> -- CHANGELOG.md | grep '^+## \['如果有,请在报告中注明该 PR:后合并的 PR 会在下一次
ship时重新编号。 -
cut-on-line— build 没有确切的目标版本:下一个标签会分配该版本,无论是工作流为每次合并打标签,还是维护者为某个发行版打标签;而且,其他合并可能先落地。已确认的 major 或 minor,其目标版本是步骤 2 中的新系列。
4. 回填跳过的标签(发布)
仅适用于 Scheme: per-merge 和 Skipped tags: backfill。列出没有对应章节的已发布版本。
按原样运行;标签会打印为 v1.1.5,章节则是 1.1.5,所以人工直接比较原始列表时,
会误以为每个标签都缺失:
comm -23 \
<(git tag -l 'v*' | sed -nE 's/^v([0-9]+\.[0-9]+\.[0-9]+)$/\1/p' | sort) \
<(sed -nE 's/^## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/p' CHANGELOG.md | sort)
排除匹配 Ignore 模式的版本。剩下的每个版本都对应一次未写入章节的合并——可能是
依赖机器人提交的 PR,也可能是在 Web UI 中创建的 PR。按日期为每个版本在相应位置添加
章节,最新的放在最前面。将 version 设为对应版本后,读取其标签;标签指向合并提交,
因此合并分支是它的第二个父提交:
git log -1 --format='%cs' "v$version" # the section's date
git log --format='%s%n%b' "v$version^1..v$version^2" # what landed
git diff "v$version^1" "v$version" --stat # manifests and lockfiles it changed
git log -1 --format=%an "v$version^2" # dependabot[bot] for a bot PR
保持客观:注明软件包名称和版本,并将仅修改锁文件的变更称为传递性变更。只有在第二个
父提交的作者是机器人时,才将变更归因于机器人:合并提交的作者是点击 Merge 的人。如果
不存在 ^2(即采用了 squash 或 fast-forward),则读取标签自身的提交。
如果使用 Skipped tags: roll up,不要为每个跳过的标签单独创建章节;这些变更会并入下
一个新增的章节。
5. 更新文档
以 fix 模式运行 docs-updater skill,仅针对此分支的差异,不要进行全面审查——如果
运行环境支持子 Agent,则在子 Agent 中运行,并要求其遵循
.agents/skills/docs-updater/SKILL.md;否则由你自行遵循。子 Agent 无法询问用户,因此请
先在此处完成其设置,方法是遵循其步骤 0。
range="$(git merge-base origin/main HEAD)..HEAD"
告知它有哪些变更及原因、它是由 ship 在尚未合并的分支上调用的,以及此 skill 会写入
变更日志和 Version file。它会保留未提交的编辑,并返回步骤 6 中发现的人工操作,以及
合并后需要执行的任何 wiki 搜索(Follow-up wiki,或 wiki 中的 Doc map 条目)。
配套编辑应在其自身仓库中提交,绝不能在当前仓库中提交:如果 Companion commit policy
为 commit 或 ship,则提交并推送;如果为 ask,则展示差异并先询问。无论哪种情况,
都要运行其 Before committing 检查。
6. 人工后续操作
在 PR 开启前,针对此分支留下的每项必需人工操作(包括 docs-updater 返回的操作),运行
.agents/skills/ship/settings/human-follow-up.md 中的 ## Procedure。应合并整理,不要
重复创建;重新发布时,沿用已有记录。后续操作不能只记录在变更日志或 PR 正文中。其链接
应放在报告和私有记录中,绝不能放在公开 PR 中。
7. 编写变更日志(发布)
记录用户可见的行为及其影响,并根据差异推导,而不是记录提交历史。使用文件已有的标题(Keep a Changelog:Added、Changed、Deprecated、Removed、Fixed、Security,以及文件中新增的任何标题),每个章节只出现一次,并保持原有语气。日期使用今天的日期,格式为 YYYY-MM-DD。
幂等。 列出此分支新增的章节:
git diff origin/main...HEAD -- CHANGELOG.md | grep '^+## \['
此处列出的章节是此分支之前一次 ship 所添加的:在原处重写,绝不再叠加一个。此分支添加到 Unreleased 的条目也要以同样方式重写。
per-merge— 在## [Unreleased]正下方插入## [<target>] - <date>,其中包含此分支的条目以及Unreleased中累积的所有内容;将Unreleased重置为Unreleased占位符。如果此分支的章节因另一项 PR 先合并而使用了较旧的版本号,请将该章节重新编号为目标版本。然后合并origin/main:他们的章节位于相同位置,因此解决CHANGELOG.md冲突时要保留两者,并让最新版本排在前面,然后回到第 3 步——他们的合并创建了一个标签,因此目标版本又变了。cut-on-line、build — 将条目添加到Unreleased下,替换其占位符。不添加带日期的章节,也不编辑Version file。cut-on-line、已确认的 major 或 minor — 将条目添加到Unreleased下,把其中的全部内容移入## [<target>] - <date>,并恢复占位符。如果之前一次ship已经完成了版本切分,则在该章节内重写此分支的条目;绝不要让它们同时出现在切分线上下。
引用链接。 如果文件以链接页脚结尾,请遵循其中条目的格式,保持链接一致:[Unreleased] 从最新版本指向 HEAD,每个新增或重新编号的章节(包括补填的章节)都要添加自己的链接,并按从新到旧的顺序排列。
运行 Changelog check。如果不匹配,它会指出合并实际创建的版本:使用该版本重新执行此步骤。
8. 快速检查——任何一项失败都不得推送
按顺序在对应文件夹中运行每条 Fast checks。只有在没有相关路径发生变化时,才跳过条件检查(git diff --name-only origin/main...HEAD)。这些检查要在文档和变更日志编辑完成后运行,因为格式化工具和 linter 也会检查这些文件。
如果任何检查失败,请停止并报告;不要推送。提供该检查对应的修复命令。修复命令写入文件后,从头重新运行整个检查列表:基于未格式化源文件生成的镜像,在源文件格式化后会再次过时。无法在此处运行的检查(缺少服务、缺少密钥)要在报告中注明,留待 CI 运行。
然后运行 docs/agents/ship.md 中的 Hook: before PR。将其发现的问题写入报告,并在说明要求时写入 PR 正文。
9. 提交、推送并创建或更新 PR
仅当 git status --porcelain 显示有更改时才提交;再次发布但没有新内容时,不要创建空提交。由于步骤 1 已确认工作树干净,因此暂存所有内容是安全的,而且配套仓库已被忽略,绝不会被暂存:
git add -A
git commit -m "<Release commit message, with the real version>"
git push -u origin "$(git branch --show-current)"
推送被拒绝意味着远程仓库有此检出版本没有的提交:停止并报告。绝不要强制推送。
gh pr list --head "$(git branch --show-current)" --state open --json number,url
- 没有 PR →
gh pr create --base main。 - 已有 PR → 使用
gh pr edit <number>刷新 PR 正文。绝不要再创建第二个 PR。
将正文写入临时文件,并传入 --body-file。如果仓库有 PR 模板(.github/pull_request_template.md 或 PULL_REQUEST_TEMPLATE.md),请根据变更日志条目和差异填写模板。加入步骤 2 中的 Release level: 行;使用 per-merge 时,还要写明此次合并将生成的版本。
议题链接,仅当设置不是 none 时适用。正文以链接行开头,放在模板预留的位置:
- 根据分支的 Branch pattern 获取
N,并读取议题:gh issue view <N> --json title,state,labels,body。 - 如果此 PR 会完成议题,使用结束行(
Closes #N);如果它是一个保持开放的规范中的一部分,则使用切片行(Part of #N)。每个已完成的议题都要有自己的关键词:GitHub 只会链接Closes #1 and #2中的第一个议题。 - 分支没有编号时:如果用户豁免了此流程,则使用 Waiver line;若类型为 Exempt,则不添加链接行。
- 如果分支编号与议题不一致,或无法确定是完成议题还是仅为其中一部分,创建 PR 前先询问。刷新正文时保留其链接行。仅在标题中写编号不会产生链接。
如果 PR 正文没有链接行且类型并非豁免,或已设置 Board 但关联议题不在该看板上,请发出警告,但不要阻止流程。Private issue repository 中的议题绝不能在公开 PR 正文、提交消息或公开议题中提及;合并后手动关闭。
此步骤写入的任何公开内容都不得包含私有仓库名称、链接、看板编号或配套仓库详情。
10. 报告
- PR URL 和分支。
Release level:行及其依据;此次合并将生成的版本(per-merge)、新版本线(cut-on-line切分),或说明构建版本将由下一个标签指定。指出声称使用相同版本的其他 PR,并说明再次发布时该章节会重新编号。- 文档刷新所做的更改、写入的变更日志条目,以及回填的任何章节。如果 docs-updater 需要在合并后搜索 wiki,请说明:下一次
end-session会执行此操作。 - 人工跟进事项:已验证的议题和 wiki 链接、未发布的内容及其原因,或说明已无待办事项。
- 快速检查结果、留给 CI 的检查、
Hook: before PR的发现,以及议题链接警告。 - 配套仓库:未配置,或已同步且状态干净,并说明是否创建了配套仓库提交。绝不要将尚未合并的 PR 描述为已在配套仓库发布。
- 步骤 0 中写入的设置文件。
明确说明:完整测试套件会在 CI 中运行,而非在此处运行;分支仅经过快速检查验证。