fix(pr-watch): 試跑的預告與實跑的守門對齊,並說出路徑被佔住這件事

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>
This commit is contained in:
2026-09-17 17:06:07 +08:00
co-authored by Claude Opus 5
parent 32edd65962
commit 617a3eef72
7 changed files with 152 additions and 60 deletions
+23
View File
@@ -38,6 +38,29 @@ export async function readPullComments(login, repo, index, me) {
];
}
/**
* 讀這三類留言會發出哪些請求。兩支腳本的 `--dry-run` 都印它——預告與實際發出的請求
* 分開寫,加一個端點就會有一邊忘了改,而預告錯了等於沒有預告。
* @returns {{method: string, path: string}[]}
*/
export function plannedRequests(repo, index) {
const pullPath = `/repos/${repo}/pulls/${index}`;
return [
{ method: 'GET', path: '/user' },
{ method: 'GET', path: pullPath },
{ method: 'GET', path: `/repos/${repo}/issues/${index}/comments` },
{ method: 'GET', path: `${pullPath}/reviews` },
{ method: 'GET', path: `/repos/${repo}/issues/${index}/timeline` },
];
}
/**
* `plannedRequests` 列不完的那部分。與 readPullComments 同進退——說明的是它發出的請求。
*/
export const COMMENT_REQUEST_NOTE =
'每則一般留言還會各查一次 reaction、每個 review 還會各查一次它的行內留言;' +
'次數取決於留言數,事前無法列舉。';
/** 還沒被處理的則數。`/sdlc-fix` 要做的量,也是 `pr-watch` 的建議動作的依據。 */
export function unhandledCount(留言) {
return 留言.filter((comment) => !comment.已處理).length;