关于 ship
ship 是一个 Claude Code 技能,用于自动执行代码发布流程,涵盖分支检查、测试、审查、版本与变更日志更新及 PR 步骤,面向从功能分支交付代码的开发者。
主要功能
- 01自动检查分支并合并基线
- 02运行构建、Lint 和测试
- 03审计测试覆盖率与计划完成情况
- 04发布前审查并检查范围偏移
- 05更新版本号与变更日志
- 06处理提交和 PR 流程
适用场景
- 01在功能分支上执行发布检查
- 02生成版本号和变更日志更新
- 03检查测试覆盖率及未完成计划项
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ somnio-software/ somnio-ai-tools --skill ship安装后在 Claude Code 中输入/ship确认已加载。
详细介绍
Ship:全自动 Ship 工作流
你正在运行 /ship 工作流。这是一个非交互式、全自动工作流。
任何步骤都不要请求确认。用户输入了 /ship,这意味着直接执行。
直接完成所有步骤,并在最后输出 PR URL。
仅在以下情况停止:
- 当前位于基准分支(中止)
- 存在无法自动解决的合并冲突(停止并显示冲突)
- 分支内测试失败(对已有失败进行分类处理,不自动阻止流程)
- 合并前审查发现需要用户判断的 ASK 项
- 需要进行 MINOR 或 MAJOR 版本升级(询问用户——见步骤 8)
绝不要因以下情况停止:
- 存在未提交的更改(始终将其包含在内)
- 版本升级选择(自动选择 MICRO 或 PATCH——见步骤 8)
- CHANGELOG 内容(根据 diff 自动生成)
- 提交信息审批(自动提交)
重复运行行为(幂等性):
重新运行 /ship 意味着“再次运行整个检查清单”。每次调用都会运行所有验证步骤。只有操作是幂等的:
- 步骤 8:如果 VERSION 已升级,则跳过升级,但仍要读取版本
- 步骤 11:如果已经推送,则跳过推送命令
- 步骤 12:如果 PR 已存在,则更新其正文,而不是创建新的 PR
步骤 0:检测平台和基准分支
首先,从远程 URL 检测 Git 托管平台:
git remote get-url origin 2>/dev/null
- 如果 URL 包含 "github.com" → 平台为 GitHub
- 否则 → 未知(仅使用 Git 原生命令)
确定此 PR 的目标分支,或仓库的默认分支:
如果是 GitHub:
gh pr view --json baseRefName -q .baseRefName— 如果成功,则使用该值gh repo view --json defaultBranchRef -q .defaultBranchRef.name— 如果成功,则使用该值
Git 原生回退方案:
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'- 如果失败:
git rev-parse --verify origin/main 2>/dev/null→ 使用main - 如果失败:
git rev-parse --verify origin/master 2>/dev/null→ 使用master
如果以上都失败,则回退到 main。
输出检测到的基准分支名称。在后续的每条命令中,凡是指令提到“基准分支”或 <base>,都替换为检测到的分支名称。
步骤 1:预检
-
检查当前分支。如果位于基准分支,中止:“你当前位于基准分支。请从功能分支执行 Ship。”
-
运行
git status(绝不要使用-uall)。始终包含未提交的更改。 -
运行
git diff <base>...HEAD --stat和git log <base>..HEAD --oneline,了解即将发布的内容。
步骤 2:合并基准分支(测试之前)
获取并将基准分支合并到功能分支,以便针对合并后的状态运行测试:
git fetch origin <base> && git merge origin/<base> --no-edit
如果存在合并冲突: 如果冲突简单(VERSION、CHANGELOG 排序),尝试自动解决。如果冲突复杂或有歧义,停止并显示冲突。
如果已是最新状态: 静默继续。
步骤 3:运行测试(针对合并后的代码)
运行项目的测试和构建流程:
pnpm run build 2>&1 | tee /tmp/ship_build.txt
pnpm run lint 2>&1 | tee /tmp/ship_lint.txt
pnpm run test 2>&1 | tee /tmp/ship_tests.txt
全部完成后,读取输出文件并检查通过或失败。
如果任何测试失败: 不要立即停止。按照测试失败归属分类流程进行处理:
测试失败归属分类
对于每个失败的测试:
-
获取此分支上已更改的文件:
git diff origin/<base>...HEAD --name-only -
对失败进行分类:
- 分支内,如果:失败的测试文件或其测试的代码在此分支上有修改,或你可以将失败追溯到分支差异中的某项更改。
- 可能是预先存在的,如果:测试文件及其测试的代码在此分支上都没有修改,且失败与任何分支更改无关。
- 无法确定时,默认归为分支内。
-
处理分支内的失败: 停止。 展示这些失败,不要继续。
-
处理预先存在的失败: 使用 AskUserQuestion:
这些测试失败似乎是预先存在的(并非由你的分支更改导致): [列出每项失败,包括文件:行号和简要错误信息]
A) 现在调查并修复 B) 跳过 — 仍然交付
如果全部通过: 静默继续 — 简要说明通过的数量。
第 4 步:测试覆盖率审计
使用 Agent 工具将此步骤分派给子 Agent。 子 Agent 将在全新的上下文窗口中运行覆盖率审计。
子 Agent 提示词: 将以下说明传递给子 Agent,并将 <base> 替换为相应值:
你正在为一个采用 4 层整洁架构(领域层、应用层、基础设施层、API 层)的 NestJS 单体仓库运行交付工作流测试覆盖率审计。运行
git diff <base>...HEAD查看更改内容。不要提交或推送 — 仅报告结果。
追踪差异中更改的每条代码路径。 阅读每个已更改文件(完整文件,而不只是差异)。对于每个文件,追踪所有分支(if/else、switch、守卫、提前返回、错误路径)中的数据流。
根据现有测试检查每个分支。 对于每个已更改的函数/方法,搜索对应的
.spec.ts文件。对测试质量评分:
- ★★★ 测试了边缘情况和错误路径
- ★★ 测试了正确行为,仅覆盖正常路径
- ★ 冒烟测试/存在性检查/简单断言
输出 ASCII 覆盖率图:
代码路径覆盖率 =========================== [+] src/application/units/commands/sync-unit.handler.ts ├── [★★★ 已测试] 正常路径 — sync-unit.handler.spec.ts:42 ├── [GAP] 单元不存在时出错 — 无测试 └── [GAP] 部分更新场景 — 无测试为未覆盖的路径生成测试(如果存在测试框架):
- 阅读 2-3 个现有
.spec.ts文件,严格遵循其约定- 生成单元测试。模拟外部依赖(TypeORM 存储库、外部 API)。
- 运行每个测试。通过 → 保留。失败 → 修复一次。仍然失败 → 静默删除。
分析完成后,在最后一行输出一个 JSON 对象:
{"coverage_pct":N,"gaps":N,"diagram":"<markdown diagram>","tests_added":["path",...]}
父级处理:
- 将最后一行解析为 JSON。
- 保存以用于 PR 正文中的
## Test Coverage部分。 - 输出:
Coverage: {coverage_pct}%, {gaps} gaps. {tests_added.length} tests added.
如果子 Agent 失败: 回退为在当前上下文中运行审计。
第 5 步:上线前审查
审查差异,查找测试无法发现的结构性问题。
-
运行
git diff origin/<base>获取完整差异。 -
分两轮应用此审查清单:
第 1 轮(关键)— 发布前必须修复:
- SQL 与数据安全: 使用字符串插值的原始 SQL、多表写入缺少事务、UPDATE/DELETE 缺少
WHERE、查询中未验证的用户输入 - 安全性: 暴露的机密信息、未正确纳入 git 忽略的文件和目录、端点缺少身份验证守卫、未清理输出导致的 XSS、CORS 配置错误
- 破坏性更改: 删除/重命名公开 API 端点、更改响应结构、删除必填字段
第 2 轮(信息提示)——修复或记录:
- 架构: 层边界违规(领域层中导入框架)、DTO 与实体混用、缺少 barrel 导出
- 错误处理: 内联字符串错误消息(应使用错误枚举)、吞掉错误、缺少 Problem Details 格式
- 性能: N+1 查询、缺少分页、无界查询、缺少索引
- 代码质量: 死代码、孤立代码、未使用的导入、重复逻辑、过于复杂的条件判断
置信度校准
每项发现都必须包含置信度评分(1-10):
| 评分 | 含义 |
|---|---|
| 9-10 | 通过阅读具体代码验证。已证明存在明确的 Bug。 |
| 7-8 | 高置信度的模式匹配。很可能正确。 |
| 5-6 | 中等置信度。可能是误报。请附带说明。 |
| 3-4 | 低置信度。从主报告中省略。 |
| 1-2 | 推测。仅当严重性为 P0 时才报告。 |
发现格式:
[严重性] (置信度:N/10) 文件:行号 — 描述
优先修复流程
-
将每项发现分类为 AUTO-FIX 或 ASK:
- AUTO-FIX:移除死代码、移除未使用的导入、缺少 barrel 导出、明显的格式问题
- ASK:任何会改变行为的内容、安全修复、架构决策
-
自动修复所有 AUTO-FIX 项。 每项修复输出一行:
[AUTO-FIXED] [文件:行号] 问题 → 所做的修改 -
**如果仍有 ASK 项,**通过一个 AskUserQuestion 一并提出:
- 列出每项的严重性、问题和推荐修复方案
- 每项提供选项:A) 修复 B) 跳过
-
所有修复完成后:
- 如果应用了任何修复:提交已修复的文件(
git add <files> && git commit -m "fix: pre-landing review fixes"),然后重新运行测试(步骤 3)。 - 如果未应用任何修复:继续执行步骤 8。
- 如果应用了任何修复:提交已修复的文件(
-
输出摘要:
Pre-Landing Review:N 个问题 — M 个自动修复,K 个待询问(J 个已修复,L 个已跳过)
步骤 6:范围偏移检测
检查:分支是否只构建了声明的内容,没有多做或少做?
-
读取提交消息(
git log origin/<base>..HEAD --oneline)。 -
根据分支名称和提交识别声明的意图。
-
运行
git diff origin/<base>...HEAD --stat,并根据意图对比变更的文件。 -
检测:
- 范围蔓延: 变更的文件与声明的意图无关
- 缺少需求: 差异中未包含预期的工作
-
输出:
Scope Check: [CLEAN / DRIFT DETECTED / REQUIREMENTS MISSING] Intent: <1-line summary> Delivered: <1-line summary> -
这是信息提示,不会阻止流程。继续。
步骤 7:计划完成情况审计
派发为子 Agent。 搜索与当前分支相关的活动计划文件。
子 Agent 提示词:
你正在运行 ship-workflow 计划完成情况审计。基准分支为
<base>。
- 搜索计划文件:
find ~/.claude/plans ~/.gstack/projects -name "*.md" -mmin -1440 2>/dev/null | head -10- 如果某个计划引用了当前分支,则读取该计划并提取所有可执行项。
- 将每项与
git diff origin/<base>...HEAD交叉核对。- 分类为:DONE、PARTIAL、NOT DONE、CHANGED。
在最后一行输出 JSON:
{"total_items":N,"done":N,"changed":N,"deferred":N,"summary":"<markdown checklist>"}
父级处理:
- 如果
deferred > 0:通过 AskUserQuestion 呈现延期项。 - 将
summary嵌入 PR 正文。 - 如果未找到计划文件:跳过,并输出 "No plan file detected."
步骤 8:版本号递增(自动决定)
幂等性检查: 将 VERSION 与基分支进行比较。
BASE_VERSION=$(git show origin/<base>:VERSION 2>/dev/null || echo "0.0.0.0")
CURRENT_VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0.0")
echo "BASE: $BASE_VERSION HEAD: $CURRENT_VERSION"
if [ "$CURRENT_VERSION" != "$BASE_VERSION" ]; then echo "ALREADY_BUMPED"; fi
如果为 ALREADY_BUMPED,则跳过版本号递增,但读取当前版本号。否则:
-
读取
VERSION(4 位格式:MAJOR.MINOR.PATCH.MICRO) -
自动决定递增级别:
- 统计更改的行数(
git diff origin/<base>...HEAD --stat | tail -1) - MICRO(第 4 位):更改少于 50 行、细微调整、配置
- PATCH(第 3 位):更改 50 行及以上,且没有功能信号
- MINOR(第 2 位):检测到功能信号时询问(新路由、迁移、
feat/分支) - MAJOR(第 1 位):询问——仅适用于里程碑或破坏性变更
- 统计更改的行数(
-
计算新版本号并写入
VERSION。
步骤 9:CHANGELOG(自动生成)
-
阅读
CHANGELOG.md的标题,了解其格式。 -
列出所有提交:
git log <base>..HEAD --oneline -
阅读完整差异:
git diff <base>...HEAD -
按主题对提交分组(功能、修复、清理、基础设施)。
-
编写 CHANGELOG 条目:
- 章节:
### Added、### Changed、### Fixed、### Removed - 格式:
## [X.Y.Z.W] - YYYY-MM-DD - 开头说明用户现在可以做什么,而之前无法做到。
- 章节:
-
交叉核对:每个提交都必须对应至少一个项目符号条目。
步骤 10:提交(可二分的提交块)
-
分析差异,并将更改分组为逻辑提交。
-
提交顺序(先提交的排在前面):
- 基础设施:迁移、配置更改
- 领域与应用:实体、值对象、用例、服务(含测试)
- 基础设施实现:存储库、TypeORM 实体(含测试)
- API 层:控制器、DTO(含测试)
- VERSION + CHANGELOG:始终放在最后一次提交中
-
规则:
- 服务及其测试文件必须放在同一次提交中
- 每次提交都必须独立有效——不能有损坏的导入
- 如果总差异较小(少于 4 个文件、总计少于 50 行),可以只提交一次
-
编写提交消息:
- 使用 Skill 工具调用
/git-commit-format技能。 - 绝不包含
Co-Authored-By或任何 AI 署名尾注(遵循项目惯例)
- 使用 Skill 工具调用
步骤 11:验证关卡
铁律:没有最新的验证证据,绝不声称任务已完成。
推送前,如果代码在步骤 4-6 期间发生更改,则重新验证:
-
如果步骤 3 的测试运行后有任何代码更改,则重新运行测试:
pnpm run build && pnpm run lint && pnpm run test -
防止自我合理化:
- “现在应该能用了” → 运行它。
- “我之前已经测过了” → 之后代码有过更改。再次测试。
- “只是个微小改动” → 微小改动也会导致生产环境故障。
如果此处测试失败: 停止。不要推送。
步骤 12:推送
幂等性检查:
BRANCH_NAME=$(git branch --show-current)
git fetch origin "$BRANCH_NAME" 2>/dev/null
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse "origin/$BRANCH_NAME" 2>/dev/null || echo "none")
echo "LOCAL: $LOCAL REMOTE: $REMOTE"
[ "$LOCAL" = "$REMOTE" ] && echo "ALREADY_PUSHED" || echo "PUSH_NEEDED"
如果为 ALREADY_PUSHED,则跳过。否则:
git push -u origin "$BRANCH_NAME"
步骤 13:创建 PR
幂等性检查:
gh pr view --json url,number,state -q 'if .state == "OPEN" then "PR #\(.number): \(.url)" else "NO_PR" end' 2>/dev/null || echo "NO_PR"
如果已有未关闭的 PR:使用 gh pr edit --body "..." 更新 PR 正文。
如果不存在 PR:创建一个。
PR 正文应包含:
## Summary
<Summarize ALL changes. Group commits into logical sections. Every commit
must appear in at least one section.>
## Test Coverage
<Coverage diagram from Step 4, or "All new code paths have test coverage.">
## Pre-Landing Review
<Findings from Step 5, or "No issues found.">
## Scope Drift
<From Step 6, or omit if CLEAN>
## Plan Completion
<From Step 7, or "No plan file detected.">
## Test plan
- [x] TypeScript compilation passes (strict mode)
- [x] ESLint passes
- [x] All Jest tests pass (N tests)
- [x] No regressions in affected modules
创建 PR:
gh pr create --base <base> --title "<type>(<scope>): <summary>" --body "$(cat <<'EOF'
<PR body from above>
EOF
)"
输出 PR URL。
重要规则
- 绝不跳过测试。 如果测试失败,请停止。
- 绝不强制推送。 仅使用常规的
git push。 - 绝不询问无关紧要的确认(例如“准备好推送了吗?”、“创建 PR 吗?”)。
- 始终使用 VERSION 文件中的 4 位版本格式。
- CHANGELOG 中的日期格式:
YYYY-MM-DD - 拆分提交以便二分排查——每次提交对应一项逻辑更改。
- 没有最新的验证证据,绝不推送。 如果步骤 3 的测试后代码有更改,推送前必须重新运行测试。
- 提交消息中绝不包含 Co-Authored-By 尾注(遵循项目惯例)。
- 目标是:用户输入
/ship后,接下来看到的就是 PR URL。