Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/scripts/triage_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import json
import urllib.request
import sys

def get_gemini_response(api_key, prompt):
# Using the stable Gemini 2.5 Flash
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
headers = {'Content-Type': 'application/json'}
data = {
"contents": [{
"parts": [{"text": prompt}]
}],
"generationConfig": {
"response_mime_type": "application/json"
}
}

req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers)
try:
with urllib.request.urlopen(req) as response:
res_data = json.loads(response.read().decode('utf-8'))
return res_data['candidates'][0]['content']['parts'][0]['text']
except urllib.error.HTTPError as e:
print(f"Gemini API Error ({e.code}): {e.reason}", file=sys.stderr)
try:
error_body = e.read().decode('utf-8')
print(f"Error details: {error_body}", file=sys.stderr)
except:
pass
return None
except Exception as e:
print(f"Error calling Gemini API: {e}", file=sys.stderr)
return None

def main():
api_key = os.getenv("GEMINI_API_KEY")
issue_title = os.getenv("ISSUE_TITLE")
issue_body = os.getenv("ISSUE_BODY")

if not api_key:
print("GEMINI_API_KEY not found", file=sys.stderr)
sys.exit(1)

if not issue_title and not issue_body:
print("Error: ISSUE_TITLE and ISSUE_BODY are both empty. Triage skipped.", file=sys.stderr)
sys.exit(0) # Exit gracefully so the workflow doesn't just fail without a reason

prompt = f"""
You are an expert software engineer and triage assistant.
Analyze the following GitHub Issue details and suggest appropriate labels.

Issue Title: {issue_title}
Issue Description: {issue_body}

Triage Criteria:
- Severity:
- priority: p0: Critical issues, crashes, security vulnerabilities (specifically if it mentions "crash" or "exception").
- priority: p1: Important issues that block release.
- priority: p2: Normal priority bugs or improvements.
- priority: p3: Minor enhancements or non-critical fixes.
- priority: p4: Low priority, nice-to-have eventually.

Return a JSON object with a 'labels' key containing an array of suggested label names.
The response MUST be valid JSON.
Example: {{"labels": ["priority: p2", "type: bug"]}}
"""

response_text = get_gemini_response(api_key, prompt)
if response_text:
try:
# Clean up response text in case it has markdown wrapping
if response_text.startswith("```json"):
response_text = response_text.replace("```json", "", 1).replace("```", "", 1).strip()

result = json.loads(response_text)
labels = result.get("labels", [])
# Print labels as a comma-separated string for GitHub Actions
print(",".join(labels))
except Exception as e:
print(f"Error parsing Gemini response: {e}", file=sys.stderr)
print(f"Raw response: {response_text}", file=sys.stderr)
sys.exit(1)
else:
sys.exit(1)

if __name__ == "__main__":
main()
55 changes: 55 additions & 0 deletions .github/workflows/triage-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Issue Triage with Gemini

on:
issues:
types: [opened, edited]
workflow_dispatch:
inputs:
title:
description: 'Mock Issue Title'
default: 'Test Issue'
body:
description: 'Mock Issue Body'
default: 'This is a test issue description.'

jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Run Triage Script
id: run_script
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
ISSUE_TITLE: ${{ github.event.issue.title || github.event.inputs.title }}
ISSUE_BODY: ${{ github.event.issue.body || github.event.inputs.body }}
run: |
labels=$(python .github/scripts/triage_issue.py)
echo "labels=$labels" >> $GITHUB_OUTPUT

- name: Apply Labels
if: steps.run_script.outputs.labels != '' && (github.event.issue.number)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
# Convert comma-separated labels to gh command arguments
IFS=',' read -ra ADDR <<< "${{ steps.run_script.outputs.labels }}"
for i in "${ADDR[@]}"; do
# Trim whitespace
label=$(echo "$i" | xargs)
# Only add priority labels as requested
if [[ "$label" == priority:* ]]; then
gh issue edit "$ISSUE_NUMBER" --add-label "$label"
fi
done