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

# Plan - REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- Task ids:
  - `pipeline`: pipeline domain이 CLI 구현체 대신 output port를 사용하도록 분리한다. 검증: runner analyze와 pipeline 관련 테스트가 통과한다.
- Completion mode: check-on-pass

## 배경

이 작업은 `01_output_port_contract` 완료 후 진행한다. pipeline domain에서 CLI 출력 구현 import와 `Application.instance.printBuildStep` 직접 호출을 제거하고 explicit `ExecutionContext.output`을 사용한다.

## Roadmap Dependency

- predecessor: `agent-task/archive/2026/06/m-runner-output-boundary/01_output_port_contract/complete.log`
- 상태: 충족. `01_output_port_contract`가 PASS 완료되어 milestone `[port]` Task에 반영되었으므로 이 subtask는 실행 가능하다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.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-test/local/pipeline-smoke.md`
- `agent-test/local/core-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/test/oto_core_test.dart`

### 테스트 환경 규칙

- test_env: `local`
- 읽은 env rule: `agent-test/local/rules.md`
- 매칭 profile: `pipeline-smoke`, `core-smoke`
- 적용 명령: `cd apps/runner && dart analyze`, `cd apps/runner && dart test`, 최소 `test/oto_core_test.dart`
- pipeline 전용 테스트가 확인되지 않아 변경 영향이 있는 경우 runner 전체 test가 필요하다는 규칙을 적용한다.

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

- pipeline 출력이 explicit context output으로 들어가는지 확인하는 테스트가 없다. `apps/runner/test/oto_core_test.dart`에 recording output 기반 regression test를 추가한다.
- CLI 출력 보존은 다음 `03` 작업에서 검증한다.

### 심볼 참조

- 제거 대상 direct dependency:
  - `apps/runner/lib/oto/pipeline/pipeline_exe.dart`: `package:oto/cli/cli.dart`, `Application.instance.printBuildStep`
  - `apps/runner/lib/oto/pipeline/pipeline_exe_handle.dart`: `package:oto/cli/cli.dart`, `Application.instance.printBuildStep`
  - `apps/runner/lib/oto/pipeline/pipeline_foreach.dart`: `package:oto/cli/cli.dart`, `CLI.println`
- `Application.log`는 로그 파일 경계로 남길 수 있으나 output port와 중복될 경우 최소 변경으로 유지한다.

### 분할 판단

- split decision policy 평가 완료.
- 이 subtask는 `02+01_pipeline_output_port`이며 predecessor `01`이 필요하다.
- `03+01,02_core_cli_adapter`는 이 작업 완료 후 DataComposer/Application/CLI adapter를 마무리한다.

### 범위 결정 근거

- pipeline domain만 수정한다.
- DataComposer compose signature와 Application CLI adapter 연결은 제외한다.
- command domain의 `CLI.println` 직접 호출은 마일스톤 범위 밖이다.

### 빌드 등급

- build lane: `local-G06`
- review lane: `local-G06`
- 근거: call-site 수는 적지만 pipeline 실행 이벤트와 explicit context 회귀 위험이 있어 focused regression test가 필요하다.

## 구현 체크리스트

- [ ] `01_output_port_contract` complete.log를 확인한다.
- [ ] `pipeline_exe.dart`, `pipeline_exe_handle.dart`, `pipeline_foreach.dart`에서 CLI import와 CLI direct call을 제거한다.
- [ ] pipeline 출력은 `runtimeContext.output.line` 또는 `runtimeContext.output.buildStep`을 사용한다.
- [ ] explicit context output regression test를 추가한다.
- [ ] `cd apps/runner && dart analyze`, `cd apps/runner && dart test test/oto_core_test.dart`, `cd apps/runner && dart test`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 디렉터리명 `02+01_pipeline_output_port` 기준 predecessor는 `01_output_port_contract` 하나다.
- 구현 시작 전 `agent-task/m-runner-output-boundary/01_output_port_contract/complete.log` 또는 archive의 같은 subtask complete.log를 확인한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/runner/lib/oto/pipeline/pipeline_exe.dart` | REFACTOR-1 |
| `apps/runner/lib/oto/pipeline/pipeline_exe_handle.dart` | REFACTOR-1 |
| `apps/runner/lib/oto/pipeline/pipeline_foreach.dart` | REFACTOR-1 |
| `apps/runner/test/oto_core_test.dart` | REFACTOR-1 |

## [REFACTOR-1] Pipeline Output Port Rollout

### 문제

[pipeline_exe.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline_exe.dart:43)는 condition 출력에 `CLI.println`을 직접 호출하고, [pipeline_exe.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline_exe.dart:93)와 [pipeline_exe_handle.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline_exe_handle.dart:114)는 `Application.instance.printBuildStep`을 직접 호출한다. [pipeline_foreach.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline_foreach.dart:116)도 CLI를 직접 호출한다.

Before:

```dart
// apps/runner/lib/oto/pipeline/pipeline_exe.dart:41
static Future printCondition(...) async {
  await CLI.println('[Pipeline-$syntax]', color: Color.magentaStrong);
  Application.log(...);
}
```

### 해결 방법

`PipelineExecutor.runtimeContext.output`을 사용한다. 색은 `RunnerOutputStyle.progress` 등 선행 계약의 neutral style로 매핑한다.

After:

```dart
await runtimeContext.output.line(
  '[Pipeline-$syntax]',
  style: RunnerOutputStyle.progress,
);
```

`printCondition`은 static이라 context를 받을 수 없으면 signature에 `ExecutionContext? context`를 추가하고 call site에서 `runtimeContext`를 넘긴다. 호출부를 모두 검색해 누락 없이 갱신한다.

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

- [ ] `pipeline_exe.dart` import에서 `package:oto/cli/cli.dart` 제거
- [ ] `pipeline_exe_handle.dart` import에서 `package:oto/cli/cli.dart` 제거
- [ ] `pipeline_foreach.dart` import에서 `package:oto/cli/cli.dart` 제거
- [ ] `Application.instance.printBuildStep` 직접 호출을 output port 호출로 대체
- [ ] `CLI.println` 직접 호출을 output port 호출로 대체
- [ ] `apps/runner/test/oto_core_test.dart`에 recording output으로 `Phase Start` 또는 foreach completion output 검증 추가

### 테스트 작성

- 작성: `apps/runner/test/oto_core_test.dart`
- 테스트 이름 후보: `pipeline emits progress through explicit context output port`
- 목표: explicit `ExecutionContext`에 recording output을 주입한 뒤 pipeline 실행 시 output이 해당 context로 들어오고 singleton CLI 경계에 의존하지 않음을 확인한다.

### 중간 검증

```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
```

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