From c35afec09ae20535fb3d7fee4b8f7dd1998c74f3 Mon Sep 17 00:00:00 2001 From: CloakHQ Date: Fri, 10 Apr 2026 23:22:48 +0200 Subject: [PATCH] feat: add GitHub Models issue knowledge responder Auto-responds to new issues with related docs and past issues. Uses actions/ai-inference with GPT-4.1 (free GitHub Models). 5 search strategies: title, keywords, body terms, labels, error msgs. No external API keys needed. --- .github/prompts/issue-responder.prompt.yml | 62 ++++++++ .../workflows/issue-knowledge-responder.yml | 148 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 .github/prompts/issue-responder.prompt.yml create mode 100644 .github/workflows/issue-knowledge-responder.yml diff --git a/.github/prompts/issue-responder.prompt.yml b/.github/prompts/issue-responder.prompt.yml new file mode 100644 index 0000000..02661c4 --- /dev/null +++ b/.github/prompts/issue-responder.prompt.yml @@ -0,0 +1,62 @@ +name: issue-knowledge-responder +description: First-responder bot that finds related docs and past issues for new GitHub issues +model: openai/gpt-4.1 +modelParameters: + maxCompletionTokens: 1500 + temperature: 0.3 +messages: + - role: system + content: | + You are a first-responder bot for CloakBrowser, an open-source stealth Chromium wrapper. + Your ONLY job: find related resources from the provided context and point the user to them. + You are NOT a maintainer. You do NOT answer questions yourself. + + Rules: + - NEVER answer the question yourself. Only point to existing resources. + - NEVER reveal internal patch details, CDP stealth methods, or bypass techniques. + - Keep it short. 5-10 lines max. + - Only include resources that are ACTUALLY relevant, not vaguely similar. + - If the issue is about financial, government, or healthcare targets, output RESPOND: NO + - Maximum 5 related issues. Prefer closed+resolved over open. + + - role: user + content: | + ## The new issue + {{issue_body}} + + ## Past issues found by search + {{past_issues}} + + ## Past issue details (body + comments from top matches) + {{issue_details}} + + ## README.md (excerpt) + {{readme}} + + ## js/README.md (excerpt) + {{js_readme}} + + ## CHANGELOG.md (recent) + {{changelog}} + + ## Your task + + Based on the context above, decide: + + **If you found related resources** (doc sections, past issues, or both), output EXACTLY this format: + + RESPOND: YES + --- + While you wait for the team to respond, I found some resources that might be related: + + **From the docs:** + - [description of relevant section] (in `README.md` / `js/README.md`) + + **Related issues:** + - #123 — [title] (closed/open) — [one-line summary of resolution or discussion] + + Hope this helps! The team will follow up soon. + --- + + **If you found NOTHING relevant**, output EXACTLY: + RESPOND: NO diff --git a/.github/workflows/issue-knowledge-responder.yml b/.github/workflows/issue-knowledge-responder.yml new file mode 100644 index 0000000..70ed9cc --- /dev/null +++ b/.github/workflows/issue-knowledge-responder.yml @@ -0,0 +1,148 @@ +name: Issue Knowledge Responder + +on: + issues: + types: [opened] + +jobs: + respond: + if: github.event.issue.user.type != 'Bot' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + issues: write + models: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Gather context + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + run: | + mkdir -p /tmp/ctx + + # 1. Read the full issue + gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title,body,labels --jq ' + "Title: " + .title + "\n\nBody:\n" + (.body // "No body") + "\n\nLabels: " + ([.labels[].name] | join(", ")) + ' > /tmp/ctx/issue_body.txt + + # 2. Extract keywords from title + body + TITLE=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title --jq '.title') + BODY_TEXT=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json body --jq '.body // ""' | head -20) + STOPWORDS='^(the|a|an|is|in|on|at|to|for|of|with|and|or|not|bug|issue|error|problem|help|please|how|why|what|does|can|i|my|me|it|this|that|when|from|have|has|been|are|was|were|be|do|did|just|but|if|so|no|yes|all|any|some|like|get|got|use|using|used|try|tried|also|want|need|would|could|should|about|into|after|before|only|very|too|more|most|much|own|same|other|than|then|there|here|each|every|both|few|many|such|these|those|its|new|old|first|last|long|great|little|right|big|high|different|small|large|next|early|young|important|public|bad|same|able)$' + TITLE_KW=$(echo "$TITLE" | tr -cs 'a-zA-Z0-9' ' ' | tr ' ' '\n' | \ + grep -v -i -E "$STOPWORDS" | head -5 | tr '\n' ' ') + BODY_KW=$(echo "$BODY_TEXT" | tr -cs 'a-zA-Z0-9' ' ' | tr ' ' '\n' | \ + grep -v -i -E "$STOPWORDS" | sort | uniq -c | sort -rn | awk '{print $2}' | head -5 | tr '\n' ' ') + + # 3. Search past issues — 5 strategies, deduplicated + # A: full title + gh issue list --repo "$REPO" --state all --search "$TITLE" --limit 10 \ + --json number,title,state --jq '.[] | "#\(.number) — \(.title) (\(.state))"' \ + 2>/dev/null > /tmp/ctx/search_results.txt || true + + # B: title keywords + gh issue list --repo "$REPO" --state all --search "$TITLE_KW" --limit 10 \ + --json number,title,state --jq '.[] | "#\(.number) — \(.title) (\(.state))"' \ + 2>/dev/null >> /tmp/ctx/search_results.txt || true + + # C: body keywords (most frequent terms from body) + if [ -n "$BODY_KW" ]; then + gh issue list --repo "$REPO" --state all --search "$BODY_KW" --limit 10 \ + --json number,title,state --jq '.[] | "#\(.number) — \(.title) (\(.state))"' \ + 2>/dev/null >> /tmp/ctx/search_results.txt || true + fi + + # D: search by labels if the new issue has any + LABELS=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json labels --jq '[.labels[].name] | join(",")') + if [ -n "$LABELS" ]; then + IFS=',' read -ra LABEL_ARR <<< "$LABELS" + for LBL in "${LABEL_ARR[@]}"; do + gh issue list --repo "$REPO" --state all --label "$LBL" --limit 10 \ + --json number,title,state --jq '.[] | "#\(.number) — \(.title) (\(.state))"' \ + 2>/dev/null >> /tmp/ctx/search_results.txt || true + done + fi + + # E: search for error messages / code snippets in body (first backtick block or quoted line) + ERROR_MSG=$(echo "$BODY_TEXT" | grep -oP '`[^`]{5,80}`' | head -1 | tr -d '`') + if [ -n "$ERROR_MSG" ]; then + gh issue list --repo "$REPO" --state all --search "$ERROR_MSG" --limit 5 \ + --json number,title,state --jq '.[] | "#\(.number) — \(.title) (\(.state))"' \ + 2>/dev/null >> /tmp/ctx/search_results.txt || true + fi + + # Deduplicate, exclude self, keep top 20 + sort -u /tmp/ctx/search_results.txt | \ + grep -v "^#${ISSUE_NUMBER} " | head -20 > /tmp/ctx/past_issues.txt + + # 4. Read top 7 issue details (body + comments) + echo "" > /tmp/ctx/issue_details.txt + for NUM in $(grep -oP '#\K[0-9]+' /tmp/ctx/past_issues.txt | head -7); do + gh issue view "$NUM" --repo "$REPO" --json title,state,body,comments --jq ' + "### #'"$NUM"' — " + .title + " (" + .state + ")\n" + + (.body // "")[:500] + "\n\nComments:\n" + + ([.comments[:3][] | .body[:300]] | join("\n---\n")) + ' >> /tmp/ctx/issue_details.txt 2>/dev/null || true + echo -e "\n---\n" >> /tmp/ctx/issue_details.txt + done + + # 5. Read docs + head -200 README.md > /tmp/ctx/readme.txt 2>/dev/null || echo "No README" > /tmp/ctx/readme.txt + head -150 js/README.md > /tmp/ctx/js_readme.txt 2>/dev/null || echo "No JS README" > /tmp/ctx/js_readme.txt + head -100 CHANGELOG.md > /tmp/ctx/changelog.txt 2>/dev/null || echo "No CHANGELOG" > /tmp/ctx/changelog.txt + + # Debug: show what we gathered + echo "=== Issue ===" && head -5 /tmp/ctx/issue_body.txt + echo "=== Past issues ===" && cat /tmp/ctx/past_issues.txt + echo "=== Keywords: $KEYWORDS ===" + + - name: AI analysis + id: ai + uses: actions/ai-inference@v1 + with: + prompt-file: .github/prompts/issue-responder.prompt.yml + file_input: | + issue_body: /tmp/ctx/issue_body.txt + past_issues: /tmp/ctx/past_issues.txt + issue_details: /tmp/ctx/issue_details.txt + readme: /tmp/ctx/readme.txt + js_readme: /tmp/ctx/js_readme.txt + changelog: /tmp/ctx/changelog.txt + + - name: Parse and post + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RESPONSE=$(cat "${{ steps.ai.outputs.response-file }}") + echo "=== AI Response ===" + echo "$RESPONSE" + + if echo "$RESPONSE" | grep -q "RESPOND: YES"; then + # Extract content between --- markers + COMMENT=$(echo "$RESPONSE" | sed -n '/^---$/,/^---$/p' | sed '1d;$d') + + if [ -z "$COMMENT" ]; then + # Fallback: everything after "RESPOND: YES", strip --- lines + COMMENT=$(echo "$RESPONSE" | sed '1,/RESPOND: YES/d' | sed '/^---$/d') + fi + + if [ -n "$COMMENT" ]; then + echo "$COMMENT" > /tmp/comment.txt + gh issue comment "${{ github.event.issue.number }}" \ + --repo "${{ github.repository }}" \ + --body-file /tmp/comment.txt + echo "✅ Comment posted" + else + echo "⚠️ RESPOND: YES but empty comment body, skipping" + fi + else + echo "ℹ️ No relevant resources found, staying silent" + fi