diff --git a/skills/doctor/SKILL.md b/skills/doctor/SKILL.md new file mode 100644 index 0000000..2d89224 --- /dev/null +++ b/skills/doctor/SKILL.md @@ -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}{本機}{遠端}{落後|最新|超前|查詢失敗}` per plugin, then `behind{count}`. + +A report with no `{domain}` row, or one carrying `noregistry{path}`, means this CLI has no local plugin registry. Report it as 無法驗證 — never as 最新. `behind0` 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 `itemscoperequiredactualexpectfixverdict`, closing with `summary{missing}{invalid}{unset}{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. diff --git a/templates/check-contents.md b/templates/check-contents.md new file mode 100644 index 0000000..fb4cb47 --- /dev/null +++ b/templates/check-contents.md @@ -0,0 +1,7 @@ +# 體檢目錄 + +> 由 `jsc-cli:doctor` 維護。每台執行環境一列;`HASH` 取 `{主機名}/{登入帳號}`,算法與其他頁面共用。 + +| 體檢頁 | 主機 | 帳號 | 必要項缺漏 | 設定錯誤 | 最後體檢 | +| --- | --- | --- | --- | --- | --- | +| [[CHECK_{HASH}]] | {hostname} | {使用者帳號} | {n} | {n} | {yyyy-MM-dd HH:mm} | diff --git a/templates/check-page.md b/templates/check-page.md new file mode 100644 index 0000000..a6de648 --- /dev/null +++ b/templates/check-page.md @@ -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} | diff --git a/tools/config-spec.tsv b/tools/config-spec.tsv new file mode 100644 index 0000000..bdcba2c --- /dev/null +++ b/tools/config-spec.tsv @@ -0,0 +1,66 @@ +# config-spec.tsv — jsc 技能組的設定規格表。體檢(/jsc-cli:doctor)與設定(/jsc-cli:setup)共用這一份。 +# +# 這張表是「必要或選擇」的唯一判準。掃描原始碼分不出必要與選擇,也分不出哪些是執行期內部 +# 變數,所以判準用手寫規格表,掃描只負責抓出漏登錄的項目(scan-config.sh orphans)。 +# 新增環境變數或設定檔時,同時補一列進來,體檢才看得到它。 +# +# 欄位:keykindscoperequireddefaultverifyfixdesc +# 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 檔路徑 diff --git a/tools/scan-config.sh b/tools/scan-config.sh new file mode 100755 index 0000000..dad88eb --- /dev/null +++ b/tools/scan-config.sh @@ -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,每行一項): +# itemscoperequiredactualexpectfixverdict +# verdict = ok 設定妥當 +# default 未設定,走預設值,可以正常運作 +# unset 選擇性項目未設定,沒有預設值,相關功能會降級 +# missing 必要項目缺了,相關技能跑不動 +# invalid 有值但驗不過(目錄不在、認證失敗、格式不對) +# skipped 離線模式略過,未取得結論 +# 最後固定一行 summary{missing 數}{invalid 數}{unset 數}{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