Skip to content

SK: 2832 Added Claude Code setup for Java #14

SK: 2832 Added Claude Code setup for Java

SK: 2832 Added Claude Code setup for Java #14

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}\`_`
});