К основному содержанию
скилл CX Codex

captain-obvious — скилл для Claude Code: чистит бесполезные тесты

Находит в тестах TypeScript и Python проверки, которые не могут упасть или ничего не проверяют, и помогает писать тесты с оглядкой на контракт.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add shmulc8/captain-obvious --skill captain-obvious -a claude-code
Открыть на GitHub

Что делает captain-obvious

Скилл удаляет тесты, которые проверяют то, что и так гарантировано компилятором, мок-фреймворком или логикой. Такие проверки жгут время CI и создают ложную уверенность в покрытии, а регрессию поймать не способны. Автор ссылается на исследования, где тестовые запахи встречаются в 38–100% тестов, сгенерированных LLM.

Для чистки в scripts/ лежат два детерминированных сканера: captain_obvious_ts.mjs для TypeScript и captain_obvious_py.py для Python. Один запуск скрипта сканирует весь проект — вручную файлы перебирать не нужно. Находки делятся на два уровня: proven (не могут упасть по построению, часть удаляется автоматически) и advisory (почти наверняка бесполезны, но недоказуемо, их скрипт не трогает). Если в проекте есть покрытие, флаг --coverage переводит самую шумную категорию условных assert'ов в проверяемую.

Правка идёт на месте, поэтому оба скрипта требуют git-репозиторий с чистым рабочим деревом: --fix завершится с кодом 2, если это не так. Флаг --force снимает эту защиту и заодно убирает единственный путь отката через git checkout -- <files>. Сканирование запускает тулчейн самого проекта: mypy подгружает плагины из конфига репозитория, а TS-детектор — локальный пакет typescript. Это повод запускать скилл только там, где вы готовы сами выполнить mypy или tsc.

Какие задачи решает

  • убрать тесты, которые не могут упасть
  • найти assert'ы без проверки условия
  • вычистить тесты, сгенерированные ИИ
  • отличить дублирующий тест от полезного
  • проверить новый тест на контракт до коммита

Примеры запросов агенту

  • прогони сканер тестов по проекту и покажи отчёт
  • почисти бесполезные тесты в этом репозитории
  • проверь мой новый тест по authoring gate

Как установить captain-obvious

1Через Skills CLI (Vercel)

npx skills add shmulc8/captain-obvious --skill captain-obvious -a claude-code

2Вручную: положить файлы

~/.claude/skills/captain-obvious/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/captain-obvious/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о captain-obvious

Как установить captain-obvious в Claude?

Выполните npx skills add shmulc8/captain-obvious --skill captain-obvious -a claude-code. Скилл окажется в ~/.claude/skills/captain-obvious/SKILL.md.

Что делает captain-obvious?

Сканирует тесты TypeScript и Python на проверки, которые не могут провалиться или ничего не проверяют, и удаляет доказанно бесполезные. Отдельно даёт набор вопросов для написания новых тестов.

Что нужно для запуска сканера?

Git-репозиторий с чистым рабочим деревом, иначе --fix откажется работать. Для категории проверок, гарантированных типами, нужен mypy в окружении проекта.

Исходный текст

SKILL.md · MIT
---
name: captain-obvious
description: Audit TypeScript and Python tests for assertions that cannot fail or check nothing, and guide test authoring with a contract and regression gate. Use when writing or changing tests to avoid low-value coverage, or cleaning redundant, tautological, duplicated, or AI-generated tests. Cleanup runs bundled deterministic scanners; authoring guidance needs no scan during red-green TDD.
---

# Captain Obvious

Deletes tests that assert what is already guaranteed — by the compiler, by the
mock framework, or by the laws of logic. These tests burn CI time, inflate
coverage confidence, and can never catch a regression. They are the signature
of AI-generated test suites (empirical studies find test smells in 38–100% of
LLM-generated tests).

For cleanup, two deterministic scripts in `scripts/` do the heavy lifting.
Run them, interpret the report, clean up the residue, and verify nothing broke.
**Do not** hand-scan test files or spawn subagents per file — one script
invocation scans the whole project. During test authoring, use the gate below
without running a cleanup scan.

## Authoring gate

Before adding or changing a test, answer four questions:

1. What observable behavior or independent contract does it protect?
2. What plausible regression would make it fail for the intended reason?
3. Is that contract already covered at a stronger boundary? If so, what
   distinct risk does this test cover?
4. Does it require a production export, flag, wrapper, or injection hook used
   only by tests? Can the real entry point be tested instead?

If a new bug regression test can be run safely against the pre-fix baseline,
verify that it fails for the intended reason and passes with the fix. Use
`references/prevention.md` for examples of patterns to avoid. A test need not
contain a direct assertion to be valuable: a deliberate must-not-raise test
can guard a real contract.

## Workflow

### 1. Detect the stack(s)

- TypeScript/JavaScript: `*.test.*` / `*.spec.*` (ts, tsx, mts, cts, js, jsx,
  mjs, cjs) or files under `__tests__`; a `tsconfig.json` enables type checks.
- Python: `test_*.py` / `*_test.py` files (pytest or unittest).
- A repo can have both; run both detectors.

### 2. Safety first

The fix step edits test files in place. Both scripts enforce this themselves:
`--fix` exits 2 unless the target is a git repository with a clean working
tree (untracked files are fine). If it refuses, stash or commit rather than
reaching for `--force` — `--force` removes the only undo path there is
(`git checkout -- <files>`), so use it only when the user has explicitly
accepted that.

Trust boundary: scanning executes the project's own toolchain. mypy loads
`[tool.mypy] plugins` from the repo's config as in-process Python;
`--mypy "uv run mypy"` / `"poetry run mypy"` resolve (and can run) the
repo's dependencies; the TS side loads the repo's own `typescript`
package. Run the scan only on repositories you would be willing to run
`mypy`/`tsc` in yourself.

Note: when installed as a plugin, a write-time PreToolUse hook may also be
active — if a test-file Write/Edit is denied with a "captain-obvious:"
reason during cleanup rewrites, fix the flagged assertions instead of
re-trying the same content (see `references/prevention.md`). Both scanners
also support `--file <path> [--stdin]` for a syntactic-only single-file
scan (JSON to stdout; no mypy/tsc, no side effects).

### 3. Scan (report-only)

```bash
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
```

- The Python detector shells out to mypy for the type-guaranteed category. Use
  the project's own environment: pass `--mypy "uv run mypy"` for uv projects,
  `--mypy "poetry run mypy"` for poetry, etc. If mypy isn't available it
  degrades gracefully to the syntactic categories.
- Note: the mypy pass briefly writes `_cap_obv_shadow_*` copies next to test
  files (removed when the run ends) — so a "report-only" scan does touch the
  working tree. Pass `--no-types` for a strictly read-only scan; if the tree
  is not writable the scan degrades to syntactic categories and says so.
- The TS detector resolves the project's own `typescript` package; without a
  tsconfig it degrades to syntactic categories.
- **If the project already produces coverage** (or you can cheaply run it),
  pass `--coverage <file>` (lcov / istanbul `coverage-final.json` / coverage.py
  `coverage json`). This is the dynamic half of the ICSE'19 rotten-green
  analysis: a `conditional-assert` whose line never ran is promoted to **proven
  rotten**, and one that did run is dropped as a confirmed false positive. It
  turns the noisiest advisory category into a trustworthy one — use it whenever
  coverage is available.

Show the user the summary table and the findings before deleting anything.

### 3a. Check test value beyond the scanner

The scripts find mechanical patterns; they cannot decide whether a test owns a
useful contract or merely repeats another test. For a focused manual audit,
apply the authoring gate to each review lead or advisory before changing it.

Look especially for expectations copied from the implementation, source-text
greps, fixtures or mocks that manufacture the asserted result, and negative
controls that pass because an unrelated guard rejects the input. These are
**review leads**, not new proven scanner categories. Read the complete test,
the production path, overlapping tests, and relevant history before deciding.
Keep tests that independently lock a public API, protocol, security boundary,
platform behavior, or release artifact, even if they inspect source or run
slowly. A behavior-preserving refactor breaking a test is a reason to examine
it, not sufficient evidence to delete it.

Before a manual deletion or rewrite, record the test and location, what it can
actually detect, the stronger remaining test (or why none is needed), non-test
callers of any seam to be removed, relevant history, and the focused validation
command. If the evidence is incomplete, keep the test pending investigation.

### 4. Understand the two levels

- **proven** — cannot fail, by construction. The scripts guard the known
  escape hatches (`any`/`unknown`, `as` casts, `!`, index signatures, unchecked
  index access, structural `instanceof`, custom assertion helpers). Those
  marked `deletable: safe` are auto-deleted; proven `report-only` findings
  (swallowed or missed-fail asserts) need a rewrite and `--fix` leaves them.
- **advisory** — almost certainly useless but *not* provable (assertion-free
  tests, structural instanceof, mock-echo variants, index-signature-backed
  checks, rotten-green conditional asserts, unawaited async assertions). The
  script never auto-deletes these, but it records *exactly why* each is
  uncertain, plus a `deletable` hint (`aggressive` = usually a deletion,
  `report-only` = usually needs a rewrite). That reason is a question **you**
  are equipped to answer against the surrounding code — so advisories are
  adjudicated by you (step 6), not dumped on the user.

See `references/detectors.md` for the full category catalog and the reasoning
behind each guard.

### 5. Fix the proven tier (deterministic)

```bash
node <skill-dir>/scripts/captain_obvious_ts.mjs --project <repo> --fix
python3 <skill-dir>/scripts/captain_obvious_py.py --path <repo> --fix
```

Plain `--fix` removes only **proven** findings marked `deletable: safe` — no
judgment required, no LLM. This is the safe deterministic core; run it first.
Proven `report-only` findings stay for you to rewrite in step 6.

### 6. Adjudicate the advisory tier (you decide, then confirm)

Advisories are the cases determinism *can't* settle — and that's your job, not
a report line for the user. Do **not** just forward the list. For each advisory
finding:

1. Read the test and the code it exercises. The finding's `reason` field is a
   pointed question — e.g. *"structural instanceof — a shaped non-instance
   could sneak in"* → check whether anything actually constructs a non-instance
   of that type; *"mock-echo, indirect"* → check whether a real code path runs
   between stub and assert.
   Apply the four value questions above before removing an advisory: identify
   the contract's strongest test owner and check whether this test protects a
   distinct failure mode. For a bug regression, confirm that the test fails on
   the pre-fix behavior for the intended reason when a safe baseline is
   available; a mock that merely produces the expected result is not proof.
2. Decide one of: **delete** (the doubt doesn't hold — it really is useless),
   **keep** (the doubt holds — it's a real check), or **rewrite** (the intent
   is valid but the assertion is broken). Rewrite is the advisory tier's real
   value: fix the unawaited `.rejects` (`await` it), narrow a
   `pytest.raises(Exception)` to the specific type, repair a rotten-green
   `conditional-assert` so it actually runs. Note `no-assert` findings are
   **smoke tests** — legitimate by design (ICSE'19); default to **keep** unless
   the test clearly *meant* to assert something and forgot.
3. **Propose before acting.** Present a compact per-item table — finding,
   verdict, one-line rationale, and the exact edit for rewrites — and apply
   only what the user approves. Never auto-delete or auto-rewrite an advisory.

For a large advisory set, delegate the per-item code reads to a **cheaper, faster
subagent model** (batch the findings; have it return verdict + rationale + proposed
edit per item) and keep the final proposal/synthesis here — don't burn the main
loop reading files one by one. The proven tier is never handed to a subagent;
it's already decided.

### 7. Clean the residue

The scripts delete whole test blocks or individual assertion lines. That can
leave behind: unused imports/variables (`noUnusedLocals` will flag them),
empty `describe()` blocks, empty test classes, orphaned fixtures/mocks. Fix
those by hand — the typechecker output is your worklist.

### 8. Verify

Run the project's typecheck AND full test suite (`tsc --noEmit` + the test
command from package.json / `pytest`). Everything must pass with the same
result as before (minus the deleted tests). If anything regresses,
`git checkout -- <files>` and report what happened instead of pushing through.

### 9. Report

Tell the user: proven tests/assertions removed (per-category counts, lines
saved), the advisory verdicts you applied (deleted / rewritten, with the fix),
and anything you chose to **keep** with the reason the doubt held — that last
group is the tool earning trust, not failing.

## What NOT to flag (the scripts already know, but so should you)

- `toBeDefined()` on `.find()` / `Map.get()` results — the type is `T | undefined`, the check is real.
- Enum/constant contract locks (`expect(ExitCode.OK).toBe(0)`) — they catch renumbering.
- Assertions on values read from files/APIs at test time — real regression tests.
- Tests asserting via custom helpers (`expectAllow(x)`, `self._check(...)`).
- "Must not raise" contract tests for fail-open code paths.

## When not to run the cleanup scanner

- **Mid red-green.** During TDD a test is *supposed* to be failing, and a
  freshly-written test may not have its assertion yet. Use the authoring gate,
  but run the cleanup scanner only once the suite is green.
- **On a branch under review.** Scan (`--json`) is fine; `--fix` is not.
  Rewriting test files while a reviewer or a merge gate is reading the diff
  invalidates what they reviewed.
- **As a coverage or CI-time optimizer.** It deletes tests that cannot fail,
  which is a correctness argument, not a speed one. "CI is slow" is not a
  reason to reach for it — a slow suite full of real tests stays slow.

© shmulc8 · MIT · shmulc8/captain-obvious

Похожие скиллы

Всё по теме «Тесты и QA» →
lint-and-validate sickn33 Скилл запускает настроенные линтеры и проверку типов, отличая сбои в коде от незапущенных утилит. Пригодится после правок кода перед сдачей задачи агентом. CX Codex +1 1 300установок pre-merge bobmatnyc Скилл-чеклист, который прогоняет типы, линтер и тесты до коммита, до PR и до мержа в main. Пригодится, когда не хочется ловить поломки уже в ревью. CX Codex +1 330установок agent-sdk-dev официальный Anthropic Плагин создаёт и проверяет приложения на Claude Agent SDK для Python и TypeScript. Пригодится для старта проекта и проверки перед деплоем. ★ 44 635звёзд репо dual-axis-skill-reviewer tradermonty Проверяет скиллы по коду и через LLM, объединяет оценки в итоговый балл и сохраняет отчёты. Пригодится для контроля качества и проверки перед слиянием. CX Codex +1 3 300установок reviewing-code spencerpauly Скилл проводит разбор кода по чек-листу: корректность, поддерживаемость, производительность, типы и тесты. Пригодится, когда нужно ревью перед мержем. CX Codex +1 516установок python-code-style Seth Hobson Скилл задаёт правила стиля Python, настройки линтеров, форматирования и документации. Пригодится для новых модулей, ревью и настройки проекта. CX Codex 16 456установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой