mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: add GitHub Actions workflow for @hapi mention auto-response
Add Codex-powered workflow that automatically responds to @hapi mentions in issue and PR review comments, with permission checks and skip conditions.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# HAPI Mention Response Assistant
|
||||
|
||||
Respond to @hapi mentions in issue comments and PR review comments. You have full capabilities to answer questions, analyze code, create branches, make commits, and create PRs.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `TRIGGERING_COMMENT_ID` - ID of the comment that triggered this workflow
|
||||
- `TARGET_NUMBER` - Issue or PR number
|
||||
- `EVENT_TYPE` - "issue_comment" or "pr_review_comment"
|
||||
- `IS_PR` - "true" if the context is a PR
|
||||
|
||||
## Context Loading (required)
|
||||
|
||||
```bash
|
||||
comment_id="$TRIGGERING_COMMENT_ID"
|
||||
target_number="$TARGET_NUMBER"
|
||||
is_pr="$IS_PR"
|
||||
repo=$(jq -r '.repository.full_name' "$GITHUB_EVENT_PATH")
|
||||
|
||||
# Load triggering comment
|
||||
comment_body=$(jq -r '.comment.body' "$GITHUB_EVENT_PATH")
|
||||
comment_author=$(jq -r '.comment.user.login' "$GITHUB_EVENT_PATH")
|
||||
|
||||
# Load issue/PR context
|
||||
if [ "$is_pr" = "true" ]; then
|
||||
gh pr view "$target_number" -R "$repo" --json number,title,body,labels,author,baseRefName,headRefName
|
||||
else
|
||||
gh issue view "$target_number" -R "$repo" --json number,title,body,labels,author,comments
|
||||
fi
|
||||
```
|
||||
|
||||
## Skip Conditions
|
||||
|
||||
**Exit immediately if any:**
|
||||
- Comment body is empty/whitespace only
|
||||
- Mention appears only in a code block or quote
|
||||
|
||||
## Phase 1: Gather Context
|
||||
|
||||
1. **Read** `AGENTS.md` for project context
|
||||
2. **Extract** the user's request from the comment (text after `@hapi`)
|
||||
3. **Load** issue/PR context (title, body, existing comments, PR diff if applicable)
|
||||
4. **Research** the codebase as needed
|
||||
|
||||
## Phase 2: Intent Classification
|
||||
|
||||
| Intent | Indicators | Action |
|
||||
|--------|------------|--------|
|
||||
| `question` | "how", "what", "why", "?" | Answer with codebase evidence |
|
||||
| `fix` | "fix", "bug", "error" | Create branch, commit fix, open PR |
|
||||
| `feature` | "implement", "add", "create" | Create branch, implement, open PR |
|
||||
| `review` | "review", "check", "look at" | Analyze and provide feedback |
|
||||
| `clarification` | Need more info | Ask specific questions |
|
||||
|
||||
**Default:** If ambiguous, choose `question` (safer).
|
||||
|
||||
## Phase 3: Execute
|
||||
|
||||
### For `question` intent:
|
||||
- Research codebase thoroughly
|
||||
- Provide accurate answer with `file:line` references
|
||||
- Post as comment reply
|
||||
|
||||
### For `fix` or `feature` intent:
|
||||
|
||||
1. **Create branch** from `dev`:
|
||||
```bash
|
||||
branch_name="hapi-bot/$target_number-$(echo "$comment_id" | tail -c 8)"
|
||||
git checkout -b "$branch_name" origin/dev
|
||||
```
|
||||
|
||||
2. **Implement changes** following repo conventions:
|
||||
- TypeScript strict mode
|
||||
- 4-space indentation
|
||||
- Run `bun typecheck` before committing
|
||||
|
||||
3. **Commit** with clear message:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: description
|
||||
|
||||
Requested by @$comment_author in #$target_number"
|
||||
```
|
||||
|
||||
4. **Push** and create PR targeting `dev`:
|
||||
```bash
|
||||
git push -u origin "$branch_name"
|
||||
gh pr create \
|
||||
--base dev \
|
||||
--title "fix: description" \
|
||||
--body "## Summary
|
||||
Description of changes
|
||||
|
||||
## Context
|
||||
Requested by @$comment_author in [comment](https://github.com/$repo/issues/$target_number#issuecomment-$comment_id)
|
||||
|
||||
---
|
||||
*HAPI Bot* <!-- reply-to:$comment_id -->"
|
||||
```
|
||||
|
||||
### For `review` intent:
|
||||
- Analyze the code/PR as requested
|
||||
- Provide constructive feedback with evidence
|
||||
|
||||
### For `clarification` intent:
|
||||
- List specific questions (max 4)
|
||||
- Explain what information is needed
|
||||
|
||||
## Response Guidelines
|
||||
|
||||
- **Accuracy**: Only state verifiable facts. Say "not found" if uncertain.
|
||||
- **Evidence**: Reference files with `path:line` format.
|
||||
- **Language**: Match the comment's language (Chinese/English).
|
||||
- **Brevity**: Be concise but complete.
|
||||
|
||||
## Response Format
|
||||
|
||||
```markdown
|
||||
[Your response here]
|
||||
|
||||
[If created a PR: **PR Created:** #NUMBER]
|
||||
|
||||
---
|
||||
*HAPI Bot* <!-- reply-to:COMMENT_ID -->
|
||||
```
|
||||
|
||||
## Post to GitHub (MANDATORY)
|
||||
|
||||
```bash
|
||||
gh issue comment "$target_number" -R "$repo" --body "YOUR_RESPONSE
|
||||
|
||||
---
|
||||
*HAPI Bot* <!-- reply-to:$comment_id -->"
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Branch discipline**: Always branch from `dev`, always PR to `dev`
|
||||
- **No force push**: Never use `--force`
|
||||
- **No direct commits**: Always use PRs for code changes
|
||||
- **Verify before commit**: Run `bun typecheck`
|
||||
- **Size limits**: For large changes (>10 files), describe plan first and ask confirmation
|
||||
- **DO NOT** speculate - only state what you verified in codebase
|
||||
@@ -0,0 +1,135 @@
|
||||
name: Codex Mention Response
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: codex-mention-${{ github.event.comment.id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
mention-response:
|
||||
if: |
|
||||
contains(github.event.comment.body, '@hapi') &&
|
||||
!endsWith(github.event.comment.user.login, '[bot]')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Check user permissions and skip conditions
|
||||
id: check_skip
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const marker = "*HAPI Bot*";
|
||||
const triggeringCommentId = context.payload.comment.id;
|
||||
const replyMarker = `<!-- reply-to:${triggeringCommentId} -->`;
|
||||
const allowedLogins = (process.env.HAPI_BOT_LOGINS || "github-actions[bot]")
|
||||
.split(",").map(v => v.trim()).filter(Boolean);
|
||||
|
||||
// Check user write permission
|
||||
const commentAuthor = context.payload.comment.user.login;
|
||||
const { data: permissionData } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: commentAuthor
|
||||
});
|
||||
const permission = permissionData.permission;
|
||||
const hasWriteAccess = ["admin", "write"].includes(permission);
|
||||
if (!hasWriteAccess) {
|
||||
core.setOutput("should_skip", "true");
|
||||
core.info(`User ${commentAuthor} has '${permission}' permission; write access required. Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine context type
|
||||
const isIssueComment = context.eventName === "issue_comment";
|
||||
|
||||
// Get issue/PR number and labels
|
||||
let targetNumber, labels, isPR;
|
||||
if (isIssueComment) {
|
||||
targetNumber = context.payload.issue.number;
|
||||
labels = context.payload.issue.labels || [];
|
||||
isPR = !!context.payload.issue.pull_request;
|
||||
} else {
|
||||
targetNumber = context.payload.pull_request.number;
|
||||
labels = context.payload.pull_request.labels || [];
|
||||
isPR = true;
|
||||
}
|
||||
|
||||
// Check for bot-skip label
|
||||
const hasBotSkip = labels.some(l => l.name === "bot-skip");
|
||||
if (hasBotSkip) {
|
||||
core.setOutput("should_skip", "true");
|
||||
core.info("bot-skip label found; skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for existing bot reply to this specific comment
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: targetNumber,
|
||||
per_page: 100
|
||||
}
|
||||
);
|
||||
|
||||
const hasReply = comments.some(comment => {
|
||||
const body = comment?.body || "";
|
||||
if (!body.includes(marker) || !body.includes(replyMarker)) return false;
|
||||
const user = comment.user;
|
||||
return user?.type === "Bot" && allowedLogins.includes(user.login);
|
||||
});
|
||||
|
||||
if (hasReply) {
|
||||
core.setOutput("should_skip", "true");
|
||||
core.info(`Existing reply to comment ${triggeringCommentId} found; skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput("should_skip", "false");
|
||||
core.setOutput("triggering_comment_id", triggeringCommentId);
|
||||
core.setOutput("target_number", targetNumber);
|
||||
core.setOutput("event_type", isIssueComment ? "issue_comment" : "pr_review_comment");
|
||||
core.setOutput("is_pr", isPR ? "true" : "false");
|
||||
env:
|
||||
HAPI_BOT_LOGINS: ${{ vars.HAPI_BOT_LOGINS }}
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.check_skip.outputs.should_skip != 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: dev
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch all branches
|
||||
if: steps.check_skip.outputs.should_skip != 'true'
|
||||
run: git fetch --all
|
||||
|
||||
- name: Run Codex for Mention Response
|
||||
if: steps.check_skip.outputs.should_skip != 'true'
|
||||
uses: openai/codex-action@v1
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
TRIGGERING_COMMENT_ID: ${{ steps.check_skip.outputs.triggering_comment_id }}
|
||||
TARGET_NUMBER: ${{ steps.check_skip.outputs.target_number }}
|
||||
EVENT_TYPE: ${{ steps.check_skip.outputs.event_type }}
|
||||
IS_PR: ${{ steps.check_skip.outputs.is_pr }}
|
||||
with:
|
||||
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
||||
responses-api-endpoint: ${{ secrets.OPENAI_BASE_URL }}
|
||||
model: ${{ vars.OPENAI_MODEL || 'gpt-5.2-codex' }}
|
||||
effort: ${{ vars.OPENAI_EFFORT || 'high' }}
|
||||
sandbox: danger-full-access
|
||||
safety-strategy: drop-sudo
|
||||
prompt-file: .github/prompts/codex-mention-response.md
|
||||
allow-bots: true
|
||||
Reference in New Issue
Block a user