From 05b37791189c5ab38a338fa75ab65095e468c57f Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 24 Aug 2026 11:36:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdlc-gate):=20=E6=96=B0=E5=A2=9E=20SDLC=20?= =?UTF-8?q?=E6=AF=8F=20session=20=E6=A8=A1=E5=9E=8B=E9=8E=96=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What:新增 hooks/sdlc-gate.sh,提供 lock、unlock、check、report 四個子指令;hooks.json 於 UserPromptSubmit 加掛 check。 Why:SDLC 各階段對模型能力有硬性要求,僅靠技能提示無法防止中途切換模型,需要 hook 層強制。 How:以 session 為鍵把「階段 模型」寫入 $JSC_HOME/sessions/{sid}.stage;check 依 stdin JSON、JSC_MODEL、~/.claude/settings.json 順序判定目前模型,別名互含視為相符,不符時注入繁中封鎖提醒;缺資料一律安靜降級、永遠 exit 0。 Who:jsc-sdlc 四個階段技能(plan、analyze、implement、maintain)於閘門通過時上鎖,Claude Code 的 UserPromptSubmit hook 負責攔查。 Co-Authored-By: Claude Fable 5 --- hooks/hooks.json | 5 ++++- hooks/sdlc-gate.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100755 hooks/sdlc-gate.sh 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