mcpField
S
ship

ship

github.com/julianobarbosa开发1439 次安装3 天前更新
代码仓库

关于 ship

ship 是用于将已完成分支提交、推送并创建 PR 的交付技能,适合需要代码评审流程的团队。它自动识别 Azure Repos 或 GitHub,并可关联工作项,合并后清理分支和 worktree。

主要功能

  1. 01自动识别 Azure Repos 或 GitHub
  2. 02预检待交付内容与工作区范围
  3. 03提交并推送当前分支
  4. 04创建 PR 并关联工作项
  5. 05设置 PR 标签及评审人
  6. 06合并后清理分支和 worktree

适用场景

  1. 01通过 PR 评审交付分支变更
  2. 02确保 PR 关联 Azure Boards 工作项
  3. 03为 PR 添加标签和评审人
  4. 04PR 合并后清理本地分支与 worktree

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/julianobarbosa/claude-code-skills --skill ship

安装后在 Claude Code 中输入/ship确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.gitignore
package.json
tsconfig.json
assets/
pr-template.md
references/
azure-devops.md
github.md
scripts/
ship-detect.ts
ship-lib.test.ts
ship-lib.ts
ship-open.ts
ship-pr.ts
ship-preflight.ts
ship-push.ts
ship-snapshot.ts
ship-tag.ts

详细介绍

Ship

ship 负责完成一个已完成分支的最后一步:干净地提交、推送(必要时处理 Azure DevOps 身份验证问题)、创建拉取请求、将其关联到工作项,并在合并后清理分支和工作树。它会自动检测你使用的是 Azure Repos 还是 GitHub,并遵循对应流程,因此同一条命令既适用于你的组织,也适用于 github.com 仓库。

它的功能刻意保持精简。它不会在本地合并、运行流水线或管理待办事项,而是将可供审查的 PR 交给平台处理,并在合并后进行清理。

何时使用它,而不是其他工具

  • ship — 基于 PR 的交付流程:推送 → PR →(通过平台合并)→ 清理。
  • commit — 仅暂存、提交并推送当前分支,不创建 PR。
  • merge — 在本地将分支合并到 main(快进合并)并清理。没有 PR 审批门槛时使用它;更改必须经过审查/策略检查时使用 ship。
  • azure-devops — 深入的 REST/MCP 工具箱(WIQL、批量更新、流水线、评论线程)。ship 只会调用所需的精简功能;需要更丰富的功能时使用 azure-devops。

流程

先运行 bun scripts/ship-detect.ts——它会输出平台、Azure 组织/项目/仓库(如有)、分支和推断出的工作项 ID。以下所有步骤都会据此选择流程。

**一次性设置:**这些脚本依赖 azure-devops-node-api 和 @octokit/rest。首次使用前,在技能目录(~/.claude/skills/ship/)中运行一次 bun install。

只说“ship”时——直接执行

简短地说 ship / ship it 意味着“完成交付”,而不是“给我解释”。运行流程时尽量少作说明:仅报告预检范围结果(仅当结果不干净时)、pr_url、任何已创建的工作项 ID,以及清理确认。不要回顾会话状态、不要大声重新推导计划,也不要在执行前逐项列出所有步骤——对简短命令过度解说本身就是一种失误模式。唯一值得暂停的情况是预检(步骤 0)返回 scope_clean=false。

0. 预检
bun scripts/ship-preflight.ts     # anything to deliver? + working-tree scope audit
  • 它会输出 commits_ahead_of_upstream、staged/unstaged/untracked 计数,以及 scope_clean=true|false。
  • 如果 scope_clean=false,请停止并向用户列出相关文件,然后再提交——确认这些文件确实有意排除在外。这是 PR #192 的防护检查:另一个目录中已更改但未暂存的文件会被悄悄漏出 PR。此检查仅提供提示(绝不会阻止操作),但未经确认的 false 正是此检查要避免的范围遗漏。
  • 注意 ship-detect.ts 检测到的平台。如果它显示 unknown(例如自定义 SSH 主机别名),请在当前会话设置 SHIP_PLATFORM=azure 或 SHIP_PLATFORM=github。
1. 提交——以作者身份,而不是以工具身份

仅暂存此任务所需的文件并提交。绝不添加 AI 署名——不要添加 Generated with Claude,也不要添加 Co-Authored-By: Claude。提交由人类署名;工具来源信息不应写入 Git 历史记录(此仓库的 commit-msg 钩子会移除这些尾注作为兜底,但不要依赖它——一开始就不要写入)。如果提交前钩子因无关问题失败,可以使用 --no-verify;如果它指出的是你所做的更改,请修复问题。

2. 分支状态
  • 在功能分支上 → 正常,继续。
  • 在 main/master 上 → 无法从 main 向自身创建 PR。请创建分支,并在推送前将提交移到该分支上:
    git branch feature/<slug> && git reset --hard @{u} && git checkout feature/<slug>
    
    (仅当这些提交尚未推送到 main 时才这样做。如果不确定,请停止并询问。)
  • 直接推送到 main 被阻止(分支策略)意味着应采用 PR 流程,而不是强制推送或绕过策略。
3. 推送
bun scripts/ship-push.ts         # pushes current branch, sets upstream

在 Azure DevOps 上,如果常规推送因身份验证失败,此操作会自动生成 az OAuth 令牌,并使用 Bearer 标头重试——当 SSH 和 HTTPS 凭据助手都不可用时的备用方案。具体实现请参阅 references/azure-devops.md。

4. 创建 PR(+ 确保工作项存在/关联工作项,+ 转换状态)

不变量 — 永远不要向用户询问工作项 ID。 查找 → 验证 →(若不存在) 创建 + 分配 + 关联工作项,这一过程完全自动。PR 在关联 Board 工作项之前,绝不能显示为已就绪。如果你正准备询问“工作项 ID 是什么?”, 请停下 — 这个问题正是此技能要避免的错误。ID 来自 --work-item、分支名称或刚创建的工作项,绝不会通过提问获得。

代码审查不负责管理工作项。 由于 ship 保证 PR 创建时已关联工作项, 审查步骤 — 无论是人工审查者,还是 code-review / bmad-code-review 技能 — 都不得再次询问工作项,也不得因此阻止审查。上游已解决此问题。

起草一份真实的描述 — 将 assets/pr-template.md 复制到临时文件,填写 Summary / Changes / Verification,并保留 AB#<id> 这一行,以便关联工作项。

bun scripts/ship-pr.ts --title "<title>" --body-file /tmp/pr-body.md \
  --work-item <id> --transition Resolved \
  --reviewer [email protected] --required-reviewer [email protected] --tag needs-review
  • 自动检测平台;脚本使用 azure-devops-node-api SDK(createPullRequest)或 Octokit(pulls.create)。
  • 先决条件会随创建操作一并提交,而不是通过后续往返调用处理。 在 Azure 上,工作项、标签和审阅者都会打包在单次 createPullRequest 调用中。在 GitHub 上,创建 API 不接受这些内容,因此标签和审阅者会通过后续调用添加——结果相同,只是在 GitHub 上无法避免后续调用。
  • 审阅者 — --reviewer <upn|username> 用于添加可选(不会阻塞流程)的审阅者;--required-reviewer 会在 Azure 上将其标记为必需(阻止完成)。两者都支持逗号分隔和/或重复指定。在 Azure 上,UPN/电子邮件地址会被解析为身份;无法解析的名称会发出警告并跳过(reviewer_skipped=…),绝不会阻塞 PR。GitHub 没有针对单个 PR 的“必需”审阅者(这是分支保护功能),因此必需审阅者只会作为普通请求发送。脚本会为每位审阅者打印 reviewer_added=<name> required=<bool>。 在 Azure 上,如果未指定这两个标志,且设置了 $SHIP_ADO_DEFAULT_REVIEWER,则会将其添加为可选审阅者(在 GitHub 上会忽略该变量,因为 ADO 身份毫无意义)——没有审阅者记录的 PR 不会留下任何已邀请审阅者的痕迹。未设置 = 不添加审阅者,沿用此前行为。
  • Azure 绝不会在没有关联 Board 工作项的情况下创建 PR。 脚本会从 --work-item(或分支名称)解析 ID,然后通过 SDK(getWorkItem)验证该工作项确实存在。从分支名称解析只是启发式方法——例如 1234-foo 这样的分支可能包含一个并非真实工作项的编号,因此要检查其是否存在,而不是直接假定它存在。
  • 如果找不到真实工作项,则会为每项任务创建一个工作项并关联到 PR,每个工作项都会分配给 $SHIP_ADO_ASSIGNEE(默认为 [email protected];可通过 --assignee 覆盖)。 每项任务传入一次 --task "<title>"(可重复指定),或传入 --tasks-file <file>(每行一个标题),即可为每项任务创建一个工作项。若未指定 --task,则根据 PR 的 --title 创建一个工作项。类型默认为 Task(--work-item-type "User Story"|Bug)。使用 --no-create-work-item 可禁用创建,仅关联现有 ID。
    • 脚本会为每个已创建的工作项打印 work_item_created=<id>,并为整组工作项打印 work_items=<ids> — 将新 ID 呈现给用户(不要编造;它们来自 SDK 响应)。
  • --transition(仅限 Azure)会在 PR 创建后将每个已关联/已创建的工作项转换到指定状态。状态名称因流程而异(Agile:Resolved;Scrum:Committed;Basic:Doing)— 请先验证有效的下一状态;参阅 references/azure-devops.md。省略 --transition 可让看板保持不变。(新建的工作项会从对应类型的初始状态开始,例如 New/To Do。)
  • Complete 对话框中的“合并后完成关联的工作项”复选框默认已勾选。 Ship 会在创建调用后立即通过 PATCH 设置 completionOptions.transitionWorkItems=true,这样用户在 PR 上点击 Complete 时,关联的工作项就会完成,而不会在 PR 合并后仍保持未完成状态。这与 --transition 相互独立:后者在 PR 创建时触发;这一项则在合并时触发。它能闭合整个流程,因为没人会记得在合并时勾选复选框。传入 --no-complete-transition 可取消勾选。脚本会打印从 PATCH 响应中读取的 complete_transitions_work_items=true|false。GitHub 上会忽略此选项,因为正文中的 Closes #<id> 可实现相同效果。
  • 脚本会打印 pr_url=…;将其呈现给用户。
4b. 标签 + 审阅者(默认应用)

Azure DevOps 将它们称为标签;GitHub 称为标签——本质上是同一回事:一种简洁醒目的信号, 有助于审阅者分流处理,并帮助团队整理 PR。Microsoft 的指导意见是,标签可以“向审阅者传达额外信息,例如 PR 仍在进行中,或是针对即将发布版本的热修复”(向拉取请求添加标签)。

PR 绝不会不带标签就创建。 如果没有 --tag,ship-pr.ts 会按以下顺序推导标签:

  1. 根据 Conventional Commit 标题——docs(finops): … → docs、finops。
  2. 根据分支前缀——feat/… → feat,fix/|hotfix/|bugfix/ → fix, docs/ → docs,chore/ → chore,refactor/ → refactor。
  3. 否则使用 needs-review,并发出 WARNING。

此处不会失败。没有哪个平台有“必须添加标签”的策略,因此这是唯一的强制执行点;硬性报错会导致采用其他约定的仓库无法使用。传入 --no-tag 可创建不带标签的 PR。如果没有 --reviewer/--required-reviewer,则会将 $SHIP_ADO_DEFAULT_REVIEWER(如果已设置)添加为可选审阅者;--no-reviewer 则会跳过此操作。

使用 --tag 在创建 PR 时添加标签(可用逗号分隔,也可重复传入):

bun scripts/ship-pr.ts --title "<title>" --body-file /tmp/pr-body.md \
  --work-item <id> --transition Resolved --tag "hotfix,do-not-merge"

……或者使用 ship-tag.ts 为已创建的 PR 管理标签(Azure 上的 ID 为 pr_id=,GitHub 上为 pr_number=, 两者都会由 ship-pr.ts 打印):

bun scripts/ship-tag.ts <pr-id> --add "needs-review"     # add
bun scripts/ship-tag.ts <pr-id> --remove "do-not-merge"  # remove
bun scripts/ship-tag.ts <pr-id> --list                   # show current tags

两个平台上的标签都支持自由填写(Azure 会在首次使用时创建标签定义;GitHub 会自动创建缺失的标签)。关于推荐且统一的标签集——do-not-merge、work-in-progress、 hotfix,以及类型/领域约定——请参阅 references/azure-devops.md / references/github.md 中的 推荐标签部分。

4c. 在浏览器中打开 PR(可选)

实际审阅是在 PR 页面进行的;当你需要在多个账号(工作、个人或云端)之间切换时,使用哪个已登录账号打开页面很重要——错误的个人资料会显示错误的 权限,或者完全没有访问权限。因此,PR 创建后,始终显示 pr_url,然后询问是否要在浏览器中打开。打开操作须由用户选择,绝不自动执行(浏览器窗口属于可见副作用;用户要求先询问)。

ship-open.ts 通过读取浏览器自身的 Local State(与其个人资料切换器显示的账号映射相同),解析电子邮件 → 浏览器个人资料,然后使用该个人资料打开 URL。它适用于 WSL(→ Windows Edge/Chrome)和 macOS(→ Chrome/Edge)。所有信息都不会硬编码——每次运行都会实时发现映射,电子邮件作为参数传入,且只会记住你的默认电子邮件,保存在 ~/.config/ship/open.json(此技能之外)。

发现账号后,使用 AskUserQuestion 询问用户选择哪个账号(只有多个账号时才值得询问;列出每个账号,并提供“不打开”选项):

bun scripts/ship-open.ts --list-profiles   # prints browser= + one `profile=<dir>\t<email>\t<name>` per account

使用选定的账号打开 PR(如果想在不打开窗口的情况下确认解析出的个人资料,可先添加 --dry-run):

bun scripts/ship-open.ts "<pr_url>" --profile <email>              # opens; prints opened_url= / profile_dir=
bun scripts/ship-open.ts "<pr_url>" --profile <email> --dry-run    # resolve only, no window

将所选账号记为默认账号,以便今后执行 ship 时只需按一次键即可选择:

bun scripts/ship-open.ts --set-default <email> [--browser edge|chrome]

电子邮件未知或未提供时,会回退到浏览器的 Default 个人资料(并在 stderr 上提示),而不会冒险猜错。此步骤独立于下面的合并/清理操作——打开 PR 进行审阅不会推进或阻碍任何流程。

5. 合并后——清理

仅在 PR 确实已合并后执行(不要删除仍有未关闭 PR 的分支)。按照 merge 技能的清理流程操作,并且删除前先询问:

  • 如果分支是在工作树中开发的,请先 cd 到主仓库目录,然后执行 git worktree remove <path>——不能在分支自身的工作树内删除该分支。
  • 执行 git branch -d <branch>(小写 -d 会拒绝删除未合并的分支——这是安全保护;只有在用户明确确认后,才升级为 -D)。
  • 执行 git push origin --delete <branch>(如果该分支仅存在于本地,则忽略失败)。
6. 快照当前状态(可选)

与 PR 流程无关——任何时候想要一个可回退的状态点,都可以使用此功能:在干净地合并到 main 后、进行高风险迁移前,或者只是标记“一切正常”。它会在 HEAD 上创建一个带注释的 git 标签(工作树状态无关紧要——标签指向的是一次提交)。

bun scripts/ship-snapshot.ts \
  -m "what changed / why you're saving here" \
  -m "verification status"

脚本会自动填充那些容易手动填错的内容:

  • 名称默认为 v<YYYY.MM.DD-HHMM>(精确到分钟,因此一天内重复创建的快照不会冲突)。传入 --daily 可使用每天一个的 v<YYYY.MM.DD>,或传入 --name <tag> 指定确切名称。
  • 第一段消息会自动生成——Snapshot <tag> — <branch> @ <shorthash>——因此即使没有 -m,标签也能自我说明。每个添加的 -m 都会成为单独一段(通过参数数组传入,因此实际换行符和标点都能保留——不像在单个 shell 字符串中使用 \n)。
  • 遇到冲突时会拒绝操作,而不会覆盖(退出码 3)——绝不能悄悄移动他人依赖的快照标签。

标签在推送前仅保存在本地(共享/可见——请先确认,就像打开 PR 一样)。添加 --push 可推送到 origin;在 Azure 上,如果身份验证失败,它会像 ship-push.ts 一样回退到 az OAuth Bearer 标头。脚本会打印 tag=、commit=、branch=、pushed=。

内置工具

脚本功能
bun scripts/ship-preflight.ts [remote]检查待交付内容,并审计工作树范围(scope_clean=…);仅提供建议,从不阻止操作。先运行
bun scripts/ship-detect.ts [remote]打印平台、Azure 坐标、分支和推断出的工作项
bun scripts/ship-push.ts [-r remote] [-b branch]推送并设置上游;身份验证失败时回退到 Azure OAuth Bearer
bun scripts/ship-pr.ts --title … [opts]在检测到的平台上创建 PR;确保工作项存在/创建工作项并关联(分配给已配置的用户);可选进行 Board 状态转换;根据标题/分支自动添加标签(可通过 --tag 覆盖,或通过 --no-tag 禁用);可选传入 --reviewer/--required-reviewer(默认使用 $SHIP_ADO_DEFAULT_REVIEWER,通过 --no-reviewer 禁用);预先检查“合并后完成已关联的工作项”(通过 --no-complete-transition 禁用)——在 Azure 上,工作项、标签和审阅者会合并到一次创建调用中
bun scripts/ship-tag.ts <pr-id> [--add|--remove "t1,t2"] [--list]在现有 PR 上添加/移除/列出标签(Azure)或标签(GitHub)
bun scripts/ship-open.ts <url> [--profile <email>] [--dry-run]在指定浏览器账户配置文件中打开 PR/URL(通过浏览器的 Local State 将电子邮件映射到配置文件);WSL→Edge/Chrome,macOS→Chrome/Edge;还支持 --list-profiles、--set-default
bun scripts/ship-snapshot.ts [-m "para"]… [--daily] [--name <tag>] [--push]将当前状态保存为带注释的git 标签(以日期命名,主题能自我说明);可选推送,并支持 Azure OAuth 回退

脚本是通过 bun 运行的 TypeScript;共用辅助函数位于 scripts/ship-lib.ts。在技能目录中运行一次 bun install (安装 azure-devops-node-api + @octokit/rest)。Git/推送仍通过子进程调用(这是 git 操作);只有 ADO/GitHub REST 接口使用 SDK。平台相关说明位于 references/azure-devops.md 和 references/github.md——请阅读与检测到的平台相匹配的文件。PR 描述以 assets/pr-template.md 为模板。

安全

  • 执行任何破坏性操作或会公开共享的操作前都要先询问——删除分支/工作树,以及打开 PR(团队可见)。推送功能分支和创建草稿风险较低;创建可供审阅的 PR 和删除任何分支都应先确认。
  • 绝不要为了绕过被拒绝的推送而强制推送。 推送被拒绝意味着历史发生分歧或存在策略限制——应先调查,不要覆盖。
  • 令牌应放在标头中,使用期限应短,并且绝不打印或放入 URL。
  • 任何地方都不要注明 AI 署名——包括提交消息、PR 标题或 PR 正文。

注意事项

  • 两条创建 PR 的路径(Octokit pulls.create、ADO createPullRequest)都要求先推送分支——步骤 3 必须在步骤 4 之前。
  • 如果 ship-detect.ts 返回 unknown,几乎总是因为使用了自定义 SSH 主机别名——请设置 SHIP_PLATFORM。 (dev.azure.com-<alias> SSH 主机会被检测为 azure,但无法解析其组织/项目/仓库——这与 shell 版本的限制相同;必要时请显式传入坐标/--work-item。)
  • 脚本需要依赖项:如果看到 Cannot find module 'azure-devops-node-api',请在 skill 目录中运行 bun install。
  • 关联的工作项不会自行更改状态。有两个独立的开关:--transition 会在 PR 打开时更改其状态;completionOptions.transitionWorkItems(默认启用,见步骤 4)会在 PR 完成时更改其状态。此默认设置生效前创建的 PR 不包含任何完成选项,因此对应复选框不会勾选——该设置在创建时就已确定,无法添加到已合并的 PR。
  • 如果设置了 AZURE_DEVOPS_EXT_PAT,但权限范围不足(例如只有 Code 权限,缺少 Work Items / Pull Request 写入权限),ship-pr.ts 会自动检测 401/403,并使用 az OAuth bearer token 重试——无需手动取消设置 PAT。回退方式要求你以组织成员身份执行 az login。
  • 创建时遇到 GitHub 404 Not Found,几乎总是因为账号错误,而不是仓库不存在。 gh auth token 会以当前活动账号响应,而不考虑仓库所有者;对于你无权查看的私有仓库,GitHub 会隐藏仓库,而不是返回 403。现在,ship-pr.ts / ship-tag.ts 会调用 githubToken(owner),优先使用 gh auth token -u <owner>;对于所有者不是已登录账号的组织仓库,则回退到活动账号;GH_TOKEN/GITHUB_TOKEN 仍具有最高优先级。如果仍然收到 404,请检查 gh auth status 是否列出了仓库所属账号。
  • 不要用原始 az repos pr create / gh pr create(或 repo_create_pull_request MCP 工具)手动创建 PR,再事后附加工作项——这种做法会绕过自动创建并关联的约束,并迫使用户手动来回确认“ID 是多少?”。此外,它还会将标题中的 & 双重转义为字面量 &amp;(API 不会解码该实体),导致标题显示错误。ship-pr.ts 通过 SDK 和参数数组直接传递标题,因此 & 会保留为 &——这也是应该使用脚本而非原始路径的另一个原因。
  • createPullRequest 会静默丢弃 completionOptions。 载荷会被接受,但返回的 PR 中 completionOptions: null,因此创建时传入的设置不会生效(在 ADO PR 1050 中观察到)。必须在 PR 创建后使用 updatePullRequest 设置完成选项。切勿根据传入的标志报告此类设置;请从 API 响应中读取实际值。
  • 从目标仓库运行脚本,并使用绝对路径——绝不要 cd 进入 skill 目录。 平台检测会读取当前工作目录的 git remote,而 skill 目录本身是另一个 GitHub 仓库,因此运行 cd ~/.claude/skills/ship && bun scripts/ship-pr.ts 会针对那个仓库创建 PR(2026-09-23:只有 422 错误“No commits between main and main”阻止了操作)。请在要发布的仓库中运行 bun ~/.claude/skills/ship/scripts/ship-pr.ts。
  • PR 仍处于打开状态时删除分支会导致 PR 被放弃——请仅在合并后清理(步骤 5)。
  • ship-open.ts 会将 --profile <email> 与浏览器中已登录账号的邮箱进行匹配,因此没有关联账号的配置文件只能通过 Default 回退选中,无法通过邮箱选中。它会自动选择已安装的浏览器(如果 Chrome 和其他浏览器都存在,则优先选择 Chrome),不需要依赖项或网络——只需浏览器本地的 Local State 文件。如果找不到该文件,说明浏览器未安装在预期位置;请传入 --browser,或检查它打印的路径。
  • 三种不同的“标签”——不要混淆。 (1) PR 标签/分类标签(--tag、ship-tag.ts)——PR 上用于分类处理的信号。(2) 工作项(步骤 4)——用于追溯的关联。(3) Git 标签(ship-snapshot.ts)——历史记录中的提交检查点。ship-tag.ts 接收 PR ID;ship-snapshot.ts 不接收位置参数,并会为 HEAD 添加标签。同一个词,三种概念。
  • 重复添加标签是安全的——Azure 的 createPullRequestLabel 会返回已有定义,而不会报错;GitHub 会自动去重;parseTags 也会从 CSV 中移除空名称和重复名称。
  • ship-tag.ts 需要 PR ID——Azure 使用数字 pr_id,GitHub 使用 pr_number(两者都由 ship-pr.ts 输出)。在 GitHub 上,如果标签名称对应的标签不存在,系统会自动创建。

评论

支持 Markdown