<!-- task=01_command_runtime_api plan=1 tag=REVIEW_REFACTOR -->

# Command Runtime API Follow-up Plan

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

**구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 해당 파일의 모든 구현 소유 섹션을 채우기 전까지 작업은 완료가 아니다.** 구현 체크리스트를 따라 작업하고, 중간/최종 검증 출력은 review stub에 실제 stdout/stderr로 기록한다. review 파일의 아카이브 지시와 `코드리뷰 전용 체크리스트`는 코드리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.

## 배경

plan 0 리뷰 결과 `CommandRuntime.runExecutable` 계약이 기존 command 도메인 잔여 `Process.run(executable, args)` 호출 의미와 맞지 않는 문제가 발견됐다. 현재 구현은 `ProcessExecutor.startExe`를 사용해 `ProcessData`를 ,,반환하므로, 후속 command 이전에서 완료 대기/반환 타입/플랫폼 의미가 달라질 수 있다.

## 분석 결과

### 읽은 파일

- `lib/oto/commands/command_runtime.dart`
- `test/oto_command_runtime_test.dart`
- `lib/oto/commands/ftp/ftp.dart`
- `lib/oto/commands/infra/smb_authorize.dart`

### 실패 원인

- `runExecutable`이 이름과 계획상 `Process.run(executable, args)` 계열 대체 API인데 `Future<ProcessData>` + `ProcessExecutor.startExe` 계약으로 구현됐다.
- 기존 직접 호출부는 `await Process.run(...)`으로 프로세스 완료 뒤 `ProcessResult.stdout/stderr/exitCode`를 사용한다.
- `ProcessExecutor.startExe`는 시작 후 `ProcessData`를 반환하는 계약이라 호출부 이전 시 의미가 바뀐다.

### 범위 결정 근거

- 이번 후속은 runtime API와 해당 테스트만 수정한다.
- SFTP/SMB 호출 이전 자체는 별도 task에서 수행한다.

### 빌드 등급

- build=`cloud-G07`, review=`cloud-G07`: 프로세스 실행 포트와 exit/stdout/stderr 계약을 다루는 터미널 에이전트 기반 후속 작업이다.

## 구현 체크리스트

- [ ] `CommandRuntime.runExecutable`을 기존 `Process.run(executable, arguments)` 호출과 같은 완료 대기/`ProcessResult` 반환 계약으로 정리한다.
- [ ] `DefaultCommandRuntime.runExecutable`이 `Process.run`에 위임하고 `workspace`와 stdout/stderr 로깅 옵션을 계약에 맞게 처리하게 한다.
- [ ] fake runtime과 테스트를 executable 호출의 인자, workspace, 옵션, `ProcessResult` 반환 의미를 검증하도록 갱신한다.
- [ ] 중간 검증과 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REFACTOR-1] runExecutable 계약 정렬

#### 문제

`CommandRuntime.runExecutable`이 기존 `Process.run(executable, args)` 호출부를 표현해야 하지만 현재 반환 타입과 위임 대상이 다르다.

Before:

```dart
Future<ProcessData> runExecutable(String exe, List<String> args,
    {String? workspace,
    bool? printStdout,
    bool? printStderr,
    LogHandler? logHandler});

return ProcessExecutor.startExe(exe, args, ...);
```

#### 해결 방법

`runExecutable`을 `Future<ProcessResult>` 반환 계약으로 바꾸고, 기본 구현은 `Process.run(executable, arguments, workingDirectory: workspace)` 완료 결과를 반환한다. stdout/stderr 로깅 옵션은 반환 결과를 바꾸지 않는 부가 동작으로만 처리한다.

After:

```dart
Future<ProcessResult> runExecutable(String executable, List<String> arguments,
    {String? workspace,
    bool printStdout = true,
    bool printStderr = true,
    LogHandler? logHandler});
```

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

- [ ] `lib/oto/commands/command_runtime.dart`: `runExecutable` 반환 타입과 기본 구현을 `Process.run` 의미로 정렬.
- [ ] `test/oto_command_runtime_test.dart`: fake runtime capture 구조와 테스트를 `ProcessResult` 반환 의미까지 검증하도록 갱신.

#### 테스트 작성

작성한다. 기존 `runtime captures executable arguments` 테스트를 확장하거나 새 테스트를 추가해 executable, args, workspace, print 옵션, 반환된 `ProcessResult.exitCode/stdout/stderr`를 확인한다.

#### 중간 검증

```bash
dart test test/oto_command_runtime_test.dart
```

예상: runtime 테스트 통과.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/commands/command_runtime.dart` | REVIEW_REFACTOR-1 |
| `test/oto_command_runtime_test.dart` | REVIEW_REFACTOR-1 |

## 최종 검증

```bash
dart format lib/oto/commands/command_runtime.dart test/oto_command_runtime_test.dart
dart analyze
dart test test/oto_command_runtime_test.dart
```

예상: format 정상, analyze 통과, runtime 테스트 통과. Dart test는 캐시가 없으므로 fresh 실행만 인정한다.

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