mcpField
S
ship

ship

github.com/somnio-software开发846 次安装1 周前更新
代码仓库

关于 ship

ship 是一个 Claude Code 技能,用于自动执行代码发布流程,涵盖分支检查、测试、审查、版本与变更日志更新及 PR 步骤,面向从功能分支交付代码的开发者。

主要功能

  1. 01自动检查分支并合并基线
  2. 02运行构建、Lint 和测试
  3. 03审计测试覆盖率与计划完成情况
  4. 04发布前审查并检查范围偏移
  5. 05更新版本号与变更日志
  6. 06处理提交和 PR 流程

适用场景

  1. 01在功能分支上执行发布检查
  2. 02生成版本号和变更日志更新
  3. 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:

  1. gh pr view --json baseRefName -q .baseRefName — 如果成功,则使用该值
  2. gh repo view --json defaultBranchRef -q .defaultBranchRef.name — 如果成功,则使用该值

Git 原生回退方案:

  1. git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'
  2. 如果失败:git rev-parse --verify origin/main 2>/dev/null → 使用 main
  3. 如果失败:git rev-parse --verify origin/master 2>/dev/null → 使用 master

如果以上都失败,则回退到 main。

输出检测到的基准分支名称。在后续的每条命令中,凡是指令提到“基准分支”或 <base>,都替换为检测到的分支名称。


步骤 1:预检

  1. 检查当前分支。如果位于基准分支,中止:“你当前位于基准分支。请从功能分支执行 Ship。”

  2. 运行 git status(绝不要使用 -uall)。始终包含未提交的更改。

  3. 运行 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

全部完成后,读取输出文件并检查通过或失败。

如果任何测试失败: 不要立即停止。按照测试失败归属分类流程进行处理:

测试失败归属分类

对于每个失败的测试:

  1. 获取此分支上已更改的文件:

    git diff origin/<base>...HEAD --name-only
    
  2. 对失败进行分类:

    • 分支内,如果:失败的测试文件或其测试的代码在此分支上有修改,或你可以将失败追溯到分支差异中的某项更改。
    • 可能是预先存在的,如果:测试文件及其测试的代码在此分支上都没有修改,且失败与任何分支更改无关。
    • 无法确定时,默认归为分支内。
  3. 处理分支内的失败: 停止。 展示这些失败,不要继续。

  4. 处理预先存在的失败: 使用 AskUserQuestion:

    这些测试失败似乎是预先存在的(并非由你的分支更改导致): [列出每项失败,包括文件:行号和简要错误信息]

    A) 现在调查并修复 B) 跳过 — 仍然交付

如果全部通过: 静默继续 — 简要说明通过的数量。


第 4 步:测试覆盖率审计

使用 Agent 工具将此步骤分派给子 Agent。 子 Agent 将在全新的上下文窗口中运行覆盖率审计。

子 Agent 提示词: 将以下说明传递给子 Agent,并将 <base> 替换为相应值:

你正在为一个采用 4 层整洁架构(领域层、应用层、基础设施层、API 层)的 NestJS 单体仓库运行交付工作流测试覆盖率审计。运行 git diff <base>...HEAD 查看更改内容。不要提交或推送 — 仅报告结果。

  1. 追踪差异中更改的每条代码路径。 阅读每个已更改文件(完整文件,而不只是差异)。对于每个文件,追踪所有分支(if/else、switch、守卫、提前返回、错误路径)中的数据流。

  2. 根据现有测试检查每个分支。 对于每个已更改的函数/方法,搜索对应的 .spec.ts 文件。对测试质量评分:

    • ★★★ 测试了边缘情况和错误路径
    • ★★ 测试了正确行为,仅覆盖正常路径
    • ★ 冒烟测试/存在性检查/简单断言
  3. 输出 ASCII 覆盖率图:

    代码路径覆盖率
    ===========================
    [+] src/application/units/commands/sync-unit.handler.ts
        ├── [★★★ 已测试] 正常路径 — sync-unit.handler.spec.ts:42
        ├── [GAP]         单元不存在时出错 — 无测试
        └── [GAP]         部分更新场景 — 无测试
    
  4. 为未覆盖的路径生成测试(如果存在测试框架):

    • 阅读 2-3 个现有 .spec.ts 文件,严格遵循其约定
    • 生成单元测试。模拟外部依赖(TypeORM 存储库、外部 API)。
    • 运行每个测试。通过 → 保留。失败 → 修复一次。仍然失败 → 静默删除。

分析完成后,在最后一行输出一个 JSON 对象: {"coverage_pct":N,"gaps":N,"diagram":"<markdown diagram>","tests_added":["path",...]}

父级处理:

  1. 将最后一行解析为 JSON。
  2. 保存以用于 PR 正文中的 ## Test Coverage 部分。
  3. 输出:Coverage: {coverage_pct}%, {gaps} gaps. {tests_added.length} tests added.

如果子 Agent 失败: 回退为在当前上下文中运行审计。


第 5 步:上线前审查

审查差异,查找测试无法发现的结构性问题。

  1. 运行 git diff origin/<base> 获取完整差异。

  2. 分两轮应用此审查清单:

第 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) 文件:行号 — 描述

优先修复流程
  1. 将每项发现分类为 AUTO-FIX 或 ASK:

    • AUTO-FIX:移除死代码、移除未使用的导入、缺少 barrel 导出、明显的格式问题
    • ASK:任何会改变行为的内容、安全修复、架构决策
  2. 自动修复所有 AUTO-FIX 项。 每项修复输出一行: [AUTO-FIXED] [文件:行号] 问题 → 所做的修改

  3. **如果仍有 ASK 项,**通过一个 AskUserQuestion 一并提出:

    • 列出每项的严重性、问题和推荐修复方案
    • 每项提供选项:A) 修复 B) 跳过
  4. 所有修复完成后:

    • 如果应用了任何修复:提交已修复的文件(git add <files> && git commit -m "fix: pre-landing review fixes"),然后重新运行测试(步骤 3)。
    • 如果未应用任何修复:继续执行步骤 8。
  5. 输出摘要:Pre-Landing Review:N 个问题 — M 个自动修复,K 个待询问(J 个已修复,L 个已跳过)


步骤 6:范围偏移检测

检查:分支是否只构建了声明的内容,没有多做或少做?

  1. 读取提交消息(git log origin/<base>..HEAD --oneline)。

  2. 根据分支名称和提交识别声明的意图。

  3. 运行 git diff origin/<base>...HEAD --stat,并根据意图对比变更的文件。

  4. 检测:

    • 范围蔓延: 变更的文件与声明的意图无关
    • 缺少需求: 差异中未包含预期的工作
  5. 输出:

    Scope Check: [CLEAN / DRIFT DETECTED / REQUIREMENTS MISSING]
    Intent: <1-line summary>
    Delivered: <1-line summary>
    
  6. 这是信息提示,不会阻止流程。继续。


步骤 7:计划完成情况审计

派发为子 Agent。 搜索与当前分支相关的活动计划文件。

子 Agent 提示词:

你正在运行 ship-workflow 计划完成情况审计。基准分支为 <base>。

  1. 搜索计划文件:find ~/.claude/plans ~/.gstack/projects -name "*.md" -mmin -1440 2>/dev/null | head -10
  2. 如果某个计划引用了当前分支,则读取该计划并提取所有可执行项。
  3. 将每项与 git diff origin/<base>...HEAD 交叉核对。
  4. 分类为: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,则跳过版本号递增,但读取当前版本号。否则:

  1. 读取 VERSION(4 位格式:MAJOR.MINOR.PATCH.MICRO)

  2. 自动决定递增级别:

    • 统计更改的行数(git diff origin/<base>...HEAD --stat | tail -1)
    • MICRO(第 4 位):更改少于 50 行、细微调整、配置
    • PATCH(第 3 位):更改 50 行及以上,且没有功能信号
    • MINOR(第 2 位):检测到功能信号时询问(新路由、迁移、feat/ 分支)
    • MAJOR(第 1 位):询问——仅适用于里程碑或破坏性变更
  3. 计算新版本号并写入 VERSION。


步骤 9:CHANGELOG(自动生成)

  1. 阅读 CHANGELOG.md 的标题,了解其格式。

  2. 列出所有提交:git log <base>..HEAD --oneline

  3. 阅读完整差异:git diff <base>...HEAD

  4. 按主题对提交分组(功能、修复、清理、基础设施)。

  5. 编写 CHANGELOG 条目:

    • 章节:### Added、### Changed、### Fixed、### Removed
    • 格式:## [X.Y.Z.W] - YYYY-MM-DD
    • 开头说明用户现在可以做什么,而之前无法做到。
  6. 交叉核对:每个提交都必须对应至少一个项目符号条目。


步骤 10:提交(可二分的提交块)

  1. 分析差异,并将更改分组为逻辑提交。

  2. 提交顺序(先提交的排在前面):

    • 基础设施:迁移、配置更改
    • 领域与应用:实体、值对象、用例、服务(含测试)
    • 基础设施实现:存储库、TypeORM 实体(含测试)
    • API 层:控制器、DTO(含测试)
    • VERSION + CHANGELOG:始终放在最后一次提交中
  3. 规则:

    • 服务及其测试文件必须放在同一次提交中
    • 每次提交都必须独立有效——不能有损坏的导入
    • 如果总差异较小(少于 4 个文件、总计少于 50 行),可以只提交一次
  4. 编写提交消息:

    • 使用 Skill 工具调用 /git-commit-format 技能。
    • 绝不包含 Co-Authored-By 或任何 AI 署名尾注(遵循项目惯例)

步骤 11:验证关卡

铁律:没有最新的验证证据,绝不声称任务已完成。

推送前,如果代码在步骤 4-6 期间发生更改,则重新验证:

  1. 如果步骤 3 的测试运行后有任何代码更改,则重新运行测试:

    pnpm run build && pnpm run lint && pnpm run test
    
  2. 防止自我合理化:

    • “现在应该能用了” → 运行它。
    • “我之前已经测过了” → 之后代码有过更改。再次测试。
    • “只是个微小改动” → 微小改动也会导致生产环境故障。

如果此处测试失败: 停止。不要推送。


步骤 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。

评论

支持 Markdown