<!-- task=04_build_ios_split plan=0 tag=REFACTOR -->

# Build iOS Command Split

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

**경고: 구현 완료의 마지막 단계는 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 모두 채우는 것이다.**
구현 체크리스트를 기준으로 작업하고, plan과 review stub 양쪽의 체크리스트를 완료하며, 중간/최종 검증을 실행한 실제 출력으로 `CODE_REVIEW-*-G??.md`를 채운다.
리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`는 수정하거나 체크하지 않는다.

## 배경

`build_ios.dart`는 iOS build, archive/export, macOS signing/notarize, Fastlane helper를 한 파일에 담고 있다.
이 파일 하나를 읽어야 모든 빌드 명령의 책임을 파악할 수 있어 AI가 변경 범위를 좁히기 어렵다.
이 작업은 public class name과 `registerBuildIosCommands()` 진입점을 유지하면서 책임별 파일로 나눈다.

## 의존 관계 및 구현 순서

- 선행 task 없음. `agent-task/04_build_ios_split`은 command 파일 내부 분리이며 다른 task와 독립적으로 시작할 수 있다.
- 단, `agent-task/03+execution_context_boundaries`와 동시에 구현 중이면 `lib/oto/commands/command.dart` 변경 결과를 먼저 반영한 뒤 format/analyze를 수행한다.

## 분석 결과

### 읽은 파일

- `lib/oto/commands/build/build_ios.dart`
- `lib/oto/data/build_data.dart`
- `lib/oto/commands/build/testflight_ios.dart`
- `assets/yaml/sample/10_build_ios.yaml`
- `test/oto_command_catalog_test.dart`
- `test/oto_command_runtime_test.dart`
- `README.md`
- `agent-ops/rules/project/domain/command/rules.md`
- `agent-ops/rules/project/domain/sample/rules.md`

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

- `registerBuildIosCommands()`의 registration/spec/samplePath는 `test/oto_command_catalog_test.dart`가 커버한다.
- `BuildiOS`, `ArchiveiOS`, `ExportiOS`, `CodeSign`, `ProductBuild`, `Notarize`의 실제 xcodebuild/codesign shell buffer는 기존 테스트가 없다.
- 사용자가 유닛 테스트를 별도 작업으로 분리했으므로 신규 테스트는 작성하지 않는다. 이 task는 기계적 파일 분리와 registration 보존에 집중한다.

### 심볼 참조

- renamed/removed symbol: none.
- `BuildiOS.initializeFastlane` call site: `lib/oto/commands/build/testflight_ios.dart:38`.
- `registerBuildIosCommands()` call site: `lib/oto/commands/command_registry.dart:53`.
- Build iOS command symbols: `BuildiOS` at `build_ios.dart:16`, `CodeSign` at `build_ios.dart:403`, `ArchiveiOS` at `build_ios.dart:513`, `registerBuildIosCommands()` at `build_ios.dart:573`.

### 범위 결정 근거

- `DataBuildiOS` 등 data model은 변경하지 않는다. 파일 분리만 수행한다.
- 샘플 YAML의 파라미터 재작성은 제외한다. 새 예제는 사용자가 이후 작성할 예정이고, 이 task는 command code 구조만 다룬다.
- 실제 xcodebuild/fastlane/codesign 실행 검증은 제외한다. 로컬 환경 의존성이 강하므로 analyze와 registration 테스트로 한정한다.

### 빌드 등급

- build lane: `cloud-G07`. 외부 CLI shell command를 품은 대형 파일 분리이고, 실제 명령 실행 테스트가 약하다.

## 구현 체크리스트

- [ ] `BuildiOS` 본체와 Fastlane/Xcodeproj helper를 build 전용 파일로 분리한다.
- [ ] `ArchiveiOS`/`ExportiOS`를 archive/export 전용 파일로 분리한다.
- [ ] macOS signing/package/notarize command를 signing 전용 파일로 분리한다.
- [ ] `build_ios.dart` import 경로와 `registerBuildIosCommands()` public 진입점을 유지한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] BuildiOS body and helper split

#### 문제

`BuildiOS.execute()`와 helper들이 `lib/oto/commands/build/build_ios.dart:16`부터 `401`까지 한 class에 모여 있다.
Fastlane 생성, xcodeproj XML 파싱, pbxproj 수정, xcodebuild shell 조립이 한 파일에 있어 변경 범위가 흐려진다.

Before (`lib/oto/commands/build/build_ios.dart:16`):

```dart
class BuildiOS extends Command {
  Function? get error => null;

  @override
  Future execute(DataCommand command) async {
```

#### 해결 방법

새 파일 `lib/oto/commands/build/build_ios_build.dart`를 만들고 `BuildiOS`와 해당 helper를 이동한다.
`initializeFastlane`은 `BuildiOS.initializeFastlane` 호출 호환을 위해 `BuildiOS` static method로 유지한다.

After:

```dart
// build_ios.dart
export 'build_ios_build.dart';
export 'build_ios_archive.dart';
export 'macos_signing.dart';

void registerBuildIosCommands() { ... }
```

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

- [ ] `lib/oto/commands/build/build_ios_build.dart`: `BuildiOS`와 helper 이동.
- [ ] `lib/oto/commands/build/build_ios.dart`: export와 registration만 남김.
- [ ] `lib/oto/commands/build/testflight_ios.dart`: 기존 import가 그대로 동작하는지 확인.

#### 테스트 작성

- 신규 유닛 테스트는 작성하지 않는다. 기존 테스트는 `BuildiOS.initializeFastlane` 직접 호출을 커버하지 않는다.

#### 중간 검증

```bash
dart analyze
```

기대 결과: 이동 후 import/export 오류가 없다.

### [REFACTOR-2] Archive and export command split

#### 문제

`ArchiveiOS`와 `ExportiOS`는 `lib/oto/commands/build/build_ios.dart:513`과 `549`에 있으며, `BuildiOS` 버전 처리와 직접 공유 상태가 없다.
같은 파일에 있어 archive/export만 수정할 때도 전체 build command를 읽어야 한다.

Before (`lib/oto/commands/build/build_ios.dart:513`):

```dart
class ArchiveiOS extends Command {
  Function? get error => null;
```

#### 해결 방법

새 파일 `lib/oto/commands/build/build_ios_archive.dart`를 만들고 `ArchiveiOS`, `ExportiOS`를 이동한다.
registration은 `build_ios.dart`에 남겨 외부 import를 유지한다.

After:

```dart
// build_ios_archive.dart
class ArchiveiOS extends Command { ... }
class ExportiOS extends Command { ... }
```

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

- [ ] `lib/oto/commands/build/build_ios_archive.dart`: `ArchiveiOS`, `ExportiOS` 이동.
- [ ] `lib/oto/commands/build/build_ios.dart`: registration에서 이동된 class를 export/import로 참조.

#### 테스트 작성

- 신규 유닛 테스트는 작성하지 않는다. external xcodebuild command는 별도 통합 테스트 대상이다.

#### 중간 검증

```bash
dart test test/oto_command_catalog_test.dart
```

기대 결과: Archive/Export registration과 samplePath 검증이 통과한다.

### [REFACTOR-3] macOS signing command split

#### 문제

`CodeSign`, `CodeSignVerify`, `ProductBuild`, `Notarize`는 `lib/oto/commands/build/build_ios.dart:403`부터 `510`에 있으며 iOS build/archive와 다른 macOS packaging 책임이다.
한 파일에 묶여 command category가 커질수록 AI가 수정해야 할 class를 찾기 어렵다.

Before (`lib/oto/commands/build/build_ios.dart:403`):

```dart
class CodeSign extends Command {
  Function? get error => null;
```

#### 해결 방법

새 파일 `lib/oto/commands/build/macos_signing.dart`로 macOS signing/package/notarize command를 이동한다.
`registerBuildIosCommands()`의 CommandSpec은 그대로 유지한다.

After:

```dart
// macos_signing.dart
class CodeSign extends Command { ... }
class CodeSignVerify extends Command { ... }
class ProductBuild extends Command { ... }
class Notarize extends Command { ... }
```

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

- [ ] `lib/oto/commands/build/macos_signing.dart`: signing 관련 class 이동.
- [ ] `lib/oto/commands/build/build_ios.dart`: export/import와 registration 유지.
- [ ] `lib/oto/commands/build/build_ios.dart`: 파일 크기가 registration 중심으로 줄었는지 확인.

#### 테스트 작성

- 신규 유닛 테스트는 작성하지 않는다. shell buffer 검증은 별도 command runtime 테스트 확장 작업에서 다룬다.

#### 중간 검증

```bash
rg --sort path -n "class BuildiOS|class ArchiveiOS|class ExportiOS|class CodeSign|class ProductBuild|void registerBuildIosCommands" lib/oto/commands/build
```

기대 결과: 각 class가 책임별 파일에 있고 `registerBuildIosCommands`는 유지된다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/commands/build/build_ios.dart` | REFACTOR-1, REFACTOR-2, REFACTOR-3 |
| `lib/oto/commands/build/build_ios_build.dart` | REFACTOR-1 |
| `lib/oto/commands/build/build_ios_archive.dart` | REFACTOR-2 |
| `lib/oto/commands/build/macos_signing.dart` | REFACTOR-3 |
| `lib/oto/commands/build/testflight_ios.dart` | REFACTOR-1 검증 대상 |
| `test/oto_command_catalog_test.dart` | REFACTOR-2 검증 대상 |

## 최종 검증

```bash
dart format lib/oto/commands/build/build_ios.dart lib/oto/commands/build/build_ios_build.dart lib/oto/commands/build/build_ios_archive.dart lib/oto/commands/build/macos_signing.dart
dart analyze
dart test test/oto_command_catalog_test.dart test/oto_command_runtime_test.dart
rg --sort path -n "class BuildiOS|class ArchiveiOS|class ExportiOS|class CodeSign|class ProductBuild|void registerBuildIosCommands" lib/oto/commands/build
```

기대 결과: format/analyze/test가 통과하고, 검색 결과가 책임별 파일 분리를 보여준다.

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