Skip to content

Commit 80150d6

Browse files
jerfowlerAgent Communication MCP Serverclaude
authored
feat: implement comprehensive GitHub issue workflow automation (#3)
- Add automated issue processing with auto-assignment and labeling - Implement PR-issue linking with automatic status updates - Configure stale issue management with 30-day lifecycle - Add 11 GitHub CLI aliases for efficient issue management - Update CONTRIBUTING.md with complete issue workflow documentation - Support issue commands (/priority, /branch, /close) for maintainers - Welcome first-time contributors with helpful information - Auto-close linked issues when PRs are merged 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Agent Communication MCP Server <noreply@example.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5df9a3c commit 80150d6

File tree

4 files changed

+941
-0
lines changed

4 files changed

+941
-0
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# Issue Management Workflow
2+
# Comprehensive automation for GitHub issues lifecycle
3+
4+
name: Issue Management
5+
6+
on:
7+
issues:
8+
types: [opened, labeled, assigned, closed, reopened]
9+
issue_comment:
10+
types: [created]
11+
12+
jobs:
13+
# Process new issues with automation
14+
process-new-issue:
15+
name: Process New Issue
16+
runs-on: ubuntu-latest
17+
if: github.event.action == 'opened'
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v4
22+
23+
- name: Auto-assign to repository owner
24+
uses: actions/github-script@v7
25+
with:
26+
script: |
27+
const issue = context.payload.issue;
28+
const owner = context.repo.owner;
29+
30+
// Auto-assign to repository owner for single-developer repo
31+
await github.rest.issues.addAssignees({
32+
owner: context.repo.owner,
33+
repo: context.repo.repo,
34+
issue_number: issue.number,
35+
assignees: [owner]
36+
});
37+
38+
console.log(`Auto-assigned issue #${issue.number} to ${owner}`);
39+
40+
- name: Add priority labels based on content
41+
uses: actions/github-script@v7
42+
with:
43+
script: |
44+
const issue = context.payload.issue;
45+
const title = issue.title.toLowerCase();
46+
const body = issue.body ? issue.body.toLowerCase() : '';
47+
const content = title + ' ' + body;
48+
49+
const labelsToAdd = [];
50+
51+
// Priority detection
52+
if (content.includes('critical') || content.includes('urgent') || content.includes('security')) {
53+
labelsToAdd.push('priority:high');
54+
} else if (content.includes('important') || content.includes('breaking')) {
55+
labelsToAdd.push('priority:medium');
56+
}
57+
58+
// Category detection beyond template labels
59+
if (content.includes('performance') || content.includes('slow') || content.includes('timeout')) {
60+
labelsToAdd.push('category:performance');
61+
}
62+
if (content.includes('security') || content.includes('vulnerability')) {
63+
labelsToAdd.push('category:security');
64+
}
65+
if (content.includes('test') || content.includes('testing') || content.includes('coverage')) {
66+
labelsToAdd.push('category:testing');
67+
}
68+
69+
// Add labels if any were detected
70+
if (labelsToAdd.length > 0) {
71+
await github.rest.issues.addLabels({
72+
owner: context.repo.owner,
73+
repo: context.repo.repo,
74+
issue_number: issue.number,
75+
labels: labelsToAdd
76+
});
77+
78+
console.log(`Added labels: ${labelsToAdd.join(', ')} to issue #${issue.number}`);
79+
}
80+
81+
- name: Welcome first-time contributors
82+
uses: actions/github-script@v7
83+
with:
84+
script: |
85+
const issue = context.payload.issue;
86+
const issueAuthor = issue.user.login;
87+
88+
// Check if this is the author's first issue
89+
const issues = await github.rest.issues.listForRepo({
90+
owner: context.repo.owner,
91+
repo: context.repo.repo,
92+
creator: issueAuthor,
93+
state: 'all'
94+
});
95+
96+
// If this is their first issue (and not the repo owner)
97+
if (issues.data.length === 1 && issueAuthor !== context.repo.owner) {
98+
await github.rest.issues.createComment({
99+
owner: context.repo.owner,
100+
repo: context.repo.repo,
101+
issue_number: issue.number,
102+
body: `👋 Welcome to the Agent Communication MCP Server project, @${issueAuthor}!
103+
104+
Thank you for taking the time to create this ${issue.labels.find(l => l.name === 'bug') ? 'bug report' : issue.labels.find(l => l.name === 'enhancement') ? 'feature request' : 'issue'}.
105+
106+
🔍 **What happens next:**
107+
- Your issue has been automatically assigned and labeled
108+
- I'll review it and provide feedback within 24-48 hours
109+
- If it's a bug, I'll prioritize it based on severity
110+
- If it's a feature request, I'll evaluate it against the project roadmap
111+
112+
📚 **Helpful resources:**
113+
- [Contributing Guide](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/CONTRIBUTING.md)
114+
- [Protocol Documentation](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/docs/PROTOCOL.md)
115+
116+
Thanks for contributing to making AI agent coordination better! 🚀`
117+
});
118+
119+
console.log(`Welcomed first-time contributor: ${issueAuthor}`);
120+
}
121+
122+
# Handle issue status updates
123+
update-issue-status:
124+
name: Update Issue Status
125+
runs-on: ubuntu-latest
126+
if: github.event.action == 'labeled' || github.event.action == 'closed' || github.event.action == 'reopened'
127+
128+
steps:
129+
- name: Update status based on labels
130+
uses: actions/github-script@v7
131+
with:
132+
script: |
133+
const issue = context.payload.issue;
134+
135+
if (github.event.action === 'labeled') {
136+
const label = context.payload.label;
137+
138+
// Remove needs-triage when other priority labels are added
139+
if (label.name.startsWith('priority:')) {
140+
const labels = issue.labels.map(l => l.name);
141+
if (labels.includes('needs-triage')) {
142+
await github.rest.issues.removeLabel({
143+
owner: context.repo.owner,
144+
repo: context.repo.repo,
145+
issue_number: issue.number,
146+
name: 'needs-triage'
147+
});
148+
149+
console.log(`Removed needs-triage label from issue #${issue.number}`);
150+
}
151+
}
152+
153+
// Add in-progress label if assigned to someone
154+
if (label.name === 'in-progress' || issue.assignees.length > 0) {
155+
const labels = issue.labels.map(l => l.name);
156+
if (!labels.includes('in-progress') && !labels.includes('completed')) {
157+
await github.rest.issues.addLabels({
158+
owner: context.repo.owner,
159+
repo: context.repo.repo,
160+
issue_number: issue.number,
161+
labels: ['in-progress']
162+
});
163+
}
164+
}
165+
}
166+
167+
// Handle issue closure
168+
if (github.event.action === 'closed') {
169+
await github.rest.issues.addLabels({
170+
owner: context.repo.owner,
171+
repo: context.repo.repo,
172+
issue_number: issue.number,
173+
labels: ['completed']
174+
});
175+
176+
// Remove in-progress label
177+
try {
178+
await github.rest.issues.removeLabel({
179+
owner: context.repo.owner,
180+
repo: context.repo.repo,
181+
issue_number: issue.number,
182+
name: 'in-progress'
183+
});
184+
} catch (error) {
185+
// Label might not exist, ignore error
186+
console.log(`Could not remove in-progress label: ${error.message}`);
187+
}
188+
}
189+
190+
// Handle issue reopening
191+
if (github.event.action === 'reopened') {
192+
// Remove completed label and add back in-progress
193+
try {
194+
await github.rest.issues.removeLabel({
195+
owner: context.repo.owner,
196+
repo: context.repo.repo,
197+
issue_number: issue.number,
198+
name: 'completed'
199+
});
200+
} catch (error) {
201+
console.log(`Could not remove completed label: ${error.message}`);
202+
}
203+
204+
await github.rest.issues.addLabels({
205+
owner: context.repo.owner,
206+
repo: context.repo.repo,
207+
issue_number: issue.number,
208+
labels: ['in-progress']
209+
});
210+
}
211+
212+
# Handle issue comments for commands
213+
process-issue-commands:
214+
name: Process Issue Commands
215+
runs-on: ubuntu-latest
216+
if: github.event.action == 'created' && github.event.issue != null
217+
218+
steps:
219+
- name: Process commands in comments
220+
uses: actions/github-script@v7
221+
with:
222+
script: |
223+
const comment = context.payload.comment;
224+
const issue = context.payload.issue;
225+
const commenter = comment.user.login;
226+
227+
// Only repo owner can execute commands
228+
if (commenter !== context.repo.owner) {
229+
return;
230+
}
231+
232+
const body = comment.body.trim();
233+
234+
// /priority <level> command
235+
if (body.match(/^\/priority (high|medium|low)$/)) {
236+
const level = body.match(/^\/priority (high|medium|low)$/)[1];
237+
238+
// Remove existing priority labels
239+
const priorityLabels = ['priority:high', 'priority:medium', 'priority:low'];
240+
const currentLabels = issue.labels.map(l => l.name);
241+
242+
for (const label of priorityLabels) {
243+
if (currentLabels.includes(label)) {
244+
await github.rest.issues.removeLabel({
245+
owner: context.repo.owner,
246+
repo: context.repo.repo,
247+
issue_number: issue.number,
248+
name: label
249+
});
250+
}
251+
}
252+
253+
// Add new priority label
254+
await github.rest.issues.addLabels({
255+
owner: context.repo.owner,
256+
repo: context.repo.repo,
257+
issue_number: issue.number,
258+
labels: [`priority:${level}`]
259+
});
260+
261+
await github.rest.issues.createComment({
262+
owner: context.repo.owner,
263+
repo: context.repo.repo,
264+
issue_number: issue.number,
265+
body: `✅ Priority set to **${level}**`
266+
});
267+
}
268+
269+
// /branch command - create development branch
270+
if (body === '/branch') {
271+
const branchName = `issue-${issue.number}-${issue.title.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').substring(0, 50)}`;
272+
273+
await github.rest.issues.createComment({
274+
owner: context.repo.owner,
275+
repo: context.repo.repo,
276+
issue_number: issue.number,
277+
body: `🌿 **Development branch suggestion:**
278+
279+
\`\`\`bash
280+
gh issue develop ${issue.number} --name ${branchName}
281+
# or manually:
282+
git checkout -b ${branchName}
283+
\`\`\`
284+
285+
This will create a feature branch linked to this issue. When you create a PR from this branch, it will automatically reference this issue.`
286+
});
287+
}
288+
289+
// /close [reason] command
290+
if (body.match(/^\/close/)) {
291+
const reason = body.replace(/^\/close\s*/, '') || 'Resolved by maintainer';
292+
293+
await github.rest.issues.createComment({
294+
owner: context.repo.owner,
295+
repo: context.repo.repo,
296+
issue_number: issue.number,
297+
body: `🔒 **Closing issue:** ${reason}`
298+
});
299+
300+
await github.rest.issues.update({
301+
owner: context.repo.owner,
302+
repo: context.repo.repo,
303+
issue_number: issue.number,
304+
state: 'closed',
305+
state_reason: 'completed'
306+
});
307+
}

0 commit comments

Comments
 (0)