Skip to content

Commit 4d361bd

Browse files
Copilotamanintech
andauthored
Add GitHub Actions workflow to manage agentkit-challenge PR reviews (#114)
* Initial plan * Add workflow to review agentkit-challenge PRs and manage CodeRabbit feedback Agent-Logs-Url: https://github.com/Lamatic/AgentKit/sessions/b80785b0-d7a9-4b3d-91ef-29604087a2fa Co-authored-by: amanintech <28403318+amanintech@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: amanintech <28403318+amanintech@users.noreply.github.com>
1 parent 3861dbd commit 4d361bd

1 file changed

Lines changed: 274 additions & 0 deletions

File tree

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
name: Review AgentKit Challenge PRs
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dry_run:
7+
description: "Dry run (log actions without posting comments)"
8+
type: boolean
9+
default: false
10+
schedule:
11+
# Run Monday, Wednesday, Friday at 09:00 UTC
12+
- cron: "0 9 * * 1,3,5"
13+
14+
jobs:
15+
review-prs:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
pull-requests: write
19+
issues: write
20+
steps:
21+
- name: Review agentkit-challenge PRs
22+
uses: actions/github-script@v7
23+
with:
24+
github-token: ${{ secrets.GITHUB_TOKEN }}
25+
script: |
26+
// DRY_RUN is only applicable for workflow_dispatch events;
27+
// scheduled runs always post comments (never dry-run).
28+
const DRY_RUN = context.eventName === 'workflow_dispatch'
29+
? Boolean(${{ inputs.dry_run }})
30+
: false;
31+
const LABEL = 'agentkit-challenge';
32+
const CODERABBIT_LOGIN = 'coderabbitai[bot]';
33+
34+
// Unique markers embedded in bot comments to prevent duplicates
35+
const MARKER_CHANGES = '<!-- agentkit-review-bot: changes-requested -->';
36+
const MARKER_COMMENTS = '<!-- agentkit-review-bot: actionable-comments -->';
37+
38+
// Messages
39+
const MSG_RESOLVE_CHANGES = (author) =>
40+
`${MARKER_CHANGES}\nHi @${author}! 👋\n\n` +
41+
`Before this PR can be reviewed by maintainers, please **resolve all comments and requested changes** from the CodeRabbit automated review.\n\n` +
42+
`Steps to follow:\n` +
43+
`1. Read through all CodeRabbit comments carefully\n` +
44+
`2. Address each issue raised (or reply explaining why you disagree)\n` +
45+
`3. Push your fixes as new commits\n` +
46+
`4. Once all issues are resolved, comment here so we can re-review\n\n` +
47+
`This helps keep the review process efficient for everyone. Thank you! 🙏`;
48+
49+
const MSG_TRIGGER_REVIEW =
50+
`@coderabbitai review`;
51+
52+
const MSG_RESOLVE_COMMENTS = (author) =>
53+
`${MARKER_COMMENTS}\nHi @${author}! 👋\n\n` +
54+
`CodeRabbit has posted actionable review comments on this PR. Please review and address them before requesting a maintainer review.\n\n` +
55+
`Once you've addressed the CodeRabbit feedback, please push your changes and we'll take another look. Thank you! 🙏`;
56+
57+
// Fetch all open PRs with the agentkit-challenge label
58+
const allPRs = await github.paginate(github.rest.pulls.list, {
59+
owner: context.repo.owner,
60+
repo: context.repo.repo,
61+
state: 'open',
62+
per_page: 100,
63+
});
64+
65+
const challengePRs = allPRs.filter(pr =>
66+
pr.labels.some(label => label.name === LABEL)
67+
);
68+
69+
console.log(`Found ${challengePRs.length} open PRs with label '${LABEL}'`);
70+
71+
const summary = {
72+
noReview: [],
73+
changesRequested: [],
74+
actionableComments: [],
75+
clean: [],
76+
};
77+
78+
for (const pr of challengePRs) {
79+
const prNum = pr.number;
80+
const author = pr.user.login;
81+
console.log(`\nChecking PR #${prNum}: "${pr.title}" by @${author}`);
82+
83+
// Get all reviews for this PR
84+
const reviewsResp = await github.rest.pulls.listReviews({
85+
owner: context.repo.owner,
86+
repo: context.repo.repo,
87+
pull_number: prNum,
88+
});
89+
const reviews = reviewsResp.data;
90+
91+
const codeRabbitReviews = reviews.filter(r => r.user.login === CODERABBIT_LOGIN);
92+
93+
if (codeRabbitReviews.length === 0) {
94+
console.log(` → No CodeRabbit review found. Triggering review.`);
95+
summary.noReview.push(`#${prNum} by @${author} — "${pr.title}"`);
96+
if (!DRY_RUN) {
97+
await github.rest.issues.createComment({
98+
owner: context.repo.owner,
99+
repo: context.repo.repo,
100+
issue_number: prNum,
101+
body: MSG_TRIGGER_REVIEW,
102+
});
103+
console.log(` ✓ Triggered CodeRabbit review`);
104+
} else {
105+
console.log(` [DRY RUN] Would trigger CodeRabbit review`);
106+
}
107+
continue;
108+
}
109+
110+
// Get the latest CodeRabbit review state
111+
// Sort by submitted_at descending to get the latest
112+
const sortedCRReviews = codeRabbitReviews.sort(
113+
(a, b) => new Date(b.submitted_at) - new Date(a.submitted_at)
114+
);
115+
const latestCRReview = sortedCRReviews[0];
116+
const hasChangesRequested = codeRabbitReviews.some(
117+
r => r.state === 'CHANGES_REQUESTED'
118+
);
119+
120+
console.log(` Latest CodeRabbit state: ${latestCRReview.state}`);
121+
122+
// Check if the review is stale (latest commit after last CR review)
123+
const allCommitsResp = await github.rest.pulls.listCommits({
124+
owner: context.repo.owner,
125+
repo: context.repo.repo,
126+
pull_number: prNum,
127+
per_page: 100,
128+
});
129+
130+
const commits = allCommitsResp.data;
131+
// Sort commits by committer date to reliably get the latest,
132+
// regardless of the API response ordering.
133+
const sortedCommits = [...commits].sort(
134+
(a, b) => new Date(b.commit.committer.date) - new Date(a.commit.committer.date)
135+
);
136+
const latestCommitDate = sortedCommits.length > 0
137+
? new Date(sortedCommits[0].commit.committer.date)
138+
: null;
139+
140+
const latestCRDate = new Date(latestCRReview.submitted_at);
141+
const isStale = latestCommitDate && latestCommitDate > latestCRDate;
142+
143+
if (isStale) {
144+
console.log(` → CodeRabbit review is stale (new commits after last review). Triggering re-review.`);
145+
summary.noReview.push(`#${prNum} by @${author} — stale review, re-triggered`);
146+
if (!DRY_RUN) {
147+
await github.rest.issues.createComment({
148+
owner: context.repo.owner,
149+
repo: context.repo.repo,
150+
issue_number: prNum,
151+
body: MSG_TRIGGER_REVIEW,
152+
});
153+
console.log(` ✓ Triggered CodeRabbit re-review`);
154+
} else {
155+
console.log(` [DRY RUN] Would trigger CodeRabbit re-review`);
156+
}
157+
continue;
158+
}
159+
160+
if (hasChangesRequested) {
161+
console.log(` → Has CHANGES_REQUESTED from CodeRabbit. Asking author to resolve.`);
162+
summary.changesRequested.push(`#${prNum} by @${author} — "${pr.title}"`);
163+
164+
// Check if we already posted this message recently (avoid spam)
165+
const commentsResp = await github.rest.issues.listComments({
166+
owner: context.repo.owner,
167+
repo: context.repo.repo,
168+
issue_number: prNum,
169+
per_page: 100,
170+
});
171+
172+
const existingReminder = commentsResp.data.some(c =>
173+
c.user.login === 'github-actions[bot]' &&
174+
c.body.includes(MARKER_CHANGES)
175+
);
176+
177+
if (existingReminder) {
178+
console.log(` → Reminder already posted. Skipping.`);
179+
} else if (!DRY_RUN) {
180+
await github.rest.issues.createComment({
181+
owner: context.repo.owner,
182+
repo: context.repo.repo,
183+
issue_number: prNum,
184+
body: MSG_RESOLVE_CHANGES(author),
185+
});
186+
console.log(` ✓ Posted reminder to resolve CodeRabbit changes`);
187+
} else {
188+
console.log(` [DRY RUN] Would post reminder to resolve CodeRabbit changes`);
189+
}
190+
} else if (codeRabbitReviews.some(r =>
191+
r.state === 'COMMENTED' && r.body && r.body.includes('Actionable comments posted:')
192+
)) {
193+
// Has actionable comments from CodeRabbit (even without CHANGES_REQUESTED)
194+
// NOTE: This regex depends on CodeRabbit's comment format.
195+
// If CodeRabbit changes how it reports actionable comments, update this pattern.
196+
const actionableMatch = latestCRReview.body &&
197+
latestCRReview.body.match(/Actionable comments posted:\s*(\d+)/);
198+
const actionableCount = actionableMatch ? parseInt(actionableMatch[1]) : 0;
199+
200+
if (actionableCount > 0) {
201+
console.log(` → Has ${actionableCount} actionable comments. Asking author to address them.`);
202+
summary.actionableComments.push(
203+
`#${prNum} by @${author} — ${actionableCount} actionable comments — "${pr.title}"`
204+
);
205+
206+
const commentsResp = await github.rest.issues.listComments({
207+
owner: context.repo.owner,
208+
repo: context.repo.repo,
209+
issue_number: prNum,
210+
per_page: 100,
211+
});
212+
213+
const existingReminder = commentsResp.data.some(c =>
214+
c.user.login === 'github-actions[bot]' &&
215+
c.body.includes(MARKER_COMMENTS)
216+
);
217+
218+
if (existingReminder) {
219+
console.log(` → Reminder already posted. Skipping.`);
220+
} else if (!DRY_RUN) {
221+
await github.rest.issues.createComment({
222+
owner: context.repo.owner,
223+
repo: context.repo.repo,
224+
issue_number: prNum,
225+
body: MSG_RESOLVE_COMMENTS(author),
226+
});
227+
console.log(` ✓ Posted reminder to address CodeRabbit comments`);
228+
} else {
229+
console.log(` [DRY RUN] Would post reminder to address CodeRabbit comments`);
230+
}
231+
} else {
232+
console.log(` → CodeRabbit reviewed with no blocking comments. Ready for maintainer review.`);
233+
summary.clean.push(`#${prNum} by @${author} — "${pr.title}"`);
234+
}
235+
} else {
236+
console.log(` → No actionable CodeRabbit comments. May be ready for review.`);
237+
summary.clean.push(`#${prNum} by @${author} — "${pr.title}"`);
238+
}
239+
}
240+
241+
// Print summary
242+
core.summary
243+
.addHeading('AgentKit Challenge PR Review Summary', 2)
244+
.addRaw(`\n**Total PRs checked:** ${challengePRs.length}\n\n`);
245+
246+
if (summary.noReview.length > 0) {
247+
core.summary
248+
.addHeading('🔄 CodeRabbit Review Triggered (no/stale review)', 3)
249+
.addList(summary.noReview);
250+
}
251+
252+
if (summary.changesRequested.length > 0) {
253+
core.summary
254+
.addHeading('🔴 Changes Requested by CodeRabbit', 3)
255+
.addList(summary.changesRequested);
256+
}
257+
258+
if (summary.actionableComments.length > 0) {
259+
core.summary
260+
.addHeading('🟡 Actionable CodeRabbit Comments Pending', 3)
261+
.addList(summary.actionableComments);
262+
}
263+
264+
if (summary.clean.length > 0) {
265+
core.summary
266+
.addHeading('✅ Ready for Maintainer Review', 3)
267+
.addList(summary.clean);
268+
}
269+
270+
await core.summary.write();
271+
272+
if (DRY_RUN) {
273+
console.log('\n[DRY RUN] No comments were actually posted.');
274+
}

0 commit comments

Comments
 (0)