codegraphcontext 0.3.0__py3-none-any.whl → 0.3.2__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 +42 -30
- codegraphcontext/cli/main.py +12 -8
- codegraphcontext/core/cgc_bundle.py +5 -1
- codegraphcontext/tools/code_finder.py +25 -26
- codegraphcontext/tools/graph_builder.py +157 -6
- codegraphcontext/tools/package_resolver.py +12 -10
- codegraphcontext/viz/server.py +178 -0
- {codegraphcontext-0.3.0.dist-info → codegraphcontext-0.3.2.dist-info}/METADATA +17 -4
- {codegraphcontext-0.3.0.dist-info → codegraphcontext-0.3.2.dist-info}/RECORD +13 -12
- {codegraphcontext-0.3.0.dist-info → codegraphcontext-0.3.2.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.3.0.dist-info → codegraphcontext-0.3.2.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.3.0.dist-info → codegraphcontext-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.3.0.dist-info → codegraphcontext-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -4,6 +4,7 @@ import uuid
|
|
|
4
4
|
import urllib.parse
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import time
|
|
7
|
+
from typing import Optional
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from rich.table import Table
|
|
9
10
|
from rich.progress import (
|
|
@@ -339,44 +340,55 @@ def cypher_helper_visual(query: str):
|
|
|
339
340
|
console.print(f"[bold red]An error occurred while executing query:[/bold red] {e}")
|
|
340
341
|
finally:
|
|
341
342
|
db_manager.close_driver()
|
|
342
|
-
|
|
343
|
-
|
|
344
343
|
import webbrowser
|
|
344
|
+
import urllib.parse
|
|
345
|
+
from ..viz.server import run_server, set_db_manager
|
|
345
346
|
|
|
346
|
-
def visualize_helper(
|
|
347
|
-
""""Generates
|
|
347
|
+
def visualize_helper(repo_path: Optional[str] = None, port: int = 8000):
|
|
348
|
+
""""Generates an interactive visualization using the Playground UI."""
|
|
348
349
|
services = _initialize_services()
|
|
349
350
|
if not all(services):
|
|
350
351
|
return
|
|
351
352
|
|
|
352
353
|
db_manager, _, _ = services
|
|
353
354
|
|
|
354
|
-
#
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
355
|
+
# Set the DB manager for the server
|
|
356
|
+
set_db_manager(db_manager)
|
|
357
|
+
|
|
358
|
+
# Determine the static directory (built React app)
|
|
359
|
+
static_dir = Path(__file__).parent.parent / "viz" / "dist"
|
|
360
|
+
if not static_dir.exists():
|
|
361
|
+
console.print("[yellow]Warning: Visualizer UI assets not found in package. Using fallback static dir.[/yellow]")
|
|
362
|
+
# Fallback for development
|
|
363
|
+
static_dir = Path.cwd() / "website" / "dist"
|
|
364
|
+
|
|
365
|
+
# Construct the URL
|
|
366
|
+
backend_url = f"http://localhost:{port}"
|
|
367
|
+
params = {"backend": backend_url}
|
|
368
|
+
if repo_path:
|
|
369
|
+
params["repo_path"] = str(Path(repo_path).resolve())
|
|
370
|
+
|
|
371
|
+
query_string = urllib.parse.urlencode(params)
|
|
372
|
+
visualization_url = f"{backend_url}/playground?{query_string}"
|
|
373
|
+
|
|
374
|
+
console.print(f"[green]Starting visualizer server on {backend_url}...[/green]")
|
|
375
|
+
console.print(f"[cyan]Opening Playground UI:[/cyan] {visualization_url}")
|
|
376
|
+
|
|
377
|
+
# Open browser in a separate thread/process if possible, or just before starting server
|
|
378
|
+
def open_browser():
|
|
379
|
+
import time
|
|
380
|
+
time.sleep(1.5) # Give the server a moment to start
|
|
381
|
+
webbrowser.open(visualization_url)
|
|
382
|
+
|
|
383
|
+
import threading
|
|
384
|
+
threading.Thread(target=open_browser, daemon=True).start()
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
run_server(host="127.0.0.1", port=port, static_dir=str(static_dir))
|
|
388
|
+
except Exception as e:
|
|
389
|
+
console.print(f"[bold red]An error occurred while running the server:[/bold red] {e}")
|
|
390
|
+
finally:
|
|
391
|
+
db_manager.close_driver()
|
|
380
392
|
|
|
381
393
|
def _visualize_falkordb(db_manager):
|
|
382
394
|
console.print("[dim]Generating FalkorDB visualization (showing up to 500 relationships)...[/dim]")
|
codegraphcontext/cli/main.py
CHANGED
|
@@ -994,15 +994,15 @@ def delete(
|
|
|
994
994
|
delete_helper(path)
|
|
995
995
|
|
|
996
996
|
@app.command()
|
|
997
|
-
def visualize(
|
|
997
|
+
def visualize(
|
|
998
|
+
repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Path to the repository to visualize."),
|
|
999
|
+
port: int = typer.Option(8000, "--port", "-p", help="Port to run the visualizer server on.")
|
|
1000
|
+
):
|
|
998
1001
|
"""
|
|
999
|
-
|
|
1000
|
-
If no query is provided, a default query will be used.
|
|
1002
|
+
Launches the interactive Playground UI to visualize the code graph.
|
|
1001
1003
|
"""
|
|
1002
|
-
if query is None:
|
|
1003
|
-
query = "MATCH p=()-->() RETURN p"
|
|
1004
1004
|
_load_credentials()
|
|
1005
|
-
visualize_helper(
|
|
1005
|
+
visualize_helper(repo, port)
|
|
1006
1006
|
|
|
1007
1007
|
@app.command("list")
|
|
1008
1008
|
def list_repositories():
|
|
@@ -2145,9 +2145,13 @@ def delete_abbrev(
|
|
|
2145
2145
|
delete(path, all_repos)
|
|
2146
2146
|
|
|
2147
2147
|
@app.command("v", rich_help_panel="Shortcuts")
|
|
2148
|
-
def visualize_abbrev(
|
|
2148
|
+
def visualize_abbrev(
|
|
2149
|
+
repo: Optional[str] = typer.Argument(None, help="Path to the repository to visualize."),
|
|
2150
|
+
port: int = typer.Option(8000, "--port", "-p", help="Port to run the visualizer server on.")
|
|
2151
|
+
):
|
|
2149
2152
|
"""Shortcut for 'cgc visualize'"""
|
|
2150
|
-
|
|
2153
|
+
_load_credentials()
|
|
2154
|
+
visualize_helper(repo, port)
|
|
2151
2155
|
|
|
2152
2156
|
@app.command("w", rich_help_panel="Shortcuts")
|
|
2153
2157
|
def watch_abbrev(path: str = typer.Argument(".", help="Path to watch")):
|
|
@@ -161,9 +161,13 @@ class CGCBundle:
|
|
|
161
161
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
162
162
|
temp_path = Path(temp_dir)
|
|
163
163
|
|
|
164
|
-
# Step 1: Extract ZIP
|
|
164
|
+
# Step 1: Extract ZIP (with Zip Slip protection)
|
|
165
165
|
info_logger("Extracting bundle...")
|
|
166
166
|
with zipfile.ZipFile(bundle_path, 'r') as zip_ref:
|
|
167
|
+
for entry in zip_ref.namelist():
|
|
168
|
+
resolved = (temp_path / entry).resolve()
|
|
169
|
+
if not str(resolved).startswith(str(temp_path.resolve())):
|
|
170
|
+
return False, f"Zip Slip detected: entry '{entry}' escapes target directory"
|
|
167
171
|
zip_ref.extractall(temp_path)
|
|
168
172
|
|
|
169
173
|
# Step 2: Validate bundle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# src/codegraphcontext/tools/code_finder.py
|
|
2
2
|
import logging
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
from typing import Any, Dict, List, Literal, Optional
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
@@ -348,7 +348,6 @@ class CodeFinder:
|
|
|
348
348
|
def what_does_function_call(self, function_name: str, path: Optional[str] = None, repo_path: Optional[str] = None) -> List[Dict]:
|
|
349
349
|
"""Find what functions a specific function calls using CALLS relationships"""
|
|
350
350
|
with self.driver.session() as session:
|
|
351
|
-
repo_filter = "AND called.path STARTS WITH $repo_path" if repo_path else ""
|
|
352
351
|
if path:
|
|
353
352
|
# Convert path to absolute path
|
|
354
353
|
absolute_file_path = str(Path(path).resolve())
|
|
@@ -450,16 +449,16 @@ class CodeFinder:
|
|
|
450
449
|
def find_class_hierarchy(self, class_name: str, path: Optional[str] = None, repo_path: Optional[str] = None) -> Dict[str, Any]:
|
|
451
450
|
"""Find class inheritance relationships using INHERITS relationships"""
|
|
452
451
|
with self.driver.session() as session:
|
|
453
|
-
repo_filter = "
|
|
452
|
+
repo_filter = "AND parent.path STARTS WITH $repo_path" if repo_path else ""
|
|
454
453
|
if path:
|
|
455
454
|
match_clause = "MATCH (child:Class {name: $class_name, path: $path})"
|
|
456
455
|
else:
|
|
457
456
|
match_clause = "MATCH (child:Class {name: $class_name})"
|
|
458
457
|
|
|
459
458
|
parents_query = f"""
|
|
460
|
-
{
|
|
459
|
+
{match_clause}
|
|
461
460
|
MATCH (child)-[:INHERITS]->(parent:Class)
|
|
462
|
-
WHERE 1=1 {
|
|
461
|
+
WHERE 1=1 {repo_filter}
|
|
463
462
|
OPTIONAL MATCH (parent_file:File)-[:CONTAINS]->(parent)
|
|
464
463
|
RETURN DISTINCT
|
|
465
464
|
parent.name as parent_class,
|
|
@@ -471,11 +470,11 @@ class CodeFinder:
|
|
|
471
470
|
"""
|
|
472
471
|
parents_result = session.run(parents_query, class_name=class_name, path=path, repo_path=repo_path)
|
|
473
472
|
|
|
474
|
-
repo_filter_child = "
|
|
473
|
+
repo_filter_child = "AND grandchild.path STARTS WITH $repo_path" if repo_path else ""
|
|
475
474
|
children_query = f"""
|
|
476
|
-
{
|
|
475
|
+
{match_clause}
|
|
477
476
|
MATCH (grandchild:Class)-[:INHERITS]->(child)
|
|
478
|
-
WHERE 1=1 {
|
|
477
|
+
WHERE 1=1 {repo_filter_child}
|
|
479
478
|
OPTIONAL MATCH (child_file:File)-[:CONTAINS]->(grandchild)
|
|
480
479
|
RETURN DISTINCT
|
|
481
480
|
grandchild.name as child_class,
|
|
@@ -513,10 +512,10 @@ class CodeFinder:
|
|
|
513
512
|
def find_function_overrides(self, function_name: str, repo_path: Optional[str] = None) -> List[Dict]:
|
|
514
513
|
"""Find all implementations of a function across different classes"""
|
|
515
514
|
with self.driver.session() as session:
|
|
516
|
-
repo_filter = "
|
|
515
|
+
repo_filter = "AND class.path STARTS WITH $repo_path" if repo_path else ""
|
|
517
516
|
result = session.run(f"""
|
|
518
517
|
MATCH (class:Class)-[:CONTAINS]->(func:Function {{name: $function_name}})
|
|
519
|
-
WHERE 1=1 {
|
|
518
|
+
WHERE 1=1 {repo_filter}
|
|
520
519
|
OPTIONAL MATCH (file:File)-[:CONTAINS]->(class)
|
|
521
520
|
RETURN DISTINCT
|
|
522
521
|
class.name as class_name,
|
|
@@ -821,69 +820,69 @@ class CodeFinder:
|
|
|
821
820
|
"instances": variable_instances.data()
|
|
822
821
|
}
|
|
823
822
|
|
|
824
|
-
def analyze_code_relationships(self, query_type: str, target: str, context: Optional[str] = None) -> Dict[str, Any]:
|
|
823
|
+
def analyze_code_relationships(self, query_type: str, target: str, context: Optional[str] = None, repo_path: Optional[str] = None) -> Dict[str, Any]:
|
|
825
824
|
"""Main method to analyze different types of code relationships with fixed return types"""
|
|
826
825
|
query_type = query_type.lower().strip()
|
|
827
826
|
|
|
828
827
|
try:
|
|
829
828
|
if query_type == "find_callers":
|
|
830
|
-
results = self.who_calls_function(target, context)
|
|
829
|
+
results = self.who_calls_function(target, context, repo_path=repo_path)
|
|
831
830
|
return {
|
|
832
831
|
"query_type": "find_callers", "target": target, "context": context, "results": results,
|
|
833
832
|
"summary": f"Found {len(results)} functions that call '{target}'"
|
|
834
833
|
}
|
|
835
834
|
|
|
836
835
|
elif query_type == "find_callees":
|
|
837
|
-
results = self.what_does_function_call(target, context)
|
|
836
|
+
results = self.what_does_function_call(target, context, repo_path=repo_path)
|
|
838
837
|
return {
|
|
839
838
|
"query_type": "find_callees", "target": target, "context": context, "results": results,
|
|
840
839
|
"summary": f"Function '{target}' calls {len(results)} other functions"
|
|
841
840
|
}
|
|
842
841
|
|
|
843
842
|
elif query_type == "find_importers":
|
|
844
|
-
results = self.who_imports_module(target)
|
|
843
|
+
results = self.who_imports_module(target, repo_path=repo_path)
|
|
845
844
|
return {
|
|
846
845
|
"query_type": "find_importers", "target": target, "results": results,
|
|
847
846
|
"summary": f"Found {len(results)} files that import '{target}'"
|
|
848
847
|
}
|
|
849
848
|
|
|
850
849
|
elif query_type == "find_functions_by_argument":
|
|
851
|
-
results = self.find_functions_by_argument(target, context)
|
|
850
|
+
results = self.find_functions_by_argument(target, context, repo_path=repo_path)
|
|
852
851
|
return {
|
|
853
852
|
"query_type": "find_functions_by_argument", "target": target, "context": context, "results": results,
|
|
854
853
|
"summary": f"Found {len(results)} functions that take '{target}' as an argument"
|
|
855
854
|
}
|
|
856
855
|
|
|
857
856
|
elif query_type == "find_functions_by_decorator":
|
|
858
|
-
results = self.find_functions_by_decorator(target, context)
|
|
857
|
+
results = self.find_functions_by_decorator(target, context, repo_path=repo_path)
|
|
859
858
|
return {
|
|
860
859
|
"query_type": "find_functions_by_decorator", "target": target, "context": context, "results": results,
|
|
861
860
|
"summary": f"Found {len(results)} functions decorated with '{target}'"
|
|
862
861
|
}
|
|
863
862
|
|
|
864
863
|
elif query_type in ["who_modifies", "modifies", "mutations", "changes", "variable_usage"]:
|
|
865
|
-
results = self.who_modifies_variable(target)
|
|
864
|
+
results = self.who_modifies_variable(target, repo_path=repo_path)
|
|
866
865
|
return {
|
|
867
866
|
"query_type": "who_modifies", "target": target, "results": results,
|
|
868
867
|
"summary": f"Found {len(results)} containers that hold variable '{target}'"
|
|
869
868
|
}
|
|
870
869
|
|
|
871
870
|
elif query_type in ["class_hierarchy", "inheritance", "extends"]:
|
|
872
|
-
results = self.find_class_hierarchy(target, context)
|
|
871
|
+
results = self.find_class_hierarchy(target, context, repo_path=repo_path)
|
|
873
872
|
return {
|
|
874
873
|
"query_type": "class_hierarchy", "target": target, "results": results,
|
|
875
874
|
"summary": f"Class '{target}' has {len(results['parent_classes'])} parents, {len(results['child_classes'])} children, and {len(results['methods'])} methods"
|
|
876
875
|
}
|
|
877
876
|
|
|
878
877
|
elif query_type in ["overrides", "implementations", "polymorphism"]:
|
|
879
|
-
results = self.find_function_overrides(target)
|
|
878
|
+
results = self.find_function_overrides(target, repo_path=repo_path)
|
|
880
879
|
return {
|
|
881
880
|
"query_type": "overrides", "target": target, "results": results,
|
|
882
881
|
"summary": f"Found {len(results)} implementations of function '{target}'"
|
|
883
882
|
}
|
|
884
883
|
|
|
885
884
|
elif query_type in ["dead_code", "unused", "unreachable"]:
|
|
886
|
-
results = self.find_dead_code()
|
|
885
|
+
results = self.find_dead_code(repo_path=repo_path)
|
|
887
886
|
return {
|
|
888
887
|
"query_type": "dead_code", "results": results,
|
|
889
888
|
"summary": f"Found {len(results['potentially_unused_functions'])} potentially unused functions"
|
|
@@ -891,21 +890,21 @@ class CodeFinder:
|
|
|
891
890
|
|
|
892
891
|
elif query_type == "find_complexity":
|
|
893
892
|
limit = int(context) if context and context.isdigit() else 10
|
|
894
|
-
results = self.find_most_complex_functions(limit)
|
|
893
|
+
results = self.find_most_complex_functions(limit, repo_path=repo_path)
|
|
895
894
|
return {
|
|
896
895
|
"query_type": "find_complexity", "limit": limit, "results": results,
|
|
897
896
|
"summary": f"Found the top {len(results)} most complex functions"
|
|
898
897
|
}
|
|
899
898
|
|
|
900
899
|
elif query_type == "find_all_callers":
|
|
901
|
-
results = self.find_all_callers(target, context)
|
|
900
|
+
results = self.find_all_callers(target, context, repo_path=repo_path)
|
|
902
901
|
return {
|
|
903
902
|
"query_type": "find_all_callers", "target": target, "context": context, "results": results,
|
|
904
903
|
"summary": f"Found {len(results)} direct and indirect callers of '{target}'"
|
|
905
904
|
}
|
|
906
905
|
|
|
907
906
|
elif query_type == "find_all_callees":
|
|
908
|
-
results = self.find_all_callees(target, context)
|
|
907
|
+
results = self.find_all_callees(target, context, repo_path=repo_path)
|
|
909
908
|
return {
|
|
910
909
|
"query_type": "find_all_callees", "target": target, "context": context, "results": results,
|
|
911
910
|
"summary": f"Found {len(results)} direct and indirect callees of '{target}'"
|
|
@@ -916,7 +915,7 @@ class CodeFinder:
|
|
|
916
915
|
start_func, end_func = target.split('->', 1)
|
|
917
916
|
# max_depth can be passed as context, default to 5 if not provided or invalid
|
|
918
917
|
max_depth = int(context) if context and context.isdigit() else 5
|
|
919
|
-
results = self.find_function_call_chain(start_func.strip(), end_func.strip(), max_depth)
|
|
918
|
+
results = self.find_function_call_chain(start_func.strip(), end_func.strip(), max_depth, repo_path=repo_path)
|
|
920
919
|
return {
|
|
921
920
|
"query_type": "call_chain", "target": target, "results": results,
|
|
922
921
|
"summary": f"Found {len(results)} call chains from '{start_func.strip()}' to '{end_func.strip()}' (max depth: {max_depth})"
|
|
@@ -928,14 +927,14 @@ class CodeFinder:
|
|
|
928
927
|
}
|
|
929
928
|
|
|
930
929
|
elif query_type in ["module_deps", "module_dependencies", "module_usage"]:
|
|
931
|
-
results = self.find_module_dependencies(target)
|
|
930
|
+
results = self.find_module_dependencies(target, repo_path=repo_path)
|
|
932
931
|
return {
|
|
933
932
|
"query_type": "module_dependencies", "target": target, "results": results,
|
|
934
933
|
"summary": f"Module '{target}' is imported by {len(results['imported_by_files'])} files"
|
|
935
934
|
}
|
|
936
935
|
|
|
937
936
|
elif query_type in ["variable_scope", "var_scope", "variable_usage_scope"]:
|
|
938
|
-
results = self.find_variable_usage_scope(target)
|
|
937
|
+
results = self.find_variable_usage_scope(target, repo_path=repo_path)
|
|
939
938
|
return {
|
|
940
939
|
"query_type": "variable_scope", "target": target, "results": results,
|
|
941
940
|
"summary": f"Variable '{target}' has {len(results['instances'])} instances across different scopes"
|
|
@@ -14,7 +14,20 @@ from ..utils.debug_log import debug_log, info_logger, error_logger, warning_logg
|
|
|
14
14
|
from tree_sitter import Language, Parser
|
|
15
15
|
from ..utils.tree_sitter_manager import get_tree_sitter_manager
|
|
16
16
|
from ..cli.config_manager import get_config_value
|
|
17
|
-
|
|
17
|
+
import fnmatch
|
|
18
|
+
|
|
19
|
+
DEFAULT_IGNORE_PATTERNS = [
|
|
20
|
+
"*.png",
|
|
21
|
+
"*.jpg",
|
|
22
|
+
"*.jpeg",
|
|
23
|
+
"*.gif",
|
|
24
|
+
"*.svg",
|
|
25
|
+
"*.mp4",
|
|
26
|
+
"*.mp3",
|
|
27
|
+
"*.zip",
|
|
28
|
+
"*.tar",
|
|
29
|
+
"*.gz",
|
|
30
|
+
]
|
|
18
31
|
|
|
19
32
|
class TreeSitterParser:
|
|
20
33
|
"""A generic parser wrapper for a specific language using tree-sitter."""
|
|
@@ -181,6 +194,38 @@ class GraphBuilder:
|
|
|
181
194
|
except Exception as e:
|
|
182
195
|
warning_logger(f"Schema creation warning: {e}")
|
|
183
196
|
|
|
197
|
+
@staticmethod
|
|
198
|
+
def _sanitize_props(props: Dict) -> Dict:
|
|
199
|
+
"""Return a copy of *props* with all values coerced to database-safe types.
|
|
200
|
+
|
|
201
|
+
FalkorDB and KùzuDB only accept node properties that are primitives
|
|
202
|
+
(str, int, float, bool, None) or flat lists of primitives. Complex
|
|
203
|
+
values such as tuples, dicts, or lists-of-dicts that come from language
|
|
204
|
+
parsers (e.g. C's ``detailed_args`` or Scala's tuple ``class_context``)
|
|
205
|
+
are serialized to a JSON string so the data is preserved rather than
|
|
206
|
+
being silently dropped.
|
|
207
|
+
"""
|
|
208
|
+
import json
|
|
209
|
+
|
|
210
|
+
def _is_primitive(v):
|
|
211
|
+
return isinstance(v, (str, int, float, bool)) or v is None
|
|
212
|
+
|
|
213
|
+
def _is_flat_list(v):
|
|
214
|
+
return isinstance(v, list) and all(_is_primitive(item) for item in v)
|
|
215
|
+
|
|
216
|
+
def _coerce(v):
|
|
217
|
+
if _is_primitive(v):
|
|
218
|
+
return v
|
|
219
|
+
if _is_flat_list(v):
|
|
220
|
+
return v
|
|
221
|
+
# Tuples, dicts, lists-of-dicts, nested structures → JSON string
|
|
222
|
+
try:
|
|
223
|
+
return json.dumps(v, default=str)
|
|
224
|
+
except Exception:
|
|
225
|
+
return str(v)
|
|
226
|
+
|
|
227
|
+
return {k: _coerce(v) for k, v in props.items()}
|
|
228
|
+
|
|
184
229
|
|
|
185
230
|
def _pre_scan_for_imports(self, files: list[Path]) -> dict:
|
|
186
231
|
"""Dispatches pre-scan to the correct language-specific implementation."""
|
|
@@ -381,7 +426,12 @@ class GraphBuilder:
|
|
|
381
426
|
MERGE (f)-[:CONTAINS]->(n)
|
|
382
427
|
"""
|
|
383
428
|
|
|
384
|
-
|
|
429
|
+
# Strip non-primitive fields (dicts, tuples, lists-of-dicts)
|
|
430
|
+
# before writing to the database to avoid runtime errors such as
|
|
431
|
+
# "Property values can only be of primitive types or arrays of
|
|
432
|
+
# primitive types" raised by FalkorDB / KùzuDB.
|
|
433
|
+
safe_props = self._sanitize_props(item)
|
|
434
|
+
session.run(query, path=file_path_str, name=item['name'], line_number=item['line_number'], props=safe_props)
|
|
385
435
|
|
|
386
436
|
if label == 'Function':
|
|
387
437
|
for arg_name in item.get('args', []):
|
|
@@ -1231,16 +1281,35 @@ class GraphBuilder:
|
|
|
1231
1281
|
break
|
|
1232
1282
|
curr = curr.parent
|
|
1233
1283
|
|
|
1284
|
+
spec = None
|
|
1234
1285
|
if cgcignore_path:
|
|
1235
1286
|
with open(cgcignore_path) as f:
|
|
1236
|
-
|
|
1287
|
+
user_patterns = [line.strip() for line in f.read().splitlines() if line.strip() and not line.strip().startswith('#')]
|
|
1288
|
+
ignore_patterns = DEFAULT_IGNORE_PATTERNS + user_patterns
|
|
1237
1289
|
spec = pathspec.PathSpec.from_lines('gitwildmatch', ignore_patterns)
|
|
1238
1290
|
else:
|
|
1239
|
-
|
|
1291
|
+
# No .cgcignore found — create one in the project root with default patterns
|
|
1292
|
+
# so the user can see and customize what's being ignored
|
|
1293
|
+
project_root = path.resolve() if path.is_dir() else path.resolve().parent
|
|
1294
|
+
new_cgcignore = project_root / ".cgcignore"
|
|
1295
|
+
try:
|
|
1296
|
+
cgcignore_content = "# Auto-generated by CodeGraphContext\n"
|
|
1297
|
+
cgcignore_content += "# Default ignore patterns for binary/media files\n"
|
|
1298
|
+
cgcignore_content += "# Add your own patterns below\n\n"
|
|
1299
|
+
cgcignore_content += "\n".join(DEFAULT_IGNORE_PATTERNS) + "\n"
|
|
1300
|
+
new_cgcignore.write_text(cgcignore_content)
|
|
1301
|
+
info_logger(f"Created default .cgcignore at {new_cgcignore}")
|
|
1302
|
+
except OSError as e:
|
|
1303
|
+
warning_logger(f"Could not create .cgcignore at {new_cgcignore}: {e}")
|
|
1304
|
+
spec = pathspec.PathSpec.from_lines('gitwildmatch', DEFAULT_IGNORE_PATTERNS)
|
|
1240
1305
|
|
|
1241
1306
|
supported_extensions = self.parsers.keys()
|
|
1242
1307
|
all_files = path.rglob("*") if path.is_dir() else [path]
|
|
1243
|
-
|
|
1308
|
+
|
|
1309
|
+
# Previously only files with supported extensions were indexed.
|
|
1310
|
+
# Updated to include all files so that unsupported file types
|
|
1311
|
+
# can still be represented as minimal File nodes in the graph.
|
|
1312
|
+
files = [f for f in all_files if f.is_file()]
|
|
1244
1313
|
|
|
1245
1314
|
# Filter default ignored directories
|
|
1246
1315
|
ignore_dirs_str = get_config_value("IGNORE_DIRS") or ""
|
|
@@ -1291,10 +1360,28 @@ class GraphBuilder:
|
|
|
1291
1360
|
self.job_manager.update_job(job_id, current_file=str(file))
|
|
1292
1361
|
repo_path = path.resolve() if path.is_dir() else file.parent.resolve()
|
|
1293
1362
|
file_data = self.parse_file(repo_path, file, is_dependency)
|
|
1363
|
+
# Previously only files with supported extensions were indexed.
|
|
1364
|
+
# Updated to include all files so that unsupported file types
|
|
1365
|
+
# can still be represented as minimal File nodes in the graph.
|
|
1294
1366
|
if "error" not in file_data:
|
|
1295
|
-
|
|
1367
|
+
try:
|
|
1368
|
+
self.add_file_to_graph(file_data, repo_name, imports_map)
|
|
1369
|
+
except Exception as file_err:
|
|
1370
|
+
# Re-raise with the offending file path so the user
|
|
1371
|
+
# can identify which source file triggered the error.
|
|
1372
|
+
raise RuntimeError(
|
|
1373
|
+
f"{file_err} (while indexing file: {file})"
|
|
1374
|
+
) from file_err
|
|
1296
1375
|
all_file_data.append(file_data)
|
|
1376
|
+
|
|
1377
|
+
# Previously only files with supported extensions were indexed.
|
|
1378
|
+
# Updated to include all files so that unsupported file types
|
|
1379
|
+
# can still be represented as minimal File nodes in the graph.
|
|
1380
|
+
else:
|
|
1381
|
+
# create minimal node if parser not available
|
|
1382
|
+
self.add_minimal_file_node(file, repo_path, is_dependency)
|
|
1297
1383
|
processed_count += 1
|
|
1384
|
+
|
|
1298
1385
|
if job_id:
|
|
1299
1386
|
self.job_manager.update_job(job_id, processed_files=processed_count)
|
|
1300
1387
|
await asyncio.sleep(0.01)
|
|
@@ -1318,3 +1405,67 @@ class GraphBuilder:
|
|
|
1318
1405
|
self.job_manager.update_job(
|
|
1319
1406
|
job_id, status=status, end_time=datetime.now(), errors=[str(e)]
|
|
1320
1407
|
)
|
|
1408
|
+
|
|
1409
|
+
# Create a minimal File node for unsupported file types.
|
|
1410
|
+
# These files do not contain parsed entities but should still
|
|
1411
|
+
# appear in the repository graph as requested in issue #707.
|
|
1412
|
+
def add_minimal_file_node(self, file_path: Path, repo_path: Path, is_dependency: bool = False):
|
|
1413
|
+
|
|
1414
|
+
file_path_str = str(file_path.resolve())
|
|
1415
|
+
file_name = file_path.name
|
|
1416
|
+
repo_name = repo_path.name
|
|
1417
|
+
repo_path_str = str(repo_path.resolve())
|
|
1418
|
+
|
|
1419
|
+
with self.driver.session() as session:
|
|
1420
|
+
|
|
1421
|
+
session.run(
|
|
1422
|
+
"""
|
|
1423
|
+
MERGE (r:Repository {path: $repo_path})
|
|
1424
|
+
SET r.name = $repo_name
|
|
1425
|
+
""",
|
|
1426
|
+
repo_path=repo_path_str,
|
|
1427
|
+
repo_name=repo_name
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
session.run(
|
|
1431
|
+
"""
|
|
1432
|
+
MERGE (f:File {path: $file_path})
|
|
1433
|
+
SET f.name = $file_name,
|
|
1434
|
+
f.is_dependency = $is_dependency
|
|
1435
|
+
""",
|
|
1436
|
+
file_path=file_path_str,
|
|
1437
|
+
file_name=file_name,
|
|
1438
|
+
is_dependency=is_dependency
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1441
|
+
# Establish directory structure
|
|
1442
|
+
file_path_obj = Path(file_path_str)
|
|
1443
|
+
repo_path_obj = Path(repo_path_str)
|
|
1444
|
+
try:
|
|
1445
|
+
relative_path_to_file = file_path_obj.relative_to(repo_path_obj)
|
|
1446
|
+
except ValueError:
|
|
1447
|
+
# Fallback if not relative
|
|
1448
|
+
relative_path_to_file = Path(file_path_obj.name)
|
|
1449
|
+
|
|
1450
|
+
parent_path = repo_path_str
|
|
1451
|
+
parent_label = 'Repository'
|
|
1452
|
+
|
|
1453
|
+
for part in relative_path_to_file.parts[:-1]:
|
|
1454
|
+
current_path = Path(parent_path) / part
|
|
1455
|
+
current_path_str = str(current_path)
|
|
1456
|
+
|
|
1457
|
+
session.run(f"""
|
|
1458
|
+
MATCH (p:{parent_label} {{path: $parent_path}})
|
|
1459
|
+
MERGE (d:Directory {{path: $current_path}})
|
|
1460
|
+
SET d.name = $part
|
|
1461
|
+
MERGE (p)-[:CONTAINS]->(d)
|
|
1462
|
+
""", parent_path=parent_path, current_path=current_path_str, part=part)
|
|
1463
|
+
|
|
1464
|
+
parent_path = current_path_str
|
|
1465
|
+
parent_label = 'Directory'
|
|
1466
|
+
|
|
1467
|
+
session.run(f"""
|
|
1468
|
+
MATCH (p:{parent_label} {{path: $parent_path}})
|
|
1469
|
+
MATCH (f:File {{path: $file_path}})
|
|
1470
|
+
MERGE (p)-[:CONTAINS]->(f)
|
|
1471
|
+
""", parent_path=parent_path, file_path=file_path_str)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# src/codegraphcontext/tools/package_resolver.py
|
|
2
|
-
import importlib
|
|
2
|
+
import importlib.util
|
|
3
3
|
import stdlibs
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import subprocess
|
|
@@ -10,25 +10,27 @@ from ..utils.debug_log import debug_log
|
|
|
10
10
|
def _get_python_package_path(package_name: str) -> Optional[str]:
|
|
11
11
|
"""
|
|
12
12
|
Finds the local installation path of a Python package.
|
|
13
|
+
Uses importlib.util.find_spec() to locate the module without executing its code.
|
|
13
14
|
"""
|
|
14
15
|
try:
|
|
15
16
|
debug_log(f"Getting local path for Python package: {package_name}")
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
|
|
17
|
+
spec = importlib.util.find_spec(package_name)
|
|
18
|
+
if spec is None:
|
|
19
|
+
return None
|
|
20
|
+
if spec.origin and spec.origin != "frozen":
|
|
21
|
+
module_file = Path(spec.origin)
|
|
19
22
|
if module_file.name == '__init__.py':
|
|
20
23
|
return str(module_file.parent)
|
|
21
24
|
elif package_name in stdlibs.module_names:
|
|
22
25
|
return str(module_file)
|
|
23
26
|
else:
|
|
24
27
|
return str(module_file.parent)
|
|
25
|
-
elif
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return str(Path(str(module.__path__)))
|
|
28
|
+
elif spec.submodule_search_locations:
|
|
29
|
+
locations = list(spec.submodule_search_locations)
|
|
30
|
+
if locations:
|
|
31
|
+
return str(Path(locations[0]))
|
|
30
32
|
return None
|
|
31
|
-
except
|
|
33
|
+
except (ModuleNotFoundError, ValueError):
|
|
32
34
|
return None
|
|
33
35
|
except Exception as e:
|
|
34
36
|
debug_log(f"Error getting local path for {package_name}: {e}")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
|
|
2
|
+
from fastapi import FastAPI, HTTPException, Query, Request
|
|
3
|
+
from fastapi.staticfiles import StaticFiles
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
from fastapi.responses import HTMLResponse, FileResponse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import uvicorn
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
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):
|
|
36
|
+
if not db_manager:
|
|
37
|
+
raise HTTPException(status_code=500, detail="Database not initialized")
|
|
38
|
+
|
|
39
|
+
def get_eid(element):
|
|
40
|
+
if not element: return None
|
|
41
|
+
if isinstance(element, (int, str)):
|
|
42
|
+
return str(element)
|
|
43
|
+
# Try various ways to get ID (Neo4j, FalkorDB, etc.)
|
|
44
|
+
for attr in ['element_id', 'id', '_id']:
|
|
45
|
+
if hasattr(element, attr):
|
|
46
|
+
val = getattr(element, attr)
|
|
47
|
+
if val is not None: return str(val)
|
|
48
|
+
return str(id(element))
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
nodes_dict = {}
|
|
52
|
+
edges = []
|
|
53
|
+
|
|
54
|
+
with db_manager.get_driver().session() as session:
|
|
55
|
+
if repo_path:
|
|
56
|
+
repo_path = str(Path(repo_path).resolve())
|
|
57
|
+
# Optimized subgraph query
|
|
58
|
+
query = """
|
|
59
|
+
MATCH (r:Repository {path: $repo_path})
|
|
60
|
+
OPTIONAL MATCH (r)-[:CONTAINS*0..]->(n)
|
|
61
|
+
WITH DISTINCT n
|
|
62
|
+
WHERE n IS NOT NULL
|
|
63
|
+
OPTIONAL MATCH (n)-[rel]->(m)
|
|
64
|
+
RETURN n, rel, m
|
|
65
|
+
"""
|
|
66
|
+
result = session.run(query, repo_path=repo_path)
|
|
67
|
+
else:
|
|
68
|
+
query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 5000"
|
|
69
|
+
result = session.run(query)
|
|
70
|
+
|
|
71
|
+
for record in result:
|
|
72
|
+
for key in ['n', 'm']:
|
|
73
|
+
node = record[key]
|
|
74
|
+
if node:
|
|
75
|
+
eid = get_eid(node)
|
|
76
|
+
if eid not in nodes_dict:
|
|
77
|
+
# FalkorDB / Neo4j labels compatibility
|
|
78
|
+
labels = []
|
|
79
|
+
if hasattr(node, 'labels'):
|
|
80
|
+
labels = list(node.labels)
|
|
81
|
+
|
|
82
|
+
# FalkorDB / Neo4j properties compatibility
|
|
83
|
+
props = {}
|
|
84
|
+
if hasattr(node, 'properties'):
|
|
85
|
+
props = node.properties
|
|
86
|
+
elif hasattr(node, 'items'):
|
|
87
|
+
props = dict(node.items())
|
|
88
|
+
|
|
89
|
+
nodes_dict[eid] = {
|
|
90
|
+
"id": eid,
|
|
91
|
+
"label": props.get('name', props.get('label', 'Unknown')),
|
|
92
|
+
"type": labels[0].lower() if labels else "default",
|
|
93
|
+
"file": props.get('path', ''),
|
|
94
|
+
"properties": props
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
rel = record['rel']
|
|
98
|
+
if rel:
|
|
99
|
+
rid = get_eid(rel)
|
|
100
|
+
|
|
101
|
+
# FalkorDB / Neo4j compatibility for source/target nodes
|
|
102
|
+
start_node = getattr(rel, 'start_node', getattr(rel, 'src_node', None))
|
|
103
|
+
end_node = getattr(rel, 'end_node', getattr(rel, 'dest_node', None))
|
|
104
|
+
|
|
105
|
+
source = get_eid(start_node)
|
|
106
|
+
target = get_eid(end_node)
|
|
107
|
+
|
|
108
|
+
if source and target:
|
|
109
|
+
# relationship type/relation
|
|
110
|
+
rel_type = "related"
|
|
111
|
+
if hasattr(rel, 'type'):
|
|
112
|
+
rel_type = rel.type
|
|
113
|
+
elif hasattr(rel, 'relation'):
|
|
114
|
+
rel_type = rel.relation
|
|
115
|
+
|
|
116
|
+
edges.append({
|
|
117
|
+
"id": rid,
|
|
118
|
+
"source": source,
|
|
119
|
+
"target": target,
|
|
120
|
+
"type": str(rel_type).lower()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"nodes": list(nodes_dict.values()),
|
|
125
|
+
"edges": edges,
|
|
126
|
+
"files": {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
debug_log(f"Error fetching graph: {str(e)}")
|
|
131
|
+
# Print stack trace for debugging if possible
|
|
132
|
+
import traceback
|
|
133
|
+
traceback.print_exc()
|
|
134
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
135
|
+
|
|
136
|
+
@app.get("/api/file")
|
|
137
|
+
async def get_file(path: str):
|
|
138
|
+
file_path = Path(path)
|
|
139
|
+
if not file_path.exists():
|
|
140
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
144
|
+
return {"content": f.read()}
|
|
145
|
+
except Exception as e:
|
|
146
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
147
|
+
|
|
148
|
+
# SPA fallback handler
|
|
149
|
+
@app.get("/{full_path:path}")
|
|
150
|
+
async def spa_fallback(request: Request, full_path: str):
|
|
151
|
+
global _static_dir
|
|
152
|
+
if not _static_dir:
|
|
153
|
+
return HTMLResponse("Static directory not configured", status_code=500)
|
|
154
|
+
|
|
155
|
+
# Filesystem path
|
|
156
|
+
file_path = Path(_static_dir) / full_path
|
|
157
|
+
|
|
158
|
+
# If the file exists and is a file, serve it normally (handled by StaticFiles usually,
|
|
159
|
+
# but we need this for routes that don't match StaticFiles mount)
|
|
160
|
+
if file_path.exists() and file_path.is_file():
|
|
161
|
+
return FileResponse(file_path)
|
|
162
|
+
|
|
163
|
+
# Otherwise serve index.html
|
|
164
|
+
index_path = Path(_static_dir) / "index.html"
|
|
165
|
+
if index_path.exists():
|
|
166
|
+
return FileResponse(index_path)
|
|
167
|
+
|
|
168
|
+
return HTMLResponse("Not Found", status_code=404)
|
|
169
|
+
|
|
170
|
+
def run_server(host: str = "127.0.0.1", port: int = 8000, static_dir: Optional[str] = None):
|
|
171
|
+
global _static_dir
|
|
172
|
+
_static_dir = static_dir
|
|
173
|
+
if static_dir:
|
|
174
|
+
# Mount API first
|
|
175
|
+
# We don't mount "/" with StaticFiles because we use spa_fallback for all routes
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
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.2
|
|
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
|
|
@@ -46,12 +46,13 @@ 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
48
|
Requires-Dist: pyyaml
|
|
49
|
-
Requires-Dist: pytest
|
|
50
49
|
Requires-Dist: nbformat
|
|
51
50
|
Requires-Dist: nbconvert>=7.16.6
|
|
52
51
|
Requires-Dist: pathspec>=0.12.1
|
|
53
52
|
Requires-Dist: falkordb>=0.1.0
|
|
54
53
|
Requires-Dist: falkordblite>=0.1.0; sys_platform != "win32" and python_version >= "3.12"
|
|
54
|
+
Requires-Dist: fastapi>=0.100.0
|
|
55
|
+
Requires-Dist: uvicorn>=0.22.0
|
|
55
56
|
Provides-Extra: parsing
|
|
56
57
|
Requires-Dist: tree-sitter>=0.21.0; extra == "parsing"
|
|
57
58
|
Requires-Dist: tree-sitter-language-pack>=0.6.0; extra == "parsing"
|
|
@@ -63,6 +64,18 @@ Dynamic: license-file
|
|
|
63
64
|
|
|
64
65
|
# 🏗️ CodeGraphContext (CGC)
|
|
65
66
|
|
|
67
|
+
**Turn code repositories into a queryable graph for AI agents.**
|
|
68
|
+
|
|
69
|
+
🌐 **Languages:**
|
|
70
|
+
- 🇬🇧 [English](README.md)
|
|
71
|
+
- 🇨🇳 [中文](README.zh-CN.md)
|
|
72
|
+
- 🇰🇷 [한국어](README.kor.md)
|
|
73
|
+
- 🇯🇵 日本語 (Soon)
|
|
74
|
+
- 🇷🇺 Русский (Soon)
|
|
75
|
+
- 🇪🇸 Español (Soon)
|
|
76
|
+
|
|
77
|
+
🌍 **Help translate CodeGraphContext to your language by raising an issue & PR on https://github.com/Shashankss1205/CodeGraphContext/issues!**
|
|
78
|
+
|
|
66
79
|
<p align="center">
|
|
67
80
|
<br>
|
|
68
81
|
<b>Bridge the gap between deep code graphs and AI context.</b>
|
|
@@ -147,7 +160,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
|
|
|
147
160
|
---
|
|
148
161
|
|
|
149
162
|
## Project Details
|
|
150
|
-
- **Version:** 0.3.
|
|
163
|
+
- **Version:** 0.3.2
|
|
151
164
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
152
165
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
153
166
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -352,7 +365,7 @@ cgc watch .
|
|
|
352
365
|
cgc help
|
|
353
366
|
```
|
|
354
367
|
|
|
355
|
-
**See the full [CLI Commands Guide](
|
|
368
|
+
**See the full [CLI Commands Guide](docs/CLI_COMPLETE_REFERENCE.md) for all available commands and usage scenarios.**
|
|
356
369
|
|
|
357
370
|
### 🎨 Premium Interactive Visualization
|
|
358
371
|
CodeGraphContext can generate stunning, interactive knowledge graphs of your code. Unlike static diagrams, these are premium web-based explorers:
|
|
@@ -4,16 +4,16 @@ 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=zlcPv0cReflgTpTM2Nj0jCB3pHvVRCqfvyAM-hCEm6c,36598
|
|
8
8
|
codegraphcontext/cli/config_manager.py,sha256=MK7GMGZ4hd8-ZOShXBd_KDtNqHQ3ngdbef5KM_naPrQ,15899
|
|
9
|
-
codegraphcontext/cli/main.py,sha256=
|
|
9
|
+
codegraphcontext/cli/main.py,sha256=D-nlcT90ah3rHQ_h7TIq451EA4WN_HxOmXKm9EY2Hnw,86497
|
|
10
10
|
codegraphcontext/cli/registry_commands.py,sha256=30rJm4SeS0n1jax4JVuhuv4zYLzyMmlHcCSnsDDhgc8,19964
|
|
11
11
|
codegraphcontext/cli/setup_macos.py,sha256=Xjlv_9jk9qv8Gh7stpH1pvlalzC0Fg176y7jc5G1zh0,3575
|
|
12
12
|
codegraphcontext/cli/setup_wizard.py,sha256=yVXKqvAtUM0UC4tUreiS6C5utJbBO5Et2MiPUPWit9s,41393
|
|
13
13
|
codegraphcontext/cli/visualizer.py,sha256=rZOLPx44wlaxVXGLR6uOaiL2GaQvxEnpyDQ4tjRDVC8,45750
|
|
14
14
|
codegraphcontext/core/__init__.py,sha256=r-dC2b8hn5e4ZBVfWnCcBYMTQVcayxG5-eA5Do6uSM4,7278
|
|
15
15
|
codegraphcontext/core/bundle_registry.py,sha256=kLvTLEl99QwfyAw8a7-4hqconLCtS64G9ECPVdfVBsM,7476
|
|
16
|
-
codegraphcontext/core/cgc_bundle.py,sha256=
|
|
16
|
+
codegraphcontext/core/cgc_bundle.py,sha256=lxaImBnEJMiscrGssLTgLaeEa7tY33xztjbxOhr6O5U,31686
|
|
17
17
|
codegraphcontext/core/database.py,sha256=2UL8AyE6vX5mVHg1zcD74TYEREmddhKzN7Bjy2THQwM,11435
|
|
18
18
|
codegraphcontext/core/database_falkordb.py,sha256=IKRcUwqdCR92ymX9rNAQSs49RWPxug_78utwePEcliQ,18842
|
|
19
19
|
codegraphcontext/core/database_falkordb_remote.py,sha256=aMiuJTB_-2rBUpbAmX8ElZiZrYZbOXK-On3ncgMCyhY,7118
|
|
@@ -23,9 +23,9 @@ codegraphcontext/core/jobs.py,sha256=d6v_IERdEcDlIsz3CW3p0QMp3N-6dgQICs3FZRPgPgU
|
|
|
23
23
|
codegraphcontext/core/watcher.py,sha256=42AWDv7LUE5v_HlY_Rvvlk8deDdlUBvM3hI-DMDWAOk,9813
|
|
24
24
|
codegraphcontext/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
codegraphcontext/tools/advanced_language_query_tool.py,sha256=APtC-KSkXXSrVLwiw3kgi6Q-xaqXl6skLWHPyuNh358,3670
|
|
26
|
-
codegraphcontext/tools/code_finder.py,sha256=
|
|
27
|
-
codegraphcontext/tools/graph_builder.py,sha256=
|
|
28
|
-
codegraphcontext/tools/package_resolver.py,sha256=
|
|
26
|
+
codegraphcontext/tools/code_finder.py,sha256=2wd05dMrPW8xZDBUixS4BaipNbrE4rttqSD6JYEeQM4,55552
|
|
27
|
+
codegraphcontext/tools/graph_builder.py,sha256=dUIJKRZRlC0WHINNOqHLmw20I28cf_ePti-NcGEEcnE,78728
|
|
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
|
|
31
31
|
codegraphcontext/tools/system.py,sha256=DGeavZoPxzV78wwApV4f7fdBFQRa8oeOSv5wprHSjRE,5733
|
|
@@ -71,9 +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=Ntq8l8SvAOvsOp19QKByjEwU7-5rPa_XfcGLOrUehq4,5003
|
|
74
|
-
codegraphcontext
|
|
75
|
-
codegraphcontext-0.3.
|
|
76
|
-
codegraphcontext-0.3.
|
|
77
|
-
codegraphcontext-0.3.
|
|
78
|
-
codegraphcontext-0.3.
|
|
79
|
-
codegraphcontext-0.3.
|
|
74
|
+
codegraphcontext/viz/server.py,sha256=KiB6UARhVHGJ3ANi1R8i5RKnDVwqOULS8kKng-L-6GI,6530
|
|
75
|
+
codegraphcontext-0.3.2.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
76
|
+
codegraphcontext-0.3.2.dist-info/METADATA,sha256=Bc0OVmQ-9RaxbL4YYE5bIGuqSDcRUbHMwtsrbxjjziY,21579
|
|
77
|
+
codegraphcontext-0.3.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
78
|
+
codegraphcontext-0.3.2.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
|
|
79
|
+
codegraphcontext-0.3.2.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
|
|
80
|
+
codegraphcontext-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|