以 plugin 形式安裝時,腳本會落在 CLI 的 plugin 快取目錄。 六支腳本原本一律取「腳本位置的上兩層」當技能組根目錄,這時一定推錯。 這一輪例行稽核的第一步就實際踩到:不先手動設環境變數,腳本根本跑不動, 而叫用它們的技能說明也沒提要設,等於留了一個必炸的預設值。 把推導規則抽成共用腳本,依序試環境變數、從目前目錄往上找、 腳本位置的上兩層、家目錄底下的 plugins,並以 gitea.sh 在不在當判準。 marketplace 每個 domain 存取庫都帶一份,單看它會把 domain 誤判成根。 推不出來就結束並指名要設環境變數,同時列出試過的每一個候選, 呼叫端一眼看得出要設什麼。只 clone 單一存取庫的環境留了逃生門: 環境變數有設且是目錄就照用,並在錯誤輸出提醒。 順手補上語言檢查與 manifest 同步兩支腳本的結束碼宣告與環境變數說明。 沒有宣告,呼叫端只能猜;猜錯就把失敗當成功。
89 lines
4.2 KiB
Bash
Executable File
89 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# find-skill-refs.sh — 掃描一個技能在所有 jsc domain 存取庫裡的引用,供刪除技能前盤點用。
|
||
#
|
||
# 用法: find-skill-refs.sh <domain> <skill-name>
|
||
#
|
||
# 掃描樣式:
|
||
# 1. 技能名稱字面 {skill-name}——**純子字串比對**,不看邊界。
|
||
# 所以短名稱也會命中「名稱+後綴」的其他技能名,以及純敘述文字裡的同一個詞。
|
||
# 這是故意的:盤點寧可多列,漏一個引用會讓刪除技能少改檔案。
|
||
# 呼叫端必須逐檔看過命中內容再判斷,不可把清單當成「一定要改的檔案」。
|
||
# 2. 指令形式 /jsc-{domain}:{skill-name}
|
||
# 這兩個樣式已涵蓋其他 SKILL.md 的引用、domain README 的「Skills 目錄」小節、
|
||
# plugins/meta 兩份 marketplace.json 與其在各 domain 存取庫的同步副本、tools/ 腳本、
|
||
# jsc-hooks 接線等位置——只要檔案裡出現技能名稱或指令形式就會被列出。
|
||
#
|
||
# 掃描範圍: **只掃正本 marketplace 上的 domain 存取庫**。
|
||
# 工作區可能還放著不屬於技能組的存取庫(例 shared、persona、code、doc)與
|
||
# 點開頭的目錄(例 .jsc-monorepo-archive、.kiro),掃進去會讓刪除技能改到
|
||
# 技能組以外的檔案。domain 清單取自 marketplace.json,不寫死。
|
||
# 每個 domain 先找 {root}/{domain},再找 {root}/jsc-{domain};兩者都沒有就略過。
|
||
# 每個存取庫內排除 .git 目錄。
|
||
# 根目錄交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||
# 輸出: 命中檔案清單(去重、排序),一行一個路徑(stdout);掃描摘要走 stderr。
|
||
# 結束碼: 0=有命中 1=掃完但零命中 2=用法錯誤
|
||
# 3=推導不出根目錄、讀不到 domain 清單、本機一個 domain 存取庫都沒有、
|
||
# 建不了暫存檔,或掃描失敗
|
||
# 零命中與掃描失敗必須分開:拿掃描失敗當「沒有引用」會讓刪除技能少改檔案。
|
||
# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh)
|
||
set -u
|
||
|
||
usage() {
|
||
echo 'usage: find-skill-refs.sh <domain> <skill-name>' >&2
|
||
exit 2
|
||
}
|
||
|
||
[ "$#" -eq 2 ] || usage
|
||
DOMAIN=$1
|
||
SKILL=$2
|
||
[ -n "$DOMAIN" ] && [ -n "$SKILL" ] || usage
|
||
|
||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
. "$HERE/plugins-root.sh"
|
||
ROOT=$(jsc_plugins_root) || exit 3
|
||
|
||
# 正本 domain 清單:優先讀本機任一份 marketplace 副本(每個 repo 都帶同一份)。
|
||
mkt=""
|
||
for cand in "$HERE/../.claude-plugin/marketplace.json" "$ROOT"/*/.claude-plugin/marketplace.json; do
|
||
[ -f "$cand" ] && { mkt="$cand"; break; }
|
||
done
|
||
[ -n "$mkt" ] || { echo "找不到 marketplace.json,無法判定 domain 清單" >&2; exit 3; }
|
||
DOMAINS=$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"jsc-\([a-z0-9-]*\)".*/\1/p' "$mkt" | sort -u)
|
||
[ -n "$DOMAINS" ] || { echo "marketplace.json 裡沒有 jsc-{domain} 條目:$mkt" >&2; exit 3; }
|
||
|
||
# 只收本機存在、且不是點開頭目錄的 domain 存取庫路徑。
|
||
TARGETS=""
|
||
for d in $DOMAINS; do
|
||
for cand in "$ROOT/$d" "$ROOT/jsc-$d"; do
|
||
[ -d "$cand" ] || continue
|
||
TARGETS="$TARGETS $cand"
|
||
break
|
||
done
|
||
done
|
||
[ -n "$TARGETS" ] || { echo "本機找不到任何 domain 存取庫:$ROOT(先跑 sync-domains.sh)" >&2; exit 3; }
|
||
|
||
PATTERN1="$SKILL"
|
||
PATTERN2="/jsc-${DOMAIN}:${SKILL}"
|
||
|
||
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||
trap 'rm -f "$TMP"' EXIT
|
||
# grep 的結束碼要單獨看:接管線的話 sort 永遠回 0,掃描失敗就被吃掉了。
|
||
# shellcheck disable=SC2086
|
||
grep -rlI --exclude-dir=.git -e "$PATTERN1" -e "$PATTERN2" $TARGETS > "$TMP" 2>/dev/null
|
||
status=$?
|
||
[ "$status" -le 1 ] || { echo "掃描失敗:$ROOT(grep 結束碼 $status)" >&2; exit 3; }
|
||
OUT=$(sort -u "$TMP")
|
||
|
||
count=$(printf '%s' "$TARGETS" | wc -w | tr -d ' ')
|
||
if [ -z "$OUT" ]; then
|
||
echo "掃描完成,零命中:$count 個 domain 存取庫" >&2
|
||
exit 1
|
||
fi
|
||
|
||
printf '%s\n' "$OUT"
|
||
printf '掃描完成,命中 %s 個檔案(掃了 %s 個 domain 存取庫;技能名稱為純子字串比對,請逐檔確認)\n' \
|
||
"$(printf '%s\n' "$OUT" | wc -l | tr -d ' ')" "$count" >&2
|
||
exit 0
|