feat/pr-watch-and-cleanup/main #48

Merged
admin merged 5 commits from feat/pr-watch-and-cleanup/main into master 2026-09-17 09:09:54 +00:00
Member

摘要

PR 開出去之後,開發者可以隨時問一句「這個 PR 現在怎麼樣、我還有什麼要做」,
並且在它結束時不必記得回頭清理那棵工作樹。

需求議題

#38 — 以 worktree 隔離平行工作包的實作與修正

工作包議題

#41 — 以 pr-watch 回報 PR 現況並清理工作樹

變更內容

新增兩支腳本與一份共用模組:

  • scripts/pr-watch.js:一次性回報 PR 現況,終止狀態時清掉工作樹。
  • scripts/worktree-remove.js:手動清理的出口,處理永遠不會被合併也不會被關閉的 PR。
  • scripts/pr-threads.js:三類留言的讀取與「已處理」判定,由 pr-comments 抽出共用。
  • scripts/lib.js:新增 inspectWorktree/removeWorktree,兩支腳本共用同一份移除實作。
  • scripts/pr-comments.js:改為只剩輸入輸出,行為不變。
  • prompts/sdlc-feat.md:第三段收尾新增第 17 步,把這兩支腳本講給使用者聽。
  • 測試:test/pr-watch.test.js、test/worktree-remove.test.js 為新增,
    test/sdlc-feat-assets.test.js 跟著補。

commit 一覽:

  • 43764f4 refactor(pr-comments):三類留言的讀取收進 pr-threads
  • 9e04958 feat(worktree-remove):手動清掉一棵工作樹,絕不 --force
  • d331bef feat(pr-watch):回報 PR 現況,並在它結束時清掉工作樹
  • 32edd65 docs(sdlc-feat):第三段收尾指向 pr-watch 與手動清理
  • 617a3ee fix(pr-watch):試跑的預告與實跑的守門對齊,並說出路徑被佔住這件事

設計重點

為什麼建議動作是列舉值。 suggestedAction 固定四個值(run-sdlc-fix/cleanup/
nothing-to-do/blocked-dirty),呼叫端要能程式化判斷,而不是去解讀一段文字——
這支腳本本來就是給使用者自己的排程去跑的。

為什麼不做變化偵測。 「已處理」的判定基準是留言上自己打的 +1 與行內留言的
resolve,那個狀態已經存在 Gitea 上、不在本機記憶裡,所以一份現況快照就足以回答
「還有沒有事要做」。不必跟上次比較,也就不必留任何游標或狀態檔。

draft 與 merged/closed 的處置刻意不一致。 merged 與 closed 是終止狀態,清掉工作樹;
draft 繼續監看且不清理——被退回草稿代表還要繼續改,這時候那棵工作樹更需要留著,
清掉它等於把人做到一半的環境收走。四種狀態各有測試。

清理絕不 --force。 這件事會被自動執行,而自動執行的東西只能做可逆的事:
工作樹重建得回來,被刪掉的未提交變更救不回來。有東西沒提交就擋下並報出路徑與檔名,
只移除工作樹,本機分支與遠端分支都保留。

試跑與實跑擋在同一個判斷上。 inspectWorktree 直接給出 reason
(missing/foreign/dirty/removable),試跑、實跑、自動清理、手動清理四條路徑
共用它。先前試跑會預告一行實跑必然拒絕的指令——那正是最難查的那種落差。

為什麼要抽出 pr-threads。 pr-watch 數的「還有幾則沒處理」與 pr-comments 讀的
是同一件事:三類留言分散在三個端點,一般留言與總評看自己打的 +1、行內留言看
resolve,而總評的 reaction 掛在它的 issue comment id 上。這套規則寫兩份,遲早會一邊
認自己的讚、另一邊認任何人的,而那個差異要等到有留言被靜靜跳過才會被發現。

順手修掉一個既有解析錯誤。 git status --porcelain 的輸出經過 runGit 的 trim 之後,
第一行的前導空白不見了,固定切前三個字元會讓已修改檔案的檔名少一個字
(README.md 變成 EADME.md)——使用者照著訊息去找會找不到那個檔案。

解決的問題

PR 開出去之後流程原本就斷在那裡:使用者只能自己去網頁上看有沒有新留言,而合併之後
那棵工作樹會一直留在 ~/.tea-sdlc/worktrees/ 底下,沒有人記得清。

另外三個被測試釘住的具體情境:工作樹裡還有沒提交的東西時清理被擋下(而不是被自動流程
刪掉);路徑上是別的 clone 留下的目錄時明確說出來(而不是靜靜跳過或炸出一句 git 的
原始錯誤);PR 讀不到 head 分支時給出 PULL_HEAD_MISSING,而不是拿空字串去推導路徑。

影響的功能

新增的兩支腳本不改變任何既有流程的行為。pr-comments 的輸出與錯誤碼完全不變,
既有的 21 條測試原封通過。

/sdlc-feat 第三段多一步(第 17 步),內容是「把這兩支腳本講給使用者聽」,
不改變 agent 既有的任何動作。這一步不在 #41 的驗收標準裡,我補上去的理由是:
沒人講的腳本等於不存在,而 #41 的「要做出什麼」開頭就是「開發者要能隨時問一句
這個 PR 現在怎麼樣」。要拿掉的話單獨回捲 32edd65 即可。

測試結果

npm test
ℹ tests 724
ℹ pass 724
ℹ fail 0

新增 31 條:pr-watch 18 條(含四種 PR 狀態的表格驅動對照)、worktree-remove 11 條、
正本 2 條。工作樹相關的測試都在臨時 git repo 上跑真的 git,工作樹由 branch-prep
實際建出來,以本機裸 repo 充當遠端,不需網路。

另外在真實環境上跑過一次:本 PR 的工作樹就是用上一顆工作包(#40)合併進 master 的
branch-prep 開的——

node scripts/branch-prep.js --repo plugins/tea-sdlc --path /root/plugins/tea-sdlc \
  --source master --type feat --slug pr-watch-and-cleanup
→ worktree: /root/.tea-sdlc/worktrees/bd457620184e
  提示.安裝指令: ["npm install"]

🤖 Generated with Claude Code

## 摘要 PR 開出去之後,開發者可以隨時問一句「這個 PR 現在怎麼樣、我還有什麼要做」, 並且在它結束時不必記得回頭清理那棵工作樹。 ## 需求議題 #38 — 以 worktree 隔離平行工作包的實作與修正 ## 工作包議題 #41 — 以 pr-watch 回報 PR 現況並清理工作樹 ## 變更內容 新增兩支腳本與一份共用模組: - `scripts/pr-watch.js`:一次性回報 PR 現況,終止狀態時清掉工作樹。 - `scripts/worktree-remove.js`:手動清理的出口,處理永遠不會被合併也不會被關閉的 PR。 - `scripts/pr-threads.js`:三類留言的讀取與「已處理」判定,由 `pr-comments` 抽出共用。 - `scripts/lib.js`:新增 `inspectWorktree`/`removeWorktree`,兩支腳本共用同一份移除實作。 - `scripts/pr-comments.js`:改為只剩輸入輸出,行為不變。 - `prompts/sdlc-feat.md`:第三段收尾新增第 17 步,把這兩支腳本講給使用者聽。 - 測試:`test/pr-watch.test.js`、`test/worktree-remove.test.js` 為新增, `test/sdlc-feat-assets.test.js` 跟著補。 commit 一覽: - `43764f4` refactor(pr-comments):三類留言的讀取收進 pr-threads - `9e04958` feat(worktree-remove):手動清掉一棵工作樹,絕不 --force - `d331bef` feat(pr-watch):回報 PR 現況,並在它結束時清掉工作樹 - `32edd65` docs(sdlc-feat):第三段收尾指向 pr-watch 與手動清理 - `617a3ee` fix(pr-watch):試跑的預告與實跑的守門對齊,並說出路徑被佔住這件事 ## 設計重點 **為什麼建議動作是列舉值。** `suggestedAction` 固定四個值(`run-sdlc-fix`/`cleanup`/ `nothing-to-do`/`blocked-dirty`),呼叫端要能程式化判斷,而不是去解讀一段文字—— 這支腳本本來就是給使用者自己的排程去跑的。 **為什麼不做變化偵測。** 「已處理」的判定基準是留言上自己打的 `+1` 與行內留言的 resolve,那個狀態已經存在 Gitea 上、不在本機記憶裡,所以一份現況快照就足以回答 「還有沒有事要做」。不必跟上次比較,也就不必留任何游標或狀態檔。 **draft 與 merged/closed 的處置刻意不一致。** merged 與 closed 是終止狀態,清掉工作樹; draft 繼續監看且**不清理**——被退回草稿代表還要繼續改,這時候那棵工作樹更需要留著, 清掉它等於把人做到一半的環境收走。四種狀態各有測試。 **清理絕不 `--force`。** 這件事會被自動執行,而自動執行的東西只能做可逆的事: 工作樹重建得回來,被刪掉的未提交變更救不回來。有東西沒提交就擋下並報出路徑與檔名, 只移除工作樹,本機分支與遠端分支都保留。 **試跑與實跑擋在同一個判斷上。** `inspectWorktree` 直接給出 `reason` (`missing`/`foreign`/`dirty`/`removable`),試跑、實跑、自動清理、手動清理四條路徑 共用它。先前試跑會預告一行實跑必然拒絕的指令——那正是最難查的那種落差。 **為什麼要抽出 `pr-threads`。** `pr-watch` 數的「還有幾則沒處理」與 `pr-comments` 讀的 是同一件事:三類留言分散在三個端點,一般留言與總評看**自己打的** `+1`、行內留言看 resolve,而總評的 reaction 掛在它的 issue comment id 上。這套規則寫兩份,遲早會一邊 認自己的讚、另一邊認任何人的,而那個差異要等到有留言被靜靜跳過才會被發現。 **順手修掉一個既有解析錯誤。** `git status --porcelain` 的輸出經過 `runGit` 的 trim 之後, 第一行的前導空白不見了,固定切前三個字元會讓已修改檔案的檔名少一個字 (`README.md` 變成 `EADME.md`)——使用者照著訊息去找會找不到那個檔案。 ## 解決的問題 PR 開出去之後流程原本就斷在那裡:使用者只能自己去網頁上看有沒有新留言,而合併之後 那棵工作樹會一直留在 `~/.tea-sdlc/worktrees/` 底下,沒有人記得清。 另外三個被測試釘住的具體情境:工作樹裡還有沒提交的東西時清理被擋下(而不是被自動流程 刪掉);路徑上是別的 clone 留下的目錄時明確說出來(而不是靜靜跳過或炸出一句 git 的 原始錯誤);PR 讀不到 head 分支時給出 `PULL_HEAD_MISSING`,而不是拿空字串去推導路徑。 ## 影響的功能 新增的兩支腳本不改變任何既有流程的行為。`pr-comments` 的輸出與錯誤碼完全不變, 既有的 21 條測試原封通過。 `/sdlc-feat` 第三段多一步(第 17 步),內容是「把這兩支腳本講給使用者聽」, 不改變 agent 既有的任何動作。**這一步不在 #41 的驗收標準裡**,我補上去的理由是: 沒人講的腳本等於不存在,而 #41 的「要做出什麼」開頭就是「開發者要能隨時問一句 這個 PR 現在怎麼樣」。要拿掉的話單獨回捲 `32edd65` 即可。 ## 測試結果 ``` npm test ℹ tests 724 ℹ pass 724 ℹ fail 0 ``` 新增 31 條:`pr-watch` 18 條(含四種 PR 狀態的表格驅動對照)、`worktree-remove` 11 條、 正本 2 條。工作樹相關的測試都在臨時 git repo 上跑真的 git,工作樹由 `branch-prep` 實際建出來,以本機裸 repo 充當遠端,不需網路。 另外在真實環境上跑過一次:本 PR 的工作樹就是用上一顆工作包(#40)合併進 master 的 `branch-prep` 開的—— ``` node scripts/branch-prep.js --repo plugins/tea-sdlc --path /root/plugins/tea-sdlc \ --source master --type feat --slug pr-watch-and-cleanup → worktree: /root/.tea-sdlc/worktrees/bd457620184e 提示.安裝指令: ["npm install"] ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jiantw83 added 5 commits 2026-09-17 09:08:51 +00:00
pr-watch 要數「還有幾則沒處理」,數的是與 pr-comments 完全同一件事:三類留言分散在
三個端點,一般留言與總評看自己打的 +1、行內留言看有沒有被 resolve,而總評的 reaction
掛在它的 issue comment id 上。這套規則寫兩份,遲早會一邊認自己的 +1、另一邊認任何人的
——而那個差異要等到有留言被靜靜跳過才會被發現。

pr-comments 只留輸入輸出,行為不變。

議題 #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pr-watch 會在 PR 合併或關閉時自動清理,但永遠不會被合併也不會被關閉的 PR 沒有出口
——沒有這一支,那些工作樹只能靠使用者自己記得去刪。兩條路共用 lib 的 removeWorktree,
不互相開子行程:守門的規則只有一份,自動的那條與手動的這條不該長出兩種行為。

**絕不 --force。** 清理會被自動執行,而自動執行的東西只能做可逆的事:工作樹重建得回來,
被刪掉的未提交變更救不回來。所以有東西沒提交就中止並報出路徑與檔名。只移除工作樹,
本機分支與遠端分支都留著。

輸入是 owner/repo 與分支名而不是一條路徑:要刪哪一棵由「哪顆工作包」決定,
使用者不必自己去記 12 碼的雜湊目錄名。

順手修掉一個測試抓出來的解析錯誤:git status --porcelain 的第一行前導空白會被 runGit
的 trim 修掉,固定切前三個字元會讓已修改檔案的檔名少一個字(README.md 變成 EADME.md),
人照著訊息去找會找不到。改成認「狀態欄一到兩個字元 + 空白」。

議題 #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
開發者要能隨時問一句「這個 PR 現在怎麼樣、我還有什麼要做」。回報 PR 狀態、還有幾則
留言沒處理、工作樹在哪、裡面有沒有沒提交的東西,以及固定列舉值的 suggestedAction
——用列舉值而不是一段文字,呼叫端才能程式化判斷。

一次性、無狀態:不做變化偵測。已處理的判定基準是 Gitea 上的 +1 與 resolve,那個狀態
不在本機記憶裡,所以一份現況快照就足以回答「還有沒有事要做」,不必跟上次比較,也就
不必留任何游標或狀態檔。不做常駐程序也不做 daemon,排程交給呼叫端。

只通知,不動手:偵測到未處理留言只給建議,不自動執行 /sdlc-fix——流程不該被模型自動
觸發,而 /sdlc-fix 要求「不確定時詢問使用者」,非互動模式下那個詢問無處可去。

四種狀態的處置不一致,所以表格驅動測:merged 與 closed 終止並清理;open 繼續監看;
draft 繼續監看且**不清理**——被退回草稿代表還要繼續改,這時候那棵工作樹更需要留著。

議題 #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR 開出去之後流程就斷在那裡,使用者不會知道有東西可以查現況、也不會知道工作樹會被
自動清掉。收尾補一步,把兩支腳本講給使用者聽,並明講「多久跑一次由他自己排」。

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

議題 #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 抓到一處落差:試跑判「終止且工作樹乾淨」就預告 git worktree remove,
但實跑多判一件事——那條路徑上的東西是不是真的一棵工作樹。別的 clone 在同一條路徑上
留下目錄時(路徑由 owner/repo/分支名 推導,不含本機 clone 的位置),試跑會預告一行
實跑必然拒絕的指令,而回報裡完全看不出原因。

判斷收進 lib 的 inspectWorktree,由它直接給出 reason(missing/foreign/dirty/
removable):試跑與實跑、自動與手動四條路徑從此擋在同一個判斷上,worktree-remove 裡
那份重算的副本也跟著刪掉。回報多一個「是工作樹」欄位,手動出口的 NOT_A_WORKTREE
不再是使用者第一次聽到這件事。

順帶兩件同源的修正:
- 「是不是工作樹」改認 .git 為**檔案**。獨立 clone 的 .git 是目錄,先前會被當成工作樹,
  然後在 git worktree remove 那一步炸出一句原始錯誤。
- 兩支腳本的 --dry-run 請求預告收進 pr-threads 的 plannedRequests,並補上 pr-watch
  先前漏掉的那句說明(逐則 reaction 與逐個 review 的行內留言事前列不完)。預告與實際
  發出的請求分開寫,加一個端點就會有一邊忘了改。
- PR 讀不到 head 分支時給出 PULL_HEAD_MISSING,而不是拿空字串去推導一條路徑。

議題 #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin approved these changes 2026-09-17 09:09:50 +00:00
admin merged commit d0e7e569a3 into master 2026-09-17 09:09:54 +00:00
admin deleted branch feat/pr-watch-and-cleanup/main 2026-09-17 09:09:54 +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#48