SK: 2832 Added Claude Code setup for Java #14
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: Claude PR Review | |
| on: | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'src/**/*.java' | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to re-run review on' | |
| required: false | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| jobs: | |
| sdk-review: | |
| name: SDK PR Review (changed lines only) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Claude CLI | |
| run: npm install -g @anthropic-ai/claude-code | |
| - name: Check for non-generated Java changes | |
| id: check | |
| run: | | |
| CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \ | |
| | grep '\.java$' | grep -v 'generated') | |
| if [ -z "$CHANGED" ]; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| LINES=$(git diff origin/${{ github.base_ref }}...HEAD -- '*.java' \ | |
| | grep -c '^[+-][^+-]' 2>/dev/null || echo 1) | |
| echo "changed_lines=$LINES" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Verify Claude CLI | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| echo "Claude version: $(claude --version)" | |
| set +e | |
| TEST=$(claude --dangerously-skip-permissions --output-format json -p "Reply with only the word: WORKING" 2>&1) | |
| EXIT=$? | |
| set -e | |
| echo "Exit code: $EXIT" | |
| echo "Output: $TEST" | |
| - name: Run code review | |
| if: steps.check.outputs.has_changes == 'true' | |
| id: review | |
| continue-on-error: true | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" | |
| GITHUB_BASE_REF: ${{ github.base_ref }} | |
| GITHUB_ACTIONS: "true" | |
| run: | | |
| RAW=$(claude --dangerously-skip-permissions --output-format json --model claude-sonnet-4-6 -p "/code-review" 2>&1) | |
| RESULT=$(echo "$RAW" | jq -r '.result // "_Review failed to produce output._"') | |
| INPUT_TOK=$(echo "$RAW" | jq '[.modelUsage[].inputTokens // 0] | add // 0') | |
| OUTPUT_TOK=$(echo "$RAW" | jq '[.modelUsage[].outputTokens // 0] | add // 0') | |
| CACHED_TOK=$(echo "$RAW" | jq '[.modelUsage[].cacheReadInputTokens // 0] | add // 0') | |
| COST=$(echo "$RAW" | jq -r '.total_cost_usd // 0') | |
| LINES="${{ steps.check.outputs.changed_lines }}" | |
| echo "result<<EOF" >> $GITHUB_OUTPUT | |
| echo "$RESULT" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| { | |
| echo "## Token Usage — SDK Review" | |
| echo "| Metric | Value |" | |
| echo "|--------|-------|" | |
| echo "| Input tokens | $INPUT_TOK |" | |
| echo "| Output tokens | $OUTPUT_TOK |" | |
| echo "| Cache hits | $CACHED_TOK |" | |
| echo "| Total cost | \$$COST |" | |
| echo "| Changed lines | $LINES |" | |
| } >> $GITHUB_STEP_SUMMARY | |
| - name: Post review comment | |
| if: steps.check.outputs.has_changes == 'true' | |
| uses: actions/github-script@v7 | |
| env: | |
| REVIEW_BODY: ${{ steps.review.outputs.result }} | |
| with: | |
| script: | | |
| const raw = process.env.REVIEW_BODY || '_Review output unavailable._'; | |
| const pull_number = context.payload.pull_request.number; | |
| // Split the inline-findings JSON block (```json:inline ... ```) from the summary. | |
| let summary = raw; | |
| let inline = []; | |
| const m = raw.match(/```json:inline\s*([\s\S]*?)```/); | |
| if (m) { | |
| summary = raw.replace(m[0], '').trim(); | |
| try { inline = JSON.parse(m[1].trim()); } catch (e) { inline = []; } | |
| } | |
| const comments = (Array.isArray(inline) ? inline : []) | |
| .filter(f => f && f.path && Number.isInteger(f.line)) | |
| .map(f => ({ | |
| path: f.path, | |
| line: f.line, | |
| side: 'RIGHT', | |
| body: `**${f.severity || 'Finding'}**: ${f.comment || ''}`.trim() | |
| })); | |
| const body = `## Claude SDK Review\n\n${summary}`; | |
| // One PR review: summary as the body + inline comments anchored to changed lines. | |
| try { | |
| await github.rest.pulls.createReview({ | |
| ...context.repo, | |
| pull_number, | |
| event: 'COMMENT', | |
| body, | |
| comments | |
| }); | |
| } catch (e) { | |
| // Inline anchoring fails if a line is not in the diff — fall back to a single | |
| // summary comment and list the would-be inline findings so nothing is lost. | |
| const list = comments.length | |
| ? '\n\n<details><summary>Inline findings (could not attach to lines)</summary>\n\n' | |
| + comments.map(c => `- \`${c.path}:${c.line}\` — ${c.body}`).join('\n') | |
| + '\n</details>' | |
| : ''; | |
| await github.rest.issues.createComment({ | |
| ...context.repo, | |
| issue_number: pull_number, | |
| body: body + list | |
| }); | |
| } | |
| security-review: | |
| name: Security Review (serviceaccount changes) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for serviceaccount changes | |
| id: filter | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| serviceaccount: | |
| - 'src/**/serviceaccount/**/*.java' | |
| - name: Install Claude CLI | |
| if: steps.filter.outputs.serviceaccount == 'true' | |
| run: npm install -g @anthropic-ai/claude-code | |
| - name: Get changed serviceaccount files | |
| if: steps.filter.outputs.serviceaccount == 'true' | |
| id: sa-files | |
| run: | | |
| FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \ | |
| | grep 'serviceaccount' | grep '\.java$' | tr '\n' ' ') | |
| echo "files=$FILES" >> $GITHUB_OUTPUT | |
| - name: Run security audit | |
| if: steps.filter.outputs.serviceaccount == 'true' && steps.sa-files.outputs.files != '' | |
| id: security | |
| continue-on-error: true | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" | |
| GITHUB_BASE_REF: ${{ github.base_ref }} | |
| GITHUB_ACTIONS: "true" | |
| SA_FILES: ${{ steps.sa-files.outputs.files }} | |
| run: | | |
| RAW=$(claude --dangerously-skip-permissions --output-format json --model claude-sonnet-4-6 \ | |
| -p "/code-security $SA_FILES") | |
| RESULT=$(echo "$RAW" | jq -r '.result // "_Audit failed to produce output._"') | |
| INPUT_TOK=$(echo "$RAW" | jq '[.modelUsage[].inputTokens // 0] | add // 0') | |
| OUTPUT_TOK=$(echo "$RAW" | jq '[.modelUsage[].outputTokens // 0] | add // 0') | |
| CACHED_TOK=$(echo "$RAW" | jq '[.modelUsage[].cacheReadInputTokens // 0] | add // 0') | |
| COST=$(echo "$RAW" | jq -r '.total_cost_usd // 0') | |
| echo "result<<EOF" >> $GITHUB_OUTPUT | |
| echo "$RESULT" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| { | |
| echo "## Token Usage — Security Audit" | |
| echo "| Metric | Value |" | |
| echo "|--------|-------|" | |
| echo "| Input tokens | $INPUT_TOK |" | |
| echo "| Output tokens | $OUTPUT_TOK |" | |
| echo "| Cache hits | $CACHED_TOK |" | |
| echo "| Total cost | \$$COST |" | |
| } >> $GITHUB_STEP_SUMMARY | |
| - name: Post security comment | |
| if: steps.filter.outputs.serviceaccount == 'true' && steps.sa-files.outputs.files != '' | |
| uses: actions/github-script@v7 | |
| env: | |
| AUDIT_BODY: ${{ steps.security.outputs.result }} | |
| SA_FILES: ${{ steps.sa-files.outputs.files }} | |
| with: | |
| script: | | |
| const audit = process.env.AUDIT_BODY || '_Audit output unavailable._'; | |
| const files = process.env.SA_FILES; | |
| await github.rest.issues.createComment({ | |
| ...context.repo, | |
| issue_number: context.payload.pull_request.number, | |
| body: `## Claude Security Audit (serviceaccount)\n\n${audit}\n\n---\n_Files audited: \`${files}\`_` | |
| }); |