diff --git a/docs/TESTING.md b/docs/TESTING.md index 86fa7a3..a47f618 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -624,11 +624,11 @@ bash hooks/tests/parallel-lines-cap-guard.test.sh `PostToolUse:Agent` 在**送出後 5 秒**就觸發(不是收工); subagent **不是獨立行程**(`ps` 只有一個 claude-code 行程,所以數行程數不到線)。 -### A24 — 一條線要有自己的工作目錄:69 條 +### A24 — 一條線要有自己的工作目錄:77 條 ``` bash hooks/tests/line-needs-own-worktree.test.sh ``` -**該看到**:`69/69 通過`。離線,每個案例自己開**兩顆**真的 git repo +一份真的 linked worktree。 +**該看到**:`77/77 通過`。離線,每個案例自己開**兩顆**真的 git repo +一份真的 linked worktree。 **E 群在守什麼**(comment 6587,2026-09-07 補):**閘印出來的那條出路要真的走得通。** 閘訊息教的是 `WORKTREE_OK=1 git -C … checkout …`,舊版讀的卻是 hook 自己的環境變數——PreToolUse hook @@ -637,6 +637,12 @@ bash hooks/tests/line-needs-own-worktree.test.sh `echo WORKTREE_OK=1; git checkout`、前綴掛在別的指令上、值不是 1、寫在 commit 訊息裡,四種都照擋。 修之前拿新測試跑舊閘:(60)(63)(68) 三條紅,其餘全綠——紅的那三條就是真的改到的。 +**F 群**是 `inkstone/ISEP#125` 票上原文的驗收表 A–F((70)–(75),期望值照票:A–E 要 0、**F 要 2**)。 +D/E 是「用 heredoc 寫一份內含 `cd <目錄> && git checkout X` 示範的文件」被當成在切分支(09-02 兩次實地命中), +現在先用 `lib/strip_heredoc.py` 剝掉 body 再解析(跟 D20 閘同一支 helper)。(76) 是寫成測試的已知邊界: +tokenize 層把換行當空白吞掉,`…EOF⏎git checkout main` 從第一版起就看不到(漏擋不是誤攔,與 (53) 同層,另報); +(77) 是它的對照組(換成分號就擋)。 + **D 群在守什麼**(comment 5398,2026-08-29 補):不只要擋對,還要**說得出是哪一個 repo**。 舊版一律拿 payload 的 `cwd` 當答案,於是 `cd <別的 repo> && git checkout` 擋是擋對了, 訊息卻指著 `cwd` 那個 repo ⇒ 照著做的人會在**錯的 repo** 開一份用不到的 worktree, @@ -655,6 +661,8 @@ D 群同時釘住反面:認不出來(`cd $VAR`、`cd -`、cd 到不是 repo - ㉞㉟ 紅 ⇒ `prune` 做錯了。㉞是沒清掉說謊的登記;**㉟是把還在的 worktree 也弄掉了,那是災難** - (63) 紅 ⇒ 閘印的出路又走不通了——被擋的人不會停下來修閘,他會去找繞過去的方法(6587 當天就是) - (64)–(67) 任一紅 ⇒ 出路退化成關鍵字:印個字就能過,閘等於沒有 +- (75) 紅 ⇒ **閘廢了**(#125 原話):剝 heredoc 剝過頭,真的切也放行 +- (76) 紅 ⇒ tokenize 那層被改了,回頭跑 (53) 與推 main 的 10+19 條 ### A25 — `sdd-guard` 退役了,而且退乾淨了:8 條 ``` @@ -1243,7 +1251,7 @@ B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面 | **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/11(2026-08-27) | | **A23 同時跑的線有上限** | isep-guard | ✅ 33/33(2026-08-29,inkstone/ISEP#109) | | **A24 一條線要有自己的工作目錄** | isep-guard | ✅ 35/35(2026-08-29,inkstone/ISEP#109→5391) | -| **A24 出路那一行原樣餵回去走得通** | isep-hand | ✅ 69/69(2026-09-07,inkstone/ISEP#109→6587)+真路徑實跑(薄殼 cwd、`-C ISEP`:擋→印的那行→放行)+舊閘實跑 3 條紅 | +| **A24 出路那一行原樣餵回去走得通+#125 A–F** | isep-hand | ✅ 77/77(2026-09-07,inkstone/ISEP#109→6587、#125)+真路徑實跑(薄殼 cwd、`-C ISEP`:擋→印的那行→放行)+舊閘實跑 3 條紅 | | **A39 D20 閘看 push 實際會推的 repo** | isep-hand | ✅ 26/26(2026-09-07,inkstone/ISEP#109→6629)+真路徑實跑(薄殼 cwd:`-C InkStoneCo` 放行、裸 `git push origin` 擋)+舊閘實跑 7 條紅(4 誤攔+3 漏擋) | | **A26 雲端憑證清單這台拿得到** | isep-hand | ✅ 11/11(2026-09-01,inkstone/ISEP#115→5526/5541/5542/5577)+兩條「該紅」方向實跑 | | **A16 放行的門真的打得開** | 總管 | ✅ 17/17(2026-08-28,inkstone/ISEP#90) | diff --git a/hooks/line-needs-own-worktree.sh b/hooks/line-needs-own-worktree.sh index 260f1c1..9405ef1 100755 --- a/hooks/line-needs-own-worktree.sh +++ b/hooks/line-needs-own-worktree.sh @@ -79,6 +79,13 @@ sys.stdout.write((ti.get("command") or "") if isinstance(ti, dict) else "") ' 2>/dev/null) || exit 0 [ -n "${CMD:-}" ] || exit 0 +# heredoc 的 body 是資料不是指令(inkstone/ISEP#125 缺陷 D/E,2026-09-02 兩次實地命中): +# 用 heredoc 寫一份「內含 `cd <目錄> && git checkout X` 示範」的說明文件,整條指令沒有任何 +# git 操作,卻被當成在切分支擋下——被擋的是「寫文件」這個動作。跟 github-contact-guard 一樣 +# 先把 body 剝掉再解析;剝不動就用原字串(fail-open 的是「這支 helper 有沒有跑」,不是判準)。 +CMD=$(printf '%s' "$CMD" | python3 "$(dirname "$0")/lib/strip_heredoc.py" 2>/dev/null || printf '%s' "$CMD") +[ -n "${CMD:-}" ] || exit 0 + CWD=$(printf '%s' "$PAYLOAD" | python3 -c ' import json, sys try: diff --git a/hooks/tests/line-needs-own-worktree.test.sh b/hooks/tests/line-needs-own-worktree.test.sh index 492b3d2..f4ce540 100755 --- a/hooks/tests/line-needs-own-worktree.test.sh +++ b/hooks/tests/line-needs-own-worktree.test.sh @@ -245,6 +245,46 @@ t 0 "(68) cd 進去再掛前綴:cd && WORKTREE_OK=1 git checkout main t 0 "(69) 在自己的 worktree 裡也掛了前綴 ⇒ 本來就放行,前綴不礙事" \ "WORKTREE_OK=1 git checkout -b feat/x2" "$OWN" +echo +echo "── F 群:inkstone/ISEP#125 驗收表 A–F(票上原文的六條,期望值照票)──" +# A 逃生門原文照打→0 B 環境變數裡真的有→0 C 文件裡只寫 'git checkout X'→0 +# D 文件裡寫 'cd <目錄> && git checkout X'→0 E 同 D 角括號拿掉→0 F 真的要切→2(本體,退化成 0=閘廢了) +t 0 "(70) #125-A 逃生門原文照打:WORKTREE_OK=1 git -C checkout main ⇒ 0" \ + "WORKTREE_OK=1 git -C $REPO checkout main" "$PLAIN" +t 0 "(71) #125-B 同一句、WORKTREE_OK 真的在 hook 的環境裡 ⇒ 0(本來就是,不要弄壞)" \ + "git -C $REPO checkout main" "$PLAIN" "CLAUDE_CODE_CHILD_SESSION=1 WORKTREE_OK=1" +DOC_C="cat > $ROOT/doc-c.md <<'EOF' +把 repo 複製一份,然後 git checkout X +EOF +echo written" +t 0 "(72) #125-C 文件裡只寫 'git checkout X' ⇒ 0" "$DOC_C" +DOC_D="cat > $ROOT/doc-d.md <<'EOF' +示範:cd <目錄> && git checkout X +EOF +echo written" +t 0 "(73) #125-D 文件裡寫 'cd <目錄> && git checkout X' ⇒ 0(寫文件不是執行;09-02 兩次實地命中)" "$DOC_D" +DOC_E="cat > $ROOT/doc-e.md <<'EOF' +示範:cd $REPO && git checkout feat/x +EOF +echo written" +t 0 "(74) #125-E 同 D、角括號拿掉、目錄是真的共用目錄 ⇒ 0" "$DOC_E" +t 2 "(75) #125-F 真的要切(對照組)⇒ 2 🔴 本體,退化成 0=閘廢了" "cd $REPO && git checkout feat/x" "$PLAIN" +# 🔴 已知邊界,寫成測試而不是註解(跟 (53) 同一層、同一個病):tokenize 層把**換行**當空白吞掉, +# 多行指令被併成一句 ⇒ `echo hi⏎git checkout main` 從第一版起就看不到(舊閘實測 exit=0, +# 同一句改成 `echo hi; git checkout main` 就擋)。剝 heredoc 沒有讓它變好也沒有變壞—— +# body 剝掉之後留下的仍是「換行分隔的兩句」。要修得動那層,會連帶改到推 main 那道閘的偵測範圍 +# ⇒ 不在本票裡動(另報)。這條紅了=那層被改了,回頭看 (53) 與推 main 的 10+19 條。 +t 0 "(76) 已知邊界:heredoc 之後**換行**再真的切 ⇒ 這道閘看不到(漏擋,不是誤攔;(53) 同層)" \ + "cat > $ROOT/doc-f.md <<'EOF' +只是文件 +EOF +git checkout main" +t 2 "(77) 同一句換成分號:cat <<'EOF' … EOF; git checkout main ⇒ 照擋(剝掉的只有 body)" \ + "cat > $ROOT/doc-g.md <<'EOF' +只是文件 +EOF +; git checkout main" + clean echo echo "══ $PASS/$N 通過${FAIL:+,$FAIL 個失敗} ══" diff --git a/system-dev/wiki/mistakes.md b/system-dev/wiki/mistakes.md index 2d87cb1..6bffe61 100644 --- a/system-dev/wiki/mistakes.md +++ b/system-dev/wiki/mistakes.md @@ -376,7 +376,7 @@ leo 當場:「**這些為什麼不寫到票裡?**」 ## ⚠️ MISTAKE: 閘看的是 hook 自己的環境/cwd,不是指令要動的那個 repo -票: `inkstone/ISEP#109`(comment 6587/6629) +票: `inkstone/ISEP#109`(comment 6587/6629);`inkstone/ISEP#125` 是缺陷 A 與 D 的原票(09-02 開) 日期: 2026-09-07 症狀: 同一天兩支閘同一個病。① `line-needs-own-worktree.sh` 印的出路