codegraphcontext 0.3.7__py3-none-any.whl → 0.3.8__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.
@@ -483,9 +483,10 @@ def clean_helper():
483
483
  console.print("[cyan]🧹 Cleaning database (removing orphaned nodes)...[/cyan]")
484
484
 
485
485
  try:
486
- # Determine if we're using FalkorDB or Neo4j for query optimization
486
+ # Determine backend type for query compatibility
487
487
  db_type = db_manager.__class__.__name__
488
488
  is_falkordb = "Falkor" in db_type
489
+ is_kuzu = "Kuzu" in db_type
489
490
 
490
491
  total_deleted = 0
491
492
  batch_size = 1000
@@ -493,14 +494,15 @@ def clean_helper():
493
494
  with db_manager.get_driver().session() as session:
494
495
  # Keep deleting orphaned nodes in batches until none are found
495
496
  while True:
496
- if is_falkordb:
497
- # FalkorDB-compatible query using OPTIONAL MATCH
497
+ if is_falkordb or is_kuzu:
498
+ # FalkorDB / KùzuDB-compatible query using OPTIONAL MATCH
499
+ # (KùzuDB does not support the Neo4j `NOT EXISTS { MATCH ... }` subquery syntax)
498
500
  query = """
499
501
  MATCH (n)
500
502
  WHERE NOT (n:Repository)
501
- OPTIONAL MATCH path = (n)-[*..10]-(r:Repository)
502
- WITH n, path
503
- WHERE path IS NULL
503
+ OPTIONAL MATCH p = (n)-[*..10]-(r:Repository)
504
+ WITH n, p
505
+ WHERE p IS NULL
504
506
  WITH n LIMIT $batch_size
505
507
  DETACH DELETE n
506
508
  RETURN count(n) as deleted
@@ -109,14 +109,21 @@ class KuzuDBManager:
109
109
  ("Parameter", "uid STRING, name STRING, path STRING, function_line_number INT64, PRIMARY KEY (uid)")
110
110
  ]
111
111
 
112
+ # rel_tables: list of (table_name, schema, use_group)
113
+ # use_group=True -> CREATE REL TABLE GROUP (for multi FROM..TO bindings)
114
+ # use_group=False -> CREATE REL TABLE (single binding)
112
115
  rel_tables = [
113
- ("CONTAINS", "FROM File TO Function, FROM File TO Class, FROM File TO Variable, FROM File TO Trait, FROM File TO Interface, FROM `Macro` TO `Macro`, FROM File TO `Macro`, FROM File TO Struct, FROM File TO Enum, FROM File TO `Union`, FROM File TO Annotation, FROM File TO Record, FROM File TO Property, FROM Repository TO Directory, FROM Directory TO Directory, FROM Directory TO File, FROM Repository TO File, FROM Class TO Function, FROM Function TO Function"),
114
- ("CALLS", "FROM Function TO Function, FROM Function TO Class, FROM File TO Function, FROM File TO Class, FROM Class TO Function, FROM Class TO Class, line_number INT64, args STRING[], full_call_name STRING"),
115
- ("IMPORTS", "FROM File TO Module, alias STRING, full_import_name STRING, imported_name STRING, line_number INT64"),
116
- ("INHERITS", "FROM Class TO Class, FROM Record TO Record, FROM Interface TO Interface"),
117
- ("HAS_PARAMETER", "FROM Function TO Parameter"),
118
- ("INCLUDES", "FROM Class TO Module"),
119
- ("IMPLEMENTS", "FROM Class TO Interface, FROM Struct TO Interface, FROM Record TO Interface")
116
+ # Note: in KùzuDB, some labels (e.g. `Macro`, `Property`, `Union`) are treated as reserved
117
+ # keywords in CREATE REL TABLE statements. We must escape them with backticks
118
+ # or the rel table creation will fail silently, leading to runtime
119
+ # "Binder exception: Table CONTAINS does not exist".
120
+ ("CONTAINS", "FROM File TO Function, FROM File TO Class, FROM File TO Variable, FROM File TO Trait, FROM File TO Interface, FROM `Macro` TO `Macro`, FROM File TO `Macro`, FROM File TO Struct, FROM File TO Enum, FROM File TO `Union`, FROM File TO Annotation, FROM File TO Record, FROM File TO `Property`, FROM Repository TO Directory, FROM Directory TO Directory, FROM Directory TO File, FROM Repository TO File, FROM Class TO Function, FROM Function TO Function", True),
121
+ ("CALLS", "FROM Function TO Function, FROM Function TO Class, FROM File TO Function, FROM File TO Class, FROM Class TO Function, FROM Class TO Class, line_number INT64, args STRING[], full_call_name STRING", True),
122
+ ("IMPORTS", "FROM File TO Module, alias STRING, full_import_name STRING, imported_name STRING, line_number INT64", False),
123
+ ("INHERITS", "FROM Class TO Class, FROM Record TO Record, FROM Interface TO Interface", True),
124
+ ("HAS_PARAMETER", "FROM Function TO Parameter", False),
125
+ ("INCLUDES", "FROM Class TO Module", False),
126
+ ("IMPLEMENTS", "FROM Class TO Interface, FROM Struct TO Interface, FROM Record TO Interface", True)
120
127
  ]
121
128
 
122
129
  for table_name, schema in node_tables:
@@ -127,10 +134,13 @@ class KuzuDBManager:
127
134
  warning_logger(f"Kuzu Schema Node Error ({table_name}): {e}")
128
135
  debug_log(f"Kuzu Schema Node Error ({table_name}): {e}")
129
136
 
130
- for table_name, schema in rel_tables:
137
+ for table_name, schema, use_group in rel_tables:
131
138
  try:
132
- # Need to handle backticks in schema as well for keywords
133
- self._conn.execute(f"CREATE REL TABLE `{table_name}`({schema})")
139
+ if use_group:
140
+ # KùzuDB requires CREATE REL TABLE GROUP for multi-binding relationships
141
+ self._conn.execute(f"CREATE REL TABLE GROUP `{table_name}`({schema})")
142
+ else:
143
+ self._conn.execute(f"CREATE REL TABLE `{table_name}`({schema})")
134
144
  except Exception as e:
135
145
  if "already exists" not in str(e).lower():
136
146
  warning_logger(f"Kuzu Schema Rel Error ({table_name}): {e}")
@@ -36,6 +36,40 @@ from .tools.handlers import (
36
36
  DEFAULT_EDIT_DISTANCE = 2
37
37
  DEFAULT_FUZZY_SEARCH = False
38
38
 
39
+ WORKSPACE_PREFIX = "/workspace/"
40
+
41
+
42
+ def _is_path_key(key: str) -> bool:
43
+ """Check if a dict key represents a file path field.
44
+
45
+ Matches keys like 'path', 'clone_path', 'caller_file_path', and also
46
+ Cypher-aliased keys like 'f.path', 'n.caller_file_path'.
47
+ """
48
+ # Strip Cypher alias prefix (e.g. "f.path" -> "path")
49
+ bare = key.rsplit(".", 1)[-1] if "." in key else key
50
+ return bare == "path" or bare.endswith("_path")
51
+
52
+
53
+ def _strip_path_value(value):
54
+ """Strip /workspace/ prefix from a single string value."""
55
+ if isinstance(value, str) and value.startswith(WORKSPACE_PREFIX):
56
+ return value[len(WORKSPACE_PREFIX):]
57
+ return value
58
+
59
+
60
+ def _strip_workspace_prefix(obj):
61
+ """Recursively strip /workspace/ prefix from path values in results."""
62
+ if isinstance(obj, dict):
63
+ return {
64
+ k: _strip_path_value(v) if _is_path_key(k) else _strip_workspace_prefix(v)
65
+ for k, v in obj.items()
66
+ }
67
+ elif isinstance(obj, list):
68
+ return [_strip_workspace_prefix(item) for item in obj]
69
+ return obj
70
+
71
+
72
+
39
73
  class MCPServer:
40
74
  """
41
75
  The main MCP Server class.
@@ -254,7 +288,8 @@ class MCPServer:
254
288
  tool_name = params.get('name')
255
289
  args = params.get('arguments', {})
256
290
  result = await self.handle_tool_call(tool_name, args)
257
-
291
+ result = _strip_workspace_prefix(result)
292
+
258
293
  if "error" in result:
259
294
  response = {
260
295
  "jsonrpc": "2.0", "id": request_id,
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Literal, Optional
5
5
  from pathlib import Path
6
6
 
7
7
  from ..core.database import DatabaseManager
8
+ from ..utils.path_ignore import cypher_path_not_under_ignore_dirs
8
9
 
9
10
  logger = logging.getLogger(__name__)
10
11
 
@@ -540,10 +541,12 @@ class CodeFinder:
540
541
  with self.driver.session() as session:
541
542
  repo_filter = "AND func.path STARTS WITH $repo_path" if repo_path else ""
542
543
  decorator_filter = "AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)" if exclude_decorated_with else ""
544
+ func_ignore = cypher_path_not_under_ignore_dirs("func.path")
545
+ caller_ignore = cypher_path_not_under_ignore_dirs("caller.path")
543
546
 
544
547
  query = f"""
545
548
  MATCH (func:Function)
546
- WHERE func.is_dependency = false {repo_filter}
549
+ WHERE func.is_dependency = false {repo_filter} {func_ignore}
547
550
  AND NOT func.name IN ['main', 'setup', 'run']
548
551
  AND NOT (func.name STARTS WITH '__' AND func.name ENDS WITH '__')
549
552
  AND NOT func.name STARTS WITH '_test'
@@ -555,7 +558,7 @@ class CodeFinder:
555
558
  {decorator_filter}
556
559
  WITH func
557
560
  OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
558
- WHERE caller.is_dependency = false
561
+ WHERE caller.is_dependency = false {caller_ignore}
559
562
  WITH func, count(caller) as caller_count
560
563
  WHERE caller_count = 0
561
564
  OPTIONAL MATCH (file:File)-[:CONTAINS]->(func)
@@ -591,8 +594,8 @@ class CodeFinder:
591
594
  # KùzuDB-compatible: Use anonymous end node and filter with WHERE
592
595
  query = f"""
593
596
  MATCH p = (f:Function)-[:CALLS*]->()
594
- WITH f, p, nodes(p) as path_nodes
595
- WITH f, path_nodes, list_extract(path_nodes, size(path_nodes)) as target
597
+ WITH f as f, p as p, nodes(p) as path_nodes
598
+ WITH f as f, path_nodes as path_nodes, path_nodes[size(path_nodes)] as target
596
599
  WHERE target.name = $function_name AND target.path = $path {repo_filter}
597
600
  RETURN DISTINCT f.name AS caller_name, f.path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
598
601
  ORDER BY caller_is_dependency ASC, caller_file_path, caller_line_number
@@ -603,8 +606,8 @@ class CodeFinder:
603
606
  # KùzuDB-compatible: Use anonymous end node and filter with WHERE
604
607
  query = f"""
605
608
  MATCH p = (f:Function)-[:CALLS*]->()
606
- WITH f, p, nodes(p) as path_nodes
607
- WITH f, path_nodes, list_extract(path_nodes, size(path_nodes)) as target
609
+ WITH f as f, p as p, nodes(p) as path_nodes
610
+ WITH f as f, path_nodes as path_nodes, path_nodes[size(path_nodes)] as target
608
611
  WHERE target.name = $function_name {repo_filter}
609
612
  RETURN DISTINCT f.name AS caller_name, f.path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
610
613
  ORDER BY caller_is_dependency ASC, caller_file_path, caller_line_number
@@ -622,8 +625,8 @@ class CodeFinder:
622
625
  query = f"""
623
626
  MATCH (caller:Function {{name: $function_name, path: $path}})
624
627
  MATCH p = (caller)-[:CALLS*]->()
625
- WITH p, nodes(p) as path_nodes
626
- WITH list_extract(path_nodes, size(path_nodes)) as f
628
+ WITH p as p, nodes(p) as path_nodes
629
+ WITH path_nodes[size(path_nodes)] as f
627
630
  {repo_filter}
628
631
  RETURN DISTINCT f.name AS callee_name, f.path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
629
632
  ORDER BY callee_is_dependency ASC, callee_file_path, callee_line_number
@@ -635,8 +638,8 @@ class CodeFinder:
635
638
  query = f"""
636
639
  MATCH (caller:Function {{name: $function_name}})
637
640
  MATCH p = (caller)-[:CALLS*]->()
638
- WITH p, nodes(p) as path_nodes
639
- WITH list_extract(path_nodes, size(path_nodes)) as f
641
+ WITH p as p, nodes(p) as path_nodes
642
+ WITH path_nodes[size(path_nodes)] as f
640
643
  {repo_filter}
641
644
  RETURN DISTINCT f.name AS callee_name, f.path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
642
645
  ORDER BY callee_is_dependency ASC, callee_file_path, callee_line_number
@@ -657,24 +660,12 @@ class CodeFinder:
657
660
  query = f"""
658
661
  MATCH (start:Function {start_props}), (end_target:Function {end_props})
659
662
  {repo_filter}
660
- WITH start, end_target
663
+ WITH start as start, end_target as end_target
661
664
  MATCH path = (start)-[:CALLS*1..{max_depth}]->()
662
- WITH path, end_target, nodes(path) as func_nodes, relationships(path) as call_rels
663
- WITH path, func_nodes, call_rels, list_extract(func_nodes, size(func_nodes)) as path_end
665
+ WITH path as path, end_target as end_target, nodes(path) as func_nodes, relationships(path) as call_rels
666
+ WITH path as path, func_nodes as func_nodes, call_rels as call_rels, end_target as end_target, func_nodes[size(func_nodes)] as path_end
664
667
  WHERE path_end.name = end_target.name AND (end_target.path IS NULL OR path_end.path = end_target.path)
665
- RETURN
666
- [node in func_nodes | {{
667
- name: node.name,
668
- path: node.path,
669
- line_number: node.line_number,
670
- is_dependency: node.is_dependency
671
- }}] as function_chain,
672
- [rel in call_rels | {{
673
- call_line: rel.line_number,
674
- args: rel.args,
675
- full_call_name: rel.full_call_name
676
- }}] as call_details,
677
- length(path) as chain_length
668
+ RETURN func_nodes as function_nodes, call_rels as call_nodes, size(call_rels) as chain_length
678
669
  ORDER BY chain_length ASC
679
670
  LIMIT 20
680
671
  """
@@ -689,7 +680,67 @@ class CodeFinder:
689
680
  }
690
681
 
691
682
  result = session.run(query, **params)
692
- return result.data()
683
+
684
+ # Post-process Node/Rel objects into plain dicts so CLI output stays stable
685
+ rows = result.data()
686
+ transformed: List[Dict[str, Any]] = []
687
+ for row in rows:
688
+ func_nodes = row.get("function_nodes") or []
689
+ rel_nodes = row.get("call_nodes") or []
690
+ chain_len = row.get("chain_length", 0)
691
+
692
+ function_chain = []
693
+ for n in func_nodes:
694
+ # Depending on KùzuDB + driver wrapping, list elements can arrive
695
+ # either as Node/Rel objects or already-materialized dicts.
696
+ if isinstance(n, dict):
697
+ props = n
698
+ else:
699
+ props = None
700
+ try:
701
+ props = n.get_properties()
702
+ except Exception:
703
+ props = getattr(n, "properties", None)
704
+ if props is None:
705
+ props = {}
706
+ function_chain.append(
707
+ {
708
+ "name": props.get("name"),
709
+ "path": props.get("path"),
710
+ "line_number": props.get("line_number"),
711
+ "is_dependency": props.get("is_dependency"),
712
+ }
713
+ )
714
+
715
+ call_details = []
716
+ for r in rel_nodes:
717
+ if isinstance(r, dict):
718
+ props = r
719
+ else:
720
+ props = None
721
+ try:
722
+ props = r.get_properties()
723
+ except Exception:
724
+ props = getattr(r, "properties", None)
725
+ if props is None:
726
+ props = {}
727
+ call_details.append(
728
+ {
729
+ "call_line": props.get("line_number"),
730
+ "args": props.get("args"),
731
+ "full_call_name": props.get("full_call_name"),
732
+ }
733
+ )
734
+
735
+ transformed.append(
736
+ {
737
+ "function_chain": function_chain,
738
+ "call_details": call_details,
739
+ "chain_length": chain_len,
740
+ }
741
+ )
742
+
743
+ return transformed
693
744
 
694
745
  def find_by_type(self, element_type: str, limit: int = 50) -> List[Dict]:
695
746
  """Find all elements of a specific type (Function, Class, File, Module)."""
@@ -998,9 +1049,10 @@ class CodeFinder:
998
1049
  """Find the most complex functions based on cyclomatic complexity."""
999
1050
  with self.driver.session() as session:
1000
1051
  repo_filter = "AND f.path STARTS WITH $repo_path" if repo_path else ""
1052
+ path_ignore = cypher_path_not_under_ignore_dirs("f.path")
1001
1053
  query = f"""
1002
1054
  MATCH (f:Function)
1003
- WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false {repo_filter}
1055
+ WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false {repo_filter} {path_ignore}
1004
1056
  RETURN f.name as function_name, f.path as path, f.cyclomatic_complexity as complexity, f.line_number as line_number
1005
1057
  ORDER BY f.cyclomatic_complexity DESC
1006
1058
  LIMIT $limit
@@ -14,9 +14,22 @@ from ..utils.debug_log import debug_log, info_logger, error_logger, warning_logg
14
14
  from tree_sitter import Language, Parser
15
15
  from ..utils.tree_sitter_manager import get_tree_sitter_manager
16
16
  from ..cli.config_manager import get_config_value
17
+ from ..utils.path_ignore import file_path_has_ignore_dir_segment
17
18
  import fnmatch
18
19
 
19
20
  DEFAULT_IGNORE_PATTERNS = [
21
+ # Vendor / env dirs (gitignore-style; complements IGNORE_DIRS during indexing)
22
+ "node_modules/",
23
+ "venv/",
24
+ ".venv/",
25
+ "env/",
26
+ ".env/",
27
+ "dist/",
28
+ "build/",
29
+ "target/",
30
+ "out/",
31
+ ".git/",
32
+ "__pycache__/",
20
33
  "*.png",
21
34
  "*.jpg",
22
35
  "*.jpeg",
@@ -210,6 +223,12 @@ class GraphBuilder:
210
223
  except Exception as e:
211
224
  warning_logger(f"Schema creation warning: {e}")
212
225
 
226
+ # Neo4j RANGE indexes have an ~8 kB key-size limit. Long C++ template
227
+ # function names (e.g. from llama.cpp) can exceed this, causing
228
+ # "Property value is too large to index" errors. We cap string properties
229
+ # at 4096 chars, which is comfortably under the 8 kB boundary.
230
+ _MAX_STR_LEN = 4096
231
+
213
232
  @staticmethod
214
233
  def _sanitize_props(props: Dict) -> Dict:
215
234
  """Return a copy of *props* with all values coerced to database-safe types.
@@ -220,9 +239,15 @@ class GraphBuilder:
220
239
  parsers (e.g. C's ``detailed_args`` or Scala's tuple ``class_context``)
221
240
  are serialized to a JSON string so the data is preserved rather than
222
241
  being silently dropped.
242
+
243
+ Additionally, string values are truncated to _MAX_STR_LEN characters to
244
+ avoid Neo4j's RANGE-index 8 kB property-size limit (triggered by very
245
+ long C++ template-mangled function names).
223
246
  """
224
247
  import json
225
248
 
249
+ MAX = GraphBuilder._MAX_STR_LEN
250
+
226
251
  def _is_primitive(v):
227
252
  return isinstance(v, (str, int, float, bool)) or v is None
228
253
 
@@ -230,15 +255,21 @@ class GraphBuilder:
230
255
  return isinstance(v, list) and all(_is_primitive(item) for item in v)
231
256
 
232
257
  def _coerce(v):
258
+ if isinstance(v, str):
259
+ # Truncate long strings to stay within Neo4j RANGE index limits
260
+ return v[:MAX] if len(v) > MAX else v
233
261
  if _is_primitive(v):
234
262
  return v
235
263
  if _is_flat_list(v):
236
- return v
264
+ # Truncate any long strings in lists too
265
+ return [s[:MAX] if isinstance(s, str) and len(s) > MAX else s for s in v]
237
266
  # Tuples, dicts, lists-of-dicts, nested structures → JSON string
238
267
  try:
239
- return json.dumps(v, default=str)
268
+ serialized = json.dumps(v, default=str)
269
+ return serialized[:MAX] if len(serialized) > MAX else serialized
240
270
  except Exception:
241
- return str(v)
271
+ s = str(v)
272
+ return s[:MAX] if len(s) > MAX else s
242
273
 
243
274
  return {k: _coerce(v) for k, v in props.items()}
244
275
 
@@ -446,8 +477,20 @@ class GraphBuilder:
446
477
  # before writing to the database to avoid runtime errors such as
447
478
  # "Property values can only be of primitive types or arrays of
448
479
  # primitive types" raised by FalkorDB / KùzuDB.
480
+ # _sanitize_props also truncates long strings to avoid Neo4j's
481
+ # 8 kB RANGE-index limit (seen with long C++ template names).
449
482
  safe_props = self._sanitize_props(item)
450
- session.run(query, path=file_path_str, name=item['name'], line_number=item['line_number'], props=safe_props)
483
+ try:
484
+ session.run(query, path=file_path_str, name=item['name'], line_number=item['line_number'], props=safe_props)
485
+ except Exception as node_err:
486
+ err_str = str(node_err)
487
+ if "too large to index" in err_str or "property size" in err_str.lower():
488
+ warning_logger(
489
+ f"Skipping {label} '{item['name']}' in {file_path_str}: "
490
+ f"property value too large for index (name length={len(item['name'])})"
491
+ )
492
+ else:
493
+ raise # Re-raise unexpected errors
451
494
 
452
495
  if label == 'Function':
453
496
  for arg_name in item.get('args', []):
@@ -517,30 +560,51 @@ class GraphBuilder:
517
560
  # Ensure full_import_name is available in params for SET clause
518
561
  params = imp.copy()
519
562
  params['path'] = file_path_str
520
- params['rel_props'] = rel_props
521
563
  params['module_name'] = imp.get('name') # Use 'name' from imp as module name
522
564
 
565
+ # Sanitize scalar params but keep rel_props as a dict
566
+ # (FalkorDB SET r += $rel_props requires a map, not a string)
567
+ sanitized = self._sanitize_props(params)
568
+ sanitized['rel_props'] = rel_props
569
+
523
570
  session.run(f"""
524
571
  MATCH (f:File {{path: $path}})
525
572
  MERGE (m:Module {{name: $module_name}})
526
573
  {set_clause_str}
527
574
  MERGE (f)-[r:IMPORTS]->(m)
528
575
  SET r += $rel_props
529
- """, **params)
576
+ """, **sanitized)
530
577
 
531
578
 
532
579
  # Handle CONTAINS relationship between class to their children like variables
533
580
  for func in file_data.get('functions', []):
534
581
  if func.get('class_context'):
535
- session.run("""
582
+ # Try same-file match first (Python, JS, etc.)
583
+ if not self._safe_run_create(session, """
536
584
  MATCH (c:Class {name: $class_name, path: $path})
537
585
  MATCH (fn:Function {name: $func_name, path: $path, line_number: $func_line})
538
586
  MERGE (c)-[:CONTAINS]->(fn)
539
- """,
540
- class_name=func['class_context'],
541
- path=file_path_str,
542
- func_name=func['name'],
543
- func_line=func['line_number'])
587
+ RETURN count(*) as created
588
+ """, {
589
+ 'class_name': func['class_context'],
590
+ 'path': file_path_str,
591
+ 'func_name': func['name'],
592
+ 'func_line': func['line_number']
593
+ }):
594
+ # Cross-file match for C/C++ where class is in .h and method in .cpp.
595
+ # Note: matches by class name only (no path constraint), so classes
596
+ # with identical names in different files could get false links.
597
+ self._safe_run_create(session, """
598
+ MATCH (c:Class {name: $class_name})
599
+ MATCH (fn:Function {name: $func_name, path: $path, line_number: $func_line})
600
+ MERGE (c)-[:CONTAINS]->(fn)
601
+ RETURN count(*) as created
602
+ """, {
603
+ 'class_name': func['class_context'],
604
+ 'path': file_path_str,
605
+ 'func_name': func['name'],
606
+ 'func_line': func['line_number']
607
+ })
544
608
 
545
609
  # --- NEW: Class INCLUDES Module (Ruby mixins) ---
546
610
  for inc in file_data.get('module_inclusions', []):
@@ -693,7 +757,7 @@ class GraphBuilder:
693
757
 
694
758
  # KùzuDB workaround: Try Function->Function first, then other combinations
695
759
  # This avoids polymorphic MERGE which KùzuDB doesn't support
696
- call_params = {
760
+ call_params = self._sanitize_props({
697
761
  'caller_name': caller_name,
698
762
  'caller_file_path': caller_file_path,
699
763
  'caller_line_number': caller_line_number,
@@ -702,8 +766,9 @@ class GraphBuilder:
702
766
  'line_number': call['line_number'],
703
767
  'args': call.get('args', []),
704
768
  'full_call_name': call.get('full_name', called_name)
705
- }
706
- # Try Function caller -> Function callee
769
+ })
770
+
771
+ # Try Function caller -> Function callee
707
772
  if not self._safe_run_create(session, """
708
773
  OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
709
774
  OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
@@ -782,14 +847,15 @@ class GraphBuilder:
782
847
  """, call_params)
783
848
  else:
784
849
  # File-level calls: Try Function first, then Class
785
- call_params = {
850
+ call_params = self._sanitize_props({
786
851
  'caller_file_path': caller_file_path,
787
852
  'called_name': called_name,
788
853
  'called_file_path': resolved_path,
789
854
  'line_number': call['line_number'],
790
855
  'args': call.get('args', []),
791
856
  'full_call_name': call.get('full_name', called_name)
792
- }
857
+ })
858
+
793
859
 
794
860
  if not self._safe_run_create(session, """
795
861
  OPTIONAL MATCH (caller:File {path: $caller_file_path})
@@ -1162,13 +1228,16 @@ class GraphBuilder:
1162
1228
 
1163
1229
  # Step 4: Write nodes to graph using existing add_file_to_graph()
1164
1230
  processed = 0
1231
+ index_root = path.resolve()
1165
1232
  for abs_path_str, file_data in files_data.items():
1166
- file_data["repo_path"] = str(path.resolve())
1233
+ file_path = Path(abs_path_str)
1234
+ if file_path.is_file() and file_path_has_ignore_dir_segment(file_path, index_root):
1235
+ continue
1236
+ file_data["repo_path"] = str(index_root)
1167
1237
  if job_id:
1168
1238
  self.job_manager.update_job(job_id, current_file=abs_path_str)
1169
1239
 
1170
1240
  # Step 5: Tree-sitter supplement — add source text, complexity, imports and bases
1171
- file_path = Path(abs_path_str)
1172
1241
  ts_parser = self.get_parser(file_path.suffix)
1173
1242
  if file_path.exists() and ts_parser:
1174
1243
  try:
@@ -11,6 +11,7 @@ CPP_QUERIES = {
11
11
  declarator: [
12
12
  (identifier) @name
13
13
  (field_identifier) @name
14
+ (qualified_identifier) @qualified_name
14
15
  ]
15
16
  )
16
17
  ) @function_node
@@ -35,6 +36,7 @@ CPP_QUERIES = {
35
36
  (field_expression
36
37
  field: (field_identifier) @method_name
37
38
  )
39
+ (qualified_identifier) @scoped_name
38
40
  ]
39
41
  arguments: (argument_list) @args
40
42
  )
@@ -158,36 +160,40 @@ class CppTreeSitterParser:
158
160
  for match in execute_query(self.language, query_str, root_node):
159
161
  capture_name = match[1]
160
162
  node = match[0]
161
- if capture_name == 'name':
162
- # node is identifier
163
- # node.parent is function_declarator
164
- # node.parent.parent is function_definition
165
- func_node = node.parent.parent
166
-
167
- # Double check to prevent crashes if AST is different (e.g. pointers)
168
- if func_node.type != 'function_definition':
169
- # Fallback or try finding function_definition upwards
170
- curr = node
171
- while curr and curr.type != 'function_definition':
172
- curr = curr.parent
173
- func_node = curr
174
-
163
+ if capture_name in ('name', 'qualified_name'):
164
+ # Find the enclosing function_definition node
165
+ func_node = node.parent
166
+ while func_node and func_node.type != 'function_definition':
167
+ func_node = func_node.parent
168
+
175
169
  if not func_node: continue
176
170
 
177
- name = self._get_node_text(node)
178
-
171
+ # For qualified names like QueueElement::execute, extract
172
+ # both the class context and the method name
173
+ raw_text = self._get_node_text(node)
174
+ class_context = None
175
+ if capture_name == 'qualified_name' and '::' in raw_text:
176
+ parts = raw_text.rsplit('::', 1)
177
+ class_context = parts[0]
178
+ name = parts[1]
179
+ else:
180
+ name = raw_text
181
+
179
182
  params = self._extract_function_params(func_node)
180
-
183
+
181
184
  func_data = {
182
185
  "name": name,
183
186
  "line_number": node.start_point[0] + 1,
184
187
  "end_line": func_node.end_point[0] + 1,
185
188
  "args": params,
186
189
  }
187
-
190
+
191
+ if class_context:
192
+ func_data["class_context"] = class_context
193
+
188
194
  if self.index_source:
189
195
  func_data["source"] = self._get_node_text(func_node)
190
-
196
+
191
197
  functions.append(func_data)
192
198
  return functions
193
199
 
@@ -238,17 +244,52 @@ class CppTreeSitterParser:
238
244
  if capture_name == 'name':
239
245
  class_node = node.parent
240
246
  name = self._get_node_text(node)
247
+ bases = self._extract_base_classes(class_node)
241
248
  class_data = {
242
249
  "name": name,
243
250
  "line_number": node.start_point[0] + 1,
244
251
  "end_line": class_node.end_point[0] + 1,
245
- "bases": [], # Placeholder
252
+ "bases": bases,
246
253
  }
247
254
  if self.index_source:
248
255
  class_data["source"] = self._get_node_text(class_node)
249
256
  classes.append(class_data)
250
257
  return classes
251
258
 
259
+ def _extract_base_classes(self, class_node) -> list[str]:
260
+ """Extract base class names from a class_specifier node.
261
+
262
+ Handles: class Foo : public Bar, private Baz, virtual Base
263
+ Tree-sitter AST: class_specifier -> base_class_clause -> (base_class_specifier)+
264
+ Each base_class_specifier has an optional access_specifier and a type node.
265
+ """
266
+ bases = []
267
+ for child in class_node.children:
268
+ if child.type == 'base_class_clause':
269
+ for base_spec in child.children:
270
+ if base_spec.type in ('base_class_specifier', 'type_identifier',
271
+ 'qualified_identifier', 'template_type'):
272
+ # base_class_specifier contains access specifier + type
273
+ if base_spec.type == 'base_class_specifier':
274
+ # Find the type identifier within the base specifier
275
+ for sub in base_spec.children:
276
+ if sub.type in ('type_identifier', 'qualified_identifier',
277
+ 'template_type'):
278
+ base_name = self._get_node_text(sub)
279
+ # Strip template args for graph matching
280
+ if '<' in base_name:
281
+ base_name = base_name[:base_name.index('<')].strip()
282
+ bases.append(base_name)
283
+ break
284
+ else:
285
+ # Direct type node (no access specifier)
286
+ base_name = self._get_node_text(base_spec)
287
+ if '<' in base_name:
288
+ base_name = base_name[:base_name.index('<')].strip()
289
+ bases.append(base_name)
290
+ break # Only one base_class_clause per class
291
+ return bases
292
+
252
293
  def _find_imports(self, root_node):
253
294
  imports = []
254
295
  query_str = CPP_QUERIES['imports']
@@ -270,6 +311,7 @@ class CppTreeSitterParser:
270
311
  query_str = CPP_QUERIES['enums']
271
312
  for node, capture_name in execute_query(self.language, query_str, root_node):
272
313
  if capture_name == 'name':
314
+ name = self._get_node_text(node)
273
315
  enum_node = node.parent
274
316
  enum_data = {
275
317
  "name": name,
@@ -346,17 +388,17 @@ class CppTreeSitterParser:
346
388
  if capture_name == 'name':
347
389
  assignment_node = node.parent
348
390
  lambda_node = assignment_node.child_by_field_name('value')
349
- if lambda_node is None or lambda_node.type != 'lambda_expression':
350
- continue
391
+ if lambda_node is None or lambda_node.type != 'lambda_expression':
392
+ continue
351
393
 
352
- params_node = lambda_node.child_by_field_name('declarator')
353
- if params_node:
354
- params_node = params_node.child_by_field_name('parameters')
394
+ params_node = lambda_node.child_by_field_name('declarator')
395
+ if params_node:
396
+ params_node = params_node.child_by_field_name('parameters')
355
397
  name = self._get_node_text(node)
356
398
  params_node = lambda_node.child_by_field_name('parameters')
357
-
399
+
358
400
  context, context_type, _ = self._get_parent_context(assignment_node)
359
- class_context, _, _ = self._get_parent_context(assignment_node, types=('class_definition',))
401
+ class_context, _, _ = self._get_parent_context(assignment_node, types=('class_specifier',))
360
402
 
361
403
  func_data = {
362
404
  "name": name,
@@ -402,7 +444,7 @@ class CppTreeSitterParser:
402
444
  type_text = self._get_node_text(type_node) if type_node else None
403
445
 
404
446
  context, _, _ = self._get_parent_context(node)
405
- class_context, _, _ = self._get_parent_context(node, types=('class_definition',))
447
+ class_context, _, _ = self._get_parent_context(node, types=('class_specifier',))
406
448
 
407
449
  variable_data = {
408
450
  "name": name,
@@ -417,17 +459,24 @@ class CppTreeSitterParser:
417
459
  variables.append(variable_data)
418
460
  return variables
419
461
 
420
- def _get_parent_context(self, node, types=('function_definition', 'class_definition')):
462
+ def _get_parent_context(self, node, types=('function_definition', 'class_specifier')):
421
463
  curr = node.parent
422
464
  while curr:
423
465
  if curr.type in types:
424
466
  if curr.type == 'function_definition':
425
- # Traverse declarator to find name
467
+ # Traverse declarator to find name, handling qualified names
426
468
  decl = curr.child_by_field_name('declarator')
427
469
  while decl:
428
470
  if decl.type == 'identifier':
429
471
  return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
430
-
472
+ if decl.type == 'qualified_identifier':
473
+ # e.g. QueueElement::execute — return just the method name
474
+ text = self._get_node_text(decl)
475
+ name = text.rsplit('::', 1)[-1] if '::' in text else text
476
+ return name, curr.type, decl.start_point[0] + 1
477
+ if decl.type == 'field_identifier':
478
+ return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
479
+
431
480
  child = decl.child_by_field_name('declarator')
432
481
  if child:
433
482
  decl = child
@@ -435,6 +484,9 @@ class CppTreeSitterParser:
435
484
  break
436
485
  # Fallback or if not found
437
486
  return None, curr.type, curr.start_point[0] + 1
487
+ elif curr.type == 'class_specifier':
488
+ name_node = curr.child_by_field_name('name')
489
+ return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
438
490
  else:
439
491
  name_node = curr.child_by_field_name('name')
440
492
  return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
@@ -445,49 +497,43 @@ class CppTreeSitterParser:
445
497
  calls = []
446
498
  query_str = CPP_QUERIES['calls']
447
499
  for node, capture_name in execute_query(self.language, query_str, root_node):
448
- if capture_name == "function_name":
449
- func_name = self._get_node_text(node)
450
- func_node = node.parent.parent # function_declarator -> function_definition
451
- full_name = self._get_full_name(func_node) or func_name
452
-
453
- # Find return type node (captured separately)
454
- return_type_node = None
455
- for n, cap in execute_query(self.language, query_str, func_node):
456
- if cap == "return_type":
457
- return_type_node = n
458
- break
459
- return_type = self._get_node_text(return_type_node) if return_type_node else None
460
-
461
- # Extract parameters
462
- args = []
463
- parameters_node = func_node.child_by_field_name("declarator")
464
- if parameters_node:
465
- param_list_node = parameters_node.child_by_field_name("parameters")
466
- if param_list_node:
467
- for param in param_list_node.children:
468
- if param.type == "parameter_declaration":
469
- type_node = param.child_by_field_name("type")
470
- name_node = param.child_by_field_name("declarator")
471
-
472
- param_type = self._get_node_text(type_node) if type_node else None
473
- param_name = self._get_node_text(name_node) if name_node else None
474
-
475
- args.append({
476
- "type": param_type,
477
- "name": param_name
478
- })
479
-
500
+ if capture_name in ("function_name", "method_name", "scoped_name"):
501
+ raw_text = self._get_node_text(node)
502
+
503
+ # Determine the called function name and any object/class qualifier
504
+ inferred_obj_type = None
505
+ if capture_name == "scoped_name" and '::' in raw_text:
506
+ # e.g. QueueElement::setStatus or std::move
507
+ parts = raw_text.rsplit('::', 1)
508
+ func_name = parts[1]
509
+ inferred_obj_type = parts[0]
510
+ elif capture_name == "method_name":
511
+ # e.g. obj.method() or ptr->method() field_expression
512
+ func_name = raw_text
513
+ # Try to get the object expression for type inference
514
+ field_expr = node.parent # field_expression node
515
+ if field_expr and field_expr.type == 'field_expression':
516
+ obj_node = field_expr.child_by_field_name('argument')
517
+ if obj_node:
518
+ obj_text = self._get_node_text(obj_node)
519
+ # Treat this->method like self.method for resolution
520
+ if obj_text == 'this':
521
+ inferred_obj_type = 'this'
522
+ else:
523
+ inferred_obj_type = obj_text
524
+ else:
525
+ func_name = raw_text
480
526
 
481
- # Get context info (function may be inside class)
527
+ # Get context info (which function/class contains this call)
482
528
  context_name, context_type, context_line = self._get_parent_context(node)
483
- class_context, _, _ = self._get_parent_context(node, types=("class_definition",))
529
+ class_context, _, _ = self._get_parent_context(node, types=("class_specifier",))
484
530
 
485
531
  call_data = {
486
532
  "name": func_name,
487
- "full_name": full_name,
533
+ "full_name": raw_text,
488
534
  "line_number": node.start_point[0] + 1,
489
- "args": args,
490
- "inferred_obj_type": None,
535
+ "args": [],
536
+ "inferred_obj_type": inferred_obj_type,
491
537
  "context": (context_name, context_type, context_line),
492
538
  "class_context": class_context,
493
539
  "lang": self.language_name,
@@ -506,8 +552,12 @@ class CppTreeSitterParser:
506
552
  while curr:
507
553
  if curr.type in ("function_definition", "function_declarator"):
508
554
  id_node = curr.child_by_field_name("declarator")
509
- if id_node and id_node.type == "identifier":
510
- name_parts.insert(0, id_node.text.decode("utf8"))
555
+ if id_node:
556
+ if id_node.type == "identifier":
557
+ name_parts.insert(0, id_node.text.decode("utf8"))
558
+ elif id_node.type == "qualified_identifier":
559
+ # Already contains Class::method — use it directly
560
+ return id_node.text.decode("utf8")
511
561
  elif curr.type == "class_specifier":
512
562
  name_node = curr.child_by_field_name("name")
513
563
  if name_node:
@@ -532,8 +582,9 @@ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
532
582
  (class_specifier name: (type_identifier) @name)
533
583
  (struct_specifier name: (type_identifier) @name)
534
584
  (function_definition declarator: (function_declarator declarator: (identifier) @name))
585
+ (function_definition declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))
535
586
  """
536
-
587
+
537
588
 
538
589
  for path in files:
539
590
  try:
@@ -542,9 +593,23 @@ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
542
593
  tree = parser_wrapper.parser.parse(source_bytes)
543
594
 
544
595
  for node, capture_name in execute_query(parser_wrapper.language, query_str, tree.root_node):
596
+ resolved_path = str(path.resolve())
545
597
  if capture_name == "name":
546
598
  name = node.text.decode("utf-8")
547
- imports_map.setdefault(name, []).append(str(path.resolve()))
599
+ paths = imports_map.setdefault(name, [])
600
+ if resolved_path not in paths:
601
+ paths.append(resolved_path)
602
+ elif capture_name == "qualified_name":
603
+ # e.g. QueueElement::execute — index both the full name and the method name
604
+ full_name = node.text.decode("utf-8")
605
+ paths = imports_map.setdefault(full_name, [])
606
+ if resolved_path not in paths:
607
+ paths.append(resolved_path)
608
+ if '::' in full_name:
609
+ method_name = full_name.rsplit('::', 1)[1]
610
+ paths = imports_map.setdefault(method_name, [])
611
+ if resolved_path not in paths:
612
+ paths.append(resolved_path)
548
613
  except Exception as e:
549
614
  warning_logger(f"Tree-sitter pre-scan failed for {path}: {e}")
550
615
 
@@ -79,6 +79,7 @@ class GoTreeSitterParser:
79
79
  self.language_name = generic_parser_wrapper.language_name
80
80
  self.language = generic_parser_wrapper.language
81
81
  self.parser = generic_parser_wrapper.parser
82
+ self.index_source = False
82
83
 
83
84
  def _get_node_text(self, node) -> str:
84
85
  return node.text.decode('utf-8')
@@ -99,17 +100,52 @@ class GoTreeSitterParser:
99
100
  return None, None, None
100
101
 
101
102
  def _calculate_complexity(self, node):
102
- complexity_nodes = {
103
- "if_statement", "for_statement", "switch_statement", "case_clause",
104
- "expression_switch_statement", "type_switch_statement",
105
- "binary_expression", "call_expression"
103
+ """
104
+ Compute a simple cyclomatic complexity score from the Go AST.
105
+
106
+ We treat each decision/control-flow construct as +1:
107
+ - if/for/switch/select
108
+ - switch cases (case_clause) and select clauses (comm_clause)
109
+ - logical operators (&&, ||) inside binary_expression as +1
110
+ """
111
+ # Note: tree-sitter-go node types differ from other languages and from
112
+ # what we'd typically expect (e.g. it's `switch`/`case` rather than
113
+ # `switch_statement`/`case_clause`).
114
+ decision_node_types = {
115
+ # top-level control constructs
116
+ "if_statement",
117
+ "for_statement",
118
+ "switch",
119
+ "select_statement",
120
+ "select",
121
+ # switch variants (tree-sitter grammar)
122
+ "expression_switch_statement",
123
+ "type_switch_statement",
124
+ # switch case branches
125
+ "case",
126
+ "expression_case",
127
+ "default_case",
128
+ # select communication branches
129
+ "communication_case",
106
130
  }
131
+
107
132
  count = 1
108
133
 
109
134
  def traverse(n):
110
135
  nonlocal count
111
- if n.type in complexity_nodes:
136
+ if n.type in decision_node_types:
112
137
  count += 1
138
+ # Still traverse children because nested constructs also contribute.
139
+ elif n.type == "binary_expression":
140
+ # Only count logical operators, not all binary expressions/comparisons.
141
+ # Example patterns: "a && b", "a || b".
142
+ try:
143
+ txt = self._get_node_text(n)
144
+ except Exception:
145
+ txt = ""
146
+ if "&&" in txt or "||" in txt:
147
+ count += 1
148
+
113
149
  for child in n.children:
114
150
  traverse(child)
115
151
 
@@ -240,6 +276,7 @@ class GoTreeSitterParser:
240
276
  "decorators": [],
241
277
  "lang": self.language_name,
242
278
  "is_dependency": False,
279
+ "cyclomatic_complexity": self._calculate_complexity(func_node),
243
280
  }
244
281
 
245
282
  if self.index_source:
@@ -0,0 +1,55 @@
1
+ """
2
+ Path segment helpers aligned with IGNORE_DIRS indexing rules.
3
+
4
+ `is_dependency` on graph nodes marks separately indexed packages, not vendored
5
+ dirs inside a repo (e.g. node_modules). Analysis queries must also exclude
6
+ those paths when IGNORE_DIRS would skip them during indexing.
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import List, Optional
11
+
12
+ from ..cli.config_manager import DEFAULT_CONFIG, get_config_value
13
+
14
+
15
+ def parse_ignore_dir_names() -> List[str]:
16
+ """Directory names from IGNORE_DIRS, falling back to defaults when unset or empty."""
17
+ raw = (get_config_value("IGNORE_DIRS") or "").strip()
18
+ if not raw:
19
+ raw = DEFAULT_CONFIG["IGNORE_DIRS"]
20
+ return [d.strip() for d in raw.split(",") if d.strip()]
21
+
22
+
23
+ def cypher_path_not_under_ignore_dirs(path_var: str, ignore_dir_names: Optional[List[str]] = None) -> str:
24
+ """
25
+ Returns a Cypher fragment that keeps only paths not under any IGNORE_DIRS segment
26
+ (Unix `/name/` or Windows `\\name\\`).
27
+ """
28
+ names = ignore_dir_names if ignore_dir_names is not None else parse_ignore_dir_names()
29
+ if not names:
30
+ return ""
31
+ parts: List[str] = []
32
+ for d in names:
33
+ esc = d.replace("\\", "\\\\").replace("'", "\\'")
34
+ parts.append(f"{path_var} CONTAINS '/{esc}/'")
35
+ parts.append(f"{path_var} CONTAINS '\\\\{esc}\\\\'")
36
+ return " AND NOT (" + " OR ".join(parts) + ")"
37
+
38
+
39
+ def file_path_has_ignore_dir_segment(file_path: Path, index_root: Path) -> bool:
40
+ """
41
+ True if any directory segment of file_path relative to index_root matches
42
+ IGNORE_DIRS (same logic as Tree-sitter indexing).
43
+ """
44
+ ignore_dirs_str = (get_config_value("IGNORE_DIRS") or "").strip()
45
+ if not ignore_dirs_str:
46
+ ignore_dirs_str = DEFAULT_CONFIG["IGNORE_DIRS"]
47
+ ignore_dirs = {d.strip().lower() for d in ignore_dirs_str.split(",") if d.strip()}
48
+ if not ignore_dirs:
49
+ return False
50
+ try:
51
+ rel = file_path.resolve().relative_to(index_root.resolve())
52
+ parts = {p.lower() for p in rel.parent.parts}
53
+ return bool(parts.intersection(ignore_dirs))
54
+ except ValueError:
55
+ return False
@@ -60,6 +60,11 @@ LANGUAGE_ALIASES = {
60
60
  "exs": "elixir",
61
61
  }
62
62
 
63
+ # Canonical names that differ from tree-sitter-language-pack names
64
+ LANGUAGE_PACK_NAMES = {
65
+ "c_sharp": "csharp",
66
+ }
67
+
63
68
 
64
69
  class TreeSitterManager:
65
70
  """
@@ -128,15 +133,9 @@ class TreeSitterManager:
128
133
  return self._language_cache[canonical_name]
129
134
 
130
135
  try:
131
- # Special handling for C# which is available as tree_sitter_c_sharp
132
- if canonical_name == "c_sharp":
133
- import tree_sitter_c_sharp
134
- # tree_sitter_c_sharp.language() returns a PyCapsule, wrap it in Language
135
- capsule = tree_sitter_c_sharp.language()
136
- language = Language(capsule)
137
- else:
138
- # Load the language from tree-sitter-language-pack
139
- language = get_language(canonical_name)
136
+ # Map canonical name to language-pack name where they differ
137
+ pack_name = LANGUAGE_PACK_NAMES.get(canonical_name, canonical_name)
138
+ language = get_language(pack_name)
140
139
 
141
140
  self._language_cache[canonical_name] = language
142
141
  return language
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.3.7
3
+ Version: 0.3.8
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
@@ -51,6 +51,7 @@ Requires-Dist: nbformat
51
51
  Requires-Dist: nbconvert>=7.16.6
52
52
  Requires-Dist: pathspec>=0.12.1
53
53
  Requires-Dist: falkordb>=0.1.0
54
+ Requires-Dist: requests>=2.28.0
54
55
  Requires-Dist: falkordblite>=0.1.0; sys_platform != "win32" and python_version >= "3.12"
55
56
  Requires-Dist: fastapi>=0.100.0
56
57
  Requires-Dist: uvicorn>=0.22.0
@@ -162,7 +163,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
162
163
  ---
163
164
 
164
165
  ## Project Details
165
- - **Version:** 0.3.7
166
+ - **Version:** 0.3.8
166
167
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
167
168
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
168
169
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -271,7 +272,7 @@ pip install codegraphcontext
271
272
 
272
273
  ### If 'cgc' command isn't found, run our one-line fix:
273
274
  ```
274
- curl -sSL [https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh](https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh) | bash
275
+ curl -sSL https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh | bash
275
276
  ```
276
277
 
277
278
  ---
@@ -1,10 +1,10 @@
1
1
  codegraphcontext/__init__.py,sha256=mFY5raGZpG90gG6E8NH0R85mDl3Ikak2HQHOiwCXl-I,77
2
2
  codegraphcontext/__main__.py,sha256=21QjL_lfd6cy-clJYPBg6xKb_IuNDcvvWdaAJEvZ-HQ,99
3
3
  codegraphcontext/prompts.py,sha256=E5P55paM0oHfBcNVfxkxpXRGZnya1kr_mKDg9i49FwM,6755
4
- codegraphcontext/server.py,sha256=gcb6V4x0Oh8haBOCm2WBjTCO9FDukrSalAMMApL-oc8,12806
4
+ codegraphcontext/server.py,sha256=Rz6Exa99INSaHKdeIBDQwwMoDOFJY8vhuXwEAdqs6qg,13917
5
5
  codegraphcontext/tool_definitions.py,sha256=_0ahezQSURX_ewWydT2sFcvC6OFGdMoPA7KwgWNVcks,11482
6
6
  codegraphcontext/cli/__init__.py,sha256=v6CMDVKM5d_sXn3S5nZNf0phXn0IdrnhLazUoen9k9w,38
7
- codegraphcontext/cli/cli_helpers.py,sha256=QcpEV9VRFuPiiIEUwiXCr_nm7Z7amzphXv9zQY0aWIU,29646
7
+ codegraphcontext/cli/cli_helpers.py,sha256=sWm9TLWy8GahX3htlonz5wO54DUV_OqkkvT9ohNVR1M,29777
8
8
  codegraphcontext/cli/config_manager.py,sha256=MK7GMGZ4hd8-ZOShXBd_KDtNqHQ3ngdbef5KM_naPrQ,15899
9
9
  codegraphcontext/cli/main.py,sha256=89jWaO8Gr3nRdLQtfwF434wpU77d5bJwt016f_lLnGg,86486
10
10
  codegraphcontext/cli/registry_commands.py,sha256=30rJm4SeS0n1jax4JVuhuv4zYLzyMmlHcCSnsDDhgc8,19964
@@ -17,14 +17,14 @@ codegraphcontext/core/cgc_bundle.py,sha256=lxaImBnEJMiscrGssLTgLaeEa7tY33xztjbxO
17
17
  codegraphcontext/core/database.py,sha256=2UL8AyE6vX5mVHg1zcD74TYEREmddhKzN7Bjy2THQwM,11435
18
18
  codegraphcontext/core/database_falkordb.py,sha256=IKRcUwqdCR92ymX9rNAQSs49RWPxug_78utwePEcliQ,18842
19
19
  codegraphcontext/core/database_falkordb_remote.py,sha256=aMiuJTB_-2rBUpbAmX8ElZiZrYZbOXK-On3ncgMCyhY,7118
20
- codegraphcontext/core/database_kuzu.py,sha256=YklmEaUVWJkO22FXnlZNbuPT7A0bSc75Va80g8Pmv9M,22799
20
+ codegraphcontext/core/database_kuzu.py,sha256=3SkKI-BtsMfSIbt-ZIavB113xtht1LszPGMdZk4Zpbs,23578
21
21
  codegraphcontext/core/falkor_worker.py,sha256=bMcTOac6e8IzSPjCd-YulGVh-S7QsNtXs2c8hx6M6gk,4958
22
22
  codegraphcontext/core/jobs.py,sha256=d6v_IERdEcDlIsz3CW3p0QMp3N-6dgQICs3FZRPgPgU,4809
23
23
  codegraphcontext/core/watcher.py,sha256=42AWDv7LUE5v_HlY_Rvvlk8deDdlUBvM3hI-DMDWAOk,9813
24
24
  codegraphcontext/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  codegraphcontext/tools/advanced_language_query_tool.py,sha256=APtC-KSkXXSrVLwiw3kgi6Q-xaqXl6skLWHPyuNh358,3670
26
- codegraphcontext/tools/code_finder.py,sha256=KNIpAejABX31eEIomTxv8OsInPTXq-xn7hGtmPHwoXY,55860
27
- codegraphcontext/tools/graph_builder.py,sha256=FaEeXQP5gdDclrXKjhWiwbmWXBEPadmqdeHZ2bWLK5A,81339
26
+ codegraphcontext/tools/code_finder.py,sha256=RwhS2uelcRmkL8TsQze0D2aXRUWEmKbBTVzhyXkq5ds,58271
27
+ codegraphcontext/tools/graph_builder.py,sha256=5cudzFiymG1J1SQ-JWnl-AdeMI3CvaHnl9ZaNGdtuQ4,84927
28
28
  codegraphcontext/tools/package_resolver.py,sha256=KtbdMReTezszjdsqYniL-Xb-QUsrAJWtf1NSiyIPkLI,18704
29
29
  codegraphcontext/tools/scip_indexer.py,sha256=gxe5-f090wonHEWYDT1CV6b3SpSTQtwng6A1SfPX_LA,19970
30
30
  codegraphcontext/tools/scip_pb2.py,sha256=dwOMNKlu6VyLq5h8kTPZRDqxrwfVL8yw7I7ziokFG48,98229
@@ -35,11 +35,11 @@ codegraphcontext/tools/handlers/management_handlers.py,sha256=jCosISjq4QD7oBa8Za
35
35
  codegraphcontext/tools/handlers/query_handlers.py,sha256=Go_qhx2XXcymlEiU8JC16PHTyrLQo59drauAPytUQZA,3508
36
36
  codegraphcontext/tools/handlers/watcher_handlers.py,sha256=9hBkMdI8mwQyXj77WvgyUWCtNeItV3DiCLnmNI7Xjp8,3623
37
37
  codegraphcontext/tools/languages/c.py,sha256=mrXQWEvMtzLVElATYNZ7VThNk1ZNZdw2UV8i6r2y2y4,22291
38
- codegraphcontext/tools/languages/cpp.py,sha256=JgQnQDtEvNFz7ZZD35buQG-Kwyqto54resVICCy2N6U,21745
38
+ codegraphcontext/tools/languages/cpp.py,sha256=aXBF_MX43PpRTr5U7UBcn5mZvP6fjP4Q8g21JuCho38,25761
39
39
  codegraphcontext/tools/languages/csharp.py,sha256=0ZIpV531i8yhz4vktaKkzbHDg-TCxgSZPC69SBkmfYU,22594
40
40
  codegraphcontext/tools/languages/dart.py,sha256=RXs4OhyGuFM7l4WH42dSfmgt1HTqwfobx0hFqnQ2rg0,15030
41
41
  codegraphcontext/tools/languages/elixir.py,sha256=mFsMvi_5ZlCsrB5QQ7Kklj94tLo3D6aFU7ukuhGmGSI,18815
42
- codegraphcontext/tools/languages/go.py,sha256=E8wdJDfaojdRw2nN47ZVg3ByViX0nJtJake9l8r40hM,19016
42
+ codegraphcontext/tools/languages/go.py,sha256=cBfeM3Qt9W73KKb39VPhS6LlcWLdVJpvrAnnUeSiH4E,20456
43
43
  codegraphcontext/tools/languages/haskell.py,sha256=ewXkp4HipC2yMkfhUwobjk5RXTlSnXd4tqzoja4W6ck,24008
44
44
  codegraphcontext/tools/languages/java.py,sha256=5lu8qY8zG2Xl2sFTXLx-pZLU3a1CDbvMnwxcwTuyQNs,20707
45
45
  codegraphcontext/tools/languages/javascript.py,sha256=W7PaY2r7KKDQ2Mw1cp20b9ZI_Y5JQB5FYL6xeYDxl1I,24291
@@ -69,12 +69,13 @@ codegraphcontext/tools/query_tool_languages/scala_toolkit.py,sha256=dbSMAUNZOBS7
69
69
  codegraphcontext/tools/query_tool_languages/swift_toolkit.py,sha256=qg5VHM4-xkAiSOt7cbcYymLg9G-uAKlzgKnIrRSEgJU,207
70
70
  codegraphcontext/tools/query_tool_languages/typescript_toolkit.py,sha256=3S4hpmOpEyaHoTP5ZdElFNkVyMZZDL_Gl4-DF0We1Mg,211
71
71
  codegraphcontext/utils/debug_log.py,sha256=Qg7jwyeg7x2h3Ur_2S34bdMCkHdlk_ngHfPwa97A9vE,2836
72
- codegraphcontext/utils/tree_sitter_manager.py,sha256=bIuKYN1aj1Zi6BnksGjZSLZzgBwuFRselZzyUpbToAU,9180
72
+ codegraphcontext/utils/path_ignore.py,sha256=WPIcDmyAi6eL9692rPBQrUZunn5gHRb57nzNRHSIq28,2124
73
+ codegraphcontext/utils/tree_sitter_manager.py,sha256=qYFAfDcMRtJ712bf8IMEZlBKsnY3-mbreCqJEOnmiuM,8975
73
74
  codegraphcontext/utils/visualize_graph.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
75
  codegraphcontext/viz/server.py,sha256=Yu4fwMC2FsE6KUt7oYNlcE3GpCrblstkwUIVaGXTPYI,12767
75
- codegraphcontext-0.3.7.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
76
- codegraphcontext-0.3.7.dist-info/METADATA,sha256=PqemrIQ3-r8slvnaUscWeHgFLAJWbSVz0ToCPl75ZjQ,21685
77
- codegraphcontext-0.3.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
78
- codegraphcontext-0.3.7.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
79
- codegraphcontext-0.3.7.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
80
- codegraphcontext-0.3.7.dist-info/RECORD,,
76
+ codegraphcontext-0.3.8.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
77
+ codegraphcontext-0.3.8.dist-info/METADATA,sha256=gH5p3c4aTnpthV2hOeqrK2013T5ASgTH5eJ_WvB2v2g,21613
78
+ codegraphcontext-0.3.8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
79
+ codegraphcontext-0.3.8.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
80
+ codegraphcontext-0.3.8.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
81
+ codegraphcontext-0.3.8.dist-info/RECORD,,