feat(hooks): 新增部署後強制重啟閘門,接線與冒煙同步到八支 hook #35
@@ -60,6 +60,10 @@
|
|||||||
{
|
{
|
||||||
"matcher": "Skill",
|
"matcher": "Skill",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/restart-gate.sh\""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/version-guard.sh\""
|
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/version-guard.sh\""
|
||||||
|
|||||||
Executable
+144
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# restart-gate.sh — 部署後強制重啟閘門(PreToolUse,matcher: Skill)。
|
||||||
|
#
|
||||||
|
# 技能組更新後,正在跑的 CLI 行程載入的還是舊版:SKILL.md、hook 腳本與 tools 都在啟動當下
|
||||||
|
# 讀進記憶體。所以部署收尾要求重新啟動,這道閘門負責讓「還沒重啟就繼續用技能」擋在門外。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# restart-gate.sh hook 模式:狀態檔存在就擋下該次技能呼叫(exit 2)
|
||||||
|
# restart-gate.sh require {模式} [{domain}...]
|
||||||
|
# 寫入狀態檔,掛上閘門。由 jsc-cli:deploy 在 install 或
|
||||||
|
# update 收尾時呼叫;模式為 install 或 update,之後接
|
||||||
|
# 這次更新的 domain 清單。
|
||||||
|
# exit 0 = 已掛上;exit 2 = 寫不進去(沒寫成等於沒掛)。
|
||||||
|
# restart-gate.sh clear 清除狀態檔,放下閘門。由 session-timer.sh 在判定為
|
||||||
|
# 新工作階段時呼叫(見下方「清除時機」)。檔案不存在也算成功。
|
||||||
|
# restart-gate.sh report 印出狀態檔內容;沒有狀態檔就不印,一律 exit 0。
|
||||||
|
#
|
||||||
|
# require、clear、report 都不讀標準輸入,只有 hook 模式讀。理由與 sdlc-gate.sh 相同:
|
||||||
|
# read_stdin 在標準輸入是管線又沒人關閉時會一直等,工具端呼叫就整支卡死。新增子命令照這個
|
||||||
|
# 原則歸類,工具端呼叫一律再補 </dev/null。
|
||||||
|
#
|
||||||
|
# --- 狀態檔格式 ---
|
||||||
|
#
|
||||||
|
# $JSC_HOME/restart-required(JSC_HOME 未設定時為 ~/.jsc),純文字 key=value,一行一欄位,
|
||||||
|
# 順序不拘,不認得的鍵一律忽略。格式壓到最簡,jsc-hooks 與 jsc-cli 兩邊各自實作也對得上。
|
||||||
|
# at={ISO 時間} 部署收尾時間,UTC
|
||||||
|
# mode={install|update} 這次部署的模式
|
||||||
|
# domains={domain 清單} 這次更新到的 domain,空白分隔
|
||||||
|
# cli={CLI 代號} 執行部署的 CLI
|
||||||
|
# 欄位只用在擋人訊息上。判定看的是「檔案在不在」——檔案存在就是還沒重啟過的證據,欄位缺了
|
||||||
|
# 只讓訊息少幾個字,不影響判定。
|
||||||
|
#
|
||||||
|
# --- 清除時機 ---
|
||||||
|
#
|
||||||
|
# 清除由 session-timer.sh 在「這一次 SessionStart 是新的工作階段」那一刻呼叫,不由本檔自己判定:
|
||||||
|
# 新舊工作階段的判準(sessions/{sid}.start 在不在)只有那支腳本知道,兩邊各寫一份就會漂移。
|
||||||
|
# 新的工作階段代表 CLI 行程是新起的,新版一定已經載入,所以清除是對的。續接同一階段
|
||||||
|
# (SessionStart 再觸發、resume、compact)不會走到那一段,閘門就一路留到真的重新啟動。
|
||||||
|
#
|
||||||
|
# --- 判定原則 ---
|
||||||
|
#
|
||||||
|
# 比照 version-guard.sh:只擋確定違規,查不到基礎資訊一律放行(exit 0)。狀態檔讀不到、
|
||||||
|
# 技能名取不到、工具名不是 Skill,三種都放行——沒有證據時擋下等於停掉每一次技能呼叫。
|
||||||
|
#
|
||||||
|
# 豁免(這些技能永遠放行,改動前想清楚後果):
|
||||||
|
# jsc-cli:deploy 部署入口本身,也是唯一能把技能組換成新版的路徑,擋了會死鎖
|
||||||
|
# jsc-hooks:hooks-install 部署後要重新接線,擋了會讓部署做一半卡住
|
||||||
|
# jsc-gitea:wiki 寫技能組異動報告與工作日誌都要它落地,擋了報告寫不完
|
||||||
|
# jsc-log:worklog 部署後還要寫得完工作日誌(R1)
|
||||||
|
# jsc-log:learn 同上,教訓也要記得完
|
||||||
|
# jsc-meta:* 技能組異動報告(R13)由這一組技能產出,另外它們是修技能組的工具
|
||||||
|
# jsc-ask:ask 上面幾支都要問使用者,擋了 deploy 連 install 或 update 都問不出來
|
||||||
|
# jsc-git:pr 報告與異動收尾要開 PR,擋了收尾做不完
|
||||||
|
# jsc-git:commit 同上,pr 的第一步就是它
|
||||||
|
# 理由講白:部署後還有兩條規則要收尾——技能組異動報告(R13)與工作日誌(R1)。整批擋下去,
|
||||||
|
# 「先重啟」與「先寫完報告」會互相打死,使用者兩件事都做不完。
|
||||||
|
#
|
||||||
|
# 清單認的是技能名,不是呼叫鏈:豁免技能轉呼叫的下一層若不在清單上,那一層照樣會被擋。
|
||||||
|
# 後三支(ask、pr、commit)就是為了這件事補進來的——它們自己不是收尾規則的主體,但前六支
|
||||||
|
# 少了它們就走不完:deploy 問不出模式、報告寫完開不了 PR。version-guard.sh 當年把
|
||||||
|
# jsc-ask:ask 與 jsc-gitea:wiki 放進豁免,也是同一個原因。
|
||||||
|
# 還有巢狀呼叫走不下去時,先重新啟動;真的卡死才下 JSC_RESTART_GATE=off。
|
||||||
|
#
|
||||||
|
# 逃生門:JSC_RESTART_GATE=off 完全略過這道閘門。
|
||||||
|
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
||||||
|
|
||||||
|
STATE="$JSC_HOME/restart-required"
|
||||||
|
|
||||||
|
# 從狀態檔取一個欄位;檔案讀不到或欄位不存在就不輸出。
|
||||||
|
state_field() { # $1=鍵名
|
||||||
|
[ -f "$STATE" ] && [ -r "$STATE" ] || return 0
|
||||||
|
sed -n "s/^$1=//p" "$STATE" 2>/dev/null | head -n1
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
require)
|
||||||
|
_mode="${2:-update}"
|
||||||
|
_domains=""
|
||||||
|
if [ "$#" -gt 2 ]; then shift 2; _domains="$*"; fi
|
||||||
|
mkdir -p "$JSC_HOME" 2>/dev/null || true
|
||||||
|
printf 'at=%s\nmode=%s\ndomains=%s\ncli=%s\n' \
|
||||||
|
"$(now_iso)" "$_mode" "$_domains" "$(cli_name)" > "$STATE" 2>/dev/null || {
|
||||||
|
# 寫不進去要講出來:沒寫成就沒有閘門,部署卻以為掛上了。
|
||||||
|
printf '[jsc][重啟閘門][ERR]:寫不進 %s,這次部署沒有掛上重啟閘門。\n' "$STATE" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
exit 0 ;;
|
||||||
|
clear)
|
||||||
|
rm -f "$STATE" 2>/dev/null || true
|
||||||
|
exit 0 ;;
|
||||||
|
report)
|
||||||
|
if [ -f "$STATE" ] && [ -r "$STATE" ]; then cat "$STATE" 2>/dev/null || true; fi
|
||||||
|
exit 0 ;;
|
||||||
|
"") ;; # 落到下面的 hook 模式
|
||||||
|
*) exit 0 ;; # 不認得的子命令一律安靜放行,不中斷宿主 CLI
|
||||||
|
esac
|
||||||
|
|
||||||
|
read_stdin
|
||||||
|
|
||||||
|
[ "${JSC_RESTART_GATE:-}" = "off" ] && exit 0
|
||||||
|
|
||||||
|
# 輸入相容:stdin JSON(Claude 格式)與環境變數(其他四支 CLI 接線時設定)都要收,
|
||||||
|
# 取法比照 version-guard.sh。工具名取不到就當成沒篩,繼續判技能名。
|
||||||
|
tool="${JSC_TOOL_NAME:-${TOOL_NAME:-$(json_str tool_name)}}"
|
||||||
|
[ -z "$tool" ] || [ "$tool" = "Skill" ] || exit 0
|
||||||
|
|
||||||
|
skill="${JSC_SKILL:-${SKILL:-$(json_str skill)}}"
|
||||||
|
[ -n "$skill" ] || exit 0
|
||||||
|
|
||||||
|
# 只管本技能組(jsc-{domain}:{name})。別人的技能不受這道閘門影響。
|
||||||
|
case "$skill" in
|
||||||
|
jsc-*:*) ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 豁免清單(理由見檔頭)
|
||||||
|
case "$skill" in
|
||||||
|
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-gitea:wiki|jsc-log:worklog|jsc-log:learn|jsc-meta:*|jsc-ask:ask|jsc-git:pr|jsc-git:commit)
|
||||||
|
exit 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 狀態檔讀不到就放行:沒有「剛部署過」的證據,就沒有擋人的理由。
|
||||||
|
[ -f "$STATE" ] && [ -r "$STATE" ] || exit 0
|
||||||
|
|
||||||
|
at=$(state_field at)
|
||||||
|
mode=$(state_field mode)
|
||||||
|
domains=$(state_field domains)
|
||||||
|
|
||||||
|
# 重啟方式依實際 CLI 給。印別的 CLI 的執行檔名等於沒給指示。
|
||||||
|
bin=$(cli_bin "$(cli_name)")
|
||||||
|
[ "$bin" = unknown ] && bin="目前的 CLI"
|
||||||
|
|
||||||
|
# 訊息裡的部署資訊逐段接起來,缺欄位就少一段,不會留下空括號或多餘的逗號。
|
||||||
|
info=""
|
||||||
|
[ -n "$at" ] && info="$at"
|
||||||
|
[ -n "$mode" ] && info="${info}${info:+,}模式 $mode"
|
||||||
|
[ -n "$domains" ] && info="${info}${info:+,}domain:$domains"
|
||||||
|
|
||||||
|
printf '[jsc][重啟閘門][ERR]:技能組已更新%s,%s 還在跑舊版,新版要重新啟動才會載入。本次技能呼叫已擋下。\n' \
|
||||||
|
"${info:+($info)}" "$bin" >&2
|
||||||
|
printf '重新啟動:結束 %s 再重新開啟一次,狀態檔 %s 會在新工作階段開始時自動清除。\n' \
|
||||||
|
"$bin" "$STATE" >&2
|
||||||
|
printf '仍可使用:/jsc-cli:deploy、/jsc-hooks:hooks-install、/jsc-gitea:wiki、/jsc-log:worklog、/jsc-log:learn、/jsc-meta:*、/jsc-ask:ask、/jsc-git:pr、/jsc-git:commit(部署後的異動報告與工作日誌要寫得完) | 確定要略過閘門:JSC_RESTART_GATE=off\n' >&2
|
||||||
|
exit 2
|
||||||
+18
-2
@@ -11,16 +11,32 @@
|
|||||||
# SessionStart 會再觸發一次,覆寫起始時間會讓花費時間歸零。
|
# SessionStart 會再觸發一次,覆寫起始時間會讓花費時間歸零。
|
||||||
# restart 給接不到 session id 的 CLI(kiro)。那些 CLI 的紀錄共用 default,
|
# restart 給接不到 session id 的 CLI(kiro)。那些 CLI 的紀錄共用 default,
|
||||||
# 不覆寫就會把上一個工作階段的起始時間算進來,花費時間虛胖。
|
# 不覆寫就會把上一個工作階段的起始時間算進來,花費時間虛胖。
|
||||||
|
#
|
||||||
|
# 這兩個子命令另外兼一件事:判定為「新的工作階段」時清除部署後的重啟閘門
|
||||||
|
# (restart-gate.sh clear)。新工作階段代表 CLI 行程是新起的,新版技能組一定已經載入。
|
||||||
|
# 判準只有這裡知道——start 分支的「起始檔不存在」就是這個 session id 第一次開始,
|
||||||
|
# 所以清除掛在這裡,不在 restart-gate.sh 裡自己再判一次。
|
||||||
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
||||||
read_stdin
|
read_stdin
|
||||||
sid=$(session_id)
|
sid=$(session_id)
|
||||||
|
|
||||||
|
# 放下部署後的重啟閘門。狀態檔的路徑與格式只留在 restart-gate.sh,這裡不碰檔案。
|
||||||
|
# 一律 </dev/null:clear 不讀標準輸入,但這裡的標準輸入是宿主餵進來的管線,不關掉會卡住。
|
||||||
|
clear_restart_gate() {
|
||||||
|
sh "$HERE/restart-gate.sh" clear </dev/null 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
case "${1:-mark}" in
|
case "${1:-mark}" in
|
||||||
start)
|
start)
|
||||||
f="$JSC_HOME/sessions/$sid.start"
|
f="$JSC_HOME/sessions/$sid.start"
|
||||||
[ -f "$f" ] || now_epoch > "$f" ;;
|
if [ ! -f "$f" ]; then
|
||||||
|
now_epoch > "$f"
|
||||||
|
clear_restart_gate # 起始檔不存在=這個工作階段第一次開始,也就是行程新起的那一次
|
||||||
|
fi ;;
|
||||||
restart)
|
restart)
|
||||||
now_epoch > "$JSC_HOME/sessions/$sid.start"
|
now_epoch > "$JSC_HOME/sessions/$sid.start"
|
||||||
rm -f "$JSC_HOME/sessions/$sid.end" ;;
|
rm -f "$JSC_HOME/sessions/$sid.end"
|
||||||
|
clear_restart_gate ;; # 接不到 session id 的 CLI 每次工作階段開始都算新的,一律清
|
||||||
mark)
|
mark)
|
||||||
now_epoch > "$JSC_HOME/sessions/$sid.end" ;;
|
now_epoch > "$JSC_HOME/sessions/$sid.end" ;;
|
||||||
report)
|
report)
|
||||||
|
|||||||
Reference in New Issue
Block a user