@embedder/embedder 1.1.1 → 2.0.5

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 (80) hide show
  1. package/assets/_commonjsHelpers-C1uRMj_j.js +1 -0
  2. package/assets/cli-DIXdkQBM.js +887 -0
  3. package/assets/devtools-B4JKPJyU.js +1 -0
  4. package/assets/index-B3hJUcJE.js +1 -0
  5. package/assets/index-BCdeyKtY.js +1 -0
  6. package/assets/index-oEV_emLn.js +1 -0
  7. package/cli.js +2 -0
  8. package/package.json +15 -96
  9. package/LICENSE +0 -36
  10. package/bundle/embedder.js +0 -618
  11. package/bundle/gdb-debugger-python/gdb_bridge.py +0 -392
  12. package/bundle/gdb-debugger-python/requirements.txt +0 -1
  13. package/bundle/postinstall-for-users.js +0 -515
  14. package/bundle/repomap-bridge.js +0 -6
  15. package/bundle/repomap-python/importance.py +0 -58
  16. package/bundle/repomap-python/queries/tree-sitter-language-pack/README.md +0 -9
  17. package/bundle/repomap-python/queries/tree-sitter-language-pack/arduino-tags.scm +0 -5
  18. package/bundle/repomap-python/queries/tree-sitter-language-pack/c-tags.scm +0 -9
  19. package/bundle/repomap-python/queries/tree-sitter-language-pack/chatito-tags.scm +0 -16
  20. package/bundle/repomap-python/queries/tree-sitter-language-pack/commonlisp-tags.scm +0 -122
  21. package/bundle/repomap-python/queries/tree-sitter-language-pack/cpp-tags.scm +0 -15
  22. package/bundle/repomap-python/queries/tree-sitter-language-pack/csharp-tags.scm +0 -26
  23. package/bundle/repomap-python/queries/tree-sitter-language-pack/d-tags.scm +0 -26
  24. package/bundle/repomap-python/queries/tree-sitter-language-pack/dart-tags.scm +0 -92
  25. package/bundle/repomap-python/queries/tree-sitter-language-pack/elisp-tags.scm +0 -5
  26. package/bundle/repomap-python/queries/tree-sitter-language-pack/elixir-tags.scm +0 -54
  27. package/bundle/repomap-python/queries/tree-sitter-language-pack/elm-tags.scm +0 -19
  28. package/bundle/repomap-python/queries/tree-sitter-language-pack/gleam-tags.scm +0 -41
  29. package/bundle/repomap-python/queries/tree-sitter-language-pack/go-tags.scm +0 -42
  30. package/bundle/repomap-python/queries/tree-sitter-language-pack/java-tags.scm +0 -20
  31. package/bundle/repomap-python/queries/tree-sitter-language-pack/javascript-tags.scm +0 -88
  32. package/bundle/repomap-python/queries/tree-sitter-language-pack/lua-tags.scm +0 -34
  33. package/bundle/repomap-python/queries/tree-sitter-language-pack/ocaml-tags.scm +0 -115
  34. package/bundle/repomap-python/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +0 -98
  35. package/bundle/repomap-python/queries/tree-sitter-language-pack/pony-tags.scm +0 -39
  36. package/bundle/repomap-python/queries/tree-sitter-language-pack/properties-tags.scm +0 -5
  37. package/bundle/repomap-python/queries/tree-sitter-language-pack/python-tags.scm +0 -14
  38. package/bundle/repomap-python/queries/tree-sitter-language-pack/r-tags.scm +0 -21
  39. package/bundle/repomap-python/queries/tree-sitter-language-pack/racket-tags.scm +0 -12
  40. package/bundle/repomap-python/queries/tree-sitter-language-pack/ruby-tags.scm +0 -64
  41. package/bundle/repomap-python/queries/tree-sitter-language-pack/rust-tags.scm +0 -60
  42. package/bundle/repomap-python/queries/tree-sitter-language-pack/solidity-tags.scm +0 -43
  43. package/bundle/repomap-python/queries/tree-sitter-language-pack/swift-tags.scm +0 -51
  44. package/bundle/repomap-python/queries/tree-sitter-language-pack/udev-tags.scm +0 -20
  45. package/bundle/repomap-python/queries/tree-sitter-languages/README.md +0 -24
  46. package/bundle/repomap-python/queries/tree-sitter-languages/c-tags.scm +0 -9
  47. package/bundle/repomap-python/queries/tree-sitter-languages/c_sharp-tags.scm +0 -46
  48. package/bundle/repomap-python/queries/tree-sitter-languages/cpp-tags.scm +0 -15
  49. package/bundle/repomap-python/queries/tree-sitter-languages/dart-tags.scm +0 -91
  50. package/bundle/repomap-python/queries/tree-sitter-languages/elisp-tags.scm +0 -8
  51. package/bundle/repomap-python/queries/tree-sitter-languages/elixir-tags.scm +0 -54
  52. package/bundle/repomap-python/queries/tree-sitter-languages/elm-tags.scm +0 -19
  53. package/bundle/repomap-python/queries/tree-sitter-languages/go-tags.scm +0 -30
  54. package/bundle/repomap-python/queries/tree-sitter-languages/hcl-tags.scm +0 -77
  55. package/bundle/repomap-python/queries/tree-sitter-languages/java-tags.scm +0 -20
  56. package/bundle/repomap-python/queries/tree-sitter-languages/javascript-tags.scm +0 -88
  57. package/bundle/repomap-python/queries/tree-sitter-languages/kotlin-tags.scm +0 -27
  58. package/bundle/repomap-python/queries/tree-sitter-languages/ocaml-tags.scm +0 -115
  59. package/bundle/repomap-python/queries/tree-sitter-languages/ocaml_interface-tags.scm +0 -98
  60. package/bundle/repomap-python/queries/tree-sitter-languages/php-tags.scm +0 -26
  61. package/bundle/repomap-python/queries/tree-sitter-languages/python-tags.scm +0 -12
  62. package/bundle/repomap-python/queries/tree-sitter-languages/ql-tags.scm +0 -26
  63. package/bundle/repomap-python/queries/tree-sitter-languages/ruby-tags.scm +0 -64
  64. package/bundle/repomap-python/queries/tree-sitter-languages/rust-tags.scm +0 -60
  65. package/bundle/repomap-python/queries/tree-sitter-languages/scala-tags.scm +0 -65
  66. package/bundle/repomap-python/queries/tree-sitter-languages/typescript-tags.scm +0 -41
  67. package/bundle/repomap-python/repomap.py +0 -229
  68. package/bundle/repomap-python/repomap_bridge.py +0 -234
  69. package/bundle/repomap-python/repomap_class.py +0 -637
  70. package/bundle/repomap-python/repomap_server.py +0 -561
  71. package/bundle/repomap-python/requirements.txt +0 -7
  72. package/bundle/repomap-python/scm.py +0 -59
  73. package/bundle/repomap-python/utils.py +0 -58
  74. package/bundle/sandbox-macos-permissive-closed.sb +0 -26
  75. package/bundle/sandbox-macos-permissive-open.sb +0 -19
  76. package/bundle/sandbox-macos-permissive-proxied.sb +0 -31
  77. package/bundle/sandbox-macos-restrictive-closed.sb +0 -87
  78. package/bundle/sandbox-macos-restrictive-open.sb +0 -90
  79. package/bundle/sandbox-macos-restrictive-proxied.sb +0 -92
  80. 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,7 +0,0 @@
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
@@ -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