Issue 分组与合并 PR 判断
本文档为 SKILL.md §3「不变量与启动门禁」的 Level 2 参考文档。 读取时机:面对一批 Issue / 任务卡、拿不准该一个一个 PR 还是打包成一个 PR 时。 核心命题:不要默认「一个 Issue 一个 worker 一个 PR」。本 Skill 之前的「先分组」只覆盖了依赖链,本文补齐另外两个维度——同根因合并与独立并行——并区分本地 task 卡和云端 GitHub Issue 两类任务源。
1. 为什么要分组
「一个 Issue 一个 PR」是一对一映射,它的问题不是慢,而是机械:
- 同根因引发的多个症状,被拆成 N 个 PR,每个只修半步,reviewer 反复切换上下文。
- 同一块代码来回改 N 次,留下「修了 A 留下 B」的隐患。
- worktree / 分支 / CI / 收口开销随 PR 数线性增长。
但合并也不是越多越好。硬塞不同类型(一个 bugfix + 一个 feat + 一个重构)、或把大改动打包,会让 PR 主题模糊、review 困难、回滚牵连。
分组的本质:识别任务之间的真实关系,再决定 worker 数、worktree 数、PR 数。下面三个维度是判断骨架。
2. 三维度判断骨架
任何两个任务之间,先逐维度判断,再综合定组。
| 维度 | 关系特征 | 典型症状 | 分组动作 |
|---|---|---|---|
| ① 同根因合并 | 同一根因引发;改动集中在同一模块/同一批文件;每个都是小修 | 同模块两个 bug、同一份导出模板的多处样式问题 | 同组顺序执行,共用一个 worktree,合并到一个 PR(Closes #xx, #yy) |
| ② 依赖链顺序 | A 做完 B 才能做;B 的验收依赖 A 的产物 | 重构 → 新功能;schema 变更 → 用到该 schema 的逻辑 | 同组但分步,一个 worker 顺序推进,A commit 后再 B |
| ③ 独立并行 | 文件范围正交、验收标准独立、无共享契约/锁文件冲突 | 不同模块的两个独立 bug | 各自 worktree + worker + PR,放同一 Wave |
2.1 维度①「同根因合并」的触发信号
满足越多条,合并越优:
- 同模块:都发生在标题行、都在导出模块、都在同一组件。
- 根因相近:同一个函数、同一份配置、同一套机制的两个表现(如「输入英文生成多余
****」和「删除时漂移」都源于 heading 节点的 IR/Selection 处理)。 - 改动位置重叠:改的是同一批文件、同一段代码区域。
- 都是小修:每个单独看都只有几行到几十行 diff。
- 主题一致:都是 bugfix、或都是同一 feature 的细化。
- 互相佐证:修 A 时能顺带验证 B,或 A 的根因定位能直接复用到 B。
2.2 维度③「独立并行」的前置条件(必须全部满足才拆)
- 文件范围清晰且不重叠(
allowed/forbidden files明确)。 - 无共享迁移 / 锁文件 / schema / 全局布局 / DEC 编号 race。
- 验收标准独立:一个的失败不拖累另一个的合并。
任一条不满足 → 退回维度②(依赖链顺序)或维度①(合并)。
3. 软阈值(经验值,不是硬规则)
下列阈值来自实战,用来快速决策,不替代 PM 判断。边界情况按 §4 决策树走。
| 判断对象 | 软阈值 | 倾向 |
|---|---|---|
| 同组合并后的总 diff 行数 | < ~300 行 | 优先打包成一个 PR |
| 同组合并后的总 diff 行数 | > ~500 行 | 重新评估,大概率拆 |
| 单个任务本身改动 | 单个就 > ~200 行 | 单独成 PR,不打包 |
| 组内任务数 | 2–3 个 | 合并的最佳区间 |
| 组内任务数 | ≥ 4 个 | 拆成多组,每组 2–3 个 |
| 跨模块 | 跨 2 个以上模块 | 拆开 |
为什么是软阈值:300 行只读改动的可读性,远高于 300 行复杂逻辑改动;同一个 PR 里的紧密耦合改动,也比同模块但松散的改动更值得合并。阈值是「到这里要停下来想一想」,不是「到这里就拆/合」。
4. 决策树
拿到一批 Issue(N ≥ 2)
│
├─ 先按【模块 / 根因】粗分组:标签、body 里的复现路径、最近 commit 涉及的文件
│
└─ 对每个粗分组,逐组判断:
│
├─ 组内是否有依赖链(A→B)?
│ 是 → 维度②:一个 worker 顺序推进,可合一个 PR 或按依赖拆 PR
│
├─ 组内文件范围是否重叠 / 根因是否相近?
│ 是 → 维度①候选:合并到一个 worker + 一个 PR
│ └─ 合并后 diff 是否 < ~300 行 且 组内 ≤ 3 个?
│ 是 → ✅ 打包合并(用 templates/issue-batch-pr.md)
│ 否 → 拆成多个小合并组
│
└─ 文件范围是否正交、验收独立、无共享冲突?
是 → 维度③:各自 worktree + worker + PR,同一 Wave
否 → 退回维度① 或 ②拿不准时的默认动作:分开。合并的好处有限,硬合的代价(review 难、回滚牵连)却可能很大。「可合可不合」的边界,倾向于拆。
5. 任务源:本地 task 卡 vs 云端 GitHub Issue
本 Skill 的任务源模型(见 references/05 §4)原本偏本地结构化任务。实际项目里任务源有两类,分组前要先用不同方式把「任务的真实关系」挖出来:
| 维度 | 本地 task 卡(.agents/tasks.md 等) |
云端 GitHub Issue |
|---|---|---|
| 结构化程度 | 高,字段齐全(owner / depends_on / phase / risk_level) | 低,自由文本 + 可选 labels |
| 谁提交 | 通常是自己/团队,写法一致 | 可能是他人,描述风格、复现深度、术语都不统一 |
| 依赖关系 | 字段直接声明(depends_on) |
需要从 body 推断,常缺失 |
| 重复/重叠 | 少,维护时一般会去重 | 常见,同一 bug 不同人各报一个 |
| 分组前必做 | 直接读字段 | 先 gh issue list/view 读 body + labels + 看最近 commits |
5.1 云端 Issue 分组 SOP
对一批云端 Issue 做分组判断时,按这个顺序:
- 拉清单:
gh issue list --state open --limit 50 \ --json number,title,labels,createdAt gh issue list --state closed --limit 20 \ --json number,title,closedAt # 看历史有没有本可合并的 - 读关键 Issue 正文(粗筛后只读疑似相关的几个):
gh issue view <N> --json title,body,labels - 粗分组:按 labels、body 里提到的模块(编辑器 / 导出 / 更新 / 表格……)和复现路径归类。labels 不全时以 body 复现路径为准。
- 查最近 commits 确认根因方向:
git log --oneline -20 # 看最近改了哪些模块、哪个文件高频出现 - 逐组走 §4 决策树,输出「建议合并组」清单。
- 去重:同一 bug 的多个 Issue 报告,合并后用一个 PR 关闭多个 Issue(
Closes #xx, #yy),并在 PR 里说明它们是同一问题的不同表现。
5.2 云端 Issue 的两个坑
- labels 不可靠:很多人提 Issue 不打 label,或打得不对。以 body 复现路径和涉及的 UI 路径为准,不要只靠 labels 分组。
- 他人描述的术语≠你的术语:用户写「导出 Word 变颜色」,你代码里是「md2word 内置模板主题色继承」。分组时做术语映射,别因为用词不同就漏判同根因。
6. 反模式(不要这样做)
| 反模式 | 后果 | 正确做法 |
|---|---|---|
| 默认一对一:不管三七二十一一 Issue 一 PR | 同根因拆成 N 个半成品,上下文反复切换 | 先分组,同根因优先合并 |
| 硬塞不同类型:一个 PR 里混 bugfix + feat + refactor | 主题模糊、回滚困难、review 评级难 | 同类型才合并,类型不同必拆 |
| 为凑数合并:把它们合一起只是因为「正好都在做」 | review 难、任一不通过拖累全部 | 只在有真实根因/模块关联时合并 |
| 把大改动打包:单个就 > ~300 行还硬塞进合并 PR | PR 难审、风险叠加 | 大改动单独 PR,只打包小修 |
| 跨模块强合:编辑器 bug + 导出 bug 硬合(仅因「都改了渲染」) | 主题失焦、回滚牵连无关模块 | 不同模块默认拆,除非根因确实同一处 |
| 跨工具混并:把需要不同 backend/额度的任务塞一个 worker | 失去并行价值、额度错配 | 并行维度(③)才拆 worker,合并维度(①)保持同 worker |
7. 与 SKILL.md 其他章节的关系
- §3 标准流程第 2 步:本文是它的展开。三维度对应 SKILL.md 里的一句话触发,本文给出判断标准和决策树。
- §3.1 Wave-Based Orchestration:维度③「独立并行」的任务才进同一 Wave;维度①②的任务不占 Wave 的并行槽(它们是同一个 worker)。
- §3.3 Project Config /
references/05§4:任务源的「项目配置 / 本地任务文件」模型,本文 §5 补充了云端 Issue 这一额外任务源形态。 templates/issue-batch-pr.md:维度①「合并到一个 PR」时的 PR 描述模板。
8. 示例(Folia 项目实测)
背景:某次审查发现 3 个 open Issue。
#75标题输入英文生成多余****(编辑器)#76标题行删除/方向键时光标漂移(编辑器)#78内置 Word 模板导出非预期颜色(导出)
分组判断:
| 组 | Issue | 维度 | 理由 | 动作 |
|---|---|---|---|---|
| A | #75 + #76 | ① 同根因合并 | 同发生在标题行 WYSIWYG;都涉及 heading 节点 IR/Selection;上一条相关 commit(收窄 sanitize IR DOM + 文本偏移快照恢复 Selection)正是这块;均为小修 | 一个 worktree、一个 worker、一个 PR,Closes #75, #76 |
| B | #78 | 单独 | 纯导出模块 DOCX 样式问题,与 A 组正交 | 独立 worktree + PR |
PR 标题示例:
- 组 A:
fix(editor): 标题行内联标记与光标漂移修复 (ISS-75/76) - 组 B:
fix(export): 内置 Word 模板导出颜色污染 (ISS-78)
历史回顾:同期已关闭的 #67(粘贴标题格式异常)+ #69(删除选中未生效)属于「可合可不合」边界——都涉编辑器 IR,但 #67 偏粘贴路径、#69 偏 sanitize IR 触发条件,根因有差异。当时分两个 PR 是合理的,印证了「拿不准就分开」的默认动作。