Complete architectural separation of concerns implementing clean layered design: • Services Layer: Pure business logic isolated from presentation - WorkspaceService: TDD workspace operations - IssueService: Issue management and creation - ProjectService: Project management and milestones - ExportService: Unix-friendly data export • CLI Layer: Clean presentation with command/presenter separation - Commands delegate to services for all business operations - Presenters handle formatted output and error messaging - Framework provides unified interface • Benefits: - Eliminates mixed concerns in 943-line CLI monolith - Enables easier testing and maintenance - Preserves all existing functionality and Unix pipeline compatibility - Provides foundation for future CLI development Resolves issue #20: CLI separation from core logic 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
116 lines
3.7 KiB
Python
116 lines
3.7 KiB
Python
"""
|
|
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 |