- Add Rev.2 authoritative plan detailing root causes and 2xK decision table - Add Phase 1 & 2 review reports from Claude (planner/reviewer) and Cline (reviewer)
9.7 KiB
🔍 Cross Review — Job 5e43d80f: Layout Engine 2×K 구현
- Reviewer:
planner-reviewer-claude-01 - 대상: W1~W5 (
layout.py,lib.sh:435,.mam.env.example, 테스트 3파일) - 검증: BSP 시뮬레이터 실행 + 헤드리스 궤적 실행 + pytest 전체 393건 실행
이해충돌 고지: 본 구현의 사양(
layout_engine_improvement_plan.mdRev.2)은 제가 Planner 로 작성했습니다. 아래는 타인이 작성한 코드가 그 사양을 충족하는가에 대한 검증이며, 사양 자체의 타당성에 대한 독립 검증이 아닙니다. §4 의 F-1 은 실제로 제 사양의 결함이며 그렇게 명시합니다.
1. 핵심 목표 달성 — 실측 확인
BSP 세분할 시뮬레이터로 N=1→5 궤적을 실행했습니다.
N=1 [p1 277x78]
N=2 split right on p1 (new_column_right)
p1 138x78 / p2 139x78 widths=[138,139] heights=[78] balanced=True
N=3 split down on p1 (fill_column)
p1 138x39 / p3 138x39 / p2 139x78 (전이 상태 — 정상)
N=4 split down on p2 (fill_column)
p1 138x39 / p3 138x39 / p2 139x39 / p4 139x39
widths=[138,139] heights=[39] balanced=True ← 🎯 깨끗한 2×2
N=5 OVERFLOW (grid_capacity_reached) → 새 워크스페이스
4 에이전트 2×2 목표 달성. Rev.1 이 진단한 R-1 왜곡(N=4 에서 widths {138,139,277}, heights {19,20,39})이 완전히 해소되었습니다.
1.1 GUI ↔ 헤드리스 패리티 (C-1 처방 검증)
headless n=1: right target=p1 reason=new_column_right
headless n=2: down target=p1 reason=fill_column
headless n=3: down target=p2 reason=fill_column
headless n=4: overflow target=p4 reason=grid_capacity_reached
GUI 와 방향·순서가 완전히 일치하며, 타깃도 p1 → p2 로 열을 번갈아 갑니다(§4.2 panes[n - max_columns] 명세대로). 홀짝(n % 2) 로직은 완전히 제거되었습니다. creator-grok-01 이 제기했던 "n=3 갈라짐"이 원천 차단되었습니다.
2. W1~W5 항목별 검증
| W | 항목 | 상태 |
|---|---|---|
| W1 | 단일 결정표 공유 | ✅ _decide / _decide_headless 분리, compute_2xk_layout 은 관측→위임 2단 |
| W2 | N=1 → right |
✅ _full_height_pane 경유 new_column_right |
| W3 | _full_height_pane |
✅ len(col)!=1 → None, area_h<=0 폴백, tol=2 |
| W4 | 캡 3중 배선 | ✅ 전부 — 아래 §2.1 |
| W5 | 테스트 | ✅ 사양의 8개 테스트 전건 구현 |
2.1 W4 — Rev.2 가 지적한 이중 누락이 모두 해소됨
# layout.py:235-236 ← _env_int 에 default 부여 (이전엔 None 반환)
--max-cols default=_env_int("MAM_MAX_COLS", "MAM_MAX_PANE_COLS", default=2)
--max-rows default=_env_int("MAM_MAX_ROWS", "MAM_MAX_PANE_ROWS", default=2)
# lib.sh:435 ← 이전엔 --max-cols 를 아예 넘기지 않았음
--max-cols "${MAM_MAX_PANE_COLS:-2}" --max-rows "${MAM_MAX_PANE_ROWS:-2}"
# .mam.env.example:147-153 ← C-5 문서 자체 불일치("unset -> no cap" vs "=3")도 정정
#default: 2 / MAM_MAX_PANE_COLS=2 + MAM_MAX_PANE_ROWS=2 신규 블록
추가로 compute_2xk_layout 진입부에 max_columns is None → 2 방어가 들어가 네 번째 경로까지 막았습니다. 사양보다 견고합니다.
test_max_cols_default_reaches_cli_path 와 test_lib_sh_passes_max_cols_and_rows 가 이 배선을 계약으로 고정합니다 — Rev.2 가 "이게 없으면 전체가 프로덕션에서 무효"라고 경고한 지점이라 특히 중요합니다.
2.2 기존 테스트 갱신 처리
| 테스트 | 처리 |
|---|---|
test_1_pane_split_down → test_1_pane_split_right |
✅ 개명 + 계약 갱신 |
test_2_panes_to_3_panes_new_column_right → test_2_panes_fill_left_column_down |
✅ |
test_4_panes_to_5_panes_new_column → ..._overflows_at_capacity |
✅ |
test_headless_max_columns_growth_guard |
✅ 옛 홀짝 단언(n=2→right, headless_odd_down) 전면 재작성 |
test_b19_headless_layout_does_not_overflow |
✅ tall 케이스 재해석 + wide-tall 케이스 신설로 커버리지 보존 |
test_b19 처리가 특히 좋습니다 — 단언만 뒤집지 않고 비오버플로 경로를 검증하는 새 픽스처를 추가해 커버리지를 유지했습니다.
2.3 테스트 실행
pytest tests/ -q → 393 passed in 511.60s
실패 0건. (직전 리뷰에서 관측된 test_d23 nats 태그 불일치도 해소되었습니다.)
3. 🟡 F-2 — MAX_*=0 이 "무제한"이 아니라 "전면 차단"입니다
_env_int 는 J-1 계약에 따라 명시적 0 을 보존합니다. 그 결과:
| 설정 | 실측 결과 |
|---|---|
max_columns=0, max_rows=2 |
down / fill_column |
max_columns=2, max_rows=0 |
right → 이후 overflow |
max_columns=0, max_rows=0 |
overflow / grid_capacity_reached (즉시) |
| 헤드리스, 둘 중 하나라도 0 | n >= 0 이 항상 참 → 영구 overflow |
문제는 같은 설정 파일 안의 의미 충돌입니다:
# .mam.env.example
# MAM_MIN_PANE_ROWS=0 ← "Set to 0 to disable vertical row constraints"
# MAM_MAX_PANE_ROWS=2 ← 0 을 넣으면 "용량 0" = 모든 세션이 새 워크스페이스
MIN_*=0 이 "제약 해제"를 뜻하므로, 운영자가 MAX_*=0 을 "상한 없음"으로 읽는 것은 자연스럽습니다. 그러나 실제로는 에이전트마다 워크스페이스가 무한 생성됩니다.
개선 방향:
if max_columns is None or max_columns <= 0:
max_columns = 2 # 또는 '무제한' 의도라면 sys.maxsize
if max_rows is None or max_rows <= 0:
max_rows = 2
.mam.env.example 에도 0 은 허용되지 않습니다(최솟값 1) 한 줄을 덧붙이십시오. 비차단이나 오설정 시 피해가 크고 되돌리기 어렵습니다(생성된 워크스페이스가 남음).
4. 🟠 F-1 — 세로 전용 적층 능력이 사라졌습니다 (제 사양의 결함)
현상 (실측)
min_cols=60 에서 단일 페인 폭별 결정:
| 폭 × 높이 | 결과 |
|---|---|
| 80×60 | overflow / column_width_overflow |
| 100×60 | overflow |
| 119×60 | overflow |
| 120×60 | right |
폭 120 미만이면 N=1 에서 즉시 오버플로합니다. 그러나 80×60 을 세로로 쌓으면 80×30 페인 2개가 되고, 두 페인 모두 폭 80 ≥ min_cols 60 을 만족합니다. 즉 사용 가능한 배치를 거부하고 새 워크스페이스를 만듭니다.
원인 — 폭 게이트가 폴스루하지 않음
if len(cols) < max_columns:
fh = _full_height_pane(cols[-1], area_h)
if fh is not None:
if fh.width > 0 and fh.width // 2 < min_cols:
return _overflow("column_width_overflow", fh) # ← 즉시 반환
return _right("new_column_right", fh)
# ② 세로 채우기에 도달하지 못함
새 엔진은 1열 × K행 배치를 구조적으로 만들 수 없습니다. 구 엔진의 single_pane_split_down 이 담당하던 경로가 사라졌습니다.
책임 소재
이것은 구현 결함이 아니라 제 사양의 결함입니다. Rev.2 §4.1 의사코드가 정확히 return OVERFLOW("column_width_overflow", fh) 로 적혀 있었고, 구현은 그대로 따랐습니다. 폭 부족 시 세로 폴백을 명시하지 않은 것은 제 누락입니다.
실무 영향
기본값 min_cols=15 에서는 폭 30 미만이어야 발동하므로 사실상 도달 불가합니다. 다만 MAM_MIN_PANE_COLS 기본값은 60 → 40 → 15 로 변해 왔고, .mam.env 는 gitignore 대상이라 자동 마이그레이션되지 않습니다. 구 설정(=60)을 지닌 기존 설치는 120칸 미만 터미널에서 에이전트마다 워크스페이스가 하나씩 생기게 됩니다.
개선 방향 (구체)
폭 게이트를 폴스루로 바꿉니다:
if len(cols) < max_columns:
fh = _full_height_pane(cols[-1], area_h)
if fh is not None and not (fh.width > 0 and fh.width // 2 < min_cols):
return _right("new_column_right", fh)
# 폭이 새 열을 감당하지 못하면 ②(세로 채우기)로 내려간다
이렇게 하면 80×60/min_cols=60 은 down / fill_column → 80×30 2개가 되고, 폭·행이 모두 소진된 뒤에야 ③에서 오버플로합니다. 회귀 가드:
def test_narrow_terminal_falls_back_to_vertical_stacking():
d = compute_2xk_layout(_one(80, 60), min_cols=60)
assert d.direction == "down" and not d.is_overflow
주의: 이 변경은 test_b19 의 tall 케이스 단언을 다시 뒤집습니다(현재 overflow → down). 원래 그 테스트가 지키던 계약이 바로 이 세로 폴백이었으므로, 사실상 원복입니다.
5. 판정
| 항목 | 판정 |
|---|---|
| W1~W5 사양 충족 | ✅ 전건. W4 는 사양보다 견고 |
| 4 에이전트 2×2 | ✅ 시뮬레이터 실측 |
| GUI/헤드리스 패리티 | ✅ 방향·타깃 모두 일치 |
| 전체 테스트 | ✅ 393 passed, 0 failed |
| F-1 세로 폴백 상실 | 🟠 제 사양 누락 — 후속 수정 |
F-2 MAX_*=0 함정 |
🟡 후속 수정 |
구현은 승인된 사양을 정확히, 그리고 일부는 더 견고하게 충족했으며 핵심 목표가 실측으로 증명되었습니다. F-1·F-2 는 이번 변경이 만든 새 결함이 아니라 사양의 미비로, 각각 3~5줄 수정으로 해소됩니다. 설계 재작업 사유가 아니므로 [ESCALATE: PLANNER] 는 부여하지 않습니다.
후속 잡 권고: F-1(세로 폴백) + F-2(0 값 클램프) 를 묶어 한 커밋으로. F-1 은 구 .mam.env 를 지닌 기존 설치에 실제 영향이 있으므로 우선순위가 높습니다.
[VERDICT: PASS]