What:CHECK_CONTENTS 改由 wiki-repo CONTENTS 解析並透過 wiki-contents.sh upsert
寫入,CHECK_{HASH} 仍走 wiki-repo CHECK。目錄頁新增一欄裸 HASH 當比對鍵。主機名
改由程式取短名,不再交給模型自由填。
Why:比對鍵原本是含網址的儲存格,換主機或換存取庫就比對不到,每跑一次體檢就替同一台
機器多附一列,畫面上還看不出來。主機名短名與 FQDN 不一致時,同一台機器會分裂成兩張頁,
而助理巡檢那邊是用程式取值的,兩邊對不起來。
How:設定規格表同一輪補齊三處既有缺漏——補上漏掉的 JSC_WIKI_REPO_MONITOR,體檢本來
看不到它而孤兒掃描還會誤報;刪掉指向不存在頁面的 MAINTAIN 內容頁字樣;MAINTAIN 那一列
改成不需要使用者處理,免得體檢叫人去設一支管不到任何頁的變數。整列保留,刪掉會讓孤兒
掃描開始誤報那個變數。
Who:jsc-cli
227 lines
9.8 KiB
Bash
Executable File
227 lines
9.8 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# scan-config.sh — 依 config-spec.tsv 盤點 jsc 技能組的設定現況。唯讀,不寫任何設定。
|
||
# 用法:
|
||
# scan-config.sh spec [global|project|all] # 印規格表(去掉註解與 internal 列)
|
||
# scan-config.sh scan [global|project|all] # 逐項檢查現況,印 TSV
|
||
# scan-config.sh orphans # 掃原始碼,找出沒登錄進規格表的變數
|
||
# 選項:
|
||
# -o 離線模式:需要連 Gitea 的檢查一律標 skipped,不發送請求
|
||
#
|
||
# scan 的輸出(TSV,每行一項):
|
||
# item<TAB>scope<TAB>required<TAB>actual<TAB>expect<TAB>fix<TAB>verdict
|
||
# verdict = ok 設定妥當
|
||
# default 未設定,走預設值,可以正常運作
|
||
# unset 選擇性項目未設定,沒有預設值,相關功能會降級
|
||
# missing 必要項目缺了,相關技能跑不動
|
||
# invalid 有值但驗不過(目錄不在、認證失敗、格式不對)
|
||
# skipped 離線模式略過,未取得結論
|
||
# 最後固定一行 summary<TAB>{missing 數}<TAB>{invalid 數}<TAB>{unset 數}<TAB>{skipped 數}
|
||
#
|
||
# 帶 TOKEN 的項目一律只印 set 或 unset,不印值:體檢報告會寫進 wiki,憑證不能跟著上去。
|
||
#
|
||
# 結束碼: 0=掃描完成(有沒有問題都算完成,判斷交給呼叫端) 2=用法錯誤 3=找不到規格表
|
||
set -eu
|
||
|
||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
SPEC="${JSC_CONFIG_SPEC:-$HERE/config-spec.tsv}"
|
||
TAB=$(printf '\t')
|
||
OFFLINE=0
|
||
|
||
[ -f "$SPEC" ] || { echo "找不到規格表:$SPEC(可用 JSC_CONFIG_SPEC 指定)" >&2; exit 3; }
|
||
|
||
usage() {
|
||
echo "用法:scan-config.sh [-o] {spec|scan|orphans} [global|project|all]" >&2
|
||
exit 2
|
||
}
|
||
|
||
case "${1:-}" in
|
||
-o) OFFLINE=1; shift ;;
|
||
esac
|
||
|
||
cmd="${1:-}"
|
||
scope_want="${2:-all}"
|
||
case "$cmd" in spec|scan|orphans) ;; *) usage ;; esac
|
||
case "$scope_want" in global|project|all) ;; *) usage ;; esac
|
||
|
||
# 找出 jsc-gitea 的 tools/gitea.sh。所有 Gitea 操作一律經由它(技能準則),不自行拼 API 呼叫。
|
||
# 找不到就回傳 1,呼叫端把需要連線的檢查標成 skipped,不讓整份體檢失敗。
|
||
gitea_sh() {
|
||
if [ -n "${JSC_GITEA_TOOLS:-}" ] && [ -f "$JSC_GITEA_TOOLS/gitea.sh" ]; then
|
||
printf '%s\n' "$JSC_GITEA_TOOLS/gitea.sh"; return 0
|
||
fi
|
||
_root="${CLAUDE_PLUGIN_ROOT:-$HERE/..}"
|
||
for _c in "$_root/../gitea/tools/gitea.sh" "$_root/../jsc-gitea/tools/gitea.sh"; do
|
||
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||
done
|
||
_c=$(ls -d "$_root"/../../jsc-gitea/*/tools/gitea.sh \
|
||
"$_root"/../../gitea/*/tools/gitea.sh \
|
||
"$HOME"/.claude/plugins/cache/*/jsc-gitea/*/tools/gitea.sh 2>/dev/null \
|
||
| sort | tail -n1)
|
||
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||
_c=$(command -v gitea.sh 2>/dev/null || true)
|
||
[ -n "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||
return 1
|
||
}
|
||
|
||
# 規格表的資料列(去註解、去空行)
|
||
spec_rows() { grep -v '^#' "$SPEC" | grep -v '^[[:space:]]*$'; }
|
||
|
||
# 這一列要不要納入本次掃描。$1=kind $2=scope
|
||
# internal 列只為登錄而存在(讓 orphans 認得出它們不是漏網之魚),永遠不進體檢報告。
|
||
row_wanted() {
|
||
[ "$1" != internal ] || return 1
|
||
[ "$scope_want" = all ] || [ "$2" = "$scope_want" ]
|
||
}
|
||
|
||
if [ "$cmd" = spec ]; then
|
||
spec_rows | while IFS="$TAB" read -r key kind scope required def verify fix desc; do
|
||
row_wanted "$kind" "$scope" || continue
|
||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$key" "$scope" "$required" "$def" "$verify" "$fix" "$desc"
|
||
done
|
||
exit 0
|
||
fi
|
||
|
||
if [ "$cmd" = orphans ]; then
|
||
# 規格表沒有的變數 = 有人加了設定卻忘了登錄。體檢照樣印出來,維護者才補得上。
|
||
root="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}"
|
||
[ -d "$root" ] || { echo "找不到 plugins 根目錄:$root(可用 JSC_PLUGINS_ROOT 指定)" >&2; exit 3; }
|
||
known=$(spec_rows | cut -f1 | sed 's/^\$//' | sort -u)
|
||
# 前面加詞邊界,否則帶別種前綴的變數(例如 PERSONA_ 開頭那批)會被從中間切出一段誤報。
|
||
# 這行註解本身也不寫出完整變數字面:掃描連自己的原始碼一起掃,寫了就會掃到自己。
|
||
grep -rhoE '\b(JSC|GITEA)_[A-Z0-9_]+' \
|
||
--include='*.sh' --include='*.md' --include='*.json' \
|
||
--exclude-dir=.git --exclude-dir=.jsc-monorepo-archive "$root" 2>/dev/null \
|
||
| sort | uniq -c | sort -rn \
|
||
| while read -r count name; do
|
||
# 原始碼寫的是樣板字面(JSC_WIKI_REPO_{TYPE}),抓出來會多一條尾巴底線;
|
||
# 去掉再比對,否則每次體檢都會多報一個不存在的變數。
|
||
name=${name%_}
|
||
printf '%s\n' "$known" | grep -qx "$name" && continue
|
||
# 只排掉樣板佔位字那一個名字,不做前綴比對:CONTENTS、MONITOR 這些是真的頁面類型,
|
||
# 排寬了就等於把整族存取庫變數從孤兒掃描裡挖掉,漏登錄再也看不出來。
|
||
case "$name" in JSC_WIKI_REPO_TYPE) continue ;; esac
|
||
printf 'orphan\t%s\t%s\n' "$name" "$count"
|
||
done
|
||
exit 0
|
||
fi
|
||
|
||
# --- scan ---
|
||
|
||
n_missing=0; n_invalid=0; n_unset=0; n_skipped=0
|
||
GITEA=$(gitea_sh 2>/dev/null || true)
|
||
|
||
# 連線類檢查的共用前置:離線、或找不到 gitea.sh,都直接標 skipped。
|
||
online_ready() {
|
||
[ "$OFFLINE" = 0 ] || return 1
|
||
[ -n "$GITEA" ] || return 1
|
||
}
|
||
|
||
# 值展開:規格表的檔案類 key 會帶 $JSC_HOME 這種變數,照字面找檔案永遠找不到。
|
||
# 開頭的 ~ 要先換成 $HOME:雙引號裡的 ~ 不展開,留著會讓 ~/.jsc 這種預設值一律驗不過。
|
||
# set +u 是必要的:預設值裡的 $JSC_HOME 常常正是「還沒設定」的那一個,
|
||
# 展開它在 set -u 底下會直接中斷整份掃描,體檢就停在半路。
|
||
expand() {
|
||
_e="$1"
|
||
case "$_e" in "~/"*) _e="$HOME/${_e#\~/}" ;; esac
|
||
# JSC_HOME 常常正是還沒設定的那一個,展開別列的 $JSC_HOME/... 卻要它有值;
|
||
# 只在這個子 shell 套用 JSC_HOME 規格列自己的預設值,不外流到主流程,
|
||
# 免得 JSC_HOME 那一列自己的檢查被連帶改成「已設定」。
|
||
( set +u; : "${JSC_HOME:=$HOME/.jsc}"; eval "printf '%s' \"$_e\"" ) 2>/dev/null
|
||
}
|
||
|
||
emit() { # item scope required actual expect fix verdict
|
||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$6" "$7"
|
||
case "$7" in
|
||
missing) n_missing=$((n_missing + 1)) ;;
|
||
invalid) n_invalid=$((n_invalid + 1)) ;;
|
||
unset) n_unset=$((n_unset + 1)) ;;
|
||
skipped) n_skipped=$((n_skipped + 1)) ;;
|
||
esac
|
||
}
|
||
|
||
# 對一個已取得的值跑 verify。印出 verdict(ok/invalid/skipped)。$1=verify $2=值
|
||
run_verify() {
|
||
case "$1" in
|
||
set|none) echo ok ;;
|
||
dir) [ -d "$2" ] && echo ok || echo invalid ;;
|
||
file) [ -f "$2" ] && echo ok || echo invalid ;;
|
||
gitea-api)
|
||
online_ready || { echo skipped; return; }
|
||
GITEA_HOST="$2" sh "$GITEA" api GET /version >/dev/null 2>&1 && echo ok || echo invalid ;;
|
||
gitea-auth)
|
||
online_ready || { echo skipped; return; }
|
||
sh "$GITEA" owners >/dev/null 2>&1 && echo ok || echo invalid ;;
|
||
wiki-repo)
|
||
case "$2" in
|
||
*/*)
|
||
online_ready || { echo ok; return; }
|
||
sh "$GITEA" default-branch "$2" >/dev/null 2>&1 && echo ok || echo invalid ;;
|
||
*) echo invalid ;;
|
||
esac ;;
|
||
*) echo ok ;;
|
||
esac
|
||
}
|
||
|
||
# 迴圈不可以放在管線右邊:那會變成子 shell,計數加不回來,summary 永遠是 0。
|
||
rows=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||
spec_rows > "$rows"
|
||
|
||
while IFS="$TAB" read -r key kind scope required def verify fix desc; do
|
||
[ -n "${key:-}" ] || continue
|
||
row_wanted "$kind" "$scope" || continue
|
||
|
||
if [ "$kind" = env ]; then
|
||
eval "val=\${$key:-}"
|
||
secret=0
|
||
case "$key" in *TOKEN*) secret=1 ;; esac
|
||
if [ -n "$val" ]; then
|
||
verdict=$(run_verify "$verify" "$val")
|
||
if [ "$secret" = 1 ]; then actual=set; else actual="$val"; fi
|
||
emit "$key" "$scope" "$required" "$actual" "$desc" "$fix" "$verdict"
|
||
continue
|
||
fi
|
||
# 未設定:有預設值就用預設值再驗一次,驗得過才算走得下去。
|
||
if [ "$def" != "-" ]; then
|
||
# 預設值寫成另一個變數名(JSC_WIKI_REPO_LOG 退回 JSC_WIKI_REPO)時,要跟去看那一個。
|
||
# 退路自己也空著卻回報「走預設值」,會讓體檢說得過去、實際上功能整個不能用。
|
||
case "$def" in
|
||
[A-Z]*)
|
||
if printf '%s' "$def" | grep -qx '[A-Z][A-Z0-9_]*'; then
|
||
eval "fallback=\${$def:-}"
|
||
if [ -z "$fallback" ]; then
|
||
emit "$key" "$scope" "$required" "未設定(退路 $def 也未設定)" "$desc" "$fix" unset
|
||
continue
|
||
fi
|
||
emit "$key" "$scope" "$required" "未設定(退回 $def=$fallback)" "$desc" "$fix" default
|
||
continue
|
||
fi ;;
|
||
esac
|
||
dval=$(expand "$def")
|
||
case "$verify" in
|
||
# 預設路徑還沒建立,算「尚未啟用」而不是「設錯了」:invalid 專指有值卻驗不過。
|
||
dir|file) if [ "$(run_verify "$verify" "$dval")" = ok ]; then verdict=default; else verdict=unset; fi ;;
|
||
*) verdict=default ;;
|
||
esac
|
||
emit "$key" "$scope" "$required" "未設定(預設 $def)" "$desc" "$fix" "$verdict"
|
||
continue
|
||
fi
|
||
[ "$required" = yes ] && verdict=missing || verdict=unset
|
||
emit "$key" "$scope" "$required" 未設定 "$desc" "$fix" "$verdict"
|
||
continue
|
||
fi
|
||
|
||
# kind=file:規格表的 key 本身就是路徑
|
||
path=$(expand "$key" 2>/dev/null || printf '%s' "$key")
|
||
if [ -e "$path" ]; then
|
||
emit "$key" "$scope" "$required" "$path" "$desc" "$fix" ok
|
||
elif [ "$required" = yes ]; then
|
||
emit "$key" "$scope" "$required" 不存在 "$desc" "$fix" missing
|
||
else
|
||
emit "$key" "$scope" "$required" 不存在 "$desc" "$fix" unset
|
||
fi
|
||
done < "$rows"
|
||
rm -f "$rows"
|
||
|
||
printf 'summary\t%s\t%s\t%s\t%s\n' "$n_missing" "$n_invalid" "$n_unset" "$n_skipped"
|
||
exit 0
|