mcpField
T
triage-visual-changes

triage-visual-changes

github.com/uiverify测试与质量2548 次安装今天更新
代码仓库

关于 triage-visual-changes

triage-visual-changes 是一项通过 UI Verify 的 MCP 服务审查构建结果的 Agent 技能。它指导检查构建和故事、比较图像、查看评论,并记录视觉变更的处理决定。

主要功能

  1. 01检查构建门禁和故事变更
  2. 02比较基线图像与候选图像
  3. 03查看 AI 判定和人工评论
  4. 04记录决定并管理评论
  5. 05汇总 PR 范围的视觉变更

适用场景

  1. 01分诊构建中的变更故事
  2. 02在 PR 审查中识别视觉回归
  3. 03接受变更前检查审查评论
  4. 04将 PR 累积变更与基线比较

MCP 相关

它将 MCP 客户端连接到 `https://uiverify.ai/mcp` 上的远程 Streamable-HTTP 服务。该服务提供构建、故事、图像、评论和审查决定相关工具。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/uiverify/uiverify --skill triage-visual-changes

安装后在 Claude Code 中输入/triage-visual-changes确认已加载。

详细介绍

在 Agent 中处理 UI Verify 构建的审查

当构建结果为 changed 时,仪表板会为每个故事显示一组三联图(基线 / 候选 / 差异)。此技能可让你通过 UI Verify MCP 在 Agent 中完成审查:查看变更内容,在数值含义不明确时查看像素图,然后总结或接受。

连接 MCP(仅需一次)

该服务是一个远程 Streamable-HTTP MCP,地址为 https://uiverify.ai/mcp。你只需在浏览器中使用 UI Verify 账号登录一次(OAuth),这一个连接便可访问你所属所有团队中的所有项目,因此无需为每个项目单独设置,也无需在配置中填写 API 密钥。请将其作为真正的 MCP 服务添加到 Agent 中,不要自行编写 curl 命令调用端点。 使用原始 curl 时,图像工具会返回 MCP 图像块,Shell 无法渲染,因此脚本无法利用这些像素;原生客户端则能直接将图像显示给模型。

已安装 UI Verify 插件的 Claude Code:插件已连接此服务。运行 /mcp,选择 UI Verify 服务并登录;无需执行下方的命令。

Claude Code — 在项目根目录运行:

claude mcp add --scope project --transport http uiverify https://uiverify.ai/mcp

然后重启 Claude Code,运行 /mcp,选择 uiverify 并登录。--scope project 会将服务写入已提交的 .mcp.json;其中不包含机密,因此整个团队都能使用,每位团队成员使用自己的账号登录即可。

Cursor — 将配置添加到 .cursor/mcp.json(提交该文件即可共享),然后在 Cursor 的 MCP 设置中登录:

{
  "mcpServers": {
    "uiverify": { "url": "https://uiverify.ai/mcp" }
  }
}

其他任何 MCP 客户端(VS Code、Codex、Copilot、Gemini):将其指向同一个 Streamable-HTTP URL,然后按提示登录。添加服务后,重启 Agent 会话,以便加载工具。如果客户端只能手动通过 HTTP 访问,请优先使用 get_diff 工具(它会返回可下载的图像 URL),而不是 render_diff_image(内嵌像素图)——请参阅下方的工具表。

它提供的工具:

工具功能关键参数
list_projects列出你可访问的所有项目,包括其 projectId、团队、名称、类型、仓库和最近构建时间。只有在你可访问多个项目时才需要:commitSha / prNumber 查询和 list_builds 随后都需要提供 projectId(只有一个项目时会自动使用;调用存在歧义时会返回错误并列出这些项目)无
list_builds列出近期构建及门禁状态,以找到要检查的构建projectId?、branch?、status?、limit?
get_build对单个构建进行精简排查——包括门禁、counts {total, changed, failed, unchanged}、整个构建的评论统计 comments {total, unresolved}、AI 统计,以及已更改 / 失败故事的第一页(25 条)(每条包含一个 diffResultId、差异百分比;启用 AI 审查时,还包含评审器的 aiVerdict、aiConfidence,以及回归项的 aiFlagReason=一句话说明“哪些内容看起来并非有意更改”);页面会附带一个 nextCursor。它不会列出未更改项——counts.unchanged 会提示其数量;使用 list_build_stories 翻页查看其余内容。comments.unresolved > 0 表示有人类 / Agent 留下了未解决的备注——在接受前先用 list_comments 阅读以下之一:commitSha / prNumber / buildId
list_build_stories按状态分页浏览单个构建中的故事——这是查看 get_build 首页之外所有已更改 / 失败 / 未更改项的唯一方式。get_build 返回的 counts.unchanged 是可分页获取的确切数量。每条已更改故事的记录(此处和 get_build 中)都包含自己的 comments {total, unresolved}选择器,status: changed | unchanged | failed、cursor?、limit?(默认 25,最大 100)
get_diff获取单个故事的差异指标和预签名图片 URL(基线 / 候选版本 / 差异),可将其下载到文件,或直接链接到 PR 中;启用 AI 审查时,还包含评审器的完整评估:aiVerdict、aiConfidence、aiSummary(更改内容)、aiReasoning(原因)、aiFlagReason;以及单个故事的 comments {total, unresolved}(与 get_build 中相同的提示信息,但仅针对该故事)。传入 storyId 可获取单个故事,即使它没有发生变化——你会得到其当前基线 URL,因此可以在通过的构建中显示“与基线完全相同”相同的选择器,可选 storyId
render_diff_image内联呈现单张图片的实际像素,供视觉模型查看(不是 URL):baseline / candidate / diff;或使用 before_after,显示放大到更改区域的前后裁剪图——当故事在多个相距较远的位置发生变化时(例如页眉和页脚),会按区域各生成一张裁剪图并堆叠显示。如果故事有未解决的审查评论,会在图片之前显示一条说明文字diffResultId(或未发生变化的故事所用的 storyId)、which
get_pr_changeset获取 PR 与基线之间的累计变更集:整个 pull request 相对于其合并目标分支对 UI 所做的更改——包括 new / changed / removed 故事,每个故事都带有审查状态(启用 AI 审查时还包含评审器的判定),以及 counts {new, changed, removed, unchanged} 和每个列表的第一页(25 条)。不同于 get_build,后者代表单个提交的门禁:此结果在 PR 内接受更改后以及合并后仍然有效,并能捕获先添加后接受的故事,以及任何单个构建差异都无法显示的已移除故事以下之一:commitSha / prNumber / buildId
list_pr_stories分页查看 get_pr_changeset 首页之外的 PR 与基线变更集中的一个类别选择器,kind: new | changed | removed、cursor?、limit?
review_diff记录对单个故事的决定。如果故事有未解决的审查评论,拒绝执行 accept 和 ignore;deny 始终可用diffResultId、decision: accept | deny | ignore
accept_build一次性接受构建中的所有已更改故事。如果任何已更改故事有未解决的审查评论,则会拒绝执行且不接受任何内容——错误会列出这些故事相同的选择器
list_comments阅读设计师 / QA(或其他 Agent)在构建差异上留下的审查评论——构建中的所有未删除评论,每条都包含其 diffResultId、authorType、body、side、parentId(回复所属的讨论串根评论)、anchor(图片上的点 / 区域)和 resolved。这是 get_build / get_diff 中 comments 计数所指向的内容以下之一:commitSha / prNumber / buildId
post_comment在单个故事的差异上留下评论(或带讨论串的回复)——例如回复审查者的问题,或向他们指出回归问题。评论作者为 Agent;如果分支上存在更新的构建,则不允许添加新的根评论(请在最新构建上评论),但始终允许回复选择器 + diffResultId、body、side?、anchor?、parentId?(回复某个根评论的 id)
resolve_comment备注处理完毕后,解决(或重新打开)评论讨论串commentId(讨论串根评论)、resolved
update_comment / delete_comment编辑或删除评论——Agent 只能操作由 Agent 撰写的评论commentId(更新时还需提供 body)

配方 1 —“对这次构建进行分诊”

将变更分到不同类别,让人看到的是关键信息,而不是 60 行内容。针对这个 PR 调用 get_build——它会提供第一页变更中每个故事的 AI 裁判结论(aiVerdict;如果是回归,还会提供 aiFlagReason);如果 counts.changed 大于该页的数量,就用 nextCursor 持续分页调用 list_build_stories { status: "changed" },直到没有更多结果,避免回归漏过分页边界。先阅读裁判结论,再形成自己的判断;对于任何确实发生了内容变化的故事,都要对照变更前的图像进行评判,而不是孤立地看变更后的图像:

  • 渲染两张图,而不只是差异图。 使用 render_diff_image,分别指定 which: "baseline" 和 which: "candidate",然后进行比较。绿色差异蒙版会告诉你像素在哪里移动了,却不会告诉你结果好不好——深色背景上的代码块、失去对比度的文字、被裁切的控件,在蒙版中看起来都只是“有些像素变了”,只有对比前后图像才能看出这些回归。只看候选图像,就会让“现在有颜色了 → 有改进”这样的判断放过一个无法辨认的代码块。对于被裁判标记的问题,调用 get_diff 查看裁判的 aiReasoning / aiFlagReason。

  • 先阅读人的评论,再做决定。 如果 get_build 的 comments.unresolved > 0(或某个 get_diff 故事显示了 comments),就说明设计师、QA 或其他 Agent 在这次构建中留下了备注——list_comments 可以读取这些备注,每条备注都关联到其 diffResultId,以便你将备注与分类中的故事对应起来。像素无法体现备注中的上下文(“这个间距是有意为之,请勿还原”/“这里的 logo 不对”),所以在对故事分类之前要先阅读备注。无论如何,你都不能直接接受带有未解决评论的故事:accept_build 和 review_diff 的接受/忽略操作都会拒绝处理带有未解决评论的故事。处理流程是:阅读备注 → 按备注要求处理(通常需要修改代码并重新推送)→ resolve_comment → 接受。绝不要只为解锁接受操作而解决备注;只有在确实处理了备注所述问题后,才解决它。

使用以下四个类别报告,并列出真实的故事名称和变化百分比:

  1. 回归 — 确实发生了错误的变更 — 内容发生了变化,但变更后的结果有问题或更糟:文字无法辨认/对比度过低、内容被裁切、发生重叠、内容缺失,或控件看起来不再像原来的控件。当前 PR 导致的回归仍然是回归——“我的差异能解释它”并不能说明它是正确的。绝不要接受这些变更;这正是进行分诊的原因。
  2. 预期变更 — 内容发生了变化,且变更后的结果正确,符合 PR 声明的意图(刻意调整样式、新文案、真正新增的故事)。这些变更仍需要人工检查,但可以考虑接受。
  3. 外观重排 — 确实有差异像素,但差异图显示的是相同内容仅移动了几个像素(例如某一行下移约 5 px、文字的影子向下偏移)。这不属于内容变更。
  4. 渲染噪声 — 可安全接受 — 图表边缘/抗锯齿线条上低于 0.4% 的抖动。纯粹的渲染抖动。

裁判给出的 regression 结论意味着要仔细检查,绝不意味着可以置之不理。 如果你打算把裁判标记的故事归为“反正是预期变更”,就必须从变更前后的图像中找到具体理由,反驳其 aiFlagReason,而不是说“只是颜色变了,应该没问题”。无法反驳时,就相信它。 示例输出格式:

67 个发生变化的故事 → 分类。 ⚠️ 回归(2) — 看起来不对的真实变化,请勿接受:SetupWizard/Playwright(8.3%, 代码块现在是深色背景配深色文字/无法辨认——评审判定为 regression),Card/Compact(5.1%,标题 被截断)…… 1. 预期变化(11) — 内容有变化,看起来正确,可作为接受候选:Hero/Default(14%, 新标题文案),…… 2. 视觉重排(9) — 像素确有变化,但仅是垂直位移:EventStatusCard/All Variants(4.1%, 行偏移约 5px),TestingInit/Playground(3.7%),…… 3. 图表抗锯齿噪声(6) — 边缘抖动低于 0.4%,可安全接受:FunnelChart/Smaller Screen(3.2%),UpliftChart(单个 1px 标记),…… 新故事(12) — 首次建立基线,没有可比较的内容:Button/AllVariants,……

切勿在分类步骤中接受任何内容——展示分类结果,由人工决定;或者在配方 3 中明确操作。

配方 2 —“总结视觉回归”

读取方式相同,但只输出需要人工处理的变化——先列出 ⚠️ 回归,再列出预期内容变化——采用 PR 评论格式,每项一行,不包含重排/噪声。将此评论发布到 PR:

🔎 视觉评审 — 1 项回归,2 项预期变化(共 67 项标记;64 项重排/噪声,见下方折叠部分)

  • ⚠️ SetupWizard/Playwright — 代码块深色背景配深色文字,无法辨认(8.3%,评审判定:回归)
  • Pricing/Card — CTA 按钮颜色从蓝色变为绿色(12.4%)
  • Nav/Header — 徽标放大 8px(3.1%)

配方 3 —“接受所有新基线”

如果所有变化都是预期的(有意进行的重新设计),或全是噪声,且你已确认列表中没有隐藏的回归(⚠️ 分类为空),即可通过一次调用更新每个发生变化的故事的基线:

accept_build { prNumber: 123 }

不要因为“标题变化是预期的”,就认为整个构建中的变化都理所当然——全局 CSS 或主题更改会波及 PR 原本无意修改的故事;“这是 PR 导致的”不等于“这是 PR 预期的”。每个发生变化的故事都应根据其自身的前后对比单独分类。

如果要有选择地接受(部分变化是真实的,部分不是),请针对每个 diffResultId 循环调用 review_diff,使用 accept/ignore/deny——接受预期变化,忽略噪声,将真实回归留给人工处理。

配方 4 —“将前后对比放进 PR”

让人工直接在 PR 中查看,而不是让他们去仪表板。完成分类后,将发生变化的故事内嵌到 PR 中:

  • 最快的单张图片: render_diff_image 的 which: "before_after" 会生成一张 PNG,将前后版本并排展示,并裁剪到发生变化的区域(如果故事在多个相距较远的位置发生变化,则每个区域各裁剪一张并上下排列)——这是最省力的查看方式。保存图片并附上。
  • 可下载文件/持久链接: get_diff 会返回预签名的 baselineUrl/candidateUrl/diffUrl。下载这些文件(curl -L "$url" -o before.png)并附到 PR,或者在评论中附上构建链接和这些 URL。务必附上构建链接(https://uiverify.ai/builds/<id>),方便评审者查看完整的三联图。

一条好的 PR 评论应包含:配方 2 中逐项列出的回归摘要、回归项的前后对比图片,以及构建链接。直接发布评论,不要让他们自己去找。

配方 5——“这个 PR 整体对 UI 做了什么?”

get_build 回答的是单个提交的问题——此构建中是否有尚未审查的内容。它无法证明 整个 PR 实现了你的预期,而且从设计上说,它也看不到两种情况:你在 PR 较早阶段添加并接受的 故事(最新构建渲染正常,因此不会显示为已更改),以及已移除的故事(已删除或重命名的基线——没有构建会渲染它,因此不会有任何单次构建差异将其标出)。 只有 get_pr_changeset 能捕捉到这两种情况,所以在你宣布 PR 完成前,请先运行它。

获取该 PR 的 changeset,查看完整的 head 与 base 对比结果:

get_pr_changeset { prNumber: 123 }                  → new / changed / removed vs the base branch
list_pr_stories  { prNumber: 123, kind: "removed" } → page any one bucket

对每一项都用同一标准来判断:你能将它归因于此 PR 的 diff 实际做了什么吗?

  • 能,而且这是正确的 → 这是 PR 预期的视觉影响。
  • 无法归因于你自己的 diff 的更改 → 停下。不要接受,不要把它当作偶发问题忽略,也不要“重新运行看看”。说明情况并标记给人工处理——可能的原因是基线在不该变动时发生了变化,而这从设计上说不会发出提示。
  • 你不希望出现的 removed 故事 → 这是意外删除(或非预期重命名,表现为一项删除加一项新增)。请明确标出,不要让它带着绿色勾选通过。

changeset 已正式推出(GA)——无需启用任何标记;对于公开项目,任何人都可以在不登录的情况下查看构建的 changeset。

配方 6——“回复或解决审查评论”

如果审查者的留言是一个你能回答的问题,或指出了你刚刚修复的问题,就由 Agent 直接跟进,而不是让对方再来找你。使用 list_comments 读取讨论串,然后:

  • 在讨论串中回复——使用 post_comment { diffResultId, body, parentId },其中 parentId 是根评论的 id(回复会继承根评论所在的一侧,无需单独设置锚点)。用它说明你做出的 diff 如何回答“为什么这里移动了?”,或确认“已在下一次推送中修复”。
  • 标记回归问题——使用 post_comment { diffResultId, body, anchor? } 发布新的根评论,指出你留给审查者处理的故事(配合配方 1 中的 ⚠️ 分类使用)。
  • 解决——留言得到处理后(你已按要求修改,或已回答问题),使用 resolve_comment { commentId, resolved: true }。请在接受前先解决评论:存在未解决的讨论串时,accept_build / review_diff 会拒绝该故事。只有在讨论确实结束时才使用此操作;未解决的评论会阻止过早接受。

你的密钥会以 Agent 身份发布评论,且只能编辑或删除自己发布的评论——绝不要触碰人工审查者的评论。

构建通过了?你仍然可以查看像素

没有更改的构建不会显示三联图——但组件仍然经过渲染,而“没有变化”只有在你能看到时才值得信任。使用 list_build_stories { status: "unchanged" } 枚举未更改的故事(根据返回的 nextCursor 翻页),然后按 ID 获取任一故事当前的图像: 用 get_diff { buildId, storyId } 获取其基线 URL,或用 render_diff_image { storyId, which: "baseline" } 查看像素。你可以借此确认绿色构建中的组件显示正确,或在无需打开仪表板的情况下告诉审查者“看这里,它与基线完全一致”。

防护措施

  • 先阅读,再操作。 在调用 review_diff / accept_build 之前,始终先调用 get_build(对有歧义的内容,还要调用 render_diff_image)。不要接受尚未查看过的构建。
  • 比较前后,而非只看修改后。 对于内容有变更的任何故事,都要分别渲染 which: "baseline" 和 which: "candidate" 并查看两者——差异蒙版会显示变更位置,而两者的对比能看出情况是否变差(对比度 / 可读性、裁切、布局)。「有颜色了 / 移动了」不等于「变好了」。
  • 默认应相信 AI 评审器给出的 regression 信号。 要推翻它,必须根据修改前后的具体情况,说明其 aiFlagReason 为何不成立;不能含糊其辞。问题由这个 PR 导致,并不是忽略它的理由。
  • 批量接受会更改基线。 accept_build 会将候选版本设为所有变更故事的新基准——只有确认列表中没有隐藏的真实回归时,才应使用它。
  • 未解决的评论会阻止接受。 get_build 中的 comments.unresolved > 0 表示有人留下了你尚未阅读的备注——先通过 list_comments 查看并妥善处理,然后再调用 review_diff / accept_build。评审者评论「不要接受这个」,而 Agent 仍然批量接受,正是这个计数旨在防止的情况。
  • 区分「新故事」和「变更过的故事」。 首次建立基线的故事没有可供比较的差异;这不算回归,只需建立基线。请将其单独归类。

评论

支持 Markdown