tree-sitter-analyzer 1.9.17.1__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 (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,779 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Analyze Code Scale MCP Tool
4
+
5
+ This tool provides code scale analysis including metrics about
6
+ complexity, size, and structure through the MCP protocol.
7
+ Enhanced for LLM-friendly analysis workflow.
8
+ """
9
+
10
+ import re
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from ...constants import (
15
+ ELEMENT_TYPE_CLASS,
16
+ ELEMENT_TYPE_FUNCTION,
17
+ ELEMENT_TYPE_IMPORT,
18
+ ELEMENT_TYPE_VARIABLE,
19
+ is_element_of_type,
20
+ )
21
+ from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
22
+ from ...language_detector import detect_language_from_file
23
+ from ...utils import setup_logger
24
+ from .base_tool import BaseMCPTool
25
+
26
+ # Set up logging
27
+ logger = setup_logger(__name__)
28
+
29
+
30
+ class AnalyzeScaleTool(BaseMCPTool):
31
+ """
32
+ MCP Tool for analyzing code scale and complexity metrics.
33
+
34
+ This tool integrates with existing analyzer components to provide
35
+ comprehensive code analysis through the MCP protocol, optimized
36
+ for LLM workflow efficiency.
37
+ """
38
+
39
+ def __init__(self, project_root: str | None = None) -> None:
40
+ """Initialize the analyze scale tool."""
41
+ # Use unified analysis engine instead of deprecated AdvancedAnalyzer
42
+ super().__init__(project_root)
43
+ self.analysis_engine = get_analysis_engine(project_root)
44
+ logger.info("AnalyzeScaleTool initialized with security validation")
45
+
46
+ def set_project_path(self, project_path: str) -> None:
47
+ """
48
+ Update the project path for all components.
49
+
50
+ Args:
51
+ project_path: New project root directory
52
+ """
53
+ super().set_project_path(project_path)
54
+ self.analysis_engine = get_analysis_engine(project_path)
55
+ logger.info(f"AnalyzeScaleTool project path updated to: {project_path}")
56
+
57
+ def _calculate_file_metrics(self, file_path: str) -> dict[str, Any]:
58
+ """
59
+ Calculate basic file metrics including line counts and estimated token count.
60
+
61
+ Args:
62
+ file_path: Path to the file to analyze
63
+
64
+ Returns:
65
+ Dictionary containing file metrics
66
+ """
67
+ try:
68
+ from ...encoding_utils import read_file_safe
69
+
70
+ content, _ = read_file_safe(file_path)
71
+
72
+ lines = content.split("\n")
73
+ total_lines = len(lines)
74
+
75
+ # Count different types of lines
76
+ code_lines = 0
77
+ comment_lines = 0
78
+ blank_lines = 0
79
+
80
+ for line in lines:
81
+ stripped = line.strip()
82
+ if not stripped:
83
+ blank_lines += 1
84
+ elif (
85
+ stripped.startswith("//")
86
+ or stripped.startswith("/*")
87
+ or stripped.startswith("*")
88
+ ):
89
+ comment_lines += 1
90
+ else:
91
+ code_lines += 1
92
+
93
+ # Estimate token count (rough approximation)
94
+ # Split by common delimiters and count non-empty tokens
95
+ tokens = re.findall(r"\b\w+\b|[^\w\s]", content)
96
+ estimated_tokens = len([t for t in tokens if t.strip()])
97
+
98
+ # Calculate file size
99
+ file_size = len(content.encode("utf-8"))
100
+
101
+ return {
102
+ "total_lines": total_lines,
103
+ "code_lines": code_lines,
104
+ "comment_lines": comment_lines,
105
+ "blank_lines": blank_lines,
106
+ "estimated_tokens": estimated_tokens,
107
+ "file_size_bytes": file_size,
108
+ "file_size_kb": round(file_size / 1024, 2),
109
+ }
110
+ except Exception as e:
111
+ logger.error(f"Error calculating file metrics for {file_path}: {e}")
112
+ return {
113
+ "total_lines": 0,
114
+ "code_lines": 0,
115
+ "comment_lines": 0,
116
+ "blank_lines": 0,
117
+ "estimated_tokens": 0,
118
+ "file_size_bytes": 0,
119
+ "file_size_kb": 0,
120
+ }
121
+
122
+ def _extract_structural_overview(self, analysis_result: Any) -> dict[str, Any]:
123
+ """
124
+ Extract structural overview with position information for LLM guidance.
125
+
126
+ Args:
127
+ analysis_result: Result from AdvancedAnalyzer
128
+
129
+ Returns:
130
+ Dictionary containing structural overview
131
+ """
132
+ overview: dict[str, Any] = {
133
+ "classes": [],
134
+ "methods": [],
135
+ "fields": [],
136
+ "imports": [],
137
+ "complexity_hotspots": [],
138
+ }
139
+
140
+ # Extract class information with position from unified analysis engine
141
+ classes = [
142
+ e
143
+ for e in analysis_result.elements
144
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
145
+ ]
146
+ for cls in classes:
147
+ class_info = {
148
+ "name": cls.name,
149
+ "type": cls.class_type,
150
+ "start_line": cls.start_line,
151
+ "end_line": cls.end_line,
152
+ "line_span": cls.end_line - cls.start_line + 1,
153
+ "visibility": cls.visibility,
154
+ "extends": cls.extends_class,
155
+ "implements": cls.implements_interfaces,
156
+ "annotations": [ann.name for ann in cls.annotations],
157
+ }
158
+ overview["classes"].append(class_info)
159
+
160
+ # Extract method information with position and complexity from unified analysis engine
161
+ methods = [
162
+ e
163
+ for e in analysis_result.elements
164
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
165
+ ]
166
+ for method in methods:
167
+ method_info = {
168
+ "name": method.name,
169
+ "start_line": method.start_line,
170
+ "end_line": method.end_line,
171
+ "line_span": method.end_line - method.start_line + 1,
172
+ "visibility": method.visibility,
173
+ "return_type": method.return_type,
174
+ "parameter_count": len(method.parameters),
175
+ "complexity": method.complexity_score,
176
+ "is_constructor": method.is_constructor,
177
+ "is_static": method.is_static,
178
+ "annotations": [ann.name for ann in method.annotations],
179
+ }
180
+ overview["methods"].append(method_info)
181
+
182
+ # Track complexity hotspots
183
+ if method.complexity_score > 10: # High complexity threshold
184
+ overview["complexity_hotspots"].append(
185
+ {
186
+ "type": "method",
187
+ "name": method.name,
188
+ "complexity": method.complexity_score,
189
+ "start_line": method.start_line,
190
+ "end_line": method.end_line,
191
+ }
192
+ )
193
+
194
+ # Extract field information with position
195
+ # Extract field information from unified analysis engine
196
+ fields = [
197
+ e
198
+ for e in analysis_result.elements
199
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
200
+ ]
201
+ for field in fields:
202
+ field_info = {
203
+ "name": field.name,
204
+ "type": field.field_type,
205
+ "start_line": field.start_line,
206
+ "end_line": field.end_line,
207
+ "visibility": field.visibility,
208
+ "is_static": field.is_static,
209
+ "is_final": field.is_final,
210
+ "annotations": [ann.name for ann in field.annotations],
211
+ }
212
+ overview["fields"].append(field_info)
213
+
214
+ # Extract import information
215
+ # Extract import information from unified analysis engine
216
+ imports = [
217
+ e
218
+ for e in analysis_result.elements
219
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
220
+ ]
221
+ for imp in imports:
222
+ import_info = {
223
+ "name": imp.imported_name,
224
+ "statement": imp.import_statement,
225
+ "line": imp.line_number,
226
+ "is_static": imp.is_static,
227
+ "is_wildcard": imp.is_wildcard,
228
+ }
229
+ overview["imports"].append(import_info)
230
+
231
+ return overview
232
+
233
+ def _generate_llm_guidance(
234
+ self, file_metrics: dict[str, Any], structural_overview: dict[str, Any]
235
+ ) -> dict[str, Any]:
236
+ """
237
+ Generate guidance for LLM on how to efficiently analyze this file.
238
+
239
+ Args:
240
+ file_metrics: Basic file metrics
241
+ structural_overview: Structural overview of the code
242
+
243
+ Returns:
244
+ Dictionary containing LLM guidance
245
+ """
246
+ guidance: dict[str, Any] = {
247
+ "analysis_strategy": "",
248
+ "recommended_tools": [],
249
+ "key_areas": [],
250
+ "complexity_assessment": "",
251
+ "size_category": "",
252
+ }
253
+
254
+ total_lines = file_metrics["total_lines"]
255
+ # estimated_tokens = file_metrics["estimated_tokens"] # Not used currently
256
+
257
+ # Determine size category
258
+ if total_lines < 100:
259
+ guidance["size_category"] = "small"
260
+ guidance["analysis_strategy"] = (
261
+ "This is a small file that can be analyzed in full detail."
262
+ )
263
+ elif total_lines < 500:
264
+ guidance["size_category"] = "medium"
265
+ guidance["analysis_strategy"] = (
266
+ "This is a medium-sized file. Consider focusing on key classes and methods."
267
+ )
268
+ elif total_lines < 1500:
269
+ guidance["size_category"] = "large"
270
+ guidance["analysis_strategy"] = (
271
+ "This is a large file. Use targeted analysis with read_code_partial."
272
+ )
273
+ else:
274
+ guidance["size_category"] = "very_large"
275
+ guidance["analysis_strategy"] = (
276
+ "This is a very large file. Strongly recommend using structural analysis first, then targeted deep-dives."
277
+ )
278
+
279
+ # Recommend tools based on file size and complexity
280
+ if total_lines > 200:
281
+ guidance["recommended_tools"].append("read_code_partial")
282
+
283
+ # Ensure all required fields exist
284
+ required_fields = [
285
+ "complexity_hotspots",
286
+ "classes",
287
+ "methods",
288
+ "fields",
289
+ "imports",
290
+ ]
291
+ for field in required_fields:
292
+ if field not in structural_overview:
293
+ structural_overview[field] = []
294
+
295
+ if len(structural_overview["complexity_hotspots"]) > 0:
296
+ guidance["recommended_tools"].append("format_table")
297
+ guidance["complexity_assessment"] = (
298
+ f"Found {len(structural_overview['complexity_hotspots'])} complexity hotspots"
299
+ )
300
+ else:
301
+ guidance["complexity_assessment"] = (
302
+ "No significant complexity hotspots detected"
303
+ )
304
+
305
+ # Identify key areas for analysis
306
+ if len(structural_overview["classes"]) > 1:
307
+ guidance["key_areas"].append(
308
+ "Multiple classes - consider analyzing class relationships"
309
+ )
310
+
311
+ if len(structural_overview["methods"]) > 20:
312
+ guidance["key_areas"].append(
313
+ "Many methods - focus on public interfaces and high-complexity methods"
314
+ )
315
+
316
+ if len(structural_overview["imports"]) > 10:
317
+ guidance["key_areas"].append("Many imports - consider dependency analysis")
318
+
319
+ return guidance
320
+
321
+ def get_tool_schema(self) -> dict[str, Any]:
322
+ """
323
+ Get the MCP tool schema for analyze_code_scale.
324
+
325
+ Returns:
326
+ Dictionary containing the tool schema
327
+ """
328
+ return {
329
+ "type": "object",
330
+ "properties": {
331
+ "file_path": {
332
+ "type": "string",
333
+ "description": "Path to the code file to analyze",
334
+ },
335
+ "language": {
336
+ "type": "string",
337
+ "description": "Programming language (optional, auto-detected if not specified)",
338
+ },
339
+ "include_complexity": {
340
+ "type": "boolean",
341
+ "description": "Include complexity metrics in the analysis",
342
+ "default": True,
343
+ },
344
+ "include_details": {
345
+ "type": "boolean",
346
+ "description": "Include detailed element information",
347
+ "default": False,
348
+ },
349
+ "include_guidance": {
350
+ "type": "boolean",
351
+ "description": "Include LLM analysis guidance",
352
+ "default": True,
353
+ },
354
+ },
355
+ "required": ["file_path"],
356
+ "additionalProperties": False,
357
+ }
358
+
359
+ async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
360
+ """
361
+ Execute the analyze_code_scale tool.
362
+
363
+ Args:
364
+ arguments: Tool arguments containing file_path and optional parameters
365
+
366
+ Returns:
367
+ Dictionary containing enhanced analysis results optimized for LLM workflow
368
+
369
+ Raises:
370
+ ValueError: If required arguments are missing or invalid
371
+ FileNotFoundError: If the specified file doesn't exist
372
+ """
373
+ # Validate required arguments
374
+ if "file_path" not in arguments:
375
+ raise ValueError("file_path is required")
376
+
377
+ file_path = arguments["file_path"]
378
+ language = arguments.get("language")
379
+ # include_complexity = arguments.get("include_complexity", True) # Not used currently
380
+ include_details = arguments.get("include_details", False)
381
+ include_guidance = arguments.get("include_guidance", True)
382
+
383
+ # Security validation BEFORE path resolution to catch symlinks
384
+ is_valid, error_msg = self.security_validator.validate_file_path(file_path)
385
+ if not is_valid:
386
+ logger.warning(
387
+ f"Security validation failed for file path: {file_path} - {error_msg}"
388
+ )
389
+ raise ValueError(f"Invalid file path: {error_msg}")
390
+
391
+ # Resolve file path to absolute path
392
+ resolved_file_path = self.path_resolver.resolve(file_path)
393
+ logger.info(f"Analyzing file: {file_path} (resolved to: {resolved_file_path})")
394
+
395
+ # Additional security validation on resolved path
396
+ is_valid, error_msg = self.security_validator.validate_file_path(
397
+ resolved_file_path
398
+ )
399
+ if not is_valid:
400
+ logger.warning(
401
+ f"Security validation failed for resolved path: {resolved_file_path} - {error_msg}"
402
+ )
403
+ raise ValueError(f"Invalid resolved path: {error_msg}")
404
+
405
+ # Sanitize inputs
406
+ if language:
407
+ language = self.security_validator.sanitize_input(language, max_length=50)
408
+
409
+ # Validate file exists
410
+ if not Path(resolved_file_path).exists():
411
+ raise ValueError("Invalid file path: file does not exist")
412
+
413
+ # Detect language if not specified
414
+ if not language:
415
+ language = detect_language_from_file(resolved_file_path)
416
+ if language == "unknown":
417
+ raise ValueError(
418
+ f"Could not detect language for file: {resolved_file_path}"
419
+ )
420
+
421
+ logger.info(
422
+ f"Analyzing code scale for {resolved_file_path} (language: {language})"
423
+ )
424
+
425
+ try:
426
+ # Use performance monitoring with proper context manager
427
+ from ...mcp.utils import get_performance_monitor
428
+
429
+ with get_performance_monitor().measure_operation(
430
+ "analyze_code_scale_enhanced"
431
+ ):
432
+ # Calculate basic file metrics
433
+ file_metrics = self._calculate_file_metrics(resolved_file_path)
434
+
435
+ # Handle JSON files specially - they don't need structural analysis
436
+ if language == "json":
437
+ return self._create_json_file_analysis(
438
+ resolved_file_path, file_metrics, include_guidance
439
+ )
440
+
441
+ # Use appropriate analyzer based on language
442
+ if language == "java":
443
+ # Use AdvancedAnalyzer for comprehensive analysis
444
+ # Use unified analysis engine instead of deprecated advanced_analyzer
445
+ request = AnalysisRequest(
446
+ file_path=resolved_file_path,
447
+ language=language,
448
+ include_complexity=True,
449
+ include_details=True,
450
+ )
451
+ analysis_result = await self.analysis_engine.analyze(request)
452
+ if analysis_result is None:
453
+ raise RuntimeError(f"Failed to analyze file: {file_path}")
454
+ # Extract structural overview
455
+ structural_overview = self._extract_structural_overview(
456
+ analysis_result
457
+ )
458
+ else:
459
+ # Use universal analysis_engine for other languages
460
+ request = AnalysisRequest(
461
+ file_path=resolved_file_path,
462
+ language=language,
463
+ include_details=include_details,
464
+ )
465
+ universal_result = await self.analysis_engine.analyze(request)
466
+ if not universal_result or not universal_result.success:
467
+ error_msg = (
468
+ universal_result.error_message or "Unknown error"
469
+ if universal_result
470
+ else "Unknown error"
471
+ )
472
+ raise RuntimeError(
473
+ f"Failed to analyze file with universal engine: {error_msg}"
474
+ )
475
+
476
+ # Adapt the result to a compatible structure for report generation
477
+ # This part needs careful implementation based on universal_result structure
478
+ analysis_result = None # Placeholder
479
+ structural_overview = {} # Placeholder
480
+
481
+ # Generate LLM guidance
482
+ llm_guidance = None
483
+ if include_guidance:
484
+ llm_guidance = self._generate_llm_guidance(
485
+ file_metrics, structural_overview
486
+ )
487
+
488
+ # Build enhanced result structure
489
+ result = {
490
+ "success": True,
491
+ "file_path": file_path,
492
+ "language": language,
493
+ "file_metrics": file_metrics,
494
+ "summary": {
495
+ "classes": len(
496
+ [
497
+ e
498
+ for e in (
499
+ analysis_result.elements if analysis_result else []
500
+ )
501
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
502
+ ]
503
+ ),
504
+ "methods": len(
505
+ [
506
+ e
507
+ for e in (
508
+ analysis_result.elements if analysis_result else []
509
+ )
510
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
511
+ ]
512
+ ),
513
+ "fields": len(
514
+ [
515
+ e
516
+ for e in (
517
+ analysis_result.elements if analysis_result else []
518
+ )
519
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
520
+ ]
521
+ ),
522
+ "imports": len(
523
+ [
524
+ e
525
+ for e in (
526
+ analysis_result.elements if analysis_result else []
527
+ )
528
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
529
+ ]
530
+ ),
531
+ "annotations": len(
532
+ getattr(analysis_result, "annotations", [])
533
+ if analysis_result
534
+ else []
535
+ ),
536
+ "package": (
537
+ analysis_result.package.name
538
+ if analysis_result and analysis_result.package
539
+ else None
540
+ ),
541
+ },
542
+ "structural_overview": structural_overview,
543
+ }
544
+
545
+ if include_guidance:
546
+ result["llm_guidance"] = llm_guidance
547
+
548
+ # Add detailed information if requested (backward compatibility)
549
+ if include_details:
550
+ result["detailed_analysis"] = {
551
+ "statistics": (
552
+ analysis_result.get_statistics() if analysis_result else {}
553
+ ),
554
+ "classes": [
555
+ {
556
+ "name": cls.name,
557
+ "type": getattr(cls, "class_type", "unknown"),
558
+ "visibility": getattr(cls, "visibility", "unknown"),
559
+ "extends": getattr(cls, "extends_class", None),
560
+ "implements": getattr(cls, "implements_interfaces", []),
561
+ "annotations": [
562
+ getattr(ann, "name", str(ann))
563
+ for ann in getattr(cls, "annotations", [])
564
+ ],
565
+ "lines": f"{cls.start_line}-{cls.end_line}",
566
+ }
567
+ for cls in [
568
+ e
569
+ for e in (
570
+ analysis_result.elements if analysis_result else []
571
+ )
572
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
573
+ ]
574
+ ],
575
+ "methods": [
576
+ {
577
+ "name": method.name,
578
+ "file_path": getattr(method, "file_path", file_path),
579
+ "visibility": getattr(method, "visibility", "unknown"),
580
+ "return_type": getattr(
581
+ method, "return_type", "unknown"
582
+ ),
583
+ "parameters": len(getattr(method, "parameters", [])),
584
+ "annotations": [
585
+ getattr(ann, "name", str(ann))
586
+ for ann in getattr(method, "annotations", [])
587
+ ],
588
+ "is_constructor": getattr(
589
+ method, "is_constructor", False
590
+ ),
591
+ "is_static": getattr(method, "is_static", False),
592
+ "complexity": getattr(method, "complexity_score", 0),
593
+ "lines": f"{method.start_line}-{method.end_line}",
594
+ }
595
+ for method in [
596
+ e
597
+ for e in (
598
+ analysis_result.elements if analysis_result else []
599
+ )
600
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
601
+ ]
602
+ ],
603
+ "fields": [
604
+ {
605
+ "name": field.name,
606
+ "type": getattr(field, "field_type", "unknown"),
607
+ "file_path": getattr(field, "file_path", file_path),
608
+ "visibility": getattr(field, "visibility", "unknown"),
609
+ "is_static": getattr(field, "is_static", False),
610
+ "is_final": getattr(field, "is_final", False),
611
+ "annotations": [
612
+ getattr(ann, "name", str(ann))
613
+ for ann in getattr(field, "annotations", [])
614
+ ],
615
+ "lines": f"{field.start_line}-{field.end_line}",
616
+ }
617
+ for field in [
618
+ e
619
+ for e in (
620
+ analysis_result.elements if analysis_result else []
621
+ )
622
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
623
+ ]
624
+ ],
625
+ }
626
+
627
+ # Count elements by type
628
+ classes_count = len(
629
+ [
630
+ e
631
+ for e in (analysis_result.elements if analysis_result else [])
632
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
633
+ ]
634
+ )
635
+ methods_count = len(
636
+ [
637
+ e
638
+ for e in (analysis_result.elements if analysis_result else [])
639
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
640
+ ]
641
+ )
642
+
643
+ logger.info(
644
+ f"Successfully analyzed {file_path}: {classes_count} classes, "
645
+ f"{methods_count} methods, {file_metrics['total_lines']} lines, "
646
+ f"~{file_metrics['estimated_tokens']} tokens"
647
+ )
648
+
649
+ return result
650
+
651
+ except Exception as e:
652
+ logger.error(f"Error analyzing {file_path}: {e}")
653
+ raise
654
+
655
+ def validate_arguments(self, arguments: dict[str, Any]) -> bool:
656
+ """
657
+ Validate tool arguments against the schema.
658
+
659
+ Args:
660
+ arguments: Arguments to validate
661
+
662
+ Returns:
663
+ True if arguments are valid
664
+
665
+ Raises:
666
+ ValueError: If arguments are invalid
667
+ """
668
+ schema = self.get_tool_schema()
669
+ required_fields = schema.get("required", [])
670
+
671
+ # Check required fields
672
+ for field in required_fields:
673
+ if field not in arguments:
674
+ raise ValueError(f"Required field '{field}' is missing")
675
+
676
+ # Validate file_path
677
+ if "file_path" in arguments:
678
+ file_path = arguments["file_path"]
679
+ if not isinstance(file_path, str):
680
+ raise ValueError("file_path must be a string")
681
+ if not file_path.strip():
682
+ raise ValueError("file_path cannot be empty")
683
+
684
+ # Validate optional fields
685
+ if "language" in arguments:
686
+ language = arguments["language"]
687
+ if not isinstance(language, str):
688
+ raise ValueError("language must be a string")
689
+
690
+ if "include_complexity" in arguments:
691
+ include_complexity = arguments["include_complexity"]
692
+ if not isinstance(include_complexity, bool):
693
+ raise ValueError("include_complexity must be a boolean")
694
+
695
+ if "include_details" in arguments:
696
+ include_details = arguments["include_details"]
697
+ if not isinstance(include_details, bool):
698
+ raise ValueError("include_details must be a boolean")
699
+
700
+ if "include_guidance" in arguments:
701
+ include_guidance = arguments["include_guidance"]
702
+ if not isinstance(include_guidance, bool):
703
+ raise ValueError("include_guidance must be a boolean")
704
+
705
+ return True
706
+
707
+ def _create_json_file_analysis(
708
+ self, file_path: str, file_metrics: dict[str, Any], include_guidance: bool
709
+ ) -> dict[str, Any]:
710
+ """
711
+ Create analysis result for JSON files.
712
+
713
+ Args:
714
+ file_path: Path to the JSON file
715
+ file_metrics: Basic file metrics
716
+ include_guidance: Whether to include guidance
717
+
718
+ Returns:
719
+ Analysis result for JSON file
720
+ """
721
+ result = {
722
+ "success": True,
723
+ "file_path": file_path,
724
+ "language": "json",
725
+ "file_size_bytes": file_metrics["file_size_bytes"],
726
+ "total_lines": file_metrics["total_lines"],
727
+ "non_empty_lines": file_metrics["total_lines"]
728
+ - file_metrics["blank_lines"],
729
+ "estimated_tokens": file_metrics["estimated_tokens"],
730
+ "complexity_metrics": {
731
+ "total_elements": 0,
732
+ "max_depth": 0,
733
+ "avg_complexity": 0.0,
734
+ },
735
+ "structural_overview": {
736
+ "classes": [],
737
+ "methods": [],
738
+ "fields": [],
739
+ },
740
+ "scale_category": (
741
+ "small"
742
+ if file_metrics["total_lines"] < 100
743
+ else "medium"
744
+ if file_metrics["total_lines"] < 1000
745
+ else "large"
746
+ ),
747
+ "analysis_recommendations": {
748
+ "suitable_for_full_analysis": file_metrics["total_lines"] < 1000,
749
+ "recommended_approach": "JSON files are configuration/data files - structural analysis not applicable",
750
+ "token_efficiency_notes": "JSON files can be read directly without tree-sitter parsing",
751
+ },
752
+ }
753
+
754
+ if include_guidance:
755
+ result["llm_analysis_guidance"] = {
756
+ "file_characteristics": "JSON configuration/data file",
757
+ "recommended_workflow": "Direct file reading for content analysis",
758
+ "token_optimization": "Use simple file reading tools for JSON content",
759
+ "analysis_focus": "Data structure and configuration values",
760
+ }
761
+
762
+ return result
763
+
764
+ def get_tool_definition(self) -> dict[str, Any]:
765
+ """
766
+ Get the MCP tool definition for check_code_scale.
767
+
768
+ Returns:
769
+ Tool definition dictionary compatible with MCP server
770
+ """
771
+ return {
772
+ "name": "check_code_scale",
773
+ "description": "Analyze code scale, complexity, and structure metrics with LLM-optimized guidance for efficient large file analysis and token-aware workflow recommendations",
774
+ "inputSchema": self.get_tool_schema(),
775
+ }
776
+
777
+
778
+ # Tool instance for easy access
779
+ analyze_scale_tool = AnalyzeScaleTool()