<!-- task=09+07_cli_process_runtime_rollout plan=0 tag=SYSTEM_RUNTIME_CLI_ROLLOUT -->

# Plan - SYSTEM_RUNTIME_CLI_ROLLOUT

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

**구현의 마지막 단계는 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다. 이 파일이 채워지기 전에는 작업이 완료된 것이 아니다.**
아래 `구현 체크리스트` 순서대로 구현하고 중간/최종 검증을 실행한 뒤, 실제 구현 내용과 stdout/stderr를 `CODE_REVIEW-cloud-G07.md`에 기록한다.
리뷰 파일의 아카이브 지시와 `코드리뷰 전용 체크리스트`는 코드리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.

## 배경

CLI 출력과 PATH 등록 보조 로직은 사용자의 터미널 환경을 직접 건드린다.
현재는 `Process.run`/`ProcessExecutor.run` 직접 호출이라 테스트가 OS와 shell에 묶이므로, foundation runtime으로 경계를 정리한다.

## 의존 관계 및 구현 순서

디렉터리 이름의 `09+07`에 따라 `agent-task/07_system_runtime_foundation/complete.log`가 먼저 있어야 한다.
core rollout과는 독립이며 foundation 이후 병렬 진행 가능하다.

## 분석 결과

### 읽은 파일

- `lib/cli/printer.dart`
- `lib/cli/commands/install/regist_path.dart`
- `lib/cli/cli.dart`
- `lib/oto/core/system_runtime.dart` (선행 task 예정 파일)
- `test/oto_application_test.dart`

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

- `Printer.windows().printCLI()` / `Printer.unix().printCLI()`: 기존 직접 테스트 없음. fake runtime으로 실행 파일과 args, temp 파일 삭제를 검증한다.
- `RegistPath` macOS/Windows shell 실행: 기존 테스트 없음. profile 파일 쓰기와 runtime shell 호출을 fake로 검증한다.

### 심볼 참조

none. public factory 이름은 유지한다.

### 분할 판단

분할 정책을 먼저 평가했다.
CLI 출력/PATH 등록은 cli 도메인에 묶여 있고 scheduler는 별도 도메인이라 `10+07`로 분리한다.
이 task는 `07_system_runtime_foundation`에만 의존한다.

### 범위 결정 근거

포함: `Printer`, `RegistPath` process 호출 runtime 이전과 CLI runtime tests.
제외: `CommandScheduler`/`SchedulerManager`는 scheduler 도메인이라 `10+07`에서 처리한다.
제외: `Application`/`DataComposer`는 core 도메인이라 `08+07`에서 처리한다.

### 빌드 등급

`cloud-G07`: CLI terminal 출력과 PATH 등록 shell 실행을 fake 가능한 process boundary로 바꾸는 작업이다.

## 구현 체크리스트

- [ ] `Printer`가 `SystemRuntime`을 사용하게 한다.
- [ ] `RegistPath`가 `SystemRuntime`을 사용하게 한다.
- [ ] CLI runtime fake 테스트를 추가한다.
- [ ] 중간 검증과 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [SYSTEM_RUNTIME_CLI_ROLLOUT-1] Printer runtime 이전

#### 문제

`lib/cli/printer.dart:153`와 `lib/cli/printer.dart:190`이 `Process.run`을 직접 호출해 CLI 출력 스크립트를 fake로 검증할 수 없다.

#### 해결 방법

`Printer.windows({SystemRuntime runtime = const DefaultSystemRuntime()})`, `Printer.unix({SystemRuntime runtime = const DefaultSystemRuntime()})` 형태로 runtime 주입을 허용한다.
private printer 구현은 `runtime.runExecutable(...)`을 사용한다.

Before (`lib/cli/printer.dart:153`):
```dart
await Process.run('cmd', ['/C', temp.path],
        stderrEncoding: utf8, stdoutEncoding: utf8)
    .then((ProcessResult result) {
```

After:
```dart
final result = await runtime.runExecutable('cmd', ['/C', temp.path],
    stdoutEncoding: utf8, stderrEncoding: utf8);
```

#### 수정 파일 및 체크리스트

- [ ] `lib/cli/printer.dart`: runtime field/constructors 추가, direct `Process.run` 제거.
- [ ] `lib/cli/cli.dart`: 기존 `CLI.printer` factory 호출 호환 확인.

#### 테스트 작성

작성한다.
`test/oto_cli_runtime_test.dart`에 `unix printer delegates script execution to system runtime`, `windows printer delegates script execution to system runtime`를 추가한다.
실제 shell은 실행하지 않고 fake runtime의 executable/args를 검증한다.

#### 중간 검증

```bash
dart test test/oto_cli_runtime_test.dart
```

기대 결과: CLI runtime 테스트 통과.

### [SYSTEM_RUNTIME_CLI_ROLLOUT-2] RegistPath runtime 이전

#### 문제

`lib/cli/commands/install/regist_path.dart:51`, `80`, `127`, `137`, `147`이 `ProcessExecutor.run`을 직접 호출한다.
PATH 등록은 사용자 환경에 영향을 주므로 테스트 가능한 boundary가 필요하다.

#### 해결 방법

`RegistPath.create({SystemRuntime runtime})` 또는 생성자 주입 경로를 추가하되 기존 `RegistPath.OS` 호출 호환을 유지한다.
OS별 private 구현은 runtime으로 shell 실행을 위임한다.
파일 쓰기 테스트는 임시 HOME/UserProfile을 runtime/environment abstraction으로 주입해서 실제 사용자 profile을 건드리지 않는다.

Before (`lib/cli/commands/install/regist_path.dart:127`):
```dart
await ProcessExecutor.run(shell);
```

After:
```dart
await runtime.runShell(shell);
```

#### 수정 파일 및 체크리스트

- [ ] `lib/cli/commands/install/regist_path.dart`: runtime/environment 주입 및 직접 process 호출 제거.
- [ ] `test/oto_cli_runtime_test.dart`: macOS/Windows shell call capture 테스트 추가.

#### 테스트 작성

작성한다.
테스트명: `regist_path osx sources profiles through system runtime`, `regist_path windows delegates setx through system runtime`.
검증: 임시 profile 파일 내용, fake shell call, 실제 사용자 profile 미수정.

#### 중간 검증

```bash
dart test test/oto_cli_runtime_test.dart
```

기대 결과: CLI runtime 테스트 통과.

### [SYSTEM_RUNTIME_CLI_ROLLOUT-3] CLI 직접 process 호출 제거 검증

#### 문제

직접 호출 제거 후에도 회귀 검색이 없으면 같은 drift가 반복될 수 있다.

#### 해결 방법

결정적 `rg --sort path` 검증을 최종 계약에 포함한다.

#### 수정 파일 및 체크리스트

- [ ] `test/oto_cli_runtime_test.dart`: fake runtime 검증 유지.

#### 테스트 작성

별도 테스트는 위 항목으로 충분하다.

#### 중간 검증

```bash
rg --sort path -n "ProcessExecutor\\.(start|run)|Process\\.run|Process\\.start" lib/cli/printer.dart lib/cli/commands/install/regist_path.dart
```

기대 결과: stdout 없음.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/cli/printer.dart` | SYSTEM_RUNTIME_CLI_ROLLOUT-1 |
| `lib/cli/cli.dart` | SYSTEM_RUNTIME_CLI_ROLLOUT-1 |
| `lib/cli/commands/install/regist_path.dart` | SYSTEM_RUNTIME_CLI_ROLLOUT-2 |
| `test/oto_cli_runtime_test.dart` | SYSTEM_RUNTIME_CLI_ROLLOUT-1, SYSTEM_RUNTIME_CLI_ROLLOUT-2, SYSTEM_RUNTIME_CLI_ROLLOUT-3 |

## 최종 검증

```bash
test -f agent-task/archive/2026/05/07_system_runtime_foundation/complete.log || test -f agent-task/07_system_runtime_foundation/complete.log
dart format --output=none --set-exit-if-changed lib/cli/printer.dart lib/cli/cli.dart lib/cli/commands/install/regist_path.dart test/oto_cli_runtime_test.dart
dart analyze
dart test test/oto_cli_runtime_test.dart
dart run bin/main.dart
rg --sort path -n "ProcessExecutor\\.(start|run)|Process\\.run|Process\\.start" lib/cli/printer.dart lib/cli/commands/install/regist_path.dart
```

기대 결과: 선행 complete marker 존재, 포맷/analyze/test/CLI help 성공, `rg` stdout 없음.

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