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/