Files
ISEP/hooks/isep-presence-beacon.sh
T
claude-code adb7009b67 信標多報三件雲端會壞掉的事:版本落差/舊複本遮蔽正門/退役機制的殘骸(inkstone/ISEP#90 ②③)
信標原本只證明「有一份 plugin 載入了」,不證明「載入的是哪一份」——
2026-08-27 雲端載的是 0.3.9、main 是 0.9.0,中間差 7 個 release,而它照樣是綠的。
那個看不見的落差的實際後果:0.3.9 裡還活著兩支 v0.9.0 已整支刪掉的 hook,
於是 .claude/pending-verification/ 被清掉之後又長回來。

三格(都只報告、不擋、拿不到答案就閉嘴):
· 版本落差:匿名讀 ISEP main 的 plugin.json(D20 判準下屬於「讀」),快取 6 小時、
  逾時 6 秒。落後就講清楚差幾版與怎麼修,並指向 inkstone/ISEP#67。
· 舊複本遮蔽正門:plugin 的 scripts/* 在專案樹(含雲端的 $TOP/InkStoneCo/)裡
  有同名但**內容不同**的複本就點名。實例=InkStoneCo/scripts/ticket 的取 token
  邏輯還停在「只認名叫 gitea 的 remote」,而雲端的 Gitea 叫 origin
  ⇒ 一律死在「拿不到 gitea token」⇒ 人被逼去繞過正門直接打 API,
  而那正是 ticket-api-bypass-guard.sh 在防的事。
· 退役機制的殘骸:.claude/ 底下的目錄,若 plugin 的 hooks/scripts 一個字都沒提到
  ⇒ 產生它的東西已經不在這一份裡了。判準是「plugin 現在還認不認得它」,
  **不是關鍵字黑名單**(leo 2026-08-17 已證明那條路 8 次誤攔、0 次正確攔截)。

訊息改由 hooks/lib/beacon_report.py 序列化:三段報告含引號與換行,
用 shell 內插拼 JSON 一個引號就會讓整行信標消失——而那正是「零閘狀態」的長相。
fail-open:python 掛掉、檔案不見、網路不通,一律退回原本那一行乾淨的信標。

測試 hooks/tests/isep-presence-beacon.test.sh:14/14(全離線)。
第 ⑪ 條守的是寫這支時真的犯過的 bug(內層迴圈變數跟外層的 d 撞名,
第二個殘骸之後全部被靜靜跳過)——把那個 bug 放回去,該條立刻轉紅。
2026-08-28 00:05:15 +00:00

70 lines
4.1 KiB
Bash
Executable File
Raw 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
# isep-presence-beacon.sh — SessionStart:報出「ISEP 真的載入了,幾版、幾支閘」
#
# 這不是閘,是**信標**。存在的理由是 2026-08-20 的雲端事故:
# 雲端 session 的閘全滅,而三個驗證步驟全部回綠——因為它們沒有鑑別力
# (`git tag` 在三支閘的白名單裡;`Skills(9)/Hooks(5)` 剛好是薄殼自己的 .claude/ 產生的數字)。
#
# 🔴 鑑別力就是這支的全部意義:
# 這行出現 ⇒ plugin 一定載入了(因為它自己就住在 plugin 裡)
# 這行不見 ⇒ plugin 沒載入,那個 session 是零閘狀態
# ——沒有第三種情況,也沒有「剛好也會過」的巧合。
set -uo pipefail
ROOT="${CLAUDE_PLUGIN_ROOT:-}"
[ -n "$ROOT" ] || exit 0
VER="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
"$ROOT/.claude-plugin/plugin.json" 2>/dev/null | head -1)"
VER="${VER:-未知}"
# 🔴 數「真的被註冊的」,不是數目錄裡有幾個 .sh(leo 的雲端驗收 2026-08-23 抓到):
# 舊寫法 `ls hooks/*.sh` 把 `pre-write-guard.template.sh`(樣板,不是閘)
# 與兩支沒掛註冊的輔助檔一起算進去 ⇒ 報 45,實際註冊 42。
# 這個數字是 leo 判斷「這個 session 有沒有閘」的唯一介面——**多報就是假綠**。
# (查過歷史:本檔自 daa1674 建立以來只有那一版,沒有別的分支修過這段。)
GATES="$(grep -oE 'hooks/[a-zA-Z0-9._-]+\.sh' "$ROOT/hooks/hooks.json" 2>/dev/null \
| sort -u | wc -l | tr -d ' ')"
case "$GATES" in
''|*[!0-9]*|0)
GATES="$(ls "$ROOT"/hooks/*.sh 2>/dev/null | grep -cv '\.template\.sh$' | tr -d ' ')" ;;
esac
# 🔴 講清楚「這一份是誰」(leo 2026-08-23 的雲端驗收卡在這裡整整一輪):
# 同一台機器上 ISEP 可能有兩份——marketplace 裝的 plugin 快取,
# 與 repo 裡 vendor 進去的複製本。**兩份都會印這一行**,而版本號一樣時分不出誰在說話。
# 「閘到底從哪一份走」正是決定「另一份能不能拆」的唯一判準
# ⇒ 讓路徑自己講:快取在 plugins/cache/ 底下,vendor 的在 repo 的 .claude/ 底下。
# (查過歷史:本檔只有 daa1674 建立、b1f399f 改閘數兩輪,沒有人試過標示來源,也沒有被否決的方案。
# KBDB 語意搜尋這段開發史:0 命中,該餵。)
case "$ROOT" in
*/plugins/cache/*) SRC="pluginmarketplace 裝的)" ;;
*/.claude/isep*) SRC="vendorrepo 裡的複製本)" ;;
*) SRC="來源不明" ;;
esac
# ── 交給 lib/beacon_report.py 組訊息 ─────────────────────────────────
#
# 🔴 為什麼改成走 python 而不是繼續用 printf 拼 JSONinkstone/ISEP#90):
# 下面要多報三件事(版本落差/舊複本遮蔽正門/退役機制的殘骸),
# 而那三段的內容含引號與換行。**用 shell 內插拼 JSON,一個引號就會把整包 JSON 弄壞,
# 而壞掉的 JSON 讓信標整行消失** —— 那正是本檔要偵測的「零閘狀態」的長相。
# ⇒ 訊息本身可以變複雜,但**序列化這件事不准手拼**。
#
# 🔴 fail-openpython 掛掉、網路不通、什麼都不准讓這行消失。
# 信標消失=leo 會判定這個 session 沒有閘(本檔第 8 行寫的判準),
# 所以組不出加料版就退回原本那一行乾淨的訊息。
MSG="🟢 ISEP v${VER} 已載入(${GATES} 支閘|來源:${SRC}${ROOT}"
FALLBACK="{
\"systemMessage\": \"${MSG}\",
\"hookSpecificOutput\": {
\"hookEventName\": \"SessionStart\",
\"additionalContext\": \"${MSG}。這行是 ISEP plugin 自己發的——看得到它就表示閘真的生效了。若某個 session 從頭到尾沒有這行,那個 session 是零閘狀態,先修 plugin 再做事,不要用『跑得動』當證據。\"
}
}"
OUT="$(python3 "$ROOT/hooks/lib/beacon_report.py" "$VER" "$GATES" "$SRC" 2>/dev/null || true)"
case "$OUT" in
\{*) printf '%s\n' "$OUT" ;;
*) printf '%s\n' "$FALLBACK" ;;
esac