c2638668e3
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>
177 lines
9.2 KiB
Bash
Executable File
177 lines
9.2 KiB
Bash
Executable File
#!/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
|