Files
meta/tools/lint-scripts.sh
T
jiantw83 5aa4a3da57 feat(skills): 新增技能盤點頁與共用部署驗證流程,並把技能驗證移到新行程
技能盤點以前只回到對話裡,換一台機器就得重跑才知道裝了什麼。
現在新增技能盤點這個 wiki 頁類型,雜湊取「主機、工具名稱、登入帳號」三段。
每支 CLI 各有自己的 plugin 集合,也各有自己的 hook 接線,那是互相獨立的事實。
少了工具名稱那一段,同一台機器上五支 CLI 會算出同一個雜湊,五份盤點互相覆蓋,
讀的人還看不出被蓋掉。技能盤點新增寫入這兩頁的步驟,整步規定必須開 sub agent。
兩份樣板刻意分開:內容頁每次盤點覆寫整頁,目錄頁只更新自己那一列,
兩者的寫入語意剛好相反,合成一份遲早有人把別台機器的紀錄刪掉。

四支異動技能原本在部署完的同一個工作階段,就叫用剛做好的技能。
部署收尾自己立起重啟閘門,那支技能必被擋下,驗證做不完。
解法不是把它加進豁免清單。豁免擋得住閘門,擋不住「行程還載著舊版」這件事,
硬過關驗到的是舊版行為,等於假通過。所以把判路線、部署、驗證、失敗分流
抽成一份共用說明,驗證一律另開 CLI 行程執行,四支技能只留一行指標指過去。

新增腳本檢查工具,一次做完語法、執行權限與結束碼宣告三項檢查,
只被 source 的函式庫豁免後兩項,而且逐支記在錯誤輸出,不靜默略過。
新增部署路線判定工具,判定改動有沒有進存取庫的預設分支,
取代四支技能各抄一段、各自漂移的散文;判不出來就回報停下,不自己挑路線走。

同時把四支技能裡的中文段落抽到共用說明、指標改回英文,
修正六處相對路徑,把技能盤點的模糊描述改成查得出來的條件,
並讓 manifest 同步的每一個呼叫端逐碼分流。

七支技能改為併行執行:例行稽核從九步併成七步,技能盤點併成六步。
技能盤點不再重跑盤點腳本內部已經跑過的三支腳本,
而那三支原本兼作獨立交叉檢查,拿掉就少一層保護,
所以把少掉的是什麼、風險由誰擋住,明白寫進 Notes,不當作沒發生。
2026-08-31 11:11:12 +08:00

106 lines
4.5 KiB
Bash
Executable File
Raw 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
# lint-scripts.sh — 檢查一個 domain 存取庫的 shell 腳本:語法、可執行、結束碼有沒有記載。
#
# 用法: lint-scripts.sh <domain-path>
#
# 檢查三項(掃 {domain-path}/tools 與 {domain-path}/hooks 底下的 *.sh):
# 1. 語法 — sh -n 通過。
# 2. 可執行 — 檔案有執行權限。技能直接呼叫的腳本沒有 +x,會在部署後才炸。
# 3. 結束碼 — 檔頭前 60 行要有結束碼宣告列(「結束碼:」或「exit code」)。
# 沒有宣告的腳本,呼叫端沒辦法逐碼分流,只能猜;猜錯就把失敗當成功。
#
# 為什麼合成一支: skill-check 原本在 SKILL.md 裡寫 find ... -exec sh -n,另外兩項靠散文
# 要求人工比對。三項的輸入輸出都固定,散文版每個 domain 各做一次,還會跟腳本實況漂移。
#
# 例外: 只被 source 的共用函式庫(判準見 is_library)第 2、3 項都不查——它不是可執行入口,
# 沒有 +x 的必要,也沒有結束碼語意可宣告。第 1 項語法檢查照查,函式庫一樣會被 sh -n 讀。
# 豁免不是靜默的: 每豁免一支就在 stderr 記一行,摘要也帶函式庫支數,才看得出誰被跳過。
#
# 輸出: 一行一個不合格項目,格式 {檔案}:{檢查項}:{說明}(stdout);
# 函式庫豁免通知與統計摘要走 stderr。
# 結束碼: 0=掃到腳本且三項全過
# 1=有不合格項目(清單在 stdout)
# 2=用法錯誤(本腳本只吃一個參數)
# 3=domain 路徑不存在,或 tools/ 與 hooks/ 都沒有 *.sh——**什麼都沒掃**,
# 不等於通過。缺這兩個目錄本身不是失敗,呼叫端照實記「無腳本可掃」即可。
set -u
usage() {
echo 'usage: lint-scripts.sh <domain-path>' >&2
exit 2
}
[ "$#" -eq 1 ] || usage
DOMAIN=${1%/}
[ -n "$DOMAIN" ] || usage
[ -d "$DOMAIN" ] || { echo "找不到 domain 路徑:$DOMAIN" >&2; exit 3; }
SCAN=''
for d in "$DOMAIN/tools" "$DOMAIN/hooks"; do
[ -d "$d" ] && SCAN="$SCAN $d"
done
[ -n "$SCAN" ] || { echo "沒有 tools/ 也沒有 hooks/,無腳本可掃:$DOMAIN" >&2; exit 3; }
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
trap 'rm -f "$TMP"' EXIT
# shellcheck disable=SC2086
find $SCAN -type f -name '*.sh' 2>/dev/null | sort > "$TMP"
[ -s "$TMP" ] || { echo "tools/ 與 hooks/ 底下沒有 *.sh,無腳本可掃:$DOMAIN" >&2; exit 3; }
has_exit_stmt() { # $1=檔案;去掉整行註解後還找得到 exit 敘述就回 0
grep -v '^[[:space:]]*#' "$1" 2>/dev/null \
| grep -qE '(^|[;&|(){}[:space:]])exit([[:space:]]|$)'
}
is_library() { # $1=檔案;只被 source 的共用函式庫,可執行與結束碼兩項都豁免
# 判準兩條,缺一不可:
# 1. 自我宣告是函式庫——檔名 lib.sh,或檔頭前 30 行寫了「以 source 載入」。
# 2. 程式碼裡一個 exit 敘述都沒有。沒有 exit 就沒有結束碼語意,也就沒有東西可宣告;
# 這一條同時是「不是可執行入口」的證據,兩項豁免共用同一個事實。
# 為什麼不能只看第 1 條: 那是字樣比對,會命中「提到這件事」的腳本。本腳本自己的
# 例外段就寫了「以 source 載入」,plugins-root.sh 的用法段也寫了,但兩支都是有
# 結束碼語意的可執行入口。只拿第 1 條去豁免結束碼,等於把工具自己漏掉。
case "${1##*/}" in
lib.sh) ;;
*) head -30 "$1" 2>/dev/null | grep -q '以 source 載入' || return 1 ;;
esac
! has_exit_stmt "$1"
}
hit=0
total=0
libs=0
while IFS= read -r f; do
[ -n "$f" ] || continue
total=$((total + 1))
err=$(sh -n "$f" 2>&1) || {
printf '%s:語法:%s\n' "$f" "$(printf '%s' "$err" | tr '\n' ' ')"
hit=1
}
if is_library "$f"; then
libs=$((libs + 1))
printf '%s:函式庫:判定為只被 source 的共用函式庫(無 exit 敘述),略過「可執行」與「結束碼」\n' "$f" >&2
continue
fi
if [ ! -x "$f" ]; then
printf '%s:可執行:缺執行權限,請 chmod +x\n' "$f"
hit=1
fi
if ! head -60 "$f" 2>/dev/null | grep -qE '結束碼|exit code'; then
printf '%s:結束碼:檔頭沒有結束碼宣告列,呼叫端無法逐碼分流\n' "$f"
hit=1
fi
done < "$TMP"
if [ "$hit" -eq 0 ]; then
echo "腳本檢查通過:$total 支(語法、可執行、結束碼宣告),其中 $libs 支判定為函式庫" >&2
else
echo "腳本檢查有不合格項目:共掃 $total 支(其中 $libs 支判定為函式庫),清單見 stdout" >&2
fi
exit $hit