The bot posted approvals with event "APPROVE". Gitea knows APPROVED,
REQUEST_CHANGES and COMMENT and stores any other value as a pending review
that only its author sees (routers/api/v1/repo/pull_review.go,
preparePullReviewType, default branch). Every approval since cdba8b62 is such
a pending draft: homelab #1270, #1278, #1287, #1318, homeassistant #385.
The bot also counted its own pending review as "reviewed at this head", so
those PRs were never reviewed again. A pending review no longer counts; the
sweep reviews them again, and Gitea submits the existing pending review with
the new event.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var emptySection = regexp.MustCompile(`(?i)^(none|nothing|n/a|-|—)\.?$`)
|
|
|
|
// cleanReport drops everything before the first "## " heading and every section that has no
|
|
// content or only "None". It returns "" when the answer has no heading at all.
|
|
func cleanReport(answer string) string {
|
|
var out []string
|
|
var heading string
|
|
var body []string
|
|
started := false
|
|
|
|
flush := func() {
|
|
if heading == "" {
|
|
return
|
|
}
|
|
var content []string
|
|
for _, l := range body {
|
|
if t := strings.TrimSpace(l); t != "" {
|
|
content = append(content, t)
|
|
}
|
|
}
|
|
if len(content) == 0 || (len(content) == 1 && emptySection.MatchString(content[0])) {
|
|
heading, body = "", nil
|
|
return
|
|
}
|
|
out = append(out, heading)
|
|
out = append(out, body...)
|
|
heading, body = "", nil
|
|
}
|
|
|
|
for _, line := range strings.Split(strings.ReplaceAll(answer, "\r\n", "\n"), "\n") {
|
|
if strings.HasPrefix(line, "## ") {
|
|
flush()
|
|
started = true
|
|
heading = line
|
|
continue
|
|
}
|
|
if started {
|
|
body = append(body, line)
|
|
}
|
|
}
|
|
flush()
|
|
return strings.TrimSpace(strings.Join(out, "\n"))
|
|
}
|
|
|
|
func hasHeading(report, prefix string) bool {
|
|
for _, line := range strings.Split(report, "\n") {
|
|
if strings.HasPrefix(line, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var verdictLine = regexp.MustCompile(`(?i)^verdict:\s*(request[-_ ]changes|comment|approve)\s*$`)
|
|
|
|
// splitVerdict takes the agent's explicit verdict, its last non-empty line "Verdict: <value>",
|
|
// off the answer. The verdict is "" when the line is missing, which later reads as a plain comment.
|
|
func splitVerdict(answer string) (rest, verdict string) {
|
|
lines := strings.Split(strings.TrimRight(strings.ReplaceAll(answer, "\r\n", "\n"), " \t\n"), "\n")
|
|
if len(lines) == 0 {
|
|
return answer, ""
|
|
}
|
|
m := verdictLine.FindStringSubmatch(strings.TrimSpace(lines[len(lines)-1]))
|
|
if m == nil {
|
|
return answer, ""
|
|
}
|
|
return strings.Join(lines[:len(lines)-1], "\n"), strings.ToLower(strings.NewReplacer("_", "-", " ", "-").Replace(m[1]))
|
|
}
|
|
|
|
// Gitea's review events. Gitea stores any other value as a pending review only its author sees.
|
|
const (
|
|
eventApproved = "APPROVED"
|
|
eventRequestChanges = "REQUEST_CHANGES"
|
|
eventComment = "COMMENT"
|
|
)
|
|
|
|
// 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 red or yellow section, and anything else is a comment.
|
|
func reviewEvent(verdict, report string, nonEmptyChange bool) string {
|
|
switch verdict {
|
|
case "request-changes":
|
|
return eventRequestChanges
|
|
case "approve":
|
|
if nonEmptyChange && !hasHeading(report, "## 🔴") && !hasHeading(report, "## 🟡") {
|
|
return eventApproved
|
|
}
|
|
}
|
|
return eventComment
|
|
}
|