mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Closes #10. A GitHub Action bot with no server: one triage comment per new issue (classification, missing-info list, grounded first answer), answers comments that @artex-bot, and a first-look summary checklist on opened pull requests. Comment-only, never an approving review. Skips the maintainer, bots, and itself; exits cleanly without the API key; all model inputs are size-capped.
123 lines
5.0 KiB
Bash
123 lines
5.0 KiB
Bash
#!/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"
|