refactor: remove obsolete issue management system in favor of issue-facade
Some checks failed
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled

Complete cleanup of the legacy TDD AI and issue management system, establishing clear separation of concerns as requested. All issue handling is now provided by the standalone issue-facade system.

Removed components:
- TDD AI framework (tddai/ directory and tddai_cli.py)
- Legacy issue management CLI commands and services
- Issue-related Makefile targets and helper commands
- Obsolete tests and infrastructure dependencies
- Finance modules that depended on the old issue system

Updated:
- Makefile: Removed issue-*, tdd-*, and test-from-issue commands
- CLI framework: Simplified to core functionality only
- Documentation: Added deprecation notice for old config system

The issue-facade now serves as the universal CLI for issue tracking,
providing backend-agnostic interface to GitHub, GitLab, Gitea, and
local SQLite storage as documented in issue-facade/README.md.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-24 21:25:04 +02:00
parent cb94c92fc0
commit a8e5b4b044
58 changed files with 11 additions and 14628 deletions

View File

@@ -1,178 +0,0 @@
"""
Issue service - business logic for issue operations.
"""
from typing import List, Dict, Any, Optional
from datetime import datetime
from tddai import IssueFetcher, TddaiError
from tddai.issue_creator import IssueCreator
from gitea.models import Issue, Priority
class IssueService:
"""Service for issue operations."""
def __init__(self):
self.issue_fetcher = IssueFetcher()
self.issue_creator = IssueCreator()
def get_issue(self, issue_number: int) -> Issue:
"""Get a specific issue by number."""
return self.issue_fetcher.fetch_issue(issue_number)
def list_issues(self, state: str = "all") -> List[Issue]:
"""List issues with optional state filter."""
return self.issue_fetcher.fetch_issues(state)
def list_open_issues(self) -> List[Issue]:
"""List only open issues."""
return self.issue_fetcher.fetch_open_issues()
def create_issue(self, title: str, body: str, **kwargs) -> Dict[str, Any]:
"""Create a new issue."""
return self.issue_creator.create_issue(title, body, **kwargs)
def create_enhancement_issue(self, title: str, use_case: str,
technical_requirements: str = "",
acceptance_criteria: Optional[List[str]] = None,
dependencies: Optional[List[str]] = None,
priority: str = "Medium") -> Dict[str, Any]:
"""Create a structured enhancement issue."""
return self.issue_creator.create_enhancement_issue(
title, use_case, technical_requirements,
acceptance_criteria, dependencies, priority
)
def create_from_template(self, template_file: str, **kwargs) -> Dict[str, Any]:
"""Create issue from template file."""
return self.issue_creator.create_from_template(template_file, **kwargs)
def close_issue(self, issue_number: int, comment: str = "") -> Dict[str, Any]:
"""Close an issue with optional comment."""
from gitea import GiteaClient, GiteaConfig
from tddai.config import get_config
import os
# Get config and create Gitea client
config = get_config()
gitea_config = GiteaConfig.from_tddai_config(config)
auth_token = os.getenv('GITEA_API_TOKEN')
if auth_token:
gitea_config.auth_token = auth_token
gitea_client = GiteaClient(gitea_config)
# Close the issue
issue = gitea_client.issues.close(issue_number)
# If comment provided, add it (this would need API support for comments)
# For now, we'll just close the issue
# Convert to dict format for consistency
return gitea_client.issues.to_dict(issue)
def get_issue_details(self, issue_number: int) -> Dict[str, Any]:
"""Get comprehensive issue details for display purposes."""
issue = self.get_issue(issue_number)
# Get additional project management information
from tddai.project_manager import ProjectManager
project_mgr = ProjectManager()
# Get detailed issue data via API for milestone and project information
from tddai.config import get_config
config = get_config()
issue_url = f"{config.issues_api_url}/{issue_number}"
detailed_issue = project_mgr._make_api_call('GET', issue_url)
# Process labels
labels = detailed_issue.get('labels', [])
state_labels = [l['name'] for l in labels if l['name'].startswith('status:')]
priority_labels = [l['name'] for l in labels if l['name'].startswith('priority:')]
type_labels = [l['name'] for l in labels if l['name'].startswith('type:')]
other_labels = [l['name'] for l in labels if not any(l['name'].startswith(p) for p in ['status:', 'priority:', 'type:'])]
# Determine project column/state
if detailed_issue.get('state') == 'closed':
if any(l['name'] == 'status:done' for l in labels):
column = "Done"
else:
column = "Closed"
else:
state_labels = [l['name'] for l in labels if l['name'].startswith('status:')]
if state_labels:
state = state_labels[0].replace('status:', '')
column_map = {
'todo': 'Todo',
'active': 'Active',
'review': 'Review',
'blocked': 'Blocked'
}
column = column_map.get(state, 'Todo')
else:
column = "Todo"
return {
'number': issue.number,
'title': issue.title,
'body': issue.body,
'state': issue.state,
'created_at': issue.created_at,
'updated_at': issue.updated_at,
'html_url': issue.html_url,
'assignee': issue.assignee.login if issue.assignee else None,
'milestone': detailed_issue.get('milestone'),
'state_label': state_labels[0].replace('status:', '').title() if state_labels else "No state label",
'priority_label': priority_labels[0].replace('priority:', '').title() if priority_labels else "No priority set",
'type_labels': [l.replace('type:', '').title() for l in type_labels],
'other_labels': other_labels,
'kanban_column': column
}
def get_issue_summary(self, issue_number: int) -> Dict[str, Any]:
"""Get issue summary for display purposes."""
issue = self.get_issue(issue_number)
return {
'number': issue.number,
'title': issue.title,
'body': issue.body,
'state': issue.state,
'priority': issue.priority,
'status': issue.status,
'created_at': issue.created_at,
'updated_at': issue.updated_at,
'html_url': issue.html_url,
'assignee': issue.assignee.login if issue.assignee else None,
'labels': [label.name for label in issue.labels],
'has_milestone': issue.milestone is not None,
'milestone_title': issue.milestone.title if issue.milestone else None
}
def analyze_coverage(self, issue_number: int) -> Dict[str, Any]:
"""Analyze test coverage for a specific issue.
Args:
issue_number: The issue number to analyze
Returns:
Coverage analysis data for the issue
Raises:
IssueError: When coverage analysis fails
"""
from tddai.coverage_analyzer import CoverageAnalyzer
analyzer = CoverageAnalyzer()
assessment = analyzer.analyze_issue_coverage(issue_number)
return {
'issue_number': assessment.issue_number,
'issue_title': assessment.issue_title,
'coverage_percentage': assessment.coverage_percentage,
'requirements': assessment.requirements,
'existing_tests': assessment.existing_tests,
'coverage_gaps': assessment.coverage_gaps,
'recommendations': assessment.recommendations
}

View File

@@ -1,76 +0,0 @@
"""
Project service - business logic for project management operations.
"""
from typing import List, Dict, Any, Optional
from tddai.project_manager import ProjectManager, ProjectState, Priority, Milestone, Label
from tddai import TddaiError
class ProjectService:
"""Service for project management operations."""
def __init__(self):
self.project_manager = ProjectManager()
def setup_project_management(self) -> None:
"""Setup project management labels and structure."""
self.project_manager.ensure_project_labels()
def create_milestone(self, title: str, description: str = "", due_date: Optional[str] = None) -> Milestone:
"""Create a new milestone (project)."""
return self.project_manager.create_milestone(title, description, due_date)
def list_milestones(self, state: str = "open") -> List[Milestone]:
"""List milestones."""
return self.project_manager.list_milestones(state)
def list_labels(self) -> List[Label]:
"""List repository labels."""
return self.project_manager.list_labels()
def set_issue_state(self, issue_number: int, state_name: str) -> Dict[str, Any]:
"""Set issue project state."""
# Convert string to ProjectState enum
state_map = {
'todo': ProjectState.TODO,
'active': ProjectState.ACTIVE,
'review': ProjectState.REVIEW,
'done': ProjectState.DONE,
'blocked': ProjectState.BLOCKED
}
if state_name not in state_map:
raise TddaiError(f"Invalid state '{state_name}'. Valid states: {list(state_map.keys())}")
project_state = state_map[state_name]
return self.project_manager.set_issue_state(issue_number, project_state)
def set_issue_priority(self, issue_number: int, priority_name: str) -> Dict[str, Any]:
"""Set issue priority."""
# Convert string to Priority enum
priority_map = {
'low': Priority.LOW,
'medium': Priority.MEDIUM,
'high': Priority.HIGH,
'critical': Priority.CRITICAL
}
if priority_name not in priority_map:
raise TddaiError(f"Invalid priority '{priority_name}'. Valid priorities: {list(priority_map.keys())}")
priority_level = priority_map[priority_name]
return self.project_manager.set_issue_priority(issue_number, priority_level)
def move_issue_to_done(self, issue_number: int) -> Dict[str, Any]:
"""Move issue to done state and close it."""
return self.project_manager.move_issue_to_done(issue_number)
def assign_issue_to_milestone(self, issue_number: int, milestone_id: int) -> Dict[str, Any]:
"""Assign issue to a milestone."""
return self.project_manager.assign_issue_to_milestone(issue_number, milestone_id)
def get_project_overview(self) -> Dict[str, Any]:
"""Get project management overview."""
return self.project_manager.get_project_overview()

View File

@@ -1,116 +0,0 @@
"""
Workspace service - business logic for TDD workspace operations.
"""
from typing import Optional, Dict, Any
from pathlib import Path
from tddai import WorkspaceManager, IssueFetcher, WorkspaceStatus, TddaiError
class WorkspaceInfo:
"""Value object for workspace information."""
def __init__(self, status: WorkspaceStatus, workspace=None):
self.status = status
self.workspace = workspace
@property
def is_clean(self) -> bool:
return self.status == WorkspaceStatus.CLEAN
@property
def is_dirty(self) -> bool:
return self.status == WorkspaceStatus.DIRTY
@property
def is_active(self) -> bool:
return self.status == WorkspaceStatus.ACTIVE
def get_test_files(self) -> list:
"""Get list of test files in workspace."""
if not self.workspace or not self.workspace.tests_dir.exists():
return []
return list(self.workspace.tests_dir.glob("*.py"))
class WorkspaceService:
"""Service for workspace operations."""
def __init__(self):
self.workspace_manager = WorkspaceManager()
self.issue_fetcher = IssueFetcher()
def get_workspace_info(self) -> WorkspaceInfo:
"""Get current workspace information."""
status = self.workspace_manager.get_status()
workspace = None
if status == WorkspaceStatus.ACTIVE:
workspace = self.workspace_manager.get_current_workspace()
return WorkspaceInfo(status, workspace)
def start_issue_workspace(self, issue_number: int) -> WorkspaceInfo:
"""Start working on an issue.
Returns:
WorkspaceInfo with the created workspace
Raises:
TddaiError: If workspace already active or issue cannot be fetched
"""
# Check if workspace already active
current_info = self.get_workspace_info()
if current_info.is_active:
raise TddaiError(f"Already working on issue #{current_info.workspace.issue_number}")
# Fetch issue data
issue_data = self.issue_fetcher.get_issue_data_dict(issue_number)
# Create workspace
workspace = self.workspace_manager.create_workspace(issue_data)
return WorkspaceInfo(WorkspaceStatus.ACTIVE, workspace)
def finish_current_workspace(self) -> Optional[int]:
"""Finish current workspace and return the issue number.
Returns:
Issue number that was finished, or None if no active workspace
Raises:
TddaiError: If workspace operations fail
"""
current_info = self.get_workspace_info()
if not current_info.is_active:
return None
issue_number = current_info.workspace.issue_number
self.workspace_manager.finish_workspace()
return issue_number
def get_workspace_summary(self) -> Dict[str, Any]:
"""Get workspace summary for display purposes."""
info = self.get_workspace_info()
summary = {
'status': info.status,
'active': info.is_active,
'clean': info.is_clean,
'dirty': info.is_dirty
}
if info.workspace:
test_files = info.get_test_files()
summary.update({
'issue_number': info.workspace.issue_number,
'issue_title': info.workspace.issue_title,
'issue_state': info.workspace.issue_state,
'workspace_dir': str(info.workspace.workspace_dir),
'test_count': len(test_files),
'test_files': [f.name for f in test_files],
'requirements_file': str(info.workspace.requirements_file),
'test_plan_file': str(info.workspace.test_plan_file)
})
return summary