GitHub 如何把 AI 生成的巨型 PR 拆成可评审的堆叠 PR
Turn one giant AI-generated pull request to a reviewable stack
GitHub 官方博客介绍用堆叠 PR 把 AI 编码智能体生成的单个巨型 PR 拆成按依赖排序的小 PR 链,以商品搜索功能为例拆成数据目录、搜索 API、聊天接入、引用 UI 四层。
GitHub 官方给出把 AI 生成的大 PR 拆成堆叠 PR 的完整流程,含 gh stack 命令与分层评审方法,可直接照做。
回想一下你最近交付的那个大功能。说实话。你是把它塞进一个巨大的 pull request 里,还是把它拆成了多个范围更小的 pull request?多年来,你一直不得不默默地在两者之间做出选择:要么眼睁睁看着一个 pull request 变得大到审查它成了一场噩梦,要么把它拆成一连串更小的 pull request,而你得照看它们、手动同步,并且每当底层引入变更时都要去理清冲突。
两种选择都有取舍。一种难以审查,另一种难以维护。你当天的决定会倾向于痛苦较小的那个选项。
现在再加上编码智能体。它们的生产力惊人,并且预计到 2028 年将在每个 SDLC 阶段带来 50% 的生产力提升,根据 Gartner 的说法。但是,它们无法替你消除如何组织 pull request 结构这一选择。它们反而放大了做出这一选择的必要性。
在本文中,跟随一个示例,了解如何使用堆叠式 pull request 来简化审查。
深入探究:为购物助手添加商品搜索
假设你发出一个提示,要求为购物助手添加商品搜索,然后走开,几分钟后——真的就是几分钟——你回来审查、引导并批准。但仔细看看最终往往会落进那单个 pull request 里的东西:
- 一个新的数据模型及其种子数据
- 一个 API 路由及其校验
- 客户端接线以及 UI 和空状态/回退状态/错误状态
……所有这一切以及更多内容,都在一个庞大的 1,000 多行 diff 中。

对于主要基于多年来代码传统编写方式训练出来的智能体来说,这种模式是它们默认的交付方式。让我们把这个过程推演一遍。
你想在一个现有的 Web 应用上添加商品搜索,而你的起始状态是:
- 一个模拟 AI 助手,显示来自随机行生成器的响应
- 不一致的商品数据被硬编码并散落在各个组件中
- 没有目录模块,没有 API,没有数据层——什么都没有

有人开了一个 issue 来实现该功能,而典型流程会是创建一个功能分支,把它分配给一个编码智能体(或多个自定义智能体),拿到整个实现代码和更新后测试的第一版草稿……
……你阅读代码(好吧,你也许读了代码)。然后,你仍然需要手动验证功能行为并做任何必要的更新,推送并打开一个 pull request,附上它那冗长却肤浅的 AI 生成描述,确保 CI 检查通过,自审 diff,然后请求审查者。你开始了……
<审查者之帽>
审查者:改了 1,721 行!!这个描述不太有帮助。我稍后再审查这个。
</审查者之帽>
接下来发生的事情很熟悉:
- 这个巨大的 pull request 变得难以审查——于是它就……搁在那里。
- 审查者失去上下文,反馈质量下降。
- 合并变得更慢。
这开启了一个手动、混乱、耗时的过程,在功能落地前容易产生冲突,而最终它落地时审查不足。
GitHub 堆叠式 pull request
堆叠式 pull request 引入了一种不同且更好的交付结构。原理很简单:分解。与其追求一个完整解决该 issue 的单个 pull request,不如把功能分解成逻辑层,并识别依赖链以达成你的目标。这为你和你的智能体提供了一种原生的方式来分解工作,把原本会落进一个巨大 pull request 的工作变成一连串小巧、聚焦且可独立审查的层。
那个难以审查的大型拉取请求变成了一个由更小、逻辑有序的拉取请求组成的堆栈,每个都限定于单一关注点,足够小,让审查者能够轻松掌握,并且有足够的上下文自然地从之前审查过的拉取请求中延续下来。
让我们来实现它。
堆栈结构
让我们看看分解问题并安排分层堆栈所涉及的步骤。
首先,重要的是设置堆栈基础。这很重要,因为整个堆栈管理生命周期中的 CI 检查和合并规则都是针对堆栈基础进行评估的。
然后,确定核心基础工作单元并将其放在更靠近基础的位置(堆栈中最低层),并将依赖的工作分层放在其上方。
| 堆栈层(L#)/分支 | 要交付的内容 | 依赖于 |
|---|---|---|
| L1 (feat/catalog-data) | 带有种子数据、验证和数据访问模块的类型化目录 | main(堆栈基础) |
| L2 (feat/search-api) | 经过验证的 /api/products/search 端点 | feat/catalog-data |
| L3 (feat/chat-grounding) | 聊天调用 API 并根据真实产品数据回答 | feat/search-api |
| L4 (feat/grounded-ui) | 产品引用卡片 + 状态 | feat/chat-grounding |
现在独立的关注点很清晰:数据、API、连接、用户体验,这使得可以为每个关注点分配不同的审查者群体。数据由数据负责人审查,用户体验由UI 负责人审查。
GitHub 对堆叠拉取请求的原生支持可以从拉取请求 UI 启动,并通过 gh stack CLI 无缝扩展到终端。
安装堆叠拉取请求 CLI 扩展
运行以下命令:
gh extension install github/gh-stack在古代,你就可以开始工作了。但今天不行。有代理与你并肩工作。这些代理需要学习堆栈如何工作,以及如何代表你创建和管理它们。gh-stack skills 教会它们这些。
gh skill install github/gh-stack或者,如果你更喜欢:
npx skills add github/gh-stack对于上述示例中的特定功能,你的开发工作流有自定义代理,每个代理都有定义的工作流,并遵循严格的范围纪律,以实现小型、单一范围的拉取请求的目标。
| 层/分支 | 代理 |
|---|---|
| L1 (feat/catalog-data) | 数据建模代理 |
| L2 ( feat/search-api) | 后端代理 |
| L3 ( feat/chat-grounding) | 前端代理 |
| L4 ( feat/grounded-ui) | 前端代理 |
设置的最后一步是确认 CI 存在。如前所述,每个拉取请求都将针对堆栈基础进行评估,这些检查将针对每一层运行。
现在工作开始了。
第一层:数据目录基础
如今大多数代理工作流都是自动化的,并在循环中自主执行,但为了说明,我们将逐一介绍每个步骤。
此时,所有代理都熟悉堆叠拉取请求的工作原理,因此此阶段的典型工作流将是:
- 使用适当的提示调用数据建模代理
- 代理初始化一个新堆栈并设置第一个分支——
feat/catalog-data,以 main 为基础,使用gh init stack - 检出、工作并运行验证
(All checks == green) ? commit the layer : Iterate
给未来审查者的备注:类型正确吗?数据经过验证吗?查询助手安全吗?就这样。
第二层:产品搜索 API
遵循类似以下的流程:
- 使用合适的提示调用 Backend agent
- 该 agent 在第一层之上添加下一层
feat/search-api,其基础是:feat/catalog-data,以导入完成的数据访问模块,使用gh stack add - 检查通过,可运行并执行验证
- 开发者手动测试 API
(API works && All checks == green) ? commit the layer : Iterate
给未来的审查者备注:输入是否经过验证?响应契约是否稳定?错误/空状态是在这里处理还是推给下游?句号。
第三层:将聊天接入 API
在下一层中,你需要:
- 使用合适的提示调用 Frontend agent
- 该 agent 在第二层之上添加下一层
feat/chat-grounding。其基础是:feat/search-api,它将同时基于数据访问模块和已验证的 API 进行分支。 - 检查通过,可运行并使用 Playwright 运行浏览器测试
(All checks == green) ? commit the layer : Iterate
给未来的审查者备注:每个答案是否都能追溯到真实的 API 响应?当 API 失败或返回空时会发生什么?句号。
第四层:有依据的 UI 和引用
你会注意到,第三层和第四层尽管作者相同(Frontend agent),却被明确地分层。这是有意为之。UI 负责人不应需要检查底层数据流,反之亦然,而这种结构允许这种独立性。
因此,前端 agent:
- 在第三层之上添加下一层
feat/grounded-ui,其基础是:feat/chat-grounding - 检查通过,可运行并使用 Playwright 运行浏览器测试
(All checks == green) ? commit the layer : Iterate
给未来的审查者备注:每个引用是否都链接回真实产品?加载、空和错误状态是否都已覆盖?句号。
提交堆栈
四个本地堆叠分支已准备就绪。接下来使用 gh stack push 将它们推送到远程,然后使用 gh stack submit 在 GitHub 上创建链接它们的拉取请求。
每一层的堆栈图和 CI
切换到 GitHub,四个拉取请求都已打开,并且在每个拉取请求的顶部,你会看到一个堆栈图,这是一个在堆栈中各拉取请求之间一键导航的系统。
审查和更新堆栈
是时候换个身份,看看审查者浏览堆叠拉取请求的旅程了。
<戴上审查者的帽子>
堆栈图是审查者的指南针——是在堆栈顶部和底部之间导航的辅助工具,朝着成功合并前进。移动是有方向性的:自上而下阅读,自下而上审查。
- 自上而下阅读,以了解上下文。这让你在审查过程一开始就获得最终目标,从而可以确定方向。“哦,所以我们想在聊天界面上显示产品卡片。”
- 自下而上审查,以基于预先确定的检查点进行构建。只有理解了前一层,每一层的实现才有意义。
你不再像我们的示例中那样,面对一个需要一次性审查的 1,720 多行的单个拉取请求,相反,审查可以分布到堆栈中一个个小型、自包含的目标上。
作为被指派的 human in the loop 审查者,你进来查看第一层,即堆栈底部的拉取请求,并看到自动 Copilot Code Review (CCR) 发现了两个你也认为应该修复的问题。
<重新戴上开发者的帽子>
变更请求位于堆栈底部,因此你:
- 将反馈交给第一层作者,即拥有该分支的数据建模 agent
- 建议被应用、测试、提交并推送
- 一旦修复落到 feat/catalog-data 上,自然的下一步问题就是:这对第二层、第三层和第四层意味着什么?
由于分支 feat/catalog-data 在审查后被乱序推送,GitHub 明确标记:“此堆栈中的某些分支已分叉,必须变基”,并附带“无法作为堆栈合并”的标记,从而阻止了合并。
回到 GitHub 的拉取请求界面,会出现一个一键 Rebase stack 按钮。在使用该按钮之前,有一点很重要,值得注意。使用此按钮触发基于网页的变基会在 GitHub 的服务器上运行,这意味着它会将提交者重置为点击按钮的人,生成的提交不会被签名,如果分支保护要求签名提交,那么这一次点击就会悄无声息地破坏它。
从终端进行更安全、等效的操作是 gh stack rebase,在本地执行同样的级联变基,同时交互式地解决冲突,但这次使用你自己的 Git 配置,然后 gh stack push。
最后,你将沿着堆栈向上传播。 堆栈的其余部分,无论是本地还是 GitHub 上的,现在都需要跟上,而这再简单不过了,只需一条同步命令 gh stack sync。
一体化流程首先从 origin 获取,将 feat/catalog-data 之上的每个分支级联变基到新提交上,推送变基后的分支,并从 GitHub 同步拉取请求状态。这样,变更就会向上传播,无需任何人手动处理第二、第三或第四层。
回到 GitHub,所有检查重新运行、通过,堆栈图恢复为从 main 到 feat/grounded-ui 的一条干净、可合并的直线。
开始使用 堆叠式拉取请求 >
文章 Turn one giant AI-generated pull request to a reviewable stack 首次出现在 The GitHub Blog。
来源:GitHub Blog · Engineering · github.blog