1. 项目缘起:一个“偷懒”的想法如何变成现实
作为一名在代码仓库里摸爬滚打了十多年的开发者,我敢说,写 Pull Request 描述绝对是开发流程里最“反人性”的环节之一。代码写完了,测试跑通了,满心欢喜准备提交,结果卡在了“写个像样的 PR 描述”这一步。你得回忆这次改了啥、为什么改、怎么测的、有没有破坏性变更……有时候为了写得全面,花的时间比修个 bug 还长。更别提那些只有“fix bug”或者“update”几个字的 PR,让 Reviewer 看得一头雾水,严重拖慢了合并和交付的速度。
我一直琢磨着,这事儿能不能自动化?毕竟,代码变更本身(diff)就包含了绝大部分信息:改了哪些文件、增加了什么函数、删除了哪些行。如果能有一个工具,像一位细心的助手,自动分析这些变更,并生成一份结构清晰、内容准确的 PR 描述初稿,那该多省事。开发者只需要在此基础上做微调,甚至直接采用,效率能提升一大截。
这个想法在我脑子里盘踞了很久,直到最近,我决定动手把它实现出来。我选择以GitHub App的形式来构建它。为什么是 GitHub App?因为它能深度集成到 GitHub 的协作流程中,无需每个开发者单独配置,安装一次就能对整个仓库生效。当有新的 PR 被创建时,它能自动触发,静默工作,完美符合“无感提升效率”的初衷。
于是,我给自己定了一个小目标:用一周多的时间,从零开始,构建并发布这个能自动编写 PR 描述的 GitHub App。我想看看,一个如此具体的工具,从想法落地到真实用户使用,到底会经历什么。这就是过去八天里发生的故事。
2. 核心设计:让机器理解代码变更的“意图”
要让机器自动写描述,核心是让它“理解”一次代码提交到底做了什么。这远不是简单罗列文件名那么简单。我的设计思路是分层解析,从“是什么”逐步推导到“为什么可能”。
2.1 信息提取的三层漏斗模型
第一层,基础变更数据。这是最直接的,从 Git diff 中获取:哪些文件被增删改(A/D/M),每个文件具体的变更行。这一步是纯机械的,但却是所有分析的基石。我会特别关注那些被大量修改或新增的文件,它们往往是本次变动的核心。
第二层,语义化聚类。单纯的文件列表是混乱的。我会根据文件路径和类型进行聚类。例如,所有在src/components/Button/下的变更,大概率是关于按钮组件的;同时修改了package.json和src/**/*.ts,可能涉及依赖升级和相应的类型调整。将文件按功能模块或目录结构分组,能让后续描述更有条理。
第三层,意图推测与摘要生成。这是最有趣也最具挑战的一层。这里,我引入了大型语言模型(LLM)。我会将聚类后的变更摘要(例如:“修改了src/utils/auth.ts,主要变动在validateToken函数,增加了令牌过期检查逻辑”)连同相关的代码片段(关键部分的 diff)一起,发送给 LLM,并给出明确的指令(Prompt):“你是一个资深的代码审查员。请根据提供的代码变更,用简洁的技术语言总结这个 PR 的主要目的、关键改动和可能的影响。请使用要点列表的形式。”
2.2 技术栈选型与架构权衡
为了实现这个三层模型,我做了如下技术选型:
- 后端框架与语言:Node.js + TypeScript + Probot。这是构建 GitHub App 的黄金组合。Probot 是一个基于 Node.js 的框架,它封装了与 GitHub API 交互的复杂细节,让开发者能专注于业务逻辑。TypeScript 的强类型在处理 GitHub Webhook 事件这种复杂数据结构时,能极大提升开发效率和代码可靠性。
- LLM 服务:OpenAI GPT-4 API。在尝试了多个模型后,GPT-4 在代码理解和生成结构化文本方面表现最为稳定和精准。虽然需要付费,但其产出质量对工具的信誉至关重要。一个胡言乱语的描述会比空的描述更糟糕。
- 部署与运行环境:Vercel Serverless Functions。GitHub App 通过 Webhook 接收事件,非常适合无服务器架构。Vercel 部署简单,能自动处理扩展,并且与我的前端项目(如果需要仪表盘的话)能无缝集成。成本在初期也几乎可以忽略不计。
- 数据存储:暂不持久化。第一个版本,我决定不引入数据库。App 的逻辑是“触发-响应”式:收到
pull_request.opened事件,处理,更新 PR 描述,然后结束。这简化了架构,避免了数据隐私和管理的麻烦。后续如果增加用户配置或历史记录功能,再考虑引入简单的 KV 存储(如 Upstash)。
整个架构非常轻量:GitHub 将事件推送到我部署在 Vercel 的 Webhook 端点,Probot 应用解析事件,执行上述三层分析逻辑,最后调用 GitHub API 更新对应 PR 的body字段。
注意:使用 LLM API 有两个关键考量:一是成本,需要设置用量监控和预算告警;二是延迟,网络请求和模型推理需要时间,必须在 GitHub App 的超时限制(默认10秒)内完成,必要时需采用异步处理或流式响应。
3. 实操构建:从零到一的八个关键日夜
这八天更像是一个高度浓缩的产品冲刺周期,每一天都有明确的里程碑。
第1-2天:搭建骨架与权限迷宫第一天,我用npx create-probot-app快速初始化了项目。接下来的重头戏是理解并配置 GitHub App 的权限(Permissions)和事件订阅(Webhook events)。这是新手最容易踩坑的地方。你的 App 需要哪些权限?pull_requests: write是必须的,否则无法修改描述。contents: read也是必须的,用于读取 diff。如果你希望分析 Issue 关联,可能还需要issues: read。权限申请不足,API 调用会返回403;申请过多,又会吓跑谨慎的用户。我花了大量时间阅读文档,最终确定了最小权限集。同时,在本地使用smee.io转发 Webhook 进行测试,确保事件能正确触发。
第3-4天:实现 Diff 解析与智能摘要核心第三天,我集中精力实现 Git diff 的解析。GitHub API 在 PR 事件中提供了diff_url,可以直接获取到格式化的 diff 文本。我使用parse-diff这个库将 diff 文本转换成结构化的对象,方便后续处理。然后,我编写了文件聚类逻辑,简单的基于路径前缀的规则就起到了不错的效果。 第四天,集成 OpenAI API。我设计了一个“安全提示词模板”,核心指令是让模型扮演代码审查者,只基于给定的 diff 进行总结,禁止臆测或生成代码。同时,我设置了合理的max_tokens以防响应过长,并加入了重试和错误处理机制。第一个能跑通的版本在这一天诞生了——当我推送一个测试 PR 时,它真的自动生成了一段描述!
第5天:打磨描述模板与用户体验生成的纯文本描述虽然可用,但不够美观和专业。我设计了一个 Markdown 模板,将 LLM 生成的核心摘要嵌入其中。模板包含几个固定部分:
## 变更摘要 (由 AI 自动生成) **主要修改目的:** - [LLM生成的要点1] - [LLM生成的要点2] **涉及的关键文件:** - `src/foo/bar.ts` (核心逻辑修改) - `tests/bar.spec.ts` (对应测试更新) --- *本描述由 [App名称] 自动生成,请核对并补充更多上下文(如关联的 Issue、测试步骤等)。*这个模板在提供信息的同时,也明确了这是 AI 辅助生成,需要人工复核,避免了误导。
第6天:处理边界情况与增强鲁棒性真实世界是混乱的。我模拟测试了各种边界情况:
- 空 Diff:比如只修改了
README.md。App 现在会检测到变更文件很少或内容无关代码,生成如“更新了文档文件”的轻量描述,而不是调用 LLM。 - 大 Diff:一次提交上百个文件。直接发送全部 diff 会给 API 带来巨大成本和超时风险。我增加了逻辑:只提取新增行数最多的前 5 个文件,以及所有被删除的文件进行摘要。
- 网络或 API 失败:给所有外部调用(GitHub API, OpenAI API)加上完善的
try-catch和日志记录。如果生成描述失败,App 会静默失败,不在 PR 上留下错误评论(避免骚扰用户),但会在我的监控日志中报警。
第7天:完善配置、日志与监控为了让 App 更可控,我添加了简单的环境变量配置,比如可以开关 AI 生成功能,或者设置忽略的文件模式(如*.lock,*.min.js)。同时,集成了结构化的日志输出(使用pino),在 Vercel 的日志流中能清晰看到每个 PR 的处理过程、耗时和结果。我还设置了一个简单的健康检查端点。
第8天:发布与撰写文档最后一天,我在 GitHub Marketplace 提交了 App 的发布申请。填写清晰的应用描述、功能截图和权限说明至关重要。同时,我在仓库中编写了详细的README.md,包括:
- 功能简介:一目了然。
- 安装指南:分步骤指导仓库管理员如何安装和配置。
- 工作原理:简要说明,增加透明度。
- 隐私与数据安全:明确承诺 diff 内容仅用于实时生成描述,不会被存储或用于其他目的,这对获取用户信任非常关键。
- 常见问题:预判了用户可能的问题,如“如何关闭?”、“支持私有仓库吗?”。
点击“发布”按钮的那一刻,感觉就像把一艘小船推入了大海,不知道它会遇到什么风浪。
4. 上线初体验:数据、反馈与意料之外
App 上线后,我将其安装在了自己的几个活跃仓库进行实战测试。同时,也在几个小型开发者社区做了分享。最初的 48 小时,是观察和收集反馈的黄金时间。
4.1 核心数据观察
通过日志和 GitHub API,我观察到一些有趣的数据模式:
- 触发成功率:大约 95% 的 PR 能成功触发并生成描述。失败的 5% 主要源于:1) 超大型 PR 处理超时;2) 极罕见的网络波动。
- 描述采纳率:这是我最关心的指标。粗略估计,超过 70% 的 PR,创建者直接使用了 AI 生成的描述,或只做了少量词语修改。约 20% 的 PR,创建者会在此基础上大幅增补上下文(如关联的 Issue 编号、测试指令)。只有不到 10% 的 PR 被完全重写或删除。这个采纳率远超我的预期。
- 处理耗时:从接收 Webhook 到更新 PR 描述,中位数时间在 3-5 秒。其中大部分时间花在 LLM API 调用上。这个延迟对于异步流程来说是可接受的,用户几乎感知不到。
4.2 来自真实用户的反馈
我收到了几十条来自早期用户的反馈,褒贬不一,但都极具价值:
正面评价集中点:
- “节省了大量机械性工作的时间。”
- “生成的摘要准确率很高,尤其是对于功能新增和 Bug 修复这类意图明确的 PR。”
- “模板化的格式让团队 PR 描述看起来更统一、专业。”
- “对于刚接手项目的新人,自动生成的描述能帮他快速理解别人的改动。”
批评与问题集中点:
- “有时会遗漏关键业务上下文。”这是最大的痛点。例如,一个修改了支付接口的 PR,AI 能准确总结代码层面的参数变化,但无法知道这个变更是为了“适配新的银行通道 XX”。这部分知识在代码之外。
- “对重构类 PR 的描述过于笼统。”当 PR 主要是代码结构调整(重命名、提取函数)时,AI 倾向于生成“代码重构和优化”这类万能但无用的描述。
- “能否支持从关联的 Issue 中提取信息?”很多用户的 PR 会链接到 Issue,他们希望描述能自动引入 Issue 的标题或核心要求。
- “偶尔会有‘幻觉’。”极少数情况下,AI 会推断出一个不存在的“修复了内存泄漏”之类的结论,虽然概率低,但一旦发生会误导 Reviewer。
4.3 遇到的技术与产品挑战
- GitHub API 速率限制:当 App 被安装在一个非常活跃的仓库时,短时间内大量 PR 创建可能会触及 GitHub API 的速率限制。我不得不为 GitHub API 客户端添加了简单的请求队列和退避重试机制。
- OpenAI API 成本与性能平衡:使用
gpt-4虽然质量好,但成本较高。我试验了gpt-3.5-turbo,发现其对复杂代码变更的理解能力有明显下降。目前我仍坚持使用gpt-4,但正在探索如何通过更精细的 diff 预处理(例如,过滤掉格式化变更)来减少输入的 token 数量,从而降低成本。 - “静默失败”与用户感知:最初设计时,我认为失败就应该完全静默。但有用户反馈,他们希望知道这个 App 是否“工作”了。我因此增加了一个轻量级功能:当描述成功生成时,在 PR 下方添加一个不显眼的表情评论(如 🤖),作为一个成功执行的标记。如果用户不需要,也可以配置关闭。
5. 迭代方向与深度思考
这八天的构建与初步运营,只是一个开始。用户的反馈清晰地指明了接下来的路。
5.1 优先级最高的功能迭代
- 集成 Issue 上下文:这是下一个要开发的核心功能。当 PR 链接了 Issue 或提到了 Issue 编号(如
#123),App 将自动获取该 Issue 的标题和首条评论,将其作为额外上下文喂给 LLM。这样生成的描述就能包含“为什么改”的业务动机,例如:“实现用户要求的夜间模式切换功能(源自 Issue #123)”。 - 支持自定义提示词模板:不同的团队、不同的项目类型(前端、后端、基础设施)对 PR 描述的期望不同。我将允许仓库管理员通过一个配置文件(如
.github/pr-description-template.md)来定制 LLM 的指令和最终输出的模板。比如,基础设施团队可能更关心“是否需停机部署”、“回滚步骤”。 - 代码变更类型识别与分类:通过更先进的静态分析(不仅仅是 diff),尝试自动识别 PR 的类型:是
feat、fix、refactor、docs还是chore?甚至进一步识别是“数据库迁移”、“API 变更”还是“UI 样式调整”。根据不同类型,采用不同的描述侧重点和提问方式。
5.2 关于“辅助”与“替代”的边界思考
这个项目让我深入思考了 AI 工具在开发流程中的定位。它绝不是要替代开发者编写 PR 描述的责任,而是替代其中枯燥、机械的信息搬运和初步整理工作。
- AI 擅长的是:快速浏览大量代码变更,提取出修改了哪些文件、函数签名如何变化、增加了哪些条件判断等客观事实,并将其组织成通顺的文字。
- 开发者不可被替代的是:提供代码之外的业务上下文、决策逻辑、与其他系统的交互影响、测试的特别注意事项等领域知识。
最理想的工作流是:AI 生成一份准确的“变更事实清单”,开发者在此基础上,用几句话补充上“为什么做这些变更”的灵魂。这样结合,既能保证效率,又能确保关键信息不丢失。我的 App 模板末尾的那句“请核对并补充更多上下文”,正是为了强调这种协作关系。
5.3 对团队协作文化的潜在影响
一个有趣的观察是,这个工具无形中在推动一种更规范的协作文化。当团队里大部分 PR 都有一个结构清晰、内容具体的描述时,那些习惯写“update”的成员也会感到压力,从而开始改善自己的习惯。它设立了一个最低可接受的标准。此外,统一的描述格式也让 Reviewer 更容易快速定位重点,提升代码审查的效率和质量。
八天时间,从一个“偷懒”的念头,到一个在真实场景中为数百个 PR 提供过帮助的 GitHub App。这个过程让我再次确信,最好的开发者工具往往源于开发者自身最切身的痛点和“懒惰”。技术(尤其是 LLM)的进步,让我们有能力将这些自动化想法以前所未有的速度实现。然而,工具的成功与否,最终不取决于技术的炫酷,而取决于它是否真正理解了工作流中的“人”,并在提升效率的同时,尊重和增强了人的判断与创造力。我的这个“自动写手”项目,还在学习和进化中,而这段旅程,本身就充满了乐趣。