<!-- task=jenkins_compatibility_boundary/01_core_adapter plan=0 tag=REFACTOR -->

# Jenkins Core Adapter Boundary - REFACTOR

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

구현 후 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실제로 실행하고 출력 원문을 붙이며, active 파일은 그대로 둔 채 review 준비 상태를 보고한다. `complete.log` 작성, `.log` 아카이브, task 디렉터리 이동은 code-review-skill 전용이다.

## 배경

활성 Milestone은 Jenkins 실행 경로를 유지하되 장기 제어면이 Jenkins 전용 환경 변수에 묶이지 않게 하는 것이다. 현재 core에는 Jenkins 환경 수집, YAML 추출, 기본 workspace 계산, file/scheduler용 기본 `DataCommon`이 섞여 있다. 이 하위 작업은 Jenkins 호환 adapter를 분리하고 일반 실행 경로의 기본값을 Jenkins 이름에서 걷어내는 foundation이다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/command/rules.md`
- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/milestones/jenkins-compatibility-boundary.md`
- `lib/oto/application.dart`
- `lib/oto/core/data_composer.dart`
- `lib/oto/core/defined_data.dart`
- `lib/oto/core/system_runtime.dart`
- `lib/oto/core/tag_system.dart`
- `lib/oto/data/base_data.dart`
- `lib/oto/data/command_data.dart`
- `lib/oto/commands/command.dart`
- `test/oto_application_test.dart`
- `test/oto_system_runtime_test.dart`
- `pubspec.yaml`
- `analysis_options.yaml`

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

- Jenkins env 수집: `test/oto_system_runtime_test.dart:160`이 Linux happy path만 검증한다. env 수집 책임이 `DataComposerJenkins.compose()`에서 분리됐는지, Unix 로그 입력이 비어 있지 않은지 검증이 없다.
- 기본 workspace: `test/oto_application_test.dart:41`은 `property.workspace`가 있는 file build만 검증한다. `property.workspace`가 없을 때 Jenkins env 없이 local workspace로 채워지는 회귀 테스트가 없다.
- scheduler common data: scheduler build 경로가 `FileData.jenkinsMap` 이름에 기대는지 여부를 직접 검증하지 않는다. 이 검증은 후속 `02+01_file_scheduler_modes`에서 보강한다.

### 심볼 참조

- 이름을 제거하거나 rename하지 않는다.
- `DataComposerJenkins` 참조: `lib/oto/application.dart:47`, `test/oto_system_runtime_test.dart:186`.
- `Application.current` 참조: `lib/oto/application.dart:29`, `lib/oto/core/tag_system.dart:198`, `lib/oto/core/tag_system.dart:202`.
- `FileData.jenkinsMap` 참조: `lib/oto/application.dart:93`, `lib/oto/core/data_composer.dart:33`, `lib/oto/core/defined_data.dart:56`.

### 분할 판단

분할 정책을 먼저 평가했다. 이 작업은 core adapter 변경, file/scheduler 검증, 문서/샘플 갱신으로 소유 경계와 검증 전략이 다르므로 multi-plan으로 분리한다.

- 공유 task group: `jenkins_compatibility_boundary`
- `01_core_adapter`: Jenkins env 수집과 기본 workspace foundation. 선행 작업 없음.
- `02+01_file_scheduler_modes`: file/scheduler 실행 검증. `01_core_adapter`의 `complete.log` 이후 진행.
- `03+01,02_docs_samples`: README와 샘플 정리. `01_core_adapter`, `02+01_file_scheduler_modes`의 `complete.log` 이후 진행.

### 범위 결정 근거

- Jenkins mode 제거, `BuildType` enum 제거, Jenkins command 제거는 Milestone 범위 제외라 하지 않는다.
- `lib/oto/data/*.g.dart`는 데이터 모델 필드 변경이 없으므로 수정하지 않는다.
- command/pipeline 실행 로직은 Jenkins env 수집 경계 밖이므로 수정하지 않는다.
- `agent-task/archive/**`는 읽거나 수정하지 않는다.

### 빌드 등급

- build=`cloud-G07`, review=`cloud-G07`: Jenkins env shell/stdout 파싱과 CLI 실행 기본값에 닿는 terminal-agent 성격의 core 변경이다.

## 구현 체크리스트

- [ ] REFACTOR-1 Jenkins 환경 변수 수집 책임을 `DataComposerJenkins.compose()` 밖의 명시적 adapter로 분리한다.
- [ ] REFACTOR-2 file/scheduler/test 모드가 Jenkins 이름의 기본 데이터와 `Platform.environment['WORKSPACE']!`에 직접 기대지 않도록 기본 workspace 경계를 정리한다.
- [ ] 모든 중간 검증과 최종 검증을 실행하고 실제 stdout/stderr를 `CODE_REVIEW-cloud-G07.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## REFACTOR Items

### REFACTOR-1 Jenkins Env Adapter 분리

#### 문제

`lib/oto/core/data_composer.dart:39`부터 `DataComposerJenkins.compose()`가 OS 감지, Jenkins env JSON 생성, BuildData 추출, `DataCommon` 생성, 로그 출력을 모두 수행한다. `lib/oto/core/data_composer.dart:96-99`는 Unix 경로에서 `jsonStr`이 계속 빈 문자열이라 Jenkins variables 로그가 실제 수집값을 보여주지 않는다.

Before (`lib/oto/core/data_composer.dart:39`):

```dart
class DataComposerJenkins extends DataComposer {
  final SystemRuntime runtime;

  DataComposerJenkins({required this.runtime});

  @override
  Future compose(String? yaml, Future Function(String, Color?) printer) async {
    Map<String, dynamic>? jenkinsMap;
    String os = Platform.operatingSystem;
    var enable = true;
    Application.log('Current OS: $os');
    Application.log('Start build...');
    String jsonStr = '';
    if (runtime.isMacOS || runtime.isLinux) {
```

#### 해결 방법

`data_composer.dart` 안에 public adapter를 추가한다. 새 파일보다 기존 core 파일 안의 작은 타입으로 둔다.

After:

```dart
class JenkinsEnvironmentSnapshot {
  final Map<String, dynamic> variables;
  final String buildYaml;
  final String rawVariablesJson;

  const JenkinsEnvironmentSnapshot({
    required this.variables,
    required this.buildYaml,
    required this.rawVariablesJson,
  });
}

class JenkinsEnvironmentCollector {
  final SystemRuntime runtime;

  JenkinsEnvironmentCollector({required this.runtime});

  Future<JenkinsEnvironmentSnapshot?> collect(
      Future Function(String, Color?) printer) async {
    if (runtime.isMacOS || runtime.isLinux) {
      return _collectUnix();
    }
    if (runtime.isWindows) {
      return _collectWindows();
    }
    await printer('is not Mac/Windows/Linux. build must be execute in Mac/Windows os system', Color.redStrong);
    return null;
  }
}
```

`DataComposerJenkins.compose()`는 `JenkinsEnvironmentCollector.collect()` 결과를 받아 `commonData`, `buildYaml`, 로그만 설정한다. Windows `getBuildData()`는 collector 내부 helper로 이동하되 기존 동작과 `%PARAM%` 치환을 유지한다.

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

- [ ] `lib/oto/core/data_composer.dart`: `JenkinsEnvironmentSnapshot`, `JenkinsEnvironmentCollector` 추가.
- [ ] `lib/oto/core/data_composer.dart`: `DataComposerJenkins.compose()`를 collector 위임 구조로 축소.
- [ ] `lib/oto/core/data_composer.dart`: Unix 경로에서 실제 raw JSON을 로그에 남기도록 `rawVariablesJson` 사용.
- [ ] `test/oto_system_runtime_test.dart`: collector 또는 composer delegation 테스트를 추가/갱신.

#### 테스트 작성

작성한다.

- `test/oto_system_runtime_test.dart`: `JenkinsEnvironmentCollector collects linux env and build yaml from runtime`
- 검증 목표: fake runtime의 `runShell` stdout JSON과 `startShell` stdout YAML이 snapshot과 composer 결과로 보존된다.
- 기존 `DataComposerJenkins composes linux env and BuildData from runtime stdout`는 유지하되 adapter 분리 후에도 `commonData.workspace == '/workspace'`, `buildYaml == buildYaml`, `fake.startShellCalls.single.printStdout == false`를 검증한다.

#### 중간 검증

```bash
dart test test/oto_system_runtime_test.dart
```

예상 결과: `All tests passed!`

### REFACTOR-2 Mode-neutral Default Workspace 정리

#### 문제

`lib/oto/application.dart:29-34`의 `Application.current`가 build type이 Jenkins면 `Platform.environment['WORKSPACE']!`를 직접 읽는다. 이 값은 `SystemRuntime` 주입을 우회하고, property가 없는 Jenkins build에서 hard dependency를 만든다. `lib/oto/application.dart:92-94`와 `lib/oto/core/data_composer.dart:30-36`은 scheduler/file 모드 기본 `DataCommon`을 `FileData.jenkinsMap` 이름으로 가져와 경계가 모호하다.

Before (`lib/oto/application.dart:29`):

```dart
static String get current {
  if (Application.instance.buildType == BuildType.jenkins) {
    return Platform.environment['WORKSPACE']!;
  } else {
    return path.current;
  }
}
```

Before (`lib/oto/core/defined_data.dart:3`):

```dart
class FileData {
  static Map<String, dynamic>? get jenkinsMap {
    return {
      "workspace": Directory.current.path.replaceAll('\\', '/'),
```

#### 해결 방법

Jenkins env에서 온 workspace는 `commonData.workspace`에만 머물게 하고, global current는 local path를 의미하게 한다. neutral 기본 데이터 API를 추가하고 기존 `FileData.jenkinsMap`은 compatibility alias로 남긴다.

After:

```dart
static String get current => path.current;

String get defaultWorkspace {
  final data = commonData;
  if (data != null && data.workspace.isNotEmpty) {
    return data.workspace;
  }
  return current;
}
```

```dart
class LocalData {
  static Map<String, dynamic> commonMap({String? workspace}) {
    return {
      'workspace': (workspace ?? Directory.current.path).replaceAll('\\', '/'),
      'jenkinsHome': '',
      // keep DataCommon compatibility fields
    };
  }
}

class FileData {
  static Map<String, dynamic>? get jenkinsMap => LocalData.commonMap();
}
```

`DataComposerFile`와 scheduler branch는 `LocalData.commonMap()`을 사용한다. `FileData.jenkinsMap`은 기존 테스트나 외부 import를 깨지 않도록 남긴다.

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

- [ ] `lib/oto/application.dart`: `Application.current`에서 Jenkins env 직접 참조 제거.
- [ ] `lib/oto/application.dart`: `property['workspace']` 기본값을 `defaultWorkspace`에서 채운다.
- [ ] `lib/oto/core/defined_data.dart`: neutral `LocalData.commonMap()` 추가, 기존 `FileData.jenkinsMap`은 alias 유지.
- [ ] `lib/oto/core/data_composer.dart`: `DataComposerFile`이 `LocalData.commonMap()`을 사용.
- [ ] `lib/oto/application.dart`: `BuildType.scheduler` branch가 `LocalData.commonMap()`을 사용.
- [ ] `test/oto_application_test.dart`: workspace 없는 file build 회귀 테스트 추가.

#### 테스트 작성

작성한다.

- `test/oto_application_test.dart`: `file build without property workspace uses local workspace without Jenkins env`
- 검증 목표: `BuildType.file` YAML에 `property.workspace`가 없어도 build가 성공하고 `Application.instance.property['workspace']`가 `Directory.current.path`와 일치한다.

#### 중간 검증

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

예상 결과: `All tests passed!`

## 의존 관계 및 구현 순서

1. `REFACTOR-1`을 먼저 적용해 Jenkins env 수집 책임을 분리한다.
2. `REFACTOR-2`를 적용해 non-Jenkins 기본값을 neutral API로 이동한다.
3. 중간 검증 후 최종 검증을 실행한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/core/data_composer.dart` | REFACTOR-1, REFACTOR-2 |
| `lib/oto/core/defined_data.dart` | REFACTOR-2 |
| `lib/oto/application.dart` | REFACTOR-2 |
| `test/oto_system_runtime_test.dart` | REFACTOR-1 |
| `test/oto_application_test.dart` | REFACTOR-2 |

## 최종 검증

```bash
dart analyze
```

예상 결과: `No issues found!`

```bash
dart test
```

예상 결과: `All tests passed!`

검증 명령은 이 저장소에서 실행 확인했다. 테스트 캐시 개념이 없는 Dart test라 별도 cache busting은 필요 없다.

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