@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,637 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
RepoMap class for generating repository maps.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from collections import namedtuple, defaultdict
|
|
9
|
-
from typing import List, Dict, Set, Optional, Tuple, Callable, Any, Union
|
|
10
|
-
import shutil
|
|
11
|
-
import sqlite3
|
|
12
|
-
from utils import Tag
|
|
13
|
-
from dataclasses import dataclass
|
|
14
|
-
import diskcache
|
|
15
|
-
import networkx as nx
|
|
16
|
-
import diskcache
|
|
17
|
-
from grep_ast import TreeContext
|
|
18
|
-
from utils import count_tokens, read_text, Tag
|
|
19
|
-
from scm import get_scm_fname
|
|
20
|
-
from importance import filter_important_files
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@dataclass
|
|
24
|
-
class FileReport:
|
|
25
|
-
excluded: Dict[str, str] # File -> exclusion reason with status
|
|
26
|
-
definition_matches: int # Total definition tags
|
|
27
|
-
reference_matches: int # Total reference tags
|
|
28
|
-
total_files_considered: int # Total files provided as input
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Constants
|
|
33
|
-
CACHE_VERSION = 1
|
|
34
|
-
import os
|
|
35
|
-
|
|
36
|
-
TAGS_CACHE_DIR = os.path.join(os.getcwd(), f".repomap.tags.cache.v{CACHE_VERSION}")
|
|
37
|
-
SQLITE_ERRORS = (sqlite3.OperationalError, sqlite3.DatabaseError)
|
|
38
|
-
|
|
39
|
-
# Tag namedtuple for storing parsed code definitions and references
|
|
40
|
-
Tag = namedtuple("Tag", "rel_fname fname line name kind".split())
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class RepoMap:
|
|
44
|
-
"""Main class for generating repository maps."""
|
|
45
|
-
|
|
46
|
-
def __init__(
|
|
47
|
-
self,
|
|
48
|
-
map_tokens: int = 1024,
|
|
49
|
-
root: str = None,
|
|
50
|
-
token_counter_func: Callable[[str], int] = count_tokens,
|
|
51
|
-
file_reader_func: Callable[[str], Optional[str]] = read_text,
|
|
52
|
-
output_handler_funcs: Dict[str, Callable] = None,
|
|
53
|
-
repo_content_prefix: Optional[str] = None,
|
|
54
|
-
verbose: bool = False,
|
|
55
|
-
max_context_window: Optional[int] = None,
|
|
56
|
-
map_mul_no_files: int = 8,
|
|
57
|
-
refresh: str = "auto",
|
|
58
|
-
exclude_unranked: bool = False
|
|
59
|
-
):
|
|
60
|
-
"""Initialize RepoMap instance."""
|
|
61
|
-
self.map_tokens = map_tokens
|
|
62
|
-
self.max_map_tokens = map_tokens
|
|
63
|
-
self.root = Path(root or os.getcwd()).resolve()
|
|
64
|
-
self.token_count_func_internal = token_counter_func
|
|
65
|
-
self.read_text_func_internal = file_reader_func
|
|
66
|
-
self.repo_content_prefix = repo_content_prefix
|
|
67
|
-
self.verbose = verbose
|
|
68
|
-
self.max_context_window = max_context_window
|
|
69
|
-
self.map_mul_no_files = map_mul_no_files
|
|
70
|
-
self.refresh = refresh
|
|
71
|
-
self.exclude_unranked = exclude_unranked
|
|
72
|
-
|
|
73
|
-
# Set up output handlers
|
|
74
|
-
if output_handler_funcs is None:
|
|
75
|
-
output_handler_funcs = {
|
|
76
|
-
'info': print,
|
|
77
|
-
'warning': print,
|
|
78
|
-
'error': print
|
|
79
|
-
}
|
|
80
|
-
self.output_handlers = output_handler_funcs
|
|
81
|
-
|
|
82
|
-
# Initialize caches
|
|
83
|
-
self.tree_cache = {}
|
|
84
|
-
self.tree_context_cache = {}
|
|
85
|
-
self.map_cache = {}
|
|
86
|
-
|
|
87
|
-
# Load persistent tags cache
|
|
88
|
-
self.load_tags_cache()
|
|
89
|
-
|
|
90
|
-
def load_tags_cache(self):
|
|
91
|
-
"""Load the persistent tags cache."""
|
|
92
|
-
cache_dir = self.root / TAGS_CACHE_DIR
|
|
93
|
-
try:
|
|
94
|
-
self.TAGS_CACHE = diskcache.Cache(str(cache_dir))
|
|
95
|
-
except Exception as e:
|
|
96
|
-
self.output_handlers['warning'](f"Failed to load tags cache: {e}")
|
|
97
|
-
self.TAGS_CACHE = {}
|
|
98
|
-
|
|
99
|
-
def save_tags_cache(self):
|
|
100
|
-
"""Save the tags cache (no-op as diskcache handles persistence)."""
|
|
101
|
-
pass
|
|
102
|
-
|
|
103
|
-
def tags_cache_error(self):
|
|
104
|
-
"""Handle tags cache errors."""
|
|
105
|
-
try:
|
|
106
|
-
cache_dir = self.root / TAGS_CACHE_DIR
|
|
107
|
-
if cache_dir.exists():
|
|
108
|
-
shutil.rmtree(cache_dir)
|
|
109
|
-
self.load_tags_cache()
|
|
110
|
-
except Exception:
|
|
111
|
-
self.output_handlers['warning']("Failed to recreate tags cache, using in-memory cache")
|
|
112
|
-
self.TAGS_CACHE = {}
|
|
113
|
-
|
|
114
|
-
def token_count(self, text: str) -> int:
|
|
115
|
-
"""Count tokens in text with sampling optimization for long texts."""
|
|
116
|
-
if not text:
|
|
117
|
-
return 0
|
|
118
|
-
|
|
119
|
-
len_text = len(text)
|
|
120
|
-
if len_text < 200:
|
|
121
|
-
return self.token_count_func_internal(text)
|
|
122
|
-
|
|
123
|
-
# Sample for longer texts
|
|
124
|
-
lines = text.splitlines(keepends=True)
|
|
125
|
-
num_lines = len(lines)
|
|
126
|
-
|
|
127
|
-
step = max(1, num_lines // 100)
|
|
128
|
-
sampled_lines = lines[::step]
|
|
129
|
-
sample_text = "".join(sampled_lines)
|
|
130
|
-
|
|
131
|
-
if not sample_text:
|
|
132
|
-
return self.token_count_func_internal(text)
|
|
133
|
-
|
|
134
|
-
sample_tokens = self.token_count_func_internal(sample_text)
|
|
135
|
-
|
|
136
|
-
if len(sample_text) == 0:
|
|
137
|
-
return self.token_count_func_internal(text)
|
|
138
|
-
|
|
139
|
-
est_tokens = (sample_tokens / len(sample_text)) * len_text
|
|
140
|
-
return int(est_tokens)
|
|
141
|
-
|
|
142
|
-
def get_rel_fname(self, fname: str) -> str:
|
|
143
|
-
"""Get relative filename from absolute path."""
|
|
144
|
-
try:
|
|
145
|
-
return str(Path(fname).relative_to(self.root))
|
|
146
|
-
except ValueError:
|
|
147
|
-
return fname
|
|
148
|
-
|
|
149
|
-
def get_mtime(self, fname: str) -> Optional[float]:
|
|
150
|
-
"""Get file modification time."""
|
|
151
|
-
try:
|
|
152
|
-
return os.path.getmtime(fname)
|
|
153
|
-
except FileNotFoundError:
|
|
154
|
-
self.output_handlers['warning'](f"File not found: {fname}")
|
|
155
|
-
return None
|
|
156
|
-
|
|
157
|
-
def get_tags(self, fname: str, rel_fname: str) -> List[Tag]:
|
|
158
|
-
"""Get tags for a file, using cache when possible."""
|
|
159
|
-
file_mtime = self.get_mtime(fname)
|
|
160
|
-
if file_mtime is None:
|
|
161
|
-
return []
|
|
162
|
-
|
|
163
|
-
try:
|
|
164
|
-
# Handle both diskcache Cache and in-memory dict
|
|
165
|
-
if isinstance(self.TAGS_CACHE, dict):
|
|
166
|
-
cached_entry = self.TAGS_CACHE.get(fname)
|
|
167
|
-
else:
|
|
168
|
-
cached_entry = self.TAGS_CACHE.get(fname)
|
|
169
|
-
|
|
170
|
-
if cached_entry and cached_entry.get("mtime") == file_mtime:
|
|
171
|
-
return cached_entry["data"]
|
|
172
|
-
except SQLITE_ERRORS:
|
|
173
|
-
self.tags_cache_error()
|
|
174
|
-
|
|
175
|
-
# Cache miss or file changed
|
|
176
|
-
tags = self.get_tags_raw(fname, rel_fname)
|
|
177
|
-
|
|
178
|
-
try:
|
|
179
|
-
self.TAGS_CACHE[fname] = {"mtime": file_mtime, "data": tags}
|
|
180
|
-
except SQLITE_ERRORS:
|
|
181
|
-
self.tags_cache_error()
|
|
182
|
-
|
|
183
|
-
return tags
|
|
184
|
-
|
|
185
|
-
def get_tags_raw(self, fname: str, rel_fname: str) -> List[Tag]:
|
|
186
|
-
"""Parse file to extract tags using Tree-sitter."""
|
|
187
|
-
try:
|
|
188
|
-
import tree_sitter
|
|
189
|
-
from grep_ast import filename_to_lang
|
|
190
|
-
from grep_ast.tsl import get_language, get_parser
|
|
191
|
-
except ImportError:
|
|
192
|
-
print("Error: tree-sitter and grep-ast are required. Install with: pip install tree-sitter grep-ast")
|
|
193
|
-
sys.exit(1)
|
|
194
|
-
|
|
195
|
-
lang = filename_to_lang(fname)
|
|
196
|
-
if not lang:
|
|
197
|
-
return []
|
|
198
|
-
|
|
199
|
-
try:
|
|
200
|
-
language = get_language(lang)
|
|
201
|
-
parser = get_parser(lang)
|
|
202
|
-
except Exception as err:
|
|
203
|
-
self.output_handlers['error'](f"Skipping file {fname}: {err}")
|
|
204
|
-
return []
|
|
205
|
-
|
|
206
|
-
scm_fname = get_scm_fname(lang)
|
|
207
|
-
if not scm_fname:
|
|
208
|
-
return []
|
|
209
|
-
|
|
210
|
-
code = self.read_text_func_internal(fname)
|
|
211
|
-
if not code:
|
|
212
|
-
return []
|
|
213
|
-
|
|
214
|
-
try:
|
|
215
|
-
tree = parser.parse(bytes(code, "utf-8"))
|
|
216
|
-
|
|
217
|
-
# Load query from SCM file
|
|
218
|
-
query_text = read_text(scm_fname, silent=True)
|
|
219
|
-
if not query_text:
|
|
220
|
-
return []
|
|
221
|
-
|
|
222
|
-
query = tree_sitter.Query(language, query_text)
|
|
223
|
-
cursor = tree_sitter.QueryCursor(query)
|
|
224
|
-
matches = cursor.matches(tree.root_node)
|
|
225
|
-
|
|
226
|
-
tags = []
|
|
227
|
-
# Process matches as (pattern_index, captures_dict) tuples
|
|
228
|
-
for pattern_index, captures_dict in matches:
|
|
229
|
-
for capture_name, nodes in captures_dict.items():
|
|
230
|
-
if "name.definition" in capture_name:
|
|
231
|
-
kind = "def"
|
|
232
|
-
elif "name.reference" in capture_name:
|
|
233
|
-
kind = "ref"
|
|
234
|
-
else:
|
|
235
|
-
# Skip other capture types like 'reference.call' if not needed for tagging
|
|
236
|
-
continue
|
|
237
|
-
|
|
238
|
-
# Process all nodes for this capture
|
|
239
|
-
for node in nodes:
|
|
240
|
-
line_num = node.start_point[0] + 1
|
|
241
|
-
# Handle potential None value
|
|
242
|
-
name = node.text.decode('utf-8') if node.text else ""
|
|
243
|
-
|
|
244
|
-
tags.append(Tag(
|
|
245
|
-
rel_fname=rel_fname,
|
|
246
|
-
fname=fname,
|
|
247
|
-
line=line_num,
|
|
248
|
-
name=name,
|
|
249
|
-
kind=kind
|
|
250
|
-
))
|
|
251
|
-
|
|
252
|
-
return tags
|
|
253
|
-
|
|
254
|
-
except Exception as e:
|
|
255
|
-
self.output_handlers['error'](f"Error parsing {fname}: {e}")
|
|
256
|
-
return []
|
|
257
|
-
|
|
258
|
-
def get_ranked_tags(
|
|
259
|
-
self,
|
|
260
|
-
chat_fnames: List[str],
|
|
261
|
-
other_fnames: List[str],
|
|
262
|
-
mentioned_fnames: Optional[Set[str]] = None,
|
|
263
|
-
mentioned_idents: Optional[Set[str]] = None
|
|
264
|
-
) -> Tuple[List[Tuple[float, Tag]], FileReport]:
|
|
265
|
-
"""Get ranked tags using PageRank algorithm with file report."""
|
|
266
|
-
# Return empty list and empty report if no files
|
|
267
|
-
if not chat_fnames and not other_fnames:
|
|
268
|
-
return [], FileReport([], {}, 0, 0, 0)
|
|
269
|
-
|
|
270
|
-
# Initialize file report early
|
|
271
|
-
included: List[str] = []
|
|
272
|
-
excluded: Dict[str, str] = {}
|
|
273
|
-
total_definitions = 0
|
|
274
|
-
total_references = 0
|
|
275
|
-
if mentioned_fnames is None:
|
|
276
|
-
mentioned_fnames = set()
|
|
277
|
-
if mentioned_idents is None:
|
|
278
|
-
mentioned_idents = set()
|
|
279
|
-
|
|
280
|
-
# Normalize paths to absolute
|
|
281
|
-
def normalize_path(path):
|
|
282
|
-
return str(Path(path).resolve())
|
|
283
|
-
|
|
284
|
-
chat_fnames = [normalize_path(f) for f in chat_fnames]
|
|
285
|
-
other_fnames = [normalize_path(f) for f in other_fnames]
|
|
286
|
-
|
|
287
|
-
# Initialize file report
|
|
288
|
-
included: List[str] = []
|
|
289
|
-
excluded: Dict[str, str] = {}
|
|
290
|
-
input_files: Dict[str, Dict] = {}
|
|
291
|
-
total_definitions = 0
|
|
292
|
-
total_references = 0
|
|
293
|
-
|
|
294
|
-
# Collect all tags
|
|
295
|
-
defines = defaultdict(set)
|
|
296
|
-
references = defaultdict(set)
|
|
297
|
-
definitions = defaultdict(set)
|
|
298
|
-
|
|
299
|
-
personalization = {}
|
|
300
|
-
chat_rel_fnames = set(self.get_rel_fname(f) for f in chat_fnames)
|
|
301
|
-
|
|
302
|
-
all_fnames = list(set(chat_fnames + other_fnames))
|
|
303
|
-
|
|
304
|
-
for fname in all_fnames:
|
|
305
|
-
rel_fname = self.get_rel_fname(fname)
|
|
306
|
-
|
|
307
|
-
if not os.path.exists(fname):
|
|
308
|
-
reason = "File not found"
|
|
309
|
-
excluded[fname] = reason
|
|
310
|
-
self.output_handlers['warning'](f"Repo-map can't include {fname}: {reason}")
|
|
311
|
-
continue
|
|
312
|
-
|
|
313
|
-
included.append(fname)
|
|
314
|
-
|
|
315
|
-
tags = self.get_tags(fname, rel_fname)
|
|
316
|
-
|
|
317
|
-
for tag in tags:
|
|
318
|
-
if tag.kind == "def":
|
|
319
|
-
defines[tag.name].add(rel_fname)
|
|
320
|
-
definitions[rel_fname].add(tag.name)
|
|
321
|
-
total_definitions += 1
|
|
322
|
-
elif tag.kind == "ref":
|
|
323
|
-
references[tag.name].add(rel_fname)
|
|
324
|
-
total_references += 1
|
|
325
|
-
|
|
326
|
-
# Set personalization for chat files
|
|
327
|
-
if fname in chat_fnames:
|
|
328
|
-
personalization[rel_fname] = 100.0
|
|
329
|
-
|
|
330
|
-
# Build graph
|
|
331
|
-
G = nx.MultiDiGraph()
|
|
332
|
-
|
|
333
|
-
# Add nodes
|
|
334
|
-
for fname in all_fnames:
|
|
335
|
-
rel_fname = self.get_rel_fname(fname)
|
|
336
|
-
G.add_node(rel_fname)
|
|
337
|
-
|
|
338
|
-
# Add edges based on references
|
|
339
|
-
for name, ref_fnames in references.items():
|
|
340
|
-
def_fnames = defines.get(name, set())
|
|
341
|
-
for ref_fname in ref_fnames:
|
|
342
|
-
for def_fname in def_fnames:
|
|
343
|
-
if ref_fname != def_fname:
|
|
344
|
-
G.add_edge(ref_fname, def_fname, name=name)
|
|
345
|
-
|
|
346
|
-
if not G.nodes():
|
|
347
|
-
return [], file_report
|
|
348
|
-
|
|
349
|
-
# Run PageRank
|
|
350
|
-
try:
|
|
351
|
-
if personalization:
|
|
352
|
-
ranks = nx.pagerank(G, personalization=personalization, alpha=0.85)
|
|
353
|
-
else:
|
|
354
|
-
ranks = {node: 1.0 for node in G.nodes()}
|
|
355
|
-
except:
|
|
356
|
-
# Fallback to uniform ranking
|
|
357
|
-
ranks = {node: 1.0 for node in G.nodes()}
|
|
358
|
-
|
|
359
|
-
# Update excluded dictionary with status information
|
|
360
|
-
for fname in set(chat_fnames + other_fnames):
|
|
361
|
-
if fname in excluded:
|
|
362
|
-
# Add status prefix to existing exclusion reason
|
|
363
|
-
excluded[fname] = f"[EXCLUDED] {excluded[fname]}"
|
|
364
|
-
elif fname not in included:
|
|
365
|
-
excluded[fname] = "[NOT PROCESSED] File not included in final processing"
|
|
366
|
-
|
|
367
|
-
# Create file report
|
|
368
|
-
file_report = FileReport(
|
|
369
|
-
excluded=excluded,
|
|
370
|
-
definition_matches=total_definitions,
|
|
371
|
-
reference_matches=total_references,
|
|
372
|
-
total_files_considered=len(all_fnames)
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
# Collect and rank tags
|
|
376
|
-
ranked_tags = []
|
|
377
|
-
|
|
378
|
-
for fname in included:
|
|
379
|
-
rel_fname = self.get_rel_fname(fname)
|
|
380
|
-
file_rank = ranks.get(rel_fname, 0.0)
|
|
381
|
-
|
|
382
|
-
# Exclude files with low Page Rank if exclude_unranked is True
|
|
383
|
-
if self.exclude_unranked and file_rank <= 0.0001: # Use a small threshold to exclude near-zero ranks
|
|
384
|
-
continue
|
|
385
|
-
|
|
386
|
-
tags = self.get_tags(fname, rel_fname)
|
|
387
|
-
for tag in tags:
|
|
388
|
-
if tag.kind == "def":
|
|
389
|
-
# Boost for mentioned identifiers
|
|
390
|
-
boost = 1.0
|
|
391
|
-
if tag.name in mentioned_idents:
|
|
392
|
-
boost *= 10.0
|
|
393
|
-
if rel_fname in mentioned_fnames:
|
|
394
|
-
boost *= 5.0
|
|
395
|
-
if rel_fname in chat_rel_fnames:
|
|
396
|
-
boost *= 20.0
|
|
397
|
-
|
|
398
|
-
final_rank = file_rank * boost
|
|
399
|
-
ranked_tags.append((final_rank, tag))
|
|
400
|
-
|
|
401
|
-
# Sort by rank (descending)
|
|
402
|
-
ranked_tags.sort(key=lambda x: x[0], reverse=True)
|
|
403
|
-
|
|
404
|
-
return ranked_tags, file_report
|
|
405
|
-
|
|
406
|
-
def render_tree(self, abs_fname: str, rel_fname: str, lois: List[int]) -> str:
|
|
407
|
-
"""Render a code snippet with specific lines of interest."""
|
|
408
|
-
code = self.read_text_func_internal(abs_fname)
|
|
409
|
-
if not code:
|
|
410
|
-
return ""
|
|
411
|
-
|
|
412
|
-
# Use TreeContext for rendering
|
|
413
|
-
try:
|
|
414
|
-
if rel_fname not in self.tree_context_cache:
|
|
415
|
-
self.tree_context_cache[rel_fname] = TreeContext(
|
|
416
|
-
rel_fname,
|
|
417
|
-
code,
|
|
418
|
-
color=False
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
tree_context = self.tree_context_cache[rel_fname]
|
|
422
|
-
return tree_context.format(lois)
|
|
423
|
-
except Exception:
|
|
424
|
-
# Fallback to simple line extraction
|
|
425
|
-
lines = code.splitlines()
|
|
426
|
-
result_lines = [f"{rel_fname}:"]
|
|
427
|
-
|
|
428
|
-
for loi in sorted(set(lois)):
|
|
429
|
-
if 1 <= loi <= len(lines):
|
|
430
|
-
result_lines.append(f"{loi:4d}: {lines[loi-1]}")
|
|
431
|
-
|
|
432
|
-
return "\n".join(result_lines)
|
|
433
|
-
|
|
434
|
-
def to_tree(self, tags: List[Tuple[float, Tag]], chat_rel_fnames: Set[str]) -> str:
|
|
435
|
-
"""Convert ranked tags to formatted tree output."""
|
|
436
|
-
if not tags:
|
|
437
|
-
return ""
|
|
438
|
-
|
|
439
|
-
# Group tags by file
|
|
440
|
-
file_tags = defaultdict(list)
|
|
441
|
-
for rank, tag in tags:
|
|
442
|
-
file_tags[tag.rel_fname].append((rank, tag))
|
|
443
|
-
|
|
444
|
-
# Sort files by importance (max rank of their tags)
|
|
445
|
-
sorted_files = sorted(
|
|
446
|
-
file_tags.items(),
|
|
447
|
-
key=lambda x: max(rank for rank, tag in x[1]),
|
|
448
|
-
reverse=True
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
tree_parts = []
|
|
452
|
-
|
|
453
|
-
for rel_fname, file_tag_list in sorted_files:
|
|
454
|
-
# Get lines of interest
|
|
455
|
-
lois = [tag.line for rank, tag in file_tag_list]
|
|
456
|
-
|
|
457
|
-
# Find absolute filename
|
|
458
|
-
abs_fname = str(self.root / rel_fname)
|
|
459
|
-
|
|
460
|
-
# Get the max rank for the file
|
|
461
|
-
max_rank = max(rank for rank, tag in file_tag_list)
|
|
462
|
-
|
|
463
|
-
# Render the tree for this file
|
|
464
|
-
rendered = self.render_tree(abs_fname, rel_fname, lois)
|
|
465
|
-
if rendered:
|
|
466
|
-
# Add rank value to the output
|
|
467
|
-
rendered_lines = rendered.splitlines()
|
|
468
|
-
first_line = rendered_lines[0]
|
|
469
|
-
code_lines = rendered_lines[1:]
|
|
470
|
-
|
|
471
|
-
tree_parts.append(
|
|
472
|
-
f"{first_line}\n"
|
|
473
|
-
f"(Rank value: {max_rank:.4f})\n\n" # Added an extra newline here
|
|
474
|
-
+ "\n".join(code_lines)
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
return "\n\n".join(tree_parts)
|
|
478
|
-
|
|
479
|
-
def get_ranked_tags_map(
|
|
480
|
-
self,
|
|
481
|
-
chat_fnames: List[str],
|
|
482
|
-
other_fnames: List[str],
|
|
483
|
-
max_map_tokens: int,
|
|
484
|
-
mentioned_fnames: Optional[Set[str]] = None,
|
|
485
|
-
mentioned_idents: Optional[Set[str]] = None,
|
|
486
|
-
force_refresh: bool = False
|
|
487
|
-
) -> Tuple[Optional[str], FileReport]:
|
|
488
|
-
"""Get the ranked tags map with caching."""
|
|
489
|
-
cache_key = (
|
|
490
|
-
tuple(sorted(chat_fnames)),
|
|
491
|
-
tuple(sorted(other_fnames)),
|
|
492
|
-
max_map_tokens,
|
|
493
|
-
tuple(sorted(mentioned_fnames or [])),
|
|
494
|
-
tuple(sorted(mentioned_idents or [])),
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
if not force_refresh and cache_key in self.map_cache:
|
|
498
|
-
return self.map_cache[cache_key]
|
|
499
|
-
|
|
500
|
-
result = self.get_ranked_tags_map_uncached(
|
|
501
|
-
chat_fnames, other_fnames, max_map_tokens,
|
|
502
|
-
mentioned_fnames, mentioned_idents
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
self.map_cache[cache_key] = result
|
|
506
|
-
return result
|
|
507
|
-
|
|
508
|
-
def get_ranked_tags_map_uncached(
|
|
509
|
-
self,
|
|
510
|
-
chat_fnames: List[str],
|
|
511
|
-
other_fnames: List[str],
|
|
512
|
-
max_map_tokens: int,
|
|
513
|
-
mentioned_fnames: Optional[Set[str]] = None,
|
|
514
|
-
mentioned_idents: Optional[Set[str]] = None
|
|
515
|
-
) -> Tuple[Optional[str], FileReport]:
|
|
516
|
-
"""Generate the ranked tags map without caching."""
|
|
517
|
-
ranked_tags, file_report = self.get_ranked_tags(
|
|
518
|
-
chat_fnames, other_fnames, mentioned_fnames, mentioned_idents
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
if not ranked_tags:
|
|
522
|
-
return None, file_report
|
|
523
|
-
|
|
524
|
-
# Filter important files
|
|
525
|
-
important_files = filter_important_files(
|
|
526
|
-
[self.get_rel_fname(f) for f in other_fnames]
|
|
527
|
-
)
|
|
528
|
-
|
|
529
|
-
# Binary search to find the right number of tags
|
|
530
|
-
chat_rel_fnames = set(self.get_rel_fname(f) for f in chat_fnames)
|
|
531
|
-
|
|
532
|
-
def try_tags(num_tags: int) -> Tuple[Optional[str], int]:
|
|
533
|
-
if num_tags <= 0:
|
|
534
|
-
return None, 0
|
|
535
|
-
|
|
536
|
-
selected_tags = ranked_tags[:num_tags]
|
|
537
|
-
tree_output = self.to_tree(selected_tags, chat_rel_fnames)
|
|
538
|
-
|
|
539
|
-
if not tree_output:
|
|
540
|
-
return None, 0
|
|
541
|
-
|
|
542
|
-
tokens = self.token_count(tree_output)
|
|
543
|
-
return tree_output, tokens
|
|
544
|
-
|
|
545
|
-
# Binary search for optimal number of tags
|
|
546
|
-
left, right = 0, len(ranked_tags)
|
|
547
|
-
best_tree = None
|
|
548
|
-
|
|
549
|
-
while left <= right:
|
|
550
|
-
mid = (left + right) // 2
|
|
551
|
-
tree_output, tokens = try_tags(mid)
|
|
552
|
-
|
|
553
|
-
if tree_output and tokens <= max_map_tokens:
|
|
554
|
-
best_tree = tree_output
|
|
555
|
-
left = mid + 1
|
|
556
|
-
else:
|
|
557
|
-
right = mid - 1
|
|
558
|
-
|
|
559
|
-
return best_tree, file_report
|
|
560
|
-
|
|
561
|
-
def get_repo_map(
|
|
562
|
-
self,
|
|
563
|
-
chat_files: List[str] = None,
|
|
564
|
-
other_files: List[str] = None,
|
|
565
|
-
mentioned_fnames: Optional[Set[str]] = None,
|
|
566
|
-
mentioned_idents: Optional[Set[str]] = None,
|
|
567
|
-
force_refresh: bool = False,
|
|
568
|
-
show_preview: bool = False
|
|
569
|
-
) -> Tuple[Optional[str], FileReport]:
|
|
570
|
-
"""Generate the repository map with file report.
|
|
571
|
-
|
|
572
|
-
Args:
|
|
573
|
-
chat_files: List of chat-related files to prioritize
|
|
574
|
-
other_files: List of other files to include
|
|
575
|
-
mentioned_fnames: Set of mentioned filenames for boosting
|
|
576
|
-
mentioned_idents: Set of mentioned identifiers for boosting
|
|
577
|
-
force_refresh: Force refresh of cached data
|
|
578
|
-
show_preview: If False, returns "complete" instead of full map content
|
|
579
|
-
|
|
580
|
-
Returns:
|
|
581
|
-
Tuple of (map_content, file_report) where map_content is either
|
|
582
|
-
the full repository map or "complete" based on show_preview setting
|
|
583
|
-
"""
|
|
584
|
-
if chat_files is None:
|
|
585
|
-
chat_files = []
|
|
586
|
-
if other_files is None:
|
|
587
|
-
other_files = []
|
|
588
|
-
|
|
589
|
-
# Create empty report for error cases
|
|
590
|
-
empty_report = FileReport({}, 0, 0, 0)
|
|
591
|
-
|
|
592
|
-
if self.max_map_tokens <= 0 or not other_files:
|
|
593
|
-
return None, empty_report
|
|
594
|
-
|
|
595
|
-
# Adjust max_map_tokens if no chat files
|
|
596
|
-
max_map_tokens = self.max_map_tokens
|
|
597
|
-
if not chat_files and self.max_context_window:
|
|
598
|
-
padding = 1024
|
|
599
|
-
available = self.max_context_window - padding
|
|
600
|
-
max_map_tokens = min(
|
|
601
|
-
max_map_tokens * self.map_mul_no_files,
|
|
602
|
-
available
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
try:
|
|
606
|
-
# get_ranked_tags_map returns (map_string, file_report)
|
|
607
|
-
map_string, file_report = self.get_ranked_tags_map(
|
|
608
|
-
chat_files, other_files, max_map_tokens,
|
|
609
|
-
mentioned_fnames, mentioned_idents, force_refresh
|
|
610
|
-
)
|
|
611
|
-
except RecursionError:
|
|
612
|
-
self.output_handlers['error']("Disabling repo map, git repo too large?")
|
|
613
|
-
self.max_map_tokens = 0
|
|
614
|
-
return None, FileReport({}, 0, 0, 0) # Ensure consistent return type
|
|
615
|
-
|
|
616
|
-
if map_string is None:
|
|
617
|
-
return None, file_report
|
|
618
|
-
|
|
619
|
-
if self.verbose:
|
|
620
|
-
tokens = self.token_count(map_string)
|
|
621
|
-
self.output_handlers['info'](f"Repo-map: {tokens / 1024:.1f} k-tokens")
|
|
622
|
-
|
|
623
|
-
# Format final output
|
|
624
|
-
if not show_preview:
|
|
625
|
-
# Return simple completion message instead of full map
|
|
626
|
-
return "complete", file_report
|
|
627
|
-
|
|
628
|
-
other = "other " if chat_files else ""
|
|
629
|
-
|
|
630
|
-
if self.repo_content_prefix:
|
|
631
|
-
repo_content = self.repo_content_prefix.format(other=other)
|
|
632
|
-
else:
|
|
633
|
-
repo_content = ""
|
|
634
|
-
|
|
635
|
-
repo_content += map_string
|
|
636
|
-
|
|
637
|
-
return repo_content, file_report
|