codegraphcontext 0.3.3__tar.gz → 0.3.5__tar.gz
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-0.3.3/src/codegraphcontext.egg-info → codegraphcontext-0.3.5}/PKG-INFO +6 -4
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/README.md +2 -2
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/pyproject.toml +4 -2
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/cli_helpers.py +28 -4
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/graph_builder.py +79 -47
- codegraphcontext-0.3.5/src/codegraphcontext/viz/server.py +272 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5/src/codegraphcontext.egg-info}/PKG-INFO +6 -4
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/requires.txt +3 -1
- codegraphcontext-0.3.3/src/codegraphcontext/viz/server.py +0 -179
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/LICENSE +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/MANIFEST.in +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/setup.cfg +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/__init__.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/__main__.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/__init__.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/config_manager.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/main.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/registry_commands.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/setup_macos.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/setup_wizard.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/visualizer.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/__init__.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/bundle_registry.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/cgc_bundle.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/database.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/database_falkordb.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/database_falkordb_remote.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/database_kuzu.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/falkor_worker.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/jobs.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/watcher.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/prompts.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/server.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tool_definitions.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/__init__.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/code_finder.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/handlers/analysis_handlers.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/handlers/indexing_handlers.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/handlers/management_handlers.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/handlers/query_handlers.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/handlers/watcher_handlers.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/c.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/cpp.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/csharp.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/dart.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/elixir.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/go.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/haskell.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/java.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/javascript.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/perl.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/php.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/python.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/ruby.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/rust.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/scala.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/swift.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/typescript.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/typescriptjsx.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/package_resolver.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/dart_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/perl_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/swift_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/scip_indexer.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/scip_pb2.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/system.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/utils/debug_log.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/utils/tree_sitter_manager.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/utils/visualize_graph.py +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/SOURCES.txt +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
- {codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/top_level.txt +0 -0
|
@@ -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`
|
|
@@ -96,7 +96,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
|
|
|
96
96
|
---
|
|
97
97
|
|
|
98
98
|
## Project Details
|
|
99
|
-
- **Version:** 0.3.
|
|
99
|
+
- **Version:** 0.3.5
|
|
100
100
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
101
101
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
102
102
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -183,7 +183,7 @@ _If you’re using CodeGraphContext in your project, feel free to open a PR and
|
|
|
183
183
|
- `stdlibs>=2023.11.18`
|
|
184
184
|
- `typer[all]>=0.9.0`
|
|
185
185
|
- `rich>=13.7.0`
|
|
186
|
-
- `inquirerpy>=0.3.
|
|
186
|
+
- `inquirerpy>=0.3.5`
|
|
187
187
|
- `python-dotenv>=1.0.0`
|
|
188
188
|
- `tree-sitter>=0.21.0`
|
|
189
189
|
- `tree-sitter-language-pack>=0.6.0`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codegraphcontext"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.5"
|
|
4
4
|
description = "An MCP server that indexes local code into a graph database to provide context to AI assistants."
|
|
5
5
|
authors = [{ name = "Shashank Shekhar Singh", email = "shashankshekharsingh1205@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -20,10 +20,11 @@ dependencies = [
|
|
|
20
20
|
"stdlibs>=2023.11.18",
|
|
21
21
|
"typer[all]>=0.9.0",
|
|
22
22
|
"rich>=13.7.0",
|
|
23
|
-
"inquirerpy>=0.3.
|
|
23
|
+
"inquirerpy>=0.3.5",
|
|
24
24
|
"python-dotenv>=1.0.0",
|
|
25
25
|
"tree-sitter>=0.21.0",
|
|
26
26
|
"tree-sitter-language-pack>=0.6.0",
|
|
27
|
+
"tree-sitter-c-sharp>=0.21.0",
|
|
27
28
|
"pyyaml",
|
|
28
29
|
"nbformat",
|
|
29
30
|
"nbconvert>=7.16.6",
|
|
@@ -38,6 +39,7 @@ dependencies = [
|
|
|
38
39
|
parsing = [
|
|
39
40
|
"tree-sitter>=0.21.0",
|
|
40
41
|
"tree-sitter-language-pack>=0.6.0",
|
|
42
|
+
"tree-sitter-c-sharp>=0.21.0",
|
|
41
43
|
]
|
|
42
44
|
dev = [
|
|
43
45
|
"pytest>=7.4.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-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/graph_builder.py
RENAMED
|
@@ -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
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from fastapi import FastAPI, HTTPException, Query, Request
|
|
2
|
+
from fastapi.staticfiles import StaticFiles
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
from fastapi.responses import HTMLResponse, FileResponse
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import uvicorn
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Optional, List, Dict, Any
|
|
11
|
+
|
|
12
|
+
from ..core.database import DatabaseManager
|
|
13
|
+
from ..utils.debug_log import debug_log
|
|
14
|
+
|
|
15
|
+
app = FastAPI()
|
|
16
|
+
|
|
17
|
+
# Enable CORS for development
|
|
18
|
+
app.add_middleware(
|
|
19
|
+
CORSMiddleware,
|
|
20
|
+
allow_origins=["*"],
|
|
21
|
+
allow_methods=["*"],
|
|
22
|
+
allow_headers=["*"],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Global database manager (will be initialized when server starts)
|
|
26
|
+
db_manager: Optional[DatabaseManager] = None
|
|
27
|
+
# Path to static directory
|
|
28
|
+
_static_dir: Optional[str] = None
|
|
29
|
+
|
|
30
|
+
def set_db_manager(manager: DatabaseManager):
|
|
31
|
+
global db_manager
|
|
32
|
+
db_manager = manager
|
|
33
|
+
|
|
34
|
+
@app.get("/api/graph")
|
|
35
|
+
async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str] = None):
|
|
36
|
+
if not db_manager:
|
|
37
|
+
raise HTTPException(status_code=500, detail="Database not initialized")
|
|
38
|
+
|
|
39
|
+
def get_eid(element):
|
|
40
|
+
if element is None: return None
|
|
41
|
+
if isinstance(element, (int, str)):
|
|
42
|
+
return str(element)
|
|
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)
|
|
61
|
+
for attr in ['element_id', 'id', '_id']:
|
|
62
|
+
if hasattr(element, attr):
|
|
63
|
+
val = getattr(element, attr)
|
|
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)
|
|
69
|
+
return str(id(element))
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
nodes_dict = {}
|
|
73
|
+
edges = []
|
|
74
|
+
|
|
75
|
+
print(f"DEBUG: Starting get_graph with repo_path={repo_path}", flush=True)
|
|
76
|
+
|
|
77
|
+
with db_manager.get_driver().session() as session:
|
|
78
|
+
if cypher_query:
|
|
79
|
+
print(f"DEBUG: Executing custom query: {cypher_query}", flush=True)
|
|
80
|
+
result = session.run(cypher_query)
|
|
81
|
+
elif repo_path:
|
|
82
|
+
repo_path = str(Path(repo_path).resolve())
|
|
83
|
+
print(f"DEBUG: Fetching subgraph for: {repo_path}", flush=True)
|
|
84
|
+
query = """
|
|
85
|
+
MATCH (r:Repository {path: $repo_path})
|
|
86
|
+
OPTIONAL MATCH (r)-[:CONTAINS*0..]->(n)
|
|
87
|
+
WITH DISTINCT n
|
|
88
|
+
WHERE n IS NOT NULL
|
|
89
|
+
OPTIONAL MATCH (n)-[rel]->(m)
|
|
90
|
+
RETURN n, rel, m
|
|
91
|
+
"""
|
|
92
|
+
result = session.run(query, repo_path=repo_path)
|
|
93
|
+
else:
|
|
94
|
+
print("DEBUG: Fetching global graph", flush=True)
|
|
95
|
+
query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 50000"
|
|
96
|
+
result = session.run(query)
|
|
97
|
+
|
|
98
|
+
record_count = 0
|
|
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)
|
|
102
|
+
for key in ['n', 'm']:
|
|
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
|
|
141
|
+
|
|
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
|
|
158
|
+
|
|
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'))
|
|
175
|
+
|
|
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)
|
|
211
|
+
|
|
212
|
+
# Build a list of unique file paths from File-type nodes for the tree
|
|
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)))
|
|
218
|
+
|
|
219
|
+
response_data = {
|
|
220
|
+
"nodes": list(nodes_dict.values()),
|
|
221
|
+
"links": edges,
|
|
222
|
+
"files": file_paths,
|
|
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
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
debug_log(f"Error fetching graph: {str(e)}")
|
|
230
|
+
import traceback
|
|
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
|
|
234
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
235
|
+
|
|
236
|
+
@app.get("/api/file")
|
|
237
|
+
async def get_file(path: str):
|
|
238
|
+
file_path = Path(path)
|
|
239
|
+
if not file_path.exists():
|
|
240
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
244
|
+
return {"content": f.read()}
|
|
245
|
+
except Exception as e:
|
|
246
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
247
|
+
|
|
248
|
+
# SPA fallback handler
|
|
249
|
+
@app.get("/{full_path:path}")
|
|
250
|
+
async def spa_fallback(request: Request, full_path: str):
|
|
251
|
+
global _static_dir
|
|
252
|
+
if not _static_dir:
|
|
253
|
+
return HTMLResponse("Static directory not configured", status_code=500)
|
|
254
|
+
|
|
255
|
+
# Filesystem path
|
|
256
|
+
file_path = Path(_static_dir) / full_path
|
|
257
|
+
|
|
258
|
+
# If the file exists and is a file, serve it normally
|
|
259
|
+
if file_path.exists() and file_path.is_file():
|
|
260
|
+
return FileResponse(file_path)
|
|
261
|
+
|
|
262
|
+
# Otherwise serve index.html (Standard SPA routing)
|
|
263
|
+
index_path = Path(_static_dir) / "index.html"
|
|
264
|
+
if index_path.exists():
|
|
265
|
+
return FileResponse(index_path)
|
|
266
|
+
|
|
267
|
+
return HTMLResponse("Not Found (Built UI not found in viz/dist)", status_code=404)
|
|
268
|
+
|
|
269
|
+
def run_server(host: str = "127.0.0.1", port: int = 8000, static_dir: Optional[str] = None):
|
|
270
|
+
global _static_dir
|
|
271
|
+
_static_dir = static_dir
|
|
272
|
+
uvicorn.run(app, host=host, port=port)
|
|
@@ -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`
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/requires.txt
RENAMED
|
@@ -3,10 +3,11 @@ watchdog>=3.0.0
|
|
|
3
3
|
stdlibs>=2023.11.18
|
|
4
4
|
typer[all]>=0.9.0
|
|
5
5
|
rich>=13.7.0
|
|
6
|
-
inquirerpy>=0.3.
|
|
6
|
+
inquirerpy>=0.3.5
|
|
7
7
|
python-dotenv>=1.0.0
|
|
8
8
|
tree-sitter>=0.21.0
|
|
9
9
|
tree-sitter-language-pack>=0.6.0
|
|
10
|
+
tree-sitter-c-sharp>=0.21.0
|
|
10
11
|
pyyaml
|
|
11
12
|
nbformat
|
|
12
13
|
nbconvert>=7.16.6
|
|
@@ -26,3 +27,4 @@ pytest-asyncio>=0.21.0
|
|
|
26
27
|
[parsing]
|
|
27
28
|
tree-sitter>=0.21.0
|
|
28
29
|
tree-sitter-language-pack>=0.6.0
|
|
30
|
+
tree-sitter-c-sharp>=0.21.0
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
from fastapi import FastAPI, HTTPException, Query, Request
|
|
2
|
-
from fastapi.staticfiles import StaticFiles
|
|
3
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
-
from fastapi.responses import HTMLResponse, FileResponse
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
import uvicorn
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
from typing import Optional, List, Dict, Any
|
|
10
|
-
|
|
11
|
-
from ..core.database import DatabaseManager
|
|
12
|
-
from ..utils.debug_log import debug_log
|
|
13
|
-
|
|
14
|
-
app = FastAPI()
|
|
15
|
-
|
|
16
|
-
# Enable CORS for development
|
|
17
|
-
app.add_middleware(
|
|
18
|
-
CORSMiddleware,
|
|
19
|
-
allow_origins=["*"],
|
|
20
|
-
allow_methods=["*"],
|
|
21
|
-
allow_headers=["*"],
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
# Global database manager (will be initialized when server starts)
|
|
25
|
-
db_manager: Optional[DatabaseManager] = None
|
|
26
|
-
# Path to static directory
|
|
27
|
-
_static_dir: Optional[str] = None
|
|
28
|
-
|
|
29
|
-
def set_db_manager(manager: DatabaseManager):
|
|
30
|
-
global db_manager
|
|
31
|
-
db_manager = manager
|
|
32
|
-
|
|
33
|
-
@app.get("/api/graph")
|
|
34
|
-
async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str] = None):
|
|
35
|
-
if not db_manager:
|
|
36
|
-
raise HTTPException(status_code=500, detail="Database not initialized")
|
|
37
|
-
|
|
38
|
-
def get_eid(element):
|
|
39
|
-
if not element: return None
|
|
40
|
-
if isinstance(element, (int, str)):
|
|
41
|
-
return str(element)
|
|
42
|
-
# Try various ways to get ID (Neo4j, FalkorDB, etc.)
|
|
43
|
-
for attr in ['element_id', 'id', '_id']:
|
|
44
|
-
if hasattr(element, attr):
|
|
45
|
-
val = getattr(element, attr)
|
|
46
|
-
if val is not None: return str(val)
|
|
47
|
-
return str(id(element))
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
nodes_dict = {}
|
|
51
|
-
edges = []
|
|
52
|
-
|
|
53
|
-
with db_manager.get_driver().session() as session:
|
|
54
|
-
if cypher_query:
|
|
55
|
-
# Direct user query (filtered view)
|
|
56
|
-
result = session.run(cypher_query)
|
|
57
|
-
elif repo_path:
|
|
58
|
-
repo_path = str(Path(repo_path).resolve())
|
|
59
|
-
# Optimized subgraph query
|
|
60
|
-
query = """
|
|
61
|
-
MATCH (r:Repository {path: $repo_path})
|
|
62
|
-
OPTIONAL MATCH (r)-[:CONTAINS*0..]->(n)
|
|
63
|
-
WITH DISTINCT n
|
|
64
|
-
WHERE n IS NOT NULL
|
|
65
|
-
OPTIONAL MATCH (n)-[rel]->(m)
|
|
66
|
-
RETURN n, rel, m
|
|
67
|
-
"""
|
|
68
|
-
result = session.run(query, repo_path=repo_path)
|
|
69
|
-
else:
|
|
70
|
-
query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 5000"
|
|
71
|
-
result = session.run(query)
|
|
72
|
-
|
|
73
|
-
for record in result:
|
|
74
|
-
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())
|
|
90
|
-
|
|
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
|
-
}
|
|
98
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
edges.append({
|
|
119
|
-
"id": rid,
|
|
120
|
-
"source": source,
|
|
121
|
-
"target": target,
|
|
122
|
-
"type": str(rel_type).upper()
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
# 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
|
-
))
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
"nodes": list(nodes_dict.values()),
|
|
133
|
-
"edges": edges,
|
|
134
|
-
"files": file_paths,
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
except Exception as e:
|
|
138
|
-
debug_log(f"Error fetching graph: {str(e)}")
|
|
139
|
-
import traceback
|
|
140
|
-
traceback.print_exc()
|
|
141
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
142
|
-
|
|
143
|
-
@app.get("/api/file")
|
|
144
|
-
async def get_file(path: str):
|
|
145
|
-
file_path = Path(path)
|
|
146
|
-
if not file_path.exists():
|
|
147
|
-
raise HTTPException(status_code=404, detail="File not found")
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
151
|
-
return {"content": f.read()}
|
|
152
|
-
except Exception as e:
|
|
153
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
154
|
-
|
|
155
|
-
# SPA fallback handler
|
|
156
|
-
@app.get("/{full_path:path}")
|
|
157
|
-
async def spa_fallback(request: Request, full_path: str):
|
|
158
|
-
global _static_dir
|
|
159
|
-
if not _static_dir:
|
|
160
|
-
return HTMLResponse("Static directory not configured", status_code=500)
|
|
161
|
-
|
|
162
|
-
# Filesystem path
|
|
163
|
-
file_path = Path(_static_dir) / full_path
|
|
164
|
-
|
|
165
|
-
# If the file exists and is a file, serve it normally
|
|
166
|
-
if file_path.exists() and file_path.is_file():
|
|
167
|
-
return FileResponse(file_path)
|
|
168
|
-
|
|
169
|
-
# Otherwise serve index.html (Standard SPA routing)
|
|
170
|
-
index_path = Path(_static_dir) / "index.html"
|
|
171
|
-
if index_path.exists():
|
|
172
|
-
return FileResponse(index_path)
|
|
173
|
-
|
|
174
|
-
return HTMLResponse("Not Found (Built UI not found in viz/dist)", status_code=404)
|
|
175
|
-
|
|
176
|
-
def run_server(host: str = "127.0.0.1", port: int = 8000, static_dir: Optional[str] = None):
|
|
177
|
-
global _static_dir
|
|
178
|
-
_static_dir = static_dir
|
|
179
|
-
uvicorn.run(app, host=host, port=port)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/config_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/cli/registry_commands.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/bundle_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/database_falkordb.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/database_kuzu.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/core/falkor_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/cpp.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/csharp.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/dart.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/elixir.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/go.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/haskell.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/java.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/javascript.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/kotlin.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/perl.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/php.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/python.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/ruby.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/rust.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/scala.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/swift.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/languages/typescript.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/package_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/tools/scip_indexer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/utils/tree_sitter_manager.py
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext/utils/visualize_graph.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.3.3 → codegraphcontext-0.3.5}/src/codegraphcontext.egg-info/top_level.txt
RENAMED
|
File without changes
|