@alibaba-group/open-code-review 1.3.14 → 1.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,223 +0,0 @@
1
- # OpenCodeReview - GitHub Actions Demo
2
-
3
- This demo shows how to integrate OpenCodeReview into your GitHub Actions workflow to automatically review Pull Requests and post review comments.
4
-
5
- ## How It Works
6
-
7
- ```
8
- PR Created/Updated → GitHub Actions Triggered → OCR Reviews Diff → Comments Posted on PR
9
- OR
10
- Comment with trigger keyword ↗
11
- ```
12
-
13
- 1. When a PR is opened, the workflow triggers (uses `pull_request_target` for fork secret access)
14
- 2. Alternatively, when a comment containing `/open-code-review` or `@open-code-review` is posted on a PR, the workflow triggers
15
- 3. It installs OCR via `npm install -g @alibaba-group/open-code-review`
16
- 4. Runs `ocr review --from origin/<base> --to <head_sha> --format json` to analyze the diff (uses commit SHA to support fork PRs)
17
- 5. Parses the JSON output and posts inline review comments on the PR using GitHub's Pull Request Review API
18
-
19
- ## Setup
20
-
21
- ### 1. Copy the workflow file
22
-
23
- Copy `ocr-review.yml` to your repository's `.github/workflows/` directory:
24
-
25
- ```bash
26
- mkdir -p .github/workflows
27
- cp ocr-review.yml .github/workflows/ocr-review.yml
28
- ```
29
-
30
- ### 2. Configure secrets
31
-
32
- Go to your repository's **Settings → Secrets and variables → Actions** and add:
33
-
34
- | Secret | Required | Description |
35
- |--------|----------|-------------|
36
- | `OCR_LLM_URL` | Yes | LLM API endpoint URL (e.g., `https://api.openai.com/v1/chat/completions`) |
37
- | `OCR_LLM_AUTH_TOKEN` | Yes | API authentication token |
38
- | `OCR_LLM_MODEL` | No | Model name (defaults to `gpt-4o`) |
39
- | `OCR_LLM_USE_ANTHROPIC` | No | Set to `true` if using Anthropic Claude models |
40
-
41
- > **Note:** `GITHUB_TOKEN` is automatically provided by GitHub Actions with the required `pull-requests: write` permission.
42
- >
43
- > The workflow also configures `llm.extra_body` to disable thinking mode for compatibility with various LLM providers.
44
-
45
- ## Customization
46
-
47
- ### Change the trigger events
48
-
49
- Modify the `on.pull_request_target.types` array in the workflow file:
50
-
51
- ```yaml
52
- on:
53
- pull_request_target:
54
- types: [opened, synchronize, reopened, ready_for_review]
55
- ```
56
-
57
- ### Customize comment trigger keywords
58
-
59
- By default, the workflow triggers when a PR comment starts with `/open-code-review` or `@open-code-review`. You can customize these keywords by modifying the `if` condition in the workflow:
60
-
61
- ```yaml
62
- if: |
63
- github.event_name == 'pull_request_target' ||
64
- (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/review')) ||
65
- (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '@mybot'))
66
- ```
67
-
68
- Or use a more flexible pattern with `contains` to trigger on any comment containing the keyword:
69
-
70
- ```yaml
71
- if: |
72
- github.event_name == 'pull_request_target' ||
73
- (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '/review'))
74
- ```
75
-
76
- > **Note:** The condition `github.event.issue.pull_request` ensures the comment is on a PR, not a regular issue.
77
-
78
- ### Use a specific OCR version
79
-
80
- ```yaml
81
- - name: Install OpenCodeReview
82
- run: npm install -g @alibaba-group/open-code-review@1.0.0
83
- ```
84
-
85
- ### Add custom review rules
86
-
87
- Use the `--rule` flag to pass a custom rules JSON file:
88
-
89
- ```yaml
90
- - name: Run OCR review
91
- run: ocr review --rule ./my-rules.json --from origin/${{ github.base_ref }} --to origin/${{ github.head_ref }}
92
- ```
93
-
94
- ### Limit concurrency
95
-
96
- Adjust the `--concurrency` flag for large PRs to control the number of concurrent LLM requests:
97
-
98
- ```yaml
99
- - name: Run OCR review
100
- run: ocr review --concurrency 5 --from origin/${{ github.base_ref }} --to origin/${{ github.head_ref }}
101
- ```
102
-
103
- ### Provide background context
104
-
105
- Use the `--background` flag to pass additional context that helps OCR better understand the purpose of the changes:
106
-
107
- ```yaml
108
- - name: Run OCR review
109
- run: ocr review --background "${{ github.event.pull_request.title }}" --from origin/${{ github.base_ref }} --to origin/${{ github.head_ref }}
110
- ```
111
-
112
- This is particularly useful when your PR titles follow semantic conventions (e.g., `feat(auth): add OAuth2 support`) that clearly summarize what the PR implements. The background information helps OCR provide more relevant and context-aware review comments.
113
-
114
- ### Customize the review comment author with GitHub App
115
-
116
- By default, review comments are posted using the built-in `GITHUB_TOKEN`, which appears as `github-actions[bot]`. You can customize this by creating a GitHub App and using its credentials instead.
117
-
118
- For more details about GitHub Apps, see the [GitHub Apps documentation](https://docs.github.com/en/apps).
119
-
120
- #### Step 1: Create a GitHub App
121
-
122
- 1. Go to your organization or personal account **Settings → Developer settings → GitHub Apps → New GitHub App**
123
- 2. Fill in the following:
124
- - **GitHub App name**: e.g., `OpenCodeReview Bot`
125
- - **Homepage URL**: Your repository or documentation URL
126
- - **Webhook**: Uncheck "Active" (not needed for this use case)
127
- 3. Under **Repository permissions**, set:
128
- - **Pull requests**: Read and write
129
- - **Contents**: Read-only (for fetching diffs)
130
- - **Metadata**: Read-only (required)
131
- 4. Click **Create GitHub App**
132
-
133
- #### Step 2: Generate a Private Key
134
-
135
- 1. After creating the app, scroll down to **Private keys**
136
- 2. Click **Generate a private key**
137
- 3. Download and save the `.pem` file securely
138
-
139
- Note your App ID from the app settings page.
140
-
141
- #### Step 3: Install the App
142
-
143
- 1. In the left sidebar, click **Install App**
144
- 2. Select the repositories where you want to use OCR
145
- 3. After installation, note the **Installation ID** from the URL (e.g., `https://github.com/settings/installations/12345` → Installation ID is `12345`)
146
-
147
- #### Step 4: Configure Repository Secrets
148
-
149
- Add the following secrets to your repository (**Settings → Secrets and variables → Actions**):
150
-
151
- | Secret | Description |
152
- |--------|-------------|
153
- | `GITHUB_APP_ID` | Your GitHub App's ID |
154
- | `GITHUB_APP_PRIVATE_KEY` | Contents of the `.pem` file (including `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----`) |
155
- | `GITHUB_APP_INSTALLATION_ID` | The Installation ID from Step 3 |
156
-
157
- #### Step 5: Update the Workflow
158
-
159
- Add a step to obtain a token from the GitHub App, then use it in the "Post review comments to PR" step:
160
-
161
- ```yaml
162
- - name: Get GitHub App Token
163
- id: app-token
164
- uses: actions/create-github-app-token@v1
165
- with:
166
- app-id: ${{ secrets.GITHUB_APP_ID }}
167
- private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}
168
-
169
- - name: Post review comments to PR
170
- uses: actions/github-script@v7
171
- with:
172
- github-token: ${{ steps.app-token.outputs.token }}
173
- script: |
174
- # ... existing script
175
- ```
176
-
177
- Now review comments will be posted with your custom GitHub App identity (e.g., `OpenCodeReview Bot`), providing a more professional and distinguishable appearance in your PRs.
178
-
179
- ## Example Output
180
-
181
- When a PR is reviewed, comments appear directly in the PR's "Files changed" tab:
182
-
183
- - ✅ If no issues found: A comment saying "No comments generated. Looks good to me."
184
- - 🔍 If issues found: Inline review comments with suggestions using GitHub's native suggestion syntax
185
-
186
- ### Inline Comment Example
187
-
188
- The workflow uses GitHub's `suggestion` code block syntax, so reviewers can apply fixes with one click:
189
-
190
- ````markdown
191
- **Suggestion:**
192
- ```suggestion
193
- // Fixed code here
194
- ```
195
- ````
196
-
197
- ## Supported LLM Providers
198
-
199
- OCR supports both OpenAI and Anthropic API formats:
200
-
201
- - **OpenAI-compatible APIs** (default):
202
- - OpenAI (GPT-4o, GPT-4, etc.)
203
- - Azure OpenAI
204
- - Self-hosted models (vLLM, Ollama, etc.)
205
- - **Anthropic APIs** (set `OCR_LLM_USE_ANTHROPIC: true`):
206
- - Anthropic Claude models
207
-
208
- ## Troubleshooting
209
-
210
- ### Common Issues
211
-
212
- 1. **"Failed to parse OCR output"**: Check that `OCR_LLM_URL` and `OCR_LLM_AUTH_TOKEN` secrets are correctly set
213
- 2. **"Cannot find merge-base"**: Ensure `fetch-depth: 0` is set in the checkout step
214
- 3. **Review comments not appearing on correct lines**: This can happen when the diff has changed since the review started; the workflow handles this gracefully with a fallback to issue comments
215
-
216
- ### Debugging
217
-
218
- Enable debug logging by adding to the OCR review step:
219
-
220
- ```yaml
221
- env:
222
- OCR_DEBUG: "1"
223
- ```
@@ -1,357 +0,0 @@
1
- # OpenCodeReview - GitHub Actions PR Auto-Review Demo
2
- #
3
- # This workflow automatically reviews pull requests using OpenCodeReview
4
- # and posts review comments directly on the PR.
5
- #
6
- # Triggers:
7
- # - PR opened (uses pull_request_target for fork secret access)
8
- # - Comment on PR containing '/open-code-review' or '@open-code-review'
9
- #
10
- # Required secrets:
11
- # OCR_LLM_URL - LLM API endpoint (e.g., https://api.openai.com/v1/chat/completions)
12
- # OCR_LLM_AUTH_TOKEN - Authentication token for the LLM API
13
- #
14
- # Optional secrets:
15
- # OCR_LLM_MODEL - Model name (default: gpt-4o)
16
- #
17
- # Note: GITHUB_TOKEN is automatically provided by GitHub Actions.
18
-
19
- name: OpenCodeReview PR Review
20
-
21
- concurrency:
22
- group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
23
- cancel-in-progress: true
24
-
25
- on:
26
- # Use pull_request_target instead of pull_request so that secrets are
27
- # available even for PRs from forks. This is safe because OCR only reads
28
- # the diff and does not execute any code from the PR.
29
- pull_request_target:
30
- types: [opened]
31
- issue_comment:
32
- types: [created]
33
-
34
- permissions:
35
- contents: read
36
- pull-requests: write
37
-
38
- jobs:
39
- code-review:
40
- runs-on: ubuntu-latest
41
- # Run on PR events, or on comments starting with trigger keywords
42
- if: |
43
- github.event_name == 'pull_request_target' ||
44
- (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/open-code-review')) ||
45
- (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '@open-code-review'))
46
- steps:
47
- - name: Get PR context
48
- id: pr-context
49
- if: github.event_name != 'pull_request_target'
50
- uses: actions/github-script@v7
51
- with:
52
- script: |
53
- // For issue_comment events, get PR info
54
- const prNumber = context.issue.number;
55
- const { data: pullRequest } = await github.rest.pulls.get({
56
- owner: context.repo.owner,
57
- repo: context.repo.repo,
58
- pull_number: prNumber
59
- });
60
- core.setOutput('base_ref', pullRequest.base.ref);
61
- core.setOutput('head_ref', pullRequest.head.ref);
62
- core.setOutput('head_sha', pullRequest.head.sha);
63
-
64
- - name: Checkout repository
65
- uses: actions/checkout@v4
66
- with:
67
- fetch-depth: 0 # Full history needed for merge-base diff
68
- ref: ${{ github.event.pull_request.head.sha || steps.pr-context.outputs.head_sha }}
69
-
70
- - name: Fetch PR head ref (ensures fork commits are available)
71
- run: git fetch origin pull/${{ github.event.pull_request.number || github.event.issue.number }}/head
72
-
73
- - name: Setup Node.js
74
- uses: actions/setup-node@v4
75
- with:
76
- node-version: '20'
77
-
78
- - name: Install OpenCodeReview
79
- run: npm install -g @alibaba-group/open-code-review
80
-
81
- - name: Configure OCR
82
- run: |
83
- ocr config set llm.url ${{ secrets.OCR_LLM_URL }}
84
- ocr config set llm.auth_token ${{ secrets.OCR_LLM_AUTH_TOKEN }}
85
- ocr config set llm.model ${{ secrets.OCR_LLM_MODEL }}
86
- ocr config set llm.use_anthropic ${{ secrets.OCR_LLM_USE_ANTHROPIC }}
87
- ocr config set llm.extra_body '{"thinking": {"type": "disabled"}}'
88
-
89
- - name: Run OpenCodeReview
90
- id: review
91
- run: |
92
- # Get base ref and head SHA from PR context (different for comment triggers)
93
- # Note: We use HEAD_SHA instead of origin/${HEAD_REF} to support fork PRs,
94
- # because fork branches don't exist on the origin remote.
95
- if [ "${{ github.event_name }}" = "pull_request_target" ]; then
96
- BASE_REF="${{ github.event.pull_request.base.ref }}"
97
- HEAD_SHA="${{ github.event.pull_request.head.sha }}"
98
- else
99
- BASE_REF="${{ steps.pr-context.outputs.base_ref }}"
100
- HEAD_SHA="${{ steps.pr-context.outputs.head_sha }}"
101
- fi
102
-
103
- echo "Reviewing PR: ${HEAD_SHA} against origin/${BASE_REF}"
104
-
105
- # Run OCR in range mode with JSON output
106
- ocr review \
107
- --from "origin/${BASE_REF}" \
108
- --to "${HEAD_SHA}" \
109
- --format json \
110
- > /tmp/ocr-result.json 2>/tmp/ocr-stderr.log || true
111
-
112
- echo "OCR review completed. Output:"
113
- cat /tmp/ocr-result.json
114
- echo "OCR review completed. Error log:"
115
- cat /tmp/ocr-stderr.log
116
-
117
- - name: Post review comments to PR
118
- uses: actions/github-script@v7
119
- with:
120
- github-token: ${{ secrets.GITHUB_TOKEN }}
121
- script: |
122
- const fs = require('fs');
123
- const path = '/tmp/ocr-result.json';
124
-
125
- // Read OCR output
126
- let result;
127
- try {
128
- const raw = fs.readFileSync(path, 'utf8');
129
- result = JSON.parse(raw);
130
- } catch (e) {
131
- console.log('Failed to parse OCR output:', e.message);
132
- // Post a simple comment if parsing fails
133
- const stderr = fs.readFileSync('/tmp/ocr-stderr.log', 'utf8').trim();
134
- if (stderr) {
135
- await github.rest.issues.createComment({
136
- owner: context.repo.owner,
137
- repo: context.repo.repo,
138
- issue_number: context.issue.number,
139
- body: `⚠️ **OpenCodeReview** encountered an error:\n${fencedBlock(stderr)}`
140
- });
141
- }
142
- return;
143
- }
144
-
145
- const comments = result.comments || [];
146
- const warnings = result.warnings || [];
147
-
148
- // If no comments, post a summary
149
- if (comments.length === 0) {
150
- const message = result.message || 'No comments generated. Looks good to me.';
151
- await github.rest.issues.createComment({
152
- owner: context.repo.owner,
153
- repo: context.repo.repo,
154
- issue_number: context.issue.number,
155
- body: `✅ **OpenCodeReview**: ${message}`
156
- });
157
- return;
158
- }
159
-
160
- // Prepare PR review with inline comments
161
- const prNumber = context.issue.number;
162
- let commitSha;
163
-
164
- // Get commit SHA from event context
165
- if (context.eventName === 'pull_request_target') {
166
- commitSha = context.payload.pull_request.head.sha;
167
- } else {
168
- // For comment events, we need to fetch the PR
169
- const { data: pullRequest } = await github.rest.pulls.get({
170
- owner: context.repo.owner,
171
- repo: context.repo.repo,
172
- pull_number: prNumber
173
- });
174
- commitSha = pullRequest.head.sha;
175
- }
176
-
177
- // Build review comments array for the PR review API
178
- // Only inline comments with line info can be posted via createReview
179
- const reviewComments = [];
180
- const commentsWithoutLine = [];
181
-
182
- for (const comment of comments) {
183
- const body = formatComment(comment);
184
-
185
- // Check if comment has valid line information for inline comment (line >= 1)
186
- const hasValidLine = (comment.start_line >= 1) || (comment.end_line >= 1);
187
- if (!hasValidLine) {
188
- commentsWithoutLine.push({ comment, body });
189
- continue;
190
- }
191
-
192
- const reviewComment = {
193
- path: comment.path,
194
- body: body
195
- };
196
-
197
- // Use line range if available
198
- if (comment.start_line >= 1 && comment.end_line >= 1 && comment.start_line !== comment.end_line) {
199
- reviewComment.start_line = comment.start_line;
200
- reviewComment.line = comment.end_line;
201
- reviewComment.start_side = 'RIGHT';
202
- reviewComment.side = 'RIGHT';
203
- } else if (comment.end_line >= 1) {
204
- reviewComment.line = comment.end_line;
205
- reviewComment.side = 'RIGHT';
206
- } else if (comment.start_line >= 1) {
207
- reviewComment.line = comment.start_line;
208
- reviewComment.side = 'RIGHT';
209
- }
210
-
211
- reviewComments.push({ comment, reviewComment });
212
- }
213
-
214
- // Submit as a single PR review with all comments
215
- const totalCount = comments.length;
216
- const inlineCount = reviewComments.length;
217
- const summaryCount = commentsWithoutLine.length;
218
- let summaryBody = buildSummaryBody(totalCount, inlineCount, summaryCount, warnings);
219
-
220
- // Add comments without line info to summary body
221
- summaryBody += formatSummaryComments(commentsWithoutLine);
222
-
223
- // Statistics tracking
224
- let successCount = 0;
225
- let failedCount = 0;
226
- const failedComments = [];
227
-
228
- try {
229
- await github.rest.pulls.createReview({
230
- owner: context.repo.owner,
231
- repo: context.repo.repo,
232
- pull_number: prNumber,
233
- commit_id: commitSha,
234
- body: summaryBody,
235
- event: 'COMMENT',
236
- comments: reviewComments.map(({ reviewComment }) => reviewComment)
237
- });
238
- successCount = reviewComments.length;
239
- console.log(`Successfully posted review with ${successCount} inline comments (${commentsWithoutLine.length} in summary)`);
240
- } catch (e) {
241
- console.log('Failed to post review with inline comments:', e.message);
242
- console.log('Falling back to posting comments individually...');
243
-
244
- // Fallback: post comments one by one
245
- for (const { comment, reviewComment } of reviewComments) {
246
- try {
247
- await github.rest.pulls.createReview({
248
- owner: context.repo.owner,
249
- repo: context.repo.repo,
250
- pull_number: prNumber,
251
- commit_id: commitSha,
252
- body: '',
253
- event: 'COMMENT',
254
- comments: [reviewComment]
255
- });
256
- successCount++;
257
- console.log(`Successfully posted comment for ${reviewComment.path}`);
258
- } catch (innerE) {
259
- failedCount++;
260
- failedComments.push({ comment, error: innerE.message });
261
- console.log(`Failed to post comment for ${reviewComment.path}: ${innerE.message}`);
262
- }
263
- }
264
-
265
- // Post summary comment with statistics
266
- let finalBody = buildSummaryBody(totalCount, successCount, commentsWithoutLine.length + failedComments.length, warnings);
267
- finalBody += formatSummaryComments(commentsWithoutLine);
268
- finalBody += `\n\n---\n\n📊 **Posting Statistics:**`;
269
- finalBody += `\n- ✅ Successfully posted: ${successCount} comment(s)`;
270
- if (failedCount > 0) {
271
- finalBody += `\n- ❌ Failed to post: ${failedCount} comment(s)`;
272
- }
273
-
274
- // Add failed comments as summary content so review feedback is not lost.
275
- if (failedComments.length > 0) {
276
- finalBody += '\n\n---\n\n### ⚠️ Inline comments shown in summary';
277
- for (const { comment, error } of failedComments) {
278
- finalBody += '\n\n---\n\n';
279
- finalBody += formatCommentMarkdown(comment, error);
280
- }
281
- }
282
-
283
- await github.rest.issues.createComment({
284
- owner: context.repo.owner,
285
- repo: context.repo.repo,
286
- issue_number: prNumber,
287
- body: finalBody
288
- });
289
- }
290
-
291
- function formatComment(comment) {
292
- let body = comment.content || '';
293
-
294
- // Add code suggestion if available
295
- if (comment.suggestion_code && comment.existing_code) {
296
- body += '\n\n**Suggestion:**\n';
297
- body += fencedBlock(comment.suggestion_code, 'suggestion');
298
- }
299
-
300
- return body;
301
- }
302
-
303
- function formatCommentMarkdown(comment, error) {
304
- let md = `### 📄 \`${comment.path}\``;
305
- if (comment.start_line && comment.end_line) {
306
- md += ` (L${comment.start_line}-L${comment.end_line})`;
307
- }
308
- md += '\n\n';
309
- if (error) {
310
- md += `⚠️ GitHub could not post this as an inline comment: ${error}\n\n`;
311
- }
312
- md += comment.content || '';
313
-
314
- if (comment.suggestion_code && comment.existing_code) {
315
- md += '\n\n<details><summary>💡 Suggested Change</summary>\n\n';
316
- md += '**Before:**\n' + fencedBlock(comment.existing_code) + '\n\n';
317
- md += '**After:**\n' + fencedBlock(comment.suggestion_code) + '\n\n';
318
- md += '</details>';
319
- }
320
-
321
- return md;
322
- }
323
-
324
- function buildSummaryBody(totalCount, inlineCount, summaryCount, warnings) {
325
- let body = `🔍 **OpenCodeReview** found **${totalCount}** issue(s) in this PR.`;
326
- if (totalCount > 0) {
327
- body += `\n- ✅ ${inlineCount} posted as inline comment(s)`;
328
- body += `\n- 📝 ${summaryCount} posted as summary`;
329
- }
330
- if (warnings.length > 0) {
331
- body += `\n\n⚠️ ${warnings.length} warning(s) occurred during review.`;
332
- }
333
- return body;
334
- }
335
-
336
- function formatSummaryComments(summaryComments) {
337
- let body = '';
338
- for (const { comment } of summaryComments) {
339
- body += '\n\n---\n\n';
340
- body += formatCommentMarkdown(comment);
341
- }
342
- return body;
343
- }
344
-
345
- function fencedBlock(content, language = '') {
346
- const text = String(content || '');
347
- const fence = safeFence(text);
348
- let block = fence + language + '\n' + text;
349
- if (!text.endsWith('\n')) block += '\n';
350
- return block + fence;
351
- }
352
-
353
- function safeFence(content) {
354
- const matches = String(content || '').match(/`+/g) || [];
355
- const maxTicks = matches.reduce((max, ticks) => Math.max(max, ticks.length), 0);
356
- return '`'.repeat(Math.max(3, maxTicks + 1));
357
- }