diff --git a/prompts/sdlc-fix.md b/prompts/sdlc-fix.md index 9e9905e..9f0104d 100644 --- a/prompts/sdlc-fix.md +++ b/prompts/sdlc-fix.md @@ -1,5 +1,5 @@ name: sdlc-fix -description: 僅由 /sdlc-fix 指令叫用。讀取 PR 上的三類留言,逐條處理並回覆,最後輸出修正摘要。 +description: 僅由 /sdlc-fix 指令叫用。定位工作包的工作樹,讀取 PR 上的三類留言,逐條處理並回覆,最後輸出修正摘要。 # sdlc-fix @@ -11,7 +11,40 @@ reviewer 留完意見,跑這一段,意見被逐條處理並回覆,不漏 一個 PR 編號。 -## 1. 讀留言 +## 1. 看 PR 現況,並定位工作樹 + +先問一次現況,再決定要不要動手: + +``` +node scripts/pr-watch.js --repo --index +``` + +**`terminal` 為 `true`(PR 已合併或已關閉)時就停下來。** 那顆工作包已經結束,在一棵 +該被清掉的工作樹上處理留言是白做工。把 `suggestedAction` 的意思講給使用者聽: +`nothing-to-do` 是工作樹也清掉了;`blocked-dirty` 是那裡還有沒提交的東西,要他自己處理。 +使用者堅持要繼續就繼續,但要說清楚 PR 已經結束了。 + +PR 還開著就定位工作樹。輸入是 `pr-watch` 給的 `branch`——路徑由「哪顆工作包」推導, +不必也不該由使用者自己去記那串雜湊目錄名: + +``` +node scripts/worktree-ensure.js --repo --branch --dry-run +``` + +試跑會印出推導出的路徑與將執行的 git 指令;確認無誤後拿掉旗標再跑一次。 + +**推導出的路徑不存在時它會重建,那是常態不是例外**:進度完全不寫在本機,換一台機器 +或換一個 agent 接手時工作樹本來就不在,重建的成本就是一次 `git worktree add`。 +重建走的是與開工時同一套建立方式,分支上已經有的進度會被接上,不是長一棵空的。 + +兩種會被擋下來的情況照實說,不要繞過去:`BRANCH_NOT_FOUND` 是那一支分支在本機與遠端 +都不見了(多半是 PR 已經合併而分支被刪,回頭確認 PR 狀態);`WORKTREE_PATH_TAKEN` 是 +推導出的路徑上有別的東西,請使用者自己確認後移除——**不要自己刪**。 + +**後面每一步都在那棵工作樹裡做**,不要回到主工作區:它可能停在別的分支上,在那裡改 +會把改動落到別顆工作包的分支去。 + +## 2. 讀留言 ``` node scripts/pr-comments.js --repo --index @@ -31,7 +64,7 @@ resolve。`已處理` 認的是**自己打的** `+1`——reviewer 對留言按 偶爾會遇到 `可標記` 是 `false` 的總評(在 timeline 上對不到它在 issue comment 表裡的 那一份)。那一則回覆照發,但沒有記號留得下來,**要在修正摘要裡單獨點出來**。 -## 2. 分類:必改還是建議 +## 3. 分類:必改還是建議 逐則判斷,**reviewer 不必逐則說明**。判斷依據是內容本身: @@ -42,7 +75,7 @@ resolve。`已處理` 認的是**自己打的** `+1`——reviewer 對留言按 分類錯的代價不對稱——把必改當成建議會漏掉真的問題,所以拿不準時歸到必改那一邊, 並在下一步問清楚。 -## 3. 不確定就問 +## 4. 不確定就問 **一次問一題。** 下列情況不要自作主張: @@ -57,7 +90,7 @@ resolve。`已處理` 認的是**自己打的** `+1`——reviewer 對留言按 **不確定卻硬改,比多問一題貴得多。** 改壞的地方 reviewer 下一輪才會看到。 -## 4. 逐則處理並回覆 +## 5. 逐則處理並回覆 一則一則來:先改,改完立刻回覆那一則,再處理下一則。**不要全部改完才一起回**—— 中途斷掉的話,沒有人知道哪幾則已經處理過。 @@ -81,7 +114,7 @@ node scripts/pr-reply.js --repo --index \ 不必自己去翻 diff。決定不改的也要回,並說明理由——建議類的留言常常合理地不採納, 但沉默會讓 reviewer 以為被忽略了。 -## 5. 修正摘要 +## 6. 修正摘要 全部處理完後印一則摘要,讓 reviewer 不必逐串點開: @@ -102,3 +135,5 @@ node scripts/pr-reply.js --repo --index \ - 不在回覆沒成功時標記已處理。 - **不自動張貼修正摘要**,也不自動關閉或合併 PR。 - 不動 PR 的 review 狀態:回覆一則意見不該順手把整個 PR 標成通過或要求變更。 +- **不在主工作區處理留言**,一律在 `worktree-ensure` 定位出來的那棵工作樹裡。 +- PR 已經合併或關閉時不繼續處理留言,也不自己去刪推導路徑上的東西。 diff --git a/test/sdlc-fix-assets.test.js b/test/sdlc-fix-assets.test.js index a23866a..5707b7c 100644 --- a/test/sdlc-fix-assets.test.js +++ b/test/sdlc-fix-assets.test.js @@ -11,6 +11,39 @@ import { assertNeutralPrompt, readPrompt } from './helpers/prompt-doc.js'; const prompt = readPrompt('sdlc-fix'); const steps = prompt.slice(prompt.indexOf('## 1.'), prompt.indexOf('## 邊界')); +/** 定位那一步,與後面處理留言的步驟分開看 */ +const locate = steps.slice(0, steps.indexOf('## 2.')); + +test('第一步先看 PR 現況,終止狀態就停下來不白做工', () => { + assert.match(locate, /pr-watch\.js/); + assert.match(locate, /terminal/); + assert.match(locate, /白做工/, '要說明為什麼停:那棵工作樹已經該被清掉了'); +}); + +test('工作樹由工作包推導,不要求使用者自己切目錄', () => { + assert.match(locate, /worktree-ensure\.js/); + assert.match(locate, /branch/, '輸入是 pr-watch 給的分支名'); + assert.match(locate, /不必也不該由使用者自己去記/); + assert.match(locate, /都在那棵工作樹裡做/); +}); + +test('不存在就重建,並說明那是常態不是例外', () => { + assert.match(locate, /常態不是例外/); + assert.match(locate, /換一台機器/, '要說明為什麼工作樹常常不在'); + assert.match(locate, /同一套建立方式/, '重建不另寫一套'); +}); + +test('兩種擋下來的情況各自寫明處置,且不替使用者決定', () => { + assert.match(locate, /BRANCH_NOT_FOUND/); + assert.match(locate, /WORKTREE_PATH_TAKEN/); + assert.match(locate, /不要自己刪/); +}); + +test('邊界擋住「回主工作區處理留言」與「PR 結束了還繼續改」', () => { + const boundary = prompt.slice(prompt.indexOf('## 邊界')); + assert.match(boundary, /不在主工作區處理留言/); + assert.match(boundary, /已經合併或關閉時不繼續處理留言/); +}); test('正本平台中立,description 前綴正確', () => { assertNeutralPrompt(prompt, 'sdlc-fix'); @@ -71,7 +104,7 @@ test('一次問一題,選項含手動輸入', () => { }); test('該問的情況有列舉,不是一句「不確定就問」', () => { - const section = steps.slice(steps.indexOf('## 3.'), steps.indexOf('## 4.')); + const section = steps.slice(steps.indexOf('## 4.'), steps.indexOf('## 5.')); const bullets = section.match(/^- /gm) ?? []; assert.ok(bullets.length >= 3, `該問的情況要列得出來,只找到 ${bullets.length} 條`); assert.match(section, /推了新 commit/, '位置對不上是最常見的一種,要點名'); @@ -112,7 +145,7 @@ test('寫入前要求先試跑', () => { // ── 摘要 ─────────────────────────────────────────────────────────── test('摘要要逐則列出,並單獨點出沒有記號的那幾則', () => { - const section = steps.slice(steps.indexOf('## 5.')); + const section = steps.slice(steps.indexOf('## 6.')); assert.match(section, /逐則一行/); assert.match(section, /沒有留下記號的那幾則/); assert.match(section, /以為它們被跳過/, '要說出為什麼得單獨列');