关于 squash-merge-and-clean-up
squash-merge-and-clean-up 是一项仅由人类明确点名启动的技能,用于合并当前会话的 PR,并清理该会话创建的工作树和分支。它将会话自身的清理与更新共享主检出目录分为两个审批关口。
主要功能
- 01检查 PR 状态、检查项、工作树和分支
- 02审阅并批准具体合并计划
- 03执行 squash 合并并通过 API 验证
- 04仅移除本会话创建的工作树和分支
- 05单独审批更新主检出目录
适用场景
- 01经明确批准后合并会话的 PR
- 02清理该会话的工作树和分支
- 03获批后更新共享检出目录
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mtngtools/ agents --skill squash-merge-and-clean-up安装后在 Claude Code 中输入/squash-merge-and-clean-up确认已加载。
详细介绍
squash-merge-and-clean-up
仅限人工操作。 只有当人类明确以名称要求时,才启动此流程。如果你是从另一个技能来到这里的,请停止,并在执行任何步骤前获取明确确认。
合并本会话创建的 PR 并清理本会话创建的内容:离开工作树,以 squash 方式合并,删除两个分支,并移除工作树。
始终限定于本会话。 同一仓库中还有其他 Agent 会话在并行运行,它们的工作树和分支也会出现在相同的列表中。只操作本会话创建的内容——本会话进入的工作树、本会话推送的分支、本会话打开的 PR。其他所有内容都是他人的进行中工作。
两道关卡,按所有权划分。 第一关涵盖本会话拥有的一切:合并,以及移除本会话自己的工作树和分支。第二关只涵盖一个会触及共享状态的步骤——更新主检出目录,其他所有会话都会读取它。
1. 检查
收集以下信息,并在制定计划时保留:
gh pr view <n> --json number,title,body,state,mergeable,mergeStateStatus,headRefName,baseRefName,closingIssuesReferencesgh pr checks <n>— 仅含文档的 PR 可能会显示没有检查项,这表示通过而非失败git worktree list— 记下本会话创建的条目;locked表示属于另一个会话git branch -vv—+前缀表示某个分支已在某个工作树中检出
先修改 PR 正文,再制定计划,不要事后修改。 合并会关闭关联的 issue,因此缺少关键词意味着这些 issue 不会随合并关闭,之后还得有人手动关闭。如果此 PR 应关闭 issue,但 closingIssuesReferences 为空或遗漏了某个 issue,就用 gh pr edit <n> --body 添加 Closes #{issue},每个 issue 单独一行;保留现有正文,并在末尾追加。从分支名称、提交记录或正文中的描述获取 issue 编号;不要猜编号,也不要关联此分支实际上不会关闭的 issue。如果这里没有要关闭的 issue,这是正常 PR——不要做任何修改。编辑后重新读取正文,并在计划中说明合并会关闭哪些 issue。
2. 第一关:输出计划,然后询问
计划通过两条途径传达,因为任何一条单独使用都可能失败。 工具调用前刚打印的文本可能不会显示——运行环境可能会吞掉它,导致问题出现时上方没有计划。而问题字段本身会显示为一段普通文本,没有列表格式,因此把计划直接塞进去会难以阅读——恰恰在人类最需要仔细阅读时如此。所以:将计划作为普通输出打印,使其出现在对话记录中,并且将同一计划作为每个选项的 preview,供问题界面显示在选项旁。无论运行环境采用哪条途径,提问时计划都会显示在屏幕上。
将步骤 3 到 5 的计划按编号列表输出,置于 PLAN: 标题下,并写明实际的 PR 编号、分支名称和工作树路径——绝不能用占位符:
PLAN:
1. Exit the worktree at `<path>`, returning to the primary checkout
2. Squash-merge <PR title> (#<n>) into `<base>` as `<commit subject>`, closing #<issue>
3. Confirm from the API that it merged, and that the branch's content landed
4. Remove the worktree at `<path>`
5. Delete `<branch>` locally and on origin
第 2 行的关闭语句须列出正文关联的每个 issue;如果没有关联任何 issue,则整句省略。
然后用 AskUserQuestion 提出一个问题,措辞如下:
执行预览中显示的 squash 合并计划?
选项为是,执行、否,到此为止和我没看到计划。请重新输出。 每个选项的 preview 都必须包含完整的 PLAN: 列表,逐字复制上方的输出——不得重新输入、缩短或概括:同一份文本,通过两条途径传达,确保两处内容完全一致。除 preview 外,问题文本、选项标签和选项描述中都不得包含计划内容——再放一份更短的副本会导致两处内容不一致。
选择我没看到计划时,说明两条途径都没有显示计划:重新打印完整的 PLAN: 列表,然后就此结束本轮,让计划作为最终消息输出,之后不再调用任何工具——这是运行环境始终会显示内容的唯一位置。只有在人类回复后,才再次提出相同的问题。只有得到肯定答复后才能继续。
3. 先离开工作树
在合并之前退出工作树,并将会话切回主检出目录。
这个步骤的核心就在于顺序。 在工作树内运行 gh pr merge 会先在服务器端完成合并,然后在本地失败,报错 fatal: '<base>' is already used by worktree at …,因为它的本地步骤会尝试检出主检出目录正在使用的基础分支。即使合并已成功,它也会以非零状态码退出,而且不会运行 --delete-branch。重试看起来是显而易见的解决办法,但这是错的——PR 已经合并。
通过将工作目录切回主检出目录来离开工作树——使用你的执行环境提供的任何机制——并且保留磁盘上的工作树:只有在第 5 步确认其中的内容已合并后,才移除它,不能提前。如果你的执行环境无法切换目录,请使用显式路径运行合并(git -C <primary-checkout> … 和 gh -R <owner/name>),不要在工作树内运行。如果你使用 Claude Code,对应操作是 ExitWorktree,参数为 action: "keep"。
4. 压缩合并,然后验证
gh pr merge <n> --squash --subject "<type>(<scope>): <subject> (#<issue>) (#<pr>)"
提交标题遵循仓库约定:常规提交标题、问题编号,然后是 PR 编号。
通过 API 确认,而不是看退出码:
gh pr view <n> --json state,mergedAt,mergeCommit
state: MERGED 且包含 mergeCommit.oid 才算完成。只要显示已合并,无论命令以何种状态退出,合并就已完成。
5. 移除此会话创建的内容
按顺序明确执行,并满足每项的前置条件:
- 确认没有内容丢失——先运行
git fetch origin --prune,然后运行git diff --stat <worktree-HEAD> origin/<base>。空差异证明压缩合并已包含这些内容。移除工作树时,本地提交会被丢弃,因此要在移除之前检查,而不是之后。 - 移除工作树——运行
git worktree remove <path>,然后运行git worktree prune。跳过任何标记为locked或并非由此会话创建的条目。即使工作树干净,只要子模块已检出内容,普通移除操作也会失败:先确认git -C <path> status显示子模块内没有修改,再使用--force移除。 - 删除本地分支——运行
git branch -D <branch>。第 2 步会释放该分支:在工作树中检出的分支无法删除。 - 删除远程分支——运行
git push origin --delete <branch>。手动执行,因为合并时--delete-branch通常可能并未运行。
6. 第二道关卡:让主工作区与最新状态同步
以上所有操作只影响了本次会话自身的状态。此步骤会移动所有并发会话共用的工作区,因此即使在第一道关卡已得到肯定答复,也要单独询问。
形式与第一道关卡相同,包括两种方式:计划既要作为普通输出打印,也要原样作为每个选项的 preview。打印工作区当前的情况——所在分支、是否干净、落后于 origin/<base> 多远,以及移动工作区时需要处理的任何未提交内容。
子模块:过时不等于脏。 在含有子模块的仓库中,git status 里的 M <submodule> 有两种不同含义,计划必须说明是哪一种。git submodule status 可以区分:+ 前缀表示工作区所在的提交与分支记录的提交不同——这是一个过时指针,因为移动分支不会移动子模块工作区而遗留下来;而子模块内部存在已修改或未跟踪文件(git -C <submodule> status)则表示真实的工作。过时指针说明工作区落后了,因此在此步骤的计划中应包含同步操作。子模块内部的真实工作属于其他会话或用户:指出它,保留原状,并将该子模块排除在同步之外。
PLAN:
- Primary checkout is on `<branch>`, <clean | N uncommitted files>, <N> behind `origin/<base>`
- Fast-forward it to `origin/<base>` (`<sha>`) with `git merge --ff-only`
- Sync stale submodule checkouts to the recorded pointers with `git submodule update --init`
只有仓库含有子模块时才会出现同步命令;它能让此步骤在这类仓库中完整完成:快进会更新分支记录的指针,并让每个子模块工作区保持原样,因此只有两步都完成后,工作区才算与最新状态同步。如果某个子模块必须保持原样,请按路径同步(git submodule update --init -- <path>)。
然后只问一个问题:
按预览所示,让主工作区与最新状态同步?
选项为是,执行和否,保持原样,每个选项都要在其 preview 中包含完整的 PLAN: 列表。选择是时,仅当工作区干净(过时的子模块指针也算干净)且领先数为 0,才运行 git merge --ff-only origin/<base>,然后执行计划中的子模块同步。选择否是正常结束,不代表失败。
最后说明移除了什么,以及还有什么遗留。你没有创建的未跟踪文件,或处于 locked 状态的工作区,都属于其他会话或用户:指出它并保留原样。