GitHub 如何用堆叠式 Pull Request 拆解 AI 生成的巨型代码
Turn one giant AI-generated pull request to a reviewable stack
GitHub 介绍用堆叠式 Pull Request(stacked PR)解决 AI 编码智能体生成巨型代码难以审查的问题。通过将 1,000+ 行的大 diff 按数据、API、接线、UI 拆成 L1-L4 四个独立分层,每层可分配不同审查者。
详细演示了用 GitHub 堆叠 PR 将 AI 生成的大 PR 拆分为逻辑层,配合 gh-stack CLI 和技能,提供可立即采用的审查优化方案。
想想你上一次交付的大功能。说实话。你是把它硬塞进一个巨大的 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 Assistant,展示来自随机行生成器的响应
- 不一致的商品数据被硬编码并散落在各个组件中
- 没有目录模块,没有 API,没有数据层——什么都没有

有人开了一个 issue 来实现这个功能,典型流程是创建一个功能分支,把它分配给一个编码智能体(或多个自定义智能体),拿到整个实现代码和更新后测试的初稿……
视频 · 前往原文观看……你读代码(好吧,你也许读了代码)。然后,你仍然需要手动验证功能行为并做任何必要的更新,推送并打开一个 pull request,附上它那又长又浅的 AI 生成描述,确保 CI 检查全绿,然后自我审查 diff 再请求审查者。你开始动手了……
<reviewer's hat>审阅者:改了 1,721 行!!这个描述没什么用。我稍后再审阅。
</reviewer's hat>接下来发生的事情就很熟悉了:
- 庞大的 pull request 变得难以审阅——于是它就……搁在那儿了。
- 审阅者失去上下文,反馈质量随之下降。
- 合并也会变得更慢。
这会启动一个手动、混乱、耗时的流程,在功能落地之前就容易产生冲突,而最终落地时又审查不足。
GitHub 堆叠式 pull request
堆叠式 pull request引入了一种不同且更好的交付结构。其原理很简单:分解。与其追求用一个 pull request 完整解决整个问题,不如把功能拆解为若干逻辑层,并识别出通往目标的依赖链。这样一来,你以及你的智能体就有了一种原生的方式,把原本会落成一个巨大 pull request 的工作,分解为一连串小而聚焦、可独立审查的层。
那个难以审查的大型 pull request 会变成一叠更小、按逻辑排序的 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) | Chat 调用该 API,并基于真实产品数据作答 | feat/search-api |
| L4(feat/grounded-ui) | 产品引用卡片 + 状态 | feat/chat-grounding |
现在各个独立的关注点已经清晰:数据、API、接线、UX,这使得可以为每一个分配不同的审查者群体。数据由数据负责人审查,UX 由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
遵循类似这样的流程:
- 用合适的提示词调用后端智能体
- 该智能体添加下一层
feat/search-api在第一层之上,其基础为:feat/catalog-data,以通过gh stack add导入已完成的数据访问模块 - 检查通过、可运行并执行验证
- 开发者手动测试 API
(API works && All checks == green) ? commit the layer : Iterate
视频 · 前往原文观看评审者留给未来的备注:输入是否经过验证?响应契约是否稳定?错误/空状态是在这里处理还是推给下游?就这样。
第三层:将聊天接入 API
在下一层中,你需要:
- 用合适的提示词调用前端智能体
- 该智能体添加下一层
feat/chat-grounding在第二层之上。其基础为:feat/search-api,它将从数据访问模块和已验证的 API 两者分支出来。 - 检查通过、可运行并使用 Playwright 运行浏览器测试
(All checks == green) ? commit the layer : Iterate
视频 · 前往原文观看给未来的评审者备注:每个答案是否都能追溯到真实的 API 响应?当 API 失败或什么都不返回时会发生什么?就这样。
第四层:有依据的 UI 与引用
你会注意到,第三层和第四层尽管出自同一位作者(前端智能体),却被清晰地分层。这是有意为之。UI 的负责人不应该去检查底层的数据流,反之亦然,而这种结构正好支持这种独立性。
所以,前端智能体:
- 在第三层之上添加下一层
feat/grounded-ui以第三层为基础,它的基础是:feat/chat-grounding - 检查无误、可运行,并用 Playwright 运行浏览器测试
(All checks == green) ? commit the layer : Iterate
视频 · 前往原文观看给未来的评审者备注:每个引用是否都链接回真实的产品?加载、空状态和错误状态是否都已覆盖?就这样。
提交整个技术栈
四个本地堆叠分支已准备就绪。接下来用 gh stack push 将它们推送到远程,然后用 gh stack submit 在 GitHub 上创建关联它们的拉取请求。
视频 · 前往原文观看每一层的堆栈图和 CI
切换到 GitHub,四个拉取请求都已打开,在每个拉取请求的顶部,你会看到一个 堆栈图,这是一个在堆栈中各拉取请求之间一键导航的系统。
视频 · 前往原文观看审查和更新堆栈
是时候换个视角,看看审查者浏览堆叠式拉取请求的历程了。
<reviewer’s hat on>栈映射图是评审者的指南针——一种在栈顶与栈底之间导航、朝着成功合并前进的辅助工具。其移动是有方向性的:自上而下阅读,自下而上评审。
- 自上而下阅读,是为了获取上下文。这让你在评审流程的一开始就掌握最终目标,从而能够确定方向。“哦,原来我们是想在聊天界面上展示产品卡片。”
- 自下而上评审,是为了在预先确定的检查点上逐步构建。只有理解了前一层,每一层的实现才有意义。
正如我们示例中所见,你不再需要面对一个 1,720+ 行的单个 pull request 并一次性完成评审,而是可以将评审分散到栈中一个个小型、自包含的目标上。
视频 · 前往原文观看作为被指定的人类在环审核者,你介入并查看第一层,也就是栈底部的拉取请求,发现自动 Copilot Code Review(CCR)捕获了两个你也认为应当修复的问题。
<developer's hat back on>变更请求在栈底部被提出,于是你:
- 将反馈交给第一层作者,即拥有该分支的数据建模智能体
- 建议被应用、测试、提交并推送
- 一旦修复落到 feat/catalog-data 上,接下来自然的问题是:这对第二层、第三层和第四层意味着什么?
由于分支 feat/catalog-data 在审核后被不合时宜地推送,GitHub 明确标记:“此栈中的某些分支已分叉,必须进行 rebase”,并伴有“无法作为栈合并”的标记,从而阻止了合并。
视频 · 前往原文观看回到 GitHub 上的拉取请求界面,会出现一个一键式 Rebase stack 按钮。在使用该按钮之前,有一点重要事项值得注意。使用此按钮触发基于网页的 rebase 会在 GitHub 的服务器上运行,这意味着它会将提交者重置为点击该按钮的人,由此产生的提交不会被签名,而如果分支保护要求签名提交,那么这一次点击就会悄无声息地将其破坏。
从终端执行更安全的等效操作是:gh stack rebase 在本地执行同样的级联 rebase,同时交互式地解决冲突,但这一次使用你自己的 Git 配置,然后 gh stack push。
最后,你将沿着整个堆栈向上传播。 堆栈的其余部分,无论是本地还是 GitHub 上的,现在都需要跟上,而这再简单不过了,只需一条同步命令 gh stack sync。
一体化流程首先从 origin 拉取,将 feat/catalog-data 之上每个分支级联 rebase 到新提交上,推送 rebase 后的分支,并从 GitHub 同步 pull request 状态。这样,变更会向上层层传递,无需任何人手动处理第二、三、四层。
回到 GitHub,所有检查重新运行、通过,堆栈图重新恢复为从 main 到 feat/grounded-ui 的一条干净、可合并的直线。
视频 · 前往原文观看开始使用 堆叠式 pull request >
来源:GitHub Blog · github.blog