@embedder/embedder 1.0.7

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