claude-mpm 4.14.7__py3-none-any.whl → 4.14.8__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 (78) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/frontmatter_validator.py +284 -253
  3. claude_mpm/cli/__init__.py +34 -740
  4. claude_mpm/cli/commands/agent_manager.py +25 -12
  5. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  6. claude_mpm/cli/commands/agents.py +204 -148
  7. claude_mpm/cli/commands/aggregate.py +7 -3
  8. claude_mpm/cli/commands/analyze.py +9 -4
  9. claude_mpm/cli/commands/analyze_code.py +7 -2
  10. claude_mpm/cli/commands/config.py +47 -13
  11. claude_mpm/cli/commands/configure.py +159 -1801
  12. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  13. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  14. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  15. claude_mpm/cli/commands/configure_models.py +18 -0
  16. claude_mpm/cli/commands/configure_navigation.py +165 -0
  17. claude_mpm/cli/commands/configure_paths.py +104 -0
  18. claude_mpm/cli/commands/configure_persistence.py +254 -0
  19. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  20. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  21. claude_mpm/cli/commands/configure_validators.py +73 -0
  22. claude_mpm/cli/commands/memory.py +54 -20
  23. claude_mpm/cli/commands/mpm_init.py +35 -21
  24. claude_mpm/cli/executor.py +202 -0
  25. claude_mpm/cli/helpers.py +105 -0
  26. claude_mpm/cli/shared/output_formatters.py +28 -19
  27. claude_mpm/cli/startup.py +455 -0
  28. claude_mpm/core/enums.py +322 -0
  29. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  30. claude_mpm/core/interactive_session.py +6 -3
  31. claude_mpm/core/logging_config.py +6 -2
  32. claude_mpm/core/oneshot_session.py +8 -4
  33. claude_mpm/core/service_registry.py +5 -1
  34. claude_mpm/core/typing_utils.py +7 -6
  35. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  36. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  37. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +3 -2
  38. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  39. claude_mpm/services/diagnostics/checks/installation_check.py +3 -2
  40. claude_mpm/services/diagnostics/checks/mcp_check.py +20 -6
  41. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +8 -7
  42. claude_mpm/services/memory_hook_service.py +4 -1
  43. claude_mpm/services/monitor/daemon_manager.py +3 -2
  44. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  45. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  46. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  47. claude_mpm/services/monitor/server.py +2 -1
  48. claude_mpm/services/session_management_service.py +3 -2
  49. claude_mpm/services/socketio/handlers/hook.py +3 -2
  50. claude_mpm/services/socketio/server/main.py +3 -1
  51. claude_mpm/services/subprocess_launcher_service.py +14 -5
  52. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +6 -5
  53. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +5 -4
  54. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +5 -4
  55. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +4 -3
  56. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +4 -3
  57. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  58. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  59. claude_mpm/services/unified/deployment_strategies/local.py +3 -2
  60. claude_mpm/services/unified/deployment_strategies/utils.py +2 -1
  61. claude_mpm/services/unified/deployment_strategies/vercel.py +2 -1
  62. claude_mpm/services/unified/interfaces.py +3 -1
  63. claude_mpm/services/unified/unified_analyzer.py +7 -6
  64. claude_mpm/services/unified/unified_config.py +2 -1
  65. claude_mpm/services/unified/unified_deployment.py +7 -2
  66. claude_mpm/tools/code_tree_analyzer.py +177 -141
  67. claude_mpm/tools/code_tree_events.py +4 -2
  68. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/METADATA +1 -1
  69. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/RECORD +73 -63
  70. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  71. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  72. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  74. claude_mpm/services/project/analyzer_refactored.py +0 -450
  75. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/WHEEL +0 -0
  76. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/entry_points.txt +0 -0
  77. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/licenses/LICENSE +0 -0
  78. {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from datetime import datetime, timezone
12
12
  from pathlib import Path
13
13
  from typing import Any, Dict, List
14
14
 
15
+ from claude_mpm.core.enums import OperationResult, ServiceState
15
16
  from claude_mpm.core.logging_utils import get_logger
16
17
  from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
17
18
 
@@ -411,11 +412,13 @@ class DockerDeploymentStrategy(DeploymentStrategy):
411
412
  def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
412
413
  """Get Docker container health."""
413
414
  container_id = deployment_info.get("container_id")
414
- health = {"status": "unknown", "container_id": container_id}
415
+ health = {"status": OperationResult.UNKNOWN, "container_id": container_id}
415
416
 
416
417
  if container_id:
417
418
  health["running"] = check_docker_container(container_id)
418
- health["status"] = "healthy" if health["running"] else "unhealthy"
419
+ health["status"] = (
420
+ ServiceState.RUNNING if health["running"] else ServiceState.ERROR
421
+ )
419
422
 
420
423
  return health
421
424
 
@@ -560,7 +563,11 @@ class GitDeploymentStrategy(DeploymentStrategy):
560
563
  def get_health_status(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
561
564
  """Get Git deployment health."""
562
565
  return {
563
- "status": "healthy" if deployment_info.get("commit_hash") else "unhealthy",
566
+ "status": (
567
+ ServiceState.RUNNING
568
+ if deployment_info.get("commit_hash")
569
+ else ServiceState.ERROR
570
+ ),
564
571
  "commit": deployment_info.get("commit_hash", "unknown"),
565
572
  "branch": deployment_info.get("branch", "unknown"),
566
573
  }
@@ -19,6 +19,7 @@ from typing import Any, Dict, List, Optional
19
19
 
20
20
  import yaml
21
21
 
22
+ from claude_mpm.core.enums import OperationResult
22
23
  from claude_mpm.core.logging_utils import get_logger
23
24
  from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
24
25
 
@@ -272,14 +273,14 @@ class LocalDeploymentStrategy(DeploymentStrategy):
272
273
  deployed_path = Path(deployment_info.get("deployed_path", ""))
273
274
 
274
275
  health = {
275
- "status": "unknown",
276
+ "status": OperationResult.UNKNOWN,
276
277
  "deployed_path": str(deployed_path),
277
278
  "exists": deployed_path.exists() if deployed_path else False,
278
279
  "checks": {},
279
280
  }
280
281
 
281
282
  if deployed_path and deployed_path.exists():
282
- health["status"] = "healthy"
283
+ health["status"] = OperationResult.SUCCESS
283
284
 
284
285
  # Check file integrity
285
286
  for file_path in deployment_info.get("deployed_files", []):
@@ -23,6 +23,7 @@ from datetime import datetime, timezone
23
23
  from pathlib import Path
24
24
  from typing import Any, Dict, List, Optional, Tuple, Union
25
25
 
26
+ from claude_mpm.core.enums import OperationResult
26
27
  from claude_mpm.core.logging_utils import get_logger
27
28
 
28
29
  logger = get_logger(__name__)
@@ -256,7 +257,7 @@ def verify_deployment_health(
256
257
  Health status dictionary
257
258
  """
258
259
  health = {
259
- "status": "unknown",
260
+ "status": OperationResult.UNKNOWN,
260
261
  "timestamp": datetime.now(timezone.utc).isoformat(),
261
262
  "checks": {},
262
263
  "errors": [],
@@ -13,6 +13,7 @@ from datetime import datetime, timezone
13
13
  from pathlib import Path
14
14
  from typing import Any, Dict, List, Optional
15
15
 
16
+ from claude_mpm.core.enums import OperationResult
16
17
  from claude_mpm.core.logging_utils import get_logger
17
18
  from claude_mpm.services.unified.strategies import StrategyMetadata, StrategyPriority
18
19
 
@@ -307,7 +308,7 @@ class VercelDeploymentStrategy(DeploymentStrategy):
307
308
  deployment_url = deployment_info.get("deployment_url")
308
309
 
309
310
  health = {
310
- "status": "unknown",
311
+ "status": OperationResult.UNKNOWN,
311
312
  "deployment_url": deployment_url,
312
313
  "checks": {},
313
314
  }
@@ -25,6 +25,8 @@ from enum import Enum
25
25
  from pathlib import Path
26
26
  from typing import Any, Dict, List, Optional, Set, TypeVar, Union
27
27
 
28
+ from claude_mpm.core.enums import ValidationSeverity
29
+
28
30
  # Type variables for generic interfaces
29
31
  T = TypeVar("T")
30
32
  ConfigType = TypeVar("ConfigType", bound=Dict[str, Any])
@@ -88,7 +90,7 @@ class AnalysisResult:
88
90
  findings: List[Dict[str, Any]] = field(default_factory=list)
89
91
  metrics: Dict[str, Any] = field(default_factory=dict)
90
92
  summary: str = ""
91
- severity: str = "info" # info, warning, error, critical
93
+ severity: str = ValidationSeverity.INFO
92
94
  recommendations: List[str] = field(default_factory=list)
93
95
 
94
96
 
@@ -25,6 +25,7 @@ Features:
25
25
  from pathlib import Path
26
26
  from typing import Any, Dict, List, Optional, Union
27
27
 
28
+ from claude_mpm.core.enums import ServiceState, ValidationSeverity
28
29
  from claude_mpm.core.logging_utils import get_logger
29
30
 
30
31
  from .interfaces import (
@@ -134,7 +135,7 @@ class UnifiedAnalyzer(IAnalyzerService, IUnifiedService):
134
135
 
135
136
  return {
136
137
  "service": "UnifiedAnalyzer",
137
- "status": "healthy" if self._initialized else "unhealthy",
138
+ "status": ServiceState.RUNNING if self._initialized else ServiceState.ERROR,
138
139
  "initialized": self._initialized,
139
140
  "registered_strategies": len(strategies),
140
141
  "cache_size": len(self._analysis_cache),
@@ -223,7 +224,7 @@ class UnifiedAnalyzer(IAnalyzerService, IUnifiedService):
223
224
  return AnalysisResult(
224
225
  success=False,
225
226
  summary=f"No strategy available for analysis type: {analysis_type}",
226
- severity="error",
227
+ severity=ValidationSeverity.ERROR,
227
228
  )
228
229
 
229
230
  # Execute analysis using strategy
@@ -236,7 +237,7 @@ class UnifiedAnalyzer(IAnalyzerService, IUnifiedService):
236
237
  return AnalysisResult(
237
238
  success=False,
238
239
  summary=f"Validation failed: {'; '.join(validation_errors)}",
239
- severity="error",
240
+ severity=ValidationSeverity.ERROR,
240
241
  )
241
242
 
242
243
  # Perform analysis
@@ -251,7 +252,7 @@ class UnifiedAnalyzer(IAnalyzerService, IUnifiedService):
251
252
  findings=result_data.get("findings", []),
252
253
  metrics=metrics,
253
254
  summary=result_data.get("summary", "Analysis completed"),
254
- severity=result_data.get("severity", "info"),
255
+ severity=result_data.get("severity", ValidationSeverity.INFO),
255
256
  recommendations=result_data.get("recommendations", []),
256
257
  )
257
258
 
@@ -405,7 +406,7 @@ class UnifiedAnalyzer(IAnalyzerService, IUnifiedService):
405
406
  )
406
407
 
407
408
  # Add severity-based recommendations
408
- if analysis_result.severity == "critical":
409
+ if analysis_result.severity == ValidationSeverity.CRITICAL:
409
410
  recommendations.insert(
410
411
  0,
411
412
  {
@@ -414,7 +415,7 @@ class UnifiedAnalyzer(IAnalyzerService, IUnifiedService):
414
415
  "priority": "high",
415
416
  },
416
417
  )
417
- elif analysis_result.severity == "error":
418
+ elif analysis_result.severity == ValidationSeverity.ERROR:
418
419
  recommendations.insert(
419
420
  0,
420
421
  {
@@ -26,6 +26,7 @@ import json
26
26
  from pathlib import Path
27
27
  from typing import Any, Dict, List, Optional, Union
28
28
 
29
+ from claude_mpm.core.enums import ServiceState
29
30
  from claude_mpm.core.logging_utils import get_logger
30
31
 
31
32
  from .interfaces import (
@@ -153,7 +154,7 @@ class UnifiedConfigManager(IConfigurationService, IUnifiedService):
153
154
 
154
155
  return {
155
156
  "service": "UnifiedConfigManager",
156
- "status": "healthy" if self._initialized else "unhealthy",
157
+ "status": ServiceState.RUNNING if self._initialized else ServiceState.ERROR,
157
158
  "initialized": self._initialized,
158
159
  "registered_strategies": len(strategies),
159
160
  "loaded_configs": len(self._configs),
@@ -24,6 +24,7 @@ Features:
24
24
  from pathlib import Path
25
25
  from typing import Any, Dict, List, Optional, Union
26
26
 
27
+ from claude_mpm.core.enums import OperationResult, ServiceState
27
28
  from claude_mpm.core.logging_utils import get_logger
28
29
 
29
30
  from .interfaces import (
@@ -140,7 +141,7 @@ class UnifiedDeploymentService(IDeploymentService, IUnifiedService):
140
141
 
141
142
  return {
142
143
  "service": "UnifiedDeploymentService",
143
- "status": "healthy" if self._initialized else "unhealthy",
144
+ "status": ServiceState.RUNNING if self._initialized else ServiceState.ERROR,
144
145
  "initialized": self._initialized,
145
146
  "registered_strategies": len(strategies),
146
147
  "active_deployments": len(self._deployments),
@@ -426,7 +427,11 @@ class UnifiedDeploymentService(IDeploymentService, IUnifiedService):
426
427
 
427
428
  return {
428
429
  "id": deployment_id,
429
- "status": "rolled_back" if deployment.get("rolled_back") else "active",
430
+ "status": (
431
+ OperationResult.CANCELLED
432
+ if deployment.get("rolled_back")
433
+ else OperationResult.SUCCESS
434
+ ),
430
435
  "type": deployment["type"],
431
436
  "strategy": deployment["strategy"],
432
437
  "source": deployment["source"],
@@ -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.8
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