# Claude Usage Status Review Follow-up Plan ## 이 파일을 읽는 구현 에이전트에게 **구현 완료 후 `CODE_REVIEW-*-G??.md`의 모든 섹션을 채우는 것이 필수 최종 단계입니다. 이 파일을 채우기 전까지 작업은 완료가 아닙니다.** 아래 체크리스트와 중간/최종 검증을 실제로 실행하고, 구현 내용과 명령 출력을 `CODE_REVIEW-cloud-G07.md`에 기록하세요. `CODE_REVIEW-cloud-G07.md`의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시(`*.log` 변경, `complete.log` 작성)는 구현 에이전트가 수행하지 않습니다. ## 배경 1차 구현은 Claude `/usage` 파서와 PTY checker를 추가했지만, 리뷰 중 실제 `./bin/edge.sh` + `./bin/node.sh` smoke에서 `/status` 출력이 `Current week`만 표시되고 `Current session`이 누락됐다. 원인은 `ClaudeChecker`가 session/week 둘 중 하나만 나오면 usage block이 준비됐다고 판단하는 조기 반환 조건이다. 이번 후속 작업은 checker가 두 usage section을 모두 기다리도록 고정하고, 지연 출력 fake TUI와 실제 bin smoke 검증으로 회귀를 막는다. ## 분석 결과 ### 읽은 파일 - `agent-ops/skills/common/code-review/SKILL.md` - `agent-task/cli_claude_usage_status/plan_local_G06_0.log` - `agent-task/cli_claude_usage_status/code_review_cloud_G07_0.log` - `apps/node/internal/adapters/cli/status/claude.go` - `apps/node/internal/adapters/cli/status/claude_test.go` - `apps/node/internal/adapters/cli/status/parser.go` - `apps/node/internal/adapters/cli/status/parser_test.go` - `apps/node/internal/adapters/cli/status/status.go` - `apps/node/internal/adapters/cli/status/status_test.go` - `apps/node/internal/adapters/cli/cli.go` - `apps/node/internal/node/node.go` - `apps/edge/cmd/edge/console.go` - `apps/edge/cmd/edge/console_test.go` - `bin/edge.sh` - `bin/node.sh` - `configs/edge.yaml` ### 테스트 커버리지 공백 - `ClaudeChecker` complete-wait behavior: 기존 `TestClaudeCheckerRequestsUsage`는 session/week를 즉시 연속 출력하므로 조기 반환을 잡지 못한다. - 실제 bin smoke: 1차 `CODE_REVIEW`는 SKIP을 기록했지만, 리뷰 환경에서 `command -v claude`와 bin smoke가 실행 가능했고 실제로 `Current session` 누락을 발견했다. - Edge formatting 자체는 단위 테스트가 있으나, node checker가 `DailyLimit`을 누락할 때 `/status` 결과가 불완전해지는 end-to-end 검증이 필요하다. ### 심볼 참조 - renamed/removed symbol 없음. - 참조 확인 명령: `rg --sort path -n "ClaudeChecker|NewClaudeChecker|ParseStatusOutput|TestClaudeChecker|formatUsageStatus" apps/node/internal/adapters/cli apps/edge/cmd/edge` - `ClaudeChecker.Check`는 `status.CheckUsage` → `CLI.HandleCommand` → node command → edge `/status`로 연결된다. ### 범위 결정 근거 - `proto/**`, `runtime/types.go`, node command transport는 수정하지 않는다. metadata와 command response 경로는 이미 동작한다. - `configs/edge.yaml`은 이번 후속에서 수정하지 않는다. 현재 dirty diff는 `target: claude`이며 smoke 전제로만 사용한다. - Codex checker와 Gemini checker는 수정하지 않는다. 다만 status package tests는 함께 실행해 Codex 회귀를 확인한다. - parser 자체는 현재 tests를 통과하므로 우선 `ClaudeChecker`의 wait predicate를 고친다. 실제 bin smoke 후 parser가 여전히 `Current session`을 못 읽으면 raw output 근거와 함께 parser 보강을 `계획 대비 변경 사항`에 기록한다. ### 빌드 등급 - build lane: `local-G06` — 변경은 Claude checker wait 조건과 tests에 국한되며 실패가 재현 가능하다. - review lane: `cloud-G07` — 실제 TUI/bin smoke 검증 신뢰가 핵심이므로 동일 review 등급을 유지한다. ## 의존 관계 및 구현 순서 1. `REVIEW_API-1`에서 checker가 both section parsed 상태를 기다리도록 고친다. 2. `REVIEW_API-2`에서 지연 출력 fake TUI regression test를 추가한다. 3. `REVIEW_API-3`에서 실제 bin smoke를 stricter assertion으로 실행해 review file에 기록한다. ### [REVIEW_API-1] ClaudeChecker가 session/week 모두 파싱될 때까지 기다리게 수정 #### 문제 `apps/node/internal/adapters/cli/status/claude.go:112-115`는 주석상 “both Current session and Current week”를 기다린다고 되어 있지만 regex가 alternation이라 둘 중 하나만 나오면 통과한다. 리뷰 중 실제 smoke에서 edge output은 `Current week: 100% remaining (resets May 16, 6pm (Asia/Seoul))`만 출력했고 `Current session`이 누락됐다. Before (`apps/node/internal/adapters/cli/status/claude.go:112`): ```go // 3. Wait for usage block output (both Current session and Current week) usageRegex := regexp.MustCompile(`(?s)(?:Current session).*?Resets|Current week.*?Resets`) if err := waitFor(usageRegex, 20*time.Second); err != nil { return nil, fmt.Errorf("failed waiting for usage output: %w", err) } ``` #### 해결 방법 `waitFor` regex 대신 predicate 기반 대기 helper를 추가한다. 매 chunk마다 `ParseStatusOutput(fullOutput)`을 호출하고 `DailyLimit`과 `WeeklyLimit`이 모두 채워진 경우에만 성공한다. timeout error에는 clean output의 마지막 일부를 포함해 실제 TUI 변경 시 parser 보강 근거를 남긴다. After: ```go if err := waitForUsageStatus(20 * time.Second); err != nil { return nil, fmt.Errorf("failed waiting for usage output: %w", err) } ``` ```go waitForUsageStatus := func(timeout time.Duration) error { timeoutCh := time.After(timeout) for { if st, _ := ParseStatusOutput(fullOutput); st != nil && st.DailyLimit != "" && st.WeeklyLimit != "" { return nil } select { case <-ctx.Done(): return ctx.Err() case <-timeoutCh: return fmt.Errorf("timeout waiting for complete Claude usage block: %s", usageOutputTail(cleanANSI(fullOutput))) case chunk, ok := <-chunks: if !ok { return io.EOF } fullOutput += chunk } } } ``` #### 수정 파일 및 체크리스트 - [ ] `apps/node/internal/adapters/cli/status/claude.go` - regex-only `usageRegex` wait 제거 - [ ] `apps/node/internal/adapters/cli/status/claude.go` - parsed status predicate로 `DailyLimit`과 `WeeklyLimit` 모두 대기 - [ ] `apps/node/internal/adapters/cli/status/claude.go` - timeout error에 clean output tail 포함 - [ ] `apps/node/internal/adapters/cli/status/claude.go` - 불필요해진 `regexp` import가 있으면 제거하지 말고 startup regex 사용 여부를 확인해 gofmt/go test로 정리 #### 테스트 작성 `REVIEW_API-2`에서 regression test를 추가하므로 이 항목 자체에는 별도 테스트 파일을 만들지 않는다. #### 중간 검증 ```bash go test ./apps/node/internal/adapters/cli/status/... -run 'TestClaudeChecker' -count=1 ``` 기대 결과: Claude checker 관련 tests가 통과하고 조기 반환 regression이 재현되지 않는다. ### [REVIEW_API-2] 지연 출력 fake TUI로 조기 반환 회귀 테스트 추가 #### 문제 `apps/node/internal/adapters/cli/status/claude_test.go:31-36`의 fake TUI는 session/week block을 즉시 연속 출력한다. 현재처럼 checker가 session만 보고 성공 처리해도 500ms flush 안에 week가 들어와 test가 통과할 수 있어 실제 smoke 누락을 잡지 못한다. Before (`apps/node/internal/adapters/cli/status/claude_test.go:31`): ```sh # Print Claude /usage output printf 'Current session: [████████░░░░░░░░░░░░] 2%% used\n' printf 'Resets 10am (Asia/Seoul)\n' printf '\n' printf 'Current week (all models): [███████████████░░░░░] 50%% used\n' printf 'Resets May 16, 6pm (Asia/Seoul)\n' ``` #### 해결 방법 새 테스트 `TestClaudeCheckerWaitsForDelayedUsageWeek`를 추가한다. fake TUI는 session block을 출력한 뒤 900ms 이상 sleep하고 week block을 출력한다. 현재 구현의 500ms flush 방식이면 `WeeklyLimit`이 비어 실패해야 하며, `REVIEW_API-1` 수정 후 통과해야 한다. After: ```go func TestClaudeCheckerWaitsForDelayedUsageWeek(t *testing.T) { // fake Claude prints Current session, sleeps longer than the old flush, // then prints Current week. } ``` #### 수정 파일 및 체크리스트 - [ ] `apps/node/internal/adapters/cli/status/claude_test.go` - `TestClaudeCheckerWaitsForDelayedUsageWeek` 추가 - [ ] `apps/node/internal/adapters/cli/status/claude_test.go` - 새 test가 `DailyLimit`, `DailyResetTime`, `WeeklyLimit`, `WeeklyResetTime`을 모두 assertion - [ ] `apps/node/internal/adapters/cli/status/claude_test.go` - 기존 `TestClaudeCheckerRequestsUsage`는 유지하되 중복 fixture가 과하면 작은 helper로만 정리 #### 테스트 작성 위 테스트를 반드시 작성한다. timeout은 기존 5초 내로 충분해야 하며 fake script sleep은 900ms 이상으로 둔다. #### 중간 검증 ```bash go test ./apps/node/internal/adapters/cli/status/... -run 'TestClaudeCheckerWaitsForDelayedUsageWeek|TestClaudeCheckerRequestsUsage' -count=1 ``` 기대 결과: 두 테스트가 모두 통과한다. ### [REVIEW_API-3] 실제 bin smoke를 실행하고 Current session/week 양쪽을 검증 #### 문제 `agent-task/cli_claude_usage_status/code_review_cloud_G07_0.log:206-208`은 bin smoke를 SKIP으로 기록했다. 하지만 `command -v claude`가 성공했고, 리뷰 중 같은 smoke를 실행해 실제 `/status` 결과를 얻었다. 계획의 final verification contract와 사용자 요청은 `bin` shell 기반 `/status` 테스트까지 포함한다. #### 해결 방법 후속 구현 완료 후 실제 bin smoke를 실행하고 `CODE_REVIEW-cloud-G07.md`에 stdout/stderr를 붙인다. `rg` 검증은 기존처럼 session/week 중 하나만 있어도 통과하는 pattern이 아니라, 두 개 명령으로 `Current session`과 `Current week`를 각각 요구한다. #### 수정 파일 및 체크리스트 - [ ] `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G07.md` - bin smoke 실제 출력 기록 - [ ] `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G07.md` - `command -v claude` 실패 시에만 SKIP 사유 기록 - [ ] `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G07.md` - smoke output에 `Current session`과 `Current week` 양쪽 line이 있음을 별도 `rg` 출력으로 기록 #### 테스트 작성 별도 Go test는 작성하지 않는다. 이 항목은 실제 bin smoke 검증 신뢰 회복이 목적이다. #### 중간 검증 ```bash command -v claude ``` 기대 결과: Claude binary path가 출력된다. 실패하면 bin smoke를 실행하지 않고 실패 stdout/stderr를 review file에 기록한다. ## 수정 파일 요약 | 파일 | 항목 | |------|------| | `apps/node/internal/adapters/cli/status/claude.go` | REVIEW_API-1 | | `apps/node/internal/adapters/cli/status/claude_test.go` | REVIEW_API-2 | | `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G07.md` | REVIEW_API-3 | ## 최종 검증 ```bash go test ./apps/node/internal/adapters/cli/status/... -count=1 go test ./apps/edge/cmd/edge -count=1 gofmt -l apps/node/internal/adapters/cli/status/claude.go apps/node/internal/adapters/cli/status/claude_test.go git diff --check command -v claude ``` 기대 결과: Go tests가 통과하고 `gofmt -l` 출력이 비어 있으며 `git diff --check`가 성공한다. `command -v claude`는 bin smoke 전제 확인이다. ```bash tmpdir="$(mktemp -d /tmp/iop-claude-status.XXXXXX)" mkfifo "$tmpdir/edge.in" ./bin/edge.sh < "$tmpdir/edge.in" > "$tmpdir/edge.out" 2>&1 & edge_pid=$! exec 9>"$tmpdir/edge.in" sleep 2 ./bin/node.sh > "$tmpdir/node.out" 2>&1 & node_pid=$! sleep 5 printf '/status\n/exit\n' >&9 timeout 120 bash -c 'while kill -0 "$0" 2>/dev/null; do sleep 1; done' "$edge_pid" || true kill "$node_pid" "$edge_pid" 2>/dev/null || true exec 9>&- sed -n '1,220p' "$tmpdir/edge.out" sed -n '1,220p' "$tmpdir/node.out" rg --sort path -n 'Current session: [0-9]+% remaining .*resets|Daily limit: [0-9]+% remaining .*resets' "$tmpdir/edge.out" rg --sort path -n 'Current week: [0-9]+% remaining .*resets|Weekly limit: [0-9]+% remaining .*resets' "$tmpdir/edge.out" ``` 기대 결과: `edge.out`에 `[edge] sent command=status ... target=claude`, `[node-...-status] target=claude`, `Current session` remaining/reset line, `Current week` remaining/reset line이 모두 출력된다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 전체 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.