Skip to content
This repository was archived by the owner on Jun 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
807 changes: 584 additions & 223 deletions README.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: 'Flutter AI Code Reviewer (Gemini)'
description: 'Automated Flutter/Dart code review using Google Gemini AI with Clean Architecture standards'
name: 'Flutter AI Code Reviewer (OpenRouter)'
description: 'Automated Flutter/Dart code review using OpenRouter AI with Clean Architecture standards'
author: 'vincetran'

branding:
icon: 'check-circle'
color: 'blue'

inputs:
gemini-api-key:
description: 'Google Gemini API key for AI code review'
openrouter-api-key:
description: 'OpenRouter API key for AI code review'
required: true
github-token:
description: 'GitHub token for posting PR comments (use secrets.GITHUB_TOKEN)'
Expand All @@ -32,10 +32,10 @@ runs:
python -m pip install --upgrade pip
pip install -r ${{ github.action_path }}/scripts/requirements.txt

- name: Run Gemini AI Code Reviewer
- name: Run OpenRouter AI Code Reviewer
shell: bash
env:
GEMINI_API_KEY: ${{ inputs.gemini-api-key }}
OPENROUTER_API_KEY: ${{ inputs.openrouter-api-key }}
GITHUB_TOKEN: ${{ inputs.github-token }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF: ${{ github.ref }}
Expand Down
16 changes: 8 additions & 8 deletions scripts/ai_review.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""AI reviewer script for GitHub Actions using Google Gemini.
"""AI reviewer script for GitHub Actions using OpenRouter AI.

This is the main orchestrator that coordinates all components:
- Configuration validation
Expand All @@ -11,7 +11,7 @@
For detailed implementation, see the reviewer package modules:
- config.py: Configuration and environment variables
- github_client.py: GitHub API operations
- gemini_client.py: Gemini AI integration
- openrouter_client.py: OpenRouter AI integration
- prompt_builder.py: Prompt construction
- utils.py: Helper functions
"""
Expand All @@ -20,7 +20,7 @@

from reviewer.config import Config
from reviewer.github_client import GitHubClient, GitHubAPIError
from reviewer.gemini_client import GeminiClient, GeminiAPIError
from reviewer.openrouter_client import OpenRouterClient, OpenRouterAPIError
from reviewer.prompt_builder import PromptBuilder
from reviewer.utils import (
get_pr_number_from_ref,
Expand Down Expand Up @@ -53,7 +53,7 @@ def main():

# Initialize clients
github_client = GitHubClient(Config.GITHUB_REPOSITORY, Config.GITHUB_TOKEN)
gemini_client = GeminiClient(Config.GEMINI_API_KEY)
openrouter_client = OpenRouterClient(Config.OPENROUTER_API_KEY)
prompt_builder = PromptBuilder(Config.REVIEW_LANGUAGE)

# Step 1: Fetch PR diff
Expand All @@ -76,17 +76,17 @@ def main():
print(f"💬 Reviewing chunk {idx + 1}/{len(prompt_chunks)} "
f"({len(chunk.files)} files: {', '.join(chunk.files[:3])}...)")
else:
print("💬 Sending prompt to Gemini AI...")
print("💬 Sending prompt to OpenRouter AI...")

review = gemini_client.generate_review(prompt)
review = openrouter_client.generate_review(prompt)
all_reviews.append({
'chunk_index': idx,
'files': chunk.files,
'review': review
})

except GeminiAPIError as e:
print(f"❌ Gemini call failed for chunk {idx + 1}: {e}")
except OpenRouterAPIError as e:
print(f"❌ OpenRouter call failed for chunk {idx + 1}: {e}")

# If first chunk fails, post fallback comment and exit
if idx == 0:
Expand Down
1 change: 0 additions & 1 deletion scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
google-generativeai>=0.8.3
requests>=2.32.3
10 changes: 5 additions & 5 deletions scripts/reviewer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""AI Code Reviewer Package for Flutter/Dart projects using Google Gemini."""
"""AI Code Reviewer Package for Flutter/Dart projects using OpenRouter AI."""

__version__ = "1.0.0"
__version__ = "3.0.0"

from .config import Config
from .github_client import GitHubClient, GitHubAPIError
from .gemini_client import GeminiClient, GeminiAPIError
from .openrouter_client import OpenRouterClient, OpenRouterAPIError
from .prompt_builder import PromptBuilder
from .diff_chunker import DiffChunker, DiffChunk
from .utils import (
Expand All @@ -18,8 +18,8 @@
"Config",
"GitHubClient",
"GitHubAPIError",
"GeminiClient",
"GeminiAPIError",
"OpenRouterClient",
"OpenRouterAPIError",
"PromptBuilder",
"DiffChunker",
"DiffChunk",
Expand Down
64 changes: 28 additions & 36 deletions scripts/reviewer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,42 @@ class Config:
"""Configuration class for managing environment variables and constants."""

# Environment variables
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
GITHUB_REF = os.getenv("GITHUB_REF", "")
REVIEW_LANGUAGE = os.getenv("REVIEW_LANGUAGE", "vietnamese").lower()

# OpenRouter model configuration
# Model is controlled by project maintainers, users cannot override
# Change this value here to switch models:
# Free options:
# - "x-ai/grok-4.1-fast:free" (Free, fast, supports reasoning)
# - "google/gemini-2.0-flash-exp:free" (Free, high quality)
# Paid options:
# - "anthropic/claude-3.5-sonnet" (Excellent for code review)
# - "openai/gpt-4-turbo" (High quality)
OPENROUTER_MODEL = "x-ai/grok-4.1-fast:free"

# Constants
MAX_DIFF_LENGTH = 100000 # Limit diff size to avoid huge token payloads (increased from 12k)
MAX_COMMENT_LENGTH = 60000 # GitHub has 65,536 char limit, use 60k for safety
COMMENT_HEADER = "🤖 **AI Code Review - Flutter (Gemini)**\n\n"
COMMENT_HEADER = "🤖 **AI Code Review - Flutter (OpenRouter)**\n\n"

# Diff processing settings
WARN_DIFF_TRUNCATED = True # Warn in prompt if diff was truncated

# Gemini model preferences (ordered by priority)
# Flash models have higher quota (15 RPM) vs Pro models (2 RPM)
PREFERRED_MODEL_PATTERNS = [
'flash-latest', # Best: gemini-flash-latest (15 RPM free tier)
'2.5-flash', # Good: gemini-2.5-flash
'2.0-flash', # Good: gemini-2.0-flash
'flash', # Any flash model
'pro-latest', # Lower priority: gemini-pro-latest (2 RPM only!)
'pro', # Fallback: any pro model
]

# Fallback models if detection fails
FALLBACK_MODELS = [
'models/gemini-flash-latest',
'models/gemini-2.5-flash',
'models/gemini-2.0-flash',
'models/gemini-pro-latest',
]

# Models to exclude (special purpose models)
EXCLUDED_MODEL_KEYWORDS = [
'thinking', 'tts', 'image', 'gemma', 'learnlm'
]

# Gemini generation settings
# OpenRouter generation settings
GENERATION_CONFIG = {
"temperature": 0.7,
"top_p": 0.95,
"top_k": 40,
"max_output_tokens": 32000, # Increased from 8192 to handle large reviews
"max_output_tokens": 32000, # Max tokens for response
}

# Enable reasoning for supported models (e.g., grok-4.1-fast)
# This is controlled by project, users cannot override
ENABLE_REASONING = True # Set to False to disable reasoning

# Retry configuration
MAX_RETRIES = 2
INITIAL_RETRY_DELAY = 5 # seconds
Expand All @@ -70,8 +60,8 @@ def validate(cls) -> list[str]:
"""
errors = []

if not cls.GEMINI_API_KEY:
errors.append("GEMINI_API_KEY is not set")
if not cls.OPENROUTER_API_KEY:
errors.append("OPENROUTER_API_KEY is not set")

if not cls.GITHUB_TOKEN:
errors.append("GITHUB_TOKEN is not set")
Expand All @@ -90,10 +80,12 @@ def print_debug_info(cls):
print("=" * 60)
print("🔍 Environment Variables Check:")
print("=" * 60)
print(f" GITHUB_REF: {cls.GITHUB_REF or '❌ NOT SET'}")
print(f" GITHUB_REPOSITORY: {cls.GITHUB_REPOSITORY or '❌ NOT SET'}")
print(f" GITHUB_TOKEN: {'✅ SET (' + cls.GITHUB_TOKEN[:8] + '...)' if cls.GITHUB_TOKEN else '❌ NOT SET'}")
print(f" GEMINI_API_KEY: {'✅ SET' if cls.GEMINI_API_KEY else '❌ NOT SET'}")
print(f" REVIEW_LANGUAGE: {cls.REVIEW_LANGUAGE}")
print(f" GITHUB_REF: {cls.GITHUB_REF or '❌ NOT SET'}")
print(f" GITHUB_REPOSITORY: {cls.GITHUB_REPOSITORY or '❌ NOT SET'}")
print(f" GITHUB_TOKEN: {'✅ SET (' + cls.GITHUB_TOKEN[:8] + '...)' if cls.GITHUB_TOKEN else '❌ NOT SET'}")
print(f" OPENROUTER_API_KEY: {'✅ SET' if cls.OPENROUTER_API_KEY else '❌ NOT SET'}")
print(f" OPENROUTER_MODEL: {cls.OPENROUTER_MODEL} (configured in code)")
print(f" REVIEW_LANGUAGE: {cls.REVIEW_LANGUAGE}")
print(f" ENABLE_REASONING: {cls.ENABLE_REASONING} (configured in code)")
print("=" * 60)
print()
Loading
Loading