<!-- task=m-agent-readable-repository-refactor/09+01,03,04,05,06,07,08_secondary_completion plan=2 tag=REVIEW_REVIEW_REFACTOR_COMPLETION -->

# 장기 함수와 secondary UI read-set 완료 보완 계획

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

아래 Required 2건을 구현하고 검증 명령을 실제로 실행한 뒤 `CODE_REVIEW-cloud-G09.md`의 구현 에이전트 소유 섹션을 실제 메모와 stdout/stderr로 채운다. active plan/review는 그대로 두고 ready for review만 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다. 선택된 Milestone `구현 잠금 > 결정 필요`가 실구현을 새로 차단할 때만 review stub의 `사용자 리뷰 요청`에 정확한 연결 근거를 기록하고 중단한다. 직접 사용자 질문, 채팅 선택지, `request_user_input`, `USER_REVIEW.md` 작성, log archive, `complete.log` 작성은 금지한다. 환경·secret·서비스·일반 범위 조정·검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 정상 follow-up 대상이다.

## 배경

두 번째 리뷰에서 세 대상 함수의 분해와 전체 회귀는 통과했지만, 남은 6개 장기 함수를 분리 불가능한 예외로 둔 근거가 실제 단계·상태·widget 경계와 모순됐다. 또한 `NodesPanel` 분해가 새 534줄 production warning과 2,118줄짜리 합성 UI read set을 baseline에 고정했고, 이미 축소된 `nodeRegisterCmd`의 stale function baseline도 남겼다. 이 follow-up은 외부 API, wire, config schema, 런타임 동작을 바꾸지 않고 해당 구조 부채와 측정 정의를 함께 닫는다.

## 사용자 리뷰 요청 흐름

선택된 Milestone의 `구현 잠금 > 결정 필요`만 active review stub의 `사용자 리뷰 요청`에 기록하며, 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따른다. 구현 중 직접 사용자 prompt는 금지되고, 요청의 타당성 검증과 실제 `USER_REVIEW.md` 작성은 code-review가 담당한다.

## Archive Evidence Snapshot

- 이전 plan: `agent-task/m-agent-readable-repository-refactor/09+01,03,04,05,06,07,08_secondary_completion/plan_cloud_G09_1.log`
- 이전 review: `agent-task/m-agent-readable-repository-refactor/09+01,03,04,05,06,07,08_secondary_completion/code_review_cloud_G09_1.log`
- 판정: FAIL, Required 2 / Suggested 0 / Nit 0.
- Required:
  - `smokeOpenAICmd`, `Server.onNodeConnected`, OpenAI-compatible/vLLM `Execute`, node bootstrap `Module`, `_IopConsoleOverviewState.build`의 exception 근거가 실제 안전한 단계·상태·widget 분리 경계와 모순된다.
  - `nodes_panel.dart` 534줄 warning과 `client-ui-readability` 2,118줄 합성 read set을 새로 수용했고, 축소된 `nodeRegisterCmd` 187줄 baseline이 stale 상태로 남았다.
- 영향 경로: `apps/edge/internal/edgecmd`, `apps/edge/internal/transport`, `apps/node/internal/adapters/{openai_compat,vllm}`, `apps/node/internal/bootstrap`, `apps/client/lib/widgets`, `packages/flutter/iop_console`, `scripts/readability_{baseline,read_sets}.json`.
- reviewer 검증: targeted 및 전체 Go, client test/analyze, iop_console test, readability unit 122개/ratchet, mock/OpenAI-Ollama/CP-Edge E2E가 모두 PASS했다. 격리 cold Go cache의 첫 E2E만 10초 bind deadline을 넘겼고 cache warm-up 뒤 focused/full 명령이 통과했다.
- roadmap carryover: `secondary-modules`, `long-functions`는 아직 미완료이며 같은 Milestone target을 유지한다. 외부 API/proto/config schema/runtime 책임 경계 변경은 범위 밖이다.
- 정확한 이전 표·출력 확인이 필요할 때만 위 `code_review_cloud_G09_1.log`를 좁게 읽는다. `agent-task/archive/**`를 일반 탐색하지 않는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)
- Task ids:
  - `secondary-modules`: HTTP/CLI/Ollama adapter, Flutter runtime panel, E2E shell의 책임 분해 완료
  - `long-functions`: Classify/adapter Execute/Node·transport handler/E2E orchestration의 장기 함수 처리 또는 응집 예외 근거
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- workflow/roadmap: `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/code-review/SKILL.md`, `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`, `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- domain/spec/contract: `agent-ops/rules/project/domain/{client,edge,node,testing,platform-common}/rules.md`, `agent-spec/index.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-spec/control/control-plane-operations.md`, `agent-contract/index.md`, `agent-contract/inner/edge-node-runtime-wire.md`, `agent-contract/inner/edge-config-runtime-refresh.md`
- test rules: `agent-test/local/rules.md`, `agent-test/local/{client,edge,node,testing,platform-common}-smoke.md`
- audit/config: `scripts/readability_baseline.json`, `scripts/readability_read_sets.json`
- source: `apps/edge/internal/edgecmd/smoke_openai.go`, `apps/edge/internal/transport/server.go`, `apps/node/internal/adapters/openai_compat/stream.go`, `apps/node/internal/adapters/vllm/stream.go`, `apps/node/internal/bootstrap/module.go`, `packages/flutter/iop_console/lib/src/iop_console_overview.dart`, `apps/client/lib/widgets/nodes_panel.dart`
- direct tests: `apps/edge/cmd/edge/smoke_command_test.go`, `apps/edge/internal/transport/integration_test.go`, `apps/edge/internal/transport/server_test.go`, `apps/node/internal/adapters/openai_compat/execute_test.go`, `apps/node/internal/adapters/openai_compat/thinking_policy_test.go`, `apps/node/internal/adapters/vllm/vllm_test.go`, `apps/node/internal/bootstrap/module_test.go`, `packages/flutter/iop_console/test/iop_console_shell_test.dart`, `apps/client/test/edge_nodes_panels_test.dart`, `apps/client/test/provider_status_test.dart`
- existing split pattern: `apps/client/lib/widgets/runtime_panel.dart`, `apps/client/lib/widgets/runtime_panel_sections.dart`
- 이전 evidence: 위 `plan_cloud_G09_1.log`, `code_review_cloud_G09_1.log`와 이 subtask가 이미 고정한 일곱 predecessor 완료 경로

### SDD 기준

Milestone은 `SDD: 불필요`다. 사유는 외부 API/proto/config schema나 runtime 책임 경계를 바꾸지 않고 같은 package 안에서 책임·상태·widget 단위를 분리하는 동작 보존형 리팩터링이기 때문이다. 구현 중 이 경계를 바꿔야 하면 현재 범위를 중단하고 SDD gate를 재판정한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 client/edge/node/testing/platform-common smoke profile을 읽고 적용했다.
- runner/repo: local `/config/workspace/iop`, branch `main`, HEAD `1378a7f`, 사용자와 이전 구현의 dirty worktree를 보존한다.
- runtime: Go `1.26.2 linux/arm64`, Flutter `3.41.5`; 기본 Go cache 권한 문제를 피하고 fresh 실행을 보장하기 위해 `GOCACHE=/tmp/iop-go-cache-review-review-g09`와 `-count=1`을 사용한다.
- local fixture가 동적 port를 사용한다. Docker, emulator/device, 외부 provider/CLI profile, credential은 이번 동작 보존 범위에 필요하지 않다.
- `make test-e2e`는 먼저 전체 Go test로 동일 격리 cache를 warm한 뒤 실행해 script의 10초 bind deadline이 compile cold-start를 측정하지 않게 한다.
- fallback은 없다. 모든 명령은 읽은 local profile과 repository Makefile/audit entrypoint에서 가져왔다.

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

- `smokeOpenAICmd`: success/auth/workspace/health failure/responses failure/empty response는 기존 `smoke_command_test.go`가 검증한다. health→models→responses의 호출 순서를 직접 assert하지 않으므로 해당 assertion을 추가한다.
- `Server.onNodeConnected`: RunEvent/tunnel 분리, registration 성공·unknown token·duplicate·disconnect·heartbeat는 기존 transport tests가 직접 검증한다. helper 추출이므로 새 test는 쓰지 않는다.
- OpenAI-compatible/vLLM `Execute`: validation, retry, text-tool fallback, SSE delta/reasoning/tool/usage/complete/error는 기존 adapter tests가 직접 검증한다. 단계 추출이므로 새 test는 쓰지 않는다.
- bootstrap `Module`: reconnect 성공, retry exhaustion, interval/attempt policy를 기존 `module_test.go`가 검증한다. named supervisor 추출이므로 새 test는 쓰지 않는다.
- `IopConsoleOverview`: endpoint/status/refresh와 disconnected 상태는 검증되지만 실제 overview의 narrow/wide responsive 조합을 같은 test에서 비교하지 않는다. 두 viewport widget test를 추가한다.
- `NodesPanel`: populated provider catalog, health/status color, loading/error/empty branch가 두 client test 파일에 있다. widget 파일 이동만 하므로 새 test는 쓰지 않는다.
- readability config: `python3 scripts/readability_audit_test.py`, worktree audit assertion, `make readability-audit`로 stale/신규/increased violation과 read-set 합계를 검증한다.

### 심볼 참조

- public symbol rename/remove는 없다. `smokeOpenAICmd`는 `smokeCmd`에서만 호출되고, `Server.onNodeConnected`는 `NewServer` callback과 same-package test에서 참조된다.
- `Adapter.Execute`, `Vllm.Execute`, `bootstrap.Module`, `IopConsoleOverview`, `NodesPanel`의 public signature와 call sites는 유지한다.
- `_buildProviderCard`는 `nodes_panel.dart` 내부 참조 하나뿐이며 새 `NodesPanelSections` source의 responsibility-named widget으로 이동한다. root package export는 추가하지 않는다.
- 새 helper/context는 해당 package 또는 widget source 내부 symbol로만 두고 범용 `utils`/`common`으로 승격하지 않는다.

### 분할 판단

split 정책을 먼저 재평가했다. 이 follow-up은 여러 domain을 건드리지만 이미 dependency gate를 통과해 실행 중인 `09+01,03,04,05,06,07,08_secondary_completion`의 동일 FAIL 판정 2건을 닫는 종결 단위다. 새 sibling으로 분리하면 한쪽 PASS만으로도 같은 `secondary-modules`/`long-functions` completion evidence가 불완전하고, 기존 routed task의 archive/roadmap anchor가 둘로 갈라진다. 따라서 같은 active task의 단일 follow-up review가 더 안전하다.

이 subtask가 상속한 정확한 predecessor는 `01_openai_modules`, `03_service_node_modules`, `04+01,03_openai_complexity`, `05_http_provider_adapters`, `06_cli_adapter`, `07_client_runtime_panel`, `08_e2e_scripts`이며 각각 `agent-task/archive/2026/07/m-agent-readable-repository-refactor/<subtask>/complete.log`가 존재한다. 같은 index의 다른 historical sibling은 현재 task의 predecessor가 아니며 새 의존성 선택에 사용하지 않는다.

### 범위 결정 근거

- 포함: 판정이 무효인 6개 장기 함수의 package-local 단계/context/widget 분리, `NodesPanel`의 established sections-file 분리, 관련 최소 회귀 test 2건, 현재 audit에 필요한 baseline/read-set 정의 동기화.
- 제외: 이미 credible한 12개 장기 함수 예외의 동작 변경, test LOC만 줄이는 분할, `skill-entrypoints`, 다른 unfinished Milestone Task, generic `utils`/micro-package.
- 제외: public API, OpenAI-compatible wire, Edge-Node proto/config schema, event 순서와 에러 문구, runtime routing/queue policy 변경. 해당 필요가 생기면 이 plan에서 확장하지 않는다.
- Agent UI Completion은 없다. `agent-ui` 정의/status 작업이 아니라 기존 Flutter widget의 동작 보존형 source 분리다.

### 빌드 등급

`cloud-G09`. transport listener, SSE retry/stream, reconnect shared state, 두 Flutter package와 audit baseline을 함께 판단해야 하며 부분적으로 그럴듯한 helper 추출만으로 runtime ordering이나 completion evidence를 놓칠 위험이 높다.

## 구현 체크리스트

- [ ] 무효한 exception 6개를 package-local named context/helper/widget로 분해하고 public signature, listener/retry/event/cleanup 순서와 UI 동작을 보존한다.
- [ ] `NodesPanel`을 established sections-file 패턴으로 분리하고 client UI read-set을 책임별 task로 재정의해 새/stale baseline 항목을 제거한다.
- [ ] targeted 및 전체 Go/Flutter, readability worktree assertion/ratchet, mock E2E를 동일 격리 cache에서 fresh 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REFACTOR_COMPLETION-1] 무효한 장기 함수 예외를 단계와 상태 경계로 분해

문제: 아래 6개 함수는 서로 독립적인 단계나 명명 가능한 상태를 가지지만, 공유 값·closure·`goto`·mutable state 부재를 분리 손실로 오해해 `exception`으로 남겼다.

Before (`apps/edge/internal/edgecmd/smoke_openai.go:130`):

```go
func smokeOpenAICmd() *cobra.Command {
    c := &cobra.Command{
        // metadata...
        RunE: func(cmd *cobra.Command, _ []string) error {
            // resolve client
            // GET /healthz
            // GET /v1/models
            // POST /v1/responses
            // verify expected file
        },
    }
}
```

Before (`apps/edge/internal/transport/server.go:167`):

```go
func (s *Server) onNodeConnected(client *toki.TcpClient) {
    toki.AddListenerTyped[*iop.RunEvent](...)
    toki.AddListenerTyped[*iop.ProviderTunnelFrame](...)
    toki.AddRequestListenerTyped[*iop.RegisterRequest, *iop.RegisterResponse](...)
}
```

Before (`apps/node/internal/adapters/openai_compat/stream.go:29`, `apps/node/internal/adapters/vllm/stream.go:21`):

```go
func (a *Adapter) Execute(...) error {
    // validate and build request
    // retry native tool / text fallback
    goto streamResponse
streamResponse:
    // scan SSE and emit events
}
```

Before (`apps/node/internal/bootstrap/module.go:118`, `packages/flutter/iop_console/lib/src/iop_console_overview.dart:47`):

```text
Module -> fx.Invoke closure owns mu/current/cancelSupervisor + reconnect loop
build  -> owns headerText + badge + responsive header + stats + body tree
```

해결 방법:

```go
// After: representative package-local orchestration.
func runOpenAISmoke(cmd *cobra.Command) error {
    run, err := newOpenAISmokeRun(cmd)
    if err != nil { return err }
    if err := run.checkHealth(); err != nil { return err }
    if err := run.checkModels(); err != nil { return err }
    if err := run.checkResponses(); err != nil { return err }
    return run.verifyExpectedFile()
}

func (s *Server) onNodeConnected(client *toki.TcpClient) {
    s.registerRunEventListener(client)
    s.registerTunnelFrameListener(client)
    s.registerNodeRequestListener(client)
}

func (a *Adapter) Execute(ctx context.Context, spec runtime.ExecutionSpec, sink runtime.EventSink) error {
    exec, err := a.prepareExecution(ctx, spec, sink)
    if err != nil { return err }
    resp, textFallback, err := exec.requestWithFallback()
    if err != nil { return err }
    return a.consumeStream(ctx, spec, sink, resp, textFallback)
}
```

- `smoke_openai.go`: command construction과 `openAISmokeRun`의 health/models/responses/expected-file method를 분리한다. writer, HTTP client, base URL, model을 struct에 보관하고 현재 출력 문구·request 순서·early return을 그대로 유지한다.
- `server.go` + `connection_handlers.go`: top-level listener 세 종류와 registration handler/registered-node disconnect binding을 이름별로 분리한다. callback 등록 순서와 registry/event 순서를 바꾸지 않는다.
- `openai_compat/execute.go` + `stream.go`: validation/request/retry 결과와 SSE decode를 분리하고 `chatStreamSession`이 usage/finish reason/tool calls/output token/trace sequence를 소유하게 해 `goto`를 제거한다.
- `vllm/stream.go`: package 고유 retry helper와 stream session method로 나눈다. OpenAI-compatible package와 generic helper를 공유하지 않는다.
- `bootstrap/module.go` + `runtime_supervisor.go`: `runtimeSupervisor`가 cfg/logger/dialer/sleeper/shutdowner, mutex/current owner/cancel을 소유하고 `start/run/reconnect/stop`을 제공한다. `fx.Hook`은 이 method만 호출한다.
- `iop_console_overview.dart`: animation controller는 state에 유지하고 header/status badge/stats/body를 private builder/widget로 분리해 `build`를 120줄 미만으로 낮춘다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/edgecmd/smoke_openai.go` — command와 순차 smoke 단계 분리.
- [ ] `apps/edge/cmd/edge/smoke_command_test.go` — endpoint 호출 순서 assertion 추가.
- [ ] `apps/edge/internal/transport/server.go` — connection callback orchestration만 유지.
- [ ] `apps/edge/internal/transport/connection_handlers.go` — listener/registration/disconnect 책임 이동.
- [ ] `apps/node/internal/adapters/openai_compat/execute.go` — validation/request/retry orchestration 이동.
- [ ] `apps/node/internal/adapters/openai_compat/stream.go` — named stream session과 SSE decode 유지.
- [ ] `apps/node/internal/adapters/vllm/stream.go` — request fallback와 SSE session 분리.
- [ ] `apps/node/internal/bootstrap/module.go` — fx wiring만 유지.
- [ ] `apps/node/internal/bootstrap/runtime_supervisor.go` — lifecycle/reconnect state owner 추가.
- [ ] `packages/flutter/iop_console/lib/src/iop_console_overview.dart` — responsive section 분리.
- [ ] `packages/flutter/iop_console/test/iop_console_shell_test.dart` — narrow/wide overview 회귀 추가.

테스트 작성:

- write: `apps/edge/cmd/edge/smoke_command_test.go`의 success test에 관측 endpoint slice를 두고 `["/healthz", "/v1/models", "/v1/responses"]` 순서를 assert한다.
- write: `packages/flutter/iop_console/test/iop_console_shell_test.dart`에 `IopConsoleOverview preserves narrow and wide responsive sections`를 추가해 두 viewport에서 header/status/endpoints와 exception 부재를 확인한다.
- skip: transport, adapter, bootstrap은 기존 직접 tests가 success/failure/retry/stream/reconnect ordering을 이미 검증하므로 새 동작 test를 만들지 않는다.

중간 검증:

```bash
gofmt -d apps/edge/internal/edgecmd/smoke_openai.go apps/edge/internal/transport/server.go apps/edge/internal/transport/connection_handlers.go apps/node/internal/adapters/openai_compat/execute.go apps/node/internal/adapters/openai_compat/stream.go apps/node/internal/adapters/vllm/stream.go apps/node/internal/bootstrap/module.go apps/node/internal/bootstrap/runtime_supervisor.go
GOCACHE=/tmp/iop-go-cache-review-review-g09 go test -count=1 ./apps/edge/cmd/edge ./apps/edge/internal/transport ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm ./apps/node/internal/bootstrap
(cd packages/flutter/iop_console && flutter test)
```

기대 결과: `gofmt -d` 출력 없음, 모든 대상 package와 iop_console widget test PASS, public signature와 event/error ordering 변화 없음.

### [REVIEW_REVIEW_REFACTOR_COMPLETION-2] NodesPanel 책임 분리와 task-local read-set 정합성 복구

문제: `apps/client/lib/widgets/nodes_panel.dart:4`부터 provider rendering, panel header/state, node card, adapters, catalog가 한 534줄 파일에 남았고 `scripts/readability_baseline.json:34`가 새 file warning을 수용했다. `scripts/readability_read_sets.json:95`는 서로 다른 Edges/Nodes/Runtime/Console 변경 이유를 하나의 2,118줄 task로 합치며, `scripts/readability_baseline.json:501`에는 이미 28줄인 `nodeRegisterCmd`의 stale 187줄 항목도 남았다.

Before (`apps/client/lib/widgets/nodes_panel.dart:4`, `scripts/readability_read_sets.json:95`):

```dart
Widget _buildProviderCard(ProviderSnapshotView p) { ... } // 118 LOC
class NodesPanel extends StatelessWidget {
  // panel header + states + node card + adapters + provider catalog
}
```

```json
{
  "task_id": "client-ui-readability",
  "files": [
    "edges_panel.dart",
    "nodes_panel.dart",
    "runtime_panel.dart",
    "runtime_panel_sections.dart",
    "iop_console_overview.dart"
  ]
}
```

해결 방법:

```dart
// After: nodes_panel.dart
import 'nodes_panel_sections.dart';

class NodesPanel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // edge selection/header only
    return NodesPanelContent(
      status: selectedEdgeStatus,
      isLoading: isLoading,
      error: error,
    );
  }
}

// After: nodes_panel_sections.dart
class NodesPanelContent extends StatelessWidget { ... }
class NodeStatusCard extends StatelessWidget { ... }
class ProviderSnapshotCard extends StatelessWidget { ... }
```

- 기존 `runtime_panel.dart`/`runtime_panel_sections.dart` 패턴을 따른다. 새 source는 client package 내부에서만 import하고 root export를 추가하지 않는다.
- `nodes_panel.dart`는 edge selector와 public `NodesPanel` composition만 소유하고, state/node/provider rendering은 `nodes_panel_sections.dart`로 이동한다. 각 file은 500줄 미만, 각 함수는 120줄 미만을 유지한다.
- `scripts/readability_read_sets.json`의 합성 task를 `client-edges-panel-readability`, `client-nodes-panel-readability`, `client-runtime-orchestration-readability`, `client-runtime-sections-readability`, `iop-console-overview-readability`로 나눈다. 각 task는 실제 해당 책임 파일만 포함하고 현재 600줄 budget 및 Milestone의 2,000줄 상한 안에 있어야 한다.
- `scripts/readability_baseline.json`에서 `nodes_panel.dart` file warning, `_buildProviderCard`, `nodeRegisterCmd`와 이번 item 1에서 해소된 6개 function 항목을 제거한다. 다른 baseline을 일괄 재생성하지 않고 worktree audit가 요구하는 touched entry만 현재 값/사유로 갱신한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/widgets/nodes_panel.dart` — public panel/edge selector composition으로 축소.
- [ ] `apps/client/lib/widgets/nodes_panel_sections.dart` — state/node/adapter/provider section widgets 추가.
- [ ] `scripts/readability_read_sets.json` — client UI task-local read set을 책임별로 분리.
- [ ] `scripts/readability_baseline.json` — 해소된/stale 항목 제거와 touched read-set 값 정합화.
- [ ] `apps/client/test/edge_nodes_panels_test.dart`, `apps/client/test/provider_status_test.dart` — 동작 변경 없이 기존 assertions를 그대로 통과시킨다.

테스트 작성: skip. `edge_nodes_panels_test.dart`가 panel populated/loading/error/empty를, `provider_status_test.dart`가 provider health/status/color를 직접 검증하므로 source 이동을 위한 중복 test는 추가하지 않는다.

중간 검증:

```bash
dart format --output=none --set-exit-if-changed apps/client/lib/widgets/nodes_panel.dart apps/client/lib/widgets/nodes_panel_sections.dart apps/client/test/edge_nodes_panels_test.dart apps/client/test/provider_status_test.dart packages/flutter/iop_console/lib/src/iop_console_overview.dart packages/flutter/iop_console/test/iop_console_shell_test.dart
(cd apps/client && flutter test test/edge_nodes_panels_test.dart test/provider_status_test.dart)
(cd apps/client && flutter analyze)
python3 scripts/readability_audit.py --input-mode worktree --output /tmp/iop-review-review-g09-readability.json
```

기대 결과: format/analyze/widget tests PASS, `nodes_panel.dart` file warning과 해소 대상 function baseline이 없고 새 client read set 각각 2,000줄 이하.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/edgecmd/smoke_openai.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/edge/cmd/edge/smoke_command_test.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/edge/internal/transport/server.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/edge/internal/transport/connection_handlers.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/node/internal/adapters/openai_compat/execute.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/node/internal/adapters/openai_compat/stream.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/node/internal/adapters/vllm/stream.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/node/internal/bootstrap/module.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/node/internal/bootstrap/runtime_supervisor.go` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `packages/flutter/iop_console/lib/src/iop_console_overview.dart` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `packages/flutter/iop_console/test/iop_console_shell_test.dart` | REVIEW_REVIEW_REFACTOR_COMPLETION-1 |
| `apps/client/lib/widgets/nodes_panel.dart` | REVIEW_REVIEW_REFACTOR_COMPLETION-2 |
| `apps/client/lib/widgets/nodes_panel_sections.dart` | REVIEW_REVIEW_REFACTOR_COMPLETION-2 |
| `scripts/readability_read_sets.json` | REVIEW_REVIEW_REFACTOR_COMPLETION-2 |
| `scripts/readability_baseline.json` | REVIEW_REVIEW_REFACTOR_COMPLETION-1, REVIEW_REVIEW_REFACTOR_COMPLETION-2 |

## 의존 관계 및 구현 순서

1. 기존 subtask predecessor completion은 충족됐다. item 1의 package별 함수 분해를 먼저 수행해 최종 파일/function LOC를 확정한다.
2. item 2에서 client source/read-set을 분리하고 item 1 결과까지 포함해 baseline을 한 번만 정합화한다.
3. targeted 검증 뒤 전체 fresh 회귀와 worktree audit assertion을 실행하고 review stub을 채운다.

## 최종 검증

```bash
gofmt -d apps/edge/internal/edgecmd/smoke_openai.go apps/edge/internal/transport/server.go apps/edge/internal/transport/connection_handlers.go apps/node/internal/adapters/openai_compat/execute.go apps/node/internal/adapters/openai_compat/stream.go apps/node/internal/adapters/vllm/stream.go apps/node/internal/bootstrap/module.go apps/node/internal/bootstrap/runtime_supervisor.go
dart format --output=none --set-exit-if-changed apps/client/lib/widgets/nodes_panel.dart apps/client/lib/widgets/nodes_panel_sections.dart apps/client/test/edge_nodes_panels_test.dart apps/client/test/provider_status_test.dart packages/flutter/iop_console/lib/src/iop_console_overview.dart packages/flutter/iop_console/test/iop_console_shell_test.dart
GOCACHE=/tmp/iop-go-cache-review-review-g09 go test -count=1 ./apps/edge/cmd/edge ./apps/edge/internal/transport ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm ./apps/node/internal/bootstrap
(cd apps/client && flutter test test/edge_nodes_panels_test.dart test/provider_status_test.dart)
(cd packages/flutter/iop_console && flutter test)
GOCACHE=/tmp/iop-go-cache-review-review-g09 go test -count=1 ./...
make client-test
(cd apps/client && flutter analyze)
python3 scripts/readability_audit_test.py
make readability-audit
python3 scripts/readability_audit.py --input-mode worktree --output /tmp/iop-review-review-g09-readability.json
python3 - <<'PY'
import json

report = json.load(open("/tmp/iop-review-review-g09-readability.json", encoding="utf-8"))
targets = {
    ("apps/edge/internal/edgecmd/smoke_openai.go", "smokeOpenAICmd"),
    ("apps/edge/internal/transport/server.go", "Server.onNodeConnected"),
    ("apps/node/internal/adapters/openai_compat/stream.go", "Adapter.Execute"),
    ("apps/node/internal/adapters/openai_compat/execute.go", "Adapter.Execute"),
    ("apps/node/internal/adapters/vllm/stream.go", "Vllm.Execute"),
    ("apps/node/internal/bootstrap/module.go", "Module"),
    ("packages/flutter/iop_console/lib/src/iop_console_overview.dart", "_IopConsoleOverviewState.build"),
}
bad_functions = [
    v for v in report["violations"]
    if v.get("metric") == "function_loc"
    and v.get("level") == "split_review"
    and (v.get("path"), v.get("function")) in targets
]
assert not bad_functions, bad_functions
bad_nodes_file = [
    v for v in report["violations"]
    if v.get("path") == "apps/client/lib/widgets/nodes_panel.dart"
    and v.get("metric") == "file_loc"
]
assert not bad_nodes_file, bad_nodes_file
client_sets = [
    item for item in report["task_read_sets"]
    if item["task_id"].startswith("client-") or item["task_id"] == "iop-console-overview-readability"
]
assert client_sets, "missing responsibility-specific client read sets"
assert all(item["total_loc"] <= 2000 for item in client_sets), client_sets
print("TARGET_LONG_FUNCTIONS_CLEARED=1")
print("NODES_PANEL_FILE_WARNING_CLEARED=1")
print("CLIENT_READ_SETS_WITHIN_2000=1")
PY
GOCACHE=/tmp/iop-go-cache-review-review-g09 make test-e2e
git diff --check
```

기대 결과: format diff 없음, targeted/full Go와 Flutter tests/analyze PASS, readability unit/ratchet 및 세 assertion PASS, warm isolated-cache E2E의 auxiliary mock/OpenAI-Ollama/CP-Edge wire PASS, whitespace/conflict 오류 없음. Go test cache 결과는 허용하지 않으며 `-count=1`로 fresh 실행한다.

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