mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Merge pull request #12 from BaaRai/bot/issue-pr-bot
Add triage and reply bot for issues and pull requests
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# Lightweight triage and reply bot for issues and pull requests.
|
||||
# Runs inside GitHub Actions; no server involved.
|
||||
#
|
||||
# Triggers (see bot.yml):
|
||||
# - issue opened: exactly one triage comment per issue
|
||||
# - comment containing @artex-bot: answers that comment (works in PR
|
||||
# threads too, since issue_comment fires there)
|
||||
# - pull request opened: one first-look review comment
|
||||
# The bot never posts an approving review and never replies to itself,
|
||||
# to the maintainer, or to other bots (enforced in bot.yml).
|
||||
set -uo pipefail
|
||||
|
||||
MAX_CHARS=8000
|
||||
FOOTER='---
|
||||
🤖 automated reply from the repo bot. A human maintainer reads everything. Ping me in any comment with `@artex-bot`.'
|
||||
|
||||
log() { echo "[bot] $*"; }
|
||||
|
||||
[ -z "${DEEPSEEK_API_KEY:-}" ] && { log "DEEPSEEK_API_KEY secret is not set; skipping."; exit 0; }
|
||||
|
||||
ev=$(cat "$GITHUB_EVENT_PATH")
|
||||
event_name="$GITHUB_EVENT_NAME"
|
||||
repo="$GITHUB_REPOSITORY"
|
||||
|
||||
ask_model() { # $1 = system prompt, $2 = user payload; prints the answer
|
||||
local body="${2:0:$MAX_CHARS}"
|
||||
local req
|
||||
req=$(jq -n --arg sys "$1" --arg body "$body" \
|
||||
'{model: "deepseek-chat",
|
||||
messages: [{role: "system", content: $sys}, {role: "user", content: $body}],
|
||||
max_tokens: 900, temperature: 0.2}')
|
||||
curl -sS --max-time 120 https://api.deepseek.com/chat/completions \
|
||||
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$req" | jq -r '.choices[0].message.content // empty'
|
||||
}
|
||||
|
||||
repo_context() { git ls-files | head -120; }
|
||||
|
||||
post_comment() { # $1 = issue or PR number, $2 = markdown text
|
||||
{ echo "$2"; echo; echo "$FOOTER"; } > /tmp/bot-comment.md
|
||||
if gh issue view "$1" --json pull_request --jq '.pull_request' | grep -q .; then
|
||||
gh pr comment "$1" --body-file /tmp/bot-comment.md
|
||||
else
|
||||
gh issue comment "$1" --body-file /tmp/bot-comment.md
|
||||
fi
|
||||
}
|
||||
|
||||
eyes() { # $1 = issue or PR number
|
||||
gh api -X POST "repos/$repo/issues/$1/reactions" -f content=eyes >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
already_triaged() { # $1 = issue number
|
||||
gh api "repos/$repo/issues/$1/comments" --jq '.[].body' 2>/dev/null \
|
||||
| grep -q 'automated reply from the repo bot'
|
||||
}
|
||||
|
||||
answer=""
|
||||
|
||||
case "$event_name" in
|
||||
issues)
|
||||
number=$(jq -r '.issue.number' <<<"$ev")
|
||||
title=$(jq -r '.issue.title' <<<"$ev")
|
||||
body=$(jq -r '.issue.body // ""' <<<"$ev")
|
||||
if already_triaged "$number"; then log "issue #$number already triaged"; exit 0; fi
|
||||
eyes "$number"
|
||||
answer=$(ask_model \
|
||||
'You are the triage bot of an open-source Go + Next.js repository (an autonomous penetration testing agent system; AGPL-3.0). Classify this report as bug, question, or feature request. If it is a question, answer it concisely using the repository file list as context. If it is a bug report, say what reproduction steps or version information are missing. If it is a feature request, restate it in one sentence. Maximum 180 words, plain markdown, reply in the language of the report, no heading.' \
|
||||
"Issue title: $title
|
||||
|
||||
Issue body:
|
||||
$body
|
||||
|
||||
Repository file list:
|
||||
$(repo_context)")
|
||||
[ -z "$answer" ] && { log "model returned nothing"; exit 0; }
|
||||
post_comment "$number" "$answer"
|
||||
;;
|
||||
|
||||
issue_comment)
|
||||
number=$(jq -r '.issue.number' <<<"$ev")
|
||||
body=$(jq -r '.comment.body' <<<"$ev")
|
||||
question="${body#*@artex-bot}" # strip up to the mention
|
||||
echo "$body" | grep -qi '@artex-bot' || { log "no mention; skipping"; exit 0; }
|
||||
eyes "$number"
|
||||
answer=$(ask_model \
|
||||
'You are the helper bot of an open-source Go + Next.js repository (an autonomous penetration testing agent system; AGPL-3.0). Someone pinged you with a question about this repository. Answer it concisely and only from what the repository file list suggests; say plainly when you are not sure and suggest opening a proper issue. Maximum 180 words, plain markdown, reply in the language of the question.' \
|
||||
"Question:
|
||||
$question
|
||||
|
||||
Repository file list:
|
||||
$(repo_context)")
|
||||
[ -z "$answer" ] && { log "model returned nothing"; exit 0; }
|
||||
post_comment "$number" "$answer"
|
||||
;;
|
||||
|
||||
pull_request)
|
||||
number=$(jq -r '.pull_request.number' <<<"$ev")
|
||||
title=$(jq -r '.pull_request.title' <<<"$ev")
|
||||
body=$(jq -r '.pull_request.body // ""' <<<"$ev")
|
||||
eyes "$number"
|
||||
diff=$(gh pr diff "$number" 2>/dev/null | head -c 4000)
|
||||
answer=$(ask_model \
|
||||
'You are the first-look bot of an open-source Go + Next.js repository (an autonomous penetration testing agent system; AGPL-3.0). Summarize what this pull request changes in two or three sentences, then a short checklist: does it include or update tests, does it touch documentation where needed, does anything look inconsistent with the described goal. You are commenting for information only; you do not approve or request changes. Maximum 180 words, plain markdown.' \
|
||||
"Pull request title: $title
|
||||
|
||||
Pull request description:
|
||||
$body
|
||||
|
||||
Diff (truncated):
|
||||
$diff")
|
||||
[ -z "$answer" ] && { log "model returned nothing"; exit 0; }
|
||||
post_comment "$number" "$answer"
|
||||
;;
|
||||
|
||||
*)
|
||||
log "unhandled event $event_name; skipping"
|
||||
;;
|
||||
esac
|
||||
|
||||
log "done"
|
||||
@@ -0,0 +1,31 @@
|
||||
name: bot
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
reply:
|
||||
# Serves visitors only: never react to the maintainer, to bots, or to itself.
|
||||
if: >
|
||||
github.actor != 'BaaRai' &&
|
||||
github.actor != 'github-actions[bot]' &&
|
||||
github.actor != 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: triage or reply
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT_PATH: ${{ github.event_path }}
|
||||
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
||||
run: bash .github/scripts/bot.sh
|
||||
Reference in New Issue
Block a user