refactor: Standardize error handling patterns across codebase

Comprehensive error handling improvements addressing inconsistent patterns:

• Created markitect/exceptions.py with complete domain-specific exception hierarchy
  - MarkitectError base class with context and cause chaining support
  - Specific exceptions for Document, AST, Cache, Database, Schema operations
  - Built-in logging and context preservation

• Fixed overly broad exception handling in tddai modules:
  - issue_fetcher.py: Replace generic Exception with specific Gitea errors
  - project_manager.py: Proper error translation with context preservation
  - coverage_analyzer.py: Replace silent suppression with logging

• Enhanced cache_service.py error handling:
  - Specific OSError/PermissionError handling for file operations
  - Logging integration for unexpected errors
  - Preserved error collection and reporting

• Implemented proper exception chaining patterns:
  - All error translations use `raise ... from e` for debugging
  - Preserved original exception context and stack traces
  - Added docstring declarations of raised exceptions

• Benefits:
  - Eliminates silent error suppression and debugging black holes
  - Provides specific, actionable error messages
  - Preserves full error context for troubleshooting
  - Establishes consistent patterns for future development

Resolves issue #21: Error handling standardization

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-26 16:35:13 +02:00
parent 7f5309c4b0
commit bbc6192fe1
7 changed files with 420 additions and 18 deletions

View File

@@ -48,6 +48,64 @@ class IssueService:
"""Create issue from template file."""
return self.issue_creator.create_from_template(template_file, **kwargs)
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)
@@ -66,4 +124,31 @@ class IssueService:
'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
}