Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-293e905f.md
T
GodopuandClaude Fable 5.1 6b7198f73c release(v4.7.1): bump framework and eight skills to 4.7.1 in lockstep; archive PR #32 merge-side reviews
Issue #25: a promoted plan is reused only with --plan-file; the implicit
newest-plan load is gone in both runners, and an empty planner report no
longer passes silently. VERSIONS.md carries the entry; the four merge-side
review reports and their identity sidecars (base 6fc9194 -> head 1e82651,
evidence review-tree) are archived under .agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-22 00:38:10 +09:00

140 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔍 Review Report — PR #32 병합 전 독립 리뷰 (job `293e905f`)
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-09-22
- **대상**: PR #32 `fix/issue-25` **@ `1e82651`**, base `main 6fc9194` (v4.7.0). 5커밋, 4파일 `+486/−31`.
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20260921T150212Z-97d845/1` (브리프가 지정한 리뷰 워크트리). 실제로 거기서만 검증했다.
- **identity 확인**: 검증 경로의 `HEAD = 1e826515cc2a07ea1f8da0ff1259e1ea4dde86fd`, `HEAD^{tree} = 4fd82ad1c2682d12c3f5114818f97291af28a032` — 브리프의 `head`·`tree` 와 **일치**한다(항목 5 identity 가 실제로 작동함을 이 잡 자체가 보여준다).
- **안전 규칙 준수**: 검증 경로·소스 워크트리(`multi-agent-mux-wt/pr-32`)·본 체크아웃(`main 6fc9194`) 모두 수정·커밋·checkout 없음. 실험은 전부 scratchpad 임시 디렉터리(uuid4 이름)에서 했고, pytest 는 `--basetemp` 을 검증 경로 밖으로 주었다. 실제 `.mam`·데몬·세션 조작 없음, push·merge·PR 상태 변경 없음, purge·scrub 실행 없음.
## 결론 요약
**병합을 막을 결함은 없다.** 핵심 변경(암묵적 계획서 로드 제거, `--plan-file` 명시 재사용, 네 지점 공통 계획 텍스트 검사)은 코드·문서·테스트가 일치하며, 새 테스트 17건 중 **13건이 수정 전 소스에서는 실패**한다(직접 확인). 비차단 지적 3건(E1–E3)을 남긴다.
## 1. 암묵적 로드 제거 (항목 1)
| 확인 | 결과 |
|---|---|
| Python 러너 | `runner.py` 에서 `glob` import 가 사라졌고, 남은 `glob` 사용처는 `find_report` 의 `report-final.md` 탐색(`:568`) 하나뿐이다. `.agents/reports/<planner>/plan-*.md` 를 읽는 코드가 없다 |
| 레거시 셸 러너 | `run_loop.sh` 의 `EXISTING_PLAN_FILE`/`ls -t .../plan-*.md` 블록이 통째로 사라지고 `log_info "No plan loaded; the Creator plans by itself."` 만 남았다 |
| 다른 경로 | 저장소 전체에서 `plan-*.md` 를 읽는 코드는 없다(승격 쪽 **쓰기**만 `runner.py:1368-1372` 에 남는다 — 정상) |
| planner 자동 선택 | `resolve_planner_session`(`run_loop.sh:386`)·`resolve_planner_session` 파이썬 대응은 남아 있으나, 그 결과는 **plan 모드의 위임 대상과 승격 경로**에만 쓰인다. 자기계획 모드에서 planner 이름으로 파일을 찾는 코드는 없다 |
## 2. `--plan-file` (항목 2) — 실측
검증 경로의 러너를 임시 제어 루트(`WORKSPACE_ROOT=<tmp>/ctl`)로 직접 돌려 확인했다.
| 입력 | 결과 |
|---|---|
| 없는 파일 | `[✗] --plan-file <절대경로>: not a readable file with plan text in it.` · **exit 1** |
| 빈 파일 / 공백만 있는 파일 | 같은 메시지 · exit 1 |
| 디렉터리 | 같은 메시지 · exit 1 (`is_file()` 로 걸린다) |
| 읽기 권한 없음(`chmod 000`) | 같은 메시지 · exit 1 (`OSError` → `None`) |
| `--plan` 과 함께 | `ERROR: --plan-file cannot be combined with --plan.` + 사용 예 2줄 · exit 1 |
| 상대경로 | **제어 루트 기준**. 호출 cwd(`<tmp>/elsewhere`)에 `plans/p.md` 가 실제로 있어도 러너는 `<ctl>/plans/p.md` 를 찾고 실패한다 — #25 의 "다른 디렉터리의 동명 파일을 집어오는" 사고를 구조적으로 막는다 |
| 공백 있는 경로(`my plans/p.md`, 상대) | 정상 로드: `Using the plan given with --plan-file: <ctl>/my plans/p.md (modified ... UTC)` (셸을 거치지 않으므로 인용 문제 없음) |
| 절대경로·`~` | `Path.__truediv__` 가 절대경로를 그대로 두고 `expanduser()` 가 앞서 적용된다(P3d 가 고정) |
| `--review-only` 와 함께 | 경고 후 무시(`log_warn`), `current_plan` 을 채우지 않는다 |
| 레거시 셸(`MAM_LOOP_PY=0`) | `ERROR: --worktree/--base/--review-only/--review-isolation/--remote-review/--plan-file require the Python loop (MAM_LOOP_PY=1)` · exit 1 — **동등 구현 대신 명시적 거부**이며, 기본 경로(`MAM_LOOP_PY=1`)는 `exec "$_py" -m lib_py.loop "${MAM_LOOP_ARGV[@]}"`(`run_loop.sh:262`)로 원본 argv 를 그대로 넘기므로 공백 경로도 안전하다 |
읽기가 **잡을 하나도 만들기 전**(parse_args 단계)에 끝나는 것도 확인했다 — 실패 시 레지스트리에 아무 흔적이 없다.
## 3. 계획 텍스트 검사 (항목 3)
`_plan_text()`(`runner.py:303-317`)가 "읽을 수 있는 일반 파일 + 공백 제외 내용 있음"을 단일 판정으로 만들고, **파이썬 러너의 네 지점이 모두 이를 쓴다**: `--plan-file`(`:265`), `--plan` 초기 계획서(`:1025`), 논의 턴 refine(`:1054`), 리뷰 에스컬레이션 refine(`:1284`).
- 빈 초기 계획서 → `log_error` 후 **return 1**. 이때 이미 만든 잡은 `release_guard`/정리 경로가 담당하고, 자기계획으로의 조용한 격하가 사라졌다.
- 빈 refine → 경고 + **이전 계획 유지**. 그래서 "갱신된 다음 계획서에 입각하여…" 프롬프트가 빈 계획을 싣는 일이 없다.
- 에스컬레이션 경로(`[ESCALATE: PLANNER]`)는 `if self.plan_mode and complex_fix`(`:1275`)로 **plan 모드에만** 걸린다. `--plan-file` 로 계획을 실은 실행에서 ESCALATE 판정이 나오면 재계획 없이 일반 수정 라운드로 간다 — 샌드박스에서 직접 구동해 확인했다(Planner 위임 0건, 수정 프롬프트는 "리뷰어들이 지적한 다음 피드백…"). SKILL.md:31 의 "수정 라운드에는 리뷰 피드백만" 서술과 **일치**한다. 다만 이 동작 자체는 E2 로 남긴다.
## 4. 체크포인트 (항목 4)
`.mam/loop-checkpoint.json` 의 `current_plan` 은 **쓰기 전용**이다. `checkpoint.load_checkpoint()` 는 정의되어 있으나 `runner.py` 어디에서도 호출하지 않는다(`grep 'checkpoint\.'` → `save_checkpoint`·`remove_checkpoint` 뿐). 따라서 본 체크아웃 체크포인트에 남아 있는 Issue #6 계획 텍스트가 다음 루프나 재개에서 되살아날 경로는 없다. 계획은 매 실행마다 `--plan-file` 또는 `--plan` 으로만 들어온다.
## 5. 옵션 없을 때의 동작 변화 (항목 5)
- 자기계획 프롬프트(`runner.py:1095`)와 승격 경로(`:1367` `if self.plan_mode and self.current_plan and self.plan_job_id`)는 그대로다. 즉 `--plan-file` 로 실은 계획서는 **승격되지 않는다**(승격은 plan 모드의 planner 산출물만) — 옳은 구분이다.
- 바뀐 것은 "planner 리포트 디렉터리에서 최신 plan-*.md 를 끌어오던" 동작 하나뿐이다. 그 외 프롬프트 문구·옵션 기본값·종료 코드는 동일하다.
- 기존 스위트 정합은 §6 에서 실측했다.
## 6. 테스트 (항목 6)
### 6-1. 검증 경로에서의 실행
```
cd <검증 경로> && PYTHONDONTWRITEBYTECODE=1 <제어 루트 .venv/bin/python> -m pytest -p no:cacheprovider -q -rfEs \
--basetemp=<검증 경로 밖> tests/test_issue25_plan_reuse.py tests/test_loop_cli.py tests/test_issue5_phase3_loop.py \
tests/test_issue21_loop_b.py tests/test_issue21_phase2_loop_a.py tests/test_issue21_acceptance.py
→ 2 failed, 108 passed in 220.98s (skipped 0)
```
**실패 2건은 PR 회귀가 아니라 리뷰 워크트리에 `.venv` 가 없어서 생긴 환경 문제다.**
| 실패 | 원인 | 확인 |
|---|---|---|
| `test_loop_cli.py::test_creator_parses_and_reaches_session_check` | `run_loop.sh` 를 하위 프로세스로 띄우는데, `REPO_ROOT`(= 검증 경로)에 `.venv` 가 없어 `_py` 가 시스템 python 으로 떨어지고 `RuntimeError: Failed to eager load lib_py.loop.autocomplete: No module named 'paho'` 로 죽는다(`run_loop.sh:253`) | `AGENT_PYTHON_BIN=<제어 루트 .venv/bin/python>` 만 주고 둘을 다시 돌리면 **2 passed in 0.34s** |
| `test_issue5_phase3_loop.py::test_p3_02_run_loop_wrapper_delegates_to_python_default` | 동일 | 동일 |
두 시험 모두 PR 이 건드리지 않은 파일이며, 실패 메시지에 계획서·`--plan-file` 은 등장하지 않는다.
### 6-2. 수정 없이는 실패하는가 — 확인함
검증 경로 사본을 만들고 `runner.py`·`run_loop.sh`·`SKILL.md` **세 파일만 base `6fc9194` 판으로 되돌려** 새 테스트를 돌렸다(검증 경로 자체는 건드리지 않았다).
```
13 failed, 4 passed in 1.51s
FAILED P1, P2, P3b, P3c, P3d, P5, P9[empty], P9[whitespace], P10, P11, P7, P7b, P8
```
통과한 4건(P3·P4·P5b·P6)은 옵션 없는 일반 CLI 오류·plan 모드 정상 경로라 수정과 무관한 것들이다. 즉 새 테스트는 이 변경을 정확히 겨냥한다.
### 6-3. 격리
| 항목 | 결과 |
|---|---|
| 실제 `.mam/jobs` 전후 | **차이 0** |
| 실제 `.agents/reports` | 새 항목 없음(테스트는 각자 `tmp_path` 안에 제어 루트를 만든다 — `ctl` 픽스처) |
| 검증 경로 tracked 파일 | 변화 없음(`git status --short` 클린, HEAD `1e82651` 유지) |
| 검증 경로 ignored | 내 실행이 `.mam/`(무시됨)을 만들었다. tracked 파일·커밋에는 영향이 없으며, 지우면 리뷰 트리를 수정하는 셈이라 그대로 두었다 → E3 |
| 실행 시간 | 새 파일 `test_issue25_plan_reuse.py` 는 전체 17건이 **1.5 s** 안에 끝난다(브로커·herdr 의존 없음, 모두 stub) |
## 7. 문서 (항목 7)
- 옛 서술("기존에 승격된 계획서가 있다면 이를 로드")은 `.agents`·`docs` 어디에도 남아 있지 않다(전수 grep).
- SKILL.md 의 사용법 문자열·CLI 표·Workflow 예시·Mermaid 다이어그램이 모두 `--plan-file` 기준으로 맞춰졌고, "계획서가 비어 있을 때" 절이 네 지점의 판정을 정확히 설명한다.
- 러너 usage 와 셸 usage 문자열이 동일하다(P7 이 이를 고정한다).
- 배포 사본: 저장소의 `.claude/skills/*` 는 `../../.agents/skills/*` **심링크**라 자동으로 동기화된다. 사용자 홈 `~/.claude/skills` 에는 mux 스킬 사본이 없다. 옛 문구를 가진 사본은 발견되지 않았다.
## 8. 병합 차단 여부 (항목 8)
버전 락스텝 bump 부재(`lib.sh:32`·`eager.py:16`·loop SKILL.md 모두 `4.7.0`, 오케스트레이터가 4.7.1 로 처리 예정) 외에 **병합을 막을 결함은 없다**.
| # | 비차단 지적 | 근거 |
|---|---|---|
| E1 | 레거시 셸 러너(`MAM_LOOP_PY=0`)의 plan 모드 읽기에는 빈 계획서 검사가 없다: `CURRENT_PLAN=$(cat "$PLAN_FILE")`(`run_loop.sh:501`). D13/D14 계약은 파이썬 러너에만 성립하는데 SKILL.md 의 "계획서가 비어 있을 때" 절은 이를 명시하지 않는다 | 후속: 한 줄 거부 또는 문서에 "Python 러너 기준" 명시 |
| E2 | `--plan-file` 로 계획을 실은 실행에서 `[ESCALATE: PLANNER]` 는 재계획 없이 일반 수정 라운드로 내려간다(`runner.py:1275` 가 `plan_mode` 로 게이트). 샌드박스 구동으로 확인(Planner 위임 0건). 이 게이트는 PR 이전부터 있던 것이고 SKILL.md 서술과도 모순되지 않지만, "계획서를 갖고 도는 루프인데 에스컬레이션이 무력화된다"는 점은 문서에 없다 | 문서 보강 권고 |
| E3 | 리뷰 격리 워크트리에는 `.venv` 가 없어, `run_loop.sh` 를 하위 프로세스로 띄우는 스위트는 `AGENT_PYTHON_BIN` 없이는 반드시 실패한다(§6-1). 또한 그 워크트리에서 pytest 를 돌리면 무시되는 `.mam/` 이 생긴다. **PR #32 가 아니라 Issue #21 항목 5 운영 쪽 후속**이다 | 리뷰 브리프의 테스트 명령에 `AGENT_PYTHON_BIN=<제어 루트 .venv/bin/python>` 를 포함하는 것을 권고 |
| E4 | `runner.py:1070` 의 `os.path.getmtime(self.plan_file)` 는 파싱 시점과 Phase 1 사이에 파일이 사라지면 예외로 죽는다(메시지 없는 트레이스백). 극히 좁은 경합 | 사소 |
## 9. 교차검증 가이드
```bash
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260921T150212Z-97d845/1
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python
git -C "$RP" rev-parse HEAD HEAD^{tree} # 1e8265... / 4fd82a... = 브리프의 head·tree
# --plan-file 실패 경로 (임시 제어 루트에서만)
C=$(mktemp -d); mkdir -p "$C/ctl" "$C/elsewhere/plans"; : > "$C/ctl/empty.md"; echo x > "$C/elsewhere/plans/p.md"
(cd "$C/elsewhere" && PYTHONPATH="$RP/.agents/skills" WORKSPACE_ROOT="$C/ctl" MAM_STATE_JSON='{"herdr_sessions":[]}' \
"$PY" -m lib_py.loop --creator c --task t --plan-file plans/p.md; echo "rc=$?") # ctl 기준 경로로 실패, rc 1
# 수정 없이는 실패하는지 (사본에서만)
S=$(mktemp -d); rsync -a --exclude .venv --exclude .git --exclude .mam --exclude nats-docker "$RP/" "$S/"
for f in .agents/skills/lib_py/loop/runner.py .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
.agents/skills/multi-agent-mux-loop/SKILL.md; do git -C "$RP" show 6fc9194:"$f" > "$S/$f"; done
(cd "$S" && PYTHONDONTWRITEBYTECODE=1 "$PY" -m pytest -q -p no:cacheprovider --basetemp=/tmp/bt tests/test_issue25_plan_reuse.py) # 13 failed
# 실패 2건이 환경 문제임을 확인
(cd "$RP" && AGENT_PYTHON_BIN="$PY" PYTHONDONTWRITEBYTECODE=1 "$PY" -m pytest -q -p no:cacheprovider --basetemp=/tmp/bt2 \
tests/test_loop_cli.py::test_creator_parses_and_reaches_session_check \
tests/test_issue5_phase3_loop.py::test_p3_02_run_loop_wrapper_delegates_to_python_default) # 2 passed
```
[VERDICT: PASS]