feat(1.16.0): 讓 wiki 真的被讀到——查詢即搜尋 wiki+subagent 自動注入
病根(leo 2026-07-20 點破,真實事故):總管三次擋回 leo「某機制早已棄用」的 正確判斷,查證後 leo 全對。根因不是知識不足,是讀取流程: ① wiki 只讀開頭就開工(關鍵記載在第 56 行,答案一直在那裡) ② 派 subagent 只叫它讀 code、沒叫讀 wiki → 從稿子推論必然得出過時結論 ③ 把 wiki 的「當時狀態」當永久事實(沒核對解除條件) leo:「我需要的不是你記住,而是機制面的解法」 「如果你不是讀而是搜尋 wiki,就不會只讀 50 行就下定論,像 cmd+F 那樣高亮。」 新增: - wiki-first-search.sh(PreToolUse: Grep|Glob|Read) 查 code/文件的當下,用同一組關鍵字 grep wiki,只推命中行。 開場 push 全文解決不了「只讀開頭」——時機才是關鍵。提醒不阻擋。 - subagent-wiki-guard.sh(PreToolUse: Task) 查證/實作類任務 → 注入「先查 wiki」指示。 第一版為「上游沒交代就擋」,經 leo 指正改注入式: 「它只要聽到查,就應該主動查 wiki」——依賴上游記得寫=同一個病。 update.sh 除同步兩支 hook 外,自動註冊進 settings.json(不只提醒)—— 靠人看提醒手動補,等於把同一個病搬到安裝環節。 template/ 新裝樣板同步(新 repo 裝完即生效)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
1.15.0
|
||||
1.16.0
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# subagent-wiki-guard.sh — PreToolUse(Task) hook:subagent 聽到「查」就自己先查 wiki
|
||||
#
|
||||
# 病根(2026-07-20):總管兩次派 agent 查 ENCRYPTION_KEY,prompt 都只叫它「去查 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
|
||||
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
|
||||
guidance = """【自動注入:查任何東西之前,先查 wiki】
|
||||
|
||||
你所在的 repo 有維護自己的 wiki(通常在 `system-dev/wiki/`,舊結構在 `.claude/wiki/`)。
|
||||
**接到「查/盤點/核實/實作」類任務時,第一個動作是搜尋 wiki,不是翻程式碼。**
|
||||
|
||||
做法(30 秒,省下大量白工):
|
||||
grep -rin "<本題關鍵字>" system-dev/wiki/ 2>/dev/null || grep -rin "<關鍵字>" .claude/wiki/
|
||||
|
||||
為什麼這是划算的:
|
||||
• wiki 是前人已經查過、驗證過、被負責人糾正過的結論——**判準**。
|
||||
• 程式碼與歷史文件是**稿子**:它反映「還沒清乾淨」,不等於「還在用」。
|
||||
從稿子推論會系統性得出過時結論。
|
||||
• wiki 沒記載,才值得花力氣翻原文。
|
||||
|
||||
三條硬規則:
|
||||
1. **wiki 與程式碼衝突 → 以 wiki 為準**,並在回報中明確指出衝突,
|
||||
不要自行用 code 推翻 wiki。
|
||||
2. wiki 寫「不可動/待廢除/進行中」→ **讀它的解除條件並逐條核對**。
|
||||
那是當時狀態,不是永久禁令;條件已滿足就是可動。
|
||||
(2026-07-20 實際事故:agent 只看到「不可動」就回報不能動,
|
||||
實際上解除條件早已滿足,害負責人被擋三輪。)
|
||||
3. 翻原文後若得到**新結論**,回報時明講「wiki 該更新」——wiki 過時是債,要還。
|
||||
"""
|
||||
|
||||
print(json.dumps({
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"additionalContext": guidance
|
||||
}
|
||||
}, ensure_ascii=False))
|
||||
PY
|
||||
|
||||
exit 0
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
# wiki-first-search.sh — PreToolUse hook:要去翻原文/程式碼前,先把 wiki 命中結果推到眼前
|
||||
#
|
||||
# 病根(2026-07-20 leo 點破,mistakes 第一鐵律):
|
||||
# 總管 session 開頭讀了 agent-memory 前 50 行就開工,關鍵那條在第 56 行 → 拿過期記憶擋了 leo 三輪。
|
||||
# leo:「如果你不是讀而是**搜尋** wiki 就不會只讀 50 行就下定論,
|
||||
# 而是就像我直接在頁面 cmd+F,那些都會高亮。」
|
||||
#
|
||||
# 設計要點(為什麼是這個形狀):
|
||||
# 1. **搜尋 ≠ 通讀**:開場 push 全文(session-start-recall.sh)解決不了這題——量大必然只讀開頭。
|
||||
# 這支反過來:在「你正要去查 code/原文」的當下,用你自己的關鍵字 grep wiki,只推命中行。
|
||||
# 2. **時機是關鍵**:不是開場推、不是寫入時擋,而是**查詢動作發生的那一刻**介入。
|
||||
# 3. **提醒不阻擋**(exit 0):wiki 沒記載時本來就該去翻原文,擋下來反而礙事。
|
||||
# 唯一目的是消滅「不知道 wiki 有寫」這件事。
|
||||
#
|
||||
# 觸發:Grep / Glob / Read 打向 code 或 docs 時(見下方 should_check)。
|
||||
# 輸出:stdout 注入 context(命中的 wiki 行 + 檔名:行號)。
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(printf '%s' "$INPUT" | python3 -c "import json,sys;print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || echo "")
|
||||
|
||||
# 取出這次查詢的關鍵字:Grep 用 pattern,Glob/Read 用路徑的檔名部分
|
||||
QUERY=$(printf '%s' "$INPUT" | python3 -c "
|
||||
import json,sys,os,re
|
||||
try:
|
||||
d=json.load(sys.stdin); ti=d.get('tool_input',{})
|
||||
q = ti.get('pattern') or ''
|
||||
if not q:
|
||||
p = ti.get('file_path') or ti.get('path') or ''
|
||||
q = os.path.splitext(os.path.basename(p))[0] if p else ''
|
||||
# grep pattern 常含 regex 元字元;取最長的英數/底線詞當搜尋詞
|
||||
words = re.findall(r'[A-Za-z_][A-Za-z0-9_]{3,}', q)
|
||||
print(max(words, key=len) if words else '')
|
||||
except Exception:
|
||||
print('')
|
||||
" 2>/dev/null || echo "")
|
||||
|
||||
[ -z "$QUERY" ] && exit 0
|
||||
|
||||
WIKI_DIR="system-dev/wiki"
|
||||
[ -d "$WIKI_DIR" ] || exit 0
|
||||
|
||||
# 只在「查程式碼/文件」時提醒;查 wiki 本身就不用了(已經在讀了)
|
||||
TARGET=$(printf '%s' "$INPUT" | python3 -c "
|
||||
import json,sys
|
||||
try:
|
||||
d=json.load(sys.stdin); ti=d.get('tool_input',{})
|
||||
print(ti.get('file_path') or ti.get('path') or '')
|
||||
except Exception: print('')
|
||||
" 2>/dev/null || echo "")
|
||||
case "$TARGET" in
|
||||
*system-dev/wiki*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# grep wiki(不分大小寫、含行號),最多 12 行避免洗版
|
||||
HITS=$(grep -rin --include="*.md" -- "$QUERY" "$WIKI_DIR" 2>/dev/null | head -12 || true)
|
||||
[ -z "$HITS" ] && exit 0
|
||||
|
||||
COUNT=$(printf '%s\n' "$HITS" | wc -l | tr -d ' ')
|
||||
|
||||
echo "════════════════════════════════════════════════"
|
||||
printf '📚 wiki 已有「%s」的記載(%s 處,先看這裡再翻原文)\n' "$QUERY" "$COUNT"
|
||||
echo "════════════════════════════════════════════════"
|
||||
printf '%s\n' "$HITS" | sed 's|^system-dev/wiki/| |'
|
||||
echo ""
|
||||
echo "⚠️ wiki 是判準,程式碼與歷史文件只是稿子(mistakes 第一鐵律)。"
|
||||
echo " • 上面若與你將要查的原文衝突 → **以 wiki 為準**,別用 code 推翻 wiki。"
|
||||
echo " • 看到「不可動/待廢除/進行中」→ 先讀它的**解除條件**並逐條核對,"
|
||||
echo " 那是當時狀態不是永久禁令;條件已滿足就是可動。"
|
||||
echo " • wiki 沒答案才值得翻原文——翻完若得到新結論,**回頭更新 wiki**。"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@@ -28,7 +28,25 @@
|
||||
"command": ".claude/hooks/wiki-secret-scan.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Grep|Glob|Read",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/wiki-first-search.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Task",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-wiki-guard.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user