mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Add support for PR live review
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# HAPI PR Review Assistant
|
||||
|
||||
Review newly opened pull requests for the HAPI project and provide a concise, high-signal review comment.
|
||||
Review opened or updated pull requests for the HAPI project and provide a concise, high-signal review comment.
|
||||
|
||||
## Security
|
||||
|
||||
@@ -23,31 +23,56 @@ Repo rules: TypeScript strict; Bun workspaces (run `bun` from repo root); path a
|
||||
|
||||
## PR Context (required)
|
||||
|
||||
Before any analysis, load PR metadata and diff from the GitHub Actions event payload.
|
||||
Before any analysis, load PR metadata, latest head SHA, and diff from the GitHub Actions event payload.
|
||||
|
||||
Workflow-provided env:
|
||||
- `CURRENT_HEAD_SHA` - PR head SHA for this run
|
||||
- `LATEST_BOT_REVIEW_ID` - most recent prior HAPI Bot review id, if any
|
||||
- `LATEST_BOT_REVIEW_COMMIT` - commit SHA reviewed by that prior HAPI Bot review, if any
|
||||
- `IS_FOLLOW_UP_REVIEW` - `true` when contributor pushed new commits after the last HAPI Bot review
|
||||
|
||||
```bash
|
||||
pr_number=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")
|
||||
repo=$(jq -r '.repository.full_name' "$GITHUB_EVENT_PATH")
|
||||
gh pr view "$pr_number" -R "$repo" --json number,title,body,labels,author,additions,deletions,changedFiles,files
|
||||
current_head_sha="${CURRENT_HEAD_SHA:-$(jq -r '.pull_request.head.sha' "$GITHUB_EVENT_PATH")}"
|
||||
latest_bot_review_id="${LATEST_BOT_REVIEW_ID:-}"
|
||||
latest_bot_review_commit="${LATEST_BOT_REVIEW_COMMIT:-}"
|
||||
is_follow_up_review="${IS_FOLLOW_UP_REVIEW:-false}"
|
||||
|
||||
gh pr view "$pr_number" -R "$repo" --json number,title,body,labels,author,additions,deletions,changedFiles,files,headRefOid
|
||||
gh pr diff "$pr_number" -R "$repo"
|
||||
|
||||
if [ "$is_follow_up_review" = "true" ] && [ -n "$latest_bot_review_id" ]; then
|
||||
gh api "repos/$repo/pulls/$pr_number/reviews/$latest_bot_review_id"
|
||||
gh api "repos/$repo/pulls/$pr_number/reviews/$latest_bot_review_id/comments"
|
||||
|
||||
if [ -n "$latest_bot_review_commit" ] && [ "$latest_bot_review_commit" != "$current_head_sha" ]; then
|
||||
gh api -H "Accept: application/vnd.github.v3.diff" \
|
||||
"repos/$repo/compare/$latest_bot_review_commit...$current_head_sha"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
## Task
|
||||
|
||||
1. **Load context (progressive)**: `README.md`, `AGENTS.md`, then only needed package README/source files.
|
||||
2. **Review the PR diff**: correctness, security, regressions, data loss, performance, and maintainability.
|
||||
3. **Check tests**: note missing or inadequate coverage.
|
||||
4. **Respond** with an evidence-based review comment (no code changes).
|
||||
2. **Determine review mode**: `initial` when no prior HAPI Bot review exists for another commit, otherwise `follow-up after new commits`.
|
||||
3. **Review the latest PR diff in full**: correctness, security, regressions, data loss, performance, and maintainability.
|
||||
4. **Follow-up context**: when `IS_FOLLOW_UP_REVIEW=true`, use the previous HAPI Bot review and compare diff only as context for what changed since the last bot pass. Do not limit the review to those changes.
|
||||
5. **Check tests**: note missing or inadequate coverage.
|
||||
6. **Respond** with an evidence-based review comment (no code changes).
|
||||
|
||||
## Response Guidelines
|
||||
|
||||
- **Findings first**: order by severity (Blocker/Major/Minor/Nit).
|
||||
- **Mode line**: summary must start with `Review mode: initial` or `Review mode: follow-up after new commits`.
|
||||
- **Evidence**: cite specific files and line numbers using `path:line`.
|
||||
- **No speculation**: if uncertain, say so; if not found, say “Not found in repo/docs”.
|
||||
- **Missing info**: ask only when required; max 4 questions.
|
||||
- **Language**: match the PR’s language (Chinese or English); if mixed, use the dominant language.
|
||||
- **Signature**: end with `*HAPI Bot*`.
|
||||
- **Diff focus**: only comment on added/modified lines; use unchanged code only for context.
|
||||
- **Fresh-head only**: before posting, re-fetch live PR head SHA; if it differs from `CURRENT_HEAD_SHA`, stop without posting a stale review.
|
||||
- **Attribution**: report only issues introduced or directly triggered by the diff; anchor comments to diff lines, citing related context if needed.
|
||||
- **High signal**: if confidence < 80%, do not report; ask a question if needed.
|
||||
- **No praise**: report issues and risks only.
|
||||
@@ -68,6 +93,7 @@ gh pr diff "$pr_number" -R "$repo"
|
||||
- ...
|
||||
|
||||
**Summary**
|
||||
- Must begin with the review mode line
|
||||
- If no issues: explicitly say so and mention residual risks/testing gaps
|
||||
|
||||
**Testing**
|
||||
@@ -75,30 +101,44 @@ gh pr diff "$pr_number" -R "$repo"
|
||||
|
||||
## Post Response to Github
|
||||
|
||||
### Inline Comments
|
||||
|
||||
For each validated issue, create an inline comment:
|
||||
Submit exactly one review for this run. Use a single atomic `create review` API call so summary and inline comments stay attached to the same `CURRENT_HEAD_SHA`.
|
||||
|
||||
```bash
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
|
||||
-f body="**[SEVERITY]** [ISSUE-TYPE] Brief description
|
||||
|
||||
**Why this is a problem**: Detailed explanation.
|
||||
|
||||
**Suggested fix**:
|
||||
\`\`\`{language}
|
||||
// Corrected code here
|
||||
\`\`\`" \
|
||||
-f commit_id="{LATEST_COMMIT_SHA}" \
|
||||
-f path="{FILE_PATH}" \
|
||||
-f line={LINE_NUMBER} \
|
||||
-f side="RIGHT"
|
||||
live_head_sha=$(gh pr view "$pr_number" -R "$repo" --json headRefOid -q .headRefOid)
|
||||
if [ "$live_head_sha" != "$current_head_sha" ]; then
|
||||
echo "PR head moved from $current_head_sha to $live_head_sha; skip stale review."
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
### Summary Report (MANDATORY)
|
||||
- If there are findings, build one review payload with:
|
||||
- `event: "COMMENT"`
|
||||
- `commit_id: "$current_head_sha"`
|
||||
- `body: "{SUMMARY}"`
|
||||
- `comments: [...]` containing every inline finding comment
|
||||
- If there are no findings, submit a summary-only review with the same `event`, `commit_id`, and `body`.
|
||||
- Prefer writing the JSON payload to a temporary file and posting it with `gh api --input`.
|
||||
|
||||
Submit a comprehensive review summary:
|
||||
Example shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "COMMENT",
|
||||
"commit_id": "CURRENT_HEAD_SHA",
|
||||
"body": "FULL_SUMMARY",
|
||||
"comments": [
|
||||
{
|
||||
"path": "path/to/file.ts",
|
||||
"line": 123,
|
||||
"side": "RIGHT",
|
||||
"body": "**[MAJOR]** ..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
gh pr review --comment --body "{SUMMARY}"
|
||||
gh api "repos/$repo/pulls/$pr_number/reviews" \
|
||||
--method POST \
|
||||
--input /tmp/hapi-pr-review.json
|
||||
```
|
||||
|
||||
@@ -2,11 +2,11 @@ name: Codex PR Review
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, ready_for_review]
|
||||
types: [opened, reopened, ready_for_review, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: codex-pr-review-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pr-review:
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
review_result: ${{ steps.run_codex.outputs.final-message }}
|
||||
|
||||
steps:
|
||||
- name: Check for existing HAPI Bot review
|
||||
- name: Check HAPI Bot review state
|
||||
id: check_bot
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
@@ -32,6 +32,7 @@ jobs:
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
const currentHeadSha = context.payload.pull_request.head.sha;
|
||||
const reviews = await github.paginate(
|
||||
github.rest.pulls.listReviews,
|
||||
{
|
||||
@@ -41,8 +42,8 @@ jobs:
|
||||
per_page: 100
|
||||
}
|
||||
);
|
||||
const hasBot = reviews.some(
|
||||
(review) => {
|
||||
const botReviews = reviews
|
||||
.filter((review) => {
|
||||
if (!(review?.body || "").includes(marker)) {
|
||||
return false;
|
||||
}
|
||||
@@ -51,24 +52,48 @@ jobs:
|
||||
return false;
|
||||
}
|
||||
return allowedLogins.includes(user.login);
|
||||
})
|
||||
.sort((left, right) => {
|
||||
const leftTime = new Date(left.submitted_at || left.created_at || 0).getTime();
|
||||
const rightTime = new Date(right.submitted_at || right.created_at || 0).getTime();
|
||||
if (rightTime !== leftTime) {
|
||||
return rightTime - leftTime;
|
||||
}
|
||||
return (right.id || 0) - (left.id || 0);
|
||||
});
|
||||
const latestBotReview = botReviews[0];
|
||||
const hasReviewForCurrentHead = botReviews.some(
|
||||
(review) => review.commit_id === currentHeadSha
|
||||
);
|
||||
const isFollowUpReview = Boolean(
|
||||
latestBotReview?.commit_id && latestBotReview.commit_id !== currentHeadSha
|
||||
);
|
||||
|
||||
core.setOutput("current_head_sha", currentHeadSha);
|
||||
core.setOutput("has_review_for_current_head", hasReviewForCurrentHead ? "true" : "false");
|
||||
core.setOutput("latest_bot_review_id", latestBotReview ? String(latestBotReview.id) : "");
|
||||
core.setOutput("latest_bot_review_commit", latestBotReview?.commit_id || "");
|
||||
core.setOutput("is_follow_up_review", isFollowUpReview ? "true" : "false");
|
||||
|
||||
if (hasReviewForCurrentHead) {
|
||||
core.info(`Existing HAPI Bot review found for ${currentHeadSha}; skipping.`);
|
||||
} else if (isFollowUpReview) {
|
||||
core.info(
|
||||
`Latest HAPI Bot review was for ${latestBotReview.commit_id}; running follow-up review for ${currentHeadSha}.`
|
||||
);
|
||||
core.setOutput("has_bot", hasBot ? "true" : "false");
|
||||
if (hasBot) {
|
||||
core.info("Existing HAPI Bot review found; skipping.");
|
||||
}
|
||||
env:
|
||||
HAPI_BOT_LOGINS: ${{ vars.HAPI_BOT_LOGINS }}
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.check_bot.outputs.has_bot != 'true'
|
||||
if: steps.check_bot.outputs.has_review_for_current_head != 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Pre-fetch base and head refs
|
||||
if: steps.check_bot.outputs.has_bot != 'true'
|
||||
if: steps.check_bot.outputs.has_review_for_current_head != 'true'
|
||||
run: |
|
||||
git fetch --no-tags origin \
|
||||
${{ github.event.pull_request.base.ref }} \
|
||||
@@ -76,11 +101,15 @@ jobs:
|
||||
|
||||
- name: Run Codex for PR Review
|
||||
id: run_codex
|
||||
if: steps.check_bot.outputs.has_bot != 'true'
|
||||
if: steps.check_bot.outputs.has_review_for_current_head != 'true'
|
||||
uses: openai/codex-action@v1
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
CURRENT_HEAD_SHA: ${{ steps.check_bot.outputs.current_head_sha }}
|
||||
LATEST_BOT_REVIEW_ID: ${{ steps.check_bot.outputs.latest_bot_review_id }}
|
||||
LATEST_BOT_REVIEW_COMMIT: ${{ steps.check_bot.outputs.latest_bot_review_commit }}
|
||||
IS_FOLLOW_UP_REVIEW: ${{ steps.check_bot.outputs.is_follow_up_review }}
|
||||
with:
|
||||
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
||||
responses-api-endpoint: ${{ secrets.OPENAI_BASE_URL }}
|
||||
|
||||
Reference in New Issue
Block a user