Kilo Code Agent Manager 中的紧凑 PR Checks:按状态分桶、去重与持久化的实现方案
2026/9/13 21:29:59 网站建设 项目流程

Kilo Code Agent Manager 中的紧凑 PR Checks:按状态分桶、去重与持久化的实现方案

【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode

本文围绕 Kilo Code 仓库中的规划文档 plans/pr-checks-compact-plan.md 展开,讲解 Agent Manager 的 PR 面板如何将多达数十条的 GitHub 检查(checks)从“每行一条”的原始列表,重构为按状态分桶、按严重度排序、可折叠且失败永不隐藏的紧凑视图。读完后,你可以掌握一条完整的 UI 数据链路:宿主侧ghGraphQL 数据如何解析与去重、纯函数分组模块如何组织桶序与自然排序、组件与 CSS 如何呈现,以及展开状态如何在组件重挂载(remount)之后仍然保持。

目标:35 条全绿检查不该占 35 行

规划文档开篇给出了一条核心设计原则:

A green PR with 35 checks must not cost 35 rows. Show signal, hide noise, keep everything expandable, and never hide a failure.

重构前的现状是 PRChecks.tsx 按gh返回的原始顺序逐条渲染 check,没有分组、排序或去重。当 PR 挂着几十个矩阵化 job(如test (linux)test (windows)unit (windows, 10/6))时,面板被大量绿色行淹没,真正失败的检查反而要靠滚动才能找到。

文档明确把 GitHub CLI(cli/cli仓库的pkg/cmd/pr/checks/aggregate.gooutput.go)列为参考实现,并提炼出 8 条规则:

  1. 按状态桶(status bucket)分组,而不是按 app 或 workflow 分组;
  2. 桶的固定顺序为 failure、pending、cancelled、skipped、success;
  3. 桶内用“数值感知的自然序”(numeric-aware natural sort)按名称排序;
  4. failure 与 pending 组默认展开,success 与 skipped 组默认折叠;
  5. 使用计数标签,如1 failing check27 successful checks
  6. 矩阵 job 保持为独立行,不合并;
  7. 按“check 标识 + 最新 startedAt”去重,剔除陈旧的 rerun 记录;
  8. 汇总行用 tally 形式(5 pending · 1 failing),而不是X of Y

预期效果(来自规划文档的 Expected result 小节):

Checks 5 pending · 1 failing [Fix with Kilo] v 1 failing check Vercel - docs [link] v 5 pending checks test (linux) [link] typecheck [link] > 29 successful checks

全绿时则收敛为一行折叠的35 successful checks

数据入口:宿主侧解析、状态映射与去重

PR 面板的数据由 VS Code 扩展宿主侧从gh的 GraphQL 结果解析而来,核心逻辑在 am-pr-utils.ts。规划文档中“Host data”一节提出的三条要求,都可以在这份源码中找到对应实现:

状态映射:TIMED_OUT / STARTUP_FAILURE 算失败,EXPECTED 算 pending

checkStatus()负责把 GitHub 的状态枚举折叠为面板使用的 5 值类型CheckStatus(定义见 pr-types.ts 第 6 行):

  • SUCCESSNEUTRALsuccess(规划文档特别指出NEUTRAL应保留为成功,避免“中性结论”的检查被误读为异常);
  • FAILUREERRORACTION_REQUIREDTIMED_OUTSTARTUP_FAILUREfailure
  • PENDINGQUEUEDIN_PROGRESSREQUESTEDWAITINGEXPECTEDpending
  • SKIPPEDskippedCANCELLEDSTALEcancelled
  • 无法识别的未知状态兜底为pending,保证“不确定的东西不会伪装成绿色”。

按 check 标识 + 最新 startedAt 去重

checks()函数(am-pr-utils.ts 第 110–156 行)实现了文档第 7 条规则。它的做法是:

  1. 为每个原始 item 计算去重键:commit status 用status:<context>,check run 用run:<name>:<workflowName>:<event>——这就是文档所说的“check 标识”(check identity);
  2. 计算每个 item 的started时间戳:有startedAt就用它;没有的话,仍处于活动状态(PENDING/QUEUED 等)的记录取+Infinity,其余取-Infinity,保证活动中的重跑总是压过陈旧的完成记录;
  3. 相同键保留started最大的一条,最后按原始index排序输出,保持用户熟悉的相对顺序。

这样,一次失败后重跑成功的 workflow 不会留下两条记录;反之,一次陈旧的成功 rerun 也不会覆盖新的失败。

汇总语义保持不变

规划文档强调“Keep aggregate counts unchanged. Grouping is a view concern only.”。summarize()(am-pr-utils.ts 第 158–167 行)在去重之后计算total / passed / failed / pending / status,其中 skipped 不计入 total、cancelled计入 failed——这些聚合值继续供徽章(badge)与上层编排使用,分组只发生在视图层,不改动这一层。

纯分组模块:DOM-free、可单测

分组逻辑独立为 pr-check-groups.ts——这正是规划文档“Pure grouping module”一节要求新增的文件。整个模块只有 3 个导出函数和一个桶类型,没有任何 DOM 依赖:

export type CheckBucket = "failure" | "pending" | "cancelled" | "skipped" | "success" const ORDER: CheckBucket[] = ["failure", "pending", "cancelled", "skipped", "success"] const SORT = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }) export function groups(checks: PRCheck[]): CheckGroup[] { return ORDER.flatMap((bucket) => { const values = checks .filter((check) => check.status === bucket) .slice() .sort((a, b) => SORT.compare(a.name, b.name) || SORT.compare(a.url ?? "", b.url ?? "")) return values.length > 0 ? [{ bucket, checks: values }] : [] }) }

几个实现细节值得注意:

  • 单一共享的 CollatorIntl.Collator(undefined, { numeric: true })在模块级创建一次(实际还加了sensitivity: "base"),numeric: trueunit (windows, 2/6)排在unit (windows, 10/6)前面——这是文档第 3 条“natural numeric-aware name comparison”的直接落地,也是矩阵 job 命名(1/610/6)排序正确的前提;
  • 空桶被剔除flatMap中空桶返回[],所以一个全绿 PR 只产生一个success组;
  • 二级排序键:名称相同时按url排序,保证同名不同链接的 job 顺序稳定;
  • 默认展开规则expands(bucket):除successskipped外一律默认展开,即 failure/pending/cancelled 三个“需要行动”的桶默认打开——落实了“never hide a failure”;
  • 计数数据counts(checks):直接复用groups(),为本地化摘要提供每个桶的数量。

对应的单测 pr-check-groups.test.ts 覆盖了规划文档“Verification”小节的三项要求:桶顺序["failure", "pending", "skipped", "success"]、自然排序(2/610/6前)、以及expands()对各桶的默认展开断言。

组件层:PRChecks.tsx 的分组渲染

PRChecks.tsx 消费上述纯模块。与文档对照,几个关键实现点如下:

  • SectionHeading 承载 tallycount()memo 用counts()取各桶数量,按tallyLabel本地化后以分隔符连接。这里有一个额外的信号优先处理:只要存在任何非 success 的桶,汇总行就只显示信号桶(如5 pending · 1 failing),全绿时才退化为35 successful checks。样式上通过am-pr-checks-count-${status}类(定义于 pr-panel.css 第 184–191 行)给失败/等待状态着不同颜色;
  • 组标题可点击展开:每组是一个<button>aria-expanded反映当前展开态,chevron 图标随状态切换,符合文档“Keep group headings visually below the main Checks section heading”与“one status signal per row”的意图规则;
  • 行内不再重复状态文字:每行只剩图标 + 名称 + 可选时长 + 外链按钮,状态完全由data-status驱动图标着色(CSS 中.am-pr-panel-check-item[data-status="failure"] .am-pr-check-icon等规则),即文档所说“Remove duplicated per-row status words. Improve density by removing duplicate content, not by shrinking type.”;
  • Fix with Kilo保持在检查组之上checkFeedback()生成的修复建议按钮渲染在组列表之前,对应文档“KeepFix with Kiloabove the check groups”;
  • 内滚动的行阈值data-scrollable={checks.length > 12}在超过约 12 行时才给容器加滚动类([data-scrollable="true"]样式见 pr-panel.css 第 847 行),落实“Add an inner scrollbar only after a sensible row-count threshold is reached”;
  • 长名称与窄面板:名称走am-pr-check-name的省略号截断,外链收进带 Tooltip 的小按钮,避免在窄面板中换行挤掉控件。

矩阵 job 按文档要求保持独立行:分组只发生在桶维度,桶内部逐条列出,不做任何合并。

展开状态持久化:跨 remount 存活

文档的“Persistence”一节要求把 checks 区的展开态与每个桶的用户覆盖态存进 pr-comment-state.ts,并按 worktree 为键。该模块的注释解释了动机:任何一次短暂拿不到gh状态的轮询、worktree 重选、侧边栏切换都会重挂载 PR 面板,组件局部状态会随 remount 死亡。

CommentState接口为此扩展了两个字段(pr-comment-state.ts 第 36–37 行):

checksOpen: boolean // Checks 整区是否展开 checkGroups: Record<string, boolean> // 每个桶的用户覆盖

PRChecks.tsx 中的解析优先级是三级回落:

const groupOpen = (bucket) => state()?.checkGroups[bucket] ?? localGroups()[bucket] ?? expands(bucket)

即“持久化用户覆盖 > 无 worktreeId 时的局部信号 > 模块默认展开规则”。切换组时,有worktreeId就写patchCommentState(持久化),否则退回写局部信号。toggleOpen对 Checks 整区做同样处理。这样就实现了文档的两条要求:“Make every collapse reversible in one click and preserve it across remounts”——用户在 worktree A 折叠的 success 组,切走再回来时依旧折叠。

本地化与样式约束

规划文档还给出了一批“意图规则”(UI preferences),约束实现不得另起炉灶:

  • 复用 kilo-ui 组件与既有pr-panel.css类,不新增内联样式或裸 hex 色值——PRChecks.tsx 中确实只从@kilocode/kilo-ui引入 Button/Icon/Spinner/Tooltip,图标全部来自图标注册表(circle-checkcircle-x-outlinestopcircle-ban-signchevron-down等);
  • 所有用户可见文案——组标题(1 failing check/27 successful checks这类带{{count}}占位符的 one/other 复数形式)、行内状态标签、汇总分隔符、外链 Tooltip——都走useLanguage()t()agentManager.pr.checks.*键(见 PRChecks.tsx 中GROUP_KEYS/TALLY_KEYS两张键表),并需补进 Agent Manager 的全部 locale;
  • 样式取值“从现有样式表与设计令牌中取具体值,而不是发明固定值”,例如滚动阈值 12 与组标题的字号层级都锚定在既有pr-panel.css的规则上。

验证清单与明确不做的事

规划文档的 Verification 一节给出可执行清单,可作为后续维护该功能的回归基线:

  • 单测覆盖分组、自然排序、状态映射、去重(pr-check-groups.test.ts 已覆盖前三项的分组侧);
  • 测试断言成功组默认折叠、失败组默认展开、展开态在 remount 后保持;
  • packages/kilo-vscode/下运行bun run compilebun run test:unitbun run lintbun run knip以及 Agent Manager 架构测试;
  • 用同一视口与代表性 check 数据截取前后对比截图。

文档的 Out of scope 一节同样值得引用,它划定了这条方案的边界,避免读者误期待:

  • 必需检查徽章(required badges)——需要额外一次 GraphQL 请求;
  • 按 workflow 分组——GitHub 在 Checks 页签用它,但 merge box 场景不用;
  • 把矩阵 job 合并成一行——保持逐行,牺牲少量行数换取定位失败的精度。

小结:一条“视图关切”的完整链路

这套紧凑 PR checks 方案值得借鉴的地方在于职责切分得非常干净:去重与状态折叠在宿主侧 am-pr-utils.ts 完成(数据关切);桶序、自然排序、默认展开、计数在 DOM-free 的 pr-check-groups.ts 完成(可单测的逻辑关切);渲染、截断、内滚动阈值在 PRChecks.tsx 与 pr-panel.css(呈现关切);展开态持久化按 worktree 键控在 pr-comment-state.ts(状态关切)。聚合计数始终不随分组变化,失败与等待永远不会被默认隐藏——这正是文档开篇那句原则在每一层代码里的具体投影。

【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询