Files
shared/skills/spec-version-guard/SKILL.md
T
jiantw83andClaude Sonnet 5 2329e4d709 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>
2026-08-17 14:53:35 +08:00

79 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 的職責,不在本規範重複描述實作細節。