diff --git a/skills/deploy/SKILL.md b/skills/deploy/SKILL.md index 50dc125..d295a2f 100644 --- a/skills/deploy/SKILL.md +++ b/skills/deploy/SKILL.md @@ -1,28 +1,89 @@ --- name: deploy -description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs via detect-clis.sh, report every plugin's local-versus-published version first and recommend update when any one of them is behind, then ask the user for the mode via decision tree, then run each CLI's native plugin commands with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. After install or update, write this machine's update and remove guides via write-guides.sh and demand a session restart. Use for rollout or removal of the jsc plugins; not for a single skill. +description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs, read the version recommendation and the marketplace domain list in parallel, then ask the user for the mode via decision tree unless the caller already passed one, then run each CLI's native plugin commands in parallel with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. After install or update, hand the detected CLI list to jsc-hooks:hooks-install, write this machine's update and remove guides via write-guides.sh, and demand a session restart. Use for rollout or removal of the jsc plugins; not for a single skill. --- # deploy — batch install, update, or uninstall the skill set +## Inputs a caller may pass + +`jsc-cli:setup` already confirmed the mode with the user and already holds a fresh version report. Re-asking and re-querying would put a second decision tree in front of someone who just answered it. + +| Input | Effect | +| --- | --- | +| mode (`install` / `update` / `uninstall`) | Step 3 skips the question and states which caller set the mode | +| version report | Step 1 skips its version collector; step 2 shows the report it was handed and names its source | + +Nothing passed in → run every step as written below. + ## Steps -1. Run `tools/detect-clis.sh` to find the installed CLIs and their executable paths. Done when the TSV lists at least one CLI with an executable path. -2. **Check versions before asking anything**, so the recommendation is based on fact rather than a guess: - 1. Run `jsc-hooks/hooks/version-guard.sh report`. It prints one line per installed jsc plugin — `{domain}{本機}{遠端}{落後|最新|超前|查詢失敗}` — and a final `behind{落後個數}`. - 2. **No local plugin registry → report this CLI as unverifiable, never as up to date.** Two forms of the same fact: a report carrying no `{domain}` row at all before the `behind` line, or the script's explicit no-registry line (`noregistry{路徑}`). Both mean the version check could not run for this CLI, so `behind0` here proves nothing. State that plainly and base no recommendation on it. - 3. Show that table to the user as-is. It is the evidence behind the recommendation, so never summarise it away. - 4. **`behind` ≥ 1 → mark `update` as the recommended option**, and name every domain that is behind together with its local and remote version. One domain behind is enough; do not wait for a majority. - 5. `behind` = 0 **with at least one domain row** → recommend nothing; present the three options neutrally. - 6. `查詢失敗` on any domain → say so explicitly. An unverified domain is not the same as an up-to-date one, and must not be counted as either. +1. Collect the three facts the rest of the run needs. They are independent, so start all three at once and wait for all three. - Done when the report is shown and either every domain row carries one of the four status literals `落後` `最新` `超前` `查詢失敗`, or the CLI is reported as having no local registry and therefore unverifiable. -3. Ask the user for the mode per the `jsc-ask:ask` rules: `install` / `update` / `uninstall`. Every option states its impact scope: which CLIs it touches and which configs it writes. Done when the user has named exactly one of `install`, `update` or `uninstall`. -4. Get the domain list (**never hardcode it**; this skill follows automatically when domains are added or removed): read `plugins[].name` from the unified marketplace via - `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`. - The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. Each `plugins[].name` already carries the `jsc-` prefix (e.g. `jsc-ask`) — pass it to `tools/deploy.sh` as-is, prefixed or not; the script normalizes it. Done when the domain list comes from that response and holds at least one name. -5. Run `tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent** (one sub agent per CLI). The script prints `cmd` and `exit` lines for every command, one `requires` line before each domain update, optional `compat` lines for Codex cache links, and one `result` line at the end; `-n` prints the commands without running them. On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. A missing or too-old required jsc plugin prints a `skip` line and leaves that domain untouched. Codex update preserves old `jsc-cli` and `jsc-hooks` cache version paths as symlinks to the newest installed version, so a still-running Codex deploy can keep using its helper scripts and a still-running Codex session whose hook_run_id points at the old cache can finish without `No such file`. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content. Done when every detected CLI has reported an exit status for every command it ran, and every skipped domain has a dependency reason or local-tree reason. -6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. The hook installer must refresh `$JSC_HOME/current/jsc-hooks` and must run `tools/wire-cli.sh smoke {cli}` for every detected CLI. Treat any `No such file` in those smoke results as a failed update and report it; do not let the deploy finish as successful when a rewritten hook path cannot execute. Done when hooks-install reports purge, wiring, smoke and scan results for each detected CLI, and every smoke result is either `status=ok` or explicitly reported as the update failure. -7. After install or update, run `tools/write-guides.sh {mode} {domain}...` **once for the whole machine**, after every CLI in step 5 has finished. It rewrites `$JSC_HOME/update-guide.md` and `$JSC_HOME/remove-guide.md` from the live detection result, so the later update and removal runs have the real commands for this machine. Skip it for `uninstall`: the guides describe an installed skill set. Done when the script printed a `wrote` line for both files. -8. Report the result and any failure reason for every CLI × mode, plus every `skip` line, every Codex `compat` line, and every CLI that could not be version-checked in step 2. Done when every detected CLI appears in the report with its `result` status. -9. For install or update, close the report with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. Name the two guide paths from step 7 in the same closing block, so the operator knows where this machine's update and removal commands now live. Done when the restart instruction is printed and both guide paths are named. + 1. **Installed CLIs** — `tools/detect-clis.sh`, printing `{name}{path}{version}`. Exit 0 with at least one row → take the CLI list from it. Exit 0 with no row → stop, and report that none of claude, codex, copilot, antigravity, kiro is installed. Any non-zero exit → stop and report the exit code and stderr; never guess a CLI list. + 2. **Version evidence and recommendation** — two subcommands of `jsc-hooks/hooks/version-guard.sh`, both needed, run together: `report` prints the per-plugin rows `{domain}{本機}{遠端}{落後|最新|超前|查詢失敗}` closing with `behind{count}`, and `recommend` prints one single line and nothing else — `recommend{update|none|unverifiable}`. `recommend` deliberately never reprints the table, so its second column stays readable by `cut`; the version table that steps 2, 3 and 7 show comes from `report`, and the conclusion comes from `recommend`. Skip this collector when the caller passed a version report. + 3. **Domain list** — read `plugins[].name` from the unified marketplace (**never hardcode it**; this skill then follows automatically when domains are added or removed): + `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`. + Exit 0 with at least one `plugins[].name` → use that list. Exit 0 with an empty or unparseable list → stop and report that the marketplace holds no plugin entry. Any non-zero exit → stop and report the exit code and stderr; a partial domain list would install a partial skill set and look successful. + + The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. Each `plugins[].name` already carries the `jsc-` prefix (e.g. `jsc-ask`) — pass it to `tools/deploy.sh` as-is, prefixed or not; the script normalizes it. + + Done when the CLI list holds at least one CLI, the domain list holds at least one name, and the recommendation is either in hand or explicitly inherited from the caller. + +2. Show the `report` version table to the user as-is. It is the evidence behind the recommendation, so never summarise it away. A report handed in by a caller is shown the same way, with a line naming that caller as its source. + + Branch on the `recommend` line: + + | Exit | `recommend` value | What it means and what to say | + | --- | --- | --- | + | 0 | `update` | At least one domain is behind. Mark `update` as the recommended option in step 3, and name every behind domain with its local and remote version. One domain behind is enough; do not wait for a majority | + | 0 | `none` | Every domain row is `最新` or `超前`. Recommend nothing; present the three options neutrally | + | 0 | `unverifiable` | The version check could not run — no local plugin registry, or every remote lookup failed. Say so plainly and base no recommendation on it. Unverified is not the same as up to date | + | 0 | no `recommend` line in the output at all | This jsc-hooks build has no `recommend` subcommand — an older `version-guard.sh` treats the argument as a hook invocation and exits 0 without printing anything. Derive the same three values yourself from the `report` table already collected in step 1.2: any `落後` row → `update`; no `{domain}` row at all, or a `noregistry` line → `unverifiable`; otherwise `none`. Say in step 7's report that the recommendation came from this fallback path, not from `recommend` | + | non-zero | — | Report the version check as `unverifiable` with the exit code and stderr, and carry on to step 3 without a recommendation | + + Any single domain row reading `查詢失敗` is called out by name even when the overall value is `none`. An unverified domain is not the same as an up-to-date one, and must not be counted as either. + + Done when the table is on screen and the recommendation is stated as exactly one of `update`, `none` or `unverifiable`. + +3. Ask the user for the mode per the `jsc-ask:ask` rules: `install` / `update` / `uninstall`. Every option states its impact scope: which CLIs it touches and which configs it writes. A caller that already passed a mode skips this step, and the report names that caller instead. + + Done when the user has named exactly one of `install`, `update` or `uninstall`, or the inherited mode is named with its source. + +4. Run `tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent**, one sub agent per CLI, and **all of them start together** — the CLIs write to separate plugin directories, so serialising them only adds up their install times. + + The script prints `cmd` and `exit` lines for every command, one `requires` line before each domain update, optional `compat` lines for Codex cache links, and one `result` line at the end; `-n` prints the commands without running them. + + | Exit | Action | + | --- | --- | + | 0 | Every command for that CLI succeeded. Record its `result` line | + | 1 | At least one command failed. Record that CLI as failed and quote every `exit` line whose code is non-zero | + | 2 | Usage error — the mode, the CLI name or the domain list is wrong. Report it as a defect in this skill, and do not retry with a guessed argument | + | other | Record that CLI as failed with the exit code and stderr | + + On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. A missing or too-old required jsc plugin prints a `skip` line and leaves that domain untouched. Codex update preserves old `jsc-cli` and `jsc-hooks` cache version paths as symlinks to the newest installed version, so a still-running Codex deploy can keep using its helper scripts and a still-running Codex session whose hook_run_id points at the old cache can finish without `No such file`. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content. + + Done when every detected CLI has reported an exit code and a `result` line, and every skipped domain has a dependency reason or a local-tree reason. + +5. After install or update, call `jsc-hooks:hooks-install` and **hand it the CLI list from step 1.1**, so it does not probe the same five executables a second time. `hooks-install` still detects for itself when it receives no list — that fallback is what keeps it usable on its own. + + Take its aggregate result rather than re-reading each CLI's smoke detail; the installer already judged purge, wiring, smoke and scan per CLI, and refreshes `$JSC_HOME/current/jsc-hooks` on the way — the path `deploy.sh` follows to reach `restart-gate.sh`. Keep exactly one extra judgement here, because it is a deploy-side fact the installer does not rule on: **a smoke result containing `No such file` is a failed update**, since it means a rewritten hook path cannot execute. Report it and do not let the deploy finish as successful. + + Done when hooks-install has returned an aggregate verdict for every CLI in the list, and every `No such file` in it is reported as an update failure. + +6. After install or update, run `tools/write-guides.sh {mode} {domain}...` **once for the whole machine**, after every CLI in step 4 has finished. It rewrites `$JSC_HOME/update-guide.md` and `$JSC_HOME/remove-guide.md` from the live detection result, so the later update and removal runs have the real commands for this machine. Skip it for `uninstall`: the guides describe an installed skill set. + + | Exit | Action | + | --- | --- | + | 0 | Both `wrote` lines printed. Name both paths in step 7 | + | 2 | Usage error — the mode or the domain list is wrong. Report it as a defect in this skill; the deploy itself still stands | + | 4 | `$JSC_HOME` or one of the two files could not be written. Name the path and the stderr, and say the machine has no up-to-date guide until this is fixed | + | other | Report the guide write as failed with the exit code, and say which of the two files did print a `wrote` line | + + Done when both `wrote` lines are printed, or the failure is reported with the exit code and the paths involved. + +7. Report the run and close it, in one block. The result and any failure reason for every CLI × mode, plus every `skip` line, every Codex `compat` line, and every CLI that could not be version-checked in step 2. + + For install or update, the same block ends with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. Name the two guide paths from step 6 in that same closing block, so the operator knows where this machine's update and removal commands now live. + + Done when every detected CLI appears in the report with its `result` status, and — for install or update — the restart instruction is printed with both guide paths named, or step 6's failure is repeated in their place. diff --git a/skills/doctor/SKILL.md b/skills/doctor/SKILL.md index 2d89224..82f293b 100644 --- a/skills/doctor/SKILL.md +++ b/skills/doctor/SKILL.md @@ -1,71 +1,119 @@ --- 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. +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 {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. +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. Skill versions +## 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}{本機}{遠端}{落後|最新|超前|查詢失敗}` 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. +`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. -## 2. Hook wiring +Done when every installed domain has a status literal, or the check is reported as unverifiable with its reason. -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. +### 1.2 Hook wiring -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. +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 has a status and its missing items are listed. +Done when every detected CLI carries one of those verdicts and its missing items are listed. -## 3. Global settings +### 1.3 Settings, global and project in one scan -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}`. +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 `{項目}{範圍}{必要}{現況}{說明}{修法}{判定}`, closing with `summary{missing}{invalid}{unset}{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. -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, the rows are split into the two scopes, and every `missing` and `invalid` row is named. -Done when the summary line is read and every `missing` and `invalid` row is named. +### 1.4 Orphan variables -## 4. Own settings +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. -Run `tools/scan-config.sh scan project` from the current working directory. Same output format, `scope=project` rows only. +They are a maintenance note for the skill set, not a fault on this machine, so they never enter the 待修項目 table. -Say which directory was scanned in the report. A project-scope result is meaningless without it, because the answer changes with every `cd`. +Done when the orphan list is returned or the check is reported as skipped with its reason. -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. +## 2. Report the five blocks, then build the 待修項目 table -Done when the scanned directory is stated and every project row has a verdict. +### 2.1 The five blocks -## 5. Report and record +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. -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 無. +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 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. +- `CHECK_{HASH}` is a **content page**: overwrite the whole page, because this page type keeps only the latest run of this one machine. +- `CHECK_CONTENTS` is a **contents page** and follows the opposite rule: read it back first, then upsert this machine's row from `templates/check-contents.md` in the same pass — add the row if missing, otherwise refresh its 必要項缺漏、設定錯誤、最後體檢 columns. Never overwrite the whole page, and never touch a row belonging to another machine: those rows are other people's records, and this run never read them from anywhere else. +- The `CHECK_CONTENTS` read branches by exit code, and only exit 4 opens the create path. Exit 0 means the page is there, so upsert into what came back. Exit 4 means the page really does not exist yet, so build it from the template. Exit 7 (key invalid or no permission) and exit 8 (any other API failure) both mean the old rows are unknown, never that the page is missing: skip the `CHECK_CONTENTS` write, name the exit code in the report, and create nothing. Writing a fresh template over a directory whose rows were never read wipes every other machine's row, and the write carries no merge and no backup. -`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. +`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. Any other non-zero exit from `wiki-repo`, `hash-id` or the wiki write is reported the same way: tables on screen, write skipped, exit code named. -Done when either the wiki page URL is reported, or the skipped write is reported together with the reason. +### 3.2 Hand off -## 6. 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. -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. +Done when either the wiki page URL is reported or the skipped write is reported together with its reason, **and** the four counts are stated with the recommendation given or explicitly withheld. diff --git a/skills/models/SKILL.md b/skills/models/SKILL.md index 7dacf48..505723d 100644 --- a/skills/models/SKILL.md +++ b/skills/models/SKILL.md @@ -7,10 +7,34 @@ description: List every model usable by each installed AI CLI (claude, codex, co ## Steps -1. Run `jsc-cli/tools/detect-clis.sh` to get the installed CLIs. Done when the TSV lists every detected CLI with its executable path. -2. Run `jsc-cli/tools/list-models.sh` to read each CLI's models and the model currently in use. It prints `climodelin-use` from each CLI's own config, and stays silent for a CLI whose config it cannot read. For every detected CLI it returns no rows for, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default). This step **MUST run as a sub agent**. Done when every detected CLI has a model list or is marked unreadable. +1. Start three collectors at once. They read different files and share no state, so the stage preference chain is fetched here rather than waited for at the end. + + 1. `jsc-cli/tools/detect-clis.sh` — the installed CLIs, as `{name}{path}{version}`. Exit 0 with at least one row → that is the CLI list. Exit 0 with no row → no AI agent CLI is installed on this machine: report that, name the five it probes, skip steps 2 to 4, and go straight to step 5, because the tag table and the stage requirements are still worth writing out. Any non-zero exit → stop and report the exit code and stderr. + 2. `jsc-cli/tools/list-models.sh` — each CLI's models and the model currently in use, as `climodelin-use`, read from each CLI's own config. It stays silent for a CLI whose config it cannot read and always exits 0; a non-zero exit means the script itself failed, so report the model inventory as 無法取得 with the exit code. This collector **MUST run as a sub agent**. + 3. `jsc-cli/tools/model-config.sh list` — one line per stage, `stagechainsource`, with `-` for unconfigured stages. Exit 0 → use the rows in step 6. Exit 2 → usage error, report it as a defect in this skill and show step 6's 階段偏好模型 table as 未取得. Any other exit → same handling, with the exit code named. + + Done when all three collectors have returned, and each has either its rows or an explicit failure reason. + +2. Reconcile the two lists. For every detected CLI that collector 1.2 returned no rows for, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default). Done when every detected CLI has either a model list from its config or a set of assumed defaults. + 3. Attach capability tags to every model per `references/model-tags.md`. A model missing from that table is not tagged by guesswork: add it to the table from the vendor's documentation, or queue it as a `jsc-ask:ask` question. Done when every listed model carries at least one tag and every unlisted model is either added to the table or queued as a `jsc-ask:ask` question. + 4. Output a table with four columns: CLI, model, tags, currently in use. Done when the table holds one row per model from step 2. -5. Run `tools/model-tags.sh sync` to write the tag table to `$JSC_HOME/model-tags.tsv`, and report the path. This file is what `jsc-hooks/hooks/sdlc-gate.sh` reads, so the SDLC gate stays broken until it exists. Done when the command prints the path. -6. Append the SDLC stage requirement table (plan and analyze need `reasoning-max`; implement needs `coding`; maintain accepts any), and state that gating is done in code by `sdlc-gate.sh lock {stage}` against the transcript's actual model id — **the models listed here are never allowed to self-assess their own tags**. Done when all four stages appear with their required tags. -7. Run `jsc-cli/tools/model-config.sh list` and append a 「階段偏好模型」 table right after the stage requirement table, with three columns: stage, chain, source (`project` / `global`). State below the table that the chain does **not** grant passage: it only names the model to suggest switching to when the gate blocks, and expresses preference among models that already satisfy the required tags. Done when the table shows all four stages, with `-` for unconfigured ones. + +5. Run `tools/model-tags.sh sync` to write the tag table to `$JSC_HOME/model-tags.tsv`. This file is what `jsc-hooks/hooks/sdlc-gate.sh` reads, so the SDLC gate stays broken until it exists. + + | Exit | Action | + | --- | --- | + | 0 | Report the path it printed | + | 1 | `references/model-tags.md` could not be parsed, or `$JSC_HOME` could not be written, so nothing was written. Name the reference path and the stderr, and state that the SDLC gate stays broken until this is fixed | + | 2 | Usage error — the subcommand or its arguments are wrong, and the script printed its usage line instead of running. Report it as a defect in this skill, and do not retry with a guessed argument. This is the same code the script uses for `UNKNOWN-MODEL` and `UNKNOWN-STAGE`, so it never means a model failed a requirement | + | other | Report the sync as failed with the exit code and stderr. Never report a path that was not printed | + + Done when the written path is reported, or the failure is reported with its exit code. + +6. Append the two stage tables, in this order. + + 1. The SDLC stage requirement table (plan and analyze need `reasoning-max`; implement needs `coding`; maintain accepts any), stating that gating is done in code by `sdlc-gate.sh lock {stage}` against the transcript's actual model id — **the models listed here are never allowed to self-assess their own tags**. + 2. A 「階段偏好模型」 table right after it, built from collector 1.3's rows, with three columns: stage, chain, source (`project` / `global`). State below the table that the chain does **not** grant passage: it only names the model to suggest switching to when the gate blocks, and expresses preference among models that already satisfy the required tags. + + Done when the requirement table shows all four stages with their required tags, and the 階段偏好模型 table shows the same four stages with `-` for unconfigured ones. diff --git a/templates/check-contents.md b/templates/check-contents.md index fb4cb47..1b80763 100644 --- a/templates/check-contents.md +++ b/templates/check-contents.md @@ -1,6 +1,8 @@ # 體檢目錄 > 由 `jsc-cli:doctor` 維護。每台執行環境一列;`HASH` 取 `{主機名}/{登入帳號}`,算法與其他頁面共用。 +> +> 寫入語意:一列代表一台執行環境,也就是一組主機加帳號。寫入前先讀回整頁,該執行環境已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。體檢頁 `CHECK_{HASH}` 只留最新一次結果、可以整頁改寫,這份目錄頁不行。 | 體檢頁 | 主機 | 帳號 | 必要項缺漏 | 設定錯誤 | 最後體檢 | | --- | --- | --- | --- | --- | --- | diff --git a/templates/check-page.md b/templates/check-page.md index a6de648..f4413fa 100644 --- a/templates/check-page.md +++ b/templates/check-page.md @@ -44,11 +44,12 @@ ## 待修項目 -> `/jsc-cli:setup` 從這張表接手。沒有待修項目時整張表寫一列「無」。 +> 前六欄直接來自 `jsc-cli/tools/build-todo.sh` 的 `todo` 列,順序與類別由那支腳本決定,這裡不另行排序。 +> 「影響」欄由 `jsc-cli:doctor` 補上。`/jsc-cli:setup` 從這張表接手;沒有待修項目時腳本會印一列「無」。 -| 順序 | 項目 | 範圍 | 判定 | 修法 | 影響 | -| --- | --- | --- | --- | --- | --- | -| {n} | {變數、檔案、hook 或 domain} | {全域、自我} | {缺漏、設錯、未接線、落後} | {自動、詢問、手動} | {不修的話哪些技能跑不動} | +| 順序 | 類別 | 項目 | 範圍 | 現況 | 修法 | 影響 | +| --- | --- | --- | --- | --- | --- | --- | +| {n} | {missing、invalid、unwired、落後、無} | {變數、檔案、接線項目或 plugin 名} | {global、project、cli 代號、版本} | {實際值、缺少接線的檔案或本機與遠端版本} | {auto、ask、manual 或接手的技能名} | {不修的話哪些技能跑不動} | ## 未登錄變數 diff --git a/tools/build-todo.sh b/tools/build-todo.sh new file mode 100755 index 0000000..73bae35 --- /dev/null +++ b/tools/build-todo.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env sh +# build-todo.sh — 把三支檢查腳本的輸出合併成一張「待修項目」表。 +# +# /jsc-cli:doctor 與 /jsc-cli:setup 都要這張表,合併規則只留一個真實來源, +# 兩支技能各寫一次就會各自漂移,一邊排序、另一邊漏掉某一類。 +# +# 用法: +# build-todo.sh [--config {檔案}]... [--wiring {cli}={檔案}]... [--version {檔案}]... +# 每個選項都可以重複。檔案給「-」代表讀標準輸入(整份只能有一個 -)。 +# 三種輸入都省略時視為用法錯誤:空跑會印出「沒有待修項目」,那是假通過。 +# +# 輸入格式(由各自的腳本產生,本腳本不自己執行它們,維持唯讀): +# --config jsc-cli/tools/scan-config.sh scan 的輸出 +# {項目}{範圍}{必要}{現況}{說明}{修法}{判定} +# 只取判定為 missing 與 invalid 的列,summary 列略過。 +# --wiring jsc-hooks/tools/wire-cli.sh status {cli} 的輸出,前面掛上該 CLI 代號。 +# 首行 status=... reason=...;其後 item{項目}{路徑}{present|missing} +# 只有 status=unwired 才進待修表,每個 missing 項目一列。 +# degraded 是 codex、copilot、antigravity、kiro 的健康狀態,不是缺失。 +# --version jsc-hooks/hooks/version-guard.sh report 的輸出 +# {domain}{本機}{遠端}{落後|最新|超前|查詢失敗} +# 只取「落後」的列。noregistry 與 behind 列略過。 +# +# 輸出(TSV,一行一筆): +# todo{序號}{類別}{項目}{範圍}{現況}{修法} +# summary{missing 數}{invalid 數}{unwired 數}{落後數} +# 類別排序固定為 missing、invalid、unwired、落後;同類別內照輸入順序。 +# 一項都沒有時仍印一列 todo,類別欄為「無」,呼叫端照樣有東西可以呈現。 +# +# 結束碼:0=合併完成(有沒有待修項目都算完成,判斷交給呼叫端) +# 2=用法錯誤(沒給任何輸入、選項寫錯、--wiring 少了 {cli}= 前綴) +# 3=指定的輸入檔讀不到 +set -u + +TAB=$(printf '\t') + +usage() { + [ -n "${WORK:-}" ] && rm -rf "$WORK" + echo "用法:build-todo.sh [--config {檔案}]... [--wiring {cli}={檔案}]... [--version {檔案}]..." >&2 + exit 2 +} + +WORK=$(mktemp -d 2>/dev/null) || { echo "無法建立暫存目錄" >&2; exit 3; } +F_MISSING="$WORK/missing"; F_INVALID="$WORK/invalid" +F_UNWIRED="$WORK/unwired"; F_BEHIND="$WORK/behind" +: > "$F_MISSING"; : > "$F_INVALID"; : > "$F_UNWIRED"; : > "$F_BEHIND" +cleanup() { rm -rf "$WORK"; } + +die() { cleanup; echo "$1" >&2; exit "$2"; } + +# 輸入檔的存在性先在主 shell 檢查。放進管線裡檢查的話,die 只會結束子 shell, +# 主流程照樣往下跑,最後印出一張少了整類項目卻看起來正常的表。 +check_input() { # $1=路徑 + [ "$1" = "-" ] && return 0 + [ -f "$1" ] || die "讀不到輸入檔:$1" 3 + [ -r "$1" ] || die "輸入檔沒有讀取權限:$1" 3 +} + +# 取得一份輸入的內容。「-」讀標準輸入,其餘一律當檔案路徑。 +slurp() { # $1=路徑 + if [ "$1" = "-" ]; then cat; else cat "$1"; fi +} + +# scan-config.sh scan 的輸出 → missing 與 invalid 兩類。 +take_config() { # $1=路徑 + slurp "$1" | while IFS="$TAB" read -r key scope req actual desc fix verdict; do + [ -n "${key:-}" ] || continue + [ "$key" = summary ] && continue + case "${verdict:-}" in + missing) printf '%s\t%s\t%s\t%s\n' "$key" "${scope:--}" "${actual:--}" "${fix:--}" >> "$F_MISSING" ;; + invalid) printf '%s\t%s\t%s\t%s\n' "$key" "${scope:--}" "${actual:--}" "${fix:--}" >> "$F_INVALID" ;; + esac + done +} + +# wire-cli.sh status 的輸出 → unwired 一類。$1=cli $2=路徑 +take_wiring() { + _txt="$WORK/wiring.$$" + slurp "$2" > "$_txt" + # 只有 status=unwired 才是待修。wired 沒事,degraded 是四支非 Claude CLI 的健康狀態, + # skipped 代表那支 CLI 根本沒裝,三者都不該出現在待修表上。 + grep -q '^status=unwired' "$_txt" || { rm -f "$_txt"; return 0; } + while IFS="$TAB" read -r kind item path state; do + [ "${kind:-}" = item ] || continue + [ "${state:-}" = missing ] || continue + printf '%s\t%s\t%s\t%s\n' "${item:--}" "$1" "缺少接線:${path:--}" "jsc-hooks:hooks-install" >> "$F_UNWIRED" + done < "$_txt" + rm -f "$_txt" +} + +# version-guard.sh report 的輸出 → 落後一類。 +take_version() { # $1=路徑 + slurp "$1" | while IFS="$TAB" read -r domain local_v remote_v state; do + [ -n "${domain:-}" ] || continue + case "$domain" in behind|noregistry) continue ;; esac + [ "${state:-}" = "落後" ] || continue + printf '%s\t%s\t%s\t%s\n' "jsc-$domain" "版本" "本機 ${local_v:--}、遠端 ${remote_v:--}" "jsc-cli:deploy update" >> "$F_BEHIND" + done +} + +got=0 +while [ "$#" -gt 0 ]; do + case "$1" in + --config) [ "$#" -ge 2 ] || usage; check_input "$2"; take_config "$2"; got=1; shift 2 ;; + --version) [ "$#" -ge 2 ] || usage; check_input "$2"; take_version "$2"; got=1; shift 2 ;; + --wiring) + [ "$#" -ge 2 ] || usage + case "$2" in *=*) ;; *) usage ;; esac + _cli=${2%%=*}; _file=${2#*=} + [ -n "$_cli" ] && [ -n "$_file" ] || usage + check_input "$_file"; take_wiring "$_cli" "$_file"; got=1; shift 2 ;; + *) usage ;; + esac +done +[ "$got" = 1 ] || usage + +n_missing=$(wc -l < "$F_MISSING" | tr -d ' ') +n_invalid=$(wc -l < "$F_INVALID" | tr -d ' ') +n_unwired=$(wc -l < "$F_UNWIRED" | tr -d ' ') +n_behind=$(wc -l < "$F_BEHIND" | tr -d ' ') + +seq_no=0 +emit_class() { # $1=類別 $2=檔案 + while IFS="$TAB" read -r item scope actual fix; do + [ -n "${item:-}" ] || continue + seq_no=$((seq_no + 1)) + printf 'todo\t%s\t%s\t%s\t%s\t%s\t%s\n' "$seq_no" "$1" "$item" "$scope" "$actual" "$fix" + done < "$2" +} + +emit_class missing "$F_MISSING" +emit_class invalid "$F_INVALID" +emit_class unwired "$F_UNWIRED" +emit_class 落後 "$F_BEHIND" + +[ "$seq_no" -gt 0 ] || printf 'todo\t1\t無\t沒有待修項目\t-\t-\t-\n' +printf 'summary\t%s\t%s\t%s\t%s\n' "$n_missing" "$n_invalid" "$n_unwired" "$n_behind" + +cleanup +exit 0 diff --git a/tools/config-spec.tsv b/tools/config-spec.tsv index 5dc1d64..74d6dc5 100644 --- a/tools/config-spec.tsv +++ b/tools/config-spec.tsv @@ -33,6 +33,7 @@ JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前 JSC_VERSION_TTL env global no 600 set ask 版本查詢快取秒數 JSC_RESTART_GATE env global no on set ask 設成 off 可略過部署後的重啟提示閘門,判讀在 jsc-hooks JSC_WIKI_REPO_SKILLSET env global no JSC_WIKI_REPO wiki-repo ask SKILLSET_CONTENTS、SKILLSET_{HASH} 所在的 {owner}/{repo},技能組異動報告寫在這裡 +JSC_WIKI_REPO_TOOLING env global no JSC_WIKI_REPO wiki-repo ask TOOLING_CONTENTS、TOOLING_{HASH} 所在的 {owner}/{repo},技能盤點寫在這裡 JSC_LANG_GUARD env global no on set ask 設成 off 可關閉繁中編碼與簡體字守門,誤判時用 JSC_COMMENT_SCOPE env global no on set ask 設成 off 可關閉註解夾帶文件編號的守門,誤判時用 JSC_CHANGED_FILE internal runtime no - none - 非 Claude CLI 傳入的變更檔路徑,註解範圍與繁中編碼守門讀它