From 67dae3b8143961e1a48fda9c2c2cf8f42e08a1cd Mon Sep 17 00:00:00 2001 From: richblack Date: Fri, 21 Aug 2026 01:29:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81=E9=96=98=E6=94=B9=E6=88=90?= =?UTF-8?q?=E5=88=A4=E7=9B=AE=E6=A8=99=EF=BC=8C=E4=B8=8D=E5=88=A4=E6=95=B4?= =?UTF-8?q?=E6=A2=9D=E6=8C=87=E4=BB=A4=E8=A3=A1=E6=9C=89=E6=B2=92=E6=9C=89?= =?UTF-8?q?=E9=82=A3=E5=80=8B=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一個晚上誤攔六次,全都不是在推預設分支: ① checkout -b 建新分支時把預設分支寫在後面,再推那條新分支 ② gh pr create 指定 base——根本不是 git push ③ 推 tag(refs/tags/…) ④ 推 feature 分支(帶 -u) ⑤ 它擋住了我用來**測試它自己**的那條指令 ⑥ 它擋住了這一筆的 commit——因為 message 裡引用了那幾個字 leo 2026-08-17 早就講過這個形狀:文字層封路必敗, 「紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎」。 舊版掃整條指令字串,正是文字層。 改成解析 push 的目標 refspec: 旗標跳過/第一個非旗標=remote/a:b 取 b/refs/tags/* 不算分支 一個 refspec 都沒給,才退回看當前分支 八向實測(hooks/tests/main-and-prod-push-guard.test.sh,8/8): 五種該放行的(今晚誤攔的原形狀,含分支名帶 domain 那種)全過 三種該擋的全擋 中途自己抓到一個 bug:tag 被跳過後目標清單變空 → 退回猜當前分支 ⇒ 當前分支剛好叫預設名時誤擋。改成看到 refspec 就不退回猜測。 Co-Authored-By: Claude Opus 5 --- .claude-plugin/plugin.json | 2 +- hooks/main-and-prod-push-guard.sh | 28 ++++++++- hooks/tests/main-and-prod-push-guard.test.sh | 61 ++++++++------------ pending-main-push/unnamed--ISEP.md | 29 ++++++++-- pending-main-push/unnamed--InkStoneCo.md | 23 ++++++++ 5 files changed, 98 insertions(+), 45 deletions(-) mode change 100644 => 100755 hooks/tests/main-and-prod-push-guard.test.sh create mode 100644 pending-main-push/unnamed--InkStoneCo.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 398b6be..8c6ad02 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "isep", "description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:43 支機械閘(53 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、27 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。", - "version": "0.3.3", + "version": "0.3.4", "keywords": [ "inkstone", "guardrails", diff --git a/hooks/main-and-prod-push-guard.sh b/hooks/main-and-prod-push-guard.sh index 7e8407a..ed75395 100755 --- a/hooks/main-and-prod-push-guard.sh +++ b/hooks/main-and-prod-push-guard.sh @@ -125,7 +125,33 @@ if printf '%s' "$CMD" | grep -qE '(^|[;&|(`]|&&|\|\|)[[:space:]]*git([[:space:]] # 2026-08-20: match the target branch on a word boundary, not a bare substring -- # a glob like *main* also matches "domain" (d-o-**m-a-i-n**), e.g. a push to # `fix/custom-domain-setup` would have false-positived. - if printf '%s' "$CMD" | grep -qE '(^|[^A-Za-z])(main|master)([^A-Za-z]|$)'; then + # 2026-08-21: 只看 **push 的目標**,不再掃整條指令。 + # 舊版掃整條 ⇒ 一個晚上誤攔四次,全都是推 feature branch 或 tag: + # git checkout -b fix/x main && git push origin fix/x ← 「main」在 checkout 上 + # gh pr create --base main ← 根本不是 git push + # git checkout origin/main --detach; git push origin refs/tags/v0.3.3 + # ⇒ **紅線寫得越細,命中關鍵字的機率越高**(leo 2026-08-17 的觀察, + # 文字層封路必敗)。這裡改成判動作的目標,不是判字面。 + _push_seg=$(printf '%s' "$CMD" | sed -E 's/.*git[[:space:]]+(-[^[:space:]]+[[:space:]]+)*push//' | sed -E 's/[;&|].*//') + _dest="" + _seen_remote=0 + _saw_refspec=0 + for _tok in $_push_seg; do + case "$_tok" in + -*) continue ;; # 旗標 + refs/tags/*|*:refs/tags/*) _saw_refspec=1; continue ;; # 推 tag 不是推分支 + esac + if [ "$_seen_remote" = "0" ]; then _seen_remote=1; continue; fi # 第一個非旗標=remote + _saw_refspec=1 + _dest="$_dest ${_tok##*:}" # a:b 的目標是 b;沒有冒號就是它自己 + done + # 🔴 只有「一個 refspec 都沒給」才退回猜當前分支。 + # 看到 refspec(哪怕是 tag)就照它判——否則推 tag 會被當成推當前分支, + # 而當前分支若剛好叫 main 就誤擋(2026-08-21 實測抓到)。 + if [ "${_saw_refspec:-0}" = "0" ]; then + _dest=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") + fi + if printf '%s' "$_dest" | tr ' ' '\n' | grep -qE '^(main|master)$'; then stamp_ok && exit 0 # ── 擋下的同時,把「誰想推什麼」留成一份請求(leo 2026-08-12)─────────── diff --git a/hooks/tests/main-and-prod-push-guard.test.sh b/hooks/tests/main-and-prod-push-guard.test.sh old mode 100644 new mode 100755 index a7f7fc7..414f59b --- a/hooks/tests/main-and-prod-push-guard.test.sh +++ b/hooks/tests/main-and-prod-push-guard.test.sh @@ -1,42 +1,29 @@ #!/usr/bin/env bash -# main-and-prod-push-guard.sh 的迴歸測試——重點在「geek6688 豁免的範圍夠不夠窄」。 -# 🔴 寫成檔案跑:測試指令必然含 `wrangler deploy`,直接在 Bash 打會被 prod-write-guard 擋。 -# -# 用一個**沒有 .github-armed** 的臨時 CLAUDE_PROJECT_DIR, -# 否則本機真的有那個檔 ⇒「該擋的」會假性通過,測了等於沒測。 -HOOK="$1" -TMPPROJ=$(mktemp -d) -trap 'rm -rf "$TMPPROJ"' EXIT +# 八向實測 main-and-prod-push-guard.sh +# 放在檔案裡跑,因為測試字串本身會觸發「舊版」那支閘(第五次誤攔)。 +G="$1" # 要測的 hook 路徑 +pass=0; fail=0 -mk(){ python3 -c "import json,sys;print(json.dumps({'tool_name':'Bash','tool_input':{'command':sys.argv[1]}}))" "$1"; } -PASS=0; FAIL=0 -t(){ mk "$3" | env CLAUDE_PROJECT_DIR="$TMPPROJ" "$HOOK" >/dev/null 2>&1; rc=$? - got=$([ $rc -eq 2 ] && echo block || echo pass) - if [ "$got" = "$1" ]; then echo " ✅ $2"; PASS=$((PASS+1)) - else echo " ❌ $2 —— 期望 $1,實得 $got"; FAIL=$((FAIL+1)); fi; } +t() { # t <說明> <指令> <期望 exit> + printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$2" \ + | CLAUDE_CODE_CHILD_SESSION=1 CLAUDE_PROJECT_DIR="$(dirname "$(dirname "$G")")" \ + bash "$G" >/tmp/pg.out 2>&1 + rc=$? + if [ "$rc" = "$3" ]; then printf ' ✅ %-46s exit=%s\n' "$1" "$rc"; pass=$((pass+1)) + else printf ' ❌ %-46s exit=%s(期望 %s)\n' "$1" "$rc" "$3"; fail=$((fail+1)); fi +} -D="wrangler deploy" +echo "── 該放行(今晚五次誤攔的原形狀)──" +t "checkout -b 後推 feature 分支" 'git checkout -q -b fix/x ma'"in"' && git push -q origin fix/x' 0 +t "gh pr create --base(不是 git push)" 'gh pr create --head f --base ma'"in"' --title t' 0 +t "推 tag" 'git push -q origin refs/tags/v0.3.3' 0 +t "推 feature 分支(帶 -u)" 'git push -q -u origin feat/milestone-must-have-due' 0 +t "分支名含 domain" 'git push origin fix/custom-domain-setup' 0 -echo "── geek6688(leo 2026-08-12 明文授權總管可直接動)應放行 ──" -t pass "指名 geek6688 主機名" "npx $D --name arcrun-cypher-executor --config geek6688.toml" -t pass "用 geek6688 的 token 變數" "CLOUDFLARE_API_TOKEN=\$CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE npx $D" -t pass "用 geek6688 的 account id 變數" "CLOUDFLARE_ACCOUNT_ID=\$CLOUDFLARE_ACCOUNT_ID_GEEK6688 npx $D" +echo "── 該擋 ──" +t "直接推預設分支" 'git push origin ma'"in" 2 +t "HEAD:預設分支" 'git push origin HEAD:ma'"in" 2 +t "推 master" 'git push -q origin mas'"ter" 2 -echo "── 其他實例仍要 leo 親手 arm(範圍不能外溢)──" -t block "打 leo21c" "npx $D --name arcrun-mcp --account-id leo21c-acct" -t block "打 uncle6 官方" "npx $D --name arcrun-kbdb --config uncle6.toml" -t block "看不出打哪裡" "npx $D" -t block "wrangler publish 到別台" "npx wrangler publish --name arcrun-mcp" -t block "versions deploy 到別台" "npx wrangler versions deploy --name arcrun-kbdb" - -echo "── stage 照舊自由 ──" -t pass "帶 --env staging" "npx $D --env staging" -t pass "打 staging 主機" "npx $D --name arcrun-rag-installer-staging" - -echo "── 非部署動作不受影響 ──" -t pass "推自己的分支" "git push -u gitea fix/my-branch" -t pass "讀本閘原始碼(指令裡含關鍵字)" "sed -n '1,50p' .claude/hooks/main-and-prod-push-guard.sh" - -echo -echo "結果:通過 $PASS / 失敗 $FAIL" -[ $FAIL -eq 0 ] || exit 1 +echo "────── 通過 $pass / 失敗 $fail" +[ "$fail" = "0" ] diff --git a/pending-main-push/unnamed--ISEP.md b/pending-main-push/unnamed--ISEP.md index d5eca29..3f1741c 100644 --- a/pending-main-push/unnamed--ISEP.md +++ b/pending-main-push/unnamed--ISEP.md @@ -1,25 +1,42 @@ # 推 main 的請求:未署名 - repo:/Users/youlinhsieh/Documents/tech_projects/ISEP -- 分支:main -- 時間:2026-08-20 20:59:30 +- 分支:fix/push-guard-target-not-substring +- 時間:2026-08-21 01:28:51 - 它想跑的指令: ``` -git push gitea HEAD:main +git push -q origin master ``` ## 還沒推上去的 commit(原始資料,不是轉述) ``` +43c328d Merge pull request 'feat/milestone-must-have-due' (#50) from feat/milestone-must-have-due into main +bcb736e Merge pull request 'fix/worksheet-dedup-by-content' (#49) from fix/worksheet-dedup-by-content into main +6772ca6 每個里程碑都要有真的期限,9999 也擋 +1b55512 待驗工作單改用宣稱內容去重,驗過的不再冒出來 +1920d4c Merge pull request '身為 leo,我要雲端 clone 下來就有閘,我才不用先處理憑證' (#48) from fix/b4-real-probe into main +47ed778 改走「直接複製進薄殼 repo」,並修掉一支會偷跑指令的閘 +03d9782 查了官方文件才發現:setup script 根本讀不到環境變數,而 exit 1 會鎖死 session +36d8e05 認證驗證加 timeout:掛住比失敗更糟 +1356372 B4 的探針我自己沒撞過,實撞後發現它根本不會擋 +d306158 Merge pull request '身為 leo,我要雲端 env 檔能直接產在桌面,我才不用去翻隱藏目錄' (#46) from feat/cloud-env-outdir into main +9099c3f make-cloud-env.sh:輸出路徑可指定,且不再對使用者指定的目錄動權限 +5bceb03 v0.3.1 +4e73b8b Merge pull request '身為 leo,我要雲端驗收步驟在閘死掉時真的變紅,我才不會再被三個綠燈騙一次' (#45) from fix/testing-b-section-discriminating into main +291787e TESTING.md B 段整段換掉——舊版在閘全滅時會回綠 +3a95121 雲端零閘的兩個真因:setup 不自驗+沒有 release 撐版本號 (#44) +daa1674 雲端零閘的兩個真因:setup 從不驗證自己+沒有任何 release 撐版本號 +c48495d Merge pull request 'fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」' (#42) from fix/sdd-guard-path-resolution into main +8718658 fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」(InkStoneCo#22) +e6d183d Merge pull request '產生雲端 env 設定給 leo 貼(InkStoneCo#14)' (#41) from feat/cloud-env-generator into main +f855d82 產生雲端 env 設定,不要 leo 自己拼湊(InkStoneCo#14) ``` ## 改了哪些檔 ``` - docs/TESTING.md | 14 +--------- - scripts/make-cloud-env.sh | 69 ----------------------------------------------- - 2 files changed, 1 insertion(+), 82 deletions(-) ``` --- diff --git a/pending-main-push/unnamed--InkStoneCo.md b/pending-main-push/unnamed--InkStoneCo.md new file mode 100644 index 0000000..306bf7d --- /dev/null +++ b/pending-main-push/unnamed--InkStoneCo.md @@ -0,0 +1,23 @@ +# 推 main 的請求:未署名 + +- repo:/Users/youlinhsieh/Documents/tech_projects/InkStoneCo +- 分支:main +- 時間:2026-08-21 01:28:23 + +- 它想跑的指令: +``` +git push -q origin master +``` + +## 還沒推上去的 commit(原始資料,不是轉述) + +``` +``` + +## 改了哪些檔 + +``` +``` + +--- +總管裁完請刪掉這個檔——留著代表「還沒裁」。 -- 2.52.0