claude-mpm 4.14.7__py3-none-any.whl → 4.14.9__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 (79) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/agent_loader.py +13 -1
  3. claude_mpm/agents/frontmatter_validator.py +284 -253
  4. claude_mpm/cli/__init__.py +34 -740
  5. claude_mpm/cli/commands/agent_manager.py +25 -12
  6. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  7. claude_mpm/cli/commands/agents.py +204 -148
  8. claude_mpm/cli/commands/aggregate.py +7 -3
  9. claude_mpm/cli/commands/analyze.py +9 -4
  10. claude_mpm/cli/commands/analyze_code.py +7 -2
  11. claude_mpm/cli/commands/config.py +47 -13
  12. claude_mpm/cli/commands/configure.py +159 -1801
  13. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  14. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  15. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  16. claude_mpm/cli/commands/configure_models.py +18 -0
  17. claude_mpm/cli/commands/configure_navigation.py +165 -0
  18. claude_mpm/cli/commands/configure_paths.py +104 -0
  19. claude_mpm/cli/commands/configure_persistence.py +254 -0
  20. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  21. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  22. claude_mpm/cli/commands/configure_validators.py +73 -0
  23. claude_mpm/cli/commands/memory.py +54 -20
  24. claude_mpm/cli/commands/mpm_init.py +35 -21
  25. claude_mpm/cli/executor.py +202 -0
  26. claude_mpm/cli/helpers.py +105 -0
  27. claude_mpm/cli/shared/output_formatters.py +28 -19
  28. claude_mpm/cli/startup.py +455 -0
  29. claude_mpm/core/enums.py +399 -0
  30. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  31. claude_mpm/core/interactive_session.py +6 -3
  32. claude_mpm/core/logging_config.py +6 -2
  33. claude_mpm/core/oneshot_session.py +8 -4
  34. claude_mpm/core/service_registry.py +5 -1
  35. claude_mpm/core/typing_utils.py +7 -6
  36. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  37. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  38. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +3 -2
  39. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  40. claude_mpm/services/diagnostics/checks/installation_check.py +3 -2
  41. claude_mpm/services/diagnostics/checks/mcp_check.py +20 -6
  42. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +8 -7
  43. claude_mpm/services/memory_hook_service.py +4 -1
  44. claude_mpm/services/monitor/daemon_manager.py +3 -2
  45. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  46. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  47. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  48. claude_mpm/services/monitor/server.py +2 -1
  49. claude_mpm/services/session_management_service.py +3 -2
  50. claude_mpm/services/socketio/handlers/hook.py +3 -2
  51. claude_mpm/services/socketio/server/main.py +3 -1
  52. claude_mpm/services/subprocess_launcher_service.py +14 -5
  53. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +6 -5
  54. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  55. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  56. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +5 -4
  57. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  58. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  59. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  60. claude_mpm/services/unified/deployment_strategies/local.py +3 -2
  61. claude_mpm/services/unified/deployment_strategies/utils.py +2 -1
  62. claude_mpm/services/unified/deployment_strategies/vercel.py +2 -1
  63. claude_mpm/services/unified/interfaces.py +3 -1
  64. claude_mpm/services/unified/unified_analyzer.py +7 -6
  65. claude_mpm/services/unified/unified_config.py +2 -1
  66. claude_mpm/services/unified/unified_deployment.py +7 -2
  67. claude_mpm/tools/code_tree_analyzer.py +177 -141
  68. claude_mpm/tools/code_tree_events.py +4 -2
  69. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.9.dist-info}/METADATA +1 -1
  70. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.9.dist-info}/RECORD +74 -64
  71. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  72. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  73. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  74. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  75. claude_mpm/services/project/analyzer_refactored.py +0 -450
  76. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.9.dist-info}/WHEEL +0 -0
  77. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.9.dist-info}/entry_points.txt +0 -0
  78. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.9.dist-info}/licenses/LICENSE +0 -0
  79. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.9.dist-info}/top_level.txt +0 -0
@@ -1528,10 +1528,31 @@ class CodeTreeAnalyzer:
1528
1528
  if not path.exists() or not path.is_file():
1529
1529
  return {"error": f"Invalid file: {file_path}"}
1530
1530
 
1531
- # Get language first (needed for return statement)
1532
1531
  language = self._get_language(path)
1532
+ self._emit_analysis_start(path, language)
1533
1533
 
1534
- # Emit analysis start event
1534
+ # Check cache
1535
+ file_hash = self._get_file_hash(path)
1536
+ cache_key = f"{file_path}:{file_hash}"
1537
+
1538
+ if cache_key in self.cache:
1539
+ nodes = self.cache[cache_key]
1540
+ self._emit_cache_hit(path)
1541
+ filtered_nodes = self._filter_nodes(nodes)
1542
+ else:
1543
+ nodes, filtered_nodes, duration = self._analyze_and_cache_file(
1544
+ path, language, cache_key
1545
+ )
1546
+ self._emit_analysis_complete(path, filtered_nodes, duration)
1547
+
1548
+ # Prepare final data structures
1549
+ final_nodes = self._prepare_final_nodes(nodes, filtered_nodes)
1550
+ elements = self._convert_nodes_to_elements(final_nodes)
1551
+
1552
+ return self._build_result(file_path, language, final_nodes, elements)
1553
+
1554
+ def _emit_analysis_start(self, path: Path, language: str) -> None:
1555
+ """Emit analysis start event."""
1535
1556
  if self.emitter:
1536
1557
  from datetime import datetime
1537
1558
 
@@ -1546,179 +1567,194 @@ class CodeTreeAnalyzer:
1546
1567
  },
1547
1568
  )
1548
1569
 
1549
- # Check cache
1550
- file_hash = self._get_file_hash(path)
1551
- cache_key = f"{file_path}:{file_hash}"
1570
+ def _emit_cache_hit(self, path: Path) -> None:
1571
+ """Emit cache hit event."""
1572
+ if self.emitter:
1573
+ from datetime import datetime
1552
1574
 
1553
- if cache_key in self.cache:
1554
- nodes = self.cache[cache_key]
1555
- if self.emitter:
1556
- from datetime import datetime
1575
+ self.emitter.emit(
1576
+ "info",
1577
+ {
1578
+ "type": "cache.hit",
1579
+ "file": str(path),
1580
+ "message": f"Using cached analysis for {path.name}",
1581
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1582
+ },
1583
+ )
1557
1584
 
1558
- self.emitter.emit(
1559
- "info",
1560
- {
1561
- "type": "cache.hit",
1562
- "file": str(path),
1563
- "message": f"Using cached analysis for {path.name}",
1564
- "timestamp": datetime.now(timezone.utc).isoformat(),
1565
- },
1566
- )
1567
- else:
1568
- # Analyze file
1569
- if self.emitter:
1570
- from datetime import datetime
1585
+ def _emit_cache_miss(self, path: Path) -> None:
1586
+ """Emit cache miss event."""
1587
+ if self.emitter:
1588
+ from datetime import datetime
1571
1589
 
1572
- self.emitter.emit(
1573
- "info",
1574
- {
1575
- "type": "cache.miss",
1576
- "file": str(path),
1577
- "message": f"Cache miss, analyzing fresh: {path.name}",
1578
- "timestamp": datetime.now(timezone.utc).isoformat(),
1579
- },
1580
- )
1590
+ self.emitter.emit(
1591
+ "info",
1592
+ {
1593
+ "type": "cache.miss",
1594
+ "file": str(path),
1595
+ "message": f"Cache miss, analyzing fresh: {path.name}",
1596
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1597
+ },
1598
+ )
1581
1599
 
1582
- if language == "python":
1583
- analyzer = self.python_analyzer
1584
- elif language in {"javascript", "typescript"}:
1585
- analyzer = self.javascript_analyzer
1586
- else:
1587
- analyzer = self.generic_analyzer
1600
+ def _emit_parsing_start(self, path: Path) -> None:
1601
+ """Emit parsing start event."""
1602
+ if self.emitter:
1603
+ from datetime import datetime
1604
+
1605
+ self.emitter.emit(
1606
+ "info",
1607
+ {
1608
+ "type": "analysis.parse",
1609
+ "file": str(path),
1610
+ "message": f"Parsing file content: {path.name}",
1611
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1612
+ },
1613
+ )
1588
1614
 
1589
- start_time = time.time()
1615
+ def _emit_node_found(self, node: CodeNode, path: Path) -> None:
1616
+ """Emit node found event."""
1617
+ if self.emitter:
1618
+ from datetime import datetime
1590
1619
 
1591
- # Emit parsing event
1592
- if self.emitter:
1593
- from datetime import datetime
1620
+ self.emitter.emit(
1621
+ "info",
1622
+ {
1623
+ "type": f"analysis.{node.node_type}",
1624
+ "name": node.name,
1625
+ "file": str(path),
1626
+ "line_start": node.line_start,
1627
+ "complexity": node.complexity,
1628
+ "message": f"Found {node.node_type}: {node.name}",
1629
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1630
+ },
1631
+ )
1594
1632
 
1595
- self.emitter.emit(
1596
- "info",
1597
- {
1598
- "type": "analysis.parse",
1599
- "file": str(path),
1600
- "message": f"Parsing file content: {path.name}",
1601
- "timestamp": datetime.now(timezone.utc).isoformat(),
1602
- },
1603
- )
1633
+ def _emit_analysis_complete(
1634
+ self, path: Path, filtered_nodes: list, duration: float
1635
+ ) -> None:
1636
+ """Emit analysis complete event."""
1637
+ if not self.emitter:
1638
+ return
1604
1639
 
1605
- nodes = analyzer.analyze_file(path) if analyzer else []
1606
- duration = time.time() - start_time
1640
+ from datetime import datetime
1641
+
1642
+ stats = self._calculate_node_stats(filtered_nodes)
1643
+ self.emitter.emit(
1644
+ "info",
1645
+ {
1646
+ "type": "analysis.complete",
1647
+ "file": str(path),
1648
+ "stats": stats,
1649
+ "duration": duration,
1650
+ "message": f"Analysis complete: {stats['classes']} classes, {stats['functions']} functions, {stats['methods']} methods",
1651
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1652
+ },
1653
+ )
1654
+ self.emitter.emit_file_analyzed(str(path), filtered_nodes, duration)
1607
1655
 
1608
- # Cache results
1609
- self.cache[cache_key] = nodes
1656
+ def _analyze_and_cache_file(
1657
+ self, path: Path, language: str, cache_key: str
1658
+ ) -> tuple:
1659
+ """Analyze file content and cache results."""
1660
+ self._emit_cache_miss(path)
1661
+ self._emit_parsing_start(path)
1610
1662
 
1611
- # Filter internal functions before emitting
1612
- filtered_nodes = []
1613
- classes_count = 0
1614
- functions_count = 0
1615
- methods_count = 0
1663
+ # Select analyzer based on language
1664
+ analyzer = self._select_analyzer(language)
1616
1665
 
1617
- for node in nodes:
1618
- # Only include main structural elements
1619
- if not self._is_internal_node(node):
1620
- # Emit found element event
1621
- if self.emitter:
1622
- from datetime import datetime
1666
+ # Perform analysis
1667
+ start_time = time.time()
1668
+ nodes = analyzer.analyze_file(path) if analyzer else []
1669
+ duration = time.time() - start_time
1623
1670
 
1624
- self.emitter.emit(
1625
- "info",
1626
- {
1627
- "type": f"analysis.{node.node_type}",
1628
- "name": node.name,
1629
- "file": str(path),
1630
- "line_start": node.line_start,
1631
- "complexity": node.complexity,
1632
- "message": f"Found {node.node_type}: {node.name}",
1633
- "timestamp": datetime.now(timezone.utc).isoformat(),
1634
- },
1635
- )
1671
+ # Cache results
1672
+ self.cache[cache_key] = nodes
1636
1673
 
1637
- # Count node types
1638
- if node.node_type == "class":
1639
- classes_count += 1
1640
- elif node.node_type == "function":
1641
- functions_count += 1
1642
- elif node.node_type == "method":
1643
- methods_count += 1
1644
-
1645
- filtered_nodes.append(
1646
- {
1647
- "name": node.name,
1648
- "type": node.node_type,
1649
- "line_start": node.line_start,
1650
- "line_end": node.line_end,
1651
- "complexity": node.complexity,
1652
- "has_docstring": node.has_docstring,
1653
- "signature": node.signature,
1654
- }
1655
- )
1674
+ # Filter and process nodes
1675
+ filtered_nodes = self._filter_and_emit_nodes(nodes, path)
1656
1676
 
1657
- # Emit analysis complete event with stats
1658
- if self.emitter:
1659
- from datetime import datetime
1677
+ return nodes, filtered_nodes, duration
1660
1678
 
1661
- self.emitter.emit(
1662
- "info",
1663
- {
1664
- "type": "analysis.complete",
1665
- "file": str(path),
1666
- "stats": {
1667
- "classes": classes_count,
1668
- "functions": functions_count,
1669
- "methods": methods_count,
1670
- "total_nodes": len(filtered_nodes),
1671
- },
1672
- "duration": duration,
1673
- "message": f"Analysis complete: {classes_count} classes, {functions_count} functions, {methods_count} methods",
1674
- "timestamp": datetime.now(timezone.utc).isoformat(),
1675
- },
1676
- )
1679
+ def _select_analyzer(self, language: str):
1680
+ """Select appropriate analyzer for language."""
1681
+ if language == "python":
1682
+ return self.python_analyzer
1683
+ if language in {"javascript", "typescript"}:
1684
+ return self.javascript_analyzer
1685
+ return self.generic_analyzer
1677
1686
 
1678
- self.emitter.emit_file_analyzed(file_path, filtered_nodes, duration)
1687
+ def _filter_nodes(self, nodes: list) -> list:
1688
+ """Filter nodes without emitting events."""
1689
+ return [self._node_to_dict(n) for n in nodes if not self._is_internal_node(n)]
1679
1690
 
1680
- # Prepare the nodes data
1681
- final_nodes = (
1682
- filtered_nodes
1683
- if "filtered_nodes" in locals()
1684
- else [
1685
- {
1686
- "name": n.name,
1687
- "type": n.node_type,
1688
- "line_start": n.line_start,
1689
- "line_end": n.line_end,
1690
- "complexity": n.complexity,
1691
- "has_docstring": n.has_docstring,
1692
- "signature": n.signature,
1693
- }
1694
- for n in nodes
1695
- if not self._is_internal_node(n)
1696
- ]
1697
- )
1691
+ def _filter_and_emit_nodes(self, nodes: list, path: Path) -> list:
1692
+ """Filter nodes and emit events for each."""
1693
+ filtered_nodes = []
1694
+ for node in nodes:
1695
+ if not self._is_internal_node(node):
1696
+ self._emit_node_found(node, path)
1697
+ filtered_nodes.append(self._node_to_dict(node))
1698
+ return filtered_nodes
1699
+
1700
+ def _node_to_dict(self, node: CodeNode) -> dict:
1701
+ """Convert CodeNode to dictionary."""
1702
+ return {
1703
+ "name": node.name,
1704
+ "type": node.node_type,
1705
+ "line_start": node.line_start,
1706
+ "line_end": node.line_end,
1707
+ "complexity": node.complexity,
1708
+ "has_docstring": node.has_docstring,
1709
+ "signature": node.signature,
1710
+ }
1711
+
1712
+ def _calculate_node_stats(self, filtered_nodes: list) -> dict:
1713
+ """Calculate statistics from filtered nodes."""
1714
+ classes_count = sum(1 for n in filtered_nodes if n["type"] == "class")
1715
+ functions_count = sum(1 for n in filtered_nodes if n["type"] == "function")
1716
+ methods_count = sum(1 for n in filtered_nodes if n["type"] == "method")
1717
+ return {
1718
+ "classes": classes_count,
1719
+ "functions": functions_count,
1720
+ "methods": methods_count,
1721
+ "total_nodes": len(filtered_nodes),
1722
+ }
1723
+
1724
+ def _prepare_final_nodes(self, nodes: list, filtered_nodes: list) -> list:
1725
+ """Prepare final nodes data structure."""
1726
+ if filtered_nodes:
1727
+ return filtered_nodes
1728
+ return [self._node_to_dict(n) for n in nodes if not self._is_internal_node(n)]
1698
1729
 
1699
- # Convert nodes to elements format for dashboard compatibility
1730
+ def _convert_nodes_to_elements(self, final_nodes: list) -> list:
1731
+ """Convert nodes to elements format for dashboard."""
1700
1732
  elements = []
1701
1733
  for node in final_nodes:
1702
1734
  element = {
1703
1735
  "name": node["name"],
1704
1736
  "type": node["type"],
1705
- "line": node["line_start"], # Dashboard expects 'line' not 'line_start'
1737
+ "line": node["line_start"],
1706
1738
  "complexity": node["complexity"],
1707
1739
  "signature": node.get("signature", ""),
1708
1740
  "has_docstring": node.get("has_docstring", False),
1709
1741
  }
1710
- # Add methods if it's a class (for expandable tree)
1711
1742
  if node["type"] == "class":
1712
- element["methods"] = [] # Could be populated with class methods
1743
+ element["methods"] = []
1713
1744
  elements.append(element)
1745
+ return elements
1714
1746
 
1747
+ def _build_result(
1748
+ self, file_path: str, language: str, final_nodes: list, elements: list
1749
+ ) -> dict:
1750
+ """Build final result dictionary."""
1715
1751
  return {
1716
1752
  "path": file_path,
1717
1753
  "language": language,
1718
- "nodes": final_nodes, # Keep for backward compatibility
1719
- "elements": elements, # Add for dashboard compatibility
1754
+ "nodes": final_nodes,
1755
+ "elements": elements,
1720
1756
  "complexity": sum(e["complexity"] for e in elements),
1721
- "lines": len(elements), # Simple line count approximation
1757
+ "lines": len(elements),
1722
1758
  "stats": {
1723
1759
  "classes": len([e for e in elements if e["type"] == "class"]),
1724
1760
  "functions": len([e for e in elements if e["type"] == "function"]),
@@ -22,6 +22,8 @@ from dataclasses import asdict, dataclass
22
22
  from datetime import datetime, timezone
23
23
  from typing import Any, Dict, List, Optional
24
24
 
25
+ from ..core.enums import OperationResult
26
+
25
27
  try:
26
28
  import socketio
27
29
 
@@ -268,7 +270,7 @@ class CodeTreeEventEmitter:
268
270
  "name": Path(file_path).name,
269
271
  "language": language or "unknown",
270
272
  "type": "file",
271
- "status": "analyzing",
273
+ "status": OperationResult.PENDING,
272
274
  },
273
275
  )
274
276
 
@@ -287,7 +289,7 @@ class CodeTreeEventEmitter:
287
289
  "nodes_count": nodes_count,
288
290
  "duration": duration,
289
291
  "type": "file",
290
- "status": "complete",
292
+ "status": OperationResult.COMPLETED,
291
293
  },
292
294
  )
293
295
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.14.7
3
+ Version: 4.14.9
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team