feat(模型標籤閘門): 新增 model-tags.sh 與 reasoning-max 分級,SDLC 階段閘門改由能力標籤判定
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+155
@@ -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
|
||||
Reference in New Issue
Block a user