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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/cli/__init__.py +34 -740
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +204 -148
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +159 -1801
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +165 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +35 -21
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +455 -0
- claude_mpm/core/enums.py +322 -0
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +6 -3
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +3 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/diagnostics/checks/installation_check.py +3 -2
- claude_mpm/services/diagnostics/checks/mcp_check.py +20 -6
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +8 -7
- claude_mpm/services/memory_hook_service.py +4 -1
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/socketio/handlers/hook.py +3 -2
- claude_mpm/services/socketio/server/main.py +3 -1
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +5 -4
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +5 -4
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +4 -3
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +4 -3
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +3 -2
- claude_mpm/services/unified/deployment_strategies/utils.py +2 -1
- claude_mpm/services/unified/deployment_strategies/vercel.py +2 -1
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +7 -6
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +7 -2
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/METADATA +1 -1
- {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/RECORD +73 -63
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/WHEEL +0 -0
- {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.14.7.dist-info → claude_mpm-4.14.8.dist-info}/licenses/LICENSE +0 -0
- {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":
|
|
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"] =
|
|
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":
|
|
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":
|
|
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"] =
|
|
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":
|
|
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":
|
|
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 =
|
|
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":
|
|
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=
|
|
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=
|
|
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",
|
|
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 ==
|
|
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 ==
|
|
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":
|
|
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":
|
|
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":
|
|
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
|
-
#
|
|
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
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
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
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
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
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
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
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
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
|
-
|
|
1606
|
-
|
|
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
|
-
|
|
1609
|
-
|
|
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
|
-
|
|
1612
|
-
|
|
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
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
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
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
1638
|
-
|
|
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
|
-
|
|
1658
|
-
if self.emitter:
|
|
1659
|
-
from datetime import datetime
|
|
1677
|
+
return nodes, filtered_nodes, duration
|
|
1660
1678
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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
|
-
|
|
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"],
|
|
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"] = []
|
|
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,
|
|
1719
|
-
"elements": elements,
|
|
1754
|
+
"nodes": final_nodes,
|
|
1755
|
+
"elements": elements,
|
|
1720
1756
|
"complexity": sum(e["complexity"] for e in elements),
|
|
1721
|
-
"lines": len(elements),
|
|
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":
|
|
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":
|
|
292
|
+
"status": OperationResult.COMPLETED,
|
|
291
293
|
},
|
|
292
294
|
)
|
|
293
295
|
|