tree-sitter-analyzer 0.1.1__py3-none-any.whl → 0.1.3__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/cli/commands/advanced_command.py +10 -10
- tree_sitter_analyzer/cli/commands/base_command.py +11 -11
- tree_sitter_analyzer/cli/commands/partial_read_command.py +12 -12
- tree_sitter_analyzer/cli/commands/structure_command.py +13 -13
- tree_sitter_analyzer/cli/commands/summary_command.py +8 -8
- tree_sitter_analyzer/cli_main.py +6 -6
- tree_sitter_analyzer/mcp/server.py +0 -11
- tree_sitter_analyzer/mcp/tools/__init__.py +0 -5
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +1 -2
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +5 -5
- tree_sitter_analyzer/plugins/python_plugin.py +598 -598
- {tree_sitter_analyzer-0.1.1.dist-info → tree_sitter_analyzer-0.1.3.dist-info}/METADATA +80 -217
- {tree_sitter_analyzer-0.1.1.dist-info → tree_sitter_analyzer-0.1.3.dist-info}/RECORD +16 -17
- tree_sitter_analyzer/mcp/tools/get_positions_tool.py +0 -448
- {tree_sitter_analyzer-0.1.1.dist-info → tree_sitter_analyzer-0.1.3.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.1.1.dist-info → tree_sitter_analyzer-0.1.3.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
|
@@ -37,7 +37,7 @@ class AdvancedCommand(BaseCommand):
|
|
|
37
37
|
"node_count": analysis_result.node_count,
|
|
38
38
|
"language": analysis_result.language,
|
|
39
39
|
}
|
|
40
|
-
output_section("
|
|
40
|
+
output_section("Statistics")
|
|
41
41
|
if self.args.output_format == "json":
|
|
42
42
|
output_json(stats)
|
|
43
43
|
else:
|
|
@@ -46,7 +46,7 @@ class AdvancedCommand(BaseCommand):
|
|
|
46
46
|
|
|
47
47
|
def _output_full_analysis(self, analysis_result: "AnalysisResult") -> None:
|
|
48
48
|
"""Output full analysis results."""
|
|
49
|
-
output_section("
|
|
49
|
+
output_section("Advanced Analysis Results")
|
|
50
50
|
if self.args.output_format == "json":
|
|
51
51
|
result_dict = {
|
|
52
52
|
"file_path": analysis_result.file_path,
|
|
@@ -72,17 +72,17 @@ class AdvancedCommand(BaseCommand):
|
|
|
72
72
|
|
|
73
73
|
def _output_text_analysis(self, analysis_result: "AnalysisResult") -> None:
|
|
74
74
|
"""Output analysis in text format."""
|
|
75
|
-
output_data(f"
|
|
76
|
-
output_data(f"
|
|
77
|
-
output_data(f"
|
|
75
|
+
output_data(f"File: {analysis_result.file_path}")
|
|
76
|
+
output_data(f"Package: (default)")
|
|
77
|
+
output_data(f"Lines: {analysis_result.line_count}")
|
|
78
78
|
|
|
79
79
|
element_counts = {}
|
|
80
80
|
for element in analysis_result.elements:
|
|
81
81
|
element_type = getattr(element, "__class__", type(element)).__name__
|
|
82
82
|
element_counts[element_type] = element_counts.get(element_type, 0) + 1
|
|
83
83
|
|
|
84
|
-
output_data(f"
|
|
85
|
-
output_data(f"
|
|
86
|
-
output_data(f"
|
|
87
|
-
output_data(f"
|
|
88
|
-
output_data(f"
|
|
84
|
+
output_data(f"Classes: {element_counts.get('Class', 0)}")
|
|
85
|
+
output_data(f"Methods: {element_counts.get('Function', 0)}")
|
|
86
|
+
output_data(f"Fields: {element_counts.get('Variable', 0)}")
|
|
87
|
+
output_data(f"Imports: {element_counts.get('Import', 0)}")
|
|
88
|
+
output_data(f"Annotations: 0")
|
|
@@ -34,13 +34,13 @@ class BaseCommand(ABC):
|
|
|
34
34
|
def validate_file(self) -> bool:
|
|
35
35
|
"""Validate input file exists and is accessible."""
|
|
36
36
|
if not hasattr(self.args, "file_path") or not self.args.file_path:
|
|
37
|
-
output_error("ERROR:
|
|
37
|
+
output_error("ERROR: File path not specified.")
|
|
38
38
|
return False
|
|
39
39
|
|
|
40
40
|
import os
|
|
41
41
|
|
|
42
42
|
if not os.path.exists(self.args.file_path):
|
|
43
|
-
output_error(f"ERROR:
|
|
43
|
+
output_error(f"ERROR: File not found: {self.args.file_path}")
|
|
44
44
|
return False
|
|
45
45
|
|
|
46
46
|
return True
|
|
@@ -50,18 +50,18 @@ class BaseCommand(ABC):
|
|
|
50
50
|
if hasattr(self.args, "language") and self.args.language:
|
|
51
51
|
target_language = self.args.language.lower()
|
|
52
52
|
if (not hasattr(self.args, "table") or not self.args.table) and (not hasattr(self.args, "quiet") or not self.args.quiet):
|
|
53
|
-
output_info(f"INFO:
|
|
53
|
+
output_info(f"INFO: Language explicitly specified: {target_language}")
|
|
54
54
|
else:
|
|
55
55
|
target_language = detect_language_from_file(self.args.file_path)
|
|
56
56
|
if target_language == "unknown":
|
|
57
57
|
output_error(
|
|
58
|
-
f"ERROR:
|
|
58
|
+
f"ERROR: Could not determine language for file '{self.args.file_path}'."
|
|
59
59
|
)
|
|
60
60
|
return None
|
|
61
61
|
else:
|
|
62
62
|
if (not hasattr(self.args, "table") or not self.args.table) and (not hasattr(self.args, "quiet") or not self.args.quiet):
|
|
63
63
|
output_info(
|
|
64
|
-
f"INFO:
|
|
64
|
+
f"INFO: Language auto-detected from extension: {target_language}"
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
# Language support validation
|
|
@@ -69,7 +69,7 @@ class BaseCommand(ABC):
|
|
|
69
69
|
if target_language != "java":
|
|
70
70
|
if (not hasattr(self.args, "table") or not self.args.table) and (not hasattr(self.args, "quiet") or not self.args.quiet):
|
|
71
71
|
output_info(
|
|
72
|
-
"INFO: Java
|
|
72
|
+
"INFO: Trying with Java analysis engine. May not work correctly."
|
|
73
73
|
)
|
|
74
74
|
target_language = "java" # Fallback
|
|
75
75
|
|
|
@@ -89,10 +89,10 @@ class BaseCommand(ABC):
|
|
|
89
89
|
end_column=getattr(self.args, 'end_column', None)
|
|
90
90
|
)
|
|
91
91
|
if partial_content is None:
|
|
92
|
-
output_error("ERROR:
|
|
92
|
+
output_error("ERROR: Failed to read file partially")
|
|
93
93
|
return None
|
|
94
94
|
except Exception as e:
|
|
95
|
-
output_error(f"ERROR:
|
|
95
|
+
output_error(f"ERROR: Failed to read file partially: {e}")
|
|
96
96
|
return None
|
|
97
97
|
|
|
98
98
|
request = AnalysisRequest(
|
|
@@ -109,13 +109,13 @@ class BaseCommand(ABC):
|
|
|
109
109
|
if analysis_result
|
|
110
110
|
else "Unknown error"
|
|
111
111
|
)
|
|
112
|
-
output_error(f"ERROR:
|
|
112
|
+
output_error(f"ERROR: Analysis failed: {error_msg}")
|
|
113
113
|
return None
|
|
114
114
|
|
|
115
115
|
return analysis_result
|
|
116
116
|
|
|
117
117
|
except Exception as e:
|
|
118
|
-
output_error(f"ERROR:
|
|
118
|
+
output_error(f"ERROR: An error occurred during analysis: {e}")
|
|
119
119
|
return None
|
|
120
120
|
|
|
121
121
|
def execute(self) -> int:
|
|
@@ -138,7 +138,7 @@ class BaseCommand(ABC):
|
|
|
138
138
|
try:
|
|
139
139
|
return asyncio.run(self.execute_async(language))
|
|
140
140
|
except Exception as e:
|
|
141
|
-
output_error(f"ERROR:
|
|
141
|
+
output_error(f"ERROR: An error occurred during command execution: {e}")
|
|
142
142
|
return 1
|
|
143
143
|
|
|
144
144
|
@abstractmethod
|
|
@@ -28,14 +28,14 @@ class PartialReadCommand(BaseCommand):
|
|
|
28
28
|
"""Validate input file exists and is accessible."""
|
|
29
29
|
if not hasattr(self.args, "file_path") or not self.args.file_path:
|
|
30
30
|
from ...output_manager import output_error
|
|
31
|
-
output_error("ERROR:
|
|
31
|
+
output_error("ERROR: File path not specified.")
|
|
32
32
|
return False
|
|
33
33
|
|
|
34
34
|
import os
|
|
35
35
|
|
|
36
36
|
if not os.path.exists(self.args.file_path):
|
|
37
37
|
from ...output_manager import output_error
|
|
38
|
-
output_error(f"ERROR:
|
|
38
|
+
output_error(f"ERROR: File not found: {self.args.file_path}")
|
|
39
39
|
return False
|
|
40
40
|
|
|
41
41
|
return True
|
|
@@ -54,17 +54,17 @@ class PartialReadCommand(BaseCommand):
|
|
|
54
54
|
# Validate partial read arguments
|
|
55
55
|
if not self.args.start_line:
|
|
56
56
|
from ...output_manager import output_error
|
|
57
|
-
output_error("ERROR: --start-line
|
|
57
|
+
output_error("ERROR: --start-line is required")
|
|
58
58
|
return 1
|
|
59
59
|
|
|
60
60
|
if self.args.start_line < 1:
|
|
61
61
|
from ...output_manager import output_error
|
|
62
|
-
output_error("ERROR: --start-line
|
|
62
|
+
output_error("ERROR: --start-line must be 1 or greater")
|
|
63
63
|
return 1
|
|
64
64
|
|
|
65
65
|
if self.args.end_line and self.args.end_line < self.args.start_line:
|
|
66
66
|
from ...output_manager import output_error
|
|
67
|
-
output_error("ERROR: --end-line
|
|
67
|
+
output_error("ERROR: --end-line must be greater than or equal to --start-line")
|
|
68
68
|
return 1
|
|
69
69
|
|
|
70
70
|
# Read partial content
|
|
@@ -79,7 +79,7 @@ class PartialReadCommand(BaseCommand):
|
|
|
79
79
|
|
|
80
80
|
if partial_content is None:
|
|
81
81
|
from ...output_manager import output_error
|
|
82
|
-
output_error("ERROR:
|
|
82
|
+
output_error("ERROR: Failed to read file partially")
|
|
83
83
|
return 1
|
|
84
84
|
|
|
85
85
|
# Output the result
|
|
@@ -88,7 +88,7 @@ class PartialReadCommand(BaseCommand):
|
|
|
88
88
|
|
|
89
89
|
except Exception as e:
|
|
90
90
|
from ...output_manager import output_error
|
|
91
|
-
output_error(f"ERROR:
|
|
91
|
+
output_error(f"ERROR: Failed to read file partially: {e}")
|
|
92
92
|
return 1
|
|
93
93
|
|
|
94
94
|
def _output_partial_content(self, content: str) -> None:
|
|
@@ -107,7 +107,7 @@ class PartialReadCommand(BaseCommand):
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
# Build range info for header
|
|
110
|
-
range_info = f"
|
|
110
|
+
range_info = f"Line {self.args.start_line}"
|
|
111
111
|
if hasattr(self.args, 'end_line') and self.args.end_line:
|
|
112
112
|
range_info += f"-{self.args.end_line}"
|
|
113
113
|
|
|
@@ -119,10 +119,10 @@ class PartialReadCommand(BaseCommand):
|
|
|
119
119
|
output_json(result_data)
|
|
120
120
|
else:
|
|
121
121
|
# Human-readable format with header
|
|
122
|
-
output_section("
|
|
123
|
-
output_data(f"
|
|
124
|
-
output_data(f"
|
|
125
|
-
output_data(f"
|
|
122
|
+
output_section("Partial Read Result")
|
|
123
|
+
output_data(f"File: {self.args.file_path}")
|
|
124
|
+
output_data(f"Range: {range_info}")
|
|
125
|
+
output_data(f"Characters read: {len(content)}")
|
|
126
126
|
output_data("") # Empty line for separation
|
|
127
127
|
|
|
128
128
|
# Output the actual content
|
|
@@ -27,7 +27,7 @@ class StructureCommand(BaseCommand):
|
|
|
27
27
|
|
|
28
28
|
def _output_structure_analysis(self, analysis_result: "AnalysisResult") -> None:
|
|
29
29
|
"""Output structure analysis results with appropriate Japanese header."""
|
|
30
|
-
output_section("
|
|
30
|
+
output_section("Structure Analysis Results")
|
|
31
31
|
|
|
32
32
|
# Convert to legacy structure format expected by tests
|
|
33
33
|
structure_dict = self._convert_to_legacy_format(analysis_result)
|
|
@@ -91,31 +91,31 @@ class StructureCommand(BaseCommand):
|
|
|
91
91
|
|
|
92
92
|
def _output_text_format(self, structure_dict: dict) -> None:
|
|
93
93
|
"""Output structure analysis in human-readable text format."""
|
|
94
|
-
output_data(f"
|
|
95
|
-
output_data(f"
|
|
94
|
+
output_data(f"File: {structure_dict['file_path']}")
|
|
95
|
+
output_data(f"Language: {structure_dict['language']}")
|
|
96
96
|
|
|
97
97
|
if structure_dict['package']:
|
|
98
|
-
output_data(f"
|
|
98
|
+
output_data(f"Package: {structure_dict['package']['name']}")
|
|
99
99
|
|
|
100
100
|
stats = structure_dict['statistics']
|
|
101
|
-
output_data(f"
|
|
102
|
-
output_data(f"
|
|
103
|
-
output_data(f"
|
|
104
|
-
output_data(f"
|
|
105
|
-
output_data(f"
|
|
106
|
-
output_data(f"
|
|
101
|
+
output_data(f"Statistics:")
|
|
102
|
+
output_data(f" Classes: {stats['class_count']}")
|
|
103
|
+
output_data(f" Methods: {stats['method_count']}")
|
|
104
|
+
output_data(f" Fields: {stats['field_count']}")
|
|
105
|
+
output_data(f" Imports: {stats['import_count']}")
|
|
106
|
+
output_data(f" Total lines: {stats['total_lines']}")
|
|
107
107
|
|
|
108
108
|
if structure_dict['classes']:
|
|
109
|
-
output_data("
|
|
109
|
+
output_data("Classes:")
|
|
110
110
|
for cls in structure_dict['classes']:
|
|
111
111
|
output_data(f" - {cls['name']}")
|
|
112
112
|
|
|
113
113
|
if structure_dict['methods']:
|
|
114
|
-
output_data("
|
|
114
|
+
output_data("Methods:")
|
|
115
115
|
for method in structure_dict['methods']:
|
|
116
116
|
output_data(f" - {method['name']}")
|
|
117
117
|
|
|
118
118
|
if structure_dict['fields']:
|
|
119
|
-
output_data("
|
|
119
|
+
output_data("Fields:")
|
|
120
120
|
for field in structure_dict['fields']:
|
|
121
121
|
output_data(f" - {field['name']}")
|
|
@@ -27,7 +27,7 @@ class SummaryCommand(BaseCommand):
|
|
|
27
27
|
|
|
28
28
|
def _output_summary_analysis(self, analysis_result: "AnalysisResult") -> None:
|
|
29
29
|
"""Output summary analysis results."""
|
|
30
|
-
output_section("
|
|
30
|
+
output_section("Summary Results")
|
|
31
31
|
|
|
32
32
|
# Get summary types from args (default: classes,methods)
|
|
33
33
|
summary_types = getattr(self.args, 'summary', 'classes,methods')
|
|
@@ -75,19 +75,19 @@ class SummaryCommand(BaseCommand):
|
|
|
75
75
|
|
|
76
76
|
def _output_text_format(self, summary_data: dict, requested_types: list) -> None:
|
|
77
77
|
"""Output summary in human-readable text format."""
|
|
78
|
-
output_data(f"
|
|
79
|
-
output_data(f"
|
|
78
|
+
output_data(f"File: {summary_data['file_path']}")
|
|
79
|
+
output_data(f"Language: {summary_data['language']}")
|
|
80
80
|
|
|
81
81
|
for element_type in requested_types:
|
|
82
82
|
if element_type in summary_data['summary']:
|
|
83
83
|
elements = summary_data['summary'][element_type]
|
|
84
84
|
type_name_map = {
|
|
85
|
-
'classes': '
|
|
86
|
-
'methods': '
|
|
87
|
-
'fields': '
|
|
88
|
-
'imports': '
|
|
85
|
+
'classes': 'Classes',
|
|
86
|
+
'methods': 'Methods',
|
|
87
|
+
'fields': 'Fields',
|
|
88
|
+
'imports': 'Imports'
|
|
89
89
|
}
|
|
90
90
|
type_name = type_name_map.get(element_type, element_type)
|
|
91
|
-
output_data(f"\n{type_name} ({len(elements)}
|
|
91
|
+
output_data(f"\n{type_name} ({len(elements)} items):")
|
|
92
92
|
for element in elements:
|
|
93
93
|
output_data(f" - {element['name']}")
|
tree_sitter_analyzer/cli_main.py
CHANGED
|
@@ -183,23 +183,23 @@ def handle_special_commands(args: argparse.Namespace) -> Optional[int]:
|
|
|
183
183
|
# Validate partial read options
|
|
184
184
|
if hasattr(args, 'partial_read') and args.partial_read:
|
|
185
185
|
if args.start_line is None:
|
|
186
|
-
output_error("ERROR: --start-line
|
|
186
|
+
output_error("ERROR: --start-line is required")
|
|
187
187
|
return 1
|
|
188
188
|
|
|
189
189
|
if args.start_line < 1:
|
|
190
|
-
output_error("ERROR: --start-line
|
|
190
|
+
output_error("ERROR: --start-line must be 1 or greater")
|
|
191
191
|
return 1
|
|
192
192
|
|
|
193
193
|
if args.end_line and args.end_line < args.start_line:
|
|
194
|
-
output_error("ERROR: --end-line
|
|
194
|
+
output_error("ERROR: --end-line must be greater than or equal to --start-line")
|
|
195
195
|
return 1
|
|
196
196
|
|
|
197
197
|
if args.start_column is not None and args.start_column < 0:
|
|
198
|
-
output_error("ERROR: --start-column
|
|
198
|
+
output_error("ERROR: --start-column must be 0 or greater")
|
|
199
199
|
return 1
|
|
200
200
|
|
|
201
201
|
if args.end_column is not None and args.end_column < 0:
|
|
202
|
-
output_error("ERROR: --end-column
|
|
202
|
+
output_error("ERROR: --end-column must be 0 or greater")
|
|
203
203
|
return 1
|
|
204
204
|
|
|
205
205
|
# Query language commands
|
|
@@ -258,7 +258,7 @@ def main() -> None:
|
|
|
258
258
|
sys.exit(exit_code)
|
|
259
259
|
else:
|
|
260
260
|
if not args.file_path:
|
|
261
|
-
output_error("ERROR:
|
|
261
|
+
output_error("ERROR: File path not specified.")
|
|
262
262
|
else:
|
|
263
263
|
output_error("ERROR: 実行可能なコマンドが指定されていません。")
|
|
264
264
|
parser.print_help()
|
|
@@ -45,7 +45,6 @@ from ..utils import setup_logger
|
|
|
45
45
|
from . import MCP_INFO
|
|
46
46
|
from .resources import CodeFileResource, ProjectStatsResource
|
|
47
47
|
from .tools.base_tool import MCPTool
|
|
48
|
-
from .tools.get_positions_tool import GetPositionsTool
|
|
49
48
|
from .tools.read_partial_tool import ReadPartialTool
|
|
50
49
|
from .tools.table_format_tool import TableFormatTool
|
|
51
50
|
from .tools.universal_analyze_tool import UniversalAnalyzeTool
|
|
@@ -73,7 +72,6 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
73
72
|
# Initialize MCP tools
|
|
74
73
|
self.read_partial_tool: MCPTool = ReadPartialTool()
|
|
75
74
|
self.universal_analyze_tool: MCPTool = UniversalAnalyzeTool()
|
|
76
|
-
self.get_positions_tool: MCPTool = GetPositionsTool()
|
|
77
75
|
self.table_format_tool: MCPTool = TableFormatTool()
|
|
78
76
|
|
|
79
77
|
# Initialize MCP resources
|
|
@@ -145,7 +143,6 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
145
143
|
# Add tools from tool classes - FIXED VERSION
|
|
146
144
|
for tool_instance in [
|
|
147
145
|
self.read_partial_tool,
|
|
148
|
-
self.get_positions_tool,
|
|
149
146
|
self.table_format_tool,
|
|
150
147
|
self.universal_analyze_tool,
|
|
151
148
|
]:
|
|
@@ -181,14 +178,6 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
181
178
|
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
182
179
|
)
|
|
183
180
|
]
|
|
184
|
-
elif name == "get_code_positions":
|
|
185
|
-
result = await self.get_positions_tool.execute(arguments)
|
|
186
|
-
return [
|
|
187
|
-
TextContent(
|
|
188
|
-
type="text",
|
|
189
|
-
text=json.dumps(result, indent=2, ensure_ascii=False),
|
|
190
|
-
)
|
|
191
|
-
]
|
|
192
181
|
elif name == "format_table":
|
|
193
182
|
result = await self.table_format_tool.execute(arguments)
|
|
194
183
|
return [
|
|
@@ -24,11 +24,6 @@ AVAILABLE_TOOLS: Dict[str, Dict[str, Any]] = {
|
|
|
24
24
|
# "module": "read_partial_tool",
|
|
25
25
|
# "class": "ReadPartialTool",
|
|
26
26
|
# },
|
|
27
|
-
# "get_code_positions": {
|
|
28
|
-
# "description": "Get position information for code elements",
|
|
29
|
-
# "module": "get_positions_tool",
|
|
30
|
-
# "class": "GetPositionsTool",
|
|
31
|
-
# },
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
__all__ = [
|
|
@@ -235,7 +235,7 @@ class AnalyzeScaleTool:
|
|
|
235
235
|
elif total_lines < 1500:
|
|
236
236
|
guidance["size_category"] = "large"
|
|
237
237
|
guidance["analysis_strategy"] = (
|
|
238
|
-
"This is a large file. Use targeted analysis with
|
|
238
|
+
"This is a large file. Use targeted analysis with read_code_partial."
|
|
239
239
|
)
|
|
240
240
|
else:
|
|
241
241
|
guidance["size_category"] = "very_large"
|
|
@@ -245,7 +245,6 @@ class AnalyzeScaleTool:
|
|
|
245
245
|
|
|
246
246
|
# Recommend tools based on file size and complexity
|
|
247
247
|
if total_lines > 200:
|
|
248
|
-
guidance["recommended_tools"].append("get_code_positions")
|
|
249
248
|
guidance["recommended_tools"].append("read_code_partial")
|
|
250
249
|
|
|
251
250
|
if len(structural_overview["complexity_hotspots"]) > 0:
|
|
@@ -158,16 +158,16 @@ class ReadPartialTool:
|
|
|
158
158
|
json_output = json.dumps(result_data, indent=2, ensure_ascii=False)
|
|
159
159
|
|
|
160
160
|
# Build range info for header
|
|
161
|
-
range_info = f"
|
|
161
|
+
range_info = f"Line {start_line}"
|
|
162
162
|
if end_line:
|
|
163
163
|
range_info += f"-{end_line}"
|
|
164
164
|
|
|
165
165
|
# Build CLI-compatible output with header and JSON (without log message)
|
|
166
166
|
cli_output = (
|
|
167
|
-
f"---
|
|
168
|
-
f"
|
|
169
|
-
f"
|
|
170
|
-
f"
|
|
167
|
+
f"--- Partial Read Result ---\n"
|
|
168
|
+
f"File: {file_path}\n"
|
|
169
|
+
f"Range: {range_info}\n"
|
|
170
|
+
f"Characters read: {len(content)}\n"
|
|
171
171
|
f"{json_output}"
|
|
172
172
|
)
|
|
173
173
|
|