feat/install-verify/main #62

Merged
admin merged 1 commits from feat/install-verify/main into master 2026-09-18 00:52:13 +00:00
Member

摘要

install 寫完轉接檔之後,把那條叫用鏈真的走一遍:轉接檔 → PATH 上的 tea-sdlc → 流程正本。
任一環不通就回 ok:false,但已經寫好的轉接檔一份都不刪。

需求議題

#56 — 補上六個指令走通後浮現的四個缺口

工作包議題

#59 — 以安裝驗證確認轉接檔真的叫得動

變更內容

檔案 動作
scripts/install-verify.js 新增。走一遍叫用鏈,逐平台回報 pass/fail,並把結果收成一句「病灶+修復」
scripts/install.js 寫完轉接檔後呼叫驗證;驗不過改回 Failure;轉接檔的叫用行改用 invocation() 這個共同來源
scripts/lib.js 新增 Failure:失敗但把 data 一起交出去
test/install-verify.test.js 新增。14 項,全部在臨時家目錄上真的寫檔、真的執行
test/helpers/fake-plugin.js 假 plugin 多一個可從 PATH 叫到的 tea-sdlc 殼
test/readme-install.test.js 釘住 README 有交代驗證與「不回滾」
README.md、AGENTS.md 對應的說明與模組邊界表

設計重點

  • 真的執行,不是查檔案在不在。 到 PATH 上把 tea-sdlc 找出來 execFileSync 一次,
    再把取回的正本跟套件裡的那一份逐字比對。所以「找不到」、「叫不動」、「叫到的是另一份安裝」
    三種都分得出來——只看 exit code 的話,第三種會靜靜地通過。
  • 叫用行只有一個來源。 寫進轉接檔的字串與驗證要比對的字串都出自 invocation(),
    分成兩份寫的話,改了格式只會讓驗證從此永遠 fail,或者更糟:永遠 pass。
  • 轉接檔回磁碟讀。 這一步要驗的正是「寫出去之後檔案真的長那樣」,所以刻意不把剛才那份
    原稿遞給驗證——遞過去它就有機會拿記憶體裡的字串來比,等於自己驗自己。
  • 叫用鏈與轉接檔分開回報。 tea-sdlc 不在 PATH 上時,每個平台仍回報 ok:true:
    轉接檔真的沒問題,刪掉它一點幫助也沒有。
  • Failure 是為了「做完了但驗不過」。 丟 ScriptError 只剩 code 與 message,
    而這種失敗最該交出去的正是「已經寫了哪些、哪一段不通」。envelope 形狀不變,
    只是 {ok:false, error} 旁邊多一個 data,只讀 error.code 的呼叫端照常運作。

解決的問題

轉接檔寫好了不等於叫得動。最脆弱的是中間那一環:套件裝在某個 Node 版本底下,換個版本管理器
或改 npm prefix 就找不到了,而轉接檔本身看起來完全正常。沒有這道驗證,使用者要到第一次打
/sdlc-plan 才發現,那時他已經離開安裝的心智狀態很久了。

驗不過不回滾。 回滾在升級情境下是淨損失:原本有一組能用的舊轉接檔,覆蓋後驗證失敗再刪掉,
使用者就從「有點舊但能用」變成什麼都沒有。

審查過程另外抓到兩個真的 bug,一併修掉:

  1. execFileSync 預設會把子行程的 stderr 接到我們的 stderr,違反 lib 寫明的
    「stderr 永遠保持乾淨」輸出契約。
  2. 病灶把有用的部分丟掉了。tea-sdlc 的失敗一律是 stdout 上的一行 JSON(stderr 按設計
    保持乾淨),所以原本讀的 error.stderr 永遠是空字串,訊息退化成光禿禿的
    Command failed: …。現在會報出子行程自己說的 PLUGIN_LAYOUT_BROKEN <訊息>。

影響的功能

  • tea-sdlc install:多一段驗證,輸出多一個 verify 欄位;驗不過時回 ok:false、退出碼 1。
    已知的行為改變:PATH 上排在前面的是另一個版本的 tea-sdlc 時,install 會失敗。
    這會咬到「從 checkout 跑、同時裝了舊的全域版」的情況——那也正是它要講的事。
  • tea-sdlc install --dry-run:不寫入,也就沒有東西可驗,verify 標成 skipped,仍回 ok:true。
  • scripts/lib.js 的 main():多認得 Failure 一種回傳值。其餘腳本行為不變。
  • 驗證全程不碰網路,與 Gitea 登入、時間追蹤無關。
  • 暫不支援 Windows(onPath() 只用 : 切 PATH),已與需求方確認為範圍外。

測試結果

npm test:

ℹ tests 807
ℹ suites 0
ℹ pass 807
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 11203.317627

test/install-verify.test.js 新增 14 項,全部在臨時家目錄上真的寫檔、真的把 tea-sdlc
放上 PATH、真的執行它:

  • 全部通過時逐平台回報 pass,checked 數目對得上
  • chain.resolved 確實指向 PATH 上那個 tea-sdlc
  • PATH 上找不到 → 整體 ok:false,病灶指向 PATH,且各平台仍 ok:true
  • PATH 上是另一份安裝 → 取回的正本不一樣,驗得出來
  • PATH 上的 tea-sdlc 跑不完 → 病灶取自它自己報的錯,且我們的 stderr 仍為空
  • PATH 上的 tea-sdlc 自己裝壞 → 病灶是 PLUGIN_LAYOUT_BROKEN,不是 Command failed
  • 轉接檔叫用行不對/檔案不見 → 該平台 fail,其他平台照常 pass
  • 某平台驗不過 → CLI 整體回 ok:false,data 照樣交出去
  • 驗證失敗時轉接檔一份都還在;升級情境下舊的也沒被弄不見
  • 整個過程對 stub Gitea 發出 0 個請求
  • --dry-run 一個字都不寫,verify.skipped 為 true,仍回 ok:true
  • test/ 不在套件白名單裡

兩個 bug 的修復都先把程式改回壞的樣子、確認對應的測試真的會紅,才算數:

# 還原 stdio 修復後
ℹ pass 12
ℹ fail 1
  AssertionError [ERR_ASSERTION]: 子行程的 stderr 漏出來了

# 還原「病灶取自 stdout」修復後
ℹ pass 13
ℹ fail 1

另外在真實家目錄(臨時 HOME)上手動跑過 node bin/tea-sdlc.js install 的兩條路徑:
PATH 上沒有 tea-sdlc 時退出碼 1、轉接檔仍在;放上殼之後退出碼 0、verify.ok 為 true。

## 摘要 `install` 寫完轉接檔之後,把那條叫用鏈真的走一遍:**轉接檔 → PATH 上的 `tea-sdlc` → 流程正本**。 任一環不通就回 `ok:false`,但已經寫好的轉接檔一份都不刪。 ## 需求議題 #56 — 補上六個指令走通後浮現的四個缺口 ## 工作包議題 #59 — 以安裝驗證確認轉接檔真的叫得動 ## 變更內容 | 檔案 | 動作 | | --- | --- | | `scripts/install-verify.js` | 新增。走一遍叫用鏈,逐平台回報 pass/fail,並把結果收成一句「病灶+修復」 | | `scripts/install.js` | 寫完轉接檔後呼叫驗證;驗不過改回 `Failure`;轉接檔的叫用行改用 `invocation()` 這個共同來源 | | `scripts/lib.js` | 新增 `Failure`:失敗但把 `data` 一起交出去 | | `test/install-verify.test.js` | 新增。14 項,全部在臨時家目錄上真的寫檔、真的執行 | | `test/helpers/fake-plugin.js` | 假 plugin 多一個可從 PATH 叫到的 `tea-sdlc` 殼 | | `test/readme-install.test.js` | 釘住 README 有交代驗證與「不回滾」 | | `README.md`、`AGENTS.md` | 對應的說明與模組邊界表 | ## 設計重點 - **真的執行,不是查檔案在不在。** 到 PATH 上把 `tea-sdlc` 找出來 `execFileSync` 一次, 再把取回的正本跟套件裡的那一份**逐字比對**。所以「找不到」、「叫不動」、「叫到的是另一份安裝」 三種都分得出來——只看 exit code 的話,第三種會靜靜地通過。 - **叫用行只有一個來源。** 寫進轉接檔的字串與驗證要比對的字串都出自 `invocation()`, 分成兩份寫的話,改了格式只會讓驗證從此永遠 fail,或者更糟:永遠 pass。 - **轉接檔回磁碟讀。** 這一步要驗的正是「寫出去之後檔案真的長那樣」,所以刻意不把剛才那份 原稿遞給驗證——遞過去它就有機會拿記憶體裡的字串來比,等於自己驗自己。 - **叫用鏈與轉接檔分開回報。** `tea-sdlc` 不在 PATH 上時,每個平台仍回報 `ok:true`: 轉接檔真的沒問題,刪掉它一點幫助也沒有。 - **`Failure` 是為了「做完了但驗不過」。** 丟 `ScriptError` 只剩 code 與 message, 而這種失敗最該交出去的正是「已經寫了哪些、哪一段不通」。envelope 形狀不變, 只是 `{ok:false, error}` 旁邊多一個 `data`,只讀 `error.code` 的呼叫端照常運作。 ## 解決的問題 轉接檔寫好了不等於叫得動。最脆弱的是中間那一環:套件裝在某個 Node 版本底下,換個版本管理器 或改 npm prefix 就找不到了,而轉接檔本身看起來完全正常。沒有這道驗證,使用者要到第一次打 `/sdlc-plan` 才發現,那時他已經離開安裝的心智狀態很久了。 **驗不過不回滾。** 回滾在升級情境下是淨損失:原本有一組能用的舊轉接檔,覆蓋後驗證失敗再刪掉, 使用者就從「有點舊但能用」變成什麼都沒有。 審查過程另外抓到兩個真的 bug,一併修掉: 1. `execFileSync` 預設會把子行程的 stderr 接到我們的 stderr,違反 lib 寫明的 「stderr 永遠保持乾淨」輸出契約。 2. 病灶把有用的部分丟掉了。`tea-sdlc` 的失敗一律是 **stdout** 上的一行 JSON(stderr 按設計 保持乾淨),所以原本讀的 `error.stderr` 永遠是空字串,訊息退化成光禿禿的 `Command failed: …`。現在會報出子行程自己說的 `PLUGIN_LAYOUT_BROKEN <訊息>`。 ## 影響的功能 - `tea-sdlc install`:多一段驗證,輸出多一個 `verify` 欄位;驗不過時回 `ok:false`、退出碼 1。 **已知的行為改變**:PATH 上排在前面的是另一個版本的 `tea-sdlc` 時,install 會失敗。 這會咬到「從 checkout 跑、同時裝了舊的全域版」的情況——那也正是它要講的事。 - `tea-sdlc install --dry-run`:不寫入,也就沒有東西可驗,`verify` 標成 `skipped`,仍回 `ok:true`。 - `scripts/lib.js` 的 `main()`:多認得 `Failure` 一種回傳值。其餘腳本行為不變。 - 驗證全程不碰網路,與 Gitea 登入、時間追蹤無關。 - 暫不支援 Windows(`onPath()` 只用 `:` 切 PATH),已與需求方確認為範圍外。 ## 測試結果 `npm test`: ``` ℹ tests 807 ℹ suites 0 ℹ pass 807 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 11203.317627 ``` `test/install-verify.test.js` 新增 14 項,全部在臨時家目錄上真的寫檔、真的把 `tea-sdlc` 放上 PATH、真的執行它: - 全部通過時逐平台回報 `pass`,`checked` 數目對得上 - `chain.resolved` 確實指向 PATH 上那個 `tea-sdlc` - PATH 上找不到 → 整體 `ok:false`,病灶指向 PATH,且各平台仍 `ok:true` - PATH 上是另一份安裝 → 取回的正本不一樣,驗得出來 - PATH 上的 `tea-sdlc` 跑不完 → 病灶取自它自己報的錯,且我們的 stderr 仍為空 - PATH 上的 `tea-sdlc` 自己裝壞 → 病灶是 `PLUGIN_LAYOUT_BROKEN`,不是 `Command failed` - 轉接檔叫用行不對/檔案不見 → 該平台 fail,其他平台照常 pass - 某平台驗不過 → CLI 整體回 `ok:false`,`data` 照樣交出去 - 驗證失敗時轉接檔一份都還在;升級情境下舊的也沒被弄不見 - 整個過程對 stub Gitea 發出 0 個請求 - `--dry-run` 一個字都不寫,`verify.skipped` 為 `true`,仍回 `ok:true` - `test/` 不在套件白名單裡 兩個 bug 的修復都先把程式改回壞的樣子、確認對應的測試真的會紅,才算數: ``` # 還原 stdio 修復後 ℹ pass 12 ℹ fail 1 AssertionError [ERR_ASSERTION]: 子行程的 stderr 漏出來了 # 還原「病灶取自 stdout」修復後 ℹ pass 13 ℹ fail 1 ``` 另外在真實家目錄(臨時 HOME)上手動跑過 `node bin/tea-sdlc.js install` 的兩條路徑: PATH 上沒有 `tea-sdlc` 時退出碼 1、轉接檔仍在;放上殼之後退出碼 0、`verify.ok` 為 `true`。
jiantw83 added 1 commit 2026-09-18 00:43:07 +00:00
install 寫完轉接檔後,把叫用鏈真的走一遍:轉接檔 → PATH 上的 tea-sdlc → 流程正本。

最脆弱的是中間那一環。套件裝在某個 Node 版本底下,換個版本就找不到了,而轉接檔本身
看起來完全正常——沒有這道驗證,使用者要到第一次打 /sdlc-plan 才發現,那時他已經離開
安裝的心智狀態很久了。所以不是查檔案在不在,而是真的到 PATH 上把 tea-sdlc 找出來執行
一次,再把取回的正本跟套件裡的那一份逐字比對:找不到、叫不動、或叫到的是另一份安裝,
三種都驗得出來。轉接檔則逐一回磁碟讀,比對存在且內容含正確的叫用行。

驗證不碰網路,也與 Gitea 登入、時間追蹤無關,所以無條件執行。

驗不過回 ok:false,但已經寫好的轉接檔一份都不刪。回滾在升級情境下是淨損失:原本有一組
能用的舊轉接檔,覆蓋後驗證失敗再刪掉,使用者就從「有點舊但能用」變成什麼都沒有;何況
最可能的病灶是「PATH 上找不到 tea-sdlc」,那不是轉接檔的問題。

為此 lib 多一個 Failure:有一種失敗是事情做完了、檔案也寫出去了,只是驗不過,那時最該
交出去的正是「已經寫了哪些、哪一段不通」。envelope 形狀不變,只是 {ok:false, error}
旁邊多一個 data,只讀 error.code 的呼叫端照常運作。

--dry-run 不寫入,也就沒有東西可驗,verify 標成 skipped。

Closes #59

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin scheduled this pull request to auto merge when all checks succeed 2026-09-18 00:52:07 +00:00
admin approved these changes 2026-09-18 00:52:11 +00:00
admin merged commit ad980fafc6 into master 2026-09-18 00:52:13 +00:00
admin deleted branch feat/install-verify/main 2026-09-18 00:52:13 +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#62