Skip to content
Open
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
82 changes: 68 additions & 14 deletions .github/workflows/ai-review.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,76 @@
# AI-powered code review & PR summary (GitHub Models free tier)
# Calls reusable workflows from nsalvacao/actions-hub
# Zero premium requests — only Actions minutes consumed
name: AI Review
name: AI Review Inline

on:
pull_request:
types: [opened, synchronize]

permissions:
contents: read
pull-requests: write

jobs:
code-review:
uses: nsalvacao/actions-hub/.github/workflows/ai-code-review.yml@main
with:
language: "en"
secrets: inherit
ai-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get PR diff
id: diff
run: |
git diff ${{ github.event.pull_request.base.sha }}...${{ github.sha }} -- "*.js" "*.ts" "*.py" > diff.txt || true
if [ ! -s diff.txt ]; then
git diff HEAD~1 > diff.txt || true
fi
head -c 6000 diff.txt > diff_trunc.txt
echo "has_diff=$([ -s diff_trunc.txt ] && echo true || echo false)" >> $GITHUB_OUTPUT

- name: AI Review via GitHub Models
if: steps.diff.outputs.has_diff == 'true'
id: review
env:
GH_MODELS_TOKEN: ${{ secrets.MODELS_PAT }}
run: |
DIFF=$(cat diff_trunc.txt)
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
"https://models.github.ai/inference/chat/completions" \
-H "Authorization: Bearer $GH_MODELS_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg diff "$DIFF" '{
model: "openai/gpt-4.1-mini",
messages: [
{role: "system", content: "Senior code reviewer. Review for security, bugs, best practices. Use markdown with 🔴 Critical 🟡 Warning 🔵 Info."},
{role: "user", content: ("Review this PR diff:\n\n" + $diff)}
],
max_tokens: 1500,
temperature: 0.2
}')")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" != "200" ]; then
echo "::error::Models API returned HTTP $HTTP_CODE"
echo "$BODY"
exit 1
fi

echo "$BODY" | jq -r '.choices[0].message.content' > review.md
TOKENS=$(echo "$BODY" | jq '.usage.total_tokens')
echo "tokens=$TOKENS" >> $GITHUB_OUTPUT

pr-summary:
uses: nsalvacao/actions-hub/.github/workflows/ai-pr-summary.yml@main
with:
language: "en"
secrets: inherit
- name: Post review comment
if: steps.diff.outputs.has_diff == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = fs.readFileSync('review.md', 'utf8');
const tokens = '${{ steps.review.outputs.tokens }}';
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: `## 🔍 AI Code Review\n\n${review}\n\n---\n*🤖 gpt-4.1-mini · ${tokens} tokens · GitHub Models free tier · 0 premium requests*`
});

22 changes: 22 additions & 0 deletions test-review-demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Demo: API endpoint for testing AI review
const express = require("express");
const jwt = require("jsonwebtoken");

function handleLogin(req, res) {
const username = req.body.username;
const password = req.body.password;

// TODO: add rate limiting
const query = `SELECT * FROM users WHERE username = "${username}" AND password = "${password}"`;

const token = jwt.sign({ user: username }, "hardcoded-secret-key-123");

console.log("Login attempt: " + username + " / " + password);

res.json({ token: token, success: true });
}

module.exports = { handleLogin };


// triggered 20:42:38