14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
# File 'lib/ai_git/review.rb', line 14
def generate_review(diff, model_name)
raise "No staged changes to review" if diff.to_s.strip.empty?
prompt = <<~PROMPT
You are a senior software engineer conducting a thorough code review. Output ONLY the review — no explanations, no markdown preamble, no backticks, no preamble.
Here are the staged changes:
#{diff}
STRICT OUTPUT FORMAT (follow exactly):
## Summary
<2–5 bullet points describing what this change does at a high level. Each bullet starts with a verb.>
## Suggestions
<For each issue or improvement, use this format:>
**<File and location, e.g. "app/models/user.rb">**
- <Concise description of the issue or suggestion. Start with a verb. Be specific.>
- <Another suggestion for the same file, if applicable.>
<Repeat the file block for each file with suggestions. Omit files that look clean.>
## Verdict
<One of: ✅ Looks good | ⚠️ Minor issues | 🚨 Needs attention>
<One sentence explaining the verdict.>
RULES:
- Summary: describe WHAT the change does, not HOW the code looks. Focus on behaviour and intent.
- Suggestions: flag real issues — bugs, edge cases, security problems, missing error handling, naming confusion, performance concerns, missing tests. Do NOT invent problems. If a file is clean, omit it entirely.
- Be direct and constructive. No filler like "great job" or "consider possibly maybe".
- No line should exceed 72 characters.
- If the diff is a minor or trivial change (e.g. version bump, typo fix), keep the review brief.
EXAMPLES OF GOOD OUTPUT:
## Summary
- Add password reset flow with tokenised email verification
- Introduce PasswordResetMailer with expiry-aware token links
- Add DB migration for reset_token and reset_token_expires_at columns
## Suggestions
**app/models/user.rb**
- Ensure reset_token is invalidated after successful use to
prevent token reuse attacks.
- Add an index on reset_token column for fast lookup queries.
**app/controllers/passwords_controller.rb**
- Handle the case where the token has expired with a user-facing
error message rather than a silent redirect.
## Verdict
⚠️ Minor issues
Core logic is solid but token invalidation and expiry feedback
need addressing before merge.
---
## Summary
- Fix nil guard in ReportGenerator when preferences are absent
## Suggestions
## Verdict
✅ Looks good
Safe defensive fix with no side effects.
---
Now review the staged changes:
PROMPT
json_body = {
model: model_name,
prompt: prompt,
stream: false,
temperature: 0.2,
top_p: 0.9,
stop: ["\n\n\n", "```", "Here is", "The review"],
num_predict: 600
}.to_json
uri = URI("http://localhost:11434/api/generate")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request.body = json_body
response = Net::HTTP.start(uri.host, uri.port, read_timeout: 120) do |http|
http.request(request)
end
raise "Failed to connect to Ollama. Is it running?" unless response.is_a?(Net::HTTPSuccess)
data = JSON.parse(response.body)
review = data["response"].to_s.strip
review = review.gsub(/^(Here is|The review is|```|json|markdown)/i, "")
.gsub(/^>\s*/, "")
.gsub(/\\n/, "\n")
.strip
lines = review.lines.map(&:strip)
lines.reject! { |line| line.match?(/^(Here|Output|Generated|Based on|The changes)/i) }
review = lines.join("\n").strip
review = "No review generated." if review.empty?
review
end
|