diff --git a/tools/find-skill-refs.sh b/tools/find-skill-refs.sh index 5ed9103..b03ec2e 100755 --- a/tools/find-skill-refs.sh +++ b/tools/find-skill-refs.sh @@ -19,12 +19,15 @@ # 技能組以外的檔案。domain 清單取自 marketplace.json,不寫死。 # 每個 domain 先找 {root}/{domain},再找 {root}/jsc-{domain};兩者都沒有就略過。 # 每個存取庫內排除 .git 目錄。 -# 根目錄預設取本腳本位置的上兩層(meta/tools -> meta -> 根),換機器不必改腳本; -# 用 JSC_PLUGINS_ROOT 覆寫,指向別處的技能組工作目錄。 +# 根目錄交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> +# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在 +# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。 # 輸出: 命中檔案清單(去重、排序),一行一個路徑(stdout);掃描摘要走 stderr。 # 結束碼: 0=有命中 1=掃完但零命中 2=用法錯誤 -# 3=找不到根目錄、讀不到 domain 清單、本機一個 domain 存取庫都沒有,或掃描失敗 +# 3=推導不出根目錄、讀不到 domain 清單、本機一個 domain 存取庫都沒有、 +# 建不了暫存檔,或掃描失敗 # 零命中與掃描失敗必須分開:拿掃描失敗當「沒有引用」會讓刪除技能少改檔案。 +# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh) set -u usage() { @@ -38,8 +41,8 @@ SKILL=$2 [ -n "$DOMAIN" ] && [ -n "$SKILL" ] || usage 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; } +. "$HERE/plugins-root.sh" +ROOT=$(jsc_plugins_root) || exit 3 # 正本 domain 清單:優先讀本機任一份 marketplace 副本(每個 repo 都帶同一份)。 mkt="" diff --git a/tools/inventory-tooling.sh b/tools/inventory-tooling.sh index 631c1f5..c0de3de 100755 --- a/tools/inventory-tooling.sh +++ b/tools/inventory-tooling.sh @@ -3,15 +3,25 @@ # # 用法: inventory-tooling.sh [root] # -# root: 預設取本腳本位置的上兩層(meta/tools -> meta -> 根)。 -# 也可用參數或 JSC_PLUGINS_ROOT 覆寫。 +# root: 給了參數就用參數。沒給就交給 tools/plugins-root.sh 推導 +# (JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> 腳本位置上兩層 -> $HOME/plugins)。 +# 以 plugin 形式安裝時,「腳本位置上兩層」會落在 plugin 快取目錄,單靠它一定推錯, +# 所以推導規則抽成共用腳本。 # # 輸出: Markdown。內容包含 domain、manifest、技能、工具、hooks 與管理入口。 -# 結束碼: 0=成功 1=根目錄、marketplace 或必要工具缺失 +# 同一趟已經跑過 list-skills.sh、detect-clis.sh 與 wire-cli.sh status,結果都寫進輸出, +# 呼叫端沿用即可,不必再各跑一次。 +# 結束碼: 0=成功 1=推導不出根目錄、根目錄不存在、marketplace 或必要工具缺失 +# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh) set -eu HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -ROOT=${1:-${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}} +. "$HERE/plugins-root.sh" +if [ "$#" -ge 1 ] && [ -n "$1" ]; then + ROOT=$1 +else + ROOT=$(jsc_plugins_root) || exit 1 +fi ROOT=${ROOT%/} [ -d "$ROOT" ] || { echo "找不到 plugins 根目錄:$ROOT" >&2; exit 1; } diff --git a/tools/list-skills.sh b/tools/list-skills.sh index 9cffb37..42ee0c3 100755 --- a/tools/list-skills.sh +++ b/tools/list-skills.sh @@ -10,17 +10,19 @@ # 誤把它們當成技能組的一部分。domain 清單取自 marketplace.json,不寫死。 # 清單只反映本機檔案;要確保檔案齊全請先跑 sync-domains.sh。 # -# 根目錄: 預設取本腳本位置的上兩層(meta/tools -> meta -> 根)。 -# 用 JSC_PLUGINS_ROOT 覆寫,換機器不必改腳本。 +# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> +# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在 +# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。 # # 輸出: 一行一個技能,格式 {domain}{name}{description},依 domain、name 排序。 -# 結束碼: 0=至少列出一個技能 1=找不到根目錄、讀不到 domain 清單,或一個技能都沒有 +# 結束碼: 0=至少列出一個技能 +# 1=推導不出根目錄、讀不到 domain 清單,或一個技能都沒有 set -u HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -ROOT="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}" +. "$HERE/plugins-root.sh" -[ -d "$ROOT" ] || { echo "找不到 plugins 根目錄:$ROOT(可用 JSC_PLUGINS_ROOT 指定)" >&2; exit 1; } +ROOT=$(jsc_plugins_root) || exit 1 # 正本 domain 清單:優先讀本機任一份 marketplace 副本(每個 repo 都帶同一份)。 mkt="" diff --git a/tools/plugins-root.sh b/tools/plugins-root.sh new file mode 100755 index 0000000..ab8df36 --- /dev/null +++ b/tools/plugins-root.sh @@ -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 diff --git a/tools/ste100-lint.sh b/tools/ste100-lint.sh index f7d59ee..d7a5a84 100755 --- a/tools/ste100-lint.sh +++ b/tools/ste100-lint.sh @@ -8,7 +8,9 @@ # 4. 簡體字(只收沒有繁體正當用法的字,避免誤報) # 5. 中文並列項用斜線(半形 / 或全形 / 夾在中日韓字元之間),應改頓號「、」 # 6. 亂碼(U+FFFD 替代字元、Latin-1 雙重編碼殘留) -# 輸出: {檔案}:{行號}:{類別}:{命中內容};全部通過 exit 0,有命中 exit 1。 +# 輸出: {檔案}:{行號}:{類別}:{命中內容}(stdout);用法錯誤走 stderr。 +# 結束碼: 0=掃過的檔案全部通過 1=有命中 2=沒給檢查對象(空跑會回 0,看起來像通過,所以擋掉) +# 環境變數: JSC_SIMPLIFIED_FILE(簡體字表路徑,優先於自動搜尋) # 掃描範圍與分流: # 文件檔(.md、.json)跑全部六項。 # 程式碼檔(.sh .js .ts .py .cs .java .go .rb .php .sql .yml .yaml .toml)只跑簡體字與亂碼。 diff --git a/tools/sync-domains.sh b/tools/sync-domains.sh index 7b4f3df..584a511 100755 --- a/tools/sync-domains.sh +++ b/tools/sync-domains.sh @@ -10,13 +10,14 @@ # 3. 本機已有的 domain:git pull。工作區有未提交變更就跳過 pull,只在 stderr 提醒—— # 正在改的 repo 不該被自動 pull 覆蓋。 # -# 根目錄: 預設取本腳本位置的上兩層(meta/tools -> meta -> 根)。 -# 用 JSC_PLUGINS_ROOT 覆寫,換機器不必改腳本。 +# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> +# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在 +# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。 # 存取庫目錄名: 先找 {root}/{domain},再找 {root}/jsc-{domain};都沒有才 clone 成 {root}/{domain}。 # # 輸出: 一行一個 domain,格式 {domain}{path}(stdout);警告與失敗說明走 stderr。 # 結束碼: 0=正本讀到、每個 domain 都在本機,而且每個既有存取庫都更新到最新 -# 1=讀不到正本 marketplace 或找不到 gitea.sh(不輸出任何 domain) +# 1=推導不出根目錄、讀不到正本 marketplace,或找不到 gitea.sh(不輸出任何 domain) # 2=正本讀到,但有 domain 沒能取得或 clone 失敗(已輸出其餘 domain) # 3=每個 domain 都在本機,但有存取庫沒更新到最新:工作區髒而跳過 pull, # 或 pull 失敗。stderr 會逐一列出這些路徑。**exit 0 才代表「全部最新」**; @@ -28,10 +29,10 @@ set -u HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -ROOT="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}" +. "$HERE/plugins-root.sh" DRY="${JSC_SYNC_DRY_RUN:-}" -[ -d "$ROOT" ] || { echo "找不到 plugins 根目錄:$ROOT(可用 JSC_PLUGINS_ROOT 指定)" >&2; exit 1; } +ROOT=$(jsc_plugins_root) || exit 1 GITEA="" for g in "$ROOT/gitea/tools/gitea.sh" "$ROOT/jsc-gitea/tools/gitea.sh"; do diff --git a/tools/sync-marketplace.sh b/tools/sync-marketplace.sh index 38d03f8..322de6f 100755 --- a/tools/sync-marketplace.sh +++ b/tools/sync-marketplace.sh @@ -18,14 +18,17 @@ # python3 不在 PATH 時:腳本不寫任何檔案,印出缺少 python3 的訊息,回 1。 # 呼叫端要先裝 python3 再重跑,不可改用 sed 手動改 marketplace。 # -# 根目錄: 預設取本腳本位置的上兩層(meta/tools -> meta -> 根)。 -# 用 JSC_PLUGINS_ROOT 覆寫,換機器不必改腳本。 +# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> +# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在 +# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。 # 存取庫目錄名: 先找 {root}/{domain},再找 {root}/jsc-{domain}。 # # 輸出: 一行一個實際寫入的檔案路徑(stdout);略過與失敗說明走 stderr。 # 結束碼: 0=全部寫入且逐檔一致 2=用法錯誤 -# 1=缺 python3、正本讀寫失敗,或有檔案比對不一致 +# 1=推導不出根目錄、缺 python3、正本讀寫失敗、建不了暫存目錄, +# 或有檔案比對不一致 # 3=寫入成功,但有 domain 存取庫不在本機(先跑 sync-domains.sh 再重跑) +# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh) set -u usage() { @@ -41,7 +44,8 @@ DESC=$3 HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) META=$(CDPATH= cd -- "$HERE/.." && pwd) -ROOT="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}" +. "$HERE/plugins-root.sh" +ROOT=$(jsc_plugins_root) || exit 1 REL_CLAUDE='.claude-plugin/marketplace.json' REL_AGENTS='.agents/plugins/marketplace.json' diff --git a/tools/sync-skill-manifest.sh b/tools/sync-skill-manifest.sh index 27380a8..7db2d17 100755 --- a/tools/sync-skill-manifest.sh +++ b/tools/sync-skill-manifest.sh @@ -13,7 +13,14 @@ # .codex-plugin/plugin.json,version 一律 bump 成同一個新值(以第一份找到的 manifest 版本為準, # 右側數字加一,滿 9 就往左進位;major 不設上限,minor 與 patch 都不超過 9)。 # -# 輸出: 變更摘要——README 新增/移除的技能小節、各 manifest 的舊版本 -> 新版本。 +# 輸出: 變更摘要——README 新增或移除的技能小節、各 manifest 的舊版本 -> 新版本(stdout); +# 失敗說明走 stderr。 +# 結束碼: 0=README 區塊與三份 manifest 都已同步 +# 1=domain 路徑不存在、缺 skills/、缺 README.md、README 缺 JSC-SKILLS 標記、 +# 掃不到任何 SKILL.md、找不到 plugin manifest,或 manifest 讀不到 version 欄位 +# 2=用法錯誤(本腳本只吃一個參數) +# 本腳本是 set -e:上列以外的指令失敗會直接中止,結束碼由該指令決定, +# 呼叫端把「不是 0、1、2」一律當執行環境故障處理,不得視為同步成功。 set -eu usage() { diff --git a/tools/verify-skill-removed.sh b/tools/verify-skill-removed.sh index a651db5..91be9be 100755 --- a/tools/verify-skill-removed.sh +++ b/tools/verify-skill-removed.sh @@ -14,9 +14,16 @@ # antigravity — ~/.antigravity/、~/.config/antigravity/ # kiro — ~/.kiro/、工作目錄的 .kiro/hooks/ # +# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> +# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在 +# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。 +# # 輸出: 檢查過的位置一行一個(stderr),殘留一行一個 {file}:{line}:{內容}(stdout)。 # 沒有殘留會印「無殘留」到 stderr,區別於「什麼都沒檢查」。 -# 結束碼: 0=沒有殘留 1=有殘留 2=用法錯誤 3=沒偵測到任何 CLI 或找不到可檢查的位置 +# 結束碼: 0=沒有殘留 1=有殘留 2=用法錯誤 +# 3=推導不出根目錄、找不到 detect-clis.sh、沒偵測到任何 CLI, +# 或找不到可檢查的位置(**不等於乾淨**) +# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh) set -u usage() { @@ -30,7 +37,8 @@ SKILL=$2 [ -n "$DOMAIN" ] && [ -n "$SKILL" ] || usage HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -ROOT="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}" +. "$HERE/plugins-root.sh" +ROOT=$(jsc_plugins_root) || exit 3 DETECT="" for d in "$ROOT/cli/tools/detect-clis.sh" "$ROOT/jsc-cli/tools/detect-clis.sh"; do