codegraphcontext 0.1.27__tar.gz → 0.1.28__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.1.27/src/codegraphcontext.egg-info → codegraphcontext-0.1.28}/PKG-INFO +2 -2
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/README.md +1 -1
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/pyproject.toml +1 -1
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/main.py +305 -2
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/code_finder.py +2 -2
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/graph_builder.py +11 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/python.py +16 -0
- codegraphcontext-0.1.28/src/codegraphcontext/tools/languages/scala.py +510 -0
- codegraphcontext-0.1.28/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/utils/tree_sitter_manager.py +2 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28/src/codegraphcontext.egg-info}/PKG-INFO +2 -2
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/SOURCES.txt +2 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/LICENSE +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/MANIFEST.in +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/setup.cfg +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/__init__.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/__main__.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/__init__.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/cli_helpers.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/config_manager.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/setup_macos.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/setup_wizard.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/__init__.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/database.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/database_falkordb.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/falkor_worker.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/jobs.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/watcher.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/prompts.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/server.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/__init__.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/c.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/cpp.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/csharp.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/go.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/java.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/javascript.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/php.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/ruby.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/rust.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/typescript.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/package_resolver.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/system.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/utils/debug_log.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/requires.txt +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/top_level.txt +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_cpp_parser.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_database_validation.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_end_to_end.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_graph_indexing.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_graph_indexing_js.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_kotlin_parser.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_tree_sitter_manager.py +0 -0
- {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_typescript_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
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
|
|
@@ -91,7 +91,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
|
|
|
91
91
|

|
|
92
92
|
|
|
93
93
|
## Project Details
|
|
94
|
-
- **Version:** 0.1.
|
|
94
|
+
- **Version:** 0.1.28
|
|
95
95
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
96
96
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
97
97
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -29,7 +29,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
|
|
|
29
29
|

|
|
30
30
|
|
|
31
31
|
## Project Details
|
|
32
|
-
- **Version:** 0.1.
|
|
32
|
+
- **Version:** 0.1.28
|
|
33
33
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
34
34
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
35
35
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codegraphcontext"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.28"
|
|
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"
|
|
@@ -708,12 +708,15 @@ def find_by_name(
|
|
|
708
708
|
# Search all
|
|
709
709
|
funcs = code_finder.find_by_function_name(name, fuzzy_search=False)
|
|
710
710
|
classes = code_finder.find_by_class_name(name, fuzzy_search=False)
|
|
711
|
+
variables = code_finder.find_by_variable_name(name)
|
|
711
712
|
|
|
712
713
|
for f in funcs: f['type'] = 'Function'
|
|
713
714
|
for c in classes: c['type'] = 'Class'
|
|
715
|
+
for v in variables: v['type'] = 'Variable'
|
|
714
716
|
|
|
715
717
|
results.extend(funcs)
|
|
716
718
|
results.extend(classes)
|
|
719
|
+
results.extend(variables)
|
|
717
720
|
|
|
718
721
|
elif type.lower() == 'function':
|
|
719
722
|
results = code_finder.find_by_function_name(name, fuzzy_search=False)
|
|
@@ -723,6 +726,10 @@ def find_by_name(
|
|
|
723
726
|
results = code_finder.find_by_class_name(name, fuzzy_search=False)
|
|
724
727
|
for r in results: r['type'] = 'Class'
|
|
725
728
|
|
|
729
|
+
elif type.lower() == 'variable':
|
|
730
|
+
results = code_finder.find_by_variable_name(name)
|
|
731
|
+
for r in results: r['type'] = 'Variable'
|
|
732
|
+
|
|
726
733
|
elif type.lower() == 'file':
|
|
727
734
|
# Quick query for file
|
|
728
735
|
with db_manager.get_driver().session() as session:
|
|
@@ -780,7 +787,7 @@ def find_by_pattern(
|
|
|
780
787
|
if not case_sensitive:
|
|
781
788
|
query = """
|
|
782
789
|
MATCH (n)
|
|
783
|
-
WHERE (n:Function OR n:Class OR n:Module) AND toLower(n.name) CONTAINS toLower($pattern)
|
|
790
|
+
WHERE (n:Function OR n:Class OR n:Module OR n:Variable) AND toLower(n.name) CONTAINS toLower($pattern)
|
|
784
791
|
RETURN
|
|
785
792
|
labels(n)[0] as type,
|
|
786
793
|
n.name as name,
|
|
@@ -793,7 +800,7 @@ def find_by_pattern(
|
|
|
793
800
|
else:
|
|
794
801
|
query = """
|
|
795
802
|
MATCH (n)
|
|
796
|
-
WHERE (n:Function OR n:Class OR n:Module) AND n.name CONTAINS $pattern
|
|
803
|
+
WHERE (n:Function OR n:Class OR n:Module OR n:Variable) AND n.name CONTAINS $pattern
|
|
797
804
|
RETURN
|
|
798
805
|
labels(n)[0] as type,
|
|
799
806
|
n.name as name,
|
|
@@ -882,6 +889,194 @@ def find_by_type(
|
|
|
882
889
|
finally:
|
|
883
890
|
db_manager.close_driver()
|
|
884
891
|
|
|
892
|
+
@find_app.command("variable")
|
|
893
|
+
def find_by_variable(
|
|
894
|
+
name: str = typer.Argument(..., help="Variable name to search for")
|
|
895
|
+
):
|
|
896
|
+
"""
|
|
897
|
+
Find variables by name.
|
|
898
|
+
|
|
899
|
+
Examples:
|
|
900
|
+
cgc find variable MAX_RETRIES
|
|
901
|
+
cgc find variable config
|
|
902
|
+
"""
|
|
903
|
+
_load_credentials()
|
|
904
|
+
services = _initialize_services()
|
|
905
|
+
if not all(services):
|
|
906
|
+
return
|
|
907
|
+
db_manager, graph_builder, code_finder = services
|
|
908
|
+
|
|
909
|
+
try:
|
|
910
|
+
results = code_finder.find_by_variable_name(name)
|
|
911
|
+
|
|
912
|
+
if not results:
|
|
913
|
+
console.print(f"[yellow]No variables found with name '{name}'[/yellow]")
|
|
914
|
+
return
|
|
915
|
+
|
|
916
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
917
|
+
table.add_column("Name", style="cyan")
|
|
918
|
+
table.add_column("File", style="dim", overflow="fold")
|
|
919
|
+
table.add_column("Line", style="green", justify="right")
|
|
920
|
+
table.add_column("Context", style="yellow")
|
|
921
|
+
|
|
922
|
+
for res in results:
|
|
923
|
+
table.add_row(
|
|
924
|
+
res.get('name', ''),
|
|
925
|
+
res.get('file_path', ''),
|
|
926
|
+
str(res.get('line_number', '')),
|
|
927
|
+
res.get('context', '') or 'module'
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
console.print(f"[cyan]Found {len(results)} variable(s) named '{name}':[/cyan]")
|
|
931
|
+
console.print(table)
|
|
932
|
+
finally:
|
|
933
|
+
db_manager.close_driver()
|
|
934
|
+
|
|
935
|
+
@find_app.command("content")
|
|
936
|
+
def find_by_content_search(
|
|
937
|
+
query: str = typer.Argument(..., help="Text to search for in source code and docstrings")
|
|
938
|
+
):
|
|
939
|
+
"""
|
|
940
|
+
Search code content (source and docstrings) using full-text index.
|
|
941
|
+
|
|
942
|
+
Examples:
|
|
943
|
+
cgc find content "error 503"
|
|
944
|
+
cgc find content "TODO: refactor"
|
|
945
|
+
"""
|
|
946
|
+
_load_credentials()
|
|
947
|
+
services = _initialize_services()
|
|
948
|
+
if not all(services):
|
|
949
|
+
return
|
|
950
|
+
db_manager, graph_builder, code_finder = services
|
|
951
|
+
|
|
952
|
+
try:
|
|
953
|
+
try:
|
|
954
|
+
results = code_finder.find_by_content(query)
|
|
955
|
+
except Exception as e:
|
|
956
|
+
error_msg = str(e).lower()
|
|
957
|
+
if 'fulltext' in error_msg or 'db.index.fulltext' in error_msg:
|
|
958
|
+
console.print("\n[bold red]❌ Full-text search is not supported on FalkorDB[/bold red]\n")
|
|
959
|
+
console.print("[yellow]💡 You have two options:[/yellow]\n")
|
|
960
|
+
console.print(" 1. [cyan]Switch to Neo4j:[/cyan]")
|
|
961
|
+
console.print(f" [dim]cgc --database neo4j find content \"{query}\"[/dim]\n")
|
|
962
|
+
console.print(" 2. [cyan]Use pattern search instead:[/cyan]")
|
|
963
|
+
console.print(f" [dim]cgc find pattern \"{query}\"[/dim]")
|
|
964
|
+
console.print(" [dim](searches in names only, not source code)[/dim]\n")
|
|
965
|
+
return
|
|
966
|
+
else:
|
|
967
|
+
# Re-raise if it's a different error
|
|
968
|
+
raise
|
|
969
|
+
|
|
970
|
+
if not results:
|
|
971
|
+
console.print(f"[yellow]No content matches found for '{query}'[/yellow]")
|
|
972
|
+
return
|
|
973
|
+
|
|
974
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
975
|
+
table.add_column("Name", style="cyan")
|
|
976
|
+
table.add_column("Type", style="blue")
|
|
977
|
+
table.add_column("File", style="dim", overflow="fold")
|
|
978
|
+
table.add_column("Line", style="green", justify="right")
|
|
979
|
+
|
|
980
|
+
for res in results:
|
|
981
|
+
table.add_row(
|
|
982
|
+
res.get('name', ''),
|
|
983
|
+
res.get('type', 'Unknown'),
|
|
984
|
+
res.get('file_path', ''),
|
|
985
|
+
str(res.get('line_number', ''))
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
console.print(f"[cyan]Found {len(results)} content match(es) for '{query}':[/cyan]")
|
|
989
|
+
console.print(table)
|
|
990
|
+
finally:
|
|
991
|
+
db_manager.close_driver()
|
|
992
|
+
|
|
993
|
+
@find_app.command("decorator")
|
|
994
|
+
def find_by_decorator_search(
|
|
995
|
+
decorator: str = typer.Argument(..., help="Decorator name to search for"),
|
|
996
|
+
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
|
|
997
|
+
):
|
|
998
|
+
"""
|
|
999
|
+
Find functions with a specific decorator.
|
|
1000
|
+
|
|
1001
|
+
Examples:
|
|
1002
|
+
cgc find decorator app.route
|
|
1003
|
+
cgc find decorator test --file tests/test_main.py
|
|
1004
|
+
"""
|
|
1005
|
+
_load_credentials()
|
|
1006
|
+
services = _initialize_services()
|
|
1007
|
+
if not all(services):
|
|
1008
|
+
return
|
|
1009
|
+
db_manager, graph_builder, code_finder = services
|
|
1010
|
+
|
|
1011
|
+
try:
|
|
1012
|
+
results = code_finder.find_functions_by_decorator(decorator, file)
|
|
1013
|
+
|
|
1014
|
+
if not results:
|
|
1015
|
+
console.print(f"[yellow]No functions found with decorator '@{decorator}'[/yellow]")
|
|
1016
|
+
return
|
|
1017
|
+
|
|
1018
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
1019
|
+
table.add_column("Function", style="cyan")
|
|
1020
|
+
table.add_column("File", style="dim", overflow="fold")
|
|
1021
|
+
table.add_column("Line", style="green", justify="right")
|
|
1022
|
+
table.add_column("Decorators", style="yellow")
|
|
1023
|
+
|
|
1024
|
+
for res in results:
|
|
1025
|
+
decorators_str = ", ".join(res.get('decorators', []))
|
|
1026
|
+
table.add_row(
|
|
1027
|
+
res.get('function_name', ''),
|
|
1028
|
+
res.get('file_path', ''),
|
|
1029
|
+
str(res.get('line_number', '')),
|
|
1030
|
+
decorators_str
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
console.print(f"[cyan]Found {len(results)} function(s) with decorator '@{decorator}':[/cyan]")
|
|
1034
|
+
console.print(table)
|
|
1035
|
+
finally:
|
|
1036
|
+
db_manager.close_driver()
|
|
1037
|
+
|
|
1038
|
+
@find_app.command("argument")
|
|
1039
|
+
def find_by_argument_search(
|
|
1040
|
+
argument: str = typer.Argument(..., help="Argument/parameter name to search for"),
|
|
1041
|
+
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
|
|
1042
|
+
):
|
|
1043
|
+
"""
|
|
1044
|
+
Find functions that take a specific argument/parameter.
|
|
1045
|
+
|
|
1046
|
+
Examples:
|
|
1047
|
+
cgc find argument password
|
|
1048
|
+
cgc find argument user_id --file src/auth.py
|
|
1049
|
+
"""
|
|
1050
|
+
_load_credentials()
|
|
1051
|
+
services = _initialize_services()
|
|
1052
|
+
if not all(services):
|
|
1053
|
+
return
|
|
1054
|
+
db_manager, graph_builder, code_finder = services
|
|
1055
|
+
|
|
1056
|
+
try:
|
|
1057
|
+
results = code_finder.find_functions_by_argument(argument, file)
|
|
1058
|
+
|
|
1059
|
+
if not results:
|
|
1060
|
+
console.print(f"[yellow]No functions found with argument '{argument}'[/yellow]")
|
|
1061
|
+
return
|
|
1062
|
+
|
|
1063
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
1064
|
+
table.add_column("Function", style="cyan")
|
|
1065
|
+
table.add_column("File", style="dim", overflow="fold")
|
|
1066
|
+
table.add_column("Line", style="green", justify="right")
|
|
1067
|
+
|
|
1068
|
+
for res in results:
|
|
1069
|
+
table.add_row(
|
|
1070
|
+
res.get('function_name', ''),
|
|
1071
|
+
res.get('file_path', ''),
|
|
1072
|
+
str(res.get('line_number', ''))
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
console.print(f"[cyan]Found {len(results)} function(s) with argument '{argument}':[/cyan]")
|
|
1076
|
+
console.print(table)
|
|
1077
|
+
finally:
|
|
1078
|
+
db_manager.close_driver()
|
|
1079
|
+
|
|
885
1080
|
|
|
886
1081
|
# ============================================================================
|
|
887
1082
|
# ANALYZE COMMAND GROUP - Code Analysis & Relationships
|
|
@@ -1232,6 +1427,114 @@ def analyze_dead_code(
|
|
|
1232
1427
|
finally:
|
|
1233
1428
|
db_manager.close_driver()
|
|
1234
1429
|
|
|
1430
|
+
@analyze_app.command("overrides")
|
|
1431
|
+
def analyze_overrides(
|
|
1432
|
+
function_name: str = typer.Argument(..., help="Function/method name to find implementations of")
|
|
1433
|
+
):
|
|
1434
|
+
"""
|
|
1435
|
+
Find all implementations of a function across different classes.
|
|
1436
|
+
|
|
1437
|
+
Useful for finding polymorphic implementations and method overrides.
|
|
1438
|
+
|
|
1439
|
+
Example:
|
|
1440
|
+
cgc analyze overrides area
|
|
1441
|
+
cgc analyze overrides process
|
|
1442
|
+
"""
|
|
1443
|
+
_load_credentials()
|
|
1444
|
+
services = _initialize_services()
|
|
1445
|
+
if not all(services):
|
|
1446
|
+
return
|
|
1447
|
+
db_manager, graph_builder, code_finder = services
|
|
1448
|
+
|
|
1449
|
+
try:
|
|
1450
|
+
results = code_finder.find_function_overrides(function_name)
|
|
1451
|
+
|
|
1452
|
+
if not results:
|
|
1453
|
+
console.print(f"[yellow]No implementations found for function '{function_name}'[/yellow]")
|
|
1454
|
+
return
|
|
1455
|
+
|
|
1456
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
1457
|
+
table.add_column("Class", style="cyan")
|
|
1458
|
+
table.add_column("Function", style="green")
|
|
1459
|
+
table.add_column("File", style="dim", overflow="fold")
|
|
1460
|
+
table.add_column("Line", style="yellow", justify="right")
|
|
1461
|
+
|
|
1462
|
+
for res in results:
|
|
1463
|
+
table.add_row(
|
|
1464
|
+
res.get('class_name', ''),
|
|
1465
|
+
res.get('function_name', ''),
|
|
1466
|
+
res.get('class_file_path', ''),
|
|
1467
|
+
str(res.get('function_line_number', ''))
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
console.print(f"\n[bold cyan]Found {len(results)} implementation(s) of '{function_name}':[/bold cyan]")
|
|
1471
|
+
console.print(table)
|
|
1472
|
+
finally:
|
|
1473
|
+
db_manager.close_driver()
|
|
1474
|
+
|
|
1475
|
+
@analyze_app.command("variable")
|
|
1476
|
+
def analyze_variable_usage(
|
|
1477
|
+
variable_name: str = typer.Argument(..., help="Variable name to analyze")
|
|
1478
|
+
):
|
|
1479
|
+
"""
|
|
1480
|
+
Analyze where a variable is defined and used across the codebase.
|
|
1481
|
+
|
|
1482
|
+
Shows all instances of the variable and their scope (function, class, module).
|
|
1483
|
+
|
|
1484
|
+
Example:
|
|
1485
|
+
cgc analyze variable MAX_RETRIES
|
|
1486
|
+
cgc analyze variable config
|
|
1487
|
+
"""
|
|
1488
|
+
_load_credentials()
|
|
1489
|
+
services = _initialize_services()
|
|
1490
|
+
if not all(services):
|
|
1491
|
+
return
|
|
1492
|
+
db_manager, graph_builder, code_finder = services
|
|
1493
|
+
|
|
1494
|
+
try:
|
|
1495
|
+
# Get variable usage scope
|
|
1496
|
+
scope_results = code_finder.find_variable_usage_scope(variable_name)
|
|
1497
|
+
instances = scope_results.get('instances', [])
|
|
1498
|
+
|
|
1499
|
+
if not instances:
|
|
1500
|
+
console.print(f"[yellow]No instances found for variable '{variable_name}'[/yellow]")
|
|
1501
|
+
return
|
|
1502
|
+
|
|
1503
|
+
console.print(f"\n[bold cyan]Variable '{variable_name}' Usage Analysis:[/bold cyan]\n")
|
|
1504
|
+
|
|
1505
|
+
# Group by scope type
|
|
1506
|
+
by_scope = {}
|
|
1507
|
+
for inst in instances:
|
|
1508
|
+
scope_type = inst.get('scope_type', 'unknown')
|
|
1509
|
+
if scope_type not in by_scope:
|
|
1510
|
+
by_scope[scope_type] = []
|
|
1511
|
+
by_scope[scope_type].append(inst)
|
|
1512
|
+
|
|
1513
|
+
# Display by scope
|
|
1514
|
+
for scope_type, items in by_scope.items():
|
|
1515
|
+
console.print(f"[bold yellow]{scope_type.upper()} Scope ({len(items)} instance(s)):[/bold yellow]")
|
|
1516
|
+
|
|
1517
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
1518
|
+
table.add_column("Scope Name", style="cyan")
|
|
1519
|
+
table.add_column("File", style="dim", overflow="fold")
|
|
1520
|
+
table.add_column("Line", style="green", justify="right")
|
|
1521
|
+
table.add_column("Value", style="yellow")
|
|
1522
|
+
|
|
1523
|
+
for item in items:
|
|
1524
|
+
table.add_row(
|
|
1525
|
+
item.get('scope_name', ''),
|
|
1526
|
+
item.get('file_path', ''),
|
|
1527
|
+
str(item.get('line_number', '')),
|
|
1528
|
+
str(item.get('variable_value', ''))[:50] if item.get('variable_value') else '-'
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
console.print(table)
|
|
1532
|
+
console.print()
|
|
1533
|
+
|
|
1534
|
+
console.print(f"[dim]Total: {len(instances)} instance(s) across {len(by_scope)} scope type(s)[/dim]")
|
|
1535
|
+
finally:
|
|
1536
|
+
db_manager.close_driver()
|
|
1537
|
+
|
|
1235
1538
|
|
|
1236
1539
|
# ============================================================================
|
|
1237
1540
|
# QUERY COMMAND - Raw Cypher Queries
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/code_finder.py
RENAMED
|
@@ -68,12 +68,12 @@ class CodeFinder:
|
|
|
68
68
|
with self.driver.session() as session:
|
|
69
69
|
result = session.run("""
|
|
70
70
|
MATCH (v:Variable)
|
|
71
|
-
WHERE v.name CONTAINS $search_term
|
|
71
|
+
WHERE v.name CONTAINS $search_term
|
|
72
72
|
RETURN v.name as name, v.file_path as file_path, v.line_number as line_number,
|
|
73
73
|
v.value as value, v.context as context, v.is_dependency as is_dependency
|
|
74
74
|
ORDER BY v.is_dependency ASC, v.name
|
|
75
75
|
LIMIT 20
|
|
76
|
-
""", search_term=search_term
|
|
76
|
+
""", search_term=search_term)
|
|
77
77
|
|
|
78
78
|
return result.data()
|
|
79
79
|
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/graph_builder.py
RENAMED
|
@@ -64,6 +64,9 @@ class TreeSitterParser:
|
|
|
64
64
|
elif self.language_name == 'kotlin':
|
|
65
65
|
from .languages.kotlin import KotlinTreeSitterParser
|
|
66
66
|
self.language_specific_parser = KotlinTreeSitterParser(self)
|
|
67
|
+
elif self.language_name == 'scala':
|
|
68
|
+
from .languages.scala import ScalaTreeSitterParser
|
|
69
|
+
self.language_specific_parser = ScalaTreeSitterParser(self)
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
|
|
@@ -105,6 +108,8 @@ class GraphBuilder:
|
|
|
105
108
|
'.cs': TreeSitterParser('c_sharp'),
|
|
106
109
|
'.php': TreeSitterParser('php'),
|
|
107
110
|
'.kt': TreeSitterParser('kotlin'),
|
|
111
|
+
'.scala': TreeSitterParser('scala'),
|
|
112
|
+
'.sc': TreeSitterParser('scala'),
|
|
108
113
|
}
|
|
109
114
|
self.create_schema()
|
|
110
115
|
|
|
@@ -213,6 +218,12 @@ class GraphBuilder:
|
|
|
213
218
|
if '.kt' in files_by_lang:
|
|
214
219
|
from .languages import kotlin as kotlin_lang_module
|
|
215
220
|
imports_map.update(kotlin_lang_module.pre_scan_kotlin(files_by_lang['.kt'], self.parsers['.kt']))
|
|
221
|
+
if '.scala' in files_by_lang:
|
|
222
|
+
from .languages import scala as scala_lang_module
|
|
223
|
+
imports_map.update(scala_lang_module.pre_scan_scala(files_by_lang['.scala'], self.parsers['.scala']))
|
|
224
|
+
if '.sc' in files_by_lang:
|
|
225
|
+
from .languages import scala as scala_lang_module
|
|
226
|
+
imports_map.update(scala_lang_module.pre_scan_scala(files_by_lang['.sc'], self.parsers['.sc']))
|
|
216
227
|
|
|
217
228
|
return imports_map
|
|
218
229
|
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/python.py
RENAMED
|
@@ -209,11 +209,27 @@ class PythonTreeSitterParser:
|
|
|
209
209
|
for p in params_node.children:
|
|
210
210
|
arg_text = None
|
|
211
211
|
if p.type == 'identifier':
|
|
212
|
+
# Simple parameter: def foo(x)
|
|
212
213
|
arg_text = self._get_node_text(p)
|
|
213
214
|
elif p.type == 'default_parameter':
|
|
215
|
+
# Parameter with default: def foo(x=5)
|
|
214
216
|
name_node = p.child_by_field_name('name')
|
|
215
217
|
if name_node:
|
|
216
218
|
arg_text = self._get_node_text(name_node)
|
|
219
|
+
elif p.type == 'typed_parameter':
|
|
220
|
+
# Typed parameter: def foo(x: int)
|
|
221
|
+
name_node = p.child_by_field_name('name')
|
|
222
|
+
if name_node:
|
|
223
|
+
arg_text = self._get_node_text(name_node)
|
|
224
|
+
elif p.type == 'typed_default_parameter':
|
|
225
|
+
# Typed parameter with default: def foo(x: int = 5) or def foo(x: str = typer.Argument(...))
|
|
226
|
+
name_node = p.child_by_field_name('name')
|
|
227
|
+
if name_node:
|
|
228
|
+
arg_text = self._get_node_text(name_node)
|
|
229
|
+
elif p.type == 'list_splat_pattern' or p.type == 'dictionary_splat_pattern':
|
|
230
|
+
# *args or **kwargs
|
|
231
|
+
arg_text = self._get_node_text(p)
|
|
232
|
+
|
|
217
233
|
if arg_text:
|
|
218
234
|
args.append(arg_text)
|
|
219
235
|
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple, List
|
|
3
|
+
import re
|
|
4
|
+
from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger
|
|
5
|
+
from codegraphcontext.utils.tree_sitter_manager import execute_query
|
|
6
|
+
|
|
7
|
+
SCALA_QUERIES = {
|
|
8
|
+
"functions": """
|
|
9
|
+
(function_definition
|
|
10
|
+
name: (identifier) @name
|
|
11
|
+
parameters: (parameters) @params
|
|
12
|
+
) @function_node
|
|
13
|
+
""",
|
|
14
|
+
"classes": """
|
|
15
|
+
[
|
|
16
|
+
(class_definition name: (identifier) @name)
|
|
17
|
+
(object_definition name: (identifier) @name)
|
|
18
|
+
(trait_definition name: (identifier) @name)
|
|
19
|
+
] @class
|
|
20
|
+
""",
|
|
21
|
+
"imports": """
|
|
22
|
+
(import_declaration) @import
|
|
23
|
+
""",
|
|
24
|
+
"calls": """
|
|
25
|
+
(call_expression) @call_node
|
|
26
|
+
(generic_function
|
|
27
|
+
function: (identifier) @name
|
|
28
|
+
) @call_node
|
|
29
|
+
""",
|
|
30
|
+
"variables": """
|
|
31
|
+
(val_definition
|
|
32
|
+
pattern: (identifier) @name
|
|
33
|
+
) @variable
|
|
34
|
+
|
|
35
|
+
(var_definition
|
|
36
|
+
pattern: (identifier) @name
|
|
37
|
+
) @variable
|
|
38
|
+
""",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class ScalaTreeSitterParser:
|
|
42
|
+
def __init__(self, generic_parser_wrapper: Any):
|
|
43
|
+
self.generic_parser_wrapper = generic_parser_wrapper
|
|
44
|
+
self.language_name = "scala"
|
|
45
|
+
self.language = generic_parser_wrapper.language
|
|
46
|
+
self.parser = generic_parser_wrapper.parser
|
|
47
|
+
|
|
48
|
+
def parse(self, file_path: Path, is_dependency: bool = False) -> Dict[str, Any]:
|
|
49
|
+
try:
|
|
50
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
51
|
+
source_code = f.read()
|
|
52
|
+
|
|
53
|
+
if not source_code.strip():
|
|
54
|
+
warning_logger(f"Empty or whitespace-only file: {file_path}")
|
|
55
|
+
return {
|
|
56
|
+
"file_path": str(file_path),
|
|
57
|
+
"functions": [],
|
|
58
|
+
"classes": [],
|
|
59
|
+
"variables": [],
|
|
60
|
+
"imports": [],
|
|
61
|
+
"function_calls": [],
|
|
62
|
+
"is_dependency": is_dependency,
|
|
63
|
+
"lang": self.language_name,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
tree = self.parser.parse(bytes(source_code, "utf8"))
|
|
67
|
+
|
|
68
|
+
parsed_functions = []
|
|
69
|
+
parsed_classes = []
|
|
70
|
+
parsed_variables = []
|
|
71
|
+
parsed_imports = []
|
|
72
|
+
parsed_calls = []
|
|
73
|
+
|
|
74
|
+
# Parse variables first for inference
|
|
75
|
+
if "variables" in SCALA_QUERIES:
|
|
76
|
+
try:
|
|
77
|
+
results = execute_query(self.language, SCALA_QUERIES["variables"], tree.root_node)
|
|
78
|
+
parsed_variables.extend(self._parse_variables(results, source_code, file_path))
|
|
79
|
+
except Exception as e:
|
|
80
|
+
error_logger(f"Error parsing Scala variables in {file_path}: {e}")
|
|
81
|
+
|
|
82
|
+
for capture_name, query in SCALA_QUERIES.items():
|
|
83
|
+
if capture_name == "variables": continue
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
results = execute_query(self.language, query, tree.root_node)
|
|
87
|
+
|
|
88
|
+
if capture_name == "functions":
|
|
89
|
+
parsed_functions.extend(self._parse_functions(results, source_code, file_path))
|
|
90
|
+
elif capture_name == "classes":
|
|
91
|
+
parsed_classes.extend(self._parse_classes(results, source_code, file_path))
|
|
92
|
+
elif capture_name == "imports":
|
|
93
|
+
parsed_imports.extend(self._parse_imports(results, source_code))
|
|
94
|
+
elif capture_name == "calls":
|
|
95
|
+
parsed_calls.extend(self._parse_calls(results, source_code, file_path, parsed_variables))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
# Some queries might fail if the grammar differs slightly, catch and log
|
|
98
|
+
error_logger(f"Error executing Scala query '{capture_name}' in {file_path}: {e}")
|
|
99
|
+
|
|
100
|
+
# Separate classes, traits, objects
|
|
101
|
+
final_classes = []
|
|
102
|
+
final_traits = []
|
|
103
|
+
|
|
104
|
+
for item in parsed_classes:
|
|
105
|
+
item_type = item.get('type', 'class')
|
|
106
|
+
if item_type == 'trait':
|
|
107
|
+
final_traits.append(item)
|
|
108
|
+
elif item_type == 'object':
|
|
109
|
+
item['is_object'] = True
|
|
110
|
+
final_classes.append(item)
|
|
111
|
+
else:
|
|
112
|
+
final_classes.append(item)
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"file_path": str(file_path),
|
|
116
|
+
"functions": parsed_functions,
|
|
117
|
+
"classes": final_classes,
|
|
118
|
+
"traits": final_traits,
|
|
119
|
+
"variables": parsed_variables,
|
|
120
|
+
"imports": parsed_imports,
|
|
121
|
+
"function_calls": parsed_calls,
|
|
122
|
+
"is_dependency": is_dependency,
|
|
123
|
+
"lang": self.language_name,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
error_logger(f"Error parsing Scala file {file_path}: {e}")
|
|
128
|
+
return {
|
|
129
|
+
"file_path": str(file_path),
|
|
130
|
+
"functions": [],
|
|
131
|
+
"classes": [],
|
|
132
|
+
"variables": [],
|
|
133
|
+
"imports": [],
|
|
134
|
+
"function_calls": [],
|
|
135
|
+
"is_dependency": is_dependency,
|
|
136
|
+
"lang": self.language_name,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def _get_parent_context(self, node: Any) -> Tuple[Optional[str], Optional[str], Optional[int]]:
|
|
140
|
+
curr = node.parent
|
|
141
|
+
while curr:
|
|
142
|
+
if curr.type == "function_definition":
|
|
143
|
+
name_node = curr.child_by_field_name("name")
|
|
144
|
+
return (
|
|
145
|
+
self._get_node_text(name_node) if name_node else None,
|
|
146
|
+
curr.type,
|
|
147
|
+
curr.start_point[0] + 1,
|
|
148
|
+
)
|
|
149
|
+
if curr.type in ("class_definition", "object_definition", "trait_definition"):
|
|
150
|
+
name_node = curr.child_by_field_name("name")
|
|
151
|
+
return (
|
|
152
|
+
self._get_node_text(name_node) if name_node else None,
|
|
153
|
+
curr.type,
|
|
154
|
+
curr.start_point[0] + 1,
|
|
155
|
+
)
|
|
156
|
+
curr = curr.parent
|
|
157
|
+
return None, None, None
|
|
158
|
+
|
|
159
|
+
def _get_node_text(self, node: Any) -> str:
|
|
160
|
+
if not node: return ""
|
|
161
|
+
return node.text.decode("utf-8")
|
|
162
|
+
|
|
163
|
+
def _parse_functions(self, captures: list, source_code: str, file_path: Path) -> List[Dict[str, Any]]:
|
|
164
|
+
functions = []
|
|
165
|
+
seen_nodes = set()
|
|
166
|
+
|
|
167
|
+
for node, capture_name in captures:
|
|
168
|
+
if capture_name == "function_node":
|
|
169
|
+
node_id = (node.start_byte, node.end_byte, node.type)
|
|
170
|
+
if node_id in seen_nodes:
|
|
171
|
+
continue
|
|
172
|
+
seen_nodes.add(node_id)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
start_line = node.start_point[0] + 1
|
|
176
|
+
end_line = node.end_point[0] + 1
|
|
177
|
+
|
|
178
|
+
name_node = node.child_by_field_name("name")
|
|
179
|
+
if name_node:
|
|
180
|
+
func_name = self._get_node_text(name_node)
|
|
181
|
+
|
|
182
|
+
params_node = node.child_by_field_name("parameters")
|
|
183
|
+
parameters = []
|
|
184
|
+
if params_node:
|
|
185
|
+
params_text = self._get_node_text(params_node)
|
|
186
|
+
parameters = self._extract_parameter_names(params_text)
|
|
187
|
+
|
|
188
|
+
source_text = self._get_node_text(node)
|
|
189
|
+
|
|
190
|
+
context_name, context_type, context_line = self._get_parent_context(node)
|
|
191
|
+
|
|
192
|
+
functions.append({
|
|
193
|
+
"name": func_name,
|
|
194
|
+
"parameters": parameters,
|
|
195
|
+
"args": parameters, # 'args' is sometimes used instead of 'parameters'
|
|
196
|
+
"line_number": start_line,
|
|
197
|
+
"end_line": end_line,
|
|
198
|
+
"source": source_text,
|
|
199
|
+
"file_path": str(file_path),
|
|
200
|
+
"lang": self.language_name,
|
|
201
|
+
"context": context_name,
|
|
202
|
+
"class_context": context_name if context_type and "class" in str(context_type) or "object" in str(context_type) or "trait" in str(context_type) else None
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
error_logger(f"Error parsing function in {file_path}: {e}")
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
return functions
|
|
210
|
+
|
|
211
|
+
def _parse_classes(self, captures: list, source_code: str, file_path: Path) -> List[Dict[str, Any]]:
|
|
212
|
+
classes = []
|
|
213
|
+
seen_nodes = set()
|
|
214
|
+
|
|
215
|
+
for node, capture_name in captures:
|
|
216
|
+
if capture_name == "class":
|
|
217
|
+
node_id = (node.start_byte, node.end_byte, node.type)
|
|
218
|
+
if node_id in seen_nodes:
|
|
219
|
+
continue
|
|
220
|
+
seen_nodes.add(node_id)
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
start_line = node.start_point[0] + 1
|
|
224
|
+
end_line = node.end_point[0] + 1
|
|
225
|
+
|
|
226
|
+
name_node = node.child_by_field_name("name")
|
|
227
|
+
if name_node:
|
|
228
|
+
class_name = self._get_node_text(name_node)
|
|
229
|
+
source_text = self._get_node_text(node)
|
|
230
|
+
|
|
231
|
+
bases = []
|
|
232
|
+
# Look for extends clause (extends_clause)
|
|
233
|
+
# class_definition -> extends_clause -> template_body
|
|
234
|
+
extends_clause = None
|
|
235
|
+
for child in node.children:
|
|
236
|
+
if child.type == "extends_clause": # Might vary by grammar version: 'extends' keyword + types
|
|
237
|
+
extends_clause = child
|
|
238
|
+
break
|
|
239
|
+
|
|
240
|
+
if extends_clause:
|
|
241
|
+
for child in extends_clause.children:
|
|
242
|
+
if child.type == "type_identifier" or child.type == "user_type": # specific to scala grammar
|
|
243
|
+
bases.append(self._get_node_text(child))
|
|
244
|
+
elif child.type == "template_invocation":
|
|
245
|
+
# template_invocation -> user_type
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
# Note: parsing bases in Scala can be complex (mixins with 'with' keyword).
|
|
249
|
+
# Using text based regex backup might be safer for now if tree query is hard.
|
|
250
|
+
|
|
251
|
+
classes.append({
|
|
252
|
+
"name": class_name,
|
|
253
|
+
"line_number": start_line,
|
|
254
|
+
"end_line": end_line,
|
|
255
|
+
"bases": bases,
|
|
256
|
+
"source": source_text,
|
|
257
|
+
"file_path": str(file_path),
|
|
258
|
+
"lang": self.language_name,
|
|
259
|
+
"type": node.type.replace("_definition", "") # class, object, trait
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
error_logger(f"Error parsing class in {file_path}: {e}")
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
return classes
|
|
267
|
+
|
|
268
|
+
def _parse_variables(self, captures: list, source_code: str, file_path: Path) -> List[Dict[str, Any]]:
|
|
269
|
+
variables = []
|
|
270
|
+
seen_vars = set()
|
|
271
|
+
|
|
272
|
+
for node, capture_name in captures:
|
|
273
|
+
if capture_name == "variable":
|
|
274
|
+
# The capture is on the whole definition (val/var_definition)
|
|
275
|
+
# But we have @name on the identifier inside pattern.
|
|
276
|
+
pass
|
|
277
|
+
if capture_name == "name":
|
|
278
|
+
# Check parent context
|
|
279
|
+
if node.parent.type in ("val_definition", "var_definition"):
|
|
280
|
+
definition = node.parent
|
|
281
|
+
var_name = self._get_node_text(node)
|
|
282
|
+
start_line = node.start_point[0] + 1
|
|
283
|
+
|
|
284
|
+
start_byte = node.start_byte
|
|
285
|
+
if start_byte in seen_vars:
|
|
286
|
+
continue
|
|
287
|
+
seen_vars.add(start_byte)
|
|
288
|
+
|
|
289
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
290
|
+
|
|
291
|
+
# Type extraction: look for type_identifier in definition
|
|
292
|
+
var_type = "Unknown"
|
|
293
|
+
type_node = definition.child_by_field_name("type")
|
|
294
|
+
if type_node:
|
|
295
|
+
var_type = self._get_node_text(type_node)
|
|
296
|
+
else:
|
|
297
|
+
# Attempt inference from value
|
|
298
|
+
val_node = definition.child_by_field_name("value")
|
|
299
|
+
if val_node:
|
|
300
|
+
if val_node.type == "instance_expression" or val_node.type == "new_expression":
|
|
301
|
+
# new Calculator()
|
|
302
|
+
# instance_expression -> new, type_identifier, arguments
|
|
303
|
+
for child in val_node.children:
|
|
304
|
+
if child.type in ("type_identifier", "simple_type", "user_type", "generic_type"):
|
|
305
|
+
var_type = self._get_node_text(child)
|
|
306
|
+
break
|
|
307
|
+
elif child.type == "template_call": # sometimes nested
|
|
308
|
+
for sub in child.children:
|
|
309
|
+
if sub.type in ("type_identifier", "simple_type", "user_type"):
|
|
310
|
+
var_type = self._get_node_text(sub)
|
|
311
|
+
break
|
|
312
|
+
elif val_node.type == "call_expression":
|
|
313
|
+
# Circle(5.0)
|
|
314
|
+
# wrapper -> function(identifier)
|
|
315
|
+
func = val_node.child_by_field_name("function")
|
|
316
|
+
if func:
|
|
317
|
+
var_type = self._get_node_text(func)
|
|
318
|
+
|
|
319
|
+
variables.append({
|
|
320
|
+
"name": var_name,
|
|
321
|
+
"type": var_type,
|
|
322
|
+
"line_number": start_line,
|
|
323
|
+
"file_path": str(file_path),
|
|
324
|
+
"lang": self.language_name,
|
|
325
|
+
"context": ctx_name,
|
|
326
|
+
"class_context": ctx_name if ctx_type and ("class" in str(ctx_type) or "object" in str(ctx_type)) else None
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return variables
|
|
330
|
+
|
|
331
|
+
def _parse_imports(self, captures: list, source_code: str) -> List[dict]:
|
|
332
|
+
imports = []
|
|
333
|
+
|
|
334
|
+
for node, capture_name in captures:
|
|
335
|
+
if capture_name == "import":
|
|
336
|
+
try:
|
|
337
|
+
# Scala imports can be complex: import java.util.{Date, List} or import java.util._
|
|
338
|
+
# We will try to extract the base path.
|
|
339
|
+
import_text = self._get_node_text(node)
|
|
340
|
+
# Simple heuristic: remove 'import ' and handle one level
|
|
341
|
+
clean_text = import_text.replace("import ", "").strip()
|
|
342
|
+
|
|
343
|
+
# Split logic for multiple imports in one line not handled perfectly here yet
|
|
344
|
+
# Just storing the whole text as name for now is better than crashing
|
|
345
|
+
|
|
346
|
+
path = clean_text
|
|
347
|
+
|
|
348
|
+
imports.append({
|
|
349
|
+
"name": path,
|
|
350
|
+
"full_import_name": path,
|
|
351
|
+
"line_number": node.start_point[0] + 1,
|
|
352
|
+
"alias": None,
|
|
353
|
+
"context": (None, None),
|
|
354
|
+
"lang": self.language_name,
|
|
355
|
+
"is_dependency": False,
|
|
356
|
+
})
|
|
357
|
+
except Exception as e:
|
|
358
|
+
error_logger(f"Error parsing import: {e}")
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
return imports
|
|
362
|
+
|
|
363
|
+
def _parse_calls(self, captures: list, source_code: str, file_path: Path, variables: List[Dict] = []) -> List[Dict]:
|
|
364
|
+
calls = []
|
|
365
|
+
seen_calls = set()
|
|
366
|
+
|
|
367
|
+
for node, capture_name in captures:
|
|
368
|
+
if capture_name == "call_node":
|
|
369
|
+
try:
|
|
370
|
+
start_line = node.start_point[0] + 1
|
|
371
|
+
|
|
372
|
+
# Heuristic to find name
|
|
373
|
+
call_name = "unknown"
|
|
374
|
+
full_name = "unknown"
|
|
375
|
+
|
|
376
|
+
if node.type == "call_expression":
|
|
377
|
+
# function (child 0) arguments (child 1)
|
|
378
|
+
func_node = node.child_by_field_name("function")
|
|
379
|
+
if func_node:
|
|
380
|
+
if func_node.type == "field_expression": # obj.method
|
|
381
|
+
call_name = self._get_node_text(func_node.child_by_field_name("field")) # or name?
|
|
382
|
+
full_name = self._get_node_text(func_node)
|
|
383
|
+
elif func_node.type == "identifier":
|
|
384
|
+
call_name = self._get_node_text(func_node)
|
|
385
|
+
full_name = call_name
|
|
386
|
+
elif func_node.type == "generic_function":
|
|
387
|
+
# generic_function -> function
|
|
388
|
+
inner = func_node.child_by_field_name("function")
|
|
389
|
+
if inner:
|
|
390
|
+
full_name = self._get_node_text(inner)
|
|
391
|
+
call_name = full_name # simplified
|
|
392
|
+
|
|
393
|
+
if call_name == "unknown":
|
|
394
|
+
# Falback to text if simple
|
|
395
|
+
# call_name = self._get_node_text(node).split('(')[0]
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
# Avoid duplicates
|
|
399
|
+
call_key = f"{call_name}_{start_line}"
|
|
400
|
+
if call_key in seen_calls:
|
|
401
|
+
continue
|
|
402
|
+
seen_calls.add(call_key)
|
|
403
|
+
|
|
404
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
405
|
+
|
|
406
|
+
# Inference from variables
|
|
407
|
+
inferred_type = None
|
|
408
|
+
if "." in full_name:
|
|
409
|
+
base_obj = full_name.split(".")[0]
|
|
410
|
+
# search for base_obj in variables
|
|
411
|
+
# Prefer variables in local context (ctx_name)
|
|
412
|
+
|
|
413
|
+
# Simple search: exact name match in same file
|
|
414
|
+
# We could improve by checking scope/context, but for now filtering by name is a good start
|
|
415
|
+
candidate = None
|
|
416
|
+
for v in variables:
|
|
417
|
+
if v["name"] == base_obj:
|
|
418
|
+
# Check if context matches or is strictly enclosing?
|
|
419
|
+
# For now, just take the first match or last match?
|
|
420
|
+
# Usually last match (closest definition)
|
|
421
|
+
candidate = v
|
|
422
|
+
if v["context"] == ctx_name:
|
|
423
|
+
break
|
|
424
|
+
|
|
425
|
+
if candidate:
|
|
426
|
+
inferred_type = candidate["type"]
|
|
427
|
+
elif call_name in variables: # Usually not happening as variables is list of dicts
|
|
428
|
+
pass
|
|
429
|
+
|
|
430
|
+
calls.append({
|
|
431
|
+
"name": call_name,
|
|
432
|
+
"full_name": full_name,
|
|
433
|
+
"line_number": start_line,
|
|
434
|
+
"args": [],
|
|
435
|
+
"inferred_obj_type": inferred_type,
|
|
436
|
+
"context": (ctx_name, ctx_type, ctx_line),
|
|
437
|
+
"class_context": (ctx_name, ctx_line) if ctx_type and ("class" in str(ctx_type) or "object" in str(ctx_type)) else (None, None),
|
|
438
|
+
"lang": self.language_name,
|
|
439
|
+
"is_dependency": False,
|
|
440
|
+
})
|
|
441
|
+
except Exception as e:
|
|
442
|
+
error_logger(f"Error parsing call: {e}")
|
|
443
|
+
continue
|
|
444
|
+
|
|
445
|
+
return calls
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _extract_parameter_names(self, params_text: str) -> List[str]:
|
|
449
|
+
# Simple extraction for Scala: (a: Int, b: String)
|
|
450
|
+
params = []
|
|
451
|
+
if not params_text: return params
|
|
452
|
+
clean = params_text.strip("()")
|
|
453
|
+
if not clean: return params
|
|
454
|
+
|
|
455
|
+
# Split by comma, respecting generics []
|
|
456
|
+
# Scala generics use []
|
|
457
|
+
|
|
458
|
+
# TODO: Reuse regex/parsing logic from other parsers or write simple one
|
|
459
|
+
# For now, simplistic split
|
|
460
|
+
parts = clean.split(',')
|
|
461
|
+
for p in parts:
|
|
462
|
+
# removing type: 'name: Type'
|
|
463
|
+
if ':' in p:
|
|
464
|
+
name = p.split(':')[0].strip()
|
|
465
|
+
# Remove modifiers like 'implicit', 'override', etc.
|
|
466
|
+
tokens = name.split()
|
|
467
|
+
if tokens:
|
|
468
|
+
params.append(tokens[-1])
|
|
469
|
+
else:
|
|
470
|
+
# maybe just name?
|
|
471
|
+
params.append(p.strip())
|
|
472
|
+
return params
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def pre_scan_scala(files: list[Path], parser_wrapper) -> dict:
|
|
476
|
+
name_to_files = {}
|
|
477
|
+
|
|
478
|
+
for file_path in files:
|
|
479
|
+
try:
|
|
480
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
481
|
+
content = f.read()
|
|
482
|
+
|
|
483
|
+
# package matches
|
|
484
|
+
package_name = ""
|
|
485
|
+
pkg_match = re.search(r'^\s*package\s+([\w\.]+)', content, re.MULTILINE)
|
|
486
|
+
if pkg_match:
|
|
487
|
+
package_name = pkg_match.group(1)
|
|
488
|
+
|
|
489
|
+
# class/object/trait matches
|
|
490
|
+
class_matches = re.finditer(r'\b(class|object|trait)\s+(\w+)', content)
|
|
491
|
+
for match in class_matches:
|
|
492
|
+
name = match.group(2)
|
|
493
|
+
type_ = match.group(1)
|
|
494
|
+
|
|
495
|
+
# Simple mapping
|
|
496
|
+
if name not in name_to_files:
|
|
497
|
+
name_to_files[name] = []
|
|
498
|
+
name_to_files[name].append(str(file_path))
|
|
499
|
+
|
|
500
|
+
# FQN mapping
|
|
501
|
+
if package_name:
|
|
502
|
+
fqn = f"{package_name}.{name}"
|
|
503
|
+
if fqn not in name_to_files:
|
|
504
|
+
name_to_files[fqn] = []
|
|
505
|
+
name_to_files[fqn].append(str(file_path))
|
|
506
|
+
|
|
507
|
+
except Exception as e:
|
|
508
|
+
error_logger(f"Error pre-scanning Scala file {file_path}: {e}")
|
|
509
|
+
|
|
510
|
+
return name_to_files
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
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
|
|
@@ -91,7 +91,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
|
|
|
91
91
|

|
|
92
92
|
|
|
93
93
|
## Project Details
|
|
94
|
-
- **Version:** 0.1.
|
|
94
|
+
- **Version:** 0.1.28
|
|
95
95
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
96
96
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
97
97
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/SOURCES.txt
RENAMED
|
@@ -41,6 +41,7 @@ src/codegraphcontext/tools/languages/php.py
|
|
|
41
41
|
src/codegraphcontext/tools/languages/python.py
|
|
42
42
|
src/codegraphcontext/tools/languages/ruby.py
|
|
43
43
|
src/codegraphcontext/tools/languages/rust.py
|
|
44
|
+
src/codegraphcontext/tools/languages/scala.py
|
|
44
45
|
src/codegraphcontext/tools/languages/typescript.py
|
|
45
46
|
src/codegraphcontext/tools/query_tool_languages/c_toolkit.py
|
|
46
47
|
src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py
|
|
@@ -51,6 +52,7 @@ src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py
|
|
|
51
52
|
src/codegraphcontext/tools/query_tool_languages/python_toolkit.py
|
|
52
53
|
src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py
|
|
53
54
|
src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py
|
|
55
|
+
src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py
|
|
54
56
|
src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py
|
|
55
57
|
src/codegraphcontext/utils/debug_log.py
|
|
56
58
|
src/codegraphcontext/utils/tree_sitter_manager.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/config_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/setup_wizard.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/database_falkordb.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/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
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/c.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/cpp.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/csharp.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/go.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/java.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/kotlin.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/php.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/ruby.py
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/rust.py
RENAMED
|
File without changes
|
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/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
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/requires.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/top_level.txt
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
|