feat/claim-and-branch-prep/main #37

Merged
admin merged 6 commits from feat/claim-and-branch-prep/main into master 2026-09-17 07:10:15 +00:00
Member

摘要

/sdlc-feat 的第一段:把一顆工作包安全地認領下來、起錶、備妥開工的分支。
這一段不改任何一行程式碼,它只負責讓後面的實作有個乾淨的起點。

需求議題

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

工作包議題

#11 — 以 sdlc-feat 領取工作包、起錶並備妥分支

變更內容

commit 內容
fix(lib) git 的進度訊息不再漏到 stderr,並讓前置檢查交回帳號
feat(claim) 領取工作包,上鎖、貼標籤、起錶
feat(branch-prep) 依命名規則備妥開工的分支
feat(流程正本) 新增 sdlc-feat 與它的第一段
test(helpers) 加上「有遠端」的臨時 repo,並收掉三份重複的 git 執行器
test(sdlc-feat) 覆蓋領取鎖決策表、分支命名規則與三處不覆蓋他人進度

新增 scripts/claim.js、scripts/branch-prep.js、prompts/sdlc-feat.md。

設計重點

  • 領取鎖用 assignee 加標籤,不用碼錶。 Gitea 只讓人讀自己的錶,看不到別人的,
    拿它當鎖會漏判。碼錶在這裡只有一個用途:發現自己忘了停掉上一顆。
  • 四種狀態,三擋一放行。 「自己已認領但沒起錶」算沒有鎖——那正是中斷後重跑的情形,
    重跑不會產生第二把鎖。兩種碼錶的錯誤碼分開,因為使用者的下一步不同。
  • 會擋的判斷全部做在任何寫入之前,包含「repo 上有沒有『進行中』標籤」。
    錶留到最後才起:前面任一步失敗時,不該留下一顆還在跑的碼錶。
  • --dry-run 走同一條路,只停在寫入之前。 它印出的是這一顆此刻真正缺的那幾步,
    不是一份手寫的固定清單——後者會跟實作走鐘,也說不出「這顆已經是你的了」。
  • 需求描述由議題標題翻譯是 agent 的事,腳本只收 --slug 並驗格式(英文 kebab、
    ≤40 字元)。中文分支名會讓 CI 與 URL 出問題,擋在建立之前。

解決的問題

Code review 在落地過程中抓出三個實際缺陷,都已修掉並各自補上回歸測試:

1. ls-remote 的樣式比對吃的是 ref 尾段。 git ls-remote --heads origin main
會匹配到 refs/heads/feat/x/main——而本 repo 的命名慣例讓每一支分支都以 /main
結尾,任何拿 main 當開發分支的專案,都會把別人的功能分支誤認成自己的來源分支。
改用全名 refs/heads/<ref> 比對。

2. 工作區不乾淨時沒擋。 未提交的改動與未追蹤的檔案會跟著 checkout 走進這顆工作包的
分支、混進 commit;而目標分支已在遠端時,git 會拖到 checkout 那一步才拒絕,屆時
fetch 與 merge 都已經跑掉了,而 --dry-run 才剛印出一份漂亮的五步計畫。改成開工前
就擋,並把 git 回報的檔案原樣交給使用者決定。

3. git 的進度訊息漏到 stderr(既有缺口)。 execFileSync 預設讓 stderr 繼承給父行程,
而 git 把 checkout/fetch 的訊息全寫在那裡。腳本的輸出契約是「stdout 一行 JSON、
stderr 乾淨」,漏出去的話呼叫端還得自己分辨哪幾行是雜訊。

影響的功能

  • lib.js 的 preflight 回傳形狀由 repo 資訊改為 {repo, user}:第二層本來就問過
    「我是誰」卻把答案丟掉,害呼叫端要再問一次。原本沒有任何呼叫端在用它的回傳值。
  • runGit 的 stderr 改為收進來,影響所有未來走 git 的腳本(目前只有 branch-prep)。
  • 尚未實作的 #12/#13 會接在這一段之後:逐項實作並勾選待辦、分批提交並開 PR 停錶。

本分支與 master 的 lib.js 各自改了不同區域(master 上新增了 RawText、
packageManifest、promptsDir),已做三方合併,無衝突,兩邊的改動都在。

測試結果

在合併 master 之後的完整樹上實際執行 npm test(master 既有 386 + 本分支新增 60):

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

領取鎖的四種狀態各一例,且每一種擋的情況都驗「一個字都沒寫進 Gitea」。分支命名是純字串
規則,表格驅動涵蓋開發分支三種寫法與功能分支兩層,加上中文、超長、大寫、底線、連續連
字號的輸入驗證。git 的部分在臨時 repo(bare origin + 工作用 clone)上跑真的 git。

另以實機演練確認:ls-remote 誤判已修正、工作區不乾淨會在動手前擋下且不留半途狀態、
branch-prep 跑完 stderr 為空、目標專案工作區乾淨無殘留狀態檔。

未能實機貫通的一段:claim 對本 repo 跑不到底——repo 尚未開啟時間追蹤
(TIME_TRACKER_OFF),且上面只有 ready-for-agent 一個標籤、沒有「進行中」
(LABEL_NOT_FOUND)。兩者都是本次設計中「明確錯誤碼指出該去哪裡改」的預期行為,
但領取流程的實機貫通要等這兩項設定就緒後才驗得了。

🤖 Generated with Claude Code

## 摘要 `/sdlc-feat` 的第一段:把一顆工作包安全地認領下來、起錶、備妥開工的分支。 這一段不改任何一行程式碼,它只負責讓後面的實作有個乾淨的起點。 ## 需求議題 #1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組 ## 工作包議題 #11 — 以 sdlc-feat 領取工作包、起錶並備妥分支 ## 變更內容 | commit | 內容 | | --- | --- | | `fix(lib)` | git 的進度訊息不再漏到 stderr,並讓前置檢查交回帳號 | | `feat(claim)` | 領取工作包,上鎖、貼標籤、起錶 | | `feat(branch-prep)` | 依命名規則備妥開工的分支 | | `feat(流程正本)` | 新增 `sdlc-feat` 與它的第一段 | | `test(helpers)` | 加上「有遠端」的臨時 repo,並收掉三份重複的 git 執行器 | | `test(sdlc-feat)` | 覆蓋領取鎖決策表、分支命名規則與三處不覆蓋他人進度 | 新增 `scripts/claim.js`、`scripts/branch-prep.js`、`prompts/sdlc-feat.md`。 ## 設計重點 - **領取鎖用 assignee 加標籤,不用碼錶。** Gitea 只讓人讀自己的錶,看不到別人的, 拿它當鎖會漏判。碼錶在這裡只有一個用途:發現自己忘了停掉上一顆。 - **四種狀態,三擋一放行。** 「自己已認領但沒起錶」算沒有鎖——那正是中斷後重跑的情形, 重跑不會產生第二把鎖。兩種碼錶的錯誤碼分開,因為使用者的下一步不同。 - **會擋的判斷全部做在任何寫入之前**,包含「repo 上有沒有『進行中』標籤」。 錶留到最後才起:前面任一步失敗時,不該留下一顆還在跑的碼錶。 - **`--dry-run` 走同一條路,只停在寫入之前。** 它印出的是這一顆此刻真正缺的那幾步, 不是一份手寫的固定清單——後者會跟實作走鐘,也說不出「這顆已經是你的了」。 - **需求描述由議題標題翻譯是 agent 的事**,腳本只收 `--slug` 並驗格式(英文 kebab、 ≤40 字元)。中文分支名會讓 CI 與 URL 出問題,擋在建立之前。 ## 解決的問題 Code review 在落地過程中抓出三個實際缺陷,都已修掉並各自補上回歸測試: **1. `ls-remote` 的樣式比對吃的是 ref 尾段。** `git ls-remote --heads origin main` 會匹配到 `refs/heads/feat/x/main`——而本 repo 的命名慣例讓**每一支**分支都以 `/main` 結尾,任何拿 `main` 當開發分支的專案,都會把別人的功能分支誤認成自己的來源分支。 改用全名 `refs/heads/<ref>` 比對。 **2. 工作區不乾淨時沒擋。** 未提交的改動與未追蹤的檔案會跟著 checkout 走進這顆工作包的 分支、混進 commit;而目標分支已在遠端時,git 會拖到 checkout 那一步才拒絕,屆時 `fetch` 與 `merge` 都已經跑掉了,而 `--dry-run` 才剛印出一份漂亮的五步計畫。改成開工前 就擋,並把 git 回報的檔案原樣交給使用者決定。 **3. git 的進度訊息漏到 stderr(既有缺口)。** `execFileSync` 預設讓 stderr 繼承給父行程, 而 git 把 `checkout`/`fetch` 的訊息全寫在那裡。腳本的輸出契約是「stdout 一行 JSON、 stderr 乾淨」,漏出去的話呼叫端還得自己分辨哪幾行是雜訊。 ## 影響的功能 - `lib.js` 的 `preflight` 回傳形狀由 repo 資訊改為 `{repo, user}`:第二層本來就問過 「我是誰」卻把答案丟掉,害呼叫端要再問一次。原本沒有任何呼叫端在用它的回傳值。 - `runGit` 的 stderr 改為收進來,影響所有未來走 git 的腳本(目前只有 `branch-prep`)。 - 尚未實作的 `#12`/`#13` 會接在這一段之後:逐項實作並勾選待辦、分批提交並開 PR 停錶。 **本分支與 master 的 `lib.js` 各自改了不同區域**(master 上新增了 `RawText`、 `packageManifest`、`promptsDir`),已做三方合併,無衝突,兩邊的改動都在。 ## 測試結果 在**合併 master 之後**的完整樹上實際執行 `npm test`(master 既有 386 + 本分支新增 60): ``` ℹ tests 446 ℹ suites 0 ℹ pass 446 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ``` 領取鎖的四種狀態各一例,且每一種擋的情況都驗「一個字都沒寫進 Gitea」。分支命名是純字串 規則,表格驅動涵蓋開發分支三種寫法與功能分支兩層,加上中文、超長、大寫、底線、連續連 字號的輸入驗證。git 的部分在臨時 repo(bare origin + 工作用 clone)上跑真的 git。 另以實機演練確認:`ls-remote` 誤判已修正、工作區不乾淨會在動手前擋下且不留半途狀態、 `branch-prep` 跑完 stderr 為空、目標專案工作區乾淨無殘留狀態檔。 **未能實機貫通的一段**:`claim` 對本 repo 跑不到底——repo 尚未開啟時間追蹤 (`TIME_TRACKER_OFF`),且上面只有 `ready-for-agent` 一個標籤、沒有「進行中」 (`LABEL_NOT_FOUND`)。兩者都是本次設計中「明確錯誤碼指出該去哪裡改」的預期行為, 但領取流程的實機貫通要等這兩項設定就緒後才驗得了。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jiantw83 added 6 commits 2026-09-17 07:08:04 +00:00
兩件都是 branch-prep 與 claim 落地時才浮現的既有缺口:

git 把 checkout/fetch 的進度訊息全寫在 stderr,而 execFileSync 預設讓 stderr 直接
繼承給父行程。腳本的輸出契約是「stdout 一行 JSON、stderr 乾淨」,不收的話呼叫端還得
自己分辨哪幾行是雜訊。改成收進來;失敗時這些內容仍讀得到,錯誤訊息不會因此變模糊。

preflight 的第二層本來就打過 /user 問「我是誰」,卻只把答案丟掉,害呼叫端要再問一次。
改為連同 repo 資訊一起交回去。原本沒有任何呼叫端在用它的回傳值,形狀改變是安全的。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
鎖用 assignee 加標籤,不用碼錶——Gitea 只讓人讀自己的錶,看不到別人的,拿它當鎖會漏判。
碼錶在這裡只有一個用途:發現自己忘了停掉上一顆。

四種狀態的處置:他人已認領擋;自己的錶跑在本議題擋;跑在別的議題擋;沒有鎖放行。
後者包含「自己已認領但沒起錶」,那正是中斷後重跑的情形,重跑不會產生第二把鎖。
兩種碼錶的錯誤碼分開,因為使用者的下一步不同——一個是「你已經在做了」,
另一個是「你忘了停掉那一顆」。

會擋的判斷全部做在任何寫入之前,包含「repo 上有沒有『進行中』標籤」:本 plugin 不自動
建標籤,缺了就整件事不做,不要只設一半的鎖。錶則留到最後才起,前面任一步失敗時不該
留下一顆還在跑的碼錶。

--dry-run 走同一條路,只停在寫入之前。它印出的是這一顆此刻真正缺的那幾步,
不是一份手寫的固定清單——後者會跟實作走鐘,也說不出「這顆已經是你的了」。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
命名規則照議題 #1:從開發分支長出 {類型}/{需求描述}/main,從功能分支長出時前兩段沿用
來源,子分支才會留在同一棵樹下。需求描述由議題標題翻譯,那是 agent 的事,所以這裡只收
--slug 並驗格式:英文 kebab、≤40 字元,中文分支名會讓 CI 與 URL 出問題。

三處「不弄丟別人的東西」,而且全部在任何 git 寫入之前判斷完:

- 工作區不乾淨就不動手。未提交的改動與未追蹤的檔案都會跟著 checkout 走到新分支上,
  混進這顆工作包的 commit;目標分支已存在時,git 還會拖到 checkout 那一步才拒絕,
  屆時 fetch 與 merge 都已經跑掉了。
- 來源分支在遠端已存在時 pull 而不是重建。
- 目標分支已存在時接上去而不是從來源蓋掉。

遠端分支的比對用全名 refs/heads/<ref>:ls-remote 的樣式比對吃的是 ref 尾段,而本 repo
的命名慣例讓每一支分支都以 /main 結尾——用短名比對,拿 main 當開發分支的專案會把
feat/x/main 誤認成 main,整個判成「遠端已經有這一支」。

算與做分成兩段,--dry-run 印出的就是真正將執行的那幾行 git,不另外維護一份描述。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
第一段把工作包安全地認領下來、起錶、備妥分支,不改任何一行程式碼——它只負責讓後面的
實作有個乾淨的起點。

正本負責三件腳本做不到的事:讀完工作包後,未處理留言不是 0 就先停下來建議整併;
新分支從哪裡長出來要問過使用者,一次一題、附理由、永遠留手動輸入;議題標題翻成英文
kebab 也是 agent 的事,腳本只驗格式。

領取鎖的四種狀態連同放行那一種都列在表上,缺標籤則另外交代——它是 repo 的前置條件,
不是鎖的第五種狀態,混在一起會讓決策表說不清楚。

工作包跨多個 repo 時逐一確認要在哪幾個開分支,分支名在每個 repo 都相同。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
branch-prep 的重點行為都繞著遠端打轉——來源分支在遠端已存在時要 pull 而不是重建、
目標分支已存在時不能覆蓋他人進度。這些事沒有遠端就驗不出來,所以補一個 bare origin
加工作用 clone,並附 pushFromElsewhere 模擬「別人推了東西上去」。

順手把 makeTempRepo 與新 helper 各自重寫一次的 git 執行器與 seed 收成共用的兩支。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
領取鎖的四種狀態各一例,而且每一種擋的情況都驗「一個字都沒寫進 Gitea」——擋下來卻已經
改了一半,比直接放行更難收拾。

分支命名是純字串規則,表格驅動:開發分支三種寫法、功能分支兩層,加上中文、超長、大寫、
底線與連續連字號的輸入驗證。

git 的部分在臨時 repo 上跑真的 git,釘住三件事後來由 code review 抓出來的實際缺陷:
遠端分支的比對必須用全名(否則 feat/x/main 會冒名頂替 main)、工作區不乾淨要在動手前
就擋、來源分支與遠端分歧要回可區分的錯誤碼而不是 git 的原始訊息。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin approved these changes 2026-09-17 07:10:12 +00:00
admin merged commit 6cf6a1e36e into master 2026-09-17 07:10:15 +00:00
admin deleted branch feat/claim-and-branch-prep/main 2026-09-17 07:10:15 +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#37