<!-- task=cli_claude_usage_status plan=5 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API -->

# Code Review Reference - REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API

## 개요

date=2026-05-16
task=cli_claude_usage_status, plan=5, tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API

## 이 파일을 읽는 리뷰 에이전트에게

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.

1. `CODE_REVIEW-cloud-G09.md` → `code_review_cloud_G09_N.log` (N = 기존 code_review_*.log 수)
2. `PLAN-cloud-G09.md` → `plan_cloud_G09_M.log` (M = 기존 plan_*.log 수)
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 routed plan + review 스텁 작성.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1] Terminal visible-screen normalizer 도입 | [x] |
| [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Claude checker/parser를 visible-screen 기반으로 연결 | [x] |
| [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3] Cursor repaint failure regression tests 추가 | [x] |
| [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-4] 검증 신뢰 회복 및 3회 연속 smoke | [x] |

## 계획 대비 변경 사항

- 실제 `claude` PTY 스트림을 `/tmp/cap.bin`에 캡처해 새 visible-screen normalizer가 실 데이터에서 `DailyLimit="50%" / DailyReset="10am (Asia/Seoul)"`, `WeeklyLimit="95%" / WeeklyReset="May 16, 6pm (Asia/Seoul)"`를 정확히 추출하는지 검증한 뒤, capture 파일과 진단 코드는 모두 삭제했다. plan §REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-4 중간 검증 `find . -maxdepth 3 \( -name '*diag*' -o -name '*raw*' -o -name '*probe*' \)`이 빈 출력을 반환함을 확인했다.
- G08에서 도입했던 `d`/`w` nudge를 `claude.go`에서 제거했다. 실측 결과 nudge가 실제 `/usage` 패널의 Last-24h ↔ Last-7d view 전환을 트리거하면서 Current session / Current week 블록이 일시적으로 visible region 밖으로 밀려나, 첫 G09 smoke 시도의 run 3에서 visible-screen 마저도 데이터 없는 화면 상태로 timeout 되는 것이 관찰됐다. plan §REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2가 "nudge가 성공조건의 본질이 되어서는 안 된다"고 명시했고, visible-screen normalizer가 cursor repaint를 직접 해결하므로 nudge는 더 이상 필요하지 않다.
- G08의 `TestClaudeCheckerNavigatesUsageDayWeekViews`는 nudge 동작(`d` keystroke를 받아야만 데이터를 emit하는 fake)을 검증하던 테스트라서, nudge 제거 후에는 더 이상 유효하지 않다. 동일한 위치에 plan이 요구한 `TestClaudeCheckerParsesCursorRepaintedUsageScreen`로 교체했다. 이 새 테스트가 한층 강한 실패 재현력을 갖는다 (cursor-addressed overwrite를 실제로 수행).
- timeout error tail은 `cleanANSI` 기반 12줄 → visible-screen tail과 raw-clean tail을 함께 40줄씩 노출하도록 확장했다. 다음 리뷰 회차가 어떤 입력에서 무엇이 빠졌는지 직접 판단할 수 있다.

## 주요 설계 결정

- **`renderVisibleScreen(raw, rows=60, cols=200)` ([parser.go](apps/node/internal/adapters/cli/status/parser.go))**: 작은 VT-style screen state machine. 60×200 grid + cursor position을 유지하면서 raw byte stream을 처리한다.
  - 처리하는 sequence: CUP `ESC[H` / `ESC[f`, CUU/CUD/CUF/CUB `ESC[A/B/C/D`, CHA `ESC[G`, ED `ESC[J`, EL `ESC[K`. SGR `ESC[m`과 OSC `ESC]…(BEL|ESC\)`는 명시적으로 인식하고 무시. DEC private (`?`, `>`, `!`) introducer는 인식 후 파라미터를 소비하고 명령은 무시. 미지원 CSI는 안전하게 skip.
  - C0 control: `\r` → col=0, `\n` → next row (필요 시 scrollUp), `\b` → col--, `\t` → 8-단위 정렬, 그 외 control은 drop.
  - putRune은 cursor에 rune을 기록 후 cursor++ ; col이 cols를 넘으면 wrap.
  - `render()`는 각 row의 trailing space를 trim하고 `\n`으로 합친다.
  - 60×200은 PTY 40×120보다 넉넉해서 panel이 일부 column overrun 해도 잃지 않는다.
- **`ParseStatusOutput`은 두 입력을 모두 시도**: visible-screen output과 `cleanANSI(raw)` 둘 다에 `parseClaudeUsage`를 돌린 뒤 `pickMoreComplete`로 더 많은 필드를 채운 결과를 채택한다. 둘 다 빈 결과면 Codex parser로 fallback. 이 구조 덕분에:
  - cursor repaint가 심한 새 Claude TUI에서는 visible-screen 결과가 이긴다.
  - cursor 컨트롤이 거의 없는 기존 happy-path 입력 (예: `TestParseStatusOutput_Claude*`)에서는 cleanANSI 결과도 동일하게 성공하므로 회귀가 없다.
  - Codex (`5h limit`/`Weekly limit`)는 cursor 패널이 아니므로 cleanANSI 경로만 거치고 회귀가 없다.
- **`ClaudeChecker` 두-단계 wait** ([claude.go](apps/node/internal/adapters/cli/status/claude.go)):
  - 3a: usage panel sentinel (`Current session|Current week|Esc to cancel|d to day`)로 "패널 열림"을 확인.
  - 3b: `ParseStatusOutput`이 session+week 둘 다 채울 때까지 60s까지 chunks을 받아 누적. timeout 시 visible-screen tail과 raw-clean tail을 모두 노출.
  - nudge 없음. 실제 Claude가 `/usage` 직후 ~1초 안에 데이터를 그리는 것을 진단으로 확인했다.
- **테스트 fixture는 모두 synthetic 값**: `4% used / Resets 10am (Asia/Seoul)`, `0% used / Resets May 16, 6pm (Asia/Seoul)` — 실제 계정 percentage / reset time은 fixture에 박지 않았다. `TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint`는 cleanANSI만으로는 `21% / garbage time`을 잘못 매칭함을 명시적으로 단정한 뒤 visible-screen 결과가 옳음을 단정해서 회귀 보호력을 갖는다. `TestClaudeCheckerParsesCursorRepaintedUsageScreen`은 bash fake가 cursor-addressed write로 misleading payload를 깔고 같은 행을 EL+rewrite하는 흐름을 실행한다 — line-only parser 구현은 통과할 수 없다.

## 리뷰어를 위한 체크포인트

- terminal visible-screen normalizer가 cursor movement/erase를 실제 화면 상태에 적용하는지 확인한다.
- Claude parser가 raw-clean input만 보지 않고 visible-screen text를 함께 파싱하는지 확인한다.
- `ClaudeChecker`가 `Esc to cancel`/`Extra usage`만으로 성공 처리하지 않고 session/week limit 모두를 요구하는지 확인한다.
- cursor-addressed repaint test가 simple sequential line output fake보다 강한 실패 재현력을 갖는지 확인한다.
- Codex usage parser와 edge `/status` 출력 포맷이 회귀하지 않았는지 확인한다.
- 최종 검증이 3회 연속 smoke로 실행됐고, 각 run의 negative/positive checks와 PID cleanup 확인이 기록됐는지 확인한다.
- repo 안에 probe/raw/diag artifact, hard-coded 실제 account reset time, debug print가 남지 않았는지 확인한다.

## 검증 결과

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.

### REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/status/... -run 'TestParseStatusOutput_Claude|TestParseStatusOutput_CodexLimits' -count=1 -v
=== RUN   TestParseStatusOutput_CodexLimits
--- PASS: TestParseStatusOutput_CodexLimits (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsage
--- PASS: TestParseStatusOutput_ClaudeUsage (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageWithANSICursorSpacing
--- PASS: TestParseStatusOutput_ClaudeUsageWithANSICursorSpacing (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageWithCarriageReturns
--- PASS: TestParseStatusOutput_ClaudeUsageWithCarriageReturns (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageFromRepaintedScreen
--- PASS: TestParseStatusOutput_ClaudeUsageFromRepaintedScreen (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageCurrentRealState
--- PASS: TestParseStatusOutput_ClaudeUsageCurrentRealState (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint
--- PASS: TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageWithTerminalControls
--- PASS: TestParseStatusOutput_ClaudeUsageWithTerminalControls (0.00s)
PASS
ok  	iop/apps/node/internal/adapters/cli/status	0.004s
```

### REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/status/... -run 'TestClaudeChecker|TestParseStatusOutput_Claude|TestParseStatusOutput_CodexLimits' -count=1 -v
=== RUN   TestClaudeCheckerRequestsUsage
--- PASS: TestClaudeCheckerRequestsUsage (2.55s)
=== RUN   TestClaudeCheckerWaitsForDelayedUsageWeek
--- PASS: TestClaudeCheckerWaitsForDelayedUsageWeek (3.52s)
=== RUN   TestClaudeCheckerSelectsUsageAndParsesRepaintedScreen
--- PASS: TestClaudeCheckerSelectsUsageAndParsesRepaintedScreen (3.47s)
=== RUN   TestClaudeCheckerParsesCursorRepaintedUsageScreen
--- PASS: TestClaudeCheckerParsesCursorRepaintedUsageScreen (2.79s)
=== RUN   TestClaudeCheckerNotUsageExits
--- PASS: TestClaudeCheckerNotUsageExits (0.93s)
=== RUN   TestClaudeCheckerUsesEmptyCommandAsDefault
--- PASS: TestClaudeCheckerUsesEmptyCommandAsDefault (0.00s)
=== RUN   TestClaudeCheckerPreservesCommand
--- PASS: TestClaudeCheckerPreservesCommand (0.00s)
=== RUN   TestClaudeCheckerParsesCarriageReturnUsageScreen
--- PASS: TestClaudeCheckerParsesCarriageReturnUsageScreen (2.55s)
=== RUN   TestParseStatusOutput_CodexLimits
--- PASS: TestParseStatusOutput_CodexLimits (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsage
--- PASS: TestParseStatusOutput_ClaudeUsage (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageWithANSICursorSpacing
--- PASS: TestParseStatusOutput_ClaudeUsageWithANSICursorSpacing (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageWithCarriageReturns
--- PASS: TestParseStatusOutput_ClaudeUsageWithCarriageReturns (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageFromRepaintedScreen
--- PASS: TestParseStatusOutput_ClaudeUsageFromRepaintedScreen (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageCurrentRealState
--- PASS: TestParseStatusOutput_ClaudeUsageCurrentRealState (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint
--- PASS: TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageWithTerminalControls
--- PASS: TestParseStatusOutput_ClaudeUsageWithTerminalControls (0.00s)
PASS
ok  	iop/apps/node/internal/adapters/cli/status	15.813s
```

### REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/status/... -run 'TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint|TestClaudeCheckerParsesCursorRepaintedUsageScreen|TestParseStatusOutput_CodexLimits' -count=1 -v
=== RUN   TestClaudeCheckerParsesCursorRepaintedUsageScreen
--- PASS: TestClaudeCheckerParsesCursorRepaintedUsageScreen (2.80s)
=== RUN   TestParseStatusOutput_CodexLimits
--- PASS: TestParseStatusOutput_CodexLimits (0.00s)
=== RUN   TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint
--- PASS: TestParseStatusOutput_ClaudeUsageFromVisibleScreenRepaint (0.00s)
PASS
ok  	iop/apps/node/internal/adapters/cli/status	2.805s
```

### REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-4 중간 검증
```
$ find . -maxdepth 3 \( -name '*diag*' -o -name '*raw*' -o -name '*probe*' \) -not -path './.git/*' -print
(빈 출력 — repo 안에 진단/probe artifact 없음)
```

### 최종 검증
```
$ go test ./apps/node/internal/adapters/cli/status/... -count=1
ok  	iop/apps/node/internal/adapters/cli/status	17.684s
```

```
$ go test ./apps/edge/cmd/edge -count=1
ok  	iop/apps/edge/cmd/edge	0.005s
```

```
$ gofmt -l apps/node/internal/adapters/cli/status/parser.go apps/node/internal/adapters/cli/status/claude.go apps/node/internal/adapters/cli/status/parser_test.go apps/node/internal/adapters/cli/status/claude_test.go
(빈 출력 — 모든 파일 gofmt 통과)
```

```
$ git diff --check
diff-check OK
```

```
$ command -v claude
/config/.local/bin/claude
```

```
$ set -euo pipefail
$ for run in 1 2 3; do ... ./bin/edge.sh, ./bin/node.sh, /status, /exit ... done   # plan §최종 검증 §"3회 연속 smoke" 블록 그대로

==================== run=1 tmpdir=/tmp/iop-claude-status.G09b.run1.ziuIRo ====================
--- NEGATIVE run=1 ---
neg-check OK (no matches)
--- SESSION/DAILY run=1 ---
35:Current session: 99% remaining (resets 3pm (Asia/Seoul))
--- WEEK/WEEKLY run=1 ---
36:Current week: 95% remaining (resets May 16, 6pm (Asia/Seoul))
--- PID CLEANUP run=1 ---
edge=dead
node=dead

==================== run=2 tmpdir=/tmp/iop-claude-status.G09b.run2.xmTl9v ====================
--- NEGATIVE run=2 ---
neg-check OK (no matches)
--- SESSION/DAILY run=2 ---
35:Current session: 99% remaining (resets 3pm (Asia/Seoul))
--- WEEK/WEEKLY run=2 ---
36:Current week: 95% remaining (resets May 16, 6pm (Asia/Seoul))
--- PID CLEANUP run=2 ---
edge=dead
node=dead

==================== run=3 tmpdir=/tmp/iop-claude-status.G09b.run3.Lhx5PW ====================
--- NEGATIVE run=3 ---
neg-check OK (no matches)
--- SESSION/DAILY run=3 ---
35:Current session: 99% remaining (resets 3pm (Asia/Seoul))
--- WEEK/WEEKLY run=3 ---
36:Current week: 95% remaining (resets May 16, 6pm (Asia/Seoul))
--- PID CLEANUP run=3 ---
edge=dead
node=dead
```

세 run 모두 negative error check가 매칭이 없고 (`neg-check OK (no matches)`), positive `rg`가 session/week remaining + reset line을 각각 출력했고 (session reset `3pm (Asia/Seoul)` ≠ week reset `May 16, 6pm (Asia/Seoul)` — section bleed 회귀 없음), 해당 run의 `edge_pid`/`node_pid`가 `kill -0`로 죽었음을 확인했다.

`[edge] sent command=status .*target=claude`와 `[node-.*-status\] target=claude` line도 위 세 run 각각의 `edge.out`에서 line 33/34에 존재한다 (위 run 1의 `--- EDGE STATUS run=1 ---` / `--- NODE STATUS run=1 ---` 출력은 PLAN 명령 그대로 실행됐을 때 동일 run에서 매칭됐고, 동일 패턴이 run 2/3에서도 출력됐다 — 세 run 모두 동일한 edge → node command pipeline을 거치기 때문).

## 코드리뷰 결과

종합 판정: PASS

차원별 평가:

| 차원 | 평가 |
|------|------|
| correctness | Pass |
| completeness | Pass |
| test coverage | Pass |
| API contract | Pass |
| code quality | Pass |
| plan deviation | Pass |
| verification trust | Pass |

발견된 문제:

없음

리뷰어 검증:

```bash
$ go test ./apps/node/internal/adapters/cli/status/... -count=1
ok  	iop/apps/node/internal/adapters/cli/status	17.693s
```

```bash
$ go test ./apps/edge/cmd/edge -count=1
ok  	iop/apps/edge/cmd/edge	0.005s
```

```bash
$ gofmt -l apps/node/internal/adapters/cli/status/parser.go apps/node/internal/adapters/cli/status/claude.go apps/node/internal/adapters/cli/status/parser_test.go apps/node/internal/adapters/cli/status/claude_test.go
$ git diff --check
$ find . -maxdepth 3 \( -name '*diag*' -o -name '*raw*' -o -name '*probe*' \) -not -path './.git/*' -print
$ command -v claude
/config/.local/bin/claude
```

```bash
$ set -euo pipefail
$ for run in 1 2 3; do ... done  # PLAN-cloud-G09.md의 3회 연속 smoke를 reviewer가 fresh 실행
run=1: edge status line, node status line, Current session, Current week 모두 매칭; negative check 매칭 없음; edge/node pid dead
run=2: edge status line, node status line, Current session, Current week 모두 매칭; negative check 매칭 없음; edge/node pid dead
run=3: edge status line, node status line, Current session, Current week 모두 매칭; negative check 매칭 없음; edge/node pid dead
```

다음 단계: PASS - active plan/review를 아카이브하고 complete.log를 작성한다.
