feat/commit-split-and-pr/main #45

Merged
admin merged 15 commits from feat/commit-split-and-pr/main into master 2026-09-17 08:25:36 +00:00
Member

摘要

/sdlc-feat 的第三段:工作包做完之後,變更被整理成讀得懂的 git 歷史,PR 開出來,碼錶停下。

需求議題

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

工作包議題

#13 — 以 sdlc-feat 分批提交、開立 PR 並停錶

變更內容

新增 scripts/commit-split.js、scripts/pr-create.js,prompts/sdlc-feat.md 加入第三段,
scripts/lib.js 增加 openGitRepo 並修正 parseFlags。15 顆 commit:

commit 內容
feat(commit-split) / test(分批提交) 把變更依類型分批 commit
feat(pr-create) / test(pr-create) 開立 PR 並停錶
feat(sdlc-feat) / test(sdlc-feat-assets) 加入第三段「提交與開立 PR」
fix(commit-split) / test(commit-split) 改名時別漏掉舊檔的刪除,失敗時說出做到哪裡
fix(pr-create) / test(pr-create) 錶停在議題的 repo,並讓重跑不會開出第二顆 PR
refactor(lib) 開啟目標專案 git repo 的那幾行收進 lib
fix(lib) / test(script-contract) 讓 --key=value 的值可以本身以 -- 開頭
test(sdlc-feat-assets) / docs(sdlc-feat) 第三段補上兩個 repo 的區別與重跑的行為

這 15 顆 commit 全部由本 PR 的 commit-split 自己產生。

設計重點

  • 一個 commit 只裝一種類型。 程式碼、測試、文件、雜項各自成批,reviewer 一次只看
    一件事。全部混成一顆「完成工作包」的巨大 commit,等於沒有歷史。
  • 類型多半看得出來,但不是全部。 測試檔就是 test、README 就是 docs;scripts/ 底下
    的改動是新功能還是修 bug 只有做的人知道,那一批吃 --type。這張對照表是純字串規則,
    表格驅動測試。
  • 功能的界線交給人。 --files 讓一次變更橫跨兩個功能時分兩次跑。腳本認得出類型,
    認不出「這兩個檔案算不算同一件事」。
  • 錶停在議題所在的 repo,不是 PR 所在的 repo。 見下。
  • pr-create 只驗描述的形狀,不評價內容。 八段齊全、順序正確、「測試結果」不是整段
    空話——就這三件。這一關擋的是明顯沒跑過就交差,不是去評斷別人的測試寫得好不好。

解決的問題

Code review 抓出四個缺陷,都已修掉並各自補上回歸測試:

1. 停錶停到別人的議題上。 claim 在工作包議題所在的 repo 起錶,而 PR 開在目標
專案
上——議題在需求的 repo,程式碼在 repos 列的那幾個,兩者常常不同。原本用同一個
--repo 同時指 PR 與停錶,會去停一顆不相干的議題(或 404,而且是在 PR 已經建立之後
才拋錯,重跑又會撞上重複的 PR),而自己的錶一直跑下去。新增 --issue-repo。

2. 改名時舊檔的刪除被漏掉。 git diff --name-only 預設偵測改名,只印目的地那一個
路徑。實測:git mv a b 之後,a 的刪除留在 index 沒被提交,而腳本回報 ok: true,
要下一次跑才會發現工作區不乾淨。加 --no-renames。

3. 類型對照表只認 test/。 目標專案用 tests/、spec/、__tests__/,或把
user.test.js 放在被測檔案旁邊,測試就會被併進 feat 那一批。

4. 中途失敗不說做到哪裡。 某一批提交失敗時,前面已經建立的 commit 不會被回捲(那會
動到使用者的歷史,而那幾顆本身是好的),但錯誤原本沒說已經做了什麼,重跑前得自己去翻
git log。

另外修掉一個 lib 的既有瑕疵:parseFlags 對 --key=value 與 --key value 套用同一條
「值不得以 -- 開頭」檢查。等號寫法沒有這個歧義,而 commit 訊息與 PR 描述裡出現 --flag
是常態。這是拿 commit-split 提交它自己時真的撞到的——--body 的內容第一行就是
「--repo 與 --issue-repo 的差別」,整顆 commit 因此做不出來。

影響的功能

  • scripts/branch-prep.js 改用 lib.js 的 openGitRepo,行為不變(既有 28 個測試全過)。
  • parseFlags 的行為改變只放寬等號寫法;空格寫法仍然擋,錯誤訊息現在會指路到等號寫法。
  • test/helpers/temp-repo.js 的 makeTempRepo 補上 git 執行器,與帶遠端的版本對稱。
  • /sdlc-feat 三段到此完整:領取與開工準備、逐項實作、提交與開立 PR。

已知的上游落差:議題 #1 的 story 38 說「PR 七段描述」,但列出的是八個名稱。本 PR 依
列表實作八段(SECTIONS),正本也寫「固定八個段落」。#1 的措辭仍待修正,不在本工作包範圍。

測試結果

在最新 master(1c6fb8aa)之上實際執行 npm test:

ℹ tests 597
ℹ suites 0
ℹ pass 597
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

master 既有 505,本分支新增 92。

commit-split 的類型分類是表格驅動(15 種路徑各一例),分批界線、scope 規則、改名、
中途失敗、--files、--body 各有覆蓋;git 的部分在臨時 repo 上跑真的 git。
pr-create 的八段各有「缺這一段就擋」的案例,空話清單逐項驗過,停錶的順序、跨 repo、
冪等與「錶本來就沒在跑」也都釘住。

實機驗證:改名後工作區確實乾淨且 A+D 都在同一顆 commit 裡;測試檔放在程式碼旁邊
認得出來;中途失敗的訊息確實列出已建立的 commit。最強的一次驗證是本 PR 的 15 顆 commit
全部由 commit-split 自己產生
,而這份描述在送出前先通過了 pr-create 自己的八段檢查。

🤖 Generated with Claude Code

## 摘要 `/sdlc-feat` 的第三段:工作包做完之後,變更被整理成讀得懂的 git 歷史,PR 開出來,碼錶停下。 ## 需求議題 #1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組 ## 工作包議題 #13 — 以 sdlc-feat 分批提交、開立 PR 並停錶 ## 變更內容 新增 `scripts/commit-split.js`、`scripts/pr-create.js`,`prompts/sdlc-feat.md` 加入第三段, `scripts/lib.js` 增加 `openGitRepo` 並修正 `parseFlags`。15 顆 commit: | commit | 內容 | | --- | --- | | `feat(commit-split)` / `test(分批提交)` | 把變更依類型分批 commit | | `feat(pr-create)` / `test(pr-create)` | 開立 PR 並停錶 | | `feat(sdlc-feat)` / `test(sdlc-feat-assets)` | 加入第三段「提交與開立 PR」 | | `fix(commit-split)` / `test(commit-split)` | 改名時別漏掉舊檔的刪除,失敗時說出做到哪裡 | | `fix(pr-create)` / `test(pr-create)` | 錶停在議題的 repo,並讓重跑不會開出第二顆 PR | | `refactor(lib)` | 開啟目標專案 git repo 的那幾行收進 lib | | `fix(lib)` / `test(script-contract)` | 讓 `--key=value` 的值可以本身以 `--` 開頭 | | `test(sdlc-feat-assets)` / `docs(sdlc-feat)` | 第三段補上兩個 repo 的區別與重跑的行為 | **這 15 顆 commit 全部由本 PR 的 `commit-split` 自己產生。** ## 設計重點 - **一個 commit 只裝一種類型。** 程式碼、測試、文件、雜項各自成批,reviewer 一次只看 一件事。全部混成一顆「完成工作包」的巨大 commit,等於沒有歷史。 - **類型多半看得出來,但不是全部。** 測試檔就是 test、README 就是 docs;`scripts/` 底下 的改動是新功能還是修 bug 只有做的人知道,那一批吃 `--type`。這張對照表是純字串規則, 表格驅動測試。 - **功能的界線交給人。** `--files` 讓一次變更橫跨兩個功能時分兩次跑。腳本認得出類型, 認不出「這兩個檔案算不算同一件事」。 - **錶停在議題所在的 repo,不是 PR 所在的 repo。** 見下。 - **`pr-create` 只驗描述的形狀,不評價內容。** 八段齊全、順序正確、「測試結果」不是整段 空話——就這三件。這一關擋的是明顯沒跑過就交差,不是去評斷別人的測試寫得好不好。 ## 解決的問題 Code review 抓出四個缺陷,都已修掉並各自補上回歸測試: **1. 停錶停到別人的議題上。** `claim` 在**工作包議題所在的 repo** 起錶,而 PR 開在**目標 專案**上——議題在需求的 repo,程式碼在 `repos` 列的那幾個,兩者常常不同。原本用同一個 `--repo` 同時指 PR 與停錶,會去停一顆不相干的議題(或 404,而且是在 PR 已經建立**之後** 才拋錯,重跑又會撞上重複的 PR),而自己的錶一直跑下去。新增 `--issue-repo`。 **2. 改名時舊檔的刪除被漏掉。** `git diff --name-only` 預設偵測改名,只印目的地那一個 路徑。實測:`git mv a b` 之後,`a` 的刪除留在 index 沒被提交,而腳本回報 `ok: true`, 要下一次跑才會發現工作區不乾淨。加 `--no-renames`。 **3. 類型對照表只認 `test/`。** 目標專案用 `tests/`、`spec/`、`__tests__/`,或把 `user.test.js` 放在被測檔案旁邊,測試就會被併進 `feat` 那一批。 **4. 中途失敗不說做到哪裡。** 某一批提交失敗時,前面已經建立的 commit 不會被回捲(那會 動到使用者的歷史,而那幾顆本身是好的),但錯誤原本沒說已經做了什麼,重跑前得自己去翻 `git log`。 另外修掉一個 lib 的既有瑕疵:`parseFlags` 對 `--key=value` 與 `--key value` 套用同一條 「值不得以 `--` 開頭」檢查。等號寫法沒有這個歧義,而 commit 訊息與 PR 描述裡出現 `--flag` 是常態。**這是拿 `commit-split` 提交它自己時真的撞到的**——`--body` 的內容第一行就是 「`--repo` 與 `--issue-repo` 的差別」,整顆 commit 因此做不出來。 ## 影響的功能 - `scripts/branch-prep.js` 改用 `lib.js` 的 `openGitRepo`,行為不變(既有 28 個測試全過)。 - `parseFlags` 的行為改變只放寬等號寫法;空格寫法仍然擋,錯誤訊息現在會指路到等號寫法。 - `test/helpers/temp-repo.js` 的 `makeTempRepo` 補上 `git` 執行器,與帶遠端的版本對稱。 - `/sdlc-feat` 三段到此完整:領取與開工準備、逐項實作、提交與開立 PR。 **已知的上游落差**:議題 #1 的 story 38 說「PR 七段描述」,但列出的是八個名稱。本 PR 依 **列表**實作八段(`SECTIONS`),正本也寫「固定八個段落」。#1 的措辭仍待修正,不在本工作包範圍。 ## 測試結果 在最新 master(`1c6fb8aa`)之上實際執行 `npm test`: ``` ℹ tests 597 ℹ suites 0 ℹ pass 597 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ``` master 既有 505,本分支新增 92。 `commit-split` 的類型分類是表格驅動(15 種路徑各一例),分批界線、scope 規則、改名、 中途失敗、`--files`、`--body` 各有覆蓋;git 的部分在臨時 repo 上跑真的 git。 `pr-create` 的八段各有「缺這一段就擋」的案例,空話清單逐項驗過,停錶的順序、跨 repo、 冪等與「錶本來就沒在跑」也都釘住。 實機驗證:改名後工作區確實乾淨且 `A`+`D` 都在同一顆 commit 裡;測試檔放在程式碼旁邊 認得出來;中途失敗的訊息確實列出已建立的 commit。**最強的一次驗證是本 PR 的 15 顆 commit 全部由 `commit-split` 自己產生**,而這份描述在送出前先通過了 `pr-create` 自己的八段檢查。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jiantw83 added 15 commits 2026-09-17 08:24:27 +00:00
一個 commit 只裝一種類型:程式碼、測試、文件、雜項各自成批,reviewer 一次只看一件事,
日後 git log 也讀得懂。全部混成一顆「完成工作包」的巨大 commit,等於沒有歷史。

類型多半看得出來——測試檔就是 test、README 就是 docs——但 scripts/ 底下的改動是新功能
還是修 bug,只有做的人知道,所以那一批由 --type 指定。這張對照表是純字串規則,表格驅動。

scope 單檔用檔名(claim.test.js 的 scope 是 claim,不是 claim.test),多檔用 --scope 的
功能名。描述要有中文:日後回顧時看得懂的是中文,而夾雜英文的專有名詞本來就該保留原文。

--files 讓一次變更橫跨兩個功能時能分兩次跑;--body 讓工具產出的歷史與本 repo 既有的
commit 一樣說明得出「為什麼」。

列變更檔案刻意不用 git status --porcelain:它的前兩欄是狀態碼,而 runGit 會 trim 掉
輸出的前導空白,未 staged 的修改會少掉檔名的第一個字元。
一個 commit 只裝一種類型:程式碼、測試、文件、雜項各自成批,reviewer 一次只看一件事,
日後 git log 也讀得懂。全部混成一顆「完成工作包」的巨大 commit,等於沒有歷史。

類型多半看得出來——測試檔就是 test、README 就是 docs——但 scripts/ 底下的改動是新功能
還是修 bug,只有做的人知道,所以那一批由 --type 指定。這張對照表是純字串規則,表格驅動。

scope 單檔用檔名(claim.test.js 的 scope 是 claim,不是 claim.test),多檔用 --scope 的
功能名。描述要有中文:日後回顧時看得懂的是中文,而夾雜英文的專有名詞本來就該保留原文。

--files 讓一次變更橫跨兩個功能時能分兩次跑;--body 讓工具產出的歷史與本 repo 既有的
commit 一樣說明得出「為什麼」。

列變更檔案刻意不用 git status --porcelain:它的前兩欄是狀態碼,而 runGit 會 trim 掉
輸出的前導空白,未 staged 的修改會少掉檔名的第一個字元。
標題等同分支名:reviewer 在列表上看到的就是分支,兩者對不上會找錯 PR。

描述的段落固定且順序固定,缺一段或順序不對就擋下,不自動補——補出來的段落是編的,
而 reviewer 會把它當成真的。

「測試結果」另外驗一次它不是空話。那一段是 reviewer 唯一能判斷「這東西真的跑過嗎」的
依據,寫「已測試通過」等於沒寫。判斷刻意很窄,只擋「整段只有一行,而那一行是已知的
偷懶寫法」——這一關要擋的是明顯沒跑過就交差,不是去評價別人的測試寫得夠不夠好。
沒有自動化測試時,寫得出可重現的手動驗證步驟就放行。

停錶排在 PR 開出去之後,而且只在 PR 真的建立了才停:工時要記在真的有做事的那段時間上。
錶本來就沒在跑不算失敗(Gitea 對此回 500)——PR 已經開出去了,把整件事報成失敗只會讓人
以為 PR 沒開成而重跑一次。
標題等同分支名:reviewer 在列表上看到的就是分支,兩者對不上會找錯 PR。

描述的段落固定且順序固定,缺一段或順序不對就擋下,不自動補——補出來的段落是編的,
而 reviewer 會把它當成真的。

「測試結果」另外驗一次它不是空話。那一段是 reviewer 唯一能判斷「這東西真的跑過嗎」的
依據,寫「已測試通過」等於沒寫。判斷刻意很窄,只擋「整段只有一行,而那一行是已知的
偷懶寫法」——這一關要擋的是明顯沒跑過就交差,不是去評價別人的測試寫得夠不夠好。
沒有自動化測試時,寫得出可重現的手動驗證步驟就放行。

停錶排在 PR 開出去之後,而且只在 PR 真的建立了才停:工時要記在真的有做事的那段時間上。
錶本來就沒在跑不算失敗(Gitea 對此回 500)——PR 已經開出去了,把整件事報成失敗只會讓人
以為 PR 沒開成而重跑一次。
PR 描述的八個段落與順序寫在正本裡,由 pr-create 擋;正本負責的是腳本擋不住的事:
測試結果要貼實際輸出而不是改寫成一句話、被擋下來時補真的內容而不是為了通過而拼湊、
以及跨兩個功能時用 --files 分兩次跑。

先開 PR 再停錶的理由也寫進去了:工時要記在真的有做事的那段時間上。
PR 描述的八個段落與順序寫在正本裡,由 pr-create 擋;正本負責的是腳本擋不住的事:
測試結果要貼實際輸出而不是改寫成一句話、被擋下來時補真的內容而不是為了通過而拼湊、
以及跨兩個功能時用 --files 分兩次跑。

先開 PR 再停錶的理由也寫進去了:工時要記在真的有做事的那段時間上。
git diff --name-only 預設偵測改名,只印出目的地那一個路徑。來源的刪除因此被漏掉——
留在 index 裡沒被提交,而腳本還回報成功,要等下一次跑才會發現工作區不乾淨。加 --no-renames。

某一批提交失敗時,錯誤現在會列出前面已經建立的那幾顆 commit。不回捲它們:那會動到使用者的
歷史,而那幾顆本身是好的;但一定要說出做到哪裡,否則重跑前得自己去翻 git log。

類型對照表補上目標專案常見的測試擺法:tests/、spec/、__tests__/,以及放在被測檔案旁邊的
user.test.js。先前只認 test/,目標專案的測試會被併進 feat 那一批。
git diff --name-only 預設偵測改名,只印出目的地那一個路徑。來源的刪除因此被漏掉——
留在 index 裡沒被提交,而腳本還回報成功,要等下一次跑才會發現工作區不乾淨。加 --no-renames。

某一批提交失敗時,錯誤現在會列出前面已經建立的那幾顆 commit。不回捲它們:那會動到使用者的
歷史,而那幾顆本身是好的;但一定要說出做到哪裡,否則重跑前得自己去翻 git log。

類型對照表補上目標專案常見的測試擺法:tests/、spec/、__tests__/,以及放在被測檔案旁邊的
user.test.js。先前只認 test/,目標專案的測試會被併進 feat 那一批。
claim 在工作包議題上起錶,而 PR 開在目標專案上——議題在需求的 repo,程式碼在 repos 列的
那幾個,兩者常常不是同一個。先前用同一個 --repo 同時指 PR 與停錶,停到的會是別人的議題
(或 404 而在 PR 已建立之後才拋錯),而自己的錶還在跑。新增 --issue-repo,預設與 --repo 相同。

--index 與 --base 改為必填:停錶是這一步的一部分,忘了給會讓工時算不準;而目標專案的開發
分支可能叫 master、main 或 develop,猜錯會開到不存在的 base。

重跑先查同一個 head 有沒有開著的 PR,有就回傳它並把 created 設為 false,然後照樣停錶——
那一步可能正是上次中斷的地方。先前重跑會撞上 Gitea 的 422,而那個錯誤看不出 PR 其實已經開好。

停錶的 500 改為只在訊息確實提到 stopwatch 時才視為「本來就沒在跑」,免得把真的伺服器錯誤
吞掉;未經證實的 409 那一支拿掉。測試結果的空話檢查改成整段每一行都是空話才擋,段落也改用
行首標題切,描述裡引用到「## 測試結果」這幾個字不會再讓檢查看錯地方。
claim 在工作包議題上起錶,而 PR 開在目標專案上——議題在需求的 repo,程式碼在 repos 列的
那幾個,兩者常常不是同一個。先前用同一個 --repo 同時指 PR 與停錶,停到的會是別人的議題
(或 404 而在 PR 已建立之後才拋錯),而自己的錶還在跑。新增 --issue-repo,預設與 --repo 相同。

--index 與 --base 改為必填:停錶是這一步的一部分,忘了給會讓工時算不準;而目標專案的開發
分支可能叫 master、main 或 develop,猜錯會開到不存在的 base。

重跑先查同一個 head 有沒有開著的 PR,有就回傳它並把 created 設為 false,然後照樣停錶——
那一步可能正是上次中斷的地方。先前重跑會撞上 Gitea 的 422,而那個錯誤看不出 PR 其實已經開好。

停錶的 500 改為只在訊息確實提到 stopwatch 時才視為「本來就沒在跑」,免得把真的伺服器錯誤
吞掉;未經證實的 409 那一支拿掉。測試結果的空話檢查改成整段每一行都是空話才擋,段落也改用
行首標題切,描述裡引用到「## 測試結果」這幾個字不會再讓檢查看錯地方。
「路徑不是 repo 就報 NOT_A_GIT_REPO」加上「把 cwd 綁進 runGit」原本在 branch-prep 與
commit-split 各寫一份。錯誤碼要一致,而這件事寫第三遍就該收起來了。
commit 訊息與 PR 描述裡出現 --flag 是常態,而原本的檢查對兩種寫法一視同仁:值只要以
-- 開頭就報「需要一個值」。空格分隔的寫法確實分不出「值」與「打錯的 flag」,但等號寫法
沒有這個歧義,不該一起擋掉。

這是實際撞到的:拿 commit-split 提交它自己時,--body 的內容第一行就是「--repo 與
--issue-repo 的差別」,整個 commit 因此做不出來。空格寫法的錯誤訊息現在會指路到等號寫法。
commit 訊息與 PR 描述裡出現 --flag 是常態,而原本的檢查對兩種寫法一視同仁:值只要以
-- 開頭就報「需要一個值」。空格分隔的寫法確實分不出「值」與「打錯的 flag」,但等號寫法
沒有這個歧義,不該一起擋掉。

這是實際撞到的:拿 commit-split 提交它自己時,--body 的內容第一行就是「--repo 與
--issue-repo 的差別」,整個 commit 因此做不出來。空格寫法的錯誤訊息現在會指路到等號寫法。
說明 --repo 與 --issue-repo 的差別、--base 要明講不讓腳本猜、重跑不會開出第二顆 PR,
以及提交中途失敗時不要自己回捲歷史。
說明 --repo 與 --issue-repo 的差別、--base 要明講不讓腳本猜、重跑不會開出第二顆 PR,
以及提交中途失敗時不要自己回捲歷史。
admin approved these changes 2026-09-17 08:25:23 +00:00
admin merged commit 03c368b6de into master 2026-09-17 08:25:36 +00:00
admin deleted branch feat/commit-split-and-pr/main 2026-09-17 08:25:37 +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#45