diff --git a/hooks/hooks.json b/hooks/hooks.json index 729bf36..11cc543 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -4,7 +4,10 @@ { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" start" } ] } ], "UserPromptSubmit": [ - { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/ste100-guard.sh\"" } ] } + { "hooks": [ + { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/ste100-guard.sh\"" }, + { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/sdlc-gate.sh\" check" } + ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" mark" } ] } diff --git a/hooks/sdlc-gate.sh b/hooks/sdlc-gate.sh new file mode 100755 index 0000000..cb1860f --- /dev/null +++ b/hooks/sdlc-gate.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env sh +# sdlc-gate.sh — SDLC 每 session 模型鎖(stage ∈ plan/analyze/implement/maintain)。 +# 狀態檔: $JSC_HOME/sessions/{sid}.stage,單行「{stage} {model}」。 +# 用法: +# sdlc-gate.sh lock {stage} {model} # 階段閘門通過時上鎖(覆寫既有鎖) +# sdlc-gate.sh unlock # 移除狀態檔 +# sdlc-gate.sh check # hook 模式(UserPromptSubmit):模型不符時注入繁中提醒 +# sdlc-gate.sh report # 印出 {sid} {stage} {model};無鎖不印 +# 缺資料時安靜降級,永遠 exit 0,不可中斷宿主 CLI。 +HERE=$(dirname "$0"); . "$HERE/lib.sh" +read_stdin +sid=$(session_id) +state="$JSC_HOME/sessions/$sid.stage" + +case "${1:-check}" in + lock) + # 需要 stage 與 model 兩個參數,缺一就安靜降級 + if [ -n "${2:-}" ] && [ -n "${3:-}" ]; then + printf '%s %s\n' "$2" "$3" > "$state" 2>/dev/null || true + fi ;; + unlock) + rm -f "$state" 2>/dev/null || true ;; + check) + [ -f "$state" ] || exit 0 + stage=$(sed -n '1s/ .*//p' "$state" 2>/dev/null) + locked=$(sed -n '1s/^[^ ]* //p' "$state" 2>/dev/null) + [ -n "$stage" ] && [ -n "$locked" ] || exit 0 + # 目前模型判定順序:stdin JSON model > JSC_MODEL > ~/.claude/settings.json(僅 JSC_CLI 為 claude 或未設定時) + cur=$(json_str model) + [ -n "$cur" ] || cur="${JSC_MODEL:-}" + if [ -z "$cur" ] && { [ -z "${JSC_CLI:-}" ] || [ "${JSC_CLI:-}" = "claude" ]; }; then + cur=$(tr -d '\n' < "$HOME/.claude/settings.json" 2>/dev/null \ + | sed -n 's/.*"model"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1) + fi + [ -n "$cur" ] || exit 0 + # 別名相容:一方包含另一方就視為同一模型(例:opus 對 claude-opus-4-6) + case "$cur" in *"$locked"*) exit 0 ;; esac + case "$locked" in *"$cur"*) exit 0 ;; esac + echo "[jsc] SDLC 模型鎖:階段「${stage}」已鎖定模型「${locked}」,目前模型「${cur}」不符。請拒絕執行任何 SDLC 工作,並請使用者切回模型「${locked}」後再繼續。此鎖會在下一個 SDLC 階段閘門執行時解除。" ;; + report) + if [ -f "$state" ]; then + line=$(sed -n '1p' "$state" 2>/dev/null) + [ -n "$line" ] && echo "$sid $line" + fi ;; +esac +exit 0