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>
136 lines
7.2 KiB
Bash
Executable File
136 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# delivery-police.sh — 交付警察(Stop / SubagentStop hook)
|
||
#
|
||
# 病根(leo 2026-07-22 原話):「它是我的助理,如果都是我檢查,那我就是它的助理。」
|
||
# 一天內三次都是 leo 先發現:①雲端沒查文件就問 ②OAuth 做好但前端還是舊按鈕、網址沒設 DNS
|
||
# ③連補規則也是 leo 講了才補。→ 角色顛倒=根本失敗。
|
||
#
|
||
# 為什麼清單沒用:CRITICAL-PATH 使用規則 4/5(v2 覆蓋前為 6/7)寫了「要驗前端」,CLAUDE.md 規則二寫了四題公式,
|
||
# 照樣被違反——因為**自評的人跟做事的人是同一個**,任何品管制度都不成立。
|
||
# 解法同 self-drive-police:在「要標完成/要收工」那一刻攔截 + 反問。
|
||
#
|
||
# 機制:Claude 想停 → 掃這回合輸出有沒有「完成宣稱」→ 有、但沒看到實測證據 → exit 2 反問。
|
||
# 豁免:輸出已含實測證據(curl/dig 實際輸出、HTTP 碼、grep 到畫面字串、檔案:行號)→ 放行。
|
||
# 撞權限閘/明說四題命中 → 放行(不逼它做沒權限的事)。
|
||
#
|
||
# 判準(leo 2026-07-22):「友善=前端。人去按了連到錯的機制、安裝失敗,不算友善。」
|
||
# → 有前端的東西,驗收只認用戶真的會走的那條路。
|
||
|
||
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
|
||
|
||
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
|
||
|
||
# ── 豁免一:撞權限閘/明說四題命中 → 真的停不下來,放行 ──
|
||
if printf '%s' "$last_text" | grep -qiE '權限閘|classifier|分類器擋|四題第|人閘|需要你放行|denied by'; then
|
||
exit 0
|
||
fi
|
||
|
||
# ── 是否有「完成宣稱」? ──
|
||
if ! printf '%s' "$last_text" | grep -qiE '✅|完成了|已完成|通了|部署成功|已上線|已部署|搞定|做完了|全綠|收工'; then
|
||
exit 0
|
||
fi
|
||
|
||
# ── 豁免二:已有「夠格的」實測證據 → 放行 ──
|
||
#
|
||
# ⚠️ 本段自身踩過的雷(2026-07-22 實測 t2 抓到):
|
||
# 舊版寫 `HTTP [0-9]{3}` 就放行 → 「curl 回 HTTP 200,這關通了」直接過關。
|
||
# 那正是要防的假綠原句(landing 回 200 但按鈕 href 還是舊的)。
|
||
# **把要防的東西寫成通行證**=規則 6「HTTP 200 不算驗過」被自己的 hook 架空。
|
||
#
|
||
# 現在的判準:2xx/3xx 一律不算證據;只有下列才算——
|
||
# ① 抓到畫面內容(curl|grep / href= / <title>)② 壞消息碼(4xx/5xx,誠實回報不罰)
|
||
# ③ DNS 實查 ④ 測試數字 ⑤ 檔案:行號 ⑥ 明確標 ❌/◐(沒宣稱通就不必擋)
|
||
if printf '%s' "$last_text" | grep -qiE 'curl -s[^|]*\| *grep|href=|<title>|HTTP (4[0-9]{2}|5[0-9]{2})|dig \+short|[0-9]+/[0-9]+ (通過|綠|passed)|\.md:[0-9]+|❌ 斷|◐ 半通'; then
|
||
exit 0
|
||
fi
|
||
|
||
cat >&2 << 'EOF'
|
||
🚓 交付警察:你宣稱完成,但我沒看到「實測證據」。標 ✅ 之前先答:
|
||
|
||
【最高規範】leo 2026-08-17(他當場更正自己前一句的範圍):
|
||
「我剛剛講錯一件事,我說你推給我的版本要測過,我錯了——
|
||
**不是版本要測過,是任何東西你交的貨都要測過,一個 PR 也要測過,
|
||
你不允許交出你不知道能不能用的東西。**
|
||
**這不是對版本的規範,這是對你所有東西的規範。**」
|
||
|
||
⇒ 「交貨」=**任何交到別人手上的東西**:版本/PR 分支/票上的結論/
|
||
派工單裡寫的「現況」/回報的每一句。
|
||
⇒ **判準只有一句:交出去之前,我知不知道它能不能用?**
|
||
不知道 → 它還沒到可以交的狀態,不是「交了再說」。
|
||
⚠️ **「我沒有說它能用」不構成豁免**——接收方還是得自己驗,成本一樣轉嫁出去了。
|
||
|
||
🔴 **`report` 與 `deliver` 是兩個字**(leo 2026-08-17 當場糾正總管的後門寫法):
|
||
「你可以說你有進度,**這是 report**;**但驗完那一格才能交,這是 deliver**。」
|
||
report 有進度、有發現、有卡點 ← 可以有沒驗的部分,標明即可
|
||
deliver 交到別人手上的東西 ← 每一格都驗過
|
||
⇒ **「有一格沒驗」不是交付的例外,它是「還沒到交付」的定義。**
|
||
那個狀態要說「我報告一下進度」,不是「我交給你但有一格沒驗」。
|
||
|
||
【判準】leo 2026-07-22:「它是我的助理,如果都是我檢查,那我就是它的助理。」
|
||
→ leo 如果現在去測,會不會發現你沒發現的東西?會 → 現在就去測,別交。
|
||
【判準】leo 2026-07-22:「友善=前端。人去按了連到錯的機制、安裝失敗,不算友善。」
|
||
→ 驗收只認**用戶真的會走的那條路**,不是你做的那個零件。
|
||
|
||
1. 有前端嗎?貼「抓實際畫面內容」的輸出:
|
||
curl -s <public 網址> | grep <該出現的字串>
|
||
⚠️ HTTP 200 不算驗過——200 只證明伺服器活著,不證明用戶看到對的東西。
|
||
(真實踩雷:landing 回 200,但按鈕 href 還是舊的 deploy.workers.cloudflare.com)
|
||
|
||
2. 對外網址四件驗了嗎?
|
||
① custom domain 綁對專案?② DNS 解析得到?③ 前端 apiBase 對?④ CORS 放行?
|
||
(真實踩雷:install.arcrun.dev 有 DNS 但 HTTP 522、畫面全空=網域建了沒綁 worker)
|
||
|
||
3. 網址還是 *.workers.dev / *.pages.dev?
|
||
→ 那叫「僅預設網域」,用戶拿不到,不准標 ✅,最多 ◐ 半通。
|
||
🔵 **例外:stage/測試環境不受此條約束**(leo 2026-08-07:
|
||
「Stage 直接用 workers.dev 不行?**反正只有你我用**」)。
|
||
本條的目的是「用戶拿不到=沒交付」——stage 的用戶就是 leo 與總管,
|
||
*.workers.dev 我們拿得到 ⇒ 前提不成立。
|
||
而且 stage 不掛 custom domain 反而是優點(不會有人誤入/被搜尋引擎索引/
|
||
把 stage 網址當正式的傳出去)。
|
||
⇒ **stage 的驗收標準=「leo 與總管能不能在上面測到真東西」,不是網域形式。**
|
||
|
||
4. 你自己找到並修掉了哪些問題?一項都沒有=多半沒認真找。
|
||
|
||
答不出來就是還沒完成——現在去測。規格見 CRITICAL-PATH.md 使用規則 4、5。
|
||
EOF
|
||
exit 2
|