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
149 lines
13 KiB
Markdown
149 lines
13 KiB
Markdown
---
|
|
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, settings from tools/scan-config.sh against tools/config-spec.tsv, and orphan variables. Report one findings table per check, build the 待修項目 table with tools/build-todo.sh, then write the whole run to wiki CHECK_{HASH} where HASH comes from {short hostname}/{user}; the page keeps only the latest run, while its CHECK_CONTENTS row is upserted through wiki-contents.sh into the contents repo. 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.
|
|
|
|
The read-only contract is enforced in code, not by prose: every `jsc-hooks/tools/wire-cli.sh` call in this skill runs with `JSC_READONLY=1` in its environment. A mistyped subcommand then refuses to write instead of rewiring the machine that was only supposed to be measured.
|
|
|
|
## 1. Collect, four checks in parallel
|
|
|
|
Start all four collectors at once. They share no data, so serialising them only makes the check four times slower. Each one **MUST run as a sub agent**, and each returns its raw output lines unchanged — no summarising inside the sub agent, because step 2.2 parses those lines.
|
|
|
|
Done when all four sub agents have returned, and each has either its raw lines or an explicit failure reason.
|
|
|
|
### 1.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.
|
|
|
|
`report` only reads, so it exits 0 even when every lookup fails. Any non-zero exit means the script itself could not run: report the whole version check as 無法驗證 together with the exit code, and let the other three checks finish.
|
|
|
|
Done when every installed domain has a status literal, or the check is reported as unverifiable with its reason.
|
|
|
|
### 1.2 Hook wiring
|
|
|
|
First run `jsc-cli/tools/detect-clis.sh`. Exit 0 with at least one row → those are the CLIs to check. Exit 0 with no row → report the wiring table as empty and say no AI agent CLI was detected; that is a finding, not a pass. Any non-zero exit → report the wiring check as 無法驗證 with the exit code and stderr.
|
|
|
|
Then run `JSC_READONLY=1 jsc-hooks/tools/wire-cli.sh status {cli}` for every detected CLI. 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 | Meaning | Action |
|
|
| --- | --- | --- |
|
|
| 0 | wired | Record as wired |
|
|
| 1 | degraded | Record as degraded and quote the `reason` text |
|
|
| 2 | usage error | Stop this CLI's wiring check. The subcommand or the CLI name is wrong — report it as a defect in this skill, not as a machine fault |
|
|
| 3 | skipped | The CLI is not installed. Drop it from the wiring table |
|
|
| 5 | unwired | Record every `item` line whose state is `missing` |
|
|
| other | unexpected | Report that CLI's wiring as 無法驗證 with the exit code and stderr. Never read it as wired |
|
|
|
|
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 carries one of those verdicts and its missing items are listed.
|
|
|
|
### 1.3 Settings, global and project in one scan
|
|
|
|
Run `jsc-cli/tools/scan-config.sh scan all` from the current working directory. One call covers both scopes: the spec table is read once and the `scope` column separates the rows. It prints `{項目}<TAB>{範圍}<TAB>{必要}<TAB>{現況}<TAB>{說明}<TAB>{修法}<TAB>{判定}`, closing with `summary<TAB>{missing}<TAB>{invalid}<TAB>{unset}<TAB>{skipped}`.
|
|
|
|
| Exit | Action |
|
|
| --- | --- |
|
|
| 0 | Scan finished. Split the rows by the `範圍` column into a global table and a project table |
|
|
| 2 | Usage error. Report it as a defect in this skill and skip the settings check |
|
|
| 3 | The spec table is missing. Name the path it looked for and `JSC_CONFIG_SPEC`, then skip the settings check |
|
|
| other | Report the settings check as 無法驗證 with the exit code and stderr |
|
|
|
|
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.
|
|
|
|
Done when the summary line is read, the rows are split into the two scopes, and every `missing` and `invalid` row is named.
|
|
|
|
### 1.4 Orphan variables
|
|
|
|
Run `jsc-cli/tools/scan-config.sh orphans` — variables used in the source but absent from the spec table. Same exit codes as 1.3; exit 3 here also covers a missing plugins root, so name `JSC_PLUGINS_ROOT` in that case.
|
|
|
|
They are a maintenance note for the skill set, not a fault on this machine, so they never enter the 待修項目 table.
|
|
|
|
Done when the orphan list is returned or the check is reported as skipped with its reason.
|
|
|
|
## 2. Report the five blocks, then build the 待修項目 table
|
|
|
|
### 2.1 The five blocks
|
|
|
|
Report all five blocks per `templates/check-page.md`: 技能版本 from 1.1, Hook 接線 from 1.2, 全域設定 and 自我設定 from 1.3's two scopes, and 未登錄變數 from 1.4. Step 1.4 is the only place the orphan list is collected, so leaving it out here drops it from the run entirely — it never enters the 待修項目 table of 2.2, which is exactly why it needs its own block.
|
|
|
|
The project rows carry the working directory in their heading. A project-scope result is meaningless without it, because the answer changes with every `cd` — so name the directory that step 1.3 scanned, even when the project table is empty.
|
|
|
|
When `.env` or `.envrc` exists in that directory, 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.
|
|
|
|
### 2.2 The 待修項目 table
|
|
|
|
Save each collector's raw lines to a file, then run
|
|
|
|
`jsc-cli/tools/build-todo.sh --config {scan-all 輸出} --wiring {cli}={status 輸出} --version {report 輸出}`
|
|
|
|
one `--wiring` per detected CLI. The script merges the three sources and orders them `missing` → `invalid` → `unwired` → `落後`. Nothing wrong → it prints one row whose class reads 無.
|
|
|
|
| Exit | Action |
|
|
| --- | --- |
|
|
| 0 | Render the `todo` rows as the 待修項目 table and the `summary` line as 3.2's counts |
|
|
| 2 | Usage error. Report it as a defect in this skill; fall back to no 待修項目 table and say the merge did not run |
|
|
| 3 | An input file was unreadable. Name the file, rerun that one collector, and say so when it still fails |
|
|
| other | Report the merge as failed with the exit code, and keep 2.1's tables on screen |
|
|
|
|
A check that step 1 reported as 無法驗證 contributes no rows. Say that in the report: an unverified check and a clean check look identical in this table, and only the sentence tells them apart.
|
|
|
|
Done when all five blocks of 2.1 are on screen with the scanned directory stated — 未登錄變數 included, showing either its rows or the reason 1.4 gave for skipping — and 2.2's 待修項目 table is on screen with its rows in that order, or 2.2's merge failure is reported with its reason while 2.1's blocks stay on screen.
|
|
|
|
## 3. Record, then hand off
|
|
|
|
### 3.1 Record
|
|
|
|
This run writes two pages, and they live in **two different wiki repos**. Resolve each repo on its own and never reuse one for the other.
|
|
|
|
**The HASH — take it from the machine, do not compose it by hand.** `jsc-assist`'s `MONITOR_{HASH}` claims the same hash source and takes it in code, so the two pages only ever line up when this skill takes it the same way:
|
|
|
|
```sh
|
|
host=$(hostname 2>/dev/null || uname -n 2>/dev/null || printf 'unknown'); host=${host%%.*}
|
|
user=${USER:-$(id -un 2>/dev/null || printf 'unknown')}
|
|
```
|
|
|
|
`${host%%.*}` is the point of the snippet: `hostname` prints the FQDN on some machines and the short name on others, so a hand-written value gives one machine two pages that never merge again. Pass `"{host}/{user}"` to `gitea.sh hash-id` and use exactly what it prints. It is 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.
|
|
|
|
**`CHECK_{HASH}` — content page.** Repo: `jsc-gitea/tools/gitea.sh wiki-repo CHECK`. Write it through `jsc-gitea:wiki` and overwrite the whole page, because this page type keeps only the latest run of this one machine. Whole-page overwrite is correct here and forbidden on the page below.
|
|
|
|
**`CHECK_CONTENTS` — contents page, in the contents repo.** Repo: `gitea.sh wiki-repo CONTENTS`. Every contents page lives there now; it never falls back to `JSC_WIKI_REPO_CHECK`. Do not hand-edit it — write the row with
|
|
|
|
`jsc-gitea/tools/wiki-contents.sh upsert CHECK 4 "{HASH}" {row file} templates/check-contents.md`
|
|
|
|
The script reads the page back, replaces this machine's row or appends it, then writes the whole page. That keeps the rule in one place: one row per run, never a whole-page overwrite, never another machine's row — those rows are other people's records, and this run read them from nowhere else.
|
|
|
|
**The key is column 4, the bare `HASH`.** `4` is the 1-based index of the `HASH` column in `templates/check-contents.md`, and the key is the exact string `hash-id` printed — 40 uppercase hex characters, not shortened, not prefixed, not wrapped in a link. The script compares the whole cell, so the key and that cell must match character for character.
|
|
|
|
The key is the bare hash and not the link cell for a reason: a cell holding a URL changes whenever `GITEA_HOST` changes, whenever `JSC_WIKI_REPO_CHECK` moves to another repo, or whenever Gitea encodes the page name differently. The comparison then never matches, and every run appends another row for the same machine — silently, because the page still looks right.
|
|
|
|
Column 1 stays the human-facing link and is never the key. Build it as an **absolute URL** from `gitea.sh wiki-url {CHECK repo} CHECK_{HASH}`. `[[CHECK_{HASH}]]` resolves only inside its own wiki, and the two pages are no longer in the same one. Fetch the URL after `CHECK_{HASH}` is written: `wiki-url` exits 4 on a page that does not exist yet.
|
|
|
|
| Exit | Meaning | Action |
|
|
| --- | --- | --- |
|
|
| 0 | `updated` or `added` | Report which one it printed, with the repo and page it named |
|
|
| 1 | Write failed | Nothing landed. Report it with the stderr, and keep 2.1's tables on screen |
|
|
| 2 | Usage error | Report it as a defect in this skill. Do not retry with guessed arguments. A `templates/check-contents.md` that is not on disk also lands here — then name the path the script looked for, confirm the plugin install is complete, and rerun |
|
|
| 3 | No contents repo configured | Skip this write and put `JSC_WIKI_REPO_CONTENTS` at the top of 待修項目 |
|
|
| 4 | Page absent and no template given | Unreachable the way this skill calls the script — the command above always passes `templates/check-contents.md`. A template that is not on disk comes back as exit 2, not 4. So treat a 4 as a malformed call: report it as a defect in this skill, name the command that produced it, and do not retry with guessed arguments |
|
|
| 7 | Key invalid or no permission | Nothing was read and nothing written. Name the exit code and create nothing |
|
|
| 8 | Any other API failure | Same as 7: the old rows are unknown, so name the exit code and create nothing |
|
|
|
|
Exits 7 and 8 never mean the page is missing. Writing a fresh template over a directory whose rows were never read wipes every other machine's row, with no merge and no backup behind it — which is exactly why the script creates a page only when its own read reported that page absent, and why it owns that branch instead of this prose.
|
|
|
|
`wiki-repo` exiting 3 means that page type has no wiki repo configured: `JSC_WIKI_REPO_CHECK` for the content page, `JSC_WIKI_REPO_CONTENTS` for the contents page. Print the tables, skip that one write, and put the unset variable at the top of 待修項目 — it is itself a finding, so a failed write never fails the health check. Any other non-zero exit from `wiki-repo`, `wiki-url`, `hash-id` or the wiki write is reported the same way: tables on screen, write skipped, exit code named.
|
|
|
|
### 3.2 Hand off
|
|
|
|
State the four counts from 2.2's `summary` line: 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 each of the two pages is reported with its URL, or its skipped write is reported together with its reason, **and** the four counts are stated with the recommendation given or explicitly withheld.
|