mcpField
G
green-gate

green-gate

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

关于 green-gate

面向 Dart 和 Flutter 包的质量门控技能,循环处理分析、格式、测试和覆盖率,直至同一轮观察中全部通过。它通过 MCP 工具执行这些检查,并在进展停滞或决策不明时升级处理。

主要功能

  1. 01分析、格式、测试与覆盖率门控
  2. 02每轮重新检查所有门控
  3. 03自主修复客观失败
  4. 04依据覆盖率改进测试
  5. 05遇阻或有歧义时升级

适用场景

  1. 01发布前检查 Dart 包
  2. 02处理分析器与测试失败
  3. 03为未覆盖代码补充测试
  4. 04验证 Flutter 包质量

MCP 相关

使用 MCP 工具执行 Dart 分析、格式化和覆盖率测试。文档所述的 Claude Code 工作流禁止使用等效 Shell 命令;其他环境在 MCP 服务不可用时可改用命令行。

安装

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

$npx skills add https://github.com/verygoodopensource/vgv-ai-flutter-plugin --skill green-gate

安装后在 Claude Code 中输入/green-gate确认已加载。

附带文件agents/openai.yamlreferences/coverage.md

详细介绍

Green Gate

面向 Dart 和 Flutter 软件包的自主质量门禁循环。运行四道门禁—— 分析、格式化、测试、覆盖率——读取真实工具输出,编辑代码和测试以修复失败, 并持续循环,直到最后一次迭代同时证明四道门禁全部通过,且有观察到的数值。 针对客观失败自主处理;只有遇到停滞、确实存在歧义或基础设施故障时才升级处理。

此技能负责编排工具和编辑文件。测试编写的 方法 由 testing 技能负责—— 本技能不会重复介绍模拟、结构或覆盖率模式的指导内容。


核心标准

所有质量门禁工作都应遵循以下标准:

  • 只使用 MCP 工具,绝不使用等效的 Bash 命令——通过 mcp__dart__analyze_files 进行分析,通过 mcp__dart__dart_format 进行格式化,通过 mcp__very-good-cli__test 进行测试和覆盖率检查。每道门禁都有 MCP 工具;没有任何门禁 通过 shell 命令运行。Bash 测试路径(very_good test、flutter test、dart test) 会被 block-cli-workarounds.sh 钩子拦截并拒绝,而通过 Bash 运行 dart analyze / dart format 则与 MCP 工具重复。 循环中唯一会运行的 Bash 命令是用于解析 coverage/lcov.info 的备用方案, 且仅在测试结果不包含 Lines not covered: 块时才运行。这条仅使用 MCP 的规则是由该 钩子强制执行的 Claude Code 限制;该钩子不会在其他宿主上运行。跨执行环境备用方案: 如果宿主上没有该钩子,也没有连接 MCP 服务,则改为运行等效的 dart analyze、dart format 和 very_good test CLI 命令——绝不要因缺少 MCP 服务而停滞。
  • 仅制定计划的请求仍属于此技能的职责——当用户询问要使用哪些 工具、参数或门禁运行顺序,且暂时不希望实际运行时,应依据此技能作答:按门禁顺序 调用相同的工具(使用 applyFixes: true 的 mcp__dart__analyze_files、 mcp__dart__dart_format、使用覆盖率参数的 mcp__very-good-cli__test),并说明 决定顺序重要性的优先级规则。绝不要用临时拼凑的 shell 计划(flutter analyze / flutter test --coverage)替代循环实际运行的工具。
  • 绝不缓存通过结果——每一轮都重新评估每道门禁。修复分析或测试失败,以及编写新的 测试文件,都会改变格式和覆盖率分母,因此此前通过的门禁可能再次失败。
  • 仅在观察到数值后退出——只有在最后一次迭代中,分析结果干净、格式化报告的更改数为 零、所有测试通过且满足 min_coverage,并且这些结果都在同一轮中观察到时,循环才终止。
  • 同时传入 coverage: true、min_coverage、check_ignore: true 和 show_uncovered: true——省略 coverage: true 会静默地导致不生成 lcov.info(类似于 配置错误);省略 check_ignore: true 会让 // coverage:ignore 补救措施失效;省略 show_uncovered: true 会从结果中删去按文件列出的未覆盖行,导致循环转而读取 lcov 文件, 而这些信息原本可以由工具直接返回。show_uncovered 从 very_good_cli 1.5.0 开始提供,这是插件的最低版本要求。
  • 将测试编写交由 testing 技能负责——当修复需要编写测试时,应遵循该技能对结构、 模拟和命名的指导。
  • 修复根本原因,而非表面症状——绝不要通过放宽门禁来通过检查(不要删除失败断言、 降低目标以逃避工作,或对可达代码使用 // coverage:ignore)。遇到确实涉及产品或 API 决策的情况时,应升级处理,而不是自行猜测。
  • 针对客观失败自主处理——分析器错误、失败的测试和覆盖率缺口都应直接修复,无需再次询问。 仅在符合矩阵规定的情况下升级处理。

循环

对每个包根目录(参见递归/Monorepo),运行以下算法:

  1. 发现 — 确定包根目录(directory 参数或工作区根目录);确认其中存在 pubspec.yaml。初始化循环状态(迭代计数器 0、空指纹集、空的已触及文件集)。
  2. 分析 — 运行 mcp__dart__analyze_files,并设置 applyFixes: true。如果仍有错误,这就是当前未通过的门禁。修复错误,记录指纹,然后转到第 7 步。
  3. 格式化 — 对包根目录运行 mcp__dart__dart_format。该命令会原地重新格式化整个包。如果它报告有文件变更,则下一轮的此门禁通过。
  4. 测试 — 仅当本轮分析通过时才执行。使用测试门禁中的覆盖率参数运行 mcp__very-good-cli__test。如果测试失败,进行修复、记录指纹,然后转到第 7 步。
  5. 覆盖率 — 仅当本轮测试通过时才执行。MCP 的 min_coverage 结果是判断通过与否的权威依据,其 Lines not covered: 块会给出各文件的修复目标;仅当该块不存在时,才解析 coverage/lcov.info。如果覆盖率低于目标,则通过 testing 技能为覆盖不足的文件(按优先级排序)编写测试,然后转到第 7 步。
  6. 退出 — 如果同一轮迭代中四道门禁全部通过,则用观测到的数据确认成功并停止。这是唯一的通过后退出路径。
  7. 重新验证 — 递增迭代计数器,重新计算失败指纹,并检查升级触发条件(无进展、振荡、达到上限)。如果触发,则升级处理;否则返回第 2 步,重新评估每一道门禁。

一轮无操作路径 — 适用于原本就已通过的包:第 1 轮迭代发现分析无问题、格式化报告零变更、所有测试通过,且覆盖率达到或超过目标。循环确认全部通过并退出,不编辑任何文件。验证一个通过的包恰好需要一轮迭代。


循环状态

在各轮迭代间保留的状态——没有这些状态,就无法判断是否“没有进展”:

状态用途
迭代计数器强制执行迭代上限(默认每个包 5 轮)
各门禁的失败指纹(上一轮)检测无进展和振荡
本轮触及的文件区分无操作轮次和无进展轮次;填充升级报告

各门禁的指纹键:

门禁指纹
分析diagnosticCode @ file:line 的排序集合
格式化格式化门禁重写的文件集合(为空表示通过)
测试失败测试的 ID/名称集合
覆盖率观测到的百分比 + 覆盖不足的文件路径排序集合

定义:

  • 无进展 — 当前失败指纹与上一轮相同,或失败数量未减少。升级处理。
  • 振荡 — 同两道门禁在连续两轮中交替通过/失败(例如,格式化修复导致分析再次失败,而分析修复又导致格式化再次失败)。升级处理。

Gate 优先级

固定顺序:analyze → format → test → coverage。以下两条规则适用于此顺序:

  1. 只有本轮上游 Gate 通过后,才会评估下游 Gate。 Analyzer 报错可能导致测试无法编译;测试未通过时,覆盖率没有意义——编译失败后绝不诊断覆盖率。Format 在 analyze 之后运行,这样它的重新格式化就不会与 analyze 的 applyFixes 即将重写的代码反复冲突。
  2. 每一轮都重新评估每个 Gate;通过状态绝不缓存。 只有四个 Gate 在同一轮最终迭代中全部通过,才算完成。

Analyze Gate

  • 运行 mcp__dart__analyze_files 并设置 applyFixes: true,以便在返回诊断结果前应用快速修复。roots 接受 [{ root: "file:///abs/path" }]——也就是包根目录的 file: URI。没有递归标志;该技能会自行枚举包根目录并逐个传入。
  • 剩余 error 数量为零时,Gate 通过。将错误级别的诊断视为阻断项;如果警告和信息与修复范围相关,也一并处理,但不要让无关的既存信息阻断 Gate。
  • Hook 协作——PostToolUse analyze.sh Hook 会在每次 Edit/Write 后触发;如果修复引入了新的 analyze 错误,它会以退出码 2 阻断操作。将此类拒绝视为同一轮的 analyze Gate 反馈,而不是单独的失败类型:编辑并未生效,因此请修改后重试。

Format Gate

  • 每一轮都运行 mcp__dart__dart_format。roots 接受与 analyze_files 相同的 [{ root: "file:///abs/path" }] 格式;省略可选的 paths,使其就地格式化整个包。这样 Gate 便基于实际观察结果(能发现手动编辑和既存的格式偏差,而不只是循环编辑过的文件),且能自行修复(一次调用即可让包通过;第二次调用会报告没有更改)。
  • 查看更改数量,而非状态——该工具无论是否重写文件都会成功,因此非错误结果并不能证明什么。其输出末尾会显示 Formatted N files (M changed);只有 M 为 0 时 Gate 才通过。若某一轮 M 不为零,文件此时已修复,因此应在下一轮重新运行 Gate,以确认结果为 0,而不是在修复后直接判定通过。
  • 为什么 format 是真正的 Gate,而不只是 Hook——PostToolUse format.sh Hook 只会对循环通过 Edit/Write 编辑的文件触发。循环从未触碰过的手动编辑或既存未格式化文件,否则会通过 Gate,却在 CI 中失败。对整个包执行 format 可以弥补这一缺口。

Test Gate

  • 运行 mcp__very-good-cli__test,并设置 coverage: true、min_coverage: <target>、check_ignore: true、show_uncovered: true 以及 exclude_coverage glob(如下所示)。
  • Dart 与 Flutter——省略 dart,让工具自动检测(检测到 Flutter 项目时会运行 Flutter)。仅当该工具可能误判纯 Dart 包时,才传入 dart: true。
  • directory——当包不在工作区根目录时(例如 monorepo 子包),请传入该参数;只有 pubspec.yaml 位于根目录时才省略。
  • timeout_seconds——始终设置上限(例如 120)。Flutter 测试在 pumpAndSettle() 未设置超时时会无限期挂起;上限会终止运行,而不是让循环停滞。因超时而终止属于工具失败,不是测试失败——请按矩阵升级处理。
  • 所有测试均通过时,Gate 才通过。除非失败的测试体现了真正的产品决策(按矩阵升级处理),否则应自主修复。

覆盖率门槛

MCP min_coverage 结果是通过与否的权威依据;启用 show_uncovered: true 后,结果还会包含各文件的修复目标:

Expected coverage >= 100.00% but actual is 33.33%.
Lines not covered:
  - lib/src/weather_repository.dart: 4, 5

根据该区块对文件排序。只有结果中没有此类区块时,才将 coverage/lcov.info 解析作为备用方案:lcov 是某次运行留下的文件, 这次运行可能不是正在诊断的那次;而结果不会过时。

  • 默认目标为 100(VGV 的标准),可按调用覆盖 (例如 80),用于旧版软件包或非模板软件包。生成文件通过 exclude_coverage 排除在分母之外,check_ignore: true 会遵循 // coverage:ignore,因此 100% 表示所有可测试的手写代码均达到 100% 覆盖率。
  • 默认 exclude_coverage — **/*.{g,freezed,gen}.dart 以及生成文件目录和 l10n 目录。使用单个 glob 字符串(大括号展开;如果 CLI 构建不支持,则 回退到 **/*.g.dart)。
  • 同时传入 coverage: true、min_coverage、check_ignore: true 和 show_uncovered: true — 请参阅核心标准。
  • 如果结果中没有 Lines not covered: 区块,则按照 references/coverage.md 回退到 coverage/lcov.info — 其中涵盖 show_uncovered 本身、lcov 缺失的三种原因、 过时 lcov 的处理、lcov 记录字段(SF/LF/LH/DA)、 备用方案与门槛保持一致的规则,以及 check_ignore 仅适用于 Dart 的限制。

修复

  • 只修复失败项 — 处理本轮发现的问题、测试失败或覆盖率不足的文件。不要重构无关代码(YAGNI)。
  • 覆盖率修复 = 编写测试 — 针对每个排序后的覆盖率不足文件,按照 testing 技能编写测试。按未覆盖行数确定文件优先级 — 使用结果中列出的各文件行号,或使用 lcov 备用方案中的 LF - LH。
  • 限制每轮处理的文件数 — 修复一批相关文件,然后重新验证。每批修复后重新运行门槛检查,才能发现“没有进展”,并避免修复一个门槛时悄然破坏另一个门槛。

升级处理

遇到以下情况时停止并告知用户:

触发条件详情
轮次之间没有进展失败指纹相同或没有减少
来回振荡同两个门禁在两轮中交替变为绿灯/红灯
已达上限,门禁仍为红灯终止 — 按门禁报告剩余失败及其指纹
红灯测试确属产品缺陷看起来源于真实产品决策而非编码错误的失败
修复方式不明确存在多种有效解决方案(例如修改 API 或抑制 lint)——优先修复根本原因;若涉及产品/API 决策,则升级处理
不可达代码的覆盖率缺口建议使用 // coverage:ignore(需要设置 check_ignore: true,仅限 Dart),而不是追求 100%
分母规范生成文件未被排除 glob 匹配——扩大 exclude_coverage 的范围,而不是使用 // coverage:ignore
工具/钩子失败MCP 测试超时(timeout_seconds 触发终止)、分析器崩溃、因缺少 CLI 而被钩子拒绝(升级处理时附上安装提示 dart pub global activate very_good_cli),或修订后 analyze.sh _重复_拒绝,仍阻碍所需修改——单次拒绝属于循环内的分析门禁反馈(见 分析门禁),不应升级处理
升级问题时,请指出变红的门禁——analyze、format、test 或 coverage——
然后逐字给出其指纹条目(analyze 的格式为 diagnosticCode @ file:line,test
为失败的测试名称,coverage 为覆盖率不足的文件路径)、已修改的文件、迭代次数,以及用户必须做出的唯一决定。总结数量(“还剩 4 个错误”)不算报告;用户无法据此采取行动。

要求持续尝试的指令不能覆盖触发条件。“只要需要就继续重试,别回来找我”并不意味着可以把预算花在指纹已停止变化的情况上。达到触发条件时就停止、报告并等待。


递归 / 单体仓库

  • 所有包都必须通过——失败时继续处理(修复每个失败的包),然后确认每个包的结果。一个包的门禁变红不会中止其他包的检查。
  • 按包计算迭代预算——上限 5 次适用于每个包,而非全局。因此,包含 12 个包的单体仓库不会在第一个包上耗尽全局预算。
  • 共享包根目录发现机制——遍历查找 pubspec.yaml 文件。analyze_files 的 roots 集合必须与 mcp__very-good-cli__test --recursive(recursive: true)所覆盖的包集合一致。
  • lcov 备用路径为 <package>/coverage/lcov.info——根据发现的根目录逐一解析。
  • 单个 min_coverage 适用于所有包——已说明的限制:工具架构不支持按包覆盖率设置不同目标。确认覆盖率时,请说明这个共享目标。

其他资源

  • references/coverage.md — green-gate 的覆盖率门禁详情(默认目标、排除 glob、show_uncovered、lcov 备用路径及其字段、决策树、check_ignore、过时的 lcov)。
  • testing skill — 测试约定,以及用于弥补逐文件覆盖率缺口的覆盖率参考。

评论

支持 Markdown