claude-mpm 3.5.6__py3-none-any.whl → 3.7.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_AGENT_TEMPLATE.md +96 -23
- claude_mpm/agents/BASE_PM.md +273 -0
- claude_mpm/agents/INSTRUCTIONS.md +114 -103
- claude_mpm/agents/agent_loader.py +36 -1
- claude_mpm/agents/async_agent_loader.py +421 -0
- claude_mpm/agents/templates/code_analyzer.json +81 -0
- claude_mpm/agents/templates/data_engineer.json +18 -3
- claude_mpm/agents/templates/documentation.json +18 -3
- claude_mpm/agents/templates/engineer.json +19 -4
- claude_mpm/agents/templates/ops.json +18 -3
- claude_mpm/agents/templates/qa.json +20 -4
- claude_mpm/agents/templates/research.json +20 -4
- claude_mpm/agents/templates/security.json +18 -3
- claude_mpm/agents/templates/version_control.json +16 -3
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +212 -3
- claude_mpm/cli/commands/aggregate.py +462 -0
- claude_mpm/cli/commands/config.py +277 -0
- claude_mpm/cli/commands/run.py +224 -36
- claude_mpm/cli/parser.py +176 -1
- claude_mpm/constants.py +19 -0
- claude_mpm/core/claude_runner.py +320 -44
- claude_mpm/core/config.py +161 -4
- claude_mpm/core/framework_loader.py +81 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +391 -9
- claude_mpm/init.py +40 -5
- claude_mpm/models/agent_session.py +511 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/start_activity_logging.py +86 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +165 -19
- claude_mpm/services/agents/deployment/async_agent_deployment.py +461 -0
- claude_mpm/services/event_aggregator.py +547 -0
- claude_mpm/utils/agent_dependency_loader.py +655 -0
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/dependency_cache.py +376 -0
- claude_mpm/utils/dependency_strategies.py +343 -0
- claude_mpm/utils/environment_context.py +310 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/METADATA +47 -3
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/RECORD +45 -31
- claude_mpm/agents/templates/pm.json +0 -122
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,376 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            Dependency caching system for smart dependency checking.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            This module provides caching for dependency check results to avoid
         | 
| 5 | 
            +
            redundant checks and improve startup performance.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            import json
         | 
| 9 | 
            +
            import time
         | 
| 10 | 
            +
            from pathlib import Path
         | 
| 11 | 
            +
            from typing import Dict, Optional, Tuple
         | 
| 12 | 
            +
            import hashlib
         | 
| 13 | 
            +
            import logging
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            from ..core.logger import get_logger
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            logger = get_logger(__name__)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
             | 
| 20 | 
            +
            class DependencyCache:
         | 
| 21 | 
            +
                """
         | 
| 22 | 
            +
                Manages caching of dependency check results.
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                WHY: Dependency checking can be slow, especially when checking many packages.
         | 
| 25 | 
            +
                By caching results keyed by deployment hash, we can skip checks when nothing
         | 
| 26 | 
            +
                has changed, significantly improving startup time.
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                DESIGN DECISION: We use a file-based cache in .claude/agents/.dependency_cache
         | 
| 29 | 
            +
                - Simple JSON format for easy debugging
         | 
| 30 | 
            +
                - TTL-based expiration (24 hours default)
         | 
| 31 | 
            +
                - Automatic invalidation when agent deployment changes
         | 
| 32 | 
            +
                """
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                DEFAULT_TTL_SECONDS = 86400  # 24 hours
         | 
| 35 | 
            +
                
         | 
| 36 | 
            +
                def __init__(self, cache_dir: Optional[Path] = None, ttl_seconds: int = DEFAULT_TTL_SECONDS):
         | 
| 37 | 
            +
                    """
         | 
| 38 | 
            +
                    Initialize the dependency cache.
         | 
| 39 | 
            +
                    
         | 
| 40 | 
            +
                    Args:
         | 
| 41 | 
            +
                        cache_dir: Directory for cache files (default: .claude/agents/)
         | 
| 42 | 
            +
                        ttl_seconds: Time-to-live for cache entries in seconds
         | 
| 43 | 
            +
                    """
         | 
| 44 | 
            +
                    if cache_dir is None:
         | 
| 45 | 
            +
                        cache_dir = Path.cwd() / ".claude" / "agents"
         | 
| 46 | 
            +
                    
         | 
| 47 | 
            +
                    self.cache_dir = cache_dir
         | 
| 48 | 
            +
                    self.cache_file = self.cache_dir / ".dependency_cache"
         | 
| 49 | 
            +
                    self.ttl_seconds = ttl_seconds
         | 
| 50 | 
            +
                    self._cache_data: Optional[Dict] = None
         | 
| 51 | 
            +
                
         | 
| 52 | 
            +
                def _load_cache(self) -> Dict:
         | 
| 53 | 
            +
                    """
         | 
| 54 | 
            +
                    Load cache data from disk.
         | 
| 55 | 
            +
                    
         | 
| 56 | 
            +
                    Returns:
         | 
| 57 | 
            +
                        Cache data dictionary or empty dict if not found.
         | 
| 58 | 
            +
                    """
         | 
| 59 | 
            +
                    if self._cache_data is not None:
         | 
| 60 | 
            +
                        return self._cache_data
         | 
| 61 | 
            +
                    
         | 
| 62 | 
            +
                    if not self.cache_file.exists():
         | 
| 63 | 
            +
                        self._cache_data = {}
         | 
| 64 | 
            +
                        return self._cache_data
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    try:
         | 
| 67 | 
            +
                        with open(self.cache_file, 'r') as f:
         | 
| 68 | 
            +
                            self._cache_data = json.load(f)
         | 
| 69 | 
            +
                            return self._cache_data
         | 
| 70 | 
            +
                    except Exception as e:
         | 
| 71 | 
            +
                        logger.debug(f"Could not load dependency cache: {e}")
         | 
| 72 | 
            +
                        self._cache_data = {}
         | 
| 73 | 
            +
                        return self._cache_data
         | 
| 74 | 
            +
                
         | 
| 75 | 
            +
                def _save_cache(self, cache_data: Dict) -> None:
         | 
| 76 | 
            +
                    """
         | 
| 77 | 
            +
                    Save cache data to disk.
         | 
| 78 | 
            +
                    
         | 
| 79 | 
            +
                    Args:
         | 
| 80 | 
            +
                        cache_data: Cache data to save.
         | 
| 81 | 
            +
                    """
         | 
| 82 | 
            +
                    try:
         | 
| 83 | 
            +
                        # Ensure directory exists
         | 
| 84 | 
            +
                        self.cache_dir.mkdir(parents=True, exist_ok=True)
         | 
| 85 | 
            +
                        
         | 
| 86 | 
            +
                        with open(self.cache_file, 'w') as f:
         | 
| 87 | 
            +
                            json.dump(cache_data, f, indent=2)
         | 
| 88 | 
            +
                        
         | 
| 89 | 
            +
                        self._cache_data = cache_data
         | 
| 90 | 
            +
                    except Exception as e:
         | 
| 91 | 
            +
                        logger.debug(f"Could not save dependency cache: {e}")
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                def _generate_cache_key(self, deployment_hash: str, context: Dict) -> str:
         | 
| 94 | 
            +
                    """
         | 
| 95 | 
            +
                    Generate a cache key for dependency results.
         | 
| 96 | 
            +
                    
         | 
| 97 | 
            +
                    Args:
         | 
| 98 | 
            +
                        deployment_hash: Hash of the current agent deployment
         | 
| 99 | 
            +
                        context: Additional context (e.g., Python version, platform)
         | 
| 100 | 
            +
                        
         | 
| 101 | 
            +
                    Returns:
         | 
| 102 | 
            +
                        Cache key string.
         | 
| 103 | 
            +
                        
         | 
| 104 | 
            +
                    WHY: We include context like Python version because dependency
         | 
| 105 | 
            +
                    availability can vary between Python versions.
         | 
| 106 | 
            +
                    """
         | 
| 107 | 
            +
                    # Include important context in the cache key
         | 
| 108 | 
            +
                    import sys
         | 
| 109 | 
            +
                    import platform
         | 
| 110 | 
            +
                    
         | 
| 111 | 
            +
                    key_parts = [
         | 
| 112 | 
            +
                        deployment_hash,
         | 
| 113 | 
            +
                        f"py{sys.version_info.major}.{sys.version_info.minor}",
         | 
| 114 | 
            +
                        platform.system().lower(),
         | 
| 115 | 
            +
                        platform.machine().lower()
         | 
| 116 | 
            +
                    ]
         | 
| 117 | 
            +
                    
         | 
| 118 | 
            +
                    # Add any additional context
         | 
| 119 | 
            +
                    for key, value in sorted(context.items()):
         | 
| 120 | 
            +
                        key_parts.append(f"{key}:{value}")
         | 
| 121 | 
            +
                    
         | 
| 122 | 
            +
                    # Create a hash of all parts for a compact key
         | 
| 123 | 
            +
                    key_string = "|".join(key_parts)
         | 
| 124 | 
            +
                    return hashlib.md5(key_string.encode()).hexdigest()
         | 
| 125 | 
            +
                
         | 
| 126 | 
            +
                def get(
         | 
| 127 | 
            +
                    self,
         | 
| 128 | 
            +
                    deployment_hash: str,
         | 
| 129 | 
            +
                    context: Optional[Dict] = None
         | 
| 130 | 
            +
                ) -> Optional[Dict]:
         | 
| 131 | 
            +
                    """
         | 
| 132 | 
            +
                    Get cached dependency check results.
         | 
| 133 | 
            +
                    
         | 
| 134 | 
            +
                    Args:
         | 
| 135 | 
            +
                        deployment_hash: Hash of the current agent deployment
         | 
| 136 | 
            +
                        context: Additional context for cache key
         | 
| 137 | 
            +
                        
         | 
| 138 | 
            +
                    Returns:
         | 
| 139 | 
            +
                        Cached results or None if not found/expired.
         | 
| 140 | 
            +
                    """
         | 
| 141 | 
            +
                    if context is None:
         | 
| 142 | 
            +
                        context = {}
         | 
| 143 | 
            +
                    
         | 
| 144 | 
            +
                    cache_data = self._load_cache()
         | 
| 145 | 
            +
                    cache_key = self._generate_cache_key(deployment_hash, context)
         | 
| 146 | 
            +
                    
         | 
| 147 | 
            +
                    if cache_key not in cache_data:
         | 
| 148 | 
            +
                        logger.debug(f"Cache miss for key {cache_key}")
         | 
| 149 | 
            +
                        return None
         | 
| 150 | 
            +
                    
         | 
| 151 | 
            +
                    entry = cache_data[cache_key]
         | 
| 152 | 
            +
                    
         | 
| 153 | 
            +
                    # Check if entry has expired
         | 
| 154 | 
            +
                    current_time = time.time()
         | 
| 155 | 
            +
                    if current_time - entry.get('timestamp', 0) > self.ttl_seconds:
         | 
| 156 | 
            +
                        logger.debug(f"Cache entry expired for key {cache_key}")
         | 
| 157 | 
            +
                        # Remove expired entry
         | 
| 158 | 
            +
                        del cache_data[cache_key]
         | 
| 159 | 
            +
                        self._save_cache(cache_data)
         | 
| 160 | 
            +
                        return None
         | 
| 161 | 
            +
                    
         | 
| 162 | 
            +
                    logger.debug(f"Cache hit for key {cache_key}")
         | 
| 163 | 
            +
                    return entry.get('results')
         | 
| 164 | 
            +
                
         | 
| 165 | 
            +
                def set(
         | 
| 166 | 
            +
                    self,
         | 
| 167 | 
            +
                    deployment_hash: str,
         | 
| 168 | 
            +
                    results: Dict,
         | 
| 169 | 
            +
                    context: Optional[Dict] = None
         | 
| 170 | 
            +
                ) -> None:
         | 
| 171 | 
            +
                    """
         | 
| 172 | 
            +
                    Cache dependency check results.
         | 
| 173 | 
            +
                    
         | 
| 174 | 
            +
                    Args:
         | 
| 175 | 
            +
                        deployment_hash: Hash of the current agent deployment
         | 
| 176 | 
            +
                        results: Dependency check results to cache
         | 
| 177 | 
            +
                        context: Additional context for cache key
         | 
| 178 | 
            +
                    """
         | 
| 179 | 
            +
                    if context is None:
         | 
| 180 | 
            +
                        context = {}
         | 
| 181 | 
            +
                    
         | 
| 182 | 
            +
                    cache_data = self._load_cache()
         | 
| 183 | 
            +
                    cache_key = self._generate_cache_key(deployment_hash, context)
         | 
| 184 | 
            +
                    
         | 
| 185 | 
            +
                    # Store with timestamp
         | 
| 186 | 
            +
                    cache_data[cache_key] = {
         | 
| 187 | 
            +
                        'timestamp': time.time(),
         | 
| 188 | 
            +
                        'results': results,
         | 
| 189 | 
            +
                        'deployment_hash': deployment_hash,
         | 
| 190 | 
            +
                        'context': context
         | 
| 191 | 
            +
                    }
         | 
| 192 | 
            +
                    
         | 
| 193 | 
            +
                    # Clean up old entries while we're at it
         | 
| 194 | 
            +
                    self._cleanup_expired_entries(cache_data)
         | 
| 195 | 
            +
                    
         | 
| 196 | 
            +
                    self._save_cache(cache_data)
         | 
| 197 | 
            +
                    logger.debug(f"Cached results for key {cache_key}")
         | 
| 198 | 
            +
                
         | 
| 199 | 
            +
                def invalidate(self, deployment_hash: Optional[str] = None) -> None:
         | 
| 200 | 
            +
                    """
         | 
| 201 | 
            +
                    Invalidate cache entries.
         | 
| 202 | 
            +
                    
         | 
| 203 | 
            +
                    Args:
         | 
| 204 | 
            +
                        deployment_hash: If provided, only invalidate entries for this hash.
         | 
| 205 | 
            +
                                       If None, invalidate all entries.
         | 
| 206 | 
            +
                    """
         | 
| 207 | 
            +
                    if deployment_hash is None:
         | 
| 208 | 
            +
                        # Clear entire cache
         | 
| 209 | 
            +
                        self._cache_data = {}
         | 
| 210 | 
            +
                        self._save_cache({})
         | 
| 211 | 
            +
                        logger.info("Cleared entire dependency cache")
         | 
| 212 | 
            +
                    else:
         | 
| 213 | 
            +
                        # Clear entries for specific deployment
         | 
| 214 | 
            +
                        cache_data = self._load_cache()
         | 
| 215 | 
            +
                        keys_to_remove = []
         | 
| 216 | 
            +
                        
         | 
| 217 | 
            +
                        for key, entry in cache_data.items():
         | 
| 218 | 
            +
                            if entry.get('deployment_hash') == deployment_hash:
         | 
| 219 | 
            +
                                keys_to_remove.append(key)
         | 
| 220 | 
            +
                        
         | 
| 221 | 
            +
                        for key in keys_to_remove:
         | 
| 222 | 
            +
                            del cache_data[key]
         | 
| 223 | 
            +
                        
         | 
| 224 | 
            +
                        if keys_to_remove:
         | 
| 225 | 
            +
                            self._save_cache(cache_data)
         | 
| 226 | 
            +
                            logger.info(f"Invalidated {len(keys_to_remove)} cache entries for deployment {deployment_hash[:8]}...")
         | 
| 227 | 
            +
                
         | 
| 228 | 
            +
                def _cleanup_expired_entries(self, cache_data: Dict) -> None:
         | 
| 229 | 
            +
                    """
         | 
| 230 | 
            +
                    Remove expired entries from cache data.
         | 
| 231 | 
            +
                    
         | 
| 232 | 
            +
                    Args:
         | 
| 233 | 
            +
                        cache_data: Cache data dictionary to clean.
         | 
| 234 | 
            +
                        
         | 
| 235 | 
            +
                    WHY: We clean up expired entries periodically to prevent the cache
         | 
| 236 | 
            +
                    from growing indefinitely.
         | 
| 237 | 
            +
                    """
         | 
| 238 | 
            +
                    current_time = time.time()
         | 
| 239 | 
            +
                    keys_to_remove = []
         | 
| 240 | 
            +
                    
         | 
| 241 | 
            +
                    for key, entry in cache_data.items():
         | 
| 242 | 
            +
                        if current_time - entry.get('timestamp', 0) > self.ttl_seconds:
         | 
| 243 | 
            +
                            keys_to_remove.append(key)
         | 
| 244 | 
            +
                    
         | 
| 245 | 
            +
                    for key in keys_to_remove:
         | 
| 246 | 
            +
                        del cache_data[key]
         | 
| 247 | 
            +
                    
         | 
| 248 | 
            +
                    if keys_to_remove:
         | 
| 249 | 
            +
                        logger.debug(f"Cleaned up {len(keys_to_remove)} expired cache entries")
         | 
| 250 | 
            +
                
         | 
| 251 | 
            +
                def get_cache_stats(self) -> Dict:
         | 
| 252 | 
            +
                    """
         | 
| 253 | 
            +
                    Get statistics about the cache.
         | 
| 254 | 
            +
                    
         | 
| 255 | 
            +
                    Returns:
         | 
| 256 | 
            +
                        Dictionary with cache statistics.
         | 
| 257 | 
            +
                    """
         | 
| 258 | 
            +
                    cache_data = self._load_cache()
         | 
| 259 | 
            +
                    current_time = time.time()
         | 
| 260 | 
            +
                    
         | 
| 261 | 
            +
                    total_entries = len(cache_data)
         | 
| 262 | 
            +
                    expired_entries = 0
         | 
| 263 | 
            +
                    valid_entries = 0
         | 
| 264 | 
            +
                    
         | 
| 265 | 
            +
                    for entry in cache_data.values():
         | 
| 266 | 
            +
                        if current_time - entry.get('timestamp', 0) > self.ttl_seconds:
         | 
| 267 | 
            +
                            expired_entries += 1
         | 
| 268 | 
            +
                        else:
         | 
| 269 | 
            +
                            valid_entries += 1
         | 
| 270 | 
            +
                    
         | 
| 271 | 
            +
                    return {
         | 
| 272 | 
            +
                        'total_entries': total_entries,
         | 
| 273 | 
            +
                        'valid_entries': valid_entries,
         | 
| 274 | 
            +
                        'expired_entries': expired_entries,
         | 
| 275 | 
            +
                        'cache_file': str(self.cache_file),
         | 
| 276 | 
            +
                        'ttl_seconds': self.ttl_seconds
         | 
| 277 | 
            +
                    }
         | 
| 278 | 
            +
                
         | 
| 279 | 
            +
                def clear(self) -> None:
         | 
| 280 | 
            +
                    """
         | 
| 281 | 
            +
                    Clear all cache entries.
         | 
| 282 | 
            +
                    """
         | 
| 283 | 
            +
                    self.invalidate(None)
         | 
| 284 | 
            +
             | 
| 285 | 
            +
             | 
| 286 | 
            +
            class SmartDependencyChecker:
         | 
| 287 | 
            +
                """
         | 
| 288 | 
            +
                Combines caching with agent dependency checking for smart, efficient checks.
         | 
| 289 | 
            +
                
         | 
| 290 | 
            +
                WHY: This class orchestrates the smart dependency checking by combining:
         | 
| 291 | 
            +
                - Change detection (only check when agents change)
         | 
| 292 | 
            +
                - Caching (reuse results when nothing has changed)
         | 
| 293 | 
            +
                - Environment awareness (only prompt in appropriate contexts)
         | 
| 294 | 
            +
                """
         | 
| 295 | 
            +
                
         | 
| 296 | 
            +
                def __init__(self, cache_ttl_seconds: int = DependencyCache.DEFAULT_TTL_SECONDS):
         | 
| 297 | 
            +
                    """
         | 
| 298 | 
            +
                    Initialize the smart dependency checker.
         | 
| 299 | 
            +
                    
         | 
| 300 | 
            +
                    Args:
         | 
| 301 | 
            +
                        cache_ttl_seconds: TTL for cache entries.
         | 
| 302 | 
            +
                    """
         | 
| 303 | 
            +
                    self.cache = DependencyCache(ttl_seconds=cache_ttl_seconds)
         | 
| 304 | 
            +
                    self._last_check_time = 0
         | 
| 305 | 
            +
                    self._min_check_interval = 60  # Don't check more than once per minute
         | 
| 306 | 
            +
                
         | 
| 307 | 
            +
                def should_check_dependencies(
         | 
| 308 | 
            +
                    self,
         | 
| 309 | 
            +
                    force_check: bool = False,
         | 
| 310 | 
            +
                    deployment_hash: Optional[str] = None
         | 
| 311 | 
            +
                ) -> Tuple[bool, str]:
         | 
| 312 | 
            +
                    """
         | 
| 313 | 
            +
                    Determine if dependency checking should be performed.
         | 
| 314 | 
            +
                    
         | 
| 315 | 
            +
                    Args:
         | 
| 316 | 
            +
                        force_check: Force checking regardless of cache/changes
         | 
| 317 | 
            +
                        deployment_hash: Current deployment hash
         | 
| 318 | 
            +
                        
         | 
| 319 | 
            +
                    Returns:
         | 
| 320 | 
            +
                        Tuple of (should_check, reason)
         | 
| 321 | 
            +
                    """
         | 
| 322 | 
            +
                    if force_check:
         | 
| 323 | 
            +
                        return True, "Forced check requested"
         | 
| 324 | 
            +
                    
         | 
| 325 | 
            +
                    # Rate limiting - don't check too frequently
         | 
| 326 | 
            +
                    current_time = time.time()
         | 
| 327 | 
            +
                    if current_time - self._last_check_time < self._min_check_interval:
         | 
| 328 | 
            +
                        return False, f"Checked recently (within {self._min_check_interval}s)"
         | 
| 329 | 
            +
                    
         | 
| 330 | 
            +
                    # Check if we have valid cached results
         | 
| 331 | 
            +
                    if deployment_hash:
         | 
| 332 | 
            +
                        cached_results = self.cache.get(deployment_hash)
         | 
| 333 | 
            +
                        if cached_results:
         | 
| 334 | 
            +
                            return False, "Valid cached results available"
         | 
| 335 | 
            +
                    
         | 
| 336 | 
            +
                    return True, "No valid cache, checking needed"
         | 
| 337 | 
            +
                
         | 
| 338 | 
            +
                def get_or_check_dependencies(
         | 
| 339 | 
            +
                    self,
         | 
| 340 | 
            +
                    loader,  # AgentDependencyLoader instance
         | 
| 341 | 
            +
                    force_check: bool = False
         | 
| 342 | 
            +
                ) -> Tuple[Dict, bool]:
         | 
| 343 | 
            +
                    """
         | 
| 344 | 
            +
                    Get dependency results from cache or perform check.
         | 
| 345 | 
            +
                    
         | 
| 346 | 
            +
                    Args:
         | 
| 347 | 
            +
                        loader: AgentDependencyLoader instance
         | 
| 348 | 
            +
                        force_check: Force checking even if cached
         | 
| 349 | 
            +
                        
         | 
| 350 | 
            +
                    Returns:
         | 
| 351 | 
            +
                        Tuple of (results, was_cached)
         | 
| 352 | 
            +
                    """
         | 
| 353 | 
            +
                    # Check if agents have changed
         | 
| 354 | 
            +
                    has_changed, deployment_hash = loader.has_agents_changed()
         | 
| 355 | 
            +
                    
         | 
| 356 | 
            +
                    # Try to get cached results first
         | 
| 357 | 
            +
                    if not force_check and not has_changed:
         | 
| 358 | 
            +
                        cached_results = self.cache.get(deployment_hash)
         | 
| 359 | 
            +
                        if cached_results:
         | 
| 360 | 
            +
                            logger.info("Using cached dependency check results")
         | 
| 361 | 
            +
                            return cached_results, True
         | 
| 362 | 
            +
                    
         | 
| 363 | 
            +
                    # Perform actual dependency check
         | 
| 364 | 
            +
                    logger.info("Performing dependency check...")
         | 
| 365 | 
            +
                    results = loader.load_and_check()
         | 
| 366 | 
            +
                    
         | 
| 367 | 
            +
                    # Cache the results
         | 
| 368 | 
            +
                    self.cache.set(deployment_hash, results)
         | 
| 369 | 
            +
                    
         | 
| 370 | 
            +
                    # Mark deployment as checked
         | 
| 371 | 
            +
                    loader.mark_deployment_checked(deployment_hash, results)
         | 
| 372 | 
            +
                    
         | 
| 373 | 
            +
                    # Update last check time
         | 
| 374 | 
            +
                    self._last_check_time = time.time()
         | 
| 375 | 
            +
                    
         | 
| 376 | 
            +
                    return results, False
         |