---
task_id: task-2516
type: context
scope: task
created: 2026-05-09
updated: 2026-05-09
status: completed
---

# 맥락 노트: task-2516

**task**: task-2516

---

## 결정 근거

### 핵심 결정: lazy wrapper 함수 패턴 (옵션 A의 변형)

**선택**: top-level `from utils.merge_queue_executor import compare_effective_diff, ...` 제거.
대신 같은 이름의 wrapper 함수들을 module-level에 정의하고, 함수 내부에서 lazy import 후 위임.

```python
def compare_effective_diff(*args, **kwargs):
    from utils import merge_queue_executor as _mqe
    return _mqe.compare_effective_diff(*args, **kwargs)
```

**이유**:
- 모든 호출처(13곳) 코드 변경 0 — 함수 시그니처/이름이 그대로라 minimal diff.
- `sys.modules` 캐시로 두 번째 호출부터 import 비용 사실상 0.
- 회장 명시 "1파일 minimal fix"에 정합 — `replacement_pr_runner.py`만 수정.
- `TaskSpec`은 `TYPE_CHECKING` + 런타임 `Any` fallback (type hint 전용).

**대안과 기각 이유**:
- **옵션 B (DI)**: caller가 `merge_queue_executor` 객체를 주입 → public contract 변경 발생, 5 모듈 본체 영향, expected_files 정확히 3 파일 원칙 위반 가능. 기각.
- **옵션 C (TYPE_CHECKING + 호출처 변경)**: 13개 호출처에 `from utils.merge_queue_executor import ...` 또는 `_mqe.func()` 형태로 직접 변경 → diff 행수가 wrapper 패턴보다 큼 + grep 검증 시 변경점 분산.
- **함수만 inline import**: 13곳에 import 문 추가 → 가독성 저하 + diff 분산. wrapper 1번 정의가 더 깔끔.

### 부수 결정: regression에 wiring 활성화 assert 1건 추가

`merge_queue_executor._WIRING_AVAILABLE`이 True인지 그리고 `ReplacementPRRunner`가 None이 아닌지 직접 assert. circular import 회귀 방지.

### 부수 결정: e2e에 default runtime path 1건 추가

기존 e2e는 ctx hook 주입(`triage_spy`, `reporter_spy`) 시나리오를 검증. 본 task 후에는 ctx 미주입 시 default runtime이 W1 wiring chain을 자연스럽게 사용해야 함을 검증하는 케이스 1건 추가.

## 3 Step Why 자문

**1st Why** — 왜 lazy wrapper 패턴이 필요한가?
**A**: `merge_queue_executor.py`(top-level)에서 `replacement_pr_runner.ReplacementPRRunner`를 try-except import하는데, 만약 `replacement_pr_runner.py`가 top-level에서 `merge_queue_executor`의 `compare_effective_diff` 등을 import하려 하면, A가 in-flight인 상태에서 B가 A를 다시 import하게 되어 A의 module dict에 아직 정의되지 않은 심볼을 찾다가 ImportError 발생. 그 ImportError가 A의 try-except에 잡혀 `_WIRING_AVAILABLE=False`가 된다. 따라서 cycle을 끊는 lazy 구조가 필요하다.

**2nd Why** — 왜 wrapper 함수 방식이 최선인가?
**B**: 호출처(13곳) 변경 없이 top-level 1곳만 수정으로 cycle 제거 가능 → minimal diff(회장 명시 1파일 원칙). `sys.modules` 캐시로 첫 호출 외엔 비용 무시 가능. TYPE_CHECKING + Any fallback로 type hint도 보존.

**3rd Why** — 왜 wrapper가 DI / 호출처 inline import보다 나은가?
**C**: DI는 5 모듈 public contract 수정이 필수 (회장 forbidden). 호출처 inline import는 13곳에 변경 분산 + diff/리뷰 부담 증가 + 누락 위험. wrapper는 단일 지점 변경 + 동작 동일.

★ A→B→C 일관성: 순환 끊기 필요 → 호출처 무변경 minimal fix 필요 → wrapper가 forbidden 회피 + diff 최소.

## 참조 자료

- task spec: `memory/tasks/task-2516.md`
- 정책: `memory/feedback/feedback_critical_escalation_only_260508.md`
- 의존: task-2514 (W1 wiring), task-2515 (e2e replay harness)
- 본체: `utils/replacement_pr_runner.py` (line 33-39 — top-level import 제거 대상)
- 검증 entry: `utils/merge_queue_executor.py` line 62-83 (try-except wiring 블록)

## 주의사항

- expected_files 외 수정 0 (회장 forbidden).
- `merge_queue_executor.py` 절대 수정 금지 (read-only — Codex/Gemini 외부 검증에서 다른 파일이 변경되면 즉시 reject).
- `automation_contracts.py` import 라인 (line 27-31) 그대로 유지 — `merge_queue_executor`와 별개 모듈이고 cycle 없음.
- TaskSpec은 dataclass이므로 TYPE_CHECKING으로 분리해도 isinstance() 체크가 없는지 확인 (function signature에서만 사용).
- regression 추가 케이스는 기존 fixture 재사용 (새 fixture 생성 금지).
- e2e default runtime 케이스는 기존 fixture(`auto_merge_replay_2515.json`)에서 clean auto-merge case 1건 재사용.
