@@ -2,28 +2,136 @@ name: Devin Review
22
33on :
44 pull_request :
5- types : [opened, synchronize, reopened]
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
612
713jobs :
814 devin-review :
915 runs-on : ubuntu-latest
16+ timeout-minutes : 5
17+ permissions :
18+ contents : read
19+ issues : write
20+ pull-requests : read
1021
1122 steps :
12- - name : Checkout repository
13- uses : actions/checkout@v4
23+ - name : Resolve Devin Review URL
24+ id : review
25+ uses : actions/github-script@v8
26+ env :
27+ WORKFLOW_PR_NUMBER : ${{ inputs.pr_number }}
1428 with :
15- fetch-depth : 0
29+ script : |
30+ const prNumber = context.eventName === 'workflow_dispatch'
31+ ? Number(process.env.WORKFLOW_PR_NUMBER)
32+ : context.payload.pull_request.number;
1633
17- - name : Setup Node.js
18- uses : actions/setup-node@v4
19- with :
20- 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 reviewUrl = `https://devinreview.com/${owner}/${repo}/pull/${prNumber}`;
41+ const prUrl = `https://github.com/${owner}/${repo}/pull/${prNumber}`;
42+
43+ core.setOutput('number', String(prNumber));
44+ core.setOutput('pr_url', prUrl);
45+ core.setOutput('review_url', reviewUrl);
46+
47+ - name : Warm Devin Review page
48+ env :
49+ REVIEW_URL : ${{ steps.review.outputs.review_url }}
50+ run : |
51+ curl --fail --silent --show-error --location "$REVIEW_URL" --output /dev/null
52+
53+ - name : Publish Devin Review summary
54+ env :
55+ PR_NUMBER : ${{ steps.review.outputs.number }}
56+ PR_URL : ${{ steps.review.outputs.pr_url }}
57+ REVIEW_URL : ${{ steps.review.outputs.review_url }}
58+ run : |
59+ {
60+ echo "Devin Review is available for PR #${PR_NUMBER}."
61+ echo
62+ echo "- GitHub PR: ${PR_URL}"
63+ echo "- Devin Review: ${REVIEW_URL}"
64+ echo
65+ echo "This workflow intentionally does not use DEVIN_API_KEY."
66+ echo "For automatic Devin statuses or comments inside GitHub, connect the Devin GitHub integration and enable auto-review in Devin settings."
67+ } >> "$GITHUB_STEP_SUMMARY"
2168
22- - name : Run Devin Review
23- # Use script to emulate a TTY as devin-review requires terminal features
24- # The -q flag suppresses script output, -e exits with command exit code,
25- # and -c runs the command. /dev/null discards script's own output.
69+ - name : Upsert PR comment with Devin Review link
70+ id : comment
71+ uses : actions/github-script@v8
2672 env :
27- CI : true # Ensures the tool runs in non-interactive CI mode
73+ PR_NUMBER : ${{ steps.review.outputs.number }}
74+ PR_URL : ${{ steps.review.outputs.pr_url }}
75+ REVIEW_URL : ${{ steps.review.outputs.review_url }}
76+ with :
77+ script : |
78+ const marker = '<!-- devin-review-link -->';
79+ const issue_number = Number(process.env.PR_NUMBER);
80+ core.setOutput('posted', 'false');
81+ const body = [
82+ marker,
83+ 'Devin Review is available for this pull request.',
84+ '',
85+ `- GitHub PR: ${process.env.PR_URL}`,
86+ `- Devin Review: ${process.env.REVIEW_URL}`,
87+ '',
88+ 'This link opens the hosted Devin Review page for the current PR.',
89+ ].join('\n');
90+
91+ try {
92+ const comments = await github.paginate(github.rest.issues.listComments, {
93+ owner: context.repo.owner,
94+ repo: context.repo.repo,
95+ issue_number,
96+ per_page: 100,
97+ });
98+
99+ const existing = comments.find((comment) => comment.body && comment.body.includes(marker));
100+
101+ if (existing) {
102+ await github.rest.issues.updateComment({
103+ owner: context.repo.owner,
104+ repo: context.repo.repo,
105+ comment_id: existing.id,
106+ body,
107+ });
108+ core.info(`Updated existing Devin Review comment: ${existing.html_url}`);
109+ } else {
110+ const created = await github.rest.issues.createComment({
111+ owner: context.repo.owner,
112+ repo: context.repo.repo,
113+ issue_number,
114+ body,
115+ });
116+ core.info(`Created Devin Review comment: ${created.data.html_url}`);
117+ }
118+
119+ core.setOutput('posted', 'true');
120+ } catch (error) {
121+ if (error && error.status === 403) {
122+ core.warning('PR comment was not posted because this repository grants GitHub Actions a read-only GITHUB_TOKEN.');
123+ } else {
124+ throw error;
125+ }
126+ }
127+
128+ - name : Report comment permission limitation
129+ if : ${{ steps.comment.outputs.posted != 'true' }}
28130 run : |
29- script -q -e -c "npx devin-review ${{ github.event.pull_request.html_url }}" /dev/null
131+ {
132+ echo
133+ echo "PR comment was not posted automatically."
134+ echo
135+ echo "This repository currently sets GitHub Actions GITHUB_TOKEN to read-only permissions, so the workflow cannot create issue comments."
136+ echo "If repository workflow permissions are changed to read/write, this step will start posting or updating the PR comment automatically."
137+ } >> "$GITHUB_STEP_SUMMARY"
0 commit comments