feat: inline review comments on specific lines
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 43s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m29s

Findings that reference a file+line within the diff are now posted as
inline comments directly on that line, in addition to appearing in the
summary table. Findings outside the diff range stay in the body only.

Implementation:
- gitea/diff.go: ParseDiffNewLines extracts new-file line numbers from
  each hunk in the unified diff
- gitea/client.go: PostReview accepts optional []ReviewComment with
  path + new_position + body (omitempty when nil)
- cmd/review-bot/main.go: maps findings → inline comments when the line
  exists in the diff, passes them to PostReview

Tests:
- diff parser: multi-hunk, new files, empty diff, boundary lines
- PostReview: with comments, nil comments (omitted from payload)
This commit is contained in:
Rodin
2026-05-01 21:59:21 -07:00
parent 177d56f218
commit 2ac7f55396
6 changed files with 274 additions and 9 deletions
+76
View File
@@ -0,0 +1,76 @@
package gitea
import (
"strconv"
"strings"
)
// DiffLineRanges maps filenames to the set of new-file line numbers present in the diff.
type DiffLineRanges struct {
files map[string]map[int]bool
}
// Contains reports whether the given file+line is within the diff hunks.
func (d *DiffLineRanges) Contains(file string, line int) bool {
if d == nil || d.files == nil {
return false
}
lines, ok := d.files[file]
if !ok {
return false
}
return lines[line]
}
// ParseDiffNewLines parses a unified diff and extracts the new-file line numbers
// that appear in each hunk (both added and context lines).
func ParseDiffNewLines(diff string) *DiffLineRanges {
result := &DiffLineRanges{files: make(map[string]map[int]bool)}
var currentFile string
var newLine int
for _, line := range strings.Split(diff, "\n") {
// Track current file from +++ header
if strings.HasPrefix(line, "+++ b/") {
currentFile = strings.TrimPrefix(line, "+++ b/")
if result.files[currentFile] == nil {
result.files[currentFile] = make(map[int]bool)
}
continue
}
if strings.HasPrefix(line, "+++ /dev/null") {
currentFile = ""
continue
}
// Parse hunk header: @@ -old,count +new,count @@
if strings.HasPrefix(line, "@@") && currentFile != "" {
// Extract the +N part
parts := strings.Split(line, "+")
if len(parts) >= 2 {
numStr := strings.Split(parts[1], ",")[0]
n, err := strconv.Atoi(numStr)
if err == nil {
newLine = n
}
}
continue
}
if currentFile == "" {
continue
}
// Count lines in hunk
if strings.HasPrefix(line, "+") || strings.HasPrefix(line, " ") {
result.files[currentFile][newLine] = true
newLine++
} else if strings.HasPrefix(line, "-") {
// Removed lines don't advance new line counter
continue
}
}
return result
}