Files
Meshtastic-Android/.github/workflows/models_pr_triage.yml

161 lines
8.8 KiB
YAML

name: PR Triage (Models)
on:
pull_request_target:
types: [opened, edited]
permissions:
pull-requests: write
issues: write
models: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
triage:
if: ${{ github.repository == 'meshtastic/Meshtastic-Android' && github.event.pull_request.user.type != 'Bot' }}
runs-on: ubuntu-24.04-arm
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Check if PR already has automation/type labels (skip if so)
# ─────────────────────────────────────────────────────────────────────────
- name: Check existing labels
uses: actions/github-script@v9
id: check-labels
with:
script: |
const skipLabels = new Set(['automation', 'release']);
const typeLabels = new Set(['bugfix', 'enhancement', 'dependencies', 'repo', 'refactor', 'chore', 'ci', 'build', 'testing', 'documentation']);
const prLabels = context.payload.pull_request.labels.map(l => l.name);
const shouldSkipAll = prLabels.some(l => skipLabels.has(l));
const hasTypeLabel = prLabels.some(l => typeLabels.has(l));
core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false');
core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false');
# ─────────────────────────────────────────────────────────────────────────
# Step 2: Quality check (spam/AI-slop detection)
# ─────────────────────────────────────────────────────────────────────────
- name: Detect spam or low-quality content
if: steps.check-labels.outputs.skip_all != 'true'
uses: actions/ai-inference@v2
id: quality
continue-on-error: true
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
with:
max-tokens: 20
prompt: |
Is this GitHub pull request spam, AI-generated slop, or low quality?
Title: ${{ env.PR_TITLE }}
Body: ${{ env.PR_BODY }}
Respond with exactly one of: spam, ai-generated, needs-review, ok
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
model: openai/gpt-4o-mini
- name: Apply quality label if needed
if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
uses: actions/github-script@v9
id: quality-label
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
with:
script: |
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
const labelMeta = {
'spam': { color: 'd73a4a', description: 'Possible spam' },
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
core.setOutput('is_spam', 'true');
# ─────────────────────────────────────────────────────────────────────────
# Step 3: Auto-label PR type (bugfix/enhancement/refactor)
# ─────────────────────────────────────────────────────────────────────────
- name: Classify PR for labeling
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '')
uses: actions/ai-inference@v2
id: classify
continue-on-error: true
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
with:
max-tokens: 30
prompt: |
Classify this pull request for the Meshtastic Android app into exactly one category.
Return exactly one of: bugfix, enhancement, refactor, chore, ci, build, testing, documentation
Label definitions:
- bugfix: Fixes a bug, crash, or incorrect behavior
- enhancement: Adds a new feature, improves UX, adds new functionality, or improves performance
- refactor: Restructures code without changing behavior, cleans up code, or improves architecture
- chore: Routine maintenance (dependency updates, config tweaks, version bumps) that doesn't change app behavior
- ci: Changes to CI/CD workflows, GitHub Actions, or automation scripts
- build: Changes to build system, Gradle config, build-logic, or compilation settings
- testing: Adds or modifies tests without changing production code
- documentation: Documentation-only changes (README, CHANGELOG, code comments, KDoc)
Title: ${{ env.PR_TITLE }}
Body: ${{ env.PR_BODY }}
system-prompt: >
You classify pull requests into categories for changelog generation.
Pick the single most specific label. Prefer ci/build/chore/testing/documentation
over refactor when the change clearly fits those narrower categories.
Only use bugfix for actual bug fixes, not for CI fixes or build fixes.
Only use enhancement for user-facing features, not for internal improvements.
model: openai/gpt-4o-mini
- name: Apply type label
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != ''
uses: actions/github-script@v9
env:
TYPE_LABEL: ${{ steps.classify.outputs.response }}
with:
script: |
const label = (process.env.TYPE_LABEL || '').trim().toLowerCase();
const labelMeta = {
'bugfix': { color: 'd73a4a', description: 'Bug fix' },
'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' },
'refactor': { color: 'c5def5', description: 'Code restructuring without behavior change' },
'chore': { color: 'ededed', description: 'Routine maintenance' },
'ci': { color: 'bfd4f2', description: 'CI/CD and automation changes' },
'build': { color: 'bfd4f2', description: 'Build system changes' },
'testing': { color: 'f9d0c4', description: 'Test additions or modifications' },
'documentation': { color: '0075ca', description: 'Documentation changes' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });