codegraphcontext 0.3.7__tar.gz → 0.3.8__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 (87) hide show
  1. {codegraphcontext-0.3.7/src/codegraphcontext.egg-info → codegraphcontext-0.3.8}/PKG-INFO +4 -3
  2. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/README.md +2 -2
  3. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/pyproject.toml +2 -1
  4. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/cli_helpers.py +8 -6
  5. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/database_kuzu.py +20 -10
  6. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/server.py +36 -1
  7. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/code_finder.py +80 -28
  8. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/graph_builder.py +88 -19
  9. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/cpp.py +137 -72
  10. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/go.py +42 -5
  11. codegraphcontext-0.3.8/src/codegraphcontext/utils/path_ignore.py +55 -0
  12. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/utils/tree_sitter_manager.py +8 -9
  13. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8/src/codegraphcontext.egg-info}/PKG-INFO +4 -3
  14. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext.egg-info/SOURCES.txt +1 -0
  15. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext.egg-info/requires.txt +1 -0
  16. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/LICENSE +0 -0
  17. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/MANIFEST.in +0 -0
  18. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/setup.cfg +0 -0
  19. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/__init__.py +0 -0
  20. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/__main__.py +0 -0
  21. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/__init__.py +0 -0
  22. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/config_manager.py +0 -0
  23. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/main.py +0 -0
  24. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/registry_commands.py +0 -0
  25. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/setup_macos.py +0 -0
  26. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/setup_wizard.py +0 -0
  27. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/cli/visualizer.py +0 -0
  28. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/__init__.py +0 -0
  29. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/bundle_registry.py +0 -0
  30. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/cgc_bundle.py +0 -0
  31. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/database.py +0 -0
  32. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/database_falkordb.py +0 -0
  33. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/database_falkordb_remote.py +0 -0
  34. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/falkor_worker.py +0 -0
  35. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/jobs.py +0 -0
  36. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/core/watcher.py +0 -0
  37. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/prompts.py +0 -0
  38. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tool_definitions.py +0 -0
  39. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/__init__.py +0 -0
  40. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
  41. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/handlers/analysis_handlers.py +0 -0
  42. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/handlers/indexing_handlers.py +0 -0
  43. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/handlers/management_handlers.py +0 -0
  44. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/handlers/query_handlers.py +0 -0
  45. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/handlers/watcher_handlers.py +0 -0
  46. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/c.py +0 -0
  47. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/csharp.py +0 -0
  48. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/dart.py +0 -0
  49. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/elixir.py +0 -0
  50. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/haskell.py +0 -0
  51. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/java.py +0 -0
  52. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/javascript.py +0 -0
  53. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
  54. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/perl.py +0 -0
  55. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/php.py +0 -0
  56. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/python.py +0 -0
  57. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/ruby.py +0 -0
  58. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/rust.py +0 -0
  59. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/scala.py +0 -0
  60. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/swift.py +0 -0
  61. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/typescript.py +0 -0
  62. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/languages/typescriptjsx.py +0 -0
  63. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/package_resolver.py +0 -0
  64. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
  65. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
  66. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
  67. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/dart_toolkit.py +0 -0
  68. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
  69. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +0 -0
  70. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
  71. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
  72. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/perl_toolkit.py +0 -0
  73. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
  74. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
  75. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
  76. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +0 -0
  77. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/swift_toolkit.py +0 -0
  78. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
  79. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/scip_indexer.py +0 -0
  80. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/scip_pb2.py +0 -0
  81. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/tools/system.py +0 -0
  82. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/utils/debug_log.py +0 -0
  83. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/utils/visualize_graph.py +0 -0
  84. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext/viz/server.py +0 -0
  85. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
  86. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
  87. {codegraphcontext-0.3.7 → codegraphcontext-0.3.8}/src/codegraphcontext.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.3.7
3
+ Version: 0.3.8
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
@@ -51,6 +51,7 @@ Requires-Dist: nbformat
51
51
  Requires-Dist: nbconvert>=7.16.6
52
52
  Requires-Dist: pathspec>=0.12.1
53
53
  Requires-Dist: falkordb>=0.1.0
54
+ Requires-Dist: requests>=2.28.0
54
55
  Requires-Dist: falkordblite>=0.1.0; sys_platform != "win32" and python_version >= "3.12"
55
56
  Requires-Dist: fastapi>=0.100.0
56
57
  Requires-Dist: uvicorn>=0.22.0
@@ -162,7 +163,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
162
163
  ---
163
164
 
164
165
  ## Project Details
165
- - **Version:** 0.3.7
166
+ - **Version:** 0.3.8
166
167
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
167
168
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
168
169
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -271,7 +272,7 @@ pip install codegraphcontext
271
272
 
272
273
  ### If 'cgc' command isn't found, run our one-line fix:
273
274
  ```
274
- curl -sSL [https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh](https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh) | bash
275
+ curl -sSL https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh | bash
275
276
  ```
276
277
 
277
278
  ---
@@ -96,7 +96,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
96
96
  ---
97
97
 
98
98
  ## Project Details
99
- - **Version:** 0.3.7
99
+ - **Version:** 0.3.8
100
100
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
101
101
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
102
102
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -205,7 +205,7 @@ pip install codegraphcontext
205
205
 
206
206
  ### If 'cgc' command isn't found, run our one-line fix:
207
207
  ```
208
- curl -sSL [https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh](https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh) | bash
208
+ curl -sSL https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh | bash
209
209
  ```
210
210
 
211
211
  ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codegraphcontext"
3
- version = "0.3.7"
3
+ version = "0.3.8"
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"
@@ -30,6 +30,7 @@ dependencies = [
30
30
  "nbconvert>=7.16.6",
31
31
  "pathspec>=0.12.1",
32
32
  "falkordb>=0.1.0",
33
+ "requests>=2.28.0",
33
34
  "falkordblite>=0.1.0; sys_platform != 'win32' and python_version >= '3.12'",
34
35
  "fastapi>=0.100.0",
35
36
  "uvicorn>=0.22.0"
@@ -483,9 +483,10 @@ def clean_helper():
483
483
  console.print("[cyan]🧹 Cleaning database (removing orphaned nodes)...[/cyan]")
484
484
 
485
485
  try:
486
- # Determine if we're using FalkorDB or Neo4j for query optimization
486
+ # Determine backend type for query compatibility
487
487
  db_type = db_manager.__class__.__name__
488
488
  is_falkordb = "Falkor" in db_type
489
+ is_kuzu = "Kuzu" in db_type
489
490
 
490
491
  total_deleted = 0
491
492
  batch_size = 1000
@@ -493,14 +494,15 @@ def clean_helper():
493
494
  with db_manager.get_driver().session() as session:
494
495
  # Keep deleting orphaned nodes in batches until none are found
495
496
  while True:
496
- if is_falkordb:
497
- # FalkorDB-compatible query using OPTIONAL MATCH
497
+ if is_falkordb or is_kuzu:
498
+ # FalkorDB / KùzuDB-compatible query using OPTIONAL MATCH
499
+ # (KùzuDB does not support the Neo4j `NOT EXISTS { MATCH ... }` subquery syntax)
498
500
  query = """
499
501
  MATCH (n)
500
502
  WHERE NOT (n:Repository)
501
- OPTIONAL MATCH path = (n)-[*..10]-(r:Repository)
502
- WITH n, path
503
- WHERE path IS NULL
503
+ OPTIONAL MATCH p = (n)-[*..10]-(r:Repository)
504
+ WITH n, p
505
+ WHERE p IS NULL
504
506
  WITH n LIMIT $batch_size
505
507
  DETACH DELETE n
506
508
  RETURN count(n) as deleted
@@ -109,14 +109,21 @@ class KuzuDBManager:
109
109
  ("Parameter", "uid STRING, name STRING, path STRING, function_line_number INT64, PRIMARY KEY (uid)")
110
110
  ]
111
111
 
112
+ # rel_tables: list of (table_name, schema, use_group)
113
+ # use_group=True -> CREATE REL TABLE GROUP (for multi FROM..TO bindings)
114
+ # use_group=False -> CREATE REL TABLE (single binding)
112
115
  rel_tables = [
113
- ("CONTAINS", "FROM File TO Function, FROM File TO Class, FROM File TO Variable, FROM File TO Trait, FROM File TO Interface, FROM `Macro` TO `Macro`, FROM File TO `Macro`, FROM File TO Struct, FROM File TO Enum, FROM File TO `Union`, FROM File TO Annotation, FROM File TO Record, FROM File TO Property, FROM Repository TO Directory, FROM Directory TO Directory, FROM Directory TO File, FROM Repository TO File, FROM Class TO Function, FROM Function TO Function"),
114
- ("CALLS", "FROM Function TO Function, FROM Function TO Class, FROM File TO Function, FROM File TO Class, FROM Class TO Function, FROM Class TO Class, line_number INT64, args STRING[], full_call_name STRING"),
115
- ("IMPORTS", "FROM File TO Module, alias STRING, full_import_name STRING, imported_name STRING, line_number INT64"),
116
- ("INHERITS", "FROM Class TO Class, FROM Record TO Record, FROM Interface TO Interface"),
117
- ("HAS_PARAMETER", "FROM Function TO Parameter"),
118
- ("INCLUDES", "FROM Class TO Module"),
119
- ("IMPLEMENTS", "FROM Class TO Interface, FROM Struct TO Interface, FROM Record TO Interface")
116
+ # Note: in KùzuDB, some labels (e.g. `Macro`, `Property`, `Union`) are treated as reserved
117
+ # keywords in CREATE REL TABLE statements. We must escape them with backticks
118
+ # or the rel table creation will fail silently, leading to runtime
119
+ # "Binder exception: Table CONTAINS does not exist".
120
+ ("CONTAINS", "FROM File TO Function, FROM File TO Class, FROM File TO Variable, FROM File TO Trait, FROM File TO Interface, FROM `Macro` TO `Macro`, FROM File TO `Macro`, FROM File TO Struct, FROM File TO Enum, FROM File TO `Union`, FROM File TO Annotation, FROM File TO Record, FROM File TO `Property`, FROM Repository TO Directory, FROM Directory TO Directory, FROM Directory TO File, FROM Repository TO File, FROM Class TO Function, FROM Function TO Function", True),
121
+ ("CALLS", "FROM Function TO Function, FROM Function TO Class, FROM File TO Function, FROM File TO Class, FROM Class TO Function, FROM Class TO Class, line_number INT64, args STRING[], full_call_name STRING", True),
122
+ ("IMPORTS", "FROM File TO Module, alias STRING, full_import_name STRING, imported_name STRING, line_number INT64", False),
123
+ ("INHERITS", "FROM Class TO Class, FROM Record TO Record, FROM Interface TO Interface", True),
124
+ ("HAS_PARAMETER", "FROM Function TO Parameter", False),
125
+ ("INCLUDES", "FROM Class TO Module", False),
126
+ ("IMPLEMENTS", "FROM Class TO Interface, FROM Struct TO Interface, FROM Record TO Interface", True)
120
127
  ]
121
128
 
122
129
  for table_name, schema in node_tables:
@@ -127,10 +134,13 @@ class KuzuDBManager:
127
134
  warning_logger(f"Kuzu Schema Node Error ({table_name}): {e}")
128
135
  debug_log(f"Kuzu Schema Node Error ({table_name}): {e}")
129
136
 
130
- for table_name, schema in rel_tables:
137
+ for table_name, schema, use_group in rel_tables:
131
138
  try:
132
- # Need to handle backticks in schema as well for keywords
133
- self._conn.execute(f"CREATE REL TABLE `{table_name}`({schema})")
139
+ if use_group:
140
+ # KùzuDB requires CREATE REL TABLE GROUP for multi-binding relationships
141
+ self._conn.execute(f"CREATE REL TABLE GROUP `{table_name}`({schema})")
142
+ else:
143
+ self._conn.execute(f"CREATE REL TABLE `{table_name}`({schema})")
134
144
  except Exception as e:
135
145
  if "already exists" not in str(e).lower():
136
146
  warning_logger(f"Kuzu Schema Rel Error ({table_name}): {e}")
@@ -36,6 +36,40 @@ from .tools.handlers import (
36
36
  DEFAULT_EDIT_DISTANCE = 2
37
37
  DEFAULT_FUZZY_SEARCH = False
38
38
 
39
+ WORKSPACE_PREFIX = "/workspace/"
40
+
41
+
42
+ def _is_path_key(key: str) -> bool:
43
+ """Check if a dict key represents a file path field.
44
+
45
+ Matches keys like 'path', 'clone_path', 'caller_file_path', and also
46
+ Cypher-aliased keys like 'f.path', 'n.caller_file_path'.
47
+ """
48
+ # Strip Cypher alias prefix (e.g. "f.path" -> "path")
49
+ bare = key.rsplit(".", 1)[-1] if "." in key else key
50
+ return bare == "path" or bare.endswith("_path")
51
+
52
+
53
+ def _strip_path_value(value):
54
+ """Strip /workspace/ prefix from a single string value."""
55
+ if isinstance(value, str) and value.startswith(WORKSPACE_PREFIX):
56
+ return value[len(WORKSPACE_PREFIX):]
57
+ return value
58
+
59
+
60
+ def _strip_workspace_prefix(obj):
61
+ """Recursively strip /workspace/ prefix from path values in results."""
62
+ if isinstance(obj, dict):
63
+ return {
64
+ k: _strip_path_value(v) if _is_path_key(k) else _strip_workspace_prefix(v)
65
+ for k, v in obj.items()
66
+ }
67
+ elif isinstance(obj, list):
68
+ return [_strip_workspace_prefix(item) for item in obj]
69
+ return obj
70
+
71
+
72
+
39
73
  class MCPServer:
40
74
  """
41
75
  The main MCP Server class.
@@ -254,7 +288,8 @@ class MCPServer:
254
288
  tool_name = params.get('name')
255
289
  args = params.get('arguments', {})
256
290
  result = await self.handle_tool_call(tool_name, args)
257
-
291
+ result = _strip_workspace_prefix(result)
292
+
258
293
  if "error" in result:
259
294
  response = {
260
295
  "jsonrpc": "2.0", "id": request_id,
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Literal, Optional
5
5
  from pathlib import Path
6
6
 
7
7
  from ..core.database import DatabaseManager
8
+ from ..utils.path_ignore import cypher_path_not_under_ignore_dirs
8
9
 
9
10
  logger = logging.getLogger(__name__)
10
11
 
@@ -540,10 +541,12 @@ class CodeFinder:
540
541
  with self.driver.session() as session:
541
542
  repo_filter = "AND func.path STARTS WITH $repo_path" if repo_path else ""
542
543
  decorator_filter = "AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)" if exclude_decorated_with else ""
544
+ func_ignore = cypher_path_not_under_ignore_dirs("func.path")
545
+ caller_ignore = cypher_path_not_under_ignore_dirs("caller.path")
543
546
 
544
547
  query = f"""
545
548
  MATCH (func:Function)
546
- WHERE func.is_dependency = false {repo_filter}
549
+ WHERE func.is_dependency = false {repo_filter} {func_ignore}
547
550
  AND NOT func.name IN ['main', 'setup', 'run']
548
551
  AND NOT (func.name STARTS WITH '__' AND func.name ENDS WITH '__')
549
552
  AND NOT func.name STARTS WITH '_test'
@@ -555,7 +558,7 @@ class CodeFinder:
555
558
  {decorator_filter}
556
559
  WITH func
557
560
  OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
558
- WHERE caller.is_dependency = false
561
+ WHERE caller.is_dependency = false {caller_ignore}
559
562
  WITH func, count(caller) as caller_count
560
563
  WHERE caller_count = 0
561
564
  OPTIONAL MATCH (file:File)-[:CONTAINS]->(func)
@@ -591,8 +594,8 @@ class CodeFinder:
591
594
  # KùzuDB-compatible: Use anonymous end node and filter with WHERE
592
595
  query = f"""
593
596
  MATCH p = (f:Function)-[:CALLS*]->()
594
- WITH f, p, nodes(p) as path_nodes
595
- WITH f, path_nodes, list_extract(path_nodes, size(path_nodes)) as target
597
+ WITH f as f, p as p, nodes(p) as path_nodes
598
+ WITH f as f, path_nodes as path_nodes, path_nodes[size(path_nodes)] as target
596
599
  WHERE target.name = $function_name AND target.path = $path {repo_filter}
597
600
  RETURN DISTINCT f.name AS caller_name, f.path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
598
601
  ORDER BY caller_is_dependency ASC, caller_file_path, caller_line_number
@@ -603,8 +606,8 @@ class CodeFinder:
603
606
  # KùzuDB-compatible: Use anonymous end node and filter with WHERE
604
607
  query = f"""
605
608
  MATCH p = (f:Function)-[:CALLS*]->()
606
- WITH f, p, nodes(p) as path_nodes
607
- WITH f, path_nodes, list_extract(path_nodes, size(path_nodes)) as target
609
+ WITH f as f, p as p, nodes(p) as path_nodes
610
+ WITH f as f, path_nodes as path_nodes, path_nodes[size(path_nodes)] as target
608
611
  WHERE target.name = $function_name {repo_filter}
609
612
  RETURN DISTINCT f.name AS caller_name, f.path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
610
613
  ORDER BY caller_is_dependency ASC, caller_file_path, caller_line_number
@@ -622,8 +625,8 @@ class CodeFinder:
622
625
  query = f"""
623
626
  MATCH (caller:Function {{name: $function_name, path: $path}})
624
627
  MATCH p = (caller)-[:CALLS*]->()
625
- WITH p, nodes(p) as path_nodes
626
- WITH list_extract(path_nodes, size(path_nodes)) as f
628
+ WITH p as p, nodes(p) as path_nodes
629
+ WITH path_nodes[size(path_nodes)] as f
627
630
  {repo_filter}
628
631
  RETURN DISTINCT f.name AS callee_name, f.path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
629
632
  ORDER BY callee_is_dependency ASC, callee_file_path, callee_line_number
@@ -635,8 +638,8 @@ class CodeFinder:
635
638
  query = f"""
636
639
  MATCH (caller:Function {{name: $function_name}})
637
640
  MATCH p = (caller)-[:CALLS*]->()
638
- WITH p, nodes(p) as path_nodes
639
- WITH list_extract(path_nodes, size(path_nodes)) as f
641
+ WITH p as p, nodes(p) as path_nodes
642
+ WITH path_nodes[size(path_nodes)] as f
640
643
  {repo_filter}
641
644
  RETURN DISTINCT f.name AS callee_name, f.path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
642
645
  ORDER BY callee_is_dependency ASC, callee_file_path, callee_line_number
@@ -657,24 +660,12 @@ class CodeFinder:
657
660
  query = f"""
658
661
  MATCH (start:Function {start_props}), (end_target:Function {end_props})
659
662
  {repo_filter}
660
- WITH start, end_target
663
+ WITH start as start, end_target as end_target
661
664
  MATCH path = (start)-[:CALLS*1..{max_depth}]->()
662
- WITH path, end_target, nodes(path) as func_nodes, relationships(path) as call_rels
663
- WITH path, func_nodes, call_rels, list_extract(func_nodes, size(func_nodes)) as path_end
665
+ WITH path as path, end_target as end_target, nodes(path) as func_nodes, relationships(path) as call_rels
666
+ WITH path as path, func_nodes as func_nodes, call_rels as call_rels, end_target as end_target, func_nodes[size(func_nodes)] as path_end
664
667
  WHERE path_end.name = end_target.name AND (end_target.path IS NULL OR path_end.path = end_target.path)
665
- RETURN
666
- [node in func_nodes | {{
667
- name: node.name,
668
- path: node.path,
669
- line_number: node.line_number,
670
- is_dependency: node.is_dependency
671
- }}] as function_chain,
672
- [rel in call_rels | {{
673
- call_line: rel.line_number,
674
- args: rel.args,
675
- full_call_name: rel.full_call_name
676
- }}] as call_details,
677
- length(path) as chain_length
668
+ RETURN func_nodes as function_nodes, call_rels as call_nodes, size(call_rels) as chain_length
678
669
  ORDER BY chain_length ASC
679
670
  LIMIT 20
680
671
  """
@@ -689,7 +680,67 @@ class CodeFinder:
689
680
  }
690
681
 
691
682
  result = session.run(query, **params)
692
- return result.data()
683
+
684
+ # Post-process Node/Rel objects into plain dicts so CLI output stays stable
685
+ rows = result.data()
686
+ transformed: List[Dict[str, Any]] = []
687
+ for row in rows:
688
+ func_nodes = row.get("function_nodes") or []
689
+ rel_nodes = row.get("call_nodes") or []
690
+ chain_len = row.get("chain_length", 0)
691
+
692
+ function_chain = []
693
+ for n in func_nodes:
694
+ # Depending on KùzuDB + driver wrapping, list elements can arrive
695
+ # either as Node/Rel objects or already-materialized dicts.
696
+ if isinstance(n, dict):
697
+ props = n
698
+ else:
699
+ props = None
700
+ try:
701
+ props = n.get_properties()
702
+ except Exception:
703
+ props = getattr(n, "properties", None)
704
+ if props is None:
705
+ props = {}
706
+ function_chain.append(
707
+ {
708
+ "name": props.get("name"),
709
+ "path": props.get("path"),
710
+ "line_number": props.get("line_number"),
711
+ "is_dependency": props.get("is_dependency"),
712
+ }
713
+ )
714
+
715
+ call_details = []
716
+ for r in rel_nodes:
717
+ if isinstance(r, dict):
718
+ props = r
719
+ else:
720
+ props = None
721
+ try:
722
+ props = r.get_properties()
723
+ except Exception:
724
+ props = getattr(r, "properties", None)
725
+ if props is None:
726
+ props = {}
727
+ call_details.append(
728
+ {
729
+ "call_line": props.get("line_number"),
730
+ "args": props.get("args"),
731
+ "full_call_name": props.get("full_call_name"),
732
+ }
733
+ )
734
+
735
+ transformed.append(
736
+ {
737
+ "function_chain": function_chain,
738
+ "call_details": call_details,
739
+ "chain_length": chain_len,
740
+ }
741
+ )
742
+
743
+ return transformed
693
744
 
694
745
  def find_by_type(self, element_type: str, limit: int = 50) -> List[Dict]:
695
746
  """Find all elements of a specific type (Function, Class, File, Module)."""
@@ -998,9 +1049,10 @@ class CodeFinder:
998
1049
  """Find the most complex functions based on cyclomatic complexity."""
999
1050
  with self.driver.session() as session:
1000
1051
  repo_filter = "AND f.path STARTS WITH $repo_path" if repo_path else ""
1052
+ path_ignore = cypher_path_not_under_ignore_dirs("f.path")
1001
1053
  query = f"""
1002
1054
  MATCH (f:Function)
1003
- WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false {repo_filter}
1055
+ WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false {repo_filter} {path_ignore}
1004
1056
  RETURN f.name as function_name, f.path as path, f.cyclomatic_complexity as complexity, f.line_number as line_number
1005
1057
  ORDER BY f.cyclomatic_complexity DESC
1006
1058
  LIMIT $limit
@@ -14,9 +14,22 @@ from ..utils.debug_log import debug_log, info_logger, error_logger, warning_logg
14
14
  from tree_sitter import Language, Parser
15
15
  from ..utils.tree_sitter_manager import get_tree_sitter_manager
16
16
  from ..cli.config_manager import get_config_value
17
+ from ..utils.path_ignore import file_path_has_ignore_dir_segment
17
18
  import fnmatch
18
19
 
19
20
  DEFAULT_IGNORE_PATTERNS = [
21
+ # Vendor / env dirs (gitignore-style; complements IGNORE_DIRS during indexing)
22
+ "node_modules/",
23
+ "venv/",
24
+ ".venv/",
25
+ "env/",
26
+ ".env/",
27
+ "dist/",
28
+ "build/",
29
+ "target/",
30
+ "out/",
31
+ ".git/",
32
+ "__pycache__/",
20
33
  "*.png",
21
34
  "*.jpg",
22
35
  "*.jpeg",
@@ -210,6 +223,12 @@ class GraphBuilder:
210
223
  except Exception as e:
211
224
  warning_logger(f"Schema creation warning: {e}")
212
225
 
226
+ # Neo4j RANGE indexes have an ~8 kB key-size limit. Long C++ template
227
+ # function names (e.g. from llama.cpp) can exceed this, causing
228
+ # "Property value is too large to index" errors. We cap string properties
229
+ # at 4096 chars, which is comfortably under the 8 kB boundary.
230
+ _MAX_STR_LEN = 4096
231
+
213
232
  @staticmethod
214
233
  def _sanitize_props(props: Dict) -> Dict:
215
234
  """Return a copy of *props* with all values coerced to database-safe types.
@@ -220,9 +239,15 @@ class GraphBuilder:
220
239
  parsers (e.g. C's ``detailed_args`` or Scala's tuple ``class_context``)
221
240
  are serialized to a JSON string so the data is preserved rather than
222
241
  being silently dropped.
242
+
243
+ Additionally, string values are truncated to _MAX_STR_LEN characters to
244
+ avoid Neo4j's RANGE-index 8 kB property-size limit (triggered by very
245
+ long C++ template-mangled function names).
223
246
  """
224
247
  import json
225
248
 
249
+ MAX = GraphBuilder._MAX_STR_LEN
250
+
226
251
  def _is_primitive(v):
227
252
  return isinstance(v, (str, int, float, bool)) or v is None
228
253
 
@@ -230,15 +255,21 @@ class GraphBuilder:
230
255
  return isinstance(v, list) and all(_is_primitive(item) for item in v)
231
256
 
232
257
  def _coerce(v):
258
+ if isinstance(v, str):
259
+ # Truncate long strings to stay within Neo4j RANGE index limits
260
+ return v[:MAX] if len(v) > MAX else v
233
261
  if _is_primitive(v):
234
262
  return v
235
263
  if _is_flat_list(v):
236
- return v
264
+ # Truncate any long strings in lists too
265
+ return [s[:MAX] if isinstance(s, str) and len(s) > MAX else s for s in v]
237
266
  # Tuples, dicts, lists-of-dicts, nested structures → JSON string
238
267
  try:
239
- return json.dumps(v, default=str)
268
+ serialized = json.dumps(v, default=str)
269
+ return serialized[:MAX] if len(serialized) > MAX else serialized
240
270
  except Exception:
241
- return str(v)
271
+ s = str(v)
272
+ return s[:MAX] if len(s) > MAX else s
242
273
 
243
274
  return {k: _coerce(v) for k, v in props.items()}
244
275
 
@@ -446,8 +477,20 @@ class GraphBuilder:
446
477
  # before writing to the database to avoid runtime errors such as
447
478
  # "Property values can only be of primitive types or arrays of
448
479
  # primitive types" raised by FalkorDB / KùzuDB.
480
+ # _sanitize_props also truncates long strings to avoid Neo4j's
481
+ # 8 kB RANGE-index limit (seen with long C++ template names).
449
482
  safe_props = self._sanitize_props(item)
450
- session.run(query, path=file_path_str, name=item['name'], line_number=item['line_number'], props=safe_props)
483
+ try:
484
+ session.run(query, path=file_path_str, name=item['name'], line_number=item['line_number'], props=safe_props)
485
+ except Exception as node_err:
486
+ err_str = str(node_err)
487
+ if "too large to index" in err_str or "property size" in err_str.lower():
488
+ warning_logger(
489
+ f"Skipping {label} '{item['name']}' in {file_path_str}: "
490
+ f"property value too large for index (name length={len(item['name'])})"
491
+ )
492
+ else:
493
+ raise # Re-raise unexpected errors
451
494
 
452
495
  if label == 'Function':
453
496
  for arg_name in item.get('args', []):
@@ -517,30 +560,51 @@ class GraphBuilder:
517
560
  # Ensure full_import_name is available in params for SET clause
518
561
  params = imp.copy()
519
562
  params['path'] = file_path_str
520
- params['rel_props'] = rel_props
521
563
  params['module_name'] = imp.get('name') # Use 'name' from imp as module name
522
564
 
565
+ # Sanitize scalar params but keep rel_props as a dict
566
+ # (FalkorDB SET r += $rel_props requires a map, not a string)
567
+ sanitized = self._sanitize_props(params)
568
+ sanitized['rel_props'] = rel_props
569
+
523
570
  session.run(f"""
524
571
  MATCH (f:File {{path: $path}})
525
572
  MERGE (m:Module {{name: $module_name}})
526
573
  {set_clause_str}
527
574
  MERGE (f)-[r:IMPORTS]->(m)
528
575
  SET r += $rel_props
529
- """, **params)
576
+ """, **sanitized)
530
577
 
531
578
 
532
579
  # Handle CONTAINS relationship between class to their children like variables
533
580
  for func in file_data.get('functions', []):
534
581
  if func.get('class_context'):
535
- session.run("""
582
+ # Try same-file match first (Python, JS, etc.)
583
+ if not self._safe_run_create(session, """
536
584
  MATCH (c:Class {name: $class_name, path: $path})
537
585
  MATCH (fn:Function {name: $func_name, path: $path, line_number: $func_line})
538
586
  MERGE (c)-[:CONTAINS]->(fn)
539
- """,
540
- class_name=func['class_context'],
541
- path=file_path_str,
542
- func_name=func['name'],
543
- func_line=func['line_number'])
587
+ RETURN count(*) as created
588
+ """, {
589
+ 'class_name': func['class_context'],
590
+ 'path': file_path_str,
591
+ 'func_name': func['name'],
592
+ 'func_line': func['line_number']
593
+ }):
594
+ # Cross-file match for C/C++ where class is in .h and method in .cpp.
595
+ # Note: matches by class name only (no path constraint), so classes
596
+ # with identical names in different files could get false links.
597
+ self._safe_run_create(session, """
598
+ MATCH (c:Class {name: $class_name})
599
+ MATCH (fn:Function {name: $func_name, path: $path, line_number: $func_line})
600
+ MERGE (c)-[:CONTAINS]->(fn)
601
+ RETURN count(*) as created
602
+ """, {
603
+ 'class_name': func['class_context'],
604
+ 'path': file_path_str,
605
+ 'func_name': func['name'],
606
+ 'func_line': func['line_number']
607
+ })
544
608
 
545
609
  # --- NEW: Class INCLUDES Module (Ruby mixins) ---
546
610
  for inc in file_data.get('module_inclusions', []):
@@ -693,7 +757,7 @@ class GraphBuilder:
693
757
 
694
758
  # KùzuDB workaround: Try Function->Function first, then other combinations
695
759
  # This avoids polymorphic MERGE which KùzuDB doesn't support
696
- call_params = {
760
+ call_params = self._sanitize_props({
697
761
  'caller_name': caller_name,
698
762
  'caller_file_path': caller_file_path,
699
763
  'caller_line_number': caller_line_number,
@@ -702,8 +766,9 @@ class GraphBuilder:
702
766
  'line_number': call['line_number'],
703
767
  'args': call.get('args', []),
704
768
  'full_call_name': call.get('full_name', called_name)
705
- }
706
- # Try Function caller -> Function callee
769
+ })
770
+
771
+ # Try Function caller -> Function callee
707
772
  if not self._safe_run_create(session, """
708
773
  OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
709
774
  OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
@@ -782,14 +847,15 @@ class GraphBuilder:
782
847
  """, call_params)
783
848
  else:
784
849
  # File-level calls: Try Function first, then Class
785
- call_params = {
850
+ call_params = self._sanitize_props({
786
851
  'caller_file_path': caller_file_path,
787
852
  'called_name': called_name,
788
853
  'called_file_path': resolved_path,
789
854
  'line_number': call['line_number'],
790
855
  'args': call.get('args', []),
791
856
  'full_call_name': call.get('full_name', called_name)
792
- }
857
+ })
858
+
793
859
 
794
860
  if not self._safe_run_create(session, """
795
861
  OPTIONAL MATCH (caller:File {path: $caller_file_path})
@@ -1162,13 +1228,16 @@ class GraphBuilder:
1162
1228
 
1163
1229
  # Step 4: Write nodes to graph using existing add_file_to_graph()
1164
1230
  processed = 0
1231
+ index_root = path.resolve()
1165
1232
  for abs_path_str, file_data in files_data.items():
1166
- file_data["repo_path"] = str(path.resolve())
1233
+ file_path = Path(abs_path_str)
1234
+ if file_path.is_file() and file_path_has_ignore_dir_segment(file_path, index_root):
1235
+ continue
1236
+ file_data["repo_path"] = str(index_root)
1167
1237
  if job_id:
1168
1238
  self.job_manager.update_job(job_id, current_file=abs_path_str)
1169
1239
 
1170
1240
  # Step 5: Tree-sitter supplement — add source text, complexity, imports and bases
1171
- file_path = Path(abs_path_str)
1172
1241
  ts_parser = self.get_parser(file_path.suffix)
1173
1242
  if file_path.exists() and ts_parser:
1174
1243
  try: