tree-sitter-analyzer 1.7.3__py3-none-any.whl → 1.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

@@ -446,80 +446,9 @@ class TreeSitterAnalyzerMCPServer:
446
446
  logger.info("Client requesting tools list")
447
447
 
448
448
  tools = [
449
- Tool(
450
- name="check_code_scale",
451
- description="Analyze code file size and complexity metrics",
452
- inputSchema={
453
- "type": "object",
454
- "properties": {
455
- "file_path": {
456
- "type": "string",
457
- "description": "Path to the code file (relative to project root)",
458
- }
459
- },
460
- "required": ["file_path"],
461
- "additionalProperties": False,
462
- },
463
- ),
464
- Tool(
465
- name="analyze_code_structure",
466
- description="Analyze code structure and generate tables with line positions, optionally save to file",
467
- inputSchema={
468
- "type": "object",
469
- "properties": {
470
- "file_path": {
471
- "type": "string",
472
- "description": "Path to the code file (relative to project root)",
473
- },
474
- "format_type": {
475
- "type": "string",
476
- "description": "Table format type",
477
- "enum": ["full", "compact", "csv", "json"],
478
- "default": "full",
479
- },
480
- "language": {
481
- "type": "string",
482
- "description": "Programming language (optional, auto-detected if not specified)",
483
- },
484
- "output_file": {
485
- "type": "string",
486
- "description": "Optional filename to save output to file (extension auto-detected based on content)",
487
- },
488
- "suppress_output": {
489
- "type": "boolean",
490
- "description": "When true and output_file is specified, suppress table_output in response to save tokens",
491
- "default": False,
492
- },
493
- },
494
- "required": ["file_path"],
495
- "additionalProperties": False,
496
- },
497
- ),
498
- Tool(
499
- name="extract_code_section",
500
- description="Extract a code section by line range",
501
- inputSchema={
502
- "type": "object",
503
- "properties": {
504
- "file_path": {
505
- "type": "string",
506
- "description": "Path to the code file (relative to project root)",
507
- },
508
- "start_line": {
509
- "type": "integer",
510
- "description": "Start line (1-based)",
511
- "minimum": 1,
512
- },
513
- "end_line": {
514
- "type": "integer",
515
- "description": "End line (optional, 1-based)",
516
- "minimum": 1,
517
- },
518
- },
519
- "required": ["file_path", "start_line"],
520
- "additionalProperties": False,
521
- },
522
- ),
449
+ Tool(**self.analyze_scale_tool.get_tool_definition()),
450
+ Tool(**self.table_format_tool.get_tool_definition()),
451
+ Tool(**self.read_partial_tool.get_tool_definition()),
523
452
  Tool(
524
453
  name="set_project_path",
525
454
  description="Set or override the project root path used for security boundaries",
@@ -603,6 +532,8 @@ class TreeSitterAnalyzerMCPServer:
603
532
  "start_column": arguments.get("start_column"),
604
533
  "end_column": arguments.get("end_column"),
605
534
  "format": arguments.get("format", "text"),
535
+ "output_file": arguments.get("output_file"),
536
+ "suppress_output": arguments.get("suppress_output", False),
606
537
  }
607
538
  result = await self.read_partial_tool.execute(full_args)
608
539
 
@@ -688,28 +688,18 @@ class AnalyzeScaleTool(BaseMCPTool):
688
688
 
689
689
  return True
690
690
 
691
- def get_tool_definition(self) -> Any:
691
+ def get_tool_definition(self) -> dict[str, Any]:
692
692
  """
693
- Get the MCP tool definition for analyze_code_scale.
693
+ Get the MCP tool definition for check_code_scale.
694
694
 
695
695
  Returns:
696
- Tool definition object compatible with MCP server
696
+ Tool definition dictionary compatible with MCP server
697
697
  """
698
- try:
699
- from mcp.types import Tool
700
-
701
- return Tool(
702
- name="analyze_code_scale",
703
- description="Analyze code scale, complexity, and structure metrics with LLM-optimized guidance for efficient large file analysis",
704
- inputSchema=self.get_tool_schema(),
705
- )
706
- except ImportError:
707
- # Fallback for when MCP is not available
708
- return {
709
- "name": "analyze_code_scale",
710
- "description": "Analyze code scale, complexity, and structure metrics with LLM-optimized guidance for efficient large file analysis",
711
- "inputSchema": self.get_tool_schema(),
712
- }
698
+ return {
699
+ "name": "check_code_scale",
700
+ "description": "Analyze code scale, complexity, and structure metrics with LLM-optimized guidance for efficient large file analysis and token-aware workflow recommendations",
701
+ "inputSchema": self.get_tool_schema(),
702
+ }
713
703
 
714
704
 
715
705
  # Tool instance for easy access
@@ -32,7 +32,7 @@ class FindAndGrepTool(BaseMCPTool):
32
32
  def get_tool_definition(self) -> dict[str, Any]:
33
33
  return {
34
34
  "name": "find_and_grep",
35
- "description": "Two-stage search: first use fd to find files matching criteria, then use ripgrep to search content within those files. Combines file filtering with content search for precise results.",
35
+ "description": "Two-stage search: first use fd to find files matching criteria, then use ripgrep to search content within those files. Combines file filtering with content search for precise results with advanced token optimization (summary_only, group_by_file, total_only, suppress_output).",
36
36
  "inputSchema": {
37
37
  "type": "object",
38
38
  "properties": {
@@ -27,7 +27,7 @@ class ListFilesTool(BaseMCPTool):
27
27
  def get_tool_definition(self) -> dict[str, Any]:
28
28
  return {
29
29
  "name": "list_files",
30
- "description": "List files and directories using fd with advanced filtering options. Supports glob patterns, file types, size filters, and more. Returns file paths with metadata or just counts.",
30
+ "description": "List files and directories using fd with advanced filtering options. Supports glob patterns, file types, size filters, and more. Returns file paths with metadata or just counts, with optional file output and token optimization.",
31
31
  "inputSchema": {
32
32
  "type": "object",
33
33
  "properties": {
@@ -7,11 +7,13 @@ Supports both predefined query keys and custom query strings.
7
7
  """
8
8
 
9
9
  import logging
10
+ from pathlib import Path
10
11
  from typing import Any
11
12
 
12
13
  from ...core.query_service import QueryService
13
14
  from ...language_detector import detect_language_from_file
14
15
  from ..utils.error_handler import handle_mcp_errors
16
+ from ..utils.file_output_manager import FileOutputManager
15
17
  from .base_tool import BaseMCPTool
16
18
 
17
19
  logger = logging.getLogger(__name__)
@@ -24,6 +26,7 @@ class QueryTool(BaseMCPTool):
24
26
  """Initialize query tool"""
25
27
  super().__init__(project_root)
26
28
  self.query_service = QueryService(project_root)
29
+ self.file_output_manager = FileOutputManager(project_root)
27
30
 
28
31
  def set_project_path(self, project_path: str) -> None:
29
32
  """
@@ -34,6 +37,7 @@ class QueryTool(BaseMCPTool):
34
37
  """
35
38
  super().set_project_path(project_path)
36
39
  self.query_service = QueryService(project_path)
40
+ self.file_output_manager.set_project_root(project_path)
37
41
  logger.info(f"QueryTool project path updated to: {project_path}")
38
42
 
39
43
  def get_tool_definition(self) -> dict[str, Any]:
@@ -45,7 +49,7 @@ class QueryTool(BaseMCPTool):
45
49
  """
46
50
  return {
47
51
  "name": "query_code",
48
- "description": "Execute tree-sitter queries on code files to extract specific code elements",
52
+ "description": "Execute tree-sitter queries on code files to extract specific code elements with filtering, multiple output formats, and optional file saving with token optimization",
49
53
  "inputSchema": {
50
54
  "type": "object",
51
55
  "properties": {
@@ -75,6 +79,15 @@ class QueryTool(BaseMCPTool):
75
79
  "default": "json",
76
80
  "description": "Output format",
77
81
  },
82
+ "output_file": {
83
+ "type": "string",
84
+ "description": "Optional filename to save output to file (extension auto-detected based on content)",
85
+ },
86
+ "suppress_output": {
87
+ "type": "boolean",
88
+ "description": "When true and output_file is specified, suppress detailed output in response to save tokens",
89
+ "default": False,
90
+ },
78
91
  },
79
92
  "required": ["file_path"],
80
93
  "anyOf": [
@@ -118,6 +131,8 @@ class QueryTool(BaseMCPTool):
118
131
  query_string = arguments.get("query_string")
119
132
  filter_expression = arguments.get("filter")
120
133
  output_format = arguments.get("output_format", "json")
134
+ output_file = arguments.get("output_file")
135
+ suppress_output = arguments.get("suppress_output", False)
121
136
 
122
137
  if not query_key and not query_string:
123
138
  raise ValueError("Either query_key or query_string must be provided")
@@ -148,9 +163,9 @@ class QueryTool(BaseMCPTool):
148
163
 
149
164
  # Format output
150
165
  if output_format == "summary":
151
- return self._format_summary(results, query_key or "custom", language)
166
+ formatted_result = self._format_summary(results, query_key or "custom", language)
152
167
  else:
153
- return {
168
+ formatted_result = {
154
169
  "success": True,
155
170
  "results": results,
156
171
  "count": len(results),
@@ -159,6 +174,60 @@ class QueryTool(BaseMCPTool):
159
174
  "query": query_key or query_string,
160
175
  }
161
176
 
177
+ # Handle file output if requested
178
+ if output_file:
179
+ try:
180
+ import json
181
+
182
+ # Generate base name from original file path if not provided
183
+ if not output_file or output_file.strip() == "":
184
+ base_name = f"{Path(file_path).stem}_query_{query_key or 'custom'}"
185
+ else:
186
+ base_name = output_file
187
+
188
+ # Convert result to JSON string for file output
189
+ json_content = json.dumps(formatted_result, indent=2, ensure_ascii=False)
190
+
191
+ # Save to file with automatic extension detection
192
+ saved_file_path = self.file_output_manager.save_to_file(
193
+ content=json_content,
194
+ base_name=base_name
195
+ )
196
+
197
+ # Add file output info to result
198
+ formatted_result["output_file_path"] = saved_file_path
199
+ formatted_result["file_saved"] = True
200
+
201
+ logger.info(f"Query output saved to: {saved_file_path}")
202
+
203
+ except Exception as e:
204
+ logger.error(f"Failed to save output to file: {e}")
205
+ formatted_result["file_save_error"] = str(e)
206
+ formatted_result["file_saved"] = False
207
+
208
+ # Apply suppress_output logic
209
+ if suppress_output and output_file:
210
+ # Create minimal result when output is suppressed
211
+ minimal_result = {
212
+ "success": formatted_result.get("success", True),
213
+ "count": formatted_result.get("count", len(results)),
214
+ "file_path": file_path,
215
+ "language": language,
216
+ "query": query_key or query_string,
217
+ }
218
+
219
+ # Include file output info if present
220
+ if "output_file_path" in formatted_result:
221
+ minimal_result["output_file_path"] = formatted_result["output_file_path"]
222
+ minimal_result["file_saved"] = formatted_result["file_saved"]
223
+ if "file_save_error" in formatted_result:
224
+ minimal_result["file_save_error"] = formatted_result["file_save_error"]
225
+ minimal_result["file_saved"] = formatted_result["file_saved"]
226
+
227
+ return minimal_result
228
+ else:
229
+ return formatted_result
230
+
162
231
  except Exception as e:
163
232
  logger.error(f"Query execution failed: {e}")
164
233
  return {
@@ -314,4 +383,18 @@ class QueryTool(BaseMCPTool):
314
383
  if output_format not in ["json", "summary"]:
315
384
  raise ValueError("output_format must be one of: json, summary")
316
385
 
386
+ # Validate output_file if provided
387
+ if "output_file" in arguments:
388
+ output_file = arguments["output_file"]
389
+ if not isinstance(output_file, str):
390
+ raise ValueError("output_file must be a string")
391
+ if not output_file.strip():
392
+ raise ValueError("output_file cannot be empty")
393
+
394
+ # Validate suppress_output if provided
395
+ if "suppress_output" in arguments:
396
+ suppress_output = arguments["suppress_output"]
397
+ if not isinstance(suppress_output, bool):
398
+ raise ValueError("suppress_output must be a boolean")
399
+
317
400
  return True
@@ -12,6 +12,7 @@ from typing import Any
12
12
 
13
13
  from ...file_handler import read_file_partial
14
14
  from ...utils import setup_logger
15
+ from ..utils.file_output_manager import FileOutputManager
15
16
  from .base_tool import BaseMCPTool
16
17
 
17
18
  # Set up logging
@@ -29,6 +30,7 @@ class ReadPartialTool(BaseMCPTool):
29
30
  def __init__(self, project_root: str = None) -> None:
30
31
  """Initialize the read partial tool."""
31
32
  super().__init__(project_root)
33
+ self.file_output_manager = FileOutputManager(project_root)
32
34
  logger.info("ReadPartialTool initialized with security validation")
33
35
 
34
36
  def get_tool_schema(self) -> dict[str, Any]:
@@ -68,12 +70,20 @@ class ReadPartialTool(BaseMCPTool):
68
70
  "format": {
69
71
  "type": "string",
70
72
  "description": "Output format for the content",
71
- "enum": ["text", "json"],
73
+ "enum": ["text", "json", "raw"],
72
74
  "default": "text",
73
75
  },
76
+ "output_file": {
77
+ "type": "string",
78
+ "description": "Optional filename to save output to file (extension auto-detected based on content)",
79
+ },
80
+ "suppress_output": {
81
+ "type": "boolean",
82
+ "description": "When true and output_file is specified, suppress partial_content_result in response to save tokens",
83
+ "default": False,
84
+ },
74
85
  },
75
86
  "required": ["file_path", "start_line"],
76
- "additionalProperties": False,
77
87
  }
78
88
 
79
89
  async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
@@ -102,7 +112,9 @@ class ReadPartialTool(BaseMCPTool):
102
112
  end_line = arguments.get("end_line")
103
113
  start_column = arguments.get("start_column")
104
114
  end_column = arguments.get("end_column")
105
- # output_format = arguments.get("format", "text") # Not used currently
115
+ output_file = arguments.get("output_file")
116
+ suppress_output = arguments.get("suppress_output", False)
117
+ output_format = arguments.get("format", "text")
106
118
 
107
119
  # Resolve file path using common path resolver
108
120
  resolved_path = self.path_resolver.resolve(file_path)
@@ -186,7 +198,59 @@ class ReadPartialTool(BaseMCPTool):
186
198
  f"Successfully read {len(content)} characters from {file_path}"
187
199
  )
188
200
 
189
- return {"partial_content_result": cli_output}
201
+ # Build result - conditionally include partial_content_result based on suppress_output
202
+ result = {
203
+ "file_path": file_path,
204
+ "range": {
205
+ "start_line": start_line,
206
+ "end_line": end_line,
207
+ "start_column": start_column,
208
+ "end_column": end_column,
209
+ },
210
+ "content_length": len(content),
211
+ }
212
+
213
+ # Only include partial_content_result if not suppressed or no output file specified
214
+ if not suppress_output or not output_file:
215
+ result["partial_content_result"] = cli_output
216
+
217
+ # Handle file output if requested
218
+ if output_file:
219
+ try:
220
+ # Generate base name from original file path if not provided
221
+ if not output_file or output_file.strip() == "":
222
+ base_name = Path(file_path).stem + "_extract"
223
+ else:
224
+ base_name = output_file
225
+
226
+ # Determine what content to save based on format preference
227
+ if output_format == "raw":
228
+ # Save only the extracted code content (no metadata)
229
+ content_to_save = content
230
+ elif output_format == "json":
231
+ # Save structured JSON data
232
+ content_to_save = json_output
233
+ else: # format == "text" (default)
234
+ # Save CLI-compatible format with headers
235
+ content_to_save = cli_output
236
+
237
+ # Save to file with automatic extension detection
238
+ saved_file_path = self.file_output_manager.save_to_file(
239
+ content=content_to_save,
240
+ base_name=base_name
241
+ )
242
+
243
+ result["output_file_path"] = saved_file_path
244
+ result["file_saved"] = True
245
+
246
+ logger.info(f"Extract output saved to: {saved_file_path}")
247
+
248
+ except Exception as e:
249
+ logger.error(f"Failed to save output to file: {e}")
250
+ result["file_save_error"] = str(e)
251
+ result["file_saved"] = False
252
+
253
+ return result
190
254
 
191
255
  except Exception as e:
192
256
  logger.error(f"Error reading partial content from {file_path}: {e}")
@@ -280,33 +344,37 @@ class ReadPartialTool(BaseMCPTool):
280
344
  format_value = arguments["format"]
281
345
  if not isinstance(format_value, str):
282
346
  raise ValueError("format must be a string")
283
- if format_value not in ["text", "json"]:
284
- raise ValueError("format must be 'text' or 'json'")
347
+ if format_value not in ["text", "json", "raw"]:
348
+ raise ValueError("format must be 'text', 'json', or 'raw'")
349
+
350
+ # Validate output_file if provided
351
+ if "output_file" in arguments:
352
+ output_file = arguments["output_file"]
353
+ if not isinstance(output_file, str):
354
+ raise ValueError("output_file must be a string")
355
+ if not output_file.strip():
356
+ raise ValueError("output_file cannot be empty")
357
+
358
+ # Validate suppress_output if provided
359
+ if "suppress_output" in arguments:
360
+ suppress_output = arguments["suppress_output"]
361
+ if not isinstance(suppress_output, bool):
362
+ raise ValueError("suppress_output must be a boolean")
285
363
 
286
364
  return True
287
365
 
288
- def get_tool_definition(self) -> Any:
366
+ def get_tool_definition(self) -> dict[str, Any]:
289
367
  """
290
368
  Get the MCP tool definition for read_code_partial.
291
369
 
292
370
  Returns:
293
- Tool definition object compatible with MCP server
371
+ Tool definition dictionary compatible with MCP server
294
372
  """
295
- try:
296
- from mcp.types import Tool
297
-
298
- return Tool(
299
- name="extract_code_section",
300
- description="Extract specific code sections by line range (equivalent to CLI --partial-read option)",
301
- inputSchema=self.get_tool_schema(),
302
- )
303
- except ImportError:
304
- # Fallback for when MCP is not available
305
- return {
306
- "name": "extract_code_section",
307
- "description": "Extract specific code sections by line range (equivalent to CLI --partial-read option)",
308
- "inputSchema": self.get_tool_schema(),
309
- }
373
+ return {
374
+ "name": "extract_code_section",
375
+ "description": "Extract specific code sections by line/column range with multiple output formats (text/json/raw), optionally save to file with token optimization",
376
+ "inputSchema": self.get_tool_schema(),
377
+ }
310
378
 
311
379
 
312
380
  # Tool instance for easy access
@@ -42,7 +42,7 @@ class SearchContentTool(BaseMCPTool):
42
42
  def get_tool_definition(self) -> dict[str, Any]:
43
43
  return {
44
44
  "name": "search_content",
45
- "description": "Search text content inside files using ripgrep. Supports regex patterns, case sensitivity, context lines, and various output formats. Can search in directories or specific files.",
45
+ "description": "Search text content inside files using ripgrep. Supports regex patterns, case sensitivity, context lines, and various output formats. Can search in directories or specific files with advanced token optimization (summary_only, group_by_file, total_only, suppress_output).",
46
46
  "inputSchema": {
47
47
  "type": "object",
48
48
  "properties": {
@@ -511,28 +511,18 @@ class TableFormatTool(BaseMCPTool):
511
511
  self.logger.error(f"Error in code structure analysis tool: {e}")
512
512
  raise
513
513
 
514
- def get_tool_definition(self) -> Any:
514
+ def get_tool_definition(self) -> dict[str, Any]:
515
515
  """
516
516
  Get the MCP tool definition for analyze_code_structure.
517
517
 
518
518
  Returns:
519
- Tool definition object compatible with MCP server
519
+ Tool definition dictionary compatible with MCP server
520
520
  """
521
- try:
522
- from mcp.types import Tool
523
-
524
- return Tool(
525
- name="analyze_code_structure",
526
- description="Analyze code structure and generate detailed overview tables (classes, methods, fields) for large files",
527
- inputSchema=self.get_tool_schema(),
528
- )
529
- except ImportError:
530
- # Fallback for when MCP is not available
531
- return {
532
- "name": "analyze_code_structure",
533
- "description": "Analyze code structure and generate detailed overview tables (classes, methods, fields) for large files",
534
- "inputSchema": self.get_tool_schema(),
535
- }
521
+ return {
522
+ "name": "analyze_code_structure",
523
+ "description": "Analyze code structure and generate detailed overview tables (classes, methods, fields) with line positions for large files, optionally save to file",
524
+ "inputSchema": self.get_tool_schema(),
525
+ }
536
526
 
537
527
 
538
528
  # Tool instance for easy access
@@ -644,6 +644,26 @@ ALL_QUERIES["objects"] = {
644
644
  }
645
645
  ALL_QUERIES["comments"] = {"query": COMMENTS, "description": "Search all comments"}
646
646
 
647
+ # Add missing method queries
648
+ ALL_QUERIES["method"] = {
649
+ "query": """
650
+ (method_definition
651
+ name: (property_identifier) @method_name
652
+ parameters: (formal_parameters) @parameters
653
+ body: (statement_block) @body) @method_definition
654
+ """,
655
+ "description": "Search method definitions",
656
+ }
657
+ ALL_QUERIES["methods"] = {
658
+ "query": """
659
+ (method_definition
660
+ name: (property_identifier) @method_name
661
+ parameters: (formal_parameters) @parameters
662
+ body: (statement_block) @body) @method_definition
663
+ """,
664
+ "description": "Search method definitions",
665
+ }
666
+
647
667
 
648
668
  def get_javascript_query(name: str) -> str:
649
669
  """
@@ -213,6 +213,28 @@ ALL_QUERIES["methods"] = {
213
213
  "description": "Search all methods declarations (alias for functions)",
214
214
  }
215
215
 
216
+ # Add singular form aliases
217
+ ALL_QUERIES["function"] = {
218
+ "query": FUNCTIONS,
219
+ "description": "Search all functions (alias for functions)",
220
+ }
221
+ ALL_QUERIES["class"] = {
222
+ "query": CLASSES,
223
+ "description": "Search all classes (alias for classes)",
224
+ }
225
+ ALL_QUERIES["interface"] = {
226
+ "query": INTERFACES,
227
+ "description": "Search all interfaces (alias for interfaces)",
228
+ }
229
+ ALL_QUERIES["type"] = {
230
+ "query": TYPE_ALIASES,
231
+ "description": "Search all type aliases (alias for type_aliases)",
232
+ }
233
+ ALL_QUERIES["types"] = {
234
+ "query": TYPE_ALIASES,
235
+ "description": "Search all types (alias for type_aliases)",
236
+ }
237
+
216
238
  # Add more specific function queries
217
239
  ALL_QUERIES["function_declaration"] = {
218
240
  "query": """