From 4621489d29d9e030aa099ed2b2e141ecde9c0281 Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Sun, 22 Mar 2026 09:20:43 -0400 Subject: [PATCH 1/8] Removed all upstream GitHub Action workflows --- .github/workflows/draft-release.yml | 25 -- .github/workflows/evals-nightly.yml | 104 -------- .github/workflows/gemini-dispatch.yml | 237 ------------------ .github/workflows/gemini-invoke.yml | 118 --------- .github/workflows/gemini-issue-fixer.yml | 94 ------- .github/workflows/gemini-plan-execute.yml | 126 ---------- .github/workflows/gemini-review.yml | 114 --------- .github/workflows/gemini-scheduled-triage.yml | 214 ---------------- .github/workflows/gemini-triage.yml | 158 ------------ .github/workflows/publish.yml | 25 -- .github/workflows/release.yml | 17 -- 11 files changed, 1232 deletions(-) delete mode 100644 .github/workflows/draft-release.yml delete mode 100644 .github/workflows/evals-nightly.yml delete mode 100644 .github/workflows/gemini-dispatch.yml delete mode 100644 .github/workflows/gemini-invoke.yml delete mode 100644 .github/workflows/gemini-issue-fixer.yml delete mode 100644 .github/workflows/gemini-plan-execute.yml delete mode 100644 .github/workflows/gemini-review.yml delete mode 100644 .github/workflows/gemini-scheduled-triage.yml delete mode 100644 .github/workflows/gemini-triage.yml delete mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml deleted file mode 100644 index 872ede857..000000000 --- a/.github/workflows/draft-release.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: 'Draft release' - -on: - workflow_dispatch: - inputs: - version_strategy: - description: 'Version strategy: The strategy to used to update the version based on semantic versioning (more info at https://semver.org/).' - required: true - default: 'patch' - type: 'choice' - options: - - 'major' - - 'minor' - - 'patch' - -jobs: - draft-release: - uses: 'google-github-actions/.github/.github/workflows/draft-release.yml@v3' # ratchet:exclude - permissions: - contents: 'read' - pull-requests: 'write' - with: - version_strategy: '${{ github.event.inputs.version_strategy }}' - secrets: - ACTIONS_BOT_TOKEN: '${{ secrets.ACTIONS_BOT_TOKEN }}' diff --git a/.github/workflows/evals-nightly.yml b/.github/workflows/evals-nightly.yml deleted file mode 100644 index f277e624a..000000000 --- a/.github/workflows/evals-nightly.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: 'Nightly Evaluations' - -permissions: - contents: 'read' - -on: - schedule: - - cron: '0 1 * * *' # 1 AM UTC - workflow_dispatch: - inputs: - iterations: - description: 'Number of iterations per test case' - required: true - default: '1' - -jobs: - list-evals: - runs-on: 'ubuntu-22.04' - outputs: - matrix: '${{ steps.set-matrix.outputs.matrix }}' - steps: - - name: 'Checkout code' - uses: 'actions/checkout@v4' # ratchet:exclude - - id: 'set-matrix' - run: | - FILES=$(find evals -maxdepth 1 -name "*.eval.ts" | sort | jq -R -s -c 'split("\n")[:-1]') - echo "matrix=${FILES}" >> "$GITHUB_OUTPUT" - - evaluate: - needs: 'list-evals' - runs-on: 'ubuntu-22.04' - strategy: - fail-fast: false - matrix: - model: ['gemini-3-flash-preview'] - eval-file: '${{ fromJson(needs.list-evals.outputs.matrix) }}' - name: 'Evaluate ${{ matrix.eval-file }} (${{ matrix.model }})' - - steps: - - name: 'Checkout code' - uses: 'actions/checkout@v4' # ratchet:exclude - - - name: 'Set up Node.js' - uses: 'actions/setup-node@v4' # ratchet:exclude - with: - node-version: '20' - cache: 'npm' - - - name: 'Install dependencies' - # Retry logic for transient network or package retrieval failures - run: | - npm ci || (sleep 10 && npm ci) || (sleep 30 && npm ci) - - - name: 'Install Gemini CLI' - # Retry logic for transient network or package retrieval failures - run: | - npm install -g @google/gemini-cli@latest || (sleep 10 && npm install -g @google/gemini-cli@latest) || (sleep 30 && npm install -g @google/gemini-cli@latest) - - - name: 'Run Evaluations' - id: 'run_evals' - env: - GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' - GOOGLE_API_KEY: '${{ secrets.GOOGLE_API_KEY }}' - GEMINI_MODEL: '${{ matrix.model }}' - run: | - BASE_NAME=$(basename "${{ matrix.eval-file }}" .eval.ts) - npm run test:evals -- "${{ matrix.eval-file }}" --reporter=json --outputFile="eval-results-${{ matrix.model }}-${BASE_NAME}.json" - - - name: 'Upload Results' - if: 'always()' - uses: 'actions/upload-artifact@v4' # ratchet:exclude - with: - name: 'eval-results-${{ matrix.model }}-${{ strategy.job-index }}' - path: 'eval-results-${{ matrix.model }}-*.json' - - report: - needs: 'evaluate' - if: 'always()' - runs-on: 'ubuntu-22.04' - steps: - - name: 'Checkout code' - uses: 'actions/checkout@v4' # ratchet:exclude - - - name: 'Set up Node.js' - uses: 'actions/setup-node@v4' # ratchet:exclude - with: - node-version: '20' - cache: 'npm' - - - name: 'Install dependencies' - # Retry logic for transient network or package retrieval failures - run: | - npm ci || (sleep 10 && npm ci) || (sleep 30 && npm ci) - - - name: 'Download Results' - uses: 'actions/download-artifact@v4' # ratchet:exclude - with: - path: 'eval-results' - pattern: 'eval-results-*' - merge-multiple: true - - - name: 'Aggregate All Results' - run: | - npx tsx scripts/aggregate_evals.ts eval-results/*.json >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/gemini-dispatch.yml b/.github/workflows/gemini-dispatch.yml deleted file mode 100644 index e085068bd..000000000 --- a/.github/workflows/gemini-dispatch.yml +++ /dev/null @@ -1,237 +0,0 @@ -name: '🔀 Gemini Dispatch' - -on: - pull_request_review_comment: - types: - - 'created' - pull_request_review: - types: - - 'submitted' - pull_request: - types: - - 'opened' - issues: - types: - - 'opened' - - 'reopened' - issue_comment: - types: - - 'created' - -defaults: - run: - shell: 'bash' - -jobs: - debugger: - if: |- - ${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }} - runs-on: 'ubuntu-latest' - permissions: - contents: 'read' - steps: - - name: 'Print context for debugging' - env: - DEBUG_event_name: '${{ github.event_name }}' - DEBUG_event__action: '${{ github.event.action }}' - DEBUG_event__comment__author_association: '${{ github.event.comment.author_association }}' - DEBUG_event__issue__author_association: '${{ github.event.issue.author_association }}' - DEBUG_event__pull_request__author_association: '${{ github.event.pull_request.author_association }}' - DEBUG_event__review__author_association: '${{ github.event.review.author_association }}' - DEBUG_event: '${{ toJSON(github.event) }}' - run: |- - env | grep '^DEBUG_' - - dispatch: - # For PRs: only if not from a fork - # For issues: only on open/reopen - # For comments: only if user types @gemini-cli and is OWNER/MEMBER/COLLABORATOR - if: |- - ( - github.event_name == 'pull_request' && - github.event.pull_request.head.repo.fork == false - ) || ( - github.event_name == 'issues' && - contains(fromJSON('["opened", "reopened"]'), github.event.action) - ) || ( - github.event.sender.type == 'User' && - startsWith(github.event.comment.body || github.event.review.body || github.event.issue.body, '@gemini-cli') && - contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association || github.event.review.author_association || github.event.issue.author_association) - ) - runs-on: 'ubuntu-latest' - permissions: - contents: 'read' - issues: 'write' - pull-requests: 'write' - outputs: - command: '${{ steps.extract_command.outputs.command }}' - request: '${{ steps.extract_command.outputs.request }}' - additional_context: '${{ steps.extract_command.outputs.additional_context }}' - issue_number: '${{ github.event.pull_request.number || github.event.issue.number }}' - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'read' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Extract command' - id: 'extract_command' - uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8 - env: - EVENT_TYPE: '${{ github.event_name }}.${{ github.event.action }}' - REQUEST: '${{ github.event.comment.body || github.event.review.body || github.event.issue.body }}' - with: - script: | - const eventType = process.env.EVENT_TYPE; - const request = process.env.REQUEST; - core.setOutput('request', request); - - if (eventType === 'pull_request.opened') { - core.setOutput('command', 'review'); - } else if (['issues.opened', 'issues.reopened'].includes(eventType)) { - core.setOutput('command', 'triage'); - } else if (request.startsWith("@gemini-cli /review")) { - core.setOutput('command', 'review'); - const additionalContext = request.replace(/^@gemini-cli \/review/, '').trim(); - core.setOutput('additional_context', additionalContext); - } else if (request.startsWith("@gemini-cli /triage")) { - core.setOutput('command', 'triage'); - } else if (request.startsWith("@gemini-cli /approve")) { - core.setOutput('command', 'approve'); - const additionalContext = request.replace(/^@gemini-cli \/approve/, '').trim(); - core.setOutput('additional_context', additionalContext); - } else if (request.startsWith("@gemini-cli /fix")) { - core.setOutput('command', 'fix'); - } else if (request.startsWith("@gemini-cli")) { - const additionalContext = request.replace(/^@gemini-cli/, '').trim(); - core.setOutput('command', 'invoke'); - core.setOutput('additional_context', additionalContext); - } else { - core.setOutput('command', 'fallthrough'); - } - - - name: 'Acknowledge request' - env: - GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' - MESSAGE: |- - 🤖 Hi @${{ github.actor }}, I've received your request, and I'm working on it now! You can track my progress [in the logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details. - REPOSITORY: '${{ github.repository }}' - run: |- - gh issue comment "${ISSUE_NUMBER}" \ - --body "${MESSAGE}" \ - --repo "${REPOSITORY}" - - review: - needs: 'dispatch' - if: |- - ${{ needs.dispatch.outputs.command == 'review' }} - uses: './.github/workflows/gemini-review.yml' - permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - with: - additional_context: '${{ needs.dispatch.outputs.additional_context }}' - secrets: 'inherit' - - triage: - needs: 'dispatch' - if: |- - ${{ needs.dispatch.outputs.command == 'triage' }} - uses: './.github/workflows/gemini-triage.yml' - permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - with: - additional_context: '${{ needs.dispatch.outputs.additional_context }}' - secrets: 'inherit' - - fix: - needs: 'dispatch' - if: |- - ${{ needs.dispatch.outputs.command == 'fix' }} - uses: './.github/workflows/gemini-issue-fixer.yml' - permissions: - contents: 'write' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - secrets: 'inherit' - - invoke: - needs: 'dispatch' - if: |- - ${{ needs.dispatch.outputs.command == 'invoke' }} - uses: './.github/workflows/gemini-invoke.yml' - permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - with: - additional_context: '${{ needs.dispatch.outputs.additional_context }}' - secrets: 'inherit' - - plan-execute: - needs: 'dispatch' - if: |- - ${{ needs.dispatch.outputs.command == 'approve' }} - uses: './.github/workflows/gemini-plan-execute.yml' - permissions: - contents: 'write' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - with: - additional_context: '${{ needs.dispatch.outputs.additional_context }}' - secrets: 'inherit' - - fallthrough: - needs: - - 'dispatch' - - 'review' - - 'triage' - - 'invoke' - - 'plan-execute' - if: |- - ${{ always() && !cancelled() && (failure() || needs.dispatch.outputs.command == 'fallthrough') }} - runs-on: 'ubuntu-latest' - permissions: - contents: 'read' - issues: 'write' - pull-requests: 'write' - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'read' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Send failure comment' - env: - GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' - MESSAGE: |- - 🤖 I'm sorry @${{ github.actor }}, but I was unable to process your request. Please [see the logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details. - REPOSITORY: '${{ github.repository }}' - run: |- - gh issue comment "${ISSUE_NUMBER}" \ - --body "${MESSAGE}" \ - --repo "${REPOSITORY}" diff --git a/.github/workflows/gemini-invoke.yml b/.github/workflows/gemini-invoke.yml deleted file mode 100644 index 5bec70a52..000000000 --- a/.github/workflows/gemini-invoke.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: 'â–ļī¸ Gemini Invoke' - -on: - workflow_call: - inputs: - additional_context: - type: 'string' - description: 'Any additional context from the request' - required: false - -concurrency: - group: '${{ github.workflow }}-invoke-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' - cancel-in-progress: false - -defaults: - run: - shell: 'bash' - -jobs: - invoke: - runs-on: 'ubuntu-latest' - permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'read' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Checkout Code' - uses: 'actions/checkout@v4' # ratchet:exclude - - - name: 'Run Gemini CLI' - id: 'run_gemini' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude - env: - TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' - DESCRIPTION: '${{ github.event.pull_request.body || github.event.issue.body }}' - EVENT_NAME: '${{ github.event_name }}' - GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - IS_PULL_REQUEST: '${{ !!github.event.pull_request }}' - ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' - REPOSITORY: '${{ github.repository }}' - ADDITIONAL_CONTEXT: '${{ inputs.additional_context }}' - with: - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' - gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' - gemini_model: '${{ vars.GEMINI_MODEL }}' - google_api_key: '${{ secrets.GOOGLE_API_KEY }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' - workflow_name: 'gemini-invoke' - settings: |- - { - "model": { - "maxSessionTurns": 25 - }, - "telemetry": { - "enabled": true, - "target": "local", - "outfile": ".gemini/telemetry.log" - }, - "mcpServers": { - "github": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", - "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:v0.27.0" - ], - "includeTools": [ - "add_issue_comment", - "issue_read", - "list_issues", - "search_issues", - "pull_request_read", - "list_pull_requests", - "search_pull_requests", - "get_commit", - "get_file_contents", - "list_commits", - "search_code" - ], - "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" - } - } - }, - "tools": { - "core": [ - "run_shell_command(cat)", - "run_shell_command(echo)", - "run_shell_command(grep)", - "run_shell_command(head)", - "run_shell_command(tail)" - ] - } - } - prompt: '/gemini-invoke' diff --git a/.github/workflows/gemini-issue-fixer.yml b/.github/workflows/gemini-issue-fixer.yml deleted file mode 100644 index a19804418..000000000 --- a/.github/workflows/gemini-issue-fixer.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: '🧙 Gemini Issue Fixer' - -on: - workflow_call: - -concurrency: - group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}-${{ github.event.issue.number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - create-pr: - timeout-minutes: 30 - runs-on: 'ubuntu-latest' - permissions: - contents: 'write' # Enable reading and modifying code - id-token: 'write' # Enable minting an identity token - issues: 'write' # Enable updating issues, such as posting a comment - pull-requests: 'write' # Enable creating pull requests - - steps: - # Mint a token so that the comments show up as gemini-cli instead of github-actions. - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'write' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Checkout repository' - uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 - - - name: 'Run Gemini PR Create' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude - id: 'gemini_pr_create' - env: - GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN }}' - REPOSITORY: '${{ github.repository }}' - ISSUE_NUMBER: '${{ github.event.issue.number }}' - ISSUE_TITLE: '${{ github.event.issue.title }}' - ISSUE_BODY: '${{ github.event.issue.body }}' - BRANCH_NAME: 'gemini-fix-${{ github.event.issue.number }}' - EVENT_NAME: '${{ github.event_name }}' - TRIGGERING_ACTOR: '${{ github.triggering_actor }}' - with: - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' - gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' - gemini_model: '${{ vars.GEMINI_MODEL }}' - google_api_key: '${{ secrets.GOOGLE_API_KEY }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' - settings: |- - { - "debug": ${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}, - "model": { - "maxSessionTurns": 200 - }, - "mcpServers": { - "github": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", - "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:v0.27.0" - ], - "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" - } - } - }, - "telemetry": { - "enabled": true, - "target": "local", - "outfile": ".gemini/telemetry.log" - } - } - prompt: '/gemini-issue-fixer' diff --git a/.github/workflows/gemini-plan-execute.yml b/.github/workflows/gemini-plan-execute.yml deleted file mode 100644 index 9dfe61a21..000000000 --- a/.github/workflows/gemini-plan-execute.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: '🧙 Gemini Plan Execution' - -on: - workflow_call: - inputs: - additional_context: - type: 'string' - description: 'Any additional context from the request' - required: false - -concurrency: - group: '${{ github.workflow }}-plan-execute-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - plan-execute: - timeout-minutes: 30 - runs-on: 'ubuntu-latest' - permissions: - contents: 'write' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'write' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Checkout Code' - uses: 'actions/checkout@v4' # ratchet:exclude - - - name: 'Run Gemini CLI' - id: 'run_gemini' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude - env: - TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' - DESCRIPTION: '${{ github.event.pull_request.body || github.event.issue.body }}' - EVENT_NAME: '${{ github.event_name }}' - GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - IS_PULL_REQUEST: '${{ !!github.event.pull_request }}' - ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' - REPOSITORY: '${{ github.repository }}' - ADDITIONAL_CONTEXT: '${{ inputs.additional_context }}' - with: - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' - gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' - gemini_model: '${{ vars.GEMINI_MODEL }}' - google_api_key: '${{ secrets.GOOGLE_API_KEY }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' - workflow_name: 'gemini-plan-execute' - settings: |- - { - "model": { - "maxSessionTurns": 25 - }, - "telemetry": { - "enabled": true, - "target": "local", - "outfile": ".gemini/telemetry.log" - }, - "mcpServers": { - "github": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", - "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:v0.27.0" - ], - "includeTools": [ - "add_issue_comment", - "issue_read", - "list_issues", - "search_issues", - "create_pull_request", - "pull_request_read", - "list_pull_requests", - "search_pull_requests", - "create_branch", - "create_or_update_file", - "delete_file", - "fork_repository", - "get_commit", - "get_file_contents", - "list_commits", - "push_files", - "search_code" - ], - "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" - } - } - }, - "tools": { - "core": [ - "run_shell_command(cat)", - "run_shell_command(echo)", - "run_shell_command(grep)", - "run_shell_command(head)", - "run_shell_command(tail)" - ] - } - } - prompt: '/gemini-plan-execute' diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml deleted file mode 100644 index 510bfb74b..000000000 --- a/.github/workflows/gemini-review.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: '🔎 Gemini Review' - -on: - workflow_call: - inputs: - additional_context: - type: 'string' - description: 'Any additional context from the request' - required: false - -concurrency: - group: '${{ github.workflow }}-review-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - review: - runs-on: 'ubuntu-latest' - timeout-minutes: 7 - permissions: - contents: 'read' - id-token: 'write' - issues: 'write' - pull-requests: 'write' - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'read' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Checkout repository' - uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 - - - name: 'Run Gemini pull request review' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude - id: 'gemini_pr_review' - env: - GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - ISSUE_TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' - ISSUE_BODY: '${{ github.event.pull_request.body || github.event.issue.body }}' - PULL_REQUEST_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' - REPOSITORY: '${{ github.repository }}' - ADDITIONAL_CONTEXT: '${{ inputs.additional_context }}' - GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' - with: - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' - gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' - gemini_model: '${{ vars.GEMINI_MODEL }}' - google_api_key: '${{ secrets.GOOGLE_API_KEY }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' - workflow_name: 'gemini-review' - settings: |- - { - "model": { - "maxSessionTurns": 25 - }, - "telemetry": { - "enabled": true, - "target": "local", - "outfile": ".gemini/telemetry.log" - }, - "mcpServers": { - "github": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", - "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:v0.27.0" - ], - "includeTools": [ - "add_comment_to_pending_review", - "pull_request_read", - "pull_request_review_write" - ], - "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" - } - } - }, - "tools": { - "core": [ - "run_shell_command(cat)", - "run_shell_command(echo)", - "run_shell_command(grep)", - "run_shell_command(head)", - "run_shell_command(tail)" - ] - } - } - extensions: | - [ - "https://github.com/gemini-cli-extensions/code-review" - ] - prompt: '/pr-code-review' diff --git a/.github/workflows/gemini-scheduled-triage.yml b/.github/workflows/gemini-scheduled-triage.yml deleted file mode 100644 index 962164576..000000000 --- a/.github/workflows/gemini-scheduled-triage.yml +++ /dev/null @@ -1,214 +0,0 @@ -name: '📋 Gemini Scheduled Issue Triage' - -on: - schedule: - - cron: '0 * * * *' # Runs every hour - pull_request: - branches: - - 'main' - - 'release/**/*' - paths: - - '.github/workflows/gemini-scheduled-triage.yml' - push: - branches: - - 'main' - - 'release/**/*' - paths: - - '.github/workflows/gemini-scheduled-triage.yml' - workflow_dispatch: - -concurrency: - group: '${{ github.workflow }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - triage: - runs-on: 'ubuntu-latest' - timeout-minutes: 7 - permissions: - contents: 'read' - id-token: 'write' - issues: 'read' - pull-requests: 'read' - outputs: - available_labels: '${{ steps.get_labels.outputs.available_labels }}' - triaged_issues: '${{ env.TRIAGED_ISSUES }}' - steps: - - name: 'Get repository labels' - id: 'get_labels' - uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 - with: - # NOTE: we intentionally do not use the minted token. The default - # GITHUB_TOKEN provided by the action has enough permissions to read - # the labels. - script: |- - const labels = []; - for await (const response of github.paginate.iterator(github.rest.issues.listLabelsForRepo, { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100, // Maximum per page to reduce API calls - })) { - labels.push(...response.data); - } - - if (!labels || labels.length === 0) { - core.setFailed('There are no issue labels in this repository.') - } - - const labelNames = labels.map(label => label.name).sort(); - core.setOutput('available_labels', labelNames.join(',')); - core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); - return labelNames; - - - name: 'Find untriaged issues' - id: 'find_issues' - env: - GITHUB_REPOSITORY: '${{ github.repository }}' - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN || github.token }}' - run: |- - echo '🔍 Finding unlabeled issues and issues marked for triage...' - ISSUES="$(gh issue list \ - --state 'open' \ - --search 'no:label label:"status/needs-triage"' \ - --json number,title,body \ - --limit '100' \ - --repo "${GITHUB_REPOSITORY}" - )" - - echo '📝 Setting output for GitHub Actions...' - echo "issues_to_triage=${ISSUES}" >> "${GITHUB_OUTPUT}" - - ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')" - echo "✅ Found ${ISSUE_COUNT} issue(s) to triage! đŸŽ¯" - - - name: 'Run Gemini Issue Analysis' - id: 'gemini_issue_analysis' - if: |- - ${{ steps.find_issues.outputs.issues_to_triage != '[]' }} - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude - env: - GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs - ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}' - REPOSITORY: '${{ github.repository }}' - AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' - with: - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' - gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' - gemini_model: '${{ vars.GEMINI_MODEL }}' - google_api_key: '${{ secrets.GOOGLE_API_KEY }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' - workflow_name: 'gemini-scheduled-triage' - settings: |- - { - "model": { - "maxSessionTurns": 25 - }, - "telemetry": { - "enabled": true, - "target": "local", - "outfile": ".gemini/telemetry.log" - }, - "tools": { - "core": [ - "run_shell_command(echo)", - "run_shell_command(jq)", - "run_shell_command(printenv)" - ] - } - } - prompt: '/gemini-scheduled-triage' - - label: - runs-on: 'ubuntu-latest' - needs: - - 'triage' - if: |- - needs.triage.outputs.available_labels != '' && - needs.triage.outputs.available_labels != '[]' && - needs.triage.outputs.triaged_issues != '' && - needs.triage.outputs.triaged_issues != '[]' - permissions: - contents: 'read' - issues: 'write' - pull-requests: 'write' - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'read' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Apply labels' - env: - AVAILABLE_LABELS: '${{ needs.triage.outputs.available_labels }}' - TRIAGED_ISSUES: '${{ needs.triage.outputs.triaged_issues }}' - uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 - with: - # Use the provided token so that the "gemini-cli" is the actor in the - # log for what changed the labels. - github-token: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - script: |- - // Parse the available labels - const availableLabels = (process.env.AVAILABLE_LABELS || '').split(',') - .map((label) => label.trim()) - .sort() - - // Parse out the triaged issues - const triagedIssues = (JSON.parse(process.env.TRIAGED_ISSUES || '{}')) - .sort((a, b) => a.issue_number - b.issue_number) - - core.debug(`Triaged issues: ${JSON.stringify(triagedIssues)}`); - - // Iterate over each label - for (const issue of triagedIssues) { - if (!issue) { - core.debug(`Skipping empty issue: ${JSON.stringify(issue)}`); - continue; - } - - const issueNumber = issue.issue_number; - if (!issueNumber) { - core.debug(`Skipping issue with no data: ${JSON.stringify(issue)}`); - continue; - } - - // Extract and reject invalid labels - we do this just in case - // someone was able to prompt inject malicious labels. - let labelsToSet = (issue.labels_to_set || []) - .map((label) => label.trim()) - .filter((label) => availableLabels.includes(label)) - .sort() - - core.debug(`Identified labels to set: ${JSON.stringify(labelsToSet)}`); - - if (labelsToSet.length === 0) { - core.info(`Skipping issue #${issueNumber} - no labels to set.`) - continue; - } - - core.debug(`Setting labels on issue #${issueNumber} to ${labelsToSet.join(', ')} (${issue.explanation || 'no explanation'})`) - - await github.rest.issues.setLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: labelsToSet, - }); - } diff --git a/.github/workflows/gemini-triage.yml b/.github/workflows/gemini-triage.yml deleted file mode 100644 index 22b1413c2..000000000 --- a/.github/workflows/gemini-triage.yml +++ /dev/null @@ -1,158 +0,0 @@ -name: '🔀 Gemini Triage' - -on: - workflow_call: - inputs: - additional_context: - type: 'string' - description: 'Any additional context from the request' - required: false - -concurrency: - group: '${{ github.workflow }}-triage-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' - cancel-in-progress: true - -defaults: - run: - shell: 'bash' - -jobs: - triage: - runs-on: 'ubuntu-latest' - timeout-minutes: 7 - outputs: - available_labels: '${{ steps.get_labels.outputs.available_labels }}' - selected_labels: '${{ env.SELECTED_LABELS }}' - permissions: - contents: 'read' - id-token: 'write' - issues: 'read' - pull-requests: 'read' - steps: - - name: 'Get repository labels' - id: 'get_labels' - uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 - with: - # NOTE: we intentionally do not use the given token. The default - # GITHUB_TOKEN provided by the action has enough permissions to read - # the labels. - script: |- - const labels = []; - for await (const response of github.paginate.iterator(github.rest.issues.listLabelsForRepo, { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100, // Maximum per page to reduce API calls - })) { - labels.push(...response.data); - } - - if (!labels || labels.length === 0) { - core.setFailed('There are no issue labels in this repository.') - } - - const labelNames = labels.map(label => label.name).sort(); - core.setOutput('available_labels', labelNames.join(',')); - core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); - return labelNames; - - - name: 'Run Gemini issue analysis' - id: 'gemini_analysis' - if: |- - ${{ steps.get_labels.outputs.available_labels != '' }} - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude - env: - GITHUB_TOKEN: '' # Do NOT pass any auth tokens here since this runs on untrusted inputs - ISSUE_TITLE: '${{ github.event.issue.title }}' - ISSUE_BODY: '${{ github.event.issue.body }}' - AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' - with: - gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' - gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' - gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' - gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' - gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' - gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' - gemini_model: '${{ vars.GEMINI_MODEL }}' - google_api_key: '${{ secrets.GOOGLE_API_KEY }}' - use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' - use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' - upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' - workflow_name: 'gemini-triage' - settings: |- - { - "model": { - "maxSessionTurns": 25 - }, - "telemetry": { - "enabled": true, - "target": "local", - "outfile": ".gemini/telemetry.log" - }, - "tools": { - "core": [ - "run_shell_command(echo)" - ] - } - } - prompt: '/gemini-triage' - - label: - runs-on: 'ubuntu-latest' - needs: - - 'triage' - if: |- - ${{ needs.triage.outputs.selected_labels != '' }} - permissions: - contents: 'read' - issues: 'write' - pull-requests: 'write' - steps: - - name: 'Mint identity token' - id: 'mint_identity_token' - if: |- - ${{ vars.APP_ID }} - uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 - with: - app-id: '${{ vars.APP_ID }}' - private-key: '${{ secrets.APP_PRIVATE_KEY }}' - permission-contents: 'read' - permission-issues: 'write' - permission-pull-requests: 'write' - - - name: 'Apply labels' - env: - ISSUE_NUMBER: '${{ github.event.issue.number }}' - AVAILABLE_LABELS: '${{ needs.triage.outputs.available_labels }}' - SELECTED_LABELS: '${{ needs.triage.outputs.selected_labels }}' - uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 - with: - # Use the provided token so that the "gemini-cli" is the actor in the - # log for what changed the labels. - github-token: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' - script: |- - // Parse the available labels - const availableLabels = (process.env.AVAILABLE_LABELS || '').split(',') - .map((label) => label.trim()) - .sort() - - // Parse the label as a CSV, reject invalid ones - we do this just - // in case someone was able to prompt inject malicious labels. - const selectedLabels = (process.env.SELECTED_LABELS || '').split(',') - .map((label) => label.trim()) - .filter((label) => availableLabels.includes(label)) - .sort() - - // Set the labels - const issueNumber = process.env.ISSUE_NUMBER; - if (selectedLabels && selectedLabels.length > 0) { - await github.rest.issues.setLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: selectedLabels, - }); - core.info(`Successfully set labels: ${selectedLabels.join(',')}`); - } else { - core.info(`Failed to determine labels to set. There may not be enough information in the issue or pull request.`) - } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 46db0a2ee..000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: 'Publish immutable action version' - -on: - workflow_dispatch: - release: - types: - - 'published' - -jobs: - publish: - runs-on: 'ubuntu-latest' - permissions: - contents: 'read' - id-token: 'write' - packages: 'write' - - steps: - - name: 'Checkout' - uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 - - - name: 'Publish' - id: 'publish' - uses: 'actions/publish-immutable-action@4bc8754ffc40f27910afb20287dbbbb675a4e978' # ratchet:actions/publish-immutable-action@v0.0.4 - with: - github-token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 934de9909..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'Release' - -on: - push: - branches: - - 'main' - - 'release/**/*' - -jobs: - release: - uses: 'google-github-actions/.github/.github/workflows/release.yml@v3' # ratchet:exclude - permissions: - attestations: 'write' - contents: 'write' - packages: 'write' - secrets: - ACTIONS_BOT_TOKEN: '${{ secrets.ACTIONS_BOT_TOKEN }}' From 1b98c3718dad4a1a2e94849a9218e2c8c5b6144c Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Sun, 22 Mar 2026 09:21:35 -0400 Subject: [PATCH 2/8] Added bucky workflow --- .github/workflows/bucky.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/bucky.yml diff --git a/.github/workflows/bucky.yml b/.github/workflows/bucky.yml new file mode 100644 index 000000000..2d8e143e3 --- /dev/null +++ b/.github/workflows/bucky.yml @@ -0,0 +1,31 @@ +name: 'đŸĻĢ Bucky' + +on: + pull_request: + types: [opened, reopened] + issue_comment: + types: [created] + +jobs: + bucky: + # For PRs only, for comments only if user types @bucky + if: |- + ( + github.event_name == 'pull_request' + ) || ( + github.event_name == 'issue_comment' && + github.event.sender.type == 'User' && + startsWith(github.event.comment.body, '@bucky') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + ) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + id-token: write + steps: + - name: Checkout + uses: 'actions/checkout@v5' + + - name: Run Bucky + uses: Sonos-Inc/bucky@v1 From 8290229967a7b1b5ca28038d5895269f06e2fe03 Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Wed, 11 Mar 2026 15:25:13 -0400 Subject: [PATCH 3/8] feat: add file-based output mode to avoid exceeding GitHub Actions output limits Add `output_to_file` input that redirects all output to files and returns file paths as step outputs instead of content. When enabled with `gemini_debug: true`, debug output goes to files without console streaming, keeping GITHUB_OUTPUT well under the ~1MB platform limit. New outputs: `output_mode` (content/file) and `artifacts_dir` (path to gemini-artifacts/). Full backwards compatibility when not set. Ref: google-github-actions/run-gemini-cli#479 --- action.yml | 106 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/action.yml b/action.yml index 7eccaf14b..02e30b02d 100644 --- a/action.yml +++ b/action.yml @@ -83,6 +83,10 @@ inputs: description: 'Whether to upload artifacts to the github action.' required: false default: 'false' + output_to_file: + description: 'When true, redirect all output to files and return file paths as step outputs instead of content. Useful when gemini_debug is true to avoid exceeding GitHub Actions output limits.' + required: false + default: 'false' use_pnpm: description: 'Whether or not to use pnpm instead of npm to install gemini-cli' required: false @@ -107,6 +111,12 @@ outputs: error: description: 'The error output from the Gemini CLI execution, if any.' value: '${{ steps.gemini_run.outputs.gemini_errors }}' + output_mode: + description: 'Output mode used: "content" (default) or "file".' + value: '${{ steps.gemini_run.outputs.output_mode }}' + artifacts_dir: + description: 'Path to gemini-artifacts/ directory containing stdout.log, stderr.log, telemetry.log.' + value: '${{ steps.gemini_run.outputs.artifacts_dir }}' runs: using: 'composite' @@ -303,7 +313,12 @@ runs: # Run Gemini CLI with the provided prompt, using JSON output format # We capture stdout (JSON) to TEMP_STDOUT and stderr to TEMP_STDERR - if [[ "${GEMINI_DEBUG}" = true ]]; then + if [[ "${GEMINI_DEBUG}" = true ]] && [[ "${OUTPUT_TO_FILE}" = true ]]; then + echo "::notice::Gemini CLI debug output redirected to files (gemini-artifacts/)" + if ! gemini --debug --yolo --prompt "${PROMPT}" --output-format json 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then + FAILED=true + fi + elif [[ "${GEMINI_DEBUG}" = true ]]; then echo "::warning::Gemini CLI debug logging is enabled. This will stream responses, which could reveal sensitive information if processed with untrusted inputs." echo "::: Start Gemini CLI STDOUT :::" if ! gemini --debug --yolo --prompt "${PROMPT}" --output-format json 2> >(tee "${TEMP_STDERR}" >&2) | tee "${TEMP_STDOUT}"; then @@ -349,23 +364,38 @@ runs: fi - # Set the captured response as a step output, supporting multiline - echo "gemini_response<> "${GITHUB_OUTPUT}" - if [[ -n "${RESPONSE}" ]]; then - echo "${RESPONSE}" >> "${GITHUB_OUTPUT}" - else - cat "${TEMP_STDOUT}" >> "${GITHUB_OUTPUT}" - fi - echo "EOF" >> "${GITHUB_OUTPUT}" + ARTIFACTS_DIR="$(pwd)/gemini-artifacts" + echo "artifacts_dir=${ARTIFACTS_DIR}" >> "${GITHUB_OUTPUT}" - # Set the captured errors as a step output, supporting multiline - echo "gemini_errors<> "${GITHUB_OUTPUT}" - if [[ -n "${ERROR_JSON}" ]]; then - echo "${ERROR_JSON}" >> "${GITHUB_OUTPUT}" + if [[ "${OUTPUT_TO_FILE}" = true ]]; then + echo "output_mode=file" >> "${GITHUB_OUTPUT}" + # Return file paths instead of content + echo "gemini_response<> "${GITHUB_OUTPUT}" + echo "${ARTIFACTS_DIR}/stdout.log" >> "${GITHUB_OUTPUT}" + echo "EOF" >> "${GITHUB_OUTPUT}" + echo "gemini_errors<> "${GITHUB_OUTPUT}" + echo "${ARTIFACTS_DIR}/stderr.log" >> "${GITHUB_OUTPUT}" + echo "EOF" >> "${GITHUB_OUTPUT}" else - cat "${TEMP_STDERR}" >> "${GITHUB_OUTPUT}" + echo "output_mode=content" >> "${GITHUB_OUTPUT}" + # Set the captured response as a step output, supporting multiline + echo "gemini_response<> "${GITHUB_OUTPUT}" + if [[ -n "${RESPONSE}" ]]; then + echo "${RESPONSE}" >> "${GITHUB_OUTPUT}" + else + cat "${TEMP_STDOUT}" >> "${GITHUB_OUTPUT}" + fi + echo "EOF" >> "${GITHUB_OUTPUT}" + + # Set the captured errors as a step output, supporting multiline + echo "gemini_errors<> "${GITHUB_OUTPUT}" + if [[ -n "${ERROR_JSON}" ]]; then + echo "${ERROR_JSON}" >> "${GITHUB_OUTPUT}" + else + cat "${TEMP_STDERR}" >> "${GITHUB_OUTPUT}" + fi + echo "EOF" >> "${GITHUB_OUTPUT}" fi - echo "EOF" >> "${GITHUB_OUTPUT}" # Generate Job Summary if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then @@ -378,26 +408,35 @@ runs: echo "${PROMPT}" echo "\`\`\`" echo - if [[ -n "${RESPONSE}" ]]; then - echo "#### Response" - echo - echo "${RESPONSE}" - echo - fi - if [[ -n "${ERROR_JSON}" ]]; then - echo "#### Error" - echo - echo "\`\`\`json" - echo "${ERROR_JSON}" - echo "\`\`\`" - echo - elif [[ "${FAILED}" == "true" ]]; then - echo "#### Error Output" + if [[ "${OUTPUT_TO_FILE}" = true ]]; then + echo "#### Output (file mode)" echo - echo "\`\`\`" - cat "${TEMP_STDERR}" - echo "\`\`\`" + echo "Output redirected to files:" + echo "- stdout.log ($(wc -c < "${TEMP_STDOUT}" | xargs) bytes)" + echo "- stderr.log ($(wc -c < "${TEMP_STDERR}" | xargs) bytes)" echo + else + if [[ -n "${RESPONSE}" ]]; then + echo "#### Response" + echo + echo "${RESPONSE}" + echo + fi + if [[ -n "${ERROR_JSON}" ]]; then + echo "#### Error" + echo + echo "\`\`\`json" + echo "${ERROR_JSON}" + echo "\`\`\`" + echo + elif [[ "${FAILED}" == "true" ]]; then + echo "#### Error Output" + echo + echo "\`\`\`" + cat "${TEMP_STDERR}" + echo "\`\`\`" + echo + fi fi } >> "${GITHUB_STEP_SUMMARY}" fi @@ -415,6 +454,7 @@ runs: fi env: GEMINI_DEBUG: '${{ fromJSON(inputs.gemini_debug || false) }}' + OUTPUT_TO_FILE: '${{ fromJSON(inputs.output_to_file || false) }}' GEMINI_API_KEY: '${{ inputs.gemini_api_key }}' SURFACE: 'GitHub' GOOGLE_CLOUD_PROJECT: '${{ inputs.gcp_project_id }}' From 2088b421ac76775bd2d199c402e0da6685df2aef Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Wed, 11 Mar 2026 17:38:48 -0400 Subject: [PATCH 4/8] refactor: decouple output_to_file from gemini_debug Check output_to_file first, independently of debug mode. The debug flag only controls whether --debug is passed to gemini CLI. Also extract command args to avoid duplicating the gemini invocation. Ref: google-github-actions/run-gemini-cli#479 --- action.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/action.yml b/action.yml index 02e30b02d..1a428a7c3 100644 --- a/action.yml +++ b/action.yml @@ -311,24 +311,30 @@ runs: # Keep track of whether we've failed FAILED=false + # Build the base command arguments + GEMINI_ARGS="--yolo --prompt ${PROMPT} --output-format json" + if [[ "${GEMINI_DEBUG}" = true ]]; then + GEMINI_ARGS="--debug ${GEMINI_ARGS}" + fi + # Run Gemini CLI with the provided prompt, using JSON output format # We capture stdout (JSON) to TEMP_STDOUT and stderr to TEMP_STDERR - if [[ "${GEMINI_DEBUG}" = true ]] && [[ "${OUTPUT_TO_FILE}" = true ]]; then - echo "::notice::Gemini CLI debug output redirected to files (gemini-artifacts/)" - if ! gemini --debug --yolo --prompt "${PROMPT}" --output-format json 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then + if [[ "${OUTPUT_TO_FILE}" = true ]]; then + echo "::notice::Gemini CLI output redirected to files (gemini-artifacts/)" + if ! gemini ${GEMINI_ARGS} 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then FAILED=true fi elif [[ "${GEMINI_DEBUG}" = true ]]; then echo "::warning::Gemini CLI debug logging is enabled. This will stream responses, which could reveal sensitive information if processed with untrusted inputs." echo "::: Start Gemini CLI STDOUT :::" - if ! gemini --debug --yolo --prompt "${PROMPT}" --output-format json 2> >(tee "${TEMP_STDERR}" >&2) | tee "${TEMP_STDOUT}"; then + if ! gemini ${GEMINI_ARGS} 2> >(tee "${TEMP_STDERR}" >&2) | tee "${TEMP_STDOUT}"; then FAILED=true fi # Wait for async stderr logging to complete. This is because process substitution in Bash is async so let tee finish writing to ${TEMP_STDERR} sleep 1 echo "::: End Gemini CLI STDOUT :::" else - if ! gemini --yolo --prompt "${PROMPT}" --output-format json 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then + if ! gemini ${GEMINI_ARGS} 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then FAILED=true fi fi From 0ea33241562a9c10618a47bd009c609f8d148643 Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Wed, 11 Mar 2026 17:46:37 -0400 Subject: [PATCH 5/8] docs: regenerate README with new input and outputs Run `npm run docs` to update autogenerated sections with: - output_to_file input - output_mode and artifacts_dir outputs Ref: google-github-actions/run-gemini-cli#479 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4387a9eb5..ab5caf5e0 100644 --- a/README.md +++ b/README.md @@ -210,10 +210,16 @@ go to the [Gemini Assistant workflow documentation](./examples/workflows/gemini- - upload_artifacts: _(Optional, default: `false`)_ Whether to upload artifacts to the github action. +- output_to_file: _(Optional, default: `false`)_ When true, redirect all output to files and return file paths as step outputs instead of content. Useful when gemini_debug is true to avoid exceeding GitHub Actions output limits. + - use_pnpm: _(Optional, default: `false`)_ Whether or not to use pnpm instead of npm to install gemini-cli - workflow_name: _(Optional, default: `${{ github.workflow }}`)_ The GitHub workflow name, used for telemetry purposes. +- github_pr_number: _(Optional, default: `${{ github.event.pull_request.number }}`)_ The Pull Request number the CLI is operating on. Defaults to the event payload. + +- github_issue_number: _(Optional, default: `${{ github.event.issue.number }}`)_ The Issue number (or comma-separated list of issue numbers) the CLI is operating on. Defaults to the event payload. + @@ -227,6 +233,10 @@ go to the [Gemini Assistant workflow documentation](./examples/workflows/gemini- - error: The error output from the Gemini CLI execution, if any. +- output_mode: Output mode used: "content" (default) or "file". + +- artifacts_dir: Path to gemini-artifacts/ directory containing stdout.log, stderr.log, telemetry.log. + From 3cb01b6e7b012b64f32a515513f6dc772789982a Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Thu, 12 Mar 2026 06:18:56 -0400 Subject: [PATCH 6/8] fix: preserve prompt quoting in gemini CLI invocation The GEMINI_ARGS variable caused word-splitting on the multi-line PROMPT string, breaking the gemini CLI argument parsing. Use a separate DEBUG_FLAG variable instead and keep "${PROMPT}" quoted in each invocation. Ref: google-github-actions/run-gemini-cli#479 --- action.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/action.yml b/action.yml index 1a428a7c3..84fbd70bf 100644 --- a/action.yml +++ b/action.yml @@ -311,30 +311,30 @@ runs: # Keep track of whether we've failed FAILED=false - # Build the base command arguments - GEMINI_ARGS="--yolo --prompt ${PROMPT} --output-format json" + # Determine debug flag + DEBUG_FLAG="" if [[ "${GEMINI_DEBUG}" = true ]]; then - GEMINI_ARGS="--debug ${GEMINI_ARGS}" + DEBUG_FLAG="--debug" fi # Run Gemini CLI with the provided prompt, using JSON output format # We capture stdout (JSON) to TEMP_STDOUT and stderr to TEMP_STDERR if [[ "${OUTPUT_TO_FILE}" = true ]]; then echo "::notice::Gemini CLI output redirected to files (gemini-artifacts/)" - if ! gemini ${GEMINI_ARGS} 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then + if ! gemini ${DEBUG_FLAG} --yolo --prompt "${PROMPT}" --output-format json 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then FAILED=true fi elif [[ "${GEMINI_DEBUG}" = true ]]; then echo "::warning::Gemini CLI debug logging is enabled. This will stream responses, which could reveal sensitive information if processed with untrusted inputs." echo "::: Start Gemini CLI STDOUT :::" - if ! gemini ${GEMINI_ARGS} 2> >(tee "${TEMP_STDERR}" >&2) | tee "${TEMP_STDOUT}"; then + if ! gemini ${DEBUG_FLAG} --yolo --prompt "${PROMPT}" --output-format json 2> >(tee "${TEMP_STDERR}" >&2) | tee "${TEMP_STDOUT}"; then FAILED=true fi # Wait for async stderr logging to complete. This is because process substitution in Bash is async so let tee finish writing to ${TEMP_STDERR} sleep 1 echo "::: End Gemini CLI STDOUT :::" else - if ! gemini ${GEMINI_ARGS} 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then + if ! gemini --yolo --prompt "${PROMPT}" --output-format json 2> "${TEMP_STDERR}" 1> "${TEMP_STDOUT}"; then FAILED=true fi fi From a7ff7ec96bb76115ae2d1897663965ffd83fffd7 Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Sun, 22 Mar 2026 09:51:40 -0400 Subject: [PATCH 7/8] Revert "Added bucky workflow" This reverts commit 1b98c3718dad4a1a2e94849a9218e2c8c5b6144c. --- .github/workflows/bucky.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/bucky.yml diff --git a/.github/workflows/bucky.yml b/.github/workflows/bucky.yml deleted file mode 100644 index 2d8e143e3..000000000 --- a/.github/workflows/bucky.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: 'đŸĻĢ Bucky' - -on: - pull_request: - types: [opened, reopened] - issue_comment: - types: [created] - -jobs: - bucky: - # For PRs only, for comments only if user types @bucky - if: |- - ( - github.event_name == 'pull_request' - ) || ( - github.event_name == 'issue_comment' && - github.event.sender.type == 'User' && - startsWith(github.event.comment.body, '@bucky') && - contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) - ) - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: read - id-token: write - steps: - - name: Checkout - uses: 'actions/checkout@v5' - - - name: Run Bucky - uses: Sonos-Inc/bucky@v1 From b64e744d946d98bf2f2827f545bc99a5bceb8f98 Mon Sep 17 00:00:00 2001 From: Michal Szelag Date: Sun, 22 Mar 2026 09:52:24 -0400 Subject: [PATCH 8/8] Revert "Removed all upstream GitHub Action workflows" This reverts commit 4621489d29d9e030aa099ed2b2e141ecde9c0281. --- .github/workflows/draft-release.yml | 25 ++ .github/workflows/evals-nightly.yml | 104 ++++++++ .github/workflows/gemini-dispatch.yml | 237 ++++++++++++++++++ .github/workflows/gemini-invoke.yml | 118 +++++++++ .github/workflows/gemini-issue-fixer.yml | 94 +++++++ .github/workflows/gemini-plan-execute.yml | 126 ++++++++++ .github/workflows/gemini-review.yml | 114 +++++++++ .github/workflows/gemini-scheduled-triage.yml | 214 ++++++++++++++++ .github/workflows/gemini-triage.yml | 158 ++++++++++++ .github/workflows/publish.yml | 25 ++ .github/workflows/release.yml | 17 ++ 11 files changed, 1232 insertions(+) create mode 100644 .github/workflows/draft-release.yml create mode 100644 .github/workflows/evals-nightly.yml create mode 100644 .github/workflows/gemini-dispatch.yml create mode 100644 .github/workflows/gemini-invoke.yml create mode 100644 .github/workflows/gemini-issue-fixer.yml create mode 100644 .github/workflows/gemini-plan-execute.yml create mode 100644 .github/workflows/gemini-review.yml create mode 100644 .github/workflows/gemini-scheduled-triage.yml create mode 100644 .github/workflows/gemini-triage.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml new file mode 100644 index 000000000..872ede857 --- /dev/null +++ b/.github/workflows/draft-release.yml @@ -0,0 +1,25 @@ +name: 'Draft release' + +on: + workflow_dispatch: + inputs: + version_strategy: + description: 'Version strategy: The strategy to used to update the version based on semantic versioning (more info at https://semver.org/).' + required: true + default: 'patch' + type: 'choice' + options: + - 'major' + - 'minor' + - 'patch' + +jobs: + draft-release: + uses: 'google-github-actions/.github/.github/workflows/draft-release.yml@v3' # ratchet:exclude + permissions: + contents: 'read' + pull-requests: 'write' + with: + version_strategy: '${{ github.event.inputs.version_strategy }}' + secrets: + ACTIONS_BOT_TOKEN: '${{ secrets.ACTIONS_BOT_TOKEN }}' diff --git a/.github/workflows/evals-nightly.yml b/.github/workflows/evals-nightly.yml new file mode 100644 index 000000000..f277e624a --- /dev/null +++ b/.github/workflows/evals-nightly.yml @@ -0,0 +1,104 @@ +name: 'Nightly Evaluations' + +permissions: + contents: 'read' + +on: + schedule: + - cron: '0 1 * * *' # 1 AM UTC + workflow_dispatch: + inputs: + iterations: + description: 'Number of iterations per test case' + required: true + default: '1' + +jobs: + list-evals: + runs-on: 'ubuntu-22.04' + outputs: + matrix: '${{ steps.set-matrix.outputs.matrix }}' + steps: + - name: 'Checkout code' + uses: 'actions/checkout@v4' # ratchet:exclude + - id: 'set-matrix' + run: | + FILES=$(find evals -maxdepth 1 -name "*.eval.ts" | sort | jq -R -s -c 'split("\n")[:-1]') + echo "matrix=${FILES}" >> "$GITHUB_OUTPUT" + + evaluate: + needs: 'list-evals' + runs-on: 'ubuntu-22.04' + strategy: + fail-fast: false + matrix: + model: ['gemini-3-flash-preview'] + eval-file: '${{ fromJson(needs.list-evals.outputs.matrix) }}' + name: 'Evaluate ${{ matrix.eval-file }} (${{ matrix.model }})' + + steps: + - name: 'Checkout code' + uses: 'actions/checkout@v4' # ratchet:exclude + + - name: 'Set up Node.js' + uses: 'actions/setup-node@v4' # ratchet:exclude + with: + node-version: '20' + cache: 'npm' + + - name: 'Install dependencies' + # Retry logic for transient network or package retrieval failures + run: | + npm ci || (sleep 10 && npm ci) || (sleep 30 && npm ci) + + - name: 'Install Gemini CLI' + # Retry logic for transient network or package retrieval failures + run: | + npm install -g @google/gemini-cli@latest || (sleep 10 && npm install -g @google/gemini-cli@latest) || (sleep 30 && npm install -g @google/gemini-cli@latest) + + - name: 'Run Evaluations' + id: 'run_evals' + env: + GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' + GOOGLE_API_KEY: '${{ secrets.GOOGLE_API_KEY }}' + GEMINI_MODEL: '${{ matrix.model }}' + run: | + BASE_NAME=$(basename "${{ matrix.eval-file }}" .eval.ts) + npm run test:evals -- "${{ matrix.eval-file }}" --reporter=json --outputFile="eval-results-${{ matrix.model }}-${BASE_NAME}.json" + + - name: 'Upload Results' + if: 'always()' + uses: 'actions/upload-artifact@v4' # ratchet:exclude + with: + name: 'eval-results-${{ matrix.model }}-${{ strategy.job-index }}' + path: 'eval-results-${{ matrix.model }}-*.json' + + report: + needs: 'evaluate' + if: 'always()' + runs-on: 'ubuntu-22.04' + steps: + - name: 'Checkout code' + uses: 'actions/checkout@v4' # ratchet:exclude + + - name: 'Set up Node.js' + uses: 'actions/setup-node@v4' # ratchet:exclude + with: + node-version: '20' + cache: 'npm' + + - name: 'Install dependencies' + # Retry logic for transient network or package retrieval failures + run: | + npm ci || (sleep 10 && npm ci) || (sleep 30 && npm ci) + + - name: 'Download Results' + uses: 'actions/download-artifact@v4' # ratchet:exclude + with: + path: 'eval-results' + pattern: 'eval-results-*' + merge-multiple: true + + - name: 'Aggregate All Results' + run: | + npx tsx scripts/aggregate_evals.ts eval-results/*.json >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/gemini-dispatch.yml b/.github/workflows/gemini-dispatch.yml new file mode 100644 index 000000000..e085068bd --- /dev/null +++ b/.github/workflows/gemini-dispatch.yml @@ -0,0 +1,237 @@ +name: '🔀 Gemini Dispatch' + +on: + pull_request_review_comment: + types: + - 'created' + pull_request_review: + types: + - 'submitted' + pull_request: + types: + - 'opened' + issues: + types: + - 'opened' + - 'reopened' + issue_comment: + types: + - 'created' + +defaults: + run: + shell: 'bash' + +jobs: + debugger: + if: |- + ${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }} + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' + steps: + - name: 'Print context for debugging' + env: + DEBUG_event_name: '${{ github.event_name }}' + DEBUG_event__action: '${{ github.event.action }}' + DEBUG_event__comment__author_association: '${{ github.event.comment.author_association }}' + DEBUG_event__issue__author_association: '${{ github.event.issue.author_association }}' + DEBUG_event__pull_request__author_association: '${{ github.event.pull_request.author_association }}' + DEBUG_event__review__author_association: '${{ github.event.review.author_association }}' + DEBUG_event: '${{ toJSON(github.event) }}' + run: |- + env | grep '^DEBUG_' + + dispatch: + # For PRs: only if not from a fork + # For issues: only on open/reopen + # For comments: only if user types @gemini-cli and is OWNER/MEMBER/COLLABORATOR + if: |- + ( + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.fork == false + ) || ( + github.event_name == 'issues' && + contains(fromJSON('["opened", "reopened"]'), github.event.action) + ) || ( + github.event.sender.type == 'User' && + startsWith(github.event.comment.body || github.event.review.body || github.event.issue.body, '@gemini-cli') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association || github.event.review.author_association || github.event.issue.author_association) + ) + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' + issues: 'write' + pull-requests: 'write' + outputs: + command: '${{ steps.extract_command.outputs.command }}' + request: '${{ steps.extract_command.outputs.request }}' + additional_context: '${{ steps.extract_command.outputs.additional_context }}' + issue_number: '${{ github.event.pull_request.number || github.event.issue.number }}' + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'read' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Extract command' + id: 'extract_command' + uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8 + env: + EVENT_TYPE: '${{ github.event_name }}.${{ github.event.action }}' + REQUEST: '${{ github.event.comment.body || github.event.review.body || github.event.issue.body }}' + with: + script: | + const eventType = process.env.EVENT_TYPE; + const request = process.env.REQUEST; + core.setOutput('request', request); + + if (eventType === 'pull_request.opened') { + core.setOutput('command', 'review'); + } else if (['issues.opened', 'issues.reopened'].includes(eventType)) { + core.setOutput('command', 'triage'); + } else if (request.startsWith("@gemini-cli /review")) { + core.setOutput('command', 'review'); + const additionalContext = request.replace(/^@gemini-cli \/review/, '').trim(); + core.setOutput('additional_context', additionalContext); + } else if (request.startsWith("@gemini-cli /triage")) { + core.setOutput('command', 'triage'); + } else if (request.startsWith("@gemini-cli /approve")) { + core.setOutput('command', 'approve'); + const additionalContext = request.replace(/^@gemini-cli \/approve/, '').trim(); + core.setOutput('additional_context', additionalContext); + } else if (request.startsWith("@gemini-cli /fix")) { + core.setOutput('command', 'fix'); + } else if (request.startsWith("@gemini-cli")) { + const additionalContext = request.replace(/^@gemini-cli/, '').trim(); + core.setOutput('command', 'invoke'); + core.setOutput('additional_context', additionalContext); + } else { + core.setOutput('command', 'fallthrough'); + } + + - name: 'Acknowledge request' + env: + GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' + MESSAGE: |- + 🤖 Hi @${{ github.actor }}, I've received your request, and I'm working on it now! You can track my progress [in the logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details. + REPOSITORY: '${{ github.repository }}' + run: |- + gh issue comment "${ISSUE_NUMBER}" \ + --body "${MESSAGE}" \ + --repo "${REPOSITORY}" + + review: + needs: 'dispatch' + if: |- + ${{ needs.dispatch.outputs.command == 'review' }} + uses: './.github/workflows/gemini-review.yml' + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + with: + additional_context: '${{ needs.dispatch.outputs.additional_context }}' + secrets: 'inherit' + + triage: + needs: 'dispatch' + if: |- + ${{ needs.dispatch.outputs.command == 'triage' }} + uses: './.github/workflows/gemini-triage.yml' + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + with: + additional_context: '${{ needs.dispatch.outputs.additional_context }}' + secrets: 'inherit' + + fix: + needs: 'dispatch' + if: |- + ${{ needs.dispatch.outputs.command == 'fix' }} + uses: './.github/workflows/gemini-issue-fixer.yml' + permissions: + contents: 'write' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + secrets: 'inherit' + + invoke: + needs: 'dispatch' + if: |- + ${{ needs.dispatch.outputs.command == 'invoke' }} + uses: './.github/workflows/gemini-invoke.yml' + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + with: + additional_context: '${{ needs.dispatch.outputs.additional_context }}' + secrets: 'inherit' + + plan-execute: + needs: 'dispatch' + if: |- + ${{ needs.dispatch.outputs.command == 'approve' }} + uses: './.github/workflows/gemini-plan-execute.yml' + permissions: + contents: 'write' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + with: + additional_context: '${{ needs.dispatch.outputs.additional_context }}' + secrets: 'inherit' + + fallthrough: + needs: + - 'dispatch' + - 'review' + - 'triage' + - 'invoke' + - 'plan-execute' + if: |- + ${{ always() && !cancelled() && (failure() || needs.dispatch.outputs.command == 'fallthrough') }} + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' + issues: 'write' + pull-requests: 'write' + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'read' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Send failure comment' + env: + GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' + MESSAGE: |- + 🤖 I'm sorry @${{ github.actor }}, but I was unable to process your request. Please [see the logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details. + REPOSITORY: '${{ github.repository }}' + run: |- + gh issue comment "${ISSUE_NUMBER}" \ + --body "${MESSAGE}" \ + --repo "${REPOSITORY}" diff --git a/.github/workflows/gemini-invoke.yml b/.github/workflows/gemini-invoke.yml new file mode 100644 index 000000000..5bec70a52 --- /dev/null +++ b/.github/workflows/gemini-invoke.yml @@ -0,0 +1,118 @@ +name: 'â–ļī¸ Gemini Invoke' + +on: + workflow_call: + inputs: + additional_context: + type: 'string' + description: 'Any additional context from the request' + required: false + +concurrency: + group: '${{ github.workflow }}-invoke-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' + cancel-in-progress: false + +defaults: + run: + shell: 'bash' + +jobs: + invoke: + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'read' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Checkout Code' + uses: 'actions/checkout@v4' # ratchet:exclude + + - name: 'Run Gemini CLI' + id: 'run_gemini' + uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + env: + TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' + DESCRIPTION: '${{ github.event.pull_request.body || github.event.issue.body }}' + EVENT_NAME: '${{ github.event_name }}' + GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + IS_PULL_REQUEST: '${{ !!github.event.pull_request }}' + ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' + REPOSITORY: '${{ github.repository }}' + ADDITIONAL_CONTEXT: '${{ inputs.additional_context }}' + with: + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' + gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' + gemini_model: '${{ vars.GEMINI_MODEL }}' + google_api_key: '${{ secrets.GOOGLE_API_KEY }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' + workflow_name: 'gemini-invoke' + settings: |- + { + "model": { + "maxSessionTurns": 25 + }, + "telemetry": { + "enabled": true, + "target": "local", + "outfile": ".gemini/telemetry.log" + }, + "mcpServers": { + "github": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server:v0.27.0" + ], + "includeTools": [ + "add_issue_comment", + "issue_read", + "list_issues", + "search_issues", + "pull_request_read", + "list_pull_requests", + "search_pull_requests", + "get_commit", + "get_file_contents", + "list_commits", + "search_code" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" + } + } + }, + "tools": { + "core": [ + "run_shell_command(cat)", + "run_shell_command(echo)", + "run_shell_command(grep)", + "run_shell_command(head)", + "run_shell_command(tail)" + ] + } + } + prompt: '/gemini-invoke' diff --git a/.github/workflows/gemini-issue-fixer.yml b/.github/workflows/gemini-issue-fixer.yml new file mode 100644 index 000000000..a19804418 --- /dev/null +++ b/.github/workflows/gemini-issue-fixer.yml @@ -0,0 +1,94 @@ +name: '🧙 Gemini Issue Fixer' + +on: + workflow_call: + +concurrency: + group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}-${{ github.event.issue.number }}' + cancel-in-progress: true + +defaults: + run: + shell: 'bash' + +jobs: + create-pr: + timeout-minutes: 30 + runs-on: 'ubuntu-latest' + permissions: + contents: 'write' # Enable reading and modifying code + id-token: 'write' # Enable minting an identity token + issues: 'write' # Enable updating issues, such as posting a comment + pull-requests: 'write' # Enable creating pull requests + + steps: + # Mint a token so that the comments show up as gemini-cli instead of github-actions. + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'write' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Checkout repository' + uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 + + - name: 'Run Gemini PR Create' + uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + id: 'gemini_pr_create' + env: + GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN }}' + REPOSITORY: '${{ github.repository }}' + ISSUE_NUMBER: '${{ github.event.issue.number }}' + ISSUE_TITLE: '${{ github.event.issue.title }}' + ISSUE_BODY: '${{ github.event.issue.body }}' + BRANCH_NAME: 'gemini-fix-${{ github.event.issue.number }}' + EVENT_NAME: '${{ github.event_name }}' + TRIGGERING_ACTOR: '${{ github.triggering_actor }}' + with: + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' + gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' + gemini_model: '${{ vars.GEMINI_MODEL }}' + google_api_key: '${{ secrets.GOOGLE_API_KEY }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' + settings: |- + { + "debug": ${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}, + "model": { + "maxSessionTurns": 200 + }, + "mcpServers": { + "github": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server:v0.27.0" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" + } + } + }, + "telemetry": { + "enabled": true, + "target": "local", + "outfile": ".gemini/telemetry.log" + } + } + prompt: '/gemini-issue-fixer' diff --git a/.github/workflows/gemini-plan-execute.yml b/.github/workflows/gemini-plan-execute.yml new file mode 100644 index 000000000..9dfe61a21 --- /dev/null +++ b/.github/workflows/gemini-plan-execute.yml @@ -0,0 +1,126 @@ +name: '🧙 Gemini Plan Execution' + +on: + workflow_call: + inputs: + additional_context: + type: 'string' + description: 'Any additional context from the request' + required: false + +concurrency: + group: '${{ github.workflow }}-plan-execute-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' + cancel-in-progress: true + +defaults: + run: + shell: 'bash' + +jobs: + plan-execute: + timeout-minutes: 30 + runs-on: 'ubuntu-latest' + permissions: + contents: 'write' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'write' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Checkout Code' + uses: 'actions/checkout@v4' # ratchet:exclude + + - name: 'Run Gemini CLI' + id: 'run_gemini' + uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + env: + TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' + DESCRIPTION: '${{ github.event.pull_request.body || github.event.issue.body }}' + EVENT_NAME: '${{ github.event_name }}' + GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + IS_PULL_REQUEST: '${{ !!github.event.pull_request }}' + ISSUE_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' + REPOSITORY: '${{ github.repository }}' + ADDITIONAL_CONTEXT: '${{ inputs.additional_context }}' + with: + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' + gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' + gemini_model: '${{ vars.GEMINI_MODEL }}' + google_api_key: '${{ secrets.GOOGLE_API_KEY }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' + workflow_name: 'gemini-plan-execute' + settings: |- + { + "model": { + "maxSessionTurns": 25 + }, + "telemetry": { + "enabled": true, + "target": "local", + "outfile": ".gemini/telemetry.log" + }, + "mcpServers": { + "github": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server:v0.27.0" + ], + "includeTools": [ + "add_issue_comment", + "issue_read", + "list_issues", + "search_issues", + "create_pull_request", + "pull_request_read", + "list_pull_requests", + "search_pull_requests", + "create_branch", + "create_or_update_file", + "delete_file", + "fork_repository", + "get_commit", + "get_file_contents", + "list_commits", + "push_files", + "search_code" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" + } + } + }, + "tools": { + "core": [ + "run_shell_command(cat)", + "run_shell_command(echo)", + "run_shell_command(grep)", + "run_shell_command(head)", + "run_shell_command(tail)" + ] + } + } + prompt: '/gemini-plan-execute' diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml new file mode 100644 index 000000000..510bfb74b --- /dev/null +++ b/.github/workflows/gemini-review.yml @@ -0,0 +1,114 @@ +name: '🔎 Gemini Review' + +on: + workflow_call: + inputs: + additional_context: + type: 'string' + description: 'Any additional context from the request' + required: false + +concurrency: + group: '${{ github.workflow }}-review-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' + cancel-in-progress: true + +defaults: + run: + shell: 'bash' + +jobs: + review: + runs-on: 'ubuntu-latest' + timeout-minutes: 7 + permissions: + contents: 'read' + id-token: 'write' + issues: 'write' + pull-requests: 'write' + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'read' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Checkout repository' + uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 + + - name: 'Run Gemini pull request review' + uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + id: 'gemini_pr_review' + env: + GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + ISSUE_TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' + ISSUE_BODY: '${{ github.event.pull_request.body || github.event.issue.body }}' + PULL_REQUEST_NUMBER: '${{ github.event.pull_request.number || github.event.issue.number }}' + REPOSITORY: '${{ github.repository }}' + ADDITIONAL_CONTEXT: '${{ inputs.additional_context }}' + GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' + with: + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' + gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' + gemini_model: '${{ vars.GEMINI_MODEL }}' + google_api_key: '${{ secrets.GOOGLE_API_KEY }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' + workflow_name: 'gemini-review' + settings: |- + { + "model": { + "maxSessionTurns": 25 + }, + "telemetry": { + "enabled": true, + "target": "local", + "outfile": ".gemini/telemetry.log" + }, + "mcpServers": { + "github": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server:v0.27.0" + ], + "includeTools": [ + "add_comment_to_pending_review", + "pull_request_read", + "pull_request_review_write" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" + } + } + }, + "tools": { + "core": [ + "run_shell_command(cat)", + "run_shell_command(echo)", + "run_shell_command(grep)", + "run_shell_command(head)", + "run_shell_command(tail)" + ] + } + } + extensions: | + [ + "https://github.com/gemini-cli-extensions/code-review" + ] + prompt: '/pr-code-review' diff --git a/.github/workflows/gemini-scheduled-triage.yml b/.github/workflows/gemini-scheduled-triage.yml new file mode 100644 index 000000000..962164576 --- /dev/null +++ b/.github/workflows/gemini-scheduled-triage.yml @@ -0,0 +1,214 @@ +name: '📋 Gemini Scheduled Issue Triage' + +on: + schedule: + - cron: '0 * * * *' # Runs every hour + pull_request: + branches: + - 'main' + - 'release/**/*' + paths: + - '.github/workflows/gemini-scheduled-triage.yml' + push: + branches: + - 'main' + - 'release/**/*' + paths: + - '.github/workflows/gemini-scheduled-triage.yml' + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}' + cancel-in-progress: true + +defaults: + run: + shell: 'bash' + +jobs: + triage: + runs-on: 'ubuntu-latest' + timeout-minutes: 7 + permissions: + contents: 'read' + id-token: 'write' + issues: 'read' + pull-requests: 'read' + outputs: + available_labels: '${{ steps.get_labels.outputs.available_labels }}' + triaged_issues: '${{ env.TRIAGED_ISSUES }}' + steps: + - name: 'Get repository labels' + id: 'get_labels' + uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 + with: + # NOTE: we intentionally do not use the minted token. The default + # GITHUB_TOKEN provided by the action has enough permissions to read + # the labels. + script: |- + const labels = []; + for await (const response of github.paginate.iterator(github.rest.issues.listLabelsForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, // Maximum per page to reduce API calls + })) { + labels.push(...response.data); + } + + if (!labels || labels.length === 0) { + core.setFailed('There are no issue labels in this repository.') + } + + const labelNames = labels.map(label => label.name).sort(); + core.setOutput('available_labels', labelNames.join(',')); + core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); + return labelNames; + + - name: 'Find untriaged issues' + id: 'find_issues' + env: + GITHUB_REPOSITORY: '${{ github.repository }}' + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN || github.token }}' + run: |- + echo '🔍 Finding unlabeled issues and issues marked for triage...' + ISSUES="$(gh issue list \ + --state 'open' \ + --search 'no:label label:"status/needs-triage"' \ + --json number,title,body \ + --limit '100' \ + --repo "${GITHUB_REPOSITORY}" + )" + + echo '📝 Setting output for GitHub Actions...' + echo "issues_to_triage=${ISSUES}" >> "${GITHUB_OUTPUT}" + + ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')" + echo "✅ Found ${ISSUE_COUNT} issue(s) to triage! đŸŽ¯" + + - name: 'Run Gemini Issue Analysis' + id: 'gemini_issue_analysis' + if: |- + ${{ steps.find_issues.outputs.issues_to_triage != '[]' }} + uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + env: + GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs + ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}' + REPOSITORY: '${{ github.repository }}' + AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' + with: + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' + gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' + gemini_model: '${{ vars.GEMINI_MODEL }}' + google_api_key: '${{ secrets.GOOGLE_API_KEY }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' + workflow_name: 'gemini-scheduled-triage' + settings: |- + { + "model": { + "maxSessionTurns": 25 + }, + "telemetry": { + "enabled": true, + "target": "local", + "outfile": ".gemini/telemetry.log" + }, + "tools": { + "core": [ + "run_shell_command(echo)", + "run_shell_command(jq)", + "run_shell_command(printenv)" + ] + } + } + prompt: '/gemini-scheduled-triage' + + label: + runs-on: 'ubuntu-latest' + needs: + - 'triage' + if: |- + needs.triage.outputs.available_labels != '' && + needs.triage.outputs.available_labels != '[]' && + needs.triage.outputs.triaged_issues != '' && + needs.triage.outputs.triaged_issues != '[]' + permissions: + contents: 'read' + issues: 'write' + pull-requests: 'write' + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'read' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Apply labels' + env: + AVAILABLE_LABELS: '${{ needs.triage.outputs.available_labels }}' + TRIAGED_ISSUES: '${{ needs.triage.outputs.triaged_issues }}' + uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 + with: + # Use the provided token so that the "gemini-cli" is the actor in the + # log for what changed the labels. + github-token: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + script: |- + // Parse the available labels + const availableLabels = (process.env.AVAILABLE_LABELS || '').split(',') + .map((label) => label.trim()) + .sort() + + // Parse out the triaged issues + const triagedIssues = (JSON.parse(process.env.TRIAGED_ISSUES || '{}')) + .sort((a, b) => a.issue_number - b.issue_number) + + core.debug(`Triaged issues: ${JSON.stringify(triagedIssues)}`); + + // Iterate over each label + for (const issue of triagedIssues) { + if (!issue) { + core.debug(`Skipping empty issue: ${JSON.stringify(issue)}`); + continue; + } + + const issueNumber = issue.issue_number; + if (!issueNumber) { + core.debug(`Skipping issue with no data: ${JSON.stringify(issue)}`); + continue; + } + + // Extract and reject invalid labels - we do this just in case + // someone was able to prompt inject malicious labels. + let labelsToSet = (issue.labels_to_set || []) + .map((label) => label.trim()) + .filter((label) => availableLabels.includes(label)) + .sort() + + core.debug(`Identified labels to set: ${JSON.stringify(labelsToSet)}`); + + if (labelsToSet.length === 0) { + core.info(`Skipping issue #${issueNumber} - no labels to set.`) + continue; + } + + core.debug(`Setting labels on issue #${issueNumber} to ${labelsToSet.join(', ')} (${issue.explanation || 'no explanation'})`) + + await github.rest.issues.setLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: labelsToSet, + }); + } diff --git a/.github/workflows/gemini-triage.yml b/.github/workflows/gemini-triage.yml new file mode 100644 index 000000000..22b1413c2 --- /dev/null +++ b/.github/workflows/gemini-triage.yml @@ -0,0 +1,158 @@ +name: '🔀 Gemini Triage' + +on: + workflow_call: + inputs: + additional_context: + type: 'string' + description: 'Any additional context from the request' + required: false + +concurrency: + group: '${{ github.workflow }}-triage-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}' + cancel-in-progress: true + +defaults: + run: + shell: 'bash' + +jobs: + triage: + runs-on: 'ubuntu-latest' + timeout-minutes: 7 + outputs: + available_labels: '${{ steps.get_labels.outputs.available_labels }}' + selected_labels: '${{ env.SELECTED_LABELS }}' + permissions: + contents: 'read' + id-token: 'write' + issues: 'read' + pull-requests: 'read' + steps: + - name: 'Get repository labels' + id: 'get_labels' + uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 + with: + # NOTE: we intentionally do not use the given token. The default + # GITHUB_TOKEN provided by the action has enough permissions to read + # the labels. + script: |- + const labels = []; + for await (const response of github.paginate.iterator(github.rest.issues.listLabelsForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, // Maximum per page to reduce API calls + })) { + labels.push(...response.data); + } + + if (!labels || labels.length === 0) { + core.setFailed('There are no issue labels in this repository.') + } + + const labelNames = labels.map(label => label.name).sort(); + core.setOutput('available_labels', labelNames.join(',')); + core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); + return labelNames; + + - name: 'Run Gemini issue analysis' + id: 'gemini_analysis' + if: |- + ${{ steps.get_labels.outputs.available_labels != '' }} + uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + env: + GITHUB_TOKEN: '' # Do NOT pass any auth tokens here since this runs on untrusted inputs + ISSUE_TITLE: '${{ github.event.issue.title }}' + ISSUE_BODY: '${{ github.event.issue.body }}' + AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' + with: + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}' + gemini_debug: '${{ fromJSON(vars.GEMINI_DEBUG || vars.ACTIONS_STEP_DEBUG || false) }}' + gemini_model: '${{ vars.GEMINI_MODEL }}' + google_api_key: '${{ secrets.GOOGLE_API_KEY }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + upload_artifacts: '${{ vars.UPLOAD_ARTIFACTS }}' + workflow_name: 'gemini-triage' + settings: |- + { + "model": { + "maxSessionTurns": 25 + }, + "telemetry": { + "enabled": true, + "target": "local", + "outfile": ".gemini/telemetry.log" + }, + "tools": { + "core": [ + "run_shell_command(echo)" + ] + } + } + prompt: '/gemini-triage' + + label: + runs-on: 'ubuntu-latest' + needs: + - 'triage' + if: |- + ${{ needs.triage.outputs.selected_labels != '' }} + permissions: + contents: 'read' + issues: 'write' + pull-requests: 'write' + steps: + - name: 'Mint identity token' + id: 'mint_identity_token' + if: |- + ${{ vars.APP_ID }} + uses: 'actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf' # ratchet:actions/create-github-app-token@v2 + with: + app-id: '${{ vars.APP_ID }}' + private-key: '${{ secrets.APP_PRIVATE_KEY }}' + permission-contents: 'read' + permission-issues: 'write' + permission-pull-requests: 'write' + + - name: 'Apply labels' + env: + ISSUE_NUMBER: '${{ github.event.issue.number }}' + AVAILABLE_LABELS: '${{ needs.triage.outputs.available_labels }}' + SELECTED_LABELS: '${{ needs.triage.outputs.selected_labels }}' + uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # ratchet:actions/github-script@v8.0.0 + with: + # Use the provided token so that the "gemini-cli" is the actor in the + # log for what changed the labels. + github-token: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' + script: |- + // Parse the available labels + const availableLabels = (process.env.AVAILABLE_LABELS || '').split(',') + .map((label) => label.trim()) + .sort() + + // Parse the label as a CSV, reject invalid ones - we do this just + // in case someone was able to prompt inject malicious labels. + const selectedLabels = (process.env.SELECTED_LABELS || '').split(',') + .map((label) => label.trim()) + .filter((label) => availableLabels.includes(label)) + .sort() + + // Set the labels + const issueNumber = process.env.ISSUE_NUMBER; + if (selectedLabels && selectedLabels.length > 0) { + await github.rest.issues.setLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: selectedLabels, + }); + core.info(`Successfully set labels: ${selectedLabels.join(',')}`); + } else { + core.info(`Failed to determine labels to set. There may not be enough information in the issue or pull request.`) + } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..46db0a2ee --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,25 @@ +name: 'Publish immutable action version' + +on: + workflow_dispatch: + release: + types: + - 'published' + +jobs: + publish: + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 + + - name: 'Publish' + id: 'publish' + uses: 'actions/publish-immutable-action@4bc8754ffc40f27910afb20287dbbbb675a4e978' # ratchet:actions/publish-immutable-action@v0.0.4 + with: + github-token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..934de9909 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: 'Release' + +on: + push: + branches: + - 'main' + - 'release/**/*' + +jobs: + release: + uses: 'google-github-actions/.github/.github/workflows/release.yml@v3' # ratchet:exclude + permissions: + attestations: 'write' + contents: 'write' + packages: 'write' + secrets: + ACTIONS_BOT_TOKEN: '${{ secrets.ACTIONS_BOT_TOKEN }}'