Compare commits
131
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0c71224b2 | ||
|
|
fd6aa6f45c | ||
|
|
5fcfc16dd1 | ||
|
|
3514b29d6b | ||
|
|
58413ab808 | ||
|
|
9df303a377 | ||
|
|
619800e10c | ||
|
|
2ab643c6bd | ||
|
|
4fd9a52559 | ||
|
|
07d04c4d0c | ||
|
|
99871cd24f | ||
|
|
763709ed7b | ||
|
|
e47da3104c | ||
|
|
5a86f33d02 | ||
|
|
f2332f965c | ||
|
|
3bd23943fb | ||
|
|
54e77a8da9 | ||
|
|
cb8123faa0 | ||
|
|
d6bd022920 | ||
|
|
2503b0ec61 | ||
|
|
9877e19bbd | ||
|
|
f1825fd653 | ||
|
|
1a34529231 | ||
|
|
737e3560f3 | ||
|
|
ca062e37a8 | ||
|
|
8bb1effb93 | ||
|
|
06979128d9 | ||
|
|
fb72d564d1 | ||
|
|
15fdb7e140 | ||
|
|
5541a41ff7 | ||
|
|
5db1d8a608 | ||
|
|
6de2aa9321 | ||
|
|
2fa8d86045 | ||
|
|
b87dbb12cd | ||
|
|
03188837d3 | ||
|
|
c987d237e4 | ||
|
|
651ddb3a6f | ||
|
|
1605248b4f | ||
|
|
3c9137a81d | ||
|
|
f13724cb79 | ||
|
|
6697aa989d | ||
|
|
e8539ecfb2 | ||
|
|
bb26c73504 | ||
|
|
7b6b9076ea | ||
|
|
dabca319a9 | ||
|
|
3beca714fc | ||
|
|
a4df38d974 | ||
|
|
06f530d589 | ||
|
|
399753599d | ||
|
|
e3054961ec | ||
|
|
9c7e192f33 | ||
|
|
970b7a4f71 | ||
|
|
9c40c11c7d | ||
|
|
06fe739070 | ||
|
|
c4fdd8b2d8 | ||
|
|
7894fa073e | ||
|
|
4a741071e5 | ||
|
|
beede79d3a | ||
|
|
d879e634c0 | ||
|
|
76c1232302 | ||
|
|
1cfd727436 | ||
|
|
9ed26de7ba | ||
|
|
f5f2cb05bd | ||
|
|
651dc19be9 | ||
|
|
2e237b7674 | ||
|
|
87952c7cb0 | ||
|
|
29e9800da8 | ||
|
|
66b865456c | ||
|
|
6d28e207c7 | ||
|
|
5aa4a3da57 | ||
|
|
7dc5c32de3 | ||
|
|
b89889c2e0 | ||
|
|
9dabe98f2b | ||
|
|
7dd230d9b4 | ||
|
|
0669b64c5f | ||
|
|
7127999eb3 | ||
|
|
239e717b86 | ||
|
|
d159847c94 | ||
|
|
0cf2392ae7 | ||
|
|
74aff34872 | ||
|
|
b48665f946 | ||
|
|
bd649d3f42 | ||
|
|
2f6c9b05c3 | ||
|
|
137491fcb6 | ||
|
|
ec35ad9a52 | ||
|
|
c593803ed5 | ||
|
|
a88bfe3d21 | ||
|
|
11b19fb572 | ||
|
|
d183f4fba5 | ||
|
|
4ac03f9ae1 | ||
|
|
771d71c28e | ||
|
|
32c619bfb3 | ||
|
|
aeb7b1c9e7 | ||
|
|
cc9cd66eb5 | ||
|
|
41cfc8d54c | ||
|
|
08a7d23a12 | ||
|
|
b0ea356ff5 | ||
|
|
2b81e67817 | ||
|
|
ccae8bfd7a | ||
|
|
29080ca6e8 | ||
|
|
baa1f1118f | ||
|
|
b1d39e7ac5 | ||
|
|
a65f546911 | ||
|
|
0ce64ba392 | ||
|
|
816fe07fbd | ||
|
|
478f670d53 | ||
|
|
9a0ea8e248 | ||
|
|
6447416c7b | ||
|
|
d4e3af8397 | ||
|
|
47c479d65c | ||
|
|
07d8583937 | ||
|
|
02b36a315c | ||
|
|
769d8f3f33 | ||
|
|
0c106ab7eb | ||
|
|
44fce3a68c | ||
|
|
69003a7d2c | ||
|
|
7a2544772f | ||
|
|
7c963b73cf | ||
|
|
19303ca555 | ||
|
|
0db1d942fb | ||
|
|
444ab9762b | ||
|
|
d9d1dafe6e | ||
|
|
a8e491663f | ||
|
|
106b9f7df3 | ||
|
|
fbb7004e70 | ||
|
|
acbbd30a0f | ||
|
|
dba1aa2b44 | ||
|
|
bdfea58fab | ||
|
|
073c51e145 | ||
|
|
9ab5b42864 | ||
|
|
287e3ff113 |
@@ -13,6 +13,14 @@
|
|||||||
},
|
},
|
||||||
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
|
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jsc-assist",
|
||||||
|
"source": {
|
||||||
|
"source": "url",
|
||||||
|
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
|
||||||
|
},
|
||||||
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "jsc-cli",
|
"name": "jsc-cli",
|
||||||
"source": {
|
"source": {
|
||||||
@@ -43,7 +51,7 @@
|
|||||||
"source": "url",
|
"source": "url",
|
||||||
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
|
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
|
||||||
},
|
},
|
||||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄"
|
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jsc-log",
|
"name": "jsc-log",
|
||||||
@@ -75,7 +83,7 @@
|
|||||||
"source": "url",
|
"source": "url",
|
||||||
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
|
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
|
||||||
},
|
},
|
||||||
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組"
|
"description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jsc-sdlc",
|
"name": "jsc-sdlc",
|
||||||
@@ -83,7 +91,7 @@
|
|||||||
"source": "url",
|
"source": "url",
|
||||||
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
|
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
|
||||||
},
|
},
|
||||||
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)"
|
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,14 @@
|
|||||||
},
|
},
|
||||||
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
|
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jsc-assist",
|
||||||
|
"source": {
|
||||||
|
"source": "url",
|
||||||
|
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
|
||||||
|
},
|
||||||
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "jsc-cli",
|
"name": "jsc-cli",
|
||||||
"source": {
|
"source": {
|
||||||
@@ -43,7 +51,7 @@
|
|||||||
"source": "url",
|
"source": "url",
|
||||||
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
|
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
|
||||||
},
|
},
|
||||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄"
|
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jsc-log",
|
"name": "jsc-log",
|
||||||
@@ -75,7 +83,7 @@
|
|||||||
"source": "url",
|
"source": "url",
|
||||||
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
|
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
|
||||||
},
|
},
|
||||||
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組"
|
"description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jsc-sdlc",
|
"name": "jsc-sdlc",
|
||||||
@@ -83,7 +91,7 @@
|
|||||||
"source": "url",
|
"source": "url",
|
||||||
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
|
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
|
||||||
},
|
},
|
||||||
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)"
|
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-meta",
|
"name": "jsc-meta",
|
||||||
"version": "0.0.8",
|
"version": "0.3.8",
|
||||||
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
|
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
@@ -13,5 +13,14 @@
|
|||||||
"meta",
|
"meta",
|
||||||
"skills",
|
"skills",
|
||||||
"cross-tool"
|
"cross-tool"
|
||||||
]
|
],
|
||||||
|
"jsc": {
|
||||||
|
"requires": {
|
||||||
|
"jsc-ask": ">=0.0.6",
|
||||||
|
"jsc-cli": ">=0.2.1",
|
||||||
|
"jsc-git": ">=0.0.9",
|
||||||
|
"jsc-gitea": ">=0.1.8",
|
||||||
|
"jsc-hooks": ">=0.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-meta",
|
"name": "jsc-meta",
|
||||||
"version": "0.0.8",
|
"version": "0.3.8",
|
||||||
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
|
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
|
||||||
"skills": "./skills"
|
"skills": "./skills",
|
||||||
|
"jsc": {
|
||||||
|
"requires": {
|
||||||
|
"jsc-ask": ">=0.0.6",
|
||||||
|
"jsc-cli": ">=0.2.1",
|
||||||
|
"jsc-git": ">=0.0.9",
|
||||||
|
"jsc-gitea": ">=0.1.8",
|
||||||
|
"jsc-hooks": ">=0.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
jsc 技能組的 meta domain:新建、更新、刪除技能的流程,以及**全技能組的準則單一來源** `references/guidelines.md`(命名、description 規則、hook / 工具 / sub agent 下放、語言、環境變數、wiki 頁命名總表、審核檢查清單)。
|
jsc 技能組的 meta domain:新建、更新、刪除技能的流程,以及**全技能組的準則單一來源** `references/guidelines.md`(命名、description 規則、hook / 工具 / sub agent 下放、語言、環境變數、wiki 頁命名總表、審核檢查清單)。
|
||||||
|
|
||||||
## 安裝 / 更新 / 移除
|
## 安裝、更新、移除
|
||||||
|
|
||||||
Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安裝 token 為 `jsc-meta@jsc`。每個指令一行:
|
Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安裝 token 為 `jsc-meta@jsc`。每個指令一行:
|
||||||
|
|
||||||
@@ -26,27 +26,31 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
|||||||
|
|
||||||
### `skill-new`
|
### `skill-new`
|
||||||
|
|
||||||
新建技能:決策樹問細節(目標、觸發、輸入輸出、domain)→ 缺 domain 時依 template 結構建立新存取庫 → sub agent 依準則產生技能 → 審核清單自檢 → PR。
|
新建技能:先併行預跑 `list-skills.sh` 與 `sync-domains.sh`,再用決策樹問細節(目標、觸發、輸入輸出、domain)→ 缺 domain 時依 template 結構建立新存取庫 → sub agent 依準則產生技能 → 審核清單自檢 → PR,並依 `references/pr-report.md` 回報 → 依 `references/deploy-verify.md` 部署與驗證。
|
||||||
|
|
||||||
### `skill-update`
|
### `skill-update`
|
||||||
|
|
||||||
更新技能:先查 Gitea 正本 marketplace 取得 domain 清單並補 clone 缺少的存取庫,列出全部技能 → 使用者選擇 → 決策樹問更新細節 → 更新後依審核檢查清單逐項檢查,不符就回到詢問 → PR。
|
更新技能:先查 Gitea 正本 marketplace 取得 domain 清單並補 clone 缺少的存取庫,列出全部技能 → 使用者選擇 → 決策樹問更新細節 → 更新後依審核檢查清單逐項檢查,不符就回到詢問 → PR,並依 `references/pr-report.md` 回報 → 依 `references/deploy-verify.md` 部署與驗證。
|
||||||
|
|
||||||
### `skill-delete`
|
### `skill-delete`
|
||||||
|
|
||||||
刪除技能:先查 Gitea 正本 marketplace 取得 domain 清單並補 clone 缺少的存取庫,列出全部技能 → 使用者選擇 → sub agent 盤點關聯檔案 → 逐檔判斷是否需修正以維持功能(需要就決策樹問修正細節並依準則檢查)→ 刪除 → sub agent 實地檢查各 CLI 的技能與 hook 保存位置,確認沒有殘留 → PR。
|
刪除技能:`sync-domains.sh` 依 Gitea 正本 marketplace 同步所有存取庫,`list-skills.sh` 列出全部技能 → 使用者選擇 → `find-skill-refs.sh` 盤點關聯檔案 → 併行的 sub agent 逐檔判斷是否需修正以維持功能(需要就決策樹問修正細節並依準則檢查)→ 刪除 → PR → 依 `references/deploy-verify.md` 部署,再用 `verify-skill-removed.sh` 實地檢查各 CLI 的技能快取與 hook 設定。深層刪除只在部署後查一次:部署前的刪除還沒生效,查了一定乾淨,證明不了任何事。
|
||||||
|
|
||||||
### `skillset-update`
|
### `skillset-update`
|
||||||
|
|
||||||
批次更新——把一份變更需求套用到整個技能組的多個技能/domain,先檢查工具化、sub agent 與環境變數優先規則,再逐 repo 開 PR;單一技能改用 skill-update。
|
批次更新——把一份變更需求套用到整個技能組的多個技能、domain。同步存取庫與決策樹併行起跑,先檢查工具化、sub agent 與環境變數優先規則,再由併行的 sub agent 逐 repo 改檔與開 PR;PR 回報格式見 `references/pr-report.md`,部署與驗證見 `references/deploy-verify.md`;單一技能改用 skill-update。
|
||||||
|
|
||||||
### `skill-check`
|
### `skill-check`
|
||||||
|
|
||||||
例行稽核——沒有變更需求時,把整個技能組逐一對照準則的審核檢查清單:sub agent 逐 domain 稽核 → 不符項目逐項決策樹確認 → sub agent 套用修正並複檢 → 逐 repo 開 PR。有變更需求改用 skillset-update。
|
例行稽核——沒有變更需求時,同步存取庫之後併行跑三組:`lint-scripts.sh` 加 `lint-frontmatter.sh` 加 `check-behaviors.sh` 加 `ste100-lint.sh` 加 `check-wiki-rules.sh` 加 `check-page-name.sh` 加 hook smoke、準則審核檢查清單、流程與成本優化審查。優化審查先讀回各 domain `SKILLSET_{HASH}` 上已決議的建議,決議欄寫著「套用」或「延後」的不重複掃、不重複問;面向包含可平行化、可下放工具、重複來回、冗餘步驟、過早或過晚的閘門與可省的成本。不符項目與優化建議分開回報,逐項決策樹確認並記下決議與決議日期後才套用,逐 repo 開 PR,最後把本輪結果附加到每個受影響 domain 的 `SKILLSET_{HASH}` 並登記在 `SKILLSET_CONTENTS`。有變更需求改用 skillset-update。
|
||||||
|
|
||||||
### `ste100-sync`
|
### `ste100-sync`
|
||||||
|
|
||||||
同步上游 speak-human-tw 的語言規則:比對 `references/ste100.md` 釘住的上游版本,有新版就萃取適用的變更、更新 lint 樣式、全庫重掃,最後開 PR。適合列為本 repo 的維護方式。
|
同步上游 speak-human-tw 的語言規則:先只讀上游 `SKILL.md` frontmatter 的版本來比對,判定要更新才 clone;有新版就萃取適用的變更、逐項決策樹確認、更新 lint 樣式與 `jsc-hooks` 的簡體字表、全庫併行重掃、bump manifest,最後開 PR。適合列為本 repo 的維護方式。
|
||||||
|
|
||||||
|
### `tooling-guide`
|
||||||
|
|
||||||
|
盤點目前支援的 plugins、skills、hooks 管理與使用路徑,產出技能組基礎指引。基礎盤點一律沿用 `inventory-tooling.sh` 的輸出,不再重跑它內部已經跑過的三支腳本。適合建立技能組地圖、支援清單、hook 管理總覽與新人交接資料;安裝、更新、刪除、稽核與修復改用對應技能。
|
||||||
|
|
||||||
<!-- JSC-SKILLS:END -->
|
<!-- JSC-SKILLS:END -->
|
||||||
|
|
||||||
@@ -56,7 +60,30 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `references/guidelines.md` | 技能準則唯一來源,所有 domain 的 AGENTS.md 都指向這裡 |
|
| `references/guidelines.md` | 技能準則唯一來源,所有 domain 的 AGENTS.md 都指向這裡 |
|
||||||
| `references/ste100.md` | STE100 擬人台灣感語言規則唯一來源(改寫自 speak-human-tw,MIT) |
|
| `references/ste100.md` | STE100 擬人台灣感語言規則唯一來源(改寫自 speak-human-tw,MIT) |
|
||||||
| `tools/ste100-lint.sh` | 語言規則的機檢工具:中國用語、中文句內半形標點、AI 套話;命中 exit 1 |
|
| `references/pr-report.md` | PR 收尾回報格式唯一來源,所有會開 PR 的技能都指向這裡 |
|
||||||
|
| `references/deploy-verify.md` | 四支異動技能共用的部署與驗證流程:判路線、部署或工作樹、**在新的 CLI 行程裡驗證**、失敗分流 |
|
||||||
|
| `references/behaviors.md` | 本 domain 的技能行為清單:一支技能一節,五列記下觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,供稽核與驗證比對。格式合約見 `references/guidelines.md` 的「技能行為清單」 |
|
||||||
|
| `templates/skillset-contents.md` | `SKILLSET_CONTENTS` 目錄頁樣板。大標題加條列:一個 H2 區塊代表一個 domain 存取庫,標題是內容頁頁名 `SKILLSET_{HASH}`,欄位一行一條;本頁落在 `JSC_WIKI_REPO_CONTENTS`,連結一律寫成 `[{文字}]({連結})` 的絕對網址並先過 `link-check.sh` 驗證,寫入一律用 `jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}"` 只寫自己那一個區塊。`<key-col>` 那個 `2` 是**舊表格裡持有內容頁連結那一欄的序號**,只供自動轉檔用,序號照**線上那一頁實際的欄位排法**數、不是照範本:線上舊表頭是 `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`,連結在第 2 欄 |
|
||||||
|
| `templates/skillset-page.md` | `SKILLSET_{HASH}` 內容頁樣板。歷次異動**累積**分節,每節記日期、異動類型、異動需求、動到的技能、改動檔案、PR 網址、部署路線判定與驗證結果;`skill-check` 那一節另含優化建議表,決議與決議日期兩欄供下一輪讀回 |
|
||||||
|
| `templates/tooling-contents.md` | `TOOLING_CONTENTS` 目錄頁樣板。大標題加條列:一個 H2 區塊代表一組「機器、CLI、帳號」,標題是內容頁頁名 `TOOLING_{HASH}`,欄位一行一條;寫入一律用 `jsc-gitea/tools/wiki-contents.sh upsert TOOLING 1 "TOOLING_{HASH}"`,只更新自己那一個區塊,別人的區塊原樣保留,**禁止整頁覆蓋** |
|
||||||
|
| `templates/tooling-page.md` | `TOOLING_{HASH}` 內容頁樣板。分節對應 `inventory-tooling.sh` 的輸出;**每次盤點覆寫整頁**,只留現況,不留歷史 |
|
||||||
|
| `tools/plugins-root.sh` | 推導技能組工作目錄的根,六支腳本共用。以 plugin 形式安裝時「腳本上兩層」會落在快取目錄,所以推導規則抽出來;推不出來 exit 1 並指名要設 `JSC_PLUGINS_ROOT` |
|
||||||
|
| `tools/ste100-lint.sh` | 語言規則的機檢工具:中國用語、中文句內半形標點、AI 套話、簡體字、中文並列斜線;命中 exit 1,沒給檢查對象 exit 2 |
|
||||||
|
| `tools/lint-scripts.sh` | 一個 domain 的腳本檢查三合一:`sh -n` 語法、執行權限、檔頭結束碼宣告;有不合格 exit 1,沒有腳本可掃 exit 3(**不等於通過**) |
|
||||||
|
| `tools/lint-frontmatter.sh` | 一個 domain 每支 `skills/*/SKILL.md` 的 frontmatter 解析檢查:分隔線成對、必要鍵齊全、未加引號的純量不含「冒號加空白」也不以 YAML 特殊字元起頭、引號收得起來。不相依任何 YAML 套件。不合格 exit 1(清單在 stderr),用法錯誤 exit 2,沒有 SKILL.md 可掃 exit 3(**不等於通過**)。frontmatter 壞掉時 Antigravity 會**靜默丟棄整支技能**,沒有任何錯誤訊息 |
|
||||||
|
| `tools/check-behaviors.sh` | 比對一個 domain 的 `references/behaviors.md` 與 `skills/`:節對技能、字典序、每節一張表、五個欄位齊全且內容欄非空,另斷言「可驗證跡象」那一列寫到收尾的 `skill-end` 事件或 `events.jsonl`(技能的成敗只有技能自己寫得出來,hook 觸發時實際工作還在後面的模型輪次;有 start 沒有配對的 end 就是中止);不符 exit 1,用法錯誤 exit 2,找不到清單或找不到技能 exit 3(**不等於通過**) |
|
||||||
|
| `tools/check-link-format.sh` | 檢查一個 domain 全部 `*.md` 的連結寫法:一律 `[{文字}]({連結})`,不留同 wiki 的雙括號連結。判定前先剝掉行內程式碼與圍籬區塊,所以說明用的字面與 shell 條件測試不會誤判。有命中 exit 1(清單在 stdout),用法錯誤 exit 2,沒有文件可掃 exit 3(**不等於通過**) |
|
||||||
|
| `tools/check-page-name.sh` | 比對 wiki 頁名樣式三處是否一致:`jsc-gitea/tools/page-name.sh`(正本)、`jsc-hooks/hooks/comment-scope.sh`、`jsc-log/tools/worklog-pending.sh`。三處刻意不共用函式,因為 hook 必須自足;斷言十五種型別、40 碼與 8 碼兩種長度。不一致 exit 1,用法錯誤 exit 2,三處一支都找不到 exit 3(**不等於通過**) |
|
||||||
|
| `tools/deploy-route.sh` | 判定改動有沒有進存取庫的預設分支,決定走部署路線(exit 0)或工作樹路線(exit 3);判不出來 exit 1,**不等於工作樹路線** |
|
||||||
|
| `tools/sync-domains.sh` | 依 Gitea 正本 marketplace 把所有 domain 存取庫 clone 或 pull 到本機,印出 `domain<TAB>path`;**只有 exit 0 代表全部到位且最新**,exit 3 代表有存取庫跳過或 pull 失敗(stderr 列路徑),exit 2 代表有 domain clone 失敗 |
|
||||||
|
| `tools/list-skills.sh` | 列出正本 marketplace 上各 domain 存取庫的技能,印出 `domain<TAB>name<TAB>description`;不在正本清單上的存取庫不列 |
|
||||||
|
| `tools/inventory-tooling.sh` | 盤點目前支援的 plugins、skills、tools、hooks,輸出技能組基礎指引 Markdown |
|
||||||
|
| `tools/sync-marketplace.sh` | 寫入或更新兩份正本 marketplace 的 plugin 條目,再複製到每個 domain 存取庫(保證位元組一致)。**需要 python3**(json 模組改 JSON),缺 python3 不寫檔並 exit 1;存取庫不在本機 exit 3 |
|
||||||
|
| `tools/sync-skill-manifest.sh` | 同步 domain README 的「Skills 目錄」,並 bump 三份 manifest 的 version;`minor` 與 `patch` 不得超過 `9`,`major` 可以超過 `9`。缺目錄、缺標記或缺 version 欄位 exit 1,用法錯誤 exit 2 |
|
||||||
|
| `tools/find-skill-refs.sh` | 盤點一個技能在正本 marketplace 各 domain 存取庫裡的引用檔案(不掃非技能組存取庫與點開頭目錄);技能名稱為純子字串比對,命中要逐檔確認;零命中 exit 1,掃描失敗 exit 3 |
|
||||||
|
| `tools/verify-skill-removed.sh` | 刪除技能後實地檢查各 CLI 的技能快取與 hook 設定有無殘留;有殘留 exit 1,沒偵測到 CLI 或沒有可查位置 exit 3(**不等於乾淨**) |
|
||||||
|
|
||||||
|
兩份 `TOOLING` 樣板的寫入語意與版面都剛好相反,套用前先分清楚。目錄頁是共用的,整頁覆蓋會刪掉別台機器的紀錄,所以只准動自己那一個 H2 區塊,版面一律大標題加條列,頁上不留 markdown 表格。內容頁只屬於一組「機器、CLI、帳號」,記的是當下現況,舊的安裝內容早就不成立,所以整頁覆寫,版面維持圖表優先。頁名、雜湊與這條版面區分見 `references/guidelines.md` 的「Wiki 頁命名總表」。
|
||||||
|
|
||||||
## 相關 domain
|
## 相關 domain
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -1,6 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-meta",
|
"name": "jsc-meta",
|
||||||
"version": "0.0.8",
|
"version": "0.3.8",
|
||||||
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
|
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
|
||||||
"skills": "./skills/"
|
"skills": "./skills/",
|
||||||
|
"jsc": {
|
||||||
|
"requires": {
|
||||||
|
"jsc-ask": ">=0.0.6",
|
||||||
|
"jsc-cli": ">=0.2.1",
|
||||||
|
"jsc-git": ">=0.0.9",
|
||||||
|
"jsc-gitea": ">=0.1.8",
|
||||||
|
"jsc-hooks": ">=0.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# jsc-meta 技能行為清單
|
||||||
|
|
||||||
|
本頁記錄 jsc-meta 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
|
||||||
|
|
||||||
|
## skill-check
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 手上沒有異動需求,要對整組技能做例行或臨時稽核時用。帶著異動需求要改多支技能走 skillset-update、只改一支走 skill-update |
|
||||||
|
| 關鍵步驟 | 先跑 sync-domains.sh 同步全部 domain 存取庫、再平行跑三組審查(第一組平行跑腳本檢查、frontmatter 檢查、行為清單檢查、語言檢查、連結寫法檢查、腳本路徑檢查、wiki 規則檢查、頁名樣式檢查、委派清單檢查與 hook smoke,其中委派清單檢查跑 check-delegate.sh 整輪一次、不逐 domain 跑,退出 0 時 stdout 上的 seed 與版本落後只是提示、不算不合規,退出 1 的缺列多列空欄、死掉的 next 與填錯的 probe 逐項當不合規報,退出 3 記成「無委派清單可查」也不算通過,第二組以 sub agent 逐 domain 對 guidelines 檢查清單稽核,第三組先用 wiki-repo SKILLSET 與 hash-id 讀回各 domain SKILLSET_{HASH} 上已決議的優化建議再以 sub agent 分六個面向審查流程與成本;讀取回 7、8 或存取庫解不出來時只停掉該 domain 的第三組,第一組、第二組與後續步驟照跑)、合併三組結果並用決策樹逐項確認(第二組留白的九項由第一組的結論補上,其中頁名樣式與 wiki 規則兩項是整輪一份,同一個結論填進每個 domain;委派清單檢查不是檢查清單那九項之一,整輪自成一條結論,只有它的不合規進決策樹、提示不進;優化建議記下決議與決議日期)、以平行 sub agent 套用確認過的修正並跑 sync-skill-manifest.sh、由主 agent 自己改 delegate-spec.tsv 補上或刪掉判定列(缺列先用 delegate-criteria.md 的決策樹問過再寫,origin 記 judged,填錯的 probe 回該技能的存取庫核對過再改寫,切不出唯讀入口就寫成 pending;那個檔整組技能共用一份,交給平行 sub agent 寫會互相蓋掉)、跑 sync-marketplace.sh 同步兩份正本 marketplace、重跑三組驗證直到接受的修正全通過、每個受影響存取庫各開一條 PR、最後以平行 sub agent 逐 domain 把本輪稽核結果附加到 SKILLSET_{HASH},再取 wiki-url 的絕對網址、把頁上與列上的每個連結交給 link-check.sh 驗證、退出 0 才用 wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" 把自己那一個 H2 區塊寫進 CONTENTS 存取庫的 SKILLSET_CONTENTS(目錄頁一律大標題加條列,鍵是 H2 標題也就是內容頁頁名,第四個參數是區塊檔),最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-check 寫一筆收尾事件(五種 status 依本輪實際結果選,中途停下的輪次也要寫;腳本不在就安靜跳過,不影響本輪結局) |
|
||||||
|
| 外部呼叫 | tools/sync-domains.sh、jsc-hooks/tools/report-status.sh skill-end、tools/lint-scripts.sh、tools/lint-frontmatter.sh、tools/check-behaviors.sh、tools/ste100-lint.sh、tools/check-link-format.sh、tools/check-skill-paths.sh、tools/check-page-name.sh、tools/check-delegate.sh、tools/sync-skill-manifest.sh、tools/sync-marketplace.sh、jsc-gitea/tools/check-wiki-rules.sh、jsc-gitea/tools/link-check.sh、jsc-gitea/tools/gitea.sh 的 wiki-repo、hash-id 與 wiki-url、jsc-gitea/tools/wiki-contents.sh upsert(目錄頁自己那個區塊)、jsc-cli/tools/detect-clis.sh、jsc-hooks/tools/wire-cli.sh smoke、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki、templates/skillset-page.md、templates/skillset-contents.md |
|
||||||
|
| 完成條件 | 每個 domain 都有腳本檢查、frontmatter 檢查、行為清單檢查、語言檢查與連結寫法檢查的結論(連結寫法檢查退出 3 是「什麼都沒掃」,不算通過),wiki 規則檢查、頁名樣式檢查與委派清單檢查各有一次結論(frontmatter 檢查退出 3 是「什麼都沒掃」、頁名樣式檢查退出 3 是「什麼都沒查」、委派清單檢查退出 3 是「無委派清單可查」,都不算通過;委派清單檢查退出 0 時的提示要與不合規分開記)、每個 domain 的檢查清單在合併後補齊且那兩項整輪一份的結論在每個 domain 都填上同一個值、讀不到已決議清單的 domain 記成「本輪未取得已決議清單,優化建議暫不提出」、每項不合規與每項優化建議都有決策紀錄且優化建議帶決議日期、接受的修正重驗通過、每個受影響存取庫都拿到 PR 網址、寫進 wiki 的每個連結都先經 link-check.sh 退出 0、每個受影響 domain 的 wiki 頁都寫成功,或列為未寫入並附完整內容、本輪的 skill-end 事件已寫入,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:skill-check 的 skill-end 事件(本輪唯一必留的跡象,腳本不在時才沒有)、受影響存取庫留下檔案改動、改到行為的技能連帶改寫該存取庫的 references/behaviors.md、每個 domain 的 lint-frontmatter.sh、ste100-lint.sh 與 check-link-format.sh 退出 0、check-skill-paths.sh 退出 0(unrooted 與 unknown 只是提示)、check-page-name.sh 退出 0、check-delegate.sh 退出 0、本輪修過的判定列留在 plugins/meta 的 tools/delegate-spec.tsv、README 的「Skills 目錄」重寫、三份 manifest 版本號提升、兩份 marketplace 檔逐位元一致、每個受影響存取庫一條 PR、每個受影響 domain 的 SKILLSET_{HASH} 各附加一節,並由 wiki-contents.sh upsert 退出 0 在 SKILLSET_CONTENTS 留下自己那一個 `## SKILLSET_{HASH}` 區塊、區塊裡以 `- 異動頁:[SKILLSET_{HASH}]({連結})` 的絕對網址指向該內容頁;本輪無 domain 被改動時,改成 plugins/meta 那一頁記「本輪無發現」 |
|
||||||
|
|
||||||
|
## skill-delete
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 要把一支技能從技能組移除時用。改名不走這支,走 skill-update |
|
||||||
|
| 關鍵步驟 | 跑 sync-domains.sh 同步、跑 list-skills.sh 列出全部技能、讓使用者挑一支確認刪除、跑 find-skill-refs.sh 盤點所有引用檔案、以平行 sub agent 逐檔修正到檢查清單全過(盤點裡的 delegate-spec.tsv 那一列留到刪除那一步一起處理,不在逐檔迴圈裡改)、刪掉 skills/{name}/ 目錄、移除 references/behaviors.md 對應那一節、刪掉 plugins/meta 的 tools/delegate-spec.tsv 那一列、把其他列指向這支的 next 改掉,並檢查有沒有別列的 probe 指到這次一起刪掉的腳本、跑 sync-skill-manifest.sh、跑 check-delegate.sh 整輪一次確認清單裡不再有這支(退出 0 時 stdout 的 seed 與版本落後只是提示、不擋收尾,退出 1 逐項修,退出 3 不算通過)、開 PR(技能不在 meta 時,清單那一筆改的是 plugins/meta,另開一條 PR)、依 deploy-verify.md 部署、用新的 CLI 行程驗證、跑 verify-skill-removed.sh 查磁碟殘留、用 wiki-repo SKILLSET 解出存取庫並依 templates/skillset-page.md 把異動報告附加到 SKILLSET_{HASH}、再取 wiki-url 的絕對網址、把頁上與列上的每個連結交給 link-check.sh 驗證、退出 0 才用 wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" 把自己那一個 H2 區塊寫進 CONTENTS 存取庫的 SKILLSET_CONTENTS(目錄頁一律大標題加條列,鍵是 H2 標題也就是內容頁頁名,第四個參數是區塊檔) 並依 0、1、2、3、4、7、8 各自分流,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-delete 寫一筆收尾事件(五種 status 依本次實際結果選,中途停下也要寫;腳本不在就安靜跳過,不影響本次結局) |
|
||||||
|
| 外部呼叫 | tools/sync-domains.sh、jsc-hooks/tools/report-status.sh skill-end、tools/list-skills.sh、tools/find-skill-refs.sh、tools/check-behaviors.sh、tools/check-delegate.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、tools/verify-skill-removed.sh、jsc-gitea/tools/link-check.sh、jsc-gitea/tools/gitea.sh 的 wiki-repo、hash-id 與 wiki-url、jsc-gitea/tools/wiki-contents.sh upsert(目錄頁自己那個區塊)、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki、templates/skillset-page.md、templates/skillset-contents.md |
|
||||||
|
| 完成條件 | 盤點清單每一檔都有「已修正」「無需修正」或「留到刪除那一步處理」的結論、技能目錄與行為清單那一節都不存在、委派清單沒有那一列也沒有別列的 next 指著它且 check-delegate.sh 退出 0、list-skills.sh 查不到那一列、殘留檢查退出 0 或據實記成「無處可查」並帶進報告、待辦簿引用那一半據實記成尚未接線、本次動到的每個存取庫都有 PR 網址、寫進頁與列的每個連結都經 link-check.sh 退出 0、SKILLSET_{HASH} 附加一節且舊節原樣留著、wiki-contents.sh upsert 退出 0、本次的 skill-end 事件已寫入,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:skill-delete 的 skill-end 事件(本次唯一必留的跡象,腳本不在時才沒有)、skills/{name}/ 目錄消失、references/behaviors.md 少一節、plugins/meta 的 tools/delegate-spec.tsv 少一列、README 與三份 manifest 更新、動到的每個存取庫各一條 PR、wiki SKILLSET_{HASH} 附加一節,並在 CONTENTS 存取庫的 SKILLSET_CONTENTS 留下自己那一個 `## SKILLSET_{HASH}` 區塊、區塊裡以 `- 異動頁:[SKILLSET_{HASH}]({連結})` 的絕對網址指向該內容頁 |
|
||||||
|
|
||||||
|
## skill-new
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 要在技能組新增一支技能時用。改既有技能走 skill-update |
|
||||||
|
| 關鍵步驟 | 平行跑 list-skills.sh 與 sync-domains.sh 預取技能清單與 domain 清單、用決策樹問出目標、觸發時機、輸入輸出、所屬 domain,以及依 delegate-criteria.md 五題加 next 與 probe 兩題問出的委派判定、domain 未註冊就先確認存取庫在不在、依 template 結構補齊內容再跑 sync-marketplace.sh 註冊、以 sub agent 產生 skills/{name}/SKILL.md、在 references/behaviors.md 依字典序插入該技能一節、在 plugins/meta 的 tools/delegate-spec.tsv 補上這支的十二欄判定列(用不到的欄位填減號,origin 記 judged,probe 的路徑以 {root}/jsc-{domain}/ 開頭且不帶變數,沒有這一列不算建立完成)、跑 sync-skill-manifest.sh、自查 guidelines 檢查清單並跑 check-behaviors.sh 與整輪一次的 check-delegate.sh(退出 0 時 stdout 的 seed 與版本落後只是提示、不算缺失,退出 1 逐項修,退出 3 不算通過)、開 PR(技能不在 meta 時,判定列那一筆改的是 plugins/meta,另開一條 PR)、依 deploy-verify.md 部署並用新的 CLI 行程驗證、用 wiki-repo SKILLSET 解出存取庫並依 templates/skillset-page.md 把異動報告附加到 SKILLSET_{HASH}、再取 wiki-url 的絕對網址、把頁上與列上的每個連結交給 link-check.sh 驗證、退出 0 才用 wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" 把自己那一個 H2 區塊寫進 CONTENTS 存取庫的 SKILLSET_CONTENTS(目錄頁一律大標題加條列,鍵是 H2 標題也就是內容頁頁名,第四個參數是區塊檔) 並依 0、1、2、3、4、7、8 各自分流,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-new 寫一筆收尾事件(五種 status 依本次實際結果選,中途停下也要寫;腳本不在就安靜跳過,不影響本次結局) |
|
||||||
|
| 外部呼叫 | tools/list-skills.sh、tools/sync-domains.sh、jsc-hooks/tools/report-status.sh skill-end、tools/sync-marketplace.sh、tools/check-behaviors.sh、tools/check-delegate.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、jsc-gitea/tools/link-check.sh、jsc-gitea/tools/gitea.sh 的 clone-url、api、wiki-repo、hash-id 與 wiki-url、jsc-gitea/tools/wiki-contents.sh upsert(目錄頁自己那個區塊)、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki、templates/skillset-page.md、templates/skillset-contents.md |
|
||||||
|
| 完成條件 | 五項提問都有紀錄(含委派判定與 next 欄)、SKILL.md、行為清單那一節與委派判定列都在且該填的欄位都不空、README 與三份 manifest 同步、檢查清單全過且 check-behaviors.sh 與 check-delegate.sh 都退出 0、本次動到的每個存取庫都有 PR 網址、deploy-verify.md 第 1 到第 5 節的完成條件全數成立、寫進頁與列的每個連結都經 link-check.sh 退出 0、SKILLSET_{HASH} 附加一節且舊節原樣留著、wiki-contents.sh upsert 退出 0、本次的 skill-end 事件已寫入,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:skill-new 的 skill-end 事件(本次唯一必留的跡象,腳本不在時才沒有)、新增 skills/{name}/SKILL.md、references/behaviors.md 多一節、plugins/meta 的 tools/delegate-spec.tsv 多一列、README 與三份 manifest 更新、新 domain 時兩份 marketplace 檔多一筆 plugin 條目並同步到每個 domain 存取庫、動到的每個存取庫各一條 PR、wiki SKILLSET_{HASH} 附加一節,並在 CONTENTS 存取庫的 SKILLSET_CONTENTS 留下自己那一個 `## SKILLSET_{HASH}` 區塊、區塊裡以 `- 異動頁:[SKILLSET_{HASH}]({連結})` 的絕對網址指向該內容頁 |
|
||||||
|
|
||||||
|
## skill-update
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 要改一支既有技能時用。新增走 skill-new、刪除走 skill-delete、一次改多支或跨 domain 走 skillset-update |
|
||||||
|
| 關鍵步驟 | 跑 sync-domains.sh 同步、跑 list-skills.sh 列出全部技能、讓使用者挑一支、用決策樹問出改動細節,並在同一棵樹裡定案委派判定(動到流程或 description 就照 delegate-criteria.md 重跑五題加 next 與 probe 兩題,只改文案不動行為才可以沿用舊結論,但 probe 一律回存取庫核對過)、以 sub agent 改 SKILL.md 與相關檔案、同步更新 references/behaviors.md 該技能那一節、改 plugins/meta 的 tools/delegate-spec.tsv 那一列(重判就整列改寫並把 origin 記成 judged,沿用就只動 version 並照樣核對 probe;改名時連別列指過來的 next 一起改;沿用這件事寫進 PR 描述與 wiki 那一節,清單沒有備註欄)、跑 sync-skill-manifest.sh、對 guidelines 檢查清單逐項自查並跑 check-behaviors.sh 與整輪一次的 check-delegate.sh(退出 0 時 stdout 的 seed 與版本落後只是提示、不算缺失,只有指名本次改到那支的版本落後要回去補,退出 1 逐項修,退出 3 不算通過)、開 PR(技能不在 meta 時,判定列那一筆改的是 plugins/meta,另開一條 PR)、依 deploy-verify.md 部署並用新的 CLI 行程驗證、用 wiki-repo SKILLSET 解出存取庫並依 templates/skillset-page.md 把異動報告附加到 SKILLSET_{HASH}、再取 wiki-url 的絕對網址、把頁上與列上的每個連結交給 link-check.sh 驗證、退出 0 才用 wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" 把自己那一個 H2 區塊寫進 CONTENTS 存取庫的 SKILLSET_CONTENTS(目錄頁一律大標題加條列,鍵是 H2 標題也就是內容頁頁名,第四個參數是區塊檔) 並依 0、1、2、3、4、7、8 各自分流,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-update 寫一筆收尾事件(五種 status 依本次實際結果選,中途停下也要寫;腳本不在就安靜跳過,不影響本次結局) |
|
||||||
|
| 外部呼叫 | tools/sync-domains.sh、jsc-hooks/tools/report-status.sh skill-end、tools/list-skills.sh、tools/check-behaviors.sh、tools/check-delegate.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、jsc-gitea/tools/link-check.sh、jsc-gitea/tools/gitea.sh 的 wiki-repo、hash-id 與 wiki-url、jsc-gitea/tools/wiki-contents.sh upsert(目錄頁自己那個區塊)、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki、templates/skillset-page.md、templates/skillset-contents.md |
|
||||||
|
| 完成條件 | 每個提問都有紀錄、技能檔案帶著改動、行為清單那一節與新行為一致且 check-behaviors.sh 退出 0、委派判定列帶著重判結果或帶著沿用結論與更新過的 version 且 check-delegate.sh 退出 0、三份 manifest 同版、檢查清單全過、本次動到的每個存取庫都有 PR 網址、deploy-verify.md 第 1 到第 5 節的完成條件全數成立、寫進頁與列的每個連結都經 link-check.sh 退出 0、SKILLSET_{HASH} 附加一節且舊節原樣留著、wiki-contents.sh upsert 退出 0、本次的 skill-end 事件已寫入,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:skill-update 的 skill-end 事件(本次唯一必留的跡象,腳本不在時才沒有)、該技能的 SKILL.md 與相關檔案改動、references/behaviors.md 對應節改寫、plugins/meta 的 tools/delegate-spec.tsv 那一列改寫、README 與三份 manifest 更新、動到的每個存取庫各一條 PR、wiki SKILLSET_{HASH} 附加一節,並在 CONTENTS 存取庫的 SKILLSET_CONTENTS 留下自己那一個 `## SKILLSET_{HASH}` 區塊、區塊裡以 `- 異動頁:[SKILLSET_{HASH}]({連結})` 的絕對網址指向該內容頁 |
|
||||||
|
|
||||||
|
## skillset-update
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 一個異動需求橫跨多支技能或多個 domain,要一次做完時用。只改一支走 skill-update、手上沒有異動需求的例行稽核走 skill-check |
|
||||||
|
| 關鍵步驟 | 平行啟動 sync-domains.sh 與異動細節決策樹、問清楚改哪一條規則、影響哪些技能與 domain,並補問工具化、sub agent、環境變數三項塑形檢查、以每個 domain 一個 sub agent 平行套用改動、同步更新每個受影響 domain 的 references/behaviors.md、每個 sub agent 對自己動到的每一支技能逐支重跑 delegate-criteria.md 的決策樹(一支都不跳,五題加 next 與 probe 兩題,只改文案不動行為的才可以沿用並只動 version,probe 一律回自己存取庫核對過)但不自己寫檔,把判定列交回主 agent 一次併進 plugins/meta 的 tools/delegate-spec.tsv(那個檔整組共用一份,平行寫會互相蓋掉)、逐存取庫跑 sync-skill-manifest.sh、以平行 sub agent 重跑 guidelines 檢查清單與 check-behaviors.sh 直到全過、由主 agent 跑整批一次的 check-delegate.sh(退出 0 時 stdout 的 seed 與版本落後只是提示、不算缺失,只有指名本批動到那幾支的版本落後要回去補,退出 1 逐項修,退出 3 不算通過)、每個受影響存取庫各開一條 PR,判定列落在 plugins/meta 而 meta 不在受影響清單裡時另開一條、依 deploy-verify.md 部署並用新的 CLI 行程驗證、以平行 sub agent 逐存取庫用 wiki-repo SKILLSET 解出存取庫並依 templates/skillset-page.md 把異動報告附加到 SKILLSET_{HASH}、再取 wiki-url 的絕對網址、把頁上與列上的每個連結交給 link-check.sh 驗證、退出 0 才用 wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" 把自己那一個 H2 區塊寫進 CONTENTS 存取庫的 SKILLSET_CONTENTS(目錄頁一律大標題加條列,鍵是 H2 標題也就是內容頁頁名,第四個參數是區塊檔) 並依 0、1、2、3、4、7、8 各自分流,最後由主 agent 呼叫一次 jsc-hooks/tools/report-status.sh skill-end jsc-meta:skillset-update 寫一筆收尾事件(整批一筆,不逐 domain 寫;五種 status 依本次實際結果選,中途停下也要寫;腳本不在就安靜跳過,不影響本次結局) |
|
||||||
|
| 外部呼叫 | tools/sync-domains.sh、jsc-hooks/tools/report-status.sh skill-end、tools/check-behaviors.sh、tools/check-delegate.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、tools/list-skills.sh、jsc-gitea/tools/link-check.sh、jsc-gitea/tools/gitea.sh 的 wiki-repo、hash-id 與 wiki-url、jsc-gitea/tools/wiki-contents.sh upsert(目錄頁自己那個區塊)、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki、templates/skillset-page.md、templates/skillset-contents.md |
|
||||||
|
| 完成條件 | 受影響技能清單與三項塑形檢查都跟使用者談定、每個受影響存取庫都帶著改動、README 同步與 manifest 提升、每支動過的技能檢查清單全過且該 domain 的 check-behaviors.sh 退出 0、每支動過的技能都有本批的委派判定(重判或據實記成沿用)併進委派清單且整批一次的 check-delegate.sh 退出 0、每個受影響存取庫都有 PR 網址、deploy-verify.md 第 1 到第 5 節對每個存取庫都成立、每個存取庫寫進頁與列的每個連結都經 link-check.sh 退出 0、每個存取庫的 SKILLSET_{HASH} 都附加一節且舊節原樣留著、每個存取庫的 wiki-contents.sh upsert 都退出 0、本次整批一筆的 skill-end 事件已寫入,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:skillset-update 的 skill-end 事件(整批只有一行,本次唯一必留的跡象,腳本不在時才沒有)、每個受影響存取庫的技能檔案改動、各自的 references/behaviors.md 更新、plugins/meta 的 tools/delegate-spec.tsv 上每支動過的技能各一列判定、README 與三份 manifest 更新、每個存取庫一條 PR、每個存取庫的 wiki SKILLSET_{HASH} 各附加一節,並在 CONTENTS 存取庫的 SKILLSET_CONTENTS 各留下自己那一個 `## SKILLSET_{HASH}` 區塊、區塊裡以 `- 異動頁:[SKILLSET_{HASH}]({連結})` 的絕對網址指向該內容頁 |
|
||||||
|
|
||||||
|
## ste100-sync
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 定期維護,或上游 speak-human-tw 發佈新版時用。只改本地自訂規則不走這支 |
|
||||||
|
| 關鍵步驟 | 先讀 references/ste100.md 釘住的上游版本、再用 HTTPS 讀上游 SKILL.md frontmatter 的版本比對、同版就回報「上游沒有新版」並停在這裡、有新版才淺層 clone 取 changelog、以 sub agent 蒸餾適用於技術文件與對話的變更、用決策樹逐項確認採用、改寫 references/ste100.md 與「上游版本」行、必要時更新 ste100-lint.sh 的樣式與 jsc-hooks/hooks/simplified.txt、平行對每個 jsc 存取庫重跑 lint、跑 sync-skill-manifest.sh、開 PR、最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-meta:ste100-sync 寫一筆收尾事件(五種 status 依本次實際結果選,上游同版就停下的那一輪照樣要寫,選 ok;腳本不在就安靜跳過,不影響本次結局) |
|
||||||
|
| 外部呼叫 | 上游 speak-human-tw 的 raw SKILL.md 與 git clone、tools/ste100-lint.sh、tools/sync-domains.sh、jsc-hooks/tools/report-status.sh skill-end、tools/sync-skill-manifest.sh、jsc-ask:ask、jsc-git:pr |
|
||||||
|
| 完成條件 | 上游同版時停在版本比對並回報;有新版時每項蒸餾出來的變更都有決策、`sh -n tools/ste100-lint.sh` 通過且新採用的詞彙在測試字串上命中、本存取庫 lint 退出 0、其他存取庫的命中附 file:line 交給擁有者、三份 manifest 同版、PR 網址到手;兩條路徑都要寫下本次的 skill-end 事件,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:ste100-sync 的 skill-end 事件(兩條路徑都有,腳本不在時才沒有)、references/ste100.md 的「上游版本」行換值、tools/ste100-lint.sh 的樣式更新、jsc-hooks/hooks/simplified.txt 更新、三份 manifest 版本號提升、一條 PR;上游同版時除了那筆收尾事件沒有其他寫入跡象,只有回報內容 |
|
||||||
|
|
||||||
|
## tooling-guide
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 觸發時機 | 使用者要技能組導覽、工具地圖、支援的 plugin 清單、支援的技能清單、hook 管理概觀或新人上手參考時用。安裝、更新、刪除、稽核、修復都不走這支 |
|
||||||
|
| 關鍵步驟 | 跑 plugins-root.sh 確認工作根目錄、跑 sync-domains.sh 取得 domain 與本機路徑、跑 inventory-tooling.sh 產生基準盤點並同時蒐集管理流程事實、需要說明或分組時以 sub agent 綜整導覽草稿、主 agent 逐項核對每個說法的來源、依記錄下來的目標交付、目標是 wiki 頁時才以 sub agent 逐 CLI 用 hash-id 算出 TOOLING_{HASH}、先用 jsc-gitea:wiki 覆寫內容頁、再取 gitea.sh wiki-url 的絕對網址、把區塊裡的每個連結交給 link-check.sh 驗證、退出 0 才用 wiki-contents.sh upsert TOOLING 1 "TOOLING_{HASH}" 把自己那一個 H2 區塊寫進 CONTENTS 存取庫的 TOOLING_CONTENTS 並依 0、1、2、3、4、7、8 各自分流(目錄頁一律大標題加條列,版面正本在 wiki-contents.sh,本技能不自己組頁)、最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-meta:tooling-guide 寫一筆收尾事件(五種 status 依本次實際結果選,交付目標是聊天回應的那一輪也要寫;腳本不在就安靜跳過,不影響本次結局) |
|
||||||
|
| 外部呼叫 | tools/plugins-root.sh、tools/sync-domains.sh、tools/inventory-tooling.sh、jsc-hooks/tools/report-status.sh skill-end、jsc-gitea/tools/hash-id、jsc-gitea/tools/gitea.sh wiki-url、jsc-gitea/tools/link-check.sh、jsc-gitea/tools/wiki-contents.sh upsert(目錄頁自己那個區塊)、jsc-gitea:wiki、jsc-ask:ask、templates/tooling-page.md、templates/tooling-contents.md |
|
||||||
|
| 完成條件 | 每項事實都指得到來源檔案或工具輸出、必填章節都不是空的、收尾回報寫明交付目標、來源新鮮度、過期輸入與未知的 hook 判定;目標是 wiki 時每一頁都確認寫成功,或列為未寫入並附完整內容、寫進區塊的每個連結都經 link-check.sh 退出 0、每支偵測到的 CLI 的 wiki-contents.sh upsert 都退出 0;每一種交付目標都要寫下本次的 skill-end 事件,或據實記成腳本不在這台機器上 |
|
||||||
|
| 可驗證跡象 | $JSC_HOME/usage/events.jsonl 多一行 kind=skill、phase=end、name=jsc-meta:tooling-guide 的 skill-end 事件,那是這支唯讀技能唯一的寫入跡象;目標是聊天回應時除了那一行沒有其他寫入跡象,只有回報內容;目標是檔案時另外只產生使用者指定的那一個檔;目標是 wiki 時每支偵測到的 CLI 各一頁 TOOLING_{HASH},並由 wiki-contents.sh upsert 退出 0 在 TOOLING_CONTENTS 留下自己那一個 `## TOOLING_{HASH}` 區塊、區塊裡以 `- 盤點頁:[TOOLING_{HASH}]({連結})` 的絕對網址指向該內容頁,別台機器與別支 CLI 的區塊原樣保留 |
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# 技能委派判準
|
||||||
|
|
||||||
|
本文件是「一支技能能不能交給背景助理」的唯一判準來源。`skill-new`、`skill-update`、`skill-delete`、`skillset-update` 四支技能異動技能共用這一份,不各自抄一套。
|
||||||
|
|
||||||
|
判定結果一律寫進 [`tools/delegate-spec.tsv`](../tools/delegate-spec.tsv),由 [`tools/check-delegate.sh`](../tools/check-delegate.sh) 檢核。本文件只寫判準,不放判定結果。
|
||||||
|
|
||||||
|
## 交出方式只有三種
|
||||||
|
|
||||||
|
超出這三種的一律不交。
|
||||||
|
|
||||||
|
| 代號 | 方式 | 邊界 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `invoke` | 觸發 | 呼叫既有技能,內容照那支技能自己的流程走。助理只決定「什麼時候跑」,不改那支技能的判斷 |
|
||||||
|
| `patrol` | 巡檢 | 跑既有的唯讀腳本或讀既有狀態,產出現況。一個字都不改,讀完寫進監控頁 |
|
||||||
|
| `remind` | 提醒 | 把巡檢結果留到前景會話提出來。不代替使用者決定,也不自己動手 |
|
||||||
|
|
||||||
|
一支技能可以同時用多種方式,例如先巡檢再提醒。
|
||||||
|
|
||||||
|
## 判定決策樹
|
||||||
|
|
||||||
|
一支技能照順序回答五個問題,答完就得到結論。
|
||||||
|
|
||||||
|
| 題號 | 問題 | 答「是」往哪走 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 一 | 本體會改檔案,或對外不可逆? | 是就跳到第四題 |
|
||||||
|
| 二 | 本體需要使用者決策? | 需要就跳到第四題 |
|
||||||
|
| 三 | 輸出穩定,跑兩次結果一致? | 是就結論「全交」,方式填觸發 |
|
||||||
|
| 四 | 切得出唯讀盤點或提醒的一段? | 切得出就結論「切片交」 |
|
||||||
|
| 五 | 要加條件才切得出? | 是就結論「條件式交」,切不出就結論「不交」 |
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
S[一支技能] --> Q1{本體會改檔案,<br/>或對外不可逆?}
|
||||||
|
Q1 -->|會| Q4
|
||||||
|
Q1 -->|不會| Q2{本體需要使用者決策?}
|
||||||
|
Q2 -->|需要| Q4
|
||||||
|
Q2 -->|不需要| Q3{輸出穩定、跑兩次結果一致?}
|
||||||
|
Q3 -->|是| R1[全交:觸發]
|
||||||
|
Q3 -->|否| Q4
|
||||||
|
Q4{切得出唯讀盤點<br/>或提醒的一段?} -->|切得出| R2[切片交]
|
||||||
|
Q4 -->|要加條件才切得出| R3[條件式交]
|
||||||
|
Q4 -->|切不出| R4[不交]
|
||||||
|
```
|
||||||
|
|
||||||
|
結論之外還要多填兩項。
|
||||||
|
|
||||||
|
一是**這支技能跑完之後建議接哪一支**,寫進清單的 `next` 欄。不交的技能也要填——不交講的是助理不代跑,跟「跑完之後該接什麼」無關。
|
||||||
|
|
||||||
|
二是**那一段唯讀盤點實際要跑哪一個指令**,寫進清單的 `probe` 欄。只有交出方式是巡檢或提醒的那幾列要填:那幾列在待辦簿上的動作原本一律退回只提醒,有指令才有事情做。交出方式含觸發的列與不交的列一律填減號,理由見下一節。
|
||||||
|
|
||||||
|
## 四種結論,每一種都要留一列
|
||||||
|
|
||||||
|
| 結論 | `verdict` | 要記什麼 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 全交 | `full` | 交出方式、預設 `trigger` 與 `recur` |
|
||||||
|
| 切片交 | `slice` | 交出的那一段、方式、**留在人手上的那一半** |
|
||||||
|
| 條件式交 | `cond` | 條件講清楚,連同條件不成立時的行為 |
|
||||||
|
| 不交 | `none` | 不交的理由。不交也要留一列,否則下次分不出「判過決定不交」與「還沒判」 |
|
||||||
|
|
||||||
|
「留在人手上的那一半」是切片交的必填欄位。少了它,助理下一輪會把整支技能當成可交的一路跑完。
|
||||||
|
|
||||||
|
## 每種結論的必填欄位
|
||||||
|
|
||||||
|
`-` 代表這一格填一個減號,不是留白。TSV 沒有空格這種值,留白會被檢核判成缺欄位。
|
||||||
|
|
||||||
|
| 欄位 | 全交 | 切片交 | 條件式交 | 不交 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `way` | 必填 | 必填 | 必填 | `-` |
|
||||||
|
| `slice` | `-` | 必填 | 必填,條件寫在這裡 | `-` |
|
||||||
|
| `human` | `-` | 必填 | 必填 | 必填,寫不交的理由 |
|
||||||
|
| `trigger` | 必填 | 必填 | 必填 | `-` |
|
||||||
|
| `recur` | 必填 | 必填 | 必填 | `-` |
|
||||||
|
| `next` | 必填 | 必填 | 必填 | 必填 |
|
||||||
|
| `version` | 必填 | 必填 | 必填 | 必填 |
|
||||||
|
| `origin` | 必填 | 必填 | 必填 | 必填 |
|
||||||
|
| `probe` | `-` | `way` 不含 `invoke` 才必填 | 同左 | `-` |
|
||||||
|
|
||||||
|
`trigger` 與 `recur` 是兩個獨立欄位,四種組合都成立,登錄時不可以壓成兩種。
|
||||||
|
|
||||||
|
| 欄位 | 可填值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `trigger` | `at:{ISO 時間}`,立刻要做寫 `at:now`;或 `after:{事件名}` |
|
||||||
|
| `recur` | `once`、`every:{間隔}`、`cron:{式子}` |
|
||||||
|
| `probe` | 一行指令、`pending:{理由}`,或 `-` |
|
||||||
|
|
||||||
|
事件名只認固定詞彙表:`worklog-written`、`wp-merged`、`stage-entered`、`analyze-completed`、`hook-error`、`session-start`、`session-end`。填一個永遠不會發生的事件名,那一筆就永遠不到期,而且看不出壞在哪。
|
||||||
|
|
||||||
|
## 唯讀指令欄怎麼填
|
||||||
|
|
||||||
|
`probe` 記的是「那一段唯讀盤點實際要跑什麼」。助理拿它當待辦簿上的動作,所以它是一個指令,不是說明文字。
|
||||||
|
|
||||||
|
| 寫法 | 什麼時候用 |
|
||||||
|
| --- | --- |
|
||||||
|
| 一行指令 | 那一段有現成的唯讀入口,跑起來一個字都不改 |
|
||||||
|
| `pending:{理由}` | 那一段切得出唯讀盤點,入口還沒接上。冒號後面寫不接的理由,不可以留白 |
|
||||||
|
| `-` | 這一列沒有唯讀盤點入口 |
|
||||||
|
|
||||||
|
指令的路徑一律寫成 `{root}/jsc-{domain}/…` 開頭。三個代入點由助理代入,別的大括號一律算填錯:
|
||||||
|
|
||||||
|
| 代入點 | 代入什麼 |
|
||||||
|
| --- | --- |
|
||||||
|
| `{root}` | 種入那一支拿到的字面絕對根目錄 |
|
||||||
|
| `{cli}` | 助理偵測到的 CLI 代號,一支跑一次 |
|
||||||
|
| `{repo}` | 助理掃到的存取庫工作目錄,一個跑一次 |
|
||||||
|
|
||||||
|
指令裡不可以出現金錢符號或波浪號:那兩種寫法在無人值守那一輪解不出來,也進不了允許清單,會被靜靜擋掉。要帶環境變數就寫在指令最前面。
|
||||||
|
|
||||||
|
**唯讀旗標帶不帶,逐支確認,不要照抄別列。** 有些腳本靠環境變數才進唯讀模式,例如接線那一支設了 `JSC_READONLY=1` 會讓它的破壞性子命令回結束碼 6;那一列就要把旗標寫進指令。沒有唯讀模式的腳本不要憑空補一個變數名,補了不會生效,卻讓下一個人以為有護欄。巡檢那一輪是無人值守的,帶錯旗標會讓唯讀盤點變成實際動手。
|
||||||
|
|
||||||
|
**交出方式含觸發的列一律填 `-`。** 觸發的意思是呼叫整支技能,內容照那支技能自己的流程走。這一欄填了指令,助理會改拿指令當動作,於是整支交出變成只跑一支腳本,那支技能該寫的頁一頁都不會寫,而且看起來完全正常。
|
||||||
|
|
||||||
|
**要連網的先寫 `pending`。** 連網要金鑰,金鑰一過期就讓那一項每輪失敗,或每輪靜靜回報沒事——後者更難查。純本機讀取本來一輪都不會失敗。先填會連網的那一種,等於用一批每輪報錯的項目把真的發現蓋掉。等入口與金鑰都有著落再換成指令。
|
||||||
|
|
||||||
|
檢核怎麼看:`pending` 只印成待接線提示,不算缺失,判準同 `origin=seed`——它是「判過、知道還沒接」,不是漏填。填錯欄位、指到不存在的腳本、用了認不得的代入點,都算缺失。
|
||||||
|
|
||||||
|
## 判定結果放哪裡
|
||||||
|
|
||||||
|
真實來源是 [`tools/delegate-spec.tsv`](../tools/delegate-spec.tsv),一支技能一列。作法沿用 `jsc-cli/tools/config-spec.tsv` 那套:TSV 加一支檢核腳本,能被腳本比對,不靠人讀。
|
||||||
|
|
||||||
|
判定結果**不寫進 `SKILL.md` 的 frontmatter**。`name` 與 `description` 之外的自訂欄位有被各 CLI 的技能驗證擋掉的風險。
|
||||||
|
|
||||||
|
「過舊」的判準是比 plugin 版本號:清單上記的版本號落後該 domain 現行版本,就是要複判。版本號是 domain 層級,所以同 domain 改一支技能,其餘技能也會被標成要複判——**這只是提示,不算稽核缺失**。當成缺失的話,每次發版整個 domain 都亮紅,提示很快就被當雜訊忽略。
|
||||||
|
|
||||||
|
`origin` 欄記這一列怎麼來的:
|
||||||
|
|
||||||
|
| 值 | 意思 | 檢核怎麼看 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `seed` | 依既有盤點種入,還沒正式走過決策樹 | 印成待複核提示,不算缺失 |
|
||||||
|
| `judged` | 正式走過決策樹判定 | 不印 |
|
||||||
|
|
||||||
|
種入的那一批先掛 `seed`。之後那支技能有異動時就地走一次決策樹,把它轉成 `judged`。
|
||||||
|
|
||||||
|
## 各種異動要做的事
|
||||||
|
|
||||||
|
| 異動 | 技能 | 要做的事 | 沒做的後果 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 新增技能 | `skill-new` | 決策樹五題、`next` 欄那一題,再加 `probe` 欄那一題,全部照 `jsc-ask:ask` 問過,產出判定結果並寫進清單。**沒有判定結果不算建立完成** | 清單缺列,助理永遠不知道這支技能存在,也建議不到它 |
|
||||||
|
| 修改技能 | `skill-update` | 動到流程或 `description` 就重判,`probe` 一併重確認——技能換了呼叫的腳本,那一欄就指到不存在的東西;只改文案不動行為可沿用舊結論,但要更新清單上的版本號並註明沿用 | 技能從唯讀變成會寫檔,助理還照舊觸發它 |
|
||||||
|
| 刪除技能 | `skill-delete` | 刪掉清單那一列、把指到它的 `next` 改指別支,並檢查有沒有別列的 `probe` 指到這次一起刪掉的腳本;另移除待辦簿裡引用它的內建項 | 助理會去觸發一支不存在的技能,而且失敗不會自動暫停,會一路重試 |
|
||||||
|
| 一次改多支 | `skillset-update` | 逐支重判,一支都不能跳 | 同上,而且範圍更大 |
|
||||||
|
| 例行稽核 | `skill-check` | 跑 `tools/check-delegate.sh`,檢查清單與實際技能一一對應。缺列、多列與 `probe` 填錯算缺失;版本號落後、`seed` 與 `probe` 的 `pending` 只列成提示 | 清單慢慢與實際脫節,回到手工盤點會過期的老問題 |
|
||||||
|
|
||||||
|
## 助理要跟著更新
|
||||||
|
|
||||||
|
清單改動之後,助理的內建定期檢查項要重建一次。
|
||||||
|
|
||||||
|
- 清單新增可交項目,待辦簿加一筆,來源標成助理內建。
|
||||||
|
- 清單移除或改成不交,待辦簿移除對應那筆,不留孤兒。
|
||||||
|
- 使用者自己交辦的項目一律不動。助理不會因為一支技能改判就把使用者交辦的事刪掉。
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# 部署與驗證
|
||||||
|
|
||||||
|
`skill-new`、`skill-update`、`skill-delete`、`skillset-update` 四支異動技能的收尾共用這份流程。四支只在 SKILL.md 留一行指標指過來,不各自抄一份——抄四份會各自漂移,改一次要記得改四個地方。
|
||||||
|
|
||||||
|
## 1. 判路線
|
||||||
|
|
||||||
|
改動有沒有進存取庫的**預設分支**,決定走哪條路線。marketplace 與 `version-guard.sh` 都讀預設分支,停在 `develop` 的改動 `jsc-cli:deploy` 看不到。
|
||||||
|
|
||||||
|
判定交給 `jsc-meta/tools/deploy-route.sh {domain-path}`,不要自己用 `git log` 目測。多個 domain 就每個各跑一次,可以並行。
|
||||||
|
|
||||||
|
| 結束碼 | 意思 | 怎麼辦 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 0 | 改動已在預設分支上 | 走第 2 節的部署路線 |
|
||||||
|
| 3 | 改動還沒併進預設分支 | 走第 3 節的工作樹路線 |
|
||||||
|
| 2 | 用法錯誤 | 修參數重跑 |
|
||||||
|
| 1 | 判不出來:不是 git 存取庫、沒有 origin、fetch 失敗,或取不到預設分支 | 停下回報 stderr 的原因。**1 不等於工作樹路線**,判不出來就問使用者,不要自己挑一條走 |
|
||||||
|
|
||||||
|
完成條件:每個受影響的 domain 存取庫都有一個路線判定,且判定來自腳本輸出的 `route` 欄位。
|
||||||
|
|
||||||
|
## 2. 部署路線(結束碼 0)
|
||||||
|
|
||||||
|
1. 叫用 `jsc-cli:deploy` 的更新模式,讓每支已安裝的 CLI 都載入新版。deploy 收尾會寫 `$JSC_HOME/restart-required.d/{cli}`,一支 CLI 一份;重啟該支 CLI 之後由 `jsc-hooks` 清掉自己那一份。閘門規則見 [`guidelines.md`](guidelines.md) 的「部署後重啟閘門」。
|
||||||
|
2. deploy 更新不了某些 CLI 時,記下哪幾支確實載入了新版,改用其中一支繼續。一支都沒載入就停下,把這次異動回報為未驗證。
|
||||||
|
|
||||||
|
完成條件:`claude plugin list`(或別支已安裝 CLI 的等效指令)印出的 `jsc-{domain}` 版本,等於三份 manifest 現在的版本。
|
||||||
|
|
||||||
|
## 3. 工作樹路線(結束碼 3)
|
||||||
|
|
||||||
|
改動還沒進預設分支,部署路線的完成條件永遠達不到,所以改對**工作樹**驗證:
|
||||||
|
|
||||||
|
1. 第 4 節的驗證對象改成 `{root}/{domain}` 工作樹,不是已安裝的副本。
|
||||||
|
2. 回報標注「工作樹驗證、尚未部署」。
|
||||||
|
3. 點名還沒合併的釋出 PR——改動要等它合進預設分支才會到任何 CLI。跨多個存取庫的異動要等最後一支合併,所以全部列出來。刪除技能還要補一句:PR 合併前技能仍然裝著,指令仍然叫得動。
|
||||||
|
|
||||||
|
完成條件:第 4 節的驗證在每個工作樹上都通過,且回報裡列出每一支待合的釋出 PR。
|
||||||
|
|
||||||
|
## 4. 功能驗證:一定要換一個工作階段
|
||||||
|
|
||||||
|
**部署會把閘門立在自己腳下。** `jsc-cli:deploy` 收尾寫下重啟閘門的狀態檔,緊接著在**同一個工作階段**叫用剛做好的技能,那支技能不在豁免清單上就必被擋;連修復路徑 `jsc-cli:doctor` 與 `jsc-cli:setup` 也一起被擋。
|
||||||
|
|
||||||
|
解法不是把它們加進豁免清單。豁免只擋得住閘門,擋不住「目前行程還載著舊版」這個事實——豁免過關的驗證,驗到的是舊版行為,等於假通過。
|
||||||
|
|
||||||
|
**正解:驗證一律在新的 CLI 行程裡跑。** 用 `jsc-cli/tools/detect-clis.sh` 取得已安裝 CLI 的執行檔路徑,對每支支援非互動 Prompt 的 CLI,另外開一個行程送出最小 Prompt。新行程是新的工作階段,`jsc-hooks` 開場就清掉那支 CLI 自己的狀態檔,載入的也是磁碟上的新版。
|
||||||
|
|
||||||
|
四支技能都不得在部署的那個工作階段內叫用剛異動的技能。
|
||||||
|
|
||||||
|
驗證項目:
|
||||||
|
|
||||||
|
1. 跑 `jsc-meta/tools/list-skills.sh`,確認該技能的列符合這次異動:新增看得到 `{domain}<TAB>{name}` 那一列;更新看得到新的 `description`;刪除看不到那一列。
|
||||||
|
2. 這次異動碰過的每一支工具,都用真實參數跑一次,把實際結束碼對照工具檔頭宣告的意思。
|
||||||
|
3. 每支可測的 CLI 各開一個新行程,送出一個最小 Prompt 叫用 `/jsc-{domain}:{name}`,記錄 CLI 結束碼與 stderr。新增與更新要確認載入的是異動後的 SKILL.md 內文,不是「未知指令」;刪除要確認指令已消失,或替代路徑仍可用。各支 CLI 可以並行送。
|
||||||
|
|
||||||
|
完成條件:上列三項各有結果;每支可測 CLI 的 Prompt 都沒有非預期 stderr;不可測的 CLI 逐支寫明原因。
|
||||||
|
|
||||||
|
## 5. 驗證失敗怎麼辦
|
||||||
|
|
||||||
|
Prompt 因為 CLI 工具、plugin 安裝、hook 接線、模型標籤表或設定而失敗時:
|
||||||
|
|
||||||
|
1. 先跑 `jsc-cli:doctor`,再用 `jsc-cli:setup` 修待修項目,然後在新行程重跑同一個 Prompt。
|
||||||
|
2. hook 冒煙測試失敗,交給 `jsc-hooks:hooks-install`,由它把 hook 接線或執行期錯誤轉給 `jsc-hooks:repair`。
|
||||||
|
3. 根因若在技能組本身的規格、工具或 hook 實作,就修對應 domain,跑 `jsc-meta/tools/sync-skill-manifest.sh {domain-path}` 同步與驗證,再用 `jsc-git:pr` 對 `develop` 開 PR;PR 送出後回到第 1 節重判路線。
|
||||||
|
|
||||||
|
任一項對不上——列不見、結束碼不在工具文件裡、指令載不到、Prompt 失敗、非預期 stderr——就修掉成因,回到第 1 節重跑整段。
|
||||||
|
|
||||||
|
完成條件:每一項不符都有對應的修正動作與重跑結果,沒有留下「已知失敗但照樣收尾」的項目。
|
||||||
+415
-27
@@ -9,15 +9,45 @@
|
|||||||
3. Skill 名稱:小寫、數字、連字號(`-`),最長 64 字元。名稱即指令(`/jsc-{domain}:{name}`)。
|
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 存取庫**(含新存取庫自己)。
|
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 化。
|
5. Git 分支名**只允許 ASCII**(`a-z0-9` 與 `/`、`-`);中文需求或標題先翻譯成英文短語再 slug 化。
|
||||||
6. Manifest 版本號從 `0.0.1` 開始,三份 manifest 同步 bump。
|
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 規則
|
## Description 規則
|
||||||
|
|
||||||
1. frontmatter 的 `description` 為一行英文,不超過 5 句或 5 個步驟。
|
1. frontmatter 的 `description` 為一行英文,不超過 5 句或 5 個步驟——**兩個上限滿足任一個就算通過**,句數與步驟數都超過才要精簡。
|
||||||
2. 使用專有名詞、概念或指引詞(例:WBS、TDD、decision tree、STE100)取代解釋。
|
2. 使用專有名詞、概念或指引詞(例:WBS、TDD、decision tree、STE100)取代解釋。
|
||||||
3. 必須寫清楚觸發時機(何時用、何時不用),這是各 CLI 自動載入的唯一依據。
|
3. 必須寫清楚觸發時機(何時用、何時不用),這是各 CLI 自動載入的唯一依據。
|
||||||
4. 複雜流程透過**組合其他技能**實現,不在單一 description 裡塞流程。
|
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 無法涵蓋的指引。
|
1. 規則的實現優先順序:**hook > prompt**。凡是可以由 hook 強制的規則(語言、計時、統計),一律下放 hook,SKILL.md 只保留 hook 無法涵蓋的指引。
|
||||||
@@ -29,8 +59,28 @@
|
|||||||
1. 所有 hook 專屬存放於 `jsc-hooks`,**不可散落在其他 domain**。
|
1. 所有 hook 專屬存放於 `jsc-hooks`,**不可散落在其他 domain**。
|
||||||
2. Hook 腳本實作優先順序:**shell > nodejs > python**。
|
2. Hook 腳本實作優先順序:**shell > nodejs > python**。
|
||||||
3. Hook 必須適用於 claude / codex / copilot / antigravity / kiro 五種 CLI:
|
3. Hook 必須適用於 claude / codex / copilot / antigravity / kiro 五種 CLI:
|
||||||
- 腳本同時支援 stdin JSON(Claude 格式)與環境變數輸入,缺欄位時安靜降級(exit 0)。
|
- 五支 CLI 的 hook 負載形態各不相同,沒有哪一支是基準格式。腳本吃 stdin JSON,也吃環境變數,缺欄位時安靜降級(exit 0)。
|
||||||
- 各 CLI 的接線方式由 `jsc-hooks:hooks-install` 技能處理。
|
- 各 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 的結束碼語意兩邊文件都沒寫,擋不擋得住純靠運氣。
|
||||||
|
|
||||||
## 技能設計
|
## 技能設計
|
||||||
|
|
||||||
@@ -40,7 +90,7 @@
|
|||||||
|
|
||||||
## 語言
|
## 語言
|
||||||
|
|
||||||
1. 所有交談與輸出內容使用 STE100 繁體中文,帶擬人台灣感:短句、一句一指令、主動語態、術語一致、台灣用語、全形標點、去 AI 味、直接講重點。完整規則的唯一來源:[`references/ste100.md`](ste100.md)。
|
1. **所有非程式碼輸出**一律 STE100 繁體中文、UTF-8、無亂碼、無簡體字,帶擬人台灣感:短句、一句一指令、主動語態、術語一致、台灣用語、全形標點、去 AI 味、直接講重點。範圍涵蓋程式碼註解、commit 訊息、PR 描述、wiki 頁、對使用者的回報、README 與各種文件;程式碼本身(識別字、關鍵字、API 名稱)不受此規則限制。完整適用範圍表、編碼要求與替換表的唯一來源:[`references/ste100.md`](ste100.md)。
|
||||||
2. **技能文件(SKILL.md)整份為英文**:frontmatter 與內文都是。風格比照 STE100:短句、祈使句、術語一致。
|
2. **技能文件(SKILL.md)整份為英文**:frontmatter 與內文都是。風格比照 STE100:短句、祈使句、術語一致。
|
||||||
3. 技能文件裡「要原樣輸出的繁中字面內容」保留繁中:wiki 狀態字串(例:已分析、未完成)、hook 注入文字、要寫進 wiki 或 commit 的文案。技能執行時產生的 commit 訊息、PR 描述、wiki 頁內容仍依第 1 條輸出繁中。
|
3. 技能文件裡「要原樣輸出的繁中字面內容」保留繁中:wiki 狀態字串(例:已分析、未完成)、hook 注入文字、要寫進 wiki 或 commit 的文案。技能執行時產生的 commit 訊息、PR 描述、wiki 頁內容仍依第 1 條輸出繁中。
|
||||||
4. README、AGENTS.md、`templates/`、`references/` 為 STE100 繁體中文。中文並列項用頓號「、」,不用半形「/」;英文項目的並列(CLI 名、頁名前綴)可用「/」。
|
4. README、AGENTS.md、`templates/`、`references/` 為 STE100 繁體中文。中文並列項用頓號「、」,不用半形「/」;英文項目的並列(CLI 名、頁名前綴)可用「/」。
|
||||||
@@ -53,37 +103,148 @@
|
|||||||
4. **漸進揭露**:所有分支都需要的內容留在 SKILL.md;只有部分分支需要的參考資料下放 `references/`,以一行指引指過去。
|
4. **漸進揭露**:所有分支都需要的內容留在 SKILL.md;只有部分分支需要的參考資料下放 `references/`,以一行指引指過去。
|
||||||
5. 善用**引導詞**(WBS、CPM、TDD、seam、STE100 等既有概念)取代整段解釋。
|
5. 善用**引導詞**(WBS、CPM、TDD、seam、STE100 等既有概念)取代整段解釋。
|
||||||
|
|
||||||
|
## 技能行為清單
|
||||||
|
|
||||||
|
每個 domain 都要有一份技能行為清單,記下每支技能實際做的事,供稽核與驗證比對。
|
||||||
|
|
||||||
|
| 項目 | 規則 |
|
||||||
|
| --- | --- |
|
||||||
|
| 位置 | 每個 domain 存取庫的 `references/behaviors.md`,UTF-8 無 BOM,內容為 STE100 繁體中文 |
|
||||||
|
| 第一行 | `# jsc-{domain} 技能行為清單` |
|
||||||
|
| 節 | 每支技能一個 `## {技能名}` 節,名稱與 `skills/` 底下的目錄名逐字相同,節數與技能支數一樣,排列照目錄名的字典序 |
|
||||||
|
| 表格 | 每節恰好一張表,表頭兩欄依序是「項目」與「內容」,五列依序為 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,每一列的「內容」欄都不得空白 |
|
||||||
|
| 寫什麼 | 寫技能實際的行為:什麼情況會用、什麼情況不該用、依序做了哪些事、呼叫哪些腳本與技能、做到什麼程度算跑完、跑完在環境裡留下哪些查得到的跡象。不要抄 `description` 的行銷語 |
|
||||||
|
| 純唯讀的技能 | 「可驗證跡象」欄寫「除了收尾的 `skill-end` 事件以外沒有寫入跡象,只有回報內容」,不得留白。收尾事件是每支技能都有的那一筆,唯讀技能也不例外 |
|
||||||
|
| 更新時機 | 技能異動時在**同一個 PR 內**一起更新:新增技能就加一節、刪除就移除該節、改行為就改該節 |
|
||||||
|
| 收尾事件 | 「可驗證跡象」那一列要寫到收尾的 `skill-end` 事件或 `events.jsonl`,規則見「執行狀態回報」一節 |
|
||||||
|
| 檢查腳本 | `jsc-meta/tools/check-behaviors.sh {domain-path}` |
|
||||||
|
|
||||||
|
`check-behaviors.sh` 的結束碼分流:
|
||||||
|
|
||||||
|
| 結束碼 | 意義 |
|
||||||
|
| --- | --- |
|
||||||
|
| 0 | 行為清單與 `skills/` 相符,五個欄位齊全、內容欄非空,且每一節的「可驗證跡象」都寫了收尾的 `skill-end` 事件 |
|
||||||
|
| 1 | 不符:缺節、多節、順序不對、表格不對、缺欄位、欄位空白,或「可驗證跡象」沒寫到收尾的 `skill-end` 事件,逐項印在 stderr,照著修再重跑 |
|
||||||
|
| 2 | 用法錯誤:本腳本只吃一個參數 |
|
||||||
|
| 3 | 找不到 `references/behaviors.md`、找不到 `skills/`,或 `skills/` 底下一支 `SKILL.md` 都沒有。**什麼都沒查,不等於通過**,先補齊檔案再重跑 |
|
||||||
|
|
||||||
|
**為什麼一個 domain 一份,不集中在 `jsc-meta`。** 技能改動與行為清單放同一個存取庫,才進得了同一個 PR;審的人在一頁 diff 上就看得出行為改了、清單也改了。集中在 meta 的話,改一支技能要開兩條 PR,一條在 domain、一條在 meta,兩條互相等待,先併的那條讓清單與技能對不上,稽核抓到的是自己造出來的漂移。跨存取庫的東西沒有原子性,同一份事實就不要拆兩邊放。
|
||||||
|
|
||||||
|
## 執行狀態回報
|
||||||
|
|
||||||
|
技能與 hook 每跑一次都要在本機事件流留下結果,助理巡檢再排空、彙整、寫監控頁。
|
||||||
|
事件流是 `$JSC_HOME/usage/events.jsonl`,一次一行,只增不改。
|
||||||
|
|
||||||
|
| 項目 | 規則 |
|
||||||
|
| --- | --- |
|
||||||
|
| 誰寫 `start` | `jsc-hooks/hooks/skill-usage.sh`。技能被叫用的當下就寫,SKILL.md 一個字都不必改 |
|
||||||
|
| 誰寫 `end` | **技能自己在收尾步驟寫**,一次執行一筆 |
|
||||||
|
| 怎麼寫 | `{jsc-hooks 路徑}/tools/report-status.sh skill-end jsc-{domain}:{技能名} {status} {結束碼} [detail]` |
|
||||||
|
| 路徑怎麼解 | 沿用該技能原本呼叫別的 plugin 腳本的那一套,不另外發明一種 |
|
||||||
|
| 找不到腳本 | 安靜跳過,照常收尾。回報機制不在場,不可以讓被回報的技能跟著失敗 |
|
||||||
|
| 回報自己失敗 | 一樣吞掉。這支腳本的三個記錄子命令一律回 0,呼叫端不得因為它的結束碼改變自己的結局 |
|
||||||
|
| `detail` | 選填,單行,最多 200 字。長內容另存別處,不要塞進這一行 |
|
||||||
|
| 寫進行為清單 | 該技能在 `references/behaviors.md` 的「關鍵步驟」「完成條件」「可驗證跡象」三列都要提到這一筆事件 |
|
||||||
|
| 檢查腳本 | `jsc-meta/tools/check-behaviors.sh {domain-path}` 斷言「可驗證跡象」那一列寫到 `skill-end` 或 `events.jsonl` |
|
||||||
|
|
||||||
|
`status` 五選一,SKILL.md 要逐項寫清楚這支技能什麼情況選哪一個:
|
||||||
|
|
||||||
|
| status | 什麼時候用 |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | 完成條件全部達成 |
|
||||||
|
| `blocked` | 被閘門或前置條件擋下,沒有做事。例如版本前置檢查擋下、相依 PR 未合併 |
|
||||||
|
| `failed` | 做到一半失敗。例如 API 回非預期狀態、寫入失敗 |
|
||||||
|
| `degraded` | 做完了但有部分沒達成。例如內容頁寫成功、目錄頁沒更新 |
|
||||||
|
| `aborted` | 使用者中止,或前提不成立而主動停止 |
|
||||||
|
|
||||||
|
**`end` 為什麼不能由 hook 代勞。** hook 接在技能工具呼叫之後就觸發,那一刻技能的實際工作
|
||||||
|
還在後面的模型輪次,成敗根本還沒發生。hook 在原理上看不到結果,寫得出來的只有「開始跑了」。
|
||||||
|
所以 `start` 是免費的,`end` 躲不掉要由技能自己寫。
|
||||||
|
|
||||||
|
**有 `start` 沒有配對的 `end`,就是中止。** 這正是這條規則要補的洞:現行紀錄只記「被叫用」,
|
||||||
|
跑完整輪的技能與開場就停的技能長得一模一樣。收尾少寫這一筆,那支技能每一次都會被算成中止,
|
||||||
|
而且不會有任何錯誤訊息——助理讀到的是一串沒有結局的技能,看起來像整組技能都在半路死掉。
|
||||||
|
|
||||||
## 環境變數
|
## 環境變數
|
||||||
|
|
||||||
| 變數 | 用途 | 未設定時 |
|
| 變數 | 用途 | 未設定時 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `GITEA_HOST` | Gitea 站台(例:`https://gitea.jsc.idv.tw`) | 詢問使用者 |
|
| `GITEA_HOST` | Gitea 站台(例:`https://gitea.jsc.idv.tw`) | 詢問使用者 |
|
||||||
| `GITEA_TOKEN` | Gitea API token | 改用 tea 登入金鑰(`tea login list`);tea 也沒有才詢問使用者 |
|
| `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_CONTENTS` | **全部** `*_CONTENTS` 目錄頁所在的 `{owner}/{repo}`,十四種型別的目錄頁共用這一組 | 退回 `JSC_WIKI_REPO`;再沒有就 exit 3。**刻意不退回型別變數** |
|
||||||
| `JSC_WIKI_REPO_PLAN` | `PLAN_CONTENTS`、`PLAN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_QUESTION` | `QUESTION_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_ANALYZE` | `ANALYZE_CONTENTS`、`ANALYZE_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_PLAN` | `PLAN_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_DELIVER` | `DELIVER_CONTENTS`、`DELIVER_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_ANALYZE` | `ANALYZE_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_MAINTAIN` | `MAINTAIN_CONTENTS`、`MAINTAIN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_DELIVER` | `DELIVER_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_REPO` | `REPO_CONTENTS`、`REPO_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_MAINTAIN` | 保留給 `MAINTAIN` 的內容頁。本類型目前只有目錄頁,目錄頁走 `CONTENTS`,所以這個變數現在解不到任何一頁 | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_LOG` | `LOG_CONTENTS`、`LOG_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_REPO` | `REPO_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_LEARN` | `LEARN_CONTENTS`、`LEARN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_LOG` | `LOG_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO_ERROR` | `ERROR_CONTENTS`、`ERROR_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
| `JSC_WIKI_REPO_LEARN` | `LEARN_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
|
| `JSC_WIKI_REPO_ERROR` | `ERROR_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
|
| `JSC_WIKI_REPO_CHECK` | `CHECK_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
|
| `JSC_WIKI_REPO_REPORT` | `REPORT_{HASH}` 內容頁所在的 `{owner}/{repo}`,也是 `REPORT` 雜湊來源的取值處 | 退回 `JSC_WIKI_REPO` |
|
||||||
|
| `JSC_WIKI_REPO_SKILLSET` | `SKILLSET_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
|
| `JSC_WIKI_REPO_TOOLING` | `TOOLING_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
|
| `JSC_WIKI_REPO_MONITOR` | `MONITOR_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
|
||||||
| `JSC_WIKI_REPO` | 未逐類設定時的共用 wiki `{owner}/{repo}` | 詢問使用者 |
|
| `JSC_WIKI_REPO` | 未逐類設定時的共用 wiki `{owner}/{repo}` | 詢問使用者 |
|
||||||
| `JSC_HOME` | Hook 資料目錄 | 預設 `~/.jsc` |
|
| `JSC_HOME` | Hook 資料目錄 | 預設 `~/.jsc` |
|
||||||
|
| `JSC_PR_WATCH_INTERVAL` | `jsc-gitea/tools/pr-watch.sh` 輪詢 PR 狀態的間隔秒數 | 預設 60 |
|
||||||
|
| `JSC_RESTART_GATE` | 部署後重啟閘門的開關,`off` 關閉整道閘門 | 閘門開啟 |
|
||||||
|
| `JSC_ASSISTANT_GATE` | 助理運行閘門的開關,`off` 關閉整道閘門 | 閘門開啟 |
|
||||||
|
| `JSC_ASSISTANT_HEARTBEAT_TTL` | 助理心跳的過期門檻秒數。排程週期由這個值推導 | 預設 300;壞值退回預設 |
|
||||||
|
|
||||||
頁面類型只讀自己的 `JSC_WIKI_REPO_{TYPE}`。只有該變數未設定時,才退回 `JSC_WIKI_REPO`。不得跨類型代用。
|
解析規則分兩條,都由 `jsc-gitea/tools/gitea.sh wiki-repo {TYPE}` 執行:
|
||||||
|
|
||||||
|
1. **內容頁**只讀自己的 `JSC_WIKI_REPO_{TYPE}`,只有該變數未設定時才退回 `JSC_WIKI_REPO`,再沒有就 exit 3。
|
||||||
|
2. **目錄頁**一律解 `CONTENTS`,鏈是 `JSC_WIKI_REPO_CONTENTS` → `JSC_WIKI_REPO` → exit 3。中間刻意不插型別變數:目錄頁全部落在同一個存取庫才找得齊,插了型別變數就等於十四個目錄頁散在十四處。
|
||||||
|
|
||||||
|
型別共十五種:十四種內容型別加上 `CONTENTS`。**十五種都不得跨類型代用**——拿 `JSC_WIKI_REPO_PLAN` 去寫 `ANALYZE_{HASH}` 不行,拿 `JSC_WIKI_REPO_LOG` 去寫 `LOG_CONTENTS` 也不行,後者要走 `JSC_WIKI_REPO_CONTENTS`。
|
||||||
|
|
||||||
## 版本前置檢查
|
## 版本前置檢查
|
||||||
|
|
||||||
技能組的每一支技能在被呼叫前都要確認本機版本沒有落後遠端發佈版本。判定在程式層,由 `jsc-hooks` 的 `version-guard.sh`(PreToolUse,matcher `Skill`)執行,**不靠技能內文自我約束**——寫在內文的規則,模型可以無視。
|
技能組的每一支技能在被呼叫前都要過兩道版本檢查:本機載入版本沒有落後遠端發佈版本,以及這支技能所屬 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 的限制,不是我們接錯。
|
||||||
|
|
||||||
| 項目 | 規則 |
|
| 項目 | 規則 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 比對對象 | 遠端發佈版本(`master` 的 `plugin.json`)對本機**實際載入**版本 |
|
| 比對對象 | 遠端發佈版本(存取庫**預設分支**的 `plugin.json`,經 `jsc-gitea/tools/gitea.sh` 讀取,不寫死分支名)對本機**實際載入**版本 |
|
||||||
| 實際載入版本 | 讀 `installed_plugins.json` 的 `installPath` 底下那份 `plugin.json`,**不可只看註冊欄位**——兩者可能不同,只看註冊值會放過真正被載入的舊版 |
|
| 實際載入版本 | 只認 `installed_plugins.json` 的 `installPath` 底下那份 `plugin.json`。註冊在 `installed_plugins.json` 的 `version` 欄位**不當備援**——註冊值可能比實際載入的版本新,拿它來比對會放過真正被載入的舊版 |
|
||||||
| 落後 | 擋下該次技能呼叫(exit 2),並印出更新指令 |
|
| 落後 | 擋下該次技能呼叫(exit 2),並印出更新指令 |
|
||||||
| 相等或超前 | 放行。開發技能組時本機本來就會超前 `master`,擋下去維護者自己動不了 |
|
| 相等或超前 | 放行。開發技能組時本機本來就會超前預設分支,擋下去維護者自己動不了 |
|
||||||
| 查不到遠端版本 | **擋**(fail-closed)。查詢失敗會重試一次,仍失敗才擋 |
|
| 查不到本機載入版本 | **放行**(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_VERSION_GUARD=off`(離線工作用),快取秒數 `JSC_VERSION_TTL`(預設 600) |
|
||||||
|
|
||||||
**豁免清單**(永遠放行,改動前想清楚後果):
|
**豁免清單**(永遠放行,改動前想清楚後果):
|
||||||
@@ -92,14 +253,132 @@
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `jsc-cli:deploy` | 更新整組技能的入口。擋了就沒有任何方法更新,形成死鎖 |
|
| `jsc-cli:deploy` | 更新整組技能的入口。擋了就沒有任何方法更新,形成死鎖 |
|
||||||
| `jsc-hooks:hooks-install` | 更新後要重新接線,擋了會讓更新做一半卡住 |
|
| `jsc-hooks:hooks-install` | 更新後要重新接線,擋了會讓更新做一半卡住 |
|
||||||
|
| `jsc-hooks:repair` | hook 壞掉時的唯一修復路徑。擋了就修不好 hook |
|
||||||
| `jsc-cli:models` | SDLC 階段閘門依賴它產生 `model-tags.tsv` |
|
| `jsc-cli:models` | SDLC 階段閘門依賴它產生 `model-tags.tsv` |
|
||||||
| `jsc-meta:*` | 開發技能組本身的工具,擋了就修不了技能組 |
|
| `jsc-meta:*` | 開發技能組本身的工具,擋了就修不了技能組 |
|
||||||
|
| `jsc-ask:ask` | 上面幾支都要問使用者 |
|
||||||
|
| `jsc-gitea:wiki` | 上面幾支的收尾要寫 wiki |
|
||||||
|
|
||||||
沒有 pre-tool hook 的 CLI 接不上這道檢查,`hooks-install` 要據實回報,不得暗示每個 CLI 都有保護。
|
共 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`](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`。新增豁免技能時要一併想它會呼叫誰。
|
||||||
|
|
||||||
|
## 助理運行閘門
|
||||||
|
|
||||||
|
技能與 hook 每跑一次就留下事件,助理負責把事件收攏、判斷健康狀態、寫進監控頁。助理沒在跑的時候,技能會以為背景有人收尾,實際上沒有。這道閘門把那個落差擋在門外。
|
||||||
|
|
||||||
|
| 項目 | 規則 |
|
||||||
|
| --- | --- |
|
||||||
|
| 狀態檔 | `$JSC_HOME/assistant/heartbeat`,欄位 `ts`、`pid`、`cli`、`session` |
|
||||||
|
| 誰寫心跳 | `jsc-assist:assistant` 的巡檢**跑完那一輪**才寫。**不是**由系統排程直接寫 |
|
||||||
|
| 判定位置 | 程式層 `jsc-hooks/hooks/assistant-gate.sh`,不靠技能內文自我約束 |
|
||||||
|
| 接線位置 | `PreToolUse`,matcher=Skill。能力事實比照「版本前置檢查」那張表,不另寫一份 |
|
||||||
|
| 放行條件 | 心跳新鮮;或技能名不是 `jsc-{domain}:{name}`;或取不到技能名 |
|
||||||
|
| 擋下條件 | 心跳不存在、已過期,或 `ts` 讀不出來 |
|
||||||
|
| 新鮮的判準 | 檔案存在,且 `ts` 距現在小於門檻。門檻預設 300 秒,`JSC_ASSISTANT_HEARTBEAT_TTL` 可覆寫。**不看 pid 存活**——五支 CLI 與容器裡的行程互相看不到彼此的 pid |
|
||||||
|
| 逃生門 | `JSC_ASSISTANT_GATE=off` |
|
||||||
|
|
||||||
|
**心跳為什麼由巡檢寫,不由排程寫。** 排程直接寫的話,心跳新鮮只證明排程活著。巡檢整個壞掉、每輪都失敗,心跳照樣新鮮,閘門照樣放行,而且沒有任何錯誤訊息。改成巡檢收尾才寫,心跳新鮮才等於上一輪真的跑完了,閘門判的才是工作訊號。
|
||||||
|
|
||||||
|
**排程週期由門檻推導,不各寫死一個數字。** 門檻是讀取端的設定,心跳檔裡不存它,所以兩邊各寫一個數字一定會撞:門檻五分鐘、巡檢十五分鐘,心跳永遠是過期的。週期取「漏掉一輪還算新鮮、漏掉兩輪才過期」的最大值。要拉長巡檢週期就調大門檻。
|
||||||
|
|
||||||
|
**心跳只看這一輪有沒有把結果記下來,不看巡檢項目的成敗。** 項目有失敗但監控頁寫成了就寫心跳,頁上判定標警示;頁寫不成就中止,一定不寫。頁每輪都寫失敗卻照樣寫心跳,等於把上面那個無聲失效原封不動搬過去。
|
||||||
|
|
||||||
|
### `heartbeat.sh check` 的六碼處置
|
||||||
|
|
||||||
|
| 碼 | 意義 | 閘門的處置 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 0 | 新鮮 | 放行 |
|
||||||
|
| 1 | 過期 | **擋**。跑過、現在停了 |
|
||||||
|
| 2 | 腳本沒跑起來 | 放行。判定機制自己壞了,不是「助理沒在跑」的證據 |
|
||||||
|
| 3 | 不存在 | **擋**。從沒啟動過 |
|
||||||
|
| 4 | `ts` 讀不出來 | **擋**。確定沒有可信心跳,絕不可以退回當成新鮮 |
|
||||||
|
| 5 | 檔案系統失敗 | 放行。理由見下 |
|
||||||
|
| 6 | 用法錯誤 | 放行。閘門固定送 `check`,收到 6 是呼叫端的缺陷 |
|
||||||
|
|
||||||
|
**5 為什麼放行。** `check` 這條路徑本來就不產生 5,5 只由 `write` 與 `clear` 產出,所以從 `check` 收到 5 意思是判定機制壞了,與 2、6 同一類。更實際的理由是:5 正是磁碟滿或權限壞的訊號,而那一刻助理自己也寫不出心跳;擋下去等於整組技能鎖死,出路只剩豁免那幾支,可是它們同樣要寫 `$JSC_HOME`,環境壞著也修不動。磁碟壞掉要人去清磁碟,不是把技能組鎖起來。代價是那種時候閘門會安靜放行,由 `jsc-cli:doctor` 抓。
|
||||||
|
|
||||||
|
**4 與 5 的差別在有沒有出路。** 4 是「檔案在、內容壞」,那是確定沒有可信心跳的證據,而且修法就在豁免清單裡(先 `stop` 再 `start`),擋得起。5 是「檔案系統問不出來」,擋了沒有出路。
|
||||||
|
|
||||||
|
**擋人訊息要分三種話講。** 心跳不存在是從沒啟動過、過期是跑過停了、`ts` 壞掉是檔案要重建。三種情況使用者要做的事不一樣,訊息混成一種就等於沒講。四件事一件都不能少:上次心跳什麼時候、怎麼啟動助理、哪幾支技能仍可用、逃生門怎麼開。
|
||||||
|
|
||||||
|
### fail-closed 閘門的專屬規則
|
||||||
|
|
||||||
|
整組 hook 的通則是**資料不足就放行**。助理運行閘門是唯一的例外:沒心跳就是沒運行,照要求要擋。例外要在準則裡點名,不能讓後來的人以為可以隨便再開一道。
|
||||||
|
|
||||||
|
新增任何 fail-closed 閘門一律照這四條:
|
||||||
|
|
||||||
|
1. **逃生門與豁免清單是上線前提,不是選配。** 任一樣被拿掉或改窄,那道閘門就不可以接線。代價講白:狀態檔寫不進去時全組停擺。
|
||||||
|
2. **逃生門的判斷要擺在載入 `lib.sh` 之前。** `lib.sh` 讀不到時 sh 會就地結束並回擋人的那個碼,逃生門也跟著跑不到,人就繞不過去。fail-open 閘門沒有這個問題,這一條只對 fail-closed 成立。
|
||||||
|
3. **豁免清單只收解鎖路徑**,不收收尾規則。這一點與「部署後重啟閘門」的方向相反:那一道解鎖靠閘門外的動作(重新啟動),所以收尾規則要能寫得完;這一道解鎖靠跑一支技能,清單收寬了閘門就等於沒有。
|
||||||
|
4. **接線的前提是解鎖條件已經成立。** 助理還沒跑起來、心跳還沒穩定就接線,等於當場把整組技能擋死,只剩豁免那幾支。
|
||||||
|
|
||||||
|
助理運行閘門的豁免清單如下。這張表的唯一真實來源是 `jsc-hooks/hooks/assistant-gate.sh` 的檔頭與豁免清單,兩邊要逐項對齊:
|
||||||
|
|
||||||
|
| 技能 | 為什麼豁免 |
|
||||||
|
| --- | --- |
|
||||||
|
| `jsc-assist:*` | 啟動助理本身就是一次技能呼叫。少了這一條,助理永遠啟動不了,整組技能鎖死 |
|
||||||
|
| `jsc-hooks:repair` | 修 hook 的唯一路徑 |
|
||||||
|
| `jsc-hooks:hooks-install` | 重新接線的唯一路徑 |
|
||||||
|
| `jsc-cli:doctor` | 環境壞掉時的診斷入口,這道閘門放行的那幾種情況都靠它抓 |
|
||||||
|
| `jsc-cli:setup` | 修設定 |
|
||||||
|
| `jsc-cli:deploy` | 部署 |
|
||||||
|
| `jsc-cli:models` | `setup` 對「模型標籤檔不見」那一項的修法就是呼叫它 |
|
||||||
|
| `jsc-gitea:wiki` | 巡檢要先把結果寫上監控頁才寫心跳。理由見下 |
|
||||||
|
| `jsc-ask:ask` | 上面幾支都要問使用者 |
|
||||||
|
| `jsc-git:commit` | `repair` 的收尾要開 PR,`pr` 的第一步就是它 |
|
||||||
|
| `jsc-git:pr` | 同上 |
|
||||||
|
|
||||||
|
**自咬環:`jsc-gitea:wiki` 為什麼一定要收。** 巡檢跑完要先把結果寫進監控頁,寫不成就中止、不寫心跳。只豁免 `jsc-assist:*` 的話會變成「沒心跳 → 擋 wiki → 巡檢跑不完 → 還是沒心跳」,自己咬住自己,永遠解不開。
|
||||||
|
|
||||||
|
這比「清單認技能名,不是呼叫鏈」那一條更進一步:新增豁免技能時不只要想它會呼叫誰,還要想**那條呼叫鏈上有沒有一步是解鎖條件本身的前置**。是的話,那一步非收不可。
|
||||||
|
|
||||||
|
**刻意不收的那幾支**:`jsc-log:worklog`、`jsc-log:learn`、`jsc-meta:*`。它們是部署收尾規則的主體,與「把助理啟動起來」無關,不在解鎖路徑上。
|
||||||
|
|
||||||
## Wiki 頁命名總表
|
## Wiki 頁命名總表
|
||||||
|
|
||||||
所有 wiki 頁面一律採雙層命名:
|
所有 wiki 頁面一律採雙層命名。`MAINTAIN` 是唯一只有目錄頁的類型,理由見表下:
|
||||||
|
|
||||||
| 類型 | 目錄頁 | 內容頁 | 用途 | 擁有者 |
|
| 類型 | 目錄頁 | 內容頁 | 用途 | 擁有者 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
@@ -107,26 +386,135 @@
|
|||||||
| `PLAN` | `PLAN_CONTENTS` | `PLAN_{HASH}` | 計畫目錄、計畫頁 | jsc-sdlc |
|
| `PLAN` | `PLAN_CONTENTS` | `PLAN_{HASH}` | 計畫目錄、計畫頁 | jsc-sdlc |
|
||||||
| `ANALYZE` | `ANALYZE_CONTENTS` | `ANALYZE_{HASH}` | 分析目錄、分析頁 | jsc-sdlc |
|
| `ANALYZE` | `ANALYZE_CONTENTS` | `ANALYZE_{HASH}` | 分析目錄、分析頁 | jsc-sdlc |
|
||||||
| `DELIVER` | `DELIVER_CONTENTS` | `DELIVER_{HASH}` | 交付目錄、工作包交付文件 | jsc-sdlc |
|
| `DELIVER` | `DELIVER_CONTENTS` | `DELIVER_{HASH}` | 交付目錄、工作包交付文件 | jsc-sdlc |
|
||||||
| `MAINTAIN` | `MAINTAIN_CONTENTS` | `MAINTAIN_{HASH}` | 維護目錄、維護頁 | jsc-sdlc |
|
| `MAINTAIN` | `MAINTAIN_CONTENTS` | 無 | 維護登記目錄:登記進維護期的專案、維護方式、起訖日期與前次維護時間 | jsc-sdlc |
|
||||||
| `REPO` | `REPO_CONTENTS` | `REPO_{HASH}` | 盤點目錄、存取庫盤點頁 | jsc-sdlc |
|
| `REPO` | `REPO_CONTENTS` | `REPO_{HASH}` | 盤點目錄、存取庫盤點頁 | jsc-sdlc |
|
||||||
| `LOG` | `LOG_CONTENTS` | `LOG_{HASH}` | 日誌目錄、工作日誌頁 | jsc-log |
|
| `LOG` | `LOG_CONTENTS` | `LOG_{HASH}` | 日誌目錄、工作日誌頁 | jsc-log |
|
||||||
| `LEARN` | `LEARN_CONTENTS` | `LEARN_{HASH}` | 教訓目錄、技能教訓頁 | jsc-log |
|
| `LEARN` | `LEARN_CONTENTS` | `LEARN_{HASH}` | 教訓目錄、技能教訓頁 | jsc-log |
|
||||||
| `ERROR` | `ERROR_CONTENTS` | `ERROR_{HASH}` | 異常目錄、異常頁 | jsc-hooks |
|
| `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 |
|
||||||
|
|
||||||
`{HASH}` 一律為 `{owner}/{repo}`(必要時加上主題字串)的 SHA-1 前 8 碼,大寫。
|
`MAINTAIN` 沒有內容頁。維護登記全部寫在 `MAINTAIN_CONTENTS` 的條列區塊上,一個專案一個 H2 區塊:`jsc-sdlc:implement` 只往那一頁附加登記,`jsc-sdlc:maintain` 只讀那一頁再回寫「前次維護時間」,兩支都沒有產生 `MAINTAIN_{HASH}` 的步驟,`jsc-sdlc/templates/` 也沒有對應範本。總表以前列著這個內容頁,照著找只會找到一個不存在的頁。要補內容頁就先補技能步驟與範本,不能只在總表上寫著。
|
||||||
若第一碼是 `0-9`、`A`、`B`、`C`,就改成 `H` 加上原 SHA-1 前 7 碼,總長仍維持 8 碼。
|
|
||||||
同一規則套用到所有目錄頁與內容頁。
|
### 目錄頁專用存取庫
|
||||||
|
|
||||||
|
目錄頁與內容頁分屬不同存取庫,這是刻意的。
|
||||||
|
|
||||||
|
| 項目 | 目錄頁 | 內容頁 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 頁名 | `{TYPE}_CONTENTS` | `{TYPE}_{HASH}` |
|
||||||
|
| 存取庫解析 | 一律解 `CONTENTS`:`JSC_WIKI_REPO_CONTENTS` → `JSC_WIKI_REPO` → exit 3 | 解自己的型別:`JSC_WIKI_REPO_{TYPE}` → `JSC_WIKI_REPO` → exit 3 |
|
||||||
|
| 帶雜湊 | 否 | 是 |
|
||||||
|
| 版面 | 大標題加條列:一筆一個 H2 區塊,欄位一行一條,不留 markdown 表格 | 圖表優先:mermaid 與表格優於散文 |
|
||||||
|
|
||||||
|
`CONTENTS` 因此是第十五種頁面類型,而且是唯一一種自己沒有頁的:沒有 `CONTENTS_CONTENTS`,也沒有 `CONTENTS_{HASH}`。
|
||||||
|
它只用來解存取庫,`gitea.sh wiki-repo CONTENTS` 是全部目錄頁的解析入口。
|
||||||
|
總表列的十四種是頁的分類,`CONTENTS` 是存取庫的分類,兩張清單長度不同是正常的。
|
||||||
|
|
||||||
|
五條規則,寫入前逐條核對:
|
||||||
|
|
||||||
|
1. 任何 `*_CONTENTS` 頁都走 `gitea.sh wiki-repo CONTENTS`,十四種型別的目錄頁全部落在同一個存取庫。
|
||||||
|
2. 目錄頁的解析鏈**不退回型別變數**。設了 `JSC_WIKI_REPO_LOG` 不會讓 `LOG_CONTENTS` 跟著搬過去。
|
||||||
|
3. **文字加連結一律寫成 `[{文字}]({連結})`。** 不分目錄頁或內容頁,也不分同存取庫或跨存取庫,全部只有這一種寫法。`[[頁名]]` 與 `[[顯示文字|頁名]]` 兩種同 wiki 連結全面取消。網址取自 `jsc-gitea/tools/gitea.sh wiki-url`,不自行組路徑。
|
||||||
|
|
||||||
|
**為什麼只留一種寫法。** `[[...]]` 只在目前這個 wiki 內解析。寫錯不會報錯,畫面上看起來像正常文字或死連結,巡不到也修不了。兩種寫法並存就得逐處判斷兩端各自解到哪個存取庫;統一成一種,這個判斷消失。
|
||||||
|
|
||||||
|
4. **連結先驗證連得到,才可以寫進文件。** 寫入任何文件前,把要放進去的每一個連結交給 `jsc-gitea/tools/link-check.sh`,結束碼 0 才寫入。有任何一筆連不到就不寫入,把連不到的清單回報給呼叫端。結束碼分流以那支腳本的檔頭為準:0 才寫入,1 不得寫入並回報死連結那幾筆,2 與 3 補齊參數或 `GITEA_HOST` 再呼叫,7 停下來回報金鑰問題。
|
||||||
|
|
||||||
|
**為什麼驗證走 API,不看網頁狀態碼。** 私有存取庫的網頁網址對未登入請求一律回 404。拿網頁狀態碼判斷,會把還在的頁判成死連結,接著整批被刪掉或改寫。金鑰失效那一種也要與死連結分開回報,理由一樣:一次金鑰過期就會把整批好頁判成壞的。
|
||||||
|
|
||||||
|
5. **目錄頁一律「大標題加條列」,內容頁才維持圖表優先。** 這條區分是全技能組的判準,每支技能寫 wiki 前先看自己寫的是哪一種頁。
|
||||||
|
|
||||||
|
目錄頁的版面固定三段:H1 頁名、`>` 引言、然後每一筆紀錄一個 H2 區塊。H2 標題就是那一筆的鍵,寫成對應的**內容頁頁名** `{TYPE}_{HASH}`,標題不放連結、不放網址、不加前後綴、不加日期。欄位在標題底下一行一條,格式 `- {欄位名}:{值}`,全形冒號,順序照原欄位從左到右,一欄一條,鍵那一欄照樣留一條,資料才不會少。區塊之間空一行,H2 與第一條之間空一行。**目錄頁不留任何 markdown 表格**,也不放 mermaid。舊頁還是表格時由 `jsc-gitea/tools/wiki-contents.sh` 讀到就自動轉成條列後寫回,不另跑批次搬移,也不得手工搬。
|
||||||
|
|
||||||
|
**`<key-col>` 怎麼決定:照線上那一頁實際的欄位排法,不是照範本。** `wiki-contents.sh upsert <TYPE> <key-col> <key> <entry-file> [template-file]` 的 `<key-col>` 填的是**舊表格裡持有「內容頁連結」那一欄的序號**,只在舊頁還是表格、需要自動轉檔時才用得到:轉檔時工具從那一欄的連結網址取最後一段路徑當 H2 標題。序號一律先把線上那一頁讀回來(`gitea.sh wiki-get {CONTENTS 存取庫} {TYPE}_CONTENTS`)、看連結實際落在第幾欄再填。**不得照 `templates/` 裡的欄位排法推**:範本的欄位順序與線上那一頁常常不一樣,自動轉檔跑的是線上那一頁。填錯欄的後果是靜默的——標題會轉成那一欄的純文字(例如 `plugins/ask`),跟鍵 `{TYPE}_{HASH}` 對不上,既有那一筆被當成新的附加到頁尾,同一筆變兩個區塊,舊區塊從此再也更新不到,而且不會有任何錯誤訊息。線上是空頁、沒有舊表格要轉時,這個參數影響不到結果,照範本填即可。
|
||||||
|
|
||||||
|
內容頁反過來:**圖表優先,mermaid 與表格優於散文**,這一條只針對內容頁,繼續有效。
|
||||||
|
|
||||||
|
**為什麼分兩種。** 目錄頁是索引,每一筆的欄位一樣多、只給人挑一筆點進去;表格一寬就得橫向捲,欄位一多就對不上表頭,而且併行寫入時只要有人少打一根豎線,整張表就散掉,別人那一筆跟著看不見。條列式一筆一個區塊,寫入端只換自己那一塊,壞掉也只壞自己那一塊。內容頁要的是另一件事:一頁講一件事的全貌,流程與比較拿圖表最省讀者的力氣,所以圖表優先留在內容頁。
|
||||||
|
|
||||||
|
**為什麼要分開。** 目錄頁是全部使用者共用的索引,內容頁按專案或機器分散在各自的存取庫。混在一起的話,換一個專案就換一份索引,「這台機器有哪些頁」永遠問不到完整答案。索引集中一處、內容各自落地,才查得到全貌。
|
||||||
|
|
||||||
|
代價寫明:跨存取庫沒有原子性。內容頁寫成功、目錄頁寫失敗時,據實回報那個沒寫進去的目錄頁區塊與完整內容,不得反過來先寫目錄頁。
|
||||||
|
|
||||||
|
`{HASH}` 一律為 `{owner}/{repo}`(必要時加上主題字串)的**完整 SHA-1**,40 碼十六進位,`a-f` 一律轉大寫。
|
||||||
|
不截短、不加前綴:截短過的舊頁名以 `jsc-gitea/tools/migrate-wiki.sh` 遷移。
|
||||||
|
由 `jsc-gitea/tools/hash-id` 產生,空輸入 exit 2。
|
||||||
|
同一規則套用到所有內容頁;目錄頁不帶雜湊。
|
||||||
|
|
||||||
|
`REPORT` 用得到那個主題字串:雜湊來源為 `{owner}/{repo}/{期間}`,期間是 `daily`、`weekly`、`monthly`、`yearly` 其中之一。
|
||||||
|
這裡的 `{owner}/{repo}` 是 **`REPORT` wiki 存取庫**,也就是 `gitea.sh wiki-repo REPORT` 解出來的那一組值,**不是**被統計的那個程式碼存取庫。
|
||||||
|
兩者取錯會算出不同雜湊,同一份報表就散成兩頁,而且兩頁都寫得成功、都看不出錯。
|
||||||
|
年、月、週、日各自一頁,每頁內依期間累積分節。
|
||||||
|
|
||||||
|
`SKILLSET` 的雜湊來源就是被改動的 domain 存取庫 `{owner}/{repo}`,算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
|
||||||
|
頁內**累積**歷次異動:每次異動附加一節,不覆蓋舊紀錄。要看一支技能改過幾次,就在同一頁上翻。
|
||||||
|
|
||||||
|
`CHECK` 記的是一台執行環境,不是一個存取庫,所以雜湊來源為 `{主機名}/{登入帳號}`。
|
||||||
|
算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
|
||||||
|
在沒有存取庫的目錄也跑得出體檢,是這個例外存在的原因。
|
||||||
|
機器層的雜湊來源不只這一個,`MONITOR` 也照同一組取值,規則見下。
|
||||||
|
|
||||||
|
**`{主機名}` 一律取短主機名,不含網域。** `CHECK`、`TOOLING`、`MONITOR` 三種機器層頁面共用這一條。
|
||||||
|
取值方式固定為:`hostname` 的輸出取第一個點以前的那一段,全部轉小寫;取不到就退回 `uname -n` 再做同樣的截取。
|
||||||
|
理由是**同一台機器只能算出同一個雜湊**。這幾張頁的來源不只一處:`CHECK` 由模型自己填,`MONITOR` 由 `jsc-assist/tools/patrol.sh` 用程式算。
|
||||||
|
同一台機器上,程式拿到 FQDN(`web01.jsc.idv.tw`)、模型填短主機名(`web01`),兩邊就各開一張頁,各寫各的,兩張都寫得成功,也都看不出被分裂。
|
||||||
|
截到第一個點以前,兩條路徑才收斂到同一個值。
|
||||||
|
|
||||||
|
`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` 產生。
|
||||||
|
`patrol.sh` 與模型填值兩條路徑都要照這一條截取,改動任一邊就回頭核對另一邊。
|
||||||
|
|
||||||
|
**為什麼不帶工具名稱。** 這一點與 `TOOLING` 相反。
|
||||||
|
`TOOLING` 一支 CLI 一頁,因為每支 CLI 各有自己的已安裝 plugin 與 hook 接線。
|
||||||
|
助理看的是整台機器一份心跳、一本待辦簿,不分 CLI,所以中間那一段不能加。
|
||||||
|
|
||||||
|
**監控頁一律附加,不覆寫。** 助理的寫入是背景行為,覆寫錯了沒人在現場。
|
||||||
|
`TOOLING` 內容頁是每次盤點覆寫整頁,兩者的寫入語意剛好相反,不要混用。
|
||||||
|
|
||||||
## 審核檢查清單
|
## 審核檢查清單
|
||||||
|
|
||||||
新增或更新技能後逐項檢查,任一不符就修正:
|
新增或更新技能後逐項檢查,任一不符就修正:
|
||||||
|
|
||||||
- [ ] 名稱符合命名規則,且與既有技能目標不重複
|
- [ ] 名稱符合命名規則,且與既有技能目標不重複
|
||||||
- [ ] description 為英文、≤ 5 句或 5 步驟、含觸發時機
|
- [ ] description 為英文、≤ 5 句或 ≤ 5 步驟(滿足任一即通過)、含觸發時機(何時用、何時不用)
|
||||||
- [ ] 可 hook 的規則已下放 jsc-hooks;可工具化的流程已下放 tools/;SKILL.md 沒有保留可由標準輸入輸出執行的細節流程
|
- [ ] 可 hook 的規則已下放 jsc-hooks;可工具化的流程已下放 tools/;SKILL.md 沒有保留可由標準輸入輸出執行的細節流程
|
||||||
- [ ] 細節流程已標示 MUST run as a sub agent
|
- [ ] 細節流程已標示 MUST run as a sub agent
|
||||||
- [ ] gitea 操作透過 gitea.sh 或 tea
|
- [ ] gitea 操作透過 gitea.sh 或 tea
|
||||||
- [ ] wiki repo 與 Gitea 認證先讀目前 shell 繼承的環境變數;只有缺值或無法解析時才詢問;頁面類型不得跨用其他 `JSC_WIKI_REPO_{TYPE}`
|
- [ ] wiki repo 與 Gitea 認證先讀目前 shell 繼承的環境變數;只有缺值或無法解析時才詢問;頁面類型不得跨用其他 `JSC_WIKI_REPO_{TYPE}`
|
||||||
|
- [ ] 目錄頁一律解 `CONTENTS` 存取庫(`gitea.sh wiki-repo CONTENTS`),內容頁解自己的型別;所有連結一律寫成 `[{文字}]({連結})`,網址取自 `gitea.sh wiki-url`,不用 `[[頁名]]`
|
||||||
|
- [ ] 目錄頁寫成「大標題加條列」:一筆一個 H2 區塊、標題是內容頁頁名 `{TYPE}_{HASH}`、欄位一行一條 `- {欄位名}:{值}`、頁上沒有 markdown 表格;內容頁維持圖表優先(mermaid 與表格優於散文)。技能內文與 `templates/` 的目錄頁樣板都照這一條,寫入一律走 `jsc-gitea/tools/wiki-contents.sh upsert`,不手工改頁。規則見「目錄頁專用存取庫」第 5 條
|
||||||
|
- [ ] `wiki-contents.sh upsert` 的 `<key-col>` 是「舊表格裡持有內容頁連結那一欄的序號」,只供自動轉檔用;序號照**線上那一頁實際的欄位排法**填,先把線上頁讀回來確認,不照 `templates/` 的欄位排法推。規則見「目錄頁專用存取庫」第 5 條的 `<key-col>` 段
|
||||||
|
- [ ] 文件裡的連結都經過 `jsc-gitea/tools/link-check.sh` 驗證(結束碼 0 才寫入)且格式為 `[{文字}]({連結})`;`tools/check-link-format.sh {domain-path}` 對該 domain 退出 0,退出 3 是「什麼都沒掃」,不算通過
|
||||||
|
- [ ] 頁名樣式三處一致:`jsc-gitea/tools/page-name.sh`(正本)、`jsc-hooks/hooks/comment-scope.sh`、`jsc-log/tools/worklog-pending.sh`,`tools/check-page-name.sh {root}` 退出 0;退出 3 是「什麼都沒查」,不算通過。三處刻意不共用函式,因為 hook 必須自足,不得在執行期相依別的 plugin 路徑
|
||||||
- [ ] 問詢透過 jsc-ask 決策樹規則
|
- [ ] 問詢透過 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 無亂碼
|
- [ ] 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 是「什麼都沒查」,不算通過
|
||||||
|
- [ ] 每支技能的收尾步驟都呼叫 `{jsc-hooks 路徑}/tools/report-status.sh skill-end jsc-{domain}:{技能名} {status} {結束碼}`,`status` 五選一且 SKILL.md 寫明哪一種情況選哪一個,找不到腳本安靜跳過、不讓技能跟著失敗;該技能的「關鍵步驟」「完成條件」「可驗證跡象」三列都寫到這一筆事件。規則見「執行狀態回報」
|
||||||
|
- [ ] 該 domain 每支 `skills/*/SKILL.md` 的 frontmatter 解析得動,`tools/lint-frontmatter.sh {domain-path}` 對該 domain 退出 0;退出 3 是「什麼都沒掃」,不算通過。frontmatter 有語法錯誤時,Antigravity 會**靜默丟棄整支技能**,沒有任何錯誤訊息,只有這支腳本抓得到
|
||||||
- [ ] 已同步更新該 domain 的 README「Skills 目錄」與三份 manifest 的 version
|
- [ ] 已同步更新該 domain 的 README「Skills 目錄」與三份 manifest 的 version
|
||||||
|
- [ ] PR 的 base 符合「PR 分支階梯」,沒有越級
|
||||||
|
|
||||||
|
流程檢查四項,對照技能自己的流程逐項確認:
|
||||||
|
|
||||||
|
- [ ] 技能內外引用的步驟編號、檔案路徑、節標題都真的存在,指標指得到。踩過的實例:規則搬到 `references/` 後指標指向空處,照著指過去只看到空白
|
||||||
|
- [ ] 每個步驟以可檢核的完成條件結尾,沒有「理解後」「適當地」這類模糊語
|
||||||
|
- [ ] 每個外部呼叫(腳本、API、其他技能)的失敗情況都有明寫怎麼辦,退出碼都有分流
|
||||||
|
- [ ] 技能自己裝的閘門不會擋掉解除那道閘門的唯一路徑(閘門不自鎖)。踩過的實例:工作包閘門若擋掉 `implement`,結清 PR 就沒有路徑
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# PR 收尾回報
|
||||||
|
|
||||||
|
所有會建立或更新 PR 的技能都用本頁格式回報。不要在各技能複製欄位定義。
|
||||||
|
|
||||||
|
## PR 資訊表格
|
||||||
|
|
||||||
|
只要本輪建立 PR、找到既有 PR,或更新既有 PR,就在回報中列出同一張表。
|
||||||
|
|
||||||
|
| {owner}/{repo} | PR 編號 | PR 連結 | PR 簡述 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `{owner}/{repo}` | `{number}` | `{url}` | `{title or one-line summary}` |
|
||||||
|
|
||||||
|
同一輪有多支 PR 時,全部放在同一張表。沒有 PR 時不要印空表,改用一句話說明沒有建立或更新 PR。
|
||||||
|
|
||||||
|
## 留言回覆
|
||||||
|
|
||||||
|
依 PR 留言完成修正、判定不需修正,或判定無法修正後,必須回覆原留言。
|
||||||
|
|
||||||
|
回覆一律走 `jsc-gitea/tools/gitea.sh comment-reply`。不要自行拼 API。
|
||||||
|
|
||||||
|
`pr-comments` 第三欄會標出留言類型與 id。呼叫 `comment-reply` 時使用下列對應。
|
||||||
|
|
||||||
|
| `pr-comments` 第三欄 | `comment-reply` 類型 |
|
||||||
|
| --- | --- |
|
||||||
|
| `留言#{id}` | `issue` |
|
||||||
|
| `審查#{id}` | `review` |
|
||||||
|
| `行內#{id}(...)` | `inline` |
|
||||||
|
|
||||||
|
回覆內容固定包含兩項:
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 處理結果 | 已修、不需修,或無法修 |
|
||||||
|
| 依據 | commit、檔案,或不修理由 |
|
||||||
|
|
||||||
|
完成條件:本輪處理過的每一則留言,都有對應的回覆連結或明確的回覆失敗原因。
|
||||||
+27
-2
@@ -4,11 +4,36 @@ jsc 技能組所有交談與文件的語言規則,唯一來源在這裡。基
|
|||||||
|
|
||||||
上游版本:speak-human-tw v1.4.0(2026-07-18)。上游有新版時用 `jsc-meta:ste100-sync` 同步。
|
上游版本:speak-human-tw v1.4.0(2026-07-18)。上游有新版時用 `jsc-meta:ste100-sync` 同步。
|
||||||
|
|
||||||
|
## 適用範圍
|
||||||
|
|
||||||
|
**所有非程式碼輸出一律適用**。程式碼本身不受限。
|
||||||
|
|
||||||
|
| 對象 | 適用 | 說明 |
|
||||||
|
| :-- | :-- | :-- |
|
||||||
|
| 程式碼註解 | 是 | 含檔頭說明與行內註解 |
|
||||||
|
| commit 訊息 | 是 | 標題與內文;type 與 scope 仍為英文 |
|
||||||
|
| PR 描述 | 是 | 標題、各節內文 |
|
||||||
|
| wiki 頁內容 | 是 | 頁名前綴與範本欄位名維持英文 |
|
||||||
|
| 對使用者的回報 | 是 | 交談、進度回報、錯誤說明 |
|
||||||
|
| README、AGENTS.md | 是 | |
|
||||||
|
| `references/`、`templates/` | 是 | |
|
||||||
|
| 程式碼識別字、關鍵字、API 名稱 | 否 | 變數、函式、型別、參數、指令、路徑、URL |
|
||||||
|
| SKILL.md | 否 | 整份英文,規則見 `guidelines.md`「語言」 |
|
||||||
|
|
||||||
## 基礎紀律
|
## 基礎紀律
|
||||||
|
|
||||||
1. 短句,一句一指令,主動語態。
|
1. 短句,一句一指令,主動語態。
|
||||||
2. 同一個概念全程用同一個詞,不換詞循環。
|
2. 同一個概念全程用同一個詞,不換詞循環。
|
||||||
3. UTF-8,無亂碼。
|
|
||||||
|
## 編碼
|
||||||
|
|
||||||
|
| 要求 | 內容 | 常見違規 |
|
||||||
|
| :-- | :-- | :-- |
|
||||||
|
| UTF-8 | 檔案一律 UTF-8(無 BOM) | Big5、GBK 存檔 |
|
||||||
|
| 無亂碼 | 不得出現替代字元或雙重編碼殘留 | `�`、`�`、`ä`、`â` 開頭的三字元序列 |
|
||||||
|
| 無簡體字 | 一律繁體字形 | 应、这、说、国、网、码 |
|
||||||
|
|
||||||
|
亂碼多半來自轉檔或複製貼上,肉眼容易漏掉,交給 `tools/ste100-lint.sh` 的「亂碼」類別擋。
|
||||||
|
|
||||||
## 台灣用語
|
## 台灣用語
|
||||||
|
|
||||||
@@ -63,4 +88,4 @@ jsc 技能組所有交談與文件的語言規則,唯一來源在這裡。基
|
|||||||
|
|
||||||
## 機檢
|
## 機檢
|
||||||
|
|
||||||
可機檢的部分(中國用語、中文句內半形標點、AI 套話)用 `tools/ste100-lint.sh <file|dir>` 檢查,命中 exit 1。掃描時跳過本檔(規則文件裡的詞是被討論,不是被使用);語感與翻譯腔仍要人工判讀。
|
可機檢的部分(中國用語、中文句內半形標點、AI 套話、簡體字、亂碼、並列斜線)用 `tools/ste100-lint.sh <file|dir>` 檢查,命中 exit 1。`.md` 與 `.json` 跑全部類別;程式碼檔只跑簡體字與亂碼,其餘類別對英文標點與路徑誤報率太高。掃描時跳過本檔(規則文件裡的詞是被討論,不是被使用);語感與翻譯腔仍要人工判讀。
|
||||||
|
|||||||
+153
-8
@@ -1,17 +1,162 @@
|
|||||||
---
|
---
|
||||||
name: skill-check
|
name: skill-check
|
||||||
description: Routine compliance audit of the whole jsc skill set with no change request in hand. Sync every domain repo from the Gitea canonical marketplace, audit every skill against the guidelines.md audit checklist via sub agents, confirm each failed item with the user via decision tree, apply the confirmed fixes and re-check until all items pass, then open a PR per affected repo via jsc-git pr. Use for periodic or on-demand compliance checks of the skill set; not for applying a change request (use skillset-update) or editing one skill (use skill-update).
|
description: Routine compliance, script, hook, flow-efficiency, and cost-efficiency audit of the whole jsc skill set with no change request in hand. Sync every domain repo from the Gitea canonical marketplace, then run three parallel groups - lint-scripts.sh plus lint-frontmatter.sh plus check-behaviors.sh plus ste100-lint.sh plus check-link-format.sh plus check-wiki-rules.sh plus check-page-name.sh plus check-delegate.sh plus check-skill-paths.sh plus hook smoke, the guidelines.md checklist audit, and an optimization review that first reads each domain's SKILLSET_{HASH} so suggestions already applied or deferred are never re-scanned or re-asked, then covers parallelism, tool extraction, repeated interaction, redundant checks, misplaced gates, and avoidable token, sub-agent, API, scan, or interaction cost. Confirm compliance fixes and optimization suggestions before applying them, recording each decision with its date, re-check until accepted fixes pass, then open a PR per affected repo via jsc-git pr. Close by appending the round's result to every changed domain's SKILLSET_{HASH} and its SKILLSET_CONTENTS block, or to the plugins/meta page when no domain was changed. Use for periodic or on-demand skill-set checks; not for applying a change request (use skillset-update) or editing one skill (use skill-update).
|
||||||
---
|
---
|
||||||
|
|
||||||
# skill-check — audit the skill set against the guidelines
|
# skill-check — audit compliance, flow efficiency, and cost efficiency
|
||||||
|
|
||||||
Single source of guidelines: [`../../references/guidelines.md`](../../references/guidelines.md).
|
Single source of guidelines: [`../../references/guidelines.md`](../../references/guidelines.md).
|
||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. Query the Gitea canonical marketplace for the authoritative domain list: run `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`. Clone any domain repo missing from the working directory (`gitea.sh clone-url plugins/{domain}`) and pull the rest. Completion condition: every domain repo exists locally and is current.
|
1. Run `tools/sync-domains.sh` to sync every domain repo of the Gitea canonical marketplace. Completion condition: the script exits 0 and prints one `domain<TAB>path` line per marketplace domain — exit 0 is the only code that means every repo is present and current. Exit 3 means some repos were not updated: reconcile every path named on stderr (commit or stash the dirty tree, or fix the failing pull) and rerun; when the user confirms a dirty tree is intentional local work, record that decision and continue on the local version — never read exit 3 as current. Exit 2 means a domain could not be cloned. Exit 1 means the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable; when stderr says the root could not be derived, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos and rerun, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there instead of the domain workspace. Resolve 2 and 1 before continuing.
|
||||||
2. Audit every skill of every domain against the guidelines.md audit checklist — this step MUST run as a sub agent, one sub agent per domain repo. Each sub agent reports its findings: skill, failed checklist item, evidence (file:line), proposed fix. Completion condition: every domain has an audit result.
|
|
||||||
3. Present each failed item via the `jsc-ask:ask` decision tree (apply the proposed fix / skip / custom fix). Every option states its impact scope (example: skipping leaves the skill non-compliant until the next audit). Completion condition: every finding has a recorded decision.
|
The three review groups of step 2 all read this synced tree, so the sync finishes first.
|
||||||
4. Apply the confirmed fixes — the fix-application part MUST run as a sub agent, one sub agent per affected domain repo: modify the files per the confirmed fix. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) for each affected domain repo to refresh that domain README's 「Skills 目錄」 section and bump the version in all three manifests. Completion condition: every affected repo carries the fixes and the manifest bump.
|
2. Run the three review groups over the synced repos. They are independent — every one only reads, none writes a file — so **launch all three in parallel** and merge their results in step 3.
|
||||||
5. Re-check the guidelines.md audit checklist for every touched skill. On any failure, **return to step 3**: confirm and fix again, until all items pass. Completion condition: all checklist items pass.
|
|
||||||
6. Call `jsc-git:pr` once per affected domain repo to open a Push Request. Completion condition: every affected repo has a PR URL.
|
**Group 1 — validate scripts, frontmatter, behavior lists, language, wiki rules, page names, the delegation list, and hooks.**
|
||||||
|
1. For every synced domain repo, run `tools/lint-scripts.sh {domain-path}`. One run per domain, and the runs go **in parallel** — no domain's verdict depends on another's. The tool covers three checks in one pass: `sh -n` syntax, executable bit, and an exit-code declaration in the file header. Route each exit code: 0 — the domain's scripts pass all three; 1 — the failing items are printed as `{file}:{check}:{detail}`, so report each one; 2 — usage error, the tool takes exactly one argument; 3 — nothing was scanned, because the path is missing or the domain has neither `tools/` nor `hooks/`. Record exit 3 as 「無腳本可掃」; a domain with no script directory is not a failure, but exit 3 is **never** a pass.
|
||||||
|
2. For every synced domain repo, run `tools/lint-frontmatter.sh {domain-path}`. One run per domain, and the runs go **in parallel** alongside the `lint-scripts.sh` runs. It parses the frontmatter of every `skills/*/SKILL.md` without a YAML library — paired `---` delimiters, the required `name` and `description` keys, unquoted scalars carrying a colon-space or ending in a colon, unquoted scalars opening with `&`, `*`, `!`, `|`, `>`, `%`, `@` or a backtick, and quoted scalars that never close. Route each exit code: 0 — every SKILL.md in that domain parses; 1 — the failures are printed on stderr as `{檔案}:{鍵}:{說明}`, so report every one as a compliance failure with the file and key it belongs to; 2 — usage error, the tool takes exactly one argument; 3 — nothing was scanned, because the domain path or `skills/` is missing, or `skills/` holds no `SKILL.md`. Record exit 3 as 「無 frontmatter 可掃」with the cause from stderr and carry it into the step 3 merge; exit 3 is **never** a pass. This check exists because a broken frontmatter makes Antigravity drop the whole skill with **no error message at all** — 34 skills on disk loaded as 28, and only a file-by-file comparison found it.
|
||||||
|
3. For every synced domain repo, run `tools/check-behaviors.sh {domain-path}`. One run per domain, and the runs go **in parallel** alongside the `lint-scripts.sh` runs — no domain's verdict depends on another's. It compares `references/behaviors.md` against `skills/`: section per skill, dictionary order, one table per section, five rows, no empty content cell. Route each exit code: 0 — that domain's behavior list matches; 1 — the mismatches are printed on stderr as `{檔案}:{技能名}:{說明}`, so report every one as a compliance failure with the skill it belongs to; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, because `references/behaviors.md` is missing, `skills/` is missing, or no `SKILL.md` was found. Record exit 3 as 「無清單可查」with the cause from stderr and carry it into the step 3 merge; a domain with no behavior list is a compliance failure, and exit 3 is **never** a pass.
|
||||||
|
4. For every synced domain repo, run `tools/ste100-lint.sh {domain-path}`. One run per domain, and the runs go **in parallel** alongside the other group 1 runs. Route each exit code: 0 — every scanned file in that domain passes; 1 — the hits are printed on stdout as `{檔案}:{行號}:{類別}:{命中內容}`, so report every one as a compliance failure with the category it belongs to, after checking the two documented false-positive classes (a path or branch name holding a slash between Chinese characters, and English items listed with a slash); 2 — no scan target was given, which is a caller defect here, so fix the argument and rerun. The tool has no exit 3: an empty run returns 2 rather than a silent pass. This check lives in group 1 because the audit checklist demands 「`tools/ste100-lint.sh` 對該 domain 全綠」 for every domain — leaving it to the group 2 sub agents meant ten agents each ran it their own way, and the main agent never held one comparable verdict per domain.
|
||||||
|
5. For every synced domain repo, run `tools/check-link-format.sh {domain-path}`. One run per domain, and the runs go **in parallel** alongside the other group 1 runs. It reads every `*.md` of that domain and reports any link still written in the double-bracket wiki-link form, which resolves only inside one wiki and dead-links everywhere else. Route each exit code: 0 — every link in that domain is written as `[{text}]({url})`; 1 — the hits are printed on stdout as `{檔案}:{行號}:{命中內容}`, so report every one as a compliance failure; 2 — usage error, the tool takes exactly one argument; 3 — nothing was scanned, because the domain path is missing or holds no `*.md`. Record exit 3 as 「無文件可掃」with the cause from stderr; it is **never** a pass. The tool strips inline code and fenced code blocks before judging, so a document that quotes the forbidden form while explaining it, and a shell test expression inside an example, both stay clean.
|
||||||
|
6. Run the two wiki-rule checkers once each, not per domain — both judge shared rules, so a second run adds nothing:
|
||||||
|
- `jsc-gitea/tools/check-wiki-rules.sh`, which verifies wiki repo resolution and the `hash-id` rule for every page type. It takes no argument. Route each exit code: 0 — printed `OK` on stdout, every item passed; 1 — the first mismatch is printed on stderr as `{項目}: want=… got=…` and the script stops there, so report that item and rerun after the fix, because the remaining items were never reached. Those are its only two codes. Until this audit, no flow in the whole repository ever called it.
|
||||||
|
- `tools/check-page-name.sh {root}`, where `{root}` is the directory holding the domain repos — the parent directory of the paths `tools/sync-domains.sh` printed in step 1, so no extra derivation is needed. It compares the page-name pattern in its three copies: `jsc-gitea/tools/page-name.sh` (the canonical one), `jsc-hooks/hooks/comment-scope.sh` and `jsc-log/tools/worklog-pending.sh`. Route each exit code: 0 — the three agree; 1 — the mismatches are printed on stderr as `{檔案}:{說明}`, so report each one as a compliance failure, and a copy that could not be found is one of those lines; 2 — usage error, the tool takes exactly one argument; 3 — none of the three copies was found, so the root is wrong: fix it and rerun. Record exit 3 as 「什麼都沒查」; it is **never** a pass. The three copies stay separate on purpose — a hook must be self-contained and may not depend on another plugin's path at run time — so consistency is checked here instead of shared in a function.
|
||||||
|
7. Run `tools/check-delegate.sh {root}` once for the whole round, with the same `{root}` item 6 passed to `check-page-name.sh`. It compares the delegation list `tools/delegate-spec.tsv` against the skills `tools/list-skills.sh` finds on this machine: one skill one row, twelve columns, every mandatory column filled, every `next` naming a skill that exists, and every `probe` either a runnable read-only command, a `pending:{reason}`, or a `-` on the rows that take one. It belongs in group 1 for the same reason `ste100-lint.sh` does — it is a deterministic script verdict, and it is judged **once for the whole round** rather than per domain, because the list is a single file covering every domain. Handing it to the group 2 sub agents would have ten agents run the same script over the same file and report ten copies of the same lines, with no single verdict anywhere; handing it to group 3 would turn a pass-or-fail check into a suggestion. Route each exit code:
|
||||||
|
- 0 — the list and the machine's skills correspond one to one and every mandatory column is filled. **A run that printed lines on stdout and exited 0 passed.** Those lines are hints, not compliance failures, and they are printed on stdout precisely so they are told apart from the failures on stderr: `origin=seed` marks a row seeded from the earlier inventory that has not been through the decision tree yet, a version-behind line marks a row whose recorded `version` trails its domain's current one, a `probe=pending:` line marks a delegated slice whose read-only entry point is not wired yet, and a line saying a `probe` domain is not installed here marks a script this machine cannot check. The version number is per domain, so one skill's change marks every other skill of that domain — counting those as failures paints whole domains red on every release, and the hint stops being read at all. Report the hint count and the rows, and open no decision-tree item for them.
|
||||||
|
- 1 — a missing row, a duplicate row, a row for a skill this machine does not have, an empty column, a column value outside its vocabulary, a `next` naming a skill that does not exist, or a `probe` in the wrong shape — a command on a row whose `way` holds `invoke`, a `-` on a row whose `way` holds only `patrol` or `remind`, a dollar sign or tilde, an unknown substitution point, or a script that does not exist. Every one is printed on stderr as `{清單路徑}:{domain}/{技能名}:{說明}`. Report each as a compliance failure, named by the skill it belongs to. A missing row means the assistant is blind to that skill; an extra row means it will trigger a skill that cannot be called, and a failing trigger retries instead of pausing. A wrong `probe` fails every unattended round in the same silent way, and the command-on-an-`invoke`-row case is worse than a failure: the assistant runs a bare script where the whole skill was supposed to run, and the round looks clean.
|
||||||
|
- 2 — usage error: the script takes at most one argument. Fix the call and rerun; this is a defect in this skill, not a finding about the skill set.
|
||||||
|
- 3 — nothing was checked, because `tools/delegate-spec.tsv` is missing, the root could not be derived, or `list-skills.sh` listed no skill. Record it as 「無委派清單可查」 with the cause from stderr and carry it into the step 3 merge; **exit 3 is never a pass**, because a check that read nothing reports neither a missing row nor an extra one.
|
||||||
|
|
||||||
|
This verdict is **not** one of the guidelines.md audit-checklist items, so it stays out of the nine that step 3 merges into every domain's checklist and is reported on its own line, one line for the round.
|
||||||
|
8. For every synced domain repo, run `tools/check-skill-paths.sh {domain-path}`. One run per domain, and the runs go **in parallel** alongside the other group 1 per-domain runs. It reads every `skills/*/SKILL.md` and `references/*.md` and resolves each `tools/…` or `hooks/…` script path written in them. `lint-scripts.sh` proves a script exists inside its own repo; this one proves the path as written reaches it. Route each exit code: 0 — no path in that domain points at a file that is not there, and the hint lines it printed are counted separately from failures; 1 — the `missing` lines name paths that resolve to nothing, so report each as a compliance failure with its file and line; 2 — usage error, the tool takes exactly one argument; 3 — nothing was scanned, because the domain path is missing or it has neither `skills/*/SKILL.md` nor `references/*.md`. Record exit 3 as 「無文件可掃」; exit 3 is **never** a pass. Its `unrooted` and `unknown` lines are hints, never failures — `unrooted` marks a path with no plugin directory name, which resolves against whatever the current directory happens to be, and `unknown` marks a cross-domain path whose repo is not on this machine. Carry the two hint counts into the report without turning them into decision-tree items: the whole skill set carries hundreds of `unrooted` paths, and promoting them to failures would turn every domain red at once, which reads the same as no report at all. This check exists because item 9 below was already supposed to catch this and could not: a human reading item 9 checks that the named script exists in `tools/`, which it does, and never asks whether the path as written reaches it — so the same defect passed review every round until a path exited 127 in front of someone. This verdict is **not** one of the guidelines.md audit-checklist items either, so it stays out of the nine that step 3 merges into every domain's checklist and is reported on its own line, one line per domain.
|
||||||
|
9. For every shell script directly named by a SKILL.md, confirm the skill routes every exit code the script's header declares. `lint-scripts.sh` proves the script exists and declares its codes; this check is the other half — that the caller branches on each of them. Report evidence as `skill file:line -> script path`.
|
||||||
|
10. When the `jsc-hooks` domain is present, run `jsc-hooks/tools/wire-cli.sh smoke {cli}` for every CLI reported by `jsc-cli/tools/detect-clis.sh`; the per-CLI smokes run **in parallel**. When no CLI is detected, run `jsc-hooks/tools/wire-cli.sh smoke codex` as the minimum hook behavior check and label it 「預設 hook smoke」 in the report. Use `smoke`, not `purge` or rewiring actions, and set `JSC_READONLY=1` for the whole audit so a mistyped sub-command is refused in code (exit 6) instead of rewiring the machine; `status` and `smoke` are unaffected by that variable. Route each `smoke` exit code: 0 — the run passed its own assertions; 2 — usage error, so fix the CLI code and rerun; 4 — the smoke failed, which includes the script's own result-line count not matching what it expected. **Read the count from the script's `lines<TAB>{數量}` output line; never write the number into this skill.** The script counts its own result lines and asserts them, so a hardcoded number here goes stale the moment a hook or a decision path is added — an out-of-date count in a SKILL.md is exactly what misled the previous audit.
|
||||||
|
11. When a hook or script smoke fails, route it as a compliance failure with script name, exit code, output summary, and proposed fix. Do not continue to report the affected hook as compliant.
|
||||||
|
|
||||||
|
**Group 2 — audit every skill of every domain against the guidelines.md audit checklist.** This group MUST run as a sub agent, one sub agent per domain repo, and those sub agents run **in parallel**. Each sub agent reports its findings: skill, failed checklist item, evidence (file:line), proposed fix. Cover the checklist's four flow checks by name, not only the naming and language items:
|
||||||
|
- Every step number, file path and section title the skill references — inside itself and in other files — really exists (the pointer points at something).
|
||||||
|
- Every step ends in a checkable completion condition, with no vague wording.
|
||||||
|
- Every external call (script, API, other skill) states what to do on failure and routes every exit code.
|
||||||
|
- No gate the skill installs blocks the only path that lifts that gate.
|
||||||
|
|
||||||
|
Nine checklist items are **already decided by group 1** and must not be re-run here: `sh -n` on every `tools/` and `hooks/` script, script existence with the executable bit, the hook smoke, the `references/behaviors.md` match, the `lint-frontmatter.sh` verdict, the `ste100-lint.sh` verdict, the link-format verdict from `tools/check-link-format.sh`, the page-name-pattern verdict from `tools/check-page-name.sh`, and the wiki repo resolution plus `hash-id` verdict from `jsc-gitea/tools/check-wiki-rules.sh`. Tell each sub agent to skip those nine and leave them blank; the main agent fills them in from the group 1 verdicts when merging in step 3. The link-checklist item has a second half no script can judge — that every link went through `jsc-gitea/tools/link-check.sh` before it was written — so each sub agent still audits that half from the skill text. The last two are **one verdict for the whole round**, not one per domain — group 1 runs each of those two checkers once, because both judge rules the domains share — so the merge writes that single verdict into every domain's checklist. Re-scanning the same files in every domain sub agent buys nothing — group 1 already scanned them all, with the same tools, on the same synced tree.
|
||||||
|
|
||||||
|
**Group 3 — a flow and cost optimization review**, kept separate from the compliance audit.
|
||||||
|
|
||||||
|
**Read the previous round's decisions before scanning anything.** For every synced domain, resolve `jsc-gitea/tools/gitea.sh wiki-repo SKILLSET`, compute the page name as `SKILLSET_` plus `gitea.sh hash-id "{owner}/{repo}"` of that domain repo, and read it through `jsc-gitea:wiki`. Take the 優化建議 table of every `skill-check` section on that page: an entry whose 決議 column reads `套用` or `延後` is **settled** — do not scan for it again, and do not put it back into the step 3 decision tree. Only a genuinely new finding, or an entry whose recorded 決議 is `自訂` with the custom fix not yet in place, reaches step 3.
|
||||||
|
|
||||||
|
Route every exit code of all three calls, not the read alone:
|
||||||
|
|
||||||
|
| Call | Exit | Do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea.sh wiki-repo SKILLSET` | 0 | The repo is in hand; go on to `hash-id` |
|
||||||
|
| | 2 | The page type was misspelled here, which is a defect in this skill and not a user setting. Fix the argument and rerun |
|
||||||
|
| | 3 | Neither `JSC_WIKI_REPO_SKILLSET` nor `JSC_WIKI_REPO` is set. Name both variables, ask per the `jsc-ask:ask` rules, then rerun. No answer means no settled list for that domain, per the rule below |
|
||||||
|
| `gitea.sh hash-id "{owner}/{repo}"` | 0 | Use the 40 characters it printed as the page-name suffix, unshortened |
|
||||||
|
| | 1 | No SHA-1 helper on this machine. Report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash |
|
||||||
|
| | 2 | Empty input, so the `{owner}/{repo}` was never resolved. Resolve it and rerun |
|
||||||
|
| `jsc-gitea:wiki` read | 0 | The settled list is in hand |
|
||||||
|
| | 4 | The page does not exist yet, so there is no previous round. Carry on with an empty settled list — the normal state for a domain audited the first time |
|
||||||
|
| | 7 | The token is invalid or lacks permission |
|
||||||
|
| | 8 | Some other API failure |
|
||||||
|
|
||||||
|
**A 7, an 8, an unanswered 3, or a stopped 1 or 2 ends group 3 for that domain, and nothing else.** An unread page cannot be told apart from an empty one, and treating it as empty re-asks every question the user already answered. So mark that domain 「本輪未取得已決議清單,優化建議暫不提出」, report the exit code that caused it, and run the rest of the round unchanged: group 1 and group 2 read no wiki at all, and steps 3 to 8 still apply and record every compliance fix. Stopping the whole round on a wiki failure would switch off the one routine compliance audit this skill set has — including the audit that finds a broken wiki setting.
|
||||||
|
|
||||||
|
This read exists because the audit was re-scanning and re-asking every accepted or deferred suggestion on every run — exactly the 「重複來回」 that aspect 3 below is supposed to catch, committed by the skill that defines it.
|
||||||
|
|
||||||
|
Each aspect **MUST run as a sub agent**, and the six aspects run in parallel with each other and with groups 1 and 2. Every sub agent gets that domain's settled list up front:
|
||||||
|
|
||||||
|
| Aspect | Scope |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 Parallelism | Steps that run in series today but have no data dependency and can run in parallel |
|
||||||
|
| 2 Tool extraction | SKILL.md text flows with clear inputs and outputs that should move to `tools/`, including hook-enforceable rules that still rely on prompts |
|
||||||
|
| 3 Repeated interaction | The same user question, repository fact, wiki page, API result, or file content being collected more than once |
|
||||||
|
| 4 Redundant checks | Completion conditions or verification steps that overlap, or a later step that necessarily covers an earlier check |
|
||||||
|
| 5 Gate timing | Gates that run too early or too late, causing wasted work before a block or blocking the only path that clears the gate |
|
||||||
|
| 6 Cost efficiency | Avoidable token, sub-agent, API, file-scan, full-repo audit, or user-interaction cost that can be reduced without weakening correctness |
|
||||||
|
|
||||||
|
Each optimization finding reports skill, aspect, evidence (file:line), current flow step count, proposed flow step count, what time or interaction it saves, what cost it saves, current cost driver, proposed cost driver, whether correctness decreases, which protection would be weakened if any, the **決議** (`套用`, `延後` or `自訂`) recorded in step 3, and the **決議日期** that decision was made. The last two fields start empty and are filled in by step 3; they are what step 8 writes to the wiki and what the next round reads back, so a finding that reaches step 8 with either field empty is unfinished, not optional. Cost savings may be token volume, sub-agent count, API calls, file scans, full-repo audits, or user prompts. Keep optimization findings separate from compliance failures.
|
||||||
|
|
||||||
|
Completion condition for all three groups: every domain has a `lint-scripts.sh` verdict, a `lint-frontmatter.sh` verdict, a `check-behaviors.sh` verdict, an `ste100-lint.sh` verdict, a `check-link-format.sh` verdict and a `check-skill-paths.sh` verdict — the last one carrying its `unrooted` and `unknown` hint counts separately from its failures — `check-wiki-rules.sh`, `check-page-name.sh` and `check-delegate.sh` each have one verdict for the whole run — `check-delegate.sh` carrying its hint lines separately from its failures, or 「無委派清單可查」 where it exited 3 — every script named by a SKILL.md has an exit-code-routing verdict, and every smoked CLI has a `smoke` exit code plus the `lines` value the script printed for it; every domain has a group 2 audit result that names a verdict for all checklist items — the four flow checks included, and the nine group 1 items left blank for the step 3 merge rather than re-scanned; and every one of the six aspects has returned a verdict for every domain whose settled list was read, 「無發現」 where an aspect found nothing and every settled entry of that domain excluded rather than re-reported — a domain whose pre-read failed carries 「本輪未取得已決議清單,優化建議暫不提出」 instead, and that sentence is a complete group 3 result for it.
|
||||||
|
3. Merge the three groups, then present compliance failures and optimization findings separately via the `jsc-ask:ask` decision tree. Merging means one thing in code: fill the nine skipped checklist items of every group 2 sub agent report from the matching group 1 verdicts, so each domain ends with one complete checklist and no item counted twice. Seven of the nine are per-domain verdicts, one domain to one item. The other two — `check-page-name.sh` and `check-wiki-rules.sh` — are judged **once for the whole round**, and that one verdict goes into that same item of **every** domain's checklist; re-judging a whole-round item per domain is precisely the double counting this merge exists to stop. A domain that group 3 marked 「本輪未取得已決議清單,優化建議暫不提出」 still gets its full compliance checklist here; only its optimization findings are missing, and the merge report says so.
|
||||||
|
- Compliance failure options: apply the proposed fix / skip / custom fix. Every option states its impact scope, for example skipping leaves the skill non-compliant until the next audit.
|
||||||
|
- The `check-delegate.sh` failures join that same set, one decision-tree item per reported row, and they carry one extra note in their impact scope: fixing a missing row means running the delegation decision tree of [`../../references/delegate-criteria.md`](../../references/delegate-criteria.md) for that skill in step 4, which is more questions than most fixes. Its **hint** lines never become decision-tree items — a hint is a note about a row that is already there, and turning it into a question re-asks a settled judgement every round, which is the 「重複來回」 group 3 exists to catch.
|
||||||
|
- Optimization options: apply / defer / custom. Record the chosen option in the finding's 決議 field as `套用`, `延後` or `自訂`, and today's date in 決議日期. Any suggestion that weakens a protection must name the protection it removes and must not be applied unless the user explicitly accepts that tradeoff. Cost optimization may move, merge, cache, or narrow checks; it must not delete a compliance check only because it is expensive.
|
||||||
|
|
||||||
|
Completion condition: every domain's checklist is complete after the merge, with the two whole-round verdicts carrying the same value in every domain, and every compliance failure and every optimization finding has a recorded decision — every optimization finding carrying both 決議 and 決議日期.
|
||||||
|
4. Apply the confirmed fixes and accepted optimizations — the file-change part MUST run as a sub agent, one sub agent per affected domain repo, and those sub agents run **in parallel**: each repo's files are independent. A fix that changes a skill's behavior also updates that skill's `## {name}` section in the same repo's `references/behaviors.md`, in the same pass, so the fix and the behavior list land in one PR. A confirmed `check-delegate.sh` fix is written by the **main agent**, never by the per-repo sub agents: `tools/delegate-spec.tsv` is one file for the whole skill set, and parallel agents writing one file overwrite each other's rows. A missing row is filled by running the decision tree of [`../../references/delegate-criteria.md`](../../references/delegate-criteria.md) for that skill through `jsc-ask:ask` and writing the answer as a row with `origin` set to `judged`; an extra row is deleted; a dead `next` is repointed at a skill that exists; a wrong `probe` is rewritten per that same file — verified against the owning repo, not guessed — and set to `pending:{reason}` when the slice has no read-only entry point on this machine. A fix that changed a skill's behavior in this same round also re-judges that skill and moves its row's `version`. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) for each affected domain repo to refresh that domain README's 「Skills 目錄」 section and bump the version in all three manifests. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path, `skills/`, `README.md`, the `JSC-SKILLS` markers, a `SKILL.md`, a manifest, or a manifest `version` field is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs under `set -e`, so treat it as an environment fault and stop, never as a successful sync. Completion condition: every affected repo carries the changes, the matching `references/behaviors.md` update for every fix that changed a skill's behavior, the `tools/delegate-spec.tsv` rows for every accepted delegation fix, and the manifest bump.
|
||||||
|
5. Sync the canonical marketplace — a **required** step, never optional. The canonical pair lives in `plugins/meta` and every domain repo carries a byte-identical copy, so a fix that leaves the copies apart makes some repos register a stale plugin set. Run `tools/sync-marketplace.sh {domain} {repo-url} {description}` once with an existing entry's own current values (rewriting the same entry is idempotent); the script rewrites both canonical files and copies them into every domain repo. Route each exit code:
|
||||||
|
- Exit 3 — written, but some domain repo is not present locally. Run `tools/sync-domains.sh`, then rerun this step.
|
||||||
|
- Exit 2 — usage error: the script takes exactly three arguments. Fix them and rerun.
|
||||||
|
- Exit 1 — the root could not be derived, python3 is missing, a canonical file was unreadable, or copies differ byte for byte. Read stderr and fix the named cause: install python3 for the second; for the root case set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there. Then rerun.
|
||||||
|
- Exit 0 — every copy holds identical bytes; the script verifies that itself.
|
||||||
|
|
||||||
|
Completion condition: the script exits 0 and prints the touched paths.
|
||||||
|
6. Re-run the group 1 script, frontmatter, behavior-list, language, link-format, script-path, wiki-rule, page-name, delegation-list and hook validation, re-check the guidelines.md audit checklist for every touched skill, then re-run the optimization aspect that produced each accepted optimization. These three re-runs are as independent as the first pass, so run them **in parallel** and merge them the same way step 3 did. On any compliance failure, **return to step 3**: confirm and fix again, until all accepted compliance fixes pass. On an accepted optimization that does not produce the promised step reduction or cost reduction, or still weakens correctness beyond the recorded decision, return to step 3 for a new decision. Completion condition: `tools/lint-scripts.sh` exits 0 or 3 for every domain, `tools/lint-frontmatter.sh` exits 0 for every domain — exit 3 is 「什麼都沒掃」 and never counts as a pass — `tools/check-behaviors.sh` exits 0 for every domain, `tools/ste100-lint.sh` exits 0 for every domain, `tools/check-link-format.sh` exits 0 for every domain — its exit 3 is 「什麼都沒掃」 and never counts as a pass — `tools/check-skill-paths.sh` exits 0 for every domain — its exit 3 is 「什麼都沒掃」 and never counts as a pass, and its `unrooted` and `unknown` lines stay hints rather than becoming failures — `jsc-gitea/tools/check-wiki-rules.sh` exits 0, `tools/check-page-name.sh` exits 0 — its exit 3 is 「什麼都沒查」 and never counts as a pass — `tools/check-delegate.sh` exits 0, its remaining stdout lines counted as hints rather than failures and its exit 3 read as 「無委派清單可查」 and never as a pass, every hook smoke exits 0 with the `lines` count the script itself asserted, every domain's checklist passes in full — the two whole-round verdicts filled into each domain from the one run that produced them — and every accepted optimization has a matching verification result.
|
||||||
|
7. Call `jsc-git:pr` once per affected domain repo to open a Push Request. Completion condition: every affected repo has a PR URL, and all URLs are reported in one table with the format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
||||||
|
8. Write the round's result to the wiki. This step **MUST run as a sub agent**, one sub agent per affected domain repo, and those sub agents run **in parallel**: each domain writes its own page, and no page waits on another.
|
||||||
|
|
||||||
|
Without this step the whole audit stops at the PR and scatters. The other four `jsc-meta` change skills all append to `SKILLSET_{HASH}`; `skill-check` was the only one that did not, so the audit that produced the deferrals had nowhere to record them and step 2's group 3 had nothing to read back.
|
||||||
|
|
||||||
|
- **Which page.** For every domain repo actually changed in this round, resolve `gitea.sh wiki-repo SKILLSET` and append one section to `SKILLSET_` plus `gitea.sh hash-id "{owner}/{repo}"` of that domain repo. Append; never overwrite — the page accumulates every change that domain has ever seen.
|
||||||
|
- **When nothing changed.** No domain repo changed in this round means one page, hashed from `plugins/meta`, gets one section recording 「本輪無發現」 with the group verdicts that produced that conclusion. A round that found nothing still has to leave the evidence that it ran.
|
||||||
|
- **What each section holds.** The layout is [`../../templates/skillset-page.md`](../../templates/skillset-page.md): date, change type `skill-check`, the change request in one sentence, the skills touched, the files changed, the PR URL from step 7, the deploy-route verdict and the verification result. A section for a round that wrote delegation rows also names the skills whose rows this round judged or repointed, so the row's own missing note column is covered here. The `skill-check` section additionally carries the 優化建議 table, every row filled including 決議 and 決議日期 — that table is exactly what the next round reads in step 2's group 3.
|
||||||
|
- **Directory page.** Refresh that page's own block in `SKILLSET_CONTENTS` with `jsc-gitea/tools/wiki-contents.sh` — never by hand, and never through `jsc-gitea:wiki`. That page is a heading-plus-bullets list and holds no markdown table: one `## SKILLSET_{HASH}` block per domain repo, every field one `- {欄位名}:{值}` line under it. Build one file holding this domain's single block, following [`../../templates/skillset-contents.md`](../../templates/skillset-contents.md), then run:
|
||||||
|
|
||||||
|
`jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {entry file} templates/skillset-contents.md`
|
||||||
|
|
||||||
|
The key is the H2 heading `SKILLSET_{HASH}`, and that page name depends only on the domain repo's `{owner}/{repo}`, so it reads the same every round and one domain keeps exactly one block. The `2` is the key column: the index of the column that held the content-page link in the **old markdown table**, and it matters only when such an old table still has to be converted automatically — the conversion takes the last path segment of that column's link URL as the H2 heading. Count that index from the **live page's own column layout**, never from the template's: the live `SKILLSET_CONTENTS` reads `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`, so the link sits in column 2 while column 1 is plain text like `plugins/ask`. Passing `1` would make the heading `plugins/ask`, which never matches the key `SKILLSET_{HASH}`, so the existing entry is appended as a brand-new one — one domain ends up with two blocks and the older one is never updated again. The fourth argument is the whole block, not a table row. The script resolves the CONTENTS repo itself — the directory page lives there, never in the SKILLSET repo — reads the whole page, converts any leftover table to blocks, replaces the block whose heading matches, appends when none matches, and writes the page back, so every block owned by another domain stays as it was.
|
||||||
|
|
||||||
|
The 異動頁 bullet is written as `[SKILLSET_{HASH}]({url})`, the URL being the **absolute** one from `gitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}`; the H2 heading itself carries no link, no URL, no affix and no date. Every link on both pages takes that `[{text}]({url})` form — the double-bracket wiki-link form is never used, because it resolves only inside one wiki. Fetch that URL only after the content page is written: **write the content page first** — a directory block naming a page whose write failed is worse than a missing block.
|
||||||
|
- **Check the links before writing.** Hand every URL going onto the content page and into the directory block to `jsc-gitea/tools/link-check.sh` and write only when it exits 0. It verifies through the Gitea API, never a web status code: a private repo answers 404 to an unauthenticated web request, so a status-code check would call a live page dead and rewrite pages that are fine.
|
||||||
|
- **Exit codes.** Route every one of them. None of these calls may be read as success by default.
|
||||||
|
|
||||||
|
| Call | Exit | Do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea.sh wiki-repo` | 2 | The page type was misspelled. Fix the argument and rerun |
|
||||||
|
| | 3 | No wiki repo is configured for that type. Name the variable (`JSC_WIKI_REPO_SKILLSET` or `JSC_WIKI_REPO_CONTENTS`) and `JSC_WIKI_REPO`, ask per the `jsc-ask:ask` rules, then rerun |
|
||||||
|
| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash |
|
||||||
|
| | 2 | Empty input, which means the `{owner}/{repo}` was never resolved. Fix that first |
|
||||||
|
| Content page read | 0 | Append into the sections already there |
|
||||||
|
| | 4 | The page does not exist yet, so build it from the template |
|
||||||
|
| | 7 or 8 | Stop and write nothing, because a page rebuilt on top of an unread read loses every section already on it |
|
||||||
|
| `gitea.sh wiki-url` | 4 | The content page is not there, so the write above did **not** succeed. Go back and write it; put no directory block in until the page exists, because a block may not name a page that failed |
|
||||||
|
| | 5 | The API answered with no `html_url`. Stop and report it; never assemble the URL by hand from the host and the page name |
|
||||||
|
| `link-check.sh` | 0 | Every link is reachable. Write the page |
|
||||||
|
| | 1 | At least one link is dead. Write nothing, and report the `DEAD` lines it printed |
|
||||||
|
| | 2 | No URL was passed, which is a defect here. Pass the links and rerun |
|
||||||
|
| | 3 | `GITEA_HOST` is unset. Set it and rerun; never skip the check instead |
|
||||||
|
| | 7 | Gitea authentication failed. Stop and report the key problem, and never read it as a dead link |
|
||||||
|
| `wiki-contents.sh upsert` | 0 | The block is in place. Report the `updated` or `added` it printed, with the page it named |
|
||||||
|
| | 1 | The page content could not be assembled, or the write failed. A page with no matching block is not an error — that case appends. Report `SKILLSET_CONTENTS` as not written, together with the block content |
|
||||||
|
| | 2 | An argument was rejected. Fix it and rerun; nothing was written |
|
||||||
|
| | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set. The round's section is on `SKILLSET_{HASH}` and stays there |
|
||||||
|
| | 4 | The directory page is absent and no template was passed. Rerun with `templates/skillset-contents.md` as the fifth argument |
|
||||||
|
| | 7 | The token is invalid or lacks permission, so the other domains' blocks are unknown. Stop, report the token problem, and create no page — writing nothing is what keeps those blocks alive |
|
||||||
|
| | 8 | Some other API failure. Stop, report that status, and create no page |
|
||||||
|
- **On a failed write.** Retry once. Still failing, hand the user the page name and the full section that was not written, so the round's result is not lost. **Never close the run reporting a page as written when it was not**, and never close it silently with the content only in the transcript.
|
||||||
|
|
||||||
|
Completion condition: every changed domain repo has one new section on its `SKILLSET_{HASH}` and one `## SKILLSET_{HASH}` block in `SKILLSET_CONTENTS` written by a `wiki-contents.sh upsert` that exited 0, or — where nothing changed — the `plugins/meta` page carries the 「本輪無發現」 section and its block on the same terms; every content-page write is confirmed by a successful read-back or reported as not written with its full content handed back.
|
||||||
|
9. Report this round's outcome to the local event stream — the last step of every run, the ones that stop early included. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-check {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` from the `domain<TAB>path` row step 1 printed for the `hooks` domain, the same way this skill resolves every other cross-plugin script. **When that script is not on this machine, skip this step in silence and close the round as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the round actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | every domain ended with a complete checklist, `check-delegate.sh` exited 0 for the round, every accepted fix passed its re-check, every affected repo has a PR URL, and every wiki write exited 0 |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the round before anything was audited — `sync-domains.sh` never reached exit 0, or the call itself was refused |
|
||||||
|
| `failed` | the round broke mid-way — a re-check in step 6 kept failing, or a wiki write failed again after its one retry |
|
||||||
|
| `degraded` | the round finished with a part missing — a domain carries 「本輪未取得已決議清單,優化建議暫不提出」, the delegation check came back 「無委派清單可查」, or a content page was written while its `SKILLSET_CONTENTS` block was not |
|
||||||
|
| `aborted` | the user stopped the round, or a prerequisite turned out not to hold and this skill stopped on its own |
|
||||||
|
|
||||||
|
`{exit code}` is this round's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The audit itself happens in the model turns after that, so no hook can see how the round ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this round is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: skill-delete
|
name: skill-delete
|
||||||
description: Remove a skill from the jsc skill set safely. List skills from the Gitea canonical marketplace (cloning any missing domain repo) and let the user pick, inventory every file referencing the skill via a sub agent, fix each affected file through decision-tree questions until guideline checks pass, then delete the skill, verify the removal is clean, and open a PR via jsc-git pr. Use only for removal; not for renaming (use skill-update).
|
description: Remove a skill from the jsc skill set safely. Pick the skill from the Gitea canonical marketplace skill list, inventory every file referencing it, fix each affected file through decision-tree questions in parallel sub agents until guideline checks pass, delete the skill, open a PR via jsc-git pr, then deploy per references/deploy-verify.md and verify from a fresh CLI process that no on-disk leftover remains, and append the change report to wiki SKILLSET_{HASH}. Use only for removal; not for renaming (use skill-update).
|
||||||
---
|
---
|
||||||
|
|
||||||
# skill-delete — delete a skill
|
# skill-delete — delete a skill
|
||||||
@@ -9,14 +9,96 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
|
|||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. Query Gitea for the canonical skill set first: run `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json` to get the authoritative domain list, then scan local `jsc-*/skills/*/SKILL.md` and present a "domain / name / description" list covering every domain in the marketplace. Completion condition: the list covers all marketplace domains.
|
1. Run `tools/sync-domains.sh` to sync every domain repo of the Gitea canonical marketplace. Completion condition: the script exits 0 and prints one `domain<TAB>path` line per marketplace domain — exit 0 is the only code that means every repo is present and current. Exit 3 means some repos were not updated: reconcile every path named on stderr (commit or stash the dirty tree, or fix the failing pull) and rerun; when the user confirms a dirty tree is intentional local work, record that decision and continue on the local version — never read exit 3 as current. Exit 2 means a domain could not be cloned. Exit 1 means the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable; when stderr says the root could not be derived, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos and rerun, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there instead of the domain workspace. Resolve 2 and 1 before continuing.
|
||||||
2. For any marketplace domain whose repo is missing from the working directory, clone it first (`gitea.sh clone-url plugins/{domain}`), then rescan. Completion condition: every domain repo exists locally.
|
2. Run `tools/list-skills.sh` and present its `domain / name / description` rows to the user. The tool prints skills, not domains, so read the domain column to prove coverage. Exit 1 means the root could not be derived, the domain list was unreadable, or no skill was found — read stderr, fix the named cause (`JSC_PLUGINS_ROOT` for the root case, as in step 1) and rerun; never read it as an empty skill set. Completion condition: the script exits 0 and every domain printed by step 1 appears in at least one row; a domain with no row means its repo is missing or holds no skill — return to step 1 for that domain.
|
||||||
3. Let the user pick the skill to delete. Options state the impact scope: which skills reference it, and that its command stops working after deletion.
|
3. Let the user pick the skill to delete. Options state the impact scope: which skills reference it, and that its command stops working after deletion. Completion condition: one `{domain}/{name}` pair is confirmed for deletion.
|
||||||
4. Inventory every file related to the skill: run `tools/find-skill-refs.sh {domain} {name}` to list every file across all jsc-* repositories referencing the skill name or its `/jsc-{domain}:{name}` command form (covers other SKILL.md files, the domain README's 「Skills 目錄」 section, the two marketplace.json files in `plugins/meta` plus their synced copies in every domain repo, `tools/`, and the `jsc-hooks` wiring). Completion condition: the tool's file list is captured for step 5.
|
4. Inventory every file related to the skill: run `tools/find-skill-refs.sh {domain} {name}` to list every file that references the skill name or its `/jsc-{domain}:{name}` command form, across every marketplace domain repo on this machine (covers other SKILL.md files, the domain README's 「Skills 目錄」 section, the two marketplace.json files in `plugins/meta` plus their synced copies in every domain repo, `tools/`, and the `jsc-hooks` wiring). The skill-name pattern is a bare substring match, so the list also carries other skills whose name starts with the same word plus plain prose — treat it as candidates to read, not as files that must change. Exit 0 means hits were printed; exit 1 means a clean zero-hit scan; exit 2 means a usage error, so fix the two arguments and rerun; exit 3 means the scan never ran — the root could not be derived, the domain list was unreadable, no domain repo is on this machine, or grep failed. Read stderr and fix the named cause; when it names the root, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there. **Never read exit 3 as zero hits** — a failed scan taken as "no references" makes this skill skip files it must fix. Completion condition: the tool's file list is captured as the step 5 inventory.
|
||||||
5. For each affected file:
|
5. Fix every file in the step 4 inventory. For each file:
|
||||||
1. Decide whether the file needs a fix to keep its current behavior after the deletion. If no fix is needed, **skip the rest of this loop**.
|
1. Read the file and decide whether it needs a fix to keep its current behavior after the deletion. If no fix is needed, record it as no-fix-needed with the reason and **skip the rest of this loop**. Completion condition: the file carries a recorded verdict — needs-fix or no-fix-needed with a reason.
|
||||||
2. Ask for fix details via the `jsc-ask:ask` decision tree (call a replacement skill? move a deterministic input/output flow to `tools/`? run the detailed flow as a sub agent? drop the feature too?). If the fix touches wiki or Gitea access, confirm it reads inherited environment variables before asking the user. Every option states its impact scope.
|
2. Ask for fix details via the `jsc-ask:ask` decision tree (call a replacement skill? move a deterministic input/output flow to `tools/`? run the detailed flow as a sub agent? drop the feature too?). If the fix touches wiki or Gitea access, confirm it reads inherited environment variables before asking the user. Every option states its impact scope. Completion condition: every question has a recorded answer.
|
||||||
3. After fixing, check the guidelines.md audit checklist. On failure, return to step 5.2.
|
3. Apply the confirmed fix, then check the guidelines.md audit checklist for the file — the per-file fix work MUST run as a sub agent, one sub agent per affected domain repo, and those sub agents **run in parallel**: each repo's files are independent, so serialising them only adds waiting. Each sub agent reports one line per file: the path and either the applied fix or「無需修正」with the reason. On any checklist failure, return to step 5.2. Completion condition: the fix is in the file and every checklist item passes for it.
|
||||||
6. Delete the skill directory `skills/{name}/`, then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README and bump the version in all three manifests.
|
|
||||||
7. Deep-delete verification — this step MUST run as a sub agent: after deleting via each CLI's native plugin commands, physically inspect every installed CLI's on-disk skill and hook storage. Detect CLIs via `jsc-cli/tools/detect-clis.sh`; check Claude's `~/.claude/plugins/cache/` and hook entries in settings, plus the equivalent locations for codex / copilot / antigravity / kiro. Confirm no file or hook wiring for the deleted skill remains. Completion condition: every location checked and clean; remove any leftover by hand and recheck.
|
`jsc-meta/tools/delegate-spec.tsv` appears in that inventory as the row naming the skill. Mark it as handled in step 6 and edit it nowhere else: the row goes out there, together with the skill directory, and splitting the edit over two steps risks one of them removing a row the other still expects to be there.
|
||||||
8. Call `jsc-git:pr` to open a Push Request.
|
|
||||||
|
Completion condition: every file in the step 4 inventory is marked either fixed-with-a-clean-checklist, explicitly no-fix-needed with a reason, or deferred to step 6 as the delegation list is — no file is left without a verdict.
|
||||||
|
6. Delete the skill directory `skills/{name}/` and remove that skill's `## {name}` section from `references/behaviors.md` — the whole section, its table included, leaving every other section untouched. Both deletions ship in this same PR: a behavior list still carrying a deleted skill fails the domain's next audit, and the extra section is exactly what `check-behaviors.sh` reports. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README and bump the version in all three manifests. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path, `skills/`, `README.md`, the `JSC-SKILLS` markers, a remaining `SKILL.md`, a manifest, or a manifest `version` field is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs under `set -e`, so treat it as an environment fault and stop, never as a successful sync.
|
||||||
|
|
||||||
|
Delete that skill's row from `jsc-meta/tools/delegate-spec.tsv` in the same pass — that one row, every other row left byte for byte as it was. A list still carrying a deleted skill makes the background assistant trigger a skill that cannot be called, and a failing trigger does not pause itself: it retries every round, for good. Then repoint every remaining row whose `next` column named the deleted skill; those rows now name something that cannot be called either, and they are the second half of the same defect. In the same pass, check every remaining row's `probe` column against the files this deletion removed: a deletion that took a `tools/` script down with the skill leaves any row whose read-only command named that script pointing at nothing, and the assistant then fails that entry every round without ever pausing on it. Repoint such a row at a script that exists, or set it to `pending:{reason}` when this deletion left the slice with no read-only entry at all. The file lives in `plugins/meta` whichever domain lost the skill, so deleting a skill outside `meta` changes two repos and step 7 opens the second Push Request for this one.
|
||||||
|
|
||||||
|
**The task-book half is not wired yet.** [`../../references/delegate-criteria.md`](../../references/delegate-criteria.md) also asks this skill to drop the assistant task-book entries that name the deleted skill. That task book does not exist yet, so there is nothing to remove from and this skill does not go looking for it. When the task book ships, add that removal here as a step of its own. Until then, carry 「待辦簿引用尚未接線」 into the step 8.3 wiki section, so a later reader does not take this deletion as having cleaned a place it never touched.
|
||||||
|
|
||||||
|
Then run `tools/check-behaviors.sh {domain-path}` and route each exit code: 0 — the remaining sections match the remaining skills; 1 — every mismatch is printed on stderr as `{檔案}:{技能名}:{說明}`, so fix each one and rerun, the deleted skill's leftover section included; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, because `references/behaviors.md` is missing, `skills/` is missing, or no `SKILL.md` was found, so fix the named cause and rerun. **Exit 3 is never a pass.**
|
||||||
|
|
||||||
|
Then run `tools/check-delegate.sh`. It takes the plugins root, not a domain path, and the list is one file covering every domain, so it runs **once for the whole flow**. Route each exit code:
|
||||||
|
- 0 — the list matches the skills on this machine and every mandatory column is filled; the deleted skill has no row left, and no surviving row points at it. **A run that printed lines on stdout and exited 0 still passed.** Those lines are hints, not defects: `origin=seed` marks a row seeded from the earlier inventory and awaiting review, and a version-behind line marks a row whose `version` trails its domain's current one, a `probe=pending:` line marks a delegated slice whose read-only entry point is not wired yet, and a line saying a `probe` domain is not installed here marks a script this machine cannot check, which every skill of the domain this deletion just bumped will now show. Report them as hints and fix nothing for them; a deletion held open over a version-behind line would never close.
|
||||||
|
- 1 — a row remains for a skill this machine no longer has, a surviving row's `next` points at the deleted skill, or a surviving row's `probe` points at a script this deletion removed. All three are printed on stderr as `{清單路徑}:{domain}/{技能名}:{說明}` — the first is the row this step was supposed to remove, the second is a `next` this step was supposed to repoint, the third a `probe` this step was supposed to repoint or set to `pending:{reason}`. Fix each and rerun.
|
||||||
|
- 2 — usage error: the script takes at most one argument. Fix the call and rerun.
|
||||||
|
- 3 — nothing was checked, because `tools/delegate-spec.tsv` is missing, the root could not be derived, or `list-skills.sh` listed no skill. Read stderr and fix the named cause; set `JSC_PLUGINS_ROOT` to the directory holding the domain repos for the root case, as in step 1. **Exit 3 is never a pass** — a check that looked nowhere reports no leftover row either.
|
||||||
|
|
||||||
|
Completion condition: the directory is gone, `references/behaviors.md` holds no `## {name}` section for the deleted skill, `tools/delegate-spec.tsv` holds no row for it and no `next` naming it, `tools/check-behaviors.sh {domain-path}` and `tools/check-delegate.sh` both exit 0, the README's 「Skills 目錄」 no longer lists the skill, and all three manifests show the same new version.
|
||||||
|
7. Call `jsc-git:pr` to open a Push Request. When the deleted skill lived in a domain other than `meta`, the `tools/delegate-spec.tsv` removal is a change to `plugins/meta` and needs its own Push Request against that repo — two repos changed, two PRs, neither waiting on the other. Completion condition: a PR URL comes back for every repo this run changed, `plugins/meta` included when the row was removed there, and each is reported with the table format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
||||||
|
8. Deploy the deletion, verify it took, then report:
|
||||||
|
1. Follow [`../../references/deploy-verify.md`](../../references/deploy-verify.md) from section 1 to section 5: `tools/deploy-route.sh {domain-path}` picks the route, the deploy route or the worktree route runs, and the verification then runs in a **fresh CLI process**, never in the session that ran the deploy. That session raised the restart gate itself and still holds the old skill set, so verifying inside it either gets blocked or passes on stale behavior. On the worktree route, add that the skill stays installed and stays callable until the outstanding release PR merges. Completion condition: every completion condition in `deploy-verify.md` sections 1 to 5 holds for this domain repo.
|
||||||
|
2. Verify the deletion concretely, on top of the `deploy-verify.md` items:
|
||||||
|
- `tools/list-skills.sh` prints no row carrying the deleted `{domain}/{name}`.
|
||||||
|
- **Deep-delete verification.** Run `tools/verify-skill-removed.sh {domain} {name}`; it detects the installed CLIs and greps each one's skill cache and hook config. Route each exit code: exit 0 — no leftover in the locations listed on stderr; exit 1 — leftovers printed as `{file}:{line}:{content}`, so remove every one by hand and rerun; exit 2 — usage error, fix the two arguments and rerun; exit 3 — nothing was checkable, because the root could not be derived, `detect-clis.sh` was not found, no CLI was detected, or no config location exists. The script printed no leftover because it looked nowhere, so exit 3 is **never** clean: report「無處可查」with the reason from stderr, and carry that sentence into step 8.3's wiki section and the final report, so nobody later reads the deletion as verified on disk. This is the only place the deep-delete check runs — running it before the PR only scanned a deletion that had not been deployed yet, so it always came back clean and proved nothing.
|
||||||
|
- Every tool and skill that step 5 fixed still finishes with its documented exit code — a fix that broke a caller shows up here, not earlier.
|
||||||
|
- One minimal prompt per checkable CLI, each in its own fresh process and all in parallel, confirming `/jsc-{domain}:{name}` is gone or that the replacement path still works.
|
||||||
|
|
||||||
|
On any mismatch — the deleted skill still listed, a leftover from exit 1, a fixed caller that now fails, a prompt failure, or unexpected stderr — fix the cause and rerun this step from 8.1. Completion condition: the skill is absent from the list, the verification script exits 0 or its exit 3 is reported as「無處可查」and carried into step 8.3, every fixed caller ran, every checkable CLI completed the prompt with the expected result, and every untestable CLI has a stated reason.
|
||||||
|
3. Write the change report to the wiki — this part MUST run as a sub agent. It is two pages in two repos, and they must not be mixed up.
|
||||||
|
- **Content page `SKILLSET_{HASH}`.** Resolve its repo with `jsc-gitea/tools/gitea.sh wiki-repo SKILLSET`, which reads `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. `{HASH}` is `gitea.sh hash-id "{owner}/{repo}"` of the domain repo that lost the skill, used at the full 40 characters it prints. Write it through `jsc-gitea:wiki` following [`../../templates/skillset-page.md`](../../templates/skillset-page.md): **append** a section for this change — date, 「刪除」, skill name, changed files (the step 5 inventory verdicts included), PR URL, the step 8.1 route verdict and the step 8.2 verification result per item, the deep-delete verdict「無處可查」included when it applies, and the step 6 note 「待辦簿引用尚未接線」 — and keep every earlier section.
|
||||||
|
- **Directory page `SKILLSET_CONTENTS`.** It lives in the CONTENTS repo, never in the SKILLSET one. `wiki-contents.sh` resolves it itself with `gitea.sh wiki-repo CONTENTS`, whose chain is `JSC_WIKI_REPO_CONTENTS` then `JSC_WIKI_REPO` and never falls back to `JSC_WIKI_REPO_SKILLSET`. That page is a heading-plus-bullets list and holds no markdown table: one `## SKILLSET_{HASH}` block per domain repo, every field one `- {欄位名}:{值}` line under it. Build one file holding this domain's single block, following [`../../templates/skillset-contents.md`](../../templates/skillset-contents.md), with its 異動頁 bullet written as `[SKILLSET_{HASH}]({url})` from the **absolute** URL that `gitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}` prints. The H2 heading itself carries no link, no URL, no affix and no date — only the content page name. Every link on both pages takes that `[{text}]({url})` form; the double-bracket wiki-link form resolves only inside one wiki, so it is never used. Then run:
|
||||||
|
|
||||||
|
`jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {entry file} templates/skillset-contents.md`
|
||||||
|
|
||||||
|
The key is the H2 heading `SKILLSET_{HASH}`, so one domain keeps exactly one block and no other domain's block moves. That page name depends only on `{owner}/{repo}`, which is why it is the key: a host rename or a changed `JSC_WIKI_REPO_SKILLSET` leaves it untouched, so the match still finds the existing block. The `2` is the key column: the index of the column that held the content-page link in the **old markdown table**, and it matters only when such an old table still has to be converted automatically — the conversion takes the last path segment of that column's link URL as the H2 heading. Count that index from the **live page's own column layout**, never from the template's: the live `SKILLSET_CONTENTS` reads `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`, so the link sits in column 2 while column 1 is plain text like `plugins/ask`. Passing `1` would make the heading `plugins/ask`, which never matches the key `SKILLSET_{HASH}`, so the existing entry is appended as a brand-new one — one domain ends up with two blocks and the older one is never updated again. The fourth argument is the whole block, not a table row. Never hand-edit the directory page. Write the content page first and fetch the URL only after it exists.
|
||||||
|
- **Check the links before writing.** Hand every URL going onto the content page and into the directory block to `jsc-gitea/tools/link-check.sh`, and write only when it exits 0. It verifies through the Gitea API, never a web status code: a private repo answers 404 to an unauthenticated web request, so a status-code check would call a live page dead.
|
||||||
|
- **Exit codes.** Route every one of them:
|
||||||
|
|
||||||
|
| Call | Exit | Do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea.sh wiki-repo` | 2 | The page type was misspelled. Fix the argument and rerun |
|
||||||
|
| | 3 | No wiki repo is configured for that type. Name the variable (`JSC_WIKI_REPO_SKILLSET` for the content page, `JSC_WIKI_REPO_CONTENTS` for the directory page) and `JSC_WIKI_REPO`, ask per the `jsc-ask:ask` rules, then rerun |
|
||||||
|
| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash |
|
||||||
|
| | 2 | Empty input, so the `{owner}/{repo}` was never resolved. Fix that first |
|
||||||
|
| Content page read | 0 | Append into the sections already there |
|
||||||
|
| | 4 | The page does not exist yet, so build it from `templates/skillset-page.md` |
|
||||||
|
| | 7 or 8 | Stop and write nothing: a page rebuilt on top of an unread read loses every section already on it |
|
||||||
|
| `gitea.sh wiki-url` | 4 | The content page is not there, so the write above did **not** succeed. Go back and write it, and add no directory block until the page exists |
|
||||||
|
| | 5 | The API answered with no `html_url`. Stop and report it; never assemble the URL by hand from the host and the page name |
|
||||||
|
| `link-check.sh` | 0 | Every link is reachable. Write the page |
|
||||||
|
| | 1 | At least one link is dead. Write nothing, and report the `DEAD` lines it printed |
|
||||||
|
| | 2 | No URL was passed, which is a defect here. Pass the links and rerun |
|
||||||
|
| | 3 | `GITEA_HOST` is unset. Set it and rerun; never skip the check instead |
|
||||||
|
| | 7 | Gitea authentication failed. Stop and report the key problem, and never read it as a dead link |
|
||||||
|
| `wiki-contents.sh upsert` | 0 | The block is in place. Report the `updated` or `added` it printed |
|
||||||
|
| | 1 | The page content could not be assembled, or the write failed. Report `SKILLSET_CONTENTS` as not written, together with the block content |
|
||||||
|
| | 2 | An argument was rejected. Fix it and rerun; nothing was written |
|
||||||
|
| | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set; the new section is on `SKILLSET_{HASH}` and stays there |
|
||||||
|
| | 4 | The directory page is absent and no template was passed. Rerun with `templates/skillset-contents.md` as the fifth argument |
|
||||||
|
| | 7 | The token is invalid or lacks permission, so the other domains' blocks are unknown. Stop, report the token problem, and create no page |
|
||||||
|
| | 8 | Some other API failure. Stop, report that status, and create no page |
|
||||||
|
|
||||||
|
On any failure, hand the page name and the unwritten entry back to the user and leave this step open; never close the flow on an unwritten report. Completion condition: `SKILLSET_{HASH}` holds the new section plus all earlier sections, and `wiki-contents.sh upsert` exited 0 with this domain's `## SKILLSET_{HASH}` block on `SKILLSET_CONTENTS` linking that page by absolute URL.
|
||||||
|
9. Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-delete {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` from the `domain<TAB>path` row step 1 printed for the `hooks` domain, the same way this skill resolves every other cross-plugin script. **When that script is not on this machine, skip this step in silence and close the run as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the run actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | the skill directory, its behavior-list section and its `delegate-spec.tsv` row are gone, every PR this run needed is open, `deploy-verify.md` sections 1 to 5 hold, `verify-skill-removed.sh` and `check-delegate.sh` both exited 0, and both wiki writes exited 0 |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the run before any file changed — `sync-domains.sh` never reached exit 0, or no skill could be listed to pick from |
|
||||||
|
| `failed` | the run broke mid-way — a leftover from `verify-skill-removed.sh` exit 1 could not be removed, or a wiki write failed again after its one retry |
|
||||||
|
| `degraded` | the deletion landed with a part missing — the deep-delete check came back 「無處可查」, the skill's PR is open while the `delegate-spec.tsv` PR is not, or the content page was written while its `SKILLSET_CONTENTS` block was not |
|
||||||
|
| `aborted` | the user stopped the run, or a prerequisite turned out not to hold and this skill stopped on its own |
|
||||||
|
|
||||||
|
`{exit code}` is this run's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The deletion itself happens in the model turns after that, so no hook can see how the run ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this run is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|||||||
+102
-16
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: skill-new
|
name: skill-new
|
||||||
description: Create a new skill in the jsc skill set. Ask skill details via decision tree, generate the skill under the right jsc-{domain} per guidelines.md (create the domain from the template repo if missing), then open a PR via jsc-git pr. Use when the user wants to add a skill; not for editing an existing one (use skill-update).
|
description: Create a new skill in the jsc skill set. Prefetch the skill list and the domain list in parallel, ask skill details via decision tree, generate the skill under the right jsc-{domain} per guidelines.md (create the domain from the template repo if missing), open a PR via jsc-git pr, then deploy and verify per references/deploy-verify.md from a fresh CLI process, and append the change report to wiki SKILLSET_{HASH}. Use when the user wants to add a skill; not for editing an existing one (use skill-update).
|
||||||
---
|
---
|
||||||
|
|
||||||
# skill-new — create a skill
|
# skill-new — create a skill
|
||||||
@@ -9,19 +9,105 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
|
|||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. Ask for skill details via the `jsc-ask:ask` decision tree until no doubt remains:
|
1. Collect the decision tree's inputs first, then ask:
|
||||||
- Goal (single and not duplicating an existing skill; first scan `jsc-*/skills/*/SKILL.md` and list similar skills for comparison — options must state the impact scope of "reuse existing" versus "create new")
|
1. Prefetch both inputs — run `tools/list-skills.sh` and `tools/sync-domains.sh` **in parallel**. They share no data, and both answers are needed before the first question, so running them after the questions only makes the user wait. Route each exit code:
|
||||||
- Trigger (when to use, when not to, trigger keywords)
|
- `list-skills.sh` exit 0 — keep the `domain<TAB>name<TAB>description` rows. Exit 1 — the root could not be derived, the domain list was unreadable, or no skill was found; read stderr and fix the named cause. When stderr says the root could not be derived, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos and rerun: under a plugin install the script sits in the CLI's plugin cache, so its built-in guess lands in that cache instead of the domain workspace.
|
||||||
- Input and output (can a standard input/output flow move down to `tools/`; does it need Gitea operations — if so, make the skill use `jsc-gitea/tools/gitea.sh` + token)
|
- `sync-domains.sh` exit 0 — the only code that means every repo is present and current; keep the `domain<TAB>path` rows. Exit 3 — some repos were not updated: reconcile every path named on stderr (commit or stash the dirty tree, or fix the failing pull) and rerun; when the user confirms a dirty tree is intentional local work, record that decision and continue on the local version. Exit 2 — a domain could not be cloned. Exit 1 — the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable; for the root case set `JSC_PLUGINS_ROOT` as above and rerun. Resolve 2 and 1 before continuing.
|
||||||
- Owning domain (list the existing `jsc-*` domains to choose from)
|
|
||||||
2. If the domain does not exist (if the repository exists on Gitea but not in the working directory, clone it and skip to step 3):
|
|
||||||
1. Propose one short English word for the new domain (a single word preferred) and confirm it with the user.
|
|
||||||
2. Ask the user to create the repository `plugins/{domain}`. Clone it, then build the content following the structure of `https://gitea.jsc.idv.tw/plugins/template`: three plugin manifests (plugin name `jsc-{domain}`, version starting at `0.0.1`), `skills/`, README.md, AGENTS.md.
|
|
||||||
3. Add the plugin entry (URL source pointing at the new repository) to `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json` in `plugins/meta` (the canonical copy), then sync the two updated files to every domain repository, including the new one. Every repo carries the same marketplace files, so any repo works as the registration entry point. The sync MUST run as a sub agent.
|
|
||||||
3. Generate the skill per guidelines.md — this step MUST run as a sub agent:
|
|
||||||
- `skills/{name}/SKILL.md`: entirely in English (description ≤ 5 sentences with trigger conditions; body in STE100-style English)
|
|
||||||
- Rules enforceable by hooks go to `jsc-hooks` (never scattered in this domain); standard input/output flows go to `tools/`
|
|
||||||
|
|
||||||
Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README's 「Skills 目錄」 section and bump the version in all three manifests.
|
Completion condition: the skill rows and the `domain<TAB>path` rows are both in hand.
|
||||||
4. Self-check every item of the guidelines.md audit checklist; fix anything that fails.
|
2. Ask for skill details via the `jsc-ask:ask` decision tree until no doubt remains:
|
||||||
5. Call `jsc-git:pr` to open a Push Request.
|
- Goal (single and not duplicating an existing skill; show the similar skills from the step 1.1 rows for comparison — options must state the impact scope of "reuse existing" versus "create new")
|
||||||
|
- Trigger (when to use, when not to, trigger keywords)
|
||||||
|
- Input and output (can a standard input/output flow move down to `tools/`; does it need Gitea operations — if so, make the skill use `jsc-gitea/tools/gitea.sh` + token)
|
||||||
|
- Owning domain (offer the domain list from the step 1.1 `domain<TAB>path` rows — the domains registered in the canonical marketplace)
|
||||||
|
- Delegation verdict — the five decision-tree questions of [`../../references/delegate-criteria.md`](../../references/delegate-criteria.md), in the order that file lists them, plus a sixth question for the `next` column: which skill should run after this one, and a seventh for the `probe` column: which read-only command the assistant actually runs for the delegated slice. Ask all seven through this same `jsc-ask:ask` tree; never answer them from the model's own reading of the draft flow. Every option states its impact scope: a `full` verdict lets the background assistant run the skill unattended, a `slice` or `cond` verdict leaves the other half in the user's hands, `none` keeps the whole skill there. The `next` question applies to all four verdicts, `none` included — `none` says the assistant does not run this skill for the user, which says nothing about what should follow it — so offer the step 1.1 skill rows as its options and the answer then names a skill that exists.
|
||||||
|
|
||||||
|
The `probe` question is asked only when the `way` answer holds no `invoke`; a `way` containing `invoke`, and a `none` verdict, both take `-` without asking, because the assistant's action there is the skill itself and a command in that column would silently downgrade the whole delegation to a bare script run. When it is asked, settle three things per that same reference file and never by copying another row: which script, whether it takes a read-only flag, and how a failure is reported. Verify the script, the sub-command and the flag name in the owning repo before writing them down — a description of a script is not evidence the script exists. The flag matters most: the patrol round is unattended, and a wrong flag turns a read-only stocktake into something that writes. An entry that needs the network gets `pending:{reason}` this round rather than a command, because an expired key then fails or silently reports nothing on every round while a local read fails on none.
|
||||||
|
|
||||||
|
Completion condition: goal, trigger, input/output, owning domain and the delegation verdict each have a recorded answer, and the verdict carries a value for every column `delegate-criteria.md` marks mandatory for that verdict, `-` where it marks the column unused.
|
||||||
|
2. If the domain does not exist (`tools/sync-domains.sh` clones every domain **registered in the marketplace**, so a missing directory means the domain is unregistered — the repository itself may already exist on Gitea):
|
||||||
|
1. Propose one short English word for the new domain (a single word preferred) and confirm it with the user. Completion condition: the user confirms the domain word.
|
||||||
|
2. Check before creating: run `jsc-gitea/tools/gitea.sh clone-url plugins/{domain}`. A URL comes back when the repository already exists — clone it, skip creation, and go on to step 2.3 to fill in whatever content is missing. Only when no URL comes back create the repository through the tool, never by hand: `gitea.sh api POST /orgs/plugins/repos` when `plugins` is an organization, `POST /user/repos` when `plugins` is the token's own account (`tea repo create` does the same job). Only when the call is refused (403 — the token has write but not admin rights on the owner) ask the user to create `plugins/{domain}` by hand, then continue. Completion condition: `gitea.sh clone-url plugins/{domain}` prints a URL and cloning it succeeds.
|
||||||
|
3. Build the content following the structure of `https://gitea.jsc.idv.tw/plugins/template`: three plugin manifests (plugin name `jsc-{domain}`, version starting at `0.0.1`), `skills/`, README.md, AGENTS.md. Completion condition: the three manifests, `skills/`, README.md and AGENTS.md all exist in the new repo.
|
||||||
|
4. Register the plugin: run `tools/sync-marketplace.sh {domain} {repo-url} {description}`. It needs `python3` on PATH — it edits the marketplace JSON with the json module. It writes the entry into both canonical marketplace files in `plugins/meta` and copies both into every domain repo, so any repo works as the registration entry point. Route each exit code:
|
||||||
|
- Exit 3 — written, but some domain repo is not present locally. Run `tools/sync-domains.sh`, then rerun this step.
|
||||||
|
- Exit 2 — usage error. Fix the three arguments and rerun.
|
||||||
|
- Exit 1 — the root could not be derived, python3 is missing, a canonical file was unreadable, or copies differ byte for byte. Read stderr and fix the named cause: install python3 for the second; for the root case set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there. Then rerun.
|
||||||
|
- Exit 0 — every copy holds identical bytes; the script verifies that itself.
|
||||||
|
|
||||||
|
Completion condition: the script exits 0 and prints the touched paths.
|
||||||
|
3. Generate the skill per guidelines.md — this step MUST run as a sub agent:
|
||||||
|
- `skills/{name}/SKILL.md`: entirely in English (description within either cap — ≤ 5 sentences or ≤ 5 steps — and stating when to use and when not to; body in STE100-style English)
|
||||||
|
- Rules enforceable by hooks go to `jsc-hooks` (never scattered in this domain); standard input/output flows go to `tools/`
|
||||||
|
- `jsc-meta/tools/delegate-spec.tsv`: append this skill's row, built from the step 1.2 verdict — one skill one row, the twelve tab-separated columns in the order that file's header lists, `probe` last. A column the verdict does not use holds a single `-`; an empty cell and a cell holding a space both fail the checker. Write `probe` as the header describes: the script path starts at `{root}/jsc-{domain}/`, `{cli}` and `{repo}` are the only other substitution points, no dollar sign and no tilde, and any read-only flag goes in front of the command. `origin` is `judged`, because the verdict came from the decision tree in this same run, and `version` is the version the three manifests carry after the `sync-skill-manifest.sh` run below. **A skill with no row is not created.** The row is the only thing that tells the background assistant this skill exists, so without it every later round is blind to it, and no later step recreates it. The file lives in `plugins/meta` whichever domain gained the skill, so a skill added to another domain changes two repos and step 5 opens the second Push Request for this one.
|
||||||
|
- `references/behaviors.md`: add one `## {name}` section for the new skill, placed in dictionary order among the existing sections, carrying the five rows the guidelines' 「技能行為清單」 section defines — 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象. Write what the skill really does; do not copy the `description`. A read-only skill still fills 可驗證跡象 with 「無寫入跡象,只有回報內容」. The behavior list ships in this same PR — a skill added without its section leaves the domain's list out of sync the moment this PR merges. When the domain has no `references/behaviors.md` yet, create it with the header line `# jsc-{domain} 技能行為清單`.
|
||||||
|
|
||||||
|
Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README's 「Skills 目錄」 section and bump the version in all three manifests. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path, `skills/`, `README.md`, the `JSC-SKILLS` markers, a `SKILL.md`, a manifest, or a manifest `version` field is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs under `set -e`, so treat it as an environment fault and stop, never as a successful sync. Completion condition: `skills/{name}/SKILL.md` exists, `references/behaviors.md` holds a `## {name}` section with all five rows filled, `tools/delegate-spec.tsv` holds this skill's row with every mandatory column filled, the README lists the skill, and all three manifests show the same new version.
|
||||||
|
4. Self-check every item of the guidelines.md audit checklist; fix anything that fails. Run `tools/check-behaviors.sh {domain-path}` for the behavior-list item instead of comparing by eye, and route each exit code: 0 — the list matches `skills/` and all five rows are filled; 1 — every mismatch is printed on stderr as `{檔案}:{技能名}:{說明}`, so fix each one and rerun; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, because `references/behaviors.md` is missing, `skills/` is missing, or no `SKILL.md` was found, so create the missing file and rerun. **Exit 3 is never a pass.**
|
||||||
|
|
||||||
|
Then run `tools/check-delegate.sh` for the delegation-list item. It takes the plugins root, not a domain path, and the list is one file covering every domain, so it runs **once for the whole flow** — a second run per domain checks the same file again and reports the same lines. Route each exit code:
|
||||||
|
- 0 — the list matches the skills on this machine and every mandatory column is filled. **A run that printed lines on stdout and exited 0 still passed.** Those lines are hints, not defects: `origin=seed` marks a row seeded from the earlier inventory and awaiting review, and a version-behind line marks a row whose `version` trails its domain's current one, a `probe=pending:` line marks a delegated slice whose read-only entry point is not wired yet, and a line saying a `probe` domain is not installed here marks a script this machine cannot check. The version number is per domain, so bumping one skill's domain marks every other skill in it — reading those lines as failures paints the whole domain red on every release until nobody reads them at all. Report the hints, fix nothing for them, and treat this item as passed.
|
||||||
|
- 1 — a missing row, a duplicate row, a row for a skill this machine does not have, an empty column, a column value outside its vocabulary, a `next` pointing at a skill that does not exist, or a `probe` in the wrong shape — a command on a row whose `way` holds `invoke`, a `-` on a row whose `way` holds only `patrol` or `remind`, a dollar sign or tilde, an unknown substitution point, or a script that does not exist. Every one is printed on stderr as `{清單路徑}:{domain}/{技能名}:{說明}`; fix each and rerun. The new skill's own missing row is the expected finding when step 3 skipped its write, and the fix is that write, not an edit here.
|
||||||
|
- 2 — usage error: the script takes at most one argument. Fix the call and rerun.
|
||||||
|
- 3 — nothing was checked, because `tools/delegate-spec.tsv` is missing, the root could not be derived, or `list-skills.sh` listed no skill. Read stderr and fix the named cause; set `JSC_PLUGINS_ROOT` to the directory holding the domain repos for the root case, as in step 1.1. **Exit 3 is never a pass** — it means the check looked nowhere, so a new skill with no row would sail through it.
|
||||||
|
|
||||||
|
Completion condition: every checklist item passes, `tools/check-behaviors.sh {domain-path}` exits 0, and `tools/check-delegate.sh` exits 0 with the new skill's row present, its hint lines reported as hints.
|
||||||
|
5. Call `jsc-git:pr` to open a Push Request. When the new skill went into a domain other than `meta`, the `tools/delegate-spec.tsv` row is a change to `plugins/meta` and needs its own Push Request against that repo — two repos changed, two PRs, neither waiting on the other. Completion condition: a PR URL comes back for every repo this run changed, `plugins/meta` included when the row landed there, and each is reported with the table format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
||||||
|
6. Deploy the new skill, verify it runs, then report:
|
||||||
|
1. Follow [`../../references/deploy-verify.md`](../../references/deploy-verify.md) from section 1 to section 5: `tools/deploy-route.sh {domain-path}` picks the route, the deploy route or the worktree route runs, and the verification then runs in a **fresh CLI process**, never in the session that ran the deploy. That session raised the restart gate itself and still holds the old skill body, so verifying inside it either gets blocked or passes on stale behavior. Verify the added skill's row in `tools/list-skills.sh`, every tool the skill added, and one minimal prompt per checkable CLI — the per-CLI prompts run in parallel. Completion condition: every completion condition in `deploy-verify.md` sections 1 to 5 holds for this domain repo.
|
||||||
|
2. Write the change report to the wiki — this part MUST run as a sub agent. It is two pages in two repos, and they must not be mixed up.
|
||||||
|
- **Content page `SKILLSET_{HASH}`.** Resolve its repo with `jsc-gitea/tools/gitea.sh wiki-repo SKILLSET`, which reads `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. `{HASH}` is `gitea.sh hash-id "{owner}/{repo}"` of the domain repo that gained the skill, used at the full 40 characters it prints. Write it through `jsc-gitea:wiki` following [`../../templates/skillset-page.md`](../../templates/skillset-page.md): **append** a section for this change — date, 「新增」, skill name, changed files, PR URL, the step 6.1 route verdict and verification result per item — and keep every earlier section.
|
||||||
|
- **Directory page `SKILLSET_CONTENTS`.** It lives in the CONTENTS repo, never in the SKILLSET one. `wiki-contents.sh` resolves it itself with `gitea.sh wiki-repo CONTENTS`, whose chain is `JSC_WIKI_REPO_CONTENTS` then `JSC_WIKI_REPO` and never falls back to `JSC_WIKI_REPO_SKILLSET`. That page is a heading-plus-bullets list and holds no markdown table: one `## SKILLSET_{HASH}` block per domain repo, every field one `- {欄位名}:{值}` line under it. Build one file holding this domain's single block, following [`../../templates/skillset-contents.md`](../../templates/skillset-contents.md), with its 異動頁 bullet written as `[SKILLSET_{HASH}]({url})` from the **absolute** URL that `gitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}` prints. The H2 heading itself carries no link, no URL, no affix and no date — only the content page name. Every link on both pages takes that `[{text}]({url})` form; the double-bracket wiki-link form resolves only inside one wiki, so it is never used. Then run:
|
||||||
|
|
||||||
|
`jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {entry file} templates/skillset-contents.md`
|
||||||
|
|
||||||
|
The key is the H2 heading `SKILLSET_{HASH}`, so one domain keeps exactly one block and no other domain's block moves. That page name depends only on `{owner}/{repo}`, which is why it is the key: a host rename or a changed `JSC_WIKI_REPO_SKILLSET` leaves it untouched, so the match still finds the existing block. The `2` is the key column: the index of the column that held the content-page link in the **old markdown table**, and it matters only when such an old table still has to be converted automatically — the conversion takes the last path segment of that column's link URL as the H2 heading. Count that index from the **live page's own column layout**, never from the template's: the live `SKILLSET_CONTENTS` reads `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`, so the link sits in column 2 while column 1 is plain text like `plugins/ask`. Passing `1` would make the heading `plugins/ask`, which never matches the key `SKILLSET_{HASH}`, so the existing entry is appended as a brand-new one — one domain ends up with two blocks and the older one is never updated again. The fourth argument is the whole block, not a table row. Never hand-edit the directory page. Write the content page first and fetch the URL only after it exists.
|
||||||
|
- **Check the links before writing.** Hand every URL going onto the content page and into the directory block to `jsc-gitea/tools/link-check.sh`, and write only when it exits 0. It verifies through the Gitea API, never a web status code: a private repo answers 404 to an unauthenticated web request, so a status-code check would call a live page dead.
|
||||||
|
- **Exit codes.** Route every one of them:
|
||||||
|
|
||||||
|
| Call | Exit | Do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea.sh wiki-repo` | 2 | The page type was misspelled. Fix the argument and rerun |
|
||||||
|
| | 3 | No wiki repo is configured for that type. Name the variable (`JSC_WIKI_REPO_SKILLSET` for the content page, `JSC_WIKI_REPO_CONTENTS` for the directory page) and `JSC_WIKI_REPO`, ask per the `jsc-ask:ask` rules, then rerun |
|
||||||
|
| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash |
|
||||||
|
| | 2 | Empty input, so the `{owner}/{repo}` was never resolved. Fix that first |
|
||||||
|
| Content page read | 0 | Append into the sections already there |
|
||||||
|
| | 4 | The page does not exist yet, so build it from `templates/skillset-page.md` |
|
||||||
|
| | 7 or 8 | Stop and write nothing: a page rebuilt on top of an unread read loses every section already on it |
|
||||||
|
| `gitea.sh wiki-url` | 4 | The content page is not there, so the write above did **not** succeed. Go back and write it, and add no directory block until the page exists |
|
||||||
|
| | 5 | The API answered with no `html_url`. Stop and report it; never assemble the URL by hand from the host and the page name |
|
||||||
|
| `link-check.sh` | 0 | Every link is reachable. Write the page |
|
||||||
|
| | 1 | At least one link is dead. Write nothing, and report the `DEAD` lines it printed |
|
||||||
|
| | 2 | No URL was passed, which is a defect here. Pass the links and rerun |
|
||||||
|
| | 3 | `GITEA_HOST` is unset. Set it and rerun; never skip the check instead |
|
||||||
|
| | 7 | Gitea authentication failed. Stop and report the key problem, and never read it as a dead link |
|
||||||
|
| `wiki-contents.sh upsert` | 0 | The block is in place. Report the `updated` or `added` it printed |
|
||||||
|
| | 1 | The page content could not be assembled, or the write failed. Report `SKILLSET_CONTENTS` as not written, together with the block content |
|
||||||
|
| | 2 | An argument was rejected. Fix it and rerun; nothing was written |
|
||||||
|
| | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set; the new section is on `SKILLSET_{HASH}` and stays there |
|
||||||
|
| | 4 | The directory page is absent and no template was passed. Rerun with `templates/skillset-contents.md` as the fifth argument |
|
||||||
|
| | 7 | The token is invalid or lacks permission, so the other domains' blocks are unknown. Stop, report the token problem, and create no page |
|
||||||
|
| | 8 | Some other API failure. Stop, report that status, and create no page |
|
||||||
|
|
||||||
|
On any failure, hand the page name and the unwritten entry back to the user and leave this step open; never close the flow on an unwritten report. Completion condition: `SKILLSET_{HASH}` holds the new section plus all earlier sections, and `wiki-contents.sh upsert` exited 0 with this domain's `## SKILLSET_{HASH}` block on `SKILLSET_CONTENTS` linking that page by absolute URL.
|
||||||
|
7. Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-new {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` from the `domain<TAB>path` row step 1.1 printed for the `hooks` domain, the same way this skill resolves every other cross-plugin script. **When that script is not on this machine, skip this step in silence and close the run as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the run actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | the new `SKILL.md`, its behavior-list section and its `delegate-spec.tsv` row are in place, the checklist passes, every PR this run needed is open, `deploy-verify.md` sections 1 to 5 hold, and both wiki writes exited 0 |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the run before any file was created — `sync-domains.sh` never reached exit 0, or Gitea refused the repository creation and nobody created it by hand |
|
||||||
|
| `failed` | the run broke mid-way — `sync-marketplace.sh` or `sync-skill-manifest.sh` kept failing, or a wiki write failed again after its one retry |
|
||||||
|
| `degraded` | the skill landed with a part missing — the content page was written while its `SKILLSET_CONTENTS` block was not, the skill's PR is open while the `delegate-spec.tsv` PR is not, or a CLI could not be verified and the reason was recorded |
|
||||||
|
| `aborted` | the user stopped the run, or a prerequisite turned out not to hold and this skill stopped on its own |
|
||||||
|
|
||||||
|
`{exit code}` is this run's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The creation itself happens in the model turns after that, so no hook can see how the run ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this run is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: skill-update
|
name: skill-update
|
||||||
description: Update an existing skill in the jsc skill set. List all skills from the Gitea canonical marketplace (cloning any missing domain repo) and let the user pick one, ask update details via decision tree, apply the change, then re-check against the guidelines checklist until it passes and open a PR via jsc-git pr. Use for modifying a skill; not for creating (skill-new) or removing (skill-delete).
|
description: Update one existing skill in the jsc skill set. List all skills from the Gitea canonical marketplace (cloning any missing domain repo) and let the user pick one, ask update details via decision tree, apply the change, re-check against the guidelines checklist until it passes, open a PR via jsc-git pr, then deploy and verify per references/deploy-verify.md from a fresh CLI process, and append the change report to wiki SKILLSET_{HASH}. Use for modifying a single skill; not for creating (skill-new), not for removing (skill-delete), and not for a change spanning several skills or domains (skillset-update).
|
||||||
---
|
---
|
||||||
|
|
||||||
# skill-update — update a skill
|
# skill-update — update a skill
|
||||||
@@ -9,10 +9,78 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
|
|||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. Query Gitea for the canonical skill set first: run `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json` to get the authoritative domain list, then scan local `jsc-*/skills/*/SKILL.md` and present a "domain / name / description" list covering every domain in the marketplace. Completion condition: the list covers all marketplace domains.
|
1. Run `tools/sync-domains.sh` to sync every domain repo of the Gitea canonical marketplace. Completion condition: the script exits 0 and prints one `domain<TAB>path` line per marketplace domain — exit 0 is the only code that means every repo is present and current. Exit 3 means some repos were not updated: reconcile every path named on stderr (commit or stash the dirty tree, or fix the failing pull) and rerun; when the user confirms a dirty tree is intentional local work, record that decision and continue on the local version — never read exit 3 as current. Exit 2 means a domain could not be cloned. Exit 1 means the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable; when stderr says the root could not be derived, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos and rerun, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there instead of the domain workspace. Resolve 2 and 1 before continuing.
|
||||||
2. For any marketplace domain whose repo is missing from the working directory, clone it first (`gitea.sh clone-url plugins/{domain}`), then rescan. Completion condition: every domain repo exists locally.
|
2. Run `tools/list-skills.sh` and present its `domain / name / description` rows to the user. The tool prints skills, not domains, so read the domain column to prove coverage. Exit 1 means the root could not be derived, the domain list was unreadable, or no skill was found — read stderr, fix the named cause (`JSC_PLUGINS_ROOT` for the root case, as in step 1) and rerun; never read it as an empty skill set. Completion condition: the script exits 0 and every domain printed by step 1 appears in at least one row; a domain with no row means its repo is missing or holds no skill — return to step 1 for that domain.
|
||||||
3. Let the user pick the skill to update.
|
3. Let the user pick the skill to update. Completion condition: one `{domain}/{name}` pair is confirmed.
|
||||||
4. Ask for update details via the `jsc-ask:ask` decision tree (change the goal? the trigger? the flow? move rules down to a hook or a tool?). Every option states its impact scope (example: renaming breaks the existing invocation command).
|
4. Ask for update details via the `jsc-ask:ask` decision tree (change the goal? the trigger? the flow? move rules down to a hook or a tool?). Every option states its impact scope (example: renaming breaks the existing invocation command). Completion condition: every question has a recorded answer.
|
||||||
5. Update the skill — the modification part MUST run as a sub agent: modify SKILL.md and related files. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README's 「Skills 目錄」 section and bump the version in all three manifests.
|
|
||||||
6. Check every item of the guidelines.md audit checklist. On any failure, **return to step 4**: ask again and fix, until all items pass.
|
Settle the delegation verdict in the same tree, before any file is touched. A change that touches the **flow** or the **`description`** re-runs the whole decision tree of [`../../references/delegate-criteria.md`](../../references/delegate-criteria.md) — all five questions, the `next` question and the `probe` question — and produces a fresh verdict. The `probe` question is re-asked even when the verdict itself comes back unchanged: a skill that switched which script it calls, or that gained a read-only flag it did not have, leaves that column naming something the assistant can no longer run, and the reference file's three points settle it — which script, whether it takes a read-only flag, how a failure is reported — each verified in the owning repo rather than copied from the old value. Skipping that leaves a skill that just turned from read-only into file-writing sitting on its old verdict, and the background assistant keeps triggering it on a description of behavior it no longer has. A change that only rewrites wording and touches no behavior may keep the recorded verdict; then step 5 moves the row's `version` alone and the reuse is stated in the report, never left silent. Every option states its impact scope, this one included: reusing a verdict wrongly is the one failure this flow cannot detect later, because the row still looks complete. Completion condition: the run holds either a fresh verdict with a value in every column `delegate-criteria.md` marks mandatory for it, or a recorded decision to reuse the existing verdict together with the reason it changed no behavior.
|
||||||
7. Call `jsc-git:pr` to open a Push Request.
|
5. Update the skill — the modification part MUST run as a sub agent: modify SKILL.md and related files. In the same pass, update this skill's `## {name}` section in `references/behaviors.md` so its five rows — 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象 — describe the new behavior. A renamed skill gets its section renamed and moved back into dictionary order. In the same pass, update this skill's row in `jsc-meta/tools/delegate-spec.tsv` from the step 4 answer: a re-judged skill has every column rewritten from the fresh verdict with `origin` set to `judged`; a reused verdict keeps its columns and its `origin` untouched. Either way the `version` column moves to the version the manifests carry after the `sync-skill-manifest.sh` run below — a row left on the old version reads as never revisited, and the next audit reports it as pending re-judgement. The reuse itself is **not** recorded in the row: the twelve columns hold no note column and a thirteenth column fails the checker, so state it in the PR description and in the step 8.2 wiki section as 「沿用前一輪判定」 with the date that judgement was made. A reused verdict still gets its `probe` column re-checked against the files this run touched — a renamed or removed script leaves that column naming something the assistant cannot run, and the checker reports it as a missing script. A renamed skill also has its row's `name` column renamed, and every other row whose `next` named the old name is repointed in the same edit — those rows now name a skill that cannot be called, and the assistant retries such a name instead of pausing on it. The file lives in `plugins/meta` whichever domain owns the skill, so updating a skill outside `meta` changes two repos. The behavior list ships in this same PR: a behavior change that lands without its section makes the domain's list wrong from the merge onward, and the next audit reports drift this step created. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README's 「Skills 目錄」 section and bump the version in all three manifests. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path, `skills/`, `README.md`, the `JSC-SKILLS` markers, a `SKILL.md`, a manifest, or a manifest `version` field is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs under `set -e`, so treat it as an environment fault and stop, never as a successful sync. Completion condition: the skill files carry the change, the skill's `references/behaviors.md` section states the new behavior with all five rows filled, its `tools/delegate-spec.tsv` row carries the fresh verdict or the reused one with a moved `version`, and all three manifests show the same new version.
|
||||||
|
6. Check every item of the guidelines.md audit checklist. Run `tools/check-behaviors.sh {domain-path}` for the behavior-list item instead of comparing by eye, and route each exit code: 0 — the list matches `skills/` and all five rows are filled; 1 — every mismatch is printed on stderr as `{檔案}:{技能名}:{說明}`, so fix each one and rerun; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, because `references/behaviors.md` is missing, `skills/` is missing, or no `SKILL.md` was found, so create the missing file and rerun. **Exit 3 is never a pass.**
|
||||||
|
|
||||||
|
Then run `tools/check-delegate.sh` for the delegation-list item. It takes the plugins root, not a domain path, and the list is one file covering every domain, so it runs **once for the whole flow**. Route each exit code:
|
||||||
|
- 0 — the list matches the skills on this machine and every mandatory column is filled. **A run that printed lines on stdout and exited 0 still passed.** Those lines are hints, not defects: `origin=seed` marks a row seeded from the earlier inventory and awaiting review, and a version-behind line marks a row whose `version` trails its domain's current one, a `probe=pending:` line marks a delegated slice whose read-only entry point is not wired yet, and a line saying a `probe` domain is not installed here marks a script this machine cannot check. The version number is per domain, so bumping one skill's domain marks every other skill in it — reading those lines as failures paints the whole domain red on every release until nobody reads them at all. Report the hints and treat this item as passed. The one hint worth acting on here is a version-behind line naming **the skill this run just changed**: that row's `version` was supposed to move in step 5, so go back and move it.
|
||||||
|
- 1 — a missing row, a duplicate row, a row for a skill this machine does not have, an empty column, a column value outside its vocabulary, a `next` pointing at a skill that does not exist, or a `probe` in the wrong shape — a command on a row whose `way` holds `invoke`, a `-` on a row whose `way` holds only `patrol` or `remind`, a dollar sign or tilde, an unknown substitution point, or a script that does not exist. Every one is printed on stderr as `{清單路徑}:{domain}/{技能名}:{說明}`; fix each and rerun. A rename that left the old name behind lands here twice — once as a stale row, once as another row's dead `next`.
|
||||||
|
- 2 — usage error: the script takes at most one argument. Fix the call and rerun.
|
||||||
|
- 3 — nothing was checked, because `tools/delegate-spec.tsv` is missing, the root could not be derived, or `list-skills.sh` listed no skill. Read stderr and fix the named cause; set `JSC_PLUGINS_ROOT` to the directory holding the domain repos for the root case, as in step 1. **Exit 3 is never a pass.**
|
||||||
|
|
||||||
|
On any failure, **return to step 4**: ask again and fix, until all items pass. Completion condition: every checklist item passes, `tools/check-behaviors.sh {domain-path}` exits 0, and `tools/check-delegate.sh` exits 0 with its hint lines reported as hints.
|
||||||
|
7. Call `jsc-git:pr` to open a Push Request. When the updated skill lives in a domain other than `meta`, the `tools/delegate-spec.tsv` row is a change to `plugins/meta` and needs its own Push Request against that repo — two repos changed, two PRs, neither waiting on the other. Completion condition: a PR URL comes back for every repo this run changed, `plugins/meta` included when the row landed there, and each is reported with the table format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
||||||
|
8. Deploy the update, verify it runs, then report:
|
||||||
|
1. Follow [`../../references/deploy-verify.md`](../../references/deploy-verify.md) from section 1 to section 5: `tools/deploy-route.sh {domain-path}` picks the route, the deploy route or the worktree route runs, and the verification then runs in a **fresh CLI process**, never in the session that ran the deploy. That session raised the restart gate itself and still holds the old skill body, so verifying inside it either gets blocked or passes on stale behavior. Verify the updated `description` in the skill's `tools/list-skills.sh` row, every tool this change touched, and one minimal prompt per checkable CLI — the per-CLI prompts run in parallel. Completion condition: every completion condition in `deploy-verify.md` sections 1 to 5 holds for this domain repo.
|
||||||
|
2. Write the change report to the wiki — this part MUST run as a sub agent. It is two pages in two repos, and they must not be mixed up.
|
||||||
|
- **Content page `SKILLSET_{HASH}`.** Resolve its repo with `jsc-gitea/tools/gitea.sh wiki-repo SKILLSET`, which reads `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. `{HASH}` is `gitea.sh hash-id "{owner}/{repo}"` of the changed domain repo, used at the full 40 characters it prints. Write it through `jsc-gitea:wiki` following [`../../templates/skillset-page.md`](../../templates/skillset-page.md): **append** a section for this change — date, 「更新」, skill name, changed files, PR URL, the step 8.1 route verdict and verification result per item, and the delegation verdict this run recorded: the fresh verdict with its columns, or 「沿用前一輪判定」 with the date of the judgement being reused. The row itself has no room for that note, so this section is the only place it is kept — and keep every earlier section.
|
||||||
|
- **Directory page `SKILLSET_CONTENTS`.** It lives in the CONTENTS repo, never in the SKILLSET one. `wiki-contents.sh` resolves it itself with `gitea.sh wiki-repo CONTENTS`, whose chain is `JSC_WIKI_REPO_CONTENTS` then `JSC_WIKI_REPO` and never falls back to `JSC_WIKI_REPO_SKILLSET`. That page is a heading-plus-bullets list and holds no markdown table: one `## SKILLSET_{HASH}` block per domain repo, every field one `- {欄位名}:{值}` line under it. Build one file holding this domain's single block, following [`../../templates/skillset-contents.md`](../../templates/skillset-contents.md), with its 異動頁 bullet written as `[SKILLSET_{HASH}]({url})` from the **absolute** URL that `gitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}` prints. The H2 heading itself carries no link, no URL, no affix and no date — only the content page name. Every link on both pages takes that `[{text}]({url})` form; the double-bracket wiki-link form resolves only inside one wiki, so it is never used. Then run:
|
||||||
|
|
||||||
|
`jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {entry file} templates/skillset-contents.md`
|
||||||
|
|
||||||
|
The key is the H2 heading `SKILLSET_{HASH}`, so one domain keeps exactly one block and no other domain's block moves. That page name depends only on `{owner}/{repo}`, which is why it is the key: a host rename or a changed `JSC_WIKI_REPO_SKILLSET` leaves it untouched, so the match still finds the existing block. The `2` is the key column: the index of the column that held the content-page link in the **old markdown table**, and it matters only when such an old table still has to be converted automatically — the conversion takes the last path segment of that column's link URL as the H2 heading. Count that index from the **live page's own column layout**, never from the template's: the live `SKILLSET_CONTENTS` reads `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`, so the link sits in column 2 while column 1 is plain text like `plugins/ask`. Passing `1` would make the heading `plugins/ask`, which never matches the key `SKILLSET_{HASH}`, so the existing entry is appended as a brand-new one — one domain ends up with two blocks and the older one is never updated again. The fourth argument is the whole block, not a table row. Never hand-edit the directory page. Write the content page first and fetch the URL only after it exists.
|
||||||
|
- **Check the links before writing.** Hand every URL going onto the content page and into the directory block to `jsc-gitea/tools/link-check.sh`, and write only when it exits 0. It verifies through the Gitea API, never a web status code: a private repo answers 404 to an unauthenticated web request, so a status-code check would call a live page dead.
|
||||||
|
- **Exit codes.** Route every one of them:
|
||||||
|
|
||||||
|
| Call | Exit | Do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea.sh wiki-repo` | 2 | The page type was misspelled. Fix the argument and rerun |
|
||||||
|
| | 3 | No wiki repo is configured for that type. Name the variable (`JSC_WIKI_REPO_SKILLSET` for the content page, `JSC_WIKI_REPO_CONTENTS` for the directory page) and `JSC_WIKI_REPO`, ask per the `jsc-ask:ask` rules, then rerun |
|
||||||
|
| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash |
|
||||||
|
| | 2 | Empty input, so the `{owner}/{repo}` was never resolved. Fix that first |
|
||||||
|
| Content page read | 0 | Append into the sections already there |
|
||||||
|
| | 4 | The page does not exist yet, so build it from `templates/skillset-page.md` |
|
||||||
|
| | 7 or 8 | Stop and write nothing: a page rebuilt on top of an unread read loses every section already on it |
|
||||||
|
| `gitea.sh wiki-url` | 4 | The content page is not there, so the write above did **not** succeed. Go back and write it, and add no directory block until the page exists |
|
||||||
|
| | 5 | The API answered with no `html_url`. Stop and report it; never assemble the URL by hand from the host and the page name |
|
||||||
|
| `link-check.sh` | 0 | Every link is reachable. Write the page |
|
||||||
|
| | 1 | At least one link is dead. Write nothing, and report the `DEAD` lines it printed |
|
||||||
|
| | 2 | No URL was passed, which is a defect here. Pass the links and rerun |
|
||||||
|
| | 3 | `GITEA_HOST` is unset. Set it and rerun; never skip the check instead |
|
||||||
|
| | 7 | Gitea authentication failed. Stop and report the key problem, and never read it as a dead link |
|
||||||
|
| `wiki-contents.sh upsert` | 0 | The block is in place. Report the `updated` or `added` it printed |
|
||||||
|
| | 1 | The page content could not be assembled, or the write failed. Report `SKILLSET_CONTENTS` as not written, together with the block content |
|
||||||
|
| | 2 | An argument was rejected. Fix it and rerun; nothing was written |
|
||||||
|
| | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set; the new section is on `SKILLSET_{HASH}` and stays there |
|
||||||
|
| | 4 | The directory page is absent and no template was passed. Rerun with `templates/skillset-contents.md` as the fifth argument |
|
||||||
|
| | 7 | The token is invalid or lacks permission, so the other domains' blocks are unknown. Stop, report the token problem, and create no page |
|
||||||
|
| | 8 | Some other API failure. Stop, report that status, and create no page |
|
||||||
|
|
||||||
|
On any failure, hand the page name and the unwritten entry back to the user and leave this step open; never close the flow on an unwritten report. Completion condition: `SKILLSET_{HASH}` holds the new section plus all earlier sections, and `wiki-contents.sh upsert` exited 0 with this domain's `## SKILLSET_{HASH}` block on `SKILLSET_CONTENTS` linking that page by absolute URL.
|
||||||
|
9. Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-update {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` from the `domain<TAB>path` row step 1 printed for the `hooks` domain, the same way this skill resolves every other cross-plugin script. **When that script is not on this machine, skip this step in silence and close the run as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the run actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | the skill files, the behavior-list section and the `delegate-spec.tsv` row carry the change, the checklist passes, every PR this run needed is open, `deploy-verify.md` sections 1 to 5 hold, and both wiki writes exited 0 |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the run before any file changed — `sync-domains.sh` never reached exit 0, or no skill could be listed to pick from |
|
||||||
|
| `failed` | the run broke mid-way — the step 6 checklist loop kept failing, or a wiki write failed again after its one retry |
|
||||||
|
| `degraded` | the update landed with a part missing — the content page was written while its `SKILLSET_CONTENTS` block was not, the skill's PR is open while the `delegate-spec.tsv` PR is not, or a CLI could not be verified and the reason was recorded |
|
||||||
|
| `aborted` | the user stopped the run, or a prerequisite turned out not to hold and this skill stopped on its own |
|
||||||
|
|
||||||
|
`{exit code}` is this run's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The update itself happens in the model turns after that, so no hook can see how the run ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this run is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: skillset-update
|
name: skillset-update
|
||||||
description: Apply one change request across the whole jsc skill set — multiple skills in multiple domains in one pass. Ask the change details via decision tree, sync every domain repo from the Gitea canonical marketplace, apply the change per affected domain via sub agents, re-check against the guidelines checklist until it passes, then open a PR per affected repo via jsc-git pr. Use when a change spans multiple skills or domains; not for a single skill (use skill-update).
|
description: Apply one change request across the whole jsc skill set — multiple skills in multiple domains in one pass. Sync every domain repo from the Gitea canonical marketplace while the decision tree asks the change details, apply the change per affected domain via parallel sub agents, re-check against the guidelines checklist until it passes, open a PR per affected repo via jsc-git pr, then deploy and verify per references/deploy-verify.md from a fresh CLI process, and append the change report to wiki SKILLSET_{HASH}. Use when a change spans multiple skills or domains; not for a single skill (use skill-update).
|
||||||
---
|
---
|
||||||
|
|
||||||
# skillset-update — apply one change across the skill set
|
# skillset-update — apply one change across the skill set
|
||||||
@@ -9,8 +9,81 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
|
|||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. Ask for the change details via the `jsc-ask:ask` decision tree: what rule or behavior changes, which skills and which domains are affected. Include three required checks before the affected-skill list is final: whether any deterministic input/output flow must move to `tools/`, whether any detailed flow must run as a sub agent, and whether any wiki or Gitea flow must read inherited environment variables before asking the user. Every option states its impact scope (example: changing a shared flow step touches every skill that calls it). Completion condition: the affected-skill list and the three checks are agreed with the user.
|
1. Start `tools/sync-domains.sh` and the change-details decision tree **in parallel** — the sync touches no answer the tree needs, and the tree's answers change nothing the sync does, so waiting for one before the other only adds idle time.
|
||||||
2. Query the Gitea canonical marketplace for the authoritative domain list: run `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`. Clone any domain repo missing from the working directory (`gitea.sh clone-url plugins/{domain}`) and pull the rest. Completion condition: every domain repo exists locally and is current.
|
1. Run `tools/sync-domains.sh` to sync every domain repo of the Gitea canonical marketplace. Exit 0 is the only code that means every repo is present and current; keep the `domain<TAB>path` rows. Exit 3 means some repos were not updated: reconcile every path named on stderr (commit or stash the dirty tree, or fix the failing pull) and rerun; when the user confirms a dirty tree is intentional local work, record that decision and continue on the local version — never read exit 3 as current. Exit 2 means a domain could not be cloned. Exit 1 means the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable; when stderr says the root could not be derived, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos and rerun, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there instead of the domain workspace. Resolve 2 and 1 before continuing.
|
||||||
3. Apply the change to every affected skill — the modification part MUST run as a sub agent, one sub agent per affected domain repo: modify SKILL.md and related files and tools. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) for each affected domain repo to sync that domain README's 「Skills 目錄」 section and bump the version in all three manifests. Completion condition: every affected domain repo carries the change, the README sync, and the manifest bump.
|
2. Ask for the change details via the `jsc-ask:ask` decision tree: what rule or behavior changes, which skills and which domains are affected. Include three required checks before the affected-skill list is final: whether any deterministic input/output flow must move to `tools/`, whether any detailed flow must run as a sub agent, and whether any wiki or Gitea flow must read inherited environment variables before asking the user. Every option states its impact scope (example: changing a shared flow step touches every skill that calls it). These three are a shaping guardrail asked before any file is touched; keep asking them even when a later step would catch the same problem.
|
||||||
4. Check every item of the guidelines.md audit checklist for each touched skill. On any failure, **return to step 1**: ask again and fix, until all items pass.
|
|
||||||
5. Call `jsc-git:pr` once per affected domain repo to open a Push Request. Completion condition: every affected repo has a PR URL.
|
Completion condition: the `domain<TAB>path` rows are in hand, and the affected-skill list plus the three checks are agreed with the user.
|
||||||
|
2. Apply the change to every affected skill — the modification part MUST run as a sub agent, one sub agent per affected domain repo, and those sub agents **run in parallel**: each repo's files are independent. Every sub agent also updates its own repo's `references/behaviors.md` in the same pass: a changed behavior rewrites that skill's `## {name}` section, a new skill gets a section inserted in dictionary order, a removed skill loses its section. Keep all five rows filled — 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象. Each repo's behavior list ships in that repo's own PR, so no cross-repo PR pair has to be merged in order.
|
||||||
|
|
||||||
|
Every sub agent also re-runs the delegation decision tree of [`../../references/delegate-criteria.md`](../../references/delegate-criteria.md) for **every** skill its repo touched — all five questions, the `next` question and the `probe` question, one skill at a time, not one verdict for the repo, and **not one skipped**. A batch is exactly where skipping happens: the change that turned three skills from read-only into file-writing looks like one change, and re-judging only the obvious one leaves the other two being triggered on a verdict that no longer describes them. A skill whose text this batch rewrote without touching its flow or its `description` may keep its verdict, and then only its `version` moves; that reuse is stated in step 5.2's wiki section, exactly as a fresh verdict is.
|
||||||
|
|
||||||
|
The sub agents do **not** write those verdicts. `jsc-meta/tools/delegate-spec.tsv` is one file for the whole skill set, and parallel sub agents writing one file overwrite each other's rows. Each sub agent returns its verdicts as rows — twelve tab-separated columns each, `probe` last, `-` in every column its verdict leaves unused, `origin` set to `judged` for a fresh verdict and left as it was for a reused one — and the **main agent** merges them into the file in one edit after the sub agents finish. A batch is where the `probe` column goes stale fastest: a change that moves or renames a `tools/` script across several domains leaves every row naming it pointing at nothing, so each sub agent verifies that column against its own repo's files — which script, whether it takes a read-only flag, how a failure is reported — and returns `pending:{reason}` rather than a command for any slice that would need the network. When `meta` is one of the affected repos, that edit rides in its PR; when it is not, it is a change to `plugins/meta` and step 4 opens the extra Push Request for it. Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) for each affected domain repo to sync that domain README's 「Skills 目錄」 section and bump the version in all three manifests; these runs are independent per repo and may also go in parallel. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path, `skills/`, `README.md`, the `JSC-SKILLS` markers, a `SKILL.md`, a manifest, or a manifest `version` field is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs under `set -e`, so treat it as an environment fault and stop, never as a successful sync. Completion condition: every affected domain repo carries the change, its behavior-list update, the README sync, and the manifest bump; and every touched skill has a delegation verdict from this run — fresh, or recorded as reused with the reason — merged into `tools/delegate-spec.tsv` by the main agent, with no touched skill left without one.
|
||||||
|
3. Check every item of the guidelines.md audit checklist for each touched skill — one sub agent per affected domain repo, run in parallel. Each sub agent runs `tools/check-behaviors.sh {domain-path}` for the behavior-list item of its own repo instead of comparing by eye, and routes each exit code: 0 — that repo's list matches its `skills/` and all five rows are filled; 1 — every mismatch is printed on stderr as `{檔案}:{技能名}:{說明}`, so fix each one and rerun; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, because `references/behaviors.md` is missing, `skills/` is missing, or no `SKILL.md` was found, so create the missing file and rerun. **Exit 3 is never a pass.**
|
||||||
|
|
||||||
|
The **main agent** then runs `tools/check-delegate.sh` once for the whole batch, not inside the per-repo sub agents: the list is one file covering every domain, so a run per repo checks the same file over again and hands back the same lines from every agent, with nobody holding one verdict. Route each exit code:
|
||||||
|
- 0 — the list matches the skills on this machine and every mandatory column is filled. **A run that printed lines on stdout and exited 0 still passed.** Those lines are hints, not defects: `origin=seed` marks a row seeded from the earlier inventory and awaiting review, and a version-behind line marks a row whose `version` trails its domain's current one, a `probe=pending:` line marks a delegated slice whose read-only entry point is not wired yet, and a line saying a `probe` domain is not installed here marks a script this machine cannot check. A batch bumps several domains at once, so it produces those lines by the dozen — reading them as failures would fail every batch this skill ever runs. Report them as hints. The ones worth acting on are the version-behind lines naming **skills this batch touched**: their `version` was supposed to move in step 2, so go back and move it.
|
||||||
|
- 1 — a missing row, a duplicate row, a row for a skill this machine does not have, an empty column, a column value outside its vocabulary, a `next` pointing at a skill that does not exist, or a `probe` in the wrong shape — a command on a row whose `way` holds `invoke`, a `-` on a row whose `way` holds only `patrol` or `remind`, a dollar sign or tilde, an unknown substitution point, or a script that does not exist. Every one is printed on stderr as `{清單路徑}:{domain}/{技能名}:{說明}`; fix each and rerun. A merge that lost one sub agent's rows shows up here as those skills missing, so read this code as a merge check too.
|
||||||
|
- 2 — usage error: the script takes at most one argument. Fix the call and rerun.
|
||||||
|
- 3 — nothing was checked, because `tools/delegate-spec.tsv` is missing, the root could not be derived, or `list-skills.sh` listed no skill. Read stderr and fix the named cause; set `JSC_PLUGINS_ROOT` to the directory holding the domain repos for the root case, as in step 1.1. **Exit 3 is never a pass.**
|
||||||
|
|
||||||
|
On any failure, **return to step 1.2**: ask again and fix, until all items pass. Completion condition: every checklist item passes for every touched skill, `tools/check-behaviors.sh` exits 0 for every affected domain repo, and `tools/check-delegate.sh` exits 0 once for the batch with its hint lines reported as hints.
|
||||||
|
4. Call `jsc-git:pr` once per affected domain repo to open a Push Request, plus one against `plugins/meta` when the `tools/delegate-spec.tsv` merge landed there and `meta` is not itself an affected repo. Completion condition: every affected repo has a PR URL, `plugins/meta` included when the list changed there, and all URLs are reported in one table with the format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
||||||
|
5. Deploy the batch change, verify it runs, then report:
|
||||||
|
1. Follow [`../../references/deploy-verify.md`](../../references/deploy-verify.md) from section 1 to section 5, once per affected domain repo — the route judgements run in parallel. The batch takes the deploy route only when **every** affected repo's `tools/deploy-route.sh` exits 0; a single exit 3 puts the whole batch on the worktree route, because the change reaches the CLIs only when the last repo merges, so name every outstanding release PR. The verification then runs in a **fresh CLI process**, never in the session that ran the deploy: that session raised the restart gate itself and still holds the old skill bodies. Verify every touched skill's row in `tools/list-skills.sh`, every tool this change touched, and one minimal prompt per affected domain per checkable CLI — the per-CLI and per-domain prompts run in parallel. Completion condition: every completion condition in `deploy-verify.md` sections 1 to 5 holds for every affected domain repo.
|
||||||
|
2. Write the change report to the wiki — this part MUST run as a sub agent, one sub agent per affected domain repo, run in parallel. Each sub agent writes two pages in two repos, and they must not be mixed up.
|
||||||
|
- **Content page `SKILLSET_{HASH}`.** Resolve its repo with `jsc-gitea/tools/gitea.sh wiki-repo SKILLSET`, which reads `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. `{HASH}` is `gitea.sh hash-id "{owner}/{repo}"` of that repo, used at the full 40 characters it prints. Write it through `jsc-gitea:wiki` following [`../../templates/skillset-page.md`](../../templates/skillset-page.md): **append** a section for this change — date, 「批次更新」, the change request in one line, touched skills, changed files, PR URL, the step 5.1 route verdict and verification result per item, and this repo's skills' delegation verdicts from step 2: each fresh verdict with its columns, each reused one as 「沿用前一輪判定」 with the date of the judgement being reused. The row itself has no note column, so this section is the only place that distinction is kept — and keep every earlier section.
|
||||||
|
- **Directory page `SKILLSET_CONTENTS`.** One shared page holds every domain's block, so each sub agent writes only its own. It lives in the CONTENTS repo, never in the SKILLSET one: `wiki-contents.sh` resolves it itself with `gitea.sh wiki-repo CONTENTS`, whose chain is `JSC_WIKI_REPO_CONTENTS` then `JSC_WIKI_REPO` and never falls back to `JSC_WIKI_REPO_SKILLSET`. That page is a heading-plus-bullets list and holds no markdown table: one `## SKILLSET_{HASH}` block per domain repo, every field one `- {欄位名}:{值}` line under it. Build one file holding this domain's single block, following [`../../templates/skillset-contents.md`](../../templates/skillset-contents.md), with its 異動頁 bullet written as `[SKILLSET_{HASH}]({url})` from the **absolute** URL that `gitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}` prints. The H2 heading itself carries no link, no URL, no affix and no date — only the content page name. Every link on both pages takes that `[{text}]({url})` form; the double-bracket wiki-link form resolves only inside one wiki, so it is never used. Then run:
|
||||||
|
|
||||||
|
`jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {entry file} templates/skillset-contents.md`
|
||||||
|
|
||||||
|
The key is the H2 heading `SKILLSET_{HASH}`, so one domain keeps exactly one block and no sibling sub agent's block moves. That page name depends only on `{owner}/{repo}`, which is why it is the key: a host rename or a changed `JSC_WIKI_REPO_SKILLSET` leaves it untouched, so the match still finds the existing block. The `2` is the key column: the index of the column that held the content-page link in the **old markdown table**, and it matters only when such an old table still has to be converted automatically — the conversion takes the last path segment of that column's link URL as the H2 heading. Count that index from the **live page's own column layout**, never from the template's: the live `SKILLSET_CONTENTS` reads `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`, so the link sits in column 2 while column 1 is plain text like `plugins/ask`. Passing `1` would make the heading `plugins/ask`, which never matches the key `SKILLSET_{HASH}`, so the existing entry is appended as a brand-new one — one domain ends up with two blocks and the older one is never updated again. The fourth argument is the whole block, not a table row. Never hand-edit the directory page, and never overwrite it as a whole. Write the content page first and fetch the URL only after it exists.
|
||||||
|
- **Check the links before writing.** Hand every URL going onto the content page and into the directory block to `jsc-gitea/tools/link-check.sh`, and write only when it exits 0. It verifies through the Gitea API, never a web status code: a private repo answers 404 to an unauthenticated web request, so a status-code check would call a live page dead.
|
||||||
|
- **Exit codes.** Route every one of them:
|
||||||
|
|
||||||
|
| Call | Exit | Do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea.sh wiki-repo` | 2 | The page type was misspelled. Fix the argument and rerun |
|
||||||
|
| | 3 | No wiki repo is configured for that type. Name the variable (`JSC_WIKI_REPO_SKILLSET` for the content page, `JSC_WIKI_REPO_CONTENTS` for the directory page) and `JSC_WIKI_REPO`, ask per the `jsc-ask:ask` rules, then rerun |
|
||||||
|
| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash |
|
||||||
|
| | 2 | Empty input, so the `{owner}/{repo}` was never resolved. Fix that first |
|
||||||
|
| Content page read | 0 | Append into the sections already there |
|
||||||
|
| | 4 | The page does not exist yet, so build it from `templates/skillset-page.md` |
|
||||||
|
| | 7 or 8 | Stop and write nothing: a page rebuilt on top of an unread read loses every section already on it |
|
||||||
|
| `gitea.sh wiki-url` | 4 | The content page is not there, so the write above did **not** succeed. Go back and write it, and add no directory block until the page exists |
|
||||||
|
| | 5 | The API answered with no `html_url`. Stop and report it; never assemble the URL by hand from the host and the page name |
|
||||||
|
| `link-check.sh` | 0 | Every link is reachable. Write the page |
|
||||||
|
| | 1 | At least one link is dead. Write nothing, and report the `DEAD` lines it printed |
|
||||||
|
| | 2 | No URL was passed, which is a defect here. Pass the links and rerun |
|
||||||
|
| | 3 | `GITEA_HOST` is unset. Set it and rerun; never skip the check instead |
|
||||||
|
| | 7 | Gitea authentication failed. Stop and report the key problem, and never read it as a dead link |
|
||||||
|
| `wiki-contents.sh upsert` | 0 | The block is in place. Report the `updated` or `added` it printed |
|
||||||
|
| | 1 | The page content could not be assembled, or the write failed. Report `SKILLSET_CONTENTS` as not written, together with the block content |
|
||||||
|
| | 2 | An argument was rejected. Fix it and rerun; nothing was written |
|
||||||
|
| | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set; the new section is on `SKILLSET_{HASH}` and stays there |
|
||||||
|
| | 4 | The directory page is absent and no template was passed. Rerun with `templates/skillset-contents.md` as the fifth argument |
|
||||||
|
| | 7 | The token is invalid or lacks permission, so the other domains' blocks are unknown. Stop, report the token problem, and create no page |
|
||||||
|
| | 8 | Some other API failure. Stop, report that status, and create no page |
|
||||||
|
|
||||||
|
On any failure, hand the page name and the unwritten entry back to the user and leave this step open; never close the flow on an unwritten report. Completion condition: every affected repo's `SKILLSET_{HASH}` holds the new section plus all earlier sections, and every one of those repos has a `## SKILLSET_{HASH}` block on `SKILLSET_CONTENTS` written by a `wiki-contents.sh upsert` that exited 0, linking its page by absolute URL.
|
||||||
|
6. Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included. One event for the whole batch, not one per domain. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:skillset-update {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` from the `domain<TAB>path` row step 1.1 printed for the `hooks` domain, the same way this skill resolves every other cross-plugin script. **When that script is not on this machine, skip this step in silence and close the run as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the run actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | every affected repo carries the change and its behavior-list update, every touched skill has this run's delegation verdict in `delegate-spec.tsv`, every checklist passes, every repo has a PR URL, `deploy-verify.md` sections 1 to 5 hold for all of them, and every wiki write exited 0 |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the run before any file changed — `sync-domains.sh` never reached exit 0, or the affected-skill list was never agreed |
|
||||||
|
| `failed` | the run broke mid-way — the step 3 checklist loop kept failing for some repo, or a wiki write failed again after its one retry |
|
||||||
|
| `degraded` | part of the batch landed and part did not — some repos got their PR and others did not, the domain PRs are open while the `delegate-spec.tsv` PR is not, or a content page was written while its `SKILLSET_CONTENTS` block was not. Name the repos in `detail` |
|
||||||
|
| `aborted` | the user stopped the run, or a prerequisite turned out not to hold and this skill stopped on its own |
|
||||||
|
|
||||||
|
`{exit code}` is this run's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The batch itself happens in the model turns after that, so no hook can see how the run ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this run is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: ste100-sync
|
name: ste100-sync
|
||||||
description: Sync the STE100 language rules with upstream speak-human-tw. Compare the pinned upstream version in references/ste100.md against the latest release, distill applicable changes, refresh ste100-lint.sh patterns, re-lint all jsc repos, then open a PR via jsc-git pr. Use on periodic maintenance or when upstream releases a new version; not for editing local-only rules.
|
description: Sync the STE100 language rules with upstream speak-human-tw. Compare the pinned upstream version in references/ste100.md against the raw upstream frontmatter version before cloning anything, distill applicable changes and confirm each one via decision tree, refresh ste100-lint.sh patterns and jsc-hooks simplified.txt, re-lint all jsc repos in parallel, then open a PR via jsc-git pr. Use on periodic maintenance or when upstream releases a new version; not for editing local-only rules.
|
||||||
---
|
---
|
||||||
|
|
||||||
# ste100-sync
|
# ste100-sync
|
||||||
@@ -9,17 +9,45 @@ Keep `references/ste100.md` in sync with its upstream source, [speak-human-tw](h
|
|||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
1. Read the pinned version from the「上游版本」line in `references/ste100.md`.
|
1. Compare versions before fetching anything large — the common case is that upstream has no new release, and a clone done first is then wasted every time:
|
||||||
2. Clone the upstream repo (`--depth 1`). Read the `version` and `changelog` fields in its `SKILL.md` frontmatter.
|
1. Read the pinned version from the「上游版本」line in `references/ste100.md`. Completion condition: the pinned version string is in hand.
|
||||||
3. Same version: report「上游沒有新版」and stop.
|
2. Read the upstream `version` from the raw `SKILL.md` frontmatter over HTTPS, without cloning. When the raw read fails — network error, a moved path, or no `version` line in the frontmatter — fall back to the `--depth 1` clone and read the same field from the working copy. Completion condition: the upstream version string is in hand, and the report names which route produced it, raw or clone.
|
||||||
4. Newer version — distill the changes. **MUST run as a sub agent**:
|
3. Same version: report「上游沒有新版」and stop, without cloning. Completion condition: either the run stops here, or the upstream version is newer than the pinned one.
|
||||||
|
2. Newer version — get the changelog. Clone the upstream repo (`--depth 1`) when step 1.2 did not already clone it, and read the `changelog` field in its `SKILL.md` frontmatter. Completion condition: the changelog entries newer than the pinned version are in hand.
|
||||||
|
3. Distill the changes into a change list. **MUST run as a sub agent**:
|
||||||
- Walk the changelog entries newer than the pinned version.
|
- Walk the changelog entries newer than the pinned version.
|
||||||
- Keep only changes that apply to technical documents and conversation: Taiwan term replacements, punctuation rules, de-AI patterns, humanize targets.
|
- Keep only changes that apply to technical documents and conversation: Taiwan term replacements, punctuation rules, de-AI patterns, humanize targets.
|
||||||
- Drop marketing-copy scenes, eval material, and workflow-mode changes.
|
- Drop marketing-copy scenes, eval material, and workflow-mode changes.
|
||||||
- Apply the distilled changes to `references/ste100.md`. Keep its trimmed structure. Update the「上游版本」line.
|
- Decide nothing and edit no file. Report one line per candidate change: the rule, the upstream wording, and what it would change in `references/ste100.md` or in the lint patterns.
|
||||||
5. If the replacement table or cliché list changed, update the `TERMS` and `CLICHES` patterns in `tools/ste100-lint.sh`.
|
|
||||||
6. Run `tools/ste100-lint.sh` over every jsc repo. Fix hits in files this repo owns; report hits elsewhere.
|
Completion condition: every kept changelog entry appears as one line in the distilled list.
|
||||||
7. Open a PR via `jsc-git:pr`.
|
4. Present the distilled list via the `jsc-ask:ask` decision tree, one question per change (adopt / drop / adapt). Every option states its impact scope (example: adopting a term replacement changes the `TERMS` pattern, so every repo re-linted in step 7 can gain new hits). `references/ste100.md` is the single source of truth for the whole skill set, so no change lands without a recorded decision. Completion condition: every distilled change has a recorded decision.
|
||||||
|
5. Apply the adopted and adapted changes to `references/ste100.md`. Keep its trimmed structure. Update the「上游版本」line. Completion condition: every adopted change is visible in the file and the「上游版本」line shows the new upstream version.
|
||||||
|
6. If the replacement table or the cliché list changed, update the `TERMS`, `CLICHES` and `SIMPLIFIED_FALLBACK` patterns in `tools/ste100-lint.sh`. `SIMPLIFIED` is the runtime variable the lint builds from the shared character table, not an editable pattern: the real source is `jsc-hooks/hooks/simplified.txt`, which `ste100-guard.sh` reads too, and `SIMPLIFIED_FALLBACK` is only the built-in backup for machines without `jsc-hooks`. A simplified-character change therefore lands in `simplified.txt` first and in `SIMPLIFIED_FALLBACK` second — editing the lint alone leaves the hook enforcing the old table. Completion condition: `sh -n tools/ste100-lint.sh` passes, each newly adopted term hits on a test string, and any simplified-character change is in `jsc-hooks/hooks/simplified.txt` as well.
|
||||||
|
7. Run `tools/ste100-lint.sh` over every jsc repo (`tools/sync-domains.sh` prints the repo paths). The repos are independent, so lint them **in parallel**, one run per repo. Route each exit code: 0 — that repo is clean; 1 — hits printed as `{檔案}:{行號}:{類別}:{命中內容}`; 2 — no target was given, so fix the arguments and rerun, never read it as clean. Fix hits in files this repo owns. Completion condition: the lint exits 0 for this repo, and hits in other repos are reported with `file:line` for their owners.
|
||||||
|
8. Run `tools/sync-skill-manifest.sh .` to sync the README's 「Skills 目錄」 section and bump the manifests. Route each exit code: 0 — the README block and all three manifests are synced; 1 — `skills/`, `README.md`, the `JSC-SKILLS` markers, a `SKILL.md`, a manifest, or a manifest `version` field is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs under `set -e`, so treat it as an environment fault and stop, never as a successful sync. Completion condition: all three manifests show the same new version.
|
||||||
|
9. Open a PR via `jsc-git:pr`. Completion condition: a PR URL comes back and is reported with the table format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
||||||
|
10. Report this run's outcome to the local event stream — the last step of every run, **the step 1.3 early stop included**. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:ste100-sync {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` the same way step 6 resolves `jsc-hooks/hooks/simplified.txt`: the sibling checkout in the workspace. On the step 1.3 early stop, where `tools/sync-domains.sh` has not run, that sibling path is the only source. **When the script is not on this machine, skip this step in silence and close the run as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the run actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | upstream had a new version and every adopted change is in `references/ste100.md`, the lint runs clean here, the manifests are bumped and the PR is open — **and also when step 1.3 stopped the run on 「上游沒有新版」**, because that is this skill's normal ending, not an abort |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the run before any comparison — the call itself was refused, or neither the raw read nor the clone could reach upstream, so no version could be compared |
|
||||||
|
| `failed` | the run broke mid-way — `sh -n tools/ste100-lint.sh` kept failing after the pattern edit, or `sync-skill-manifest.sh` could not be resolved |
|
||||||
|
| `degraded` | the sync landed with a part missing — this repo lints clean but hits in other repos were only handed to their owners, or a simplified-character change reached the lint and not `jsc-hooks/hooks/simplified.txt` |
|
||||||
|
| `aborted` | the user stopped the run, or the user dropped every distilled change so nothing was left to apply |
|
||||||
|
|
||||||
|
`{exit code}` is this run's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The comparison and the sync happen in the model turns after that, so no hook can see how the run ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job, and why the 「上游沒有新版」 path must write one too.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this run is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
name: tooling-guide
|
||||||
|
description: Inventory the current jsc plugins, skills, hook management, and usage paths as the baseline tooling guide, taking the skill, CLI, and hook-wiring facts from one inventory-tooling.sh run rather than re-running the scripts it already called. On request, publish that inventory through jsc-gitea:wiki to TOOLING_{HASH}, hashed from {hostname}/{tool}/{account}, and register it in TOOLING_CONTENTS. Use when the user asks for a skill-set guide, tooling map, supported plugin list, supported skill list, hook management overview, or onboarding reference. Do not use for installing, updating, deleting, auditing, or repairing the skill set; use jsc-cli:deploy, jsc-meta:skill-check, jsc-meta:skill-update, jsc-meta:skill-delete, or jsc-hooks:hooks-install instead.
|
||||||
|
---
|
||||||
|
|
||||||
|
# tooling-guide - build the baseline tooling guide
|
||||||
|
|
||||||
|
Goal: produce a current guide for the jsc skill set from the local repos and the supported tooling scripts.
|
||||||
|
|
||||||
|
Single source of guidelines: [`../../references/guidelines.md`](../../references/guidelines.md).
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Every factual claim in the guide carries the source path or the tool output line it came from.
|
||||||
|
- Prefer script output over copied lists.
|
||||||
|
- Use `tools/inventory-tooling.sh` for the baseline inventory.
|
||||||
|
- Keep command syntax in the guide only when it comes from README files or tool output.
|
||||||
|
- Do not modify README files, manifests, marketplace files, hooks, tools, or other skills.
|
||||||
|
- Put generated guide text in the response or in the user-requested target only.
|
||||||
|
- Run detail synthesis as a sub agent when the guide needs explanations, grouping, or onboarding prose.
|
||||||
|
- Route every wiki read and write through `jsc-gitea:wiki`, and every `{HASH}` through `jsc-gitea/tools/hash-id`. The directory page `TOOLING_CONTENTS` is the one exception: it goes through `jsc-gitea/tools/wiki-contents.sh`, which owns the directory-page layout for every page type, so this skill never assembles that page itself.
|
||||||
|
- Close every run with the step 8 `skill-end` event. That one line in `$JSC_HOME/usage/events.jsonl` is the only thing this skill writes outside the recorded output target, and the rule above about not modifying files does not cover it.
|
||||||
|
|
||||||
|
Done when each rule above has a recorded pass, or a recorded exception naming the claim and the reason, checked before the final report.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- Optional user scope: plugin inventory, skill inventory, hook management, CLI usage, or all areas.
|
||||||
|
- Optional output target: chat response, wiki draft text, a named file that the user explicitly requests, or the wiki pages `TOOLING_{HASH}` and `TOOLING_CONTENTS`.
|
||||||
|
|
||||||
|
Done when the scope and the output target are each written down as one of the values listed above. With no scope given, write down `all areas`. With no target given, write down `chat response`. Only the `wiki page` target runs step 7.
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
1. Confirm the working roots. Run `tools/plugins-root.sh` — it prints the workspace root that holds the domain checkouts, and it is the same derivation every other tool here uses. Exit 1 means the root could not be derived: read stderr, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos, and rerun. Under a plugin install the tools sit in the CLI's plugin cache, so the built-in guess lands in that cache instead of the workspace. The meta root is `{root}/meta`, or `{root}/jsc-meta` when that is the checkout name; the sibling directories under `{root}` are the domain checkouts. Completion condition: the script exits 0, and the meta root it names exists and contains `references/guidelines.md`.
|
||||||
|
|
||||||
|
2. Sync the domain inventory with `tools/sync-domains.sh`. This script owns marketplace discovery and local repo synchronization.
|
||||||
|
- Exit 0: continue with the printed `domain<TAB>path` rows.
|
||||||
|
- Exit 3: keep the printed rows, report every skipped or dirty repo from stderr as stale input, and continue only after the user accepts a guide with stale rows.
|
||||||
|
- Exit 2: report the clone failure and stop.
|
||||||
|
- Exit 1: report which cause stderr names — the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable — then stop. For the root case, fix it the same way as step 1 and rerun.
|
||||||
|
|
||||||
|
Completion condition: each plugin row used by the guide has a domain and a local path, or the stale-input decision is recorded.
|
||||||
|
|
||||||
|
3. Collect the baseline inventory and the management-flow facts. The two halves are independent — one reads tool output, the other reads files — so run them **at the same time**.
|
||||||
|
|
||||||
|
**Baseline inventory.** Build it with `tools/inventory-tooling.sh` and use its Markdown output as the base document.
|
||||||
|
- Exit 0: continue with the generated guide.
|
||||||
|
- Exit 1: report which cause stderr names — the root could not be derived, the root does not exist, or the marketplace or `list-skills.sh` is missing — then stop.
|
||||||
|
- Any other exit: report the command, exit code, and stderr, then stop.
|
||||||
|
|
||||||
|
**This one run already covers the skill catalog, the CLI detection and the hook wiring status.** Internally it runs `meta/tools/list-skills.sh`, `cli/tools/detect-clis.sh` and `hooks/tools/wire-cli.sh status {cli}` and writes each result into its own section, so read those sections instead of calling the three scripts again:
|
||||||
|
- `Supported skills` — one row per skill, from `list-skills.sh`. An empty table means no skill was scanned; return to step 2.
|
||||||
|
- `Supported CLIs` — one row per detected CLI with its path and version, from `detect-clis.sh`. The single placeholder row means no CLI is installed here, or `detect-clis.sh` is missing; mark CLI-specific checks as not available on this machine.
|
||||||
|
- `Hook wiring status` — one row per detected CLI with the `wire-cli.sh status` exit code and verdict, `CLI 代號不符合 wire-cli.sh 用法` for exit 2 and `未知狀態,結束碼 {rc}` for anything outside 0, 1, 3 and 5. The single placeholder row means no CLI was detected or `wire-cli.sh` is missing; state the hook verdict as unknown and say why.
|
||||||
|
|
||||||
|
Re-running those three scripts on top of this buys nothing and can disagree with the base document — the second run sees a different machine state, and the guide then carries two answers for one fact. What that costs is written down under `Notes`.
|
||||||
|
|
||||||
|
**Management-flow facts.** Collect them from the current docs and skills. Read only README files, `SKILL.md` files, and tool help or headers from `tools/` under the domain repo paths that step 2 printed — never a hardcoded absolute path, because the workspace root differs per machine and per install form. Do not infer support from missing or stale files.
|
||||||
|
|
||||||
|
Completion condition: the generated guide contains `Source freshness`, `Supported plugins`, `Supported skills`, `Supported CLIs`, `Hook wiring status`, `Hook management`, `Plugin and skill management`, `Operational checks` and `Use this when`; the skill, CLI and hook facts used by the guide are quoted from those sections; and each management flow in the guide points to one source file or one tool output.
|
||||||
|
|
||||||
|
4. Synthesize the guide. This step MUST run as a sub agent when the output needs explanations, grouping, onboarding prose, or cross-domain comparison. Give the sub agent only the collected inventories, the relevant README and SKILL paths, and this required section list:
|
||||||
|
- Supported plugins
|
||||||
|
- Supported skills
|
||||||
|
- Supported CLIs and usage forms
|
||||||
|
- Hook management
|
||||||
|
- Plugin and skill management
|
||||||
|
- Health checks and repair paths
|
||||||
|
- Known coverage limits
|
||||||
|
|
||||||
|
Completion condition: the sub agent returns a guide draft with every required section and with source paths for each factual claim.
|
||||||
|
|
||||||
|
5. Verify the draft in the main agent.
|
||||||
|
- Check that each plugin comes from the step 2 `sync-domains.sh` rows.
|
||||||
|
- Check that each skill comes from the step 3 `Supported skills` section.
|
||||||
|
- Check that each CLI fact comes from the step 3 `Supported CLIs` section, and each hook-wiring fact from the step 3 `Hook wiring status` section.
|
||||||
|
- Check that install, update, delete, audit, repair, and health-check actions point to the owning skill or tool.
|
||||||
|
- Check that the draft does not copy long implementation details from README files or scripts.
|
||||||
|
|
||||||
|
Completion condition: every factual claim has a source path or tool output, and no required section is empty.
|
||||||
|
|
||||||
|
6. Deliver the guide in the requested target. If the target is chat, keep it concise and include the source paths used. If the target is a file, write only that user-requested file and do not update manifests or README files. If the target is the wiki page, step 7 performs the delivery — keep the chat summary short here and let step 7 report the page names and URLs. Completion condition: the guide is delivered and the final report names the target, source freshness, stale inputs if any, and any unknown hook verdicts.
|
||||||
|
|
||||||
|
7. Publish the inventory to the wiki. Run this step only when the recorded output target is the wiki page; for any other target, record `wiki publish skipped — target is {target}` and go to the final report. **This whole step MUST run as a sub agent.** Every wiki read and write goes through `jsc-gitea:wiki`; never assemble a Gitea API call here.
|
||||||
|
|
||||||
|
7.1 **Build one page name per detected CLI.** Take the CLI code names from the step 3 `Supported CLIs` section — that section already carries the first column of `jsc-cli/tools/detect-clis.sh`, one of `claude`, `codex`, `copilot`, `antigravity`, `kiro`. Pair each code name with this machine's host name and the current login account, then hand `{hostname}/{tool}/{account}` to `jsc-gitea/tools/hash-id`. The hash rules live in `../../references/guidelines.md` and are not restated here; compute nothing by hand. One page per host, CLI, and account: every CLI carries its own installed plugin set and its own hook wiring, and the tool segment is what keeps five CLIs off one page. A missing host name, tool name, or account stops the step — name the missing segment and substitute no default value. `hash-id` exit 1 means this machine has neither `sha1sum` nor `shasum`: stop and report that one of them has to be installed. Completion condition: every detected CLI has one `TOOLING_{HASH}` name built from three non-empty segments, all of them produced by `hash-id`.
|
||||||
|
|
||||||
|
7.2 **Resolve the wiki repo** for type `TOOLING` through `jsc-gitea:wiki`, which reads `JSC_WIKI_REPO_TOOLING` first and `JSC_WIKI_REPO` second. Exit 3 — neither variable is set: ask for that type's `{owner}/{repo}` per the `jsc-ask:ask` rules. Exit 2 — the installed `jsc-gitea` does not accept the `TOOLING` type yet: stop and report that the type has to be registered there first. Completion condition: exactly one `{owner}/{repo}` is recorded, and every **content page** write in this step targets it; the directory page lives in the CONTENTS repo instead, and `wiki-contents.sh` resolves that one itself in step 7.4.
|
||||||
|
|
||||||
|
7.3 **Write the content pages first.** Render `templates/tooling-page.md` for each `TOOLING_{HASH}` from the step 3 inventory, keeping only that page's own CLI row in the `Supported CLIs` and `Hook wiring status` tables. Each run overwrites the whole page: it records what this machine looks like right now, so keeping earlier runs buys nothing. Content pages go before the directory page for the same reason as every other jsc skill — a directory block must never point at a page whose write failed. Completion condition: every `TOOLING_{HASH}` write returned exit 0, or its failure went to step 7.5.
|
||||||
|
|
||||||
|
7.4 **Register the pages in `TOOLING_CONTENTS` second, with `jsc-gitea/tools/wiki-contents.sh`.** That page is a heading-plus-bullets list and holds no markdown table: one `## TOOLING_{HASH}` block per machine, CLI and account, every field one `- {欄位名}:{值}` line under it, following [`../../templates/tooling-contents.md`](../../templates/tooling-contents.md). Build one file holding this run's single block, its 盤點頁 bullet written as `[TOOLING_{HASH}]({url})` from the **absolute** URL that `jsc-gitea/tools/gitea.sh wiki-url {TOOLING repo} TOOLING_{HASH}` prints; the H2 heading itself carries no link, no URL, no affix and no date — only the content page name. Hand every URL to `jsc-gitea/tools/link-check.sh` first and write only when it exits 0; it verifies through the Gitea API, because a private repo answers 404 to an unauthenticated web request. Then run this once per page written in step 7.3:
|
||||||
|
|
||||||
|
`jsc-gitea/tools/wiki-contents.sh upsert TOOLING 1 "TOOLING_{HASH}" {entry file} templates/tooling-contents.md`
|
||||||
|
|
||||||
|
That tool owns the whole read-modify-write of the directory page: it resolves the CONTENTS repo itself, reads the page, converts any leftover markdown table to blocks, replaces the block whose heading matches, appends when none matches, and writes the page back, so every block belonging to another machine or CLI stays as it was. The key is the H2 heading `TOOLING_{HASH}`, and that name is hashed from `{hostname}/{tool}/{account}`, so a heading match already proves all three segments match — no per-field comparison is needed. The `1` is the key column, and it only matters while the page is still an old markdown table: it names the 盤點頁 column, whose cell text is that same page name, so the automatic conversion produces headings that match. The fourth argument is the whole block, not a table row. Route each exit code:
|
||||||
|
- 0 — the block is in place. Report the `updated` or `added` it printed.
|
||||||
|
- 1 — the page content could not be assembled, or the write failed. A page holding no matching block is **not** this case; that one appends. Report `TOOLING_CONTENTS` as not written together with the block content, and take it to step 7.5.
|
||||||
|
- 2 — an argument was rejected. Fix it and rerun; nothing was written.
|
||||||
|
- 3 — no CONTENTS wiki repo is configured. Name `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO`, ask per the `jsc-ask:ask` rules, then rerun. The content pages of step 7.3 stay written.
|
||||||
|
- 4 — the directory page is absent and no template was passed. Rerun with `templates/tooling-contents.md` as the fifth argument. **This is the only path that creates that page.**
|
||||||
|
- 7 or 8 — the token was rejected, or the API failed, so the other machines' blocks are unknown. Stop. Create nothing and overwrite nothing: a page built on top of unknown content deletes blocks that nobody can get back. Report the exit code and the page name.
|
||||||
|
|
||||||
|
Completion condition: `TOOLING_CONTENTS` holds one `## TOOLING_{HASH}` block per page written in step 7.3, each written by an `upsert` that exited 0, every block belonging to another machine or CLI is unchanged, or the step stopped with the exit code and the page name reported.
|
||||||
|
|
||||||
|
7.5 **Route a failed write.** Retry the failed write once — a `jsc-gitea:wiki` content-page write, or a `wiki-contents.sh upsert` that exited 1. When it fails again, stop the publish and report the page name together with the content that never reached the wiki, so the user can place it by hand. Report a page as written only after its write returned exit 0. Completion condition: every page named in this step is either confirmed written with its page name, or listed as unwritten with its exit code and its full content.
|
||||||
|
|
||||||
|
8. Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included, and the ones whose target was the chat response. Run:
|
||||||
|
|
||||||
|
`jsc-hooks/tools/report-status.sh skill-end jsc-meta:tooling-guide {status} {exit code} [detail]`
|
||||||
|
|
||||||
|
Resolve `jsc-hooks` from the `domain<TAB>path` row step 2 printed for the `hooks` domain, the same way this skill resolves every other cross-plugin script; when step 2 never produced rows, take the sibling checkout under the root step 1 printed. **When the script is not on this machine, skip this step in silence and close the run as normal.** A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.
|
||||||
|
|
||||||
|
Pick `{status}` from what the run actually did:
|
||||||
|
|
||||||
|
| status | Use it when |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | the guide holds every required section with a source behind each claim, it reached the recorded target, and — for the wiki target — every page write and the `TOOLING_CONTENTS` registration returned exit 0 |
|
||||||
|
| `blocked` | a gate or a missing prerequisite stopped the run before any inventory was built — `tools/plugins-root.sh` exited 1, or `sync-domains.sh` exited 2 or 1 |
|
||||||
|
| `failed` | the run broke mid-way — `inventory-tooling.sh` exited non-zero, or a wiki write failed again after its one retry |
|
||||||
|
| `degraded` | the guide was delivered with a part missing — stale rows were accepted from `sync-domains.sh` exit 3, a hook verdict stayed unknown, or the content pages were written while `TOOLING_CONTENTS` was not |
|
||||||
|
| `aborted` | the user stopped the run, or the user refused a guide built on stale input so this skill stopped on its own |
|
||||||
|
|
||||||
|
`{exit code}` is this run's own result as a number: `0` for `ok`, non-zero otherwise. `detail` is optional, one line, at most 200 characters.
|
||||||
|
|
||||||
|
The matching `skill-start` comes free from the hook, which fires when the skill loads. The inventory and the delivery happen in the model turns after that, so no hook can see how the run ended — a `start` with no `end` reads as an abort, which is why writing the `end` is this skill's own job. This is the one write a read-only skill still makes.
|
||||||
|
|
||||||
|
Completion condition: one `skill-end` line for this run is appended to `$JSC_HOME/usage/events.jsonl`, or the script was absent and the final report says so.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Removed protection, on purpose.** The flow used to carry three more steps that re-ran `list-skills.sh`, `detect-clis.sh` and `wire-cli.sh status {cli}` after `inventory-tooling.sh` had already called all three. That second pass doubled as an independent cross-check: it read the same three facts straight from the source scripts, so a wrong skill row, a missing CLI, or a stale hook verdict produced by `inventory-tooling.sh` surfaced as a disagreement between the two sets. That cross-check is gone. The guide now takes the skill catalog, the CLI list and the hook wiring status from one `inventory-tooling.sh` run, with no second raw output to compare against, so a bug in that script's own scanning, parsing, or section writing reaches the guide unnoticed and reads as fact. Two things bound the risk: step 5 still rejects any claim with no source section behind it, and `Hook wiring status` carries the per-CLI exit code, so a nonsense verdict stays visible. When a decision rests on the guide's skill, CLI, or hook facts, get the second opinion elsewhere — run the three scripts by hand and compare, or run `jsc-cli:doctor` for an independent wiring verdict.
|
||||||
|
|
||||||
|
## Output Contract
|
||||||
|
|
||||||
|
The guide must include these fields in this order:
|
||||||
|
|
||||||
|
1. `Source freshness`: synced, stale accepted, or blocked.
|
||||||
|
2. `Supported plugins`: one row per domain.
|
||||||
|
3. `Supported skills`: one row per skill.
|
||||||
|
4. `Supported CLIs`: one row per detected CLI, or one sentence for none detected.
|
||||||
|
5. `Hook management`: wiring, smoke, error scan, repair owner, and coverage limits, with the per-CLI wiring verdicts from `Hook wiring status`.
|
||||||
|
6. `Plugin and skill management`: install, update, delete, audit, and creation owners.
|
||||||
|
7. `Operational checks`: doctor, setup, version guard, restart gate, language guard, and comment-scope guard.
|
||||||
|
8. `Use this when`: short usage guidance for maintainers.
|
||||||
|
|
||||||
|
Done when the output has all fields in order and each non-empty table has at least one source reference.
|
||||||
|
|
||||||
|
For the wiki-page target, the same fields go to `TOOLING_{HASH}` in the section order of `templates/tooling-page.md`, and the `## TOOLING_{HASH}` block registered in `TOOLING_CONTENTS` follows `templates/tooling-contents.md`. Both templates own their own field lists; do not restate them here.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 技能組異動目錄
|
||||||
|
|
||||||
|
> 由 `jsc-meta` 的 `skill-new`、`skill-update`、`skill-delete`、`skillset-update`、`skill-check` 共同維護。這是目錄頁 `SKILLSET_CONTENTS`。
|
||||||
|
> 一個區塊代表一個 domain 存取庫。技能組有幾個 domain 被改過,就有幾個區塊。
|
||||||
|
> 本頁落在 `JSC_WIKI_REPO_CONTENTS` 解出來的存取庫,不是內容頁那一個。解析鏈是 `JSC_WIKI_REPO_CONTENTS` → `JSC_WIKI_REPO` → exit 3,中間不退回 `JSC_WIKI_REPO_SKILLSET`。
|
||||||
|
> 寫入一律用 `jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {區塊檔} templates/skillset-contents.md`:`<TYPE>` 填 `SKILLSET`,`<key>` 填這一筆的 H2 標題,也就是內容頁頁名 `SKILLSET_{HASH}`,第四個參數是整個 H2 區塊的檔案,不是一列表格。
|
||||||
|
> `<key-col>` 填 `2`。這個參數填的是**舊表格裡持有「內容頁連結」那一欄的序號**,只在舊頁還是 markdown 表格、需要自動轉檔時才用得到:轉檔時工具從那一欄的連結網址取最後一段路徑當 H2 標題。序號要照**線上那一頁實際的欄位排法**數,不是照這份範本的欄位排法——線上 `SKILLSET_CONTENTS` 的舊表頭是 `| 存放庫 | 異動報告 | 目前版本 | 最後更新 |`,連結在第 2 欄,第 1 欄是 `plugins/ask` 這種純文字。填成 `1` 會把標題轉成 `plugins/ask`,跟鍵 `SKILLSET_{HASH}` 對不上,既有那一筆會被當成新的附加上去,同一筆變兩個區塊,舊區塊從此再也更新不到。頁面已經是條列格式時這個參數完全不影響結果。
|
||||||
|
> 它讀回整頁、換掉 H2 標題相符的那個區塊、找不到才附加到頁尾,最後整頁寫回。不得手工改目錄頁。
|
||||||
|
> `SKILLSET_{HASH}` 的 `{HASH}` 交給 `jsc-gitea/tools/hash-id` 產生,雜湊來源見 `jsc-meta/references/guidelines.md` 的「Wiki 頁命名總表」。
|
||||||
|
> 連結寫法:所有連結一律 `[{文字}]({連結})`,網址放 `jsc-gitea/tools/gitea.sh wiki-url` 印出的絕對網址,不用 `[[...]]`。寫入前先把每個連結交給 `jsc-gitea/tools/link-check.sh` 驗證,結束碼 0 才寫入;驗證走 API,不看網頁狀態碼。
|
||||||
|
>
|
||||||
|
> 欄位說明:一個區塊固定五條,順序照下面從上到下。
|
||||||
|
>
|
||||||
|
> - 異動頁:`[SKILLSET_{HASH}]({連結})`,連結是 `gitea.sh wiki-url` 印出的絕對網址。與 H2 標題指的是同一頁,標題不放連結,這一條才放。
|
||||||
|
> - 存取庫:被改動的 domain 存取庫 `{owner}/{repo}`,也就是那一頁的雜湊來源。
|
||||||
|
> - 最近異動:最後一次異動的一句話摘要,與內容頁最新一節的「異動需求」同一句。
|
||||||
|
> - 異動次數:該內容頁累積的節數。內容頁只附加不覆蓋,所以這個數字只會往上加。
|
||||||
|
> - 最後更新:最後一次寫入內容頁的時間,與那一節的日期一致。
|
||||||
|
>
|
||||||
|
> 為什麼 H2 標題寫頁名:頁名只由 `{owner}/{repo}` 決定,換主機名、`JSC_WIKI_REPO_SKILLSET` 改指別的存取庫、Gitea 的頁名編碼有差,都動不到它。鍵夠穩,`upsert` 才比得到既有那一筆;鍵一漂,同一個 domain 就多出第二個區塊,兩邊都寫得成功,也都看不出被分裂。
|
||||||
|
>
|
||||||
|
> 為什麼連結要用絕對網址,還要先驗證:目錄頁與內容頁分屬不同存取庫。同 wiki 連結解到的是目錄頁自己那個存取庫,那裡沒有這一頁,點下去是 404。更麻煩的是它看起來像「頁沒寫成功」,實際上頁好好的,只是連結指錯地方,查的人會回去重寫一次已經寫好的頁。驗證則走 API,不看網頁狀態碼。私有存取庫的網頁網址對未登入請求一律回 404,拿狀態碼判斷會把還在的頁判成死連結,接著被刪掉或改寫。
|
||||||
|
>
|
||||||
|
> 寫入規則:
|
||||||
|
>
|
||||||
|
> - 一律走 `jsc-gitea/tools/wiki-contents.sh upsert`,鍵是 H2 標題 `SKILLSET_{HASH}`。
|
||||||
|
> - 那支腳本先整頁讀回來,再逐個比對 H2 標題。
|
||||||
|
> - 標題相同就整塊換掉,區塊裡的每一條都覆寫成本次結果。
|
||||||
|
> - 找不到相同的標題,才附加一個新區塊。
|
||||||
|
> - 只動自己那一個區塊,別人的區塊原樣保留。
|
||||||
|
> - 禁止整頁覆蓋。這一頁是全部 domain 共用的索引,覆蓋等於刪掉別的 domain 的紀錄。
|
||||||
|
> - 讀不到舊內容就中止,不附加區塊,也不寫入。
|
||||||
|
> - 這一頁不留任何 markdown 表格。舊頁還是表格時由 `wiki-contents.sh` 自動轉成條列後寫回,不要手工搬。
|
||||||
|
> - 先寫內容頁,成功了才回來更新這個區塊。目錄頁指向一個寫失敗的頁,比缺一筆更難查。
|
||||||
|
|
||||||
|
## SKILLSET_{HASH}
|
||||||
|
|
||||||
|
- 異動頁:[SKILLSET_{HASH}]({wiki-url 印出的絕對網址})
|
||||||
|
- 存取庫:{owner}/{repo}
|
||||||
|
- 最近異動:{一句話寫這一次改了什麼}
|
||||||
|
- 異動次數:{n}
|
||||||
|
- 最後更新:{yyyy-MM-dd HH:mm}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 技能組異動 — {owner}/{repo}
|
||||||
|
|
||||||
|
> 由 `jsc-meta` 的 `skill-new`、`skill-update`、`skill-delete`、`skillset-update`、`skill-check` 共同維護。這是內容頁 `SKILLSET_{HASH}`。
|
||||||
|
> 一個 domain 存取庫一頁。雜湊來源是這個存取庫的 `{owner}/{repo}`。
|
||||||
|
> 本頁落在 `JSC_WIKI_REPO_SKILLSET` 解出來的存取庫;目錄頁 `SKILLSET_CONTENTS` 在別的存取庫,版面也不同:那頁是大標題加條列,一筆一個 H2 區塊,本頁是內容頁,版面維持圖表優先。兩者不要混。
|
||||||
|
> **每次異動附加一節,不覆蓋舊紀錄。** 要看一支技能改過幾次,就在這一頁上翻。
|
||||||
|
> 節的排列由新到舊,最新那一次放最上面。
|
||||||
|
|
||||||
|
## {yyyy-MM-dd HH:mm} — {一句話寫這一次改了什麼}
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 日期 | {yyyy-MM-dd HH:mm} |
|
||||||
|
| 異動類型 | {skill-new、skill-update、skill-delete、skillset-update、skill-check 五選一} |
|
||||||
|
| 異動需求 | {一句話。與目錄頁「最近異動」那一條同一句} |
|
||||||
|
| 動到的技能 | {技能名,多支用頓號隔開;一支都沒動就寫「無」} |
|
||||||
|
| 改動檔案 | {存取庫內相對路徑,一行一個;一個檔都沒動就寫「無」} |
|
||||||
|
| PR 網址 | {絕對網址;沒開 PR 就寫「無」並說明原因} |
|
||||||
|
| 部署路線判定 | {部署路線、工作樹路線二選一,附 `tools/deploy-route.sh` 的結束碼} |
|
||||||
|
| 驗證結果 | {在新的 CLI 行程裡驗證的結果,寫實際看到的行為,不寫「已驗證」三個字了事} |
|
||||||
|
|
||||||
|
### 優化建議
|
||||||
|
|
||||||
|
> 只有 `skill-check` 那一節要附這張表,其餘四支不附。
|
||||||
|
> 下一輪 `skill-check` 會先讀回這張表:「決議」欄寫著 `套用` 或 `延後` 的項目不重複掃、不重複問。
|
||||||
|
> 所以「決議」與「決議日期」兩欄不得留空,留空等於下一輪讀不懂,只好重問一次。
|
||||||
|
|
||||||
|
| 面向 | 技能 | 證據 | 建議 | 決議 | 決議日期 |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| {1 可平行化、2 可下放工具、3 重複來回、4 冗餘檢查、5 閘門時機、6 成本效率 六選一} | {技能名} | {file:line} | {一句話寫怎麼改} | {套用、延後、自訂 三選一} | {yyyy-MM-dd} |
|
||||||
|
|
||||||
|
| 欄位 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 面向 | 六個優化面向之一,名稱與 `skill-check` 的面向表逐字相同 |
|
||||||
|
| 技能 | 被建議的技能,跨技能的建議一列一支 |
|
||||||
|
| 證據 | `file:line`,指得到才寫得進來 |
|
||||||
|
| 建議 | 一句話寫怎麼改。會削弱防護的建議要在這裡點名被削弱的是哪一道 |
|
||||||
|
| 決議 | 使用者當輪的決定:`套用`、`延後`、`自訂`。`自訂` 要在同一列的建議欄補上實際採用的做法 |
|
||||||
|
| 決議日期 | 做出決定那一天。決議欄寫 `延後` 的項目,下一輪照這個日期認定為已決議,照樣不重問 |
|
||||||
|
|
||||||
|
## {yyyy-MM-dd HH:mm} — {上一次異動的一句話}
|
||||||
|
|
||||||
|
(上一次的內容原樣留著,不改、不刪。)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# 技能盤點目錄
|
||||||
|
|
||||||
|
> 由 `jsc-meta:tooling-guide` 維護。這是目錄頁 `TOOLING_CONTENTS`。
|
||||||
|
> 一個區塊代表一組「機器、CLI、帳號」。同一台機器裝了幾支 CLI,就有幾個區塊。
|
||||||
|
> 寫入一律用 `jsc-gitea/tools/wiki-contents.sh upsert TOOLING 1 "TOOLING_{HASH}" {區塊檔} templates/tooling-contents.md`:`<TYPE>` 填 `TOOLING`,`<key>` 填這一筆的 H2 標題,也就是內容頁頁名 `TOOLING_{HASH}`,第四個參數是整個 H2 區塊的檔案,不是一列表格。
|
||||||
|
> `<key-col>` 填 `1`。這個參數填的是**舊表格裡持有「內容頁連結」那一欄的序號**,只在舊頁還是 markdown 表格、需要自動轉檔時才用得到:轉檔時工具從那一欄的連結網址取最後一段路徑當 H2 標題。序號要照**線上那一頁實際的欄位排法**數,不是照這份範本的欄位排法。這裡之所以是 `1`:線上 `TOOLING_CONTENTS` 目前是空頁,沒有舊表格要轉,而這份範本的「盤點頁」連結就在第 1 欄。線上哪一天真有舊表格,就先讀回線上那一頁、看連結落在第幾欄,再照那個序號填。頁面已經是條列格式時這個參數完全不影響結果。
|
||||||
|
> 它讀回整頁、換掉 H2 標題相符的那個區塊、找不到才附加到頁尾,最後整頁寫回。不得手工改目錄頁。
|
||||||
|
> `TOOLING_{HASH}` 的 `{HASH}` 交給 `jsc-gitea/tools/hash-id` 產生,雜湊來源見 `jsc-meta/references/guidelines.md` 的「Wiki 頁命名總表」。
|
||||||
|
> 連結寫法:所有連結一律 `[{文字}]({連結})`,網址放 `jsc-gitea/tools/gitea.sh wiki-url` 印出的絕對網址,不用 `[[...]]`。寫入前先把每個連結交給 `jsc-gitea/tools/link-check.sh` 驗證,結束碼 0 才寫入;驗證走 API,不看網頁狀態碼,私有存取庫的網頁網址對未登入請求會回 404。
|
||||||
|
>
|
||||||
|
> 欄位說明:一個區塊固定八條,順序照下面從上到下。
|
||||||
|
>
|
||||||
|
> - 盤點頁:`[TOOLING_{HASH}]({連結})`,連結是 `gitea.sh wiki-url` 印出的絕對網址。與 H2 標題指的是同一頁,標題不放連結,這一條才放。
|
||||||
|
> - 主機:這次盤點的機器名,與雜湊第一段相同。
|
||||||
|
> - 工具:CLI 代號,與雜湊第二段相同。
|
||||||
|
> - 帳號:執行盤點的登入帳號,與雜湊第三段相同。
|
||||||
|
> - plugin 數:該頁「已安裝 plugin」一節的筆數。
|
||||||
|
> - 技能數:該頁「可用技能」一節的筆數。
|
||||||
|
> - hook 接線:該頁「hook 接線狀態」對這支 CLI 的判定。
|
||||||
|
> - 最後盤點:該頁盤點時間,與內容頁標頭一致。
|
||||||
|
>
|
||||||
|
> 為什麼 H2 標題寫頁名:`TOOLING_{HASH}` 的雜湊來源就是「主機、工具、帳號」三段,所以標題相符等於三段都相符,一個鍵就夠。以前靠三個欄位逐欄比對,任一欄的寫法差一點(FQDN 對短主機名、大小寫不同)就比不到既有那一筆,同一台機器同一支 CLI 於是多出第二筆,兩筆都寫得成功,也都看不出被分裂。
|
||||||
|
>
|
||||||
|
> 寫入規則:
|
||||||
|
>
|
||||||
|
> - 一律走 `jsc-gitea/tools/wiki-contents.sh upsert`,鍵是 H2 標題 `TOOLING_{HASH}`。
|
||||||
|
> - 那支腳本先整頁讀回來,再逐個比對 H2 標題。
|
||||||
|
> - 標題相同就整塊換掉,區塊裡的每一條都覆寫成本次結果。
|
||||||
|
> - 找不到相同的標題,才附加一個新區塊。
|
||||||
|
> - 只動自己那一個區塊,別人的區塊原樣保留。
|
||||||
|
> - 禁止整頁覆蓋。這一頁是共用目錄,覆蓋等於刪掉別台機器的紀錄。
|
||||||
|
> - 讀不到舊內容就中止,不附加區塊,也不寫入。
|
||||||
|
> - 這一頁不留任何 markdown 表格。舊頁還是表格時由 `wiki-contents.sh` 自動轉成條列後寫回,不要手工搬。
|
||||||
|
> - 先寫內容頁,成功了才回來更新這個區塊。目錄頁指向一個寫失敗的頁,比缺一筆更難查。
|
||||||
|
|
||||||
|
## TOOLING_{HASH}
|
||||||
|
|
||||||
|
- 盤點頁:[TOOLING_{HASH}]({wiki-url 印出的絕對網址})
|
||||||
|
- 主機:{主機名}
|
||||||
|
- 工具:{claude、codex、copilot、antigravity、kiro 五選一}
|
||||||
|
- 帳號:{登入帳號}
|
||||||
|
- plugin 數:{n}
|
||||||
|
- 技能數:{n}
|
||||||
|
- hook 接線:{wired、degraded、unwired、unknown 四選一}
|
||||||
|
- 最後盤點:{yyyy-MM-dd HH:mm}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
# 技能盤點 — {主機名}/{工具名稱}/{登入帳號}
|
||||||
|
|
||||||
|
> 由 `jsc-meta:tooling-guide` 維護。這是盤點頁 `TOOLING_{HASH}`。
|
||||||
|
> 這頁記的是「現在這台機器上這支 CLI 長什麼樣」。每次盤點覆寫整頁,不保留歷史。
|
||||||
|
> 覆寫是刻意的:舊的安裝內容與接線狀態早就不成立,留著只會讓人照著過期的事實下判斷。
|
||||||
|
> 目錄頁 `TOOLING_CONTENTS` 的規則相反:那頁是大標題加條列,一筆一個 H2 區塊,每次只更新自己那一個區塊,兩者不要混用。本頁是內容頁,版面維持圖表優先。
|
||||||
|
> 要看技能組歷次異動請翻 `SKILLSET_{HASH}`,累積紀錄在那一頁。
|
||||||
|
|
||||||
|
## 本次盤點
|
||||||
|
|
||||||
|
| 項目 | 內容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 盤點時間 | {yyyy-MM-dd HH:mm} |
|
||||||
|
| 主機 | {主機名} |
|
||||||
|
| 工具 | {claude、codex、copilot、antigravity、kiro 五選一} |
|
||||||
|
| 帳號 | {登入帳號} |
|
||||||
|
| 資料來源 | `meta/tools/inventory-tooling.sh` 單次執行的輸出 |
|
||||||
|
| plugins 根目錄 | {絕對路徑} |
|
||||||
|
| marketplace | {絕對路徑} |
|
||||||
|
|
||||||
|
> 全頁事實出自上面那一次執行。同一趟不重跑 `list-skills.sh`、`detect-clis.sh` 與 `wire-cli.sh status`,避免同一件事出現兩個答案。
|
||||||
|
|
||||||
|
## 資料新鮮度
|
||||||
|
|
||||||
|
對應輸出的 `Source freshness` 一節。
|
||||||
|
|
||||||
|
| 項目 | 狀態 |
|
||||||
|
| --- | --- |
|
||||||
|
| 本機 domain 清單 | {synced、stale accepted、blocked 三選一} |
|
||||||
|
| marketplace | {絕對路徑} |
|
||||||
|
|
||||||
|
## 現況摘要
|
||||||
|
|
||||||
|
對應輸出的 `現況摘要` 一節。
|
||||||
|
|
||||||
|
| 項目 | 數量 |
|
||||||
|
| --- | ---: |
|
||||||
|
| 已註冊 plugin domain | {n} |
|
||||||
|
| 已掃到技能 | {n} |
|
||||||
|
| 已偵測 CLI | {n} |
|
||||||
|
|
||||||
|
## 已安裝 plugin
|
||||||
|
|
||||||
|
對應輸出的 `Supported plugins` 一節。
|
||||||
|
|
||||||
|
| domain | 版本 | 本機路徑 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| {domain} | {版本或「缺本機存取庫」} | {絕對路徑} |
|
||||||
|
|
||||||
|
## 可用技能
|
||||||
|
|
||||||
|
對應輸出的 `Supported skills` 一節。
|
||||||
|
|
||||||
|
| domain | skill | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| {domain} | {技能名} | {一句用途} |
|
||||||
|
|
||||||
|
## 已偵測 CLI
|
||||||
|
|
||||||
|
對應輸出的 `Supported CLIs` 一節。只留這一頁對應的那支 CLI,其餘 CLI 各自有自己的盤點頁。
|
||||||
|
|
||||||
|
| CLI | 執行檔 | 版本 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| {工具名稱} | {絕對路徑} | {版本字串} |
|
||||||
|
|
||||||
|
## 可用工具
|
||||||
|
|
||||||
|
對應輸出的 `Supported tools` 一節。
|
||||||
|
|
||||||
|
| domain | tool | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| {domain} | {腳本檔名} | {檔頭第二行的說明} |
|
||||||
|
|
||||||
|
## hook 管理
|
||||||
|
|
||||||
|
對應輸出的 `Hook management` 一節。
|
||||||
|
|
||||||
|
| hook | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
| {腳本檔名} | {檔頭第二行的說明} |
|
||||||
|
|
||||||
|
## hook 接線狀態
|
||||||
|
|
||||||
|
對應輸出的 `Hook wiring status` 一節。只留這一頁對應的那支 CLI。
|
||||||
|
|
||||||
|
| CLI | 結束碼 | 狀態 |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| {工具名稱} | {n} | {狀態字串;查不到就寫「未知狀態,結束碼 {n}」} |
|
||||||
|
|
||||||
|
## plugin 與技能管理
|
||||||
|
|
||||||
|
對應輸出的 `Plugin and skill management` 一節。
|
||||||
|
|
||||||
|
| 需求 | 入口 |
|
||||||
|
| --- | --- |
|
||||||
|
| 新增技能 | `jsc-meta:skill-new` |
|
||||||
|
| 更新單一技能 | `jsc-meta:skill-update` |
|
||||||
|
| 批次更新技能組 | `jsc-meta:skillset-update` |
|
||||||
|
| 刪除技能 | `jsc-meta:skill-delete` |
|
||||||
|
| 安裝、更新、移除整組 plugin | `jsc-cli:deploy` |
|
||||||
|
| 例行稽核 | `jsc-meta:skill-check` |
|
||||||
|
| 重新接線 hook | `jsc-hooks:hooks-install` |
|
||||||
|
|
||||||
|
## 例行檢查
|
||||||
|
|
||||||
|
對應輸出的 `Operational checks` 一節。
|
||||||
|
|
||||||
|
| 檢查 | 負責的技能或 hook |
|
||||||
|
| --- | --- |
|
||||||
|
| 體檢目前環境 | `jsc-cli:doctor` |
|
||||||
|
| 修復體檢項目 | `jsc-cli:setup` |
|
||||||
|
| 版本前置檢查 | `jsc-hooks/hooks/version-guard.sh` |
|
||||||
|
| 部署後重啟閘門 | `jsc-hooks/hooks/restart-gate.sh` |
|
||||||
|
| 語言提示與掃描 | `jsc-hooks/hooks/ste100-guard.sh`、`jsc-hooks/hooks/lang-guard.sh` |
|
||||||
|
| 註解範圍檢查 | `jsc-hooks/hooks/comment-scope.sh` |
|
||||||
|
|
||||||
|
## 使用時機
|
||||||
|
|
||||||
|
對應輸出的 `Use this when` 一節。
|
||||||
|
|
||||||
|
- 這頁只回答「這台機器這支 CLI 現在裝了什麼」。
|
||||||
|
- 要動手安裝、更新或修復,照上面兩張表找對應的入口。
|
||||||
|
- 盤點時間離現在太久就重跑一次 `jsc-meta:tooling-guide`,不要拿舊頁當現況。
|
||||||
|
|
||||||
|
## 已知限制
|
||||||
|
|
||||||
|
| 限制 | 說明 |
|
||||||
|
| --- | --- |
|
||||||
|
| {限制項目} | {為什麼這一項在這台機器上查不到或不適用} |
|
||||||
Executable
+188
@@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# check-behaviors.sh — 檢查一個 domain 的技能行為清單 references/behaviors.md 有沒有跟 skills/ 對齊。
|
||||||
|
#
|
||||||
|
# 用法: check-behaviors.sh <domain-path>
|
||||||
|
#
|
||||||
|
# 檢查七項(格式合約見 jsc-meta references/guidelines.md 的「技能行為清單」一節):
|
||||||
|
# 1. 標題 — 第一行是「# jsc-{domain} 技能行為清單」,檔案不得有 UTF-8 BOM。
|
||||||
|
# 2. 節對技能 — 每支 skills/*/SKILL.md 一個「## {技能名}」節,名稱與目錄名逐字相同,不多不少。
|
||||||
|
# 3. 節順序 — 節的排列照技能目錄名的字典序(LC_ALL=C)。
|
||||||
|
# 4. 表格 — 每節恰好一張表,表頭是「| 項目 | 內容 |」。
|
||||||
|
# 5. 五個欄位 — 依序為 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,不多不少。
|
||||||
|
# 6. 內容 — 每一列的「內容」欄不得空白。
|
||||||
|
# 7. 收尾事件 — 「可驗證跡象」那一列要寫到 skill-end 或 events.jsonl。
|
||||||
|
#
|
||||||
|
# 為什麼要這支: 技能改了行為、清單沒跟著改,兩邊就漂移。漂移靠眼睛比對,10 個 domain 每次稽核
|
||||||
|
# 都要重做一遍,還會漏。這七項的輸入輸出固定,交給程式判定才穩。
|
||||||
|
#
|
||||||
|
# 為什麼加第 7 項: 技能的成敗只有技能自己寫得出來。hook 接在技能工具呼叫之後就觸發,那一刻
|
||||||
|
# 實際工作還在後面的模型輪次,看不到結果;有 start 沒有配對的 end 就是中止。收尾少寫這一筆,
|
||||||
|
# 那支技能每一次都會被算成中止,而且不會有任何錯誤訊息。跡象欄是清單裡唯一寫得下「跑完在
|
||||||
|
# 環境裡留下什麼」的地方,所以判準放在這一列,而不是另開一項只檢查文字。
|
||||||
|
# 本腳本是所有 domain 共用的稽核入口,這一項加進來之後,還沒補收尾事件的 domain 會開始回 1。
|
||||||
|
# 那是預期的結果,不是誤報:要修的是那些 domain 的技能與清單,不是把斷言放寬。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個不合格項目,格式 {behaviors.md 路徑}:{技能名或 -}:{說明}(stderr);
|
||||||
|
# 通過時在 stderr 印一行摘要。stdout 不印東西。
|
||||||
|
# 結束碼: 0=行為清單與 skills/ 相符,五個欄位齊全、內容欄非空,且每節都寫了收尾事件
|
||||||
|
# 1=不符:缺節、多節、順序不對、表格不對、缺欄位、欄位空白,或跡象欄沒寫收尾事件,
|
||||||
|
# 清單在 stderr
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 3=找不到 {domain-path}/references/behaviors.md,或找不到 {domain-path}/skills/,
|
||||||
|
# 或 skills/ 底下一支 SKILL.md 都沒有——**什麼都沒查**,不等於通過
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: check-behaviors.sh <domain-path>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 1 ] || usage
|
||||||
|
DOMAIN=${1%/}
|
||||||
|
[ -n "$DOMAIN" ] || usage
|
||||||
|
|
||||||
|
SKILLS="$DOMAIN/skills"
|
||||||
|
DOC="$DOMAIN/references/behaviors.md"
|
||||||
|
[ -d "$DOMAIN" ] || { echo "找不到 domain 路徑:$DOMAIN" >&2; exit 3; }
|
||||||
|
[ -d "$SKILLS" ] || { echo "找不到 skills/:$SKILLS" >&2; exit 3; }
|
||||||
|
[ -f "$DOC" ] || { echo "找不到行為清單:$DOC" >&2; exit 3; }
|
||||||
|
|
||||||
|
TMPD=$(mktemp -d) || { echo "無法建立暫存目錄" >&2; exit 3; }
|
||||||
|
trap 'rm -rf "$TMPD"' EXIT
|
||||||
|
|
||||||
|
TAB=$(printf '\t')
|
||||||
|
BOM=$(printf '\357\273\277')
|
||||||
|
FIELDS='觸發時機 關鍵步驟 外部呼叫 完成條件 可驗證跡象'
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
report() { # $1=技能名或 -,$2=說明
|
||||||
|
printf '%s:%s:%s\n' "$DOC" "$1" "$2" >&2
|
||||||
|
fail=1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 技能清單: skills/ 底下帶 SKILL.md 的目錄名,字典序。
|
||||||
|
for d in "$SKILLS"/*/; do
|
||||||
|
[ -f "${d}SKILL.md" ] || continue
|
||||||
|
n=${d%/}
|
||||||
|
echo "${n##*/}"
|
||||||
|
done | LC_ALL=C sort > "$TMPD/skills.txt"
|
||||||
|
[ -s "$TMPD/skills.txt" ] || { echo "skills/ 底下沒有任何 SKILL.md:$SKILLS" >&2; exit 3; }
|
||||||
|
|
||||||
|
# 解析 behaviors.md,攤平成三種記錄:
|
||||||
|
# SEC<TAB>{節名} 一個「## 」標題
|
||||||
|
# HDR<TAB>{節名} 一列表頭「| 項目 | 內容 |」
|
||||||
|
# ROW<TAB>{節名}<TAB>{項目}<TAB>{內容} 一列資料(分隔列不算)
|
||||||
|
awk '
|
||||||
|
function trim(s) { gsub(/^[ \t]+/, "", s); gsub(/[ \t]+$/, "", s); return s }
|
||||||
|
BEGIN { FS = "|"; sec = "-" }
|
||||||
|
{ sub(/\r$/, "") }
|
||||||
|
/^## / {
|
||||||
|
sec = trim(substr($0, 4))
|
||||||
|
printf "SEC\t%s\n", sec
|
||||||
|
next
|
||||||
|
}
|
||||||
|
/^[ \t]*\|/ {
|
||||||
|
item = trim($2)
|
||||||
|
body = ""
|
||||||
|
for (i = 3; i < NF; i++) body = (body == "" ? $i : body "|" $i)
|
||||||
|
body = trim(body)
|
||||||
|
if (item ~ /^[-: ]+$/ && body ~ /^[-: ]*$/) next
|
||||||
|
if (item == "項目" && body == "內容") { printf "HDR\t%s\n", sec; next }
|
||||||
|
printf "ROW\t%s\t%s\t%s\n", sec, item, body
|
||||||
|
}
|
||||||
|
' "$DOC" > "$TMPD/parsed.txt"
|
||||||
|
|
||||||
|
# --- 1. 標題 ---
|
||||||
|
first=$(head -1 "$DOC" | tr -d '\r')
|
||||||
|
case $first in
|
||||||
|
"$BOM"*) report - '檔頭帶 UTF-8 BOM,請改存無 BOM'; first=${first#"$BOM"} ;;
|
||||||
|
esac
|
||||||
|
# domain 名以 plugin.json 的 name 為準,checkout 目錄名只是退路。
|
||||||
|
# 目錄名是誰 clone 誰決定的,同一個 repo 換一台機器就可能叫別的名字,
|
||||||
|
# 拿它當唯一來源會在別人的工作區誤報一次「標題不符」。
|
||||||
|
dom=$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$DOMAIN/plugin.json" 2>/dev/null | head -n1)
|
||||||
|
if [ -z "$dom" ]; then
|
||||||
|
dom=$(cd "$DOMAIN" 2>/dev/null && pwd) || dom=$DOMAIN
|
||||||
|
dom=${dom##*/}
|
||||||
|
fi
|
||||||
|
dom=${dom#jsc-}
|
||||||
|
want_title="# jsc-$dom 技能行為清單"
|
||||||
|
[ "$first" = "$want_title" ] || report - "第一行要是「$want_title」,實際是「$first」"
|
||||||
|
|
||||||
|
# --- 2. 節對技能 ---
|
||||||
|
awk -F"$TAB" '$1 == "SEC" { print $2 }' "$TMPD/parsed.txt" > "$TMPD/sections.txt"
|
||||||
|
LC_ALL=C sort "$TMPD/sections.txt" > "$TMPD/sections-sorted.txt"
|
||||||
|
|
||||||
|
LC_ALL=C uniq -d "$TMPD/sections-sorted.txt" | while IFS= read -r s; do
|
||||||
|
[ -n "$s" ] && printf '%s:%s:%s\n' "$DOC" "$s" '同一支技能出現多個節,只留一節' >&2
|
||||||
|
done
|
||||||
|
if [ -n "$(LC_ALL=C uniq -d "$TMPD/sections-sorted.txt")" ]; then fail=1; fi
|
||||||
|
|
||||||
|
LC_ALL=C uniq "$TMPD/sections-sorted.txt" > "$TMPD/sections-uniq.txt"
|
||||||
|
LC_ALL=C comm -23 "$TMPD/skills.txt" "$TMPD/sections-uniq.txt" > "$TMPD/missing.txt"
|
||||||
|
LC_ALL=C comm -13 "$TMPD/skills.txt" "$TMPD/sections-uniq.txt" > "$TMPD/extra.txt"
|
||||||
|
|
||||||
|
while IFS= read -r s; do
|
||||||
|
[ -n "$s" ] || continue
|
||||||
|
report "$s" "skills/$s/SKILL.md 存在,行為清單缺這一節,請補「## $s」"
|
||||||
|
done < "$TMPD/missing.txt"
|
||||||
|
|
||||||
|
while IFS= read -r s; do
|
||||||
|
[ -n "$s" ] || continue
|
||||||
|
report "$s" "行為清單多這一節,skills/ 底下沒有這支技能,請移除或改名"
|
||||||
|
done < "$TMPD/extra.txt"
|
||||||
|
|
||||||
|
# --- 3. 節順序 ---
|
||||||
|
if ! cmp -s "$TMPD/sections.txt" "$TMPD/sections-sorted.txt"; then
|
||||||
|
report - '節的排列不是技能目錄名的字典序,請重排'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 4~6. 逐節查表格與五個欄位 ---
|
||||||
|
while IFS= read -r name; do
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
grep -q "^$name\$" "$TMPD/missing.txt" && continue
|
||||||
|
|
||||||
|
hdr=$(awk -F"$TAB" -v s="$name" '$1 == "HDR" && $2 == s' "$TMPD/parsed.txt" | wc -l)
|
||||||
|
hdr=$((hdr + 0))
|
||||||
|
if [ "$hdr" -eq 0 ]; then
|
||||||
|
report "$name" '這一節沒有表頭「| 項目 | 內容 |」,五個欄位無從判讀'
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[ "$hdr" -eq 1 ] || report "$name" "這一節有 $hdr 張表,合約規定恰好一張"
|
||||||
|
|
||||||
|
awk -F"$TAB" -v s="$name" '$1 == "ROW" && $2 == s { print $3 "\t" $4 }' \
|
||||||
|
"$TMPD/parsed.txt" > "$TMPD/rows.txt"
|
||||||
|
|
||||||
|
rows=$(wc -l < "$TMPD/rows.txt")
|
||||||
|
rows=$((rows + 0))
|
||||||
|
[ "$rows" -eq 5 ] || report "$name" "表格有 $rows 列,合約規定 5 列:$FIELDS"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while IFS="$TAB" read -r item body; do
|
||||||
|
i=$((i + 1))
|
||||||
|
[ "$i" -le 5 ] || { report "$name" "第 $i 列「$item」是多的,合約只收 5 列"; continue; }
|
||||||
|
want=$(echo "$FIELDS" | cut -d' ' -f"$i")
|
||||||
|
[ "$item" = "$want" ] || report "$name" "第 $i 列的項目要是「$want」,實際是「$item」"
|
||||||
|
[ -n "$body" ] || report "$name" "「$item」的內容欄空白,請補實際行為"
|
||||||
|
# 第 7 項只在跡象欄成立。認項目名不認列號: 列號錯位時上面那一行已經報過,
|
||||||
|
# 這裡再報一次只是同一個缺陷印兩遍,反而蓋掉真正沒寫收尾事件的那幾節。
|
||||||
|
if [ "$item" = '可驗證跡象' ]; then
|
||||||
|
case $body in
|
||||||
|
*skill-end*|*events.jsonl*) ;;
|
||||||
|
*) report "$name" '「可驗證跡象」沒寫到收尾的 skill-end 事件,請補上這一筆(見準則「執行狀態回報」)' ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done < "$TMPD/rows.txt"
|
||||||
|
done < "$TMPD/skills.txt"
|
||||||
|
|
||||||
|
# --- 節裡以外的表格 ---
|
||||||
|
if awk -F"$TAB" '$2 == "-" { found = 1 } END { exit found ? 0 : 1 }' "$TMPD/parsed.txt"; then
|
||||||
|
report - '有表格落在任何「## 」節之外,請搬進所屬技能的節裡'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$fail" -eq 0 ]; then
|
||||||
|
echo "行為清單檢查通過:$DOC 對上 $(wc -l < "$TMPD/skills.txt" | tr -d ' ') 支技能,五個欄位齊全,跡象欄都寫了收尾事件" >&2
|
||||||
|
else
|
||||||
|
echo "行為清單檢查不符:$DOC 與 $SKILLS 對不起來,逐項見上方" >&2
|
||||||
|
fi
|
||||||
|
exit $fail
|
||||||
Executable
+292
@@ -0,0 +1,292 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# check-delegate.sh — 檢查委派判定清單 tools/delegate-spec.tsv 有沒有跟本機實際技能對齊。
|
||||||
|
#
|
||||||
|
# 用法: check-delegate.sh [<plugins-root>]
|
||||||
|
# 不給參數就交給 tools/plugins-root.sh 推導根目錄,判準與 list-skills.sh 完全相同。
|
||||||
|
#
|
||||||
|
# 檢查五項:
|
||||||
|
# 1. 一一對應 — 清單一支技能一列,跟 list-skills.sh 的實際技能不多不少,也不重複。
|
||||||
|
# 2. 欄位齊全 — 12 欄,每一欄非空;四種結論各自的必填欄位都填了,不該填的填 -。
|
||||||
|
# 3. 欄位可填值 — verdict、way、trigger、recur、origin 只認固定詞彙;next 要指到
|
||||||
|
# 一支真的存在的技能。指到不存在的技能,助理會一路重試一支叫不出來的東西。
|
||||||
|
# 4. 唯讀指令 — probe 的填法與那一支腳本在不在,判準見下。
|
||||||
|
# 5. 版本號與來源 — 只印提示,不影響結束碼,理由見下。
|
||||||
|
#
|
||||||
|
# 為什麼要這支: 技能組每天在動,手工盤點只會過期。清單缺一列,助理就永遠不知道那支技能
|
||||||
|
# 存在;清單多一列,助理會去觸發一支不存在的技能,而且失敗不會自動暫停。這兩件事靠眼睛
|
||||||
|
# 比對每輪都會漏,交給程式判定才穩。
|
||||||
|
#
|
||||||
|
# 為什麼版本號落後只算提示: 版本號是 domain 層級的,同一個 domain 改一支技能,其餘技能的
|
||||||
|
# 版本號也會一起落後、也會被標成要複判。當成稽核缺失的話,每次發版整個 domain 都亮紅,
|
||||||
|
# 提示很快就會被當成雜訊忽略,真正要複判的那一支反而看不見。
|
||||||
|
#
|
||||||
|
# 為什麼 origin=seed 只算提示: 種入的那一批是依既有盤點填的,還沒正式走過決策樹,本來就
|
||||||
|
# 知道它待複核。它是「已經有結論、但要再確認一次」,不是「漏填」。列成缺失會讓清單從
|
||||||
|
# 第一天就是紅的,紅到全部複核完為止,這段期間所有真的缺失都會被蓋掉。
|
||||||
|
#
|
||||||
|
# probe 什麼算缺失、什麼只算提示: 缺失的判準只有一條——這一列照著跑,無人值守那一輪會出事。
|
||||||
|
# 四種算缺失:
|
||||||
|
# 一、way 含 invoke 或這一列不交,probe 卻填了指令。種入那一支會改拿指令當待辦簿的動作,
|
||||||
|
# 整支交出於是變成只跑一支腳本,那支技能該寫的頁一頁都不會寫,而且看起來完全正常。
|
||||||
|
# 二、way 只有 patrol 或 remind,probe 卻是減號。那幾列的動作原本一律退回只提醒,
|
||||||
|
# 等於判過要交、實際什麼都沒交出去。接不上就寫 pending 並講明理由,別留減號。
|
||||||
|
# 三、指令裡有金錢符號或波浪號,或用了 root、cli、repo 以外的代入點。前者在無人值守
|
||||||
|
# 那一輪解不出來也進不了允許清單,後者代不進去會原樣送進指令,兩種都是靜靜失敗。
|
||||||
|
# 四、指令指到的腳本在本機不存在。助理每一輪都會叫不到它,而失敗不會自動暫停。
|
||||||
|
# 兩種只算提示:
|
||||||
|
# 一、pending:{理由}。入口還沒接上是判過的結論,跟 origin=seed 同一類,不是漏填。
|
||||||
|
# 二、指令指到的 domain 本機沒有裝。那是這台機器少裝一個 domain,不是清單填錯——
|
||||||
|
# 算成缺失的話,只裝半套的機器每一輪都亮紅,真的填錯反而看不見。
|
||||||
|
#
|
||||||
|
# 輸出: 缺失一行一個,格式 {清單路徑}:{domain}/{name}:{說明}(stderr);
|
||||||
|
# 提示同格式(stdout),讓呼叫端分得開缺失與提示;統計摘要走 stderr。
|
||||||
|
# 結束碼: 0=清單與實際技能一一對應、必填欄位齊全(可能帶提示,提示不影響這一碼)
|
||||||
|
# 1=缺列、多列、重複列、欄位空著、欄位值不合法、next 指到不存在的技能,
|
||||||
|
# 或 probe 填錯欄位、指到不存在的腳本、用了認不得的代入點,清單在 stderr
|
||||||
|
# 2=用法錯誤(本腳本最多吃一個參數)
|
||||||
|
# 3=找不到 tools/delegate-spec.tsv、推導不出根目錄,或列不出任何技能——
|
||||||
|
# **什麼都沒查**,不等於通過
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: check-delegate.sh [<plugins-root>]' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -le 1 ] || usage
|
||||||
|
if [ "$#" -eq 1 ]; then
|
||||||
|
[ -n "$1" ] || usage
|
||||||
|
JSC_PLUGINS_ROOT=${1%/}
|
||||||
|
export JSC_PLUGINS_ROOT
|
||||||
|
fi
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
SPEC="$HERE/delegate-spec.tsv"
|
||||||
|
[ -f "$SPEC" ] || { echo "找不到委派判定清單:$SPEC" >&2; exit 3; }
|
||||||
|
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
ROOT=$(jsc_plugins_root) || { echo "推導不出技能組工作目錄根位置" >&2; exit 3; }
|
||||||
|
|
||||||
|
TMPD=$(mktemp -d) || { echo "無法建立暫存目錄" >&2; exit 3; }
|
||||||
|
trap 'rm -rf "$TMPD"' EXIT
|
||||||
|
TAB=$(printf '\t')
|
||||||
|
|
||||||
|
# 實際技能清單。真實來源只有 list-skills.sh 一處,本腳本不自己掃 SKILL.md:
|
||||||
|
# 兩套掃法會各自漂移,屆時分不出是清單錯還是掃法錯。
|
||||||
|
"$HERE/list-skills.sh" > "$TMPD/raw.txt" 2>/dev/null \
|
||||||
|
|| { echo "list-skills.sh 列不出技能,先跑 sync-domains.sh:$ROOT" >&2; exit 3; }
|
||||||
|
cut -f1,2 "$TMPD/raw.txt" | LC_ALL=C sort > "$TMPD/skills.txt"
|
||||||
|
[ -s "$TMPD/skills.txt" ] || { echo "掃不到任何技能:$ROOT" >&2; exit 3; }
|
||||||
|
|
||||||
|
# 各 domain 的現行版本號與本機目錄。domain 名以 plugin.json 的 name 為準,checkout 目錄名
|
||||||
|
# 只是退路:目錄名是誰 clone 誰決定的,換一台機器就可能叫別的名字。第三欄記那個目錄,
|
||||||
|
# probe 要拿它把 {root}/jsc-{domain} 換成本機的實際位置,才驗得到腳本在不在。
|
||||||
|
: > "$TMPD/versions.txt"
|
||||||
|
for p in "$ROOT"/*/plugin.json; do
|
||||||
|
[ -f "$p" ] || continue
|
||||||
|
n=$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$p" | head -n1)
|
||||||
|
v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$p" | head -n1)
|
||||||
|
case $n in
|
||||||
|
jsc-?*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
[ -n "$v" ] || continue
|
||||||
|
printf '%s\t%s\t%s\n' "${n#jsc-}" "$v" "${p%/plugin.json}" >> "$TMPD/versions.txt"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 清單資料列:# 開頭的註解與空行都不算。
|
||||||
|
grep -v '^[[:space:]]*#' "$SPEC" | grep -v '^[[:space:]]*$' > "$TMPD/rows.txt" || true
|
||||||
|
[ -s "$TMPD/rows.txt" ] || { echo "清單裡一列資料都沒有:$SPEC" >&2; exit 3; }
|
||||||
|
|
||||||
|
: > "$TMPD/fail-set.txt"
|
||||||
|
: > "$TMPD/fail-row.txt"
|
||||||
|
: > "$TMPD/hint.txt"
|
||||||
|
|
||||||
|
report() { # $1=domain/name 或 -,$2=說明
|
||||||
|
printf '%s:%s:%s\n' "$SPEC" "$1" "$2" >> "$TMPD/fail-set.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1. 一一對應 ---
|
||||||
|
cut -f1,2 "$TMPD/rows.txt" | LC_ALL=C sort > "$TMPD/keys.txt"
|
||||||
|
LC_ALL=C uniq -d "$TMPD/keys.txt" > "$TMPD/dup.txt"
|
||||||
|
LC_ALL=C uniq "$TMPD/keys.txt" > "$TMPD/keys-uniq.txt"
|
||||||
|
LC_ALL=C comm -23 "$TMPD/skills.txt" "$TMPD/keys-uniq.txt" > "$TMPD/missing.txt"
|
||||||
|
LC_ALL=C comm -13 "$TMPD/skills.txt" "$TMPD/keys-uniq.txt" > "$TMPD/extra.txt"
|
||||||
|
|
||||||
|
while IFS="$TAB" read -r d n; do
|
||||||
|
[ -n "${d:-}" ] || continue
|
||||||
|
report "$d/$n" '清單有兩列以上寫同一支技能,只留一列'
|
||||||
|
done < "$TMPD/dup.txt"
|
||||||
|
|
||||||
|
while IFS="$TAB" read -r d n; do
|
||||||
|
[ -n "${d:-}" ] || continue
|
||||||
|
report "$d/$n" '這支技能存在,清單缺這一列。沒有判定結果,助理永遠不知道它存在'
|
||||||
|
done < "$TMPD/missing.txt"
|
||||||
|
|
||||||
|
while IFS="$TAB" read -r d n; do
|
||||||
|
[ -n "${d:-}" ] || continue
|
||||||
|
report "$d/$n" '清單多這一列,本機沒有這支技能。請刪掉,否則助理會去觸發叫不出來的技能'
|
||||||
|
done < "$TMPD/extra.txt"
|
||||||
|
|
||||||
|
# --- 2~5. 逐列查欄位。多出來的列上面已經報過,這裡跳過,同一個缺陷不印兩遍。 ---
|
||||||
|
awk -F"$TAB" \
|
||||||
|
-v SPEC="$SPEC" -v SKILLS="$TMPD/skills.txt" -v VERS="$TMPD/versions.txt" \
|
||||||
|
-v FAILS="$TMPD/fail-row.txt" -v HINTS="$TMPD/hint.txt" '
|
||||||
|
function bad(k, m) { printf "%s:%s:%s\n", SPEC, k, m > FAILS }
|
||||||
|
function hint(k, m) { printf "%s:%s:%s\n", SPEC, k, m > HINTS }
|
||||||
|
function vlt(a, b, x, y, na, nb, n, i, ai, bi) {
|
||||||
|
na = split(a, x, "."); nb = split(b, y, ".")
|
||||||
|
n = (na > nb) ? na : nb
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
ai = (i <= na) ? x[i] + 0 : 0
|
||||||
|
bi = (i <= nb) ? y[i] + 0 : 0
|
||||||
|
if (ai < bi) return 1
|
||||||
|
if (ai > bi) return 0
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
function way_ok(w, t, m, i) {
|
||||||
|
m = split(w, t, ",")
|
||||||
|
for (i = 1; i <= m; i++) if (!(t[i] in WAY)) return 0
|
||||||
|
return (m > 0)
|
||||||
|
}
|
||||||
|
function trig_ok(t, e) {
|
||||||
|
if (t ~ /^at:..*$/) return 1
|
||||||
|
if (t ~ /^after:..*$/) { e = substr(t, 7); sub(/:.*$/, "", e); return (e in EV) }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
# probe 裡認不得的代入點,回傳第一個;全部認得回傳空字串。認不得的代入點代不進去,
|
||||||
|
# 會原樣送進指令,於是那一輪安安靜靜跑錯東西。
|
||||||
|
function ph_bad(s, t, p) {
|
||||||
|
t = s
|
||||||
|
while (match(t, /\{[^}]*\}/)) {
|
||||||
|
p = substr(t, RSTART + 1, RLENGTH - 2)
|
||||||
|
if (!(p in PH)) return p
|
||||||
|
t = substr(t, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
# probe 的第一個非環境變數指派詞,也就是腳本路徑本身。
|
||||||
|
function script_of(s, n, a, i) {
|
||||||
|
n = split(s, a, " ")
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
if (a[i] ~ /^[A-Za-z_][A-Za-z0-9_]*=/) continue
|
||||||
|
return a[i]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
split("full slice cond none", a, " "); for (i in a) VERDICT[a[i]] = 1
|
||||||
|
split("invoke patrol remind", b, " "); for (i in b) WAY[b[i]] = 1
|
||||||
|
split("seed judged", c, " "); for (i in c) ORIGIN[c[i]] = 1
|
||||||
|
split("worklog-written wp-merged stage-entered analyze-completed hook-error session-start session-end", \
|
||||||
|
d, " "); for (i in d) EV[d[i]] = 1
|
||||||
|
split("root cli repo", e, " "); for (i in e) PH[e[i]] = 1
|
||||||
|
}
|
||||||
|
FILENAME == SKILLS { sk[$1 "\t" $2] = 1; next }
|
||||||
|
FILENAME == VERS { cur[$1] = $2; ddir[$1] = $3; next }
|
||||||
|
{
|
||||||
|
key = $1 "/" $2
|
||||||
|
if (NF != 12) { bad(key, "這一列有 " NF " 欄,合約規定 12 欄"); next }
|
||||||
|
if (!(($1 "\t" $2) in sk)) next
|
||||||
|
verdict = $3; way = $4; slice = $5; human = $6
|
||||||
|
trig = $7; rec = $8; nxt = $9; ver = $10; org = $11; probe = $12
|
||||||
|
|
||||||
|
for (i = 1; i <= 12; i++)
|
||||||
|
if ($i == "") bad(key, "第 " i " 欄是空的。TSV 沒有空格這種值,不填就寫一個減號")
|
||||||
|
|
||||||
|
if (!(verdict in VERDICT)) {
|
||||||
|
bad(key, "verdict 只認 full、slice、cond、none,實際是「" verdict "」")
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# 不交的那一列只留理由:交出方式、切片、時間點、週期全部要空成減號。
|
||||||
|
# 留著值等於清單自己在說「不交但這樣交」,下一輪讀的人不知道該信哪一邊。
|
||||||
|
if (verdict == "none") {
|
||||||
|
if (way != "-") bad(key, "不交的列,way 要寫 -,實際是「" way "」")
|
||||||
|
if (slice != "-") bad(key, "不交的列,slice 要寫 -,實際是「" slice "」")
|
||||||
|
if (trig != "-") bad(key, "不交的列,trigger 要寫 -,實際是「" trig "」")
|
||||||
|
if (rec != "-") bad(key, "不交的列,recur 要寫 -,實際是「" rec "」")
|
||||||
|
if (human == "-") bad(key, "不交的列要寫不交的理由,否則下次分不出「判過決定不交」與「還沒判」")
|
||||||
|
} else {
|
||||||
|
if (way == "-") bad(key, "交出去的列一定要有交出方式,way 不可以是 -")
|
||||||
|
else if (!way_ok(way)) bad(key, "way 只認 invoke、patrol、remind,多個用半形逗號隔開,實際是「" way "」")
|
||||||
|
if (!trig_ok(trig)) bad(key, "trigger 要寫 at:{時間} 或 after:{固定事件名},實際是「" trig "」")
|
||||||
|
if (rec != "once" && rec !~ /^every:..*$/ && rec !~ /^cron:..*$/)
|
||||||
|
bad(key, "recur 只認 once、every:{間隔}、cron:{式子},實際是「" rec "」")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verdict == "full") {
|
||||||
|
if (slice != "-") bad(key, "全交是整支都交,slice 要寫 -,實際是「" slice "」")
|
||||||
|
if (human != "-") bad(key, "全交沒有留在人手上的那一半,human 要寫 -,實際是「" human "」")
|
||||||
|
}
|
||||||
|
# 切片交與條件式交少了「留在人手上的那一半」,助理下一輪會把整支技能當成可交的一路跑完。
|
||||||
|
if (verdict == "slice" || verdict == "cond") {
|
||||||
|
if (slice == "-") bad(key, "交出的那一段沒寫,slice 不可以是 -")
|
||||||
|
if (human == "-") bad(key, "留在人手上的那一半沒寫,助理下一輪會把整支技能當成可交的一路跑完")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nxt !~ /^jsc-[a-z0-9-]+:[a-z0-9-]+$/)
|
||||||
|
bad(key, "next 要寫成 jsc-{domain}:{name},實際是「" nxt "」")
|
||||||
|
else {
|
||||||
|
n2 = substr(nxt, 5); sub(/:/, "\t", n2)
|
||||||
|
if (!(n2 in sk)) bad(key, "next 指到不存在的技能「" nxt "」,助理會一路重試叫不出來的東西")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(org in ORIGIN)) bad(key, "origin 只認 seed 與 judged,實際是「" org "」")
|
||||||
|
else if (org == "seed") hint(key, "origin=seed:依既有盤點種入,還沒走過決策樹,待複核")
|
||||||
|
|
||||||
|
if (ver !~ /^[0-9]+(\.[0-9]+)*$/) bad(key, "version 要是 plugin 版本號,實際是「" ver "」")
|
||||||
|
else if (($1 in cur) && vlt(ver, cur[$1]))
|
||||||
|
hint(key, "判定時記的版本號 " ver " 落後 jsc-" $1 " 現行的 " cur[$1] ",要複判")
|
||||||
|
|
||||||
|
# --- probe:那一段唯讀盤點實際要跑的指令。判準見檔頭「什麼算缺失、什麼只算提示」。 ---
|
||||||
|
if (verdict == "none" || way ~ /(^|,)invoke(,|$)/) {
|
||||||
|
if (probe != "-")
|
||||||
|
bad(key, "way 含 invoke 或這一列不交,probe 要寫 -,實際是「" probe "」。填了指令,種入那一支會改拿指令當動作,整支交出就變成只跑一支腳本")
|
||||||
|
} else if (probe == "-") {
|
||||||
|
bad(key, "way 只有 patrol 或 remind,probe 不可以是 -:沒有指令的那幾筆一律退回只提醒,等於判過要交卻什麼都沒交出去。接不上就寫 pending:{理由}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (probe ~ /^pending:/) {
|
||||||
|
if (probe == "pending:") bad(key, "pending 後面要寫不接的理由,留白的話下一輪分不出是刻意還是漏填")
|
||||||
|
else hint(key, "probe=" probe ":唯讀盤點的入口還沒接上,待接線")
|
||||||
|
} else if (probe != "-") {
|
||||||
|
if (probe ~ /[$~]/)
|
||||||
|
bad(key, "probe 裡有金錢符號或波浪號,實際是「" probe "」。那兩種寫法在無人值守那一輪解不出來,也進不了允許清單,會被靜靜擋掉")
|
||||||
|
p2 = ph_bad(probe)
|
||||||
|
if (p2 != "")
|
||||||
|
bad(key, "probe 用了認不得的代入點「{" p2 "}」,只認 {root}、{cli}、{repo}。代不進去的字面值會原樣送進指令")
|
||||||
|
sp = script_of(probe)
|
||||||
|
if (sp !~ /^\{root\}\/jsc-[a-z0-9-]+\/..*$/)
|
||||||
|
bad(key, "probe 的腳本路徑要寫成 {root}/jsc-{domain}/… ,實際是「" sp "」")
|
||||||
|
else {
|
||||||
|
pd = sp; sub(/^\{root\}\/jsc-/, "", pd); sub(/\/.*$/, "", pd)
|
||||||
|
rest = sp; sub(/^\{root\}\/jsc-[a-z0-9-]+\//, "", rest)
|
||||||
|
if (!(pd in ddir))
|
||||||
|
hint(key, "probe 指到的 jsc-" pd " 本機沒有裝,這一列的腳本這一次驗不到")
|
||||||
|
else if (system("test -f '\''" ddir[pd] "/" rest "'\''") != 0)
|
||||||
|
bad(key, "probe 指到的腳本不存在:" ddir[pd] "/" rest "。助理每一輪都會叫不到它,而失敗不會自動暫停")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$TMPD/skills.txt" "$TMPD/versions.txt" "$TMPD/rows.txt"
|
||||||
|
|
||||||
|
cat "$TMPD/fail-set.txt" "$TMPD/fail-row.txt" > "$TMPD/fails.txt"
|
||||||
|
|
||||||
|
if [ -s "$TMPD/hint.txt" ]; then
|
||||||
|
LC_ALL=C sort "$TMPD/hint.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rows=$(wc -l < "$TMPD/rows.txt" | tr -d ' ')
|
||||||
|
skills=$(wc -l < "$TMPD/skills.txt" | tr -d ' ')
|
||||||
|
hints=$(wc -l < "$TMPD/hint.txt" | tr -d ' ')
|
||||||
|
|
||||||
|
if [ -s "$TMPD/fails.txt" ]; then
|
||||||
|
LC_ALL=C sort "$TMPD/fails.txt" >&2
|
||||||
|
echo "委派判定清單不符:$SPEC 有 $rows 列,本機有 $skills 支技能,逐項見上方(另有 $hints 則提示,見 stdout)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "委派判定清單檢查通過:$SPEC 的 $rows 列對上 $skills 支技能,必填欄位齊全(另有 $hints 則提示,見 stdout)" >&2
|
||||||
|
exit 0
|
||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# check-link-format.sh — 檢查一個 domain 存取庫的 markdown 連結寫法。
|
||||||
|
#
|
||||||
|
# 用法: check-link-format.sh <domain-path>
|
||||||
|
#
|
||||||
|
# 檢查一項(掃 {domain-path} 底下全部 *.md,跳過 .git):
|
||||||
|
# 連結一律 [{文字}]({連結})。雙括號那種同 wiki 連結一個都不留。
|
||||||
|
#
|
||||||
|
# 為什麼要有這支: 雙括號只在目前這個 wiki 內解析。指到別的存取庫時不會報錯,
|
||||||
|
# 畫面上看起來像正常文字或死連結,巡不到也修不了。人工比對每輪都會漏,交給程式判。
|
||||||
|
#
|
||||||
|
# 判定範圍與其極限: 只看**渲染得出來的正文**。行內程式碼(單引號反引號夾起來的那段)
|
||||||
|
# 與圍籬程式碼區塊都先剝掉再比,理由有二:
|
||||||
|
# 1. 文件解釋「不要用雙括號」時,本來就要把那個寫法原樣寫出來,那是說明不是連結。
|
||||||
|
# 2. shell 腳本的條件測試語法長得一模一樣,範例貼進 markdown 就會被誤判。
|
||||||
|
# 代價講白: 有人把真正的連結寫進程式碼區塊,這支抓不到;那種寫法本來也不會被渲染成連結。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個不合格項目,格式 {檔案}:{行號}:{命中內容}(stdout);統計摘要走 stderr。
|
||||||
|
# 結束碼: 0=掃到 markdown 且全部通過
|
||||||
|
# 1=有不合格項目(清單在 stdout)
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 3=domain 路徑不存在,或底下一個 *.md 都沒有——**什麼都沒掃**,不等於通過
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: check-link-format.sh <domain-path>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 1 ] || usage
|
||||||
|
DOMAIN=${1%/}
|
||||||
|
[ -n "$DOMAIN" ] || usage
|
||||||
|
[ -d "$DOMAIN" ] || { echo "找不到 domain 路徑:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
|
||||||
|
find "$DOMAIN" -name .git -prune -o -type f -name '*.md' -print 2>/dev/null | sort > "$TMP"
|
||||||
|
[ -s "$TMP" ] || { echo "底下沒有 *.md,無文件可掃:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
total=$(wc -l < "$TMP" | tr -d ' ')
|
||||||
|
|
||||||
|
hits=$(
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
awk '
|
||||||
|
FNR == 1 { fence = 0 }
|
||||||
|
# 圍籬起訖各自成行,起與訖用同一個判準,所以一個旗標開關就夠。
|
||||||
|
/^[[:space:]]*(```|~~~)/ { fence = !fence; next }
|
||||||
|
fence { next }
|
||||||
|
{
|
||||||
|
line = $0
|
||||||
|
gsub(/`[^`]*`/, "", line)
|
||||||
|
if (match(line, /\[\[[^]]*\]\]/)) {
|
||||||
|
printf "%s:%d:%s\n", FILENAME, FNR, substr(line, RSTART, RLENGTH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' $(cat "$TMP")
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ -n "$hits" ]; then
|
||||||
|
printf '%s\n' "$hits"
|
||||||
|
echo "連結寫法檢查有不合格項目:共掃 $total 份文件,清單見 stdout" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "連結寫法檢查通過:$total 份文件,連結全部為 [{文字}]({連結})" >&2
|
||||||
|
exit 0
|
||||||
Executable
+142
@@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# check-page-name.sh — 比對 wiki 頁名樣式三處是否一致。
|
||||||
|
#
|
||||||
|
# 用法: check-page-name.sh <root>
|
||||||
|
# <root> 是放 domain 存取庫的目錄,例如 /root/plugins。
|
||||||
|
# 目錄名同時吃 {domain} 與 jsc-{domain} 兩種寫法,也吃已安裝版面的版本目錄。
|
||||||
|
#
|
||||||
|
# 查哪三處:
|
||||||
|
# 1. {gitea}/tools/page-name.sh — 頁名樣式正本
|
||||||
|
# 2. {hooks}/hooks/comment-scope.sh — hook 內建的一份
|
||||||
|
# 3. {log}/tools/worklog-pending.sh — 工作日誌暫存內建的一份
|
||||||
|
#
|
||||||
|
# 為什麼三處各留一份,不共用函式: hook 必須自足。hook 在執行期去載別的 plugin 路徑,
|
||||||
|
# 那個 plugin 沒裝、版本不同、或路徑換了,hook 就當場失效,而且失效是安靜的。
|
||||||
|
# 共用函式換來的一致,付出的是 hook 的可用性。所以改成三處各自實作,一致性由稽核時比對。
|
||||||
|
#
|
||||||
|
# 每處斷言四項(判準見 jsc-meta references/guidelines.md 的「Wiki 頁命名總表」):
|
||||||
|
# 前兩處比對整個頁名,四項全查;第三處只驗純雜湊,沒有型別前綴,只查後兩項長度。
|
||||||
|
# 1. 型別清單 — 十五種型別逐一出現(只查比對整個頁名的那兩處): QUESTION、PLAN、ANALYZE、DELIVER、MAINTAIN、REPO、
|
||||||
|
# LOG、LEARN、ERROR、CHECK、REPORT、SKILLSET、TOOLING、MONITOR、CONTENTS。
|
||||||
|
# CONTENTS 是第十五種型別,「接受 CONTENTS」由這一項涵蓋。
|
||||||
|
# 2. 未知型別 — 出現 {某型別}_CONTENTS 或 {某型別}_{HASH} 的字樣,那個型別要在十五種之內。
|
||||||
|
# 3. 40 碼 — 接受完整 SHA-1 的 40 碼長度。
|
||||||
|
# 4. 8 碼 — 仍接受舊的 8 碼長度,遷移期間的舊頁名才讀得到。
|
||||||
|
# 5. H 開頭 — 仍接受 H 加 7 碼的舊頁名。這一項單獨列,因為三處很容易「一致地只收
|
||||||
|
# 純十六進位」——一致但都是錯的,前四項照樣全過。
|
||||||
|
#
|
||||||
|
# 判定方式與其極限: 本腳本做的是**文字層**比對,不執行那三支腳本。
|
||||||
|
# 三支的寫法各不相同(正規表示式、case 樣式、長度比較),沒有共同的可執行介面可以問,
|
||||||
|
# 要問就得各寫一套呼叫方式,那等於把三處的差異又抄第四份。
|
||||||
|
# 所以長度證據同時收樣式量詞({40}、{8,40})與檔頭文字(「40 碼」),兩者都算數。
|
||||||
|
# 代價講白: 這支抓得到「某一處漏了一種型別或一種長度」,抓不到「樣式寫法本身有錯」。
|
||||||
|
# 後者仍要人看,或由那三支自己的測試涵蓋。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個不合格項目,格式 {檔案}:{說明}(stderr);每處的判定摘要走 stdout。
|
||||||
|
# 結束碼: 0=三處一致,五項斷言全過
|
||||||
|
# 1=有不一致或有缺項,清單在 stderr(找不到其中一處或兩處也算這一碼)
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 3=三處一支都找不到——**什麼都沒查**,不等於通過。先確認 <root> 指對地方再重跑
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: check-page-name.sh <root>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 1 ] || usage
|
||||||
|
ROOT=${1%/}
|
||||||
|
[ -n "$ROOT" ] || usage
|
||||||
|
[ -d "$ROOT" ] || { echo "找不到根目錄:$ROOT" >&2; exit 3; }
|
||||||
|
|
||||||
|
# 十五種型別。這一行是本腳本的唯一真實來源,改動時同步 guidelines.md 的「Wiki 頁命名總表」。
|
||||||
|
TYPES='QUESTION PLAN ANALYZE DELIVER MAINTAIN REPO LOG LEARN ERROR CHECK REPORT SKILLSET TOOLING MONITOR CONTENTS'
|
||||||
|
|
||||||
|
# 長度證據的樣式。同時收樣式量詞與檔頭文字,理由見檔頭「判定方式與其極限」。
|
||||||
|
LEN40='\{40\}|\{8,40\}|\{40,40\}|40 碼|(-eq|=|\||\()[[:space:]]*40([^0-9]|$)'
|
||||||
|
LEN8='\{8\}|\{8,40\}|\{8,8\}|8 碼|(-eq|=|\||\()[[:space:]]*8([^0-9]|$)'
|
||||||
|
# H 開頭的舊頁名。舊演算法把首碼落在 0-9ABC 的改寫成 H 加原前 7 碼,十六個首碼有十三個
|
||||||
|
# 會命中,所以既有頁名多半長這樣。少了這一項,三處可以「都只收純十六進位」而一致地錯。
|
||||||
|
LENH='H\[0-9A-F\]\{7\}|H\[0-9A-Fa-f\]\{7\}|H 加(上)?(原)?前 7 碼|H\*'
|
||||||
|
|
||||||
|
hit=0
|
||||||
|
found=0
|
||||||
|
|
||||||
|
note() { # $1=檔案 $2=說明
|
||||||
|
printf '%s:%s\n' "$1" "$2" >&2
|
||||||
|
hit=1
|
||||||
|
}
|
||||||
|
|
||||||
|
find_file() { # $1=domain 目錄名(不含 jsc- 前綴) $2=存取庫內相對路徑
|
||||||
|
for _d in "$ROOT/$1" "$ROOT/jsc-$1"; do
|
||||||
|
[ -f "$_d/$2" ] && { printf '%s\n' "$_d/$2"; return 0; }
|
||||||
|
done
|
||||||
|
# 已安裝版面:一個 plugin 一個版本目錄,取排序最後的一份(通常即最新版)
|
||||||
|
_c=$(ls "$ROOT/jsc-$1"/*/"$2" "$ROOT/$1"/*/"$2" 2>/dev/null | sort | tail -n1)
|
||||||
|
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_one() { # $1=檔案 $2=這一處的稱呼 $3=比對範圍:page=整個頁名 hash=只有雜湊
|
||||||
|
file=$1
|
||||||
|
label=$2
|
||||||
|
scope=$3
|
||||||
|
# 只有比對整個頁名的地方才該列型別。驗純雜湊的地方沒有型別前綴可比,
|
||||||
|
# 硬要它列十五種型別,等於逼它抄一份自己用不到的清單,下次改型別就多一處會漏。
|
||||||
|
[ "$scope" = 'page' ] || { check_len "$file"; printf '%s\t%s\n' "$label" "$file"; return 0; }
|
||||||
|
miss=''
|
||||||
|
for t in $TYPES; do
|
||||||
|
if ! grep -qE "(^|[^A-Za-z0-9_])$t([^A-Za-z0-9_]|\$)" "$file" 2>/dev/null; then
|
||||||
|
if [ -z "$miss" ]; then miss=$t; else miss="$miss、$t"; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -z "$miss" ] || note "$file" "型別清單缺 $miss"
|
||||||
|
|
||||||
|
# 反向查:出現了十五種以外的型別,代表某一處還留著已經改名或已經刪掉的型別。
|
||||||
|
unknown=''
|
||||||
|
for u in $(grep -oE '(^|[^A-Za-z0-9_])[A-Z][A-Z0-9]{2,}_(CONTENTS|\{HASH\})' "$file" 2>/dev/null \
|
||||||
|
| sed -e 's/^[^A-Z]*//' -e 's/_.*$//' | sort -u); do
|
||||||
|
case " $TYPES " in
|
||||||
|
*" $u "*) ;;
|
||||||
|
*) if [ -z "$unknown" ]; then unknown=$u; else unknown="$unknown、$u"; fi ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -z "$unknown" ] || note "$file" "出現十五種以外的型別 $unknown"
|
||||||
|
|
||||||
|
check_len "$file"
|
||||||
|
|
||||||
|
printf '%s\t%s\n' "$label" "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_len() { # $1=檔案。長度斷言三處都要過,驗純雜湊的那一處也不例外。
|
||||||
|
grep -qE "$LEN40" "$1" 2>/dev/null || note "$1" '沒有接受 40 碼長度的跡象,完整 SHA-1 的頁名會被判為不合法'
|
||||||
|
grep -qE "$LEN8" "$1" 2>/dev/null || note "$1" '沒有接受 8 碼長度的跡象,遷移期間的舊頁名會讀不到'
|
||||||
|
grep -qE "$LENH" "$1" 2>/dev/null || note "$1" '沒有接受 H 開頭舊頁名的跡象,八成的既有頁名會讀不到'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 三處逐一查。找不到就記一行,不中止:一次把三處的狀況都報回去,比修一處重跑一次快。
|
||||||
|
scan() { # $1=domain 目錄名 $2=相對路徑 $3=稱呼 $4=比對範圍 page|hash
|
||||||
|
if f=$(find_file "$1" "$2"); then
|
||||||
|
found=$((found + 1))
|
||||||
|
check_one "$f" "$3" "$4"
|
||||||
|
else
|
||||||
|
printf '%s/%s:%s\n' "$1" "$2" "在 $ROOT 底下找不到這一處(也試過 jsc-$1 與版本目錄)" >&2
|
||||||
|
hit=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
scan gitea tools/page-name.sh '正本' page
|
||||||
|
scan hooks hooks/comment-scope.sh 'hook' page
|
||||||
|
scan log tools/worklog-pending.sh '工作日誌暫存' hash
|
||||||
|
|
||||||
|
if [ "$found" -eq 0 ]; then
|
||||||
|
echo "三處一支都找不到,什麼都沒查:$ROOT" >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$hit" -eq 0 ]; then
|
||||||
|
echo "頁名樣式檢查通過:三處一致(型別十五種、40 碼、8 碼、H 加 7 碼)" >&2
|
||||||
|
else
|
||||||
|
echo "頁名樣式檢查有不一致:已查 $found 處,清單見 stderr" >&2
|
||||||
|
fi
|
||||||
|
exit $hit
|
||||||
Executable
+141
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# check-skill-paths.sh — 檢查一個 domain 存取庫的文件裡,寫出來的腳本路徑解不解得出來。
|
||||||
|
#
|
||||||
|
# 用法: check-skill-paths.sh <domain-path>
|
||||||
|
#
|
||||||
|
# 檢查兩項(掃 {domain-path} 底下的 skills/*/SKILL.md 與 references/*.md):
|
||||||
|
# 一、帶 domain 名的路徑(jsc-{domain}/tools/x.sh、{domain}/hooks/x.sh)——那個檔案要
|
||||||
|
# 真的在對應的存取庫底下。指到不存在的檔案算缺失。
|
||||||
|
# 二、不帶 domain 名的路徑(tools/x.sh、hooks/x.sh)——檔案在這個存取庫底下就只算提示,
|
||||||
|
# 不算缺失,理由見下面「為什麼不帶前綴只算提示」。檔案根本不在,才算缺失。
|
||||||
|
#
|
||||||
|
# 為什麼要有這支: lint-scripts.sh 掃的是 tools/ 與 hooks/ 目錄裡的檔案,它證明得了
|
||||||
|
# 「這個存取庫裡有這支腳本、語法沒問題」,卻證明不了「文件裡寫的那條路徑解得出那支腳本」。
|
||||||
|
# 兩件事差很遠。2026-09-04 實際踩到: 一份技能本文把自家腳本寫成不帶前綴的 tools/x.sh,
|
||||||
|
# 而那支技能的基底目錄是 skills/{名稱}/,照字面解出來是 skills/{名稱}/tools/x.sh,
|
||||||
|
# 結束碼 127。人工覆核那一輪看的是「tools/ 底下有沒有 x.sh」,有,於是就過了——
|
||||||
|
# 覆核的問題問錯了,所以每一輪都會再過一次。
|
||||||
|
#
|
||||||
|
# 為什麼不帶前綴只算提示,不算缺失:
|
||||||
|
# 不帶前綴的相對路徑會相對於**當下的工作目錄**解。在自己那個存取庫裡跑,剛好解得對;
|
||||||
|
# 換一個工作目錄就解到別人的 tools/ 底下,或者解不出來。兩種都不會有明確的錯誤訊息,
|
||||||
|
# 前者更糟——它會跑起來,跑的是另一支腳本。無人值守那一輪還多一層: 相對路徑進不了
|
||||||
|
# 權限允許清單(那邊比對的是完整字面絕對路徑),會被靜靜擋掉。
|
||||||
|
# 話說回來,這是整個技能組共通的寫法,一次上百處。把它判成缺失會讓每一個存取庫都紅,
|
||||||
|
# 而一份全紅的報告跟沒有報告一樣。所以先量出來、指得出是哪幾行,改不改由人決定。
|
||||||
|
#
|
||||||
|
# 判定範圍與其極限: 只認 tools/ 與 hooks/ 兩個目錄底下的 *.sh。templates/ 與 references/
|
||||||
|
# 底下的檔案不在這一支的範圍內——那些是資料不是入口,指錯了不會變成一支跑起來的別的程式。
|
||||||
|
# 跨 domain 的路徑要那個 domain 的存取庫也在這台機器上才驗得到;不在就印成提示,
|
||||||
|
# 不當缺失。這台機器沒裝,跟路徑寫錯,是兩件事。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一項,格式 {檔案}:{行號}:{類別}:{路徑}(stdout);統計摘要走 stderr。
|
||||||
|
# 類別 missing=指到不存在的檔案,unrooted=不帶前綴、相對於工作目錄解,
|
||||||
|
# unknown=跨 domain 但那個存取庫不在這台機器上。
|
||||||
|
# 結束碼: 0=掃到文件且沒有缺失(提示不算缺失)
|
||||||
|
# 1=有缺失(missing 那幾行)
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 3=domain 路徑不存在,或底下一份可掃的文件都沒有——**什麼都沒掃**,不等於通過
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: check-skill-paths.sh <domain-path>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 1 ] || usage
|
||||||
|
DOMAIN=${1%/}
|
||||||
|
[ -n "$DOMAIN" ] || usage
|
||||||
|
[ -d "$DOMAIN" ] || { echo "找不到 domain 路徑:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -P -- "$(dirname -- "$0")" && pwd -P)
|
||||||
|
# 根目錄推導與別的檢核腳本共用同一套規則。跨 domain 的路徑要靠它才找得到別的存取庫;
|
||||||
|
# 推不出來不是致命的,那幾筆改印成 unknown 提示。
|
||||||
|
ROOT=''
|
||||||
|
if [ -f "$HERE/plugins-root.sh" ]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
ROOT=$(jsc_plugins_root 2>/dev/null) || ROOT=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||||||
|
HITS=$(mktemp) || { rm -f "$TMP"; echo "無法建立暫存檔" >&2; exit 3; }
|
||||||
|
trap 'rm -f "$TMP" "$HITS"' EXIT
|
||||||
|
|
||||||
|
# skills/*/SKILL.md 與 references/*.md 兩處。README 不掃: 那裡的路徑是給人看的目錄,
|
||||||
|
# 不是叫誰去跑的入口。
|
||||||
|
find "$DOMAIN/skills" -type f -name 'SKILL.md' -print 2>/dev/null > "$TMP"
|
||||||
|
find "$DOMAIN/references" -type f -name '*.md' -print 2>/dev/null >> "$TMP"
|
||||||
|
sort -o "$TMP" "$TMP"
|
||||||
|
[ -s "$TMP" ] || { echo "底下沒有 skills/*/SKILL.md 也沒有 references/*.md,無文件可掃:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
TOTAL=$(wc -l < "$TMP" | tr -d ' ')
|
||||||
|
|
||||||
|
# 這個存取庫自己的 domain 名。目錄名可能帶 jsc- 前綴(安裝後)或不帶(工作目錄版面),
|
||||||
|
# 兩種都要認得,否則自家的路徑會被當成跨 domain 而驗不到。
|
||||||
|
SELF=$(basename -- "$DOMAIN")
|
||||||
|
SELF=${SELF#jsc-}
|
||||||
|
|
||||||
|
# 找某個 domain 的存取庫目錄。工作目錄版面不帶前綴、安裝後帶前綴,兩種都試。
|
||||||
|
domain_dir() { # $1=domain 名
|
||||||
|
[ -n "$ROOT" ] || return 1
|
||||||
|
if [ -d "$ROOT/jsc-$1" ]; then printf '%s' "$ROOT/jsc-$1"; return 0; fi
|
||||||
|
if [ -d "$ROOT/$1" ]; then printf '%s' "$ROOT/$1"; return 0; fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
|
||||||
|
# 第一趟:帶 domain 名的路徑。前面可能還接著根目錄代入點(例如 {CURRENT}/),
|
||||||
|
# 那不影響判定——要驗的是 {domain}/{tools|hooks}/{檔名} 這一段。
|
||||||
|
grep -noE '(jsc-)?[a-z][a-z0-9-]*/(tools|hooks)/[a-z0-9-]+\.sh' "$f" 2>/dev/null |
|
||||||
|
while IFS=: read -r ln path; do
|
||||||
|
dom=${path%%/*}
|
||||||
|
dom=${dom#jsc-}
|
||||||
|
rest=${path#*/}
|
||||||
|
if [ "$dom" = "$SELF" ]; then
|
||||||
|
[ -f "$DOMAIN/$rest" ] || printf '%s:%s:missing:%s\n' "$f" "$ln" "$path"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if dir=$(domain_dir "$dom"); then
|
||||||
|
[ -f "$dir/$rest" ] || printf '%s:%s:missing:%s\n' "$f" "$ln" "$path"
|
||||||
|
else
|
||||||
|
printf '%s:%s:unknown:%s\n' "$f" "$ln" "$path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 第二趟:不帶 domain 名的路徑。字元集把斜線排掉,第一趟已經算過的那幾筆才不會再算一次。
|
||||||
|
grep -noE '(^|[^-a-zA-Z0-9/_.{])(tools|hooks)/[a-z0-9-]+\.sh' "$f" 2>/dev/null |
|
||||||
|
while IFS=: read -r ln raw; do
|
||||||
|
path=$(printf '%s' "$raw" | sed 's|^[^t h]*||; s|^\(tools\|hooks\)|\1|')
|
||||||
|
case "$path" in
|
||||||
|
tools/*|hooks/*) ;;
|
||||||
|
*) path=${raw#?} ;;
|
||||||
|
esac
|
||||||
|
case "$path" in
|
||||||
|
tools/*|hooks/*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
if [ -f "$DOMAIN/$path" ]; then
|
||||||
|
printf '%s:%s:unrooted:%s\n' "$f" "$ln" "$path"
|
||||||
|
else
|
||||||
|
printf '%s:%s:missing:%s\n' "$f" "$ln" "$path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done < "$TMP" | sort -u > "$HITS"
|
||||||
|
|
||||||
|
N_MISS=$(grep -c ':missing:' "$HITS" 2>/dev/null || true)
|
||||||
|
N_UNROOT=$(grep -c ':unrooted:' "$HITS" 2>/dev/null || true)
|
||||||
|
N_UNKNOWN=$(grep -c ':unknown:' "$HITS" 2>/dev/null || true)
|
||||||
|
: "${N_MISS:=0}" "${N_UNROOT:=0}" "${N_UNKNOWN:=0}"
|
||||||
|
|
||||||
|
[ -s "$HITS" ] && cat "$HITS"
|
||||||
|
|
||||||
|
if [ "$N_MISS" -gt 0 ]; then
|
||||||
|
echo "腳本路徑檢查有缺失:共掃 $TOTAL 份文件,$N_MISS 條指到不存在的檔案,另有 $N_UNROOT 條不帶 domain 名、$N_UNKNOWN 條跨 domain 但存取庫不在這台機器上" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "腳本路徑檢查通過:共掃 $TOTAL 份文件,沒有指到不存在的檔案;另有 $N_UNROOT 條不帶 domain 名、$N_UNKNOWN 條跨 domain 但存取庫不在這台機器上,兩種都只是提示" >&2
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# delegate-spec.tsv — jsc 技能組的委派判定清單。一支技能一列,記「這一支能不能交給背景助理」。
|
||||||
|
#
|
||||||
|
# 判準本身寫在 references/delegate-criteria.md,四支技能異動技能(skill-new、skill-update、
|
||||||
|
# skill-delete、skillset-update)共用那一份。這張表只放判定結果,不放判準。
|
||||||
|
#
|
||||||
|
# 判定結果只放這裡,不寫進 SKILL.md 的 frontmatter:name 與 description 之外的自訂欄位,
|
||||||
|
# 有被各 CLI 的技能驗證擋掉的風險。新增或刪除技能時同時補上或刪掉這一列,
|
||||||
|
# tools/check-delegate.sh 才對得起來;缺列的技能,助理永遠不知道它存在。
|
||||||
|
#
|
||||||
|
# 欄位:domain<TAB>name<TAB>verdict<TAB>way<TAB>slice<TAB>human<TAB>trigger<TAB>recur<TAB>next<TAB>version<TAB>origin<TAB>probe
|
||||||
|
# domain 技能所屬 domain,去掉開頭的 jsc-。形狀比照 list-skills.sh 第一欄
|
||||||
|
# name 技能名。形狀比照 list-skills.sh 第二欄,兩欄合起來就是一支技能的身分
|
||||||
|
# verdict full=全交 slice=切片交 cond=條件式交 none=不交
|
||||||
|
# way 交出方式,多個用半形逗號隔開:invoke=觸發 patrol=巡檢 remind=提醒;不交寫 -
|
||||||
|
# slice 交出的那一段。全交寫 -,整支都交;條件式交要把條件與條件不成立時的行為
|
||||||
|
# 一起寫進來;不交寫 -
|
||||||
|
# human 留在人手上的那一半。切片交與條件式交必填——少了它,助理下一輪會把整支技能
|
||||||
|
# 當成可交的一路跑完。不交填不交的理由,否則下次分不出「判過決定不交」與
|
||||||
|
# 「還沒判」。全交寫 -
|
||||||
|
# trigger 第一次什麼時候到期:at:{ISO 時間},立刻要做寫 at:now;或 after:{事件名}。
|
||||||
|
# 事件名只認固定詞彙,其中三個一定要帶參數,少了參數會被擋下:
|
||||||
|
# 不帶參數:worklog-written、hook-error、session-start、session-end
|
||||||
|
# 要帶參數:wp-merged:{工作包代號}、stage-entered:{階段}、
|
||||||
|
# analyze-completed:{HASH}
|
||||||
|
# 為什麼要寫明參數形狀:那三個事件是「哪一個工作包合併了」而不是「有工作包
|
||||||
|
# 合併了」,少了參數就指不到特定的一件事。寫成不帶參數的形狀,登錄那一步會
|
||||||
|
# 回欄位不合法,那支技能於是靜靜沒有內建項——少了什麼要看種入回報的 bad=
|
||||||
|
# 那幾行才發現。不交寫 -
|
||||||
|
# recur 做完之後還要不要再排:once、every:{間隔}、cron:{式子}。事件型 trigger 配
|
||||||
|
# every:{間隔} 時,間隔讀作重新武裝的最短間隔。不交寫 -
|
||||||
|
# next 跑完之後建議接哪一支,寫成 jsc-{domain}:{name}。每一列都要填,不交的也要填
|
||||||
|
# ——不交講的是助理不代跑,跟「跑完之後該接什麼」無關
|
||||||
|
# version 判定當下該 domain 的 plugin 版本號。落後現行版本代表要複判,那只是提示,
|
||||||
|
# 不算稽核缺失:版本號是 domain 層級,同 domain 改一支技能其餘技能也會被標到,
|
||||||
|
# 當成缺失的話每次發版整個 domain 都亮紅,提示很快就被當雜訊忽略
|
||||||
|
# origin seed=依既有盤點種入、待複核 judged=正式走過決策樹判定。
|
||||||
|
# seed 只印成待複核提示,不算缺失;技能有異動時就地走一次決策樹,轉成 judged
|
||||||
|
# probe 那一段唯讀盤點實際要跑的指令。只認三種寫法:
|
||||||
|
# 一、一行指令。腳本路徑一律寫成 {root}/jsc-{domain}/… 開頭。{root} 是種入那一支
|
||||||
|
# 拿到的字面絕對根目錄,由它自己代入;那一層的目錄名以 jsc-{domain} 為準,
|
||||||
|
# 找不到就退回不帶前綴的 {domain},比照它找這一份清單本身的作法。
|
||||||
|
# 指令裡不可以出現金錢符號或波浪號:那兩種寫法在無人值守那一輪解不出來,
|
||||||
|
# 也進不了允許清單,會被靜靜擋掉。要帶環境變數就寫在指令最前面,
|
||||||
|
# 例如 JSC_READONLY=1。另外兩個代入點,一個目標跑一次:{cli} 是助理偵測到的
|
||||||
|
# CLI 代號,{repo} 是助理掃到的存取庫工作目錄。這三個以外的大括號一律算填錯——
|
||||||
|
# 代不進去的字面值會原樣送進指令。
|
||||||
|
# 結束碼只准表示過與不過,不准用來編碼狀態。這一條是後來補的,因為漏了它
|
||||||
|
# 就踩過一次:填進來的那一支用結束碼帶三種狀態(接好、接好但這支 CLI 做不到、
|
||||||
|
# 有東西沒接),而讀的那一邊照非零判失敗,於是有先天限制的那一支 CLI 每輪
|
||||||
|
# 讓那一筆失敗一次、一天 96 次,而沒有人修得動。失敗次數存在的理由是指出
|
||||||
|
# 「有一筆壞掉的項目每輪重試而沒人知道」,被修不動的數字填滿就等於用假的
|
||||||
|
# 壞掉把真的壞掉蓋掉。要填的那一支如果用結束碼帶狀態,先去那一邊加一個
|
||||||
|
# 只回過與不過的模式,不要在這一欄將就。
|
||||||
|
# 填之前一定要真的跑一次那一行,不能照技能文件的措辭抄。實際踩過兩次:
|
||||||
|
# 一次抄來的子命令那支腳本根本沒有,跑起來是用法錯誤;一次抄來的子命令會
|
||||||
|
# 在標準輸入沒人關閉時無限等待,換成同一支的另一個子命令才回得來。兩種都
|
||||||
|
# 不會在種入那一刻報錯,要等無人值守那一輪才發作,而那一輪沒有人在看。
|
||||||
|
# 二、pending:{理由}。這一段切得出唯讀盤點,入口還沒接上;冒號後面寫不接的理由,
|
||||||
|
# 留白的話下一輪分不出是刻意還是漏填。
|
||||||
|
# 三、-。這一列沒有唯讀盤點入口。
|
||||||
|
# 哪一種列填哪一種:way 含 invoke 的列與不交的列一律填 -。觸發的意思是呼叫整支
|
||||||
|
# 技能,這一欄填了指令會讓種入那一支改拿指令當待辦簿的動作,於是整支交出變成
|
||||||
|
# 只跑一支腳本,那支技能該寫的頁一頁都不會寫,而且看起來完全正常。way 只有
|
||||||
|
# patrol 或 remind 的列一定要有值:那幾列的動作原本一律退回只提醒,有值才有
|
||||||
|
# 事情做。
|
||||||
|
# 為什麼要連網的先不填:連網要金鑰,金鑰一過期就讓那一項每輪失敗,或每輪靜靜
|
||||||
|
# 回報沒事——後者更難查。純本機讀取本來一輪都不會失敗。先填會連網的那一種,等於
|
||||||
|
# 用一批每輪報錯的項目把真的發現蓋掉。所以要連網的先寫 pending,理由寫清楚,
|
||||||
|
# 等入口與金鑰都有著落再換成指令。
|
||||||
|
# 檢核怎麼看:pending 只印成待接線提示,不算缺失,同 origin=seed 那一條——它是
|
||||||
|
# 「判過、知道還沒接」,不是漏填。填錯欄位、指到不存在的腳本、用了認不得的
|
||||||
|
# 代入點,都算缺失。
|
||||||
|
#
|
||||||
|
ask ask slice patrol 唯讀查 QUESTION_CONTENTS,確認要交辦的事是不是早就有答案 問答本身。助理在背景問不到人,沒有人在場就收不到答案 at:now every:1d jsc-gitea:wiki 0.1.3 seed pending:要連 wiki 讀 QUESTION 目錄頁,得帶金鑰
|
||||||
|
assist assistant none - - 助理本身。讓它自我發動,一輪巡檢會在背景又叫起另一輪,誰都停不下來 - - jsc-log:stats 0.1.6 seed -
|
||||||
|
cli delegate cond invoke 條件是這一項任務唯讀:唯讀巡檢可以委派給別支 CLI,省當前 CLI 的額度。條件不成立,也就是會改檔案的任務,一律不委派;委派失敗就退回自己跑 判斷哪一項算唯讀、額度要不要省,還有委派給哪一支 CLI at:now every:1d jsc-log:stats 0.3.4 seed -
|
||||||
|
cli deploy slice remind 遠端有新版就提醒 安裝、更新、卸除、要求重啟 at:now every:1d jsc-hooks:hooks-install 0.3.4 seed pending:查遠端發佈版本要連 Gitea 並帶金鑰
|
||||||
|
cli doctor cond invoke 條件式:只有在 doctor 呼叫自家 tools/ 與 templates/ 的路徑不再帶版本號之後才可以交。交出的那一段是定期整輪健檢、寫 CHECK_{HASH}。條件不成立時的行為:一律不交,維持人在現場叫用 修——那是 jsc-cli:setup,逐項確認。另外,條件沒滿足之前整支都留在人手上:doctor 自家腳本走的是 CLI 載入技能時給的外掛基底目錄,那條路徑帶版本號、進不了允許清單,無人值守的每一輪都會無聲卡在第一支腳本 at:now every:7d jsc-cli:setup 0.3.4 seed -
|
||||||
|
cli models slice invoke 定期跑 model-tags.sh sync 重建能力標籤表,並盤點各 CLI 可用模型 換模型、改設定 at:now every:7d jsc-cli:doctor 0.3.4 seed -
|
||||||
|
cli setup none - - 改設定與接線,要逐項確認 - - jsc-cli:doctor 0.3.4 seed -
|
||||||
|
git commit none - - 對外不可逆 - - jsc-git:pr 0.1.5 seed -
|
||||||
|
git pr none - - 對外不可逆 - - jsc-log:worklog 0.1.5 seed -
|
||||||
|
gitea html-export none - - 要人指定是哪一頁或哪一個議題。助理在背景猜不到,猜錯就匯出別人的頁 - - jsc-gitea:html-style 0.2.5 seed -
|
||||||
|
gitea html-style none - - 版型與樣式是使用者的偏好,要逐項確認才寫得下去 - - jsc-gitea:html-export 0.2.5 seed -
|
||||||
|
gitea repo-sync slice patrol 盤點哪些存取庫落後遠端、哪些還沒同步下來 clone 與 update——會動工作目錄 at:now every:1d jsc-git:pr 0.2.5 seed pending:repo-sync.sh 會 clone 與 pull,沒有唯讀子命令;盤點落後也要連遠端
|
||||||
|
gitea wiki none - - 助理的手腳,不獨立排程。助理所有 wiki 讀寫都經過它,一律附加 - - jsc-gitea:html-export 0.2.5 seed -
|
||||||
|
gitea wiki-to-issue none - - 對外不可逆 - - jsc-sdlc:analyze 0.2.5 seed -
|
||||||
|
hooks hooks-install slice patrol wire-cli.sh status --verdict 唯讀盤點接線,先天限制不算失敗 重新接線 at:now every:1d jsc-hooks:repair 0.4.5 seed JSC_READONLY=1 {root}/jsc-hooks/tools/wire-cli.sh status {cli} --verdict
|
||||||
|
hooks repair slice invoke 巡檢抓到 hook 錯誤就發動修復 診斷、改哪一支、開 PR,全照 repair 自己的流程 after:hook-error every:1h jsc-git:pr 0.4.4 seed -
|
||||||
|
log learn slice patrol 發動技能前先查過去教訓,也就是 consult 那一段 記錄教訓的判斷。哪一次值得留下來要人說 at:now every:1d jsc-log:worklog 0.1.9 seed pending:要連 wiki 讀 LEARN 目錄頁,得帶金鑰
|
||||||
|
log report slice invoke 週期到了就產週報、月報、年報 報告要不要改寫、要不要送人看 at:now every:7d jsc-log:learn 0.1.9 seed -
|
||||||
|
log stats full invoke - - at:now every:7d jsc-log:report 0.1.9 seed -
|
||||||
|
log worklog slice patrol,remind 任務結束時收口計時與 token,補進 worklog-pending;該寫沒寫就催 寫日誌本體——狀態、細節、難處要人給 after:session-end every:1h jsc-log:learn 0.1.9 seed pending:worklog-pending.sh 七個子命令全部要先給工作階段雜湊,沒有不帶參數的盤點入口,掃不出「該寫沒寫」的那幾筆
|
||||||
|
meta skill-check cond invoke 條件是只交機械檢核那一段:腳本語法、manifest 版本一致、README 技能清單與實際技能對得上。條件不成立的那一段,也就是語意審查與流程效率判斷,一律不交,那要讀完整份 SKILL.md 語意審查、流程與成本效率判斷,還有要不要套用建議 at:now every:7d jsc-meta:skill-update 0.3.1 seed -
|
||||||
|
meta skill-delete none - - 改技能組本身,等於助理改自己 - - jsc-meta:skill-check 0.3.1 seed -
|
||||||
|
meta skill-new none - - 改技能組本身,等於助理改自己 - - jsc-meta:skill-check 0.3.1 seed -
|
||||||
|
meta skill-update none - - 改技能組本身,等於助理改自己 - - jsc-meta:skill-check 0.3.1 seed -
|
||||||
|
meta skillset-update none - - 改技能組本身,等於助理改自己 - - jsc-meta:skill-check 0.3.1 seed -
|
||||||
|
meta ste100-sync slice patrol,remind 定期比對上游版本,落後就提醒 套用上游變更 at:now every:7d jsc-meta:skill-check 0.3.1 seed pending:比對上游版本要連上游站台
|
||||||
|
meta tooling-guide full invoke - - at:now every:7d jsc-meta:skill-check 0.3.1 seed -
|
||||||
|
pkg pkg-update slice patrol 用 list-packages.sh、latest-version.sh 唯讀盤點落後最新穩定版的套件 升版、跑建置與測試 at:now every:7d jsc-review:code-review 0.1.0 seed {root}/jsc-pkg/tools/list-packages.sh {repo}
|
||||||
|
review api-doc none - - 審查要判斷,判準要讀完整份文件與程式碼;助理在背景判不出來 - - jsc-git:pr 0.1.3 seed -
|
||||||
|
review code-review none - - 審查要判斷,而且要不要照著改由呼叫端決定 - - jsc-review:api-doc 0.1.3 seed -
|
||||||
|
review comment-cleanup none - - 會改專案檔案 - - jsc-git:commit 0.1.3 seed -
|
||||||
|
sdlc analyze slice patrol,remind 盤點 ANALYZE_CONTENTS 未完成的分析並先把選項清單備好;另巡檢分析頁勾選與工作包議題狀態對不對得上 分析本體全部要決策。勾選與議題對不上時要對齊哪一邊,是真實來源衝突,也要人判 at:now every:1d jsc-sdlc:implement 0.3.2 seed pending:要連 wiki 讀 ANALYZE 目錄頁,得帶金鑰
|
||||||
|
sdlc implement slice patrol,remind wp-gate.sh check-deps 盤點哪些工作包可以開始;階段鎖殘留就提醒 挑工作包、寫程式碼、開 PR。鎖檔不自己刪 at:now every:1d jsc-log:worklog 0.3.2 seed {root}/jsc-hooks/hooks/sdlc-gate.sh wp-report
|
||||||
|
sdlc maintain none - - 核心是決策與寫程式碼 - - jsc-log:worklog 0.3.2 seed -
|
||||||
|
sdlc plan slice patrol,remind 盤點 PLAN_CONTENTS 還沒分析的計畫,先把選項清單備好 規劃本體全部要決策 at:now every:1d jsc-sdlc:analyze 0.3.2 seed pending:要連 wiki 讀 PLAN 目錄頁,得帶金鑰
|
||||||
|
Executable
+95
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# deploy-route.sh — 判定一個 domain 存取庫的改動是否已進預設分支,決定走部署路線或工作樹路線。
|
||||||
|
#
|
||||||
|
# 用法: deploy-route.sh <domain-path> [branch]
|
||||||
|
# branch 不給就用目前分支。
|
||||||
|
#
|
||||||
|
# 為什麼要有這支: marketplace 與 version-guard.sh 都讀存取庫的**預設分支**,
|
||||||
|
# 停在 develop 的改動 jsc-cli:deploy 看不到,部署路線的完成條件永遠達不到。
|
||||||
|
# skill-new、skill-update、skill-delete、skillset-update 四支各抄一段同樣的判定散文,
|
||||||
|
# 四份會各自漂移,所以下放成同一支腳本。
|
||||||
|
#
|
||||||
|
# 判定:
|
||||||
|
# 1. git fetch --prune origin,讓比對基準是遠端而不是本機殘影。
|
||||||
|
# 2. 預設分支依序取 origin/HEAD 的指向、遠端有沒有 master、遠端有沒有 main。
|
||||||
|
# 3. 待判分支的 HEAD 是不是 origin/{預設分支} 的祖先——是就代表已經合進去。
|
||||||
|
#
|
||||||
|
# 輸出(stdout,一行一組 {鍵}<TAB>{值}):
|
||||||
|
# route<TAB>deploy|worktree 要走哪條路線
|
||||||
|
# default-branch<TAB>{name} 比對用的預設分支
|
||||||
|
# branch<TAB>{name} 被判定的分支
|
||||||
|
# head<TAB>{sha} 被判定分支的 commit
|
||||||
|
# pending<TAB>{name} 只有 worktree 路線才有:還沒併進預設分支的分支名
|
||||||
|
# 說明與失敗原因走 stderr。
|
||||||
|
#
|
||||||
|
# 結束碼: 0=deploy 路線,改動已在預設分支上,可以叫 jsc-cli:deploy
|
||||||
|
# 3=worktree 路線,改動還沒進預設分支,要改對工作樹驗證並在回報裡點名待合的 PR
|
||||||
|
# 2=用法錯誤(一或兩個參數)
|
||||||
|
# 1=判不出來:路徑不是 git 存取庫、沒有 origin、fetch 失敗,或取不到預設分支。
|
||||||
|
# **1 不等於 worktree**:判不出來就停下問人,別自己挑一條路線走。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: deploy-route.sh <domain-path> [branch]' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -ge 1 ] && [ "$#" -le 2 ] || usage
|
||||||
|
DOMAIN=${1%/}
|
||||||
|
[ -n "$DOMAIN" ] || usage
|
||||||
|
BRANCH=${2:-}
|
||||||
|
|
||||||
|
[ -d "$DOMAIN/.git" ] || git -C "$DOMAIN" rev-parse --git-dir >/dev/null 2>&1 || {
|
||||||
|
echo "不是 git 存取庫:$DOMAIN" >&2; exit 1; }
|
||||||
|
|
||||||
|
git -C "$DOMAIN" remote get-url origin >/dev/null 2>&1 || {
|
||||||
|
echo "存取庫沒有 origin 遠端,無從比對預設分支:$DOMAIN" >&2; exit 1; }
|
||||||
|
|
||||||
|
git -C "$DOMAIN" fetch --prune --quiet origin >/dev/null 2>&1 || {
|
||||||
|
echo "git fetch origin 失敗,比對基準會是本機殘影,先修連線再重跑:$DOMAIN" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [ -z "$BRANCH" ]; then
|
||||||
|
BRANCH=$(git -C "$DOMAIN" rev-parse --abbrev-ref HEAD 2>/dev/null) || BRANCH=''
|
||||||
|
fi
|
||||||
|
[ -n "$BRANCH" ] && [ "$BRANCH" != HEAD ] || {
|
||||||
|
echo "取不到目前分支名(可能在 detached HEAD),請用第二個參數指定分支:$DOMAIN" >&2; exit 1; }
|
||||||
|
|
||||||
|
# 預設分支:先問 origin/HEAD,再退回遠端實際存在的 master、main。
|
||||||
|
DEFAULT=$(git -C "$DOMAIN" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null) || DEFAULT=''
|
||||||
|
DEFAULT=${DEFAULT#origin/}
|
||||||
|
if [ -z "$DEFAULT" ]; then
|
||||||
|
for cand in master main; do
|
||||||
|
if git -C "$DOMAIN" rev-parse --verify --quiet "refs/remotes/origin/$cand" >/dev/null 2>&1; then
|
||||||
|
DEFAULT=$cand
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
[ -n "$DEFAULT" ] || { echo "取不到預設分支(origin/HEAD、origin/master、origin/main 都沒有):$DOMAIN" >&2; exit 1; }
|
||||||
|
|
||||||
|
git -C "$DOMAIN" rev-parse --verify --quiet "refs/remotes/origin/$DEFAULT" >/dev/null 2>&1 || {
|
||||||
|
echo "遠端沒有 origin/$DEFAULT:$DOMAIN" >&2; exit 1; }
|
||||||
|
|
||||||
|
HEADSHA=$(git -C "$DOMAIN" rev-parse --verify --quiet "$BRANCH" 2>/dev/null) || HEADSHA=''
|
||||||
|
[ -n "$HEADSHA" ] || { echo "取不到分支的 commit:$BRANCH" >&2; exit 1; }
|
||||||
|
|
||||||
|
TAB=$(printf '\t')
|
||||||
|
if git -C "$DOMAIN" merge-base --is-ancestor "$HEADSHA" "origin/$DEFAULT" 2>/dev/null; then
|
||||||
|
ROUTE=deploy
|
||||||
|
else
|
||||||
|
ROUTE=worktree
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'route%s%s\n' "$TAB" "$ROUTE"
|
||||||
|
printf 'default-branch%s%s\n' "$TAB" "$DEFAULT"
|
||||||
|
printf 'branch%s%s\n' "$TAB" "$BRANCH"
|
||||||
|
printf 'head%s%s\n' "$TAB" "$HEADSHA"
|
||||||
|
|
||||||
|
if [ "$ROUTE" = deploy ]; then
|
||||||
|
echo "改動已在 origin/$DEFAULT 上,走部署路線:$DOMAIN" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'pending%s%s\n' "$TAB" "$BRANCH"
|
||||||
|
echo "改動還沒併進 origin/$DEFAULT,走工作樹路線;回報時要點名待合的 PR:$DOMAIN" >&2
|
||||||
|
exit 3
|
||||||
@@ -4,15 +4,31 @@
|
|||||||
# 用法: find-skill-refs.sh <domain> <skill-name>
|
# 用法: find-skill-refs.sh <domain> <skill-name>
|
||||||
#
|
#
|
||||||
# 掃描樣式:
|
# 掃描樣式:
|
||||||
# 1. 技能名稱字面 {skill-name}
|
# 1. 技能名稱字面 {skill-name}——**純子字串比對**,不看邊界。
|
||||||
|
# 所以短名稱也會命中「名稱+後綴」的其他技能名,以及純敘述文字裡的同一個詞。
|
||||||
|
# 這是故意的:盤點寧可多列,漏一個引用會讓刪除技能少改檔案。
|
||||||
|
# 呼叫端必須逐檔看過命中內容再判斷,不可把清單當成「一定要改的檔案」。
|
||||||
# 2. 指令形式 /jsc-{domain}:{skill-name}
|
# 2. 指令形式 /jsc-{domain}:{skill-name}
|
||||||
# 這兩個樣式已涵蓋其他 SKILL.md 的引用、domain README 的「Skills 目錄」小節、
|
# 這兩個樣式已涵蓋其他 SKILL.md 的引用、domain README 的「Skills 目錄」小節、
|
||||||
# plugins/meta 兩份 marketplace.json 與其在各 domain 存取庫的同步副本、tools/ 腳本、
|
# plugins/meta 兩份 marketplace.json 與其在各 domain 存取庫的同步副本、tools/ 腳本、
|
||||||
# jsc-hooks 接線等位置——只要檔案裡出現技能名稱或指令形式就會被列出。
|
# jsc-hooks 接線等位置——只要檔案裡出現技能名稱或指令形式就會被列出。
|
||||||
#
|
#
|
||||||
# 掃描範圍: /root/plugins 底下所有 domain 存取庫,排除 .git 目錄。
|
# 掃描範圍: **只掃正本 marketplace 上的 domain 存取庫**。
|
||||||
# 輸出: 命中檔案清單(去重、排序),一行一個路徑;exit 0 一律成功,即使沒有命中。
|
# 工作區可能還放著不屬於技能組的存取庫(例 shared、persona、code、doc)與
|
||||||
set -eu
|
# 點開頭的目錄(例 .jsc-monorepo-archive、.kiro),掃進去會讓刪除技能改到
|
||||||
|
# 技能組以外的檔案。domain 清單取自 marketplace.json,不寫死。
|
||||||
|
# 每個 domain 先找 {root}/{domain},再找 {root}/jsc-{domain};兩者都沒有就略過。
|
||||||
|
# 每個存取庫內排除 .git 目錄。
|
||||||
|
# 根目錄交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||||||
|
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||||||
|
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||||||
|
# 輸出: 命中檔案清單(去重、排序),一行一個路徑(stdout);掃描摘要走 stderr。
|
||||||
|
# 結束碼: 0=有命中 1=掃完但零命中 2=用法錯誤
|
||||||
|
# 3=推導不出根目錄、讀不到 domain 清單、本機一個 domain 存取庫都沒有、
|
||||||
|
# 建不了暫存檔,或掃描失敗
|
||||||
|
# 零命中與掃描失敗必須分開:拿掃描失敗當「沒有引用」會讓刪除技能少改檔案。
|
||||||
|
# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh)
|
||||||
|
set -u
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo 'usage: find-skill-refs.sh <domain> <skill-name>' >&2
|
echo 'usage: find-skill-refs.sh <domain> <skill-name>' >&2
|
||||||
@@ -22,13 +38,51 @@ usage() {
|
|||||||
[ "$#" -eq 2 ] || usage
|
[ "$#" -eq 2 ] || usage
|
||||||
DOMAIN=$1
|
DOMAIN=$1
|
||||||
SKILL=$2
|
SKILL=$2
|
||||||
|
[ -n "$DOMAIN" ] && [ -n "$SKILL" ] || usage
|
||||||
|
|
||||||
ROOT=/root/plugins
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
[ -d "$ROOT" ] || { echo "找不到 plugins 根目錄: $ROOT" >&2; exit 1; }
|
. "$HERE/plugins-root.sh"
|
||||||
|
ROOT=$(jsc_plugins_root) || exit 3
|
||||||
|
|
||||||
|
# 正本 domain 清單:優先讀本機任一份 marketplace 副本(每個 repo 都帶同一份)。
|
||||||
|
mkt=""
|
||||||
|
for cand in "$HERE/../.claude-plugin/marketplace.json" "$ROOT"/*/.claude-plugin/marketplace.json; do
|
||||||
|
[ -f "$cand" ] && { mkt="$cand"; break; }
|
||||||
|
done
|
||||||
|
[ -n "$mkt" ] || { echo "找不到 marketplace.json,無法判定 domain 清單" >&2; exit 3; }
|
||||||
|
DOMAINS=$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"jsc-\([a-z0-9-]*\)".*/\1/p' "$mkt" | sort -u)
|
||||||
|
[ -n "$DOMAINS" ] || { echo "marketplace.json 裡沒有 jsc-{domain} 條目:$mkt" >&2; exit 3; }
|
||||||
|
|
||||||
|
# 只收本機存在、且不是點開頭目錄的 domain 存取庫路徑。
|
||||||
|
TARGETS=""
|
||||||
|
for d in $DOMAINS; do
|
||||||
|
for cand in "$ROOT/$d" "$ROOT/jsc-$d"; do
|
||||||
|
[ -d "$cand" ] || continue
|
||||||
|
TARGETS="$TARGETS $cand"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
done
|
||||||
|
[ -n "$TARGETS" ] || { echo "本機找不到任何 domain 存取庫:$ROOT(先跑 sync-domains.sh)" >&2; exit 3; }
|
||||||
|
|
||||||
PATTERN1="$SKILL"
|
PATTERN1="$SKILL"
|
||||||
PATTERN2="/jsc-${DOMAIN}:${SKILL}"
|
PATTERN2="/jsc-${DOMAIN}:${SKILL}"
|
||||||
|
|
||||||
grep -rlE --exclude-dir=.git -e "$PATTERN1" -e "$PATTERN2" "$ROOT" 2>/dev/null | sort -u
|
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
# grep 的結束碼要單獨看:接管線的話 sort 永遠回 0,掃描失敗就被吃掉了。
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
grep -rlI --exclude-dir=.git -e "$PATTERN1" -e "$PATTERN2" $TARGETS > "$TMP" 2>/dev/null
|
||||||
|
status=$?
|
||||||
|
[ "$status" -le 1 ] || { echo "掃描失敗:$ROOT(grep 結束碼 $status)" >&2; exit 3; }
|
||||||
|
OUT=$(sort -u "$TMP")
|
||||||
|
|
||||||
|
count=$(printf '%s' "$TARGETS" | wc -w | tr -d ' ')
|
||||||
|
if [ -z "$OUT" ]; then
|
||||||
|
echo "掃描完成,零命中:$count 個 domain 存取庫" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$OUT"
|
||||||
|
printf '掃描完成,命中 %s 個檔案(掃了 %s 個 domain 存取庫;技能名稱為純子字串比對,請逐檔確認)\n' \
|
||||||
|
"$(printf '%s\n' "$OUT" | wc -l | tr -d ' ')" "$count" >&2
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Executable
+283
@@ -0,0 +1,283 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# inventory-tooling.sh — 盤點 jsc plugins、skills、tools、hooks,輸出技能組基礎指引。
|
||||||
|
#
|
||||||
|
# 用法: inventory-tooling.sh [root]
|
||||||
|
#
|
||||||
|
# root: 給了參數就用參數。沒給就交給 tools/plugins-root.sh 推導
|
||||||
|
# (JSC_PLUGINS_ROOT -> 從 $PWD 往上找 -> 腳本位置上兩層 -> $HOME/plugins)。
|
||||||
|
# 以 plugin 形式安裝時,「腳本位置上兩層」會落在 plugin 快取目錄,單靠它一定推錯,
|
||||||
|
# 所以推導規則抽成共用腳本。
|
||||||
|
#
|
||||||
|
# 輸出: Markdown。內容包含 domain、manifest、技能、工具、hooks 與管理入口。
|
||||||
|
# 同一趟已經跑過 list-skills.sh、detect-clis.sh 與 wire-cli.sh status,結果都寫進輸出,
|
||||||
|
# 呼叫端沿用即可,不必再各跑一次。
|
||||||
|
# 結束碼: 0=成功 1=推導不出根目錄、根目錄不存在、marketplace 或必要工具缺失
|
||||||
|
# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh)
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
if [ "$#" -ge 1 ] && [ -n "$1" ]; then
|
||||||
|
ROOT=$1
|
||||||
|
else
|
||||||
|
ROOT=$(jsc_plugins_root) || exit 1
|
||||||
|
fi
|
||||||
|
ROOT=${ROOT%/}
|
||||||
|
|
||||||
|
[ -d "$ROOT" ] || { echo "找不到 plugins 根目錄:$ROOT" >&2; exit 1; }
|
||||||
|
|
||||||
|
MARKETPLACE=""
|
||||||
|
for cand in "$ROOT/meta/.claude-plugin/marketplace.json" "$ROOT"/*/.claude-plugin/marketplace.json; do
|
||||||
|
[ -f "$cand" ] && { MARKETPLACE=$cand; break; }
|
||||||
|
done
|
||||||
|
[ -n "$MARKETPLACE" ] || { echo "找不到 marketplace.json,無法判定 domain 清單" >&2; exit 1; }
|
||||||
|
|
||||||
|
LIST_SKILLS="$ROOT/meta/tools/list-skills.sh"
|
||||||
|
[ -x "$LIST_SKILLS" ] || { echo "找不到可執行的 list-skills.sh:$LIST_SKILLS" >&2; exit 1; }
|
||||||
|
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
domains_file="$tmp/domains.tsv"
|
||||||
|
skills_file="$tmp/skills.tsv"
|
||||||
|
clis_file="$tmp/clis.tsv"
|
||||||
|
hook_status_file="$tmp/hook-status.tsv"
|
||||||
|
|
||||||
|
sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"jsc-\([a-z0-9-]*\)".*/\1/p' "$MARKETPLACE" |
|
||||||
|
sort -u |
|
||||||
|
while read -r domain; do
|
||||||
|
[ -n "$domain" ] || continue
|
||||||
|
path="$ROOT/$domain"
|
||||||
|
[ -d "$path" ] || path="$ROOT/jsc-$domain"
|
||||||
|
if [ -d "$path" ]; then
|
||||||
|
version=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$path/plugin.json" 2>/dev/null | head -1)
|
||||||
|
[ -n "$version" ] || version="未知"
|
||||||
|
printf '%s\t%s\t%s\n' "$domain" "$version" "$path"
|
||||||
|
else
|
||||||
|
printf '%s\t%s\t%s\n' "$domain" "缺本機存取庫" "-"
|
||||||
|
fi
|
||||||
|
done > "$domains_file"
|
||||||
|
|
||||||
|
JSC_PLUGINS_ROOT="$ROOT" "$LIST_SKILLS" > "$skills_file" 2>/dev/null || : > "$skills_file"
|
||||||
|
|
||||||
|
DETECT_CLIS="$ROOT/cli/tools/detect-clis.sh"
|
||||||
|
if [ -x "$DETECT_CLIS" ]; then
|
||||||
|
"$DETECT_CLIS" > "$clis_file" 2>/dev/null || : > "$clis_file"
|
||||||
|
else
|
||||||
|
: > "$clis_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
WIRE_CLI="$ROOT/hooks/tools/wire-cli.sh"
|
||||||
|
: > "$hook_status_file"
|
||||||
|
if [ -x "$WIRE_CLI" ] && [ -s "$clis_file" ]; then
|
||||||
|
while IFS="$(printf '\t')" read -r cli path version; do
|
||||||
|
[ -n "$cli" ] || continue
|
||||||
|
out="$tmp/wire-$cli.out"
|
||||||
|
err="$tmp/wire-$cli.err"
|
||||||
|
if "$WIRE_CLI" status "$cli" > "$out" 2> "$err"; then
|
||||||
|
rc=0
|
||||||
|
else
|
||||||
|
rc=$?
|
||||||
|
fi
|
||||||
|
verdict=$(sed -n '1{s/[|]/-/g;p;}' "$out")
|
||||||
|
[ -n "$verdict" ] || verdict=$(sed -n '1{s/[|]/-/g;p;}' "$err")
|
||||||
|
[ -n "$verdict" ] || verdict="無狀態輸出"
|
||||||
|
case "$rc" in
|
||||||
|
0|1|3|5) ;;
|
||||||
|
2) verdict="CLI 代號不符合 wire-cli.sh 用法" ;;
|
||||||
|
*) verdict="未知狀態,結束碼 $rc" ;;
|
||||||
|
esac
|
||||||
|
printf '%s\t%s\t%s\n' "$cli" "$rc" "$verdict" >> "$hook_status_file"
|
||||||
|
done < "$clis_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
count_lines() {
|
||||||
|
file=$1
|
||||||
|
[ -s "$file" ] || { echo 0; return; }
|
||||||
|
wc -l < "$file" | tr -d ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
domains_count=$(count_lines "$domains_file")
|
||||||
|
skills_count=$(count_lines "$skills_file")
|
||||||
|
clis_count=$(count_lines "$clis_file")
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
# jsc 技能組基礎指引
|
||||||
|
|
||||||
|
產生時間:$(date '+%Y-%m-%d %H:%M:%S %z')
|
||||||
|
|
||||||
|
資料來源:
|
||||||
|
|
||||||
|
- plugins 根目錄:\`$ROOT\`
|
||||||
|
- marketplace:\`$MARKETPLACE\`
|
||||||
|
- 技能清單工具:\`$LIST_SKILLS\`
|
||||||
|
|
||||||
|
## Source freshness
|
||||||
|
|
||||||
|
| 項目 | 狀態 |
|
||||||
|
| --- | --- |
|
||||||
|
| 本機 domain 清單 | synced |
|
||||||
|
| marketplace | \`$MARKETPLACE\` |
|
||||||
|
|
||||||
|
## 現況摘要
|
||||||
|
|
||||||
|
| 項目 | 數量 |
|
||||||
|
| --- | ---: |
|
||||||
|
| 已註冊 plugin domain | $domains_count |
|
||||||
|
| 已掃到技能 | $skills_count |
|
||||||
|
| 已偵測 CLI | $clis_count |
|
||||||
|
|
||||||
|
## Supported plugins
|
||||||
|
|
||||||
|
| domain | version | 本機路徑 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
EOF
|
||||||
|
|
||||||
|
while IFS="$(printf '\t')" read -r domain version path; do
|
||||||
|
[ -n "$domain" ] || continue
|
||||||
|
printf '| `%s` | `%s` | `%s` |\n' "$domain" "$version" "$path"
|
||||||
|
done < "$domains_file"
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
## Supported skills
|
||||||
|
|
||||||
|
| domain | skill | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -s "$skills_file" ]; then
|
||||||
|
while IFS="$(printf '\t')" read -r domain name desc; do
|
||||||
|
[ -n "$domain" ] || continue
|
||||||
|
printf '| `%s` | `%s` | %s |\n' "$domain" "$name" "$desc"
|
||||||
|
done < "$skills_file"
|
||||||
|
else
|
||||||
|
echo '| - | - | 未掃到技能。請先執行 `meta/tools/sync-domains.sh`。 |'
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
Skill 使用方式:
|
||||||
|
|
||||||
|
- Claude、Antigravity:使用 `/jsc-{domain}:{name}`。
|
||||||
|
- Codex:使用技能名稱或自然語意觸發。
|
||||||
|
- Copilot、Kiro:用需求描述觸發。
|
||||||
|
- 每支技能只保留決策與流程。可標準輸入輸出的細節放到 `tools/`。
|
||||||
|
- 需要強制執行的規則放到 `jsc-hooks`,不要散落在各 domain。
|
||||||
|
|
||||||
|
## Supported CLIs
|
||||||
|
|
||||||
|
| CLI | 執行檔 | 版本 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -s "$clis_file" ]; then
|
||||||
|
while IFS="$(printf '\t')" read -r cli path version; do
|
||||||
|
[ -n "$cli" ] || continue
|
||||||
|
printf '| `%s` | `%s` | `%s` |\n' "$cli" "$path" "$version"
|
||||||
|
done < "$clis_file"
|
||||||
|
else
|
||||||
|
echo '| - | - | 這台機器未偵測到支援的 CLI,或找不到 `cli/tools/detect-clis.sh`。 |'
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
## Supported tools
|
||||||
|
|
||||||
|
| domain | tool | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
EOF
|
||||||
|
|
||||||
|
while IFS="$(printf '\t')" read -r domain version path; do
|
||||||
|
[ -d "$path/tools" ] || continue
|
||||||
|
for tool in "$path"/tools/*; do
|
||||||
|
[ -f "$tool" ] || continue
|
||||||
|
base=$(basename "$tool")
|
||||||
|
first=$(sed -n '2{s/^# *//;p;}' "$tool" 2>/dev/null)
|
||||||
|
[ -n "$first" ] || first="工具腳本"
|
||||||
|
printf '| `%s` | `%s` | %s |\n' "$domain" "$base" "$first"
|
||||||
|
done
|
||||||
|
done < "$domains_file"
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
Tool 使用方式:
|
||||||
|
|
||||||
|
- 先讀工具檔頭的用法與結束碼。
|
||||||
|
- 用真實參數驗證新增或修改的工具。
|
||||||
|
- 結束碼沒有文件化時,先補工具說明,再讓技能呼叫它。
|
||||||
|
|
||||||
|
## Hook management
|
||||||
|
|
||||||
|
| hook | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
EOF
|
||||||
|
|
||||||
|
HOOK_DIR="$ROOT/hooks/hooks"
|
||||||
|
if [ -d "$HOOK_DIR" ]; then
|
||||||
|
for hook in "$HOOK_DIR"/*.sh; do
|
||||||
|
[ -f "$hook" ] || continue
|
||||||
|
base=$(basename "$hook")
|
||||||
|
first=$(sed -n '2{s/^# *//;p;}' "$hook" 2>/dev/null)
|
||||||
|
[ -n "$first" ] || first="hook 腳本"
|
||||||
|
printf '| `%s` | %s |\n' "$base" "$first"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo '| - | 找不到 `hooks/hooks`。請先執行 `meta/tools/sync-domains.sh`。 |'
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
Hook 使用方式:
|
||||||
|
|
||||||
|
- Hook 只放在 `jsc-hooks` domain。
|
||||||
|
- `jsc-hooks:hooks-install` 負責接線到各 CLI。
|
||||||
|
- hook 腳本要能接受 stdin JSON 與環境變數。
|
||||||
|
- 缺欄位時安靜降級並 `exit 0`。
|
||||||
|
- 版本閘門與重啟閘門要保留解除自己的路徑。
|
||||||
|
|
||||||
|
## Hook wiring status
|
||||||
|
|
||||||
|
| CLI | 結束碼 | 狀態 |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -s "$hook_status_file" ]; then
|
||||||
|
while IFS="$(printf '\t')" read -r cli rc status; do
|
||||||
|
[ -n "$cli" ] || continue
|
||||||
|
printf '| `%s` | %s | %s |\n' "$cli" "$rc" "$status"
|
||||||
|
done < "$hook_status_file"
|
||||||
|
else
|
||||||
|
echo '| - | - | 未偵測到 CLI,或找不到 `hooks/tools/wire-cli.sh`。 |'
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
## Plugin and skill management
|
||||||
|
|
||||||
|
- 新增技能:使用 `jsc-meta:skill-new`。
|
||||||
|
- 更新單一技能:使用 `jsc-meta:skill-update`。
|
||||||
|
- 批次更新技能組:使用 `jsc-meta:skillset-update`。
|
||||||
|
- 刪除技能:使用 `jsc-meta:skill-delete`。
|
||||||
|
- 安裝、更新、移除整組 plugin:使用 `jsc-cli:deploy`。
|
||||||
|
- 例行稽核:使用 `jsc-meta:skill-check`。
|
||||||
|
- 重新接線 hooks:使用 `jsc-hooks:hooks-install`。
|
||||||
|
|
||||||
|
## Operational checks
|
||||||
|
|
||||||
|
- 體檢目前環境:使用 `jsc-cli:doctor`。
|
||||||
|
- 修復體檢項目:使用 `jsc-cli:setup`。
|
||||||
|
- 版本前置檢查:由 `jsc-hooks/hooks/version-guard.sh` 管理。
|
||||||
|
- 部署後重啟閘門:由 `jsc-hooks/hooks/restart-gate.sh` 管理。
|
||||||
|
- 語言提示與掃描:由 `jsc-hooks/hooks/ste100-guard.sh` 與 `jsc-hooks/hooks/lang-guard.sh` 管理。
|
||||||
|
- 註解範圍檢查:由 `jsc-hooks/hooks/comment-scope.sh` 管理。
|
||||||
|
|
||||||
|
## Use this when
|
||||||
|
|
||||||
|
- 先執行 `meta/tools/sync-domains.sh`,同步 marketplace 上的 domain。
|
||||||
|
- 再執行 `meta/tools/list-skills.sh`,確認技能清單。
|
||||||
|
- 執行本工具,產出基礎指引。
|
||||||
|
- 若新增或修改技能,執行 `meta/tools/sync-skill-manifest.sh {domain-path}`。
|
||||||
|
- 執行 `meta/tools/ste100-lint.sh {domain-path}`。
|
||||||
|
- 依 `jsc-git:pr` 開立 Push Request。
|
||||||
|
EOF
|
||||||
Executable
+168
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# lint-frontmatter.sh — 檢查一個 domain 存取庫每支 skills/*/SKILL.md 的 frontmatter 能不能安全解析。
|
||||||
|
#
|
||||||
|
# 用法: lint-frontmatter.sh <domain-path>
|
||||||
|
#
|
||||||
|
# 檢查五項(掃 {domain-path}/skills/*/SKILL.md):
|
||||||
|
# 1. 分隔線 — 第一行是「---」,而且找得到成對的收尾「---」。缺一邊,底下整段都不是
|
||||||
|
# frontmatter,鍵一個都讀不到。
|
||||||
|
# 2. 必要鍵 — 「name」與「description」都在,而且值不是空的。載入器靠這兩個鍵認技能。
|
||||||
|
# 3. 冒號 — 未加引號的純量不得含「冒號加空白」,也不得以冒號結尾。那在 YAML 是鍵的
|
||||||
|
# 分隔符號,解析器會把一行拆成兩個鍵,整份 frontmatter 當場語法錯誤。
|
||||||
|
# 4. 起頭字元 — 未加引號的純量不得以 & * ! | > % @ ` 起頭。這八個在 YAML 1.2 分別是錨點、
|
||||||
|
# 別名、標籤、區塊純量、指令與保留字元,起頭寫了就不是原本那串字。
|
||||||
|
# 5. 引號 — 加了引號的值要收得起來:單引號內部的「'」要寫成「''」,收尾引號之後除了
|
||||||
|
# 註解不得有殘餘。修這個缺陷的手法就是補單引號,補歪了照樣是語法錯誤。
|
||||||
|
#
|
||||||
|
# 為什麼要這支: 2026-08-31 抓到 6 支技能的 description 是未加引號的純量、內容含「冒號加空白」。
|
||||||
|
# 那在 YAML 是語法錯誤,Antigravity 讀到就**靜默丟棄整支技能**——磁碟上 34 支,它只認 28 支,
|
||||||
|
# 而且**完全沒有錯誤訊息**:載入器不報、CLI 不報、技能清單只是少了幾列。這種缺陷唯一的發現
|
||||||
|
# 途徑是逐檔比對磁碟數量與載入數量,人工比對 10 個 domain 每次稽核都要重做一遍,還會漏。
|
||||||
|
# 輸入輸出固定的判定就交給程式,別靠眼睛。
|
||||||
|
#
|
||||||
|
# 為什麼不用 YAML 套件: 本機沒有 pyyaml,而護欄不該把自己綁在一個不保證存在的相依上。這五項
|
||||||
|
# 都只需要 YAML 1.2 的 plain scalar 規則,自己判定就夠,也才跑得到每一台機器上。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個不合格項目,格式 {檔案}:{鍵}:{說明}(stderr);通過時在 stderr 印一行摘要。
|
||||||
|
# 結構性問題(分隔線、必要鍵、無法辨識的一行)的「鍵」欄寫 frontmatter。stdout 不印東西。
|
||||||
|
# 結束碼: 0=掃到 SKILL.md 且五項全過
|
||||||
|
# 1=有不合格項目(清單在 stderr)
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 3=domain 路徑不存在、找不到 {domain-path}/skills/,或 skills/ 底下一支 SKILL.md
|
||||||
|
# 都沒有——**什麼都沒掃**,不等於通過
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: lint-frontmatter.sh <domain-path>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 1 ] || usage
|
||||||
|
DOMAIN=${1%/}
|
||||||
|
[ -n "$DOMAIN" ] || usage
|
||||||
|
|
||||||
|
SKILLS="$DOMAIN/skills"
|
||||||
|
[ -d "$DOMAIN" ] || { echo "找不到 domain 路徑:$DOMAIN" >&2; exit 3; }
|
||||||
|
[ -d "$SKILLS" ] || { echo "找不到 skills/:$SKILLS" >&2; exit 3; }
|
||||||
|
|
||||||
|
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
|
||||||
|
find "$SKILLS" -mindepth 2 -maxdepth 2 -type f -name 'SKILL.md' 2>/dev/null \
|
||||||
|
| LC_ALL=C sort > "$TMP"
|
||||||
|
[ -s "$TMP" ] || { echo "skills/ 底下沒有任何 SKILL.md,無 frontmatter 可掃:$SKILLS" >&2; exit 3; }
|
||||||
|
|
||||||
|
BOM=$(printf '\357\273\277')
|
||||||
|
|
||||||
|
hit=0
|
||||||
|
total=0
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
total=$((total + 1))
|
||||||
|
awk -v f="$f" -v bom="$BOM" '
|
||||||
|
function trim(s) { gsub(/^[ \t\r]+/, "", s); gsub(/[ \t\r]+$/, "", s); return s }
|
||||||
|
function rep(k, m) { printf "%s:%s:%s\n", f, k, m; bad = 1 }
|
||||||
|
|
||||||
|
# 掃過一段引號括起來的值,回傳收尾引號之後的殘餘;收不起來就回哨兵值。
|
||||||
|
# 為什麼要自己逐字掃: 單引號的跳脫是「重複一次」、雙引號的跳脫是反斜線,兩套規則不同,
|
||||||
|
# 用正規表示式一次比對兩種只會在其中一種上判錯。
|
||||||
|
function scan_quoted(v, q, i, c, n) {
|
||||||
|
n = length(v)
|
||||||
|
i = 2
|
||||||
|
while (i <= n) {
|
||||||
|
c = substr(v, i, 1)
|
||||||
|
if (q == "\"" && c == "\\") { i += 2; continue }
|
||||||
|
if (c == q) {
|
||||||
|
if (q == "\047" && substr(v, i + 1, 1) == "\047") { i += 2; continue }
|
||||||
|
return substr(v, i + 1)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return "\001"
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN { state = 0; bad = 0 }
|
||||||
|
{ sub(/\r$/, "") }
|
||||||
|
|
||||||
|
NR == 1 {
|
||||||
|
line = $0
|
||||||
|
sub("^" bom, "", line)
|
||||||
|
if (trim(line) != "---") {
|
||||||
|
rep("frontmatter", "第一行不是 ---,整份 frontmatter 讀不到,載入器會靜默丟棄這支技能")
|
||||||
|
state = 3 # 已判定並回報,END 不必再補話
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
state = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# frontmatter 收尾。YAML 的文件結束標記「...」一樣算收尾。
|
||||||
|
state == 1 && (trim($0) == "---" || trim($0) == "...") { state = 2; next }
|
||||||
|
|
||||||
|
state == 1 {
|
||||||
|
if ($0 ~ /^[ \t]/) next # 縮排的續行或巢狀對應,判不出就不判
|
||||||
|
if ($0 ~ /^[ \t]*$/) next # 空行
|
||||||
|
if ($0 ~ /^#/) next # 註解
|
||||||
|
if ($0 ~ /^- /) next # 與鍵同縮排的序列項
|
||||||
|
|
||||||
|
if ($0 !~ /^[A-Za-z0-9_.-]+:([ \t]|$)/) {
|
||||||
|
rep("frontmatter", "這一行既不是鍵也不是續行,YAML 解析會在這裡中斷:" substr($0, 1, 40))
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
ci = index($0, ":")
|
||||||
|
k = substr($0, 1, ci - 1)
|
||||||
|
v = trim(substr($0, ci + 1))
|
||||||
|
|
||||||
|
if (k in seen) rep(k, "同一個鍵出現兩次,後面那份會靜默蓋掉前面那份")
|
||||||
|
seen[k] = 1
|
||||||
|
val[k] = v
|
||||||
|
|
||||||
|
if (v == "") next # 值在下一段,前面的縮排規則已經放過
|
||||||
|
|
||||||
|
q = substr(v, 1, 1)
|
||||||
|
if (q == "\047" || q == "\"") {
|
||||||
|
rest = scan_quoted(v, q)
|
||||||
|
if (rest == "\001") {
|
||||||
|
if (q == "\047") rep(k, "單引號沒有收尾,內部的 \047 要寫成 \047\047")
|
||||||
|
else rep(k, "雙引號沒有收尾")
|
||||||
|
} else {
|
||||||
|
rest = trim(rest)
|
||||||
|
if (rest != "" && substr(rest, 1, 1) != "#")
|
||||||
|
rep(k, "收尾引號之後還有內容,解析器會當成語法錯誤:" substr(rest, 1, 30))
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# 以下都是未加引號的純量(plain scalar)。
|
||||||
|
if (index("&*!|>%@`", q) > 0)
|
||||||
|
rep(k, "未加引號的純量以 YAML 特殊字元「" q "」起頭,會被當成錨點、別名、標籤、區塊純量或指令")
|
||||||
|
|
||||||
|
if (index(v, ": ") > 0)
|
||||||
|
rep(k, "未加引號的純量含「冒號加空白」,YAML 會把它當成鍵的分隔符號,整份 frontmatter 語法錯誤;整串加單引號即可")
|
||||||
|
else if (substr(v, length(v), 1) == ":")
|
||||||
|
rep(k, "未加引號的純量以冒號結尾,YAML 會把它當成鍵的分隔符號;整串加單引號即可")
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (state == 0) {
|
||||||
|
rep("frontmatter", "檔案是空的,沒有 frontmatter")
|
||||||
|
} else if (state == 1) {
|
||||||
|
rep("frontmatter", "frontmatter 分隔線不成對,找不到收尾的 ---")
|
||||||
|
} else if (state == 2) {
|
||||||
|
if (!("name" in seen)) rep("frontmatter", "缺必要鍵 name")
|
||||||
|
else if (val["name"] == "") rep("name", "必要鍵的值是空的")
|
||||||
|
if (!("description" in seen)) rep("frontmatter", "缺必要鍵 description")
|
||||||
|
else if (val["description"] == "") rep("description", "必要鍵的值是空的")
|
||||||
|
}
|
||||||
|
exit bad
|
||||||
|
}
|
||||||
|
' "$f" >&2 || hit=1
|
||||||
|
done < "$TMP"
|
||||||
|
|
||||||
|
if [ "$hit" -eq 0 ]; then
|
||||||
|
echo "frontmatter 檢查通過:$total 支(分隔線、必要鍵、冒號、起頭字元、引號)" >&2
|
||||||
|
else
|
||||||
|
echo "frontmatter 檢查有不合格項目:共掃 $total 支,清單在上面" >&2
|
||||||
|
fi
|
||||||
|
exit $hit
|
||||||
Executable
+105
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# lint-scripts.sh — 檢查一個 domain 存取庫的 shell 腳本:語法、可執行、結束碼有沒有記載。
|
||||||
|
#
|
||||||
|
# 用法: lint-scripts.sh <domain-path>
|
||||||
|
#
|
||||||
|
# 檢查三項(掃 {domain-path}/tools 與 {domain-path}/hooks 底下的 *.sh):
|
||||||
|
# 1. 語法 — sh -n 通過。
|
||||||
|
# 2. 可執行 — 檔案有執行權限。技能直接呼叫的腳本沒有 +x,會在部署後才炸。
|
||||||
|
# 3. 結束碼 — 檔頭前 60 行要有結束碼宣告列(「結束碼:」或「exit code」)。
|
||||||
|
# 沒有宣告的腳本,呼叫端沒辦法逐碼分流,只能猜;猜錯就把失敗當成功。
|
||||||
|
#
|
||||||
|
# 為什麼合成一支: skill-check 原本在 SKILL.md 裡寫 find ... -exec sh -n,另外兩項靠散文
|
||||||
|
# 要求人工比對。三項的輸入輸出都固定,散文版每個 domain 各做一次,還會跟腳本實況漂移。
|
||||||
|
#
|
||||||
|
# 例外: 只被 source 的共用函式庫(判準見 is_library)第 2、3 項都不查——它不是可執行入口,
|
||||||
|
# 沒有 +x 的必要,也沒有結束碼語意可宣告。第 1 項語法檢查照查,函式庫一樣會被 sh -n 讀。
|
||||||
|
# 豁免不是靜默的: 每豁免一支就在 stderr 記一行,摘要也帶函式庫支數,才看得出誰被跳過。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個不合格項目,格式 {檔案}:{檢查項}:{說明}(stdout);
|
||||||
|
# 函式庫豁免通知與統計摘要走 stderr。
|
||||||
|
# 結束碼: 0=掃到腳本且三項全過
|
||||||
|
# 1=有不合格項目(清單在 stdout)
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 3=domain 路徑不存在,或 tools/ 與 hooks/ 都沒有 *.sh——**什麼都沒掃**,
|
||||||
|
# 不等於通過。缺這兩個目錄本身不是失敗,呼叫端照實記「無腳本可掃」即可。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: lint-scripts.sh <domain-path>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 1 ] || usage
|
||||||
|
DOMAIN=${1%/}
|
||||||
|
[ -n "$DOMAIN" ] || usage
|
||||||
|
[ -d "$DOMAIN" ] || { echo "找不到 domain 路徑:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
SCAN=''
|
||||||
|
for d in "$DOMAIN/tools" "$DOMAIN/hooks"; do
|
||||||
|
[ -d "$d" ] && SCAN="$SCAN $d"
|
||||||
|
done
|
||||||
|
[ -n "$SCAN" ] || { echo "沒有 tools/ 也沒有 hooks/,無腳本可掃:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
TMP=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
find $SCAN -type f -name '*.sh' 2>/dev/null | sort > "$TMP"
|
||||||
|
[ -s "$TMP" ] || { echo "tools/ 與 hooks/ 底下沒有 *.sh,無腳本可掃:$DOMAIN" >&2; exit 3; }
|
||||||
|
|
||||||
|
has_exit_stmt() { # $1=檔案;去掉整行註解後還找得到 exit 敘述就回 0
|
||||||
|
grep -v '^[[:space:]]*#' "$1" 2>/dev/null \
|
||||||
|
| grep -qE '(^|[;&|(){}[:space:]])exit([[:space:]]|$)'
|
||||||
|
}
|
||||||
|
|
||||||
|
is_library() { # $1=檔案;只被 source 的共用函式庫,可執行與結束碼兩項都豁免
|
||||||
|
# 判準兩條,缺一不可:
|
||||||
|
# 1. 自我宣告是函式庫——檔名 lib.sh,或檔頭前 30 行寫了「以 source 載入」。
|
||||||
|
# 2. 程式碼裡一個 exit 敘述都沒有。沒有 exit 就沒有結束碼語意,也就沒有東西可宣告;
|
||||||
|
# 這一條同時是「不是可執行入口」的證據,兩項豁免共用同一個事實。
|
||||||
|
# 為什麼不能只看第 1 條: 那是字樣比對,會命中「提到這件事」的腳本。本腳本自己的
|
||||||
|
# 例外段就寫了「以 source 載入」,plugins-root.sh 的用法段也寫了,但兩支都是有
|
||||||
|
# 結束碼語意的可執行入口。只拿第 1 條去豁免結束碼,等於把工具自己漏掉。
|
||||||
|
case "${1##*/}" in
|
||||||
|
lib.sh) ;;
|
||||||
|
*) head -30 "$1" 2>/dev/null | grep -q '以 source 載入' || return 1 ;;
|
||||||
|
esac
|
||||||
|
! has_exit_stmt "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
hit=0
|
||||||
|
total=0
|
||||||
|
libs=0
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
total=$((total + 1))
|
||||||
|
|
||||||
|
err=$(sh -n "$f" 2>&1) || {
|
||||||
|
printf '%s:語法:%s\n' "$f" "$(printf '%s' "$err" | tr '\n' ' ')"
|
||||||
|
hit=1
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_library "$f"; then
|
||||||
|
libs=$((libs + 1))
|
||||||
|
printf '%s:函式庫:判定為只被 source 的共用函式庫(無 exit 敘述),略過「可執行」與「結束碼」\n' "$f" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$f" ]; then
|
||||||
|
printf '%s:可執行:缺執行權限,請 chmod +x\n' "$f"
|
||||||
|
hit=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! head -60 "$f" 2>/dev/null | grep -qE '結束碼|exit code'; then
|
||||||
|
printf '%s:結束碼:檔頭沒有結束碼宣告列,呼叫端無法逐碼分流\n' "$f"
|
||||||
|
hit=1
|
||||||
|
fi
|
||||||
|
done < "$TMP"
|
||||||
|
|
||||||
|
if [ "$hit" -eq 0 ]; then
|
||||||
|
echo "腳本檢查通過:$total 支(語法、可執行、結束碼宣告),其中 $libs 支判定為函式庫" >&2
|
||||||
|
else
|
||||||
|
echo "腳本檢查有不合格項目:共掃 $total 支(其中 $libs 支判定為函式庫),清單見 stdout" >&2
|
||||||
|
fi
|
||||||
|
exit $hit
|
||||||
Executable
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# list-skills.sh — 列出本機所有 domain 存取庫的技能,供選技能與稽核用。
|
||||||
|
#
|
||||||
|
# 用法: list-skills.sh(不吃參數)
|
||||||
|
#
|
||||||
|
# 掃描: {root}/*/skills/*/SKILL.md,讀 frontmatter 的 name 與 description。
|
||||||
|
# domain 取存取庫目錄名,開頭的 jsc- 會去掉。
|
||||||
|
# **只列正本 marketplace 上的 domain**:工作區可能還放著不屬於技能組的
|
||||||
|
# 存取庫(例 shared、persona、code、doc),列出來會讓選技能的流程
|
||||||
|
# 誤把它們當成技能組的一部分。domain 清單取自 marketplace.json,不寫死。
|
||||||
|
# 清單只反映本機檔案;要確保檔案齊全請先跑 sync-domains.sh。
|
||||||
|
#
|
||||||
|
# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||||||
|
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||||||
|
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個技能,格式 {domain}<TAB>{name}<TAB>{description},依 domain、name 排序。
|
||||||
|
# 結束碼: 0=至少列出一個技能
|
||||||
|
# 1=推導不出根目錄、讀不到 domain 清單,或一個技能都沒有
|
||||||
|
set -u
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
|
||||||
|
ROOT=$(jsc_plugins_root) || exit 1
|
||||||
|
|
||||||
|
# 正本 domain 清單:優先讀本機任一份 marketplace 副本(每個 repo 都帶同一份)。
|
||||||
|
mkt=""
|
||||||
|
for cand in "$HERE/../.claude-plugin/marketplace.json" "$ROOT"/*/.claude-plugin/marketplace.json; do
|
||||||
|
[ -f "$cand" ] && { mkt="$cand"; break; }
|
||||||
|
done
|
||||||
|
[ -n "$mkt" ] || { echo "找不到 marketplace.json,無法判定 domain 清單" >&2; exit 1; }
|
||||||
|
DOMAINS=$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"jsc-\([a-z0-9-]*\)".*/\1/p' "$mkt" | sort -u)
|
||||||
|
[ -n "$DOMAINS" ] || { echo "marketplace.json 裡沒有 jsc-{domain} 條目:$mkt" >&2; exit 1; }
|
||||||
|
|
||||||
|
OUT=$(
|
||||||
|
for f in "$ROOT"/*/skills/*/SKILL.md; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
repo=${f#"$ROOT"/}
|
||||||
|
repo=${repo%%/*}
|
||||||
|
domain=${repo#jsc-}
|
||||||
|
# 不在正本清單上的存取庫直接跳過。
|
||||||
|
printf '%s\n' "$DOMAINS" | grep -qx "$domain" || continue
|
||||||
|
name=$(awk 'BEGIN{f=0} /^---$/{f++; next} f==1 && /^name:/{sub(/^name: */,""); print; exit}' "$f")
|
||||||
|
desc=$(awk 'BEGIN{f=0} /^---$/{f++; next} f==1 && /^description:/{sub(/^description: */,""); print; exit}' "$f")
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
printf '%s\t%s\t%s\n' "$domain" "$name" "$desc"
|
||||||
|
done | sort -t"$(printf '\t')" -k1,1 -k2,2
|
||||||
|
)
|
||||||
|
|
||||||
|
[ -n "$OUT" ] || { echo "掃不到任何 SKILL.md:$ROOT/*/skills/*/SKILL.md(先跑 sync-domains.sh)" >&2; exit 1; }
|
||||||
|
|
||||||
|
printf '%s\n' "$OUT"
|
||||||
|
count=$(printf '%s\n' "$OUT" | wc -l | tr -d ' ')
|
||||||
|
echo "共 $count 個技能" >&2
|
||||||
|
exit 0
|
||||||
Executable
+95
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# plugins-root.sh — 推導 jsc 技能組工作目錄的根,meta/tools 六支腳本共用同一套規則。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# . "$(dirname -- "$0")/plugins-root.sh" # 以 source 載入,取得 jsc_plugins_root()
|
||||||
|
# ROOT=$(jsc_plugins_root) || exit {該腳本的碼}
|
||||||
|
# sh plugins-root.sh # 直接執行,把推導結果印到 stdout
|
||||||
|
#
|
||||||
|
# 為什麼要抽出來:
|
||||||
|
# 舊寫法一律取「本腳本位置的上兩層」。技能組以 plugin 形式安裝時,腳本落在
|
||||||
|
# ~/.claude/plugins/cache/jsc/jsc-meta/{版本}/tools/,上兩層是 .../cache/jsc/jsc-meta,
|
||||||
|
# 那是 plugin 快取,不是放各 domain 存取庫的工作目錄,**一定推錯**。
|
||||||
|
# 2026-08 例行稽核第一步就實際踩到:不手動設 JSC_PLUGINS_ROOT 就跑不動,
|
||||||
|
# 而呼叫這些腳本的 SKILL.md 都沒提要設。
|
||||||
|
#
|
||||||
|
# 推導順序(取第一個帶得出 gitea.sh 的候選):
|
||||||
|
# 1. JSC_PLUGINS_ROOT
|
||||||
|
# 2. 從 $PWD 逐層往上,找含 meta/.claude-plugin/marketplace.json
|
||||||
|
# (或 jsc-meta/.claude-plugin/marketplace.json)的目錄
|
||||||
|
# 3. 本腳本位置的上兩層({root}/meta/tools -> {root})
|
||||||
|
# 4. $HOME/plugins
|
||||||
|
#
|
||||||
|
# 判準: 候選目錄下要有 gitea/tools/gitea.sh 或 jsc-gitea/tools/gitea.sh。
|
||||||
|
# marketplace.json 每個 domain 存取庫都帶一份,單看它會把某個 domain 存取庫自己
|
||||||
|
# 誤判成根;gitea.sh 只存在於 jsc-gitea 存取庫裡,拿它當標記才分得出根與 domain。
|
||||||
|
#
|
||||||
|
# 逃生門: 候選都不帶 gitea.sh,但 JSC_PLUGINS_ROOT 有設且是目錄時,照設定值使用,
|
||||||
|
# 並在 stderr 提醒。只 clone plugins/meta 的環境(CI、單存取庫維護)本來就沒有
|
||||||
|
# jsc-gitea;這時硬擋會讓「請設 JSC_PLUGINS_ROOT」變成解不開的死路。
|
||||||
|
#
|
||||||
|
# 結束碼(直接執行時): 0=推導成功,根目錄印在 stdout
|
||||||
|
# 1=推導不出來,訊息會指名要設 JSC_PLUGINS_ROOT 並列出試過的候選
|
||||||
|
# 環境變數: JSC_PLUGINS_ROOT(根目錄,最優先)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
_jsc_walk_up() { # 從 $PWD 逐層往上,印出第一個含 meta 存取庫的目錄
|
||||||
|
_jpr_d=$(pwd -P 2>/dev/null) || return 1
|
||||||
|
while [ -n "$_jpr_d" ]; do
|
||||||
|
if [ -f "$_jpr_d/meta/.claude-plugin/marketplace.json" ] ||
|
||||||
|
[ -f "$_jpr_d/jsc-meta/.claude-plugin/marketplace.json" ]; then
|
||||||
|
printf '%s\n' "$_jpr_d"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
[ "$_jpr_d" = / ] && break
|
||||||
|
_jpr_d=$(dirname -- "$_jpr_d")
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_jsc_has_gitea() { # $1=候選根目錄;jsc-gitea 存取庫是「這是根」的唯一可靠標記
|
||||||
|
[ -f "$1/gitea/tools/gitea.sh" ] || [ -f "$1/jsc-gitea/tools/gitea.sh" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsc_plugins_root() { # 印出根目錄(stdout);推不出來回 1,說明走 stderr
|
||||||
|
_jpr_tried=''
|
||||||
|
for _jpr_src in env pwd here home; do
|
||||||
|
case "$_jpr_src" in
|
||||||
|
env) _jpr_c="${JSC_PLUGINS_ROOT:-}" ;;
|
||||||
|
pwd) _jpr_c=$(_jsc_walk_up 2>/dev/null) || _jpr_c='' ;;
|
||||||
|
here) _jpr_c=$(CDPATH= cd -- "$(dirname -- "$0")/../.." 2>/dev/null && pwd) || _jpr_c='' ;;
|
||||||
|
home) if [ -n "${HOME:-}" ]; then _jpr_c="$HOME/plugins"; else _jpr_c=''; fi ;;
|
||||||
|
*) _jpr_c='' ;;
|
||||||
|
esac
|
||||||
|
[ -n "$_jpr_c" ] || continue
|
||||||
|
_jpr_tried="$_jpr_tried $_jpr_src: $_jpr_c
|
||||||
|
"
|
||||||
|
[ -d "$_jpr_c" ] || continue
|
||||||
|
_jsc_has_gitea "$_jpr_c" || continue
|
||||||
|
_jpr_c=$(CDPATH= cd -- "$_jpr_c" && pwd) || continue
|
||||||
|
printf '%s\n' "${_jpr_c%/}"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "${JSC_PLUGINS_ROOT:-}" ] && [ -d "$JSC_PLUGINS_ROOT" ]; then
|
||||||
|
echo "JSC_PLUGINS_ROOT 底下找不到 gitea/tools/gitea.sh,仍照設定值使用:$JSC_PLUGINS_ROOT" >&2
|
||||||
|
_jpr_c=$(CDPATH= cd -- "$JSC_PLUGINS_ROOT" && pwd) || return 1
|
||||||
|
printf '%s\n' "${_jpr_c%/}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo '推導不出 jsc 技能組的根目錄。'
|
||||||
|
echo '以 plugin 形式安裝時,腳本位在 ~/.claude/plugins/cache/jsc/jsc-meta/{版本}/tools/,'
|
||||||
|
echo '上兩層落在 plugin 快取目錄,不是放各 domain 存取庫的工作目錄,所以推不出來。'
|
||||||
|
echo '請設 JSC_PLUGINS_ROOT 指向放各 domain 存取庫的工作目錄(底下要有 gitea 或 jsc-gitea)。'
|
||||||
|
echo '已試過的候選:'
|
||||||
|
printf '%s' "$_jpr_tried"
|
||||||
|
} >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 直接執行時印出結果;被 source 時只提供函式。
|
||||||
|
case "${0##*/}" in
|
||||||
|
plugins-root.sh) jsc_plugins_root || exit 1 ;;
|
||||||
|
esac
|
||||||
+107
-6
@@ -5,15 +5,108 @@
|
|||||||
# 1. 中國用語(references/ste100.md 的替換表左欄)
|
# 1. 中國用語(references/ste100.md 的替換表左欄)
|
||||||
# 2. 中文句內的半形標點(, ; ! ? 緊鄰中日韓字元)
|
# 2. 中文句內的半形標點(, ; ! ? 緊鄰中日韓字元)
|
||||||
# 3. 常見 AI 套話(總的來說、綜上所述、希望這對你有幫助 等)
|
# 3. 常見 AI 套話(總的來說、綜上所述、希望這對你有幫助 等)
|
||||||
# 輸出: {檔案}:{行號}:{類別}:{命中內容};全部通過 exit 0,有命中 exit 1。
|
# 4. 簡體字(只收沒有繁體正當用法的字,避免誤報)
|
||||||
# 限制: 只掃 .md 檔;程式碼圍欄內的內容可能誤報,人工複核。
|
# 5. 中文並列項用斜線(半形 / 或全形 / 夾在中日韓字元之間),應改頓號「、」
|
||||||
|
# 6. 亂碼(U+FFFD 替代字元、Latin-1 雙重編碼殘留)
|
||||||
|
# 輸出: {檔案}:{行號}:{類別}:{命中內容}(stdout);用法錯誤走 stderr。
|
||||||
|
# 結束碼: 0=掃過的檔案全部通過 1=有命中 2=沒給檢查對象(空跑會回 0,看起來像通過,所以擋掉)
|
||||||
|
# 環境變數: JSC_SIMPLIFIED_FILE(簡體字表路徑,優先於自動搜尋)
|
||||||
|
# 掃描範圍與分流:
|
||||||
|
# 文件檔(.md、.json)跑全部六項。
|
||||||
|
# 程式碼檔(.sh .js .ts .py .cs .java .go .rb .php .sql .yml .yaml .toml)只跑簡體字與亂碼。
|
||||||
|
# 理由: 程式碼註解也要繁中無亂碼,但中國用語、半形標點、AI 套話、並列斜線這四項在程式碼裡
|
||||||
|
# 誤報率太高——英文標點、檔案路徑、URL、正規表示式到處都是半形逗號與斜線,識別字也常撞到
|
||||||
|
# 替換表左欄。只留簡體字與亂碼,命中就幾乎都是真的。
|
||||||
|
# 限制: 程式碼圍欄內的內容可能誤報,人工複核。
|
||||||
|
# references/ste100.md 與本檔本身跳過——規則文件與規則實作裡的詞是被討論,不是被使用。
|
||||||
|
# 並列斜線會誤報兩類:含中文的路徑或分支名範例(例 feat/報表/P2),
|
||||||
|
# 以及英文項目並列(準則允許)。命中後先判斷是不是真的並列項。
|
||||||
set -u
|
set -u
|
||||||
TERMS='默認|支持某|兼容|信息|數據庫|服務器|軟件|硬件|網絡|質量|卸載|反饋|視頻|屏幕|鼠標|打印|立馬|靠譜|賦能|閉環|抓手|復盤'
|
TERMS='默認|支持某|兼容|信息|數據庫|服務器|軟件|硬件|網絡|質量|卸載|反饋|視頻|屏幕|鼠標|打印|立馬|靠譜|賦能|閉環|抓手|復盤'
|
||||||
CLICHES='總的來說|綜上所述|希望這對你有幫助|好問題|在當今|瞬息萬變|標誌著|奠定了基礎|體現了|不僅僅是|讓我們一起'
|
CLICHES='總的來說|綜上所述|希望這對你有幫助|好問題|在當今|瞬息萬變|標誌著|奠定了基礎|體現了|不僅僅是|讓我們一起'
|
||||||
|
# 亂碼特徵,全部用十六進位跳脫寫,本檔才不會存進真的亂碼字元自己打自己:
|
||||||
|
# \x{fffd} 替代字元,轉檔失敗留下的痕跡
|
||||||
|
# \x{ef}\x{bf}\x{bd} U+FFFD 本身又被雙重編碼一次(顯示成 �)
|
||||||
|
# [\x{c2}-\x{f4}][\x{80}-\x{bf}]
|
||||||
|
# 雙重編碼特徵:UTF-8 位元組被當 Latin-1 解讀再存回去,原本的前導位元組變成
|
||||||
|
# U+00C2–U+00F4 的拉丁字母(Ã、â、ä),後面緊跟著本來的續接位元組 U+0080–U+00BF。
|
||||||
|
# 兩者相連在正常文字裡幾乎不會出現,所以誤報極低;只寫 Ã 或 â 開頭會漏掉中日韓
|
||||||
|
# 字元最常見的 ä¸ 這一類。
|
||||||
|
GARBLED='\x{fffd}|\x{ef}\x{bf}\x{bd}|[\x{c2}-\x{f4}][\x{80}-\x{bf}]'
|
||||||
|
# 簡體字表的真實來源是 jsc-hooks 的 hooks/simplified.txt(ste100-guard.sh 共用同一份)。
|
||||||
|
# 讀不到就退回下面的內建備援字表。備援不能拿掉: jsc-hooks 不一定裝在這台機器上(單獨 clone
|
||||||
|
# plugins/meta、CI 只取一個 repo 都會發生),缺了基礎設施就讓簡體字檢查靜靜失效,會把「沒命中」
|
||||||
|
# 變成假通過,比不檢查更危險。
|
||||||
|
# 刻意排除繁體也在用的字(后、台、干、只、里、面、制、志),只留簡化後才出現的字形。
|
||||||
|
SIMPLIFIED_FALLBACK='应|为|这|说|发|国|过|对|开|关|问|题|东|车|马|鸟|龙|飞|见|无|产|业|务|书|写|学|习|报|纸|认|识|证|际|网|络|计|划|实|现|给|条|约|级|组|织|变|换|电|脑|两|双|单|构|价|钱|众|议|论|传|统|么|儿|们|从|来|时|间|长|门|闻|声|员|图|团|转|输|达|运|进|远|连|边|还|经|该|营|规|则|范|围|参|数|类|结|态|设|备|辑|译|码|库|档'
|
||||||
|
|
||||||
|
# 找共用字表。搜尋路徑比照 jsc-hooks/hooks/lib.sh 的 jsc_gitea_sh():
|
||||||
|
# 先環境變數,再開發用的並排存取庫版面,最後已安裝的 plugin 快取版面。
|
||||||
|
simplified_file() {
|
||||||
|
if [ -n "${JSC_SIMPLIFIED_FILE:-}" ] && [ -f "$JSC_SIMPLIFIED_FILE" ]; then
|
||||||
|
printf '%s\n' "$JSC_SIMPLIFIED_FILE"; return 0
|
||||||
|
fi
|
||||||
|
_root="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
||||||
|
# 開發用的並排存取庫版面:{workspace}/meta 旁邊就是 {workspace}/hooks
|
||||||
|
for _c in "$_root/../hooks/hooks/simplified.txt" "$_root/../jsc-hooks/hooks/simplified.txt"; do
|
||||||
|
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||||||
|
done
|
||||||
|
# 已安裝版面:每個 plugin 各有版本目錄,取排序最後的一份(通常即最新版)
|
||||||
|
_c=$(ls "$_root"/../../jsc-hooks/*/hooks/simplified.txt \
|
||||||
|
"$_root"/../../hooks/*/hooks/simplified.txt \
|
||||||
|
"$HOME"/.claude/plugins/cache/*/jsc-hooks/*/hooks/simplified.txt 2>/dev/null \
|
||||||
|
| sort | tail -n1)
|
||||||
|
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
SIMPLIFIED=''
|
||||||
|
SIMPLIFIED_SRC=$(simplified_file || true)
|
||||||
|
if [ -n "$SIMPLIFIED_SRC" ]; then
|
||||||
|
# 一行一個字,# 開頭與空行忽略,行內空白去掉,再串成 grep -E 的交替式
|
||||||
|
SIMPLIFIED=$(sed 's/#.*//; s/[[:space:]]//g' "$SIMPLIFIED_SRC" \
|
||||||
|
| grep -v '^$' | tr '\n' '|' | sed 's/|$//')
|
||||||
|
fi
|
||||||
|
[ -n "$SIMPLIFIED" ] || SIMPLIFIED=$SIMPLIFIED_FALLBACK
|
||||||
|
|
||||||
hit=0
|
hit=0
|
||||||
|
# 不帶參數會空跑並回 0,看起來像「全部通過」。這種假通過比不檢查更危險,所以擋掉。
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
echo '用法: ste100-lint.sh <file|dir> [...]' >&2
|
||||||
|
echo '沒有給檢查對象。空跑會回 0,看起來像通過,所以這裡直接視為錯誤。' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
skip() { # $1=路徑;規則文件、字表與規則實作本身不掃
|
||||||
|
# 這幾個檔案裡的簡體字是被討論、被列舉的對象,不是被使用。掃它們等於自己打自己,
|
||||||
|
# 每次都命中卻永遠改不掉,久了就會有人把整個檢查關掉。
|
||||||
|
case "$1" in
|
||||||
|
*/references/ste100.md|references/ste100.md) return 0 ;;
|
||||||
|
*/tools/ste100-lint.sh|tools/ste100-lint.sh) return 0 ;;
|
||||||
|
*/hooks/simplified.txt|simplified.txt) return 0 ;;
|
||||||
|
*/hooks/ste100-guard.sh|ste100-guard.sh) return 0 ;;
|
||||||
|
*/hooks/lang-guard.sh|lang-guard.sh) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
kind() { # $1=路徑;輸出 doc(跑全部六項)或 code(只跑簡體字與亂碼),其餘不掃
|
||||||
|
case "$1" in
|
||||||
|
*.md|*.json) printf 'doc' ;;
|
||||||
|
*.sh|*.js|*.ts|*.py|*.cs|*.java|*.go|*.rb|*.php|*.sql|*.yml|*.yaml|*.toml) printf 'code' ;;
|
||||||
|
*) printf '' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
files() {
|
files() {
|
||||||
for p in "$@"; do
|
for p in "$@"; do
|
||||||
if [ -d "$p" ]; then find "$p" -name '*.md' -not -path '*/.git/*'; else echo "$p"; fi
|
if [ -d "$p" ]; then
|
||||||
|
find "$p" \( \
|
||||||
|
-name '*.md' -o -name '*.json' \
|
||||||
|
-o -name '*.sh' -o -name '*.js' -o -name '*.ts' -o -name '*.py' \
|
||||||
|
-o -name '*.cs' -o -name '*.java' -o -name '*.go' -o -name '*.rb' \
|
||||||
|
-o -name '*.php' -o -name '*.sql' -o -name '*.yml' -o -name '*.yaml' \
|
||||||
|
-o -name '*.toml' \) -not -path '*/.git/*'
|
||||||
|
else
|
||||||
|
echo "$p"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
check() { # $1=檔案 $2=類別 $3=grep 模式 $4=grep 旗標
|
check() { # $1=檔案 $2=類別 $3=grep 模式 $4=grep 旗標
|
||||||
@@ -21,8 +114,16 @@ check() { # $1=檔案 $2=類別 $3=grep 模式 $4=grep 旗標
|
|||||||
if [ -n "$out" ]; then printf '%s\n' "$out"; hit=1; fi
|
if [ -n "$out" ]; then printf '%s\n' "$out"; hit=1; fi
|
||||||
}
|
}
|
||||||
for f in $(files "$@"); do
|
for f in $(files "$@"); do
|
||||||
check "$f" "中國用語" "$TERMS" E
|
skip "$f" && continue
|
||||||
check "$f" "半形標點" '[\x{4e00}-\x{9fff}][,;!?]|[,;][\x{4e00}-\x{9fff}]' P
|
k=$(kind "$f")
|
||||||
check "$f" "AI 套話" "$CLICHES" E
|
[ -n "$k" ] || continue
|
||||||
|
if [ "$k" = doc ]; then
|
||||||
|
check "$f" "中國用語" "$TERMS" E
|
||||||
|
check "$f" "半形標點" '[\x{4e00}-\x{9fff}][,;!?]|[,;][\x{4e00}-\x{9fff}]' P
|
||||||
|
check "$f" "AI 套話" "$CLICHES" E
|
||||||
|
check "$f" "並列斜線" '[\x{4e00}-\x{9fff}][ ]?[//][ ]?[\x{4e00}-\x{9fff}]' P
|
||||||
|
fi
|
||||||
|
check "$f" "簡體字" "$SIMPLIFIED" E
|
||||||
|
check "$f" "亂碼" "$GARBLED" P
|
||||||
done
|
done
|
||||||
exit $hit
|
exit $hit
|
||||||
|
|||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# sync-domains.sh — 依 Gitea 正本 marketplace 把所有 domain 存取庫同步到本機。
|
||||||
|
#
|
||||||
|
# 用法: sync-domains.sh(不吃參數)
|
||||||
|
#
|
||||||
|
# 流程:
|
||||||
|
# 1. 透過 jsc-gitea/tools/gitea.sh 讀正本 marketplace
|
||||||
|
# (plugins/meta 的 .claude-plugin/marketplace.json),取得權威 domain 清單。
|
||||||
|
# 2. 本機缺少的 domain:用 gitea.sh clone-url plugins/{domain} 取網址後 clone。
|
||||||
|
# 3. 本機已有的 domain:git pull。工作區有未提交變更就跳過 pull,只在 stderr 提醒——
|
||||||
|
# 正在改的 repo 不該被自動 pull 覆蓋。
|
||||||
|
#
|
||||||
|
# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||||||
|
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||||||
|
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||||||
|
# 存取庫目錄名: 先找 {root}/{domain},再找 {root}/jsc-{domain};都沒有才 clone 成 {root}/{domain}。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個 domain,格式 {domain}<TAB>{path}(stdout);警告與失敗說明走 stderr。
|
||||||
|
# 結束碼: 0=正本讀到、每個 domain 都在本機,而且每個既有存取庫都更新到最新
|
||||||
|
# 1=推導不出根目錄、讀不到正本 marketplace,或找不到 gitea.sh(不輸出任何 domain)
|
||||||
|
# 2=正本讀到,但有 domain 沒能取得或 clone 失敗(已輸出其餘 domain)
|
||||||
|
# 3=每個 domain 都在本機,但有存取庫沒更新到最新:工作區髒而跳過 pull,
|
||||||
|
# 或 pull 失敗。stderr 會逐一列出這些路徑。**exit 0 才代表「全部最新」**;
|
||||||
|
# 拿 3 當乾淨會讓後續步驟改在落後的檔案上。
|
||||||
|
# 同時發生時回較嚴重的那個:2 蓋過 3。
|
||||||
|
# 環境變數: JSC_PLUGINS_ROOT(根目錄)
|
||||||
|
# JSC_SYNC_DRY_RUN=1(只印出會做什麼,不動檔案;不執行 pull/clone,
|
||||||
|
# 所以也不因為「沒更新」回 3)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
DRY="${JSC_SYNC_DRY_RUN:-}"
|
||||||
|
|
||||||
|
ROOT=$(jsc_plugins_root) || exit 1
|
||||||
|
|
||||||
|
GITEA=""
|
||||||
|
for g in "$ROOT/gitea/tools/gitea.sh" "$ROOT/jsc-gitea/tools/gitea.sh"; do
|
||||||
|
[ -f "$g" ] && { GITEA="$g"; break; }
|
||||||
|
done
|
||||||
|
[ -n "$GITEA" ] || { echo "找不到 gitea.sh:請先取得 jsc-gitea 存取庫" >&2; exit 1; }
|
||||||
|
|
||||||
|
mkt=$(sh "$GITEA" api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json 2>/dev/null) || mkt=""
|
||||||
|
[ -n "$mkt" ] || { echo "讀不到正本 marketplace:檢查 GITEA_HOST、GITEA_TOKEN 或 tea 登入" >&2; exit 1; }
|
||||||
|
|
||||||
|
DOMAINS=$(printf '%s\n' "$mkt" | sed -n 's/.*"name": *"jsc-\([a-z0-9-]*\)".*/\1/p' | sort -u)
|
||||||
|
[ -n "$DOMAINS" ] || { echo "正本 marketplace 裡沒有 jsc-{domain} 條目" >&2; exit 1; }
|
||||||
|
|
||||||
|
repo_path() { # $1=domain;印出既有路徑(return 0)或預設路徑(return 1)
|
||||||
|
for d in "$ROOT/$1" "$ROOT/jsc-$1"; do
|
||||||
|
[ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
||||||
|
done
|
||||||
|
printf '%s' "$ROOT/$1"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
stale=0
|
||||||
|
for domain in $DOMAINS; do
|
||||||
|
if path=$(repo_path "$domain"); then
|
||||||
|
if [ -n "$(git -C "$path" status --porcelain 2>/dev/null)" ]; then
|
||||||
|
echo "工作區有未提交變更,跳過 pull,未更新到最新:$path" >&2
|
||||||
|
stale=1
|
||||||
|
elif [ "$DRY" = 1 ]; then
|
||||||
|
echo "[dry-run] git -C $path pull --ff-only" >&2
|
||||||
|
elif ! git -C "$path" pull --quiet --ff-only >/dev/null 2>&1; then
|
||||||
|
echo "pull 失敗,保留本機版本,未更新到最新:$path" >&2
|
||||||
|
stale=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
url=$(sh "$GITEA" clone-url "plugins/$domain" 2>/dev/null) || url=""
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
echo "取不到 clone 網址,略過:plugins/$domain" >&2
|
||||||
|
rc=2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$DRY" = 1 ]; then
|
||||||
|
echo "[dry-run] git clone $url $path" >&2
|
||||||
|
elif ! git clone --quiet "$url" "$path" >/dev/null 2>&1; then
|
||||||
|
echo "clone 失敗:$url" >&2
|
||||||
|
rc=2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\n' "$domain" "$path"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2(取不到或 clone 失敗)比 3(沒更新到最新)嚴重,優先回報。
|
||||||
|
[ "$rc" -eq 0 ] && [ "$stale" -eq 1 ] && rc=3
|
||||||
|
exit $rc
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# sync-marketplace.sh — 把一個 plugin 條目寫進正本 marketplace,再同步到所有 domain 存取庫。
|
||||||
|
#
|
||||||
|
# 用法: sync-marketplace.sh <domain> <repo-url> <description>
|
||||||
|
# 例: sync-marketplace.sh doc https://gitea.jsc.idv.tw/plugins/doc.git "文件產出與同步"
|
||||||
|
#
|
||||||
|
# 行為:
|
||||||
|
# 1. 在 plugins/meta 的兩份正本(.claude-plugin/marketplace.json 與
|
||||||
|
# .agents/plugins/marketplace.json)插入或更新 jsc-{domain} 條目;已存在就覆寫網址與描述。
|
||||||
|
# 條目依 name 排序,格式維持兩格縮排、不轉義非 ASCII、結尾一個換行。
|
||||||
|
# 2. 兩份正本與所有副本都由同一份算好的內容複製過去,保證位元組完全一致。
|
||||||
|
# 3. 逐一複製到每個 domain 存取庫的相同兩個路徑(含新 domain 自己)。
|
||||||
|
# 4. 全部寫完後逐檔比對,有任何一份不一致就報錯。
|
||||||
|
#
|
||||||
|
# 依賴: **python3**(只用標準函式庫的 json 模組)。
|
||||||
|
# 本 repo 的腳本一律 shell 優先,這裡是唯一例外:條目的插入、排序與重新序列化要動 JSON,
|
||||||
|
# 純 shell 拼 JSON 會壞掉(縮排、逸出、非 ASCII 描述)。所以保留 python3。
|
||||||
|
# python3 不在 PATH 時:腳本不寫任何檔案,印出缺少 python3 的訊息,回 1。
|
||||||
|
# 呼叫端要先裝 python3 再重跑,不可改用 sed 手動改 marketplace。
|
||||||
|
#
|
||||||
|
# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||||||
|
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||||||
|
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||||||
|
# 存取庫目錄名: 先找 {root}/{domain},再找 {root}/jsc-{domain}。
|
||||||
|
#
|
||||||
|
# 輸出: 一行一個實際寫入的檔案路徑(stdout);略過與失敗說明走 stderr。
|
||||||
|
# 結束碼: 0=全部寫入且逐檔一致 2=用法錯誤
|
||||||
|
# 1=推導不出根目錄、缺 python3、正本讀寫失敗、建不了暫存目錄,
|
||||||
|
# 或有檔案比對不一致
|
||||||
|
# 3=寫入成功,但有 domain 存取庫不在本機(先跑 sync-domains.sh 再重跑)
|
||||||
|
# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: sync-marketplace.sh <domain> <repo-url> <description>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 3 ] || usage
|
||||||
|
DOMAIN=$1
|
||||||
|
URL=$2
|
||||||
|
DESC=$3
|
||||||
|
[ -n "$DOMAIN" ] && [ -n "$URL" ] && [ -n "$DESC" ] || usage
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
META=$(CDPATH= cd -- "$HERE/.." && pwd)
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
ROOT=$(jsc_plugins_root) || exit 1
|
||||||
|
|
||||||
|
REL_CLAUDE='.claude-plugin/marketplace.json'
|
||||||
|
REL_AGENTS='.agents/plugins/marketplace.json'
|
||||||
|
CANON="$META/$REL_CLAUDE"
|
||||||
|
|
||||||
|
[ -f "$CANON" ] || { echo "找不到正本 marketplace:$CANON" >&2; exit 1; }
|
||||||
|
|
||||||
|
# 先擋掉缺 python3 的環境,才不會寫到一半才失敗。
|
||||||
|
command -v python3 >/dev/null 2>&1 || {
|
||||||
|
echo '找不到 python3:本腳本用 python3 的 json 模組改 marketplace 條目。請先安裝 python3 再重跑。' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
WORKDIR=$(mktemp -d) || exit 1
|
||||||
|
trap 'rm -rf "$WORKDIR"' EXIT
|
||||||
|
NEW="$WORKDIR/marketplace.json"
|
||||||
|
|
||||||
|
# 條目的插入與更新交給 json 解析器;用 sed 拼 JSON 會壞掉。
|
||||||
|
JSC_MK_DOMAIN="$DOMAIN" JSC_MK_URL="$URL" JSC_MK_DESC="$DESC" \
|
||||||
|
JSC_MK_IN="$CANON" JSC_MK_OUT="$NEW" python3 - <<'PY' || { echo "更新正本內容失敗:$CANON" >&2; exit 1; }
|
||||||
|
import json, os
|
||||||
|
|
||||||
|
name = "jsc-" + os.environ["JSC_MK_DOMAIN"]
|
||||||
|
entry = {
|
||||||
|
"name": name,
|
||||||
|
"source": {"source": "url", "url": os.environ["JSC_MK_URL"]},
|
||||||
|
"description": os.environ["JSC_MK_DESC"],
|
||||||
|
}
|
||||||
|
with open(os.environ["JSC_MK_IN"], encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
plugins = [p for p in data.get("plugins", []) if p.get("name") != name]
|
||||||
|
plugins.append(entry)
|
||||||
|
data["plugins"] = sorted(plugins, key=lambda p: p.get("name", ""))
|
||||||
|
with open(os.environ["JSC_MK_OUT"], "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
||||||
|
PY
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
|
||||||
|
write_copy() { # $1=目標檔案
|
||||||
|
target=$1
|
||||||
|
dir=$(dirname "$target")
|
||||||
|
mkdir -p "$dir" 2>/dev/null || { echo "無法建立目錄:$dir" >&2; rc=1; return 1; }
|
||||||
|
cp "$NEW" "$target" 2>/dev/null || { echo "無法寫入:$target" >&2; rc=1; return 1; }
|
||||||
|
printf '%s\n' "$target"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- 1. 兩份正本 ----
|
||||||
|
write_copy "$META/$REL_CLAUDE"
|
||||||
|
write_copy "$META/$REL_AGENTS"
|
||||||
|
|
||||||
|
# ---- 2. 每個 domain 存取庫的副本 ----
|
||||||
|
DOMAINS=$(sed -n 's/.*"name": *"jsc-\([a-z0-9-]*\)".*/\1/p' "$NEW" | sort -u)
|
||||||
|
for d in $DOMAINS; do
|
||||||
|
path=""
|
||||||
|
for cand in "$ROOT/$d" "$ROOT/jsc-$d"; do
|
||||||
|
[ -d "$cand" ] && { path="$cand"; break; }
|
||||||
|
done
|
||||||
|
if [ -z "$path" ]; then
|
||||||
|
echo "存取庫不在本機,略過:$d(先跑 sync-domains.sh)" >&2
|
||||||
|
[ "$rc" -eq 0 ] && rc=3
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[ "$path" = "$META" ] && continue
|
||||||
|
write_copy "$path/$REL_CLAUDE"
|
||||||
|
write_copy "$path/$REL_AGENTS"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- 3. 逐檔比對,確認位元組一致 ----
|
||||||
|
for d in "$META" $(for x in $DOMAINS; do
|
||||||
|
for cand in "$ROOT/$x" "$ROOT/jsc-$x"; do [ -d "$cand" ] && { echo "$cand"; break; }; done
|
||||||
|
done); do
|
||||||
|
for rel in "$REL_CLAUDE" "$REL_AGENTS"; do
|
||||||
|
[ -f "$d/$rel" ] || continue
|
||||||
|
cmp -s "$NEW" "$d/$rel" || { echo "內容不一致:$d/$rel" >&2; rc=1; }
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $rc
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
# sync-skill-manifest.sh — 同步 domain 的 README「Skills 目錄」區塊,並把該 domain 所有
|
# sync-skill-manifest.sh — 同步 domain 的 README「Skills 目錄」區塊,並把該 domain 所有
|
||||||
# plugin manifest 的 version 各 bump 一個 patch。
|
# plugin manifest 的 version 各 bump 一次,且 major 可超過 9;minor 與 patch 仍是單位數。
|
||||||
#
|
#
|
||||||
# 用法: sync-skill-manifest.sh <domain-path>
|
# 用法: sync-skill-manifest.sh <domain-path>
|
||||||
#
|
#
|
||||||
@@ -11,9 +11,16 @@
|
|||||||
#
|
#
|
||||||
# manifest: 依序找出 domain 下存在的 plugin.json、.claude-plugin/plugin.json、
|
# manifest: 依序找出 domain 下存在的 plugin.json、.claude-plugin/plugin.json、
|
||||||
# .codex-plugin/plugin.json,version 一律 bump 成同一個新值(以第一份找到的 manifest 版本為準,
|
# .codex-plugin/plugin.json,version 一律 bump 成同一個新值(以第一份找到的 manifest 版本為準,
|
||||||
# patch 位加一;major.minor 不變)。
|
# 右側數字加一,滿 9 就往左進位;major 不設上限,minor 與 patch 都不超過 9)。
|
||||||
#
|
#
|
||||||
# 輸出: 變更摘要——README 新增/移除的技能小節、各 manifest 的舊版本 -> 新版本。
|
# 輸出: 變更摘要——README 新增或移除的技能小節、各 manifest 的舊版本 -> 新版本(stdout);
|
||||||
|
# 失敗說明走 stderr。
|
||||||
|
# 結束碼: 0=README 區塊與三份 manifest 都已同步
|
||||||
|
# 1=domain 路徑不存在、缺 skills/、缺 README.md、README 缺 JSC-SKILLS 標記、
|
||||||
|
# 掃不到任何 SKILL.md、找不到 plugin manifest,或 manifest 讀不到 version 欄位
|
||||||
|
# 2=用法錯誤(本腳本只吃一個參數)
|
||||||
|
# 本腳本是 set -e:上列以外的指令失敗會直接中止,結束碼由該指令決定,
|
||||||
|
# 呼叫端把「不是 0、1、2」一律當執行環境故障處理,不得視為同步成功。
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
@@ -151,7 +158,20 @@ old=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$first" | head -1)
|
|||||||
major=$(printf '%s' "$old" | cut -d. -f1)
|
major=$(printf '%s' "$old" | cut -d. -f1)
|
||||||
minor=$(printf '%s' "$old" | cut -d. -f2)
|
minor=$(printf '%s' "$old" | cut -d. -f2)
|
||||||
patch=$(printf '%s' "$old" | cut -d. -f3)
|
patch=$(printf '%s' "$old" | cut -d. -f3)
|
||||||
new="$major.$minor.$((patch + 1))"
|
|
||||||
|
major=$((major + 0))
|
||||||
|
minor=$((minor + 0))
|
||||||
|
patch=$((patch + 1))
|
||||||
|
while [ "$patch" -gt 9 ]; do
|
||||||
|
patch=$((patch - 10))
|
||||||
|
minor=$((minor + 1))
|
||||||
|
done
|
||||||
|
while [ "$minor" -gt 9 ]; do
|
||||||
|
minor=$((minor - 10))
|
||||||
|
major=$((major + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
new="$major.$minor.$patch"
|
||||||
|
|
||||||
echo "== manifest version =="
|
echo "== manifest version =="
|
||||||
for m in $MANIFESTS; do
|
for m in $MANIFESTS; do
|
||||||
|
|||||||
Executable
+85
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# verify-skill-removed.sh — 刪除技能後,實地檢查各 CLI 的磁碟上有沒有殘留。
|
||||||
|
#
|
||||||
|
# 用法: verify-skill-removed.sh <domain> <skill-name>
|
||||||
|
#
|
||||||
|
# 行為: 先用 jsc-cli/tools/detect-clis.sh 找出已安裝的 CLI,再逐一 grep 該 CLI 的技能快取
|
||||||
|
# 與 hook 設定,找技能名稱字面與指令形式 /jsc-{domain}:{name}。
|
||||||
|
# 未安裝的 CLI 不檢查;設定位置不存在就跳過(安靜降級,不算殘留)。
|
||||||
|
#
|
||||||
|
# 檢查位置(每個 CLI 的技能快取與 hook 設定):
|
||||||
|
# claude — ~/.claude/plugins/、~/.claude/settings.json、~/.claude/settings.local.json
|
||||||
|
# codex — ${CODEX_HOME:-~/.codex}/(config.toml 的 notify、AGENTS.md、plugins/)
|
||||||
|
# copilot — ~/.config/copilot/
|
||||||
|
# antigravity — ~/.antigravity/、~/.config/antigravity/
|
||||||
|
# kiro — ~/.kiro/、工作目錄的 .kiro/hooks/
|
||||||
|
#
|
||||||
|
# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||||||
|
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||||||
|
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||||||
|
#
|
||||||
|
# 輸出: 檢查過的位置一行一個(stderr),殘留一行一個 {file}:{line}:{內容}(stdout)。
|
||||||
|
# 沒有殘留會印「無殘留」到 stderr,區別於「什麼都沒檢查」。
|
||||||
|
# 結束碼: 0=沒有殘留 1=有殘留 2=用法錯誤
|
||||||
|
# 3=推導不出根目錄、找不到 detect-clis.sh、沒偵測到任何 CLI,
|
||||||
|
# 或找不到可檢查的位置(**不等於乾淨**)
|
||||||
|
# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: verify-skill-removed.sh <domain> <skill-name>' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -eq 2 ] || usage
|
||||||
|
DOMAIN=$1
|
||||||
|
SKILL=$2
|
||||||
|
[ -n "$DOMAIN" ] && [ -n "$SKILL" ] || usage
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$HERE/plugins-root.sh"
|
||||||
|
ROOT=$(jsc_plugins_root) || exit 3
|
||||||
|
|
||||||
|
DETECT=""
|
||||||
|
for d in "$ROOT/cli/tools/detect-clis.sh" "$ROOT/jsc-cli/tools/detect-clis.sh"; do
|
||||||
|
[ -f "$d" ] && { DETECT="$d"; break; }
|
||||||
|
done
|
||||||
|
[ -n "$DETECT" ] || { echo "找不到 detect-clis.sh:請先取得 jsc-cli 存取庫" >&2; exit 3; }
|
||||||
|
|
||||||
|
CLIS=$(sh "$DETECT" 2>/dev/null | cut -f1)
|
||||||
|
[ -n "$CLIS" ] || { echo "沒偵測到任何已安裝的 CLI,無處可查" >&2; exit 3; }
|
||||||
|
|
||||||
|
cli_paths() { # $1=CLI 代號;印出該 CLI 的技能快取與 hook 設定位置
|
||||||
|
case "$1" in
|
||||||
|
claude)
|
||||||
|
printf '%s\n' "$HOME/.claude/plugins" "$HOME/.claude/settings.json" \
|
||||||
|
"$HOME/.claude/settings.local.json" ;;
|
||||||
|
codex)
|
||||||
|
printf '%s\n' "${CODEX_HOME:-$HOME/.codex}" ;;
|
||||||
|
copilot)
|
||||||
|
printf '%s\n' "$HOME/.config/copilot" ;;
|
||||||
|
antigravity)
|
||||||
|
printf '%s\n' "$HOME/.antigravity" "$HOME/.config/antigravity" ;;
|
||||||
|
kiro)
|
||||||
|
printf '%s\n' "$HOME/.kiro" "./.kiro" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
checked=0
|
||||||
|
hit=0
|
||||||
|
for cli in $CLIS; do
|
||||||
|
for p in $(cli_paths "$cli"); do
|
||||||
|
[ -e "$p" ] || continue
|
||||||
|
checked=$((checked + 1))
|
||||||
|
echo "檢查 $cli:$p" >&2
|
||||||
|
out=$(grep -rnI --exclude-dir=.git -e "$SKILL" -e "/jsc-${DOMAIN}:${SKILL}" "$p" 2>/dev/null)
|
||||||
|
if [ -n "$out" ]; then
|
||||||
|
printf '%s\n' "$out"
|
||||||
|
hit=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$checked" -gt 0 ] || { echo "偵測到 CLI,但沒有任何設定位置存在,無處可查" >&2; exit 3; }
|
||||||
|
[ "$hit" -eq 0 ] && echo "無殘留:已檢查 $checked 個位置" >&2
|
||||||
|
exit $hit
|
||||||
Reference in New Issue
Block a user