From 5500b9b291804b99d727ab9581f10bf7f8f03349 Mon Sep 17 00:00:00 2001 From: Birditch Date: Fri, 10 Jul 2026 20:17:26 +0800 Subject: [PATCH] Improve automated PR review feedback --- .github/PR_AUTOMATION.md | 4 +- .github/workflows/chatgpt-pr-review.yml | 113 ++++++++++++++---------- .github/workflows/pr-gate.yml | 2 +- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/.github/PR_AUTOMATION.md b/.github/PR_AUTOMATION.md index b3439c6..d5b99cb 100644 --- a/.github/PR_AUTOMATION.md +++ b/.github/PR_AUTOMATION.md @@ -8,7 +8,7 @@ Every PR should fill in the template and check the `SIGN` declaration. The `PR G - The PR has a meaningful title and summary. - The `SIGN` declaration is checked. -- Third-party PRs do not change privileged automation, packaging, script, or binary artifact paths without maintainer handling. +- Changes to privileged automation, packaging, script, or binary artifact paths are surfaced as warnings for maintainer review. The gate uses `pull_request_target` but does not check out or execute contributor code. @@ -28,6 +28,8 @@ or: The workflow reads PR metadata and diff through the GitHub API, then posts or updates one bot comment with the preliminary review. It does not execute pull request code. +The bot first posts a review-in-progress status, then updates the same comment with the result or a visible failure reason. Reviews favor accepting useful open-source contributions: only concrete, high-confidence correctness, security, CI, or release problems are treated as blockers; style and optional improvements remain non-blocking suggestions. + ## OpenAI Configuration Set these repository secrets or variables: diff --git a/.github/workflows/chatgpt-pr-review.yml b/.github/workflows/chatgpt-pr-review.yml index d9ba557..795a6f2 100644 --- a/.github/workflows/chatgpt-pr-review.yml +++ b/.github/workflows/chatgpt-pr-review.yml @@ -9,7 +9,7 @@ on: permissions: contents: read issues: write - pull-requests: read + pull-requests: write jobs: review: @@ -49,13 +49,6 @@ jobs: process.exit(0); } - if (!process.env.OPENAI_API_KEY) { - fs.appendFileSync( - process.env.GITHUB_STEP_SUMMARY, - '# ChatGPT PR Review\n\nOPENAI_API_KEY is not configured, so the automatic review was skipped.\n'); - process.exit(0); - } - const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); const githubHeaders = { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, @@ -133,10 +126,15 @@ jobs: 'Content-Type': 'application/json', }; const system = [ - 'You are doing a preliminary pull request review for AndroidTreeView.', - 'Focus on correctness, security, CI risk, release risk, and missing tests.', - 'Be concise. Write in Simplified Chinese unless a quoted code identifier is English.', - 'Do not claim approval. This is an initial automated review only.', + 'You are doing a friendly preliminary pull request review for the open-source AndroidTreeView project.', + 'Favor merging useful contributions when they do not introduce a concrete correctness, security, CI, or release blocker.', + 'Only call something blocking when the diff provides high-confidence evidence of a real user-facing failure, security vulnerability, data loss, broken build, or broken release.', + 'Do not block on style preferences, optional refactors, speculative risks, pre-existing problems, minor documentation gaps, or missing non-critical tests.', + 'Keep optional improvements under non-blocking suggestions and do not invent requirements that are not documented by the repository.', + 'When no blocking issue exists, clearly conclude 建议通过 even if you have minor suggestions.', + 'When reporting a blocker, cite the file and relevant code, explain the concrete impact, and state the smallest reasonable fix.', + 'Write concise Simplified Chinese with exactly these sections: ## 结论, ## 阻塞问题, and ## 非阻塞建议.', + 'Use 无 when a section has no findings. This remains an automated recommendation, not a formal maintainer approval.', ].join(' '); const responsesBody = { @@ -208,44 +206,69 @@ jobs: } } - const pr = await gh(`/repos/${owner}/${repo}/pulls/${prNumber}`); - const files = await listFiles(); - const diff = await ghText(`/repos/${owner}/${repo}/pulls/${prNumber}`, 'application/vnd.github.v3.diff'); - const fileSummary = files - .map(file => `${file.status} ${file.filename} (+${file.additions}/-${file.deletions})`) - .join('\n'); - const truncatedDiff = diff.length > 60000 - ? `${diff.slice(0, 60000)}\n\n[Diff truncated for automated review.]` - : diff; - - const prompt = [ - `PR #${pr.number}: ${pr.title}`, - `Author: ${pr.user?.login} (${pr.author_association})`, - `Base: ${pr.base.ref}`, - `Head: ${pr.head.label}`, - '', - 'PR body:', - pr.body || '(empty)', - '', - 'Changed files:', - fileSummary || '(none)', - '', - 'Unified diff:', - truncatedDiff, - ].join('\n'); - - const review = await callOpenAI(prompt); - const commentBody = [ + const statusBody = [ '## ChatGPT PR 初审', '', - review, + `状态:正在审查 PR #${prNumber},请稍候。`, '', - '---', - '这是自动初审,不代表维护者批准。第三方 PR 的代码没有在此 workflow 中执行。', + '完成后,本评论会自动更新为审核结论。', ].join('\n'); + await upsertComment(statusBody); + + try { + if (!process.env.OPENAI_API_KEY) { + throw new Error('OPENAI_API_KEY is not configured.'); + } + + const pr = await gh(`/repos/${owner}/${repo}/pulls/${prNumber}`); + const files = await listFiles(); + const diff = await ghText(`/repos/${owner}/${repo}/pulls/${prNumber}`, 'application/vnd.github.v3.diff'); + const fileSummary = files + .map(file => `${file.status} ${file.filename} (+${file.additions}/-${file.deletions})`) + .join('\n'); + const truncatedDiff = diff.length > 60000 + ? `${diff.slice(0, 60000)}\n\n[Diff truncated for automated review.]` + : diff; - await upsertComment(commentBody); - fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${commentBody}\n`); + const prompt = [ + `PR #${pr.number}: ${pr.title}`, + `Author: ${pr.user?.login} (${pr.author_association})`, + `Base: ${pr.base.ref}`, + `Head: ${pr.head.label}`, + '', + 'PR body:', + pr.body || '(empty)', + '', + 'Changed files:', + fileSummary || '(none)', + '', + 'Unified diff:', + truncatedDiff, + ].join('\n'); + + const review = await callOpenAI(prompt); + const commentBody = [ + '## ChatGPT PR 初审', + '', + review, + '', + '---', + '这是自动初审建议,不代表维护者正式批准。第三方 PR 的代码没有在此 workflow 中执行。', + ].join('\n'); + + await upsertComment(commentBody); + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${commentBody}\n`); + } catch (error) { + const failureBody = [ + '## ChatGPT PR 初审', + '', + '状态:审查失败。', + '', + '自动审核服务或 GitHub 评论接口调用失败。请检查 Actions 运行日志,修复后重新发送 `/chatgpt-review`。', + ].join('\n'); + await upsertComment(failureBody); + throw error; + } })().catch(error => { console.error(`::error::${error.message}`); process.exit(1); diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index 9c49f4f..32f2d3f 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -111,7 +111,7 @@ jobs: .filter(name => sensitivePatterns.some(pattern => pattern.test(name))); if (!trustedAuthor && sensitiveFiles.length > 0) { - failures.push(`Third-party PR changes security-sensitive files: ${sensitiveFiles.join(', ')}`); + warnings.push(`Third-party PR changes sensitive files and needs maintainer review: ${sensitiveFiles.join(', ')}`); } else if (sensitiveFiles.length > 0) { warnings.push(`Sensitive files changed by trusted author: ${sensitiveFiles.join(', ')}`); }