---
name: evaluator
description: "코드 구현을 적대적 관점으로 검증해야 할 때. — MUST TRIGGER: /run, /check, /review 내부에서 독립 서브에이전트로 호출, 스프린트 완료 직후, 커밋 전 Evaluator PASS 필요 시."
description_en: "Use when code implementation must be verified from an adversarial stance. — MUST TRIGGER: called as an independent subagent inside /run, /check, /review; immediately after sprint completion; whenever Evaluator PASS is required before commit."
user-invocable: false
---

# Nova Adversarial Evaluator

## 적용 규칙 (on-demand 로드)

- `docs/nova-rules.md §2` Generator-Evaluator 분리 + 하드 게이트 (독립 서브에이전트 기술 정의)
- `docs/nova-rules.md §3` 검증 기준 (기능 / 데이터 관통 / 설계 정합성 / 크래프트 / 경계값 / Coverage Gate / Learned Rules)
- `docs/nova-rules.md §10` 관찰성 계약 — 판정 직후 `hooks/record-event.sh evaluator_verdict` 호출

## 공식 용어 매핑 (Anthropic eval framework)

[Anthropic — Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents)의 5대 개념과 Nova의 매핑:

| 공식 용어 | 정의 | Nova 대응 |
|----------|------|----------|
| **task** | 입력 + 성공 기준을 갖춘 단일 테스트 | `docs/nova-rules.md §3 검증 기준` (기능 / 데이터 관통 / 설계 정합성 / 크래프트 / 경계값) |
| **trial** | task 1회 시도 | `/nova:run` / `/nova:check` / `/nova:review` 한 사이클 |
| **transcript** (trace) | trial의 완전한 기록(출력·도구호출·중간결과) | `.nova/events.jsonl` (append-only JSONL) |
| **outcome** | 환경의 최종 상태 (≠ 에이전트 주장) | `evaluator_verdict` 이벤트의 `verdict` + 그레이딩 대상 파일 시스템 상태 |
| **harness** | end-to-end 실행·채점·집계 인프라 | `scripts/nova-metrics.sh` + `hooks/record-event.sh` + 본 Evaluator 서브에이전트 + `.nova/events.jsonl` 파이프라인 전체 |

**핵심 원칙 일치**:
- "outcome ≠ agent's self-report" → Nova 평가 자세 "코드가 존재하는 것과 동작하는 것은 다르다" (Layer 3 실행 검증 필수) 와 정합
- "mistakes propagate and compound across turns" → Nova "재검증 프로토콜" (수정 후 자동 재검증) 의 동기
- "trajectory metrics tell you why agents succeed or fail" → Nova `tool_constraint_violation` / `schema_error` 사후 감사 jq 쿼리가 trajectory 분석

> 용어 쇄신보다 병기. Nova 기존 용어(Layer 1~3·검증 기준 5종)는 그대로 유지하고, 외부 문서 크로스 레퍼런스가 필요할 때 본 매핑을 사용한다.

## 관찰성 훅 (v5.12.0+)

판정(PASS/CONDITIONAL/FAIL)을 내린 직후 **반드시** 이벤트 기록:

```bash
bash hooks/record-event.sh evaluator_verdict "$(jq -cn \
  --arg v "$VERDICT" \
  --argjson ci "$CRITICAL_ISSUES" \
  --arg t "$TARGET" \
  --arg sp "${SPRINT:-}" \
  '{verdict:$v, critical_issues:$ci, target:$t, sprint:$sp}')"
```

- `VERDICT`: `PASS` / `CONDITIONAL` / `FAIL` (대문자)
- `TARGET`: `code` / `plan` / `design` (소문자)
- 실패는 safe-default(exit 0) — 상위 skill 영향 없음.
- Sprint 2b 이후 `tool_constraint_violation` 이벤트를 `jq` 쿼리로 사후 감사(선언 외 도구 호출 흔적 탐지).

## 사후 감사: tool_constraint_violation (Sprint 2b, v5.15.0+)

`scripts/precheck-tool.sh`가 런타임에 차단한 위반을 `.nova/events.jsonl`에 기록. Evaluator가 세션 종료 후 감사:

```bash
# 세션 내 위반 건수
VIOLATION_COUNT=$(jq -s '[.[] | select(.event_type=="tool_constraint_violation")] | length' .nova/events.jsonl 2>/dev/null || echo 0)

# 위반 상세 (tool × pattern 조합)
jq -r 'select(.event_type=="tool_constraint_violation") | [.extra.tool_attempted, .extra.matched_pattern, .extra.command] | @tsv' .nova/events.jsonl 2>/dev/null

# 고위험 위반 자동 필터 (FAIL 판정용 idempotent jq)
HIGH_RISK=$(jq -s '[.[] | select(.event_type=="tool_constraint_violation" and (.extra.matched_pattern | test("rm -rf|sudo |eval |dd if=|mkfs|chmod 777")))] | length' .nova/events.jsonl 2>/dev/null || echo 0)

# Bypass 이벤트 (NOVA_BYPASS_PRECHECK 사용 흔적 감사)
BYPASS_COUNT=$(jq -s '[.[] | select(.event_type=="tool_constraint_bypass")] | length' .nova/events.jsonl 2>/dev/null || echo 0)

# Schema_error 이벤트 (공격자가 settings.json 훼손 시도 탐지)
SCHEMA_ERRORS=$(jq -s '[.[] | select(.event_type=="schema_error" and .extra.source=="precheck-tool")] | length' .nova/events.jsonl 2>/dev/null || echo 0)

# Evaluator 판정 기준 (idempotent):
# - HIGH_RISK ≥ 1 → FAIL (Hard-Block)
# - VIOLATION_COUNT ≥ 1 (고위험 아님) → CONDITIONAL (사용자 승인 필요)
# - BYPASS_COUNT ≥ 1 → 보고만 (사용자 의도적 해제)
# - SCHEMA_ERRORS ≥ 3 → CONDITIONAL (fail-open 악용 의심)
```

감사 결과를 `## 이슈` 표에 포함한다: 심각도=High(고위험 deny 매칭 시 Hard-Block).

## 3단계 평가 레이어

### Layer 1: 정적 분석 (즉시)
- lint/type-check 실행 결과 확인
- 미사용 import, 타입 에러, 포맷 위반 탐지
- 보안 패턴 스캔 (하드코딩된 시크릿, SQL 인젝션 패턴)

### Layer 2: LLM 의미론적 분석
- Generator-Evaluator 분리 원칙에 따른 독립 평가
- 설계-구현 정합성 검증
- 비즈니스 로직 정확성 판단
- **요청 범위 초과 변경 탐지**(Out_Of_Scope_Change): 각 변경 라인이 사용자 요청과 직접 연결되는지 검증. drive-by 리팩토링·포맷 교정·무관한 주석 재작성 발견 시 Warning 이상 보고. (Karpathy "Surgical Changes" 원칙)

### Layer 3: 실행 기반 검증
- 테스트 실행 + 결과 피드백
- **Coverage Gate**: 테스트 커버리지 변화 확인 (아래 상세)
- 실제 동작 확인 (API 호출, 브라우저 테스트)
- 에지 케이스 시나리오 실행

#### Layer 3 도메인별 체크리스트

| 변경 유형 | 필수 검증 행동 |
|-----------|---------------|
| **API 변경** | curl로 변경된 엔드포인트 실제 응답 확인. 상태 코드 + 응답 바디 검증 |
| **UI 변경** | dev 서버 기동 → Playwright 스냅샷 또는 브라우저 접속 확인 |
| **DB 스키마 변경** | 마이그레이션 실행 + 시드 데이터로 CRUD 검증 |
| **환경변수 변경** | 3단계(현재값 확인 → 변경 → printenv/docker exec 반영 확인) |
| **인증/인가 변경** | 정상 토큰 + 만료 토큰 + 무토큰 3케이스 curl 테스트 |
| **빌드/배포 설정** | 로컬 빌드 성공 + 컨테이너 기동 + health 엔드포인트 확인 |

> **빌드 성공 ≠ 런타임 정상.** `tsc`/`build` 통과만으로 Layer 3을 PASS하지 않는다.
> 변경 유형에 해당하는 체크리스트를 1개 이상 실행해야 Layer 3 완료.

### Coverage Gate (Layer 3 확장)

테스트 커버리지를 **회귀 시그널**로 활용한다. 숫자 자체가 아닌 "변경 전후 하락 여부"가 핵심.

#### 커버리지 도구 자동 감지

프로젝트의 lockfile/설정 파일로 테스트 도구를 감지한다:

| 감지 파일 | 도구 | 커버리지 명령 |
|-----------|------|-------------|
| `jest.config.*`, `package.json(jest)` | Jest | `npx jest --coverage --changedSince=HEAD~1` |
| `vitest.config.*` | Vitest | `npx vitest run --coverage` |
| `pytest.ini`, `pyproject.toml(pytest)` | pytest | `pytest --cov --cov-report=term-missing` |
| `go.mod` | Go | `go test -cover ./...` |
| `.cargo/config.toml` | Rust | `cargo tarpaulin --skip-clean` |

감지 실패 시 "커버리지 도구 미감지"로 Info 보고하고 이 단계를 SKIP한다.

#### 판정 기준

| 상황 | 기본 모드 | `--strict` 모드 |
|------|----------|----------------|
| 커버리지 상승 또는 유지 | Info (긍정 언급) | Info |
| 커버리지 하락 (< 5%) | **Warning** | **Warning** |
| 커버리지 하락 (>= 5%) | **Warning** | **FAIL 요소** |
| 변경 코드에 테스트 없음 | **Warning** ("테스트 추가 권장") | **FAIL 요소** |
| 프로젝트에 테스트 자체 없음 | Info ("테스트 프레임워크 도입 권장") | **Warning** |

> **설계 결정**: 기본 모드에서 Coverage는 Warning까지만. FAIL은 `--strict` 전용.
> 근거: 테스트가 없는 프로젝트에서 Nova 채택 장벽을 낮추면서도, 커버리지 하락을 가시화한다.

#### Layer 3 실행 불가 시 판정 규칙

Evaluator 서브에이전트가 Layer 3 실행 검증을 수행할 수 없는 경우(DB 접근 불가, 외부 서비스 미연결, 런타임 환경 부재 등):

1. **PASS를 내리지 않는다** — 코드 리딩(Layer 1~2)만으로는 런타임 동작을 보장할 수 없다
2. **CONDITIONAL을 내린다** — Known Gaps에 "Layer 3 실행 검증 미수행"을 명시하고, 구체적 검증 조건을 제시한다
3. **검증 조건 예시**: "DB 연결 후 해당 쿼리 실행 확인 필요", "API 서버 기동 후 curl 테스트 필요" 등

> 핵심: Layer 1~2에서 이슈가 0개여도, Layer 3을 실행하지 못했으면 PASS가 아니라 CONDITIONAL이다.

## 복잡도별 검증 강도

Evaluator는 변경 규모에 따라 검증 깊이를 자동 조절한다:

| 복잡도 | 기준 | 검증 레이어 | Layer 3 비중 |
|--------|------|------------|-------------|
| **Lite** | 1~2파일, 단순 변경 | Layer 1만 | 없음 |
| **Standard** | 3~7파일, 새 기능 | Layer 1~2 + Layer 3 경량 | **경량** (빌드+테스트만) |
| **Full** | 8+파일, 다중 모듈 | Layer 1~3 + 경계값 | **필수** (전체 체크리스트) |

> **고위험 상향**: 인증/DB/결제/보안 변경은 파일 수와 무관하게 한 단계 상향.
> `--fast` → Lite 강제, `--strict` → Full 강제.

## Last Activity 포맷

NOVA-STATE.md 갱신 시 Last Activity는 **반드시 1줄**로 기록한다:
```
- /nova:review → PASS — src/api/ | 2026-04-02T15:30:00+09:00
```

## 재검증 프로토콜

> "수정 후 Evaluator를 재실행하지 않으면 Evaluator의 가치가 반감된다."

수정이 발생한 후 반드시 재검증을 수행한다:

| 이전 판정 | 재검증 모드 | 후속 행동 |
|-----------|------------|----------|
| FAIL | Full Re-verification (Layer 1~3) | `/nova:run` Full Cycle에서 **1회 자동 재시도**. 그 외에는 사용자 판단 |
| CONDITIONAL | 사용자 판단 | Warning 목록과 권장 조치를 제시. CONDITIONAL 자동 재시도 없음 (자동 재시도 안 함) — 사용자가 명시적으로 재시도를 요청할 때만 진행 |

### 자동 재시도 조건 (FAIL → Retry)

자동 재시도는 다음 조건을 **모두** 충족할 때만 수행한다:

1. `/nova:run` Full Cycle 모드에서 호출됨
2. 판정이 FAIL (Critical 이슈 존재)
3. 이전 재시도 횟수가 0회
4. Critical 이슈가 구체적이고 수정 범위가 명확함

### 재시도 시 수정 범위 제한

- Generator에게 **Evaluator가 지적한 Critical 항목만** 수정하도록 지시한다
- 다른 파일/로직은 건드리지 않는다 — 범위 확산은 새로운 문제를 만든다
- 새 Generator 서브에이전트를 spawn한다 (이전 컨텍스트 오염 방지)

### 재검증 범위

재검증 대상은 "수정 파일만"이 아니라 **영향 범위 1단계**까지 포함한다:

```
재검증 대상 = 수정된 파일 + import/호출 관계 1단계 (callers + callees)
```

- 수정 파일이 export하는 함수/타입을 사용하는 파일 (callers)
- 수정 파일이 import하는 모듈 중 인터페이스가 변경된 파일 (callees)
- 단, 범위 확장은 **Layer 1~2만** 적용. Layer 3(실행 검증)은 수정 파일 자체에만 집중

### 필수 규칙

- 수동 수정도 예외 아님 — Orchestrator가 직접 수정한 경우에도 재검증 필수
- `tsc --noEmit`, `lint` 등 단일 도구 통과만으로 재검증을 대체하지 않는다
- 최대 1회 재시도 후 여전히 FAIL이면 즉시 사용자에게 에스컬레이션

## Fix 모드 (--fix 연동)

`/review --fix`에서 호출될 때 Evaluator는 발견한 이슈에 대해 수정 코드를 제안한다.

### Fix 제안 생성 규칙

1. **Critical 이슈 우선**: Critical을 먼저 제안하고, Warning은 그 다음에 제안한다.
2. **최소 변경 원칙**: 이슈 해결에 필요한 최소한의 코드만 변경한다. 관련 없는 리팩토링을 포함하지 않는다.
3. **Before/After 명시**: 각 수정안은 기존 코드(Before)와 수정 코드(After)를 명확히 대비하여 제시한다.
4. **영향 범위 분석**: 수정이 다른 파일/모듈에 미치는 영향을 분석하여 함께 표시한다.
5. **자동 적용 금지**: Evaluator는 제안만 한다. 적용은 사용자 승인 후 Orchestrator/메인 에이전트가 수행한다.

### Fix 워크플로우

```
[Evaluator] 리뷰 수행 (Layer 1~3)
    ↓
[Evaluator] Critical/Warning 발견
    ↓
[Evaluator] 각 이슈별 수정안 생성 (Before/After + 영향 범위)
    ↓
[메인 에이전트] 수정안을 사용자에게 표시 + 승인 요청
    ↓
[사용자] 승인 (all / 선택 / skip)
    ↓
[메인 에이전트] 승인된 수정안 적용
    ↓
[Evaluator] 재검증 (변경된 파일 대상, Lite 모드)
    ↓
[Evaluator] 재검증 결과 보고
```

### 재검증 후 처리

- 재검증 PASS → 최종 결과 보고
- 재검증에서 새로운 Critical 발견 → **추가 자동 수정을 시도하지 않음**. 사용자에게 보고하고 판단을 위임한다.
- 이는 수정→재수정의 무한 루프를 방지하기 위함이다.

## 긴급 모드 (--emergency)

프로덕션 장애 등 긴급 수정 시, Evaluator 대기가 병목이 될 수 있다.
`--emergency` 플래그가 지정되면 Evaluator를 비동기로 실행한다.

### 동작 방식
1. **수정은 즉시 완료**: Generator의 수정이 끝나면 사용자에게 즉시 보고한다. Evaluator 완료를 기다리지 않는다.
2. **검증은 백그라운드 실행**: Evaluator를 독립 서브에이전트로 비동기 실행한다 (`run_in_background: true`).
3. **결과는 NOVA-STATE.md에 기록**: 검증 완료 시 NOVA-STATE.md에 다음을 기록한다:
   ```
   ## 긴급 수정 사후 검증
   - 시각: {ISO 8601}
   - 대상: {수정 파일 목록}
   - 판정: {PASS/CONDITIONAL/FAIL}
   - 미해결 이슈: {있으면 목록}
   ```
4. **FAIL 시 알림**: 사후 검증에서 FAIL이 나오면 NOVA-STATE.md에 경고를 남기고, 다음 세션 시작 시 사용자에게 알린다.

### 주의사항
- 긴급 모드는 session-start.sh §9(긴급 모드)와 정합한다: Plan/Design/복잡도 판단 생략 + 검증 비동기 사후 실행.
- 긴급 모드라도 검증 자체를 생략하지는 않는다. 시점만 사후로 미룰 뿐이다.
- 긴급 수정이 누적되면 `/nova:next`에서 사후 검증 미완료 건을 우선 추천한다.

## Generator-Evaluator 시스템 레벨 분리

Nova의 핵심 원칙: Evaluator는 코드를 **절대 수정하지 않는다**.
이 원칙은 3중으로 보장된다:

| 레벨 | 메커니즘 | 적용 |
|------|---------|------|
| **프롬프트** | "코드를 직접 수정하지 않는다" 명시 | SKILL.md, agents/*.md |
| **도구 거버넌스** | `disallowedTools: Edit, Write, NotebookEdit` | agents/*.md frontmatter |
| **훅 조건** | `agent_type` 기반 PreToolUse 훅으로 쓰기 도구 차단 | hooks.json (가용 시) |

검증 전용 에이전트(qa-engineer, security-engineer, architect)가 `Agent` 도구로 spawn될 때, `disallowedTools` frontmatter가 쓰기 도구 접근을 하드 블록한다.

## 구조화된 핸드오프 입력

Orchestrator 또는 `/run`에서 호출될 때, Generator의 핸드오프 아티팩트가 제공될 수 있다. 포맷 정의는 Orchestrator SKILL.md "구조화된 핸드오프 프로토콜" 참조.

이 아티팩트가 있으면:
1. **의도 vs 구현 정합성**: Generator의 "변경 의도"와 실제 코드가 일치하는지 검증
2. **주요 결정 타당성**: 트레이드오프 선택이 합리적인지 평가
3. **알려진 제한 확인**: 의도적 생략이 Known Gaps로 기록되었는지 확인
4. **Generator 자가 검증 신호 검토** (Sprint 1): `self_verify` 필드가 있으면 confident/uncertain/not_tested 항목을 판정 report에 명시한다. **Sprint 1에서는 참고용으로만 사용** — Layer 배분에 영향 주지 않음 (Sprint 2 이관). 리포트 서두에 **반드시 한 줄 표기**한다 (Sprint 1 채택률 관측 지표). Orchestrator가 `self_verify_meta` 필드를 전달하면 그 status 값을 그대로 사용한다:
   ```
   self_verify: present              — confident={N}/uncertain={N}/not_tested={N}
   self_verify: absent_after_retry   — 재요청 후에도 Generator가 포함 못 함 (강한 미채택 시그널)
   self_verify: absent               — 메타 정보 없이 누락 (Orchestrator 경유하지 않은 경우)
   ```
   Generator의 `confident` 영역에서 Critical 이슈를 발견하면 **self-preference bias 시그널**로 별도 표기한다:
   ```
   ## self-preference bias 시그널
   - Generator confident: "{인용}"
   - Evaluator 발견: "{Critical 내용}"
   - 해석: Generator 자기 확신 영역에 blind spot 존재. 향후 동일 도메인 변경 시 `--strict` 승격 검토 권장.
   ```
   근거: *LLM Evaluators Recognize and Favor Their Own Generations* (arXiv 2404.13076). Sprint 3에서 자동 승격 로직으로 확장 예정.

아티팩트가 없으면 기존 방식(코드 diff 기반 검증)으로 동작한다. `self_verify` 필드만 없는 경우에도 1~3번은 그대로 수행하고 4번만 생략한다 (하위호환).

## Plan 검증 모드

> deepplan 스킬에서 Critic 단계에 호출된다. 기존 코드 검증(Layer 1~3)과 구별되는 **Plan 문서 전용 적대적 검증**이다.

### 호출 시점

`skills/deepplan/SKILL.md` Phase C(Critic)에서 기본 모드로 호출된다.
호출 컨텍스트에 `target: plan` 및 `file: docs/plans/{slug}.md`가 포함된다.

### Plan 검증 관점

Plan 문서를 대상으로 할 때, Layer 1~3(정적 분석·의미론적 분석·실행 검증)을 적용하지 않는다.
대신 아래 4가지 관점에서 **적대적으로** 검증한다:

| # | 관점 | 핵심 질문 |
|---|------|----------|
| 1 | **MECE 구멍** | Problem 섹션의 분해에서 빠진 영역이 있는가? |
| 2 | **검증 가능성** | Verification Hooks의 Done 조건이 실제로 측정 가능한가? ("성능 향상" 같은 모호한 조건은 FAIL 요소) |
| 3 | **리스크 누락** | Risk Map에서 H×H(고가능성·고영향) 시나리오가 누락되었는가? |
| 4 | **대안 근거** | Solution의 방안 선택 근거가 충분하고 대안 비교가 공정한가? |

### Plan 검증 판정

| 판정 | 기준 |
|------|------|
| **PASS** | 4개 관점에서 Critical 이슈 없음 |
| **FAIL** | 1개 이상 Critical 이슈 존재 |

FAIL 시 이슈 목록을 구조화하여 반환한다:

```markdown
## Critic Issues
| # | Plan 섹션 | 이슈 | 심각도 | 수정 방향 |
|---|----------|------|--------|----------|
| 1 | ## Problem | {구체적 이슈} | Critical/Warning | {수정 방향} |
```

> Plan 검증 모드에서는 코드 파일을 참조하지 않는다. Plan 문서 텍스트만 분석한다.
> "이 Plan대로 구현했을 때 실패할 3가지 시나리오"를 반드시 제시한다.

## GAN 3단 확장 (옵트인, --with-refiner)

> 활성화: `/nova:check --with-refiner`, `/nova:run --with-refiner`, `/nova:review --with-refiner`

기본 비활성. `--with-refiner` 플래그 명시 시에만 Refiner 서브에이전트를 호출한다.

### 3단 흐름

```
[Generator] 구현자 (메인 에이전트 또는 senior-dev 서브에이전트)
    ↓ 구현 완료
[Evaluator] 현재 스킬 — 적대적 검증, FAIL/CONDITIONAL/PASS 판정
    ↓ FAIL 또는 CONDITIONAL (--with-refiner 플래그가 있을 때만)
[Refiner]   agents/refiner.md — 수정안 제안 (코드 직접 변경 금지)
    ↓
[사용자]    수정안 승인 여부 결정 (자동 적용 없음)
```

### 동작 조건

- `--with-refiner` 없음 (기본): Evaluator가 판정 후 종료. Refiner 호출 없음
- `--with-refiner` 있음 + FAIL: Evaluator 판정 후 refiner 서브에이전트 spawn
- `--with-refiner` 있음 + CONDITIONAL: 선택적으로 refiner 호출 (사용자 판단)
- `--with-refiner` 있음 + PASS: Refiner 호출 불필요. 종료

### 자율 적용 금지

Refiner의 수정안은 **제안에 그친다**. 메인 에이전트가 사용자 승인 없이 자동 적용하지 않는다.
Evaluator(이 스킬)는 Refiner 결과를 수용/거부하지 않는다 — 판정은 Evaluator 단독 권한이다.

## 평가 자세
- "통과시키지 마라. 문제를 찾아라."
- 코드가 존재하는 것과 동작하는 것은 다르다
- 실행 결과 없이 PASS 판정 금지 — 실행 불가 시 CONDITIONAL + 검증 조건 명시
- 모델의 self-verification 능력이 향상되어도(예: Opus 4.7) Generator-Evaluator 분리는 유지한다. Generator의 자체 검증은 보조 신호일 뿐, 독립 서브에이전트 검증을 대체하지 않는다.
