Issue #21 items 4, 5, 7 (worktree helpers, review identity, acceptance A1-A8), #20 test isolation, #27 .gitignore managed block, #28 host portability. VERSIONS.md carries the entry; the eight review reports of PR #29 (two rounds plus the codex re-review) are archived under .agents/reports/<session>/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
171 lines
21 KiB
Markdown
171 lines
21 KiB
Markdown
# 🔍 Review Report — PR #29 병합 전 독립 재검토 (job `291e38b5`)
|
||
|
||
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-09-20
|
||
- **대상**: PR #29 `feat/issue-21-items-4-5-7` **@ `46ea4cc`**, base `main @ 1134d74` (v4.6.0). 20커밋, `git diff --stat 1134d74...HEAD` = **25 files, +3229/−108**.
|
||
- **검토 트리**: 워크트리 `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-29` (detached @ `46ea4cc`, 자체 `.venv`).
|
||
- **안전 규칙 준수**: 워크트리에 파일 수정·커밋·checkout 없음. 본 체크아웃은 `main @ 1134d74` 그대로(checkout/switch/merge 없음). `worktree_remove.sh` 를 실제 워크트리에 실행하지 않았다. 실제 `.mam`·monitor 데몬·에이전트 세션 조작 없음, push·merge·이슈/PR 상태 변경 없음. 재현 실험은 scratchpad 의 임시 저장소(uuid4 이름)에서만 했다.
|
||
- **heartbeat**: 브리프 직후 `started --heartbeat 30` 을 가장 먼저 발행했다.
|
||
|
||
## 결론 요약
|
||
|
||
브리프의 8개 항목을 모두 확인했다. **병합을 막을 결함은 찾지 못했다.** 항목 5(리뷰 identity)의 설계·구현 품질은 높고, 기본 경로(옵션 없는 루프)의 호환성은 코드상 보장된다. 아래 **비차단 지적 9건**(N1–N9)을 남긴다. 그중 N1 은 재현 실험으로 확인한 실제 소실 가능성(판정 증거의 gc 소멸)이므로 후속 이슈 등록을 권한다.
|
||
|
||
## 1. 항목 4 — 워크트리 준비·해체 헬퍼
|
||
|
||
### 1-1. `.venv` — 브리프가 물은 "실제 venv" 는 아니다 (N2, 비차단)
|
||
|
||
`worktree_prepare.sh:111-118` 은 여전히 **심링크**를 만든다(`ln -s "$MAIN/.venv" "$WT/.venv"`). 실제 venv 생성도, 인터프리터 경로 전달도 하지 않는다. 다만 이번 PR·base 에서 **커밋 사고 경로는 3중으로 닫혀 있다**:
|
||
|
||
| 층 | 근거 |
|
||
|---|---|
|
||
| 저장소 `.gitignore` | base `1134d74` 의 `.gitignore:15` 가 `.venv`(슬래시 없음) — 파일·링크·디렉터리 모두 무시 |
|
||
| 공유 `info/exclude` | `worktree_prepare.sh:120-129` 가 `/.venv`·`.pytest_cache/` 를 `$GIT_COMMON_DIR/info/exclude` 에 추가 |
|
||
| 배포 관리 블록 | `deploy/install.sh:549`·`deploy/install_mam.sh:249` 가 `/.venv/` → `/.venv` (#27 `fadd38c`) |
|
||
|
||
남는 위험은 두 가지이며, 둘 다 헬퍼가 유발하지는 않는다.
|
||
- `rm -rf "$WT/.venv/"` 처럼 **끝에 슬래시를 붙이면 링크를 따라가** 제어 루트의 venv 내용을 지운다. 헬퍼는 그런 호출을 하지 않지만(스크립트 2개의 `rm -rf` 는 `$gd/modules*` 뿐), 사람·에이전트의 수작업에서는 그대로 남는 함정이다.
|
||
- 워크트리와 본 체크아웃이 venv 를 공유하므로 브랜치별 의존성 분기가 불가능하다.
|
||
|
||
→ 권고: 헬퍼에 `--venv link|create` 같은 선택지를 두거나, 최소한 SKILL.md 에 "`.venv` 는 링크이므로 워크트리에서 `pip install`·`rm -rf .venv/` 금지"를 명시.
|
||
|
||
### 1-2. `info/exclude` 는 공유물이다 (N3, 비차단)
|
||
|
||
linked worktree 의 `info/exclude` 는 `$GIT_COMMON_DIR` 의 것이므로 prepare 가 **본 체크아웃의 무시 규칙도 바꾼다**. 코드 주석(`:120-122`)과 설계 문서가 이를 명시하고, 추가되는 두 패턴은 본 체크아웃에서도 산출물이다. 수용 가능하나, 되돌리는 경로(remove 시 정리)는 없다.
|
||
|
||
### 1-3. 서브모듈 opt-in·롤백
|
||
|
||
`worktree_prepare.sh:131-204`: `--submodules` 일 때만 초기화하고, 초기화 전 상태(각 서브모듈의 git dir 존재 여부, 공용 `.git/config` 의 `submodule.<n>.url|active`)를 **인덱스 배열**(bash 3.2 호환)로 기록한다. 초기화 안 된 경로에 파일이 이미 있으면 **아무것도 바꾸기 전에** exit 3 으로 거부한다(R4) — 롤백이 "이번 시도가 만든 것"만 지울 수 있다는 전제를 실제로 성립시키는 올바른 순서다. 실패 시 deinit → `.git` 파일 제거 → `modules/<name>` 제거 → 없던 `modules/` 제거 → config 복원 순으로 되돌리고, 마지막에 `git status` 로 워크트리가 여전히 쓸 수 있는 체크아웃인지 확인해 메시지를 갈라 준다. 사람이 강제 제거로 더 망가뜨리지 말라는 안내까지 있다.
|
||
|
||
### 1-4. `--force` 가 작업을 잃을 수 있는가 — 잃지 않는다
|
||
|
||
`worktree_remove.sh` 의 판정은 **변경을 가하기 전에** 모두 끝난다(`:57-121`): 잠금, 실행 중 세션(`--stop` 없으면 거부), 열린 잡, 트리 청결(서브모듈 포함), 서브모듈 소견. `--force` 는 워크트리 git dir 에 `modules/` 가 있어 git 이 plain remove 를 거부하는 경우에만, 그것도 **단 한 번** 쓴다(`METHOD="single --force"`).
|
||
|
||
- **R1(정지 중 쓰기)**: `judge_tree()` 를 `--stop` **이후 다시** 실행하고, 달라졌으면 `:154` 에서 거부한다.
|
||
- **R6(tag/notes 로만 보존된 커밋)**: `lib_py/worktree.py:UNPUSHED_REVS = ("HEAD","--exclude=refs/stash","--all","--not","--remotes")` 로 모든 로컬 ref 기준 미푸시 커밋을 본다. stash 는 별도 확인.
|
||
- **R5(deinit 저장소)**: `submodule foreach` 로 닿지 않는 `modules/` 아래 저장소까지 `--git-dir` 로 직접 검사하고, 판단 불가한 "부분 저장소"는 오류로 남겨 거부한다.
|
||
|
||
즉 `--force` 시점에는 "미커밋·미푸시·stash 없음"이 증명돼 있다. 한 가지만 적어 둔다(N8): plain/force 어느 쪽이든 git 은 **무시된 파일**을 말없이 지운다. 워크트리에 테스트가 남긴 `.mam/delegate_job_logs` 같은 산출물은 제거와 함께 사라진다(제어 평면이 본 체크아웃이므로 설계상 무해).
|
||
|
||
또 하나(N7): 열린 잡 검사는 `registry_dirs`(`MAM_JOBS_DIR` → `WORKSPACE_ROOT/.mam/jobs` → 행들의 `control_plane.mam_jobs_dir`)만 본다. 행이 없는 워크트리 로컬 `.mam/jobs` 의 잡은 보이지 않는다. prepare 가 만든 creator 는 제어 루트에 묶이므로 모델상 발생하지 않는 경로지만, 수작업으로 만든 워크트리에는 해당한다.
|
||
|
||
## 2. 항목 4 — resume 계약 변경(`2679ddf`)은 기존 사용자를 깨지 않는다
|
||
|
||
`resume_session.sh:50-70` 이 행의 `pane.cwd` 와 `--workspace` 를 비교해 다르면 exit 2, 경로가 사라졌으면 exit 2 로 안내한다. 기존 단일 워크스페이스 사용자에게 영향이 없는 이유:
|
||
|
||
1. `--workspace` 는 원래부터 **필수 인자**이고(`:42`), 기존 행의 `pane.cwd` 는 그 워크스페이스 경로다. 본 체크아웃의 실제 `.mam/agent-sessions.yaml` 5개 행 모두 `cwd: /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux` 로, 평소 호출과 일치한다.
|
||
2. 비교는 `mam_abs_workspace`(`lib.sh:1606-1609`, `cd -P … pwd -P`)로 정규화한 뒤 하므로 심링크·상대경로 표기 차이는 통과한다.
|
||
3. `pane.cwd` 가 없는 구(舊) 행은 `ROW_CWD` 가 비어 검사 자체를 건너뛴다.
|
||
|
||
바인딩 영속은 **이미 실행 중인 경로에도** 적용된다: `:145` 가 `CONTROL_PLANE_JSON="$CP_JSON"` 를 `update_yaml_resumed.sh` 에 넘기고, 그쪽 `:168` 이 이를 읽어 행에 기록한다(A13). 계산 실패 시 워크트리면 exit 2, 제어 루트면 경고 후 무기록 — create 쪽(`create_session.sh:211-221`)과 같은 규칙이다.
|
||
|
||
## 3. 항목 5 — 리뷰 identity
|
||
|
||
- **기록**: `_record_round`(`runner.py`)가 라운드마다 `iteration/base_sha/head_sha/tree_sha/review_commit/isolation/review_path/jobs/verdicts/reviewers(evidence)/stale` 를 남기고 체크포인트에 `loop_id` 와 `review_rounds` 를 싣는다. 승격 시에는 리포트 옆에 `report-<jid>.identity.json` 을 원자적으로(`.tmp` → `replace`) 쓴다.
|
||
- **스냅샷**: `_worktree_tree()` 가 임시 `GIT_INDEX_FILE` 로 `read-tree HEAD` → `add -A` → `write-tree` 하여 **실제 인덱스·status·ref 를 건드리지 않는다**. 미커밋이 있으면 `commit-tree` 로 ref 없는 스냅샷 커밋을 만든다. untracked 중첩 저장소는 gitlink 만 남아 고정 불가이므로 거부하고(`_snapshot`), dirty 서브모듈도 거부한다(`_dirty_submodules` 는 `--ignore-submodules=none` 으로 물어 `submodule.*.ignore` 설정이 숨기지 못하게 한다). 판정 불가(`None`)는 전부 실패로 떨어지는 fail-closed 다.
|
||
- **격리**: `_open_review_tree` 는 `TMPDIR/mam-review/<loop_id>/<n>` 에 detached 워크트리를 만들고, **실패하면 격리를 낮추지 않고 exit 1**(로그 문구까지 그렇게 적혀 있다). `_close_review_tree` 는 remove 실패 시에만 `prune` 을 부르고, 부모 디렉터리는 비었을 때만 지운다. `run()` 이 `_run_body()` 를 `finally: _close_all_review_trees()` 로 감싸고 `release_guard` 도 같은 정리를 한다.
|
||
- **stale 재리뷰**: 라운드 판정 뒤 `_round_is_stale`(현재 head·tree 비교)로 소스 변경을 잡아 승인 전량 폐기 후 재리뷰, 마지막 라운드면 exit 1. 승격 직전에도 `_identity_now() != (accepted head, tree)` 면 "source changed after approval; not promoting" 으로 exit 1(B10).
|
||
- **원격 리뷰어**: `_check_remote_recipients` 가 **어떤 잡도 위임하기 전에** 판정 수신자 전원을 검사해 기본 거부, `--remote-review diff-only` 만 허용하며 그때 diff 가 truncated/unavailable 이면 증거가 없다고 exit 1.
|
||
- **기본 동작 동일성**: `_context_active()` 가 거짓이면 `_worktree_context()` 는 빈 문자열을 반환하고 `collect_changes_diff(None)` 은 기존 호출과 동일하다. `_dispatch` 의 새 인자는 기본값(`remote=False, purpose="work"`)이라 프롬프트는 바이트 동일하다. 다만 옵션과 무관하게 `Loop id:` 로그 1줄과 체크포인트의 `loop_id` 필드가 늘었다(N9, 무해).
|
||
|
||
### N1 (비차단, 후속 권고) — 스냅샷 커밋이 gc 로 사라진다
|
||
|
||
`_snapshot` 의 스냅샷 커밋은 ref 가 없고, 유일한 도달 경로였던 리뷰 워크트리 HEAD 는 `_close_review_tree` 가 라운드 판정 직후 없앤다. 그래서 `identity.json` 의 `review_commit`·`tree_sha` 는 **나중에 검증하려 할 때 이미 없을 수 있다**. scratchpad 임시 저장소에서 재현했다(워크트리 제거 → `reflog expire --expire=now --all` → `gc --prune=now` → `git cat-file -e <snap>` 실패). 일상적으로도 unreachable 객체는 기본 2주 뒤 대상이다. 미커밋이 있던 라운드에만 해당하며(클린 트리면 `review_commit == head_sha`), 판정 자체의 정합성에는 영향이 없다.
|
||
|
||
→ 권고: `refs/mam/review/<loop_id>/<n>` 같은 ref 로 스냅샷을 고정하고, 루프 종료 시 정책적으로 정리(또는 보존).
|
||
|
||
### N6 (비차단) — 강제 종료 시 리뷰 워크트리 잔존
|
||
|
||
`finally`/`release_guard`/시그널 핸들러로 정상·TERM·INT 경로는 덮이지만, SIGKILL 이면 `TMPDIR/mam-review/...` 디렉터리와 소스 저장소의 worktree 항목이 남는다. `prune` 은 remove 실패 시에만 부른다(의도된 선택, 주석에 근거 있음).
|
||
|
||
## 4. #20 격리 — 설계와 실측
|
||
|
||
- `tests/conftest.py` 의 `pytest_configure` 가 **수집 이전에** ambient 바인딩 4개를 기록하고 `AGENT_SESSIONS_YAML`·`MAM_JOBS_DIR`·`DELEGATE_JOB_LOGS_DIR` 를 제거한다. `WORKSPACE_ROOT` 는 **다른 체크아웃을 가리킬 때만** 제거한다(이 체크아웃을 가리키면 lib.sh 가 어차피 유도하는 값). import 시점에 `os.environ` 을 복사하는 모듈(`test_orc_onboard.SCRUBBED_ENV`) 때문에 세션 fixture 로는 늦다는 설명이 정확하다. `pytest_unconfigure` 가 되돌려 in-process `pytest.main()` 호출자를 배려한다.
|
||
- autouse `_isolate_mam_state_dirs` 는 **env 만** 설정하고 디렉터리를 만들지 않으며, 숨김 이름(`.mam-test-state`)이라 `tmp_path` 를 나열하는 기존 테스트를 깨지 않는다. `mam_sandbox` 와 테스트 자신의 `setenv/delenv` 가 뒤에 실행돼 이긴다.
|
||
- 세션 가드 `mam_state_leak_guard` + `_mam_state_guard.py` 는 **보고만 하고 절대 지우지 않는다**(실제 제어 평면이므로 옳다). 감시 대상은 이 체크아웃의 `.mam` 과 기록된 바인딩이 가리킨 제어 평면이며, 동시에 도는 실제 위임과는 "레코드 먼저 → 등록된 세션" 규칙으로 구분한다.
|
||
- #15 옵트아웃(N4, 비차단): `test_issue15_registry_isolation.py` 의 누수 단언이 "전체 목록 동일"에서 "자기 잡 id 가 실제 루트에 없음 + 그 사이 늘어난 항목 중 자기 세션 이름이 없음"으로 **약해졌다**. 동시 실행되는 실제 위임 때문에 전체 비교가 오탐을 냈다는 근거가 타당하고, 세션 이름 스캔이 핵심 보장을 유지하므로 수용한다.
|
||
|
||
## 5. acceptance5 안정화(`7bc3e1a`·`371099a`·`e6d2168`) — 계약을 약화하지 않는가
|
||
|
||
| 변경 | 판단 |
|
||
|---|---|
|
||
| 세션 이름·HMAC secret 을 실행마다 유니크하게(`tag = os.urandom(4).hex()`) | **개선**. 공개 브로커에 retain 된 과거 dispatch 가 이번 실행의 잡 대신 배달되던 교차 오염을 없앤다 |
|
||
| `--heartbeat-misses 3 → 10` | 허용. 이 시험의 목적은 "원격 러너가 브로커만으로 잡을 끝낸다"이고, K×N 무응답 임계 자체는 AC1·AC3 가 고정한다. 원격 러너의 `started --heartbeat` 계약은 그대로다 |
|
||
| 단언에 `advanced to 'completed'` 추가 | 허용. retain 된 종단 이벤트만 배달된 경우도 프로토콜상 정당한 배달 확인이다 |
|
||
| `_skip_if_broker_did_not_deliver` | 조건부 허용(N5). skip 은 "러너가 started 를 발행했고, 구독자가 **맞는 토픽**에 SUBSCRIBED 했고, 그 잡에 대해 **아무것도**(드롭 로그조차) 받지 못했다"는 세 조건이 동시에 성립할 때만이다. HMAC·identity 드롭, 잘못된 토픽, 발행 실패는 여전히 실패한다. 다만 구독자 배달 경로의 실제 회귀가 같은 증상을 내면 skip 으로 가려질 수 있다 |
|
||
|
||
## 6. 수용 사례·신규 테스트의 결정성·격리
|
||
|
||
- `tests/test_issue21_acceptance.py`(A1–A8, 278줄): 실제 herdr·실제 브로커를 쓰지 않는다. mock herdr / stub 위임 + `mam_sandbox`·`tmp_path` 기반이며, 브로커 관련 언급은 주석과 "브로커 도달 불가 조건에서 래퍼를 돌린다"는 A3 한 곳(`:102`)뿐이다.
|
||
- `tests/test_issue21_loop_b.py`(526줄, L1–L21): 러너를 직접 구동하되 위임·대기를 스텁으로 바꾸고 임시 git 저장소에서 돈다. 브로커 의존 0, `time.sleep` 없음. L1/L12/L14b/L18b 가 **옵션 없는 기존 동작 불변**을 별도로 고정하는 점이 좋다.
|
||
- `tests/test_issue21_phase2_loop_a.py`(649줄): `time.sleep(0.2)` 1회(`:312`) 외에 시간 의존 없음. `deploy/install.sh` 를 임시 타깃에 실행하는 경로가 있으나 모두 `tmp_path` 안이다.
|
||
- `tests/test_issue20_state_isolation.py`(202줄): 순수 함수 단위 + 중첩 pytest 1건(설계된 skip).
|
||
- uuid 사용은 `uuid.uuid4()`(issue15 의 `_unique`)로 브리프 규칙과 맞는다.
|
||
|
||
## 7. 문서 정합성과 권고의 타당성
|
||
|
||
- `docs/design/worktree-creator-model.md`: 항목 표가 1·2·3·6 = 완료(v4.6.0), 4·5·7 = 이 브랜치, 8 = 보류로 갱신됐고, 실행 예시가 `worktree_prepare.sh`/`worktree_remove.sh` 기준으로 다시 쓰였다. 호환성 표에 resume 의 exit 2 조건, remote 리뷰어 거부/opt-in, "실제 herdr pane 값 확인은 단계 C" 라는 한계가 명시돼 과장 없이 정확하다.
|
||
- `multi-agent-mux-loop/SKILL.md` 가 `--review-isolation`·`--remote-review` 를 표에 추가하고 기본값·거부 시점(위임 이전)·evidence 기록까지 적는다. create/resume/stop SKILL.md 도 헬퍼를 반영한다.
|
||
- PR 본문의 **비차단 권고 4건**을 코드에서 확인했고 모두 사실이다: ① A4 의 `_main_state` 비교가 untracked `.agents/`·`.mam/` 변화를 보지 못함, ② A4 가 `info/exclude` 추가를 **선반영**한 뒤 비교함(`test_issue21_acceptance.py:121-122`), ③ `_close_review_tree` 의 remove 실패 시 `prune` 이 전역, ④ `worktree_remove.sh` 의 서브모듈 거부 출력 중복. 넷 다 병합을 막을 성질이 아니며 후속으로 적절하다.
|
||
- #30(멈춘 에이전트 감지) 등록은 타당하다. #18 이 "프로세스 생존"에 결속된 설계라 "살아 있지만 멈춘" 상태는 그 계약 밖이며, 해결은 알림·운영 수준이므로 별도 이슈가 맞다.
|
||
|
||
## 8. 전체 스위트 실행 (이 리뷰어가 직접, 워크트리에서)
|
||
|
||
```
|
||
cd <pr-29 워크트리> && PYTHONDONTWRITEBYTECODE=1 .venv/bin/python -m pytest -p no:cacheprovider -q -rfE tests/
|
||
→ 2 failed, 1011 passed, 2 skipped in 2266.28s (0:37:46) (HEAD 46ea4cc, 실행 전후 동일)
|
||
```
|
||
|
||
| 실패 | 판정 |
|
||
|---|---|
|
||
| `test_issue14_delivery_ack.py::test_wrapper_idle_grace_never_outlives_the_wall_clock` | **PR 회귀 아님**. 이 시험은 PR 이 건드리지 않았다(`git diff -U0` 의 issue14 hunk 는 전부 `_job_for_inprocess_publish` 뒤쪽 `:520` 이후). 단독 재실행에서도 close_delay 28.5 s(임계 20+8)로 재현되며, **다른 브랜치에 이미 `90a289d test(issue-14): widen the wall-clock close margin under suite load` 가 있다** — 임계 자체가 좁다는 기지 문제다 |
|
||
| `test_issue19_subscriber_daemon_lifecycle.py::test_mqtt_loop_sigterm_exits_cleanly` | **브로커 flake**. 파일 자체가 PR 변경 0줄이고, 로그가 `broker did not accept connection; falling back to polling` 이다. 단독 재실행 **통과** |
|
||
|
||
- PR 본문의 "이 호스트 기준 실패 33건(#28)" 주장은 **내 실행으로는 확인되지 않는다**(내 조건에서는 2건). 제어 평면 바인딩이 실린 creator pane 조건에서 재현된 수치로 보이며, 내가 재현할 수 있는 조건이 아니므로 그 수치의 참·거짓을 주장하지 않는다. 내가 말할 수 있는 것은 **내 조건에서 새 회귀가 0 이라는 것**이다.
|
||
|
||
### 격리·위생 실측 (#20, 브리프 항목 4)
|
||
|
||
| 항목 | 결과 |
|
||
|---|---|
|
||
| 실제 `.mam/jobs`(본 체크아웃) 전후 | **차이 0** (레코드 추가 0건) |
|
||
| 실제 `.mam/delegate_job_logs` 전후 | **차이 0** |
|
||
| 워크트리 `git status --short --ignored` 전후 | **완전 동일**(무시 파일 포함) — 스위트가 워크트리에 아무것도 남기지 않았다 |
|
||
| 워크트리 HEAD | 실행 전후 `46ea4cc` |
|
||
| 본 체크아웃 | `main @ 1134d74`, 클린 |
|
||
|
||
이전 회차(PR #22)에서 내가 지적했던 "리뷰어 pytest 가 워크트리에 `.mam/delegate_job_logs`·`.mam/shim` 을 남긴다" 가 이번 실행에서는 **재현되지 않았다**. #20 수정이 의도대로 동작한다.
|
||
|
||
## 9. 병합 차단 여부 (브리프 항목 8)
|
||
|
||
알려진 버전 bump 미포함(오케스트레이터가 v4.7.0 으로 처리) 외에 **병합을 막을 결함은 찾지 못했다**. N1–N9 는 모두 비차단이며, 후속 순위는 N1 > N2 > N5 순으로 본다.
|
||
|
||
| # | 지적 | 성격 |
|
||
|---|---|---|
|
||
| N1 | 스냅샷 커밋·tree 가 ref 없이 남아 gc 로 소멸 → `identity.json` 사후 검증 불가(재현 확인) | 후속 이슈 권고 |
|
||
| N2 | prepare 의 `.venv` 는 여전히 심링크(실제 venv/인터프리터 경로 아님); 커밋 경로는 3중으로 닫혔으나 `rm -rf .venv/`·의존성 공유 위험 잔존 | 문서화 또는 옵션화 권고 |
|
||
| N3 | prepare 가 공유 `info/exclude` 를 바꿔 본 체크아웃에도 적용(되돌림 경로 없음) | 기지·문서화됨 |
|
||
| N4 | issue15 누수 단언이 전체 목록 비교에서 자기 잡 id + 세션 스캔으로 약화(근거 타당) | 수용 |
|
||
| N5 | acceptance5 의 skip 경로가 구독자 배달 회귀를 가릴 여지(조건은 매우 좁음), `--heartbeat-misses 3→10` | 수용 |
|
||
| N6 | SIGKILL 시 리뷰 워크트리·worktree 항목 잔존(prune 은 실패 시에만) | 사소 |
|
||
| N7 | remove 의 열린 잡 검사가 행 없는 워크트리 로컬 `.mam/jobs` 를 못 봄 | 모델 밖 경로 |
|
||
| N8 | remove 는 plain/force 모두 무시 파일을 말없이 삭제(추적 작업 손실은 없음) | 정보 |
|
||
| N9 | 옵션 없는 루프에도 `Loop id:` 로그 1줄과 체크포인트 `loop_id` 필드가 추가(프롬프트는 바이트 동일) | 정보 |
|
||
|
||
## 10. 교차검증 가이드
|
||
|
||
```bash
|
||
WT=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-29
|
||
(cd "$WT" && PYTHONDONTWRITEBYTECODE=1 .venv/bin/python -m pytest -p no:cacheprovider -q -rfE tests/) # 2 failed / 1011 passed
|
||
(cd "$WT" && PYTHONDONTWRITEBYTECODE=1 .venv/bin/python -m pytest -p no:cacheprovider -q \
|
||
tests/test_issue14_delivery_ack.py::test_wrapper_idle_grace_never_outlives_the_wall_clock \
|
||
tests/test_issue19_subscriber_daemon_lifecycle.py::test_mqtt_loop_sigterm_exits_cleanly) # issue19 통과, issue14 재현
|
||
git -C "$WT" diff 1134d74...HEAD -U0 -- tests/test_issue14_delivery_ack.py | grep '^@@' # 실패 시험은 변경 밖
|
||
# N1 재현 (임시 저장소에서만)
|
||
R=$(mktemp -d); cd "$R"; git init -q .; git config user.email t@t; git config user.name t
|
||
echo a > a; git add a; git commit -qm base; H=$(git rev-parse HEAD); echo dirty > b
|
||
GIT_INDEX_FILE=$R/idx git read-tree HEAD; GIT_INDEX_FILE=$R/idx git add -A; T=$(GIT_INDEX_FILE=$R/idx git write-tree)
|
||
C=$(git commit-tree "$T" -p "$H" -m snap); git worktree add -q --detach "$R/wt" "$C"; git worktree remove --force "$R/wt"
|
||
git reflog expire --expire=now --all; git gc --prune=now -q; git cat-file -e "$C^{commit}" || echo "snapshot gone"
|
||
```
|
||
|
||
[VERDICT: PASS]
|