|
1 | 1 | name: Devin Review |
2 | 2 |
|
3 | 3 | on: |
4 | | - pull_request: |
| 4 | + pull_request_target: |
5 | 5 | types: [opened, synchronize, reopened, ready_for_review] |
| 6 | + workflow_dispatch: |
| 7 | + inputs: |
| 8 | + pr_number: |
| 9 | + description: Pull request number to review |
| 10 | + required: true |
| 11 | + type: string |
6 | 12 |
|
7 | 13 | jobs: |
8 | 14 | devin-review: |
| 15 | + if: ${{ github.event_name == 'workflow_dispatch' || !github.event.pull_request.draft }} |
9 | 16 | runs-on: ubuntu-latest |
10 | | - timeout-minutes: 30 |
| 17 | + timeout-minutes: 10 |
| 18 | + env: |
| 19 | + DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }} |
11 | 20 | permissions: |
12 | 21 | contents: read |
13 | | - pull-requests: write |
| 22 | + pull-requests: read |
14 | 23 |
|
15 | 24 | steps: |
16 | | - - name: Checkout repository |
17 | | - uses: actions/checkout@v4 |
| 25 | + - name: Resolve pull request context |
| 26 | + id: pr |
| 27 | + uses: actions/github-script@v8 |
18 | 28 | with: |
19 | | - fetch-depth: 0 |
| 29 | + script: | |
| 30 | + const prNumber = context.eventName === 'workflow_dispatch' |
| 31 | + ? Number(core.getInput('pr_number')) |
| 32 | + : context.payload.pull_request.number; |
20 | 33 |
|
21 | | - - name: Setup Node.js |
22 | | - uses: actions/setup-node@v4 |
23 | | - with: |
24 | | - node-version: '20' |
| 34 | + if (!Number.isInteger(prNumber) || prNumber <= 0) { |
| 35 | + core.setFailed(`Invalid pull request number: ${prNumber}`); |
| 36 | + return; |
| 37 | + } |
| 38 | +
|
| 39 | + const { owner, repo } = context.repo; |
| 40 | + const { data: pr } = await github.rest.pulls.get({ |
| 41 | + owner, |
| 42 | + repo, |
| 43 | + pull_number: prNumber, |
| 44 | + }); |
| 45 | + const files = await github.paginate(github.rest.pulls.listFiles, { |
| 46 | + owner, |
| 47 | + repo, |
| 48 | + pull_number: prNumber, |
| 49 | + per_page: 100, |
| 50 | + }); |
| 51 | +
|
| 52 | + core.setOutput('number', String(prNumber)); |
| 53 | + core.setOutput('url', pr.html_url); |
| 54 | + core.setOutput('title', pr.title); |
| 55 | + core.setOutput('author', pr.user.login); |
| 56 | + core.setOutput('head_sha', pr.head.sha); |
| 57 | + core.setOutput('head_repo', pr.head.repo.full_name); |
| 58 | + core.setOutput('base_repo', pr.base.repo.full_name); |
| 59 | + core.setOutput('files_json', JSON.stringify(files.map((file) => file.filename))); |
| 60 | +
|
| 61 | + - name: Validate Devin API configuration |
| 62 | + if: ${{ env.DEVIN_API_KEY == '' }} |
| 63 | + run: | |
| 64 | + echo "DEVIN_API_KEY is not configured for this repository or organization." >&2 |
| 65 | + echo "Configure DEVIN_API_KEY before using the API-based Devin review workflow." >&2 |
| 66 | + exit 1 |
25 | 67 |
|
26 | | - - name: Run Devin Review |
27 | | - # Use script to emulate a TTY as devin-review requires terminal features |
28 | | - # The -q flag suppresses script output, -e exits with command exit code, |
29 | | - # and -c runs the command. /dev/null discards script's own output. |
| 68 | + - name: Start Devin review session |
| 69 | + id: devin |
30 | 70 | env: |
31 | | - CI: true # Ensures the tool runs in non-interactive CI mode |
| 71 | + PR_NUMBER: ${{ steps.pr.outputs.number }} |
| 72 | + PR_URL: ${{ steps.pr.outputs.url }} |
| 73 | + PR_TITLE: ${{ steps.pr.outputs.title }} |
| 74 | + PR_AUTHOR: ${{ steps.pr.outputs.author }} |
| 75 | + PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }} |
| 76 | + PR_HEAD_REPO: ${{ steps.pr.outputs.head_repo }} |
| 77 | + PR_BASE_REPO: ${{ steps.pr.outputs.base_repo }} |
| 78 | + PR_FILES_JSON: ${{ steps.pr.outputs.files_json }} |
| 79 | + REPOSITORY: ${{ github.repository }} |
32 | 80 | run: | |
33 | | - script -q -e -c "printf 'y\n' | npx devin-review '${{ github.event.pull_request.html_url }}'" /dev/null |
| 81 | + PROMPT=$(cat <<EOF |
| 82 | + You are reviewing pull request #${PR_NUMBER} in ${REPOSITORY}. |
| 83 | +
|
| 84 | + Repository: |
| 85 | + - Base repository: ${PR_BASE_REPO} |
| 86 | + - Head repository: ${PR_HEAD_REPO} |
| 87 | +
|
| 88 | + Pull request: |
| 89 | + - Title: ${PR_TITLE} |
| 90 | + - Author: ${PR_AUTHOR} |
| 91 | + - URL: ${PR_URL} |
| 92 | + - Head SHA: ${PR_HEAD_SHA} |
| 93 | +
|
| 94 | + Changed files JSON: |
| 95 | + ${PR_FILES_JSON} |
| 96 | +
|
| 97 | + Tasks: |
| 98 | + 1. Review the current pull request diff and related repository context for PR #${PR_NUMBER}. |
| 99 | + 2. Never commit, push, or open a new pull request. |
| 100 | + 3. Never ask the user for confirmation and never wait for user messages. |
| 101 | + 4. Leave at most 3 total review comments. |
| 102 | + 5. Use inline review comments with precise line references when possible. |
| 103 | + 6. Before commenting, check whether the same issue was already raised or already fixed in the current PR discussion. |
| 104 | + 7. If no issues are found, leave a short summary comment saying everything looks good. |
| 105 | + 8. Follow repository instruction files such as AGENTS.md, CLAUDE.md, CONTRIBUTING.md, and REVIEW.md if present. |
| 106 | +
|
| 107 | + Focus on bugs, regressions, missing tests, and clear correctness issues. Avoid speculative nits. |
| 108 | + EOF |
| 109 | + ) |
| 110 | +
|
| 111 | + PAYLOAD=$(jq -n \ |
| 112 | + --arg prompt "$PROMPT" \ |
| 113 | + --arg title "PR Review #${PR_NUMBER} (${REPOSITORY})" \ |
| 114 | + '{ |
| 115 | + prompt: $prompt, |
| 116 | + idempotent: true, |
| 117 | + title: $title, |
| 118 | + tags: ["github-actions", "devin-review", "pull-request"] |
| 119 | + }') |
| 120 | +
|
| 121 | + RESPONSE=$(curl --fail --silent --show-error \ |
| 122 | + --request POST \ |
| 123 | + --url https://api.devin.ai/v1/sessions \ |
| 124 | + --header "Authorization: Bearer ${DEVIN_API_KEY}" \ |
| 125 | + --header "Content-Type: application/json" \ |
| 126 | + --data "$PAYLOAD") |
| 127 | +
|
| 128 | + SESSION_ID=$(echo "$RESPONSE" | jq -r '.session_id') |
| 129 | + SESSION_URL=$(echo "$RESPONSE" | jq -r '.url') |
| 130 | +
|
| 131 | + if [[ -z "$SESSION_ID" || "$SESSION_ID" == "null" || -z "$SESSION_URL" || "$SESSION_URL" == "null" ]]; then |
| 132 | + echo "Unexpected Devin API response: $RESPONSE" >&2 |
| 133 | + exit 1 |
| 134 | + fi |
| 135 | +
|
| 136 | + echo "session_id=$SESSION_ID" >> "$GITHUB_OUTPUT" |
| 137 | + echo "session_url=$SESSION_URL" >> "$GITHUB_OUTPUT" |
| 138 | +
|
| 139 | + { |
| 140 | + echo "Started Devin review session." |
| 141 | + echo |
| 142 | + echo "- PR: #${PR_NUMBER}" |
| 143 | + echo "- Session ID: ${SESSION_ID}" |
| 144 | + echo "- Session URL: ${SESSION_URL}" |
| 145 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments