tree-sitter-analyzer 0.9.6__py3-none-any.whl → 0.9.8__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.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/core/query_service.py +162 -162
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +22 -10
- tree_sitter_analyzer/mcp/tools/query_tool.py +18 -6
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +4 -15
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +11 -24
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +32 -22
- tree_sitter_analyzer/mcp/utils/__init__.py +7 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -569
- tree_sitter_analyzer/mcp/utils/path_resolver.py +194 -0
- {tree_sitter_analyzer-0.9.6.dist-info → tree_sitter_analyzer-0.9.8.dist-info}/METADATA +22 -11
- {tree_sitter_analyzer-0.9.6.dist-info → tree_sitter_analyzer-0.9.8.dist-info}/RECORD +14 -13
- {tree_sitter_analyzer-0.9.6.dist-info → tree_sitter_analyzer-0.9.8.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.9.6.dist-info → tree_sitter_analyzer-0.9.8.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Query Service
|
|
4
|
-
|
|
5
|
-
Unified query service for both CLI and MCP interfaces to avoid code duplication.
|
|
6
|
-
Provides core tree-sitter query functionality including predefined and custom queries.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from ..encoding_utils import read_file_safe
|
|
13
|
-
from ..query_loader import query_loader
|
|
14
|
-
from .parser import Parser
|
|
15
|
-
from .query_filter import QueryFilter
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class QueryService:
|
|
21
|
-
"""Unified query service providing tree-sitter query functionality"""
|
|
22
|
-
|
|
23
|
-
def __init__(self, project_root: str | None = None) -> None:
|
|
24
|
-
"""Initialize the query service"""
|
|
25
|
-
self.project_root = project_root
|
|
26
|
-
self.parser = Parser()
|
|
27
|
-
self.filter = QueryFilter()
|
|
28
|
-
|
|
29
|
-
async def execute_query(
|
|
30
|
-
self,
|
|
31
|
-
file_path: str,
|
|
32
|
-
language: str,
|
|
33
|
-
query_key: str | None = None,
|
|
34
|
-
query_string: str | None = None,
|
|
35
|
-
filter_expression: str | None = None,
|
|
36
|
-
) -> list[dict[str, Any]] | None:
|
|
37
|
-
"""
|
|
38
|
-
Execute a query
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
file_path: Path to the file to analyze
|
|
42
|
-
language: Programming language
|
|
43
|
-
query_key: Predefined query key (e.g., 'methods', 'class')
|
|
44
|
-
query_string: Custom query string (e.g., '(method_declaration) @method')
|
|
45
|
-
filter_expression: Filter expression (e.g., 'name=main', 'name=~get*,public=true')
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
List of query results, each containing capture_name, node_type, start_line, end_line, content
|
|
49
|
-
|
|
50
|
-
Raises:
|
|
51
|
-
ValueError: If neither query_key nor query_string is provided
|
|
52
|
-
FileNotFoundError: If file doesn't exist
|
|
53
|
-
Exception: If query execution fails
|
|
54
|
-
"""
|
|
55
|
-
if not query_key and not query_string:
|
|
56
|
-
raise ValueError("Must provide either query_key or query_string")
|
|
57
|
-
|
|
58
|
-
if query_key and query_string:
|
|
59
|
-
raise ValueError("Cannot provide both query_key and query_string")
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
# Read file content
|
|
63
|
-
content, encoding = read_file_safe(file_path)
|
|
64
|
-
|
|
65
|
-
# Parse file
|
|
66
|
-
parse_result = self.parser.parse_code(content, language, file_path)
|
|
67
|
-
if not parse_result or not parse_result.tree:
|
|
68
|
-
raise Exception("Failed to parse file")
|
|
69
|
-
|
|
70
|
-
tree = parse_result.tree
|
|
71
|
-
language_obj = tree.language if hasattr(tree, "language") else None
|
|
72
|
-
if not language_obj:
|
|
73
|
-
raise Exception(f"Language object not available for {language}")
|
|
74
|
-
|
|
75
|
-
# Get query string
|
|
76
|
-
if query_key:
|
|
77
|
-
query_string = query_loader.get_query(language, query_key)
|
|
78
|
-
if not query_string:
|
|
79
|
-
raise ValueError(
|
|
80
|
-
f"Query '{query_key}' not found for language '{language}'"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
# Execute tree-sitter query
|
|
84
|
-
ts_query = language_obj.query(query_string)
|
|
85
|
-
captures = ts_query.captures(tree.root_node)
|
|
86
|
-
|
|
87
|
-
# Process capture results
|
|
88
|
-
results = []
|
|
89
|
-
if isinstance(captures, dict):
|
|
90
|
-
# New tree-sitter API returns dictionary
|
|
91
|
-
for capture_name, nodes in captures.items():
|
|
92
|
-
for node in nodes:
|
|
93
|
-
results.append(self._create_result_dict(node, capture_name))
|
|
94
|
-
else:
|
|
95
|
-
# Old tree-sitter API returns list of tuples
|
|
96
|
-
for capture in captures:
|
|
97
|
-
if isinstance(capture, tuple) and len(capture) == 2:
|
|
98
|
-
node, name = capture
|
|
99
|
-
results.append(self._create_result_dict(node, name))
|
|
100
|
-
|
|
101
|
-
# Apply filters
|
|
102
|
-
if filter_expression and results:
|
|
103
|
-
results = self.filter.filter_results(results, filter_expression)
|
|
104
|
-
|
|
105
|
-
return results
|
|
106
|
-
|
|
107
|
-
except Exception as e:
|
|
108
|
-
logger.error(f"Query execution failed: {e}")
|
|
109
|
-
raise
|
|
110
|
-
|
|
111
|
-
def _create_result_dict(self, node: Any, capture_name: str) -> dict[str, Any]:
|
|
112
|
-
"""
|
|
113
|
-
Create result dictionary from tree-sitter node
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
node: tree-sitter node
|
|
117
|
-
capture_name: capture name
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Result dictionary
|
|
121
|
-
"""
|
|
122
|
-
return {
|
|
123
|
-
"capture_name": capture_name,
|
|
124
|
-
"node_type": node.type if hasattr(node, "type") else "unknown",
|
|
125
|
-
"start_line": (
|
|
126
|
-
node.start_point[0] + 1 if hasattr(node, "start_point") else 0
|
|
127
|
-
),
|
|
128
|
-
"end_line": node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
|
|
129
|
-
"content": (
|
|
130
|
-
node.text.decode("utf-8", errors="replace")
|
|
131
|
-
if hasattr(node, "text") and node.text
|
|
132
|
-
else ""
|
|
133
|
-
),
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
def get_available_queries(self, language: str) -> list[str]:
|
|
137
|
-
"""
|
|
138
|
-
Get available query keys for specified language
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
language: Programming language
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
List of available query keys
|
|
145
|
-
"""
|
|
146
|
-
return query_loader.list_queries(language)
|
|
147
|
-
|
|
148
|
-
def get_query_description(self, language: str, query_key: str) -> str | None:
|
|
149
|
-
"""
|
|
150
|
-
Get description for query key
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
language: Programming language
|
|
154
|
-
query_key: Query key
|
|
155
|
-
|
|
156
|
-
Returns:
|
|
157
|
-
Query description, or None if not found
|
|
158
|
-
"""
|
|
159
|
-
try:
|
|
160
|
-
return query_loader.get_query_description(language, query_key)
|
|
161
|
-
except Exception:
|
|
162
|
-
return None
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Service
|
|
4
|
+
|
|
5
|
+
Unified query service for both CLI and MCP interfaces to avoid code duplication.
|
|
6
|
+
Provides core tree-sitter query functionality including predefined and custom queries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ..encoding_utils import read_file_safe
|
|
13
|
+
from ..query_loader import query_loader
|
|
14
|
+
from .parser import Parser
|
|
15
|
+
from .query_filter import QueryFilter
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QueryService:
|
|
21
|
+
"""Unified query service providing tree-sitter query functionality"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, project_root: str | None = None) -> None:
|
|
24
|
+
"""Initialize the query service"""
|
|
25
|
+
self.project_root = project_root
|
|
26
|
+
self.parser = Parser()
|
|
27
|
+
self.filter = QueryFilter()
|
|
28
|
+
|
|
29
|
+
async def execute_query(
|
|
30
|
+
self,
|
|
31
|
+
file_path: str,
|
|
32
|
+
language: str,
|
|
33
|
+
query_key: str | None = None,
|
|
34
|
+
query_string: str | None = None,
|
|
35
|
+
filter_expression: str | None = None,
|
|
36
|
+
) -> list[dict[str, Any]] | None:
|
|
37
|
+
"""
|
|
38
|
+
Execute a query
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
file_path: Path to the file to analyze
|
|
42
|
+
language: Programming language
|
|
43
|
+
query_key: Predefined query key (e.g., 'methods', 'class')
|
|
44
|
+
query_string: Custom query string (e.g., '(method_declaration) @method')
|
|
45
|
+
filter_expression: Filter expression (e.g., 'name=main', 'name=~get*,public=true')
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of query results, each containing capture_name, node_type, start_line, end_line, content
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If neither query_key nor query_string is provided
|
|
52
|
+
FileNotFoundError: If file doesn't exist
|
|
53
|
+
Exception: If query execution fails
|
|
54
|
+
"""
|
|
55
|
+
if not query_key and not query_string:
|
|
56
|
+
raise ValueError("Must provide either query_key or query_string")
|
|
57
|
+
|
|
58
|
+
if query_key and query_string:
|
|
59
|
+
raise ValueError("Cannot provide both query_key and query_string")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Read file content
|
|
63
|
+
content, encoding = read_file_safe(file_path)
|
|
64
|
+
|
|
65
|
+
# Parse file
|
|
66
|
+
parse_result = self.parser.parse_code(content, language, file_path)
|
|
67
|
+
if not parse_result or not parse_result.tree:
|
|
68
|
+
raise Exception("Failed to parse file")
|
|
69
|
+
|
|
70
|
+
tree = parse_result.tree
|
|
71
|
+
language_obj = tree.language if hasattr(tree, "language") else None
|
|
72
|
+
if not language_obj:
|
|
73
|
+
raise Exception(f"Language object not available for {language}")
|
|
74
|
+
|
|
75
|
+
# Get query string
|
|
76
|
+
if query_key:
|
|
77
|
+
query_string = query_loader.get_query(language, query_key)
|
|
78
|
+
if not query_string:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"Query '{query_key}' not found for language '{language}'"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Execute tree-sitter query
|
|
84
|
+
ts_query = language_obj.query(query_string)
|
|
85
|
+
captures = ts_query.captures(tree.root_node)
|
|
86
|
+
|
|
87
|
+
# Process capture results
|
|
88
|
+
results = []
|
|
89
|
+
if isinstance(captures, dict):
|
|
90
|
+
# New tree-sitter API returns dictionary
|
|
91
|
+
for capture_name, nodes in captures.items():
|
|
92
|
+
for node in nodes:
|
|
93
|
+
results.append(self._create_result_dict(node, capture_name))
|
|
94
|
+
else:
|
|
95
|
+
# Old tree-sitter API returns list of tuples
|
|
96
|
+
for capture in captures:
|
|
97
|
+
if isinstance(capture, tuple) and len(capture) == 2:
|
|
98
|
+
node, name = capture
|
|
99
|
+
results.append(self._create_result_dict(node, name))
|
|
100
|
+
|
|
101
|
+
# Apply filters
|
|
102
|
+
if filter_expression and results:
|
|
103
|
+
results = self.filter.filter_results(results, filter_expression)
|
|
104
|
+
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Query execution failed: {e}")
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
def _create_result_dict(self, node: Any, capture_name: str) -> dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Create result dictionary from tree-sitter node
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
node: tree-sitter node
|
|
117
|
+
capture_name: capture name
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Result dictionary
|
|
121
|
+
"""
|
|
122
|
+
return {
|
|
123
|
+
"capture_name": capture_name,
|
|
124
|
+
"node_type": node.type if hasattr(node, "type") else "unknown",
|
|
125
|
+
"start_line": (
|
|
126
|
+
node.start_point[0] + 1 if hasattr(node, "start_point") else 0
|
|
127
|
+
),
|
|
128
|
+
"end_line": node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
|
|
129
|
+
"content": (
|
|
130
|
+
node.text.decode("utf-8", errors="replace")
|
|
131
|
+
if hasattr(node, "text") and node.text
|
|
132
|
+
else ""
|
|
133
|
+
),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def get_available_queries(self, language: str) -> list[str]:
|
|
137
|
+
"""
|
|
138
|
+
Get available query keys for specified language
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
language: Programming language
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of available query keys
|
|
145
|
+
"""
|
|
146
|
+
return query_loader.list_queries(language)
|
|
147
|
+
|
|
148
|
+
def get_query_description(self, language: str, query_key: str) -> str | None:
|
|
149
|
+
"""
|
|
150
|
+
Get description for query key
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
language: Programming language
|
|
154
|
+
query_key: Query key
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Query description, or None if not found
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
return query_loader.get_query_description(language, query_key)
|
|
161
|
+
except Exception:
|
|
162
|
+
return None
|
|
@@ -15,6 +15,7 @@ from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
|
|
|
15
15
|
from ...language_detector import detect_language_from_file
|
|
16
16
|
from ...security import SecurityValidator
|
|
17
17
|
from ...utils import setup_logger
|
|
18
|
+
from ..utils.path_resolver import PathResolver
|
|
18
19
|
|
|
19
20
|
# Set up logging
|
|
20
21
|
logger = setup_logger(__name__)
|
|
@@ -35,6 +36,7 @@ class AnalyzeScaleTool:
|
|
|
35
36
|
self.project_root = project_root
|
|
36
37
|
self.analysis_engine = get_analysis_engine(project_root)
|
|
37
38
|
self.security_validator = SecurityValidator(project_root)
|
|
39
|
+
self.path_resolver = PathResolver(project_root)
|
|
38
40
|
logger.info("AnalyzeScaleTool initialized with security validation")
|
|
39
41
|
|
|
40
42
|
def _calculate_file_metrics(self, file_path: str) -> dict[str, Any]:
|
|
@@ -354,11 +356,17 @@ class AnalyzeScaleTool:
|
|
|
354
356
|
include_details = arguments.get("include_details", False)
|
|
355
357
|
include_guidance = arguments.get("include_guidance", True)
|
|
356
358
|
|
|
357
|
-
#
|
|
358
|
-
|
|
359
|
+
# Resolve file path to absolute path
|
|
360
|
+
resolved_file_path = self.path_resolver.resolve(file_path)
|
|
361
|
+
logger.info(f"Analyzing file: {file_path} (resolved to: {resolved_file_path})")
|
|
362
|
+
|
|
363
|
+
# Security validation using resolved path
|
|
364
|
+
is_valid, error_msg = self.security_validator.validate_file_path(
|
|
365
|
+
resolved_file_path
|
|
366
|
+
)
|
|
359
367
|
if not is_valid:
|
|
360
368
|
logger.warning(
|
|
361
|
-
f"Security validation failed for file path: {
|
|
369
|
+
f"Security validation failed for file path: {resolved_file_path} - {error_msg}"
|
|
362
370
|
)
|
|
363
371
|
raise ValueError(f"Invalid file path: {error_msg}")
|
|
364
372
|
|
|
@@ -367,16 +375,20 @@ class AnalyzeScaleTool:
|
|
|
367
375
|
language = self.security_validator.sanitize_input(language, max_length=50)
|
|
368
376
|
|
|
369
377
|
# Validate file exists
|
|
370
|
-
if not Path(
|
|
378
|
+
if not Path(resolved_file_path).exists():
|
|
371
379
|
raise ValueError("Invalid file path: file does not exist")
|
|
372
380
|
|
|
373
381
|
# Detect language if not specified
|
|
374
382
|
if not language:
|
|
375
|
-
language = detect_language_from_file(
|
|
383
|
+
language = detect_language_from_file(resolved_file_path)
|
|
376
384
|
if language == "unknown":
|
|
377
|
-
raise ValueError(
|
|
385
|
+
raise ValueError(
|
|
386
|
+
f"Could not detect language for file: {resolved_file_path}"
|
|
387
|
+
)
|
|
378
388
|
|
|
379
|
-
logger.info(
|
|
389
|
+
logger.info(
|
|
390
|
+
f"Analyzing code scale for {resolved_file_path} (language: {language})"
|
|
391
|
+
)
|
|
380
392
|
|
|
381
393
|
try:
|
|
382
394
|
# Use performance monitoring with proper context manager
|
|
@@ -386,14 +398,14 @@ class AnalyzeScaleTool:
|
|
|
386
398
|
"analyze_code_scale_enhanced"
|
|
387
399
|
):
|
|
388
400
|
# Calculate basic file metrics
|
|
389
|
-
file_metrics = self._calculate_file_metrics(
|
|
401
|
+
file_metrics = self._calculate_file_metrics(resolved_file_path)
|
|
390
402
|
|
|
391
403
|
# Use appropriate analyzer based on language
|
|
392
404
|
if language == "java":
|
|
393
405
|
# Use AdvancedAnalyzer for comprehensive analysis
|
|
394
406
|
# Use unified analysis engine instead of deprecated advanced_analyzer
|
|
395
407
|
request = AnalysisRequest(
|
|
396
|
-
file_path=
|
|
408
|
+
file_path=resolved_file_path,
|
|
397
409
|
language=language,
|
|
398
410
|
include_complexity=True,
|
|
399
411
|
include_details=True,
|
|
@@ -408,7 +420,7 @@ class AnalyzeScaleTool:
|
|
|
408
420
|
else:
|
|
409
421
|
# Use universal analysis_engine for other languages
|
|
410
422
|
request = AnalysisRequest(
|
|
411
|
-
file_path=
|
|
423
|
+
file_path=resolved_file_path,
|
|
412
424
|
language=language,
|
|
413
425
|
include_details=include_details,
|
|
414
426
|
)
|
|
@@ -13,6 +13,7 @@ from ...core.query_service import QueryService
|
|
|
13
13
|
from ...language_detector import detect_language_from_file
|
|
14
14
|
from ...security import SecurityValidator
|
|
15
15
|
from ..utils.error_handler import handle_mcp_errors
|
|
16
|
+
from ..utils.path_resolver import PathResolver
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
@@ -25,6 +26,7 @@ class QueryTool:
|
|
|
25
26
|
self.project_root = project_root
|
|
26
27
|
self.query_service = QueryService(project_root)
|
|
27
28
|
self.security_validator = SecurityValidator(project_root)
|
|
29
|
+
self.path_resolver = PathResolver(project_root)
|
|
28
30
|
|
|
29
31
|
def get_tool_definition(self) -> dict[str, Any]:
|
|
30
32
|
"""
|
|
@@ -41,7 +43,7 @@ class QueryTool:
|
|
|
41
43
|
"properties": {
|
|
42
44
|
"file_path": {
|
|
43
45
|
"type": "string",
|
|
44
|
-
"description": "Path to the code file to query (relative to project root)",
|
|
46
|
+
"description": "Path to the code file to query (relative to project root or absolute path)",
|
|
45
47
|
},
|
|
46
48
|
"language": {
|
|
47
49
|
"type": "string",
|
|
@@ -74,7 +76,7 @@ class QueryTool:
|
|
|
74
76
|
},
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
@handle_mcp_errors
|
|
79
|
+
@handle_mcp_errors("query_code")
|
|
78
80
|
async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
79
81
|
"""
|
|
80
82
|
Execute query tool
|
|
@@ -90,8 +92,18 @@ class QueryTool:
|
|
|
90
92
|
if not file_path:
|
|
91
93
|
raise ValueError("file_path is required")
|
|
92
94
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
+
# Resolve file path to absolute path
|
|
96
|
+
resolved_file_path = self.path_resolver.resolve(file_path)
|
|
97
|
+
logger.info(f"Querying file: {file_path} (resolved to: {resolved_file_path})")
|
|
98
|
+
|
|
99
|
+
# Security validation using resolved path
|
|
100
|
+
is_valid, error_msg = self.security_validator.validate_file_path(
|
|
101
|
+
resolved_file_path
|
|
102
|
+
)
|
|
103
|
+
if not is_valid:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Invalid or unsafe file path: {error_msg or resolved_file_path}"
|
|
106
|
+
)
|
|
95
107
|
|
|
96
108
|
# Get query parameters
|
|
97
109
|
query_key = arguments.get("query_key")
|
|
@@ -108,14 +120,14 @@ class QueryTool:
|
|
|
108
120
|
# Detect language
|
|
109
121
|
language = arguments.get("language")
|
|
110
122
|
if not language:
|
|
111
|
-
language = detect_language_from_file(
|
|
123
|
+
language = detect_language_from_file(resolved_file_path)
|
|
112
124
|
if not language:
|
|
113
125
|
raise ValueError(f"Could not detect language for file: {file_path}")
|
|
114
126
|
|
|
115
127
|
try:
|
|
116
128
|
# Execute query
|
|
117
129
|
results = await self.query_service.execute_query(
|
|
118
|
-
|
|
130
|
+
resolved_file_path, language, query_key, query_string, filter_expression
|
|
119
131
|
)
|
|
120
132
|
|
|
121
133
|
if not results:
|
|
@@ -7,13 +7,13 @@ allowing selective content extraction with line and column range support.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
|
-
import os
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
from typing import Any
|
|
13
12
|
|
|
14
13
|
from ...file_handler import read_file_partial
|
|
15
14
|
from ...security import SecurityValidator
|
|
16
15
|
from ...utils import setup_logger
|
|
16
|
+
from ..utils.path_resolver import PathResolver
|
|
17
17
|
|
|
18
18
|
# Set up logging
|
|
19
19
|
logger = setup_logger(__name__)
|
|
@@ -31,6 +31,7 @@ class ReadPartialTool:
|
|
|
31
31
|
"""Initialize the read partial tool."""
|
|
32
32
|
self.security_validator = SecurityValidator(project_root)
|
|
33
33
|
self.project_root = project_root
|
|
34
|
+
self.path_resolver = PathResolver(project_root)
|
|
34
35
|
logger.info("ReadPartialTool initialized with security validation")
|
|
35
36
|
|
|
36
37
|
def get_tool_schema(self) -> dict[str, Any]:
|
|
@@ -106,20 +107,8 @@ class ReadPartialTool:
|
|
|
106
107
|
end_column = arguments.get("end_column")
|
|
107
108
|
# output_format = arguments.get("format", "text") # Not used currently
|
|
108
109
|
|
|
109
|
-
# Resolve
|
|
110
|
-
|
|
111
|
-
getattr(
|
|
112
|
-
getattr(self.security_validator, "boundary_manager", None),
|
|
113
|
-
"project_root",
|
|
114
|
-
None,
|
|
115
|
-
)
|
|
116
|
-
or self.project_root
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
if not os.path.isabs(file_path) and base_root:
|
|
120
|
-
resolved_path = os.path.realpath(os.path.join(base_root, file_path))
|
|
121
|
-
else:
|
|
122
|
-
resolved_path = file_path
|
|
110
|
+
# Resolve file path using common path resolver
|
|
111
|
+
resolved_path = self.path_resolver.resolve(file_path)
|
|
123
112
|
|
|
124
113
|
# Security validation (validate resolved absolute path when possible)
|
|
125
114
|
is_valid, error_msg = self.security_validator.validate_file_path(resolved_path)
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Table Format MCP
|
|
3
|
+
Table Format Tool for MCP
|
|
4
4
|
|
|
5
|
-
This tool provides
|
|
6
|
-
|
|
5
|
+
This tool provides code structure analysis and table formatting through the MCP protocol,
|
|
6
|
+
converting analysis results into structured table formats for better readability.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import os
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
@@ -16,6 +15,7 @@ from ...security import SecurityValidator
|
|
|
16
15
|
from ...table_formatter import TableFormatter
|
|
17
16
|
from ...utils import setup_logger
|
|
18
17
|
from ..utils import get_performance_monitor
|
|
18
|
+
from ..utils.path_resolver import PathResolver
|
|
19
19
|
|
|
20
20
|
# Set up logging
|
|
21
21
|
logger = setup_logger(__name__)
|
|
@@ -23,20 +23,19 @@ logger = setup_logger(__name__)
|
|
|
23
23
|
|
|
24
24
|
class TableFormatTool:
|
|
25
25
|
"""
|
|
26
|
-
MCP Tool for
|
|
26
|
+
MCP Tool for code structure analysis and table formatting.
|
|
27
27
|
|
|
28
|
-
This tool integrates with existing
|
|
29
|
-
|
|
30
|
-
the CLI --table=full option.
|
|
28
|
+
This tool integrates with existing analyzer components to provide
|
|
29
|
+
structured table output through the MCP protocol.
|
|
31
30
|
"""
|
|
32
31
|
|
|
33
32
|
def __init__(self, project_root: str = None) -> None:
|
|
34
33
|
"""Initialize the table format tool."""
|
|
35
|
-
self.logger = logger
|
|
36
34
|
self.project_root = project_root
|
|
37
35
|
self.analysis_engine = get_analysis_engine(project_root)
|
|
38
36
|
self.security_validator = SecurityValidator(project_root)
|
|
39
|
-
|
|
37
|
+
self.path_resolver = PathResolver(project_root)
|
|
38
|
+
self.logger = logger
|
|
40
39
|
|
|
41
40
|
def get_tool_schema(self) -> dict[str, Any]:
|
|
42
41
|
"""
|
|
@@ -272,20 +271,8 @@ class TableFormatTool:
|
|
|
272
271
|
format_type = args.get("format_type", "full")
|
|
273
272
|
language = args.get("language")
|
|
274
273
|
|
|
275
|
-
# Resolve
|
|
276
|
-
|
|
277
|
-
getattr(
|
|
278
|
-
getattr(self.security_validator, "boundary_manager", None),
|
|
279
|
-
"project_root",
|
|
280
|
-
None,
|
|
281
|
-
)
|
|
282
|
-
or self.project_root
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
if not os.path.isabs(file_path) and base_root:
|
|
286
|
-
resolved_path = os.path.realpath(os.path.join(base_root, file_path))
|
|
287
|
-
else:
|
|
288
|
-
resolved_path = file_path
|
|
274
|
+
# Resolve file path using common path resolver
|
|
275
|
+
resolved_path = self.path_resolver.resolve(file_path)
|
|
289
276
|
|
|
290
277
|
# Security validation
|
|
291
278
|
is_valid, error_msg = self.security_validator.validate_file_path(
|