mcpField
S
splitting-pr

splitting-pr

github.com/satanlittlehelper开发041 次安装1 天前更新
代码仓库

关于 splitting-pr

splitting-pr 面向需要拆分大型未提交差异的开发者,将其整理为便于审查的堆叠式 PR 链。它会跨会话记录文件分组和进度,并通过校验避免遗漏文件。

主要功能

  1. 01将差异拆分为堆叠式 PR
  2. 02用状态文件跟踪进度
  3. 03跨会话续做未完成拆分
  4. 04按路径和扩展名分类文件
  5. 05在备份分支保留原始差异

适用场景

  1. 01将大型分支差异拆成易审查的 PR
  2. 02暂停后继续多 PR 拆分
  3. 03分组时保持紧密耦合文件在一起

安装

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

$npx skills add https://github.com/satanlittlehelper/ai-dev-kit --skill splitting-pr

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

附带文件references/block-cycle.mdreferences/resuming.md

详细介绍

拆分 PR

概述

同一分支上大量尚未提交的差异会产生一个庞大且难以审查的 PR。本技能会将其拆分为一串堆叠的小型 PR:每个 PR 都小到便于审查(不超过 15 个业务逻辑文件,资源和文档除外),并且每个 PR 都会记录在状态文件中,因此可以跨会话暂停和继续拆分,而不会弄错文件的归属。本技能不会自行执行构建/lint/测试步骤——验证由项目自身的推送前钩子在执行 git push 时完成(references/block-cycle.md 的第 6 步);如果该钩子拒绝推送,就说明代码块的拆分有误,而不是本技能会预先主动检查的内容。

本技能仅用于事后拆分——只有差异存在后才会规划拆分。如果调用本技能时不存在大量尚未提交的差异,请说明这一点并停止;目前没有内容需要拆分。

本技能是入口;它负责存储、命名和状态文件约定(见下文,这些约定由两个参考文件共用),并将流程引导至适用的处理方式:

情况阅读
当前分支尚无 .claude/pr-split/<slug>-state.json,或者用户要开始全新的拆分从 references/block-cycle.md 中的“仅首次调用:先备份,再建立基线”部分开始阅读
当前分支已有 .claude/pr-split/<slug>-state.json,且仍有未完成的代码块(新会话,或恢复之前的工作)references/resuming.md

只阅读当前情况所需的参考文件——两者都假设本文件中的存储/架构/分类约定是共用背景,因此无需在每个参考文件中重新推导这些约定。

审批关卡

在 references/block-cycle.md 中每个需要用户批准、拒绝或编辑的环节(代码块的文件分组、提交消息、PR 标题/正文、Plannotator 审查结果),都应通过 AskUserQuestion 提出并提供具体选项(例如“批准”/“更改分组”、“批准”/“编辑消息”),绝不能只在聊天中发文字并等待自由回复。这样用户只需点击即可批准,无需输入任何内容。唯一例外是真正没有固定选项的资料输入——例如 Forgejo 和 gh 都不可用时,粘贴手动创建的 PR 的 URL(references/block-cycle.md 第 6 步)——这种情况应保留为普通聊天提示,因为没有可供选择的选项。

文件分类

采用基于扩展名/路径的启发式判断,绝不根据文件内容猜测:

  • 资源/文档(不计入 15 个文件的上限): *.md、*.mdx、README*、CHANGELOG*、图片、字体和其他静态文件(.svg .png .jpg .jpeg .gif .webp .ico .woff .woff2 .ttf .eot .pdf)、区域设置文件(.po、.pot)。
  • 业务逻辑(计入上限): 其他所有文件。任何语言的源文件,以及配置/测试文件(.json、.yaml、.yml 等)始终计入业务逻辑,不得根据对其语义用途的猜测重新分类。

分组优先级

将文件分组到代码块中,是本技能根据阅读差异后作出的判断(导入、符号使用、领域关联),并非僵化的静态算法。**始终优先让相互关联的文件保持在一起,而不是遵守 15 个文件的上限:**如果紧密耦合的一组文件无法拆分,否则其中一半会引用另一半尚未引入的内容,那么该代码块可以超过上限。发生这种情况时,应在向用户展示的代码块提案中明确指出,并在继续之前,就这一具体情况征得用户确认。这里不会运行本地构建/lint/测试来实证确认——真正的检查由项目自身的推送前钩子在执行 git push 时完成(references/block-cycle.md 第 6 步);如果推送遭拒,就应重新分组。

存储

  • 状态文件:.claude/pr-split/<slug>-state.json,其中 <slug> 是原始源分支的名称 (即拆分开始时所在的分支),并将其中的 / 替换为 -。
  • 未跟踪。 绝不执行 git add,绝不提交,也绝不会包含在任何区块的差异或任何 PR 中。 它只存在于工作树中——在同一检出环境中切换分支时会保留 (除非未跟踪文件会发生冲突,否则 git checkout <branch> 不会触及它),但它不会被推送,也不会备份。
  • 拆分完成后自动删除——参见下文的“完成”。这是进行中的拆分所用的工作流 状态,并非持久记录;仓库中没有其他内容依赖它在最后一个区块的 PR 创建后继续存在。
  • 备份分支: <sourceBranch>_backup——一个普通提交,包含完整的原始差异,在任何其他操作触及它之前创建(references/block-cycle.md 步骤 0)。如果拆分后续步骤出错,这是防止数据丢失的保障;与状态文件不同,此技能自身的自动化流程绝不会删除它——清理还是保留,由用户自行决定,与拆分本身何时完成无关。

完成

当 blocks 中的每个区块都达到 status: "pr-created",且 unassignedFiles 为空时,拆分即告完成。此时,在工作流的最后一步删除 .claude/pr-split/<slug>-state.json——拆分的任务是在执行期间跟踪进度;一旦每个区块都有了 PR,state.json 就没有需要继续跟踪的内容了。

删除之前,向用户打印最终摘要(从 state.json 读取,因此必须先读取该文件),以便可以原样转发整条链供审核:

  1. 按区块顺序列出所有 PR,每项格式为 N. <prTitle> — <prUrl>。
  2. 列出堆栈的表格:| # | PR | Head branch | Base branch |,每个区块一行,其中 branch 为头部分支,baseBranch 为基部分支(区块 1 的目标是原始基分支;之后的每个区块都以 前一个区块的分支为目标)。

然后告知用户拆分已完成。 这与备份分支 <sourceBranch>_backup 无关(见上文“存储”);无论是否完成,此技能的自动化流程都绝不会删除该分支。

编辑状态文件

.claude/pr-split/<slug>-state.json 是纯文本文件——使用 Read/Edit 工具,像处理仓库中的其他文件一样读取和编辑,绝不要通过 shell 单行命令(jq、 python -c、node -e 等)操作。该文件很小,因此此技能中的每次写入(追加区块、切换 status,或在 unassignedFiles 与某个区块的 files 之间移动少量路径)都可以直接进行清晰可见的文本编辑——无需使用 JSON 处理工具。

状态文件架构

{
  "sourceBranch": "feature-x",
  "baseBranch": "main",
  "createdAt": "2026-09-08T12:00:00Z",
  "updatedAt": "2026-09-08T12:00:00Z",
  "totalFiles": { "logic": 42, "assets": 7 },
  "originalFiles": ["src/a.ts", "src/z.ts", "README.md"],
  "blocks": [
    {
      "index": 1,
      "branch": "feature-x",
      "baseBranch": "main",
      "files": { "logic": ["src/a.ts"], "assets": ["README.md"] },
      "status": "approved",
      "commitSha": null,
      "prUrl": null,
      "prTitle": null
    }
  ],
  "unassignedFiles": ["src/z.ts"]
}
  • originalFiles 是原始差异的冻结完整文件列表,在拆分开始时(任何区块尚不存在之前)捕获一次,此后绝不重新推导。它是“无文件丢失”不变量所依据的持久事实来源,详见下文。区块 1 的 branch 就是源分支本身(sourceBranch),而非重命名或新建的分支;这与下文的分支与 PR 策略一致。
  • status 按以下顺序推进,绝不倒退:planned → approved → committed → review-passed → pr-created。没有自动设置的 closed 状态,详见上文的完成部分。提议时(block-cycle.md 步骤 1,在用户批准之前),区块写为 "planned";用户批准后(步骤 3)则变为 "approved"。关于这对 "planned" 区块的文件意味着什么,见下方的不变量例外说明。
  • unassignedFiles 列出原始差异中尚未归入任何区块的所有文件。"planned" 区块拟议的文件仍列在这里,详见下文。
  • 无文件丢失不变量:每次写入此文件时都要以机械方式检查(绝不能只靠目视检查):所有 status 为 "approved" 或更后状态的区块,其 files.logic 和 files.assets 的并集,加上 unassignedFiles,必须与 originalFiles 完全一致(不能有文件缺失,也不能有文件重复归属多个区块)。例外:status: "planned" 的区块不计入该并集;其拟议文件在获批前只通过 unassignedFiles 计入。用户批准后,block-cycle.md 步骤 3 会在同一次写入中将这些文件从 unassignedFiles 移除,从而确保不变量在每个步骤都成立。originalFiles 在拆分开始时通过一条支持单次重命名和路径中的空格的命令捕获一次(git status --porcelain=v1 -z,使用空字符分隔,并按相应方式解析,而不是会破坏重命名信息及含空格路径的 awk '{print $2}'),此后绝不从工作树重新推导,因为已提交的文件不再显示为未提交更改,否则会在检查中悄然消失。如果并集(按上述例外处理)不匹配——例如文件出现在两个区块中,或者 originalFiles 中的文件既不属于任何区块,也不在 unassignedFiles 中——则停止操作,并在进行其他操作前向用户指出不匹配之处。绝不能为了满足不变量而悄悄丢弃或重复归入文件。

分支与 PR 策略

  • 当前分支成为区块 1,不重命名。区块 1 的提交直接落在该分支上;其 PR 的目标分支是该分支通常对应的目标分支(在状态文件中记录为 baseBranch;如果无法从现有 PR 或跟踪分支判断,就询问用户)。
  • 区块 N+1 从区块 N 的分支创建,且必须在区块 N 自身的提交已存在于该分支上之后创建,分支名为 <sourceBranch>-<N+1>(例如 feature-x-2、feature-x-3 等)。其 PR 的基础分支是区块 N 的分支;这是一个堆叠链,而不是所有分支都以 main 为基础。已合并区块的 PR 基础分支是否改回 main 由用户决定,此处不自动处理。
  • 创建 PR 时按以下顺序匹配,匹配到第一项即采用:(1) 环境中已设置 FORGEJO_TOKEN → 通过 Forgejo API 创建 PR(POST /api/v1/repos/<owner>/<repo>/pulls,主机和仓库根据 origin URL 推导);(2) 已安装 gh 且远程仓库位于 GitHub → 执行 gh pr create;(3) 否则推送分支,并请用户手动创建 PR,然后等待用户发回 PR URL,再记录该 URL 并继续操作——绝不能悄悄跳过创建 PR。详情见 references/block-cycle.md 步骤 6。

常见错误

错误修正
将 15 个文件的上限视为绝对限制,为了遵守上限而拆开紧密耦合的文件耦合优先——该代码块可以超出上限,标记出来并获得明确确认
将 .json/.yaml 配置文件归类为“并非真正的逻辑”始终视为业务逻辑——只按扩展名分类,绝不根据内容分类
提交或 git add .claude/pr-split/<slug>-state.json始终保持未跟踪状态
提交代码块前运行本地构建、lint 或测试步骤不属于此技能的范围——项目自身的 pre-push hook 负责验证,在 git push 时运行
将每个代码块的 PR 都基于 main只有代码块 1 以原始基准分支为目标;后续每个代码块的 PR 都基于前一个代码块的分支
文件无遗漏不变量不成立时仍悄然继续停止并指出不匹配——绝不自动丢弃或重复文件来强行平衡
未创建备份分支就开始拆分,或在工作流中删除 <sourceBranch>_backup始终先运行 references/block-cycle.md 第 0 步,此后此技能自身的自动化流程绝不触碰备份分支
最后一个代码块达到 pr-created 后仍将 .claude/pr-split/<slug>-state.json 留在磁盘上在 unassignedFiles 为空后,将其作为最后一步删除——参见“完成”

评论

支持 Markdown