codegraphcontext 0.5.5__py3-none-any.whl → 0.5.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2830,15 +2830,18 @@ def analyze_complexity(
2830
2830
 
2831
2831
  @analyze_app.command("dead-code")
2832
2832
  def analyze_dead_code(
2833
- path: Optional[str] = typer.Argument(None, help="Path to analyze (not yet implemented)"),
2833
+ path: Optional[str] = typer.Argument(None, help="Repository path to scope the analysis to"),
2834
2834
  exclude_decorators: Optional[str] = typer.Option(None, "--exclude", "-e", help="Comma-separated decorators to exclude"),
2835
2835
  context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
2836
2836
  ):
2837
2837
  """
2838
- Find potentially unused functions and classes.
2839
-
2838
+ Find potentially unused functions.
2839
+
2840
+ Without a path the whole database is scanned, which on a shared graph
2841
+ reports dead code from every indexed repository. Pass a path to scope it.
2842
+
2840
2843
  Example:
2841
- cgc analyze dead-code
2844
+ cgc analyze dead-code .
2842
2845
  cgc analyze dead-code --exclude route,task,api
2843
2846
  """
2844
2847
  _load_credentials()
@@ -2849,8 +2852,11 @@ def analyze_dead_code(
2849
2852
 
2850
2853
  try:
2851
2854
  exclude_list = exclude_decorators.split(',') if exclude_decorators else []
2852
- results = code_finder.find_dead_code(exclude_list)
2853
-
2855
+ # `path` was accepted and then dropped, so running inside one repository
2856
+ # still reported dead code from every repository in the database.
2857
+ repo_path = Path(path).resolve().as_posix() if path else None
2858
+ results = code_finder.find_dead_code(exclude_list, repo_path=repo_path)
2859
+
2854
2860
  unused_funcs = results.get('potentially_unused_functions', [])
2855
2861
 
2856
2862
  if not unused_funcs:
@@ -82,10 +82,12 @@ class Advanced_language_query:
82
82
 
83
83
  if language not in self.TOOLKITS:
84
84
  raise ValueError(f"Unsupported language: {language}")
85
- self.toolkit = self.TOOLKITS[language]()
86
-
87
- # Getting the language query
88
- cypher_query = self.toolkit.get_cypher_query(label)
85
+ try:
86
+ self.toolkit = self.TOOLKITS[language]()
87
+ # Getting the language query
88
+ cypher_query = self.toolkit.get_cypher_query(label)
89
+ except NotImplementedError as exc:
90
+ return {"error": str(exc), "language": language, "query": query}
89
91
  try:
90
92
  debug_log(f"Executing Cypher query: {cypher_query}")
91
93
  with self.db_manager.get_driver().session() as session:
@@ -14,6 +14,22 @@ logger = logging.getLogger(__name__)
14
14
 
15
15
  _MAX_TRAVERSAL_DEPTH = 20
16
16
 
17
+ # Names that are entry points rather than dead code. Matched on the *whole*
18
+ # name, lowercased. This used to be a substring test (`name CONTAINS 'main'`,
19
+ # `toLower(name) CONTAINS 'entry'`), which silently excluded any function whose
20
+ # name merely contained one of them — `domain_check`, `remainder`,
21
+ # `maintain_index`, `entry_count` — so genuinely unused code was never
22
+ # reported and there was no way to tell.
23
+ _ENTRY_POINT_NAMES = (
24
+ "main",
25
+ "setup",
26
+ "run",
27
+ "application",
28
+ "entry",
29
+ "entrypoint",
30
+ )
31
+ _ENTRY_POINT_NAMES_CYPHER = "[" + ", ".join(f"'{n}'" for n in _ENTRY_POINT_NAMES) + "]"
32
+
17
33
 
18
34
  def _sanitize_depth(depth, default: int = 3) -> int:
19
35
  """Coerce and clamp a traversal depth before interpolating it into Cypher.
@@ -809,19 +825,16 @@ class CodeFinder:
809
825
  query = f"""
810
826
  MATCH (func:Function)
811
827
  WHERE func.is_dependency = false {repo_filter} {func_ignore}
812
- AND NOT func.name IN ['main', 'setup', 'run']
828
+ AND NOT toLower(func.name) IN {_ENTRY_POINT_NAMES_CYPHER}
813
829
  AND func.name <> '<module>'
814
830
  AND NOT (func.name STARTS WITH '__' AND func.name ENDS WITH '__')
815
831
  AND NOT func.name STARTS WITH '_test'
816
832
  AND NOT func.name STARTS WITH 'test_'
817
- AND NOT func.name CONTAINS 'main'
818
- AND NOT toLower(func.name) CONTAINS 'application'
819
- AND NOT toLower(func.name) CONTAINS 'entry'
820
- AND NOT toLower(func.name) CONTAINS 'entrypoint'
821
833
  {decorator_filter}
822
834
  WITH func
823
- OPTIONAL MATCH (caller:Function)-[:CALLS|HEURISTIC_CALLS]->(func)
824
- WHERE caller.is_dependency = false {caller_ignore}
835
+ OPTIONAL MATCH (caller)-[:CALLS|HEURISTIC_CALLS]->(func)
836
+ WHERE (caller.is_dependency IS NULL OR caller.is_dependency = false)
837
+ {caller_ignore}
825
838
  WITH func, count(caller) as caller_count
826
839
  WHERE caller_count = 0
827
840
  OPTIONAL MATCH (file:File)-[:CONTAINS]->(func)
@@ -1211,16 +1224,31 @@ class CodeFinder:
1211
1224
  try:
1212
1225
  if query_type == "find_callers":
1213
1226
  results = self.who_calls_function(target, context, repo_path=repo_path)
1214
- # Hoist target_file_path (constant across rows) to the envelope once,
1215
- # instead of repeating the absolute path in every row (payload slimming).
1216
- target_file_path = next((r.pop("target_file_path", None) for r in results), None) if results else None
1217
- for r in results:
1218
- r.pop("target_file_path", None)
1219
- return {
1227
+ # target_file_path is only constant when `context` pinned a single
1228
+ # definition. Without it, who_calls_function matches
1229
+ # Function {name: $function_name} across every file, so rows
1230
+ # legitimately carry different targets — hoisting the first row's
1231
+ # value and stripping the rest reported every caller as calling
1232
+ # whichever definition happened to sort first.
1233
+ #
1234
+ # Hoist only when the rows agree; otherwise leave the field on each
1235
+ # row so the ambiguity is visible to the caller.
1236
+ target_paths = {r.get("target_file_path") for r in results}
1237
+ target_file_path = target_paths.pop() if len(target_paths) == 1 else None
1238
+ if target_file_path is not None:
1239
+ for r in results:
1240
+ r.pop("target_file_path", None)
1241
+ envelope = {
1220
1242
  "query_type": "find_callers", "target": target, "context": context,
1221
1243
  "target_file_path": target_file_path, "results": results,
1222
1244
  "summary": f"Found {len(results)} functions that call '{target}'"
1223
1245
  }
1246
+ if target_file_path is None and len(target_paths) > 1:
1247
+ envelope["note"] = (
1248
+ f"'{target}' is defined in {len(target_paths)} files; each result "
1249
+ "carries its own target_file_path. Pass `context` to disambiguate."
1250
+ )
1251
+ return envelope
1224
1252
 
1225
1253
  elif query_type == "find_callees":
1226
1254
  results = self.what_does_function_call(target, context, repo_path=repo_path)
@@ -1272,7 +1300,10 @@ class CodeFinder:
1272
1300
  }
1273
1301
 
1274
1302
  elif query_type in ["dead_code", "unused", "unreachable"]:
1275
- results = self.find_dead_code(repo_path=repo_path)
1303
+ # graph_name must be forwarded: find_dead_code defaults it to None and
1304
+ # re-assigns the shared self._active_graph, so omitting it here
1305
+ # silently redirected the query to the default graph.
1306
+ results = self.find_dead_code(repo_path=repo_path, graph_name=graph_name)
1276
1307
  return {
1277
1308
  "query_type": "dead_code", "results": results,
1278
1309
  "summary": f"Found {len(results['potentially_unused_functions'])} potentially unused functions"
@@ -1281,7 +1312,7 @@ class CodeFinder:
1281
1312
  elif query_type == "find_complexity":
1282
1313
  limit_val = context if context else target
1283
1314
  limit = int(limit_val) if limit_val and str(limit_val).isdigit() else 10
1284
- results = self.find_most_complex_functions(limit, repo_path=repo_path)
1315
+ results = self.find_most_complex_functions(limit, repo_path=repo_path, graph_name=graph_name)
1285
1316
  return {
1286
1317
  "query_type": "find_complexity", "limit": limit, "results": results,
1287
1318
  "summary": f"Found the top {len(results)} most complex functions"
@@ -236,8 +236,8 @@ def find_java_spring_beans(code_finder: CodeFinder, **args) -> Dict[str, Any]:
236
236
  query = f"""
237
237
  MATCH (c:Class)
238
238
  WHERE {where_clause}
239
- OPTIONAL MATCH ()-[:INJECTS]->(c)
240
- WITH c, count(*) AS injection_count
239
+ OPTIONAL MATCH ()-[inj:INJECTS]->(c)
240
+ WITH c, count(inj) AS injection_count
241
241
  RETURN c.name AS name, c.spring_stereotype AS stereotype,
242
242
  c.path AS file, c.line_number AS line_number, injection_count
243
243
  ORDER BY stereotype, name
@@ -296,7 +296,8 @@ def find_datasource_nodes(code_finder: CodeFinder, **args) -> Dict[str, Any]:
296
296
  {kp_where}
297
297
  OPTIONAL MATCH (kp:RedisKeyPattern)-[:STORED_IN]->(d)
298
298
  RETURN d.name AS datasource,
299
- collect({{pattern: kp.pattern, key_type: kp.key_type, count: kp.count}}) AS key_patterns
299
+ [x IN collect(kp) | {{pattern: x.pattern, key_type: x.key_type,
300
+ count: x.count}}] AS key_patterns
300
301
  """
301
302
 
302
303
  try:
@@ -19,6 +19,25 @@ def add_code_to_graph(graph_builder, job_manager, loop, list_repos_func, **args)
19
19
 
20
20
  if not path:
21
21
  return {"error": "Path is a required argument (repo_path)."}
22
+
23
+ # `graph_name` is advertised in this tool's schema but indexing cannot yet
24
+ # honour it: GraphBuilder binds its GraphWriter to the default driver at
25
+ # construction, so a named graph would need a per-job writer threaded
26
+ # through the whole pipeline. Refuse explicitly rather than write to the
27
+ # default graph and report success — an agent that believed the repository
28
+ # landed in a named graph would find nothing there later, with no error to
29
+ # explain it.
30
+ graph_name = args.get("graph_name")
31
+ if graph_name:
32
+ return {
33
+ "error": (
34
+ f"Indexing into a named graph is not supported yet, so "
35
+ f"graph_name={graph_name!r} cannot be honoured. Omit it to index "
36
+ "into the default graph, or select the graph via the CLI context "
37
+ "(`cgc context create` / `cgc index --context`)."
38
+ ),
39
+ "unsupported_argument": "graph_name",
40
+ }
22
41
 
23
42
  try:
24
43
  path_obj = Path(path).resolve()
@@ -354,15 +354,15 @@ def get_repository_stats(code_finder: CodeFinder, **args) -> Dict[str, Any]:
354
354
  }
355
355
 
356
356
  # 1. Files
357
- file_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(f:File) RETURN count(f) as c"
357
+ file_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(f:File) RETURN count(DISTINCT f) as c"
358
358
  file_count = session.run(file_query, path=repo_path_obj).single()["c"]
359
359
 
360
360
  # 2. Functions
361
- func_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(func:Function) RETURN count(func) as c"
361
+ func_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(func:Function) RETURN count(DISTINCT func) as c"
362
362
  func_count = session.run(func_query, path=repo_path_obj).single()["c"]
363
363
 
364
364
  # 3. Classes
365
- class_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(cls:Class) RETURN count(cls) as c"
365
+ class_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(cls:Class) RETURN count(DISTINCT cls) as c"
366
366
  class_count = session.run(class_query, path=repo_path_obj).single()["c"]
367
367
 
368
368
  # 4. Modules (imported)
@@ -191,6 +191,14 @@ async def run_scip_index_async(
191
191
  cgcignore_path=cgcignore_path,
192
192
  supported_extensions=set(parsers_keys),
193
193
  )
194
+ # Recompute total_files now that we know the full set (SCIP-covered +
195
+ # supplementary), so progress_percentage stays <= 100% throughout.
196
+ if job_id:
197
+ new_total = len(files_data) + sum(
198
+ 1 for f in supplementary_files
199
+ if str(f.resolve()) not in scip_abs_paths
200
+ )
201
+ job_manager.update_job(job_id, total_files=new_total)
194
202
  minimal_nodes = 0
195
203
  for repo_file in supplementary_files:
196
204
  abs_str = str(repo_file.resolve())
@@ -6,20 +6,37 @@ from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logge
6
6
  from codegraphcontext.utils.tree_sitter_manager import execute_query
7
7
 
8
8
  C_QUERIES = {
9
+ # A pointer return type wraps the function declarator from the OUTSIDE:
10
+ # `char* f()` is
11
+ # function_definition > pointer_declarator > function_declarator > identifier
12
+ # The second alternative below used to nest these the other way round, which
13
+ # matches nothing, so no function returning `T*` or `T**` was extracted —
14
+ # and because pre_scan_c shares this query they were invisible as call
15
+ # targets too.
9
16
  "functions": """
10
17
  (function_definition
11
18
  declarator: (function_declarator
12
19
  declarator: (identifier) @name
13
20
  )
14
21
  ) @function_node
15
-
22
+
16
23
  (function_definition
17
- declarator: (function_declarator
18
- declarator: (pointer_declarator
24
+ declarator: (pointer_declarator
25
+ declarator: (function_declarator
19
26
  declarator: (identifier) @name
20
27
  )
21
28
  )
22
29
  ) @function_node
30
+
31
+ (function_definition
32
+ declarator: (pointer_declarator
33
+ declarator: (pointer_declarator
34
+ declarator: (function_declarator
35
+ declarator: (identifier) @name
36
+ )
37
+ )
38
+ )
39
+ ) @function_node
23
40
  """,
24
41
  "structs": """
25
42
  (struct_specifier
@@ -762,15 +779,25 @@ def pre_scan_c(files: list[Path], parser_wrapper) -> dict:
762
779
  declarator: (identifier) @name
763
780
  )
764
781
  )
765
-
782
+
766
783
  (function_definition
767
- declarator: (function_declarator
768
- declarator: (pointer_declarator
784
+ declarator: (pointer_declarator
785
+ declarator: (function_declarator
769
786
  declarator: (identifier) @name
770
787
  )
771
788
  )
772
789
  )
773
-
790
+
791
+ (function_definition
792
+ declarator: (pointer_declarator
793
+ declarator: (pointer_declarator
794
+ declarator: (function_declarator
795
+ declarator: (identifier) @name
796
+ )
797
+ )
798
+ )
799
+ )
800
+
774
801
  (struct_specifier
775
802
  name: (type_identifier) @name
776
803
  )
@@ -6,6 +6,13 @@ from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logge
6
6
  from codegraphcontext.utils.tree_sitter_manager import execute_query
7
7
 
8
8
  CPP_QUERIES = {
9
+ # A pointer or reference return type wraps the function declarator from the
10
+ # OUTSIDE: `char* f()` is
11
+ # function_definition > pointer_declarator > function_declarator > identifier
12
+ # not function_declarator > pointer_declarator. Matching only the unwrapped
13
+ # shape meant no function returning `T*`, `T&` or `T**` was ever extracted —
14
+ # accessors, factories and `operator[]` are pervasive in C++, and because
15
+ # pre_scan_cpp shares this query they were invisible as call targets too.
9
16
  "functions": """
10
17
  (function_definition
11
18
  declarator: (function_declarator
@@ -16,6 +23,44 @@ CPP_QUERIES = {
16
23
  ]
17
24
  )
18
25
  ) @function_node
26
+
27
+ (function_definition
28
+ declarator: (pointer_declarator
29
+ declarator: (function_declarator
30
+ declarator: [
31
+ (identifier) @name
32
+ (field_identifier) @name
33
+ (qualified_identifier) @qualified_name
34
+ ]
35
+ )
36
+ )
37
+ ) @function_node
38
+
39
+ (function_definition
40
+ declarator: (pointer_declarator
41
+ declarator: (pointer_declarator
42
+ declarator: (function_declarator
43
+ declarator: [
44
+ (identifier) @name
45
+ (field_identifier) @name
46
+ (qualified_identifier) @qualified_name
47
+ ]
48
+ )
49
+ )
50
+ )
51
+ ) @function_node
52
+
53
+ (function_definition
54
+ declarator: (reference_declarator
55
+ (function_declarator
56
+ declarator: [
57
+ (identifier) @name
58
+ (field_identifier) @name
59
+ (qualified_identifier) @qualified_name
60
+ ]
61
+ )
62
+ )
63
+ ) @function_node
19
64
  """,
20
65
  "classes": """
21
66
  (class_specifier
@@ -651,6 +696,23 @@ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
651
696
  (type_definition declarator: (type_identifier) @name)
652
697
  (function_definition declarator: (function_declarator declarator: (identifier) @name))
653
698
  (function_definition declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))
699
+
700
+ ; A pointer/reference return type wraps the function declarator from the
701
+ ; outside, so these forms need their own alternatives — without them a
702
+ ; `T* f()` was not registered here either, and so was unresolvable as a
703
+ ; call target even once it became a node.
704
+ (function_definition declarator: (pointer_declarator
705
+ declarator: (function_declarator declarator: (identifier) @name)))
706
+ (function_definition declarator: (pointer_declarator
707
+ declarator: (function_declarator declarator: (qualified_identifier) @qualified_name)))
708
+ (function_definition declarator: (pointer_declarator declarator: (pointer_declarator
709
+ declarator: (function_declarator declarator: (identifier) @name))))
710
+ (function_definition declarator: (pointer_declarator declarator: (pointer_declarator
711
+ declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))))
712
+ (function_definition declarator: (reference_declarator
713
+ (function_declarator declarator: (identifier) @name)))
714
+ (function_definition declarator: (reference_declarator
715
+ (function_declarator declarator: (qualified_identifier) @qualified_name)))
654
716
  """
655
717
 
656
718
 
@@ -6,6 +6,21 @@ from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logge
6
6
  from codegraphcontext.utils.tree_sitter_manager import execute_query
7
7
 
8
8
  CSHARP_QUERIES = {
9
+ # Fields and locals share a shape: `variable_declaration` carries the type
10
+ # and holds one `variable_declarator` per name. There was no `variables`
11
+ # query at all and the key was hardcoded to [], so C# produced no Variable
12
+ # nodes — and `resolution/calls.py` lost the local-variable declarations it
13
+ # uses to infer a receiver's type, so `var s = new UserService(); s.Save();`
14
+ # could not resolve Save to UserService.Save.
15
+ "variables": """
16
+ (field_declaration
17
+ (variable_declaration) @decl
18
+ ) @field_node
19
+
20
+ (local_declaration_statement
21
+ (variable_declaration) @decl
22
+ ) @local_node
23
+ """,
9
24
  "functions": """
10
25
  (method_declaration
11
26
  name: (identifier) @name
@@ -123,6 +138,7 @@ class CSharpTreeSitterParser:
123
138
  parsed_enums = []
124
139
  parsed_records = []
125
140
  parsed_properties = []
141
+ parsed_variables = []
126
142
  parsed_imports = []
127
143
  parsed_calls = []
128
144
 
@@ -143,6 +159,8 @@ class CSharpTreeSitterParser:
143
159
  parsed_records = self._parse_type_declarations(captures, source_code, path, "Record")
144
160
  elif capture_name == "properties":
145
161
  parsed_properties = self._parse_properties(captures, source_code, path, tree.root_node)
162
+ elif capture_name == "variables":
163
+ parsed_variables = self._parse_variables(captures)
146
164
  elif capture_name == "imports":
147
165
  parsed_imports = self._parse_imports(captures, source_code)
148
166
  elif capture_name == "calls":
@@ -157,7 +175,7 @@ class CSharpTreeSitterParser:
157
175
  "enums": parsed_enums,
158
176
  "records": parsed_records,
159
177
  "properties": parsed_properties,
160
- "variables": [],
178
+ "variables": parsed_variables,
161
179
  "imports": parsed_imports,
162
180
  "function_calls": parsed_calls,
163
181
  "is_dependency": is_dependency,
@@ -359,6 +377,70 @@ class CSharpTreeSitterParser:
359
377
 
360
378
  return imports
361
379
 
380
+ def _parse_variables(self, captures: list) -> list:
381
+ """Fields and locals, from `variable_declaration` nodes.
382
+
383
+ Both shapes nest identically — `variable_declaration` carries the type
384
+ and holds one `variable_declarator` per declared name, so
385
+ `int a = 1, b = 2;` yields two rows sharing a type.
386
+ """
387
+ variables = []
388
+ seen = set()
389
+ for node, capture_name in captures:
390
+ if capture_name != "decl":
391
+ continue
392
+ type_node = node.child_by_field_name("type")
393
+ var_type = self._get_node_text(type_node) if type_node is not None else None
394
+
395
+ for declarator in node.children:
396
+ if declarator.type != "variable_declarator":
397
+ continue
398
+ name_node = declarator.child_by_field_name("name")
399
+ if name_node is None:
400
+ name_node = next(
401
+ (c for c in declarator.children if c.type == "identifier"), None
402
+ )
403
+ if name_node is None:
404
+ continue
405
+
406
+ line_number = name_node.start_point[0] + 1
407
+ name = self._get_node_text(name_node)
408
+ if (name, line_number) in seen:
409
+ continue
410
+ seen.add((name, line_number))
411
+
412
+ # tree-sitter-c-sharp has no `equals_value_clause`: the
413
+ # initialiser is a direct sibling following the `=` token.
414
+ value = None
415
+ seen_equals = False
416
+ for child in declarator.children:
417
+ if child.type == "=":
418
+ seen_equals = True
419
+ continue
420
+ if seen_equals and child.is_named:
421
+ value = self._get_node_text(child)
422
+ break
423
+
424
+ context, _ctx_type, _ctx_line = self._get_parent_context(
425
+ declarator, types=("method_declaration", "function_declaration")
426
+ )
427
+ class_context, _c_type, _c_line = self._get_parent_context(
428
+ declarator,
429
+ types=("class_declaration", "struct_declaration", "record_declaration"),
430
+ )
431
+
432
+ variables.append({
433
+ "name": name,
434
+ "line_number": line_number,
435
+ "value": value,
436
+ "type": var_type,
437
+ "context": context,
438
+ "class_context": class_context,
439
+ "lang": self.language_name,
440
+ "is_dependency": False,
441
+ })
442
+ return variables
443
+
362
444
  def _get_parent_context(self, node: Any, types: Tuple[str, ...] = ('class_declaration', 'struct_declaration', 'function_declaration', 'method_declaration')):
363
445
  """Find parent context for C# constructs."""
364
446
  curr = node.parent
@@ -360,13 +360,77 @@ class JavascriptTreeSitterParser:
360
360
  left_child = child.child_by_field_name('left')
361
361
  if left_child and left_child.type == 'identifier':
362
362
  params.append(self._get_node_text(left_child))
363
- elif child.type == 'rest_pattern':
363
+ elif child.type in ('rest_element', 'rest_pattern'):
364
364
  # Rest parameter: ...args
365
- argument = child.child_by_field_name('argument')
366
- if argument and argument.type == 'identifier':
367
- params.append(f"...{self._get_node_text(argument)}")
365
+ # Try named field first, then direct identifier child
366
+ name_node = (child.child_by_field_name('name') or
367
+ child.child_by_field_name('argument'))
368
+ if name_node is None:
369
+ name_node = next(
370
+ (c for c in child.children if c.type == 'identifier'), None
371
+ )
372
+ if name_node:
373
+ params.append(f"...{self._get_node_text(name_node)}")
374
+ elif child.type == 'object_pattern':
375
+ # Destructured object: {a}, {a: renamed}, {a = 1}
376
+ names = self._pattern_binding_names(child)
377
+ params.append("{" + ", ".join(names) + "}" if names else "{...}")
378
+ elif child.type == 'array_pattern':
379
+ # Destructured array: [a, b]
380
+ names = self._pattern_binding_names(child)
381
+ params.append("[" + ", ".join(names) + "]" if names else "[...]")
368
382
  return params
369
383
 
384
+ def _pattern_binding_names(self, pattern_node):
385
+ """Local names a destructuring pattern binds, in source order.
386
+
387
+ Recorded rather than collapsed to a placeholder because the binding is
388
+ what later code refers to — `function Button({label})` is the dominant
389
+ React idiom, and `label` is the name that appears in the body. One
390
+ entry is still emitted per *parameter position* so arity, which call
391
+ resolution matches on, is unchanged.
392
+ """
393
+ names = []
394
+
395
+ def walk(node):
396
+ for child in node.named_children:
397
+ if child.type == 'shorthand_property_identifier_pattern':
398
+ # { a }
399
+ names.append(self._get_node_text(child))
400
+ elif child.type == 'pair_pattern':
401
+ # { a: local } — the binding is the value, not the key
402
+ value = child.child_by_field_name('value')
403
+ if value is not None and value.type == 'identifier':
404
+ names.append(self._get_node_text(value))
405
+ elif value is not None:
406
+ walk(value) # nested destructuring
407
+ elif child.type == 'object_assignment_pattern':
408
+ # { a = 1 }
409
+ target = child.child_by_field_name('left') or (
410
+ child.named_children[0] if child.named_children else None
411
+ )
412
+ if target is not None:
413
+ if target.type in (
414
+ 'identifier', 'shorthand_property_identifier_pattern'
415
+ ):
416
+ names.append(self._get_node_text(target))
417
+ else:
418
+ walk(target)
419
+ elif child.type == 'identifier':
420
+ # [ a, b ]
421
+ names.append(self._get_node_text(child))
422
+ elif child.type in ('object_pattern', 'array_pattern'):
423
+ walk(child)
424
+ elif child.type in ('rest_element', 'rest_pattern'):
425
+ inner = next(
426
+ (c for c in child.children if c.type == 'identifier'), None
427
+ )
428
+ if inner is not None:
429
+ names.append(f"...{self._get_node_text(inner)}")
430
+
431
+ walk(pattern_node)
432
+ return names
433
+
370
434
 
371
435
  def _get_jsdoc_comment(self, func_node):
372
436
  """Extract JSDoc comment preceding the function."""
@@ -180,14 +180,47 @@ class RubyTreeSitterParser:
180
180
  prev_sibling = prev_sibling.prev_sibling
181
181
  return None
182
182
 
183
+ # Ruby parameter node types, and the prefix each carries in source. The
184
+ # bare `identifier` case is a required positional parameter.
185
+ _PARAM_PREFIXES = {
186
+ 'optional_parameter': '', # b = 1
187
+ 'keyword_parameter': '', # key:
188
+ 'splat_parameter': '*', # *rest
189
+ 'hash_splat_parameter': '**', # **opts
190
+ 'block_parameter': '&', # &blk
191
+ }
192
+
183
193
  def _parse_method_parameters(self, method_node: Any) -> list[str]:
184
- """Parse method parameters from a method node."""
194
+ """Parse method parameters from a method node.
195
+
196
+ Parameters live inside a `method_parameters` child, not directly on the
197
+ method — the only direct `identifier` child is the method's own name,
198
+ which this used to skip, so every Ruby method reported no parameters at
199
+ all and no `HAS_PARAMETER` edge was ever created.
200
+ """
201
+ params_node = method_node.child_by_field_name('parameters')
202
+ if params_node is None:
203
+ params_node = next(
204
+ (c for c in method_node.children
205
+ if c.type in ('method_parameters', 'parameters', 'bare_parameters')),
206
+ None,
207
+ )
208
+ if params_node is None:
209
+ return []
210
+
185
211
  params = []
186
- # Look for parameters in the method node
187
- for child in method_node.children:
188
- if child.type == 'identifier' and child != method_node.child_by_field_name('name'):
189
- # This is likely a parameter
212
+ for child in params_node.children:
213
+ if not child.is_named:
214
+ continue # punctuation: ( ) ,
215
+ if child.type == 'identifier':
190
216
  params.append(self._get_node_text(child))
217
+ continue
218
+ prefix = self._PARAM_PREFIXES.get(child.type)
219
+ if prefix is None:
220
+ continue
221
+ name_node = next((c for c in child.children if c.type == 'identifier'), None)
222
+ if name_node is not None:
223
+ params.append(prefix + self._get_node_text(name_node))
191
224
  return params
192
225
 
193
226
  def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
@@ -297,23 +330,26 @@ class RubyTreeSitterParser:
297
330
  # Collect all captures first
298
331
  all_captures = list(execute_query(self.language, query_str, root_node))
299
332
 
300
- # Group captures by class node using a different approach
333
+ # Read each class's own `name` field rather than matching @name captures
334
+ # to classes by byte range. An outer class always contains an inner
335
+ # class's name node, so range-matching assigned the inner name to the
336
+ # outer class (the first container, in document order) and left the
337
+ # inner entry nameless — where it was then dropped. `class Client`
338
+ # wrapping `class TimeoutError` yielded exactly one class, named
339
+ # TimeoutError, carrying Client's line range.
301
340
  captures_by_class = {}
302
341
  for node, capture_name in all_captures:
303
- if capture_name == 'class':
304
- captures_by_class[id(node)] = {'node': node, 'name': None}
305
-
306
- # Now find names for each class
307
- for node, capture_name in all_captures:
308
- if capture_name == 'name':
309
- # Find which class this name belongs to
310
- for class_id, class_data in captures_by_class.items():
311
- class_node = class_data['node']
312
- # Check if this name node is within the class node
313
- if (node.start_byte >= class_node.start_byte and
314
- node.end_byte <= class_node.end_byte):
315
- captures_by_class[class_id]['name'] = self._get_node_text(node)
316
- break
342
+ if capture_name != 'class':
343
+ continue
344
+ name_node = node.child_by_field_name('name')
345
+ if name_node is None:
346
+ # tree-sitter-ruby gives the `class` keyword token the same
347
+ # node type as the definition; only the definition has a name.
348
+ continue
349
+ captures_by_class[id(node)] = {
350
+ 'node': node,
351
+ 'name': self._get_node_text(name_node),
352
+ }
317
353
 
318
354
  # Build class entries
319
355
  for class_data in captures_by_class.values():
@@ -49,7 +49,15 @@ def _get_python_package_path(package_name: str) -> Optional[str]:
49
49
  if _is_stdlib_module(package_name):
50
50
  return str(module_file)
51
51
 
52
- return str(module_file.parent)
52
+ # For a flat, single-file package (e.g. six.py, typing_extensions.py),
53
+ # the parent is site-packages itself. Returning it would cause
54
+ # add_package_to_graph to index every installed dependency.
55
+ # Return the file instead so only the package is indexed.
56
+ parent = module_file.parent
57
+ if parent.name in ("site-packages", "dist-packages"):
58
+ return str(module_file)
59
+
60
+ return str(parent)
53
61
 
54
62
  if spec.submodule_search_locations:
55
63
  locations = list(spec.submodule_search_locations)
@@ -12,15 +12,23 @@ from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
14
  class JavaToolkit:
15
- """Spring-aware Java analysis methods backed by CodeFinder / raw Cypher."""
15
+ """Spring-aware Java analysis methods backed by CodeFinder / raw Cypher.
16
16
 
17
- def __init__(self, code_finder: Any):
18
- """
19
- Args:
20
- code_finder: A ``CodeFinder`` instance (or compatible duck-type).
21
- """
17
+ Note: The generic ``advanced_language_query`` path calls this toolkit
18
+ with no arguments (consistent with all other language toolkits). The
19
+ Spring-specific MCP tools (``find_java_spring_endpoints`` /
20
+ ``find_java_spring_beans``) pass a ``code_finder`` via ``_init_with``.
21
+ """
22
+
23
+ def __init__(self) -> None:
24
+ self._cf: Any = None
25
+ self._driver: Any = None
26
+
27
+ def _init_with(self, code_finder: Any) -> "JavaToolkit":
28
+ """Initialise with a live CodeFinder for Spring-specific queries."""
22
29
  self._cf = code_finder
23
30
  self._driver = code_finder.driver
31
+ return self
24
32
 
25
33
  # ── Internal helpers ──────────────────────────────────────────────────────
26
34
 
@@ -106,8 +114,8 @@ class JavaToolkit:
106
114
  f"""
107
115
  MATCH (c:Class)
108
116
  WHERE {where}
109
- OPTIONAL MATCH ()-[:INJECTS]->(c)
110
- WITH c, count(*) AS injection_count
117
+ OPTIONAL MATCH ()-[inj:INJECTS]->(c)
118
+ WITH c, count(inj) AS injection_count
111
119
  RETURN c.name AS name, c.spring_stereotype AS stereotype,
112
120
  c.path AS file, c.line_number AS line_number,
113
121
  injection_count
@@ -249,5 +257,6 @@ class JavaToolkit:
249
257
  # Legacy shim — keeps existing callers working
250
258
  def get_cypher_query(self, query: str) -> str:
251
259
  raise NotImplementedError(
260
+ "AdvancedLanguageQuery is not implemented yet for Java. "
252
261
  "Use find_spring_endpoints(), find_beans_by_stereotype(), etc. instead."
253
262
  )
@@ -220,12 +220,25 @@ def _section_cross_module_calls(driver: Any, limit: int = 20, repo_path: Optiona
220
220
 
221
221
  def _section_dead_code(driver: Any, limit: int = 20, repo_path: Optional[str] = None) -> str:
222
222
  """Functions with no incoming CALLS edges (potential dead code)."""
223
+ # Share the entry-point list with `CodeFinder.find_dead_code` so the report
224
+ # and the CLI/MCP tool cannot disagree. They previously did, in both
225
+ # directions: this query had no name exclusions at all (so every dunder and
226
+ # `test_*` showed up as dead), while `find_dead_code` restricted callers to
227
+ # `(caller:Function)` and so missed the File-sourced edges that this one
228
+ # correctly counts.
229
+ from .code_finder import _ENTRY_POINT_NAMES_CYPHER
230
+
223
231
  rows = _run_cypher(
224
232
  driver,
225
- """
233
+ f"""
226
234
  MATCH (fn:Function)
227
235
  WHERE (fn.is_dependency IS NULL OR fn.is_dependency = false)
228
236
  AND ($repo_path IS NULL OR fn.path STARTS WITH $repo_path)
237
+ AND NOT toLower(fn.name) IN {_ENTRY_POINT_NAMES_CYPHER}
238
+ AND fn.name <> '<module>'
239
+ AND NOT (fn.name STARTS WITH '__' AND fn.name ENDS WITH '__')
240
+ AND NOT fn.name STARTS WITH '_test'
241
+ AND NOT fn.name STARTS WITH 'test_'
229
242
  AND NOT ()-[:CALLS|HEURISTIC_CALLS]->(fn)
230
243
  RETURN fn.name AS name, fn.path AS path
231
244
  ORDER BY fn.path, fn.name
@@ -12,6 +12,7 @@ import json
12
12
  import os
13
13
  import tempfile
14
14
  import webbrowser
15
+ from html import escape as html_escape
15
16
  from pathlib import Path
16
17
  from typing import Any, Dict, List, Optional
17
18
 
@@ -55,6 +56,24 @@ def edge_color(rel_type: str) -> str:
55
56
  return _EDGE_COLORS.get(rel_type, _EDGE_COLORS["default"])
56
57
 
57
58
 
59
+ def _json_for_script(data: Any) -> str:
60
+ """Serialise ``data`` for inlining inside a ``<script>`` element.
61
+
62
+ ``json.dumps`` escapes quotes and backslashes but leaves ``<``, ``>`` and
63
+ ``&`` alone, so a node name or file path containing ``</script>`` closes
64
+ the element early and the rest of the document is parsed as markup. Those
65
+ three characters only ever appear inside string literals, so replacing
66
+ them with their ``\\uXXXX`` form keeps the payload valid JSON and valid
67
+ JavaScript while making it inert as markup.
68
+ """
69
+ return (
70
+ json.dumps(data, indent=2)
71
+ .replace("<", "\\u003c")
72
+ .replace(">", "\\u003e")
73
+ .replace("&", "\\u0026")
74
+ )
75
+
76
+
58
77
  def build_graph_data(
59
78
  nodes: List[Dict[str, Any]],
60
79
  edges: List[Dict[str, Any]],
@@ -125,14 +144,15 @@ def render_html(
125
144
  str
126
145
  Complete HTML document as a string.
127
146
  """
128
- graph_json = json.dumps(graph_data, indent=2)
147
+ graph_json = _json_for_script(graph_data)
148
+ safe_title = html_escape(title)
129
149
 
130
150
  html = f"""<!DOCTYPE html>
131
151
  <html lang="en">
132
152
  <head>
133
153
  <meta charset="UTF-8" />
134
154
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
135
- <title>{title}</title>
155
+ <title>{safe_title}</title>
136
156
  <style>
137
157
  * {{ box-sizing: border-box; margin: 0; padding: 0; }}
138
158
  body {{
@@ -198,7 +218,7 @@ def render_html(
198
218
  </head>
199
219
  <body>
200
220
  <div id="header">
201
- <h1>🏗 {title}</h1>
221
+ <h1>🏗 {safe_title}</h1>
202
222
  <span id="stats"></span>
203
223
  </div>
204
224
  <div id="canvas-container">
@@ -96,13 +96,16 @@ def _get_eid(element):
96
96
  if isinstance(element, (int, str)):
97
97
  return str(element)
98
98
 
99
- # If element is a dict (like Neo4j returned items or KuzuDB node/rel dicts)
99
+ # If element is a dict (like Neo4j returned items or KuzuDB/Ladybug node/rel
100
+ # dicts). KùzuDB spells its internal id field '_id'; LadybugDB spells the
101
+ # same field '_ID' (uppercase) — confirmed against a live query on each
102
+ # embedded backend.
100
103
  if isinstance(element, dict):
101
104
  # KuzuDB _src / _dst are directly {'offset': X, 'table': Y}
102
105
  if 'offset' in element and 'table' in element:
103
106
  return f"{element.get('table')}_{element.get('offset')}"
104
-
105
- for key in ['_id', 'id', 'element_id']:
107
+
108
+ for key in ['_id', '_ID', 'id', 'element_id']:
106
109
  if key in element:
107
110
  val = element[key]
108
111
  if val is not None:
@@ -116,7 +119,7 @@ def _get_eid(element):
116
119
  for attr in ['element_id', 'id', '_id']:
117
120
  if hasattr(element, attr):
118
121
  val = getattr(element, attr)
119
- if val is not None:
122
+ if val is not None:
120
123
  # KuzuDB objects if any
121
124
  if isinstance(val, dict):
122
125
  return f"{val.get('table', 0)}_{val.get('offset', 0)}"
@@ -124,6 +127,18 @@ def _get_eid(element):
124
127
  return str(id(element))
125
128
 
126
129
 
130
+ def _meta(d: dict, name: str):
131
+ """Read a KùzuDB/LadybugDB internal dict field regardless of its casing.
132
+
133
+ KùzuDB spells these fields lowercase (_label/_src/_dst); LadybugDB spells
134
+ the same fields uppercase (_LABEL/_SRC/_DST) — confirmed against a live
135
+ query on each embedded backend. Keying on one casing only silently drops
136
+ every row on the other backend.
137
+ """
138
+ lower = f"_{name}"
139
+ return d[lower] if lower in d else d.get(f"_{name.upper()}")
140
+
141
+
127
142
  @app.get("/api/graph")
128
143
  async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str] = None):
129
144
  if not db_manager:
@@ -186,9 +201,10 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
186
201
  # Extract labels
187
202
  labels = []
188
203
  if isinstance(node, dict):
189
- # KuzuDB node label is under '_label'
190
- if '_label' in node:
191
- labels = [node['_label']]
204
+ # KuzuDB node label is under '_label' (LadybugDB: '_LABEL')
205
+ meta_label = _meta(node, 'label')
206
+ if meta_label is not None:
207
+ labels = [meta_label]
192
208
  elif 'label' in node:
193
209
  labels = [node['label']]
194
210
  else:
@@ -247,13 +263,17 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
247
263
  # FalkorDB / KuzuDB may return rels as dicts OR objects
248
264
  if isinstance(rel, dict):
249
265
  rid = get_eid(rel)
250
- # KuzuDB uses _src and _dst, FalkorDB uses src_node/dest_node
251
- src = rel.get('_src', rel.get('src_node'))
252
- dst = rel.get('_dst', rel.get('dest_node'))
253
-
266
+ # KuzuDB uses _src/_dst (LadybugDB: _SRC/_DST), FalkorDB uses src_node/dest_node
267
+ src = _meta(rel, 'src')
268
+ if src is None:
269
+ src = rel.get('src_node')
270
+ dst = _meta(rel, 'dst')
271
+ if dst is None:
272
+ dst = rel.get('dest_node')
273
+
254
274
  source = get_eid(src) if src is not None else None
255
275
  target = get_eid(dst) if dst is not None else None
256
- rel_type = str(rel.get('_label', rel.get('relation', rel.get('type', 'RELATED')))).upper()
276
+ rel_type = str(_meta(rel, 'label') or rel.get('relation', rel.get('type', 'RELATED'))).upper()
257
277
  else:
258
278
  rid = get_eid(rel)
259
279
  start_node = None
@@ -420,8 +440,9 @@ def parse_node(node, nodes_dict):
420
440
 
421
441
  labels = []
422
442
  if isinstance(node, dict):
423
- if '_label' in node:
424
- labels = [node['_label']]
443
+ meta_label = _meta(node, 'label')
444
+ if meta_label is not None:
445
+ labels = [meta_label]
425
446
  elif 'label' in node:
426
447
  labels = [node['label']]
427
448
  else:
@@ -463,11 +484,15 @@ def parse_node(node, nodes_dict):
463
484
  def parse_rel(rel, edges):
464
485
  rid = _get_eid(rel)
465
486
  if isinstance(rel, dict):
466
- src = rel.get('_src', rel.get('src_node'))
467
- dst = rel.get('_dst', rel.get('dest_node'))
487
+ src = _meta(rel, 'src')
488
+ if src is None:
489
+ src = rel.get('src_node')
490
+ dst = _meta(rel, 'dst')
491
+ if dst is None:
492
+ dst = rel.get('dest_node')
468
493
  source = _get_eid(src) if src is not None else None
469
494
  target = _get_eid(dst) if dst is not None else None
470
- rel_type = str(rel.get('_label', rel.get('relation', rel.get('type', 'RELATED')))).upper()
495
+ rel_type = str(_meta(rel, 'label') or rel.get('relation', rel.get('type', 'RELATED'))).upper()
471
496
  else:
472
497
  start_node = None
473
498
  end_node = None
@@ -510,9 +535,16 @@ def parse_element(val, nodes_dict, edges):
510
535
 
511
536
  type_name = type(val).__name__
512
537
 
513
- if type_name in ('Node', 'KuzuNode') or (isinstance(val, dict) and ('_label' in val or 'labels' in val) and not any(k in val for k in ('_src', '_dst', 'src_node', 'dest_node'))):
538
+ is_dict_rel = isinstance(val, dict) and (
539
+ any(k in val for k in ('_src', '_dst', '_SRC', '_DST', 'src_node', 'dest_node'))
540
+ )
541
+ is_dict_node = isinstance(val, dict) and (
542
+ '_label' in val or '_LABEL' in val or 'labels' in val
543
+ ) and not is_dict_rel
544
+
545
+ if type_name in ('Node', 'KuzuNode') or is_dict_node:
514
546
  parse_node(val, nodes_dict)
515
- elif type_name in ('Relationship', 'KuzuRelationship') or (isinstance(val, dict) and any(k in val for k in ('_src', '_dst', 'src_node', 'dest_node'))):
547
+ elif type_name in ('Relationship', 'KuzuRelationship') or is_dict_rel:
516
548
  parse_rel(val, edges)
517
549
  elif isinstance(val, (list, tuple)):
518
550
  for item in val:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.5.5
3
+ Version: 0.5.7
4
4
  Summary: An MCP server that indexes local code into a graph database to provide context to AI assistants.
5
5
  Author-email: Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
6
6
  License: MIT License
@@ -372,8 +372,8 @@ CodeGraphContext supports multiple graph database backends to suit your environm
372
372
  | **Platform** | **All (Windows Native, macOS, Linux)** | **All (Windows Native, macOS, Linux)** | Unix-only (Linux/macOS/WSL) | All Platforms |
373
373
  | **Use Case** | Desktop, IDE, Local development | Custom research projects | Specialized Unix development | Enterprise, Massive graphs |
374
374
  | **Requirement**| `pip install kuzu` | `pip install ladybug` | `pip install falkordblite` | Neo4j Server / Docker / Nornic Cloud |
375
- | **Speed** | ⚡ Extremely Fast | ⚡ Fast | 🚀 Scalable |
376
- | **Persistence**| Yes (to disk) | Yes (to disk) | Yes (to disk) |
375
+ | **Speed** | ⚡ Extremely Fast | ⚡ Fast | 🚀 Scalable | 🌐 Network-dependent |
376
+ | **Persistence**| Yes (to disk) | Yes (to disk) | Yes (to disk) | Yes (server-side) |
377
377
 
378
378
  ---
379
379
 
@@ -14,7 +14,7 @@ codegraphcontext/cli/__init__.py,sha256=2vUlnf7H09M9JbS2gS_LmMUBJWAWcGGHkkl4I2Yr
14
14
  codegraphcontext/cli/cli_helpers.py,sha256=w7TCwcL5T6XyRPYKzLkBdt0V6O3YEkWlPl_WTBZHWEA,54179
15
15
  codegraphcontext/cli/config_manager.py,sha256=wjYT-aYreRfgRxd0Hy_thLSmlgsV2_JRuMVlVlZlEBo,54960
16
16
  codegraphcontext/cli/hook_manager.py,sha256=nKr6SiuM6zA3eZv1rZSNPXUyqkha8Ja0VGQ3_145bLE,7615
17
- codegraphcontext/cli/main.py,sha256=9JL3Y7rA3LnUweRcRKV7HMQmT5FLSBYjOI1qjyvSIuo,140755
17
+ codegraphcontext/cli/main.py,sha256=G5DEaVvLX4rqOrB2YemRz8YvG8Alc0Naot9B_rhwTJ0,141135
18
18
  codegraphcontext/cli/project_config.py,sha256=Slae6fIojlcqRbFzRkAMAsglamryLCPrjUFG16e165g,7492
19
19
  codegraphcontext/cli/registry_commands.py,sha256=KwLwxKzMWs0tAFd__nPf_gz3kmlPO5b6Zcb_9Qkguf0,18516
20
20
  codegraphcontext/cli/setup_macos.py,sha256=0jcxRsQX7_UcfKrFUnc5Yx3m34w0C3w3x5n7KH6ty6g,3743
@@ -38,11 +38,11 @@ codegraphcontext/core/jobs.py,sha256=8D5E8K_t9jTzPGSAeNK05d-7dPq2SeIJA0igSbHit9A
38
38
  codegraphcontext/core/simulator.py,sha256=Sb3PpnoMGVDWvqPVSv5dcQqiL3nTvhmLzzLzJ41krkE,24140
39
39
  codegraphcontext/core/watcher.py,sha256=nSfkmZueMBHxx8-7zNHScKtaSGfNWrfjWdAe-T0Y2vI,18793
40
40
  codegraphcontext/tools/__init__.py,sha256=iqQPEkDlGXO7rAaLNQrS_ZAbLSklBSK-OowpARq0mig,41
41
- codegraphcontext/tools/advanced_language_query_tool.py,sha256=QXDKcS3Ug6bNjzY3gjjS2zmPC5cEPyrHGJhAqPXTMuo,4025
42
- codegraphcontext/tools/code_finder.py,sha256=ZjHoOLYeC5Xc8N4VU5APqGiATzk9AB5gX9u9wVl0SEY,72040
41
+ codegraphcontext/tools/advanced_language_query_tool.py,sha256=8pRVMg-Tbp3JrbN31McyTbum91gJPlrjHFCFVJ78coA,4169
42
+ codegraphcontext/tools/code_finder.py,sha256=d8RK2HQVPD4EQ_ZmhSsEhU6AY0NoYPnIjROwZpGEB78,73651
43
43
  codegraphcontext/tools/graph_builder.py,sha256=uPHc4q8u7NQ7lSceRm4IwN3nLxgq9C0MvQtkY8HA9CE,57271
44
- codegraphcontext/tools/package_resolver.py,sha256=d2lkHNguvL_QRihD2bbBN-aMGrXRgsK-8pLWkci3nFk,7366
45
- codegraphcontext/tools/report_generator.py,sha256=HLCT6sUmYZxouSXsk0vc1ILV-j2jJjer_RXHTV5MskM,15377
44
+ codegraphcontext/tools/package_resolver.py,sha256=j6s4H5VnourUdJ-CkA3kCOc-Vdewnjc-PNDshAPCiaU,7802
45
+ codegraphcontext/tools/report_generator.py,sha256=3tH83_24nm-pWcY_oXUeLB2jYtGGZmkiBb_oyuhS4WM,16114
46
46
  codegraphcontext/tools/scip_indexer.py,sha256=8ZHaooZeQgTtO_wRI0VwiMgIQF1QNAg7o6Xx5VGj3BM,39112
47
47
  codegraphcontext/tools/scip_pb2.py,sha256=jSMLR7ud4eaKrIkQN-XDWNaFCyUaueafUm9WgOhjqRA,98270
48
48
  codegraphcontext/tools/system.py,sha256=RCUraHQOT-M146CwLM3W1GcBHsAvuans1puL9SPakxs,6647
@@ -52,9 +52,9 @@ codegraphcontext/tools/datasources/__init__.py,sha256=EDY0_s7DJPRzJhfmYUz3Pxv0b6
52
52
  codegraphcontext/tools/datasources/cassandra_ingester.py,sha256=sgbskSkvE98qtOprgzkbVSEIasZWe4WXOzaCiRR2c_Q,3060
53
53
  codegraphcontext/tools/datasources/mysql_ingester.py,sha256=uWstOv7mOEws1pmT8uAsEBkoOGM1FXOWt_2VCE-tZeg,3991
54
54
  codegraphcontext/tools/datasources/redis_ingester.py,sha256=mVUKYmr_v6g7wYaavmQscEHGi_CPZrxYLjfkldc6cWE,3788
55
- codegraphcontext/tools/handlers/analysis_handlers.py,sha256=wasVt3k0fUwNZn6gr3tvD29cJ3IY5Qqk26B7dkE_2XM,14104
56
- codegraphcontext/tools/handlers/indexing_handlers.py,sha256=EDPZOcT4LwhqYrvytoN_NvpOPxTWprLmsc67h5HXmGg,6472
57
- codegraphcontext/tools/handlers/management_handlers.py,sha256=D06vHxhtWeRlyzeFmIDcSNw5qrilLW5sO47HsDdrGcI,17392
55
+ codegraphcontext/tools/handlers/analysis_handlers.py,sha256=8pZAcNNw0CFJ4LfuYqcJ-4cnS-X7_4sKWwd6S7EUGos,14155
56
+ codegraphcontext/tools/handlers/indexing_handlers.py,sha256=sKtbP19EX694zs-6rsVkhwihGwSIWaKjm4N41jMXP2M,7437
57
+ codegraphcontext/tools/handlers/management_handlers.py,sha256=19-fCuMuY5oHXQ9O4F1qG4ZsKKsGpSGbxxA1gH4AE-g,17419
58
58
  codegraphcontext/tools/handlers/query_handlers.py,sha256=mqI7pfuSRc92ChAS5IGtrkPyhpPeh-dg-ewVnysx5nw,4643
59
59
  codegraphcontext/tools/handlers/watcher_handlers.py,sha256=po0NP3WnUqJAiDyZW9pz2g2V-itnazI10cXQJuc1UZ8,3809
60
60
  codegraphcontext/tools/indexing/__init__.py,sha256=exCNeB4UYhKhNSoEBNta_EZ51Kdze4iK4e3oWH8B4X8,126
@@ -66,7 +66,7 @@ codegraphcontext/tools/indexing/pre_scan.py,sha256=St7Hc3-NFTMYF39vWneJL6iIRIc78
66
66
  codegraphcontext/tools/indexing/sanitize.py,sha256=IaTP7Aztso3SsvjNZMEqJ1iktV4Tl75hXcYnCWBGpLs,1490
67
67
  codegraphcontext/tools/indexing/schema.py,sha256=AcHaqxDi9d5nGjZdg5S6mbjhHo98UYPRfcz1ZpvNEy4,9157
68
68
  codegraphcontext/tools/indexing/schema_contract.py,sha256=23ok37ASjvOsU8S-sqyRiKIkGhcUxQ8PSErbBmylggU,1815
69
- codegraphcontext/tools/indexing/scip_pipeline.py,sha256=Eb7VKN13vPOJ_Ag34c1yQ4c41TGvsemjMKFHnSjBuCg,12724
69
+ codegraphcontext/tools/indexing/scip_pipeline.py,sha256=OLbf0rhs1XU4LTQ6B3mS1-ngbPEKIN69o59rw9Pbk_E,13128
70
70
  codegraphcontext/tools/indexing/vector_resolver.py,sha256=0D0QClro7nx9Wx6hbbFlOsO4dqgcTrwPMqE0CsJK01o,5477
71
71
  codegraphcontext/tools/indexing/persistence/__init__.py,sha256=hFRlNgiYS7uN9X4e4xfP3c1BvcYSxpmq0k_uaDRn2Fg,121
72
72
  codegraphcontext/tools/indexing/persistence/utils.py,sha256=OtndHpE_R14mPz5i-Bo9mA7NsoIuYnCNER-oIwxDOYA,2028
@@ -75,9 +75,9 @@ codegraphcontext/tools/indexing/resolution/__init__.py,sha256=BR57mVydpzjOnFSbGb
75
75
  codegraphcontext/tools/indexing/resolution/calls.py,sha256=Uamtm8DXJnwlnTkdzYfu6G74MOcEyQ4njasweE7Nydo,130762
76
76
  codegraphcontext/tools/indexing/resolution/inheritance.py,sha256=xataBg0Lvgj3Sexgxx434TmwgRpvODaQs5HQYEVxPVs,21192
77
77
  codegraphcontext/tools/indexing/resolution/post_resolution.py,sha256=OzmbbmLwIbKb5wm1RodoyvDST3d35A2e35ruDKBQDF4,10859
78
- codegraphcontext/tools/languages/c.py,sha256=1lmae7RMttJPJRv1YdRNv8jVn2bU35JxFy_JlMARJyo,30601
79
- codegraphcontext/tools/languages/cpp.py,sha256=_ZeKPu5c7dR5IhGMWJnklcQOD4DbEMSF_PuntdEEmio,28378
80
- codegraphcontext/tools/languages/csharp.py,sha256=JvHdg-IsXBm7DC5KtxVJyFMWnQw6FnspCDrAoUqe1H0,23988
78
+ codegraphcontext/tools/languages/c.py,sha256=quo2j0PCTlSOJctqqF_5zKeisERvTDyKuPtz_qvFgQA,31618
79
+ codegraphcontext/tools/languages/cpp.py,sha256=41kDYWbuH6hjNAZyCEH9RhZBNCUz8kQyccw3Ppd-efQ,31389
80
+ codegraphcontext/tools/languages/csharp.py,sha256=oiSBmEf96mIU-2u-0OVXyx3QTn1_lOv-XLPc6aujarA,27502
81
81
  codegraphcontext/tools/languages/css.py,sha256=aN1Nm8vHmfV0J0jQ-_q5x0YDTOtLV2IW-5lp7WtGG5o,3789
82
82
  codegraphcontext/tools/languages/dart.py,sha256=yjqVA6XwoADrZab95p6wva0jT-PAGw_x90LR-cp2BDk,27160
83
83
  codegraphcontext/tools/languages/elisp.py,sha256=QVxJq328bR4DsQK8Vs8b9oxthx1Wm_BS1dsCSjD_P8I,22717
@@ -87,7 +87,7 @@ codegraphcontext/tools/languages/gradle.py,sha256=WA1nQTi-JMHv5vLTJ35ECW0dQCb4PE
87
87
  codegraphcontext/tools/languages/haskell.py,sha256=YyslUwcanFs00IinFrj7_TJ2IY2Q97NYsG2Y-9qqolU,19888
88
88
  codegraphcontext/tools/languages/html.py,sha256=cjzAH55-JYbObGcMiqjuYD6X0Cbs1Kj5ixcZv9BzMns,5595
89
89
  codegraphcontext/tools/languages/java.py,sha256=dpyHBGPhL1FaYP_unh-ncRDOlmmA2nBqnkLc1tacYm0,52024
90
- codegraphcontext/tools/languages/javascript.py,sha256=Pz2HlHx2YxChx6yA76BNZDNHpjEBjshmSmW8bavKUg0,26941
90
+ codegraphcontext/tools/languages/javascript.py,sha256=LR0W2BKZANQaQlNiIshORmJ08IAVVf9Pv1a8-6pILDo,30197
91
91
  codegraphcontext/tools/languages/kotlin.py,sha256=8W367uThQzuTqbFzobUnjm_VEGf_jhLox_fmo13wYsc,88644
92
92
  codegraphcontext/tools/languages/lua.py,sha256=TxpOxFd47E4hKwkJpO7b_dMvWveYd4Eaqp1n5SnN-zM,16726
93
93
  codegraphcontext/tools/languages/maven.py,sha256=yQjrdSRZJPy6tUopZ7x5qYp8Rtbug4PPbBjCdl9FMDY,6393
@@ -95,7 +95,7 @@ codegraphcontext/tools/languages/mybatis.py,sha256=T6mVBCg7_5xOobBIAqZg9oHtZQy7s
95
95
  codegraphcontext/tools/languages/perl.py,sha256=vjz65VFoKLsql2gyaT44B3R0sieloQmxO7fHMp8aqLA,12837
96
96
  codegraphcontext/tools/languages/php.py,sha256=WwdFmWvBxQX8rsqHFCr8tdrz_W3pWv_COp1Bw-xiGiE,29804
97
97
  codegraphcontext/tools/languages/python.py,sha256=6Fd05Hxc-jVRTa6t0wYuk8-yZ-0MdrRW3xo1TOucxEk,32714
98
- codegraphcontext/tools/languages/ruby.py,sha256=GCU2AOfU8H4nNzunf0F4OU_aLr3GGIWcSJgVhO89up8,23428
98
+ codegraphcontext/tools/languages/ruby.py,sha256=vkSNS6JOmmVKxG9eDPo5rNd6fNACUScrEjGBZbHg8Fc,24961
99
99
  codegraphcontext/tools/languages/rust.py,sha256=o_uhEL_a4REPYXqdH4tljucyXMs5ModaMfeT-sanTBc,15560
100
100
  codegraphcontext/tools/languages/scala.py,sha256=TmH_DK7baoo7_eMYdu9e7Cqb0ddVpibrhUO5TQD4AsI,23820
101
101
  codegraphcontext/tools/languages/sfc_common.py,sha256=BLCqNO8EoLSdxt1u-1XvrkUotgWHJkXts6w1NQXH6XU,8567
@@ -113,7 +113,7 @@ codegraphcontext/tools/query_tool_languages/dart_toolkit.py,sha256=W5ClOZ-KCRFmw
113
113
  codegraphcontext/tools/query_tool_languages/elisp_toolkit.py,sha256=fH8T5HKULMSgq8m46kCUi7Ql1lglTH5taQemJLCrKi0,2591
114
114
  codegraphcontext/tools/query_tool_languages/go_toolkit.py,sha256=wK-QMk8fBlJjvnvp9nK9hRq-GbcfsdRaY35E65uacME,273
115
115
  codegraphcontext/tools/query_tool_languages/haskell_toolkit.py,sha256=3GMwoSAk8X1nK4Yjz8YMhc_m7bStEI7wYOu0tgTkZl4,283
116
- codegraphcontext/tools/query_tool_languages/java_toolkit.py,sha256=ABruH-CA_rmm6qCjBKVWoVGEwoApWc5LgJoGEqFUSdo,8961
116
+ codegraphcontext/tools/query_tool_languages/java_toolkit.py,sha256=jjHs_lMgMFu8AgOcUvNrWJSdGSKTZuSYRyjzuESWFMo,9423
117
117
  codegraphcontext/tools/query_tool_languages/javascript_toolkit.py,sha256=AZNo63prnaETy1BXy1en40brGODIpcrH6GdmSohp5co,289
118
118
  codegraphcontext/tools/query_tool_languages/perl_toolkit.py,sha256=vkd9O5YCXIgSbq2dYVvWT4T8BvL66Qo98TRf0kD5w-c,277
119
119
  codegraphcontext/tools/query_tool_languages/python_toolkit.py,sha256=wijYvL7g4wevJhrtbk5asLC8oxIlpZ8IxidRi4HChKM,281
@@ -133,8 +133,8 @@ codegraphcontext/utils/path_sandbox.py,sha256=AemD1DHVMA0lj9BpmRkKwJB2OaE0Sewggm
133
133
  codegraphcontext/utils/repo_path.py,sha256=BxS3y6RV_WaZhg_WxgJygS4SnQa-Ryth2fsicfnYorY,1121
134
134
  codegraphcontext/utils/tool_limits.py,sha256=3d-QnlzxBNocz58VKNhdsta7ZpWThs1AX46_gYXl6pQ,2751
135
135
  codegraphcontext/utils/tree_sitter_manager.py,sha256=Xr26xYL1HUP8V1Hk9d7tbS4RUb774f-Stc2DtuE7oN0,13307
136
- codegraphcontext/utils/visualize_graph.py,sha256=GncCYgDgT2G23lh-DD-qmrMiahilhHOaAGh6Fei1Yvo,13857
137
- codegraphcontext/viz/server.py,sha256=IBNSCSOua3xJPcOvKltkU8TGxfiUqJJ3_5vCF4hBKB0,30719
136
+ codegraphcontext/utils/visualize_graph.py,sha256=me35VuizqLyveT86XdgavrgnB2B4lG7WqFyiMr5ea1w,14644
137
+ codegraphcontext/viz/server.py,sha256=l2hM6phhYeMn0vAmMz7wtRGdj7LMkg2BsNMOvS1RUfg,31833
138
138
  codegraphcontext/viz/dist/cgcIcon.png,sha256=lik7hnqvkqLc5uo9w9Q_YJXtRx7cWPrXqSLttGtW1sg,58703
139
139
  codegraphcontext/viz/dist/favicon.ico,sha256=AjhUxD_Eslt5XuSVHIAZ494Fk__rb5GLXR8qm0elfP4,1870
140
140
  codegraphcontext/viz/dist/index.html,sha256=uEaFo3uHWD2KojFiyfWRJoS1_t6zrllOnf_siaB0I5U,1946
@@ -173,9 +173,9 @@ codegraphcontext/viz/dist/wasm/tree-sitter-typescript.wasm,sha256=hRVATc7tOOHthq
173
173
  codegraphcontext/viz/dist/wasm/tree-sitter.wasm,sha256=CCeVuI_hXktkBD-DW01xYPv5XoAYVRIqzJUJI5te-RY,196763
174
174
  codegraphcontext/viz/dist/wasm/web-tree-sitter.js,sha256=DIaCNqRylrT_PBVw8g4ImeSnhP9uXNe_ycOlUiVGPko,153666
175
175
  codegraphcontext/viz/dist/wasm/web-tree-sitter.wasm,sha256=CCeVuI_hXktkBD-DW01xYPv5XoAYVRIqzJUJI5te-RY,196763
176
- codegraphcontext-0.5.5.dist-info/licenses/LICENSE,sha256=rh8M-bJpQYJnw2vtRVgt0t7piMZXh5QzaKeNEI0vqqA,1061
177
- codegraphcontext-0.5.5.dist-info/METADATA,sha256=g-1RGSS7QULexGiay2b2oPTWrrm8A3XiQYX8QC2D7Eo,32847
178
- codegraphcontext-0.5.5.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
179
- codegraphcontext-0.5.5.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
180
- codegraphcontext-0.5.5.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
181
- codegraphcontext-0.5.5.dist-info/RECORD,,
176
+ codegraphcontext-0.5.7.dist-info/licenses/LICENSE,sha256=rh8M-bJpQYJnw2vtRVgt0t7piMZXh5QzaKeNEI0vqqA,1061
177
+ codegraphcontext-0.5.7.dist-info/METADATA,sha256=pb9kZrMtqIxBsrCVhKXy0zX--PZTWRc6jE0Px6G7dyg,32892
178
+ codegraphcontext-0.5.7.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
179
+ codegraphcontext-0.5.7.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
180
+ codegraphcontext-0.5.7.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
181
+ codegraphcontext-0.5.7.dist-info/RECORD,,