mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-12 16:55:02 -04:00
161 lines
8.8 KiB
YAML
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] });
|