feat/skillset-governance/main
develop
restart-gate.sh
session-timer.sh
knowledges/QUESTION
QUESTION_FB8DF0B5
hooks/restart-gate.sh
$JSC_HOME/restart-required
require
clear
report
hooks/session-timer.sh
start
restart
restart-gate.sh clear
hooks/hooks.json
PreToolUse
Skill
version-guard.sh
tools/wire-cli.sh
skills/hooks-install/SKILL.md
degraded
README.md
AGENTS.md
JSC_RESTART_GATE
plugin.json
.claude-plugin/plugin.json
.codex-plugin/plugin.json
jsc-ask:ask
jsc-git:pr
jsc-git:commit
at
mode
domains
cli
jsc-cli
jsc-cli:deploy
jsc-hooks:hooks-install
jsc-gitea:wiki
jsc-log:worklog
jsc-log:learn
jsc-meta:*
deploy
jsc-meta
pr
jsc-log
sessions/{sid}.start
jsc-{domain}:{name}
sdlc-gate.sh
read_stdin
</dev/null
cli_bin
JSC_RESTART_GATE=off
tools/ste100-lint.sh
jsc-sdlc:implement
$JSC_HOME
session-timer.sh start
tools/wire-cli.sh smoke claude
status=ok
wire-cli.sh
reason
hooks-install
tools/deploy.sh
hooks/restart-gate.sh require
What:新增第八支 hook `hooks/restart-gate.sh`。hook 模式在 `$JSC_HOME/restart-required` 存在時以 exit 2 擋下 jsc 技能呼叫,另有三個子命令:`require {模式} [{domain}...]` 寫入狀態檔掛上閘門、`clear` 清除狀態檔、`report` 印出狀態檔內容。`hooks/hooks.json` 把它接到 PreToolUse(Skill),排在 `version-guard.sh` 前面。`hooks/session-timer.sh` 的 `start` 與 `restart` 在判定為新工作階段時轉呼叫 `clear`。 Why:部署換掉的是磁碟上的技能檔,正在跑的 CLI 行程載入的還是舊版——SKILL.md、hook 腳本與 tools 都在啟動當下讀進記憶體。這段落差期間跑技能,改動看起來沒生效,人會以為部署失敗又重跑一次。所以部署收尾要求重新啟動,這道閘門負責讓「還沒重啟就繼續用技能」擋在門外。 How:判定看的是「檔案在不在」,欄位只用在擋人訊息上——欄位缺了只讓訊息少幾個字,不影響判定。狀態檔用純文字 `key=value`(`at`、`mode`、`domains`、`cli`),格式與工作包狀態檔同一套,`jsc-hooks` 與 `jsc-cli` 兩邊各自實作也對得上。放行原則比照 `version-guard.sh`:只擋確定違規,狀態檔讀不到、技能名取不到、工具名不是 `Skill`、技能不是 `jsc-*:*` 一律 exit 0,沒有證據時擋下等於停掉每一次技能呼叫。九支豁免(`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`)的理由是同一件事:部署後還要寫得完技能組異動報告與工作日誌,整批擋下去「先重啟」與「先寫完報告」會互相打死。後三支自己不是收尾規則的主體,是為了讓前六支走得完才補進來的——清單認的是技能名,不是呼叫鏈。清除交給 `session-timer.sh`,不由本檔自己判:新舊工作階段的判準(`sessions/{sid}.start` 在不在)只有那支腳本知道,兩邊各寫一份就會漂移;續接同一階段走不到那一段,閘門就一路留到真的重新啟動。`require`、`clear`、`report` 都不讀標準輸入,只有 hook 模式讀,工具端呼叫一律再補 `</dev/null`,理由與 `sdlc-gate.sh` 相同。接線排在 `version-guard.sh` 前面:還沒重啟的舊版比落後一個版號更該先攔。不認得的子命令一律安靜 exit 0,不中斷宿主 CLI。擋人訊息依實際 CLI 給重啟方式,並附上豁免清單與逃生門 `JSC_RESTART_GATE=off`。 Who:部署後的第一次技能呼叫,以及 `jsc-cli:deploy` 收尾寫入狀態檔的那一端。
What:`tools/wire-cli.sh` 四處增修。`smoke` 加跑 `restart-gate.sh`,另外自備一份暫時的 `$JSC_HOME`,把八條判定路徑(狀態檔不存在、狀態檔存在且技能為 `jsc-sdlc:implement`、四支豁免技能各一條、逃生門、取不到技能名)各跑一次並比對結束碼,再驗一次清除機制真的清得掉;claude 的接線驗證與 `status` 盤點各加一個 `restart-gate` 檢查點;codex、copilot、antigravity、kiro 四支的 degraded 說法補上「部署後重啟閘門也接不上」;檔頭與各處「七支」一律改為「八支」。 Why:新的 hook 接進 `hooks.json` 只代表宣告在檔案裡。接線驗證不點名就漏得掉——`comment-scope.sh` 與 `lang-guard.sh` 當初就是為同一個原因各列一項。冒煙測試也一樣:不自備狀態檔,只走得到「狀態檔不存在」與「取不到技能名」兩條捷徑,擋人與豁免那幾條一次都跑不到,判定寫了卻沒驗等於沒寫。四個 CLI 的降級說法不補,回報就會暗示每個 CLI 都擋得下來。 How:驗的是判定結果本身,不只是腳本跑得完——每一條路徑都給定預期結束碼,對不上就計入失敗並印出前 200 字的輸出。狀態檔放在暫時目錄,冒煙測試不該把使用者真正的 `$JSC_HOME/restart-required` 拆掉。清除機制那一段刻意走 `session-timer.sh start` 這條真實路徑,不直接呼叫 `clear`:要驗的是「新工作階段會不會清」,不是「`clear` 這個子命令能不能刪檔」。每次呼叫都接 `</dev/null`:hook 模式會讀標準輸入,管線沒人關閉時整支會卡死。建不出暫存目錄也算失敗,不能靜悄悄跳過。四支非 claude 的 CLI 除了 degraded 一句,另外逐支印一行講明後果:一次技能呼叫都擋不下來,狀態檔照樣寫、下次工作階段開始照樣清,只是中間沒有判定點,重啟要靠 `/jsc-cli:deploy` 收尾的提示自己動手。 Who:`jsc-hooks:hooks-install` 的接線、冒煙與盤點三個子命令。
What:`skills/hooks-install/SKILL.md` 四處增修。`description` 與目標段落的 hook 清單加入 `restart-gate.sh`,七支改八支;降級說明補上「四個 CLI 連部署後重啟閘門也接不上」與後果一句;第 4 步的冒煙說明補上重啟閘門的每條判定路徑;備註新增 `restart-gate.sh` 一條,並在 `session-timer.sh` 那一條補上「`start` 與 `restart` 會清除閘門」。 Why:技能是接線這件事的對外說法。hook 加了一支、技能還寫七支,回報就會少一項,而且降級說法不補會暗示每個 CLI 都擋得下來——這正是準則明文禁止的。`session-timer.sh` 那條也要補:清除閘門掛在那兩個事件上,接線少了它們,閘門會一路擋到使用者自己下逃生門。 How:降級那一段把後果講明白,不只說「接不上」:在那四個 CLI 上一次技能呼叫都擋不下來,狀態檔照樣寫、下一個工作階段開始照樣清,重啟本身只靠 `jsc-cli:deploy` 的收尾訊息。備註那一條寫出豁免清單與逃生門,並講明豁免的理由是異動報告與工作日誌要寫得完,讓接線的人知道哪些技能在閘門升起時仍然叫得動。 Who:`/jsc-hooks:hooks-install` 的接線流程與對使用者的回報。
What:`README.md` 五處增修:hook 表新增 `hooks/restart-gate.sh` 一列,`session-timer.sh` 那一列補上會清除閘門、`wire-cli.sh` 那一列補上冒煙新增的判定路徑;覆蓋範圍那段的「七支」改「八支」並補上四個 CLI 接不上重啟閘門的後果;新增「部署後重啟狀態檔」一節,用表列出四個欄位與範例,寫明誰寫誰讀;環境變數表新增 `JSC_RESTART_GATE` 一列;`hooks-install` 段落同步。`AGENTS.md` 的 domain 一句話說明補上「部署後強制重啟閘門」。 Why:狀態檔的格式是 `jsc-hooks` 與 `jsc-cli` 兩邊的介面。介面只寫在腳本註解裡,另一邊改的時候看不到,格式一走鐘閘門就掛不上——這次就真的發生過:`jsc-cli` 寫四欄 TSV、這邊讀 `key=value`,狀態檔存在卻解不出欄位。覆蓋範圍也要據實寫,八支 hook 只有 claude 全接得上。 How:格式壓到最簡的純文字 `key=value`,一行一欄位,順序不拘,不認得的鍵一律忽略,與工作包狀態檔同一套寫法,兩邊各自實作也對得上。表只寫欄位與範例,判定規則寫在表底下:判定看檔案在不在、欄位只用在擋人訊息上、寫的一律是 `jsc-cli:deploy`、清的一律是 `session-timer.sh`,而且判準留在 `session-timer.sh`、狀態檔留在 `restart-gate.sh`,兩邊都不抄對方那一半。 Who:讀 `jsc-hooks` 說明的人,以及 `jsc-cli` 那一側寫 `deploy.sh` 的人。
What:`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 的 `version` 由 0.2.3 改為 0.2.4,`description` 的 hook 清單從五支補齊到八支,補上註解範圍守門、繁中編碼守門與部署後強制重啟。 Why:本次新增第八支 hook `restart-gate.sh` 並接進 `hooks.json`,`session-timer.sh` 多了清除閘門這件事,接線與冒煙也跟著改,屬於行為變更,版本要跟著往上走,各 CLI 才知道要更新。`description` 原本只列到「版本前置檢查」,而註解範圍與繁中編碼兩支早就在跑了,`README.md` 與 `AGENTS.md` 也都寫了八支——manifest 是各 CLI 安裝時唯一看得到的說明,落後就會讓人以為這個外掛只有五支 hook。 How:三份只改 `version` 與 `description` 兩個欄位,其餘內容不動,三份保持同一版號與同一段描述。 Who:`jsc-hooks` 外掛的套件描述檔。
Reviewed-on: #33
What:`skills/hooks-install/SKILL.md` 的 Notes 段落,重啟閘門的豁免技能清單從六支補到九支,補上 `jsc-ask:ask`、`jsc-git:pr`、`jsc-git:commit`,並寫明「閘門認技能名不認呼叫鏈」以及清單的唯一來源在 `hooks/restart-gate.sh`。 Why:這三支是使用者在同一輪追問後才裁定加入的,實作 `hooks/restart-gate.sh` 與準則 `guidelines.md`「部署後重啟閘門」都已經是九支,只有這份技能文件還停在六支。技能文件是接線時唯一會被讀到的說明,少列三支會讓人以為 `deploy` 問模式、收尾開 PR 都會被擋,反而去下逃生門。 How:只改那一行,補上三支與兩句說明,並指向清單的唯一來源,避免下次又各自維護一份。三份 manifest 版本同步升到 0.2.5。 Who:`jsc-hooks:hooks-install` 技能文件,以及部署後重啟閘門這條規則的說明一致性。
Reviewed-on: #34 Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
No dependencies set.
The note is not visible to the blocked user.
PR 描述
摘要
restart-gate.sh把「還沒重啟就繼續用技能」擋在門外,並由session-timer.sh在判定為新工作階段時清除。決策紀錄在 wikiknowledges/QUESTION的QUESTION_FB8DF0B5(2026-08-27 兩節共 12 題)。本 PR 是主幹feat/skillset-governance/main併回develop的釋出 PR,內容為已合併的子功能 PR #33。變更內容
hooks/restart-gate.sh$JSC_HOME/restart-required存在時擋下 jsc 技能呼叫(exit 2),並提供require、clear、report三個子命令給工具端呼叫。狀態檔格式與判定全在這一支,是唯一真實來源hooks/session-timer.shstart與restart兩個子命令判定為新工作階段時轉呼叫restart-gate.sh clear。清除掛在這裡,因為新舊工作階段的判準只有這支腳本知道hooks/hooks.jsonPreToolUse的Skillmatcher 加上restart-gate.sh,排在version-guard.sh前面tools/wire-cli.shskills/hooks-install/SKILL.mddegraded的理由補上重啟閘門接不上,並寫明狀態檔照樣寫、照樣清,只是中間沒有判定點README.md、AGENTS.mdJSC_RESTART_GATE逃生門plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.jsonskills/hooks-install/SKILL.md(#34)jsc-ask:ask、jsc-git:pr、jsc-git:commit,並寫明閘門認技能名不認呼叫鏈。實作與準則在 #33 就已是九支,只有這份技能文件落後設計重點
at、mode、domains、cli),欄位只用在擋人訊息上;檔案存在就是還沒重啟過的證據。欄位缺了只讓訊息少幾個字,不影響判定。格式壓到最簡,是為了讓jsc-cli那端也對得上。jsc-cli:deploy、jsc-hooks:hooks-install、jsc-gitea:wiki、jsc-log:worklog、jsc-log:learn、jsc-meta:*),但豁免技能轉呼叫非豁免技能照樣被擋:deploy要問模式必須呼叫jsc-ask:ask、jsc-meta四支異動技能的收尾要開 PR 必須呼叫jsc-git:pr(而pr的第一步是jsc-git:commit)。使用者裁定補上這三支,共九支。version-guard.sh當年把jsc-ask:ask收進豁免,也是同一個原因。jsc-gitea:wiki與jsc-log。整批擋下去,「先重啟」與「先寫完報告」會互相打死,使用者兩件事都做不完。這正是同批加進jsc-meta稽核清單的流程檢查第 4 項要抓的東西。sessions/{sid}.start在不在)只有session-timer.sh知道,兩邊各寫一份就會漂移。所以清除由session-timer.sh的start與restart分支轉呼叫,續接同一階段(SessionStart 再觸發、resume、compact)不會走到那一段,閘門就一路留到真的重新啟動。version-guard.sh:狀態檔讀不到、技能名取不到、工具名不是Skill,三種都 exit 0。沒有證據時擋下等於停掉每一次技能呼叫。另外只管jsc-{domain}:{name}形式的技能,別人的技能不受影響。require寫不進去要出聲。 寫失敗回 exit 2 並印錯誤,因為沒寫成就沒有閘門,部署那端卻會以為掛上了。clear、report一律 exit 0,檔案不存在也算成功。require、clear、report都不讀 stdin,只有 hook 模式讀。理由與sdlc-gate.sh相同:標準輸入是管線又沒人關閉時read_stdin會一直等,工具端呼叫就整支卡死。工具端呼叫一律再補</dev/null。cli_bin,取不到就寫「目前的 CLI」。印別的 CLI 的執行檔名等於沒給指示。訊息同時列出九支仍可使用的技能與逃生門。JSC_RESTART_GATE=off完全略過整道閘門。測試結果
tools/ste100-lint.sh掃本存取庫全綠。plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json)版本一致,皆為 0.2.4。hooks/restart-gate.sh的判定逐條實測,各自比對退出碼:狀態檔不存在放行、狀態檔存在時擋下jsc-sdlc:implement(exit 2)、九支豁免技能在狀態檔存在時全部放行、逃生門JSC_RESTART_GATE=off放行、取不到技能名放行。require寫出狀態檔後,以暫時的$JSC_HOME跑session-timer.sh start,狀態檔確實被清除。tools/wire-cli.sh smoke claude回status=ok,14 項判定路徑的退出碼與預期全部相同。冒煙用暫時目錄自備狀態檔,不會動到使用者真正的$JSC_HOME/restart-required。PreToolUse,沒有任何判定點,所以是「無法測試」而不是「還沒測試」。狀態檔照樣寫、下次工作階段開始照樣清,中間沒有守門,重啟只能靠jsc-cli:deploy收尾的提示由使用者自己動手。wire-cli.sh對這四支一律回報degraded並在reason講明,hooks-install的敘述也照這個說法回報,不暗示每個 CLI 都有保護。前置 Push Request
jsc-cli的tools/deploy.sh轉呼叫本存取庫的hooks/restart-gate.sh require,因此 cli 那支釋出 PR 依賴本 PR,本 PR 自己沒有前置。子功能 PR #33 已合併進主幹feat/skillset-governance/main,本 PR 只是把主幹併回develop,沒有未結清的前置 PR。What:新增第八支 hook `hooks/restart-gate.sh`。hook 模式在 `$JSC_HOME/restart-required` 存在時以 exit 2 擋下 jsc 技能呼叫,另有三個子命令:`require {模式} [{domain}...]` 寫入狀態檔掛上閘門、`clear` 清除狀態檔、`report` 印出狀態檔內容。`hooks/hooks.json` 把它接到 PreToolUse(Skill),排在 `version-guard.sh` 前面。`hooks/session-timer.sh` 的 `start` 與 `restart` 在判定為新工作階段時轉呼叫 `clear`。 Why:部署換掉的是磁碟上的技能檔,正在跑的 CLI 行程載入的還是舊版——SKILL.md、hook 腳本與 tools 都在啟動當下讀進記憶體。這段落差期間跑技能,改動看起來沒生效,人會以為部署失敗又重跑一次。所以部署收尾要求重新啟動,這道閘門負責讓「還沒重啟就繼續用技能」擋在門外。 How:判定看的是「檔案在不在」,欄位只用在擋人訊息上——欄位缺了只讓訊息少幾個字,不影響判定。狀態檔用純文字 `key=value`(`at`、`mode`、`domains`、`cli`),格式與工作包狀態檔同一套,`jsc-hooks` 與 `jsc-cli` 兩邊各自實作也對得上。放行原則比照 `version-guard.sh`:只擋確定違規,狀態檔讀不到、技能名取不到、工具名不是 `Skill`、技能不是 `jsc-*:*` 一律 exit 0,沒有證據時擋下等於停掉每一次技能呼叫。九支豁免(`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`)的理由是同一件事:部署後還要寫得完技能組異動報告與工作日誌,整批擋下去「先重啟」與「先寫完報告」會互相打死。後三支自己不是收尾規則的主體,是為了讓前六支走得完才補進來的——清單認的是技能名,不是呼叫鏈。清除交給 `session-timer.sh`,不由本檔自己判:新舊工作階段的判準(`sessions/{sid}.start` 在不在)只有那支腳本知道,兩邊各寫一份就會漂移;續接同一階段走不到那一段,閘門就一路留到真的重新啟動。`require`、`clear`、`report` 都不讀標準輸入,只有 hook 模式讀,工具端呼叫一律再補 `</dev/null`,理由與 `sdlc-gate.sh` 相同。接線排在 `version-guard.sh` 前面:還沒重啟的舊版比落後一個版號更該先攔。不認得的子命令一律安靜 exit 0,不中斷宿主 CLI。擋人訊息依實際 CLI 給重啟方式,並附上豁免清單與逃生門 `JSC_RESTART_GATE=off`。 Who:部署後的第一次技能呼叫,以及 `jsc-cli:deploy` 收尾寫入狀態檔的那一端。