关于 Verdict QA
Verdict QA 是面向软件团队的 QA Agent,依据测试和代码变更生成可追溯的质量判定,并将每次运行与已保存状态比较。可选的只读 MCP 服务让其他客户端查询 Verdict 状态、发现项、隔离的易 Flaky 测试和历史记录。
主要功能
- 01按稳定 ID 跟踪发现项及状态变化
- 02分类测试失败并隔离 Flaky 测试
- 03按证据生成四种最终判定
- 04记录并排序回归问题
- 05提供只读状态与历史查询
- 06支持多种编码 Agent 的 QA 技能
适用场景
- 01比较连续 QA 运行并识别回归
- 02复核失败测试并记录隔离期限
- 03让 CI 或客户端读取 Verdict 状态
- 04在发布前审查代码或规格
MCP 相关
可选的 Verdict MCP 服务以只读方式暴露 Verdict 状态,包括判定、发现项、Flaky 测试隔离信息和运行历史。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
10收录的工具列表 · 更新于 今天
此工具无需参数。
详细介绍
Verdict
CI · Verdict 对自身的评估 · 评测:8/8 个预置缺陷 · 固定规则:501/501 个突变均被捕获 · PyPI:verdict-qa-mcp · MIT 许可证
你的测试套件全绿。Verdict 发现了一个存在了 4,595 天的缺陷。
Verdict 是一个 QA Agent,不会修复问题、不会奉承,也不会遗忘。它先衡量,再下结论——测试框架会运行你的门禁,计算每一行发现所引用内容的哈希值,并在旧提交和新提交上重新运行负责防护的测试;它还会保留记忆:每次运行都会与上次进行差异比较,发现会记录存续时间,回归问题优先处理,测试者自身的漏报也会与命中一并公布。它所遵循的契约不可更改,并会写入每份判定的哈希值;它学到的内容会记录在契约旁,附有日期且可审计,但绝不会修改契约。上面的数字是真实的:一个拥有 7k★ 的 Python 库中的 FilePerms 自 2014-02-07 起便无法撤销权限位,而 Verdict 提交该发现当天,它的 625 项测试全部通过——运行记录和漏报都在台账中。
/plugin marketplace add ArtJack/verdict # Claude Code
/plugin install verdict@verdict
/verdict:run
npx skills add ArtJack/verdict # every other coding agent
大多数 AI“QA Agent”不过是一段满怀热情的话,再加上一份检查清单。它们每次都从头审查你的仓库,反复报告相同的 20 个问题,直到你不再阅读;把不稳定的测试称为“失败”,把过时的测试也称为“失败”,最后还要说一句“LGTM!🎉”。
Verdict 是一个 Claude Code 插件,按照 QA 的实际工作方式构建:
- 它会记住。 状态文件会保存基线。每条发现都有稳定 ID 和存续时间;每次运行都会报告
NEW / STILL_OPEN / RESOLVED / REGRESSED——回归问题始终优先处理。 - 测试变红,必有缘由。 每项失败都会分类——
REAL_DEFECT、STALE_EXPECTATION(需要引用证据来证明变更是有意为之)、BRITTLE_TEST、ENVIRONMENT或FLAKY(通过重新运行确认,并在隔离处理时注明到期时间)。最可能为回归开脱的分类,证据门槛最高。 - 可辩护的判定。 每次运行都以且仅以
pass | pass with risks | blocked | fail中的一种结果结束——存在未解决的 Blocker 就会强制判定为fail,blocked是合理结果,而pass总会明确指出哪些内容未经测试。 - 它绝不修复你的代码。 没有
Edit工具,钩子会将写入限制在 QA 根目录内,严格模式的 Bash 防护会关闭 shell 的写入通道——评判问题的测试者如果修补自己评判的对象,就不再独立。该防护是启发式机制,并非沙箱,README 对此有明确说明。 - 它经过测试,也会测试自身。 评分后的评测套件会公布漏报;带签名的运行历史无法由模型伪造;测试者无法编辑自己的历史记录——它还会审计自身的发布:前 15 次运行中,共提交了 78 条针对自身的发现——59 条已修复并验证修复结果,1 条作为已记录的风险接受,2 条仍未解决,另有 16 条在未提供证明的情况下关闭,其自身台账将这些计为未知,而非胜利。每条已修复的测试框架规则都会固定为突变,套件必须能够捕获这些突变。
运行结果示例:真实的差异报告——Verdict 对自身代码的第 14 次运行。
模型费用由谁承担? 由你承担,使用你已有的 Claude 订阅即可:该插件在你自己的会话中运行,不会经由任何第三方路由;模型以下的所有部分——状态、门禁、测试框架、评测评分器——都由标准库 Python 实现(可选的 MCP 服务会额外使用 mcp SDK),运行完全免费。它适用于 Python、TypeScript、Go 或任何带有测试运行器的语言;评测夹具涵盖 Python 和 TypeScript。
接下来阅读:安装 · 安装哪些内容,以及何时运行 ·
快速入门 · 为何需要另一个 QA Agent ·
经过测试的测试器 · CI 门禁 ·
接受风险 · 常见问题
安装
/plugin marketplace add ArtJack/verdict
/plugin install verdict@verdict
其他任何编码 Agent——Cursor、Codex、OpenCode,以及 Agent Skills 生态系统中的其他工具——都可以获得相同的准则(以五个技能的形式提供),以及作为 pip 包提供的相同测试框架:
npx skills add ArtJack/verdict # release risk · verify a fix · flaky triage · root cause · spec review
pip install verdict-qa-mcp # verdict-facts, verdict-finalize, verdict-gate, verdict-accept, verdict-answer, verdict-replay, verdict-dupes
已在 skills.sh 上列出。
这些技能会向无法运行 verdict Agent 的 Agent 重申这份约定;强制执行只读保证的钩子仅存在于 Claude Code 中,因此在其他地方,这项保证只能依靠 Agent 自律和测试框架的拒绝机制。仓库根目录中的 AGENTS.md 和 llms.txt 面向那些在采取行动前会先阅读文件的 Agent。
Python 3.9 或更高版本,具体取决于 python3 所解析到的版本——钩子和事实测试框架仅使用标准库,并通过该命令调用;在原装 Mac 上,该命令指向 /usr/bin/python3(3.9)。可选的 MCP 服务通过 pip 安装,需要 3.10 及以上版本,这也是 pyproject.toml 中 requires-python 所指的版本。最低版本要求经过测试:任何会在 3.9 上导致导入失败的模块,都会使 CI 失败,而不是悄无声息地出错(tests/test_interpreter_floor.py)——因为它要防止的故障曾悄无声息地发生过。Bash 防护曾在该版本上于导入时引发异常,而它旁边的写入防护仍能正常工作,于是严格会话看起来已启动,实际上却缺少一半控制措施。
安装内容及其运行时机
安装插件意味着允许其代码在你的会话中运行,因此,以下是这个插件的实际行为——依据 hooks/hooks.json 测得,而非凭记忆概述。
共注册了 6 个钩子;每个钩子在事件触发时都会启动一个 python3(耗时数十毫秒):
| 事件 | 触发时机 | 脚本 | 静默条件 |
|---|---|---|---|
PreToolUse | Write/Edit/MultiEdit/NotebookEdit | 写入范围守卫 | 始终静默,除非 VERDICT_STRICT=1,或调用方本身就是 Verdict Agent;此外,在任何会话中,针对 QA 根目录下的 accepted.json / answers.json 路径也会静默,这些文件只由 verdict-accept / verdict-answer 写入 |
PreToolUse | Bash | Bash 范围守卫 | 始终静默,除非 VERDICT_STRICT=1,或调用方本身就是 Verdict Agent |
PostToolUse | Write/Edit/MultiEdit | 状态验证器 | 除非写入的文件是 QA 根目录下的 state.json 或 findings/<ID>.json |
Stop / SubagentStop | 回合结束 | 运行契约检查 | 除非本次会话中的 QA 运行留下了手写状态,或本次会话中的 Verdict Agent 已测量事实,却未调用 verdict-finalize 就停止(运行标记会记录会话;其他会话的标记,或在运行进行中停止的其他 Agent,都不代表任何情况)——每种情况最多阻止一次,绝不会循环 |
SessionStart | 会话开启 | 发现项横幅,或一次性首次运行提示 | 除非仓库中存在 QA 状态;对于提示,除非这是在 git 仓库中进行的全新交互式启动,且仓库包含 Verdict 从未见过的测试套件(每个仓库仅提示一次,每人最多提示 3 个仓库;使用 VERDICT_STRICT、VERDICT_NO_HINT=1 或无头模式 claude -p 时不会提示) |
每个钩子都默认放行:输入格式错误、文件缺失或发生异常时,都会以退出码 0 静默结束——钩子故障绝不能导致会话无法使用。只有设置 VERDICT_STRICT=1 才会启用范围守卫;你只应在专用 QA 会话(无头模式、CI、夜间任务)中设置它;在普通交互式工作中,守卫不会执行任何操作。
不想全局安装? 所有功能都支持按仓库使用:将 agents/verdict.md 复制到 <repo>/.claude/agents/,并将 hooks/hooks.json 中的条目复制到 <repo>/.claude/settings.json,同时将 ${CLAUDE_PLUGIN_ROOT} 替换为检出目录的路径。这正是评估框架为临时项目配置环境的方式——eval/run_eval.py 是参考实现。
快速开始
/verdict:run # first run: profile + isolation rules + baseline
/verdict:run the payment retry change # every later run: a delta against the stored state
之后的每次运行都会与存储的状态进行差异比较。再次运行会返回类似以下内容:
VERDICT: fail
Scope: 2c67f47..b4e2943 (4 commits, 16 files) · run 4 (delta)
Isolation check: pass (no .env present; no live service touched)
Findings — REGRESSED first:
REGRESSED PRICER-F-002 Critical/P0 round_cents uses banker's rounding again (pricer.py:17)
resolved 08-19, reintroduced by b4e2943 — this forces the verdict
NEW PRICER-F-007 Major/P1 quarantine graveyard: test_listable_at_floor_exactly
skipped 114 days with no expiry — it is the test that would catch F-001
STILL_OPEN PRICER-F-001 Critical/P0 age 6d is_listable rejects a price exactly at the floor
FLAKY test_bulk_discount_applies — fails 3/6 runs with no code change; quarantined
until 2026-09-07, excluded from this verdict, listed until re-evaluated
Delta gates: tests 213 → 213 · duration +0.4% · coverage on changed files: no decrease
Release blockers: PRICER-F-002 (regressed), PRICER-F-001
Not tested: concurrency under parallel checkout — no harness present
Fix order: 1) F-002 2) F-001 (unskip its test first, watch it fail red) 3) F-007 expiry
Artifact: .qa/reports/2026-08-24-pricer-review.md
为什么还需要另一个 QA Agent
典型的 qa-expert.md | 结论 | |
|---|---|---|
| 记住上次运行的结果 | 不会——每次运行都是全新审计 | 状态文件;通过 NEW/STILL_OPEN/RESOLVED/REGRESSED 标记状态,并记录持续时间 |
| 不稳定测试 | “将不稳定测试控制在 1% 以下”(文字说明) | 隔离台账,必须设置到期时间;不稳定测试不计入结论,但绝不会从报告中删除 |
| 发布决策 | “go/no-go”只是清单中的一个词 | 四级结论约定;存在未解决的 Blocker 即强制判为 fail |
| 失败测试分类 | “调查失败” | 五类分类法;STALE_EXPECTATION 必须附上意图引用 |
| 质量门槛 | “覆盖率 >90%”这类绝对指标 | 趋势门槛:变更文件的覆盖率不得下降;收集到 0 个测试 ≠ 1 个测试失败 |
| 测试设计 | “测试边界情况” | 包含 24 种技术的目录,并附有风险触发条件——包括基于属性的测试、蜕变测试(适用于 ML/LLM 输出)、MC-DC、契约测试(docs/test-design.md) |
| 能否编辑你的代码 | 没有任何限制 | 无 Edit 工具 + 写入范围钩子 + 严格模式 Bash 防护 |
| 安全性 | 被忽略,或被过度吹嘘 | 可选的仅报告检查:依赖项审计 + diff 密钥扫描;明确不涵盖渗透测试 |
| 风险优先级排序 | “重点关注高风险区域” | 根据项目自身的问题记录计算排名(按严重程度加权,跨引用深度合并路径);报告必须展示排名、截止位置及其后所有条目——这些条目归入未测试 |
| 根本原因 | “调查故障” | 四环因果链,每环都附有引用;必须检查类别(这是个例还是模式?);通过在临时副本中翻转原因来证明因果关系——并配有围绕诱饵构建的独立评分测试样例 |
| 需求审查 | 从不——只审查代码 | /verdict:spec 在代码编写前评审规格说明(矛盾、不可度量的要求、边界模糊、历史冲突)——并配有独立评分评估测试样例 |
| “没发现 Bug!” | 经常这样说 | 绝不这样说——改为报告覆盖情况、缺口和剩余风险 |
| AI 编写的代码 | 与人工编写的代码使用同一份清单 | 衡量代码来源(对代码范围进行 trailer 统计,配置文件为 authorship);模式目录中的每个条目都附有操作流程和证据标准(docs/ai-authored-code.md);对幻觉导入、占位符和被吞掉的错误进行确定性统计,以供评判——并通过评分测试样例证明这些行为 |
| 自身准确性 | 未衡量,事后也无法衡量 | 每条问题记录时都注明置信度;根据该问题实际造成的结果计算成效,永久保存于台账,并以测试人员无法修改的业绩记录形式报告 |
| 是否经过自测 | — | 评分评估套件:基线 + 增量记忆 + 对抗性诚实测试样例;采用确定性评分器,并公开答案(eval/) |
| 其他工具能否使用其状态 | — | verdict-mcp:基于状态提供只读 MCP 服务——可在 Cursor、Codex、CI 及任何 MCP 客户端中使用 |
经测试的测试者
一个从未经过测试的 QA Agent,恰恰会拒绝这种说法。
eval/ 是一套带评分的评估套件,使用确定性评分器 —
score.py 读取状态文件,而不是文字说明 — 并包含八个夹具,其中四个支撑了主要声明:
- 基线(fixtures/pricer):8 个预置问题,涵盖全部 五种失败分类,包括一个隐藏在“temporarily”跳过测试背后的边界缺陷,以及一个过时的预期,其意图引用位于 CHANGELOG 中。 答案。
- 差异(fixtures/pricer_rev_b):评估旗舰功能 —
针对编写好的第 2 轮历史运行时,必须产出
REGRESSED(排在首位)、NEW、STILL_OPEN、RESOLVED,并解除已过期的隔离;同时,CHANGELOG 中的诱饵试图 将新缺陷伪装成预期行为。 答案。 - 说谎者(fixtures/liar):对诚实性的对抗性测试 — 一个无条件输出“ALL TESTS PASSED”的测试脚本、一个将整个套件标记为跳过的 conftest、一个断言自身返回值的模拟对象,以及一个同义反复式断言。评分 verdict 是否照单全收输出。
- 规格(fixtures/refund-spec):左移测试 —
一份预置了矛盾内容的 PRD 草案,其中包含无法衡量的需求、恰好位于边界上的歧义、未明确说明的失败路径缺口,以及一份与规格矛盾的 CHANGELOG。评分
/verdict:spec能否在代码尚不存在时发现所有问题。
python3 eval/run_eval.py --mode seeded|live|baseline 会在隔离的临时仓库和临时状态目录中运行全部评估。结果按实测情况发布;漏报以及任何答案修订都会保留在表格中(eval/README.md)。
所有这些答案都是编写提示词的人亲手编写的。外部答案(eval/swebench.py)则是这里无人挑选的:SWE-bench Verified
实例 — 真实缺陷,每个缺陷都由其维护者自行修复,并配有一个修复前会失败的测试 — 集合中五个最小仓库的全部实例(pytest、pylint、requests、
seaborn、flask;40)。检出的历史停留在缺陷的基准提交,环境与维护者当周使用的环境相同,被隐藏的测试不会进入代码树,而问题文本就是完整的任务要求。评分是确定性的定位评估:检查发现报告引用的 path:line 是否位于修复所改动的文件中、代码修改区块内,以及同一个函数中。评分、耗时、令牌数和所有漏报均记录在
台账中。
状态模式
- 单人模式(默认): 状态存储在
~/.claude/verdict/<repo-name>/中 — 不会向你的仓库添加任何内容。 - 团队模式: 在仓库中创建
.qa/(/verdict:baseline team)并提交 — 队友 和 CI 会共享同一份基线,QA 报告也会随代码一起流转。
状态架构见 docs/state-schema.md — 这是带版本控制、向前兼容且人类可读的 JSON。
命令
插件命令以插件名称为命名空间,必须输入完整命令——/verdict:run、
/verdict:status。没有简写形式:单独的 /verdict 是未知命令,
这一点是经过测量得出的,而非想当然。
从这里开始: /verdict:run——入口命令。它会读取测试人员的记忆,并自行选择合适的检查:尚无状态 → 基线;已有状态 → 今日增量;提供了参数 → 将增量范围限定为你指定的内容。它会说明自己的选择及原因。以下每条命令都是针对特定任务调用的同一套机制,适用于你已明确要做哪项任务的情况。
| 命令 | 功能 |
|---|---|
/verdict:run | 入口命令——根据存储的状态,转到基线、增量或指定范围的审查 |
/verdict:baseline | 初始化 QA 根目录、项目配置文件和基线状态 |
/verdict:regression | 回归检查清单:变更区域 → 相邻流程 → 集成 |
/verdict:release | 使用四项判定约定的发布门禁 |
/verdict:bug | 将症状/日志/投诉整理为分类明确、结构化的 Bug 报告 |
/verdict:flake | 对间歇性故障进行分类:复现 ≥3 次,寻找机制 → 创建 BRITTLE_TEST 修复任务,或将 FLAKY 隔离并设置到期时间 |
/verdict:status | 只读地查看存储的状态——不运行、不写入、不启动 Agent |
/verdict:spec | 左移测试:在代码编写前,判断规格说明/问题/PRD 的可测试性——检查矛盾、不可测量项、未定义边界、未明示的空缺、历史冲突,以及 Given/When/Then 标准 |
/verdict:cause | 追溯故障的根本原因:症状 → 机制 → 起因 → 类别,逐环节引用证据;通过反事实而非叙述来证明因果关系;将触发因素、原因和潜在条件区分开来 |
/verdict:charter | 制定限时的探索性章程,依据配置文件中的事故历史设定风险重点;将观察结果作为证据记录,并将发现转化为 Bug 报告和回归测试候选项 |
测试人员的记忆,通过 MCP 提供(可选)
Verdict 的状态并未锁定在 Agent 内。verdict-mcp 是一个小型的只读 MCP 服务,可读取相同的状态文件,因此任何支持 MCP 的组件都能查阅你的 QA 记忆——无论是负责控制合并的编排器、Cursor 或 Codex 会话,还是在 PR 中发表评论的 CI 步骤:
| 工具 | 返回内容 |
|---|---|
get_verdict(project) | 最近一次判定、发布阻碍项、报告路径、未测试列表 |
get_findings(project, status) | open(默认)、all,或 NEW / STILL_OPEN / RESOLVED / REGRESSED——REGRESSED 排在最前 |
get_quarantine(project) | 不稳定测试台账,每条记录都带有计算得出的 expired 标记 |
get_questions(project) | 测试人员留给某个人的问题,每个问题都带有存续时间,以及尚未被任何一次运行读取的回答——使用 verdict-answer 作答 |
get_history(project) | 从报告 INDEX 解析出的逐次运行趋势 |
get_report(project, report?) | 完整报告内容(默认:最近一次运行的报告)——路径限制在 QA 根目录内,因此 CI 步骤可以引用证据,而不只是添加链接 |
get_profile(project) | 项目的 QA 配置:隔离规则、风险领域、实际测试命令,以及(若存在)经验教训台账 |
get_trends(project) | 来自 INDEX 的逐次运行趋势、当前压力概况(按严重程度统计的未解决问题、存续时间分布、隔离数量、耗时),以及热点——根据该项目自身的问题计算出的缺陷聚集区域,并按严重程度加权,同时显示排名所依据的运行次数 |
list_projects() / get_state(project) | 所有具有基线的项目 / 原始状态 |
claude mcp add verdict -- uvx --from verdict-qa-mcp verdict-mcp
发行包名称为 verdict-qa-mcp——控制台脚本和导入包仍分别为 verdict-mcp / verdict_mcp;只有 PyPI 上索引的名称不同,因为 verdict-mcp 已被一个无关项目占用。也可以直接从仓库安装,无需发布版本:
uvx --from git+https://github.com/ArtJack/verdict verdict-mcp
project 是单机根目录(~/.claude/verdict/,可通过 VERDICT_HOME 覆盖)中的一个键,或团队模式下的仓库路径(解析为 <repo>/.qa/)。每个工具都带有只读注解,且服务绝不会写入数据——**测试人员的记忆是公共 API;测试人员的笔则不是。**需要 uv(或 pipx install verdict-qa-mcp);插件本身仍无任何依赖,即使没有该服务也能正常工作。
闭环(但不让测试器修复任何问题)
Verdict 刻意作为修复循环的门禁,而非执行者——一个修复问题后又自行评判修复结果的 Agent,就像自己给自己的作业打分。这个循环属于你的编排器、编码 Agent 或 CI;Verdict 的职责是让每一轮循环都有证据引用,杜绝敷衍盖章:
┌─────> implement the ordered fix list (you / your coding agent)
│ │
│ v
│ /verdict:run — delta run (scoped by diff, findings aged)
│ │
│ v
│ get_verdict over MCP ────────── pass ──> merge
│ │ (the not-tested list travels with the PR)
└──────── fail · pass with risks
(fix order is dependency-aware, REGRESSED first)
最精简的驱动程序,适用于任何 MCP 客户端:
while True:
before = mcp.call("verdict", "get_verdict", {"project": "myapp"}).get("run_number") or 0
subprocess.run(["claude", "-p", "/verdict:run delta pass on myapp"]) # the agent runs
v = mcp.call("verdict", "get_verdict", {"project": "myapp"}) # the gate reads
assert (v.get("run_number") or 0) > before, "run died before writing state — not a verdict"
if v["verdict"] == "pass":
break
fix(v["release_blockers"],
mcp.call("verdict", "get_findings", {"project": "myapp", "status": "open"}))
(run_number 检查很重要:如果没有它,某次运行在写入状态前崩溃,就会重新提供昨天的判定——如果昨天通过了,循环就会合并未经审查的代码。verdict-gate --min-run-number 是同一项检查的命令行版本。)
以下规则确保循环诚实——全部由 Agent 的契约强制执行,而非寄希望于运气:
- REGRESSED 会大声中断循环。 复现的问题优先级高于任意数量的 NEW 问题;每份报告和每个
get_findings响应中,它都排在最前面。 - 红色测试通过正确的途径处理。
STALE_EXPECTATION通过测试更新任务处理(并附带意图引用),REAL_DEFECT通过代码修复处理——循环绝不会通过修改失败测试来迎合代码,从而收敛。 - 不允许掩盖不稳定测试。 隔离的测试不纳入门禁,但到期后会重新纳入,因此循环无法通过跳过测试来得到绿色结果。
blocked会停止循环,不会让它通过。 缺少环境时,循环会停下来交由操作员处理,而不会把这一问题粉饰成判定结果。- 运行崩溃不等于判定结果。 门禁会断言
run_number已递增;过期状态有独立的退出码(5),与通过和失败都不同。
这并非假设情形——作者的私有部署每晚都会在无人值守的情况下,对生产代码库运行这一循环。
如果某个发现引用的内容都没有变动,verdict-run --skip-unless-drift 就会结束一次扫描,而不运行模型——按 ID 沿用先前的判定、不由模型签署、运行编号递增;若无法这样做,则会说明原因。判断条件来自测试框架自身的测量结果:每个引用行都仍在原处、所有门禁均通过、测试 ID 集合未变、没有隔离项到期(docs/nightly.md)。
CI:利用测试器的记忆来门禁 PR
此仓库也可作为复合 GitHub Action 使用。门禁模式不需要 API 密钥、安装步骤或模型——一个仅使用标准库的脚本读取已提交的团队模式 .qa/ 状态,设置任务状态,并维护一条置顶 PR 评论(判定标题、阻塞项、REGRESSED 优先的问题表、未测试列表):
permissions:
pull-requests: write
concurrency: verdict-${{ github.ref }}
steps:
- uses: actions/checkout@v4
- uses: ArtJack/verdict@v0
with:
max-age-hours: 48 # a stale verdict is exit 5, never a pass
max-commits-behind: 0 # ...and so is one measured before these commits
运行模式(实验性)会先执行一轮无头 Verdict 检查——可在配有 anthropic-api-key 的 GitHub 托管运行器上运行,也可在配有 claude-oauth-token 的自托管运行器上运行;该令牌通过 claude setup-token 获取,这样每晚的 QA 就能使用你的订阅,而非按 API 计费(anthropic-base-url 可传递给兼容 Anthropic 的网关)。同一契约也可在任何地方通过命令行使用:
verdict-gate myapp --max-age-hours 24 --max-commits-behind 0 --fail-on risks
退出码:0 通过 · 1 失败 · 2 用法错误 · 3 阻塞 · 4 无状态(测试器未运行)· 5 过期 · 6 手写状态(使用 --require-harness 时)。4 和 5 刻意与 1 区分开来:“测试器未运行”绝不能看起来像“测试器给出了否定结果”。如需在自己的机器上运行每晚检查——包括 cron、systemd、订阅令牌和严格模式——请参阅docs/nightly.md。
--format sarif 会将未解决的问题输出为 SARIF 2.1.0(严重程度 → 级别,从 file:line 证据中解析位置),从而将其作为注释显示在 GitHub 的 Security 选项卡中:
- run: verdict-gate --format sarif > verdict.sarif || true
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: verdict.sarif }
让测试 Agent 看到你的项目(自带 MCP)
Agent 仅附带核心工具,但 frontmatter 是一个扩展点:将
agents/verdict.md 复制到项目的 .claude/agents/,并将项目的 MCP 工具
(数据库、预发布 API、浏览器)添加到它的 tools: 列表中。Agent 的 §0 隔离规则
规定了它可以如何使用这些工具——只读事实,绝不执行变更;无法验证时标记为
blocked。这种模式经过了实战检验:这个 Agent 的私有前身每晚都会使用十一个只读的商城数据库工具运行,这正是它能发现一个仅靠阅读源代码无论如何也无法发现的线上超卖漏洞的原因。
示例——连接了 Playwright MCP 的 Web 应用:
# your project's .claude/agents/verdict.md, frontmatter tools:
tools:
- Read
- Glob
- Grep
- Bash
- Write
- mcp__playwright__browser_navigate
- mcp__playwright__browser_snapshot
- mcp__playwright__browser_click
- mcp__playwright__browser_console_messages
……配置文件还会说明交互规则:哪个来源是测试环境 (绝不是生产环境)、哪些账号是测试账号,以及导航、快照、读取属于允许范围,而任何提交、付款或更改账号的操作都被禁止。§0 对浏览器工具的约束与对 Bash 完全相同——不确定点击是否会产生变更?那就会产生变更; 应返回风险,而不是点击。探索性测试计划(§4,技巧 23)也可以直接转化为实践:在限定时间内进行浏览器会话,聚焦于风险,将观察结果作为证据,并把可复现的失败转化为 Bug 报告。
模型负责判断,系统负责度量
状态文件大约三分之二的内容是算术和转录——时间戳、SHA、差异范围、门禁退出码、持续时间、测试计数、问题哈希、年龄、差值。这些都不需要判断,而每一项都可能成为自信地犯错的地方。
因此,运行过程分成两部分。verdict-facts 负责度量:运行你指定的门禁、计时、解析计数、读取 git、推导项目键,并确定 run_number 和 run_type(包括何时必须将一次运行重新声明为重新基线)。然后,Agent 只编写判断内容——每发现一个问题就立即将其单独写入文件,并在写入时进行验证;对于检查过并确认未变化的问题,则写入其 ID;此外还会写入判定结果和未测试的内容。verdict-finalize 会汇总这些文件,计算每个问题的哈希、first_seen、age_days,以及相对于上一状态的 NEW/STILL_OPEN/RESOLVED/REGRESSED 变化;它还会对证据引用的每一行进行哈希,以便在下次运行时告知代码位置发生了什么变化,并在写入任何内容之前验证结果。
verdict-finalize 还会生成报告——范围、门禁、按 REGRESSED 优先排序的问题表、未测试项、隔离项——这些内容均来自同一份状态,并将 Agent 编写的文字(风险、修复顺序、每个问题的叙述)注入其中。报告不会遗漏,因为它由执行框架写入;报告也不会与状态矛盾,因为它就是状态本身。
凡是模型无法正确计算的内容,都不会留给模型计算。
测试者自身的错误率
一条发现的价值,由测试者的记录决定。Verdict 会保留这份记录,设计原则也与其他地方相同:模型最容易宽松评分的部分,正是它不能触碰的部分。
每条发现都要注明提交时的置信度——proven(已演示问题确实发生)、probable(已追踪,但未执行)、hypothesis(推测)。验证器会拒绝缺少置信度的新发现,测试框架则会将其冻结:后续运行不能在看到结果后修改预测。
结果由系统计算,而非由测试者声称。出现回归的发现,或通过重新注入缺陷并观察防护措施失败来验证修复的发现,算是经受住了检验。测试者撤回的发现则不算。其他所有发现都保持未定,不计入任何比率——未经验证的解决结果只是缺失,而非证明;仍未解决的发现也还没有定论。已确定的结果会保存在 outcomes.json 中,因为 state.json 会丢弃两次运行前已解决的发现,否则样本量就会不断重置。
随后,报告会包含一个记录汇总部分:此项目跟踪了多少条发现、其中多少条已有定论,以及各置信度和各证明方法对应的数量。只有当某个类别已有 30 个确定结果时,才会显示百分比。低于此数量时,你看到的是“3 条中有 2 条”这个事实,而不是“67%”这个装饰。
接受风险——维护者之笔
有些发现是正确的,但不会修复:经过权衡并写入决策日志的剩余风险,或是某个即将退役的功能背后的缺陷。如果将这类发现留为 open,那么在项目存续期间,每个横幅都会将其重新报告为一项未关闭的 Major——这正是本工具旨在避免的“直到你不再阅读为止,始终是同样的二十项发现”问题;而将其标为 withdrawn,则会把一项正确的发现算作测试者的错误。因此还有第四种状态,但测试者无权填写:
verdict-accept myapp MYAPP-F-021 --cite "DECISIONS.md 2026-09-02" \
--reason "deleting the ledger too defeats the anchor; the cost is the whole track record"
这会在 QA 根目录中,与 outcomes.json 并列写入 accepted.json——作用域保护机制会禁止 Agent 访问该文件,验证器也会禁止在判定中使用这种状态。该发现会立即从未关闭数量中移除(包括横幅、verdict-gate 和 MCP 服务),并从下一次运行的判定中移除;它会在每份报告的 已接受的风险 下连同引用一并列出,并根据维护者的裁定记录为 confirmed——与实测确认和声称确认分别保存,因为它既不属于前者,也不属于后者。--revoke 可撤销该决定,并须说明原因;--list 会显示该台账。决定会影响下一次判定,而不会改动上一次判定。
另一个只有人才能作出的决定是回答问题——; 是否仍是查询分隔符,单文件 vendoring 是否属于受支持的约定,gate 是否应运行已安装的 wheel。测试者会将这些问题暂存(在判定中放入 questions;finalize 会生成 MYAPP-Q-3 并保留 questions.json),再由第二支笔作答:
verdict-answer myapp MYAPP-Q-3 --answer "at or above is the rule; README rule 1 is the spec"
这会写入 answers.json,和 accepted.json 一样禁止 Agent 访问。下一次运行会在事实中读取该决定,不再重复提问;报告会根据台账显示 需要人工决策;会话开始横幅和 verdict-gate 会显示待处理问题的数量。问题不会通过邮件发送,也不会为问题创建 issue——它会推送到所有能触达你的界面,并在那里等候。
第三支笔用于检查测试者。已证实的 Major 及以上级别发现会附带 repro——一条展示缺陷及其产生结果的命令,而 verdict-replay myapp 会在基于 HEAD 创建的独立工作树中运行所有此类命令,其中不包含测试者探查时做的任何操作:
verdict-replay myapp # every open Major+ finding with a repro; --show lists the commands first
verdict-replay myapp MYAPP-F-3 # one finding
每个结果都会写入 replays.jsonl,不属于已签名状态;gate 会将发现标记为 replay ✓ reproduced、✗ NOT reproduced 或 ? could not run,并统计无人重放的已证实发现。若一项发现的唯一证据是文字描述,那么它只对作者本人而言已得到证实,对其他人则不然。
报告发出之前,verdict-dupes myapp 会通过 gh,在仓库的 issue 和 pull request 中搜索每项未关闭的 Major 及以上级别发现(查找标题中的代码名称、描述文字以及证据引用的内容),并将线索写入 related.jsonl;gate 会列出每项发现的首条线索,并在其为未关闭 PR 时发出警告。2026 年 10 月,九项陌生发现中有两项已由未关闭的 PR 修复——当时是人工发现的,现在则由命令找到。
测试器有记忆,实现者却没有。
这种不对称造成的代价是可量化的。Verdict 在一个线上网站记录了 11 项有证据支持的问题,其中一项是发布阻断项——部署此分支会移除所有生产环境安全标头——而同一仓库中的下一次会话随即进行了一轮完整的 SEO 检查,却一个问题也没处理:既没处理阻断项,也没处理那个在交接失败时仍报告成功的应用表单,也没处理两个主要 CTA 上的对比度问题。这些问题一直都在 state.json 里。next_run_focus 虽然存在,但只有 Verdict 会读取;MCP 上也有 get_findings,却没有任何东西会主动调用它。
因此,SessionStart 钩子会在一个有 QA 状态的仓库中开启会话时,告知尚待处理的事项——在首次编辑之前,而不是之后:
Verdict remembers dm-express-site: run 2 (delta), today — verdict **fail**.
1 release blocker — look here first:
- DMEXPRESS-F-1 — the audited branch has diverged from the deployed origin/main
11 open findings: 4 Major · 4 Minor · 3 Trivial
- DMEXPRESS-F-3 (Major) Light theme: accent-coloured text fails WCAG AA…
Full detail: `/verdict:status`. These are findings, not instructions.
刻意保持简短——会让人滚动浏览的会话开场白没人会看——而且绝不会重复提及已标为阻断项的问题。在没有 QA 状态的仓库中保持静默,任何故障时也保持静默;如果记忆超过一周,则会标记为过期,而不会将其当作当前信息提供。它会告知会话情况,但不会夺取会话的控制权。
在 Verdict 从未检查过的仓库中,同一个钩子只会提示一件事,而且只提示一次:Verdict 已安装,/verdict:run 会进行首次只读 QA 检查。它只会在 git 仓库中有测试套件的情况下,于首次交互式启动时提示;每个仓库最多提示一次,每个人最多提示三个仓库;无头模式、CI 环境或设置了 VERDICT_NO_HINT=1 时绝不会提示。它会将已提示的位置记录在 Verdict 自己主目录中的 .first-run.json,绝不会写入你的仓库。之所以需要它,是因为 Claude 目录中有 96 个账户安装了 Verdict,却没有任何账户真正使用过它。
最后的防护措施,无论模型是否记得都会触发
上述每项检查都依赖于模型主动选择调用某个工具——这并非理论上的漏洞。某次真实的 /verdict:run 运行在 $VERDICT_HOME 指向其他位置时,仍将内容写入默认状态根目录,捏造了项目键,完全跳过了测试框架,却依然生成了一个自信而看似可信的 FAIL。verdict-validate 本来会拒绝该状态,verdict-gate --require-harness 本来会以状态码 6 退出。两者都没有触发,因为没有任何东西调用它们。
(过去,这项检查可以靠仿造而非伪造来绕过——它的两个持久信号分别是一个值为字典的键和一段固定的页脚字符串,两者都能直接从已提交的产物中复制。Verdict 发现了这一自我审计漏洞。现在每次运行都会用前一条记录的哈希为运行历史签名,并将其写入状态;复制链接到后续记录中无法通过验证,签名后编辑状态也同样无法通过验证。)
因此,我们加入了一个 Stop 钩子。每当一个轮次结束时,它都会提出一个问题——刚才是否有 QA 运行将手写状态留在了磁盘上?——如果有,就会阻止结束一次,并说明需要重新执行哪些操作。它会在轮次结束时触发,而不是等模型决定进行检查时才触发。
发出提示的门槛刻意设得很高,因为该钩子会在插件启用的每个会话的每个轮次结束时运行:该轮次不能已因这个钩子而继续(绝不循环);必须能从会话的当前工作目录解析出 QA 根目录;其 state.json 必须在最近半小时内写入;并且测试框架跟踪记录必须缺失。除此之外,检查只需约两次 stat 调用便会退出,实测耗时为 37 ms。所有故障路径——输入无法解析、导入无法解析、状态无法读取——也都会静默退出:导致会话无法继续的钩子,比它要监管的问题更糟糕。
状态契约经过机器检查
提示词中的文字说明能减少模型虚构值的频率,但无法阻止模型虚构它有能力编造的值。此处的实测结果是:在将 date -u 设为明确规则数月后,四个生产时间戳中仍有两个的秒数恰好为 :00——这些值是凭空捏造的,而且悄无声息地出现在所有下游使用方都信以为真的状态中。
因此,契约不再只是文字说明,而是变成了一道关卡。verdict-validate 会在每次写入 state.json 时作为 PostToolUse hook 运行(在 CI 中也可作为 CLI 使用),并立即在会话中报告任何存在以下情况的状态:将 report 指向一个并非现存文件路径的值 · 携带的是回忆而非实测得出的时间戳 · 将 run_number 留在崩溃运行留下的值上 · 虚构枚举值 · 在存在未解决 Critical 问题时仍声称 pass · 提交没有证据的未解决发现 · 隔离没有到期时间的测试。
它首次检查四个正在使用的生产状态时,发现其中两个存在违规——其中包括那个确切的规避手段(在 report 字段中填写 "delivered inline to the caller…"),而提示词规则此前曾三次未能阻止这种做法。它包含的每条规则,都源于一次真实运行中的故障。
如实说明只读保障
共有四层:(1)Agent 没有 Edit 工具;(2)其约定将 Write 限定在 QA 根目录内;(3)PreToolUse 钩子会拦截超出范围的 Write/Edit 调用;(4)另一个钩子会封堵常见的 Bash 写入途径——输出重定向(>、>>、&>、>&file、N<>)、tee、sed -i/perl -i、rm/mv/cp 等命令、会修改内容的 git 子命令,以及读操作子命令的 --output 参数;以写入模式运行的格式化工具(black .、ruff format、prettier --write)、原地压缩工具、对上述任何命令使用的 find -exec 和 xargs——每个目标都会根据 QA 根目录进行解析。平台将 verdict Agent 指定为调用方时(Claude Code 会在子 Agent 内触发的每个钩子输入中发送 agent_type),以及设置 VERDICT_STRICT=1 时,两个钩子都会启用;无头运行、CI 和定时运行都会设置此变量,因为在这些情况下,整个会话就是 QA 运行。你自己的编辑和 Shell 不受保护:如果事件没有指定 Agent,且会话未启用严格模式,就会原样放行——但有一个例外。维护者的账本(accepted.json、answers.json)只能由 verdict-accept 和 verdict-answer 写入:无论是谁尝试写入这两个文件,都会被拒绝;测试人员也不能运行这两个命令。
测试框架自身的写入同样遵守这一限制。facts.json、状态文件、报告、运行索引和账本都会按文件名写入 QA 根目录,因此在创建任何内容之前,verdict-facts、verdict-finalize、verdict-local 和维护者的命令都会先确认根目录的位置——可以是个人主目录、检出目录顶层的 .qa/ 目录内或其下方,也可以位于任何检出目录之外;若根目录位于检出目录内的其他位置,就会被视为代码目录——并拒绝使用其自身条目、reports/ 或 findings/ 中含有符号链接、junction 或拥有第二个硬链接的文件的根目录,因为通过链接写入就等于写到了其他位置。报告是直接放在 reports/ 下的一个普通文件,绝不会是运行索引
(tests/test_qa_root_links.py)。
Bash 防护是对命令字符串进行启发式检查,不是沙盒;2026-10-02 的审计测出了它的边界:它无法阻止程序通过自身代码写入文件——例如解释器(python3 -c、node -e)、构建或软件包步骤(pip、npm、cargo、pytest 自己的缓存和 .coverage)、任务运行器(npm run format、make fmt)、从文件或管道读取的脚本、Shell 函数或别名。未知命令会照常运行,因为 QA 检查需要 pytest、coverage 和代码检查工具。真正的边界是操作系统级沙盒,或让测试人员对临时副本进行检查;防护措施的作用是提高意外修改的成本。格式错误的钩子输入会默认放行;钩子启用后,防护程序无法读取的命令会被拒绝,而不是直接放行。已在 CI 中使用 Python 3.9 测试,也在标准 Mac 启动这些钩子时使用的 python3 上测试
(tests/test_hooks.py、tests/test_hooks_0903.py、
tests/test_hooks_pens.py、
tests/test_hooks_links.py)。这就是全部实情;QA 工具不应夸大自身的防护能力。
常见问题
模型费用由谁承担? 由你承担——使用你已有的 Claude 订阅;所有内容都不会经由作者处理,也不需要 API 密钥。唯一可能用到密钥的地方,是可选 GitHub Action 的运行模式;那是你的密钥,存放在你的仓库中,用于你的 CI。模型之外的一切都只是普通文件和 Python 标准库。
可以在本地大模型上运行吗? 有三种答案,按成本从低到高排列。完全不使用模型:如果某个发现所引用的内容一夜之间都没有变动,verdict-run --skip-unless-drift 就会沿用现有判定——耗时两秒,并明确说明这一点。小型本地模型:verdict-local 反转控制权——测试框架负责测量、切分代码,并在临时副本中验证断言;模型每次只回答一个有明确边界的问题。使用 LiteLLM 和 Ollama 运行 qwen3:8b 测得:在基线测试样例上得分为 9/10 · 9/10 · 10/10;每次运行约三十次调用,输入 token 约七千个。自 0.90.0 起,它既能执行增量检查,也能进行首次检查,并且可安全地用于已有状态的项目:每个此前未解决的发现,要么通过在用户选定的测试上测得从失败到通过而解决,并沿用原 ID;要么以自己的 ID 重新记录,并附上导致变化的漂移情况——绝不会对此只字不提,因为测试框架会将沉默视为已解决。它的判定具有单调性:可以让判定变差,也可以保持不变;而 fail 和 blocked 都会持续保留,直到有判断力的对象进行审查。使用 --range/--base 搭配临时 --qa-root,可以在不触碰项目自身状态的情况下评估分支;如果范围内没有 Python 代码,则不会读取任何内容,并会明确说明,而不是报告检查通过。使用 verdict-run --on-drift local 将其接入夜间运行;它绝不会调用 claude CLI。
通过网关运行完整 Agent(verdict-run --env-file,ANTHROPIC_BASE_URL → LiteLLM → 你的模型服务,文档)需要能容纳 12,000 token 合约及 CLI 自身提示词的模型窗口;通过 4k 服务的 8B 模型不符合要求。由哪种模型签署判定,应由评估结果决定,而不是采用默认值:在诚实性测试样例中,Sonnet 与 Opus 打平;在 n=3 的根因测试中,两者表现相当,其中一次运行没有写入状态(模型维度)。运行测试、公布分数,然后再做决定。
为什么它不会修复发现的错误? 为了保持独立性。修补代码后又宣布代码健康的 Agent,等于在给自己的作业打分。Verdict 会返回一份有序且可直接实施的修复清单,供你(或你的编码 Agent)执行。
它会取代 CI 吗? 不会——它是在 CI 之上运行的。CI 会告诉你测试套件未通过;Verdict 会告诉你哪些失败值得关注、意味着什么、与上次运行相比有哪些回归,以及你是否仍然可以发布。
它适用于定时或无头运行吗? 适用——这正是状态文件的用途。每晚运行;喝咖啡时查看增量报告,而不是重新做一次完整审计。
路线图
- 本地优先路线(项目最初的愿景):提供一个符合 agent-skills-standard 的变体——提示词、技术目录和状态约定均采用可移植的 Markdown 编写,为在非 Claude 运行环境中使用铺平道路;并开展本地模型实验:通过兼容 Anthropic 的网关,使用本地模型运行评估套件并公布分数。模型必须通过与其他模型相同的评估,才能胜任夜间运行。
- 提供与 Python 测试样例并列的 JS/TS 测试样例
- 在有可用工具的情况下集成变异测试
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。