From a78ef46133c5d77a3304b256f69f16545155e95e Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 31 Jan 2026 22:30:38 +0000 Subject: [PATCH] feat: add AI worker workflows (Gemini + Jules) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Distributed AI compute using contributor's free tiers: Gemini 2.0 Flash (ai-worker.yml): - 1500 req/day free from Google - Code review, security scan, bug detection - Contributor sets GEMINI_API_KEY in fork secrets Jules/Copilot (jules-dispatch.yml): - Triggered by @jules or /jules comments - Creates PRs to fix issues automatically - Uses contributor's Copilot allowance (free for OSS) Documentation (doc/free-tier-compute.md): - Setup guide for all free tiers - Compute distribution model diagram - Donor fleet instructions Innocent strategy: Jules commits fixes to contributor's fork 😇 Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ai-worker.yml | 126 +++++++++++++++++++++++++++ .github/workflows/jules-dispatch.yml | 64 ++++++++++++++ doc/free-tier-compute.md | 103 ++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 .github/workflows/ai-worker.yml create mode 100644 .github/workflows/jules-dispatch.yml create mode 100644 doc/free-tier-compute.md diff --git a/.github/workflows/ai-worker.yml b/.github/workflows/ai-worker.yml new file mode 100644 index 0000000..7c32bf3 --- /dev/null +++ b/.github/workflows/ai-worker.yml @@ -0,0 +1,126 @@ +name: AI Worker (Free Tier) + +# Runs on contributor's fork - uses THEIR API allowances +# Gemini 2.0 Flash: 1500 req/day free +# Jules: Available to all GitHub users + +on: + pull_request: + types: [opened, synchronize] + issues: + types: [labeled] + +permissions: + contents: write + pull-requests: write + issues: write + +env: + # Contributors set their own GEMINI_API_KEY in fork secrets + # Free tier: 1500 requests/day, 1M tokens/min + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + +jobs: + # Analyze PR with Gemini (contributor's free tier) + gemini-review: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get diff + id: diff + run: | + DIFF=$(git diff origin/${{ github.base_ref }}...HEAD | head -c 50000) + echo "diff<> $GITHUB_OUTPUT + echo "$DIFF" >> $GITHUB_OUTPUT + echo "EOFMARKER" >> $GITHUB_OUTPUT + + - name: Gemini Analysis + if: env.GEMINI_API_KEY != '' + id: gemini + run: | + # Call Gemini 2.0 Flash (free tier) + RESPONSE=$(curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=$GEMINI_API_KEY" \ + -H 'Content-Type: application/json' \ + -d @- << 'PAYLOAD' + { + "contents": [{ + "parts": [{ + "text": "Review this code diff. Be concise. List: 1) Security issues 2) Bugs 3) Improvements. If none, say 'LGTM'.\n\nDiff:\n${{ steps.diff.outputs.diff }}" + }] + }], + "generationConfig": { + "temperature": 0.2, + "maxOutputTokens": 1000 + } + } + PAYLOAD + ) + + # Extract text from response + REVIEW=$(echo "$RESPONSE" | jq -r '.candidates[0].content.parts[0].text // "Analysis failed"') + echo "review<> $GITHUB_OUTPUT + echo "$REVIEW" >> $GITHUB_OUTPUT + echo "EOFMARKER" >> $GITHUB_OUTPUT + + - name: Post Gemini review + if: steps.gemini.outputs.review != '' + uses: actions/github-script@v7 + with: + script: | + const review = `${{ steps.gemini.outputs.review }}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## 🤖 Gemini Analysis (Free Tier)\n\n${review}\n\n---\n_Powered by contributor's Gemini API allowance (1500 req/day free)_` + }); + + # Trigger Jules to fix issues (contributor's Copilot allowance) + trigger-jules: + if: github.event_name == 'issues' && github.event.label.name == 'agent:ready' + runs-on: ubuntu-latest + steps: + - name: Check Jules eligibility + id: check + uses: actions/github-script@v7 + with: + script: | + // Check if this repo has Copilot coding agent enabled + const issue = context.payload.issue; + + console.log(`Issue #${issue.number}: ${issue.title}`); + console.log(`Labels: ${issue.labels.map(l => l.name).join(', ')}`); + + // Jules can be assigned to issues with specific labels + const hasAgentic = issue.labels.some(l => l.name === 'agentic'); + + return { eligible: hasAgentic, issueNumber: issue.number }; + + - name: Assign to Jules + if: steps.check.outputs.result != '' + uses: actions/github-script@v7 + with: + script: | + // Trigger Jules by adding the copilot label + // Jules will pick up the issue and create a PR + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: ['copilot'] // This triggers Jules/Copilot coding agent + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `## 🤖 Jules Activated\n\nThis issue has been assigned to GitHub Copilot coding agent (Jules).\n\nJules will:\n1. Analyze the issue\n2. Create a solution\n3. Open a PR with the fix\n\n_Using contributor's Copilot allowance_ 🆓` + }); + } catch (e) { + console.log('Could not assign to Jules:', e.message); + } diff --git a/.github/workflows/jules-dispatch.yml b/.github/workflows/jules-dispatch.yml new file mode 100644 index 0000000..7378d16 --- /dev/null +++ b/.github/workflows/jules-dispatch.yml @@ -0,0 +1,64 @@ +name: Jules Task Dispatch + +# Dispatch tasks to Jules (Copilot coding agent) +# Jules works on the contributor's fork using their Copilot Pro allowance + +on: + issue_comment: + types: [created] + +jobs: + dispatch-to-jules: + # Trigger when someone comments "@jules" or "/jules" + if: contains(github.event.comment.body, '@jules') || contains(github.event.comment.body, '/jules') + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Parse Jules command + id: parse + uses: actions/github-script@v7 + with: + script: | + const comment = context.payload.comment.body; + + // Extract task from comment + // Format: @jules + // or: /jules fix the bug in auth.php + const match = comment.match(/(?:@jules|\/jules)\s+(.+)/i); + const task = match ? match[1].trim() : null; + + return { task, commenter: context.payload.comment.user.login }; + + - name: Create Jules task + uses: actions/github-script@v7 + with: + script: | + const result = ${{ steps.parse.outputs.result }}; + + if (!result.task) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `@${result.commenter} Please specify a task for Jules:\n\n\`@jules fix the authentication bug\`\n\`@jules add unit tests for User model\`\n\`@jules refactor this to use async/await\`` + }); + return; + } + + // Add copilot label to trigger Jules + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['copilot', 'agent:wip'] + }); + + // Create a sub-issue or task for Jules + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## 🤖 Jules Task Queued\n\n**Task:** ${result.task}\n**Requested by:** @${result.commenter}\n\nJules (Copilot coding agent) will work on this using the repository owner's Copilot allowance.\n\n---\n_Tip: Fork this repo to use your own Copilot Pro allowance for faster processing._` + }); diff --git a/doc/free-tier-compute.md b/doc/free-tier-compute.md new file mode 100644 index 0000000..61c2728 --- /dev/null +++ b/doc/free-tier-compute.md @@ -0,0 +1,103 @@ +# Free Tier Compute Guide + +This project uses distributed compute from contributors' free tier allowances. Here's how to maximize it. + +## GitHub Actions (Microsoft) + +**Free tier:** 2,000 minutes/month for public repos + +When you fork this repo, CI runs on YOUR allowance, not upstream's. This includes: +- Full test suite +- Linting and formatting +- Security scanning +- AI-powered triage + +## Gemini API (Google) + +**Free tier:** 1,500 requests/day, 1M tokens/minute + +### Setup + +1. Get API key: https://aistudio.google.com/apikey +2. Add to your fork's secrets: + ```bash + gh secret set GEMINI_API_KEY --repo YOUR_USER/YOUR_FORK + ``` + +The `ai-worker.yml` workflow will use your Gemini allowance for: +- Code review +- Bug detection +- Security analysis + +## Jules / Copilot (GitHub) + +**Free tier:** Available with GitHub Copilot (free for OSS contributors) + +### Trigger Jules + +Comment on any issue: +``` +@jules fix the authentication bug in login.php +@jules add unit tests for the User model +@jules refactor to use async/await +``` + +Jules will: +1. Analyze the issue +2. Write code to fix it +3. Create a PR on your fork +4. Uses YOUR Copilot allowance + +### Get Copilot Free + +- Open source contributors: https://github.com/settings/copilot +- Students: GitHub Education pack +- Verified OSS maintainers: Free Copilot Pro + +## CodeRabbit + +**Free tier:** Unlimited for public repos + +Already configured via `.coderabbit.yaml`. Reviews run automatically on PRs. + +## Compute Distribution Model + +``` +┌────────────────────────────────────────────┐ +│ CONTRIBUTOR FORK │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ GitHub │ │ Gemini │ │ +│ │ Actions │ │ 2.0 Flash │ │ +│ │ FREE │ │ FREE │ │ +│ └─────────────┘ └─────────────┘ │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ Jules/ │ │ CodeRabbit │ │ +│ │ Copilot │ │ FREE │ │ +│ │ FREE* │ │ │ │ +│ └─────────────┘ └─────────────┘ │ +│ │ +│ * With Copilot Pro (free for OSS) │ +└────────────────────────────────────────────┘ + │ + │ PR (already analyzed) + ▼ +┌────────────────────────────────────────────┐ +│ UPSTREAM REPO │ +│ │ +│ Just verify fork CI passed ✓ │ +│ Cost: ~$0.001 per PR │ +└────────────────────────────────────────────┘ +``` + +## For Donor Fleet Members + +If you want to contribute compute without coding: + +1. Fork the repo +2. Set up your API keys (Gemini, etc.) +3. Keep Actions enabled +4. Pick up `agent:review` tasks to verify others' work + +Your free tier becomes part of the distributed compute network!