feat: always post fresh review, supersede old with collapsed body
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m2s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m25s

Closes #34

- Remove reviewUnchanged() skip logic — every push gets a fresh review
- Remove edit-in-place (PATCH same body) — always POST new
- Supersede old review: PATCH with struck-through banner + collapsed
  original body in <details> for historical reference
- Add commit footer to every review: 'Evaluated against <sha>'
- Remove --update-existing flag (no longer needed)
- Add CommitID field to Review struct
- Add TestBuildSupersededBody tests
This commit is contained in:
Rodin
2026-05-02 11:26:06 -07:00
parent dc450f7771
commit fdd75699d9
3 changed files with 88 additions and 134 deletions
+41 -54
View File
@@ -67,7 +67,6 @@ func main() {
patternsRepo := flag.String("patterns-repo", envOrDefault("PATTERNS_REPO", ""), "Repo with language patterns (e.g. rodin/elixir-patterns)")
patternsFiles := flag.String("patterns-files", envOrDefault("PATTERNS_FILES", "README.md"), "Comma-separated file paths to fetch from patterns repo")
dryRun := flag.Bool("dry-run", false, "Print review to stdout instead of posting")
updateExisting := flag.Bool("update-existing", envOrDefaultBool("UPDATE_EXISTING", true), "Delete previous review from same bot before posting (default true)")
llmTemp := flag.Float64("llm-temperature", envOrDefaultFloat("LLM_TEMPERATURE", 0), "LLM temperature (0 = server default)")
llmTimeout := flag.Int("llm-timeout", envOrDefaultInt("LLM_TIMEOUT", 300), "LLM request timeout in seconds (default 300)")
llmProvider := flag.String("llm-provider", envOrDefault("LLM_PROVIDER", "openai"), "LLM API provider: openai or anthropic")
@@ -279,6 +278,16 @@ func main() {
// Step 10: Format and post review
reviewBody := review.FormatMarkdown(result, *reviewerName)
// Add commit footer so readers know which commit was evaluated
if pr.Head.Sha != "" {
shortSHA := pr.Head.Sha
if len(shortSHA) > 8 {
shortSHA = shortSHA[:8]
}
reviewBody += fmt.Sprintf("\n\n---\n*Evaluated against %s*", shortSHA)
}
event := review.GiteaEvent(result.Verdict)
if *dryRun {
@@ -307,56 +316,33 @@ func main() {
}
// --- Review update strategy ---
// 1. No existing review → POST new
// 2. Existing review, same state → PATCH body in place (preserves threads)
// 3. Existing review, state change → PATCH old to "Superseded", POST new
if *updateExisting && *reviewerName != "" {
// Always post a fresh review on the current commit. If a previous review
// exists, mark it as superseded (preserves old threads as navigable history).
// Never skip posting — the fresh review must land on HEAD to clear stale badges.
if *reviewerName != "" {
existingReviews, err := giteaClient.ListReviews(ctx, owner, repoName, prNumber)
if err != nil {
slog.Warn("could not list existing reviews", "pr", prNumber, "error", err)
} else {
// Detect shared-token misconfiguration: if detected, skip all
// update logic (PATCH/supersede) to avoid clobbering a sibling's review.
// In shared-token mode, skip superseding to avoid clobbering sibling reviews.
sharedToken := hasSharedToken(existingReviews, sentinel)
if sharedToken {
slog.Warn("shared token mode: skipping update-in-place logic to avoid clobbering sibling review")
} else {
if !sharedToken {
existing := findOwnReview(existingReviews, sentinel)
if existing != nil {
if reviewUnchanged(existingReviews, reviewBody, event, sentinel) {
slog.Info("review unchanged from previous run; skipping to preserve threads", "pr", prNumber)
return
}
// Same state → PATCH in place
if existing.State == event {
commentID, err := giteaClient.GetTimelineReviewCommentID(ctx, owner, repoName, prNumber, sentinel)
if err != nil {
slog.Warn("could not find review comment ID, falling back to new post", "error", err)
} else {
if err := giteaClient.EditComment(ctx, owner, repoName, commentID, reviewBody); err != nil {
slog.Warn("could not edit review, falling back to new post", "comment_id", commentID, "error", err)
} else {
slog.Info("review updated in place", "comment_id", commentID, "pr", prNumber)
return
}
}
commentID, err := giteaClient.GetTimelineReviewCommentID(ctx, owner, repoName, prNumber, sentinel)
if err != nil {
slog.Warn("could not find old review comment ID for supersede", "error", err)
} else {
// State change → mark old as superseded, post new below
commentID, err := giteaClient.GetTimelineReviewCommentID(ctx, owner, repoName, prNumber, sentinel)
if err != nil {
slog.Warn("could not find old review comment ID", "error", err)
supersededBody := buildSupersededBody(existing.Body, existing.CommitID, sentinel)
if err := giteaClient.EditComment(ctx, owner, repoName, commentID, supersededBody); err != nil {
slog.Warn("could not mark old review as superseded", "comment_id", commentID, "error", err)
} else {
supersededBody := fmt.Sprintf("~~*This review has been superseded by a newer review below.*~~\n\n%s", sentinel)
if err := giteaClient.EditComment(ctx, owner, repoName, commentID, supersededBody); err != nil {
slog.Warn("could not mark old review as superseded", "comment_id", commentID, "error", err)
} else {
slog.Info("marked old review as superseded", "old_state", existing.State, "new_state", event, "pr", prNumber)
}
slog.Info("marked old review as superseded", "old_state", existing.State, "pr", prNumber)
}
}
}
} else {
slog.Warn("shared token mode: skipping supersede to avoid clobbering sibling review")
}
}
}
@@ -526,22 +512,23 @@ func validateReviewerName(name string) error {
return nil
}
// reviewUnchanged checks if an existing review with the same sentinel
// already has identical body and state. Returns true if a re-post would
// produce the same result (skip to preserve conversation threads).
func reviewUnchanged(reviews []gitea.Review, newBody, newEvent, sentinel string) bool {
for _, r := range reviews {
if r.Stale {
continue
}
if !strings.Contains(r.Body, sentinel) {
continue
}
if r.State == newEvent && r.Body == newBody {
return true
}
// buildSupersededBody creates the body for a superseded review: struck-through banner
// with collapsed original content and the commit it was evaluated against.
func buildSupersededBody(originalBody, commitSHA, sentinel string) string {
shortSHA := commitSHA
if len(shortSHA) > 8 {
shortSHA = shortSHA[:8]
}
return false
var sb strings.Builder
sb.WriteString("~~Original review~~\n\n")
sb.WriteString("**Superseded** \u2014 see current review for up-to-date findings.\n\n")
sb.WriteString("<details><summary>Previous findings (commit ")
sb.WriteString(shortSHA)
sb.WriteString(")</summary>\n\n")
sb.WriteString(originalBody)
sb.WriteString("\n\n</details>\n\n")
sb.WriteString(sentinel)
return sb.String()
}
// hasSharedToken detects if another review-bot role posted under the same