feat(model-config): 新增 SDLC 階段指定模型鏈解析工具

What:新增 tools/model-config.sh,提供 get {stage} 與 list 兩個子命令,解析 SDLC 各階段(plan、analyze、implement、maintain)的指定模型鏈。
Why:讓 jsc-sdlc 各階段閘門能以角色式模型路由指定模型與遞補鏈,而不是只靠能力標籤比對;同時讓專案能覆寫全域設定。
How:以 POSIX sh 加 awk 實作;先讀專案目錄 .jsc/models,該階段沒設定才退回 $JSC_HOME/models.conf(JSC_HOME 預設 ~/.jsc);設定格式 stage=model[,fallback...],同階段多行取最後一行;get 未設定時空輸出且 exit 0,list 每階段輸出 stage、chain、source(未設定印「-」)。
Who:jsc-cli models 技能與 jsc-sdlc 各階段的模型閘門。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 11:36:31 +08:00
co-authored by Claude Fable 5
parent b72c51c234
commit de504ee2ed
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env sh
# model-config.sh — 解析 SDLC 各階段的指定模型鏈(角色式路由)。
# 設定格式(一行一階段):stage=model[,fallback1[,fallback2...]]
# 「#」開頭為註解,空白行忽略。階段限定:plan、analyze、implement、maintain。
# 解析順序:
# 1. 目前工作目錄的 ./.jsc/models(專案覆寫)
# 2. $JSC_HOME/models.conf(JSC_HOME 預設 ~/.jsc)
# 專案檔有設定的階段完全覆寫全域設定;其餘階段仍讀全域檔。
# 用法:
# model-config.sh get {stage} 印出該階段解析後的模型鏈(逗號分隔);未設定不印。兩種情況都 exit 0。
# model-config.sh list 每階段一行:stage<TAB>chain<TAB>source(project 或 global);未設定的 chain 與 source 印「-」。
set -u
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
PROJECT_FILE=./.jsc/models
GLOBAL_FILE="$JSC_HOME/models.conf"
STAGES="plan analyze implement maintain"
# 取出單一設定檔裡某階段的模型鏈。
# 同階段出現多行時取最後一行,結果才有確定性。
read_chain() { # $1=設定檔 $2=階段
[ -f "$1" ] || return 0
awk -F= -v stage="$2" '
{ sub(/\r$/, "") } # 去掉 CR
/^[ \t]*#/ { next } # 註解行
/^[ \t]*$/ { next } # 空白行
index($0, "=") == 0 { next }
{
key = $1
gsub(/[ \t]/, "", key)
if (key != stage) next
val = substr($0, index($0, "=") + 1)
gsub(/[ \t]/, "", val)
if (val != "") chain = val
}
END { if (chain != "") print chain }
' "$1"
}
# 解析單一階段:專案檔優先,該階段沒設定才退回全域檔。
# 輸出:chain<TAB>source;兩檔都沒設定時不輸出。
resolve() { # $1=階段
chain=$(read_chain "$PROJECT_FILE" "$1")
if [ -n "$chain" ]; then
printf '%s\tproject\n' "$chain"
return 0
fi
chain=$(read_chain "$GLOBAL_FILE" "$1")
if [ -n "$chain" ]; then
printf '%s\tglobal\n' "$chain"
fi
return 0
}
usage() {
echo "用法:model-config.sh get {plan|analyze|implement|maintain} | list" >&2
exit 1
}
case "${1:-}" in
get)
stage="${2:-}"
case " $STAGES " in
*" $stage "*) ;;
*) usage ;;
esac
resolve "$stage" | cut -f1
exit 0
;;
list)
for stage in $STAGES; do
line=$(resolve "$stage")
if [ -n "$line" ]; then
printf '%s\t%s\n' "$stage" "$line"
else
printf '%s\t-\t-\n' "$stage"
fi
done
exit 0
;;
*)
usage
;;
esac