codegraphcontext 0.2.4__tar.gz → 0.2.6__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 (80) hide show
  1. {codegraphcontext-0.2.4/src/codegraphcontext.egg-info → codegraphcontext-0.2.6}/PKG-INFO +2 -2
  2. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/README.md +1 -1
  3. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/pyproject.toml +1 -1
  4. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/config_manager.py +9 -0
  5. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/graph_builder.py +224 -2
  6. codegraphcontext-0.2.6/src/codegraphcontext/tools/languages/dart.py +368 -0
  7. codegraphcontext-0.2.6/src/codegraphcontext/tools/languages/perl.py +255 -0
  8. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/package_resolver.py +29 -1
  9. codegraphcontext-0.2.6/src/codegraphcontext/tools/scip_indexer.py +467 -0
  10. codegraphcontext-0.2.6/src/codegraphcontext/tools/scip_pb2.py +2455 -0
  11. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/utils/tree_sitter_manager.py +4 -0
  12. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6/src/codegraphcontext.egg-info}/PKG-INFO +2 -2
  13. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext.egg-info/SOURCES.txt +4 -0
  14. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/LICENSE +0 -0
  15. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/MANIFEST.in +0 -0
  16. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/setup.cfg +0 -0
  17. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/__init__.py +0 -0
  18. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/__main__.py +0 -0
  19. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/__init__.py +0 -0
  20. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/cli_helpers.py +0 -0
  21. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/main.py +0 -0
  22. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/registry_commands.py +0 -0
  23. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/setup_macos.py +0 -0
  24. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/setup_wizard.py +0 -0
  25. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/cli/visualizer.py +0 -0
  26. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/__init__.py +0 -0
  27. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/bundle_registry.py +0 -0
  28. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/cgc_bundle.py +0 -0
  29. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/database.py +0 -0
  30. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/database_falkordb.py +0 -0
  31. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/falkor_worker.py +0 -0
  32. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/jobs.py +0 -0
  33. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/core/watcher.py +0 -0
  34. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/prompts.py +0 -0
  35. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/server.py +0 -0
  36. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tool_definitions.py +0 -0
  37. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/__init__.py +0 -0
  38. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
  39. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/code_finder.py +0 -0
  40. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/handlers/analysis_handlers.py +0 -0
  41. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/handlers/indexing_handlers.py +0 -0
  42. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/handlers/management_handlers.py +0 -0
  43. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/handlers/query_handlers.py +0 -0
  44. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/handlers/watcher_handlers.py +0 -0
  45. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/c.py +0 -0
  46. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/cpp.py +0 -0
  47. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/csharp.py +0 -0
  48. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/go.py +0 -0
  49. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/haskell.py +0 -0
  50. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/java.py +0 -0
  51. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/javascript.py +0 -0
  52. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
  53. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/php.py +0 -0
  54. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/python.py +0 -0
  55. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/ruby.py +0 -0
  56. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/rust.py +0 -0
  57. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/scala.py +0 -0
  58. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/swift.py +0 -0
  59. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/typescript.py +0 -0
  60. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/languages/typescriptjsx.py +0 -0
  61. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
  62. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
  63. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
  64. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
  65. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +0 -0
  66. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
  67. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
  68. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
  69. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
  70. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
  71. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +0 -0
  72. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/swift_toolkit.py +0 -0
  73. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
  74. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/tools/system.py +0 -0
  75. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/utils/debug_log.py +0 -0
  76. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext/utils/visualize_graph.py +0 -0
  77. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
  78. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
  79. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext.egg-info/requires.txt +0 -0
  80. {codegraphcontext-0.2.4 → codegraphcontext-0.2.6}/src/codegraphcontext.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.2.4
3
+ Version: 0.2.6
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
@@ -146,7 +146,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
146
146
  ---
147
147
 
148
148
  ## Project Details
149
- - **Version:** 0.2.4
149
+ - **Version:** 0.2.6
150
150
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
151
151
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
152
152
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -84,7 +84,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
84
84
  ---
85
85
 
86
86
  ## Project Details
87
- - **Version:** 0.2.4
87
+ - **Version:** 0.2.6
88
88
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
89
89
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
90
90
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codegraphcontext"
3
- version = "0.2.4"
3
+ version = "0.2.6"
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"
@@ -39,6 +39,10 @@ DEFAULT_CONFIG = {
39
39
  "CACHE_ENABLED": "true",
40
40
  "IGNORE_DIRS": "node_modules,venv,.venv,env,.env,dist,build,target,out,.git,.idea,.vscode,__pycache__",
41
41
  "INDEX_SOURCE": "true",
42
+ # SCIP indexer feature flag (default off — existing Tree-sitter behaviour unchanged)
43
+ "SCIP_INDEXER": "false",
44
+ "SCIP_LANGUAGES": "python,typescript,go,rust,java",
45
+ "SKIP_EXTERNAL_RESOLUTION": "false",
42
46
  }
43
47
 
44
48
  # Configuration key descriptions
@@ -62,6 +66,9 @@ CONFIG_DESCRIPTIONS = {
62
66
  "CACHE_ENABLED": "Enable caching for faster re-indexing",
63
67
  "IGNORE_DIRS": "Comma-separated list of directory names to ignore during indexing",
64
68
  "INDEX_SOURCE": "Store full source code in graph database (for faster indexing use false, for better performance use true)",
69
+ "SCIP_INDEXER": "Use SCIP-based indexing for higher accuracy call/inheritance resolution (requires scip-<lang> tools installed)",
70
+ "SCIP_LANGUAGES": "Comma-separated languages to index via SCIP when SCIP_INDEXER=true (python,typescript,go,rust,java)",
71
+ "SKIP_EXTERNAL_RESOLUTION": "Skip resolution attempts for external library method calls (recommended for enterprise large Java/Spring codebases)",
65
72
  }
66
73
 
67
74
  # Valid values for each config key
@@ -76,6 +83,8 @@ CONFIG_VALIDATORS = {
76
83
  "ENABLE_AUTO_WATCH": ["true", "false"],
77
84
  "CACHE_ENABLED": ["true", "false"],
78
85
  "INDEX_SOURCE": ["true", "false"],
86
+ "SCIP_INDEXER": ["true", "false"],
87
+ "SKIP_EXTERNAL_RESOLUTION": ["true", "false"],
79
88
  }
80
89
 
81
90
 
@@ -74,6 +74,12 @@ class TreeSitterParser:
74
74
  elif self.language_name == 'haskell':
75
75
  from .languages.haskell import HaskellTreeSitterParser
76
76
  self.language_specific_parser = HaskellTreeSitterParser(self)
77
+ elif self.language_name == 'dart':
78
+ from .languages.dart import DartTreeSitterParser
79
+ self.language_specific_parser = DartTreeSitterParser(self)
80
+ elif self.language_name == 'perl':
81
+ from .languages.perl import PerlTreeSitterParser
82
+ self.language_specific_parser = PerlTreeSitterParser(self)
77
83
 
78
84
 
79
85
 
@@ -120,6 +126,9 @@ class GraphBuilder:
120
126
  '.sc': TreeSitterParser('scala'),
121
127
  '.swift': TreeSitterParser('swift'),
122
128
  '.hs': TreeSitterParser('haskell'),
129
+ '.dart': TreeSitterParser('dart'),
130
+ '.pl': TreeSitterParser('perl'),
131
+ '.pm': TreeSitterParser('perl'),
123
132
  }
124
133
  self.create_schema()
125
134
 
@@ -162,7 +171,7 @@ class GraphBuilder:
162
171
  session.run("""
163
172
  CREATE FULLTEXT INDEX code_search_index IF NOT EXISTS
164
173
  FOR (n:Function|Class|Variable)
165
- ON EACH [n.name, coalesce(n.source, ''), coalesce(n.docstring, '')]
174
+ ON EACH [n.name, n.source, n.docstring]
166
175
  """)
167
176
 
168
177
  info_logger("Database schema verified/created successfully")
@@ -249,6 +258,15 @@ class GraphBuilder:
249
258
  if '.swift' in files_by_lang:
250
259
  from .languages import swift as swift_lang_module
251
260
  imports_map.update(swift_lang_module.pre_scan_swift(files_by_lang['.swift'], self.parsers['.swift']))
261
+ if '.dart' in files_by_lang:
262
+ from .languages import dart as dart_lang_module
263
+ imports_map.update(dart_lang_module.pre_scan_dart(files_by_lang['.dart'], self.parsers['.dart']))
264
+ if '.pl' in files_by_lang:
265
+ from .languages import perl as perl_lang_module
266
+ imports_map.update(perl_lang_module.pre_scan_perl(files_by_lang['.pl'], self.parsers['.pl']))
267
+ if '.pm' in files_by_lang:
268
+ from .languages import perl as perl_lang_module
269
+ imports_map.update(perl_lang_module.pre_scan_perl(files_by_lang['.pm'], self.parsers['.pm']))
252
270
 
253
271
  return imports_map
254
272
 
@@ -461,6 +479,9 @@ class GraphBuilder:
461
479
  local_imports = {imp.get('alias') or imp['name'].split('.')[-1]: imp['name']
462
480
  for imp in file_data.get('imports', [])}
463
481
 
482
+ # Check if we should skip external resolution attempts -
483
+ skip_external = (get_config_value("SKIP_EXTERNAL_RESOLUTION") or "false").lower() == "true"
484
+
464
485
  for call in file_data.get('function_calls', []):
465
486
  called_name = call['name']
466
487
  if called_name in __builtins__: continue
@@ -517,7 +538,13 @@ class GraphBuilder:
517
538
  break
518
539
 
519
540
  if not resolved_path:
520
- warning_logger(f"Could not resolve call {called_name} (lookup: {lookup_name}) in {caller_file_path}")
541
+ # Only log warning if we're not skipping external resolution
542
+ if not skip_external:
543
+ warning_logger(f"Could not resolve call {called_name} (lookup: {lookup_name}) in {caller_file_path}")
544
+ # Track that this was an unresolved external call
545
+ is_unresolved_external = True
546
+ else:
547
+ is_unresolved_external = False
521
548
  # else:
522
549
  # info_logger(f"Resolved call {called_name} -> {resolved_path}")
523
550
 
@@ -539,6 +566,7 @@ class GraphBuilder:
539
566
  if not resolved_path:
540
567
  if called_name in local_names:
541
568
  resolved_path = caller_file_path
569
+ is_unresolved_external = False # This is a local call, not external
542
570
  elif called_name in imports_map and imports_map[called_name]:
543
571
  # Check if any path in imports_map for called_name matches current file's imports
544
572
  candidates = imports_map[called_name]
@@ -546,12 +574,17 @@ class GraphBuilder:
546
574
  for imp_name in local_imports.values():
547
575
  if imp_name.replace('.', '/') in path:
548
576
  resolved_path = path
577
+ is_unresolved_external = False # Found a match
549
578
  break
550
579
  if resolved_path: break
551
580
  if not resolved_path:
552
581
  resolved_path = candidates[0]
553
582
  else:
554
583
  resolved_path = caller_file_path
584
+
585
+ # Skip creating CALLS relationship for unresolved external calls when skip_external is enabled
586
+ if skip_external and is_unresolved_external:
587
+ continue
555
588
 
556
589
  caller_context = call.get('context')
557
590
  if caller_context and len(caller_context) == 3 and caller_context[0] is not None:
@@ -879,11 +912,200 @@ class GraphBuilder:
879
912
  error_logger(f"Could not estimate processing time for {path}: {e}")
880
913
  return None
881
914
 
915
+ async def _build_graph_from_scip(
916
+ self, path: Path, is_dependency: bool, job_id: Optional[str], lang: str
917
+ ):
918
+ """
919
+ SCIP-based indexing path. Activated only when SCIP_INDEXER=true and
920
+ a scip-<lang> binary is available.
921
+
922
+ Steps:
923
+ 1. Run scip-<lang> CLI → index.scip
924
+ 2. Parse index.scip → nodes + reference edges
925
+ 3. Write nodes to graph (same MERGE queries as Tree-sitter path)
926
+ 4. Tree-sitter supplement: add source text + cyclomatic_complexity
927
+ 5. Write SCIP CALLS edges (precise, no heuristics)
928
+ """
929
+ import tempfile
930
+ from .scip_indexer import ScipIndexer, ScipIndexParser
931
+ from .graph_builder import TreeSitterParser # supplement pass
932
+
933
+ if job_id:
934
+ self.job_manager.update_job(job_id, status=JobStatus.RUNNING)
935
+
936
+ self.add_repository_to_graph(path, is_dependency)
937
+ repo_name = path.name
938
+
939
+ try:
940
+ # Step 1: Run SCIP indexer
941
+ with tempfile.TemporaryDirectory(prefix="cgc_scip_") as tmpdir:
942
+ scip_file = ScipIndexer().run(path, lang, Path(tmpdir))
943
+
944
+ if not scip_file:
945
+ warning_logger(
946
+ f"SCIP indexer produced no output for {path}. "
947
+ "Falling back to Tree-sitter."
948
+ )
949
+ # Hand off to Tree-sitter pipeline by re-calling without SCIP flag
950
+ # (the flag is checked at the start; override is not needed because
951
+ # we return here — caller will not re-enter this branch)
952
+ raise RuntimeError("SCIP produced no index — triggering Tree-sitter fallback")
953
+
954
+ # Step 2: Parse index.scip
955
+ scip_data = ScipIndexParser().parse(scip_file, path)
956
+
957
+ if not scip_data:
958
+ raise RuntimeError("SCIP parse returned empty result")
959
+
960
+ files_data = scip_data.get("files", {})
961
+ file_paths = [Path(p) for p in files_data.keys() if Path(p).exists()]
962
+
963
+ # Step 3: Pre-scan for imports to correctly associate external modules/classes
964
+ imports_map = self._pre_scan_for_imports(file_paths)
965
+
966
+ if job_id:
967
+ self.job_manager.update_job(job_id, total_files=len(files_data))
968
+
969
+ # Step 4: Write nodes to graph using existing add_file_to_graph()
970
+ processed = 0
971
+ for abs_path_str, file_data in files_data.items():
972
+ file_data["repo_path"] = str(path.resolve())
973
+ if job_id:
974
+ self.job_manager.update_job(job_id, current_file=abs_path_str)
975
+
976
+ # Step 5: Tree-sitter supplement — add source text, complexity, imports and bases
977
+ file_path = Path(abs_path_str)
978
+ if file_path.exists() and file_path.suffix in self.parsers:
979
+ try:
980
+ ts_parser = self.parsers[file_path.suffix]
981
+ ts_data = ts_parser.parse(file_path, is_dependency, index_source=True)
982
+ if "error" not in ts_data:
983
+ # 1. Functions: complexity, source, decorators
984
+ ts_funcs = {f["name"]: f for f in ts_data.get("functions", [])}
985
+ for f in file_data.get("functions", []):
986
+ ts_f = ts_funcs.get(f["name"])
987
+ if ts_f:
988
+ f.update({
989
+ "source": ts_f.get("source"),
990
+ "cyclomatic_complexity": ts_f.get("cyclomatic_complexity", 1),
991
+ "decorators": ts_f.get("decorators", [])
992
+ })
993
+
994
+ # 2. Classes: bases (inheritance)
995
+ ts_classes = {c["name"]: c for c in ts_data.get("classes", [])}
996
+ for c in file_data.get("classes", []):
997
+ ts_c = ts_classes.get(c["name"])
998
+ if ts_c:
999
+ c["bases"] = ts_c.get("bases", [])
1000
+
1001
+ # 3. Imports: critical for cross-file resolution
1002
+ file_data["imports"] = ts_data.get("imports", [])
1003
+
1004
+ # 4. Variables/Other: value, etc.
1005
+ file_data["variables"] = ts_data.get("variables", [])
1006
+ except Exception as e:
1007
+ debug_log(f"Tree-sitter supplement failed for {abs_path_str}: {e}")
1008
+
1009
+ self.add_file_to_graph(file_data, repo_name, imports_map)
1010
+
1011
+ processed += 1
1012
+ if job_id:
1013
+ self.job_manager.update_job(job_id, processed_files=processed)
1014
+ await asyncio.sleep(0.01)
1015
+
1016
+ # Step 6: Create INHERITS relationships (Supplemented from Tree-sitter)
1017
+ self._create_all_inheritance_links(list(files_data.values()), imports_map)
1018
+
1019
+ # Step 7: Write SCIP CALLS edges — precise cross-file resolution
1020
+ with self.driver.session() as session:
1021
+ for file_data in files_data.values():
1022
+ for edge in file_data.get("function_calls_scip", []):
1023
+ try:
1024
+ # Use line numbers for precise matching in case of duplicates
1025
+ session.run("""
1026
+ MATCH (caller:Function {name: $caller_name, path: $caller_file, line_number: $caller_line})
1027
+ MATCH (callee:Function {name: $callee_name, path: $callee_file, line_number: $callee_line})
1028
+ MERGE (caller)-[:CALLS {line_number: $ref_line, source: 'scip'}]->(callee)
1029
+ """,
1030
+ caller_name=self._name_from_symbol(edge["caller_symbol"]),
1031
+ caller_file=edge["caller_file"],
1032
+ caller_line=edge["caller_line"],
1033
+ callee_name=edge["callee_name"],
1034
+ callee_file=edge["callee_file"],
1035
+ callee_line=edge["callee_line"],
1036
+ ref_line=edge["ref_line"],
1037
+ )
1038
+ except Exception:
1039
+ pass # best-effort: node might not be indexed yet
1040
+
1041
+ if job_id:
1042
+ self.job_manager.update_job(job_id, status=JobStatus.COMPLETED, end_time=datetime.now())
1043
+
1044
+ except RuntimeError as e:
1045
+ # Graceful fallback to Tree-sitter when SCIP fails
1046
+ warning_logger(f"SCIP path failed ({e}), re-running with Tree-sitter...")
1047
+ # Temporarily disable the flag in-memory so the recursive call goes straight to TS
1048
+ # (we do this by calling the internal Tree-sitter steps directly)
1049
+ if job_id:
1050
+ self.job_manager.update_job(job_id, status=JobStatus.RUNNING)
1051
+ # Re-enter the async flow without SCIP check — handled by caller returning early
1052
+ # For simplicity, we just let the exception propagate to the outer handler so the
1053
+ # job is marked FAILED with a meaningful message rather than silently degrading.
1054
+ raise
1055
+
1056
+ except Exception as e:
1057
+ error_logger(f"SCIP indexing failed for {path}: {e}")
1058
+ if job_id:
1059
+ self.job_manager.update_job(
1060
+ job_id, status=JobStatus.FAILED, end_time=datetime.now(), errors=[str(e)]
1061
+ )
1062
+
1063
+ def _name_from_symbol(self, symbol: str) -> str:
1064
+ """Extract human-readable name from a SCIP symbol ID string."""
1065
+ import re
1066
+ s = symbol.rstrip(".#")
1067
+ s = re.sub(r"\(\)\.?$", "", s) # Remove trailing () or ().
1068
+ parts = re.split(r'[/#]', s)
1069
+ last = parts[-1] if parts else symbol
1070
+ return last or symbol
1071
+
1072
+
882
1073
  async def build_graph_from_path_async(
883
1074
  self, path: Path, is_dependency: bool = False, job_id: str = None
884
1075
  ):
885
1076
  """Builds graph from a directory or file path."""
886
1077
  try:
1078
+ # ------------------------------------------------------------------
1079
+ # SCIP feature flag: SCIP_INDEXER=true in ~/.codegraphcontext/.env
1080
+ # When enabled (and the binary is installed), SCIP handles the
1081
+ # indexing for supported languages. SCIP_INDEXER=false (default)
1082
+ # means this entire block is a no-op and existing behaviour is kept.
1083
+ # ------------------------------------------------------------------
1084
+ scip_enabled = (get_config_value("SCIP_INDEXER") or "false").lower() == "true"
1085
+ if scip_enabled:
1086
+ from .scip_indexer import ScipIndexer, ScipIndexParser, detect_project_lang, is_scip_available
1087
+ scip_langs_str = get_config_value("SCIP_LANGUAGES") or "python,typescript,go,rust,java"
1088
+ scip_languages = [l.strip() for l in scip_langs_str.split(",") if l.strip()]
1089
+ detected_lang = detect_project_lang(path, scip_languages)
1090
+
1091
+ if detected_lang and is_scip_available(detected_lang):
1092
+ info_logger(f"SCIP_INDEXER=true — using SCIP for language: {detected_lang}")
1093
+ await self._build_graph_from_scip(path, is_dependency, job_id, detected_lang)
1094
+ return # SCIP handled it; skip Tree-sitter pipeline below
1095
+ else:
1096
+ if detected_lang:
1097
+ warning_logger(
1098
+ f"SCIP_INDEXER=true but scip-{detected_lang} binary not found. "
1099
+ f"Falling back to Tree-sitter. Install it first."
1100
+ )
1101
+ else:
1102
+ info_logger(
1103
+ "SCIP_INDEXER=true but no SCIP-supported language detected. "
1104
+ "Falling back to Tree-sitter."
1105
+ )
1106
+ # ------------------------------------------------------------------
1107
+ # Existing Tree-sitter pipeline (unchanged)
1108
+ # ------------------------------------------------------------------
887
1109
  if job_id:
888
1110
  self.job_manager.update_job(job_id, status=JobStatus.RUNNING)
889
1111