Files
ISEP/hooks/no-ticket-no-dispatch.sh
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

125 lines
5.6 KiB
Bash
Executable File
Raw Permalink 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.
#!/bin/sh
# no-ticket-no-dispatch.sh — PreToolUseAgent|Task):**無票不發包。**
#
# 🔴 為什麼是硬擋而不是提醒(leo 2026-08-16):
# 「**提醒可以忽略,deny 不能**——被 deny 的動作沒有發生,不存在「忽略」這個選項。
# 所以問題化簡為:找出『起任務』在物理上必經的瓶頸,把閘設在那裡。」
# 「因為我發現**你不會去更新票**,所以定義 tags 完全是裝飾⋯⋯
# 唯一辦法是,你不准寫 code,要 subagent 寫**一定要有票號**,它自己去拿任務,
# 回覆在票裡⋯⋯這樣你就會去更新狀態,否則永遠會『我忘了』,
# **我就無法知道你做到哪裡**。」
#
# 瓶頸就是這個工具呼叫:總管要讓 subagent 動起來,唯一的機制是它,繞不過去。
# ⇒ 「先開票」不是 SOP 第一條,是**發包動作的前置條件**,
# 跟「不插鑰匙車不會發動」同一性質。
#
# 🔴 同日實錯(本閘的來由):總管說了三次「下一步要查 X」都沒有派人;
# 真的派人時又**沒搜就開新票**arcrun-rag#110),而那條線早有 hubInkStoneCo#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