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.
Files changed (308) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  4. claude_mpm/agents/BASE_PM.md +106 -1
  5. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
  7. claude_mpm/agents/agent_loader.py +17 -5
  8. claude_mpm/agents/frontmatter_validator.py +284 -253
  9. claude_mpm/agents/templates/README.md +465 -0
  10. claude_mpm/agents/templates/agent-manager.json +4 -1
  11. claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
  12. claude_mpm/agents/templates/api_qa.json +11 -2
  13. claude_mpm/agents/templates/circuit_breakers.md +638 -0
  14. claude_mpm/agents/templates/clerk-ops.json +12 -2
  15. claude_mpm/agents/templates/code_analyzer.json +8 -2
  16. claude_mpm/agents/templates/content-agent.json +358 -0
  17. claude_mpm/agents/templates/dart_engineer.json +15 -2
  18. claude_mpm/agents/templates/data_engineer.json +15 -2
  19. claude_mpm/agents/templates/documentation.json +10 -2
  20. claude_mpm/agents/templates/engineer.json +21 -1
  21. claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
  22. claude_mpm/agents/templates/git_file_tracking.md +584 -0
  23. claude_mpm/agents/templates/golang_engineer.json +270 -0
  24. claude_mpm/agents/templates/imagemagick.json +4 -1
  25. claude_mpm/agents/templates/java_engineer.json +346 -0
  26. claude_mpm/agents/templates/local_ops_agent.json +1227 -6
  27. claude_mpm/agents/templates/memory_manager.json +4 -1
  28. claude_mpm/agents/templates/nextjs_engineer.json +141 -133
  29. claude_mpm/agents/templates/ops.json +12 -2
  30. claude_mpm/agents/templates/php-engineer.json +270 -174
  31. claude_mpm/agents/templates/pm_examples.md +474 -0
  32. claude_mpm/agents/templates/pm_red_flags.md +240 -0
  33. claude_mpm/agents/templates/product_owner.json +338 -0
  34. claude_mpm/agents/templates/project_organizer.json +14 -4
  35. claude_mpm/agents/templates/prompt-engineer.json +13 -2
  36. claude_mpm/agents/templates/python_engineer.json +174 -81
  37. claude_mpm/agents/templates/qa.json +11 -2
  38. claude_mpm/agents/templates/react_engineer.json +16 -3
  39. claude_mpm/agents/templates/refactoring_engineer.json +12 -2
  40. claude_mpm/agents/templates/research.json +34 -21
  41. claude_mpm/agents/templates/response_format.md +583 -0
  42. claude_mpm/agents/templates/ruby-engineer.json +129 -192
  43. claude_mpm/agents/templates/rust_engineer.json +270 -0
  44. claude_mpm/agents/templates/security.json +10 -2
  45. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  46. claude_mpm/agents/templates/ticketing.json +10 -2
  47. claude_mpm/agents/templates/typescript_engineer.json +116 -125
  48. claude_mpm/agents/templates/validation_templates.md +312 -0
  49. claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
  50. claude_mpm/agents/templates/version_control.json +12 -2
  51. claude_mpm/agents/templates/web_qa.json +11 -2
  52. claude_mpm/agents/templates/web_ui.json +15 -2
  53. claude_mpm/cli/__init__.py +34 -614
  54. claude_mpm/cli/commands/agent_manager.py +25 -12
  55. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  56. claude_mpm/cli/commands/agents.py +235 -148
  57. claude_mpm/cli/commands/agents_detect.py +380 -0
  58. claude_mpm/cli/commands/agents_recommend.py +309 -0
  59. claude_mpm/cli/commands/aggregate.py +7 -3
  60. claude_mpm/cli/commands/analyze.py +9 -4
  61. claude_mpm/cli/commands/analyze_code.py +7 -2
  62. claude_mpm/cli/commands/auto_configure.py +570 -0
  63. claude_mpm/cli/commands/config.py +47 -13
  64. claude_mpm/cli/commands/configure.py +419 -1571
  65. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  66. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  67. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  68. claude_mpm/cli/commands/configure_models.py +18 -0
  69. claude_mpm/cli/commands/configure_navigation.py +167 -0
  70. claude_mpm/cli/commands/configure_paths.py +104 -0
  71. claude_mpm/cli/commands/configure_persistence.py +254 -0
  72. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  73. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  74. claude_mpm/cli/commands/configure_validators.py +73 -0
  75. claude_mpm/cli/commands/local_deploy.py +537 -0
  76. claude_mpm/cli/commands/memory.py +54 -20
  77. claude_mpm/cli/commands/mpm_init.py +585 -196
  78. claude_mpm/cli/commands/mpm_init_handler.py +37 -3
  79. claude_mpm/cli/commands/search.py +170 -4
  80. claude_mpm/cli/commands/upgrade.py +152 -0
  81. claude_mpm/cli/executor.py +202 -0
  82. claude_mpm/cli/helpers.py +105 -0
  83. claude_mpm/cli/interactive/__init__.py +3 -0
  84. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  85. claude_mpm/cli/parsers/__init__.py +7 -1
  86. claude_mpm/cli/parsers/agents_parser.py +9 -0
  87. claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
  88. claude_mpm/cli/parsers/base_parser.py +110 -3
  89. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  90. claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
  91. claude_mpm/cli/shared/output_formatters.py +28 -19
  92. claude_mpm/cli/startup.py +481 -0
  93. claude_mpm/cli/utils.py +52 -1
  94. claude_mpm/commands/mpm-agents-detect.md +168 -0
  95. claude_mpm/commands/mpm-agents-recommend.md +214 -0
  96. claude_mpm/commands/mpm-agents.md +75 -1
  97. claude_mpm/commands/mpm-auto-configure.md +217 -0
  98. claude_mpm/commands/mpm-help.md +163 -0
  99. claude_mpm/commands/mpm-init.md +148 -3
  100. claude_mpm/commands/mpm-version.md +113 -0
  101. claude_mpm/commands/mpm.md +1 -0
  102. claude_mpm/config/agent_config.py +2 -2
  103. claude_mpm/config/model_config.py +428 -0
  104. claude_mpm/constants.py +1 -0
  105. claude_mpm/core/base_service.py +13 -12
  106. claude_mpm/core/enums.py +452 -0
  107. claude_mpm/core/factories.py +1 -1
  108. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  109. claude_mpm/core/interactive_session.py +9 -3
  110. claude_mpm/core/log_manager.py +2 -0
  111. claude_mpm/core/logging_config.py +6 -2
  112. claude_mpm/core/oneshot_session.py +8 -4
  113. claude_mpm/core/optimized_agent_loader.py +3 -3
  114. claude_mpm/core/output_style_manager.py +12 -192
  115. claude_mpm/core/service_registry.py +5 -1
  116. claude_mpm/core/types.py +2 -9
  117. claude_mpm/core/typing_utils.py +7 -6
  118. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  119. claude_mpm/dashboard/templates/index.html +3 -41
  120. claude_mpm/hooks/__init__.py +20 -0
  121. claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
  122. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  123. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
  124. claude_mpm/hooks/failure_learning/__init__.py +60 -0
  125. claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
  126. claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
  127. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
  128. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  129. claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
  130. claude_mpm/hooks/kuzu_memory_hook.py +37 -12
  131. claude_mpm/hooks/kuzu_response_hook.py +183 -0
  132. claude_mpm/models/resume_log.py +340 -0
  133. claude_mpm/services/agents/__init__.py +18 -5
  134. claude_mpm/services/agents/auto_config_manager.py +796 -0
  135. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  136. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  137. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  138. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  139. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  140. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  141. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  142. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  143. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  144. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  145. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  146. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  147. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  148. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  149. claude_mpm/services/agents/local_template_manager.py +1 -1
  150. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  151. claude_mpm/services/agents/observers.py +547 -0
  152. claude_mpm/services/agents/recommender.py +568 -0
  153. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  154. claude_mpm/services/command_handler_service.py +11 -5
  155. claude_mpm/services/core/__init__.py +33 -1
  156. claude_mpm/services/core/interfaces/__init__.py +90 -3
  157. claude_mpm/services/core/interfaces/agent.py +184 -0
  158. claude_mpm/services/core/interfaces/health.py +172 -0
  159. claude_mpm/services/core/interfaces/model.py +281 -0
  160. claude_mpm/services/core/interfaces/process.py +372 -0
  161. claude_mpm/services/core/interfaces/project.py +121 -0
  162. claude_mpm/services/core/interfaces/restart.py +307 -0
  163. claude_mpm/services/core/interfaces/stability.py +260 -0
  164. claude_mpm/services/core/memory_manager.py +11 -24
  165. claude_mpm/services/core/models/__init__.py +79 -0
  166. claude_mpm/services/core/models/agent_config.py +381 -0
  167. claude_mpm/services/core/models/health.py +162 -0
  168. claude_mpm/services/core/models/process.py +235 -0
  169. claude_mpm/services/core/models/restart.py +302 -0
  170. claude_mpm/services/core/models/stability.py +264 -0
  171. claude_mpm/services/core/models/toolchain.py +306 -0
  172. claude_mpm/services/core/path_resolver.py +23 -7
  173. claude_mpm/services/diagnostics/__init__.py +2 -2
  174. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  175. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  176. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  177. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  178. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  179. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  180. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  181. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  182. claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
  183. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  184. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  185. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  186. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  187. claude_mpm/services/diagnostics/models.py +19 -24
  188. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  189. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  190. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  191. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  192. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  193. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  194. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  195. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  196. claude_mpm/services/local_ops/__init__.py +163 -0
  197. claude_mpm/services/local_ops/crash_detector.py +257 -0
  198. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  199. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  200. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  201. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  202. claude_mpm/services/local_ops/health_manager.py +430 -0
  203. claude_mpm/services/local_ops/log_monitor.py +396 -0
  204. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  205. claude_mpm/services/local_ops/process_manager.py +595 -0
  206. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  207. claude_mpm/services/local_ops/restart_manager.py +401 -0
  208. claude_mpm/services/local_ops/restart_policy.py +387 -0
  209. claude_mpm/services/local_ops/state_manager.py +372 -0
  210. claude_mpm/services/local_ops/unified_manager.py +600 -0
  211. claude_mpm/services/mcp_config_manager.py +9 -4
  212. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  213. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  214. claude_mpm/services/mcp_gateway/main.py +30 -0
  215. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
  216. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  217. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
  218. claude_mpm/services/mcp_service_verifier.py +1 -1
  219. claude_mpm/services/memory/failure_tracker.py +563 -0
  220. claude_mpm/services/memory_hook_service.py +165 -4
  221. claude_mpm/services/model/__init__.py +147 -0
  222. claude_mpm/services/model/base_provider.py +365 -0
  223. claude_mpm/services/model/claude_provider.py +412 -0
  224. claude_mpm/services/model/model_router.py +453 -0
  225. claude_mpm/services/model/ollama_provider.py +415 -0
  226. claude_mpm/services/monitor/daemon_manager.py +3 -2
  227. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  228. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  229. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  230. claude_mpm/services/monitor/server.py +2 -1
  231. claude_mpm/services/project/__init__.py +23 -0
  232. claude_mpm/services/project/detection_strategies.py +719 -0
  233. claude_mpm/services/project/toolchain_analyzer.py +581 -0
  234. claude_mpm/services/self_upgrade_service.py +342 -0
  235. claude_mpm/services/session_management_service.py +3 -2
  236. claude_mpm/services/session_manager.py +205 -1
  237. claude_mpm/services/shared/async_service_base.py +16 -27
  238. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  239. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  240. claude_mpm/services/socketio/handlers/hook.py +13 -2
  241. claude_mpm/services/socketio/handlers/registry.py +4 -2
  242. claude_mpm/services/socketio/server/main.py +10 -8
  243. claude_mpm/services/subprocess_launcher_service.py +14 -5
  244. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  245. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  246. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  247. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  248. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  249. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  250. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  251. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  252. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  253. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  254. claude_mpm/services/unified/interfaces.py +3 -1
  255. claude_mpm/services/unified/unified_analyzer.py +14 -10
  256. claude_mpm/services/unified/unified_config.py +2 -1
  257. claude_mpm/services/unified/unified_deployment.py +9 -4
  258. claude_mpm/services/version_service.py +104 -1
  259. claude_mpm/skills/__init__.py +21 -0
  260. claude_mpm/skills/bundled/__init__.py +6 -0
  261. claude_mpm/skills/bundled/api-documentation.md +393 -0
  262. claude_mpm/skills/bundled/async-testing.md +571 -0
  263. claude_mpm/skills/bundled/code-review.md +143 -0
  264. claude_mpm/skills/bundled/database-migration.md +199 -0
  265. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  266. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  267. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  268. claude_mpm/skills/bundled/git-workflow.md +414 -0
  269. claude_mpm/skills/bundled/imagemagick.md +204 -0
  270. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  271. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  272. claude_mpm/skills/bundled/pdf.md +141 -0
  273. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  274. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  275. claude_mpm/skills/bundled/security-scanning.md +327 -0
  276. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  277. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  278. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  279. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  280. claude_mpm/skills/bundled/xlsx.md +157 -0
  281. claude_mpm/skills/registry.py +286 -0
  282. claude_mpm/skills/skill_manager.py +310 -0
  283. claude_mpm/storage/state_storage.py +15 -15
  284. claude_mpm/tools/code_tree_analyzer.py +177 -141
  285. claude_mpm/tools/code_tree_events.py +4 -2
  286. claude_mpm/utils/agent_dependency_loader.py +40 -20
  287. claude_mpm/utils/display_helper.py +260 -0
  288. claude_mpm/utils/git_analyzer.py +407 -0
  289. claude_mpm/utils/robust_installer.py +73 -19
  290. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
  291. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
  292. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  293. claude_mpm/dashboard/static/index-hub-backup.html +0 -713
  294. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  295. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  296. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  297. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  298. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  299. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  300. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  301. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  302. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  303. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  304. claude_mpm/services/project/analyzer_refactored.py +0 -450
  305. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  306. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  307. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  308. {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