Files
tea-sdlc/prompts/sdlc-feat.md
T
jiantw83andClaude Opus 5 32edd65962 docs(sdlc-feat): 第三段收尾指向 pr-watch 與手動清理
PR 開出去之後流程就斷在那裡,使用者不會知道有東西可以查現況、也不會知道工作樹會被
自動清掉。收尾補一步,把兩支腳本講給使用者聽,並明講「多久跑一次由他自己排」。

邊界同時擋住兩件事:agent 不自己反覆跑 pr-watch,也不因為它建議了 run-sdlc-fix 就
自己去跑 /sdlc-fix——流程只由使用者明確叫用。

議題 #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 16:59:30 +08:00

352 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: sdlc-feat
description: 僅由 /sdlc-feat 指令叫用。領取一顆工作包、備妥工作樹、起錶,逐項實作並勾選待辦,最後分批提交並開立 PR。
# sdlc-feat
拿一顆工作包,從領取到開出 PR。
第一段**領取與開工準備**:把工作包安全地認領下來,備妥一棵屬於它的工作樹,然後開始計時。
這一段不改任何一行程式碼——它只負責讓後面的實作有個乾淨的起點。
第二段**逐項實作**:一項一項把待辦做完並即時勾選,讓議題頁的進度條隨時反映真實狀態。
第三段**提交與開立 PR**:把變更整理成讀得懂的歷史,開出 PR,停錶。
這份檔案是流程正本。各平台的轉接檔只是指回這裡,不要把規則抄過去。
## 輸入
一個工作包議題編號。
## 第一段:領取與開工準備
### 1. 讀工作包
```
node scripts/wp-extract.js --repo <owner/name> --index <編號>
```
拿到的是結構化欄位:待辦與它自己的驗收、範圍邊界、介面契約、相依、repo 列表。
不必再讀整份議題全文。
**先看 `未處理留言數`。** 只要不是 0,就代表議題描述可能是過期的——留言裡有決策還沒被
整併回描述。這時**先停下來**告訴使用者有幾則未整併的留言,建議先執行 `/sdlc-sync`
把它們整併回描述,再回來實作。使用者堅持要繼續就繼續,但要記下這件事,
並在最後的 PR 描述裡註明「實作基於未整併留言前的描述」。
**再看 `相依.depends`。** 裡面還有沒關閉的議題,代表這顆的前置還沒做完。照樣先說出來,
讓使用者決定要不要現在做。
### 2. 領取工作包
先試跑,看清楚會做什麼:
```
node scripts/claim.js --repo <owner/name> --index <編號> --dry-run
```
確認無誤後拿掉旗標再跑一次。放行時它會設 assignee、貼「進行中」標籤——這兩件事一起
構成領取鎖。**錶不在這一步起**:它等工作樹建好之後才起(第 6 步)。工作樹建立失敗會
中止整個領取,錶要是先起了,使用者就被計了一段什麼都沒做的時間。
領取鎖有四種狀態,三種擋、一種放行。被擋下來時**不要繞過去**,照著錯誤碼告訴使用者
發生什麼事、下一步是什麼:
| 狀態 | 錯誤碼 | 下一步 |
| --- | --- | --- |
| 別人已經認領這顆 | `CLAIMED_BY_OTHER` | 改領別顆,或先跟對方確認 |
| 你的錶已經跑在這顆上 | `STOPWATCH_ON_THIS_ISSUE` | 這顆你正在做;要重新計時請先手動停錶 |
| 你的錶跑在別的議題上 | `STOPWATCH_ON_OTHER_ISSUE` | 多半是忘了停上一顆;先去停掉再回來 |
被錶擋下來時**要順帶說明停錶不會動到既有的工作樹**:碼錶只管時間、工作樹只管檔案。
不講清楚,使用者會以為停錶等於放棄那顆工作包,於是寧可不停——工時就記到別顆去了。
| 沒有鎖 | —— | 放行。自己已認領但沒起錶也算沒有鎖,那正是中斷後重跑的情形 |
碼錶一律由使用者自己停。哪一段時間該記在哪顆議題上只有他知道,代勞會把工時記錯地方。
鎖以外還有一個前置條件:repo 上要有「進行中」標籤。缺了會得到 `LABEL_NOT_FOUND`,
請使用者自己去建立——**不要自己建**,標籤體系不該在多個 repo 之間長出雜草。
### 3. 問來源分支
**一次問一題。** 新分支要從哪裡長出來,只有使用者知道,不要替他決定。
給兩個選項,並附上你判斷的理由:
- **建議** — 你的答案。多數情況是開發分支(`master`/`main`/`develop`);
但若這顆工作包明顯是某個既有功能分支的一部分,就建議那一支,並說明為什麼。
- **手動輸入** — 讓使用者自己填分支名。
不論哪一種,來源分支都必須**已經在遠端上**:工作樹的起點一律取自 `origin/{來源分支}`。
### 4. 把議題標題翻成英文
分支名的中段要用英文,中文會讓 CI 與 URL 出問題。把工作包議題的標題翻成
**小寫英文 kebab、40 字元以內**,例如「建立工作包的抽取契約」→ `wp-extract-contract`。
翻譯要保留原意而不是逐字直譯,寧可用一個更短的說法,也不要把長句截斷成看不懂的字串。
### 5. 備妥工作樹
工作樹開在**工作包的 `repos` 列出的那些 repo** 上,不是開在本 plugin 的目錄裡。
`repos` 只有一顆就用那一顆;**有多顆時逐一確認**要在哪幾個開分支,
再對每一個各跑一次 `branch-prep`,分支名在每個 repo 都相同。
```
node scripts/branch-prep.js --repo <owner/name> --path <目標專案路徑> \
--source <來源分支> --slug <英文-kebab> [--type feat] --dry-run
```
`--type` 只在來源是開發分支時要給(`feat`/`fix`/`chore`…);從功能分支長出時,
類型與需求描述沿用來源,不必也不能再指定。
試跑會印出將執行的 git 指令、算出來的分支名與工作樹路徑。確認無誤後拿掉旗標再跑一次。
**不在原地切換分支,一律開一棵獨立的工作樹。** 每顆工作包有自己的目錄、自己的建置
產物、自己的未提交變更,彼此看不見對方。這件事對 agent 特別重要:它是非同步的,
可能在分支已經被切走之後才去讀檔,而它**不會察覺**自己讀到的是別顆工作包的內容——
產出看起來完全合理,只是接錯了上下文。
**工作樹一律建立,沒有例外。** 建不起來就照實中止,**不要改成在原地切分支**:
使用者會以為自己在隔離環境裡,其實在原地改。
工作樹路徑由 `owner/repo/分支名` 推導而得,印在輸出的 `worktree` 欄位。
**後面幾段的實作、測試與提交都在那棵工作樹裡做**,不要回到主工作區動手。
它保證三件事:
- **起點一律是遠端的來源分支**(`origin/{來源分支}`),不是本機同名分支——後者可能
落後好幾天。遠端沒有那一支時得到 `SOURCE_NOT_FOUND`,把訊息念給使用者,讓他決定
是先把來源分支推上去,還是改指定一個別的來源——**不要自己換一個**。
- **目標分支已經存在時接上去而不是蓋掉**;工作樹已經在了就沿用,不動裡面還沒提交的東西。
- **失敗時不留半成品**:不會出現有分支沒工作樹、或有工作樹沒分支的狀態。
推導出的路徑被別的東西佔住時(`WORKTREE_PATH_TAKEN`,多半是別的 clone 留下的),
把路徑念給使用者,請他確認裡面沒有還沒保存的東西再移除——**不要自己刪**。
工作樹是乾淨的:**沒有安裝依賴,也沒有任何建置產物**,`.env` 這類機密檔案更不會被
複製過去。把輸出的 `提示.安裝指令` 念給使用者,機密檔案請他自己放一份。
### 6. 起錶
工作樹建好之後才起錶:
```
node scripts/timer.js --repo <owner/name> --index <編號> --dry-run
```
確認無誤後拿掉旗標再跑一次。錶已經跑在這顆議題上時它什麼都不做——那正是中斷後重跑
的情形,重新起錶會把已經累積的時間切成兩段。
### 7. 回報
印出一份開工前的現況,不寫回議題:
- 工作包標題與網址、這一顆有幾項待辦
- 認領結果(是否本來就是自己的)、碼錶已起
- 來源分支、新分支名、分支是新建還是接上既有
- 工作樹路徑,以及它是乾淨的、要先跑哪一行安裝指令
- 未處理留言數與未關閉的先決議題(若有)
## 第二段:逐項實作
### 8. 認出語言,讀規則正本
改任何一個檔案之前,先依專案檔認出這是什麼語言,再讀兩份規則正本:
- `references/coding-standards.md` — 分層判定與各層要寫什麼註解
- `references/comment-styles.md` — 該語言的註解格式
規則以那兩份為準,這裡不複述——抄過來就會有兩份各自演化的規則。只強調兩件最常被跳過的:
**認不出語言就停下來問、不要猜**,以及**規則只存在於本 plugin 裡**,
不寫進目標專案的任何檔案。
屬性的資料範例**優先從 MCP 取得**;取不到就以邏輯推理,並照 `comment-styles.md` 的寫法
在註解裡註明「由邏輯推理、未經驗證」。這句註明不能省,否則後面的人會照著沒對過的格式寫解析。
### 9. 一項一項做
**改的是工作樹裡的檔案**,路徑就是 `branch-prep` 印出來的 `worktree`,不是主工作區——
主工作區可能停在別的分支上,在那裡動手會把改動落到別顆工作包的分支去。
依 `wp-extract` 給的 `待辦` 順序做。每一項的做法:
1. 讀它底下的 `驗收`——那是「這一項做到什麼程度算完成」的定義。
2. 實作,照 `coding-standards.md` 的分層與註解規範。
3. 這一項的驗收都成立了,才算完成。
**過程不打斷。** 不要每做完一項就問一次「可以繼續嗎」——二十項待辦不該按二十次同意。
只印進度,例如 `[3/12] 已完成:解析九個段落`。
真正需要停下來問的只有三種:語言認不出來、待辦的意思有歧義、做下去會超出工作包的
`範圍邊界`。除此之外一路做完。
### 10. 做完一項就勾一項
```
node scripts/issue-update.js --repo <owner/name> --index <編號> \
--tick '<wp-extract 給的那一行 raw>' --section 待辦
```
`--tick` 收的是抽取契約交出的**那一整行 `raw`**,逐字包含縮排;它只把那一行的方框換成
已勾,議題其餘部分一字不動。待辦與它底下的驗收各自是一行,各勾各的。
`--section` 是那一項所在的段落:勾 `待辦` 裡的項目就給 `待辦`,勾 `整體驗收` 就給
`整體驗收`。**一定要給**——兩個段落常有一模一樣的一句話,不給就分不出要勾哪一個。
**不要自己拼那一行**,一律用 `wp-extract` 給的 `raw`。四種擋下來的情況都照實說,不要繞過去:
| 錯誤碼 | 意思 | 下一步 |
| --- | --- | --- |
| `RAW_NOT_FOUND` | 議題上找不到這一行 | 手上的抽取結果過期了(議題被改過);重跑 `wp-extract` 再試 |
| `RAW_AMBIGUOUS` | 這一行在同一個段落裡出現不只一次 | 分不出要勾哪個;請使用者把重複的那幾項改寫成看得出差別的說法 |
| `NOT_A_CHECKBOX` | 議題上那一項沒有方框 | 請使用者把它補成 `- [ ] …`;**不要自己改寫議題** |
| `SECTION_NOT_FOUND` | `--section` 的段落不存在 | 對照 `wp-extract` 的輸出確認段落名稱 |
**不要為了勾選在議題上留留言。** 勾選改的是 body,進度條自己會動;逐項留言會把議題洗版,
reviewer 得從一堆「已完成第 N 項」裡找真正的討論。
### 11. 中斷後重跑
進度完全由 Gitea 上的勾選狀態推導,**不看任何本機檔案**。重跑這一段時:
1. 重新 `wp-extract`,看 `待辦` 裡哪些 `done` 已經是 `true`。
2. 從第一個還沒勾的接下去做。
3. 已經勾過的項目再 `--tick` 一次是安靜的 no-op(回傳 `已經勾過: true`,不發 PATCH),
所以不確定某一項有沒有勾到時,直接再勾一次即可,不必先查。
### 12. 回報
全部待辦完成後印一份小結,不寫回議題:
- 幾項待辦、幾項驗收,全部勾選完成
- 改了哪些檔案,各屬於哪一層
- 有沒有待辦因為 `範圍邊界` 而被刻意不做
- 語言與註解格式用的是哪一份對照
- **哪些資料範例是推理來的**(MCP 取不到的那些),讓 reviewer 知道哪幾個格式還沒人對過
## 第三段:提交與開立 PR
### 13. 分批提交
全部待辦都勾完之後才進這一段。變更依類型分批:
```
node scripts/commit-split.js --path <工作樹路徑> --type feat \
--subject '<繁中描述>' [--scope <功能名>] --dry-run
```
`--path` 給的是第一段建出來的那棵**工作樹**——commit 要落在它的分支上。
`--type` 是**這次程式碼變更**的類型(`feat`/`fix`/`refactor`…);測試、文件與設定檔
由腳本自己認出來,各自成批,不必也不能指定。`--body` 寫「為什麼這樣做」,那一段會接在
每一顆 commit 的首行之後——本 repo 的歷史靠它讀得懂。
某一批提交失敗時,錯誤會列出**前面已經建立的那幾顆 commit**。修掉原因之後重跑即可,
已建立的不會重複;不要自己去回捲歷史。
`--scope` 只在某一批有多個檔案時才需要:單檔那批的 scope 就是檔名。試跑會印出將建立的
每一顆 commit 與它各自的檔案,確認無誤後拿掉旗標再跑一次。
**描述用繁體中文。** 日後回顧時看得懂的是中文;夾雜英文的專有名詞(函式名、旗標名)
保留原文即可。
一次變更橫跨兩個不相干的功能時,用 `--files` **分兩次跑**:
```
node scripts/commit-split.js ... --files scripts/claim.js,test/claim.test.js
```
一顆 commit 的描述只說得清楚一件事,硬湊在一起就失去了分批的意義。
### 14. 寫 PR 描述
固定八個段落,順序不能換——reviewer 每次都在同一個位置找到要找的資訊:
1. **摘要** — 這個 PR 做完之後,什麼事變得可能。
2. **需求議題** — `#<編號>`。
3. **工作包議題** — `#<編號>`。
4. **變更內容** — 改了什麼。commit 一覽加上新增/修改的檔案。
5. **設計重點** — 為什麼這樣做。取捨與理由,不是實作步驟的複述。
6. **解決的問題** — 這次修掉了什麼。有具體觸發條件的就寫出來。
7. **影響的功能** — 誰會被影響、既有行為有沒有改變。
8. **測試結果** — 見下。
**「測試結果」放實際跑過的輸出**,原樣貼上,不要改寫成「已測試通過」——那句話看不出
跑過什麼,reviewer 沒辦法據以判斷。沒有自動化測試時,寫出 reviewer 自己能重現的手動
驗證步驟(跑什麼指令、看到什麼算對)。
`pr-create` 會擋下缺段落、順序不對、以及測試結果只有空話的描述。被擋下來時**補真的內容**,
不要為了通過而拼湊。
### 15. 開 PR 並停錶
```
node scripts/pr-create.js --repo <目標專案 owner/name> --head <分支名> \
--base <來源分支> --body-file <描述檔> \
--issue-repo <工作包議題的 owner/name> --index <工作包編號> --dry-run
```
`--repo` 是**程式碼所在的 repo**(PR 開在那裡),`--issue-repo` 是**工作包議題所在的
repo**(錶停在那裡)。兩者常常不是同一個——議題在需求的 repo,程式碼在 `repos` 列的
那幾個。同一個 repo 時 `--issue-repo` 可以省略。
`--base` 就是第一段問到的那支來源分支,要明講——腳本不替你猜 `master` 還是 `main`。
標題由腳本設為分支名,不必也不能另外指定。
順序是**先開 PR 再停錶**,而且 PR 沒開成就不停錶——工時要記在真的有做事的那段時間上。
錶本來就沒在跑不算失敗(`碼錶已停` 會是 `false` 並附一句說明),PR 仍然開出去了。
重跑不會開出第二顆 PR:同一個 head 已經有開著的 PR 就回傳它(`created` 為 `false`),
然後照樣停錶——那一步可能正是上次中斷的地方。
### 16. 回報
- PR 的網址與編號、標題(等同分支名),以及它是這次新開的還是接上既有的
- 建立了哪幾顆 commit
- 碼錶是否已停;沒停的話把腳本回的那句說明一起帶出來
- 議題上還有沒有沒勾完的待辦(理論上應該沒有;有的話要說出來)
### 17. 告訴使用者之後怎麼查
PR 開出去之後就交給 reviewer 了。**把下面這件事講給使用者聽,不要自己反覆跑**:
```
node scripts/pr-watch.js --repo <owner/name> --index <PR 編號>
```
問一次答一次:PR 狀態、還有幾則留言沒處理、工作樹在哪、裡面有沒有沒提交的東西,
以及固定列舉值的 `suggestedAction`(`run-sdlc-fix`/`cleanup`/`nothing-to-do`/
`blocked-dirty`)。多久跑一次由使用者自己排(cron 或他自己的循環機制),
本工具不長出排程器。
PR 合併或關閉時它會順手清掉那棵工作樹,**本機分支與遠端分支都留著**;工作樹裡還有
沒提交的東西就會擋下來(`blocked-dirty`),由使用者自己處理。永遠不會被合併也不會被
關閉的那些 PR,用手動出口清:
```
node scripts/worktree-remove.js --repo <owner/name> --branch <分支名>
```
## 邊界
- 第一段**不改任何一行程式碼**、不勾待辦、不提交、不開 PR——那些是後面幾段的事。
- 第二段只實作與勾選。**不提交、不開 PR、不停錶**——那是第三段的事。
- 第三段不改任何一行程式碼。到這裡實作已經結束,要改就回第二段改完再來。
- **不在主工作區動手。** 第二段與第三段的每一個動作都在 `branch-prep` 建出來的那棵
工作樹裡進行,包含跑測試與 `--path`。
- 不把依賴、建置產物或 `.env` 這類機密檔案複製到工作樹裡,也不做連結——
兩棵工作樹共用同一份依賴,正好把工作樹要隔離的東西又接回去。
- 工作樹建不起來時中止,**不退回原地切分支**。
- 不把「已測試通過」這種空話寫進 PR 描述,也不為了通過檢查而拼湊內容。
- 不代替使用者決定 commit 的類型與描述;`--type` 與 `--subject` 都要是這次真的做了什麼。
- 不把實作規範或註解格式寫進目標專案的任何檔案。
- 不改與待辦無關的程式碼;順手想修的東西記下來說出來,不要摸進這次的變更裡。
- 不為了勾選在議題上留留言。
- **不自動反覆執行 `pr-watch`**,也不因為它建議了 `run-sdlc-fix` 就自己去跑 `/sdlc-fix`——
流程只由使用者明確叫用。
- 不自行建立標籤。缺「進行中」標籤時中止並請使用者建立。
- 不代替使用者停錶,也不在被鎖擋下時繞過去。
- 不替使用者決定來源分支。
- **不寫任何本機狀態檔。** 進度完全由 Gitea 上的 assignee、標籤、碼錶與 git 本身推導,
換一台機器或換一個 agent 都要能直接接手。