tree-sitter-analyzer 0.8.0__py3-none-any.whl → 0.8.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.

Potentially problematic release.


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

@@ -15,6 +15,8 @@ from ...file_handler import read_file_partial
15
15
  from ...language_detector import detect_language_from_file, is_language_supported
16
16
  from ...models import AnalysisResult
17
17
  from ...output_manager import output_error, output_info
18
+ from ...project_detector import detect_project_root
19
+ from ...security import SecurityValidator
18
20
 
19
21
 
20
22
  class BaseCommand(ABC):
@@ -28,18 +30,32 @@ class BaseCommand(ABC):
28
30
  def __init__(self, args: Namespace):
29
31
  """Initialize command with parsed arguments."""
30
32
  self.args = args
31
- self.analysis_engine = get_analysis_engine()
33
+
34
+ # Detect project root with priority handling
35
+ file_path = getattr(args, 'file_path', None)
36
+ explicit_root = getattr(args, 'project_root', None)
37
+ self.project_root = detect_project_root(file_path, explicit_root)
38
+
39
+ # Initialize components with project root
40
+ self.analysis_engine = get_analysis_engine(self.project_root)
41
+ self.security_validator = SecurityValidator(self.project_root)
32
42
 
33
43
  def validate_file(self) -> bool:
34
44
  """Validate input file exists and is accessible."""
35
45
  if not hasattr(self.args, "file_path") or not self.args.file_path:
36
- output_error("ERROR: File path not specified.")
46
+ output_error("File path not specified.")
47
+ return False
48
+
49
+ # Security validation
50
+ is_valid, error_msg = self.security_validator.validate_file_path(self.args.file_path)
51
+ if not is_valid:
52
+ output_error(f"Invalid file path: {error_msg}")
37
53
  return False
38
54
 
39
55
  import os
40
56
 
41
57
  if not os.path.exists(self.args.file_path):
42
- output_error(f"ERROR: File not found: {self.args.file_path}")
58
+ output_error(f"File not found: {self.args.file_path}")
43
59
  return False
44
60
 
45
61
  return True
@@ -47,7 +63,9 @@ class BaseCommand(ABC):
47
63
  def detect_language(self) -> str | None:
48
64
  """Detect or validate the target language."""
49
65
  if hasattr(self.args, "language") and self.args.language:
50
- target_language = self.args.language.lower()
66
+ # Sanitize language input
67
+ sanitized_language = self.security_validator.sanitize_input(self.args.language, max_length=50)
68
+ target_language = sanitized_language.lower()
51
69
  if (not hasattr(self.args, "table") or not self.args.table) and (
52
70
  not hasattr(self.args, "quiet") or not self.args.quiet
53
71
  ):
@@ -94,10 +112,10 @@ class BaseCommand(ABC):
94
112
  end_column=getattr(self.args, "end_column", None),
95
113
  )
96
114
  if partial_content is None:
97
- output_error("ERROR: Failed to read file partially")
115
+ output_error("Failed to read file partially")
98
116
  return None
99
117
  except Exception as e:
100
- output_error(f"ERROR: Failed to read file partially: {e}")
118
+ output_error(f"Failed to read file partially: {e}")
101
119
  return None
102
120
 
103
121
  request = AnalysisRequest(
@@ -114,13 +132,13 @@ class BaseCommand(ABC):
114
132
  if analysis_result
115
133
  else "Unknown error"
116
134
  )
117
- output_error(f"ERROR: Analysis failed: {error_msg}")
135
+ output_error(f"Analysis failed: {error_msg}")
118
136
  return None
119
137
 
120
138
  return analysis_result
121
139
 
122
140
  except Exception as e:
123
- output_error(f"ERROR: An error occurred during analysis: {e}")
141
+ output_error(f"An error occurred during analysis: {e}")
124
142
  return None
125
143
 
126
144
  def execute(self) -> int:
@@ -143,7 +161,7 @@ class BaseCommand(ABC):
143
161
  try:
144
162
  return asyncio.run(self.execute_async(language))
145
163
  except Exception as e:
146
- output_error(f"ERROR: An error occurred during command execution: {e}")
164
+ output_error(f"An error occurred during command execution: {e}")
147
165
  return 1
148
166
 
149
167
  @abstractmethod
@@ -14,5 +14,5 @@ class DefaultCommand(BaseCommand):
14
14
 
15
15
  async def execute_async(self, language: str) -> int:
16
16
  """Execute default command - show error for missing options."""
17
- output_error("ERROR: Please specify a query or --advanced option")
17
+ output_error("Please specify a query or --advanced option")
18
18
  return 1
@@ -28,7 +28,7 @@ class PartialReadCommand(BaseCommand):
28
28
  if not hasattr(self.args, "file_path") or not self.args.file_path:
29
29
  from ...output_manager import output_error
30
30
 
31
- output_error("ERROR: File path not specified.")
31
+ output_error("File path not specified.")
32
32
  return False
33
33
 
34
34
  import os
@@ -36,7 +36,7 @@ class PartialReadCommand(BaseCommand):
36
36
  if not os.path.exists(self.args.file_path):
37
37
  from ...output_manager import output_error
38
38
 
39
- output_error(f"ERROR: File not found: {self.args.file_path}")
39
+ output_error(f"File not found: {self.args.file_path}")
40
40
  return False
41
41
 
42
42
  return True
@@ -56,20 +56,20 @@ class PartialReadCommand(BaseCommand):
56
56
  if not self.args.start_line:
57
57
  from ...output_manager import output_error
58
58
 
59
- output_error("ERROR: --start-line is required")
59
+ output_error("--start-line is required")
60
60
  return 1
61
61
 
62
62
  if self.args.start_line < 1:
63
63
  from ...output_manager import output_error
64
64
 
65
- output_error("ERROR: --start-line must be 1 or greater")
65
+ output_error("--start-line must be 1 or greater")
66
66
  return 1
67
67
 
68
68
  if self.args.end_line and self.args.end_line < self.args.start_line:
69
69
  from ...output_manager import output_error
70
70
 
71
71
  output_error(
72
- "ERROR: --end-line must be greater than or equal to --start-line"
72
+ "--end-line must be greater than or equal to --start-line"
73
73
  )
74
74
  return 1
75
75
 
@@ -86,7 +86,7 @@ class PartialReadCommand(BaseCommand):
86
86
  if partial_content is None:
87
87
  from ...output_manager import output_error
88
88
 
89
- output_error("ERROR: Failed to read file partially")
89
+ output_error("Failed to read file partially")
90
90
  return 1
91
91
 
92
92
  # Output the result
@@ -96,7 +96,7 @@ class PartialReadCommand(BaseCommand):
96
96
  except Exception as e:
97
97
  from ...output_manager import output_error
98
98
 
99
- output_error(f"ERROR: Failed to read file partially: {e}")
99
+ output_error(f"Failed to read file partially: {e}")
100
100
  return 1
101
101
 
102
102
  def _output_partial_content(self, content: str) -> None:
@@ -18,21 +18,28 @@ class QueryCommand(BaseCommand):
18
18
  query_to_execute = None
19
19
 
20
20
  if hasattr(self.args, "query_key") and self.args.query_key:
21
+ # Sanitize query key input
22
+ sanitized_query_key = self.security_validator.sanitize_input(self.args.query_key, max_length=100)
21
23
  try:
22
- query_to_execute = query_loader.get_query(language, self.args.query_key)
24
+ query_to_execute = query_loader.get_query(language, sanitized_query_key)
23
25
  if query_to_execute is None:
24
26
  output_error(
25
- f"ERROR: Query '{self.args.query_key}' not found for language '{language}'"
27
+ f"ERROR: Query '{sanitized_query_key}' not found for language '{language}'"
26
28
  )
27
29
  return 1
28
30
  except ValueError as e:
29
- output_error(f"ERROR: {e}")
31
+ output_error(f"{e}")
30
32
  return 1
31
33
  elif hasattr(self.args, "query_string") and self.args.query_string:
34
+ # Security check for query string (potential regex patterns)
35
+ is_safe, error_msg = self.security_validator.regex_checker.validate_pattern(self.args.query_string)
36
+ if not is_safe:
37
+ output_error(f"Unsafe query pattern: {error_msg}")
38
+ return 1
32
39
  query_to_execute = self.args.query_string
33
40
 
34
41
  if not query_to_execute:
35
- output_error("ERROR: No query specified.")
42
+ output_error("No query specified.")
36
43
  return 1
37
44
 
38
45
  # Perform analysis
@@ -42,7 +42,7 @@ class TableCommand(BaseCommand):
42
42
  return 0
43
43
 
44
44
  except Exception as e:
45
- output_error(f"ERROR: An error occurred during table format analysis: {e}")
45
+ output_error(f"An error occurred during table format analysis: {e}")
46
46
  return 1
47
47
 
48
48
  def _convert_to_structure_format(
@@ -78,7 +78,7 @@ class DescribeQueryCommand(InfoCommand):
78
78
 
79
79
  if query_description is None or query_content is None:
80
80
  output_error(
81
- f"ERROR: Query '{self.args.describe_query}' not found for language '{language}'"
81
+ f"Query '{self.args.describe_query}' not found for language '{language}'"
82
82
  )
83
83
  return 1
84
84
 
@@ -87,7 +87,7 @@ class DescribeQueryCommand(InfoCommand):
87
87
  )
88
88
  output_data(f"Query content:\n{query_content}")
89
89
  except ValueError as e:
90
- output_error(f"ERROR: {e}")
90
+ output_error(f"{e}")
91
91
  return 1
92
92
  return 0
93
93
 
@@ -166,6 +166,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
166
166
  help="Explicitly specify language (auto-detected from extension if omitted)",
167
167
  )
168
168
 
169
+ # Project options
170
+ parser.add_argument(
171
+ "--project-root",
172
+ help="Project root directory for security validation (auto-detected if not specified)",
173
+ )
174
+
169
175
  # Logging options
170
176
  parser.add_argument(
171
177
  "--quiet",
@@ -201,25 +207,25 @@ def handle_special_commands(args: argparse.Namespace) -> int | None:
201
207
  # Validate partial read options
202
208
  if hasattr(args, "partial_read") and args.partial_read:
203
209
  if args.start_line is None:
204
- output_error("ERROR: --start-line is required")
210
+ output_error("--start-line is required")
205
211
  return 1
206
212
 
207
213
  if args.start_line < 1:
208
- output_error("ERROR: --start-line must be 1 or greater")
214
+ output_error("--start-line must be 1 or greater")
209
215
  return 1
210
216
 
211
217
  if args.end_line and args.end_line < args.start_line:
212
218
  output_error(
213
- "ERROR: --end-line must be greater than or equal to --start-line"
219
+ "--end-line must be greater than or equal to --start-line"
214
220
  )
215
221
  return 1
216
222
 
217
223
  if args.start_column is not None and args.start_column < 0:
218
- output_error("ERROR: --start-column must be 0 or greater")
224
+ output_error("--start-column must be 0 or greater")
219
225
  return 1
220
226
 
221
227
  if args.end_column is not None and args.end_column < 0:
222
- output_error("ERROR: --end-column must be 0 or greater")
228
+ output_error("--end-column must be 0 or greater")
223
229
  return 1
224
230
 
225
231
  # Query language commands
@@ -279,9 +285,9 @@ def main() -> None:
279
285
  sys.exit(exit_code)
280
286
  else:
281
287
  if not args.file_path:
282
- output_error("ERROR: File path not specified.")
288
+ output_error("File path not specified.")
283
289
  else:
284
- output_error("ERROR: No executable command specified.")
290
+ output_error("No executable command specified.")
285
291
  parser.print_help()
286
292
  sys.exit(1)
287
293
 
@@ -15,11 +15,12 @@ Roo Code compliance:
15
15
  import hashlib
16
16
  import threading
17
17
  from dataclasses import dataclass
18
- from typing import Any, Optional, Protocol
18
+ from typing import Any, Dict, Optional, Protocol
19
19
 
20
20
  from ..models import AnalysisResult
21
21
  from ..plugins.base import LanguagePlugin as BaseLanguagePlugin
22
22
  from ..plugins.manager import PluginManager
23
+ from ..security import SecurityValidator
23
24
  from ..utils import log_debug, log_error, log_info, log_performance
24
25
  from .cache_service import CacheService
25
26
 
@@ -204,32 +205,41 @@ class UnifiedAnalysisEngine:
204
205
  _performance_monitor: パフォーマンス監視
205
206
  """
206
207
 
207
- _instance: Optional["UnifiedAnalysisEngine"] = None
208
+ _instances: Dict[str, "UnifiedAnalysisEngine"] = {}
208
209
  _lock: threading.Lock = threading.Lock()
209
210
 
210
- def __new__(cls) -> "UnifiedAnalysisEngine":
211
- """シングルトンパターンでインスタンス共有"""
212
- if cls._instance is None:
211
+ def __new__(cls, project_root: str = None) -> "UnifiedAnalysisEngine":
212
+ """シングルトンパターンでインスタンス共有 (project_root aware)"""
213
+ # Create a key based on project_root for different instances
214
+ instance_key = project_root or "default"
215
+
216
+ if instance_key not in cls._instances:
213
217
  with cls._lock:
214
- if cls._instance is None:
215
- cls._instance = super().__new__(cls)
216
- return cls._instance
218
+ if instance_key not in cls._instances:
219
+ instance = super().__new__(cls)
220
+ cls._instances[instance_key] = instance
221
+ # Mark as not initialized for this instance
222
+ instance._initialized = False
217
223
 
218
- def __init__(self) -> None:
224
+ return cls._instances[instance_key]
225
+
226
+ def __init__(self, project_root: str = None) -> None:
219
227
  """初期化(一度のみ実行)"""
220
- if hasattr(self, "_initialized"):
228
+ if hasattr(self, "_initialized") and self._initialized:
221
229
  return
222
230
 
223
231
  self._cache_service = CacheService()
224
232
  self._plugin_manager = PluginManager()
225
233
  self._performance_monitor = PerformanceMonitor()
234
+ self._security_validator = SecurityValidator(project_root)
235
+ self._project_root = project_root
226
236
 
227
237
  # プラグインを自動ロード
228
238
  self._load_plugins()
229
239
 
230
240
  self._initialized = True
231
241
 
232
- log_info("UnifiedAnalysisEngine initialized")
242
+ log_info(f"UnifiedAnalysisEngine initialized with project root: {project_root}")
233
243
 
234
244
  def _load_plugins(self) -> None:
235
245
  """利用可能なプラグインを自動ロード"""
@@ -265,6 +275,12 @@ class UnifiedAnalysisEngine:
265
275
  """
266
276
  log_info(f"Starting analysis for {request.file_path}")
267
277
 
278
+ # Security validation
279
+ is_valid, error_msg = self._security_validator.validate_file_path(request.file_path)
280
+ if not is_valid:
281
+ log_error(f"Security validation failed for file path: {request.file_path} - {error_msg}")
282
+ raise ValueError(f"Invalid file path: {error_msg}")
283
+
268
284
  # キャッシュチェック(CLI・MCP間で共有)
269
285
  cache_key = self._generate_cache_key(request)
270
286
  cached_result = await self._cache_service.get(cache_key)
@@ -323,6 +339,12 @@ class UnifiedAnalysisEngine:
323
339
  Returns:
324
340
  Analysis result
325
341
  """
342
+ # Security validation
343
+ is_valid, error_msg = self._security_validator.validate_file_path(file_path)
344
+ if not is_valid:
345
+ log_error(f"Security validation failed for file path: {file_path} - {error_msg}")
346
+ raise ValueError(f"Invalid file path: {error_msg}")
347
+
326
348
  request = AnalysisRequest(
327
349
  file_path=file_path,
328
350
  language=None, # Auto-detect
@@ -545,11 +567,14 @@ class MockLanguagePlugin:
545
567
  )
546
568
 
547
569
 
548
- def get_analysis_engine() -> UnifiedAnalysisEngine:
570
+ def get_analysis_engine(project_root: str = None) -> UnifiedAnalysisEngine:
549
571
  """
550
572
  統一解析エンジンのインスタンスを取得
551
573
 
574
+ Args:
575
+ project_root: Project root directory for security validation
576
+
552
577
  Returns:
553
578
  統一解析エンジンのシングルトンインスタンス
554
579
  """
555
- return UnifiedAnalysisEngine()
580
+ return UnifiedAnalysisEngine(project_root)
@@ -6,8 +6,10 @@ This module provides the main MCP server that exposes tree-sitter analyzer
6
6
  functionality through the Model Context Protocol.
7
7
  """
8
8
 
9
+ import argparse
9
10
  import asyncio
10
11
  import json
12
+ import os
11
13
  import sys
12
14
  from typing import Any
13
15
 
@@ -43,6 +45,8 @@ except ImportError:
43
45
 
44
46
 
45
47
  from ..core.analysis_engine import get_analysis_engine
48
+ from ..project_detector import detect_project_root
49
+ from ..security import SecurityValidator
46
50
  from ..utils import setup_logger
47
51
  from . import MCP_INFO
48
52
  from .resources import CodeFileResource, ProjectStatsResource
@@ -64,16 +68,17 @@ class TreeSitterAnalyzerMCPServer:
64
68
  integrating with existing analyzer components.
65
69
  """
66
70
 
67
- def __init__(self) -> None:
71
+ def __init__(self, project_root: str = None) -> None:
68
72
  """Initialize the MCP server with analyzer components."""
69
73
  self.server: Server | None = None
70
- self.analysis_engine = get_analysis_engine()
74
+ self.analysis_engine = get_analysis_engine(project_root)
75
+ self.security_validator = SecurityValidator(project_root)
71
76
  # Use unified analysis engine instead of deprecated AdvancedAnalyzer
72
77
 
73
- # Initialize MCP tools
74
- self.read_partial_tool: MCPTool = ReadPartialTool()
75
- self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool()
76
- self.table_format_tool: MCPTool = TableFormatTool()
78
+ # Initialize MCP tools with security validation
79
+ self.read_partial_tool: MCPTool = ReadPartialTool(project_root)
80
+ self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool(project_root)
81
+ self.table_format_tool: MCPTool = TableFormatTool(project_root)
77
82
 
78
83
  # Initialize MCP resources
79
84
  self.code_file_resource = CodeFileResource()
@@ -161,9 +166,25 @@ class TreeSitterAnalyzerMCPServer:
161
166
  async def handle_call_tool(
162
167
  name: str, arguments: dict[str, Any]
163
168
  ) -> list[TextContent]:
164
- """Handle tool calls."""
169
+ """Handle tool calls with security validation."""
165
170
  try:
166
- if name == "analyze_code_scale":
171
+ # Security validation for tool name
172
+ sanitized_name = self.security_validator.sanitize_input(name, max_length=100)
173
+
174
+ # Log tool call for audit
175
+ logger.info(f"MCP tool call: {sanitized_name} with args: {list(arguments.keys())}")
176
+
177
+ # Validate arguments contain no malicious content
178
+ for key, value in arguments.items():
179
+ if isinstance(value, str):
180
+ # Check for potential injection attempts
181
+ if len(value) > 10000: # Prevent extremely large inputs
182
+ raise ValueError(f"Input too large for parameter {key}")
183
+
184
+ # Basic sanitization for string inputs
185
+ sanitized_value = self.security_validator.sanitize_input(value, max_length=10000)
186
+ arguments[key] = sanitized_value
187
+ if sanitized_name == "analyze_code_scale":
167
188
  result = await self._analyze_code_scale(arguments)
168
189
  return [
169
190
  TextContent(
@@ -171,7 +192,7 @@ class TreeSitterAnalyzerMCPServer:
171
192
  text=json.dumps(result, indent=2, ensure_ascii=False),
172
193
  )
173
194
  ]
174
- elif name == "read_code_partial":
195
+ elif sanitized_name == "read_code_partial":
175
196
  result = await self.read_partial_tool.execute(arguments)
176
197
  return [
177
198
  TextContent(
@@ -179,7 +200,7 @@ class TreeSitterAnalyzerMCPServer:
179
200
  text=json.dumps(result, indent=2, ensure_ascii=False),
180
201
  )
181
202
  ]
182
- elif name == "format_table":
203
+ elif sanitized_name == "format_table":
183
204
  result = await self.table_format_tool.execute(arguments)
184
205
  return [
185
206
  TextContent(
@@ -187,7 +208,7 @@ class TreeSitterAnalyzerMCPServer:
187
208
  text=json.dumps(result, indent=2, ensure_ascii=False),
188
209
  )
189
210
  ]
190
- elif name == "analyze_code_universal":
211
+ elif sanitized_name == "analyze_code_universal":
191
212
  result = await self.universal_analyze_tool.execute(arguments)
192
213
  return [
193
214
  TextContent(
@@ -318,10 +339,51 @@ class TreeSitterAnalyzerMCPServer:
318
339
  pass # Silently ignore logging errors during shutdown
319
340
 
320
341
 
342
+ def parse_mcp_args(args=None) -> argparse.Namespace:
343
+ """Parse command line arguments for MCP server."""
344
+ parser = argparse.ArgumentParser(
345
+ description="Tree-sitter Analyzer MCP Server",
346
+ formatter_class=argparse.RawDescriptionHelpFormatter,
347
+ epilog="""
348
+ Environment Variables:
349
+ TREE_SITTER_PROJECT_ROOT Project root directory (alternative to --project-root)
350
+
351
+ Examples:
352
+ python -m tree_sitter_analyzer.mcp.server
353
+ python -m tree_sitter_analyzer.mcp.server --project-root /path/to/project
354
+ """
355
+ )
356
+
357
+ parser.add_argument(
358
+ "--project-root",
359
+ help="Project root directory for security validation (auto-detected if not specified)"
360
+ )
361
+
362
+ return parser.parse_args(args)
363
+
364
+
321
365
  async def main() -> None:
322
366
  """Main entry point for the MCP server."""
323
367
  try:
324
- server = TreeSitterAnalyzerMCPServer()
368
+ # Parse command line arguments (empty list for testing)
369
+ args = parse_mcp_args([])
370
+
371
+ # Determine project root with priority handling
372
+ project_root = None
373
+
374
+ # Priority 1: Command line argument
375
+ if args.project_root:
376
+ project_root = args.project_root
377
+ # Priority 2: Environment variable
378
+ elif os.getenv('TREE_SITTER_PROJECT_ROOT'):
379
+ project_root = os.getenv('TREE_SITTER_PROJECT_ROOT')
380
+ # Priority 3: Auto-detection from current directory
381
+ else:
382
+ project_root = detect_project_root()
383
+
384
+ logger.info(f"MCP server starting with project root: {project_root}")
385
+
386
+ server = TreeSitterAnalyzerMCPServer(project_root)
325
387
  await server.run()
326
388
  except KeyboardInterrupt:
327
389
  try:
@@ -13,6 +13,7 @@ from typing import Any
13
13
 
14
14
  from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
15
15
  from ...language_detector import detect_language_from_file
16
+ from ...security import SecurityValidator
16
17
  from ...utils import setup_logger
17
18
 
18
19
  # Set up logging
@@ -28,11 +29,13 @@ class AnalyzeScaleTool:
28
29
  for LLM workflow efficiency.
29
30
  """
30
31
 
31
- def __init__(self) -> None:
32
+ def __init__(self, project_root: str = None) -> None:
32
33
  """Initialize the analyze scale tool."""
33
34
  # Use unified analysis engine instead of deprecated AdvancedAnalyzer
34
- self.analysis_engine = get_analysis_engine()
35
- logger.info("AnalyzeScaleTool initialized")
35
+ self.project_root = project_root
36
+ self.analysis_engine = get_analysis_engine(project_root)
37
+ self.security_validator = SecurityValidator(project_root)
38
+ logger.info("AnalyzeScaleTool initialized with security validation")
36
39
 
37
40
  def _calculate_file_metrics(self, file_path: str) -> dict[str, Any]:
38
41
  """
@@ -251,6 +254,12 @@ class AnalyzeScaleTool:
251
254
  if total_lines > 200:
252
255
  guidance["recommended_tools"].append("read_code_partial")
253
256
 
257
+ # Ensure all required fields exist
258
+ required_fields = ["complexity_hotspots", "classes", "methods", "fields", "imports"]
259
+ for field in required_fields:
260
+ if field not in structural_overview:
261
+ structural_overview[field] = []
262
+
254
263
  if len(structural_overview["complexity_hotspots"]) > 0:
255
264
  guidance["recommended_tools"].append("format_table")
256
265
  guidance["complexity_assessment"] = (
@@ -339,6 +348,16 @@ class AnalyzeScaleTool:
339
348
  include_details = arguments.get("include_details", False)
340
349
  include_guidance = arguments.get("include_guidance", True)
341
350
 
351
+ # Security validation
352
+ is_valid, error_msg = self.security_validator.validate_file_path(file_path)
353
+ if not is_valid:
354
+ logger.warning(f"Security validation failed for file path: {file_path} - {error_msg}")
355
+ raise ValueError(f"Invalid file path: {error_msg}")
356
+
357
+ # Sanitize inputs
358
+ if language:
359
+ language = self.security_validator.sanitize_input(language, max_length=50)
360
+
342
361
  # Validate file exists
343
362
  if not Path(file_path).exists():
344
363
  raise FileNotFoundError(f"File not found: {file_path}")
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  from typing import Any
12
12
 
13
13
  from ...file_handler import read_file_partial
14
+ from ...security import SecurityValidator
14
15
  from ...utils import setup_logger
15
16
 
16
17
  # Set up logging
@@ -25,9 +26,10 @@ class ReadPartialTool:
25
26
  selective file content reading through the MCP protocol.
26
27
  """
27
28
 
28
- def __init__(self) -> None:
29
+ def __init__(self, project_root: str = None) -> None:
29
30
  """Initialize the read partial tool."""
30
- logger.info("ReadPartialTool initialized")
31
+ self.security_validator = SecurityValidator(project_root)
32
+ logger.info("ReadPartialTool initialized with security validation")
31
33
 
32
34
  def get_tool_schema(self) -> dict[str, Any]:
33
35
  """
@@ -102,6 +104,12 @@ class ReadPartialTool:
102
104
  end_column = arguments.get("end_column")
103
105
  # output_format = arguments.get("format", "text") # Not used currently
104
106
 
107
+ # Security validation
108
+ is_valid, error_msg = self.security_validator.validate_file_path(file_path)
109
+ if not is_valid:
110
+ logger.warning(f"Security validation failed for file path: {file_path} - {error_msg}")
111
+ raise ValueError(f"Invalid file path: {error_msg}")
112
+
105
113
  # Validate file exists
106
114
  if not Path(file_path).exists():
107
115
  raise FileNotFoundError(f"File not found: {file_path}")
@@ -11,6 +11,7 @@ from typing import Any
11
11
 
12
12
  from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
13
13
  from ...language_detector import detect_language_from_file
14
+ from ...security import SecurityValidator
14
15
  from ...table_formatter import TableFormatter
15
16
  from ...utils import setup_logger
16
17
  from ..utils import get_performance_monitor
@@ -28,11 +29,13 @@ class TableFormatTool:
28
29
  the CLI --table=full option.
29
30
  """
30
31
 
31
- def __init__(self) -> None:
32
+ def __init__(self, project_root: str = None) -> None:
32
33
  """Initialize the table format tool."""
33
34
  self.logger = logger
34
- self.analysis_engine = get_analysis_engine()
35
- logger.info("TableFormatTool initialized")
35
+ self.project_root = project_root
36
+ self.analysis_engine = get_analysis_engine(project_root)
37
+ self.security_validator = SecurityValidator(project_root)
38
+ logger.info("TableFormatTool initialized with security validation")
36
39
 
37
40
  def get_tool_schema(self) -> dict[str, Any]:
38
41
  """
@@ -268,6 +271,20 @@ class TableFormatTool:
268
271
  format_type = args.get("format_type", "full")
269
272
  language = args.get("language")
270
273
 
274
+ # Security validation
275
+ is_valid, error_msg = self.security_validator.validate_file_path(file_path)
276
+ if not is_valid:
277
+ self.logger.warning(f"Security validation failed for file path: {file_path} - {error_msg}")
278
+ raise ValueError(f"Invalid file path: {error_msg}")
279
+
280
+ # Sanitize format_type input
281
+ if format_type:
282
+ format_type = self.security_validator.sanitize_input(format_type, max_length=50)
283
+
284
+ # Sanitize language input
285
+ if language:
286
+ language = self.security_validator.sanitize_input(language, max_length=50)
287
+
271
288
  # Validate file exists
272
289
  if not Path(file_path).exists():
273
290
  raise FileNotFoundError(f"File not found: {file_path}")
@@ -12,6 +12,7 @@ from typing import Any
12
12
 
13
13
  from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
14
14
  from ...language_detector import detect_language_from_file, is_language_supported
15
+ from ...security import SecurityValidator
15
16
  from ..utils import get_performance_monitor
16
17
  from ..utils.error_handler import handle_mcp_errors
17
18
 
@@ -26,10 +27,13 @@ class UniversalAnalyzeTool:
26
27
  the appropriate analyzer to provide comprehensive code analysis.
27
28
  """
28
29
 
29
- def __init__(self) -> None:
30
+ def __init__(self, project_root: str = None) -> None:
30
31
  """Initialize the universal analysis tool"""
31
32
  # Use unified analysis engine instead of deprecated AdvancedAnalyzer
32
- self.analysis_engine = get_analysis_engine()
33
+ self.project_root = project_root
34
+ self.analysis_engine = get_analysis_engine(project_root)
35
+ self.security_validator = SecurityValidator(project_root)
36
+ logger.info("UniversalAnalyzeTool initialized with security validation")
33
37
 
34
38
  def get_tool_definition(self) -> dict[str, Any]:
35
39
  """
@@ -96,6 +100,18 @@ class UniversalAnalyzeTool:
96
100
  file_path = arguments["file_path"]
97
101
  language = arguments.get("language")
98
102
  analysis_type = arguments.get("analysis_type", "basic")
103
+
104
+ # Security validation
105
+ is_valid, error_msg = self.security_validator.validate_file_path(file_path)
106
+ if not is_valid:
107
+ logger.warning(f"Security validation failed for file path: {file_path} - {error_msg}")
108
+ raise ValueError(f"Invalid file path: {error_msg}")
109
+
110
+ # Sanitize inputs
111
+ if language:
112
+ language = self.security_validator.sanitize_input(language, max_length=50)
113
+ if analysis_type:
114
+ analysis_type = self.security_validator.sanitize_input(analysis_type, max_length=50)
99
115
  include_ast = arguments.get("include_ast", False)
100
116
  include_queries = arguments.get("include_queries", False)
101
117
 
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Project Root Detection
4
+
5
+ Intelligent detection of project root directories based on common project markers.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Optional, List, Tuple
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Common project root indicators (in priority order)
16
+ PROJECT_MARKERS = [
17
+ # Version control
18
+ '.git',
19
+ '.hg',
20
+ '.svn',
21
+
22
+ # Python projects
23
+ 'pyproject.toml',
24
+ 'setup.py',
25
+ 'setup.cfg',
26
+ 'requirements.txt',
27
+ 'Pipfile',
28
+ 'poetry.lock',
29
+ 'conda.yaml',
30
+ 'environment.yml',
31
+
32
+ # JavaScript/Node.js projects
33
+ 'package.json',
34
+ 'package-lock.json',
35
+ 'yarn.lock',
36
+ 'node_modules',
37
+
38
+ # Java projects
39
+ 'pom.xml',
40
+ 'build.gradle',
41
+ 'build.gradle.kts',
42
+ 'gradlew',
43
+ 'mvnw',
44
+
45
+ # C/C++ projects
46
+ 'CMakeLists.txt',
47
+ 'Makefile',
48
+ 'configure.ac',
49
+ 'configure.in',
50
+
51
+ # Rust projects
52
+ 'Cargo.toml',
53
+ 'Cargo.lock',
54
+
55
+ # Go projects
56
+ 'go.mod',
57
+ 'go.sum',
58
+
59
+ # .NET projects
60
+ '*.sln',
61
+ '*.csproj',
62
+ '*.vbproj',
63
+ '*.fsproj',
64
+
65
+ # Other common markers
66
+ 'README.md',
67
+ 'README.rst',
68
+ 'README.txt',
69
+ 'LICENSE',
70
+ 'CHANGELOG.md',
71
+ '.gitignore',
72
+ '.dockerignore',
73
+ 'Dockerfile',
74
+ 'docker-compose.yml',
75
+ '.editorconfig',
76
+ ]
77
+
78
+
79
+ class ProjectRootDetector:
80
+ """Intelligent project root directory detection."""
81
+
82
+ def __init__(self, max_depth: int = 10):
83
+ """
84
+ Initialize project root detector.
85
+
86
+ Args:
87
+ max_depth: Maximum directory levels to traverse upward
88
+ """
89
+ self.max_depth = max_depth
90
+
91
+ def detect_from_file(self, file_path: str) -> Optional[str]:
92
+ """
93
+ Detect project root from a file path.
94
+
95
+ Args:
96
+ file_path: Path to a file within the project
97
+
98
+ Returns:
99
+ Project root directory path, or None if not detected
100
+ """
101
+ if not file_path:
102
+ return None
103
+
104
+ try:
105
+ # Convert to absolute path and get directory
106
+ abs_path = os.path.abspath(file_path)
107
+ if os.path.isfile(abs_path):
108
+ start_dir = os.path.dirname(abs_path)
109
+ else:
110
+ start_dir = abs_path
111
+
112
+ return self._traverse_upward(start_dir)
113
+
114
+ except Exception as e:
115
+ logger.warning(f"Error detecting project root from {file_path}: {e}")
116
+ return None
117
+
118
+ def detect_from_cwd(self) -> Optional[str]:
119
+ """
120
+ Detect project root from current working directory.
121
+
122
+ Returns:
123
+ Project root directory path, or None if not detected
124
+ """
125
+ try:
126
+ return self._traverse_upward(os.getcwd())
127
+ except Exception as e:
128
+ logger.warning(f"Error detecting project root from cwd: {e}")
129
+ return None
130
+
131
+ def _traverse_upward(self, start_dir: str) -> Optional[str]:
132
+ """
133
+ Traverse upward from start directory looking for project markers.
134
+
135
+ Args:
136
+ start_dir: Directory to start traversal from
137
+
138
+ Returns:
139
+ Project root directory path, or None if not found
140
+ """
141
+ current_dir = os.path.abspath(start_dir)
142
+ candidates = []
143
+
144
+ for depth in range(self.max_depth):
145
+ # Check for project markers in current directory
146
+ markers_found = self._find_markers_in_dir(current_dir)
147
+
148
+ if markers_found:
149
+ # Calculate score based on marker priority and count
150
+ score = self._calculate_score(markers_found)
151
+ candidates.append((current_dir, score, markers_found))
152
+
153
+ # If we find high-priority markers, we can stop early
154
+ if any(marker in ['.git', 'pyproject.toml', 'package.json', 'pom.xml', 'Cargo.toml', 'go.mod']
155
+ for marker in markers_found):
156
+ logger.debug(f"Found high-priority project root: {current_dir} (markers: {markers_found})")
157
+ return current_dir
158
+
159
+ # Move up one directory
160
+ parent_dir = os.path.dirname(current_dir)
161
+ if parent_dir == current_dir: # Reached filesystem root
162
+ break
163
+ current_dir = parent_dir
164
+
165
+ # Return the best candidate if any found
166
+ if candidates:
167
+ # Sort by score (descending) and return the best
168
+ candidates.sort(key=lambda x: x[1], reverse=True)
169
+ best_candidate = candidates[0]
170
+ logger.debug(f"Selected project root: {best_candidate[0]} (score: {best_candidate[1]}, markers: {best_candidate[2]})")
171
+ return best_candidate[0]
172
+
173
+ logger.debug(f"No project root detected from {start_dir}")
174
+ return None
175
+
176
+ def _find_markers_in_dir(self, directory: str) -> List[str]:
177
+ """
178
+ Find project markers in a directory.
179
+
180
+ Args:
181
+ directory: Directory to search in
182
+
183
+ Returns:
184
+ List of found marker names
185
+ """
186
+ found_markers = []
187
+
188
+ try:
189
+ dir_contents = os.listdir(directory)
190
+
191
+ for marker in PROJECT_MARKERS:
192
+ if '*' in marker:
193
+ # Handle glob patterns
194
+ import glob
195
+ pattern = os.path.join(directory, marker)
196
+ if glob.glob(pattern):
197
+ found_markers.append(marker)
198
+ else:
199
+ # Handle exact matches
200
+ if marker in dir_contents:
201
+ found_markers.append(marker)
202
+
203
+ except (OSError, PermissionError) as e:
204
+ logger.debug(f"Cannot access directory {directory}: {e}")
205
+
206
+ return found_markers
207
+
208
+ def _calculate_score(self, markers: List[str]) -> int:
209
+ """
210
+ Calculate a score for project root candidates based on markers found.
211
+
212
+ Args:
213
+ markers: List of found markers
214
+
215
+ Returns:
216
+ Score (higher is better)
217
+ """
218
+ score = 0
219
+
220
+ # High-priority markers
221
+ high_priority = ['.git', 'pyproject.toml', 'package.json', 'pom.xml', 'Cargo.toml', 'go.mod']
222
+ medium_priority = ['setup.py', 'requirements.txt', 'CMakeLists.txt', 'Makefile']
223
+
224
+ for marker in markers:
225
+ if marker in high_priority:
226
+ score += 100
227
+ elif marker in medium_priority:
228
+ score += 50
229
+ else:
230
+ score += 10
231
+
232
+ # Bonus for multiple markers
233
+ if len(markers) > 1:
234
+ score += len(markers) * 5
235
+
236
+ return score
237
+
238
+ def get_fallback_root(self, file_path: str) -> str:
239
+ """
240
+ Get fallback project root when detection fails.
241
+
242
+ Args:
243
+ file_path: Original file path
244
+
245
+ Returns:
246
+ Fallback directory (file's directory or cwd)
247
+ """
248
+ try:
249
+ if file_path and os.path.exists(file_path):
250
+ if os.path.isfile(file_path):
251
+ return os.path.dirname(os.path.abspath(file_path))
252
+ else:
253
+ return os.path.abspath(file_path)
254
+ else:
255
+ return os.getcwd()
256
+ except Exception:
257
+ return os.getcwd()
258
+
259
+
260
+ def detect_project_root(file_path: Optional[str] = None,
261
+ explicit_root: Optional[str] = None) -> str:
262
+ """
263
+ Unified project root detection with priority handling.
264
+
265
+ Priority order:
266
+ 1. explicit_root parameter (highest priority)
267
+ 2. Auto-detection from file_path
268
+ 3. Auto-detection from current working directory
269
+ 4. Fallback to file directory or cwd
270
+
271
+ Args:
272
+ file_path: Path to a file within the project
273
+ explicit_root: Explicitly specified project root
274
+
275
+ Returns:
276
+ Project root directory path
277
+ """
278
+ detector = ProjectRootDetector()
279
+
280
+ # Priority 1: Explicit root
281
+ if explicit_root:
282
+ if os.path.exists(explicit_root) and os.path.isdir(explicit_root):
283
+ logger.info(f"Using explicit project root: {explicit_root}")
284
+ return os.path.abspath(explicit_root)
285
+ else:
286
+ logger.warning(f"Explicit project root does not exist: {explicit_root}")
287
+
288
+ # Priority 2: Auto-detection from file path
289
+ if file_path:
290
+ detected_root = detector.detect_from_file(file_path)
291
+ if detected_root:
292
+ logger.info(f"Auto-detected project root from file: {detected_root}")
293
+ return detected_root
294
+
295
+ # Priority 3: Auto-detection from cwd
296
+ detected_root = detector.detect_from_cwd()
297
+ if detected_root:
298
+ logger.info(f"Auto-detected project root from cwd: {detected_root}")
299
+ return detected_root
300
+
301
+ # Priority 4: Fallback
302
+ fallback_root = detector.get_fallback_root(file_path)
303
+ logger.info(f"Using fallback project root: {fallback_root}")
304
+ return fallback_root
305
+
306
+
307
+ if __name__ == "__main__":
308
+ # Test the detector
309
+ import sys
310
+
311
+ if len(sys.argv) > 1:
312
+ test_path = sys.argv[1]
313
+ result = detect_project_root(test_path)
314
+ print(f"Project root for '{test_path}': {result}")
315
+ else:
316
+ result = detect_project_root()
317
+ print(f"Project root from cwd: {result}")
@@ -76,13 +76,24 @@ class SecurityValidator:
76
76
  log_warning(f"Null byte detected in file path: {file_path}")
77
77
  return False, "File path contains null bytes"
78
78
 
79
- # Layer 3: Windows drive letter check (before absolute path check)
80
- if len(file_path) > 1 and file_path[1] == ":":
81
- return False, "Windows drive letters are not allowed"
79
+ # Layer 3: Windows drive letter check (only on non-Windows systems)
80
+ if len(file_path) > 1 and file_path[1] == ":" and os.name != 'nt':
81
+ return False, "Windows drive letters are not allowed on this system"
82
82
 
83
- # Layer 4: Absolute path rejection
83
+ # Layer 4: Absolute path check
84
84
  if os.path.isabs(file_path):
85
- return False, "Absolute file paths are not allowed"
85
+ # If we have a project root, check if the absolute path is within it
86
+ if self.boundary_manager and self.boundary_manager.project_root:
87
+ if not self.boundary_manager.is_within_project(file_path):
88
+ return False, "Absolute path must be within project directory"
89
+ else:
90
+ # In test environments (temp directories), allow absolute paths
91
+ import tempfile
92
+ temp_dir = tempfile.gettempdir()
93
+ if file_path.startswith(temp_dir):
94
+ return True, ""
95
+ # No project root defined, reject all other absolute paths
96
+ return False, "Absolute file paths are not allowed"
86
97
 
87
98
  # Layer 5: Path normalization and traversal check
88
99
  norm_path = os.path.normpath(file_path)
@@ -179,11 +190,17 @@ class SecurityValidator:
179
190
 
180
191
  # Remove null bytes and control characters
181
192
  sanitized = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', user_input)
182
-
193
+
194
+ # Remove HTML/XML tags for XSS prevention
195
+ sanitized = re.sub(r'<[^>]*>', '', sanitized)
196
+
197
+ # Remove potentially dangerous characters
198
+ sanitized = re.sub(r'[<>"\']', '', sanitized)
199
+
183
200
  # Log if sanitization occurred
184
201
  if sanitized != user_input:
185
202
  log_warning("Input sanitization performed")
186
-
203
+
187
204
  return sanitized
188
205
 
189
206
  def validate_glob_pattern(self, pattern: str) -> Tuple[bool, str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tree-sitter-analyzer
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Extensible multi-language code analyzer framework using Tree-sitter with dynamic plugin architecture
5
5
  Project-URL: Homepage, https://github.com/aimasteracc/tree-sitter-analyzer
6
6
  Project-URL: Documentation, https://github.com/aimasteracc/tree-sitter-analyzer#readme
@@ -306,9 +306,32 @@ uv sync --extra all --extra mcp
306
306
 
307
307
  - **[MCP Setup Guide for Users](https://github.com/aimasteracc/tree-sitter-analyzer/blob/main/MCP_SETUP_USERS.md)** - Simple setup for AI assistant users
308
308
  - **[MCP Setup Guide for Developers](https://github.com/aimasteracc/tree-sitter-analyzer/blob/main/MCP_SETUP_DEVELOPERS.md)** - Local development configuration
309
+ - **[Project Root Configuration](https://github.com/aimasteracc/tree-sitter-analyzer/blob/main/PROJECT_ROOT_CONFIG.md)** - Complete configuration reference
309
310
  - **[API Documentation](https://github.com/aimasteracc/tree-sitter-analyzer/blob/main/docs/api.md)** - Detailed API reference
310
311
  - **[Contributing Guide](https://github.com/aimasteracc/tree-sitter-analyzer/blob/main/CONTRIBUTING.md)** - How to contribute
311
312
 
313
+ ### 🔒 Project Root Configuration
314
+
315
+ Tree-sitter-analyzer automatically detects and secures your project boundaries:
316
+
317
+ - **Auto-detection**: Finds project root from `.git`, `pyproject.toml`, `package.json`, etc.
318
+ - **CLI**: Use `--project-root /path/to/project` for explicit control
319
+ - **MCP**: Set `TREE_SITTER_PROJECT_ROOT=${workspaceFolder}` for workspace integration
320
+ - **Security**: Only analyzes files within project boundaries
321
+
322
+ **Recommended MCP configuration:**
323
+ ```json
324
+ {
325
+ "mcpServers": {
326
+ "tree-sitter-analyzer": {
327
+ "command": "uv",
328
+ "args": ["run", "--with", "tree-sitter-analyzer[mcp]", "python", "-m", "tree_sitter_analyzer.mcp.server"],
329
+ "env": {"TREE_SITTER_PROJECT_ROOT": "${workspaceFolder}"}
330
+ }
331
+ }
332
+ }
333
+ ```
334
+
312
335
  ## 🧪 Testing
313
336
 
314
337
  This project maintains high code quality with **1126 passing tests**.
@@ -1,7 +1,7 @@
1
1
  tree_sitter_analyzer/__init__.py,sha256=T8urKvmHQaqEoh_-jKgNJOb2Snz2ySjMqYWE53vLKKA,3199
2
2
  tree_sitter_analyzer/__main__.py,sha256=ilhMPpn_ar28oelzxLfQcX6WH_UbQ2euxiSoV3z_yCg,239
3
3
  tree_sitter_analyzer/api.py,sha256=_94HoE1LKGELSE6FpZ6pEqm2R7qfoPokyfpGSjawliQ,17487
4
- tree_sitter_analyzer/cli_main.py,sha256=bJP7CUHMSSgbPrze7PmOYsVXJ1z1jfhaP0nIVkU7dRw,9581
4
+ tree_sitter_analyzer/cli_main.py,sha256=ses68m5tLoYMP6Co3Fk2vqBACuFd38MqF85uEoa0mbw,9714
5
5
  tree_sitter_analyzer/encoding_utils.py,sha256=C5DpH2-qkAKfsJeSGNHbhOCy4bmn46X6rUw5xPpki34,14938
6
6
  tree_sitter_analyzer/exceptions.py,sha256=xO_U6JuJ4QPkmZoXL_3nmV9QUbTa7-hrI05VAuo5r-Y,12093
7
7
  tree_sitter_analyzer/file_handler.py,sha256=vl4bGx-OgC6Lq63FEZNu2XCXNM0iDTmpNCRTK2msP3U,7104
@@ -9,23 +9,24 @@ tree_sitter_analyzer/language_detector.py,sha256=IjkYF1E7_TtWlwYjz780ZUJAyPltL2a
9
9
  tree_sitter_analyzer/language_loader.py,sha256=gdLxkSoajm-q7c1vcvFONtBf5XJRgasUVI4L0wMzra0,8124
10
10
  tree_sitter_analyzer/models.py,sha256=z0aqdZOVA8rYWF0143TSAUoCvncVRLZ1O70eAjV87gU,16564
11
11
  tree_sitter_analyzer/output_manager.py,sha256=eiBOSL2vUUQi1ghYBr4gwT7aOYC2WTgIoISBZlXkzPo,8399
12
+ tree_sitter_analyzer/project_detector.py,sha256=tB5giHVa_jvPv44l0gv7u265Ih28Fw2ADKUEkb1YFnk,9565
12
13
  tree_sitter_analyzer/query_loader.py,sha256=NilC2XmmhYrBL6ONlzRGlehGa23C_4V6nDVap6YG8v0,10120
13
14
  tree_sitter_analyzer/table_formatter.py,sha256=BfrAouAr3r6MD9xY9yhHw_PwD0aJ4BQo5p1UFhorT5k,27284
14
15
  tree_sitter_analyzer/utils.py,sha256=Pq_2vlDPul8jean0PwlQ_XC-RDjkuaUbwoXp2ls7dV8,8268
15
16
  tree_sitter_analyzer/cli/__init__.py,sha256=swCjWlrPEVIKGznqM_BPxbNvd_0Qz5r1_RmZ-j6EWIU,910
16
17
  tree_sitter_analyzer/cli/__main__.py,sha256=xgCuvLv5NNeEsxKM40pF_7b1apgj3DZ4ECa-xcbLKWc,230
17
- tree_sitter_analyzer/cli/info_commands.py,sha256=z6JhXO1Sm983dHj0uxNVzycWBYz1JIGVCOi0ukSAyTM,4419
18
+ tree_sitter_analyzer/cli/info_commands.py,sha256=0x_6mfMq7jpKBLT9jzhTikXcs0n4TzNEV2Te9dyKNd4,4405
18
19
  tree_sitter_analyzer/cli/commands/__init__.py,sha256=qLtJ7rRge-Reu4aZbczn_jmUHQNQ4lEAsve9BZYHYd0,697
19
20
  tree_sitter_analyzer/cli/commands/advanced_command.py,sha256=YJGrFBEqFPpS0VB-o28Un89Cjwr-eTirNdcFLP4rlN8,3512
20
- tree_sitter_analyzer/cli/commands/base_command.py,sha256=LNRTNYMpAuSAwyxM7rtA1oZcvWPMaAHXsBdv--FOGgw,5947
21
- tree_sitter_analyzer/cli/commands/default_command.py,sha256=-bJJC3Klie7EU3B3n_YXUYKAUSBzC2XUHP3XOnwENvQ,549
22
- tree_sitter_analyzer/cli/commands/partial_read_command.py,sha256=D8dY3CUaNn5EEPgrJNR4e7IClfJal7zenlbWxZcHwPQ,4825
23
- tree_sitter_analyzer/cli/commands/query_command.py,sha256=5SsxwJQInS0YQz9W6ZD_P88-wekfl7VKN_Utlm47YTk,3200
21
+ tree_sitter_analyzer/cli/commands/base_command.py,sha256=0CyODjCOWahH2x-PdeirxrKJMBNzTeRkfPvPuimhIXA,6770
22
+ tree_sitter_analyzer/cli/commands/default_command.py,sha256=R9_GuI5KVYPK2DfXRuG8L89vwxv0QVW8sur_sigjZKo,542
23
+ tree_sitter_analyzer/cli/commands/partial_read_command.py,sha256=kD3E2f1zCseSKpGQ3bgHnEuCq-DCPRQrT91JJJh8B4Q,4776
24
+ tree_sitter_analyzer/cli/commands/query_command.py,sha256=5GlctGaJIc_3AGdISMNFJkbGB3hx6YixYBnKK8VmsF0,3647
24
25
  tree_sitter_analyzer/cli/commands/structure_command.py,sha256=u-NKm06CLgx4srdK5bVo7WtcV4dArA7WYWQWmeXcWMs,5358
25
26
  tree_sitter_analyzer/cli/commands/summary_command.py,sha256=X3pLK7t2ma4SDlG7yYsaFX6bQ4OVUrHv8OWDfgTMNMw,3703
26
- tree_sitter_analyzer/cli/commands/table_command.py,sha256=LuQWeWqCxOArrINcDY9fnXnFyWtESKpffwg_oMQByGw,9683
27
+ tree_sitter_analyzer/cli/commands/table_command.py,sha256=BAIw26WRi_yXbKvkuV7tXFKzSiWvYKVzRUxAcgsJ7VQ,9676
27
28
  tree_sitter_analyzer/core/__init__.py,sha256=Um_BRFICWihZybxoAR6Ck32gJ42ZatkBoZR18XGl9FQ,455
28
- tree_sitter_analyzer/core/analysis_engine.py,sha256=pWyvdYOlTHyLTBcV90yg-yaWQRRi7MWbXTGhoylpzQ0,19122
29
+ tree_sitter_analyzer/core/analysis_engine.py,sha256=Hp72NvmnWduDI1bn7ArKOT1ho9o2_0RZqj_lU_UMqjQ,20493
29
30
  tree_sitter_analyzer/core/cache_service.py,sha256=lTFhGsmuGWgEauxtk2pz_1h5Z945456CaQILfreS5Rw,9944
30
31
  tree_sitter_analyzer/core/engine.py,sha256=6NRPBlN1GvFDtSh8hDJ8udKLC7IOjvoCPetuM7MPrnw,19287
31
32
  tree_sitter_analyzer/core/parser.py,sha256=07vL-mESeMsaIrQqAg-3sr9MLWYVdzT5RyBDO1AkBh0,9586
@@ -45,17 +46,17 @@ tree_sitter_analyzer/languages/java_plugin.py,sha256=o_9F_anKCemnUDV6hq28RatRmBm
45
46
  tree_sitter_analyzer/languages/javascript_plugin.py,sha256=9al0ScXmM5Y8Xl82oNp7cUaU9P59eNCJCPXSlfea4u8,16290
46
47
  tree_sitter_analyzer/languages/python_plugin.py,sha256=nlVxDx6thOB5o6QfQzGbD7gph3_YuM32YYzqYZoHlMw,29899
47
48
  tree_sitter_analyzer/mcp/__init__.py,sha256=mL_XjEks3tJOGAl9ULs_09KQOH1BWi92yvXpBidwmlI,752
48
- tree_sitter_analyzer/mcp/server.py,sha256=uXV4hUroFRnHTP8_axkKWh2OkN3o6OwNxS-BjlrKMfA,12937
49
+ tree_sitter_analyzer/mcp/server.py,sha256=7p5LdihdLMl0BH_u6vGk1VDLTiycLzAOeBsxaug4LrY,15669
49
50
  tree_sitter_analyzer/mcp/resources/__init__.py,sha256=PHDvZyHZawoToDQVqrepsmcTk00ZlaTsu6uxwVjoa4A,1433
50
51
  tree_sitter_analyzer/mcp/resources/code_file_resource.py,sha256=MDHvJl6akElHtcxlN6eCcY5WYSjQEQFCyhAVGiPGk9s,6462
51
52
  tree_sitter_analyzer/mcp/resources/project_stats_resource.py,sha256=lZF9TGxjKvTwPyuWE_o3I3V4LK0zEj3lab4L0Iq-hho,19758
52
53
  tree_sitter_analyzer/mcp/tools/__init__.py,sha256=RMvJOzfZMVe24WUNWJJ-pdygc1RbEVrhW5NZwpykDoQ,792
53
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py,sha256=ZVNEghqKFOcLNZKsv9cJI19t8ZqcgcPp_bXiNkfoWps,26997
54
+ tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py,sha256=yI33yev1W-MztyjiPlSX4uwCcFigRpzdHloXNCXAQz8,27938
54
55
  tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py,sha256=Ie1yeGTFNxuEeTLgXVnKEdKktoMEV27ychIMVkStRY8,9244
55
56
  tree_sitter_analyzer/mcp/tools/base_tool.py,sha256=szW84sSYejzRyBlFbskOARQbsfc2JLwHmjZ6rJZ8SQA,1264
56
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py,sha256=aWBfJXDbjUpiu9xx_-89Js_GebB468mUyupAwT8oA5E,11035
57
- tree_sitter_analyzer/mcp/tools/table_format_tool.py,sha256=O9bxfwGNNjTNpBN5HJ5iTgKALoMTZ2HSYRryBZxaQu0,14623
58
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py,sha256=1UslG_OpB9r1upX1xPYUSquRLYSVzglV8XgO3V2RJjs,21328
57
+ tree_sitter_analyzer/mcp/tools/read_partial_tool.py,sha256=Hjfl1-b0BVsT-g6zr0-pxXA0T1tKaE0iLJZFMm-fxRI,11505
58
+ tree_sitter_analyzer/mcp/tools/table_format_tool.py,sha256=JUfkB32ZXf-RQ5O2nKC2jFTVR1AxD8lks5vjjDFEoNw,15502
59
+ tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py,sha256=MbJEzWa0b2KtHLIgmy5WVcCN89YL4tB1drujoHt9axs,22173
59
60
  tree_sitter_analyzer/mcp/utils/__init__.py,sha256=F_qFFC2gvGNdgRWGLxIh4Amd0dPhZv0Ni1ZbCbaYLlI,3063
60
61
  tree_sitter_analyzer/mcp/utils/error_handler.py,sha256=n9ME2U5L1o65Vewnv8kD2O8dVI1CiEGm1pLWdnpnyqM,17972
61
62
  tree_sitter_analyzer/plugins/__init__.py,sha256=MfSW8P9GLaL_9XgLISdlpIUY4quqapk0avPLIpBdMTg,10606
@@ -69,8 +70,8 @@ tree_sitter_analyzer/queries/typescript.py,sha256=I1ndwPjAMGOIa1frSK3ewLqEkeDAJu
69
70
  tree_sitter_analyzer/security/__init__.py,sha256=zVpzS5jtECwgYnhKL4YoMfnIdkJABnVeziTBB4IOTyU,624
70
71
  tree_sitter_analyzer/security/boundary_manager.py,sha256=e4iOJTygHLqlImkOntjLhfTpCvqCfb2LTpYwGpYmVQg,8051
71
72
  tree_sitter_analyzer/security/regex_checker.py,sha256=Qvldh-TiVYqtcYQbD80wk0eHUvhALYtWTWBy_bGmJUk,10025
72
- tree_sitter_analyzer/security/validator.py,sha256=RRGyacxCjJ2CbfvGzpZCvza8YxqSrOiO2JWY49oFMh8,8187
73
- tree_sitter_analyzer-0.8.0.dist-info/METADATA,sha256=JtFjrCj-g9vPbMvYKurXQXB8I8Ogxo_DGB_lATblvIk,13525
74
- tree_sitter_analyzer-0.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
75
- tree_sitter_analyzer-0.8.0.dist-info/entry_points.txt,sha256=EA0Ow27x2SqNt2300sv70RTWxKRIxJzOhNPIVlez4NM,417
76
- tree_sitter_analyzer-0.8.0.dist-info/RECORD,,
73
+ tree_sitter_analyzer/security/validator.py,sha256=yL72kKnMN2IeqJk3i9l9FpWP9_Mt4lPErpj8ys5J_WY,9118
74
+ tree_sitter_analyzer-0.8.1.dist-info/METADATA,sha256=DGfahyUO9cgkNyyOUtwQrew4UtZbTSpanuhAivGRSN8,14422
75
+ tree_sitter_analyzer-0.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
76
+ tree_sitter_analyzer-0.8.1.dist-info/entry_points.txt,sha256=EA0Ow27x2SqNt2300sv70RTWxKRIxJzOhNPIVlez4NM,417
77
+ tree_sitter_analyzer-0.8.1.dist-info/RECORD,,