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>
125 lines
5.6 KiB
Bash
Executable File
125 lines
5.6 KiB
Bash
Executable File
#!/bin/sh
|
||
# no-ticket-no-dispatch.sh — PreToolUse(Agent|Task):**無票不發包。**
|
||
#
|
||
# 🔴 為什麼是硬擋而不是提醒(leo 2026-08-16):
|
||
# 「**提醒可以忽略,deny 不能**——被 deny 的動作沒有發生,不存在「忽略」這個選項。
|
||
# 所以問題化簡為:找出『起任務』在物理上必經的瓶頸,把閘設在那裡。」
|
||
# 「因為我發現**你不會去更新票**,所以定義 tags 完全是裝飾⋯⋯
|
||
# 唯一辦法是,你不准寫 code,要 subagent 寫**一定要有票號**,它自己去拿任務,
|
||
# 回覆在票裡⋯⋯這樣你就會去更新狀態,否則永遠會『我忘了』,
|
||
# **我就無法知道你做到哪裡**。」
|
||
#
|
||
# 瓶頸就是這個工具呼叫:總管要讓 subagent 動起來,唯一的機制是它,繞不過去。
|
||
# ⇒ 「先開票」不是 SOP 第一條,是**發包動作的前置條件**,
|
||
# 跟「不插鑰匙車不會發動」同一性質。
|
||
#
|
||
# 🔴 同日實錯(本閘的來由):總管說了三次「下一步要查 X」都沒有派人;
|
||
# 真的派人時又**沒搜就開新票**(arcrun-rag#110),而那條線早有 hub(InkStoneCo#44)。
|
||
# ⇒ 兩個病:說了不派、派了亂開票。前者這道閘擋不到,後者由 `scripts/ticket` 擋。
|
||
#
|
||
# 認什麼:派工單裡要有一行 `【工單】owner/repo#N`,可再帶 comment 定址
|
||
# (`#issuecomment-2749` 或 `→ comment 2749`)。
|
||
# leo:「你可以更細的寫票號+對話的號,因為它不一定拿整張票,
|
||
# 可能你把指令寫在某個對話裡。」
|
||
#
|
||
# 刻意的取捨(fail-closed vs fail-open 的界線畫在哪):
|
||
# · **票號缺席 → 硬擋。** 這是純本地檢查,永遠可靠,沒有藉口。
|
||
# · **票號在、但 Gitea 連不上 → 放行並警告。** 網路抖動不該讓工作停擺;
|
||
# 而且「他寫了票號」這件事本身已經是可追溯的痕跡。
|
||
# · **票號在、Gitea 說那張票不存在或已關 → 硬擋。** 那是指向空氣。
|
||
set -eu
|
||
|
||
PAYLOAD=$(cat 2>/dev/null || echo '{}')
|
||
|
||
REF=$(printf '%s' "$PAYLOAD" | python3 -c '
|
||
import sys, json, re
|
||
try:
|
||
d = json.load(sys.stdin)
|
||
except Exception:
|
||
sys.exit(0)
|
||
p = (d.get("tool_input") or {}).get("prompt") or ""
|
||
for line in p.splitlines():
|
||
if "【工單】" not in line:
|
||
continue
|
||
m = re.search(r"([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)#(\d+)", line)
|
||
if m:
|
||
print(f"{m.group(1)} {m.group(2)} {m.group(3)}")
|
||
break
|
||
' 2>/dev/null || true)
|
||
|
||
if [ -z "$REF" ]; then
|
||
cat >&2 <<'EOF'
|
||
🚫 無票不發包(leo 2026-08-16 立)
|
||
|
||
leo 原話:「**你不會去更新票,所以定義 tags 完全是裝飾**⋯⋯
|
||
要 subagent 寫一定要有票號,它自己去拿任務,回覆在票裡,
|
||
這樣你就會去更新狀態,否則永遠會『我忘了』,**我就無法知道你做到哪裡**。」
|
||
|
||
派工單裡缺一行工單號。加上這一行(放最前面):
|
||
|
||
【工單】inkstone/<repo>#<N>
|
||
【工單】inkstone/InkStoneCo#44 → comment 2749 ← 指令寫在某則對話裡時這樣寫
|
||
|
||
── 還沒有票?照這個順序,不要直接開新的 ────────────────────
|
||
1. 先搜「這件事該放哪」 scripts/ticket where <關鍵字...>
|
||
2. 命中同一條線 scripts/ticket say <owner/repo#N> -F <內文檔> ← 預設
|
||
3. 真的是新的一條線 scripts/ticket new <repo> -F <內文檔> --title <標題>
|
||
|
||
🔴 **預設是貼進既有的票,不是開新的。**
|
||
leo:「不要每個開新票,現有的票開在它下面的對話裡」
|
||
「我希望你把 gitea 變成**可以追蹤**,不是變成一個池子」
|
||
|
||
為什麼連「只是查個東西」也要票:研究任務沒有票就是逃逸艙口。
|
||
票上宣告 `deliverable 類型: research` 即可,交付物是貼在票上的結論,不是 PR。
|
||
EOF
|
||
exit 2
|
||
fi
|
||
|
||
set -- $REF
|
||
OWNER="$1"; REPO="$2"; NUM="$3"
|
||
|
||
# 拿 token(拿不到就放行——這是本地環境問題,不是派工者的錯)
|
||
URL=$(git -C "${CLAUDE_PROJECT_DIR:-.}" remote get-url gitea 2>/dev/null || true)
|
||
case "${URL:-}" in *@*) ;; *) exit 0 ;; esac
|
||
TOKEN=$(printf '%s' "$URL" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
|
||
HOST=$(printf '%s' "$URL" | sed -E 's|.*@([^/]+)/.*|\1|')
|
||
|
||
RESP=$(curl -s --max-time 15 -H "Authorization: token $TOKEN" \
|
||
"https://$HOST/api/v1/repos/$OWNER/$REPO/issues/$NUM" 2>/dev/null || true)
|
||
[ -n "$RESP" ] || exit 0 # 連不上 → 放行(票號在,已有痕跡)
|
||
|
||
STATE=$(printf '%s' "$RESP" | python3 -c '
|
||
import sys, json
|
||
try:
|
||
d = json.load(sys.stdin)
|
||
except Exception:
|
||
print("unknown"); sys.exit(0)
|
||
print(d.get("state") or ("missing" if d.get("message") else "unknown"))
|
||
' 2>/dev/null || echo unknown)
|
||
|
||
case "$STATE" in
|
||
open|unknown) exit 0 ;;
|
||
closed)
|
||
cat >&2 <<EOF
|
||
🚫 工單 $OWNER/$REPO#$NUM **已經關閉**,不能拿它當本次派工的依據。
|
||
|
||
關掉的票代表那件事已經有交付物、已經結案。拿它發包=那條線接到一個死結。
|
||
|
||
· 這是同一條線的後續 → 貼進**還開著**的那張票,或重開這張:
|
||
scripts/ticket where <關鍵字...>
|
||
· 這確實是新的一條線 → scripts/ticket new <repo> -F <檔> --title <標題>
|
||
EOF
|
||
exit 2 ;;
|
||
missing)
|
||
cat >&2 <<EOF
|
||
🚫 工單 $OWNER/$REPO#$NUM **不存在**(Gitea 查不到)。
|
||
|
||
指向空氣的票號比沒有票號更糟——它看起來像有追溯,實際上斷在第一步。
|
||
|
||
先確認 owner/repo 對不對(org 是 \`inkstone\`,不是 \`Leo\`;\`gh\` 打不到 Gitea),
|
||
或用 scripts/ticket where <關鍵字...> 找出真正該掛的那張。
|
||
EOF
|
||
exit 2 ;;
|
||
esac
|
||
exit 0
|