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

# 장기 함수·ratchet·실제 CLI evidence 보완 계획

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

아래 Required 3건을 구현하고 모든 검증을 실제로 실행한 뒤 `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 대상이다.

## 배경

첫 리뷰는 production/E2E 장기 함수 21개 중 10개를 symbol별 근거 없이 일괄 예외 처리했고, 533→566 baseline 상향을 신규 수용이 아니라고 기술했으며, 실제 CLI 상태도 사전 점검과 다르게 기록해 FAIL됐다. 이 후속은 책임 혼합이 확인된 세 함수를 같은 파일 안에서 분해하고, 남는 모든 장기 함수를 개별 판정하며, ratchet과 profile blocker evidence를 관측 사실에 맞춘다. 외부 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_0.log`
- 이전 review: `agent-task/m-agent-readable-repository-refactor/09+01,03,04,05,06,07,08_secondary_completion/code_review_cloud_G09_0.log`
- 판정: FAIL, Required 3 / Suggested 0 / Nit 0.
- Required:
  - production/E2E `function_loc >= 120` 21개 전수의 symbol별 `refactor|exception` 근거가 없다.
  - `scripts/readability_baseline.json`의 533→566 상향이 allowlist 수용임을 숨겼고 `reason`이 수용 사유를 설명하지 않는다.
  - 실제 CLI 설치/config preflight 없이 네 profile 모두 설치·로그인·계정 부재로 일괄 기록했다.
- reviewer 검증: 전체 Go, Flutter test/analyze, readability audit 122 unit tests, `GOCACHE=/tmp/iop-go-cache make test-e2e`는 PASS. 기본 Go cache는 권한 오류가 있어 `/tmp/iop-go-cache`를 사용한다.
- 실제 profile preflight: Claude 2.1.212, Codex 0.144.5, OpenCode 1.18.3 설치; Antigravity 미설치. 네 profile 모두 활성 `configs/edge.yaml` 항목이 없어 `scripts/e2e-smoke.sh`에서 config blocker로 종료한다.
- 이전 실제 Codex evidence는 `agent-task/archive/2026/07/m-agent-readable-repository-refactor/06_cli_adapter/complete.log`에 있으며, 현재 실행과 구분해서만 재사용한다.

## 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

## 분석 결과

### 읽은 파일

- 규칙/계획: `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/code-review/SKILL.md`, `agent-test/local/rules.md`, `agent-test/local/{edge,node,client,testing,platform-common}-smoke.md`
- Milestone/spec/contract: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`, `agent-spec/index.md`와 매칭 runtime/input/control spec, `agent-contract/index.md`와 매칭 inner/outer contract
- 이전 evidence: 위 `plan_cloud_G09_0.log`, `code_review_cloud_G09_0.log`, 일곱 predecessor `complete.log`
- audit: `scripts/readability_audit.py`, `scripts/readability_audit_test.py`, `scripts/readability_baseline.json`
- 변경 source: `apps/client/lib/widgets/edges_panel.dart`, `apps/client/lib/widgets/nodes_panel.dart`, `apps/edge/internal/edgecmd/node_register.go`
- 직접 test: `apps/client/test/edge_nodes_panels_test.dart`, `apps/edge/internal/edgecmd/edgecmd_test.go`
- 예외 비교: `packages/flutter/iop_console/lib/src/iop_console_overview.dart`, `packages/flutter/iop_console/test/iop_console_shell_test.dart`
- 실행/preflight: `scripts/e2e-smoke.sh`, `configs/edge.yaml`

### SDD 기준

Milestone은 `SDD: 불필요`다. 외부 API/proto/config schema와 runtime 책임 경계를 바꾸지 않는 동일 파일·패키지 내부 동작 보존형 분해이며, 이 경계를 넘으면 구현을 중단하고 SDD gate를 재판정한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 edge/node/client/testing/platform-common smoke profile을 읽고 적용했다.
- 현재 runner/repo: local `/config/workspace/iop`, branch `main`, 계획 작성 시 HEAD `1378a7f`, 사용자의 baseline 변경과 task artifact가 있는 dirty worktree를 보존한다.
- Go/Flutter: Go package test와 Flutter widget/analyze를 fresh 실행한다. Go cache는 기본 `/config/.cache/go-build` 권한 오류가 관측됐으므로 명령에 `GOCACHE=/tmp/iop-go-cache`를 고정한다.
- 외부 CLI preflight: `command -v`와 version을 먼저 기록한다. 활성 profile config가 없으면 인증/계정을 추측하지 않고 config blocker로 기록한다. profile이 생겼다면 `scripts/e2e-smoke.sh` full drive 결과를 기록한다.
- 로컬 포트는 smoke fixture가 동적 할당한다. Docker, 외부 provider endpoint, emulator/device는 필요하지 않다.

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

- `EdgesPanel`과 `NodesPanel`의 populated path는 `edge_nodes_panels_test.dart`가 검증하지만 loading/error/empty branch는 직접 검증이 없다. 분해 중 branch 누락을 막는 widget tests를 추가한다.
- `nodeRegisterCmd`는 create/update, Windows bootstrap, YAML 보존을 기존 Go tests가 검증한다. 내부 helper 추출이므로 새 동작 test는 추가하지 않고 기존 tests를 fresh 실행한다.
- baseline reason은 `readability_audit_test.py`의 schema/ratchet tests와 `make readability-audit`로 검증한다.
- real profile은 active config가 없어 현재 full drive가 불가능하다. 설치/config/실행 blocker를 profile별로 분리하는 것이 이번 evidence 완료 조건이다.

### 심볼 참조

- public symbol rename/remove는 없다.
- `EdgesPanel`: `apps/client/lib/client_home_page.dart:160`.
- `NodesPanel`: `apps/client/lib/client_home_page.dart:168`, `apps/client/test/provider_status_test.dart:170,256,406`.
- `nodeRegisterCmd`: `apps/edge/internal/edgecmd/node_register.go:82`.
- 새 helper는 기존 파일의 private/package-private symbol로만 만든다.

### 분할 판단

split 정책을 재평가했다. 이 task는 이미 `09+01,03,04,05,06,07,08_secondary_completion`이라는 통합 완료 subtask이고 일곱 predecessor의 archive `complete.log`가 각각 하나씩 존재한다. Required는 UI/Go 분해, ratchet 설명, CLI evidence로 소유 경계가 다르지만 모두 동일한 `secondary-modules`/`long-functions` 완료 주장의 신뢰성을 닫아야 하며 하나만 먼저 PASS할 수 없다. 기존 dependency 경로를 바꾸는 새 sibling 분할보다 같은 task의 단일 follow-up review가 안전하다.

### 범위 결정 근거

- 포함: 세 책임 혼합 함수의 동일 파일 내부 helper 분해, branch regression tests, baseline allowlist reason, review evidence.
- 제외: 다른 18개 장기 함수의 동작 변경. 이들은 전수 표에서 단일 책임 경계·분리 손실·직접 test를 각각 입증해야 하며, 입증하지 못한 symbol은 완료로 표시하지 않는다.
- 제외: `skill-entrypoints`, `evidence-links`, 외부 API/wire/schema, generic `utils`/micro-package, test LOC만 줄이는 기계적 분할.

### 빌드 등급

`cloud-G09`. 다도메인 장기 함수 판정과 실제 terminal profile blocker를 함께 검증해야 하고, 부분적으로 그럴듯한 문서화만으로 잘못 PASS할 위험이 높다.

## 구현 체크리스트

- [ ] 21개 production/E2E 장기 함수의 전수 판정표를 만들고 `EdgesPanel.build`, `NodesPanel._buildNodeContent`, `nodeRegisterCmd`를 책임별 helper로 분해한다.
- [ ] `update-roadmap/SKILL.md` 566줄 baseline을 명시적 allowlist 수용으로 정정하거나 533으로 복원해 ratchet 의미를 정확히 맞춘다.
- [ ] Claude/Codex/OpenCode/Antigravity의 설치·version·활성 profile·실행 결과를 profile별 실제 evidence로 기록한다.
- [ ] targeted 및 전체 Go/Flutter, readability, mock E2E와 profile blocker 검증을 fresh 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REFACTOR_COMPLETION-1] 장기 함수 전수 판정과 책임 혼합 함수 분해

문제: 이전 review는 21개 중 10개 symbol의 근거를 생략했고, 아래 세 함수는 상태/데이터/렌더링 또는 검증/저장/출력을 한 함수에 함께 둔다.

Before (`apps/client/lib/widgets/edges_panel.dart:23`):

```dart
Widget build(BuildContext context) {
  if (isLoading && edges.isEmpty) { ... }
  if (error != null && edges.isEmpty) { ... }
  if (edges.isEmpty) { ... }
  final selectedEdge = ...;
  Widget buildEdgeList(bool isMobile) { ... }
  Widget buildEdgeDetails(bool isMobile) { ... }
  return LayoutBuilder(...);
}
```

Before (`apps/client/lib/widgets/nodes_panel.dart:243`):

```dart
Widget _buildNodeContent() {
  if (isLoading) { ... }
  if (error != null) { ... }
  final nodes = selectedEdgeStatus?.nodes ?? [];
  if (nodes.isEmpty) { ... }
  return ListView.builder(...node header/config/adapters/providers...);
}
```

Before (`apps/edge/internal/edgecmd/node_register.go:86`):

```go
func nodeRegisterCmd() *cobra.Command {
	c := &cobra.Command{
		// metadata
		RunE: func(cmd *cobra.Command, args []string) error {
			// load, validate flags, upsert, artifact refresh, patch/write, output
		},
	}
	// flags
	return c
}
```

해결 방법:

- `EdgesPanel.build`는 state 선택과 responsive 조합만 유지하고 `_buildLoadingState`, `_buildErrorState`, `_buildEmptyState`, `_buildPopulatedPanel`, `_buildEdgeList`, `_buildEdgeDetails`로 위임한다.
- `NodesPanel._buildNodeContent`는 state 선택만 유지하고 `_buildLoadingState`, `_buildErrorState`, `_buildEmptyState`, `_buildNodeList`, `_buildNodeCard`, `_buildAdapters`, `_buildProviderCatalog`로 위임한다.
- `nodeRegisterCmd`는 Cobra metadata/flags만 구성하고 `RunE: runNodeRegister`로 위임한다. `runNodeRegister`는 load/orchestration만 맡고 flag validation, existing/new definition 적용, target/artifact resolution, persist/output을 domain-named helper로 분리한다.
- 새 helper는 같은 파일과 기존 package/class 경계에 둔다. public API, text, YAML shape, file mode, bootstrap output을 바꾸지 않는다. 각 대상 원함수는 audit `split_review`에서 제거하고 새 helper도 120줄 미만으로 유지한다.
- 최신 audit에서 나온 production/E2E `function_loc >= 120` 전부를 review에 `path | symbol | LOC | resolution(refactor/exception) | 단일 책임 경계 | 분리 시 손실 | 직접 test` 열로 기록한다. 세 대상은 `refactor`; 나머지는 generic “workflow 응집”이 아니라 symbol별 근거가 있어야 한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/widgets/edges_panel.dart`: state/list/detail/responsive helper 분해.
- [ ] `apps/client/lib/widgets/nodes_panel.dart`: state/node/config/provider helper 분해.
- [ ] `apps/client/test/edge_nodes_panels_test.dart`: 두 panel의 loading/error/empty branch tests 추가.
- [ ] `apps/edge/internal/edgecmd/node_register.go`: command construction과 register execution 단계 분해.
- [ ] `apps/edge/internal/edgecmd/edgecmd_test.go`: 기존 create/update/Windows/YAML tests를 유지하고 필요 시 private helper 회귀만 추가.
- [ ] `CODE_REVIEW-cloud-G09.md`: audit가 산출한 모든 production/E2E split_review symbol의 전수 판정표 작성.

테스트 작성:

- 추가: `apps/client/test/edge_nodes_panels_test.dart`의 `EdgesPanel preserves loading error and empty states`와 `NodesPanel preserves loading error and empty states`. 각 state의 대표 text/progress/retry callback을 검증한다.
- Go test 추가는 skip. `TestNodeRegisterUpsertsYAMLAndOutputsBootstrap`, `TestNodeRegisterOutputsPowerShellBootstrapForWindowsTarget`, YAML 보존 tests가 extraction 전후 계약을 직접 검증한다.

중간 검증:

```bash
dart format apps/client/lib/widgets/edges_panel.dart apps/client/lib/widgets/nodes_panel.dart apps/client/test/edge_nodes_panels_test.dart
(cd apps/client && flutter test test/edge_nodes_panels_test.dart)
GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/edgecmd
python3 scripts/readability_audit.py --input-mode worktree --output /tmp/iop-review-followup-readability.json
```

예상: targeted tests PASS. `EdgesPanel.build`, `NodesPanel._buildNodeContent`, `nodeRegisterCmd`와 새 helper가 function `split_review`에 없다.

### [REVIEW_REFACTOR_COMPLETION-2] baseline allowlist 의미 정정

문제: `scripts/readability_baseline.json:27-31`은 566을 허용하면서 reason이 임계치만 반복하고, 이전 review는 533→566 상향을 신규 수용이 아니라고 잘못 기술했다.

Before (`scripts/readability_baseline.json:27`):

```json
{
  "path": "agent-ops/skills/common/update-roadmap/SKILL.md",
  "metric": "file_loc",
  "level": "split_review",
  "value": 566,
  "reason": "file skill_entrypoint exceeds split_review threshold (566 > 500)"
}
```

해결 방법: 별도 `skill-entrypoints` Task 경계를 침범하지 않기 위해 value 566은 유지하되 이것을 명시적 regression allowlist로 취급한다. `reason`은 `accepted ceiling 566 for committed update-roadmap workflow growth in 1378a7f; bounded at 566 pending milestone task skill-entrypoints`로 바꾸고, review에도 “33 LOC 증가를 수용했다”고 정확히 기록한다. 전체 baseline 재생성은 금지한다.

수정 파일 및 체크리스트:

- [ ] `scripts/readability_baseline.json`: 해당 단일 reason만 정정.
- [ ] `CODE_REVIEW-cloud-G09.md`: baseline 상향을 allowlist 수용으로 정확히 기록.

테스트 작성: skip. data-only 문구 변경이며 기존 strict schema/ratchet 122 tests로 검증한다.

중간 검증:

```bash
python3 scripts/readability_audit_test.py
make readability-audit
```

예상: 122 tests PASS, ratchet PASS, 변경된 reason이 비어 있지 않고 수용 경계를 설명한다.

### [REVIEW_REFACTOR_COMPLETION-3] 실제 CLI profile evidence 정정

문제: 이전 review는 설치 여부와 활성 config를 확인하지 않고 네 profile 모두 설치·로그인·계정 부재라고 기록했다. 실제로 세 CLI는 설치돼 있고 네 실행의 첫 blocker는 profile config 부재다.

해결 방법: 각 tool의 `command -v`와 version을 기록하고, 각 profile에 `IOP_E2E_PROFILE`을 지정해 실행한다. `Profile '<name>' not found in configs/edge.yaml`이면 config blocker로 기록하고 인증/계정 상태는 추측하지 않는다. Antigravity는 binary absent와 config absent를 둘 다 기록한다. `06_cli_adapter/complete.log`의 Codex 실제 왕복은 prior evidence로 별도 표기한다.

수정 파일 및 체크리스트:

- [ ] `CODE_REVIEW-cloud-G09.md`: tool/profile별 `binary | version | active config | current run | prior evidence` 표와 실제 stdout/stderr 작성.
- [ ] tracked config에 credential/profile을 임시 추가하지 않는다.

테스트 작성: skip. 외부 CLI/config preflight evidence다.

중간 검증:

```bash
for tool in claude codex opencode antigravity; do command -v "$tool" || true; done
claude --version
codex --version
opencode --version
for profile in claude codex opencode antigravity; do
  echo "PROFILE=$profile"
  IOP_E2E_PROFILE="$profile" bash scripts/e2e-smoke.sh || true
done
```

예상: 설치/version과 profile별 current blocker가 실제 출력으로 구분된다. config가 계속 없으면 네 profile 모두 config blocker이며, 이를 미설치/미로그인으로 바꾸지 않는다.

### [REVIEW_REFACTOR_COMPLETION-4] 전체 회귀와 완료 evidence

문제: UI/Go 내부 분해가 사용자 실행 계약을 보존했고 audit/CLI evidence가 재현 가능함을 한 번에 확인해야 한다.

해결 방법: targeted 검증 뒤 전체 Go/Flutter, package Flutter, syntax, mock E2E, readability, diff check를 fresh 실행한다. 기본 Go cache 문제를 피하려고 모든 Go/E2E 명령에 `/tmp/iop-go-cache`를 사용한다.

수정 파일 및 체크리스트:

- [ ] 아래 최종 검증을 실제로 실행하고 stdout/stderr를 review stub에 붙인다.
- [ ] 실패/차단은 성공으로 요약하지 않고 명령·exit·원인을 남긴다.

테스트 작성: skip. 전체 regression verification item이다.

중간 검증:

```bash
GOCACHE=/tmp/iop-go-cache go test -count=1 ./...
make client-test
(cd packages/flutter/iop_console && flutter test)
```

예상: 모두 PASS.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/client/lib/widgets/edges_panel.dart` | REVIEW_REFACTOR_COMPLETION-1 |
| `apps/client/lib/widgets/nodes_panel.dart` | REVIEW_REFACTOR_COMPLETION-1 |
| `apps/client/test/edge_nodes_panels_test.dart` | REVIEW_REFACTOR_COMPLETION-1 |
| `apps/edge/internal/edgecmd/node_register.go` | REVIEW_REFACTOR_COMPLETION-1 |
| `apps/edge/internal/edgecmd/edgecmd_test.go` | REVIEW_REFACTOR_COMPLETION-1 |
| `scripts/readability_baseline.json` | REVIEW_REFACTOR_COMPLETION-2 |
| `CODE_REVIEW-cloud-G09.md` | REVIEW_REFACTOR_COMPLETION-1,2,3,4 |

## 의존 관계 및 구현 순서

directory name의 predecessor 01/03/04/05/06/07/08은 모두 `agent-task/archive/2026/07/m-agent-readable-repository-refactor/<subtask>/complete.log`로 충족됐다. 1의 source 분해와 test를 먼저 끝내고, 2의 baseline reason을 정정한 다음 3의 환경 evidence를 수집하고 4의 전체 검증을 실행한다.

## 최종 검증

```bash
dart format --output=none --set-exit-if-changed apps/client/lib/widgets/edges_panel.dart apps/client/lib/widgets/nodes_panel.dart apps/client/test/edge_nodes_panels_test.dart
GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/internal/edgecmd
(cd apps/client && flutter test test/edge_nodes_panels_test.dart)
GOCACHE=/tmp/iop-go-cache go test -count=1 ./...
make client-test
(cd apps/client && flutter analyze)
(cd packages/flutter/iop_console && flutter test)
bash -n scripts/e2e-smoke.sh scripts/e2e-long-context-admission-smoke.sh
GOCACHE=/tmp/iop-go-cache make test-e2e
python3 scripts/readability_audit_test.py
make readability-audit
python3 scripts/readability_audit.py --input-mode worktree --output /tmp/iop-review-followup-readability.json
python3 - <<'PY'
import json
data = json.load(open("/tmp/iop-review-followup-readability.json"))
targets = {
    ("apps/client/lib/widgets/edges_panel.dart", "EdgesPanel.build"),
    ("apps/client/lib/widgets/nodes_panel.dart", "NodesPanel._buildNodeContent"),
    ("apps/edge/internal/edgecmd/node_register.go", "nodeRegisterCmd"),
}
remaining = {
    (v["path"], v.get("function", ""))
    for v in data["violations"]
    if v.get("metric") == "function_loc" and v.get("level") == "split_review"
}
assert not targets & remaining, sorted(targets & remaining)
print("TARGET_SPLIT_REVIEW_CLEARED=1")
PY
python3 - <<'PY'
import json
data = json.load(open("scripts/readability_baseline.json"))
entry = next(x for x in data["file_thresholds"] if x["path"] == "agent-ops/skills/common/update-roadmap/SKILL.md")
assert entry["value"] == 566
assert "accepted ceiling 566" in entry["reason"]
assert "skill-entrypoints" in entry["reason"]
print("BASELINE_REASON_OK=1")
PY
for tool in claude codex opencode antigravity; do command -v "$tool" || true; done
claude --version
codex --version
opencode --version
for profile in claude codex opencode antigravity; do
  echo "PROFILE=$profile"
  IOP_E2E_PROFILE="$profile" bash scripts/e2e-smoke.sh || true
done
git diff --check
```

예상: formatter, targeted/full tests, analyze, mock E2E, audit와 assertions가 PASS한다. real profile은 실제 실행 성공 또는 profile별 정확한 blocker가 기록된다. Go test cache 출력은 허용하지 않고 `-count=1`로 fresh 실행한다.

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