name: Issue & PR Automation on: issues: types: [opened] pull_request: types: [opened] issue_comment: types: [created] pull_request_review_comment: types: [created] permissions: contents: read issues: write pull-requests: write models: read id-token: write jobs: validate-issue: if: github.event_name == 'issues' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 - name: Get issue templates id: get-templates run: | if [ -f ".github/ISSUE_TEMPLATE/01-bug-report.md" ]; then echo "bug-template-exists=true" >> $GITHUB_OUTPUT fi if [ -f ".github/ISSUE_TEMPLATE/02-feature-request.md" ]; then echo "feature-template-exists=true" >> $GITHUB_OUTPUT fi - name: Create issue analysis prompt id: create-prompt env: ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_BODY: ${{ github.event.issue.body }} ISSUE_LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }} run: | cat > issue_analysis.txt << EOF ## Issue Content to Analyze: **Title:** $ISSUE_TITLE **Body:** $ISSUE_BODY **Labels:** $ISSUE_LABELS EOF - name: Validate issue with AI id: validate uses: actions/ai-inference@a6101c89c6feaecc585efdd8d461f18bb7896f20 # v2.0.5 with: prompt-file: issue_analysis.txt system-prompt: | You are an issue validation assistant for Donut Browser, an anti-detect browser. Analyze the provided issue content and determine if it contains sufficient information based on these requirements: **For Bug Reports, the issue should include:** 1. Clear description of the problem 2. Steps to reproduce the issue (numbered list preferred) 3. Expected vs actual behavior 4. Environment information (OS, browser version, etc.) 5. Error messages, stack traces, or screenshots if applicable **For Feature Requests, the issue should include:** 1. Clear description of the requested feature 2. Use case or problem it solves 3. Proposed solution or how it should work 4. Priority level or importance **General Requirements for all issues:** 1. Descriptive title 2. Sufficient detail to understand and act upon 3. Professional tone and clear communication Respond ONLY with valid JSON (no markdown fences). Keep responses concise. JSON structure: { "is_valid": true|false, "issue_type": "bug_report"|"feature_request"|"other", "missing_info": ["item1", "item2"], "suggestions": ["suggestion1", "suggestion2"], "overall_assessment": "One sentence assessment" } IMPORTANT CONSTRAINTS: - Maximum 3 items in missing_info array - Maximum 3 items in suggestions array - Each array item must be under 80 characters - overall_assessment must be under 100 characters - Output ONLY the JSON object, nothing else model: gpt-5-mini - name: Check if first-time contributor id: check-first-time env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_AUTHOR: ${{ github.event.issue.user.login }} run: | ISSUE_COUNT=$(gh api "/repos/${{ github.repository }}/issues" \ --jq "map(select(.user.login == \"$ISSUE_AUTHOR\" and .number != ${{ github.event.issue.number }})) | length" \ --paginate || echo "0") if [ "$ISSUE_COUNT" = "0" ]; then echo "is_first_time=true" >> $GITHUB_OUTPUT else echo "is_first_time=false" >> $GITHUB_OUTPUT fi - name: Parse validation result and take action env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RESPONSE_FILE: ${{ steps.validate.outputs.response-file }} run: | if [ -n "$RESPONSE_FILE" ] && [ -f "$RESPONSE_FILE" ]; then RAW_OUTPUT=$(cat "$RESPONSE_FILE") else echo "::error::Response file not found: $RESPONSE_FILE" exit 1 fi JSON_RESULT=$(printf "%s" "$RAW_OUTPUT" | sed -n '/```json/,/```/p' | sed '1d;$d') if [ -z "$JSON_RESULT" ]; then JSON_RESULT="$RAW_OUTPUT" fi if ! echo "$JSON_RESULT" | jq empty 2>/dev/null; then echo "::warning::Invalid JSON in AI response, using fallback" JSON_RESULT='{"is_valid":true,"issue_type":"other","missing_info":[],"suggestions":[],"overall_assessment":"Unable to validate automatically"}' fi IS_VALID=$(echo "$JSON_RESULT" | jq -r '.is_valid // false') ISSUE_TYPE=$(echo "$JSON_RESULT" | jq -r '.issue_type // "other"') MISSING_INFO=$(echo "$JSON_RESULT" | jq -r '.missing_info[]? // empty' | sed 's/^/- /') SUGGESTIONS=$(echo "$JSON_RESULT" | jq -r '.suggestions[]? // empty' | sed 's/^/- /') ASSESSMENT=$(echo "$JSON_RESULT" | jq -r '.overall_assessment // "No assessment provided"') IS_FIRST_TIME="${{ steps.check-first-time.outputs.is_first_time }}" GREETING_SECTION="" if [ "$IS_FIRST_TIME" = "true" ]; then GREETING_SECTION="## 👋 Welcome!\n\nThank you for your first issue ❤️ If this is a feature request, please make sure it is clear what you want, why you want it, and how important it is to you. If you posted a bug report, please make sure it includes as much detail as possible.\n\n---\n\n" fi if [ "$IS_VALID" = "false" ]; then { printf "%b" "$GREETING_SECTION" printf "## 🤖 Issue Validation\n\n" printf "Thank you for submitting this issue! However, it appears that some required information might be missing to help us better understand and address your concern.\n\n" printf "**Issue Type Detected:** \`%s\`\n\n" "$ISSUE_TYPE" printf "**Assessment:** %s\n\n" "$ASSESSMENT" printf "### 📋 Missing Information:\n%s\n\n" "$MISSING_INFO" printf "### 💡 Suggestions for Improvement:\n%s\n\n" "$SUGGESTIONS" printf "### 📝 How to Provide Additional Information:\n\n" printf "Please edit your original issue description to include the missing information. Here are our issue templates for reference:\n\n" printf -- "- **Bug Report Template:** [View Template](.github/ISSUE_TEMPLATE/01-bug-report.md)\n" printf -- "- **Feature Request Template:** [View Template](.github/ISSUE_TEMPLATE/02-feature-request.md)\n\n" printf "### 🔧 Quick Tips:\n" printf -- "- For **bug reports**: Include step-by-step reproduction instructions, your environment details, and any error messages\n" printf -- "- For **feature requests**: Describe the use case, expected behavior, and why this feature would be valuable\n" printf -- "- Add **screenshots** or **logs** when applicable\n\n" printf "Once you have updated the issue with the missing information, feel free to remove this comment or reply to let us know you have made the updates.\n\n" printf -- "---\n*This validation was performed automatically to ensure we have all the information needed to help you effectively.*\n" } > comment.md gh issue comment ${{ github.event.issue.number }} --body-file comment.md gh issue edit ${{ github.event.issue.number }} --add-label "needs-info" else SUGGESTIONS_SECTION="" if [ -n "$SUGGESTIONS" ]; then SUGGESTIONS_SECTION=$(printf "### 💡 Suggestions:\n%s\n\n" "$SUGGESTIONS") fi { printf "%b" "$GREETING_SECTION" printf "## 🤖 Issue Validation\n\n" printf "**Issue Type Detected:** \`%s\`\n\n" "$ISSUE_TYPE" printf "**Assessment:** %s\n\n" "$ASSESSMENT" printf "%b" "$SUGGESTIONS_SECTION" printf -- "---\n*This validation was performed automatically to help triage issues.*\n" } > comment.md gh issue comment ${{ github.event.issue.number }} --body-file comment.md case "$ISSUE_TYPE" in "bug_report") gh issue edit ${{ github.event.issue.number }} --add-label "bug" ;; "feature_request") gh issue edit ${{ github.event.issue.number }} --add-label "enhancement" ;; esac fi - name: Run opencode analysis uses: anomalyco/opencode/github@latest env: ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} with: model: zai-coding-plan/glm-4.7 - name: Cleanup run: rm -f issue_analysis.txt comment.md handle-pr: if: github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 with: fetch-depth: 0 - name: Check if first-time contributor id: check-first-time env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} run: | PR_COUNT=$(gh api "/repos/${{ github.repository }}/pulls?state=all&per_page=100" \ --jq "[.[] | select(.user.login == \"$PR_AUTHOR\" and .number != ${{ github.event.pull_request.number }})] | length" \ || echo "0") if [ "$PR_COUNT" = "0" ]; then echo "is_first_time=true" >> $GITHUB_OUTPUT else echo "is_first_time=false" >> $GITHUB_OUTPUT fi - name: Get PR diff id: get-diff env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr diff ${{ github.event.pull_request.number }} > pr_diff.txt head -c 10000 pr_diff.txt > pr_diff_truncated.txt - name: Create PR analysis prompt env: PR_TITLE: ${{ github.event.pull_request.title }} PR_BODY: ${{ github.event.pull_request.body }} run: | { printf "## Pull Request to Review:\n\n" printf "**Title:** %s\n\n" "$PR_TITLE" printf "**Description:**\n%s\n\n" "$PR_BODY" printf "**Diff:**\n" cat pr_diff_truncated.txt } > pr_analysis.txt - name: Analyze PR with AI id: analyze uses: actions/ai-inference@a6101c89c6feaecc585efdd8d461f18bb7896f20 # v2.0.5 with: prompt-file: pr_analysis.txt system-prompt: | You are a code review assistant for Donut Browser, an open-source anti-detect browser built with Tauri, Next.js, and Rust. Review the provided pull request and provide constructive feedback. Focus on: 1. Code quality and best practices 2. Potential bugs or issues 3. Security concerns (especially important for an anti-detect browser) 4. Performance implications 5. Consistency with the project's patterns Respond ONLY with valid JSON (no markdown fences). JSON structure: { "summary": "Brief 1-2 sentence summary of what this PR does", "quality_score": "good"|"needs_work"|"critical_issues", "feedback": ["feedback point 1", "feedback point 2"], "suggestions": ["suggestion 1", "suggestion 2"], "security_notes": ["security note if any"] or [] } IMPORTANT CONSTRAINTS: - Maximum 4 items in feedback array - Maximum 3 items in suggestions array - Maximum 2 items in security_notes array - Each array item must be under 150 characters - summary must be under 200 characters - Be constructive and helpful, not harsh - Output ONLY the JSON object, nothing else model: gpt-5-mini - name: Post PR feedback comment env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RESPONSE_FILE: ${{ steps.analyze.outputs.response-file }} run: | if [ -n "$RESPONSE_FILE" ] && [ -f "$RESPONSE_FILE" ]; then RAW_OUTPUT=$(cat "$RESPONSE_FILE") else echo "::error::Response file not found" exit 1 fi JSON_RESULT=$(printf "%s" "$RAW_OUTPUT" | sed -n '/```json/,/```/p' | sed '1d;$d') if [ -z "$JSON_RESULT" ]; then JSON_RESULT="$RAW_OUTPUT" fi if ! echo "$JSON_RESULT" | jq empty 2>/dev/null; then echo "::warning::Invalid JSON in AI response, using fallback" JSON_RESULT='{"summary":"Unable to analyze automatically","quality_score":"good","feedback":[],"suggestions":[],"security_notes":[]}' fi SUMMARY=$(echo "$JSON_RESULT" | jq -r '.summary // "No summary"') QUALITY=$(echo "$JSON_RESULT" | jq -r '.quality_score // "good"') FEEDBACK=$(echo "$JSON_RESULT" | jq -r '.feedback[]? // empty' | sed 's/^/- /') SUGGESTIONS=$(echo "$JSON_RESULT" | jq -r '.suggestions[]? // empty' | sed 's/^/- /') SECURITY=$(echo "$JSON_RESULT" | jq -r '.security_notes[]? // empty' | sed 's/^/- ⚠️ /') IS_FIRST_TIME="${{ steps.check-first-time.outputs.is_first_time }}" { if [ "$IS_FIRST_TIME" = "true" ]; then printf "## 👋 Welcome!\n\n" printf "Thank you for your first contribution ❤️ A human will review your PR shortly. Make sure that the pipelines are green, so that the PR is considered ready for review and could be merged.\n\n" printf -- "---\n\n" fi printf "## 🤖 PR Review\n\n" printf "**Summary:** %s\n\n" "$SUMMARY" case "$QUALITY" in "good") printf "**Status:** ✅ Looking good!\n\n" ;; "needs_work") printf "**Status:** 🔧 Some improvements suggested\n\n" ;; "critical_issues") printf "**Status:** ⚠️ Please address the issues below\n\n" ;; esac if [ -n "$FEEDBACK" ]; then printf "### 📝 Feedback:\n%s\n\n" "$FEEDBACK" fi if [ -n "$SUGGESTIONS" ]; then printf "### 💡 Suggestions:\n%s\n\n" "$SUGGESTIONS" fi if [ -n "$SECURITY" ]; then printf "### 🔒 Security Notes:\n%s\n\n" "$SECURITY" fi printf -- "---\n*This review was performed automatically. A human maintainer will also review your changes.*\n" } > comment.md gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md - name: Run opencode analysis uses: anomalyco/opencode/github@latest env: ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} with: model: zai-coding-plan/glm-4.7 - name: Cleanup run: rm -f pr_diff.txt pr_diff_truncated.txt pr_analysis.txt comment.md opencode-command: if: | (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, ' /oc') || startsWith(github.event.comment.body, '/oc') || contains(github.event.comment.body, ' /opencode') || startsWith(github.event.comment.body, '/opencode')) runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 - name: Run opencode uses: anomalyco/opencode/github@latest env: ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} with: model: zai-coding-plan/glm-4.7