mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-05 14:48:01 +02:00
134 lines
5.5 KiB
YAML
134 lines
5.5 KiB
YAML
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"
|