feat: Implement comprehensive project management system with issue lifecycle support
- Add ProjectManager with milestone and label-based project organization - Support project states (Todo, Active, Review, Done, Blocked) via labels - Add priority management (Low, Medium, High, Critical) with label integration - Implement milestone creation and management for project tracking - Enhance IssueWriter with project management methods (assign_to_milestone, add/remove_labels) - Add 8 new CLI commands for complete project management workflow - Support automatic project management setup with ensure_project_labels() - Enable issue state transitions with automatic closing for completed issues - Integrate with existing Gitea API authentication and error handling patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -79,4 +79,60 @@ class IssueWriter:
|
||||
|
||||
def reopen_issue(self, issue_number: int) -> Dict[str, Any]:
|
||||
"""Reopen a closed issue."""
|
||||
return self.update_issue_state(issue_number, 'open')
|
||||
return self.update_issue_state(issue_number, 'open')
|
||||
|
||||
def assign_to_milestone(self, issue_number: int, milestone_id: int) -> Dict[str, Any]:
|
||||
"""Assign issue to a milestone (project)."""
|
||||
return self.update_issue(issue_number, {'milestone': milestone_id})
|
||||
|
||||
def remove_from_milestone(self, issue_number: int) -> Dict[str, Any]:
|
||||
"""Remove issue from its current milestone."""
|
||||
return self.update_issue(issue_number, {'milestone': None})
|
||||
|
||||
def update_labels(self, issue_number: int, labels: list) -> Dict[str, Any]:
|
||||
"""Update issue labels completely."""
|
||||
return self.update_issue(issue_number, {'labels': labels})
|
||||
|
||||
def add_labels(self, issue_number: int, new_labels: list) -> Dict[str, Any]:
|
||||
"""Add labels to issue (preserving existing labels)."""
|
||||
# First get current labels
|
||||
url = f"{self.config.issues_api_url}/{issue_number}"
|
||||
curl_cmd = [
|
||||
'curl', '-s', '-X', 'GET',
|
||||
'-H', f'Authorization: token {self.auth_token}',
|
||||
url
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(curl_cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True)
|
||||
issue_data = json.loads(result.stdout)
|
||||
current_labels = [label['name'] for label in issue_data.get('labels', [])]
|
||||
|
||||
# Add new labels (avoid duplicates)
|
||||
updated_labels = list(set(current_labels + new_labels))
|
||||
return self.update_labels(issue_number, updated_labels)
|
||||
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
|
||||
raise IssueError(f"Failed to add labels to issue #{issue_number}: {e}")
|
||||
|
||||
def remove_labels(self, issue_number: int, labels_to_remove: list) -> Dict[str, Any]:
|
||||
"""Remove specific labels from issue."""
|
||||
# First get current labels
|
||||
url = f"{self.config.issues_api_url}/{issue_number}"
|
||||
curl_cmd = [
|
||||
'curl', '-s', '-X', 'GET',
|
||||
'-H', f'Authorization: token {self.auth_token}',
|
||||
url
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(curl_cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True)
|
||||
issue_data = json.loads(result.stdout)
|
||||
current_labels = [label['name'] for label in issue_data.get('labels', [])]
|
||||
|
||||
# Remove specified labels
|
||||
updated_labels = [label for label in current_labels if label not in labels_to_remove]
|
||||
return self.update_labels(issue_number, updated_labels)
|
||||
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
|
||||
raise IssueError(f"Failed to remove labels from issue #{issue_number}: {e}")
|
||||
Reference in New Issue
Block a user