fix(review-bot): let an unverified section no longer block approval
ci/woodpecker/pr/review-bot-test Pipeline failed
ci/woodpecker/pr/ci-scripts-test Pipeline was successful

The bot has no cluster access, so any PR citing live-cluster evidence
gets a ⚪ Unverified section and could never be approved (e.g. #1281).
Approval now requires only no 🔴/🟡 section, matching the review banner
("approves only when it found nothing red or yellow"); the prompt's
verdict rule is aligned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 08:47:10 +02:00
co-authored by Claude Opus 5.5
parent e031f79865
commit cc31d1860d
3 changed files with 6 additions and 4 deletions
+1 -1
View File
@@ -112,7 +112,7 @@ Verdict: <request-changes | comment | approve>
The last line of your answer is the verdict, exactly as written above, and nothing follows it. It is what the
bot acts on, so it must agree with your report: `request-changes` when the Blocking section lists at least one
verified defect, `approve` only when you have no blocking issue, suggestion or unverified point, and `comment`
verified defect, `approve` only when you have no blocking issue or suggestion, and `comment`
otherwise.
Be specific and concise. Quote only the minimal lines that carry a point.
+2 -2
View File
@@ -76,13 +76,13 @@ func splitVerdict(answer string) (rest, verdict string) {
// reviewEvent maps the agent's verdict to the Gitea review event. Changes are requested and
// comments posted as the agent said; an approval is only honoured for a non-empty change whose
// report holds no yellow or unverified section, and anything else is a comment.
// report holds no red or yellow section, and anything else is a comment.
func reviewEvent(verdict, report string, nonEmptyChange bool) string {
switch verdict {
case "request-changes":
return "REQUEST_CHANGES"
case "approve":
if nonEmptyChange && !hasHeading(report, "## 🔴") && !hasHeading(report, "## 🟡") && !hasHeading(report, "## ⚪") {
if nonEmptyChange && !hasHeading(report, "## 🔴") && !hasHeading(report, "## 🟡") {
return "APPROVE"
}
}
+3 -1
View File
@@ -137,7 +137,8 @@ func TestSplitVerdict(t *testing.T) {
func TestReviewEvent(t *testing.T) {
const clean = "## Summary\n\nx\n\n## Recommendation\n\nz"
const yellow = "## Summary\n\nx\n\n## 🟡 Suggestions\n\n- a\n\n## Recommendation\n\nz"
const red = "## Summary\n\nx\n\n## 🔴 Blocking\n\nNone at this head; an earlier finding is fixed.\n\n## Recommendation\n\nz"
const unverified = "## Summary\n\nx\n\n## ⚪ Unverified\n\n- a\n\n## Recommendation\n\nz"
const red ="## Summary\n\nx\n\n## 🔴 Blocking\n\nNone at this head; an earlier finding is fixed.\n\n## Recommendation\n\nz"
cases := []struct {
name, verdict, report string
nonEmpty bool
@@ -148,6 +149,7 @@ func TestReviewEvent(t *testing.T) {
{"approve", "approve", clean, true, "APPROVE"},
{"approve with a yellow section is only a comment", "approve", yellow, true, "COMMENT"},
{"approve with a red section is only a comment", "approve", red, true, "COMMENT"},
{"approve with an unverified section approves", "approve", unverified, true, "APPROVE"},
{"an empty change never approves", "approve", clean, false, "COMMENT"},
{"comment", "comment", yellow, true, "COMMENT"},
{"no verdict is a comment", "", clean, true, "COMMENT"},