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