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
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env bash
# self-drive-judge.sh — 自走警察【第二級:語意判官】(藍圖 v3 §5.5 落地)
#
# 為什麼要它(病根):
# self-drive-police.sh 用 grep 抓請示句,**grep 只認字面**。
# leo 2026-08-04 抓到的病是「停下來問早已決議的事」,但那個病**換句話說就閃過去了**——
# 「要我 X 嗎」擋得下,「不知道這樣是否符合你的期待」「兩條路你偏好哪個」擋不下。
# ⇒ 字面判擋不住換句話說,語意判才治本(藍圖 v3 §5.5「升級 prompt 型 hook」)。
#
# 為什麼不用官方 prompt 型 hook(實查,2026-08-05):
# 官方 `type: prompt` 的 $ARGUMENTS **hook 輸入 JSON**session_id/transcript_path/…),
# Stop 事件的輸入**不含我實際說了什麼**,而 prompt hook 單輪無工具、不能自己去讀 transcript。
# ⇒ 直接換成 prompt hook 會變成「拿不到內容的判官」。
# `type: agent`(可帶 Read/Grep 查證)做得到,但官方標 experimental ⇒ 依藍圖 §9.4 成熟度門檻只登記觀察。
# ⇒ 本 hookcommand hook 抽文字 外呼 haiku 判語意。判準與成本都在我們自己手上。
#
# 判準(只有一條,就是四題公式):
# 這個問題是否真的命中人閘——① 花錢 ② 不可逆/難回收 ③ 跨專案結構 ④ 品味/方向?
# 命中任一 → 該問,放行。四題全否 → 擋回去,逼它自己裁。
#
# 成本控制(三道前置閘,正常回合完全不花錢):
# ① 沒有問號 → 不判(絕大多數回合走這條,零成本)
# ② 已被 self-drive-police 的 grep 擋下的句型 → 不重判(不重複收費)
# ③ 明說撞權限閘/四題第 X 命中 → 豁免(與 police 同一套豁免語)
#
# 失敗一律 fail-open(exit 0):判官掛掉不能變成「永遠停不下來」。
set -u
# 遞迴保險:判官自己起的那個 claude 不准再跑判官
if [ "${SELF_DRIVE_JUDGE:-}" = "1" ]; then exit 0; fi
input="$(cat)"
# stop_hook_active=已經被擋過一輪,別再疊
stop_active="$(printf '%s' "$input" | python3 -c '
import sys, json
try:
print(json.load(sys.stdin).get("stop_hook_active", False))
except Exception:
print(False)
' 2>/dev/null)"
[ "$stop_active" = "True" ] && exit 0
transcript="$(printf '%s' "$input" | python3 -c '
import sys, json
try:
print(json.load(sys.stdin).get("transcript_path", ""))
except Exception:
print("")
' 2>/dev/null)"
[ -z "$transcript" ] || [ ! -f "$transcript" ] && exit 0
# 抽「我這回合最後的 assistant 文字」(與 self-drive-police.sh 同一套抽法)
last_text="$(python3 -c '
import sys, json
path = sys.argv[1]
texts = []
try:
with open(path) as f:
lines = f.readlines()
for line in reversed(lines):
try:
ev = json.loads(line)
except Exception:
continue
msg = ev.get("message", ev)
if msg.get("role") == "assistant":
content = msg.get("content", "")
if isinstance(content, list):
for b in content:
if isinstance(b, dict) and b.get("type") == "text":
texts.append(b.get("text", ""))
elif isinstance(content, str):
texts.append(content)
break
except Exception:
pass
print("\n".join(texts))
' "$transcript" 2>/dev/null)"
[ -z "$last_text" ] && exit 0
# ── 前置閘①:沒問號就不是在問人 ──
printf '%s' "$last_text" | grep -qE '|\?' || exit 0
# ── 前置閘②:已被 grep 警察涵蓋的句型,交給它,不重複判 ──
if printf '%s' "$last_text" | grep -qiE '要我(現在|先|繼續|開始)?[^?]{0,12}嗎|需要我[^?]{0,12}嗎|下一步(要|該)?做什麼|接下來(要)?做什麼|要不要|該不該'; then
exit 0
fi
# ── 前置閘③:撞真閘的豁免語(與 self-drive-police.sh 同一套)──
if printf '%s' "$last_text" | grep -qiE '權限閘|classifier|分類器擋|denied by|四題第|需要你放行|需要 leo 放行'; then
exit 0
fi
# 只送最後 1200 字給判官(夠判語氣、不吃 context)
snippet="$(printf '%s' "$last_text" | tail -c 3600)"
judge_prompt="你是「自走警察」的語意判官。下面是一個 AI 助理(總管)準備結束回合時對老闆 leo 說的話。
判斷它是否在**把一個自己該裁的決定推回給 leo**。
判準只有一條——四題人閘公式。它問的事情是否命中下列任一:
① 花錢(付費、開資源、產生帳單、明顯多耗訂閱額度)
② 不可逆/難回收(刪資料、push 到 main、部署上線、對外公開、跨 repo 搬遷)
③ 跨專案結構決策(改全體共用框架的架構、改 repo 歸屬、立新鐵律)
④ 品味/方向(產品要不要某功能、UX 取捨——leo 的主觀偏好)
⑤ 物理人閘(只有人能做:貼憑證、終端機同意、平台上按批准)
命中任一 → decision=allow(它該問)。
四題全否,而它仍在徵詢、確認、討好、或丟空白選擇題 → decision=block。
注意:
- 單純**回報**「我做了 X,發現 Y」即使句尾有問號式修辭,也 allow。
- 提供選項但**已明說自己的建議與理由**、只等一個詞確認 → 若命中四題則 allow;四題全否仍是 block。
- 只輸出 JSON,不要任何其他文字。
輸出格式:
{\"decision\":\"allow\"或\"block\",\"reason\":\"一句正體中文,若 block 要指出它在問什麼、以及四題為何全否\"}
--- 助理說的話 ---
$snippet
--- 結束 ---"
# ⚠️ 判官必須跑在「中性目錄 + --safe-mode」:
# 實測(2026-08-05):直接在專案內跑 `claude -p` → **54 秒**,因為內層把整個專案的
# SessionStart recall(數十萬字)、18 支 hook、40 個 arcrun MCP 工具全載進去,
# 還會拿專案脈絡去回答判官的問題(實測它跑去讀 self-drive-judge.sh)。
# 換成 `cd /tmp` `--safe-mode`(關閉全部客製化)→ **8 秒**。
# 判官只需要判一段文字,不需要專案脈絡;載進來只會又慢又汙染判斷。
verdict="$(cd /tmp && SELF_DRIVE_JUDGE=1 printf '%s' "$judge_prompt" \
| (cd /tmp && SELF_DRIVE_JUDGE=1 claude -p --safe-mode --model haiku --allowedTools "" 2>/dev/null))"
# 判官掛了/回傳空/不是 JSON → fail-open
[ -z "$verdict" ] && exit 0
decision="$(printf '%s' "$verdict" | python3 -c '
import sys, json, re
raw = sys.stdin.read()
m = re.search(r"\{.*\}", raw, re.S)
if not m:
print("allow|"); raise SystemExit
try:
d = json.loads(m.group(0))
print((d.get("decision") or "allow") + "|" + (d.get("reason") or ""))
except Exception:
print("allow|")
' 2>/dev/null)"
case "$decision" in
block\|*)
reason="${decision#block|}"
cat >&2 <<EOF
🚓 自走警察【語意判官】:你在徵詢 leo,但這題**四題全否**——那就是你自己該裁的。
判官的話:$reason
【leo 2026-08-04】「不要停下來問要不要繼續,**這是前面明說要做的事**」
【藍圖 v3 §5.5】問題跟記憶走同一種階梯:先窮盡已有答案,答不出才升一級。
到 leo 是**最後一級**,不是預設一級。
送出前先在這四處找答案(找到就別問,直接做):
A. leo 這個 session 說過的話——他是不是已經給了優先序/清單/「開工」?
B. root.mdjourneys.md/派工表——下一站就是答案
C. decisions / ADR / pending-changes 已裁決區——這個做法定案過嗎?
D. KBDB 語意搜尋——換句話說的同一題(grep 猜不中用詞時的保險)
📌 真要升級到 leo,格式是:「<檔案:行> 記錄 X,我判斷走 A 因為 Y,回『好』我就繼續」
——裸問句、空白選擇題=視同沒查。
EOF
exit 2
;;
esac
exit 0