Skip to content

Commit d67aa91

Browse files
committed
feat: add GitHub CLI version of roadmap issue creation script
1 parent 6f0a320 commit d67aa91

File tree

1 file changed

+343
-0
lines changed

1 file changed

+343
-0
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
#!/usr/bin/env python3
2+
"""
3+
RagaSense Roadmap Issue Creator (GitHub CLI Version)
4+
5+
This script creates GitHub issues from the roadmap tasks using GitHub CLI.
6+
Much more secure and easier than using API tokens.
7+
8+
Usage:
9+
python scripts/create_roadmap_issues_gh.py
10+
11+
Requirements:
12+
- GitHub CLI (gh) installed and authenticated
13+
- gh auth login completed
14+
"""
15+
16+
import os
17+
import re
18+
import subprocess
19+
import json
20+
from typing import Dict, List, Optional
21+
22+
# Configuration
23+
REPO_OWNER = 'adhit-r'
24+
REPO_NAME = 'RagaSense'
25+
ROADMAP_FILE = 'docs/ROADMAP.md'
26+
27+
class RoadmapIssueCreatorGH:
28+
def __init__(self):
29+
self.issues_created = []
30+
self.milestones = {}
31+
self.labels = {}
32+
33+
def run_gh_command(self, command: str) -> Dict:
34+
"""Run a GitHub CLI command and return JSON output"""
35+
try:
36+
result = subprocess.run(
37+
f"gh {command}",
38+
shell=True,
39+
capture_output=True,
40+
text=True,
41+
check=True
42+
)
43+
return json.loads(result.stdout) if result.stdout.strip() else {}
44+
except subprocess.CalledProcessError as e:
45+
print(f"❌ GitHub CLI command failed: {e}")
46+
print(f"Error: {e.stderr}")
47+
return {}
48+
except json.JSONDecodeError:
49+
print(f"⚠️ Non-JSON output: {result.stdout}")
50+
return {}
51+
52+
def run_gh_command_simple(self, command: str) -> bool:
53+
"""Run a GitHub CLI command without JSON output"""
54+
try:
55+
result = subprocess.run(
56+
f"gh {command}",
57+
shell=True,
58+
capture_output=True,
59+
text=True,
60+
check=True
61+
)
62+
return True
63+
except subprocess.CalledProcessError as e:
64+
print(f"❌ GitHub CLI command failed: {e}")
65+
print(f"Error: {e.stderr}")
66+
return False
67+
68+
def create_milestones(self):
69+
"""Create milestones for each development phase using GitHub CLI"""
70+
milestones_data = [
71+
{
72+
'title': 'Phase 1: Foundation & Core Features',
73+
'description': 'Complete core ML model, frontend polish, and database integration',
74+
'due_on': '2024-03-31'
75+
},
76+
{
77+
'title': 'Phase 2: Advanced Features',
78+
'description': 'Implement AI music generation, social features, and advanced analytics',
79+
'due_on': '2024-06-30'
80+
},
81+
{
82+
'title': 'Phase 3: Enterprise & Scale',
83+
'description': 'Add enterprise features, performance optimization, and scalability',
84+
'due_on': '2024-09-30'
85+
},
86+
{
87+
'title': 'Phase 4: Innovation & Research',
88+
'description': 'Advanced AI features, research collaboration, and educational platform',
89+
'due_on': '2024-12-31'
90+
}
91+
]
92+
93+
for milestone_data in milestones_data:
94+
# Create milestone using GitHub CLI
95+
command = f"api repos/{REPO_OWNER}/{REPO_NAME}/milestones --field title='{milestone_data['title']}' --field description='{milestone_data['description']}' --field due_on='{milestone_data['due_on']}'"
96+
97+
result = self.run_gh_command(command)
98+
if result and 'number' in result:
99+
self.milestones[milestone_data['title']] = result['number']
100+
print(f"✅ Created milestone: {milestone_data['title']}")
101+
else:
102+
print(f"❌ Failed to create milestone: {milestone_data['title']}")
103+
104+
def create_labels(self):
105+
"""Create labels for issue categorization using GitHub CLI"""
106+
labels_data = [
107+
{'name': 'phase-1', 'color': '0e8a16', 'description': 'Phase 1: Foundation & Core Features'},
108+
{'name': 'phase-2', 'color': 'fbca04', 'description': 'Phase 2: Advanced Features'},
109+
{'name': 'phase-3', 'color': 'd93f0b', 'description': 'Phase 3: Enterprise & Scale'},
110+
{'name': 'phase-4', 'color': '5319e7', 'description': 'Phase 4: Innovation & Research'},
111+
{'name': 'ml-model', 'color': '1d76db', 'description': 'Machine Learning Model'},
112+
{'name': 'frontend', 'color': 'c2e0c6', 'description': 'Frontend Development'},
113+
{'name': 'backend', 'color': 'd4c5f9', 'description': 'Backend Development'},
114+
{'name': 'database', 'color': 'fef2c0', 'description': 'Database & Authentication'},
115+
{'name': 'ai-generation', 'color': 'bfdadc', 'description': 'AI Music Generation'},
116+
{'name': 'social-features', 'color': 'f9d0c4', 'description': 'Social & Collaborative Features'},
117+
{'name': 'analytics', 'color': 'c5def5', 'description': 'Analytics & Monitoring'},
118+
{'name': 'performance', 'color': 'fef7c0', 'description': 'Performance & Optimization'},
119+
{'name': 'research', 'color': 'd1ecf1', 'description': 'Research & Education'},
120+
{'name': 'high-priority', 'color': 'd73a4a', 'description': 'High Priority - Must Have'},
121+
{'name': 'medium-priority', 'color': 'fbca04', 'description': 'Medium Priority - Should Have'},
122+
{'name': 'low-priority', 'color': '0e8a16', 'description': 'Low Priority - Nice to Have'},
123+
{'name': 'enhancement', 'color': 'a2eeef', 'description': 'New feature or request'},
124+
{'name': 'roadmap', 'color': '5319e7', 'description': 'Part of the development roadmap'}
125+
]
126+
127+
for label_data in labels_data:
128+
# Create label using GitHub CLI
129+
command = f"api repos/{REPO_OWNER}/{REPO_NAME}/labels --field name='{label_data['name']}' --field color='{label_data['color']}' --field description='{label_data['description']}'"
130+
131+
result = self.run_gh_command(command)
132+
if result:
133+
print(f"✅ Label ready: {label_data['name']}")
134+
else:
135+
print(f"⚠️ Label may already exist: {label_data['name']}")
136+
137+
def parse_roadmap_file(self) -> List[Dict]:
138+
"""Parse the roadmap file and extract issue information"""
139+
issues = []
140+
141+
if not os.path.exists(ROADMAP_FILE):
142+
print(f"❌ Roadmap file not found: {ROADMAP_FILE}")
143+
return issues
144+
145+
with open(ROADMAP_FILE, 'r', encoding='utf-8') as f:
146+
content = f.read()
147+
148+
# Parse issues from the roadmap
149+
issue_pattern = r'- \[ \] \*\*Issue #(\d+)\*\*: (.+?)(?=\n- \[ \]|\n\n|\Z)'
150+
matches = re.findall(issue_pattern, content, re.DOTALL)
151+
152+
for issue_num, issue_content in matches:
153+
# Extract title and description
154+
lines = issue_content.strip().split('\n')
155+
title = lines[0].strip()
156+
description_lines = [line.strip() for line in lines[1:] if line.strip()]
157+
description = '\n'.join(description_lines)
158+
159+
# Determine phase and labels
160+
phase = self._determine_phase(issue_num)
161+
labels = self._determine_labels(title, description, phase)
162+
priority = self._determine_priority(issue_num)
163+
164+
issues.append({
165+
'number': int(issue_num),
166+
'title': title,
167+
'description': description,
168+
'phase': phase,
169+
'labels': labels,
170+
'priority': priority
171+
})
172+
173+
return sorted(issues, key=lambda x: x['number'])
174+
175+
def _determine_phase(self, issue_num: str) -> str:
176+
"""Determine which phase an issue belongs to based on its number"""
177+
num = int(issue_num)
178+
if num <= 8:
179+
return 'Phase 1: Foundation & Core Features'
180+
elif num <= 15:
181+
return 'Phase 2: Advanced Features'
182+
elif num <= 19:
183+
return 'Phase 3: Enterprise & Scale'
184+
else:
185+
return 'Phase 4: Innovation & Research'
186+
187+
def _determine_labels(self, title: str, description: str, phase: str) -> List[str]:
188+
"""Determine appropriate labels for an issue"""
189+
labels = ['roadmap', 'enhancement']
190+
191+
# Phase labels
192+
if 'Phase 1' in phase:
193+
labels.append('phase-1')
194+
elif 'Phase 2' in phase:
195+
labels.append('phase-2')
196+
elif 'Phase 3' in phase:
197+
labels.append('phase-3')
198+
elif 'Phase 4' in phase:
199+
labels.append('phase-4')
200+
201+
# Category labels
202+
title_lower = title.lower()
203+
desc_lower = description.lower()
204+
205+
if any(word in title_lower or word in desc_lower for word in ['ml', 'model', 'training', 'data', 'accuracy']):
206+
labels.append('ml-model')
207+
208+
if any(word in title_lower or word in desc_lower for word in ['frontend', 'ui', 'mobile', 'app', 'lynx']):
209+
labels.append('frontend')
210+
211+
if any(word in title_lower or word in desc_lower for word in ['backend', 'api', 'server', 'fastapi']):
212+
labels.append('backend')
213+
214+
if any(word in title_lower or word in desc_lower for word in ['database', 'auth', 'convex', 'analytics']):
215+
labels.append('database')
216+
if 'analytics' in title_lower or 'analytics' in desc_lower:
217+
labels.append('analytics')
218+
219+
if any(word in title_lower or word in desc_lower for word in ['generation', 'compose', 'music', 'ai']):
220+
labels.append('ai-generation')
221+
222+
if any(word in title_lower or word in desc_lower for word in ['social', 'share', 'community', 'collaborative']):
223+
labels.append('social-features')
224+
225+
if any(word in title_lower or word in desc_lower for word in ['performance', 'optimization', 'scale', 'speed']):
226+
labels.append('performance')
227+
228+
if any(word in title_lower or word in desc_lower for word in ['research', 'education', 'academic', 'tutorial']):
229+
labels.append('research')
230+
231+
return labels
232+
233+
def _determine_priority(self, issue_num: str) -> str:
234+
"""Determine priority based on issue number"""
235+
num = int(issue_num)
236+
high_priority = [1, 2, 4, 7, 9]
237+
medium_priority = [3, 5, 8, 10, 12]
238+
239+
if num in high_priority:
240+
return 'high-priority'
241+
elif num in medium_priority:
242+
return 'medium-priority'
243+
else:
244+
return 'low-priority'
245+
246+
def create_issue(self, issue_data: Dict) -> bool:
247+
"""Create a GitHub issue using GitHub CLI"""
248+
issue_body = f"""## Issue #{issue_data['number']}: {issue_data['title']}
249+
250+
### Description
251+
{issue_data['description']}
252+
253+
### Phase
254+
{issue_data['phase']}
255+
256+
### Priority
257+
{issue_data['priority'].replace('-', ' ').title()}
258+
259+
### Acceptance Criteria
260+
- [ ] Feature implemented according to specifications
261+
- [ ] Code reviewed and approved
262+
- [ ] Tests written and passing
263+
- [ ] Documentation updated
264+
- [ ] Deployed to staging environment
265+
- [ ] User acceptance testing completed
266+
267+
### Technical Notes
268+
- This issue is part of the [RagaSense Development Roadmap](https://github.com/{REPO_OWNER}/{REPO_NAME}/blob/main/docs/ROADMAP.md)
269+
- Please refer to the roadmap for context and dependencies
270+
- Update progress in issue comments
271+
- Link related pull requests to this issue
272+
273+
### Related Resources
274+
- [Project Board](https://github.com/{REPO_OWNER}/{REPO_NAME}/projects)
275+
- [Wiki Documentation](https://github.com/{REPO_OWNER}/{REPO_NAME}/wiki)
276+
- [Contributing Guide](https://github.com/{REPO_OWNER}/{REPO_NAME}/blob/main/CONTRIBUTING.md)
277+
"""
278+
279+
# Create issue using GitHub CLI
280+
title = f"Issue #{issue_data['number']}: {issue_data['title']}"
281+
labels_str = ','.join(issue_data['labels'])
282+
283+
command = f"issue create --title '{title}' --body '{issue_body}' --label '{labels_str}'"
284+
285+
if self.milestones.get(issue_data['phase']):
286+
command += f" --milestone '{issue_data['phase']}'"
287+
288+
result = self.run_gh_command(command)
289+
290+
if result and 'url' in result:
291+
self.issues_created.append(result)
292+
print(f"✅ Created Issue #{issue_data['number']}: {issue_data['title']}")
293+
print(f" URL: {result['url']}")
294+
return True
295+
else:
296+
print(f"❌ Failed to create Issue #{issue_data['number']}: {issue_data['title']}")
297+
return False
298+
299+
def run(self):
300+
"""Main execution method"""
301+
print("🚀 Starting RagaSense Roadmap Issue Creation (GitHub CLI)...")
302+
print(f"Repository: {REPO_OWNER}/{REPO_NAME}")
303+
print()
304+
305+
# Create milestones
306+
print("📅 Creating milestones...")
307+
self.create_milestones()
308+
print()
309+
310+
# Create labels
311+
print("🏷️ Creating labels...")
312+
self.create_labels()
313+
print()
314+
315+
# Parse roadmap
316+
print("📋 Parsing roadmap file...")
317+
issues = self.parse_roadmap_file()
318+
print(f"Found {len(issues)} issues to create")
319+
print()
320+
321+
# Create issues
322+
print("🎯 Creating issues...")
323+
success_count = 0
324+
for issue_data in issues:
325+
if self.create_issue(issue_data):
326+
success_count += 1
327+
print()
328+
329+
# Summary
330+
print("📊 Summary:")
331+
print(f"✅ Successfully created {success_count}/{len(issues)} issues")
332+
print(f"📅 Created {len(self.milestones)} milestones")
333+
print()
334+
print("🔗 Next steps:")
335+
print(f"1. View all issues: https://github.com/{REPO_OWNER}/{REPO_NAME}/issues")
336+
print(f"2. Set up project board: https://github.com/{REPO_OWNER}/{REPO_NAME}/projects")
337+
print(f"3. Review roadmap: https://github.com/{REPO_OWNER}/{REPO_NAME}/blob/main/docs/ROADMAP.md")
338+
print()
339+
print("🎉 Roadmap issues creation complete!")
340+
341+
if __name__ == '__main__':
342+
creator = RoadmapIssueCreatorGH()
343+
creator.run()

0 commit comments

Comments
 (0)