feat/cli-doctor-and-setup-skills #13
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: doctor
|
||||
description: Health-check the execution environment in one pass and record the result, changing nothing. Four checks - plugin versions from jsc-hooks/hooks/version-guard.sh report, hook wiring from jsc-hooks/tools/wire-cli.sh status, global settings and current-directory settings from tools/scan-config.sh against tools/config-spec.tsv. Report one findings table per check, then write the whole run to wiki CHECK_{HASH} where HASH comes from {hostname}/{user}; the page keeps only the latest run. Use after installing or updating the skill set, when a skill fails on a settings or wiring problem, or before handing a machine over; not for applying fixes, which is jsc-cli:setup.
|
||||
---
|
||||
|
||||
# doctor — execution environment health check
|
||||
|
||||
Read-only. Every command below either reads a file or asks Gitea; none of them writes a setting. That is the contract with `jsc-cli:setup`: doctor states the facts, setup changes things.
|
||||
|
||||
Collection (steps 1 to 4) **MUST run as a sub agent** — one sub agent for all four, returning the raw TSV lines. Only the report and the wiki write stay in the main agent.
|
||||
|
||||
## 1. Skill versions
|
||||
|
||||
Run `jsc-hooks/hooks/version-guard.sh report`. It prints `{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}` per plugin, then `behind<TAB>{count}`.
|
||||
|
||||
A report with no `{domain}` row, or one carrying `noregistry<TAB>{path}`, means this CLI has no local plugin registry. Report it as 無法驗證 — never as 最新. `behind<TAB>0` proves nothing when no domain row precedes it.
|
||||
|
||||
Done when every installed domain has a status literal, or the CLI is reported as unverifiable.
|
||||
|
||||
## 2. Hook wiring
|
||||
|
||||
Run `jsc-hooks/tools/wire-cli.sh status {cli}` for every CLI that `tools/detect-clis.sh` found. Use `status` and nothing else: `wire-cli.sh` without a subcommand rewires, `purge` deletes, and `smoke` executes hooks — all three break the read-only contract.
|
||||
|
||||
Exit codes: 0 wired, 1 degraded, 3 skipped (CLI not installed), 5 unwired. Each `item` line names one wiring point and whether it is present.
|
||||
|
||||
Only claude reaches `wired`. The other four have no pre-tool hook, so `degraded` is their healthy state — report the degradation reason as-is and never present it as a defect to fix.
|
||||
|
||||
Done when every detected CLI has a status and its missing items are listed.
|
||||
|
||||
## 3. Global settings
|
||||
|
||||
Run `tools/scan-config.sh scan global`. It checks every `scope=global` row of `tools/config-spec.tsv` and prints `item<TAB>scope<TAB>required<TAB>actual<TAB>expect<TAB>fix<TAB>verdict`, closing with `summary<TAB>{missing}<TAB>{invalid}<TAB>{unset}<TAB>{skipped}`.
|
||||
|
||||
Verdicts: `ok`, `default` (unset, default works), `unset` (optional, feature degrades), `missing` (required, skills break), `invalid` (set but fails verification), `skipped` (offline).
|
||||
|
||||
Add `-o` when Gitea is unreachable; the Gitea-dependent rows then come back `skipped`. Report those rows as 未取得結論 and never as passes.
|
||||
|
||||
Also run `tools/scan-config.sh orphans` — variables used in the source but absent from the spec table. They are a maintenance note for the skill set, not a fault on this machine.
|
||||
|
||||
Done when the summary line is read and every `missing` and `invalid` row is named.
|
||||
|
||||
## 4. Own settings
|
||||
|
||||
Run `tools/scan-config.sh scan project` from the current working directory. Same output format, `scope=project` rows only.
|
||||
|
||||
Say which directory was scanned in the report. A project-scope result is meaningless without it, because the answer changes with every `cd`.
|
||||
|
||||
When `.env` or `.envrc` exists, name the spec-table variables it overrides and state the value actually in effect. A global setting silently overridden here is the failure this check exists to catch.
|
||||
|
||||
Done when the scanned directory is stated and every project row has a verdict.
|
||||
|
||||
## 5. Report and record
|
||||
|
||||
Report all four tables per `templates/check-page.md`. Then build the 待修項目 table from every `missing`, `invalid` and `unwired` item, plus every domain reported 落後. Order them `missing` → `invalid` → `unwired` → `落後`. Nothing wrong → one row reading 無.
|
||||
|
||||
Write the page through `jsc-gitea:wiki`:
|
||||
|
||||
- Wiki repo: `jsc-gitea/tools/gitea.sh wiki-repo CHECK`.
|
||||
- Page name: `CHECK_` plus `gitea.sh hash-id "{hostname}/{user}"` — the host and the login account, not `{owner}/{repo}`. Doctor checks a machine, and it has to work in directories that are not repositories at all.
|
||||
- Overwrite the whole page. This page type keeps only the latest run.
|
||||
- Update `CHECK_CONTENTS` from `templates/check-contents.md` in the same pass.
|
||||
|
||||
`wiki-repo` exiting 3 means no wiki repo is configured for CHECK. Print the tables, skip the wiki write, and put `JSC_WIKI_REPO_CHECK` at the top of 待修項目 — that unset variable is itself a finding, so a failed write never fails the health check.
|
||||
|
||||
Done when either the wiki page URL is reported, or the skipped write is reported together with the reason.
|
||||
|
||||
## 6. Hand off
|
||||
|
||||
State the counts: required items missing, settings invalid, CLIs unwired, domains behind. Recommend `/jsc-cli:setup` when any of those is above zero. Never fix anything here.
|
||||
|
||||
Done when the counts are stated and the recommendation is given or explicitly withheld.
|
||||
@@ -0,0 +1,7 @@
|
||||
# 體檢目錄
|
||||
|
||||
> 由 `jsc-cli:doctor` 維護。每台執行環境一列;`HASH` 取 `{主機名}/{登入帳號}`,算法與其他頁面共用。
|
||||
|
||||
| 體檢頁 | 主機 | 帳號 | 必要項缺漏 | 設定錯誤 | 最後體檢 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| [[CHECK_{HASH}]] | {hostname} | {使用者帳號} | {n} | {n} | {yyyy-MM-dd HH:mm} |
|
||||
@@ -0,0 +1,59 @@
|
||||
# 執行環境體檢 — {hostname}/{使用者帳號}
|
||||
|
||||
> 由 `jsc-cli:doctor` 維護。這是體檢頁 `CHECK_{HASH}`,只保留最新一次結果,重跑就整頁覆寫。
|
||||
> 修復請執行 `/jsc-cli:setup`,它讀這頁的「待修項目」逐項處理。
|
||||
|
||||
- 體檢時間:{yyyy-MM-dd HH:mm}
|
||||
- 工作目錄:{絕對路徑}
|
||||
- 執行 CLI:{claude、codex、copilot、antigravity、kiro 五選一}
|
||||
|
||||
## 結論
|
||||
|
||||
| 分類 | 通過 | 待修 | 略過 |
|
||||
| --- | --- | --- | --- |
|
||||
| 技能版本 | {n} | {n} | {n} |
|
||||
| Hook 接線 | {n} | {n} | {n} |
|
||||
| 全域設定 | {n} | {n} | {n} |
|
||||
| 自我設定 | {n} | {n} | {n} |
|
||||
|
||||
## 技能版本
|
||||
|
||||
| Domain | 本機 | 遠端 | 狀態 |
|
||||
| --- | --- | --- | --- |
|
||||
| {domain} | {版本} | {版本} | {落後、最新、超前、查詢失敗、無法驗證} |
|
||||
|
||||
## Hook 接線
|
||||
|
||||
| CLI | 狀態 | 缺漏項目 | 說明 |
|
||||
| --- | --- | --- | --- |
|
||||
| {cli} | {wired、degraded、unwired、skipped} | {項目名,逗號分隔;無則寫「無」} | {降級原因或未偵測到執行檔} |
|
||||
|
||||
## 全域設定
|
||||
|
||||
| 項目 | 必要 | 現況 | 期望 | 修法 | 判定 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| {變數或檔案} | {是、否} | {實際值或未設定} | {該是什麼} | {自動、詢問、手動} | {通過、走預設、未設定、缺漏、設錯、略過} |
|
||||
|
||||
## 自我設定
|
||||
|
||||
> 工作目錄:{絕對路徑}
|
||||
|
||||
| 項目 | 必要 | 現況 | 期望 | 修法 | 判定 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| {變數或檔案} | {是、否} | {實際值或未設定} | {該是什麼} | {自動、詢問、手動} | {通過、走預設、未設定、缺漏、設錯、略過} |
|
||||
|
||||
## 待修項目
|
||||
|
||||
> `/jsc-cli:setup` 從這張表接手。沒有待修項目時整張表寫一列「無」。
|
||||
|
||||
| 順序 | 項目 | 範圍 | 判定 | 修法 | 影響 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| {n} | {變數、檔案、hook 或 domain} | {全域、自我} | {缺漏、設錯、未接線、落後} | {自動、詢問、手動} | {不修的話哪些技能跑不動} |
|
||||
|
||||
## 未登錄變數
|
||||
|
||||
> 原始碼有用到、`config-spec.tsv` 沒登錄的變數。體檢不判定它們,只提醒維護者補登錄。
|
||||
|
||||
| 變數 | 出現次數 |
|
||||
| --- | --- |
|
||||
| {變數名} | {n} |
|
||||
@@ -0,0 +1,66 @@
|
||||
# config-spec.tsv — jsc 技能組的設定規格表。體檢(/jsc-cli:doctor)與設定(/jsc-cli:setup)共用這一份。
|
||||
#
|
||||
# 這張表是「必要或選擇」的唯一判準。掃描原始碼分不出必要與選擇,也分不出哪些是執行期內部
|
||||
# 變數,所以判準用手寫規格表,掃描只負責抓出漏登錄的項目(scan-config.sh orphans)。
|
||||
# 新增環境變數或設定檔時,同時補一列進來,體檢才看得到它。
|
||||
#
|
||||
# 欄位:key<TAB>kind<TAB>scope<TAB>required<TAB>default<TAB>verify<TAB>fix<TAB>desc
|
||||
# kind env=環境變數 file=檔案或目錄 internal=執行期內部變數(體檢略過,只為登錄而存在)
|
||||
# scope global=整台機器 project=當前工作目錄 runtime=hook 執行當下才存在
|
||||
# required yes=缺了就有技能跑不動 no=選擇性,缺了走預設或降級
|
||||
# default 未設定時的實際值;沒有預設寫 -
|
||||
# verify set=有值即可 dir=目錄要在 file=檔案要在 gitea-api=站台連得上
|
||||
# gitea-auth=認證過得了 wiki-repo=值可解析成 {owner}/{repo} none=不驗
|
||||
# fix auto=工具算得出,可直接寫入 ask=值要人給,問完才寫 manual=只能人手動處理 -=不需修
|
||||
# desc 一句繁中說明,直接印給使用者看
|
||||
#
|
||||
GITEA_HOST env global yes - gitea-api ask Gitea 站台位址,所有 wiki 與 PR 操作的去處
|
||||
GITEA_TOKEN env global yes - gitea-auth ask Gitea API token;未設定時退回 tea CLI 的登入金鑰
|
||||
JSC_HOME env global no ~/.jsc dir auto hook 資料目錄,放工作階段計時、用量統計、版本快取
|
||||
JSC_WIKI_REPO env global no - wiki-repo ask 未逐類設定時的共用 wiki {owner}/{repo}
|
||||
JSC_WIKI_REPO_QUESTION env global no JSC_WIKI_REPO wiki-repo ask QUESTION_CONTENTS、QUESTION_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_PLAN env global no JSC_WIKI_REPO wiki-repo ask PLAN_CONTENTS、PLAN_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_ANALYZE env global no JSC_WIKI_REPO wiki-repo ask ANALYZE_CONTENTS、ANALYZE_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_DELIVER env global no JSC_WIKI_REPO wiki-repo ask DELIVER_CONTENTS、DELIVER_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_MAINTAIN env global no JSC_WIKI_REPO wiki-repo ask MAINTAIN_CONTENTS、MAINTAIN_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_REPO env global no JSC_WIKI_REPO wiki-repo ask REPO_CONTENTS、REPO_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_LOG env global no JSC_WIKI_REPO wiki-repo ask LOG_CONTENTS、LOG_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_LEARN env global no JSC_WIKI_REPO wiki-repo ask LEARN_CONTENTS、LEARN_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_ERROR env global no JSC_WIKI_REPO wiki-repo ask ERROR_CONTENTS、ERROR_{HASH} 所在存取庫
|
||||
JSC_WIKI_REPO_CHECK env global no JSC_WIKI_REPO wiki-repo ask CHECK_CONTENTS、CHECK_{HASH} 所在存取庫,體檢紀錄寫在這裡
|
||||
JSC_WIKI_REPO_REPORT env global no JSC_WIKI_REPO wiki-repo ask REPORT_CONTENTS、REPORT_{HASH} 所在存取庫,年月週日報表寫在這裡
|
||||
JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前置檢查,離線工作時用
|
||||
JSC_VERSION_TTL env global no 600 set ask 版本查詢快取秒數
|
||||
JSC_LOCAL_PLUGINS env global no $JSC_HOME/plugins dir auto antigravity 安裝來源的本地 plugin 目錄
|
||||
JSC_PLUGINS_ROOT env global no - dir ask 技能組工作目錄根位置,jsc-meta 的工具用它找各 domain
|
||||
JSC_CLAUDE_SETTINGS_DIR env global no ~/.claude dir ask claude 使用者層設定檔目錄,測試 purge 時才需覆寫
|
||||
JSC_COPILOT_INSTRUCTIONS env global no ~/.config/copilot/copilot-instructions.md file ask copilot 指引檔位置,STE100 規則段落寫在這裡
|
||||
JSC_ANTIGRAVITY_RULES env global no ~/.antigravity/AGENTS.md file ask antigravity 全域規則檔位置
|
||||
JSC_KIRO_SKILLS env global no - dir ask kiro 技能目錄,接線與部署都會用到
|
||||
JSC_GITEA_TOOLS env global no - dir ask jsc-gitea/tools 的位置,跨 domain 呼叫 gitea.sh 時用
|
||||
$JSC_HOME/model-tags.tsv file global yes - file auto SDLC 階段閘門讀的能力標籤表,由 /jsc-cli:models 產生
|
||||
$JSC_HOME/models.conf file global no - file ask 各 SDLC 階段的偏好模型鏈,專案 .jsc/models 可覆寫
|
||||
$JSC_HOME/html-styles.conf file global no - file ask 各類頁面的 HTML 匯出版型與樣式,專案 .jsc/html-styles 可覆寫
|
||||
.jsc/models file project no - file ask 本專案的 SDLC 偏好模型鏈,覆寫 $JSC_HOME/models.conf
|
||||
.jsc/html-styles file project no - file ask 本專案的 HTML 匯出版型與樣式,覆寫 $JSC_HOME/html-styles.conf
|
||||
.claude/settings.json file project no - file manual claude 的專案層設定;jsc hook 由 plugin 的 hooks.json 接線,這裡不該有 jsc 殘留接線
|
||||
AGENTS.md file project no - file manual codex 與 antigravity 讀的專案層指引檔
|
||||
.env file project no - none manual 專案層環境變數;覆寫全域設定時,體檢會標出實際生效值
|
||||
.envrc file project no - none manual direnv 設定檔;覆寫全域設定時,體檢會標出實際生效值
|
||||
JSC_CLI internal runtime no - none - 接線時帶入的 CLI 代號,hook 用來分辨宿主
|
||||
JSC_SKILL internal runtime no - none - 目前呼叫的技能名,版本前置檢查與用量統計用
|
||||
JSC_SESSION_ID internal runtime no - none - 工作階段代號,計時與 token 統計用
|
||||
JSC_TOOL_NAME internal runtime no - none - 目前的工具名,PreToolUse hook 用來比對 matcher
|
||||
JSC_SCRIPT_DIR internal runtime no - none - 呼叫端腳本所在目錄,由 lib.sh 算出
|
||||
JSC_HOOKS_DIR internal runtime no - none - hooks 目錄位置,包裝啟動器用
|
||||
JSC_MODEL internal runtime no - none - 目前模型 id,SDLC 閘門用來比對能力標籤
|
||||
JSC_GITEA_OWNER internal runtime no - none - 批次同步存取庫時鎖定的 owner
|
||||
JSC_DEPLOY_DRYRUN internal runtime no - none - 部署試跑旗標,只印指令不執行
|
||||
JSC_WP_GATE internal runtime no - none - 工作包閘門的逃生門,實作在 jsc-sdlc/tools/wp-gate.sh
|
||||
JSC_CONFIG_SPEC internal runtime no - none - 改讀別份設定規格表,測試 scan-config.sh 時用
|
||||
JSC_SYNC_DRY_RUN internal runtime no - none - 同步 domain 存取庫的試跑旗標,只印不動檔案
|
||||
JSC_MK_DOMAIN internal runtime no - none - sync-marketplace.sh 傳給 python 的 domain 名
|
||||
JSC_MK_URL internal runtime no - none - sync-marketplace.sh 傳給 python 的存取庫網址
|
||||
JSC_MK_DESC internal runtime no - none - sync-marketplace.sh 傳給 python 的 plugin 描述
|
||||
JSC_MK_IN internal runtime no - none - sync-marketplace.sh 讀入的 marketplace 檔路徑
|
||||
JSC_MK_OUT internal runtime no - none - sync-marketplace.sh 寫出的 marketplace 檔路徑
|
||||
|
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/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
|
||||
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
|
||||
( set +u; 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
|
||||
Reference in New Issue
Block a user