gemini-cli 的 Issue 与 PR 自动化分诊:从 Bot 触发规则到源码级实现解析
本文基于 gemini-cli 仓库中的 自动化与分诊流程文档 展开,系统拆解该项目用于管理 Issue 与 Pull Request 的七条自动化工作流:谁触发、何时运行、做了什么、以及贡献者应该配合做什么。读完本文,你不仅能理解该项目机器人系统的完整运作方式(包括标签体系、Gemini 模型分诊提示词、防重复注释策略等),还能直接对照仓库中每个工作流的 YAML 定义与配套脚本,将其作为你自己项目 CI/分诊自动化的设计参考。
核心原则:Issue 描述"是什么",PR 描述"怎么做"
整套自动化建立在一个基本约定上:几乎每个 PR 都应关联一个对应的 Issue。Issue 描述"是什么"和"为什么"(缺陷或功能需求),PR 描述"怎么做"(具体实现)。这种分离让团队可以追踪工作量、排定优先级,并保留清晰的历史上下文,仓库里的标签同步、链接检查等自动化都围绕这一原则构建。
注意:带有 "🔒Maintainers only" 标签的 Issue 是保留给项目维护者的,不会接受与之相关的 PR。
这一约定不是空话,仓库中有多处证据支撑:
- PR 模板 pull_request_template.md 中的 "Related Issues" 一节明确要求使用关键字自动关闭 Issue(
Closes #123、Fixes #456),如果只是部分相关则直接引用编号(Related to #123); - 定时 PR 分诊脚本 pr-triage.sh 会扫描 PR 的
closingIssuesReferences,甚至用正则(^|[^a-zA-Z0-9])#(?<num>[0-9]+)从 PR 正文中兜底提取#编号(见该脚本第 141 行附近),找不到任何关联 Issue 的非 Draft PR 会被打上status/need-issue标签。
标签体系:分诊输出的"结构化语言"
理解各工作流之前,先了解它们共同操作的一组标签前缀。从 定时 Issue 分诊工作流 的检索语句中可以看到完整标签空间:
| 前缀 | 含义 | 示例取值 |
|---|---|---|
area/* |
功能领域 | area/agent、area/core、area/enterprise、area/non-interactive、area/security、area/platform、area/extensions、area/documentation、area/unknown |
kind/* |
Issue 类型 | kind/bug、kind/enhancement、kind/customer-issue、kind/question |
priority/* |
优先级(P0 严重到 P3 低) | priority/p0、priority/p1、priority/p2、priority/p3、priority/unknown |
effort/* |
工作量估计 | effort/small、effort/medium、effort/large |
status/* |
状态 | status/need-triage、status/need-information、status/need-issue、status/bot-triaged、status/manual-triage |
size/* |
PR 改动规模 | size/XS 到 size/XL |
其中 area/*、kind/*、priority/* 三类都遵循"每个 Issue 有且只有一个"的约束,后面的分诊工作流正是围绕"补齐缺失项、消除冲突项"运转的。
工作流一:开 Issue 时的即时分诊(Automated Issue Triage)
- 工作流文件:gemini-automated-issue-triage.yml
- 触发时机:Issue 创建或重新打开(
issues: opened/reopened);也支持workflow_dispatch手动传入 Issue 编号,以及被其他工作流以workflow_call复用。 - 运行条件(可从文件头部
if表达式读出):仓库必须是官方主仓库;Issue 尚未带有area/标签(避免重复分诊);issue_comment触发的重分诊要求评论包含@gemini-cli /triage且评论者为 OWNER/MEMBER/COLLABORATOR。
它做的事情,是把"给 Issue 打标签"这件事交给 Gemini 模型完成,流程如下:
-
准备上下文:将 Issue 标题与正文写入工作区的
issue_context.md文件("Prepare Issue Data" 步骤)。 -
列出可用标签:通过 GitHub API 拉取仓库标签,并硬编码一个
allowedLabels白名单(9 个area/*标签,见工作流文件第 118–128 行),只允许模型在这些标签中做选择。 -
运行 Gemini CLI 分析:使用
google-github-actions/run-gemini-cli动作,提示词要求模型:- 用
read_file读取issue_context.md; - 根据内嵌的 "Reference 1: Area Definitions"(对每个
area/*的定义与典型 Issue 举例)选出恰好一个area/标签; - 无法确定时回退到
area/unknown; - 只输出 JSON:
{"labels_to_set": ["area/core"]}。
这里的安全约束值得注意:运行环境显式设置
GITHUB_TOKEN: ''(注释写明"此处不传任何认证 token,因为这是在不可信输入上运行"),并通过settings把可用工具限制为run_shell_command(echo)和read_file,maxSessionTurns限制为 25。也就是说,分诊 Agent 是一个只读、无凭证、工具白名单收窄的受限实例。 - 用
-
容错解析模型输出:
apply-issue-labels相关脚本先直接JSON.parse模型输出;失败后尝试从 Markdown 代码块(json ...)中提取;再失败则用正则(\{[\s\S]*"labels_to_set"[\s\S]*\})在含调试日志的输出里定位 JSON 对象。解析后还会验证"必须恰好一个标签",先移除旧的冲突area/*标签再添加新标签(见工作流文件第 296–375 行)。 -
失败兜底:如果 Gemini 分析步骤失败,会在 Issue 下发布评论提示查看 Action 运行日志("Post Issue Analysis Failure Comment" 步骤)。
贡献者该做什么:尽量完整填写 Issue 模板(仓库提供了 bug_report.yml、feature_request.yml 等模板);如果被打上 status/need-information,在评论中补充缺失的日志或复现步骤。
工作流二:PR 的持续集成(CI)
- 工作流文件:ci.yml
- 触发时机:每次推送到 PR(
pull_request,目标分支main与release/**)、直接推送这些分支(push)、合并队列(merge_group)以及手动触发。
从源码看,CI 由七个并行 Job 组成,最后由一个聚合 Job 汇总判定:
| Job | 做什么 |
|---|---|
lint |
依据 .nvmrc 装 Node 后依次执行:ESLint、actionlint、shellcheck、yamllint、npm run build + npm run typecheck、Prettier、设置文档一致性检查(npm run docs:settings -- --check)、敏感词检查、GitHub Actions 版本钉死检查(--check-github-actions-pinning)。所有 linter 统一由 scripts/lint.js 驱动 |
link_checker |
用 lychee 检查仓库内所有 .md 文件中的链接有效性 |
test_linux |
在 Node 20.x/22.x/24.x × 两个测试分片(cli / others)矩阵上运行 npm run test:ci,随后 npm run bundle 打包并做 node ./bundle/gemini.js --version、npx 安装冒烟测试;非 fork 仓库用 dorny/test-reporter 发布 JUnit 报告,fork 仓库则上传产物 |
test_mac |
与 Linux 相同的矩阵,另上传 coverage 报告 |
codeql |
对 JavaScript 做 CodeQL 静态安全分析 |
bundle_size |
用 preactjs/compressed-size-action 监控 bundle/** 产物体积变化(阈值 1000 字节) |
test_windows |
在 16 核 Windows 自定义 Runner 上跑"慢测试",60 分钟超时 |
几个从配置中能读出的工程细节:
- 每个仓库 Job 都先经过
merge_queue_skipper,用于合并队列场景下避免重复跑 CI; - Linux 测试前会安装
bubblewrap(沙箱依赖)并放宽 AppArmor 限制(Ubuntu 24.04+ 需要); - 测试环境变量包含
GEMINI_CLI_TRUST_WORKSPACE: true,即在 CI 中预信任工作区; - 测试按 workspace 分片:
cli分片只跑@google/gemini-cli,others分片跑@google/gemini-cli-core、@google/gemini-cli-a2a-server、gemini-cli-vscode-ide-companion、@google/gemini-cli-test-utils以及npm run test:scripts,保证分片互不重叠; - 最终
ciJob 逐一检查七个 Job 的needs.*.result,任何一个非 success/skipped 即整体失败。
关于"Post Coverage Comment":文档提到测试全部通过后机器人会在 PR 上发布覆盖度总结评论。该能力由 post-coverage-comment 复合 Action 实现:用 jq 从 CLI 与 Core 两个包的 coverage-summary.json 中取出 Lines/Statements/Functions/Branches 百分比,拼成 Markdown 表格(附可折叠的完整文本报告),并通过 thollander/actions-comment-pull-request 以 code-coverage-summary 标签写入或更新评论——同一标签下重复运行是"更新"而非"新发",避免刷屏。
贡献者该做什么:确保所有检查通过(绿色对勾);出现红色叉号时点击 "Details" 查看日志、定位问题并推送修复。
工作流三:PR 的持续审计与标签同步(PR Auditing and Label Sync)
- 工作流文件:gemini-scheduled-pr-triage.yml
- 触发时机:
cron: '*/15 * * * *',即每 15 分钟对所有打开的 PR 审计一次,也可手动触发;整个 Job 限时 15 分钟。
核心逻辑全部在 Bash 脚本 pr-triage.sh 中(约 183 行),可以逐段读懂:
- 批量拉取:
gh pr list --state open --limit 1000,一次性取回每个 PR 的number、isDraft、closingIssuesReferences、body、labels五个字段,再用jq压缩成 TSV 逐行处理。关联 Issue 的识别优先用 GraphQL 的closingIssuesReferences,兜底才用正则从正文提取#数字。 - 无关联 Issue 的处理:
- 非 Draft PR:添加
status/need-issue标签(如尚未有),并把 PR 编号记入prs_needing_comment输出,供后续步骤通知; - Draft PR:相反地移除
status/need-issue——草稿阶段不催关联 Issue。
- 非 Draft PR:添加
- 有关联 Issue 的标签同步:先移除
status/need-issue;然后拉取 Issue 标签,但只关心area/*、priority/*、help wanted、🔒 maintainer only这几类(脚本第 43 行的grep -x -E过滤),把 PR 上缺失的补齐。Issue 标签有进程内缓存(兼容 Bash 3.2 的扁平字符串缓存)避免重复请求 API。 - 原子更新:最终用一条
gh pr edit --add-label ... --remove-label ...完成增删。
一个微妙的设计点:同步方向是 Issue → PR 单向补齐(只添加、只移除 status/need-issue),不会把 PR 上多余的 area/*/priority/* 删掉,因此不会误伤人工调整。
贡献者该做什么:永远把 PR 关联到 Issue(在描述中写 Resolves #<issue-number> 之类),这是整个流程中最重要的一步——它会保证 PR 被正确归类并顺畅地走评审。
工作流四:Issue 的定时兜底分诊(Scheduled Issue Triage)
- 工作流文件:gemini-scheduled-issue-triage.yml
- 触发时机:
cron: '0 * * * *',每小时对全部打开的 Issue 运行一次(Job 限时 60 分钟),也支持手动触发。
它是即时分诊的"安全网",但能力比即时版更强。从源码看,一次定时运行会依次做:
- 同步 Issue 类型:sync-issue-types.cjs 通过 GraphQL 查询最近更新的 50 个打开 Issue,对尚未设置 GitHub Issue Type 的,依据
kind/bug或kind/feature/kind/enhancement标签回填 Bug/Feature 类型(运行前后各同步一次)。 - 找出冲突 Issue:一次拉取最多 2000 个打开 Issue,筛出带多个
area/或多个priority/标签的(上限 50 个)——违反"每类唯一"约束。 - 找出漏网 Issue:分别检索缺失
area/*、缺失kind/*、缺失priority/*的 Issue(各限 50 个),合并去重;另外为area/core、area/extensions、area/site、area/non-interactive中的 Issue 单独检查缺失的effort/*标签(限 20 个)。 - 标准分诊(Gemini):与即时分诊相同的方式运行
run-gemini-cli(模型gemini-3-flash-preview,工具白名单只有echo与read_file,不传GITHUB_TOKEN),但提示词是一整套标签策略:- 已有唯一
area/、唯一kind/、唯一priority/的不动;缺失或有冲突时必须恰好选一个,冲突项写入labels_to_remove; - P0 不能自动打:判定为 P0 时改打
priority/p1+status/manual-triage,留给人工升级; - 对
kind/bug检查正文中的 CLI 版本:若比当前版本(运行时从 package.json 读取)旧超过 6 个 minor 版本,打status/need-information并留言请用户在最新版上复测;信息不足(如缺复现步骤、缺版本号)同样打status/need-information; - 输出 JSON 数组,每个对象含
issue_number、labels_to_add、labels_to_remove与面向用户措辞的explanation(明确规定解释中不得暴露标签机制)。
- 已有唯一
- 工作量分诊(Effort,Gemini):这是定时分诊独有的深度环节。针对缺
effort/*标签的 Issue,提示词要求模型扮演"资深软件架构师",必须用grep_search、glob、read_file实际搜索代码库定位涉及的文件与组件后再评级,并输出必须引用具体文件路径、禁止"likely"式猜测的effort_analysis。分级标准内嵌在提示词中:effort/small(≤1 天):schema 更新、单文件逻辑修复、UI 微调、文案修改;effort/medium(2–3 天):React/Ink 状态管理调试、异步流与 IDE 伴生扩展问题、跨包(packages/cli与packages/core)重构;effort/large(3+ 天):node-pty/信号等跨平台复杂度、Scheduler 与 A2A/MCP 协议级重构、大规模性能内存问题;- 并特别规定:间歇性、难复现、平台相关的缺陷不得评为
effort/small。
- 应用与清理:由 apply-issue-labels.cjs 统一应用标签;随后 cleanup-triage-labels.cjs 清理互斥的
status/*组合(如同时带status/bot-triaged与status/need-triage的 Issue)。
贡献者该做什么:通常什么都不用做——这正是"兜底"工作流,确保即使即时分诊失败,每个 Issue 最终也会被归类。
工作流五:自动移除失联的 Issue 处理人(Unassign Inactive Assignees)
- 工作流文件:unassign-inactive-assignees.yml
- 触发时机:每天 09:00 UTC(
cron: '0 9 * * *'),也可手动触发并传dry_run: true做无副作用演练。
目的:让 help wanted Issue 的处理权保持在流动状态。完整算法(内嵌在该工作流的 github-script 步骤中,约 250 行):
- 找出所有带
help wanted标签且至少有一个 assignee 的打开 Issue; - 豁免特权用户:三个组织团队(
gemini-cli-maintainers、gemini-cli-askmode-approvers、gemini-cli-docs)成员、仓库协作者中权限为admin/maintain/write/triage者、以及googlers/google组织成员,全部跳过——他们永远不会被自动移除; - 对每个普通 assignee 读取 Issue 时间线(
assigned/unassigned事件确定精确的分配时刻,找不到事件则回退到 Issue 创建时间); - 从时间线的
cross-referenced事件中提取所有关联 PR,逐个拉取详情验证"就绪"状态:open 且非 Draft,或已 merged;Draft PR 明确不算数(工作流头部注释解释了原因——防止贡献者用一个空 Draft PR 刷掉检查); - 若分配超过 7 天(
GRACE_PERIOD_DAYS = 7)且无合格 PR,则调用removeAssignees并留言,说明原因、如何重新认领(评论/assign)以及如何避免再次发生(7 天内开出含Fixes #<issue-number>的非 Draft PR)。
贡献者该做什么:
- 被分配后 7 天内开一个真正可评审的 PR(非 Draft),并在描述中写
Fixes #<issue-number>;Draft PR 不满足条件; - 若被误移除,评论
/assign重新认领; - 若放弃处理,评论
/unassign释放给其他人。
工作流六:按改动规模自动打 PR 标签(PR Size Labeler)
- 工作流文件:pr-size-labeler.yml
- 触发时机:PR 创建、同步(推送新提交)、重新打开(
pull_request_target,注意它运行在主仓库权限下),也可用workflow_dispatch手动指定 PR 编号。
实现是一个纯 gh CLI 脚本,逻辑清晰可逐行对照:
- 自愈标签:先
gh label create确保五个size/*标签存在(带规定颜色与描述),失败则忽略; - 单次 API 取数:
gh pr view --json additions,deletions,changedFiles,labels一次拿全,TOTAL = additions + deletions; - 计算标签(边界与文档一致):
| 标签 | 改动行数 |
|---|---|
size/XS |
< 10 |
size/S |
10–49 |
size/M |
50–249 |
size/L |
250–999 |
size/XL |
≥ 1000 |
- 原子更新:对比现有标签,只把"新增正确标签 + 移除过时标签"合并成一条
gh pr edit调用; - 防刷屏评论:构造形如
📊 PR Size: **size/M** …的评论后,先在 Issue 评论区检索由github-actions[bot]发布且以📊 PR Size:开头的旧评论——找到就PATCH原地更新,找不到才新建。这样无论推多少轮提交,PR 时间线上永远只有一条尺寸说明。
贡献者该做什么:无需任何操作,标签与评论会随推送自动更新。
工作流七:发布自动化(Release Automation)
- 工作流文件:release-manual.yml
- 触发时机:官方 patch/minor 版本通过
workflow_dispatch手动触发;nightly 版本由定时任务(仓库另有 release-nightly.yml)触发。
手动发布工作流的 workflow_dispatch 输入本身就是一套发布参数表:
| 输入 | 说明 |
|---|---|
version |
要发布的版本号(带 v 前缀的合法 semver,如 v0.1.11) |
ref |
发布所用的分支、tag 或 SHA |
npm_channel |
发布渠道:dev / preview / nightly / latest(默认 latest) |
dry_run |
干跑模式(默认 true):不创建分支、不发布 npm 包、不创建 GitHub Release |
force_skip_tests |
是否跳过测试步骤(默认不跳过,正式版本应跑测试) |
skip_github_release |
是否跳过创建 GitHub Release(仅 prod 环境有意义) |
environment |
prod / dev |
执行链:先通过可复用工作流构建 macOS 二进制,再在 release 子目录检出目标 ref、npm ci、下载二进制、计算 PREVIOUS_TAG(git describe --tags --abbrev=0)、可选跑测试,最后调用 publish-release 复合 Action 完成版本号提升、发布到 npm 并创建带生成说明的 GitHub Release。若正式发布(dry_run == false)中途失败,会自动创建一个带 release-failure,priority/p0 标签的 Issue 并附上运行链接——发布失败本身也被纳入了 Issue 分诊体系。
贡献者该做什么:不需要参与发布流程;PR 合入 main 后,其变更会进入下一次 nightly 发布。
小结:这套自动化体系的设计要点
把七条工作流放在一起看,可以提炼出几个可复用的模式:
- 模型做判断,脚本做执行:Gemini 只负责输出结构化 JSON(选哪个标签、为什么),真正调用 GitHub API 增删标签的始终是确定性脚本,且对模型输出做多级容错解析与数量校验;
- 最小权限 + 不可信输入隔离:在用户可控的 Issue 正文上运行的 Gemini 实例不持有任何 GitHub token,工具白名单收窄到只读;
- 幂等与防刷屏:评论一律"找到就更新"(覆盖率评论按 tag、尺寸评论按前缀匹配),标签操作先对比再批量原子提交,定时任务只处理"缺失或冲突"的增量;
- 分层兜底:即时分诊(秒级)→ 每小时定时分诊(补齐漏网 + 冲突治理 + 工作量评估)→ 每天清理失联 assignee,层层递进保证仓库状态最终一致;
- 人机边界清晰:P0 永远留给
status/manual-triage人工升级,特权用户永远豁免自动 unassign,自动化只处理机械性环节。
对希望为自己的开源仓库搭建类似体系的开发者而言,本仓库的 docs/issue-and-pr-automation.md 是"给用户看的说明",而 .github/workflows/ 与 .github/scripts/ 目录则是"可直接抄的参考实现"——从触发器设计、标签约束策略到防重复注释的小技巧,都能从中找到答案。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00