claude-mpm 3.4.24__py3-none-any.whl → 3.4.27__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/cli/__init__.py +26 -0
- claude_mpm/init.py +2 -0
- claude_mpm/services/project_registry.py +602 -0
- {claude_mpm-3.4.24.dist-info → claude_mpm-3.4.27.dist-info}/METADATA +1 -1
- {claude_mpm-3.4.24.dist-info → claude_mpm-3.4.27.dist-info}/RECORD +10 -9
- {claude_mpm-3.4.24.dist-info → claude_mpm-3.4.27.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.24.dist-info → claude_mpm-3.4.27.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.24.dist-info → claude_mpm-3.4.27.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.24.dist-info → claude_mpm-3.4.27.dist-info}/top_level.txt +0 -0
    
        claude_mpm/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            3.4. | 
| 1 | 
            +
            3.4.27
         | 
    
        claude_mpm/cli/__init__.py
    CHANGED
    
    | @@ -69,6 +69,9 @@ def main(argv: Optional[list] = None): | |
| 69 69 | 
             
                # Ensure directories are initialized on first run
         | 
| 70 70 | 
             
                ensure_directories()
         | 
| 71 71 |  | 
| 72 | 
            +
                # Initialize or update project registry
         | 
| 73 | 
            +
                _initialize_project_registry()
         | 
| 74 | 
            +
                
         | 
| 72 75 | 
             
                # Create parser with version
         | 
| 73 76 | 
             
                parser = create_parser(version=__version__)
         | 
| 74 77 |  | 
| @@ -110,6 +113,29 @@ def main(argv: Optional[list] = None): | |
| 110 113 | 
             
                    return 1
         | 
| 111 114 |  | 
| 112 115 |  | 
| 116 | 
            +
            def _initialize_project_registry():
         | 
| 117 | 
            +
                """
         | 
| 118 | 
            +
                Initialize or update the project registry for the current session.
         | 
| 119 | 
            +
                
         | 
| 120 | 
            +
                WHY: The project registry tracks all claude-mpm projects and their metadata
         | 
| 121 | 
            +
                across sessions. This function ensures the current project is properly
         | 
| 122 | 
            +
                registered and updates session information.
         | 
| 123 | 
            +
                
         | 
| 124 | 
            +
                DESIGN DECISION: Registry failures are logged but don't prevent startup
         | 
| 125 | 
            +
                to ensure claude-mpm remains functional even if registry operations fail.
         | 
| 126 | 
            +
                """
         | 
| 127 | 
            +
                try:
         | 
| 128 | 
            +
                    from ..services.project_registry import ProjectRegistry
         | 
| 129 | 
            +
                    registry = ProjectRegistry()
         | 
| 130 | 
            +
                    registry.get_or_create_project_entry()
         | 
| 131 | 
            +
                except Exception as e:
         | 
| 132 | 
            +
                    # Import logger here to avoid circular imports
         | 
| 133 | 
            +
                    from ..core.logger import get_logger
         | 
| 134 | 
            +
                    logger = get_logger("cli")
         | 
| 135 | 
            +
                    logger.debug(f"Failed to initialize project registry: {e}")
         | 
| 136 | 
            +
                    # Continue execution - registry failure shouldn't block startup
         | 
| 137 | 
            +
             | 
| 138 | 
            +
             | 
| 113 139 | 
             
            def _ensure_run_attributes(args):
         | 
| 114 140 | 
             
                """
         | 
| 115 141 | 
             
                Ensure run command attributes exist when defaulting to run.
         | 
    
        claude_mpm/init.py
    CHANGED
    
    | @@ -30,6 +30,7 @@ class ProjectInitializer: | |
| 30 30 | 
             
                      - config/
         | 
| 31 31 | 
             
                      - logs/
         | 
| 32 32 | 
             
                      - templates/
         | 
| 33 | 
            +
                      - registry/
         | 
| 33 34 | 
             
                    """
         | 
| 34 35 | 
             
                    try:
         | 
| 35 36 | 
             
                        # Create main user directory
         | 
| @@ -41,6 +42,7 @@ class ProjectInitializer: | |
| 41 42 | 
             
                            self.user_dir / "config",
         | 
| 42 43 | 
             
                            self.user_dir / "logs",
         | 
| 43 44 | 
             
                            self.user_dir / "templates",
         | 
| 45 | 
            +
                            self.user_dir / "registry",
         | 
| 44 46 | 
             
                        ]
         | 
| 45 47 |  | 
| 46 48 | 
             
                        for directory in directories:
         | 
| @@ -0,0 +1,602 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            Project Registry Service.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            WHY: This service manages a persistent registry of claude-mpm projects to enable
         | 
| 5 | 
            +
            project identification, tracking, and metadata management. The registry stores
         | 
| 6 | 
            +
            comprehensive project information including git status, environment details,
         | 
| 7 | 
            +
            runtime information, and project characteristics.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            DESIGN DECISION: Uses YAML for human-readable registry files stored in the user's
         | 
| 10 | 
            +
            home directory (~/.claude-mpm/registry/). Each project gets a unique UUID-based
         | 
| 11 | 
            +
            registry file to avoid conflicts and enable easy project identification.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The registry captures both static project information (paths, git info) and
         | 
| 14 | 
            +
            dynamic runtime information (startup times, process IDs, command line args)
         | 
| 15 | 
            +
            to provide complete project lifecycle tracking.
         | 
| 16 | 
            +
            """
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            import os
         | 
| 19 | 
            +
            import sys
         | 
| 20 | 
            +
            import uuid
         | 
| 21 | 
            +
            import subprocess
         | 
| 22 | 
            +
            import platform
         | 
| 23 | 
            +
            import shutil
         | 
| 24 | 
            +
            from datetime import datetime, timezone, timedelta
         | 
| 25 | 
            +
            from pathlib import Path
         | 
| 26 | 
            +
            from typing import Dict, Any, Optional, List
         | 
| 27 | 
            +
            import yaml
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            from claude_mpm.core.logger import get_logger
         | 
| 30 | 
            +
             | 
| 31 | 
            +
             | 
| 32 | 
            +
            class ProjectRegistryError(Exception):
         | 
| 33 | 
            +
                """Base exception for project registry operations."""
         | 
| 34 | 
            +
                pass
         | 
| 35 | 
            +
             | 
| 36 | 
            +
             | 
| 37 | 
            +
            class ProjectRegistry:
         | 
| 38 | 
            +
                """
         | 
| 39 | 
            +
                Manages the project registry for claude-mpm installations.
         | 
| 40 | 
            +
                
         | 
| 41 | 
            +
                WHY: The project registry provides persistent project tracking across sessions,
         | 
| 42 | 
            +
                enabling project identification, metadata collection, and usage analytics.
         | 
| 43 | 
            +
                This is crucial for multi-project environments where users switch between
         | 
| 44 | 
            +
                different codebases.
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                DESIGN DECISION: Registry files are stored in ~/.claude-mpm/registry/ with
         | 
| 47 | 
            +
                UUID-based filenames to ensure uniqueness and avoid conflicts. The registry
         | 
| 48 | 
            +
                uses YAML for human readability and ease of manual inspection/editing.
         | 
| 49 | 
            +
                """
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                def __init__(self):
         | 
| 52 | 
            +
                    """
         | 
| 53 | 
            +
                    Initialize the project registry.
         | 
| 54 | 
            +
                    
         | 
| 55 | 
            +
                    WHY: Sets up the registry directory and logger. The registry directory
         | 
| 56 | 
            +
                    is created in the user's home directory to persist across project
         | 
| 57 | 
            +
                    directories and system reboots.
         | 
| 58 | 
            +
                    """
         | 
| 59 | 
            +
                    self.logger = get_logger("project_registry")
         | 
| 60 | 
            +
                    self.registry_dir = Path.home() / ".claude-mpm" / "registry"
         | 
| 61 | 
            +
                    self.current_project_path = Path.cwd().resolve()
         | 
| 62 | 
            +
                    
         | 
| 63 | 
            +
                    # Ensure registry directory exists
         | 
| 64 | 
            +
                    try:
         | 
| 65 | 
            +
                        self.registry_dir.mkdir(parents=True, exist_ok=True)
         | 
| 66 | 
            +
                    except Exception as e:
         | 
| 67 | 
            +
                        self.logger.error(f"Failed to create registry directory: {e}")
         | 
| 68 | 
            +
                        raise ProjectRegistryError(f"Cannot create registry directory: {e}")
         | 
| 69 | 
            +
                
         | 
| 70 | 
            +
                def get_or_create_project_entry(self) -> Dict[str, Any]:
         | 
| 71 | 
            +
                    """
         | 
| 72 | 
            +
                    Get existing project registry entry or create a new one.
         | 
| 73 | 
            +
                    
         | 
| 74 | 
            +
                    WHY: This is the main entry point for project registration. It handles
         | 
| 75 | 
            +
                    both new project registration and existing project updates, ensuring
         | 
| 76 | 
            +
                    that every claude-mpm session is properly tracked.
         | 
| 77 | 
            +
                    
         | 
| 78 | 
            +
                    DESIGN DECISION: Matching is done by normalized absolute path to handle
         | 
| 79 | 
            +
                    symbolic links and different path representations consistently.
         | 
| 80 | 
            +
                    
         | 
| 81 | 
            +
                    Returns:
         | 
| 82 | 
            +
                        Dictionary containing the project registry data
         | 
| 83 | 
            +
                        
         | 
| 84 | 
            +
                    Raises:
         | 
| 85 | 
            +
                        ProjectRegistryError: If registry operations fail
         | 
| 86 | 
            +
                    """
         | 
| 87 | 
            +
                    try:
         | 
| 88 | 
            +
                        # Look for existing registry entry
         | 
| 89 | 
            +
                        existing_entry = self._find_existing_entry()
         | 
| 90 | 
            +
                        
         | 
| 91 | 
            +
                        if existing_entry:
         | 
| 92 | 
            +
                            self.logger.debug(f"Found existing project entry: {existing_entry['project_id']}")
         | 
| 93 | 
            +
                            # Update existing entry with current session info
         | 
| 94 | 
            +
                            return self._update_existing_entry(existing_entry)
         | 
| 95 | 
            +
                        else:
         | 
| 96 | 
            +
                            self.logger.debug("Creating new project registry entry")
         | 
| 97 | 
            +
                            # Create new entry
         | 
| 98 | 
            +
                            return self._create_new_entry()
         | 
| 99 | 
            +
                            
         | 
| 100 | 
            +
                    except Exception as e:
         | 
| 101 | 
            +
                        self.logger.error(f"Failed to get or create project entry: {e}")
         | 
| 102 | 
            +
                        raise ProjectRegistryError(f"Registry operation failed: {e}")
         | 
| 103 | 
            +
                
         | 
| 104 | 
            +
                def _find_existing_entry(self) -> Optional[Dict[str, Any]]:
         | 
| 105 | 
            +
                    """
         | 
| 106 | 
            +
                    Search for existing registry entry matching current project path.
         | 
| 107 | 
            +
                    
         | 
| 108 | 
            +
                    WHY: We need to match projects by their absolute path to avoid creating
         | 
| 109 | 
            +
                    duplicate entries when the same project is accessed from different
         | 
| 110 | 
            +
                    working directories or via different path representations.
         | 
| 111 | 
            +
                    
         | 
| 112 | 
            +
                    Returns:
         | 
| 113 | 
            +
                        Existing registry data if found, None otherwise
         | 
| 114 | 
            +
                    """
         | 
| 115 | 
            +
                    try:
         | 
| 116 | 
            +
                        # Normalize current path for consistent matching
         | 
| 117 | 
            +
                        current_path_str = str(self.current_project_path)
         | 
| 118 | 
            +
                        
         | 
| 119 | 
            +
                        # Search all registry files
         | 
| 120 | 
            +
                        for registry_file in self.registry_dir.glob("*.yaml"):
         | 
| 121 | 
            +
                            try:
         | 
| 122 | 
            +
                                with open(registry_file, 'r', encoding='utf-8') as f:
         | 
| 123 | 
            +
                                    data = yaml.safe_load(f) or {}
         | 
| 124 | 
            +
                                
         | 
| 125 | 
            +
                                # Check if project_path matches
         | 
| 126 | 
            +
                                if data.get('project_path') == current_path_str:
         | 
| 127 | 
            +
                                    data['_registry_file'] = registry_file  # Add file reference
         | 
| 128 | 
            +
                                    return data
         | 
| 129 | 
            +
                                    
         | 
| 130 | 
            +
                            except Exception as e:
         | 
| 131 | 
            +
                                self.logger.warning(f"Failed to read registry file {registry_file}: {e}")
         | 
| 132 | 
            +
                                continue
         | 
| 133 | 
            +
                        
         | 
| 134 | 
            +
                        return None
         | 
| 135 | 
            +
                        
         | 
| 136 | 
            +
                    except Exception as e:
         | 
| 137 | 
            +
                        self.logger.error(f"Error searching for existing entry: {e}")
         | 
| 138 | 
            +
                        return None
         | 
| 139 | 
            +
                
         | 
| 140 | 
            +
                def _create_new_entry(self) -> Dict[str, Any]:
         | 
| 141 | 
            +
                    """
         | 
| 142 | 
            +
                    Create a new project registry entry.
         | 
| 143 | 
            +
                    
         | 
| 144 | 
            +
                    WHY: New projects need to be registered with comprehensive metadata
         | 
| 145 | 
            +
                    including project information, environment details, and initial runtime
         | 
| 146 | 
            +
                    data. This creates a complete snapshot of the project at first access.
         | 
| 147 | 
            +
                    
         | 
| 148 | 
            +
                    Returns:
         | 
| 149 | 
            +
                        Newly created registry data
         | 
| 150 | 
            +
                    """
         | 
| 151 | 
            +
                    project_id = str(uuid.uuid4())
         | 
| 152 | 
            +
                    registry_file = self.registry_dir / f"{project_id}.yaml"
         | 
| 153 | 
            +
                    
         | 
| 154 | 
            +
                    # Build comprehensive project data
         | 
| 155 | 
            +
                    project_data = {
         | 
| 156 | 
            +
                        'project_id': project_id,
         | 
| 157 | 
            +
                        'project_path': str(self.current_project_path),
         | 
| 158 | 
            +
                        'project_name': self.current_project_path.name,
         | 
| 159 | 
            +
                        'metadata': self._build_metadata(is_new=True),
         | 
| 160 | 
            +
                        'runtime': self._build_runtime_info(),
         | 
| 161 | 
            +
                        'environment': self._build_environment_info(),
         | 
| 162 | 
            +
                        'git': self._build_git_info(),
         | 
| 163 | 
            +
                        'session': self._build_session_info(),
         | 
| 164 | 
            +
                        'project_info': self._build_project_info()
         | 
| 165 | 
            +
                    }
         | 
| 166 | 
            +
                    
         | 
| 167 | 
            +
                    # Save to registry file
         | 
| 168 | 
            +
                    self._save_registry_data(registry_file, project_data)
         | 
| 169 | 
            +
                    project_data['_registry_file'] = registry_file
         | 
| 170 | 
            +
                    
         | 
| 171 | 
            +
                    self.logger.info(f"Created new project registry entry: {project_id}")
         | 
| 172 | 
            +
                    return project_data
         | 
| 173 | 
            +
                
         | 
| 174 | 
            +
                def _update_existing_entry(self, existing_data: Dict[str, Any]) -> Dict[str, Any]:
         | 
| 175 | 
            +
                    """
         | 
| 176 | 
            +
                    Update existing project registry entry with current session information.
         | 
| 177 | 
            +
                    
         | 
| 178 | 
            +
                    WHY: Existing projects need their metadata updated to reflect current
         | 
| 179 | 
            +
                    access patterns, runtime information, and any changes in project state.
         | 
| 180 | 
            +
                    This maintains accurate usage tracking and project state history.
         | 
| 181 | 
            +
                    
         | 
| 182 | 
            +
                    Args:
         | 
| 183 | 
            +
                        existing_data: The existing registry data to update
         | 
| 184 | 
            +
                        
         | 
| 185 | 
            +
                    Returns:
         | 
| 186 | 
            +
                        Updated registry data
         | 
| 187 | 
            +
                    """
         | 
| 188 | 
            +
                    registry_file = existing_data.get('_registry_file')
         | 
| 189 | 
            +
                    if not registry_file:
         | 
| 190 | 
            +
                        raise ProjectRegistryError("Registry file reference missing from existing data")
         | 
| 191 | 
            +
                    
         | 
| 192 | 
            +
                    # Update timestamps and counters
         | 
| 193 | 
            +
                    metadata = existing_data.get('metadata', {})
         | 
| 194 | 
            +
                    access_count = metadata.get('access_count', 0) + 1
         | 
| 195 | 
            +
                    now = datetime.now(timezone.utc).isoformat()
         | 
| 196 | 
            +
                    
         | 
| 197 | 
            +
                    existing_data['metadata'].update({
         | 
| 198 | 
            +
                        'updated_at': now,
         | 
| 199 | 
            +
                        'last_accessed': now,
         | 
| 200 | 
            +
                        'access_count': access_count
         | 
| 201 | 
            +
                    })
         | 
| 202 | 
            +
                    
         | 
| 203 | 
            +
                    # Update runtime information
         | 
| 204 | 
            +
                    existing_data['runtime'] = self._build_runtime_info()
         | 
| 205 | 
            +
                    
         | 
| 206 | 
            +
                    # Update session information
         | 
| 207 | 
            +
                    existing_data['session'] = self._build_session_info()
         | 
| 208 | 
            +
                    
         | 
| 209 | 
            +
                    # Update git information (may have changed)
         | 
| 210 | 
            +
                    existing_data['git'] = self._build_git_info()
         | 
| 211 | 
            +
                    
         | 
| 212 | 
            +
                    # Update project info (may have changed)
         | 
| 213 | 
            +
                    existing_data['project_info'] = self._build_project_info()
         | 
| 214 | 
            +
                    
         | 
| 215 | 
            +
                    # Save updated data
         | 
| 216 | 
            +
                    self._save_registry_data(registry_file, existing_data)
         | 
| 217 | 
            +
                    
         | 
| 218 | 
            +
                    self.logger.debug(f"Updated project registry entry (access #{access_count})")
         | 
| 219 | 
            +
                    return existing_data
         | 
| 220 | 
            +
                
         | 
| 221 | 
            +
                def _build_metadata(self, is_new: bool = False) -> Dict[str, Any]:
         | 
| 222 | 
            +
                    """
         | 
| 223 | 
            +
                    Build metadata section for registry entry.
         | 
| 224 | 
            +
                    
         | 
| 225 | 
            +
                    WHY: Metadata tracks creation, modification, and access patterns for
         | 
| 226 | 
            +
                    analytics and project lifecycle management.
         | 
| 227 | 
            +
                    """
         | 
| 228 | 
            +
                    now = datetime.now(timezone.utc).isoformat()
         | 
| 229 | 
            +
                    
         | 
| 230 | 
            +
                    metadata = {
         | 
| 231 | 
            +
                        'updated_at': now,
         | 
| 232 | 
            +
                        'last_accessed': now,
         | 
| 233 | 
            +
                        'access_count': 1
         | 
| 234 | 
            +
                    }
         | 
| 235 | 
            +
                    
         | 
| 236 | 
            +
                    if is_new:
         | 
| 237 | 
            +
                        metadata['created_at'] = now
         | 
| 238 | 
            +
                    
         | 
| 239 | 
            +
                    return metadata
         | 
| 240 | 
            +
                
         | 
| 241 | 
            +
                def _build_runtime_info(self) -> Dict[str, Any]:
         | 
| 242 | 
            +
                    """
         | 
| 243 | 
            +
                    Build runtime information section.
         | 
| 244 | 
            +
                    
         | 
| 245 | 
            +
                    WHY: Runtime information helps track session lifecycle, process management,
         | 
| 246 | 
            +
                    and system state. This is valuable for debugging session issues and
         | 
| 247 | 
            +
                    understanding usage patterns.
         | 
| 248 | 
            +
                    """
         | 
| 249 | 
            +
                    # Get claude-mpm version
         | 
| 250 | 
            +
                    try:
         | 
| 251 | 
            +
                        from claude_mpm import __version__ as claude_mpm_version
         | 
| 252 | 
            +
                    except ImportError:
         | 
| 253 | 
            +
                        claude_mpm_version = "unknown"
         | 
| 254 | 
            +
                    
         | 
| 255 | 
            +
                    return {
         | 
| 256 | 
            +
                        'startup_time': datetime.now(timezone.utc).isoformat(),
         | 
| 257 | 
            +
                        'pid': os.getpid(),
         | 
| 258 | 
            +
                        'python_version': f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
         | 
| 259 | 
            +
                        'claude_mpm_version': claude_mpm_version,
         | 
| 260 | 
            +
                        'command_line': ' '.join(sys.argv),
         | 
| 261 | 
            +
                        'launch_method': 'subprocess'  # Default, could be detected based on parent process
         | 
| 262 | 
            +
                    }
         | 
| 263 | 
            +
                
         | 
| 264 | 
            +
                def _build_environment_info(self) -> Dict[str, Any]:
         | 
| 265 | 
            +
                    """
         | 
| 266 | 
            +
                    Build environment information section.
         | 
| 267 | 
            +
                    
         | 
| 268 | 
            +
                    WHY: Environment information helps with debugging, platform-specific
         | 
| 269 | 
            +
                    behavior analysis, and provides context for project usage patterns
         | 
| 270 | 
            +
                    across different systems and user setups.
         | 
| 271 | 
            +
                    """
         | 
| 272 | 
            +
                    return {
         | 
| 273 | 
            +
                        'user': os.getenv('USER') or os.getenv('USERNAME', 'unknown'),
         | 
| 274 | 
            +
                        'hostname': platform.node(),
         | 
| 275 | 
            +
                        'os': platform.system(),
         | 
| 276 | 
            +
                        'os_version': platform.release(),
         | 
| 277 | 
            +
                        'shell': os.getenv('SHELL', 'unknown'),
         | 
| 278 | 
            +
                        'terminal': os.getenv('TERM', 'unknown'),
         | 
| 279 | 
            +
                        'cwd': str(Path.cwd())
         | 
| 280 | 
            +
                    }
         | 
| 281 | 
            +
                
         | 
| 282 | 
            +
                def _build_git_info(self) -> Dict[str, Any]:
         | 
| 283 | 
            +
                    """
         | 
| 284 | 
            +
                    Build git repository information.
         | 
| 285 | 
            +
                    
         | 
| 286 | 
            +
                    WHY: Git information is crucial for project identification and tracking
         | 
| 287 | 
            +
                    changes across different branches and commits. This helps understand
         | 
| 288 | 
            +
                    project state and enables better project management features.
         | 
| 289 | 
            +
                    """
         | 
| 290 | 
            +
                    git_info = {
         | 
| 291 | 
            +
                        'is_repo': False,
         | 
| 292 | 
            +
                        'branch': None,
         | 
| 293 | 
            +
                        'remote_url': None,
         | 
| 294 | 
            +
                        'last_commit': None,
         | 
| 295 | 
            +
                        'has_uncommitted': False
         | 
| 296 | 
            +
                    }
         | 
| 297 | 
            +
                    
         | 
| 298 | 
            +
                    try:
         | 
| 299 | 
            +
                        # Check if we're in a git repository
         | 
| 300 | 
            +
                        result = subprocess.run(
         | 
| 301 | 
            +
                            ['git', 'rev-parse', '--git-dir'],
         | 
| 302 | 
            +
                            cwd=self.current_project_path,
         | 
| 303 | 
            +
                            capture_output=True,
         | 
| 304 | 
            +
                            text=True,
         | 
| 305 | 
            +
                            timeout=5
         | 
| 306 | 
            +
                        )
         | 
| 307 | 
            +
                        
         | 
| 308 | 
            +
                        if result.returncode == 0:
         | 
| 309 | 
            +
                            git_info['is_repo'] = True
         | 
| 310 | 
            +
                            
         | 
| 311 | 
            +
                            # Get current branch
         | 
| 312 | 
            +
                            try:
         | 
| 313 | 
            +
                                result = subprocess.run(
         | 
| 314 | 
            +
                                    ['git', 'branch', '--show-current'],
         | 
| 315 | 
            +
                                    cwd=self.current_project_path,
         | 
| 316 | 
            +
                                    capture_output=True,
         | 
| 317 | 
            +
                                    text=True,
         | 
| 318 | 
            +
                                    timeout=5
         | 
| 319 | 
            +
                                )
         | 
| 320 | 
            +
                                if result.returncode == 0:
         | 
| 321 | 
            +
                                    git_info['branch'] = result.stdout.strip()
         | 
| 322 | 
            +
                            except Exception:
         | 
| 323 | 
            +
                                pass
         | 
| 324 | 
            +
                            
         | 
| 325 | 
            +
                            # Get remote URL
         | 
| 326 | 
            +
                            try:
         | 
| 327 | 
            +
                                result = subprocess.run(
         | 
| 328 | 
            +
                                    ['git', 'remote', 'get-url', 'origin'],
         | 
| 329 | 
            +
                                    cwd=self.current_project_path,
         | 
| 330 | 
            +
                                    capture_output=True,
         | 
| 331 | 
            +
                                    text=True,
         | 
| 332 | 
            +
                                    timeout=5
         | 
| 333 | 
            +
                                )
         | 
| 334 | 
            +
                                if result.returncode == 0:
         | 
| 335 | 
            +
                                    git_info['remote_url'] = result.stdout.strip()
         | 
| 336 | 
            +
                            except Exception:
         | 
| 337 | 
            +
                                pass
         | 
| 338 | 
            +
                            
         | 
| 339 | 
            +
                            # Get last commit
         | 
| 340 | 
            +
                            try:
         | 
| 341 | 
            +
                                result = subprocess.run(
         | 
| 342 | 
            +
                                    ['git', 'rev-parse', 'HEAD'],
         | 
| 343 | 
            +
                                    cwd=self.current_project_path,
         | 
| 344 | 
            +
                                    capture_output=True,
         | 
| 345 | 
            +
                                    text=True,
         | 
| 346 | 
            +
                                    timeout=5
         | 
| 347 | 
            +
                                )
         | 
| 348 | 
            +
                                if result.returncode == 0:
         | 
| 349 | 
            +
                                    git_info['last_commit'] = result.stdout.strip()
         | 
| 350 | 
            +
                            except Exception:
         | 
| 351 | 
            +
                                pass
         | 
| 352 | 
            +
                            
         | 
| 353 | 
            +
                            # Check for uncommitted changes
         | 
| 354 | 
            +
                            try:
         | 
| 355 | 
            +
                                result = subprocess.run(
         | 
| 356 | 
            +
                                    ['git', 'status', '--porcelain'],
         | 
| 357 | 
            +
                                    cwd=self.current_project_path,
         | 
| 358 | 
            +
                                    capture_output=True,
         | 
| 359 | 
            +
                                    text=True,
         | 
| 360 | 
            +
                                    timeout=5
         | 
| 361 | 
            +
                                )
         | 
| 362 | 
            +
                                if result.returncode == 0:
         | 
| 363 | 
            +
                                    git_info['has_uncommitted'] = bool(result.stdout.strip())
         | 
| 364 | 
            +
                            except Exception:
         | 
| 365 | 
            +
                                pass
         | 
| 366 | 
            +
                    
         | 
| 367 | 
            +
                    except Exception as e:
         | 
| 368 | 
            +
                        self.logger.debug(f"Failed to get git info: {e}")
         | 
| 369 | 
            +
                    
         | 
| 370 | 
            +
                    return git_info
         | 
| 371 | 
            +
                
         | 
| 372 | 
            +
                def _build_session_info(self) -> Dict[str, Any]:
         | 
| 373 | 
            +
                    """
         | 
| 374 | 
            +
                    Build session information.
         | 
| 375 | 
            +
                    
         | 
| 376 | 
            +
                    WHY: Session information tracks the current claude-mpm session state,
         | 
| 377 | 
            +
                    including active components and configuration. This helps with session
         | 
| 378 | 
            +
                    management and debugging.
         | 
| 379 | 
            +
                    """
         | 
| 380 | 
            +
                    # These would be populated by the actual session manager
         | 
| 381 | 
            +
                    # For now, we provide placeholders that can be updated by the caller
         | 
| 382 | 
            +
                    return {
         | 
| 383 | 
            +
                        'session_id': None,  # Could be set by session manager
         | 
| 384 | 
            +
                        'ticket_count': 0,   # Could be updated by ticket manager
         | 
| 385 | 
            +
                        'agent_count': 0,    # Could be updated by agent manager
         | 
| 386 | 
            +
                        'hooks_enabled': False,  # Could be detected from configuration
         | 
| 387 | 
            +
                        'monitor_enabled': False  # Could be detected from process state
         | 
| 388 | 
            +
                    }
         | 
| 389 | 
            +
                
         | 
| 390 | 
            +
                def _build_project_info(self) -> Dict[str, Any]:
         | 
| 391 | 
            +
                    """
         | 
| 392 | 
            +
                    Build project information section.
         | 
| 393 | 
            +
                    
         | 
| 394 | 
            +
                    WHY: Project information helps identify the type of project and its
         | 
| 395 | 
            +
                    characteristics, enabling better tool selection and project-specific
         | 
| 396 | 
            +
                    optimizations.
         | 
| 397 | 
            +
                    """
         | 
| 398 | 
            +
                    project_info = {
         | 
| 399 | 
            +
                        'has_claude_config': False,
         | 
| 400 | 
            +
                        'has_claude_md': False,
         | 
| 401 | 
            +
                        'has_pyproject': False,
         | 
| 402 | 
            +
                        'has_package_json': False,
         | 
| 403 | 
            +
                        'project_type': 'unknown'
         | 
| 404 | 
            +
                    }
         | 
| 405 | 
            +
                    
         | 
| 406 | 
            +
                    # Check for various project files
         | 
| 407 | 
            +
                    claude_files = ['.claude', 'claude.toml', '.claude.toml']
         | 
| 408 | 
            +
                    for claude_file in claude_files:
         | 
| 409 | 
            +
                        if (self.current_project_path / claude_file).exists():
         | 
| 410 | 
            +
                            project_info['has_claude_config'] = True
         | 
| 411 | 
            +
                            break
         | 
| 412 | 
            +
                    
         | 
| 413 | 
            +
                    claude_md_files = ['CLAUDE.md', 'claude.md', '.claude.md']
         | 
| 414 | 
            +
                    for claude_md in claude_md_files:
         | 
| 415 | 
            +
                        if (self.current_project_path / claude_md).exists():
         | 
| 416 | 
            +
                            project_info['has_claude_md'] = True
         | 
| 417 | 
            +
                            break
         | 
| 418 | 
            +
                    
         | 
| 419 | 
            +
                    if (self.current_project_path / 'pyproject.toml').exists():
         | 
| 420 | 
            +
                        project_info['has_pyproject'] = True
         | 
| 421 | 
            +
                    
         | 
| 422 | 
            +
                    if (self.current_project_path / 'package.json').exists():
         | 
| 423 | 
            +
                        project_info['has_package_json'] = True
         | 
| 424 | 
            +
                    
         | 
| 425 | 
            +
                    # Determine project type
         | 
| 426 | 
            +
                    if project_info['has_pyproject'] or (self.current_project_path / 'setup.py').exists():
         | 
| 427 | 
            +
                        project_info['project_type'] = 'python'
         | 
| 428 | 
            +
                    elif project_info['has_package_json']:
         | 
| 429 | 
            +
                        project_info['project_type'] = 'javascript'
         | 
| 430 | 
            +
                    elif (self.current_project_path / 'Cargo.toml').exists():
         | 
| 431 | 
            +
                        project_info['project_type'] = 'rust'
         | 
| 432 | 
            +
                    elif (self.current_project_path / 'go.mod').exists():
         | 
| 433 | 
            +
                        project_info['project_type'] = 'go'
         | 
| 434 | 
            +
                    elif (self.current_project_path / 'pom.xml').exists():
         | 
| 435 | 
            +
                        project_info['project_type'] = 'java'
         | 
| 436 | 
            +
                    elif any((self.current_project_path / ext).exists() for ext in ['*.c', '*.cpp', '*.h', '*.hpp']):
         | 
| 437 | 
            +
                        project_info['project_type'] = 'c/cpp'
         | 
| 438 | 
            +
                    elif project_info['has_claude_config'] or project_info['has_claude_md']:
         | 
| 439 | 
            +
                        project_info['project_type'] = 'claude'
         | 
| 440 | 
            +
                    else:
         | 
| 441 | 
            +
                        # Try to detect by file extensions
         | 
| 442 | 
            +
                        common_files = list(self.current_project_path.iterdir())[:20]  # Check first 20 files
         | 
| 443 | 
            +
                        extensions = {f.suffix.lower() for f in common_files if f.is_file()}
         | 
| 444 | 
            +
                        
         | 
| 445 | 
            +
                        if '.py' in extensions:
         | 
| 446 | 
            +
                            project_info['project_type'] = 'python'
         | 
| 447 | 
            +
                        elif any(ext in extensions for ext in ['.js', '.ts', '.jsx', '.tsx']):
         | 
| 448 | 
            +
                            project_info['project_type'] = 'javascript'
         | 
| 449 | 
            +
                        elif any(ext in extensions for ext in ['.rs']):
         | 
| 450 | 
            +
                            project_info['project_type'] = 'rust'
         | 
| 451 | 
            +
                        elif any(ext in extensions for ext in ['.go']):
         | 
| 452 | 
            +
                            project_info['project_type'] = 'go'
         | 
| 453 | 
            +
                        elif any(ext in extensions for ext in ['.java', '.scala', '.kt']):
         | 
| 454 | 
            +
                            project_info['project_type'] = 'jvm'
         | 
| 455 | 
            +
                        elif any(ext in extensions for ext in ['.c', '.cpp', '.cc', '.h', '.hpp']):
         | 
| 456 | 
            +
                            project_info['project_type'] = 'c/cpp'
         | 
| 457 | 
            +
                        elif any(ext in extensions for ext in ['.md', '.txt', '.rst']):
         | 
| 458 | 
            +
                            project_info['project_type'] = 'documentation'
         | 
| 459 | 
            +
                    
         | 
| 460 | 
            +
                    return project_info
         | 
| 461 | 
            +
                
         | 
| 462 | 
            +
                def _save_registry_data(self, registry_file: Path, data: Dict[str, Any]) -> None:
         | 
| 463 | 
            +
                    """
         | 
| 464 | 
            +
                    Save registry data to YAML file.
         | 
| 465 | 
            +
                    
         | 
| 466 | 
            +
                    WHY: Centralized saving logic ensures consistent formatting and error
         | 
| 467 | 
            +
                    handling across all registry operations. YAML format provides human
         | 
| 468 | 
            +
                    readability for debugging and manual inspection.
         | 
| 469 | 
            +
                    
         | 
| 470 | 
            +
                    Args:
         | 
| 471 | 
            +
                        registry_file: Path to the registry file
         | 
| 472 | 
            +
                        data: Registry data to save
         | 
| 473 | 
            +
                    """
         | 
| 474 | 
            +
                    try:
         | 
| 475 | 
            +
                        # Remove internal fields before saving
         | 
| 476 | 
            +
                        save_data = {k: v for k, v in data.items() if not k.startswith('_')}
         | 
| 477 | 
            +
                        
         | 
| 478 | 
            +
                        with open(registry_file, 'w', encoding='utf-8') as f:
         | 
| 479 | 
            +
                            yaml.dump(save_data, f, default_flow_style=False, sort_keys=False, indent=2)
         | 
| 480 | 
            +
                            
         | 
| 481 | 
            +
                        self.logger.debug(f"Saved registry data to {registry_file}")
         | 
| 482 | 
            +
                        
         | 
| 483 | 
            +
                    except Exception as e:
         | 
| 484 | 
            +
                        self.logger.error(f"Failed to save registry data: {e}")
         | 
| 485 | 
            +
                        raise ProjectRegistryError(f"Failed to save registry: {e}")
         | 
| 486 | 
            +
                
         | 
| 487 | 
            +
                def list_projects(self) -> List[Dict[str, Any]]:
         | 
| 488 | 
            +
                    """
         | 
| 489 | 
            +
                    List all registered projects.
         | 
| 490 | 
            +
                    
         | 
| 491 | 
            +
                    WHY: Provides visibility into all projects managed by claude-mpm,
         | 
| 492 | 
            +
                    useful for project management and analytics.
         | 
| 493 | 
            +
                    
         | 
| 494 | 
            +
                    Returns:
         | 
| 495 | 
            +
                        List of project registry data dictionaries
         | 
| 496 | 
            +
                    """
         | 
| 497 | 
            +
                    projects = []
         | 
| 498 | 
            +
                    
         | 
| 499 | 
            +
                    try:
         | 
| 500 | 
            +
                        for registry_file in self.registry_dir.glob("*.yaml"):
         | 
| 501 | 
            +
                            try:
         | 
| 502 | 
            +
                                with open(registry_file, 'r', encoding='utf-8') as f:
         | 
| 503 | 
            +
                                    data = yaml.safe_load(f) or {}
         | 
| 504 | 
            +
                                projects.append(data)
         | 
| 505 | 
            +
                            except Exception as e:
         | 
| 506 | 
            +
                                self.logger.warning(f"Failed to read registry file {registry_file}: {e}")
         | 
| 507 | 
            +
                                continue
         | 
| 508 | 
            +
                    
         | 
| 509 | 
            +
                    except Exception as e:
         | 
| 510 | 
            +
                        self.logger.error(f"Failed to list projects: {e}")
         | 
| 511 | 
            +
                    
         | 
| 512 | 
            +
                    return projects
         | 
| 513 | 
            +
                
         | 
| 514 | 
            +
                def cleanup_old_entries(self, max_age_days: int = 90) -> int:
         | 
| 515 | 
            +
                    """
         | 
| 516 | 
            +
                    Clean up old registry entries that haven't been accessed recently.
         | 
| 517 | 
            +
                    
         | 
| 518 | 
            +
                    WHY: Prevents registry directory from growing indefinitely with old
         | 
| 519 | 
            +
                    project entries. Keeps the registry focused on active projects.
         | 
| 520 | 
            +
                    
         | 
| 521 | 
            +
                    Args:
         | 
| 522 | 
            +
                        max_age_days: Maximum age in days for keeping entries
         | 
| 523 | 
            +
                        
         | 
| 524 | 
            +
                    Returns:
         | 
| 525 | 
            +
                        Number of entries cleaned up
         | 
| 526 | 
            +
                    """
         | 
| 527 | 
            +
                    if max_age_days <= 0:
         | 
| 528 | 
            +
                        return 0
         | 
| 529 | 
            +
                    
         | 
| 530 | 
            +
                    cleaned_count = 0
         | 
| 531 | 
            +
                    cutoff_date = datetime.now(timezone.utc) - timedelta(days=max_age_days)
         | 
| 532 | 
            +
                    
         | 
| 533 | 
            +
                    try:
         | 
| 534 | 
            +
                        for registry_file in self.registry_dir.glob("*.yaml"):
         | 
| 535 | 
            +
                            try:
         | 
| 536 | 
            +
                                with open(registry_file, 'r', encoding='utf-8') as f:
         | 
| 537 | 
            +
                                    data = yaml.safe_load(f) or {}
         | 
| 538 | 
            +
                                
         | 
| 539 | 
            +
                                # Check last accessed time
         | 
| 540 | 
            +
                                last_accessed_str = data.get('metadata', {}).get('last_accessed')
         | 
| 541 | 
            +
                                if last_accessed_str:
         | 
| 542 | 
            +
                                    last_accessed = datetime.fromisoformat(last_accessed_str.replace('Z', '+00:00'))
         | 
| 543 | 
            +
                                    if last_accessed < cutoff_date:
         | 
| 544 | 
            +
                                        registry_file.unlink()
         | 
| 545 | 
            +
                                        cleaned_count += 1
         | 
| 546 | 
            +
                                        self.logger.debug(f"Cleaned up old registry entry: {registry_file}")
         | 
| 547 | 
            +
                                
         | 
| 548 | 
            +
                            except Exception as e:
         | 
| 549 | 
            +
                                self.logger.warning(f"Failed to process registry file {registry_file}: {e}")
         | 
| 550 | 
            +
                                continue
         | 
| 551 | 
            +
                    
         | 
| 552 | 
            +
                    except Exception as e:
         | 
| 553 | 
            +
                        self.logger.error(f"Failed to cleanup old entries: {e}")
         | 
| 554 | 
            +
                    
         | 
| 555 | 
            +
                    if cleaned_count > 0:
         | 
| 556 | 
            +
                        self.logger.info(f"Cleaned up {cleaned_count} old registry entries")
         | 
| 557 | 
            +
                    
         | 
| 558 | 
            +
                    return cleaned_count
         | 
| 559 | 
            +
                
         | 
| 560 | 
            +
                def update_session_info(self, session_updates: Dict[str, Any]) -> bool:
         | 
| 561 | 
            +
                    """
         | 
| 562 | 
            +
                    Update session information for the current project.
         | 
| 563 | 
            +
                    
         | 
| 564 | 
            +
                    WHY: Allows other components (session manager, ticket manager, etc.)
         | 
| 565 | 
            +
                    to update the registry with current session state information.
         | 
| 566 | 
            +
                    
         | 
| 567 | 
            +
                    Args:
         | 
| 568 | 
            +
                        session_updates: Dictionary of session updates to apply
         | 
| 569 | 
            +
                        
         | 
| 570 | 
            +
                    Returns:
         | 
| 571 | 
            +
                        True if update was successful, False otherwise
         | 
| 572 | 
            +
                    """
         | 
| 573 | 
            +
                    try:
         | 
| 574 | 
            +
                        existing_entry = self._find_existing_entry()
         | 
| 575 | 
            +
                        if not existing_entry:
         | 
| 576 | 
            +
                            self.logger.warning("No existing registry entry found for session update")
         | 
| 577 | 
            +
                            return False
         | 
| 578 | 
            +
                        
         | 
| 579 | 
            +
                        registry_file = existing_entry.get('_registry_file')
         | 
| 580 | 
            +
                        if not registry_file:
         | 
| 581 | 
            +
                            self.logger.error("Registry file reference missing")
         | 
| 582 | 
            +
                            return False
         | 
| 583 | 
            +
                        
         | 
| 584 | 
            +
                        # Update session information
         | 
| 585 | 
            +
                        if 'session' not in existing_entry:
         | 
| 586 | 
            +
                            existing_entry['session'] = {}
         | 
| 587 | 
            +
                        
         | 
| 588 | 
            +
                        existing_entry['session'].update(session_updates)
         | 
| 589 | 
            +
                        
         | 
| 590 | 
            +
                        # Update metadata timestamp
         | 
| 591 | 
            +
                        now = datetime.now(timezone.utc).isoformat()
         | 
| 592 | 
            +
                        existing_entry['metadata']['updated_at'] = now
         | 
| 593 | 
            +
                        
         | 
| 594 | 
            +
                        # Save updated data
         | 
| 595 | 
            +
                        self._save_registry_data(registry_file, existing_entry)
         | 
| 596 | 
            +
                        
         | 
| 597 | 
            +
                        self.logger.debug(f"Updated session info: {session_updates}")
         | 
| 598 | 
            +
                        return True
         | 
| 599 | 
            +
                        
         | 
| 600 | 
            +
                    except Exception as e:
         | 
| 601 | 
            +
                        self.logger.error(f"Failed to update session info: {e}")
         | 
| 602 | 
            +
                        return False
         | 
| @@ -1,9 +1,9 @@ | |
| 1 | 
            -
            claude_mpm/VERSION,sha256= | 
| 1 | 
            +
            claude_mpm/VERSION,sha256=hhCrnKPRWgpiBU7FuephYkZuc9HC8J3tY6EJASjpJn8,7
         | 
| 2 2 | 
             
            claude_mpm/__init__.py,sha256=ix_J0PHZBz37nVBDEYJmLpwnURlWuBKKQ8rK_00TFpk,964
         | 
| 3 3 | 
             
            claude_mpm/__main__.py,sha256=8IcM9tEbTqSN_er04eKTPX3AGo6qzRiTnPI7KfIf7rw,641
         | 
| 4 4 | 
             
            claude_mpm/constants.py,sha256=yOf-82f1HH6pL19dB3dWPUqU09dnXuAx3kDh3xWpc1U,4526
         | 
| 5 5 | 
             
            claude_mpm/deployment_paths.py,sha256=JO7-fhhp_AkVB7ZssggHDBbee-r2sokpkqjoqnQLTmM,9073
         | 
| 6 | 
            -
            claude_mpm/init.py,sha256= | 
| 6 | 
            +
            claude_mpm/init.py,sha256=lscRw6fKuwIcokLtaFdVh-cuTx0cZWMSMSLR3qlDJkM,8154
         | 
| 7 7 | 
             
            claude_mpm/ticket_wrapper.py,sha256=bWjLReYyuHSBguuiRm1d52rHYNHqrPJAOLUbMt4CnuM,836
         | 
| 8 8 | 
             
            claude_mpm/agents/BASE_AGENT_TEMPLATE.md,sha256=TYgSd9jNBMWp4mAOBUl9dconX4RcGbvmMEScRy5uyko,3343
         | 
| 9 9 | 
             
            claude_mpm/agents/INSTRUCTIONS.md,sha256=tdekngpZ5RjECYZosOaDSBmXPZsVvZcwDQEmmlw7fOQ,14268
         | 
| @@ -37,7 +37,7 @@ claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json,sha256=_FHWnUeh | |
| 37 37 | 
             
            claude_mpm/agents/templates/backup/research_agent_20250726_234551.json,sha256=o4n_sqSbjnsFRELB2q501vgwm-o2tQNLJLYvnVP9LWU,5629
         | 
| 38 38 | 
             
            claude_mpm/agents/templates/backup/security_agent_20250726_234551.json,sha256=l5YuD-27CxKSOsRLv0bDY_tCZyds0yGbeizLb8paeFY,2322
         | 
| 39 39 | 
             
            claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json,sha256=too38RPTLJ9HutCMn0nfmEdCj2me241dx5tUYDFtu94,2143
         | 
| 40 | 
            -
            claude_mpm/cli/__init__.py,sha256= | 
| 40 | 
            +
            claude_mpm/cli/__init__.py,sha256=CV0utQO2Zq_kDSYqsFsjOkILxMZ14MgMHj7ffLDEGHA,6958
         | 
| 41 41 | 
             
            claude_mpm/cli/parser.py,sha256=ajdlusfbfcY44756pdrkfROEVlTaVJyEBDJup78Q-yE,18270
         | 
| 42 42 | 
             
            claude_mpm/cli/utils.py,sha256=k_EHLcjDAzYhDeVeWvE-vqvHsEoG6Cc6Yk7fs3YoRVA,6022
         | 
| 43 43 | 
             
            claude_mpm/cli/commands/__init__.py,sha256=kUtBjfTYZnfAL_4QEPCBtFg2nWgJ2cxCPzIIsiFURXM,567
         | 
| @@ -158,6 +158,7 @@ claude_mpm/services/memory_builder.py,sha256=Y_Gx0IIJhGjUx1NAjslC1MFPbRaXuoRdkQX | |
| 158 158 | 
             
            claude_mpm/services/memory_optimizer.py,sha256=uydUqx0Nd3ib7KNfKnlR9tPW5MLSUC5OAJg0GH2Jj4E,23638
         | 
| 159 159 | 
             
            claude_mpm/services/memory_router.py,sha256=oki6D2ma1KWdgP6uSfu8xvLzRpk-k3PGw6CO5XDMGPY,22504
         | 
| 160 160 | 
             
            claude_mpm/services/project_analyzer.py,sha256=3Ub_Tpcxm0LnYgcAipebvWr6TfofVqZNMkANejA2C44,33564
         | 
| 161 | 
            +
            claude_mpm/services/project_registry.py,sha256=1LRO6LsKvrVa4xkNyDb4rMDN8LXElAgIqhI7DoUjChI,24176
         | 
| 161 162 | 
             
            claude_mpm/services/recovery_manager.py,sha256=K46vXbEWbxFUoS42s34OxN1rkddpisGG7E_ZdRyAZ-A,25792
         | 
| 162 163 | 
             
            claude_mpm/services/shared_prompt_cache.py,sha256=D04lrRWyg0lHyqGcAHy7IYvRHRKSg6EOpAJwBUPa2wk,29890
         | 
| 163 164 | 
             
            claude_mpm/services/socketio_client_manager.py,sha256=2Ly63iiGA_BUzf73UwQDu9Q75wA1C4O1CWFv8hi8bms,18074
         | 
| @@ -215,9 +216,9 @@ claude_mpm/utils/path_operations.py,sha256=6pLMnAWBVzHkgp6JyQHmHbGD-dWn-nX21yV4E | |
| 215 216 | 
             
            claude_mpm/utils/paths.py,sha256=Xv0SZWdZRkRjN9e6clBcA165ya00GNQxt7SwMz51tfA,10153
         | 
| 216 217 | 
             
            claude_mpm/validation/__init__.py,sha256=bJ19g9lnk7yIjtxzN8XPegp87HTFBzCrGQOpFgRTf3g,155
         | 
| 217 218 | 
             
            claude_mpm/validation/agent_validator.py,sha256=GCA2b2rKhKDeaNyUqWxTiWIs3sDdWjD9cgOFRp9K6ic,18227
         | 
| 218 | 
            -
            claude_mpm-3.4. | 
| 219 | 
            -
            claude_mpm-3.4. | 
| 220 | 
            -
            claude_mpm-3.4. | 
| 221 | 
            -
            claude_mpm-3.4. | 
| 222 | 
            -
            claude_mpm-3.4. | 
| 223 | 
            -
            claude_mpm-3.4. | 
| 219 | 
            +
            claude_mpm-3.4.27.dist-info/licenses/LICENSE,sha256=cSdDfXjoTVhstrERrqme4zgxAu4GubU22zVEHsiXGxs,1071
         | 
| 220 | 
            +
            claude_mpm-3.4.27.dist-info/METADATA,sha256=1_Yq8G2LAPn07vwi_EOAJAf8q50f5pm5P9ZJnKRp28M,6527
         | 
| 221 | 
            +
            claude_mpm-3.4.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
         | 
| 222 | 
            +
            claude_mpm-3.4.27.dist-info/entry_points.txt,sha256=3_d7wLrg9sRmQ1SfrFGWoTNL8Wrd6lQb2XVSYbTwRIg,324
         | 
| 223 | 
            +
            claude_mpm-3.4.27.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
         | 
| 224 | 
            +
            claude_mpm-3.4.27.dist-info/RECORD,,
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |