Slopwatch: .NET용 LLM 안티 치트
이 스킬을 사용해야 하는 경우
이 스킬을 지속적으로 사용하세요. LLM(Claude 포함)이 다음 파일을 변경할 때마다:
- C# 소스 파일 (.cs)
- 프로젝트 파일 (.csproj)
- Props 파일 (Directory.Build.props, Directory.Packages.props)
- 테스트 파일
slopwatch를 실행하여 변경 사항에 '슬롭(slop)'이 도입되지 않았는지 검증하세요.
슬롭이란?
'슬롭'은 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 [오류]: 비활성화된 테스트 탐지
파일: tests/MyApp.Tests/OrderTests.cs:45
패턴: [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 dirty 파일만 분석 (큰 저장소에서도 빠름)
- 오류를 읽기 쉬운 형식으로 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 | 오류 | 비활성화된 테스트 (Skip=, Ignore, #if false) |
| SW002 | 경고 | 경고 억제 (#pragma warning disable, SuppressMessage) |
| SW003 | 오류 | 예외를 삼키는 빈 catch 블록 |
| SW004 | 경고 | 테스트 내 임의 지연 (Task.Delay, Thread.Sleep) |
| SW005 | 경고 | 프로젝트 파일 슬롭 (NoWarn, TreatWarningsAsErrors=false) |
| SW006 | 경고 | 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
재정의해야 하는 경우 (거의 없음)
베이스라인을 업데이트하거나 규칙을 비활성화할 수 있는 유일한 타당한 이유:
| 시나리오 | 조치 | 필요 사항 |
|---|---|---|
| 타사가 패턴을 강제함 | 베이스라인 업데이트 | 이유를 설명하는 코드 주석 |
| 생성 코드 (편집 불가) | 제외 목록에 추가 | 설정에 문서화 |
| 의도적인 레이트 제한 지연 | 베이스라인 업데이트 | 코드 주석, 테스트 아님 |
| 레거시 코드 정리 | 일회성 베이스라인 업데이트 | PR 설명 |
유효하지 않은 이유:
- "테스트가 불안정합니다" → 불안정성을 수정하세요
- "경고가 성가십니다" → 코드를 수정하세요
- "제 컴퓨터에서는 작동합니다" → 경쟁 조건을 수정하세요
- "나중에 수정하겠습니다" → 지금 수정하세요