From 26ffaa8a07d168bbc6e7bbc69f139210a1a03626 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 25 Aug 2026 14:58:54 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(cli):=20=E8=A3=9C=E9=BD=8A=E7=A8=BD?= =?UTF-8?q?=E6=A0=B8=E7=BC=BA=E5=A4=B1=E4=B8=A6=E4=BF=AE=E6=8E=89=E8=AD=B7?= =?UTF-8?q?=E6=AC=84=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What:依 jsc-meta:skill-check 的稽核結果修正技能與工具——補上每個步驟的可檢核完成條件、 把留在內文的標準輸入輸出流程下放 tools/、修正查表與退碼路由造成的誤判。 Why:稽核發現這些缺失會讓技能在實際執行時走錯分支或靜默通過。 完成條件缺漏是最常被違反的一項;退碼誤判與查表錯誤則會讓良性狀況被當成失敗。 How:逐項對照 references/guidelines.md 的審核檢查清單修正,新增的工具都有 documented exit codes,並以真實執行驗證每條路徑。 Who:jsc-meta:skill-check 例行稽核(2026-08-25)。 Co-Authored-By: Claude Opus 5 --- skills/deploy/SKILL.md | 32 +++--- skills/models/SKILL.md | 22 ++-- tools/deploy.sh | 226 +++++++++++++++++++++++++++++++++++++++++ tools/list-models.sh | 83 +++++++++++++++ tools/model-config.sh | 14 +-- 5 files changed, 335 insertions(+), 42 deletions(-) create mode 100755 tools/deploy.sh create mode 100755 tools/list-models.sh diff --git a/skills/deploy/SKILL.md b/skills/deploy/SKILL.md index f711457..d898d38 100644 --- a/skills/deploy/SKILL.md +++ b/skills/deploy/SKILL.md @@ -7,26 +7,20 @@ description: Batch install, update, or uninstall the whole jsc skill set on ever ## Steps -1. Run `tools/detect-clis.sh` to find the installed CLIs and their executable paths. +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. Show that table to the user as-is. It is the evidence behind the recommendation, so never summarise it away. - 3. **`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. - 4. `behind` = 0 → recommend nothing; present the three options neutrally. - 5. `查詢失敗` 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. -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. + 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. + + 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` (`{MKT}` = `https://gitea.jsc.idv.tw/plugins/meta.git`); the install token is `jsc-{domain}@jsc`. -5. Run the matching commands for each CLI (add the marketplace once per CLI, then install per domain). This step **MUST run as a sub agent** (one sub agent per CLI): - -| CLI | install | update | uninstall | -| --- | --- | --- | --- | -| claude | `claude plugin marketplace add {MKT}`, then per domain `claude plugin install jsc-{domain}@jsc` | `claude plugin marketplace update jsc`, then per domain `claude plugin update jsc-{domain}@jsc` | per domain `claude plugin uninstall jsc-{domain}@jsc`, finally `claude plugin marketplace remove jsc` | -| codex | `codex plugin marketplace add {MKT}`, then per domain `codex plugin add jsc-{domain}@jsc` | `codex plugin marketplace upgrade jsc` | per domain `codex plugin remove jsc-{domain}@jsc`, finally `codex plugin marketplace remove jsc` | -| copilot | `copilot plugin marketplace add {MKT}`, then per domain `copilot plugin install jsc-{domain}@jsc` | `copilot plugin marketplace update jsc`, then per domain `copilot plugin update jsc-{domain}@jsc` | per domain `copilot plugin uninstall jsc-{domain}@jsc`, finally `copilot plugin marketplace remove jsc` | -| antigravity | per domain `git clone https://gitea.jsc.idv.tw/plugins/{domain}.git ~/plugins/{domain}`, then `agy plugin install ~/plugins/{domain}` (agy cannot install from a gitea URL) | `git -C ~/plugins/{domain} pull`, then `agy plugin uninstall jsc-{domain}` and install again | `agy plugin uninstall jsc-{domain}` | -| kiro | same plugin commands as copilot (executable `kiro-cli`); if unsupported, copy each repo's `skills/` into the kiro skills directory | pull again, then copy again | delete the matching skills directories | - -6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. -7. Report the result and any failure reason for every CLI × mode. + The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. 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, then one `result` line; `-n` prints the commands without running them. 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. +6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. Done when hooks-install reports a wiring result for each detected CLI. +7. Report the result and any failure reason for every CLI × mode, plus every `skip` 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. diff --git a/skills/models/SKILL.md b/skills/models/SKILL.md index 13ca409..7dacf48 100644 --- a/skills/models/SKILL.md +++ b/skills/models/SKILL.md @@ -1,26 +1,16 @@ --- name: models -description: List every model usable by each installed AI CLI (claude, codex, copilot, antigravity, kiro) and attach capability tags from references/model-tags.md. Syncs the tag table to $JSC_HOME/model-tags.tsv via tools/model-tags.sh so jsc-sdlc gates can be enforced in code, states which tags each SDLC stage requires (plan and analyze need reasoning-max, implement needs coding, maintain any), and resolves each stage's preferred model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf) for switch suggestions only. Use when checking model fitness, inventorying models, or reviewing stage gating; not for switching models or editing the config files. +description: List every model usable by each installed AI CLI (claude, codex, copilot, antigravity, kiro) and attach capability tags from references/model-tags.md. Syncs the tag table to $JSC_HOME/model-tags.tsv via tools/model-tags.sh, so jsc-sdlc gates are enforced in code. States each SDLC stage's required tags: plan and analyze need reasoning-max, implement needs coding, maintain any. Resolves each stage's preferred model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf), for switch suggestions only. Use when checking model fitness, inventorying models, or reviewing stage gating; not for switching models or editing the config files. --- # models — list CLI models with capability tags ## Steps -1. Run `jsc-cli/tools/detect-clis.sh` to get the installed CLIs. -2. For each CLI, read the available models and the model currently in use. This step **MUST run as a sub agent**: - -| CLI | Source | -| --- | --- | -| claude | `model` in `~/.claude/settings.json`; known families are in the claude rows of model-tags.md | -| codex | `model` in `~/.codex/config.toml` | -| copilot | model options listed in the copilot config (`~/.config/copilot/`) | -| antigravity | models listed in the agy config | -| kiro | models listed in the kiro config | - - If a config is unreadable, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default). -3. Attach capability tags to every model per `references/model-tags.md`. Handle unlisted models per the closing rule of that file. -4. Output a table with four columns: CLI, model, tags, currently in use. +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. +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**. +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. diff --git a/tools/deploy.sh b/tools/deploy.sh new file mode 100755 index 0000000..dcb6578 --- /dev/null +++ b/tools/deploy.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env sh +# deploy.sh — 對單一 CLI 執行 jsc 技能組的安裝、更新或解除安裝。 +# 用法: +# deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...] +# -n 或 --dry-run(或 JSC_DEPLOY_DRYRUN=1):只印指令,不執行。 +# 輸出(TSV,一行一筆): +# cmd{指令} 即將執行的指令 +# exit{結束碼}{指令} 該指令的結束碼;dry-run 時結束碼印「-」 +# skip{domain}{原因} 本地 clone 是開發中的樹,略過 git pull +# result{cli}{mode}{domain 清單}{ok|fail} +# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip 不算失敗,但呼叫端要據實回報。 +# marketplace 指令一輪只跑一次:install 與 update 先跑,uninstall 最後跑。 +# 各 CLI 的細節都收在這裡,SKILL.md 只描述何時呼叫與參數: +# antigravity 不接受 gitea URL,先 clone 到本地再從路徑安裝,更新時 pull 同一份。 +# kiro 的執行檔是 kiro-cli;plugin 指令失敗時退回複製 skills 目錄。 +# 環境變數: +# GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw) +# JSC_GITEA_OWNER 存取庫的 owner(預設 plugins) +# JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄 +# 預設 $JSC_HOME/plugins(即 ~/.jsc/plugins),刻意不用 ~/plugins: +# 那是維護者放開發 checkout 的地方,pull 下去會蓋掉未提交的工作。 +# 指到開發中的樹(有未提交變更或未推送的 commit)時只印 skip,不 pull。 +# JSC_KIRO_SKILLS kiro 退路用的 skills 目錄(預設 ~/.kiro/skills) +# JSC_DEPLOY_DRYRUN 設為 1 等同 -n +set -u + +# Gitea 站台一律讀 GITEA_HOST(技能準則指定的變數),未設定才用正本站台。 +HOST="${GITEA_HOST:-https://gitea.jsc.idv.tw}" +case "$HOST" in http://*|https://*) ;; *) HOST="https://$HOST" ;; esac +HOST="${HOST%/}" +OWNER="${JSC_GITEA_OWNER:-plugins}" +MKT="$HOST/$OWNER/meta.git" +REPO_BASE="$HOST/$OWNER" +LOCAL_DIR="${JSC_LOCAL_PLUGINS:-${JSC_HOME:-$HOME/.jsc}/plugins}" +KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}" +DRYRUN="${JSC_DEPLOY_DRYRUN:-0}" +FAILED=0 + +# CLI 代號 → 實際執行檔。唯一真實來源是 jsc-hooks 的 hooks/lib.sh cli_bin()。 +# 這裡保留一份副本,因為這支腳本是整組技能的安裝入口:jsc-hooks 還沒裝上來時 +# 也要能跑,不能 source 一個可能不存在的檔案。lib.sh 的對應表改了就同步改這裡。 +cli_bin() { # $1=CLI 代號 + case "$1" in + antigravity) printf 'agy' ;; + kiro) printf 'kiro-cli' ;; + *) printf '%s' "$1" ;; + esac +} + +usage() { + echo "用法:deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...]" >&2 + exit 2 +} + +# 執行一個指令,並印出指令本身與結束碼。失敗就記進 FAILED。 +run() { # $@=指令 + printf 'cmd\t%s\n' "$*" + if [ "$DRYRUN" = 1 ]; then + printf 'exit\t-\t%s\n' "$*" + return 0 + fi + "$@" + code=$? + printf 'exit\t%s\t%s\n' "$code" "$*" + [ "$code" -eq 0 ] || FAILED=1 + return "$code" +} + +# 同 run,但失敗不記進 FAILED——留給「失敗還有退路」的指令用。 +run_soft() { # $@=指令 + printf 'cmd\t%s\n' "$*" + if [ "$DRYRUN" = 1 ]; then + printf 'exit\t-\t%s\n' "$*" + return 0 + fi + "$@" + code=$? + printf 'exit\t%s\t%s\n' "$code" "$*" + return "$code" +} + +# 這份 clone 是不是「開發中的樹」。有原因就印出原因,沒有就不印。 +# 判斷兩件事:有未提交變更,或有還沒推上去的 commit。兩者被 pull 蓋掉都救不回來。 +local_hold() { # $1=存取庫路徑 + if [ -n "$(git -C "$1" status --porcelain 2>/dev/null)" ]; then + printf '有未提交變更' + return 0 + fi + up=$(git -C "$1" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null) || return 0 + [ -n "$up" ] || return 0 + ahead=$(git -C "$1" rev-list --count "$up..HEAD" 2>/dev/null) || return 0 + [ "${ahead:-0}" -eq 0 ] || printf '有 %s 個未推送的 commit' "$ahead" +} + +# 把某 domain 的存取庫抓到本地:有 .git 就 pull,沒有就 clone。 +# 目標是開發中的樹時只印 skip,改用現地內容安裝,不 pull:這支腳本可以被指到任何 +# 目錄,蓋掉維護者未提交或未推送的工作救不回來,安裝一份舊內容還能重跑。 +sync_local() { # $1=domain + dir="$LOCAL_DIR/$1" + if [ -d "$dir/.git" ]; then + hold=$(local_hold "$dir") + if [ -n "$hold" ]; then + printf 'skip\t%s\t%s %s,未執行 git pull\n' "$1" "$dir" "$hold" + return 0 + fi + run git -C "$dir" pull + else + run git clone "$REPO_BASE/$1.git" "$dir" + fi +} + +# claude、copilot、kiro-cli 共用的 plugin 指令組。 +marketplace_cli() { # $1=執行檔 + case "$MODE" in + install) + run "$1" plugin marketplace add "$MKT" + for d in $DOMAINS; do run "$1" plugin install "jsc-$d@jsc"; done + ;; + update) + run "$1" plugin marketplace update jsc + for d in $DOMAINS; do run "$1" plugin update "jsc-$d@jsc"; done + ;; + uninstall) + for d in $DOMAINS; do run "$1" plugin uninstall "jsc-$d@jsc"; done + run "$1" plugin marketplace remove jsc + ;; + esac +} + +deploy_codex() { + bin=$(cli_bin codex) + case "$MODE" in + install) + run "$bin" plugin marketplace add "$MKT" + for d in $DOMAINS; do run "$bin" plugin add "jsc-$d@jsc"; done + ;; + update) + run "$bin" plugin marketplace upgrade jsc + ;; + uninstall) + for d in $DOMAINS; do run "$bin" plugin remove "jsc-$d@jsc"; done + run "$bin" plugin marketplace remove jsc + ;; + esac +} + +deploy_antigravity() { + bin=$(cli_bin antigravity) + for d in $DOMAINS; do + case "$MODE" in + install) + sync_local "$d" + run "$bin" plugin install "$LOCAL_DIR/$d" + ;; + update) + sync_local "$d" + run "$bin" plugin uninstall "jsc-$d" + run "$bin" plugin install "$LOCAL_DIR/$d" + ;; + uninstall) + run "$bin" plugin uninstall "jsc-$d" + ;; + esac + done +} + +# plugin 指令不支援時的退路:從本地 clone 複製 skills 目錄。 +kiro_copy() { # $1=domain + sync_local "$1" + run mkdir -p "$KIRO_SKILLS/jsc-$1" + run cp -R "$LOCAL_DIR/$1/skills/." "$KIRO_SKILLS/jsc-$1/" +} + +deploy_kiro() { + bin=$(cli_bin kiro) + case "$MODE" in + install) run_soft "$bin" plugin marketplace add "$MKT" ;; + update) run_soft "$bin" plugin marketplace update jsc ;; + esac + for d in $DOMAINS; do + case "$MODE" in + install) + run_soft "$bin" plugin install "jsc-$d@jsc" || kiro_copy "$d" + ;; + update) + run_soft "$bin" plugin update "jsc-$d@jsc" || kiro_copy "$d" + ;; + uninstall) + run_soft "$bin" plugin uninstall "jsc-$d@jsc" || run rm -rf "$KIRO_SKILLS/jsc-$d" + ;; + esac + done + [ "$MODE" = uninstall ] && run_soft "$bin" plugin marketplace remove jsc + return 0 +} + +case "${1:-}" in + -n|--dry-run) DRYRUN=1; shift ;; +esac + +[ $# -ge 3 ] || usage +MODE=$1 +CLI=$2 +shift 2 +DOMAINS="$*" + +case "$MODE" in + install|update|uninstall) ;; + *) usage ;; +esac + +case "$CLI" in + claude) marketplace_cli "$(cli_bin claude)" ;; + copilot) marketplace_cli "$(cli_bin copilot)" ;; + codex) deploy_codex ;; + antigravity) deploy_antigravity ;; + kiro) deploy_kiro ;; + *) usage ;; +esac + +if [ "$FAILED" -eq 0 ]; then + printf 'result\t%s\t%s\t%s\tok\n' "$CLI" "$MODE" "$DOMAINS" + exit 0 +fi +printf 'result\t%s\t%s\t%s\tfail\n' "$CLI" "$MODE" "$DOMAINS" +exit 1 diff --git a/tools/list-models.sh b/tools/list-models.sh new file mode 100755 index 0000000..e0554e0 --- /dev/null +++ b/tools/list-models.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env sh +# list-models.sh — 讀各 AI CLI 的設定檔,列出模型與目前使用中的模型。 +# 輸出(TSV,一行一個模型):climodelin-use(in-use 為 yes 或 no) +# 設定檔不存在或裡面沒寫模型,就不輸出該 CLI 的列;一律 exit 0。 +# 設定檔位置只寫在這裡,SKILL.md 不再抄一份: +# claude ${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json 的 model;ANTHROPIC_MODEL 可覆寫使用中的模型 +# codex ${CODEX_HOME:-~/.codex}/config.toml:頂層 model 為使用中,[profiles.*] 的 model 併入清單 +# copilot ~/.copilot/settings.json,退回 ~/.config/copilot/settings.json +# antigravity ~/.antigravity/settings.json,退回 ~/.config/antigravity/settings.json +# kiro ~/.kiro/settings/cli.json,退回 ~/.config/kiro/settings.json +# 五個 CLI 都沒有「列出可用模型」的指令,所以清單只到設定檔寫出來的模型。 +# 沒有列的 CLI 由 skills/models 的 sub agent 補上預設模型並標註「預設推定」。 +set -u + +# 取第一個存在的檔案;都不存在就不印。 +first_file() { # $@=候選路徑 + for f in "$@"; do + [ -f "$f" ] && { printf '%s\n' "$f"; return 0; } + done + return 0 +} + +# 取 JSON 檔裡某個鍵的所有字串值,一行一個,順序照檔案。 +json_val() { # $1=檔案 $2=鍵名 + [ -n "$1" ] || return 0 + [ -f "$1" ] || return 0 + grep -oE "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" "$1" 2>/dev/null \ + | sed -e 's/^[^:]*:[[:space:]]*"//' -e 's/"$//' + return 0 +} + +# 取 TOML 檔裡所有 model 設定值,一行一個,順序照檔案。 +# 只認行首的 model=,所以 model_reasoning_effort 這類鍵不會誤中。 +toml_val() { # $1=檔案 + [ -n "$1" ] || return 0 + [ -f "$1" ] || return 0 + grep -E '^[[:space:]]*model[[:space:]]*=' "$1" 2>/dev/null \ + | sed -e 's/^[^=]*=[[:space:]]*//' -e 's/[[:space:]]*#.*$//' \ + -e 's/^"//' -e 's/"[[:space:]]*$//' -e "s/^'//" -e "s/'[[:space:]]*$//" + return 0 +} + +# 印出某 CLI 的列:使用中的模型排第一,重複的只留一筆。 +emit() { # $1=cli $2=使用中的模型(可空) $3=模型清單(換行分隔,可空) + printf '%s\n%s\n' "$2" "$3" | awk -v cli="$1" -v cur="$2" ' + { gsub(/^[ \t]+|[ \t]+$/, "") } + $0 == "" { next } + seen[$0]++ { next } + { printf "%s\t%s\t%s\n", cli, $0, ($0 == cur ? "yes" : "no") }' +} + +# claude +cfg=$(first_file "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json") +list=$(json_val "$cfg" model) +cur="${ANTHROPIC_MODEL:-}" +[ -n "$cur" ] || cur=$(printf '%s\n' "$list" | head -n1) +emit claude "$cur" "$list" + +# codex +cfg=$(first_file "${CODEX_HOME:-$HOME/.codex}/config.toml") +list=$(toml_val "$cfg") +cur=$(printf '%s\n' "$list" | head -n1) +emit codex "$cur" "$list" + +# copilot +cfg=$(first_file "$HOME/.copilot/settings.json" "$HOME/.config/copilot/settings.json") +list=$(json_val "$cfg" model) +cur=$(printf '%s\n' "$list" | head -n1) +emit copilot "$cur" "$list" + +# antigravity +cfg=$(first_file "$HOME/.antigravity/settings.json" "$HOME/.config/antigravity/settings.json") +list=$(json_val "$cfg" model) +cur=$(printf '%s\n' "$list" | head -n1) +emit antigravity "$cur" "$list" + +# kiro +cfg=$(first_file "$HOME/.kiro/settings/cli.json" "$HOME/.config/kiro/settings.json") +list=$(json_val "$cfg" model) +cur=$(printf '%s\n' "$list" | head -n1) +emit kiro "$cur" "$list" + +exit 0 diff --git a/tools/model-config.sh b/tools/model-config.sh index 93adcab..c81243e 100755 --- a/tools/model-config.sh +++ b/tools/model-config.sh @@ -65,13 +65,13 @@ current_cli() { } # resolve 子指令:印出「目前 CLI 可用的第一個模型」單一名稱。 -# 簡化說明:這個 repo 目前沒有「列出某 CLI 實際安裝/可用模型」的 shell 級機制 -# ——現有的 skills/models 是靠 sub agent 讀各 CLI 的設定檔(~/.claude/settings.json、 -# ~/.codex/config.toml 等),屬於 LLM 才能做的判讀,沒辦法在這支 POSIX sh 腳本裡重現。 -# 因此這裡只用 current_cli() 抓到的「目前 CLI 名稱」這個既有訊號做記錄, -# 實際判斷可用性一律簡化為「直接取模型鏈的第一個模型」,不逐一檢查該模型是否真的能用。 -# 之後若要做到依 CLI 實際可用模型過濾,可以在這裡比對 current_cli 的結果與 -# references/model-tags.md/detect-clis.sh 的輸出,逐一嘗試鏈上模型直到找到可用的。 +# 簡化說明:可用性一律簡化為「直接取模型鏈的第一個模型」,不逐一檢查該模型是否真的能用。 +# tools/list-models.sh 已經能讀各 CLI 設定檔列出模型,但五個 CLI 都沒有 +# 「列出可用模型」的指令,設定檔沒寫出來的模型仍然查不到;拿這份清單當白名單過濾, +# 會把合法但沒寫進設定檔的模型誤判成不可用,比不過濾更糟。 +# 因此這裡只用 current_cli() 抓到的「目前 CLI 名稱」這個既有訊號做記錄。 +# 之後若要依 CLI 實際可用模型過濾,等各 CLI 提供列出模型的指令,再比對 +# list-models.sh 的輸出,逐一嘗試鏈上模型直到找到可用的。 resolve_first_usable() { # $1=階段 chain=$(resolve "$1" | cut -f1) [ -n "$chain" ] || return 0 -- 2.53.0 From a739ae20f88554ab0a71989203361484d83d468a Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 25 Aug 2026 14:58:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(cli):=20=E5=90=8C=E6=AD=A5=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=88=87=E5=8F=83=E8=80=83=E8=B3=87=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What:更新 README、AGENTS.md、templates 與 references,讓文件敘述與實際行為一致。 Why:稽核發現多處文件與程式行為分歧,違反「每個意義只有單一真實來源」。 How:以實際程式行為為準改寫敘述,重複的規則收成單一來源並以一行指引指過去。 Who:jsc-meta:skill-check 例行稽核(2026-08-25)。 Co-Authored-By: Claude Opus 5 --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cfc4d2f..4445e9c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 | 工具 | 用途 | | --- | --- | -| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;kiro 的執行檔為 `kiro-cli`) | +| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;antigravity 的執行檔為 `agy`、kiro 為 `kiro-cli`) | +| `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 | +| `tools/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 | | `tools/model-config.sh` | 解析 SDLC 各階段的偏好模型鏈(`get {stage}`、`list`、`resolve {stage}` 印出目前 CLI 可用的第一個模型);專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`,格式見 `references/model-tags.md`。鏈只影響建議與偏好順序,不影響閘門放行 | | `tools/model-tags.sh` | 解析 `references/model-tags.md` 的能力標籤與 SDLC 階段必要標籤(`dump`、`sync`、`stage {階段}`、`model {模型 id}`、`gate {階段} {模型 id}`);`sync` 寫出 `$JSC_HOME/model-tags.tsv` 供 `jsc-hooks` 的 sdlc-gate 讀取 | @@ -34,14 +36,26 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 ### `models` -讀取各已安裝 CLI 可使用的模型並加上能力標籤(`references/model-tags.md`),用 `tools/model-tags.sh sync` 把標籤表寫進 `$JSC_HOME/model-tags.tsv` 供 sdlc-gate 讀取,列出 SDLC 各階段的必要標籤(plan、analyze 需 `reasoning-max`;implement 需 `coding`;maintain 任意),並用 `tools/model-config.sh` 列出各階段的偏好模型鏈。`jsc-sdlc` 閘門一律以能力標籤判定,偏好鏈只用來建議切換目標。 +用 `tools/list-models.sh` 讀出各已安裝 CLI 可使用的模型並加上能力標籤(`references/model-tags.md`),用 `tools/model-tags.sh sync` 把標籤表寫進 `$JSC_HOME/model-tags.tsv` 供 sdlc-gate 讀取,列出 SDLC 各階段的必要標籤(plan、analyze 需 `reasoning-max`;implement 需 `coding`;maintain 任意),並用 `tools/model-config.sh` 列出各階段的偏好模型鏈。`jsc-sdlc` 閘門一律以能力標籤判定,偏好鏈只用來建議切換目標。 ### `deploy` -技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。 +技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 呼叫 `tools/deploy.sh` 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。 +## 環境變數 + +| 變數 | 用途 | 未設定時 | +| --- | --- | --- | +| `GITEA_HOST` | Gitea 站台(可省略 scheme,預設 https) | 用正本站台 `https://gitea.jsc.idv.tw` | +| `JSC_GITEA_OWNER` | 技能組存取庫的 owner | 用 `plugins` | +| `JSC_LOCAL_PLUGINS` | antigravity 與 kiro 退路用的本地 clone 目錄 | 用 `$JSC_HOME/plugins`(即 `~/.jsc/plugins`) | +| `JSC_KIRO_SKILLS` | kiro 退路複製 skills 的目標目錄 | 用 `~/.kiro/skills` | +| `JSC_DEPLOY_DRYRUN` | 設為 `1` 等同 `deploy.sh -n`,只印指令不執行 | 照常執行 | + +`JSC_LOCAL_PLUGINS` 的預設值刻意避開 `~/plugins`:那是維護者放技能組開發 checkout 的地方,`git pull` 下去會蓋掉未提交的工作。這個變數指到的目錄若是開發中的樹(有未提交變更,或有未推送的 commit),`deploy.sh` 只印一行 `skip` 並直接用現地內容安裝,不執行 `git pull`。 + ## 相關 domain - [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):取得 domain 名單(`tools/gitea.sh repos plugins`) -- 2.53.0 From 3082e5167f87dff7495ee203eaf48d5dd9854155 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 25 Aug 2026 14:58:54 +0800 Subject: [PATCH 3/3] =?UTF-8?q?chore(cli):=20=E4=B8=89=E4=BB=BD=20manifest?= =?UTF-8?q?=20=E5=90=8C=E6=AD=A5=E5=8D=87=E7=89=88=E4=B8=A6=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=20marketplace=20=E6=AD=A3=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What:三份 plugin manifest 版本同步 bump,兩份 marketplace 檔與 plugins/meta 正本對齊。 Why:準則要求技能異動必須同步升版;marketplace 副本必須與正本完全一致。 How:以 jsc-meta 的 tools/sync-skill-manifest.sh 升版,marketplace 檔由正本複製。 Who:jsc-meta:skill-check 例行稽核(2026-08-25)。 Co-Authored-By: Claude Opus 5 --- .agents/plugins/marketplace.json | 6 +++--- .claude-plugin/marketplace.json | 6 +++--- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- plugin.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index a9b4e66..8a2f91b 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -43,7 +43,7 @@ "source": "url", "url": "https://gitea.jsc.idv.tw/plugins/hooks.git" }, - "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄" + "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查" }, { "name": "jsc-log", @@ -75,7 +75,7 @@ "source": "url", "url": "https://gitea.jsc.idv.tw/plugins/review.git" }, - "description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組" + "description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組" }, { "name": "jsc-sdlc", @@ -83,7 +83,7 @@ "source": "url", "url": "https://gitea.jsc.idv.tw/plugins/sdlc.git" }, - "description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)" + "description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)" } ] } diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a9b4e66..8a2f91b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -43,7 +43,7 @@ "source": "url", "url": "https://gitea.jsc.idv.tw/plugins/hooks.git" }, - "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄" + "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查" }, { "name": "jsc-log", @@ -75,7 +75,7 @@ "source": "url", "url": "https://gitea.jsc.idv.tw/plugins/review.git" }, - "description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組" + "description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組" }, { "name": "jsc-sdlc", @@ -83,7 +83,7 @@ "source": "url", "url": "https://gitea.jsc.idv.tw/plugins/sdlc.git" }, - "description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)" + "description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 5422cb5..bdfe30a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-cli", - "version": "0.0.6", + "version": "0.0.8", "description": "CLI 偵測、模型能力標籤與技能庫批次部署", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 6f49295..a364fdd 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-cli", - "version": "0.0.6", + "version": "0.0.8", "description": "CLI 偵測、模型能力標籤與技能庫批次部署", "skills": "./skills" } diff --git a/plugin.json b/plugin.json index 4cce266..ea1cba8 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-cli", - "version": "0.0.6", + "version": "0.0.8", "description": "CLI 偵測、模型能力標籤與技能庫批次部署", "skills": "./skills/" } -- 2.53.0