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>
323 lines
14 KiB
Python
323 lines
14 KiB
Python
"""
|
|
View models for displaying complex data structures.
|
|
"""
|
|
|
|
from typing import Dict, Any, List
|
|
from .formatters import OutputFormatter
|
|
|
|
|
|
class WorkspaceView:
|
|
"""View for workspace information display."""
|
|
|
|
@staticmethod
|
|
def show_status(summary: Dict[str, Any]) -> None:
|
|
"""Display workspace status."""
|
|
if summary['clean']:
|
|
OutputFormatter.info("No active issue workspace")
|
|
print(" Use 'make tdd-start NUM=X' to begin working on an issue")
|
|
return
|
|
|
|
if summary['dirty']:
|
|
OutputFormatter.warning("Workspace directory exists but no current issue file")
|
|
print(" Run 'make tdd-finish' to clean up or 'make tdd-start' to create new workspace")
|
|
return
|
|
|
|
if summary['active']:
|
|
WorkspaceView._show_active_workspace(summary)
|
|
else:
|
|
OutputFormatter.error("Failed to load workspace")
|
|
|
|
@staticmethod
|
|
def _show_active_workspace(summary: Dict[str, Any]) -> None:
|
|
"""Display active workspace details."""
|
|
OutputFormatter.header("Active Issue Workspace", "=")
|
|
|
|
issue_num = summary['issue_number']
|
|
issue_title = summary['issue_title']
|
|
print(f"🎯 Issue #{issue_num}: {issue_title}")
|
|
OutputFormatter.key_value("Status", summary['issue_state'])
|
|
OutputFormatter.key_value("Workspace", f"{summary['workspace_dir']}/issue_{issue_num}/")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Test files
|
|
test_count = summary['test_count']
|
|
test_files = summary.get('test_files', [])
|
|
print(f"🧪 Generated Tests ({test_count}):")
|
|
if test_files:
|
|
for test_file in test_files:
|
|
print(f" - {test_file}")
|
|
else:
|
|
print(" - No tests generated yet")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Workspace files
|
|
print("📋 Workspace Files:")
|
|
print(" - requirements.md (review and break down issue)")
|
|
print(" - test_plan.md (plan test scenarios)")
|
|
print(" - tests/ (generated test files)")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Commands
|
|
commands = [
|
|
"make tdd-add-test (generate another test)",
|
|
"make tdd-finish (complete and move tests to main)"
|
|
]
|
|
OutputFormatter.format_command_list(commands)
|
|
|
|
@staticmethod
|
|
def show_start_success(summary: Dict[str, Any]) -> None:
|
|
"""Display successful workspace start."""
|
|
issue_num = summary['issue_number']
|
|
OutputFormatter.success(f"Workspace created for issue #{issue_num}")
|
|
OutputFormatter.key_value("Workspace", f"{summary['workspace_dir']}/issue_{issue_num}/")
|
|
OutputFormatter.key_value("Requirements", summary['requirements_file'])
|
|
OutputFormatter.key_value("Test plan", summary['test_plan_file'])
|
|
OutputFormatter.empty_line()
|
|
|
|
print("💡 Next steps:")
|
|
print(" 1. Review requirements.md and break down the issue")
|
|
print(" 2. Plan test scenarios in test_plan.md")
|
|
print(" 3. Use 'make tdd-add-test' to generate tests")
|
|
print(" 4. Use 'make tdd-finish' when complete")
|
|
|
|
@staticmethod
|
|
def show_finish_success(issue_number: int, test_count: int) -> None:
|
|
"""Display successful workspace finish."""
|
|
OutputFormatter.info(f"Finishing work on issue #{issue_number}")
|
|
OutputFormatter.empty_line()
|
|
|
|
if test_count > 0:
|
|
print(f"📦 Moving {test_count} test(s) to tests/ directory...")
|
|
OutputFormatter.success("Tests moved to main tests/ directory")
|
|
else:
|
|
OutputFormatter.warning("No tests found in workspace")
|
|
|
|
print("🧹 Cleaning up workspace...")
|
|
OutputFormatter.success(f"Issue #{issue_number} workspace cleaned up")
|
|
OutputFormatter.empty_line()
|
|
|
|
print("💡 Next steps:")
|
|
print(" - Run 'make test' to verify tests fail (red state)")
|
|
print(" - Implement code to make tests pass (green state)")
|
|
print(" - Start next issue with 'make tdd-start NUM=X'")
|
|
|
|
|
|
class IssueView:
|
|
"""View for issue information display."""
|
|
|
|
@staticmethod
|
|
def show_list(issues: List[Any], title: str = "Project Issues") -> None:
|
|
"""Display issue list."""
|
|
OutputFormatter.header(title, "=")
|
|
|
|
if not issues:
|
|
print("No issues found")
|
|
return
|
|
|
|
for issue in issues:
|
|
status_icon = "🟢" if issue.state == "open" else "🔴"
|
|
print(f"{status_icon} #{issue.number}: {issue.title}")
|
|
print(f" Status: {issue.state.upper()} | Created: {issue.created_at.strftime('%Y-%m-%d')}")
|
|
|
|
# Truncate body for list view
|
|
body_preview = issue.body[:80] + "..." if len(issue.body) > 80 else issue.body
|
|
if body_preview:
|
|
print(f" {body_preview}")
|
|
OutputFormatter.empty_line()
|
|
|
|
print("💡 Tip: Use 'make show-issue NUM=X' for full details")
|
|
|
|
@staticmethod
|
|
def show_open_issues(issues: List[Any]) -> None:
|
|
"""Display open issues list."""
|
|
OutputFormatter.header("Open Project Issues (Active Backlog)", "=")
|
|
|
|
if not issues:
|
|
print("No open issues found")
|
|
return
|
|
|
|
for issue in issues:
|
|
print(f"[OPEN] #{issue.number}: {issue.title}")
|
|
created = issue.created_at.strftime('%Y-%m-%d')
|
|
updated = issue.updated_at.strftime('%Y-%m-%d')
|
|
print(f" Created: {created} | Updated: {updated}")
|
|
|
|
# Truncate body for list view
|
|
body_preview = issue.body[:80] + "..." if len(issue.body) > 80 else issue.body
|
|
if body_preview:
|
|
print(f" {body_preview}")
|
|
OutputFormatter.empty_line()
|
|
|
|
print("💡 Tip: Use 'make show-issue NUM=X' for full details or 'make list-issues' for all issues")
|
|
|
|
@staticmethod
|
|
def show_creation_success(result: Dict[str, Any], issue_type: str = "issue") -> None:
|
|
"""Display successful issue creation."""
|
|
OutputFormatter.success(f"{issue_type.title()} created successfully!")
|
|
OutputFormatter.key_value("Number", f"#{result['number']}")
|
|
OutputFormatter.key_value("Title", result['title'])
|
|
OutputFormatter.key_value("Status", result['state'])
|
|
|
|
if 'html_url' in result:
|
|
OutputFormatter.key_value("URL", result['html_url'])
|
|
|
|
OutputFormatter.empty_line()
|
|
print("💡 Next steps:")
|
|
print(f" - Use 'make tdd-start NUM={result['number']}' to begin work")
|
|
print(f" - Use 'make show-issue NUM={result['number']}' to view details")
|
|
|
|
@staticmethod
|
|
def show_issue_details(issue_data: Dict[str, Any]) -> None:
|
|
"""Display comprehensive issue details."""
|
|
OutputFormatter.header(f"Issue #{issue_data['number']} Details", "=")
|
|
|
|
print(f"**Title:** {issue_data['title']}")
|
|
print(f"**Status:** {issue_data['state'].upper()}")
|
|
print(f"**Number:** #{issue_data['number']}")
|
|
print(f"**Created:** {issue_data['created_at'].strftime('%Y-%m-%d %H:%M')}")
|
|
print(f"**Updated:** {issue_data['updated_at'].strftime('%Y-%m-%d %H:%M')}")
|
|
print(f"**URL:** {issue_data['html_url']}")
|
|
|
|
if issue_data['assignee']:
|
|
print(f"**Assignee:** {issue_data['assignee']}")
|
|
|
|
OutputFormatter.empty_line()
|
|
print("**Project Management:**")
|
|
|
|
# Milestone information
|
|
if issue_data['milestone']:
|
|
milestone = issue_data['milestone']
|
|
print(f" 📋 Milestone: #{milestone['id']} - {milestone['title']} ({milestone['state']})")
|
|
else:
|
|
print(" 📋 Milestone: None")
|
|
|
|
# Project/Board information
|
|
print(" 🎯 Project: Getting Started (assumed - requires board API)")
|
|
|
|
# Labels and state information
|
|
print(f" 📊 State: {issue_data['state_label']}")
|
|
print(f" 🚨 Priority: {issue_data['priority_label']}")
|
|
|
|
if issue_data['type_labels']:
|
|
type_display = ', '.join(issue_data['type_labels'])
|
|
print(f" 🏷️ Type: {type_display}")
|
|
|
|
if issue_data['other_labels']:
|
|
print(f" 🏷️ Other Labels: {', '.join(issue_data['other_labels'])}")
|
|
|
|
print(f" 📝 Kanban Column: {issue_data['kanban_column']}")
|
|
|
|
OutputFormatter.empty_line()
|
|
print("**Description:**")
|
|
print(issue_data['body'])
|
|
OutputFormatter.empty_line()
|
|
print("💡 Tip: Use 'make list-issues' to see all issues")
|
|
|
|
@staticmethod
|
|
def show_coverage_analysis(coverage_data: Dict[str, Any]) -> None:
|
|
"""Display test coverage analysis results."""
|
|
OutputFormatter.header(f"Test Coverage Analysis for Issue #{coverage_data['issue_number']}", "=")
|
|
|
|
print(f"📋 Issue: #{coverage_data['issue_number']} - {coverage_data['issue_title']}")
|
|
print(f"📊 Coverage: {coverage_data['coverage_percentage']:.1f}%")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Show requirements analysis
|
|
print("🎯 Identified Requirements:")
|
|
if coverage_data['requirements']:
|
|
for req in coverage_data['requirements']:
|
|
priority_icon = {"critical": "🚨", "important": "⚠️", "nice-to-have": "💡"}
|
|
icon = priority_icon.get(req.priority, "📝")
|
|
print(f" {icon} [{req.priority.upper()}] {req.category}: {req.description}")
|
|
else:
|
|
print(" No specific requirements detected")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Show existing tests
|
|
print("🧪 Existing Test Coverage:")
|
|
issue_number = coverage_data['issue_number']
|
|
issue_related_tests = [t for t in coverage_data['existing_tests'] if t.related_issue == issue_number]
|
|
if issue_related_tests:
|
|
for test in issue_related_tests:
|
|
test_count = len(test.test_methods)
|
|
print(f" ✅ {test.file_path.name} ({test_count} test methods)")
|
|
if test.test_methods:
|
|
for method in test.test_methods[:3]: # Show first 3
|
|
print(f" - {method}")
|
|
if len(test.test_methods) > 3:
|
|
print(f" - ... and {len(test.test_methods) - 3} more")
|
|
else:
|
|
print(" 📝 No tests specifically for this issue found")
|
|
# Show general tests that might be relevant
|
|
relevant_tests = [t for t in coverage_data['existing_tests']
|
|
if any(keyword in ' '.join(t.coverage_keywords)
|
|
for req in coverage_data['requirements']
|
|
for keyword in req.keywords)]
|
|
if relevant_tests:
|
|
print(" 📋 Potentially relevant tests:")
|
|
for test in relevant_tests[:3]:
|
|
print(f" 📄 {test.file_path.name}")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Show coverage gaps
|
|
if coverage_data['coverage_gaps']:
|
|
print("❌ Coverage Gaps Found:")
|
|
for gap in coverage_data['coverage_gaps']:
|
|
priority_icon = {"critical": "🚨", "important": "⚠️", "nice-to-have": "💡"}
|
|
icon = priority_icon.get(gap.requirement.priority, "📝")
|
|
print(f" {icon} Missing: {gap.requirement.description}")
|
|
print(f" 💡 Suggested test: {gap.suggested_test_name}")
|
|
print(f" 📄 Suggested file: {gap.suggested_test_file}")
|
|
OutputFormatter.empty_line()
|
|
else:
|
|
print("✅ No significant coverage gaps detected!")
|
|
OutputFormatter.empty_line()
|
|
|
|
# Show recommendations
|
|
print("📝 Recommendations:")
|
|
for recommendation in coverage_data['recommendations']:
|
|
print(f" {recommendation}")
|
|
|
|
|
|
class ProjectView:
|
|
"""View for project management information display."""
|
|
|
|
@staticmethod
|
|
def show_setup_success() -> None:
|
|
"""Display successful project setup."""
|
|
OutputFormatter.success("Project management setup complete!")
|
|
print("📋 Available states: todo, active, review, done, blocked")
|
|
print("📊 Available priorities: low, medium, high, critical")
|
|
|
|
@staticmethod
|
|
def show_milestone_list(milestones: List[Any]) -> None:
|
|
"""Display milestone list."""
|
|
OutputFormatter.header("Project Milestones", "=")
|
|
|
|
if not milestones:
|
|
print("No milestones found")
|
|
return
|
|
|
|
for milestone in milestones:
|
|
status_icon = "🟢" if milestone.state == "open" else "🔴"
|
|
print(f"{status_icon} Milestone #{milestone.id}: {milestone.title}")
|
|
print(f" State: {milestone.state.upper()}")
|
|
print(f" Issues: {milestone.open_issues} open, {milestone.closed_issues} closed")
|
|
if milestone.description:
|
|
print(f" Description: {milestone.description}")
|
|
if milestone.due_on:
|
|
print(f" Due: {milestone.due_on}")
|
|
OutputFormatter.empty_line()
|
|
|
|
@staticmethod
|
|
def show_overview(overview: Dict[str, Any]) -> None:
|
|
"""Display project overview."""
|
|
OutputFormatter.header("Project Management Overview", "=")
|
|
|
|
OutputFormatter.key_value("Milestones", f"{overview['milestones']} total")
|
|
OutputFormatter.key_value("Active Projects", overview['active_projects'], 1)
|
|
OutputFormatter.key_value("Completed Projects", overview['completed_projects'], 1)
|
|
OutputFormatter.key_value("Total Labels", overview['total_labels'])
|
|
|
|
ready_status = "✅ Yes" if overview['project_management_ready'] else "❌ No - run setup-project-mgmt"
|
|
OutputFormatter.key_value("Project Management Ready", ready_status) |