Files
ISEP/hooks/wiki-first-police.sh
T
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
9.2 KiB
Bash
Executable File
Raw 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
# wiki-first-police.sh — 記憶警察(Stop / SubagentStop hook
#
# 病根(leo 2026-07-26 兩句原話):
# ①「你應該隨時記錄進 wiki,然後用記憶回答,而不是每次都去實測,除非 wiki 沒做好記錄找不到」
# ②「你要把不憑記憶回答改成檢查 wiki……不依賴你自己的記憶,那個過 session 就死了」
# ③「我看 CP 也沒用,因為你執行時沒去更新」
#
# 兩個病,同一根:**做完不落帳** → 下次(或下個 session)只好重新實測/憑會死的記憶亂答。
# - 病 A:驗完就往下做,CP/wiki 事後才補 → leo 看 CP 永遠落後現況=「看了也沒用」。
# - 病 B:被問狀態時憑對話記憶或重跑實測,而不是查 wiki → 過 session 就失憶。
#
# 為什麼要 hook 而不是寫規則:CLAUDE.md 早就寫了「tasks.md 是唯一進度來源,不靠對話記憶」,
# 照樣違反——自評的人跟做事的人是同一個。同 delivery-police:在「要收工」那刻攔截。
#
# 機制:這回合有「實測/修好/部署」這類狀態變更 → 檢查有沒有動過 wiki/CP/tasks → 沒動就 exit 2。
# 豁免:純查詢/純對話回合、已動過帳、撞權限閘。
input="$(cat)"
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
# 取本回合(最後一次 user 訊息之後)的所有 assistant 文字 + 工具輸入
turn="$(python3 -c '
import sys, json
path = sys.argv[1]
out = []
try:
with open(path) as f: lines = f.readlines()
# 從尾巴往回走到最近一則 user 訊息為止
buf = []
for line in reversed(lines):
try: ev = json.loads(line)
except Exception: continue
msg = ev.get("message", ev)
role = msg.get("role")
if role == "user":
break
if role == "assistant":
content = msg.get("content", "")
if isinstance(content, list):
for b in content:
if not isinstance(b, dict): continue
if b.get("type") == "text":
buf.append(b.get("text", ""))
elif b.get("type") == "tool_use":
buf.append(json.dumps(b.get("input", {}), ensure_ascii=False))
elif isinstance(content, str):
buf.append(content)
out = list(reversed(buf))
except Exception: pass
print("\n".join(out))
' "$transcript" 2>/dev/null)"
[ -z "$turn" ] && exit 0
# ── 豁免一:撞閘/明說停等 → 放行 ──
if printf '%s' "$turn" | grep -qiE '權限閘|classifier|分類器擋|四題第|人閘|denied by'; then
exit 0
fi
# ── 這回合有沒有「狀態變更」?(值得落帳的事)──
# 部署、修好、實測通過、端到端驗證、立案——這些都是下次會被問到的狀態。
if ! printf '%s' "$turn" | grep -qiE '部署|deploy|已上線|修好|修掉|真兇|實測|端到端|驗證通過|全綠|通了|立案'; then
exit 0
fi
# ── 豁免〇(2026-08-08 新增):**這回合根本沒有動過任何 repo → 純討論,不必落帳** ──
#
# 🔴 為什麼補這道(leo 08-08 問「為什麼花了一整天」時查出來的):
# 上面那個「有沒有狀態變更」的判斷,比對的是**我回覆裡的散文**。
# 於是我只要在回答中**談到**「修好/實測/全綠/部署」——例如向 leo 解釋
# 今天做了什麼、或提出改善流程的建議——就會被判成「有狀態變更卻沒落帳」。
# ⇒ **把「談論」當成「執行」**。這是同一天第三個同款 bug
# (前兩個在我自己寫的 stage-before-prod-guard:讀腳本、commit 訊息提到關鍵字都被誤攔)。
# ⇒ 代價很實在:leo 每插話一次,我就被判一次「沒落帳」,於是又寫一段落帳、
# commit、push。今天 65 個 commit 裡 41 個是落帳,這是其中一個來源。
#
# 判準改成看**事實**:這 30 分鐘內有沒有任何 repo 真的產生 commit。
# 沒有 ⇒ 這回合是討論/回答,沒有東西需要被記住,放行。
ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
recent_commits=0
for r in "$ROOT" "$ROOT/products/arcrun-rag" "$ROOT/matrix/arcrun"; do
[ -d "$r/.git" ] || continue
n=$(git -C "$r" log --since="30 minutes ago" --oneline 2>/dev/null | wc -l | tr -d ' ')
recent_commits=$((recent_commits + n))
done
[ "$recent_commits" -eq 0 ] && exit 0
# ── 豁免二:這回合確實動過帳(wiki / CRITICAL-PATH / tasks.md / memory)──
#
# 🔴 同上,這裡原本也是比對散文——**只要回覆裡「提到」wiki 路徑就算落帳**,
# 等於「說了就算做了」。改成先看 git:這 30 分鐘的 commit 有沒有真的動到帳本檔。
doc_commits=0
for r in "$ROOT" "$ROOT/products/arcrun-rag" "$ROOT/matrix/arcrun"; do
[ -d "$r/.git" ] || continue
n=$(git -C "$r" log --since="30 minutes ago" --name-only --pretty=format: 2>/dev/null \
| grep -cE 'system-dev/wiki/|CRITICAL-PATH\.md|critical-paths/|tasks\.md|memory/.*\.md' || true)
doc_commits=$((doc_commits + n))
done
if [ "$doc_commits" -gt 0 ]; then
# 🔗 落帳了,但有沒有標「這件事牽涉哪些 repo」?
# leo 2026-08-01:「你需要在每次記錄你自己的 wiki 時建立連到發生此事件的 hyperlink 或三元組,
# 每件事牽涉到 2 個 repo,就可以在該事件查到那兩個 repo,細節記錄在該 repo 裡。」
# 病根:08-01 實測 status.md 今日 34 段落帳中,只有 2 段標題提到 repo、
# 0 筆有結構化 repo 欄位、僅 2 行三元組 ⇒ 下次仍要重查「這件事在哪」。
# 只在「跨 repo 事件」時要求(單 repo 的事不必標)。
if printf '%s' "$turn" | grep -qiE 'arcrun-rag|matrix/arcrun|console-ui|installer|portal|bundle|kbdb|cypher|跨 ?repo'; then
# 🔴 2026-08-08 第二刀:這一段原本也在比對**我的回覆散文**——
# 於是 wiki 條目裡明明寫了 📍 repo 與三元組,只因為**回覆裡沒提**就被判「沒標 repo」。
# (早上已把上層觸發條件改看 git,這裡是同一個病漏掉的另一半。)
# ⇒ 改成看**剛剛真的寫進帳本的內容**:近 30 分鐘 commit 新增的行有沒有 repo 指針。
doc_added=""
for r in "$ROOT" "$ROOT/products/arcrun-rag" "$ROOT/matrix/arcrun"; do
[ -d "$r/.git" ] || continue
doc_added="$doc_added$(git -C "$r" log --since='30 minutes ago' -p --unified=0 \
-- 'system-dev/wiki/*' 'system-dev/docs/3-specs/*' 2>/dev/null \
| grep '^+' || true)"
done
# ⚠️ 樣式別寫死 '📍 repo'——實際格式是 `📍 **repo**:`(粗體),
# 寫死會永遠對不上(08-08 實撞:條目明明有指針卻一直被判沒有)。
if ! printf '%s' "$doc_added$turn" | grep -qE '牽涉 repo|📍|真身在|真兇|位於|\[\[.*\]\]'; then
cat >&2 << 'EOF2'
🔗 記憶警察(連結層):你落帳了,但**沒標這件事牽涉哪些 repo**。
【leo 2026-08-01】「你需要在每次記錄你自己的 wiki 時建立連到發生此事件的 hyperlink 或三元組,
**每件事牽涉到 2 個 repo,就可以在該事件查到那兩個 repo**,細節記錄在該 repo 裡。」
【為什麼】08-01 實測:status.md 當日 34 段落帳,只有 2 段標題提到 repo、
**0 筆有結構化 repo 欄位** ⇒ 下次查「這件事在哪」還是要從頭找,
這就是同一天猜錯真身 3 次的根源。
補一行就好(擇一格式,放在該段落帳裡):
📍 repo`products/arcrun-rag`landing/worker.js)+`matrix/arcrun`console-ui/public/portal/
或三元組:`portal 視覺 >> 真身在 >> matrix/arcrun:console-ui/public/portal/index.html`
原則:**頂層 wiki 記「事件+牽涉哪些 repo」,細節記在該 repo 自己的 wiki**。
EOF2
exit 2
fi
fi
exit 0
fi
cat >&2 << 'EOF'
🚓 記憶警察:這回合有狀態變更(實測/修復/部署),但我沒看到你落帳。
【病根】leo 2026-07-26
「我看 CP 也沒用,因為你執行時沒去更新。」
「不依賴你自己的記憶——那個過 session 就死了。」
→ 你現在腦裡有的東西,下個 session 一個字都不剩。沒寫進檔案=沒發生過。
收工前補完這兩件(擇需要的做,一句話也算):
1. **狀態變更落帳**
- CP 關卡狀態變了 → 改 `system-dev/docs/3-specs/CRITICAL-PATH.md`(✅/◐/❌+實測輸出)
- 任務進度/新發現 → 改對應 `tasks.md`(含真兇、殘項、下一步)
- 跨 session 要記得的操作知識 → `system-dev/wiki/`status/mistakes/agent-memory
2. **下次別再重測**:把「這件事現在是什麼狀態、證據在哪」寫成一句話,
讓下個 session(或被 leo 問時)**查得到就不用重跑**。
⚠️ 落帳=寫檔案,不是寫在回覆裡。回覆會消失,檔案不會。
EOF
exit 2