feat(shared): 新增14個共用spec、models/todo工具與樣板產生器,收斂跨repo重複規範

依 todo.md 執行的規範治理專案:新增 spec-preflight 等 14 個共用規範(含
conventional-commit/pull-request/git-push/issue-read/todo-list/ask-user/
subagent/no-scratch-files/skill-invocation/script-path/action-scaffold/
node-src-layout/plugin-cli/model),擴充 spec-git-safety 與 spec-gitea(token
優先序、機密遮蔽、Wiki 頁名轉義規則);新增可執行 skill `models`(模型能力
查詢與標籤)與 `todo`(依指定模型產生/附加 todo.md);新增 plugin.meta.json
單一事實來源與 gen-plugin-files.mjs 樣板產生器,統一四個 repo 的 manifest/
README/AGENTS.md 並移除寫死的本機使用者路徑;新增 shared/scripts/lib 的
log/機密遮蔽三語言參考實作。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 06:02:30 +00:00
co-authored by Claude Sonnet 5
parent d49ae1085d
commit 1030f9d403
38 changed files with 2329 additions and 139 deletions
+67
View File
@@ -40,6 +40,43 @@ description: JSC plugins 共用「Gitea 工具規範」:tea 或 Gitea REST API
- 流程若可能使對話內文殘留 token(push/API 呼叫),完成後提醒使用者清除對話(Claude Code:`/clear`),並先確認輸出與 log 無明文 token。
### token 解析優先序
完整優先序(依序取第一個驗證成功者):
1. 呼叫端工具自訂的專用環境變數(若有,例如 persona 的 `PERSONA_GITEA_TOKEN`)。
2. `$GITEA_TOKEN`。
3. tea 設定檔(`~/.config/tea/config.yml` 或 `~/.tea/config.yml`)中對應 host 的 token。
4. `~/.git-credentials`(`credential.helper=store`)中對應 host 的密碼。
每個候選都必須用 `GET /repos/<owner>/<repo>` 之類的輕量請求實際驗證可用(HTTP 200 才採用),驗證失敗就換下一個候選;全部候選都失敗,才回報錯誤並停止。
各工具可以只實作其中適用的子集(例如純粹用 git `http.extraHeader` 認證的工具可以不需要 tea 設定檔/git-credentials 這兩層 fallback),但已實作的部分不可打亂這個順序,且專用變數只能插在最前面,不能插在中間。
參考實作:`doc/scripts/worklog/wiki_api.py` 的 `resolve_token()` 依「`GITEA_TOKEN` → tea 設定檔 → git-credentials」順序逐一以 API 驗證;`persona/scripts/persona-gitea.mjs` 的 `giteaEnv()` 則示範了「專用變數插在最前面」——優先取 `PERSONA_GITEA_TOKEN`,其次才是 `GITEA_TOKEN`。
### 機密遮蔽實作
任何會把 Gitea API 回應內容、git stderr/stdout、或組出的錯誤訊息顯示給使用者/寫進 log 的地方,一律先套用下列遮蔽規則。
| 規則 | 用途 |
| --- | --- |
| `[A-Za-z0-9_-]*:[A-Za-z0-9_-]{16,}@` → 取代為 `***@` | URL 內嵌憑證 user:token@ |
| `\b[0-9a-f]{40}\b` → `***` | Gitea 40 字元 token |
| `\bgh[pousr]_[A-Za-z0-9_]{16,}\b` → `***` | GitHub token |
| `\bsk-[A-Za-z0-9\-_]{16,}\b` → `***` | API key |
| `(?i)\b(token\|password\|passwd\|pwd\|secret\|api[_-]?key)\b\s*[:=]\s*\S+` → `\1=***` | key=value 形式機密 |
| `(?i)Authorization:\s*(token\|bearer)\s+\S+` → `Authorization: \1 ***` | Authorization 標頭 |
| `[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}` → `***` | Email |
| `\b09\d{2}[-\s]?\d{3}[-\s]?\d{3}\b` → `***` | 台灣手機 |
| `\b[A-Z][12]\d{8}\b` → `***` | 台灣身分證字號 |
(來源:`doc/scripts/worklog/transcript.py` 的 `REDACT_PATTERNS`,逐條照抄。)
這 9 條規則的**唯一權威資料來源**為 `shared/scripts/lib/redact-patterns.json`(Python/JavaScript/Bash 三語言共用),上表僅供閱讀對照,異動一律先改該 JSON 檔再回頭同步本表,避免文件與 JSON 各自漂移。
任何存取 Gitea 的 JSC 腳本/skill,其錯誤輸出路徑都必須套用這 9 條規則(或功能對等實作),而不只是遮蔽 token 字面值。
## 不依賴 `jq`(環境未必安裝)
- 解析 JSON 用 `tea` 的結構化輸出(`--output csv`/`--fields`),或把原始 JSON 直接交給助理/subagent 解析,**不要 pipe 到 `jq`**。
@@ -53,8 +90,38 @@ description: JSC plugins 共用「Gitea 工具規範」:tea 或 Gitea REST API
- API 失敗(401/403/網路錯誤)→ 回報錯誤(**遮蔽 token**)並停止;401/403 多半是 token 失效或權限不足。
- 版本相依端點(project/column/dependency 等)先以 GET 探測(404/501 視為不支援),**不得對未確認存在的端點做寫入**。
### Wiki 頁名轉義規則
Gitea wiki 的「title」與實際存放用的「sub_url/檔名」不是同一個字串,Gitea 會對 title
做內部轉義;這個轉義演算法未完全公開,**不可自行用字串取代規則反推**。已知規律與收斂
後的共通作法如下:
1. 已觀測到的規律:title 中的**空白**在 sub_url/檔名中會對應成 `-`;但字面上的 `-`
本身另有轉義形式(例如 title `Worklog-2026-07-W4`(本身含 `-`、不含空白)對應到的
sub_url 觀測值為 `Worklog-2026-07-W4.-`)。也就是「title→sub_url」只有空白這一條
規則可靠,`-`/`.-` 等其他符號的精確對應**不可靠、不要照抄硬編碼**。
2. **查表優先於猜測轉義規則**:任何需要「用 title 找到實際頁面路徑」的操作(讀取、
更新、刪除既有頁面),一律先呼叫 `GET /repos/<owner>/<repo>/wiki/pages`(分頁完整
讀取,見「API 呼叫慣例」)列出全部頁面,比對 `title` 欄位取得真正的 `sub_url`,
再用該 `sub_url` 組出 `/wiki/page/<sub_url>`。查不到才退回把 title 本身當作
sub_url 使用(新頁尚未建立時的合理退路)。
3. **建立新頁不必自行轉義**:`POST /wiki/new` 直接帶完整、人類可讀的 title 字串即可
(含空白與符號皆可),轉義是 Gitea 伺服器端完成的,呼叫端不用預先處理。
4. 若寫入策略是透過 **git clone/push** 直接操作 wiki repo 產生 `.md` 檔(而非呼叫
REST API),則不必還原 Gitea 的內部轉義:改由呼叫端自建一份「工作路徑 → 儲存
檔名」manifest(例如 `_paths.json`),寫入時查 manifest 決定檔名、讀回時查 manifest
還原原始路徑,全程不依賴、也不猜測 Gitea 從檔名反推 title 的規則。
5. 兩種寫入策略(REST API 查表 vs git clone/push + 自建 manifest)各有各的理由,
不合併;但上述查表優先、不猜測轉義的原則對兩者都適用。
參考實作:`doc/scripts/worklog/wiki_api.py` 的 `resolve_sub_url()` 走 REST API 查表
(規則 2);`persona/scripts/persona-gitea.mjs` 的 `wikiName()` 走 git clone/push +
`_paths.json` manifest(規則 4)。
## gitea 主機決定順序
本節為唯一權威順序,其他 skill 若需描述 host 決定流程,只能引用本節,不可另行複述或改動順序。
依序決定(取第一個成功者):
1. 參數 `--host <主機>`。