Skip to content

AI Implementation

AI Implementation #44

Workflow file for this run

name: AI Implementation
on:
schedule:
- cron: '0 16 * * *'
workflow_dispatch:
push:
branches:
- 'ai/**'
concurrency:
group: ai-impl-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
issues: read
id-token: write
jobs:
ai-implement:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
fetch-depth: 1
- name: Setup bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Get opencode version
id: version
run: |
VERSION=$(curl -sf https://api.github.com/repos/anomalyco/opencode/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4)
echo "version=${VERSION:-latest}" >> $GITHUB_OUTPUT
- name: Cache opencode
id: cache
uses: actions/cache@v4
with:
path: ~/.opencode/bin
key: opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.version.outputs.version }}
- name: Install opencode
if: steps.cache.outputs.cache-hit != 'true'
run: curl -fsSL https://opencode.ai/install | bash
- name: Add opencode to PATH
run: echo "$HOME/.opencode/bin" >> $GITHUB_PATH
- name: Install oh-my-opencode
run: |
bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=no
- name: Copy oh-my-opencode config
env:
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
run: |
mkdir -p ~/.config/opencode
sed "s/MINIMAX_API_KEY/${MINIMAX_API_KEY}/g" .github/workflows/opencode.json > ~/.config/opencode/opencode.json
sed "s/MINIMAX_API_KEY/${MINIMAX_API_KEY}/g" .github/workflows/oh-my-opencode.json > ~/.config/opencode/oh-my-opencode.json
- name: Determine trigger type and get issue info
id: determine
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const isSchedule = context.eventName === 'schedule';
const isDispatch = context.eventName === 'workflow_dispatch';
const isPush = context.eventName === 'push';
let issueNumber = null;
let branchName = null;
if (isPush && '${{ github.ref_name }}'.startsWith('ai/issue-')) {
const match = '${{ github.ref_name }}'.match(/ai\/issue-(\d+)/);
if (match) {
issueNumber = parseInt(match[1]);
branchName = '${{ github.ref_name }}';
}
} else if (isDispatch) {
const inputs = context.payload.inputs;
if (inputs && inputs.issue_number) {
issueNumber = parseInt(inputs.issue_number);
branchName = 'ai/issue-' + issueNumber;
}
} else if (isSchedule) {
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'ai-task',
per_page: 10
});
const { data: comments } = await github.rest.issues.listComments({
issue_number: issues[0]?.number,
owner: context.repo.owner,
repo: context.repo.repo
});
const hasPlan = comments.some(c => c.body.includes('## AI Implementation Plan'));
if (issues.length > 0 && hasPlan) {
issueNumber = issues[0].number;
branchName = 'ai/issue-' + issueNumber;
}
}
return JSON.stringify({ issueNumber, branchName });
- name: Set issue number
id: extract
run: |
node -e "const d=JSON.parse(process.env.RESULT); console.log('issue_number='+(d.issueNumber||'')); console.log('branch_name='+(d.branchName||''))" >> $GITHUB_OUTPUT
env:
RESULT: ${{ steps.determine.outputs.result }}
- name: Skip if no issues to process
if: steps.extract.outputs.issue_number == ''
run: |
echo "SKIP=true" >> $GITHUB_OUTPUT
echo "No issues with plan found"
- name: Checkout branch
if: steps.extract.outputs.branch_name != ''
env:
BRANCH: ${{ steps.extract.outputs.branch_name }}
run: |
git fetch origin $BRANCH || git checkout -b $BRANCH || true
git checkout $BRANCH || true
- name: Set environment variables
if: steps.extract.outputs.branch_name != ''
run: |
echo "ISSUE_NUM=${{ steps.extract.outputs.issue_number }}" >> $GITHUB_ENV
echo "PR_NUMBER=${{ steps.extract.outputs.issue_number }}" >> $GITHUB_ENV
- name: Get issue and plan
id: issue
if: steps.extract.outputs.branch_name != ''
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const num = '${{ steps.extract.outputs.issue_number }}';
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(num)
});
const { data: comments } = await github.rest.issues.listComments({
issue_number: parseInt(num),
owner: context.repo.owner,
repo: context.repo.repo
});
const plan = comments.find(c => c.body.includes('## AI Implementation Plan'));
return JSON.stringify({ number: issue.number, title: issue.title, plan: plan ? plan.body : '' });
- name: Prepare prompt
if: steps.extract.outputs.branch_name != ''
id: prepare_prompt
env:
ISSUE_DATA: ${{ steps.issue.outputs.result }}
run: |
node -e "
const fs = require('fs');
const data = JSON.parse(process.env.ISSUE_DATA || '{}');
let p = fs.readFileSync('.github/workflows/prompts/ai-implement-prompt.txt','utf8');
p = p.replace(/{{issue_number}}/g, data.number || '')
.replace(/{{issue_title}}/g, data.title || '')
.replace(/{{plan}}/g, data.plan || 'No plan found');
console.log('prompt_content=' + p);
" >> $GITHUB_OUTPUT
- name: Run OpenCode
id: opencode
if: steps.extract.outputs.branch_name != ''
run: opencode github run
env:
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MODEL: minimax-cn-coding-plan/MiniMax-M2.5
AGENT: sisyphus
PROMPT: ${{ steps.prepare_prompt.outputs.prompt_content }}
- name: Check changes
id: changes
run: |
echo "changed=$(git status --porcelain | wc -l)" >> $GITHUB_OUTPUT
- name: Commit changes
if: steps.changes.outputs.changed != '0'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "AI Agent"
git config user.email "ai@github.local"
git add -A
git commit -m "AI: Implement issue-${{ env.ISSUE_NUM }}" || true
git push origin "ai/issue-${{ env.ISSUE_NUM }}" || true
- name: Build
id: build
run: |
dotnet restore TelegramSearchBot.sln
dotnet build TelegramSearchBot.sln --configuration Release --no-restore
continue-on-error: true
- name: Prepare build fix prompt
if: steps.build.outcome == 'failure'
id: prepare_build_fix
run: |
node -e "
const fs = require('fs');
let p = fs.readFileSync('.github/workflows/prompts/ai-fix-build-prompt.txt','utf8');
p = p.replace(/{{issue_number}}/g, '${{ env.ISSUE_NUM }}');
console.log('prompt_content=' + p);
" >> $GITHUB_OUTPUT
- name: Fix build
if: steps.build.outcome == 'failure'
run: opencode github run
env:
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MODEL: minimax-cn-coding-plan/MiniMax-M2.5
AGENT: sisyphus
PROMPT: ${{ steps.prepare_build_fix.outputs.prompt_content }}
- name: Rebuild after fix
if: steps.build.outcome == 'failure'
run: dotnet build TelegramSearchBot.sln --configuration Release
- name: Commit build fixes
if: steps.build.outcome == 'failure'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "AI Agent"
git config user.email "ai@github.local"
git add -A
git commit -m "AI: Fix build issue-${{ env.ISSUE_NUM }}" || true
git push origin "ai/issue-${{ env.ISSUE_NUM }}" || true
- name: Run tests
id: tests
run: dotnet test TelegramSearchBot.sln --configuration Release --no-build
continue-on-error: true
- name: Prepare test fix prompt
if: steps.tests.outcome == 'failure'
id: prepare_test_fix
run: |
node -e "
const fs = require('fs');
let p = fs.readFileSync('.github/workflows/prompts/ai-fix-tests-prompt.txt','utf8');
p = p.replace(/{{issue_number}}/g, '${{ env.ISSUE_NUM }}');
console.log('prompt_content=' + p);
" >> $GITHUB_OUTPUT
- name: Fix tests
if: steps.tests.outcome == 'failure'
run: opencode github run
env:
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MODEL: minimax-cn-coding-plan/MiniMax-M2.5
AGENT: sisyphus
PROMPT: ${{ steps.prepare_test_fix.outputs.prompt_content }}
- name: Rebuild after test fix
if: steps.tests.outcome == 'failure'
run: |
dotnet build TelegramSearchBot.sln --configuration Release
dotnet test TelegramSearchBot.sln --configuration Release --no-build || true
- name: Commit fixes
if: steps.tests.outcome == 'failure'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "AI Agent"
git config user.email "ai@github.local"
git add -A
git commit -m "AI: Fix tests issue-${{ env.ISSUE_NUM }}" || true
git push origin "ai/issue-${{ env.ISSUE_NUM }}" || true
- name: Create PR
if: always()
id: create_pr
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --base master --head "ai/issue-${{ env.ISSUE_NUM }}" --title "AI: Issue #${{ env.ISSUE_NUM }}" --body "AI implementation" || echo "PR exists"