<!-- task=01+01_application_result_contract plan=1 tag=REVIEW_REFACTOR -->

# Application Result Contract Follow-up Plan

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

**필수 경고: 구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채워야 한다. 이 파일이 채워지기 전에는 작업이 완료된 것이 아니다.**
구현 체크리스트와 검증 명령을 그대로 수행하고 실제 출력을 review stub에 기록한다.
review 파일의 아카이브 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이다.

## 배경

plan 0 구현은 `Application.build()`를 `BuildResult` 반환 계약으로 바꿨지만, 리뷰에서 파일 없음 CLI 경로와 같은 process/isolate 재호출 상태에서 Required 문제가 발견됐다.
이번 follow-up은 기존 `BuildResult` 방향을 유지하면서 실패 경로와 재사용 계약을 보완한다.

## 분석 결과

### 읽어야 할 파일

- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `lib/cli/commands/command_exe.dart`
- `lib/oto/application.dart`
- `lib/oto/core/build_result.dart`
- `test/oto_application_test.dart`

### 리뷰 발견 이슈

- `CommandExe.execute()`는 `-f` 파일 없음에서도 `ranBuild = true`로 표시해 `_handler.exit()`를 호출한다. 이때 `_handler`가 초기화되지 않아 `LateInitializationError`가 발생한다.
- `Application.build()`가 반환형으로 바뀌었지만 `dataCommandMap`/`commandStates` 같은 per-build 상태를 호출 시작 시 초기화하지 않아 같은 process/isolate 연속 실행에서 이전 build 상태가 남을 수 있다.
- plan 0 review stub의 검증 출력이 일부 생략되어 실제 stdout/stderr 기록 계약을 만족하지 못했다.

### 빌드 등급

- build lane: `cloud-G07` — CLI process/exit-status 계약과 smoke 검증이 필요하다.
- review lane: `cloud-G07` — terminal-agent 성격의 종료/상태 계약과 검증 출력 신뢰를 다시 확인해야 한다.

## 구현 체크리스트

- [ ] [REVIEW_REFACTOR-1] `CommandExe` 파일 없음 경로가 `_handler` 미초기화 상태를 정리하지 않게 한다.
- [ ] [REVIEW_REFACTOR-2] `Application.build()`가 호출마다 per-build 상태를 초기화하게 한다.
- [ ] [REVIEW_REFACTOR-3] 검증 출력 신뢰를 복구하고 CLI smoke/regression 검증을 기록한다.
- [ ] 모든 중간 검증과 최종 검증을 실행하고 실제 출력을 `CODE_REVIEW-*-G??.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- `plan_cloud_G07_0.log`와 `code_review_cloud_G07_0.log`의 Required 이슈를 해결한다.
- 이번 follow-up 완료 후 `01+02_runtime_context`를 진행할 수 있다.

### [REVIEW_REFACTOR-1] 파일 없음 CLI 경로 수정

#### 문제

`lib/cli/commands/command_exe.dart:51`의 `startYaml(file)`은 파일이 없으면 메시지만 출력하고 `_handler`를 만들지 않는다.
하지만 `execute()`는 곧바로 `ranBuild = true`를 설정하고 마지막에 `_handler.exit()`를 호출한다.

#### 해결 방법

`startYaml`이 실제 isolate 실행 여부를 반환하거나, `_handler`를 nullable로 전환해 생성된 경우에만 cleanup을 수행한다.
파일이 없을 때는 기존 사용자 메시지를 유지하고, CLI 종료 코드는 실패로 관찰 가능하게 만든다.

예시 방향:

```dart
final started = await startYaml(file);
if (started) {
  ranBuild = true;
} else {
  _exitCode = 10;
}
...
if (ranBuild) {
  exitCode = _exitCode;
  _handler.exit();
} else if (_exitCode != 0) {
  exitCode = _exitCode;
}
```

위 예시는 방향만 제시한다. 기존 구조에 맞는 더 단순한 구현이 있으면 선택해도 된다.

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

- [ ] `lib/cli/commands/command_exe.dart`에서 파일 없음 경로가 late `_handler`를 참조하지 않게 수정
- [ ] 파일 없음 경로의 실패 exit code가 관찰되게 유지
- [ ] `LateInitializationError`가 발생하지 않는 regression 검증 추가

#### 테스트 작성

- 작성: `test/oto_application_test.dart` 또는 별도 CLI 테스트 파일
- 목적: `exe -f` missing file 경로가 `LateInitializationError` 없이 종료되고, 실패 exit code 또는 실패 상태가 관찰되는지 검증

#### 중간 검증

```bash
dart test test/oto_application_test.dart
```

예상: 기존 BuildResult 테스트와 파일 없음 regression 테스트 통과.

### [REVIEW_REFACTOR-2] Application per-build 상태 초기화

#### 문제

`Application.build()`가 값을 반환하도록 바뀌면서 같은 process/isolate 내 재호출이 가능해졌지만, `dataCommandMap`과 `commandStates`가 호출 시작 시 초기화되지 않는다.
동일 YAML을 두 번 실행하면 이전 command id가 남아 duplicate command validation 실패가 발생할 수 있다.

#### 해결 방법

`Application.build()` 시작부에서 per-build 실행 상태를 초기화한다.
최소 대상은 `dataCommandMap`과 `commandStates`이며, `property`는 현재처럼 YAML property로 재할당되는 흐름을 유지한다.
필요하면 `pipeline`/`commonData` 등도 기존 동작에 맞춰 새 build 데이터로 덮어쓰는지 확인한다.

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

- [ ] `lib/oto/application.dart`에서 build 호출마다 per-build command state를 초기화
- [ ] 기존 `property = build.property ?? {}` 흐름과 workspace 기본값 주입 흐름 유지
- [ ] scheduler build path에서도 동일 초기화가 안전한지 확인

#### 테스트 작성

- 작성: `test/oto_application_test.dart`
- 테스트명 후보: `file build can run twice in same process`
- 목적: 같은 YAML을 `Application.instance.build(BuildType.file, ...)`로 연속 두 번 호출해 두 결과 모두 success인지 검증

#### 중간 검증

```bash
dart test test/oto_application_test.dart
```

예상: 연속 build 테스트까지 통과.

### [REVIEW_REFACTOR-3] 검증 출력 신뢰 복구

#### 문제

plan 0 review stub에는 중간 검증 출력 일부가 생략되어 있었다.
또한 리뷰 중 추가 실행한 `dart run bin/main.dart exe -f /tmp/oto_missing_review.yaml`에서 `LateInitializationError`와 hang이 재현됐다.

#### 해결 방법

고정 검증 명령을 다시 실행하고 실제 stdout/stderr를 생략 없이 `CODE_REVIEW-cloud-G07.md`에 기록한다.
파일 없음 CLI smoke도 함께 기록해 종료 여부와 오류 부재를 확인할 수 있게 한다.

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

- [ ] `CODE_REVIEW-cloud-G07.md` 검증 결과에 중간/최종 검증 실제 출력 기록
- [ ] 파일 없음 CLI smoke 출력과 exit code 또는 종료 사실 기록
- [ ] 출력 생략 문구(`... 생략 ...`)를 남기지 않음

#### 테스트 작성

- 별도 테스트 작성은 [REVIEW_REFACTOR-1], [REVIEW_REFACTOR-2]에 포함한다.

#### 중간 검증

```bash
dart run bin/main.dart exe -f /tmp/oto_missing_review.yaml
```

예상: 파일 없음 메시지가 출력되고 `LateInitializationError` 없이 즉시 종료한다. 실패 exit code를 의도했다면 실제 exit code를 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/cli/commands/command_exe.dart` | REVIEW_REFACTOR-1 |
| `lib/oto/application.dart` | REVIEW_REFACTOR-2 |
| `test/oto_application_test.dart` | REVIEW_REFACTOR-1, REVIEW_REFACTOR-2 |
| `agent-task/01+01_application_result_contract/CODE_REVIEW-cloud-G07.md` | REVIEW_REFACTOR-3 |

## 최종 검증

```bash
dart analyze
```

예상: `No issues found!`

```bash
dart test
```

예상: 모든 테스트 통과.

```bash
rg --sort path -n "exit\\(" lib/oto/application.dart lib/cli/commands/command_exe.dart lib/cli/commands/command_scheduler.dart lib/cli/commands/scheduler
```

예상: `lib/oto/application.dart`에는 결과가 없어야 한다. `command_exe.dart`에 남는 결과가 있다면 process 종료 호출이 아닌 handler cleanup이어야 하며 review stub에 기록한다.

```bash
dart run bin/main.dart exe -f /tmp/oto_missing_review.yaml
```

예상: 파일 없음 메시지를 출력하고 `LateInitializationError` 없이 종료한다. 의도한 실패 exit code가 있으면 실제 값을 review stub에 기록한다.

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