# 교차 코드 리뷰 리포트 — Job c666854d (rev.2)

- **대상**: `.agents/skills/lib.sh`, `tests/test_b19_headless_reconcile_fixes.py`, `FIX.md`
- **리뷰어**: claude (`planner-reviewer-claude-01`)
- **선행 리뷰**: Job b9e42784 — `[VERDICT: NOT PASS]` (D-1 ~ D-5)
- **관점**: 린트 / 동작성 / 유실
- **결론**: 선행 리뷰의 지적 5건이 **모두 정확히 해소**되었고, 이번에는 **실제 Claude Code 클라이언트로 종단 검증**까지 마쳤다.
  잔여 지적은 전부 경미(Low)하며 병합을 막지 않는다.

작업 트리 diff는 브리프에 첨부된 diff와 **완전히 일치**한다 (`lib.sh` 16줄, 테스트 106줄, `FIX.md` 신규).

---

## 0. 검증 방법

이번 리뷰의 핵심은 **실물 검증**이다. 선행 리뷰에서는 합성 화면으로만 확인했으나, 이번에는
`CLAUDE_CODE_FORCE_FULLSCREEN_UPSELL=1`로 **실제 업셀 모달을 강제 재현**하여 확인했다.

| # | 방법 |
|---|---|
| V-1 | 실제 herdr 0.8.2 출력값으로 `agent start` 분류 로직 재현 |
| V-2 | herdr 워크스페이스에 **Claude Code v2.1.247 실기동** → 모달 강제 표시 → Escape 전송 → 상태 측정 |
| V-3 | `lib.sh`를 그대로 source 하여 실제 함수 실행 (HEAD 대비 비교) |
| V-4 | **거부되었던 rev.1 구현을 격리 worktree에 복원**하고 신규 테스트를 돌려 회귀 검출력 확인 |
| V-5 | pytest 전체 |

프로브 워크스페이스(`w1H`/`w1J`/`w1K`/`w1M`/`w1N`)와 임시 worktree는 **전부 정리 완료**.
현재 남은 워크스페이스는 실사용 `w1E` 하나뿐이며, `git worktree list`도 1개(본체)로 복귀했다.

---

## 1. 선행 지적 해소 확인

### D-1 (🔴 → ✅) — 죽은 프로세스가 성공으로 승격되던 문제

성공 정규식이 `agent_started|agent_not_ready`로 축소되었다.
선행 리뷰에서 **실제 herdr 프로브로 채집한 출력값**을 그대로 넣어 분류를 재현한 결과:

| herdr 실제 출력 | 분류 결과 |
|---|---|
| `{"error":{"code":"timeout","message":"timed out waiting for agent startup"}}` (← `/bin/false`, **죽은 프로세스**) | `retry → exit 1` ✅ |
| `{"error":{"code":"agent_pane_busy", …}}` | `retry → exit 1` ✅ |
| `{"error":{"code":"agent_not_ready", …}}` (프로세스 생존, 다이얼로그 차단) | `success=1` ✅ |
| `agent_started` | `success=1` ✅ |

Fail-Closed 복원 확인. 치명 오류 우선 분류 순서도 유지되었다.

또한 이번 실기동에서 herdr가 **정확히 그 상태를 반환하는 것을 실물로 확인**했다:

```
{"error":{"code":"agent_not_ready","message":"agent probe-fs5 is blocked during startup and is not ready for prompts"}}
```

→ `agent_not_ready`를 롤백 사유로 보지 않는 처리가 **가정이 아니라 실측으로** 정당화되었다.

부수 확인: 워크스페이스 생성 직후 즉시 `agent start` 하면 `agent_pane_busy`가 실제로 발생한다(2회 재현).
`lib.sh`의 3회 백오프(0.5/1/2초) 재시도가 이 창구를 정확히 덮으므로 **재시도 루프는 유지되어야 한다**.

### D-2 / D-2c (🔴 → ✅) — idle 팁을 차단형 다이얼로그로 오인하던 문제

광의 토큰 `fullscreen renderer|Try the new fullscreen`이 제거되고 모달 고유 문자열만 사용한다.
`lib.sh`를 실제 source 하여 **정상 기동(팁만 표시, TUI 준비 완료)** 화면을 넣은 결과:

| 팁만 있는 정상 화면 | HEAD | rev.1 (거부됨) | **rev.2 (현재)** |
|---|---|---|---|
| `_pane_dialog_open` | false | 🔴 TRUE | ✅ **false** |
| `handle_startup_dialogs` 전송 키 | 0 | 🔴 Enter 20회 | ✅ **0회** |
| `wait_for_tui_ready` | rc=0 | 🔴 rc=1 (+Enter 30회) | ✅ **rc=0** |

정상 경로가 HEAD와 **완전히 동일**하게 복귀했다. 데드락 해소 확인.

### D-3 (🟠 → ✅) — Enter가 업셀을 "수락"하던 문제

**실제 모달을 강제 재현해 캡처했다.** 모달 하단 안내가 결정적이다:

```
  Try the new fullscreen renderer?

  · Flicker-free output
  · Mouse support — click to move your cursor or expand results
  · Selected text auto-copies to your clipboard

  ❯ 1. Yes, try it
    2. Not now

  Enter to confirm · Esc to cancel
```

- `Enter to confirm` → 기본 선택지 `Yes, try it` 수락. 선행 리뷰의 D-3 지적이 **모달 자체 문구로 확증**되었다.
- `Esc to cancel` → 수정이 택한 Escape가 **모달이 스스로 안내하는 취소 키**다.

Escape 전송 후 실측:

| 항목 | 결과 |
|---|---|
| 모달 제거 | ✅ 사라짐 (`Yes, try it` 0건) |
| 잔여 다이얼로그 토큰 | ✅ 0건 → `send_keys_safe` 차단 해제 |
| ready 토큰 가시성 | ✅ 2건 (`Claude Code v2.1.247`, `Sonnet 5 with high effort`) → `wait_for_tui_ready` 통과 |
| **`⏵⏵ bypass permissions on` 유지** | ✅ **세션 재시작 없음 — `--dangerously-skip-permissions` 보존** |

마지막 항목이 중요하다. 우려했던 "수락 시 permission flag 없이 재시작" 경로를 **Escape가 회피함을 실물로 확인**했다.

### D-4 (🟠 → ✅) — 테스트가 소스 문자열만 확인하던 문제

신규 4건 중 3건이 `_pane_capture`를 stub 하고 **함수를 실제 실행**하는 행위 테스트로 바뀌었다.
(`_pane_tail` → `_pane_capture` → `_sks_herdr` 체인이므로 `_pane_capture` stub은 올바른 주입 지점이다.)

회귀 검출력을 직접 측정했다. **거부되었던 rev.1 구현을 격리 worktree에 복원**하고 신규 테스트를 실행:

```
FAILED test_agent_start_success_tokens_exclude_startup_timeout
FAILED test_fullscreen_tip_is_not_a_blocking_dialog
FAILED test_fullscreen_modal_is_rejected_not_accepted
FAILED test_wait_for_tui_ready_succeeds_on_fullscreen_tip
4 failed
```

→ **4건 전부 rev.1에서 실패하고 rev.2에서 통과**한다. 실질적 회귀 방지력이 확인되었다.
`test_wait_for_tui_ready_succeeds_on_fullscreen_tip` 실패 로그에는 rev.1의 `Enter` 30회 주입과
`⚠️ TUI readiness check timed out`이 그대로 찍혔다 — 정확히 선행 리뷰가 지적한 증상이다.

### D-5 (🔵 → 대부분 해소)

`FIX.md`가 재작성되어 순서 변경·타임아웃 토큰 배제 근거·팁/모달 구분이 모두 기술되었고, 말미 개행도 정상이다.

---

## 2. 테스트 / 린트 결과

- `tests/` 전체 **397 passed** (8분 30초). HEAD 393 + 신규 4건과 정확히 일치.
- 변경 파일 단독 **10 passed** (신규 4건 포함).
- `bash -n .agents/skills/lib.sh` **통과**. `shellcheck`는 이 환경에 미설치라 미실행.

---

## 3. 잔여 지적 (전부 Low — 병합 차단 아님)

### N-1 `_MAM_DIALOG_TOKENS`의 `|Yes, try it`은 **불필요하며** 오탐 면적만 넓힌다

실제 모달 문구에는 `Esc to cancel`이 포함되어 있고, 이 토큰은 **HEAD의 기존 토큰 목록에 이미 존재**한다.
HEAD 토큰만으로 실제 모달이 매칭되는 것을 확인했다 → **탐지 목적으로는 추가가 중복**이다.
(선행 리뷰의 합성 픽스처에는 이 하단 안내줄이 없어 드러나지 않았던 부분이다.)

반면 `Yes, try it`은 평문 대화에 등장할 수 있는 자연어다. 실제로 아래 한 줄이 `_pane_dialog_open`을 참으로 만든다:

```
⏺ Sure — if the build fails again, Yes, try it with the --clean flag.
```

→ `send_keys_safe`가 30초 대기 후 `rc=2`로 실패한다. 확률은 낮고, HEAD에도 `Allow this` / `No, exit` 같은
평문형 토큰 선례가 있어 **새로운 부류의 위험은 아니다.** 다만 이 건은 얻는 것이 없으므로 제거를 권한다.

- **권고**: `_MAM_DIALOG_TOKENS`에서 `|Yes, try it` 제거. `handle_startup_dialogs`의 분기는 그대로 둔다
  (모달 탐지는 기존 `Esc to cancel`이 이미 담당). 더 좁히려면 팁에 없는 물음표형
  `Try the new fullscreen renderer\?`를 앵커로 쓰는 편이 가장 정확하다.

### N-2 테스트의 `_init_herdr_isolation` stub이 **동작하지 않는다**

`_run_lib_helpers`는 `source` **뒤에** `_init_herdr_isolation() { :; }`을 정의하지만,
`lib.sh:1881`에서 이미 source 시점에 실호출된다. 따라서 stub은 사실상 죽은 코드이고,
매 테스트가 `$WORKSPACE_ROOT/.mam/shim/herdr`를 실제로 기록한다(실행 중 mtime 갱신 확인).
`.mam/`은 gitignore 대상이라 git 오염은 없고 멱등이라 실피해도 없으나, **의도와 실제가 어긋나 있다.**

- **권고**: 아래 N-3의 미사용 파라미터를 활용해 `WORKSPACE_ROOT`를 임시 디렉터리로 넘긴다.

### N-3 `_run_lib_helpers(env_extra=...)`가 **어떤 호출부에서도 사용되지 않는다** (미사용 파라미터)

N-2의 해법 통로이므로 제거보다 활용을 권한다.

### N-4 테스트가 `/tmp/mam-fs-*-keys.$$`를 하드코딩한다

스크립트 말미의 `rm -f`는 `set -euo pipefail` 하에서 앞 단계가 실패하면 실행되지 않아 잔여 파일이 남을 수 있다
(이번 실행에서는 잔여물 없음). pytest `tmp_path` 사용을 권한다.

### N-5 `FIX.md`가 여전히 **untracked**다

변경 근거 문서로 참조되고 있으므로, 병합 전 커밋하거나 의도적으로 제외한다면 그 판단을 남겨야 한다.

---

## 4. git discard 여부

**discard 하지 말 것.** 두 문제 모두 실재함이 이번에 실물로 확정되었다 —
herdr가 기동 중 차단 상태에서 `agent_not_ready`를 반환하는 것, 그리고
Claude Code v2.1.247이 `Try the new fullscreen renderer?` 모달로 기동을 막는 것 모두 직접 재현했다.

---

## 5. 요약

| 선행 지적 | 상태 | 근거 |
|---|---|---|
| D-1 죽은 프로세스 → 성공 승격 | ✅ 해소 | 실채집 herdr 출력 4종 분류 재현 |
| D-2 팁을 다이얼로그로 오인 | ✅ 해소 | 실함수 실행, HEAD와 동일 동작 복귀 |
| D-2c `wait_for_tui_ready` 데드락 | ✅ 해소 | rc=1 → **rc=0** |
| D-3 Enter가 업셀 수락 | ✅ 해소 | 실모달 `Enter to confirm · Esc to cancel`, Escape 후 permission flag 보존 확인 |
| D-4 회귀 방지력 없음 | ✅ 해소 | rev.1 복원 시 신규 4건 전부 실패 |
| D-5 문서/린트 | ✅ 대부분 해소 | FIX.md 재작성 |

| 신규 지적 | 심각도 | 요지 |
|---|---|---|
| N-1 | 🔵 Low | `Yes, try it` 토큰 추가는 중복이며 평문 오탐 면적만 넓힘 |
| N-2 | 🔵 Low | `_init_herdr_isolation` stub 무효 → 테스트가 `.mam/shim` 실제 기록 |
| N-3 | 🔵 Low | `env_extra` 미사용 파라미터 |
| N-4 | 🔵 Low | `/tmp` 하드코딩, 실패 시 잔여 가능 |
| N-5 | 🔵 Low | `FIX.md` untracked |

핵심 결함은 모두 해소되었고 잔여는 전부 위생 수준이므로 병합 가능하다고 판단한다.
설계 재작업 사유가 없어 PLANNER 에스컬레이션은 두지 않는다.

[VERDICT: PASS]
