Files
claude-code 5342917f2b roster-guard 認不出真實的 subagent_type,整個環境派不出工
Claude Code 給 plugin agent 的 subagent_type 帶前綴(isep:isep-hand),
而 agents/*.md 的 frontmatter name 沒有(isep-hand)。roster.find() 用完全相等比對
⇒ 真實派工一律被擋,而名單上的裸名字工具又不收 ⇒ 兩種寫法都派不出去。

測試沒抓到是因為 24 條全部餵手寫 payload,沒有一條用真實的 subagent_type 形狀。
補第 19 條專測這個:舊 roster.py + 新測試 → 紅;修好 → 25/25。

順手補 gate-ok 漏掉的 roster——v0.15.0 新增那支閘時沒同步逃生口清單,
所以撞上死鎖時連留痕的出口都沒有。
2026-08-28 12:36:13 +00:00

116 lines
5.8 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.
#!/usr/bin/env bash
# gate-ok — 各道閘「總管看過了,放行下一個動作」的**單一入口**inkstone/ISEP#90 ④)
#
# ══ 為什麼要有這支 ═════════════════════════════════════════════════════
#
# 這些閘每一支都留了一道給總管的門,而每一道門長得都不一樣:
# touch /tmp/.prod-write-ok
# git rev-parse --show-toplevel > /tmp/.main-push-ok
# touch /tmp/.stage-verified
# touch /tmp/.solo-ok-<session_id>
# …
# 兩個後果:
# ① **記不住**。門的形狀藏在被擋下的那則訊息裡,每支都不同,
# 而且有兩支還帶參數(綁 repo、綁 session)——照著抄錯一個字,門就打不開。
# ② **放行不了**。逃生口是「一條臨時湊出來的 Bash 指令」,
# 而臨時湊出來的指令**沒有穩定的形狀可以事先放行**:
# `.claude/settings.json` 的 allow 只能逐條寫死
# (現場真的就寫著 `Bash(touch /tmp/.prod-write-ok)` 這種完全比對的一行),
# 多一個 `&&`、換一個檔名、加一個 session id,就落在那條規則之外。
# 2026-08-28inkstone/InkStoneCo#99)總管在雲端連續四次沒能把
# prod-write 的戳記蓋出去 ⇒ **閘留了一道給總管的門,而那道門打不開**
# ⇒ 任何「擋下來、但總管可以放行」的閘,在那個環境裡等於「純擋」。
#
# ⇒ 這支把所有逃生口收斂成**一個名字、一種形狀**:
# bash "$CLAUDE_PLUGIN_ROOT/scripts/gate-ok" <閘名> [參數]
# 一條前綴規則就涵蓋全部,而且以後新增閘不必再動一次設定。
#
# 🔴 這支**不會弱化任何一道閘**:它蓋的戳記跟閘原本認的是同一個檔、同一種語意
# (單次用完即丟、綁 repo、綁 session、有效期都不變)。它換掉的只有「怎麼蓋」。
# 🔴 也**不是給 subagent 用的**:閘擋的是無心不是惡意(見 main-and-prod-push-guard
# 第 76 行)。subagent 照樣造得出戳記,但它得刻意繞過一段明講「不要這樣做」的訊息。
set -eu
usage() {
cat <<'USAGE'
用法:gate-ok <閘名> [參數]
prod-write 改到線上實例(prod-write-guard)——放行下一個動作,用完即消失
main-push [repo 路徑] 推 gitea mainmain-and-prod-push-guard)——綁 repo,預設是 cwd
stage-verified stage 真的驗過了(stage-before-prod-guard)——記得在回覆貼實測輸出
solo <session_id> 這件事真的該自己做(subagent-first-guard)——回覆裡要寫理由
dispatch <session_id> 派工單真的有例外(dispatch-format-guard
ask <session_id> 這題真的是人閘(ask-user-question-guard
roster <session_id> 派工真的是名單的例外(roster-guard
kbdb-down KBDB 真的連不上(history-first-guard)——回覆裡要說明
--list 列出目前存在的戳記
🔴 每一枚戳記都是**留痕**:它會出現在指令歷史上,而且多數是單次用完即丟。
蓋之前先確定你真的看過那個動作會改到什麼;蓋完在回覆裡寫一句為什麼。
USAGE
}
stamp() { # stamp <檔> <說明> [內容]
if [ $# -ge 3 ]; then printf '%s\n' "$3" > "$1"; else : >> "$1"; touch "$1"; fi
printf '✅ 已蓋戳記:%s\n %s\n' "$1" "$2"
}
[ $# -ge 1 ] || { usage; exit 2; }
case "$1" in
-h|--help|help) usage; exit 0 ;;
--list)
printf '目前存在的戳記:\n'
ls -la /tmp/.prod-write-ok /tmp/.main-push-ok /tmp/.stage-verified \
/tmp/.kbdb-down /tmp/.solo-ok-* /tmp/.dispatch-ok-* /tmp/.ask-ok-* 2>/dev/null \
|| printf '(一枚都沒有)\n'
exit 0 ;;
esac
GATE="$1"; shift || true
case "$GATE" in
prod-write)
stamp /tmp/.prod-write-ok "只放行**下一個**會改到線上那台的動作,用完即消失(15 分鐘失效)" ;;
main-push)
# 綁 repo:戳記內容要是那個 repo 的 toplevel,閘會拿它跟「push 的目標 repo」比對。
# 🔴 解不出 repo 時**寧可失敗也不蓋空的**——閘明文寫著空內容不算數
# (08-12 那次穿透就是空檔案變成萬用鑰匙),蓋一個註定打不開的戳記
# 只會讓人以為門開了。
D="${1:-$(pwd)}"
TOP="$(git -C "$D" rev-parse --show-toplevel 2>/dev/null || true)"
if [ -z "$TOP" ]; then
printf '❌ %s 不在任何 git repo 裡,解不出要放行哪個 repo。\n' "$D" >&2
printf ' 用法:gate-ok main-push <那個 repo 的路徑>\n' >&2
exit 2
fi
stamp /tmp/.main-push-ok "只放行推 $TOP 的 main,單次、15 分鐘失效" "$TOP" ;;
stage-verified)
stamp /tmp/.stage-verified "stage 驗過了(6 小時)——**記得在回覆裡貼 stage 的實測輸出**" ;;
kbdb-down)
stamp /tmp/.kbdb-down "KBDB 連不上而放行——**回覆裡要說明**,這是留痕不是豁免" ;;
solo|dispatch|ask|roster)
SID="${1:-${CLAUDE_SESSION_ID:-}}"
if [ -z "$SID" ]; then
printf '❌ %s 要綁 session id(閘用它認「是不是同一個 session」)。\n' "$GATE" >&2
printf ' 被擋下的那則訊息裡就有;或 gate-ok %s <session_id>\n' "$GATE" >&2
exit 2
fi
case "$GATE" in
solo) stamp "/tmp/.solo-ok-$SID" "這件事自己做——**回覆裡要寫理由**" ;;
dispatch) stamp "/tmp/.dispatch-ok-$SID" "這張派工單的例外,放行一次" ;;
ask) stamp "/tmp/.ask-ok-$SID" "這題確認是人閘,放行一次" ;;
roster) stamp "/tmp/.roster-ok-$SID" "這次派工確認是名單的例外,放行一次" ;;
esac ;;
*)
printf '❌ 不認得的閘名:%s\n\n' "$GATE" >&2
usage >&2
exit 2 ;;
esac