<!-- task=m-agent-readable-repository-refactor/06_cli_adapter plan=1 tag=REVIEW_REFACTOR_ADAPTER_CLI -->

# CLI adapter 책임 분해·검증 신뢰 복구 후속 계획

## 이 파일을 읽는 구현 에이전트에게

검증 실행과 active `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션 작성이 구현의 마지막 단계다. 실제 구현 내용, 계획 대비 변경, 설계 결정, 명령별 stdout/stderr를 채운 뒤 active 파일을 그대로 두고 리뷰 준비 완료를 보고한다. 최종 판정, log rename, `complete.log`, task archive는 code-review 전용이다.

선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 review stub의 `사용자 리뷰 요청`에 연결 대상과 근거를 기록하고 중단한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`를 만들지 않는다. 외부 CLI/로그인/secret/service 준비, 일반 범위 충돌, 반복 실패, 검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 blocker 또는 다음 follow-up 근거다.

## 배경

이전 구현은 package/Node 회귀를 통과했지만 계획한 command construction과 대형 output/session 책임 분해를 끝내지 못했다. 전역 readability baseline 재생성과 현재 소스에 없는 테스트 이름의 출력 때문에 검증 신뢰도도 회복해야 한다. 외부 CLI adapter 실행 경로이므로 구조 수정 뒤 실제 profile별 full-cycle 또는 정확한 blocker까지 한 루프에서 확인한다.

## 사용자 리뷰 요청 흐름

구현 에이전트는 선택된 Milestone lock 결정만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식의 active review `사용자 리뷰 요청`에 기록한다. 직접 사용자 prompt는 금지하며, code-review가 요청의 정당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-agent-readable-repository-refactor/06_cli_adapter/plan_cloud_G08_0.log`
- Prior review: `agent-task/m-agent-readable-repository-refactor/06_cli_adapter/code_review_cloud_G08_0.log`
- Verdict: `FAIL`
- Issue summary: Required 5, Suggested 0, Nit 0.
- Required:
  - lifecycle 세 경로가 공통 command builder를 사용하지 않고 dead helper가 남음.
  - emitter/filter/session 파일이 미분해 또는 증가했고 `opencodeSSERun.handleEvent` 92줄 위반이 새로 생김.
  - 전역 baseline 재생성이 HEAD 대비 CLI readability 회귀 4건을 허용함.
  - review에 기록된 targeted test 이름/출력이 현재 소스와 불일치함.
  - `claude,antigravity,codex,opencode` real CLI full-cycle 또는 profile별 blocker 증거가 없음.
- Affected files: `apps/node/internal/adapters/cli/{command.go,codex_app_server.go,emitters.go,opencode_sse.go,persistent.go,persistent_output_filter.go}`, 분해 후 같은 package의 새 책임 파일, 관련 기존 tests, `scripts/readability_baseline.json`, active review stub.
- Reviewer verification: fresh CLI/status와 `./apps/node/...` 테스트 PASS, gofmt/diff check PASS, 현재 수정 baseline 기준 audit PASS. `HEAD:scripts/readability_baseline.json` 기준 전체 23건 중 CLI 회귀 4건. preflight에서 `claude`, `codex`, `opencode` 발견, `antigravity` 미발견.
- 추가 세부 문맥이 필요할 때만 위 두 prior log를 좁게 읽는다. `agent-task/archive/**`를 탐색하지 않는다.
- Roadmap carryover: 없음. 이전 plan에 `Roadmap Targets`가 없었으며 이 follow-up도 특정 Milestone Task 완료 체크를 주장하지 않는다.

## 분석 결과

### 읽은 파일

- 규칙/계약/스펙:
  - `agent-ops/rules/project/domain/node/rules.md`
  - `agent-ops/rules/project/domain/testing/rules.md`
  - `agent-ops/rules/project/domain/testing/node-smoke.md`
  - `agent-ops/rules/project/domain/testing/testing-smoke.md`
  - `agent-test/local/rules.md`
  - `agent-test/local/node-smoke.md`
  - `agent-test/local/testing-smoke.md`
  - `agent-spec/runtime/edge-node-execution.md`
  - `agent-contract/inner/edge-node-runtime-wire.md`
  - `agent-ops/skills/project/e2e-smoke/SKILL.md`
- source/call site:
  - `apps/node/internal/adapters/cli/cli.go`
  - `apps/node/internal/adapters/cli/profile.go`
  - `apps/node/internal/adapters/cli/command.go`
  - `apps/node/internal/adapters/cli/workspace.go`
  - `apps/node/internal/adapters/cli/oneshot.go`
  - `apps/node/internal/adapters/cli/emitters.go`
  - `apps/node/internal/adapters/cli/codex_app_server.go`
  - `apps/node/internal/adapters/cli/opencode_sse.go`
  - `apps/node/internal/adapters/cli/persistent.go`
  - `apps/node/internal/adapters/cli/persistent_output_filter.go`
  - `apps/node/internal/adapters/cli/status/parser.go`
  - `apps/node/internal/adapters/cli/status/screen.go`
  - `apps/node/internal/adapters/config_set.go`
  - `scripts/readability_audit.py`
  - `scripts/readability_baseline.json`
  - `scripts/readability_read_sets.json`
- tests:
  - `apps/node/internal/adapters/cli/cli_workspace_test.go`
  - `apps/node/internal/adapters/cli/cli_session_test.go`
  - `apps/node/internal/adapters/cli/cli_emitters_test.go`
  - `apps/node/internal/adapters/cli/oneshot_blackbox_test.go`
  - `apps/node/internal/adapters/cli/codex_app_server_events_test.go`
  - `apps/node/internal/adapters/cli/codex_app_server_session_test.go`
  - `apps/node/internal/adapters/cli/opencode_sse_internal_test.go`
  - `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`
  - `apps/node/internal/adapters/cli/persistent_completion_test.go`
  - `apps/node/internal/adapters/cli/persistent_process_test.go`
  - `apps/node/internal/adapters/cli/persistent_terminal_test.go`
  - `apps/node/internal/adapters/cli/persistent_output_filter_test.go`
  - `apps/node/internal/adapters/cli/status/parser_test.go`

### SDD 기준

Milestone 문서에 `SDD: 불필요`로 기록된 adapter runtime 계약 보존형 내부 리팩터링이다. 새 제품 동작이나 외부 wire schema를 정의하지 않고 기존 edge-node execution 및 runtime wire 계약을 보존한다.

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`를 읽었고 라우팅된 `agent-test/local/node-smoke.md`, `agent-test/local/testing-smoke.md`를 적용한다.
- fresh 실행이 중요하므로 Go test cache 결과를 허용하지 않고 모든 필수 Go 명령에 `-count=1`을 쓴다.
- package/Node 회귀, readability ratchet, `git diff --check`를 local deterministic 검증으로 사용한다.
- 사용자 실행 pipeline 변경 검증은 `e2e-smoke`의 `mode=real-cli`, `profiles=claude,antigravity,codex,opencode`를 적용한다. 같은 session 메시지 2회, start/message-or-delta/complete 순서, `/sessions`, persistent profile의 terminate, 지원 여부를 포함한 `/status` 결과를 profile별 기록한다.

테스트 환경 프리플라이트:

- runner/workdir: 현재 local runner, `/config/workspace/iop`.
- branch/HEAD/dirty state: 구현 시작과 검증 직전에 `git branch --show-current`, `git rev-parse HEAD`, `git status --short` 실제 출력을 기록한다. 이 shared worktree의 다른 subtask 변경은 되돌리거나 baseline으로 흡수하지 않는다.
- source sync: 현재 worktree source를 직접 빌드·실행하며 stale binary를 재사용하지 않는다. `scripts/dev/edge.sh`, `scripts/dev/node.sh`는 executable 여부와 `--help` 또는 script entry를 확인한다.
- binaries: `command -v claude antigravity codex opencode`로 profile별 경로를 기록한다. reviewer 시점에는 `claude`, `codex`, `opencode`가 있고 `antigravity`가 없다.
- config: tracked `configs/*.yaml`을 수정하지 않고 `/tmp`의 임시 config 또는 환경 override를 사용한다. 실제 임시 경로와 override를 review에 기록한다.
- runtime/network: 충돌 없는 임시 localhost port를 사용한다. 외부 host/login/provider 상태는 각 CLI 실제 실행으로 확인하며 실패 시 명령, exit status, stderr를 profile별 blocker로 남긴다.
- OS/arch: local runner의 `uname -srm`을 기록한다.
- 누락된 CLI를 검증 중 다운로드하거나 repo 안에 도구를 생성하지 않는다.

### 테스트 커버리지 공백

- args/env/cwd 보존: oneshot 및 각 session cwd 테스트가 있으나 `buildCmdWithoutContext` 자체는 미호출이라 직접 경계 테스트가 없다. 호출부 전환과 함께 새 단위 테스트 1개를 추가한다.
- Codex app-server session/process/event: 기존 events/session tests가 ordering, resume, cwd, terminate를 검증한다.
- OpenCode SSE session/event/finalize: 기존 internal/blackbox tests가 session reuse, event decode, cancel/error를 검증한다.
- persistent process/terminal/filter: 기존 completion/process/terminal/filter tests가 prompt, output, cancel, idle/exit를 검증한다.
- emitter/status parser: 기존 emitter/parser tests가 JSON event와 ANSI/status parsing을 검증한다.
- real provider integration: 자동 Go tests로 대체할 수 없고 이전 review에 증거가 없다. e2e-smoke real CLI 결과 또는 정확한 profile blocker가 필수다.

### 심볼 참조

- 새 public symbol, rename, remove 없음.
- `buildCmdWithoutContext`: `apps/node/internal/adapters/cli/command.go:36` 정의만 있고 호출자가 없다.
- `exec.Command` lifecycle call sites: `codex_app_server.go:422`, `opencode_sse.go:280`, `persistent.go:436`.
- 분해 시 함수/타입은 같은 `cli` package에 유지해 import/API call site 변경을 만들지 않는다.

### 분할 판단

split decision policy를 먼저 평가했다. 이번 follow-up은 이미 split된 `06_cli_adapter`의 미완료 단일 package refactor를 복구하는 작업이다. helper와 lifecycle call site가 같은 package 내부에서 함께 바뀌므로 foundation/call-site를 다시 나누면 dead 중간 상태가 된다. emitter/filter/session 분해와 baseline 회복은 동일 audit 결과에 묶이고, 실제 CLI verification은 구조 변경 후에만 의미가 있어 독립 완료 단위가 아니다. 여러 domain/public API 경계나 별도 predecessor도 없으며, 추가 subtask는 shared dirty worktree와 검증 증거를 중복시킨다. 따라서 현재 subtask 안의 단일 follow-up plan이 더 안전하다.

### 범위 결정 근거

- 외부 command args, env 합성 순서, workspace validation/error text, login 정책, logical session key/resume, cancellation/termination, event ordering, filtering/status 결과를 변경하지 않는다.
- `apps/node/internal/adapters/cli/status/{parser.go,screen.go}`는 이전 분해가 이미 파일 threshold 아래이고 새 회귀가 없어 동작 수정 대상에서 제외한다. 관련 tests는 회귀로 실행한다.
- `scripts/readability_read_sets.json`의 다른 subtask 변경과 baseline의 비-CLI identity를 수정하지 않는다.
- config/contract/spec/roadmap 문서를 수정하지 않는다. 이 follow-up은 prior plan처럼 `Roadmap Targets`를 갖지 않는다.
- 새 package나 dependency를 추가하지 않는다.

### 빌드 등급

`cloud-G08`. 5개 Required와 여러 process/session/output 파일을 함께 보며 실제 외부 CLI profile의 interactive full-cycle 및 검증 신뢰 복구가 필요하다.

## 구현 체크리스트

- [ ] 공통 command builder를 Codex app-server, OpenCode server, non-terminal persistent lifecycle에서 실제 사용하고 args/env/cwd/error 보존 테스트를 추가한다.
- [ ] emitter/filter/session 파일을 변경 이유별 파일과 단계 함수로 분해해 HEAD baseline 대비 CLI readability 회귀를 0건으로 만든다.
- [ ] readability baseline의 CLI identity만 ratchet 규칙에 맞게 복구하고 비-CLI 병렬 변경을 보존한다.
- [ ] 실제 존재하는 테스트 이름으로 fresh targeted/package/Node 검증을 실행하고 stdout/stderr를 그대로 기록한다.
- [ ] `e2e-smoke` real CLI full-cycle을 `claude,antigravity,codex,opencode`별로 수행하거나 명령·exit status·stderr가 포함된 정확한 blocker를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REFACTOR_ADAPTER_CLI-1] command builder를 lifecycle에 적용

문제: `command.go:36-49`의 `buildCmdWithoutContext`는 사용되지 않고 `codex_app_server.go:420-432`, `opencode_sse.go:278-296`, `persistent.go:387-442`가 command/env/cwd를 다시 조립한다. 계획의 construction/execution 분리가 facade/oneshot에만 적용됐다.

해결 방법:

Before (`apps/node/internal/adapters/cli/codex_app_server.go:420-432`):

```go
func startCodexAppServerProc(ctx context.Context, profile config.CLIProfileConf, workspace string) (*codexAppServerProc, error) {
	args := append(append([]string{}, profile.Args...), "--stdio")
	cmd := exec.Command(profile.Command, args...)
	if len(profile.Env) > 0 {
		cmd.Env = append(cmd.Environ(), profile.Env...)
	}
	dir, err := prepareWorkspaceDir(workspace)
	// ...
}
```

After:

```go
func startCodexAppServerProc(ctx context.Context, profile config.CLIProfileConf, workspace string) (*codexAppServerProc, error) {
	args := append(append([]string{}, profile.Args...), "--stdio")
	cmd, err := buildCmdWithoutContext(profile.Command, args, profile.Env, workspace)
	if err != nil {
		return nil, err
	}
	// pipe/start lifecycle only
}
```

OpenCode는 `opts.Dir` 우선 workspace를 builder 입력으로 넘기고, persistent non-terminal branch도 이미 검증한 `dir` 또는 원 workspace 중 하나만 일관되게 넘긴다. context를 실제 subprocess 종료에 연결하지 않는 기존 session semantics는 유지한다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/cli/command.go`: 중복 없이 context/non-context builder의 공통 env/cwd 적용을 유지한다.
- [ ] `apps/node/internal/adapters/cli/codex_app_server.go`: `--stdio` args를 보존하고 builder 결과에 pipe/start만 적용한다.
- [ ] `apps/node/internal/adapters/cli/opencode_sse.go`: `serve --hostname 127.0.0.1 --port 0`, `opts.Dir` 우선순위를 보존한다.
- [ ] `apps/node/internal/adapters/cli/persistent.go`: non-terminal process만 builder를 사용하고 terminal core 경로는 변경하지 않는다.
- [ ] `apps/node/internal/adapters/cli/cli_workspace_test.go`: 아래 새 테스트를 추가한다.

테스트 작성: 작성한다. `apps/node/internal/adapters/cli/cli_workspace_test.go`에 `TestBuildCmdWithoutContextAppliesEnvAndCwd`를 추가해 temp workspace, 추가 env, command/args, invalid workspace error를 검증한다. 기존 `TestCodexAppServerProcCwd`, `TestOpencodeLocalServerCwd`, `TestCLIPersistentNonTerminalCwd` assertions는 제거하지 않는다.

중간 검증:

```bash
rg -n --sort path 'exec\.Command\(' apps/node/internal/adapters/cli/{codex_app_server.go,opencode_sse.go,persistent.go}
rg -n --sort path '\bbuildCmdWithoutContext\b' apps/node/internal/adapters/cli
GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 ./apps/node/internal/adapters/cli -run 'TestBuildCmdWithoutContextAppliesEnvAndCwd|TestCodexAppServerProcCwd|TestOpencodeLocalServerCwd|TestCLIPersistentNonTerminalCwd'
```

첫 `rg`는 출력이 없어야 하고, 둘째는 정의와 세 lifecycle 호출을 보여야 하며, fresh 테스트가 PASS해야 한다.

### [REVIEW_REFACTOR_ADAPTER_CLI-2] output/session 책임과 long function 분해

문제: `emitters.go:18-509`와 `persistent_output_filter.go:12-701`이 여러 profile 책임을 계속 소유한다. `codex_app_server.go`, `opencode_sse.go`, `persistent.go`는 HEAD보다 각각 31/29/53줄 늘었고 `opencode_sse.go:500-590`의 `handleEvent`는 새 92줄 위반이다.

해결 방법:

Before (`apps/node/internal/adapters/cli/opencode_sse.go:500-590`):

```go
func (r *opencodeSSERun) handleEvent(ev opencodeEnvelope, sidStatus string) (bool, error) {
	switch ev.Type {
	case "session.next.text.delta":
		// emit/trace
	case "message.part.updated":
		// part/token state
	// all event kinds in one function
	}
}
```

After:

```go
func (r *opencodeSSERun) handleEvent(ev opencodeEnvelope, sidStatus string) (bool, error) {
	handler := opencodeEventHandlers[ev.Type]
	if handler == nil {
		r.addTrace(sseEvtTrace{Type: ev.Type, SID: sidStatus, Outcome: "skip"})
		return false, nil
	}
	return handler(r, ev, sidStatus)
}
```

같은 package 안에서 다음 책임 파일로 이동한다: Codex proc/read/send는 `codex_app_server_process.go`, OpenCode envelope/event decode/handlers는 `opencode_sse_events.go`, persistent process start/drain/stop은 `persistent_process.go`, stream-json emitter는 `emitter_stream_json.go`, profile JSON emitters는 `emitter_profile_json.go`, terminal passthrough filter는 `persistent_output_filter_terminal.go`, Claude filter/parser는 `persistent_output_filter_claude.go`. 원 파일은 registry/facade/session orchestration만 남긴다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/cli/{codex_app_server.go,codex_app_server_process.go}`: executor/session orchestration과 proc transport를 분리한다.
- [ ] `apps/node/internal/adapters/cli/{opencode_sse.go,opencode_sse_events.go}`: session/HTTP orchestration과 event decode/handler를 분리하고 함수 warning을 없앤다.
- [ ] `apps/node/internal/adapters/cli/{persistent.go,persistent_process.go}`: run state와 process lifecycle/drain/stop을 분리한다.
- [ ] `apps/node/internal/adapters/cli/{emitters.go,emitter_stream_json.go,emitter_profile_json.go}`: registry/driver와 emitter 구현을 분리한다.
- [ ] `apps/node/internal/adapters/cli/{persistent_output_filter.go,persistent_output_filter_terminal.go,persistent_output_filter_claude.go}`: interface/factory, terminal passthrough, Claude parser를 분리한다.
- [ ] moved symbol 이름과 package-private behavior를 유지하고 wrapper/dead helper를 남기지 않는다.

테스트 작성: 새 behavior가 없는 내부 이동이므로 새 test file은 작성하지 않는다. 기존 emitter, Codex app-server, OpenCode SSE, persistent process/terminal/filter tests가 각 이동 경계를 이미 검증한다. 기존 test 이름/fixture/assertion은 삭제하거나 완화하지 않는다.

중간 검증:

```bash
GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 ./apps/node/internal/adapters/cli -run 'Emitter|CodexAppServer|Opencode|Persistent|Terminal|OutputFilter|Resume|Cancel'
python3 scripts/readability_audit.py --input-mode worktree --output /tmp/iop-cli-readability-review-fix.json
python3 - <<'PY'
import json
report = json.load(open("/tmp/iop-cli-readability-review-fix.json", encoding="utf-8"))
issues = [v for v in report["violations"] if v["path"].startswith("apps/node/internal/adapters/cli/")]
for issue in issues:
    print(issue["path"], issue["metric"], issue.get("function", ""), issue["value"])
PY
```

fresh tests가 PASS하고, 출력된 CLI violation에 새 identity나 HEAD baseline 초과 값이 없어야 한다. `/tmp` 외 repo-local audit 도구를 만들지 않는다.

### [REVIEW_REFACTOR_ADAPTER_CLI-3] readability ratchet 무결성 복구

문제: `scripts/readability_baseline.json:125-157,1051-1056`이 증가한 CLI 파일과 새 `handleEvent` 위반을 허용하고, prior review는 전역 fixed-point 재생성을 근거로 PASS를 기록했다. 이는 refactor ratchet의 비교 기준을 구현 결과에 맞춰 올린 것이다.

해결 방법:

Before:

```json
{
  "path": "apps/node/internal/adapters/cli/opencode_sse.go",
  "metric": "file_loc",
  "level": "split_review",
  "value": 892
}
```

After:

```json
{
  "path": "apps/node/internal/adapters/cli/opencode_sse.go",
  "metric": "file_loc",
  "level": "split_review",
  "value": 863
}
```

단, 실제 분해로 threshold 아래가 되면 해당 identity는 current violation에서 사라져도 된다. baseline 전체를 재생성하거나 `git show HEAD:...`로 파일 전체를 덮어쓰지 않는다. 비-CLI 병렬 변경은 보존하고, CLI identity는 HEAD 값보다 높이지 않으며 새 `opencodeSSERun.handleEvent` allowlist는 제거한다.

수정 파일 및 체크리스트:

- [ ] `scripts/readability_baseline.json`: CLI file/function identity만 surgical edit한다.
- [ ] `scripts/readability_read_sets.json`: 이번 항목에서 수정하지 않고 병렬 변경을 보존한다.
- [ ] global `generated_at`, 비-CLI reason/value/order를 이번 follow-up 명목으로 재생성하지 않는다.

테스트 작성: production test는 작성하지 않는다. HEAD baseline과 worktree 측정값을 직접 비교하는 deterministic audit 명령이 regression test 역할을 한다.

중간 검증:

```bash
make readability-audit
python3 - <<'PY'
import json
import subprocess
from scripts import readability_audit as audit

current = audit.audit(audit.collect_files("worktree"))
baseline = json.loads(subprocess.check_output(
    ["git", "show", "HEAD:scripts/readability_baseline.json"],
    text=True,
))
regressions = [
    issue for issue in audit.ratchet_check(current, baseline, None)
    if issue["path"].startswith("apps/node/internal/adapters/cli/")
]
for issue in regressions:
    print(audit._render_issue(issue))
print(f"cli_regressions={len(regressions)}")
raise SystemExit(1 if regressions else 0)
PY
git diff -- scripts/readability_baseline.json scripts/readability_read_sets.json
```

현재 baseline audit가 PASS하고 HEAD 기준 `cli_regressions=0`이어야 한다. 마지막 diff에서 비-CLI 변경이 이번 follow-up에 의해 덮이거나 새로 재생성되지 않아야 한다.

### [REVIEW_REFACTOR_ADAPTER_CLI-4] 실제 테스트 이름과 fresh 출력으로 evidence 교체

문제: prior review `code_review_cloud_G08_0.log:104-135`의 targeted test 이름은 현재 Go source에 없고 동일 regex rerun 출력과 다르다. 구현 완료 주장과 실제 검증의 연결이 끊겼다.

해결 방법:

Before:

```text
=== RUN   TestCLIBuildCmd
=== RUN   TestCLIPersistentExecutorExecute
=== RUN   TestCLIAppServerExecutorExecute
```

After:

```text
$ GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 -v ./apps/node/internal/adapters/cli -run '<exact existing regex>'
<actual stdout/stderr copied without reconstruction>
```

test discovery는 `rg '^func Test'`를 deterministic하게 실행하고, 계획 명령을 그대로 fresh rerun한다. stdout/stderr를 요약하거나 과거 출력으로 재구성하지 않는다.

수정 파일 및 체크리스트:

- [ ] source에서 실제 테스트 이름을 수집하고 verification regex가 최소 한 테스트와 매칭함을 확인한다.
- [ ] `CODE_REVIEW-cloud-G08.md`의 각 중간/최종 검증 블록에 실행 명령과 실제 stdout/stderr를 붙인다.
- [ ] 출력이 길면 `/tmp` 저장 경로와 stdout/stderr를 함께 저장한 정확한 명령을 기록하고 repo 안에 log/tool을 만들지 않는다.

테스트 작성: evidence mismatch 자체를 위한 새 Go test는 작성하지 않는다. 항목 1의 builder 경계 test 외에는 기존 test suite를 그대로 fresh 실행한다.

중간 검증:

```bash
rg -n --sort path '^func Test' apps/node/internal/adapters/cli apps/node/internal/adapters/cli/status
GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 -v ./apps/node/internal/adapters/cli -run 'TestBuildCmdWithoutContextAppliesEnvAndCwd|TestCodexAppServerProcCwd|TestOpencodeLocalServerCwd|TestCLIPersistentNonTerminalCwd|Emitter|CodexAppServer|Opencode|Persistent|Terminal|OutputFilter|Resume|Cancel'
GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 -v ./apps/node/internal/adapters/cli/status
```

모든 명령이 exit 0이어야 하고 review에 기록된 `=== RUN` 이름이 첫 명령의 source 정의와 일치해야 한다.

### [REVIEW_REFACTOR_ADAPTER_CLI-5] real CLI profile별 full-cycle 또는 blocker 확보

문제: prior plan은 `e2e-smoke mode=real-cli`를 필수로 했지만 prior review에는 실행 결과가 없다. package tests만으로 login, provider, workspace, session reuse, stream/complete ordering을 확인할 수 없다.

해결 방법: tracked config를 수정하지 않는 임시 config/환경 override로 repo 내부 Edge와 Node를 현재 source에서 실행한다. `claude`, `antigravity`, `codex`, `opencode` 각각 같은 session에서 메시지 2회, start/message-or-delta/complete 순서, node payload와 edge rendering 순서, `/nodes`, `/capabilities`, `/transport`, `/sessions`, `/status`, 지원 profile의 `/terminate-session`을 기록한다. 실행 불가 profile도 생략하지 않고 `command -v`, 실제 실행 명령, exit status, stderr, 재개 조건을 blocker로 남긴다.

수정 파일 및 체크리스트:

- [ ] tracked `configs/*.yaml`을 수정하지 않고 `/tmp` config/override 경로를 review에 기록한다.
- [ ] `claude`, `antigravity`, `codex`, `opencode` preflight와 실제 결과를 각각 분리해 기록한다.
- [ ] profile별 같은-session message x2와 event ordering을 기록한다.
- [ ] Edge command 응답과 session terminate/status 결과를 기록한다.
- [ ] 실행하지 못한 profile의 위험과 blocker를 정확히 남긴다.

테스트 작성: 외부 provider 통합 검증이므로 Go test를 추가하지 않는다. `e2e-smoke` real CLI 절차 자체가 필수 evidence다.

중간 검증:

```bash
pwd
git branch --show-current
git rev-parse HEAD
git status --short
uname -srm
command -v claude
command -v antigravity
command -v codex
command -v opencode
test -x scripts/dev/edge.sh
test -x scripts/dev/node.sh
```

이어 `e2e-smoke scope="CLI adapter responsibility split follow-up" mode=real-cli profiles="claude,antigravity,codex,opencode" config_strategy="temporary /tmp config or environment overrides"`를 수행한다. profile별 PASS 또는 실제 blocker가 review 출력 형식에 모두 있어야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/adapters/cli/command.go` | 1 |
| `apps/node/internal/adapters/cli/{codex_app_server.go,codex_app_server_process.go}` | 1, 2 |
| `apps/node/internal/adapters/cli/{opencode_sse.go,opencode_sse_events.go}` | 1, 2 |
| `apps/node/internal/adapters/cli/{persistent.go,persistent_process.go}` | 1, 2 |
| `apps/node/internal/adapters/cli/{emitters.go,emitter_stream_json.go,emitter_profile_json.go}` | 2 |
| `apps/node/internal/adapters/cli/{persistent_output_filter.go,persistent_output_filter_terminal.go,persistent_output_filter_claude.go}` | 2 |
| `apps/node/internal/adapters/cli/cli_workspace_test.go` | 1 |
| 기존 CLI/status tests | 1, 2, 4 |
| `scripts/readability_baseline.json` | 3 |
| `CODE_REVIEW-cloud-G08.md` | 4, 5 |

## 의존 관계 및 구현 순서

1. 항목 1로 lifecycle call sites를 builder에 연결한다.
2. 항목 2로 파일/함수 분해를 마친다.
3. 항목 3에서 최종 source 크기를 기준으로 baseline을 surgical하게 복구한다.
4. 항목 4의 fresh deterministic 검증 후 항목 5의 실제 profile 검증을 수행한다.
5. 모든 실제 출력을 review stub에 기록한다.

## 최종 검증

```bash
gofmt -d apps/node/internal/adapters/cli/*.go apps/node/internal/adapters/cli/status/*.go
GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 ./apps/node/internal/adapters/cli ./apps/node/internal/adapters/cli/status
GOCACHE=/tmp/iop-go-cache-review-fix go test -count=1 ./apps/node/...
make readability-audit
python3 - <<'PY'
import json
import subprocess
from scripts import readability_audit as audit

current = audit.audit(audit.collect_files("worktree"))
baseline = json.loads(subprocess.check_output(
    ["git", "show", "HEAD:scripts/readability_baseline.json"],
    text=True,
))
regressions = [
    issue for issue in audit.ratchet_check(current, baseline, None)
    if issue["path"].startswith("apps/node/internal/adapters/cli/")
]
for issue in regressions:
    print(audit._render_issue(issue))
print(f"cli_regressions={len(regressions)}")
raise SystemExit(1 if regressions else 0)
PY
git diff --check
```

이후 `e2e-smoke scope="CLI adapter responsibility split follow-up" mode=real-cli profiles="claude,antigravity,codex,opencode" config_strategy="temporary /tmp config or environment overrides"`를 실행한다. 네 profile 각각 PASS 또는 command/exit status/stderr가 있는 blocker를 기록해야 하며, 보조 smoke를 full-cycle PASS로 대체하지 않는다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
