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.
- codegraphcontext/cli/cli_helpers.py +28 -4
- codegraphcontext/tools/graph_builder.py +79 -47
- codegraphcontext/viz/server.py +151 -58
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.5.dist-info}/METADATA +6 -4
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.5.dist-info}/RECORD +9 -9
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.5.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.5.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.5.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.5.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
|
|
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
|
|
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,
|
|
724
|
-
WHERE caller IS NOT NULL AND
|
|
725
|
-
MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
742
|
-
OPTIONAL MATCH (called:
|
|
743
|
-
|
|
744
|
-
WHERE
|
|
745
|
-
|
|
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
|
-
|
|
752
|
-
|
|
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
|
|
759
|
-
WHERE
|
|
760
|
-
MERGE (
|
|
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,
|
|
788
|
-
WHERE caller IS NOT NULL AND
|
|
789
|
-
MERGE (caller)-[:CALLS {line_number: $line_number, args: $args, full_call_name: $full_call_name}]->(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
codegraphcontext/viz/server.py
CHANGED
|
@@ -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
|
|
40
|
+
if element is None: return None
|
|
40
41
|
if isinstance(element, (int, str)):
|
|
41
42
|
return str(element)
|
|
42
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 =
|
|
127
|
-
|
|
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
|
-
|
|
219
|
+
response_data = {
|
|
132
220
|
"nodes": list(nodes_dict.values()),
|
|
133
|
-
"
|
|
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
|
+
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.
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
75
|
-
codegraphcontext-0.3.
|
|
76
|
-
codegraphcontext-0.3.
|
|
77
|
-
codegraphcontext-0.3.
|
|
78
|
-
codegraphcontext-0.3.
|
|
79
|
-
codegraphcontext-0.3.
|
|
80
|
-
codegraphcontext-0.3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|