编码代理很容易一次生成数千行改动,但“代码能运行”不等于“改动能审查”。当数据库迁移、领域模型、API、界面和测试全部塞进一个 Pull Request,审查者很难区分基础设施变更与业务行为,也难以判断某段代码究竟依赖什么。
更实用的做法,是在任务开始前就要求代理把工作拆成一组有顺序的分支,并在 GitHub 上建立彼此依赖的 stacked pull requests(堆叠式 PR)。每个 PR 只表达一个清晰决策,后续 PR 以上一个分支为基础。
PR 栈解决的不是行数,而是依赖关系
假设要为订单系统增加“取消订单”功能,可以拆成四层:
cancel/domain:增加订单状态与状态转换规则。cancel/storage:增加数据库字段和持久化逻辑,依赖领域模型。cancel/api:暴露取消订单接口,依赖存储层。cancel/ui:增加取消按钮和交互,依赖 API。
对应的 GitHub PR 基线应当形成一条链:
cancel/domain -> main
cancel/storage -> cancel/domain
cancel/api -> cancel/storage
cancel/ui -> cancel/api
这样,审查 cancel/api 时,GitHub 展示的是 API 分支相对 cancel/storage 新增的内容,而不是整个功能从 main 开始的全部差异。每一层都可以独立讨论、修改和测试。
拆分边界应围绕依赖和行为,而不是机械地限制文件数量。一个包含模型实现及其单元测试的 PR,通常比“所有生产代码一个 PR、所有测试另一个 PR”更容易验证。
先让代理提交拆分方案
不要只给编码代理一句“实现取消订单”。可以先要求它输出分支计划,并明确禁止在计划确认前修改代码。下面的提示词可以直接改造:
你正在一个 GitHub 仓库中实现“取消订单”功能。
在修改代码前:
1. 阅读相关模型、数据库迁移、API、前端和测试。
2. 将工作拆成 3 到 5 个按依赖顺序排列的 PR。
3. 每个 PR 必须可单独审查,并包含与该层行为对应的测试。
4. 为每层列出:分支名、目标基线分支、改动范围、验证命令。
5. 不要按文件类型拆分;按可验证的行为和依赖边界拆分。
6. 等计划确认后,再逐层实现并提交。
实现阶段要求:
- 每个分支只包含该层新增的提交。
- 提交信息说明行为变化,不使用“misc changes”之类的描述。
- 不重写已经进入审查的分支,除非明确得到允许。
- 完成每层后运行对应测试,并生成 PR 标题和正文。
这一步的价值在于提前暴露错误边界。例如代理可能把数据库迁移放在 UI 分支中,或者让基础模型依赖尚未出现的 API 类型。此时调整计划,成本远低于在一个巨大 PR 中重新整理提交历史。
用 Git 和 GitHub CLI 建立一个 PR 栈
下面示例假设仓库已配置 origin,本机安装并登录了 GitHub CLI。请把测试命令和分支名称替换成项目实际内容。
set -euo pipefail
git switch main
git pull --ff-only origin main
# 第 1 层:领域规则
git switch -c cancel/domain
# 在这里实现领域模型和单元测试
npm test -- --runInBand domain
git add -A
git commit -m "Add order cancellation domain rules"
git push -u origin cancel/domain
gh pr create \
--base main \
--head cancel/domain \
--title "Add order cancellation domain rules" \
--body "Introduces cancellation states, transition rules, and domain tests."
# 第 2 层从第 1 层继续,而不是重新从 main 开始
git switch -c cancel/storage
# 在这里增加迁移和持久化实现
npm test -- --runInBand storage
git add -A
git commit -m "Persist order cancellation state"
git push -u origin cancel/storage
gh pr create \
--base cancel/domain \
--head cancel/storage \
--title "Persist order cancellation state" \
--body "Adds the schema migration and persistence behavior on top of the domain changes."
# 第 3 层继续依赖第 2 层
git switch -c cancel/api
# 在这里实现接口和集成测试
npm test -- --runInBand api
git add -A
git commit -m "Expose order cancellation API"
git push -u origin cancel/api
gh pr create \
--base cancel/storage \
--head cancel/api \
--title "Expose order cancellation API" \
--body "Adds the cancellation endpoint and API tests; depends on the storage PR."
创建完成后,应在每个 PR 的正文中标出栈顺序,例如:
## Stack
1. `cancel/domain` <- current base
2. `cancel/storage` <- this PR
3. `cancel/api`
## Verification
```bash
npm test -- --runInBand storage
实际 PR 正文中的嵌套代码块可以按仓库模板调整。关键是让审查者立即知道当前层的基线、后续依赖和验证方式。
## 合并顺序与历史重写
PR 栈通常从底部开始合并,也就是先合并最接近 `main` 的 PR。基础层进入 `main` 后,把下一层的 base 改成 `main`:
```bash
gh pr edit 123 --base main
这里需要提前决定合并策略。如果使用普通 merge commit,祖先关系通常能保留下来,后续 PR 更容易重新设置 base。如果仓库强制 squash merge,合并后的提交身份会改变,后续分支可能需要 rebase。以 cancel/storage 为例,可以这样实践:
git fetch origin
git switch cancel/storage
git rebase --onto origin/main origin/cancel/domain cancel/storage
git push --force-with-lease origin cancel/storage
gh pr edit 123 --base main
--force-with-lease 仍然会改写远端历史,只应在确认没有其他人向该分支推送后使用。更深的分支也要跟随新的父分支进行 rebase,因此栈越长,维护成本越高。多数团队把栈控制在 3 到 5 层会更容易管理;更复杂的变更可以拆成多个相对独立的栈。
落地时检查这几件事
采用 PR 栈时,可以用以下标准约束代理和人工提交:
- 每个 PR 都有一句能够独立说清楚的行为描述。
- 测试与对应实现处于同一层,而不是集中到栈顶。
- 后续分支只依赖前一层,不跨层引用尚未出现的代码。
- PR 正文列出整个栈,并标明当前 PR 的位置。
- CI 对每个分支运行,不能只在最后一层获得绿色结果。
- 合并前明确采用 merge、rebase 还是 squash,并演练后续分支如何更新。
- 已进入审查的分支尽量保持稳定;确需 rebase 时通知审查者。
巨型 AI PR 的根本问题不是代理写得太快,而是生成速度超过了团队验证决策的速度。把“生成一个 PR”改成“生成一组有依赖、有测试、可按顺序合并的 PR”,才能让自动化产出真正进入日常工程流程。