Files
meta/tools/find-skill-refs.sh
jiantw83 7dc5c32de3 fix(tools): 改用共用推導取得技能組根目錄,並補齊結束碼宣告
以 plugin 形式安裝時,腳本會落在 CLI 的 plugin 快取目錄。
六支腳本原本一律取「腳本位置的上兩層」當技能組根目錄,這時一定推錯。
這一輪例行稽核的第一步就實際踩到:不先手動設環境變數,腳本根本跑不動,
而叫用它們的技能說明也沒提要設,等於留了一個必炸的預設值。

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

順手補上語言檢查與 manifest 同步兩支腳本的結束碼宣告與環境變數說明。
沒有宣告,呼叫端只能猜;猜錯就把失敗當成功。
2026-08-31 11:11:12 +08:00

89 lines
4.2 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 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