@embedder/embedder 1.1.1 → 2.0.6
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.
- package/assets/_commonjsHelpers-C1uRMj_j.js +1 -0
- package/assets/cli-Ccqv_iE9.js +865 -0
- package/assets/devtools-B4JKPJyU.js +1 -0
- package/assets/index-B3hJUcJE.js +1 -0
- package/assets/index-BSFXqd02.js +1 -0
- package/assets/index-DjlgDVzW.js +1 -0
- package/cli.js +2 -0
- package/package.json +15 -96
- package/LICENSE +0 -36
- package/bundle/embedder.js +0 -618
- package/bundle/gdb-debugger-python/gdb_bridge.py +0 -392
- package/bundle/gdb-debugger-python/requirements.txt +0 -1
- package/bundle/postinstall-for-users.js +0 -515
- package/bundle/repomap-bridge.js +0 -6
- package/bundle/repomap-python/importance.py +0 -58
- package/bundle/repomap-python/queries/tree-sitter-language-pack/README.md +0 -9
- package/bundle/repomap-python/queries/tree-sitter-language-pack/arduino-tags.scm +0 -5
- package/bundle/repomap-python/queries/tree-sitter-language-pack/c-tags.scm +0 -9
- package/bundle/repomap-python/queries/tree-sitter-language-pack/chatito-tags.scm +0 -16
- package/bundle/repomap-python/queries/tree-sitter-language-pack/commonlisp-tags.scm +0 -122
- package/bundle/repomap-python/queries/tree-sitter-language-pack/cpp-tags.scm +0 -15
- package/bundle/repomap-python/queries/tree-sitter-language-pack/csharp-tags.scm +0 -26
- package/bundle/repomap-python/queries/tree-sitter-language-pack/d-tags.scm +0 -26
- package/bundle/repomap-python/queries/tree-sitter-language-pack/dart-tags.scm +0 -92
- package/bundle/repomap-python/queries/tree-sitter-language-pack/elisp-tags.scm +0 -5
- package/bundle/repomap-python/queries/tree-sitter-language-pack/elixir-tags.scm +0 -54
- package/bundle/repomap-python/queries/tree-sitter-language-pack/elm-tags.scm +0 -19
- package/bundle/repomap-python/queries/tree-sitter-language-pack/gleam-tags.scm +0 -41
- package/bundle/repomap-python/queries/tree-sitter-language-pack/go-tags.scm +0 -42
- package/bundle/repomap-python/queries/tree-sitter-language-pack/java-tags.scm +0 -20
- package/bundle/repomap-python/queries/tree-sitter-language-pack/javascript-tags.scm +0 -88
- package/bundle/repomap-python/queries/tree-sitter-language-pack/lua-tags.scm +0 -34
- package/bundle/repomap-python/queries/tree-sitter-language-pack/ocaml-tags.scm +0 -115
- package/bundle/repomap-python/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +0 -98
- package/bundle/repomap-python/queries/tree-sitter-language-pack/pony-tags.scm +0 -39
- package/bundle/repomap-python/queries/tree-sitter-language-pack/properties-tags.scm +0 -5
- package/bundle/repomap-python/queries/tree-sitter-language-pack/python-tags.scm +0 -14
- package/bundle/repomap-python/queries/tree-sitter-language-pack/r-tags.scm +0 -21
- package/bundle/repomap-python/queries/tree-sitter-language-pack/racket-tags.scm +0 -12
- package/bundle/repomap-python/queries/tree-sitter-language-pack/ruby-tags.scm +0 -64
- package/bundle/repomap-python/queries/tree-sitter-language-pack/rust-tags.scm +0 -60
- package/bundle/repomap-python/queries/tree-sitter-language-pack/solidity-tags.scm +0 -43
- package/bundle/repomap-python/queries/tree-sitter-language-pack/swift-tags.scm +0 -51
- package/bundle/repomap-python/queries/tree-sitter-language-pack/udev-tags.scm +0 -20
- package/bundle/repomap-python/queries/tree-sitter-languages/README.md +0 -24
- package/bundle/repomap-python/queries/tree-sitter-languages/c-tags.scm +0 -9
- package/bundle/repomap-python/queries/tree-sitter-languages/c_sharp-tags.scm +0 -46
- package/bundle/repomap-python/queries/tree-sitter-languages/cpp-tags.scm +0 -15
- package/bundle/repomap-python/queries/tree-sitter-languages/dart-tags.scm +0 -91
- package/bundle/repomap-python/queries/tree-sitter-languages/elisp-tags.scm +0 -8
- package/bundle/repomap-python/queries/tree-sitter-languages/elixir-tags.scm +0 -54
- package/bundle/repomap-python/queries/tree-sitter-languages/elm-tags.scm +0 -19
- package/bundle/repomap-python/queries/tree-sitter-languages/go-tags.scm +0 -30
- package/bundle/repomap-python/queries/tree-sitter-languages/hcl-tags.scm +0 -77
- package/bundle/repomap-python/queries/tree-sitter-languages/java-tags.scm +0 -20
- package/bundle/repomap-python/queries/tree-sitter-languages/javascript-tags.scm +0 -88
- package/bundle/repomap-python/queries/tree-sitter-languages/kotlin-tags.scm +0 -27
- package/bundle/repomap-python/queries/tree-sitter-languages/ocaml-tags.scm +0 -115
- package/bundle/repomap-python/queries/tree-sitter-languages/ocaml_interface-tags.scm +0 -98
- package/bundle/repomap-python/queries/tree-sitter-languages/php-tags.scm +0 -26
- package/bundle/repomap-python/queries/tree-sitter-languages/python-tags.scm +0 -12
- package/bundle/repomap-python/queries/tree-sitter-languages/ql-tags.scm +0 -26
- package/bundle/repomap-python/queries/tree-sitter-languages/ruby-tags.scm +0 -64
- package/bundle/repomap-python/queries/tree-sitter-languages/rust-tags.scm +0 -60
- package/bundle/repomap-python/queries/tree-sitter-languages/scala-tags.scm +0 -65
- package/bundle/repomap-python/queries/tree-sitter-languages/typescript-tags.scm +0 -41
- package/bundle/repomap-python/repomap.py +0 -229
- package/bundle/repomap-python/repomap_bridge.py +0 -234
- package/bundle/repomap-python/repomap_class.py +0 -637
- package/bundle/repomap-python/repomap_server.py +0 -561
- package/bundle/repomap-python/requirements.txt +0 -7
- package/bundle/repomap-python/scm.py +0 -59
- package/bundle/repomap-python/utils.py +0 -58
- package/bundle/sandbox-macos-permissive-closed.sb +0 -26
- package/bundle/sandbox-macos-permissive-open.sb +0 -19
- package/bundle/sandbox-macos-permissive-proxied.sb +0 -31
- package/bundle/sandbox-macos-restrictive-closed.sb +0 -87
- package/bundle/sandbox-macos-restrictive-open.sb +0 -90
- package/bundle/sandbox-macos-restrictive-proxied.sb +0 -92
- package/postinstall.js +0 -42
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
import logging
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import List, Optional, Dict, Any, Set
|
|
8
|
-
import dataclasses
|
|
9
|
-
|
|
10
|
-
# Add startup logging before any imports that might fail
|
|
11
|
-
startup_log = logging.getLogger('startup')
|
|
12
|
-
startup_handler = logging.StreamHandler(sys.stderr)
|
|
13
|
-
startup_handler.setLevel(logging.INFO)
|
|
14
|
-
startup_formatter = logging.Formatter('🐍 STARTUP: %(message)s')
|
|
15
|
-
startup_handler.setFormatter(startup_formatter)
|
|
16
|
-
startup_log.addHandler(startup_handler)
|
|
17
|
-
startup_log.setLevel(logging.INFO)
|
|
18
|
-
|
|
19
|
-
startup_log.info("Starting RepoMapper server initialization...")
|
|
20
|
-
startup_log.info(f"Python version: {sys.version}")
|
|
21
|
-
startup_log.info(f"Working directory: {os.getcwd()}")
|
|
22
|
-
startup_log.info(f"Script path: {__file__}")
|
|
23
|
-
startup_log.info(f"Python path: {sys.path[:3]}...") # Show first few entries
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
startup_log.info("Importing FastMCP...")
|
|
27
|
-
from fastmcp import FastMCP, settings
|
|
28
|
-
startup_log.info("✅ FastMCP imported successfully")
|
|
29
|
-
except ImportError as e:
|
|
30
|
-
startup_log.error(f"❌ Failed to import FastMCP: {e}")
|
|
31
|
-
startup_log.error("💡 Try: pip install fastmcp")
|
|
32
|
-
sys.exit(1)
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
startup_log.info("Importing RepoMapper dependencies...")
|
|
36
|
-
from repomap_class import RepoMap
|
|
37
|
-
from utils import count_tokens, read_text
|
|
38
|
-
from scm import get_scm_fname
|
|
39
|
-
from importance import filter_important_files
|
|
40
|
-
startup_log.info("✅ All RepoMapper dependencies imported successfully")
|
|
41
|
-
except ImportError as e:
|
|
42
|
-
startup_log.error(f"❌ Failed to import RepoMapper dependencies: {e}")
|
|
43
|
-
startup_log.error(f"💡 Missing dependency: {str(e).split('No module named')[-1] if 'No module named' in str(e) else 'unknown'}")
|
|
44
|
-
startup_log.error(f"Current working directory: {os.getcwd()}")
|
|
45
|
-
startup_log.error(f"Files in current directory: {list(os.listdir('.'))[:10]}")
|
|
46
|
-
sys.exit(1)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# Helper function from your CLI, useful to have here
|
|
50
|
-
def find_src_files(directory: str) -> List[str]:
|
|
51
|
-
if not os.path.isdir(directory):
|
|
52
|
-
return [directory] if os.path.isfile(directory) else []
|
|
53
|
-
src_files = []
|
|
54
|
-
for r, d, f_list in os.walk(directory):
|
|
55
|
-
d[:] = [d_name for d_name in d if not d_name.startswith('.') and d_name not in {'node_modules', '__pycache__', 'venv', 'env'}]
|
|
56
|
-
for f in f_list:
|
|
57
|
-
if not f.startswith('.'):
|
|
58
|
-
src_files.append(os.path.join(r, f))
|
|
59
|
-
return src_files
|
|
60
|
-
|
|
61
|
-
# Configure logging - show info and above
|
|
62
|
-
root_logger = logging.getLogger()
|
|
63
|
-
root_logger.setLevel(logging.INFO)
|
|
64
|
-
|
|
65
|
-
# Create console handler for info and above (explicitly use stderr)
|
|
66
|
-
console_handler = logging.StreamHandler(sys.stderr)
|
|
67
|
-
console_handler.setLevel(logging.INFO)
|
|
68
|
-
console_formatter = logging.Formatter('%(levelname)-5s %(asctime)-15s %(name)s:%(funcName)s:%(lineno)d - %(message)s')
|
|
69
|
-
console_handler.setFormatter(console_formatter)
|
|
70
|
-
root_logger.addHandler(console_handler)
|
|
71
|
-
|
|
72
|
-
# Suppress FastMCP logs
|
|
73
|
-
fastmcp_logger = logging.getLogger('fastmcp')
|
|
74
|
-
fastmcp_logger.setLevel(logging.ERROR)
|
|
75
|
-
# Suppress server startup message
|
|
76
|
-
server_logger = logging.getLogger('fastmcp.server')
|
|
77
|
-
server_logger.setLevel(logging.ERROR)
|
|
78
|
-
|
|
79
|
-
log = logging.getLogger(__name__)
|
|
80
|
-
|
|
81
|
-
# Set global stateless_http setting
|
|
82
|
-
settings.stateless_http = True
|
|
83
|
-
|
|
84
|
-
# Create MCP server
|
|
85
|
-
mcp = FastMCP("RepoMapServer")
|
|
86
|
-
|
|
87
|
-
async def _repo_map_impl(
|
|
88
|
-
project_root: str,
|
|
89
|
-
chat_files: Optional[List[str]] = None,
|
|
90
|
-
other_files: Optional[List[str]] = None,
|
|
91
|
-
token_limit: Any = 8192, # Accept any type to handle empty strings
|
|
92
|
-
exclude_unranked: bool = False,
|
|
93
|
-
force_refresh: bool = False,
|
|
94
|
-
mentioned_files: Optional[List[str]] = None,
|
|
95
|
-
mentioned_idents: Optional[List[str]] = None,
|
|
96
|
-
verbose: bool = False,
|
|
97
|
-
max_context_window: Optional[int] = None,
|
|
98
|
-
) -> Dict[str, Any]:
|
|
99
|
-
"""Generate a repository map for the specified files, providing a list of function prototypes and variables for files as well as relevant related
|
|
100
|
-
files. Provide filenames relative to the project_root. In addition to the files provided, relevant related files will also be included with a
|
|
101
|
-
very small ranking boost.
|
|
102
|
-
|
|
103
|
-
:param project_root: Root directory of the project to search. (must be an absolute path!)
|
|
104
|
-
:param chat_files: A list of file paths that are currently in the chat context. These files will receive the highest ranking.
|
|
105
|
-
:param other_files: A list of other relevant file paths in the repository to consider for the map. They receive a lower ranking boost than mentioned_files and chat_files.
|
|
106
|
-
:param token_limit: The maximum number of tokens the generated repository map should occupy. Defaults to 8192.
|
|
107
|
-
:param exclude_unranked: If True, files with a PageRank of 0.0 will be excluded from the map. Defaults to False.
|
|
108
|
-
:param force_refresh: If True, forces a refresh of the repository map cache. Defaults to False.
|
|
109
|
-
:param mentioned_files: Optional list of file paths explicitly mentioned in the conversation and receive a mid-level ranking boost.
|
|
110
|
-
:param mentioned_idents: Optional list of identifiers explicitly mentioned in the conversation, to boost their ranking.
|
|
111
|
-
:param verbose: If True, enables verbose logging for the RepoMap generation process. Defaults to False.
|
|
112
|
-
:param max_context_window: Optional maximum context window size for token calculation, used to adjust map token limit when no chat files are provided.
|
|
113
|
-
:returns: A dictionary containing:
|
|
114
|
-
- 'map': the generated repository map string
|
|
115
|
-
- 'report': a dictionary with file processing details including:
|
|
116
|
-
- 'included': list of processed files
|
|
117
|
-
- 'excluded': dictionary of excluded files with reasons
|
|
118
|
-
- 'definition_matches': count of matched definitions
|
|
119
|
-
- 'reference_matches': count of matched references
|
|
120
|
-
- 'total_files_considered': total files processed
|
|
121
|
-
Or an 'error' key if an error occurred.
|
|
122
|
-
"""
|
|
123
|
-
if not os.path.isdir(project_root):
|
|
124
|
-
return {"error": f"Project root directory not found: {project_root}"}
|
|
125
|
-
|
|
126
|
-
# 1. Handle and validate parameters
|
|
127
|
-
# Convert token_limit to integer with fallback
|
|
128
|
-
try:
|
|
129
|
-
token_limit = int(token_limit) if token_limit else 8192
|
|
130
|
-
except (TypeError, ValueError):
|
|
131
|
-
token_limit = 8192
|
|
132
|
-
|
|
133
|
-
# Ensure token_limit is positive
|
|
134
|
-
if token_limit <= 0:
|
|
135
|
-
token_limit = 8192
|
|
136
|
-
|
|
137
|
-
chat_files_list = chat_files or []
|
|
138
|
-
mentioned_fnames_set = set(mentioned_files) if mentioned_files else None
|
|
139
|
-
mentioned_idents_set = set(mentioned_idents) if mentioned_idents else None
|
|
140
|
-
|
|
141
|
-
# 2. If a specific list of other_files isn't provided, scan the whole root directory.
|
|
142
|
-
# This should happen regardless of whether chat_files are present.
|
|
143
|
-
effective_other_files = []
|
|
144
|
-
if other_files:
|
|
145
|
-
effective_other_files = other_files
|
|
146
|
-
else:
|
|
147
|
-
log.info("No other_files provided, scanning root directory for context...")
|
|
148
|
-
effective_other_files = find_src_files(project_root)
|
|
149
|
-
|
|
150
|
-
# Add a print statement for debugging so you can see what the tool is working with.
|
|
151
|
-
log.debug(f"Chat files: {chat_files_list}")
|
|
152
|
-
log.debug(f"Effective other_files count: {len(effective_other_files)}")
|
|
153
|
-
|
|
154
|
-
# If after all that we have no files, we can exit early.
|
|
155
|
-
if not chat_files_list and not effective_other_files:
|
|
156
|
-
log.info("No files to process.")
|
|
157
|
-
return {"map": "No files found to generate a map."}
|
|
158
|
-
|
|
159
|
-
# 3. Resolve paths relative to project root
|
|
160
|
-
root_path = Path(project_root).resolve()
|
|
161
|
-
abs_chat_files = [str(root_path / f) for f in chat_files_list]
|
|
162
|
-
abs_other_files = [str(root_path / f) for f in effective_other_files]
|
|
163
|
-
|
|
164
|
-
# Remove any chat files from the other_files list to avoid duplication
|
|
165
|
-
abs_chat_files_set = set(abs_chat_files)
|
|
166
|
-
abs_other_files = [f for f in abs_other_files if f not in abs_chat_files_set]
|
|
167
|
-
|
|
168
|
-
# 4. Instantiate and run RepoMap
|
|
169
|
-
try:
|
|
170
|
-
repo_mapper = RepoMap(
|
|
171
|
-
map_tokens=token_limit,
|
|
172
|
-
root=str(root_path),
|
|
173
|
-
token_counter_func=lambda text: count_tokens(text, "gpt-4"),
|
|
174
|
-
file_reader_func=read_text,
|
|
175
|
-
output_handler_funcs={'info': log.info, 'warning': log.warning, 'error': log.error},
|
|
176
|
-
verbose=verbose,
|
|
177
|
-
exclude_unranked=exclude_unranked,
|
|
178
|
-
max_context_window=max_context_window
|
|
179
|
-
)
|
|
180
|
-
except Exception as e:
|
|
181
|
-
log.exception(f"Failed to initialize RepoMap for project '{project_root}': {e}")
|
|
182
|
-
return {"error": f"Failed to initialize RepoMap: {str(e)}"}
|
|
183
|
-
|
|
184
|
-
try:
|
|
185
|
-
map_content, file_report = await asyncio.to_thread(
|
|
186
|
-
repo_mapper.get_repo_map,
|
|
187
|
-
chat_files=abs_chat_files,
|
|
188
|
-
other_files=abs_other_files,
|
|
189
|
-
mentioned_fnames=mentioned_fnames_set,
|
|
190
|
-
mentioned_idents=mentioned_idents_set,
|
|
191
|
-
force_refresh=force_refresh
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
# Convert FileReport to dictionary for JSON serialization
|
|
195
|
-
report_dict = {
|
|
196
|
-
"excluded": file_report.excluded,
|
|
197
|
-
"definition_matches": file_report.definition_matches,
|
|
198
|
-
"reference_matches": file_report.reference_matches,
|
|
199
|
-
"total_files_considered": file_report.total_files_considered
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
"map": map_content or "No repository map could be generated.",
|
|
204
|
-
"report": report_dict
|
|
205
|
-
}
|
|
206
|
-
except Exception as e:
|
|
207
|
-
log.exception(f"Error generating repository map for project '{project_root}': {e}")
|
|
208
|
-
return {"error": f"Error generating repository map: {str(e)}"}
|
|
209
|
-
|
|
210
|
-
@mcp.tool()
|
|
211
|
-
async def repo_map(
|
|
212
|
-
project_root: str,
|
|
213
|
-
chat_files: Optional[List[str]] = None,
|
|
214
|
-
other_files: Optional[List[str]] = None,
|
|
215
|
-
token_limit: Any = 8192, # Accept any type to handle empty strings
|
|
216
|
-
exclude_unranked: bool = False,
|
|
217
|
-
force_refresh: bool = False,
|
|
218
|
-
mentioned_files: Optional[List[str]] = None,
|
|
219
|
-
mentioned_idents: Optional[List[str]] = None,
|
|
220
|
-
verbose: bool = False,
|
|
221
|
-
max_context_window: Optional[int] = None,
|
|
222
|
-
) -> Dict[str, Any]:
|
|
223
|
-
"""Generate a repository map for a given project root.
|
|
224
|
-
|
|
225
|
-
Returns:
|
|
226
|
-
Dictionary containing map content and generation report
|
|
227
|
-
"""
|
|
228
|
-
return await _repo_map_impl(project_root, chat_files, other_files, token_limit, exclude_unranked, force_refresh, mentioned_files, mentioned_idents, verbose, max_context_window)
|
|
229
|
-
|
|
230
|
-
async def _search_identifiers_impl(
|
|
231
|
-
project_root: str,
|
|
232
|
-
query: str,
|
|
233
|
-
max_results: int = 50,
|
|
234
|
-
context_lines: int = 2,
|
|
235
|
-
include_definitions: bool = True,
|
|
236
|
-
include_references: bool = True
|
|
237
|
-
) -> Dict[str, Any]:
|
|
238
|
-
"""Implementation of search_identifiers without MCP decoration for direct calling.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
project_root: Root directory of the project to search. (must be an absolute path!)
|
|
242
|
-
query: Search query (identifier name)
|
|
243
|
-
max_results: Maximum number of results to return
|
|
244
|
-
context_lines: Number of lines of context to show
|
|
245
|
-
include_definitions: Whether to include definition occurrences
|
|
246
|
-
include_references: Whether to include reference occurrences
|
|
247
|
-
|
|
248
|
-
Returns:
|
|
249
|
-
Dictionary containing search results or error message
|
|
250
|
-
"""
|
|
251
|
-
if not os.path.isdir(project_root):
|
|
252
|
-
return {"error": f"Project root directory not found: {project_root}"}
|
|
253
|
-
|
|
254
|
-
try:
|
|
255
|
-
# Initialize RepoMap with search-specific settings
|
|
256
|
-
repo_map = RepoMap(
|
|
257
|
-
root=project_root,
|
|
258
|
-
token_counter_func=lambda text: count_tokens(text, "gpt-4"),
|
|
259
|
-
file_reader_func=read_text,
|
|
260
|
-
output_handler_funcs={'info': log.info, 'warning': log.warning, 'error': log.error},
|
|
261
|
-
verbose=False,
|
|
262
|
-
exclude_unranked=True
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
# Find all source files in the project
|
|
266
|
-
all_files = find_src_files(project_root)
|
|
267
|
-
|
|
268
|
-
# Get all tags (definitions and references) for all files
|
|
269
|
-
all_tags = []
|
|
270
|
-
for file_path in all_files:
|
|
271
|
-
rel_path = str(Path(file_path).relative_to(project_root))
|
|
272
|
-
tags = repo_map.get_tags(file_path, rel_path)
|
|
273
|
-
all_tags.extend(tags)
|
|
274
|
-
|
|
275
|
-
# Filter tags based on search query and options
|
|
276
|
-
matching_tags = []
|
|
277
|
-
query_lower = query.lower()
|
|
278
|
-
|
|
279
|
-
for tag in all_tags:
|
|
280
|
-
if query_lower in tag.name.lower():
|
|
281
|
-
if (tag.kind == "def" and include_definitions) or \
|
|
282
|
-
(tag.kind == "ref" and include_references):
|
|
283
|
-
matching_tags.append(tag)
|
|
284
|
-
|
|
285
|
-
# Sort by relevance (definitions first, then references)
|
|
286
|
-
matching_tags.sort(key=lambda x: (x.kind != "def", x.name.lower().find(query_lower)))
|
|
287
|
-
|
|
288
|
-
# Limit results
|
|
289
|
-
matching_tags = matching_tags[:max_results]
|
|
290
|
-
|
|
291
|
-
# Format results with context
|
|
292
|
-
results = []
|
|
293
|
-
for tag in matching_tags:
|
|
294
|
-
file_path = str(Path(project_root) / tag.rel_fname)
|
|
295
|
-
|
|
296
|
-
# Calculate context range based on context_lines parameter
|
|
297
|
-
start_line = max(1, tag.line - context_lines)
|
|
298
|
-
end_line = tag.line + context_lines
|
|
299
|
-
context_range = list(range(start_line, end_line + 1))
|
|
300
|
-
|
|
301
|
-
context = repo_map.render_tree(
|
|
302
|
-
file_path,
|
|
303
|
-
tag.rel_fname,
|
|
304
|
-
context_range
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
if context:
|
|
308
|
-
results.append({
|
|
309
|
-
"file": tag.rel_fname,
|
|
310
|
-
"line": tag.line,
|
|
311
|
-
"name": tag.name,
|
|
312
|
-
"kind": tag.kind,
|
|
313
|
-
"context": context
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
return {"results": results}
|
|
317
|
-
|
|
318
|
-
except Exception as e:
|
|
319
|
-
log.exception(f"Error searching identifiers in project '{project_root}': {e}")
|
|
320
|
-
return {"error": f"Error searching identifiers: {str(e)}"}
|
|
321
|
-
|
|
322
|
-
@mcp.tool()
|
|
323
|
-
async def search_identifiers(
|
|
324
|
-
project_root: str,
|
|
325
|
-
query: str,
|
|
326
|
-
max_results: int = 50,
|
|
327
|
-
context_lines: int = 2,
|
|
328
|
-
include_definitions: bool = True,
|
|
329
|
-
include_references: bool = True
|
|
330
|
-
) -> Dict[str, Any]:
|
|
331
|
-
"""Search for identifiers in code files. Get back a list of matching identifiers with their file, line number, and context.
|
|
332
|
-
When searching, just use the identifier name without any special characters, prefixes or suffixes. The search is
|
|
333
|
-
case-insensitive.
|
|
334
|
-
|
|
335
|
-
Args:
|
|
336
|
-
project_root: Root directory of the project to search. (must be an absolute path!)
|
|
337
|
-
query: Search query (identifier name)
|
|
338
|
-
max_results: Maximum number of results to return
|
|
339
|
-
context_lines: Number of lines of context to show
|
|
340
|
-
include_definitions: Whether to include definition occurrences
|
|
341
|
-
include_references: Whether to include reference occurrences
|
|
342
|
-
|
|
343
|
-
Returns:
|
|
344
|
-
Dictionary containing search results or error message
|
|
345
|
-
"""
|
|
346
|
-
return await _search_identifiers_impl(project_root, query, max_results, context_lines, include_definitions, include_references)
|
|
347
|
-
|
|
348
|
-
async def _warm_up_cache_impl(project_root: str) -> Dict[str, Any]:
|
|
349
|
-
"""Implementation of warm_up_cache without MCP decoration for direct calling.
|
|
350
|
-
|
|
351
|
-
Performs background indexing of the project to populate the cache.
|
|
352
|
-
This should be called when the application starts to avoid delays
|
|
353
|
-
when the user first runs search queries.
|
|
354
|
-
|
|
355
|
-
Args:
|
|
356
|
-
project_root: Root directory of the project to index
|
|
357
|
-
|
|
358
|
-
Returns:
|
|
359
|
-
Dictionary containing status or error message
|
|
360
|
-
"""
|
|
361
|
-
if not os.path.isdir(project_root):
|
|
362
|
-
return {"error": f"Project root directory not found: {project_root}"}
|
|
363
|
-
|
|
364
|
-
try:
|
|
365
|
-
log.info(f"Starting background cache warm-up for project: {project_root}")
|
|
366
|
-
|
|
367
|
-
# Initialize RepoMap with indexing-specific settings
|
|
368
|
-
repo_map = RepoMap(
|
|
369
|
-
root=project_root,
|
|
370
|
-
token_counter_func=lambda text: count_tokens(text, "gpt-4"),
|
|
371
|
-
file_reader_func=read_text,
|
|
372
|
-
output_handler_funcs={'info': log.info, 'warning': log.warning, 'error': log.error},
|
|
373
|
-
verbose=False,
|
|
374
|
-
exclude_unranked=True
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
# Find all source files in the project
|
|
378
|
-
all_files = find_src_files(project_root)
|
|
379
|
-
log.info(f"Found {len(all_files)} files to index")
|
|
380
|
-
|
|
381
|
-
# Process files in batches to avoid overwhelming the system
|
|
382
|
-
batch_size = 50
|
|
383
|
-
indexed_files = 0
|
|
384
|
-
|
|
385
|
-
for i in range(0, len(all_files), batch_size):
|
|
386
|
-
batch_files = all_files[i:i + batch_size]
|
|
387
|
-
|
|
388
|
-
for file_path in batch_files:
|
|
389
|
-
try:
|
|
390
|
-
rel_path = str(Path(file_path).relative_to(project_root))
|
|
391
|
-
# This call to get_tags will populate the cache for this file
|
|
392
|
-
tags = repo_map.get_tags(file_path, rel_path)
|
|
393
|
-
indexed_files += 1
|
|
394
|
-
|
|
395
|
-
# Log progress every 100 files
|
|
396
|
-
if indexed_files % 100 == 0:
|
|
397
|
-
log.info(f"Cache warm-up progress: {indexed_files}/{len(all_files)} files indexed")
|
|
398
|
-
|
|
399
|
-
except Exception as file_error:
|
|
400
|
-
# Log individual file errors but continue processing
|
|
401
|
-
log.warning(f"Failed to index file {file_path}: {file_error}")
|
|
402
|
-
|
|
403
|
-
# Small delay between batches to be nice to the system
|
|
404
|
-
await asyncio.sleep(0.01)
|
|
405
|
-
|
|
406
|
-
log.info(f"Cache warm-up completed. Indexed {indexed_files} files.")
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
"status": "completed",
|
|
410
|
-
"files_indexed": indexed_files,
|
|
411
|
-
"total_files": len(all_files)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
except Exception as e:
|
|
415
|
-
log.exception(f"Error during cache warm-up for project '{project_root}': {e}")
|
|
416
|
-
return {"error": f"Error during cache warm-up: {str(e)}"}
|
|
417
|
-
|
|
418
|
-
@mcp.tool()
|
|
419
|
-
async def warm_up_cache(project_root: str) -> Dict[str, Any]:
|
|
420
|
-
"""Warm up the cache by indexing all files in the project.
|
|
421
|
-
|
|
422
|
-
This is a background operation that should be called when the application
|
|
423
|
-
starts to ensure that subsequent search queries are fast.
|
|
424
|
-
|
|
425
|
-
Args:
|
|
426
|
-
project_root: Root directory of the project to index (must be an absolute path!)
|
|
427
|
-
|
|
428
|
-
Returns:
|
|
429
|
-
Dictionary containing status information or error message
|
|
430
|
-
"""
|
|
431
|
-
return await _warm_up_cache_impl(project_root)
|
|
432
|
-
|
|
433
|
-
def handle_interactive_query(query_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
434
|
-
"""Handle interactive queries from the TypeScript client"""
|
|
435
|
-
try:
|
|
436
|
-
if 'tool' not in query_data:
|
|
437
|
-
return {"error": "Missing 'tool' field in query"}
|
|
438
|
-
|
|
439
|
-
tool_name = query_data['tool']
|
|
440
|
-
args = query_data.get('args', {})
|
|
441
|
-
|
|
442
|
-
if tool_name == 'repo_map':
|
|
443
|
-
return asyncio.run(_repo_map_impl(**args))
|
|
444
|
-
elif tool_name == 'search_identifiers':
|
|
445
|
-
return asyncio.run(_search_identifiers_impl(**args))
|
|
446
|
-
elif tool_name == 'warm_up_cache':
|
|
447
|
-
return asyncio.run(_warm_up_cache_impl(**args))
|
|
448
|
-
else:
|
|
449
|
-
return {"error": f"Unknown tool: {tool_name}"}
|
|
450
|
-
except Exception as e:
|
|
451
|
-
log.exception(f"Error handling interactive query: {e}")
|
|
452
|
-
return {"error": str(e)}
|
|
453
|
-
|
|
454
|
-
def interactive_mode():
|
|
455
|
-
"""Run in interactive mode for direct TypeScript communication"""
|
|
456
|
-
log.info("Starting RepoMapper in terminal interactive mode...")
|
|
457
|
-
|
|
458
|
-
try:
|
|
459
|
-
while True:
|
|
460
|
-
# Read line from stdin
|
|
461
|
-
line = input()
|
|
462
|
-
if not line.strip():
|
|
463
|
-
continue
|
|
464
|
-
|
|
465
|
-
try:
|
|
466
|
-
# Parse the JSON query
|
|
467
|
-
query_data = json.loads(line)
|
|
468
|
-
query_id = query_data.get('id', 'unknown')
|
|
469
|
-
query = query_data.get('query', {})
|
|
470
|
-
|
|
471
|
-
# Handle the query
|
|
472
|
-
result = handle_interactive_query(query)
|
|
473
|
-
|
|
474
|
-
# Send response back
|
|
475
|
-
response = {
|
|
476
|
-
"id": query_id,
|
|
477
|
-
"result": result
|
|
478
|
-
}
|
|
479
|
-
print(json.dumps(response), flush=True)
|
|
480
|
-
|
|
481
|
-
except json.JSONDecodeError as e:
|
|
482
|
-
error_response = {
|
|
483
|
-
"id": "unknown",
|
|
484
|
-
"error": f"Invalid JSON: {str(e)}"
|
|
485
|
-
}
|
|
486
|
-
print(json.dumps(error_response), flush=True)
|
|
487
|
-
except EOFError:
|
|
488
|
-
# Client disconnected
|
|
489
|
-
break
|
|
490
|
-
except Exception as e:
|
|
491
|
-
error_response = {
|
|
492
|
-
"id": query_data.get('id', 'unknown') if 'query_data' in locals() else 'unknown',
|
|
493
|
-
"error": str(e)
|
|
494
|
-
}
|
|
495
|
-
print(json.dumps(error_response), flush=True)
|
|
496
|
-
|
|
497
|
-
except KeyboardInterrupt:
|
|
498
|
-
log.debug("Received interrupt signal, shutting down...")
|
|
499
|
-
except Exception as e:
|
|
500
|
-
log.exception(f"Fatal error in interactive mode: {e}")
|
|
501
|
-
|
|
502
|
-
# --- Main Entry Point ---
|
|
503
|
-
def main():
|
|
504
|
-
import sys
|
|
505
|
-
import argparse
|
|
506
|
-
|
|
507
|
-
startup_log.info("Parsing command line arguments...")
|
|
508
|
-
parser = argparse.ArgumentParser(description='RepoMapper Server')
|
|
509
|
-
parser.add_argument('--interactive', action='store_true',
|
|
510
|
-
help='Force interactive mode for direct stdin/stdout JSON communication')
|
|
511
|
-
parser.add_argument('--mcp-server', action='store_true',
|
|
512
|
-
help='Force MCP server mode')
|
|
513
|
-
|
|
514
|
-
args = parser.parse_args()
|
|
515
|
-
startup_log.info(f"Arguments: {args}")
|
|
516
|
-
|
|
517
|
-
# Determine which mode to run in
|
|
518
|
-
if args.interactive:
|
|
519
|
-
# Explicitly requested interactive mode
|
|
520
|
-
startup_log.info("🔄 Running in interactive mode...")
|
|
521
|
-
interactive_mode()
|
|
522
|
-
elif args.mcp_server:
|
|
523
|
-
# Explicitly requested MCP server mode
|
|
524
|
-
startup_log.info("🚀 Running in MCP server mode...")
|
|
525
|
-
log.info("Starting FastMCP server...")
|
|
526
|
-
sys.stderr.flush() # Force immediate output to stderr
|
|
527
|
-
|
|
528
|
-
try:
|
|
529
|
-
startup_log.info("🔧 Initializing FastMCP server...")
|
|
530
|
-
mcp.run()
|
|
531
|
-
except Exception as e:
|
|
532
|
-
startup_log.error(f"❌ FastMCP server failed to start: {e}")
|
|
533
|
-
startup_log.error(f"Error type: {type(e).__name__}")
|
|
534
|
-
import traceback
|
|
535
|
-
startup_log.error(f"Traceback: {traceback.format_exc()}")
|
|
536
|
-
sys.exit(1)
|
|
537
|
-
else:
|
|
538
|
-
# Auto-detect based on stdin
|
|
539
|
-
startup_log.info("🔍 Auto-detecting mode based on stdin...")
|
|
540
|
-
if sys.stdin.isatty():
|
|
541
|
-
# Running from terminal - use interactive mode for direct communication
|
|
542
|
-
startup_log.info("📺 TTY detected, using interactive mode")
|
|
543
|
-
interactive_mode()
|
|
544
|
-
else:
|
|
545
|
-
# Running as subprocess - default to interactive mode for TypeScript tools
|
|
546
|
-
# (This fixes the issue where piped stdin was incorrectly starting MCP server)
|
|
547
|
-
startup_log.info("🔗 Non-TTY detected, using interactive mode for subprocess")
|
|
548
|
-
interactive_mode()
|
|
549
|
-
|
|
550
|
-
if __name__ == "__main__":
|
|
551
|
-
try:
|
|
552
|
-
startup_log.info("🎬 Starting main function...")
|
|
553
|
-
main()
|
|
554
|
-
except KeyboardInterrupt:
|
|
555
|
-
startup_log.info("🛑 Interrupted by user")
|
|
556
|
-
sys.exit(0)
|
|
557
|
-
except Exception as e:
|
|
558
|
-
startup_log.error(f"💥 Fatal error in main: {e}")
|
|
559
|
-
import traceback
|
|
560
|
-
startup_log.error(f"Traceback: {traceback.format_exc()}")
|
|
561
|
-
sys.exit(1)
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
SCM file handling for RepoMap.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
def get_scm_fname(lang: str) -> Optional[str]:
|
|
9
|
-
"""Get the SCM query file for a language."""
|
|
10
|
-
scm_files = {
|
|
11
|
-
'arduino': 'arduino-tags.scm',
|
|
12
|
-
'chatito': 'chatito-tags.scm',
|
|
13
|
-
'commonlisp': 'commonlisp-tags.scm',
|
|
14
|
-
'cpp': 'cpp-tags.scm',
|
|
15
|
-
'csharp': 'csharp-tags.scm',
|
|
16
|
-
'c': 'c-tags.scm',
|
|
17
|
-
'dart': 'dart-tags.scm',
|
|
18
|
-
'd': 'd-tags.scm',
|
|
19
|
-
'elisp': 'elisp-tags.scm',
|
|
20
|
-
'elixir': 'elixir-tags.scm',
|
|
21
|
-
'elm': 'elm-tags.scm',
|
|
22
|
-
'gleam': 'gleam-tags.scm',
|
|
23
|
-
'go': 'go-tags.scm',
|
|
24
|
-
'javascript': 'javascript-tags.scm',
|
|
25
|
-
'java': 'java-tags.scm',
|
|
26
|
-
'lua': 'lua-tags.scm',
|
|
27
|
-
'ocaml_interface': 'ocaml_interface-tags.scm',
|
|
28
|
-
'ocaml': 'ocaml-tags.scm',
|
|
29
|
-
'pony': 'pony-tags.scm',
|
|
30
|
-
'properties': 'properties-tags.scm',
|
|
31
|
-
'python': 'python-tags.scm',
|
|
32
|
-
'racket': 'racket-tags.scm',
|
|
33
|
-
'r': 'r-tags.scm',
|
|
34
|
-
'ruby': 'ruby-tags.scm',
|
|
35
|
-
'rust': 'rust-tags.scm',
|
|
36
|
-
'solidity': 'solidity-tags.scm',
|
|
37
|
-
'swift': 'swift-tags.scm',
|
|
38
|
-
'udev': 'udev-tags.scm',
|
|
39
|
-
'c_sharp': 'c_sharp-tags.scm',
|
|
40
|
-
'hcl': 'hcl-tags.scm',
|
|
41
|
-
'kotlin': 'kotlin-tags.scm',
|
|
42
|
-
'php': 'php-tags.scm',
|
|
43
|
-
'ql': 'ql-tags.scm',
|
|
44
|
-
'scala': 'scala-tags.scm',
|
|
45
|
-
'typescript': 'typescript-tags.scm',
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if lang in scm_files:
|
|
49
|
-
scm_filename = scm_files[lang]
|
|
50
|
-
# Search in tree-sitter-language-pack
|
|
51
|
-
scm_path = Path(__file__).parent / "queries" / "tree-sitter-language-pack" / scm_filename
|
|
52
|
-
if scm_path.exists():
|
|
53
|
-
return str(scm_path)
|
|
54
|
-
# Search in tree-sitter-languages
|
|
55
|
-
scm_path = Path(__file__).parent / "queries" / "tree-sitter-languages" / scm_filename
|
|
56
|
-
if scm_path.exists():
|
|
57
|
-
return str(scm_path)
|
|
58
|
-
|
|
59
|
-
return None
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Utility functions for RepoMap.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Optional, List
|
|
9
|
-
from collections import namedtuple
|
|
10
|
-
|
|
11
|
-
try:
|
|
12
|
-
import tiktoken
|
|
13
|
-
except ImportError:
|
|
14
|
-
print("Error: tiktoken is required. Install with: pip install tiktoken")
|
|
15
|
-
sys.exit(1)
|
|
16
|
-
|
|
17
|
-
# Tag namedtuple for storing parsed code definitions and references
|
|
18
|
-
Tag = namedtuple("Tag", "rel_fname fname line name kind".split())
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def count_tokens(text: str, model_name: str = "gpt-4") -> int:
|
|
22
|
-
"""Count tokens in text using tiktoken."""
|
|
23
|
-
if not text:
|
|
24
|
-
return 0
|
|
25
|
-
|
|
26
|
-
try:
|
|
27
|
-
encoding = tiktoken.encoding_for_model(model_name)
|
|
28
|
-
except KeyError:
|
|
29
|
-
# Fallback for unknown models
|
|
30
|
-
encoding = tiktoken.get_encoding("cl100k_base")
|
|
31
|
-
|
|
32
|
-
return len(encoding.encode(text))
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def read_text(filename: str, encoding: str = "utf-8", silent: bool = False) -> Optional[str]:
|
|
36
|
-
"""Read text from file with error handling."""
|
|
37
|
-
try:
|
|
38
|
-
return Path(filename).read_text(encoding=encoding, errors='ignore')
|
|
39
|
-
except FileNotFoundError:
|
|
40
|
-
if not silent:
|
|
41
|
-
print(f"Error: {filename} not found.")
|
|
42
|
-
return None
|
|
43
|
-
except IsADirectoryError:
|
|
44
|
-
if not silent:
|
|
45
|
-
print(f"Error: {filename} is a directory.")
|
|
46
|
-
return None
|
|
47
|
-
except OSError as e:
|
|
48
|
-
if not silent:
|
|
49
|
-
print(f"Error reading {filename}: {e}")
|
|
50
|
-
return None
|
|
51
|
-
except UnicodeError as e:
|
|
52
|
-
if not silent:
|
|
53
|
-
print(f"Error decoding {filename}: {e}")
|
|
54
|
-
return None
|
|
55
|
-
except Exception as e:
|
|
56
|
-
if not silent:
|
|
57
|
-
print(f"An unexpected error occurred while reading {filename}: {e}")
|
|
58
|
-
return None
|