Slopwatch: LLM アンチチート for .NET
いつこのスキルを使うか
常にこのスキルを使用してください。 LLM(Claude を含む)が以下のファイルを変更するたびに実行します。
- C# ソースファイル (.cs)
- プロジェクトファイル (.csproj)
- Props ファイル (Directory.Build.props, Directory.Packages.props)
- テストファイル
slopwatch を実行して、変更によって「スロップ」が持ち込まれていないか検証します。
スロップとは?
「スロップ」とは、LLM が実際の問題を解決せずにテストを通したりビルドを成功させるために取るショートカットのことです。これらは報酬ハッキング行為であり、LLM は真の修正ではなく見かけ上の成功を最適化します。
一般的なスロップのパターン
| パターン | 例 | なぜ悪いか |
|---|---|---|
| テストの無効化 | [Fact(Skip="flaky")] | 失敗を隠して修正しない |
| 警告の抑制 | #pragma warning disable CS8618 | コンパイラ警告を黙らせ、問題を修正しない |
| 空の catch ブロック | catch (Exception) { } | エラーを飲み込み、バグを隠す |
| 任意の遅延 | await Task.Delay(1000); | 競合状態を隠し、テストを遅くする |
| プロジェクト全体の抑制 | <NoWarn>CS1591</NoWarn> | プロジェクト全体で警告を無効化 |
| CPM バイパス | インラインで Version="1.0.0" | 集中パッケージ管理を弱体化 |
これらのパターンを決して受け入れないでください。 LLM がスロップを導入した場合、その変更を拒否し、適切な修正を要求してください。
インストール
ローカルツールとして(推奨)
.config/dotnet-tools.json に追加:
{
"version": 1,
"isRoot": true,
"tools": {
"slopwatch.cmd": {
"version": "0.2.0",
"commands": ["slopwatch"],
"rollForward": false
}
}
}
次に復元:
dotnet tool restore
グローバルツールとして
dotnet tool install --global Slopwatch.Cmd
初回セットアップ: ベースラインの確立
既存のプロジェクトで slopwatch を使用する前に、現在の問題のベースラインを作成します:
# 既存のコードからベースラインを初期化
slopwatch init
# これにより .slopwatch/baseline.json が作成されます
git add .slopwatch/baseline.json
git commit -m "Add slopwatch baseline"
なぜベースラインが必要か? レガシーコードには既存の問題が含まれている可能性があります。ベースラインにより、slopwatch は新しく導入されたスロップのみを検出し、既存の技術的負債を無視できます。
LLM セッション中の使用方法
コード変更のたびに
LLM によるコード変更のたびに slopwatch を実行:
# 新しい問題を分析(ベースラインを使用)
slopwatch analyze
# 厳格モード - 警告もエラーとして扱う
slopwatch analyze --fail-on warning
Slopwatch が問題を検出した場合
無視しないでください。 代わりに:
- なぜ LLM がそのショートカットを取ったのかを理解する
- 適切な修正を要求する - 何が問題かを具体的に伝える
- 修正が別のスロップを導入しないか検証する
# 例: LLM がテストを無効化した場合
❌ SW001 [Error]: Detected disabled test
File: tests/MyApp.Tests/OrderTests.cs:45
Pattern: [Fact(Skip="Test is flaky")]
# 正しい対応: 実際の修正を依頼する
"このテストは無効化されただけで修正されていません。テストがフレイキーな理由を調査し、
根本的なタイミング/競合状態の問題を修正してください。"
ベースラインの更新(稀)
スロップが真に正当化され、文書化されている場合にのみベースラインを更新:
# 現在の検出項目をベースラインに追加(慎重に使用!)
slopwatch analyze --update-baseline
正当化の例:
- サードパーティライブラリがパターンを強制する場合(例: 特定の警告を抑制する必要がある)
- レート制限のための意図的な遅延(テストのフレーキネスではない)
- 変更できない生成コード
ベースラインを更新する際は、コードコメントに理由を文書化してください。
Claude Code フック統合
slopwatch をフックとして追加し、すべての編集を自動検証します。.claude/settings.json を作成または更新:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "slopwatch analyze -d . --hook",
"timeout": 60000
}
]
}
]
}
}
--hook フラグ:
- git のダーティファイルのみを分析(大規模リポジトリでも高速)
- エラーを stderr に読みやすい形式で出力
- 警告/エラーが発生した場合、編集をブロック(終了コード 2)
- Claude はエラーを確認し、即座に修正できます
CI/CD 統合
slopwatch を品質ゲートとして CI パイプラインに追加:
GitHub Actions
jobs:
slopwatch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Install Slopwatch
run: dotnet tool install --global Slopwatch.Cmd
- name: Run Slopwatch
run: slopwatch analyze -d . --fail-on warning
Azure Pipelines
- task: DotNetCoreCLI@2
displayName: 'Install Slopwatch'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'install --global Slopwatch.Cmd'
- script: slopwatch analyze -d . --fail-on warning
displayName: 'Slopwatch Analysis'
検出ルール
| ルール | 重大度 | 検出内容 |
|---|---|---|
| SW001 | Error | 無効化されたテスト (Skip=, Ignore, #if false) |
| SW002 | Warning | 警告の抑制 (#pragma warning disable, SuppressMessage) |
| SW003 | Error | 例外を握りつぶす空の catch ブロック |
| SW004 | Warning | テスト内の任意の遅延 (Task.Delay, Thread.Sleep) |
| SW005 | Warning | プロジェクトファイルのスロップ (NoWarn, TreatWarningsAsErrors=false) |
| SW006 | Warning | CPM バイパス (VersionOverride, インライン Version 属性) |
設定
.slopwatch/slopwatch.json を作成してカスタマイズ:
{
"minSeverity": "warning",
"rules": {
"SW001": { "enabled": true, "severity": "error" },
"SW002": { "enabled": true, "severity": "warning" },
"SW003": { "enabled": true, "severity": "error" },
"SW004": { "enabled": true, "severity": "warning" },
"SW005": { "enabled": true, "severity": "warning" },
"SW006": { "enabled": true, "severity": "warning" }
},
"exclude": [
"**/Generated/**",
"**/obj/**",
"**/bin/**"
]
}
厳格モード(LLM セッション推奨)
LLM コーディングセッション中に最大限の保護を提供するため、すべてのルールをエラーに引き上げ:
{
"minSeverity": "warning",
"rules": {
"SW001": { "enabled": true, "severity": "error" },
"SW002": { "enabled": true, "severity": "error" },
"SW003": { "enabled": true, "severity": "error" },
"SW004": { "enabled": true, "severity": "error" },
"SW005": { "enabled": true, "severity": "error" },
"SW006": { "enabled": true, "severity": "error" }
}
}
哲学: 新しいスロップに対するゼロトレランス
- ベースラインでレガシーをキャプチャ - 既存の問題は認識するが隔離する
- 新しいスロップはブロック - 新たなショートカットはビルド/編集を失敗させる
- 例外には正当な理由が必要 - ベースラインを更新する場合は理由を文書化する
- LLM は特別ではない - 人間と AI 生成コードに同じルールを適用する
目標は、LLM が「実際の問題を修正する」ではなく「テストを通す」ことを最適化するために生じる技術的負債の累積を防ぐことです。
クイックリファレンス
# 初回セットアップ
slopwatch init
git add .slopwatch/baseline.json
# LLM コード変更のたびに
slopwatch analyze
# 厳格モード(推奨)
slopwatch analyze --fail-on warning
# 統計情報付き(パフォーマンスデバッグ)
slopwatch analyze --stats
# ベースライン更新(稀、理由を文書化)
slopwatch analyze --update-baseline
# ツール連携用 JSON 出力
slopwatch analyze --output json
オーバーライドする場合(ほぼありません)
ベースラインを更新したりルールを無効化できる唯一の正当な理由:
| シナリオ | 対応 | 必須条件 |
|---|---|---|
| サードパーティがパターンを強制 | ベースラインを更新 | 理由を説明するコードコメント |
| 生成コード(編集不可) | 除外リストに追加 | 設定に文書化 |
| 意図的なレート制限遅延 | ベースラインを更新 | テスト外のコードコメント |
| レガシーコードのクリーンアップ | 1回限りのベースライン更新 | PR 説明 |
無効な理由:
- 「テストがフレイキー」 → フレーキネスを修正する
- 「警告がうっとうしい」 → コードを修正する
- 「私のマシンでは動く」 → 競合状態を修正する
- 「後で直す」 → 今直す