captain-obvious について
Captain Obviousは、回帰を検出できないテストをスクリプトで見つけ、テストスイートの整理を支援するスキルです。TypeScript、JavaScript、Pythonの開発者向けに、機械的に削除できる項目と人の判断が必要な項目を分けます。
主な機能
- 01TypeScript、JavaScript、Pythonのテストをスキャン
- 02確実な指摘と要確認の指摘を分類
- 03確実に不要なテストを安全に削除
- 04テスト追加・変更時の確認基準
- 05型チェックと全テストの実行を要求
活用シーン
- 01回帰を検出できないアサーションの削除
- 02AI生成テストの無効な確認のレビュー
- 03新規テストが保護する独立した振る舞いの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ shmulc8/ captain-obvious --skill captain-obviousインストール後、Claude Code で/captain-obviousを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/detectors.mdprevention.mdscripts/captain_obvious_py.pycaptain_obvious_ts.mjsscripts/co_py/__init__.pyanalyzer.pyast_utils.pycoverage.pydiscovery.pyduplicates.pyfixer.pygitguard.pymodels.pymypy_pass.pyscripts/co_ts/analyzer.mjsast_utils.mjsclassifier.mjscoverage.mjsdiscovery.mjsduplicates.mjsexpect_parser.mjsfixer.mjsgitguard.mjslaundering.mjsmock_echo.mjstype_predicates.mjs詳細
Captain Obvious
コンパイラー、モックフレームワーク、または論理の法則によってすでに保証されていることを検証するテストを削除します。こうしたテストは CI の時間を浪費し、カバレッジへの過信を招き、リグレッションを検出することもありません。AI 生成テストスイートの典型的な特徴です(実証研究では、LLM 生成テストの 38〜100% にテストのスメルが見つかっています)。
クリーンアップでは、scripts/ にある 2 つの決定論的スクリプトが大部分の作業を担います。スクリプトを実行し、レポートを確認して、残ったものを整理し、問題がないことを検証してください。テストファイルを手作業で一つずつ確認したり、ファイルごとにサブエージェントを起動したりしないでください。スクリプトを 1 回実行すれば、プロジェクト全体をスキャンできます。テスト作成時は、クリーンアップスキャンを実行せずに、以下のゲートを使ってください。
テスト作成時のゲート
テストを追加または変更する前に、次の 4 つの質問に答えてください。
- どの観測可能な動作または独立した契約を保護しますか?
- どのような現実的なリグレッションが、意図した理由でこのテストを失敗させますか?
- その契約は、より強い境界ですでにカバーされていますか? されている場合、このテストはどのような別のリスクをカバーしますか?
- テストでしか使わない本番コードのエクスポート、フラグ、ラッパー、または注入用フックが必要ですか? 代わりに実際のエントリーポイントをテストできますか?
新しいバグのリグレッションテストを修正前のベースラインに対して安全に実行できる場合は、意図した理由で失敗し、修正後に成功することを確認してください。避けるべきパターンの例については、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. まず安全性を確保する
修正ステップでは、テストファイルをその場で編集します。どちらのスクリプトも、これを独自に強制します。--fix は、対象が Git リポジトリで、作業ツリーがクリーンな場合を除き、終了コード 2 で終了します(未追跡ファイルは問題ありません)。拒否された場合は、--force に頼らず、スタッシュするかコミットしてください。--force を使うと、唯一の取り消し手段である git checkout -- <files> が使えなくなるため、ユーザーが明示的に承諾した場合に限り使用してください。
信頼境界:スキャンでは、プロジェクト独自のツールチェーンが実行されます。mypy はリポジトリの設定から [tool.mypy] plugins を読み込み、同一プロセス内の Python として実行します。--mypy "uv run mypy" / "poetry run mypy" は、リポジトリの依存関係を解決し、実行する場合があります。TypeScript 側では、リポジトリ独自の typescript パッケージを読み込みます。自分で mypy / tsc を実行してもよいリポジトリに限り、スキャンを実行してください。
注:プラグインとしてインストールした場合、書き込み時の PreToolUse フックも有効になっていることがあります。クリーンアップでテストファイルの Write/Edit が "captain-obvious:" の理由で拒否された場合は、同じ内容を再試行するのではなく、指摘されたアサーションを修正してください(references/prevention.md を参照)。どちらのスキャナーも、構文のみの単一ファイルスキャン用に --file <path> [--stdin] に対応しています(JSON を標準出力に出力し、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、アサートした結果を作り出すフィクスチャやモック、無関係なガードが入力を拒否するために通過するネガティブコントロールには、特に注意してください。これらはレビュー候補であり、新たに証明されたスキャナーカテゴリではありません。判断する前に、テスト全体、本番コードの経路、重複するテスト、関連する履歴を確認してください。ソースを調べたり実行に時間がかかったりしても、公開 API、プロトコル、セキュリティ境界、プラットフォームの動作、リリース成果物を独立して保証するテストは残してください。動作を保つリファクタリングでテストが壊れる場合は、調査する理由にはなりますが、削除する十分な根拠にはなりません。
手動で削除または書き換える前に、テストと場所、実際に検出できること、より強力な代替テスト(または不要である理由)、削除する接続点のテスト以外の呼び出し元、関連する履歴、対象を絞った検証コマンドを記録してください。証拠が不十分な場合は、調査が終わるまでテストを残してください。
4. 2 つのレベルを理解する
- proven — 構造上、失敗し得ません。スクリプトは既知の回避手段(
any/unknown、asキャスト、!、インデックスシグネチャ、未チェックのインデックスアクセス、構造的なinstanceof、カスタムアサーションヘルパー)をガードします。deletable: safeとマークされたものは自動削除されます。proven のreport-only検出結果(握りつぶされたアサートや失敗を見逃すアサート)は書き換えが必要で、--fixではそのまま残ります。 - advisory — ほぼ確実に役に立ちませんが、証明はできません(アサーションのないテスト、構造的な instanceof、モックのエコーを使ったパターン、インデックスシグネチャに依存するチェック、腐敗したグリーンの条件付きアサート、await されていない非同期アサート)。スクリプトはこれらを自動削除しませんが、それぞれの不確実性の理由と、
deletableのヒント(aggressive= 通常は削除、report-only= 通常は書き換えが必要)を正確に記録します。その理由は、周辺コードを照らし合わせてあなたが判断できます。そのため、助言項目はユーザーにそのまま提示するのではなく、あなたが(手順 6 で)判定します。
カテゴリの全一覧と各ガードの根拠については、references/detectors.md を参照してください。
5. proven 層を修正する(決定論的)
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 とマークされた proven 検出結果だけです。判断は不要で、LLM も使いません。これが安全で決定論的な中核なので、最初に実行してください。proven の report-only 検出結果は残り、手順 6 で書き換えます。
6. アドバイザリの判定を行う(判断したうえで確認する)
アドバイザリは、決定論では判断できないケースです。それを判断するのがあなたの役割であり、ユーザーへの報告項目ではありません。リストをそのまま転送してはいけません。アドバイザリの各指摘について、次の手順を実行してください。
- テストと、そのテストが対象とするコードを読みます。指摘の
reasonフィールドは、具体的な問いを示しています。たとえば、「構造的な instanceof — 形状だけが一致する非インスタンスが紛れ込む可能性がある」 なら、その型の非インスタンスを実際に生成するものがあるか確認します。「モックのエコー、間接的」 なら、スタブとアサーションの間で実際のコードパスが実行されるか確認します。アドバイザリを削除する前に、前述の4つの値に関する問いを適用し、契約を最も強く担保するテストの担当箇所を特定したうえで、このテストが別個の失敗モードを防いでいるか確認してください。バグの再発防止テストでは、安全なベースラインが利用できる場合、修正前の動作で、意図した理由によりテストが失敗することを確認します。期待どおりの結果を生成するだけのモックは、証明にはなりません。 - 次のいずれかを判断します。削除(疑念は当たらず、実際に役に立たない)、維持(疑念は妥当で、実際に有効なチェックである)、または書き換え(意図は妥当だが、アサーションに問題がある)。書き換えこそ、アドバイザリ層の真価です。未 await の
.rejectsを修正する(awaitを付ける)、pytest.raises(Exception)を具体的な型に絞る、機能していないconditional-assertを修正して実際に実行されるようにするといった対応です。no-assertの指摘はスモークテストであり、設計上正当なものです(ICSE'19)。テストが明らかにアサーションを行うつもりで忘れている場合を除き、原則として維持してください。 - 対応する前に提案します。 各項目について、指摘、判定、1行の理由、書き換えの場合は具体的な変更内容を記した簡潔な表を提示し、ユーザーの承認があったものだけを適用します。アドバイザリを自動で削除または書き換えてはいけません。
アドバイザリが大量にある場合は、各項目のコード確認をより低コストで高速なサブエージェントモデルに委任します(指摘をまとめて渡し、項目ごとに判定、理由、変更案を返させます)。最終的な提案と統合はここで行い、メインループでファイルを1つずつ読むのに時間を費やさないでください。実証済みの層をサブエージェントに渡してはいけません。そこはすでに判断済みです。
7. 残骸を整理する
スクリプトはテストブロック全体や個々のアサーション行を削除します。その結果、未使用のインポートや変数(noUnusedLocals が検出します)、空の describe() ブロック、空のテストクラス、孤立したフィクスチャやモックが残ることがあります。これらは手作業で修正してください。型チェッカーの出力を作業リストにします。
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のレッド・グリーン途中。 TDDではテストは失敗することが前提であり、書いたばかりのテストにはまだアサーションがない場合もあります。作成時のゲートは使用しますが、クリーンアップスキャナーを実行するのは、テストスイートがグリーンになってからにしてください。
- レビュー中のブランチ。 スキャン(
--json)は問題ありませんが、--fixは使用しないでください。レビュアーやマージゲートが差分を確認している最中にテストファイルを書き換えると、レビュー内容が無効になります。 - カバレッジやCIの実行時間を最適化するため。 このツールが削除するのは、失敗する可能性のないテストです。これは速度ではなく、正しさに関する判断です。「CIが遅い」ことは、このツールを使う理由にはなりません。実際にテストを行うテストスイートは、遅いままです。