# This composite action is designed for Gitea Actions runners. # Gitea Actions supports GitHub Actions syntax including $GITHUB_OUTPUT, # actions/cache, and actions/checkout. # Requirements: python3, sha256sum, curl (all present on ubuntu-* runners). name: 'AI Code Review' description: 'Run AI-powered code review on a pull request using review-bot' inputs: vcs-url: description: 'VCS server URL (defaults to server_url)' required: false default: '' repo: description: 'Repository (owner/name, defaults to current)' required: false default: '' pr-number: description: 'Pull request number (defaults to current PR)' required: false default: '' reviewer-token: description: 'Gitea token for posting the review' required: true reviewer-name: description: 'Display name for the reviewer' required: false default: '' llm-base-url: description: 'OpenAI-compatible LLM API base URL (not required for aicore provider)' required: false default: '' llm-api-key: description: 'LLM API key (not required for aicore provider)' required: false default: '' llm-model: description: 'LLM model name' required: true llm-provider: description: 'LLM API provider: openai, anthropic, or aicore (default openai)' required: false default: 'openai' aicore-client-id: description: 'SAP AI Core client ID (required for aicore provider)' required: false default: '' aicore-client-secret: description: 'SAP AI Core client secret (required for aicore provider)' required: false default: '' aicore-auth-url: description: 'SAP AI Core authentication URL (required for aicore provider)' required: false default: '' aicore-api-url: description: 'SAP AI Core API URL (required for aicore provider)' required: false default: '' aicore-resource-group: description: 'SAP AI Core resource group (default: default)' required: false default: 'default' conventions-file: description: 'Path to conventions file in the repo (e.g. CLAUDE.md)' required: false default: '' patterns-repo: description: 'Comma-separated repos with language patterns (e.g. rodin/elixir-patterns,rodin/phoenix-conventions)' required: false default: '' patterns-files: description: 'Comma-separated file paths or directories to fetch from patterns repos' required: false default: 'README.md' temperature: description: 'LLM temperature (0 = server default)' required: false default: '0' timeout: description: 'LLM request timeout in seconds (default 300)' required: false default: '300' version: description: 'review-bot version to install (e.g. v0.1.0, defaults to latest)' required: false default: 'latest' dry-run: description: 'Print review to stdout instead of posting' required: false default: 'false' update-existing: description: 'Delete previous review from same bot after posting new one. Accepts: true/1/yes or false/0/no (default true)' required: false default: 'true' system-prompt-file: description: 'Local file with additional system prompt instructions (e.g. security review focus)' required: false default: '' persona: description: 'Built-in persona name (security, architect, docs)' required: false default: '' persona-file: description: 'Path to custom persona JSON file' required: false default: '' runs: using: 'composite' steps: - name: Determine version id: version shell: bash run: | BASE_URL="${{ inputs.vcs-url || github.server_url }}" REPO="${{ inputs.repo || 'rodin/review-bot' }}" if [ "${{ inputs.version }}" = "latest" ]; then VERSION=$(curl -sSf "${BASE_URL}/api/v1/repos/${REPO}/releases?limit=1" \ | python3 -c "import sys, json; releases = json.load(sys.stdin); print(releases[0]['tag_name'] if releases else '')") if [ -z "$VERSION" ]; then echo "Failed to determine latest version" >&2 exit 1 fi else VERSION="${{ inputs.version }}" fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - name: Cache review-bot binary id: cache uses: actions/cache@v4 with: path: ${{ runner.temp }}/review-bot key: review-bot-linux-amd64-${{ steps.version.outputs.version }} - name: Install review-bot if: steps.cache.outputs.cache-hit != 'true' shell: bash run: | BASE_URL="${{ inputs.vcs-url || github.server_url }}" REPO="${{ inputs.repo || 'rodin/review-bot' }}" VERSION="${{ steps.version.outputs.version }}" BINARY="review-bot-linux-amd64" curl -sSfL "${BASE_URL}/${REPO}/releases/download/${VERSION}/${BINARY}" \ -o "${{ runner.temp }}/review-bot" curl -sSfL "${BASE_URL}/${REPO}/releases/download/${VERSION}/checksums.txt" \ -o "${{ runner.temp }}/checksums.txt" # Verify SHA-256 checksum cd "${{ runner.temp }}" EXPECTED=$(grep "${BINARY}" checksums.txt | awk '{print $1}') ACTUAL=$(sha256sum review-bot | awk '{print $1}') if [ -z "$EXPECTED" ]; then echo "Error: no checksum found for ${BINARY}" >&2 exit 1 fi if [ "$EXPECTED" != "$ACTUAL" ]; then echo "Error: checksum mismatch!" >&2 echo " Expected: $EXPECTED" >&2 echo " Actual: $ACTUAL" >&2 exit 1 fi chmod +x "${{ runner.temp }}/review-bot" echo "Installed review-bot ${VERSION} (checksum verified)" - name: Run review shell: bash env: VCS_URL: ${{ inputs.vcs-url || github.server_url }} GITEA_REPO: ${{ inputs.repo || github.repository }} PR_NUMBER: ${{ inputs.pr-number || github.event.pull_request.number }} REVIEWER_TOKEN: ${{ inputs.reviewer-token }} REVIEWER_NAME: ${{ inputs.reviewer-name }} LLM_BASE_URL: ${{ inputs.llm-base-url }} LLM_API_KEY: ${{ inputs.llm-api-key }} LLM_MODEL: ${{ inputs.llm-model }} CONVENTIONS_FILE: ${{ inputs.conventions-file }} PATTERNS_REPO: ${{ inputs.patterns-repo }} PATTERNS_FILES: ${{ inputs.patterns-files }} LLM_TEMPERATURE: ${{ inputs.temperature }} LLM_TIMEOUT: ${{ inputs.timeout }} LLM_PROVIDER: ${{ inputs.llm-provider }} UPDATE_EXISTING: ${{ inputs.update-existing }} SYSTEM_PROMPT_FILE: ${{ inputs.system-prompt-file }} PERSONA: ${{ inputs.persona }} PERSONA_FILE: ${{ inputs.persona-file }} AICORE_CLIENT_ID: ${{ inputs.aicore-client-id }} AICORE_CLIENT_SECRET: ${{ inputs.aicore-client-secret }} AICORE_AUTH_URL: ${{ inputs.aicore-auth-url }} AICORE_API_URL: ${{ inputs.aicore-api-url }} AICORE_RESOURCE_GROUP: ${{ inputs.aicore-resource-group }} run: | ARGS="" if [ "${{ inputs.dry-run }}" = "true" ]; then ARGS="--dry-run" fi ${{ runner.temp }}/review-bot $ARGS