fix(meta): 補齊稽核缺失並修掉護欄失效

What:依 jsc-meta:skill-check 的稽核結果修正技能與工具——補上每個步驟的可檢核完成條件、
把留在內文的標準輸入輸出流程下放 tools/、修正查表與退碼路由造成的誤判。

Why:稽核發現這些缺失會讓技能在實際執行時走錯分支或靜默通過。
完成條件缺漏是最常被違反的一項;退碼誤判與查表錯誤則會讓良性狀況被當成失敗。

How:逐項對照 references/guidelines.md 的審核檢查清單修正,新增的工具都有
documented exit codes,並以真實執行驗證每條路徑。

Who:jsc-meta:skill-check 例行稽核(2026-08-25)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 14:58:54 +08:00
co-authored by Claude Opus 5
parent 287e3ff113
commit 9ab5b42864
12 changed files with 490 additions and 51 deletions
+58 -7
View File
@@ -4,15 +4,28 @@
# 用法: find-skill-refs.sh <domain> <skill-name>
#
# 掃描樣式:
# 1. 技能名稱字面 {skill-name}
# 1. 技能名稱字面 {skill-name}——**純子字串比對**,不看邊界。
# 所以短名稱也會命中「名稱+後綴」的其他技能名,以及純敘述文字裡的同一個詞。
# 這是故意的:盤點寧可多列,漏一個引用會讓刪除技能少改檔案。
# 呼叫端必須逐檔看過命中內容再判斷,不可把清單當成「一定要改的檔案」。
# 2. 指令形式 /jsc-{domain}:{skill-name}
# 這兩個樣式已涵蓋其他 SKILL.md 的引用、domain README 的「Skills 目錄」小節、
# plugins/meta 兩份 marketplace.json 與其在各 domain 存取庫的同步副本、tools/ 腳本、
# jsc-hooks 接線等位置——只要檔案裡出現技能名稱或指令形式就會被列出。
#
# 掃描範圍: /root/plugins 底下所有 domain 存取庫,排除 .git 目錄。
# 輸出: 命中檔案清單(去重、排序),一行一個路徑;exit 0 一律成功,即使沒有命中。
set -eu
# 掃描範圍: **只掃正本 marketplace 上的 domain 存取庫**。
# 工作區可能還放著不屬於技能組的存取庫(例 shared、persona、code、doc)與
# 點開頭的目錄(例 .jsc-monorepo-archive、.kiro),掃進去會讓刪除技能改到
# 技能組以外的檔案。domain 清單取自 marketplace.json,不寫死。
# 每個 domain 先找 {root}/{domain},再找 {root}/jsc-{domain};兩者都沒有就略過。
# 每個存取庫內排除 .git 目錄。
# 根目錄預設取本腳本位置的上兩層(meta/tools -> meta -> 根),換機器不必改腳本;
# 用 JSC_PLUGINS_ROOT 覆寫,指向別處的技能組工作目錄。
# 輸出: 命中檔案清單(去重、排序),一行一個路徑(stdout);掃描摘要走 stderr。
# 結束碼: 0=有命中 1=掃完但零命中 2=用法錯誤
# 3=找不到根目錄、讀不到 domain 清單、本機一個 domain 存取庫都沒有,或掃描失敗
# 零命中與掃描失敗必須分開:拿掃描失敗當「沒有引用」會讓刪除技能少改檔案。
set -u
usage() {
echo 'usage: find-skill-refs.sh <domain> <skill-name>' >&2
@@ -22,13 +35,51 @@ usage() {
[ "$#" -eq 2 ] || usage
DOMAIN=$1
SKILL=$2
[ -n "$DOMAIN" ] && [ -n "$SKILL" ] || usage
ROOT=/root/plugins
[ -d "$ROOT" ] || { echo "找不到 plugins 根目錄: $ROOT" >&2; exit 1; }
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
ROOT="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}"
[ -d "$ROOT" ] || { echo "找不到 plugins 根目錄:$ROOT(可用 JSC_PLUGINS_ROOT 指定)" >&2; 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}"
grep -rlE --exclude-dir=.git -e "$PATTERN1" -e "$PATTERN2" "$ROOT" 2>/dev/null | sort -u
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