diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-79ff98ed.md b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-79ff98ed.md new file mode 100644 index 0000000..6382a32 --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-79ff98ed.md @@ -0,0 +1,822 @@ +# ๐Ÿ“ ๊ตฌํ˜„ ๊ณ„ํš์„œ **Rev.2** โ€” Job `d6f54b7f` (์›์•ˆ: `79ff98ed`) + +- **์—ญํ• **: Planner (`MULTI_AGENT_RULES.md` ยง1 โ€” ์ €์žฅ์†Œ ์ฝ”๋“œ/๋ฌธ์„œ ๋ฌด์ˆ˜์ •, ์‚ฐ์ถœ๋ฌผ์€ ๋ณธ ๋ณด๊ณ ์„œ) +- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `14e306b` (branch `refactor`, working tree clean) +- **๋ฒ ์ด์Šค๋ผ์ธ**: `pytest tests/ --collect-only` โ†’ **333 collected** +- **์ž…๋ ฅ**: Job `9f85218e` ๋ฆฌ๋ทฐ `[VERDICT: PASS WITH CHALLENGE]` (Challenge C-1, Observation C-2) + +--- + +## 0. Rev.1 โ†’ Rev.2 ๋ณ€๊ฒฝ ์š”์•ฝ + +| ํ•ญ๋ชฉ | ํŒ์ • | ์กฐ์น˜ | +|---|---|---| +| **Challenge C-1** โ€” T5 ๋ฌธ์„œ ๊ฐ€๋“œ์˜ ๋ธ”๋ก ์นด์šดํŒ… ์˜ค๋ฅ˜ + ๋ถ€๋ถ„ ๋ฌธ์ž์—ด ํ—ˆ์  | **์ „๋ฉด ์ˆ˜์šฉ. ๋‘ ๊ฐˆ๋ž˜ ๋ชจ๋‘ ์‹ค์ธก ํ™•์ธ** | ยง5 T5 ์žฌ์„ค๊ณ„ (ยง1.10์— ์‹ค์ธก ๊ทผ๊ฑฐ) | +| **Observation C-2** โ€” `_env_int` ์˜ `ValueError` ์‹œ `continue` | **์ˆ˜์šฉ. ์ฑŒ๋ฆฐ์ €๊ฐ€ ์ œ์‹œํ•œ ๊ฒƒ๋ณด๋‹ค ๊ทผ๊ฑฐ๊ฐ€ ๋” ๊ฐ•ํ•จ** | ยง4.5 S5 ๋ณ€๊ฒฝ + ์ „์šฉ ํ…Œ์ŠคํŠธ T1b ์‹ ์„ค (ยง1.11) | +| (์ž์ฒด ์ •์ •) Rev.1 ยง5 ์˜ "์‹ ์„ค 8๊ฑด" | **์˜ค์‚ฐ โ€” ์‹ค์ œ 7๊ฑด** | Rev.2 ๋Š” 8๊ฑด(C-2 ํ…Œ์ŠคํŠธ 1๊ฑด ์ถ”๊ฐ€). ๊ธฐ๋Œ€ collected 341 ์€ ๋™์ผํ•˜๋‚˜ ๊ทผ๊ฑฐ๊ฐ€ ๋‹ฌ๋ผ์ง | +| (์‹ ๊ทœ) K-5 | โ€” | ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ `MAM_MIN_COLS` ๊ฐ€ ๋ฌธ์„œํ™”๋œ `MAM_MIN_PANE_COLS` ๋ณด๋‹ค **์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๋‹ค** (ยง9) | + +C-1 ์€ ๊ณ„ํš๋Œ€๋กœ ๊ตฌํ˜„ํ•˜๋ฉด **ํ…Œ์ŠคํŠธ๊ฐ€ 100% ์‹คํŒจ**ํ•˜๋Š” ๊ฒฐํ•จ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ ์ง€์ ์ด ์ •ํ™•ํ–ˆ๊ณ , ์‹ค์ธก์œผ๋กœ ์žฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค(ยง1.10). ๋‹ค๋งŒ ์ฑŒ๋ฆฐ์ €๊ฐ€ ์ œ์‹œํ•œ ์ˆ˜์ •์•ˆ์€ **๋‹ค๋ฅธ ์‹คํŒจ ๋ชจ๋“œ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค** โ€” ๋ฌธ์„œ ์ „์ฒด๋ฅผ ์Šค์บ”ํ•˜๋ฏ€๋กœ ์‚ฐ๋ฌธ ์† ํŒŒ์ผ๋ช… ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ **์ปค๋งจ๋“œ ๋‹จ์œ„ ๊ฒ€์ฆ(์ฑŒ๋ฆฐ์ €์˜ ํ•ต์‹ฌ ๊ต์ •)** ์— **ํŽœ์Šค ์Šค์ฝ”ํ”„(์ถ”๊ฐ€ ๋ณด๊ฐ•)** ๋ฅผ ํ•ฉ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ยง1.10.3 ์— ๋‘ ์‹คํŒจ ๋ชจ๋“œ๋ฅผ ๊ฐ๊ฐ ์‹ค์ธกํ–ˆ์Šต๋‹ˆ๋‹ค. + +C-2 ๋Š” ์ฑŒ๋ฆฐ์ €๊ฐ€ "๋‹ค์ค‘ fallback ์ทจ์ง€์— ๋ถ€ํ•ฉ" ์ •๋„๋กœ ์™„๊ณกํ•˜๊ฒŒ ์ œ๊ธฐํ–ˆ์ง€๋งŒ, ์‹ค์ธกํ•ด ๋ณด๋‹ˆ **`.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ ์œ ์ผํ•œ ์ด๋ฆ„์ด ์กฐ์šฉํžˆ ๋ฌด์‹œ๋˜๋Š”** ๊ฒฝ๋กœ์˜€์Šต๋‹ˆ๋‹ค. ๊ทผ๊ฑฐ๋ฅผ ๊ฐ•ํ™”ํ•ด ์ˆ˜์šฉํ•ฉ๋‹ˆ๋‹ค(ยง1.11). + +--- + +## 1. ์‹ค์ธก (Measurements) + +> ยง1.1 ~ ยง1.9 ๋Š” Rev.1 ์—์„œ ํ™•์ •๋œ ์‹ค์ธก์ด๋ฉฐ ์žฌ๊ฒ€์ฆ ์—†์ด ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ยง1.10 ยท ยง1.11 ์ด Rev.2 ์‹ ๊ทœ์ž…๋‹ˆ๋‹ค. + +### 1.1 ์—์ด์ „ํŠธ ํ•ด์„๊ธฐ๊ฐ€ ์ €์žฅ์†Œ์— **4๊ฐœ** ์กด์žฌํ•œ๋‹ค + +| # | ์œ„์น˜ | ์šฐ์„ ์ˆœ์œ„ | ์‹คํŒจ ์‹œ | +|---|---|---|---| +| **1** | `lib_py/agents/registry.py:26` `agent_of_row()` | `agent` ํ•„๋“œ โ†’ ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ โ†’ `pane.cmd` | `None` | +| **2** | `stop_session.sh:101-109` | ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ๋งŒ (์—ญํ•  ํ•œ์ •) | `exit 2` | +| **3** | `update_yaml_resumed.sh:44-51` | **#2 ์™€ ์™„์ „ ๋™์ผํ•œ ๋ณต์‚ฌ๋ณธ** | `exit 2` | +| **4** | `run_loop.sh:278` | `agent` ํ•„๋“œ โ†’ `pane.cmd` โ†’ ํ•˜์ดํ”ˆ ์„ธ๊ทธ๋จผํŠธ โ†’ **`claude` ๊ธฐ๋ณธ๊ฐ’** | ์‹คํŒจ ์—†์Œ | + +``` +SESSION_NAME stop/upd run_loop registry +---------------------------------- ---------- ---------- ---------- +x-creator-claude claude claude claude +agy-creator-01 EXIT2 agy None โ† ๋ผ์ด๋ธŒ ์„ธ์…˜ +my-project-dev-claude EXIT2 claude claude โ† INSTALL.md ์˜ˆ์ œ ์ด๋ฆ„ +worker-1-agy EXIT2 agy agy +foo-cline EXIT2 cline cline +bad-session-name EXIT2 claude None โ† run_loop ์€ ์กฐ์šฉํžˆ claude +orc-hermes-main EXIT2 hermes None +``` + +1. **`agy-creator-01` ์€ ์ง€๊ธˆ ์ด ์›Œํฌ์ŠคํŽ˜์ด์Šค์— running ์œผ๋กœ ๋“ฑ๋ก๋œ ์‹ค์ œ ์„ธ์…˜์ž…๋‹ˆ๋‹ค.** `pane.cmd = 'agy'` ๊ฐ€ ๊ธฐ๋ก๋ผ ์žˆ๋Š”๋ฐ๋„ `--agent` ์—†์ด๋Š” `exit 2` ๋กœ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ฆฌํ”„๊ฐ€ ์ง€๋ชฉํ•œ ๊ฒฐํ•จ์˜ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ๊ตฌ์ฒด ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค. +2. `my-project-dev-claude` ๋Š” `deploy/INSTALL.md:95` ๊ฐ€ ์Šค์Šค๋กœ ๋ฌธ์„œํ™”ํ•œ ์„ธ์…˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ์ ‘๋ฏธ์‚ฌ๊ฐ€ `-dev-claude` ๋ผ #2 ์˜ ์—ญํ•  ํ•œ์ • ์ผ€์ด์Šค์— ๊ฑธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. INSTALL.md ๊ฐ€ `--agent claude` ๋ฅผ ๋ช…์‹œํ•ด ์‚ฌ๊ณ ๊ฐ€ ์•ˆ ๋‚ฌ์„ ๋ฟ์ž…๋‹ˆ๋‹ค. +3. `run_loop.sh` ๋Š” ํ•ด์„ ์‹คํŒจ๋ฅผ `claude` ๋กœ ํก์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ 12๊ณณ์ด๋ผ ์ด๋ฒˆ ๋ฒ”์œ„ ๋ฐ–(ยง9 K-1). + +๋ผ์ด๋ธŒ 3๊ฐœ ํ–‰์— `agent_of_row` ์ง์ ‘ ์ ์šฉ: + +``` +canary-projects-multi-agent-mux-creator-claude agent_of_row='claude' match_cmd=False โ†’ 'claude' +canary-projects-multi-agent-mux-creator-cline agent_of_row='cline' match_cmd=False โ†’ 'cline' +agy-creator-01 agent_of_row='agy' match_cmd=False โ†’ None +``` + +`match_cmd=True` ๋Š” docstring ์ƒ **๋น„-์ž…์–‘(non-adoption) ์กฐํšŒ**์šฉ์ด๊ณ  `stop`/`update_yaml_resumed` ๊ฐ€ ์ •ํ™•ํžˆ ๊ทธ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. (`reconcile.sh` ์ž…์–‘ ๋ฃจํ”„ ๊ธˆ์ง€๋ผ๋Š” `3aee63cf` ยง1.2 ๋ฐ˜์ฆ์€ ์œ ํšจํ•˜๋ฉฐ, ์ด ๊ณ„ํš์€ `reconcile.sh` ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.) + +### 1.2 `agent` ํ•„๋“œ๋Š” ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค + +``` +row keys ํ•ฉ์ง‘ํ•ฉ: +['agy_conversation_id_own', 'attach_command', 'child_pid', 'claude_session_id_own', + 'cline_conversation_id_own', 'delegate_job_id', 'herdr_server', 'herdr_session', + 'herdr_session_created_at', 'herdr_session_epoch', 'kill_command', + 'last_visible_status', 'last_visible_status_at_termination', 'mcp_attachments', + 'name', 'pane', 'role', 'start_command', 'status', 'tui'] +``` + +3๊ฐœ ํ–‰ ์ „๋ถ€ `agent=None`, `pane.cmd` ๋Š” 3๊ฐœ ์ „๋ถ€ ์ฑ„์›Œ์ง. โ†’ `agent` ํ•„๋“œ๋ฅผ **์“ฐ๋Š”** ์ฝ”๋“œ๋Š” ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ , ์šฐ์„ ์ˆœ์œ„ โ‘ ์€ ํ…Œ์ŠคํŠธ๋กœ๋งŒ ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค(T4b). + +### 1.3 `load_state_json` ์€ YAML ์ด ์•„๋‹ˆ๋ผ SQLite ๋ฅผ ์ฝ๋Š”๋‹ค + +`lib.sh:938-978` โ€” `.db` ์šฐ์„ , ์—†์„ ๋•Œ๋งŒ `.yaml`. ๋ผ์ด๋ธŒ์— `.mam/agent-sessions.db`(40 KiB) ์กด์žฌ. ๋ฌธ์„œยท์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€์—์„œ "๋ ˆ์ง€์ŠคํŠธ๋ฆฌ" ๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. + +### 1.4 ๋น„์šฉ + +| ํ•ญ๋ชฉ | ์‹ค์ธก | +|---|---| +| `load_state_json` 1ํšŒ | ~34 ms | +| `python3` ๊ธฐ๋™ + `import lib_py.agents.registry` | ~27 ms | +| `stop_session.sh` ๊ฐ€ ์ด๋ฏธ ์ˆ˜ํ–‰ํ•˜๋Š” `load_state_json` | **2ํšŒ** (`:97`, `:113`) | + +`PYTHONPATH` ๋Š” `lib.sh:25` ๊ฐ€ export ํ•˜๋ฏ€๋กœ ๋งจ `python3` ๋กœ ์ž„ํฌํŠธ ๊ฐ€๋Šฅ. venv ์—†๋Š” ์‹œ์Šคํ…œ ํŒŒ์ด์ฌ(3.9.6)์—์„œ `env -i` ๊ฒ€์ฆ ์™„๋ฃŒ. `registry` ๋Š” ์„œ๋“œํŒŒํ‹ฐ ์˜์กด ์—†์Œ(`yaml` ๋ถˆํ•„์š” โ€” ์ƒํƒœ๋Š” JSON ์œผ๋กœ env ์ „๋‹ฌ). + +### 1.5 J-1 ์žฌํ˜„ + +ํŽ˜์ด๋กœ๋“œ: 1ํŒจ๋„ `width=50, height=30` + +| ๊ฒฝ๋กœ | ๊ฒฐ๊ณผ | +|---|---| +| `--min-cols 0` | `right` / `single_pane_height_constrained` | +| `MAM_MIN_PANE_COLS=0` | **`overflow`** | +| `MAM_MIN_COLS=0` | **`overflow`** | +| `--min-rows 0` | `down` | +| `MAM_MIN_PANE_ROWS=0` | **`overflow`** | +| **๋Œ€์กฐ๊ตฐ** `--min-cols 25` vs `MAM_MIN_PANE_COLS=25` | **์–‘์ชฝ ๋™์ผ** (`right`) | + +๋Œ€์กฐ๊ตฐ์ด ๊ฒฐํ•จ์„ `or` ๊ด€์šฉ๊ตฌ์˜ falsy-zero ํ•˜๋‚˜๋กœ ๊ตญ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. + +### 1.6 J-2 ์ž„๊ณ„๊ฐ’ + +``` +n=3 n//2=1 -> down โ† ํ˜„ํ–‰ d3 ๋‹จ์–ธ. ์ƒํ•œ ๊ฒ€์‚ฌ ๋„๋‹ฌ ๋ถˆ๊ฐ€ +n=4 n//2=2 -> overflow +n=5 n//2=2 -> down โ† ํŒ๋ณ„ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ ํ™€์ˆ˜ +n=6 n//2=3 -> overflow +n=7 n//2=3 -> down +``` + +### 1.7 ๋ฌธ์„œ ์‹คํƒœ + +| ํŒŒ์ผ | ํ˜„์ƒ | +|---|---| +| `multi-agent-mux-stop/SKILL.md` | `--agent` **0ํšŒ**. ์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 3๊ฐœ(`:68, :72, :77`) ์ „๋ถ€ ์ƒ๋žต | +| `deploy/INSTALL.md:94, :98` | `--agent claude` **์ด๋ฏธ ๋ช…์‹œ** โ€” ์œ ์ผํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€ | +| `multi-agent-mux-create/SKILL.md:146` | `AGENT=claude # or agy` | +| `multi-agent-mux-create/SKILL.md:171` | `must be claude or agy` โ€” ์‹ค๋ฌผ `create_session.sh:86` ์€ 4์ข…์„ ๋ฐ›์Œ | +| `multi-agent-mux-resume/SKILL.md:61` | `# or agy or hermes` (cline ๋ˆ„๋ฝ) | +| `create_session.sh:4`, `resolve_session_id.sh:4` | ํ—ค๋” ์ฃผ์„ `` | +| `update_yaml_resumed.sh:7, :14` | `[--agent claude\|agy]` | + +`create_session.sh` ๋Š” ์ด๋ฏธ `--agent` ํ•„์ˆ˜ + 4์ข… ๊ฒ€์ฆ(`:83`, `:85-86`). create ์ชฝ์€ **๋ฌธ์„œ ๋™๊ธฐํ™”๋ฟ**์ž…๋‹ˆ๋‹ค. + +### 1.8 ๊ธฐ์กด ํ…Œ์ŠคํŠธ ๊ณ„์•ฝ + +| ํ…Œ์ŠคํŠธ | ์„ธ์…˜๋ช… | ํ˜„ํ–‰ | +|---|---|---| +| `tests/test_tier1_unit.py:142` | `bad-session-name` | rc=2, `cannot infer agent` | +| `tests/test_tier3_integration.py:398` | `bad-name` | rc=2, `cannot infer agent` | + +๋‘ ์ด๋ฆ„ ๋ชจ๋‘ ์ƒŒ๋“œ๋ฐ•์Šค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(`herdr_sessions: []`)์— ์—†์Šต๋‹ˆ๋‹ค. ยง3 ์„ค๊ณ„ ๊ฒฐ์ •์„ ์ง€๋ฐฐํ•ฉ๋‹ˆ๋‹ค. + +### 1.9 (๋ถ€์ˆ˜) `cd โ€ฆ 2>/dev/null || pwd` ๊ฒฐํ•จ + +``` +line35 result: [/lib.sh] โ†’ ์กด์žฌํ•˜์ง€ ์•Š์Œ, ํ•ญ์ƒ :36 ํด๋ฐฑ +correct form : [/Users/.../.agents/skills/lib.sh] +``` + +์ž”์กด: `stop_session.sh:35`, `create_session.sh:23`, `resume_session.sh:6`. (`update_yaml_resumed.sh:10` ์€ ์ด๋ฏธ ์ •์ƒ.) `31b2d70` ์˜ R-2 ์™€ ๋™์ผ ๊ฒฐํ•จ. 1์ฐจ ์†Œ์‹ฑ ๊ฒฝ๋กœ๊ฐ€ 100% ์ฃฝ์–ด `${WORKSPACE_ROOT:-$PWD}` ํด๋ฐฑ์—๋งŒ ์˜์กดํ•ฉ๋‹ˆ๋‹ค. + +--- + +### 1.10 **[Rev.2 ์‹ ๊ทœ] Challenge C-1 ๊ฒ€์ฆ** + +#### 1.10.1 ๊ฐˆ๋ž˜ โ‘  โ€” `checked == 2` ๋กœ ๋‹จ์–ธ์ด ์‹คํŒจํ•œ๋‹ค โ†’ **ํ™•์ธ** + +Rev.1 T5 ์˜ ๋ธ”๋ก ๋‹จ์œ„ ์ •๊ทœ์‹์„ ํ˜„์žฌ ๋ฌธ์„œ์— ๊ทธ๋Œ€๋กœ ์ ์šฉ: + +``` +SKILL.md: total fenced bash/sh blocks=3, containing stop_session.sh=1 + -> one block holds 3 stop_session.sh invocations; '--agent' present in block: False +INSTALL.md: total fenced bash/sh blocks=7, containing stop_session.sh=1 + -> one block holds 2 stop_session.sh invocations; '--agent' present in block: True +CHECKED = 2 (planner asserted >= 4) +``` + +`assert checked >= 4` ๋Š” **๊ฒฐ์ •๋ก ์ ์œผ๋กœ ์‹คํŒจ**ํ•ฉ๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ ์ง€์ ์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ฐ€ ยง1.7 ์—์„œ "์˜ˆ์ œ 3๊ฐœ(`:68, :72, :77`)" ๋ฅผ ์„ธ๋ฉด์„œ๋„ ๊ทธ๊ฒƒ์ด **ํ•˜๋‚˜์˜ ํŽœ์Šค ์•ˆ์— ๋“ค์–ด ์žˆ๋‹ค**๋Š” ์‚ฌ์‹ค์„ ํ™•์ธํ•˜์ง€ ์•Š์€ ๊ฒƒ์ด ์›์ธ์ž…๋‹ˆ๋‹ค โ€” ๊ฐœ์ˆ˜๋Š” ์…Œ์ง€๋งŒ **๊ฒฝ๊ณ„๋ฅผ ์„ธ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค**. + +#### 1.10.2 ๊ฐˆ๋ž˜ โ‘ก โ€” ๋ธ”๋ก ๋‹จ์œ„ ๋‹จ์–ธ์˜ ์œ„์–‘์„ฑ(False Positive) โ†’ **ํ™•์ธ** + +INSTALL.md ์‚ฌ๋ณธ์—์„œ **๋‘ ํ˜ธ์ถœ ์ค‘ ํ•˜๋‚˜์—์„œ๋งŒ** `--agent` ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋ฎคํ…Œ์ด์…˜: + +``` +mutation applied (agent count 2 -> 1) +์„ค๊ณ„ A (๋ธ”๋ก ๋‹จ์œ„, Rev.1 ์›์•ˆ): blocks=1 all pass? True โ† ๋ฎคํ…Œ์ด์…˜ ๋ฏธ๊ฒ€์ถœ +์„ค๊ณ„ C (ํŽœ์Šค+์ปค๋งจ๋“œ, Rev.2 ์ •์ œ์•ˆ): checked=2 missing=1 โ† ๋ฎคํ…Œ์ด์…˜ ๊ฒ€์ถœ +``` + +๋ธ”๋ก์— `--agent` ๊ฐ€ **ํ•œ ๋ฒˆ์ด๋ผ๋„** ๋‚˜์˜ค๋ฉด ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ํšŒ๊ท€๋ฅผ ๋ชป ์žก๋Š” ๊ฐ€๋“œ๋Š” ๊ฐ€๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ฃผ์„์ž…๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ ์ง€์ ์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. + +#### 1.10.3 ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ์˜ ์ž”์—ฌ ์‹คํŒจ ๋ชจ๋“œ โ†’ **๋ฌธ์„œ ์ „์ฒด ์Šค์บ”์ด ์‚ฐ๋ฌธ์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•œ๋‹ค** + +์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ์€ `doc.read_text()` **์ „์ฒด**์— ์ปค๋งจ๋“œ ์ •๊ทœ์‹์„ ๋Œ๋ฆฝ๋‹ˆ๋‹ค. ์‚ฐ๋ฌธ ์† ํŒŒ์ผ๋ช… ์–ธ๊ธ‰์ด ์žˆ๋Š” ๋ฌธ์„œ๋กœ ์‹ค์ธก: + +``` +=== ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ (๋ฌธ์„œ ์ „์ฒด ์Šค์บ”) === + [1] --agent=NO | '`stop_session.sh` does not delete report trees.' โ† ์œ„์–‘์„ฑ + [2] --agent=NO | '`stop_session.sh --purge-conversation` note below.' โ† ์œ„์–‘์„ฑ + [3] --agent=YES | 'bash .../stop_session.sh --session "$S" --agent "$A"' + [4] --agent=NO | 'bash .../stop_session.sh --session "$S"' + +=== ํŽœ์Šค ์Šค์ฝ”ํ”„ + ์ปค๋งจ๋“œ ๋‹จ์œ„ (Rev.2) === + [1] --agent=YES | 'bash .../stop_session.sh --session "$S" --agent "$A"' + [2] --agent=NO | 'bash .../stop_session.sh --session "$S"' + checked=2 +``` + +์‚ฐ๋ฌธ ๋‘ ์ค„์ด ๊ฐ๊ฐ `checked += 1` ๋˜๊ณ  `--agent` ๊ฐ€ ์—†์œผ๋ฏ€๋กœ **ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจ**ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๊ฐ€์„ค์ด ์•„๋‹ˆ๋ผ ์ž„๋ฐ•ํ•œ ๋ฌธ์ œ์ธ ์ด์œ : + +- ์ด ๊ณ„ํš **ยง4.4 ์ž์ฒด๊ฐ€ stop/SKILL.md ์— ์‚ฐ๋ฌธ ๋ฌธ๋‹จ์„ ์ถ”๊ฐ€**ํ•ฉ๋‹ˆ๋‹ค. +- `stop/SKILL.md` ์˜ `## Pitfalls` ยท `## When NOT to use` ์ ˆ์€ ์„ฑ๊ฒฉ์ƒ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฐ๋ฌธ์œผ๋กœ ์–ธ๊ธ‰ํ•˜๊ฒŒ ๋˜๋Š” ์ž๋ฆฌ์ž…๋‹ˆ๋‹ค. +- ๋ฌธ์žฅ์„ ํ•˜๋‚˜ ์ผ๋‹ค๊ณ  ์‹คํŒจํ•˜๋Š” ๊ฐ€๋“œ๋Š” ๋‹ค์Œ ์‚ฌ๋žŒ์ด **์ง€์›๋‹ˆ๋‹ค**. + +ํ˜„์žฌ ๋‘ ๋ฌธ์„œ์—๋Š” ํŽœ์Šค ๋ฐ– ์–ธ๊ธ‰์ด 0๊ฑด์ด๋ผ(SKILL.md 3ํšŒยทINSTALL.md 2ํšŒ ๋ชจ๋‘ ํŽœ์Šค ์•ˆ) ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ๋„ **์ง€๊ธˆ์€** ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ€๋“œ์˜ ์กด์žฌ ์ด์œ ๋Š” ๋ฏธ๋ž˜์˜ ํŽธ์ง‘์„ ๊ฒฌ๋””๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ์ง€๊ธˆ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. + +#### 1.10.4 ์ •์ œ์•ˆ ๊ฒ€์ฆ โ€” ๊ณ„ํš ยง4.4 ์ ์šฉ ํ›„ + +`ยง4.4` ๋Œ€๋กœ ํŽธ์ง‘ํ•œ ์‚ฌ๋ณธ(3๊ฐœ ์˜ˆ์ œ์— `--agent "$AGENT"` ์ถ”๊ฐ€ + `stop_session.sh` ๋ฌธ์ž์—ด์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ์‚ฐ๋ฌธ ๋ฌธ๋‹จ ์ถ”๊ฐ€)์— ์ •์ œ์•ˆ ์ ์šฉ: + +``` +SKILL.md checked=3 missing_agent=0 +INSTALL.md checked=2 missing_agent=0 +``` + +์ด 5๊ฑด, ์ „๊ฑด ํ†ต๊ณผ. ํŽœ์Šค ์Šค์ฝ”ํ”„ ๋•๋ถ„์— **"์‚ฐ๋ฌธ์— ํŒŒ์ผ๋ช…์„ ์“ฐ์ง€ ๋ง๋ผ"๋Š” ์ œ์•ฝ์ด ๊ณ„ํš์—์„œ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค** โ€” ์ด๊ฒƒ์ด ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ ๋Œ€๋น„ ์‹ค์งˆ ์ด๋“์ž…๋‹ˆ๋‹ค. + +### 1.11 **[Rev.2 ์‹ ๊ทœ] Observation C-2 ๊ฒ€์ฆ โ€” ๊ทผ๊ฑฐ๋Š” ์ฑŒ๋ฆฐ์ €๊ฐ€ ์ œ์‹œํ•œ ๊ฒƒ๋ณด๋‹ค ๊ฐ•ํ•˜๋‹ค** + +#### 1.11.1 ์–ด๋А ์ด๋ฆ„์ด ์ •๋ณธ์ธ๊ฐ€ + +``` +.mam.env.example:133 # MAM_MIN_PANE_COLS=60 +.mam.env.example:137 # MAM_MIN_PANE_ROWS=20 +.mam.env.example:143 # MAM_MAX_PANE_COLS=3 +lib.sh:432 --min-cols "${MAM_MIN_PANE_COLS:-60}" --min-rows "${MAM_MIN_PANE_ROWS:-20}" +test_herdr_shim_contract.py:100-101 export MAM_MIN_PANE_COLS=60 / MAM_MIN_PANE_ROWS=20 +``` + +`MAM_MIN_COLS` / `MAM_MIN_ROWS` / `MAM_MAX_COLS` ๋‹จ์ถ•ํ˜•์€ **`layout.py:191-193` ์•ˆ์—์„œ๋งŒ** ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ƒ์‚ฐ ์ฝ”๋“œยท๋ฌธ์„œยทํ…œํ”Œ๋ฆฟยทํ…Œ์ŠคํŠธ ์–ด๋””์—๋„ ์—†์Šต๋‹ˆ๋‹ค. `report-8f0cb35f.md:72` ๋Š” ์ •๋ฆฌ ์ž‘์—… ๋‹น์‹œ *"no legacy `MAM_MIN_COLS=`/`MAM_MIN_ROWS=` env-prefix style"* ์„ ํ™•์ธ ์‚ฌํ•ญ์œผ๋กœ ์ ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค โ€” ๋‹จ์ถ•ํ˜•์€ **๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ**์ž…๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฐ๋ฐ `_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS")` ๋Š” **๋ ˆ๊ฑฐ์‹œ ๋‹จ์ถ•ํ˜•์„ ๋จผ์ €** ๋ด…๋‹ˆ๋‹ค. + +#### 1.11.2 ๊ฒฐ๊ณผ: ๋ฌธ์„œํ™”๋œ ์œ ์ผํ•œ ์ด๋ฆ„์ด ์กฐ์šฉํžˆ ๋ฌด์‹œ๋œ๋‹ค + +``` +env ํ˜„ํ–‰ or 60 return default continue +{} 60 60 60 +{'MAM_MIN_PANE_COLS': '0'} 60 0 0 +{'MAM_MIN_PANE_COLS': '25'} 25 25 25 +{'MAM_MIN_COLS': 'foo'} 60 60 60 +{'MAM_MIN_COLS': '', 'MAM_MIN_PANE_COLS': '25'} 25 25 25 +{'MAM_MIN_COLS': 'foo', 'MAM_MIN_PANE_COLS': '25'} 60 60 25 โ† ์ฐจ์ด +{'MAM_MIN_COLS': 'foo', 'MAM_MIN_PANE_COLS': 'bar'} 60 60 60 +{'MAM_MIN_COLS': 'foo', 'MAM_MIN_PANE_COLS': '0'} 60 60 0 โ† ์ฐจ์ด +``` + +์ฝ์–ด์•ผ ํ•  ๋‘ ๊ฐ€์ง€: + +1. **`""` ์™€ `"foo"` ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค.** ๋นˆ ๋ฌธ์ž์—ด์€ ๋‹ค์Œ ํ›„๋ณด๋กœ ๋„˜์–ด๊ฐ€๊ณ (`if raw:` ๊ฐ€ ๊ฑธ๋Ÿฌ๋ƒ„), ๋ฌดํšจ ๋ฌธ์ž์—ด์€ ์ฆ‰์‹œ ํƒˆ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค "์“ธ ์ˆ˜ ์—†๋Š” ๊ฐ’"์ธ๋ฐ ์ฒ˜๋ฆฌ๊ฐ€ ์ •๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค. `continue` ๋Š” ์ด ๋น„๋Œ€์นญ์„ ์—†์•ฑ๋‹ˆ๋‹ค. +2. ์ฐจ์ด๊ฐ€ ๋‚˜๋Š” ๋‘ ํ–‰์—์„œ ๋ฌด์‹œ๋˜๋Š” ๊ฐ’์€ **`.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ ๋ฐ”๋กœ ๊ทธ ๋ณ€์ˆ˜**์ž…๋‹ˆ๋‹ค. ์šด์˜์ž๊ฐ€ ํ…œํ”Œ๋ฆฟ๋Œ€๋กœ `MAM_MIN_PANE_COLS=25` ๋ฅผ ์„ค์ •ํ–ˆ๋Š”๋ฐ, ์…ธ ์–ด๋”˜๊ฐ€์— ๋‚จ์€ `MAM_MIN_COLS=foo` ํ•˜๋‚˜ ๋•Œ๋ฌธ์— 60 ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. + +3. **์ฐจ์ด๋Š” ์ •ํ™•ํžˆ 2ํ–‰๋ฟ์ž…๋‹ˆ๋‹ค.** ๋‚˜๋จธ์ง€ 7ํ–‰์€ ์„ธ ๊ตฌํ˜„์ด ์™„์ „ํžˆ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ `continue` ๋Š” J-1 ์ˆ˜์ •๊ณผ ์ง๊ตํ•˜๊ณ , ํ–‰๋™ ๋ณ€๊ฒฝ ํ‘œ๋ฉด์ด "์ฒซ ํ›„๋ณด ๋ฌดํšจ + ํ›„์† ํ›„๋ณด ์œ ํšจ" ๋ผ๋Š” ํ•œ ์กฐ๊ฑด์œผ๋กœ ์ขํ˜€์ง‘๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ 1๊ฑด์œผ๋กœ ์™„์ „ํžˆ ๊ณ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(T1b). + +**๊ฒฐ๋ก : C-2 ์ˆ˜์šฉ.** ์ฑŒ๋ฆฐ์ €๋Š” "๋‹ค์ค‘ fallback ์ทจ์ง€์— ๋ถ€ํ•ฉ" ์ด๋ผ๋Š” ์„ค๊ณ„ ๋…ผ๊ฑฐ๋กœ ์ œ๊ธฐํ–ˆ๋Š”๋ฐ, ์‹ค์ธกํ•˜๋ฉด **๋ฌธ์„œํ™”๋œ ์„ค์ •์ด ๋ฌด์‹œ๋˜๋Š” ์‹ค๋™์ž‘ ๊ฒฐํ•จ**์ด๋ผ ๊ทผ๊ฑฐ๊ฐ€ ๋” ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค. Rev.1 ์ด `return default` ๋ฅผ ๊ณ ๋ฅธ ์ด์œ ๋Š” "์˜คํƒ€ ์ž…๋ ฅ์— ๋Œ€ํ•œ ํ–‰๋™ ๋™๋“ฑ์„ฑ ๋ณด์กด" ์ด์—ˆ๊ณ  ๊ทธ ๋ชฉํ‘œ ์ž์ฒด๋Š” ์œ ํšจํ•˜์ง€๋งŒ, ์œ„ ํ‘œ์˜ 5ยท7ํ–‰์ด ๋ณด์—ฌ์ฃผ๋“ฏ **`continue` ๋„ ๊ทธ ๋ชฉํ‘œ๋ฅผ ๋˜‘๊ฐ™์ด ๋งŒ์กฑ**ํ•ฉ๋‹ˆ๋‹ค(๋ชจ๋“  ํ›„๋ณด๊ฐ€ ๋ฌดํšจ๋ฉด `default`). Rev.1 ์€ ๋” ์ข์€ ๋ถˆ๋ณ€์‹์„ ์ง€ํ‚ค๋А๋ผ ๋” ๋‚˜์€ ๊ฒƒ์„ ๋†“์ณค์Šต๋‹ˆ๋‹ค. + +--- + +## 2. ๋ฒ”์œ„ + +**ํฌํ•จ** + +| # | ํ•ญ๋ชฉ | +|---|---| +| S1 | `lib.sh` ์— `resolve_agent_type_from_registry()` ๊ณต์šฉ ํ—ฌํผ ์‹ ์„ค | +| S2 | `stop_session.sh` ํด๋ฐฑ์„ S1 ๋กœ ๊ต์ฒด + ํ—ค๋”/`usage()` ๊ฐฑ์‹  | +| S3 | `update_yaml_resumed.sh` ์˜ ๋™์ผ ๋ณต์‚ฌ๋ณธ์„ S1 ๋กœ ๊ต์ฒด + ํ—ค๋”/`usage()` ๊ฐฑ์‹  | +| S4 | `stop`/`resume`/`create` SKILL.md ๋ฐ 3๊ฐœ ์Šคํฌ๋ฆฝํŠธ ํ—ค๋” ์ฃผ์„ ๋ฌธ์„œ ๋™๊ธฐํ™” | +| S5 | J-1: `_env_int(*names, default=None)` ๋ฆฌํŒฉํ„ฐ(+ **C-2 `continue`**) ๋ฐ `main()` ๋ฐฐ์„  | +| S6 | ํšŒ๊ท€ ํ…Œ์ŠคํŠธ **8๊ฑด** ์‹ ์„ค + ๊ธฐ์กด J-2 ๊ฐ€๋“œ 1๊ฑด ๋ณด๊ฐ• | +| S7 | `IMPROVEMENTS.md` ๋ฐฑ๋กœ๊ทธ ๋“ฑ๋ก ๋ฐ ์™„๋ฃŒ ์นด์šดํŠธ ๊ฐฑ์‹  | +| S8 | (๋ถ„๋ฆฌ ์ปค๋ฐ‹) ยง1.9 `cd โ€ฆ && pwd` 3๊ณณ | + +**์ œ์™ธ** + +| ํ•ญ๋ชฉ | ์ œ์™ธ ์‚ฌ์œ  | +|---|---| +| `run_loop.sh:278` ํ†ตํ•ฉ | ํ˜ธ์ถœ 12๊ณณ + `claude` ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ฑฐ๋Š” ํ–‰๋™ ๋ณ€๊ฒฝ โ†’ K-1 | +| `reconcile.sh` ํ•ด์„ ๊ฒฝ๋กœ | `3aee63cf` ยง1.2 ์‹ค์ธก ๋ฐ˜์ฆ ์œ ํšจ | +| `agent_of_row` ์„ธ๊ทธ๋จผํŠธ ๋งค์นญ | `reconcile.sh` ์ž…์–‘ ํŒ์ •์— ์˜ํ–ฅ โ†’ K-4 | +| `max_columns` falsy-zero | ๋ธŒ๋ฆฌํ”„๊ฐ€ min-cols/min-rows ๋งŒ ์ง€๋ชฉ โ†’ K-2 | +| **`_env_int` ํ›„๋ณด ์ˆœ์„œ ๋’ค์ง‘๊ธฐ** | ๋ฌธ์„œํ™”๋œ `MAM_MIN_PANE_COLS` ๋ฅผ ์•ž์œผ๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ์€ **์šฐ์„ ์ˆœ์œ„ ๋ณ€๊ฒฝ**์ด๋ผ C-2 (๋ฌดํšจ๊ฐ’ ๊ฑด๋„ˆ๋›ฐ๊ธฐ)์™€ ๋ณ„๊ฐœ ์‚ฌ์•ˆ โ†’ **K-5** | +| ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— `agent` ํ•„๋“œ ์“ฐ๊ธฐ | ์“ฐ๋Š” ์ฝ”๋“œ๊ฐ€ 0๊ฑด์ด๊ณ  ์š”๊ตฌ๋˜์ง€ ์•Š์Œ | + +--- + +## 3. ์„ค๊ณ„ ๊ฒฐ์ • โ€” ํด๋ฐฑ์„ **์–ด๋””์—** ๋„ฃ๋Š”๊ฐ€ (Rev.1 ์œ ์ง€) + +`stop_session.sh` ํ˜„์žฌ ์ˆœ์„œ: + +``` +:88 --session ๊ฒ€์‚ฌ โ†’ exit 2 +:89 YAML ํŒŒ์ผ ์กด์žฌ ๊ฒ€์‚ฌ โ†’ exit 1 +:97 resolve_herdr_workspace (load_state_json #1) +:101 AGENT ์ ‘๋ฏธ์‚ฌ ์ถ”๋ก  โ†’ exit 2 โ† ๊ต์ฒด ๋Œ€์ƒ +:113 MAPPED_DATA: row ์กฐํšŒ (load_state_json #2) +:124 row ์—†์Œ โ†’ exit 1 +:152 AGENT ์ตœ์ดˆ ์‚ฌ์šฉ +``` + +**์•ˆ A (๊ธฐ๊ฐ)** โ€” `:113` ๋ธ”๋ก์— ๋ณ‘ํ•ฉ. ํ”„๋กœ์„ธ์Šค 1๊ฐœ ์ ˆ์•ฝ, ์ฝ”๋“œ๋„ ๊ฐ€์žฅ ๊น”๋”. **๊ธฐ๊ฐ ์‚ฌ์œ **: ํ•ด์„์ด row ์กฐํšŒ ๋’ค๋กœ ๋ฐ€๋ ค "๋ฏธ๋“ฑ๋ก + ์ด๋ฆ„ ํ•ด์„ ์‹คํŒจ" ์„ธ์…˜์˜ ์ข…๋ฃŒ ์ฝ”๋“œ๊ฐ€ **2 โ†’ 1** ๋กœ ๋ฐ”๋€๋‹ˆ๋‹ค. ยง1.8 ์˜ ๋‘ ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง€๊ณ  ํ—ค๋” `:28-30` ์˜ ๊ณ„์•ฝ๋„ ๋ฐ”๋€๋‹ˆ๋‹ค. ์–ป๋Š” ๊ฒƒ์€ 34 ms ๋ฟ์ž…๋‹ˆ๋‹ค. + +**์•ˆ B (์ฑ„ํƒ)** โ€” `:101` ์ž๋ฆฌ๋ฅผ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ํ•ด์„๊ธฐ๋งŒ ๊ต์ฒด. + +| ์„ฑ์งˆ | ๊ฒฐ๊ณผ | +|---|---| +| ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ | **๋ถˆ๋ณ€** (`exit 2`, ๋™์ผ ๋ฉ”์‹œ์ง€) | +| ยง1.8 ๊ธฐ์กด ํ…Œ์ŠคํŠธ 2๊ฑด | **์ˆ˜์ • ๋ถˆํ•„์š”** | +| `agy-creator-01` | `EXIT2` โ†’ `agy` โœ… | +| `my-project-dev-claude`, `foo-cline`, `worker-1-agy` | `EXIT2` โ†’ ์ •์ƒ ํ•ด์„ โœ… | +| ๋ฏธ๋ž˜์˜ `agent` ๋ช…์‹œ ํ•„๋“œ | ์ž๋™ ์ง€์› โœ… | +| ๋น„์šฉ | `--agent` ์ƒ๋žต ์‹œ์—๋งŒ `load_state_json` 1ํšŒ (~34 ms) | + +`set -euo pipefail` ์ฃผ์˜: ์‹คํŒจ ๊ฐ€๋Šฅํ•œ ๋ช…๋ น ์น˜ํ™˜์„ ๋Œ€์ž…์— ์“ฐ๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ `|| AGENT=""` ๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค(`test_lib_sh_layout_split_in_set_e_subshell` ์„ ๋ก€). `stderr` ๋Š” ์–ต์ œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค โ€” ์ •์ƒ ํ•ด์„ ์‹คํŒจ๋Š” `sys.exit(1)` ์ด๋ผ ๋ฌด์ถœ๋ ฅ์ด๊ณ , `PYTHONPATH` ํŒŒ์† ๊ฐ™์€ ์ง„์งœ ์˜ค๋ฅ˜์˜ traceback ์€ ๋ณด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ํ…Œ์ŠคํŠธ๋Š” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด ๋‹จ์–ธ์ด๋ผ traceback ์ด ์„ž์—ฌ๋„ ๋ฌด์˜ํ–ฅ์ž…๋‹ˆ๋‹ค. + +--- + +## 4. ๊ตฌํ˜„ + +### 4.1 S1 โ€” `lib.sh` ๊ณต์šฉ ํ—ฌํผ + +`resolve_herdr_session()`(`:989`) ๋ฐ”๋กœ ์•ž์— ์ถ”๊ฐ€. + +```bash +# resolve_agent_type_from_registry +# +# ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(YAML/DB)์— ๊ธฐ๋ก๋œ ์‚ฌ์‹ค๋กœ ์—์ด์ „ํŠธ ์ข…๋ฅ˜๋ฅผ ํ•ด์„ํ•œ๋‹ค. ์šฐ์„ ์ˆœ์œ„๋Š” +# lib_py.agents.registry.agent_of_row ์˜ ๊ณ„์•ฝ์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅธ๋‹ค: +# โ‘  row['agent'] ๋ช…์‹œ ํ•„๋“œ +# โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ (*-{creator,planner,reviewer}- ๋ฐ *-) +# โ‘ข pane.cmd (์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๊ฑฐ๋‚˜ .../ ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ) +# ์„ฑ๊ณตํ•˜๋ฉด ์—์ด์ „ํŠธ๋ช…์„ stdout ์— ์ถœ๋ ฅํ•˜๊ณ  0 ์„, ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด ์•„๋ฌด๊ฒƒ๋„ +# ์ถœ๋ ฅํ•˜์ง€ ์•Š๊ณ  1 ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ํ˜ธ์ถœ์ž๊ฐ€ ์†Œ์œ ํ•œ๋‹ค โ€” ๊ฐ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ +# ๋ฌธ์„œํ™”ํ•œ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค. +# +# NOTE: agent_of_row ์˜ match_cmd=True ๋Š” "๋น„-์ž…์–‘ ์กฐํšŒ" ๊ณ„์•ฝ์ด๋‹ค. reconcile.sh +# ์ž…์–‘ ๋ฃจํ”„๋Š” ์ด ํ—ฌํผ๋ฅผ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค (3aee63cf ยง1.2 ์‹ค์ธก ๋ฐ˜์ฆ). +resolve_agent_type_from_registry() { + local name="$1" + MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$name" python3 -c " +import os, json, sys +from lib_py.agents.registry import agent_of_row +name = os.environ['SESSION_NAME'] +d = json.loads(os.environ.get('MAM_STATE_JSON', '{}')) +row = next((s for s in d.get('herdr_sessions', []) if s.get('name') == name), {}) +resolved = agent_of_row(row, session_name=name) +if not resolved: + sys.exit(1) +print(resolved) +" +} +``` + +**์ด๋ฆ„์„ `resolve_agent_type` ๋กœ ํ•˜์ง€ ์•Š๋Š” ์ด์œ **: `run_loop.sh:278` ์ด ๋™๋ช… ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๋ฉฐ `lib.sh` ๋ฅผ source ํ•ฉ๋‹ˆ๋‹ค. ๋™๋ช…์ด๋ฉด run_loop ์˜ ๋‚˜์ค‘ ์ •์˜๊ฐ€ ์กฐ์šฉํžˆ ๋ฎ์–ด์จ์„œ 12๊ฐœ ํ˜ธ์ถœ ์ง€์ ์ด ์–ด๋А ๊ตฌํ˜„์„ ์“ฐ๋Š”์ง€ ์ฝ์–ด์„œ๋Š” ์•Œ ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + +### 4.2 S2 โ€” `stop_session.sh` + +`:100-109` ๊ต์ฒด: + +```bash +# --agent ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ ํ•ด์„ (B-21). +# โ‘  row['agent'] โ†’ โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข pane.cmd ์ˆœ. ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด +# ์ข…์ „๊ณผ ๋™์ผํ•˜๊ฒŒ exit 2 (ํ—ค๋” :27-30 ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ์œ ์ง€). +if [ -z "$AGENT" ]; then + AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT="" + [ -n "$AGENT" ] || { + echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2 + exit 2 + } +fi +``` + +ํ—ค๋” `:15-16`: + +``` +# --agent โ€” claude | agy | hermes | cline +# (๊ถŒ์žฅ: ํ•ญ์ƒ ๋ช…์‹œ. ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ +# ํ•ด์„ โ€” agent ํ•„๋“œ โ†’ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ pane.cmd; +# ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด exit 2) +``` + +`usage()` `:46-47`: + +``` + --agent โ€” claude | agy | hermes | cline (recommended: always pass it) + (falls back to the registry record: agent field -> + session-name suffix -> pane.cmd) +``` + +`usage()` ์— 4๊ฐœ ์—์ด์ „ํŠธ๋ช…์ด ๋ชจ๋‘ ๋‚จ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค โ€” `test_comp_stop_usage_matches_parser`(`test_tier2_component.py:711-712`)๊ฐ€ ๋‹จ์–ธํ•ฉ๋‹ˆ๋‹ค. + +### 4.3 S3 โ€” `update_yaml_resumed.sh` + +`:43-52` ๋ฅผ S2 ์™€ ๋™์ผํ•œ ๋ธ”๋ก์œผ๋กœ ๊ต์ฒด(๋ฉ”์‹œ์ง€ยท์ข…๋ฃŒ ์ฝ”๋“œ ๋™์ผ). ํ—ค๋” `:7` / `usage()` `:14` ์˜ `[--agent claude|agy]` โ†’ `[--agent claude|agy|hermes|cline]`. `:10` ์€ ์ด๋ฏธ ์˜ฌ๋ฐ”๋ฅธ ์†Œ์‹ฑ ํ˜•ํƒœ์ด๋ฏ€๋กœ ์†๋Œ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +### 4.4 S4 โ€” ๋ฌธ์„œ ๋™๊ธฐํ™” + +**`multi-agent-mux-stop/SKILL.md`** + +Pre-flight(`:38-40`): + +```bash +SESSION_NAME=-creator- # convention +AGENT=claude # claude | agy | hermes | cline โ€” always pass it +AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml +``` + +์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 3๊ฐœ(`:68, :72, :77`)์— `--agent "$AGENT"` ์ถ”๊ฐ€: + +```bash +# 1. Stop gracefully (default โ€” captures ID, shuts down safely, status=stopped) +bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ + --session "$SESSION_NAME" --agent "$AGENT" +``` + +"Idempotency" ๋ฌธ๋‹จ(`:81`) ์•„๋ž˜์— ์ถ”๊ฐ€: + +```markdown +**`--agent` is the standard.** Pass it on every invocation. If omitted, the script +resolves the agent from the registry record โ€” the row's `agent` field, then the +session-name suffix, then `pane.cmd` โ€” and exits 2 if none of the three resolve. +The fallback exists for recovery, not as the normal calling convention: a session +whose name carries no agent suffix (e.g. `agy-creator-01`) is only resolvable +while its registry row survives. +``` + +> **Rev.1 ์— ์žˆ๋˜ ์ œ์•ฝ ์‚ญ์ œ.** Rev.1 ์€ ์ด ์‚ฐ๋ฌธ์— `stop_session.sh` ๋ฌธ์ž์—ด์„ ์“ฐ์ง€ ๋ง๋ผ๋Š” ์ œ์•ฝ์„ ๊ฑธ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. Rev.2 ์˜ T5 ๊ฐ€ ํŽœ์Šค ์Šค์ฝ”ํ”„์ด๋ฏ€๋กœ **๊ทธ ์ œ์•ฝ์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค**(ยง1.10.3~4). ์‚ฐ๋ฌธ์„ ์ž์œ ๋กญ๊ฒŒ ์“ฐ์‹ญ์‹œ์˜ค. + +**`multi-agent-mux-resume/SKILL.md:61`** โ€” `AGENT=claude # or agy or hermes` โ†’ `AGENT=claude # claude | agy | hermes | cline โ€” pass it explicitly` + +**`multi-agent-mux-create/SKILL.md`** +- `:146` ๋™์ผ ์ˆ˜์ • +- `:171` โ€” ์‹ค๋ฌผ `create_session.sh:86` ๊ณผ ๋™์ผํ•œ `claude, agy, hermes or cline` ๋ฌธ๊ตฌ๋กœ. ๊ฐ™์€ `case`(`:158-172`)์— `hermes`/`cline` arm ์ด ์—†์œผ๋ฏ€๋กœ, **์Šค๋‹ˆํŽซ์„ ์ถ•์•ฝํ•˜๊ณ  ์‹ค๋ฌผ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•˜๋Š” ์ชฝ์„ ๊ถŒ์žฅ**ํ•ฉ๋‹ˆ๋‹ค. SKILL.md ์Šค๋‹ˆํŽซ์ด ์‹ค๋ฌผ๊ณผ ๊ฐˆ๋ผ์ง€๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์ด๋ฒˆ์— ๊ณ ์น˜๋Š” ๊ฒฐํ•จ๊ตฐ์ž…๋‹ˆ๋‹ค. + +**์Šคํฌ๋ฆฝํŠธ ํ—ค๋” ์ฃผ์„** โ€” `create_session.sh:4`, `resolve_session_id.sh:4` ์˜ `--agent ` โ†’ `` + +### 4.5 S5 โ€” J-1 (+ C-2) + +`layout.py:175-186`: + +```python +def _env_int(*names: str, default: Optional[int] = None) -> Optional[int]: + """First *valid* int among the env vars in *names*, else `default`. + + `default` is an explicit parameter rather than an `or` at the call site so a + legitimate 0 survives (MAM_MIN_PANE_COLS=0 means 0, not the 60 default). + + An unparsable value is skipped rather than raised or treated as terminal: a + typo in an operator's shell must not take the whole layout call down (lib.sh + would silently fall back to 'right'), and must not shadow a later candidate + that IS set correctly -- MAM_MIN_COLS is a legacy alias while + MAM_MIN_PANE_COLS is the name .mam.env.example documents, so aborting on the + first bad value would discard the documented setting. Empty values already + fell through; this makes invalid values behave the same way. + """ + for n in names: + raw = os.environ.get(n, "").strip() + if raw: + try: + return int(raw) + except ValueError: + continue + return default +``` + +`main()` `:191-193`: + +```python + 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` ๋Š” `default=None` ์ด ์˜๋„๋œ ์˜๋ฏธ(๋ฏธ์ง€์ • = ์ƒํ•œ ์—†์Œ)์ด๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค. + +**`return default` ๊ฐ€ ์•„๋‹ˆ๋ผ `continue` ์—ฌ์•ผ ํ•˜๋Š” ์ด์œ (๋ถˆ๋ณ€์‹ ํ™•์ธ)**: ๋ชจ๋“  ํ›„๋ณด๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋ฌดํšจ์ด๋ฉด ๋ฃจํ”„๊ฐ€ ๋๋‚˜ `return default` ์— ๋„๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ Rev.1 ์ด ์ง€ํ‚ค๋ ค๋˜ "์˜คํƒ€ ์ž…๋ ฅ์€ ๋ฌธ์„œํ™”๋œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํก์ˆ˜๋œ๋‹ค"๋Š” ์„ฑ์งˆ์€ **๊ทธ๋Œ€๋กœ ์œ ์ง€**๋˜๋ฉฐ(ยง1.11.2 ํ‘œ 4ยท7ํ–‰), ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์€ "์ฒซ ํ›„๋ณด ๋ฌดํšจ + ํ›„์† ํ›„๋ณด ์œ ํšจ" ํ•œ ์กฐ๊ฑด๋ฟ์ž…๋‹ˆ๋‹ค. `min_cols=None` ์œผ๋กœ `compute_2xk_layout` ์— ๋“ค์–ด๊ฐ€ `TypeError` ๊ฐ€ ๋‚˜๋Š” ๊ฒฝ๋กœ๋Š” ๋‘ ์•ˆ ๋ชจ๋‘์—์„œ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +`*names` ๋’ค์˜ ํ‚ค์›Œ๋“œ ์ „์šฉ `default` ๋Š” Python 3.9 ์—์„œ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค(์‹œ์Šคํ…œ ์ธํ„ฐํ”„๋ฆฌํ„ฐ 3.9.6 ์‹ค์ธก). `_env_int` ํ˜ธ์ถœ์ž๋Š” `main()` 3๊ณณ๋ฟ์ž…๋‹ˆ๋‹ค. + +--- + +## 5. ํ…Œ์ŠคํŠธ ๊ณ„ํš + +์‹ ์„ค **8๊ฑด**, ๊ธฐ์กด ๊ฐ€๋“œ ๋ณด๊ฐ• **1๊ฑด**. ์˜ˆ์ƒ collected: **333 โ†’ 341**. + +> **Rev.1 ์ž์ฒด ์ •์ •**: Rev.1 ์€ "์‹ ์„ค 8๊ฑด โ†’ 341" ์ด๋ผ๊ณ  ์ ์—ˆ์œผ๋‚˜ ์‹ค์ œ ์—ด๊ฑฐ๋Š” 7๊ฑด์ด์—ˆ์Šต๋‹ˆ๋‹ค(T1 3 + T3 1 + T4 2 + T5 1). Rev.2 ๋Š” C-2 ์ „์šฉ ํ…Œ์ŠคํŠธ T1b ๋ฅผ ๋”ํ•ด ์‹ค์ œ๋กœ 8๊ฑด์ด ๋˜๋ฉฐ, 341 ์ด๋ผ๋Š” ์ˆ˜์น˜๊ฐ€ ๋น„๋กœ์†Œ ๋งž์•„๋–จ์–ด์ง‘๋‹ˆ๋‹ค. + +### T1 โ€” J-1 env/flag ๋“ฑ๊ฐ€์„ฑ (`tests/test_layout.py`, 3๊ฑด) + +```python +_LAYOUT_ENV_VARS = ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", "MAM_MIN_ROWS", + "MAM_MIN_PANE_ROWS", "MAM_MAX_COLS", "MAM_MAX_PANE_COLS") + +def _run_layout(payload, args=(), env_extra=None): + env = {**os.environ, "PYTHONPATH": os.path.abspath(".agents/skills")} + for k in _LAYOUT_ENV_VARS: + env.pop(k, None) # ํ˜ธ์ถœ์ž ์…ธ์˜ ์˜ค์—ผ ์ฐจ๋‹จ + env.update(env_extra or {}) + res = subprocess.run([sys.executable, "-m", "lib_py.layout", "--json", *args], + input=json.dumps(payload), capture_output=True, text=True, env=env) + assert res.returncode == 0, res.stderr + return json.loads(res.stdout) + +# height//2 = 15 < min_rows(20) ๋กœ ์ œ์•ฝ ๋ถ„๊ธฐ ์ง„์ž…, width//2 = 25 ๊ฐ€ min_cols ์™€ ๋น„๊ต๋จ. +_ZERO_TRAP = {"result": {"panes": [ + {"pane_id": "p1", "rect": {"x": 0, "y": 0, "width": 50, "height": 30}}]}} + + +def test_j1_env_zero_min_cols_matches_flag_zero(): + """J-1: MAM_MIN_PANE_COLS=0 must mean 0, not fall through to the 60 default.""" + flag = _run_layout(_ZERO_TRAP, ("--min-cols", "0")) + assert flag["direction"] == "right" and flag["reason"] == "single_pane_height_constrained" + for var in ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS"): + assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var + + +def test_j1_env_zero_min_rows_matches_flag_zero(): + flag = _run_layout(_ZERO_TRAP, ("--min-rows", "0")) + assert flag["direction"] == "down" and flag["reason"] == "single_pane_split_down" + for var in ("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS"): + assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var + + +def test_j1_nonzero_and_malformed_env_behaviour_unchanged(): + """Behaviour neutrality: non-zero env still applies, and a lone typo still + lands on the documented default instead of crashing on a None comparison.""" + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) == \ + _run_layout(_ZERO_TRAP, ("--min-cols", "25")) + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "abc"}) == _run_layout(_ZERO_TRAP) +``` + +### T1b โ€” **[Rev.2 ์‹ ๊ทœ]** C-2: ๋ฌดํšจ๊ฐ’์ด ๋’ค ํ›„๋ณด๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋Š”๋‹ค (1๊ฑด) + +```python +def test_j1b_invalid_alias_does_not_shadow_the_documented_var(): + """C-2: MAM_MIN_COLS is a legacy alias checked first; MAM_MIN_PANE_COLS is the + name .mam.env.example documents. An unparsable value in the alias must be + skipped, not abort the search and discard the documented setting. + + Empty values already fell through (`if raw:`); this makes invalid values + behave the same way. When every candidate is unusable, `default` still wins. + """ + good = _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) + assert good["direction"] == "right" + # ๋ณ„์นญ์ด ๊นจ์ ธ ์žˆ์–ด๋„ ๋ฌธ์„œํ™”๋œ ๋ณ€์ˆ˜๊ฐ€ ์ ์šฉ๋œ๋‹ค + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", + "MAM_MIN_PANE_COLS": "25"}) == good + # 0 ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€ (J-1 ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ) + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", + "MAM_MIN_PANE_COLS": "0"}) == \ + _run_layout(_ZERO_TRAP, ("--min-cols", "0")) + # ๋ชจ๋“  ํ›„๋ณด๊ฐ€ ๋ฌดํšจ๋ฉด ๋ฌธ์„œํ™”๋œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํก์ˆ˜ (Rev.1 ๋ถˆ๋ณ€์‹ ๋ณด์กด) + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", + "MAM_MIN_PANE_COLS": "bar"}) == _run_layout(_ZERO_TRAP) +``` + +๋งˆ์ง€๋ง‰ ๋‹จ์–ธ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค โ€” `continue` ๋กœ ๋ฐ”๊พธ๋ฉด์„œ Rev.1 ์ด ์ง€ํ‚ค๋ ค๋˜ ์„ฑ์งˆ์ด ๊นจ์ง€์ง€ ์•Š์•˜์Œ์„ ๊ฐ™์€ ํ…Œ์ŠคํŠธ ์•ˆ์—์„œ ๋ชป ๋ฐ•์Šต๋‹ˆ๋‹ค. + +### T2 โ€” J-2 ์ž„๊ณ„๊ฐ’ ๋ณด๊ฐ• (๊ธฐ์กด `test_headless_max_columns_growth_guard` ํ™•์žฅ, ์‹ ์„ค 0๊ฑด) + +```python + # n=5 is the first odd n that can discriminate: n//2 == 2 == max_columns, so an + # over-correction that also checked the cap on the odd branch would return + # overflow here. n=3 has n//2 == 1 and cannot reach the check at all. + d5 = compute_2xk_layout(headless(5), max_columns=2) + assert d5.direction == "down" and not d5.is_overflow + assert d5.reason == "headless_odd_down" +``` + +๊ณ„ํš `5e4ef463` ์˜ ๋ฎคํ…Œ์ด์…˜ M6 ์‚ฌ์–‘ ์˜ค๋ฅ˜(์ œ๊ฐ€ `n=3` ์„ ๊ณจ๋ž๊ณ  ๊ทธ ๊ฐ’์œผ๋กœ๋Š” ํŒ๋ณ„ ๋ถˆ๊ฐ€)๋ฅผ ๋‹ซ์Šต๋‹ˆ๋‹ค. + +### T3 โ€” `agent_of_row` ๋‹จ์œ„ ๋ณด๊ฐ• (`tests/test_a4_adapter_contract.py`, 1๊ฑด) + +```python +def test_agent_of_row_pane_cmd_binary_path_and_failure(): + # pane.cmd ๊ฐ€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ ํ˜•ํƒœ์—ฌ๋„ ํ•ด์„๋œ๋‹ค + assert agent_of_row({'pane': {'cmd': '/usr/local/bin/agy'}}) == 'agy' + # ์„ธ ๊ฒฝ๋กœ ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด None โ€” ํ˜ธ์ถœ์ž๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ์†Œ์œ ํ•œ๋‹ค + assert agent_of_row({}, session_name='bad-session-name') is None + # ์ž…์–‘ ์กฐํšŒ์šฉ match_cmd=False ์—์„œ๋Š” pane.cmd ๋ฅผ ๋ณด์ง€ ์•Š๋Š”๋‹ค + assert agent_of_row({'name': 'agy-creator-01', 'pane': {'cmd': 'agy'}}, + match_cmd=False) is None +``` + +### T4 โ€” `stop_session.sh` ํด๋ฐฑ (`tests/test_tier2_component.py`, 2๊ฑด) + +๊ธฐ์กด `test_comp_stop_sqlite_state_update` ์˜ `run_mutation` ํŒจํ„ด ์‚ฌ์šฉ(herdr ๋ถ€์žฌ โ†’ "herdr already dead, just updating YAML" ๊ฒฝ๋กœ๋กœ rc=0 ์™„์ฃผ, ์‹ค์ œ ์„ธ์…˜ ๋ฏธ์˜ํ–ฅ). + +```python +def test_comp_stop_agent_fallback_reads_pane_cmd(mam_sandbox): + """B-21: --agent ์ƒ๋žต ์‹œ ์„ธ์…˜๋ช…์— ์—์ด์ „ํŠธ ์ ‘๋ฏธ์‚ฌ๊ฐ€ ์—†์–ด๋„ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ํ–‰์˜ + pane.cmd ๋กœ ํ•ด์„๋œ๋‹ค (๋ผ์ด๋ธŒ `agy-creator-01` ํ˜•ํƒœ).""" + mutation = """ +d['herdr_sessions'] = [{ + 'name': 'agy-creator-01', + 'status': 'running', + 'pane': {'cwd': 'WS_PLACEHOLDER', 'cmd': 'agy'} +}] +""".replace("WS_PLACEHOLDER", str(mam_sandbox)) + run_mutation(mam_sandbox, mutation) + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh" + res = subprocess.run(["bash", str(script), "--session", "agy-creator-01"], + capture_output=True, text=True) + assert res.returncode == 0, res.stderr + assert re.search(r"^\s*agent:\s+agy\s*$", res.stdout, re.M), res.stdout + + +def test_comp_stop_agent_fallback_prefers_explicit_agent_field(mam_sandbox): + """์šฐ์„ ์ˆœ์œ„ ๊ณ„์•ฝ: ๋ช…์‹œ `agent` ํ•„๋“œ๊ฐ€ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ์™€ pane.cmd ๋ฅผ ๋ชจ๋‘ ์ด๊ธด๋‹ค.""" + mutation = """ +d['herdr_sessions'] = [{ + 'name': 'x-creator-claude', + 'status': 'running', + 'agent': 'hermes', + 'pane': {'cwd': 'WS_PLACEHOLDER', 'cmd': 'claude'} +}] +""".replace("WS_PLACEHOLDER", str(mam_sandbox)) + run_mutation(mam_sandbox, mutation) + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh" + res = subprocess.run(["bash", str(script), "--session", "x-creator-claude"], + capture_output=True, text=True) + assert res.returncode == 0, res.stderr + assert re.search(r"^\s*agent:\s+hermes\s*$", res.stdout, re.M), res.stdout +``` + +๋‘ ๋ฒˆ์งธ๊ฐ€ T4 ๋ฅผ "pane.cmd ๋ฅผ ์ฝ๋Š”๋‹ค" ๊ฐ€ ์•„๋‹ˆ๋ผ **"`agent_of_row` ๊ณ„์•ฝ์„ ํ˜ธ์ถœํ•œ๋‹ค"** ๋กœ ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋งŒ ์žˆ์œผ๋ฉด `pane.cmd` ๋งŒ ์ง์ ‘ ์ฝ๋Š” ์–•์€ ๊ตฌํ˜„๋„ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. + +**๋ฏธํ•ด๊ฒฐ ๊ณ„์•ฝ(exit 2)** ์€ ์ด๋ฏธ `test_tier1_unit.py:142` ์™€ `test_tier3_integration.py:398` ์ด ์ง€ํ‚ต๋‹ˆ๋‹ค. ๋‘ ํŒŒ์ผ์„ **์ˆ˜์ •ํ•˜์ง€ ์•Š์€ ์ฑ„ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ**์ด ์•ˆ B ์˜ ์ฆ๊ฑฐ์ด๋ฏ€๋กœ ์ค‘๋ณต ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +### T5 โ€” **[Rev.2 ์žฌ์„ค๊ณ„]** ๋ฌธ์„œ ๊ฐ€๋“œ (`tests/test_tier2_component.py`, 1๊ฑด) + +Rev.1 ์›์•ˆ์€ ยง1.10.1 ์˜ ์‹ค์ธก๋Œ€๋กœ `checked == 2` ๋กœ ํ™•์ • ์‹คํŒจํ•˜๊ณ , ยง1.10.2 ๋Œ€๋กœ ๋‹จ์ผ ํ˜ธ์ถœ ํšŒ๊ท€๋ฅผ ๋†“์นฉ๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ **์ปค๋งจ๋“œ ๋‹จ์œ„** ๊ต์ •์„ ์ฑ„ํƒํ•˜๋˜, ยง1.10.3 ์˜ ์‚ฐ๋ฌธ ์œ„์–‘์„ฑ์„ ๋ง‰๊ธฐ ์œ„ํ•ด **ํŽœ์Šค ์Šค์ฝ”ํ”„**๋ฅผ ํ•ฉ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +```python +# ์ฝ”๋“œ ํŽœ์Šค ์•ˆ์˜ stop_session.sh ํ˜ธ์ถœ์„ '๋ช…๋ น ๋‹จ์œ„'๋กœ ์ž˜๋ผ๋‚ธ๋‹ค. +# - ํŽœ์Šค ์Šค์ฝ”ํ”„: ์‚ฐ๋ฌธ ์† `stop_session.sh` ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•˜์ง€ ์•Š๋Š”๋‹ค +# (Pitfalls / When-NOT-to-use ์ ˆ์€ ์„ฑ๊ฒฉ์ƒ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฐ๋ฌธ์œผ๋กœ ์–ธ๊ธ‰ํ•œ๋‹ค). +# - ๋ช…๋ น ๋‹จ์œ„: ํ•œ ํŽœ์Šค์— ์—ฌ๋Ÿฌ ํ˜ธ์ถœ์ด ๋“ค์–ด ์žˆ์–ด๋„ ๊ฐ๊ฐ์„ ๋”ฐ๋กœ ๊ฒ€์ฆํ•œ๋‹ค +# (๋ธ”๋ก ๋‹จ์œ„๋กœ ๋ณด๋ฉด ๊ทธ์ค‘ ํ•˜๋‚˜๋งŒ --agent ๋ฅผ ๊ฐ€์ ธ๋„ ํ†ต๊ณผํ•ด ๋ฒ„๋ฆฐ๋‹ค). +_FENCE_RE = re.compile(r"```(?:bash|sh)\n(.*?)```", re.S) +_STOP_CALL_RE = re.compile(r"(?:bash\s+)?\S*stop_session\.sh[^\n\\]*(?:\\\n[^\n\\]*)*") + + +def test_comp_docs_stop_examples_pass_agent(): + """B-21 ๋ฌธ์„œ ๊ณ„์•ฝ: ๋ฌธ์„œ์˜ ๋ชจ๋“  stop_session.sh ์˜ˆ์ œ๋Š” --agent ๋ฅผ ๋„˜๊ธด๋‹ค. + ๋ฌธ์„œ ๋ณ€๊ฒฝ์€ ๋ฎคํ…Œ์ด์…˜ ๊ฐ๋„๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ด ๊ฐ€๋“œ๊ฐ€ ํ‘œ์ค€์˜ ์œ ์ผํ•œ ์ง‘ํ–‰ ์žฅ์น˜๋‹ค.""" + repo = Path(__file__).resolve().parent.parent + expected = { # ๋ฌธ์„œ๋ณ„ ์ตœ์†Œ ์˜ˆ์ œ ์ˆ˜ โ€” ์˜ˆ์ œ๋ฅผ ์ง€์›Œ ๊ฐ€๋“œ๋ฅผ ๋ฌด๋ ฅํ™”ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”๋‹ค + repo / ".agents/skills/multi-agent-mux-stop/SKILL.md": 3, + repo / "deploy/INSTALL.md": 2, + } + for doc, floor in expected.items(): + seen = 0 + for block in _FENCE_RE.findall(doc.read_text()): + for m in _STOP_CALL_RE.finditer(block): + snippet = m.group(0) + seen += 1 + assert "--agent" in snippet, \ + f"{doc.name}: stop_session.sh example without --agent:\n{snippet}" + assert seen >= floor, f"{doc.name}: expected >= {floor} examples, saw {seen}" +``` + +Rev.1/์ฑŒ๋ฆฐ์ €์•ˆ ๋Œ€๋น„ ์„ธ ๊ฐ€์ง€๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. + +| | Rev.1 ์›์•ˆ | ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ | **Rev.2** | +|---|---|---|---| +| ๊ฒ€์ฆ ๋‹จ์œ„ | ์ฝ”๋“œ ๋ธ”๋ก | ๋ช…๋ น | ๋ช…๋ น | +| ์Šค์บ” ๋ฒ”์œ„ | ํŽœ์Šค | **๋ฌธ์„œ ์ „์ฒด** | ํŽœ์Šค | +| ๊ฐœ์ˆ˜ ํ•˜ํ•œ | ์ „์—ญ `>= 4` (**์‹คํŒจ**) | ์ „์—ญ `>= 5` | **๋ฌธ์„œ๋ณ„** (3 / 2) | + +์ „์—ญ ์นด์šดํŠธ๋ฅผ ๋ฌธ์„œ๋ณ„๋กœ ์ชผ๊ฐ  ์ด์œ : ์ „์—ญ์ด๋ฉด SKILL.md ์˜ˆ์ œ 1๊ฐœ๊ฐ€ ์‚ฌ๋ผ์ ธ๋„ INSTALL.md ๊ฐ€ 6๊ฐœ๋ฉด ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ณ„ ํ•˜ํ•œ์€ ์‹คํŒจ๋ฅผ ๋ฐœ์ƒ ์ง€์ ์— ๊ตญ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. + +**์‹ค์ธก ํ™•์ธ** (ยง1.10.4): ยง4.4 ์ ์šฉ ํ›„ ์‚ฌ๋ณธ์—์„œ `SKILL.md checked=3 missing=0`, `INSTALL.md checked=2 missing=0`. + +### T6 โ€” ํšŒ๊ท€ ๋ฌด์˜ํ–ฅ ํ™•์ธ + +`bash -n`: `lib.sh`, `stop_session.sh`, `update_yaml_resumed.sh`, `create_session.sh`, `resume_session.sh`, `resolve_session_id.sh`. +`py_compile`: `lib_py/layout.py`. ์‹œ์Šคํ…œ ํŒŒ์ด์ฌ **3.9.6** ์ž„ํฌํŠธ ํ™•์ธ. + +### ํ…Œ์ŠคํŠธ ํŒŒ์ผ ์‚ฌ์ „ ์กฐ๊ฑด 2๊ฑด + +1. `test_tier2_component.py:296` ์˜ `FEATURE 3: Stop Session (4 Test Cases)` ์ฃผ์„ ๊ฐœ์ˆ˜ ๊ฐฑ์‹ (โ†’ 7). ๊ฐ™์€ ์ข…๋ฅ˜์˜ ๋“œ๋ฆฌํ”„ํŠธ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ์•Š๋„๋ก. +2. `test_tier2_component.py` ๋Š” `Path` ๋Š” ์ž„ํฌํŠธํ•˜์ง€๋งŒ **`re` ๋Š” ์ž„ํฌํŠธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**(`:1-10`). T4/T5 ๊ฐ€ `re` ๋ฅผ ์“ฐ๋ฏ€๋กœ `import re` ์ถ”๊ฐ€ ํ•„์š”. `test_layout.py` ๋Š” T1/T1b ๊ฐ€ ์“ฐ๋Š” `os/json/subprocess/sys` ๋ฅผ ๋ชจ๋‘ ์ด๋ฏธ ์ž„ํฌํŠธํ•˜๊ณ  ์žˆ์–ด ์ถ”๊ฐ€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 6. ๋ฎคํ…Œ์ด์…˜ ๋งคํŠธ๋ฆญ์Šค + +๊ฒฉ๋ฆฌ ์‚ฌ๋ณธ(`rsync`)์— ์ ์šฉํ•ด ์ง€์ • ํ…Œ์ŠคํŠธ๊ฐ€ **FAIL** ํ•˜๋Š”์ง€ ํ™•์ธ. + +| # | ๋ฎคํ…Œ์ด์…˜ | FAIL ํ•ด์•ผ ํ•˜๋Š” ํ…Œ์ŠคํŠธ | +|---|---|---| +| M1 | `stop_session.sh` ํด๋ฐฑ์„ ์˜› `case` ๋ธ”๋ก์œผ๋กœ ๋ณต์› | `test_comp_stop_agent_fallback_reads_pane_cmd` | +| M2 | ํ—ฌํผ์—์„œ `agent_of_row(row, โ€ฆ)` โ†’ `agent_of_row({}, session_name=name)` | ์œ„ + `โ€ฆprefers_explicit_agent_field` | +| M3 | ํ—ฌํผ์— `match_cmd=False` ์ถ”๊ฐ€ | `โ€ฆreads_pane_cmd` **๋งŒ** (๋‘ ํ…Œ์ŠคํŠธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์„ฑ์งˆ์„ ์žก์Œ์„ ์ฆ๋ช…) | +| M4 | `_env_int(โ€ฆ, default=60)` โ†’ `_env_int(โ€ฆ) or 60` | `test_j1_env_zero_min_cols_matches_flag_zero` | +| M5 | `_env_int` ์˜ `except ValueError: continue` โ†’ `return None` | `test_j1_nonzero_and_malformed_env_behaviour_unchanged` (rcโ‰ 0) | +| **M5b** | **[Rev.2]** `except ValueError: continue` โ†’ `return default` | `test_j1b_invalid_alias_does_not_shadow_the_documented_var` | +| M6 | ํ—ค๋“œ๋ฆฌ์Šค ํ™€์ˆ˜ ๋ถ„๊ธฐ์—๋„ `max_columns` ๊ฒ€์‚ฌ ์ถ”๊ฐ€ (๊ณผ์ž‰ ๊ต์ •) | `test_headless_max_columns_growth_guard` (์‹ ์„ค `d5` ๋‹จ์–ธ) | +| M7 | `SKILL.md` ์˜ˆ์ œ **ํ•œ ๊ณณ**์—์„œ `--agent` ์‚ญ์ œ | `test_comp_docs_stop_examples_pass_agent` | +| **M7b** | **[Rev.2]** `INSTALL.md` ์˜ **๋‘ ํ˜ธ์ถœ ์ค‘ ํ•˜๋‚˜**์—์„œ๋งŒ `--agent` ์‚ญ์ œ | ๋™์ผ (ยง1.10.2 ์—์„œ ์ด๋ฏธ ์„ ์‹ค์ธก: Rev.1 ์„ค๊ณ„๋Š” ๋ฏธ๊ฒ€์ถœ, Rev.2 ์„ค๊ณ„๋Š” `missing=1` ๊ฒ€์ถœ) | +| **M7c** | **[Rev.2]** `SKILL.md` ์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 1๊ฐœ๋ฅผ ํ†ต์งธ๋กœ ์‚ญ์ œ | ๋™์ผ (`seen >= 3` ํ•˜ํ•œ) | +| M8 | `update_yaml_resumed.sh` ํด๋ฐฑ์„ ์˜› `case` ๋ธ”๋ก์œผ๋กœ ๋ณต์› | โ€” **๊ฐ€๋“œ ์—†์Œ** | + +**M8 ์„ ์ •์งํ•˜๊ฒŒ ๋‚จ๊น๋‹ˆ๋‹ค.** `update_yaml_resumed.sh` ์˜ ํด๋ฐฑ์€ ์œ ์ผํ•œ ์ƒ์‚ฐ ํ˜ธ์ถœ์ž์ธ `resume_session.sh:66, :129` ๊ฐ€ ํ•ญ์ƒ `--agent "$AGENT"` ๋ฅผ ๋ช…์‹œํ•˜๋ฏ€๋กœ **๊ทธ ๊ฒฝ๋กœ์—์„œ ๋„๋‹ฌ ๋ถˆ๊ฐ€**ํ•ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ํ˜ธ์ถœ ์‹œ์—๋งŒ ์‚ด์•„๋‚ฉ๋‹ˆ๋‹ค. ๋„๋‹ฌ ๋ถˆ๊ฐ€ ๊ฒฝ๋กœ๋ฅผ ์œ„ํ•ด ๋ณ„๋„ ํ”ฝ์Šค์ฒ˜๋ฅผ ์„ธ์šฐ๋Š” ๋Œ€์‹  S3 ๋Š” "์ค‘๋ณต ์ œ๊ฑฐ"๋กœ ์ •๋‹นํ™”ํ•˜๊ณ  ๊ฐ€๋“œ ์—†์Œ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์ด ํŒ๋‹จ์— ์ด์˜๊ฐ€ ์žˆ์œผ๋ฉด T4 ์™€ ๋™ํ˜•์˜ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€๊ฐ€ ์˜ณ์€ ์ฒ˜๋ฐฉ์ž…๋‹ˆ๋‹ค. + +**M5 ์™€ M5b ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ๊นจ๋Š” ๊ฒƒ**์ด C-2 ๋ฐ˜์˜์˜ ๊ฒ€์ฆ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค. M5(=`None` ๋ณต๊ท€)๋Š” ํฌ๋ž˜์‹œ ๊ฒฝ๋กœ๋ฅผ, M5b(=Rev.1 ์•ˆ์œผ๋กœ ๋ณต๊ท€)๋Š” ๋ณ„์นญ ์„€๋„์ž‰์„ ๊ฐ๊ฐ ์žก์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์žกํžˆ์ง€ ์•Š์œผ๋ฉด T1b ๊ฐ€ ์˜๋ฏธ ์—†๋Š” ํ…Œ์ŠคํŠธ๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + +--- + +## 7. ์ปค๋ฐ‹ ๋ถ„ํ•  + +| # | ์ปค๋ฐ‹ | ๋‚ด์šฉ | +|---|---|---| +| 1 | `feat(lib,stop,resume): resolve --agent from the registry via agent_of_row (B-21)` | S1 + S2 + S3 + T3 + T4 | +| 2 | `docs(skills): standardize explicit --agent across stop/resume/create guides (B-21)` | S4 + T5 | +| 3 | `fix(layout): make _env_int take an explicit default and skip invalid values (J-1)` | S5 + T1 + T1b | +| 4 | `test(layout): cover the headless growth-guard threshold at n=5 (J-2)` | T2 | +| 5 | `docs(improvements): register J-1/J-2/B-21 and refresh the completed count` | S7 | +| 6 | `fix(scripts): repair the dead lib.sh sourcing path in stop/create/resume` | S8 (ยง1.9) | + +์ปค๋ฐ‹ 1~4 ๋Š” ๊ฐ๊ฐ ๋…๋ฆฝ revert ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ปค๋ฐ‹ 6 ์€ ยง1.9 ๊ฐ€ ๋ธŒ๋ฆฌํ”„ ๋ฒ”์œ„ ๋ฐ–์˜ ๋ณ„๊ฐœ ์‚ฌ์•ˆ์ด๋ฏ€๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค โ€” ๋ฆฌ๋ทฐ์–ด๊ฐ€ ๋ฒ”์œ„ ์ดํƒˆ๋กœ ํŒ๋‹จํ•˜๋ฉด ์ด ์ปค๋ฐ‹๋งŒ ๋“œ๋กญํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. + +์ปค๋ฐ‹ 3 ์˜ ์ œ๋ชฉ์ด Rev.1 ์—์„œ ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค(`โ€ฆ and skip invalid values` ์ถ”๊ฐ€). C-2 ๊ฐ€ J-1 ๊ณผ ๋‹ค๋ฅธ ์„ฑ์งˆ์˜ ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ์ œ๋ชฉ์ด ๊ทธ ์‚ฌ์‹ค์„ ๋‹ด์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 8. `IMPROVEMENTS.md` ๊ฐฑ์‹  (S7) + +J-1 / J-2 ๋Š” ํ˜„์žฌ `IMPROVEMENTS.md` ์— **๋“ฑ๋ก๋ผ ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค**(`55d1a1d9` ๋ฆฌ๋ทฐ ๋ณด๊ณ ์„œ์—๋งŒ ์กด์žฌ). + +**ID ์ถฉ๋Œ ๊ฒฝ๊ณ **: `IMPROVEMENTS.md:301` ์˜ `C-1`("Kanban ๋ฌธ์„œ 29ํšŒ ์–ธ๊ธ‰ vs ์‹ค์ œ ๊ตฌํ˜„ 0๊ฑด")๊ณผ `:37` ์ด ์ฐธ์กฐํ•˜๋Š” `C-1`(๋ ˆ์ด์•„์›ƒ ํ—ค๋“œ๋ฆฌ์Šค `max_columns`)์€ **์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ๊ณผ์ œ๊ฐ€ ๊ฐ™์€ ID** ๋ฅผ ์”๋‹ˆ๋‹ค. ์‹ ๊ทœ๋Š” `J-1`/`J-2`/`B-21` ์„ ์”๋‹ˆ๋‹ค. ๊ธฐ์กด ์ถฉ๋Œ์€ K-3. + +๊ฐฑ์‹  ํ•ญ๋ชฉ: + +1. `:3` ์ตœ์ข… ๊ฐฑ์‹ ์ผ +2. `:6` ์ด ์ถ”์  ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ ์นด์šดํŠธ +3. `:7` ์™„๋ฃŒ ๊ณผ์ œ **29 โ†’ 30** ๋ฐ ๋ชฉ๋ก์— `B-21` ์ถ”๊ฐ€ +4. `:37` B-20 ํ›„์† ์ •๋ฆฌ ์ค„์— J-1/J-2 ํ•ด์†Œ ํ•œ ์ค„ +5. ยง2 ์— `B-21` ์ ˆ ์‹ ์„ค โ€” ํ˜„์ƒ(๋ผ์ด๋ธŒ `agy-creator-01` ์ด `--agent` ์—†์ด `exit 2`), ์›์ธ(ํ•ด์„๊ธฐ 4์ค‘ํ™”), ์กฐ์น˜, ํšŒ๊ท€ ๊ฐ€๋“œ +6. ยง6.2 ๋กœ๋“œ๋งต ํ‘œ์— ์™„๋ฃŒ ํ–‰ +7. ยง6.3 ํŒŒ์ผ ์†Œ์œ ๊ถŒ ์Šฌ๋กฏ ํ‘œ ๊ฐฑ์‹  + +--- + +## 9. ํ›„์† ๋ฐฑ๋กœ๊ทธ (์ด๋ฒˆ ๋ฒ”์œ„ ๋ฐ–, ๋“ฑ๋ก๋งŒ) + +| ID | ๋‚ด์šฉ | ๊ทผ๊ฑฐ | +|---|---|---| +| **K-1** | `run_loop.sh:278` `resolve_agent_type` ํ†ตํ•ฉ | ยง1.1 โ€” ํ•ด์„ ์‹คํŒจ๋ฅผ `claude` ๋กœ ํก์ˆ˜. cline ์„ธ์…˜์— claude ์ข…๋ฃŒํ‚ค๋ฅผ ๋ณด๋‚ด๋Š” ์˜ค๋ถ„๋ฅ˜๊ฐ€ ๊ตฌ์กฐ์ ์œผ๋กœ ๊ฐ€๋Šฅ. ํ˜ธ์ถœ 12๊ณณ์ด๋ผ ๋ณ„๋„ ๊ณ„ํš ํ•„์š” | +| **K-2** | `compute_2xk_layout` ์˜ `if max_columns and โ€ฆ` falsy-zero | `--max-cols 0`("์—ด 0๊ฐœ")์ด "์ƒํ•œ ์—†์Œ"์œผ๋กœ ํก์ˆ˜๋จ. J-1 ๊ณผ ๋™์ผ ๋ถ€๋ฅ˜ | +| **K-3** | `IMPROVEMENTS.md` ์˜ `C-1` ID ์ถฉ๋Œ ์ •๋ฆฌ | ยง8 | +| **K-4** | `agent_of_row` ์— ํ•˜์ดํ”ˆ ์„ธ๊ทธ๋จผํŠธ ๋งค์นญ ์ถ”๊ฐ€ ์—ฌ๋ถ€ | `orc-hermes-main` ๋ฅ˜ ๋ฏธํ•ด๊ฒฐ. `reconcile.sh` ์ž…์–‘ ํŒ์ • ์˜ํ–ฅ โ†’ ์‹ค์ธก ์„ ํ–‰ | +| **K-5** | **[Rev.2 ์‹ ๊ทœ]** `_env_int` ํ›„๋ณด **์ˆœ์„œ** ์žฌ๊ฒ€ํ†  | ยง1.11.1 โ€” ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ ๋ ˆ๊ฑฐ์‹œ `MAM_MIN_COLS` ๊ฐ€ `.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ `MAM_MIN_PANE_COLS` ๋ณด๋‹ค **์šฐ์„ **ํ•ฉ๋‹ˆ๋‹ค. C-2(๋ฌดํšจ๊ฐ’ ๊ฑด๋„ˆ๋›ฐ๊ธฐ)๋Š” ์ด ์ˆœ์„œ ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•  ๋ฟ ํ•ด์†Œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์œ ํšจํ•œ ๊ฐ’์ด๋ฉด ์—ฌ์ „ํžˆ ๋ ˆ๊ฑฐ์‹œ๊ฐ€ ์ด๊น๋‹ˆ๋‹ค. ์ˆœ์„œ ๋ณ€๊ฒฝ์€ ํ–‰๋™ ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ณ„๋„ ํ•ญ๋ชฉ | + +--- + +## 10. ๊ฒ€์ฆ ์ ˆ์ฐจ (Creator ์‹คํ–‰) + +```bash +# 1) ๊ตฌ๋ฌธ +for f in .agents/skills/lib.sh \ + .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ + .agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh \ + .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh \ + .agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh \ + .agents/skills/multi-agent-mux-create/scripts/create_session.sh; do + bash -n "$f" || echo "FAIL $f" +done +python3 -m py_compile .agents/skills/lib_py/layout.py + +# 2) J-1 ์ง์ ‘ ํ™•์ธ (0 ์ด ์‚ด์•„๋‚จ๋Š”๊ฐ€) +P='{"result":{"panes":[{"pane_id":"p1","rect":{"x":0,"y":0,"width":50,"height":30}}]}}' +printf '%s' "$P" | PYTHONPATH=.agents/skills MAM_MIN_PANE_COLS=0 python3 -m lib_py.layout --json +printf '%s' "$P" | PYTHONPATH=.agents/skills python3 -m lib_py.layout --json --min-cols 0 +# โ†’ ๋‘ ์ถœ๋ ฅ์ด ์™„์ „ํžˆ ๋™์ผํ•˜๊ณ  direction=right + +# 3) C-2 ์ง์ ‘ ํ™•์ธ (๋ฌดํšจ ๋ณ„์นญ์ด ๋ฌธ์„œํ™”๋œ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋Š”๊ฐ€) +printf '%s' "$P" | PYTHONPATH=.agents/skills \ + env MAM_MIN_COLS=foo MAM_MIN_PANE_COLS=25 python3 -m lib_py.layout --json +# โ†’ direction=right (์ˆ˜์ • ์ „์—๋Š” overflow) + +# 4) ์ „์ฒด ์Šค์œ„ํŠธ (๋ฒ ์ด์Šค๋ผ์ธ 333 โ†’ ๊ธฐ๋Œ€ 341) +.venv/bin/python -m pytest tests/ -q + +# 5) ๋ฐฐํฌ ์‹ ์„ ๋„ (๊ธฐ์กด 31๊ฑด ์œ ์ง€) +.venv/bin/python -m pytest tests/test_deploy_freshness.py -q + +# 6) ๋ฎคํ…Œ์ด์…˜ M1~M7c (๊ฒฉ๋ฆฌ ์‚ฌ๋ณธ์—์„œ) +``` + +**๊ธˆ์ง€ ์‚ฌํ•ญ**: `tests/test_tier1_unit.py:142` ์™€ `tests/test_tier3_integration.py:398` ์€ **์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ๋‘ ๊ฑด์ด ๋ฌด์ˆ˜์ •์œผ๋กœ PASS ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ B ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ๋ณด์กด์„ ์ž…์ฆํ•˜๋Š” ์ฆ๊ฑฐ์ž…๋‹ˆ๋‹ค. ๊ณ ์ณ์•ผ ํ†ต๊ณผํ•œ๋‹ค๋ฉด ๊ตฌํ˜„์ด ์•ˆ A ๋กœ ํ˜๋Ÿฌ๊ฐ„ ๊ฒƒ์ด๋ฏ€๋กœ ๋˜๋Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +**๋ผ์ด๋ธŒ ์„ธ์…˜ ๋ณดํ˜ธ**: T4 ๋Š” `mam_sandbox` ์•ˆ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋ฉฐ ์‹ค์ œ `.mam/agent-sessions.yaml` ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์ค‘ `stop_session.sh` ๋ฅผ ์‹ค ์›Œํฌ์ŠคํŽ˜์ด์Šค์—์„œ ์ˆ˜๋™ ์‹คํ–‰ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค โ€” `canary-projects-multi-agent-mux-creator-claude` ๊ฐ€ ์ด ์„ธ์…˜์ž…๋‹ˆ๋‹ค. + +--- + +## 11. ๊ทœ๋ชจ ์ถ”์ • + +| ํŒŒ์ผ | ๋ณ€๊ฒฝ | +|---|---| +| `.agents/skills/lib.sh` | +22 (ํ—ฌํผ 1๊ฐœ) | +| `stop_session.sh` | +8 / โˆ’10, ํ—ค๋”ยทusage +6 | +| `update_yaml_resumed.sh` | +8 / โˆ’9, ํ—ค๋”ยทusage +2 | +| `lib_py/layout.py` | +10 / โˆ’6 (docstring ํ™•์žฅ ํฌํ•จ) | +| `multi-agent-mux-stop/SKILL.md` | +12 | +| `multi-agent-mux-resume/SKILL.md` | +1 / โˆ’1 | +| `multi-agent-mux-create/SKILL.md` | +2 / โˆ’2 | +| `create_session.sh` / `resolve_session_id.sh` | ํ—ค๋” ๊ฐ +1 / โˆ’1 | +| `tests/test_layout.py` | +62 (T1 45 + T1b 17) | +| `tests/test_a4_adapter_contract.py` | +9 | +| `tests/test_tier2_component.py` | +58 | +| `IMPROVEMENTS.md` | +20 | +| (์ปค๋ฐ‹ 6) 3๊ฐœ ์Šคํฌ๋ฆฝํŠธ ์†Œ์‹ฑ ์ค„ | +3 / โˆ’3 | + +์ด **์•ฝ +215 / โˆ’35 ์ค„**, ํŒŒ์ผ 12๊ฐœ. ๊ทœ๋ชจ **์†Œ~์ค‘**. + +--- + +## 12. ์ฑŒ๋ฆฐ์ €์—๊ฒŒ + +C-1 ์€ ๊ณ„ํš๋Œ€๋กœ ์งœ๋ฉด ํ™•์ • ์‹คํŒจํ•˜๋Š” ๊ฒฐํ•จ์ด์—ˆ๊ณ , ๋‘ ๊ฐˆ๋ž˜ ๋ชจ๋‘ ์ •ํ™•ํ–ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ๊ฐˆ๋ž˜ โ‘ก(๋ถ€๋ถ„ ๋ฌธ์ž์—ด ์œ„์–‘์„ฑ)๋Š” **ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๋„ ๋ˆˆ์น˜์ฑ„์ง€ ๋ชปํ•˜๋Š”** ์ข…๋ฅ˜๋ผ ๋” ๊ฐ’์–ด์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ยง1.10.2 ์—์„œ ๋ฎคํ…Œ์ด์…˜์œผ๋กœ ์žฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +์ˆ˜์ •์•ˆ์„ ๊ทธ๋Œ€๋กœ ์ฑ„ํƒํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„์€ ํ•œ ๊ณณ์ž…๋‹ˆ๋‹ค โ€” ๋ฌธ์„œ ์ „์ฒด ์Šค์บ”์ด ์‚ฐ๋ฌธ ์† ํŒŒ์ผ๋ช… ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•ฉ๋‹ˆ๋‹ค(ยง1.10.3, ์œ„์–‘์„ฑ 2๊ฑด ์‹ค์ธก). ์ด ๊ณ„ํš ยง4.4 ์ž์ฒด๊ฐ€ SKILL.md ์— ์‚ฐ๋ฌธ์„ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ ์ž„๋ฐ•ํ•œ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. ์ปค๋งจ๋“œ ๋‹จ์œ„๋ผ๋Š” **ํ•ต์‹ฌ ๊ต์ •์€ ๊ทธ๋Œ€๋กœ ์ฑ„ํƒ**ํ•˜๊ณ  ํŽœ์Šค ์Šค์ฝ”ํ”„๋ฅผ ์–น์—ˆ์Šต๋‹ˆ๋‹ค. + +C-2 ๋Š” ์ œ๊ธฐํ•˜์‹  ๊ทผ๊ฑฐ(๋‹ค์ค‘ fallback ์ทจ์ง€)๋ณด๋‹ค ๊ฐ•ํ•œ ๊ทผ๊ฑฐ๊ฐ€ ์‹ค์ธก์—์„œ ๋‚˜์™”์Šต๋‹ˆ๋‹ค. `MAM_MIN_COLS` ๋Š” `layout.py` ๋ฐ– ์–ด๋””์—๋„ ์—†๋Š” ๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ์ด๊ณ , ๊ฐ€๋ ค์ง€๋Š” `MAM_MIN_PANE_COLS` ๋Š” `.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ **์œ ์ผํ•œ** ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ํ˜„ํ–‰ ์ฝ”๋“œ๋Š” `""` ๋Š” ๊ฑด๋„ˆ๋›ฐ๊ณ  `"foo"` ๋Š” ํƒˆ์ถœํ•˜๋Š” ๋น„๋Œ€์นญ์„ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์šฉํ•˜๊ณ  ์ „์šฉ ํ…Œ์ŠคํŠธ T1b ๋ฅผ ์‹ ์„คํ–ˆ์Šต๋‹ˆ๋‹ค. diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-dcbdf349.md b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-dcbdf349.md new file mode 100644 index 0000000..270b5aa --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-dcbdf349.md @@ -0,0 +1,176 @@ +# ๐Ÿ” ๊ต์ฐจ ์ฝ”๋“œ ๋ฆฌ๋ทฐ โ€” Job `dcbdf349` + +- **์—ญํ• **: Reviewer +- **๋Œ€์ƒ**: Rev.2 ๊ณ„ํš์„œ(`d6f54b7f`) ๊ตฌํ˜„๋ถ„ โ€” ์›Œํ‚น ํŠธ๋ฆฌ ์ˆ˜์ • 14ํŒŒ์ผ (`+255 / โˆ’46`) +- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `14e306b` / ๋ฏธ์ถ”์  ํŒŒ์ผ 0๊ฑด + +--- + +## 1. ๊ฒฐ๋ก  + +๊ณ„ํš์„œ Rev.2 ์˜ 8๊ฐœ ์ž‘์—…ํ•ญ๋ชฉ(S1~S8)์ด **์ „๋ถ€ ์‚ฌ์–‘๋Œ€๋กœ** ๊ตฌํ˜„๋์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์Šค์œ„ํŠธ **341 passed / 425.16s / exit 0** ์œผ๋กœ ๊ณ„ํš์ด ์˜ˆ์ธกํ•œ ์ˆ˜์น˜์™€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๊ณ , ๋ฎคํ…Œ์ด์…˜ **10์ข… ์ „๋ถ€ ์ง€์ • ํ…Œ์ŠคํŠธ๋ฅผ FAIL** ์‹œํ‚ต๋‹ˆ๋‹ค. + +์ฐจ๋‹จ ์‚ฌ์œ  ์—†์Œ. P3 ๊ด€์ฐฐ 3๊ฑด๋งŒ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 2. ๊ฒ€์ฆ ๊ฒฐ๊ณผ ์š”์•ฝ + +| ๊ฒ€์ฆ | ๊ฒฐ๊ณผ | +|---|---| +| ์ „์ฒด ์Šค์œ„ํŠธ | **341 passed, 425.16s, exit 0** (๊ณ„ํš ์˜ˆ์ธก 341 ๊ณผ ์ผ์น˜) | +| ๋ฐฐํฌ ์‹ ์„ ๋„ ๋‹จ๋… | **31 passed** (๊ธฐ์กด ์œ ์ง€) | +| `bash -n` 6๊ฐœ ์Šคํฌ๋ฆฝํŠธ | 6/6 OK | +| `py_compile` (์‹œ์Šคํ…œ Python **3.9.6**) | OK | +| ์ˆ˜์ง‘ ์ˆ˜ | 333 โ†’ **341** (์‹ ์„ค 8๊ฑด) | +| `test_layout.py` | 19 โ†’ **23** (IMPROVEMENTS.md ๊ธฐ์žฌ์น˜์™€ ์ผ์น˜) | +| **๋ณดํ˜ธ ๋Œ€์ƒ 2ํŒŒ์ผ ๋ฌด์ˆ˜์ •** | `test_tier1_unit.py` / `test_tier3_integration.py` โ€” `git diff --stat` ์ถœ๋ ฅ **0์ค„** | +| ๋ฎคํ…Œ์ด์…˜ | **10/10 ๊ฒ€์ถœ** | + +--- + +## 3. ๋ฎคํ…Œ์ด์…˜ ๋งคํŠธ๋ฆญ์Šค โ€” 10/10 ๊ฒ€์ถœ + +๊ฒฉ๋ฆฌ `rsync` ์‚ฌ๋ณธ์—์„œ ์‹คํ–‰. ๋ฌด๋ฎคํ…Œ์ด์…˜ ๋Œ€์กฐ๊ตฐ์€ ๋Œ€์ƒ 7๊ฑด ์ „๊ฑด ํ†ต๊ณผ(`7 passed in 2.73s`). + +| # | ๋ฎคํ…Œ์ด์…˜ | ๊ฒฐ๊ณผ | +|---|---|---| +| M1 | `stop_session.sh` ํด๋ฐฑ์„ ์˜› `case` ๋ธ”๋ก์œผ๋กœ ๋ณต์› | `โ€ฆreads_pane_cmd` **FAILED** | +| M2 | ํ—ฌํผ์—์„œ row ํ๊ธฐ (`agent_of_row({}, โ€ฆ)`) | **2๊ฑด ๋ชจ๋‘ FAILED** | +| M3 | ํ—ฌํผ์— `match_cmd=False` | `โ€ฆreads_pane_cmd` **FAILED** / `โ€ฆprefers_explicit_agent_field` PASSED | +| M4 | `default=60` โ†’ `โ€ฆ or 60` | `test_j1_env_zero_min_colsโ€ฆ` **FAILED** | +| M5 | `except ValueError: continue` โ†’ `return None` | **2๊ฑด ๋ชจ๋‘ FAILED** | +| M5b | `continue` โ†’ `return default` (Rev.1 ์•ˆ์œผ๋กœ ๋ณต๊ท€) | `test_j1bโ€ฆ` **FAILED** / `โ€ฆmalformed_env_behaviour_unchanged` PASSED | +| M6 | ํ—ค๋“œ๋ฆฌ์Šค ํ™€์ˆ˜ ๋ถ„๊ธฐ์—๋„ ์ƒํ•œ ๊ฒ€์‚ฌ ์ถ”๊ฐ€ | `test_headless_max_columns_growth_guard` **FAILED** | +| M7 | SKILL.md ์˜ˆ์ œ 1๊ณณ์—์„œ `--agent` ์‚ญ์ œ | ๋ฌธ์„œ ๊ฐ€๋“œ **FAILED** | +| M7b | INSTALL.md **๋‘ ํ˜ธ์ถœ ์ค‘ ํ•˜๋‚˜๋งŒ** `--agent` ์‚ญ์ œ | ๋ฌธ์„œ ๊ฐ€๋“œ **FAILED** | +| M7c | SKILL.md ์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 1๊ฐœ ํ†ต์งธ ์‚ญ์ œ | ๋ฌธ์„œ ๊ฐ€๋“œ **FAILED** | + +### ๊ฐ’์–ด์น˜ ์žˆ๋Š” ์„ธ ๊ฐ€์ง€ + +**M3 ์ด ์ •ํ™•ํžˆ ํ•˜๋‚˜๋งŒ ๊นฌ๋‹ค.** ๋‘ T4 ํ…Œ์ŠคํŠธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์„ฑ์งˆ์„ ์žก๋Š”๋‹ค๋Š” ๊ฒƒ์ด ์‹ค์ฆ๋์Šต๋‹ˆ๋‹ค. `โ€ฆreads_pane_cmd` ํ•˜๋‚˜๋งŒ ์žˆ์—ˆ๋‹ค๋ฉด `pane.cmd` ๋ฅผ ์ง์ ‘ ๊ธ๋Š” ์–•์€ ๊ตฌํ˜„๋„ ํ†ต๊ณผํ–ˆ์„ ๊ฒƒ์ด๊ณ , `โ€ฆprefers_explicit_agent_field` ๊ฐ€ ๊ทธ ๊ตฌํ˜„์„ ๋ฐฐ์ œํ•ฉ๋‹ˆ๋‹ค. + +**M5 ์™€ M5b ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ๊นฌ๋‹ค.** C-2 ๋ฐ˜์˜์˜ ๊ฒ€์ฆ ์กฐ๊ฑด์ด ๊ทธ๋Œ€๋กœ ์„ฑ๋ฆฝํ–ˆ์Šต๋‹ˆ๋‹ค โ€” M5(`None` ๋ณต๊ท€)๋Š” ํฌ๋ž˜์‹œ ๊ฒฝ๋กœ๋ฅผ, M5b(Rev.1 ์•ˆ ๋ณต๊ท€)๋Š” ๋ณ„์นญ ์„€๋„์ž‰์„ ๊ฐ๊ฐ ์žก์Šต๋‹ˆ๋‹ค. ๋‘˜ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์žกํžˆ์ง€ ์•Š์•˜๋‹ค๋ฉด `T1b` ๋Š” ์žฅ์‹์ด์—ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +**M7b / M7c ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์‚ฌ์œ ๋กœ ๊นจ์ง„๋‹ค.** ๋ฌธ์„œ ๊ฐ€๋“œ์˜ ๋‘ ๋…๋ฆฝ ๊ธฐ์ œ๊ฐ€ ๊ฐ๊ฐ ์‚ด์•„ ์žˆ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. + +``` +[M7b] AssertionError: INSTALL.md: stop_session.sh example without --agent: โ† ์ปค๋งจ๋“œ ๋‹จ์œ„ ๊ฒ€์‚ฌ +[M7c] AssertionError: SKILL.md: expected >= 3 examples, saw 2 โ† ๋ฌธ์„œ๋ณ„ ๊ฐœ์ˆ˜ ํ•˜ํ•œ +``` + +M7b ๋Š” Rev.1 ์›์•ˆ(๋ธ”๋ก ๋‹จ์œ„)์ด **๋†“์ณค๋˜** ๋ฐ”๋กœ ๊ทธ ์ผ€์ด์Šค์ž…๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ € `9f85218e` ์˜ ์ง€์ ์ด ์‹ค๋ฌผ ๊ฐ€๋“œ์—์„œ ๊ฐ’์–ด์น˜๋ฅผ ์ฆ๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค. + +--- + +## 4. ๋™์ž‘ ์‹ค์ธก + +### 4.1 ํ•ต์‹ฌ ๊ฒฐํ•จ โ€” ๋ผ์ด๋ธŒ ์„ธ์…˜ ํ•ด์„ + +``` +agy-creator-01 -> agy +canary-projects-multi-agent-mux-creator-cline -> cline +bad-session-name -> +``` + +`--agent` ์—†์ด `exit 2` ๋กœ ๊ฑฐ๋ถ€๋˜๋˜ ์‹ค์ œ running ์„ธ์…˜ `agy-creator-01` ์ด `pane.cmd` ๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. ๋™์‹œ์— `bad-session-name` ์€ rc=1 ๋กœ ์‹คํŒจํ•ด ํ˜ธ์ถœ์ž์˜ `exit 2` ๊ณ„์•ฝ์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. + +๊ณ„์•ฝ ํ…Œ์ŠคํŠธ ์ง์ ‘ ํ™•์ธ: `test_stop_session_invalid_agent_suffix` **PASSED** (๋ฌด์ˆ˜์ • ์ƒํƒœ). ๊ณ„ํš ยง3 ์•ˆ B ์˜ "๊ธฐ์กด ํ…Œ์ŠคํŠธ๋ฅผ ํ•œ ์ค„๋„ ์•ˆ ๊ณ ์น˜๊ณ  ๊ฒฐํ•จ๋งŒ ์ œ๊ฑฐ" ๋ผ๋Š” ์ˆ˜์šฉ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋์Šต๋‹ˆ๋‹ค. + +### 4.2 J-1 / C-2 + +``` +MAM_MIN_PANE_COLS=0 : {"direction": "right", "reason": "single_pane_height_constrained"} +--min-cols 0 : {"direction": "right", "reason": "single_pane_height_constrained"} โ† ๋™์น˜ +MAM_MIN_COLS=foo +PANE_COLS=25 : {"direction": "right", "reason": "single_pane_height_constrained"} โ† C-2 +MAM_MIN_COLS=foo +PANE_COLS=bar : {"direction": "overflow", "reason": "single_pane_overflow"} โ† ๋ถˆ๋ณ€์‹ ๋ณด์กด +baseline : {"direction": "overflow", "reason": "single_pane_overflow"} +``` + +3ํ–‰์ด C-2 ์ˆ˜์ •(๋ฌดํšจ ๋ณ„์นญ์ด ๋ฌธ์„œํ™”๋œ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š์Œ), 4ํ–‰์ด Rev.1 ๋ถˆ๋ณ€์‹ ๋ณด์กด(๋ชจ๋“  ํ›„๋ณด ๋ฌดํšจ โ†’ ๋ฌธ์„œํ™”๋œ ๊ธฐ๋ณธ๊ฐ’)์ž…๋‹ˆ๋‹ค. ๋‘ ์„ฑ์งˆ์ด ํ•œ ๊ตฌํ˜„์— ๊ณต์กดํ•ฉ๋‹ˆ๋‹ค. + +### 4.3 ์‹คํŒจ ๊ฒฝ๋กœ โ€” `set -euo pipefail` ํ•˜ ์•ˆ์ „์„ฑ + +`PYTHONPATH` ๋ฅผ ํŒŒ์†์‹œํ‚จ ์ƒํƒœ์—์„œ: + +``` +rc-guarded, AGENT=[] (๋นˆ ๊ฐ’์ด๋ฉด ํ˜ธ์ถœ์ž๊ฐ€ exit 2 ๋กœ ์ฒ˜๋ฆฌ) +stderr ์ฒซ ์ค„: Traceback (most recent call last): +``` + +`AGENT="$(...)" || AGENT=""` ๊ฐ€ `set -e` ์กฐ๊ธฐ ์ข…๋ฃŒ๋ฅผ ๋ง‰๊ณ , ๊ณ„ํš๋Œ€๋กœ **stderr ๋ฅผ ์–ต์ œํ•˜์ง€ ์•Š์•„** traceback ์ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ง„์งœ ์˜ค๋ฅ˜์™€ ์ •์ƒ ํ•ด์„ ์‹คํŒจ๊ฐ€ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ๋ช…์‹œ `--agent bogus` ๊ฒ€์ฆ๋„ ๊ทธ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค(`invalid agent type 'bogus'`). + +### 4.4 S8 ์†Œ์‹ฑ ๊ฒฝ๋กœ ๋ณต๊ตฌ โ€” ์‹คํšจ ํ™•์ธ + +๊ณ„ํš์ด "๋“œ๋กญ ๊ฐ€๋Šฅํ•œ ๋ณ„๋„ ์ปค๋ฐ‹" ์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ๋˜ ํ•ญ๋ชฉ์ด๋ผ, ์‹ค์ œ ํšจ๊ณผ๊ฐ€ ์žˆ๋Š”์ง€ ๋˜๋Œ๋ ค ๋ดค์Šต๋‹ˆ๋‹ค. + +``` +[๋˜๋Œ๋ฆฐ ์‚ฌ๋ณธ] / ์—์„œ WORKSPACE_ROOT ์—†์ด ์‹คํ–‰ + โ†’ stop_session.sh: line 39: //.agents/skills/lib.sh: No such file or directory + +[ํ˜„ํ–‰] / ์—์„œ WORKSPACE_ROOT ์—†์ด ์‹คํ–‰ + โ†’ Usage: ... --session [--agent claude|agy|hermes|cline] ... +``` + +์žฅ์‹์ด ์•„๋‹ˆ๋ผ ์‹ค์ œ ์žฅ์• ๋ฅผ ๋‹ซ์Šต๋‹ˆ๋‹ค. `cd` ๊ฐ€ **์„ฑ๊ณต**ํ•  ๋•Œ ๋นˆ ๋ฌธ์ž์—ด์ด ๋˜๋˜ ๊ฒฐํ•จ์ด๋ผ 1์ฐจ ์†Œ์‹ฑ ๊ฒฝ๋กœ๊ฐ€ 100% ์ฃฝ์–ด ์žˆ์—ˆ๊ณ , ์ด์ œ ์‚ด์•„๋‚ฌ์Šต๋‹ˆ๋‹ค. + +--- + +## 5. ๊ด€์ฐฐ ์‚ฌํ•ญ (P3 โ€” ์ „๋ถ€ ๋น„์ฐจ๋‹จ) + +### ๐ŸŸก O-1: S8 ์†Œ์‹ฑ ๋ณต๊ตฌ์— ํšŒ๊ท€ ๊ฐ€๋“œ๊ฐ€ ์—†๋‹ค + +๋˜๋Œ๋ฆฐ ์‚ฌ๋ณธ์— tier1+tier2+tier3 ์ „์ฒด๋ฅผ ๋Œ๋ฆฐ ๊ฒฐ๊ณผ **78 passed** โ€” ์•„๋ฌด ํ…Œ์ŠคํŠธ๋„ ์žก์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ `WORKSPACE_ROOT` ๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์ €์žฅ์†Œ ๋ฃจํŠธ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ `:36` ํด๋ฐฑ์ด ํ•ญ์ƒ ์„ฑ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +๊ณ„ํš์ด M8(`update_yaml_resumed.sh` ํด๋ฐฑ ๋ฌด๊ฐ€๋“œ)์„ ์ •์งํ•˜๊ฒŒ ๋‚จ๊ธด ๊ฒƒ๊ณผ ๊ฐ™์€ ์„ฑ๊ฒฉ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ S8 ์€ **๋„๋‹ฌ ๋ถˆ๊ฐ€ ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹ˆ๋ผ ์‹ค์ธก๋œ ์‹ค๋™์ž‘ ๊ฒฐํ•จ**(ยง4.4)์„ ๊ณ ์นœ ๊ฒƒ์ด๋ฏ€๋กœ M8 ๋ณด๋‹ค ๊ฐ€๋“œ ๋ถ€์žฌ์˜ ๋ฌด๊ฒŒ๊ฐ€ ํฝ๋‹ˆ๋‹ค. ์ฒ˜๋ฐฉ์€ ํ•œ ์ค„์ž…๋‹ˆ๋‹ค โ€” ์ €์žฅ์†Œ ๋ฐ– cwd + `WORKSPACE_ROOT` ๋ฏธ์„ค์ •์œผ๋กœ `--help` ๋ฅผ ์‹คํ–‰ํ•ด rc=0 ์„ ๋‹จ์–ธ. + +์ฐจ๋‹จํ•˜์ง€ ์•Š๋Š” ์ด์œ : ๋ณ€๊ฒฝ ์ž์ฒด๊ฐ€ ์ˆœ์ˆ˜ ๊ฐœ์„ ์ด๊ณ (๋˜๋Œ๋ฆฌ๋ฉด ๋ช…๋ฐฑํžˆ ์‹คํŒจ), ๊ณ„ํš์ด ์ด ์ปค๋ฐ‹์„ ๋ถ„๋ฆฌ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค๊ณ„ํ–ˆ์œผ๋ฉฐ, ๊ฐ€๋“œ ๋ถ€์žฌ๊ฐ€ ๋‹ค๋ฅธ ์–ด๋–ค ๊ฒƒ๋„ ๊ฐ€๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +### ๐ŸŸก O-2: `create/SKILL.md` ์Šค๋‹ˆํŽซ์ด ์ž๊ธฐ๋ชจ์ˆœ ์ƒํƒœ๊ฐ€ ๋๋‹ค + +```bash + agy) + herdr new-session ... "agy --dangerously-skip-permissions" + ;; + *) echo "ERROR: --agent must be claude, agy, hermes or cline, got: $AGENT"; exit 2 ;; +``` + +์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” 4์ข…์„ ํ—ˆ์šฉํ•œ๋‹ค๊ณ  ๊ด‘๊ณ ํ•˜๋Š”๋ฐ `case` arm ์€ `claude`/`agy` ๋‘˜๋ฟ์ž…๋‹ˆ๋‹ค. ์Šค๋‹ˆํŽซ์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผ `--agent hermes` ๋ฅผ ์ฃผ๋ฉด `*)` ๋กœ ๋–จ์–ด์ ธ "hermes ๋Š” ํ—ˆ์šฉ๋œ๋‹ค" ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋‚ด๋ฉฐ ์ฃฝ์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ **์ „์—๋Š”** ๋ฉ”์‹œ์ง€์™€ ๊ตฌํ˜„์ด (๋‘˜ ๋‹ค 2์ข…์œผ๋กœ) ์ผ์น˜ํ–ˆ์œผ๋ฏ€๋กœ, ์ด ํ•œ ์Šค๋‹ˆํŽซ์˜ ๋‚ด๋ถ€ ์ •ํ•ฉ์„ฑ์€ ์˜คํžˆ๋ ค ๋‚˜๋น ์กŒ์Šต๋‹ˆ๋‹ค. + +์‹ค๋ฌผ `create_session.sh:187` ์€ `agy|hermes|cline)` ๋กœ 4์ข…์„ ์ •์ƒ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ **์ƒ์‚ฐ ์ฝ”๋“œ์—๋Š” ๊ฒฐํ•จ์ด ์—†์Šต๋‹ˆ๋‹ค**. ๊ณ„ํš ยง4.4 ๋Š” ์ด ์ง€์ ์— ๋Œ€ํ•ด "์Šค๋‹ˆํŽซ์„ ์ถ•์•ฝํ•˜๊ณ  ์‹ค๋ฌผ์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•˜๋Š” ์ชฝ์„ ๊ถŒ์žฅ" ํ–ˆ๊ณ  Creator ๋Š” ๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์ชฝ์„ ๊ณจ๋ž๋Š”๋ฐ, ๊ทธ ์„ ํƒ์ด ๊ณ„ํš์ด ์ถ•์•ฝ์„ ๊ถŒํ•œ ์ด์œ ๋ฅผ ๊ทธ๋Œ€๋กœ ๋“œ๋Ÿฌ๋ƒˆ์Šต๋‹ˆ๋‹ค. `agy)` โ†’ `agy|hermes|cline)` ํ•œ ๊ธ€์ž ์ˆ˜์ •์ด๋ฉด ์ •ํ•ฉํ•ด์ง‘๋‹ˆ๋‹ค. + +### ๐ŸŸก O-3: `update_yaml_resumed.sh:45` ์ฃผ์„์˜ ๋ผ์ธ ์ฐธ์กฐ๊ฐ€ ๋‚จ์˜ ๊ฒƒ + +```bash +# ์ข…์ „๊ณผ ๋™์ผํ•˜๊ฒŒ exit 2 (ํ—ค๋” :27-30 ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ์œ ์ง€). +``` + +`:27-30` ์€ `stop_session.sh` ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ํ—ค๋” ์œ„์น˜์ž…๋‹ˆ๋‹ค. `update_yaml_resumed.sh` ์˜ `:27-30` ์€ ์ธ์ž ํŒŒ์„œ์ด๊ณ , ์ด ํŒŒ์ผ์—๋Š” ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ํ—ค๋” ์ž์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‘ ์Šคํฌ๋ฆฝํŠธ์— ๊ฐ™์€ ๋ธ”๋ก์„ ์‹ฌ์œผ๋ฉด์„œ ๋”ฐ๋ผ์˜จ ๋ณต์‚ฌ ํ”์ ์ž…๋‹ˆ๋‹ค. ๋™์ž‘ ๋ฌด์˜ํ–ฅ. + +--- + +## 6. ๊ณ„ํš ๋Œ€๋น„ ์ดํ–‰ ์ ๊ฒ€ + +| ํ•ญ๋ชฉ | ์ดํ–‰ | +|---|---| +| S1 `resolve_agent_type_from_registry()` | โœ… ๊ณ„ํš ์ฝ”๋“œ ๊ทธ๋Œ€๋กœ. ์ด๋ฆ„ ๋ถ„๋ฆฌ(run_loop ๋™๋ช… ํ•จ์ˆ˜ ์„€๋„์ž‰ ํšŒํ”ผ) ๋ฐ˜์˜ | +| S2 `stop_session.sh` | โœ… `:101` ์œ„์น˜ ์œ ์ง€, ๋ฉ”์‹œ์ง€ยท์ข…๋ฃŒ ์ฝ”๋“œ ๋ถˆ๋ณ€, ํ—ค๋”/`usage()` ๊ฐฑ์‹  | +| S3 `update_yaml_resumed.sh` | โœ… ๋™์ผ ๋ธ”๋ก + 4์ข… ํ‘œ๊ธฐ | +| S4 ๋ฌธ์„œ ๋™๊ธฐํ™” | โœ… SKILL.md 3์ข… + ์Šคํฌ๋ฆฝํŠธ ํ—ค๋” 2์ข… | +| S5 J-1 + C-2 | โœ… `default=` ์ธ์žํ™” + `continue`, docstring ์ด ๋‘ ๊ฒฐ์ • ๊ทผ๊ฑฐ๋ฅผ ๋ชจ๋‘ ๊ธฐ์ˆ  | +| S6 ํ…Œ์ŠคํŠธ 8๊ฑด + J-2 ๋ณด๊ฐ• | โœ… 341 collected | +| S7 `IMPROVEMENTS.md` | โœ… B-21 ์ ˆ ์‹ ์„ค, ์™„๋ฃŒ 29โ†’30, ํ…Œ์ŠคํŠธ ์ˆ˜ 19โ†’23 ๊ฐฑ์‹  | +| S8 ์†Œ์‹ฑ ๋ณต๊ตฌ 3๊ณณ | โœ… (๊ฐ€๋“œ ์—†์Œ โ€” O-1) | +| ๋ฒ”์œ„ ์ค€์ˆ˜ | โœ… `run_loop.sh`ยท`reconcile.sh` ๋ฌด์ˆ˜์ • (K-1/K-4 ์œ ์ง€) | + +`usage()` ์— 4๊ฐœ ์—์ด์ „ํŠธ๋ช…์ด ๋ชจ๋‘ ๋‚จ์•„์•ผ ํ•˜๋Š” ์ œ์•ฝ(`test_comp_stop_usage_matches_parser`)๋„ ์œ ์ง€๋๊ณ , `FEATURE 3` ์ฃผ์„ ๊ฐœ์ˆ˜(4โ†’7)์™€ `import re` ์ถ”๊ฐ€๋ผ๋Š” ์‚ฌ์ „ ์กฐ๊ฑด 2๊ฑด๋„ ๋ฐ˜์˜๋์Šต๋‹ˆ๋‹ค. + +--- + +## 7. ์ดํ‰ + +๊ณ„ํš Rev.2 ๋Š” ์ฑŒ๋ฆฐ์ง€ ๋ผ์šด๋“œ๋ฅผ ๊ฑฐ์น˜๋ฉฐ ๋ฌธ์„œ ๊ฐ€๋“œ๊ฐ€ ๋‘ ๋ฒˆ ์žฌ์„ค๊ณ„๋๋Š”๋ฐ, ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ M7b ์—์„œ ๊ฐ’์–ด์น˜๋ฅผ ์ฆ๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค โ€” Rev.1 ์„ค๊ณ„์˜€๋‹ค๋ฉด ํ†ต๊ณผํ–ˆ์„ ํšŒ๊ท€๋ฅผ ํ˜„ํ–‰ ๊ฐ€๋“œ๊ฐ€ ์žก์Šต๋‹ˆ๋‹ค. C-2 ๋„ M5/M5b ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ๊นจ๋ฉด์„œ "ํ–‰๋™ ๋ณ€๊ฒฝ์ด์ง€๋งŒ ๊ธฐ์กด ๋ถˆ๋ณ€์‹์€ ๋ณด์กด" ์ด๋ผ๋Š” ์ฃผ์žฅ์ด ์‹ค์ฆ๋์Šต๋‹ˆ๋‹ค. + +๋‚จ์€ 3๊ฑด์€ ์ „๋ถ€ P3 ์ด๊ณ  ์–ด๋А ๊ฒƒ๋„ ๊ฒฐํ•จ์„ ๊ฐ€๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. O-2 ๋Š” ๋ฌธ์„œ ์Šค๋‹ˆํŽซ ํ•œ ๊ธ€์ž, O-3 ์€ ์ฃผ์„ ํ•œ ์ค„, O-1 ์€ ๊ฐ€๋“œ ์‹ ์„ค ๊ถŒ๊ณ ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ์ปค๋ฐ‹์— ํ•จ๊ป˜ ์ •๋ฆฌํ•˜๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. + +[VERDICT: PASS] diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-34201859.md b/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-34201859.md new file mode 100644 index 0000000..8fa2ea7 --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-34201859.md @@ -0,0 +1,122 @@ +# Cross-Code Review โ€” Job `34201859` + +- **Reviewer**: cline (herdr:canary-projects-multi-agent-mux-creator-cline) +- **Scope**: `--agent` ํ‘œ์ค€ํ™”(B-21), ๋ฐฑ๋กœ๊ทธ J-1(`_env_int` falsy-zero), C-2(๋ฌดํšจ ๋ณ„์นญ skip), J-2(์ž„๊ณ„๊ฐ’), ํšŒ๊ท€ ํ…Œ์ŠคํŠธ, ์ „์ฒด PASS +- **Changeset**: 14 files, +255 / โˆ’46 (`git diff --stat HEAD`) +- **Baseline**: ์ž‘์—… ํŠธ๋ฆฌ modified(์ปค๋ฐ‹ ์ „). `pytest tests/ --collect-only` ๊ธฐ์ค€ ์•ฝ 341 ๊ฑด. + +--- + +## ยง0. ๊ฒฐ๋ก  (TL;DR) + +4๊ฐœ ์ž‘์—… ๋ชฉํ‘œ ๋ชจ๋‘ ๊ตฌํ˜„๋˜์—ˆ๊ณ , ๋ณ€๊ฒฝ๋ถ„์˜ ์ง์ ‘ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ 39 ๊ฑด์€ 100% ํ†ต๊ณผํ•œ๋‹ค. ์ „์ฒด ์Šค์œ„ํŠธ๋Š” ๋ณธ ๋ฆฌ๋ทฐ ํ™˜๊ฒฝ์—์„œ **๋ผ์ด๋ธŒ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ herdr ์„œ๋ฒ„(pid 2702)์™€์˜ ์ถฉ๋Œ**๋กœ ์ธํ•ด ์‚ฌ์ „ ์กด์žฌํ•˜๋˜ herdr/installer ์˜์กด ํ…Œ์ŠคํŠธ(`test_deploy_freshness::test_d10`, `test_o2_race_free_lock`, `test_orc_onboard`, tier2/3/4์˜ `reconcile.sh --subscribe --idle-timeout 0` ์Šคํฐ ํ…Œ์ŠคํŠธ)๊ฐ€ hang/๊ฐ•์ œ ์ข…๋ฃŒ๋˜์–ด ๋‹จ์ผ run์œผ๋กœ ๋๊นŒ์ง€ ๋‹ฟ์ง€ ๋ชปํ•œ๋‹ค. ์ด๋“ค์€ **๋ณธ ๋ณ€๊ฒฝ๋ถ„์ด ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” ์‚ฌ์ „ ์กด์žฌ ํ…Œ์ŠคํŠธ**์ด๋ฉฐ, ์–ด๋А run ์—์„œ๋„ `FAILED`/`ERROR` ๋ฅผ ๋‚ธ ์ ์ด ์—†๋‹ค(์•„๋ž˜ ยง6). ์„ค๊ณ„ ๋ณ€๊ฒฝ/์žฌ์ž‘์—… ์ˆ˜์ค€์˜ ์žฌ๊ณ„ํš์€ ๋ถˆํ•„์š”ํ•˜๋‹ค. ์ตœ์ข… ํŒ์ •์€ ๋ฆฌํฌํŠธ ๋งˆ์ง€๋ง‰ ๋‹จ๋… ํ–‰์— ๋ช…์‹œ(ยง7). + +--- + +## ยง1. ๋ณ€๊ฒฝ ํŒŒ์ผ ๋ฒ”์œ„ + +| ํŒŒ์ผ | ๋ณ€๊ฒฝ | ์š”์ง€ | +|---|---|---| +| `.agents/skills/lib.sh` | +28 | `resolve_agent_type_from_registry()` ๊ณต์šฉ ํ—ฌํผ ์‹ ์„ค (`agent_of_row` ์œ„์ž„) | +| `.agents/skills/lib_py/layout.py` | +20/โˆ’6 | `_env_int(*names, default=None)` ๋ฆฌํŒฉํ„ฐ + `main()` `default=60/20` | +| `.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh` | +14/โˆ’13 | ์ ‘๋ฏธ์‚ฌ case โ†’ ๊ณต์šฉ ํ—ฌํผ, usage/ํ—ค๋” ๋™๊ธฐํ™”, ์†Œ์‹ฑ ๊ฒฝ๋กœ ๋ณต๊ตฌ | +| `.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh` | +13/โˆ’7 | ๋™์ผ ํด๋ฐฑ ๊ต์ฒด + ํ—ค๋” ๋™๊ธฐํ™” | +| `.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh` | +1/โˆ’1 | `lib.sh` ์†Œ์‹ฑ ๊ฒฝ๋กœ(`2>/dev/null \|\| pwd` ์ œ๊ฑฐ) | +| `.agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh` | +1/โˆ’1 | ํ—ค๋” 4-์—์ด์ „ํŠธ ํ‘œ์ค€ | +| `.agents/skills/multi-agent-mux-create/scripts/create_session.sh` | +2/โˆ’2 | ํ—ค๋” + ์†Œ์‹ฑ ๊ฒฝ๋กœ | +| `.agents/skills/multi-agent-mux-{stop,resume,create}/SKILL.md` | +11/โˆ’3 | `--agent` ๋ช…์‹œ ํ‘œ์ค€(4์ข…), ์˜ˆ์ œ์— `--agent` ๋ถ€์—ฌ | +| `tests/test_layout.py` | +72 | J-1/C-2 4๊ฑด + J-2(n=5/max=2) 1๊ฑด | +| `tests/test_a4_adapter_contract.py` | +9 | `agent_of_row` binary-path/failure/match_cmd ๋‹จ์œ„ | +| `tests/test_tier2_component.py` | +67/โˆ’2 | B-21 stop ํด๋ฐฑ 2๊ฑด + ๋ฌธ์„œ ํŽœ์Šค/๋ช…๋ น๋‹จ์œ„ ๊ฐ€๋“œ 1๊ฑด | +| `IMPROVEMENTS.md` | +18/โˆ’4 | B-21 ์™„๋ฃŒ, J-1/J-2, ์นด์šดํŠธ ๊ฐฑ์‹ (์™„๋ฃŒ 30๊ฑด) | + +--- + +## ยง2. ๋ชฉํ‘œ๋ณ„ ๊ฒ€์ฆ + +### B-21 โ€” `--agent` ํ‘œ์ค€ํ™” + ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ํด๋ฐฑ +- `lib.sh:995` `resolve_agent_type_from_registry()` ๋Š” `agent_of_row(row, session_name=name)` ์— ํ•ด์„์„ ์ „์ ์œผ๋กœ ์œ„์ž„ํ•œ๋‹ค. ์šฐ์„ ์ˆœ์œ„ โ‘  `row['agent']` โ†’ โ‘ก ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข `pane.cmd`(`registry.py:26` ๊ณ„์•ฝ๊ณผ ์ผ์น˜). ์‹คํŒจ ์‹œ stdout ๋ฏธ์ถœ๋ ฅ + `sys.exit(1)`. +- `stop_session.sh:106-112` ์™€ `update_yaml_resumed.sh:46-52` ์ด ์ ‘๋ฏธ์‚ฌ ์ „์šฉ case ๋ธ”๋ก์„ ๊ณต์šฉ ํ—ฌํผ๋กœ ๊ต์ฒด. `AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT=""` + `[ -n "$AGENT" ] || { โ€ฆ; exit 2; }` ํŒจํ„ด์œผ๋กœ **๊ธฐ์กด `exit 2` ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ๋ณด์กด**(`stop_session.sh` ํ—ค๋” :27-30 ๋ช…์‹œ). +- `--agent` ๋ช…์‹œ ์‹œ ์œ ํšจ๊ฐ’ ๊ฒ€์ฆ(`stop_session.sh:85-90`)์€ 4์ข… `claude|agy|hermes|cline` ์œผ๋กœ ์œ ์ง€. +- SKILL.md(stop/resume/create) ์˜ˆ์ œ๊ฐ€ ๋ชจ๋‘ `--agent "$AGENT"` ๋ถ€์—ฌ, 4-์—์ด์ „ํŠธ ํ‘œ์ค€๋ฌธ๊ตฌ ํ†ต์ผ. `deploy/INSTALL.md` ์˜ˆ์ œ๋„ ๋ฌธ์„œ ๊ฐ€๋“œ ๋Œ€์ƒ(ยง6 T5). +- **๋ผ์ด๋ธŒ ์‚ฌ๋ก€ ํ•ด๊ฒฐ**: `agy-creator-01`(์ ‘๋ฏธ์‚ฌ ์—†์Œ, `pane.cmd='agy'`)์ด ์ด์ œ `--agent` ์ƒ๋žต ์‹œ `agy`๋กœ ํ•ด์„๋จ(T4 ์‹ค์ธก). + +### J-1 โ€” `_env_int` falsy-zero trap +- `layout.py:175` `def _env_int(*names, default: Optional[int] = None) -> Optional[int]`. `default` ๊ฐ€ ๋ช…์‹œ ํŒŒ๋ผ๋ฏธํ„ฐ. +- `main()` `layout.py:201-202` ๊ฐ€ `or 60`/`or 20` ๋Œ€์‹  `_env_int(..., default=60)` / `default=20` ์‚ฌ์šฉ โ†’ `MAM_MIN_PANE_COLS=0` ์ด 0 ์œผ๋กœ ์กด์ค‘๋จ(60 ์œผ๋กœ ๋ญ‰๊ฐœ์ง€์ง€ ์•Š์Œ). +- `except ValueError: continue`(`layout.py:194-195`) โ€” ๋ฌดํšจ๊ฐ’์„ ํƒˆ์ถœ์ด ์•„๋‹Œ skip. ์ด๋Š” C-2 ์˜ ์ „์ œ์ด๊ธฐ๋„ ํ•˜๋‹ค. + +### C-2 โ€” ๋ฌดํšจ ๋ณ„์นญ์ด ๋ฌธ์„œํ™” ๋ณ€์ˆ˜๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š์Œ +- `MAM_MIN_COLS`(๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ, `layout.py` ์™ธ ์‚ฌ์šฉ์ฒ˜ ์—†์Œ)์ด `foo` ๋ฉด skip โ†’ `MAM_MIN_PANE_COLS`(`.mam.env.example` ๋ฌธ์„œ๋ช…)์ด ์ ์šฉ. T1b ์‹ค์ธก: `MAM_MIN_COLS=foo MAM_MIN_PANE_COLS=25` โ†’ `direction=right`(= `--min-cols 25`). ์ „ ํ›„๋ณด ๋ฌดํšจ ์‹œ `default` ํก์ˆ˜(Rev.1 ๋ถˆ๋ณ€์‹ ๋ณด์กด). + +### J-2 โ€” ํ—ค๋“œ๋ฆฌ์Šค n=5 ์ž„๊ณ„๊ฐ’ +- `test_layout.py` `test_headless_max_columns_growth_guard` ์— `n=5/max=2` ์ผ€์ด์Šค ์ถ”๊ฐ€: `n//2 == 2 == max_columns` ์ด๋ฏ€๋กœ ํ™€์ˆ˜ ๋ถ„๊ธฐ์—์„œ cap ๊ณผ๊ต์ • ์—ฌ๋ถ€๋ฅผ ํŒ๋ณ„ ๊ฐ€๋Šฅ(ํ˜„์žฌ `direction=down`, `reason=headless_odd_down`, `is_overflow=False`). `n=3`(`n//2==1`)์€ ๊ฒ€์‚ฌ์— ๋„๋‹ฌํ•˜์ง€ ๋ชปํ•ด ํŒ๋ณ„ ๋ถˆ๊ฐ€ โ€” n=5 ์„ ํƒ ์ •๋‹น. + +### ํšŒ๊ท€ ํ…Œ์ŠคํŠธ +- `test_layout.py`: 4๊ฑด(J-1 zero min-cols/min-rows, J-1 ๋™์ž‘ ์ค‘๋ฆฝ, C-2 alias skip) + J-2 1๊ฑด โ†’ ์ด 23๊ฑด. +- `test_a4_adapter_contract.py`: `agent_of_row` binary-path(`/usr/local/bin/agy`)ยท์‹คํŒจ(`None`)ยท`match_cmd=False` non-adoption 1๊ฑด. +- `test_tier2_component.py`: B-21 stop ํด๋ฐฑ(pane.cmd ํ•ด์„, ๋ช…์‹œ agent ํ•„๋“œ ์šฐ์„ ) 2๊ฑด + ๋ฌธ์„œ ๊ฐ€๋“œ(ํŽœ์Šค ์Šค์ฝ”ํ”„ + ๋ช…๋ น ๋‹จ์œ„) 1๊ฑด. + +### ๋ชฉํ‘œ 4 โ€” ์ „์ฒด PASS +- ยง6 ์ฐธ์กฐ. ๋ณ€๊ฒฝ๋ถ„ ์ง์ ‘ ํ…Œ์ŠคํŠธ 39๊ฑด 100% ํ†ต๊ณผ. ์‚ฌ์ „ ์กด์žฌ herdr/installer ํ…Œ์ŠคํŠธ์˜ ํ™˜๊ฒฝ์  hang ๋กœ ์ธํ•ด ๋‹จ์ผ full-run ์€ ๋ถˆ๊ฐ€ํ–ˆ์œผ๋‚˜, ์–ด๋А run ์—์„œ๋„ ์‹คํŒจ ์—†์Œ. + +--- + +## ยง3. ๋กœ์ง ๊ฐ์‚ฌ + +1. **`resolve_agent_type_from_registry` ํ™˜๊ฒฝ ์˜์กด์„ฑ**: `MAM_STATE_JSON="$(load_state_json)"` โ€” `load_state_json()` ์€ `lib.sh:938` ์— ์กด์žฌ(์‹ค์ธก). `from lib_py.agents.registry import agent_of_row` import ๋Š” `lib.sh:25` `export PYTHONPATH="$SKILL_DIR:โ€ฆ"` ๋กœ ํ•ด๊ฒฐ(์Šคํฌ๋ฆฝํŠธ๊ฐ€ `source lib.sh` ํ›„ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ ์ž์‹ python ์— ์ƒ์†). `herdr_sessions` ํ‚ค๋Š” `load_state_json` ์ถœ๋ ฅ(`lib.sh:959/1025`)๊ณผ ๋™์ผ. โœ… +2. **`set -euo pipefail` ํ˜ธํ™˜**: `AGENT="$(โ€ฆ)" \|\| AGENT=""` ์€ OR-list ์ด๋ฏ€๋กœ `set -e` ๊ฐ€ ๋น„๋™์ž‘. ์‹คํŒจ ์‹œ helper ๊ฐ€ ์ถœ๋ ฅ ์—†์ด exit 1 โ†’ `AGENT=""` ํ™•์ • ํ›„ `[ -n ] \|\| exit 2`. ์ •ํ™•. โœ… +3. **์†Œ์‹ฑ ๊ฒฝ๋กœ ๋ณต๊ตฌ**: `stop_session.sh:37`, `create_session.sh:23`, `resume_session.sh` ์˜ `cd "$_script_dir/../.." && pwd` (์‚ฌ์žฅ๋œ `2>/dev/null \|\| pwd` ์ œ๊ฑฐ). `cd` ์‹คํŒจ ์‹œ `set -e` ๋กœ ์ฆ‰์‹œ ์ข…๋ฃŒ โ†’ ์ž˜๋ชป๋œ `lib.sh` ๊ฒฝ๋กœ๋กœ ๋„˜์–ด๊ฐ€์ง€ ์•Š์Œ(์•ˆ์ „ ๊ฐ•ํ™”). โœ… +4. **`_env_int` `default` ์œ„์น˜ ์ธ์ž ์œ„ํ—˜**: ํ˜ธ์ถœ์ฒ˜๊ฐ€ ๋ชจ๋‘ `default=` ํ‚ค์›Œ๋“œ๋กœ ์ „๋‹ฌ(`layout.py:201-203`) โ†’ ๊ฐ€๋ณ€ `*names` ์™€ ์ถฉ๋Œ ์—†์Œ. โœ… +--- + +## ยง4. ๋ฆฐํŠธ / ์ •์  ๊ฒ€์‚ฌ + +| ๊ฒ€์‚ฌ | ๋ช…๋ น | ๊ฒฐ๊ณผ | +|---|---|---| +| bash ๊ตฌ๋ฌธ | `bash -n` on lib.sh, stop_session.sh, update_yaml_resumed.sh, resume_session.sh, resolve_session_id.sh, create_session.sh | **6/6 OK** | +| python ์ปดํŒŒ์ผ | `python -m py_compile lib_py/layout.py` | OK | +| shellcheck | โ€” | ํ™˜๊ฒฝ ๋ฏธ์„ค์น˜(์‚ฌ์ „ ์ œํ•œ, `bash -n` ๋Œ€์ฒด) | +| ๊ตฌ๋ฌธ ์ž”์กด | ์ ‘๋ฏธ์‚ฌ case `*-creator-claude\|*-planner-โ€ฆ` in stop/resume ๋””๋ ‰ํ† ๋ฆฌ | **0๊ฑด**(์ œ๊ฑฐ ์™„๋ฃŒ) | +| ๊ตฌ์‹ 2-์—์ด์ „ํŠธ ํ‘œ๊ธฐ | `claude\|agy)` ํŒจํ„ด(4์ข… ์•„๋‹˜) in `*.sh`/`*.md` | **0๊ฑด** | + +--- + +## ยง5. ์œ ์‹ค / ์ผ๊ด€์„ฑ / orphan + +- **์ œ๊ฑฐ ์‹ฌ๋ณผ orphan**: ์ ‘๋ฏธ์‚ฌ case ๋ธ”๋ก ์ œ๊ฑฐ ํ›„ ๋‚จ๋Š” ์ฐธ์กฐ ์—†์Œ(grep ์‹ค์ธก). `agent_of_row` ๋Š” ์‹ ๊ทœ ํ—ฌํผ๊ฐ€ ์‚ฌ์šฉ. โœ… +- **๋ฌธ์„œ-์Šคํฌ๋ฆฝํŠธ ์ผ์น˜**: SKILL.md ์˜ˆ์ œ์˜ `--agent` ๋ถ€์—ฌ๊ฐ€ `test_comp_docs_stop_examples_pass_agent`(ํŽœ์Šค+๋ช…๋ น๋‹จ์œ„) ๊ฐ€๋“œ๋กœ ์ง‘ํ–‰. `INSTALL.md`(floor=2), `stop/SKILL.md`(floor=3) ์ตœ์†Œ ์˜ˆ์ œ ์ˆ˜ ํ•˜ํ•œ์œผ๋กœ ๋ฌด๋ ฅํ™” ๋ฐฉ์ง€. โœ… +- **์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ**: `stop_session.sh`/`update_yaml_resumed.sh` `exit 2` ์œ ์ง€. `test_tier1_unit.py:142`, `test_tier3_integration.py:398`(๋ฌด์ˆ˜์ • PASS ๊ณ„์•ฝ)์€ ๋ณธ ๋ณ€๊ฒฝ๋ถ„์ด ๋ฏธ์ ‘์ด‰. โœ… +- **์˜๋„์น˜ ์•Š์€ ์ˆ˜์ •**: `lib.sh` ์™ธ๋ถ€ ๋™์ž‘ ๋ณ€๊ฒฝ ์—†์Œ(ํ—ฌํผ ์‹ ๊ทœ ์ถ”๊ฐ€๋งŒ). โœ… + +--- + +## ยง6. ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ + +### 6.1 ๋ณ€๊ฒฝ๋ถ„ ์ง์ ‘ ํšŒ๊ท€ (clean, ๋‹จ๋… run) +``` +tests/test_layout.py + tests/test_a4_adapter_contract.py โ†’ 36 passed in 1.22s +tests/test_tier2_component.py::test_comp_stop_agent_fallback_reads_pane_cmd PASSED +tests/test_tier2_component.py::test_comp_stop_agent_fallback_prefers_explicit_agent_field PASSED +tests/test_tier2_component.py::test_comp_docs_stop_examples_pass_agent PASSED +โ†’ 3 passed in 2.23s +``` +๋ณ€๊ฒฝ๋ถ„ ์ง์ ‘ ํšŒ๊ท€ **39๊ฑด 100% ํ†ต๊ณผ**. + +### 6.2 ๊ด‘์—ญ ์Šค์œ„ํŠธ (๋ผ์ด๋ธŒ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ ํ™˜๊ฒฝ) +- verbose run(`--ignore=tier2/3/4`, `-v`): **69 PASSED, 0 FAILED, 0 ERROR** โ€” `test_deploy_freshness::test_d10_customization_survives_repeated_refresh`(70๋ฒˆ์งธ, ์‚ฌ์ „ ์กด์žฌ deploy/installer ํ…Œ์ŠคํŠธ)์—์„œ hang. ๋ณธ ๋ณ€๊ฒฝ๋ถ„ ๋ฏธ์ ‘์ด‰. +- tail run(11๊ฐœ ๋น„-tier ํŒŒ์ผ): **135 passed, 0 failures** โ€” `test_o2_race_free_lock`/`test_orc_onboard` ๋ถ€๊ทผ(herdr ์˜์กด ์‚ฌ์ „ ํ…Œ์ŠคํŠธ)์—์„œ hang. +- ์š”์•ฝ: **์–ด๋А run ์—์„œ๋„ `F`/`E` ์—†์Œ**; 130+ ๊ฑด ํ†ต๊ณผ ํ›„ ํ™˜๊ฒฝ์  hang. ์‚ฌ์ „ ์กด์žฌ `reconcile.sh --subscribe --idle-timeout 0` ๋ฐ๋ชฌ์ด ๋ผ์ด๋ธŒ herdr(2702)๊ณผ NATS/์ž์› ์ถฉ๋Œ. + +### 6.3 ํ™˜๊ฒฝ์  ์ œ์•ฝ(๋น„-๋ธ”๋กœํ‚น, ๋ณธ ๋ณ€๊ฒฝ๋ถ„ ๋ฌด๊ด€) +๋ณธ ๋ฆฌ๋ทฐ๋Š” loop-active ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ํ™˜๊ฒฝ์—์„œ ์ˆ˜ํ–‰๋˜์–ด ๋ผ์ด๋ธŒ `herdr --session multi-agent-mux server`(pid 2702)๊ฐ€ ํ™œ์„ฑ. ์‚ฌ์ „ ์กด์žฌ herdr/installer ์˜์กด ํ…Œ์ŠคํŠธ(`test_deploy_freshness::test_d10`, `test_o2`, `test_orc_onboard`, tier2/3/4 reconcile ํ…Œ์ŠคํŠธ)๊ฐ€ ์ด ์„œ๋ฒ„์™€ ์ถฉ๋Œํ•˜์—ฌ hang/๊ฐ•์ œ์ข…๋ฃŒ. ์ด๋“ค์€ **๋ณ€๊ฒฝ๋ถ„์ด ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” ํ…Œ์ŠคํŠธ**์ด๋ฉฐ ์‹คํŒจ(๋‹จ์ • ์œ„๋ฐ˜)๊ฐ€ ์•„๋‹Œ ํ™˜๊ฒฝ์  hang. ๋™ํ˜• ๋ณ€๊ฒฝ๋ถ„์— ๋Œ€ํ•œ ์„ ํ–‰ ๋ฆฌ๋ทฐ(์˜ˆ: job `6f18ba0f`, `79ff98ed`)๋Š” clean ํ™˜๊ฒฝ์—์„œ ์ „์ฒด 100% PASS ๋ฅผ ๋ณด๊ณ ํ•จ. ์žฌํ˜„์€ ๋ผ์ด๋ธŒ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ ๋น„ํ™œ์„ฑ ํ™˜๊ฒฝ์—์„œ ๊ถŒ์žฅ. + +> ๊ด€์ฐฐ: ์‚ฌ์ „ ์กด์žฌ ํ…Œ์ŠคํŠธ ์ธํ”„๋ผ ๊ฐœ์„  ํ›„๋ณด โ€” `reconcile.sh --idle-timeout 0` ๋ฐ๋ชฌ์ด run ๊ฐ•์ œ์ข…๋ฃŒ ์‹œ orphan ๋กœ ์ž”์กด(๋ณธ ๋ฆฌ๋ทฐ ์ค‘ 21๊ฑด ์ˆ˜๊ฑฐ). ํ…Œ์ŠคํŠธ fixture teardown ๊ฐ•ํ™” ๋˜๋Š” ์œ ํ•œ idle-timeout ๊ธฐ๋ณธ๊ฐ’์ด ํ–ฅํ›„ ํ™˜๊ฒฝ ์•ˆ์ •์„ฑ์— ๊ธฐ์—ฌ. **๋ณธ ๋ณ€๊ฒฝ๋ถ„ ์ฑ…์ž„ ์•„๋‹˜.** + +--- + +## ยง7. ์ดํ‰ + +4๊ฐœ ๋ชฉํ‘œ๊ฐ€ ์ •ํ™•ยท์™„์ „ํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ๊ณ , ๋ณ€๊ฒฝ๋ถ„ ์ง์ ‘ ํšŒ๊ท€ 39๊ฑด์ด 100% ํ†ต๊ณผํ•˜๋ฉฐ, ๋ฆฐํŠธ/orphan/์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋ชจ๋‘ clean ํ•˜๋‹ค. ์ „์ฒด ์Šค์œ„ํŠธ์˜ ๋‹จ์ผ 100% PASS ์žฌํ˜„์€ ๋ผ์ด๋ธŒ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ herdr ์ถฉ๋Œ(์‚ฌ์ „ ์กด์žฌ ํ…Œ์ŠคํŠธ, ๋ณ€๊ฒฝ๋ถ„ ๋ฌด๊ด€)๋กœ ๋ง‰ํ˜”์œผ๋‚˜ ์–ด๋–ค run ๋„ ์‹คํŒจ๋ฅผ ๋‚ธ ์ ์ด ์—†๋‹ค. ์„ค๊ณ„ ์žฌ์ž‘์—… ์ˆ˜์ค€์˜ ์žฌ๊ณ„ํš์€ ๋ถˆํ•„์š” โ€” ๋ชจ๋“  ๋ฐœ๊ฒฌ์€ ๋น„-๋ธ”๋กœํ‚น ๊ด€์ฐฐ ๋˜๋Š” ํ™˜๊ฒฝ ์ œ์•ฝ์ด๋‹ค. + +[VERDICT: PASS] \ No newline at end of file diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index 2be2047..f5c77d6 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -979,6 +979,34 @@ print(json.dumps(d, ensure_ascii=False)) PYEOF } +# resolve_agent_type_from_registry +# +# ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(YAML/DB)์— ๊ธฐ๋ก๋œ ์‚ฌ์‹ค๋กœ ์—์ด์ „ํŠธ ์ข…๋ฅ˜๋ฅผ ํ•ด์„ํ•œ๋‹ค. ์šฐ์„ ์ˆœ์œ„๋Š” +# lib_py.agents.registry.agent_of_row ์˜ ๊ณ„์•ฝ์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅธ๋‹ค: +# โ‘  row['agent'] ๋ช…์‹œ ํ•„๋“œ +# โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ (*-{creator,planner,reviewer}- ๋ฐ *-) +# โ‘ข pane.cmd (์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๊ฑฐ๋‚˜ .../ ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ) +# ์„ฑ๊ณตํ•˜๋ฉด ์—์ด์ „ํŠธ๋ช…์„ stdout ์— ์ถœ๋ ฅํ•˜๊ณ  0 ์„, ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด ์•„๋ฌด๊ฒƒ๋„ +# ์ถœ๋ ฅํ•˜์ง€ ์•Š๊ณ  1 ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ํ˜ธ์ถœ์ž๊ฐ€ ์†Œ์œ ํ•œ๋‹ค โ€” ๊ฐ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ +# ๋ฌธ์„œํ™”ํ•œ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค. +# +# NOTE: agent_of_row ์˜ match_cmd=True ๋Š” "๋น„-์ž…์–‘ ์กฐํšŒ" ๊ณ„์•ฝ์ด๋‹ค. reconcile.sh +# ์ž…์–‘ ๋ฃจํ”„๋Š” ์ด ํ—ฌํผ๋ฅผ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค (3aee63cf ยง1.2 ์‹ค์ธก ๋ฐ˜์ฆ). +resolve_agent_type_from_registry() { + local name="$1" + MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$name" python3 -c " +import os, json, sys +from lib_py.agents.registry import agent_of_row +name = os.environ['SESSION_NAME'] +d = json.loads(os.environ.get('MAM_STATE_JSON', '{}')) +row = next((s for s in d.get('herdr_sessions', []) if s.get('name') == name), {}) +resolved = agent_of_row(row, session_name=name) +if not resolved: + sys.exit(1) +print(resolved) +" +} + # Despite the name (kept for caller compatibility โ€” resume/stop/update_yaml_resumed # all do `HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"`), this # returns the isolated herdr *session* name to use for this MAM session row, not diff --git a/.agents/skills/lib_py/layout.py b/.agents/skills/lib_py/layout.py index 302765b..b1c9d86 100644 --- a/.agents/skills/lib_py/layout.py +++ b/.agents/skills/lib_py/layout.py @@ -172,24 +172,34 @@ def compute_2xk_layout( return LayoutDecision(target_pane_id=rightmost_top_pane.pane_id, direction="right", reason="new_column_right") -def _env_int(*names: str) -> Optional[int]: - """First non-empty env var among *names, parsed as int. Bad values are - ignored rather than raised: a typo in an operator's shell must not take the - whole layout call down (lib.sh would silently fall back to 'right').""" +def _env_int(*names: str, default: Optional[int] = None) -> Optional[int]: + """First *valid* int among the env vars in *names*, else `default`. + + `default` is an explicit parameter rather than an `or` at the call site so a + legitimate 0 survives (MAM_MIN_PANE_COLS=0 means 0, not the 60 default). + + An unparsable value is skipped rather than raised or treated as terminal: a + typo in an operator's shell must not take the whole layout call down (lib.sh + would silently fall back to 'right'), and must not shadow a later candidate + that IS set correctly -- MAM_MIN_COLS is a legacy alias while + MAM_MIN_PANE_COLS is the name .mam.env.example documents, so aborting on the + first bad value would discard the documented setting. Empty values already + fell through; this makes invalid values behave the same way. + """ for n in names: raw = os.environ.get(n, "").strip() if raw: try: return int(raw) except ValueError: - return None - return None + continue + return default def main(): parser = argparse.ArgumentParser(description="Compute 2xK grid TUI layout split direction") - 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) + 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")) parser.add_argument("--sample-pane", type=str, default=None) parser.add_argument("--json", action="store_true", help="Output full JSON decision") diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index 060a006..00402de 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -143,7 +143,7 @@ herdr_sessions: ```bash WORKSPACE=/path/to/project -AGENT=claude # or agy +AGENT=claude # claude | agy | hermes | cline โ€” always pass it explicitly source .agents/skills/lib.sh SESSION_NAME="$(derive_session_name "$WORKSPACE" "$AGENT")" @@ -168,7 +168,7 @@ case "$AGENT" in agy) herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "agy --dangerously-skip-permissions" ;; - *) echo "ERROR: --agent must be claude or agy, got: $AGENT"; exit 2 ;; + *) echo "ERROR: --agent must be claude, agy, hermes or cline, got: $AGENT"; exit 2 ;; esac # 3. Wait for agent TUI to be ready (varies: claude ~5s, agy ~3s) diff --git a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh index 86a3651..f532e48 100755 --- a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh +++ b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # create_session.sh โ€” multi-agent-mux-create ์˜ ๋ถ€์† ์Šคํฌ๋ฆฝํŠธ # Usage: -# bash create_session.sh --workspace --agent --role [--session ] [--wrapper] +# bash create_session.sh --workspace --agent --role [--session ] [--wrapper] # # ๋™์ž‘: # 1) preflight: herdr/claude/agy ๊ฐ€์šฉ์„ฑ, workspace ์กด์žฌ @@ -20,7 +20,7 @@ set -euo pipefail _script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -_lib_sh="$(cd "$_script_dir/../.." 2>/dev/null || pwd)/lib.sh" +_lib_sh="$(cd "$_script_dir/../.." && pwd)/lib.sh" [ -f "$_lib_sh" ] || _lib_sh="${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh" source "$_lib_sh" diff --git a/.agents/skills/multi-agent-mux-resume/SKILL.md b/.agents/skills/multi-agent-mux-resume/SKILL.md index ad807d9..6214978 100644 --- a/.agents/skills/multi-agent-mux-resume/SKILL.md +++ b/.agents/skills/multi-agent-mux-resume/SKILL.md @@ -58,7 +58,7 @@ If both are empty โ†’ the workspace has no conversation yet. Fall back to `multi ```bash WORKSPACE=/path/to/project -AGENT=claude # or agy or hermes +AGENT=claude # claude | agy | hermes | cline โ€” pass it explicitly SESSION_NAME=-creator- # same convention as multi-agent-mux-create # Resolve the isolated herdr server name & load common utils diff --git a/.agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh b/.agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh index 4423b9a..c17dc62 100755 --- a/.agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh +++ b/.agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # resolve_session_id.sh โ€” multi-agent-mux-resume ์˜ ๋ถ€์† ์Šคํฌ๋ฆฝํŠธ # Usage: -# bash resolve_session_id.sh --workspace --agent +# bash resolve_session_id.sh --workspace --agent # ์ถœ๋ ฅ: stdout ์œผ๋กœ UUID ํ•œ ์ค„ (์—†์œผ๋ฉด ๋นˆ ์ค„ + exit 0) # # P0-C: ์ „์—ญ agent_identities ๋ฅผ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค. lib.sh::find_workspace_uuid diff --git a/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh b/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh index ff35979..d5ed58c 100755 --- a/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh +++ b/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh @@ -3,7 +3,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LIB_SH="$(cd "$SCRIPT_DIR/../.." 2>/dev/null || pwd)/lib.sh" +LIB_SH="$(cd "$SCRIPT_DIR/../.." && pwd)/lib.sh" [ -f "$LIB_SH" ] || LIB_SH="${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh" source "$LIB_SH" diff --git a/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh b/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh index 4c11ab7..593e665 100755 --- a/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh +++ b/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh @@ -4,14 +4,14 @@ # resume UUID ๋ฅผ per-row own id (claude_session_id_own / agy_conversation_id_own) # ์— ๋ฐ•๋Š”๋‹ค โ€” agent_identities ์ „์—ญ์€ ๋” ์ด์ƒ primary ์•„๋‹˜ (cache ๋กœ ๊ฐ•๋“ฑ, P0-C/๋‹จ๊ณ„ e). # -# Usage: bash update_yaml_resumed.sh --session --uuid [--agent claude|agy] +# Usage: bash update_yaml_resumed.sh --session --uuid [--agent claude|agy|hermes|cline] set -euo pipefail source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh" usage() { cat < --uuid [--agent claude|agy] +Usage: $0 --session --uuid [--agent claude|agy|hermes|cline] EOF } @@ -40,15 +40,15 @@ done HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")" export HERDR_SESSION_NAME -# --agent ๋ฏธ์ง€์ • ์‹œ ์ด๋ฆ„ suffix ๋กœ fallback (P1-F: ๊ฐ€๋Šฅํ•˜๋ฉด --agent ๋ช…์‹œ) +# --agent ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ ํ•ด์„ (B-21). +# โ‘  row['agent'] โ†’ โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข pane.cmd ์ˆœ. ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด +# ์ข…์ „๊ณผ ๋™์ผํ•˜๊ฒŒ exit 2 (ํ—ค๋” :27-30 ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ์œ ์ง€). if [ -z "$AGENT" ]; then - case "$SESSION_NAME" in - *-creator-claude|*-planner-claude|*-reviewer-claude) AGENT=claude ;; - *-creator-agy|*-planner-agy|*-reviewer-agy) AGENT=agy ;; - *-creator-hermes|*-planner-hermes|*-reviewer-hermes) AGENT=hermes ;; - *-creator-cline|*-planner-cline|*-reviewer-cline) AGENT=cline ;; - *) echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2; exit 2 ;; - esac + AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT="" + [ -n "$AGENT" ] || { + echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2 + exit 2 + } fi if [ -z "$ROLE" ]; then diff --git a/.agents/skills/multi-agent-mux-stop/SKILL.md b/.agents/skills/multi-agent-mux-stop/SKILL.md index 287ab3b..1221d0b 100644 --- a/.agents/skills/multi-agent-mux-stop/SKILL.md +++ b/.agents/skills/multi-agent-mux-stop/SKILL.md @@ -37,6 +37,7 @@ The stop command is always **graceful by default**: ```bash SESSION_NAME=-creator- # convention +AGENT=claude # claude | agy | hermes | cline โ€” always pass it AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml # 1) Session is registered? @@ -66,20 +67,27 @@ fi ```bash # 1. Stop gracefully (default โ€” captures ID, shuts down safely, status=stopped) bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ - --session "$SESSION_NAME" + --session "$SESSION_NAME" --agent "$AGENT" # 2. Stop gracefully + record a custom stop reason bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ - --session "$SESSION_NAME" --reason api_error + --session "$SESSION_NAME" --agent "$AGENT" --reason api_error # 3. Stop gracefully + clean up on-disk conversation (DANGEROUS) # โ€” this prevents any future resume (status=terminated, resumable=false). bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ - --session "$SESSION_NAME" --purge-conversation + --session "$SESSION_NAME" --agent "$AGENT" --purge-conversation ``` **Idempotency**: if the row is already `status: stopped`, the script prints `already stopped (...)` and exits 0 โ€” re-running is a safe no-op. +**`--agent` is the standard.** Pass it on every invocation. If omitted, the script +resolves the agent from the registry record โ€” the row's `agent` field, then the +session-name suffix, then `pane.cmd` โ€” and exits 2 if none of the three resolve. +The fallback exists for recovery, not as the normal calling convention: a session +whose name carries no agent suffix (e.g. `agy-creator-01`) is only resolvable +while its registry row survives. + ### State machine ``` diff --git a/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh b/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh index 27aec40..59c7dd4 100755 --- a/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh +++ b/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh @@ -13,7 +13,9 @@ # ์˜ต์…˜: # --session โ€” ๋Œ€์ƒ ์„ธ์…˜ (ํ•„์ˆ˜) # --agent โ€” claude | agy | hermes | cline -# (๋ฏธ์ง€์ • ์‹œ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ๋กœ ์ถ”๋ก ; ์ถ”๋ก  ์‹คํŒจ ์‹œ exit 2) +# (๊ถŒ์žฅ: ํ•ญ์ƒ ๋ช…์‹œ. ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ +# ํ•ด์„ โ€” agent ํ•„๋“œ โ†’ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ pane.cmd; +# ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด exit 2) # --reason โ€” ์ƒํƒœ ์ „์ด ์‚ฌ์œ  (stop_reason). ๊ธฐ๋ณธ๊ฐ’ manual_stop # --purge-conversation โ€” ๋””์Šคํฌ์˜ conversation artifact ๊นŒ์ง€ ์‚ญ์ œ. # status=terminated, resumable=false ๋กœ ์ „์ดํ•˜๋ฉฐ @@ -32,7 +34,7 @@ set -euo pipefail # shellcheck disable=SC1091 _script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -_lib_sh="$(cd "$_script_dir/../.." 2>/dev/null || pwd)/lib.sh" +_lib_sh="$(cd "$_script_dir/../.." && pwd)/lib.sh" [ -f "$_lib_sh" ] || _lib_sh="${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh" source "$_lib_sh" @@ -43,8 +45,9 @@ Usage: $0 --session [--agent claude|agy|hermes|cline] [--reason ] Arguments: --session โ€” target session name (required) - --agent โ€” claude | agy | hermes | cline - (inferred from the session-name suffix when omitted) + --agent โ€” claude | agy | hermes | cline (recommended: always pass it) + (falls back to the registry record: agent field -> + session-name suffix -> pane.cmd) --reason โ€” stop_reason field (default: manual_stop) --purge-conversation โ€” also delete on-disk conversation artifacts; status becomes terminated and resume is impossible @@ -97,15 +100,15 @@ fi HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME" "${WORKSPACE:-$WORKSPACE_ROOT}")" export HERDR_SESSION_NAME -# --agent ๋ฏธ์ง€์ • ์‹œ ์ด๋ฆ„ suffix ๋กœ fallback (P1-F) +# --agent ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ ํ•ด์„ (B-21). +# โ‘  row['agent'] โ†’ โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข pane.cmd ์ˆœ. ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด +# ์ข…์ „๊ณผ ๋™์ผํ•˜๊ฒŒ exit 2 (ํ—ค๋” :27-30 ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ์œ ์ง€). if [ -z "$AGENT" ]; then - case "$SESSION_NAME" in - *-creator-claude|*-planner-claude|*-reviewer-claude) AGENT=claude ;; - *-creator-agy|*-planner-agy|*-reviewer-agy) AGENT=agy ;; - *-creator-hermes|*-planner-hermes|*-reviewer-hermes) AGENT=hermes ;; - *-creator-cline|*-planner-cline|*-reviewer-cline) AGENT=cline ;; - *) echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2; exit 2 ;; - esac + AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT="" + [ -n "$AGENT" ] || { + echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2 + exit 2 + } fi # ์„ธ์…˜์ด YAML ์— ์žˆ๋Š”์ง€ + ํ•ด๋‹น row ์˜ ์›Œํฌ์ŠคํŽ˜์ด์Šค cwd ๋ฐ delegate_job_id ์ถ”์ถœ. diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md index e49cb55..629cb1a 100644 --- a/IMPROVEMENTS.md +++ b/IMPROVEMENTS.md @@ -1,9 +1,9 @@ # ๐Ÿ› ๏ธ Multi-Agent Mux ์ข…ํ•ฉ ๊ฐœ์„  ๋ฐ ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ ๋ฐฑ๋กœ๊ทธ (`IMPROVEMENTS.md`) -- **์ตœ์ข… ๊ฐฑ์‹ ์ผ**: 2026-08-23 (`nats-docker` ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ, B-20 2ร—K ๊ทธ๋ฆฌ๋“œ TUI ๋ ˆ์ด์•„์›ƒ ์—”์ง„ lib_py/layout.py ๊ณต์šฉํ™” ๋ฐ lib.sh ์ธ๋ผ์ธ ์ •๋ฆฌ ์™„๋ฃŒ) +- **์ตœ์ข… ๊ฐฑ์‹ ์ผ**: 2026-08-24 (`nats-docker` ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ, B-20 2ร—K ๊ทธ๋ฆฌ๋“œ TUI ๋ ˆ์ด์•„์›ƒ ์—”์ง„, J-1/J-2 ๋ ˆ์ด์•„์›ƒ ํ™˜๊ฒฝ๋ณ€์ˆ˜/์ž„๊ณ„๊ฐ’ ๋ณด๊ฐ•, B-21 `--agent` ํ‘œ์ค€ํ™” ๋ฐ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ agent_of_row ํด๋ฐฑ ํ†ตํ•ฉ ์™„๋ฃŒ) - **ํ†ตํ•ฉ ๊ด€๋ฆฌ ๋Œ€์ƒ**: ๊ธฐ์กด `CODEBASE_REVIEW_REPORT.md` + `OPTIMIZATION.md` + `NATS_REPORT.md` - **์ด ์ถ”์  ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ**: **5๊ฑด** (์•„ํ‚คํ…์ฒ˜ 1๊ฑด: `A-2`, ์—ฃ์ง€์ผ€์ด์Šค ๋ฐ ๊ฐ€์šฉ์„ฑ 3๊ฑด: `B-16`, `B-17`, `B-18`, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ 1๊ฑด: `O-5`) -- **์™„๋ฃŒ๋œ ๊ณผ์ œ**: **29๊ฑด** (A-1, A-3, A-4, A-5, B-1, B-3, B-4, B-5, B-7, B-8, B-9, B-10, B-13, B-14, B-15, B-19, B-20, C-1, C-2, C-3b, C-6, O-1, O-2, O-3, O-4-OrcOnboard, O-6, Herdr-0.8.0-Compat-SanitizeHash, P2-1-DelegateJobSafe-TrapFix, P2-2-C3a-C4-LegacyCleanup) +- **์™„๋ฃŒ๋œ ๊ณผ์ œ**: **30๊ฑด** (A-1, A-3, A-4, A-5, B-1, B-3, B-4, B-5, B-7, B-8, B-9, B-10, B-13, B-14, B-15, B-19, B-20, B-21, C-1, C-2, C-3b, C-6, O-1, O-2, O-3, O-4-OrcOnboard, O-6, Herdr-0.8.0-Compat-SanitizeHash, P2-1-DelegateJobSafe-TrapFix, P2-2-C3a-C4-LegacyCleanup) --- @@ -25,7 +25,21 @@ --- -## 2. ๐ŸŸ  ์—ฃ์ง€ ์ผ€์ด์Šค ๋ฐ ๋Ÿฐํƒ€์ž„ ๋ฒ„๊ทธ (Edge-case Bugs โ€” 5๊ฑด / ์™„๋ฃŒ 4๊ฑด) +## 2. ๐ŸŸ  ์—ฃ์ง€ ์ผ€์ด์Šค ๋ฐ ๋Ÿฐํƒ€์ž„ ๋ฒ„๊ทธ (Edge-case Bugs โ€” 6๊ฑด / ์™„๋ฃŒ 5๊ฑด) + +### **B-21 (โœ… ์™„๋ฃŒ โ€” `--agent` ํ”Œ๋ž˜๊ทธ ํ‘œ์ค€ํ™” ๋ฐ `stop_session.sh`/`update_yaml_resumed.sh` ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ `agent_of_row` ํด๋ฐฑ ํ†ตํ•ฉ)** +- **ํ˜„์ƒ**: + - `stop_session.sh` ๋ฐ `update_yaml_resumed.sh`๊ฐ€ `--agent` ์ƒ๋žต ์‹œ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ regex์—๋งŒ ์˜์กดํ•˜์—ฌ, ๋ผ์ด๋ธŒ ์„ธ์…˜์ธ `agy-creator-01` ๋“ฑ ์œ ํšจํ•˜๊ฒŒ ์‹คํ–‰ ์ค‘์ธ ์„ธ์…˜์ด `exit 2`๋กœ ๊ฑฐ๋ถ€๋˜๋˜ ๊ฒฐํ•จ. + - ์—์ด์ „ํŠธ ํ•ด์„๊ธฐ๊ฐ€ 4์ค‘ํ™”(`registry.py`, `stop_session.sh`, `update_yaml_resumed.sh`, `run_loop.sh`)๋˜์–ด ์ผ๊ด€์„ฑ์ด ๊ฒฐ์—ฌ๋จ. + - ๊ฐ€์ด๋“œ ๋ฌธ์„œ(SKILL.md) ์˜ˆ์ œ ๋ฐ ์Šคํฌ๋ฆฝํŠธ ํ—ค๋”์—์„œ `--agent` ์ „๋‹ฌ์ด ๋ˆ„๋ฝ๋˜๊ฑฐ๋‚˜ ์—์ด์ „ํŠธ ํƒ€์ž…(4์ข…: `claude|agy|hermes|cline`)์ด ๋ถˆ์ผ์น˜ํ•จ. +- **์กฐ์น˜ ๊ฒฐ๊ณผ (์™„๋ฃŒ)**: + - `lib.sh`์— `resolve_agent_type_from_registry()` ๊ณต์šฉ ํ—ฌํผ ์‹ ์„ค: `agent_of_row` ์šฐ์„ ์ˆœ์œ„(โ‘  `row['agent']` โ†’ โ‘ก ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข `pane.cmd`)๋ฅผ ์—„๊ฒฉํžˆ ์ค€์ˆ˜ํ•˜์—ฌ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ฐ˜ ํ•ด์„ ์ง€์›. + - `stop_session.sh` ๋ฐ `update_yaml_resumed.sh`์˜ ์ ‘๋ฏธ์‚ฌ ์ „์šฉ case ๋ธ”๋ก์„ ๊ณต์šฉ ํ—ฌํผ๋กœ ๊ต์ฒดํ•˜๊ณ , ๋ฏธํ•ด์„ ์‹œ ๊ธฐ์กด `exit 2` ๊ณ„์•ฝ ๋ฐ ํ—ค๋”/usage ๋ฌธ์„œ ๋™๊ธฐํ™”. + - `stop_session.sh`, `create_session.sh`, `resume_session.sh`์˜ ์‚ฌ์žฅ๋œ `lib.sh` ์†Œ์‹ฑ ๊ฒฝ๋กœ(`cd ... 2>/dev/null || pwd`) ๋ณต๊ตฌ. + - `lib_py/layout.py`: `_env_int(*names, default=None)` ํ—ฌํผ๋กœ ๋ฆฌํŒฉํ„ฐํ•˜์—ฌ `MAM_MIN_PANE_COLS=0` ๋“ฑ falsy-zero ๋ฒ„๊ทธ(J-1)๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ์ž˜๋ชป๋œ ๋ณ„์นญ ์ž…๋ ฅ ์‹œ ํ›„์† ์œ ํšจ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ fallback ํ•˜๋„๋ก `continue` ์ฒ˜๋ฆฌ(C-2). + - `multi-agent-mux-stop`, `multi-agent-mux-resume`, `multi-agent-mux-create`์˜ SKILL.md ๋ฐ ์Šคํฌ๋ฆฝํŠธ ํ—ค๋”๋ฅผ 4๊ฐœ ์—์ด์ „ํŠธ ๋ช…์‹œ ํ‘œ์ค€์œผ๋กœ ๋™๊ธฐํ™”. +- **ํšŒ๊ท€ ๊ฐ€๋“œ**: + - `tests/test_layout.py` (J-1 zero min-cols/min-rows ๋ฐ C-2 ๋ฌดํšจ๊ฐ’ fallback ํ…Œ์ŠคํŠธ 4๊ฑด, J-2 n=5 ์ž„๊ณ„๊ฐ’ ๋ณด๊ฐ• 1๊ฑด), `tests/test_a4_adapter_contract.py` (T3 1๊ฑด), `tests/test_tier2_component.py` (T4 fallback/priority 2๊ฑด, T5 ํŽœ์Šค+๋ช…๋ น ๋‹จ์œ„ ๋ฌธ์„œ ๊ฐ€๋“œ 1๊ฑด). ### **B-20 (โœ… ์™„๋ฃŒ โ€” 2ร—K ๊ทธ๋ฆฌ๋“œ TUI ๋ ˆ์ด์•„์›ƒ ์—”์ง„ `lib_py/layout.py` ๊ณต์šฉํ™” ๋ฐ `lib.sh` ์ธ๋ผ์ธ ๋ ˆ๊ฑฐ์‹œ ์ •๋ฆฌ)** - **ํ˜„์ƒ**: @@ -34,8 +48,8 @@ - **์กฐ์น˜ ๊ฒฐ๊ณผ (์™„๋ฃŒ)**: - `.agents/skills/lib_py/layout.py` ๊ณต์šฉ ์—”์ง„ ์‹ ์„ค: ์˜ค๋ฅธ์ชฝ ํ™•์žฅ 2ร—K ๊ทธ๋ฆฌ๋“œ ์•Œ๊ณ ๋ฆฌ์ฆ˜, ํ•ด์ƒ๋„ ์˜ค๋ฒ„ํ”Œ๋กœ ๊ฐ€๋“œ(`min_cols=60`, `min_rows=20`), ํ—ค๋“œ๋ฆฌ์Šค 0ร—0 ๊ฒฐ์ •๋ก ์  ๋ถ„ํ•  ์ง€์›. - `lib.sh`: ์ธ๋ผ์ธ Python ์Šค๋‹ˆํŽซ์„ `python3 -m lib_py.layout` ๋‹จ์ผ ํ˜ธ์ถœ๋กœ ๊ต์ฒดํ•˜๊ณ  ๋ ˆ๊ฑฐ์‹œ ๋ณ€์ˆ˜/์ฃผ์„ ์ •๋ฆฌ. - - ํ›„์† ์ •๋ฆฌ (I-2/I-3/C-1): `PaneInfo.focused` ๋ฏธ์‚ฌ์šฉ ํ•„๋“œ ์ •๋ฆฌ, `MAM_MAX_PANE_COLS`/`MAM_MAX_COLS` env ๋ฐฐ์„  ์™„๋ฃŒ, ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ์—์„œ `max_columns`๋ฅผ ์šฐํšŒํ•˜๋˜ ๊ฒฐํ•จ(C-1)์„ ๊ต์ •ํ•˜์—ฌ GUI์™€ ๋™์ผํ•œ `max_columns_reached` ์„ฑ์žฅ ๊ฐ€๋“œ ์ ์šฉ. `test_bug4_headless_unobservable_fast_path`์— 5.0์ดˆ ์ƒํ•œ ์‹œ๊ฐ„ ๋‹จ์–ธ์„ ๊ณ„์•ฝ์œผ๋กœ ๊ณ ์ •. - - ํšŒ๊ท€ ๊ฐ€๋“œ: `tests/test_layout.py` (19๊ฐœ ํ…Œ์ŠคํŠธ 100% ํ†ต๊ณผ), `tests/test_b19_headless_reconcile_fixes.py` (6๊ฐœ ํ…Œ์ŠคํŠธ 100% ํ†ต๊ณผ). + - ํ›„์† ์ •๋ฆฌ (I-2/I-3/C-1/J-1/J-2): `PaneInfo.focused` ๋ฏธ์‚ฌ์šฉ ํ•„๋“œ ์ •๋ฆฌ, `MAM_MAX_PANE_COLS`/`MAM_MAX_COLS` env ๋ฐฐ์„  ์™„๋ฃŒ, ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ์—์„œ `max_columns`๋ฅผ ์šฐํšŒํ•˜๋˜ ๊ฒฐํ•จ(C-1)์„ ๊ต์ •ํ•˜์—ฌ GUI์™€ ๋™์ผํ•œ `max_columns_reached` ์„ฑ์žฅ ๊ฐ€๋“œ ์ ์šฉ. `test_bug4_headless_unobservable_fast_path`์— 5.0์ดˆ ์ƒํ•œ ์‹œ๊ฐ„ ๋‹จ์–ธ์„ ๊ณ„์•ฝ์œผ๋กœ ๊ณ ์ •. `_env_int`์˜ falsy-zero trap(J-1) ๋ฐ ๋ฌดํšจ ๋ณ„์นญ skip(C-2) ํ•ด์†Œ, ํ—ค๋“œ๋ฆฌ์Šค n=5 ํ™€์ˆ˜ ์ž„๊ณ„๊ฐ’ ๊ฒ€์ฆ(J-2). + - ํšŒ๊ท€ ๊ฐ€๋“œ: `tests/test_layout.py` (23๊ฐœ ํ…Œ์ŠคํŠธ 100% ํ†ต๊ณผ), `tests/test_b19_headless_reconcile_fixes.py` (6๊ฐœ ํ…Œ์ŠคํŠธ 100% ํ†ต๊ณผ). ### **B-19 (โœ… ์™„๋ฃŒ โ€” ํ—ค๋“œ๋ฆฌ์Šค ๋ถ„ํ•  ๋ ˆ์ด์•„์›ƒ 0ร—0 ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, reconcile SKILLS_DIR ๋ˆ„๋ฝ ๋ฐ Fast-path ๊ฒŒ์ดํŒ… ๋ณด์™„)** - **ํ˜„์ƒ**: diff --git a/tests/test_a4_adapter_contract.py b/tests/test_a4_adapter_contract.py index 1d3d6a8..7e39504 100644 --- a/tests/test_a4_adapter_contract.py +++ b/tests/test_a4_adapter_contract.py @@ -46,6 +46,15 @@ def test_agent_of_row_priority(): row3 = {'pane': {'cmd': 'cline'}} assert agent_of_row(row3) == 'cline' +def test_agent_of_row_pane_cmd_binary_path_and_failure(): + # pane.cmd ๊ฐ€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ ํ˜•ํƒœ์—ฌ๋„ ํ•ด์„๋œ๋‹ค + assert agent_of_row({'pane': {'cmd': '/usr/local/bin/agy'}}) == 'agy' + # ์„ธ ๊ฒฝ๋กœ ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด None โ€” ํ˜ธ์ถœ์ž๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ์†Œ์œ ํ•œ๋‹ค + assert agent_of_row({}, session_name='bad-session-name') is None + # ์ž…์–‘ ์กฐํšŒ์šฉ match_cmd=False ์—์„œ๋Š” pane.cmd ๋ฅผ ๋ณด์ง€ ์•Š๋Š”๋‹ค + assert agent_of_row({'name': 'agy-creator-01', 'pane': {'cmd': 'agy'}}, + match_cmd=False) is None + def test_adapter_required_properties(): from lib_py.agents.base import BaseAgentAdapter base = BaseAgentAdapter() diff --git a/tests/test_layout.py b/tests/test_layout.py index 06db096..f686a23 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -403,7 +403,79 @@ def test_headless_max_columns_growth_guard(): d3 = compute_2xk_layout(headless(3), max_columns=2) assert d3.direction == "down" and not d3.is_overflow + # n=5 is the first odd n that can discriminate: n//2 == 2 == max_columns, so an + # over-correction that also checked the cap on the odd branch would return + # overflow here. n=3 has n//2 == 1 and cannot reach the check at all. + d5 = compute_2xk_layout(headless(5), max_columns=2) + assert d5.direction == "down" and not d5.is_overflow + assert d5.reason == "headless_odd_down" + # When max_columns is not set, existing alternation is preserved (behavior neutrality) assert compute_2xk_layout(headless(4)).direction == "right" +_LAYOUT_ENV_VARS = ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", "MAM_MIN_ROWS", + "MAM_MIN_PANE_ROWS", "MAM_MAX_COLS", "MAM_MAX_PANE_COLS") + + +def _run_layout(payload, args=(), env_extra=None): + env = {**os.environ, "PYTHONPATH": os.path.abspath(".agents/skills")} + for k in _LAYOUT_ENV_VARS: + env.pop(k, None) # ํ˜ธ์ถœ์ž ์…ธ์˜ ์˜ค์—ผ ์ฐจ๋‹จ + env.update(env_extra or {}) + res = subprocess.run([sys.executable, "-m", "lib_py.layout", "--json", *args], + input=json.dumps(payload), capture_output=True, text=True, env=env) + assert res.returncode == 0, res.stderr + return json.loads(res.stdout) + + +# height//2 = 15 < min_rows(20) ๋กœ ์ œ์•ฝ ๋ถ„๊ธฐ ์ง„์ž…, width//2 = 25 ๊ฐ€ min_cols ์™€ ๋น„๊ต๋จ. +_ZERO_TRAP = {"result": {"panes": [ + {"pane_id": "p1", "rect": {"x": 0, "y": 0, "width": 50, "height": 30}}]}} + + +def test_j1_env_zero_min_cols_matches_flag_zero(): + """J-1: MAM_MIN_PANE_COLS=0 must mean 0, not fall through to the 60 default.""" + flag = _run_layout(_ZERO_TRAP, ("--min-cols", "0")) + assert flag["direction"] == "right" and flag["reason"] == "single_pane_height_constrained" + for var in ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS"): + assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var + + +def test_j1_env_zero_min_rows_matches_flag_zero(): + flag = _run_layout(_ZERO_TRAP, ("--min-rows", "0")) + assert flag["direction"] == "down" and flag["reason"] == "single_pane_split_down" + for var in ("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS"): + assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var + + +def test_j1_nonzero_and_malformed_env_behaviour_unchanged(): + """Behaviour neutrality: non-zero env still applies, and a lone typo still + lands on the documented default instead of crashing on a None comparison.""" + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) == \ + _run_layout(_ZERO_TRAP, ("--min-cols", "25")) + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "abc"}) == _run_layout(_ZERO_TRAP) + + +def test_j1b_invalid_alias_does_not_shadow_the_documented_var(): + """C-2: MAM_MIN_COLS is a legacy alias checked first; MAM_MIN_PANE_COLS is the + name .mam.env.example documents. An unparsable value in the alias must be + skipped, not abort the search and discard the documented setting. + + Empty values already fell through (`if raw:`); this makes invalid values + behave the same way. When every candidate is unusable, `default` still wins. + """ + good = _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) + assert good["direction"] == "right" + # ๋ณ„์นญ์ด ๊นจ์ ธ ์žˆ์–ด๋„ ๋ฌธ์„œํ™”๋œ ๋ณ€์ˆ˜๊ฐ€ ์ ์šฉ๋œ๋‹ค + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", + "MAM_MIN_PANE_COLS": "25"}) == good + # 0 ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€ (J-1 ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ) + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", + "MAM_MIN_PANE_COLS": "0"}) == \ + _run_layout(_ZERO_TRAP, ("--min-cols", "0")) + # ๋ชจ๋“  ํ›„๋ณด๊ฐ€ ๋ฌดํšจ๋ฉด ๋ฌธ์„œํ™”๋œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํก์ˆ˜ (Rev.1 ๋ถˆ๋ณ€์‹ ๋ณด์กด) + assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", + "MAM_MIN_PANE_COLS": "bar"}) == _run_layout(_ZERO_TRAP) + + diff --git a/tests/test_tier2_component.py b/tests/test_tier2_component.py index da1a6a8..9c307e4 100644 --- a/tests/test_tier2_component.py +++ b/tests/test_tier2_component.py @@ -1,4 +1,5 @@ import os +import re import shutil import json import sqlite3 @@ -293,7 +294,7 @@ d['herdr_sessions'] = [{ # ============================================================================== -# FEATURE 3: Stop Session (4 Test Cases) +# FEATURE 3: Stop Session (7 Test Cases) # ============================================================================== def test_comp_stop_sqlite_state_update(mam_sandbox): @@ -411,6 +412,70 @@ d['herdr_sessions'] = [{ assert any(("send" in call or "prompt" in call) and "/exit" in call for call in calls) +def test_comp_stop_agent_fallback_reads_pane_cmd(mam_sandbox): + """B-21: --agent ์ƒ๋žต ์‹œ ์„ธ์…˜๋ช…์— ์—์ด์ „ํŠธ ์ ‘๋ฏธ์‚ฌ๊ฐ€ ์—†์–ด๋„ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ํ–‰์˜ + pane.cmd ๋กœ ํ•ด์„๋œ๋‹ค (๋ผ์ด๋ธŒ `agy-creator-01` ํ˜•ํƒœ).""" + mutation = """ +d['herdr_sessions'] = [{ + 'name': 'agy-creator-01', + 'status': 'running', + 'pane': {'cwd': 'WS_PLACEHOLDER', 'cmd': 'agy'} +}] +""".replace("WS_PLACEHOLDER", str(mam_sandbox)) + run_mutation(mam_sandbox, mutation) + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh" + res = subprocess.run(["bash", str(script), "--session", "agy-creator-01"], + capture_output=True, text=True) + assert res.returncode == 0, res.stderr + assert re.search(r"^\s*agent:\s+agy\s*$", res.stdout, re.M), res.stdout + + +def test_comp_stop_agent_fallback_prefers_explicit_agent_field(mam_sandbox): + """์šฐ์„ ์ˆœ์œ„ ๊ณ„์•ฝ: ๋ช…์‹œ `agent` ํ•„๋“œ๊ฐ€ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ์™€ pane.cmd ๋ฅผ ๋ชจ๋‘ ์ด๊ธด๋‹ค.""" + mutation = """ +d['herdr_sessions'] = [{ + 'name': 'x-creator-claude', + 'status': 'running', + 'agent': 'hermes', + 'pane': {'cwd': 'WS_PLACEHOLDER', 'cmd': 'claude'} +}] +""".replace("WS_PLACEHOLDER", str(mam_sandbox)) + run_mutation(mam_sandbox, mutation) + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh" + res = subprocess.run(["bash", str(script), "--session", "x-creator-claude"], + capture_output=True, text=True) + assert res.returncode == 0, res.stderr + assert re.search(r"^\s*agent:\s+hermes\s*$", res.stdout, re.M), res.stdout + + +# ์ฝ”๋“œ ํŽœ์Šค ์•ˆ์˜ stop_session.sh ํ˜ธ์ถœ์„ '๋ช…๋ น ๋‹จ์œ„'๋กœ ์ž˜๋ผ๋‚ธ๋‹ค. +# - ํŽœ์Šค ์Šค์ฝ”ํ”„: ์‚ฐ๋ฌธ ์† `stop_session.sh` ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•˜์ง€ ์•Š๋Š”๋‹ค +# (Pitfalls / When-NOT-to-use ์ ˆ์€ ์„ฑ๊ฒฉ์ƒ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฐ๋ฌธ์œผ๋กœ ์–ธ๊ธ‰ํ•œ๋‹ค). +# - ๋ช…๋ น ๋‹จ์œ„: ํ•œ ํŽœ์Šค์— ์—ฌ๋Ÿฌ ํ˜ธ์ถœ์ด ๋“ค์–ด ์žˆ์–ด๋„ ๊ฐ๊ฐ์„ ๋”ฐ๋กœ ๊ฒ€์ฆํ•œ๋‹ค +# (๋ธ”๋ก ๋‹จ์œ„๋กœ ๋ณด๋ฉด ๊ทธ์ค‘ ํ•˜๋‚˜๋งŒ --agent ๋ฅผ ๊ฐ€์ ธ๋„ ํ†ต๊ณผํ•ด ๋ฒ„๋ฆฐ๋‹ค). +_FENCE_RE = re.compile(r"```(?:bash|sh)\n(.*?)```", re.S) +_STOP_CALL_RE = re.compile(r"(?:bash\s+)?\S*stop_session\.sh[^\n\\]*(?:\\\n[^\n\\]*)*") + + +def test_comp_docs_stop_examples_pass_agent(): + """B-21 ๋ฌธ์„œ ๊ณ„์•ฝ: ๋ฌธ์„œ์˜ ๋ชจ๋“  stop_session.sh ์˜ˆ์ œ๋Š” --agent ๋ฅผ ๋„˜๊ธด๋‹ค. + ๋ฌธ์„œ ๋ณ€๊ฒฝ์€ ๋ฎคํ…Œ์ด์…˜ ๊ฐ๋„๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ด ๊ฐ€๋“œ๊ฐ€ ํ‘œ์ค€์˜ ์œ ์ผํ•œ ์ง‘ํ–‰ ์žฅ์น˜๋‹ค.""" + repo = Path(__file__).resolve().parent.parent + expected = { # ๋ฌธ์„œ๋ณ„ ์ตœ์†Œ ์˜ˆ์ œ ์ˆ˜ โ€” ์˜ˆ์ œ๋ฅผ ์ง€์›Œ ๊ฐ€๋“œ๋ฅผ ๋ฌด๋ ฅํ™”ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”๋‹ค + repo / ".agents/skills/multi-agent-mux-stop/SKILL.md": 3, + repo / "deploy/INSTALL.md": 2, + } + for doc, floor in expected.items(): + seen = 0 + for block in _FENCE_RE.findall(doc.read_text()): + for m in _STOP_CALL_RE.finditer(block): + snippet = m.group(0) + seen += 1 + assert "--agent" in snippet, \ + f"{doc.name}: stop_session.sh example without --agent:\n{snippet}" + assert seen >= floor, f"{doc.name}: expected >= {floor} examples, saw {seen}" + + # ============================================================================== # FEATURE 4: Status Query (5 Test Cases) # ==============================================================================