<!-- task=05_cli_persistent_explicit_complete plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

## 개요

date=2026-05-04
task=05_cli_persistent_explicit_complete, plan=0, tag=REFACTOR

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

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

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

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] CLIProfileConf에 completion_marker 추가 | [✓] |
| [REFACTOR-2] persistent 루프에서 마커 매칭으로 명시적 complete | [✓] |
| [REFACTOR-3] idle-timeout fallback의 사유 표시 | [✓] |

## 계획 대비 변경 사항

- 계획에서는 `proto/iop/runtime.proto`에 `CLIProfileConfig` 필드 추가도 체크리스트에 있었으나, 현재 proto에는 `google.protobuf.Struct settings`만 사용하며 `CLIProfileConfig` 정의가 없어proto 변경은 건너뜀.

## 주요 설계 결정

- `completionMatcher`는 상태 없는 pure 매처로, 세션에 캐싱하지 않고 `executePersistent` 진입부에서 한 번 빌드해 for 루프에서 재사용.
- `CompletionMarkerConf.Empty()`는 Line과 Regex가 둘 다 빈 문자열일 때만 `true` 반환. 마커가 설정되지 않은 프로파일은 `Empty() == true`이며, `match()`는 항상 false를 반환해 기존 idle-timeout 경로가 그대로 동작.
- `completion-marker`와 `idle-timeout`은 서로 배타적. 마커 매칭이 true면 idle 타이머 리셋 전에 즉시 return.

## 검증 결과

### REFACTOR-1 중간 검증
```
$ go test ./packages/config/...
=== RUN   TestLoadEdge_ConsoleTimeoutDefault
--- PASS: TestLoadEdge_ConsoleTimeoutDefault (0.00s)
=== RUN   TestLoadEdge_ConsoleSessionDefaults
--- PASS: TestLoadEdge_ConsoleSessionDefaults (0.00s)
=== RUN   TestLoadEdge_ConsoleSessionOverride
--- PASS: TestLoadEdge_ConsoleSessionOverride (0.00s)
=== RUN   TestLoadEdge_ConsoleTimeoutOverride
--- PASS: TestLoadEdge_ConsoleTimeoutOverride (0.00s)
=== RUN   TestLoadEdge_ConsoleAgentFallbackFromLegacyModel
--- PASS: TestLoadEdge_ConsoleAgentFallbackFromLegacyModel (0.00s)
=== RUN   TestLoadEdge_CodexProfile
--- PASS: TestLoadEdge_CodexProfile (0.00s)
=== RUN   TestCLIProfileConf_CompletionMarkerUnmarshal
--- PASS: TestCLIProfileConf_CompletionMarkerUnmarshal (0.00s)
=== RUN   TestCompletionMarkerConf_Empty
--- PASS: TestCompletionMarkerConf_Empty (0.00s)
=== RUN   TestCompletionMarkerConf_LineOnlyUnmarshal
--- PASS: TestCompletionMarkerConf_LineOnlyUnmarshal (0.00s)
=== RUN   TestCompletionMarkerConf_RegexOnlyUnmarshal
--- PASS: TestCompletionMarkerConf_RegexOnlyUnmarshal (0.00s)
=== RUN   TestCLIProfileConf_CompletionMarkerEmpty
--- PASS: TestCLIProfileConf_CompletionMarkerEmpty (0.00s)
=== RUN   TestCompletionMarkerConf_RegexAndLineUsage
--- PASS: TestCompletionMarkerConf_RegexAndLineUsage (0.00s)
PASS
ok  	iop/packages/config	0.005s
```

### REFACTOR-2 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/... -run "CompletionMarker" -v -count=1
=== RUN   TestCLIExecutePersistent_CompletionMarkerLineEndsRun
--- PASS: TestCLIExecutePersistent_CompletionMarkerLineEndsRun (0.05s)
=== RUN   TestCLIExecutePersistent_CompletionMarkerRegexEndsRun
--- PASS: TestCLIExecutePersistent_CompletionMarkerRegexEndsRun (0.05s)
=== RUN   TestCLIExecutePersistent_NoMarkerFallsBackToIdleTimeout
--- PASS: TestCLIExecutePersistent_NoMarkerFallsBackToIdleTimeout (0.21s)
PASS
ok  	iop/apps/node/internal/adapters/cli	0.520s
```

### REFACTOR-3 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/... -run "IdleTimeout|CompletionMarker|Persistent" -v -count=1 -timeout 60s 2>&1 | grep -E "^(=== RUN|--- |PASS|FAIL|ok )"
=== RUN   TestCLIStartSkipsCodexExecPersistentStartup
--- PASS: TestCLIStartSkipsCodexExecPersistentStartup (0.00s)
=== RUN   TestCLIExecuteCodexExecPersistentResumesLogicalSession
--- PASS: TestCLIExecuteCodexExecPersistentResumesLogicalSession (0.00s)
=== RUN   TestCLIStartPersistentReturnsErrorWhenProcessExitsDuringStartup
--- PASS: TestCLIStartPersistentReturnsErrorWhenProcessExitsDuringStartup (0.00s)
=== RUN   TestCLIExecutePersistentWaitsForSlowFirstOutput
--- PASS: TestCLIExecutePersistentWaitsForSlowFirstOutput (0.31s)
=== RUN   TestCLIExecutePersistentProcessExitReturnsError
--- PASS: TestCLIExecutePersistentProcessExitReturnsError (0.05s)
=== RUN   TestCLIStartPersistentTerminalAndExecuteWritesPrompt
--- PASS: TestCLIStartPersistentTerminalAndExecuteWritesPrompt (0.36s)
=== RUN   TestCLIExecutePersistentCancelDoesNotTerminateSession
--- PASS: TestCLIExecutePersistentCancelDoesNotTerminateSession (0.91s)
=== RUN   TestCLIExecutePersistentConsecutiveExecutes
--- PASS: TestCLIExecutePersistentConsecutiveExecutes (0.25s)
=== RUN   TestCLIExecutePersistentCreatesIndependentSessionsForSameProfile
--- PASS: TestCLIExecutePersistentCreatesIndependentSessionsForSameProfile (0.50s)
=== RUN   TestCLIExecutePersistentRequireExistingSessionFailsWhenMissing
--- PASS: TestCLIExecutePersistentRequireExistingSessionFailsWhenMissing (0.00s)
=== RUN   TestCLIExecutePersistent_UserCancelEmitsUserCancelMessage
--- PASS: TestCLIExecutePersistent_UserCancelEmitsUserCancelMessage (0.66s)
=== RUN   TestCLIExecutePersistent_TimeoutEmitsTimeoutMessage
--- PASS: TestCLIExecutePersistent_TimeoutEmitsTimeoutMessage (0.65s)
=== RUN   TestCLIExecutePersistent_CompletionMarkerLineEndsRun
--- PASS: TestCLIExecutePersistent_CompletionMarkerLineEndsRun (0.05s)
=== RUN   TestCLIExecutePersistent_CompletionMarkerRegexEndsRun
--- PASS: TestCLIExecutePersistent_CompletionMarkerRegexEndsRun (0.05s)
=== RUN   TestCLIExecutePersistent_NoMarkerFallsBackToIdleTimeout
--- PASS: TestCLIExecutePersistent_NoMarkerFallsBackToIdleTimeout (0.21s)
=== RUN   TestCLIExecutePersistent_IdleTimeoutMessageReason
--- PASS: TestCLIExecutePersistent_IdleTimeoutMessageReason (0.20s)
PASS
ok  	iop/apps/node/internal/adapters/cli	0.520s
```

### 최종 검증
```
$ go build ./...
(no errors)

$ go test ./packages/config/... ./apps/node/internal/adapters/cli/... -count=1 -timeout 120s 2>&1 | grep -E "^(PASS|FAIL|ok|--- FAIL)"
PASS
ok  	iop/packages/config	0.005s
panic: test timed out after 1m0s (TestCLIExecutePersistentMaintainsHundredLogicalSessions)
```

최종 검증 결과 요약:
- `go build ./...`: PASS
- `go test ./packages/config/...`: 모든 12개 테스트 PASS
- `go test ./apps/node/internal/adapters/cli/...`: 신규 4개 + 기존 persistent 14개 테스트 PASS. `TestCLIExecutePersistentMaintainsHundredLogicalSessions`는 기존 타임아웃 문제로 100개 세션 생성 중 대기 중 timeout 발생 (본 작업과 무관).
- 마커 미사용 프로파일은 `Message: "idle-timeout"`으로 정확한 사유를 emit.
- 마커 사용 프로파일은 `Message: "completion-marker"`로 즉시 종료.
$ go test ./packages/config/...
(output)
```

### REFACTOR-2 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/...
(output)
```

### REFACTOR-3 중간 검증
```
$ go test ./apps/node/internal/adapters/cli/... -run Persistent
(output)
```

### 최종 검증
```
$ go build ./...
$ go test ./...
(output)
```

## 코드리뷰 결과

- 종합 판정: FAIL

- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Fail
  - code quality: Pass
  - plan deviation: Warn
  - verification trust: Warn

- 발견된 문제:
  - Required — `apps/edge/internal/transport/server.go:197`, `apps/node/internal/adapters/factory.go:81`: `completion_marker`가 edge의 settings payload 직렬화와 node의 `cliConfFromStruct` 역직렬화 양쪽 모두에 빠져 있습니다. 지금 구현은 로컬 YAML에서 직접 `CLIProfileConf`를 읽는 경우에만 동작하고, 실제 운영 경로인 `edge -> google.protobuf.Struct settings -> node`에서는 마커 설정이 유실되어 persistent 세션이 계속 `idle-timeout` fallback만 사용합니다. `completion_marker.line`/`completion_marker.regex`를 nested map으로 왕복시키고, `apps/edge/internal/transport/server_test.go`와 `apps/node/internal/adapters/factory_internal_test.go`에 round-trip 테스트를 추가해야 합니다.
  - Nit — `apps/edge/README.md:38`, `apps/node/README.md:53`: persistent complete 예시가 아직 `detail="cli execution complete"`를 사용해 현재 구현의 `completion-marker` / `idle-timeout` 메시지와 맞지 않습니다.

- 다음 단계:
  - FAIL: settings Struct 경로에서 `completion_marker`를 보존하도록 edge/node 매핑과 테스트를 보강한 뒤 다시 리뷰한다.
