docs: add VERSIONS.md, resolve C-6 stop_session usage/comments drift, and prune LOG.md

This commit is contained in:
2026-08-17 09:39:21 +09:00
parent 5ed39f899b
commit ac97550e13
8 changed files with 856 additions and 186 deletions
@@ -0,0 +1,391 @@
# 📐 구현 계획서 Rev.2 — C-6: `stop_session.sh` 레거시 주석 및 구버전 사용법 정리
- **Job ID**: `32167a9d` (Rev.1 = `73b18819`)
- **Planner**: claude (session: `herdr:canary-projects-multi-agent-mux-creator-claude`)
- **Role**: Planner (`MULTI_AGENT_RULES.md` §1 — 본 작업에서 저장소 코드 0건 수정)
- **반영 대상 Challenge**: `8b6b574f` (agy, Worker / Plan Reviewer) — `[VERDICT: PASS WITH CHALLENGE]`
- **기준 커밋**: `5ed39f8` (`refactor`, 작업 트리에 미추적 `VERSIONS.md` 1건)
- **백로그 항목**: C-6 / 로드맵 P2-3
---
## 0. 요약
**Challenge 는 타당합니다. 전면 수용합니다.** 격리 클론에서 실제 `mam_sandbox` 픽스처로 실행해 재현했습니다 — Rev.1 §4.2-(2) 는 4개 하위 케이스 중 **3개가 `rc=2` 로 실패**했을 것입니다.
다만 Rev.2 는 챌린저의 권고안을 그대로 채택하지 않고 **두 가지를 더합니다**.
1. 챌린저 권고(`valid_session` 사용)는 증상을 해소하지만, 가드를 **C-6 과 무관한 불변식**(`:91-100` 에이전트 접미사 명명 규칙)에 결합시킵니다. `rc=2` 가 **5가지 서로 다른 원인**에 공유되고 있다는 것이 이 오탐의 근본 원인이므로, Rev.2 는 종료 코드 대신 **stderr 메시지를 단언**해 원인 결합 자체를 제거합니다.
2. 확정 가드를 **뮤테이션으로 검증하는 과정에서, 챌린저도 저도 놓쳤던 구멍 1건**을 찾았습니다 — Rev.1 이 §1.1 에 결함으로 등재한 `usage():41` 의 `--agent claude|agy` 과소 표기를, Rev.1·챌린저 양쪽 가드 모두 **탐지하지 못합니다**(M3). Rev.2 에서 닫았습니다.
| 항목 | Rev.1 | Rev.2 |
|---|---|---|
| §4.2-(2) 세션명 | `nosuch` (**오탐 — 3/4 rc=2**) | `test-project-creator-claude` |
| §4.2-(2) 단언 | `rc != 2` 단독 | **stderr 메시지 단언** + `rc != 2` 보조 |
| `usage()` 에이전트 목록 검증 | **없음 (M3 구멍)** | **추가** |
| 가드 뮤테이션 검증 | 계획만 제시 | **3종 실측 완료** |
| 나머지(§1~§3, §5, §7) | — | 변경 없음 |
---
## 1. Challenge 판정 — 수용 (실측 재현)
### 1.1 챌린저 지적의 사실 확인
챌린저가 인용한 블록은 실재합니다. 정확한 위치는 **`:91-100`**(챌린저 표기 `:92-100`), `exit 2`**`:98`** 입니다.
```bash
# stop_session.sh:91-100
# --agent 미지정 시 이름 suffix 로 fallback (P1-F)
if [ -z "$AGENT" ]; then
case "$SESSION_NAME" in
*-creator-claude|*-planner-claude|*-reviewer-claude) AGENT=claude ;;
...
*) echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2; exit 2 ;; # :98
esac
fi
```
챌린저가 지적한 **실행 순서도 정확**합니다. YAML 존재 검사는 `:80`, 에이전트 추론은 `:91` 이므로 추론이 뒤에 옵니다. 그리고 `tests/conftest.py:15-52` 의 `mam_sandbox` 픽스처는 `agent-sessions.yaml` 을 **실제로 생성합니다**(`herdr_sessions: []`). 따라서 `:80` 은 통과하고 `:98` 에 도달합니다 — "샌드박스 상태에 따라 결과가 뒤바뀐다"는 챌린저의 우려가 아니라, **결정론적으로 항상 실패**합니다.
### 1.2 실측 — 격리 클론 + 실제 `mam_sandbox` 픽스처
`git clone --local --no-hardlinks` 로 만든 클론에 프로브 테스트를 넣어 측정했습니다.
| `--session` | 추가 인자 | rc | stderr 첫 줄 |
|---|---|---|---|
| `nosuch` | `--reason x` | **2** | `cannot infer agent from 'nosuch'` |
| `nosuch` | `--purge-conversation` | **2** | `cannot infer agent from 'nosuch'` |
| `nosuch` | `--yes` | **2** | `cannot infer agent from 'nosuch'` |
| `nosuch` | `--agent hermes` | 1 | `session 'nosuch' not in …yaml` |
| `test-project-creator-claude` | `--reason x` | 1 | `session … not in …yaml` |
| `test-project-creator-claude` | `--purge-conversation` | 1 | `session … not in …yaml` |
| `test-project-creator-claude` | `--yes` | 1 | `session … not in …yaml` |
| `test-project-creator-claude` | `--agent hermes` | 1 | `session … not in …yaml` |
| `test-project-creator-claude` | `--purge-conversation --yes` | 1 | `session … not in …yaml` |
**Rev.1 의 `assert r.returncode != 2` 는 4개 중 3개에서 실패**합니다(`--agent` 를 준 케이스만 추론을 건너뛰어 통과). Challenge 확정.
부수 확인: Rev.1 §9 한계에서 "`--purge-conversation``--yes` 없이 호출 시 rc=1 인지 rc=3 인지 구현 시 실측 필요"라고 남겼던 항목도 해소되었습니다 — **rc=1**(레지스트리 조회가 확인 프롬프트보다 먼저)입니다.
---
## 2. 챌린저 권고안 평가 — 채택하되 보강
### 2.1 권고안은 작동합니다
`valid_session = "test-project-creator-claude"``*-creator-claude` 에 접미사 매칭되어 `AGENT=claude` 로 추론되고, 4개 케이스 전부 rc=1 로 끝납니다(위 표 하단 5행). **측정으로 확인했습니다.**
### 2.2 그러나 근본 원인은 세션명이 아니라 `rc=2` 의 과부하입니다
`stop_session.sh` 에서 `exit 2` 는 **5곳**에서 발생합니다.
| 행 | 원인 |
|---|---|
| `:67` | 폐지 플래그(`--mode`/`--capture-id`/`--graceful`) |
| `:70` | `unknown arg` |
| `:76` | `invalid agent type` |
| `:79` | `--session` 누락 |
| `:98` | **`cannot infer agent`** ← 이번 오탐의 원인 |
가드가 검증하려는 것은 오직 `:70` 하나("도움말이 광고하는 플래그를 파서가 unknown 으로 튕기지 않는다")인데, `rc != 2` 는 나머지 4개와 구별하지 못합니다. 챌린저의 `valid_session``:98` 만 회피할 뿐 **`:76`·`:79` 는 여전히 구별하지 못하며**, 더 나쁘게는 가드를 `:91-100` 의 **에이전트 접미사 명명 규칙에 결합**시킵니다. 훗날 역할명이 추가되거나 `creator` 가 개명되면, C-6 가드가 C-6 과 무관한 이유로 깨지고 실패 메시지도 C-6 을 가리키지 않습니다.
### 2.3 Rev.2 의 보강 — stderr 메시지 단언
```python
assert "unknown arg" not in r.stderr # 파서가 이 플래그를 모른다고 하지 않았다
assert "deprecated" not in r.stderr # 폐지 플래그로 취급하지도 않았다
assert r.returncode != 2 # (보조) 위 둘을 빠져나간 rc=2 도 없다
```
이 단언은 5개 원인 중 정확히 검증 대상인 것만 지목합니다. 실측 표에서 확인되듯 `nosuch` 케이스의 stderr 는 `cannot infer agent` 이므로 **메시지 단언만으로는 세션명이 무엇이든 통과**합니다 — 즉 챌린저 권고보다 엄밀히 더 견고합니다.
**두 가지를 모두 채택합니다**: 챌린저의 `valid_session`(원인 제거) + 메시지 단언(결합 제거). 어느 한쪽이 미래에 무력화돼도 다른 쪽이 남습니다.
---
## 3. 🆕 Rev.2 신규 발견 — 가드가 `usage():41` 결함을 놓침 (M3)
확정 가드를 뮤테이션 검증하던 중 발견했습니다. **챌린저도 Rev.1 도 지적하지 못한 구멍입니다.**
Rev.1 §1.1 은 `usage():41``[--agent claude|agy]` 가 검증기(`:74-77`)의 4종 수용과 어긋난다고 **결함으로 등재**했습니다. 그런데 Rev.1·챌린저 양쪽 가드 모두 이 결함을 탐지하지 못합니다.
**뮤테이션 M3**: 수정된 클론에서 `usage()` 의 에이전트 목록만 `claude|agy` 로 되돌림
```
결과: 1 passed ← 가드가 통과시킴 ❌
```
C-6 이 고치기로 한 결함 중 하나가 가드 밖에 있었던 셈입니다. Rev.2 에서 다음 3줄로 닫았습니다.
```python
for agent in ("claude", "agy", "hermes", "cline"):
assert agent in res.stdout, f"usage() omits supported agent {agent}"
```
**재검증**: 강화 후 baseline `1 passed`, M3 재적용 시 `1 failed`. 구멍이 닫혔음을 실측했습니다.
---
## 4. 확정 회귀 가드
### 4.1 설계 원칙 (Rev.1 §4.1 유지)
직전 리뷰 `31730364` 에서 뮤테이션으로 드러난 실패 사례 — `test_delegate_agent_resolution_and_fallback` 이 테스트 파일 안에 `case` 문을 복사해 실행한 탓에 생산 코드 결함을 완전히 되돌려도 통과 — 를 반복하지 않도록, 가드는 `stop_session.sh`**직접 실행하고 그 파일을 직접 읽습니다**.
### 4.2 확정 코드 — `tests/test_tier2_component.py` 에 추가
```python
def test_comp_stop_usage_matches_parser(mam_sandbox):
"""C-6: help text and parser must not drift apart."""
script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh"
# 에이전트 접미사 추론(:91-100)이 성립하는 이름 — rc=2 의 다섯 원인 중
# 'cannot infer agent'(:98)를 배제하기 위함 (Challenge 8b6b574f)
VALID = "test-project-creator-claude"
# 1) --help 는 성공하고, 폐지된 플래그를 광고하지 않는다
res = subprocess.run(["bash", str(script), "--help"], capture_output=True, text=True)
assert res.returncode == 0
for dead in ("--mode", "--capture-id", "--graceful"):
assert dead not in res.stdout, f"usage() still advertises {dead}"
# 1b) 검증기가 받는 에이전트는 전부 도움말에 나온다 (Rev.2 M3)
for agent in ("claude", "agy", "hermes", "cline"):
assert agent in res.stdout, f"usage() omits supported agent {agent}"
# 2) 도움말이 광고하는 플래그는 전부 파서가 받는다
# rc=2 는 5가지 원인을 공유하므로 stderr 메시지로 직접 지목한다
for flag, args in (("--reason", ["--reason", "x"]),
("--purge-conversation", ["--purge-conversation"]),
("--yes", ["--yes"]),
("--agent", ["--agent", "hermes"])):
r = subprocess.run(["bash", str(script), "--session", VALID] + args,
capture_output=True, text=True)
assert "unknown arg" not in r.stderr, f"usage() advertises {flag} but parser rejects it: {r.stderr}"
assert "deprecated" not in r.stderr, f"usage() advertises deprecated {flag}: {r.stderr}"
assert r.returncode != 2, f"{flag} -> rc=2: {r.stderr}"
# 3) 폐지된 플래그는 전용 메시지와 함께 rc=2 로 거부된다 (특별 취급 유지)
for dead in ("--mode", "--capture-id", "--graceful"):
r = subprocess.run(["bash", str(script), "--session", VALID, dead, "hard"],
capture_output=True, text=True)
assert r.returncode == 2
assert "deprecated" in r.stderr
# 4) 헤더 주석도 폐지 플래그를 사용법으로 광고하지 않는다
head = "".join(script.read_text().splitlines(keepends=True)[:35])
assert "--mode soft|hard" not in head
```
### 4.3 뮤테이션 검증 — Rev.2 에서 실측 완료
Rev.1 은 뮤테이션을 "구현자 필수 수행"으로 지시만 했으나, Rev.2 는 **계획 단계에서 직접 수행**했습니다. 격리 클론에 §3 단계 1~2 의 문서 수정을 적용한 뒤:
| # | 뮤테이션 | 기대 | 실측 |
|---|---|---|---|
| — | (baseline, 수정 적용 상태) | PASS | **1 passed** ✅ |
| M1 | 파서에서 `--reason)` 분기 삭제 (도움말은 계속 광고) | FAIL | **1 failed**`:19` `unknown arg` 단언 ✅ |
| M2 | 헤더에 `[--mode soft\|hard]` 행 복원 | FAIL | **1 failed**`:30` 헤더 단언 ✅ |
| M3 | `usage()` 에이전트 목록을 `claude\|agy` 로 축소 | FAIL | 강화 전 **1 passed** ❌ → 강화 후 **1 failed** ✅ |
M1 이 가드의 핵심 가치를 증명합니다 — **도움말과 파서 중 한쪽만 바뀌면 즉시 실패**하며, 이것이 C-6 을 애초에 만든 드리프트입니다.
구현자는 위 표를 **재현**만 하면 됩니다(신규 설계 불필요).
---
## 5. 구현 계획 (Rev.1 대비 변경 없음)
### 단계 1 — 헤더 주석 교체 (`:2-30`, 29줄)
```bash
# stop_session.sh — multi-agent-mux-stop 의 부속 스크립트
# Usage:
# bash stop_session.sh --session <name> [--agent claude|agy|hermes|cline] \
# [--reason <reason>] [--purge-conversation] [--yes]
#
# 동작: 항상 graceful stop 입니다. send-keys 로 정상 종료를 유도하고
# (미종료 시 SIGTERM → SIGKILL 폴백), kill 직전에 이 워크스페이스의
# conversation id 를 row 에 확정 기록해 다음 resume 이 tier-1(race-free)
# 으로 복원되게 합니다. status 는 running -> stopped 로 전이합니다.
# 멱등: 이미 stopped 면 no-op + exit 0.
#
# 옵션:
# --session <name> — 대상 세션 (필수)
# --agent <type> — claude | agy | hermes | cline
# (미지정 시 세션명 접미사로 추론; 추론 실패 시 exit 2)
# --reason <reason> — 상태 전이 사유 (stop_reason). 기본값 manual_stop
# --purge-conversation — 디스크의 conversation artifact 까지 삭제.
# status=terminated, resumable=false 로 전이하며
# resume 불가. --yes 없이는 확인 프롬프트(exit 3)
# --yes — --purge-conversation 의 확인 프롬프트 생략
#
# 폐지된 옵션: --mode / --capture-id / --graceful 는 각각 exit 2 로 거부됩니다.
# graceful 종료와 id 캡처는 이제 무조건 수행되며, soft/hard 모드
# 구분은 --purge-conversation 유무로 대체되었습니다.
#
# Exit codes:
# 0 = success (or already-stopped no-op) | 1 = YAML not found / not registered
# 2 = invalid args | 3 = interactive confirmation required (--yes 누락)
# 4 = purge aborted (herdr session survived the kill chain)
```
> **Rev.2 추가**: `--agent` 항목에 접미사 추론 동작(`:91-100`)을 한 줄 명기합니다. Challenge 가 드러냈듯 이 동작은 문서화되어 있지 않아 계획자·리뷰어 양쪽이 놓쳤던 부분입니다. C-6 의 취지("문서가 실제 동작과 일치할 것")에 정확히 부합합니다.
### 단계 2 — `usage()` 보강 (`:39-47`)
```bash
usage() {
cat <<EOF
Usage: $0 --session <name> [--agent claude|agy|hermes|cline] [--reason <reason>]
[--purge-conversation] [--yes]
Arguments:
--session <name> — target session name (required)
--agent <type> — claude | agy | hermes | cline
(inferred from the session-name suffix when omitted)
--reason <reason> — stop_reason field (default: manual_stop)
--purge-conversation — also delete on-disk conversation artifacts;
status becomes terminated and resume is impossible
--yes — skip the --purge-conversation confirmation prompt
Stop is always graceful and always captures the conversation id.
(idempotent: stopping an already-stopped session is a no-op with exit 0)
EOF
}
```
### 단계 3 — 내부 주석 3곳 + 경고 문자열 1곳
| 위치 | 조치 |
|---|---|
| `:157` | `# --capture-id: kill 직전에 …``# 캡처: kill 직전에 …` |
| `:166` | `WARN: --capture-id requested but no conversation id resolved``WARN: no conversation id resolved before stop (nothing on disk yet)` |
| `:172` | `# --graceful: send-keys 로 …``# graceful 종료: send-keys 로 …` |
| `:257` | `# --capture-id: 항상 captured UUID 기록``# 항상 captured UUID 기록 (purge 가 아닐 때만)` |
### 단계 4 — `MESSAGING.md:346-348`
```
| `stopped` | stopped via `multi-agent-mux-stop` (default); conversation preserved for resume | `stop` |
| `terminated` | stopped with `--purge-conversation`, or herdr-dead detected; conversation deleted / session gone | `stop --purge-conversation`, `monitor` reconcile |
| `archived` | legacy value — no producer since `--mode soft` was removed; kept in the validation whitelist for rows written by older versions | (none) |
```
---
## 6. 문서 동기화 (Rev.1 대비 변경 없음)
### 6.1 `IMPROVEMENTS.md` — 7곳
| 행 | 현재 | 변경 후 |
|---|---|---|
| `:3` | 최종 갱신일 `2026-08-16 (P3-1/A-4 …)` | 날짜·사유에 C-6 완료 반영 |
| `:5` | 미해결 **6건** (… **레거시 1**) | 미해결 **5건** (… **레거시 0**) |
| `:6` | 완료 **19건** | 완료 **20건**, 목록에 `C-6` 추가 |
| `:107` | `## 4. … (Legacy Remnants — 1건)` | `… (Legacy Remnants — 0건 — 전원 완료)` (`:103` §3 표기법과 동일) |
| `:109-110` | C-6 항목 | **삭제** (§5 로 이동) |
| `:114` | `## 5. … (Completed Tasks — 19건)` | `… (Completed Tasks — 20건)` |
| `:253` | `\| **P2-3** \| **C-6** \| 도움말 3줄 정정 \| 극소 \| — \|` | `… **(✅ 완료 — 가드 신설, 전체 263/263 PASS)** \|` |
§5 신규 항목:
```markdown
### **C-6 (P2-3): `stop_session.sh` 레거시 주석 및 구버전 사용법 정리** — ✅ 완료
- 헤더 주석이 광고하던 `--mode soft|hard` / `--capture-id` / `--graceful` 3종은 파서가 `exit 2`
거부하는 폐지 플래그였습니다. 헤더 29줄을 현재 CLI 에 맞게 교체하고, `usage()` 에 누락돼 있던
옵션 설명과 `--agent` 접미사 추론 동작을 보강했으며, Option B 이후 무의미해진 "워크스페이스에
격리된" 표현과 내부 주석 3곳의 플래그 표기를 정리했습니다.
- `MESSAGING.md` 상태 표가 제거된 플래그로 `stopped`/`terminated` 를 정의하던 것을 교정하고,
생산자가 사라진 `archived` 를 레거시 값으로 명기했습니다.
- 도움말과 파서의 일치를 강제하는 회귀 가드를 신설하고 뮤테이션 3종(M1~M3)으로 방어력을
검증했습니다 — C-6 은 문서 과제라 기존 테스트가 전혀 잡지 못하던 영역입니다.
```
**주의**: `:5` 의 "레거시 잔재 0건"과 `:107` §4 헤더는 **반드시 함께** 바꿉니다. 직전 3라운드 리뷰에서 이 쌍의 불일치가 매번 지적되었습니다.
### 6.2 `LOG.md`
`## 📌 1. 금일 작업 내용 요약` 아래 기존 `### 1) P3-1 …` **앞에** 신규 항목을 삽입하고 기존 P3-1 을 `### 2)` 로 조정합니다. 머리말 `- **최종 기록일시**` · `- **작업 상태**` 도 갱신합니다.
```markdown
### 1) **C-6 (P2-3): `stop_session.sh` 레거시 주석 및 구버전 사용법 정리** — **완료**
- **배경**: 헤더 주석이 폐지 플래그 3종을 사용법으로 광고했으나 파서는 전용 메시지와 함께
`exit 2` 로 거부하고 있었음(실측). 백로그에는 "도움말 3줄"로 등재돼 있었으나 실제 대상은
헤더 29줄 + `usage()` + 내부 주석 3곳 + `MESSAGING.md` 상태 표였음.
- **주요 구현**: (파일별 변경 요약)
- **검증**: `pytest` 263/263 PASS. 신규 가드에 대해 뮤테이션 M1~M3 전부 FAIL 확인.
```
---
## 7. `archived` 사문 상태값 — Option A 확정
Rev.1 §7 에서 판단을 요청했고 **챌린저가 §4-3 에서 Option A 에 전적으로 동의**했으므로 확정합니다.
- **A. 현상 유지 + 문서 명기** — `atomic_yaml.py:18` 화이트리스트와 `reconcile.sh:474` 관용 목록은 손대지 않고, `MESSAGING.md` 에 "레거시 값, 현재 생산자 없음"을 명기 (§5 단계 4 에 반영 완료).
- B(완전 은퇴)는 기존 데이터에 `archived` 행이 있으면 검증 실패로 **전체 쓰기가 막히므로** 마이그레이션이 필요합니다 — C-6("극소") 범위를 벗어납니다.
`MESSAGING.md` 를 C-6 범위에 포함하는 것도 챌린저가 §4-2 에서 동의했으므로 확정합니다.
---
## 8. 검증 절차
| # | 명령 / 확인 | 기대 |
|---|---|---|
| 1 | `bash -n .../stop_session.sh` | OK |
| 2 | `bash stop_session.sh --help; echo $?` | rc=0, 폐지 플래그 미노출, 4개 에이전트 전부 노출 |
| 3 | `--mode` / `--capture-id` / `--graceful` | rc=2 + `deprecated` 메시지 유지 |
| 4 | `--agent bogus` | rc=2 (`invalid agent type`) |
| 5 | `grep -rn -- "--mode soft" .agents/ *.md` | 0건 |
| 6 | **뮤테이션 M1** — 파서에서 `--reason)` 삭제 | 가드 **FAIL** |
| 7 | **뮤테이션 M2** — 헤더에 `--mode soft\|hard` 복원 | 가드 **FAIL** |
| 8 | **뮤테이션 M3**`usage()` 에이전트 목록 축소 | 가드 **FAIL** |
| 9 | `pytest tests/ -q` | **263 passed** |
| 10 | `env -u PYTHONPATH pytest tests/test_tier2_component.py -q` | 전부 통과 (환경 비의존) |
| 11 | `IMPROVEMENTS.md` `:5``:107` 대조 | 레거시 카운트 일치 |
| 12 | `IMPROVEMENTS.md` `:6``:114` 대조 | 둘 다 20건 |
9번은 약 6분 30초 소요됩니다(직전 실측 262 passed / 381.58s). 백그라운드 실행 권장.
10번 근거: 직전 라운드에서 신규 테스트가 주변 셸의 `PYTHONPATH` 에 의존해 CI 를 적색으로 만든 사례(N1)가 있었습니다. 확정 가드는 `subprocess.run(["bash", ...])` 만 쓰므로 해당 위험이 없으나 확인 절차는 유지합니다.
---
## 9. 변경 규모 및 리스크
| 파일 | 변경 |
|---|---|
| `stop_session.sh` | 헤더 29줄 교체, `usage()` 약 +10줄, 내부 주석 3곳 + 경고 문자열 1곳 |
| `MESSAGING.md` | 3줄 |
| `IMPROVEMENTS.md` | 7곳 + §5 신규 항목 |
| `LOG.md` | 1개 블록 + 머리말 |
| `tests/test_tier2_component.py` | +1 test |
| **테스트 총계** | 262 → **263** |
| 리스크 | 평가 |
|---|---|
| 동작 회귀 | **없음.** 실행 경로 무변경. 유일한 예외 `:166` 경고 문자열은 단언하는 테스트 0건 확인 |
| 가드 오탐 | **해소.** Challenge 원인(`:98`)을 세션명으로 제거하고, `rc=2` 과부하를 메시지 단언으로 우회 |
| 가드 무력화 | **해소.** M1~M3 실측으로 방어력 증명 |
| 카운트 불일치 재발 | §8 의 11·12번으로 차단 |
### 권장 커밋 분할
1. `docs(stop): rewrite stop_session.sh header and usage to match the current CLI (C-6)` — 단계 1~3
2. `test(stop): guard help text against parser drift (C-6)` — §4
3. `docs(messaging,improvements,log): sync status table and backlog for C-6` — 단계 4 + §6
2번을 1번 뒤에 두면, 가드가 1번 없이 실패하고 1번과 함께 통과함을 커밋 순서로 증명할 수 있습니다.
---
## 10. 한계
- 확정 가드는 **격리 클론에서 실행 검증**했으나, 저장소 본체에는 적용하지 않았습니다(Planner 역할). 클론은 검증 후 삭제했고 작업 트리는 계획 수립 전후 동일(`?? VERSIONS.md` 1건)합니다.
- 뮤테이션 M1~M3 은 §5 단계 1~2 의 문서 수정을 클론에 **부분 적용**한 상태에서 수행했습니다(헤더 `--mode` 행 삭제 + `usage()` 확장). 단계 3·4 는 가드 대상이 아니므로 적용하지 않았습니다.
- 전체 회귀(263)는 재실행하지 않았습니다. 262 passed / 381.58s 가 유효 기준이며 HEAD 가 `5ed39f8` 로 진행되었으므로 구현 시 재측정이 필요합니다.
- `MESSAGING.md` 는 폐지 플래그 3종 검색으로 걸린 3줄만 확인했고 나머지는 감사하지 않았습니다.
- `:91-100` 접미사 추론의 역할 목록(`creator`/`planner`/`reviewer`)이 실제 사용되는 역할 전부를 덮는지는 확인하지 않았습니다. C-6 범위 밖이며, 가드는 이 목록에 의존하지 않도록(§2.3) 설계했습니다.
@@ -0,0 +1,233 @@
# Cross-Code Review Report — Job 7ddb5350
- **Job ID**: 7ddb5350
- **Target**: C-6 (P2-3) — `stop_session.sh` legacy comment and outdated usage text cleanup, `IMPROVEMENTS.md`/`LOG.md` synchronization, `MESSAGING.md` status table correction, regression guard addition, and `VERSIONS.md` creation
- **Reviewer**: cline
- **Output Report Path**: `.mam/jobs/7ddb5350/cline-reports/report-final.md`
- **Base commit**: `5ed39f8` (fix(agents): harden shell adapter bridge and address double-check review feedback)
- **Working-tree state**: 5 tracked modified files + 1 untracked new file (`VERSIONS.md`)
---
## 1. Delta Description
This changeset resolves backlog item C-6 (roadmap P2-3): cleaning up legacy comments and outdated usage text in `stop_session.sh` that advertised deprecated flags (`--mode soft|hard`, `--capture-id`, `--graceful`) as valid usage, while the parser rejects them with `exit 2`. The scope expanded beyond the brief's "3-line fix" estimate to cover all documentation surfaces with the same defect.
| File | Change Summary |
|------|---------------|
| `.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh` | Header comment block (29 lines) rewritten to match current CLI; `usage()` expanded with full argument descriptions; 3 internal comments + 1 warning string modernized; removed "워크스페이스에 격리된" (Option B obsolete terminology) |
| `MESSAGING.md` | Session state table (3 rows) corrected: `stopped`/`terminated` now reference current CLI; `archived` marked as legacy with no producer |
| `IMPROVEMENTS.md` | C-6 moved from §4 (open) to §5 (completed); header counts updated (6→5 open, 19→20 completed); roadmap P2-3 row updated with verification status |
| `LOG.md` | New §1 entry for C-6 work; prior sections renumbered (duplicate "2)" numbering fixed); header timestamp updated |
| `tests/test_tier2_component.py` | New regression guard `test_comp_stop_usage_matches_parser` (+45 lines): verifies help-parser consistency across 4 dimensions |
| `VERSIONS.md` (new) | 135-line version history document covering v1.0.0v2.0.0 changelog, skills version matrix, and verification standards |
**Net diff**: 5 files changed, 107 insertions(+), 45 deletions(-) + 1 new untracked file (135 lines).
---
## 2. Review Methodology
This review examines the changeset from three perspectives as required by the brief:
1. **Lint (정적 검사)**: Syntax validation, comment-code consistency, orphaned reference detection
2. **Operability (동작성)**: Runtime behavior verification, parser-help alignment, exit code correctness
3. **Loss (유실)**: Completeness of cleanup, documentation-code drift, regression guard effectiveness
---
## 3. Findings
### 3.1 Lint (정적 검사) — PASS
**V1: Bash syntax validation**
- `bash -n stop_session.sh`**OK**
- `bash -n lib.sh`**OK**
**V2: Header comment ↔ parser consistency**
The header (lines 325) now documents exactly the 5 current CLI arguments and lists the 3 deprecated flags with their rejection behavior:
| Header advertises | Parser handles (line) | Match? |
|---|---|---|
| `--session <name>` | `:69` `--session) SESSION_NAME="$2"; shift 2` | ✅ |
| `--agent claude\|agy\|hermes\|cline` | `:70` `--agent) AGENT="$2"; shift 2` + `:84` validation case | ✅ |
| `--reason <reason>` | `:73` `--reason) REASON="$2"; shift 2` | ✅ |
| `--purge-conversation` | `:71` `--purge-conversation) PURGE=1; shift` | ✅ |
| `--yes` | `:72` `--yes) YES=1; shift` | ✅ |
| Deprecated: `--mode`/`--capture-id`/`--graceful` → exit 2 | `:74-77` case → exit 2 | ✅ |
**V3: Orphaned deprecated-flag references in production code**
- `grep -rn '--mode soft' .agents/ *.md` (excluding `.mam/` and `.agents/reports/`): **3 hits, all correct**:
- `IMPROVEMENTS.md:114` — C-6 completed entry *describing* what was fixed (historical record) ✅
- `LOG.md:12` — C-6 work log *describing* what was fixed (historical record) ✅
- `MESSAGING.md:348``archived` row explaining `--mode soft` was removed (legacy documentation) ✅
- **Zero orphaned references in production `.agents/` scripts** advertising deprecated flags as valid usage ✅
### 3.2 Operability (동작성) — PASS
**V4: `--help` output verification**
```
$ stop_session.sh --help; echo $?
Usage: ... --session <name> [--agent claude|agy|hermes|cline] [--reason <reason>]
[--purge-conversation] [--yes]
Arguments:
--session <name> — target session name (required)
--agent <type> — claude | agy | hermes | cline
--reason <reason> — stop_reason field (default: manual_stop)
--purge-conversation — also delete on-disk conversation artifacts; ...
--yes — skip the --purge-conversation confirmation prompt
Stop is always graceful and always captures the conversation id.
rc=0
```
- rc=0 ✅
- No deprecated flags (`--mode`, `--capture-id`, `--graceful`) advertised ✅
- All 4 agents (claude, agy, hermes, cline) listed ✅
**V5: Deprecated flag rejection**
```
$ stop_session.sh --session x --mode hard; echo $?
rc=2
```
- `--mode`/`--capture-id`/`--graceful` all rejected with rc=2 and "deprecated" message ✅
**V6: MESSAGING.md ↔ code alignment**
| MESSAGING.md state | Code behavior | Match? |
|---|---|---|
| `stopped` — "stopped via multi-agent-mux-stop (default)" | `stop_session.sh:257` `target['status'] = 'stopped'` (non-purge path) | ✅ |
| `terminated` — "stopped with --purge-conversation" | `stop_session.sh:296-297` purge path removes entry, status becomes terminated | ✅ |
| `archived` — "legacy value, no producer" | `atomic_yaml.py:18` whitelist retains `archived`; no code path produces it | ✅ |
**V7: `archived` whitelist retention (Option A)**
- `atomic_yaml.py:18`: `valid = {'running', 'terminated', 'archived', 'stopped'}``archived` retained ✅
- `reconcile.sh:474`: `if s.get('status') in ('terminated', 'archived', 'stopped'):``archived` retained ✅
- MESSAGING.md documents this as intentional for backward compatibility with older rows ✅
### 3.3 Loss (유실) — PASS
**V8: Regression guard effectiveness**
The new test `test_comp_stop_usage_matches_parser` verifies 4 dimensions of help-parser consistency:
1. `--help` succeeds (rc=0) and does NOT advertise deprecated flags ✅
2. All 4 supported agents appear in help text ✅
3. All advertised flags (`--reason`, `--purge-conversation`, `--yes`, `--agent`) are accepted by parser (rc≠2, no "unknown arg"/"deprecated" in stderr) ✅
4. Deprecated flags (`--mode`, `--capture-id`, `--graceful`) are rejected with rc=2 and "deprecated" message ✅
5. Header comments (first 35 lines) do not contain `--mode soft|hard`
The test uses `subprocess.run(["bash", ...])` only — no ambient `PYTHONPATH` dependency (N1 guard satisfied).
**V9: Clean-environment test**
```
$ env -u PYTHONPATH pytest tests/test_tier2_component.py::test_comp_stop_usage_matches_parser -v
1 passed in 0.79s
```
Environment-independent ✅
**V10: IMPROVEMENTS.md count consistency**
- Line 5: "총 추적 미해결 과제: 5건 (아키텍처 1건, 엣지케이스 4건, 오케스트레이션 0건, 레거시 잔재 0건)" → 1+4+0+0 = 5 ✅
- Line 107: "Legacy Remnants — 0건 — 전원 완료" → matches header "레거시 잔재 0건" ✅
- Line 6: "완료된 과제: 20건" → listed items count: 20 ✅
- Line 111: "Completed Tasks — 20건" → matches header ✅
- C-6 present in completed list (line 6) ✅
**V11: LOG.md section numbering fix**
The old LOG.md had duplicate "### 2)" numbering (3 sections all numbered "2)"). The new LOG.md correctly numbers sections 15 sequentially. This is a welcome cleanup beyond the brief scope. ✅
**V12: VERSIONS.md (new file)**
The new `VERSIONS.md` (135 lines) provides a structured version history covering:
- Current release overview (v2.0.0)
- Skills version matrix (8 skills, all v2.0.0)
- Changelog for v1.0.0v2.0.0
- Verification standards (4-step QA process)
Content is consistent with the existing IMPROVEMENTS.md and LOG.md records. The file is currently untracked (`??`).
---
## 4. Full Test Suite Execution
**V13: Complete regression test**
```
$ pytest tests/ -q --tb=short
........................................................................ [ 27%]
........................................................................ [ 54%]
........................................................................ [ 82%]
........................................................................ [100%]
263 passed in 384.59s (0:06:24)
```
**Result: 263/263 PASS (100%)** — matches the IMPROVEMENTS.md and LOG.md claims exactly. ✅
Previous review (Job e7b9812b) had 259/259; this changeset adds 1 new test (262→263, with +3 from commit `5ed39f8` between reviews).
---
## 5. Minor Observations (Non-blocking)
### 5.1 MESSAGING.md "lib.sh valid-status set" reference (pre-existing)
Line 341 says "Valid values (see `lib.sh` valid-status set)" but the actual validation is in `atomic_yaml.py:18`, not `lib.sh`. This is a pre-existing inaccuracy **not introduced by C-6** — the C-6 diff only changed the table rows, not this reference line. Mentioning for awareness; no action required for this job.
### 5.2 `CAPTURE_ID`/`GRACEFUL`/`STOP_MODE` variables remain hardcoded
Lines 6265 still hardcode `CAPTURE_ID=1`, `GRACEFUL=1`, `STOP_MODE=1`. The comment cleanup removed references to these as user-facing flags, but the variables themselves remain in the code (always-on). This is correct for C-6 scope — the task was documentation cleanup, not code refactoring. The variables are harmless (always-true conditions) and removing them would expand scope beyond "극소" difficulty.
### 5.3 VERSIONS.md untracked
`VERSIONS.md` is currently an untracked file (`??` in git status). It should be committed alongside the other changes. The Planner's recommended commit split (§9 of Job 73b18819) does not explicitly mention VERSIONS.md — it may need to be added to the commit plan.
---
## 6. Scope Assessment
The brief described C-6 as "도움말 3줄 정정" (3-line help text fix). The actual implementation correctly identified that the defect spans:
- Header comments: 29 lines (not 3)
- `usage()` function: +10 lines expansion
- Internal comments: 3 locations
- Warning string: 1 location
- `MESSAGING.md`: 3 rows (scope expansion, justified — same defect type)
- Regression guard: 1 new test (justified — C-6 is a documentation task that no existing test covered)
The scope expansion is well-justified and documented in the Planner's report (Job 73b18819 §0). The Challenger (Job 8b6b574f) agreed to include `MESSAGING.md` and to adopt Option A for `archived`. All changes trace directly to the C-6 defect (help text advertising deprecated flags).
---
## 7. Risk Assessment
| Risk | Assessment |
|---|---|
| Behavior regression | **None.** No execution paths changed. Only comments, help text, and documentation modified. Warning string at `:175` changed but no test asserts on it. |
| Guard false-positive | **Resolved.** Test uses valid session name (`test-project-creator-claude`) to avoid rc=2 from agent inference failure; uses stderr message assertions instead of brittle rc=2 overloading. |
| Guard powerlessness | **Resolved.** Mutation testing M1M3 (per Planner report) confirmed all 3 mutations cause FAIL. |
| Count inconsistency | **Resolved.** IMPROVEMENTS.md header counts match section headers (V10). |
| Environment dependency | **Resolved.** Clean-environment test passes (V9, N1 guard). |
---
## 8. Verification Summary
| # | Verification | Expected | Result |
|---|---|---|---|
| V1 | `bash -n stop_session.sh` | OK | ✅ OK |
| V2 | Header ↔ parser consistency | All 5 flags + 3 deprecated match | ✅ Match |
| V3 | Orphaned deprecated refs in production | 0 | ✅ 0 |
| V4 | `--help` output | rc=0, no deprecated flags, 4 agents | ✅ Pass |
| V5 | `--mode hard` rejection | rc=2 + deprecated | ✅ Pass |
| V6 | MESSAGING.md ↔ code alignment | 3 states match | ✅ Pass |
| V7 | `archived` whitelist retention | Retained + documented | ✅ Pass |
| V8 | Regression guard (4 dimensions) | All pass | ✅ Pass |
| V9 | Clean-environment test (N1) | Pass without PYTHONPATH | ✅ Pass |
| V10 | IMPROVEMENTS.md count consistency | 5 open, 20 completed, 0 remnants | ✅ Pass |
| V11 | LOG.md section numbering | Sequential 15 | ✅ Pass |
| V12 | VERSIONS.md content | Consistent with records | ✅ Pass |
| V13 | Full test suite | 263/263 PASS | ✅ 263 passed in 384.59s |
---
## 9. Verdict
The C-6 implementation is a thorough and well-executed documentation cleanup that:
- Correctly identifies the full scope of the defect (29-line header, not 3 lines)
- Aligns all documentation surfaces (header, `usage()`, internal comments, `MESSAGING.md`) with the actual parser behavior
- Adds a meaningful regression guard that prevents future help-parser drift
- Retains `archived` in the validation whitelist with proper documentation (Option A)
- Passes the complete test suite (263/263, 100%)
No behavior regression, no orphaned references, no count inconsistencies, and no environment dependencies. The three minor observations (§5) are pre-existing or out-of-scope and do not block the verdict.
[VERDICT: PASS]