feat(模型標籤閘門): 新增 model-tags.sh 與 reasoning-max 分級,SDLC 階段閘門改由能力標籤判定 #5

Merged
admin merged 3 commits from fix/model-config-resolve-subcommand into develop 2026-08-24 08:16:32 +00:00
7 changed files with 196 additions and 16 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-cli",
"version": "0.0.3",
"version": "0.0.4",
"description": "CLI 偵測、模型能力標籤與技能庫批次部署",
"skills": "./skills",
"author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-cli",
"version": "0.0.3",
"version": "0.0.4",
"description": "CLI 偵測、模型能力標籤與技能庫批次部署",
"skills": "./skills"
}
+3 -2
View File
@@ -21,7 +21,8 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/jsc.git),安
| 工具 | 用途 |
| --- | --- |
| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;kiro 的執行檔為 `kiro-cli`) |
| `tools/model-config.sh` | 解析 SDLC 各階段的指定模型鏈(`get {stage}`、`list`、`resolve {stage}` 印出目前 CLI 可用的第一個模型);專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`,格式見 `references/model-tags.md` |
| `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 讀取 |
## Skills 目錄
@@ -31,7 +32,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/jsc.git),安
### `models`
讀取各已安裝 CLI 可使用的模型並加上能力標籤(`references/model-tags.md`),列出 SDLC 各階段的必要標籤,並用 `tools/model-config.sh` 解析各階段的指定模型鏈(專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`)。`jsc-sdlc` 閘門先看指定模型,沒有指定才退回標籤比對。
讀取各已安裝 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`
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-cli",
"version": "0.0.3",
"version": "0.0.4",
"description": "CLI 偵測、模型能力標籤與技能庫批次部署",
"skills": "./skills/"
}
+31 -8
View File
@@ -4,7 +4,8 @@
| 標籤 | 意義 |
| --- | --- |
| reasoning-high | 高階推理,適合規劃與分析 |
| reasoning-max | 頂級推理,各家當代最強一階;需求拆解、架構決策、跨檔案分析 |
| reasoning-high | 高階推理,足以判讀規格與寫實作 |
| coding | 程式碼產生與修改能力佳 |
| fast | 低延遲 |
| cheap | 低成本 |
@@ -15,25 +16,42 @@
| 模型 | 標籤 |
| --- | --- |
| claude-fable-5 | reasoning-high, coding, long-context, vision |
| claude-opus-5 | reasoning-high, coding, long-context, vision |
| claude-fable-5 | reasoning-max, reasoning-high, coding, long-context, vision |
| claude-opus-5 | reasoning-max, reasoning-high, coding, long-context, vision |
| claude-sonnet-5 | reasoning-high, coding, fast, long-context, vision |
| claude-haiku-4-5 | coding, fast, cheap, long-context, vision |
| gpt-5.x / o 系列(codex 預設) | reasoning-high, coding, long-context, vision |
| gpt-5.x / o 系列(codex 預設) | reasoning-max, reasoning-high, coding, long-context, vision |
| gpt-5.x-mini / codex-mini | coding, fast, cheap |
| gemini-3-pro | reasoning-high, coding, long-context, vision |
| gemini-3-pro | reasoning-max, reasoning-high, coding, long-context, vision |
| gemini-3-flash | coding, fast, cheap, long-context, vision |
| copilot 內建(gpt/claude 選項) | 依所選底層模型比照上表 |
標籤蘊含關係:`reasoning-max` 是 `reasoning-high` 的上位標籤。補表時,帶 `reasoning-max` 的模型**必須同時列出 `reasoning-high`**——比對是逐標籤字面比對,不推導上下位關係,漏列會讓該模型卡在只要求 `reasoning-high` 的階段。
## SDLC 階段需求
| 階段 | 必要標籤 |
| --- | --- |
| 規劃(plan) | reasoning-high |
| 分析(analyze) | reasoning-high |
| 規劃(plan) | reasoning-max |
| 分析(analyze) | reasoning-max |
| 實作(implement) | coding |
| 維護(maintain) | 任意 |
規劃與分析要求 `reasoning-max`,實作只要求 `coding`:需求拆解與架構決策一步走錯,後面整批工作包都跟著錯,成本遠高於實作階段的單點失誤。
### 判定方式(程式層,不由模型自評)
閘門一律由腳本判定,**模型不得自行宣稱具備標籤**:
| 工具 | 職責 |
| --- | --- |
| `jsc-cli/tools/model-tags.sh gate {階段} {模型 id}` | 查上面兩張表,印 `PASS`/`FAIL:{缺少的標籤}`/`UNKNOWN-MODEL`;exit code 0/1/2 |
| `jsc-cli/tools/model-tags.sh sync` | 把兩張表寫成 `$JSC_HOME/model-tags.tsv`,供 hook 讀取 |
| `jsc-hooks/hooks/sdlc-gate.sh lock {階段}` | 從 transcript 讀出**實際**模型 id,比對必要標籤;不符就拒絕上鎖並回報 |
| `jsc-hooks/hooks/sdlc-gate.sh check` | 每次送出提示時比對;不符即擋下該輪提示(exit 2) |
模型 id 一律取自 transcript 記錄的實際值,不採用模型自我回報——自我回報無法驗證,等同沒有閘門。
## 階段指定模型(角色式路由)
每個 SDLC 階段可以指定一個模型,並附上遞補模型。設定格式一行一階段:
@@ -55,6 +73,11 @@ stage=model[,fallback1[,fallback2...]]
遞補鏈語意:鏈上第一個模型優先;前面的模型不可用時依序後退到下一個。
指定模型優先於標籤比對:階段有指定模型時直接用指定鏈;只有階段沒有指定模型時,才用上表的必要標籤判定。
**標籤是閘門的唯一判準**:階段能不能跑,一律由「SDLC 階段需求」的必要標籤決定。指定模型鏈不參與放行判定,只有兩個用途:
1. 閘門擋下時,指出建議切換到哪一個模型(鏈上第一個滿足該階段必要標籤的模型)。
2. 同樣合格的模型有多個時,表達偏好順序。
因此「指定鏈上的模型」若不具備該階段必要標籤,照樣被擋;設定檔不能當成繞過閘門的後門。
表列以外的模型:依 `jsc-ask:ask` 規則詢問使用者或依供應商文件補表後再判定。
+4 -3
View File
@@ -1,6 +1,6 @@
---
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. Also resolves each SDLC stage's designated model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf) and states which tags each phase requires, so jsc-sdlc skills gate on the designated model first and fall back to capability tags. Use when checking model fitness, inventorying models, or reviewing designated-model routing; 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 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.
---
# models — list CLI models with capability tags
@@ -21,5 +21,6 @@ description: List every model usable by each installed AI CLI (claude, codex, co
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.
5. Append the SDLC phase requirement table (plan and analyze need `reasoning-high`; implement needs `coding`; maintain accepts any), so `jsc-sdlc` skills can gate on model capability.
6. Run `jsc-cli/tools/model-config.sh list` and append a 「階段指定模型」 table right after the phase requirement table, with three columns: stage, designated chain, source (`project` / `global`). State below the table that `jsc-sdlc` gates resolve the designated model first and fall back to capability tags only when a stage has no designated model. 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`, 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**.
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.
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env sh
# model-tags.sh — 解析「模型能力標籤」與「SDLC 階段必要標籤」,並判定模型是否夠格跑某階段。
#
# 資料來源優先序(前一項可取得就不往下):
# 1. $JSC_HOME/model-tags.tsv(機器可讀快取,可手改,用來補表列以外的模型)
# 2. 本 plugin 的 references/model-tags.md(唯一權威來源,不在腳本內另存一份對照表)
#
# 用法:
# model-tags.sh dump 把對照表印成 TSV,不寫檔
# model-tags.sh sync 把對照表寫進 $JSC_HOME/model-tags.tsv(給 hook 讀)
# model-tags.sh stage {stage} 印出該階段的必要標籤(逗號分隔);階段不限標籤印 any
# model-tags.sh model {model-id} 印出該模型的標籤(逗號分隔);查不到不印
# model-tags.sh gate {stage} {model-id} 判定該模型能否跑該階段
#
# TSV 欄位(三欄,Tab 分隔):
# stage<TAB>{階段}<TAB>{必要標籤以逗號分隔,不限標籤時為 any}
# model<TAB>{模型鍵}<TAB>{能力標籤以逗號分隔}
#
# gate 的輸出與 exit code:
# PASS exit 0 模型具備該階段全部必要標籤
# FAIL:{缺少的標籤} exit 1 模型缺標籤,不得執行該階段
# UNKNOWN-MODEL exit 2 模型不在對照表上,無法判定
# UNKNOWN-STAGE exit 2 階段不在對照表上,無法判定
set -u
HERE=$(dirname "$0")
REF="$HERE/../references/model-tags.md"
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
TSV="$JSC_HOME/model-tags.tsv"
# 解析 references/model-tags.md 的兩張表格,輸出 TSV。
# 「## 對照表」段落 → model 列;「## SDLC 階段需求」段落 → stage 列。
# 模型欄可能寫成「gpt-5.x / o 系列(codex 預設)」這種人讀格式,
# 因此先砍掉全形括號後的說明,再用「/」切成多個鍵,只留純 ASCII 的鍵。
parse_ref() {
[ -f "$REF" ] || return 0
awk '
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
# 砍掉第一個全形左括號之後的內容(表中括號都是尾端的補充說明)。
# 一律用 split 切字串,不用 substr 算位移——全形字元的長度在不同 locale 下
# 會被算成 1 個字元或 3 個 byte,位移法會在其中一種 locale 下算錯。
function strip_note(s) { split(s, a, "("); return trim(a[1]) }
# 取全形括號內的文字(例:規劃(plan) → plan)
function paren(s) {
if (split(s, a, "(") < 2) return ""
split(a[2], b, ")")
return trim(b[1])
}
function is_ascii_key(s) { return s ~ /^[A-Za-z0-9._-]+$/ }
function norm_tags(s) { gsub(/[ \t]/, "", s); return s }
/^##[ \t]/ {
if (index($0, "對照表") > 0) sect = "model"
else if (index($0, "SDLC 階段需求") > 0) sect = "stage"
else sect = ""
next
}
sect == "" { next }
/^\|/ {
line = $0
sub(/^\|/, "", line); sub(/\|[ \t]*$/, "", line)
n = split(line, f, "|")
if (n < 2) next
c1 = trim(f[1]); c2 = trim(f[2])
if (c1 == "" || c2 == "") next
if (c1 ~ /^:?-+:?$/) next # 表頭分隔列
if (c1 == "模型" || c1 == "階段" || c1 == "標籤") next # 表頭
if (sect == "stage") {
key = paren(c1)
if (key == "") key = c1
if (!is_ascii_key(key)) next
tags = (index(c2, "任意") > 0) ? "any" : norm_tags(c2)
printf "stage\t%s\t%s\n", key, tags
next
}
tags = norm_tags(c2)
if (tags == "") next
m = split(strip_note(c1), keys, "/")
for (i = 1; i <= m; i++) {
k = trim(keys[i])
if (k == "" || !is_ascii_key(k)) continue
printf "model\t%s\t%s\n", k, tags
}
}
' "$REF"
}
# 取得對照表內容:快取優先,沒有快取才解析 md。
data() {
if [ -s "$TSV" ]; then
cat "$TSV"
else
parse_ref
fi
}
stage_tags() { # $1=階段
data | awk -F'\t' -v s="$1" '$1 == "stage" && $2 == s { print $3; exit }'
}
# 模型鍵與實際模型 id 雙向包含即視為同一家族。
# 例:表列 claude-haiku-4-5 對得上實際 id claude-haiku-4-5-20251001。
# 多筆命中時取最長鍵,避免 claude 這種短鍵蓋掉精確鍵。
model_tags() { # $1=模型 id
data | awk -F'\t' -v m="$1" '
$1 == "model" && (index(m, $2) > 0 || index($2, m) > 0) {
if (length($2) > best_len) { best_len = length($2); best = $3 }
}
END { if (best != "") print best }
'
}
gate() { # $1=階段 $2=模型 id
req=$(stage_tags "$1")
if [ -z "$req" ]; then echo "UNKNOWN-STAGE"; return 2; fi
[ "$req" = "any" ] && { echo "PASS"; return 0; }
have=$(model_tags "$2")
if [ -z "$have" ]; then echo "UNKNOWN-MODEL"; return 2; fi
missing=$(awk -v req="$req" -v have="$have" '
BEGIN {
n = split(have, h, ","); for (i = 1; i <= n; i++) owned[h[i]] = 1
n = split(req, r, ",")
for (i = 1; i <= n; i++) if (!(r[i] in owned)) out = (out == "" ? r[i] : out "," r[i])
print out
}')
if [ -n "$missing" ]; then echo "FAIL:$missing"; return 1; fi
echo "PASS"; return 0
}
usage() {
echo "用法:model-tags.sh dump | sync | stage {階段} | model {模型 id} | gate {階段} {模型 id}" >&2
exit 1
}
case "${1:-}" in
dump) parse_ref; exit 0 ;;
sync)
out=$(parse_ref)
if [ -z "$out" ]; then
echo "[jsc] 解析不到 $REF,未寫出 $TSV" >&2
exit 1
fi
mkdir -p "$JSC_HOME" 2>/dev/null || true
printf '%s\n' "$out" > "$TSV" || exit 1
echo "$TSV"
exit 0 ;;
stage) [ -n "${2:-}" ] || usage; stage_tags "$2"; exit 0 ;;
model) [ -n "${2:-}" ] || usage; model_tags "$2"; exit 0 ;;
gate)
[ -n "${2:-}" ] && [ -n "${3:-}" ] || usage
gate "$2" "$3"; exit $? ;;
*) usage ;;
esac