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>
This commit is contained in:
2026-08-20 11:41:46 +08:00
commit c2638668e3
85 changed files with 11879 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
#!/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