This is an automated email from the ASF dual-hosted git repository.

damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new a7847a7bba3 Add Gemini Enterprise Agent Platform code review workflow 
(#40561)
a7847a7bba3 is described below

commit a7847a7bba32bcba8757f5158d47a0023a0f4a07
Author: Danny McCormick <[email protected]>
AuthorDate: Wed Oct 14 14:31:55 2026 +0000

    Add Gemini Enterprise Agent Platform code review workflow (#40561)
    
    * Add Gemini Enterprise Agent Platform code review workflow
    
    * Temporarily trigger on pull_request for testing
    
    * Set GEMINI_CLI_TRUST_WORKSPACE=true for headless run
    
    * Set GEMINI_FORCE_FILE_STORAGE=true and close stdin on headless Linux 
runner
    
    * Skip checkout, enable directWebFetch and gh pr view/diff tools
    
    * Use gemini-3.1-pro-preview with global location
    
    * Switch Gemini code review model to gemini-4-argon
    
    * Address Gemini 4 code review feedback
    
    * Apply remaining Gemini 4 review hardening suggestions
    
    * Re-enable pull_request trigger to test updated Gemini 4 review
    
    * Add /gemini review comment trigger and address final review feedback
---
 .github/workflows/gemini_code_review.yml | 218 +++++++++++++++++++++++++++++++
 1 file changed, 218 insertions(+)

diff --git a/.github/workflows/gemini_code_review.yml 
b/.github/workflows/gemini_code_review.yml
new file mode 100644
index 00000000000..397ebe01644
--- /dev/null
+++ b/.github/workflows/gemini_code_review.yml
@@ -0,0 +1,218 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Gemini Enterprise Code Review
+
+on:
+  pull_request_target:
+    types: [opened, reopened, ready_for_review]
+  issue_comment:
+    types: [created]
+  workflow_dispatch:
+    inputs:
+      pr_number:
+        description: 'Pull Request number to review'
+        required: true
+        type: number
+
+permissions: read-all
+
+concurrency:
+  group: gemini-code-review-${{ github.event.pull_request.number || 
github.event.issue.number || inputs.pr_number }}
+  cancel-in-progress: true
+
+jobs:
+  gemini-review:
+    name: Gemini Enterprise PR Review
+    if: >-
+      github.repository == 'apache/beam' &&
+      (
+        (github.event_name == 'pull_request_target' && 
!github.event.pull_request.draft) ||
+        (github.event_name == 'issue_comment' && 
github.event.issue.pull_request && contains(github.event.comment.body, '/gemini 
review')) ||
+        github.event_name == 'workflow_dispatch'
+      )
+    runs-on: [self-hosted, ubuntu-24.04, main]
+    timeout-minutes: 15
+    permissions:
+      contents: read
+      pull-requests: write
+
+    steps:
+      - name: Setup Node.js
+        uses: actions/setup-node@v7
+        with:
+          node-version: '20'
+
+      - name: Run Gemini Enterprise Code Review Agent
+        id: gemini_review
+        shell: bash
+        env:
+          GEMINI_CLI_TRUST_WORKSPACE: 'true'
+          GEMINI_FORCE_FILE_STORAGE: 'true'
+          GOOGLE_GENAI_USE_VERTEXAI: 'true'
+          GOOGLE_CLOUD_PROJECT: ${{ secrets.GCP_PROJECT_ID || 
'apache-beam-testing' }}
+          GOOGLE_CLOUD_LOCATION: 'global'
+          GEMINI_MODEL: ${{ vars.GEMINI_MODEL || 'gemini-4-argon' }}
+          PR_NUMBER: ${{ github.event.pull_request.number || 
github.event.issue.number || inputs.pr_number }}
+          REPOSITORY: ${{ github.repository }}
+        run: |
+          set -euo pipefail
+
+          PR_URL="https://github.com/${REPOSITORY}/pull/${PR_NUMBER}";
+
+          mkdir -p .gemini
+          cat << 'EOF' > .gemini/settings.json
+          {
+            "model": {
+              "maxSessionTurns": 25
+            },
+            "experimental": {
+              "directWebFetch": true
+            },
+            "tools": {
+              "core": [
+                "web_fetch"
+              ]
+            }
+          }
+          EOF
+
+          cat << 'EOF' > prompt_template.txt
+          You are an autonomous Apache Beam code review agent running on the 
Gemini Enterprise Agent Platform.
+          Review Pull Request: __PR_URL__
+
+          Respond with ONLY a single valid JSON object matching this exact 
schema (no markdown prose outside the JSON):
+          {
+            "summary": "## 📋 Review Summary\n\n2-3 sentence high-level 
assessment.\n\n## 🔍 General Feedback\n- Key architectural or cross-file 
observations.",
+            "comments": [
+              {
+                "path": "relative/path/to/file.ext",
+                "line": 123,
+                "side": "RIGHT",
+                "severity": "🟠 High",
+                "body": "Clear explanation of why this is an issue and how to 
fix it.",
+                "suggestion": "optional exact replacement for the single 
target line without backticks, or null"
+              }
+            ]
+          }
+          EOF
+          sed "s|__PR_URL__|${PR_URL}|g" prompt_template.txt > prompt.txt
+
+          npx --yes @google/[email protected] \
+            --yolo \
+            --model "${GEMINI_MODEL}" \
+            --prompt "$(cat prompt.txt)" \
+            --output-format json \
+            < /dev/null \
+            > gemini_raw_output.json
+
+          if jq -e . gemini_raw_output.json >/dev/null 2>&1; then
+            jq -r '.response // ""' gemini_raw_output.json > 
gemini_response.txt
+          else
+            cp gemini_raw_output.json gemini_response.txt
+          fi
+
+      - name: Post PR Review to GitHub
+        uses: actions/github-script@v9
+        env:
+          PR_NUMBER: ${{ github.event.pull_request.number || 
github.event.issue.number || inputs.pr_number }}
+        with:
+          script: |
+            const fs = require('fs');
+            const prNumber = Number(process.env.PR_NUMBER);
+            const rawResponse = fs.existsSync('gemini_response.txt')
+              ? fs.readFileSync('gemini_response.txt', 'utf8').trim()
+              : '';
+            if (!rawResponse) {
+              core.warning('Gemini agent returned an empty response; skipping 
PR review submission.');
+              return;
+            }
+
+            let parsed;
+            const fencedMatch = 
rawResponse.match(/^```(?:json)?\s*([\s\S]*?)```$/i);
+            const candidateJson = fencedMatch ? fencedMatch[1].trim() : 
rawResponse;
+            try {
+              parsed = JSON.parse(candidateJson);
+            } catch (firstErr) {
+              const firstBrace = rawResponse.indexOf('{');
+              const lastBrace = rawResponse.lastIndexOf('}');
+              if (firstBrace !== -1 && lastBrace > firstBrace) {
+                try {
+                  parsed = JSON.parse(rawResponse.slice(firstBrace, lastBrace 
+ 1));
+                } catch {
+                  parsed = { summary: rawResponse, comments: [] };
+                }
+              } else {
+                parsed = { summary: rawResponse, comments: [] };
+              }
+            }
+
+            const inlineComments = [];
+            if (Array.isArray(parsed.comments)) {
+              for (const item of parsed.comments) {
+                if (!item || !item.path || !item.body || 
!Number.isInteger(Number(item.line)) || Number(item.line) <= 0) {
+                  continue;
+                }
+                const side = item.side === 'LEFT' ? 'LEFT' : 'RIGHT';
+                const line = Number(item.line);
+                const severityPrefix = item.severity ? `**${item.severity}**: 
` : '';
+                let commentBody = `${severityPrefix}${item.body.trim()}`;
+
+                if (item.suggestion && String(item.suggestion).trim() !== '' 
&& item.suggestion !== 'null') {
+                  commentBody += 
`\n\n\`\`\`suggestion\n${String(item.suggestion).replace(/\r\n/g, 
'\n')}\n\`\`\``;
+                }
+
+                inlineComments.push({
+                  path: item.path,
+                  line,
+                  side,
+                  body: commentBody,
+                });
+              }
+            }
+
+            const baseSummary = parsed.summary
+              ? String(parsed.summary).trim()
+              : '## 📋 Review Summary\n\nAutomated code review completed.';
+            const reviewBody = `${baseSummary}\n\n---\n*💡 Tip: Comment 
\`/gemini review\` on this pull request to trigger a new Gemini code review.*`;
+
+            try {
+              await github.rest.pulls.createReview({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                pull_number: prNumber,
+                event: 'COMMENT',
+                body: reviewBody,
+                comments: inlineComments,
+              });
+              core.info(`Posted Gemini review on PR #${prNumber} with 
${inlineComments.length} inline comment(s).`);
+            } catch (reviewErr) {
+              core.warning(`Failed to post inline review comments 
(${reviewErr.message}); falling back to summary-only review.`);
+              const allNotes = inlineComments.map(
+                c => `- **\`${c.path}\` (line ${c.line})**: ${c.body}`
+              );
+              const fallbackBody = [
+                reviewBody,
+                allNotes.length > 0 ? '## 💬 Inline Findings\n\n' + 
allNotes.join('\n\n') : '',
+              ].filter(Boolean).join('\n\n');
+
+              await github.rest.pulls.createReview({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                pull_number: prNumber,
+                event: 'COMMENT',
+                body: fallbackBody,
+              });
+            }

Reply via email to