fix(issue-21): review ffb5055e R1-R3 - re-check the tree after --stop, refuse submodule stashes, roll a partial submodule init back to a usable checkout

Review ffb5055e (planner-reviewer-codex-01):

- R1 [P1] worktree_remove.sh: the clean-tree and submodule checks ran only
  before --stop, so work a stopping session wrote (or committed, or stashed)
  was deleted by the forced removal. The checks are one function now, run
  before any change and again right before the removal; any finding then
  refuses with exit 3 (sessions already stopped, worktree kept), and the
  removal method follows that latest look.
- R2 [P1] lib_py/worktree.py: a submodule stash is reachable from no branch
  and leaves the status clean, and the forced removal deletes the worktree's
  own submodule repositories. `git stash list` in every submodule is a
  separate finding (`stashes`), refused like dirty/unpushed; errors fail
  closed as before.
- R3 [P2] worktree_prepare.sh: when one submodule cloned and a later one
  failed, the rollback deleted modules/ while the cloned checkout's .git file
  still pointed into it, and `git status` failed in the worktree. The rollback
  now undoes only this attempt: each submodule whose git dir did not exist
  before is deinitialised before its git dir is deleted; submodules
  initialised earlier are kept; shared submodule.* config values are restored
  exactly (new keys removed, pre-existing values put back - init and deinit
  both write .git/config). A final `git status` either confirms a usable
  checkout or prints what to inspect.

Tests W6e (a write during stop keeps the worktree and the file), W6f (a
submodule stash refuses), W6g (partial failure on a fresh worktree: usable,
no modules/, no new config, then prepare and a plain removal work) and W6h (a
failing retry keeps the earlier `sm` init and its config value). The skill
docs and the design doc describe the recheck, the stash rule and the scoped
rollback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
This commit is contained in:
GodopuandClaude Opus 5 committed 2026-09-14 16:57:39 +00:00
1 parent 7988db9adf
commit bbbbb95ef6
7 files changed
+181 -42

No files matched your search

+2 -2
View File
@@ -89,8 +89,8 @@ bash "$MAIN/.agents/skills/multi-agent-mux-stop/scripts/worktree_remove.sh" --pa
- 준비·해체 헬퍼는 항목 4 로 구현했다(`worktree_prepare.sh`, `worktree_remove.sh`).
- `.mam.env` 는 복사하지 않는다. creator 에는 제어 루트의 것을 `MAM_ENV_FILE` 로 준다.
- `.venv` 는 제어 루트 것의 심링크다. 무시 규칙(`/.venv`, `.pytest_cache/`)은 linked worktree 가 공유하는 `$GIT_COMMON_DIR/info/exclude` 에 들어간다. 그래서 본 체크아웃에도 적용되며, 둘 다 본 체크아웃에서도 무시해야 하는 산출물이다.
- 서브모듈은 `--submodules` 로만 초기화한다. 초기화는 성공·실패와 무관하게 워크트리 git 디렉터리에 `modules/` 를 남기고, 그러면 git 이 force 없는 `worktree remove` 를 거부한다. prepare 는 실패한 초기화의 흔적(`modules/`, 새 `submodule.*` 공유 설정)을 되돌리고 exit 1 한다.
- 서브모듈을 초기화한 워크트리는 remove 가 수정과 원격에 없는 커밋(`HEAD` 포함 — 서브모듈은 detached HEAD 다)을 확인한 뒤 `--force` 를 한 번만 써서 제거한다. **사람이 `git worktree remove --force` 를 직접 쓰지 말 것.** 워크트리 전용 서브모듈 저장소와 미푸시 커밋이 함께 사라진다.
- 서브모듈은 `--submodules` 로만 초기화한다. 초기화는 성공·실패와 무관하게 워크트리 git 디렉터리에 `modules/` 를 남기고, 그러면 git 이 force 없는 `worktree remove` 를 거부한다. prepare 는 실패한 시도가 만든 것만 되돌리고 exit 1 한다. 새 체크아웃은 git 디렉터리보다 먼저 deinit 해서 워크트리가 쓸 수 있는 체크아웃으로 남게 하고, 새 `submodule.*` 공유 설정 키는 지운다. 이전 초기화 상태와 기존 설정 값은 보존한다.
- 서브모듈을 초기화한 워크트리는 remove 가 수정, 원격에 없는 커밋(`HEAD` 포함 — 서브모듈은 detached HEAD 다), stash 를 확인한 뒤 `--force` 를 한 번만 써서 제거한다. 이 확인은 `--stop` 으로 세션을 정지한 뒤 제거 직전에 한 번 더 한다(정지 중에도 쓰기·커밋·stash 가 생길 수 있다). **사람이 `git worktree remove --force` 를 직접 쓰지 말 것.** 워크트리 전용 서브모듈 저장소와 미푸시 커밋이 함께 사라진다.
## 5. 필요한 변경 (Issue #21 작업 항목)