release: v0.2.6 develop 到 master #38

Merged
admin merged 5 commits from develop into master 2026-08-27 10:58:03 +00:00
Member

PR 描述

摘要

  • 需求描述:把 develop 上的 v0.2.6 放行到預設分支 master,共 5 個 commit。內容是 R15 部署後重啟閘門的狀態檔設計修正:狀態檔從全機器單一檔案改成一支 CLI 一份,落在 $JSC_HOME/restart-required.d/{cli}。這一段是必要的:marketplace 與 hooks/version-guard.sh 都讀存取庫的預設分支 master(version-guard.sh 取 raw plugin.json 時刻意不指定 ref,Gitea 就回預設分支那一份),develop 併了不等於生效,要放行到 master,已安裝的五支 CLI 才抓得到這次修正。修的兩個缺陷都是 2026-08-27 這道閘門上線當日部署時實測抓到的:一、require 用 > 覆寫同一個檔案,並行部署互相覆蓋,實測 kiro 寫入 9 秒後被 codex 蓋掉;二、clear 用 rm -f 刪整個檔案,任一支 CLI 重啟就解除全部五支的閘門,其餘四支沒重啟卻不再被擋——這台機器裝了五支 CLI,等於這道閘門半失效。
  • 計畫名稱:無
  • 計畫頁:無
  • 分析頁:無

變更內容

檔案 為什麼改
hooks/restart-gate.sh 修正主體。狀態檔改為 $JSC_HOME/restart-required.d/{cli},一支 CLI 一份,檔名就是 CLI 代號。新增 cli_code 取當前 CLI 代號並做髒值防護;require 只寫自己那份、取不到代號就 exit 2 並講明沒掛上閘門;hook 判定只讀自己那份、取不到代號一律放行;clear 只刪自己那份,順手刪掉舊格式單一檔案;report 改成一支 CLI 一行,看得出還有哪幾支沒重啟。四行 key=value 的欄位格式不變。
hooks/session-timer.sh 註解補上清除的範圍:清的只有跑到這支腳本的那一支 CLI 自己那一份,別支沒重啟就繼續被擋。判準留在這裡、狀態檔留在 restart-gate.sh,這裡不碰檔案,所以改成一支一份之後這支腳本的邏輯不用跟著改。
tools/wire-cli.sh 冒煙測試加驗「一支 CLI 一份」的範圍,光比結束碼看不出該留的留、該刪的刪。新增 smoke_rs_file 比狀態檔在不在,情境擴充為:當前 CLI 沒有狀態檔、只有別支 CLI 有狀態檔、當前 CLI 那份存在、四支豁免技能、逃生門、取不到技能名,再加清除只刪自己那份、別支那份留著、舊格式單一檔案照樣擋得下來也清得掉。
README.md hook 一覽表與「部署後重啟狀態檔」一節同步:路徑改成狀態目錄、判定與清除的範圍寫清楚、補上 report 的輸出格式與舊檔相容的取捨。非 claude 四支的覆蓋範圍說明也跟著改成「那一支自己那份」。
skills/hooks-install/SKILL.md 技能文件的重啟閘門段落同步一支 CLI 一份、report 的用途、舊檔一律擋且下次 clear 刪掉。豁免九支的清單不變。
plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json 三份 manifest 版本升到 0.2.6。

設計重點

  • 一支 CLI 一份的理由:這道閘門管的是「這支 CLI 的行程還在跑舊版」,那是每支 CLI 各自的事實。一台機器上五支 CLI 是五個獨立行程,各自載入自己記憶體裡的那一版,所以寫入、判定、清除三件事都只能碰自己那一份。抽出來的通則:跨 CLI 或跨工作階段的狀態檔,設計時先問清楚那個事實屬於誰。
  • 職責分界不動:狀態檔的路徑、範圍與格式的唯一來源仍是 hooks/restart-gate.sh;新舊工作階段的判準仍只在 hooks/session-timer.sh。兩邊都不抄對方那一半,這也是為什麼 session-timer.sh 這次只改註解。
  • 髒值防護:CLI 代號直接拿去當檔名,所以含 /、以 . 開頭、或帶 [A-Za-z0-9._-] 以外字元的值一律當成取不到,不寫檔也不判定。實測 JSC_CLI=../evil、a/b、.、.. 四種都回 exit 2,狀態目錄外沒有多出任何檔案。
  • 舊檔相容(過渡用):舊格式的單一檔案 $JSC_HOME/restart-required 存在時一律擋,視為每一支 CLI 都有未重啟的部署——舊檔沒有 per-CLI 資訊,分不出是哪一支寫的,寧可擋多不擋少,擋人訊息會標明這是舊格式紀錄。clear 除了刪自己那份也刪掉舊檔:clear 只在新工作階段被呼叫,呼叫到就代表確實有一支重新啟動過了,舊檔留著會讓五支一路被擋到有人手動刪。可移除的時機(所有機器都跑過一次寫狀態目錄的部署與重啟之後)寫在檔頭與 README,連要刪哪幾段都列了。
  • 沒有證據就放行:判定原則比照 version-guard.sh,這次從三種放行擴充為四種——狀態檔讀不到、CLI 代號取不到、技能名取不到、工具名不是 Skill。
  • 升級後的行為差異:重啟一支 CLI 只解除那一支的閘門,其餘的照樣被擋、要各自重啟;restart-gate.sh report 現在看得出哪幾支還沒重啟。仍然只有 claude 擋得住——codex、copilot、antigravity、kiro 沒有 pre-tool hook,狀態檔照樣寫、照樣清,但中間沒有判定點。

測試結果

  • tools/ste100-lint.sh 掃本存取庫全綠。
  • 三份 manifest(plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json)版本一致為 0.2.6。
  • sh -n 對改動過的腳本(hooks/restart-gate.sh、hooks/session-timer.sh、tools/wire-cli.sh)全部通過。
  • 核心隔離實測:kiro 與 codex 各跑一次 require,產生兩份獨立狀態檔、不互相覆蓋;三支 CLI 各只看得到自己那一份;kiro clear 之後只剩 codex 那一份;重啟後 kiro 放行而 codex 仍被擋。最後這一列正是舊版壞掉的行為,主 agent 另外獨立複驗過一次,結果一致。
  • 舊檔相容實測:只有舊檔時三支 CLI 全擋、訊息標明是舊格式紀錄;clear 之後舊檔被刪;再判定一次放行。
  • 放行路徑實測:九支豁免技能全放行、逃生門 JSC_RESTART_GATE=off 放行、取不到技能名放行、取不到 CLI 代號放行。
  • 髒值防護實測:JSC_CLI=../evil、a/b、.、.. 四種全部 exit 2,狀態目錄外沒有多出檔案。
  • 所有子命令在 </dev/null 加 timeout 之下都不卡住。
  • tools/wire-cli.sh smoke claude 回 status=ok,重啟閘門 16 條專屬情境加通用執行共 17 行全部與預期相同。
  • 沒跑的據實說明:非 claude 四支 CLI 上這道閘門無法測試——它們沒有 pre-tool hook,沒有判定點可測,不是還沒測。
  • 這批放行本身沒有重跑腳本測試。上面這些是在 fix PR 階段跑的(hooks#37),本 PR 只是把已驗過的 develop 內容搬到 master,沒有新的程式碼變更。

前置 Push Request

  • 無。三支 fix PR(hooks#37、cli#30、meta#28)都已合併進各自的 develop。
# PR 描述 ## 摘要 - 需求描述:把 `develop` 上的 v0.2.6 放行到預設分支 `master`,共 5 個 commit。內容是 R15 部署後重啟閘門的狀態檔設計修正:狀態檔從全機器單一檔案改成一支 CLI 一份,落在 `$JSC_HOME/restart-required.d/{cli}`。這一段是必要的:marketplace 與 `hooks/version-guard.sh` 都讀存取庫的預設分支 `master`(`version-guard.sh` 取 raw `plugin.json` 時刻意不指定 ref,Gitea 就回預設分支那一份),`develop` 併了不等於生效,要放行到 `master`,已安裝的五支 CLI 才抓得到這次修正。修的兩個缺陷都是 2026-08-27 這道閘門上線當日部署時實測抓到的:一、`require` 用 `>` 覆寫同一個檔案,並行部署互相覆蓋,實測 kiro 寫入 9 秒後被 codex 蓋掉;二、`clear` 用 `rm -f` 刪整個檔案,任一支 CLI 重啟就解除全部五支的閘門,其餘四支沒重啟卻不再被擋——這台機器裝了五支 CLI,等於這道閘門半失效。 - 計畫名稱:無 - 計畫頁:無 - 分析頁:無 ## 變更內容 | 檔案 | 為什麼改 | | --- | --- | | `hooks/restart-gate.sh` | 修正主體。狀態檔改為 `$JSC_HOME/restart-required.d/{cli}`,一支 CLI 一份,檔名就是 CLI 代號。新增 `cli_code` 取當前 CLI 代號並做髒值防護;`require` 只寫自己那份、取不到代號就 exit 2 並講明沒掛上閘門;hook 判定只讀自己那份、取不到代號一律放行;`clear` 只刪自己那份,順手刪掉舊格式單一檔案;`report` 改成一支 CLI 一行,看得出還有哪幾支沒重啟。四行 key=value 的欄位格式不變。 | | `hooks/session-timer.sh` | 註解補上清除的範圍:清的只有跑到這支腳本的那一支 CLI 自己那一份,別支沒重啟就繼續被擋。判準留在這裡、狀態檔留在 `restart-gate.sh`,這裡不碰檔案,所以改成一支一份之後這支腳本的邏輯不用跟著改。 | | `tools/wire-cli.sh` | 冒煙測試加驗「一支 CLI 一份」的範圍,光比結束碼看不出該留的留、該刪的刪。新增 `smoke_rs_file` 比狀態檔在不在,情境擴充為:當前 CLI 沒有狀態檔、只有別支 CLI 有狀態檔、當前 CLI 那份存在、四支豁免技能、逃生門、取不到技能名,再加清除只刪自己那份、別支那份留著、舊格式單一檔案照樣擋得下來也清得掉。 | | `README.md` | hook 一覽表與「部署後重啟狀態檔」一節同步:路徑改成狀態目錄、判定與清除的範圍寫清楚、補上 `report` 的輸出格式與舊檔相容的取捨。非 claude 四支的覆蓋範圍說明也跟著改成「那一支自己那份」。 | | `skills/hooks-install/SKILL.md` | 技能文件的重啟閘門段落同步一支 CLI 一份、`report` 的用途、舊檔一律擋且下次 `clear` 刪掉。豁免九支的清單不變。 | | `plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` | 三份 manifest 版本升到 0.2.6。 | ## 設計重點 - **一支 CLI 一份的理由**:這道閘門管的是「這支 CLI 的行程還在跑舊版」,那是每支 CLI 各自的事實。一台機器上五支 CLI 是五個獨立行程,各自載入自己記憶體裡的那一版,所以寫入、判定、清除三件事都只能碰自己那一份。抽出來的通則:跨 CLI 或跨工作階段的狀態檔,設計時先問清楚那個事實屬於誰。 - **職責分界不動**:狀態檔的路徑、範圍與格式的唯一來源仍是 `hooks/restart-gate.sh`;新舊工作階段的判準仍只在 `hooks/session-timer.sh`。兩邊都不抄對方那一半,這也是為什麼 `session-timer.sh` 這次只改註解。 - **髒值防護**:CLI 代號直接拿去當檔名,所以含 `/`、以 `.` 開頭、或帶 `[A-Za-z0-9._-]` 以外字元的值一律當成取不到,不寫檔也不判定。實測 `JSC_CLI=../evil`、`a/b`、`.`、`..` 四種都回 exit 2,狀態目錄外沒有多出任何檔案。 - **舊檔相容(過渡用)**:舊格式的單一檔案 `$JSC_HOME/restart-required` 存在時一律擋,視為每一支 CLI 都有未重啟的部署——舊檔沒有 per-CLI 資訊,分不出是哪一支寫的,寧可擋多不擋少,擋人訊息會標明這是舊格式紀錄。`clear` 除了刪自己那份也刪掉舊檔:`clear` 只在新工作階段被呼叫,呼叫到就代表確實有一支重新啟動過了,舊檔留著會讓五支一路被擋到有人手動刪。可移除的時機(所有機器都跑過一次寫狀態目錄的部署與重啟之後)寫在檔頭與 README,連要刪哪幾段都列了。 - **沒有證據就放行**:判定原則比照 `version-guard.sh`,這次從三種放行擴充為四種——狀態檔讀不到、CLI 代號取不到、技能名取不到、工具名不是 Skill。 - **升級後的行為差異**:重啟一支 CLI 只解除那一支的閘門,其餘的照樣被擋、要各自重啟;`restart-gate.sh report` 現在看得出哪幾支還沒重啟。仍然只有 claude 擋得住——codex、copilot、antigravity、kiro 沒有 pre-tool hook,狀態檔照樣寫、照樣清,但中間沒有判定點。 ## 測試結果 - `tools/ste100-lint.sh` 掃本存取庫全綠。 - 三份 manifest(`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`)版本一致為 0.2.6。 - `sh -n` 對改動過的腳本(`hooks/restart-gate.sh`、`hooks/session-timer.sh`、`tools/wire-cli.sh`)全部通過。 - 核心隔離實測:kiro 與 codex 各跑一次 `require`,產生兩份獨立狀態檔、不互相覆蓋;三支 CLI 各只看得到自己那一份;kiro `clear` 之後只剩 codex 那一份;重啟後 kiro 放行而 **codex 仍被擋**。最後這一列正是舊版壞掉的行為,主 agent 另外獨立複驗過一次,結果一致。 - 舊檔相容實測:只有舊檔時三支 CLI 全擋、訊息標明是舊格式紀錄;`clear` 之後舊檔被刪;再判定一次放行。 - 放行路徑實測:九支豁免技能全放行、逃生門 `JSC_RESTART_GATE=off` 放行、取不到技能名放行、取不到 CLI 代號放行。 - 髒值防護實測:`JSC_CLI=../evil`、`a/b`、`.`、`..` 四種全部 exit 2,狀態目錄外沒有多出檔案。 - 所有子命令在 `</dev/null` 加 `timeout` 之下都不卡住。 - `tools/wire-cli.sh smoke claude` 回 `status=ok`,重啟閘門 16 條專屬情境加通用執行共 17 行全部與預期相同。 - 沒跑的據實說明:非 claude 四支 CLI 上這道閘門無法測試——它們沒有 pre-tool hook,沒有判定點可測,不是還沒測。 - 這批放行本身沒有重跑腳本測試。上面這些是在 fix PR 階段跑的(hooks#37),本 PR 只是把已驗過的 `develop` 內容搬到 `master`,沒有新的程式碼變更。 ## 前置 Push Request - 無。三支 fix PR(hooks#37、cli#30、meta#28)都已合併進各自的 `develop`。
jiantw83 added 5 commits 2026-08-27 10:56:27 +00:00
What:`hooks/restart-gate.sh` 的狀態檔從全機器單一檔案 `$JSC_HOME/restart-required` 改為狀態目錄 `$JSC_HOME/restart-required.d/{CLI 代號}`,一支 CLI 一份,檔名就是 CLI 代號,四行 key=value 的格式不變。新增 `cli_code()` 取當前 CLI 代號(含不能當檔名的髒值防護)與 `state_line()` 統一 `report` 的輸出格式;`require` 只寫自己那一份、hook 判定只讀自己那一份、`clear` 只刪自己那一份、`report` 一支 CLI 一行印出還沒重啟的是哪幾支。舊格式的單一檔案存在時一律擋,訊息標明是舊格式紀錄,`clear` 會一併刪掉它。檔頭註解同步改寫,新增「report 輸出格式」與「舊檔相容(過渡用)」兩節。`hooks/session-timer.sh` 程式碼不動,兩處註解補上「清除範圍只有跑到這支腳本的那一支 CLI」。

Why:這道閘門 2026-08-27 才上線,部署現場實測抓到兩個缺陷。第一,`require` 用 `>` 覆寫單一檔案,並行部署互相覆蓋——kiro 寫入 9 秒後被 codex 蓋掉,`domains=` 與 `cli=` 不再代表 kiro。第二,`clear` 用 `rm -f` 刪整個檔案,任一支 CLI 重啟就解除全部五支的閘門,其餘四支沒重啟卻不再被擋。這台機器就裝了五支 CLI,閘門在多 CLI 環境等於半失效。閘門管的是「這一支 CLI 的行程還在跑舊版」,那是每支 CLI 各自的事實,狀態檔本來就不該共用。

How:寫入、判定、清除三件事都只碰自己那一份,別支那幾份一律不看,`clear` 的範圍也就收斂成呼叫端那一支。取不到 CLI 代號時比照既有的「查不到就放行」原則:hook 模式放行,`require` 回 exit 2 並講明這次沒有掛上閘門——寫到讀不到的檔名等於沒掛,不能讓部署以為掛上了。代號會直接拿去當檔名,所以含 `/`、以 `.` 開頭、或出現 `[A-Za-z0-9._-]` 以外字元的值一律當成取不到,狀態檔就寫不到目錄外面去。舊檔沒有 per-CLI 資訊,分不出是哪一支寫的,判定寧可擋多不擋少;`clear` 只在新工作階段被呼叫,呼叫到就代表確實有一支重新啟動過了,舊檔留著會讓五支一路被擋到有人手動刪,所以一併刪掉。這段相容邏輯與可移除的時機(所有機器都跑過一次寫狀態目錄的部署與重啟之後)寫在檔頭「舊檔相容」與 README。`session-timer.sh` 不必跟著改:狀態檔的路徑、範圍與格式只留在 `restart-gate.sh`,那裡只負責判斷新舊工作階段。

Who:`jsc-hooks` 的部署後強制重啟閘門(R15),以及它與 `session-timer.sh` 的清除分工。
What:`tools/wire-cli.sh` 的 `smoke` 重啟閘門段從 9 條情境擴為 16 條,新增 `smoke_rs_file()` 比對狀態檔在不在。新增的情境是:只有別支 CLI 有狀態檔時放行、`require` 寫出的是當前 CLI 那一份、新工作階段開始後只清掉自己那一份、清除不動別支那一份、清除後放行、舊格式單一狀態檔存在時擋下、`clear` 一併刪掉舊檔、舊檔清除後放行。原有的豁免技能、逃生門與取不到技能名三類情境照舊,情境名稱從「狀態檔存在」改寫為「當前 CLI 那份存在」。檔頭的判定路徑清單同步改寫。

Why:只比對結束碼看不出「一支 CLI 一份」有沒有真的成立——別支那一份要留著、自己那一份要刪掉,兩件事都反映在檔案在不在,不反映在結束碼。舊版的冒煙只驗得到「狀態檔不存在」與「狀態檔存在」兩條,正好漏掉這次修掉的兩個缺陷所在的範圍:並行部署的覆寫與清除的全域解除,兩者在單一檔案的年代都跑得出 exit 0 與 exit 2 的正確值。

How:情境沿用暫時的 `$JSC_HOME`,不動使用者真正的狀態目錄——冒煙測試不該把別人的閘門拆掉。別支 CLI 的代號取 `smoke-other` 這個不在五支之列的固定值,才不會跟這一輪的 `$cli` 撞在一起。`smoke_rs_file()` 只比 `exist` 與 `absent` 兩種預期,失敗訊息直接講明「一支 CLI 一份的範圍壞了」,看訊息就知道是範圍出錯,不是腳本跑不動。舊檔那兩條情境走 `session-timer.sh restart` 觸發清除,跟真實路徑一致。

Who:`jsc-hooks` 接線工具的冒煙測試,覆蓋部署後重啟閘門的判定與清除範圍。
What:`README.md` 改三處——Hooks 表的 `restart-gate.sh` 與 `session-timer.sh` 兩列、覆蓋範圍的引言、以及「部署後重啟狀態檔」整節。整節改寫後寫明狀態檔路徑是 `$JSC_HOME/restart-required.d/{CLI 代號}`、一支 CLI 一份的理由、`report` 的輸出格式,以及舊檔相容與它可移除的時機。`skills/hooks-install/SKILL.md` 第 45 項改為 `restart-required.d/{cli}`,補上「每支 CLI 只讀自己那一份」、「重啟只清自己那一份」、「`report` 一支 CLI 一行」與舊檔過渡說明。

Why:這兩份是接線與判讀閘門時唯一會被讀到的說明。路徑改了而文件沒改,看文件的人會去看一個不存在的檔案,也會沿用「重啟一支就全清」的舊認知去判斷閘門有沒有生效。一支 CLI 一份的理由(並行覆寫與全域解除兩個實測缺陷)要留在文件裡,下一個人才不會為了少一個目錄又改回單一檔案。

How:只改說明,不動任何判定邏輯。路徑與格式的唯一來源仍是 `hooks/restart-gate.sh`,文件只轉述,舊檔相容的取捨與可移除清單也指回檔頭那一節,兩邊不各自維護一份。覆蓋範圍的引言照舊據實寫:非 claude 的四支 CLI 沒有 pre-tool hook,這道閘門一次都擋不下來,改成一支一份也不會變。

Who:`jsc-hooks` 的存取庫說明與 `jsc-hooks:hooks-install` 技能文件,對齊部署後重啟閘門的實作。
What:`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 的 `version` 從 0.2.5 升到 0.2.6,三份同步,description 不動。

Why:這一輪改掉重啟閘門的狀態檔路徑與範圍,是使用者裝上去就會拿到的行為變更。版本不升,`version-guard.sh` 的版本前置檢查與 `jsc-cli:deploy` 的落後判定都看不出本機還是舊版,機器上就不會被提示更新。

How:只改版號一個欄位。三份必須一致:`plugin.json` 給 marketplace、`.claude-plugin` 給 claude、`.codex-plugin` 給 codex,任一份落後都會讓那一路的版本比對抓錯。修的是既有行為的缺陷、沒有新增子命令也沒有改變呼叫介面,所以走修訂號。

Who:`jsc-hooks` 的三份 plugin manifest,配合這一輪重啟閘門的修正發佈。
Reviewed-on: #37
admin approved these changes 2026-08-27 10:58:01 +00:00
admin merged commit 668880e0f4 into master 2026-08-27 10:58:03 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/hooks#38