- I-2: add 5.0s upper-bound execution time assertion in test_bug4_headless_unobservable_fast_path to contractually guard SKS_EMPTY_GIVEUP early-exit latency - I-3: clean up PaneInfo.focused, wire MAM_MAX_PANE_COLS/MAM_MAX_COLS environment variables and CLI --max-cols flag - C-1: apply max_columns growth guard to headless 0x0 layouts, mirroring GUI behavior - Promoted Planner consensus plan Rev.2 (plan-fea5f1b2.md) and unanimous PASS reports from Reviewers Claude (report-55d1a1d9.md) and Cline (report-6f18ba0f.md) - Verified all 333 test cases pass with exit code 0
13 KiB
🔍 리뷰 리포트: 백로그 I-2 / I-3 + C-1 구현 (Job 55d1a1d9)
- 작성일: 2026-08-23
- 역할: Reviewer (
claude) - 대상 계획:
5e4ef463(Rev.2 — I-2 / I-3 / C-1) - 리뷰 대상: HEAD
31b2d70위의 미커밋 작업 트리 변경 5파일 (+160 / −26) - 테스트: 333 passed in 428.43s (exit 0), 333 collected
0. 종합 판정
| 계획 항목 | 상태 | 검증 |
|---|---|---|
| I-2 헤드리스 지연 상한 단언 | 🟢 해결 (뮤테이션 2종 검증) | 조기 return 2 제거 · SKS_EMPTY_GIVEUP 3→20 양쪽에서 FAIL. 정상 소요 1.18 s |
I-3a PaneInfo.focused 제거 |
🟢 해결 | .focused 판독 0건, extract_panes_and_focus 잔존 0건, Tuple import 정리됨 |
I-3b --max-cols env 배선 |
🟢 해결 (뮤테이션 2종 검증) | argparse 인자 제거 · env 기본값 None 복귀 양쪽에서 FAIL. lib.sh 무변경 확인 |
I-3c 앵커/no_panes_default 주석 |
🟢 해결 | 근거 주석(추론 vs 측정, 성장 가드) 반영 |
C-1 헤드리스 max_columns 우회 교정 |
🟢 해결 (뮤테이션 검증) | 체크 제거 시 FAIL. GUI/헤드리스가 4페인·max=2 에서 동일하게 max_columns_reached |
문서 .mam.env.example 3종 + IMPROVEMENTS.md |
🟢 해결 | 템플릿 규약 준수, 배포 신선도 31/31 유지 |
[VERDICT: PASS]
계획의 모든 요구가 구현되었고 뮤테이션 7종이 지정 테스트를 FAIL 시킵니다. 남은 두 항목은 P3 수준이며, 그중 하나는 제 계획의 뮤테이션 명세 오류입니다(§3.2). 차단하지 않고 J-1 / J-2 로 이월합니다.
1. 검증 기반 (Measurement Ledger)
| # | 검증 | 방법 | 결과 |
|---|---|---|---|
| M-1 | 전체 회귀 | pytest tests/ -q |
333 passed (428 s), exit 0 |
| M-2 | 수집 수 | --collect-only |
333 — 계획 예상치와 일치 (330 → 333) |
| M-3 | 대상 파일 | test_layout.py + test_b19_*.py |
25 passed (19 + 6) |
| M-4 | I-2 정상 소요 | --durations |
headless 1.18 s (상한 5.0 s) |
| M-5 | 컴파일 / 인터프리터 | py_compile, /usr/bin/python3 (3.9.6) |
양호 / right P9 |
| M-6 | 배포 신선도 | pytest tests/test_deploy_freshness.py -q |
31 passed — .mam.env.example 추가가 D-7/D-21/D-32 를 깨지 않음 |
| M-7 | 죽은 표면 제거 | grep -rn "\.focused\b" .agents/skills/ / extract_panes_and_focus in tests/ |
0 / 0 |
| M-8 | GUI ↔ 헤드리스 대칭 | 4페인 · max_columns=2 양 모드 |
둘 다 overflow / max_columns_reached |
| M-9 | 뮤테이션 M1 조기 return 2 제거 |
격리 복제본 | 🟢 test_bug4_headless_unobservable_fast_path FAIL |
| M-10 | 뮤테이션 M2 SKS_EMPTY_GIVEUP 3→20 |
동 | 🟢 동 테스트 FAIL |
| M-11 | 뮤테이션 M3 --max-cols argparse 인자 삭제 |
동 | 🟢 CLI·env 테스트 FAIL |
| M-12 | 뮤테이션 M4 --max-cols 기본값 None 복귀 |
동 | 🟢 test_env_max_cols_applies_without_flag FAIL |
| M-13 | 뮤테이션 M5 헤드리스 max_columns 체크 제거 |
동 | 🟢 test_headless_max_columns_growth_guard FAIL |
| M-14 | 뮤테이션 M6 홀수 분기에도 상한 검사(과잉 교정) | 동 | 🔴 19 passed — 가드가 검출 못 함 (§3.2) |
| M-15 | 뮤테이션 M7 GUI max_columns 분기 삭제 |
동 | 🟢 3건 FAIL |
| M-16 | MAM_MIN_PANE_COLS=0 거동 |
env vs 플래그 대조 | 🔴 env 0 → overflow / 플래그 --min-cols 0 → down (§3.1) |
| M-17 | M6 판별 조건 분석 | 헤드리스 n=3/5/7 × max=2 |
정상 코드 전부 down. 과잉 교정 시 n=5,7 만 overflow — 테스트의 n=3 은 임계 미달 |
2. 구현 확인 상세
2.1 I-2 — 시간 단언이 실제로 계약이 되었다
import time 추가, SKS_* 3종을 pin 이 아니라 제거(계획 요구대로), subprocess.run 만 감싼 측정, 원인 가설을 담은 실패 메시지까지 사양대로 구현되었습니다.
두 방향의 뮤테이션에서 모두 FAIL 합니다.
| 뮤테이션 | 결과 |
|---|---|
조기 return 2 제거 |
test_bug4_headless_unobservable_fast_path FAIL (13.7 s 소요) |
SKS_EMPTY_GIVEUP:-3 → :-20 |
동 FAIL (13.5 s) |
두 번째가 특히 값어치 있습니다 — 코드 구조는 그대로 두고 상수만 바꿔도 잡힙니다. 정상 경로는 1.18 s 로 상한 5.0 s 대비 4배 여유가 유지됩니다.
2.2 I-3a — 죽은 표면이 실제로 사라졌다
PaneInfo.focused 필드, focused_id 반환, layout.get("focused_pane_id") 조회가 모두 제거되고 extract_panes_and_focus → extract_panes 로 개명, 호출부 1곳과 tests/test_layout.py 의 미사용 import 2개가 함께 정리되었습니다. Tuple import 도 제거되어 잔재가 없습니다(M-7).
제거 이유를 dataclass 자리에 주석으로 남긴 것도 적절합니다 — 다음 사람이 "왜 focused 가 없지"를 되묻지 않게 합니다.
2.3 I-3b / C-1 — 배선과 교정이 같은 커밋에 함께 들어갔다
계획이 분리 불가로 못박은 부분입니다. MAM_MAX_PANE_COLS opt-in 경로를 살리는 변경과, 그 경로의 헤드리스 정합성을 보장하는 C-1 교정이 한 커밋에 있습니다. 결과적으로 이 변경은 결함을 활성화하지 않습니다.
GUI 와 헤드리스가 같은 시점에 같은 사유로 overflow 합니다(M-8).
GUI -> overflow overflow=True reason=max_columns_reached
HEADLESS -> overflow overflow=True reason=max_columns_reached
lib.sh 는 한 글자도 바뀌지 않았습니다 — bash 3.2 빈 배열 함정을 피하려던 계획의 의도가 그대로 지켜졌습니다.
C-1 주석도 계획이 요구한 두 근거(추론 vs 측정 / 성장 가드)를 모두 담고 있습니다.
2.4 문서
.mam.env.example 3종이 기존 템플릿 규약(#default: + 주석 처리된 대입)을 따르고, MAM_MAX_PANE_COLS 설명에 "Applies to both measured (GUI) and headless 0x0 layouts" 를 명기해 C-1 의 결과를 운영자에게 전달합니다. IMPROVEMENTS.md 는 B-20 항목에 후속 정리를 1–2줄로 추가하고 C-1 을 별도 문장으로 기록했습니다(계획 Q-3 의 처방대로).
3. 잔여 지적 (비차단)
🟡 J-1 (P3) — or 60 관용구가 MAM_MIN_PANE_COLS=0 을 삼킨다
parser.add_argument("--min-cols", type=int, default=_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS") or 60)
parser.add_argument("--min-rows", type=int, default=_env_int("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS") or 20)
_env_int 가 0 을 반환하면 falsy 이므로 or 60 이 발동해 60 으로 덮어씁니다. 같은 값을 플래그로 주면 0 이 그대로 쓰입니다.
MAM_MIN_PANE_COLS=0 MAM_MIN_PANE_ROWS=0 -> {"direction": "overflow", "reason": "single_pane_overflow"}
--min-cols 0 --min-rows 0 -> {"direction": "down", "reason": "single_pane_split_down"}
_env_int('MAM_MIN_PANE_COLS') 는 0 을 정확히 반환하며, or 60 단계에서만 60 이 됩니다(M-16). 즉 동일한 설정을 표현하는 두 경로가 갈라집니다.
0 은 "폭 하한 없음" 을 뜻하는 자연스러운 표현이고, 이번 커밋 이전의 int(os.environ.get(..., 60)) 은 이를 올바르게 처리했습니다. 계획은 --min-cols/--min-rows 의 헬퍼 통일을 권고(비필수) 로만 적었으므로 이 코드는 선택적 확장이었고, 확장 과정에서 falsy-zero 함정이 들어왔습니다.
처방 — _env_int 에 기본값 인자를 주어 or 를 없앱니다.
def _env_int(*names: str, default: Optional[int] = None) -> Optional[int]:
for n in names:
raw = os.environ.get(n, "").strip()
if raw:
try:
return int(raw)
except ValueError:
return default
return default
parser.add_argument("--min-cols", type=int, default=_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", default=60))
parser.add_argument("--min-rows", type=int, default=_env_int("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS", default=20))
parser.add_argument("--max-cols", type=int, default=_env_int("MAM_MAX_COLS", "MAM_MAX_PANE_COLS"))
--max-cols 는 영향이 없습니다 — or 를 쓰지 않았고, 0 이 if max_columns and … 에서 falsy 가 되어 "상한 없음" 으로 읽히는 것은 의도에 부합합니다.
권고 가드: MAM_MIN_PANE_COLS=0 과 --min-cols 0 이 같은 결정을 내는지 단언하는 테스트 1건.
🟡 J-2 (P3, 계획 측 오류) — 성장 가드 테스트의 d3 케이스가 임계에 못 미친다
계획 §5 는 뮤테이션 M6(홀수 분기에도 상한 검사 추가 = 과잉 교정)이 test_headless_max_columns_growth_guard 의 d3 단언에서 FAIL 할 것으로 적었습니다. 실제로는 19/19 통과합니다(M-14).
원인은 테스트 데이터에 있습니다. d3 은 headless(3), max_columns=2 인데 n // 2 = 1 이라 1 >= 2 가 거짓이므로, 과잉 교정을 넣어도 그 분기에 도달하지 않습니다.
n (max_columns=2) |
n // 2 |
정상 코드 | 과잉 교정 시 |
|---|---|---|---|
| 3 | 1 | down |
down ← 판별 불가 |
| 5 | 2 | down |
overflow |
| 7 | 3 | down |
overflow |
즉 "기존 열을 채우는 것은 막지 않는다" 는 계약 — C-1 처방이 GUI 와 대칭임을 보장하는 바로 그 성질 — 이 현재 아무 단언에도 걸려 있지 않습니다.
이 오류의 출처는 구현이 아니라 계획입니다. Creator 는 계획이 지정한 테스트를 그대로 구현했고, 임계값을 넘지 않는 데이터를 고른 것은 제 쪽입니다.
처방 — 한 줄 추가.
# Filling an existing column is not blocked even at/above the cap
# (n=5 -> n//2=2 >= max_columns=2, so this case actually reaches the check)
d5 = compute_2xk_layout(headless(5), max_columns=2)
assert d5.direction == "down" and not d5.is_overflow
수용 기준: 홀수 분기에 상한 검사를 넣는 뮤테이션에서 FAIL 해야 합니다.
🟢 참고 (조치 불요)
test_bug4_headless_unobservable_fast_path 의 목에서 paste-buffer 분기의 return 0 이 삭제되었습니다. 바로 아래 return 0 으로 떨어지므로 동작은 같습니다. 계획에 없던 변경이지만 무해합니다.
4. 규약 준수 확인
| 항목 | 확인 |
|---|---|
역할 분리 (MULTI_AGENT_RULES.md §1) |
Planner 계획 → Creator 구현 → Reviewer 검증 절차 준수 ✅ |
| 반박 절차 (§3.1) | [REBUT:] 제기 없음 ✅ |
| 민감정보 미포함 (§2) | diff 에 자격증명·절대 시스템 경로 하드코딩 없음 ✅ |
| 회귀 가드 실효성 | 뮤테이션 7종 FAIL / 1종 미검출(J-2, 계획 측 오류) ⚠️ |
| 계획 DoD 1·3·4·5·6·7 | 333 passed / 컴파일 / 배포 31 / 죽은 표면 0 / GUI·헤드리스 대칭 / 의도한 5파일만 변경 ✅ |
| 계획 DoD 2 (뮤테이션 8종) | 7/8 — M6 은 계획 명세 자체가 판별 불가였음 ⚠️ |
5. 이월 항목
| # | 파일 | 조치 |
|---|---|---|
| J-1 | .agents/skills/lib_py/layout.py |
_env_int 에 default= 인자 도입, or 60 / or 20 제거. MAM_MIN_PANE_COLS=0 ↔ --min-cols 0 동치 가드 1건 |
| J-2 | tests/test_layout.py |
test_headless_max_columns_growth_guard 에 headless(5) 케이스 추가. 수용 기준: 홀수 분기 상한 검사 뮤테이션에서 FAIL |
| I-3 잔여 | layout.py |
(기존 이월) PaneInfo 관련 정리는 완료. 추가 잔여 없음 |
6. 결론
계획 5e4ef463 의 여섯 항목(I-2, I-3a/b/c, C-1, 문서)이 모두 구현되었고, 사양에서 벗어난 곳이 없습니다. 특히 C-1 교정과 --max-cols 배선이 같은 커밋에 들어가 결함 활성화를 피했고, GUI 와 헤드리스가 4페인·max_columns=2 에서 동일하게 max_columns_reached 를 내는 것을 실측했습니다. 뮤테이션 7종이 각각 지정 테스트를 FAIL 시키며, 그중 SKS_EMPTY_GIVEUP 상수만 바꾸는 뮤테이션까지 잡히는 것은 I-2 가드가 구조가 아니라 동작을 붙들고 있음을 보여줍니다. 전체 333/333 통과, 배포 신선도 31/31 유지, 시스템 python 3.9.6 호환 확인.
남은 두 항목은 모두 P3 입니다. J-1(MAM_MIN_PANE_COLS=0 이 60 으로 덮임)은 문서화되지 않은 경계값에서만 나타나는 좁은 회귀이고, J-2(성장 가드의 판별 불가 케이스)는 제 계획의 뮤테이션 명세 오류로서 구현 책임이 아닙니다. 어느 쪽도 현재 동작을 해치지 않고 결함을 가리지도 않으므로 차단하지 않으며, 각각 한 줄 수정으로 다음 커밋에 정리하면 충분합니다.
[VERDICT: PASS]