refactor: remove obsolete issue management system in favor of issue-facade

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:
tegwick 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