main
yml 181 lines 6.78 KB
Raw
1 name: Squad Label Enforce
2
3 on:
4 issues:
5 types: [labeled]
6
7 permissions:
8 issues: write
9 contents: read
10
11 jobs:
12 enforce:
13 runs-on: ubuntu-latest
14 steps:
15 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
16
17 - name: Enforce mutual exclusivity
18 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19 with:
20 script: |
21 const issue = context.payload.issue;
22 const appliedLabel = context.payload.label.name;
23
24 // Namespaces with mutual exclusivity rules
25 const EXCLUSIVE_PREFIXES = ['go:', 'release:', 'type:', 'priority:'];
26
27 // Skip if not a managed namespace label
28 if (!EXCLUSIVE_PREFIXES.some(p => appliedLabel.startsWith(p))) {
29 core.info(`Label ${appliedLabel} is not in a managed namespace — skipping`);
30 return;
31 }
32
33 const allLabels = issue.labels.map(l => l.name);
34
35 // Handle go: namespace (mutual exclusivity)
36 if (appliedLabel.startsWith('go:')) {
37 const otherGoLabels = allLabels.filter(l =>
38 l.startsWith('go:') && l !== appliedLabel
39 );
40
41 if (otherGoLabels.length > 0) {
42 // Remove conflicting go: labels
43 for (const label of otherGoLabels) {
44 await github.rest.issues.removeLabel({
45 owner: context.repo.owner,
46 repo: context.repo.repo,
47 issue_number: issue.number,
48 name: label
49 });
50 core.info(`Removed conflicting label: ${label}`);
51 }
52
53 // Post update comment
54 await github.rest.issues.createComment({
55 owner: context.repo.owner,
56 repo: context.repo.repo,
57 issue_number: issue.number,
58 body: `🏷️ Triage verdict updated → \`${appliedLabel}\``
59 });
60 }
61
62 // Auto-apply release:backlog if go:yes and no release target
63 if (appliedLabel === 'go:yes') {
64 const hasReleaseLabel = allLabels.some(l => l.startsWith('release:'));
65 if (!hasReleaseLabel) {
66 await github.rest.issues.addLabels({
67 owner: context.repo.owner,
68 repo: context.repo.repo,
69 issue_number: issue.number,
70 labels: ['release:backlog']
71 });
72
73 await github.rest.issues.createComment({
74 owner: context.repo.owner,
75 repo: context.repo.repo,
76 issue_number: issue.number,
77 body: `📋 Marked as \`release:backlog\` — assign a release target when ready.`
78 });
79
80 core.info('Applied release:backlog for go:yes issue');
81 }
82 }
83
84 // Remove release: labels if go:no
85 if (appliedLabel === 'go:no') {
86 const releaseLabels = allLabels.filter(l => l.startsWith('release:'));
87 if (releaseLabels.length > 0) {
88 for (const label of releaseLabels) {
89 await github.rest.issues.removeLabel({
90 owner: context.repo.owner,
91 repo: context.repo.repo,
92 issue_number: issue.number,
93 name: label
94 });
95 core.info(`Removed release label from go:no issue: ${label}`);
96 }
97 }
98 }
99 }
100
101 // Handle release: namespace (mutual exclusivity)
102 if (appliedLabel.startsWith('release:')) {
103 const otherReleaseLabels = allLabels.filter(l =>
104 l.startsWith('release:') && l !== appliedLabel
105 );
106
107 if (otherReleaseLabels.length > 0) {
108 // Remove conflicting release: labels
109 for (const label of otherReleaseLabels) {
110 await github.rest.issues.removeLabel({
111 owner: context.repo.owner,
112 repo: context.repo.repo,
113 issue_number: issue.number,
114 name: label
115 });
116 core.info(`Removed conflicting label: ${label}`);
117 }
118
119 // Post update comment
120 await github.rest.issues.createComment({
121 owner: context.repo.owner,
122 repo: context.repo.repo,
123 issue_number: issue.number,
124 body: `🏷️ Release target updated → \`${appliedLabel}\``
125 });
126 }
127 }
128
129 // Handle type: namespace (mutual exclusivity)
130 if (appliedLabel.startsWith('type:')) {
131 const otherTypeLabels = allLabels.filter(l =>
132 l.startsWith('type:') && l !== appliedLabel
133 );
134
135 if (otherTypeLabels.length > 0) {
136 for (const label of otherTypeLabels) {
137 await github.rest.issues.removeLabel({
138 owner: context.repo.owner,
139 repo: context.repo.repo,
140 issue_number: issue.number,
141 name: label
142 });
143 core.info(`Removed conflicting label: ${label}`);
144 }
145
146 await github.rest.issues.createComment({
147 owner: context.repo.owner,
148 repo: context.repo.repo,
149 issue_number: issue.number,
150 body: `🏷️ Issue type updated → \`${appliedLabel}\``
151 });
152 }
153 }
154
155 // Handle priority: namespace (mutual exclusivity)
156 if (appliedLabel.startsWith('priority:')) {
157 const otherPriorityLabels = allLabels.filter(l =>
158 l.startsWith('priority:') && l !== appliedLabel
159 );
160
161 if (otherPriorityLabels.length > 0) {
162 for (const label of otherPriorityLabels) {
163 await github.rest.issues.removeLabel({
164 owner: context.repo.owner,
165 repo: context.repo.repo,
166 issue_number: issue.number,
167 name: label
168 });
169 core.info(`Removed conflicting label: ${label}`);
170 }
171
172 await github.rest.issues.createComment({
173 owner: context.repo.owner,
174 repo: context.repo.repo,
175 issue_number: issue.number,
176 body: `🏷️ Priority updated → \`${appliedLabel}\``
177 });
178 }
179 }
180
181 core.info(`Label enforcement complete for ${appliedLabel}`);