254 lines
13 KiB
Text
254 lines
13 KiB
Text
<!-- task=cli_claude_usage_status plan=4 tag=REVIEW_REVIEW_REVIEW_REVIEW_API -->
|
|
|
|
# Claude Usage Status TUI State Recovery Plan
|
|
|
|
## 이 파일을 읽는 구현 에이전트에게
|
|
|
|
**구현 완료 후 `CODE_REVIEW-*-G??.md`의 모든 섹션을 채우는 것이 필수 최종 단계입니다. 이 파일을 채우기 전까지 작업은 완료가 아닙니다.** 아래 체크리스트와 중간/최종 검증을 실제로 실행하고, 구현 내용과 명령 출력을 `CODE_REVIEW-cloud-G08.md`에 기록하세요. `CODE_REVIEW-cloud-G08.md`의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시(`*.log` 변경, `complete.log` 작성)는 구현 에이전트가 수행하지 않습니다.
|
|
|
|
## 배경
|
|
|
|
Cloud-G07 후속 구현은 단위 테스트와 한 번의 smoke 성공을 기록했지만, 리뷰 재실행에서 같은 실패 감지형 smoke가 다시 실패했다. 실패 tail은 `d to day · w to week`, `Extra usage`, `Esc to cancel` 상태에서 60초 timeout이었다. 이번 라운드는 timeout 증가나 happy-path fixture 추가가 아니라, 현재 Claude `/usage` TUI state를 실제로 관측하고 day/week/session view를 안정적으로 수집하는 구현으로 전환한다.
|
|
|
|
## 분석 결과
|
|
|
|
### 읽은 파일
|
|
|
|
- `agent-ops/skills/common/code-review/SKILL.md`
|
|
- `agent-task/cli_claude_usage_status/plan_cloud_G07_3.log`
|
|
- `agent-task/cli_claude_usage_status/code_review_cloud_G07_3.log`
|
|
- `apps/node/internal/adapters/cli/status/claude.go`
|
|
- `apps/node/internal/adapters/cli/status/parser.go`
|
|
- `apps/node/internal/adapters/cli/status/parser_test.go`
|
|
- `apps/node/internal/adapters/cli/status/claude_test.go`
|
|
- `apps/node/internal/adapters/cli/status/status.go`
|
|
- `apps/edge/cmd/edge/console.go`
|
|
- `apps/edge/cmd/edge/console_test.go`
|
|
- `configs/edge.yaml`
|
|
|
|
### 테스트 커버리지 공백
|
|
|
|
- `TestClaudeCheckerSelectsUsageAndParsesRepaintedScreen`는 loading screen 뒤 바로 `Current session`/`Current week` block이 출력되는 성공 경로만 검증한다.
|
|
- 실제 실패 화면은 `d to day · w to week`, `Extra usage not enabled`, `Esc to cancel`에서 멈추며, 현재 tests는 이 persistent usage-state failure를 재현하지 못한다.
|
|
- 최종 smoke는 한 번 성공한 기록이 있었지만 리뷰 재실행에서 실패했다. 재현성 판단을 위해 최소 2회 연속 성공 검증이 필요하다.
|
|
|
|
### 심볼 참조
|
|
|
|
- renamed/removed symbol 없음.
|
|
- 참조 확인 대상: `ClaudeChecker`, `ParseStatusOutput`, `cleanANSI`, `claudeSessionRegex`, `claudeWeekRegex`, `TestClaudeCheckerSelectsUsageAndParsesRepaintedScreen`, `TestParseStatusOutput_ClaudeUsageFromRepaintedScreen`.
|
|
|
|
### 범위 결정 근거
|
|
|
|
- `proto/**`, runtime command routing, edge-node transport schema는 수정하지 않는다.
|
|
- `apps/edge/cmd/edge` 출력 포맷은 이미 metadata label을 표시할 수 있으므로 핵심 범위가 아니다. 단, 최종 smoke 출력 때문에 edge console tests는 유지한다.
|
|
- `configs/edge.yaml`의 `target: claude`는 smoke 전제다. 설정 변경은 새로 하지 않는다.
|
|
- `agent-ops/skills/**` 변경은 이전 사용자 요청의 라우팅 정책 수정이며 이번 구현 범위가 아니다.
|
|
|
|
### 빌드 등급
|
|
|
|
- build lane: `cloud-G08` - cloud-G07에서도 real smoke가 재현 실패했고, terminal-agent/TUI/PTY 상태 전이 판단이 핵심이다.
|
|
- review lane: `cloud-G08` - 반복 verification trust 실패와 실제 smoke 재현성이 핵심이다.
|
|
|
|
## 의존 관계 및 구현 순서
|
|
|
|
1. `REVIEW_REVIEW_REVIEW_REVIEW_API-1`에서 실제 실패 화면의 full raw/sanitized/visible state를 확보하고 원인을 구분한다.
|
|
2. `REVIEW_REVIEW_REVIEW_REVIEW_API-2`에서 checker/parser를 실제 day/week usage state에 맞게 수정한다.
|
|
3. `REVIEW_REVIEW_REVIEW_REVIEW_API-3`에서 실패 형태를 재현하는 tests를 추가한다.
|
|
4. `REVIEW_REVIEW_REVIEW_REVIEW_API-4`에서 2회 연속 smoke로 검증 신뢰를 회복한다.
|
|
|
|
### [REVIEW_REVIEW_REVIEW_REVIEW_API-1] 실제 실패 화면 full state 진단
|
|
|
|
#### 문제
|
|
|
|
`apps/node/internal/adapters/cli/status/claude.go:122-127`의 timeout error는 last 12 lines만 보여준다. 리뷰 smoke 실패 tail은 `d to day · w to week`, `Extra usage`, `Esc to cancel`만 보여 실제 usage data가 화면 위에 있었는지, 다른 pane에 있었는지, parser label이 다른지 판단할 수 없다.
|
|
|
|
#### 해결 방법
|
|
|
|
- repo 밖 `/tmp`에 임시 probe를 두고 실제 `claude` PTY raw, `cleanANSI` output, 가능한 visible-screen output을 저장한다.
|
|
- 최종 코드에는 임시 probe, debug file write, hard-coded actual reset time을 남기지 않는다.
|
|
- 진단 결과를 `CODE_REVIEW-cloud-G08.md`에 요약한다. 최소한 아래를 구분한다.
|
|
- `/usage` 입력이 결과 view까지 도달했는지
|
|
- default usage view가 `Current session`/`Current week`를 숨기고 `d`/`w` navigation을 요구하는지
|
|
- reset line label과 percentage label이 현재 regex와 다른지
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `/tmp` probe 또는 임시 local command로 raw/sanitized output 확보
|
|
- [ ] repository에 probe/debug artifact가 남지 않음
|
|
- [ ] `CODE_REVIEW-cloud-G08.md`에 실제 실패 state와 결론 기록
|
|
|
|
#### 테스트 작성
|
|
|
|
별도 Go test는 `REVIEW_REVIEW_REVIEW_REVIEW_API-3`에서 작성한다.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
find . -maxdepth 3 \( -name '*diag*' -o -name '*raw*' -o -name '*probe*' \) -not -path './.git/*' -print
|
|
```
|
|
|
|
기대 결과: repo 안에 진단 artifact가 없다.
|
|
|
|
### [REVIEW_REVIEW_REVIEW_REVIEW_API-2] Claude `/usage` TUI state machine 수정
|
|
|
|
#### 문제
|
|
|
|
`apps/node/internal/adapters/cli/status/claude.go:112-136`은 `/usage` 이후 같은 buffer에서 `Current session`과 `Current week`가 나오기만 기다린다. 실제 실패 state는 usage screen 내부에 있지만 필요한 data pane이 아직 선택되지 않았거나 parser가 현재 label을 모르고 있다.
|
|
|
|
#### 해결 방법
|
|
|
|
- 실제 진단 결과에 따라 아래 중 하나 또는 둘 다 구현한다.
|
|
- view navigation이 필요한 경우: `/usage` 진입 후 `d`/`w` 등 실제 shortcut을 sentinel 기반으로 보내 day/session과 week data를 각각 수집한다.
|
|
- label/parser 문제인 경우: `ParseStatusOutput`이 현재 Claude usage labels를 synthetic fixture로 파싱하도록 보강한다.
|
|
- checker는 "usage shell에 들어갔다"와 "필수 data를 얻었다"를 구분해야 한다. `Extra usage`/`Esc to cancel`만으로 성공 대기 상태로 보지 않는다.
|
|
- timeout error는 민감값을 과도하게 노출하지 않는 선에서 full failure state를 리뷰 가능하게 남긴다. 최소 tail 12줄만으로 원인 판단이 안 되면 sanitized excerpt를 더 넓힌다.
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `apps/node/internal/adapters/cli/status/claude.go` - usage view 진입과 data 수집을 분리한 state machine 구현
|
|
- [ ] `apps/node/internal/adapters/cli/status/claude.go` - 필요 시 `d`/`w` navigation을 sentinel 기반으로 수행
|
|
- [ ] `apps/node/internal/adapters/cli/status/parser.go` - 실제 현재 Claude labels/parser format 보강
|
|
- [ ] `apps/node/internal/adapters/cli/status/parser.go` - Codex parsing 회귀 없음
|
|
- [ ] 임시 sleep/timeout 증가만으로 문제를 덮지 않음
|
|
|
|
#### 테스트 작성
|
|
|
|
`REVIEW_REVIEW_REVIEW_REVIEW_API-3`에서 fake TUI tests를 추가한다.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
go test ./apps/node/internal/adapters/cli/status/... -run 'TestClaudeChecker|TestParseStatusOutput_Claude|TestParseStatusOutput_CodexLimits' -count=1 -v
|
|
```
|
|
|
|
기대 결과: checker/parser tests와 Codex parser regression이 fresh 실행으로 통과한다.
|
|
|
|
### [REVIEW_REVIEW_REVIEW_REVIEW_API-3] 현재 실패 형태 regression tests 추가
|
|
|
|
#### 문제
|
|
|
|
`apps/node/internal/adapters/cli/status/claude_test.go:128`와 `apps/node/internal/adapters/cli/status/parser_test.go:105`는 현재 실패 tail을 재현하지 못한다. 그래서 단위 테스트가 통과해도 real smoke가 실패한다.
|
|
|
|
#### 해결 방법
|
|
|
|
- fake Claude TUI가 `/usage` 이후 `d to day · w to week`, `Extra usage`, `Esc to cancel` state를 먼저 그리고, 구현이 필요한 shortcut 또는 parsing path를 수행해야만 session/week data를 얻도록 만든다.
|
|
- parser fixture는 실제 진단에서 확인한 label/spacing/control sequence를 synthetic 값으로 축약한다.
|
|
- test assertion은 `DailyLimit`, `DailyResetTime`, `WeeklyLimit`, `WeeklyResetTime`, metadata label을 모두 확인한다.
|
|
- `TestClaudeCheckerNotUsageExits`처럼 err nil을 허용하는 test는 이번 성공조건으로 인정하지 않는다.
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `apps/node/internal/adapters/cli/status/claude_test.go` - `TestClaudeCheckerNavigatesUsageDayWeekViews` 또는 동등 test 추가
|
|
- [ ] `apps/node/internal/adapters/cli/status/parser_test.go` - 실제 실패 state에서 파생한 parser fixture 추가
|
|
- [ ] 기존 happy-path repaint fixture 유지
|
|
- [ ] Codex parser test 유지
|
|
|
|
#### 테스트 작성
|
|
|
|
필수 test names:
|
|
|
|
- `TestClaudeCheckerNavigatesUsageDayWeekViews`
|
|
- `TestParseStatusOutput_ClaudeUsageCurrentRealState`
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
go test ./apps/node/internal/adapters/cli/status/... -run 'TestClaudeCheckerNavigatesUsageDayWeekViews|TestParseStatusOutput_ClaudeUsageCurrentRealState|TestParseStatusOutput_CodexLimits' -count=1 -v
|
|
```
|
|
|
|
기대 결과: 현재 실패 형태 regression tests와 Codex parser test가 fresh 실행으로 통과한다.
|
|
|
|
### [REVIEW_REVIEW_REVIEW_REVIEW_API-4] 2회 연속 smoke 검증 신뢰성 회복
|
|
|
|
#### 문제
|
|
|
|
한 번의 smoke 성공 기록 후 리뷰 재실행에서 다시 실패했다. 이 작업은 실제 Claude TUI/PTY timing에 민감하므로 단발성 성공을 완료로 인정하면 안 된다.
|
|
|
|
#### 해결 방법
|
|
|
|
- 최종 smoke를 2회 연속 실행한다.
|
|
- 각 run마다 아래 조건을 모두 만족해야 한다.
|
|
- `node reported error`, `timeout waiting`, `raw output did not include parsed limits`가 없어야 한다.
|
|
- `[edge] sent command=status ... target=claude`가 있어야 한다.
|
|
- `[node-...-status] target=claude`가 있어야 한다.
|
|
- `Current session` 또는 `Daily limit` remaining/reset line이 있어야 한다.
|
|
- `Current week` 또는 `Weekly limit` remaining/reset line이 있어야 한다.
|
|
- 실패하면 `REVIEW_REVIEW_REVIEW_REVIEW_API-4`는 미완료로 기록한다.
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G08.md` - 2회 smoke stdout/stderr 기록
|
|
- [ ] `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G08.md` - 각 run의 tmpdir과 `rg` output 기록
|
|
- [ ] smoke 후 orphan edge/node process 없음 확인
|
|
|
|
#### 테스트 작성
|
|
|
|
별도 Go test 없음.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
command -v claude
|
|
```
|
|
|
|
기대 결과: 실제 Claude binary path가 출력된다.
|
|
|
|
## 수정 파일 요약
|
|
|
|
| 파일 | 항목 |
|
|
|------|------|
|
|
| `apps/node/internal/adapters/cli/status/claude.go` | REVIEW_REVIEW_REVIEW_REVIEW_API-2 |
|
|
| `apps/node/internal/adapters/cli/status/parser.go` | REVIEW_REVIEW_REVIEW_REVIEW_API-2 |
|
|
| `apps/node/internal/adapters/cli/status/claude_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_API-3 |
|
|
| `apps/node/internal/adapters/cli/status/parser_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_API-3 |
|
|
| `agent-task/cli_claude_usage_status/CODE_REVIEW-cloud-G08.md` | REVIEW_REVIEW_REVIEW_REVIEW_API-1, REVIEW_REVIEW_REVIEW_REVIEW_API-4 |
|
|
|
|
## 최종 검증
|
|
|
|
```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/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
|
|
command -v claude
|
|
```
|
|
|
|
기대 결과: Go tests가 fresh 실행으로 통과하고 `gofmt -l` 출력이 비어 있으며 `git diff --check`가 성공한다.
|
|
|
|
```bash
|
|
set -euo pipefail
|
|
for run in 1 2; do
|
|
tmpdir="$(mktemp -d /tmp/iop-claude-status.run${run}.XXXXXX)"
|
|
echo "run=$run tmpdir=$tmpdir"
|
|
mkfifo "$tmpdir/edge.in"
|
|
./bin/edge.sh < "$tmpdir/edge.in" > "$tmpdir/edge.out" 2>&1 &
|
|
edge_pid=$!
|
|
cleanup() {
|
|
kill "${node_pid:-}" "$edge_pid" 2>/dev/null || true
|
|
exec 9>&- 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
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 150 bash -c 'while kill -0 "$0" 2>/dev/null; do sleep 1; done' "$edge_pid"
|
|
kill "$node_pid" "$edge_pid" 2>/dev/null || true
|
|
exec 9>&-
|
|
trap - EXIT
|
|
sed -n '1,260p' "$tmpdir/edge.out"
|
|
sed -n '1,220p' "$tmpdir/node.out"
|
|
! rg --sort path -n 'node reported error|timeout waiting|raw output did not include parsed limits' "$tmpdir/edge.out"
|
|
rg --sort path -n '\[edge\] sent command=status .*target=claude' "$tmpdir/edge.out"
|
|
rg --sort path -n '\[node-.*-status\] target=claude' "$tmpdir/edge.out"
|
|
rg --sort path -n 'Current session: [0-9]+(\.[0-9]+)?% remaining \(resets .+\)|Daily limit: [0-9]+(\.[0-9]+)?% remaining \(resets .+\)' "$tmpdir/edge.out"
|
|
rg --sort path -n 'Current week: [0-9]+(\.[0-9]+)?% remaining \(resets .+\)|Weekly limit: [0-9]+(\.[0-9]+)?% remaining \(resets .+\)' "$tmpdir/edge.out"
|
|
done
|
|
ps -ef | rg './bin/(edge|node)\.sh|go run ./apps/(edge|node)' || true
|
|
```
|
|
|
|
기대 결과: 두 run 모두 negative error check가 매칭 없이 성공하고, positive `rg` 4개가 각각 matching line을 출력한다. 이 command가 non-zero면 작업은 미완료다.
|
|
|
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 전체 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|