claude-mpm 3.7.8__py3-none-any.whl → 3.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +0 -106
- claude_mpm/agents/INSTRUCTIONS.md +0 -96
- claude_mpm/agents/MEMORY.md +94 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +3 -8
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +2 -2
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +8 -3
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +217 -5
- claude_mpm/config/__init__.py +30 -39
- claude_mpm/config/socketio_config.py +8 -5
- claude_mpm/constants.py +13 -0
- claude_mpm/core/__init__.py +8 -18
- claude_mpm/core/cache.py +596 -0
- claude_mpm/core/claude_runner.py +166 -622
- claude_mpm/core/config.py +7 -3
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +548 -38
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +249 -93
- claude_mpm/core/interactive_session.py +479 -0
- claude_mpm/core/interfaces.py +424 -0
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/logging_config.py +444 -0
- claude_mpm/core/oneshot_session.py +465 -0
- claude_mpm/core/optimized_agent_loader.py +485 -0
- claude_mpm/core/optimized_startup.py +490 -0
- claude_mpm/core/service_registry.py +52 -26
- claude_mpm/core/socketio_pool.py +162 -5
- claude_mpm/core/types.py +292 -0
- claude_mpm/core/typing_utils.py +477 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
- claude_mpm/init.py +2 -1
- claude_mpm/services/__init__.py +78 -14
- claude_mpm/services/agent/__init__.py +24 -0
- claude_mpm/services/agent/deployment.py +2548 -0
- claude_mpm/services/agent/management.py +598 -0
- claude_mpm/services/agent/registry.py +813 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +728 -308
- claude_mpm/services/agents/memory/agent_memory_manager.py +160 -4
- claude_mpm/services/async_session_logger.py +8 -3
- claude_mpm/services/communication/__init__.py +21 -0
- claude_mpm/services/communication/socketio.py +1933 -0
- claude_mpm/services/communication/websocket.py +479 -0
- claude_mpm/services/core/__init__.py +123 -0
- claude_mpm/services/core/base.py +247 -0
- claude_mpm/services/core/interfaces.py +951 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
- claude_mpm/services/framework_claude_md_generator.py +3 -2
- claude_mpm/services/health_monitor.py +4 -3
- claude_mpm/services/hook_service.py +64 -4
- claude_mpm/services/infrastructure/__init__.py +21 -0
- claude_mpm/services/infrastructure/logging.py +202 -0
- claude_mpm/services/infrastructure/monitoring.py +893 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/project/__init__.py +21 -0
- claude_mpm/services/project/analyzer.py +864 -0
- claude_mpm/services/project/registry.py +608 -0
- claude_mpm/services/project_analyzer.py +95 -2
- claude_mpm/services/recovery_manager.py +15 -9
- claude_mpm/services/response_tracker.py +3 -5
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/base.py +121 -0
- claude_mpm/services/socketio/handlers/connection.py +198 -0
- claude_mpm/services/socketio/handlers/file.py +213 -0
- claude_mpm/services/socketio/handlers/git.py +723 -0
- claude_mpm/services/socketio/handlers/memory.py +27 -0
- claude_mpm/services/socketio/handlers/project.py +25 -0
- claude_mpm/services/socketio/handlers/registry.py +145 -0
- claude_mpm/services/socketio_client_manager.py +12 -7
- claude_mpm/services/socketio_server.py +156 -30
- claude_mpm/services/ticket_manager.py +172 -9
- claude_mpm/services/ticket_manager_di.py +1 -1
- claude_mpm/services/version_control/semantic_versioning.py +80 -7
- claude_mpm/services/version_control/version_parser.py +528 -0
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/METADATA +38 -128
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/RECORD +100 -59
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/top_level.txt +0 -0
| @@ -4,13 +4,12 @@ from pathlib import Path | |
| 4 4 | 
             
            from typing import Optional, Dict, Any, List
         | 
| 5 5 | 
             
            from datetime import datetime
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
                from ..core.logger import get_logger
         | 
| 9 | 
            -
            except ImportError:
         | 
| 10 | 
            -
                from core.logger import get_logger
         | 
| 7 | 
            +
            from claude_mpm.core.logging_config import get_logger
         | 
| 11 8 |  | 
| 9 | 
            +
            from ..core.interfaces import TicketManagerInterface
         | 
| 12 10 |  | 
| 13 | 
            -
             | 
| 11 | 
            +
             | 
| 12 | 
            +
            class TicketManager(TicketManagerInterface):
         | 
| 14 13 | 
             
                """
         | 
| 15 14 | 
             
                Manage ticket creation using ai-trackdown-pytools.
         | 
| 16 15 |  | 
| @@ -25,7 +24,7 @@ class TicketManager: | |
| 25 24 | 
             
                    Args:
         | 
| 26 25 | 
             
                        project_path: Project root (defaults to current directory)
         | 
| 27 26 | 
             
                    """
         | 
| 28 | 
            -
                    self.logger = get_logger( | 
| 27 | 
            +
                    self.logger = get_logger(__name__)
         | 
| 29 28 | 
             
                    self.project_path = project_path or Path.cwd()
         | 
| 30 29 | 
             
                    self.task_manager = self._init_task_manager()
         | 
| 31 30 |  | 
| @@ -50,7 +49,7 @@ class TicketManager: | |
| 50 49 | 
             
                        # Check if config exists, create if needed
         | 
| 51 50 | 
             
                        if not config_file.exists():
         | 
| 52 51 | 
             
                            try:
         | 
| 53 | 
            -
                                config = Config.create_default( | 
| 52 | 
            +
                                config = Config.create_default(config_file)  # Pass Path object directly
         | 
| 54 53 | 
             
                                config.set("paths.tickets_dir", "tickets")
         | 
| 55 54 | 
             
                                config.set("paths.epics_dir", "tickets/epics")
         | 
| 56 55 | 
             
                                config.set("paths.issues_dir", "tickets/issues")
         | 
| @@ -65,7 +64,7 @@ class TicketManager: | |
| 65 64 |  | 
| 66 65 | 
             
                        # Initialize TaskManager directly with the project path
         | 
| 67 66 | 
             
                        # TaskManager will handle project initialization internally
         | 
| 68 | 
            -
                        task_manager = TaskManager( | 
| 67 | 
            +
                        task_manager = TaskManager(self.project_path)  # Pass Path object directly
         | 
| 69 68 |  | 
| 70 69 | 
             
                        # Verify it's using the right directory
         | 
| 71 70 | 
             
                        if hasattr(task_manager, 'tasks_dir'):
         | 
| @@ -373,4 +372,168 @@ class TicketManager: | |
| 373 372 | 
             
                        self.logger.error(f"Failed to get ticket {ticket_id}: {e.__class__.__name__}: {e}")
         | 
| 374 373 | 
             
                        self.logger.info(f"Try using the CLI: aitrackdown show {ticket_id}")
         | 
| 375 374 | 
             
                        self.logger.debug("Full error details:", exc_info=True)
         | 
| 376 | 
            -
                        return None
         | 
| 375 | 
            +
                        return None
         | 
| 376 | 
            +
                
         | 
| 377 | 
            +
                # ================================================================================
         | 
| 378 | 
            +
                # Interface Adapter Methods
         | 
| 379 | 
            +
                # ================================================================================
         | 
| 380 | 
            +
                # These methods adapt the existing implementation to comply with TicketManagerInterface
         | 
| 381 | 
            +
                
         | 
| 382 | 
            +
                def create_task(self, title: str, description: str, **kwargs) -> Optional[str]:
         | 
| 383 | 
            +
                    """Create a new task ticket.
         | 
| 384 | 
            +
                    
         | 
| 385 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 386 | 
            +
                    the underlying task manager's create functionality.
         | 
| 387 | 
            +
                    
         | 
| 388 | 
            +
                    Args:
         | 
| 389 | 
            +
                        title: Task title
         | 
| 390 | 
            +
                        description: Task description
         | 
| 391 | 
            +
                        **kwargs: Additional task properties
         | 
| 392 | 
            +
                        
         | 
| 393 | 
            +
                    Returns:
         | 
| 394 | 
            +
                        Task ID if created successfully, None otherwise
         | 
| 395 | 
            +
                    """
         | 
| 396 | 
            +
                    if not self.task_manager:
         | 
| 397 | 
            +
                        self.logger.error("Task manager not initialized")
         | 
| 398 | 
            +
                        return None
         | 
| 399 | 
            +
                    
         | 
| 400 | 
            +
                    try:
         | 
| 401 | 
            +
                        # Create task using ai-trackdown-pytools
         | 
| 402 | 
            +
                        from ai_trackdown_pytools.core.task import Task
         | 
| 403 | 
            +
                        
         | 
| 404 | 
            +
                        task = Task(
         | 
| 405 | 
            +
                            title=title,
         | 
| 406 | 
            +
                            description=description,
         | 
| 407 | 
            +
                            status=kwargs.get('status', 'open'),
         | 
| 408 | 
            +
                            priority=kwargs.get('priority', 'medium'),
         | 
| 409 | 
            +
                            tags=kwargs.get('tags', []),
         | 
| 410 | 
            +
                            assignees=kwargs.get('assignees', [])
         | 
| 411 | 
            +
                        )
         | 
| 412 | 
            +
                        
         | 
| 413 | 
            +
                        # Save the task
         | 
| 414 | 
            +
                        task_id = self.task_manager.create_task(task)
         | 
| 415 | 
            +
                        self.logger.info(f"Created task {task_id}: {title}")
         | 
| 416 | 
            +
                        return task_id
         | 
| 417 | 
            +
                        
         | 
| 418 | 
            +
                    except ImportError:
         | 
| 419 | 
            +
                        self.logger.error("ai-trackdown-pytools not available")
         | 
| 420 | 
            +
                        return None
         | 
| 421 | 
            +
                    except Exception as e:
         | 
| 422 | 
            +
                        self.logger.error(f"Failed to create task: {e}")
         | 
| 423 | 
            +
                        return None
         | 
| 424 | 
            +
                
         | 
| 425 | 
            +
                def update_task(self, task_id: str, **updates) -> bool:
         | 
| 426 | 
            +
                    """Update an existing task.
         | 
| 427 | 
            +
                    
         | 
| 428 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 429 | 
            +
                    task update operations.
         | 
| 430 | 
            +
                    
         | 
| 431 | 
            +
                    Args:
         | 
| 432 | 
            +
                        task_id: ID of task to update
         | 
| 433 | 
            +
                        **updates: Fields to update
         | 
| 434 | 
            +
                        
         | 
| 435 | 
            +
                    Returns:
         | 
| 436 | 
            +
                        True if update successful
         | 
| 437 | 
            +
                    """
         | 
| 438 | 
            +
                    if not self.task_manager:
         | 
| 439 | 
            +
                        self.logger.error("Task manager not initialized")
         | 
| 440 | 
            +
                        return False
         | 
| 441 | 
            +
                    
         | 
| 442 | 
            +
                    try:
         | 
| 443 | 
            +
                        # Get the existing task
         | 
| 444 | 
            +
                        task = self.task_manager.get_task(task_id)
         | 
| 445 | 
            +
                        if not task:
         | 
| 446 | 
            +
                            self.logger.error(f"Task {task_id} not found")
         | 
| 447 | 
            +
                            return False
         | 
| 448 | 
            +
                        
         | 
| 449 | 
            +
                        # Apply updates
         | 
| 450 | 
            +
                        for key, value in updates.items():
         | 
| 451 | 
            +
                            if hasattr(task, key):
         | 
| 452 | 
            +
                                setattr(task, key, value)
         | 
| 453 | 
            +
                        
         | 
| 454 | 
            +
                        # Save the updated task
         | 
| 455 | 
            +
                        self.task_manager.update_task(task)
         | 
| 456 | 
            +
                        self.logger.info(f"Updated task {task_id}")
         | 
| 457 | 
            +
                        return True
         | 
| 458 | 
            +
                        
         | 
| 459 | 
            +
                    except Exception as e:
         | 
| 460 | 
            +
                        self.logger.error(f"Failed to update task {task_id}: {e}")
         | 
| 461 | 
            +
                        return False
         | 
| 462 | 
            +
                
         | 
| 463 | 
            +
                def get_task(self, task_id: str) -> Optional[Dict[str, Any]]:
         | 
| 464 | 
            +
                    """Get task details.
         | 
| 465 | 
            +
                    
         | 
| 466 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 467 | 
            +
                    the existing get_ticket method.
         | 
| 468 | 
            +
                    
         | 
| 469 | 
            +
                    Args:
         | 
| 470 | 
            +
                        task_id: ID of task to retrieve
         | 
| 471 | 
            +
                        
         | 
| 472 | 
            +
                    Returns:
         | 
| 473 | 
            +
                        Task data dictionary or None if not found
         | 
| 474 | 
            +
                    """
         | 
| 475 | 
            +
                    # Use existing get_ticket method which already returns dict format
         | 
| 476 | 
            +
                    return self.get_ticket(task_id)
         | 
| 477 | 
            +
                
         | 
| 478 | 
            +
                def list_tasks(self, status: Optional[str] = None, **filters) -> List[Dict[str, Any]]:
         | 
| 479 | 
            +
                    """List tasks with optional filtering.
         | 
| 480 | 
            +
                    
         | 
| 481 | 
            +
                    WHY: This adapter method provides interface compliance by wrapping
         | 
| 482 | 
            +
                    task listing operations.
         | 
| 483 | 
            +
                    
         | 
| 484 | 
            +
                    Args:
         | 
| 485 | 
            +
                        status: Optional status filter
         | 
| 486 | 
            +
                        **filters: Additional filter criteria
         | 
| 487 | 
            +
                        
         | 
| 488 | 
            +
                    Returns:
         | 
| 489 | 
            +
                        List of task dictionaries
         | 
| 490 | 
            +
                    """
         | 
| 491 | 
            +
                    if not self.task_manager:
         | 
| 492 | 
            +
                        self.logger.error("Task manager not initialized")
         | 
| 493 | 
            +
                        return []
         | 
| 494 | 
            +
                    
         | 
| 495 | 
            +
                    try:
         | 
| 496 | 
            +
                        # Get all tasks
         | 
| 497 | 
            +
                        tasks = self.task_manager.list_tasks()
         | 
| 498 | 
            +
                        
         | 
| 499 | 
            +
                        # Apply filters
         | 
| 500 | 
            +
                        filtered_tasks = []
         | 
| 501 | 
            +
                        for task in tasks:
         | 
| 502 | 
            +
                            # Check status filter
         | 
| 503 | 
            +
                            if status and task.get('status') != status:
         | 
| 504 | 
            +
                                continue
         | 
| 505 | 
            +
                            
         | 
| 506 | 
            +
                            # Check additional filters
         | 
| 507 | 
            +
                            match = True
         | 
| 508 | 
            +
                            for key, value in filters.items():
         | 
| 509 | 
            +
                                if task.get(key) != value:
         | 
| 510 | 
            +
                                    match = False
         | 
| 511 | 
            +
                                    break
         | 
| 512 | 
            +
                            
         | 
| 513 | 
            +
                            if match:
         | 
| 514 | 
            +
                                filtered_tasks.append(task)
         | 
| 515 | 
            +
                        
         | 
| 516 | 
            +
                        return filtered_tasks
         | 
| 517 | 
            +
                        
         | 
| 518 | 
            +
                    except Exception as e:
         | 
| 519 | 
            +
                        self.logger.error(f"Failed to list tasks: {e}")
         | 
| 520 | 
            +
                        return []
         | 
| 521 | 
            +
                
         | 
| 522 | 
            +
                def close_task(self, task_id: str, resolution: Optional[str] = None) -> bool:
         | 
| 523 | 
            +
                    """Close a task.
         | 
| 524 | 
            +
                    
         | 
| 525 | 
            +
                    WHY: This adapter method provides interface compliance by updating
         | 
| 526 | 
            +
                    task status to closed.
         | 
| 527 | 
            +
                    
         | 
| 528 | 
            +
                    Args:
         | 
| 529 | 
            +
                        task_id: ID of task to close
         | 
| 530 | 
            +
                        resolution: Optional resolution description
         | 
| 531 | 
            +
                        
         | 
| 532 | 
            +
                    Returns:
         | 
| 533 | 
            +
                        True if close successful
         | 
| 534 | 
            +
                    """
         | 
| 535 | 
            +
                    updates = {'status': 'closed'}
         | 
| 536 | 
            +
                    if resolution:
         | 
| 537 | 
            +
                        updates['resolution'] = resolution
         | 
| 538 | 
            +
                    
         | 
| 539 | 
            +
                    return self.update_task(task_id, **updates)
         | 
| @@ -56,7 +56,7 @@ class AITrackdownAdapter(ITaskManagerAdapter): | |
| 56 56 | 
             
                        # Configure ai-trackdown if needed
         | 
| 57 57 | 
             
                        config_file = self.project_path / ".trackdown.yaml"
         | 
| 58 58 | 
             
                        if not config_file.exists():
         | 
| 59 | 
            -
                            config = TrackdownConfig.create_default(config_file)
         | 
| 59 | 
            +
                            config = TrackdownConfig.create_default(config_file)  # Already correct - using Path object
         | 
| 60 60 | 
             
                            config.set("paths.tickets_dir", "tickets")
         | 
| 61 61 | 
             
                            config.set("paths.epics_dir", "tickets/epics")
         | 
| 62 62 | 
             
                            config.set("paths.issues_dir", "tickets/issues")
         | 
| @@ -334,11 +334,42 @@ class SemanticVersionManager: | |
| 334 334 |  | 
| 335 335 | 
             
                def get_current_version(self) -> Optional[SemanticVersion]:
         | 
| 336 336 | 
             
                    """
         | 
| 337 | 
            -
                    Get the current version from  | 
| 337 | 
            +
                    Get the current version from multiple sources with intelligent fallback.
         | 
| 338 | 
            +
                    
         | 
| 339 | 
            +
                    Uses the enhanced version parser to check:
         | 
| 340 | 
            +
                    1. Git tags (most recent)
         | 
| 341 | 
            +
                    2. VERSION file
         | 
| 342 | 
            +
                    3. package.json
         | 
| 343 | 
            +
                    4. pyproject.toml
         | 
| 344 | 
            +
                    5. Other configured version files
         | 
| 338 345 |  | 
| 339 346 | 
             
                    Returns:
         | 
| 340 347 | 
             
                        Current SemanticVersion or None if not found
         | 
| 341 348 | 
             
                    """
         | 
| 349 | 
            +
                    try:
         | 
| 350 | 
            +
                        # Import here to avoid circular dependency
         | 
| 351 | 
            +
                        from claude_mpm.services.version_control.version_parser import get_version_parser
         | 
| 352 | 
            +
                        
         | 
| 353 | 
            +
                        # Use enhanced parser for current version
         | 
| 354 | 
            +
                        parser = get_version_parser(self.project_root)
         | 
| 355 | 
            +
                        version_meta = parser.get_current_version()
         | 
| 356 | 
            +
                        
         | 
| 357 | 
            +
                        if version_meta:
         | 
| 358 | 
            +
                            version = self.parse_version(version_meta.version)
         | 
| 359 | 
            +
                            if version:
         | 
| 360 | 
            +
                                self.logger.info(f"Found version {version} from {version_meta.source}")
         | 
| 361 | 
            +
                                # Optionally attach metadata
         | 
| 362 | 
            +
                                if hasattr(version, '__dict__'):
         | 
| 363 | 
            +
                                    version.source = version_meta.source
         | 
| 364 | 
            +
                                return version
         | 
| 365 | 
            +
                            
         | 
| 366 | 
            +
                    except ImportError:
         | 
| 367 | 
            +
                        # Fallback to original implementation
         | 
| 368 | 
            +
                        self.logger.debug("Enhanced version parser not available, using fallback")
         | 
| 369 | 
            +
                    except Exception as e:
         | 
| 370 | 
            +
                        self.logger.error(f"Error getting current version with enhanced parser: {e}")
         | 
| 371 | 
            +
                    
         | 
| 372 | 
            +
                    # Fallback to original implementation
         | 
| 342 373 | 
             
                    for filename, parser in self.version_files.items():
         | 
| 343 374 | 
             
                        file_path = self.project_root / filename
         | 
| 344 375 |  | 
| @@ -811,19 +842,61 @@ class SemanticVersionManager: | |
| 811 842 |  | 
| 812 843 | 
             
                def get_version_history(self) -> List[SemanticVersion]:
         | 
| 813 844 | 
             
                    """
         | 
| 814 | 
            -
                    Get version history from  | 
| 845 | 
            +
                    Get version history from multiple sources with intelligent fallback.
         | 
| 846 | 
            +
                    
         | 
| 847 | 
            +
                    Uses the enhanced version parser to retrieve version history from:
         | 
| 848 | 
            +
                    1. Git tags (primary source)
         | 
| 849 | 
            +
                    2. CHANGELOG.md (fallback)
         | 
| 850 | 
            +
                    3. VERSION files (current version only)
         | 
| 815 851 |  | 
| 816 852 | 
             
                    Returns:
         | 
| 817 853 | 
             
                        List of versions in descending order
         | 
| 818 854 | 
             
                    """
         | 
| 855 | 
            +
                    try:
         | 
| 856 | 
            +
                        # Import here to avoid circular dependency
         | 
| 857 | 
            +
                        from claude_mpm.services.version_control.version_parser import get_version_parser
         | 
| 858 | 
            +
                        
         | 
| 859 | 
            +
                        # Use enhanced parser for comprehensive version history
         | 
| 860 | 
            +
                        parser = get_version_parser(self.project_root)
         | 
| 861 | 
            +
                        version_metadata = parser.get_version_history(include_prereleases=False)
         | 
| 862 | 
            +
                        
         | 
| 863 | 
            +
                        # Convert to SemanticVersion objects
         | 
| 864 | 
            +
                        versions = []
         | 
| 865 | 
            +
                        for meta in version_metadata:
         | 
| 866 | 
            +
                            version = self.parse_version(meta.version)
         | 
| 867 | 
            +
                            if version:
         | 
| 868 | 
            +
                                # Optionally attach metadata to version
         | 
| 869 | 
            +
                                if hasattr(version, '__dict__'):
         | 
| 870 | 
            +
                                    version.source = meta.source
         | 
| 871 | 
            +
                                    version.release_date = meta.release_date
         | 
| 872 | 
            +
                                    version.commit_hash = meta.commit_hash
         | 
| 873 | 
            +
                                versions.append(version)
         | 
| 874 | 
            +
                        
         | 
| 875 | 
            +
                        return versions
         | 
| 876 | 
            +
                        
         | 
| 877 | 
            +
                    except ImportError:
         | 
| 878 | 
            +
                        # Fallback to original implementation if enhanced parser not available
         | 
| 879 | 
            +
                        self.logger.warning("Enhanced version parser not available, falling back to changelog parsing")
         | 
| 880 | 
            +
                        return self._parse_changelog_versions_fallback()
         | 
| 881 | 
            +
                    except Exception as e:
         | 
| 882 | 
            +
                        self.logger.error(f"Error getting version history: {e}")
         | 
| 883 | 
            +
                        # Fallback to original implementation
         | 
| 884 | 
            +
                        return self._parse_changelog_versions_fallback()
         | 
| 885 | 
            +
             | 
| 886 | 
            +
                def _parse_changelog_versions_fallback(self) -> List[SemanticVersion]:
         | 
| 887 | 
            +
                    """Fallback method: Parse versions from changelog file only."""
         | 
| 819 888 | 
             
                    versions = []
         | 
| 820 889 |  | 
| 821 890 | 
             
                    # Try to get versions from changelog
         | 
| 822 | 
            -
                     | 
| 823 | 
            -
             | 
| 824 | 
            -
                         | 
| 825 | 
            -
             | 
| 826 | 
            -
                     | 
| 891 | 
            +
                    changelog_paths = [
         | 
| 892 | 
            +
                        self.project_root / "CHANGELOG.md",
         | 
| 893 | 
            +
                        self.project_root / "docs" / "CHANGELOG.md"
         | 
| 894 | 
            +
                    ]
         | 
| 895 | 
            +
                    
         | 
| 896 | 
            +
                    for changelog_path in changelog_paths:
         | 
| 897 | 
            +
                        if changelog_path.exists():
         | 
| 898 | 
            +
                            versions.extend(self._parse_changelog_versions(changelog_path))
         | 
| 899 | 
            +
                            break
         | 
| 827 900 |  | 
| 828 901 | 
             
                    # Sort versions in descending order
         | 
| 829 902 | 
             
                    versions.sort(reverse=True)
         |