mcpField
C
captain-obvious

captain-obvious

github.com/shmulc8测试与质量151.5k 次安装4 周前更新
代码仓库

关于 captain-obvious

Captain Obvious 是用于审查和清理测试套件的技能,通过脚本识别无法捕获回归的测试,并指导人工判断不确定项。它适合维护 TypeScript、JavaScript 或 Python 项目的开发者,强调先保护工作区,再验证清理结果。

主要功能

  1. 01扫描 TypeScript、JavaScript 和 Python 测试
  2. 02识别确定无效与待审查测试
  3. 03安全删除确定无效的测试
  4. 04提供新增和修改测试的审查门槛
  5. 05要求运行类型检查与完整测试套件

适用场景

  1. 01清理测试套件中无法发现回归的断言
  2. 02审查 AI 生成测试中的无效检查
  3. 03新增测试前确认其保护的独立行为

安装

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

$npx skills add https://github.com/shmulc8/captain-obvious --skill captain-obvious

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
detectors.md
prevention.md
scripts/
captain_obvious_py.py
captain_obvious_ts.mjs
scripts/co_py/
__init__.py
analyzer.py
ast_utils.py
coverage.py
discovery.py
duplicates.py
fixer.py
gitguard.py
models.py
mypy_pass.py
scripts/co_ts/
analyzer.mjs
ast_utils.mjs
classifier.mjs
coverage.mjs
discovery.mjs
duplicates.mjs
expect_parser.mjs
fixer.mjs
gitguard.mjs
laundering.mjs
mock_echo.mjs
type_predicates.mjs

详细介绍

Captain Obvious

删除那些断言编译器、mock 框架或逻辑定律已经保证的内容的测试。这些测试浪费 CI 时间,夸大覆盖率带来的信心,而且永远无法捕获回归问题。它们是 AI 生成测试套件的典型特征(实证研究发现,38–100% 的 LLM 生成测试存在测试异味)。

要清理时,scripts/ 中的两个确定性脚本会完成繁重工作。运行脚本,解读报告,清理剩余问题,并验证没有破坏任何内容。不要手动逐个扫描测试文件,也不要为每个文件启动子 Agent——运行一次脚本就能扫描整个项目。编写测试时,请使用下面的检查关卡,不要运行清理扫描。

编写测试的检查关卡

添加或修改测试前,请回答以下四个问题:

  1. 它保护的是哪种可观察行为或独立契约?
  2. 哪种合理的回归会让它因预期原因而失败?
  3. 该契约是否已在更强的边界得到覆盖?如果是,此测试覆盖了什么不同的风险?
  4. 它是否需要仅供测试使用的生产导出项、标志、包装器或注入钩子?能否改为测试真实入口?

如果新的 Bug 回归测试可以在修复前的基线上安全运行,请验证它因预期原因而失败,并在修复后通过。请参阅 references/prevention.md 中应避免的模式示例。测试不一定要包含直接断言才有价值:有意编写的、确保不会抛出异常的测试可以保护真实契约。

工作流

1. 检测技术栈
  • TypeScript/JavaScript:*.test.* / *.spec.*(ts、tsx、mts、cts、js、jsx、mjs、cjs)或 __tests__ 目录下的文件;tsconfig.json 可启用类型检查。
  • Python:test_*.py / *_test.py 文件(pytest 或 unittest)。
  • 一个仓库可能同时包含两者;请运行两个检测器。
2. 安全第一

修复步骤会就地编辑测试文件。两个脚本都会自行执行此项检查:如果目标不是 Git 仓库,或工作树不干净,--fix 就会以状态码 2 退出(未跟踪文件不受影响)。如果脚本拒绝执行,请先暂存或提交,不要使用 --force——--force 会移除唯一的撤销途径(git checkout -- <files>),因此只有在用户明确接受后才能使用。

信任边界:扫描会执行项目自身的工具链。mypy 会以内嵌 Python 的方式加载仓库配置中的 [tool.mypy] plugins;--mypy "uv run mypy" / "poetry run mypy" 会解析(并可能运行)仓库的依赖项;TS 端会加载仓库自己的 typescript 包。只在你愿意亲自运行 mypy/tsc 的仓库中运行扫描。

注意:如果以插件形式安装,写入时的 PreToolUse 钩子也可能处于启用状态——如果在清理重写期间,测试文件的 Write/Edit 操作因 "captain-obvious:" 原因被拒绝,请修正被标记的断言,而不要用相同内容重试(参见 references/prevention.md)。两个扫描器也都支持 --file <path> [--stdin],用于对单个文件进行纯语法扫描(JSON 输出到 stdout;不运行 mypy/tsc,也没有副作用)。

3. 扫描(仅报告)
node <skill-dir>/scripts/captain_obvious_ts.mjs --project <repo> --json /tmp/co-ts.json
python3 <skill-dir>/scripts/captain_obvious_py.py --path <repo> --json /tmp/co-py.json
  • Python 检测器会调用 mypy 来检查类型保证类别。请使用项目自身的环境:对于 uv 项目,传入 --mypy "uv run mypy";对于 poetry 项目,传入 --mypy "poetry run mypy",依此类推。如果无法使用 mypy,检测器会平稳退化为语法类别。
  • 注意:mypy 扫描会短暂地在测试文件旁写入 _cap_obv_shadow_* 副本(运行结束时会删除),因此“仅报告”扫描确实会触及工作树。传入 --no-types 可进行严格的只读扫描;如果工作树不可写,扫描会退化为语法类别并说明原因。
  • TS 检测器会解析项目自身的 typescript 包;如果没有 tsconfig,它会退化为语法类别。
  • 如果项目已经生成覆盖率数据(或你可以轻松运行覆盖率工具),请传入 --coverage <file>(lcov / istanbul coverage-final.json / coverage.py coverage json)。这是 ICSE'19 rotten-green 分析的动态部分:如果 conditional-assert 所在行从未运行过,就会被提升为已证实的无效测试;如果运行过,则会被剔除,确认是假阳性。它能将噪声最大的提示类别变成可信类别——只要有覆盖率数据,就应使用它。

在删除任何内容之前,先向用户展示汇总表和发现结果。

3a. 检查测试除扫描器之外的价值

这些脚本能找到机械模式,但无法判断测试是否维护了有用的契约,还是仅仅重复了另一个测试。要进行有针对性的人工审查,请在修改之前,对每个审查线索或提示应用编写门槛。

尤其要留意从实现中复制的预期、对源代码文本的 grep、制造出断言结果的固定数据或 mock,以及因无关保护逻辑拒绝输入而通过的负向对照。这些是审查线索,并非新的已证实扫描器类别。在决定之前,请阅读完整测试、生产代码路径、重叠测试和相关历史。即使测试会检查源代码或运行缓慢,只要它能独立锁定公共 API、协议、安全边界、平台行为或发布产物,就应保留。行为保持不变的重构若导致测试失败,值得进一步检查,但这本身不足以证明应该删除测试。

人工删除或重写之前,请记录测试及其位置、它实际能检测到的内容、更强的剩余测试(或说明为何不需要)、将被移除接口的非测试调用方、相关历史,以及有针对性的验证命令。如果证据不完整,请保留该测试,待进一步调查。

4. 了解两个层级
  • 已证实 — 按构造不可能失败。脚本会防范已知的逃逸方式(any/unknown、as 强制转换、!、索引签名、未经检查的索引访问、结构化 instanceof、自定义断言辅助函数)。标记为 deletable: safe 的项目会自动删除;已证实但标记为 report-only 的发现(被吞掉或漏掉失败的断言)需要重写,而 --fix 会保留它们。
  • 提示 — 几乎肯定没有用,但无法证实(没有断言的测试、结构化 instanceof、mock 回显变体、基于索引签名的检查、rotten-green 条件断言、未等待的异步断言)。脚本绝不会自动删除这些项目,但会准确记录每项不确定的原因,并附上 deletable 提示(aggressive = 通常应删除,report-only = 通常需要重写)。这个原因是你可以结合周边代码判断的问题,因此应由你裁定提示(第 6 步),而不是把它们一股脑交给用户。

完整类别目录及各项保护措施背后的推理,请参阅 references/detectors.md。

5. 修复已证实层级(确定性处理)
node <skill-dir>/scripts/captain_obvious_ts.mjs --project <repo> --fix
python3 <skill-dir>/scripts/captain_obvious_py.py --path <repo> --fix

普通的 --fix 只会移除标记为 deletable: safe 的已证实发现——无需判断,也不需要大模型。这是安全、确定性的核心步骤;请先运行。标记为 report-only 的已证实发现会保留,供你在第 6 步重写。

6. 判定建议项的等级(由你决定,然后确认)

确定性 无法 解决建议项中的疑点——这份工作由你负责,而不是写在报告里给用户看。不要只是转发列表。针对每项建议发现:

  1. 阅读测试及其所测试的代码。发现项的 reason 字段会提出一个明确的问题,例如 “结构性 instanceof——形状相符的非实例可能混进来” → 检查是否有代码实际构造了该类型的非实例;“mock-echo,间接” → 检查桩与断言之间是否运行了真正的代码路径。 移除建议项前,先应用上面的四个价值问题,找出契约的最强测试所有者,并检查此测试是否保护了不同的故障模式。对于 Bug 回归测试,如果有安全的基线可用,请确认该测试在修复前的行为下会因预期原因失败;仅仅由 mock 产生预期结果并不能作为证据。
  2. 从以下选项中决定:删除(疑点不成立——测试确实无用)、保留(疑点成立——这是有效检查),或重写(意图有效,但断言有问题)。重写是建议项等级的真正价值:修复未等待的 .rejects(加上 await)、将 pytest.raises(Exception) 收窄为具体类型、修复失效的 conditional-assert,让它真正执行。注意,no-assert 发现项是冒烟测试——这是有意为之的合理做法(ICSE'19);默认应保留,除非测试显然本来想断言某项内容,却忘了写。
  3. 先提出方案,再执行。 按项提供一份精简表格,列出发现项、判定、一句话理由,以及重写时的确切修改;并且只应用用户批准的修改。绝不自动删除或自动重写建议项。

对于数量较多的建议项,请委派给更便宜、更快的子 Agent 模型逐项阅读代码(分批处理发现项;要求其为每项返回判定、理由和建议修改),并在此处完成最终方案和综合判断——不要在主流程中逐个文件阅读。已证实等级的内容绝不交给子 Agent;这些已经有了结论。

7. 清理残留

脚本会删除整个测试块或单条断言。这可能会留下未使用的导入/变量(noUnusedLocals 会将其标出)、空的 describe() 块、空的测试类、孤立的 fixture/mock。请手动修复这些问题——类型检查器的输出就是你的待办清单。

8. 验证

运行项目的类型检查和完整测试套件(tsc --noEmit + package.json 中的测试命令 / pytest)。所有检查都必须通过,且结果与之前相同(删除的测试除外)。如果有任何回归,执行 git checkout -- <files>,并报告发生了什么,而不是硬着头皮继续。

9. 报告

告诉用户:已移除的已证实测试/断言(按类别统计数量、节省的行数)、已应用的建议项判定(已删除/已重写及相应修复),以及你选择保留的内容和疑点成立的原因——最后这一类体现的是工具赢得了信任,而不是工具失效。

不要标记的内容(脚本已经知道,但你也应该知道)

  • 对 .find() / Map.get() 的结果调用 toBeDefined()——其类型为 T | undefined,这项检查确实有效。
  • 枚举/常量契约锁定(expect(ExitCode.OK).toBe(0))——它们能捕获重新编号的问题。
  • 对测试时从文件/API 读取的值进行断言——这是真正的回归测试。
  • 使用自定义辅助函数进行断言的测试(expectAllow(x)、self._check(...))。
  • 针对故障开放代码路径的“不得抛出”契约测试。

不应运行清理扫描器的情况

  • 红绿循环进行期间。 在 TDD 中,测试本就应该失败,而刚编写的测试可能还没有断言。使用编写门禁,但只有在测试套件全部通过后才运行清理扫描器。
  • 正在审核的分支上。 扫描(--json)没问题;不要使用 --fix。在审核者或合并门禁查看差异时重写测试文件,会使他们审核的内容失效。
  • 将其用作覆盖率或 CI 耗时优化工具。 它会删除不会失败的测试,这关乎正确性,而非速度。“CI 太慢”不是使用它的理由——一个包含大量真实测试的慢速测试套件依然会很慢。

评论

支持 Markdown