release: v0.2.5 develop 到 master #36

Merged
admin merged 9 commits from develop into master 2026-08-27 09:00:47 +00:00
3 changed files with 166 additions and 2 deletions
Showing only changes of commit dbf9aac25e - Show all commits
+4
View File
@@ -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\""
+144
View File
@@ -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
View File
@@ -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)