Files
multi-agent-mux/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-07740cc7.md
T

22 KiB
Raw Blame History

P2-2 (C-3a + C-4) 레거시 코드 정리 — 구현 계획서 Rev.2

  • Job: 84300b64 · Role: Planner (MULTI_AGENT_RULES.md §1 — 저장소 코드 미수정)
  • 선행 계획: 07740cc7 (Rev.1) · 반영 챌린지: e63843ca (agy, [VERDICT: PASS WITH CHALLENGE])
  • 기준 커밋: 5e519e2 (Rev.1 과 동일 — HEAD 이동 없음, §3.3 참조) · 작성 시각: 2026-08-16
  • 산출물: 본 계획서 + proposed/p2-2_c3a_c4.rev2.patch + proposed/test_legacy_isolate_flags_noop.py + proposed/pytest_rev2.log
  • 검증 방식: git clone --local --no-hardlinks 로 만든 스크래치패드 사본에 패치를 적용해 전체 스위트 + 변이 검사(mutation check)를 실행했습니다. 본 저장소 워킹 트리는 계획 수립 전후 모두 clean 입니다.

0. 챌린지 판정 요약

# 챌린지 판정 근거
1 --isolate/--no-isolate 자동화 회귀 테스트 부재 수용 + 강화 제시된 테스트를 그대로 실행 → 통과(0.09s). 변이 4종 중 3종 검출. 나머지 1종(usage 문서 줄 삭제)을 잡도록 assert 1줄 추가
2 test_tier1_unit.py:31 섹션 헤더 (7 Test Cases) 동기화 수용 현재 5개 헤더 전부 정확(7/6/5/5/6 = 29 = 실측)함을 확인. 방치하면 이 파일 최초의 불일치가 됨. (5 Test Cases) 로 갱신
3 IMPROVEMENTS.md 라인 번호를 최신 HEAD 로 동기화 ⚖️ 사실관계는 반박, 우려는 수용 HEAD 는 5e519e2이동하지 않았고 Rev.1 의 20개 인용 라인은 전부 현행 일치. 챌린지의 "문두 완료 15건" 은 실측 16건. 다만 §6.1 편집들이 서로의 오프셋을 밀어내는 문제는 실재하므로 편집 순서 명세를 신설(§4.3)

Rev.1 대비 순증분: 테스트 1건 추가(순감 4 → 순감 3), 섹션 헤더 1줄, 편집 순서 명세 1개 절. 수집 개수 259 → 256.


1. Challenge 1 검증 — 수용, 그리고 한 줄 강화

1.1 제안된 테스트를 그대로 실행

챌린저가 제시한 코드를 한 글자도 고치지 않고 패치된 사본에 넣어 실행했습니다.

1 passed in 0.13s
0.09s call     test_create_session_legacy_isolate_flags_noop
0.02s setup

동작합니다. 다만 실측 0.09s 로, 챌린지가 적은 <0.05s 보다 약 2배입니다. 원인은 create_session.sh:25 가 인자 파싱 이전에 source "$_lib_sh" 를 하기 때문이며(플래그 2개 × 서브프로세스 2회), 절대값이 미미하므로 채택에는 영향이 없습니다. 계획에는 실측값으로 적습니다.

1.2 변이 검사 — 이 테스트가 실제로 무엇을 잡는가

"통과한다" 는 것만으로는 가드가 되지 못하므로, 이 테스트가 막으려는 회귀를 직접 주입해 실패하는지 확인했습니다.

변이 내용 챌린지 원안 강화안
A --isolate · --no-isolate 분기 둘 다 삭제 FAIL (rc=2, ERROR: unknown arg: --isolate) FAIL
B --no-isolate 한쪽만 삭제 FAIL (ERROR: unknown arg: --no-isolate) FAIL
C 분기는 두되 echo 를 지워 조용한 no-op 으로 FAIL (stderr assert) FAIL
D 분기는 두되 usage() 의 문서 줄(:42-43) 삭제 PASS (놓침) FAIL
E 무변이 대조군 PASS PASS

변이 A/B/C 를 잡는다는 점에서 챌린지의 지적은 정확하고 실효적입니다. 특히 B(한쪽만 삭제)를 잡는 것은 for flag in [...] 루프 덕분이며, 원안 설계가 이미 이 경우를 고려했음을 보여줍니다.

D 만 빠져나갑니다. --isolate/--no-isolatecreate_session.sh:42-43 에서 usage 에 정식 문서화되어 있는 옵션입니다. 챌린지가 지목한 "누군가 미사용으로 오판하여 삭제" 시나리오에서, 가장 먼저 지워질 후보는 실행 분기가 아니라 도움말 줄입니다(C-6 이 정확히 "도움말과 실제 파서의 불일치" 과제인 점을 상기하십시오). 그리고 -h 를 이미 실행하고 있으므로 그 출력은 이미 res.stdout 에 잡혀 있습니다 — 서브프로세스 추가 없이 assert 한 줄이면 닫힙니다.

1.3 채택 최종본

def test_create_session_legacy_isolate_flags_noop(mam_sandbox):
    """Legacy --isolate/--no-isolate must stay a documented no-op, not an arg-parser error."""
    create_script = mam_sandbox / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh"
    for flag in ["--isolate", "--no-isolate"]:
        res = subprocess.run(["bash", str(create_script), flag, "-h"], capture_output=True, text=True)
        assert res.returncode == 0, f"{flag} rejected by arg parser: {res.stderr}"
        assert "NOTE: --isolate/--no-isolate is a no-op" in res.stderr
        assert flag in res.stdout, f"{flag} missing from usage() help text"

원안 대비 변경은 3줄입니다.

  1. assert flag in res.stdout 신설 — 변이 D 를 닫습니다. 부분 문자열 오탐 우려가 있어 확인했으나 "--isolate" in "--no-isolate"False 입니다(--no-isolate--no 다음에 하이픈이 하나뿐이므로 --isolate 를 부분 문자열로 포함하지 않음). 따라서 단순 in 으로 두 플래그가 모호함 없이 구분됩니다.
  2. assert res.returncode == 0실패 메시지 추가 — 실패 시 assert 2 == 0 대신 어느 플래그가 왜 거부됐는지 즉시 보이게 합니다(루프라서 어느 회차인지 모호해집니다).
  3. docstring 을 계약 문장으로 교체 — "documented no-op" 이 assert 3개의 의도를 그대로 서술합니다.

1.4 배치 결정 — test_tier1_unit.py FEATURE 1

챌린지의 제안대로 tier1 에 둡니다. 스크립트를 실행하는 테스트라 tier2 도 후보였으나, 동일 파일에 정확한 선례가 있습니다:

def test_resume_script_invalid_args(mam_sandbox):          # tier1:114 (현행)
    script_path = mam_sandbox / "skills" / "multi-agent-mux-resume" / "scripts" / "resolve_session_id.sh"
    res = subprocess.run(["bash", str(script_path), ...], capture_output=True, text=True)
    assert res.returncode == 2
    assert "ERROR: --agent required" in res.stderr

mam_sandbox / "skills" / ... 경로 관례, subprocess.run, rc + stderr assert — 신규 테스트가 이 관용구를 그대로 따릅니다. tier1 은 이미 인자 파서 단위 테스트의 자리입니다. subprocesstests/test_tier1_unit.py:2 에서 이미 임포트되어 있어 추가 임포트도 없습니다.

삭제되는 3건이 있던 바로 그 자리(test_create_derive_session_name_weird_characterstest_create_validate_env_key 사이)에 넣습니다.

1.5 격리 검증 — 신규 테스트는 저장소를 오염시키지 않는가

이 테스트는 create_session.sh 를 실행하고, 그 스크립트는 :25 에서 lib.sh 를 source 하며, lib.sh_init_herdr_isolation 으로 $WORKSPACE_ROOT/.mam/shim/herdr씁니다. 실제로 쓰기가 일어나는 테스트이므로 확인했습니다.

rm -rf <clone>/.mam
pytest ...::test_create_session_legacy_isolate_flags_noop  →  1 passed
after run, .mam exists?  NO

conftest.py:44monkeypatch.setenv("WORKSPACE_ROOT", str(tmp_path)) 가 서브프로세스까지 상속되어 쓰기가 tmp_path 안에 갇힙니다. 저장소 트리에 흔적 0건.

(참고: 전체 스위트를 돌리면 사본에 .mam/shim/ 이 생깁니다. 이는 다른 기존 테스트들이 만드는 것으로 P2-2 이전부터의 성질이며 .gitignore:14 대상입니다. 신규 테스트가 원인이 아님을 위 실험이 분리해 보여 줍니다.)

1.6 이 테스트가 여전히 잡지 못하는 것 (명시)

  • create_session.sh 본문의 동작(세션 생성 자체)은 검증하지 않습니다. -h 로 조기 종료하므로 파서 진입 지점까지만 봅니다. 이는 의도된 범위입니다 — 챌린지가 요구한 것은 "인자 파서 게이트" 입니다.
  • 다른 레거시 no-op 플래그가 생기면 이 테스트는 자동으로 커버하지 않습니다. for flag in [...] 목록에 추가해야 합니다.

2. Challenge 2 검증 — 수용, 범위 명확화

tests/test_tier1_unit.py:31# FEATURE 1: Create Session (7 Test Cases) 를 갱신하라는 지적입니다. 파일 전체의 헤더 정합성을 실측했습니다.

헤더 라인 섹션 선언 실측
31 FEATURE 1: Create Session 7 7
106 FEATURE 2: Resume Session 6 6
152 FEATURE 3: Stop Session 5 5
197 FEATURE 4: Status Query 5 5
283 FEATURE 5: Monitor/Reconcile 6 6
합계 29 29 (grep -c "^def test_" = 29)

5개 헤더 전부 현재 정확합니다. 이 파일은 메타데이터를 성실하게 유지해 온 파일이고, 따라서 (7 Test Cases) 를 방치하면 그것이 이 파일 최초의 불일치가 됩니다. 챌린지 판단이 옳습니다.

갱신값은 (5 Test Cases) 입니다 — 7 3(삭제) + 1(신규) = 5. 다른 4개 헤더는 손대지 않습니다(변동 없음).

패치 적용 후 재실측:

  31 FEATURE 1: Create Session      claimed=5 actual=5 OK
  77 FEATURE 2: Resume Session      claimed=6 actual=6 OK
 123 FEATURE 3: Stop Session        claimed=5 actual=5 OK
 168 FEATURE 4: Status Query        claimed=5 actual=5 OK
 254 FEATURE 5: Monitor/Reconcile   claimed=6 actual=6 OK
 file total: 27

tests/test_tier2_component.py 에는 이런 개수 선언 헤더가 없으므로 해당 파일은 추가 조치 불필요합니다.


3. Challenge 3 판정 — 사실관계 반박, 우려는 §4.3 으로 수용

3.1 HEAD 는 이동하지 않았습니다

$ git rev-parse --short HEAD
5e519e2
$ git log --oneline -1
5e519e2 docs(improvements): synchronize header counts and roadmap table with completed P2-1 task

Rev.1 의 기준 커밋이 5e519e2 이고 현재 HEAD 도 5e519e2 입니다. 챌린지가 지목한 b490713(P2-1 수정)은 4 커밋 이전이며, 그 이후의 af3dc16a875b135e519e2 가 전부 문서 커밋입니다. 그중 5e519e2 는 커밋 제목 그대로 "헤더 개수와 로드맵 표를 P2-1 완료와 동기화" 한 커밋 — 즉 챌린지가 요구하는 동기화는 Rev.1 작성 시점에 이미 반영된 상태였습니다.

3.2 Rev.1 의 인용 라인 20개 전수 재검증

챌린지를 계기로 §6.1·§6.3 이 인용한 모든 라인을 다시 대조했습니다.

인용 현행 내용 판정
:5 총 추적 미해결 과제: 9건 (아키텍처 2, 엣지케이스 4, 오케스트레이션 0, 레거시 잔재 3)
:6 완료된 과제: **16건** (A-1 … P2-1-DelegateJobSafe-TrapFix)
:70 ## 2. 엣지 케이스 및 런타임 버그 (Edge-case Bugs — 5건)
:107 ## 4. 레거시 잔재 및 죽은 코드 (Legacy Remnants — 3건)
:109-111 C-3 제목 / C-3a / C-3b
:113-116 C-4 제목 / 실제 대상 3종 / 목록 제외 / provision_isolation 중복
:123 ## 5. 완료된 과제 (Completed Tasks — 13건)
:249 로드맵 P2-2 행 ("공허한 테스트 5건")
:260 "정리(C 계열)를 P2 에 두는 이유"
:317 :319-322 §6.5-1 / §6.5-2
:328 §6.6 결론 ("총 12건")

20/20 일치. 오프셋 충돌은 발생하지 않습니다.

3.3 챌린지의 수치 주장은 사실과 다릅니다

챌린지 §Challenge 3 은 "완료 과제 개수도 13건(문두 완료 15건)으로 갱신되었습니다" 라고 적었습니다. 실측:

:6  - **완료된 과제**: **16건** (A-1, A-3, A-5, B-1, B-3, B-4, B-7, B-8, C-1, C-2,
                                 O-1, O-2, O-3, O-4-OrcOnboard,
                                 Herdr-0.8.0-Compat-SanitizeHash, P2-1-DelegateJobSafe-TrapFix)

쉼표 구분 항목 수 = 16개, 선언값 = 16건. 문두는 15가 아니라 16이며 목록과 자체 정합합니다. Rev.1 §6.1 의 "16건 → 17건" 이 맞습니다.

한편 챌린지가 같은 문장에서 언급한 "C-3/C-4 섹션의 시작 위치가 IMPROVEMENTS.md:107" 은 Rev.1 §6.1 이 이미 :107 로 적고 있는 값과 동일합니다 — 이 대목은 정정이 아니라 Rev.1 의 확인입니다.

3.4 그럼에도 수용하는 부분 — 편집 상호 간섭

챌린지가 우려한 "오프셋 충돌" 은 HEAD 대비로는 존재하지 않지만, 편집 도중에는 실재합니다. §6.1 의 지시 11개가 전부 같은 파일을 대상으로 하고, 그중 3개가 줄 수를 바꿉니다:

  • :113-116 C-4 블록 삭제 (−4줄) → 이후 모든 라인 상향 이동
  • :123 직후 P2-2 완료 항목 삽입 (+16줄) → 이후 모든 라인 하향 이동
  • :109-111 C-3 축소 (줄 수 변동 가능)

따라서 구현자가 :5:328 순으로 위에서 아래로 편집하면 :249 이후의 라인 번호가 전부 어긋납니다. 이것이 챌린지가 감지한 실제 위험이며, 해법은 "HEAD 동기화" 가 아니라 편집 순서 규정입니다. §4.3 에 신설했습니다.


4. Rev.1 대비 변경 명세

Rev.1(07740cc7)의 §1~§4(실측·경계·위험), §7.1 게이트, §8 비용·효과 정정, §9 예상 지적은 전부 유효하며 변경 없습니다. 아래는 델타만 기술합니다.

4.1 S5 개정 — 테스트 4건 제거 → 4건 제거 + 1건 추가 + 헤더 1줄

tests/test_tier1_unit.py
  :31          "(7 Test Cases)" → "(5 Test Cases)"                        [Challenge 2]
  :52-88       test_create_isolation_lever
               test_create_isolation_env_prefix                            삭제
               test_create_isolation_cmd_args
  같은 자리     test_create_session_legacy_isolate_flags_noop               신설 [Challenge 1]

tests/test_tier2_component.py
  :99-107      test_comp_create_isolation_folder_setup                     삭제

패치 전체(proposed/p2-2_c3a_c4.rev2.patch): 5 files, +14 / 72. Rev.1 은 +5/72 였습니다.

4.2 §7.2 개정 — 수동 스모크 항목 정리

Rev.1 §7.2 의 3개 요구 중 3번(--isolate/--no-isolate 각 1회 수동 실행)은 자동화되었으므로 삭제합니다. 이것이 Challenge 1 의 핵심 성과입니다 — 수동 절차가 CI 게이트로 승격되었습니다.

구현자가 여전히 직접 해야 할 것:

  1. pytest tests/ -q 재실행 — 사본에는 .mam/(gitignore)이 없습니다. 256 passed 재현 확인.
  2. create_session.sh 실경로 스모크 1회 (--dry-run 가능) — ISOLATE 제거가 파서 본류에 영향 없음을 실행으로 확인. (신규 테스트는 -h 조기 종료 경로까지만 봅니다 — §1.6)

4.3 §6.1 신설 — 편집 순서 (Challenge 3 수용)

IMPROVEMENTS.md 의 11개 지시는 반드시 아래 순서(= 라인 번호 내림차순)로 적용하십시오. 그러면 앞선 편집이 뒤이을 편집의 라인 번호를 바꾸지 않습니다.

대상 작업 줄 수 변화
1 :319-322 §6.5-2 C-4 완료 표기. :320lib.sh:57:79lib.sh:83:105 로 정정 ±0
2 :317 §6.5-1 C-3a 완료 표기. 총계 표현 있으면 "4건" ±0
3 :260 근거 문장 교체 (Rev.1 §8) ±0
4 :249 로드맵 행 "5건"→"4건", (✅ 완료 — 256/256 PASS) ±0
5 :123 직후 §5 최상단에 P2-2 완료 항목 삽입 (§4.4) +16
6 :123 §5 제목 항목 수 갱신 ±0
7 :113-116 C-4 블록 §4 에서 삭제 (내용은 5번에서 이미 §5 로 이관) 4
8 :109-111 C-3 제목을 C-3b: isolation.root 소비자 처분 (보류 — A-4 M2) 으로 축소, C-3a 줄 제거 1 내외
9 :107 §4 제목 Legacy Remnants — 3건2건 ±0
10 :6 완료 16건17건, 목록에 P2-2-C3a-C4-LegacyCleanup 추가 ±0
11 :5 미해결 9건8건, 레거시 잔재 3건2건 ±0

대안 (권장): 라인 번호 대신 고유 문자열 앵커로 편집하면 순서 제약이 사라집니다. 위 11개 지시는 모두 유일 문자열을 갖고 있습니다(예: Legacy Remnants — 3건, 공허한 테스트 5건, Completed Tasks — 13건). 도구가 문자열 치환을 지원한다면 그쪽이 안전합니다.

⚠️ Rev.1 §6.3 은 ":115/:320 의 라인 번호를 정정" 하라고 했으나, :115 는 7번에서 삭제되는 C-4 블록 안에 있습니다. 따라서 정정 대상은 :320 하나이며, :115 의 내용은 §5 로 이관될 때(§4.4 마지막 항목) 이미 올바른 lib.sh:83-84 → :105 로 적혀 나갑니다. Rev.2 에서 정정합니다.

4.4 §6.2 개정 — §5 완료 항목 (테스트 문구 수정)

Rev.1 초안에서 두 번째 불릿만 교체합니다.

- 위 스텁의 빈 출력만 재확인하던 공허한 테스트 4건(`tests/test_tier1_unit.py` 3,
  `tests/test_tier2_component.py` 1)을 제거하고, 그 자리에 `--isolate`/`--no-isolate`
  레거시 no-op 플래그의 인자 파서 계약을 고정하는
  `test_create_session_legacy_isolate_flags_noop` 1건을 신설했습니다. 신규 테스트는
  분기 삭제·한쪽만 삭제·조용한 no-op 화·usage 문서 줄 삭제 4종 변이를 모두 검출함을
  변이 검사로 입증했습니다. `test_tier1_unit.py:31` 섹션 헤더도 `(5 Test Cases)` 로
  동기화했습니다.

마지막 불릿의 수치도 갱신합니다: 전체 회귀 256/256 PASS (100%) (259 → 256, 순감 3 = 제거 4 신설 1).

4.5 §6.4 개정 — LOG.md

주요 구현 목록의 테스트 줄을 교체하고 검증 수치를 갱신합니다.

  - `tests/test_tier1_unit.py` / `tests/test_tier2_component.py`: 공허한 테스트 4건 제거 및
    `--isolate`/`--no-isolate` no-op 회귀 가드 1건 신설(변이 4종 검출 입증), 섹션 헤더 동기화.
- **검증**: `pytest tests/ -q` **256 passed (100%)**.

4.6 §3 미접촉 경계 — 한 줄 보강

Rev.1 §3 표의 --isolate/--no-isolate 행 사유를 다음으로 대체합니다.

레거시 호환 경고이자 create_session.sh:42-43 에 정식 문서화된 옵션. 제거하면 기존 호출자가 unknown argexit 2. P2-2 이후로는 test_create_session_legacy_isolate_flags_noop 이 CI 게이트로 이를 고정한다.


5. Rev.2 검증 결과

# 검증 기대 실측
V1 bash -n lib.sh / create_session.sh rc=0 (Rev.1 에서 확인, 해당 hunk 무변경)
V2 ast.parse(registry.py) rc=0 (동상)
V3 신규 테스트 단독 실행 pass 1 passed, 0.09s call
V4 변이 A (분기 2개 삭제) FAIL FAIL
V5 변이 B (한쪽만 삭제) FAIL FAIL
V6 변이 C (조용한 no-op) FAIL FAIL
V7 변이 D (usage 문서 줄 삭제) FAIL FAIL (강화 후. 원안은 PASS)
V8 변이 E (무변이 대조군) PASS PASS
V9 신규 테스트의 저장소 오염 0건 .mam 미생성
V10 tier1 섹션 헤더 5개 정합 전부 일치 5/5
V11 미사용화되는 헬퍼·임포트 없음 run_lib_func 15회, get_mqtt_common 7회, subprocess/shlex/hmac/hashlib 전부 잔존 사용
V12 수집 개수 259 → 256 256 collected
V13 pytest tests/ -q 전체 256 passed 256 passed in 392.29s

5.1 전체 회귀 (Rev.2 사본)

256 passed in 392.29s (0:06:32)

원본 로그는 proposed/pytest_rev2.log 입니다. 참고로 Rev.1(255건) 은 376.08s 였습니다 — 차이 16s 는 신규 테스트 1건(0.09s)으로 설명되지 않는 실행 간 편차이며, Rev.1 §8 에서 이미 밝혔듯 이 스위트의 총 실행 시간은 P2-2 의 판단 근거가 아닙니다.


6. 검증 한계 (Rev.1 §10 갱신)

  1. 실측은 5e519e2 로컬 클론에서 수행. 실제 트리에서의 256 passed 는 미확인 — §4.2-1 이 요구합니다.
  2. create_session.sh 본류 실행 스모크 미수행. 신규 테스트는 -h 조기 종료 경로까지만 검증합니다(§1.6). §4.2-2 가 요구합니다.
  3. 변이 검사는 create_session.sh 4종에 한정. lib.sh 스텁 제거·registry.py·_REAL_HERDR_PATH 에는 변이 검사를 적용하지 않았습니다(제거 대상이라 고정할 계약이 없음 — Rev.1 §4.3).
  4. _REAL_HERDR_PATH 의 저장소 외부 소비자 미검색. 확인 범위는 저장소 트리, 생성된 .mam/shim/herdr, .agents/hooks/, ~/.claude/settings.json (Rev.1 §10-4 유지).
  5. shellcheck 미설치 — 정적 분석은 bash -n 까지.
  6. macOS · 직렬 실행. Linux · pytest-xdist 병렬 미검증(xdist 미설치). 신규 테스트는 mam_sandbox(tmp_path) 안에서만 쓰기하므로 병렬 안전할 것으로 판단하나 실측은 아닙니다.
  7. 챌린지 §Challenge 3 의 "15건" 반박은 IMPROVEMENTS.md 현행 파일 대조에 근거합니다. 챌린저가 다른 시점의 파일을 봤을 가능성은 배제하지 못하나, HEAD 가 5e519e2 로 고정되어 있고 워킹 트리가 clean 이므로 두 에이전트가 본 파일은 동일해야 합니다.
  8. 본 계획은 Planner 산출물이므로 IMPROVEMENTS.md / LOG.md / 소스를 직접 수정하지 않았습니다. §4 는 구현자가 적용할 명세입니다.