feat/script-contract-preflight/main #20

Merged
admin merged 3 commits from feat/script-contract-preflight/main into master 2026-09-17 04:32:55 +00:00
Member

摘要

讓第一支腳本從頭到尾跑通,並把所有腳本共用的契約一次立好:具名 flag 輸入、單行 JSON 輸出、四層前置檢查、--dry-run、冪等查重,以及 Gitea 與 git 各自唯一的出口。

需求議題

#1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組

工作包議題

#3 — 走通腳本契約:共用函式庫、四層前置檢查與 labels-list

變更內容

  • scripts/lib.js:flag 解析、{ok, data, error:{code, message}} 單行輸出、giteaRequest(全專案唯一 HTTP 出口)、runGit(唯一子行程出口)、四層前置檢查、findIssueByTitle 查重、以 import.meta.url 回推的路徑定位。
  • scripts/labels-list.js:第一支腳本,只讀不寫。
  • test/helpers/:stub Gitea server(錄下每一筆請求)、子行程執行器、臨時 git repo。
  • test/:五支測試,共 50 個案例。

設計重點

  • issues unit 的寫入權採實測。Gitea 的 team unit 權限可獨立於 repo 的 push 權限,permissions.push 為真不代表 issues 寫得進去。探針打在不存在的議題 index 0:有寫入權會通過權限中介層後回 404,沒有則直接 403,兩種結果都不改動任何東西。真實 repo 上已驗證回 404。
  • 前一層沒過就不往下打,避免把一個設定問題報成四個。測試直接斷言請求序列。
  • 認證沿用既有的 tea login,讀 tea 的 config.yml;TEA_SDLC_API_BASE / TEA_SDLC_TOKEN 只是測試與 CI 的覆寫出口。YAML 只做針對 tea 實際輸出格式的最小剖析,不引入套件。
  • process.stdout.write 後不能立刻 process.exit。stdout 接到 pipe 時寫入是非同步的,舊寫法在約 83KB 處截斷;已改為等 callback 回來再退出,並以 8000 筆標籤的回歸測試覆蓋。
  • 查重翻頁有上限。超過即以 DEDUPE_LIMIT 報錯而非無聲回 null——無聲回 null 會讓呼叫端把既有議題再建一次,正好是查重要防的事。

解決的問題

後續十幾支腳本不必各自處理參數、輸出格式、認證、錯誤碼與前置檢查;使用者踩到環境問題時,拿到的是一個明確的錯誤碼與「該去哪裡改」,而不是後續步驟東一個西一個地失敗。

影響的功能

新增,無既有功能受影響。唯一對外可見的行為是 labels-list 這支新指令。

測試結果

npm test:

ℹ tests 50
ℹ suites 0
ℹ pass 50
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1029.524667

對真實 Gitea 的手動驗證(plugins/tea-sdlc,該 repo 的時間追蹤目前是關的):

$ node scripts/labels-list.js --repo plugins/tea-sdlc --dry-run
{"ok":true,"data":{"dryRun":true,"repo":"plugins/tea-sdlc","requests":[{"method":"GET","path":"/repos/plugins/tea-sdlc/labels"}],"paths":{"templates":"/root/plugins/tea-sdlc/templates","references":"/root/plugins/tea-sdlc/references"}}}

$ node scripts/labels-list.js --repo plugins/tea-sdlc
{"ok":false,"error":{"code":"TIME_TRACKER_OFF","message":"repo 尚未開啟時間追蹤,工時碼錶無法運作;請到 Settings → Advanced Settings → Enable Time Tracker 開啟"}}
$ echo $?
1

第一到第三層都對真實 Gitea 通過(環境、tea 登入、issues unit 寫入權探針回 404),第四層如實擋下。從 /tmp 執行結果完全相同,驗證路徑定位不依賴 cwd。

待確認

  1. labels-list 是否該跑滿四層前置檢查。 目前照「任一層不通過即中止」實作,因此在時間追蹤關閉的 repo 上,連列標籤都會被擋。若希望讀取型腳本只跑前三層,我改成由各腳本宣告所需層級。
  2. 直接打 REST API vs. tea 原生子指令。 議題 #1 的 Further Notes 寫「議題、標籤、Milestone、留言、工時列表走 tea 原生子指令」,但 Testing Decisions 寫「lib.js 的 Gitea 呼叫集中於單一函式,測試時以環境變數指向本機 stub server」——後者只有自己擁有 HTTP 呼叫才做得到。此 PR 採後者。這個取捨會決定 #4–#16 的寫法,請確認。
  3. 第一層額外檢查了 plugin 目錄完整性(PLUGIN_LAYOUT_BROKEN),不在 #3 對第一層的字面定義內。加它是為了讓路徑定位在正式流程中有真正的消費者,而不是只出現在 --dry-run 輸出裡。

🤖 Generated with Claude Code

## 摘要 讓第一支腳本從頭到尾跑通,並把所有腳本共用的契約一次立好:具名 flag 輸入、單行 JSON 輸出、四層前置檢查、`--dry-run`、冪等查重,以及 Gitea 與 git 各自唯一的出口。 ## 需求議題 #1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組 ## 工作包議題 #3 — 走通腳本契約:共用函式庫、四層前置檢查與 labels-list ## 變更內容 - `scripts/lib.js`:flag 解析、`{ok, data, error:{code, message}}` 單行輸出、`giteaRequest`(全專案唯一 HTTP 出口)、`runGit`(唯一子行程出口)、四層前置檢查、`findIssueByTitle` 查重、以 `import.meta.url` 回推的路徑定位。 - `scripts/labels-list.js`:第一支腳本,只讀不寫。 - `test/helpers/`:stub Gitea server(錄下每一筆請求)、子行程執行器、臨時 git repo。 - `test/`:五支測試,共 50 個案例。 ## 設計重點 - **issues unit 的寫入權採實測**。Gitea 的 team unit 權限可獨立於 repo 的 push 權限,`permissions.push` 為真不代表 issues 寫得進去。探針打在不存在的議題 index 0:有寫入權會通過權限中介層後回 404,沒有則直接 403,兩種結果都不改動任何東西。真實 repo 上已驗證回 404。 - **前一層沒過就不往下打**,避免把一個設定問題報成四個。測試直接斷言請求序列。 - **認證沿用既有的 `tea login`**,讀 tea 的 `config.yml`;`TEA_SDLC_API_BASE` / `TEA_SDLC_TOKEN` 只是測試與 CI 的覆寫出口。YAML 只做針對 tea 實際輸出格式的最小剖析,不引入套件。 - **`process.stdout.write` 後不能立刻 `process.exit`**。stdout 接到 pipe 時寫入是非同步的,舊寫法在約 83KB 處截斷;已改為等 callback 回來再退出,並以 8000 筆標籤的回歸測試覆蓋。 - **查重翻頁有上限**。超過即以 `DEDUPE_LIMIT` 報錯而非無聲回 `null`——無聲回 null 會讓呼叫端把既有議題再建一次,正好是查重要防的事。 ## 解決的問題 後續十幾支腳本不必各自處理參數、輸出格式、認證、錯誤碼與前置檢查;使用者踩到環境問題時,拿到的是一個明確的錯誤碼與「該去哪裡改」,而不是後續步驟東一個西一個地失敗。 ## 影響的功能 新增,無既有功能受影響。唯一對外可見的行為是 `labels-list` 這支新指令。 ## 測試結果 `npm test`: ``` ℹ tests 50 ℹ suites 0 ℹ pass 50 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 1029.524667 ``` 對真實 Gitea 的手動驗證(`plugins/tea-sdlc`,該 repo 的時間追蹤目前是關的): ``` $ node scripts/labels-list.js --repo plugins/tea-sdlc --dry-run {"ok":true,"data":{"dryRun":true,"repo":"plugins/tea-sdlc","requests":[{"method":"GET","path":"/repos/plugins/tea-sdlc/labels"}],"paths":{"templates":"/root/plugins/tea-sdlc/templates","references":"/root/plugins/tea-sdlc/references"}}} $ node scripts/labels-list.js --repo plugins/tea-sdlc {"ok":false,"error":{"code":"TIME_TRACKER_OFF","message":"repo 尚未開啟時間追蹤,工時碼錶無法運作;請到 Settings → Advanced Settings → Enable Time Tracker 開啟"}} $ echo $? 1 ``` 第一到第三層都對真實 Gitea 通過(環境、tea 登入、issues unit 寫入權探針回 404),第四層如實擋下。從 `/tmp` 執行結果完全相同,驗證路徑定位不依賴 cwd。 ## 待確認 1. **`labels-list` 是否該跑滿四層前置檢查。** 目前照「任一層不通過即中止」實作,因此在時間追蹤關閉的 repo 上,連列標籤都會被擋。若希望讀取型腳本只跑前三層,我改成由各腳本宣告所需層級。 2. **直接打 REST API vs. `tea` 原生子指令。** 議題 #1 的 Further Notes 寫「議題、標籤、Milestone、留言、工時列表走 tea 原生子指令」,但 Testing Decisions 寫「lib.js 的 Gitea 呼叫集中於單一函式,測試時以環境變數指向本機 stub server」——後者只有自己擁有 HTTP 呼叫才做得到。此 PR 採後者。這個取捨會決定 #4–#16 的寫法,請確認。 3. **第一層額外檢查了 plugin 目錄完整性**(`PLUGIN_LAYOUT_BROKEN`),不在 #3 對第一層的字面定義內。加它是為了讓路徑定位在正式流程中有真正的消費者,而不是只出現在 `--dry-run` 輸出裡。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jiantw83 added 3 commits 2026-09-17 04:27:56 +00:00
scripts/lib.js 立起所有腳本共用的地基:具名 flag 解析、單行 JSON 輸出
{ok, data, error:{code, message}}、Gitea API 與 git 各自唯一的出口、
四層前置檢查、依標題的冪等查重。

前置檢查依序為執行環境(git/tea 在 PATH 上、plugin 目錄完整)、Gitea 登入
有效、帳號對 issues unit 的寫入權、repo 已開啟時間追蹤;任一層不通過即帶著
可區分的錯誤碼中止,訊息一律指出該去哪裡改設定,前一層沒過就不再往下打。

issues unit 的寫入權採實測而非讀 permissions.push——Gitea 的 team unit 權限
可獨立於 repo 的 push 權限。探針打在不存在的議題 index 0:有寫入權會通過權限
中介層後回 404,沒有則直接 403,兩種結果都不改動任何東西。

認證預設沿用使用者既有的 tea login(讀 tea 的 config.yml),環境變數
TEA_SDLC_API_BASE / TEA_SDLC_TOKEN 只作為測試與 CI 的覆寫出口。

labels-list 為第一支腳本,刻意只讀不寫,呼應「不自動建立 Gitea 標籤」。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
測試一律以子行程執行腳本、比對 stdout 的單行 JSON 與 exit code,因為那正是
七個平台共用的實際呼叫方式,不會因內部重構而破碎。

Gitea 以本機 stub server 替身並錄下每一筆請求,藉此斷言「腳本到底發了哪些
請求」——包含 --dry-run 不得發出任何請求、前一層檢查沒過就不再往下打、
寫入權探針不得挾帶任何要寫入的欄位。git 則在 .tmp/ 下的臨時 repo 跑真實
指令,比 mock 可信且成本低。

lib-exports.test.js 是唯一直接 import lib 的例外:冪等查重與 git 執行點在
CLI 邊界上還沒有消費者,檔頭已註明等 #4 與 #10 落地後即可縮小或移除。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
process.stdout.write 之後立刻 process.exit 會截斷輸出——stdout 接到 pipe 時
寫入是非同步的。改為等 write 的 callback 回來再退出。實測舊寫法在約 83KB 處
被切斷,新增的回歸測試以 8000 筆標籤覆蓋這條路徑。

findIssueByTitle 的翻頁原本沒有上限,Gitea 若持續回滿一頁就會無限打下去。
加上 200 頁上限,超過即以 DEDUPE_LIMIT 報錯而非無聲回 null——無聲回 null 會
讓呼叫端把既有議題再建一次,正好是冪等查重要防的事。

parseFlags 取值時不再於三元運算式內遞增迴圈變數,改為獨立敘述。

測試工具的 maxBuffer 調高到 64MB:預設 1MB 會在長輸出時砍掉子行程,那是測試
工具的限制而非腳本的問題。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin approved these changes 2026-09-17 04:32:52 +00:00
admin merged commit d417132b4e into master 2026-09-17 04:32:55 +00:00
admin deleted branch feat/script-contract-preflight/main 2026-09-17 04:32:55 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/tea-sdlc#20