codegraphcontext 0.3.3__py3-none-any.whl → 0.3.5__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.
@@ -359,16 +359,40 @@ def visualize_helper(repo_path: Optional[str] = None, port: int = 8000):
359
359
 
360
360
  # Determine the static directory (built React app)
361
361
  # This points to src/codegraphcontext/viz/dist where we build the website
362
- static_dir = Path(__file__).parent.parent / "viz" / "dist"
362
+ # (relative to src/codegraphcontext/cli/cli_helpers.py)
363
+ # Using .resolve() is more robust for path comparison and existence checks
364
+ this_file = Path(__file__).resolve()
365
+ package_root = this_file.parent.parent
366
+ static_dir = package_root / "viz" / "dist"
363
367
 
364
368
  # Fallback for development if not yet built in viz/dist
365
369
  if not static_dir.exists():
366
- dev_static_dir = Path.cwd() / "website" / "dist"
370
+ # Look for website/dist in the project root (3 levels up from cli/cli_helpers.py, 4 parents)
371
+ # 1: cli/, 2: codegraphcontext/, 3: src/, 4: project_root/
372
+ project_root = this_file.parent.parent.parent.parent
373
+ dev_static_dir = project_root / "website" / "dist"
374
+
375
+ # Also try one level up from package_root just in case of different layouts
376
+ alt_dev_dir = package_root.parent.parent / "website" / "dist"
377
+
367
378
  if dev_static_dir.exists():
368
379
  static_dir = dev_static_dir
380
+ elif alt_dev_dir.exists():
381
+ static_dir = alt_dev_dir
369
382
  else:
370
- console.print("[yellow]Warning: Visualization assets not found. Please run 'cd website && npm run build' first.[/yellow]")
371
- # We continue anyway to let the server start (helpful for dev)
383
+ # Last resort: try current working directory
384
+ cwd_static_dir = Path.cwd() / "website" / "dist"
385
+ if cwd_static_dir.exists():
386
+ static_dir = cwd_static_dir
387
+ else:
388
+ console.print(f"[yellow]Warning: Visualization assets not found.[/yellow]")
389
+ console.print(f"[dim]Checked paths:[/dim]")
390
+ console.print(f" [dim]- {static_dir}[/dim]")
391
+ console.print(f" [dim]- {dev_static_dir}[/dim]")
392
+ console.print(f" [dim]- {alt_dev_dir}[/dim]")
393
+ console.print(f" [dim]- {cwd_static_dir}[/dim]")
394
+ console.print("[dim]Please run 'cd website && npm run build' first.[/dim]")
395
+ # We continue anyway to let the server start (helpful for dev)
372
396
 
373
397
  # Construct the URL
374
398
  backend_url = f"http://localhost:{port}"
@@ -703,8 +703,7 @@ class GraphBuilder:
703
703
  'args': call.get('args', []),
704
704
  'full_call_name': call.get('full_name', called_name)
705
705
  }
706
-
707
- # Try Function caller -> Function callee
706
+ # Try Function caller -> Function callee
708
707
  if not self._safe_run_create(session, """
709
708
  OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
710
709
  OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
@@ -713,51 +712,73 @@ class GraphBuilder:
713
712
  MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
714
713
  RETURN count(*) as created
715
714
  """, call_params):
716
-
717
- # Try Function caller -> Class callee (with __init__ resolution)
715
+
716
+ # Try Function caller -> Class.__init__ / constructor
718
717
  if not self._safe_run_create(session, """
719
718
  OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
720
719
  OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
721
720
  OPTIONAL MATCH (called)-[:CONTAINS]->(init:Function)
722
721
  WHERE init.name IN ["__init__", "constructor"]
723
- WITH caller, COALESCE(init, called) as final_target
724
- WHERE caller IS NOT NULL AND final_target IS NOT NULL
725
- MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(final_target)
722
+ WITH caller, init
723
+ WHERE caller IS NOT NULL AND init IS NOT NULL
724
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(init)
726
725
  RETURN count(*) as created
727
726
  """, call_params):
728
-
729
- # Try Class caller -> Function callee
727
+ # No __init__ found - link directly to the Class node
728
+ self._safe_run_create(session, """
729
+ OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
730
+ OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
731
+ WITH caller, called
732
+ WHERE caller IS NOT NULL AND called IS NOT NULL
733
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
734
+ RETURN count(*) as created
735
+ """, call_params)
736
+
737
+ # Try Class caller -> Function callee
738
+ if not self._safe_run_create(session, """
739
+ OPTIONAL MATCH (caller:Class {name: $caller_name, path: $caller_file_path})
740
+ OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
741
+ WITH caller, called
742
+ WHERE caller IS NOT NULL AND called IS NOT NULL
743
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
744
+ RETURN count(*) as created
745
+ """, call_params):
746
+
747
+ # Try Class caller -> Class.__init__ / constructor
748
+ if not self._safe_run_create(session, """
749
+ OPTIONAL MATCH (caller:Class {name: $caller_name, path: $caller_file_path})
750
+ OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
751
+ OPTIONAL MATCH (called)-[:CONTAINS]->(init:Function)
752
+ WHERE init.name IN ["__init__", "constructor"]
753
+ WITH caller, init
754
+ WHERE caller IS NOT NULL AND init IS NOT NULL
755
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(init)
756
+ RETURN count(*) as created
757
+ """, call_params):
758
+ # No __init__ - link directly to the Class node
730
759
  if not self._safe_run_create(session, """
731
760
  OPTIONAL MATCH (caller:Class {name: $caller_name, path: $caller_file_path})
732
- OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
761
+ OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
733
762
  WITH caller, called
734
763
  WHERE caller IS NOT NULL AND called IS NOT NULL
735
764
  MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
736
765
  RETURN count(*) as created
737
766
  """, call_params):
738
-
739
- # Try Class caller -> Class callee
767
+ # Fallback: Relaxed Global Search (Function caller -> any Function callee)
740
768
  if not self._safe_run_create(session, """
741
- OPTIONAL MATCH (caller:Class {name: $caller_name, path: $caller_file_path})
742
- OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
743
- OPTIONAL MATCH (called)-[:CONTAINS]->(init:Function)
744
- WHERE init.name IN ["__init__", "constructor"]
745
- WITH caller, COALESCE(init, called) as final_target
746
- WHERE caller IS NOT NULL AND final_target IS NOT NULL
747
- MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(final_target)
748
- RETURN count(*) as created
769
+ OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
770
+ OPTIONAL MATCH (called:Function {name: $called_name})
771
+ WITH caller, called
772
+ WHERE caller IS NOT NULL AND called IS NOT NULL
773
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
749
774
  """, call_params):
750
-
751
- # Fallback: Relaxed Global Search (Caller: Function/Class -> Callee: Function)
752
- # Used when path resolution failed or was ambiguous
753
- self._safe_run_create(session, """
754
- OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
755
- OPTIONAL MATCH (callerClass:Class {name: $caller_name, path: $caller_file_path})
756
- WITH COALESCE(caller, callerClass) as final_caller
775
+ # Fallback: Class caller -> any Function callee
776
+ self._safe_run_create(session, """
777
+ OPTIONAL MATCH (caller:Class {name: $caller_name, path: $caller_file_path})
757
778
  OPTIONAL MATCH (called:Function {name: $called_name})
758
- WITH final_caller, called
759
- WHERE final_caller IS NOT NULL AND called IS NOT NULL
760
- MERGE (final_caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
779
+ WITH caller, called
780
+ WHERE caller IS NOT NULL AND called IS NOT NULL
781
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
761
782
  """, call_params)
762
783
  else:
763
784
  # File-level calls: Try Function first, then Class
@@ -769,7 +790,7 @@ class GraphBuilder:
769
790
  'args': call.get('args', []),
770
791
  'full_call_name': call.get('full_name', called_name)
771
792
  }
772
-
793
+
773
794
  if not self._safe_run_create(session, """
774
795
  OPTIONAL MATCH (caller:File {path: $caller_file_path})
775
796
  OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
@@ -778,26 +799,35 @@ class GraphBuilder:
778
799
  MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
779
800
  RETURN count(*) as created
780
801
  """, call_params):
781
-
802
+
803
+ # Try File caller -> Class.__init__ / constructor
782
804
  if not self._safe_run_create(session, """
783
805
  OPTIONAL MATCH (caller:File {path: $caller_file_path})
784
806
  OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
785
807
  OPTIONAL MATCH (called)-[:CONTAINS]->(init:Function)
786
808
  WHERE init.name IN ["__init__", "constructor"]
787
- WITH caller, COALESCE(init, called) as final_target
788
- WHERE caller IS NOT NULL AND final_target IS NOT NULL
789
- MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(final_target)
809
+ WITH caller, init
810
+ WHERE caller IS NOT NULL AND init IS NOT NULL
811
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(init)
790
812
  RETURN count(*) as created
791
813
  """, call_params):
792
-
793
- # Fallback: Relaxed Global Search (Caller: File -> Callee: Function)
794
- self._safe_run_create(session, """
814
+ # No __init__ - link directly to the Class node
815
+ if not self._safe_run_create(session, """
795
816
  OPTIONAL MATCH (caller:File {path: $caller_file_path})
796
- OPTIONAL MATCH (called:Function {name: $called_name})
817
+ OPTIONAL MATCH (called:Class {name: $called_name, path: $called_file_path})
797
818
  WITH caller, called
798
819
  WHERE caller IS NOT NULL AND called IS NOT NULL
799
820
  MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
800
- """, call_params)
821
+ RETURN count(*) as created
822
+ """, call_params):
823
+ # Fallback: Relaxed Global Search (File -> any Function)
824
+ self._safe_run_create(session, """
825
+ OPTIONAL MATCH (caller:File {path: $caller_file_path})
826
+ OPTIONAL MATCH (called:Function {name: $called_name})
827
+ WITH caller, called
828
+ WHERE caller IS NOT NULL AND called IS NOT NULL
829
+ MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(called)
830
+ """, call_params)
801
831
 
802
832
  def _create_all_function_calls(self, all_file_data: list[Dict], imports_map: dict):
803
833
  """Create CALLS relationships for all functions after all files have been processed."""
@@ -1278,20 +1308,22 @@ class GraphBuilder:
1278
1308
 
1279
1309
  # Search for .cgcignore upwards
1280
1310
  cgcignore_path = None
1281
- ignore_root = path.resolve()
1282
-
1311
+ # ignore_root is always the indexed path itself so that file paths
1312
+ # are matched relative to the project being indexed. A parent
1313
+ # .cgcignore is still loaded (for monorepo support), but anchoring
1314
+ # to its directory would make patterns like "website/" incorrectly
1315
+ # filter out every file when indexing the website sub-directory.
1316
+ ignore_root = path.resolve() if path.is_dir() else path.resolve().parent
1317
+
1283
1318
  # Start search from path (or parent if path is file)
1284
- curr = path.resolve()
1285
- if not curr.is_dir():
1286
- curr = curr.parent
1319
+ curr = ignore_root
1287
1320
 
1288
1321
  # Walk up looking for .cgcignore
1289
1322
  while True:
1290
1323
  candidate = curr / ".cgcignore"
1291
1324
  if candidate.exists():
1292
1325
  cgcignore_path = candidate
1293
- ignore_root = curr
1294
- debug_log(f"Found .cgcignore at {ignore_root}")
1326
+ debug_log(f"Found .cgcignore at {curr} (filtering relative to {ignore_root})")
1295
1327
  break
1296
1328
  if curr.parent == curr: # Root hit
1297
1329
  break
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  import uvicorn
7
7
  import json
8
8
  import os
9
+ import sys
9
10
  from typing import Optional, List, Dict, Any
10
11
 
11
12
  from ..core.database import DatabaseManager
@@ -36,27 +37,50 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
36
37
  raise HTTPException(status_code=500, detail="Database not initialized")
37
38
 
38
39
  def get_eid(element):
39
- if not element: return None
40
+ if element is None: return None
40
41
  if isinstance(element, (int, str)):
41
42
  return str(element)
42
- # Try various ways to get ID (Neo4j, FalkorDB, etc.)
43
+
44
+ # If element is a dict (like Neo4j returned items or KuzuDB node/rel dicts)
45
+ if isinstance(element, dict):
46
+ # KuzuDB _src / _dst are directly {'offset': X, 'table': Y}
47
+ if 'offset' in element and 'table' in element:
48
+ return f"{element.get('table')}_{element.get('offset')}"
49
+
50
+ for key in ['_id', 'id', 'element_id']:
51
+ if key in element:
52
+ val = element[key]
53
+ if val is not None:
54
+ # KuzuDB returns dict IDs like {'offset': 1, 'table': 0} inside nodes
55
+ if isinstance(val, dict):
56
+ return f"{val.get('table', 0)}_{val.get('offset', 0)}"
57
+ return str(val)
58
+ return str(id(element))
59
+
60
+ # Try various ways to get ID (Neo4j, FalkorDB, etc. objects)
43
61
  for attr in ['element_id', 'id', '_id']:
44
62
  if hasattr(element, attr):
45
63
  val = getattr(element, attr)
46
- if val is not None: return str(val)
64
+ if val is not None:
65
+ # KuzuDB objects if any
66
+ if isinstance(val, dict):
67
+ return f"{val.get('table', 0)}_{val.get('offset', 0)}"
68
+ return str(val)
47
69
  return str(id(element))
48
70
 
49
71
  try:
50
72
  nodes_dict = {}
51
73
  edges = []
52
74
 
75
+ print(f"DEBUG: Starting get_graph with repo_path={repo_path}", flush=True)
76
+
53
77
  with db_manager.get_driver().session() as session:
54
78
  if cypher_query:
55
- # Direct user query (filtered view)
79
+ print(f"DEBUG: Executing custom query: {cypher_query}", flush=True)
56
80
  result = session.run(cypher_query)
57
81
  elif repo_path:
58
82
  repo_path = str(Path(repo_path).resolve())
59
- # Optimized subgraph query
83
+ print(f"DEBUG: Fetching subgraph for: {repo_path}", flush=True)
60
84
  query = """
61
85
  MATCH (r:Repository {path: $repo_path})
62
86
  OPTIONAL MATCH (r)-[:CONTAINS*0..]->(n)
@@ -67,77 +91,146 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
67
91
  """
68
92
  result = session.run(query, repo_path=repo_path)
69
93
  else:
70
- query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 5000"
94
+ print("DEBUG: Fetching global graph", flush=True)
95
+ query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 50000"
71
96
  result = session.run(query)
72
97
 
98
+ record_count = 0
73
99
  for record in result:
100
+ record_count += 1
101
+ # Use .get() to avoid KeyError if the query doesn't return all fields (n, rel, m)
74
102
  for key in ['n', 'm']:
75
- node = record[key]
76
- if node:
77
- eid = get_eid(node)
78
- if eid not in nodes_dict:
79
- # FalkorDB / Neo4j labels compatibility
80
- labels = []
81
- if hasattr(node, 'labels'):
82
- labels = list(node.labels)
83
-
84
- # FalkorDB / Neo4j properties compatibility
85
- props = {}
86
- if hasattr(node, 'properties'):
87
- props = node.properties
88
- elif hasattr(node, 'items'):
89
- props = dict(node.items())
103
+ try:
104
+ node = record.get(key)
105
+ if node:
106
+ eid = get_eid(node)
107
+ if eid and eid not in nodes_dict:
108
+ # Extract labels
109
+ labels = []
110
+ if isinstance(node, dict):
111
+ # KuzuDB node label is under '_label'
112
+ if '_label' in node:
113
+ labels = [node['_label']]
114
+ elif 'label' in node:
115
+ labels = [node['label']]
116
+ else:
117
+ for label_attr in ['_labels', 'labels']:
118
+ if hasattr(node, label_attr):
119
+ attr_val = getattr(node, label_attr)
120
+ if attr_val:
121
+ labels = list(attr_val)
122
+ break
123
+
124
+ # Extract properties
125
+ props = {}
126
+ if isinstance(node, dict):
127
+ props = {k: v for k, v in node.items() if not k.startswith('_')}
128
+ else:
129
+ for prop_attr in ['properties', '_properties']:
130
+ if hasattr(node, prop_attr):
131
+ attr_val = getattr(node, prop_attr)
132
+ if attr_val:
133
+ props = dict(attr_val)
134
+ break
135
+
136
+ # Fallback if props still empty but node acts like dict
137
+ if not props and hasattr(node, 'items'):
138
+ try:
139
+ props = dict(node.items())
140
+ except: pass
90
141
 
91
- nodes_dict[eid] = {
92
- "id": eid,
93
- "label": props.get('name', props.get('label', 'Unknown')),
94
- "type": labels[0].capitalize() if labels else "Other",
95
- "file": props.get('path', props.get('file', '')),
96
- "properties": props
97
- }
142
+ # Extract name/label for frontend
143
+ # Prefer 'name' property, fallback to 'label', then 'path' or 'Unknown'
144
+ display_name = str(props.get('name', props.get('label', props.get('path', 'Unknown'))))
145
+
146
+ nodes_dict[eid] = {
147
+ "id": eid,
148
+ "name": display_name,
149
+ "label": display_name,
150
+ "type": str(labels[0]).capitalize() if labels else "Other",
151
+ "file": str(props.get('path', props.get('file', ''))),
152
+ "val": 4 if (labels and labels[0] in ['Repository', 'Class', 'Interface', 'Trait']) else 2,
153
+ "properties": props
154
+ }
155
+ except Exception as e:
156
+ print(f"DEBUG: Error parsing node: {e}", file=sys.stderr, flush=True)
157
+ continue
98
158
 
99
- rel = record['rel']
100
- if rel:
101
- rid = get_eid(rel)
102
-
103
- # FalkorDB / Neo4j compatibility for source/target nodes
104
- start_node = getattr(rel, 'start_node', getattr(rel, 'src_node', None))
105
- end_node = getattr(rel, 'end_node', getattr(rel, 'dest_node', None))
106
-
107
- source = get_eid(start_node)
108
- target = get_eid(end_node)
109
-
110
- if source and target:
111
- # relationship type/relation
112
- rel_type = "related"
113
- if hasattr(rel, 'type'):
114
- rel_type = rel.type
115
- elif hasattr(rel, 'relation'):
116
- rel_type = rel.relation
159
+ try:
160
+ rel = record.get('rel')
161
+ if rel is not None:
162
+ # One-shot debug: print type and keys/attrs of first rel
163
+ if not edges:
164
+ if isinstance(rel, dict):
165
+ print(f"DEBUG rel (dict): keys={list(rel.keys())}", file=sys.stderr, flush=True)
166
+ else:
167
+ print(f"DEBUG rel (obj): type={type(rel).__name__}, attrs={[a for a in dir(rel) if not a.startswith('__')]}", file=sys.stderr, flush=True)
168
+
169
+ # FalkorDB / KuzuDB may return rels as dicts OR objects
170
+ if isinstance(rel, dict):
171
+ rid = get_eid(rel)
172
+ # KuzuDB uses _src and _dst, FalkorDB uses src_node/dest_node
173
+ src = rel.get('_src', rel.get('src_node'))
174
+ dst = rel.get('_dst', rel.get('dest_node'))
117
175
 
118
- edges.append({
119
- "id": rid,
120
- "source": source,
121
- "target": target,
122
- "type": str(rel_type).upper()
123
- })
176
+ source = get_eid(src) if src is not None else None
177
+ target = get_eid(dst) if dst is not None else None
178
+ rel_type = str(rel.get('_label', rel.get('relation', rel.get('type', 'RELATED')))).upper()
179
+ else:
180
+ rid = get_eid(rel)
181
+ start_node = None
182
+ end_node = None
183
+ for src_attr in ['start_node', 'src_node', '_src_node']:
184
+ if hasattr(rel, src_attr):
185
+ start_node = getattr(rel, src_attr)
186
+ break
187
+ for dest_attr in ['end_node', 'dest_node', '_dest_node']:
188
+ if hasattr(rel, dest_attr):
189
+ end_node = getattr(rel, dest_attr)
190
+ break
191
+ source = get_eid(start_node) if start_node is not None else None
192
+ target = get_eid(end_node) if end_node is not None else None
193
+ rel_type = "RELATED"
194
+ for rel_attr in ['type', 'relation', '_relation']:
195
+ if hasattr(rel, rel_attr):
196
+ rel_type = str(getattr(rel, rel_attr)).upper()
197
+ break
198
+
199
+ if source and target:
200
+ edges.append({
201
+ "id": rid,
202
+ "source": source,
203
+ "target": target,
204
+ "type": rel_type
205
+ })
206
+ except Exception as e:
207
+ print(f"DEBUG: Error parsing relationship: {e}", file=sys.stderr, flush=True)
208
+ pass
209
+
210
+ print(f"DEBUG: Processed {record_count} records. extracted {len(nodes_dict)} nodes and {len(edges)} edges.", file=sys.stderr, flush=True)
124
211
 
125
212
  # Build a list of unique file paths from File-type nodes for the tree
126
- file_paths = sorted(set(
127
- n["file"] for n in nodes_dict.values()
128
- if n.get("file") and n.get("type", "").lower() == "file"
129
- ))
213
+ file_paths = []
214
+ for n in nodes_dict.values():
215
+ if n.get("file") and str(n.get("type", "")).lower() == "file":
216
+ file_paths.append(str(n["file"]))
217
+ file_paths = sorted(list(set(file_paths)))
130
218
 
131
- return {
219
+ response_data = {
132
220
  "nodes": list(nodes_dict.values()),
133
- "edges": edges,
221
+ "links": edges,
134
222
  "files": file_paths,
135
223
  }
224
+
225
+ print(f"API SUCCESS: Returning graph with {len(response_data['nodes'])} nodes and {len(response_data['links'])} links.", file=sys.stderr, flush=True)
226
+ return response_data
136
227
 
137
228
  except Exception as e:
138
229
  debug_log(f"Error fetching graph: {str(e)}")
139
230
  import traceback
140
231
  traceback.print_exc()
232
+ # Still return a valid structure so the frontend doesn't crash, but with 500 status if raised
233
+ # Actually, let's just return a 500 error but with JSON body if possible
141
234
  raise HTTPException(status_code=500, detail=str(e))
142
235
 
143
236
  @app.get("/api/file")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.3.3
3
+ Version: 0.3.5
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
@@ -41,10 +41,11 @@ Requires-Dist: watchdog>=3.0.0
41
41
  Requires-Dist: stdlibs>=2023.11.18
42
42
  Requires-Dist: typer[all]>=0.9.0
43
43
  Requires-Dist: rich>=13.7.0
44
- Requires-Dist: inquirerpy>=0.3.4
44
+ Requires-Dist: inquirerpy>=0.3.5
45
45
  Requires-Dist: python-dotenv>=1.0.0
46
46
  Requires-Dist: tree-sitter>=0.21.0
47
47
  Requires-Dist: tree-sitter-language-pack>=0.6.0
48
+ Requires-Dist: tree-sitter-c-sharp>=0.21.0
48
49
  Requires-Dist: pyyaml
49
50
  Requires-Dist: nbformat
50
51
  Requires-Dist: nbconvert>=7.16.6
@@ -56,6 +57,7 @@ Requires-Dist: uvicorn>=0.22.0
56
57
  Provides-Extra: parsing
57
58
  Requires-Dist: tree-sitter>=0.21.0; extra == "parsing"
58
59
  Requires-Dist: tree-sitter-language-pack>=0.6.0; extra == "parsing"
60
+ Requires-Dist: tree-sitter-c-sharp>=0.21.0; extra == "parsing"
59
61
  Provides-Extra: dev
60
62
  Requires-Dist: pytest>=7.4.0; extra == "dev"
61
63
  Requires-Dist: black>=23.11.0; extra == "dev"
@@ -160,7 +162,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
160
162
  ---
161
163
 
162
164
  ## Project Details
163
- - **Version:** 0.3.3
165
+ - **Version:** 0.3.5
164
166
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
165
167
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
166
168
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -247,7 +249,7 @@ _If you’re using CodeGraphContext in your project, feel free to open a PR and
247
249
  - `stdlibs>=2023.11.18`
248
250
  - `typer[all]>=0.9.0`
249
251
  - `rich>=13.7.0`
250
- - `inquirerpy>=0.3.4`
252
+ - `inquirerpy>=0.3.5`
251
253
  - `python-dotenv>=1.0.0`
252
254
  - `tree-sitter>=0.21.0`
253
255
  - `tree-sitter-language-pack>=0.6.0`
@@ -4,7 +4,7 @@ codegraphcontext/prompts.py,sha256=E5P55paM0oHfBcNVfxkxpXRGZnya1kr_mKDg9i49FwM,6
4
4
  codegraphcontext/server.py,sha256=gcb6V4x0Oh8haBOCm2WBjTCO9FDukrSalAMMApL-oc8,12806
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=8T7oMy6bBvQtfDjhQik5kKKkkyqWxeG4sXzeM8n-uxI,28374
7
+ codegraphcontext/cli/cli_helpers.py,sha256=QcpEV9VRFuPiiIEUwiXCr_nm7Z7amzphXv9zQY0aWIU,29646
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
@@ -24,7 +24,7 @@ codegraphcontext/core/watcher.py,sha256=42AWDv7LUE5v_HlY_Rvvlk8deDdlUBvM3hI-DMDW
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
26
  codegraphcontext/tools/code_finder.py,sha256=2wd05dMrPW8xZDBUixS4BaipNbrE4rttqSD6JYEeQM4,55552
27
- codegraphcontext/tools/graph_builder.py,sha256=tKg_unJ3NumOpxv4WEdC0b9_pk6YXrxtBOY8v0eXJNs,78876
27
+ codegraphcontext/tools/graph_builder.py,sha256=FaEeXQP5gdDclrXKjhWiwbmWXBEPadmqdeHZ2bWLK5A,81339
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
@@ -71,10 +71,10 @@ codegraphcontext/tools/query_tool_languages/typescript_toolkit.py,sha256=3S4hpmO
71
71
  codegraphcontext/utils/debug_log.py,sha256=Qg7jwyeg7x2h3Ur_2S34bdMCkHdlk_ngHfPwa97A9vE,2836
72
72
  codegraphcontext/utils/tree_sitter_manager.py,sha256=bIuKYN1aj1Zi6BnksGjZSLZzgBwuFRselZzyUpbToAU,9180
73
73
  codegraphcontext/utils/visualize_graph.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
- codegraphcontext/viz/server.py,sha256=f2xjgkjMOBgrZd5fZ6kCEO-lzH9hNfPPCcSwfitH4io,6719
75
- codegraphcontext-0.3.3.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
76
- codegraphcontext-0.3.3.dist-info/METADATA,sha256=pw9ykqFiZCaX1PioazQoU9S_Kmb7BRRAuPjveWyykk4,21579
77
- codegraphcontext-0.3.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
78
- codegraphcontext-0.3.3.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
79
- codegraphcontext-0.3.3.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
80
- codegraphcontext-0.3.3.dist-info/RECORD,,
74
+ codegraphcontext/viz/server.py,sha256=Yu4fwMC2FsE6KUt7oYNlcE3GpCrblstkwUIVaGXTPYI,12767
75
+ codegraphcontext-0.3.5.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
76
+ codegraphcontext-0.3.5.dist-info/METADATA,sha256=rkKzmh5bAjIJNVO_ShCsjn54nCb2dCOySSpT9Ptlo_Y,21685
77
+ codegraphcontext-0.3.5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
78
+ codegraphcontext-0.3.5.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
79
+ codegraphcontext-0.3.5.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
80
+ codegraphcontext-0.3.5.dist-info/RECORD,,