Files
ISEP/hooks/empty-handed-stop-guard.sh
T
Leo c2638668e3 ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
              「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
                你自己可以 dogfooding」

搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。

51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。

尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:41:46 +08:00

129 lines
6.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# empty-handed-stop-guard.sh — 「空手停下」封路(leo 2026-08-17 立)
#
# ── 為什麼是這支,而不是再修一次句型偵測 ──────────────────────────────
# leo 2026-08-17 的診斷(原話節錄):
# 「你的 hook 為什麼漏?因為你在**文字層**封路。抓問號、抓句型,這就是軍火競賽:
# 『回「做」我就啓動』沒有問號,下次它會寫『準備就緒』,再下次寫『待命中』。
# 自然語言的變體是無限的,blacklist 永遠追不完。
# 封路哲學之所以在 forge 架構裡有效,是因為它封的是**動作**(不能 push main、
# 不能繞過 PR),動作是有限且可枚舉的;文字不是。」
#
# 當日實測佐證:文字層的閘**八次誤攔、零次正確攔截**
# irreversible-dispatch-guard 4(三次咬的是「禁止」句本身)
# stage-before-prod-guard 2(內文出現 bundle repo 名就當成在出貨)
# browser-verify-guard 1(認不出另一套瀏覽器工具)
# factory-idle-guard 1(剝引號把自己的句子攪碎)
# ⇒ 而且方向穩定:**紅線寫得越細,命中關鍵字的機會越高 ⇒ 懲罰謹慎。**
#
# ── 這支封的是狀態,不是措辭 ────────────────────────────────────────
# 判準只有一條:**這個回合有沒有實際執行的證據(tool call)?**
# 有 → 放行,不管文字怎麼寫
# 沒有 → 擋一次,注入「不等待確認,繼續執行」
# ⇒ 句型隨它變(「回 X 我就做」「準備就緒」「待命中」都一樣),
# 但「空手停下」這條路本身物理上不通。
#
# ── 迴圈安全 ──────────────────────────────────────────────────────
# `stop_hook_active` 為真=已經擋過一次 ⇒ 放行。**至多提醒一次,不會卡死。**
#
# ── 留痕(InkStoneCo#48:36 支閘只有 2 支會記錄自己擋了什麼)───────────
# 擋下與放行都記。只記擋下的話分母未知,回答不了「這道閘有沒有在運作」。
INPUT=$(cat)
LOG="$CLAUDE_PROJECT_DIR/.claude/hooks/empty-handed-stop-guard.log"
VERDICT=$(printf '%s' "$INPUT" | python3 -c '
import json, os, sys
try:
d = json.load(sys.stdin)
except Exception:
print("SKIP"); raise SystemExit
# 已經擋過一次 ⇒ 不再擋(迴圈安全)
if d.get("stop_hook_active"):
print("SKIP:already-nudged"); raise SystemExit
tp = d.get("transcript_path") or ""
if not tp or not os.path.exists(tp):
print("SKIP:no-transcript"); raise SystemExit
rows = []
try:
with open(tp) as f:
for line in f:
line = line.strip()
if line:
try: rows.append(json.loads(line))
except Exception: pass
except Exception:
print("SKIP:unreadable"); raise SystemExit
# 這個回合=最後一則「真的來自使用者」的訊息之後(工具結果不算)
start = 0
for i, r in enumerate(rows):
if r.get("type") == "user":
c = (r.get("message") or {}).get("content")
blocks = c if isinstance(c, list) else [{"type": "text"}]
if not any(isinstance(b, dict) and b.get("type") == "tool_result" for b in blocks):
start = i
turn = rows[start:]
# 數這個回合裡「我實際做了幾個動作」
tools = 0
for r in turn:
if r.get("type") != "assistant":
continue
c = (r.get("message") or {}).get("content")
if not isinstance(c, list):
continue
for b in c:
if isinstance(b, dict) and b.get("type") == "tool_use":
tools += 1
print(("EMPTY" if tools == 0 else "OK") + ":%d" % tools)
' 2>/dev/null || echo "SKIP:crash")
TOOLS="${VERDICT##*:}"
STAMP=$(date "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "?")
case "$VERDICT" in
EMPTY:*)
printf '| %s | ⛔ 擋下 | 這個回合 %s 個動作 |\n' "$STAMP" "$TOOLS" >> "$LOG" 2>/dev/null || true
cat >&2 <<'MSG'
🤖 空手停下警察:**這個回合你一個動作都沒做。**
【leo 2026-08-17 的診斷】
「你的命令是第一把鑰匙(讓它開始想),它要再跟你要第二把(讓它開始做)。
從它的『文化』看,這是禮貌;**從你的系統看,這是違約。**」
━━ 常駐授權(這不是禁令,是授權)━━━━━━━━━━━━━━━━━━━━━━━━
🔑 **leo 的直接命令即完整授權。**
收到命令後停下來等第二次確認 = **任務失敗**,不是謹慎。
提問不是謹慎,是**把成本轉嫁給正在上課的人**。
━━ 不確定時走這條,不要停 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
① 查 wiki`system-dev/wiki/`,語意搜尋優先於 grep
② 套四題公式(花錢/不可逆/跨專案結構/品味方向)
③ 仍不確定 → **做出最合理的假設,把假設寫進 commit message 或票的留言,繼續走**
🔴 **假設之後不是問,是記錄。**
leo 下課後 review 時一次看到所有假設:對的併,錯的打回,
錯的連同「為什麼不妥」寫回 wiki ⇒ 下次同類情境查得到判例。
⇒ 這把**同步的提問**改造成**非同步的問答**——問題排隊等他,不掛起整個 loop。
━━ 真的無事可做?━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
本閘**至多提醒一次**`stop_hook_active` 之後放行),不會卡死。
若這回合確實只是回答一個不需要任何動作的問題,直接再送一次即可。
MSG
exit 2
;;
OK:*)
printf '| %s | ✅ 放行 | 這個回合 %s 個動作 |\n' "$STAMP" "$TOOLS" >> "$LOG" 2>/dev/null || true
exit 0
;;
*)
printf '| %s | ⚪ 略過 | %s |\n' "$STAMP" "$VERDICT" >> "$LOG" 2>/dev/null || true
exit 0
;;
esac