Add Stage 1 workflow for external PR info collection

- Collects PR information without requiring secrets
- Triggers on pull_request events and @claude-review-ext comments
- Uploads PR details as artifact for secure processing
This commit is contained in:
Rasmus Widing 2025-08-19 10:09:30 +03:00
parent 46e8358422
commit d64745991b

View File

@ -0,0 +1,231 @@
name: Claude Review External - Stage 1 (PR Info Collection)
# This workflow runs on pull requests from forks
# It collects PR information and saves it as an artifact for Stage 2
# No secrets are available or needed in this stage
on:
pull_request:
types: [opened, synchronize, reopened]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
collect-pr-info:
# Only trigger on specific conditions
# For PRs: always collect info
# For comments: only when @claude-review-ext is mentioned
if: |
github.event_name == 'pull_request' ||
(
(github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
contains(github.event.comment.body, '@claude-review-ext')
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout PR code
uses: actions/checkout@v4
with:
# For PRs, this automatically checks out the merge commit
fetch-depth: 0
- name: Collect PR Information
id: pr-info
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let prNumber, prTitle, prBody, prAuthor, baseBranch, headBranch, headSha;
let triggerPhrase = '';
let commentBody = '';
let commentId = null;
let commentAuthor = '';
if (context.eventName === 'pull_request') {
prNumber = context.payload.pull_request.number;
prTitle = context.payload.pull_request.title;
prBody = context.payload.pull_request.body || '';
prAuthor = context.payload.pull_request.user.login;
baseBranch = context.payload.pull_request.base.ref;
headBranch = context.payload.pull_request.head.ref;
headSha = context.payload.pull_request.head.sha;
} else if (context.eventName === 'issue_comment') {
// For issue comments on PRs
const issue = context.payload.issue;
if (!issue.pull_request) {
console.log('Not a PR comment, skipping');
return;
}
prNumber = issue.number;
triggerPhrase = '@claude-review-ext';
commentBody = context.payload.comment.body;
commentId = context.payload.comment.id;
commentAuthor = context.payload.comment.user.login;
// Fetch full PR details
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
prTitle = pr.title;
prBody = pr.body || '';
prAuthor = pr.user.login;
baseBranch = pr.base.ref;
headBranch = pr.head.ref;
headSha = pr.head.sha;
} else if (context.eventName === 'pull_request_review_comment') {
prNumber = context.payload.pull_request.number;
prTitle = context.payload.pull_request.title;
prBody = context.payload.pull_request.body || '';
prAuthor = context.payload.pull_request.user.login;
baseBranch = context.payload.pull_request.base.ref;
headBranch = context.payload.pull_request.head.ref;
headSha = context.payload.pull_request.head.sha;
triggerPhrase = '@claude-review-ext';
commentBody = context.payload.comment.body;
commentId = context.payload.comment.id;
commentAuthor = context.payload.comment.user.login;
}
// Get list of changed files
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const changedFiles = files.map(f => ({
filename: f.filename,
status: f.status,
additions: f.additions,
deletions: f.deletions,
changes: f.changes,
patch: f.patch
}));
// Get diff
const { data: diff } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
mediaType: {
format: 'diff'
}
});
const prInfo = {
prNumber,
prTitle,
prBody,
prAuthor,
baseBranch,
headBranch,
headSha,
triggerPhrase,
commentBody,
commentId,
commentAuthor,
changedFiles,
diff,
eventName: context.eventName,
repository: `${context.repo.owner}/${context.repo.repo}`,
triggeredAt: new Date().toISOString()
};
// Save to file
fs.writeFileSync('pr-info.json', JSON.stringify(prInfo, null, 2));
console.log(`Collected info for PR #${prNumber} by ${prAuthor}`);
console.log(`Changed files: ${changedFiles.length}`);
console.log(`Event: ${context.eventName}`);
if (triggerPhrase) {
console.log(`Triggered by: ${commentAuthor} with phrase: ${triggerPhrase}`);
}
// Set outputs for job summary
core.setOutput('pr_number', prNumber);
core.setOutput('pr_author', prAuthor);
core.setOutput('changed_files_count', changedFiles.length);
- name: Run Basic Validation
id: validation
run: |
echo "Running basic validation checks..."
# Check if this is from a fork
IS_FORK="${{ github.event.pull_request.head.repo.fork }}"
echo "Is from fork: $IS_FORK"
# Count files changed
CHANGED_FILES=$(jq '.changedFiles | length' pr-info.json)
echo "Files changed: $CHANGED_FILES"
# Check file types
echo "File types changed:"
jq -r '.changedFiles[].filename' pr-info.json | sed 's/.*\.//' | sort | uniq -c
# Check for potential issues
LARGE_DIFF=false
if [ "$CHANGED_FILES" -gt 100 ]; then
LARGE_DIFF=true
echo "⚠️ Large PR detected: $CHANGED_FILES files changed"
fi
echo "is_fork=$IS_FORK" >> $GITHUB_OUTPUT
echo "large_diff=$LARGE_DIFF" >> $GITHUB_OUTPUT
- name: Upload PR Information
uses: actions/upload-artifact@v4
with:
name: pr-info-${{ github.event.pull_request.number || github.event.issue.number }}
path: pr-info.json
retention-days: 1
- name: Post Status Comment
if: github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment'
uses: actions/github-script@v7
with:
script: |
const prNumber = ${{ steps.pr-info.outputs.pr_number }};
const isFork = '${{ steps.validation.outputs.is_fork }}' === 'true';
const filesCount = ${{ steps.pr-info.outputs.changed_files_count }};
let statusMessage = `🔄 **Claude Review Stage 1 Complete**\n\n`;
statusMessage += `- PR: #${prNumber}\n`;
statusMessage += `- Author: @${{ steps.pr-info.outputs.pr_author }}\n`;
statusMessage += `- Files Changed: ${filesCount}\n`;
statusMessage += `- Fork Status: ${isFork ? '🔱 External Fork' : '✅ Direct Branch'}\n\n`;
statusMessage += `📋 Stage 2 (Claude Review) will run automatically after this workflow completes.\n`;
statusMessage += `This two-stage process ensures secure handling of forked PRs.`;
// Post comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: statusMessage
});
- name: Job Summary
run: |
echo "## Claude Review Stage 1 Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **PR Number**: #${{ steps.pr-info.outputs.pr_number }}" >> $GITHUB_STEP_SUMMARY
echo "- **Author**: @${{ steps.pr-info.outputs.pr_author }}" >> $GITHUB_STEP_SUMMARY
echo "- **Files Changed**: ${{ steps.pr-info.outputs.changed_files_count }}" >> $GITHUB_STEP_SUMMARY
echo "- **From Fork**: ${{ steps.validation.outputs.is_fork }}" >> $GITHUB_STEP_SUMMARY
echo "- **Large PR**: ${{ steps.validation.outputs.large_diff }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ PR information collected and uploaded as artifact for Stage 2 processing." >> $GITHUB_STEP_SUMMARY