关于 tewip
tewip 是用于验证软件变更的开发工具,可分析测试失败、测试有效性和数据库迁移,并为变更给出带证据的判断。它面向开发团队,优先使用确定性规则;无法确认时会明确表示无法判断。
主要功能
- 01诊断测试失败原因
- 02检查测试是否可能失败
- 03通过变异验证测试检测能力
- 04分析迁移对数据库的影响
- 05提供变更风险与完成度判断
- 06在判断中标明证据和来源
适用场景
- 01排查 CI 中的测试失败
- 02识别无法触发失败的测试
- 03评估迁移对线上数据库的影响
- 04审查代码变更及测试覆盖情况
MCP 相关
tewip 可通过 `tewip mcp` 作为 MCP 服务运行,向 AI 助手提供 15 个工具,用于查询测试、需求覆盖、历史问题及下一步工作。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y tewip-aiOPENAI_API_KEY密钥Optional. Turns on the AI layer for failures the deterministic rules cannot decide. Without it only the rules run, and Tewip says which layer decided each answer.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
23收录的工具列表 · 更新于 1 天前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pr | integer | 否 | 拉取请求编号 |
| repo | string | 是 | owner/name,例如 hmamut39/triage-agent-sandbox |
| branch | string | 否 | 没有 PR 时使用的分支名称 |
详细介绍
Tewip
Tewip(维吾尔语 téwip,تېۋىپ:疗愈者、医生)是一个证明软件工作成果的层——无论工作是由人还是 Agent 编写的。
由 Mamuti Aishan (Hasan)(@hmamut39)创建并维护。
它会找出每个失败测试失败的原因(真实 Bug、易波动测试、环境、测试数据或变更过的选择器),判断某个测试是否可能失败,通过破坏你的代码来确认测试能否发现问题,读取数据库迁移对在线数据库的影响,并对变更是否完成给出一个结论。每个答案都会附上证据,并指出结论由什么决定:确定性规则、AI 模型,还是某人的修正。
安装它的价值,恰恰在于它拒绝说什么。它会回答“无法判断”,而不是猜测;不会缩小无法证明可安全缩小的测试运行范围;并且每份报告末尾都会列出它没有检查的内容。下面的每个数字都来自对陌生人真实代码的测量,从未使用固定测试样例。
它适用于各种技术栈:Node.js(node:test、Jest、Vitest、Mocha)、Python(pytest、unittest)、Java 和 Kotlin(JUnit、TestNG)、Go、.NET、Ruby、PHP 和 Rust,且每种技术栈都根据其实际运行器的报告进行检查(完整列表);同时还深入支持 Playwright,包括追踪记录、选择器漂移和经过验证的修复。它还会读取这些运行器所搭配的浏览器和组件测试工具中的失败描述(Selenium、WebdriverIO、Cypress、Testing Library for React、Vue 和 Angular、Vue Test Utils、jest-dom 和 Jasmine),并根据来自公开项目的 500 多条真实消息进行验证。
| 场景 | 使用方式 |
|---|---|
| 终端 | npm install -g tewip-ai,然后运行 tewip explain report.xml、tewip ci owner/repo --pr 12、tewip mcp |
| VS Code、Cursor、Windsurf、VSCodium | Tewip 扩展(下载):将 Tewip 注册到编辑器的 AI 助手中,并添加六个命令——解释报告、查看此分支上的 CI 失败原因、评审变更风险、找出要隔离的易波动测试、进行安全检查,以及汇总你的修正带来了什么 |
| JetBrains、Claude、其他 AI 助手 | 添加 MCP 服务 tewip mcp:包含十五个工具,包括此处以前发生过这种情况吗?、此需求是否已覆盖且通过测试?以及接下来应如何推进这个目标?(见编辑器设置) |
| 任何模型,包括不使用模型 | OpenAI、Claude、Gemini、Azure,或通过 Ollama 在自己的机器上运行的模型——无需账号、密钥,源代码也不会离开办公环境:TEWIP_PROVIDER=ollama。Tewip 回答的问题中,有四分之三完全不需要模型。了解详情 |
| GitHub CI | Tewip Action:为每个拉取请求发表评论、可选的合并门禁,以及经过验证的修复 PR(QUICKSTART) |
原名为 triage-agent:仓库已迁移至 hmamut39/tewip,GitHub 会将旧地址重定向到新地址。计划见 ROADMAP.md。
状态:第 1 至 14 阶段已完成;仍在进行中的只有两个自动修复阶段(第 5 和第 8 阶段)。 Tewip 可以从任何框架收集失败信息,优先用规则进行诊断,仅在规则无法定论时才使用模型,并为不同角色转化为相应工作:CI 评论和合并门禁、测试计划和经过验证的测试、易波动测试档案、PR 风险评审、安全扫描、在 Jira、Rally、GitHub Issues、Azure Boards 或 Linear 中创建工单、根据 Figma 设计生成检查项、面向人员编写的规范,以及——选择启用且仅在得到证明时——修复测试或应用代码。它会在 GitHub CI 中实时运行,并通过 MCP 回答 AI 助手的问题。在新近发生的真实 Gutenberg CI 失败上,先冻结流程再标注,准确率为 93.4%;在六个真实数据集上,准确率介于 86.1% 到 99.7% 之间,相关注意事项见 eval/README.md。
如果你的 CI 已经生成覆盖率报告,就让 Tewip 使用它
Tewip 通过跟踪导入来判断测试覆盖了什么,这是基于事实而非名称匹配的判断——也因此无法识别会发送 GraphQL 查询、访问 HTTP 路由或渲染控制器的测试。
在 Django、Rails 或 Spring 代码库中,大部分测试套件都是这种情况,因此以前 tewip done 对最重要的问题只能回答
“无法判断”。
覆盖率报告可以解决这个问题,而且 CI 几乎肯定已经生成了报告。无需配置:
| 格式 | 生成工具 | 查找位置 |
|---|---|---|
lcov.info | JavaScript、TypeScript、Rust、Go、C++ | coverage/lcov.info、lcov.info |
Cobertura coverage.xml | pytest-cov、coverlet (.NET)、simplecov | coverage.xml、coverage/coverage.xml |
| JaCoCo | Java、Kotlin | target/site/jacoco/jacoco.xml、build/reports/... |
| Clover | PHPUnit | clover.xml、build/logs/clover.xml |
只要有一份报告,done 就不再猜测:
### Is the changed code tested?
Yes: the suite executes saleor/graphql/utils/sorting.py.
_Decided by coverage.xml, which records what ran rather than what imports what._
它也会同样直截了当地给出相反结论——“在 coverage.xml 中,但没有任何代码行执行,因此测试套件没有运行这部分代码”,这表示尚未就绪,而不是含糊其辞。
覆盖率报告无法做到什么。 它记录的是整个测试套件运行期间哪些代码行被执行,而不是由哪个测试执行,因此它能回答*“这段代码有没有经过测试”,却不能回答“这里发生变化时,哪些测试会发现”*。tewip pick 仍会根据导入范围缩小测试范围,并且在无法证明其余部分不受影响时,仍会拒绝缩小范围。对于来自其他检出版本的报告,Tewip 会忽略而非盲信:如果报告中列出的文件不在磁盘上,Tewip 会保持沉默,而不会报告整个改动均未经过测试。
在不让数据离开办公场所的情况下运行
医院、银行或政府部门通常无法将源代码发送到托管 API,而这类机构正是本工具主要面向的用户。Tewip 会在同一台机器上使用模型:
# once
curl -fsSL https://ollama.com/install.sh | sh # or ollama.com for Windows and macOS
ollama pull qwen3:8b
# then
export TEWIP_PROVIDER=ollama
tewip done --diff main...HEAD
无需密钥、无需账户,也不会向机器外发送网络请求。TEWIP_BASE_URL 可将同一路径指向 LM Studio、vLLM、llama.cpp 的服务器,或任何兼容 OpenAI 格式的服务。
哪些会改变,哪些不会。 确定性层——vouch、schema、access、perf、standards——在任何时候、使用任何提供方,都不会调用模型,因此不受影响。只有需要判断的部分才会使用模型:解释失败原因、编写测试、起草计划。本地小型模型在这些方面的准确度不如前沿模型,而 Tewip 会说明每个答案由哪一层作出判断,因此差异清晰可见,不会被隐藏。
本地模型也更难被约束为符合指定的架构:有些后端会接受请求,却悄悄不执行约束。Tewip 可以读取位于 Markdown 代码围栏内或前置说明之后的回复,但如果回复中完全没有 JSON,仍会拒绝。
成本
大多数失败都不会交给模型处理。在来自六个公开项目的 3,556 个真实 CI 失败案例中,确定性规则独立回答了 75.1%;在有标注的案例中,规则正确回答了 2,012 次中的 1,979 次(98.4%)。由规则回答的失败不产生费用、自带解释,而且每次答案都相同。
只有剩下的四分之一会发送给模型,因此对 1,000 个真实失败案例进行分类,按 gpt-5.4-mini 公布的价格计算,费用约为 $1.12。这是实测数据,而非估算:在留出失败案例上进行的三个真实未缓存调用,每次计费 3,624 个提示词 token 和 499 个补全 token。实际运行一周的成本更低,因为重复出现的失败会从缓存中作答,而已有人修正的失败则不会再次发送给模型。
Tewip 会打印每条命令的成本、限制自身支出,并且完全无需密钥也能运行——规则会独立执行,所有无法确定的问题都会标记为 UNKNOWN,而不是进行猜测。
从这里开始
npm i -g tewip-ai
cd your-project
tewip
单独运行 tewip,它会检查你的项目,列出能让你了解情况的两三个命令,并填入你自己的路径,同时直言在这里无法回答的问题。它只读取目录,然后停止:不会运行任何内容,也不会写入任何内容,耗时约一秒。
Here is what Tewip can tell you about **this** project, in the order worth doing.
**1. `tewip vouch`**
282 test files here (282 JavaScript or TypeScript). This reads them and names the ones
that cannot fail - no model, a second or two.
**2. `tewip schema --diff main...HEAD`**
165 migrations here (165 SQL). This says what a change would do to a live database
before it runs.
## What it will not do here
- No k6, JMeter or Lighthouse report was found. Tewip judges one you produce; it does
not run load tests itself.
以下是完整列表,供你需要时查阅。
在项目中使用
提供给你的团队(或另一家公司):TEAM-GUIDE.md — 需要安装什么、各个角色如何使用它,以及它绝不会做什么。
在真实仓库中使用:QUICKSTART.md — 十分钟、复制粘贴,以及首次运行失败时要检查什么。
1. 在 GitHub CI 中(开发者、SDET、DevOps)
让 Playwright 在 CI 中生成 blob 报告。这是内置的报告器,因此配置中无需编写分诊 Agent 代码:
// playwright.config.ts
reporter: process.env.CI ? [['list'], ['blob']] : 'list',
use: { trace: 'retain-on-failure' },
然后在测试后添加一个步骤:
permissions:
contents: write # only needed for autofix
pull-requests: write
issues: write
actions: read # lets it read the failed job's log when no test report was written
steps:
# ... checkout, install, npx playwright test ...
- name: triage-agent
if: always()
uses: hmamut39/tewip@main
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }} # optional: without it only the rules run
autofix: 'true' # optional: verified locator-fix PRs
在 CI 中运行判定动词(Phase 38,全新)——一个步骤,按需执行任意数量的检查:
- uses: hmamut39/tewip@main
with:
mode: check
checks: schema, access, vouch # or: done, standards, perf, or `all`
gate: blocking # optional: fail the job on what a verb calls blocking
即使较早的检查已发现问题,每项检查仍会继续运行,因此一次推送就能让拉取请求全面了解情况。门禁刻意比报告范围更窄:只有动词本身判定为阻塞的问题才会导致失败,因为如果建议也触发门禁,就会让团队学会通过设置 continue-on-error 来绕过它。没有可检查内容的检查会明确说明这一点,并且不会导致作业失败——差异中没有迁移文件不等于通过。
使用
@main、v0.46.0或更高版本。 在此之前发布的每个标签都包含一个action.yml,其描述中有未加引号的冒号,YAML 会将其解析为嵌套映射——运行器会报错 "Failed to load action.yml",且操作不会运行。这个问题已修复,现在还有一项针对清单文件的测试,可防止问题再次出现。 每次运行时,该 Action 都会:
- **开发者:**在拉取请求上发布一条诊断评论并持续更新。对于每项失败,它会说明该问题是否可能由此次变更导致、原因是什么,以及下一步该怎么做。
- **SDET:**将分类看板写入任务摘要。
- DevOps:将环境故障(服务器错误、连接重置、服务中断)汇总到一个 GitHub issue 中,每次新运行都会向其中添加一条汇总评论。
- **拉取请求中的测试:**在 PR 上,它会读取此次变更涉及的测试文件,并指出其中哪些测试永远不会失败——未完成的断言、与自身比较的值、丢弃自身失败的测试,或根本不会运行的测试。如果所有测试都正常,则不会输出任何内容;即使运行结果为绿色,它也会指出这些问题,因为当某项测试不可能失败时,“所有测试均已通过”这句话就需要加以限定。
actions/checkout需要设置fetch-depth: 0,才能读取差异;如果未设置,则跳过此部分,而不是猜测。此类测试也会作为拒绝项写入证明文件,因此基于该文件作出的发布决策不会依赖无法证明任何问题的测试。 - **变更可能漏过的问题(
prove: true):**以小幅、刻意的方式修改拉取请求涉及的源文件,运行覆盖这些文件的测试,并报告测试未发现的每项变更——“你的测试会漏过代码中的这 2 处变更”,并附上对应行号。默认关闭,因为每项变更都会消耗一次真实的测试运行;prove-budget(默认 300s)会限制整个过程的时长,并指出未能检查的文件,而不会暗示这些文件没有问题。每个文件都会被精确还原。每项未被发现的变更也会作为拒绝项写入证明文件:无人检查的代码行,不能被该文件宣称为已验证。 - **证据,每次运行都会生成:**该 Action 会在其制品中写入证明文件,其中记录各项声明及其证据、每项由哪个层级作出判定、Tewip 拒绝作出的判定,以及完全未检查的内容。只有 Tewip 自行验证过的内容才会写入,因此无需打开 CI,也能据此作出发布决策。
- **修复(
autofix: true):**对于确定性规则匹配到已重命名元素的漂移定位器,它会重写定位器、重新运行对应测试,并另开一个拉取请求,提交通过测试的变更。它绝不会合并拉取请求,也不会修改应用代码。 - **所有角色:**将所有角色视图作为
tewip-report制品上传。 - 任务在任何测试运行前就失败时(镜像无法拉取、运行器磁盘空间不足、服务容器始终无法启动):它会读取失败任务的日志并指出原因,而不是只留下一个没有解释的红色 X。
- **历史记录:**将运行历史保存在 Actions 缓存中,绝不写入 git。
- **更正:**回复 Agent 的评论,使用
/triage CATEGORY #n reason(例如/triage TIMING #2 flaky on CI)。它会将更正与历史记录一起保存;下次同一测试以相同方式失败时,它会改为给出用户的判断,并注明该用户及其理由。更正后的故障也不会产生额外开销:它不会再次调用模型。
对于资深工程师、架构师和技术负责人,可添加一个按计划运行、设置 mode: report 的工作流,例如每周运行一次。它会根据存储的 CI 历史生成工程质量、架构和风险摘要视图,并复用此前的诊断,不会重复调用 AI。所有视图都会保存在同一个 GitHub issue 中,每次报告都会更新该 issue:
on:
schedule: [{ cron: '0 6 * * 1' }]
workflow_dispatch:
permissions: { contents: read, issues: write }
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: hmamut39/tewip@main
with: { mode: report }
报告会读取其运行所在分支的历史记录缓存,通常是默认分支。 对于修复用的 PR,需要在仓库中启用“允许 GitHub Actions 创建并批准拉取请求”。由工作流令牌打开的拉取请求不会自行触发 CI;Agent 在打开 PR 前会重新运行测试,以验证每项更改。此流程最初是在一个私有练习仓库中端到端验证的,因此这里没有可点击的链接。如果此仓库是私有的,其他仓库只有在 Settings → Actions → Access 中允许后,才能使用此操作。
作为 Claude Code 插件使用(无需账号,无需市场审核)
claude plugin marketplace add hmamut39/tewip-plugin
claude plugin install tewip@tewip
这就是全部发布流程。市场就是 git 仓库中的 .claude-plugin/marketplace.json 文件,因此 Tewip 在
hmamut39/tewip-plugin 发布了一个市场——无需任何人审核,也
无需付费。该仓库只存放插件:一个清单、三个技能,以及指向 npm 包的链接,因此这个仓库可以保持私有。该插件包含 Tewip 的 MCP 服务和三个技能,用于告诉 Claude Code 何时 使用它:
在声称更改已完成之前、测试失败时,以及更改涉及迁移时。每个会话大约消耗 410 个令牌,直到实际需要使用某个技能为止。
MCP 服务通过已发布的包运行(npx -y tewip-ai@latest mcp),因此插件体积小巧,
你也始终能获取当前版本。
编辑器扩展(VS Code、Cursor、Windsurf、VSCodium)
**打开扩展面板并搜索“Tewip”。**它同时上架于两个扩展库,因此可用于 VS Code、Cursor、Windsurf、VSCodium、Gitpod 和 Eclipse Theia:
- VS Code — Visual Studio Marketplace
- Cursor、Windsurf、VSCodium、Gitpod、Theia — Open VSX
也可以安装任何发布版本附带的 .vsix 文件,并选择
扩展:从 VSIX 安装…。无论使用哪种方式,都需要命令行:npm install -g tewip-ai。
按下 Ctrl+Shift+P 并输入“Tewip”,即可使用以下功能:这项工作完成了吗?、所有人都能使用它吗?、这些测试有没有可能失败?、这些测试能发现此文件中的错误吗?、哪些测试可能会注意到这项更改?、这次迁移会对线上数据库造成什么影响?、为什么 CI 在此分支上失败了?,以及更多功能。编辑器的 AI 助手也会自动将 Tewip 识别为 MCP 服务,无需任何配置。
2. 通过 MCP 使用任意 AI 助手(整个 IT 团队)
mcp/server.ts 是一个只读的 Model Context Protocol 服务。团队中的任何人都可以在自己常用的编辑器或聊天工具中,向自己的助手询问测试失败相关的问题:
| 向助手提问 | 使用的工具 |
|---|---|
| “我们组织的 web 项目中,PR 12 为什么 CI 失败了?” | github_ci_report:该 PR 的最新 CI 报告,以及任何已验证的修复 PR |
| “解释一下失败的 checkout 测试” | explain_failure:根本原因、置信度、证据、此提交与其他提交的历史对比,以及下一步操作 |
| “上次运行后,我应该先修复什么?” | triage_board(SDET)· pr_impact(开发者) |
| “测试环境出问题了吗?” | environment_report(DevOps / SRE) |
| “我们可以发布了吗?” | release_readiness(业务 / UAT) |
| “我们的测试套件在哪些方面比较脆弱?” | quality_report(高级 / 首席)· architecture_report(架构师)· risk_digest(负责人) |
| “为 C:\work\shop 中的 checkout 功能编写测试计划” | test_plan:根据该项目的测试、代码和页面制定风险与场景;计划保存在该项目的 .tewip 文件夹中 |
npm install 后进行设置:
- Claude Code(在此仓库中):此处已包含 .mcp.json,因此在提示时批准
tewip即可。从其他位置运行claude mcp add tewip -- tewip mcp(已安装时),或运行claude mcp add tewip -- node /path/to/tewip/mcp/server.ts(从检出目录运行)。 - **Claude Desktop、Cursor、VS Code 及其他 MCP 客户端:**添加一个 stdio 服务,命令设为
tewip,参数设为["mcp"];或者从检出目录运行时,将命令设为node,参数设为["/path/to/tewip/mcp/server.ts"]。
github_ci_report 使用你机器上的 gh CLI 登录信息。其他工具会读取 data/ 下的数据集,AI 层使用 .env 中的 OPENAI_API_KEY。
在编辑器中使用(任何支持 MCP 的 IDE)
**VS Code、Cursor、Windsurf 和 VSCodium:**安装 Tewip 扩展(extensions/vscode,打包文件为 tewip-<version>.vsix:扩展 → … → 从 VSIX 安装)。扩展会自动将 Tewip 注册为 MCP 服务,无需配置即可供助手使用,并添加两个命令:Tewip:解释测试报告… 和 Tewip:此分支的 CI 为什么失败?
所有其他 MCP 客户端(JetBrains IDE 2025.2+、Claude Code、Claude Desktop,或未安装扩展的编辑器)只需添加一项。通过发布文件安装 Tewip(npm install -g tewip-<version>.tgz)后,命令只需设为 tewip mcp;从检出目录运行时,指定 cli/tewip.ts:
使用绝对路径将此检出目录填入配置项(服务会自行解析数据,因此编辑器的工作目录无关紧要)。VS Code 使用自己的配置格式,包含 servers 键和 type;将其放入工作区的 .vscode/mcp.json,或放入用户级配置(命令 MCP: 打开用户配置),即可在每个窗口中使用:
{
"servers": {
"tewip": { "type": "stdio", "command": "tewip", "args": ["mcp"] }
}
}
Cursor、Claude Desktop、Windsurf 和大多数其他客户端使用 mcpServers 格式(Cursor:设置 → MCP):
{
"mcpServers": {
"tewip": { "command": "tewip", "args": ["mcp"] }
}
}
- JetBrains(IntelliJ、WebStorm、PyCharm 2025.2+):设置 → 工具 → MCP Server。
- 在此仓库中,Claude Code 无需任何设置:
.mcp.json已配置好。
然后用日常语言提问:“我的 PR 为什么 CI 失败了?”、“checkout 中哪些测试不稳定?”、“主分支可以安全发布吗?”。你也可以让它*“为 C:\work\shop 中的 checkout 功能编写测试计划”*:test_plan 工具会根据该项目的实际测试、代码和页面制定计划,并将计划保存在其 .tewip 文件夹中。其他所有工具均为只读;github_ci_report 会读取 Action 上传的报告,因此你无需克隆仓库或打开 Actions 页面,就能询问仓库的 CI 情况(它使用你的 gh 登录信息)。
规划并编写测试(新增阶段 6 和阶段 7)
在你的项目中运行这些命令。两者都需要 OPENAI_API_KEY,并且都会显示费用。
tewip plan "Checkout: a signed-in shopper pays by card and gets a receipt" --url http://localhost:3000/checkout
tewip write # tests for the plan's "to automate next" scenarios
tewip write --only CO-002,CO-004 --framework pytest
tewip plan会读取项目中的实际情况:你已有的测试(任意语言)、变更(--diff main...HEAD)、实时页面(用户在每个页面上看到的内容,通过 Playwright 获取),以及 Tewip 之前在此项目中诊断出的故障。计划会按优先级列出风险和场景。它会根据实际测试名称核实某个场景是否“已覆盖”;如果不存在对应测试,就会删除该说法。计划会以 Markdown 格式保存到.tewip/plans/供你审核,并以 JSON 格式保存供write使用。- 计划能发现真实 Bug 吗? 我们在 18 个后来被发现导致问题的真实拉取请求上进行了测量:检出拉取请求自身的 head 版本,在审核者看到的差异基础上编写计划,并且不向计划提供任何 Bug 相关信息。在可判定的 9 组配对中,有 8 组的场景指出了 Bug——涉及
matrix-org/complement、NLnetLabs/domain、Bioconductor/rhdf5、Try/OpenGothic等项目。其中一个拉取请求的标题是“Resolve nightly warnings”,但计划仍发现了其中隐藏的行为变更。相关注意事项都已在 ROADMAP.md 中明确说明:经判定的配对只有 9 组,样本量较小;判定者是 Claude,而非各项目的工程师;而唯一一次漏判的情况最值得关注——当拉取请求的意图有误时,基于该意图制定的计划也会赞同它。 tewip write会按照你的框架和风格,将每个场景编写为测试:Playwright、Cypress、WebdriverIO、Angular、Jest、Vitest、pytest(搭配 Selenium 或 Playwright)、Maven 或 Gradle 上的 JUnit(Selenium、Spring Boot)、.NET 或 Go。它会仿照现有测试的风格,并且只使用真实页面上出现的元素。前端应用会按照自身测试的编写方式添加组件测试:React、Vue 或 Svelte 搭配 Testing Library,并运行于 Jest 或 Vitest;Angular 则通过ng test(Karma 和 Jasmine,或 Vitest)运行。如果项目同时有端到端测试和单元测试,计划读取过页面时会生成浏览器测试,否则会生成单元或组件测试。随后,它会运行测试。只有在每次运行(默认 3 次)都通过,并且在故意破坏主要检查时至少失败一次,草稿才会保留,因为无法失败的测试还不如没有测试。测试草稿失败时,最多会根据真实错误修复两次。如果测试看起来正确,但应用实际行为不同,Tewip 会报告可能存在 Bug,而不是不断放宽测试直到通过。保留的测试会作为新文件放在你的测试文件旁边,供你审核和提交;不会提交任何内容,也不会修改应用代码。
对照设计检查页面(新增阶段 11)
tewip design "https://www.figma.com/design/KEY/Checkout?node-id=12-34" --url http://localhost:3000/checkout
tewip design frame.json --frame "Sign in" # a saved frame, no Figma token needed
Tewip 会读取设计稿(通过 Figma 的 REST API,只读,TEWIP_FIGMA_TOKEN)和运行中的页面(读取无障碍快照,也就是屏幕阅读器和测试都能看到的内容),并指出两者不一致之处:the design says button "Sign in", the page says button "Log in"。它会检查设计稿指定的文字、图层名称所暗示的控件,以及各个区块所暗示的区域——不会检查像素、颜色或间距,因为设计工具和浏览器对这些内容的测量方式并不相同。它不会调用模型,因此不产生费用,而且每次的结果都一致。
我们在 4 组真实设计稿与页面上进行了测量(某个公开仓库导出的 Figma 文件及其构建出的页面):共 38 项检查,在与设计稿一致的页面上报告的差异为 0,11 个真实偏差全部检出(控件改名、句子被删、句子中的单词被替换、区域丢失标记),5 个无害变更全部忽略(大小写、额外空格、将按钮实现为链接、区块顺序调整)。目前还没有设计师评判过输出结果,因此路线图中的指标仍待评估。
更多运行器:Robot Framework、JMeter、k6(新增)
Tewip 现在可以读取 Robot Framework 的 output.xml、JMeter 的 .jtl 和 k6 摘要,还可以读取 JUnit XML、TRX、NUnit 和 Playwright 自带的报告。负载测试不会逐个测试地失败,因此 JMeter 报告会为每个请求生成一行(“5 个请求中有 4 个失败,某项断言失败”),而 k6 运行则会为每个越过阈值的情况生成一条记录,并附上越过该阈值的数值。
Appium 和 Selenium 无需导入器:这些测试套件通过 Mocha、pytest 或 JUnit 报告,而 Tewip 已能读取这些格式;它也已经能识别它们自己的错误消息(“找不到已连接的设备”、“元素引用已过期”)。
每种读取器都经过公开代码仓库中实际发布的报告验证,因此在发布前发现了三个缺陷:Robot 的时间戳不是日期、k6 的数值存放在 values 下,以及 JMeter 在断言失败时却显示“基本上都是 200”。
告诉它什么时候错了(新增)
tewip correct diagnosis "checkout charges twice" TEST_DATA "the fixture has stale prices"
tewip correct flaky "adds an item" fix-now "it is a real race, not flakiness"
tewip correct security "src/config.ts:12" noise "that is a sample key in a fixture"
tewip correct --list
从此以后,Tewip 会采用你的判断,并说明这是你的判断:“TEST_DATA,置信度 1.00,来源:由人工更正”。更正保存在你的项目中,因此归团队所有;最新的更正优先,所以改变主意时只需再次说明。
更正只会改变 Tewip 的说法,不会改变它自行采取的行动——它不会因为你更正了判断就修改代码、隔离测试或提交工单。
汇总你的更正(新增)
tewip learn # where Tewip is systematically wrong, from what you corrected
tewip learn --labels labels.jsonl # your corrections as labelled examples, to measure a change against
一条更正修复一个失败。二十条更正则能说明该更改哪条规则。tewip learn 会将它们归类——“45 次判为 REAL_BUG,你判为 TIMING;规则 locator-drift 对其中 27 次作出了判定;涉及 11 个不同的测试”——并指出值得查看的规则,下面还会列出三条实际失败记录。
它不会装作有结论。相同模式的更正少于三条时,结果会是“尚未形成模式”,而不是发现;如果某个分组背后没有单一规则,它会说规则缺失,而不是归咎于某条规则;它还会告诉你某个模式涉及多少个不同的测试,因为同一个不稳定测试更正六次,只算一份证据,而不是六份。
先测试,再构建功能(新增)
tewip build "naturalsize should take a sep argument that goes between the number and the unit"
tewip build --issue PROJ-123 # build from the ticket's own description and acceptance criteria
tewip build "..." --pr # commit the proved work on its own branch, open a draft PR
tewip build "..." --dry-run # prove it, show the patch, leave nothing on disk
Tewip 会先编写测试并运行。如果测试通过,它就会停止,且不会保留任何内容——功能尚不存在时测试就通过,说明它什么也没测到。然后它会编写能让测试通过的最少代码;只有新测试通过,且此前通过的测试没有任何一个开始失败,它才会保留这项工作。否则,所有文件都会逐字节恢复原样,包括它编写的测试。它绝不会提交任何内容。
它已在两个真实的公开项目中试用:为 unjs/ufo 添加了尾部斜杠规则(4 行 TypeScript,$0.18),为 python-humanize 添加了 sep 参数(2 行 Python,$0.10);两项改动都有各自先失败后通过的测试,且项目的整个测试套件仍然全部通过。对于已经实现的功能,它只需调用一次模型就会停止,且不保留任何内容。
它还会拒绝添加完全不该属于这里的功能。有人给了它一张真实的 Apache 工单,内容涉及 Flink HTTP sink;结果一个 URL 库却内置了“HTTP sink”——测试通过、测试套件全绿,所有验证规则都符合,因为 Tewip 同时编写测试和代码时,总能让一对自洽的内容通过。因此,现在它会先判断该功能的描述对这个代码仓库是否有意义;如果没有,就会直接拒绝,且不产生费用。
以前的承诺是*“它不能碰你的代码”。现在的承诺是“它不会在未向你证明改动有效之前更改你的代码”*。
在任何人查看差异之前(新增)
tewip review --diff main...HEAD
哪些测试覆盖了这项变更,以及如何覆盖(它们导入该文件、导入导出该文件的包,还是仅仅文字匹配);其中哪些以前出现过不稳定情况,因此运行失败不该归咎于你的变更;这些领域以前出过什么问题;哪些内容完全没有测试;以及先运行哪些测试最能说明问题的命令。所有信息都来自项目自身的历史,因此无需付出任何成本。
在两个公开项目的 16 次真实提交上进行测量,开发者在这些提交中同时修改了源代码和测试:只提供源文件时,Tewip 16 次都找对了测试,12 次将其排在首位。
它还会展示变更重新协商了哪些约定——差异重写、撤回或重命名的每一条测试断言,并将新旧文本并列呈现。较早编写的测试代表有人认同的行为,而这是审查中唯一一件无法由变更自身的描述解释的事:其他内容都只是变更对自身的陈述。它不会评判——测试的变更自有其合理原因——它只会询问作者有意修改了哪些测试。
此外,它还会展示变更在测试之外改变的决定:拒绝某项操作的保护条件、其他代码会回退使用的值、决定负载下行为的限制、有意固定的版本或运行器。规则相同——只涉及已有内容,绝不涉及新增内容——提出的也是问题,而不是给出判决。
这两项功能都源于实际测量,而非构想。在后来发现导致问题的 18 个真实拉取请求中,Tewip 漏掉的那一项,其意图本身就是错的:它修改了新鲜度规则,也相应修改了测试,第二天就被撤回了。
这些检查并不是预测器,测量结果对此说得很清楚。在已知导致回归的 14 个拉取请求中,它们会对其中 4 个发出提示;而在来自相同仓库的 30 个普通合并拉取请求中,会对其中 9 个发出提示——即 29%,而非 30%。改变约定的变更并不更容易出错。它们的作用是把这些改动呈现给人看:在普通变更中,它们发现了 23 项改动,其中包括被删除的 raise SystemExit 保护条件、从 4 降至 1 的信号量,以及被更换的 CI 运行器镜像。无需调用模型,因此无需付出任何成本。
应隔离哪些不稳定测试,以及它们的成本(新增)
tewip flaky --days 30 # the dossier: quarantine, fix, or watch - with the evidence
tewip flaky --apply # writes the quarantine line into the test file
其他工具会统计重试次数。Tewip 已经判断出每次失败的原因,因此它可以指出:这项测试在 109 个不同分支上的 647 次运行中失败了 161 次——问题出在测试本身,而不是你的变更——将其隔离后,团队每周可省下 148 分钟 CI 时间。它不会隐藏一项大多数时候都会失败的测试(那是测试坏了,并非不稳定),也绝不会隔离失败迹象像是真实 Bug 的测试。
根据三个真实项目的 CI 历史记录(Gutenberg 两次、Supabase)进行测量:建议隔离 76 项测试,没有一项包含任何真实 Bug 导致的失败,每周分别可省下约 34、148 和 1,178 分钟 CI 时间。它还会告诉你隔离的测试何时不再失败、应该恢复使用——而这件事从来没人做。
回报的 Bug,以及团队会保留的测试用例(第 10 阶段,已完成)
tewip bugs report.xml --project SHOP # shows what it would file; nothing is created
tewip bugs report.xml --project SHOP --create # files them
tewip jira plan.json --export xray --out cases.csv # or --export zephyr
tewip bugs 只会针对它诊断为真实 Bug 的失败创建工单,并附上错误、证据和运行链接。不会为不稳定测试或损坏的环境创建工单——先进行诊断,正是这么做的原因。同一失败绝不会重复创建工单:第二次出现时,Tewip 会在自己创建的工单上添加简短备注。试运行完全无需 Jira 账号。
--export xray|zephyr 会将计划中的场景导出为这些工具可导入的 CSV,每个步骤占一行。无需账号,无需联网;导入由你完成。
安全性:哪些发现才真正重要(Phase 9,新增)
tewip secure # secrets + npm audit / pip-audit / osv-scanner, triaged
tewip secure --url http://localhost:3000 # also check the running app
tewip secure --zap-report zap.json # and triage an OWASP ZAP baseline report
tewip secure --gate # fails a build only on what needs a person now
扫描器善于发现问题,却不善于判断哪些问题重要。Tewip 会运行项目已有的扫描器,并将每项发现归类为立即处理、确实存在但不可触达(开发依赖、没有被导入的包、Git 未跟踪的文件)、噪声(README 中的占位符)或已接受(已有人员评估过,且位于仅由人编写的文件中)。每项结论都会附带证据。不调用模型,因此不产生费用,而且只定位凭据,绝不打印出来。
使用 --url 时,它还会像浏览器一样检查正在运行的应用:安全标头、Cookie 标志,以及绝不能提供给用户的文件(.env、.git)。不发送负载、不进行模糊测试、不绕过登录——而且如果目标地址不是本机地址,就会拒绝检查,除非你指定 --i-am-authorised。
对于深度扫描,它会打印 OWASP ZAP 命令,并对 ZAP 的报告进行分类,而不是假装自己就是扫描器。
在 gitleaks 项目自身带标签的样本上测得,在 Tewip 覆盖的凭据类型中:54 个真实凭据中找到 50 个。Web 检查在两个本地应用中找到了全部 8 个植入的弱点,而对经过精心配置、仅提供安全内容的应用未报告任何问题;在四个真实的公开首页上,每次报告都与这些网站实际发送的标头一致。该语料库暴露出的四个缺陷已修复;仍未发现的四项都不是凭据(一个未签名的令牌、一把 Stripe 测试密钥)。它绝不会声称项目是安全的——而是会列出检查了什么,以及没有检查什么。
Epic、故事与验收标准(Phase 14,新增)
tewip spec "Readers can search the library and open a result"
tewip spec "..." --project PROJ # shows what it would file in Jira
tewip spec "..." --project PROJ --create # files the epic and its stories
Epic、用户故事和 Given / When / Then 验收标准,均根据你的代码、测试和故障历史编写。凡是声称系统已经具备某项行为的内容,都会附上它读取的文件中的引文;Tewip 会逐字核验引文。无法引用的内容会变成待解答问题,而不是被写成笃定的陈述。它不会编写估算、日期或商业论证,因为这些内容无法核验。
在三个真实项目上测得:15 个故事、49 条验收标准;在 46 条关于现有行为的陈述中,22 条保留并附上可核验的引文,24 条转为待解答问题。
适用于任何 CI、你的聊天工具和你自己的机器(Phase 13,新增)
tewip report report.xml --gate # GitLab CI, Jenkins, CircleCI, Azure, Bitbucket, Travis, Drone
tewip watch --cmd "npm test" # beside your editor: explains each failure as it happens
tewip note --days 7 # the weekly quality note for a lead - free, no model calls
tewip report 会从 CI 平台自身的变量中识别当前所处的平台,打印分类看板,将其写入文件,并追加到作业摘要中;使用 --gate 时,只有对于已知的不稳定失败或环境噪声以外的失败,才会导致构建失败。在 GitLab 上,它会将诊断结果作为一条合并请求备注发布,并在下次运行时编辑同一条备注,而不是不断新增备注。设置 TEWIP_SLACK_WEBHOOK 或 TEWIP_TEAMS_WEBHOOK 后,它会通知团队聊天工具;如果没有需要人员处理的失败,则完全不会发送消息,因此没人会将它静音。
修复代码,但仅在测试证明可行时(Phase 12,新功能,选择启用)
tewip fix --code report.xml # shows the patch; your files are left as they were
tewip fix --code report.xml --apply # leaves the proved change in your working tree
从第一天起,Tewip 就拒绝修改应用代码。这是唯一的例外,而且只有在你输入 --code 时才会运行。它会针对自己以 0.85 或更高置信度诊断为真实 Bug 的故障,找到故障指向的文件,请求做出最小改动,然后验证结果:失败的测试必须通过,而且整个测试套件仍必须通过。它不会修改测试、配置、CI 文件、锁文件、迁移文件或任何包含机密内容的文件,因为修改这些内容后测试转绿并不能证明任何问题。如果它无法证明修改有效,就会改为解释 Bug,并逐字节还原你的文件。它绝不会提交或合并。
通过回放两个公开代码库中的 16 个真实 Bug 修复进行测量,涵盖两种语言:每个修复都使用修复前的提交,并且只运行该修复对应的测试,以确保真实 Bug 确实会复现。
| 项目 | 语言 | Bug 数 | 经测试证明 | 文件正确 |
|---|---|---|---|---|
| unjs/ufo | TypeScript、Vitest | 8 | 8 | 8 个中 8 个 |
| python-humanize/humanize | Python、pytest | 8 | 8 | 8 个中 8 个 |
每次改动都很小(1 至 16 行,中位数为 4 行),而且大多数改动与开发者自己编写的一致。 没有提出过错误修复:凡是无法证明有效的改动,都会被丢弃,并逐字节还原文件。
发布经理可读的一份完整记录(Phase 15)
tewip attest --out attestation.md
Tewip 对一次构建的所有了解,都记录在一个文件中:运行了哪些测试、每个故障是什么、由哪条规则或哪个模型作出判断、它拒绝作出哪些判断,以及它没有检查哪些内容。拒绝判断才是重点——一个从不说“我无法判断”的工具,不值得任何认证,因为它写下的每一行都可能只是猜测。
记录包含摘要;如果你提供密钥,还会包含 Ed25519 签名:
TEWIP_ATTEST_KEY=./private.pem tewip attest # sign it
tewip attest --verify attestation.json --key public.pem # check it
摘要不是签名,Tewip 会明确说明这一点:没有密钥时,它只能告诉你记录未受损,不能证明无人编辑过。编辑已签名记录中的任何声明都会导致验证失败,并以非零状态码退出。每次 CI 运行都会自动生成这样的记录。
这次事件有测试覆盖吗?(Phase 16)
tewip incident sentry-issue.json
读取 Sentry 负载、OpenTelemetry span 或普通堆栈跟踪,找到本应捕获该问题的测试,并给出四种判定之一:已复现、已通过(测试已运行但未捕获问题,这意味着情况更糟)、以不同方式失败,或从未运行。它不会说“可能已覆盖”。
上次我们对此作出了什么决定?(Phase 17)
tewip recall "the login test that keeps flaking"
团队曾经给 Tewip 的每条更正都会被保存。你只要询问,它就会给出你们当时作出的决定,并说明那是你们的决定,而不是它的。它绝不会自行依据更正采取行动。
从工单到运行记录(Phase 18)
tewip trace --only "SHOP-12"
将 Tewip 编写的测试计划与覆盖这些计划的测试,以及尝试执行这些测试的运行记录关联起来。只有能指出真实测试的场景才算已覆盖,只有真实运行记录尝试过的场景才算通过;其他情况都会说明缺少什么。无需模型,也没有成本。
这项工作真的完成了吗?(阶段 24)
tewip done --diff main...HEAD # one verdict
tewip done --prove --strict # also break the code, and fail the build unless ready
tewip done --json done.json # a sealed verdict that can be checked later
Tewip 可以回答有关一项变更的六个问题。审查 Agent 拉取请求的团队必须运行六条
命令并自行汇总答案——实际上,这意味着他们一条也不运行,只看 CI 通过就合并了。done 汇总了答案:
# Is this work done? Not ready
| Question | Answer |
| Is anything failing? | not checked |
| Is the changed code tested? | yes |
| Can the tests in this change fail? | no |
| Would the tests catch a bug in this code? | no |
| Is what was asked for covered? | not checked |
这是一次真实运行,针对的变更中三项测试全部通过。其中一项是 assert.ok(true),而这段新代码的三处刻意改动都没有被测试套件发现。CI 显示通过;但工作并未完成。
这里不做任何评判。每个答案都来自一个有自身准确率测量值的层——分诊规则、vouch、prove、trace——而这个功能只是将它们按固定且公开的顺序排列:哪里出了问题,然后哪些内容不能相信,最后缺少什么。同一项变更总会得到相同的结论。
**“无法判断”是一个真正的答案,而且绝不会被向上取整为“已就绪”。**结论需要回答两个无可替代的问题:是否有任何测试失败,以及是否有测试实际覆盖这段代码。如果 Tewip 从未见过该项目的测试结果,就会如实说明并拒绝批准;第一个版本对此给出的回答是“已就绪,有 3 项未检查”,这等于把一堆耸肩拼成了批准。每个结论也都会说明它不涉及什么,包括这里任何工具都无法知道的一件事:这项变更是否实现了预期目标。
使用 --json 时,结论会被封存:如果在文件中把 not-ready 改成 ready,验证就会失败,因此传入工单或发布说明的结论可以被核验,而不是只能被信任。它也是一个 MCP 工具,因此 Agent 可以在声称工作已完成之前,先询问自己的工作是否完成。
让 Tewip 成为其他 Agent 必须通过的同行(阶段 34,全新)
tewip a2a # serves on 127.0.0.1:41241
MCP 让助手可以将 Tewip 作为工具调用。A2A 则反过来:Tewip 本身成为一个 Agent,其他 Agent 必须通过它的检查,工作才算完成。对于一个声称要验证由人或 Agent 编写的工作的工具来说,这种形式再合适不过——当大部分代码由 Agent 编写时,负责检查代码的工具也必须能被 Agent 调用。
它会在 /.well-known/agent-card.json 提供卡片,并通过 JSON-RPC SendMessage 响应,包含八项技能:triage、vouch、pick、schema、access、perf、standards 和 done。
**它会提问,而不是猜测。**发送“检查我的代码”,你会收到一个问题,列出 Tewip 可回答的八项内容——A2A 为此专门提供了一个状态:TASK_STATE_INPUT_REQUIRED:
TASK_STATE_INPUT_REQUIRED
Tewip will not guess which question you are asking: that could be "Does this keep the
team's own rules?" or "Is this work actually finished?", and Tewip will not pick for you.
**防护措施就是设计本身。**这是一个 HTTP 端口,会在你的代码库中运行命令:
- 只能访问只读技能。
fix、write、build、pursue和prove均不在列表中——它们会修改文件或运行测试套件。有一项测试会断言它们绝不可能被调用。 - 除非传入
--host,否则它只会监听本机,并且会明确说明这意味着什么。 - 每个请求都会记录在
.tewip/a2a.jsonl中。
它的卡片声明 streaming: false 和 pushNotifications: false,而调用这些操作时会返回错误,并指出说明原因的字段。声明尚未实现的功能,是 Agent 卡片绝不能做的事。
这次改动会变慢吗?(Phase 30,新增)
tewip perf summary.json --record # on main: add this run to the baseline
tewip perf summary.json --strict # on a branch: fail the build on a real regression
Tewip 导入 k6 结果已经有一段时间了,但从未对它们作出判断。现在,它能回答每个团队在发布前都会问的问题。
难点不在于算术。“p95 从 180ms 升到了 210ms,涨幅为 16%”只需四行代码,但毫无用处,因为没人知道这 16% 是改动造成的,还是笔记本电脑造成的。以下是四次代码未发生变化的 k6 实际运行结果:
run 1 p95 = 60.9ms
run 2 p95 = 60.8ms
run 3 p95 = 62.8ms
run 4 p95 = 61.4ms
什么都没变,结果却相差 3%。如果工具把这叫作回归,它就会被关掉。
因此,Tewip 会分别回答两个问题,因为这两个问题会分别失效:
| 你自己的阈值达标了吗? | 你已经在 k6 脚本中写明了“足够快”的标准。Tewip 会读取结果,而不是让你把预算再写一遍。 |
| 它比之前慢了吗? | 只有在记录了三个或更多基线时才会回答。少于三个:无法判断。 |
有了这三个实际基线,第四次相同的运行结果为 61.4ms 时,不会被判为回归——而真正的回归则会:
- `http_req_duration` p95 282.5ms — **slower**
- baseline 60.8ms–62.8ms over 3 runs, middle 60.9ms
- 282.5ms is outside everything 3 baseline runs produced (60.8-62.8ms) by more than
their own spread, so the runs cannot explain it. That is +364% on the middle run.
**记录基线并非自动进行,这是刻意的。**如果工具把每次运行都存进基线,基线就会不断抬高:今天的回归变成明天的常态,对比结果总是显示“在范围内”,而服务的延迟在一个季度内涨到三倍,却没有任何一份报告出错。在主分支上使用 --record,其他地方只做对比。
**它绝不会预测耗时、容量,或用户的实际感受。**它看不到你的生产环境硬件、真实数据量或实际流量,而且每份报告都会明确说明这一点。
Lighthouse 也支持(Phase 31,新增)
tewip perf lighthouse-report.json --record
k6 衡量的是你的服务在负载下的表现。Lighthouse 衡量的是真实浏览器中加载一个页面的代价——最大内容绘制、布局偏移、阻塞时间等,这些指标更接近用户实际等待的时间。tewip perf 可以接收两者,并判断输入的是哪一种。
Lighthouse 更需要遵循同样的原则,而不是更少。**同一个未改动页面的四次 Lighthouse 实际运行结果中,LCP 分别为 922、912、936 和 916ms。**规则相同:至少三个基线,且超出范围的幅度要大于范围本身的宽度。
而且 Tewip 不会凭空给出结论。“2112ms 算差吗?”这个问题有公开答案,而答案来自 Google:Lighthouse 会根据各项指标各自的曲线进行评分。Tewip 会读取该分数,而不是照搬它记住的阈值——这很重要,因为在实际变慢的运行中,FCP 和 LCP 的值恰好相同,都是 2112ms,但 Lighthouse 对它们的评价却不同:
- good — `largest-contentful-paint`: 2112ms
- needs improvement — `first-contentful-paint`: 2112ms
- **poor** — `server-response-time`: 611ms
如果把一个记住的阈值同时用于两者,结果就会出错。
它还会显示 Lighthouse 不予评分的十项无障碍审计——逻辑焦点顺序、焦点陷阱,以及视觉顺序是否与 DOM 顺序一致。这些既不算失败,也不算通过;一个没有失败项、却有十项尚未回答的手动检查的页面,不能算是通过了检查。
它还会拒绝一件事:如果你的摘要来自 --new-machine-readable-summary,该导出根本不包含任何阈值结果——这个词只出现在脚本副本中。Tewip 会说“这份导出无法告诉我”,而不是说“没有任何指标超标”。
人人都能用吗?(Phase 28,新增)
tewip access # every markup file in the project
tewip access src/components/Nav.tsx # or just one
tewip access --strict # fails the build on a barrier that stops somebody outright
可读取 HTML、JSX、TSX、Vue、Svelte、Astro、ERB、PHP,以及服务端渲染模板语言—— Blade、Twig、Jinja、Razor、Handlebars、Liquid、Nunjucks、EJS——它们涵盖了网络上大多数实际使用的 HTML。
无障碍性是一个既关乎质量、也关乎法律的问题。《欧洲无障碍法案》自 2025 年 6 月起已可强制执行,而责任落在那些本就负责测试的人身上。
团队不信任现有工具的原因,也正是他们关闭所有扫描器的原因:axe、 Lighthouse 和 pa11y 会返回数百条未加区分的违规项,其中很大一部分根本不构成障碍,而没人有空花整个下午来逐一筛选。
Tewip 只会作出两种判断,不会有第三种:
- 障碍——按当前写法,这个元素会阻碍某些人,仅凭阅读就足以判断;
- 待确认项——这里有内容缺失或无法读取,只有你能作出判断。
它没有“警告”这一类,因为人们会学着忽略警告,然后一路滚过。
**6 barriers (5 that stop somebody outright), 18 for a person to settle.**
### button-name - 3 places
WCAG 2.2 4.1.2 Name, Role, Value (A), blocker.
Who it stops: a screen reader announces "button" and nothing else, so it cannot be used or spoken to.
- `components/VPNavBarAskAiButton.vue:2` <button type="button" class="VPNavBarAskAiButton">
- it contains no text and carries no aria-label, so it is announced with its role and nothing else
- do this: put text inside it, or add aria-label="..." saying what it does
每条发现都会注明其 WCAG 2.2 成功准则和等级,这样你就能把它交给监管机构,而不是只附上一句“对比度不好”的备注。
它拒绝擅自下判断,这正是值得运行它的原因。 alt={caption} 并不代表缺少 alt。文本来自子组件的按钮不能称为空。:aria-label="label" 是标签。<slot /> 是此文件无法看到的内容。这些情况都会被标为待确认项,而绝不会被指责——因为如果一个工具指控 <img alt={alt} /> 没有 alt 文本,它一天之内就会被关闭。
每份报告还会列出它没有检查的内容——对比度、焦点顺序、有意义的顺序、alt 文本是否合适——因为这些都需要渲染后的页面。结果干净,意味着这些检查没有发现问题。它绝不意味着产品具备无障碍性。
无需浏览器、服务端、网络、模型,也无需成本。1.65 秒内扫描了 416 个真实文件。
在四个公开项目上进行测量,从未使用测试样例——excalidraw、bootstrap、vitepress 和 svelte: 50 项障碍,其中 29 项会直接阻碍某些人,另有 90 项待确认。 过程中发现并修复了七类误报,它们全都是 Tewip 自身的缺陷,而非这些项目的问题,并且在测试样例中都无法触发。详见 ROADMAP.md。
已经运行过 axe?把 JSON 交给 Tewip(Phase 29,新增)
tewip access --report axe-results.json
阅读标记语言无法检查对比度。浏览器可以,所以如果你的 Playwright、Cypress 或 jest-axe 测试套件 已经生成 axe 报告,就把它交给 Tewip——它会进行分类处理,而不是转述报告。
这里的数字会让人有些不安。axe 4.14 检查九个真实页面后,返回了 174 项发现:
| 53 项障碍 | 属于真实的 WCAG 准则,并附有 EU Accessibility Act 引用的 EN 301 549 条款 |
| 51 项建议 | axe 规则标记为 best-practice,但无法对应到任何准则 |
| 70 项待确认 | axe 自己的 incomplete 项——它无法判定 |
axe 提供的内容中,有 42% 并未测试任何标准。 单是 region——“所有内容都应包含在地标中”——就有 40 项发现,超过了页面上的所有真实障碍。Tewip 会将这些报告为建议,
绝不会报告为障碍,也不会因此导致构建失败。这种区分正是核心所在。
而未检查清单也诚实地缩短了:其中七项里一项——1.4.3 对比度——转为 已检查。其余六项保持不变,因为没有任何工具可以判定它们。报告还会注明本次运行 覆盖了多少个页面,并明确说明:对于未打开的页面或未到达的状态,相关情况一概未知。
## Checked here only because a browser run was given
- **1.4.3 Contrast (Minimum)** - would otherwise be unreachable: needs the computed colours of a
rendered page, not the markup
只有在本次运行打开了同一个文件并重新运行等效规则时,才会搁置源代码发现。 在 React 或 Vue 项目中,这意味着不会搁置任何发现——源代码和渲染后的输出是两种 不同的产物,任何一方都不能作为另一方的证据。
已经改用 Lighthouse?也可以(Phase 32,新增):
tewip access --report lighthouse-report.json
Lighthouse 的无障碍审计就是 axe 规则——规则 ID 相同——因此适用相同的分类处理方式。大多数 团队都在 CI 中运行 Lighthouse,却没有单独的 axe 步骤;他们可以通过已在运行的工具获得上述全部 功能。
问题在于,Lighthouse 报告会去掉 axe 的标签,所以它只会显示 landmark-one-main,却不会说明对应的是哪个 WCAG 准则,也不会说明它是否属于任何准则。这一区分涉及 axe 的 105 条规则中的 30 条,而一张记载了 100 个映射关系的记忆表格难免出错。因此,这张表直接从 axe-core 生成(scripts/axe-rules.ts)——105 条规则中,75 条带有准则,30 条仅属于最佳实践,没有未分类规则。
它还会列出 Lighthouse 标记为 manual 的十项审计——逻辑 Tab 顺序、焦点陷阱,以及视觉顺序是否符合 DOM 顺序。这些都会成为待确认项,绝不会算作通过。一个页面即使没有失败项,但仍有十项未回答的手动检查,也不能算通过。
教会它你们的内部规范(Phase 27)
tewip standards # what this project has taught Tewip
tewip standards --diff main...HEAD --strict
Tewip 了解许多适用于一般软件的真理,却不了解任何关于你们公司的事情。每个 IT 部门都有自己的规则——“发布时,绝不能删除已经停止写入的列”,“定位器使用 data-testid”,“测试不得休眠”——而无法教会这些规则的工具,只会与团队的内部规范相冲突。
规范是 .tewip/standards/ 中的 Markdown 文件:
---
description: A fixed wait hides a race instead of fixing it.
applies-to: tests/, e2e/
forbid: /waitForTimeout|sleep\(/ because a fixed wait passes by luck and fails in CI
---
Wait for the thing you are actually waiting for — an element, a response, a state —
never for a number of milliseconds.
区分这两者正是关键。 forbid 和 require 行会经过检查并强制执行,同时引用违规行,并在旁边打印你给出的理由。下方的说明文字则是指导原则:Tewip 会在编写或修复内容时参考它们,并在报告中引用——但它们绝不会被计为已检查,也绝不会报告为已遵循。如果团队误以为无法强制执行的规则正在被执行,处境会比没有任何工具的团队更糟,因为他们自己也不再留意这些规则。Tewip 无法解析的规则会被报告为不可用,而不会被悄悄丢弃。
写好后,这些规则会出现在所有评估工作成果的地方:tewip review、拉取请求评论,以及 tewip done 中的一个问题——“它是否遵循了本项目自己的规范?”——如果答案是否,就会指出具体规则。Tewip 编写测试草稿时,指导原则也会放入提示词中,因此生成的内容会遵循你们的惯例,而不是 Tewip 的惯例。
此迁移会对正在运行的数据库造成什么影响?(Phase 26)
tewip schema --diff main...HEAD # or: tewip schema migrations/0002_drop_nickname.sql
tewip schema --strict # fails the build on a change that breaks the running version
拉取请求中代价最高的变更通常不是测试,而是迁移。在上一次部署仍在查询某列时将其删除。创建索引时未使用 CONCURRENTLY,导致最繁忙的表上的所有写入操作都被阻塞。在已有数据的表上添加 NOT NULL。这些风险都有据可查,而工具可以仅凭迁移文本指出其中每一项。
1 of 1 migration file(s) read. 1 change(s) would break the running version or destroy data
## Breaks the version that is still running (1)
- drops the column nickname from orders — migrations/0002_drop_nickname.sql:1
- Instead: deploy code that no longer selects or writes the column, then drop it in a later
migration. During a rolling deploy the old version is still running and will fail on a column
that is gone
## Code that still uses what this takes away (1)
- orders.nickname is still named in 1 file(s): app/orders.py
最后这一部分是 SQL 检查工具做不到的:它会在你自己的代码中搜索迁移会移除的内容。“此操作会删除 users.nickname,而仍有四个文件在读取它”和“删除列有风险”是两种不同的说法,前者才能避免故障。
它可以读取 SQL、Django、Rails、Alembic、TypeORM、Sequelize、Knex、Laravel、EF Core 和 Liquibase 迁移——Prisma、Drizzle、Flyway 和 golang-migrate 也涵盖在内,因为它们最终都会生成纯 .sql。它只检查正向迁移(回滚步骤包含变更的镜像操作,因此读取它会把创建表的迁移误判为删除表),并且在团队已经采用安全做法时保持沉默——atomic = False、algorithm: :concurrently、NOT VALID、postgresql_concurrently=True。如果它无法读取某个迁移,就会明确说明,而不是放行;“已读取但未发现问题”绝不会被误认为“未读取”。
已在真实项目中试用。 在 Zulip 的 200 个迁移中,它读取了 199 个,发现了 19 项会破坏当前运行版本或造成数据丢失的变更;逐一对照文件核验后,发现都准确无误,其中包括一个藏在 Python 数据迁移原始 SQL 中的 DROP COLUMN。在 Mastodon 的 60 个迁移中,它发现了一项会重写整张表的 change_column,而迁移的名称却声称只添加了一列。
它不了解、也会明确说明的事项:表的大小和数据库的繁忙程度。因此,它会报告语句会做什么——重写表、扫描每一行、阻塞写入——并将所需时间留给了解数据的人判断。在 CI 中,发现结果会写入拉取请求评论;任何会破坏当前运行版本的变更都会作为拒绝项进入证据包:测试套件通过并不能验证迁移。
只运行可能发现此更改的测试(Phase 25)
tewip pick --diff main...HEAD # or: tewip pick src/pricing.js
tewip pick --list # just the files, for piping into your runner
一套需要运行四十分钟的测试,人们会逐渐不再运行。供应商把这包装成一种预测——由模型猜测哪些测试重要——但猜测并不适合这项工作,因为猜错的代价是 Bug 进入生产环境,而且没人能审计这项猜测。
在这里,跳过某项测试,就等于声称它不可能发现此更改,而声称需要证据。符合以下任一条件的测试都会运行:导入了已更改的文件;通过另一个模块访问了已更改的文件;本身被此次更改编辑;运行时会读取代码库(扫描所有源文件的守护程序可能被任何文件破坏);或者最近失败过——未知情况绝不会被跳过。其他测试都会排除,并在旁边打印原因。
以下情况会完全拒绝缩小测试范围:更改涉及依赖项、构建或测试配置、共享的设置或夹具;测试套件会驱动浏览器,因为浏览器测试实际覆盖的内容不会写在导入语句中;或者无法将已更改文件追溯到任何测试,因为代码可能通过导入之外的方式被访问。
通过破坏代码来衡量(eval/pick-safety.ts):实际进行一次变异,询问 pick 哪些测试可能发现它,然后运行整个测试套件,找出哪些测试确实捕获了它。漏掉任何一个能捕获变异的测试,就算失败。
2026-10-19,在 10 个模块和包含 1,288 项测试的套件上重新推导;由于 Phase 92 发现变异引擎可能写入与报告内容不同的内容,因此从头重做:**28 次实际更改,其中 19 次被完整测试套件捕获,因而可用于评估测试选择;0 个能捕获变异的测试被漏掉。**其余 9 次没有被任何测试捕获,因此不计为成功,而是排除——没有测试发现的更改,无法证明某项选择是安全的。
**节省的比例并非一个固定数字,而且此前公布的 5.7% 并不具代表性。**这取决于已更改文件的核心程度;这反映的是测试实际能够覆盖的范围,而不是测试选择的缺陷:
| 已更改文件 | 选中的测试文件(共 101 个) | 占比 |
|---|---|---|
engine/release/channels.ts | 4 | 4% |
engine/flaky/flaky.ts、engine/perf/compare.ts | 5 | 5% |
engine/secure/secrets.ts | 12 | 12% |
engine/prove/mutants.ts | 31 | 31% |
engine/vouch/vouch.ts | 44 | 44% |
engine/schema/statements.ts | 47 | 47% |
engine/rules.ts、engine/import/junit.ts | 52 | 52% |
在 19 次用于评估的更改中,平均值为:27.9%。更改叶子模块时,运行范围会缩小到整个测试套件的二十分之一;更改规则引擎时,套件的一半会合理地覆盖到此次更改。此前的数字是在边缘文件上测得的,位于该范围的底端,因此不适合用来规划 CI 预算。在所有测试都会导入包入口的单体代码库中,它会指出没有测试可以跳过,而不是凭空编造节省空间。它还是一个 MCP 工具,因此 Agent 可以在运行任何测试前询问应该运行哪些测试。
在 CI 中,mode: pick 会在测试运行前执行,并将要运行的文件交给工作流:
- id: pick
uses: hmamut39/tewip@main
with: { mode: pick } # needs fetch-depth: 0 on actions/checkout
- run: npx jest ${{ steps.pick.outputs.tests }}
if: steps.pick.outputs.narrowed == 'true'
- run: npx jest
if: steps.pick.outputs.narrowed != 'true'
所有被排除的测试及其原因都会写入作业摘要,因此“为什么 CI 没有运行那个测试”可以直接从这次运行中找到答案。如果无法安全缩小范围,narrowed 会是 false,且 tests 为空——因此,即使工作流忽略该标志,也仍会运行全部测试,这是安全的默认行为。
测试能发现错误吗?(第 23 阶段)
tewip prove src/pricing.js # or --diff main...HEAD for what a change touched
tewip prove src/pricing.js --strict # exits non-zero when something goes unnoticed
覆盖率说明某行代码运行过,但并不说明有人检查过它。prove 通过执行来回答这个问题:
它会对代码做一些细微且刻意的改动——将边界移动一位、反转条件、把 and 改成 or、翻转返回标志、放宽模式——运行会触及该文件的测试,并报告所有未被测试发现的改动。
4 change(s) were made to your code and the covering tests were run against each. 3 went unnoticed.
- line 2: a boundary moved by one
- was: if (total >= 100 && isMember) return 0.2;
- became: if (total > 100 && isMember) return 0.2;
- line 9: a sign reversed
- was: return total - total * off;
- became: return total + total * off;
这两种情况都确实存在:测试用过 150 和 10,却从未测试边界本身;而调用 finalPrice 的测试根本没有断言任何内容。覆盖率报告看不出这两个漏洞,因为这两行代码都已被覆盖。
它宁可拒绝执行,也不轻易报告,而这些拒绝正是报告值得一看的原因:
- 文件会被逐字节恢复原样,包括换行符,并且会验证恢复结果,而不是想当然。恢复失败会被视为错误,而不会被一笔带过。
- 不会触碰任何可能导致程序卡住的代码。 循环头永远不会被修改:反转
while中的比较条件不会让测试失败,只会让程序永远运行下去。 - 测试套件失败并不能证明任何事。 它会先原样运行所有覆盖该文件的测试。未通过的测试会按名称排除;如果没有任何测试通过,它会说明这一点并停止——否则,原本就失败的测试会看起来像是“发现”了每一处改动。
- 它会检查测试是否确实加载了该文件。 如果直接破坏该文件也没有任何测试失败,那说明测试读取的是已安装的副本或构建产物;它会指出这一点,而不是报告一页不存在的漏洞。它在遇到的第一个 Python 项目中就发现了这种真实情况:
import humanize实际解析到了完全不同的代码检出目录。 - 字符串和注释永远不会被修改,因为测试不应该被要求发现消息内容的改动。
它会追踪导入,因此即便文件是通过另一个模块被访问的,也仍会得到验证——在分层代码库中,大多数文件都是如此。它需要单元测试框架(jest、vitest、mocha、node --test、pytest、go test、maven、gradle、dotnet),不调用任何模型,成本只有时间。它也是一个 MCP 工具,因此编码 Agent 可以询问:它所依赖的测试能否拦住自己犯下的错误。
这些测试真的可能失败吗?(阶段 22)
tewip vouch tests/ # or a file, or the whole project
tewip vouch tests/ --strict # exits non-zero, for a merge gate
可读取 JavaScript、TypeScript、Python、Java、Kotlin、Scala、Go、C#、Ruby、PHP 和 Rust,并会明确告知,而不是清空它无法读取的文件。
如今,拉取请求中的大多数测试都是由 Agent 编写的,但决定这些测试是否有价值的问题却无人提出:这个测试真的可能失败吗? 覆盖率会统计那些什么都没检查的测试。CI 会因此显示绿色。审查者匆匆扫过看似合理的内容,然后批准。
vouch 会读取他人编写的测试,并逐个测试给出答案,同时引用所依据的代码行。它能找到未完成预期的测试(expect(value); 没有匹配器——读起来确实像是在检查)、将值与自身比较的测试、捕获并丢弃自身失败的测试、从未运行的测试,以及完全不比较任何值的测试。不用模型,也没有费用。
它也是一个 MCP 工具,因此编码 Agent 在声称某项更改已被测试覆盖之前,可以先询问它刚刚编写的测试。
在六个公开项目的 41,523 个测试上进行测量——包括 Keycloak、OpenMRS、bitwarden/server、Saleor、GOV.UK Frontend 和 Firefly III,使用 Java、C#、Python、PHP 和 TypeScript。每项指认都由人工对照源代码进行核验,统计数据会针对固定的提交保存在 corpus/expected.json 中,并在每次发布时通过 npm run corpus 重新生成。
它曾经严重出错过,这件事值得直说。 这段文字的早期版本曾报告,在 5,448 个测试中有 19 个判断全部正确。后来有人让 Tewip 检查 Keycloak,它却把 166 个正确的测试判为毫无价值——这些测试全都采用了标准的 Java 写法来表达“我预期这里会抛出异常”;Assert.fail() 会让它们正确失败,因为 AssertionError 不是 Exception。这条规则从未检查捕获的类型。在此之前和之后都有 918 个单元测试通过,这正是建立测试语料库的原因:真正重要的是 Tewip 如何评价他人的代码,而此前没有任何东西对此进行衡量。
它有两件事绝不会做,这也是这份清单值得一读的原因:
- 它不会猜测。 如果某个测试的检查逻辑位于它无法跟踪的辅助函数中,它会按名称报告为尚无定论,绝不会说它毫无价值。错误指责他人的测试,代价高于漏报。
- 它不会夸大结论。 即使没有断言,代码抛出异常时测试仍会失败——在 axios 中,有个测试会编译 TypeScript 测试样例并运行它,但这是有意为之,不检查任何内容。这类测试会单独报告为“未比较任何值”,不会被说成有问题,也不会触发
--strict。
通过执行测量召回率(eval/vouch-recall.ts):修改源文件,记录哪些测试能捕获哪些修改,移除那些被证明捕获了某项内容的测试中的最后一条语句,然后重新运行所有测试。之前能捕获某项修改、现在却什么也捕获不到的测试,已被证明失去了检查能力——这个标签并不依赖 vouch 自身的模式。在两个项目的七组源文件/测试对中,这类情况共计 17 个,vouch 指认了 12 个(70.6%);未指认的五个测试仍然保留着真实断言,因此它们的判断也正确。没有任何仍能捕获修改的测试被错误指认。
目标,而不是动词(阶段 19 和 21)
tewip pursue "keep the checkout flow green"
tewip pursue "keep the checkout flow green" --act
为 Tewip 设定一个目标,它就会根据实际可见的信息说明接下来会做什么,以及为什么。执行顺序固定、公开且可测试——不由模型决定:先处理真正的 bug,然后是环境问题、间歇性故障、无法解释的失败、不会失败的测试、覆盖率缺口,只有到最后,目标才值得进行证明。对同一状态做出两次判断,结果相同。
使用 --act 时,它会执行这个决定,而拒绝操作在这里最为重要:
- 它绝不会亲自完成工作。 每项操作都是现有命令,且能证明其所做的更改——只有测试通过,并且在出现故障时失败,
write才会保留该测试。 - 允许的操作只有两项:编写缺失的测试,以及证明目标已达成且状态良好。其他所有操作都会按名称拒绝。它不会绕过真正的 bug。
- 执行一项操作后就停止。 不会在无人看管的情况下循环执行。
- 所有执行或拒绝的操作都会记录到
.tewip/actions.jsonl,并附上为其提供依据的运行记录。 - 它不会重复操作:在新的运行提供新证据之前,同一次尝试都会被拒绝。
已尝试的项目
| 项目 | 方式 | 结果 |
|---|---|---|
| 一个私有练习仓库(未附链接:该仓库是私有的) | 在包含 11 个预设故障的发布 PR 上运行 GitHub Action | 诊断评论、一个环境问题,以及一个修复 PR;检出后,其中 5 项定位器更改均通过检查。陷阱未触碰。合成数据。 |
| WordPress Gutenberg(公开项目) | 离线分析 5 个时间窗口中的真实 CI blob 报告,其中两个是在流水线冻结后收集的 | 在 1,441 个已标注的真实故障中,准确率为 84–98%;这是唯一拥有足够漂移案例以测量该类别的项目(注意事项见 eval/README.md) |
| JupyterLab(公开项目) | 离线分析其 Galata UI 测试套件 | 在 41 个已标注的故障中,准确率为 90.2%;仅使用确定性规则时,判定的 34 个案例全部正确 |
| LXD UI(公开项目) | 离线分析其拉取请求测试套件 | 在 69 个已标注的故障中,准确率为 91.3%;规则判定的 63 个案例全部正确。6 个误判全都是被当作真实 bug 的跨分支间歇性故障 |
| Supabase Studio(公开项目) | 影子运行:使用真实 CI blob 报告,不向项目发布任何内容 | REAL_BUG 6/6,TEST_DATA 6/6;TIMING 占多数,其信号与标签一致 |
| Penpot(公开项目) | 对仍可下载的 21 次运行进行影子运行 | 在第三个项目上运行正常;只有 5 个已标注案例,数量不足以评分 |
| Nextcloud server(公开项目) | 已尝试 | 收集顺利(45 个故障),但在该时间窗口内,只有 1 个案例具备足够证据进行标注:数据过于稀疏,无法评分 |
| Ghost(公开项目) | 已尝试 | 无法从外部使用:Ghost 只会为失败的运行上传 blob 报告,且仅保留约一天,因此 255 次运行中只有 3 次留存 |
影子运行用于在不触碰公开项目的情况下测试 Agent:npm run harvest -- <source>,然后运行 node scripts/shadow.ts <source> [--publish your/private-repo]。
对比(2026 年 9 月)
其他工具涵盖了这项工作的一部分。下表根据各工具自己的公开资料,说明它们的主要用途,以及 triage-agent 的不同之处;这并不是对它们的基准测试。
| 工具 | 功能 | triage-agent 的不同之处 |
|---|---|---|
| Playwright Test Agents — Healer | 重写失效的选择器并重新运行测试;其开发者报告称,约 75% 的选择器失败都能修复 | triage-agent 会先判断是否确实是选择器发生变化(先排除真实 Bug、竞态、环境和测试数据问题),再只修复符合确定性规则且经重新运行验证的问题:14 项修复全部正确。遇到每个选择器失败都进行修复的 Healer,也会把已移除的功能“修复”回来 |
| Trunk、BuildPulse、Datadog Test Optimization | 根据通过/失败历史检测不稳定测试,并将其隔离,以免阻止合并 | 核心信号相同(同一份代码上的测试既失败又通过),但还会为每个失败提供原因,而不只是标记不稳定测试,并为每个失败设置合并门禁。基于 2,296 个真实失败进行测量(见下文) |
| ReportPortal | 将失败分类为产品 Bug、自动化 Bug 或系统问题,方法是匹配此前人工做出的分类 | 同样借助人的经验进行学习(/triage 更正),还提供无需训练数据的确定性规则,因此从首次运行起就能发挥作用 |
| Develocity | 根据历史记录对构建和测试失败进行根因归类(Gradle 和 Maven) | 通过 JUnit XML 或 TRX 支持任何语言,并针对每个失败采取行动:评论、归类环境问题、验证修复、设置门禁 |
它尚不具备而其他工具具备的功能包括:仪表板和托管历史记录(它会将历史记录保存在 Actions 缓存中),以及多年的生产环境使用经验。
智能合并门禁
设置 gate: real-failures 后,只有当失败的测试需要人工处理时,分诊步骤才会使构建失败:真实 Bug、发生变化的选择器、测试数据问题,或任何尚未确定的情况。被确定性规则或人工判定为不稳定或环境问题的失败不会阻止合并。仅由 AI 得出的结论仍会阻止合并,因为模型的可靠性较低。
- name: Tests
continue-on-error: true # let the gate decide the outcome
run: npx playwright test # or pytest, jest, mvn test, go test ...
- name: triage-agent
if: always()
uses: hmamut39/tewip@main
with:
gate: real-failures
在来自五个项目的 2,296 个已标注真实失败上进行测量,门禁放行了 1,823 个失败,其中没有一个是真实 Bug、发生变化的选择器或测试数据问题;所有不稳定和环境问题中,有 84%(2,160 个中的 1,823 个)不再阻止合并。请谨慎看待这个零:首次测量中有一个真实 Bug 被放行(功能分支上的新测试遇到了新后端代码引发的服务器错误),随后服务器错误规则进行了调整:如果测试从未通过过,就不再将其归为环境问题。零是在暴露这一漏洞的同一批数据上测得的,因此结果偏乐观;每周运行的任务会持续用新数据进行检查。这些计数也会作为步骤输出(blocking、flaky、environment),供后续步骤使用。
工作原理
-
reporters/triage-reporter.ts是一个自定义 Playwright 报告器。它与list一同运行。对于每次状态为failed或timedOut的尝试,它会捕获错误、堆栈、持续时间、重试次数、trace、截图和error-context.md路径、运行 ID 以及 git SHA。它会对每次失败运行规则引擎,并将记录(每条都包含其diagnosis)追加到data/triage.jsonl。它还会在data/runs.jsonl中为每次运行追加一行摘要,记录每项测试的运行次数。 -
engine/是第 2 阶段规则引擎。它具有确定性:相同的失败和历史记录总会得出相同的答案。它会将失败归类为五种根因之一(REAL_BUG、LOCATOR_DRIFT、TIMING、ENV、TEST_DATA),并给出置信度和证据。如果没有规则能作出判断,它会返回置信度为 0 的UNKNOWN,并附上每条规则观察到的内容,供第 3 阶段使用。 -
engine/llm/+engine/trace/构成第 3 阶段 LLM 层。它只处理规则判定为UNKNOWN的失败。它会读取trace.zip(最终 DOM、失败的请求、控制台错误、测试步骤)和error-context.md(页面快照和失败代码行,已去除注释)。它会生成简短的结构化摘要,而不是原始日志,并要求模型返回严格的 JSON 答案,且答案必须属于五种类别之一。答案会在本地再次校验。如果调用失败或答案无效,该失败仍会保持为UNKNOWN。engine/pipeline.ts会先运行规则,再调用 LLM;报告器、CLI 和评估流程都会使用它。 -
eval/存放真实标签和evaluate.ts,后者会输出第 3 阶段的准确率指标。eval/harvest/会从公开仓库导入真实的 CI 运行记录。请参阅 eval/README.md。 -
engine/decide.ts是 Agent 的 DECIDE 步骤。它会根据 ROADMAP.md 中的自主性表格,将每项诊断转化为下一步行动:修复定位器或固定等待时间、重新运行并通知、修复测试设置、提交 Bug,或升级给人工处理。置信度低于 0.5 时总会升级处理。只有置信度高于 0.85 时,代码修复才符合由 Agent 执行的条件。目前尚不会执行任何操作:第 5 阶段才会开始执行。 -
outlets/存放第 4 阶段视图。每个视图都会呈现相同的TriageItem:诊断、下一步行动、置信度、数据来源和证据。npm run triage会将它们以 Markdown 格式写入data/reports/<dataset>/(已加入 gitignore)。角色 视图 范围 开发者 pr-comment.md:哪些失败可能由此变更导致,以及原因每次失败的运行 SDET sdet-board.md:按下一步行动分组的所有失败每次失败的运行 DevOps / SRE infra.md:按故障类型分组的环境失败、重试恢复情况,以及一条合并告警每次失败的运行 业务 / UAT release.md:用通俗文字给出 READY / WAIT / NOT READY 状态每次失败的运行 高级 / 首席工程师 quality.md:不稳定性热点、重复出现的模式,以及按 CI 时间损失排序的测试设计欠缺整个时间窗口 架构师 architecture.md:各领域的脆弱性、导致多个测试套件失败的单次提交,以及失败的依赖项整个时间窗口 主管 / 经理 digest.md:失败率在时间窗口前半段和后半段之间上升的领域整个时间窗口 -
scripts/history.ts会按测试对记录分组并输出失败率。当某项测试的失败率在 20% 到 80% 之间,且错误消息不一致时,它会将该测试标记为SUSPECTED_FLAKY。 -
scripts/diagnose.ts会对所有已收集的失败运行引擎,并输出第 2 阶段指标(覆盖率),同时标明数据集来源。 -
tests/包含 5 项故意失败的测试,由fixtures/离线提供。这些测试仅用于验证流水线。根据它们计算出的数值并非产品指标。
data/ 已加入 gitignore,因为收集的数据会保留在本地。每次运行都会将其产物写入 test-results/<runId>/,因此旧运行的附件路径仍可正常使用。
规则(engine/rules.ts)
按此顺序,首个匹配的规则决定类别。如果优先级较低的规则给出不同结论,置信度会降低 0.1。
| 规则 | 类别 | 触发条件 |
|---|---|---|
env-mass-failure | ENV | 至少 3 个测试失败,且占本次运行的至少 30%,并且失败具有相同的错误特征。大量失败但错误各不相同不计入。 |
env-network | ENV | 错误显示连接错误(net::ERR_*、ECONNREFUSED 等)、HTTP 5xx 或身份验证失败 |
test-data-collision | TEST_DATA | 错误显示测试数据与现有数据冲突(重复键、唯一约束、“already exists”) |
env-server-error | ENV | 测试期间应用的后端返回了 5xx;如果测试是有意触发错误,或错误仅在此提交中出现,则不适用 |
locator-drift-strict | LOCATOR_DRIFT | 某个操作的定位器现在匹配多个元素,且其中恰好有一个元素包含定位器名称作为完整单词(name: 'Edit' → “Editor canvas”、“Edit link”)。修复方法是使用 Playwright 为该元素打印的定位器。 |
timing-intercepted | TIMING | 元素已就绪,但错误指出另一个元素(提示条、菜单或覆盖层)拦截了指针事件,导致点击未能触达目标。也包括 Selenium 的 Other element would receive the click 和 Cypress 的 is being covered by another element(在标注数据上测量前为 0.65) |
timing-stale-element | TIMING | Selenium 的 StaleElementReferenceException 或 Cypress 的 the page updated while this command was executing:页面在查找元素后、使用元素前替换了它(测量前为 0.65) |
timing-retry-pass | TIMING | 同一次运行中,同一代码的另一次尝试通过了 |
timing-known-flaky | TIMING | 已知的不稳定测试:在较早的一次运行中,它先因相同错误而失败,随后在重试时通过。请求在测试期间失败时,回退步骤 |
locator-drift | LOCATOR_DRIFT | 从未找到该元素(Playwright、Selenium NoSuchElementException、WebdriverIO、Cypress Expected to find element),但页面快照中存在名称相似的元素(相似度 ≥ 0.6)。如果测试没有记录过通过的运行,则不适用:没有记录就无法证明该元素曾经匹配。 |
timing-flaky | TIMING | 至少 3 次运行中,失败率为 20%-80%,且错误消息各不相同 |
real-bug-assertion | REAL_BUG | 对历史稳定的测试出现值不匹配:之前测试正常,此后一直以相同方式失败 |
各规则判断正确的频率
使用 node eval/calibration.ts 在六个已标注的数据集上测得,仅使用规则,不使用模型。claimed 列表示规则在答案旁打印的置信度;observed 列表示该答案与人工标签一致的频率。规则声称的准确率高于实际准确率,是本项目绝不会容忍的一类错误。
| 规则 | 案例数 | 正确数 | 实际准确率 | 声称的置信度 | 项目数 |
|---|---|---|---|---|---|
timing-retry-pass | 1478 | 1463 | 99.0% | 0.74 | 6 |
env-network | 395 | 395 | 100.0% | 0.80 | 5 |
env-server-error | 43 | 43 | 100.0% | 0.60 | 5 |
timing-intercepted | 32 | 32 | 100.0% | 0.80 | 5 |
real-bug-assertion | 26 | 10 | 38.5% | 0.40 | 5 |
locator-drift-strict | 17 | 17 | 100.0% | 0.89 | 2 |
timing-flaky | 9 | 9 | 100.0% | 0.90 | 1 |
test-data-collision | 6 | 6 | 100.0% | 0.70 | 1 |
locator-drift | 5 | 3 | 60.0% | 0.80 | 2 |
除两条规则外,其余规则都低估了自身准确率。这很安全,但也有代价:合并门槛信任 0.7,自动修复需要 0.85;一条规则在 395 次中判断正确 395 次,却只声称置信度为 0.80,就会把本不需要人工查看的工作交给人处理。
real-bug-assertion 是例外,而且它会告诉团队应用程序出了问题。它声称置信度为 0.65 + 0.05 x failingStreak,最高可达 0.9,但在 26 次中只判断正确 10 次——漏掉的 16 次里,都是把不稳定测试误判成真实错误。连续失败次数这一项不仅毫无依据,方向甚至完全相反:连续失败 1 次时,这条规则在五个项目中的准确率为 62.5%;连续失败 2 次或更多时,十次中一次也没判断正确。现在它的置信度设为 0.40,与实际准确率一致,并且低于两个门槛——交由人工处理,这正是五次中两次正确的信号该处的位置。它的判断结果没有改变:规则层仍然回答相同的 2,012 个失败案例,判断正确的仍是同样的 1,979 个。
locator-drift 的准确率为 60%,经过测量后暂不调整:两个项目中的五个案例不足以改变这个数字;如实说明这一点,好过忽略它或凭空编造修复方案。
历史记录是截至当时的:每个失败案例只根据截至该次运行(包括该次运行)的记录进行判断,不会参考后续记录。因此,测试首次失败时没有历史记录,基于历史的规则会放弃判断。
用法
需要 Node >= 22.18。Node 可直接运行 .ts 脚本。
npm install
npx playwright install chromium
npm test # one run (records are diagnosed as they are saved)
npm run test:loop # 5 runs in a row, to build up history
npm run history # failure-rate table
npm run diagnose # rule-engine classification + coverage metric (add -- --evidence for details)
npm run eval:synthetic # rules + LLM vs the synthetic labels (pipeline check, not a product metric)
npm run eval # rules + LLM vs your real labels in eval/ground-truth.jsonl
npm run lab # drift lab: the full collect -> diagnose -> decide -> auto-fix -> verify loop, locally
npm run triage # every role's view for data/ + the phase-4 metric
npm run triage -- --data data/real/gutenberg-holdout # the same on real CI data
npm run test:engine # unit tests for rules, LLM layer, decide step and views (no network)
自动修复与漂移实验室(第 5 阶段,进行中)
engine/autofix/ 是用于 LOCATOR_DRIFT 的 ACT + VERIFY 循环。它会在测试规范中找到失败的定位器,然后尝试最多 3 个从失败时页面中获取的、基于角色的替代定位器。每次尝试后都会重新运行该测试;仅当测试通过时才保留改动,否则就还原文件。代码内置了以下安全规则:
- 只编辑沙盒目录中的文件,绝不修改应用程序代码。
- 如果损坏的调用之后没有
expect(),就拒绝处理,因为此时通过测试并不能证明任何问题已解决。 - 仅处理置信度高于 0.85、由确定性漂移规则(
locator-drift或locator-drift-strict)诊断为漂移的情况。在真实 CI 数据中,AI 单独会以 0.88–0.96 的置信度把真实错误判成“漂移”,因此仅由 AI 判定为漂移的情况会交由人工处理。 - 每项判断都会记录原因,并且绝不会推送或合并。
npm run lab 会针对 lab/app 运行整个循环。测试是针对版本 v1 编写的。版本 v2 是一个发布版本,其中有 6 个重命名或重新设计的元素,以及 5 个陷阱:已移除功能的相似元素、错误的价格、已禁用的按钮、服务器宕机,以及一个没有断言的测试。实际情况记录在 lab/cases.json 中。当前结果为:7 个漂移定位器中有 6 个修复并验证通过,0/6 次错误修复。这是合成数据,并非产品指标。在真实且已标注的 Gutenberg 失败案例中,规则目前识别出 15 个符合自动修复条件的漂移案例,且没有一个判断错误,每个案例建议的定位器都与开发者亲自编写的完全一致。
从用户反馈中学习(LEARN)
Agent 的 PR 评论最后会用一行告诉用户如何纠正它。像 /triage TEST_DATA #1 the fixture user has no saved card 这样的回复会在下次运行时被读取,与运行历史一同保存,并应用于之后所有以相同方式失败的该测试。视图随后会将类别显示为由用户纠正,以纠正者的姓名和理由作为依据,同时保留 Agent 给出的判断,以便分歧清晰可见,而不会被悄悄覆盖。
这里有两项有意设置的限制:
- 纠正只确定类别,绝不更改代码。有人写下
LOCATOR_DRIFT,并不代表已经给 Agent 提供了经重新运行验证的替代定位器,因此仍需由 SDET 编写修复。 - 纠正仅适用于以相同方式失败的同一测试。该测试的其他失败属于不同问题,由 Agent 自行判断。
一次运行的成本
每次分流运行结束时,CLI、评估和 CI 作业摘要都会显示一行信息:模型调用次数、耗时、缓存命中次数、使用的令牌数以及美元成本。每次调用都按 OpenAI 公布的价格,使用对应模型的价格计算(缓存输入按缓存价格计费);团队可以通过 TRIAGE_LLM_PRICE_IN/OUT 设置自己的价格,而价格未知的模型会如实标记为未知,绝不猜测。运行成本较低,是因为确定性规则可以处理大多数失败,只有无法判定的情况才会交给模型。实测示例(JupyterLab,41 个真实失败):规则判定了其中 34 个,6 个交给模型处理,这 6 次调用总共耗时 30 秒,使用约 31,000 个令牌。
安全性
Agent 会读取由拉取请求作者控制的文本(测试名称、错误消息、跟踪信息),而且它可以改变合并所受的阻拦,因此它的设计考虑了不可信输入:
- 只有拥有写入权限的人才能提交纠正(所有者、成员或协作者)。在公开仓库中,陌生人发表的
/triage评论会被忽略,因此团队外的任何人都无法让真实失败绕过合并关卡。 - 只有 Agent 自己的评论才会定义
#1的含义。 其他人发布的编号映射都会被忽略,且映射中的字段会经过编码,确保测试名称无法突破其边界。 - 测试运行中的文本在评论中会被处理为惰性内容:
@mentions不会通知任何人,链接和图片会以纯文本显示,HTML 会经过转义。 - 任何有风险的事项都不会由 AI 最终拍板。 精心构造的错误消息可能试图诱导模型给出某种答案,但仅由 AI 作出的判定绝不会让失败绕过关卡,也绝不会授权代码更改;修复必须匹配确定性规则,并通过重新运行。
- 它绝不会合并,也绝不会编辑应用代码,并将收集的数据保存在 Actions 缓存中,而非仓库中。OpenAI 密钥是可选的,且只会从密钥中读取。
已知问题(基于真实数据发现)
- 漂移和真实 Bug 难以区分(正在改进)。 Rules-5 会读取 Playwright 的严格模式错误:当
name: 'Edit'这样的定位器开始匹配多个元素时,错误已经列出这些元素,而名称包含完整单词的元素就是测试实际要找的元素。在新数据集(9 月 1–3 日)中,漂移占 16/20 = 80%(标签标注缺陷修正前为 16/23 = 70%,参见 eval/README.md),被误判为漂移的真实 Bug 有 1/29,并且 15 个漂移案例现在可以自动修复,每条建议都与开发者自己的修复完全一致。在未见过的一周数据(8 月 20–26 日,473 个标签)中,漂移占 3/3,Agent 仍然不会进行 任何自动更改。其余漏判案例没有可用的页面数据:错误中根本没有提到任何定位器。 - Playwright 并不总会写入页面快照。 对于某些失败(框架内超时),
error-context.md只包含错误和测试源码,因此相似度规则无法发挥作用。根据跟踪 DOM 重建页面也无济于事:在真实数据中,真实 Bug 的相似度分数与漂移一样高(0.80–0.96),这给 AI 带来的是噪声而非信号(尝试后,留出集准确率从 86.1% 降至 81.3%)。 - 预期中的服务端错误(部分已修复)。 以前,故意触发服务端错误的测试会被判定为 ENV。现在,模型会在测试名称表明其用于测试错误处理时得到提示(提示词 llm-4)。
- 视图目前无法区分拉取请求分支和主分支。 记录中没有存储分支信息,因此,未合并的 PR 中的回归问题会在架构视图和摘要中被统计为产品 Bug。
- 一些重复出现的间歇性故障被误判。 在其他分支上也会失败的间歇性测试会被错误分类(留出集为 0/22),而 TEST_DATA 和 LOCATOR_DRIFT 尚未在真实数据上进行评估。
环境变量(可以放入 .env,该文件已被 git 忽略):
OPENAI_API_KEY:启用 LLM 层。未设置时,仅运行规则。TRIAGE_LLM=off:即使已设置密钥,也会关闭 LLM 层。TRIAGE_LLM_MODEL(默认值gpt-5.4-mini)和TRIAGE_LLM_REASONING(默认值low):选择模型及其推理力度。TRIAGE_LLM_CACHE=off:跳过data/llm-cache.jsonl中的响应缓存。启用缓存后,未更改的请求会从缓存中免费获取响应。TRIAGE_LLM_MAX_CALLS:每次运行最多调用模型的次数。超出此数量后,后续失败将保留规则给出的结论并说明原因,而不会在大量失败的运行中产生无上限费用。TRIAGE_LLM_PRICE_IN/TRIAGE_LLM_PRICE_OUT:自定义每百万输入/输出 token 的价格,覆盖内置的 OpenAI 模型公开价格。对于没有内置价格的模型,设置这些变量即可显示费用;否则运行结果会注明价格未知。CI_RUN_ID:用作运行 ID。未设置时,运行 ID 将根据时间戳生成。GIT_SHA或GITHUB_SHA:存储为记录的gitSha。两者均未设置时,gitSha将留空。
离开本机的数据: 启用 LLM 层后,每个 UNKNOWN 失败的摘要都会发送给 OpenAI。该摘要包括错误文本、页面快照、失败请求 URL(不含查询字符串)以及控制台错误。没有任何内容会提交到 git。
作者
Tewip 由 Mamuti Aishan (Hasan) 创建并维护 — GitHub @hmamut39。
这个名字源自维吾尔语:téwip(تېۋىپ),意为治疗者或医生。理念与医生的行医之道相同 — 找出原因、展示证据,并在无法判断时直言不讳。
同一作者还发布了:
- npm —
tewip-ai - VS Code Marketplace — Tewip
- Open VSX(Cursor、Windsurf、VSCodium)— hmamut39.tewip
- MCP registry —
io.github.hmamut39/tewip - Claude Code plugin — tewip-plugin
采用 Apache-2.0 许可证。机器可读的作者信息请参阅 CITATION.cff。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。