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

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 85b89e4cdc
commit ea11ca0590
6 changed files with 277 additions and 28 deletions
+27 -8
View File
@@ -1,17 +1,36 @@
#!/usr/bin/env sh
# apply-version.sh — 改寫版本來源檔案,把套件釘選到指定版本。
# 用法: apply-version.sh <ecosystem> <project-dir> <name> <version>
# ecosystem: nodejs | python | dotnet
# 對應檔案(與 list-packages.sh 相同的解析邏輯):
# 用法:apply-version.sh <ecosystem> <project-dir> <name> <version>
# ecosystem:nodejs | python | dotnet
# 對應檔案(與 list-packages.sh 相同的解析邏輯):
# nodejs → package.json(dependencies / devDependencies)
# python → requirements.txt(優先)或 pyproject.toml 的 [project] dependencies
# dotnet → *.csproj 的 PackageReference
# 找不到對應檔案 exit 3;檔案中找不到該套件 exit 4
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 改寫成功
# 1 參數個數不對
# 2 ecosystem 不認識
# 3 找不到對應的版本來源檔案——呼叫方跳過這個套件
# 4 檔案中找不到該套件——呼叫方跳過這個套件
# 5 需要的指令不存在(python3)
# 6 找不到專案目錄——呼叫方停手,不要當成跳過
set -u
eco="${1:?ecosystem required (nodejs|python|dotnet)}"
DIR="${2:?project dir required}"
name="${3:?package name required}"
version="${4:?version required}"
if [ "$#" -ne 4 ]; then
echo "用法:apply-version.sh <ecosystem> <project-dir> <name> <version>" >&2
exit 1
fi
eco="$1"
DIR="$2"
name="$3"
version="$4"
# 先擋找不到專案目錄。少了這道,壞路徑會被報成「沒有版本來源檔案」,然後被當成跳過藏起來。
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
# 三種來源檔案都靠 python3 改寫。少了 python3 會被誤判成檔案中找不到該套件,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 5; }
case "$eco" in
nodejs)
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env sh
# build-test.sh — 偵測並執行專案的建置與測試,先建置再測試。
# 用法:build-test.sh <ecosystem> <project-dir>
# ecosystem:nodejs | python | dotnet
# 偵測方式:
# nodejs → package.json 的 scripts.build 與 scripts.test(npm init 產生的佔位 test 視為沒有)
# python → pytest 設定(pytest.ini、setup.cfg、tox.ini、pyproject.toml)或 tests 目錄與 test_*.py
# dotnet → *.sln 或 *.csproj,建置與測試都用 dotnet
# 輸出:底層指令的原始輸出。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 建置與測試都通過
# 1 參數個數不對
# 2 ecosystem 不認識
# 4 需要的指令不存在(npm、python3、pytest、dotnet)
# 5 推不出任何建置或測試指令——呼叫方要改問使用者
# 6 找不到專案目錄——呼叫方停手
# 其他 建置或測試失敗,帶回失敗指令的結束碼
# 本工具不用 3:3 保留給「該 ecosystem 沒有來源檔案」,這支改用 5 表達推不出指令。
set -u
if [ "$#" -ne 2 ]; then
echo "用法:build-test.sh <ecosystem> <project-dir>" >&2
exit 1
fi
eco="$1"
DIR="$2"
NO_COMMAND=5
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
case "$eco" in
nodejs)
[ -f "$DIR/package.json" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
# package.json 靠 python3 解析。少了 python3 會解出空字串,被誤判成推不出指令,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 4; }
scripts=$(python3 - "$DIR/package.json" <<'EOF'
import json,sys
try:
d=json.load(open(sys.argv[1],encoding="utf-8"))
except Exception:
sys.exit(0)
s=(d.get("scripts") or {})
if s.get("build"): print("build")
t=s.get("test") or ""
if t and "no test specified" not in t: print("test")
EOF
)
[ -n "$scripts" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
command -v npm >/dev/null 2>&1 || { echo "npm not found" >&2; exit 4; }
for s in $scripts; do
( cd "$DIR" && npm run --if-present "$s" ) || exit $?
done
exit 0
;;
python)
has_tests=0
for f in pytest.ini setup.cfg tox.ini pyproject.toml; do
if [ -f "$DIR/$f" ] && grep -q 'pytest' "$DIR/$f" 2>/dev/null; then has_tests=1; fi
done
[ -d "$DIR/tests" ] && has_tests=1
if [ "$has_tests" -eq 0 ]; then
found=$(find "$DIR" -maxdepth 3 -name 'test_*.py' 2>/dev/null | head -n 1)
[ -n "$found" ] && has_tests=1
fi
[ "$has_tests" -eq 1 ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
if command -v pytest >/dev/null 2>&1; then
( cd "$DIR" && pytest )
exit $?
elif command -v python3 >/dev/null 2>&1 && python3 -m pytest --version >/dev/null 2>&1; then
( cd "$DIR" && python3 -m pytest )
exit $?
else
echo "pytest not found" >&2
exit 4
fi
;;
dotnet)
projs=$(find "$DIR" -maxdepth 3 \( -name '*.sln' -o -name '*.csproj' \) 2>/dev/null)
[ -n "$projs" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
command -v dotnet >/dev/null 2>&1 || { echo "dotnet not found" >&2; exit 4; }
( cd "$DIR" && dotnet build ) || exit $?
( cd "$DIR" && dotnet test ) || exit $?
exit 0
;;
*) echo "unknown ecosystem: $eco" >&2; exit 2 ;;
esac
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env sh
# install-deps.sh — 重新解析並安裝專案的相依套件。
# 用法:install-deps.sh <ecosystem> <project-dir>
# ecosystem:nodejs | python | dotnet
# 對應動作:
# nodejs → npm install
# python → pip install -r requirements.txt(優先)或 pip install -e .(pyproject.toml)
# dotnet → dotnet restore
# 輸出:底層指令的原始輸出。
# 檢查順序:先確認相依來源檔案存在,才檢查指令存在。順序顛倒會把「沒有來源檔案」報成「指令不存在」。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 安裝成功
# 1 參數個數不對
# 2 ecosystem 不認識
# 3 該 ecosystem 沒有相依來源檔案——呼叫方跳過這個 ecosystem
# 4 需要的指令不存在(npm、pip、dotnet)
# 6 找不到專案目錄——呼叫方停手,不要當成跳過
# 其他 底層指令的結束碼,代表安裝失敗
set -u
if [ "$#" -ne 2 ]; then
echo "用法:install-deps.sh <ecosystem> <project-dir>" >&2
exit 1
fi
eco="$1"
DIR="$2"
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
case "$eco" in
nodejs)
[ -f "$DIR/package.json" ] || { echo "package.json not found in $DIR" >&2; exit 3; }
command -v npm >/dev/null 2>&1 || { echo "npm not found" >&2; exit 4; }
( cd "$DIR" && npm install )
exit $?
;;
python)
if [ -f "$DIR/requirements.txt" ]; then
target="-r requirements.txt"
elif [ -f "$DIR/pyproject.toml" ]; then
target="-e ."
else
echo "requirements.txt or pyproject.toml not found in $DIR" >&2
exit 3
fi
pip=""
for c in pip3 pip; do
if command -v "$c" >/dev/null 2>&1; then pip="$c"; break; fi
done
if [ -z "$pip" ]; then
if command -v python3 >/dev/null 2>&1 && python3 -m pip --version >/dev/null 2>&1; then
pip="python3 -m pip"
else
echo "pip not found" >&2
exit 4
fi
fi
( cd "$DIR" && $pip install $target )
exit $?
;;
dotnet)
projs=$(find "$DIR" -maxdepth 3 \( -name '*.csproj' -o -name '*.sln' \) 2>/dev/null)
[ -n "$projs" ] || { echo "*.csproj or *.sln not found in $DIR" >&2; exit 3; }
command -v dotnet >/dev/null 2>&1 || { echo "dotnet not found" >&2; exit 4; }
( cd "$DIR" && dotnet restore )
exit $?
;;
*) echo "unknown ecosystem: $eco" >&2; exit 2 ;;
esac
+23 -5
View File
@@ -1,11 +1,29 @@
#!/usr/bin/env sh
# latest-version.sh — 查詢套件最新且穩定的版本號。
# 用法: latest-version.sh <ecosystem> <name>
# ecosystem: nodejs | python | dotnet
# 輸出: 版本號一行;查不到 exit 4
# 用法:latest-version.sh <ecosystem> <name>
# ecosystem:nodejs | python | dotnet
# 輸出:版本號一行。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 查到版本號
# 1 參數個數不對
# 2 ecosystem 不認識
# 4 登錄站上查不到這個套件——呼叫方跳過這個套件
# 5 需要的指令不存在(curl、python3)
# 本工具不收專案目錄,所以沒有 6。
set -u
eco="${1:?ecosystem required (nodejs|python|dotnet)}"
name="${2:?package name required}"
if [ "$#" -ne 2 ]; then
echo "用法:latest-version.sh <ecosystem> <name>" >&2
exit 1
fi
eco="$1"
name="$2"
# 回應靠 curl 取得、靠 python3 解析。少了任一支會解出空字串,被誤判成查不到套件,所以先擋。
for c in curl python3; do
command -v "$c" >/dev/null 2>&1 || { echo "$c not found" >&2; exit 5; }
done
case "$eco" in
nodejs)
+20 -3
View File
@@ -1,11 +1,28 @@
#!/usr/bin/env sh
# list-packages.sh — 列出專案的所有外部套件與目前版本。
# 用法: list-packages.sh [專案目錄](預設目前目錄)
# 輸出(TSV): ecosystem<TAB>name<TAB>current
# 支援: nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj)
# 用法:list-packages.sh [專案目錄](預設目前目錄)
# 輸出(TSV):ecosystem<TAB>name<TAB>current
# 支援:nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj)
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 掃描完成(沒有套件時輸出零行)
# 1 參數個數不對
# 4 需要的指令不存在(python3)
# 6 找不到專案目錄——呼叫方停手
set -u
if [ "$#" -gt 1 ]; then
echo "用法:list-packages.sh [專案目錄]" >&2
exit 1
fi
DIR="${1:-.}"
# 先擋找不到專案目錄。少了這道,壞路徑會輸出零行,被誤判成專案沒有套件。
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
# 四種來源檔案都靠 python3 解析。少了 python3 會輸出零行,被誤判成專案沒有套件,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 4; }
# nodejs: package.json 的 dependencies / devDependencies
if [ -f "$DIR/package.json" ]; then
python3 - "$DIR/package.json" <<'EOF'