Repository navigation
docs(readme): trim to principles and cross-link docs #15
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: AI Review Bot | |
| on: | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review] | |
| issues: | |
| types: [opened] | |
| issue_comment: | |
| types: [created] | |
| workflow_dispatch: | |
| inputs: | |
| mode: | |
| description: "Manual mode" | |
| required: true | |
| default: review | |
| type: choice | |
| options: | |
| - review | |
| - triage | |
| - explain | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: ai-review-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}-${{ github.event_name }} | |
| cancel-in-progress: true | |
| jobs: | |
| ai-review: | |
| name: OpenCode2API AI review (gateway + CPA) | |
| # Slash commands only run for collaborators (budget/abuse gate); automatic | |
| # PR/issue events are unaffected. | |
| if: > | |
| github.event_name != 'issue_comment' || | |
| (( | |
| contains(github.event.comment.body, '/review') || | |
| contains(github.event.comment.body, '/triage') || | |
| contains(github.event.comment.body, '/explain') | |
| ) && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Skip draft pull requests | |
| id: gate | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.draft }}" == "true" ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Skipping draft pull request" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Ensure LLM route is available | |
| if: steps.gate.outputs.skip != 'true' | |
| shell: bash | |
| env: | |
| GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} | |
| CPA_BASE_URL: ${{ secrets.CPA_BASE_URL }} | |
| CPA_API_KEY: ${{ secrets.CPA_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| # Free models always have a route: an in-job no-auth gateway is | |
| # started below unless an external GATEWAY_BASE_URL secret is set. | |
| # CPA credentials are optional (fallback channel only). | |
| if [[ -n "${GATEWAY_BASE_URL}" ]]; then | |
| echo "Using external gateway (URL from secrets; value not printed)." | |
| else | |
| echo "No external gateway secret; starting in-job no-auth gateway." | |
| fi | |
| if [[ -z "${CPA_API_KEY}" || -z "${CPA_BASE_URL}" ]]; then | |
| echo "::warning::No CPA credentials; CPA fallback channel disabled (gateway only)." | |
| fi | |
| - name: Check out repository | |
| if: steps.gate.outputs.skip != 'true' | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Resolve PR head for comment-triggered runs | |
| if: steps.gate.outputs.skip != 'true' && github.event_name == 'issue_comment' && github.event.issue.pull_request | |
| id: pr_ref | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| pr_json="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.issue.number }}")" | |
| base_sha="$(printf '%s' "$pr_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["base"]["sha"])')" | |
| head_sha="$(printf '%s' "$pr_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["head"]["sha"])')" | |
| git fetch --no-tags origin "pull/${{ github.event.issue.number }}/head:refs/bot/pr-${{ github.event.issue.number }}" | |
| git checkout --force "refs/bot/pr-${{ github.event.issue.number }}" | |
| { | |
| echo "base_sha=${base_sha}" | |
| echo "head_sha=${head_sha}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Detect gateway source | |
| id: gw | |
| shell: bash | |
| env: | |
| GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} | |
| run: | | |
| set -euo pipefail | |
| # secrets context is not allowed in step `if:`; expose via outputs. | |
| # Always runs (trivial check) so downstream never reads an absent output. | |
| if [[ -n "${GATEWAY_BASE_URL}" ]]; then | |
| echo "external=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "external=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Node | |
| if: steps.gate.outputs.skip != 'true' && steps.gw.outputs.external != 'true' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Start in-job gateway (free models, no login needed) | |
| if: steps.gate.outputs.skip != 'true' && steps.gw.outputs.external != 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm ci | |
| npm run build | |
| # Pinned to track @opencode-ai/sdk ^1.18.29 used by this repo. | |
| npm i -g opencode-ai@1.18.30 | |
| # No-auth localhost gateway: empty API_KEY = no auth; the gateway | |
| # spawns its own backend (anonymous out-of-box free quota). | |
| API_KEY= OPENCODE_PROXY_MANAGE_BACKEND=true OPENCODE_SERVER_PASSWORD=ci-local-only \ | |
| nohup node dist/index.js > /tmp/opencode-gateway.log 2>&1 & | |
| ok=0 | |
| for i in $(seq 1 60); do | |
| if curl -sf http://127.0.0.1:10000/health; then | |
| echo | |
| echo "gateway: /health 200" | |
| ok=1 | |
| break | |
| fi | |
| sleep 5 | |
| done | |
| if [[ "$ok" != "1" ]]; then | |
| echo "gateway failed to start" >&2 | |
| tail -50 /tmp/opencode-gateway.log || true | |
| exit 1 | |
| fi | |
| - name: Set up Python | |
| if: steps.gate.outputs.skip != 'true' | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Determine bot mode | |
| if: steps.gate.outputs.skip != 'true' | |
| id: mode | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mode="review" | |
| case "${{ github.event_name }}" in | |
| issues) | |
| mode="triage" | |
| ;; | |
| issue_comment) | |
| mode="comment" | |
| ;; | |
| workflow_dispatch) | |
| mode="${{ inputs.mode }}" | |
| ;; | |
| pull_request) | |
| mode="review" | |
| ;; | |
| esac | |
| echo "mode=${mode}" >> "$GITHUB_OUTPUT" | |
| - name: Run AI bot | |
| if: steps.gate.outputs.skip != 'true' | |
| shell: bash | |
| env: | |
| GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} | |
| GATEWAY_API_KEY: ${{ secrets.GATEWAY_API_KEY }} | |
| OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} | |
| CPA_BASE_URL: ${{ secrets.CPA_BASE_URL }} | |
| CPA_API_KEY: ${{ secrets.CPA_API_KEY }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| COMMENT_BODY: ${{ github.event.comment.body || '' }} | |
| ISSUE_TITLE: ${{ github.event.issue.title || '' }} | |
| ISSUE_BODY: ${{ github.event.issue.body || '' }} | |
| PR_BASE_SHA: ${{ steps.pr_ref.outputs.base_sha || github.event.pull_request.base.sha || '' }} | |
| PR_HEAD_SHA: ${{ steps.pr_ref.outputs.head_sha || github.event.pull_request.head.sha || '' }} | |
| PYTHONPATH: github_bot/src | |
| run: | | |
| set -euo pipefail | |
| # Default to the in-job no-auth gateway when no external URL secret. | |
| export GATEWAY_BASE_URL="${GATEWAY_BASE_URL:-http://127.0.0.1:10000}" | |
| python3 github_bot/src/github_runner.py --mode="${{ steps.mode.outputs.mode }}" |