name: Issue Compliance Check on: issues: types: [opened] permissions: contents: read issues: write env: MODEL: z-ai/glm-5.1 jobs: check-compliance: # Maintainers' own issues are exempt — they open quick tracking issues # without the template on purpose. Everyone else is checked. if: >- github.repository == 'zhom/donutbrowser' && github.event.issue.author_association != 'OWNER' && github.event.issue.author_association != 'MEMBER' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Gather context env: ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_BODY: ${{ github.event.issue.body }} run: | printf '%s' "$ISSUE_TITLE" > /tmp/issue-title.txt printf '%s' "${ISSUE_BODY:-}" > /tmp/issue-body.txt - name: Build prompt run: | cat > /tmp/system.txt <<'PROMPT' You are reviewing a new GitHub issue for template compliance. Return ONLY a single JSON object, no prose, no markdown fences. Project: Donut Browser. There are three valid templates: - Bug Report (Description + Operating System + Donut Browser version + Which browser is affected + Steps to reproduce + Error logs/screenshots fields) - Feature Request (description + verification checkbox) - Question (free form) ## Compliance — flag NON-compliant ONLY when at least one of these is true - The issue body is empty or contains only placeholder text from the template - The issue is an obvious AI-generated wall of text with no real specifics - A bug report has no reproduction information or no error description - A feature request gives no use case at all - The author left required fields empty (Operating System, Donut Browser version, Which browser is affected, Steps to reproduce on bug reports) Do NOT flag for missing optional fields, missing screenshots, short titles, or stylistic issues. Be conservative — a non-compliant verdict closes the issue, so only flag a genuine template violation. ## Output schema { "is_compliant": true | false, "non_compliance_reasons": ["short bullet", ...] } If there is nothing to flag, return: {"is_compliant": true, "non_compliance_reasons": []} PROMPT - name: Call OpenRouter env: OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} run: | PAYLOAD=$(jq -n \ --arg model "$MODEL" \ --rawfile system_prompt /tmp/system.txt \ --rawfile title /tmp/issue-title.txt \ --rawfile body /tmp/issue-body.txt \ '{ model: $model, messages: [ { role: "system", content: $system_prompt }, { role: "user", content: ("New issue title: " + $title + "\n\nNew issue body:\n" + $body) } ], response_format: { type: "json_object" } }') RESPONSE=$(curl -fsSL https://openrouter.ai/api/v1/chat/completions \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d "$PAYLOAD") jq -r '.choices[0].message.content // empty' <<< "$RESPONSE" > /tmp/raw.txt # Strip accidental markdown fences and parse. On parse failure, fall back # to a compliant result so a flaky model never closes a legitimate issue. sed -E 's/^```(json)?$//; s/```$//' /tmp/raw.txt > /tmp/result.json if ! jq -e . /tmp/result.json >/dev/null 2>&1; then echo "::warning::Model returned non-JSON; treating as compliant" cat /tmp/raw.txt echo '{"is_compliant": true, "non_compliance_reasons": []}' > /tmp/result.json fi echo "Result:" cat /tmp/result.json - name: Build comment id: build run: | python3 - <<'EOF' import json, os r = json.load(open('/tmp/result.json')) compliant = bool(r.get('is_compliant', True)) reasons = r.get('non_compliance_reasons') or [] parts = [] if not compliant: parts.append("This issue was automatically closed because it doesn't follow our [issue templates](../issues/new/choose).") parts.append('') parts.append('**What was missing:**') for reason in reasons: parts.append(f'- {reason}') parts.append('') parts.append('If this is a real bug or feature request, please open a new issue using the **Bug Report** or **Feature Request** template and fill in the required fields. Issues that ignore the template are not triaged.') comment = '\n'.join(parts).strip() open('/tmp/comment.md', 'w').write(comment) with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: fh.write(f'non_compliant={"true" if not compliant else "false"}\n') EOF - name: Comment and close non-compliant issue if: steps.build.outputs.non_compliant == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number }} run: | gh issue comment "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/comment.md gh issue close "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --reason "not planned"