feat(runner): output port 경계 작업을 반영한다

runner 출력 경계 분리의 첫 계약 작업을 완료하고 후속 subtask와 로드맵 상태를 함께 기록한다. workspace 포트와 local smoke 기준도 독립 Control Plane 방향에 맞춰 정리한다.
This commit is contained in:
toki 2026-06-07 13:12:46 +09:00
parent 69248c805d
commit 365995900e
21 changed files with 1218 additions and 31 deletions

View file

@ -19,6 +19,21 @@ YAML 기반 CI/CD 파이프라인 자동화 도구 OTO의 Monorepo 프로젝트
루트 `Makefile`을 통해 모든 서브프로젝트의 의존성 설치, 분석, 테스트 작업을 통합 관리할 수 있습니다. 루트 `Makefile`을 통해 모든 서브프로젝트의 의존성 설치, 분석, 테스트 작업을 통합 관리할 수 있습니다.
## Workspace 포트와 환경 기준
OTO는 독립 Control Plane 제품 경로를 기준으로 포트와 환경값을 기록합니다.
iop Edge 또는 wire 포트는 OTO workspace 포트 표준의 source of truth가 아닙니다.
| 표면 | 표준 후보 | 호환 기준 |
|------|-----------|-----------|
| OTO Core HTTP | backend host publish `18020` | 로컬 실행 기본값 `OTO_CORE_ADDR` 미설정 시 `127.0.0.1:8080`은 기존 개발 호환 경로로 유지 |
| Flutter client/web preview | frontend preview `13020` | preview 포트는 UI dev server 포트이며, 서버 연결 URL은 `OTO_SERVER_HTTP_URL`로 별도 지정 |
| runner bootstrap/release | `OTO_RUNNER_RELEASE_BASE_URL` | release base URL은 `https://`만 기록하고, token/credential/private endpoint 원문은 tracked 문서에 남기지 않음 |
Core를 workspace publish 포트로 띄울 때는 `OTO_CORE_ADDR=0.0.0.0:18020`처럼 listen 주소를 명시합니다.
Flutter web preview는 `cd apps/client && flutter run -d chrome --web-port=13020`을 기준 후보로 둡니다.
bootstrap command와 artifact URL 문서는 public validation 기준과 source of truth만 남기고, enrollment token이나 private release host는 런타임 환경에서만 주입합니다.
### 의존성 설치 (Setup) ### 의존성 설치 (Setup)
각 앱/서비스의 패키지 및 모듈 의존성을 설치합니다. 각 앱/서비스의 패키지 및 모듈 의존성을 설치합니다.
@ -54,4 +69,3 @@ make test
- [OTO Runner README](apps/runner/README.md) - [OTO Runner README](apps/runner/README.md)
- [OTO Client README](apps/client/README.md) - [OTO Client README](apps/client/README.md)
- OTO Core Server: [services/core/](services/core/) - OTO Core Server: [services/core/](services/core/)

View file

@ -17,9 +17,9 @@ OTO는 YAML 기반 빌드/배포 파이프라인을 실행하는 Dart CLI에서
- [폐기] Edge 직접 연결 기반 `oto-agent` - [폐기] Edge 직접 연결 기반 `oto-agent`
- 경로: `agent-roadmap/archive/phase/edge-direct-oto-agent/PHASE.md` - 경로: `agent-roadmap/archive/phase/edge-direct-oto-agent/PHASE.md`
- 요약: OTO가 iop Edge와 proto-socket 기반 outbound 통신을 하는 경로는 검증 근거만 보존하고, 독립 OTO Control Plane 방향으로 전환하며 기본 제품 경로에서는 폐기한다. - 요약: OTO가 iop Edge와 proto-socket 기반 outbound 통신을 하는 경로는 검증 근거만 보존하고, 독립 OTO Control Plane 방향으로 전환하며 기본 제품 경로에서는 폐기한다.
- [검토중] 독립 Control Plane 기반 OTO - [진행중] 독립 Control Plane 기반 OTO
- 경로: `agent-roadmap/phase/independent-control-plane/PHASE.md` - 경로: `agent-roadmap/phase/independent-control-plane/PHASE.md`
- 요약: OTO를 iop 직접 연결 구조에서 분리해 `apps/runner`, `apps/client`, `services/core`를 가진 독립 CI/CD runner/control plane 제품 구조로 전환다. - 요약: OTO를 iop 직접 연결 구조에서 분리해 `apps/runner`, `apps/client`, `services/core`를 가진 독립 CI/CD runner/control plane 제품 구조로 전환하며, 현재는 runner 출력 경계 분리를 진행 중이다.
- [검토중] 메시지 기반 빌드 에이전트 - [검토중] 메시지 기반 빌드 에이전트
- 경로: `agent-roadmap/phase/message-based-build-agent/PHASE.md` - 경로: `agent-roadmap/phase/message-based-build-agent/PHASE.md`
- 요약: `oto agent` 또는 `oto daemon` 모드에서 Edge와 양방향 메시지 통신을 사용하며, OTO를 build/deploy 전용 domain agent로 노출한다. - 요약: `oto agent` 또는 `oto daemon` 모드에서 Edge와 양방향 메시지 통신을 사용하며, OTO를 build/deploy 전용 domain agent로 노출한다.

View file

@ -2,7 +2,7 @@
## 상태 ## 상태
[진행중] [완료]
## 목표 ## 목표
@ -38,4 +38,17 @@
- [x] [project-map] 프로젝트 구조, 패키지 경계, 주요 entrypoint 설명을 현재 모노레포 경로로 갱신한다. - [x] [project-map] 프로젝트 구조, 패키지 경계, 주요 entrypoint 설명을 현재 모노레포 경로로 갱신한다.
- [x] [sdk-deps] Dart/Flutter/Go dependency와 SDK 설명을 실제 package 기준으로 정리한다. - [x] [sdk-deps] Dart/Flutter/Go dependency와 SDK 설명을 실제 package 기준으로 정리한다.
- [x] [domain-paths] domain rule의 파일 경로 예시와 책임 경계 설명이 현재 코드 위치와 맞는지 검토해 갱신한다. - [x] [domain-paths] domain rule의 파일 경로 예시와 책임 경계 설명이 현재 코드 위치와 맞는지 검토해 갱신한다.
- [ ] [test-rules] local test rule과 smoke 문서가 모노레포 target 이름과 일치하는지 확인한다. - [x] [test-rules] local test rule과 smoke 문서가 모노레포 target 이름과 일치하는지 확인한다.
## 완료 리뷰
- 상태: 승인됨
- 요청일: 2026-06-07
- 완료 근거:
- `Makefile`의 runner/client/core/proto target과 `agent-test/local/rules.md`의 Monorepo Targets가 일치함을 확인했다.
- `agent-test/local` smoke 문서의 runner test 파일 참조와 실제 `apps/runner/test/**` 파일 존재를 확인했다.
- common local, agent-smoke, framework-smoke 환경 설명을 Dart/Flutter/Go monorepo 기준으로 보정했다.
- 리뷰 필요:
- [x] 사용자가 완료 결과를 확인했다
- [x] archive 이동을 승인했다
- 리뷰 코멘트: 사용자 요청에 따라 Milestone을 완료 처리하고 archive로 이동한다.

View file

@ -0,0 +1,67 @@
# Milestone: 워크스페이스 포트/환경 표준화
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/independent-control-plane/PHASE.md`
## 목표
OTO의 Core HTTP, Flutter client/dev preview, runner/local test, bootstrap/release URL 환경을 workspace 공통 포트 대역으로 정리한다. OTO는 iop Edge 직접 연결 경로가 아니라 독립 Control Plane/runner 제품이므로, IOP wire 포트와 섞이지 않는 backend/frontend 대역 기준을 먼저 문서화한다.
## 상태
[완료]
## 구현 잠금
- 상태: 해제
- 결정 필요: 없음
## 범위
- OTO Core HTTP 기본 listen과 host publish 후보 정리
- Flutter client/web preview 후보 정리
- runner/local typed-command test 환경과 서버 선택 연결 경계 정리
- `OTO_RUNNER_RELEASE_BASE_URL`, bootstrap command, artifact URL 같은 환경값의 public/private 기록 기준 정리
- IOP Edge 직접 연결 폐기 경로와 독립 OTO Control Plane 포트 경계를 분리해 문서화
## 기능
### Epic: [inventory] OTO environment inventory
OTO의 독립 Control Plane과 runner가 사용하는 환경 변수를 workspace 공통 대역으로 정렬한다.
- [x] [core-port] OTO Core host publish 후보를 backend `18020`으로 문서화하고 기존 local default `127.0.0.1:8080`과의 compatibility note를 남긴다.
- [x] [client-preview] Flutter/web preview 후보를 frontend `13020`으로 정리한다.
- [x] [bootstrap-env] runner release/base URL, bootstrap command, artifact URL 환경값은 secret 없이 source of truth와 validation 기준만 남긴다.
- [x] [test-docs] `agent-test/local`과 smoke 문서가 독립 OTO Control Plane 포트와 iop 폐기 경로를 혼동하지 않도록 체크리스트를 만든다.
## 완료 리뷰
- 상태: 승인됨
- 요청일: 2026-06-07
- 완료 근거:
- `README.md`에 OTO Core `18020`, Flutter preview `13020`, `OTO_RUNNER_RELEASE_BASE_URL` 검증 기준과 secret 기록 금지 기준을 추가했다.
- `apps/client/README.md`, `apps/runner/README.md`에 client preview와 agent bootstrap 환경 기준을 반영했다.
- `agent-test/local/rules.md``agent-test/local/agent-smoke.md`에 독립 OTO Control Plane 포트/환경 체크리스트와 services/core bootstrap 검증 기준을 추가했다.
- 리뷰 필요:
- [x] 사용자가 완료 결과를 확인했다
- [x] archive 이동을 승인했다
- 리뷰 코멘트: 사용자 요청에 따라 Milestone을 완료 처리하고 archive로 이동한다.
## 범위 제외
- OTO Core API 구현 변경
- runner job/log/artifact 기능 구현
- iop Edge 직접 domain agent 경로 복원
- private release host, credential, token 기록
## 작업 컨텍스트
- 관련 경로: `services/core/`, `apps/client/`, `apps/runner/`, `packages/flutter/oto_console/`, `agent-test/local/`
- 표준선(선택): OTO는 독립 Control Plane 포트를 소유하고, IOP wire/Edge 포트와 같은 런타임으로 묶지 않는다.
- 현재 동기화: `18020`/`13020` 표준은 root/client/runner README와 local smoke 기준에 반영되었고, Milestone 완료 승인 후 archive로 이동한다.
- 선행 작업: workspace 공통 포트 inventory 정리
- 후속 작업: env/test smoke 갱신과 drift 방지
- 확인 필요: 없음

View file

@ -2,7 +2,7 @@
## 상태 ## 상태
[검토중] [진행중]
## 목표 ## 목표
@ -18,10 +18,13 @@ OTO를 iop Edge에 직접 붙는 domain agent가 아니라, 독립 실행 가능
- [완료] 독립 Control Plane 분리 마이그레이션 - [완료] 독립 Control Plane 분리 마이그레이션
- 경로: `agent-roadmap/archive/phase/independent-control-plane/milestones/control-plane-separation-migration.md` - 경로: `agent-roadmap/archive/phase/independent-control-plane/milestones/control-plane-separation-migration.md`
- 요약: iop에 구현된 OTO 등록/heartbeat/bootstrap 구조를 OTO 전용 Server/Runner 계약으로 이관하고, repo를 `apps/runner`, `apps/client`, `services/core` monorepo로 전환한다. - 요약: iop에 구현된 OTO 등록/heartbeat/bootstrap 구조를 OTO 전용 Server/Runner 계약으로 이관하고, repo를 `apps/runner`, `apps/client`, `services/core` monorepo로 전환한다.
- [진행중] agent-ops 규칙 모노레포 정합화 - [완료] 워크스페이스 포트/환경 표준화
- 경로: `agent-roadmap/phase/independent-control-plane/milestones/ops-rule-monorepo-alignment.md` - 경로: `agent-roadmap/archive/phase/independent-control-plane/milestones/workspace-port-env-standardization.md`
- 요약: OTO Core, Flutter client, runner/local test, bootstrap/release URL 포트를 workspace 공통 대역과 독립 Control Plane 기준으로 정리한다.
- [완료] agent-ops 규칙 모노레포 정합화
- 경로: `agent-roadmap/archive/phase/independent-control-plane/milestones/ops-rule-monorepo-alignment.md`
- 요약: 프로젝트 규칙과 domain rule을 현재 `apps/runner`, `apps/client`, `services/core`, `packages/flutter/oto_console` 구조에 맞게 최신화한다. - 요약: 프로젝트 규칙과 domain rule을 현재 `apps/runner`, `apps/client`, `services/core`, `packages/flutter/oto_console` 구조에 맞게 최신화한다.
- [계획] runner 출력 경계 분리 - [진행중] runner 출력 경계 분리
- 경로: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md` - 경로: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- 요약: pipeline/core 실행 로직의 CLI 출력 직접 의존을 output/progress port와 adapter 구조로 분리한다. - 요약: pipeline/core 실행 로직의 CLI 출력 직접 의존을 output/progress port와 adapter 구조로 분리한다.
- [계획] oto_console 계약 경계 정리 - [계획] oto_console 계약 경계 정리

View file

@ -2,7 +2,7 @@
## 상태 ## 상태
[계획] [진행중]
## 목표 ## 목표
@ -35,7 +35,7 @@ pipeline/core 실행 로직이 CLI 출력 구현에 직접 의존하지 않도
### Epic: [output] output port boundary ### Epic: [output] output port boundary
- [ ] [port] runner 실행 progress와 일반 출력에 필요한 최소 output port 계약을 정의한다. - [x] [port] runner 실행 progress와 일반 출력에 필요한 최소 output port 계약을 정의한다.
- [ ] [pipeline] pipeline domain이 CLI 구현체 대신 output port를 사용하도록 분리한다. 검증: runner analyze와 pipeline 관련 테스트가 통과한다. - [ ] [pipeline] pipeline domain이 CLI 구현체 대신 output port를 사용하도록 분리한다. 검증: runner analyze와 pipeline 관련 테스트가 통과한다.
- [ ] [core] data composition 경로의 printer/color 의존을 output port 또는 domain-neutral 계약으로 정리한다. - [ ] [core] data composition 경로의 printer/color 의존을 output port 또는 domain-neutral 계약으로 정리한다.
- [ ] [cli-adapter] 기존 CLI 출력 의미를 유지하는 adapter를 연결한다. 검증: CLI smoke 출력이 회귀하지 않는다. - [ ] [cli-adapter] 기존 CLI 출력 의미를 유지하는 adapter를 연결한다. 검증: CLI smoke 출력이 회귀하지 않는다.

View file

@ -0,0 +1,141 @@
<!-- task=m-runner-output-boundary/01_output_port_contract plan=1 tag=REFACTOR -->
# Code Review Reference - REFACTOR
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
## 개요
date=2026-06-07
task=m-runner-output-boundary/01_output_port_contract, plan=1, tag=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
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
1. 판정을 append한다.
2. `CODE_REVIEW-local-G06.md` → `code_review_local_G06_N.log`, `PLAN-local-G06.md` → `plan_local_G06_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-runner-output-boundary/01_output_port_contract/`로 이동한다.
4. PASS이고 task group이 `m-runner-output-boundary`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Output Port Contract | [x] |
## 구현 체크리스트
- [x] output port 계약과 no-op 구현을 core 소유 파일에 추가한다.
- [x] `ExecutionContext`가 output port를 보유하고 reset 없이 explicit context별 output을 유지하게 한다.
- [x] output port 기본 동작을 검증하는 core 테스트를 추가한다.
- [x] `cd apps/runner && dart analyze`와 지정 core 테스트를 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리를 archive로 이동한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
없음. 계획에 명시된 심볼, 파일 경로, 테스트 이름을 그대로 따랐다.
## 주요 설계 결정
- `RunnerOutputPort`는 CLI 패키지를 전혀 import하지 않는 독립 파일(`core/output_port.dart`)로 분리했다.
- `NoopRunnerOutputPort`는 `const` 생성자를 가지므로 `ExecutionContext`의 기본값으로 const 인스턴스를 사용한다.
- `ExecutionContext.output`은 non-final 필드로 두어 explicit context별 교체를 지원하되, reset 메서드는 추가하지 않았다 (계획 범위 외).
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- output port 계약이 `cli` 패키지를 import하지 않는지 확인한다.
- `ExecutionContext` 기본 output이 테스트/명시 context에서 안전한 no-op인지 확인한다.
- 후속 subtask가 사용할 public symbol 이름이 과도하게 CLI에 묶이지 않는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### REFACTOR-1 중간 검증
```bash
$ cd apps/runner && dart analyze
Analyzing runner...
No issues found!
$ cd apps/runner && dart test test/oto_core_test.dart
00:00 +29: All tests passed!
```
### 최종 검증
```bash
$ cd apps/runner && dart analyze
Analyzing runner...
No issues found!
$ 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
00:03 +71: All tests passed!
```
## 코드리뷰 결과
### 종합 판정: PASS
### 차원별 평가
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- plan deviation: Pass
- verification trust: Pass
### 발견된 문제
없음
### 다음 단계
PASS이므로 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.

View file

@ -0,0 +1,41 @@
# Complete - m-runner-output-boundary/01_output_port_contract
## 완료 일시
2026-06-07
## 요약
runner output port 계약, no-op 구현, ExecutionContext 보유 필드, core 테스트를 추가했고 1회 리뷰에서 PASS로 종료했다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_local_G06_0.log` | `code_review_local_G06_0.log` | PASS | output port 계약과 검증이 계획 범위를 충족했다. |
## 구현/정리 내용
- `RunnerOutputStyle`, `RunnerOutputPort`, `NoopRunnerOutputPort`를 core 소유 계약으로 추가했다.
- `ExecutionContext`가 기본 no-op output port를 보유하도록 했다.
- `ExecutionContext exposes no-op runner output port` 테스트로 기본 no-op 호출과 explicit context별 output 교체를 검증했다.
## 최종 검증
- `cd apps/runner && dart analyze` - PASS; `No issues found!`
- `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` - PASS; `All tests passed!` (`+71`)
## Roadmap Completion
- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- Completed task ids:
- `port`: PASS; evidence=`agent-task/archive/2026/06/m-runner-output-boundary/01_output_port_contract/plan_local_G06_0.log`, `agent-task/archive/2026/06/m-runner-output-boundary/01_output_port_contract/code_review_local_G06_0.log`; verification=`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`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,180 @@
<!-- 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,108 @@
<!-- task=m-runner-output-boundary/02+01_pipeline_output_port plan=1 tag=REFACTOR -->
# Code Review Reference - REFACTOR
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> Do not ask the user directly; record blockers in `사용자 리뷰 요청`.
> Finalization is review-agent-only.
## 개요
date=2026-06-07
task=m-runner-output-boundary/02+01_pipeline_output_port, plan=1, tag=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
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
1. predecessor complete.log 확인 여부를 검증한다.
2. 판정을 append한다.
3. PASS이면 plan/review log화, complete.log 작성, archive 이동을 수행한다.
4. roadmap 수정은 런타임 책임이다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Pipeline Output Port Rollout | [ ] |
## 구현 체크리스트
- [ ] `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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] active review/plan 파일을 `.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [ ] PASS split 작업이면 parent 유지/정리 상태를 확인한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- pipeline 도메인에서 `package:oto/cli/cli.dart` import가 사라졌는지 확인한다.
- `Application.instance.printBuildStep` 직접 호출이 pipeline에 남지 않았는지 확인한다.
- explicit context output 테스트가 singleton Application output에 기대지 않는지 확인한다.
## 검증 결과
### REFACTOR-1 중간 검증
```bash
$ cd apps/runner && dart analyze
(output)
$ cd apps/runner && dart test test/oto_core_test.dart
(output)
```
### 최종 검증
```bash
$ cd apps/runner && dart analyze
(output)
$ cd apps/runner && dart test
(output)
```
## 코드리뷰 결과
_리뷰 에이전트가 append한다._

View file

@ -0,0 +1,160 @@
<!-- 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/m-runner-output-boundary/01_output_port_contract/complete.log`
- 상태: 작성 시점에는 미충족. 구현 시작 전 active 또는 archive complete.log를 확인한다.
## 분석 결과
### 읽은 파일
- `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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,130 @@
<!-- task=m-runner-output-boundary/03+01,02_core_cli_adapter plan=1 tag=REFACTOR -->
# Code Review Reference - REFACTOR
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> Do not ask the user directly; record blockers in `사용자 리뷰 요청`.
> Finalization is review-agent-only.
## 개요
date=2026-06-07
task=m-runner-output-boundary/03+01,02_core_cli_adapter, plan=1, tag=REFACTOR
## Roadmap Targets
- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- Task ids:
- `core`: data composition 경로의 printer/color 의존을 output port 또는 domain-neutral 계약으로 정리한다.
- `cli-adapter`: 기존 CLI 출력 의미를 유지하는 adapter를 연결한다. 검증: CLI smoke 출력이 회귀하지 않는다.
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
1. predecessor complete.log 2개 확인 여부를 검증한다.
2. 판정을 append한다.
3. PASS이면 plan/review log화, complete.log 작성, archive 이동을 수행한다.
4. roadmap 수정은 런타임 책임이다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] DataComposer Neutral Output | [ ] |
| [REFACTOR-2] CLI Adapter Wiring | [ ] |
## 구현 체크리스트
- [ ] `01_output_port_contract``02+01_pipeline_output_port` complete.log를 확인한다.
- [ ] DataComposer/Jenkins collector signature를 CLI `Color` 없는 output port 또는 domain-neutral style 계약으로 바꾼다.
- [ ] Application이 build 시작 시 context output을 CLI adapter로 설정하고 `logEnable=false` 및 scheduler log 경계를 유지한다.
- [ ] CLI adapter가 기존 `CLI.printString`, `CLI.println`, build step banner 의미를 보존하도록 연결한다.
- [ ] `apps/runner/lib/oto/core/data_composer.dart``apps/runner/lib/oto/pipeline/**``package:oto/cli` import 또는 `CLI.` direct call이 남지 않게 한다.
- [ ] core/CLI 관련 테스트와 CLI smoke를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] active review/plan 파일을 `.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [ ] PASS split 작업이면 parent 유지/정리 상태를 확인한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `data_composer.dart`에서 CLI import와 CLI `Color` type이 제거됐는지 확인한다.
- `Application`이 기존 scheduler logFunc, `logEnable=false`, validate JSON no-log 경계를 깨지 않았는지 확인한다.
- 최종 `rg` 검증이 no-match 성공 조건으로 기록됐는지 확인한다.
## 검증 결과
### REFACTOR-1 중간 검증
```bash
$ cd apps/runner && dart analyze
(output)
$ cd apps/runner && dart test test/oto_core_test.dart test/oto_application_test.dart
(output)
```
### REFACTOR-2 중간 검증
```bash
$ cd apps/runner && dart analyze
(output)
$ cd apps/runner && dart test test/oto_application_test.dart test/oto_cli_runtime_test.dart test/oto_validate_cli_test.dart
(output)
```
### 최종 검증
```bash
$ cd apps/runner && dart analyze
(output)
$ 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
(output)
$ cd apps/runner && dart test test/oto_cli_runtime_test.dart test/oto_catalog_cli_test.dart test/oto_validate_cli_test.dart
(output)
$ cd apps/runner && dart run bin/main.dart --help
(output)
$ ! rg --sort path -n "package:oto/cli|CLI\\.|Color\\." apps/runner/lib/oto/pipeline apps/runner/lib/oto/core/data_composer.dart
(output)
```
## 코드리뷰 결과
_리뷰 에이전트가 append한다._

View file

@ -0,0 +1,205 @@
<!-- task=m-runner-output-boundary/03+01,02_core_cli_adapter plan=1 tag=REFACTOR -->
# Plan - REFACTOR
## Roadmap Targets
- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- Task ids:
- `core`: data composition 경로의 printer/color 의존을 output port 또는 domain-neutral 계약으로 정리한다.
- `cli-adapter`: 기존 CLI 출력 의미를 유지하는 adapter를 연결한다. 검증: CLI smoke 출력이 회귀하지 않는다.
- Completion mode: check-on-pass
## 배경
이 작업은 output port 계약과 pipeline rollout이 끝난 뒤 Application/DataComposer/CLI adapter를 연결하는 마무리 단계다. 완료 후 `apps/runner/lib/oto/core/data_composer.dart``apps/runner/lib/oto/pipeline/**`는 CLI 구현 타입을 직접 import하지 않아야 한다.
## Roadmap Dependency
- predecessor: `agent-task/m-runner-output-boundary/01_output_port_contract/complete.log`
- predecessor: `agent-task/m-runner-output-boundary/02+01_pipeline_output_port/complete.log`
- 상태: 작성 시점에는 미충족. 구현 시작 전 active 또는 archive complete.log를 확인한다.
## 분석 결과
### 읽은 파일
- `agent-roadmap/current.md`
- `agent-roadmap/phase/independent-control-plane/milestones/runner-output-boundary.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/cli-smoke.md`
- `apps/runner/lib/oto/application.dart`
- `apps/runner/lib/oto/core/data_composer.dart`
- `apps/runner/lib/oto/core/execution_context.dart`
- `apps/runner/lib/cli/cli.dart`
- `apps/runner/lib/cli/printer.dart`
- `apps/runner/lib/cli/cli_style.dart`
- `apps/runner/test/oto_application_test.dart`
- `apps/runner/test/oto_core_test.dart`
- `apps/runner/test/oto_cli_runtime_test.dart`
- `apps/runner/test/oto_validate_cli_test.dart`
### 테스트 환경 규칙
- test_env: `local`
- env rule: `agent-test/local/rules.md`
- matched profiles: `core-smoke`, `cli-smoke`
- 적용 명령:
- `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`
- `cd apps/runner && dart test test/oto_cli_runtime_test.dart test/oto_catalog_cli_test.dart test/oto_validate_cli_test.dart`
- CLI smoke: `cd apps/runner && dart run bin/main.dart --help`
### 테스트 커버리지 공백
- DataComposer가 CLI `Color` 없이 output port를 사용하는지 직접 검증하는 테스트가 없다. `oto_application_test.dart` 또는 `oto_core_test.dart`에 fake output/data compose 경로 테스트를 추가한다.
- CLI adapter의 style mapping은 unit으로 직접 검증하기 어렵다. `dart run bin/main.dart --help`와 기존 validate JSON no-log test로 회귀를 확인한다.
### 심볼 참조
- 변경 대상:
- `DataComposer.compose(String?, Future Function(String, Color?) printer)`
- `JenkinsEnvironmentCollector.collect(Future Function(String, Color?) printer)`
- `Application.printBuildStep(String, Color?)`
- call sites:
- `Application.build` line 102 passes `printBuildStep`
- `DataComposerJenkins.compose` line 174 passes printer into collector
- `DataComposerJenkins.compose` line 179 calls printer
- `JenkinsEnvironmentCollector.collect` line 63 calls printer
- `Application.build` lines 123, 159, 160, 161, 169 use CLI or printBuildStep
### 분할 판단
- split decision policy 평가 완료.
- 이 subtask는 `03+01,02_core_cli_adapter`이며 `01`, `02` 완료 후 진행한다.
- core DataComposer 변경과 CLI adapter 연결은 같은 runtime entrypoint에서 맞물리므로 한 subtask로 묶는다.
### 범위 결정 근거
- command implementations의 direct CLI usage는 제외한다.
- agent domain output (`apps/runner/lib/oto/agent/**`)은 제외한다.
- 로그 포맷 전면 개편, remote log streaming, YAML 문법 변경은 제외한다.
### 빌드 등급
- build lane: `cloud-G07`
- review lane: `cloud-G07`
- 근거: Application build path, DataComposer, CLI output adapter가 교차하고 CLI JSON/log suppression 회귀 위험이 있어 더 큰 문맥과 세밀한 검증이 필요하다.
## 구현 체크리스트
- [ ] `01_output_port_contract``02+01_pipeline_output_port` complete.log를 확인한다.
- [ ] DataComposer/Jenkins collector signature를 CLI `Color` 없는 output port 또는 domain-neutral style 계약으로 바꾼다.
- [ ] Application이 build 시작 시 context output을 CLI adapter로 설정하고 `logEnable=false` 및 scheduler log 경계를 유지한다.
- [ ] CLI adapter가 기존 `CLI.printString`, `CLI.println`, build step banner 의미를 보존하도록 연결한다.
- [ ] `apps/runner/lib/oto/core/data_composer.dart``apps/runner/lib/oto/pipeline/**``package:oto/cli` import 또는 `CLI.` direct call이 남지 않게 한다.
- [ ] core/CLI 관련 테스트와 CLI smoke를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- 디렉터리명 `03+01,02_core_cli_adapter` 기준 predecessor는 `01_output_port_contract`, `02+01_pipeline_output_port`다.
- 구현 시작 전 두 predecessor의 active 또는 archived `complete.log`를 확인한다.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/runner/lib/oto/core/data_composer.dart` | REFACTOR-1 |
| `apps/runner/lib/oto/application.dart` | REFACTOR-1, REFACTOR-2 |
| `apps/runner/lib/cli/runner_output_adapter.dart` 또는 기존 CLI 파일 | REFACTOR-2 |
| `apps/runner/test/oto_application_test.dart` | REFACTOR-1, REFACTOR-2 |
| `apps/runner/test/oto_core_test.dart` | REFACTOR-1 |
## [REFACTOR-1] DataComposer Neutral Output
### 문제
[data_composer.dart](/config/workspace/oto/apps/runner/lib/oto/core/data_composer.dart:5)는 core 도메인인데 `package:oto/cli/cli.dart`를 import한다. [data_composer.dart](/config/workspace/oto/apps/runner/lib/oto/core/data_composer.dart:15)의 compose signature가 `Color`를 노출하고, [data_composer.dart](/config/workspace/oto/apps/runner/lib/oto/core/data_composer.dart:63)는 CLI color를 core error path로 사용한다.
Before:
```dart
// apps/runner/lib/oto/core/data_composer.dart:15
Future compose(String? yaml, Future Function(String, Color?) printer) async {
return simpleFuture;
}
```
### 해결 방법
선행 output port 계약을 받아 `DataComposer.compose(String? yaml, RunnerOutputPort output)` 형태 또는 equivalent domain-neutral callback으로 바꾼다. `RunnerOutputStyle.error/success`를 사용하고 CLI `Color`는 adapter에서만 매핑한다.
### 수정 파일 및 체크리스트
- [ ] `data_composer.dart`에서 `package:oto/cli/cli.dart` import 제거
- [ ] compose/collector signature를 output port 기반으로 변경
- [ ] unsupported OS/Jenkins Variables 출력이 output port를 통해 발생하도록 변경
- [ ] 관련 테스트에서 fake output으로 메시지를 검증
### 테스트 작성
- 작성: `apps/runner/test/oto_core_test.dart` 또는 `apps/runner/test/oto_application_test.dart`
- 목표: DataComposer/Jenkins collector가 CLI type 없이 output port로 message를 emit하는지 검증한다.
### 중간 검증
```bash
cd apps/runner && dart analyze
cd apps/runner && dart test test/oto_core_test.dart test/oto_application_test.dart
```
## [REFACTOR-2] CLI Adapter Wiring
### 문제
[application.dart](/config/workspace/oto/apps/runner/lib/oto/application.dart:8)는 core orchestration인데 CLI package를 import하고, [application.dart](/config/workspace/oto/apps/runner/lib/oto/application.dart:123), [application.dart](/config/workspace/oto/apps/runner/lib/oto/application.dart:159), [application.dart](/config/workspace/oto/apps/runner/lib/oto/application.dart:301)는 직접 CLI 출력과 `Color`를 사용한다. CLI 출력 의미는 유지하되 domain 실행 로직이 CLI 구현을 몰라야 한다.
Before:
```dart
// apps/runner/lib/oto/application.dart:123
await CLI.printString(
'$enter********************************* Build Data *************************************',
color: Color.magenta,
);
```
### 해결 방법
CLI domain에 `CliRunnerOutputPort` adapter를 추가한다. `Application`은 runner entrypoint로서 adapter를 context에 주입하되, pipeline/core domain은 adapter 구현을 직접 import하지 않는다. `Application.printBuildStep`는 adapter로 이동하거나 compatibility wrapper만 남긴다.
### 수정 파일 및 체크리스트
- [ ] `apps/runner/lib/cli/runner_output_adapter.dart` 추가 또는 `cli.dart` 내부 adapter 추가
- [ ] neutral style -> `Color` mapping을 adapter 안에 둔다
- [ ] `Application.build``_logEnable`과 scheduler logFunc 흐름을 유지하며 output adapter를 설정한다
- [ ] `Application.printBuildStep` 직접 사용을 제거하거나 deprecated wrapper로 제한한다
- [ ] validate `--json` stdout에 execute/build logs가 섞이지 않는 기존 테스트를 유지한다
### 테스트 작성
- 작성: `apps/runner/test/oto_application_test.dart`
- 보강: build success/failure가 output port로 build step을 emit하는지 또는 log disabled일 때 emit하지 않는지 검증한다.
- 기존: `apps/runner/test/oto_validate_cli_test.dart`의 JSON no execute logs 테스트를 유지한다.
### 중간 검증
```bash
cd apps/runner && dart analyze
cd apps/runner && dart test test/oto_application_test.dart test/oto_cli_runtime_test.dart test/oto_validate_cli_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
cd apps/runner && dart test test/oto_cli_runtime_test.dart test/oto_catalog_cli_test.dart test/oto_validate_cli_test.dart
cd apps/runner && dart run bin/main.dart --help
! rg --sort path -n "package:oto/cli|CLI\\.|Color\\." apps/runner/lib/oto/pipeline apps/runner/lib/oto/core/data_composer.dart
```
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -3,18 +3,19 @@ test_env: local
test_profile: agent-smoke test_profile: agent-smoke
domain: agent domain: agent
verification_type: smoke verification_type: smoke
last_rule_updated_at: 2026-06-05 last_rule_updated_at: 2026-06-07
--- ---
# agent-smoke local 테스트 # agent-smoke local 테스트
## 읽기 조건 ## 읽기 조건
- `apps/runner/lib/oto/agent/**`, `apps/runner/lib/cli/commands/command_agent.dart`, 또는 `apps/runner/assets/script/shell/oto_agent_bootstrap.sh` 변경 검증이 필요한 경우 - `apps/runner/lib/oto/agent/**`, `apps/runner/lib/cli/commands/command_agent.dart`, `apps/runner/assets/script/shell/oto_agent_bootstrap.sh`, `services/core/cmd/oto-core/main.go`, `services/core/internal/httpserver/**`, 또는 `services/core/internal/runnerregistry/**` 변경 검증이 필요한 경우
## 적용 범위 ## 적용 범위
- OTO agent 등록/설정/CLI/bootstrap smoke 경로 - OTO agent 등록/설정/CLI/bootstrap smoke 경로
- OTO Server runner registration, bootstrap command, release URL validation 경로
- legacy iop smoke는 별도 iop 쪽 OTO 제거 작업의 검증 대상이며, 이 OTO 독립 마이그레이션 agent-smoke 필수 검증에서는 제외한다. - legacy iop smoke는 별도 iop 쪽 OTO 제거 작업의 검증 대상이며, 이 OTO 독립 마이그레이션 agent-smoke 필수 검증에서는 제외한다.
## 분류 ## 분류
@ -26,19 +27,31 @@ last_rule_updated_at: 2026-06-05
## 환경 ## 환경
- host: - host:
- port: - port: backend host publish 후보 `18020`; local compatibility 기본값 `127.0.0.1:8080`
- runtime: Dart SDK - runtime: Dart SDK, Go toolchain
- package manager: dart pub - package manager: dart pub, Go modules
- docker: - docker:
- external service: 없음 - external service: 없음
- model endpoint: - model endpoint:
- credential: - credential:
## 포트/환경 체크리스트
- Core publish 검증은 `OTO_CORE_ADDR=0.0.0.0:18020` 기준 후보를 사용한다. `127.0.0.1:8080`은 기존 local default compatibility로만 남긴다.
- bootstrap command는 `--server-url`을 기준 인자로 문서화한다. `--edge-url`은 script 호환 alias로만 본다.
- `OTO_RUNNER_RELEASE_BASE_URL``https://` scheme만 허용한다. token, credential, private release host 원문은 테스트 규칙과 tracked 문서에 쓰지 않는다.
- iop Edge/wire endpoint는 독립 OTO Control Plane smoke의 필수 검증 대상이 아니다.
## 명령 ## 명령
- setup: `cd apps/runner && dart pub get` (또는 `make runner-get`) - setup:
- lint: `cd apps/runner && dart analyze` (또는 `make runner-analyze`) - runner: `cd apps/runner && dart pub get` (또는 `make runner-get`)
- unit: `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart` (또는 `make runner-test`) - core: `cd services/core && go mod download`
- lint:
- runner: `cd apps/runner && dart analyze` (또는 `make runner-analyze`)
- unit:
- runner: `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart` (또는 `make runner-test`)
- core: `cd services/core && go test ./...` (또는 `make core-test`)
- smoke: - smoke:
- e2e: - e2e:
- model: - model:
@ -46,17 +59,20 @@ last_rule_updated_at: 2026-06-05
## 필수 검증 ## 필수 검증
- agent 도메인 변경 후 `cd apps/runner && dart analyze`를 실행한다. - runner agent 도메인 변경 후 `cd apps/runner && dart analyze`를 실행한다.
- agent 도메인 변경 후 agent 관련 `cd apps/runner && dart test ...` 명령을 실행한다. - runner agent 도메인 변경 후 agent 관련 `cd apps/runner && dart test ...` 명령을 실행한다.
- services/core registration, bootstrap command, release URL validation 변경 후 `cd services/core && go test ./...`를 실행한다.
## 보조 검증 ## 보조 검증
- 전체 회귀가 필요한 경우 `cd apps/runner && dart test` 또는 `make runner-test`를 실행한다. - 전체 회귀가 필요한 경우 `cd apps/runner && dart test` 또는 `make runner-test`를 실행한다.
- OTO Server 전체 회귀가 필요한 경우 `make core-test`를 실행한다.
## 판정 기준 ## 판정 기준
- `cd apps/runner && dart analyze`가 issue 없이 종료한다. - `cd apps/runner && dart analyze`가 issue 없이 종료한다.
- 지정한 `cd apps/runner && dart test`가 모두 통과한다. - 지정한 `cd apps/runner && dart test`가 모두 통과한다.
- 지정한 `cd services/core && go test ./...`가 모두 통과한다.
## 기준 출력 예시 ## 기준 출력 예시

View file

@ -3,7 +3,7 @@ test_env: local
test_profile: framework-smoke test_profile: framework-smoke
domain: framework domain: framework
verification_type: smoke verification_type: smoke
last_rule_updated_at: 2026-06-05 last_rule_updated_at: 2026-06-07
--- ---
# framework-smoke local 테스트 # framework-smoke local 테스트
@ -26,8 +26,8 @@ last_rule_updated_at: 2026-06-05
- host: - host:
- port: - port:
- runtime: Dart SDK - runtime: Dart SDK, Flutter SDK, Go toolchain
- package manager: dart pub - package manager: dart pub, flutter pub, Go modules
- docker: - docker:
- external service: 없음 - external service: 없음
- model endpoint: - model endpoint:

View file

@ -1,6 +1,6 @@
--- ---
test_env: local test_env: local
last_rule_updated_at: 2026-06-05 last_rule_updated_at: 2026-06-07
--- ---
# local 테스트 규칙 # local 테스트 규칙
@ -19,13 +19,20 @@ last_rule_updated_at: 2026-06-05
- host: - host:
- port: - port:
- runtime: Dart SDK - runtime: Dart SDK, Flutter SDK, Go toolchain
- package manager: dart pub - package manager: dart pub, flutter pub, Go modules
- docker: - docker:
- external service: 없음 - external service: 없음
- model endpoint: - model endpoint:
- credential: - credential:
## Workspace 포트/환경 체크리스트
- OTO Core host publish 후보는 backend `18020`이다. 로컬 실행 기본값 `127.0.0.1:8080`은 compatibility 경로로만 해석한다.
- Flutter client/web preview 후보는 frontend `13020`이다. preview 포트와 `OTO_SERVER_HTTP_URL` 서버 연결 URL을 혼동하지 않는다.
- `OTO_RUNNER_RELEASE_BASE_URL`과 bootstrap command 검증은 source of truth와 validation 기준만 기록한다. secret, token, private release host 원문은 tracked 문서와 테스트 규칙에 남기지 않는다.
- 독립 OTO Control Plane 검증에서 iop Edge/wire 포트를 OTO workspace 포트 표준으로 간주하지 않는다.
## 라우팅 ## 라우팅
- agent / smoke / OTO agent CLI, OTO Server registration, bootstrap script: `agent-test/local/agent-smoke.md` - agent / smoke / OTO agent CLI, OTO Server registration, bootstrap script: `agent-test/local/agent-smoke.md`

View file

@ -28,3 +28,22 @@ dependencies:
Then mount any exported widget, for example `OtoConsoleShell` or Then mount any exported widget, for example `OtoConsoleShell` or
`OtoAgentPanel`, inside the host application's own workbench shell. `OtoAgentPanel`, inside the host application's own workbench shell.
## Local Preview
The workspace frontend preview port candidate is `13020`:
```bash
flutter run -d chrome --web-port=13020
```
The preview port is separate from the OTO Core server URL. When the client
needs to talk to a host-published Core server, pass the server URL with a
compile-time define:
```bash
flutter run -d chrome --web-port=13020 --dart-define=OTO_SERVER_HTTP_URL=http://localhost:18020
```
The current app-level fallback `http://localhost:8080` remains a local
compatibility default until the runtime default is intentionally migrated.

View file

@ -8,7 +8,7 @@ Jenkins, FTP, Git, Slack/Mattermost, iOS/Flutter/Android 빌드 등 다양한
## 현재 상태 ## 현재 상태
OTO CLI는 현재 Jenkins 실행, 로컬 YAML 파일 실행, 스케줄러 실행을 지원한다. OTO CLI는 현재 Jenkins 실행, 로컬 YAML 파일 실행, 스케줄러 실행을 지원한다.
이 실행 표면은 이후 구조화 출력, YAML validation, Edge agent 확장을 추가할 때도 깨지면 안 되는 호환 기준선이다. 이 실행 표면은 이후 구조화 출력, YAML validation, 독립 Control Plane agent 확장을 추가할 때도 깨지면 안 되는 호환 기준선이다.
상세 진행 상황과 다음 작업 후보는 [`agent-roadmap/current.md`](agent-roadmap/current.md)와 [`agent-roadmap/ROADMAP.md`](agent-roadmap/ROADMAP.md)에서 확인한다. 상세 진행 상황과 다음 작업 후보는 [`agent-roadmap/current.md`](agent-roadmap/current.md)와 [`agent-roadmap/ROADMAP.md`](agent-roadmap/ROADMAP.md)에서 확인한다.
--- ---
@ -287,12 +287,19 @@ scheduler:
상세 로드맵은 [`agent-roadmap/ROADMAP.md`](agent-roadmap/ROADMAP.md)에 둔다. 상세 로드맵은 [`agent-roadmap/ROADMAP.md`](agent-roadmap/ROADMAP.md)에 둔다.
README에는 장기 방향과 작업 진입 문서 경로만 유지한다. README에는 장기 방향과 작업 진입 문서 경로만 유지한다.
OTO의 장기 방향은 Jenkins 안에서 실행되는 CLI에 머물지 않고, Edge에 직접 붙는 가벼운 build/deploy agent까지 확장하는 것이다. OTO의 장기 방향은 Jenkins 안에서 실행되는 CLI에 머물지 않고, OTO Control Plane에 등록되는 가벼운 build/deploy agent까지 확장하는 것이다.
- 현재 활성 Milestone 후보는 [`agent-roadmap/current.md`](agent-roadmap/current.md)에서 확인한다. - 현재 활성 Milestone 후보는 [`agent-roadmap/current.md`](agent-roadmap/current.md)에서 확인한다.
- CLI 자동화 표면 정리: 기존 CLI 실행 모드와 YAML 파이프라인, 커맨드 확장, 단일 바이너리 배포 구조를 장기 호환 표면으로 정리한다. - CLI 자동화 표면 정리: 기존 CLI 실행 모드와 YAML 파이프라인, 커맨드 확장, 단일 바이너리 배포 구조를 장기 호환 표면으로 정리한다.
- Edge bootstrap 기반 `oto-agent`: Jenkins node 연결식 UX를 Edge bootstrap 기반 `oto-agent` 설치와 등록 계약으로 재해석한다. - 독립 Control Plane 기반 `oto-agent`: Jenkins node 연결식 UX를 OTO Server bootstrap 기반 `oto-agent` 설치와 등록 계약으로 재해석한다.
- 메시지 기반 빌드 에이전트: Edge가 OTO 파이프라인을 원격 제어할 수 있게 한다. - 메시지 기반 빌드 에이전트: OTO Server가 OTO 파이프라인을 원격 제어할 수 있게 한다.
### Agent bootstrap 환경 기준
- `OTO_RUNNER_RELEASE_BASE_URL`은 runner binary asset을 내려받는 release source of truth다.
- Core가 HTTP로 실행될 때는 release base URL을 자동 추론하지 않으며, 환경 변수로 주입한 값도 `https://` scheme이어야 한다.
- bootstrap command 예시는 public validation 기준만 문서화하고, enrollment token, private release host, credential 원문은 tracked 문서에 남기지 않는다.
- bootstrap script의 `--edge-url` 인자는 기존 호출 호환 alias로만 보며, 새 문서와 smoke 기준은 `--server-url`을 사용한다.
--- ---

View file

@ -1,4 +1,5 @@
import 'package:oto/oto/application.dart'; import 'package:oto/oto/application.dart';
import 'package:oto/oto/core/output_port.dart';
import 'package:oto/oto/data/command_data.dart'; import 'package:oto/oto/data/command_data.dart';
class StepEvent { class StepEvent {
@ -41,6 +42,7 @@ class ExecutionContext {
Map<String, dynamic> property = {}; Map<String, dynamic> property = {};
Map<String, CommandState> commandStates = {}; Map<String, CommandState> commandStates = {};
Map<String, DataCommand> dataCommandMap = {}; Map<String, DataCommand> dataCommandMap = {};
RunnerOutputPort output = const NoopRunnerOutputPort();
final List<StepEvent> stepEvents = []; final List<StepEvent> stepEvents = [];
int _nextStepId = 0; int _nextStepId = 0;

View file

@ -0,0 +1,33 @@
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 {}
}

View file

@ -2,6 +2,7 @@ import 'package:oto/oto/application.dart';
import 'package:oto/oto/commands/command.dart'; import 'package:oto/oto/commands/command.dart';
import 'package:oto/oto/commands/command_registry.dart'; import 'package:oto/oto/commands/command_registry.dart';
import 'package:oto/oto/core/execution_context.dart'; import 'package:oto/oto/core/execution_context.dart';
import 'package:oto/oto/core/output_port.dart';
import 'package:oto/oto/data/command_data.dart'; import 'package:oto/oto/data/command_data.dart';
import 'package:oto/oto/pipeline/pipeline.dart'; import 'package:oto/oto/pipeline/pipeline.dart';
import 'package:oto/oto/pipeline/pipeline_exe.dart'; import 'package:oto/oto/pipeline/pipeline_exe.dart';
@ -557,6 +558,22 @@ pipeline:
expect(failed.error, isNotNull); expect(failed.error, isNotNull);
expect(failed.error!['message'], contains('Test error message')); expect(failed.error!['message'], contains('Test error message'));
}); });
test('ExecutionContext exposes no-op runner output port', () async {
final ctx = ExecutionContext();
expect(ctx.output, isA<NoopRunnerOutputPort>());
await expectLater(ctx.output.line('hello'), completes);
await expectLater(
ctx.output.block('block', style: RunnerOutputStyle.success), completes);
await expectLater(
ctx.output.buildStep('step', style: RunnerOutputStyle.accent),
completes);
final recording = _RecordingOutputPort();
ctx.output = recording;
await ctx.output.line('msg', style: RunnerOutputStyle.warning);
expect(recording.lines, contains('msg'));
});
} }
class ThrowingExecutor extends PipelineExecutor { class ThrowingExecutor extends PipelineExecutor {
@ -567,3 +584,27 @@ class ThrowingExecutor extends PipelineExecutor {
@override @override
Future execute() => Future.error(Exception('Test error message')); Future execute() => Future.error(Exception('Test error message'));
} }
class _RecordingOutputPort implements RunnerOutputPort {
final List<String> lines = [];
final List<String> blocks = [];
final List<String> steps = [];
@override
Future<void> line(String message,
{RunnerOutputStyle style = RunnerOutputStyle.normal}) async {
lines.add(message);
}
@override
Future<void> block(String message,
{RunnerOutputStyle style = RunnerOutputStyle.normal}) async {
blocks.add(message);
}
@override
Future<void> buildStep(String name,
{RunnerOutputStyle style = RunnerOutputStyle.success}) async {
steps.add(name);
}
}