Yurii bart feat add retryable policy 01 #67
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: AI Code Risk & Compatibility Analysis (Gemini) | |
| on: | |
| pull_request: | |
| branches: | |
| - master | |
| - main | |
| jobs: | |
| gemini-analysis: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # 1. Checkout PR code | |
| - name: Checkout PR branch | |
| uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 | |
| # 2. Get changed C# files and generate diff | |
| - name: Get changed files and diff | |
| id: changed | |
| run: | | |
| BASE_BRANCH="${{ github.event.pull_request.base.ref }}" | |
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | |
| HEAD_SHA="${{ github.event.pull_request.head.sha }}" | |
| echo "Base branch: $BASE_BRANCH" | |
| echo "Base SHA: $BASE_SHA" | |
| echo "Head SHA: $HEAD_SHA" | |
| # Fetch the base branch explicitly to ensure it's available | |
| git fetch origin "$BASE_BRANCH" || true | |
| # Get changed C# files comparing base SHA to head SHA | |
| # Fallback to branch comparison if SHA comparison fails | |
| CHANGED_FILES=$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA" 2>/dev/null | grep '\.cs$' || \ | |
| git diff --name-only "origin/$BASE_BRANCH"...HEAD 2>/dev/null | grep '\.cs$' || \ | |
| git diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null | grep '\.cs$' || true) | |
| if [ -z "$CHANGED_FILES" ]; then | |
| echo "No C# files changed in this PR" | |
| echo "changed_files=" >> $GITHUB_OUTPUT | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Changed C# files:" | |
| echo "$CHANGED_FILES" | |
| # Convert newlines to spaces for GitHub Actions output (multiline values cause issues) | |
| CHANGED_FILES_SPACE=$(echo "$CHANGED_FILES" | tr '\n' ' ' | xargs) | |
| echo "changed_files=$CHANGED_FILES_SPACE" >> $GITHUB_OUTPUT | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| # Generate unified diff for all changed C# files | |
| # Use the same fallback logic for diff generation | |
| # Try three-dot syntax first, then two-dot syntax | |
| git diff "$BASE_SHA"..."$HEAD_SHA" -- $CHANGED_FILES_SPACE > code_diff.txt 2>/dev/null || \ | |
| git diff "$BASE_SHA..$HEAD_SHA" -- $CHANGED_FILES_SPACE > code_diff.txt 2>/dev/null || \ | |
| git diff "origin/$BASE_BRANCH"...HEAD -- $CHANGED_FILES_SPACE > code_diff.txt 2>/dev/null || \ | |
| git diff "origin/$BASE_BRANCH..HEAD" -- $CHANGED_FILES_SPACE > code_diff.txt 2>/dev/null || \ | |
| git diff "$BASE_BRANCH"...HEAD -- $CHANGED_FILES_SPACE > code_diff.txt 2>/dev/null || \ | |
| git diff "$BASE_BRANCH..HEAD" -- $CHANGED_FILES_SPACE > code_diff.txt 2>/dev/null || true | |
| if [ -s code_diff.txt ]; then | |
| echo "Generated diff file with $(wc -l < code_diff.txt) lines" | |
| echo "diff_file=code_diff.txt" >> $GITHUB_OUTPUT | |
| else | |
| echo "WARNING: Could not generate diff file" | |
| echo "Attempting alternative diff method..." | |
| # Try diffing each file individually and concatenating | |
| > code_diff.txt | |
| for file in $CHANGED_FILES_SPACE; do | |
| if [ -f "$file" ]; then | |
| echo "=== $file ===" >> code_diff.txt | |
| git diff "$BASE_SHA" "$HEAD_SHA" -- "$file" >> code_diff.txt 2>/dev/null || \ | |
| git diff "origin/$BASE_BRANCH" HEAD -- "$file" >> code_diff.txt 2>/dev/null || \ | |
| git diff "$BASE_BRANCH" HEAD -- "$file" >> code_diff.txt 2>/dev/null || true | |
| echo "" >> code_diff.txt | |
| fi | |
| done | |
| if [ -s code_diff.txt ]; then | |
| echo "Generated diff file with $(wc -l < code_diff.txt) lines (alternative method)" | |
| echo "diff_file=code_diff.txt" >> $GITHUB_OUTPUT | |
| else | |
| echo "ERROR: Still could not generate diff file" | |
| echo "diff_file=" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| fi | |
| # 3. Install Gemini CLI | |
| - name: Install Gemini CLI | |
| run: npm install -g @google/gemini-cli | |
| # 4. Run Gemini AI | |
| - name: Run Gemini Analysis | |
| id: gemini | |
| if: steps.changed.outputs.has_changes == 'true' && steps.changed.outputs.diff_file != '' | |
| env: | |
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} | |
| run: | | |
| DIFF_FILE="${{ steps.changed.outputs.diff_file }}" | |
| CHANGED_FILES="${{ steps.changed.outputs.changed_files }}" | |
| if [ ! -f "$DIFF_FILE" ] || [ ! -s "$DIFF_FILE" ]; then | |
| echo "ERROR: Diff file not found or empty" | |
| echo "output_file=" >> $GITHUB_OUTPUT | |
| echo "exit_code=1" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Validate API key is set | |
| if [ -z "$GEMINI_API_KEY" ]; then | |
| echo "ERROR: GEMINI_API_KEY is not set" | |
| echo "output_file=" >> $GITHUB_OUTPUT | |
| echo "exit_code=1" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| echo "Analyzing diff for files: $CHANGED_FILES" | |
| echo "Diff file size: $(wc -l < "$DIFF_FILE") lines" | |
| # Create prompt file with diff content to avoid command line length issues | |
| { | |
| echo "Analyze the following code changes (unified diff) in C# files for: Security and performance risks, Deprecated API usage, Coding standard violations, Potential backward compatibility risks (API signature changes, Data contract changes, Removal/renaming of public members, Behavior changes affecting clients). Focus only on the changed lines (lines starting with + or -), not the entire files. Always provide severity (Low/Medium/High) and improvement suggestions as summary. Do not use tables for rendering - use plain text, bullet points, or code blocks instead." | |
| echo "" | |
| echo "Code changes (diff):" | |
| echo "\`\`\`diff" | |
| cat "$DIFF_FILE" | |
| echo "\`\`\`" | |
| } > gemini_prompt.txt | |
| # Pass prompt file to Gemini (read from stdin or file if supported) | |
| # If Gemini CLI doesn't support file input, we'll use the file content | |
| gemini "$(cat gemini_prompt.txt)" > gemini_output.txt 2>&1 | |
| GEMINI_EXIT_CODE=$? | |
| echo "Gemini CLI exit code: $GEMINI_EXIT_CODE" | |
| # Filter out potential API key exposure from output (security measure) | |
| if [ -f gemini_output.txt ]; then | |
| # Remove lines that might contain exposed API keys in error messages | |
| # Match patterns like: "api-key: xxx", "GEMINI_API_KEY=xxx", or long alphanumeric strings that look like keys | |
| sed -i.bak -E '/api[_-]?key[=:]\s*[a-zA-Z0-9_-]{20,}|GEMINI_API_KEY[=:]\s*[a-zA-Z0-9_-]{20,}/Id' gemini_output.txt 2>/dev/null || true | |
| rm -f gemini_output.txt.bak 2>/dev/null || true | |
| fi | |
| if [ $GEMINI_EXIT_CODE -ne 0 ]; then | |
| echo "WARNING: Gemini CLI failed" | |
| echo "" >> gemini_output.txt | |
| echo "--- Error Details ---" >> gemini_output.txt | |
| echo "Exit code: $GEMINI_EXIT_CODE" >> gemini_output.txt | |
| echo "Diff file: $DIFF_FILE ($(wc -l < "$DIFF_FILE") lines)" >> gemini_output.txt | |
| fi | |
| echo "output_file=gemini_output.txt" >> $GITHUB_OUTPUT | |
| echo "exit_code=$GEMINI_EXIT_CODE" >> $GITHUB_OUTPUT | |
| # 5. Post results to PR comment | |
| - name: Comment on PR with Gemini Findings | |
| if: steps.changed.outputs.changed_files != '' && steps.gemini.outputs.output_file != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const outputFile = '${{ steps.gemini.outputs.output_file }}'; | |
| const exitCode = '${{ steps.gemini.outputs.exit_code }}'; | |
| let analysisOutput = ''; | |
| let statusIcon = '[OK]'; | |
| let statusText = 'Analysis completed successfully'; | |
| if (fs.existsSync(outputFile)) { | |
| analysisOutput = fs.readFileSync(outputFile, 'utf8'); | |
| if (!analysisOutput.trim()) { | |
| analysisOutput = 'No analysis output was generated.'; | |
| statusIcon = '[WARNING]'; | |
| statusText = 'No output generated'; | |
| } else if (exitCode && exitCode !== '0') { | |
| statusIcon = '[ERROR]'; | |
| statusText = `Analysis failed with exit code ${exitCode}`; | |
| } | |
| } else { | |
| analysisOutput = 'Analysis output file was not found.'; | |
| statusIcon = '[ERROR]'; | |
| statusText = 'Output file not found'; | |
| } | |
| // Format files as bullet point list | |
| const changedFiles = '${{ steps.changed.outputs.changed_files }}'; | |
| const filesList = changedFiles | |
| .split(' ') | |
| .filter(file => file.trim()) | |
| .map(file => `- \`${file.trim()}\``) | |
| .join('\n'); | |
| const body = `## Gemini AI Code Risk & Compatibility Analysis | |
| **Status:** ${statusIcon} ${statusText} | |
| **Files analyzed:** | |
| ${filesList} | |
| ### Analysis Results | |
| ${analysisOutput} | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.payload.pull_request.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); |