依 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>
9.0 KiB
name, description
| name | description |
|---|---|
| spec-gitea | JSC plugins 共用「Gitea 工具規範」:tea 或 Gitea REST API + GITEA_TOKEN 的工具選擇與可用性檢查、token 機密保護(不 echo、遮蔽、不落地)、不依賴 jq、API 呼叫慣例(分頁完整讀取、UTF-8 JSON body、實際換行)、gitea 主機決定順序。當其他 skill 內文引用 spec-gitea 或 /jsc-shared:spec-gitea、或執行任何需存取 Gitea 的 JSC skill 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。 |
spec-gitea — 共用 Gitea 工具規範
所有 JSC skills 存取 Gitea(議題、留言、PR、repo 清單)時,一律遵守以下規範。
工具選擇(tea 或 api)
使用者已明確選定工具(--tool 或對話中已選)時跳過詢問,只做該工具的可用性驗證。否則:
-
檢查
tea是否存在:command -v tea;存在則執行tea login list記錄可用 login 與 host(失敗記錄原因,不中止)。 -
檢查
GITEA_TOKEN是否已設定,只輸出「已設定/未設定」,不得輸出 token 內容:[ -n "${GITEA_TOKEN}" ] && echo "GITEA_TOKEN 已設定" || echo "GITEA_TOKEN 未設定" -
以表格呈現檢查結果後詢問使用者要用哪一種:
選項 可選條件 後續使用方式 teatea可執行且目標 host 有對應 login命令一律帶 --login <name> --repo <owner>/<repo>apiGITEA_TOKEN已設定Gitea REST API + curl,標頭Authorization: token $GITEA_TOKEN -
兩種方式都不可用 → 回報缺少
tea login或GITEA_TOKEN並停止;不要請使用者把 token 貼進對話。 -
多筆來源分屬不同 host:選
tea須確認每個 host 都有對應 login;選api須同一個 token 可存取全部,否則回報權限不足並停止。
Token 機密保護(極重要)
-
token 一律從環境變數讀取(
$GITEA_TOKEN),絕不寫死在 skill、commit、PR、議題、log 或任何輸出。 -
不可 echo 含 token 的指令或 URL;顯示給使用者的指令/錯誤訊息一律遮蔽 token(以
***取代);檢查時只輸出「已設定/未設定」。 -
帶 token 的 URL(clone/push)用變數帶入、不可印出;token 用完即棄,不寫進 git remote 設定、不落地。clone 完成後把 origin 還原成不含 token 的乾淨 URL:
git -C "${dest}" remote set-url origin "https://<host>/<owner>/<repo>.git" -
流程若可能使對話內文殘留 token(push/API 呼叫),完成後提醒使用者清除對話(Claude Code:
/clear),並先確認輸出與 log 無明文 token。
token 解析優先序
完整優先序(依序取第一個驗證成功者):
- 呼叫端工具自訂的專用環境變數(若有,例如 persona 的
PERSONA_GITEA_TOKEN)。 $GITEA_TOKEN。- tea 設定檔(
~/.config/tea/config.yml或~/.tea/config.yml)中對應 host 的 token。 ~/.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,} → *** |
|
\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。
API 呼叫慣例
- API base:
https://<host>/api/v1(repo 層:https://<host>/api/v1/repos/<owner>/<repo>)。 - 標頭:
Authorization: token $GITEA_TOKEN。 - 分頁必須完整讀取:持續累加
page直到回傳筆數< limit(或回空陣列)為止,不可只取第一頁。 - 寫入(議題描述/留言/PR body)以 UTF-8 JSON 檔帶入(如
--data @body.json);換行必須是實際換行,不可讓內容顯示字面\n(編碼細節見/jsc-shared:spec-output)。 - API 失敗(401/403/網路錯誤)→ 回報錯誤(遮蔽 token)並停止;401/403 多半是 token 失效或權限不足。
- 版本相依端點(project/column/dependency 等)先以 GET 探測(404/501 視為不支援),不得對未確認存在的端點做寫入。
Wiki 頁名轉義規則
Gitea wiki 的「title」與實際存放用的「sub_url/檔名」不是同一個字串,Gitea 會對 title 做內部轉義;這個轉義演算法未完全公開,不可自行用字串取代規則反推。已知規律與收斂 後的共通作法如下:
- 已觀測到的規律:title 中的空白在 sub_url/檔名中會對應成
-;但字面上的-本身另有轉義形式(例如 titleWorklog-2026-07-W4(本身含-、不含空白)對應到的 sub_url 觀測值為Worklog-2026-07-W4.-)。也就是「title→sub_url」只有空白這一條 規則可靠,-/.-等其他符號的精確對應不可靠、不要照抄硬編碼。 - 查表優先於猜測轉義規則:任何需要「用 title 找到實際頁面路徑」的操作(讀取、
更新、刪除既有頁面),一律先呼叫
GET /repos/<owner>/<repo>/wiki/pages(分頁完整 讀取,見「API 呼叫慣例」)列出全部頁面,比對title欄位取得真正的sub_url, 再用該sub_url組出/wiki/page/<sub_url>。查不到才退回把 title 本身當作 sub_url 使用(新頁尚未建立時的合理退路)。 - 建立新頁不必自行轉義:
POST /wiki/new直接帶完整、人類可讀的 title 字串即可 (含空白與符號皆可),轉義是 Gitea 伺服器端完成的,呼叫端不用預先處理。 - 若寫入策略是透過 git clone/push 直接操作 wiki repo 產生
.md檔(而非呼叫 REST API),則不必還原 Gitea 的內部轉義:改由呼叫端自建一份「工作路徑 → 儲存 檔名」manifest(例如_paths.json),寫入時查 manifest 決定檔名、讀回時查 manifest 還原原始路徑,全程不依賴、也不猜測 Gitea 從檔名反推 title 的規則。 - 兩種寫入策略(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 決定流程,只能引用本節,不可另行複述或改動順序。
依序決定(取第一個成功者):
- 參數
--host <主機>。 - 環境變數
$GITEA_HOST(若有)。 - 目前工作目錄是 git repo 且
git remote get-url origin指向某 gitea 主機 → 取該 host。 - 以上皆無 → 詢問使用者,不臆測。
主機僅取 host 部分(如 gitea.jsc.idv.tw)。