claude-mpm 3.7.8__py3-none-any.whl → 3.8.1__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 +88 -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 +2 -7
- 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/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 +5 -1
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +461 -22
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +208 -94
- claude_mpm/core/interactive_session.py +432 -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 +587 -268
- claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
- 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/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/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 +170 -7
- 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.8.1.dist-info}/METADATA +58 -21
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +93 -53
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
| @@ -22,6 +22,25 @@ import asyncio | |
| 22 22 | 
             
            from pathlib import Path
         | 
| 23 23 | 
             
            from collections import deque
         | 
| 24 24 |  | 
| 25 | 
            +
            # Import constants for configuration
         | 
| 26 | 
            +
            try:
         | 
| 27 | 
            +
                from claude_mpm.core.constants import (
         | 
| 28 | 
            +
                    NetworkConfig,
         | 
| 29 | 
            +
                    TimeoutConfig,
         | 
| 30 | 
            +
                    RetryConfig
         | 
| 31 | 
            +
                )
         | 
| 32 | 
            +
            except ImportError:
         | 
| 33 | 
            +
                # Fallback values if constants module not available
         | 
| 34 | 
            +
                class NetworkConfig:
         | 
| 35 | 
            +
                    SOCKETIO_PORT_RANGE = (8080, 8099)
         | 
| 36 | 
            +
                    RECONNECTION_DELAY = 0.5
         | 
| 37 | 
            +
                    SOCKET_WAIT_TIMEOUT = 1.0
         | 
| 38 | 
            +
                class TimeoutConfig:
         | 
| 39 | 
            +
                    QUICK_TIMEOUT = 2.0
         | 
| 40 | 
            +
                class RetryConfig:
         | 
| 41 | 
            +
                    MAX_RETRIES = 3
         | 
| 42 | 
            +
                    INITIAL_RETRY_DELAY = 0.1
         | 
| 43 | 
            +
             | 
| 25 44 | 
             
            # Debug mode is enabled by default for better visibility into hook processing
         | 
| 26 45 | 
             
            # Set CLAUDE_MPM_HOOK_DEBUG=false to disable debug output
         | 
| 27 46 | 
             
            DEBUG = os.environ.get('CLAUDE_MPM_HOOK_DEBUG', 'true').lower() != 'false'
         | 
| @@ -412,7 +431,7 @@ class ClaudeHookHandler: | |
| 412 431 | 
             
                            ['git', 'branch', '--show-current'],
         | 
| 413 432 | 
             
                            capture_output=True,
         | 
| 414 433 | 
             
                            text=True,
         | 
| 415 | 
            -
                            timeout= | 
| 434 | 
            +
                            timeout=TimeoutConfig.QUICK_TIMEOUT  # Quick timeout to avoid hanging
         | 
| 416 435 | 
             
                        )
         | 
| 417 436 |  | 
| 418 437 | 
             
                        # Restore original directory
         | 
| @@ -464,8 +483,8 @@ class ClaudeHookHandler: | |
| 464 483 |  | 
| 465 484 | 
             
                    # Need to create or reconnect client
         | 
| 466 485 | 
             
                    port = int(os.environ.get('CLAUDE_MPM_SOCKETIO_PORT', '8765'))
         | 
| 467 | 
            -
                    max_retries =  | 
| 468 | 
            -
                    retry_delay =  | 
| 486 | 
            +
                    max_retries = RetryConfig.MAX_RETRIES
         | 
| 487 | 
            +
                    retry_delay = RetryConfig.INITIAL_RETRY_DELAY
         | 
| 469 488 |  | 
| 470 489 | 
             
                    for attempt in range(max_retries):
         | 
| 471 490 | 
             
                        try:
         | 
| @@ -481,7 +500,7 @@ class ClaudeHookHandler: | |
| 481 500 | 
             
                            self.sio_client = socketio.Client(
         | 
| 482 501 | 
             
                                reconnection=True,  # Enable auto-reconnection
         | 
| 483 502 | 
             
                                reconnection_attempts=3,
         | 
| 484 | 
            -
                                reconnection_delay= | 
| 503 | 
            +
                                reconnection_delay=NetworkConfig.RECONNECTION_DELAY,
         | 
| 485 504 | 
             
                                reconnection_delay_max=2,
         | 
| 486 505 | 
             
                                logger=False,
         | 
| 487 506 | 
             
                                engineio_logger=False
         | 
| @@ -491,7 +510,7 @@ class ClaudeHookHandler: | |
| 491 510 | 
             
                            self.sio_client.connect(
         | 
| 492 511 | 
             
                                f'http://localhost:{port}', 
         | 
| 493 512 | 
             
                                wait=True, 
         | 
| 494 | 
            -
                                wait_timeout= | 
| 513 | 
            +
                                wait_timeout=NetworkConfig.SOCKET_WAIT_TIMEOUT
         | 
| 495 514 | 
             
                            )
         | 
| 496 515 |  | 
| 497 516 | 
             
                            # Verify connection
         | 
| @@ -540,36 +559,80 @@ class ClaudeHookHandler: | |
| 540 559 | 
             
                    - Always continues regardless of event status
         | 
| 541 560 | 
             
                    """
         | 
| 542 561 | 
             
                    try:
         | 
| 543 | 
            -
                        # Read event
         | 
| 544 | 
            -
                         | 
| 545 | 
            -
                         | 
| 546 | 
            -
             | 
| 547 | 
            -
             | 
| 548 | 
            -
                        # Fast path for common events
         | 
| 549 | 
            -
                        if hook_type == 'UserPromptSubmit':
         | 
| 550 | 
            -
                            self._handle_user_prompt_fast(event)
         | 
| 551 | 
            -
                        elif hook_type == 'PreToolUse':
         | 
| 552 | 
            -
                            self._handle_pre_tool_fast(event)
         | 
| 553 | 
            -
                        elif hook_type == 'PostToolUse':
         | 
| 554 | 
            -
                            self._handle_post_tool_fast(event)
         | 
| 555 | 
            -
                        elif hook_type == 'Notification':
         | 
| 556 | 
            -
                            self._handle_notification_fast(event)
         | 
| 557 | 
            -
                        elif hook_type == 'Stop':
         | 
| 558 | 
            -
                            self._handle_stop_fast(event)
         | 
| 559 | 
            -
                        elif hook_type == 'SubagentStop':
         | 
| 560 | 
            -
                            self._handle_subagent_stop_fast(event)
         | 
| 561 | 
            -
                        elif hook_type == 'AssistantResponse':
         | 
| 562 | 
            -
                            self._handle_assistant_response(event)
         | 
| 562 | 
            +
                        # Read and parse event
         | 
| 563 | 
            +
                        event = self._read_hook_event()
         | 
| 564 | 
            +
                        if not event:
         | 
| 565 | 
            +
                            self._continue_execution()
         | 
| 566 | 
            +
                            return
         | 
| 563 567 |  | 
| 564 | 
            -
                        #  | 
| 565 | 
            -
                         | 
| 568 | 
            +
                        # Route event to appropriate handler
         | 
| 569 | 
            +
                        self._route_event(event)
         | 
| 566 570 |  | 
| 567 | 
            -
                        # Always continue
         | 
| 568 | 
            -
                         | 
| 571 | 
            +
                        # Always continue execution
         | 
| 572 | 
            +
                        self._continue_execution()
         | 
| 569 573 |  | 
| 570 574 | 
             
                    except:
         | 
| 571 575 | 
             
                        # Fail fast and silent
         | 
| 572 | 
            -
                         | 
| 576 | 
            +
                        self._continue_execution()
         | 
| 577 | 
            +
                
         | 
| 578 | 
            +
                def _read_hook_event(self) -> dict:
         | 
| 579 | 
            +
                    """
         | 
| 580 | 
            +
                    Read and parse hook event from stdin.
         | 
| 581 | 
            +
                    
         | 
| 582 | 
            +
                    WHY: Centralized event reading with error handling
         | 
| 583 | 
            +
                    ensures consistent parsing and validation.
         | 
| 584 | 
            +
                    
         | 
| 585 | 
            +
                    Returns:
         | 
| 586 | 
            +
                        Parsed event dictionary or None if invalid
         | 
| 587 | 
            +
                    """
         | 
| 588 | 
            +
                    try:
         | 
| 589 | 
            +
                        event_data = sys.stdin.read()
         | 
| 590 | 
            +
                        return json.loads(event_data)
         | 
| 591 | 
            +
                    except (json.JSONDecodeError, ValueError):
         | 
| 592 | 
            +
                        if DEBUG:
         | 
| 593 | 
            +
                            print("Failed to parse hook event", file=sys.stderr)
         | 
| 594 | 
            +
                        return None
         | 
| 595 | 
            +
                
         | 
| 596 | 
            +
                def _route_event(self, event: dict) -> None:
         | 
| 597 | 
            +
                    """
         | 
| 598 | 
            +
                    Route event to appropriate handler based on type.
         | 
| 599 | 
            +
                    
         | 
| 600 | 
            +
                    WHY: Centralized routing reduces complexity and makes
         | 
| 601 | 
            +
                    it easier to add new event types.
         | 
| 602 | 
            +
                    
         | 
| 603 | 
            +
                    Args:
         | 
| 604 | 
            +
                        event: Hook event dictionary
         | 
| 605 | 
            +
                    """
         | 
| 606 | 
            +
                    hook_type = event.get('hook_event_name', 'unknown')
         | 
| 607 | 
            +
                    
         | 
| 608 | 
            +
                    # Map event types to handlers
         | 
| 609 | 
            +
                    event_handlers = {
         | 
| 610 | 
            +
                        'UserPromptSubmit': self._handle_user_prompt_fast,
         | 
| 611 | 
            +
                        'PreToolUse': self._handle_pre_tool_fast,
         | 
| 612 | 
            +
                        'PostToolUse': self._handle_post_tool_fast,
         | 
| 613 | 
            +
                        'Notification': self._handle_notification_fast,
         | 
| 614 | 
            +
                        'Stop': self._handle_stop_fast,
         | 
| 615 | 
            +
                        'SubagentStop': self._handle_subagent_stop_fast,
         | 
| 616 | 
            +
                        'AssistantResponse': self._handle_assistant_response
         | 
| 617 | 
            +
                    }
         | 
| 618 | 
            +
                    
         | 
| 619 | 
            +
                    # Call appropriate handler if exists
         | 
| 620 | 
            +
                    handler = event_handlers.get(hook_type)
         | 
| 621 | 
            +
                    if handler:
         | 
| 622 | 
            +
                        try:
         | 
| 623 | 
            +
                            handler(event)
         | 
| 624 | 
            +
                        except Exception as e:
         | 
| 625 | 
            +
                            if DEBUG:
         | 
| 626 | 
            +
                                print(f"Error handling {hook_type}: {e}", file=sys.stderr)
         | 
| 627 | 
            +
                
         | 
| 628 | 
            +
                def _continue_execution(self) -> None:
         | 
| 629 | 
            +
                    """
         | 
| 630 | 
            +
                    Send continue action to Claude.
         | 
| 631 | 
            +
                    
         | 
| 632 | 
            +
                    WHY: Centralized response ensures consistent format
         | 
| 633 | 
            +
                    and makes it easier to add response modifications.
         | 
| 634 | 
            +
                    """
         | 
| 635 | 
            +
                    print(json.dumps({"action": "continue"}))
         | 
| 573 636 |  | 
| 574 637 | 
             
                def _emit_socketio_event(self, namespace: str, event: str, data: dict):
         | 
| 575 638 | 
             
                    """Emit Socket.IO event with improved reliability and logging.
         | 
| @@ -1066,6 +1129,78 @@ class ClaudeHookHandler: | |
| 1066 1129 | 
             
                    # Emit to /hook namespace
         | 
| 1067 1130 | 
             
                    self._emit_socketio_event('/hook', 'notification', notification_data)
         | 
| 1068 1131 |  | 
| 1132 | 
            +
                def _extract_stop_metadata(self, event: dict) -> dict:
         | 
| 1133 | 
            +
                    """
         | 
| 1134 | 
            +
                    Extract metadata from stop event.
         | 
| 1135 | 
            +
                    
         | 
| 1136 | 
            +
                    WHY: Centralized metadata extraction ensures consistent
         | 
| 1137 | 
            +
                    data collection across stop event handling.
         | 
| 1138 | 
            +
                    
         | 
| 1139 | 
            +
                    Args:
         | 
| 1140 | 
            +
                        event: Stop event dictionary
         | 
| 1141 | 
            +
                        
         | 
| 1142 | 
            +
                    Returns:
         | 
| 1143 | 
            +
                        Metadata dictionary
         | 
| 1144 | 
            +
                    """
         | 
| 1145 | 
            +
                    working_dir = event.get('cwd', '')
         | 
| 1146 | 
            +
                    return {
         | 
| 1147 | 
            +
                        'timestamp': datetime.now().isoformat(),
         | 
| 1148 | 
            +
                        'working_directory': working_dir,
         | 
| 1149 | 
            +
                        'git_branch': self._get_git_branch(working_dir) if working_dir else 'Unknown',
         | 
| 1150 | 
            +
                        'event_type': 'stop',
         | 
| 1151 | 
            +
                        'reason': event.get('reason', 'unknown'),
         | 
| 1152 | 
            +
                        'stop_type': event.get('stop_type', 'normal')
         | 
| 1153 | 
            +
                    }
         | 
| 1154 | 
            +
                
         | 
| 1155 | 
            +
                def _track_stop_response(self, event: dict, session_id: str, metadata: dict) -> None:
         | 
| 1156 | 
            +
                    """
         | 
| 1157 | 
            +
                    Track response for stop events.
         | 
| 1158 | 
            +
                    
         | 
| 1159 | 
            +
                    WHY: Separated response tracking logic for better modularity
         | 
| 1160 | 
            +
                    and easier testing/maintenance.
         | 
| 1161 | 
            +
                    
         | 
| 1162 | 
            +
                    Args:
         | 
| 1163 | 
            +
                        event: Stop event dictionary
         | 
| 1164 | 
            +
                        session_id: Session identifier
         | 
| 1165 | 
            +
                        metadata: Event metadata
         | 
| 1166 | 
            +
                    """
         | 
| 1167 | 
            +
                    if not (self.response_tracking_enabled and self.response_tracker):
         | 
| 1168 | 
            +
                        return
         | 
| 1169 | 
            +
                    
         | 
| 1170 | 
            +
                    try:
         | 
| 1171 | 
            +
                        # Extract output from event
         | 
| 1172 | 
            +
                        output = event.get('output', '') or event.get('final_output', '') or event.get('response', '')
         | 
| 1173 | 
            +
                        
         | 
| 1174 | 
            +
                        # Check if we have a pending prompt for this session
         | 
| 1175 | 
            +
                        prompt_data = self.pending_prompts.get(session_id)
         | 
| 1176 | 
            +
                        
         | 
| 1177 | 
            +
                        if DEBUG:
         | 
| 1178 | 
            +
                            print(f"  - output present: {bool(output)} (length: {len(str(output)) if output else 0})", file=sys.stderr)
         | 
| 1179 | 
            +
                            print(f"  - prompt_data present: {bool(prompt_data)}", file=sys.stderr)
         | 
| 1180 | 
            +
                        
         | 
| 1181 | 
            +
                        if output and prompt_data:
         | 
| 1182 | 
            +
                            # Add prompt timestamp to metadata
         | 
| 1183 | 
            +
                            metadata['prompt_timestamp'] = prompt_data.get('timestamp')
         | 
| 1184 | 
            +
                            
         | 
| 1185 | 
            +
                            # Track the main Claude response
         | 
| 1186 | 
            +
                            file_path = self.response_tracker.track_response(
         | 
| 1187 | 
            +
                                agent_name='claude_main',
         | 
| 1188 | 
            +
                                request=prompt_data['prompt'],
         | 
| 1189 | 
            +
                                response=str(output),
         | 
| 1190 | 
            +
                                session_id=session_id,
         | 
| 1191 | 
            +
                                metadata=metadata
         | 
| 1192 | 
            +
                            )
         | 
| 1193 | 
            +
                            
         | 
| 1194 | 
            +
                            if file_path and DEBUG:
         | 
| 1195 | 
            +
                                print(f"  - Response tracked to: {file_path}", file=sys.stderr)
         | 
| 1196 | 
            +
                            
         | 
| 1197 | 
            +
                            # Clean up pending prompt
         | 
| 1198 | 
            +
                            del self.pending_prompts[session_id]
         | 
| 1199 | 
            +
                            
         | 
| 1200 | 
            +
                    except Exception as e:
         | 
| 1201 | 
            +
                        if DEBUG:
         | 
| 1202 | 
            +
                            print(f"Error tracking stop response: {e}", file=sys.stderr)
         | 
| 1203 | 
            +
                
         | 
| 1069 1204 | 
             
                def _handle_stop_fast(self, event):
         | 
| 1070 1205 | 
             
                    """Handle stop events when Claude processing stops.
         | 
| 1071 1206 |  | 
| @@ -1075,83 +1210,62 @@ class ClaudeHookHandler: | |
| 1075 1210 | 
             
                    - Enables tracking of session completion patterns
         | 
| 1076 1211 | 
             
                    - Useful for understanding when and why Claude stops responding
         | 
| 1077 1212 | 
             
                    """
         | 
| 1078 | 
            -
                    reason = event.get('reason', 'unknown')
         | 
| 1079 | 
            -
                    stop_type = event.get('stop_type', 'normal')
         | 
| 1080 1213 | 
             
                    session_id = event.get('session_id', '')
         | 
| 1081 1214 |  | 
| 1082 | 
            -
                    #  | 
| 1083 | 
            -
                     | 
| 1084 | 
            -
                    git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
         | 
| 1215 | 
            +
                    # Extract metadata for this stop event
         | 
| 1216 | 
            +
                    metadata = self._extract_stop_metadata(event)
         | 
| 1085 1217 |  | 
| 1086 | 
            -
                    #  | 
| 1218 | 
            +
                    # Debug logging
         | 
| 1087 1219 | 
             
                    if DEBUG:
         | 
| 1088 | 
            -
                         | 
| 1089 | 
            -
                        print(f"  - response_tracking_enabled: {self.response_tracking_enabled}", file=sys.stderr)
         | 
| 1090 | 
            -
                        print(f"  - response_tracker exists: {self.response_tracker is not None}", file=sys.stderr)
         | 
| 1091 | 
            -
                        print(f"  - session_id: {session_id[:8] if session_id else 'None'}...", file=sys.stderr)
         | 
| 1092 | 
            -
                        print(f"  - reason: {reason}", file=sys.stderr)
         | 
| 1093 | 
            -
                        print(f"  - stop_type: {stop_type}", file=sys.stderr)
         | 
| 1220 | 
            +
                        self._log_stop_event_debug(event, session_id, metadata)
         | 
| 1094 1221 |  | 
| 1095 | 
            -
                     | 
| 1096 | 
            -
             | 
| 1097 | 
            -
             | 
| 1098 | 
            -
             | 
| 1099 | 
            -
             | 
| 1100 | 
            -
             | 
| 1101 | 
            -
             | 
| 1102 | 
            -
             | 
| 1103 | 
            -
             | 
| 1104 | 
            -
             | 
| 1105 | 
            -
             | 
| 1106 | 
            -
             | 
| 1107 | 
            -
             | 
| 1108 | 
            -
             | 
| 1109 | 
            -
             | 
| 1110 | 
            -
             | 
| 1111 | 
            -
             | 
| 1112 | 
            -
             | 
| 1113 | 
            -
             | 
| 1114 | 
            -
             | 
| 1115 | 
            -
             | 
| 1116 | 
            -
             | 
| 1117 | 
            -
             | 
| 1118 | 
            -
             | 
| 1119 | 
            -
             | 
| 1120 | 
            -
             | 
| 1121 | 
            -
             | 
| 1122 | 
            -
             | 
| 1123 | 
            -
                                    request=prompt_data['prompt'],
         | 
| 1124 | 
            -
                                    response=str(output),
         | 
| 1125 | 
            -
                                    session_id=session_id,
         | 
| 1126 | 
            -
                                    metadata=metadata
         | 
| 1127 | 
            -
                                )
         | 
| 1128 | 
            -
                                
         | 
| 1129 | 
            -
                                if file_path and DEBUG:
         | 
| 1130 | 
            -
                                    print(f"✅ Tracked main Claude response on Stop event for session {session_id[:8]}...: {file_path.name}", file=sys.stderr)
         | 
| 1131 | 
            -
                                
         | 
| 1132 | 
            -
                                # Clean up the stored prompt
         | 
| 1133 | 
            -
                                if session_id in self.pending_prompts:
         | 
| 1134 | 
            -
                                    del self.pending_prompts[session_id]
         | 
| 1135 | 
            -
                                    
         | 
| 1136 | 
            -
                            elif DEBUG and not prompt_data:
         | 
| 1137 | 
            -
                                print(f"No stored prompt for Stop event session {session_id[:8]}...", file=sys.stderr)
         | 
| 1138 | 
            -
                            elif DEBUG and not output:
         | 
| 1139 | 
            -
                                print(f"No output in Stop event for session {session_id[:8]}...", file=sys.stderr)
         | 
| 1140 | 
            -
                                
         | 
| 1141 | 
            -
                        except Exception as e:
         | 
| 1142 | 
            -
                            if DEBUG:
         | 
| 1143 | 
            -
                                print(f"❌ Failed to track response on Stop event: {e}", file=sys.stderr)
         | 
| 1222 | 
            +
                    # Track response if enabled
         | 
| 1223 | 
            +
                    self._track_stop_response(event, session_id, metadata)
         | 
| 1224 | 
            +
                    
         | 
| 1225 | 
            +
                    # Emit stop event to Socket.IO
         | 
| 1226 | 
            +
                    self._emit_stop_event(event, session_id, metadata)
         | 
| 1227 | 
            +
                
         | 
| 1228 | 
            +
                def _log_stop_event_debug(self, event: dict, session_id: str, metadata: dict) -> None:
         | 
| 1229 | 
            +
                    """
         | 
| 1230 | 
            +
                    Log debug information for stop events.
         | 
| 1231 | 
            +
                    
         | 
| 1232 | 
            +
                    WHY: Separated debug logging for cleaner code and easier
         | 
| 1233 | 
            +
                    enable/disable of debug output.
         | 
| 1234 | 
            +
                    
         | 
| 1235 | 
            +
                    Args:
         | 
| 1236 | 
            +
                        event: Stop event dictionary
         | 
| 1237 | 
            +
                        session_id: Session identifier
         | 
| 1238 | 
            +
                        metadata: Event metadata
         | 
| 1239 | 
            +
                    """
         | 
| 1240 | 
            +
                    print(f"[DEBUG] Stop event processing:", file=sys.stderr)
         | 
| 1241 | 
            +
                    print(f"  - response_tracking_enabled: {self.response_tracking_enabled}", file=sys.stderr)
         | 
| 1242 | 
            +
                    print(f"  - response_tracker exists: {self.response_tracker is not None}", file=sys.stderr)
         | 
| 1243 | 
            +
                    print(f"  - session_id: {session_id[:8] if session_id else 'None'}...", file=sys.stderr)
         | 
| 1244 | 
            +
                    print(f"  - reason: {metadata['reason']}", file=sys.stderr)
         | 
| 1245 | 
            +
                    print(f"  - stop_type: {metadata['stop_type']}", file=sys.stderr)
         | 
| 1246 | 
            +
                
         | 
| 1247 | 
            +
                def _emit_stop_event(self, event: dict, session_id: str, metadata: dict) -> None:
         | 
| 1248 | 
            +
                    """
         | 
| 1249 | 
            +
                    Emit stop event data to Socket.IO.
         | 
| 1144 1250 |  | 
| 1251 | 
            +
                    WHY: Separated Socket.IO emission for better modularity
         | 
| 1252 | 
            +
                    and easier testing/mocking.
         | 
| 1253 | 
            +
                    
         | 
| 1254 | 
            +
                    Args:
         | 
| 1255 | 
            +
                        event: Stop event dictionary
         | 
| 1256 | 
            +
                        session_id: Session identifier
         | 
| 1257 | 
            +
                        metadata: Event metadata
         | 
| 1258 | 
            +
                    """
         | 
| 1145 1259 | 
             
                    stop_data = {
         | 
| 1146 | 
            -
                        'reason': reason,
         | 
| 1147 | 
            -
                        'stop_type': stop_type,
         | 
| 1260 | 
            +
                        'reason': metadata['reason'],
         | 
| 1261 | 
            +
                        'stop_type': metadata['stop_type'],
         | 
| 1148 1262 | 
             
                        'session_id': session_id,
         | 
| 1149 | 
            -
                        'working_directory':  | 
| 1150 | 
            -
                        'git_branch': git_branch,
         | 
| 1151 | 
            -
                        'timestamp':  | 
| 1152 | 
            -
                        'is_user_initiated': reason in ['user_stop', 'user_cancel', 'interrupt'],
         | 
| 1153 | 
            -
                        'is_error_stop': reason in ['error', 'timeout', 'failed'],
         | 
| 1154 | 
            -
                        'is_completion_stop': reason in ['completed', 'finished', 'done'],
         | 
| 1263 | 
            +
                        'working_directory': metadata['working_directory'],
         | 
| 1264 | 
            +
                        'git_branch': metadata['git_branch'],
         | 
| 1265 | 
            +
                        'timestamp': metadata['timestamp'],
         | 
| 1266 | 
            +
                        'is_user_initiated': metadata['reason'] in ['user_stop', 'user_cancel', 'interrupt'],
         | 
| 1267 | 
            +
                        'is_error_stop': metadata['reason'] in ['error', 'timeout', 'failed'],
         | 
| 1268 | 
            +
                        'is_completion_stop': metadata['reason'] in ['completed', 'finished', 'done'],
         | 
| 1155 1269 | 
             
                        'has_output': bool(event.get('final_output'))
         | 
| 1156 1270 | 
             
                    }
         | 
| 1157 1271 |  | 
    
        claude_mpm/init.py
    CHANGED
    
    | @@ -11,6 +11,7 @@ import json | |
| 11 11 | 
             
            import yaml
         | 
| 12 12 |  | 
| 13 13 | 
             
            from claude_mpm.core.logger import get_logger
         | 
| 14 | 
            +
            from claude_mpm.core.constants import NetworkConfig
         | 
| 14 15 |  | 
| 15 16 |  | 
| 16 17 | 
             
            class ProjectInitializer:
         | 
| @@ -249,7 +250,7 @@ class ProjectInitializer: | |
| 249 250 | 
             
                        "version": "1.0",
         | 
| 250 251 | 
             
                        "hooks": {
         | 
| 251 252 | 
             
                            "enabled": True,
         | 
| 252 | 
            -
                            "port_range":  | 
| 253 | 
            +
                            "port_range": list(NetworkConfig.SOCKETIO_PORT_RANGE)
         | 
| 253 254 | 
             
                        },
         | 
| 254 255 | 
             
                        "logging": {
         | 
| 255 256 | 
             
                            "level": "INFO",
         | 
    
        claude_mpm/services/__init__.py
    CHANGED
    
    | @@ -1,4 +1,15 @@ | |
| 1 | 
            -
            """Services for Claude MPM. | 
| 1 | 
            +
            """Services for Claude MPM.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This module provides backward compatibility for the reorganized service layer.
         | 
| 4 | 
            +
            Part of TSK-0046: Service Layer Architecture Reorganization
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            New structure:
         | 
| 7 | 
            +
            - core/: Core interfaces and base classes
         | 
| 8 | 
            +
            - agent/: Agent-related services
         | 
| 9 | 
            +
            - communication/: SocketIO and WebSocket services
         | 
| 10 | 
            +
            - project/: Project management services
         | 
| 11 | 
            +
            - infrastructure/: Logging and monitoring services
         | 
| 12 | 
            +
            """
         | 
| 2 13 |  | 
| 3 14 | 
             
            # Use lazy imports to prevent circular dependency issues
         | 
| 4 15 | 
             
            def __getattr__(name):
         | 
| @@ -7,8 +18,13 @@ def __getattr__(name): | |
| 7 18 | 
             
                    from .ticket_manager import TicketManager
         | 
| 8 19 | 
             
                    return TicketManager
         | 
| 9 20 | 
             
                elif name == "AgentDeploymentService":
         | 
| 10 | 
            -
                     | 
| 11 | 
            -
                     | 
| 21 | 
            +
                    # Try new location first, fall back to old
         | 
| 22 | 
            +
                    try:
         | 
| 23 | 
            +
                        from .agent.deployment import AgentDeploymentService
         | 
| 24 | 
            +
                        return AgentDeploymentService
         | 
| 25 | 
            +
                    except ImportError:
         | 
| 26 | 
            +
                        from .agents.deployment import AgentDeploymentService
         | 
| 27 | 
            +
                        return AgentDeploymentService
         | 
| 12 28 | 
             
                elif name == "AgentMemoryManager":
         | 
| 13 29 | 
             
                    from .agents.memory import AgentMemoryManager
         | 
| 14 30 | 
             
                    return AgentMemoryManager
         | 
| @@ -17,8 +33,13 @@ def __getattr__(name): | |
| 17 33 | 
             
                    return get_memory_manager
         | 
| 18 34 | 
             
                # Add backward compatibility for other agent services
         | 
| 19 35 | 
             
                elif name == "AgentRegistry":
         | 
| 20 | 
            -
                     | 
| 21 | 
            -
                     | 
| 36 | 
            +
                    # Try new location first, fall back to old
         | 
| 37 | 
            +
                    try:
         | 
| 38 | 
            +
                        from .agent.registry import AgentRegistry
         | 
| 39 | 
            +
                        return AgentRegistry
         | 
| 40 | 
            +
                    except ImportError:
         | 
| 41 | 
            +
                        from .agents.registry import AgentRegistry
         | 
| 42 | 
            +
                        return AgentRegistry
         | 
| 22 43 | 
             
                elif name == "AgentLifecycleManager":
         | 
| 23 44 | 
             
                    from .agents.deployment import AgentLifecycleManager
         | 
| 24 45 | 
             
                    return AgentLifecycleManager
         | 
| @@ -53,23 +74,36 @@ def __getattr__(name): | |
| 53 74 | 
             
                    from .hook_service import HookService
         | 
| 54 75 | 
             
                    return HookService
         | 
| 55 76 | 
             
                elif name == "ProjectAnalyzer":
         | 
| 56 | 
            -
                     | 
| 57 | 
            -
                     | 
| 58 | 
            -
             | 
| 77 | 
            +
                    # Try new location first, fall back to old
         | 
| 78 | 
            +
                    try:
         | 
| 79 | 
            +
                        from .project.analyzer import ProjectAnalyzer
         | 
| 80 | 
            +
                        return ProjectAnalyzer
         | 
| 81 | 
            +
                    except ImportError:
         | 
| 82 | 
            +
                        from .project_analyzer import ProjectAnalyzer
         | 
| 83 | 
            +
                        return ProjectAnalyzer
         | 
| 84 | 
            +
                elif name == "AdvancedHealthMonitor" or name == "HealthMonitor":
         | 
| 59 85 | 
             
                    try:
         | 
| 60 | 
            -
                        from . | 
| 61 | 
            -
                        return  | 
| 86 | 
            +
                        from .infrastructure.monitoring import HealthMonitor
         | 
| 87 | 
            +
                        return HealthMonitor
         | 
| 62 88 | 
             
                    except ImportError:
         | 
| 63 | 
            -
                         | 
| 89 | 
            +
                        try:
         | 
| 90 | 
            +
                            from .health_monitor import AdvancedHealthMonitor
         | 
| 91 | 
            +
                            return AdvancedHealthMonitor
         | 
| 92 | 
            +
                        except ImportError:
         | 
| 93 | 
            +
                            raise AttributeError(f"Health monitoring not available: {name}")
         | 
| 64 94 | 
             
                elif name == "RecoveryManager":
         | 
| 65 95 | 
             
                    try:
         | 
| 66 96 | 
             
                        from .recovery_manager import RecoveryManager
         | 
| 67 97 | 
             
                        return RecoveryManager
         | 
| 68 98 | 
             
                    except ImportError:
         | 
| 69 99 | 
             
                        raise AttributeError(f"Recovery management not available: {name}")
         | 
| 70 | 
            -
                elif name == "StandaloneSocketIOServer":
         | 
| 71 | 
            -
                     | 
| 72 | 
            -
             | 
| 100 | 
            +
                elif name == "StandaloneSocketIOServer" or name == "SocketIOServer":
         | 
| 101 | 
            +
                    try:
         | 
| 102 | 
            +
                        from .communication.socketio import SocketIOServer
         | 
| 103 | 
            +
                        return SocketIOServer
         | 
| 104 | 
            +
                    except ImportError:
         | 
| 105 | 
            +
                        from .standalone_socketio_server import StandaloneSocketIOServer
         | 
| 106 | 
            +
                        return StandaloneSocketIOServer
         | 
| 73 107 | 
             
                # Backward compatibility for memory services
         | 
| 74 108 | 
             
                elif name == "MemoryBuilder":
         | 
| 75 109 | 
             
                    from .memory.builder import MemoryBuilder
         | 
| @@ -86,6 +120,23 @@ def __getattr__(name): | |
| 86 120 | 
             
                elif name == "SharedPromptCache":
         | 
| 87 121 | 
             
                    from .memory.cache.shared_prompt_cache import SharedPromptCache
         | 
| 88 122 | 
             
                    return SharedPromptCache
         | 
| 123 | 
            +
                # New service organization imports
         | 
| 124 | 
            +
                elif name == "AgentManagementService":
         | 
| 125 | 
            +
                    from .agent.management import AgentManagementService
         | 
| 126 | 
            +
                    return AgentManagementService
         | 
| 127 | 
            +
                elif name == "ProjectRegistry":
         | 
| 128 | 
            +
                    from .project.registry import ProjectRegistry
         | 
| 129 | 
            +
                    return ProjectRegistry
         | 
| 130 | 
            +
                elif name == "LoggingService":
         | 
| 131 | 
            +
                    from .infrastructure.logging import LoggingService
         | 
| 132 | 
            +
                    return LoggingService
         | 
| 133 | 
            +
                elif name == "SocketIOClientManager":
         | 
| 134 | 
            +
                    from .communication.websocket import SocketIOClientManager
         | 
| 135 | 
            +
                    return SocketIOClientManager
         | 
| 136 | 
            +
                # Core interfaces and base classes
         | 
| 137 | 
            +
                elif name.startswith('I') or name in ['BaseService', 'SyncBaseService', 'SingletonService']:
         | 
| 138 | 
            +
                    from . import core
         | 
| 139 | 
            +
                    return getattr(core, name)
         | 
| 89 140 | 
             
                raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
         | 
| 90 141 |  | 
| 91 142 | 
             
            __all__ = [
         | 
| @@ -96,12 +147,15 @@ __all__ = [ | |
| 96 147 | 
             
                "HookService",
         | 
| 97 148 | 
             
                "ProjectAnalyzer",
         | 
| 98 149 | 
             
                "AdvancedHealthMonitor",
         | 
| 150 | 
            +
                "HealthMonitor",  # New alias
         | 
| 99 151 | 
             
                "RecoveryManager", 
         | 
| 100 152 | 
             
                "StandaloneSocketIOServer",
         | 
| 153 | 
            +
                "SocketIOServer",  # New alias
         | 
| 101 154 | 
             
                # Additional agent services for backward compatibility
         | 
| 102 155 | 
             
                "AgentRegistry",
         | 
| 103 156 | 
             
                "AgentLifecycleManager",
         | 
| 104 157 | 
             
                "AgentManager",
         | 
| 158 | 
            +
                "AgentManagementService",  # New service
         | 
| 105 159 | 
             
                "AgentCapabilitiesGenerator",
         | 
| 106 160 | 
             
                "AgentModificationTracker",
         | 
| 107 161 | 
             
                "AgentPersistenceService",
         | 
| @@ -110,10 +164,20 @@ __all__ = [ | |
| 110 164 | 
             
                "BaseAgentManager",
         | 
| 111 165 | 
             
                "DeployedAgentDiscovery",
         | 
| 112 166 | 
             
                "FrameworkAgentLoader",
         | 
| 167 | 
            +
                # Project services
         | 
| 168 | 
            +
                "ProjectRegistry",  # New service
         | 
| 169 | 
            +
                # Infrastructure services
         | 
| 170 | 
            +
                "LoggingService",  # New service
         | 
| 171 | 
            +
                # Communication services
         | 
| 172 | 
            +
                "SocketIOClientManager",  # New service
         | 
| 113 173 | 
             
                # Memory services (backward compatibility)
         | 
| 114 174 | 
             
                "MemoryBuilder",
         | 
| 115 175 | 
             
                "MemoryRouter",
         | 
| 116 176 | 
             
                "MemoryOptimizer",
         | 
| 117 177 | 
             
                "SimpleCacheService",
         | 
| 118 178 | 
             
                "SharedPromptCache",
         | 
| 179 | 
            +
                # Core exports
         | 
| 180 | 
            +
                "BaseService",
         | 
| 181 | 
            +
                "SyncBaseService",
         | 
| 182 | 
            +
                "SingletonService",
         | 
| 119 183 | 
             
            ]
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            Agent Services Module
         | 
| 3 | 
            +
            ====================
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            This module contains all agent-related services including deployment,
         | 
| 6 | 
            +
            management, and registry operations.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Part of TSK-0046: Service Layer Architecture Reorganization
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            Services:
         | 
| 11 | 
            +
            - AgentDeploymentService: Handles agent deployment to Claude Code
         | 
| 12 | 
            +
            - AgentManagementService: Manages agent lifecycle and operations
         | 
| 13 | 
            +
            - AgentRegistry: Central registry for agent discovery and registration
         | 
| 14 | 
            +
            """
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            from .deployment import AgentDeploymentService
         | 
| 17 | 
            +
            from .management import AgentManagementService
         | 
| 18 | 
            +
            from .registry import AgentRegistry
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            __all__ = [
         | 
| 21 | 
            +
                'AgentDeploymentService',
         | 
| 22 | 
            +
                'AgentManagementService',
         | 
| 23 | 
            +
                'AgentRegistry',
         | 
| 24 | 
            +
            ]
         |