fix(issue-21): PR #29 review R1-R3 - untracked-safe cleanliness checks; a worktree gets its own venv

R1/R2: `git status --porcelain` honours status.showUntrackedFiles, so a
`no` setting in a submodule (or the worktree) hid untracked files from the
checks that justify worktree_remove.sh's single --force and from the review
snapshot guard (_dirty_submodules), which then approved content it never
captured. All three checks (lib_py/worktree.py, worktree_remove.sh,
runner._dirty_submodules) now pass --untracked-files=all. The source tree's
own snapshot was already safe: `git add -A` ignores that setting.

R3: worktree_prepare.sh no longer links .venv to the control root's. It
creates the worktree's own venv with the control root's interpreter and
installs requirements*.txt; a leftover link is refused (exit 1), a control
root without an interpreter is a warning (VENV=skipped); VENV= is printed.

Tests: w1 (real venv, kept on re-run), w1b (link refused / skipped), w6e
parametrised over the hidden setting for the post-stop recheck, w6e2
(hidden untracked file in the submodule and in the worktree blocks the
removal), l17 untracked-hidden / during-hidden (guard refuses before the
first review; a mid-round hidden file makes the round stale). Each new
case fails without the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-09-20 20:43:20 +09:00
1 parent 90a289da44
commit 08db1da9d5
9 files changed
+107 -29

No files matched your search

+1 -1
View File
@@ -120,7 +120,7 @@ Every check runs first and changes nothing; a refusal exits 3:
1. a linked worktree of the control root, not locked (`git worktree unlock` first - git refuses even `--force` on a locked one);
2. running sessions under it only with `--stop` (stopped normally, conversations kept);
3. no open job of those sessions - never cancelled automatically, they may belong to a running loop (the delegate job of a session `--stop` stops is cancelled by stop itself);
4. a clean tree, submodules included (`status --porcelain --ignore-submodules=none`);
4. a clean tree, submodules included (`status --porcelain --untracked-files=all --ignore-submodules=none` - a `status.showUntrackedFiles=no` setting cannot hide a file from a check that decides a forced removal);
5. inside submodules: no modification, no commit reachable from `HEAD` or any local ref that is on no remote (a submodule sits on a detached HEAD; a commit kept only by a tag or a note is protected too) and no stash (`refs/stash` is reachable from no branch and leaves the status clean) - checked in every repository under the worktree's `modules/`, including deinitialised submodules (their repository stays there) and nested ones; a repository that cannot be checked refuses.
Then it stops the sessions, re-checks the jobs, runs checks 4 and 5 **again** (a session that is being stopped can still write, commit or stash - any change refuses with exit 3, sessions already stopped) and removes the worktree (the branch is kept): plain, or - when the worktree holds submodule state, which git refuses to remove otherwise - a **single `--force` justified by checks 4 and 5**. Do not run `git worktree remove --force` by hand on such a worktree: it deletes the worktree's own submodule repositories, unpushed commits included.