Skip to content

Commit 3e16a5b

Browse files
authored
Merge pull request #2 from LimpidTech/fix/review-feedback
fix: address legitimate review feedback
2 parents 3fac9db + 306c426 commit 3e16a5b

5 files changed

Lines changed: 70 additions & 21 deletions

File tree

internal/diff/parse.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func Parse(diffText string) ([]File, error) {
4444
return nil, fmt.Errorf("parsing hunk header %q: %w", line, err)
4545
}
4646

47-
lineNum := hunk.StartLine
47+
lineNum := hunk.NewStartLine
4848
i++
4949

5050
for i < len(lines) && !strings.HasPrefix(lines[i], diffGitPrefix) && !strings.HasPrefix(lines[i], hunkPrefix) {
@@ -109,29 +109,57 @@ func resolvePathFromHeaders(lines []string, start int, fallback string) string {
109109
return fallback
110110
}
111111

112+
// parseHunkHeader parses "@@ -old,count +new,count @@" into a Hunk.
112113
func parseHunkHeader(line string) (Hunk, error) {
113-
atIdx := strings.Index(line, "+")
114-
if atIdx < 0 {
115-
return Hunk{}, fmt.Errorf("no + range in hunk header")
114+
// Find the range portion between the @@ markers
115+
// Format: @@ -old_start[,old_count] +new_start[,new_count] @@
116+
trimmed := strings.TrimPrefix(line, "@@ ")
117+
if end := strings.Index(trimmed, " @@"); end >= 0 {
118+
trimmed = trimmed[:end]
116119
}
117120

118-
rest := line[atIdx+1:]
119-
endIdx := strings.Index(rest, " ")
120-
if endIdx < 0 {
121-
endIdx = strings.Index(rest, hunkPrefix)
121+
parts := strings.SplitN(trimmed, " ", 2)
122+
if len(parts) < 2 {
123+
return Hunk{}, fmt.Errorf("invalid hunk header: %q", line)
122124
}
123-
if endIdx < 0 {
124-
endIdx = len(rest)
125+
126+
oldRange := strings.TrimPrefix(parts[0], "-")
127+
newRange := strings.TrimPrefix(parts[1], "+")
128+
129+
oldStart, oldCount, err := parseRange(oldRange)
130+
if err != nil {
131+
return Hunk{}, fmt.Errorf("parsing old range %q: %w", oldRange, err)
132+
}
133+
134+
newStart, newCount, err := parseRange(newRange)
135+
if err != nil {
136+
return Hunk{}, fmt.Errorf("parsing new range %q: %w", newRange, err)
125137
}
126138

127-
rangeStr := rest[:endIdx]
128-
parts := strings.SplitN(rangeStr, ",", 2)
129-
startLine, err := strconv.Atoi(parts[0])
139+
return Hunk{
140+
OldStartLine: oldStart,
141+
OldLineCount: oldCount,
142+
NewStartLine: newStart,
143+
NewLineCount: newCount,
144+
}, nil
145+
}
146+
147+
func parseRange(s string) (int, int, error) {
148+
parts := strings.SplitN(s, ",", 2)
149+
start, err := strconv.Atoi(parts[0])
130150
if err != nil {
131-
return Hunk{}, fmt.Errorf("parsing start line %q: %w", parts[0], err)
151+
return 0, 0, fmt.Errorf("parsing start: %w", err)
152+
}
153+
154+
count := 1
155+
if len(parts) == 2 {
156+
count, err = strconv.Atoi(parts[1])
157+
if err != nil {
158+
return 0, 0, fmt.Errorf("parsing count: %w", err)
159+
}
132160
}
133161

134-
return Hunk{StartLine: startLine}, nil
162+
return start, count, nil
135163
}
136164

137165
func trimLeadingSpace(s string) string {

internal/diff/types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ type Line struct {
1515
}
1616

1717
type Hunk struct {
18-
StartLine int
19-
Lines []Line
18+
OldStartLine int
19+
OldLineCount int
20+
NewStartLine int
21+
NewLineCount int
22+
Lines []Line
2023
}
2124

2225
type File struct {

internal/github/client.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import (
77
"io"
88
"net/http"
99
"strings"
10+
"time"
1011

1112
"github.com/monokrome/codereview/internal/review"
1213
)
1314

15+
const httpTimeout = 30 * time.Second
16+
1417
const apiBase = "https://api.github.com"
1518

1619
type Client struct {
@@ -21,7 +24,7 @@ type Client struct {
2124
func New(token string) *Client {
2225
return &Client{
2326
token: token,
24-
httpClient: &http.Client{},
27+
httpClient: &http.Client{Timeout: httpTimeout},
2528
}
2629
}
2730

@@ -60,6 +63,9 @@ func (c *Client) fetchAllPRComments(ctx context.Context, owner, repo string, prN
6063

6164
body, err := io.ReadAll(resp.Body)
6265
resp.Body.Close()
66+
if err != nil {
67+
return nil, fmt.Errorf("reading response: %w", err)
68+
}
6369

6470
if resp.StatusCode != http.StatusOK {
6571
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))

internal/prompt/prompt.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import (
99

1010
const systemTemplate = `You are a senior code reviewer. Review the provided unified diff and produce a JSON response.
1111
12+
Reviewer principles:
13+
- Review only the code changes in the diff. Do not critique project-level decisions like CI configuration, tech stack choices, runner OS, or deployment strategy.
14+
- Accept existing project conventions as intentional. If the codebase uses a particular pattern, do not suggest changing it unless the diff introduces an inconsistency with that pattern.
15+
- Do not make factual claims unless you are certain. If you are unsure whether something is correct (e.g., whether a version exists, whether an API behaves a certain way), say so rather than asserting.
16+
- Do not suggest adding dependencies, frameworks, or libraries unless the code has a clear bug that requires one.
17+
- Never repeat a comment you have already made. If your prior comments are listed below, do not raise the same point again. You may note whether prior feedback was addressed.
18+
- Focus on correctness, bugs, security issues, and logic errors over style preferences.
19+
- Be sparing with comments. A review with zero comments is perfectly valid if the code is correct.
20+
1221
Use these conventional comment labels:
1322
- "nit": style or trivial improvements that don't affect correctness
1423
- "suggestion": a better approach or alternative worth considering
@@ -37,7 +46,7 @@ Your response must be valid JSON matching this exact structure:
3746
]
3847
}
3948
40-
Rules:
49+
Output rules:
4150
- The "line" field must reference a valid line number from the diff (a line that was added or exists as context in the new file)
4251
- The "body" field must start with the label followed by a colon and space (e.g. "nit: unused import")
4352
- The "path" field must match a file path from the diff exactly
@@ -97,7 +106,7 @@ func Build(files []diff.File, instructions string, priorComments []PriorComment,
97106
fmt.Fprintf(&user, "--- a/%s\n+++ b/%s\n", f.Path, f.Path)
98107

99108
for _, h := range f.Hunks {
100-
fmt.Fprintf(&user, "@@ -%d +%d @@\n", h.StartLine, h.StartLine)
109+
fmt.Fprintf(&user, "@@ -%d,%d +%d,%d @@\n", h.OldStartLine, h.OldLineCount, h.NewStartLine, h.NewLineCount)
101110

102111
for _, l := range h.Lines {
103112
switch l.Kind {

internal/provider/gemini/gemini.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import (
77
"io"
88
"net/http"
99
"strings"
10+
"time"
1011

1112
"github.com/monokrome/codereview/internal/provider"
1213
)
1314

1415
const (
1516
DefaultModel = "gemini-2.5-flash"
1617
apiBaseURL = "https://generativelanguage.googleapis.com/v1beta/models"
18+
httpTimeout = 120 * time.Second
1719
)
1820

1921
type part struct {
@@ -70,7 +72,8 @@ func New(apiKey string, model string) provider.ReviewFunc {
7072

7173
httpReq.Header.Set("Content-Type", "application/json")
7274

73-
resp, err := http.DefaultClient.Do(httpReq)
75+
client := &http.Client{Timeout: httpTimeout}
76+
resp, err := client.Do(httpReq)
7477
if err != nil {
7578
return provider.Response{}, fmt.Errorf("calling Gemini API: %w", err)
7679
}

0 commit comments

Comments
 (0)