Skip to content

Commit 4cfd4ab

Browse files
rubenmarcusclaude
andcommitted
feat: smart auto-labeling and auto-assign for PRs
- Add auto-label.yml with intelligent label detection: - Labels based on changed files (core, docs, ci/cd, security, etc.) - Labels based on PR title (feat, fix, docs, etc.) - Only adds candidate-release for actual src changes - Auto-assign @rubenmarcus on PRs they create - Remove duplicate auto-label job from prepare-release.yml Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2e79c6d commit 4cfd4ab

File tree

2 files changed

+152
-43
lines changed

2 files changed

+152
-43
lines changed

.github/workflows/auto-label.yml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Auto Label & Assign
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
11+
jobs:
12+
auto-assign-and-label:
13+
name: Auto Assign & Smart Label
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v6
18+
19+
- name: Get changed files
20+
id: changed-files
21+
uses: tj-actions/changed-files@v47
22+
with:
23+
files_yaml: |
24+
src:
25+
- 'src/**'
26+
docs:
27+
- 'docs/**'
28+
- '**/*.md'
29+
- '!CHANGELOG.md'
30+
ci:
31+
- '.github/workflows/**'
32+
- '.github/actions/**'
33+
security:
34+
- '.github/workflows/security.yml'
35+
- '.coderabbit.yaml'
36+
- 'SECURITY.md'
37+
- '.gitleaks.toml'
38+
config:
39+
- 'package.json'
40+
- 'tsconfig.json'
41+
- '.eslintrc*'
42+
- '.prettierrc*'
43+
tests:
44+
- '**/*.test.ts'
45+
- '**/*.spec.ts'
46+
- 'vitest.config.*'
47+
- 'jest.config.*'
48+
49+
- name: Auto assign and label
50+
uses: actions/github-script@v8
51+
with:
52+
script: |
53+
const { owner, repo } = context.repo;
54+
const pr = context.payload.pull_request;
55+
const prNumber = pr.number;
56+
const prAuthor = pr.user.login;
57+
const prTitle = pr.title.toLowerCase();
58+
59+
// Auto-assign if author is rubenmarcus
60+
if (prAuthor === 'rubenmarcus') {
61+
await github.rest.issues.addAssignees({
62+
owner, repo,
63+
issue_number: prNumber,
64+
assignees: ['rubenmarcus']
65+
});
66+
console.log('Auto-assigned rubenmarcus');
67+
}
68+
69+
// Collect labels to add
70+
const labelsToAdd = new Set();
71+
72+
// Labels based on changed files
73+
const srcChanged = '${{ steps.changed-files.outputs.src_any_changed }}' === 'true';
74+
const docsChanged = '${{ steps.changed-files.outputs.docs_any_changed }}' === 'true';
75+
const ciChanged = '${{ steps.changed-files.outputs.ci_any_changed }}' === 'true';
76+
const securityChanged = '${{ steps.changed-files.outputs.security_any_changed }}' === 'true';
77+
const configChanged = '${{ steps.changed-files.outputs.config_any_changed }}' === 'true';
78+
const testsChanged = '${{ steps.changed-files.outputs.tests_any_changed }}' === 'true';
79+
80+
if (srcChanged) labelsToAdd.add('core');
81+
if (docsChanged) labelsToAdd.add('documentation');
82+
if (ciChanged) labelsToAdd.add('ci/cd');
83+
if (securityChanged) labelsToAdd.add('security');
84+
if (configChanged) labelsToAdd.add('config');
85+
if (testsChanged) labelsToAdd.add('tests');
86+
87+
// Labels based on PR title (conventional commits)
88+
if (prTitle.startsWith('feat')) labelsToAdd.add('enhancement');
89+
if (prTitle.startsWith('fix')) labelsToAdd.add('bug');
90+
if (prTitle.startsWith('docs')) labelsToAdd.add('documentation');
91+
if (prTitle.startsWith('chore')) labelsToAdd.add('chore');
92+
if (prTitle.startsWith('refactor')) labelsToAdd.add('refactor');
93+
if (prTitle.startsWith('perf')) labelsToAdd.add('performance');
94+
if (prTitle.startsWith('test')) labelsToAdd.add('tests');
95+
if (prTitle.startsWith('ci')) labelsToAdd.add('ci/cd');
96+
97+
// Special keywords in title
98+
if (prTitle.includes('security') || prTitle.includes('vulnerability')) labelsToAdd.add('security');
99+
if (prTitle.includes('breaking')) labelsToAdd.add('breaking-change');
100+
if (prTitle.includes('devops') || prTitle.includes('automation')) labelsToAdd.add('devops');
101+
if (prTitle.includes('dependabot') || prTitle.includes('deps')) labelsToAdd.add('dependencies');
102+
103+
// Only add candidate-release if src files changed (actual code changes)
104+
if (srcChanged && !prTitle.startsWith('chore') && !prTitle.startsWith('docs')) {
105+
labelsToAdd.add('candidate-release');
106+
}
107+
108+
// Convert to array and filter out empty
109+
const labels = Array.from(labelsToAdd).filter(Boolean);
110+
111+
if (labels.length > 0) {
112+
// Ensure labels exist
113+
for (const label of labels) {
114+
try {
115+
await github.rest.issues.getLabel({ owner, repo, name: label });
116+
} catch (e) {
117+
// Create label if it doesn't exist
118+
const colors = {
119+
'core': '0052CC',
120+
'documentation': '0075CA',
121+
'ci/cd': '7057FF',
122+
'security': 'B60205',
123+
'config': 'FBCA04',
124+
'tests': '1D76DB',
125+
'enhancement': 'A2EEEF',
126+
'bug': 'D73A4A',
127+
'chore': 'FEF2C0',
128+
'refactor': 'D4C5F9',
129+
'performance': '0E8A16',
130+
'breaking-change': 'B60205',
131+
'devops': '5319E7',
132+
'dependencies': '0366D6',
133+
'candidate-release': 'EDEDED'
134+
};
135+
await github.rest.issues.createLabel({
136+
owner, repo,
137+
name: label,
138+
color: colors[label] || 'EDEDED'
139+
});
140+
}
141+
}
142+
143+
// Add labels to PR
144+
await github.rest.issues.addLabels({
145+
owner, repo,
146+
issue_number: prNumber,
147+
labels
148+
});
149+
console.log(`Added labels: ${labels.join(', ')}`);
150+
}

.github/workflows/prepare-release.yml

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,8 @@ permissions:
1111
pull-requests: write
1212

1313
jobs:
14-
# Auto-add candidate-release label when src/ files change
15-
auto-label:
16-
name: Auto Label Release Candidate
17-
if: |
18-
github.event.action != 'labeled' &&
19-
github.event.action != 'closed' &&
20-
!startsWith(github.event.pull_request.head.ref, 'release/')
21-
runs-on: ubuntu-latest
22-
steps:
23-
- name: Checkout
24-
uses: actions/checkout@v6
25-
26-
- name: Check for package changes
27-
id: changes
28-
uses: dorny/paths-filter@v3
29-
with:
30-
filters: |
31-
package:
32-
- 'src/**'
33-
- 'package.json'
34-
- '!**/*.md'
35-
- '!docs/**'
36-
37-
- name: Add candidate-release label
38-
if: steps.changes.outputs.package == 'true'
39-
uses: actions/github-script@v8
40-
with:
41-
script: |
42-
const { owner, repo } = context.repo;
43-
const issue_number = context.payload.pull_request.number;
44-
45-
// Check if label already exists
46-
const labels = await github.rest.issues.listLabelsOnIssue({
47-
owner, repo, issue_number
48-
});
49-
50-
if (!labels.data.find(l => l.name === 'candidate-release')) {
51-
await github.rest.issues.addLabels({
52-
owner, repo, issue_number,
53-
labels: ['candidate-release']
54-
});
55-
console.log('Added candidate-release label');
56-
}
14+
# NOTE: Auto-labeling (including candidate-release) is handled by auto-label.yml
15+
# This workflow only handles creating release PRs when candidate-release PRs are merged
5716

5817
# Create release PR when a PR with candidate-release label is merged
5918
create-release-pr:

0 commit comments

Comments
 (0)