Files
ISEP/hooks/subagent-wiki-guard.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

148 lines
8.1 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.
#!/bin/bash
# subagent-wiki-guard.sh — PreToolUse(Task) hooksubagent 聽到「查」就自己先查 wiki
#
# 病根(2026-07-20):總管兩次派 agent 查 ENCRYPTION_KEYprompt 都只叫它「去查 repo 程式碼」。
# agent 於是從**稿子**推論出「這東西還活著、不能動」,總管照單全收去擋 leo 三輪。
#
# 🔑 設計轉向(leo 2026-07-21):
# 第一版是「上游沒交代讀 wiki 就擋下」——但那**還是依賴上游記得寫**,
# 跟「我記得讀 wiki」是同一個病。leo 點破:
# 「subagent 的問題跟你一樣。你叫它去查,就算你沒說要先查 wiki,
# 但它**只要聽到查,就應該主動查 wiki**,因為每個 repo 都有維護自己的 wiki。」
# → 改成 **注入式**:不擋、不要求上游改 prompt,直接把「先查 wiki」這條
# 以 additionalContext 注入給 subagent,讓它自己做。零依賴任何人記得。
#
# 行為:偵測到查證/實作類任務 → exit 0 並用 hookSpecificOutput 注入指示。
# 已含 wiki 指示、或非查證類任務 → 靜默放行(不重複注入)。
set -euo pipefail
INPUT=$(cat)
PROMPT=$(printf '%s' "$INPUT" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(d.get('tool_input',{}).get('prompt',''))
except Exception: print('')
" 2>/dev/null || echo "")
[ -z "$PROMPT" ] && exit 0
# 上游已經交代了 → 不必重複注入
if printf '%s' "$PROMPT" | grep -qiE "wiki|agent-memory|mistakes\.md|decisions-summary"; then
exit 0
fi
# 只對「查證/實作」類任務注入(純寫作、計算、潤稿等不需要)
if ! printf '%s' "$PROMPT" | grep -qiE "查|盤點|核實|確認|調查|研究|找出|repo|程式碼|原始碼|source|實作|移除|刪除|重構|修|grep|codebase|\.ts|\.go|src/"; then
exit 0
fi
# 🗺️ 真身地圖注入判斷(leo 2026-08-01:「你身為總管就是要能搞定這些跨 repo,
# 你才不會發出錯誤的命令給 subagent」)——偵測到部署/真身相關關鍵詞就把地圖注入。
NEED_MAP=0
if printf '%s' "$PROMPT" | grep -qiE "install|portal|landing|部署|deploy|worker|bundle|wrangler|真身|上線|出貨|arcrun-rag|console-ui"; then
NEED_MAP=1
fi
export NEED_MAP
python3 - <<'PY'
import json, os, pathlib
guidance = """【自動注入 0**先看你的 skill 清單有沒有現成的**】
**動手前先掃一眼 system prompt 的 available skills**——那裡的東西是「已經被驗證過的做法」,
比你自己從 repo 文件推測可靠得多。命中就 `Skill(skill="<name>")` 讀它,照它做。
判準:任務裡出現的**專有名詞**(產品名/平台名/工具名,如 Arcrun、Cloudflare、n8n…),
到 skill 清單裡找同名或近義的那支。**有就一定要先讀**,不要跳過去自己翻程式碼。
> 為什麼這條排在「查 wiki」前面(2026-07-30 實測四次考試逼出來的):
> 叫 haiku「幫我用 Arcrun 做 X」,它三次都失敗——
> ①跑去 call MCP 拿到 401 ②自己 find repo 猜著寫 YAML ③讀 repo 文件後用了
> **不存在的 `ON_TRUE``ON_FALSE` 邊**n8n 式推測)。
> 第四次只多給一句「你的 skill 清單裡有一支相關的」→ 它 `Skill(arcrun)` → **一次寫對**。
> ⇒ 差別不在能力,在**它沒想到要看清單**。而本 hook 原本只教「查 wikigrep」,
> 反而把它導向 repo 文件(`grep skill` 於本檔=0)。
---
【自動注入 1:查任何東西之前,先查 wiki】
你所在的 repo 有維護自己的 wiki(通常在 `system-dev/wiki/`,舊結構在 `.claude/wiki/`)。
**接到「查/盤點/核實/實作」類任務時,第一個動作是搜尋 wiki,不是翻程式碼。**
🔴 **查法有強弱之分,一律從最強的開始——沒有那個能力才降級。**
leo 2026-07-21:「它一定是用最好的搜尋,如果沒有才 fallback,
但那不是你要指定的,對搜尋者來說,我就是要去搜尋,如果你沒這個機制才降。」)
**① 語意搜尋(最強,優先)**——有 Arcrun RAG MCP 就用它,用**自然語言問句**,不是關鍵字:
kbdb_search(q="<用一句話描述你要找什麼>", mode="semantic")
不確定該查哪個庫 → 先 kbdb_get_map() 看藏書地圖
要沿關係展開 → kbdb_graph_neighbors()
**② 關鍵字搜尋**——語意不可用時:kbdb_search(q="...", mode="keyword")
**③ grep(最弱,最後手段)**——連 MCP 都沒有時:
grep -rin "<關鍵字>" system-dev/wiki/ 2>/dev/null || grep -rin "<關鍵字>" .claude/wiki/
🔴 **為什麼順序是硬規定(2026-07-21 實際事故)**
查「CF 上的 git 託管」時只用了 grep,搜 Gitea/freeze/D43 等字面詞 → **零命中**
結論寫成「這件事沒查過、申請表沒送」。
事後用**同一個問題**跑語意搜尋,**第一筆就命中**(score 0.858):
「Cloudflare Artifacts:假設內建 git 倉庫機制的 CF 功能,成立則可全 CF 化」,
還帶出三元組「Cloudflare Artifacts >> 若提供 git 倉庫則可取代 >> Gitea」——
**負責人 15 天前就記在筆記裡了。**
→ **grep 只認字面,要求你先猜對那個詞;語意搜尋不需要你猜對。**
用 grep 查不到 ≠ wiki 沒記載,只代表你沒猜中用詞。
🔴 **凡結論涉及「某人沒做某事」,回報前必須先用語意搜尋查該事的記載**——
這種結論錯了會變成**指控**,成本遠高於技術判斷錯誤。
為什麼這是划算的:
• wiki 是前人已經查過、驗證過、被負責人糾正過的結論——**判準**。
• 程式碼與歷史文件是**稿子**:它反映「還沒清乾淨」,不等於「還在用」。
從稿子推論會系統性得出過時結論。
• wiki 沒記載,才值得花力氣翻原文。
• **凡結論涉及「某人沒做某事」,回報前必須先 grep 該事在 wiki 的記載**——
這種結論錯了會變成指控,成本遠高於技術判斷錯誤。
三條硬規則:
1. **wiki 與程式碼衝突 → 以 wiki 為準**,並在回報中明確指出衝突,
不要自行用 code 推翻 wiki。
2. wiki 寫「不可動/待廢除/進行中」→ **讀它的解除條件並逐條核對**。
那是當時狀態,不是永久禁令;條件已滿足就是可動。
2026-07-20 實際事故:agent 只看到「不可動」就回報不能動,
實際上解除條件早已滿足,害負責人被擋三輪。)
3. 翻原文後若得到**新結論**,回報時明講「wiki 該更新」——wiki 過時是債,要還。
"""
# 🗺️ 注入 2:跨 repo 真身地圖(NEED_MAP=1 時才附)
if os.environ.get('NEED_MAP') == '1':
parts = []
for f, title in [
('system-dev/wiki/install-update-chain.md', '安裝/更新鏈(改了什麼→怎麼到用戶手上)'),
('system-dev/wiki/deployment-map.md', '網址↔Worker/Pages↔後端 對照表'),
]:
fp = pathlib.Path(f)
if fp.exists():
body = fp.read_text(encoding='utf-8', errors='ignore')
parts.append(f"### {title}\n來源:`{f}`(**這是實測釘死的真相源,勝過你從程式碼推論**\n\n{body[:9000]}")
if parts:
guidance += ("\n\n---\n\n【自動注入 2:🗺️ 跨 repo 真身地圖——**先讀這個,不要自己猜哪份是活的**】\n\n"
"🔴 **病根(leo 2026-08-01 重話)**:「哪個已經廢棄了你也不知道,總不能每次都花很多時間\n"
"做簡單的『找到程式碼在哪裡』的工作?」——同名/相似檔案有很多份,\n"
"**問題不是找不到,是找到太多份而不知道哪份還活著**。\n\n"
"**規則**:① 動手前先在下方地圖找你要改的東西 ② 地圖沒有才自己驗\n"
"③ **驗完立刻補進地圖**(帶佐證:怎麼證明它是活的)④ 地圖與程式碼衝突 → **以地圖為準**並回報衝突。\n\n"
+ "\n\n".join(parts))
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": guidance
}
}, ensure_ascii=False))
PY
exit 0