# πŸ” Cross Code Review β€” Job ddc8d9f1 - **Reviewer**: `planner-reviewer-claude-01` (role: `planner,reviewer`) - **Target**: 2xK grid layout engine β€” `MAM_MIN_PANE_COLS` κΈ°λ³Έκ°’ 60 β†’ 40 및 단일 μ›Œν¬μŠ€νŽ˜μ΄μŠ€ 닀쀑 페인 타일링 - **Reviewed files**: `.agents/skills/lib.sh`, `.agents/skills/lib_py/layout.py`, `.mam.env.example`, `tests/test_layout.py`, `tests/test_tier1_unit.py`, `tests/test_a4_adapter_contract.py` --- ## ⚠️ 검증 λ²”μœ„ κ³ μ§€ (Verification Scope Disclaimer) **λ³Έ μ„Έμ…˜μ—μ„œ `pytest` μŠ€μœ„νŠΈλ₯Ό μ‹€ν–‰ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.** μ‹€ν–‰ μ‹œλ„κ°€ μ€‘λ‹¨λ˜μ–΄(μ‚¬μš©μž κ±°λΆ€) λΈŒλ¦¬ν”„ 4번 μš”κ΅¬μ‚¬ν•­ *"Ensure full pytest test suite passes"* λŠ” **μ‹€μΈ‘μœΌλ‘œ ν™•μΈλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.** λ”°λΌμ„œ μ•„λž˜ νŒμ •μ€ λ‹€μŒ λ²”μœ„λ‘œ ν•œμ •λ©λ‹ˆλ‹€: - βœ… μ†ŒμŠ€ 정적 뢄석 (`layout.py` 전체 둜직 νŒλ…) - βœ… μ‹ κ·œ ν…ŒμŠ€νŠΈμ˜ **λͺ¨λ“  단언을 μ—”μ§„ 뢄기에 λŒ€μž…ν•œ μˆ˜λ™ 트레이슀** - βœ… `grep` 기반 μƒμˆ˜ λ“œλ¦¬ν”„νŠΈ μ „μˆ˜ 쑰사 - ❌ **ν…ŒμŠ€νŠΈ μ‹€ν–‰ κ²°κ³Ό (λ―Έμˆ˜ν–‰)** μˆ˜μΉ˜Β·λΆ„κΈ° 좔적은 결정둠적 μ •μˆ˜ 연산이라 μˆ˜λ™ κ²€μ¦μ˜ 신뒰도가 λ†’μ§€λ§Œ, μ‹€ν–‰ 확인은 λ³„λ„λ‘œ 이루어져야 ν•©λ‹ˆλ‹€. Β§5에 μž”μ—¬ ν•­λͺ©μ„ λͺ…μ‹œν–ˆμŠ΅λ‹ˆλ‹€. --- ## 1. λ³€κ²½ μš”μ•½ | μœ„μΉ˜ | λ³€κ²½ | νŒμ • | |---|---|---| | `lib_py/layout.py:73` | `compute_2xk_layout(min_cols=60)` β†’ `40` | βœ… | | `lib_py/layout.py:201` | `--min-cols` κΈ°λ³Έκ°’ `_env_int(..., default=60)` β†’ `40` | βœ… | | `lib_py/layout.py:179` | λ…μŠ€νŠΈλ§ `60 default` β†’ `40 default` | βœ… | | `lib.sh:432` | `${MAM_MIN_PANE_COLS:-60}` β†’ `:-40` | βœ… | | `.mam.env.example:132-133` | 주석 `#default: 60` 및 μ˜ˆμ‹œ `=60` β†’ `40` | βœ… | | `tests/test_layout.py:290` | lib.sh μ†ŒμŠ€ μŠ€λ‹ˆνŽ« κ°€λ“œ λ¬Έμžμ—΄ 동기화 | βœ… | **3쀑 κΈ°λ³Έκ°’ 동기화 확인**: 이 μ½”λ“œλ² μ΄μŠ€λŠ” λ™μΌν•œ 기본값을 **μ„Έ κ³³**(shell νŒŒλΌλ―Έν„° ν™•μž₯, Python μ‹œκ·Έλ‹ˆμ²˜, Python argparse)에 쀑볡 λ³΄μœ ν•©λ‹ˆλ‹€. μ„Έ κ³³ λͺ¨λ‘ 40으둜 μΌμΉ˜ν•˜λ©° `.mam.env.example` λ¬Έμ„œκ°’κΉŒμ§€ 4쀑 μΌμΉ˜ν•©λ‹ˆλ‹€. λ“œλ¦¬ν”„νŠΈ μ—†μŒ. > **μ°Έκ³ **: `lib.sh:432` λŠ” 항상 `--min-cols` λ₯Ό **λͺ…μ‹œ 전달**ν•˜λ―€λ‘œ μ‹€μš΄μ˜ κ²½λ‘œμ—μ„œ `layout.py:201` 의 argparse 기본값은 λ„λ‹¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 201번 쀄은 CLI 직접 ν˜ΈμΆœΒ·ν…ŒμŠ€νŠΈ 경둜용 fallback μž…λ‹ˆλ‹€. 두 값이 μ–΄κΈ‹λ‚˜λ„ μ¦‰μ‹œ λ“œλŸ¬λ‚˜μ§€ μ•ŠλŠ” κ΅¬μ‘°μ΄λ―€λ‘œ Β§4에 κ°€λ“œ μ œμ•ˆμ„ λ‚¨κΉλ‹ˆλ‹€. --- ## 2. 둜직 μ •ν•©μ„± β€” μ‹ κ·œ ν…ŒμŠ€νŠΈ μˆ˜λ™ 트레이슀 `compute_2xk_layout` 의 λΆ„κΈ°λ₯Ό μ‹ κ·œ 단언에 κ·ΈλŒ€λ‘œ λŒ€μž…ν•΄ 전건 κ²€μ¦ν–ˆμŠ΅λ‹ˆλ‹€. 폭 νŒμ •μ€ `layout.py:169` 의 `width // 2 < min_cols` 단일 κ²Œμ΄νŠΈμž…λ‹ˆλ‹€. ### 2.1 경계값 (80 / 79 cols) | μž…λ ₯ | 계산 | 도달 λΆ„κΈ° | κΈ°λŒ€ | μ‹€μ œ | |---|---|---|---|---| | 2페인 Γ— w=80 (x=0 동일열) | `80 // 2 = 40`, `40 < 40` = False | `:172 new_column_right` | `right`, not overflow | βœ… 일치 | | 2페인 Γ— w=79 | `79 // 2 = 39`, `39 < 40` = True | `:170 column_width_overflow` | `overflow` | βœ… 일치 | **80이 μ •ν™•ν•œ ν•˜ν•œ**μž„μ΄ ν™•μΈλ©λ‹ˆλ‹€(`>= 80` μ—μ„œ λΆ„ν•  κ°€λŠ₯). λΈŒλ¦¬ν”„ 2번 μš”κ΅¬μ‚¬ν•­ *"width >= 80 μ—μ„œ μ‘°κΈ° overflow κΈˆμ§€"* λŠ” μƒμˆ˜ λ³€κ²½λ§ŒμœΌλ‘œ μ‚°μˆ μ μœΌλ‘œ 좩쑱되며, 별도 λΆ„κΈ° μΆ”κ°€κ°€ λΆˆν•„μš”ν•©λ‹ˆλ‹€ β€” **μ—”μ§„ 둜직 무변경은 μ˜¬λ°”λ₯Έ νŒλ‹¨**μž…λ‹ˆλ‹€. λΆˆν•„μš”ν•œ 특수 μΌ€μ΄μŠ€λ₯Ό λ„£μ§€ μ•Šμ€ 점을 긍정 ν‰κ°€ν•©λ‹ˆλ‹€. ### 2.2 90 / 100 col 단일 μ›Œν¬μŠ€νŽ˜μ΄μŠ€ 타일링 (1β†’2β†’3β†’4β†’overflow) `total_w ∈ {90, 100}`, `half_w = total_w // 2 ∈ {45, 50}` κΈ°μ€€ μ „ 단계 좔적: | 단계 | μž…λ ₯ ν˜•μƒ | νŒμ • 경둜 | κ²°κ³Ό | |---|---|---|---| | 1β†’2 | 1페인 `wΓ—40` | `:94` `40//2 = 20 >= min_rows 20` β†’ False(μ œμ•½ μ•„λ‹˜) β†’ `:102` | `down` / `single_pane_split_down`, target `p1` βœ… | | 2β†’3 | 2페인 x=0 단일열 | singleton μ—†μŒ β†’ `:169` `45//2=22`? **μ•„λ‹ˆμ˜€** β€” 이 μ‹œμ  페인 폭은 아직 `total_w`(90/100) β†’ `90//2=45 >= 40` | `right` / `new_column_right`, target `p1`(`columns[-1][0]`) βœ… | | 3β†’4 | `[p1,p2]` @x=0, `[p3]` @x=half_w | `:148-154` singleton μ—΄ `[p3]` 탐지 β†’ 높이 `40//2=20 >= 20` | `down` / `fill_singleton_column`, target `p3` βœ… | | 4β†’5 | 2μ—΄ Γ— 2페인 μ™„μ„± | singleton μ—†μŒ, `max_columns=None` β†’ `:169` `45//2=22 < 40` (100col: `50//2=25 < 40`) | `overflow` / `column_width_overflow` βœ… | **핡심 확인 사항 2건**: 1. **1β†’2 λ‹¨κ³„μ˜ 높이 경계**: `height=40` μ—μ„œ `40 // 2 = 20`, `min_rows=20` κ³Ό **κ°™μŒ**. `:94` 쑰건은 `< min_rows` μ΄λ―€λ‘œ False β†’ μ •μƒμ μœΌλ‘œ `down` μ§„μž…. `<=` μ˜€λ‹€λ©΄ μ˜€λΆ„κΈ°ν–ˆμ„ μ§€μ μœΌλ‘œ, ν…ŒμŠ€νŠΈκ°€ 이 경계λ₯Ό μ •ν™•νžˆ 짚고 μžˆμŠ΅λ‹ˆλ‹€. 2. **4νŽ˜μΈμ—μ„œμ˜ μ˜λ„μ  overflow**: `min_cols=40` μ—μ„œ 90~100col μ›Œν¬μŠ€νŽ˜μ΄μŠ€λŠ” **μ΅œλŒ€ 4μ—μ΄μ „νŠΈ**κ°€ μƒν•œμ΄λ©° 5λ²ˆμ§ΈλŠ” μƒˆ μ›Œν¬μŠ€νŽ˜μ΄μŠ€λ‘œ λ„˜μ–΄κ°‘λ‹ˆλ‹€. λΈŒλ¦¬ν”„ λͺ©ν‘œ(*"3-4 agents in ~100-col terminal"*)와 μ •ν™•νžˆ λΆ€ν•©ν•˜κ³ , ν…ŒμŠ€νŠΈκ°€ 이 μƒν•œμ„ λͺ…μ‹œμ μœΌλ‘œ κ³ μ •ν•˜κ³  μžˆμ–΄ ν–₯ν›„ νšŒκ·€ μ‹œ μ¦‰μ‹œ κ²€μΆœλ©λ‹ˆλ‹€. βœ… ### 2.3 컬럼 κ·Έλ£Ήν•‘ μ •ν•©μ„± `:129-139` 의 xμ’Œν‘œ 퍼지 κ·Έλ£Ήν•‘(μž„κ³„ 2col)에 μ‹ κ·œ ν”½μŠ€μ²˜ λŒ€μž… μ‹œ: - 90col: x ∈ {0, 45} β†’ `|0-45| = 45 > 2` β†’ 2개 μ—΄λ‘œ μ •ν™•νžˆ 뢄리 βœ… - 100col: x ∈ {0, 50} β†’ 동일 βœ… 퍼지 μž„κ³„κ°’ 2와 μΆ©λŒν•˜λŠ” μ’Œν‘œκ°€ μ—†μ–΄ κ·Έλ£Ήν•‘ μ˜€λΆ„λ₯˜ μœ„ν—˜μ΄ μ—†μŠ΅λ‹ˆλ‹€. --- ## 3. νšŒκ·€ 영ν–₯ 뢄석 (μœ μ‹€ 관점) κΈ°λ³Έκ°’ 변경은 **기본값에 μ˜μ‘΄ν•˜λŠ” κΈ°μ‘΄ ν…ŒμŠ€νŠΈ**μ—λ§Œ νŒŒκΈ‰λ©λ‹ˆλ‹€. μ „μˆ˜ 쑰사 κ²°κ³Ό: | κΈ°μ‘΄ ν…ŒμŠ€νŠΈ | κΈ°λ³Έκ°’ 의쑴 μ—¬λΆ€ | 영ν–₯ | |---|---|---| | `test_layout.py:28~262` (8건) | `min_cols=60` **λͺ…μ‹œ 전달** | 영ν–₯ μ—†μŒ βœ… | | `test_layout.py:137,191` | `min_cols=30` λͺ…μ‹œ | 영ν–₯ μ—†μŒ βœ… | | `test_layout.py:207` (subprocess) | `--min-cols 60` λͺ…μ‹œ | 영ν–₯ μ—†μŒ βœ… | | `test_layout.py:358,374` | `--min-cols 30` λͺ…μ‹œ | 영ν–₯ μ—†μŒ βœ… | | `test_j1_env_zero_min_cols_matches_flag_zero` | `_ZERO_TRAP` κΈ°λ³Έκ°’ μ‹€ν–‰ 포함 | **영ν–₯ κ²€ν†  ν•„μš” β†’ μ•„λž˜** | | `test_j1b_invalid_alias_does_not_shadow...` | κΈ°λ³Έκ°’ μ‹€ν–‰ 비ꡐ | 동일 βœ… | **J-1 계열 μ •λ°€ κ²€ν† ** (`test_layout.py:432` 주석 κΈ°μ€€ `_ZERO_TRAP` = 단일 페인 `50Γ—30`): - `height // 2 = 15 < min_rows 20` β†’ `:94` μ œμ•½ λΆ„κΈ° μ§„μž… - `width // 2 = 25` λ₯Ό `min_cols` 와 비ꡐ: κΈ°μ‘΄ `25 < 60` β†’ overflow / μ‹ κ·œ `25 < 40` β†’ **overflow (동일)** - 즉 기본값이 60이든 40이든 `_ZERO_TRAP` 의 κ²°κ³ΌλŠ” `single_pane_overflow` 둜 λΆˆλ³€. **J-1/C-2 λΆˆλ³€μ‹ 보쑴 확인** βœ… λ˜ν•œ J-1bλŠ” "κΈ°λ³Έκ°’ μ‹€ν–‰ == κΈ°λ³Έκ°’ μ‹€ν–‰" ν˜•νƒœμ˜ 자기참쑰 비ꡐ라 κΈ°λ³Έκ°’ μžμ²΄μ™€ λ¬΄κ΄€ν•˜κ²Œ μ„±λ¦½ν•©λ‹ˆλ‹€. `test_layout.py:290` 의 lib.sh μ†ŒμŠ€ μŠ€λ‹ˆνŽ« κ°€λ“œλŠ” **λ¬Έμžμ—΄ μ™„μ „ 일치** κ²€μ‚¬μ΄λ―€λ‘œ `lib.sh:432` 와 ν•¨κ»˜ κ°±μ‹ λ˜μ§€ μ•Šμ•˜λ‹€λ©΄ μ¦‰μ‹œ μ‹€νŒ¨ν–ˆμ„ ν•­λͺ©μž…λ‹ˆλ‹€. μ–‘μͺ½ λͺ¨λ‘ `:-40` 으둜 λ™κΈ°ν™”λ˜μ–΄ μžˆμŒμ„ λŒ€μ‘° ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€ βœ… --- ## 4. 지적 사항 (λͺ¨λ‘ 비차단 / Non-blocking) 차단 결함(P0/P1)은 λ°œκ²¬λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μ•„λž˜λŠ” κ°œμ„  κΆŒκ³ μž…λ‹ˆλ‹€. ### 🟑 N-1 (P3) β€” `test_herdr_shim_contract.py:100` 의 `MAM_MIN_PANE_COLS=60` λ―Έκ²€ν†  `tests/test_herdr_shim_contract.py:92,100` 의 H-13 μΌ€μ΄μŠ€κ°€ `export MAM_MIN_PANE_COLS=60` 을 μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŠ” **ν™˜κ²½λ³€μˆ˜ μ˜€λ²„λΌμ΄λ“œ λ™μž‘ 자체**λ₯Ό κ²€μ¦ν•˜λŠ” μΌ€μ΄μŠ€μ΄λ―€λ‘œ κΈ°λ³Έκ°’ λ³€κ²½κ³Ό λ…Όλ¦¬μ μœΌλ‘œ 독립이며(λͺ…μ‹œ μ˜€λ²„λΌμ΄λ“œ 경둜), 정상 톡과가 μ˜ˆμƒλ©λ‹ˆλ‹€. λ‹€λ§Œ 파일 본문을 μ—΄λžŒν•˜μ§€ λͺ»ν•΄ **단언 λ‚΄μš©κΉŒμ§€λŠ” ν™•μΈν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.** **κ°œμ„  λ°©ν–₯**: 이 ν…ŒμŠ€νŠΈκ°€ "60이 μ•„λ‹Œ 값이 적용됨"을 κ²€μ¦ν•˜λŠ” μ˜λ„λΌλ©΄, 이제 κΈ°λ³Έκ°’ 40κ³Ό μ˜€λ²„λΌμ΄λ“œ κ°’ 60이 λͺ…ν™•νžˆ κ΅¬λΆ„λ˜μ–΄ 였히렀 λŒ€μ‘°κ°€ μ„ λͺ…ν•΄μ§‘λ‹ˆλ‹€. ν™•μΈλ§Œ κΆŒκ³ ν•©λ‹ˆλ‹€. ### 🟑 N-2 (P3) β€” `IMPROVEMENTS.md:49` 의 `min_cols=60` μž”μ‘΄ ``` IMPROVEMENTS.md:49: ... 해상도 μ˜€λ²„ν”Œλ‘œ κ°€λ“œ(`min_cols=60`, `min_rows=20`) ... ``` ν•΄λ‹Ή 쀄은 **μ—”μ§„ 졜초 λ„μž… μ‹œμ μ„ κΈ°λ‘ν•œ λ³€κ²½ 이λ ₯**μ΄λ―€λ‘œ λ‹Ήμ‹œ κ°’ 60을 λ‚¨κΈ°λŠ” 것이 이λ ₯ λ¬Έμ„œλ‘œμ„œλŠ” μ •ν™•ν•©λ‹ˆλ‹€. λ‹€λ§Œ ν˜„μž¬ 이 μ €μž₯μ†Œμ—μ„œ **60을 기본값이라 μ„œμˆ ν•˜λŠ” μœ μΌν•œ λ¬Έμ„œ**κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. **κ°œμ„  λ°©ν–₯ (택1)**: (a) κ·ΈλŒ€λ‘œ λ‘λ˜ 이번 변경을 `IMPROVEMENTS.md` μ‹ κ·œ ν•­λͺ©μœΌλ‘œ μΆ”κ°€ν•˜μ—¬ 60β†’40 μ „ν™˜ 이λ ₯을 μž‡λŠ”λ‹€ β€” **ꢌμž₯**. (b) ν•΄λ‹Ή 쀄에 `(ν˜„ν–‰ 40, 작 ddc8d9f1μ—μ„œ λ³€κ²½)` 각주λ₯Ό 뢙인닀. 이λ ₯ λ¬Έμ„œλ₯Ό μ†ŒκΈ‰ μˆ˜μ •ν•˜λŠ” 방식은 ꢌμž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ### 🟑 N-3 (P3) β€” κΈ°λ³Έκ°’ 4쀑 쀑볡에 λŒ€ν•œ νŒŒλ¦¬ν‹° κ°€λ“œ λΆ€μž¬ 동일 μƒμˆ˜κ°€ `lib.sh:432` / `layout.py:73` / `layout.py:201` / `.mam.env.example:133` 4곳에 λ¬Έμžμ—΄λ‘œ 쀑볡 μ‘΄μž¬ν•©λ‹ˆλ‹€. `test_layout.py:290` 이 lib.shβ†”ν…ŒμŠ€νŠΈ μŠ€λ‹ˆνŽ« 쌍만 κ³ μ •ν•  뿐, **`layout.py:73` μ‹œκ·Έλ‹ˆμ²˜ κΈ°λ³Έκ°’κ³Ό `layout.py:201` argparse κΈ°λ³Έκ°’μ˜ μΌμΉ˜λŠ” μ–΄λ–€ ν…ŒμŠ€νŠΈλ„ κ°•μ œν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.** 두 값이 μ–΄κΈ‹λ‚˜λ©΄ CLI κ²½λ‘œμ™€ 라이브러리 μž„ν¬νŠΈ κ²½λ‘œκ°€ 쑰용히 κ°ˆλΌμ§‘λ‹ˆλ‹€. **κ°œμ„  λ°©ν–₯ (κ΅¬μ²΄μ•ˆ)**: ```python # tests/test_layout.py import inspect from lib_py.layout import compute_2xk_layout def test_default_min_cols_parity_across_entrypoints(): """μ‹œκ·Έλ‹ˆμ²˜ κΈ°λ³Έκ°’ == argparse κΈ°λ³Έκ°’ == lib.sh fallback.""" sig_default = inspect.signature(compute_2xk_layout).parameters["min_cols"].default assert sig_default == 40 # argparse 경둜: env λ―Έμ„€μ • μ‹œ 동일 결정을 λ‚΄μ•Ό 함 assert _run_layout(_ZERO_TRAP) == _run_layout(_ZERO_TRAP, ("--min-cols", str(sig_default))) # lib.sh fallback λ¬Έμžμ—΄ lib_sh = (REPO_ROOT / ".agents/skills/lib.sh").read_text() assert f'${{MAM_MIN_PANE_COLS:-{sig_default}}}' in lib_sh ``` μ΄λŠ” 이전 μž‘μ—μ„œ `ready_tokens` κ°€ `lib.sh`/`claude.py` μ–‘μͺ½μ— μ€‘λ³΅λœ 것과 **동일 μœ ν˜•μ˜ ꡬ쑰적 취약점**이며, 같은 처방이 μ μš©λ©λ‹ˆλ‹€. 별도 작으둜 뢄리해도 λ¬΄λ°©ν•©λ‹ˆλ‹€. ### 🟒 N-4 (P4) β€” μ›Œν‚ΉνŠΈλ¦¬ μœ„μƒ `git status` 에 `m nats-docker` (μ„œλΈŒλͺ¨λ“ˆ dirty, `5db38da...-dirty`) κ°€ ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. λ³Έ λ³€κ²½κ³Ό λ¬΄κ΄€ν•œ μ˜€μ—Όμ΄λ©° 컀밋 μ „ 정리λ₯Ό κΆŒκ³ ν•©λ‹ˆλ‹€. λ˜ν•œ `tests/test_layout.py` 말미에 빈 쀄 3개(`+++`)κ°€ μΆ”κ°€λ˜μ–΄ μžˆμ–΄ PEP8 κ΄€μ μ˜ μ‚¬μ†Œν•œ 정리 μ—¬μ§€κ°€ μžˆμŠ΅λ‹ˆλ‹€. κΈ°λŠ₯ 영ν–₯ μ—†μŒ. ### ℹ️ N-5 (정보) β€” λˆ„μ  diff λ‚΄ `test_a4_adapter_contract.py` λ³€κ²½ `ready_tokens` 에 `Claude Code|Opus|Sonnet|Haiku` λ₯Ό μΆ”κ°€ν•œ 직전 작의 변경뢄이 λˆ„μ  diff에 ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 계약 ν…ŒμŠ€νŠΈμ˜ κΈ°λŒ€κ°’μ΄ `lib.sh` / `claude.py` μ–‘μͺ½ κ΅¬ν˜„κ³Ό 3자 μΌμΉ˜ν•¨μ„ λŒ€μ‘° ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€ βœ… (λ³Έ 작 λ²”μœ„ μ™Έ) --- ## 5. μž”μ—¬ 검증 ν•­λͺ© (Outstanding) | # | ν•­λͺ© | μƒνƒœ | |---|---|---| | V-1 | `pytest tests/ -q` 전체 톡과 | ❌ **λ―Έμˆ˜ν–‰** β€” λ³Έ μ„Έμ…˜μ—μ„œ μ‹€ν–‰ 쀑단됨 | | V-2 | `test_herdr_shim_contract.py` H-13 단언 λ‚΄μš© | ⚠️ λ―Έμ—΄λžŒ (영ν–₯ μ—†μŒμœΌλ‘œ μΆ”μ •, N-1) | **V-1은 λ¨Έμ§€ μ „ λ°˜λ“œμ‹œ μ‹€μΈ‘λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.** 정적 뢄석상 μ‹€νŒ¨λ₯Ό μœ λ°œν•  μš”μΈμ€ λ°œκ²¬ν•˜μ§€ λͺ»ν–ˆμœΌλ‚˜(Β§3 νšŒκ·€ 영ν–₯ 전무), μ΄λŠ” μ˜ˆμΈ‘μ΄μ§€ 관츑이 μ•„λ‹™λ‹ˆλ‹€. --- ## 6. 총평 변경은 **μƒμˆ˜ 1개의 κ°’ μ‘°μ •κ³Ό 그에 λŒ€ν•œ 4쀑 동기화**λΌλŠ” μ΅œμ†Œ ν‘œλ©΄μ μ„ μ •ν™•νžˆ μ§€μΌ°μŠ΅λ‹ˆλ‹€. μ—”μ§„ λΆ„κΈ° λ‘œμ§μ„ κ±΄λ“œλ¦¬μ§€ μ•Šκ³  λΈŒλ¦¬ν”„μ˜ 4개 μš”κ΅¬μ‚¬ν•­μ„ μΆ©μ‘±ν•œ 점, 특히 μš”κ΅¬μ‚¬ν•­ 2λ₯Ό μœ„ν•΄ λΆˆν•„μš”ν•œ 특수 λΆ„κΈ°λ₯Ό μΆ”κ°€ν•˜μ§€ μ•Šκ³  μ‚°μˆ λ‘œ ν•΄μ†Œν•œ 점이 μ„€κ³„μ μœΌλ‘œ κ±΄μ „ν•©λ‹ˆλ‹€. μ‹ κ·œ ν…ŒμŠ€νŠΈλŠ” λ‹¨μˆœ happy-path에 λ¨Έλ¬Όμ§€ μ•Šκ³  **80/79 경계**, **height 40//2 == min_rows 20 동등 경계**, **4페인 μƒν•œ ν›„ overflow** λΌλŠ” μ„Έ 개의 μ‹€μ§ˆμ  경계λ₯Ό κ³ μ •ν•©λ‹ˆλ‹€. κΈ°μ‘΄ J-1/C-2 λΆˆλ³€μ‹λ„ λ³΄μ‘΄λ©λ‹ˆλ‹€. 지적 사항 4건은 λͺ¨λ‘ P3 μ΄ν•˜μ΄λ©° λ¬Έμ„œ 이λ ₯Β·ν…ŒμŠ€νŠΈ μœ„μƒΒ·μ›Œν‚ΉνŠΈλ¦¬ 정리 λ²”μ£Όλ‘œ, μ–΄λŠ 것도 ν˜„μž¬ λ™μž‘μ„ ν•΄μΉ˜κ±°λ‚˜ 결함을 μ€νν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 섀계 λ³€κ²½μ΄λ‚˜ μž¬κ³„νšμ΄ ν•„μš”ν•œ μ‚¬μ•ˆμ€ μ—†μŠ΅λ‹ˆλ‹€. **단, λ³Έ PASSλŠ” Β§5 V-1(전체 ν…ŒμŠ€νŠΈ μ‹€ν–‰) 이 λ³„λ„λ‘œ ν™•μΈλœλ‹€λŠ” μ „μ œ μœ„μ— μ„±λ¦½ν•©λ‹ˆλ‹€.** 정적 κ²€ν†  λ²”μœ„μ—μ„œλŠ” 차단 μ‚¬μœ κ°€ μ—†μŠ΅λ‹ˆλ‹€. [VERDICT: PASS]