fix(tools): 改用共用推導取得技能組根目錄,並補齊結束碼宣告

以 plugin 形式安裝時,腳本會落在 CLI 的 plugin 快取目錄。
六支腳本原本一律取「腳本位置的上兩層」當技能組根目錄,這時一定推錯。
這一輪例行稽核的第一步就實際踩到:不先手動設環境變數,腳本根本跑不動,
而叫用它們的技能說明也沒提要設,等於留了一個必炸的預設值。

把推導規則抽成共用腳本,依序試環境變數、從目前目錄往上找、
腳本位置的上兩層、家目錄底下的 plugins,並以 gitea.sh 在不在當判準。
marketplace 每個 domain 存取庫都帶一份,單看它會把 domain 誤判成根。
推不出來就結束並指名要設環境變數,同時列出試過的每一個候選,
呼叫端一眼看得出要設什麼。只 clone 單一存取庫的環境留了逃生門:
環境變數有設且是目錄就照用,並在錯誤輸出提醒。

順手補上語言檢查與 manifest 同步兩支腳本的結束碼宣告與環境變數說明。
沒有宣告,呼叫端只能猜;猜錯就把失敗當成功。
This commit is contained in:
2026-08-31 11:11:12 +08:00
parent 9dabe98f2b
commit 7dc5c32de3
9 changed files with 159 additions and 27 deletions
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env sh
# plugins-root.sh — 推導 jsc 技能組工作目錄的根,meta/tools 六支腳本共用同一套規則。
#
# 用法:
# . "$(dirname -- "$0")/plugins-root.sh" # 以 source 載入,取得 jsc_plugins_root()
# ROOT=$(jsc_plugins_root) || exit {該腳本的碼}
# sh plugins-root.sh # 直接執行,把推導結果印到 stdout
#
# 為什麼要抽出來:
# 舊寫法一律取「本腳本位置的上兩層」。技能組以 plugin 形式安裝時,腳本落在
# ~/.claude/plugins/cache/jsc/jsc-meta/{版本}/tools/,上兩層是 .../cache/jsc/jsc-meta,
# 那是 plugin 快取,不是放各 domain 存取庫的工作目錄,**一定推錯**。
# 2026-08 例行稽核第一步就實際踩到:不手動設 JSC_PLUGINS_ROOT 就跑不動,
# 而呼叫這些腳本的 SKILL.md 都沒提要設。
#
# 推導順序(取第一個帶得出 gitea.sh 的候選):
# 1. JSC_PLUGINS_ROOT
# 2. 從 $PWD 逐層往上,找含 meta/.claude-plugin/marketplace.json
# (或 jsc-meta/.claude-plugin/marketplace.json)的目錄
# 3. 本腳本位置的上兩層({root}/meta/tools -> {root})
# 4. $HOME/plugins
#
# 判準: 候選目錄下要有 gitea/tools/gitea.sh 或 jsc-gitea/tools/gitea.sh。
# marketplace.json 每個 domain 存取庫都帶一份,單看它會把某個 domain 存取庫自己
# 誤判成根;gitea.sh 只存在於 jsc-gitea 存取庫裡,拿它當標記才分得出根與 domain。
#
# 逃生門: 候選都不帶 gitea.sh,但 JSC_PLUGINS_ROOT 有設且是目錄時,照設定值使用,
# 並在 stderr 提醒。只 clone plugins/meta 的環境(CI、單存取庫維護)本來就沒有
# jsc-gitea;這時硬擋會讓「請設 JSC_PLUGINS_ROOT」變成解不開的死路。
#
# 結束碼(直接執行時): 0=推導成功,根目錄印在 stdout
# 1=推導不出來,訊息會指名要設 JSC_PLUGINS_ROOT 並列出試過的候選
# 環境變數: JSC_PLUGINS_ROOT(根目錄,最優先)
set -u
_jsc_walk_up() { # 從 $PWD 逐層往上,印出第一個含 meta 存取庫的目錄
_jpr_d=$(pwd -P 2>/dev/null) || return 1
while [ -n "$_jpr_d" ]; do
if [ -f "$_jpr_d/meta/.claude-plugin/marketplace.json" ] ||
[ -f "$_jpr_d/jsc-meta/.claude-plugin/marketplace.json" ]; then
printf '%s\n' "$_jpr_d"
return 0
fi
[ "$_jpr_d" = / ] && break
_jpr_d=$(dirname -- "$_jpr_d")
done
return 1
}
_jsc_has_gitea() { # $1=候選根目錄;jsc-gitea 存取庫是「這是根」的唯一可靠標記
[ -f "$1/gitea/tools/gitea.sh" ] || [ -f "$1/jsc-gitea/tools/gitea.sh" ]
}
jsc_plugins_root() { # 印出根目錄(stdout);推不出來回 1,說明走 stderr
_jpr_tried=''
for _jpr_src in env pwd here home; do
case "$_jpr_src" in
env) _jpr_c="${JSC_PLUGINS_ROOT:-}" ;;
pwd) _jpr_c=$(_jsc_walk_up 2>/dev/null) || _jpr_c='' ;;
here) _jpr_c=$(CDPATH= cd -- "$(dirname -- "$0")/../.." 2>/dev/null && pwd) || _jpr_c='' ;;
home) if [ -n "${HOME:-}" ]; then _jpr_c="$HOME/plugins"; else _jpr_c=''; fi ;;
*) _jpr_c='' ;;
esac
[ -n "$_jpr_c" ] || continue
_jpr_tried="$_jpr_tried $_jpr_src: $_jpr_c
"
[ -d "$_jpr_c" ] || continue
_jsc_has_gitea "$_jpr_c" || continue
_jpr_c=$(CDPATH= cd -- "$_jpr_c" && pwd) || continue
printf '%s\n' "${_jpr_c%/}"
return 0
done
if [ -n "${JSC_PLUGINS_ROOT:-}" ] && [ -d "$JSC_PLUGINS_ROOT" ]; then
echo "JSC_PLUGINS_ROOT 底下找不到 gitea/tools/gitea.sh,仍照設定值使用:$JSC_PLUGINS_ROOT" >&2
_jpr_c=$(CDPATH= cd -- "$JSC_PLUGINS_ROOT" && pwd) || return 1
printf '%s\n' "${_jpr_c%/}"
return 0
fi
{
echo '推導不出 jsc 技能組的根目錄。'
echo '以 plugin 形式安裝時,腳本位在 ~/.claude/plugins/cache/jsc/jsc-meta/{版本}/tools/,'
echo '上兩層落在 plugin 快取目錄,不是放各 domain 存取庫的工作目錄,所以推不出來。'
echo '請設 JSC_PLUGINS_ROOT 指向放各 domain 存取庫的工作目錄(底下要有 gitea 或 jsc-gitea)。'
echo '已試過的候選:'
printf '%s' "$_jpr_tried"
} >&2
return 1
}
# 直接執行時印出結果;被 source 時只提供函式。
case "${0##*/}" in
plugins-root.sh) jsc_plugins_root || exit 1 ;;
esac