Skip to content

Commit 8d431ba

Browse files
Merge pull request #70 from gobackup/feat/add-inline-comment-ai-review
Change ai review stage to add inline comments to reference the exact …
2 parents 1be4374 + dad19c1 commit 8d431ba

1 file changed

Lines changed: 109 additions & 39 deletions

File tree

.github/workflows/ai-review.yml

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,22 @@ jobs:
6666
DIFF_CONTENT=$(cat pr_diff.txt | jq -Rs .)
6767
6868
# Prepare the review prompt
69-
PROMPT="Act as a senior Go and Kubernetes engineer. Review the diff and point out only real bugs, security concerns, or risky changes. Keep the review concise and specific, referencing files or resources when it helps. If there is nothing actionable to flag, respond with NO_ISSUES and nothing else.
69+
PROMPT="Act as a senior Go and Kubernetes engineer. Review the diff and flag only true bugs, security concerns, or risky changes. Return ONLY valid JSON matching this schema:
70+
{
71+
\"summary\": string | null,
72+
\"comments\": [
73+
{
74+
\"path\": \"relative/file.go\",
75+
\"line\": number, # line number in the PR head
76+
\"side\": \"RIGHT\" | \"LEFT\" (default RIGHT),
77+
\"body\": \"Explain the issue and impact.\",
78+
\"suggestion\": \"Optional replacement code without fences.\"
79+
}
80+
]
81+
}
82+
- Use comments array only for actionable findings. Leave it empty when there are no issues.
83+
- Omit suggestion when not needed. When present, include only the replacement code lines.
84+
- Set summary to a short overview or null. If there are no issues, respond with {\"summary\":\"NO_ISSUES\",\"comments\":[]}.
7085
7186
Code diff:
7287
$DIFF_CONTENT"
@@ -99,19 +114,34 @@ jobs:
99114
if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then
100115
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"')
101116
REVIEW="AI review failed: $ERROR_MSG"
102-
HAS_ISSUES=false
117+
echo "$REVIEW" >&2
103118
else
104-
TRIMMED=$(echo "$REVIEW" | tr -d '[:space:]')
105-
if [ -z "$TRIMMED" ] || [ "$TRIMMED" = "NO_ISSUES" ]; then
106-
HAS_ISSUES=false
107-
REVIEW="NO_ISSUES"
119+
echo "$REVIEW" > ai_raw_review.txt
120+
if ! echo "$REVIEW" | jq '.' > review.json 2>review_parse_error.log; then
121+
echo "AI review response was not valid JSON, skipping inline comments." >&2
122+
cat review_parse_error.log >&2
108123
else
109-
HAS_ISSUES=true
124+
COMMENTS_COUNT=$(jq '.comments | length' review.json 2>/dev/null || echo 0)
125+
SUMMARY=$(jq -r '.summary // ""' review.json)
126+
if [ "$COMMENTS_COUNT" -gt 0 ]; then
127+
HAS_ISSUES=true
128+
elif [ "$SUMMARY" = "NO_ISSUES" ]; then
129+
:
130+
else
131+
SUMMARY=$(echo "$SUMMARY" | tr -d '[:space:]')
132+
if [ -z "$SUMMARY" ]; then
133+
jq '.summary = "NO_ISSUES"' review.json > review.tmp && mv review.tmp review.json
134+
fi
135+
fi
110136
fi
111137
fi
112138
113-
# Save review to file
114-
echo "$REVIEW" > review.txt
139+
# Persist summary for diagnostics
140+
if [ -f review.json ]; then
141+
jq -r '.summary // "NO_ISSUES"' review.json 2>/dev/null > summary.txt || echo "$REVIEW" > summary.txt
142+
else
143+
echo "$REVIEW" > summary.txt
144+
fi
115145
echo "has_issues=$HAS_ISSUES" >> "$GITHUB_OUTPUT"
116146
117147
- name: Post AI Review Comment
@@ -121,37 +151,77 @@ jobs:
121151
github-token: ${{ secrets.GITHUB_TOKEN }}
122152
script: |
123153
const fs = require('fs');
124-
const reviewContent = fs.readFileSync('review.txt', 'utf8').trim();
125-
const marker = '<!-- ai-review -->';
126-
const header = '### AI Code Review (automated)\n\n';
127-
const comment = `${header}${reviewContent}\n\n${marker}`;
154+
const core = require('@actions/core');
155+
156+
if (!fs.existsSync('review.json')) {
157+
core.info('No structured review to post.');
158+
return;
159+
}
160+
161+
const data = JSON.parse(fs.readFileSync('review.json', 'utf8'));
162+
const summary = data.summary && data.summary !== 'NO_ISSUES' ? data.summary : '';
163+
const comments = Array.isArray(data.comments) ? data.comments : [];
164+
165+
const formattedComments = comments
166+
.map((item, index) => {
167+
if (!item || !item.path || item.path.trim() === '' || item.line === undefined || item.body === undefined) {
168+
core.warning(`Skipping malformed comment at index ${index}`);
169+
return null;
170+
}
171+
172+
const lineNumber = Number(item.line);
173+
if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
174+
core.warning(`Invalid line number for comment at index ${index}`);
175+
return null;
176+
}
177+
178+
const body = String(item.body || '').trim();
179+
if (!body) {
180+
core.warning(`Empty body for comment at index ${index}`);
181+
return null;
182+
}
183+
184+
const parts = [body];
128185
129-
// Check if there's already an AI review comment
130-
const { data: comments } = await github.rest.issues.listComments({
186+
if (item.suggestion) {
187+
const suggestion = String(item.suggestion).trimEnd();
188+
if (suggestion) {
189+
parts.push('```suggestion');
190+
parts.push(suggestion);
191+
parts.push('```');
192+
}
193+
}
194+
195+
return {
196+
path: item.path,
197+
line: lineNumber,
198+
side: item.side === 'LEFT' ? 'LEFT' : 'RIGHT',
199+
body: parts.join('\n\n')
200+
};
201+
})
202+
.filter(Boolean);
203+
204+
if (!formattedComments.length && !summary) {
205+
core.info('AI review found no actionable comments.');
206+
return;
207+
}
208+
209+
let reviewBody = summary ? summary.trim() : '';
210+
if (formattedComments.length) {
211+
reviewBody = reviewBody ? `${reviewBody}\n\n@copilot please review these suggestions.` : '@copilot please review these suggestions.';
212+
}
213+
214+
if (!reviewBody) {
215+
reviewBody = '@copilot please review this change.';
216+
}
217+
218+
await github.rest.pulls.createReview({
131219
owner: context.repo.owner,
132220
repo: context.repo.repo,
133-
issue_number: ${{ inputs.pr_number }},
221+
pull_number: Number(process.env.PR_NUMBER),
222+
event: 'COMMENT',
223+
body: reviewBody,
224+
comments: formattedComments
134225
});
135-
136-
const botComment = comments.find(comment =>
137-
comment.user.type === 'Bot' &&
138-
comment.body.includes(marker)
139-
);
140-
141-
if (botComment) {
142-
// Update existing comment
143-
await github.rest.issues.updateComment({
144-
owner: context.repo.owner,
145-
repo: context.repo.repo,
146-
comment_id: botComment.id,
147-
body: comment
148-
});
149-
} else {
150-
// Create new comment
151-
await github.rest.issues.createComment({
152-
owner: context.repo.owner,
153-
repo: context.repo.repo,
154-
issue_number: ${{ inputs.pr_number }},
155-
body: comment
156-
});
157-
}
226+
env:
227+
PR_NUMBER: ${{ inputs.pr_number }}

0 commit comments

Comments
 (0)