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.
Files changed (68) hide show
  1. {codegraphcontext-0.1.27/src/codegraphcontext.egg-info → codegraphcontext-0.1.28}/PKG-INFO +2 -2
  2. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/README.md +1 -1
  3. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/pyproject.toml +1 -1
  4. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/main.py +305 -2
  5. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/code_finder.py +2 -2
  6. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/graph_builder.py +11 -0
  7. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/python.py +16 -0
  8. codegraphcontext-0.1.28/src/codegraphcontext/tools/languages/scala.py +510 -0
  9. codegraphcontext-0.1.28/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
  10. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/utils/tree_sitter_manager.py +2 -0
  11. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28/src/codegraphcontext.egg-info}/PKG-INFO +2 -2
  12. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/SOURCES.txt +2 -0
  13. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/LICENSE +0 -0
  14. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/MANIFEST.in +0 -0
  15. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/setup.cfg +0 -0
  16. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/__init__.py +0 -0
  17. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/__main__.py +0 -0
  18. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/__init__.py +0 -0
  19. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/cli_helpers.py +0 -0
  20. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/config_manager.py +0 -0
  21. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/setup_macos.py +0 -0
  22. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/cli/setup_wizard.py +0 -0
  23. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/__init__.py +0 -0
  24. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/database.py +0 -0
  25. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/database_falkordb.py +0 -0
  26. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/falkor_worker.py +0 -0
  27. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/jobs.py +0 -0
  28. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/core/watcher.py +0 -0
  29. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/prompts.py +0 -0
  30. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/server.py +0 -0
  31. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/__init__.py +0 -0
  32. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
  33. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/c.py +0 -0
  34. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/cpp.py +0 -0
  35. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/csharp.py +0 -0
  36. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/go.py +0 -0
  37. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/java.py +0 -0
  38. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/javascript.py +0 -0
  39. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
  40. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/php.py +0 -0
  41. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/ruby.py +0 -0
  42. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/rust.py +0 -0
  43. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/languages/typescript.py +0 -0
  44. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/package_resolver.py +0 -0
  45. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
  46. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
  47. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
  48. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
  49. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
  50. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
  51. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
  52. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
  53. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
  54. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
  55. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/tools/system.py +0 -0
  56. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext/utils/debug_log.py +0 -0
  57. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
  58. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
  59. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/requires.txt +0 -0
  60. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/src/codegraphcontext.egg-info/top_level.txt +0 -0
  61. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_cpp_parser.py +0 -0
  62. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_database_validation.py +0 -0
  63. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_end_to_end.py +0 -0
  64. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_graph_indexing.py +0 -0
  65. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_graph_indexing_js.py +0 -0
  66. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_kotlin_parser.py +0 -0
  67. {codegraphcontext-0.1.27 → codegraphcontext-0.1.28}/tests/test_tree_sitter_manager.py +0 -0
  68. {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.27
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
  ![Using the MCP server](https://github.com/Shashankss1205/CodeGraphContext/blob/main/images/Usecase.gif)
92
92
 
93
93
  ## Project Details
94
- - **Version:** 0.1.27
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
  ![Using the MCP server](https://github.com/Shashankss1205/CodeGraphContext/blob/main/images/Usecase.gif)
30
30
 
31
31
  ## Project Details
32
- - **Version:** 0.1.27
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.27"
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
@@ -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 OR v.name =~ $regex_pattern
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, regex_pattern=f"(?i).*{re.escape(search_term)}.*")
76
+ """, search_term=search_term)
77
77
 
78
78
  return result.data()
79
79
 
@@ -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
 
@@ -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
@@ -0,0 +1,5 @@
1
+ class ScalaToolkit:
2
+ """Template placeholder for future implementation."""
3
+
4
+ def get_cypher_query(query: str) -> str:
5
+ raise NotImplementedError("AdvancedLanguageQuery is not implemented yet.")
@@ -46,6 +46,8 @@ LANGUAGE_ALIASES = {
46
46
  "rust": "rust",
47
47
  "kt": "kotlin",
48
48
  "kotlin": "kotlin",
49
+ "scala": "scala",
50
+ ".scala": "scala",
49
51
  }
50
52
 
51
53
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.1.27
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
  ![Using the MCP server](https://github.com/Shashankss1205/CodeGraphContext/blob/main/images/Usecase.gif)
92
92
 
93
93
  ## Project Details
94
- - **Version:** 0.1.27
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/)
@@ -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