What: - Wiki 頁命名總表新增 MONITOR 一列,擁有者是技能助理。 - 表下補三段說明:雜湊來源、為什麼不帶工具名稱、寫入語意。 - CHECK 那一段拿掉「是唯一例外」的斷言,改成指向新的機器層規則。 - 三份 manifest 的版本一起提升。 Why: - 總表是所有 wiki 頁命名的唯一來源。新型別沒登錄進來,各技能就沒有依據,只能各自猜。 - CHECK 原本寫著「是唯一例外」,指的是雜湊來源取主機名與帳號而不是存放庫。加了第二個同樣取法的型別之後,這句話就不成立了,留著會讓讀的人以為只有一個。 How: - MONITOR 的雜湊來源比照 CHECK,取主機名與登入帳號。助理巡檢的是一台機器,不是一個存放庫。 - 刻意不帶工具名稱,這一點與 TOOLING 相反。TOOLING 一支 CLI 一頁,因為每支 CLI 各有自己的已安裝 plugin 與 hook 接線;助理看的是整台機器一份心跳、一本待辦簿,不分 CLI。 - 監控頁一律附加,不覆寫。助理的寫入是背景行為,覆寫錯了沒人在現場。TOOLING 內容頁是每次盤點覆寫整頁,兩者語意相反,所以各自寫明白。 - 只動總表那一節,其餘章節不碰。 Who: 技能助理落地帶出來的頁型別需求,四個存放庫同一批改。
36 KiB
JSC 技能準則
本文件是 jsc 技能組的唯一準則來源。skill-new、skill-update、skill-delete 與技能審核都必須依此檢查。
命名
- Plugin 名稱:
jsc-{domain},domain 用一個簡短的英文代表詞(例:ask、sdlc、gitea)。 - 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 都能當註冊入口,互相覆蓋沒關係。 - Skill 名稱:小寫、數字、連字號(
-),最長 64 字元。名稱即指令(/jsc-{domain}:{name})。 - 每個 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 存取庫(含新存取庫自己)。 - Git 分支名只允許 ASCII(
a-z0-9與/、-);中文需求或標題先翻譯成英文短語再 slug 化。 - 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 |
{子功能}={功能}/{子功能內容中文簡述},可以多層,例如{A}/{A的子功能B}/{B的子功能C}/{C的子功能}。{功能}= 功能內容中文簡述;{修改}= 修改內容中文簡述。- 中文簡述先交給
jsc-git/tools/slugify.sh轉成 ASCII slug,再組成分支名;分支名本身的字元限制見「命名」第 5 條。 - base 一律由
jsc-git/tools/base-branch.sh --derive推導。推不出唯一合法基底就中止並詢問使用者,不猜,也不退回develop。 - 功能主幹
{類型}/{功能}/main不在 origin 上時,自動從develop建立並推上去,收尾要回報建立了哪一條分支。 - 階梯最後一級不能省:
develop併進master才會生效,marketplace 與version-guard.sh都讀存取庫的預設分支。
PR 開立、更新、留言修正的收尾回報格式只看 references/pr-report.md。所有會產生 PR 的技能都引用那份文件,不在技能內各自抄欄位。
Description 規則
- frontmatter 的
description為一行英文,不超過 5 句或 5 個步驟——兩個上限滿足任一個就算通過,句數與步驟數都超過才要精簡。 - 使用專有名詞、概念或指引詞(例:WBS、TDD、decision tree、STE100)取代解釋。
- 必須寫清楚觸發時機(何時用、何時不用),這是各 CLI 自動載入的唯一依據。
- 複雜流程透過組合其他技能實現,不在單一 description 裡塞流程。
Manifest 相依版本
-
Plugin 需要另一個 jsc plugin 的技能、工具、hooks、references 或 templates 才能完成自己的流程時,必須在三份 manifest 寫入
jsc.requires。 -
jsc.requires是物件。鍵是完整 plugin 名稱,格式為jsc-{domain}。值是最低版本,格式為>=x.y.z。 -
沒有跨 plugin 相依時不寫
jsc.requires。不要留下空物件。 -
只宣告 jsc plugin 對 jsc plugin 的相依。系統指令、語言執行環境與第三方套件寫在 README 或工具說明,不寫進這個欄位。
-
jsc-cli/tools/check-requires.sh是部署端相依版本回報的唯一程式來源。jsc-cli/tools/deploy.sh update必須在更新每個 domain 前呼叫它,版本不符時照樣更新那個 domain,只在回報裡寫明缺哪一個 plugin 的哪一版,不得靜默略過這段回報。為什麼不跳過。 跳過更新會讓落後的 domain 永遠更新不到:它落後所以被跳過,被跳過所以永遠落後,更新指令跑幾次都一樣,只能手動拆。相依版本不符要擋的是「拿舊版去跑」,不是「把舊版換成新版」,更新本身正是解除落後的唯一路徑,擋它等於自鎖。
阻擋改由
jsc-hooks/hooks/version-guard.sh在技能被呼叫的當下執行,規則見「版本前置檢查」。那個時點才擋得住真正會出事的動作,也不會擋掉更新路徑。
強制力層級
- 規則的實現優先順序:hook > prompt。凡是可以由 hook 強制的規則(語言、計時、統計),一律下放 hook,SKILL.md 只保留 hook 無法涵蓋的指引。
- 有標準輸入與輸出的流程一律下放到
tools/腳本,SKILL.md 只描述何時呼叫與參數。 - 主 agent 不需要處理細節的流程,SKILL.md 必須明確要求建立 sub agent 處理(關鍵字:「必須以 sub agent 執行」)。
Hook 規則
- 所有 hook 專屬存放於
jsc-hooks,不可散落在其他 domain。 - Hook 腳本實作優先順序:shell > nodejs > python。
- Hook 必須適用於 claude / codex / copilot / antigravity / kiro 五種 CLI:
- 五支 CLI 的 hook 負載形態各不相同,沒有哪一支是基準格式。腳本吃 stdin JSON,也吃環境變數,缺欄位時安靜降級(exit 0)。
- 各 CLI 的接線位置、事件名與 matcher 由
jsc-hooks:hooks-install技能處理,接線位置表見「版本前置檢查」。 - 負載解析與阻擋輸出不各寫一份,一律走下面兩支共用腳本。
技能名解析與阻擋輸出的共用腳本
| 腳本 | 用法 | 做什麼 |
|---|---|---|
jsc-hooks/hooks/skill-name.sh |
skill-name.sh {claude|codex|copilot|antigravity|kiro} |
從 stdin 讀該 CLI 的 hook 負載,印出一行 {domain}<TAB>{技能名}。解析不出就印空字串並退出 0,由呼叫端安靜放行 |
jsc-hooks/hooks/deny.sh |
deny.sh {cli},訊息從參數或 stdin 進 |
依該 CLI 的阻擋形態輸出:claude、codex、copilot 走結束碼 2 加 stderr;antigravity 走 stdout {"decision":"deny","reason":"..."},不可靠結束碼;kiro 擋不了,改印警告到 stdout 供注入並退出 0 |
各 CLI 的技能名取值來源:
| CLI | 取自 |
|---|---|
| claude | stdin JSON 的 skill 欄位,或環境變數 JSC_SKILL |
| codex | tool_input.command 裡的 SKILL.md 路徑 |
| copilot | toolArgs 裡的技能名。toolArgs 是字串化的 JSON,要剝兩層 |
| antigravity | toolCall.args.AbsolutePath。args 鍵名是 PascalCase |
| kiro | prompt 開頭的 /{技能名} |
為什麼要抽出來。 三種負載形態(工具名、指令字串、檔案路徑)指向同一件事:從負載取出 domain 與技能名。同一套規則寫進兩支 hook 就會漂移——改了 version-guard.sh、忘了 restart-gate.sh,其中一道閘門就在某支 CLI 上安靜失效,而且失效不會報錯,跟 2026-08-31 抓到的接線缺陷是同一種病。抽成一支之後只有一份真實來源,CLI 換了負載形態也只改一個地方。阻擋輸出同理:五支 CLI 四種形態,寫散了就會有人拿 claude 的結束碼去擋 antigravity,而 antigravity 的結束碼語意兩邊文件都沒寫,擋不擋得住純靠運氣。
技能設計
- 每個技能必須有單一明確目標,不可與既有技能重複;能複用就複用(呼叫其他技能或工具)。
- 需要操作 gitea 且輸入輸出明確的技能,一律透過
jsc-gitea/tools/gitea.sh(curl +GITEA_TOKEN)或teaCLI,不可自行拼 API 呼叫。gitea.sh 在 token 未設定或收到 401/403 時,會自動改用 tea 的登入金鑰重試一次。 - 需要問使用者的技能,一律透過
jsc-ask:ask的決策樹規則:選項式提問、每個選項標明影響範圍、問到沒有疑慮為止、已有紀錄的答案不再問。
語言
- 所有非程式碼輸出一律 STE100 繁體中文、UTF-8、無亂碼、無簡體字,帶擬人台灣感:短句、一句一指令、主動語態、術語一致、台灣用語、全形標點、去 AI 味、直接講重點。範圍涵蓋程式碼註解、commit 訊息、PR 描述、wiki 頁、對使用者的回報、README 與各種文件;程式碼本身(識別字、關鍵字、API 名稱)不受此規則限制。完整適用範圍表、編碼要求與替換表的唯一來源:
references/ste100.md。 - 技能文件(SKILL.md)整份為英文:frontmatter 與內文都是。風格比照 STE100:短句、祈使句、術語一致。
- 技能文件裡「要原樣輸出的繁中字面內容」保留繁中:wiki 狀態字串(例:已分析、未完成)、hook 注入文字、要寫進 wiki 或 commit 的文案。技能執行時產生的 commit 訊息、PR 描述、wiki 頁內容仍依第 1 條輸出繁中。
- README、AGENTS.md、
templates/、references/為 STE100 繁體中文。中文並列項用頓號「、」,不用半形「/」;英文項目的並列(CLI 名、頁名前綴)可用「/」。
撰寫規範(SKILL.md 內文)
- 每個步驟以可檢核的完成條件結尾(例:「成功標上工作證才可以進入下一步」),不用模糊語(「理解後」「適當地」)。
- 用正向敘述寫目標行為;禁止句只留給無法正向表達的硬性護欄。
- 每個意義只有單一真實來源:環境可查的資訊(指令、設定、目錄結構)不要抄進技能,只寫環境查不到的慣例、原因與陷阱。
- 漸進揭露:所有分支都需要的內容留在 SKILL.md;只有部分分支需要的參考資料下放
references/,以一行指引指過去。 - 善用引導詞(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 執行,不靠技能內文自我約束——寫在內文的規則,模型可以無視。
兩道都只擋「確定落後」一種情況。查不到任何一項基礎資訊就安靜放行(exit 0),不要求先修好環境:只有 claude 讀得到 installed_plugins.json 那份本機載入版本,fail-closed 會把另外四支整批鎖死。這是版本讀得到讀不到的限制,跟能不能阻擋是兩件事,不要混談。
五支 CLI 的接線位置
五支裡有四支都有能阻擋的 pre-tool 事件,kiro 是唯一例外。 以下每一列都經過執行檔抽出或本機實測。
| CLI | 事件與 matcher | 接線寫到哪 | 阻擋方式 | verdict |
|---|---|---|---|---|
| claude | PreToolUse,matcher Skill |
jsc-hooks/hooks/hooks.json |
結束碼 2 加 stderr | wired |
| codex | PreToolUse,matcher 對 tool_name 做正規表示式比對,用 Bash 與 Write|Edit|MultiEdit |
.codex-plugin/plugin.json 的 hooks 鍵 |
結束碼 2 加 stderr,或 stdout 回 permissionDecision: deny |
wired |
| copilot | PreToolUse,matcher skill(小寫) |
$COPILOT_HOME/hooks/jsc-hooks.json |
stdout 回 {"permissionDecision":"deny","permissionDecisionReason":"..."},或結束碼 2 |
wired |
| antigravity | PreToolUse 加 PreInvocation,matcher ^view_file$ |
~/.gemini/config/hooks.json 的 jsc 標記段落 |
stdout 回 {"decision":"deny","reason":"..."} |
wired |
| kiro | userPromptSubmit(hook 宣告在 agent 設定檔的 hooks 鍵) |
~/.kiro/agents/jsc.json,並設 chat.defaultAgent=jsc |
擋不了。只能把警告印到 stdout 供注入,退出 0 | degraded |
每一支的陷阱,接線與改動時逐條核對:
| CLI | 陷阱 |
|---|---|
| codex | Codex 沒有 Skill 工具。技能是模型自己用 Bash 讀 SKILL.md 載進來的,matcher 寫 Skill 等於沒接。非受管 hook 要先審核,內容一改就重新標記待審 |
| copilot | command hook 是 fail-closed:崩潰或任何非零結束碼都算拒絕,但逾時 fail-open。所以那支腳本的每一條非預期路徑都要明確 exit 0。事件名 PascalCase 與 camelCase 都吃,兩種同時存在會跑兩次 |
| antigravity | 沒有專用的技能工具,系統提示要求模型用 view_file 讀 SKILL.md。matcher 的錨點一定要寫,view_file 不加錨點會誤中 view_file_outline。結束碼語意兩邊文件都沒寫,絕對不可靠 exit code。斜線指令與預載技能直接把 SKILL.md 全文注入訊息,不產生工具呼叫,那條路徑擋不住 |
| kiro | 技能不走工具管線,是 ResolveSkill 這個 agent 內部請求、由前端發起,所以 preToolUse 攔不到技能叫用;userPromptSubmit 的非零結束碼也不會擋下那一輪。合法 trigger 只有 agentSpawn、userPromptSubmit、preToolUse、postToolUse、stop,sessionStart 與 sessionEnd 不是合法事件。hook 只認 agent 設定檔的 hooks 鍵,.kiro/hooks/ 不被讀 |
未實測的部分要據實標明。 antigravity 與 kiro 的 hook 觸發都沒有實跑驗證——前者對話 quota 用盡、後者未登入。這兩支的接線位置與欄位結構是從執行檔抽出來的事實,但「hook 真的被觸發」還沒看到。回報時不得把這兩支混進「已驗證」的結論。
為什麼要留這段。 這一節以前寫著「只有 claude 接得上,其餘四支沒有 pre-tool hook」,那是錯的。四支全都有能阻擋的 pre-tool 事件,是我們接錯位置:codex 用了它根本沒有的 Skill matcher,copilot 與 antigravity 完全沒接,kiro 連接線位置、事件名、欄位結構三者都錯。錯誤的結論被寫進準則之後,就沒有人再去查——版本前置檢查與部署後重啟閘門因此在四支 CLI 上長期失效,而失效是安靜的:hook 沒被觸發不會報錯,閘門沒擋下來看起來就跟「沒有東西該擋」一樣。一道護欄回報「這裡沒有能力」時,要先確認那是查證過的事實,不是沒查。
kiro 是唯一真的擋不了的,verdict 據實寫 degraded,不寫 wired 也不寫 failed:那是 CLI 的限制,不是我們接錯。
| 項目 | 規則 |
|---|---|
| 比對對象 | 遠端發佈版本(存取庫預設分支的 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 才有路徑補上來。
hooks-install 要據實回報每一支的 verdict:claude、codex、copilot、antigravity 是 wired,kiro 是 degraded。不得暗示每個 CLI 都擋得住,也不得反過來暗示只有 claude 有保護。
部署後重啟閘門
部署換掉的是磁碟上的技能檔,目前工作階段載入的還是舊版。這段落差期間跑技能,改動看起來沒生效,人會以為部署失敗又重跑一次。
| 項目 | 規則 |
|---|---|
| 狀態檔 | $JSC_HOME/restart-required.d/{cli},一支 CLI 一份,由 jsc-cli:deploy 收尾寫入 |
| 清除時機 | 重啟 CLI 之後由 jsc-hooks 清除自己那一份,不必手動刪 |
| 該 CLI 那份存在時 | 擋下這支 CLI 的 jsc 技能呼叫,印出要重啟哪一支與狀態檔路徑。kiro 擋不了,改注入警告 |
| 該 CLI 那份不存在時 | 放行。別支 CLI 的狀態檔不影響這一支 |
| 判定位置 | 程式層,由 jsc-hooks/hooks/restart-gate.sh 執行,不靠技能內文自我約束 |
| 接線位置 | 與版本前置檢查完全相同,逐支見「版本前置檢查」的接線位置表 |
| 逃生門 | JSC_RESTART_GATE=off |
這道閘門在五支 CLI 上的能力,跟版本前置檢查一模一樣。 claude、codex、copilot、antigravity 都有能阻擋的 pre-tool 事件,接上去就真的擋得住,verdict 是 wired;kiro 的技能叫用不走工具管線,攔不到,只能在 userPromptSubmit 注入警告,verdict 是 degraded。這一節以前跟著「只有 claude 有 pre-tool hook」那個錯誤結論走,所以重啟閘門也在四支 CLI 上長期失效:部署完照樣跑舊版技能,沒有任何東西擋,也沒有任何東西報錯。兩道閘門共用同一套接線,就共用同一份事實表,不要在這一節另寫一份能力描述——寫兩份就會只改一份,另一份繼續錯著。
豁免清單(狀態檔存在也放行):
| 技能 | 為什麼不能擋 |
|---|---|
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。
狀態檔為什麼一支 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 |
MONITOR |
MONITOR_CONTENTS |
MONITOR_{HASH} |
助理巡檢的監控頁。技能與 hook 每跑一次就留下事件,助理把事件收攏、判斷健康狀態、寫進這裡 | jsc-assist |
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 產生。
在沒有存取庫的目錄也跑得出體檢,是這個例外存在的原因。
機器層的雜湊來源不只這一個,MONITOR 也照同一組取值,規則見下。
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 重跑也不會動到別支的頁。
MONITOR 的雜湊來源比照 CHECK,取 {主機名}/{登入帳號}。
助理巡檢的是一台機器,不是一個存取庫。
一台機器一頁,換一支 CLI 不另開頁。
算法同上,由同一支 jsc-gitea/tools/hash-id 產生。
為什麼不帶工具名稱。 這一點與 TOOLING 相反。
TOOLING 一支 CLI 一頁,因為每支 CLI 各有自己的已安裝 plugin 與 hook 接線。
助理看的是整台機器一份心跳、一本待辦簿,不分 CLI,所以中間那一段不能加。
監控頁一律附加,不覆寫。 助理的寫入是背景行為,覆寫錯了沒人在現場。
TOOLING 內容頁是每次盤點覆寫整頁,兩者的寫入語意剛好相反,不要混用。
審核檢查清單
新增或更新技能後逐項檢查,任一不符就修正:
- 名稱符合命名規則,且與既有技能目標不重複
- 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 每支
skills/*/SKILL.md的 frontmatter 解析得動,tools/lint-frontmatter.sh {domain-path}對該 domain 退出 0;退出 3 是「什麼都沒掃」,不算通過。frontmatter 有語法錯誤時,Antigravity 會靜默丟棄整支技能,沒有任何錯誤訊息,只有這支腳本抓得到 - 已同步更新該 domain 的 README「Skills 目錄」與三份 manifest 的 version
- PR 的 base 符合「PR 分支階梯」,沒有越級
流程檢查四項,對照技能自己的流程逐項確認:
- 技能內外引用的步驟編號、檔案路徑、節標題都真的存在,指標指得到。踩過的實例:規則搬到
references/後指標指向空處,照著指過去只看到空白 - 每個步驟以可檢核的完成條件結尾,沒有「理解後」「適當地」這類模糊語
- 每個外部呼叫(腳本、API、其他技能)的失敗情況都有明寫怎麼辦,退出碼都有分流
- 技能自己裝的閘門不會擋掉解除那道閘門的唯一路徑(閘門不自鎖)。踩過的實例:工作包閘門若擋掉
implement,結清 PR 就沒有路徑