codegraphcontext 0.3.8__tar.gz → 0.3.9__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 (88) hide show
  1. {codegraphcontext-0.3.8/src/codegraphcontext.egg-info → codegraphcontext-0.3.9}/PKG-INFO +3 -2
  2. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/README.md +2 -1
  3. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/pyproject.toml +1 -1
  4. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/cli_helpers.py +121 -98
  5. codegraphcontext-0.3.9/src/codegraphcontext/cli/config_manager.py +878 -0
  6. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/main.py +252 -122
  7. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/__init__.py +11 -12
  8. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/cgc_bundle.py +95 -21
  9. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/database_falkordb.py +24 -11
  10. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/database_kuzu.py +194 -34
  11. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/watcher.py +85 -28
  12. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/server.py +10 -4
  13. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/code_finder.py +37 -36
  14. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/graph_builder.py +716 -575
  15. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9/src/codegraphcontext.egg-info}/PKG-INFO +3 -2
  16. codegraphcontext-0.3.8/src/codegraphcontext/cli/config_manager.py +0 -412
  17. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/LICENSE +0 -0
  18. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/MANIFEST.in +0 -0
  19. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/setup.cfg +0 -0
  20. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/__init__.py +0 -0
  21. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/__main__.py +0 -0
  22. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/__init__.py +0 -0
  23. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/registry_commands.py +0 -0
  24. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/setup_macos.py +0 -0
  25. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/setup_wizard.py +0 -0
  26. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/cli/visualizer.py +0 -0
  27. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/bundle_registry.py +0 -0
  28. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/database.py +0 -0
  29. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/database_falkordb_remote.py +0 -0
  30. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/falkor_worker.py +0 -0
  31. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/core/jobs.py +0 -0
  32. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/prompts.py +0 -0
  33. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tool_definitions.py +0 -0
  34. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/__init__.py +0 -0
  35. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
  36. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/handlers/analysis_handlers.py +0 -0
  37. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/handlers/indexing_handlers.py +0 -0
  38. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/handlers/management_handlers.py +0 -0
  39. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/handlers/query_handlers.py +0 -0
  40. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/handlers/watcher_handlers.py +0 -0
  41. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/c.py +0 -0
  42. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/cpp.py +0 -0
  43. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/csharp.py +0 -0
  44. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/dart.py +0 -0
  45. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/elixir.py +0 -0
  46. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/go.py +0 -0
  47. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/haskell.py +0 -0
  48. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/java.py +0 -0
  49. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/javascript.py +0 -0
  50. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
  51. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/perl.py +0 -0
  52. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/php.py +0 -0
  53. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/python.py +0 -0
  54. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/ruby.py +0 -0
  55. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/rust.py +0 -0
  56. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/scala.py +0 -0
  57. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/swift.py +0 -0
  58. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/typescript.py +0 -0
  59. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/languages/typescriptjsx.py +0 -0
  60. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/package_resolver.py +0 -0
  61. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
  62. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
  63. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
  64. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/dart_toolkit.py +0 -0
  65. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
  66. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +0 -0
  67. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
  68. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
  69. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/perl_toolkit.py +0 -0
  70. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
  71. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
  72. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
  73. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +0 -0
  74. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/swift_toolkit.py +0 -0
  75. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
  76. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/scip_indexer.py +0 -0
  77. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/scip_pb2.py +0 -0
  78. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/tools/system.py +0 -0
  79. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/utils/debug_log.py +0 -0
  80. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/utils/path_ignore.py +0 -0
  81. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/utils/tree_sitter_manager.py +0 -0
  82. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/utils/visualize_graph.py +0 -0
  83. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext/viz/server.py +0 -0
  84. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext.egg-info/SOURCES.txt +0 -0
  85. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
  86. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
  87. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/src/codegraphcontext.egg-info/requires.txt +0 -0
  88. {codegraphcontext-0.3.8 → codegraphcontext-0.3.9}/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.8
3
+ Version: 0.3.9
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
@@ -73,6 +73,7 @@ Dynamic: license-file
73
73
  - 🇬🇧 [English](README.md)
74
74
  - 🇨🇳 [中文](README.zh-CN.md)
75
75
  - 🇰🇷 [한국어](README.kor.md)
76
+ - 🇺🇦 [Українська](README.uk.md)
76
77
  - 🇯🇵 日本語 (Soon)
77
78
  - 🇷🇺 Русский (Soon)
78
79
  - 🇪🇸 Español (Soon)
@@ -163,7 +164,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
163
164
  ---
164
165
 
165
166
  ## Project Details
166
- - **Version:** 0.3.8
167
+ - **Version:** 0.3.9
167
168
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
168
169
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
169
170
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -6,6 +6,7 @@
6
6
  - 🇬🇧 [English](README.md)
7
7
  - 🇨🇳 [中文](README.zh-CN.md)
8
8
  - 🇰🇷 [한국어](README.kor.md)
9
+ - 🇺🇦 [Українська](README.uk.md)
9
10
  - 🇯🇵 日本語 (Soon)
10
11
  - 🇷🇺 Русский (Soon)
11
12
  - 🇪🇸 Español (Soon)
@@ -96,7 +97,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
96
97
  ---
97
98
 
98
99
  ## Project Details
99
- - **Version:** 0.3.8
100
+ - **Version:** 0.3.9
100
101
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
101
102
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
102
103
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codegraphcontext"
3
- version = "0.3.8"
3
+ version = "0.3.9"
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"
@@ -4,7 +4,8 @@ import uuid
4
4
  import urllib.parse
5
5
  from pathlib import Path
6
6
  import time
7
- from typing import Optional
7
+ import os
8
+ from typing import Optional, List, Dict, Any
8
9
  from rich.console import Console
9
10
  from rich.table import Table
10
11
  from rich.progress import (
@@ -22,18 +23,41 @@ from ..core.jobs import JobManager
22
23
  from ..tools.code_finder import CodeFinder
23
24
  from ..tools.graph_builder import GraphBuilder
24
25
  from ..tools.package_resolver import get_local_package_path
26
+ from ..utils.debug_log import info_logger, warning_logger
27
+ from .config_manager import resolve_context, ResolvedContext, register_repo_in_context, ensure_first_run_bootstrap
25
28
 
26
29
  console = Console()
27
30
 
28
31
 
29
- def _initialize_services():
30
- """Initializes and returns core service managers."""
32
+ def _initialize_services(cli_context_flag: Optional[str] = None) -> tuple[Any, Any, Any, ResolvedContext]:
33
+ """
34
+ Initializes and returns core service managers based on the resolved context.
35
+ Returns (db_manager, graph_builder, code_finder, resolved_context).
36
+ """
37
+ ensure_first_run_bootstrap()
38
+ console.print("[dim]Resolving context...[/dim]")
39
+ ctx = resolve_context(cli_context_flag)
40
+
41
+ # Let the user know what context we're operating in
42
+ if ctx.mode == "named":
43
+ console.print(f"[cyan]Context:[/cyan] {ctx.context_name} (Database: {ctx.database})")
44
+ elif ctx.mode == "per-repo":
45
+ console.print(f"[cyan]Context:[/cyan] Per-repo local mode (Database: {ctx.database})")
46
+ else:
47
+ # Default global mode — silent to keep CLI clean for existing users
48
+ pass
49
+
31
50
  console.print("[dim]Initializing services and database connection...[/dim]")
32
51
  try:
33
- db_manager = get_database_manager()
52
+ # Override the database backend with the context's specific choice
53
+ if ctx.database:
54
+ os.environ['CGC_RUNTIME_DB_TYPE'] = ctx.database
55
+
56
+ # Pass the exact DB path resolved from the context
57
+ db_manager = get_database_manager(db_path=ctx.db_path)
34
58
  except ValueError as e:
35
59
  console.print(f"[bold red]Database Configuration Error:[/bold red] {e}")
36
- return None, None, None
60
+ return None, None, None, ctx
37
61
 
38
62
  try:
39
63
  db_manager.get_driver()
@@ -58,11 +82,11 @@ def _initialize_services():
58
82
  console.print("[green]✓[/green] Successfully switched to KùzuDB fallback")
59
83
  except Exception as kuzu_e:
60
84
  console.print(f"[bold red]Critical Error:[/bold red] Both FalkorDB and KùzuDB failed: {kuzu_e}")
61
- return None, None, None
85
+ return None, None, None, ctx
62
86
  else:
63
87
  console.print(f"[bold red]Database Connection Error:[/bold red] {e}")
64
88
  console.print("Please ensure your database is configured correctly or run 'cgc doctor'.")
65
- return None, None, None
89
+ return None, None, None, ctx
66
90
 
67
91
  # The GraphBuilder requires an event loop, even for synchronous-style execution
68
92
  try:
@@ -74,10 +98,10 @@ def _initialize_services():
74
98
  graph_builder = GraphBuilder(db_manager, JobManager(), loop)
75
99
  code_finder = CodeFinder(db_manager)
76
100
  console.print("[dim]Services initialized.[/dim]")
77
- return db_manager, graph_builder, code_finder
101
+ return db_manager, graph_builder, code_finder, ctx
78
102
 
79
103
 
80
- async def _run_index_with_progress(graph_builder: GraphBuilder, path_obj: Path, is_dependency: bool = False):
104
+ async def _run_index_with_progress(graph_builder: GraphBuilder, path_obj: Path, is_dependency: bool = False, cgcignore_path: str = None):
81
105
  """Internal helper to run indexing with a Live progress bar."""
82
106
  job_id = graph_builder.job_manager.create_job(str(path_obj), is_dependency=is_dependency)
83
107
 
@@ -101,7 +125,7 @@ async def _run_index_with_progress(graph_builder: GraphBuilder, path_obj: Path,
101
125
  )
102
126
 
103
127
  indexing_task = asyncio.create_task(
104
- graph_builder.build_graph_from_path_async(path_obj, is_dependency=is_dependency, job_id=job_id)
128
+ graph_builder.build_graph_from_path_async(path_obj, is_dependency=is_dependency, job_id=job_id, cgcignore_path=cgcignore_path)
105
129
  )
106
130
 
107
131
  from ..core.jobs import JobStatus
@@ -135,14 +159,14 @@ async def _run_index_with_progress(graph_builder: GraphBuilder, path_obj: Path,
135
159
  raise e
136
160
 
137
161
 
138
- def index_helper(path: str):
139
- """Synchronously indexes a repository."""
162
+ def index_helper(path: str, context: Optional[str] = None):
163
+ """Synchronously indexes a repository in a given context."""
140
164
  time_start = time.time()
141
- services = _initialize_services()
142
- if not all(services):
165
+ services = _initialize_services(context)
166
+ if not all(services[:3]):
143
167
  return
144
168
 
145
- db_manager, graph_builder, code_finder = services
169
+ db_manager, graph_builder, code_finder, ctx = services
146
170
  path_obj = Path(path).resolve()
147
171
 
148
172
  if not path_obj.exists():
@@ -155,10 +179,12 @@ def index_helper(path: str):
155
179
 
156
180
  if repo_exists:
157
181
  # Check if the repository actually has files (not just an empty node from interrupted indexing)
182
+ # Use variable-length path to handle both flat (Repository->File) and
183
+ # hierarchical (Repository->Directory->...->File) graph structures
158
184
  try:
159
185
  with db_manager.get_driver().session() as session:
160
186
  result = session.run(
161
- "MATCH (r:Repository {path: $path})-[:CONTAINS]->(f:File) RETURN count(f) as file_count",
187
+ "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(f:File) RETURN count(f) as file_count",
162
188
  path=str(path_obj)
163
189
  )
164
190
  record = result.single()
@@ -174,10 +200,14 @@ def index_helper(path: str):
174
200
  except Exception as e:
175
201
  console.print(f"[yellow]Warning: Could not check file count: {e}. Proceeding with indexing...[/yellow]")
176
202
 
203
+ # Auto-register the repo into the named context (auto-creates if needed)
204
+ if context and ctx.mode == "named":
205
+ register_repo_in_context(context, str(path_obj), auto_create=True)
206
+
177
207
  console.print(f"Starting indexing for: {path_obj}")
178
208
 
179
209
  try:
180
- asyncio.run(_run_index_with_progress(graph_builder, path_obj, is_dependency=False))
210
+ asyncio.run(_run_index_with_progress(graph_builder, path_obj, is_dependency=False, cgcignore_path=ctx.cgcignore_path))
181
211
  time_end = time.time()
182
212
  elapsed = time_end - time_start
183
213
  console.print(f"[green]Successfully finished indexing: {path} in {elapsed:.2f} seconds[/green]")
@@ -200,13 +230,13 @@ def index_helper(path: str):
200
230
  db_manager.close_driver()
201
231
 
202
232
 
203
- def add_package_helper(package_name: str, language: str):
233
+ def add_package_helper(package_name: str, language: str, context: Optional[str] = None):
204
234
  """Synchronously indexes a package."""
205
- services = _initialize_services()
206
- if not all(services):
235
+ services = _initialize_services(context)
236
+ if not all(services[:3]):
207
237
  return
208
238
 
209
- db_manager, graph_builder, code_finder = services
239
+ db_manager, graph_builder, code_finder, ctx = services
210
240
 
211
241
  package_path_str = get_local_package_path(package_name, language)
212
242
  if not package_path_str:
@@ -225,7 +255,7 @@ def add_package_helper(package_name: str, language: str):
225
255
  console.print(f"Starting indexing for package '{package_name}' at: {package_path}")
226
256
 
227
257
  try:
228
- asyncio.run(_run_index_with_progress(graph_builder, package_path, is_dependency=True))
258
+ asyncio.run(_run_index_with_progress(graph_builder, package_path, is_dependency=True, cgcignore_path=ctx.cgcignore_path))
229
259
  console.print(f"[green]Successfully finished indexing package: {package_name}[/green]")
230
260
  except Exception as e:
231
261
  console.print(f"[bold red]An error occurred during package indexing:[/bold red] {e}")
@@ -233,13 +263,13 @@ def add_package_helper(package_name: str, language: str):
233
263
  db_manager.close_driver()
234
264
 
235
265
 
236
- def list_repos_helper():
266
+ def list_repos_helper(context: Optional[str] = None):
237
267
  """Lists all indexed repositories."""
238
- services = _initialize_services()
239
- if not all(services):
268
+ services = _initialize_services(context)
269
+ if not all(services[:3]):
240
270
  return
241
271
 
242
- db_manager, _, code_finder = services
272
+ db_manager, _, code_finder, ctx = services
243
273
 
244
274
  try:
245
275
  repos = code_finder.list_indexed_repositories()
@@ -263,13 +293,13 @@ def list_repos_helper():
263
293
  db_manager.close_driver()
264
294
 
265
295
 
266
- def delete_helper(repo_path: str):
296
+ def delete_helper(repo_path: str, context: Optional[str] = None):
267
297
  """Deletes a repository from the graph."""
268
- services = _initialize_services()
269
- if not all(services):
298
+ services = _initialize_services(context)
299
+ if not all(services[:3]):
270
300
  return
271
301
 
272
- db_manager, graph_builder, _ = services
302
+ db_manager, graph_builder, _, ctx = services
273
303
 
274
304
  try:
275
305
  if graph_builder.delete_repository_from_graph(repo_path):
@@ -283,13 +313,13 @@ def delete_helper(repo_path: str):
283
313
  db_manager.close_driver()
284
314
 
285
315
 
286
- def cypher_helper(query: str):
316
+ def cypher_helper(query: str, context: Optional[str] = None):
287
317
  """Executes a read-only Cypher query."""
288
- services = _initialize_services()
289
- if not all(services):
318
+ services = _initialize_services(context)
319
+ if not all(services[:3]):
290
320
  return
291
321
 
292
- db_manager, _, _ = services
322
+ db_manager, _, _, ctx = services
293
323
 
294
324
  # Replicating safety checks from MCPServer
295
325
  forbidden_keywords = ['CREATE', 'MERGE', 'DELETE', 'SET', 'REMOVE', 'DROP', 'CALL apoc']
@@ -309,15 +339,15 @@ def cypher_helper(query: str):
309
339
  db_manager.close_driver()
310
340
 
311
341
 
312
- def cypher_helper_visual(query: str):
342
+ def cypher_helper_visual(query: str, context: Optional[str] = None):
313
343
  """Executes a read-only Cypher query and visualizes the results."""
314
344
  from .visualizer import visualize_cypher_results
315
345
 
316
- services = _initialize_services()
317
- if not all(services):
346
+ services = _initialize_services(context)
347
+ if not all(services[:3]):
318
348
  return
319
349
 
320
- db_manager, _, _ = services
350
+ db_manager, _, _, ctx = services
321
351
 
322
352
  # Replicating safety checks from MCPServer
323
353
  forbidden_keywords = ['CREATE', 'MERGE', 'DELETE', 'SET', 'REMOVE', 'DROP', 'CALL apoc']
@@ -346,13 +376,13 @@ import uvicorn
346
376
  import urllib.parse
347
377
  from ..viz.server import run_server, set_db_manager
348
378
 
349
- def visualize_helper(repo_path: Optional[str] = None, port: int = 8000):
379
+ def visualize_helper(repo_path: Optional[str] = None, port: int = 8000, context: Optional[str] = None):
350
380
  """"Generates an interactive visualization using the Playground UI."""
351
- services = _initialize_services()
352
- if not all(services):
381
+ services = _initialize_services(context)
382
+ if not all(services[:3]):
353
383
  return
354
384
 
355
- db_manager, _, _ = services
385
+ db_manager, _, _, ctx = services
356
386
 
357
387
  # Set the DB manager for the server
358
388
  set_db_manager(db_manager)
@@ -424,14 +454,14 @@ def visualize_helper(repo_path: Optional[str] = None, port: int = 8000):
424
454
  db_manager.close_driver()
425
455
 
426
456
 
427
- def reindex_helper(path: str):
457
+ def reindex_helper(path: str, context: Optional[str] = None):
428
458
  """Force re-index by deleting and rebuilding the repository."""
429
459
  time_start = time.time()
430
- services = _initialize_services()
431
- if not all(services):
460
+ services = _initialize_services(context)
461
+ if not all(services[:3]):
432
462
  return
433
463
 
434
- db_manager, graph_builder, code_finder = services
464
+ db_manager, graph_builder, code_finder, ctx = services
435
465
  path_obj = Path(path).resolve()
436
466
 
437
467
  if not path_obj.exists():
@@ -456,7 +486,7 @@ def reindex_helper(path: str):
456
486
  console.print(f"[cyan]Re-indexing: {path_obj}[/cyan]")
457
487
 
458
488
  try:
459
- asyncio.run(_run_index_with_progress(graph_builder, path_obj, is_dependency=False))
489
+ asyncio.run(_run_index_with_progress(graph_builder, path_obj, is_dependency=False, cgcignore_path=ctx.cgcignore_path))
460
490
  time_end = time.time()
461
491
  elapsed = time_end - time_start
462
492
  console.print(f"[green]Successfully re-indexed: {path} in {elapsed:.2f} seconds[/green]")
@@ -466,62 +496,39 @@ def reindex_helper(path: str):
466
496
  db_manager.close_driver()
467
497
 
468
498
 
469
- def update_helper(path: str):
499
+ def update_helper(path: str, context: Optional[str] = None):
470
500
  """Update/refresh index for a path (alias for reindex)."""
471
501
  console.print("[cyan]Updating repository index...[/cyan]")
472
- reindex_helper(path)
502
+ reindex_helper(path, context)
473
503
 
474
504
 
475
- def clean_helper():
505
+ def clean_helper(context: Optional[str] = None):
476
506
  """Remove orphaned nodes and relationships from the database."""
477
- services = _initialize_services()
478
- if not all(services):
507
+ services = _initialize_services(context)
508
+ if not all(services[:3]):
479
509
  return
480
510
 
481
- db_manager, _, _ = services
511
+ db_manager, _, _, ctx = services
482
512
 
483
513
  console.print("[cyan]🧹 Cleaning database (removing orphaned nodes)...[/cyan]")
484
514
 
485
515
  try:
486
- # Determine backend type for query compatibility
487
- db_type = db_manager.__class__.__name__
488
- is_falkordb = "Falkor" in db_type
489
- is_kuzu = "Kuzu" in db_type
490
-
491
516
  total_deleted = 0
492
- batch_size = 1000
517
+ batch_size = 500
493
518
 
494
519
  with db_manager.get_driver().session() as session:
495
- # Keep deleting orphaned nodes in batches until none are found
520
+ # Layer-by-layer deletion: iteratively delete nodes that lost
521
+ # their CONTAINS parent. Each pass peels one layer of the
522
+ # Repository → File → Class/Function → Variable hierarchy.
496
523
  while True:
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)
500
- query = """
501
- MATCH (n)
502
- WHERE NOT (n:Repository)
503
- OPTIONAL MATCH p = (n)-[*..10]-(r:Repository)
504
- WITH n, p
505
- WHERE p IS NULL
506
- WITH n LIMIT $batch_size
507
- DETACH DELETE n
508
- RETURN count(n) as deleted
509
- """
510
- else:
511
- # Neo4j optimized query using NOT EXISTS with bounded path
512
- # This is much faster than OPTIONAL MATCH with variable-length paths
513
- query = """
524
+ result = session.run("""
514
525
  MATCH (n)
515
- WHERE NOT (n:Repository)
516
- AND NOT EXISTS {
517
- MATCH (n)-[*..10]-(r:Repository)
518
- }
526
+ WHERE NOT n:Repository
527
+ AND NOT ()-[:CONTAINS]->(n)
519
528
  WITH n LIMIT $batch_size
520
529
  DETACH DELETE n
521
530
  RETURN count(n) as deleted
522
- """
523
-
524
- result = session.run(query, batch_size=batch_size)
531
+ """, batch_size=batch_size)
525
532
  record = result.single()
526
533
  deleted_count = record["deleted"] if record else 0
527
534
  total_deleted += deleted_count
@@ -529,17 +536,13 @@ def clean_helper():
529
536
  if deleted_count == 0:
530
537
  break
531
538
 
532
- console.print(f"[dim]Deleted {deleted_count} orphaned nodes (batch)...[/dim]")
539
+ console.print(f"[dim] Deleted {deleted_count} orphaned nodes (batch)...[/dim]")
533
540
 
534
541
  if total_deleted > 0:
535
542
  console.print(f"[green]✓[/green] Deleted {total_deleted} orphaned nodes total")
536
543
  else:
537
544
  console.print("[green]✓[/green] No orphaned nodes found")
538
545
 
539
- # Clean up any duplicate relationships (if any)
540
- console.print("[dim]Checking for duplicate relationships...[/dim]")
541
- # Note: This is database-specific and might not work for all backends
542
-
543
546
  console.print("[green]✅ Database cleanup complete![/green]")
544
547
  except Exception as e:
545
548
  console.print(f"[bold red]An error occurred during cleanup:[/bold red] {e}")
@@ -547,13 +550,13 @@ def clean_helper():
547
550
  db_manager.close_driver()
548
551
 
549
552
 
550
- def stats_helper(path: str = None):
553
+ def stats_helper(path: str = None, context: Optional[str] = None):
551
554
  """Show indexing statistics for a repository or overall."""
552
- services = _initialize_services()
553
- if not all(services):
555
+ services = _initialize_services(context)
556
+ if not all(services[:3]):
554
557
  return
555
558
 
556
- db_manager, _, code_finder = services
559
+ db_manager, _, code_finder, ctx = services
557
560
 
558
561
  try:
559
562
  if path:
@@ -635,7 +638,7 @@ def stats_helper(path: str = None):
635
638
  db_manager.close_driver()
636
639
 
637
640
 
638
- def watch_helper(path: str):
641
+ def watch_helper(path: str, context: Optional[str] = None):
639
642
  """Watch a directory for changes and auto-update the graph (blocking mode)."""
640
643
  import logging
641
644
  from ..core.watcher import CodeWatcher
@@ -645,11 +648,11 @@ def watch_helper(path: str):
645
648
  logging.getLogger('watchdog.observers').setLevel(logging.WARNING)
646
649
  logging.getLogger('watchdog.observers.inotify_buffer').setLevel(logging.WARNING)
647
650
 
648
- services = _initialize_services()
649
- if not all(services):
651
+ services = _initialize_services(context)
652
+ if not all(services[:3]):
650
653
  return
651
654
 
652
- db_manager, graph_builder, code_finder = services
655
+ db_manager, graph_builder, code_finder, ctx = services
653
656
  path_obj = Path(path).resolve()
654
657
 
655
658
  if not path_obj.exists():
@@ -664,9 +667,29 @@ def watch_helper(path: str):
664
667
 
665
668
  console.print(f"[bold cyan]🔍 Watching {path_obj} for changes...[/bold cyan]")
666
669
 
667
- # Check if already indexed
670
+ # Check if already indexed — use File node count as a robust fallback so a
671
+ # transient empty result from list_indexed_repositories never triggers a
672
+ # destructive full rescan of an already-populated graph.
668
673
  indexed_repos = code_finder.list_indexed_repositories()
669
674
  is_indexed = any(Path(repo["path"]).resolve() == path_obj for repo in indexed_repos)
675
+ if not is_indexed:
676
+ # Fallback: count File nodes whose path starts with this repo's path.
677
+ # If > 100 exist, the repo is clearly already indexed — skip the scan.
678
+ try:
679
+ with code_finder.driver.session() as _s:
680
+ _r = _s.run(
681
+ "MATCH (n:File) WHERE n.path STARTS WITH $p RETURN count(n) AS c",
682
+ p=str(path_obj) + "/"
683
+ )
684
+ _count = _r.single()["c"]
685
+ if _count > 100:
686
+ info_logger(
687
+ f"[watch] list_indexed_repositories returned no match for {path_obj} "
688
+ f"but {_count} File nodes exist — treating as already indexed."
689
+ )
690
+ is_indexed = True
691
+ except Exception as _e:
692
+ warning_logger(f"[watch] Fallback indexed check failed: {_e}")
670
693
 
671
694
  # Create watcher instance
672
695
  job_manager = JobManager()