关于 ship
ship 是发布工作流技能,会按仓库类型检查条件,再推送变更或处理待合并的 PR、MR。它先展示检查清单,并在执行远程操作前等待明确批准。
主要功能
- 01按仓库类型检查发布条件
- 02支持 push、release、PR、MR 和 merge
- 03运行健康检查及适用的远程 UAT
- 04准备发布说明或 PR、MR 文本
- 05远程操作前等待批准
适用场景
- 01推送变更前检查准备情况
- 02准备发布或创建 PR、MR
- 03合并前测试待处理请求
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rheinmir/ setup --skill ship安装后在 Claude Code 中输入/ship确认已加载。
详细介绍
技能:ship
围绕远程端完成的工作流。运行条件检查清单,在任何产生副作用的步骤(push/tag/PR/MR/merge)之前停止并等待批准。五种级别——4 种推送(outbound)+ 1 种拉取(inbound):push(完成所有步骤,不打 tag)· release(push + tag
vX.Y.Z+ 发布说明)· pr(推送分支 + 通过gh创建 GitHub Pull Request)· mr(推送分支 + 通过glab创建 GitLab Merge Request)· merge(列出待合并的 PR/MR → 拉取分支 → gate+测试 → 测试通过则合并)。
WHAT
目的和上下文
- 目的: 仅在条件检查清单全部通过后,将更改推送到远程端(push/release/PR/MR),或将待合并的 PR/MR 拉取并合入(merge);任何产生副作用的步骤之前都要停止并等待批准。
- 触发条件(何时使用):
- 用户提到“release” / “push” / “ship” / “发布” / “准备 push” / “满足 push 条件了吗” / “创建 PR” / “创建 MR”。
- 在将代码推送到远程端、切出一个 release,或创建 PR/MR 以供审查后合并之前。
- 想要审查后合并仓库中已打开的 PR/MR:“拉取 PR 并合并” / “测试通过就合并 PR” / “ship merge”。
- 非目标: 不自动修复 gate 错误(由
/medic+ 修复技能负责),不自动关闭 issue,不自动安装或配置身份验证gh/glab。
心智模型
先执行 repo_role (framework · module · downstream · foreign) → 正确仓库类型的 gate 表 → ,然后才进入通用流程:
级别 (push · release · pr · mr · merge) → 健康 gate (medic --ci + ci-local) → 远程 UAT(仅具备能力的 release/pr/mr)→ git 干净 → selftest → [版本 + 补丁说明] → 检查清单 → 停止等待批准 → 产生副作用 → [UAT 第 2 阶段]。不同级别的区别在于最后一步,而非检查清单。
输入和输出约定
| 字段 | 是否必需? | 含义 | |
|---|---|---|---|
| 输入 | repo_role | 自动读取 | python3 harness/scripts/repo_role.py . --json(客户端:~/.claude/harness/harness/scripts/repo_role.py)——.overstack.yaml 中声明的标签;若缺失,则根据证据推断并询问用户一次,然后使用 --set;本次手动指定:ship <级别> --as <role> |
| 输入 | 级别 | 否(默认为 push) | push · release · pr · mr · merge——用户描述范围即可,无需记住标志 |
| 输入 | 用户批准 | 是,任何产生副作用的步骤之前 | 对 push/tag/release/PR/MR/merge/canary 明确回复“yes” |
| 输出 | 检查清单 + 建议命令 | 是 | 各步骤 gate 的结果、UAT 第 1 阶段或跳过的原因、commit message、最终命令 |
| 输出 | 文本 | 视级别而定 | release → RELEASE-vX.Y.Z.md;pr/mr → PR/MR 正文;push → 仅 commit message;merge → 测试报告 + 决策 |
| 输出 | 产生副作用 | 仅在批准后 | 已执行的 push / tag / release / PR / MR / merge |
规则与能力
- RULE-01 (MUST):
medic --ci未通过时不得推送——除非用户明确覆盖;即使覆盖,也必须在补丁说明中写明理由。 - RULE-02 (MUST):补丁说明不得夸大——如果有未完成的部分,必须列出已知限制。
- RULE-03 (MUST):**级别只在最后一步不同,清单不变:**所有级别都必须通过健康门禁;仅最终产物不同(tag / release / PR / MR / merge)。用户说“pr”/“mr”时不要创建 tag;用户只说“push”时不要开 PR。
- RULE-04 (MUST):**
medic --ci通过并不能证明远程路径可用——这属于/fdk-uat的职责。**具有新能力的release/pr/mr必须先通过 UAT 阶段 1(canary),才能建议执行步骤 7;只有在 diff 确认没有增加任何用户可见能力时才可跳过,并且必须写明理由。真实事故记录:2026-09-17,此前所有推送(包括6c839d8)都只通过了medic --ci/ci-local,从未通过真实远程 UAT。 - RULE-05 (MUST):**
merge级别——测试是合并条件,不是走过场:**合并前必须拉取分支 + 运行medic --ci+ 执行真实测试;如果未通过,绝对不能合并,保留原样交给作者。一对一处理,不要一次盲目合并多个 PR。 - RULE-06 (MUST):**PR/MR 正文遵循与补丁说明相同的规则:**面向用户表述;如果有未完成的部分,必须列出已知限制。不得包含 SHA/内部函数名。不得自动添加
Closes #N。 - RULE-07 (MUST):**副作用操作需要用户说“yes”:**push/tag/release/PR/MR 都是难以撤销的对外操作 → 始终先停止并展示命令,等待批准;不得在 commit/PR 正文中添加 AI 署名(遵循 fdk)。
- RULE-08 (MUST):**不得自行安装/认证
gh/glab:**缺少工具 → 停止并告知用户;不要猜测 host。 - RULE-10 (MUST):**运行任何门禁前,先确认 repo 类型。**清单第一行始终为
🏷 repo_role=<role> (khai báo | sổ máy | suy luận | ép tay)。来源为suy luận→ 询问用户确认,然后标记(repo_role.py . --set <role>;foreign则记录到机器登记簿中,不要动他人的 repo)。某种 repo 类型的门禁不适用于其他类型:ci-local/capability-stamp/UAT 只对framework有意义;在module/downstream中运行属于误判,在framework中跳过则会造成漏洞。2026-09-20 的教训:单流程/ship让人必须记住,发布 engine repo 的流程截然不同。 - RULE-11 (MUST):**推送前先读取门禁的 rc。**push/tag 命令必须单独放在读取并打印
rc=之后的一个步骤中;禁止用;将门禁与 push 串接,或用| tail管道吞掉 rc。2026-09-20 当天发生的两起事故:medic未通过时仍推送了c83b065,以及install-test报告 FAIL 时仍创建了 tagv3.0.1——两者都因命令串接导致。在framework中,如果工作树有未提交的陌生文件(例如被其他工具覆盖),请在该 commit 的干净 CHECKOUT 上运行门禁(git worktree add --detach <tmp> HEAD),以免误判 workflow。 - RULE-12 (MUST):**
foreign= 做客于他人的地盘。**仅限pr/mr级别,不得直接推送到默认分支;遵循对方的 commit/CI 规则(阅读 CONTRIBUTING,不适用 R15 或 overstack 规则);不得安装,不得写入.overstack.yaml,不得向 repo 添加我们的任何配置文件。 - RULE-09 (MUST):组合使用,不要重复造轮子:用
medic检查健康状况,用git tag管理版本,用gh/glab创建 PR/MR——此 skill 只负责协调清单。 - 能力:读取 repo + 运行本地门禁;写入远程(push/tag/PR/MR/merge)只能通过已认证的工具,并且必须先获批准。
失败边界
- 门禁未通过(
medic --ci/ci-local/freshinstall)→ blocked,打印问题所在 + 修复命令,不得 push。 - UAT 任一阶段 FAIL → failed,停止发布,删除 canary;阶段 2 FAIL → 按照
/fdk-uat步骤 5 从远程移除 commit。 - 不确定 diff 是否包含“新能力”或版本级别 → clarify(询问用户),不得自行决定。
- 缺少
gh/glab或尚未认证 → blocked,告知用户安装并登录。 merge级别:PR 未通过 → 不得合并该 PR(整批标记为 partial),并说明原因。
HOW
工作流
| 步骤 | 类型 | 输入 | 操作 | 输出/退出 | 失败/下一步 |
|---|---|---|---|---|---|
| W00 | 确定性 | repo | repo_role.py . --json → 位于 🏷 repo_role=…;来源为 inferred 时询问用户 + --set;从「按 repo_role 划分的流程」表中选择 gate 列 | role + gate 表 | role 无效 → blocked |
| W01 | 确定性 | repo | 健康检查 medic --ci,然后运行 python3 fdk/tools/ci-local.py(包含 freshinstall 探测) | rc 0 | 报错 → blocked |
| W02 | 效果 | diff、级别 | UAT /fdk-uat 阶段 1 — 仅适用于具备新能力的 release/pr/mr(B02) | PASS 或跳过原因 | FAIL → failed |
| W03 | 确定性 | worktree | Git 干净:git status --short,不存在未跟踪的临时文件 | 干净 | 不干净 → 清理后重复 W03 |
| W04 | 确定性 | diff | 对涉及的引擎进行自测/快速检查 | rc 0 | 报错 → blocked |
| W05 | 判断 | 最新 tag | 版本 vX.Y.(Z+1) — 仅适用于 release(B01) | 建议的 tag | 不确定级别 → clarify |
| W06 | 判断 | git log、scratch-log | 如实撰写 Patch note / PR-MR 正文;如有未完成部分,必须注明 Known-limitations | 文本 | — |
| W07 | 确定性 | W01–W06 的结果 | 显示检查清单 + 建议命令,停下等待批准 | "yes" | 未获批准 → cancelled |
| W08 | 效果 | 批准 | 按级别执行;如果已运行 W02,则紧接着运行 UAT 阶段 2 | 副作用 + 回执 | 阶段 2 FAIL → 撤销 commit |
各步骤的详细说明(W01–W08 的权威来源):
描述范围,而不是记住标志:"push"(仅推送)· "release"(推送 + tag + notes)· "pr"(推送分支 + GitHub Pull Request)· "mr"(推送分支 + GitLab Merge Request)· "merge"(汇总传入的 PR/MR → 测试 → 合并)。默认 = push。
push/release/pr/mr 级别(推送出去)运行以下步骤 1-8。merge 级别(汇总进来)在步骤 8 走单独的分支(参见「merge 级别」)。
步骤 1、3-4 适用于所有级别。步骤 2(UAT)仅适用于 release/pr/mr。步骤 5(version tag)仅适用于 release。步骤 6 将「撰写内容」改为:release → RELEASE-vX.Y.Z.md;pr/mr → PR/MR 正文(采用同样的用户视角语气,并遵循同样的 Known-limitations 规则)。push 不需要文本,只需 commit msg。merge 级别不撰写说明(只报告测试结果,以及合并/不合并的决定)。
- 健康门禁 —
medic --ci(或python3 fdk/tools/medic.py --ci)。FAIL → 停止,打印缺口 + 修复命令;红灯时不要推送。Warn(已知欠债)→ 可以通过,但必须在补丁说明中列出。- 然后运行
python3 fdk/tools/ci-local.py(框架仓库)——在本地运行 GitHub 所有工作流中每个run:步骤的完整内容(L4)。medic 是 L2;CI 中还有门禁(search-index、skill-provenance、retrieval-eval……)——080926 已经踩过坑:medic 通过并推送后,CI 有 2 个 job 失败。失败 → 不要推送。 - 包含
freshinstall探测(E2E):通过 working-tree 将 overstack 用 curl 安装到隔离的空项目中(mktemp 位于仓库外)→ 断言 3 个支柱 + harness 能触发 + orchestration-ready。失败 = 新用户安装路径损坏 → 不要推送。完整版本 curl-github + acceptance live:bash harness/scripts/fresh-install-smoke.sh --remote。
- 然后运行
- **UAT 门禁 —
/fdk-uat(仅限release/pr/mr级别,不适用于普通push):**步骤 1 中的medic --ci/ci-local只通过file://working-tree 安装——证明“本机代码正常”,不能证明“陌生用户通过curl从 remote 下载后能运行”(GH#77 这一类问题:“文档承诺了,但用户没拿到”,2026-09-17 对 push6c839d8的实际测量显示完全没有经过 UAT)。- 包含新能力的 diff(新 skill / 能触发的新 rule / 新 engine-tool)→ 必须至少运行阶段 1(canary:推送临时分支,从该分支的 raw 进行 curl 安装,检查新能力是否可用),然后才能进入步骤 7。阶段 2(main-URL smoke,等待 CDN)在步骤 8 用户批准真实 merge/tag 后立即运行——不是提议时运行。
- 纯 bugfix/内部重构的 diff,不增加用户可见能力 → 跳过此步骤,并在步骤 7 的 checklist 中明确写明原因(“没有新能力 — skip UAT”)。
- 不确定 diff 是否算“新能力” → 询问用户,不要自行决定跳过。
- 推送 canary 是真实副作用(将临时分支推送到 remote)→ 运行前先停止并询问用户,遵循此 skill 的副作用规则。任何阶段失败 → 停止发布,报告具体错误 + 删除 canary(不能“留待以后处理”)。
- Git 干净:
git status --short;确保临时垃圾(scratchpad/…)已加入.gitignore,如果误追踪则运行git rm -r --cached。检查没有混入机密/临时文件。 - 对涉及的 engine 运行自测/快速检查(例如
council.py selftest、与 diff 相关的测试)。 - 版本:
git tag --sort=-v:refname | head -1→ 最近的 tagvX.Y.Z→ 建议vX.Y.(Z+1)(patch);如果 diff 值得提升,则建议 minor/major——不确定级别时询问用户。 - **如实撰写补丁说明(采用面向用户的发布说明语气——类似 Claude Code):**编写
RELEASE-vX.Y.Z.md。- **格式:**标题
Version X.Y.Z:,然后使用扁平项目符号列表,每个要点一行。 - **语气:**以动词开头,描述用户可见的变化,不要罗列 commit/内部实现。
Fixed <症状> — <现在的表现>·Added <能力> — <用途>·Removed/Changed …。从读者(“你/你的”)的角度撰写,不写 SHA;除非用户直接输入,否则不写函数名。 - **按动词隐式分组:**先写 Added/Changed,再写 Fixed(类似 Claude 发布说明)。列表较短时不需要分组标题。
- **来源:**阅读
git log <tag-cũ>..HEAD+ 本次会话的 scratch-log → 将技术性 commit 翻译成用户能理解的表述(可根据感知价值将一个 commit 合并或拆分为多个要点,不必一一对应)。 - **已知局限(如有未完成部分则必须写明——优于 Claude 的地方):**明确说明尚未实现的提案草案、部分覆盖、medic 已知警告等。“正在处理/已知”。保持沉默 = 夸大其词(council 2026-07-03 的教训)。
- **格式:**标题
- 向用户展示 checklist + 建议命令(commit 消息、push、tag/PR/MR、UAT 阶段 1 结果或跳过原因)。停止并等待批准。
- 用户批准后:按级别执行。未得到“yes”前,不要自行执行有副作用的步骤。如果步骤 2 已运行 UAT 阶段 1(PASS)→ merge/tag/push 完成后,立即运行 UAT 阶段 2(main-URL smoke,等待 sentinel CDN),然后再告知用户“完成”;失败 → 按
/fdk-uat步骤 5 从 remote 撤销 commit。- **push:**commit +
git push。 - **release:**commit + push +
git tag vX.Y.Z+ 创建 release(notes = 补丁说明)。 - **pr(GitHub):**commit +
git push -u origin <nhánh>+gh pr create --base <base> --head <nhánh> --title "…" --body-file <PR-body>。Base 默认为仓库主分支(例如orca),不确定时询问。 - **mr(GitLab):**commit +
git push -u origin <nhánh>+glab mr create --source-branch <nhánh> --target-branch <base> --title "…" --description "…"(或-F <file>)。 - 根据 remote 选择工具:
git remote get-url origin包含github.com→gh,包含gitlab→glab。ship pr/ship mr明确指定工具;如果工具未安装/未认证(gh auth status/glab auth status),则停止并告知用户安装+登录(不要自行处理)。 - 不要自行在 PR/MR body 中添加
Closes #N/Fixes #N,除非用户明确要求——让用户保留手动关闭 issue 的控制权。
- **push:**commit +
按 repo_role 执行的流程(W00 选择一列;上方 W01–W08 描述 framework 列)
| 步骤 | framework(Rheinmir/setup) | module(orca-graph、uiux-asset…) | downstream(有 .llmwiki/ 的项目) | foreign(他人的 repo) |
|---|---|---|---|---|
| Gate | medic --ci → ci-local(如果工作树中有陌生文件,则在干净的 checkout 上运行) | 先 commit → 执行它自己的 test + eval + install-test(逐项读取 rc;install-test 会从 HEAD 克隆,因此未 commit 就等于尚未测试) | 项目 test + llmwiki-validate + medic(downstream 模式) | git 干净 + 他们的 test/CI |
| 远程 UAT | diff 包含新能力时(B02) | push 后从远程进行安装 smoke test(将模块的 install.sh 安装到临时 HOME) | 不需要 | 不需要 |
| Version | 当下发到客户端的内容发生变化时(hook、shim、installer、全局 tool),运行 capability-stamp.py --update — 即使表面 sha 没有变化也必须 bump,否则已是当前版本的机器不会 refresh | bump VERSION + 代码中的常量 + CHANGELOG | 按项目规定 | 按他们的规定 |
| 最后一步 | push(· 根据级别打 tag/创建 PR) | push → 等待 CI 通过 → tag + release → 在 framework 中重新 pin(.overstack.yaml 中的 upstream_pin:如果 SKILL 有变更则复制 → sync-skills.py → 将 commit/version 写入 fdk/skills.provenance.json → 发布 framework) | push / pr | 仅 pr / mr |
| 禁止 | installer 写入此 repo(自行拒绝,rc 3) | ci-local、capability-stamp | 推送 framework 文件、UAT、stamp | 安装、写入 overstack 配置、违反他们规定的 AI attribution |
分支
| ID | 类型 | 条件 | 行为 | 跳过 / 失败 | 汇合 |
|---|---|---|---|---|---|
| B01 | conditional_required | 级别 = release | W05 version + tag vX.Y.Z + release notes | 其他级别 → 跳过 | W06 |
| B02 | conditional_required | 级别 ∈ {release, pr, mr} 且 diff 包含新能力 | W07 前进行 UAT 阶段 1,W08 后进行阶段 2 | bugfix/内部重构 → 跳过,并在 checklist 中记录原因;不确定 → clarify | W03 |
| B03 | user_optional | 级别 = merge | 单独执行 GOM VỀ 循环(见下文),跳过 W02、W05–W06 | PR 未通过 → 不合并该 PR | 总结报告 |
merge 级别(GOM VỀ — 审核后合并收到的 PR/MR)
不写 note/tag。跳过步骤 2、5-6,改为逐个请求测试的循环:
- 根据 remote 选择工具(如上):
github.com→gh,gitlab→glab。尚未 auth → 停止并告知用户。 - 列出 repo 中打开的 PR/MR:
- GitHub:
gh pr list --json number,title,headRefName,mergeable,isDraft - GitLab:
glab mr list(如果版本支持,也可以使用--output json)。 向用户显示列表;如果用户指定了具体编号,则筛选;否则询问是全部处理还是选择处理。
- GitHub:
- 逐个处理每个 PR/MR,不要盲目批量合并:
a. 将分支拉到干净的本地环境:GitHub
gh pr checkout <n>;GitLabgit fetch origin merge-requests/<n>/head:mr-<n> && git checkout mr-<n>(或glab mr checkout <n>)。 b. 在该分支上运行 GATE + TEST:medic --ci+ 与 PR diff 相关的 selftest/test。这是合并条件 — 未运行前不得合并。 c. 通过 → 提议 merge 命令,停止并等待批准,然后合并:GitHubgh pr merge <n> --squash(或根据 repo 使用 --merge/--rebase);GitLabglab mr merge <n>。未通过 → 不得合并;报告未通过项,并保留 PR 原样以便作者修复。 d. 合并后:返回原分支(git checkout <nhánh-cũ>),清理临时分支。 - 总结报告: 对每个 PR/MR,说明已合并 / 因未通过而跳过(附原因)/ 因用户要求而跳过。
验证与停止条件
是否继续取决于工具的 rc(medic、ci-local、fdk-uat、test),而非模型的自我判断。所有副作用均在 W07 停止并等待 “yes”;merge 级别则逐个 PR 停止并等待批准。网络错误时不要自动重试 push/tag — 告知用户。
示例
- 正向(模块): 在仓库
orca-graph中输入 “ship release” → W00 打印🏷 repo_role=module (声明)→ 提交 →pytest tests返回码为 0 →evals/run.py返回码为 0 →tests/install-test.sh返回码为 0 → 更新VERSION→ 停止并等待审核 → 推送 → CI 全绿 → 创建标签并发布 → 提醒按照upstream_pin在Rheinmir/setup中重新固定版本。 - 边界情况(外部仓库): 在一个没有标记、没有标签的仓库中 → W00 打印
🏷 repo_role=foreign (推断:没有 fdk/wiki,也没有 .harness-stamp)→ 请用户确认 → 仅建议ship pr,阅读对方的 CONTRIBUTING,不写入任何 overstack 文件。 - 正向: 修改文档后输入 “ship push” → W01 通过,B02 跳过(没有新增能力,记录原因),W03–W04 通过 → 检查清单 +
git push→ 用户回复 “yes” → 推送。 - 边界情况/失败: “ship pr” 包含新技能,但
ci-local有 1 个任务失败 → 阻止于 W01,打印失败任务和修复命令,不推送分支,也不创建 PR。 - 边界情况: “ship merge” 有 2 个 PR,PR #2 测试失败 → 审核后合并 #1,跳过 #2 并说明原因(部分完成)。