codegraphcontext 0.3.3__py3-none-any.whl → 0.3.4__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/viz/server.py +106 -55
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.4.dist-info}/METADATA +4 -2
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.4.dist-info}/RECORD +8 -8
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.4.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.4.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.3.3.dist-info → codegraphcontext-0.3.4.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}"
|
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
|
|
@@ -50,13 +51,17 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
|
|
|
50
51
|
nodes_dict = {}
|
|
51
52
|
edges = []
|
|
52
53
|
|
|
54
|
+
print(f"DEBUG: Starting get_graph with repo_path={repo_path}", flush=True)
|
|
55
|
+
nodes_dict = {}
|
|
56
|
+
edges = []
|
|
57
|
+
|
|
53
58
|
with db_manager.get_driver().session() as session:
|
|
54
59
|
if cypher_query:
|
|
55
|
-
|
|
60
|
+
print(f"DEBUG: Executing custom query: {cypher_query}", flush=True)
|
|
56
61
|
result = session.run(cypher_query)
|
|
57
62
|
elif repo_path:
|
|
58
63
|
repo_path = str(Path(repo_path).resolve())
|
|
59
|
-
|
|
64
|
+
print(f"DEBUG: Fetching subgraph for: {repo_path}", flush=True)
|
|
60
65
|
query = """
|
|
61
66
|
MATCH (r:Repository {path: $repo_path})
|
|
62
67
|
OPTIONAL MATCH (r)-[:CONTAINS*0..]->(n)
|
|
@@ -67,77 +72,123 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
|
|
|
67
72
|
"""
|
|
68
73
|
result = session.run(query, repo_path=repo_path)
|
|
69
74
|
else:
|
|
75
|
+
print("DEBUG: Fetching global graph", flush=True)
|
|
70
76
|
query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 5000"
|
|
71
77
|
result = session.run(query)
|
|
72
78
|
|
|
79
|
+
record_count = 0
|
|
73
80
|
for record in result:
|
|
81
|
+
record_count += 1
|
|
82
|
+
# Use .get() to avoid KeyError if the query doesn't return all fields (n, rel, m)
|
|
74
83
|
for key in ['n', 'm']:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
elif hasattr(node, 'items'):
|
|
89
|
-
props = dict(node.items())
|
|
84
|
+
try:
|
|
85
|
+
node = record.get(key)
|
|
86
|
+
if node:
|
|
87
|
+
eid = get_eid(node)
|
|
88
|
+
if eid and eid not in nodes_dict:
|
|
89
|
+
# Extract labels
|
|
90
|
+
labels = []
|
|
91
|
+
for label_attr in ['_labels', 'labels']:
|
|
92
|
+
if hasattr(node, label_attr):
|
|
93
|
+
attr_val = getattr(node, label_attr)
|
|
94
|
+
if attr_val:
|
|
95
|
+
labels = list(attr_val)
|
|
96
|
+
break
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
# Extract properties
|
|
99
|
+
props = {}
|
|
100
|
+
for prop_attr in ['properties', '_properties']:
|
|
101
|
+
if hasattr(node, prop_attr):
|
|
102
|
+
attr_val = getattr(node, prop_attr)
|
|
103
|
+
if attr_val:
|
|
104
|
+
props = dict(attr_val)
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
# Fallback if props still empty but node acts like dict
|
|
108
|
+
if not props and hasattr(node, 'items'):
|
|
109
|
+
try:
|
|
110
|
+
props = dict(node.items())
|
|
111
|
+
except: pass
|
|
112
|
+
|
|
113
|
+
# Extract name/label for frontend
|
|
114
|
+
# Prefer 'name' property, fallback to 'label', then 'path' or 'Unknown'
|
|
115
|
+
display_name = str(props.get('name', props.get('label', props.get('path', 'Unknown'))))
|
|
116
|
+
|
|
117
|
+
nodes_dict[eid] = {
|
|
118
|
+
"id": eid,
|
|
119
|
+
"name": display_name,
|
|
120
|
+
"label": display_name,
|
|
121
|
+
"type": str(labels[0]).capitalize() if labels else "Other",
|
|
122
|
+
"file": str(props.get('path', props.get('file', ''))),
|
|
123
|
+
"val": 4 if (labels and labels[0] in ['Repository', 'Class', 'Interface', 'Trait']) else 2,
|
|
124
|
+
"properties": props
|
|
125
|
+
}
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"DEBUG: Error parsing node: {e}", file=sys.stderr, flush=True)
|
|
128
|
+
continue
|
|
98
129
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
try:
|
|
131
|
+
rel = record.get('rel')
|
|
132
|
+
if rel:
|
|
133
|
+
rid = get_eid(rel)
|
|
134
|
+
|
|
135
|
+
# Try various ways to get start/end nodes
|
|
136
|
+
start_node = None
|
|
137
|
+
end_node = None
|
|
138
|
+
for src_attr in ['start_node', 'src_node', '_src_node']:
|
|
139
|
+
if hasattr(rel, src_attr):
|
|
140
|
+
start_node = getattr(rel, src_attr)
|
|
141
|
+
break
|
|
142
|
+
for dest_attr in ['end_node', 'dest_node', '_dest_node']:
|
|
143
|
+
if hasattr(rel, dest_attr):
|
|
144
|
+
end_node = getattr(rel, dest_attr)
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
source = get_eid(start_node) if start_node is not None else None
|
|
148
|
+
target = get_eid(end_node) if end_node is not None else None
|
|
149
|
+
|
|
150
|
+
if source and target:
|
|
151
|
+
# Extract relationship type
|
|
152
|
+
rel_type = "RELATED"
|
|
153
|
+
for rel_attr in ['type', 'relation', '_relation']:
|
|
154
|
+
if hasattr(rel, rel_attr):
|
|
155
|
+
rel_type = getattr(rel, rel_attr)
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
edges.append({
|
|
159
|
+
"id": rid,
|
|
160
|
+
"source": source,
|
|
161
|
+
"target": target,
|
|
162
|
+
"type": str(rel_type).upper()
|
|
163
|
+
})
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f"DEBUG: Error parsing relationship: {e}", file=sys.stderr, flush=True)
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
print(f"DEBUG: Processed {record_count} records. extracted {len(nodes_dict)} nodes and {len(edges)} edges.", file=sys.stderr, flush=True)
|
|
124
169
|
|
|
125
170
|
# 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
|
-
|
|
171
|
+
file_paths = []
|
|
172
|
+
for n in nodes_dict.values():
|
|
173
|
+
if n.get("file") and str(n.get("type", "")).lower() == "file":
|
|
174
|
+
file_paths.append(str(n["file"]))
|
|
175
|
+
file_paths = sorted(list(set(file_paths)))
|
|
130
176
|
|
|
131
|
-
|
|
177
|
+
response_data = {
|
|
132
178
|
"nodes": list(nodes_dict.values()),
|
|
133
|
-
"
|
|
179
|
+
"links": edges,
|
|
134
180
|
"files": file_paths,
|
|
135
181
|
}
|
|
182
|
+
|
|
183
|
+
print(f"API SUCCESS: Returning graph with {len(response_data['nodes'])} nodes and {len(response_data['links'])} links.", file=sys.stderr, flush=True)
|
|
184
|
+
return response_data
|
|
136
185
|
|
|
137
186
|
except Exception as e:
|
|
138
187
|
debug_log(f"Error fetching graph: {str(e)}")
|
|
139
188
|
import traceback
|
|
140
189
|
traceback.print_exc()
|
|
190
|
+
# Still return a valid structure so the frontend doesn't crash, but with 500 status if raised
|
|
191
|
+
# Actually, let's just return a 500 error but with JSON body if possible
|
|
141
192
|
raise HTTPException(status_code=500, detail=str(e))
|
|
142
193
|
|
|
143
194
|
@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.4
|
|
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
|
|
@@ -45,6 +45,7 @@ Requires-Dist: inquirerpy>=0.3.4
|
|
|
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.4
|
|
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/)
|
|
@@ -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
|
|
@@ -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=gBl6yfTDa4F7FW4JxHV9QtG1kVSt8iSKvhCXSKB0lIw,10017
|
|
75
|
+
codegraphcontext-0.3.4.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
76
|
+
codegraphcontext-0.3.4.dist-info/METADATA,sha256=PHNdbGKtvsw3S0hhTHgCoanKx_T8DMPPdLZ3iGEWe4o,21685
|
|
77
|
+
codegraphcontext-0.3.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
78
|
+
codegraphcontext-0.3.4.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
|
|
79
|
+
codegraphcontext-0.3.4.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
|
|
80
|
+
codegraphcontext-0.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|