<!-- task=01_command_catalog plan=0 tag=REFACTOR -->

# Command Catalog Refactor

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

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

## 배경

커맨드 메타데이터는 `CommandSpec`에 모였지만 사람이 읽는 카탈로그는 README 표와 테스트에 흩어져 있다.
AI가 새 커맨드나 YAML을 작성할 때 등록 타입, 데이터 모델, 샘플 경로를 한 번에 읽을 수 있는 기계 생성형 진입점이 필요하다.
이 작업은 실행 로직을 바꾸지 않고 `Command.specs`를 기준으로 카탈로그 조회와 문서 동기화 지점을 만든다.

## 의존 관계 및 구현 순서

- 선행 task 없음. `agent-task/01_command_catalog`는 첫 번째 독립 작업이다.
- 이후 `agent-task/02+yaml_validation`은 이 task의 `complete.log`가 생긴 뒤 시작한다.

## 분석 결과

### 읽은 파일

- `lib/oto/commands/command.dart`
- `lib/oto/commands/command_registry.dart`
- `test/oto_command_catalog_test.dart`
- `README.md`
- `pubspec.yaml`
- `agent-ops/rules/project/domain/command/rules.md`

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

- `CommandType` 전체 등록, `CommandSpec` 존재, 샘플 경로 존재 여부는 `test/oto_command_catalog_test.dart`가 커버한다.
- 사람이 읽는 카탈로그 출력 형식과 README 동기화는 기존 테스트가 없다. 사용자가 유닛 테스트는 별도 작업한다고 했으므로 신규 테스트는 만들지 않고 기존 catalog 테스트와 deterministic `rg`로 검증한다.

### 심볼 참조

- renamed/removed symbol: none.
- `CommandSpec` 참조: `lib/oto/commands/command.dart:92`, `lib/oto/commands/command_registry.dart:45`, 각 `Command.register(..., spec:)`, `test/oto_command_catalog_test.dart:25`.
- `Command.specs` 참조: `test/oto_command_catalog_test.dart:26`, `test/oto_command_catalog_test.dart:41`, `test/oto_command_catalog_test.dart:59`, `test/oto_command_catalog_test.dart:88`.

### 범위 결정 근거

- 각 커맨드 구현 파일의 `Command.register()` 메타데이터는 이번 작업에서 수정하지 않는다. 전체 커맨드별 설명 필드 추가는 모든 command 파일을 건드리는 별도 대형 작업이다.
- YAML 실행 검증은 `agent-task/02+yaml_validation`에서 다룬다.
- iOS/Git 파일 분리는 `agent-task/04_build_ios_split`, `agent-task/05_git_split`에서 다룬다.

### 빌드 등급

- build lane: `local-G05`. 실행 로직 변경 없이 catalog helper와 문서 진입점 추가가 중심이고, `dart analyze`와 기존 catalog 테스트로 회귀를 잡을 수 있다.

## 구현 체크리스트

- [ ] `CommandSpec` 기반 catalog helper를 추가하고 기존 `Command.specs` API를 유지한다.
- [ ] README의 수동 커맨드 목록을 catalog 중심 안내로 축소한다.
- [ ] 기존 catalog 테스트와 deterministic 검색 검증을 실행한다.
- [ ] 최종 검증 명령을 모두 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] CommandSpec catalog helper

#### 문제

`CommandSpec`는 `lib/oto/commands/command.dart:92`에 있지만 필드 접근만 제공한다.
AI가 `Command.specs`를 읽어도 category별 표나 안정적인 정렬 결과를 바로 얻을 수 없다.

Before (`lib/oto/commands/command.dart:92`):

```dart
class CommandSpec {
  final CommandType type;
  final String category;
  final String dataModel;
  final String? samplePath;
```

#### 해결 방법

`CommandSpec`에 `toCatalogRow()`를 추가하고, `Command`에 category/type 기준으로 정렬된 catalog rows getter를 추가한다.
새 패키지는 추가하지 않는다.

After:

```dart
class CommandSpec {
  Map<String, String> toCatalogRow() => {
        'type': type.name,
        'category': category,
        'dataModel': dataModel,
        'samplePath': samplePath ?? '',
      };
}

abstract class Command {
  static List<Map<String, String>> get catalogRows {
    final rows = _specMap.values.map((spec) => spec.toCatalogRow()).toList();
    rows.sort((a, b) {
      final byCategory = a['category']!.compareTo(b['category']!);
      return byCategory == 0 ? a['type']!.compareTo(b['type']!) : byCategory;
    });
    return List.unmodifiable(rows);
  }
}
```

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

- [ ] `lib/oto/commands/command.dart`: `CommandSpec.toCatalogRow()`와 `Command.catalogRows` 추가.
- [ ] `test/oto_command_catalog_test.dart`: 신규 테스트는 만들지 않는다. 기존 테스트가 `Command.specs` 불변 조건을 계속 검증하는지 확인한다.

#### 테스트 작성

- 신규 유닛 테스트는 작성하지 않는다. 사용자가 유닛 테스트를 이후 별도로 작업한다고 했고, 이번 변경은 기존 메타데이터의 read-only projection이다.

#### 중간 검증

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

기대 결과: 모든 catalog 테스트가 통과한다.

### [REFACTOR-2] README command list as catalog entry point

#### 문제

README의 커맨드 표는 `README.md:161`부터 수동으로 유지된다.
이 표가 `CommandSpec`과 따로 움직이면 AI가 오래된 목록을 기준으로 YAML을 작성할 수 있다.

Before (`README.md:161`):

```markdown
## 커맨드 목록

### 빌드
```

#### 해결 방법

수동 표를 축소하고 `Command.specs` / `Command.catalogRows`를 단일 진실 소스로 안내한다.
README에는 category, dataModel, samplePath를 확인하는 짧은 Dart snippet과 샘플 디렉터리 위치만 남긴다.

After:

```markdown
## 커맨드 카탈로그

등록된 커맨드는 `Command.specs`와 `Command.catalogRows`가 단일 진실 소스다.
샘플은 `assets/yaml/sample/**`에서 확인한다.
```

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

- [ ] `README.md`: `## 커맨드 목록` 절을 catalog 중심 안내로 교체.
- [ ] `README.md`: 새 커맨드 추가 절의 `CommandSpec` 설명을 `catalogRows`와 연결.

#### 테스트 작성

- 문서 변경만 포함하므로 신규 유닛 테스트는 작성하지 않는다.

#### 중간 검증

```bash
rg --sort path -n "Command\\.catalogRows|Command\\.specs|## 커맨드 카탈로그" README.md lib/oto/commands/command.dart
```

기대 결과: README와 `command.dart` 양쪽에서 catalog 진입점이 검색된다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/commands/command.dart` | REFACTOR-1 |
| `README.md` | REFACTOR-2 |
| `test/oto_command_catalog_test.dart` | REFACTOR-1 검증 대상 |

## 최종 검증

```bash
dart analyze
dart test test/oto_command_catalog_test.dart
rg --sort path -n "Command\\.catalogRows|Command\\.specs|## 커맨드 카탈로그" README.md lib/oto/commands/command.dart
```

기대 결과: analyze와 catalog 테스트가 통과하고, 검색 결과가 README와 `command.dart`의 catalog 진입점을 보여준다.

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