claude-mpm 4.1.4__py3-none-any.whl → 4.1.5__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.
Files changed (41) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/tickets.py +365 -784
  3. claude_mpm/core/output_style_manager.py +24 -0
  4. claude_mpm/core/unified_agent_registry.py +46 -15
  5. claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
  6. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
  7. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
  8. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  9. claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
  10. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  11. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
  12. claude_mpm/services/infrastructure/__init__.py +31 -5
  13. claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
  14. claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
  15. claude_mpm/services/infrastructure/monitoring/base.py +130 -0
  16. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  17. claude_mpm/services/infrastructure/monitoring/network.py +218 -0
  18. claude_mpm/services/infrastructure/monitoring/process.py +342 -0
  19. claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
  20. claude_mpm/services/infrastructure/monitoring/service.py +367 -0
  21. claude_mpm/services/infrastructure/monitoring.py +67 -1030
  22. claude_mpm/services/project/analyzer.py +13 -4
  23. claude_mpm/services/project/analyzer_refactored.py +450 -0
  24. claude_mpm/services/project/analyzer_v2.py +566 -0
  25. claude_mpm/services/project/architecture_analyzer.py +461 -0
  26. claude_mpm/services/project/dependency_analyzer.py +462 -0
  27. claude_mpm/services/project/language_analyzer.py +265 -0
  28. claude_mpm/services/project/metrics_collector.py +410 -0
  29. claude_mpm/services/ticket_manager.py +5 -1
  30. claude_mpm/services/ticket_services/__init__.py +26 -0
  31. claude_mpm/services/ticket_services/crud_service.py +328 -0
  32. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  33. claude_mpm/services/ticket_services/search_service.py +324 -0
  34. claude_mpm/services/ticket_services/validation_service.py +303 -0
  35. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  36. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/RECORD +41 -17
  38. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/WHEEL +0 -0
  39. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/entry_points.txt +0 -0
  40. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/licenses/LICENSE +0 -0
  41. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/top_level.txt +0 -0
@@ -21,6 +21,10 @@ from ..utils.imports import safe_import
21
21
  # Import with fallback support
22
22
  get_logger = safe_import("claude_mpm.core.logger", "core.logger", ["get_logger"])
23
23
 
24
+ # Global cache for Claude version to avoid duplicate detection/logging
25
+ _CACHED_CLAUDE_VERSION: Optional[str] = None
26
+ _VERSION_DETECTED: bool = False
27
+
24
28
 
25
29
  class OutputStyleManager:
26
30
  """Manages output style deployment and version-based handling."""
@@ -41,10 +45,17 @@ class OutputStyleManager:
41
45
  def _detect_claude_version(self) -> Optional[str]:
42
46
  """
43
47
  Detect Claude Code version by running 'claude --version'.
48
+ Uses global cache to avoid duplicate detection and logging.
44
49
 
45
50
  Returns:
46
51
  Version string (e.g., "1.0.82") or None if Claude not found
47
52
  """
53
+ global _CACHED_CLAUDE_VERSION, _VERSION_DETECTED
54
+
55
+ # Return cached version if already detected
56
+ if _VERSION_DETECTED:
57
+ return _CACHED_CLAUDE_VERSION
58
+
48
59
  try:
49
60
  # Run claude --version command
50
61
  result = subprocess.run(
@@ -57,6 +68,8 @@ class OutputStyleManager:
57
68
 
58
69
  if result.returncode != 0:
59
70
  self.logger.warning(f"Claude command failed: {result.stderr}")
71
+ _VERSION_DETECTED = True
72
+ _CACHED_CLAUDE_VERSION = None
60
73
  return None
61
74
 
62
75
  # Parse version from output
@@ -66,19 +79,30 @@ class OutputStyleManager:
66
79
 
67
80
  if version_match:
68
81
  version = version_match.group(1)
82
+ # Only log on first detection
69
83
  self.logger.info(f"Detected Claude version: {version}")
84
+ _CACHED_CLAUDE_VERSION = version
85
+ _VERSION_DETECTED = True
70
86
  return version
71
87
  self.logger.warning(f"Could not parse version from: {version_output}")
88
+ _VERSION_DETECTED = True
89
+ _CACHED_CLAUDE_VERSION = None
72
90
  return None
73
91
 
74
92
  except FileNotFoundError:
75
93
  self.logger.info("Claude Code not found in PATH")
94
+ _VERSION_DETECTED = True
95
+ _CACHED_CLAUDE_VERSION = None
76
96
  return None
77
97
  except subprocess.TimeoutExpired:
78
98
  self.logger.warning("Claude version check timed out")
99
+ _VERSION_DETECTED = True
100
+ _CACHED_CLAUDE_VERSION = None
79
101
  return None
80
102
  except Exception as e:
81
103
  self.logger.warning(f"Error detecting Claude version: {e}")
104
+ _VERSION_DETECTED = True
105
+ _CACHED_CLAUDE_VERSION = None
82
106
  return None
83
107
 
84
108
  def _compare_versions(self, version1: str, version2: str) -> int:
@@ -136,7 +136,13 @@ class UnifiedAgentRegistry:
136
136
 
137
137
  # Discovery configuration
138
138
  self.file_extensions = {".md", ".json", ".yaml", ".yml"}
139
- self.ignore_patterns = {"__pycache__", ".git", "node_modules", ".pytest_cache"}
139
+ self.ignore_patterns = {
140
+ "__pycache__",
141
+ ".git",
142
+ "node_modules",
143
+ ".pytest_cache",
144
+ "backup",
145
+ }
140
146
 
141
147
  # Statistics
142
148
  self.discovery_stats = {
@@ -166,15 +172,15 @@ class UnifiedAgentRegistry:
166
172
  if user_path.exists():
167
173
  self.discovery_paths.append(user_path)
168
174
 
169
- # System-level agents
175
+ # System-level agents (includes templates as a subdirectory)
170
176
  system_path = self.path_manager.get_system_agents_dir()
171
177
  if system_path.exists():
172
178
  self.discovery_paths.append(system_path)
173
179
 
174
- # Templates directory
175
- templates_path = self.path_manager.get_templates_dir()
176
- if templates_path.exists():
177
- self.discovery_paths.append(templates_path)
180
+ # NOTE: Templates directory is NOT added separately because:
181
+ # - templates_path = system_path / "templates"
182
+ # - The rglob("*") in _discover_path will already find templates
183
+ # - Adding it separately causes duplicate discovery
178
184
 
179
185
  logger.debug(
180
186
  f"Discovery paths configured: {[str(p) for p in self.discovery_paths]}"
@@ -256,7 +262,10 @@ class UnifiedAgentRegistry:
256
262
  try:
257
263
  metadata = self._create_agent_metadata(file_path, agent_name, tier)
258
264
  if metadata:
259
- self.registry[agent_name] = metadata
265
+ # Store all discovered agents temporarily for tier precedence
266
+ # Use a unique key that includes tier to prevent overwrites
267
+ registry_key = f"{agent_name}_{tier.value}"
268
+ self.registry[registry_key] = metadata
260
269
  self.discovered_files.add(file_path)
261
270
  logger.debug(
262
271
  f"Discovered agent: {agent_name} ({tier.value}) at {file_path}"
@@ -269,9 +278,29 @@ class UnifiedAgentRegistry:
269
278
  # Remove extension and use filename as agent name
270
279
  name = file_path.stem
271
280
 
272
- # Skip certain files
273
- skip_files = {"README", "INSTRUCTIONS", "template", "example"}
274
- if name.upper() in skip_files:
281
+ # Skip certain files and non-agent templates
282
+ skip_files = {
283
+ "README",
284
+ "INSTRUCTIONS",
285
+ "template",
286
+ "example",
287
+ "base_agent",
288
+ "base_agent_template",
289
+ "agent_template",
290
+ "agent_schema",
291
+ "base_pm",
292
+ "workflow",
293
+ "output_style",
294
+ "memory",
295
+ "optimization_report",
296
+ "vercel_ops_instructions",
297
+ "agent-template",
298
+ "agent-schema", # Also handle hyphenated versions
299
+ }
300
+ # Case-insensitive comparison
301
+ if name.replace("-", "_").upper() in {
302
+ s.replace("-", "_").upper() for s in skip_files
303
+ }:
275
304
  return None
276
305
 
277
306
  # Normalize name
@@ -424,12 +453,14 @@ class UnifiedAgentRegistry:
424
453
 
425
454
  def _apply_tier_precedence(self) -> None:
426
455
  """Apply tier precedence rules to resolve conflicts."""
427
- # Group agents by name
456
+ # Group agents by their actual name (without tier suffix)
428
457
  agent_groups = {}
429
- for name, metadata in self.registry.items():
430
- if name not in agent_groups:
431
- agent_groups[name] = []
432
- agent_groups[name].append(metadata)
458
+ for registry_key, metadata in self.registry.items():
459
+ # Extract the actual agent name (registry_key is "name_tier")
460
+ agent_name = metadata.name # Use the actual name from metadata
461
+ if agent_name not in agent_groups:
462
+ agent_groups[agent_name] = []
463
+ agent_groups[agent_name].append(metadata)
433
464
 
434
465
  # Resolve conflicts using tier precedence
435
466
  resolved_registry = {}
@@ -34,10 +34,14 @@ class AgentDiscoveryService:
34
34
  self.logger = get_logger(__name__)
35
35
  self.templates_dir = templates_dir
36
36
 
37
- def list_available_agents(self) -> List[Dict[str, Any]]:
37
+ def list_available_agents(self, log_discovery: bool = True) -> List[Dict[str, Any]]:
38
38
  """
39
39
  List all available agent templates with their metadata.
40
40
 
41
+ Args:
42
+ log_discovery: Whether to log discovery results (default: True).
43
+ Set to False when called from multi-source discovery to avoid duplicate logs.
44
+
41
45
  Returns:
42
46
  List of agent information dictionaries containing:
43
47
  - name: Agent name
@@ -73,7 +77,11 @@ class AgentDiscoveryService:
73
77
  # Sort by agent name for consistent ordering
74
78
  agents.sort(key=lambda x: x.get("name", ""))
75
79
 
76
- self.logger.info(f"Discovered {len(agents)} available agent templates")
80
+ # Only log if requested (to avoid duplicate logging from multi-source discovery)
81
+ if log_discovery:
82
+ self.logger.info(
83
+ f"Discovered {len(agents)} available agent templates from {self.templates_dir.name}"
84
+ )
77
85
  return agents
78
86
 
79
87
  def get_filtered_templates(
@@ -153,7 +161,8 @@ class AgentDiscoveryService:
153
161
  Dictionary mapping categories to lists of agent names
154
162
  """
155
163
  categories = {}
156
- agents = self.list_available_agents()
164
+ # Don't log discovery when called internally
165
+ agents = self.list_available_agents(log_discovery=False)
157
166
 
158
167
  for agent in agents:
159
168
  agent_name = agent.get("name", "unknown")