# G3.1 Evidence/Inference 4-Field Design

본 spec 은 `visual_pipeline_contracts_plan` 의 Group 3 #6 (`evidence/inference 최소 필드 도입`) 1차 round 설계.
G3.2 (`bg/fg ownership`) 는 별도 spec 으로 분리 — 본 spec 종료 후 별도 brainstorm 진입.

- 작성일: 2026-05-03
- 적용 대상: `scene_consistency` (v5 → v6) + `scene_detail` (v13 → v14)
- 박은 정책: refactor 금지 / fail-fast 기본 block / PNG 재생성 0 (G3.1 한정)

## 1. 배경 (왜 필요한가)

`scene_consistency.fixed_elements` 는 같은 씬의 여러 selected shot 에 걸쳐 변하지
않는 시각 요소를 고정한다. 일단 만들어진 fixed_element 는 downstream
`scene_detail` 의 continuity reference 로 주입되어 모든 shot prompt 에 그대로 박힌다.
즉 fixed_element 가 `canon` 으로 굳는다.

문제:

- 시나리오 시각화에는 추론이 필수다. 원문에 "A figure lies motionless" 만 있어도
  scene_consistency 는 자세, 의복 상태, 광원 방향까지 결정해야 한다.
- 그러나 현재 schema 는 `description` field 안에 "사실 + 추론 + 작가적 결정" 을
  구분 없이 한 문장으로 박는다.
- 한 번의 hallucination 이 canon 에 들어가면 모든 downstream shot 이 그것을
  사실처럼 사용한다.
- 디버깅 시 "이 묘사가 원문 근거인가, LLM 추론인가" 구분 불가.

해결 = 4 필드 분리:

- `source_facts` — 원문/씬/샷에서 인용·요약한 사실
- `visual_inferences` — 원문에 없으나 시각화 위해 추론한 항목
- `creative_decisions` — framing/composition/atmosphere 작가적 선택
- `confidence` — high/medium/low/legacy enum

## 2. Scope (B 안)

| step | 적용 | 이유 |
|---|---|---|
| `scene_consistency` | ✅ | 가장 큰 canon. fixed_element 마다 4 필드. |
| `scene_detail` | ✅ | terminal compile step. t2i_variation 마다 4 필드. PNG 직결. |
| shot_extract | ❌ (out) | description 짧고 extract 성격. follow-up 후보. |
| shot_director / shot_staging | ❌ (out) | follow-up 후보. |

근거 = brainstorm Q2 — B 가 "양 끝 (canon source + 최종 prompt) 만 잡으면 hallucination
지점 거의 잡힘 + scene_detail v13 직후 회귀 위험 컨트롤 가능" 사이의 sweet spot.

## 3. Schema 설계

### 3.1 placement = per-item

각 `fixed_element` (scene_consistency) / 각 `t2i_variation` (scene_detail) item 에
4 필드 모두 추가. top-level 1 세트 안 함.

근거 = brainstorm Q3 — top-level 으로는 "어느 element 가 추론" 인지 trace 불가.
contract 문서 Contract 6 도 fixed_element 마다 confidence 배치.

### 3.2 4 필드 정의

각 fixed_element / t2i_variation item 의 properties 에 4 필드 추가, **item-level
`required` 배열에도 4 필드 모두 추가**, 기존 `additionalProperties: false` 정책 유지:

```json
{
  "source_facts": {
    "type": "array",
    "items": {"type": "string"},
    "description": "원문/씬/샷에서 인용·요약한 사실. 영어 또는 원어. hallucination 방지 텍스트 근거."
  },
  "visual_inferences": {
    "type": "array",
    "items": {"type": "string"},
    "description": "원문에 없으나 시각화 위해 추론한 항목. source_facts 와 명확 분리."
  },
  "creative_decisions": {
    "type": "array",
    "items": {"type": "string"},
    "description": "framing/composition/atmosphere 등 작가적 선택. 사실/추론 외 모든 결정."
  },
  "confidence": {
    "type": "string",
    "enum": ["high", "medium", "low"],
    "description": "high=facts 위주 / medium=balanced / low=대부분 inference. LLM 출력은 이 3개만."
  }
}
```

scene_consistency v6 의 fixed_element required 예시:

```json
"required": [
  "element_id", "element_type", "character_name", "description", "applies_to_shots",
  "source_facts", "visual_inferences", "creative_decisions", "confidence"
]
```

scene_detail v14 의 t2i_variation required 도 동일 패턴 (기존 `variant_label`,
`camera_effect`, `t2i_prompt`, `outfit_assignments` + 4 필드).

### 3.3 enum 3개 only — `legacy` 는 코드 const

**Codex review fix (CRITICAL #1)**: 초안은 enum 4개 (legacy 포함) 였으나, 그렇게
두면 LLM 이 `confidence="legacy"` 출력해도 strict mode 통과 — 박은 fail-fast 정책
(silent fallback X) 위반. 수정:

- schema enum = `["high", "medium", "low"]` only — LLM strict mode 가 legacy 출력 자동 reject.
- `LEGACY_CONFIDENCE = "legacy"` 코드 const 분리 (`_evidence_helpers.py` 모듈).
- adapter 가 옛 cp 에 legacy 부여한 결과는 **다시 schema validate 안 함** —
  in-memory 만 사용. 즉 schema validate 는 항상 LLM 응답에만 적용.
- 디버깅 가시성 = adapter 호출 지점에 `marker="legacy"` 로깅 + helper 코드의 const
  주석으로 발견 보장.

근거 = brainstorm Q3 → codex critical 회귀 후 옵션 (a) (3개 enum + 코드 const) 로 정정.

### 3.4 format = string[]

`source_facts` 등 list item 은 단순 string. 구조화 EvidenceRef
(`{scene_index, source_type, span_id, quote_short}`) 는 본 round 제외 — span_id
인프라 부재. 향후 Group 4 RenderPromptCard 에서 도입 후보.

## 4. Prompt 변경

### 4.1 새 prompt 버전

박은 versioning 정책 — 옛 버전 디렉토리 보존, 새 버전 디렉토리 생성:

- `prompts/_base/scene_consistency/6.{YYYYMMDDHHMM}/` (system.md + schema.json)
- `prompts/_base/scene_detail/14.{YYYYMMDDHHMM}/` (system.md + detail_schema.json)

기존 v5 / v13 디렉토리는 그대로 유지 — `prompt_version_sorting` 정책에 의해 `_version_sort_key` 가 자동으로 최신 선택.

### 4.2 system.md 신규 섹션

scene_consistency v6 에 추가:

```markdown
## Evidence and inference disclosure (per fixed_element)

각 fixed_element 는 4 traceability 필드를 반드시 포함한다. canon 으로 굳기 전
추론 지점을 드러내는 게 목적.

- source_facts: scene_text 또는 shot description 에서 인용·요약한 사실. 영어
  또는 원어. 사실이 없으면 빈 배열 — 그러면 confidence 는 low.
- visual_inferences: 원문에 없으나 시각화 위해 추론한 항목.
- creative_decisions: framing/composition/atmosphere 작가적 선택.
- confidence: "high" | "medium" | "low" — 신뢰도. **절대 "legacy" 출력 금지**
  (legacy 는 옛 checkpoint 식별자, LLM 영역 아님).
  - high: source_facts 위주, inference 보조
  - medium: facts + inference balanced
  - low: 대부분 inference, source_facts 빈약

원칙:
- source_facts 에 inference 섞지 말 것 — 텍스트 근거만.
- 같은 항목을 두 필드에 중복 기재 금지.
- 빈 배열 은 fail 이 아니다 — 정직한 신호 (confidence 로 표현).

예시:
{
  "element_id": "body_full_pose",
  "element_type": "character_state",
  "character_name": "C02_woman",
  "description": "an East Asian woman lying motionless on her left side, left arm bent near the face, eyes closed",
  "applies_to_shots": [1, 3, 4],
  "source_facts": [
    "S12 action: 'A figure lies motionless on the floor.'",
    "scene heading: NIGHT, small interior room"
  ],
  "visual_inferences": [
    "left-side recumbent pose (text only specifies motionless, not orientation)"
  ],
  "creative_decisions": [
    "framing class 'full' to anchor body silhouette across selected shots"
  ],
  "confidence": "high"
}
```

scene_detail v14 에 같은 섹션 (per t2i_variation) 추가. **단 source_facts 정의가
다름 (codex review fix PROBLEM #2)**:

- `source_facts` = **scene_text 원문 + shot_extract description** 만 (text-grounded).
- `visual_inferences` = fixed_element 인용 + LLM 자체 추론 (fixed_element 도 이미
  canonized LLM 출력이라 "사실" 이 아님).
- `creative_decisions` = camera_effect / framing 선택.
- `confidence` = 같은 enum 3개.

이렇게 분리하면 upstream inference cascade ("fixed_element 가 source_fact 로
세탁") 차단. fixed_element 의 visual_inferences 자체가 추적되므로 trace 단절 X.

### 4.3 변경 폭

- 추가만, 삭제 0. 기존 30 섹션 / Rule A~J / 절대 규칙 7종 그대로.
- scene_detail v14 ≈ +50 LOC system.md.
- Phase 8.1 source_language 정책 그대로 — 한국어 시나리오면 source_facts 도 한국어 인용 OK.

## 5. Lazy backfill adapter

### 5.1 helper 함수 (single source)

**위치 결정**: `backend/app/core/steps/_evidence_helpers.py` (신규 모듈).

이유:
- 두 step (scene_consistency + scene_detail) 이 공유 → scene_consistency_step.py 안에 두면 scene_detail 이 cross-step import 해야 함 (semantically odd).
- G2.2 의 `is_scene_result_consumer_safe` 는 scene_consistency 결과 전용이라 scene_consistency_step.py 가 적절했음. evidence helper 는 generic dict 정규화라 step-agnostic.
- detail_steps.py 의 `_DetailStepMixin` 같은 shared util 패턴 이미 있음 (scene_consistency_step 이 import). 같은 패턴 follow.

```python
# backend/app/core/steps/_evidence_helpers.py (신규)
import logging
from typing import Any, Dict
from app.core.errors import AppError

logger = logging.getLogger(__name__)

LEGACY_CONFIDENCE = "legacy"  # adapter-only marker. LLM strict schema 거부.
VALID_LLM_CONFIDENCE = ("high", "medium", "low")
VALID_STORED_CONFIDENCE = VALID_LLM_CONFIDENCE + (LEGACY_CONFIDENCE,)
EVIDENCE_FIELDS = ("source_facts", "visual_inferences", "creative_decisions")


def _normalize_evidence_fields(item: Dict[str, Any], where: str = "") -> Dict[str, Any]:
    """G3.1 lazy backfill — 4 evidence 필드 누락 시 default 주입.

    **옛 checkpoint 전용** (CRITICAL #2 fix). 새 LLM 응답에는 호출 금지 —
    response_schema strict mode 가 4 필드 강제.

    Args:
        item: fixed_element 또는 t2i_variation dict
        where: 호출자 식별 (logging marker)

    Mutation in-place. file 안 건드림.
    """
    if not isinstance(item, dict):
        return item

    backfilled = False
    for field in EVIDENCE_FIELDS:
        if field not in item or not isinstance(item[field], list):
            item[field] = []
            backfilled = True

    if "confidence" not in item or item["confidence"] not in VALID_STORED_CONFIDENCE:
        item["confidence"] = LEGACY_CONFIDENCE
        backfilled = True
    elif backfilled:
        # 일부 필드만 있음 → 부분 legacy → 전체 legacy 강제
        item["confidence"] = LEGACY_CONFIDENCE

    if backfilled and where:
        logger.debug("evidence backfill marker=legacy where=%s", where)

    return item


def _normalize_scene_consistency_result(
    scene_result: Dict[str, Any], where: str = ""
) -> Dict[str, Any]:
    """fixed_elements 각 element normalize.

    PROBLEM #3 fix: list 가 아니면 silent 통과 X (caller status guard 책임).
    """
    if not isinstance(scene_result, dict):
        return scene_result
    fixed = scene_result.get("fixed_elements")
    if not isinstance(fixed, list):
        return scene_result  # malformed — caller status 로 차단
    for fe in fixed:
        _normalize_evidence_fields(fe, where=where)
    return scene_result


def _normalize_scene_detail_result(
    scene_result: Dict[str, Any], where: str = ""
) -> Dict[str, Any]:
    """t2i_variations 각 variation normalize. malformed container type guard."""
    if not isinstance(scene_result, dict):
        return scene_result
    vars_ = scene_result.get("t2i_variations")
    if not isinstance(vars_, list):
        return scene_result
    for var in vars_:
        _normalize_evidence_fields(var, where=where)
    return scene_result


def assert_fresh_llm_evidence(item: Dict[str, Any], step_name: str) -> None:
    """post-parse contract validator (IMPROVEMENT #2).

    LLM strict schema = 4 필드 존재만 강제. contract consistency 추가 검증:
      - confidence == "legacy" → reject (LLM 출력 금지)
      - source_facts == [] AND confidence != "low" → contract violation
      - 4 lists 모두 [] AND confidence != "low" → violation

    AppError(code="step.contract_violation") raise → step_runner retry path.
    **새 LLM 응답에만 호출**. 옛 cp 는 normalize 가 처리.
    """
    confidence = item.get("confidence")
    if confidence == LEGACY_CONFIDENCE:
        raise AppError(
            code="step.contract_violation",
            message=f"{step_name}: LLM 출력에 confidence='legacy' 금지 (adapter-only)",
            status_code=502,
        )
    sf = item.get("source_facts") or []
    vi = item.get("visual_inferences") or []
    cd = item.get("creative_decisions") or []
    if not sf and confidence != "low":
        raise AppError(
            code="step.contract_violation",
            message=f"{step_name}: source_facts=[] 인데 confidence={confidence} (low 여야 함)",
            status_code=502,
        )
    if not (sf or vi or cd) and confidence != "low":
        raise AppError(
            code="step.contract_violation",
            message=f"{step_name}: 4 lists 모두 비어있음 — confidence={confidence}",
            status_code=502,
        )
```

### 5.2 호출 지점 (codex review fix CRITICAL #2 + #3 정정)

**원칙**:
- **새 LLM 응답** = `assert_fresh_llm_evidence` 호출 (각 element/variant 마다).
  normalize 호출 X — strict schema 가 4 필드 강제, normalize 가 빈자리 채우면 silent 통과 됨.
- **옛 cp 읽는 모든 consumer** = `_normalize_*_result` 호출. legacy 마킹 보장.

| 호출자 | 호출 함수 | 시점 | 효과 |
|---|---|---|---|
| `SceneConsistencyStep._execute` | `assert_fresh_llm_evidence` × element | LLM 응답 후, cp write 전 | contract violation retry |
| `SceneConsistencyStep.verify_completion` | `_normalize_scene_consistency_result` | cp 읽기 직후 | 옛 cp legacy normalize |
| `SceneDetailStep._execute` | `assert_fresh_llm_evidence` × variant | LLM 응답 후, cp write 전 | contract violation retry |
| `SceneDetailStep._execute` user-edited path | `_normalize_scene_detail_result` | `_user_edited` reuse 직후 (`detail_steps.py:286-297`) | **PROBLEM #4 fix** — strict 우회 경로 보호 |
| `SceneDetailStep.verify_completion` | `_normalize_scene_detail_result` | cp 읽기 직후 | 옛 cp legacy normalize |
| `scene_context_loader._load_fixed_elements` | `_normalize_scene_consistency_result` | dispatcher → consumer 진입 | downstream 보호 |
| `shot_dependency_t2i_step` (`detail_steps.py:88-104` 와 동등 — `for fe in sc.get("fixed_elements")`) | `_normalize_scene_consistency_result` per scene | fixed_elements 읽기 직전 | 동일 |
| **`t2i_review` (`backend/app/modules/pipeline/t2i_review.py:241, 343`)** | `_normalize_scene_detail_result` | t2i_variations iter 직전 | scene_detail consumer |
| **`scene_still_normalizer` (`backend/app/services/checkpoint_sync/scene_still_normalizer.py:101-141`)** | `_normalize_scene_detail_result` | t2i_variations sync DB 전 | DB 저장 전 보호 |
| **`scene_checkpoint_loaders.load_shot_t2i_variations` (`scene_checkpoint_loaders.py:240-285`)** | 결과 list 에 `_normalize_evidence_fields` × variant | 반환 직전 | UI/downstream 보호 |
| ~~`SceneVerifyStep`~~ | (제거) | — | **codex CRITICAL #3** — t2i_variations 소비 안 함 |

### 5.3 mutation 정책

- adapter 는 in-place — caller 가 같은 dict 재사용 (메모리 효율).
- checkpoint **파일 자체는 건드리지 않음** — `_load_prev_checkpoint` 가 dict 화 한 후 in-memory 만 변경.
- 다음 step 이 cp write 시 normalize 된 dict 가 새 cp 로 저장될 수 있음 — 의도된 점진 진화 (LLM 재실행 없이도 옛 cp 가 점차 새 shape 로 transition).

### 5.4 대안 검토 (각각 왜 안 함)

- (대안 a) 일회성 migration script — checkpoint 일괄 mutation 위험. rollback 어려움.
- (대안 b) Pydantic adapter — 복잡, 의존성 추가. 작은 dict 정규화에 과한 도구.
- (대안 c) 매 호출자가 default 주입 — drift 위험. G2.1/G2.2 single-source helper 패턴 위반.

## 6. fail-fast 정책 + verify_completion

### 6.1 fail-fast 적용 표 (codex review fix)

| 시나리오 | 동작 | 이유 |
|---|---|---|
| 새 LLM 응답 4 필드 누락 | response_schema strict reject → LLM retry | enum 3개 strict 모드 자동 |
| 새 LLM 응답 `confidence="legacy"` | response_schema enum 위반 → strict reject → retry | enum 3개에서 legacy 제외 (CRITICAL #1) |
| 새 LLM 응답 `source_facts=[]` AND `confidence != "low"` | `assert_fresh_llm_evidence` raise → retry (status=contract_violation) | post-parse contract validator (IMPROVEMENT #2) |
| 새 LLM 응답 4 lists 모두 [] AND confidence != low | `assert_fresh_llm_evidence` raise → retry | 동일 contract |
| 옛 cp 4 필드 모두 누락 | `_normalize_*_result` → legacy 부여, in-memory 통과 | lazy backfill |
| 옛 cp 일부 필드만 있음 | `_normalize_*_result` → 누락 채움 + legacy 강제 | 부분 legacy = 전체 legacy |
| 옛 cp `fixed_elements`/`t2i_variations` 가 list 가 아님 (malformed) | normalize noop, status guard 가 차단 | PROBLEM #3 — caller 책임 분리 |
| user-edited v13 cp `_user_edited` reuse | `_user_edited` 분기에서 `_normalize_scene_detail_result` 호출 | PROBLEM #4 — strict 우회 경로 보호 |
| consumer normalize 누락 (drift) | helper sentinel test 가 회귀 차단 (IMPROVEMENT #1) | G2.2 sham 패턴 방지 |

### 6.2 verify_completion 변경

scene_consistency / scene_detail 모두:

- **status 분류 변경 X** — G2.1/G2.2 STATUS_* 그대로.
- **수정 1**: cp 읽기 직후 `_normalize_*_result` 호출 — 옛 cp legacy 자동 normalize.
- **수정 2 (NEW)**: 새 LLM 응답 분기는 `assert_fresh_llm_evidence` 호출 — contract violation 발견 시 retry.

### 6.3 existing_ok filter 변경

scene_consistency 의 existing_ok filter (G2.1):

- **legacy confidence 자체는 retry trigger 가 아님** — 옛 cp 도 valid. 박은 plan 정책 = "LLM step 재실행은 점진적".
- 단, **새 cp 가 `status="contract_violation"` 이면 retry 대상** (G2.1 `failed_all_tiers` 패턴 확장).
- 사용자가 새 evidence 보고 싶으면 → frontend force-run.

## 7. Testing

### 7.1 신규 테스트 (총 ~85-100)

| 분류 | 위치 | 개수 |
|---|---|---|
| adapter unit (`_normalize_evidence_fields`) | `tests/unit/test_evidence_normalize.py` | 10-15 |
| scene_consistency normalize wrapper | `tests/unit/test_evidence_normalize.py` 동일 파일 | 5 |
| scene_detail normalize wrapper | 동일 | 5 |
| `assert_fresh_llm_evidence` post-parse validator | 동일 | 8-10 |
| scene_consistency v6 schema | `tests/unit/test_scene_consistency_schema_v6.py` | 8-10 |
| scene_detail v14 schema | `tests/unit/test_scene_detail_schema_v14.py` | 8-10 |
| **consumer wiring sentinel** (IMPROVEMENT #1) | `tests/integration/test_evidence_consumer_wiring.py` | 12-18 |
| verify_completion (scene_consistency) | 기존 확장 | 5 |
| verify_completion (scene_detail) | 기존 확장 | 5 |
| user-edited 경로 (PROBLEM #4) | 기존 detail_steps tests 확장 | 3 |
| contract_violation status (existing_ok filter) | 기존 scene_consistency tests 확장 | 4 |
| malformed container (PROBLEM #3) | adapter unit + integration 양쪽 | 4-6 |
| prompt presence regression (LEGACY 절대 금지 키워드 포함) | `tests/test_prompt_versions.py` 확장 | 5 |

### 7.1a wiring sentinel 패턴 (IMPROVEMENT #1)

G2.2 review 에서 발견된 sham 패턴 ("loader 호출 검증 X") 회귀 방지. 단순 unit
통과 ≠ wiring 통과. 각 consumer 마다 다음 패턴:

```python
def test_t2i_review_calls_evidence_normalizer(monkeypatch):
    """t2i_review 의 t2i_variations iter 직전에 _normalize_scene_detail_result
    호출되는지 sentinel 로 검증. helper monkey patch + 호출 횟수 assert."""
    calls: List[str] = []
    def sentinel(scene_result, where=""):
        calls.append(where)
        return scene_result
    monkeypatch.setattr(
        "app.modules.pipeline.t2i_review._normalize_scene_detail_result",
        sentinel,
    )
    # 실제 t2i_review run with old-shape fixture
    run_t2i_review(...)
    assert "t2i_review" in calls  # marker 확인
```

각 consumer wiring 마다 sentinel test 1개 → drift 차단.

대상 4 consumer: `t2i_review`, `scene_still_normalizer`,
`scene_checkpoint_loaders.load_shot_t2i_variations`, `shot_dependency_t2i_step`.

### 7.2 fixture 정책

- v5 / v13 (옛) fixture: 4 필드 없음 — legacy 검증용.
- v6 / v14 (새) fixture: 4 필드 포함 — 정상 검증용.
- 두 fixture 공존 — adapter 가 양쪽 모두 처리한다는 증명.

### 7.3 회귀 보호

- 기존 665 passed (G2.2 까지) 0 fail 유지.
- 기존 scene_consistency 23 tests v5 fixture 그대로 통과.
- 새 v6/v14 fixture 별도 분리.

## 8. Out of scope (이번 round 제외)

| 항목 | 왜 제외 | 다룰 곳 |
|---|---|---|
| shot_extract / shot_director 4 필드 | brainstorm Q2 = B (2 step) | follow-up 또는 별 plan |
| EvidenceRef 구조화 (span_id) | brainstorm Q5 = string[] | Group 4 RenderPromptCard |
| Quality gate — **항목별 분리 (codex IMPROVEMENT #2 fix)** | — | — |
| ㄴ contract consistency (`source_facts=[]` ↔ `confidence=low` 일치, 4 lists 모두 비었으면 low 강제, legacy LLM 출력 금지) | **이번 round 포함** (`assert_fresh_llm_evidence`) | 5.1 / 6.1 |
| ㄴ low-confidence 비율 retry / quality 점수 게이트 | brainstorm Q4 = schema-level만 | follow-up |
| Migration script 일회성 backfill | 박은 mutation 0 | 영구 제외 |
| PNG 재생성 | plan 명시 = #6 PNG 0 | G3.2 만 |
| scene_detail prompt 룰 → upstream 이동 | 박은 refactor 금지 | Group 4 #9 |
| DB schema 변화 | cp JSON 안에만 | 영구 제외 |
| UI 노출 (frontend evidence 표시) | 디버깅 우선 | follow-up |
| Opik 4 필드 별 logging | 추가 가치 미정 | follow-up |
| scene_consistency v5 / scene_detail v13 deprecate | 박은 versioning 정책 | 영구 보존 |

## 9. 박은 정책 재확인 (Group 1/2/3 일관)

1. **refactor 금지** — schema/prompt 추가만, 구조 이동 X.
2. **fail-fast 기본 block** — schema strict + lazy backfill 명시 (silent X).
3. **PNG 재생성 X** (G3.1 한정. G3.2 는 PNG 재생성 함).

## 10. 다음 단계

- spec 사용자 검토 → 승인
- `writing-plans` skill 진입 → 구현 plan 작성
- 듀얼 리뷰 패턴 (Codex CLI + Claude code-reviewer) 일관 적용
- 단일 commit + push (Group 1/2 패턴: G3.1 = 1 commit)
