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

@@ -84,11 +84,13 @@ fi
# (4) clean, submodules included, and (5) submodules. A function: it runs now,
# before any change, and again right before the removal (review R1). It sets
# METHOD from what it saw and returns 1 on any finding.
# METHOD from what it saw and returns 1 on any finding. --untracked-files=all:
# a status.showUntrackedFiles=no setting (worktree or submodule) must not hide
# files from a check that decides a forced removal (review R1).
METHOD="plain" # set before any early return below: the summary line reads it under set -u
judge_tree() {
local found=0 dirty sub trace key val
if ! dirty="$(git -C "$WT" status --porcelain --ignore-submodules=none 2>&1)"; then
if ! dirty="$(git -C "$WT" status --porcelain --untracked-files=all --ignore-submodules=none 2>&1)"; then
refuse "git status failed in the worktree: $dirty"
if [ ! -d "$WT" ]; then
echo " the directory no longer exists; if that is intended, run: git -C '$MAIN' worktree prune" >&2