fix(model-gate): 支援 Codex 可驗證模型來源
This commit is contained in:
+89
-10
@@ -30,20 +30,99 @@ session_id() {
|
||||
printf '%s' "$sid"
|
||||
}
|
||||
|
||||
# 目前實際使用的模型 id:讀 transcript 最後一筆帶 model 的訊息。
|
||||
# 這是 shell 唯一能「驗證」的模型來源——模型自我回報無法驗證,等同沒有閘門。
|
||||
# 取不到(無 transcript_path、檔案不存在、尚無 assistant 訊息)時不輸出,由呼叫端決定如何降級。
|
||||
transcript_model() {
|
||||
tp=$(json_str transcript_path)
|
||||
[ -n "$tp" ] && [ -f "$tp" ] || return 0
|
||||
# 只掃尾端若干行即可命中最近一輪;`<...>` 這類佔位模型名(例如 <synthetic>)排除。
|
||||
tail -n 500 "$tp" 2>/dev/null \
|
||||
| grep -o '"model":"[^"]*"' \
|
||||
| sed 's/^"model":"//; s/"$//' \
|
||||
MODEL_SOURCE_CHECKS=""
|
||||
|
||||
model_checked() {
|
||||
MODEL_SOURCE_CHECKS="${MODEL_SOURCE_CHECKS:+$MODEL_SOURCE_CHECKS;}$1"
|
||||
}
|
||||
|
||||
model_clean() {
|
||||
printf '%s' "$1" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
json_model_value() {
|
||||
printf '%s' "$1" \
|
||||
| grep -o '"\(model\|model_id\|model_slug\|modelName\|current_model\|currentModel\)"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
||||
| sed 's/^"[^"]*"[[:space:]]*:[[:space:]]*"//; s/"$//' \
|
||||
| grep -v '^<' \
|
||||
| tail -n 1
|
||||
}
|
||||
|
||||
model_from_file() { # $1=檔案
|
||||
[ -f "$1" ] && [ -r "$1" ] || return 1
|
||||
json_model_value "$(tail -n 1000 "$1" 2>/dev/null)" | tail -n 1
|
||||
}
|
||||
|
||||
# transcript 是首選。它是 CLI 寫下的執行紀錄,不採用對話裡模型自己的宣稱。
|
||||
transcript_model() {
|
||||
tp=$(json_str transcript_path)
|
||||
[ -n "$tp" ] || tp="${JSC_TRANSCRIPT_PATH:-}"
|
||||
[ -n "$tp" ] && [ -f "$tp" ] || return 0
|
||||
model_from_file "$tp"
|
||||
}
|
||||
|
||||
codex_session_file() { # $1=CODEX_HOME $2=session id
|
||||
ch="$1"; sid="$2"
|
||||
[ -d "$ch/sessions" ] || return 1
|
||||
if [ -n "$sid" ] && [ "$sid" != default ]; then
|
||||
find "$ch/sessions" -type f -name "*$sid*.jsonl" 2>/dev/null | sort | tail -n 1
|
||||
return 0
|
||||
fi
|
||||
files=$(find "$ch/sessions" -type f -name '*.jsonl' 2>/dev/null)
|
||||
[ -n "$files" ] || return 1
|
||||
printf '%s\n' "$files" | xargs ls -t 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
codex_model() {
|
||||
ch="${CODEX_HOME:-$HOME/.codex}"
|
||||
[ -d "$ch" ] || return 1
|
||||
|
||||
sf=$(codex_session_file "$ch" "$(session_id)" 2>/dev/null || true)
|
||||
if [ -n "$sf" ]; then
|
||||
m=$(model_from_file "$sf" 2>/dev/null || true)
|
||||
[ -n "$m" ] && { printf '%s\t%s\n' "$m" "codex-session:$sf"; return 0; }
|
||||
fi
|
||||
|
||||
for f in "$ch/history.jsonl" "$ch/session_index.jsonl"; do
|
||||
[ -f "$f" ] || continue
|
||||
m=$(model_from_file "$f" 2>/dev/null || true)
|
||||
[ -n "$m" ] && { printf '%s\t%s\n' "$m" "codex-jsonl:$f"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
current_model_report() {
|
||||
MODEL_SOURCE_CHECKS=""
|
||||
|
||||
tp=$(json_str transcript_path)
|
||||
[ -n "$tp" ] || tp="${JSC_TRANSCRIPT_PATH:-}"
|
||||
model_checked "transcript_path:${tp:-未提供}"
|
||||
if [ -n "$tp" ] && [ -f "$tp" ]; then
|
||||
m=$(transcript_model)
|
||||
[ -n "$m" ] && { printf '%s\t%s\t%s\n' "$(model_clean "$m")" "transcript:$tp" "$MODEL_SOURCE_CHECKS"; return 0; }
|
||||
fi
|
||||
|
||||
model_checked "hook-stdin:model/model_id/model_slug/modelName/current_model/currentModel"
|
||||
m=$(json_model_value "$STDIN_JSON")
|
||||
[ -n "$m" ] && { printf '%s\t%s\t%s\n' "$(model_clean "$m")" "hook-stdin" "$MODEL_SOURCE_CHECKS"; return 0; }
|
||||
|
||||
model_checked "codex:${CODEX_HOME:-$HOME/.codex}、~/.codex/sessions、history.jsonl、session_index.jsonl"
|
||||
cm=$(codex_model 2>/dev/null || true)
|
||||
if [ -n "$cm" ]; then
|
||||
m=$(printf '%s' "$cm" | cut -f1)
|
||||
src=$(printf '%s' "$cm" | cut -f2)
|
||||
[ -n "$m" ] && { printf '%s\t%s\t%s\n' "$(model_clean "$m")" "$src" "$MODEL_SOURCE_CHECKS"; return 0; }
|
||||
fi
|
||||
|
||||
model_checked "JSC_MODEL"
|
||||
if [ -n "${JSC_MODEL:-}" ]; then
|
||||
printf '%s\t%s\t%s\n' "$(model_clean "$JSC_MODEL")" "人工覆寫:JSC_MODEL" "$MODEL_SOURCE_CHECKS"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '\t\t%s\n' "$MODEL_SOURCE_CHECKS"
|
||||
}
|
||||
|
||||
# 目前 CLI 名稱:環境變數 > 依 stdin 特徵猜測 > unknown
|
||||
cli_name() {
|
||||
if [ -n "${JSC_CLI:-}" ]; then printf '%s' "$JSC_CLI"
|
||||
|
||||
+46
-25
@@ -3,19 +3,20 @@
|
||||
#
|
||||
# 判準是階段的「能力標籤」,不是模型名稱:
|
||||
# - 標籤資料讀 $JSC_HOME/model-tags.tsv,由 jsc-cli/tools/model-tags.sh sync 產生。
|
||||
# - 目前模型 id 取自 transcript 記錄的實際值(lib.sh 的 transcript_model),
|
||||
# 不採用模型自我回報——自我回報無法驗證,等同沒有閘門。
|
||||
# - 目前模型 id 取自可驗證紀錄。優先讀 transcript,其次讀 hook stdin JSON 與 Codex 本機
|
||||
# session 記錄。只有這些來源都失敗時,才接受 JSC_MODEL 這個人工覆寫。
|
||||
# - 不採用對話內容裡模型自稱的 id。自我回報無法驗證,等同沒有閘門。
|
||||
# - 鎖存的是「階段的必要標籤」,不是「上鎖那一刻的模型」。鎖當時的模型只留作記錄,
|
||||
# 否則用不合格的模型起跑就會把自己鎖成合格,閘門永遠通過。
|
||||
#
|
||||
# 狀態檔:$JSC_HOME/sessions/{sid}.stage,單行「{stage}<TAB>{必要標籤}<TAB>{上鎖時的模型}」。
|
||||
# 狀態檔:$JSC_HOME/sessions/{sid}.stage,單行「{stage}<TAB>{必要標籤}<TAB>{上鎖時的模型}<TAB>{模型來源}」。
|
||||
#
|
||||
# 用法:
|
||||
# sdlc-gate.sh lock {stage} 階段閘門:比對實際模型與該階段必要標籤,通過才上鎖。
|
||||
# exit 0 = 通過並已上鎖;exit 1 = 未通過,呼叫端必須停止流程。
|
||||
# sdlc-gate.sh unlock 移除狀態檔(被擋住又確定要放行時的逃生門)。
|
||||
# sdlc-gate.sh check hook 模式(UserPromptSubmit):模型不符即擋下該輪提示。
|
||||
# sdlc-gate.sh report 印出 {sid} {stage} {必要標籤} {上鎖時的模型};無鎖不印。
|
||||
# sdlc-gate.sh report 印出 {sid} {stage} {必要標籤} {模型 id} {模型來源} {判定};無鎖不印。
|
||||
#
|
||||
# sdlc-gate.sh wp-lock {owner}/{repo} {index} [{工作包代號}]
|
||||
# 記下一筆未結清的工作包 PR;第四個參數是這支 PR 做的是哪一包,
|
||||
@@ -136,17 +137,22 @@ eligible_models() { # $1=必要標籤
|
||||
' "$TAGS_TSV"
|
||||
}
|
||||
|
||||
# 目前模型:transcript 實際值 > stdin JSON model > JSC_MODEL > ~/.claude/settings.json。
|
||||
# transcript 排最前面,因為那是唯一可驗證的實際值,其餘都只是宣告值。
|
||||
current_model() {
|
||||
m=$(transcript_model)
|
||||
[ -n "$m" ] || m=$(json_str model)
|
||||
[ -n "$m" ] || m="${JSC_MODEL:-}"
|
||||
if [ -z "$m" ] && { [ -z "${JSC_CLI:-}" ] || [ "${JSC_CLI:-}" = "claude" ]; }; then
|
||||
m=$(tr -d '\n' < "$HOME/.claude/settings.json" 2>/dev/null \
|
||||
| sed -n 's/.*"model"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
||||
fi
|
||||
printf '%s' "$m"
|
||||
model_info() {
|
||||
current_model_report
|
||||
}
|
||||
|
||||
model_verdict() { # $1=stage $2=req $3=model
|
||||
stage="$1"; req="$2"; cur="$3"
|
||||
[ -n "$cur" ] || { printf 'unknown-model'; return 0; }
|
||||
[ "$req" = "any" ] && { printf 'pass'; return 0; }
|
||||
have=$(model_tags "$cur")
|
||||
[ -n "$have" ] || { printf 'unknown-model'; return 0; }
|
||||
miss=$(missing_tags "$req" "$have")
|
||||
[ -z "$miss" ] && printf 'pass' || printf 'fail:%s' "$miss"
|
||||
}
|
||||
|
||||
model_fail_message() { # $1=stage $2=checked
|
||||
echo "[jsc][SDLC 閘門][ERR]:判定不出目前實際使用的模型,無法驗證是否符合階段「$1」。已檢查來源:${2:-無}。請確認 hook stdin 帶有 transcript_path 或 model 欄位、Codex session 記錄可讀,或明確設定 JSC_MODEL 作為人工覆寫後重跑。本次不進行任何工作。" >&2
|
||||
}
|
||||
|
||||
# --- 工作包 PR 閘門(狀態檔:$JSC_HOME/wp/{owner}-{repo}-{index}.pr,一個工作包一支) ---
|
||||
@@ -249,9 +255,12 @@ case "${1:-check}" in
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cur=$(current_model)
|
||||
info=$(model_info)
|
||||
cur=$(printf '%s' "$info" | cut -f1)
|
||||
src=$(printf '%s' "$info" | cut -f2)
|
||||
checked=$(printf '%s' "$info" | cut -f3-)
|
||||
if [ -z "$cur" ]; then
|
||||
echo "[jsc][SDLC 閘門][ERR]:判定不出目前實際使用的模型,無法驗證是否符合階段「$stage」。請確認 transcript 可讀,或設定 JSC_MODEL 後重跑,本次不進行任何工作。" >&2
|
||||
model_fail_message "$stage" "$checked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -269,9 +278,9 @@ case "${1:-check}" in
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\t%s\t%s\n' "$stage" "$req" "$cur" > "$state" 2>/dev/null || {
|
||||
printf '%s\t%s\t%s\t%s\n' "$stage" "$req" "$cur" "$src" > "$state" 2>/dev/null || {
|
||||
echo "[jsc][SDLC 閘門][ERR]:寫不進狀態檔 $state,階段鎖未生效。" >&2; exit 1; }
|
||||
echo "[jsc][SDLC 閘門][OK]:階段「$stage」通過(需要 $req,目前模型 $cur),已上鎖。"
|
||||
echo "[jsc][SDLC 閘門][OK]:階段「$stage」通過(需要 $req,目前模型 $cur,來源 $src),已上鎖。"
|
||||
exit 0 ;;
|
||||
|
||||
unlock)
|
||||
@@ -286,16 +295,19 @@ case "${1:-check}" in
|
||||
[ -n "$stage" ] && [ -n "$req" ] && [ "$stage" != "$req" ] || exit 0
|
||||
[ "$req" = "any" ] && exit 0
|
||||
|
||||
cur=$(current_model)
|
||||
info=$(model_info)
|
||||
cur=$(printf '%s' "$info" | cut -f1)
|
||||
src=$(printf '%s' "$info" | cut -f2)
|
||||
checked=$(printf '%s' "$info" | cut -f3-)
|
||||
# 判定不出模型時只提醒,不擋——否則使用者會被鎖在無法送出提示的狀態。
|
||||
if [ -z "$cur" ]; then
|
||||
echo "[jsc] SDLC 階段「${stage}」需要標籤「${req}」,但判定不出目前模型。請自行確認模型是否合格。"
|
||||
echo "[jsc] SDLC 階段「${stage}」需要標籤「${req}」,但判定不出目前模型。已檢查來源:${checked:-無}。請確認 transcript、Codex session 記錄,或設定 JSC_MODEL 人工覆寫。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
have=$(model_tags "$cur")
|
||||
if [ -z "$have" ]; then
|
||||
echo "[jsc][SDLC 閘門][ERR]:模型「${cur}」不在能力標籤表上,無法確認是否夠格跑階段「${stage}」(需要 ${req})。請補進 jsc-cli/references/model-tags.md 並執行 model-tags.sh sync;確定要放行請執行 jsc-hooks/hooks/sdlc-gate.sh unlock。本輪提示已擋下。" >&2
|
||||
echo "[jsc][SDLC 閘門][ERR]:模型「${cur}」(來源 ${src:-未知})不在能力標籤表上,無法確認是否夠格跑階段「${stage}」(需要 ${req})。請補進 jsc-cli/references/model-tags.md 並執行 model-tags.sh sync;確定要放行請執行 jsc-hooks/hooks/sdlc-gate.sh unlock。本輪提示已擋下。" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
@@ -303,13 +315,22 @@ case "${1:-check}" in
|
||||
[ -z "$miss" ] && exit 0
|
||||
|
||||
ok=$(eligible_models "$req")
|
||||
echo "[jsc][SDLC 閘門][ERR]:SDLC 階段「${stage}」需要標籤「${req}」,目前模型「${cur}」缺少「${miss}」。請切換到下列任一模型後重送:${ok:-(表上無合格模型,請補表)}。要結束本階段的鎖請執行 jsc-hooks/hooks/sdlc-gate.sh unlock。本輪提示已擋下。" >&2
|
||||
echo "[jsc][SDLC 閘門][ERR]:SDLC 階段「${stage}」需要標籤「${req}」,目前模型「${cur}」(來源 ${src:-未知})缺少「${miss}」。請切換到下列任一模型後重送:${ok:-(表上無合格模型,請補表)}。要結束本階段的鎖請執行 jsc-hooks/hooks/sdlc-gate.sh unlock。本輪提示已擋下。" >&2
|
||||
exit 2 ;;
|
||||
|
||||
report)
|
||||
if [ -f "$state" ]; then
|
||||
line=$(sed -n '1p' "$state" 2>/dev/null | tr '\t' ' ')
|
||||
[ -n "$line" ] && echo "$sid $line"
|
||||
stage=$(cut -f1 "$state" 2>/dev/null | head -n1)
|
||||
req=$(cut -f2 "$state" 2>/dev/null | head -n1)
|
||||
locked_model=$(cut -f3 "$state" 2>/dev/null | head -n1)
|
||||
locked_source=$(cut -f4 "$state" 2>/dev/null | head -n1)
|
||||
info=$(model_info)
|
||||
cur=$(printf '%s' "$info" | cut -f1)
|
||||
src=$(printf '%s' "$info" | cut -f2)
|
||||
[ -n "$cur" ] || cur="$locked_model"
|
||||
[ -n "$src" ] || src="$locked_source"
|
||||
verdict=$(model_verdict "$stage" "$req" "$cur")
|
||||
[ -n "$stage" ] && echo "$sid $stage $req ${cur:-未知} ${src:-未知} $verdict"
|
||||
fi
|
||||
exit 0 ;;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user