# task-930.1: output-review.py — Champion/Challenger 핵심 엔진

**팀**: dev1-team (헤르메스 팀장)
**담당**: 불칸(백엔드), 아르고스(테스터)
**일시**: 2026-03-25

---

## SCQA

**S**: 스킬 품질 평가 + 자기학습 시스템(v1.2 설계서)이 확정되어, 84개 스킬의 Champion/Challenger 비교 엔진 구현이 필요한 상태다.

**C**: 현재 스킬 아웃풋의 품질 평가 파이프라인이 부재하여, 스킬 실행 결과의 체계적 비교/학습/축적이 불가능하다.

**Q**: 초회(A/B 비교) 및 일상(챌린저 도전) 모드를 지원하는 Champion/Challenger 엔진을 구현하고, 성과 기반 상태 갱신(stable/unstable/manual_intervention)까지 작동하는가?

**A**: `scripts/output-review.py` + `output_review_helpers.py`로 전체 파이프라인 구현 완료. pytest 51/51 PASS, pyright 에러 0건. 초회/일상 모드, 성과 기반 갱신, graceful degradation, 아카이브 모두 동작 확인.

---

## 생성/수정 파일 목록

- `scripts/output-review.py` (신규, 181줄) — 메인 CLI 스크립트
- `scripts/output_review_helpers.py` (신규, 178줄) — 헬퍼 모듈
- `scripts/tests/test_output_review.py` (신규, 862줄) — 테스트 51개

---

## 구현 상세

### output-review.py (메인 스크립트, 181줄)

**--init 모드**: A/B 두 아웃풋 비교 → 승자를 챔피언으로 저장 → learnings.jsonl에 기록
**--compare 모드**: 챌린저 vs 기존 챔피언 비교 (순서 랜덤화) → 챌린저 승리 시 아카이브+교체 / 챔피언 승리 시 방어 기록+상태 업데이트

### output_review_helpers.py (헬퍼 모듈, 178줄)

10개 함수:
- `get_workspace_root()` — WORKSPACE_ROOT 환경변수 우선
- `load_eval_axes()` / `load_champion()` / `save_champion()` — 파일 I/O
- `archive_champion()` — champions-archive/{skill-name}/{timestamp}.json 이동
- `append_learning()` — learnings.jsonl append-only
- `compare_outputs()` — 비교 스텁 (실제 AI 호출은 Phase 2)
- `update_champion_status()` — reinit>=2 → manual, losses>=3 → unstable, defenses>=5 → stable
- `build_champion_data()` / `record_defense()` / `record_loss()` — 데이터 조작

### 제약사항 준수

- 절대경로 금지: WORKSPACE_ROOT 환경변수 또는 상대경로 사용
- learnings.jsonl: append만 (2팀 스키마 소관)
- eval-axes.json: 읽기만 (2팀 생성 소관)
- champions/champions-archive 경로: 설계서 준수

---

## 테스트 결과

- **pytest**: 51/51 PASSED (0.20s)
- **pyright**: 0 errors, 0 warnings, 0 informations
- **black + isort**: 포매팅 적용 완료

### 테스트 커버리지 (15개 클래스)

1. TestBuildChampionData — 스키마 필수 필드, 타입, 기본값, 커스텀 값 (6건)
2. TestSaveAndLoadChampion — 라운드트립, 경로 반환, JSON 유효성 (3건)
3. TestLoadChampionNotExists — None 반환, 예외 없음 (2건)
4. TestArchiveChampion — 이동, 타임스탬프, 원본 삭제 (3건)
5. TestArchiveChampionNoExisting — None 반환, 예외 없음 (2건)
6. TestAppendLearning — 줄 수 증가, JSON 유효성, 다중 append, 덮어쓰기 방지 (4건)
7. TestLoadEvalAxes — 정상 로드, 스킬별 분리 (2건)
8. TestLoadEvalAxesMissingSkill — 빈 리스트, 파일 부재 (2건)
9. TestRecordDefense — +1, 리셋, 초기값 (3건)
10. TestRecordLoss — +1, 리셋, 초기값 (3건)
11. TestStatusStable — 5연속 방어=stable, 10연속=stable, 4연속≠stable (3건)
12. TestStatusUnstable — 3연속 교체=unstable, 5연속=unstable, 2연속≠unstable (3건)
13. TestStatusManualIntervention — reinit>=2=manual, reinit>=3=manual, reinit=1≠manual (3건)
14. TestGracefulDegradation — 빈 axes, 빈 문자열 (3건)
15. TestCompareOutputsStructure — winner/reason/scores 키, 타입 (7건)
16. TestGetWorkspaceRoot — 환경변수, Path 타입 (2건)

---

## 발견 이슈 및 해결

### 자체 해결 (3건)

1. **output-review.py 181줄 (목표 ~100줄)** — argparse 설정 30줄 + 2개 모드 각각 40줄으로 합리적 범위. 핵심 로직은 헬퍼로 분리 완료.
2. **compare_outputs 스텁 구현** — 실제 AI 비교 로직은 Phase 2에서 연동. 현재 길이 기반 스텁으로 인터페이스 계약 확립. 설계서 Section 5 "Phase 2에서 연동, 지금은 스킵 가능" 준수.
3. **black 포매팅 미적용** — QC style_check WARN 발생. black + isort 적용으로 해결.

### 범위 외 미해결 (1건)

1. **eval-axes.json 미존재** — 2팀(dev2) 소관 파일. 현재 graceful degradation으로 빈 리스트 반환 처리. 2팀 task에서 생성 예정.

---

## QC 자동 검증 결과

```json
{
  "task_id": "task-930.1",
  "overall": "PASS",
  "checks": {
    "pyright_check": "PASS (0 errors, 0 warnings)",
    "style_check": "PASS (black + isort 적용 완료)",
    "data_integrity": "PASS",
    "spec_compliance": "PASS",
    "file_check": "PASS"
  }
}
```
