 <!-- task=m-architecture-refactor-foundation/01_edge_cmd_split plan=0 tag=REFACTOR -->

# Edge Cmd Split Plan

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

`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 일부다. 코드 변경 후 검증을 실행하고 실제 변경 내용, 검증 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌 때문에 진행할 수 없으면 review stub의 `사용자 리뷰 요청`에 근거와 재개 조건을 적고 중단한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 최종 판정, log archive, `complete.log` 작성은 code-review 전용이다.

## 배경

현재 `apps/edge/cmd/edge/main.go`는 Cobra root, config discovery, bootstrap pack, node registration, YAML patch, OpenAI smoke client까지 1500라인 이상을 한 파일에서 처리한다. Edge domain rule은 공식 local/field UX를 `iop-edge` command 표면에 모으라고 하지만, command entrypoint가 구현 세부까지 소유하면 후속 command 확장과 테스트가 계속 커진다. 이 작업은 동작과 help text를 유지하면서 command support 로직을 내부 패키지로 분리한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active `CODE_REVIEW-local-G05.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자 프롬프트를 만들지 않으며, code-review가 요청의 타당성을 판단하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- Task ids:
  - `edge-cmd-split`: `apps/edge/cmd/edge/main.go`의 config resolution, bootstrap pack, node registration, YAML patch, smoke client 로직을 기존 동작을 유지한 채 내부 패키지 또는 작은 모듈로 분리한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- `apps/edge/cmd/edge/main.go`
- `apps/edge/cmd/edge/main_test.go`

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다.
- matching profile: `agent-test/local/edge-smoke.md`.
- 적용 명령: 변경한 edge 패키지 또는 `go test ./apps/edge/...`; command-only 변경은 `go test ./apps/edge/cmd/edge`와 최종 `go test ./apps/edge/...`로 검증한다.
- OpenAI-compatible 런타임 smoke는 실제 endpoint가 필요하므로 이 리팩토링의 필수 조건은 아니다. command smoke client 동작은 `httptest` 기반 `TestSmokeOpenAICommandSuccess/Failure`로 확인한다.

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

- Config discovery, config init/check, env output, bootstrap pack, node register YAML patch, nodes list, OpenAI smoke command는 `apps/edge/cmd/edge/main_test.go`에 기존 회귀 테스트가 있다.
- 새 내부 package boundary 자체는 아직 없다. 구현 시 이동한 helper별 테스트를 새 package 또는 기존 command test로 유지해야 한다.

### 심볼 참조

- `resolveConfigPath`, `resolveEdgeLogPath`, `resolveBootstrapArtifactDir`, `renderEnv`, `packNodeBootstrap`, `validateEdgeConfig`, `nodeRegisterCmd`, `patchYAML`, `resolveSmokeBaseURL`, `smokeOpenAICmd` 참조는 `apps/edge/cmd/edge/main.go`와 `apps/edge/cmd/edge/main_test.go`에 한정된다.
- repo 외부 공개 API는 없다. 이동 후 main package tests that depend on unexported helpers must move with the helpers or test via root command.

### 분할 판단

- shared task group: `agent-task/m-architecture-refactor-foundation/`.
- sibling subtasks: `01_edge_cmd_split` independent, `02_edge_service_split` independent, `03+02_openai_surface_split` depends on service split.
- 이 plan은 CLI command boundary만 다루며 service/openai 코드와 독립 검증 가능하므로 첫 subtask로 분리한다.

### 범위 결정 근거

- `apps/edge/internal/service/**`, `apps/edge/internal/openai/**`, `apps/edge/internal/events/**`는 이 plan에서 수정하지 않는다.
- 사용자-facing command 이름, flags, help flow, bootstrap one-line command 형식은 유지한다.
- 새 외부 dependency는 추가하지 않는다.

### 빌드 등급

- `local-G05`: 파일 이동과 package boundary 정리가 크지만 동작 보존형이고 기존 Go 테스트가 촘촘해 local 검증으로 충분하다.

## 구현 체크리스트

- [ ] `apps/edge/cmd/edge/main.go`에서 Cobra root/main/serve/console wiring만 남기고 config/env/bootstrap/node-register/YAML patch/smoke helper를 `apps/edge/internal/edgecmd` 같은 내부 package의 작은 파일들로 이동한다.
- [ ] 기존 command help, flags, default config discovery, one-line bootstrap output, YAML preservation, smoke client 응답 검증을 유지하도록 `apps/edge/cmd/edge/main_test.go` 테스트를 새 package boundary에 맞게 옮기거나 조정한다.
- [ ] `go test ./apps/edge/cmd/edge`와 `go test ./apps/edge/...`를 실행해 command package와 edge 전체 회귀를 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Extract Edge Command Support Modules

#### 문제

- [main.go](/config/workspace/iop/apps/edge/cmd/edge/main.go:42)는 root command wiring에서 시작해 config/env/bootstrap/node/smoke 구현을 같은 package/file에 둔다.
- [main.go](/config/workspace/iop/apps/edge/cmd/edge/main.go:513), [main.go](/config/workspace/iop/apps/edge/cmd/edge/main.go:914), [main.go](/config/workspace/iop/apps/edge/cmd/edge/main.go:1357)는 bootstrap pack, node register, smoke client 구현이 각각 수백 라인으로 command entrypoint에 붙어 있다.

#### 해결 방법

Before:

```go
// apps/edge/cmd/edge/main.go:42
func rootCmd() *cobra.Command {
    root := &cobra.Command{Use: "edge", ...}
    root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file path (defaults to bundle-local edge.yaml)")
    root.AddCommand(serveCmd(), consoleCmd(), versionCmd(), configCmd(), envCmd(), setupCmd(), bootstrapCmd(), nodeCmd(), nodesCmd(), smokeCmd())
    return root
}
```

After:

```go
// apps/edge/cmd/edge/main.go
func rootCmd() *cobra.Command {
    root := edgecmd.NewRoot(edgecmd.Options{
        ConfigPath: &cfgFile,
        Serve:      serveCmd(),
        Console:    consoleCmd(),
    })
    return root
}
```

- `apps/edge/internal/edgecmd/config.go`: config discovery, config init/print/check, validation.
- `apps/edge/internal/edgecmd/env.go`: advertise host, derived URLs, env report rendering.
- `apps/edge/internal/edgecmd/bootstrap.go`: bootstrap pack, checksum, script rendering, artifact refresh.
- `apps/edge/internal/edgecmd/node_register.go`: node register flag state, config mutation, YAML patch.
- `apps/edge/internal/edgecmd/smoke_openai.go`: smoke openai client DTOs and checks.
- Keep command behavior stable; only package/file ownership changes.

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

- [ ] `apps/edge/cmd/edge/main.go`
- [ ] `apps/edge/cmd/edge/main_test.go`
- [ ] `apps/edge/internal/edgecmd/*.go`
- [ ] `apps/edge/internal/edgecmd/*_test.go`

#### 테스트 작성

- 기존 command tests를 이동한 helper package와 root command package에 맞춰 유지한다.
- `TestRootCmdIncludesOperationalCommands`, config/env/bootstrap/node-register/smoke tests must still cover the user-visible command surface.

#### 중간 검증

```bash
go test ./apps/edge/cmd/edge
```

Expected: command package tests pass.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/cmd/edge/main.go` | REFACTOR-1 |
| `apps/edge/cmd/edge/main_test.go` | REFACTOR-1 |
| `apps/edge/internal/edgecmd/*.go` | REFACTOR-1 |
| `apps/edge/internal/edgecmd/*_test.go` | REFACTOR-1 |

## 최종 검증

```bash
go test ./apps/edge/...
```

Expected: all edge package tests pass. Go test cache output is acceptable for unchanged packages; use `-count=1` only if a command behavior assertion is flaky or suspected stale.

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