AI生成1700行PR后,为什么要改成Stacked PR

Coding Agent 最大的生产力问题,正在从“写不出代码”变成“写得太快,人看不过来”。

GitHub 最近给了一个非常典型的例子:让 Agent 给购物助手增加产品搜索,结果一个任务很自然地膨胀成一个 1700+ 行的大 PR,里面同时包含数据模型、Seed Data、API Route、校验、前端接线、UI、空状态和错误状态。

这不是 Agent 特有的坏习惯。人类也会写巨型 PR。

区别在于 Agent 可以在几分钟内把这种问题放大很多倍。

GitHub 给出的解法是 Stacked Pull Requests:把一个功能拆成有依赖顺序的小 PR,每层只负责一个 concern,底层先审,依赖层依次叠上去。更重要的是,GitHub 现在已经把这个流程做进 gh stack CLI,并提供 Skill 让 Coding Agent 学会怎么创建和维护 Stack。

真正值得研究的是:如何让 Agent 的“任务分解”直接映射成可 Review 的代码交付结构。

1700 行 PR 的问题不只是“太长”

一份大 PR 同时改:

Data Model
API
Business Logic
UI
Test

Review 时至少有四个问题。

不同 Reviewer 被迫看不属于自己的内容

数据库 Owner 只关心:

Schema
Index
Migration

UI Owner 只关心:

Interaction
State
Accessibility

一个大 PR 把他们绑在一起。

反馈无法局部合并

如果底层数据模型错了,上层 API 和 UI 都建立在错误前提上。

大 PR 往往要整包返工。

CI 失败难定位

1700 行改动后测试挂了,很难立刻判断:

数据层?
API?
UI?

Agent 很容易在一个大 Diff 里藏掉错误

不是故意“藏”,而是人类 Reviewer 的注意力有限。

Diff 越大,审查深度通常越差。

GitHub 示例的四层 Stack 很值得直接抄

产品搜索被拆成:

L1 feat/catalog-data
   typed catalog + seed + validation + data access

L2 feat/search-api
   /api/products/search

L3 feat/chat-grounding
   chat calls API and uses real product data

L4 feat/grounded-ui
   citation cards + UI state

依赖关系:

main
↓
L1 data
↓
L2 API
↓
L3 grounding
↓
L4 UI

这个结构的价值在于:

架构依赖
=
Review 顺序

不要按“文件数量”拆 PR

差的分解:

PR1:前 10 个文件
PR2:后 10 个文件

好的分解:

每个 PR 一个独立 Concern

我会用四个判断条件:

Single Responsibility
Independent Reviewability
Dependency Order
Rollback Boundary

一个 PR 如果必须同时解释三个独立业务概念,通常还可以继续拆。

安装 Stacked PR CLI

GitHub 当前示例使用:

gh extension install github/gh-stack

让 Agent 学会 Stack 工作流:

gh skill install github/gh-stack

或者:

npx skills add github/gh-stack

这里一个非常有意思的变化是:

Git Workflow 本身
开始变成 Agent Skill

以后代码 Agent 不只是会 git commit,还需要理解团队交付策略。

Stack Base 为什么必须明确

Stack 不是随便一串 Branch。

要先确定:

stack base = main

CI 和 Merge Rule 都围绕 Base 评估。

假设:

L2 depends on L1

L2 自己当然不能在纯 main 上通过编译。

所以 CI 需要理解:

这个 PR 的有效基础是谁?

否则会出现大量假失败。

Agent Planner 应该直接输出 Stack Plan

普通 Planner:

{
  "steps": [
    "add catalog",
    "add api",
    "update chat",
    "build ui"
  ]
}

更实用:

{
  "stack": [
    {
      "layer": 1,
      "branch": "feat/catalog-data",
      "scope": "catalog foundation",
      "depends_on": "main",
      "reviewer_role": "data-owner"
    },
    {
      "layer": 2,
      "branch": "feat/search-api",
      "scope": "search endpoint",
      "depends_on": "feat/catalog-data",
      "reviewer_role": "backend-owner"
    }
  ]
}

Planning 结果直接成为 Delivery Graph。

我会给每层加 Change Budget

例如:

layers:
  max_files: 12
  max_changed_lines: 500
  max_new_dependencies: 2

超过阈值:

必须重新分解

这不是绝对规则,但对 Agent 特别有价值。

因为模型天然容易把“顺手优化”一起塞进当前任务。

Scope Creep 是 Coding Agent 最常见的隐性问题

任务:

增加产品搜索

Agent 顺手:

重构 Product 类型
换 HTTP Client
调整按钮组件
重命名 CSS
修另外两个 lint

每一项单独看都合理。

合在一起 Review 非常痛苦。

所以每层 Branch 应有 Scope Contract:

scope:
  include:
    - src/catalog/**
    - tests/catalog/**
  exclude:
    - src/auth/**
    - infra/**

Diff 超边界直接 Gate。

PR Description 不应该让 Agent写一大段泛泛说明

更有用的模板:

Goal

What changed

What did not change

Depends on

Verification

Risk

Reviewer focus

例如:

Reviewer focus:
- schema shape
- duplicate SKU handling
- seed validation

Reviewer 一打开就知道该看哪里。

每层 CI 要验证“层自己的 Contract”

L1 Data:

Schema Test
Validation Test
Seed Test

L2 API:

Contract Test
Input Validation
Error Mapping

L4 UI:

Component Test
Accessibility
Visual State

不要所有 PR 都只跑一个巨大 Test Suite,然后只给 Green/Red。

Agent 生成 Stack 时最容易遇到 Branch Drift

假设:

L1 Review 后修改

L2、L3、L4 都依赖旧 L1。

这时候要:

Restack

手工维护很痛苦。

Stack 工具的价值就在于自动同步依赖 Branch。

但依然要注意:

Rebase 后测试必须重跑

不能因为上一次 L3 是 Green,就继续沿用。

Merge 顺序必须从底到顶

L1
→ L2
→ L3
→ L4

如果上层先合:

依赖不存在

所以 Merge Queue 必须理解 Stack DAG。

一个简单状态机:

WAITING_DEPENDENCY
READY_FOR_REVIEW
CHANGES_REQUESTED
APPROVED
READY_TO_MERGE
MERGED

不要让 Agent 自动合并整个 Stack

即使所有 CI 都绿。

更合理:

低风险层:Auto Merge 可选
高风险层:Human Approval

例如:

UI Copy:LOW
Read-only API:MEDIUM
DB Migration:HIGH
Permission Change:CRITICAL

Stack 只是 Review 结构,不会自动解决权限风险。

Stacked PR 还能帮助多 Agent 分工

GitHub 示例里不同层可以由不同 Agent 负责:

Data Modeler Agent
Backend Agent
Frontend Agent

重点是它们不是同时乱改同一个 Working Tree。

而是:

每个 Agent 对应明确 Layer

这能降低多 Agent 代码冲突。

但要避免“一个 Layer 一个 Agent”变成机械规则

如果两个 Layer 都改核心类型定义,强行交给不同 Agent 可能增加上下文交接成本。

更合理的分配依据:

Ownership
Domain Knowledge
File Overlap
Dependency

而不是 Agent 数量。

我会计算 Review Load,而不是只看 PR 数

指标:

Changed Lines
Files
Risk
Reviewer Count
Review Minutes

可以粗略生成:

Review Load Score

例如:

score = (
    changed_lines / 100
    + files * 0.5
    + risk_weight
    + dependency_depth * 0.8
)

当单层 Score 太高:

建议继续拆

一个非常重要的指标:Rejected Work Ratio

Agent 写了很多代码,但最终没合入。

Rejected LOC
/
Generated LOC

如果:

45%

说明 Agent 吞吐很高,但有效交付差。

Stack 可以帮助定位:

到底哪个层经常被拒

而一个巨型 PR 只能整包统计。

Coding Agent 的“完成”不应该是代码写完

我会把 Definition of Done 改成:

Stack Planned
Each Layer Scoped
CI Green
Required Review Complete
Merged Bottom-up
Final Integration Test Green

只有最后一步才算完成。

Agent 如果只完成:

代码生成

只能算:

IMPLEMENTED

不能算 DONE

一个 Agent Stack Manifest

{
  "issue": 1842,
  "base": "main",
  "layers": [
    {
      "id": "L1",
      "branch": "feat/catalog-data",
      "depends_on": "main",
      "risk": "MEDIUM",
      "status": "APPROVED"
    },
    {
      "id": "L2",
      "branch": "feat/search-api",
      "depends_on": "L1",
      "risk": "MEDIUM",
      "status": "READY_FOR_REVIEW"
    }
  ]
}

Control Plane 可以直接画成 DAG。

大 PR 什么时候反而没必要拆

不要把 Stacked PR 教条化。

这些情况单 PR 可能更好:

纯机械 rename
Generated Code
单一文件独立修改
强原子性 Migration

如果拆开以后每层都无法独立 Review,也没有明显 Reviewer Boundary,Stack 反而增加维护成本。

我会用一个简单决策表

> 800 LOC + 多 Concern:优先拆
> 15 Files + 多 Owner:优先拆
有明确 Dependency Layers:优先 Stack
单一机械变更:单 PR
强原子发布:谨慎拆

阈值按团队历史调整。


Coding Agent 让“写代码”越来越快以后,Review 结构本身会变成生产力基础设施。

一个 1700 行的大 PR 并不说明 Agent 能力强。

它可能只说明:

生成速度
已经超过交付结构的承载能力

Stacked PR 真正解决的是:

把 Agent 的任务分解
映射成代码依赖、Review 边界和 Merge 顺序

未来 Coding Agent 做得好不好,我会越来越少看:

一天生成多少行代码

而更多看:

每个 PR 多快被真正理解、批准、合并,并且上线后不返工。

这才是有效吞吐。


更多企业级 AI 应用、Agent、RAG 与大模型工程化内容,我会继续整理在 智元界

https://www.zyentor.com/