name: Issue Compliance Check on: issues: types: [opened] permissions: contents: read issues: write models: read env: # GitHub Models (free, billed to the repo's plan). gpt-4.1 is the most capable # model reachable on the free tier: the gpt-5 family returns # unavailable_model and o3/o3-mini return 403. MODEL: openai/gpt-4.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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Gather context env: ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_BODY: ${{ github.event.issue.body }} run: | printf '%s' "$ISSUE_TITLE" | node scripts/redact-sensitive-text.mjs > /tmp/issue-title.txt printf '%s' "${ISSUE_BODY:-}" | node scripts/redact-sensitive-text.mjs --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 GitHub Models env: GH_MODELS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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" } }') # Never use curl -f here: a transport or quota error (402 once the repo's # GitHub Models allowance is spent) must not abort the job. The whole # step is fail-open, so capture the status and degrade instead. STATUS=$(curl -sSL -o /tmp/response.json -w '%{http_code}' \ https://models.github.ai/inference/chat/completions \ -H "Authorization: Bearer $GH_MODELS_TOKEN" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2026-03-10" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" || echo "000") if [ "$STATUS" != "200" ]; then echo "::warning::GitHub Models returned HTTP $STATUS; treating as compliant" echo '{"is_compliant": true, "non_compliance_reasons": []}' > /tmp/result.json exit 0 fi jq -r '.choices[0].message.content // empty' /tmp/response.json > /tmp/raw.txt || : > /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" echo '{"is_compliant": true, "non_compliance_reasons": []}' > /tmp/result.json fi echo "Compliance response validated" - 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, 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"