Files
meta/references/guidelines.md
jiantw83 651dc19be9 docs(guidelines): 改寫相依版本準則並補上技能行為清單合約
What:「Manifest 相依版本」第 5 條改成部署端照樣更新,只在回報裡寫明缺哪一版。「版本前置檢查」補上相依版本檢查三列。新增「技能行為清單」一節,訂出位置、標題、節、表格、欄位與更新時機。審核檢查清單加上行為清單這一項。README 補上 behaviors.md 與 check-behaviors.sh 兩列,並把 skill-check 段落改成三組腳本。

Why:跳過更新會讓落後的 domain 永遠更新不到。它落後所以被跳過,被跳過所以永遠落後。相依版本不符要擋的是拿舊版去跑,不是把舊版換成新版。阻擋改到技能被呼叫的當下,才擋得住真正會出事的動作。行為清單要有一份格式合約,check-behaviors.sh 才有判定依據。

How:阻擋交給 jsc-hooks/hooks/version-guard.sh。版本比對由它自己實作,不呼叫 jsc-cli/tools/check-requires.sh。hook 專屬存放於 jsc-hooks,而且 jsc-cli 已宣告相依 jsc-hooks,反向呼叫會做出循環相依。兩道檢查共用同一份豁免清單。行為清單一個 domain 一份,放進該 domain 的 references/behaviors.md,技能改動與清單改動才進得了同一個 PR。

Who:涵蓋這次兩件需求的準則與說明文件,一件是相依版本不符改為阻擋執行,一件是技能行為清單。
2026-08-31 13:37:22 +08:00

282 lines
28 KiB
Markdown
Raw Permalink 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.
# JSC 技能準則
本文件是 jsc 技能組的唯一準則來源。`skill-new`、`skill-update`、`skill-delete` 與技能審核都必須依此檢查。
## 命名
1. Plugin 名稱:`jsc-{domain}`,domain 用一個簡短的英文代表詞(例:`ask`、`sdlc`、`gitea`)。
2. Marketplace 統一為 `jsc`,正本在 `https://gitea.jsc.idv.tw/plugins/meta.git`;安裝 token 一律 `jsc-{domain}@jsc`。**每個 domain 存取庫都帶同一份 marketplace 檔**(`.claude-plugin/marketplace.json` 與 `.agents/plugins/marketplace.json`,內容與正本完全一致),所以任何一個 repo 都能當註冊入口,互相覆蓋沒關係。
3. Skill 名稱:小寫、數字、連字號(`-`),最長 64 字元。名稱即指令(`/jsc-{domain}:{name}`)。
4. 每個 domain 是一個獨立存取庫 `https://gitea.jsc.idv.tw/plugins/{domain}.git`。新 domain 必須依 `https://gitea.jsc.idv.tw/plugins/template` 的結構建立新存取庫(三份 plugin manifest、`skills/`、`README.md`、`AGENTS.md`),並把 plugin 條目(URL 來源指向新存取庫)加入 `plugins/meta` 正本的兩份 marketplace 檔,再把更新後的檔案**同步到所有 domain 存取庫**(含新存取庫自己)。
5. Git 分支名**只允許 ASCII**(`a-z0-9` 與 `/`、`-`);中文需求或標題先翻譯成英文短語再 slug 化。
6. Manifest 版本號從 `0.0.1` 開始,三份 manifest 同步 bump;`minor` 與 `patch` 不得超過 `9`,滿 `9` 就往左進位,`major` 可以超過 `9`。
## PR 分支階梯
所有存取庫的 PR 一律照階梯逐級上推,**禁止越級**。
| 類型 | 階梯 |
| --- | --- |
| `feat`、`docs`、`style`、`refactor`、`perf`、`test`、`chore`、`revert` | `{類型}/{子功能}` → `{類型}/{功能}/main` → `develop` → `master` |
| `fix` | `fix/{修改}` → `develop` → `master` |
1. `{子功能}` = `{功能}/{子功能內容中文簡述}`,可以多層,例如 `{A}/{A的子功能B}/{B的子功能C}/{C的子功能}`。
2. `{功能}` = 功能內容中文簡述;`{修改}` = 修改內容中文簡述。
3. 中文簡述先交給 `jsc-git/tools/slugify.sh` 轉成 ASCII slug,再組成分支名;分支名本身的字元限制見「命名」第 5 條。
4. base 一律由 `jsc-git/tools/base-branch.sh --derive` 推導。推不出唯一合法基底就中止並詢問使用者,不猜,也不退回 `develop`。
5. 功能主幹 `{類型}/{功能}/main` 不在 origin 上時,自動從 `develop` 建立並推上去,收尾要回報建立了哪一條分支。
6. 階梯最後一級不能省:`develop` 併進 `master` 才會生效,marketplace 與 `version-guard.sh` 都讀存取庫的**預設分支**。
PR 開立、更新、留言修正的收尾回報格式只看 [`references/pr-report.md`](pr-report.md)。所有會產生 PR 的技能都引用那份文件,不在技能內各自抄欄位。
## Description 規則
1. frontmatter 的 `description` 為一行英文,不超過 5 句或 5 個步驟——**兩個上限滿足任一個就算通過**,句數與步驟數都超過才要精簡。
2. 使用專有名詞、概念或指引詞(例:WBS、TDD、decision tree、STE100)取代解釋。
3. 必須寫清楚觸發時機(何時用、何時不用),這是各 CLI 自動載入的唯一依據。
4. 複雜流程透過**組合其他技能**實現,不在單一 description 裡塞流程。
## Manifest 相依版本
1. Plugin 需要另一個 jsc plugin 的技能、工具、hooks、references 或 templates 才能完成自己的流程時,必須在三份 manifest 寫入 `jsc.requires`。
2. `jsc.requires` 是物件。鍵是完整 plugin 名稱,格式為 `jsc-{domain}`。值是最低版本,格式為 `>=x.y.z`。
3. 沒有跨 plugin 相依時不寫 `jsc.requires`。不要留下空物件。
4. 只宣告 jsc plugin 對 jsc plugin 的相依。系統指令、語言執行環境與第三方套件寫在 README 或工具說明,不寫進這個欄位。
5. `jsc-cli/tools/check-requires.sh` 是**部署端**相依版本回報的唯一程式來源。`jsc-cli/tools/deploy.sh update` 必須在更新每個 domain 前呼叫它,**版本不符時照樣更新那個 domain**,只在回報裡寫明缺哪一個 plugin 的哪一版,不得靜默略過這段回報。
**為什麼不跳過。** 跳過更新會讓落後的 domain 永遠更新不到:它落後所以被跳過,被跳過所以永遠落後,更新指令跑幾次都一樣,只能手動拆。相依版本不符要擋的是「拿舊版去跑」,不是「把舊版換成新版」,更新本身正是解除落後的唯一路徑,擋它等於自鎖。
阻擋改由 `jsc-hooks/hooks/version-guard.sh` 在技能被呼叫的當下執行,規則見「版本前置檢查」。那個時點才擋得住真正會出事的動作,也不會擋掉更新路徑。
## 強制力層級
1. 規則的實現優先順序:**hook > prompt**。凡是可以由 hook 強制的規則(語言、計時、統計),一律下放 hook,SKILL.md 只保留 hook 無法涵蓋的指引。
2. 有標準輸入與輸出的流程一律下放到 `tools/` 腳本,SKILL.md 只描述何時呼叫與參數。
3. 主 agent 不需要處理細節的流程,SKILL.md 必須明確要求建立 sub agent 處理(關鍵字:「必須以 sub agent 執行」)。
## Hook 規則
1. 所有 hook 專屬存放於 `jsc-hooks`,**不可散落在其他 domain**。
2. Hook 腳本實作優先順序:**shell > nodejs > python**。
3. Hook 必須適用於 claude / codex / copilot / antigravity / kiro 五種 CLI:
- 腳本同時支援 stdin JSON(Claude 格式)與環境變數輸入,缺欄位時安靜降級(exit 0)。
- 各 CLI 的接線方式由 `jsc-hooks:hooks-install` 技能處理。
## 技能設計
1. 每個技能必須有單一明確目標,不可與既有技能重複;能複用就複用(呼叫其他技能或工具)。
2. 需要操作 gitea 且輸入輸出明確的技能,一律透過 `jsc-gitea/tools/gitea.sh`(curl + `GITEA_TOKEN`)或 `tea` CLI,不可自行拼 API 呼叫。gitea.sh 在 token 未設定或收到 401/403 時,會自動改用 tea 的登入金鑰重試一次。
3. 需要問使用者的技能,一律透過 `jsc-ask:ask` 的決策樹規則:選項式提問、每個選項標明影響範圍、問到沒有疑慮為止、已有紀錄的答案不再問。
## 語言
1. **所有非程式碼輸出**一律 STE100 繁體中文、UTF-8、無亂碼、無簡體字,帶擬人台灣感:短句、一句一指令、主動語態、術語一致、台灣用語、全形標點、去 AI 味、直接講重點。範圍涵蓋程式碼註解、commit 訊息、PR 描述、wiki 頁、對使用者的回報、README 與各種文件;程式碼本身(識別字、關鍵字、API 名稱)不受此規則限制。完整適用範圍表、編碼要求與替換表的唯一來源:[`references/ste100.md`](ste100.md)。
2. **技能文件(SKILL.md)整份為英文**:frontmatter 與內文都是。風格比照 STE100:短句、祈使句、術語一致。
3. 技能文件裡「要原樣輸出的繁中字面內容」保留繁中:wiki 狀態字串(例:已分析、未完成)、hook 注入文字、要寫進 wiki 或 commit 的文案。技能執行時產生的 commit 訊息、PR 描述、wiki 頁內容仍依第 1 條輸出繁中。
4. README、AGENTS.md、`templates/`、`references/` 為 STE100 繁體中文。中文並列項用頓號「、」,不用半形「/」;英文項目的並列(CLI 名、頁名前綴)可用「/」。
## 撰寫規範(SKILL.md 內文)
1. 每個步驟以**可檢核的完成條件**結尾(例:「成功標上工作證才可以進入下一步」),不用模糊語(「理解後」「適當地」)。
2. 用**正向敘述**寫目標行為;禁止句只留給無法正向表達的硬性護欄。
3. 每個意義只有**單一真實來源**:環境可查的資訊(指令、設定、目錄結構)不要抄進技能,只寫環境查不到的慣例、原因與陷阱。
4. **漸進揭露**:所有分支都需要的內容留在 SKILL.md;只有部分分支需要的參考資料下放 `references/`,以一行指引指過去。
5. 善用**引導詞**(WBS、CPM、TDD、seam、STE100 等既有概念)取代整段解釋。
## 技能行為清單
每個 domain 都要有一份技能行為清單,記下每支技能實際做的事,供稽核與驗證比對。
| 項目 | 規則 |
| --- | --- |
| 位置 | 每個 domain 存取庫的 `references/behaviors.md`,UTF-8 無 BOM,內容為 STE100 繁體中文 |
| 第一行 | `# jsc-{domain} 技能行為清單` |
| 節 | 每支技能一個 `## {技能名}` 節,名稱與 `skills/` 底下的目錄名逐字相同,節數與技能支數一樣,排列照目錄名的字典序 |
| 表格 | 每節恰好一張表,表頭兩欄依序是「項目」與「內容」,五列依序為 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,每一列的「內容」欄都不得空白 |
| 寫什麼 | 寫技能實際的行為:什麼情況會用、什麼情況不該用、依序做了哪些事、呼叫哪些腳本與技能、做到什麼程度算跑完、跑完在環境裡留下哪些查得到的跡象。不要抄 `description` 的行銷語 |
| 純唯讀的技能 | 「可驗證跡象」欄寫「無寫入跡象,只有回報內容」,不得留白 |
| 更新時機 | 技能異動時在**同一個 PR 內**一起更新:新增技能就加一節、刪除就移除該節、改行為就改該節 |
| 檢查腳本 | `jsc-meta/tools/check-behaviors.sh {domain-path}` |
`check-behaviors.sh` 的結束碼分流:
| 結束碼 | 意義 |
| --- | --- |
| 0 | 行為清單與 `skills/` 相符,五個欄位齊全且內容欄非空 |
| 1 | 不符:缺節、多節、順序不對、表格不對、缺欄位或欄位空白,逐項印在 stderr,照著修再重跑 |
| 2 | 用法錯誤:本腳本只吃一個參數 |
| 3 | 找不到 `references/behaviors.md`、找不到 `skills/`,或 `skills/` 底下一支 `SKILL.md` 都沒有。**什麼都沒查,不等於通過**,先補齊檔案再重跑 |
**為什麼一個 domain 一份,不集中在 `jsc-meta`。** 技能改動與行為清單放同一個存取庫,才進得了同一個 PR;審的人在一頁 diff 上就看得出行為改了、清單也改了。集中在 meta 的話,改一支技能要開兩條 PR,一條在 domain、一條在 meta,兩條互相等待,先併的那條讓清單與技能對不上,稽核抓到的是自己造出來的漂移。跨存取庫的東西沒有原子性,同一份事實就不要拆兩邊放。
## 環境變數
| 變數 | 用途 | 未設定時 |
| --- | --- | --- |
| `GITEA_HOST` | Gitea 站台(例:`https://gitea.jsc.idv.tw`) | 詢問使用者 |
| `GITEA_TOKEN` | Gitea API token | 改用 tea 登入金鑰(`tea login list`);tea 也沒有才詢問使用者 |
| `JSC_WIKI_REPO_QUESTION` | `QUESTION_CONTENTS`、`QUESTION_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_PLAN` | `PLAN_CONTENTS`、`PLAN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_ANALYZE` | `ANALYZE_CONTENTS`、`ANALYZE_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_DELIVER` | `DELIVER_CONTENTS`、`DELIVER_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_MAINTAIN` | `MAINTAIN_CONTENTS` 所在的 `{owner}/{repo}`(本類型只有目錄頁) | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_REPO` | `REPO_CONTENTS`、`REPO_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_LOG` | `LOG_CONTENTS`、`LOG_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_LEARN` | `LEARN_CONTENTS`、`LEARN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_ERROR` | `ERROR_CONTENTS`、`ERROR_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_CHECK` | `CHECK_CONTENTS`、`CHECK_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_REPORT` | `REPORT_CONTENTS`、`REPORT_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_SKILLSET` | `SKILLSET_CONTENTS`、`SKILLSET_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_TOOLING` | `TOOLING_CONTENTS`、`TOOLING_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO` | 未逐類設定時的共用 wiki `{owner}/{repo}` | 詢問使用者 |
| `JSC_HOME` | Hook 資料目錄 | 預設 `~/.jsc` |
| `JSC_PR_WATCH_INTERVAL` | `jsc-gitea/tools/pr-watch.sh` 輪詢 PR 狀態的間隔秒數 | 預設 60 |
| `JSC_RESTART_GATE` | 部署後重啟閘門的開關,`off` 關閉整道閘門 | 閘門開啟 |
頁面類型只讀自己的 `JSC_WIKI_REPO_{TYPE}`。只有該變數未設定時,才退回 `JSC_WIKI_REPO`。不得跨類型代用。
## 版本前置檢查
技能組的每一支技能在被呼叫前都要過兩道版本檢查:本機載入版本沒有落後遠端發佈版本,以及這支技能所屬 plugin 宣告的 `jsc.requires` 每一項都吃得到。兩道判定都在程式層,由 `jsc-hooks` 的 `version-guard.sh`(PreToolUse,matcher `Skill`)執行,**不靠技能內文自我約束**——寫在內文的規則,模型可以無視。
兩道都只擋「確定落後」一種情況。查不到任何一項基礎資訊就安靜放行(exit 0),不要求先修好環境:五支 CLI 只有 claude 讀得到本機載入版本,fail-closed 會把另外四支整批鎖死。
| 項目 | 規則 |
| --- | --- |
| 比對對象 | 遠端發佈版本(存取庫**預設分支**的 `plugin.json`,經 `jsc-gitea/tools/gitea.sh` 讀取,不寫死分支名)對本機**實際載入**版本 |
| 實際載入版本 | 只認 `installed_plugins.json` 的 `installPath` 底下那份 `plugin.json`。註冊在 `installed_plugins.json` 的 `version` 欄位**不當備援**——註冊值可能比實際載入的版本新,拿它來比對會放過真正被載入的舊版 |
| 落後 | 擋下該次技能呼叫(exit 2),並印出更新指令 |
| 相等或超前 | 放行。開發技能組時本機本來就會超前預設分支,擋下去維護者自己動不了 |
| 查不到本機載入版本 | **放行**(exit 0,安靜降級)。讀不到 `installed_plugins.json`、裡面沒有該 plugin 的條目、取不到 `installPath`、`installPath` 底下那份 `plugin.json` 讀不到,四種都算這一列,不退回註冊欄位 |
| 解不出 Gitea 站台 | **放行**(exit 0,安靜降級) |
| 查不到遠端版本 | **放行**(exit 0,安靜降級)。缺基礎設施不等於落後,擋下去會把四支非 Claude CLI 整批鎖死 |
| 相依版本落後 | 這支技能所屬 plugin 的 `jsc.requires` 有一項落後就擋下該次呼叫(exit 2),印出缺哪一個 plugin 的哪一版與更新指令。版本比對由 `version-guard.sh` 自己實作,不呼叫 `jsc-cli/tools/check-requires.sh`:hook 專屬存放於 `jsc-hooks`(見「Hook 規則」第 1 條),而且 `jsc-cli` 已宣告相依 `jsc-hooks`,反過來呼叫會做出循環相依。兩支的比法要保持一致,改動任一支就回頭核對另一支 |
| 相依版本相等或超前 | 放行。每一項都吃得到才算過 |
| 查不到相依資訊 | **放行**(exit 0,安靜降級)。解不出這支技能所屬 plugin 的安裝路徑、讀不到它的 manifest、manifest 沒有 `jsc.requires`、查不到某一項相依 plugin 的本機載入版本,四種都算這一列 |
| 逃生門 | `JSC_VERSION_GUARD=off`(離線工作用),快取秒數 `JSC_VERSION_TTL`(預設 600) |
**豁免清單**(永遠放行,改動前想清楚後果):
| 技能 | 為什麼不能擋 |
| --- | --- |
| `jsc-cli:deploy` | 更新整組技能的入口。擋了就沒有任何方法更新,形成死鎖 |
| `jsc-hooks:hooks-install` | 更新後要重新接線,擋了會讓更新做一半卡住 |
| `jsc-hooks:repair` | hook 壞掉時的唯一修復路徑。擋了就修不好 hook |
| `jsc-cli:models` | SDLC 階段閘門依賴它產生 `model-tags.tsv` |
| `jsc-meta:*` | 開發技能組本身的工具,擋了就修不了技能組 |
| `jsc-ask:ask` | 上面幾支都要問使用者 |
| `jsc-gitea:wiki` | 上面幾支的收尾要寫 wiki |
共 7 項。**兩道檢查共用這一份清單,不另立一份。** 豁免的理由兩道完全一樣:這幾支是解除落後的唯一路徑,擋了就沒有東西能把版本補上來。分成兩份只會兩邊漂移,改了一份、忘了另一份,`deploy` 照樣被相依版本擋死。這張表的唯一真實來源是 `jsc-hooks/hooks/version-guard.sh` 的檔頭與豁免清單,兩邊要逐項對齊。
相依版本檢查移到這裡,是因為 `deploy.sh update` 原本會跳過不符的 domain,跳過就永遠更新不到,理由見「Manifest 相依版本」第 5 條。更新照跑、呼叫才擋,落後的 domain 才有路徑補上來。
沒有 pre-tool hook 的 CLI 接不上這道檢查,`hooks-install` 要據實回報,不得暗示每個 CLI 都有保護。
## 部署後重啟閘門
部署換掉的是磁碟上的技能檔,目前工作階段載入的還是舊版。這段落差期間跑技能,改動看起來沒生效,人會以為部署失敗又重跑一次。
| 項目 | 規則 |
| --- | --- |
| 狀態檔 | `$JSC_HOME/restart-required.d/{cli}`,**一支 CLI 一份**,由 `jsc-cli:deploy` 收尾寫入 |
| 清除時機 | 重啟 CLI 之後由 `jsc-hooks` 清除**自己那一份**,不必手動刪 |
| 該 CLI 那份存在時 | 擋下這支 CLI 的 jsc 技能呼叫,印出要重啟哪一支與狀態檔路徑 |
| 該 CLI 那份不存在時 | 放行。別支 CLI 的狀態檔不影響這一支 |
| 判定位置 | 程式層,由 `jsc-hooks` 執行,不靠技能內文自我約束 |
| 逃生門 | `JSC_RESTART_GATE=off` |
**豁免清單**(狀態檔存在也放行):
| 技能 | 為什麼不能擋 |
| --- | --- |
| `jsc-cli:deploy` | 部署本身的入口。擋了就沒有方法重跑部署,形成死鎖 |
| `jsc-hooks:hooks-install` | 部署後要重新接線,擋了會讓部署做一半卡住 |
| `jsc-hooks:repair` | hook 壞掉時的唯一修復路徑。擋了就修不好 hook |
| `jsc-gitea:wiki` | 寫 `SKILLSET_{HASH}` 異動報告與工作日誌的唯一路徑 |
| `jsc-log:worklog` | 部署後還要結清工作日誌 |
| `jsc-log:learn` | 部署後還要記這次的教訓 |
| `jsc-meta:*` | 開發技能組本身的工具,擋了就修不了技能組 |
| `jsc-ask:ask` | 上面幾支都要問使用者。擋了 `deploy` 連 install 或 update 都問不出來 |
| `jsc-git:pr` | 報告與異動的收尾要開 PR,擋了收尾做不完 |
| `jsc-git:commit` | 同上,`pr` 的第一步就是它 |
豁免這幾支的理由是同一件事:`jsc-meta` 四支異動技能的收尾要求把驗證結果寫進 `SKILLSET_{HASH}`,還要結清工作日誌,而這條路徑必經 `jsc-gitea:wiki` 與 `jsc-log`。全擋的話,部署一跑完就沒有路徑寫完報告,重啟閘門與報告要求互相打死。閘門不自鎖的通則見「審核檢查清單」的流程檢查第 4 項。共 10 項;這張表的唯一真實來源是 `jsc-hooks/hooks/restart-gate.sh` 的檔頭與豁免清單,兩邊要逐項對齊。
**豁免清單不是自鎖的通解。** 部署剛跑完就要在同一個工作階段叫用剛做好的技能,那支技能本來就不該靠豁免過關——豁免只擋得住這一道閘門,擋不住「行程還載著舊版」這個事實,驗證結果會是舊版的行為。正解是換一個工作階段:由 `jsc-cli/tools/detect-clis.sh` 開出的新 CLI 行程去叫用,新行程自己會清掉那份狀態檔,載到的也是新版。做法見 [`deploy-verify.md`](deploy-verify.md)。
**狀態檔為什麼一支 CLI 一份。** 這道閘門管的是「這支 CLI 的行程還在跑舊版」,那是每支 CLI 各自的事實。初版用全機器單一檔案,2026-08-27 部署時實測出兩個後果:並行部署互相覆蓋,`cli=` 與 `domains=` 只留最後一支;更嚴重的是清除也是全域的,**任一支 CLI 重啟就解除全部五支的閘門**,其餘四支沒重啟卻不再被擋,這道閘門在多 CLI 環境等於半失效。改成 per-CLI 之後,`require` 寫自己那份、判定只看自己那份、`clear` 只刪自己那份,`report` 才列得出「哪幾支還沒重啟」。**跨 CLI 或跨工作階段的狀態檔,設計時先問清楚那個事實屬於誰**——同一類錯誤在工作包歸屬狀態檔上也踩過,解法見 `jsc-sdlc/tools/wp-gate.sh` 的 `claim` 與 `owns`:`claim` 在領包當下把歸屬登錄成「這個存取庫目前是哪一包」,`owns` 查驗每支 PR 是不是自己那一包的,兩層各管一件事實,狀態檔也刻意不綁工作階段。
**清單認的是技能名,不是呼叫鏈。** 豁免技能轉呼叫的下一層若不在清單上,那一層照樣會被擋。後三支(`jsc-ask:ask`、`jsc-git:pr`、`jsc-git:commit`)自己不是收尾規則的主體,是為了讓前七支走得完才補進來的。`version-guard.sh` 的豁免清單當年也是為同一個原因收進 `jsc-ask:ask`。新增豁免技能時要一併想它會呼叫誰。
## Wiki 頁命名總表
所有 wiki 頁面一律採雙層命名。`MAINTAIN` 是唯一只有目錄頁的類型,理由見表下:
| 類型 | 目錄頁 | 內容頁 | 用途 | 擁有者 |
| --- | --- | --- | --- | --- |
| `QUESTION` | `QUESTION_CONTENTS` | `QUESTION_{HASH}` | 問詢目錄、單一存取庫的問詢紀錄 | jsc-ask |
| `PLAN` | `PLAN_CONTENTS` | `PLAN_{HASH}` | 計畫目錄、計畫頁 | jsc-sdlc |
| `ANALYZE` | `ANALYZE_CONTENTS` | `ANALYZE_{HASH}` | 分析目錄、分析頁 | jsc-sdlc |
| `DELIVER` | `DELIVER_CONTENTS` | `DELIVER_{HASH}` | 交付目錄、工作包交付文件 | jsc-sdlc |
| `MAINTAIN` | `MAINTAIN_CONTENTS` | 無 | 維護登記目錄:登記進維護期的專案、維護方式、起訖日期與前次維護時間 | jsc-sdlc |
| `REPO` | `REPO_CONTENTS` | `REPO_{HASH}` | 盤點目錄、存取庫盤點頁 | jsc-sdlc |
| `LOG` | `LOG_CONTENTS` | `LOG_{HASH}` | 日誌目錄、工作日誌頁 | jsc-log |
| `LEARN` | `LEARN_CONTENTS` | `LEARN_{HASH}` | 教訓目錄、技能教訓頁 | jsc-log |
| `ERROR` | `ERROR_CONTENTS` | `ERROR_{HASH}` | 異常目錄、異常頁 | jsc-hooks |
| `CHECK` | `CHECK_CONTENTS` | `CHECK_{HASH}` | 體檢目錄、執行環境體檢頁 | jsc-cli |
| `REPORT` | `REPORT_CONTENTS` | `REPORT_{HASH}` | 報表目錄、工作報表頁(年、月、週、日各一頁) | jsc-log |
| `SKILLSET` | `SKILLSET_CONTENTS` | `SKILLSET_{HASH}` | 技能組異動目錄、技能組異動報告頁(新增、更新、刪除、批次更新之後的驗證結果與改動清單) | jsc-meta |
| `TOOLING` | `TOOLING_CONTENTS` | `TOOLING_{HASH}` | 技能盤點目錄、單機單 CLI 的技能盤點頁:一台機器上某一支 CLI 的已安裝 plugin 與版本、可用技能、hook 接線狀態 | jsc-meta |
`MAINTAIN` 沒有內容頁。維護登記全部寫在 `MAINTAIN_CONTENTS` 的表格裡:`jsc-sdlc:implement` 只往那一頁附加登記,`jsc-sdlc:maintain` 只讀那一頁再回寫「前次維護時間」,兩支都沒有產生 `MAINTAIN_{HASH}` 的步驟,`jsc-sdlc/templates/` 也沒有對應範本。總表以前列著這個內容頁,照著找只會找到一個不存在的頁。要補內容頁就先補技能步驟與範本,不能只在總表上寫著。
`{HASH}` 一律為 `{owner}/{repo}`(必要時加上主題字串)的 SHA-1 前 8 碼,大寫。
若第一碼是 `0-9`、`A`、`B`、`C`,就改成 `H` 加上原 SHA-1 前 7 碼,總長仍維持 8 碼。
同一規則套用到所有目錄頁與內容頁。
`REPORT` 用得到那個主題字串:雜湊來源為 `{owner}/{repo}/{期間}`,期間是 `daily`、`weekly`、`monthly`、`yearly` 其中之一。
年、月、週、日各自一頁,每頁內依期間累積分節。
`SKILLSET` 的雜湊來源就是被改動的 domain 存取庫 `{owner}/{repo}`,算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
頁內**累積**歷次異動:每次異動附加一節,不覆蓋舊紀錄。要看一支技能改過幾次,就在同一頁上翻。
`CHECK` 是唯一例外:它記的是一台執行環境,不是一個存取庫,所以雜湊來源為 `{主機名}/{登入帳號}`。
8 碼與 `H` 前綴的算法完全相同,由同一支 `jsc-gitea/tools/hash-id` 產生。
在沒有存取庫的目錄也跑得出體檢,是這個例外存在的原因。
`TOOLING` 記的也是機器層事實,雜湊來源再多一段:`{主機名}/{工具名稱}/{登入帳號}`。
`{工具名稱}` 是 CLI 代號,取自 `jsc-cli/tools/detect-clis.sh` 輸出的第一欄,值為 `claude`、`codex`、`copilot`、`antigravity`、`kiro` 其中之一。
一台機器、一支 CLI、一個帳號各一頁;算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
**為什麼要帶工具名稱。** 每支 CLI 各有自己的已安裝 plugin 集合,也各有自己的 hook 接線狀態,那是五組互相獨立的事實。
少了中間那一段,同一台機器上五支 CLI 會算出同一個雜湊,五份盤點互相覆蓋,最後只剩最後寫入的那一支,讀的人卻看不出被蓋掉。
帶上工具名稱,一支 CLI 就有一頁,換一支 CLI 重跑也不會動到別支的頁。
## 審核檢查清單
新增或更新技能後逐項檢查,任一不符就修正:
- [ ] 名稱符合命名規則,且與既有技能目標不重複
- [ ] description 為英文、≤ 5 句或 ≤ 5 步驟(滿足任一即通過)、含觸發時機(何時用、何時不用)
- [ ] 可 hook 的規則已下放 jsc-hooks;可工具化的流程已下放 tools/;SKILL.md 沒有保留可由標準輸入輸出執行的細節流程
- [ ] 細節流程已標示 MUST run as a sub agent
- [ ] gitea 操作透過 gitea.sh 或 tea
- [ ] wiki repo 與 Gitea 認證先讀目前 shell 繼承的環境變數;只有缺值或無法解析時才詢問;頁面類型不得跨用其他 `JSC_WIKI_REPO_{TYPE}`
- [ ] 問詢透過 jsc-ask 決策樹規則
- [ ] `tools/` 與 `hooks/` 內的 shell 腳本都通過 `sh -n`;技能直接呼叫的腳本都存在、可執行,且退出碼有分流
- [ ] hook 相關變更已用 `jsc-hooks/tools/wire-cli.sh smoke {cli}` 實測;沒有偵測到 CLI 時,至少跑 `smoke codex` 並標明是預設 hook smoke。行數讀腳本自己印的 `lines` 那一行,**技能與 README 都不得寫死數字**——腳本會自我斷言,抄一份數字進文件,加減判定路徑時就漂移,稽核反而被舊數字誤導
- [ ] 唯讀的稽核與體檢流程呼叫 `wire-cli.sh` 時帶 `JSC_READONLY=1`:打錯子命令就由程式擋下(exit 6),不靠呼叫端自我約束;`status` 與 `smoke` 不受影響
- [ ] SKILL.md 整份為英文(要原樣輸出的繁中字面除外);README、AGENTS、templates、references 為 STE100 繁中;UTF-8 無亂碼
- [ ] 所有非程式碼輸出(程式碼註解、commit 訊息、PR 描述、wiki 頁、回報、文件)為繁體中文、UTF-8、無亂碼、無簡體字,且 `tools/ste100-lint.sh` 對該 domain 全綠
- [ ] 該 domain 的 `references/behaviors.md` 與 `skills/` 相符,`tools/check-behaviors.sh {domain-path}` 對該 domain 退出 0;退出 3 是「什麼都沒查」,不算通過
- [ ] 已同步更新該 domain 的 README「Skills 目錄」與三份 manifest 的 version
- [ ] PR 的 base 符合「PR 分支階梯」,沒有越級
流程檢查四項,對照技能自己的流程逐項確認:
- [ ] 技能內外引用的步驟編號、檔案路徑、節標題都真的存在,指標指得到。踩過的實例:規則搬到 `references/` 後指標指向空處,照著指過去只看到空白
- [ ] 每個步驟以可檢核的完成條件結尾,沒有「理解後」「適當地」這類模糊語
- [ ] 每個外部呼叫(腳本、API、其他技能)的失敗情況都有明寫怎麼辦,退出碼都有分流
- [ ] 技能自己裝的閘門不會擋掉解除那道閘門的唯一路徑(閘門不自鎖)。踩過的實例:工作包閘門若擋掉 `implement`,結清 PR 就沒有路徑