三份正本各加一節說明這個後綴是什麼意思,只留能力描述那一句與指回判準正本的指路, 判準本身不複寫——抄過去就會有兩份各自演化的規則(AGENTS.md 的 references/ 那一列)。 標的是六個:sdlc-plan 的產生圖解版總覽;sdlc-analyze 的對四份清單列出疑點、算出截止日、 產生分析版的圖解總覽;sdlc-feat 的把議題標題翻成英文、分批提交。 其中三步是部分委派,各自在該步寫明哪一半留給主流程:兩份圖解總覽委派的是產出 HTML, 寫回議題的 issue-update 不委派;分批提交委派的是方案計算,實際跑 commit-split.js 不委派。 理由不在這裡複述,指回判準第四條。 **「認出語言,讀規則正本」不標**,儘管議題的列舉點了它。那一步的核心動作含「認不出語言 就停下來問、不要猜」,正是判準第二條硬排除的事;排掉它之後剛好是議題所寫的六個。 這一條與 repo 擁有者確認過。 AGENTS.md 的 references/ 那一列補上「委派判準」,否則那串括號裡的列舉會漏掉新的一份。 議題 #58 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
367 lines
19 KiB
Markdown
367 lines
19 KiB
Markdown
name: sdlc-feat
|
||
description: 僅由 /sdlc-feat 指令叫用。領取一顆工作包、備妥工作樹、起錶,逐項實作並勾選待辦,最後分批提交並開立 PR。
|
||
|
||
# sdlc-feat
|
||
|
||
拿一顆工作包,從領取到開出 PR。
|
||
|
||
第一段**領取與開工準備**:把工作包安全地認領下來,備妥一棵屬於它的工作樹,然後開始計時。
|
||
這一段不改任何一行程式碼——它只負責讓後面的實作有個乾淨的起點。
|
||
|
||
第二段**逐項實作**:一項一項把待辦做完並即時勾選,讓議題頁的進度條隨時反映真實狀態。
|
||
|
||
第三段**提交與開立 PR**:把變更整理成讀得懂的歷史,開出 PR,停錶。
|
||
|
||
這份檔案是流程正本。各平台的轉接檔只是指回這裡,不要把規則抄過去。
|
||
|
||
## 輸入
|
||
|
||
一個工作包議題編號。使用者直接給的,或 `/sdlc-fix` 收到議題後交棒過來的——
|
||
兩者一樣處理,**不要因為是交棒來的就要求他再打一次指令**。
|
||
|
||
## 〔可委派〕的意思
|
||
|
||
標題後綴 `〔可委派〕` 的步驟只在意結果:**你的環境若能把工作交給子代理,就交出去,
|
||
只把結果帶回來;不能就自己做。** 沒有這個後綴的步驟一律自己做。
|
||
|
||
怎麼挑、為什麼這樣挑,見 `references/delegation.md`——判準只有那一份,這裡不複述。
|
||
|
||
## 第一段:領取與開工準備
|
||
|
||
### 1. 讀工作包
|
||
|
||
```
|
||
node scripts/wp-extract.js --repo <owner/name> --index <編號>
|
||
```
|
||
|
||
拿到的是結構化欄位:待辦與它自己的驗收、範圍邊界、介面契約、相依、repo 列表。
|
||
不必再讀整份議題全文。
|
||
|
||
**先看 `未處理留言數`。** 只要不是 0,就代表議題描述可能是過期的——留言裡有決策還沒被
|
||
整併回描述。這時**先停下來**告訴使用者有幾則未整併的留言,問他要不要現在整併。
|
||
|
||
要整併的話**直接走 `/sdlc-sync` 的流程**(`prompts/sdlc-sync.md`),做完**自動接回這裡**:
|
||
重新抽取一次拿到更新後的描述,再往下走。**不要要求使用者重打指令**——他已經說要整併了。
|
||
|
||
使用者選擇不整併就繼續,但要記下這件事,並在最後的 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` | 多半是忘了停上一顆;先去停掉再回來 |
|
||
|
||
被錶擋下來時**要順帶說明停錶不會動到既有的工作樹**:碼錶只管時間、工作樹只管檔案。
|
||
不講清楚,使用者會以為停錶等於放棄那顆工作包,於是寧可不停——工時就記到別顆去了。
|
||
| 沒有鎖 | —— | 放行。自己已認領但沒起錶也算沒有鎖,那正是中斷後重跑的情形 |
|
||
|
||
**別顆議題上的錶一律由使用者自己停。** 哪一段時間該記在哪顆議題上只有他知道,代勞會把
|
||
工時記錯地方。每道指令停掉的只有自己起的那一支——這一道停在「開 PR 並停錶」那一步。
|
||
|
||
鎖以外還有一個前置條件: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. 分批提交 〔可委派〕
|
||
|
||
全部待辦都勾完之後才進這一段。變更依類型分批。
|
||
|
||
委派的是**方案計算**:變更分成哪幾批、每一批收哪些檔案、各自的 `--type` 與描述怎麼寫。
|
||
**實際提交不委派**——底下那支 `commit-split.js` 由主流程執行(判準第四條)。
|
||
|
||
```
|
||
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 都要能直接接手。
|