Files
awesome-copilot/.github/workflows/label-pr-intent.yml
Aaron Powell b644d55e12 feat: add PR intent labeling workflow (#1604)
* feat: label PR intent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* need a git repo

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-04 16:34:15 +10:00

242 lines
8.0 KiB
YAML

name: Label PR Intent
on:
pull_request_target:
types: [opened, synchronize, reopened, edited, ready_for_review]
permissions:
issues: write
pull-requests: read
jobs:
label-pr:
runs-on: ubuntu-latest
if: >-
github.actor != 'dependabot[bot]' &&
github.actor != 'github-actions[bot]'
steps:
- name: Apply intent labels
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const managedLabels = {
'targets-main': {
color: 'B60205',
description: 'PR targets main instead of staged'
},
'branched-main': {
color: 'D93F0B',
description: 'PR appears to include plugin files materialized from main'
},
'skills': {
color: '1D76DB',
description: 'PR touches skills'
},
'plugin': {
color: '5319E7',
description: 'PR touches plugins'
},
'agent': {
color: '0E8A16',
description: 'PR touches agents'
},
'instructions': {
color: 'FBCA04',
description: 'PR touches instructions'
},
'new-submission': {
color: '006B75',
description: 'PR adds at least one new contribution'
},
'website-update': {
color: '0052CC',
description: 'PR touches website content or code'
},
'external-plugin': {
color: 'FEF2C0',
description: 'PR updates plugins/external.json'
},
'hooks': {
color: 'C2E0C6',
description: 'PR touches hooks'
},
'workflow': {
color: 'BFD4F2',
description: 'PR touches workflow automation'
}
};
const matchesAny = (filename, patterns) => patterns.some((pattern) => pattern.test(filename));
async function listAllFiles() {
const files = [];
let page = 1;
while (true) {
const response = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100,
page
});
files.push(...response.data);
if (response.data.length < 100) {
return files;
}
page += 1;
}
}
async function ensureLabel(name, { color, description }) {
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
color,
description
});
} catch (error) {
if (error.status !== 422) {
throw error;
}
}
}
const files = await listAllFiles();
const filenames = files.map((file) => file.filename);
const patterns = {
branchedMain: [
/^plugins\/[^/]+\/(?:agents|commands|skills)\//
],
skills: [
/^skills\//
],
plugin: [
/^plugins\//
],
agent: [
/^agents\/.+\.agent\.md$/
],
instructions: [
/^instructions\/.+\.instructions\.md$/
],
websiteUpdate: [
/^website\//
],
externalPlugin: [
/^plugins\/external\.json$/
],
hooks: [
/^hooks\//
],
workflow: [
/^workflows\/.+\.md$/,
/^\.github\/workflows\/.+\.(?:ya?ml|md)$/
],
newSubmission: [
/^agents\/.+\.agent\.md$/,
/^instructions\/.+\.instructions\.md$/,
/^skills\/[^/]+\/SKILL\.md$/,
/^hooks\/[^/]+\/(?:README\.md|hooks\.json)$/,
/^plugins\/[^/]+\/\.github\/plugin\/plugin\.json$/,
/^workflows\/.+\.md$/,
/^\.github\/workflows\/.+\.(?:ya?ml|md)$/,
/^website\//
]
};
const isBranchedMain = filenames.some((filename) => matchesAny(filename, patterns.branchedMain));
const hasNewSubmission = files.some(
(file) => file.status === 'added' && matchesAny(file.filename, patterns.newSubmission)
);
const desiredLabels = new Set();
if (context.payload.pull_request.base.ref === 'main') {
desiredLabels.add('targets-main');
}
if (filenames.some((filename) => matchesAny(filename, patterns.externalPlugin))) {
desiredLabels.add('external-plugin');
}
if (isBranchedMain) {
desiredLabels.add('branched-main');
} else {
if (filenames.some((filename) => matchesAny(filename, patterns.skills))) {
desiredLabels.add('skills');
}
if (filenames.some((filename) => matchesAny(filename, patterns.plugin))) {
desiredLabels.add('plugin');
}
if (filenames.some((filename) => matchesAny(filename, patterns.agent))) {
desiredLabels.add('agent');
}
if (filenames.some((filename) => matchesAny(filename, patterns.instructions))) {
desiredLabels.add('instructions');
}
if (filenames.some((filename) => matchesAny(filename, patterns.websiteUpdate))) {
desiredLabels.add('website-update');
}
if (filenames.some((filename) => matchesAny(filename, patterns.hooks))) {
desiredLabels.add('hooks');
}
if (filenames.some((filename) => matchesAny(filename, patterns.workflow))) {
desiredLabels.add('workflow');
}
if (hasNewSubmission) {
desiredLabels.add('new-submission');
}
}
await Promise.all(
Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config))
);
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100
});
const currentManagedLabels = currentLabels
.map((label) => label.name)
.filter((name) => Object.prototype.hasOwnProperty.call(managedLabels, name));
const labelsToAdd = [...desiredLabels].filter((name) => !currentManagedLabels.includes(name));
const labelsToRemove = currentManagedLabels.filter((name) => !desiredLabels.has(name));
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: labelsToAdd
});
}
for (const name of labelsToRemove) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name
});
}
core.info(`Managed labels: ${[...desiredLabels].sort().join(', ') || 'none'}`);