Author SHA1 Message Date
admin 06979128d9 Merge pull request '技能準則新增目錄頁條列與內容頁圖表優先的區分,兩個目錄頁範本與六支技能同步' (#67) from feat/contents-list/main into develop
Reviewed-on: #67
2026-09-02 10:00:50 +00:00
jiantw83 fb72d564d1 chore(manifest): 三份 plugin 資訊檔的版本號提升一階
What
- `plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 的版本號同步提升一個修訂號。

Why
- 本輪改了範本、準則與六支技能的內文,安裝端要靠版本號才判得出手上的快取是舊的。
- 三份資訊檔的版本號必須一致,任一份沒跟上,不同 CLI 會各自認到不同版本。

How
- 三份檔案只動版本號那一個欄位,其餘內容不變。

Who
- 各支 CLI 的安裝與更新流程,以及版本守門檢查。
2026-09-02 17:21:02 +08:00
jiantw83 15fdb7e140 docs(skills): 五支技能與盤點技能的目錄頁呼叫敘述同步條列版面
What
- `skills/skill-new`、`skills/skill-update`、`skills/skill-delete`、`skills/skillset-update`、`skills/skill-check`:目錄頁寫入步驟的呼叫從「單列 upsert」改成單一 H2 區塊 upsert,鍵補上內容頁頁名這個引數,並註明第四個引數是區塊檔而不是列檔。
- `skills/tooling-guide`:盤點結果寫回目錄頁的敘述照同一套改寫,並寫明鍵是內容頁頁名。
- `references/behaviors.md`:六支技能的關鍵步驟、外部呼叫與可驗證跡象三列同步,跡象從「留下自己那一列」改成留下自己那一個 H2 區塊,區塊內的連結寫成一條欄位。

Why
- 範本與準則已經改成條列版面,技能內文還寫著「那一列」,執行時就會照舊敘述組出表格列,跟工具的區塊 upsert 對不上。
- 呼叫少帶鍵這個引數,工具無從判斷要換掉哪一個區塊,同一筆會被當成新的附加上去。
- 行為清單是稽核與驗證的比對基準,敘述沒跟上,稽核會拿舊描述判合規。

How
- 六支技能的呼叫一律寫成 `wiki-contents.sh upsert {TYPE} {鍵欄} "{內容頁頁名}" {區塊檔} [{範本}]`,並在旁邊點明目錄頁一律大標題加條列。
- 完成條件與可驗證跡象改用區塊的說法,連結範例改成 `- {欄位名}:[{頁名}]({連結})` 的形態。
- 只改敘述,不動任何腳本;轉檔與 upsert 的實作在別的存取庫。

Who
- 本存取庫六支會寫目錄頁的技能。
- 稽核與驗證流程改拿新的行為清單比對。
2026-09-02 17:21:02 +08:00
jiantw83 5541a41ff7 docs(guidelines): 新增目錄頁條列與內容頁圖表優先的區分準則
What
- 「目錄頁專用存取庫」從四條規則擴成五條,新增的第 5 條把版面判準定下來:目錄頁一律大標題加條列,內容頁才維持圖表優先。
- 第 5 條裡另立一段講 `<key-col>` 怎麼決定:那是舊表格裡持有內容頁連結那一欄的序號,只在自動轉檔時用得到,序號一律照線上那一頁實際的欄位排法填。
- 目錄頁與內容頁的對照表多一列「版面」。
- 稽核檢查清單新增兩項,一項查目錄頁版面與範本是否照第 5 條,一項查 `<key-col>` 的填法。
- `MAINTAIN` 沒有內容頁那一段,說法從「寫在表格裡」改成「寫在條列區塊上,一個專案一個 H2 區塊」。
- 跨存取庫沒有原子性那一段,回報對象從「未寫入的目錄列」改成「沒寫進去的目錄頁區塊」。

Why
- 這條區分是整組技能之後寫 wiki 的判準。準則裡沒有正本,每支技能各自解讀,改完的範本過一輪又會長回表格。
- `<key-col>` 填錯的後果是靜默的:標題會轉成那一欄的純文字、跟鍵對不上,既有那一筆被當成新的附加到頁尾,同一筆變成兩個區塊,舊區塊從此再也更新不到,而且不會有任何錯誤訊息。這一輪稽核就抓到六個頁型填錯。
- 範本的欄位順序與線上那一頁常常不一樣,而自動轉檔跑的是線上那一頁,所以序號不能照範本推。
- 檢查清單沒有對應項,這條準則就只剩內文約束,沒有稽核時的把關。

How
- 第 5 條寫明目錄頁的三段版面、H2 標題就是鍵且寫成內容頁頁名、欄位格式 `- {欄位名}:{值}`、頁上不留 markdown 表格也不放 mermaid,舊表格頁由工具讀到就自動轉寫回、不另跑批次搬移也不得手工搬。
- `<key-col>` 那一段要求先把線上那一頁讀回來確認連結落在第幾欄再填,並寫明填錯的靜默後果;線上是空頁、沒有舊表格要轉時照範本填即可。
- 補上「為什麼分兩種」的理由:目錄頁是索引,只給人挑一筆點進去,條列式壞也只壞一塊;內容頁一頁講一件事的全貌,流程與比較拿圖表最省讀者的力氣。

Who
- 全部十四種頁型的目錄頁與寫這些頁的每支技能都受這條準則約束。
- 稽核技能多兩項要判的檢查項。
2026-09-02 17:21:02 +08:00
jiantw83 5db1d8a608 docs(templates): 兩個目錄頁範本改成大標題加條列
What
- `templates/skillset-contents.md`:`SKILLSET_CONTENTS` 的版面從 markdown 表格改成一筆一個 H2 區塊,標題寫成該筆對應內容頁的實際頁名,欄位改成標題底下的一層條列。
- `templates/tooling-contents.md`:同樣轉條列,原本說明用的 `## ` 區段併進 `>` 引言,正式頁上才不會被讀成一筆假紀錄。
- `templates/skillset-page.md`、`templates/tooling-page.md`:引言補上目錄頁與內容頁的版面差別,內容頁本身維持圖表優先、不改版面。
- `README.md`:兩份目錄頁範本的說明同步改寫,兩份 `TOOLING` 範本語意相反那一段補上「版面也相反」。

Why
- 目錄頁是全部使用者共用的索引。表格一寬就得橫向捲、欄位一多就對不上表頭,而且併行寫入時只要有人少打一根豎線,整張表就散掉,別人那一筆跟著看不見。
- 條列式一筆一個區塊,寫入端只換自己那一塊,壞掉也只壞自己那一塊。
- 三個範本原本除了示範區塊之外還留著說明用的 `## ` 區段,轉條列後那種區段會在正式頁上被當成一筆紀錄讀進去。

How
- 一頁固定三段:H1 頁名、`>` 引言、然後每一筆一個 H2 區塊;區塊之間空一行,H2 與第一條之間空一行。
- 欄位在標題底下一行一條,格式 `- {欄位名}:{值}`,全形冒號,順序照原本的欄位從左到右,鍵那一欄照樣留一條。
- 寫入示例改成 `wiki-contents.sh upsert {TYPE} {鍵欄} "{內容頁頁名}" {區塊檔}`,並註明鍵欄是舊表格裡持有內容頁連結那一欄的序號、只供自動轉檔用、要照線上那一頁實際的欄位排法填。
- 頁上不留任何 markdown 表格,也不放 mermaid。

Who
- 影響照這兩個範本寫目錄頁的技能:`skill-new`、`skill-update`、`skill-delete`、`skillset-update`、`skill-check` 與 `tooling-guide`。
- 舊頁的轉檔與單一區塊 upsert 的實作不在本存取庫,本存取庫只提供範本與說明。
2026-09-02 17:21:02 +08:00
admin 6de2aa9321 Merge pull request '收尾寫一筆 skill-end 事件,執行狀態才回報得到助理' (#66) from feat/status-report into develop
Reviewed-on: #66
2026-09-02 08:04:08 +00:00
jiantw83 2fa8d86045 chore(plugin 版本): 三份 manifest 升版至 0.3.0 2026-09-02 16:01:16 +08:00
jiantw83 b87dbb12cd feat(狀態回報): 收尾寫一筆 skill-end 事件
現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就
中止的技能,在紀錄裡長得一模一樣。

start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾
步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在
原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。

status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜
跳過,回報失敗一律不改變技能自己的結論。
2026-09-02 16:01:16 +08:00
admin 03188837d3 Merge pull request '連結一律寫成 [文字](絕對網址),並在寫入前驗證連得到' (#65) from feat/link-verification/main into develop
Reviewed-on: #65
2026-09-02 06:46:18 +00:00
jiantw83 c987d237e4 chore(plugin 版本): 三份 manifest 升版至 0.2.9 2026-09-02 14:27:18 +08:00
jiantw83 651ddb3a6f feat(link): 連結一律寫成 [文字](絕對網址),寫入前先驗證連得到
取消 [[頁名]] 與 [[顯示文字|頁名]] 兩種同 wiki 寫法,不再分「同存取庫」與
「跨存取庫」兩條規則。那種寫法只在自己那個 wiki 內解析,寫錯不報錯,畫面上
看起來像普通文字或死連結,巡不到也修不了。

連結寫進頁面前先過 jsc-gitea 的 link-check.sh,結束碼 0 才寫。驗證一律走 API,
不看網頁狀態碼:私有存取庫的網頁網址對未登入請求一律回 404,拿狀態碼判會把
好連結判成壞的。認證失敗回 7,與死連結的 1 分開,免得金鑰一過期就把還在的頁
整批判死。
2026-09-02 14:27:18 +08:00
admin 3c9137a81d Merge pull request 'feat(wiki): 目錄頁專用存取庫入準則,skill-check 加入優化建議流程' (#63) from feat/wiki-contents-repo/main into develop
Reviewed-on: #63
2026-09-02 03:28:05 +00:00
jiantw83 f13724cb79 feat(wiki): 目錄頁專用存取庫入準則,skill-check 加入優化建議流程
What:準則的環境變數表與命名總表加入 JSC_WIKI_REPO_CONTENTS 與目錄頁專用存取庫
一節,HASH 規則改為完整 40 碼。skill-check 的 Group 3 先讀上一輪決議,建議表加上
決議與決議日期兩欄,新增步驟 8 把稽核結果寫進 SKILLSET 頁。新增 check-page-name.sh
與兩份 SKILLSET 範本。

Why:優化建議原本每輪產出後就散掉,決議為延後的項目下一輪會重新掃、重新問一次,
正是 skill-check 自己第三個面向點名的毛病。SKILLSET_CONTENTS 是 14 個目錄頁裡
唯一沒有範本的,四支技能都被要求寫它,卻沒有欄位定義可套。

How:Group 1 補進三支現成但沒人呼叫的檢查腳本——ste100-lint.sh、check-wiki-rules.sh
與新增的 check-page-name.sh。讀不到上一輪決議時只停掉 Group 3,不再中止整輪:那兩組
完全不碰 wiki,金鑰失效就會鎖死整組技能唯一的稽核路徑。另外四支 meta 技能原本把目錄頁
寫進 SKILLSET 存取庫,一併改走 CONTENTS。
2026-09-02 11:02:48 +08:00
admin e8539ecfb2 Merge pull request '釋出 jsc-meta 0.2.7:助理運行閘門的準則' (#61) from docs/assistant-gate/main into develop
Reviewed-on: #61
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-01 07:51:35 +00:00
admin bb26c73504 Merge pull request 'docs/assistant-gate/guideline-section' (#60) from docs/assistant-gate/guideline-section into docs/assistant-gate/main
Reviewed-on: #60
2026-09-01 07:41:28 +00:00
jiantw83 7b6b9076ea docs(guidelines): 補上助理運行閘門與 fail-closed 閘門的專屬規則
What:
- 新增「助理運行閘門」一節:規格表、心跳判定的六碼處置、豁免清單十一支。
- 節內另立「fail-closed 閘門的專屬規則」四條,那是準則現在完全沒有的東西。
- 環境變數表補上閘門開關與心跳門檻兩列。
- 三份 manifest 的版本一起提升。

Why:
- 整組 hook 的通則是資料不足就放行,這一道相反。例外不點名,後來的人會以為可以隨便再開一道 fail-closed 的閘門,而那種閘門開錯就是整組技能鎖死。
- 豁免清單與腳本檔頭是同一件事實。準則沒有那張表,兩邊就會各走各的,改一支忘了另一支。

How:
- 四條專屬規則裡有兩條是這一輪實作時才想清楚的。逃生門的判斷要擺在載入共用函式庫之前——函式庫讀不到時 sh 會就地結束並回擋人的那個碼,逃生門也跟著跑不到,人就繞不過去;這一條只對 fail-closed 成立。豁免清單只收解鎖路徑,方向與重啟閘門相反,那一道解鎖靠閘門外的動作,這一道解鎖靠跑一支技能。
- 「清單認技能名不認呼叫鏈」那一條補了一個更狠的實例:巡檢要先把結果寫上監控頁才寫心跳,只豁免助理自己會做出自咬環。所以新增豁免技能時不只要想它會呼叫誰,還要想那條呼叫鏈上有沒有一步是解鎖條件本身的前置。
- 心跳判定回「檔案系統問不出來」時放行不擋,理由與代價都寫進去了。那一碼與「時間戳壞掉」的差別在有沒有出路。

Who:
助理閘門實作完之後,把當中的判斷收進準則,讓下一道同類閘門有依據。
2026-09-01 15:27:06 +08:00
admin 3beca714fc Merge pull request '放行 jsc-assist 的 marketplace 條目到預設分支' (#58) from chore/marketplace-assist-registry/main into develop
Reviewed-on: #58
2026-09-01 04:55:27 +00:00
admin a4df38d974 Merge pull request 'chore/marketplace-assist-registry/sync-copies' (#57) from chore/marketplace-assist-registry/sync-copies into chore/marketplace-assist-registry/main
Reviewed-on: #57
2026-09-01 04:53:43 +00:00
jiantw83 06f530d589 chore(marketplace): 把 jsc-assist 登錄進統一 marketplace
What:
- 兩份 marketplace 檔各加一個 jsc-assist 條目,來源網址指向 assist 存放庫。

Why:
- 準則要求每個 domain 存放庫都帶同一份 marketplace 檔,任何一個存放庫都能當註冊入口。副本之間只要有一份沒跟上,稽核就會報出不一致。
- 正本少了這個條目,各 CLI 的安裝指令就找不到 jsc-assist,這個 domain 等於發佈不出去。

How:
- 條目由 meta 的 sync-marketplace.sh 產生,同時寫進正本與每個 domain 存放庫的副本,寫完逐檔比對位元組。這一支存放庫的兩份副本就是那一輪的產物。
- 條目依名稱排序,縮排與非 ASCII 描述的處理都交給同一支腳本,不手改 JSON。
- 這一批是從最新的預設分支重新產生的。前一輪的分支基底早於監控頁型別那批改動,直接合併會把那些改動回退掉,所以整批重做而不是解衝突。

Who:
助理 domain 落地的註冊步驟在這個存放庫的同步。
2026-09-01 12:50:04 +08:00
admin e3054961ec Merge pull request '釋出 jsc-meta 0.2.6:命名總表新增 MONITOR' (#55) from feat/monitor-wiki-page-type/main into develop
Reviewed-on: #55
2026-09-01 04:42:55 +00:00
admin 9c7e192f33 Merge pull request 'feat/monitor-wiki-page-type/naming-table' (#54) from feat/monitor-wiki-page-type/naming-table into feat/monitor-wiki-page-type/main
Reviewed-on: #54
2026-09-01 04:28:06 +00:00
jiantw83 970b7a4f71 docs(guidelines): 命名總表加入 MONITOR 頁型別
What:
- Wiki 頁命名總表新增 MONITOR 一列,擁有者是技能助理。
- 表下補三段說明:雜湊來源、為什麼不帶工具名稱、寫入語意。
- CHECK 那一段拿掉「是唯一例外」的斷言,改成指向新的機器層規則。
- 三份 manifest 的版本一起提升。

Why:
- 總表是所有 wiki 頁命名的唯一來源。新型別沒登錄進來,各技能就沒有依據,只能各自猜。
- CHECK 原本寫著「是唯一例外」,指的是雜湊來源取主機名與帳號而不是存放庫。加了第二個同樣取法的型別之後,這句話就不成立了,留著會讓讀的人以為只有一個。

How:
- MONITOR 的雜湊來源比照 CHECK,取主機名與登入帳號。助理巡檢的是一台機器,不是一個存放庫。
- 刻意不帶工具名稱,這一點與 TOOLING 相反。TOOLING 一支 CLI 一頁,因為每支 CLI 各有自己的已安裝 plugin 與 hook 接線;助理看的是整台機器一份心跳、一本待辦簿,不分 CLI。
- 監控頁一律附加,不覆寫。助理的寫入是背景行為,覆寫錯了沒人在現場。TOOLING 內容頁是每次盤點覆寫整頁,兩者語意相反,所以各自寫明白。
- 只動總表那一節,其餘章節不碰。

Who:
技能助理落地帶出來的頁型別需求,四個存放庫同一批改。
2026-09-01 12:10:55 +08:00
jiantw83 06fe739070 Merge pull request '收攏 frontmatter 檢查與五支 CLI 的 hook 接線準則改寫' (#51) from feat/cli-hook-rewire/main into develop 2026-09-01 00:58:42 +00:00
22 changed files with 1012 additions and 116 deletions
+8
View File
@@ -13,6 +13,14 @@
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-assist",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
},
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
+8
View File
@@ -13,6 +13,14 @@
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-assist",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
},
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-meta",
"version": "0.2.5",
"version": "0.3.1",
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
"skills": "./skills",
"author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-meta",
"version": "0.2.5",
"version": "0.3.1",
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
"skills": "./skills",
"jsc": {
+8 -4
View File
@@ -42,7 +42,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `skill-check`
例行稽核——沒有變更需求時,同步存取庫之後併行跑三組:`lint-scripts.sh` 加 `lint-frontmatter.sh` 加 `check-behaviors.sh` 加 hook smoke、準則審核檢查清單、流程與成本優化審查。優化面向包含可平行化、可下放工具、重複來回、冗餘步驟、過早或過晚的閘門與可省的成本;不符項目與優化建議分開回報,逐項決策樹確認後才套用,最後逐 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`
@@ -63,13 +63,17 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `references/pr-report.md` | PR 收尾回報格式唯一來源,所有會開 PR 的技能都指向這裡 |
| `references/deploy-verify.md` | 四支異動技能共用的部署與驗證流程:判路線、部署或工作樹、**在新的 CLI 行程裡驗證**、失敗分流 |
| `references/behaviors.md` | 本 domain 的技能行為清單:一支技能一節,五列記下觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,供稽核與驗證比對。格式合約見 `references/guidelines.md` 的「技能行為清單」 |
| `templates/tooling-contents.md` | `TOOLING_CONTENTS` 目錄頁樣板。一列代表一組「機器、CLI、帳號」;只更新自己那一列,別人的列原樣保留,**禁止整頁覆蓋** |
| `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/`:節對技能、字典序、每節一張表、五個欄位齊全且內容欄非空;不符 exit 1,用法錯誤 exit 2,找不到清單或找不到技能 exit 3(**不等於通過**) |
| `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`;不在正本清單上的存取庫不列 |
@@ -79,7 +83,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `tools/find-skill-refs.sh` | 盤點一個技能在正本 marketplace 各 domain 存取庫裡的引用檔案(不掃非技能組存取庫與點開頭目錄);技能名稱為純子字串比對,命中要逐檔確認;零命中 exit 1,掃描失敗 exit 3 |
| `tools/verify-skill-removed.sh` | 刪除技能後實地檢查各 CLI 的技能快取與 hook 設定有無殘留;有殘留 exit 1,沒偵測到 CLI 或沒有可查位置 exit 3(**不等於乾淨**) |
兩份 `TOOLING` 樣板的寫入語意剛好相反,套用前先分清楚。目錄頁是共用的,整頁覆蓋會刪掉別台機器的紀錄,所以只准動自己那一列。內容頁只屬於一組「機器、CLI、帳號」,記的是當下現況,舊的安裝內容早就不成立,所以整頁覆寫。頁名與雜湊規則見 `references/guidelines.md` 的「Wiki 頁命名總表」。
兩份 `TOOLING` 樣板的寫入語意與版面都剛好相反,套用前先分清楚。目錄頁是共用的,整頁覆蓋會刪掉別台機器的紀錄,所以只准動自己那一個 H2 區塊,版面一律大標題加條列,頁上不留 markdown 表格。內容頁只屬於一組「機器、CLI、帳號」,記的是當下現況,舊的安裝內容早就不成立,所以整頁覆寫,版面維持圖表優先。頁名、雜湊與這條版面區分見 `references/guidelines.md` 的「Wiki 頁命名總表」。
## 相關 domain
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-meta",
"version": "0.2.5",
"version": "0.3.1",
"description": "技能組自我管理:新建、更新、刪除技能與技能準則",
"skills": "./skills/",
"jsc": {
+28 -28
View File
@@ -7,67 +7,67 @@
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 手上沒有異動需求,要對整組技能做例行或臨時稽核時用。帶著異動需求要改多支技能走 skillset-update、只改一支走 skill-update |
| 關鍵步驟 | 先跑 sync-domains.sh 同步全部 domain 存取庫、再平行跑三組審查(第一組平行跑腳本檢查、frontmatter 檢查、行為清單檢查與 hook smoke,第二組以 sub agent 逐 domain 對 guidelines 檢查清單稽核,第三組以 sub agent 分六個面向審查流程與成本)、合併三組結果並用決策樹逐項確認(第二組留白的五項由第一組的結論補上)、以平行 sub agent 套用確認過的修正並跑 sync-skill-manifest.sh、跑 sync-marketplace.sh 同步兩份正本 marketplace、重跑三組驗證直到接受的修正全通過、每個受影響存取庫各開一條 PR |
| 外部呼叫 | tools/sync-domains.sh、tools/lint-scripts.sh、tools/lint-frontmatter.sh、tools/check-behaviors.sh、tools/sync-skill-manifest.sh、tools/sync-marketplace.sh、jsc-cli/tools/detect-clis.sh、jsc-hooks/tools/wire-cli.sh smoke、jsc-ask:ask、jsc-git:pr |
| 完成條件 | 每個 domain 都有腳本檢查、frontmatter 檢查與行為清單檢查的結論(frontmatter 檢查退出 3 是「什麼都沒掃」,不算通過)、每個 domain 的檢查清單在合併後補齊、每項不合規與每項優化建議都有決策紀錄、接受的修正重驗通過、每個受影響存取庫都拿到 PR 網址 |
| 可驗證跡象 | 受影響存取庫留下檔案改動、改到行為的技能連帶改寫該存取庫的 references/behaviors.md、每個 domain 的 lint-frontmatter.sh 退出 0、README 的「Skills 目錄」重寫、三份 manifest 版本號提升、兩份 marketplace 檔逐位元一致、每個受影響存取庫一條 PR |
| 關鍵步驟 | 先跑 sync-domains.sh 同步全部 domain 存取庫、再平行跑三組審查(第一組平行跑腳本檢查、frontmatter 檢查、行為清單檢查、語言檢查、連結寫法檢查、wiki 規則檢查、頁名樣式檢查與 hook smoke,第二組以 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、跑 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-page-name.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 是「什麼都沒查」,都不算通過)、每個 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-page-name.sh 退出 0、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 逐檔修正到檢查清單全過、刪掉 skills/{name}/ 目錄、移除 references/behaviors.md 對應那一節、跑 sync-skill-manifest.sh、開 PR、依 deploy-verify.md 部署、用新的 CLI 行程驗證、跑 verify-skill-removed.sh 查磁碟殘留、把異動報告附加到 wiki |
| 外部呼叫 | tools/sync-domains.sh、tools/list-skills.sh、tools/find-skill-refs.sh、tools/check-behaviors.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、tools/verify-skill-removed.sh、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki |
| 完成條件 | 盤點清單每一檔都有「已修正」或「無需修正」的結論、技能目錄與行為清單那一節都不存在、list-skills.sh 查不到那一列、殘留檢查退出 0 或據實記成「無處可查」並帶進報告、PR 網址與 wiki 頁都到手 |
| 可驗證跡象 | skills/{name}/ 目錄消失、references/behaviors.md 少一節、README 與三份 manifest 更新、一條 PR、wiki SKILLSET_{HASH} 附加一節並登記在 SKILLSET_CONTENTS |
| 關鍵步驟 | 跑 sync-domains.sh 同步、跑 list-skills.sh 列出全部技能、讓使用者挑一支確認刪除、跑 find-skill-refs.sh 盤點所有引用檔案、以平行 sub agent 逐檔修正到檢查清單全過、刪掉 skills/{name}/ 目錄、移除 references/behaviors.md 對應那一節、跑 sync-skill-manifest.sh、開 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/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 |
| 完成條件 | 盤點清單每一檔都有「已修正」或「無需修正」的結論、技能目錄與行為清單那一節都不存在、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 少一節、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、domain 未註冊就先確認存取庫在不在、依 template 結構補齊內容再跑 sync-marketplace.sh 註冊、以 sub agent 產生 skills/{name}/SKILL.md、在 references/behaviors.md 依字典序插入該技能一節、跑 sync-skill-manifest.sh、自查 guidelines 檢查清單並跑 check-behaviors.sh、開 PR、依 deploy-verify.md 部署並用新的 CLI 行程驗證、把異動報告附加到 wiki |
| 外部呼叫 | tools/list-skills.sh、tools/sync-domains.sh、tools/sync-marketplace.sh、tools/check-behaviors.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、jsc-gitea/tools/gitea.sh、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki |
| 完成條件 | 四項提問都有紀錄、SKILL.md 與行為清單那一節都在、README 與三份 manifest 同步、檢查清單全過且 check-behaviors.sh 退出 0、PR 網址到手、deploy-verify.md 第 1 到第 5 節的完成條件全數成立、wiki 頁寫成功 |
| 可驗證跡象 | 新增 skills/{name}/SKILL.md、references/behaviors.md 多一節、README 與三份 manifest 更新、新 domain 時兩份 marketplace 檔多一筆 plugin 條目並同步到每個 domain 存取庫、一條 PR、wiki SKILLSET_{HASH} 附加一節 |
| 關鍵步驟 | 平行跑 list-skills.sh 與 sync-domains.sh 預取技能清單與 domain 清單、用決策樹問出目標、觸發時機、輸入輸出與所屬 domain、domain 未註冊就先確認存取庫在不在、依 template 結構補齊內容再跑 sync-marketplace.sh 註冊、以 sub agent 產生 skills/{name}/SKILL.md、在 references/behaviors.md 依字典序插入該技能一節、跑 sync-skill-manifest.sh、自查 guidelines 檢查清單並跑 check-behaviors.sh、開 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/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 |
| 完成條件 | 四項提問都有紀錄、SKILL.md 與行為清單那一節都在、README 與三份 manifest 同步、檢查清單全過且 check-behaviors.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 多一節、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 列出全部技能、讓使用者挑一支、用決策樹問出改動細節、以 sub agent 改 SKILL.md 與相關檔案、同步更新 references/behaviors.md 該技能那一節、跑 sync-skill-manifest.sh、對 guidelines 檢查清單逐項自查並跑 check-behaviors.sh、開 PR、依 deploy-verify.md 部署並用新的 CLI 行程驗證、把異動報告附加到 wiki |
| 外部呼叫 | tools/sync-domains.sh、tools/list-skills.sh、tools/check-behaviors.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki |
| 完成條件 | 每個提問都有紀錄、技能檔案帶著改動、行為清單那一節與新行為一致且 check-behaviors.sh 退出 0、三份 manifest 同版、檢查清單全過、PR 網址到手、deploy-verify.md 第 1 到第 5 節的完成條件全數成立、wiki 頁寫成功 |
| 可驗證跡象 | 該技能的 SKILL.md 與相關檔案改動、references/behaviors.md 對應節改寫、README 與三份 manifest 更新、一條 PR、wiki SKILLSET_{HASH} 附加一節 |
| 關鍵步驟 | 跑 sync-domains.sh 同步、跑 list-skills.sh 列出全部技能、讓使用者挑一支、用決策樹問出改動細節、以 sub agent 改 SKILL.md 與相關檔案、同步更新 references/behaviors.md 該技能那一節、跑 sync-skill-manifest.sh、對 guidelines 檢查清單逐項自查並跑 check-behaviors.sh、開 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/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、三份 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 對應節改寫、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、逐存取庫跑 sync-skill-manifest.sh、以平行 sub agent 重跑 guidelines 檢查清單與 check-behaviors.sh 直到全過、每個受影響存取庫各開一條 PR、依 deploy-verify.md 部署並用新的 CLI 行程驗證、逐存取庫把異動報告附加到 wiki |
| 外部呼叫 | tools/sync-domains.sh、tools/check-behaviors.sh、tools/sync-skill-manifest.sh、tools/deploy-route.sh、tools/list-skills.sh、jsc-ask:ask、jsc-git:pr、jsc-gitea:wiki |
| 完成條件 | 受影響技能清單與三項塑形檢查都跟使用者談定、每個受影響存取庫都帶著改動、README 同步與 manifest 提升、每支動過的技能檢查清單全過且該 domain 的 check-behaviors.sh 退出 0、每個受影響存取庫都有 PR 網址、deploy-verify.md 第 1 到第 5 節對每個存取庫都成立、每個存取庫的 wiki 頁都寫成功 |
| 可驗證跡象 | 每個受影響存取庫的技能檔案改動、各自的 references/behaviors.md 更新、README 與三份 manifest 更新、每個存取庫一條 PR、每個存取庫的 wiki SKILLSET_{HASH} 各附加一節並登記在 SKILLSET_CONTENTS |
| 關鍵步驟 | 平行啟動 sync-domains.sh 與異動細節決策樹、問清楚改哪一條規則、影響哪些技能與 domain,並補問工具化、sub agent、環境變數三項塑形檢查、以每個 domain 一個 sub agent 平行套用改動、同步更新每個受影響 domain 的 references/behaviors.md、逐存取庫跑 sync-skill-manifest.sh、以平行 sub agent 重跑 guidelines 檢查清單與 check-behaviors.sh 直到全過、每個受影響存取庫各開一條 PR、依 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/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、每個受影響存取庫都有 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 更新、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 |
| 外部呼叫 | 上游 speak-human-tw 的 raw SKILL.md 與 git clone、tools/ste100-lint.sh、tools/sync-domains.sh、tools/sync-skill-manifest.sh、jsc-ask:ask、jsc-git:pr |
| 完成條件 | 上游同版時停在版本比對並回報;有新版時每項蒸餾出來的變更都有決策、`sh -n tools/ste100-lint.sh` 通過且新採用的詞彙在測試字串上命中、本存取庫 lint 退出 0、其他存取庫的命中附 file:line 交給擁有者、三份 manifest 同版、PR 網址到手 |
| 可驗證跡象 | references/ste100.md 的「上游版本」行換值、tools/ste100-lint.sh 的樣式更新、jsc-hooks/hooks/simplified.txt 更新、三份 manifest 版本號提升、一條 PR;上游同版時無寫入跡象,只有回報內容 |
| 關鍵步驟 | 先讀 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 算出頁名、先寫內容頁再登記目錄頁 |
| 外部呼叫 | tools/plugins-root.sh、tools/sync-domains.sh、tools/inventory-tooling.sh、jsc-gitea/tools/hash-id、jsc-gitea:wiki、jsc-ask:ask |
| 完成條件 | 每項事實都指得到來源檔案或工具輸出、必填章節都不是空的、收尾回報寫明交付目標、來源新鮮度、過期輸入與未知的 hook 判定;目標是 wiki 時每一頁都確認寫成功,或列為未寫入並附完整內容 |
| 可驗證跡象 | 目標是聊天回應時無寫入跡象,只有回報內容;目標是檔案時只產生使用者指定的那一個檔;目標是 wiki 時每支偵測到的 CLI 各一頁 TOOLING_{HASH},並在 TOOLING_CONTENTS 更新自己那一列 |
| 關鍵步驟 | 跑 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 的區塊原樣保留 |
+211 -23
View File
@@ -114,46 +114,91 @@ PR 開立、更新、留言修正的收尾回報格式只看 [`references/pr-rep
| 節 | 每支技能一個 `## {技能名}` 節,名稱與 `skills/` 底下的目錄名逐字相同,節數與技能支數一樣,排列照目錄名的字典序 |
| 表格 | 每節恰好一張表,表頭兩欄依序是「項目」與「內容」,五列依序為 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,每一列的「內容」欄都不得空白 |
| 寫什麼 | 寫技能實際的行為:什麼情況會用、什麼情況不該用、依序做了哪些事、呼叫哪些腳本與技能、做到什麼程度算跑完、跑完在環境裡留下哪些查得到的跡象。不要抄 `description` 的行銷語 |
| 純唯讀的技能 | 「可驗證跡象」欄寫「無寫入跡象,只有回報內容」,不得留白 |
| 純唯讀的技能 | 「可驗證跡象」欄寫「除了收尾的 `skill-end` 事件以外沒有寫入跡象,只有回報內容」,不得留白。收尾事件是每支技能都有的那一筆,唯讀技能也不例外 |
| 更新時機 | 技能異動時在**同一個 PR 內**一起更新:新增技能就加一節、刪除就移除該節、改行為就改該節 |
| 收尾事件 | 「可驗證跡象」那一列要寫到收尾的 `skill-end` 事件或 `events.jsonl`,規則見「執行狀態回報」一節 |
| 檢查腳本 | `jsc-meta/tools/check-behaviors.sh {domain-path}` |
`check-behaviors.sh` 的結束碼分流:
| 結束碼 | 意義 |
| --- | --- |
| 0 | 行為清單與 `skills/` 相符,五個欄位齊全且內容欄非空 |
| 1 | 不符:缺節、多節、順序不對、表格不對、缺欄位或欄位空白,逐項印在 stderr,照著修再重跑 |
| 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_TOKEN` | Gitea API token | 改用 tea 登入金鑰(`tea login list`);tea 也沒有才詢問使用者 |
| `JSC_WIKI_REPO_QUESTION` | `QUESTION_CONTENTS`、`QUESTION_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_PLAN` | `PLAN_CONTENTS`、`PLAN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_ANALYZE` | `ANALYZE_CONTENTS`、`ANALYZE_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_DELIVER` | `DELIVER_CONTENTS`、`DELIVER_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_MAINTAIN` | `MAINTAIN_CONTENTS` 所在的 `{owner}/{repo}`(本類型只有目錄頁) | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_REPO` | `REPO_CONTENTS`、`REPO_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_LOG` | `LOG_CONTENTS`、`LOG_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_LEARN` | `LEARN_CONTENTS`、`LEARN_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_ERROR` | `ERROR_CONTENTS`、`ERROR_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_CHECK` | `CHECK_CONTENTS`、`CHECK_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_REPORT` | `REPORT_CONTENTS`、`REPORT_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_SKILLSET` | `SKILLSET_CONTENTS`、`SKILLSET_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_TOOLING` | `TOOLING_CONTENTS`、`TOOLING_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_CONTENTS` | **全部** `*_CONTENTS` 目錄頁所在的 `{owner}/{repo}`,十四種型別的目錄頁共用這一組 | 退回 `JSC_WIKI_REPO`;再沒有就 exit 3。**刻意不退回型別變數** |
| `JSC_WIKI_REPO_QUESTION` | `QUESTION_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_PLAN` | `PLAN_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_ANALYZE` | `ANALYZE_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_DELIVER` | `DELIVER_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_MAINTAIN` | 保留給 `MAINTAIN` 的內容頁。本類型目前只有目錄頁,目錄頁走 `CONTENTS`,所以這個變數現在解不到任何一頁 | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_REPO` | `REPO_{HASH}` 內容頁所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO` |
| `JSC_WIKI_REPO_LOG` | `LOG_{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_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`。
## 版本前置檢查
@@ -259,6 +304,78 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
**清單認的是技能名,不是呼叫鏈。** 豁免技能轉呼叫的下一層若不在清單上,那一層照樣會被擋。後三支(`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 頁面一律採雙層命名。`MAINTAIN` 是唯一只有目錄頁的類型,理由見表下:
@@ -278,22 +395,74 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
| `REPORT` | `REPORT_CONTENTS` | `REPORT_{HASH}` | 報表目錄、工作報表頁(年、月、週、日各一頁) | jsc-log |
| `SKILLSET` | `SKILLSET_CONTENTS` | `SKILLSET_{HASH}` | 技能組異動目錄、技能組異動報告頁(新增、更新、刪除、批次更新之後的驗證結果與改動清單) | jsc-meta |
| `TOOLING` | `TOOLING_CONTENTS` | `TOOLING_{HASH}` | 技能盤點目錄、單機單 CLI 的技能盤點頁:一台機器上某一支 CLI 的已安裝 plugin 與版本、可用技能、hook 接線狀態 | jsc-meta |
| `MONITOR` | `MONITOR_CONTENTS` | `MONITOR_{HASH}` | 助理巡檢的監控頁。技能與 hook 每跑一次就留下事件,助理把事件收攏、判斷健康狀態、寫進這裡 | jsc-assist |
`MAINTAIN` 沒有內容頁。維護登記全部寫在 `MAINTAIN_CONTENTS` 的表格裡:`jsc-sdlc:implement` 只往那一頁附加登記,`jsc-sdlc:maintain` 只讀那一頁再回寫「前次維護時間」,兩支都沒有產生 `MAINTAIN_{HASH}` 的步驟,`jsc-sdlc/templates/` 也沒有對應範本。總表以前列著這個內容頁,照著找只會找到一個不存在的頁。要補內容頁就先補技能步驟與範本,不能只在總表上寫著。
`MAINTAIN` 沒有內容頁。維護登記全部寫在 `MAINTAIN_CONTENTS` 的條列區塊上,一個專案一個 H2 區塊:`jsc-sdlc:implement` 只往那一頁附加登記,`jsc-sdlc:maintain` 只讀那一頁再回寫「前次維護時間」,兩支都沒有產生 `MAINTAIN_{HASH}` 的步驟,`jsc-sdlc/templates/` 也沒有對應範本。總表以前列著這個內容頁,照著找只會找到一個不存在的頁。要補內容頁就先補技能步驟與範本,不能只在總表上寫著。
`{HASH}` 一律為 `{owner}/{repo}`(必要時加上主題字串)的 SHA-1 前 8 碼,大寫。
若第一碼是 `0-9`、`A`、`B`、`C`,就改成 `H` 加上原 SHA-1 前 7 碼,總長仍維持 8 碼。
同一規則套用到所有目錄頁與內容頁。
### 目錄頁專用存取庫
目錄頁與內容頁分屬不同存取庫,這是刻意的。
| 項目 | 目錄頁 | 內容頁 |
| --- | --- | --- |
| 頁名 | `{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` 是唯一例外:它記的是一台執行環境,不是一個存取庫,所以雜湊來源為 `{主機名}/{登入帳號}`。
8 碼與 `H` 前綴的算法完全相同,由同一支 `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` 其中之一。
@@ -303,6 +472,19 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
少了中間那一段,同一台機器上五支 CLI 會算出同一個雜湊,五份盤點互相覆蓋,最後只剩最後寫入的那一支,讀的人卻看不出被蓋掉。
帶上工具名稱,一支 CLI 就有一頁,換一支 CLI 重跑也不會動到別支的頁。
`MONITOR` 的雜湊來源比照 `CHECK`,取 `{主機名}/{登入帳號}`,`{主機名}` 照上面那條取短主機名。
助理巡檢的是一台機器,不是一個存取庫。
一台機器一頁,換一支 CLI 不另開頁。
算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
`patrol.sh` 與模型填值兩條路徑都要照這一條截取,改動任一邊就回頭核對另一邊。
**為什麼不帶工具名稱。** 這一點與 `TOOLING` 相反。
`TOOLING` 一支 CLI 一頁,因為每支 CLI 各有自己的已安裝 plugin 與 hook 接線。
助理看的是整台機器一份心跳、一本待辦簿,不分 CLI,所以中間那一段不能加。
**監控頁一律附加,不覆寫。** 助理的寫入是背景行為,覆寫錯了沒人在現場。
`TOOLING` 內容頁是每次盤點覆寫整頁,兩者的寫入語意剛好相反,不要混用。
## 審核檢查清單
新增或更新技能後逐項檢查,任一不符就修正:
@@ -313,6 +495,11 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
- [ ] 細節流程已標示 MUST run as a sub agent
- [ ] gitea 操作透過 gitea.sh 或 tea
- [ ] 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 決策樹規則
- [ ] `tools/` 與 `hooks/` 內的 shell 腳本都通過 `sh -n`;技能直接呼叫的腳本都存在、可執行,且退出碼有分流
- [ ] hook 相關變更已用 `jsc-hooks/tools/wire-cli.sh smoke {cli}` 實測;沒有偵測到 CLI 時,至少跑 `smoke codex` 並標明是預設 hook smoke。行數讀腳本自己印的 `lines` 那一行,**技能與 README 都不得寫死數字**——腳本會自我斷言,抄一份數字進文件,加減判定路徑時就漂移,稽核反而被舊數字誤導
@@ -320,6 +507,7 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
- [ ] 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
- [ ] PR 的 base 符合「PR 分支階梯」,沒有越級
+105 -13
View File
@@ -1,6 +1,6 @@
---
name: skill-check
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 hook smoke, the guidelines.md checklist audit, and a review of 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, re-check until accepted fixes pass, then open a PR per affected repo via jsc-git pr. 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).
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 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 compliance, flow efficiency, and cost efficiency
@@ -14,13 +14,18 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
The three review groups of step 2 all read this synced tree, so the sync finishes first.
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.
**Group 1 — validate scripts, frontmatter, behavior lists, and hooks.**
**Group 1 — validate scripts, frontmatter, behavior lists, language, wiki rules, page names, 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 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`.
5. 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.
6. 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.
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. 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`.
8. 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.
9. 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).
@@ -28,9 +33,32 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
- 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.
Five 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, and the `lint-frontmatter.sh` verdict. Tell each sub agent to skip those five and leave them blank; the main agent fills them in from the group 1 verdicts when merging in step 3. 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.
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. Each aspect **MUST run as a sub agent**, and the six aspects run in parallel with each other and with groups 1 and 2:
**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 |
| --- | --- |
@@ -41,14 +69,14 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
| 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, and which protection would be weakened if any. 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.
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 and a `check-behaviors.sh` verdict, 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 five 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, 「無發現」 where an aspect found nothing.
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 five 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.
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 and a `check-link-format.sh` verdict, `check-wiki-rules.sh` and `check-page-name.sh` each have one verdict for the whole run, 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.
- Optimization options: apply / defer / custom. 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.
- 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, and every compliance failure and every optimization finding has a recorded decision.
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. 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, 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.
@@ -57,5 +85,69 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
- 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, 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, every hook smoke exits 0 with the `lines` count the script itself asserted, all checklist items pass, and every accepted optimization has a matching verification result.
6. Re-run the group 1 script, frontmatter, behavior-list, language, link-format, wiki-rule, page-name 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 — `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 — 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. 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, 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 「本輪未取得已決議清單,優化建議暫不提出」, 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.
+56 -1
View File
@@ -34,4 +34,59 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
- 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 wiki page `SKILLSET_{HASH}` — this part MUST run as a sub agent. Call `jsc-gitea:wiki`; `{HASH}` comes from the `{owner}/{repo}` of the domain repo that lost the skill, and the wiki repo resolves through `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. **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 keep every earlier section. Add the page to `SKILLSET_CONTENTS` when it is new. When the write fails — no `{owner}/{repo}` resolves, or `jsc-gitea:wiki` reports an API error — 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: the page holds the new section plus all earlier sections, and `SKILLSET_CONTENTS` links it.
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 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 and its behavior-list section are gone, the PR is open, `deploy-verify.md` sections 1 to 5 hold, `verify-skill-removed.sh` 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 「無處可查」, 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.
+56 -1
View File
@@ -43,4 +43,59 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
5. Call `jsc-git:pr` to open a Push Request. Completion condition: a PR URL comes back and 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 wiki page `SKILLSET_{HASH}` — this part MUST run as a sub agent. Call `jsc-gitea:wiki`; `{HASH}` comes from the `{owner}/{repo}` of the domain repo that gained the skill, and the wiki repo resolves through `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. **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. Add the page to `SKILLSET_CONTENTS` when it is new. When the write fails — no `{owner}/{repo}` resolves, or `jsc-gitea:wiki` reports an API error — 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: the page holds the new section plus all earlier sections, and `SKILLSET_CONTENTS` links it.
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` and its behavior-list section are in place, the checklist passes, the PR 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, 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.
+56 -1
View File
@@ -18,4 +18,59 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
7. Call `jsc-git:pr` to open a Push Request. Completion condition: a PR URL comes back and 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 wiki page `SKILLSET_{HASH}` — this part MUST run as a sub agent. Call `jsc-gitea:wiki`; `{HASH}` comes from the `{owner}/{repo}` of the changed domain repo, and the wiki repo resolves through `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. **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 keep every earlier section. Add the page to `SKILLSET_CONTENTS` when it is new. When the write fails — no `{owner}/{repo}` resolves, or `jsc-gitea:wiki` reports an API error — 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: the page holds the new section plus all earlier sections, and `SKILLSET_CONTENTS` links it.
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 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 and the behavior-list section carry the change, the checklist passes, the PR 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, 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.
+56 -1
View File
@@ -19,4 +19,59 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
4. 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).
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 wiki page `SKILLSET_{HASH}` — this part MUST run as a sub agent, one sub agent per affected domain repo, run in parallel. Call `jsc-gitea:wiki`; `{HASH}` comes from that repo's `{owner}/{repo}`, and the wiki repo resolves through `JSC_WIKI_REPO_SKILLSET` first, then `JSC_WIKI_REPO`. **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 keep every earlier section. Add each page to `SKILLSET_CONTENTS` when it is new. When a write fails — no `{owner}/{repo}` resolves, or `jsc-gitea:wiki` reports an API error — 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 page holds the new section plus all earlier sections, and `SKILLSET_CONTENTS` links them all.
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 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 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, 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.
+21
View File
@@ -27,6 +27,27 @@ Keep `references/ste100.md` in sync with its upstream source, [speak-human-tw](h
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
+40 -10
View File
@@ -18,7 +18,8 @@ Single source of guidelines: [`../../references/guidelines.md`](../../references
- 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`.
- 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.
@@ -85,18 +86,47 @@ Done when the scope and the output target are each written down as one of the va
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 write in this step targets it.
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 contents page for the same reason as every other jsc skill — a contents row 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.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.** Read that page first, then route the read exit code:
- 0 — the page is there. Find the row whose host, tool, and account all match this run, refresh that one row per `templates/tooling-contents.md`, leave every other row exactly as it was, and write the whole page back.
- 4 — the page does not exist yet. **This is the only code that allows creating it.** Build it from the template with this run's rows.
- 7 or 8 — the key was rejected, or the API failed, so the old content is unknown. Stop. Create nothing and overwrite nothing: a page built on top of unknown content deletes rows that nobody can get back. Report the exit code and the page name.
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:
Completion condition: `TOOLING_CONTENTS` holds one row per page written in step 7.3, every row belonging to another machine or CLI is unchanged, or the step stopped with the read exit code and the page name reported.
`jsc-gitea/tools/wiki-contents.sh upsert TOOLING 1 "TOOLING_{HASH}" {entry file} templates/tooling-contents.md`
7.5 **Route a failed write.** Retry the failed `jsc-gitea:wiki` write once. 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.
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
@@ -117,4 +147,4 @@ The guide must include these fields in this order:
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 row registered in `TOOLING_CONTENTS` follows `templates/tooling-contents.md`. Both templates own their own field lists; do not restate them here.
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.
+42
View File
@@ -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}
+43
View File
@@ -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} — {上一次異動的一句話}
(上一次的內容原樣留著,不改、不刪。)
+39 -25
View File
@@ -1,31 +1,45 @@
# 技能盤點目錄
> 由 `jsc-meta:tooling-guide` 維護。這是目錄頁 `TOOLING_CONTENTS`。
> 一列代表一組「機器、CLI、帳號」。同一台機器裝了幾支 CLI,就有幾列。
> 一個區塊代表一組「機器、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` 自動轉成條列後寫回,不要手工搬。
> - 先寫內容頁,成功了才回來更新這個區塊。目錄頁指向一個寫失敗的頁,比缺一筆更難查。
| 盤點頁 | 主機 | 工具 | 帳號 | plugin 數 | 技能數 | hook 接線 | 最後盤點 |
| --- | --- | --- | --- | ---: | ---: | --- | --- |
| [[TOOLING_{HASH}]] | {主機名} | {claude、codex、copilot、antigravity、kiro 五選一} | {登入帳號} | {n} | {n} | {wired、degraded、unwired、unknown 四選一} | {yyyy-MM-dd HH:mm} |
## TOOLING_{HASH}
## 欄位說明
| 欄位 | 內容 |
| --- | --- |
| 盤點頁 | 指向 `TOOLING_{HASH}` 的同 wiki 連結 |
| 主機 | 這次盤點的機器名,與雜湊第一段相同 |
| 工具 | CLI 代號,與雜湊第二段相同 |
| 帳號 | 執行盤點的登入帳號,與雜湊第三段相同 |
| plugin 數 | 該頁「已安裝 plugin」表的列數 |
| 技能數 | 該頁「可用技能」表的列數 |
| hook 接線 | 該頁「hook 接線狀態」對這支 CLI 的判定 |
| 最後盤點 | 該頁盤點時間,與內容頁標頭一致 |
## 寫入規則
- 先整頁讀回來,再比對主機、工具、帳號三欄。
- 三欄都相同就更新那一列,其餘欄位覆寫成本次結果。
- 三欄找不到相同的一列,才新增一列。
- 只動自己那一列,別人的列原樣保留。
- 禁止整頁覆蓋。這一頁是共用目錄,覆蓋等於刪掉別台機器的紀錄。
- 讀不到舊內容就中止,不新增列,也不寫入。
- 盤點頁:[TOOLING_{HASH}]({wiki-url 印出的絕對網址})
- 主機:{主機名}
- 工具:{claude、codex、copilot、antigravity、kiro 五選一}
- 帳號:{登入帳號}
- plugin 數:{n}
- 技能數:{n}
- hook 接線:{wired、degraded、unwired、unknown 四選一}
- 最後盤點:{yyyy-MM-dd HH:mm}
+1 -1
View File
@@ -3,7 +3,7 @@
> 由 `jsc-meta:tooling-guide` 維護。這是盤點頁 `TOOLING_{HASH}`。
> 這頁記的是「現在這台機器上這支 CLI 長什麼樣」。每次盤點覆寫整頁,不保留歷史。
> 覆寫是刻意的:舊的安裝內容與接線狀態早就不成立,留著只會讓人照著過期的事實下判斷。
> 目錄頁 `TOOLING_CONTENTS` 的規則相反,那頁只更新自己那一列,兩者不要混用。
> 目錄頁 `TOOLING_CONTENTS` 的規則相反:那頁是大標題加條列,一筆一個 H2 區塊,每次只更新自己那一個區塊,兩者不要混用。本頁是內容頁,版面維持圖表優先。
> 要看技能組歷次異動請翻 `SKILLSET_{HASH}`,累積紀錄在那一頁。
## 本次盤點
+22 -5
View File
@@ -3,21 +3,30 @@
#
# 用法: check-behaviors.sh <domain-path>
#
# 檢查六項(格式合約見 jsc-meta references/guidelines.md 的「技能行為清單」一節):
# 檢查七項(格式合約見 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
# 結束碼: 0=行為清單與 skills/ 相符,五個欄位齊全、內容欄非空,且每節都寫了收尾事件
# 1=不符:缺節、多節、順序不對、表格不對、缺欄位、欄位空白,或跡象欄沒寫收尾事件,
# 清單在 stderr
# 2=用法錯誤(本腳本只吃一個參數)
# 3=找不到 {domain-path}/references/behaviors.md,或找不到 {domain-path}/skills/,
# 或 skills/ 底下一支 SKILL.md 都沒有——**什麼都沒查**,不等於通過
@@ -155,6 +164,14 @@ while IFS= read -r name; do
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"
@@ -164,7 +181,7 @@ if awk -F"$TAB" '$2 == "-" { found = 1 } END { exit found ? 0 : 1 }' "$TMPD/pars
fi
if [ "$fail" -eq 0 ]; then
echo "行為清單檢查通過:$DOC 對上 $(wc -l < "$TMPD/skills.txt" | tr -d ' ') 支技能,五個欄位齊全" >&2
echo "行為清單檢查通過:$DOC 對上 $(wc -l < "$TMPD/skills.txt" | tr -d ' ') 支技能,五個欄位齊全,跡象欄都寫了收尾事件" >&2
else
echo "行為清單檢查不符:$DOC 與 $SKILLS 對不起來,逐項見上方" >&2
fi
+67
View File
@@ -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
+142
View File
@@ -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