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。
This commit is contained in:
2026-09-02 11:02:48 +08:00
parent e8539ecfb2
commit f13724cb79
14 changed files with 505 additions and 60 deletions
+62 -19
View File
@@ -135,19 +135,21 @@ PR 開立、更新、留言修正的收尾回報格式只看 [`references/pr-rep
| --- | --- | --- |
| `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 |
@@ -155,7 +157,12 @@ PR 開立、更新、留言修正的收尾回報格式只看 [`references/pr-rep
| `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`。
## 版本前置檢查
@@ -356,21 +363,54 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
`MAINTAIN` 沒有內容頁。維護登記全部寫在 `MAINTAIN_CONTENTS` 的表格裡:`jsc-sdlc:implement` 只往那一頁附加登記,`jsc-sdlc:maintain` 只讀那一頁再回寫「前次維護時間」,兩支都沒有產生 `MAINTAIN_{HASH}` 的步驟,`jsc-sdlc/templates/` 也沒有對應範本。總表以前列著這個內容頁,照著找只會找到一個不存在的頁。要補內容頁就先補技能步驟與範本,不能只在總表上寫著。
`{HASH}` 一律為 `{owner}/{repo}`(必要時加上主題字串)的 SHA-1 前 8 碼,大寫。
若第一碼是 `0-9`、`A`、`B`、`C`,就改成 `H` 加上原 SHA-1 前 7 碼,總長仍維持 8 碼。
同一規則套用到所有目錄頁與內容頁。
### 目錄頁專用存取庫
目錄頁與內容頁分屬不同存取庫,這是刻意的。
| 項目 | 目錄頁 | 內容頁 |
| --- | --- | --- |
| 頁名 | `{TYPE}_CONTENTS` | `{TYPE}_{HASH}` |
| 存取庫解析 | 一律解 `CONTENTS`:`JSC_WIKI_REPO_CONTENTS` → `JSC_WIKI_REPO` → exit 3 | 解自己的型別:`JSC_WIKI_REPO_{TYPE}` → `JSC_WIKI_REPO` → exit 3 |
| 帶雜湊 | 否 | 是 |
`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. 目錄頁指向內容頁的連結**一律用絕對網址**(`{GITEA_HOST}/{owner}/{repo}/wiki/{頁名}`),不用 `[[頁名]]` 這種同 wiki 連結。兩頁跨存取庫,同 wiki 連結會指到目錄頁自己那個存取庫裡不存在的頁,點下去是 404,而且看起來像頁沒寫成功。
**為什麼要分開。** 目錄頁是全部使用者共用的索引,內容頁按專案或機器分散在各自的存取庫。混在一起的話,換一個專案就換一份索引,「這台機器有哪些頁」永遠問不到完整答案。索引集中一處、內容各自落地,才查得到全貌。
代價寫明:跨存取庫沒有原子性。內容頁寫成功、目錄頁寫失敗時,據實回報未寫入的目錄列與完整內容,不得反過來先寫目錄頁。
`{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` 產生。
算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
在沒有存取庫的目錄也跑得出體檢,是這個例外存在的原因。
機器層的雜湊來源不只這一個,`MONITOR` 也照同一組取值,規則見下。
**`{主機名}` 一律取短主機名,不含網域。** `CHECK`、`TOOLING`、`MONITOR` 三種機器層頁面共用這一條。
取值方式固定為:`hostname` 的輸出取第一個點以前的那一段,全部轉小寫;取不到就退回 `uname -n` 再做同樣的截取。
理由是**同一台機器只能算出同一個雜湊**。這幾張頁的來源不只一處:`CHECK` 由模型自己填,`MONITOR` 由 `jsc-assist/tools/patrol.sh` 用程式算。
同一台機器上,程式拿到 FQDN(`web01.jsc.idv.tw`)、模型填短主機名(`web01`),兩邊就各開一張頁,各寫各的,兩張都寫得成功,也都看不出被分裂。
截到第一個點以前,兩條路徑才收斂到同一個值。
`TOOLING` 記的也是機器層事實,雜湊來源再多一段:`{主機名}/{工具名稱}/{登入帳號}`。
`{工具名稱}` 是 CLI 代號,取自 `jsc-cli/tools/detect-clis.sh` 輸出的第一欄,值為 `claude`、`codex`、`copilot`、`antigravity`、`kiro` 其中之一。
一台機器、一支 CLI、一個帳號各一頁;算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
@@ -379,10 +419,11 @@ kiro 是唯一真的擋不了的,verdict 據實寫 `degraded`,不寫 `wired`
少了中間那一段,同一台機器上五支 CLI 會算出同一個雜湊,五份盤點互相覆蓋,最後只剩最後寫入的那一支,讀的人卻看不出被蓋掉。
帶上工具名稱,一支 CLI 就有一頁,換一支 CLI 重跑也不會動到別支的頁。
`MONITOR` 的雜湊來源比照 `CHECK`,取 `{主機名}/{登入帳號}`。
`MONITOR` 的雜湊來源比照 `CHECK`,取 `{主機名}/{登入帳號}`,`{主機名}` 照上面那條取短主機名。
助理巡檢的是一台機器,不是一個存取庫。
一台機器一頁,換一支 CLI 不另開頁。
算法同上,由同一支 `jsc-gitea/tools/hash-id` 產生。
`patrol.sh` 與模型填值兩條路徑都要照這一條截取,改動任一邊就回頭核對另一邊。
**為什麼不帶工具名稱。** 這一點與 `TOOLING` 相反。
`TOOLING` 一支 CLI 一頁,因為每支 CLI 各有自己的已安裝 plugin 與 hook 接線。
@@ -401,6 +442,8 @@ 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`),內容頁解自己的型別;目錄頁指向內容頁的連結用絕對網址
- [ ] 頁名樣式三處一致:`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 都不得寫死數字**——腳本會自我斷言,抄一份數字進文件,加減判定路徑時就漂移,稽核反而被舊數字誤導