claude-mpm 4.1.3__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 (77) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +16 -19
  3. claude_mpm/agents/MEMORY.md +21 -49
  4. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +156 -0
  5. claude_mpm/agents/templates/api_qa.json +36 -116
  6. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +42 -9
  7. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +29 -6
  8. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +34 -6
  9. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +41 -9
  10. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +30 -8
  11. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +2 -2
  12. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +29 -6
  13. claude_mpm/agents/templates/backup/research_memory_efficient.json +2 -2
  14. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +41 -9
  15. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +23 -7
  16. claude_mpm/agents/templates/code_analyzer.json +18 -36
  17. claude_mpm/agents/templates/data_engineer.json +43 -14
  18. claude_mpm/agents/templates/documentation.json +55 -74
  19. claude_mpm/agents/templates/engineer.json +56 -61
  20. claude_mpm/agents/templates/imagemagick.json +7 -2
  21. claude_mpm/agents/templates/memory_manager.json +1 -1
  22. claude_mpm/agents/templates/ops.json +36 -4
  23. claude_mpm/agents/templates/project_organizer.json +23 -71
  24. claude_mpm/agents/templates/qa.json +34 -2
  25. claude_mpm/agents/templates/refactoring_engineer.json +9 -5
  26. claude_mpm/agents/templates/research.json +36 -4
  27. claude_mpm/agents/templates/security.json +29 -2
  28. claude_mpm/agents/templates/ticketing.json +3 -3
  29. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  30. claude_mpm/agents/templates/version_control.json +28 -2
  31. claude_mpm/agents/templates/web_qa.json +38 -151
  32. claude_mpm/agents/templates/web_ui.json +2 -2
  33. claude_mpm/cli/commands/agent_manager.py +221 -1
  34. claude_mpm/cli/commands/tickets.py +365 -784
  35. claude_mpm/cli/parsers/agent_manager_parser.py +34 -0
  36. claude_mpm/core/framework_loader.py +91 -0
  37. claude_mpm/core/log_manager.py +49 -1
  38. claude_mpm/core/output_style_manager.py +24 -0
  39. claude_mpm/core/unified_agent_registry.py +46 -15
  40. claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
  41. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
  42. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
  43. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  44. claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
  45. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
  47. claude_mpm/services/infrastructure/__init__.py +31 -5
  48. claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
  49. claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
  50. claude_mpm/services/infrastructure/monitoring/base.py +130 -0
  51. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  52. claude_mpm/services/infrastructure/monitoring/network.py +218 -0
  53. claude_mpm/services/infrastructure/monitoring/process.py +342 -0
  54. claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
  55. claude_mpm/services/infrastructure/monitoring/service.py +367 -0
  56. claude_mpm/services/infrastructure/monitoring.py +67 -1030
  57. claude_mpm/services/memory/router.py +116 -10
  58. claude_mpm/services/project/analyzer.py +13 -4
  59. claude_mpm/services/project/analyzer_refactored.py +450 -0
  60. claude_mpm/services/project/analyzer_v2.py +566 -0
  61. claude_mpm/services/project/architecture_analyzer.py +461 -0
  62. claude_mpm/services/project/dependency_analyzer.py +462 -0
  63. claude_mpm/services/project/language_analyzer.py +265 -0
  64. claude_mpm/services/project/metrics_collector.py +410 -0
  65. claude_mpm/services/ticket_manager.py +5 -1
  66. claude_mpm/services/ticket_services/__init__.py +26 -0
  67. claude_mpm/services/ticket_services/crud_service.py +328 -0
  68. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  69. claude_mpm/services/ticket_services/search_service.py +324 -0
  70. claude_mpm/services/ticket_services/validation_service.py +303 -0
  71. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  72. {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/METADATA +1 -1
  73. {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/RECORD +77 -52
  74. {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/WHEEL +0 -0
  75. {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/entry_points.txt +0 -0
  76. {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/licenses/LICENSE +0 -0
  77. {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/top_level.txt +0 -0
@@ -32,6 +32,9 @@ Examples:
32
32
  claude-mpm agent-manager show --id engineer # Show agent details
33
33
  claude-mpm agent-manager test --id my-agent # Test agent configuration
34
34
  claude-mpm agent-manager templates # List available templates
35
+ claude-mpm agent-manager reset --dry-run # Preview agent cleanup
36
+ claude-mpm agent-manager reset --force # Remove all claude-mpm agents
37
+ claude-mpm agent-manager reset --project-only # Clean only project agents
35
38
  """,
36
39
  )
37
40
 
@@ -197,3 +200,34 @@ Examples:
197
200
  default="text",
198
201
  help="Output format (default: text)",
199
202
  )
203
+
204
+ # Reset command
205
+ reset_parser = agent_subparsers.add_parser(
206
+ "reset", help="Remove claude-mpm authored agents for clean install"
207
+ )
208
+ reset_parser.add_argument(
209
+ "--force",
210
+ action="store_true",
211
+ help="Execute cleanup immediately without confirmation",
212
+ )
213
+ reset_parser.add_argument(
214
+ "--dry-run",
215
+ action="store_true",
216
+ help="Preview what would be removed without making changes",
217
+ )
218
+ reset_parser.add_argument(
219
+ "--project-only",
220
+ action="store_true",
221
+ help="Only clean project-level agents (.claude/agents)",
222
+ )
223
+ reset_parser.add_argument(
224
+ "--user-only",
225
+ action="store_true",
226
+ help="Only clean user-level agents (~/.claude/agents)",
227
+ )
228
+ reset_parser.add_argument(
229
+ "--format",
230
+ choices=["text", "json"],
231
+ default="text",
232
+ help="Output format (default: text)",
233
+ )
@@ -1283,6 +1283,14 @@ Extract tickets from these patterns:
1283
1283
  if agent.get("model") and agent["model"] != "opus":
1284
1284
  section += f"- **Model**: {agent['model']}\n"
1285
1285
 
1286
+ # Add memory routing information if available
1287
+ if agent.get("memory_routing"):
1288
+ memory_routing = agent["memory_routing"]
1289
+ if memory_routing.get("description"):
1290
+ section += (
1291
+ f"- **Memory Routing**: {memory_routing['description']}\n"
1292
+ )
1293
+
1286
1294
  # Add simple Context-Aware Agent Selection
1287
1295
  section += "\n## Context-Aware Agent Selection\n\n"
1288
1296
  section += (
@@ -1384,6 +1392,14 @@ Extract tickets from these patterns:
1384
1392
  if routing_data:
1385
1393
  agent_data["routing"] = routing_data
1386
1394
 
1395
+ # Try to load memory routing metadata from JSON template if not in YAML frontmatter
1396
+ if "memory_routing" not in agent_data:
1397
+ memory_routing_data = self._load_memory_routing_from_template(
1398
+ agent_file.stem
1399
+ )
1400
+ if memory_routing_data:
1401
+ agent_data["memory_routing"] = memory_routing_data
1402
+
1387
1403
  # Cache the parsed metadata
1388
1404
  self._cache_manager.set_agent_metadata(cache_key, agent_data, file_mtime)
1389
1405
 
@@ -1393,6 +1409,81 @@ Extract tickets from these patterns:
1393
1409
  self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
1394
1410
  return None
1395
1411
 
1412
+ def _load_memory_routing_from_template(
1413
+ self, agent_name: str
1414
+ ) -> Optional[Dict[str, Any]]:
1415
+ """Load memory routing metadata from agent JSON template.
1416
+
1417
+ Args:
1418
+ agent_name: Name of the agent (stem of the file)
1419
+
1420
+ Returns:
1421
+ Dictionary with memory routing metadata or None if not found
1422
+ """
1423
+ try:
1424
+ import json
1425
+
1426
+ # Check if we have a framework path
1427
+ if not self.framework_path or self.framework_path == Path("__PACKAGED__"):
1428
+ # For packaged installations, try to load from package resources
1429
+ if files:
1430
+ try:
1431
+ templates_package = files("claude_mpm.agents.templates")
1432
+ template_file = templates_package / f"{agent_name}.json"
1433
+
1434
+ if template_file.is_file():
1435
+ template_content = template_file.read_text()
1436
+ template_data = json.loads(template_content)
1437
+ return template_data.get("memory_routing")
1438
+ except Exception as e:
1439
+ self.logger.debug(
1440
+ f"Could not load memory routing from packaged template for {agent_name}: {e}"
1441
+ )
1442
+ return None
1443
+
1444
+ # For development mode, load from filesystem
1445
+ templates_dir = (
1446
+ self.framework_path / "src" / "claude_mpm" / "agents" / "templates"
1447
+ )
1448
+ template_file = templates_dir / f"{agent_name}.json"
1449
+
1450
+ if template_file.exists():
1451
+ with open(template_file) as f:
1452
+ template_data = json.load(f)
1453
+ return template_data.get("memory_routing")
1454
+
1455
+ # Also check for variations in naming (underscore vs dash)
1456
+ # Handle common naming variations between deployed .md files and .json templates
1457
+ # Remove duplicates by using a set
1458
+ alternative_names = list(
1459
+ {
1460
+ agent_name.replace("-", "_"), # api-qa -> api_qa
1461
+ agent_name.replace("_", "-"), # api_qa -> api-qa
1462
+ agent_name.replace("-", ""), # api-qa -> apiqa
1463
+ agent_name.replace("_", ""), # api_qa -> apiqa
1464
+ agent_name.replace("-agent", ""), # research-agent -> research
1465
+ agent_name.replace("_agent", ""), # research_agent -> research
1466
+ agent_name + "_agent", # research -> research_agent
1467
+ agent_name + "-agent", # research -> research-agent
1468
+ }
1469
+ )
1470
+
1471
+ for alt_name in alternative_names:
1472
+ if alt_name != agent_name: # Skip the original name we already tried
1473
+ alt_file = templates_dir / f"{alt_name}.json"
1474
+ if alt_file.exists():
1475
+ with open(alt_file) as f:
1476
+ template_data = json.load(f)
1477
+ return template_data.get("memory_routing")
1478
+
1479
+ return None
1480
+
1481
+ except Exception as e:
1482
+ self.logger.debug(
1483
+ f"Could not load memory routing from template for {agent_name}: {e}"
1484
+ )
1485
+ return None
1486
+
1396
1487
  def _load_routing_from_template(self, agent_name: str) -> Optional[Dict[str, Any]]:
1397
1488
  """Load routing metadata from agent JSON template.
1398
1489
 
@@ -179,6 +179,11 @@ class LogManager:
179
179
  # Add to cache
180
180
  self._dir_cache[log_type] = log_dir
181
181
 
182
+ # One-time migration for MPM logs from old location to new subdirectory
183
+ if log_type == "mpm" and not hasattr(self, "_mpm_logs_migrated"):
184
+ await self._migrate_mpm_logs()
185
+ self._mpm_logs_migrated = True
186
+
182
187
  # Schedule cleanup for old logs
183
188
  await self.cleanup_old_logs(
184
189
  log_dir,
@@ -206,7 +211,7 @@ class LogManager:
206
211
  # Map log types to directory names
207
212
  dir_mapping = {
208
213
  "startup": "startup",
209
- "mpm": "", # Root of logs directory
214
+ "mpm": "mpm", # MPM logs in dedicated subdirectory
210
215
  "prompts": "prompts",
211
216
  "sessions": "sessions",
212
217
  "agents": "agents",
@@ -343,6 +348,49 @@ class LogManager:
343
348
 
344
349
  return deleted_count
345
350
 
351
+ async def _migrate_mpm_logs(self):
352
+ """
353
+ One-time migration to move existing MPM logs to new subdirectory.
354
+
355
+ Moves mpm_*.log files from .claude-mpm/logs/ to .claude-mpm/logs/mpm/
356
+ """
357
+ try:
358
+ old_location = self.base_log_dir
359
+ new_location = self.base_log_dir / "mpm"
360
+
361
+ # Only proceed if old location exists and has MPM logs
362
+ if not old_location.exists():
363
+ return
364
+
365
+ # Find all MPM log files in the old location
366
+ mpm_logs = list(old_location.glob("mpm_*.log"))
367
+
368
+ if not mpm_logs:
369
+ return # No logs to migrate
370
+
371
+ # Ensure new directory exists
372
+ new_location.mkdir(parents=True, exist_ok=True)
373
+
374
+ migrated_count = 0
375
+ for log_file in mpm_logs:
376
+ try:
377
+ # Move file to new location
378
+ new_path = new_location / log_file.name
379
+ if not new_path.exists(): # Don't overwrite existing files
380
+ log_file.rename(new_path)
381
+ migrated_count += 1
382
+ except Exception as e:
383
+ logger.debug(f"Could not migrate {log_file}: {e}")
384
+
385
+ if migrated_count > 0:
386
+ logger.info(
387
+ f"Migrated {migrated_count} MPM log files to {new_location}"
388
+ )
389
+
390
+ except Exception as e:
391
+ # Migration is best-effort, don't fail if something goes wrong
392
+ logger.debug(f"MPM log migration skipped: {e}")
393
+
346
394
  async def log_prompt(
347
395
  self, prompt_type: str, content: str, metadata: Optional[Dict[str, Any]] = None
348
396
  ) -> Optional[Path]:
@@ -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")