新增 spec-version-guard 規範(定義遠端發佈版本 vs 當前實際載入版本的比對規則、 fail-closed、錯誤訊息格式)與 scripts/version-guard.mjs(hook/CLI 雙模式, hook 模式輸出 Claude Code/Copilot 相容的 PreToolUse deny JSON);spec-preflight 的載入順序補上版本檢查第 0 步;新增 hooks/hooks.json 掛 PreToolUse; do-wiki/models/plan-wiki/plugins-uninstall/todo-wiki 五個 skill 檔頭引用新規範 (plugins-install 刻意排除,避免版本落後時擋住自己的修復手段)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.2 KiB
name, description
| name | description |
|---|---|
| spec-version-guard | JSC plugins 共用「plugin 版本前置檢查規範」:定義三種版本(遠端發佈版本/助理註冊版本/當前實際載入版本)的差異、規定比對對象只能是「遠端發佈版本 vs 當前實際載入版本」不得只看註冊版本、查不到遠端版本一律 fail-closed 阻擋、錯誤訊息格式依 spec-time-log、阻擋後導向 plugins-install 或 spec-plugin-cli 更新指令。當其他 skill 內文引用 spec-version-guard 或 /jsc-shared:spec-version-guard、或執行任何 JSC skill 前需要先確認本機版本是否落後於 Gitea master 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。 |
spec-version-guard — 共用 plugin 版本前置檢查規範
任何 JSC skill 在做任何實質步驟之前,都必須先確認自己實際載入的內容是否落後於已發佈的版本;落後時繼續執行等於用舊規則做事,可能直接違反已經改掉的規則(例如舊版 todo-wiki 用連字號命名 TODO 頁、完全不知道 spec-wiki-contents 存在)。本規範定義這個檢查該比對什麼、比對不到時怎麼辦、以及失敗訊息長什麼樣子。
三種版本,只有兩種能拿來比
| 版本 | 意義 | 取得方式 |
|---|---|---|
| 遠端發佈版本 | Gitea master(發佈分支)上 plugin.json 的 version 欄位;四個 JSC plugin 各自對應 plugins/<name>.git |
GET https://<host>/api/v1/repos/plugins/<name>/raw/plugin.json?ref=master |
| 助理註冊版本 | 各助理自己的 plugin 安裝清單記錄的版本(例如 Claude Code 的 ~/.claude/plugins/installed_plugins.json、claude plugin list 的輸出) |
助理原生指令或設定檔 |
| 當前實際載入版本 | 本次 session/本次 hook 呼叫,實際從磁碟讀進來執行的那份 plugin 內容的版本 | 見〔取得「當前實際載入版本」〕 |
唯一合法的比對對象是「遠端發佈版本 vs 當前實際載入版本」,不得只比對「遠端發佈版本 vs 助理註冊版本」。原因:助理的安裝清單只記錄「上次安裝/更新時寫入的版本號」,跟「這次 session 實際從哪個目錄讀取內容」是兩回事——同一個助理的快取目錄底下可能同時存在好幾個版本的 plugin 副本(例如 Claude Code 的 ~/.claude/plugins/cache/shared/jsc-shared/ 下並存 0.1.2/0.1.3/0.1.4/0.1.5/0.2.0 五個版本目錄),註冊表可能已經寫著最新版號,但本次 session 因為快取或會話啟動時機問題,實際載入的還是舊目錄。只比註冊版本會誤判「一切正常」,抓不到這種落差。
取得「當前實際載入版本」
- Claude Code/GitHub Copilot CLI:hook 情境下讀
${CLAUDE_PLUGIN_ROOT}/plugin.json的version(CLAUDE_PLUGIN_ROOT由這兩家助理在呼叫 hook 時注入,指向這次 session 實際掛載的 plugin 目錄,不是註冊表路徑);skill/command 執行情境下,可從呼叫端提供的「Base directory for this skill」之類的實際路徑取得同等資訊。 - Codex:無對應環境變數時,由呼叫端傳入 plugin root 路徑(見
shared/scripts/version-guard.mjs的--plugin-root參數),或依 skill 執行時的實際路徑推得。 - Antigravity/OpenCode:兩者走「clone 到固定目錄+本地路徑安裝」(見
/jsc-shared:spec-plugin-cli),當前實際載入版本=該固定 clone 目錄下plugin.json的version。 - 一律不得用「助理安裝清單版本」或「使用者記得自己上次更新到幾版」頂替上述任何一種取得方式。
強制機制形態:規範+腳本+hook 雙層,僅部分助理有真阻擋
依實測與官方文件比對(見〔各助理 hook 真阻擋能力〕),五家助理只有 Claude Code 與 GitHub Copilot CLI 具備官方文件保證、可真正中止工具呼叫的 hook 事件(PreToolUse/preToolUse,兩者格式相容,可共用同一份 hooks/hooks.json);Codex(hook 能擋,但每次腳本內容變動都要人工 /hooks 重新信任,不算全自動生效)、Antigravity(只有社群 issue 佐證、無官方文件)、OpenCode(已知 bug:subagent 發出的工具呼叫會繞過 hook)三家都不掛 hook,改為完全依賴本規範的規範層自律(讀到 model:/spec-version-guard frontmatter 或本規範內容時,執行者自行比對版本並中止,而非靠工具強制擋下)。
各助理 hook 真阻擋能力(研究結論,供設計依據)
| 助理 | 可用 hook 事件 | 是否真中止 | 本規範採用方式 |
|---|---|---|---|
| Claude Code | PreToolUse |
✅ 真中止(persona/hooks/guard.mjs 已驗證) |
掛 hook |
| GitHub Copilot CLI | preToolUse/PreToolUse |
✅ 真中止,且腳本錯誤/逾時預設視為 deny(fail-closed,與本規範精神一致) | 掛 hook |
| Codex | 同名事件技術上可掛 | ⚠️ 需人工 /hooks 逐次重新信任,不算自動 |
不掛 hook,僅規範層 |
| Antigravity | JSON deny 型 hook | ⚠️ 僅社群 issue 佐證,無官方文件 | 不掛 hook,僅規範層 |
| OpenCode | tool.execute.before |
⚠️ 已知會被 subagent 呼叫繞過 | 不掛 hook,僅規範層 |
此表為使用者於 2026/08/17 依上述研究結果裁示採用「只對 Claude Code/Copilot 掛 hook,其餘三家靠規範自律」。
hook 設計:matcher 與檢查範圍
Claude Code/Copilot 官方文件都沒有明確記載 Skill 工具呼叫的 tool_input 欄位結構、也沒有說明 PreToolUse payload 是否帶有「這次呼叫屬於哪個 plugin」的資訊(已實際查證官方文件,查無此欄位說明)。在無法可靠辨識「這次工具呼叫是不是我這個 plugin 自己的 skill」的前提下,本規範採兩個保守決定:
- matcher 沿用
persona/hooks/hooks.json已驗證可真正阻擋的既有 pattern——Read|Write|Edit|MultiEdit|NotebookEdit|Glob|Grep|LS|Bash,而不是賭一個沒有文件佐證的Skill工具名稱;這樣至少能保證 hook 真的會被觸發,不會因為 matcher 打錯字而變成裝了跟沒裝一樣。 - 腳本一律無條件檢查自己這個 plugin 的版本,不嘗試判斷「這次工具呼叫是不是我的 skill 觸發的」。四個 JSC plugin 大量互相引用彼此的
spec-*規範,任一個過期都有風險,因此「呼叫任何一個 JSC skill 時,四個 plugin 的 hook 都一起檢查一次自己的版本」是刻意的保守設計,不是失誤。
這兩個決定都還沒有實機驗證(Skill 工具呼叫時 PreToolUse 是否真的會帶著這個 matcher 一起觸發、四個 hook 同時觸發會不會互相干擾),留給第 14/23 項的端到端驗證實測確認;若實測發現行為與預期不符,以實測結果為準修正本節,不得憑本節文字繼續假設它一定成立。
fail-closed:查不到遠端版本一律阻擋
- 遠端查詢失敗(逾時、網路錯誤、404、host 不可達)時,視同版本不符,一律阻擋,不得因為「連不上網」就放行——寧可誤擋,不可誤放。
- 查詢必須設逾時(建議 5 秒),避免拖住工作階段啟動或每次工具呼叫。
錯誤訊息格式
阻擋時的訊息依 /jsc-shared:spec-time-log 的 [yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息 格式,階段固定為「版本檢查」,等級固定 ERR:
[yyyy/MM/dd HH:mm:ss][版本檢查][ERR]: <plugin 名> 遠端發佈版本為 <remote-version>,
當前實際載入版本為 <loaded-version>(或「查無法取得遠端版本」)。
請執行 /jsc-shared:plugins-install 更新後重新開啟工作階段。
- 找不到遠端版本時,
<remote-version>替換為「查無法取得遠端版本」,訊息其餘部分不變。 - 阻擋後一律導向
/jsc-shared:plugins-install(一次更新四個 JSC plugin)或/jsc-shared:spec-plugin-cli對應助理小節的「更新」指令,不得只說「請更新」而不給出具體指令。
適用範圍
依 /jsc-shared:spec-preflight〔載入順序〕,本規範的檢查排在所有其他 spec 載入之前執行(見 spec-preflight 第 0 步);規範檔本身(spec-*)依 spec-preflight〔適用範圍〕不引用 preflight,因此也不引用本規範,避免循環依賴。
/jsc-shared:plugins-install 額外排除,理由是避免自我鎖死:plugins-install 正是 spec-version-guard 阻擋後指引使用者執行的修復手段;若 plugins-install 自己也引用本規範,遇到 shared 版本落後時,它會在修復自己之前就先被自己判定版本不符而擋下——使用者永遠跑不到能修好問題的那個 skill。因此 plugins-install 不列入〔給實作端的備註〕所述「六個非 spec skill」的檔頭清單,其餘 skill(含 plugins-uninstall)不受此例外影響。
給實作端(腳本、hook)的備註
本規範只定義「比對什麼、失敗了算什麼、訊息長怎樣」;實際取值、發 HTTP 請求、決定 hook 事件的程式碼是共用腳本 shared/scripts/version-guard.mjs 與四個 plugin 各自的 hooks/hooks.json 的職責,不在本規範重複描述實作細節。