Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/actions/fetch-team-context/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Fetch review context
description: Fetch read-only files from configured source repositories with a short-lived app token, for use as review context.
inputs:
app-id:
description: GitHub App ID.
required: true
private-key:
description: GitHub App private key.
required: true
sources:
description: >
Newline list of `repo:path[,path...]` to fetch from the 0xPolygon org
(paths ending in `/` are directories).
required: true
output-dir:
description: Directory to write the fetched files into.
required: false
default: review-context
runs:
using: composite
steps:
# The dedicated app is installed only on the source repos with contents:read,
# so the minted token is already least-privilege — no extra scoping needed.
- id: app-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
owner: 0xPolygon
permission-contents: read
- name: Fetch
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SOURCES: ${{ inputs.sources }}
OUTPUT_DIR: ${{ inputs.output-dir }}
ACTION_PATH: ${{ github.action_path }}
run: bash "$ACTION_PATH/fetch.sh"
- name: Revoke token
if: always()
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api --method DELETE /installation/token || true
59 changes: 59 additions & 0 deletions .github/actions/fetch-team-context/fetch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
#
# Fetch the files listed in $SOURCES into $OUTPUT_DIR. Each source line is
# `repo:path[,path...]`; a path ending in `/` is a directory (cone
# sparse-checkout, which excludes sibling subdirectories), otherwise a single
# file. Only file contents are copied out — never a clone's .git dir, so the
# token in the clone URL is not carried into the output. Logs counts, not names.
set -euo pipefail

: "${GH_TOKEN:?GH_TOKEN required}"
: "${OUTPUT_DIR:?OUTPUT_DIR required}"
: "${SOURCES:?SOURCES required}"

owner=0xPolygon
out="$(mkdir -p "$OUTPUT_DIR" && cd "$OUTPUT_DIR" && pwd)"
count=0

fetch_file() { # repo path
local repo="$1" path="$2"
mkdir -p "$out/$repo/$(dirname "$path")"
if gh api "repos/$owner/$repo/contents/$path" \
-H "Accept: application/vnd.github.raw" > "$out/$repo/$path" 2>/dev/null; then
count=$((count + 1))
else
echo "::warning::could not fetch a configured file"
rm -f "$out/$repo/$path"
fi
}

fetch_dir() { # repo dir (no trailing slash)
local repo="$1" dir="$2" tmp
tmp="$(mktemp -d)"
git clone --no-checkout --depth 1 --filter=blob:none \
"https://x-access-token:${GH_TOKEN}@github.com/${owner}/${repo}.git" "$tmp" >/dev/null 2>&1
git -C "$tmp" sparse-checkout set "$dir" >/dev/null
git -C "$tmp" checkout >/dev/null 2>&1
if [[ -d "$tmp/$dir" ]]; then
mkdir -p "$out/$repo/$(dirname "$dir")"
cp -R "$tmp/$dir" "$out/$repo/$(dirname "$dir")/" # content only — never $tmp/.git
count=$((count + $(find "$tmp/$dir" -type f | wc -l)))
else
echo "::warning::could not fetch a configured directory"
fi
rm -rf "$tmp"
}

while IFS= read -r line; do
[[ -z "${line// /}" ]] && continue
repo="${line%%:*}"
paths="${line#*:}"
IFS=',' read -ra parts <<< "$paths"
for p in "${parts[@]}"; do
p="$(echo "$p" | xargs)" # trim whitespace
[[ -z "$p" ]] && continue
if [[ "$p" == */ ]]; then fetch_dir "$repo" "${p%/}"; else fetch_file "$repo" "$p"; fi
done
done <<< "$SOURCES"

echo "Fetched $count file(s) into $OUTPUT_DIR"
5 changes: 5 additions & 0 deletions .github/workflows/apps-claude-code-review-trigger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ jobs:
uses: ./.github/workflows/apps-claude-code-review.yml
secrets:
CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
# Optional: enables team-standards context in the review. Provide a
# read-only app's credentials and an APPS_TEAM_CONTEXT_SOURCES secret.
TEAM_CONTEXT_APP_ID: ${{ secrets.APPS_TEAM_CONTEXT_APP_ID }}
TEAM_CONTEXT_APP_PRIVATE_KEY: ${{ secrets.APPS_TEAM_CONTEXT_APP_PRIVATE_KEY }}
TEAM_CONTEXT_SOURCES: ${{ secrets.APPS_TEAM_CONTEXT_SOURCES }}
93 changes: 89 additions & 4 deletions .github/workflows/apps-claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,75 @@
secrets:
CLAUDE_API_KEY:
required: true
# Optional team-context credentials. If absent, the review still runs,
# just without injected standards.
TEAM_CONTEXT_APP_ID:
required: false
TEAM_CONTEXT_APP_PRIVATE_KEY:
required: false
# Newline list of `repo:path[,path...]` to fetch.
TEAM_CONTEXT_SOURCES:
required: false

permissions: {}

# Run conditions (claude-review inherits via `needs`): same-repo PRs only, skip
# changeset-release branches, skip the disable-claude-code-review label.

jobs:
claude-review:
# Only run on PRs from the same repository (not forks) to avoid API costs on external contributions.
# Skip changeset-release branches — those are automated version-bump PRs, not code to review.
# Skip PRs labelled disable-claude-code-review.
# Job 1 — fetches review context with a short-lived, read-only token and hands
# it to job 2 as an artifact. No PR content is checked out here, and the token
# is never passed to the review job.
fetch-context:
if: >-
github.event.pull_request.head.repo.full_name == github.repository &&
!startsWith(github.head_ref, 'changeset-release/') &&
!contains(github.event.pull_request.labels.*.name, 'disable-claude-code-review')
runs-on: ubuntu-latest
permissions: {}
steps:
- id: guard
name: Detect whether team-context inputs were provided
shell: bash
env:
APP_ID: ${{ secrets.TEAM_CONTEXT_APP_ID }}
SOURCES: ${{ secrets.TEAM_CONTEXT_SOURCES }}
run: |
if [[ -n "$APP_ID" && -n "$SOURCES" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "::notice::Team context not configured — review will run without injected standards."
fi
- name: Prepare context dir
shell: bash
# Always produce an artifact so job 2's download never fails. The marker
# is non-hidden because upload-artifact excludes dotfiles by default.
run: |
mkdir -p review-context
touch review-context/placeholder
- name: Fetch review context
if: steps.guard.outputs.enabled == 'true'
# TODO(before merge): switch @feat/... back to @main. The branch ref lets

Check warning on line 57 in .github/workflows/apps-claude-code-review.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_pipelines&issues=AZ8EZG0K_9qMPqdayxLX&open=AZ8EZG0K_9qMPqdayxLX&pullRequest=46
# this PR self-test the action before it exists on main; reusable
# workflows resolve composite actions by full path, not local ./.
uses: 0xPolygon/pipelines/.github/actions/fetch-team-context@feat/claude-review-team-context

Check failure on line 60 in .github/workflows/apps-claude-code-review.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_pipelines&issues=AZ8EZG0K_9qMPqdayxLW&open=AZ8EZG0K_9qMPqdayxLW&pullRequest=46
with:
app-id: ${{ secrets.TEAM_CONTEXT_APP_ID }}
private-key: ${{ secrets.TEAM_CONTEXT_APP_PRIVATE_KEY }}
sources: ${{ secrets.TEAM_CONTEXT_SOURCES }}
output-dir: review-context
- name: Upload context artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: review-context
path: review-context
retention-days: 1

# Job 2 — the review. Separate runner; not given the team-context credentials.
claude-review:
needs: fetch-context
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
Expand All @@ -29,6 +85,35 @@
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
- name: Download review context
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: review-context
path: .review-context
- name: Inject team standards into CLAUDE.md
shell: bash
# Load the standards natively via CLAUDE.md so they're guaranteed in the
# review's context; any other fetched docs stay on disk for on-demand reads.
run: |
std="$(find .review-context -type f -name team-standards.md | head -1)"
if [[ -z "$std" ]]; then
echo "::warning::No team standards in context — reviewing without them."
exit 0
fi
{
echo ""
echo "<!-- BEGIN injected team standards (CI review only; not committed) -->"
echo "# Team standards (injected for this review)"
echo ""
echo "These team standards govern this review. Additional reference material"
echo "(rationale docs and a service dependency graph for cross-repo impact)"
echo "is available on disk under \`.review-context/\` — consult it as needed."
echo ""
cat "$std"
echo ""
echo "<!-- END injected team standards -->"
} >> CLAUDE.md
echo "Injected $(wc -l < "$std") lines of team standards into CLAUDE.md"
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@df37d2f0760a4b5683a6e617c9325bc1a36443f6 # v1
Expand Down
Loading