Files

324 lines
18 KiB
Markdown

# 리뷰 및 보완 구현 계획서 Rev.2 — A-1 / A-5 / .mam.env
**Job**: `4dbf4feb` | **Role**: Planner | **작성일**: 2026-08-05
**대체 대상**: Rev.1 (`e691297c`) — 본 문서가 우선한다
**반영 피드백**: Creator `agy` Challenge Report `3daf49ab`
**리뷰 대상 코드**: `68eff79..8dcb2b2` + 미커밋 워킹트리 1건 (변동 없음)
Rev.1의 §1~§11은 아래에서 명시적으로 수정하지 않은 한 그대로 유효하다.
본 문서는 **R-3 / F4 하나**를 재설계하고, 그 과정에서 발견한 결함 1건을 추가한다.
---
## 0. 이의제기 판정
`agy`의 주장을 넷으로 분해해 각각 실행으로 검증했다.
| # | `agy`의 주장 | 판정 | 근거 |
|---|---|---|---|
| 1 | `derive_session_name``agent_type`을 요구하는데 `resolve_herdr_session`에는 그 정보가 없어 **폴백 로직이 붕괴**한다 | **반증(사유), 인정(증상)** | 슬러그는 agent 인자 유무와 **무관하게 동일**하다(E-A). 붕괴하는 진짜 원인은 정보 부재가 아니라 `set -u` 하의 **인자 개수**이며, `${2:-}` 한 글자로 해소된다 |
| 2 | herdr 세션명은 워크스페이스 수준이어야 하고, create의 `sed 's/-creator-.*//'`가 그 증거다 | **인정** | 옳다. 전용 헬퍼를 두는 **형태(shape)는 채택**한다 |
| 3 | 대안 — `derive_workspace_slug` 신설 후 양쪽에서 직접 호출 | **구현 기각** | 제시된 구현이 `create_session.sh`**4개 경로 전수 불일치**(E-B). F4가 없애려던 이중 규칙을 **세 번째 규칙**으로 되살린다 |
| 4 | 그 헬퍼를 `resolve_herdr_session` 폴백에서 `derive_workspace_slug "$WORKSPACE"`로 호출 | **설계 기각** | `resolve_herdr_session` 스코프에는 `$WORKSPACE`**없다**(E-C). `${1:-$PWD}` 기본값이 오늘의 cwd 의존을 그대로 물려받아, 같은 세션명이 호출 위치마다 다른 herdr 세션으로 해석된다(E-D) |
**요약**: `agy`**옳은 형태를 틀린 이유로, 틀린 구현과 함께** 제안했다.
헬퍼 도입은 채택한다. 다만 규칙을 새로 쓰는 대신 **기존 규칙을 추출**해야 하고,
빠진 파라미터는 `$AGENT`가 아니라 `$WORKSPACE`다 — `agy`는 자신이 진단한 결여를
자기 처방에서 그대로 반복했다.
---
## 1. 신규 측정 증거
### E-A — agent 인자는 슬러그에 아무 영향이 없다
```
$ derive_session_name "$WS" claude | sed 's/-creator-.*//' -> [parent-dir-my-project]
$ derive_session_name "$WS" | sed 's/-creator-.*//' -> [parent-dir-my-project]
$ derive_session_name "$WS" "" | sed 's/-creator-.*//' -> [parent-dir-my-project]
```
`derive_session_name``printf '%s-creator-%s' "$slug" "$agent"`로 끝난다.
슬러그는 **workspace 경로만으로** 계산되고 agent는 접미사에만 쓰인다.
`sed`가 그 접미사를 잘라내므로 agent가 비어 있어도 결과가 같다.
따라서 "에이전트 타입 정보 부재로 폴백이 붕괴한다"는 인과는 성립하지 않는다.
**다만 붕괴 자체는 실재한다 — 원인이 다르다.**
```
$ set -u; derive_session_name "$WS"
ABORT: .agents/skills/lib.sh: line 595: $2: unbound variable
```
그리고 `resolve_herdr_session`을 호출하는 스크립트는 **8개 전부** `set -euo pipefail`이다:
```
create_session.sh run_loop.sh resume_session.sh reconcile.sh
resolve_session_id.sh update_yaml_resumed.sh status.sh stop_session.sh
```
즉 실제 위험은 **셸 엄격 모드에서의 인자 개수**이고, `local agent="${2:-}"` 로 끝난다.
`agy`의 결론(직접 호출하지 말라)은 방어 가능하나, 제시한 이유는 틀렸고
그 이유를 근거로 설계를 바꾸면 엉뚱한 곳을 고치게 된다.
### E-B — `agy`가 제시한 구현은 4개 경로 전수 불일치
챌린지 리포트의 함수를 **원문 그대로** 옮겨 `create_session.sh`의 실제 산출물과 대조했다.
```
workspace create_session.sh writes agy derive_workspace_slug
parent_dir/my_project mam-parent-dir-my-project mam-parentdir-myproject ** MISMATCH **
Upper_Case/Web_App mam-upper-case-web-app mam-uppercase-webapp ** MISMATCH **
/tmp mam-workspace-tmp mam--tmp ** MISMATCH **
/ mam-workspace-root mam-- ** MISMATCH **
```
원인 두 가지:
1. **밑줄 처리가 반대다.** `derive_session_name``tr '_' '-'`로 **변환**하는데,
`agy`의 구현은 `tr -cd 'a-z0-9-'`로 **삭제**한다. `my_project`
`my-project`가 아니라 `myproject`가 된다.
2. **경계 가드가 없다.** `derive_session_name`은 부모가 `/`·`.`·빈 문자열일 때
`workspace`를, 작업 디렉터리가 그럴 때 `root`를 대입하고 선행 하이픈을 제거한다.
`agy`의 구현에는 이 가드가 전부 없어 `/tmp`에서 `mam--tmp`,
루트에서 `mam--`라는 **사실상 이름이 아닌 문자열**을 만든다.
R-3은 "규칙이 두 개라 서로 다르다"는 결함이다.
이 처방은 **세 번째 규칙을 추가해 세 개로 만든다.** 고치려던 문제를 악화시킨다.
### E-C — 폴백에 없는 파라미터는 `$AGENT`가 아니라 `$WORKSPACE`다
```
lib.sh:555 resolve_herdr_session() {
lib.sh:556 local session_name="$1"
# 인자는 세션명 하나. workspace도 agent도 없다.
```
`agy`의 제안 `derive_workspace_slug "$WORKSPACE"`는 이 스코프에서 **정의되지 않은 변수**를 쓴다.
그래서 그들의 헬퍼는 `local ws="${1:-$PWD}"`로 조용히 `$PWD`를 대신 쓴다 —
그 순간 자신이 지적한 "정보 결여"를 그대로 재현한다.
호출자별 workspace 보유 현황(실측):
```
create_session.sh has --workspace L190 resolve_herdr_workspace "$SESSION_NAME"
resume_session.sh has --workspace L47 resolve_herdr_session "$SESSION_NAME"
update_yaml_resumed.sh has --workspace L40 resolve_herdr_session "$SESSION_NAME"
stop_session.sh no --workspace L85 resolve_herdr_workspace "$SESSION_NAME"
```
**4곳 중 3곳은 workspace를 이미 갖고 있으면서 넘기지 않고 있다.**
`stop_session.sh`만 없는데, 그조차 L117에서 `TARGET_CWD`를 읽으므로 **순서만 바꾸면** 확보된다.
### E-D — cwd 폴백은 같은 세션명을 호출 위치마다 다르게 해석한다
미등록 세션명 하나를 세 디렉터리에서 조회했다.
```
cwd=/Users/…/canary_projects/multi-agent-mux -> mam-multi-agent-mux
cwd=/tmp -> mam-tmp
cwd=/…/scratchpad -> mam-scratchpad
```
동일 입력, 세 가지 답이다. 그리고 첫 줄은 R-3을 라이브로 재확인해 준다 —
이 워크스페이스에서 create가 만드는 이름은 `mam-canary-projects-multi-agent-mux`인데
폴백은 `mam-multi-agent-mux`를 낸다.
`agy``${1:-$PWD}`는 이 동작을 **그대로 보존**한다.
---
## 2. 신규 결함
| ID | 결함 | 증거 | 등급 |
|---|---|---|---|
| **R-12** | `resolve_herdr_session`의 폴백이 **호출자의 cwd를 워크스페이스로 추측**한다. 같은 세션명이 호출 위치에 따라 다른 herdr 세션으로 해석되어, stop/resume이 생성된 적 없는 세션을 대상으로 삼는다. R-3(규칙 두 개)보다 근본적이다 — 규칙을 통일해도 **입력이 틀리면 결과는 여전히 틀리다** | E-D, E-C | **높음** |
Rev.1 대비 총계: **12건** (치명 3, 높음 4, 중간 5).
---
## 3. 재설계 — R-3 / F4
### 3.1 설계 원칙
`agy`의 형태를 채택하되 두 가지를 바꾼다.
1. **규칙을 새로 쓰지 않고 추출한다.** `derive_workspace_slug`
`derive_session_name`의 정규화 블록을 **그대로 옮긴 것**이어야 하고,
`derive_session_name`은 그 헬퍼를 호출해 접미사만 붙이도록 재작성한다.
그래야 규칙이 물리적으로 하나가 된다. 두 함수가 "같은 규칙을 따르기로 합의"하는 구조는
R-3이 이미 실패를 증명했다.
2. **추측하지 않고 전달받는다.** `resolve_herdr_session`에 선택적 두 번째 인자
`[workspace]`를 추가하고, workspace를 아는 호출자는 반드시 넘긴다.
**모를 때는 cwd로 추측하지 않고 `default`를 반환하며 stderr에 경고한다.**
틀린 세션을 조용히 가리키는 것보다 `default`가 안전하다 — 최소한 관측 가능하다.
### 3.2 추출안 검증
제안한 추출 구현을 실제로 작성해 5개 경로에서 대조했다.
```
parent_dir/my_project create=parent-dir-my-project extracted=parent-dir-my-project MATCH
Upper_Case/Web_App create=upper-case-web-app extracted=upper-case-web-app MATCH
/tmp create=workspace-tmp extracted=workspace-tmp MATCH
/ create=workspace-root extracted=workspace-root MATCH
canary_projects/multi-… create=canary-projects-multi-… extracted=canary-projects-multi-… MATCH
derive_session_name : parent-dir-my-project-creator-claude
derive_workspace_slug+sfx : parent-dir-my-project-creator-claude
```
**5/5 일치**, 그리고 `derive_session_name``derive_workspace_slug` + `-creator-<agent>`
정확히 분해된다. 이 형태면 `create_session.sh``sed`도 사라진다 —
`agy`가 지적한 냄새의 근본 제거다.
### 3.3 `set -u` 대응
`derive_session_name``local agent="$2"``local agent="${2:-}"`로 바꾼다(E-A).
추출 후에도 이 함수는 남으므로(호출자 다수) 방어는 필요하다.
`agy`가 감지한 증상에 대한 **정확한 크기의 수정**이다.
### 3.4 채택하지 않은 것
- **`tr -cd 'a-z0-9-'` 방식** — 밑줄을 삭제해 기존 이름과 어긋난다(E-B).
- **`${1:-$PWD}` 기본값** — cwd 추측을 영속화한다(E-D). 명시 전달 또는 `default`.
- **`derive_session_name`을 그대로 호출하고 `sed`로 자르는 방식(Rev.1 F4 원안)** —
동작은 하지만(E-A) 문자열 조작이 남고 `set -u` 지뢰를 유지한다.
`agy`의 §2 지적이 이 부분에서는 맞다.
---
## 4. Rev.1 대비 변경
| 항목 | Rev.1 | Rev.2 |
|---|---|---|
| F4 | `resolve_herdr_session` 폴백이 `derive_session_name` 재사용 | **F4a/F4b/F4c로 분할.** 추출 헬퍼 + workspace 파라미터 + cwd 추측 제거 |
| 결함 수 | 11건 | **12건** (R-12 추가) |
| 차단 항목 | BK-A, BK-B | **BK-A, BK-B, BK-C** |
| V-6 | 두 슬러그가 같은 문자열을 낸다 | 유지 + V-11~V-15 추가 |
---
## 5. 수정된 커밋 계획 (변경분만)
Rev.1의 F1·F2·F3·F5~F9는 그대로다. F4만 분할한다.
| # | 커밋 | 내용 | 선행 |
|---|---|---|---|
| **F4a** | `refactor(lib): extract derive_workspace_slug as the single naming rule` | `derive_session_name`의 정규화 블록을 헬퍼로 추출하고, `derive_session_name`은 그 헬퍼 + 접미사로 재작성. `local agent="${2:-}"` 방어 포함. **동작 변화 0 — 순수 리팩터** | F2 |
| **F4b** | `feat(lib): let callers pass the workspace to resolve_herdr_session` | 선택적 2번째 인자 추가. 폴백이 `derive_workspace_slug "$ws"` 사용. `create/resume/update_yaml_resumed`가 보유 중인 workspace 전달. `stop_session.sh``TARGET_CWD` 조회를 L85 앞으로 옮겨 전달 | **F4a** |
| **F4c** | `fix(lib): stop guessing the workspace from the caller's cwd` | R-12. workspace 미지정 시 `default` 반환 + stderr 경고 1회. **F4b와 같은 커밋에 넣지 않는다** — 전달 경로가 먼저 완성되어야 이 변경이 안전하다 | **F4b** |
`create_session.sh:153``derive_session_name … | sed 's/-creator-.*//'`도 F4a에서
`derive_workspace_slug "$WORKSPACE"`로 교체한다.
### 순서 근거
- **F4a는 순수 리팩터**여야 한다. 동작 변경과 섞으면 5/5 일치(§3.2)를 회귀로 검증할 수 없다.
- **F4a → F4b**: 헬퍼가 없으면 전달할 대상이 없다.
- **F4b → F4c**(BK-C): 전달 경로가 완성되기 전에 cwd 추측을 없애면,
아직 workspace를 넘기지 않는 호출자가 전부 `default`로 떨어져 **stop이 세션을 못 찾는다**
R-1과 정확히 같은 고아 pane 증상을 새로 만든다.
- F4 계열 전체는 **F2 이후**다. rename이 끝나기 전에 이름 규칙을 건드리면
어느 층에서 깨졌는지 분간할 수 없다.
---
## 6. 추가 테스트
Rev.1 V-1~V-10은 유효하다(V-6은 아래 V-11로 강화).
| ID | 검증 |
|---|---|
| **V-11** | `derive_workspace_slug``create_session.sh`의 실제 산출물이 **5개 경로 전수 일치**: 밑줄, 대문자, `/tmp`, `/`, 실제 저장소. §3.2 매트릭스를 회귀로 고정 |
| **V-12** | `derive_session_name "$WS" "$agent"` == `derive_workspace_slug "$WS"` + `-creator-$agent` (분해 항등식) |
| **V-13** | `set -u` 하에서 `derive_session_name "$WS"`**중단되지 않는다** — F4a 이전 반드시 실패 (E-A 재현) |
| **V-14** | 동일 세션명을 서로 다른 cwd 3곳에서 조회해도 **같은 결과**를 낸다 — F4c 이전 반드시 실패 (E-D 재현) |
| **V-15** | workspace 미지정 시 `default` + stderr 경고. cwd 기반 추측 문자열이 나오지 않는다 |
| **V-16** | `grep -rn "sed 's/-creator" .agents/skills` 결과 0 (문자열 조작 제거 확인) |
**신규 6건.** V-13·V-14는 수정 전 반드시 실패해야 한다.
**V-11은 `agy`의 구현이 통과하지 못하는 테스트**이며(E-B 4/4 불일치),
어떤 구현이든 이 테스트를 먼저 세우면 규칙이 셋으로 늘어나는 것을 막는다.
---
## 7. 추가 DoD 게이트
Rev.1 게이트 A~G에 더한다.
| 게이트 | 조건 |
|---|---|
| **H** | F4a 커밋의 diff가 **동작 변경 0**임을 V-11/V-12로 입증 (리팩터 순수성) |
| **I** | `grep -rn 'basename' .agents/skills/lib.sh .agents/skills/*/scripts` 결과에 **워크스페이스 슬러그를 만드는 두 번째 구현이 없다** |
| **J** | F4c 이후 `resolve_herdr_session`이 workspace 없이 호출되는 지점이 0 (있다면 그 호출자가 `default`를 받아도 무해함을 명시) |
---
## 8. 추가 리스크
| ID | 리스크 | 완화 |
|---|---|---|
| **RK-F** | F4c가 workspace를 넘기지 않는 잔여 호출자를 `default`로 떨어뜨려 R-1과 같은 고아 pane을 유발 | BK-C 순서 + 게이트 J + V-15. F4b에서 전 호출자 전달을 완료한 뒤에만 F4c 착수 |
| **RK-G** | F4a 리팩터가 미묘하게 이름을 바꿔 **기존에 만들어진 herdr 세션과 어긋난다** | V-11의 5경로 전수 대조를 F4a **이전에 먼저 작성**해 현재 값을 스냅샷으로 고정. 리팩터는 그 스냅샷을 깨지 않아야 함 |
---
## 9. 차단 항목 (갱신)
BK-A, BK-B는 Rev.1과 동일하다. 하나 추가한다.
> **BK-C — cwd 추측 제거(F4c)는 workspace 전달 완료(F4b) 이후에만.**
> 순서를 뒤집으면 아직 workspace를 넘기지 않는 호출자가 전부 `default`를 받아
> 살아있는 세션을 찾지 못한다. 이는 Rev.1 R-1이 만든 고아 pane과 **동일한 증상**을
> 새 경로로 재생산하는 것이다. 두 커밋을 합치는 것도 금지한다 —
> 합치면 F4b의 전달 경로가 올바른지 독립적으로 검증할 수 없다.
**차단 항목은 BK-A, BK-B, BK-C 3건이다.**
---
## 10. `agy`에 대한 평가
이번 이의제기는 **Rev.1 F4의 실질적 약점을 짚었다.** `derive_session_name`을 직접 호출하고
`sed`로 접미사를 깎아내는 방식은 확실히 나쁜 형태이고, 전용 워크스페이스 슬러그 헬퍼가
옳다는 §2 지적은 그대로 채택했다. `create_session.sh``sed`를 쓰는 이유를
개념적 불일치의 증거로 읽은 것도 정확한 독해다.
동시에 검증이 빠진 부분도 분명하다.
- 핵심 논거인 "agent 정보 부재로 폴백 붕괴"는 **한 번 실행해 보면 반증된다**(E-A).
슬러그는 agent와 무관하다. 실제 붕괴 원인은 `set -u` 인자 개수이며,
이를 오진한 탓에 처방이 필요 이상으로 커졌다.
- 제시한 구현은 **자기 목적을 달성하지 못한다**. 4개 경로 전수 불일치이고(E-B),
특히 밑줄을 삭제해 `my_project → myproject`를 만든다 —
R-3이 지적한 바로 그 종류의 불일치를 새로 만든다.
- 가장 아쉬운 점은 **자기가 진단한 결여를 자기 처방에서 반복**했다는 것이다.
`derive_workspace_slug "$WORKSPACE"``$WORKSPACE`는 그 스코프에 없고,
헬퍼의 `${1:-$PWD}`가 그 사실을 조용히 덮는다(E-C). 빠진 파라미터는 `$AGENT`가 아니라
`$WORKSPACE`였고, 그것을 끝까지 따라갔다면 R-12에 스스로 도달했을 것이다.
결과적으로 이 라운드에서 **R-12가 발굴되었고, F4의 형태가 개선되었다.**
그 두 가지는 이의제기가 없었다면 나오지 않았다.
---
## 11. Planner 경계
본 문서는 **설계·리뷰 산출물**이며 저장소 코드는 한 줄도 수정하지 않았다.
모든 프로브(`agy` 제안 함수의 원문 재현, 추출안 프로토타입, cwd 3분기 대조)는
`/tmp` 하위 임시 디렉터리에서만 실행했고 종료 시 제거했다.
라이브 herdr 세션에는 이번 라운드에서 접근하지 않았다.
워킹트리의 `M .agents/skills/lib.sh`는 **여전히 미커밋 상태**이며 리뷰 대상이다
(Rev.1 R-6 / BK-A — 변동 없음).
`*.tmp` 잔재는 이번 job 시작 시점에도 또 관측되었다
(`…31385_22852.tmp`**4회 연속 재발**, Rev.1 B-6).
`MULTI_AGENT_RULES.md` §1에 따라 **구현은 Creator, 커밋은 GM 소관**이다.
**차단 항목은 BK-A, BK-B, BK-C 3건이며, F1 커밋이 여전히 모든 작업의 선행 조건이다.**
[AGREEMENT: REACHED]