From 3f3ddb8a6d47f625cda8bcfd4fa074d6bdfd884a Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 18:02:22 -0600 Subject: [PATCH 01/12] Add multiply function, tests, and CI workflow --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ app.py | 4 ++++ tests/test_app.py | 5 ++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d0863fdd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Run tests + run: pytest -v diff --git a/app.py b/app.py index 8f2f7ae1..6fffa848 100644 --- a/app.py +++ b/app.py @@ -14,3 +14,7 @@ def is_even(n: int) -> bool: def reverse_string(s: str) -> str: """Reverse a string.""" return s[::-1] + +def multiply(a: int, b: int) -> int: + """Multiply two numbers together.""" + return a * b diff --git a/tests/test_app.py b/tests/test_app.py index 79c3e093..5c609125 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,6 +1,6 @@ """Tests for app.py - you'll add more!""" -from app import add, is_even, reverse_string +from app import add, is_even, reverse_string, multiply class TestMath: @@ -12,6 +12,9 @@ def test_add_positive(self): def test_add_negative(self): assert add(-1, -1) == -2 + def test_multiply(self): + assert multiply(2, 3) == 6 + class TestStrings: """Tests for string functions.""" From 253afcad0c777f4c99cc43d5914e89326eae3f12 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 18:05:30 -0600 Subject: [PATCH 02/12] Break a test --- tests/test_app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 5c609125..4dc7791b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -13,7 +13,9 @@ def test_add_negative(self): assert add(-1, -1) == -2 def test_multiply(self): - assert multiply(2, 3) == 6 + assert multiply(2, 3) == 12 + + class TestStrings: From 5a7abac7bf7ab5c07d69cebcf47bc53718755f9f Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 18:08:32 -0600 Subject: [PATCH 03/12] Fix the test --- tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 4dc7791b..034e834a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -13,7 +13,7 @@ def test_add_negative(self): assert add(-1, -1) == -2 def test_multiply(self): - assert multiply(2, 3) == 12 + assert multiply(2, 3) == 6 From c8ce98b031c75ad44ff5108f7a29858a35eb8cc8 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 18:14:38 -0600 Subject: [PATCH 04/12] Add build and artifact upload to CI --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0863fdd..d43c223f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,3 +24,13 @@ jobs: - name: Run tests run: pytest -v + + - name: Build package + run: python -m build + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: python-package + path: dist/ + From 4a12497cffc143bf190d520db8a343e2870dd1e8 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 19:27:16 -0600 Subject: [PATCH 05/12] Add AI code review script and PR workflow --- .github/workflows/pr-review.yml | 56 +++++++++++++++++++++++++++++++++ requirements.txt | 5 ++- scripts/ai_review.py | 52 ++++++++++++++++++++++++++++++ scripts/sample_diff.txt | 17 ++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-review.yml create mode 100644 scripts/ai_review.py create mode 100644 scripts/sample_diff.txt diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml new file mode 100644 index 00000000..1bbebca3 --- /dev/null +++ b/.github/workflows/pr-review.yml @@ -0,0 +1,56 @@ +name: AI Code Review + +on: + pull_request: + branches: [main] + +permissions: + contents: read + pull-requests: write + +jobs: + review: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Get PR diff + run: | + git diff origin/main...HEAD > pr_diff.txt + + - name: Run AI review + id: ai-review + env: + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + run: | + REVIEW=$(python scripts/ai_review.py pr_diff.txt) + echo "review<> $GITHUB_OUTPUT + echo "$REVIEW" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Post review comment + uses: actions/github-script@v7 + env: + REVIEW: ${{ steps.ai-review.outputs.review }} + with: + script: | + const review = process.env.REVIEW; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## 🤖 AI Code Review\n\n${review}\n\n---\n*Powered by Gemini AI*` + }); diff --git a/requirements.txt b/requirements.txt index 5f456517..80eb1728 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ -# Production dependencies (none for this simple app) +# Production dependencies +google-genai>=1.0.0 # Development/testing dependencies pytest>=7.0.0 build>=1.0.0 + + diff --git a/scripts/ai_review.py b/scripts/ai_review.py new file mode 100644 index 00000000..8269bfe4 --- /dev/null +++ b/scripts/ai_review.py @@ -0,0 +1,52 @@ +from google import genai +import sys + +client = genai.Client() + +# Define a function that takes a code diff as input +def gemini_code_review(diff:str): + + # Write a multi-line f-string prompt that includes {diff_text} + # Tell Gemini to act as a code reviewer and focus on security, bugs, performance + prompt = f""" + You are a senior software engineer and an expert code reviewer. Your task will be to evaluate the code diff and provide feedback on the following criteria: + - Security + - Bugs + - Performance + - Code quality + - Best practices + - Maintainability + - Readability + - Scalability + - Testability + Review the following code diff: + {diff} + """ + + # Send the prompt to the model and get a response + response = client.models.generate_content( + model="gemini-2.5-flash-lite", contents=prompt + ) + + # Return just the text from the response + return response.text + + +# Only run this code when the script is executed directly +if __name__ == "__main__": + + # Check if a filename was passed as a command-line argument + if len(sys.argv) > 1: + + # Get the filename from sys.argv and read the file + diff_file = sys.argv[1] + with open(diff_file, "r") as f: + diff_content = f.read() + + # If no filename was passed, read from standard input + else: + diff_content = sys.stdin.read() + + # Call the review function and print the result + review = gemini_code_review(diff_content) + print(review) diff --git a/scripts/sample_diff.txt b/scripts/sample_diff.txt new file mode 100644 index 00000000..d4b5984b --- /dev/null +++ b/scripts/sample_diff.txt @@ -0,0 +1,17 @@ +diff --git a/app.py b/app.py +index 1234567..abcdefg 100644 +--- a/app.py ++++ b/app.py +@@ -1,5 +1,12 @@ + """Simple utility functions""" + ++import sqlite3 ++ ++def get_user(username): ++ conn = sqlite3.connect("users.db") ++ query = f"SELECT * FROM users WHERE name = '{username}'" ++ return conn.execute(query).fetchone() ++ + def add(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b From 2108bdb7e07b6c13f2242e6458fddfad920e4c4e Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 20:22:19 -0600 Subject: [PATCH 06/12] Add auto-labeling by AI review severity --- .github/workflows/pr-review.yml | 45 +++++++++++++++++++++++ scripts/ai_review.py | 63 +++++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index 1bbebca3..d8838ab8 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -39,6 +39,8 @@ jobs: echo "review<> $GITHUB_OUTPUT echo "$REVIEW" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT + echo "severity=$(cat severity.txt)" >> $GITHUB_OUTPUT + - name: Post review comment uses: actions/github-script@v7 @@ -54,3 +56,46 @@ jobs: issue_number: context.issue.number, body: `## 🤖 AI Code Review\n\n${review}\n\n---\n*Powered by Gemini AI*` }); + + + - name: Apply severity label + uses: actions/github-script@v7 + with: + script: | + const severity = '${{ steps.ai-review.outputs.severity }}'; + + const labelMap = { + 'CRITICAL': 'ai-review: critical', + 'WARNING': 'ai-review: warning', + 'GOOD': 'ai-review: looks-good' + }; + + const label = labelMap[severity] || labelMap['WARNING']; + + // Remove any existing ai-review labels + const currentLabels = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + for (const existingLabel of currentLabels.data) { + if (existingLabel.name.startsWith('ai-review:')) { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: existingLabel.name + }); + } + } + + // Add the new severity label + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [label] + }); + + console.log(`Applied label: ${label}`); diff --git a/scripts/ai_review.py b/scripts/ai_review.py index 8269bfe4..9e2c227e 100644 --- a/scripts/ai_review.py +++ b/scripts/ai_review.py @@ -8,29 +8,55 @@ def gemini_code_review(diff:str): # Write a multi-line f-string prompt that includes {diff_text} # Tell Gemini to act as a code reviewer and focus on security, bugs, performance - prompt = f""" - You are a senior software engineer and an expert code reviewer. Your task will be to evaluate the code diff and provide feedback on the following criteria: - - Security - - Bugs - - Performance - - Code quality - - Best practices - - Maintainability - - Readability - - Scalability - - Testability - Review the following code diff: - {diff} - """ + prompt = f"""You are an expert code reviewer. Review the following code diff and provide feedback. + + Focus on: + 1. Security vulnerabilities + 2. Bug risks + 3. Performance issues + 4. Best practice violations + + For each issue found, provide: + - Severity: HIGH / MEDIUM / LOW + - Description of the issue + - Suggested fix + + If the code looks good, say so. + + IMPORTANT: At the very end of your review, add a severity summary line in exactly this format: + SEVERITY_SUMMARY: + Where is one of: CRITICAL, WARNING, GOOD + + Use CRITICAL if any HIGH severity issues exist. + Use WARNING if only MEDIUM or LOW severity issues exist. + Use GOOD if no issues found. + + Code diff to review: + + {diff_text} + + + Provide your review in a clear, structured format, ending with the SEVERITY_SUMMARY line.""" + # Send the prompt to the model and get a response response = client.models.generate_content( - model="gemini-2.5-flash-lite", contents=prompt + model="gemini-3-flash-preview", contents=prompt ) # Return just the text from the response return response.text +def parse_severity(review_text): + """Extract severity level from the review output.""" + for line in review_text.strip().split("\n"): + if line.strip().startswith("SEVERITY_SUMMARY:"): + level = line.split(":", 1)[1].strip().upper() + if level in ("CRITICAL", "WARNING", "GOOD"): + return level + return "WARNING" # Default to WARNING if parsing fails + + # Only run this code when the script is executed directly if __name__ == "__main__": @@ -49,4 +75,11 @@ def gemini_code_review(diff:str): # Call the review function and print the result review = gemini_code_review(diff_content) + severity = parse_severity(review) + print(review) + # Open severity.txt for writing + with open("severity.txt", "w") as f: + # Write the severity string to the file + f.write(severity) + From f58b7c4142a992deccad8faa2489022758db9986 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 20:23:25 -0600 Subject: [PATCH 07/12] Add command execution feature --- dangerous.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 dangerous.py diff --git a/dangerous.py b/dangerous.py new file mode 100644 index 00000000..74d74fcf --- /dev/null +++ b/dangerous.py @@ -0,0 +1,6 @@ +import subprocess + +def run_command(user_input): + subprocess.call(user_input, shell=True) + +API_KEY = "sk-live-abc123def456" From 85f3a29c6727c2719b99e41cacfe3172bcdd1bfa Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 20:33:35 -0600 Subject: [PATCH 08/12] Add command execution feature --- dangerous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dangerous.py b/dangerous.py index 74d74fcf..604394ab 100644 --- a/dangerous.py +++ b/dangerous.py @@ -3,4 +3,4 @@ def run_command(user_input): subprocess.call(user_input, shell=True) -API_KEY = "sk-live-abc123def456" +API_KEY = "sk-live-abc123def4567" From bbdb5c30cc19392d2e0109eeebdb5c442e052214 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 20:38:46 -0600 Subject: [PATCH 09/12] Add command execution feature --- dangerous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dangerous.py b/dangerous.py index 604394ab..c3be8d6d 100644 --- a/dangerous.py +++ b/dangerous.py @@ -3,4 +3,4 @@ def run_command(user_input): subprocess.call(user_input, shell=True) -API_KEY = "sk-live-abc123def4567" +API_KEY = "sk-live-abc123def45678" From 7a6c517e9f6d3f236a4702190c97d84443018a60 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 20:44:54 -0600 Subject: [PATCH 10/12] Add command execution feature --- dangerous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dangerous.py b/dangerous.py index c3be8d6d..0f14ed5c 100644 --- a/dangerous.py +++ b/dangerous.py @@ -1,6 +1,6 @@ import subprocess def run_command(user_input): - subprocess.call(user_input, shell=True) + subprocess.call(user_input, shell=False) API_KEY = "sk-live-abc123def45678" From 019a1385bd6774bb59aa6e968aefbab33ef8d455 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 21:04:46 -0600 Subject: [PATCH 11/12] Add command execution feature --- dangerous.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dangerous.py b/dangerous.py index 0f14ed5c..bac1cd37 100644 --- a/dangerous.py +++ b/dangerous.py @@ -1,6 +1,8 @@ import subprocess def run_command(user_input): - subprocess.call(user_input, shell=False) + subprocess.call(user_input, shell=True) +# Exposed API Key API_KEY = "sk-live-abc123def45678" + From 1a9bcb6a5fd8eddfa729988e86780ab8ad1114a4 Mon Sep 17 00:00:00 2001 From: Jafet Date: Tue, 3 Feb 2026 21:12:30 -0600 Subject: [PATCH 12/12] Add command execution feature 2 --- dangerous2.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 dangerous2.py diff --git a/dangerous2.py b/dangerous2.py new file mode 100644 index 00000000..5853ae91 --- /dev/null +++ b/dangerous2.py @@ -0,0 +1,7 @@ +import subprocess + +def run_command(user_input): + subprocess.call(user_input, shell=True) + +# Exposed API Key +API_KEY = "sk-live-abc123def45678" \ No newline at end of file