name: Nightly Code Review # Nightly automated code review: Claude Code scans recent commits for # improvements, then creates a draft PR requiring human approval. # Estimated cost: ~$3-8 per run depending on change volume. on: # schedule: # - cron: '0 3 * * *' # 3 AM UTC daily — enable after cost validation workflow_dispatch: inputs: commits_to_analyze: description: 'Number of recent commits to analyze' type: number default: 10 dry_run: description: 'Dry run (skip PR creation)' type: boolean default: false concurrency: group: nightly-code-review cancel-in-progress: false jobs: preflight: name: Pre-flight Checks runs-on: ubuntu-latest timeout-minutes: 5 outputs: has_changes: ${{ steps.detect.outputs.has_changes }} skip: ${{ steps.dedup.outputs.skip }} changed_files: ${{ steps.detect.outputs.changed_files }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Check for existing review PR id: dedup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | EXISTING_PR=$(gh pr list --state open --label "nightly-review" --json number,headRefName \ --jq '[.[] | select(.headRefName | startswith("review/nightly"))][0].number' 2>/dev/null || echo "") if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then echo "::notice::Existing review PR #$EXISTING_PR is still open — skipping" echo "skip=true" >> $GITHUB_OUTPUT else echo "skip=false" >> $GITHUB_OUTPUT fi - name: Detect code changes in recent commits id: detect if: steps.dedup.outputs.skip != 'true' run: | COMMITS=${{ inputs.commits_to_analyze || 10 }} CHANGED_FILES=$(git log --oneline -"$COMMITS" --name-only --pretty=format: \ | sort -u | grep -v '^$' | grep -E '\.(py|sh|ts|tsx)$' || true) if [ -z "$CHANGED_FILES" ]; then echo "has_changes=false" >> $GITHUB_OUTPUT echo "::notice::No code changes in last $COMMITS commits" else FILE_COUNT=$(echo "$CHANGED_FILES" | wc -l | tr -d ' ') echo "has_changes=true" >> $GITHUB_OUTPUT echo "changed_files=$FILE_COUNT" >> $GITHUB_OUTPUT echo "::notice::Found $FILE_COUNT changed code files in last $COMMITS commits" fi code-review: name: Claude Code Review needs: preflight if: needs.preflight.outputs.has_changes == 'true' && needs.preflight.outputs.skip != 'true' runs-on: ubuntu-latest timeout-minutes: 30 outputs: has_improvements: ${{ steps.check-changes.outputs.has_improvements }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Validate API key env: HAS_API_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} run: | if [ "$HAS_API_KEY" != "true" ]; then echo "::error::ANTHROPIC_API_KEY not configured" exit 1 fi echo "Required secrets present" - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 20 - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.11' - name: Install tools run: pip install ruff - name: Run Claude Code review env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} COMMITS: ${{ inputs.commits_to_analyze || 10 }} run: | { cat .github/prompts/nightly-code-review.md echo "" echo "COMMITS_TO_ANALYZE: ${COMMITS}" } | npx -y @anthropic-ai/claude-code@2.1.89 \ --print \ --model claude-sonnet-4-6-v1 \ --max-turns 40 \ --allowedTools "Read,Edit,Glob,Grep,Bash(git log *),Bash(git diff *),Bash(git checkout -- *),Bash(ruff *),Bash(python -m py_compile *),Bash(shellcheck *),Bash(bash -n *)" - name: Enforce protected file guardrails run: | MODIFIED=$(git diff --name-only) if [ -z "$MODIFIED" ]; then exit 0; fi PROTECTED_PATTERNS=( ".github/" ".env" "ods/installers/" "ods/ods-cli" "ods/config/" ) for file in $MODIFIED; do for pattern in "${PROTECTED_PATTERNS[@]}"; do if [[ "$file" == $pattern* ]]; then echo "::warning::Reverting protected file: $file" git checkout -- "$file" fi done done - name: Secret scanning run: | DIFF_CONTENT=$(git diff) if [ -z "$DIFF_CONTENT" ]; then exit 0; fi if echo "$DIFF_CONTENT" | grep -iE '(sk-[a-zA-Z0-9]{20,}|AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|password\s*=\s*["\x27][^"\x27]+["\x27])' > /dev/null; then echo "::error::Potential secret detected in changes — aborting" git checkout -- . exit 1 fi - name: Diff size gate run: | if git diff --quiet; then exit 0; fi INSERTIONS=$(git diff --numstat | awk '{s+=$1} END {print s+0}') DELETIONS=$(git diff --numstat | awk '{s+=$2} END {print s+0}') DIFF_LINES=$((INSERTIONS + DELETIONS)) echo "::notice::Diff size: +$INSERTIONS -$DELETIONS ($DIFF_LINES total)" if [ "$DIFF_LINES" -gt 500 ]; then echo "::error::Diff too large ($DIFF_LINES lines) — aborting" git checkout -- . exit 1 fi - name: Check for improvements id: check-changes run: | if git diff --quiet; then echo "has_improvements=false" >> $GITHUB_OUTPUT echo "::notice::No code improvements generated" else echo "has_improvements=true" >> $GITHUB_OUTPUT git diff > /tmp/code-improvements.patch fi - name: Upload patch if: steps.check-changes.outputs.has_improvements == 'true' uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: code-improvements-patch path: /tmp/code-improvements.patch retention-days: 7 create-pr: name: Create Draft PR needs: [preflight, code-review] if: needs.code-review.outputs.has_improvements == 'true' runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Download patch uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: code-improvements-patch path: /tmp/ - name: Apply patch run: | if ! git apply --check /tmp/code-improvements.patch 2>&1; then echo "::error::Patch cannot be applied cleanly" exit 1 fi git apply /tmp/code-improvements.patch - name: Get date id: date run: echo "date=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT - name: Create Draft Pull Request if: inputs.dry_run != true uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: | refactor: nightly code improvements (${{ steps.date.outputs.date }}) Automated improvements from nightly code review. Co-Authored-By: Claude Sonnet 4.6 branch: review/nightly-${{ steps.date.outputs.date }}-${{ github.run_id }} delete-branch: true draft: true title: "refactor: nightly code improvements (${{ steps.date.outputs.date }})" body: | ## Nightly Code Review Improvements Automated code improvements generated by Claude Code. ### Review Process 1. **Claude Code**: Scanned last ${{ inputs.commits_to_analyze || 10 }} commits for code quality issues 2. **Guardrails**: Protected files enforced, secret scanning passed, diff size validated ### Review Checklist - [ ] Changes are correct and don't introduce bugs - [ ] Tests pass - [ ] Linting passes - [ ] No unintended behavioral changes --- Generated by [Claude Code](https://claude.com/claude-code) | [Workflow Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) labels: | nightly-review ai-generated needs-human-review