claude-mpm 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,677 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent Registry Service - Consolidated Module
4
+ ===========================================
5
+
6
+ Provides fully synchronous agent discovery and management system with caching,
7
+ validation, and hierarchical organization support.
8
+
9
+ Features:
10
+ - Two-tier hierarchy discovery (user → system)
11
+ - Synchronous directory scanning
12
+ - Agent metadata collection and caching
13
+ - Agent type detection and classification
14
+ - SharedPromptCache integration
15
+ - Agent validation and error handling
16
+
17
+ This is a consolidated version combining all functionality from the previous
18
+ multi-file implementation for better maintainability.
19
+ """
20
+
21
+ import os
22
+ import json
23
+ import time
24
+ import hashlib
25
+ import logging
26
+ from pathlib import Path
27
+ from typing import Dict, List, Optional, Set, Tuple, Any, Union
28
+ from dataclasses import dataclass, field, asdict
29
+ from datetime import datetime
30
+ from enum import Enum
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ # ============================================================================
36
+ # Constants and Types
37
+ # ============================================================================
38
+
39
+ CORE_AGENT_TYPES = {
40
+ 'engineer', 'architect', 'qa', 'security', 'documentation',
41
+ 'ops', 'data', 'research', 'version_control'
42
+ }
43
+
44
+ SPECIALIZED_AGENT_TYPES = {
45
+ 'pm_orchestrator', 'frontend', 'backend', 'devops', 'ml',
46
+ 'database', 'api', 'mobile', 'cloud', 'testing'
47
+ }
48
+
49
+ ALL_AGENT_TYPES = CORE_AGENT_TYPES | SPECIALIZED_AGENT_TYPES
50
+
51
+
52
+ class AgentTier(Enum):
53
+ """Agent hierarchy tiers."""
54
+ USER = "user"
55
+ SYSTEM = "system"
56
+
57
+
58
+ class AgentType(Enum):
59
+ """Agent classification types."""
60
+ CORE = "core"
61
+ SPECIALIZED = "specialized"
62
+ CUSTOM = "custom"
63
+ UNKNOWN = "unknown"
64
+
65
+
66
+ # ============================================================================
67
+ # Data Models
68
+ # ============================================================================
69
+
70
+ @dataclass
71
+ class AgentMetadata:
72
+ """Complete metadata for discovered agent."""
73
+ name: str
74
+ path: str
75
+ tier: AgentTier
76
+ agent_type: AgentType
77
+ description: str = ""
78
+ version: str = "0.0.0"
79
+ dependencies: List[str] = field(default_factory=list)
80
+ capabilities: List[str] = field(default_factory=list)
81
+ created_at: float = field(default_factory=time.time)
82
+ last_modified: float = field(default_factory=time.time)
83
+ file_size: int = 0
84
+ checksum: str = ""
85
+ is_valid: bool = True
86
+ validation_errors: List[str] = field(default_factory=list)
87
+ metadata: Dict[str, Any] = field(default_factory=dict)
88
+
89
+ def to_dict(self) -> Dict[str, Any]:
90
+ """Convert to dictionary for serialization."""
91
+ data = asdict(self)
92
+ data['tier'] = self.tier.value
93
+ data['agent_type'] = self.agent_type.value
94
+ return data
95
+
96
+ @classmethod
97
+ def from_dict(cls, data: Dict[str, Any]) -> 'AgentMetadata':
98
+ """Create from dictionary."""
99
+ data['tier'] = AgentTier(data['tier'])
100
+ data['agent_type'] = AgentType(data['agent_type'])
101
+ return cls(**data)
102
+
103
+
104
+ # ============================================================================
105
+ # Main Registry Class
106
+ # ============================================================================
107
+
108
+ class AgentRegistry:
109
+ """
110
+ Core Agent Registry - Fully synchronous agent discovery and management system.
111
+
112
+ This consolidated version combines all functionality from the previous
113
+ multi-file implementation into a single, maintainable module.
114
+ """
115
+
116
+ def __init__(self, cache_service=None, model_selector=None):
117
+ """Initialize AgentRegistry with optional cache service and model selector."""
118
+ self.cache_service = cache_service
119
+ self.model_selector = model_selector
120
+
121
+ # Registry storage
122
+ self.registry: Dict[str, AgentMetadata] = {}
123
+ self.discovery_paths: List[Path] = []
124
+
125
+ # Cache configuration
126
+ self.cache_enabled = cache_service is not None
127
+ self.cache_ttl = 3600 # 1 hour
128
+ self.cache_prefix = "agent_registry"
129
+
130
+ # Discovery configuration
131
+ self.file_extensions = {'.md', '.json', '.yaml', '.yml'}
132
+ self.ignore_patterns = {'__pycache__', '.git', 'node_modules', '.pytest_cache'}
133
+
134
+ # Statistics
135
+ self.discovery_stats = {
136
+ 'last_discovery': None,
137
+ 'total_discovered': 0,
138
+ 'cache_hits': 0,
139
+ 'cache_misses': 0,
140
+ 'discovery_duration': 0.0
141
+ }
142
+
143
+ # Setup discovery paths
144
+ self._setup_discovery_paths()
145
+
146
+ logger.info(f"AgentRegistry initialized with cache={'enabled' if self.cache_enabled else 'disabled'}")
147
+
148
+ def _setup_discovery_paths(self) -> None:
149
+ """Setup standard discovery paths for agent files."""
150
+ # User-level agents
151
+ user_path = Path.home() / '.claude-pm' / 'agents'
152
+ if user_path.exists():
153
+ self.discovery_paths.append(user_path)
154
+
155
+ # System-level agents - multiple possible locations
156
+ system_paths = [
157
+ Path(__file__).parent.parent / 'agents' / 'templates',
158
+ Path(__file__).parent.parent / 'framework' / 'agent-roles',
159
+ Path('/opt/claude-pm/agents'),
160
+ Path('/usr/local/claude-pm/agents')
161
+ ]
162
+
163
+ for path in system_paths:
164
+ if path.exists():
165
+ self.discovery_paths.append(path)
166
+
167
+ logger.debug(f"Discovery paths configured: {[str(p) for p in self.discovery_paths]}")
168
+
169
+ # ========================================================================
170
+ # Discovery Methods
171
+ # ========================================================================
172
+
173
+ def discover_agents(self, force_refresh: bool = False) -> Dict[str, AgentMetadata]:
174
+ """
175
+ Discover all available agents across configured paths.
176
+
177
+ Args:
178
+ force_refresh: Force re-discovery even if cache is valid
179
+
180
+ Returns:
181
+ Dictionary of agent name to metadata
182
+ """
183
+ start_time = time.time()
184
+
185
+ # Try cache first
186
+ if not force_refresh and self.cache_enabled:
187
+ cached = self._get_cached_registry()
188
+ if cached:
189
+ self.registry = cached
190
+ self.discovery_stats['cache_hits'] += 1
191
+ logger.debug("Using cached agent registry")
192
+ return self.registry
193
+
194
+ self.discovery_stats['cache_misses'] += 1
195
+
196
+ # Clear existing registry
197
+ self.registry.clear()
198
+
199
+ # Discover agents from all paths
200
+ for discovery_path in self.discovery_paths:
201
+ tier = self._determine_tier(discovery_path)
202
+ self._discover_path(discovery_path, tier)
203
+
204
+ # Handle tier precedence
205
+ self._apply_tier_precedence()
206
+
207
+ # Cache the results
208
+ if self.cache_enabled:
209
+ self._cache_registry()
210
+
211
+ # Update statistics
212
+ self.discovery_stats['last_discovery'] = time.time()
213
+ self.discovery_stats['total_discovered'] = len(self.registry)
214
+ self.discovery_stats['discovery_duration'] = time.time() - start_time
215
+
216
+ logger.info(f"Discovered {len(self.registry)} agents in {self.discovery_stats['discovery_duration']:.2f}s")
217
+
218
+ return self.registry
219
+
220
+ def _discover_path(self, path: Path, tier: AgentTier) -> None:
221
+ """Discover agents in a specific path."""
222
+ if not path.exists():
223
+ return
224
+
225
+ for file_path in path.rglob('*'):
226
+ # Skip directories and ignored patterns
227
+ if file_path.is_dir():
228
+ continue
229
+
230
+ if any(pattern in str(file_path) for pattern in self.ignore_patterns):
231
+ continue
232
+
233
+ # Check file extension
234
+ if file_path.suffix not in self.file_extensions:
235
+ continue
236
+
237
+ # Extract agent name
238
+ agent_name = self._extract_agent_name(file_path)
239
+ if not agent_name:
240
+ continue
241
+
242
+ # Create metadata
243
+ metadata = self._create_agent_metadata(file_path, agent_name, tier)
244
+
245
+ # Validate agent
246
+ if self._validate_agent(metadata):
247
+ # Check tier precedence
248
+ if agent_name in self.registry:
249
+ existing = self.registry[agent_name]
250
+ if self._has_tier_precedence(metadata.tier, existing.tier):
251
+ self.registry[agent_name] = metadata
252
+ logger.debug(f"Replaced {agent_name} with higher precedence version from {tier.value}")
253
+ else:
254
+ self.registry[agent_name] = metadata
255
+
256
+ def _extract_agent_name(self, file_path: Path) -> Optional[str]:
257
+ """Extract agent name from file path."""
258
+ name = file_path.stem
259
+
260
+ # Remove common suffixes
261
+ suffixes_to_remove = ['_agent', '-agent', '.agent']
262
+ for suffix in suffixes_to_remove:
263
+ if name.endswith(suffix):
264
+ name = name[:-len(suffix)]
265
+ break
266
+
267
+ # Skip empty or invalid names
268
+ if not name or name.startswith('.'):
269
+ return None
270
+
271
+ return name
272
+
273
+ def _create_agent_metadata(self, file_path: Path, agent_name: str, tier: AgentTier) -> AgentMetadata:
274
+ """Create agent metadata from file."""
275
+ # Get file stats
276
+ stat = file_path.stat()
277
+
278
+ # Calculate checksum
279
+ checksum = ""
280
+ try:
281
+ with open(file_path, 'rb') as f:
282
+ checksum = hashlib.md5(f.read()).hexdigest()
283
+ except Exception as e:
284
+ logger.warning(f"Failed to calculate checksum for {file_path}: {e}")
285
+
286
+ # Determine agent type
287
+ agent_type = self._classify_agent(agent_name)
288
+
289
+ # Extract description and metadata from file
290
+ description = ""
291
+ version = "0.0.0"
292
+ capabilities = []
293
+ metadata = {}
294
+
295
+ try:
296
+ content = file_path.read_text()
297
+
298
+ # Try to parse as JSON/YAML for structured data
299
+ if file_path.suffix in ['.json', '.yaml', '.yml']:
300
+ try:
301
+ if file_path.suffix == '.json':
302
+ data = json.loads(content)
303
+ else:
304
+ import yaml
305
+ data = yaml.safe_load(content)
306
+
307
+ description = data.get('description', '')
308
+ version = data.get('version', '0.0.0')
309
+ capabilities = data.get('capabilities', [])
310
+ metadata = data.get('metadata', {})
311
+ except Exception:
312
+ pass
313
+
314
+ # Extract from markdown files
315
+ elif file_path.suffix == '.md':
316
+ lines = content.split('\n')
317
+ for i, line in enumerate(lines[:20]): # Check first 20 lines
318
+ if line.strip().startswith('#') and i == 0:
319
+ description = line.strip('#').strip()
320
+ elif line.startswith('Version:'):
321
+ version = line.split(':', 1)[1].strip()
322
+ elif line.startswith('Description:'):
323
+ description = line.split(':', 1)[1].strip()
324
+
325
+ except Exception as e:
326
+ logger.warning(f"Failed to parse {file_path}: {e}")
327
+
328
+ return AgentMetadata(
329
+ name=agent_name,
330
+ path=str(file_path),
331
+ tier=tier,
332
+ agent_type=agent_type,
333
+ description=description,
334
+ version=version,
335
+ capabilities=capabilities,
336
+ created_at=stat.st_ctime,
337
+ last_modified=stat.st_mtime,
338
+ file_size=stat.st_size,
339
+ checksum=checksum,
340
+ metadata=metadata
341
+ )
342
+
343
+ def _classify_agent(self, agent_name: str) -> AgentType:
344
+ """Classify agent based on name."""
345
+ name_lower = agent_name.lower()
346
+
347
+ # Remove common suffixes for classification
348
+ for suffix in ['_agent', '-agent', '.agent']:
349
+ if name_lower.endswith(suffix):
350
+ name_lower = name_lower[:-len(suffix)]
351
+
352
+ if name_lower in CORE_AGENT_TYPES:
353
+ return AgentType.CORE
354
+ elif name_lower in SPECIALIZED_AGENT_TYPES:
355
+ return AgentType.SPECIALIZED
356
+ elif any(core in name_lower for core in CORE_AGENT_TYPES):
357
+ return AgentType.CORE
358
+ elif any(spec in name_lower for spec in SPECIALIZED_AGENT_TYPES):
359
+ return AgentType.SPECIALIZED
360
+ else:
361
+ return AgentType.CUSTOM
362
+
363
+ def _determine_tier(self, path: Path) -> AgentTier:
364
+ """Determine tier based on path location."""
365
+ path_str = str(path).lower()
366
+
367
+ if '.claude-pm' in path_str or str(Path.home()) in str(path):
368
+ return AgentTier.USER
369
+ else:
370
+ return AgentTier.SYSTEM
371
+
372
+ def _has_tier_precedence(self, tier1: AgentTier, tier2: AgentTier) -> bool:
373
+ """Check if tier1 has precedence over tier2."""
374
+ precedence = {
375
+ AgentTier.USER: 2,
376
+ AgentTier.SYSTEM: 1
377
+ }
378
+ return precedence.get(tier1, 0) > precedence.get(tier2, 0)
379
+
380
+ def _apply_tier_precedence(self) -> None:
381
+ """Apply tier precedence rules to discovered agents."""
382
+ # Group agents by name
383
+ agents_by_name: Dict[str, List[AgentMetadata]] = {}
384
+
385
+ for agent in self.registry.values():
386
+ if agent.name not in agents_by_name:
387
+ agents_by_name[agent.name] = []
388
+ agents_by_name[agent.name].append(agent)
389
+
390
+ # Apply precedence
391
+ self.registry.clear()
392
+ for agent_name, agents in agents_by_name.items():
393
+ if len(agents) == 1:
394
+ self.registry[agent_name] = agents[0]
395
+ else:
396
+ # Sort by tier precedence
397
+ agents.sort(key=lambda a: {AgentTier.USER: 2, AgentTier.SYSTEM: 1}.get(a.tier, 0), reverse=True)
398
+ self.registry[agent_name] = agents[0]
399
+
400
+ if len(agents) > 1:
401
+ logger.debug(f"Applied tier precedence for {agent_name}: using {agents[0].tier.value} version")
402
+
403
+ # ========================================================================
404
+ # Validation Methods
405
+ # ========================================================================
406
+
407
+ def _validate_agent(self, metadata: AgentMetadata) -> bool:
408
+ """Validate agent metadata and file."""
409
+ errors = []
410
+
411
+ # Check file exists
412
+ if not Path(metadata.path).exists():
413
+ errors.append("Agent file does not exist")
414
+
415
+ # Check name validity
416
+ if not metadata.name or metadata.name.startswith('.'):
417
+ errors.append("Invalid agent name")
418
+
419
+ # Check for required fields based on file type
420
+ if metadata.path.endswith('.json'):
421
+ try:
422
+ with open(metadata.path) as f:
423
+ data = json.load(f)
424
+ if 'name' not in data:
425
+ errors.append("Missing 'name' field in JSON")
426
+ if 'role' not in data:
427
+ errors.append("Missing 'role' field in JSON")
428
+ except Exception as e:
429
+ errors.append(f"Invalid JSON: {e}")
430
+
431
+ # Update metadata
432
+ metadata.is_valid = len(errors) == 0
433
+ metadata.validation_errors = errors
434
+
435
+ return metadata.is_valid
436
+
437
+ # ========================================================================
438
+ # Cache Methods
439
+ # ========================================================================
440
+
441
+ def _get_cached_registry(self) -> Optional[Dict[str, AgentMetadata]]:
442
+ """Get registry from cache if available."""
443
+ if not self.cache_service:
444
+ return None
445
+
446
+ try:
447
+ cache_key = f"{self.cache_prefix}_registry"
448
+ cached_data = self.cache_service.get(cache_key)
449
+
450
+ if cached_data:
451
+ # Deserialize metadata
452
+ registry = {}
453
+ for name, data in cached_data.items():
454
+ registry[name] = AgentMetadata.from_dict(data)
455
+ return registry
456
+
457
+ except Exception as e:
458
+ logger.warning(f"Failed to get cached registry: {e}")
459
+
460
+ return None
461
+
462
+ def _cache_registry(self) -> None:
463
+ """Cache the current registry."""
464
+ if not self.cache_service:
465
+ return
466
+
467
+ try:
468
+ cache_key = f"{self.cache_prefix}_registry"
469
+ # Serialize metadata
470
+ cache_data = {
471
+ name: metadata.to_dict()
472
+ for name, metadata in self.registry.items()
473
+ }
474
+
475
+ self.cache_service.set(cache_key, cache_data, ttl=self.cache_ttl)
476
+ logger.debug("Cached agent registry")
477
+
478
+ except Exception as e:
479
+ logger.warning(f"Failed to cache registry: {e}")
480
+
481
+ def invalidate_cache(self) -> None:
482
+ """Invalidate the registry cache."""
483
+ if self.cache_service:
484
+ try:
485
+ cache_key = f"{self.cache_prefix}_registry"
486
+ self.cache_service.delete(cache_key)
487
+ logger.debug("Invalidated registry cache")
488
+ except Exception as e:
489
+ logger.warning(f"Failed to invalidate cache: {e}")
490
+
491
+ # ========================================================================
492
+ # Query Methods
493
+ # ========================================================================
494
+
495
+ def get_agent(self, name: str) -> Optional[AgentMetadata]:
496
+ """Get metadata for a specific agent."""
497
+ # Ensure registry is populated
498
+ if not self.registry:
499
+ self.discover_agents()
500
+
501
+ return self.registry.get(name)
502
+
503
+ def list_agents(self, tier: Optional[AgentTier] = None,
504
+ agent_type: Optional[AgentType] = None) -> List[AgentMetadata]:
505
+ """List agents with optional filtering."""
506
+ # Ensure registry is populated
507
+ if not self.registry:
508
+ self.discover_agents()
509
+
510
+ agents = list(self.registry.values())
511
+
512
+ # Apply filters
513
+ if tier:
514
+ agents = [a for a in agents if a.tier == tier]
515
+
516
+ if agent_type:
517
+ agents = [a for a in agents if a.agent_type == agent_type]
518
+
519
+ return agents
520
+
521
+ def get_agent_names(self) -> List[str]:
522
+ """Get list of all agent names."""
523
+ if not self.registry:
524
+ self.discover_agents()
525
+
526
+ return sorted(self.registry.keys())
527
+
528
+ def get_core_agents(self) -> List[AgentMetadata]:
529
+ """Get all core framework agents."""
530
+ return self.list_agents(agent_type=AgentType.CORE)
531
+
532
+ def get_specialized_agents(self) -> List[AgentMetadata]:
533
+ """Get all specialized agents."""
534
+ return self.list_agents(agent_type=AgentType.SPECIALIZED)
535
+
536
+ def get_custom_agents(self) -> List[AgentMetadata]:
537
+ """Get all custom user-defined agents."""
538
+ return self.list_agents(agent_type=AgentType.CUSTOM)
539
+
540
+ def search_agents(self, query: str) -> List[AgentMetadata]:
541
+ """Search agents by name or description."""
542
+ if not self.registry:
543
+ self.discover_agents()
544
+
545
+ query_lower = query.lower()
546
+ results = []
547
+
548
+ for agent in self.registry.values():
549
+ if (query_lower in agent.name.lower() or
550
+ query_lower in agent.description.lower()):
551
+ results.append(agent)
552
+
553
+ return results
554
+
555
+ # ========================================================================
556
+ # Statistics and Monitoring
557
+ # ========================================================================
558
+
559
+ def get_statistics(self) -> Dict[str, Any]:
560
+ """Get comprehensive registry statistics."""
561
+ if not self.registry:
562
+ self.discover_agents()
563
+
564
+ stats = {
565
+ 'total_agents': len(self.registry),
566
+ 'discovery_stats': self.discovery_stats.copy(),
567
+ 'agents_by_tier': {},
568
+ 'agents_by_type': {},
569
+ 'validation_stats': {
570
+ 'valid': 0,
571
+ 'invalid': 0,
572
+ 'errors': []
573
+ }
574
+ }
575
+
576
+ # Count by tier
577
+ for agent in self.registry.values():
578
+ tier = agent.tier.value
579
+ stats['agents_by_tier'][tier] = stats['agents_by_tier'].get(tier, 0) + 1
580
+
581
+ # Count by type
582
+ for agent in self.registry.values():
583
+ agent_type = agent.agent_type.value
584
+ stats['agents_by_type'][agent_type] = stats['agents_by_type'].get(agent_type, 0) + 1
585
+
586
+ # Validation stats
587
+ for agent in self.registry.values():
588
+ if agent.is_valid:
589
+ stats['validation_stats']['valid'] += 1
590
+ else:
591
+ stats['validation_stats']['invalid'] += 1
592
+ stats['validation_stats']['errors'].extend(agent.validation_errors)
593
+
594
+ return stats
595
+
596
+ def validate_all_agents(self) -> Dict[str, List[str]]:
597
+ """Validate all discovered agents and return errors."""
598
+ if not self.registry:
599
+ self.discover_agents()
600
+
601
+ errors = {}
602
+
603
+ for agent_name, metadata in self.registry.items():
604
+ # Re-validate
605
+ self._validate_agent(metadata)
606
+
607
+ if not metadata.is_valid:
608
+ errors[agent_name] = metadata.validation_errors
609
+
610
+ return errors
611
+
612
+ # ========================================================================
613
+ # Utility Methods
614
+ # ========================================================================
615
+
616
+ def add_discovery_path(self, path: Union[str, Path]) -> None:
617
+ """Add a new path for agent discovery."""
618
+ path = Path(path)
619
+ if path.exists() and path not in self.discovery_paths:
620
+ self.discovery_paths.append(path)
621
+ logger.info(f"Added discovery path: {path}")
622
+ # Invalidate cache since paths changed
623
+ self.invalidate_cache()
624
+
625
+ def remove_discovery_path(self, path: Union[str, Path]) -> None:
626
+ """Remove a path from agent discovery."""
627
+ path = Path(path)
628
+ if path in self.discovery_paths:
629
+ self.discovery_paths.remove(path)
630
+ logger.info(f"Removed discovery path: {path}")
631
+ # Invalidate cache since paths changed
632
+ self.invalidate_cache()
633
+
634
+ def export_registry(self, output_path: Union[str, Path]) -> None:
635
+ """Export registry to JSON file."""
636
+ if not self.registry:
637
+ self.discover_agents()
638
+
639
+ output_path = Path(output_path)
640
+
641
+ # Serialize registry
642
+ export_data = {
643
+ 'metadata': {
644
+ 'exported_at': time.time(),
645
+ 'total_agents': len(self.registry),
646
+ 'discovery_paths': [str(p) for p in self.discovery_paths]
647
+ },
648
+ 'agents': {
649
+ name: metadata.to_dict()
650
+ for name, metadata in self.registry.items()
651
+ }
652
+ }
653
+
654
+ with open(output_path, 'w') as f:
655
+ json.dump(export_data, f, indent=2)
656
+
657
+ logger.info(f"Exported registry to {output_path}")
658
+
659
+ def import_registry(self, input_path: Union[str, Path]) -> None:
660
+ """Import registry from JSON file."""
661
+ input_path = Path(input_path)
662
+
663
+ with open(input_path, 'r') as f:
664
+ data = json.load(f)
665
+
666
+ # Clear current registry
667
+ self.registry.clear()
668
+
669
+ # Import agents
670
+ for name, agent_data in data.get('agents', {}).items():
671
+ self.registry[name] = AgentMetadata.from_dict(agent_data)
672
+
673
+ # Cache imported registry
674
+ if self.cache_enabled:
675
+ self._cache_registry()
676
+
677
+ logger.info(f"Imported {len(self.registry)} agents from {input_path}")