Build an AI Code Review Script

Build a Python script that uses Gemini AI to review code diffs and return structured findings.

Introduction

30 Second Summary

Code reviews are one of the most important quality gates in software development. But reviewing every pull request line by line is time-consuming and easy to procrastinate on.

In this project, you will build a Python script that uses Gemini AI to analyze code diffs and return structured findings with file paths, line numbers, and severity ratings.

What You'll Build

You'll create a Python script that uses Gemini structured output and Pydantic models to analyze code diffs and return typed, parseable security findings.

By the end of this project, you'll have:

  • A Python script using Pydantic models and Gemini structured output to analyze code diffs and return typed JSON findings.
  • A Gemini API key configured and tested locally with a sample diff containing deliberate security vulnerabilities.
  • Verified output proving the script correctly identifies security issues like SQL injection and command injection with file paths, line numbers, and severity ratings.
  • Secret Mission: Add a severity threshold and exit codes so your script can fail a build when critical issues are found.

Are there any prerequisites?

None. This guide includes full setup instructions for Python, Git, GitHub, and Cursor. You do not need any prior experience with AI APIs.

Set Up Your Environment

Before you can build your AI code reviewer, you need a few tools in place.

In this step, you will install Python, set up a code editor, and create a GitHub repository where your project will live.

In this step, get ready to:
  • Install Python and Git in your terminal.
  • Install Cursor (or VS Code) and create a project folder.
  • Create a GitHub repository and clone it to your computer.
Install Python

Python is the programming language you will use to write the AI review script. You need version 3.11 or higher.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Check if Python is already installed by running this command:
python3 --version

✔️ I see a version number (3.11 or higher)

You are all set. Move on to the next substep.

ⓧ Command not found

You need to install Python. Follow the instructions for your operating system.

macOS

  • Open https://www.python.org/downloads/ in your browser.
  • Click the Download Python 3.13 button (or whatever the latest version is, as long as it is 3.11 or higher).
  • Open the downloaded .pkg file and follow the installer prompts.
  • Once installed, close and reopen your terminal, then run python3 --version again to confirm.

Windows

  • Open https://www.python.org/downloads/ in your browser.
  • Click the Download Python 3.13 button (or whatever the latest version is, as long as it is 3.11 or higher).
  • Run the installer. Check the box that says Add python.exe to PATH at the bottom of the first screen before clicking Install Now.
  • Once installed, close and reopen Command Prompt, then run python --version to confirm.
Windows uses python, not python3

On Windows, the command is typically python instead of python3. Use whichever one works when you check the version.

Still seeing an old version?

If you see a version below 3.11, download the latest from python.org and install it. Make sure to close and reopen your terminal after installing so the new version is picked up.

Help me install the correct version of Python.

Install Git

Git is a version control tool that tracks changes to your code. You need it to clone repositories, create branches, and push code to GitHub.

  • Check if Git is already installed by running this command in your terminal:
git --version

✔️ I see a version number

You are all set. Move on to the next substep.

ⓧ Command not found or no version shown

You need to install Git. Follow the instructions for your operating system.

macOS

  • When you ran git --version, macOS may have shown a dialog asking you to install Xcode Command Line Tools. Click Install and wait for the installation to finish.
  • If no dialog appeared, install the tools manually by running this command:
xcode-select --install
  • Once the installation completes, close and reopen your terminal, then run git --version again to confirm.

Windows

  • Open https://git-scm.com/download/win in your browser. The download should start automatically.
  • Run the downloaded installer and accept all the default options.
  • Once the installer finishes, close and reopen Command Prompt or PowerShell.
  • Verify the installation by running git --version in the new terminal window.

Linux

  • Install Git using your package manager by running this command:
sudo apt install git -y
  • Once the installation completes, run git --version to confirm.

You should see a version string like git version 2.40.0. Any version 2.x or higher works for this project.

Don't see a version number?

Make sure you closed and reopened your terminal after installing. On Windows, check that you ran the installer as an administrator. On macOS, the Xcode Command Line Tools installation can take several minutes.

Help me troubleshoot my Git installation.

Install Cursor and create your project folder

Cursor is an AI-powered code editor that makes writing code faster. You can also use VS Code if you prefer.

  • Open https://www.cursor.com/ in your browser and download the installer for your operating system.
  • Run the installer and follow the prompts to complete the installation.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Cursor and press Enter to open it.

Now create a project folder on your Desktop. Go back to your terminal and run these commands.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ai-code-reviewer
cd ai-code-reviewer
  • Confirm the folder was created by running:
ls

You should see the ai-code-reviewer folder on your Desktop. The folder is empty for now.

  • Open this folder in Cursor by clicking File in the top menu bar.
  • Click Open Folder.
  • Select the ai-code-reviewer folder on your Desktop, then click Open.

Don't see the folder on your Desktop?

Make sure you ran cd ~/Desktop before running mkdir. If the folder ended up somewhere else, delete it and try again from the Desktop.

Help me find or create my project folder.

Create a GitHub repository and clone it locally

GitHub is where your code will live online. It is also where GitHub Actions will run your AI reviewer automatically whenever someone opens a pull request.

  • Open https://github.com in your browser.
  • Sign in to your account. If you do not have one, click Sign up and follow the prompts to create a free account.
  • Click the + icon in the top-right corner of the page.
  • Click New repository.
  • Enter ai-code-reviewer in the Repository name field.
  • Leave the repository set to Public.
  • Check the box for Add a README file.
  • Click Create repository.

Why add a README?

Adding a README creates an initial commit on the main branch. This gives you a branch to compare against when you create pull requests later.

Now clone the repository to your computer so you can work on it locally.

  • On your new repository page, click the green Code button.
  • Make sure HTTPS is selected, then click the copy icon to copy the URL.
  • Go back to your terminal. Make sure you are on your Desktop by running:
cd ~/Desktop
  • Delete the empty folder you created earlier (you will replace it with the cloned repo):
rm -rf ai-code-reviewer
  • Clone your repository by running this command (replace the URL with the one you copied):
git clone [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/ai-code-reviewer.git"]]
  • Move into the cloned folder by running:
cd ai-code-reviewer

What did git clone do?

The git clone command downloaded your GitHub repository to your computer. It created a folder called ai-code-reviewer that is connected to GitHub, so you can push code changes back up later.

  • Confirm the clone worked by running:
ls

You should see README.md in the output. This is the file GitHub created when you checked the README box.

  • Open this folder in Cursor. In Cursor, click File in the top menu bar.
  • Click Open Folder.
  • Select the ai-code-reviewer folder on your Desktop (the cloned one with the README), then click Open.

You should see README.md listed in the Explorer sidebar on the left.

Git clone failed or asked for a password?

If you see an authentication error, GitHub may need you to sign in through the terminal. When prompted for a password, you need a Personal Access Token, not your GitHub password. Go to GitHub Settings > Developer Settings > Personal Access Tokens > Tokens (classic) to generate one.

Help me fix my git clone authentication issue.

Your environment is ready. Next up, you will get your Gemini API key so your script can talk to Google's AI model.

Get Your Gemini API Key

Your environment is ready and your repository is cloned locally. Now you need an API key so your script can talk to Gemini, Google's AI model.

An API key is a credential that authenticates your requests to the Gemini API. You will generate one in Google AI Studio, test it with a quick Python script, and then store it safely for later use as a GitHub Secret.

In this step, get ready to:
  • Sign in to Google AI Studio and generate a Gemini API key.
  • Test the API key by calling Gemini from a Python script.
  • Store the API key safely for use as a GitHub Secret later.
Sign in to Google AI Studio and generate an API key

Google AI Studio is a free web tool where you can manage API keys for the Gemini API. New users get a default project and API key created automatically after accepting the Terms of Service.

  • Open your browser and navigate to https://aistudio.google.com/.
  • Sign in with your Google account.
  • Accept the Terms of Service if prompted.
  • Click Get API key in the left sidebar (or from the dashboard).
  • Click Create API key.
  • If prompted, select the default Google Cloud project.
  • Copy the API key that appears. You will need it in the next substep.

What is an API key?

An API key is a unique string that identifies your application to a service. When your Python script sends a request to Gemini, the key tells Google who is making the request and which free tier quota to apply.

💡 No billing required

The Gemini API free tier is generous enough for this entire project. You do not need to set up billing or enter a credit card.

🙋‍♀️ Seeing a permission denied error?

Some users encounter this when browser extensions block the request. Try disabling ad blockers or privacy shields, then refresh the page and click Create API key again.

If the issue persists, create a new Google Cloud project (not linked to an organization) and try generating the key from that project instead.

help me fix a permission denied error when generating my Gemini API key in Google AI Studio

Test the API key with a quick Python script

Before building the full review script, confirm your key works by making a simple API call. This saves debugging time later.

  • Install the google-genai package by running this command in your terminal:

macOS

python3 -m pip install google-genai

Windows

python -m pip install google-genai

Linux

python3 -m pip install google-genai

What does this command do?

This installs Google's official Python SDK for the Gemini API. You will set up a proper virtual environment with a requirements.txt in the next step. For now, a quick global install is enough to verify your key.

Now set your API key as an environment variable so the SDK can find it automatically.

macOS

  • Set the environment variable by running this command (replace your-api-key-here with the key you copied):
export GEMINI_API_KEY=your-api-key-here

Windows

  • Set the environment variable by running this command (replace your-api-key-here with the key you copied):
$env:GEMINI_API_KEY="your-api-key-here"
  • Create a file called test_key.py in your ai-code-reviewer folder by running:

macOS

touch test_key.py

Windows

New-Item test_key.py

Linux

touch test_key.py
  • Open test_key.py in your editor and paste the following code:
from google import genai

# Create a client - it reads GEMINI_API_KEY from the environment automatically
client = genai.Client()

# Send a simple prompt to verify the connection works
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Say hello in one sentence."
)

print(response.text)

What does this code do?

`genai.Client()` creates a client that automatically reads the GEMINI_API_KEY environment variable you just set. No need to pass the key directly in code.

`client.models.generate_content()` sends a prompt to the gemini-2.5-flash model and returns the response. If this works, your key is valid.

  • Run the test script:

macOS

python3 test_key.py

Windows

python test_key.py

Linux

python3 test_key.py

You should see a short greeting printed in your terminal, something like "Hello there, how can I help you today?" The exact words will vary each time.

Seeing an error instead of a greeting?

If you see API key not valid, double-check that you copied the full key from Google AI Studio without any extra spaces.

If you see ModuleNotFoundError: No module named 'google.genai', make sure your pip install google-genai command completed successfully. Run it again if needed.

If you see GEMINI_API_KEY not set, you need to re-run the export or $env: command in the same terminal session where you run the script.

help me debug why my Gemini API test script is failing

  • Once you see a successful response, delete the test file. You will not need it for the rest of the project:
rm test_key.py
Store your API key safely

You will add this key as a GitHub Secret in Step 5. For now, save it somewhere you can retrieve it later.

  • Copy your API key into a password manager, a local notes file, or any secure place you will not lose it.
  • Do not commit your API key to your repository. It should never appear in code that gets pushed to GitHub.

Why not just hardcode the key?

Hardcoded keys in source code get leaked when repositories are made public (or when commit history is exposed). In Step 5, you will add this key as a GitHub Secret so it is encrypted at rest and only available to your workflow at runtime.

Your API key is confirmed working and stored safely. Next up, you'll set up an isolated Python environment and install the packages your AI reviewer needs.

Set Up Your Project Dependencies

Your API key is ready and confirmed working. Now you need to set up an isolated Python environment for your project.

Installing packages globally can create version conflicts between projects. A virtual environment solves this by giving your project its own private set of packages. You'll create one now and install the two libraries your AI reviewer depends on.

In this step, get ready to:
  • Create and activate a Python virtual environment.
  • Define your project dependencies in a requirements.txt file.
  • Install the packages your AI reviewer needs.
Create and activate a virtual environment

A virtual environment keeps your project's packages isolated from other Python projects on your machine. This prevents version conflicts between different projects.

  • In your terminal, make sure you're inside your ai-code-reviewer project folder. Create a virtual environment by running this command:

macOS

python3 -m venv venv

Windows

python -m venv venv

Linux

python3 -m venv venv
  • Activate the virtual environment by running this command:

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

Linux

source venv/bin/activate

You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Don't see (venv) in your prompt?

Make sure you ran the activation command for your operating system. On Windows, if you see a permissions error, try running your terminal as Administrator.

Why isn't my virtual environment activating?

Install your project dependencies

Now you need a requirements.txt file. This tells Python which packages your project depends on.

  • Create a file called requirements.txt in the root of your ai-code-reviewer folder. Copy the following code into it:
google-genai>=1.0.0
pydantic>=2.0.0

What are these packages?

google-genai is the official SDK for calling the Gemini API from Python. It handles authentication, requests, and structured output parsing.

pydantic is a data validation library that lets you define typed models. You will use it to describe the exact JSON shape Gemini must return.

  • Save requirements.txt.
  • Install the dependencies by running this command:
pip install -r requirements.txt

You should see output showing both packages being downloaded and installed successfully.

Seeing a permission error or 'pip not found'?

Make sure your virtual environment is still active. You should see (venv) in your prompt. If it disappeared, run the activation command again before installing.

Help me fix my pip install error.

Your dependencies are installed and isolated. Next up, you'll build the actual AI review script that uses Pydantic models and Gemini's structured output to produce typed JSON findings.

Build the AI Review Script

Your dependencies are installed and your virtual environment is active. Now it's time to build the Python script that actually analyzes code diffs and returns structured findings.

The key challenge here is getting predictable output from the AI. If you ask Gemini to review code and return results as freeform text, you get something different every time. You need typed, parseable JSON so downstream tools (like GitHub Actions) can reliably read the review findings.

In this step, get ready to:
  • Define Pydantic models that enforce the exact JSON structure Gemini must return.
  • Write the review function that sends a diff to Gemini and gets structured findings back.
Define Pydantic models for structured output

Gemini's structured output feature forces the model to return JSON that matches a schema you define. Instead of hoping the AI returns something parseable, you guarantee it.

You define this schema using Pydantic models. Each model is a Python class that declares what fields exist and what types they must be.

  • Create a scripts directory inside your project by running this command:
mkdir scripts
  • Confirm the directory exists by running:
ls

You should see scripts and requirements.txt listed in the output.

  • Create a new file called ai_review.py inside the scripts folder. Copy the following code into it:
import json
import sys

from google import genai
from google.genai import types
from pydantic import BaseModel


class ReviewFinding(BaseModel):
    file_path: str
    line_number: int
    severity: str
    description: str
    suggestion: str


class CodeReview(BaseModel):
    summary: str
    findings: list[ReviewFinding]

What does this code do?

ReviewFinding describes a single issue the AI found. It includes the file path, line number, severity level (HIGH, MEDIUM, or LOW), a description of the problem, and a suggestion for fixing it.

CodeReview is the top-level response. It contains an overall summary and a list of individual findings. When you pass CodeReview as the schema to Gemini, the model is forced to return JSON matching exactly these fields and types.

💡 Why no default values in these models?

The Gemini API does not support Pydantic models with default field values. If you add something like severity: str = "LOW", the API will raise a ValueError. Keep all fields plain with just a type annotation.

  • Save scripts/ai_review.py.
Write the review function

Now you will write the function that actually sends a code diff to Gemini and gets structured findings back. This is the core logic of your AI reviewer.

  • In scripts/ai_review.py, add the following function below the CodeReview class:
def review_diff(diff_text: str) -> CodeReview:
    """Send a code diff to Gemini and get structured review findings."""
    client = genai.Client()

    prompt = f"""You are an expert code reviewer. Analyze the following code diff and identify issues.

For each issue, provide:
- file_path: the path to the file (from the diff header, e.g. "app.py")
- line_number: the line number in the NEW version of the file where the issue exists
- severity: one of HIGH, MEDIUM, or LOW
- description: a clear explanation of the problem
- suggestion: how to fix it

Focus on:
1. Security vulnerabilities (HIGH severity)
2. Bug risks (HIGH or MEDIUM severity)
3. Performance issues (MEDIUM severity)
4. Best practice violations (LOW severity)

If the code looks good, return an empty findings list with a positive summary.

Code diff to review:

{diff_text}"""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt,
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=CodeReview,
        ),
    )

    return CodeReview.model_validate_json(response.text)

What does this function do?

genai.Client() creates the Gemini API client. It automatically reads your GEMINI_API_KEY environment variable, so you don't need to pass it in explicitly.

The config parameter is where the magic happens. Setting response_mime_type="application/json" tells Gemini to return JSON. Setting response_schema=CodeReview tells it exactly what shape that JSON must take.

CodeReview.model_validate_json(response.text) parses the JSON string back into a typed Python object. If Gemini somehow returned malformed data, this line would catch it with a validation error.

  • Save scripts/ai_review.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete scripts/ai_review.py file so far. Make sure your version matches:

import json
import sys

from google import genai
from google.genai import types
from pydantic import BaseModel


class ReviewFinding(BaseModel):
    file_path: str
    line_number: int
    severity: str
    description: str
    suggestion: str


class CodeReview(BaseModel):
    summary: str
    findings: list[ReviewFinding]


def review_diff(diff_text: str) -> CodeReview:
    """Send a code diff to Gemini and get structured review findings."""
    client = genai.Client()

    prompt = f"""You are an expert code reviewer. Analyze the following code diff and identify issues.

For each issue, provide:
- file_path: the path to the file (from the diff header, e.g. "app.py")
- line_number: the line number in the NEW version of the file where the issue exists
- severity: one of HIGH, MEDIUM, or LOW
- description: a clear explanation of the problem
- suggestion: how to fix it

Focus on:
1. Security vulnerabilities (HIGH severity)
2. Bug risks (HIGH or MEDIUM severity)
3. Performance issues (MEDIUM severity)
4. Best practice violations (LOW severity)

If the code looks good, return an empty findings list with a positive summary.

Code diff to review:

{diff_text}"""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt,
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=CodeReview,
        ),
    )

    return CodeReview.model_validate_json(response.text)

Why structured output instead of freeform text?

Without structured output, Gemini might return review findings as bullet points one time and numbered lists the next. Parsing that reliably is fragile and error-prone.

With structured output, you get guaranteed typed JSON every single time. Your GitHub Actions workflow can read the findings, extract line numbers, and post inline comments without worrying about parsing failures.

Your AI review script defines the data models and the core function. Next up, you'll test it locally with a sample diff full of deliberately vulnerable code to confirm Gemini catches real security issues.

Test Your Script Locally

Your AI review function is built, but you haven't seen it work yet. Before trusting it in a CI/CD pipeline, you need to confirm it can actually read a diff and return structured findings.

You'll start by feeding it a hardcoded sample diff with obvious security vulnerabilities. Once you see the JSON output, you'll upgrade the script to accept any file from the command line.

In this step, get ready to:
  • Create a sample diff file with deliberately insecure code.
  • Add a test entry point, run the script, and verify Gemini returns structured JSON.
  • Upgrade the entry point to accept command-line arguments for production use.
Create a sample diff with vulnerable code

Your script expects a unified diff as input. You need a realistic test file that contains obvious security problems so you can verify Gemini catches them.

  • Create a new file called sample_diff.txt inside your scripts folder by pasting the following content:
diff --git a/app.py b/app.py
index 1234567..abcdefg 100644
--- a/app.py
+++ b/app.py
@@ -1,3 +1,15 @@
 """Simple utility functions."""
 
+import subprocess
+import sqlite3
+
+
+def run_command(user_input):
+    """Execute a system command."""
+    subprocess.call(user_input, shell=True)
+
+
+def get_user(username):
+    """Fetch a user from the database."""
+    conn = sqlite3.connect("users.db")
+    query = f"SELECT * FROM users WHERE name = '{username}'"
+    return conn.execute(query).fetchone()

What does this diff contain?

This is a unified diff format, the same format Git produces when comparing code changes. It shows two functions being added to app.py:

  • run_command() passes user input directly to subprocess.call() with shell=True. This is a command injection vulnerability.
  • get_user() builds a SQL query using an f-string with unsanitized input. This is a SQL injection vulnerability.
  • Save scripts/sample_diff.txt.
  • Confirm the file exists by running:
ls scripts/

You should see both ai_review.py and sample_diff.txt listed in the output.

Only see ai_review.py?

Make sure you saved the file inside the scripts folder, not in the project root. The file path should be scripts/sample_diff.txt.

help me find where my sample_diff.txt file was saved

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete content of scripts/sample_diff.txt:

diff --git a/app.py b/app.py
index 1234567..abcdefg 100644
--- a/app.py
+++ b/app.py
@@ -1,3 +1,15 @@
 """Simple utility functions."""
 
+import subprocess
+import sqlite3
+
+
+def run_command(user_input):
+    """Execute a system command."""
+    subprocess.call(user_input, shell=True)
+
+
+def get_user(username):
+    """Fetch a user from the database."""
+    conn = sqlite3.connect("users.db")
+    query = f"SELECT * FROM users WHERE name = '{username}'"
+    return conn.execute(query).fetchone()
Add a test entry point and run the script

Before adding command-line argument handling, you want to confirm the core AI call works. A simple hardcoded entry point lets you test the function directly.

  • At the bottom of scripts/ai_review.py, add this test entry point:
if __name__ == "__main__":
    # Read the sample diff file for testing
    with open("scripts/sample_diff.txt", "r") as f:
        diff_content = f.read()

    review = review_diff(diff_content)
    print(json.dumps(review.model_dump(), indent=2))

What does this code do?

if __name__ == "__main__" means this code only runs when you execute the file directly (not when it's imported by another script).

It reads the sample diff you just created, sends it through review_diff(), and prints the structured JSON result. This is a quick way to verify the AI call works before adding real CLI argument handling.

  • Save scripts/ai_review.py.

The genai.Client() in your script automatically reads the GEMINI_API_KEY environment variable. You need to set this in your terminal before running the script.

  • Set your API key as an environment variable by running this command:

macOS

export GEMINI_API_KEY=your-api-key-here

Windows

$env:GEMINI_API_KEY="your-api-key-here"

Linux

export GEMINI_API_KEY=your-api-key-here
  • Replace your-api-key-here with the actual API key you generated in Google AI Studio.

Why not use a .env file?

Setting the variable directly in the terminal keeps things simple for testing. When this script runs in GitHub Actions later, the key will come from GitHub Secrets instead. No .env file needed in either case.

  • Run the AI review script by running this command:
python scripts/ai_review.py

After a few seconds, you should see structured JSON output printed to your terminal with a summary field and a findings array.

Seeing an authentication error?

If you see an error mentioning "API key not valid" or "GEMINI_API_KEY not found", make sure you ran the export (or $env: on Windows) command in the same terminal session where you're running the script. Opening a new terminal tab resets environment variables.

help me debug my GEMINI_API_KEY setup

Your output should look similar to this (the exact wording will vary since Gemini generates natural language descriptions):

{
  "summary": "This diff introduces two critical security vulnerabilities...",
  "findings": [
    {
      "file_path": "app.py",
      "line_number": 8,
      "severity": "HIGH",
      "description": "Command injection via subprocess.call with shell=True...",
      "suggestion": "Use subprocess.run with a list of arguments instead..."
    },
    {
      "file_path": "app.py",
      "line_number": 14,
      "severity": "HIGH",
      "description": "SQL injection via f-string query construction...",
      "suggestion": "Use parameterized queries instead..."
    }
  ]
}

What should I look for in the output?

Confirm these key properties in your JSON output:

  • file_path is app.py for both findings (extracted from the diff header).
  • severity is HIGH for both findings. These are critical security vulnerabilities.
  • line_number values point to the lines where the vulnerable code exists (around lines 8 and 14).
  • Each finding includes a description explaining the problem and a suggestion for fixing it.
Make your script command-line ready

The hardcoded file path works for testing, but in production your script needs to accept any diff as input. For GitHub Actions, the script will receive diff content via standard input or a file argument.

  • In scripts/ai_review.py, find the if __name__ == "__main__": block at the bottom of your file.
  • Find this code:
if __name__ == "__main__":
    # Read the sample diff file for testing
    with open("scripts/sample_diff.txt", "r") as f:
        diff_content = f.read()

    review = review_diff(diff_content)
    print(json.dumps(review.model_dump(), indent=2))
  • Replace the entire if __name__ block with this version that accepts command-line arguments:
if __name__ == "__main__":
    # Read diff from a file argument or from standard input
    if len(sys.argv) > 1:
        with open(sys.argv[1], "r") as f:
            diff_content = f.read()
    else:
        diff_content = sys.stdin.read()

    review = review_diff(diff_content)
    print(json.dumps(review.model_dump(), indent=2))

What changed?

Instead of hardcoding scripts/sample_diff.txt, the script now checks if you passed a filename as an argument. If you did, it reads that file. If not, it reads from standard input (stdin).

This makes the script flexible. You can run it with a file path (python scripts/ai_review.py some_diff.txt) or pipe content into it (git diff | python scripts/ai_review.py). GitHub Actions will use the piped approach.

  • Save scripts/ai_review.py.
  • Verify the updated script still works by running it with a file argument:
python scripts/ai_review.py scripts/sample_diff.txt

You should see the same structured JSON output as before. The script now accepts any diff file as input.

Getting a FileNotFoundError?

Make sure you're running the command from your ai-code-reviewer project folder (not from inside scripts/). The file path scripts/sample_diff.txt is relative to where you run the command.

help me fix my FileNotFoundError

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete final scripts/ai_review.py file. Make sure your version matches:

import json
import sys

from google import genai
from google.genai import types
from pydantic import BaseModel


class ReviewFinding(BaseModel):
    file_path: str
    line_number: int
    severity: str
    description: str
    suggestion: str


class CodeReview(BaseModel):
    summary: str
    findings: list[ReviewFinding]


def review_diff(diff_text: str) -> CodeReview:
    """Send a code diff to Gemini and get structured review findings."""
    client = genai.Client()

    prompt = f"""You are an expert code reviewer. Analyze the following code diff and identify issues.

For each issue, provide:
- file_path: the path to the file (from the diff header, e.g. "app.py")
- line_number: the line number in the NEW version of the file where the issue exists
- severity: one of HIGH, MEDIUM, or LOW
- description: a clear explanation of the problem
- suggestion: how to fix it

Focus on:
1. Security vulnerabilities (HIGH severity)
2. Bug risks (HIGH or MEDIUM severity)
3. Performance issues (MEDIUM severity)
4. Best practice violations (LOW severity)

If the code looks good, return an empty findings list with a positive summary.

Code diff to review:

{diff_text}"""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt,
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=CodeReview,
        ),
    )

    return CodeReview.model_validate_json(response.text)


if __name__ == "__main__":
    # Read diff from a file argument or from standard input
    if len(sys.argv) > 1:
        with open(sys.argv[1], "r") as f:
            diff_content = f.read()
    else:
        diff_content = sys.stdin.read()

    review = review_diff(diff_content)
    print(json.dumps(review.model_dump(), indent=2))

Your script works and accepts command-line input. It reads a diff, sends it to Gemini, and gets back clean, typed JSON you can use programmatically. This is the foundation for any automated code review integration.

Secret mission

Add a Severity Threshold and Exit Codes

Your script finds issues, but what if you could make it fail a build automatically? In this extension, you'll add a severity threshold so the script exits with a non-zero code when critical issues are found.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally with the Gemini API free tier, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • A GitHub repository with your AI review script.
  • A Gemini API key (free tier with generous rate limits).
  • A local project folder on your Desktop.
  • A Python virtual environment with installed dependencies.

Keep everything running

No action needed. Choose this if you plan to extend the script or connect it to a CI pipeline later.

  • Your Gemini API key remains active within the free tier.
  • Your local project files stay on your Desktop for future edits.
  • Your GitHub repository keeps your code backed up online.

Pause - I'll come back to this later

Deactivate your virtual environment to free up terminal state. Your code and API key stay intact for when you return.

  • Deactivate your virtual environment by running deactivate in your terminal.
  • You can reactivate it later by navigating to your project folder and running the activation command again.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your repository and revokes your API key.

  • Delete your GitHub repository: go to your repo's Settings, scroll to the bottom, and click Delete this repository.
  • Revoke your Gemini API key: go to Google AI Studio, find your key, and delete it.
  • Delete your local project folder from your Desktop.

Nice Work!

Nice Work!

You just built an AI-powered code review script that analyzes diffs and returns structured security findings. Feed it any code change and it identifies vulnerabilities with file paths, line numbers, and severity ratings.

You've learned how to:

  • Use Gemini structured output with Pydantic models to guarantee typed, parseable JSON from an AI model.
  • Build a code review script that reads unified diffs and identifies security vulnerabilities with file paths, line numbers, and severity ratings.
  • Test AI output locally using sample diffs with deliberate security flaws to validate your script catches SQL injection and command injection.
  • Added a severity threshold with exit codes so the script can gate CI pipelines (Secret Mission).

Ready to quiz yourself?