fix(gates): 修好會蓋掉異常目錄、把修復路徑鎖死與誤擋計畫階段的缺陷
異常回報依 wiki 讀取的結束碼分流,只有頁面確定不存在才套範本建新頁。 版本閘門與重啟閘門的豁免清單各補上 hook 修復技能。 階段閘門把計畫階段移出擋人名單,改成只注入提醒。 錯誤掃描的自家 hook 判定補齊九支腳本,並加一條路徑判定。 修復技能與接線技能的內文改成真的走得到的路徑與真的存在的關卡數。 原本 wiki 讀取失敗會一路落到套範本那一步,金鑰失效或 API 出狀況時, 就拿一份空白範本蓋掉整份異常目錄,而寫入不做合併也不留備份,蓋掉就救不回來。 兩道閘門把唯一的 hook 修復路徑一起擋住,hook 一壞就沒有任何方法修回來, 閘門等於鎖掉解除自己的路徑。工作包閘門擋下計畫階段是誤擋: 計畫是純邏輯階段、不碰程式碼,而閘門只知道有 PR 未合併,判不出跟新計畫有沒有關聯。 自家 hook 判定只認得早期那五支,後來加的四支出錯會被當成第三方的,只回報不修正。 修復技能裡三個指向流程的路徑指到不存在的位置,照著走一定撲空; 接線流程寫四道關卡,實際上有五道,兩段中文說明也混在英文內文裡。 異常目錄改成先讀回舊頁、把新列附在文末、再整頁寫回;讀不回來就放棄寫目錄頁並回報, 寧可少一列索引,也不覆蓋別人的紀錄。兩份豁免清單各補一項,理由逐項寫在腳本檔頭。 計畫階段改印提醒後放行,放棄的在製品上限與代價一併寫在檔頭。 自家 hook 判定逐支列出腳本名,再加一條安裝路徑判定,日後新增 hook 忘了補清單也還認得出來。 三個路徑改指到擁有它的技能組,關卡數改成五道並逐關寫明結束碼,兩段中文說明改回英文。 版本閘門在同一次改動另補唯讀的建議子命令,把版本比對表收斂成一行結論, 部署技能不必自己再解一次那張表。
This commit is contained in:
+16
-3
@@ -4,6 +4,17 @@
|
||||
# 技能組更新後,正在跑的 CLI 行程載入的還是舊版:SKILL.md、hook 腳本與 tools 都在啟動當下
|
||||
# 讀進記憶體。所以部署收尾要求重新啟動,這道閘門負責讓「還沒重啟就繼續用技能」擋在門外。
|
||||
#
|
||||
# 結束碼(hook 模式):0=放行 2=擋下該次技能呼叫,訊息走 stderr。
|
||||
# 安靜放行(exit 0)的情況:逃生門 JSC_RESTART_GATE=off、工具名取得到但不是 Skill、
|
||||
# 取不到技能名、技能名不是 jsc-{domain}:{name}、命中下方豁免清單那 10 支、取不到 CLI 代號、
|
||||
# 當前 CLI 那份狀態檔與舊格式狀態檔都不在。
|
||||
# 只有「當前 CLI 那份狀態檔存在」或「退回讀到的舊格式狀態檔存在」會 exit 2。
|
||||
# 結束碼(require):0=閘門已掛上 2=取不到 CLI 代號或寫不進狀態檔,兩種都等於沒掛上。
|
||||
# 結束碼(clear、report):0=永遠成功。clear 檔案不存在也算成功,report 一份都沒有就不印。
|
||||
# 結束碼(不認得的子命令):0=安靜放行,不中斷宿主 CLI。
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會就地
|
||||
# 結束並回 2,接在 PreToolUse 上就是無聲擋下每一次技能呼叫,上面那些放行路徑一條都跑不到。
|
||||
#
|
||||
# 用法:
|
||||
# restart-gate.sh hook 模式:當前 CLI 那份狀態檔存在就擋下該次技能
|
||||
# 呼叫(exit 2)。別支 CLI 那幾份不看。
|
||||
@@ -79,6 +90,8 @@
|
||||
# 豁免(這些技能永遠放行,改動前想清楚後果):
|
||||
# jsc-cli:deploy 部署入口本身,也是唯一能把技能組換成新版的路徑,擋了會死鎖
|
||||
# jsc-hooks:hooks-install 部署後要重新接線,擋了會讓部署做一半卡住
|
||||
# jsc-hooks:repair 接線或執行期出錯時唯一的修復路徑。修 hook 的技能被 hook 擋下,
|
||||
# 就沒有任何方法把 hook 修回來,閘門等於把解除自己的路徑一起鎖掉
|
||||
# jsc-gitea:wiki 寫技能組異動報告與工作日誌都要它落地,擋了報告寫不完
|
||||
# jsc-log:worklog 部署後還要寫得完工作日誌(R1)
|
||||
# jsc-log:learn 同上,教訓也要記得完
|
||||
@@ -90,7 +103,7 @@
|
||||
# 「先重啟」與「先寫完報告」會互相打死,使用者兩件事都做不完。
|
||||
#
|
||||
# 清單認的是技能名,不是呼叫鏈:豁免技能轉呼叫的下一層若不在清單上,那一層照樣會被擋。
|
||||
# 後三支(ask、pr、commit)就是為了這件事補進來的——它們自己不是收尾規則的主體,但前六支
|
||||
# 後三支(ask、pr、commit)就是為了這件事補進來的——它們自己不是收尾規則的主體,但前七支
|
||||
# 少了它們就走不完:deploy 問不出模式、報告寫完開不了 PR。version-guard.sh 當年把
|
||||
# jsc-ask:ask 與 jsc-gitea:wiki 放進豁免,也是同一個原因。
|
||||
# 還有巢狀呼叫走不下去時,先重新啟動;真的卡死才下 JSC_RESTART_GATE=off。
|
||||
@@ -187,7 +200,7 @@ 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)
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-hooks:repair|jsc-gitea:wiki|jsc-log:worklog|jsc-log:learn|jsc-meta:*|jsc-ask:ask|jsc-git:pr|jsc-git:commit)
|
||||
exit 0 ;;
|
||||
esac
|
||||
|
||||
@@ -227,5 +240,5 @@ printf '[jsc][重啟閘門][ERR]:技能組已更新%s,%s 還在跑舊版,
|
||||
"${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
|
||||
printf '仍可使用:/jsc-cli:deploy、/jsc-hooks:hooks-install、/jsc-hooks:repair、/jsc-gitea:wiki、/jsc-log:worklog、/jsc-log:learn、/jsc-meta:*、/jsc-ask:ask、/jsc-git:pr、/jsc-git:commit(部署後的異動報告與工作日誌要寫得完,hook 壞掉也要修得回來) | 確定要略過閘門:JSC_RESTART_GATE=off\n' >&2
|
||||
exit 2
|
||||
|
||||
+41
-4
@@ -32,8 +32,30 @@
|
||||
# sdlc-gate.sh wp-report 印出 {owner}/{repo} {index} {上鎖時間} {工作包代號},每個未結清
|
||||
# 工作包各一行;沒有未結清就不印,exit 0。查無歸屬時第四欄留白。
|
||||
# sdlc-gate.sh wp-check prompt hook 模式(UserPromptSubmit):注入提醒,一律 exit 0。
|
||||
# sdlc-gate.sh wp-check skill hook 模式(PreToolUse,matcher Skill):命中別的階段技能時
|
||||
# exit 2 擋下該次呼叫;其餘 exit 0。
|
||||
# sdlc-gate.sh wp-check skill hook 模式(PreToolUse,matcher Skill):命中 analyze 或 maintain
|
||||
# 時 exit 2 擋下該次呼叫;plan 與 implement 只注入提醒後 exit 0,
|
||||
# 其餘技能一律 exit 0。
|
||||
#
|
||||
# 結束碼(一個子命令一列):
|
||||
# lock 0=通過並已上鎖 1=未通過,呼叫端必須停止流程(階段名不合法、讀不到該
|
||||
# 階段的必要標籤、判定不出目前模型、模型不在標籤表上、缺標籤、寫不進狀態檔)
|
||||
# unlock 0=永遠成功,狀態檔不存在也算
|
||||
# check 0=放行 2=擋下該輪提示。安靜放行:沒有階段鎖、舊格式狀態檔讀不出必要標籤、
|
||||
# 必要標籤是 any、判定不出目前模型(只提醒不擋)
|
||||
# report 0=永遠成功,無鎖就不印
|
||||
# wp-lock 0=已記下 2=存取庫不是 {owner}/{repo}、PR 編號不是數字、寫不進狀態檔
|
||||
# wp-unlock 0=已結清,狀態檔不存在也算 2=存取庫或 PR 編號格式錯誤
|
||||
# wp-claim 0=已記下 2=存取庫格式錯誤、工作包代號不帶數字、寫不進領取檔
|
||||
# wp-unclaim 0=已交回,領取檔不存在也算 2=存取庫格式錯誤
|
||||
# wp-report 0=永遠成功,沒有未結清就不印
|
||||
# wp-check prompt 0=永遠放行,只注入提醒
|
||||
# wp-check skill 0=放行 2=擋下該次技能呼叫。安靜放行:逃生門 JSC_WP_GATE=off、沒有未結清
|
||||
# 的工作包、取不到技能名、技能是 plan 或 implement、其餘不在名單上的技能。
|
||||
# 只有 analyze 與 maintain 會 exit 2
|
||||
# 不認得的子命令 0=安靜放行
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會就地結束
|
||||
# 並回 2;check 接在 UserPromptSubmit、wp-check skill 接在 PreToolUse,那一下都是無聲擋人,
|
||||
# 上面那些放行路徑一條都跑不到。
|
||||
#
|
||||
# 鎖檔一個工作包一支($JSC_HOME/wp/{owner}-{repo}-{index}.pr),不是整個存取庫共用一支:
|
||||
# SDLC 實作可能同時有好幾個互不相依的工作包平行進行,各自開各自的 PR。整庫共用一支鎖檔
|
||||
@@ -42,6 +64,15 @@
|
||||
# 跟「implement 挑下一個工作包能不能挑到某一包」是兩件事——後者的判斷依據是該包在分析頁
|
||||
# WBS 表的相依欄,走 jsc-sdlc/tools/wp-gate.sh check-deps,不靠這支鎖檔。
|
||||
#
|
||||
# --- plan 已從擋人名單移出,被放棄的保護寫在這裡 ---
|
||||
#
|
||||
# plan 原本跟 analyze、maintain 一起被擋。現在改成只提醒、照樣放行,放棄的是「在製品上限」:
|
||||
# 手上的工作包還沒結清就不准開新計畫。放棄的理由有兩個。一是 plan 是純邏輯階段,產出是計畫頁,
|
||||
# 不碰程式碼,開一份新計畫不會動到那支未結清的 PR。二是這道閘門手上只有「這個存取庫有 PR
|
||||
# 未合併」這一個事實,判不出新計畫跟那支 PR 有沒有關聯,擋下去多半是誤擋。
|
||||
# 代價要據實看待:沒有東西再擋住計畫越積越多,計畫的產出速度可以快過實作。
|
||||
# analyze 與 maintain 兩道仍在,上限只是晚一個階段才生效。
|
||||
#
|
||||
# --- 狀態檔格式(與 jsc-sdlc/tools/wp-gate.sh 對齊,兩邊都靠這段註解對格式) ---
|
||||
#
|
||||
# 兩種檔案都放在 $JSC_HOME/wp/ 底下,都是純文字 key=value,一行一欄位,順序不拘,
|
||||
@@ -428,11 +459,17 @@ WP_PENDING_EOF
|
||||
# 帶前綴(jsc-sdlc:plan)與裸名(plan)都要認。
|
||||
sname=${skill##*:}
|
||||
case "$sname" in
|
||||
plan|analyze|maintain)
|
||||
analyze|maintain)
|
||||
echo "[jsc][工作包閘門][ERR]:${brief} PR 尚未合併,禁止在此存取庫執行「${sname}」。${foreign:+其中 ${foreign}還不屬於領取中的工作包,更不該由這裡處理。}請先把該 PR 結清(合併或關閉),或執行 jsc-hooks/hooks/sdlc-gate.sh wp-unlock {owner}/{repo} {index} 解除;確定要整體放行請設 JSC_WP_GATE=off。本次技能呼叫已擋下。" >&2
|
||||
exit 2 ;;
|
||||
plan)
|
||||
# plan 只提醒不擋,理由見檔頭「plan 已從擋人名單移出」。放棄的是在製品上限,
|
||||
# 換來的是不再誤擋跟那支 PR 無關的新計畫;analyze 與 maintain 兩道仍在。
|
||||
echo "[jsc][工作包閘門]:${brief} PR 尚未合併。plan 是純邏輯階段、不碰程式碼,這裡只提醒不擋,但新計畫排進實作前請先把它結清。"
|
||||
[ -n "$foreign" ] && echo "[jsc][工作包閘門]:${foreign}不屬於這個存取庫領取中的工作包。那幾支交給領取它的工作階段結清:這裡不要改它的程式碼,也不要回它的留言。"
|
||||
exit 0 ;;
|
||||
implement)
|
||||
# implement 一律放行,連別包的 PR 未結清也放行:結清 PR 正是 implement 步驟 4 要做
|
||||
# implement 一律放行,連別包的 PR 未結清也放行:結清 PR 正是 implement 步驟 2 要做
|
||||
# 的事,擋掉就沒有任何路徑能解除這道鎖。領取檔不綁工作階段,擋下去連領取那一包的
|
||||
# 工作階段都會被自己的舊紀錄擋住,等於把流程鎖死。所以這裡只注入歸屬提醒。
|
||||
[ -n "$foreign" ] && echo "[jsc][工作包閘門]:${foreign}不屬於這個存取庫領取中的工作包。這裡只結清自己領取那一包的 PR${mine:+(${mine})};別包的 PR 不要改、留言也不要回,交給領取它的工作階段。"
|
||||
|
||||
+62
-4
@@ -3,6 +3,17 @@
|
||||
#
|
||||
# 本機版本落後遠端發佈版本時擋下該次技能呼叫,並提示更新指令。
|
||||
#
|
||||
# 結束碼(hook 模式):0=放行 2=擋下該次技能呼叫,訊息走 stderr。
|
||||
# 安靜放行(exit 0)的情況要記清楚,這道閘門絕大多數時候走的是這幾條:逃生門
|
||||
# JSC_VERSION_GUARD=off、工具名取得到但不是 Skill、取不到技能名、技能名不是
|
||||
# jsc-{domain}:{name}、拆不出 domain、命中下方豁免清單那 7 支、讀不到本機實際載入版本、
|
||||
# 推導不出遠端站台、查不到遠端版本、本機版本等於或超前遠端。
|
||||
# 只有「本機落後遠端」這一條會 exit 2。
|
||||
# 結束碼(report、recommend):0=永遠成功,只讀不擋。結論看 stdout,不看結束碼。
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會就地
|
||||
# 結束並回 2,接在 PreToolUse 上就是無聲擋下每一次技能呼叫,上面那些放行路徑一條都跑不到
|
||||
# (write-guard.sh 踩過這個坑)。部署時要確認 hooks/lib.sh 跟這支腳本一起裝上。
|
||||
#
|
||||
# 輸入:stdin JSON(Claude 格式)或環境變數,兩者都收。
|
||||
# 工具名 JSC_TOOL_NAME、TOOL_NAME、stdin 的 tool_name
|
||||
# 技能名 JSC_SKILL、SKILL、stdin 的 skill
|
||||
@@ -30,6 +41,8 @@
|
||||
# deploy 卡在問不出模式那一步,跟直接擋 deploy 是同一種死鎖
|
||||
# jsc-gitea:wiki jsc-ask:ask 問完一定寫回 wiki 才算完成,理由同上一條,
|
||||
# 擋在這一步一樣是 deploy 做不完
|
||||
# jsc-hooks:repair hook 壞掉時唯一的修復路徑。修 hook 的技能被 hook 擋下,
|
||||
# 就沒有任何方法把 hook 修回來,跟直接擋 deploy 是同一種死鎖
|
||||
#
|
||||
# 逃生門:JSC_VERSION_GUARD=off 完全略過檢查(離線工作時用)。
|
||||
#
|
||||
@@ -37,13 +50,26 @@
|
||||
# 預設 600 秒內不重查(JSC_VERSION_TTL 可調)。hook 與 report 在同一支 CLI 內共用。
|
||||
# 舊路徑 $JSC_HOME/version-cache/{domain} 會在第一次讀取時複製到當前 CLI 的新路徑。
|
||||
#
|
||||
# 另有一個非 hook 的子指令:
|
||||
# 另有兩個非 hook 的子指令:
|
||||
# version-guard.sh report 把每個已安裝 jsc-* plugin 的版本比對印成 TSV,每行
|
||||
# 「{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}」,
|
||||
# 最後一行「behind<TAB>{落後個數}」。供 jsc-cli:deploy 判斷要不要
|
||||
# 把「更新」設成推薦選項。report 只讀不擋,永遠 exit 0。
|
||||
# 本機沒有 Claude 的 plugin 註冊檔時改印「noregistry<TAB>{路徑}」
|
||||
# 再接 behind 0:那代表這台機器無法做版本檢查,跟「全部最新」是兩件事。
|
||||
#
|
||||
# version-guard.sh recommend
|
||||
# 把 report 那張表收斂成一個結論,只印一行、只有這一種格式:
|
||||
# recommend<TAB>update|none|unverifiable
|
||||
# 判定規則(呼叫端不必自己再判一次):
|
||||
# update 任何一個 plugin 落後就是 update,一個就夠,不等多數
|
||||
# unverifiable 查不到註冊資訊(noregistry),或一列 domain 都沒有,
|
||||
# 或每一列都是查詢失敗——沒有任何一項查得到的證據
|
||||
# none 至少有一列查得到結果,而且沒有任何一項落後
|
||||
# 查詢失敗的那幾列不計入:查不到不等於最新,也不等於落後,只是沒有證據。
|
||||
# 輸出格式必須穩定,別的 domain 的技能直接讀第二欄;證據表要另外看的話
|
||||
# 再呼叫一次 report,這裡刻意不混印,免得 cut 取值被表格內容打亂。
|
||||
# recommend 只讀不擋,永遠 exit 0:判定結果只看那一行的第二欄。
|
||||
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
||||
|
||||
REG="$HOME/.claude/plugins/installed_plugins.json"
|
||||
@@ -165,7 +191,9 @@ ver_cmp() { # $1=版本 A $2=版本 B
|
||||
}
|
||||
|
||||
# ── report:一次比對所有已安裝的 jsc plugin(非 hook 模式,不讀 stdin)
|
||||
if [ "${1:-}" = "report" ]; then
|
||||
# 抽成函式是為了讓 recommend 讀同一份輸出。判定規則只寫在這一支腳本裡,recommend 直接解析
|
||||
# 這裡印出來的表;兩邊各實作一次比對邏輯就會漂移,結論與證據對不起來。
|
||||
do_report() {
|
||||
# 註冊檔不存在或讀不到就明講。這裡不能只印 behind 0:呼叫端會把它讀成「都是最新」,
|
||||
# 於是把「這台機器無法做版本檢查」誤報成「不用更新」。
|
||||
# 舊版用 `tr -d '\n' < "$REG" 2>/dev/null`,那個 2>/dev/null 只蓋住 tr 的 stderr,
|
||||
@@ -173,7 +201,7 @@ if [ "${1:-}" = "report" ]; then
|
||||
if [ ! -f "$REG" ] || [ ! -r "$REG" ]; then
|
||||
printf 'noregistry\t%s\n' "$REG"
|
||||
printf 'behind\t0\n'
|
||||
exit 0
|
||||
return 0
|
||||
fi
|
||||
ho=$(remote_host_owner)
|
||||
r_host=$(printf '%s' "$ho" | cut -d' ' -f1)
|
||||
@@ -197,6 +225,36 @@ if [ "${1:-}" = "report" ]; then
|
||||
printf '%s\t%s\t%s\t%s\n' "$d" "${lv:-?}" "${rv:-?}" "$st"
|
||||
done
|
||||
printf 'behind\t%s\n' "$behind"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "report" ]; then
|
||||
do_report
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── recommend:把 report 那張表收斂成一個結論(非 hook 模式,不讀 stdin)
|
||||
# 規則的唯一來源就是這一段,jsc-cli:deploy 只讀第二欄,不再自己解那張表。
|
||||
if [ "${1:-}" = "recommend" ]; then
|
||||
rep=$(do_report)
|
||||
verdict=none
|
||||
if printf '%s\n' "$rep" | grep -q '^noregistry '; then
|
||||
# 沒有本機註冊檔,一項都比不了。這跟「全部最新」是兩件事,不能推薦 none。
|
||||
verdict=unverifiable
|
||||
else
|
||||
behind_n=$(printf '%s\n' "$rep" | sed -n 's/^behind //p' | head -n1)
|
||||
# 查得到結果的列:狀態欄是落後、最新或超前三種之一。查詢失敗那幾列不算證據。
|
||||
known=$(printf '%s\n' "$rep" | awk -F'\t' '$1 != "behind" && $1 != "noregistry" && ($4 == "落後" || $4 == "最新" || $4 == "超前")' | wc -l | tr -d ' ')
|
||||
if [ -n "$behind_n" ] && [ "$behind_n" -gt 0 ] 2>/dev/null; then
|
||||
verdict=update
|
||||
elif [ "${known:-0}" -gt 0 ] 2>/dev/null; then
|
||||
verdict=none
|
||||
else
|
||||
# 一列 domain 都沒有,或每一列都查詢失敗:兩種都是「沒有任何查得到的證據」。
|
||||
verdict=unverifiable
|
||||
fi
|
||||
fi
|
||||
printf 'recommend\t%s\n' "$verdict"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -225,7 +283,7 @@ domain=${domain%%:*}
|
||||
|
||||
# 豁免清單
|
||||
case "$skill" in
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-cli:models|jsc-meta:*|jsc-ask:ask|jsc-gitea:wiki) exit 0 ;;
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-hooks:repair|jsc-cli:models|jsc-meta:*|jsc-ask:ask|jsc-gitea:wiki) exit 0 ;;
|
||||
esac
|
||||
|
||||
# 更新指令依實際 CLI 給。印別的 CLI 的指令等於沒給指令,使用者照著打只會失敗。
|
||||
|
||||
Reference in New Issue
Block a user