v0.2.1:43 支閘的白話盤點、測試手冊、補上兩個被抓到的洞
leo 2026-08-20 問「InkStoneCo#40 加入了嗎?如果是這樣我應該可以白話文看到 hooks 的內容?」
答案是不行——43 支閘沒有任何白話清單。這一版補上。
docs/hooks-inventory.md 43 支逐支一行,按「你會在什麼時候撞到它」分 9 組
抽驗 5 支逐行核對源碼;順帶抓到 3 支有檔案沒註冊
docs/TESTING.md A1-A8 + B1-B5,每格都有「怎麼跑/該看到什麼/什麼算失敗」
scripts/test-*.sh 兩支閘的測試,共 21 條,全過
兩個實撞的洞:
- release-tag-guard 的排除清單是前綴比對,x 整條放行
(A8 那個新 session 抓到的,總管複驗屬實)。改用 #23 驗證過的判準:
關鍵字要在指令位置才算執行。補 3 條複合指令測試,8/8。
⇒ 這是 InkStoneCo#36「包一層就繞過去」的同一個病,發生在同一天新寫的閘上。
- scripts/ticket 寫死只認名叫 gitea 的 remote,在 ISEP(remote 叫 origin)整個跑不起來
⇒「開票前先搜」那道閘在新 repo 等於不存在。改成掃所有指向本站的 remote + 環境變數 fallback。
A8 已通過:新 session 裡 plugin 的閘真的觸發(exit 2、tag 未建立、訊息來自 plugin 路徑)。
文件漂移訂正:plugin.json 與 README 寫 42 支/52 條,實際 43 支/53 條。
兩支新閘補上 #40 §1 要求的三行中文檔頭。
🔴 但仍違反 #40 §3「新規則一律先 warn」——兩支都是 block。理由記在 #40 留言,等 leo 裁。
This commit is contained in:
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# 打 tag 閘的測試(docs/TESTING.md A3)
|
||||
# 判準:版本對不上的 tag 要擋;只是讀 tag、或文字裡提到,都不准擋
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
H=hooks/release-tag-guard.sh
|
||||
PASS=0; FAIL=0
|
||||
run(){
|
||||
printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$2")}}" \
|
||||
| bash "$H" >/dev/null 2>&1
|
||||
got=$?
|
||||
if [ "$got" = "$1" ]; then PASS=$((PASS+1)); printf ' ✅ '; else FAIL=$((FAIL+1)); printf ' ❌ '; fi
|
||||
printf 'want=%s got=%s %.56s\n' "$1" "$got" "$2"
|
||||
}
|
||||
echo "── 該擋:版本號與 plugin.json 對不上 ──"
|
||||
run 2 'git tag -a v9.9.9 -m test'
|
||||
run 2 'echo 開始 && git tag -a v9.9.9 -m test'
|
||||
run 2 'ls && git tag -a v9.9.9 -m x'
|
||||
run 2 'cd /tmp; git tag -a v9.9.9 -m x'
|
||||
echo "── 不該擋:只是讀、只是提到 ──"
|
||||
|
||||
run 0 'git tag -l'
|
||||
run 0 'echo 等一下要 git tag -a v9.9.9'
|
||||
run 0 'git tag -a v9.9.9 -m x --dry-run'
|
||||
run 0 'grep -n "git tag" hooks/release-tag-guard.sh'
|
||||
echo
|
||||
echo "$PASS/$((PASS+FAIL)) 通過"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# 開票側門閘的測試(docs/TESTING.md A4)
|
||||
# 判準:4 種該擋、8 種不該擋、1 種有戳記時放行 = 13 條
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
H=hooks/ticket-api-bypass-guard.sh
|
||||
PASS=0; FAIL=0
|
||||
run(){ # $1=want $2=cmd
|
||||
printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$2")}}" \
|
||||
| bash "$H" >/dev/null 2>&1
|
||||
got=$?
|
||||
if [ "$got" = "$1" ]; then PASS=$((PASS+1)); printf ' ✅ '; else FAIL=$((FAIL+1)); printf ' ❌ '; fi
|
||||
printf 'want=%s got=%s %.56s\n' "$1" "$got" "$2"
|
||||
}
|
||||
SAVED=""; [ -f /tmp/.ticket-where-ok ] && SAVED=$(cat /tmp/.ticket-where-ok)
|
||||
rm -f /tmp/.ticket-where-ok
|
||||
|
||||
echo "── 該擋(沒有搜尋戳記,且真的在開新票)──"
|
||||
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues -d @b.json'
|
||||
run 2 'python3 -c "req(\"POST\", f\"{API}/repos/{REPO}/issues\", {\"title\":\"x\"})"'
|
||||
run 2 'curl --request POST "$API/repos/inkstone/InkStoneCo/issues"'
|
||||
run 2 'req("POST",f"{API}/repos/{REPO}/issues",{"title":"x","labels":[1]})'
|
||||
|
||||
echo "── 不該擋(誤攔比漏擋更該修)──"
|
||||
run 0 'curl -s "https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues?state=open"'
|
||||
run 0 'req("POST", f"{API}/repos/{REPO}/issues/14/comments", {"body":"x"})'
|
||||
run 0 'req("POST", f"{API}/repos/{REPO}/issues/5/labels", {"labels":[1]})'
|
||||
run 0 'req("PATCH", f"{API}/repos/{REPO}/issues/5", {"state":"closed"})'
|
||||
run 0 'scripts/ticket new ISEP -F /tmp/b.md --title "x"'
|
||||
run 0 'echo "等一下要開票到 /repos/x/issues"'
|
||||
run 0 'grep -n issues hooks/ticket-api-bypass-guard.sh'
|
||||
run 0 'curl -s "$API/repos/inkstone/Arcrun/issues?state=open&limit=100"'
|
||||
|
||||
echo "── 有新鮮戳記時放行 ──"
|
||||
python3 -c "import json,time;json.dump({'at':time.time(),'n':0,'top':[]},open('/tmp/.ticket-where-ok','w'))"
|
||||
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues'
|
||||
|
||||
rm -f /tmp/.ticket-where-ok
|
||||
[ -n "$SAVED" ] && printf '%s' "$SAVED" > /tmp/.ticket-where-ok
|
||||
echo
|
||||
echo "$PASS/$((PASS+FAIL)) 通過"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
+27
-7
@@ -47,15 +47,35 @@ def die(msg, code=2):
|
||||
|
||||
def token():
|
||||
root = os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
||||
# 掃這個 repo 的**所有** remote,找第一個指向本站、且帶憑證的。
|
||||
# 原本寫死只認名叫 "gitea" 的 remote —— 2026-08-20 實撞:
|
||||
# ISEP 這個新 repo 的 remote 叫 origin,於是這支腳本在那裡整個跑不起來,
|
||||
# 「開票前先搜」那道閘在新 repo 等於不存在。閘不該綁在某個 remote 的名字上。
|
||||
host = HOST.split("//")[-1].rstrip("/")
|
||||
try:
|
||||
url = subprocess.run(["git", "-C", root, "remote", "get-url", "gitea"],
|
||||
capture_output=True, text=True, timeout=20).stdout.strip()
|
||||
out = subprocess.run(["git", "-C", root, "remote", "-v"],
|
||||
capture_output=True, text=True, timeout=20).stdout
|
||||
except Exception:
|
||||
url = ""
|
||||
m = re.search(r"//[^:]+:([^@]+)@", url)
|
||||
if not m:
|
||||
die("🔴 拿不到 gitea token(該 repo 的 gitea remote 沒有帶憑證)")
|
||||
return m.group(1)
|
||||
out = ""
|
||||
for line in out.splitlines():
|
||||
if host not in line:
|
||||
continue
|
||||
m = re.search(r"//[^:/]+:([^@]+)@", line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
# 退而求其次:環境變數(雲端/CI 沒有帶憑證的 remote 時走這條)
|
||||
for env in ("GITEA_TOKEN_CLAUDE_CODE", "GITEA_TOKEN"):
|
||||
v = os.environ.get(env)
|
||||
if v:
|
||||
return v
|
||||
die(f"""🔴 拿不到 {host} 的 token
|
||||
|
||||
這個 repo 的 remote 裡沒有一個帶憑證且指向 {host}:
|
||||
{out.strip() or "(沒有任何 remote)"}
|
||||
|
||||
擇一:
|
||||
• 讓某個 remote 帶憑證(多數 repo 的 gitea/origin 本來就有)
|
||||
• 或設環境變數 GITEA_TOKEN_CLAUDE_CODE""")
|
||||
|
||||
|
||||
def api(path, payload=None, method=None):
|
||||
|
||||
Reference in New Issue
Block a user