Files
ISEP/hooks/self-drive-judge.sh
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

177 lines
7.7 KiB
Bash
Executable File
Raw Permalink 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
# 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