claude-mpm 4.7.4__py3-none-any.whl → 4.18.2__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +106 -1
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +4 -1
- claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
- claude_mpm/agents/templates/api_qa.json +11 -2
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +12 -2
- claude_mpm/agents/templates/code_analyzer.json +8 -2
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +15 -2
- claude_mpm/agents/templates/data_engineer.json +15 -2
- claude_mpm/agents/templates/documentation.json +10 -2
- claude_mpm/agents/templates/engineer.json +21 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +4 -1
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/local_ops_agent.json +1227 -6
- claude_mpm/agents/templates/memory_manager.json +4 -1
- claude_mpm/agents/templates/nextjs_engineer.json +141 -133
- claude_mpm/agents/templates/ops.json +12 -2
- claude_mpm/agents/templates/php-engineer.json +270 -174
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +240 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +14 -4
- claude_mpm/agents/templates/prompt-engineer.json +13 -2
- claude_mpm/agents/templates/python_engineer.json +174 -81
- claude_mpm/agents/templates/qa.json +11 -2
- claude_mpm/agents/templates/react_engineer.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +12 -2
- claude_mpm/agents/templates/research.json +34 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +129 -192
- claude_mpm/agents/templates/rust_engineer.json +270 -0
- claude_mpm/agents/templates/security.json +10 -2
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +10 -2
- claude_mpm/agents/templates/typescript_engineer.json +116 -125
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
- claude_mpm/agents/templates/version_control.json +12 -2
- claude_mpm/agents/templates/web_qa.json +11 -2
- claude_mpm/agents/templates/web_ui.json +15 -2
- claude_mpm/cli/__init__.py +34 -614
- 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 +235 -148
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- 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/auto_configure.py +570 -0
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +419 -1571
- 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 +167 -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/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +585 -196
- claude_mpm/cli/commands/mpm_init_handler.py +37 -3
- claude_mpm/cli/commands/search.py +170 -4
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agents_parser.py +9 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +110 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +75 -1
- claude_mpm/commands/mpm-auto-configure.md +217 -0
- claude_mpm/commands/mpm-help.md +163 -0
- claude_mpm/commands/mpm-init.md +148 -3
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/log_manager.py +2 -0
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +20 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +37 -12
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +568 -0
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/memory_manager.py +11 -24
- claude_mpm/services/core/models/__init__.py +79 -0
- claude_mpm/services/core/models/agent_config.py +381 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/main.py +30 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +165 -4
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- 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/project/__init__.py +23 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/self_upgrade_service.py +342 -0
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- 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 +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +40 -20
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/index-hub-backup.html +0 -713
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- 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.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Toolchain Detection Strategies for Claude MPM Framework
|
|
3
|
+
=======================================================
|
|
4
|
+
|
|
5
|
+
WHY: This module implements pluggable detection strategies for different
|
|
6
|
+
programming languages and frameworks. Using the Strategy pattern allows
|
|
7
|
+
easy addition of new language detectors without modifying existing code.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISION: Each strategy is independent and responsible for detecting
|
|
10
|
+
a specific language/ecosystem. Strategies calculate confidence scores based
|
|
11
|
+
on multiple indicators, providing transparency in detection results.
|
|
12
|
+
|
|
13
|
+
Part of TSK-0054: Auto-Configuration Feature - Phase 2
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, List, Optional, Set
|
|
23
|
+
|
|
24
|
+
from ..core.models.toolchain import (
|
|
25
|
+
ConfidenceLevel,
|
|
26
|
+
Framework,
|
|
27
|
+
LanguageDetection,
|
|
28
|
+
ToolchainComponent,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DetectionEvidence:
|
|
34
|
+
"""Evidence gathered during detection process.
|
|
35
|
+
|
|
36
|
+
WHY: Transparency in detection helps users understand why certain
|
|
37
|
+
technologies were detected and builds trust in recommendations.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
indicators_found: List[str] = field(default_factory=list)
|
|
41
|
+
confidence_contributors: Dict[str, float] = field(default_factory=dict)
|
|
42
|
+
version_sources: List[str] = field(default_factory=list)
|
|
43
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
def add_indicator(self, indicator: str, confidence_boost: float = 0.15) -> None:
|
|
46
|
+
"""Add an indicator and its confidence contribution."""
|
|
47
|
+
self.indicators_found.append(indicator)
|
|
48
|
+
self.confidence_contributors[indicator] = confidence_boost
|
|
49
|
+
|
|
50
|
+
def total_confidence(self) -> float:
|
|
51
|
+
"""Calculate total confidence score."""
|
|
52
|
+
base = 0.5
|
|
53
|
+
total = base + sum(self.confidence_contributors.values())
|
|
54
|
+
return min(total, 0.95) # Cap at 0.95
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class IToolchainDetectionStrategy(ABC):
|
|
58
|
+
"""Base interface for toolchain detection strategies.
|
|
59
|
+
|
|
60
|
+
WHY: Defines contract for all detection strategies, ensuring consistency
|
|
61
|
+
and enabling polymorphic usage in the analyzer service.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def can_detect(self, project_path: Path) -> bool:
|
|
66
|
+
"""Check if this strategy can detect anything in the project.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
project_path: Path to project root
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if strategy found relevant indicators
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def detect_language(self, project_path: Path) -> Optional[LanguageDetection]:
|
|
77
|
+
"""Detect language with confidence and evidence.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
project_path: Path to project root
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
LanguageDetection if detected, None otherwise
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def detect_frameworks(self, project_path: Path) -> List[Framework]:
|
|
88
|
+
"""Detect frameworks used in the project.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
project_path: Path to project root
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
List of detected frameworks
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def get_language_name(self) -> str:
|
|
99
|
+
"""Get the language this strategy detects."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class BaseDetectionStrategy(IToolchainDetectionStrategy):
|
|
103
|
+
"""Base implementation with common detection utilities.
|
|
104
|
+
|
|
105
|
+
WHY: Provides shared utilities for file checking, version extraction,
|
|
106
|
+
and confidence calculation to reduce code duplication.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self):
|
|
110
|
+
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
|
111
|
+
|
|
112
|
+
def _file_exists(self, project_path: Path, *relative_paths: str) -> bool:
|
|
113
|
+
"""Check if file exists in project."""
|
|
114
|
+
return any((project_path / rel_path).exists() for rel_path in relative_paths)
|
|
115
|
+
|
|
116
|
+
def _read_file(self, file_path: Path) -> Optional[str]:
|
|
117
|
+
"""Safely read file contents."""
|
|
118
|
+
try:
|
|
119
|
+
return file_path.read_text(encoding="utf-8", errors="ignore")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
self.logger.debug(f"Could not read {file_path}: {e}")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
def _extract_version_from_file(
|
|
125
|
+
self, file_path: Path, patterns: List[str]
|
|
126
|
+
) -> Optional[str]:
|
|
127
|
+
"""Extract version using regex patterns."""
|
|
128
|
+
content = self._read_file(file_path)
|
|
129
|
+
if not content:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
for pattern in patterns:
|
|
133
|
+
match = re.search(pattern, content)
|
|
134
|
+
if match:
|
|
135
|
+
return match.group(1)
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def _calculate_confidence_level(self, score: float) -> ConfidenceLevel:
|
|
139
|
+
"""Convert numeric score to confidence level."""
|
|
140
|
+
if score >= 0.80:
|
|
141
|
+
return ConfidenceLevel.HIGH
|
|
142
|
+
if score >= 0.50:
|
|
143
|
+
return ConfidenceLevel.MEDIUM
|
|
144
|
+
if score >= 0.20:
|
|
145
|
+
return ConfidenceLevel.LOW
|
|
146
|
+
return ConfidenceLevel.VERY_LOW
|
|
147
|
+
|
|
148
|
+
def _count_source_files(
|
|
149
|
+
self, project_path: Path, extensions: Set[str], max_depth: int = 5
|
|
150
|
+
) -> int:
|
|
151
|
+
"""Count source files with given extensions."""
|
|
152
|
+
count = 0
|
|
153
|
+
try:
|
|
154
|
+
for ext in extensions:
|
|
155
|
+
# Limit search depth to avoid performance issues
|
|
156
|
+
pattern = f"**/*{ext}"
|
|
157
|
+
files = list(project_path.glob(pattern))
|
|
158
|
+
# Filter out common excluded directories
|
|
159
|
+
files = [
|
|
160
|
+
f
|
|
161
|
+
for f in files
|
|
162
|
+
if not any(
|
|
163
|
+
part.startswith(".")
|
|
164
|
+
or part in {"node_modules", "venv", "target", "build", "dist"}
|
|
165
|
+
for part in f.parts
|
|
166
|
+
)
|
|
167
|
+
]
|
|
168
|
+
count += len(files[:1000]) # Cap at 1000 per extension
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.logger.debug(f"Error counting files: {e}")
|
|
171
|
+
return count
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class NodeJSDetectionStrategy(BaseDetectionStrategy):
|
|
175
|
+
"""Detection strategy for Node.js projects.
|
|
176
|
+
|
|
177
|
+
WHY: Node.js projects have distinct markers (package.json, node_modules)
|
|
178
|
+
and well-defined dependency management that enables high-confidence detection.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
MARKER_FILES = ["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"]
|
|
182
|
+
VERSION_PATTERNS = [
|
|
183
|
+
r'"node":\s*"([^"]+)"', # engines.node in package.json
|
|
184
|
+
r'"version":\s*"([^"]+)"', # package version
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
FRAMEWORK_INDICATORS = {
|
|
188
|
+
"next": {
|
|
189
|
+
"type": "web",
|
|
190
|
+
"patterns": ["next"],
|
|
191
|
+
"config_files": ["next.config.js", "next.config.ts"],
|
|
192
|
+
},
|
|
193
|
+
"react": {"type": "web", "patterns": ["react", "react-dom"]},
|
|
194
|
+
"vue": {"type": "web", "patterns": ["vue"]},
|
|
195
|
+
"angular": {"type": "web", "patterns": ["@angular/core"]},
|
|
196
|
+
"express": {"type": "web", "patterns": ["express"]},
|
|
197
|
+
"nestjs": {"type": "web", "patterns": ["@nestjs/core"]},
|
|
198
|
+
"nuxt": {"type": "web", "patterns": ["nuxt"]},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
def get_language_name(self) -> str:
|
|
202
|
+
return "Node.js"
|
|
203
|
+
|
|
204
|
+
def can_detect(self, project_path: Path) -> bool:
|
|
205
|
+
"""Check for Node.js indicators."""
|
|
206
|
+
return self._file_exists(project_path, *self.MARKER_FILES)
|
|
207
|
+
|
|
208
|
+
def detect_language(self, project_path: Path) -> Optional[LanguageDetection]:
|
|
209
|
+
"""Detect Node.js with confidence scoring."""
|
|
210
|
+
if not self.can_detect(project_path):
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
evidence = DetectionEvidence()
|
|
214
|
+
|
|
215
|
+
# Check for package.json (strongest indicator)
|
|
216
|
+
package_json_path = project_path / "package.json"
|
|
217
|
+
if package_json_path.exists():
|
|
218
|
+
evidence.add_indicator("package.json found", 0.20)
|
|
219
|
+
|
|
220
|
+
# Extract version information
|
|
221
|
+
version = self._extract_version_from_file(
|
|
222
|
+
package_json_path, self.VERSION_PATTERNS
|
|
223
|
+
)
|
|
224
|
+
if version:
|
|
225
|
+
evidence.version_sources.append("package.json engines.node")
|
|
226
|
+
|
|
227
|
+
# Check for lock files
|
|
228
|
+
if (project_path / "package-lock.json").exists():
|
|
229
|
+
evidence.add_indicator("package-lock.json found", 0.10)
|
|
230
|
+
if (project_path / "yarn.lock").exists():
|
|
231
|
+
evidence.add_indicator("yarn.lock found", 0.10)
|
|
232
|
+
if (project_path / "pnpm-lock.yaml").exists():
|
|
233
|
+
evidence.add_indicator("pnpm-lock.yaml found", 0.10)
|
|
234
|
+
|
|
235
|
+
# Check for node_modules
|
|
236
|
+
if (project_path / "node_modules").exists():
|
|
237
|
+
evidence.add_indicator("node_modules directory found", 0.05)
|
|
238
|
+
|
|
239
|
+
# Count JavaScript/TypeScript files
|
|
240
|
+
js_count = self._count_source_files(
|
|
241
|
+
project_path, {".js", ".jsx", ".ts", ".tsx"}
|
|
242
|
+
)
|
|
243
|
+
if js_count > 0:
|
|
244
|
+
evidence.add_indicator(f"{js_count} JavaScript/TypeScript files", 0.10)
|
|
245
|
+
|
|
246
|
+
# Determine confidence
|
|
247
|
+
confidence_score = evidence.total_confidence()
|
|
248
|
+
confidence = self._calculate_confidence_level(confidence_score)
|
|
249
|
+
|
|
250
|
+
# Detect secondary languages (TypeScript)
|
|
251
|
+
secondary_languages = []
|
|
252
|
+
ts_count = self._count_source_files(project_path, {".ts", ".tsx"})
|
|
253
|
+
if ts_count > 0:
|
|
254
|
+
ts_percentage = (ts_count / max(js_count, 1)) * 100
|
|
255
|
+
if ts_percentage > 10: # More than 10% TypeScript
|
|
256
|
+
secondary_languages.append(
|
|
257
|
+
ToolchainComponent(
|
|
258
|
+
name="TypeScript",
|
|
259
|
+
confidence=(
|
|
260
|
+
ConfidenceLevel.HIGH
|
|
261
|
+
if ts_percentage > 50
|
|
262
|
+
else ConfidenceLevel.MEDIUM
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Calculate language percentages
|
|
268
|
+
total_files = js_count
|
|
269
|
+
language_percentages = {"JavaScript": 100.0}
|
|
270
|
+
if secondary_languages:
|
|
271
|
+
js_only = js_count - ts_count
|
|
272
|
+
language_percentages = {
|
|
273
|
+
"JavaScript": (js_only / total_files * 100) if total_files > 0 else 0,
|
|
274
|
+
"TypeScript": (ts_count / total_files * 100) if total_files > 0 else 0,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return LanguageDetection(
|
|
278
|
+
primary_language="Node.js",
|
|
279
|
+
primary_version=version,
|
|
280
|
+
primary_confidence=confidence,
|
|
281
|
+
secondary_languages=secondary_languages,
|
|
282
|
+
language_percentages=language_percentages,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def detect_frameworks(self, project_path: Path) -> List[Framework]:
|
|
286
|
+
"""Detect Node.js frameworks from package.json."""
|
|
287
|
+
frameworks = []
|
|
288
|
+
|
|
289
|
+
package_json_path = project_path / "package.json"
|
|
290
|
+
if not package_json_path.exists():
|
|
291
|
+
return frameworks
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
content = self._read_file(package_json_path)
|
|
295
|
+
if not content:
|
|
296
|
+
return frameworks
|
|
297
|
+
|
|
298
|
+
package_data = json.loads(content)
|
|
299
|
+
all_deps = {}
|
|
300
|
+
all_deps.update(package_data.get("dependencies", {}))
|
|
301
|
+
all_deps.update(package_data.get("devDependencies", {}))
|
|
302
|
+
|
|
303
|
+
# Check for known frameworks
|
|
304
|
+
for fw_name, fw_info in self.FRAMEWORK_INDICATORS.items():
|
|
305
|
+
# Check dependency names
|
|
306
|
+
for dep_name, dep_version in all_deps.items():
|
|
307
|
+
if any(pattern in dep_name for pattern in fw_info["patterns"]):
|
|
308
|
+
# Check if framework-specific config exists (for higher confidence)
|
|
309
|
+
config_files = fw_info.get("config_files", [])
|
|
310
|
+
has_config = (
|
|
311
|
+
any(
|
|
312
|
+
(project_path / config_file).exists()
|
|
313
|
+
for config_file in config_files
|
|
314
|
+
)
|
|
315
|
+
if config_files
|
|
316
|
+
else False
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
confidence = (
|
|
320
|
+
ConfidenceLevel.HIGH
|
|
321
|
+
if has_config
|
|
322
|
+
else ConfidenceLevel.MEDIUM
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
frameworks.append(
|
|
326
|
+
Framework(
|
|
327
|
+
name=fw_name,
|
|
328
|
+
version=(
|
|
329
|
+
dep_version.strip("^~>=<")
|
|
330
|
+
if isinstance(dep_version, str)
|
|
331
|
+
else None
|
|
332
|
+
),
|
|
333
|
+
framework_type=fw_info["type"],
|
|
334
|
+
confidence=confidence,
|
|
335
|
+
is_dev_dependency=dep_name
|
|
336
|
+
in package_data.get("devDependencies", {}),
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
break # Found this framework, move to next
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
self.logger.warning(f"Error parsing package.json: {e}")
|
|
343
|
+
|
|
344
|
+
return frameworks
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class PythonDetectionStrategy(BaseDetectionStrategy):
|
|
348
|
+
"""Detection strategy for Python projects.
|
|
349
|
+
|
|
350
|
+
WHY: Python has multiple dependency management approaches (pip, poetry, pipenv)
|
|
351
|
+
requiring flexible detection logic.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
MARKER_FILES = ["requirements.txt", "pyproject.toml", "setup.py", "Pipfile"]
|
|
355
|
+
VERSION_PATTERNS = [
|
|
356
|
+
r'python_requires\s*=\s*["\']([^"\']+)["\']', # setup.py
|
|
357
|
+
r'python\s*=\s*["\']([^"\']+)["\']', # pyproject.toml
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
FRAMEWORK_INDICATORS = {
|
|
361
|
+
"Django": {"type": "web", "patterns": ["django", "Django"]},
|
|
362
|
+
"Flask": {"type": "web", "patterns": ["flask", "Flask"]},
|
|
363
|
+
"FastAPI": {"type": "web", "patterns": ["fastapi", "FastAPI"]},
|
|
364
|
+
"Tornado": {"type": "web", "patterns": ["tornado"]},
|
|
365
|
+
"pytest": {"type": "testing", "patterns": ["pytest"]},
|
|
366
|
+
"SQLAlchemy": {"type": "orm", "patterns": ["sqlalchemy", "SQLAlchemy"]},
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
def get_language_name(self) -> str:
|
|
370
|
+
return "Python"
|
|
371
|
+
|
|
372
|
+
def can_detect(self, project_path: Path) -> bool:
|
|
373
|
+
"""Check for Python indicators."""
|
|
374
|
+
return self._file_exists(project_path, *self.MARKER_FILES)
|
|
375
|
+
|
|
376
|
+
def detect_language(self, project_path: Path) -> Optional[LanguageDetection]:
|
|
377
|
+
"""Detect Python with confidence scoring."""
|
|
378
|
+
if not self.can_detect(project_path):
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
evidence = DetectionEvidence()
|
|
382
|
+
version = None
|
|
383
|
+
|
|
384
|
+
# Check for requirements.txt
|
|
385
|
+
if (project_path / "requirements.txt").exists():
|
|
386
|
+
evidence.add_indicator("requirements.txt found", 0.15)
|
|
387
|
+
|
|
388
|
+
# Check for pyproject.toml (modern Python)
|
|
389
|
+
pyproject_path = project_path / "pyproject.toml"
|
|
390
|
+
if pyproject_path.exists():
|
|
391
|
+
evidence.add_indicator("pyproject.toml found", 0.20)
|
|
392
|
+
version = self._extract_version_from_file(
|
|
393
|
+
pyproject_path, self.VERSION_PATTERNS
|
|
394
|
+
)
|
|
395
|
+
if version:
|
|
396
|
+
evidence.version_sources.append("pyproject.toml")
|
|
397
|
+
|
|
398
|
+
# Check for setup.py
|
|
399
|
+
setup_path = project_path / "setup.py"
|
|
400
|
+
if setup_path.exists():
|
|
401
|
+
evidence.add_indicator("setup.py found", 0.15)
|
|
402
|
+
if not version:
|
|
403
|
+
version = self._extract_version_from_file(
|
|
404
|
+
setup_path, self.VERSION_PATTERNS
|
|
405
|
+
)
|
|
406
|
+
if version:
|
|
407
|
+
evidence.version_sources.append("setup.py")
|
|
408
|
+
|
|
409
|
+
# Check for Pipfile
|
|
410
|
+
if (project_path / "Pipfile").exists():
|
|
411
|
+
evidence.add_indicator("Pipfile found", 0.10)
|
|
412
|
+
|
|
413
|
+
# Check for virtual environment
|
|
414
|
+
if self._file_exists(project_path, "venv", ".venv", "env"):
|
|
415
|
+
evidence.add_indicator("Virtual environment found", 0.05)
|
|
416
|
+
|
|
417
|
+
# Count Python files
|
|
418
|
+
py_count = self._count_source_files(project_path, {".py"})
|
|
419
|
+
if py_count > 0:
|
|
420
|
+
evidence.add_indicator(f"{py_count} Python files", 0.10)
|
|
421
|
+
|
|
422
|
+
# Determine confidence
|
|
423
|
+
confidence_score = evidence.total_confidence()
|
|
424
|
+
confidence = self._calculate_confidence_level(confidence_score)
|
|
425
|
+
|
|
426
|
+
return LanguageDetection(
|
|
427
|
+
primary_language="Python",
|
|
428
|
+
primary_version=version,
|
|
429
|
+
primary_confidence=confidence,
|
|
430
|
+
secondary_languages=[],
|
|
431
|
+
language_percentages={"Python": 100.0},
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def detect_frameworks(self, project_path: Path) -> List[Framework]:
|
|
435
|
+
"""Detect Python frameworks from dependency files."""
|
|
436
|
+
frameworks = []
|
|
437
|
+
dependencies = self._extract_dependencies(project_path)
|
|
438
|
+
|
|
439
|
+
for fw_name, fw_info in self.FRAMEWORK_INDICATORS.items():
|
|
440
|
+
for dep_name, dep_version in dependencies.items():
|
|
441
|
+
if any(
|
|
442
|
+
pattern.lower() in dep_name.lower()
|
|
443
|
+
for pattern in fw_info["patterns"]
|
|
444
|
+
):
|
|
445
|
+
frameworks.append(
|
|
446
|
+
Framework(
|
|
447
|
+
name=fw_name,
|
|
448
|
+
version=dep_version,
|
|
449
|
+
framework_type=fw_info["type"],
|
|
450
|
+
confidence=ConfidenceLevel.HIGH,
|
|
451
|
+
is_dev_dependency=False, # Hard to determine from requirements.txt
|
|
452
|
+
)
|
|
453
|
+
)
|
|
454
|
+
break
|
|
455
|
+
|
|
456
|
+
return frameworks
|
|
457
|
+
|
|
458
|
+
def _extract_dependencies(self, project_path: Path) -> Dict[str, Optional[str]]:
|
|
459
|
+
"""Extract dependencies from various Python dependency files."""
|
|
460
|
+
dependencies = {}
|
|
461
|
+
|
|
462
|
+
# Parse requirements.txt
|
|
463
|
+
req_path = project_path / "requirements.txt"
|
|
464
|
+
if req_path.exists():
|
|
465
|
+
content = self._read_file(req_path)
|
|
466
|
+
if content:
|
|
467
|
+
for line in content.splitlines():
|
|
468
|
+
line = line.strip()
|
|
469
|
+
if line and not line.startswith("#"):
|
|
470
|
+
# Parse package==version or package>=version
|
|
471
|
+
match = re.match(
|
|
472
|
+
r"([a-zA-Z0-9_-]+)\s*([>=<~!]+)\s*([0-9.]+)", line
|
|
473
|
+
)
|
|
474
|
+
if match:
|
|
475
|
+
dependencies[match.group(1)] = match.group(3)
|
|
476
|
+
else:
|
|
477
|
+
# Just package name
|
|
478
|
+
pkg_name = re.match(r"([a-zA-Z0-9_-]+)", line)
|
|
479
|
+
if pkg_name:
|
|
480
|
+
dependencies[pkg_name.group(1)] = None
|
|
481
|
+
|
|
482
|
+
# Parse pyproject.toml (basic parsing without tomllib for now)
|
|
483
|
+
pyproject_path = project_path / "pyproject.toml"
|
|
484
|
+
if pyproject_path.exists():
|
|
485
|
+
content = self._read_file(pyproject_path)
|
|
486
|
+
if content:
|
|
487
|
+
# Simple regex-based parsing
|
|
488
|
+
dep_matches = re.findall(
|
|
489
|
+
r'([a-zA-Z0-9_-]+)\s*=\s*["\']([^"\']+)["\']', content
|
|
490
|
+
)
|
|
491
|
+
for dep_name, dep_version in dep_matches:
|
|
492
|
+
if dep_name not in dependencies:
|
|
493
|
+
dependencies[dep_name] = dep_version.strip("^~>=<")
|
|
494
|
+
|
|
495
|
+
return dependencies
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class RustDetectionStrategy(BaseDetectionStrategy):
|
|
499
|
+
"""Detection strategy for Rust projects.
|
|
500
|
+
|
|
501
|
+
WHY: Rust has a standardized toolchain (Cargo) making detection reliable.
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
MARKER_FILES = ["Cargo.toml", "Cargo.lock"]
|
|
505
|
+
VERSION_PATTERNS = [
|
|
506
|
+
r'rust-version\s*=\s*"([^"]+)"', # Cargo.toml
|
|
507
|
+
r'edition\s*=\s*"([^"]+)"', # Rust edition
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
FRAMEWORK_INDICATORS = {
|
|
511
|
+
"actix-web": {"type": "web", "patterns": ["actix-web"]},
|
|
512
|
+
"rocket": {"type": "web", "patterns": ["rocket"]},
|
|
513
|
+
"warp": {"type": "web", "patterns": ["warp"]},
|
|
514
|
+
"tokio": {"type": "async", "patterns": ["tokio"]},
|
|
515
|
+
"async-std": {"type": "async", "patterns": ["async-std"]},
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
def get_language_name(self) -> str:
|
|
519
|
+
return "Rust"
|
|
520
|
+
|
|
521
|
+
def can_detect(self, project_path: Path) -> bool:
|
|
522
|
+
"""Check for Rust indicators."""
|
|
523
|
+
return self._file_exists(project_path, *self.MARKER_FILES)
|
|
524
|
+
|
|
525
|
+
def detect_language(self, project_path: Path) -> Optional[LanguageDetection]:
|
|
526
|
+
"""Detect Rust with confidence scoring."""
|
|
527
|
+
if not self.can_detect(project_path):
|
|
528
|
+
return None
|
|
529
|
+
|
|
530
|
+
evidence = DetectionEvidence()
|
|
531
|
+
version = None
|
|
532
|
+
|
|
533
|
+
# Check for Cargo.toml (strongest indicator)
|
|
534
|
+
cargo_path = project_path / "Cargo.toml"
|
|
535
|
+
if cargo_path.exists():
|
|
536
|
+
evidence.add_indicator("Cargo.toml found", 0.25)
|
|
537
|
+
version = self._extract_version_from_file(cargo_path, self.VERSION_PATTERNS)
|
|
538
|
+
if version:
|
|
539
|
+
evidence.version_sources.append("Cargo.toml")
|
|
540
|
+
|
|
541
|
+
# Check for Cargo.lock
|
|
542
|
+
if (project_path / "Cargo.lock").exists():
|
|
543
|
+
evidence.add_indicator("Cargo.lock found", 0.10)
|
|
544
|
+
|
|
545
|
+
# Check for src/ directory with main.rs or lib.rs
|
|
546
|
+
if (project_path / "src" / "main.rs").exists():
|
|
547
|
+
evidence.add_indicator("src/main.rs found", 0.10)
|
|
548
|
+
if (project_path / "src" / "lib.rs").exists():
|
|
549
|
+
evidence.add_indicator("src/lib.rs found", 0.10)
|
|
550
|
+
|
|
551
|
+
# Count Rust files
|
|
552
|
+
rs_count = self._count_source_files(project_path, {".rs"})
|
|
553
|
+
if rs_count > 0:
|
|
554
|
+
evidence.add_indicator(f"{rs_count} Rust files", 0.10)
|
|
555
|
+
|
|
556
|
+
# Determine confidence
|
|
557
|
+
confidence_score = evidence.total_confidence()
|
|
558
|
+
confidence = self._calculate_confidence_level(confidence_score)
|
|
559
|
+
|
|
560
|
+
return LanguageDetection(
|
|
561
|
+
primary_language="Rust",
|
|
562
|
+
primary_version=version,
|
|
563
|
+
primary_confidence=confidence,
|
|
564
|
+
secondary_languages=[],
|
|
565
|
+
language_percentages={"Rust": 100.0},
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
def detect_frameworks(self, project_path: Path) -> List[Framework]:
|
|
569
|
+
"""Detect Rust frameworks from Cargo.toml."""
|
|
570
|
+
frameworks = []
|
|
571
|
+
|
|
572
|
+
cargo_path = project_path / "Cargo.toml"
|
|
573
|
+
if not cargo_path.exists():
|
|
574
|
+
return frameworks
|
|
575
|
+
|
|
576
|
+
content = self._read_file(cargo_path)
|
|
577
|
+
if not content:
|
|
578
|
+
return frameworks
|
|
579
|
+
|
|
580
|
+
# Parse dependencies section (simple approach)
|
|
581
|
+
in_dependencies = False
|
|
582
|
+
for line in content.splitlines():
|
|
583
|
+
line = line.strip()
|
|
584
|
+
|
|
585
|
+
if line.startswith("[dependencies]"):
|
|
586
|
+
in_dependencies = True
|
|
587
|
+
continue
|
|
588
|
+
if line.startswith("["):
|
|
589
|
+
in_dependencies = False
|
|
590
|
+
continue
|
|
591
|
+
|
|
592
|
+
if in_dependencies and "=" in line:
|
|
593
|
+
dep_match = re.match(r"([a-zA-Z0-9_-]+)\s*=", line)
|
|
594
|
+
if dep_match:
|
|
595
|
+
dep_name = dep_match.group(1)
|
|
596
|
+
|
|
597
|
+
# Check against known frameworks
|
|
598
|
+
for fw_name, fw_info in self.FRAMEWORK_INDICATORS.items():
|
|
599
|
+
if any(pattern in dep_name for pattern in fw_info["patterns"]):
|
|
600
|
+
# Extract version if present
|
|
601
|
+
version_match = re.search(r'"([0-9.]+)"', line)
|
|
602
|
+
version = version_match.group(1) if version_match else None
|
|
603
|
+
|
|
604
|
+
frameworks.append(
|
|
605
|
+
Framework(
|
|
606
|
+
name=fw_name,
|
|
607
|
+
version=version,
|
|
608
|
+
framework_type=fw_info["type"],
|
|
609
|
+
confidence=ConfidenceLevel.HIGH,
|
|
610
|
+
is_dev_dependency=False,
|
|
611
|
+
)
|
|
612
|
+
)
|
|
613
|
+
break
|
|
614
|
+
|
|
615
|
+
return frameworks
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
class GoDetectionStrategy(BaseDetectionStrategy):
|
|
619
|
+
"""Detection strategy for Go projects.
|
|
620
|
+
|
|
621
|
+
WHY: Go has standardized module system (go.mod) since Go 1.11.
|
|
622
|
+
"""
|
|
623
|
+
|
|
624
|
+
MARKER_FILES = ["go.mod", "go.sum"]
|
|
625
|
+
VERSION_PATTERNS = [
|
|
626
|
+
r"go\s+([0-9.]+)", # go.mod
|
|
627
|
+
]
|
|
628
|
+
|
|
629
|
+
FRAMEWORK_INDICATORS = {
|
|
630
|
+
"gin": {"type": "web", "patterns": ["gin-gonic/gin"]},
|
|
631
|
+
"echo": {"type": "web", "patterns": ["labstack/echo"]},
|
|
632
|
+
"fiber": {"type": "web", "patterns": ["gofiber/fiber"]},
|
|
633
|
+
"beego": {"type": "web", "patterns": ["beego/beego"]},
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
def get_language_name(self) -> str:
|
|
637
|
+
return "Go"
|
|
638
|
+
|
|
639
|
+
def can_detect(self, project_path: Path) -> bool:
|
|
640
|
+
"""Check for Go indicators."""
|
|
641
|
+
return self._file_exists(project_path, *self.MARKER_FILES)
|
|
642
|
+
|
|
643
|
+
def detect_language(self, project_path: Path) -> Optional[LanguageDetection]:
|
|
644
|
+
"""Detect Go with confidence scoring."""
|
|
645
|
+
if not self.can_detect(project_path):
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
evidence = DetectionEvidence()
|
|
649
|
+
version = None
|
|
650
|
+
|
|
651
|
+
# Check for go.mod
|
|
652
|
+
gomod_path = project_path / "go.mod"
|
|
653
|
+
if gomod_path.exists():
|
|
654
|
+
evidence.add_indicator("go.mod found", 0.25)
|
|
655
|
+
version = self._extract_version_from_file(gomod_path, self.VERSION_PATTERNS)
|
|
656
|
+
if version:
|
|
657
|
+
evidence.version_sources.append("go.mod")
|
|
658
|
+
|
|
659
|
+
# Check for go.sum
|
|
660
|
+
if (project_path / "go.sum").exists():
|
|
661
|
+
evidence.add_indicator("go.sum found", 0.10)
|
|
662
|
+
|
|
663
|
+
# Count Go files
|
|
664
|
+
go_count = self._count_source_files(project_path, {".go"})
|
|
665
|
+
if go_count > 0:
|
|
666
|
+
evidence.add_indicator(f"{go_count} Go files", 0.10)
|
|
667
|
+
|
|
668
|
+
# Check for main.go
|
|
669
|
+
if (project_path / "main.go").exists():
|
|
670
|
+
evidence.add_indicator("main.go found", 0.05)
|
|
671
|
+
|
|
672
|
+
# Determine confidence
|
|
673
|
+
confidence_score = evidence.total_confidence()
|
|
674
|
+
confidence = self._calculate_confidence_level(confidence_score)
|
|
675
|
+
|
|
676
|
+
return LanguageDetection(
|
|
677
|
+
primary_language="Go",
|
|
678
|
+
primary_version=version,
|
|
679
|
+
primary_confidence=confidence,
|
|
680
|
+
secondary_languages=[],
|
|
681
|
+
language_percentages={"Go": 100.0},
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
def detect_frameworks(self, project_path: Path) -> List[Framework]:
|
|
685
|
+
"""Detect Go frameworks from go.mod."""
|
|
686
|
+
frameworks = []
|
|
687
|
+
|
|
688
|
+
gomod_path = project_path / "go.mod"
|
|
689
|
+
if not gomod_path.exists():
|
|
690
|
+
return frameworks
|
|
691
|
+
|
|
692
|
+
content = self._read_file(gomod_path)
|
|
693
|
+
if not content:
|
|
694
|
+
return frameworks
|
|
695
|
+
|
|
696
|
+
# Parse require section
|
|
697
|
+
for line in content.splitlines():
|
|
698
|
+
line = line.strip()
|
|
699
|
+
|
|
700
|
+
# Match require lines
|
|
701
|
+
if "require" in line or line.startswith("github.com"):
|
|
702
|
+
for fw_name, fw_info in self.FRAMEWORK_INDICATORS.items():
|
|
703
|
+
if any(pattern in line for pattern in fw_info["patterns"]):
|
|
704
|
+
# Extract version
|
|
705
|
+
version_match = re.search(r"v([0-9.]+)", line)
|
|
706
|
+
version = version_match.group(1) if version_match else None
|
|
707
|
+
|
|
708
|
+
frameworks.append(
|
|
709
|
+
Framework(
|
|
710
|
+
name=fw_name,
|
|
711
|
+
version=version,
|
|
712
|
+
framework_type=fw_info["type"],
|
|
713
|
+
confidence=ConfidenceLevel.HIGH,
|
|
714
|
+
is_dev_dependency=False,
|
|
715
|
+
)
|
|
716
|
+
)
|
|
717
|
+
break
|
|
718
|
+
|
|
719
|
+
return frameworks
|