diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index f9f3866..1188480 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -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*` + }); diff --git a/test-review-demo.js b/test-review-demo.js new file mode 100644 index 0000000..10ab355 --- /dev/null +++ b/test-review-demo.js @@ -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