🤖 Implement Gemini PR Pre-review Reflex

This PR adds a new reflex action to the Gemini CLI Bot that automatically provides high-level pre-reviews for new pull requests.

### Changes:
- Created `tools/gemini-cli-bot/reflexes/scripts/pr_pre_review.ts`: A reflex script that uses Gemini to evaluate PRs based on quality, roadmap alignment, "obvious wins," and repository best practices.
- Updated `.github/workflows/gemini-cli-bot-pulse.yml`: Added `GEMINI_API_KEY` and ensure the Gemini CLI is bundled before running reflex actions.

### Impact:
- **Time to First Response**: Will decrease as the bot provides initial feedback within 30 minutes of PR submission.
- **Maintainer Productivity**: High-level assessments and roadmap alignment checks will help maintainers prioritize reviews.
- **Contributor Experience**: Immediate feedback on best practices and roadmap alignment.

This addresses the request in issue #26471.
This commit is contained in:
gemini-cli[bot]
2026-05-04 22:28:40 +00:00
parent a79da4f3a9
commit 923a692acb
2 changed files with 188 additions and 0 deletions
@@ -34,9 +34,14 @@ jobs:
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Build Gemini CLI'
run: 'npm run bundle'
- name: 'Run Reflex Processes'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
GEMINI_MODEL: 'gemini-3-flash-preview'
run: |
if [ -d "tools/gemini-cli-bot/reflexes/scripts" ] && [ "$(ls -A tools/gemini-cli-bot/reflexes/scripts)" ]; then
for script in tools/gemini-cli-bot/reflexes/scripts/*.ts; do
@@ -0,0 +1,183 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { execFileSync, execSync } from 'node:child_process';
import { readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs';
import { join } from 'node:path';
const REVIEW_HEADER = '## 🤖 Gemini Bot Pre-review';
/**
* Runs a shell command and returns the output.
*/
function runCommand(cmd: string, args: string[], options: any = {}): string {
try {
return execFileSync(cmd, args, {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
...options,
}).trim();
} catch (err) {
// Silence errors for expected failures or missing data
return '';
}
}
async function main() {
console.log('Starting Gemini PR Pre-review Reflex...');
// 1. Get open PRs
const prsJson = runCommand('gh', [
'pr',
'list',
'--state',
'open',
'--limit',
'20',
'--json',
'number,title,body,author,labels',
]);
if (!prsJson) {
console.log('No open PRs found or gh command failed.');
return;
}
let prs;
try {
prs = JSON.parse(prsJson);
} catch (err) {
console.error('Failed to parse PR list JSON.');
return;
}
for (const pr of prs) {
// Skip bot PRs
if (pr.author.login.includes('[bot]') || pr.author.login.includes('robot')) {
continue;
}
// 2. Check if already reviewed by the bot
const commentsJson = runCommand('gh', [
'pr',
'view',
pr.number.toString(),
'--json',
'comments',
]);
if (!commentsJson) continue;
let comments;
try {
comments = JSON.parse(commentsJson).comments;
} catch (err) {
continue;
}
const alreadyReviewed = comments.some(
(c: any) => c.body && c.body.includes(REVIEW_HEADER),
);
if (alreadyReviewed) {
console.log(`PR #${pr.number} already has a Gemini pre-review. Skipping.`);
continue;
}
console.log(`Pre-reviewing PR #${pr.number}: ${pr.title}`);
// 3. Get Diff
const diff = runCommand('gh', ['pr', 'diff', pr.number.toString()]);
if (!diff) {
console.log(`Could not get diff for PR #${pr.number}.`);
continue;
}
// 4. Prepare Prompt
const roadmapPath = join(process.cwd(), 'ROADMAP.md');
const roadmap = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : 'No roadmap found.';
const prompt = `
You are the Gemini CLI Bot. Your task is to perform a pre-review of a Pull Request.
Provide a high-level assessment based on:
1. **Objective measures of quality**: (code structure, tests, documentation, performance).
2. **Alignment with the project roadmap**: (roadmap provided below).
3. **Identification of "obvious wins"**: (significant improvements, critical bug fixes, "good to go" small PRs).
4. **Conformity with TypeScript and repository best practices**: (license headers, type safety, naming conventions).
Roadmap:
${roadmap}
PR Title: ${pr.title}
PR Body: ${pr.body || 'No description provided.'}
PR Diff:
${diff.substring(0, 50000)}
Output your review in Markdown.
Rules:
- Start with the header: ${REVIEW_HEADER}
- Use sections for the 4 points above.
- If you find a "roadmap match", mention it explicitly.
- If it's an "obvious win", recommend it for fast-track review.
- Suggest 1-3 labels if appropriate (e.g., area/core, status/needs-tests, priority/p2).
- Be concise, professional, and encouraging.
- Do NOT use tools. Just output the text of the review.
`;
// 5. Run Gemini
const promptFile = join(process.cwd(), `pr_prompt_${pr.number}.md`);
writeFileSync(promptFile, prompt);
let review = '';
try {
const geminiPath = join(process.cwd(), 'bundle', 'gemini.js');
if (existsSync(geminiPath)) {
review = execFileSync('node', [
geminiPath,
'--prompt-file', promptFile
], {
encoding: 'utf-8',
env: {
...process.env,
GEMINI_CLI_TRUST_WORKSPACE: 'true',
GEMINI_MODEL: process.env.GEMINI_MODEL || 'gemini-3-flash-preview'
}
});
} else {
console.error('bundle/gemini.js not found. Cannot perform review.');
continue;
}
} catch (err) {
console.error(`Error running Gemini for PR #${pr.number}:`, err);
continue;
} finally {
if (existsSync(promptFile)) rmSync(promptFile);
}
if (!review || review.trim().length === 0) {
console.log(`Gemini returned an empty review for PR #${pr.number}.`);
continue;
}
// 6. Post Comment
const commentFile = join(process.cwd(), `pr_review_${pr.number}.md`);
writeFileSync(commentFile, review);
try {
execFileSync('gh', ['pr', 'comment', pr.number.toString(), '--body-file', commentFile]);
console.log(`Successfully posted pre-review for PR #${pr.number}.`);
} catch (err) {
console.error(`Failed to post comment for PR #${pr.number}:`, err);
} finally {
if (existsSync(commentFile)) rmSync(commentFile);
}
}
}
main().catch((err) => {
console.error('Fatal error in PR pre-review reflex:', err);
process.exit(1);
});