feat/comments-merge/main
master
本 PR 取代 #49。#49 的 head 分支在解衝突時被重建,Gitea 因此自動關閉了它。 內容相同,另外含把本工作包的修正套到 #48 的 pr-threads 共用結構上的那一段。
pr-threads
/sdlc-sync:把議題留言裡的決策整併回描述,新加入的人不必爬完整串留言就知道現況。 六份流程正本到此全部到齊。
/sdlc-sync
#1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組
#15 — 以 sdlc-sync 把留言裡的決策整併回議題描述
refactor(lib)
test(pr-create)
+1
fix(pr-comments)
test(pr-comments)
feat(議題解析)
feat(comments-merge)
test(comments-merge)
feat(流程正本)
test(流程正本)
sdlc-sync
新增 scripts/comments-merge.js、prompts/sdlc-sync.md;issue-body.js 增加 replaceSection;lib.js 增加 readTextFile/listIssueComments/mergedByMe。
scripts/comments-merge.js
prompts/sdlc-sync.md
issue-body.js
replaceSection
lib.js
readTextFile
listIssueComments
mergedByMe
ambiguous
tickLine
Code review 抓出四類缺陷。第一個是我自己寫進正本的錯誤斷言:
1. /sdlc-sync 對它唯一的輸入完全跑不起來。 我在正本寫「pr-comments 對議題一樣管用 (Gitea 的 PR 也是 issue)」——這句話本身就反了:每個 PR 都是議題,議題不一定是 PR。 程式也照著反的寫,先打 /pulls/{index}:
pr-comments
/pulls/{index}
$ node scripts/pr-comments.js --repo plugins/tea-sdlc --index 15 {"ok":false,"error":{"code":"PULL_NOT_FOUND","message":"plugins/tea-sdlc 沒有編號 15 的 PR"}}
而 /sdlc-sync 的輸入正是純議題,於是在讀到第一則留言之前就斷了。改成先讀 /issues/{index}(兩種都有),看它有沒有 pull_request 才決定要不要翻 review。
/issues/{index}
pull_request
2. 跨工作包的「已整併」定義不一致,後果很安靜。 countUnmergedComments(#9)接受 任何人的 +1,而 pr-comments 的判斷(#14)只認自己的。隊友對一則決策留言按個讚 → 未處理留言數 掉到 0 → analyze/feat 再也不提示 → 那則決策永遠不會被收進描述。 統一成只認自己打的:別人按讚是「我同意」,不是「已經收進去了」。
countUnmergedComments
未處理留言數
analyze
feat
3. replaceSection 的三個邊界(都實測過):CRLF 的 body 會被混進 LF,讓抽取契約交出的 raw 對不上原文、之後就勾不動那幾行;同名標題靜靜蓋掉第一個;空段落遺失與下一個標題之間 的空行。
raw
4. 四份重複的「讀一個 --xxx-file 或直接失敗」,錯誤碼還有三種拼法 (BODY_FILE_NOT_FOUND/BODY_FILE_MISSING/CONTENT_FILE_NOT_FOUND)。同一種情況要有 同一個碼,呼叫端才分辨得出是哪一步壞了。連同第三份留言分頁一起收進 lib.js。
--xxx-file
BODY_FILE_NOT_FOUND
BODY_FILE_MISSING
CONTENT_FILE_NOT_FOUND
issue-extract
wp-extract
pr-create
FILE_NOT_FOUND
FILE_EMPTY
類型
PR
議題
PULL_NOT_FOUND
ISSUE_NOT_FOUND
sdlc-analyze
sdlc-feat
這兩條放在一起會互相牴觸:
使用者選擇略過的留言保持未標記,下次執行仍會被提出 sdlc-analyze 與 sdlc-feat 開始時偵測到未整併留言即提示先處理
結果是略過一次 = 以後每次 analyze/feat 都會被打斷,而 sync 每次重新提出同一則。 契約裡只有「+1 = 已處理」一種狀態,沒有「看過但決定不併」。
sync
本 PR 依驗收實作(略過就是不標記),並在正本的回報段落要求講明白「這不是沒整併乾淨, 是你選了略過」。真正的解法需要第二種狀態(例如另一種 reaction 表示「看過不併」), 那超出 #15 的驗收範圍。
pr-reply
三件都是議題 #1 的文字,我沒有自行更動契約正本。要我一次改掉的話說一聲。
在最新 master(86bdfd90)之上實際執行 npm test:
86bdfd90
npm test
ℹ tests 735 ℹ suites 0 ℹ pass 735 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
master 既有 691,本分支新增 44。
局部更新的六種情況(一般段落、最後一段、空段落、CRLF、重複標題、圍欄裡的假標題)各有 覆蓋;標記只給 --merged 列出的那幾則、描述寫失敗就不標記、冪等重跑、--dry-run 在無 變更時不預告 PATCH 也都釘住。
--merged
--dry-run
實機驗證:拿議題 #1 那份 212 行的真實描述換掉一段,段落數與順序不變,只有目標那一段的 內容變了,其餘六段逐字相同;pr-comments 對純議題 #15 的四個端點回應也逐一確認過 (/pulls/15 是 404,/issues/15/comments 與 /issues/15/timeline 是 200)。
/pulls/15
/issues/15/comments
/issues/15/timeline
🤖 Generated with Claude Code
「讀一個 --xxx-file 或直接失敗」原本在四支腳本各寫一份,錯誤碼還有三種拼法 (BODY_FILE_NOT_FOUND/BODY_FILE_MISSING/CONTENT_FILE_NOT_FOUND)。同一種情況要有同一個 碼,呼叫端才分辨得出是哪一步壞了。留言分頁的那段咒語也是第三份,一併收成 listIssueComments。 countUnmergedComments 原本接受任何人的 +1,而讀留言那邊只認自己的——兩端對「已整併」的 定義不一致。後果是隊友對決策留言按個讚,未處理留言數就掉到 0,analyze 與 feat 再也不提示, 那則決策永遠不會被收進描述。統一成只認自己打的:別人按讚是「我同意」,不是「已經收進去了」。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
每個 PR 都是議題,議題不一定是 PR——原本的註解把這句話講反了,程式也照著反過來寫: 先打 /pulls/{index},對純議題回 404,於是 /sdlc-sync 在讀到第一則留言之前就斷了。 改成先讀 /issues/{index}(兩種都有),看它有沒有 pull_request 才決定要不要去翻 review。 輸出加上「類型」讓下游知道拿到的是議題還是 PR。 讀取的共用結構沿用 pr-threads(#48 為了讓 pr-watch 數同一件事而抽出來的):readGeneral 改名 readGeneralComments 並導出,純議題只叫它;pr-threads 自己那份 markedByMe 拿掉, 改用 lib 的 mergedByMe。#48 的檔頭擔心「規則寫兩份遲早會各自演化」,現在三處共用一份。 試跑改印 commonRequests:pr-comments 收得下兩種輸入,而試跑階段還沒讀過議題、不知道是 哪一種。與其假設是 PR 而列出五個(對純議題有三個根本不會發),不如只列一定會發的。 pr-watch 的輸入一定是 PR,繼續用 plannedRequests。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
整併留言裡的決策時用它。不整份重寫的理由跟 upsertLineInSection 一樣,只是代價更大: 重寫會把別人在其他段落的編輯一起蓋掉,而議題的編輯紀錄沒有人會去比對。 三件事照著同檔既有的規矩做:圍欄裡的假標題不算段落;同名標題出現不只一次時交回 ambiguous 而不賭第一個(蓋掉的是一整段,猜錯的代價比 tickLine 更高);換行沿用 body 原本的那一種, CRLF 的 body 裡混進 LF 會讓抽取契約交出的 raw 對不上原文,之後就勾不動那幾行。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
判斷「哪幾則有決策、該併進哪一段」是讀得懂內容的人的事;這一支只負責把結果安全地寫回去。 先寫描述再標記,描述寫失敗就不標記:反過來的話,那幾則已經被標成處理過,再也不會被提出來。 --merged 只收真的併進去的那幾則,略過的保持未標記。標記之前先核對那幾則確實在這顆議題上—— 打錯 id 的 reaction 會落在別顆議題的留言上,而那幾乎不會有人發現。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
挑出哪些留言真的是決策、寫回去之前讓使用者點頭、略過的保持未標記——三件都只有正本做得到。 analyze 與 feat 原本寫「建議先執行 /sdlc-sync,再回來」,那等於要使用者重打指令。改成直接走 sync 的流程、做完自動接回,並在接回前重新抽取一次——接著用舊的那一份做事,這一整段就白做了。 略過的留言會讓未處理留言數停在大於 0,於是 analyze 與 feat 每次都會再停一次。這是驗收標準 本身的兩條放在一起的結果,正本把它講明白,讓使用者分得出「沒整併乾淨」與「我選了略過」。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No dependencies set.
The note is not visible to the blocked user.
摘要
/sdlc-sync:把議題留言裡的決策整併回描述,新加入的人不必爬完整串留言就知道現況。六份流程正本到此全部到齊。
需求議題
#1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組
工作包議題
#15 — 以 sdlc-sync 把留言裡的決策整併回議題描述
變更內容
refactor(lib)/test(pr-create)+1fix(pr-comments)/test(pr-comments)feat(議題解析)feat(comments-merge)/test(comments-merge)feat(流程正本)/test(流程正本)sdlc-sync,並讓另外兩份正本整併完自動接回新增
scripts/comments-merge.js、prompts/sdlc-sync.md;issue-body.js增加replaceSection;lib.js增加readTextFile/listIssueComments/mergedByMe。設計重點
描述上換掉一段,另外六段逐字未變。
決定並先給使用者點頭;腳本只負責安全地寫回去。
replaceSection照同檔既有的規矩做:圍欄裡的假標題不算段落;同名標題出現不只一次時交回
ambiguous而不賭第一個(蓋掉的是一整段,猜錯的代價比tickLine更高)。解決的問題
Code review 抓出四類缺陷。第一個是我自己寫進正本的錯誤斷言:
1.
/sdlc-sync對它唯一的輸入完全跑不起來。 我在正本寫「pr-comments對議題一樣管用(Gitea 的 PR 也是 issue)」——這句話本身就反了:每個 PR 都是議題,議題不一定是 PR。
程式也照著反的寫,先打
/pulls/{index}:而
/sdlc-sync的輸入正是純議題,於是在讀到第一則留言之前就斷了。改成先讀/issues/{index}(兩種都有),看它有沒有pull_request才決定要不要翻 review。2. 跨工作包的「已整併」定義不一致,後果很安靜。
countUnmergedComments(#9)接受任何人的
+1,而pr-comments的判斷(#14)只認自己的。隊友對一則決策留言按個讚 →未處理留言數掉到 0 →analyze/feat再也不提示 → 那則決策永遠不會被收進描述。統一成只認自己打的:別人按讚是「我同意」,不是「已經收進去了」。
3.
replaceSection的三個邊界(都實測過):CRLF 的 body 會被混進 LF,讓抽取契約交出的raw對不上原文、之後就勾不動那幾行;同名標題靜靜蓋掉第一個;空段落遺失與下一個標題之間的空行。
4. 四份重複的「讀一個
--xxx-file或直接失敗」,錯誤碼還有三種拼法(
BODY_FILE_NOT_FOUND/BODY_FILE_MISSING/CONTENT_FILE_NOT_FOUND)。同一種情況要有同一個碼,呼叫端才分辨得出是哪一步壞了。連同第三份留言分頁一起收進
lib.js。影響的功能
issue-extract/wp-extract的未處理留言數語意收緊為「自己還沒標記過的則數」。這是修正,不是行為變更的副作用——舊語意會讓決策被靜靜跳過。
pr-create改用lib.js的readTextFile,錯誤碼由BODY_FILE_NOT_FOUND變成FILE_NOT_FOUND/FILE_EMPTY。pr-comments的輸出新增類型(PR/議題),錯誤碼由PULL_NOT_FOUND變成ISSUE_NOT_FOUND。sdlc-analyze/sdlc-feat偵測到未整併留言時,改為直接轉進/sdlc-sync並自動接回,不再要求使用者重打指令。
驗收標準本身的一個矛盾,請裁示
這兩條放在一起會互相牴觸:
結果是略過一次 = 以後每次
analyze/feat都會被打斷,而sync每次重新提出同一則。契約裡只有「
+1= 已處理」一種狀態,沒有「看過但決定不併」。本 PR 依驗收實作(略過就是不標記),並在正本的回報段落要求講明白「這不是沒整併乾淨,
是你選了略過」。真正的解法需要第二種狀態(例如另一種 reaction 表示「看過不併」),
那超出 #15 的驗收範圍。
另外兩件累積中的契約落差
pr-reply(#14 交付)不在議題 #1 的腳本清單裡。三件都是議題 #1 的文字,我沒有自行更動契約正本。要我一次改掉的話說一聲。
測試結果
在最新 master(
86bdfd90)之上實際執行npm test:master 既有 691,本分支新增 44。
局部更新的六種情況(一般段落、最後一段、空段落、CRLF、重複標題、圍欄裡的假標題)各有
覆蓋;標記只給
--merged列出的那幾則、描述寫失敗就不標記、冪等重跑、--dry-run在無變更時不預告 PATCH 也都釘住。
實機驗證:拿議題 #1 那份 212 行的真實描述換掉一段,段落數與順序不變,只有目標那一段的
內容變了,其餘六段逐字相同;
pr-comments對純議題 #15 的四個端點回應也逐一確認過(
/pulls/15是 404,/issues/15/comments與/issues/15/timeline是 200)。🤖 Generated with Claude Code
每個 PR 都是議題,議題不一定是 PR——原本的註解把這句話講反了,程式也照著反過來寫: 先打 /pulls/{index},對純議題回 404,於是 /sdlc-sync 在讀到第一則留言之前就斷了。 改成先讀 /issues/{index}(兩種都有),看它有沒有 pull_request 才決定要不要去翻 review。 輸出加上「類型」讓下游知道拿到的是議題還是 PR。 讀取的共用結構沿用 pr-threads(#48 為了讓 pr-watch 數同一件事而抽出來的):readGeneral 改名 readGeneralComments 並導出,純議題只叫它;pr-threads 自己那份 markedByMe 拿掉, 改用 lib 的 mergedByMe。#48 的檔頭擔心「規則寫兩份遲早會各自演化」,現在三處共用一份。 試跑改印 commonRequests:pr-comments 收得下兩種輸入,而試跑階段還沒讀過議題、不知道是 哪一種。與其假設是 PR 而列出五個(對純議題有三個根本不會發),不如只列一定會發的。 pr-watch 的輸入一定是 PR,繼續用 plannedRequests。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>