<!-- task=m-runner-output-boundary/01_output_port_contract plan=1 tag=REFACTOR -->

# Plan - REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- Task ids:
  - `port`: runner 실행 progress와 일반 출력에 필요한 최소 output port 계약을 정의한다.
- Completion mode: check-on-pass

## 배경

현재 pipeline/core 실행 코드가 `package:oto/cli/cli.dart`의 `CLI`, `Color`를 직접 사용한다. 첫 단계에서는 domain-neutral output port 계약과 기본 no-op 구현만 정의하고, 실제 call-site 전환은 후속 split 작업에서 수행한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/independent-control-plane/PHASE.md`
- `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- `agent-ops/rules/project/domain/pipeline/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/pipeline-smoke.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/cli-smoke.md`
- `apps/runner/lib/oto/core/execution_context.dart`
- `apps/runner/lib/oto/pipeline/pipeline.dart`
- `apps/runner/lib/oto/pipeline/pipeline_exe.dart`
- `apps/runner/lib/oto/pipeline/pipeline_exe_handle.dart`
- `apps/runner/lib/oto/pipeline/pipeline_foreach.dart`
- `apps/runner/lib/oto/core/data_composer.dart`
- `apps/runner/lib/oto/application.dart`
- `apps/runner/lib/cli/cli.dart`
- `apps/runner/lib/cli/printer.dart`
- `apps/runner/lib/cli/cli_style.dart`
- `apps/runner/test/oto_core_test.dart`
- `apps/runner/test/oto_application_test.dart`

### 테스트 환경 규칙

- test_env: `local`
- `agent-test/local/rules.md` 읽음.
- 매칭 profile: `agent-test/local/core-smoke.md`, `agent-test/local/pipeline-smoke.md`, `agent-test/local/cli-smoke.md` 읽음.
- 적용 명령: core 변경 기준 `cd apps/runner && dart analyze`, `cd apps/runner && dart test test/oto_application_test.dart test/oto_context_test.dart test/oto_core_test.dart test/oto_system_runtime_test.dart`.
- 이번 subtask는 core 계약 추가라 pipeline/CLI smoke는 후속 subtask에서 실행한다.
- 구조상 blank/skeleton 없음. `<확인 필요>` 값 없음.

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

- 새 output port가 default no-op인지 확인하는 직접 테스트가 없다. `apps/runner/test/oto_core_test.dart`에 추가한다.
- CLI adapter 동작, pipeline call-site 제거, DataComposer printer 제거는 후속 subtask에서 다룬다.

### 심볼 참조

- 변경/제거 예정 심볼: 없음.
- 추가 예정 심볼: `RunnerOutputPort`, `RunnerOutputStyle`, `NoopRunnerOutputPort`, 테스트용 recording output.

### 분할 판단

- split decision policy를 평가했다.
- 공유 계약과 broad call-site rollout이 분리되어야 하므로 split한다.
- task group: `agent-task/m-runner-output-boundary`
- sibling:
  - `01_output_port_contract`: output port 계약 정의, 선행 없음.
  - `02+01_pipeline_output_port`: pipeline call-site 전환, `01` complete.log 필요.
  - `03+01,02_core_cli_adapter`: DataComposer/Application/CLI adapter 연결, `01`, `02` complete.log 필요.

### 범위 결정 근거

- 이 subtask는 계약과 기본 구현만 추가한다.
- `apps/runner/lib/oto/pipeline/**` call-site 전환은 제외한다.
- `apps/runner/lib/oto/core/data_composer.dart` compose signature 변경은 제외한다.
- CLI adapter와 기존 출력 회귀 검증은 제외한다.

### 빌드 등급

- build lane: `local-G06`
- review lane: `local-G06`
- 근거: core 계약 추가와 테스트 보강은 범위가 작지만 후속 refactor의 기반이므로 명시적 테스트가 필요하다.

## 구현 체크리스트

- [ ] output port 계약과 no-op 구현을 core 소유 파일에 추가한다.
- [ ] `ExecutionContext`가 output port를 보유하고 reset 없이 explicit context별 output을 유지하게 한다.
- [ ] output port 기본 동작을 검증하는 core 테스트를 추가한다.
- [ ] `cd apps/runner && dart analyze`와 지정 core 테스트를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/runner/lib/oto/core/output_port.dart` | REFACTOR-1 |
| `apps/runner/lib/oto/core/execution_context.dart` | REFACTOR-1 |
| `apps/runner/test/oto_core_test.dart` | REFACTOR-1 |

## [REFACTOR-1] Output Port Contract

### 문제

[execution_context.dart](/config/workspace/oto/apps/runner/lib/oto/core/execution_context.dart:39)는 runtime state를 보유하지만 output 경계가 없다. 그래서 [pipeline_exe.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline_exe.dart:43), [pipeline_foreach.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline_foreach.dart:116), [data_composer.dart](/config/workspace/oto/apps/runner/lib/oto/core/data_composer.dart:15)가 CLI type을 직접 소비한다.

Before:

```dart
// apps/runner/lib/oto/core/execution_context.dart:39
class ExecutionContext {
  DataCommon? commonData;
  Map<String, dynamic> property = {};
  Map<String, CommandState> commandStates = {};
  Map<String, DataCommand> dataCommandMap = {};
```

### 해결 방법

`apps/runner/lib/oto/core/output_port.dart`를 추가하고 CLI enum을 import하지 않는 최소 계약을 둔다. 이름은 runner domain 의미를 드러내되 UI/CLI 구현 세부를 갖지 않는다.

After:

```dart
enum RunnerOutputStyle {
  normal,
  success,
  warning,
  error,
  accent,
  progress,
}

abstract class RunnerOutputPort {
  Future<void> line(String message, {RunnerOutputStyle style = RunnerOutputStyle.normal});
  Future<void> block(String message, {RunnerOutputStyle style = RunnerOutputStyle.normal});
  Future<void> buildStep(String name, {RunnerOutputStyle style = RunnerOutputStyle.success});
}

class NoopRunnerOutputPort implements RunnerOutputPort {
  const NoopRunnerOutputPort();
  @override
  Future<void> line(String message, {RunnerOutputStyle style = RunnerOutputStyle.normal}) async {}
  @override
  Future<void> block(String message, {RunnerOutputStyle style = RunnerOutputStyle.normal}) async {}
  @override
  Future<void> buildStep(String name, {RunnerOutputStyle style = RunnerOutputStyle.success}) async {}
}
```

`ExecutionContext`에 기본 no-op output을 둔다.

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

- [ ] `apps/runner/lib/oto/core/output_port.dart` 추가
- [ ] `apps/runner/lib/oto/core/execution_context.dart`에 `RunnerOutputPort output = const NoopRunnerOutputPort();` 추가
- [ ] core 또는 pipeline이 CLI 패키지를 import하지 않아도 output 계약을 사용할 수 있게 import 경로 정리
- [ ] `apps/runner/test/oto_core_test.dart`에 no-op/recording output 테스트 추가

### 테스트 작성

- 작성: `apps/runner/test/oto_core_test.dart`
- 테스트 이름: `ExecutionContext exposes no-op runner output port`
- 목표: 기본 output port 호출이 예외 없이 완료되고 explicit context별로 output port를 교체할 수 있음을 검증한다.

### 중간 검증

```bash
cd apps/runner && dart analyze
cd apps/runner && dart test test/oto_core_test.dart
```

## 최종 검증

```bash
cd apps/runner && dart analyze
cd apps/runner && dart test test/oto_application_test.dart test/oto_context_test.dart test/oto_core_test.dart test/oto_system_runtime_test.dart
```

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