feat(spec-version-guard): 新增版本前置檢查規範、共用腳本與 hook

新增 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>
This commit is contained in:
2026-08-17 14:53:35 +08:00
co-authored by Claude Sonnet 5
parent 9275863342
commit 2329e4d709
9 changed files with 291 additions and 8 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ argument-hint: "[--wiki-repo <owner/repo>] [--wiki-index CONTENTS] [--wiki-page
先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
本 skill 需要的規範:`spec-model`、`spec-output`、`spec-execution`、`spec-todo-list`、`spec-ask-user`、`spec-time-log`、`spec-gitea`、`spec-wiki-contents`、`spec-no-scratch-files`、`spec-skill-invocation`
本 skill 需要的規範:`spec-version-guard`、`spec-model`、`spec-output`、`spec-execution`、`spec-todo-list`、`spec-ask-user`、`spec-time-log`、`spec-gitea`、`spec-wiki-contents`、`spec-no-scratch-files`、`spec-skill-invocation`
## 參數
+1 -1
View File
@@ -23,7 +23,7 @@ argument-hint: "[--refresh] [--task <analysis|implement|review|summary|persona>]
先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
本 skill 需要的規範:`spec-model`、`spec-output`、`spec-execution`、`spec-time-log`、`spec-skill-invocation`
本 skill 需要的規範:`spec-version-guard`、`spec-model`、`spec-output`、`spec-execution`、`spec-time-log`、`spec-skill-invocation`
`spec-model` 是本 skill 的行為本體(標籤體系、任務對映表、來源優先序、快取設計、強制切換規則五節),下文只描述**本 skill 如何呼叫這五節**,不重抄內容;標籤字彙、對映表、來源優先序、快取欄位、錯誤訊息格式如與本檔敘述有出入,一律以 `spec-model` 當次實際載入到的內容為準。
+1 -1
View File
@@ -20,7 +20,7 @@ argument-hint: "[--wiki-repo <owner/repo>] [--index <英文系統名稱>_<中文
先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-gitea`、`spec-wiki-contents`、`spec-ask-user`、`spec-time-log`、`spec-no-scratch-files`、`spec-skill-invocation`
本 skill 需要的規範:`spec-version-guard`、`spec-output`、`spec-execution`、`spec-gitea`、`spec-wiki-contents`、`spec-ask-user`、`spec-time-log`、`spec-no-scratch-files`、`spec-skill-invocation`
本 skill 特有補充:
+1 -1
View File
@@ -20,7 +20,7 @@ argument-hint: "[--assistant <助理清單,逗號分隔,或 all>] [--plugins
## 共用規範(必要前置)
先載入 `/jsc-shared:spec-preflight` 並依其流程處理。
本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-git-safety`、`spec-plugin-cli`
本 skill 需要的規範:`spec-version-guard`、`spec-output`、`spec-execution`、`spec-git-safety`、`spec-plugin-cli`
本 skill 特有補充:
+16 -3
View File
@@ -11,9 +11,21 @@ description: JSC plugins 共用「規範前置載入流程」:每個 skill 執
每個 skill 執行前,依下列順序以 Skill 工具載入,**順序不可顛倒**:
1. **先載入本規範自身**:`/jsc-shared:spec-preflight`。這一步本身就是探測——載入成功代表 shared plugin 已安裝,可以繼續往下載入其他 spec;載入失敗直接進入〔載入失敗的處理〕。
0. **先依 `/jsc-shared:spec-version-guard` 執行版本檢查**:確認當前實際載入的 `jsc-shared`(以及該 skill 所屬 plugin 自己)版本沒有落後於 Gitea `master` 現行版本;不符即中斷本 skill,見〔「版本不符」與「shared 未安裝」是兩種不同中斷原因〕。這一步排在所有 spec 載入之前,避免用舊版規則做事。
1. **再載入本規範自身**:`/jsc-shared:spec-preflight`。這一步本身就是探測——載入成功代表 shared plugin 已安裝,可以繼續往下載入其他 spec;載入失敗直接進入〔載入失敗的處理〕。
2. **再依該 skill 自己列出的規範清單,逐一載入其他 `/jsc-shared:spec-xxx`**。清單與載入順序由各 skill 自己在檔頭決定(通常照該 skill 內文實際用到的先後順序排列),本規範不代為規定其他 spec 之間的順序。
## 「版本不符」與「shared 未安裝」是兩種不同中斷原因
第 0 步(版本檢查)與第 1 步(本規範是否載入得到)失敗時的原因完全不同,**中斷訊息不可混用**:
| 情境 | 代表什麼 | 中斷訊息依據 |
| --- | --- | --- |
| 第 0 步版本檢查不符或查不到遠端版本 | shared plugin **已安裝**,但版本落後於 Gitea `master`,或遠端查詢失敗(fail-closed) | 依 `/jsc-shared:spec-version-guard`〔錯誤訊息格式〕,導向 `/jsc-shared:plugins-install` 更新 |
| 第 1 步載入不到本規範自身 | shared plugin **根本未安裝** | 依本規範〔載入失敗的處理〕,導向安裝 `https://gitea.jsc.idv.tw/plugins/shared.git` |
不得把「版本落後」誤報成「未安裝」(使用者會照著安裝流程走卻發現早就裝了),也不得把「未安裝」誤報成「版本落後」(使用者會照著更新指令走卻發現裝不了,因為根本沒有 marketplace)。
## 載入失敗的處理
只要**任一** spec(包含本規範自身)載入不到,一律判定為**shared plugin(`jsc-shared`)未安裝**,不視為暫時性錯誤、不重試、不略過繼續:
@@ -53,11 +65,12 @@ description: JSC plugins 共用「規範前置載入流程」:每個 skill 執
先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-gitea`、…(依各 skill 實際需要的規範清單列出)
preflight 的第一件事是依 `/jsc-shared:spec-version-guard` 比對遠端與當前實際載入版本,不符即中斷本 skill。
本 skill 需要的規範:`spec-version-guard`、`spec-output`、`spec-execution`、`spec-gitea`、…(依各 skill 實際需要的規範清單列出)
```
- 最後一行的規範清單**只列名稱**,不附一行摘要(摘要是規範內容的重抄,會與本文漂移不一致;需要摘要時直接載入該 spec 看本文)。
- 清單順序建議照該 skill 內文實際用到的先後排列,方便對照。
- 清單順序建議照該 skill 內文實際用到的先後排列,方便對照;`spec-version-guard` 因為是最前置的檢查,習慣上放在清單最前面。
## 適用範圍
+78
View File
@@ -0,0 +1,78 @@
---
name: spec-version-guard
description: 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」的前提下,本規範採兩個保守決定:
1. **matcher 沿用 `persona/hooks/hooks.json` 已驗證可真正阻擋的既有 pattern**——`Read|Write|Edit|MultiEdit|NotebookEdit|Glob|Grep|LS|Bash`,而不是賭一個沒有文件佐證的 `Skill` 工具名稱;這樣至少能保證 hook 真的會被觸發,不會因為 matcher 打錯字而變成裝了跟沒裝一樣。
2. **腳本一律無條件檢查自己這個 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` 的職責,不在本規範重複描述實作細節。
+1 -1
View File
@@ -22,7 +22,7 @@ argument-hint: "[--source <需求描述|檔案路徑|議題編號>] [--impl-mode
先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
本 skill 需要的規範:`spec-model`、`spec-output`、`spec-execution`、`spec-issue-read`、`spec-todo-list`、`spec-ask-user`、`spec-time-log`、`spec-gitea`、`spec-wiki-contents`、`spec-no-scratch-files`、`spec-skill-invocation`
本 skill 需要的規範:`spec-version-guard`、`spec-model`、`spec-output`、`spec-execution`、`spec-issue-read`、`spec-todo-list`、`spec-ask-user`、`spec-time-log`、`spec-gitea`、`spec-wiki-contents`、`spec-no-scratch-files`、`spec-skill-invocation`
本 skill 特有補充: