fix: 審核交棒複合訊號——等候審核/請合併形態不再誤判 author-idle(agents #53)

#51 的三就緒詞漏掉「等候審核」形態:alterminal#2 作者留言「無待修改
項目…等候 @chenyunda218 審核…請直接合併」實質已交棒,仍誤判
author-idle,巡邏回頭催已無待辦的作者。

IsReviewHandoff 改複合訊號:@目前審核者 ∧(就緒語 ∨(完成語 ∧
等候語))。就緒語補「請…審核」;等候語=等候/等待…審核、待審、
請…合併(間距容 @帳號);完成語=已完成/無待修改等。新增防護:
預告式(即將/稍後/之後/預計…、條件句「…後請審核」)與百分比進度
(完成 80%)一律不算——作者自述還沒到位不能解讀成已交棒;句讀阻斷
避免誤傷「合併後,我會回報」真交棒尾巴,「後續」除外。

gitea.py _is_review_handoff 同步(另 PR);測試案例兩側同集
(fox#60、alterminal#2 實例+17 個邊界)。
This commit is contained in:
2026-10-04 08:25:55 +08:00
parent 23f0cb3e16
commit 8ce8f410ed
3 changed files with 74 additions and 15 deletions
+50 -8
View File
@@ -202,27 +202,69 @@ func IsDeliveryReport(issueBody string, last *Comment) bool {
return deliveryHandoffRe.MatchString(body)
}
// ── 審核交棒判定(agents #51,同步 gitea.py _is_review_handoff)──────────
// ── 審核交棒判定(agents #51、#53,同步 gitea.py _is_review_handoff)──────
//
// PR 作者的最後一則留言已把審核交棒給審核者(@審核者帳號+就緒語)時,
// 停滯點不在作者:stalled 分類由 author-idle 改判 reviewer-idle,既有
// 48 小時改派規則自然適用。案例:fox#60 作者留言「PR 已就緒,可審閱」
// 並 @chenyunda218,teai stalled 仍判 author-idle——作者已無待辦。
// #53 補遺:alterminal#2 作者留言「等候 @chenyunda218 審核…無待修改
// 項目…請直接合併」——實質已交棒但無三就緒詞,仍誤判 author-idle。
var (
// reviewReadyRe 對應 gitea.py _REVIEW_READY_RE:PR 端就緒語。
reviewReadyRe = regexp.MustCompile(`已就緒|可審閱|請審閱`)
reviewReadyRe = regexp.MustCompile(`已就緒|可審閱|請審閱|請[^,。;\n]{0,8}審核`)
// reviewWaitMergeRe 對應 gitea.py _REVIEW_WAIT_MERGE_RE:等候審核語
//(#53)——「等候/等待…審核」「待審」與「請…合併」形態。R2/R3 允許
// @帳號夾在中間(「等候 @chenyunda218 審核」),故不含 @;
// 「即將/稍後…」的請求不合併語義,由前瞻防護 reviewHedgeRe 排除。
reviewWaitMergeRe = regexp.MustCompile(`(等候|等待)[^,。;\n]{0,12}(審核|審閱|review)|待審|請[^,。;\n]{0,20}合併`)
// reviewDoneRe 對應 gitea.py _REVIEW_DONE_RE:完成語(#53)——
// 作者自述 PR 內容已完成/無待辦。
reviewDoneRe = regexp.MustCompile(`已完成|已完成實作|全數完成|已完成全部|完成[^,。;\n]{0,6}(修改|調整|實作|更新)|無待修改|無待辦|沒有.{0,4}待修改|不再有.{0,4}待修改`)
// reviewPercentRe 對應 gitea.py _REVIEW_PERCENT_RE:百分比進度
//(#53)——「完成 80%」「已完成 90%」是進度回報,不是完成;命中時
// 整則留言視為未交棒(保守:作者仍有待辦,維持 author-idle)。
reviewPercentRe = regexp.MustCompile(`完成[^,。;\n]{0,6}[0-90-9]{1,3}\s*[%%]`)
// reviewHedgeRe 對應 gitea.py _REVIEW_HEDGE_RE:預告式防護(#51 既有
// 精神+#53 擴詞彙後更顯重要)——「即將/稍後/之後/預計+就緒/審核/
// review/完成/合併」與條件句「…後請審核/合併」(「實作完成後請審核」)
// 的前瞻字眼。命中時就緒語/等候語一律不算,保持 author-idle:作者
// 自己說還沒到位,不能解讀成已交棒。句讀(,。;)阻斷間距,避免
// 「合併後,我會回報」這類真交棒尾巴被誤傷;「後」後接「續」除外
//(「已完成修改,後續請審核」——完成已發生,非條件句)。
reviewHedgeRe = regexp.MustCompile(`(即將|稍後|之後|預計)[^,。;\n]{0,10}(就緒|審核|審閱|review|完成|合併)|後[^續,。;\n]{0,4}(審核|審閱|合併|review|就緒)`)
)
// isReviewHandoffSignal 判定留言文字是否命中交棒訊號(#53 複合訊號):
//
// 就緒語 ∨(完成語 ∧ 等候語)
//
// 兩路徑都先過防護:預告式(reviewHedgeRe)與百分比進度(reviewPercentRe)
// 命中時一律不算,保持 author-idle——作者自己說還沒到位/還在做,不能解讀
// 成已交棒。等候語與完成語同現才算:仍在實作的進度回報(「實作完成後請
// 審核」是條件句、無完成自述)不誤傷。
func isReviewHandoffSignal(body string) bool {
if reviewHedgeRe.MatchString(body) || reviewPercentRe.MatchString(body) {
return false
}
if reviewReadyRe.MatchString(body) {
return true
}
return reviewDoneRe.MatchString(body) && reviewWaitMergeRe.MatchString(body)
}
// IsReviewHandoff 判定 PR 作者的最後一則留言是否已把審核交棒給審核者
// (agents #51;gitea.py _is_review_handoff 的同語義實作)。
// (agents #51、#53;gitea.py _is_review_handoff 的同語義實作)。
//
// 審核交棒=「@審核者帳號」∧「就緒語」同時命中:作者已表明 PR 就緒、
// 明確點名目前指定的審核者,球在審核者——author-idle 改判 reviewer-idle。
// 審核交棒=「@審核者帳號」∧「交棒訊號」同時命中:作者已表明 PR 就緒
// (或已完成+等候審核)、明確點名目前指定的審核者,球在審核者——
// author-idle 改判 reviewer-idle。
//
// 只命中就緒語不算:進度回報(「快好了,即將就緒」)沒有點名對象,作者
// 只命中訊號不算:進度回報(「快好了,即將就緒」)沒有點名對象,作者
// 仍可能有待辦——保持 author-idle,修正不製造「丟一句已就緒就永不被催」
// 的新死角。@的帳號不在目前審核者清單也不算:交棒對象需為實際審核者。
// 的新死角。預告式(「即將就緒」「之後再審」)由 reviewHedgeRe 排除。
// @的帳號不在目前審核者清單也不算:交棒對象需為實際審核者。
func IsReviewHandoff(last *Comment, reviewers []string) bool {
if last == nil {
return false
@@ -231,7 +273,7 @@ func IsReviewHandoff(last *Comment, reviewers []string) bool {
if body == "" || len(reviewers) == 0 {
return false
}
if !reviewReadyRe.MatchString(body) {
if !isReviewHandoffSignal(body) {
return false
}
for _, r := range reviewers {
+17 -2
View File
@@ -180,11 +180,15 @@ func TestIsDeliveryReport(t *testing.T) {
}
}
// TestIsReviewHandoff(agents #51):審核交棒=@審核者帳號 ∧ 就緒語。
// 案例與 gitea.py _is_review_handoff 的離線驗證同集(fox#60 實例)。
// TestIsReviewHandoff(agents #51、#53):審核交棒=@審核者帳號 ∧ 交棒訊號
// (就緒語 ∨(完成語 ∧ 等候語))。案例與 gitea.py _is_review_handoff 的
// 離線驗證同集(fox#60、alterminal#2 實例)。
func TestIsReviewHandoff(t *testing.T) {
// fox#60 作者(alex)最後留言結尾(2026-09-16 08:05)。
fox60 := "狀態回報(作者端自查,分支無需再變更):\n\n- 已重新核對分支與 main 的差異…\n- PR 分支 CI 已通過(run 209)。\n\n@chenyunda218 PR 已就緒,可審閱。@ceo 已知悉管理者巡邏的備註;審核合併後,我會在下一個 main 部署 run 跑通時回 #58 驗收。"
// alterminal#2 作者(alex)最後留言節錄(2026-10-04 03:06,#53 案例):
// 有 @審核者+完成回報+「等候審核」+「請直接合併」,但無 #51 三就緒詞。
al2 := "狀態確認:PR #2 內容已依 issue #1 完成(commit e1f2516、分支 feat/dockerfile),交付回報已於 2026-10-03 22:59 留在 #1,包含建置驗證與煙霧測試結果;目前 mergeable、無待修改項目。\n\n等候 @chenyunda218 審核:若內容符合預期請直接合併(合併後 #1 即可關閉);若有修改意見請在 PR 留言,我會在原分支 feat/dockerfile 接續更新。若本週仍無法排入審核,請回報,以便另安排審核者。"
cases := []struct {
name string
comment string
@@ -192,11 +196,22 @@ func TestIsReviewHandoff(t *testing.T) {
want bool
}{
{"fox#60 實例(@審核者+已就緒,可審閱)", fox60, []string{"chenyunda218"}, true},
{"alterminal#2 實例(@審核者+無待修改+等候審核+請合併,#53)", al2, []string{"chenyunda218"}, true},
{"多位審核者命中其一", fox60, []string{"ceo", "chenyunda218"}, true},
{"前綴碰撞(@chenyunda218 ≠ chenyunda)", fox60, []string{"chenyunda"}, false},
{"就緒語但無 @(無點名對象)", fox60, []string{"dan"}, false},
{"進度回報(無就緒語)", "進行中:已改完兩步,CI 跑著,@chenyunda218 稍後再看。", []string{"chenyunda218"}, false},
{"預告式(即將就緒不算)", "@chenyunda218 快好了,即將整理完成。", []string{"chenyunda218"}, false},
{"預告式(之後再請審核不算)", "@chenyunda218 還有兩項要改,之後再請審核。", []string{"chenyunda218"}, false},
{"百分比進度(完成 80% 不算)", "@chenyunda218 進度:已完成 80%,剩下的明天補上,屆時再請合併。", []string{"chenyunda218"}, false},
{"仍在實作(有請合併但無完成自述)", "@chenyunda218 實作完成後請審核合併。", []string{"chenyunda218"}, false},
{"仍在實作(有等候審核但無完成自述)", "@chenyunda218 還在實作,等候審核語先記著:等待審核中。", []string{"chenyunda218"}, false},
{"等候審核+已完成(複合訊號命中)", "@chenyunda218 所有修改已完成,等待審核。", []string{"chenyunda218"}, true},
{"待審+無待辦(複合訊號命中)", "PR 無待辦,@chenyunda218 待審中。", []string{"chenyunda218"}, true},
{"請審核(點名就緒語命中)", "@chenyunda218 請審核。", []string{"chenyunda218"}, true},
{"請儘速審核(擴充就緒語命中)", "@chenyunda218 內容無待辦,請儘速審核。", []string{"chenyunda218"}, true},
{"預告式(預計完成後請合併不算)", "@chenyunda218 預計今晚完成,屆時請合併。", []string{"chenyunda218"}, false},
{"後續請審核(完成已發生,非條件句)", "@chenyunda218 所有修改已完成,後續請審核。", []string{"chenyunda218"}, true},
{"無審核者", fox60, nil, false},
{"空留言", "", []string{"chenyunda218"}, false},
}