codegraphcontext 0.4.12__py3-none-any.whl → 0.4.14__py3-none-any.whl

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 (32) hide show
  1. codegraphcontext/api/router.py +1 -1
  2. codegraphcontext/cli/config_manager.py +15 -13
  3. codegraphcontext/cli/main.py +39 -42
  4. codegraphcontext/cli/registry_commands.py +30 -19
  5. codegraphcontext/cli/setup_wizard.py +21 -9
  6. codegraphcontext/core/__init__.py +35 -1
  7. codegraphcontext/core/bundle_registry.py +2 -42
  8. codegraphcontext/core/cgc_bundle.py +101 -42
  9. codegraphcontext/core/database_falkordb.py +88 -14
  10. codegraphcontext/core/database_kuzu.py +5 -2
  11. codegraphcontext/core/database_nornic.py +11 -3
  12. codegraphcontext/server.py +11 -1
  13. codegraphcontext/tools/advanced_language_query_tool.py +13 -12
  14. codegraphcontext/tools/code_finder.py +4 -2
  15. codegraphcontext/tools/graph_builder.py +10 -2
  16. codegraphcontext/tools/indexing/persistence/writer.py +36 -10
  17. codegraphcontext/tools/indexing/pre_scan.py +4 -0
  18. codegraphcontext/tools/indexing/resolution/calls.py +2 -1
  19. codegraphcontext/tools/indexing/scip_pipeline.py +1 -1
  20. codegraphcontext/tools/languages/elisp.py +682 -0
  21. codegraphcontext/tools/query_tool_languages/elisp_toolkit.py +73 -0
  22. codegraphcontext/tools/scip_indexer.py +4 -13
  23. codegraphcontext/tools/system.py +8 -3
  24. codegraphcontext/tools/tree_sitter_parser.py +4 -0
  25. codegraphcontext/utils/tree_sitter_manager.py +5 -0
  26. codegraphcontext/viz/server.py +4 -2
  27. {codegraphcontext-0.4.12.dist-info → codegraphcontext-0.4.14.dist-info}/METADATA +40 -17
  28. {codegraphcontext-0.4.12.dist-info → codegraphcontext-0.4.14.dist-info}/RECORD +32 -30
  29. {codegraphcontext-0.4.12.dist-info → codegraphcontext-0.4.14.dist-info}/WHEEL +0 -0
  30. {codegraphcontext-0.4.12.dist-info → codegraphcontext-0.4.14.dist-info}/entry_points.txt +0 -0
  31. {codegraphcontext-0.4.12.dist-info → codegraphcontext-0.4.14.dist-info}/licenses/LICENSE +0 -0
  32. {codegraphcontext-0.4.12.dist-info → codegraphcontext-0.4.14.dist-info}/top_level.txt +0 -0
@@ -87,7 +87,7 @@ async def execute_query(
87
87
  server: MCPServer = Depends(get_server)
88
88
  ):
89
89
  result = await server.handle_tool_call("execute_cypher_query", {
90
- "query": request.query,
90
+ "cypher_query": request.query,
91
91
  "params": request.params
92
92
  })
93
93
 
@@ -31,6 +31,7 @@ DEFAULT_CONFIG = {
31
31
  "FALKORDB_PATH": str(CONFIG_DIR / "global" / "db" / "falkordb"),
32
32
  "FALKORDB_SOCKET_PATH": str(CONFIG_DIR / "global" / "db" / "falkordb.sock"),
33
33
  "LADYBUGDB_PATH": str(CONFIG_DIR / "global" / "db" / "ladybugdb"),
34
+ "KUZUDB_PATH": str(CONFIG_DIR / "global" / "db" / "kuzudb"),
34
35
  "INDEX_VARIABLES": "true",
35
36
  "ALLOW_DB_DELETION": "false",
36
37
  "DEBUG_LOGS": "false",
@@ -72,6 +73,7 @@ CONFIG_DESCRIPTIONS = {
72
73
  "FALKORDB_PATH": "Path to FalkorDB database file",
73
74
  "FALKORDB_SOCKET_PATH": "Path to FalkorDB Unix socket",
74
75
  "LADYBUGDB_PATH": "Path to LadybugDB database directory",
76
+ "KUZUDB_PATH": "Path to KuzuDB database directory",
75
77
  "INDEX_VARIABLES": "Index variable nodes in the graph (lighter graph if false)",
76
78
  "ALLOW_DB_DELETION": "Allow full database deletion commands",
77
79
  "DEBUG_LOGS": "Enable debug logging (for development/troubleshooting)",
@@ -232,7 +234,7 @@ def load_config() -> Dict[str, str]:
232
234
  # Load global config
233
235
  if CONFIG_FILE.exists():
234
236
  try:
235
- with open(CONFIG_FILE, "r") as f:
237
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
236
238
  for line in f:
237
239
  line = line.strip()
238
240
  if line and not line.startswith("#") and "=" in line:
@@ -245,7 +247,7 @@ def load_config() -> Dict[str, str]:
245
247
  local_env = find_local_env()
246
248
  if local_env and local_env.exists():
247
249
  try:
248
- with open(local_env, "r") as f:
250
+ with open(local_env, "r", encoding="utf-8") as f:
249
251
  for line in f:
250
252
  line = line.strip()
251
253
  if line and not line.startswith("#") and "=" in line:
@@ -319,7 +321,7 @@ def save_config(config: Dict[str, str], preserve_db_credentials: bool = True):
319
321
  if preserve_db_credentials and CONFIG_FILE.exists():
320
322
  # Load existing credentials from file to preserve them
321
323
  try:
322
- with open(CONFIG_FILE, "r") as f:
324
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
323
325
  for line in f:
324
326
  line = line.strip()
325
327
  if line and not line.startswith("#") and "=" in line:
@@ -340,7 +342,7 @@ def save_config(config: Dict[str, str], preserve_db_credentials: bool = True):
340
342
  credentials_to_write[key] = config[key]
341
343
 
342
344
  try:
343
- with open(CONFIG_FILE, "w") as f:
345
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
344
346
  f.write("# CodeGraphContext Configuration\n")
345
347
  f.write(f"# Location: {CONFIG_FILE}\n\n")
346
348
 
@@ -454,7 +456,7 @@ def validate_config_value(key: str, value: str) -> tuple[bool, Optional[str]]:
454
456
  except Exception as e:
455
457
  return False, f"Cannot create log directory: {e}"
456
458
 
457
- if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH", "LADYBUGDB_PATH"):
459
+ if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH", "LADYBUGDB_PATH", "KUZUDB_PATH"):
458
460
  # Validate path is writable
459
461
  db_path = Path(normalize_config_path(value, absolute=True))
460
462
  try:
@@ -694,7 +696,7 @@ def load_context_config() -> ContextConfig:
694
696
  return cfg
695
697
 
696
698
  try:
697
- with open(CONTEXT_CONFIG_FILE, "r") as f:
699
+ with open(CONTEXT_CONFIG_FILE, "r", encoding="utf-8") as f:
698
700
  raw = yaml.safe_load(f) or {}
699
701
 
700
702
  contexts: Dict[str, ContextInfo] = {}
@@ -744,7 +746,7 @@ def save_context_config(cfg: ContextConfig) -> None:
744
746
  }
745
747
 
746
748
  try:
747
- with open(CONTEXT_CONFIG_FILE, "w") as f:
749
+ with open(CONTEXT_CONFIG_FILE, "w", encoding="utf-8") as f:
748
750
  yaml.dump(raw, f, default_flow_style=False, sort_keys=False)
749
751
  except Exception as e:
750
752
  console.print(f"[red]Error saving config.yaml: {e}[/red]")
@@ -836,7 +838,7 @@ def resolve_context(
836
838
  local_db = "falkordb"
837
839
  if local_yaml.exists():
838
840
  try:
839
- with open(local_yaml) as f:
841
+ with open(local_yaml, encoding="utf-8") as f:
840
842
  local_raw = yaml.safe_load(f) or {}
841
843
  local_db = local_raw.get("database", "falkordb")
842
844
  except Exception:
@@ -1051,7 +1053,7 @@ def discover_child_contexts(
1051
1053
  local_yaml = candidate / "config.yaml"
1052
1054
  if local_yaml.exists():
1053
1055
  try:
1054
- with open(local_yaml) as f:
1056
+ with open(local_yaml, encoding="utf-8") as f:
1055
1057
  raw = yaml.safe_load(f) or {}
1056
1058
  local_db = raw.get("database", "falkordb")
1057
1059
  except Exception:
@@ -1080,7 +1082,7 @@ def _load_workspace_mappings() -> Dict[str, Dict[str, str]]:
1080
1082
  if not CONTEXT_CONFIG_FILE.exists():
1081
1083
  return {}
1082
1084
  try:
1083
- with open(CONTEXT_CONFIG_FILE, "r") as f:
1085
+ with open(CONTEXT_CONFIG_FILE, "r", encoding="utf-8") as f:
1084
1086
  raw = yaml.safe_load(f) or {}
1085
1087
  return raw.get("workspace_mappings", {}) or {}
1086
1088
  except Exception:
@@ -1094,13 +1096,13 @@ def _save_workspace_mappings(mappings: Dict[str, Dict[str, str]]) -> None:
1094
1096
  raw: Dict[str, Any] = {}
1095
1097
  if CONTEXT_CONFIG_FILE.exists():
1096
1098
  try:
1097
- with open(CONTEXT_CONFIG_FILE, "r") as f:
1099
+ with open(CONTEXT_CONFIG_FILE, "r", encoding="utf-8") as f:
1098
1100
  raw = yaml.safe_load(f) or {}
1099
1101
  except Exception:
1100
1102
  raw = {}
1101
1103
  raw["workspace_mappings"] = mappings
1102
1104
  try:
1103
- with open(CONTEXT_CONFIG_FILE, "w") as f:
1105
+ with open(CONTEXT_CONFIG_FILE, "w", encoding="utf-8") as f:
1104
1106
  yaml.dump(raw, f, default_flow_style=False, sort_keys=False)
1105
1107
  except Exception as e:
1106
1108
  console.print(f"[red]Error saving workspace mappings: {e}[/red]")
@@ -1122,7 +1124,7 @@ def save_workspace_mapping(cwd: Path, context_path: Path) -> None:
1122
1124
  local_yaml = context_path / "config.yaml"
1123
1125
  if local_yaml.exists():
1124
1126
  try:
1125
- with open(local_yaml) as f:
1127
+ with open(local_yaml, encoding="utf-8") as f:
1126
1128
  raw = yaml.safe_load(f) or {}
1127
1129
  local_db = raw.get("database", "falkordb")
1128
1130
  except Exception:
@@ -22,7 +22,6 @@ from pathlib import Path
22
22
  from importlib.metadata import version as pkg_version, PackageNotFoundError
23
23
 
24
24
  from codegraphcontext.server import MCPServer
25
- from codegraphcontext.core.database import DatabaseManager
26
25
  from .setup_wizard import run_neo4j_setup_wizard, configure_mcp_client
27
26
  from . import config_manager
28
27
  # Import the new helper functions
@@ -108,7 +107,7 @@ def mcp_setup():
108
107
  Configure MCP Client (IDE/CLI Integration).
109
108
 
110
109
  Sets up CodeGraphContext integration with your IDE or CLI tool:
111
- - VS Code, Cursor, Windsurf
110
+ - VS Code, Cursor, Windsurf, Zed
112
111
  - Claude Desktop, Gemini CLI
113
112
  - Cline, RooCode, Amazon Q Developer, Goose
114
113
  - OpenCode (prints stdio config + link to vendor docs)
@@ -125,7 +124,7 @@ def mcp_start():
125
124
  Start the CodeGraphContext MCP server.
126
125
 
127
126
  Starts the server which listens for JSON-RPC requests from stdin.
128
- This is used by IDE integrations (VS Code, Cursor, etc.).
127
+ This is used by IDE integrations (VS Code, Cursor, Zed, etc.).
129
128
  """
130
129
  console.print("[bold green]Starting CodeGraphContext Server...[/bold green]")
131
130
  _load_credentials()
@@ -250,7 +249,7 @@ def context_list():
250
249
  @context_app.command("create")
251
250
  def context_create(
252
251
  name: str = typer.Argument(..., help="Name of the new context"),
253
- database: str = typer.Option(None, "--database", "-d", help="Database backend (falkordb, kuzudb, neo4j). Defaults to DEFAULT_DATABASE from config."),
252
+ database: str = typer.Option(None, "--database", "--db", "-db", "-d", help="Database backend (falkordb, kuzudb, neo4j). Defaults to DEFAULT_DATABASE from config."),
254
253
  db_path: str = typer.Option(None, "--db-path", help="Explicit path for the DB (defaults to ~/.codegraphcontext/contexts/<name>/db)"),
255
254
  ):
256
255
  """Create a new logical context."""
@@ -291,17 +290,14 @@ def _load_credentials(cli_context_flag: Optional[str] = None):
291
290
  Uses per-variable precedence - each variable is loaded from the highest priority source.
292
291
  Priority order (highest to lowest):
293
292
  1. Runtime environment variables (shell/CI)
294
- 2. Local `.env` in project directory (project-specific overrides)
293
+ 2. Local `.codegraphcontext/.env` and `.env` in the current project directory (project-specific overrides)
295
294
  3. Global `~/.codegraphcontext/.env` (user defaults, including `cgc config set`)
296
295
  4. Local `mcp.json` env vars (project defaults)
297
- 1. Local `mcp.json` env vars (highest - explicit MCP server config)
298
- 2. ``<cwd>/.codegraphcontext/.env`` only (no parent-directory walk)
299
- 3. Global `~/.codegraphcontext/.env` (lowest - user defaults)
300
296
 
301
- Step 2 skips duplicate loading when that file is the same path as the global file.
302
- Arbitrary repo-root `.env` files are not loaded—only CodeGraphContext config paths.
297
+ Duplicate loading is skipped when the local file resolves to the same path as the global file.
298
+ Arbitrary parent directory `.env` files are not loaded—ensuring isolation.
303
299
  """
304
- from dotenv import dotenv_values, find_dotenv
300
+ from dotenv import dotenv_values
305
301
  from codegraphcontext.cli.config_manager import (
306
302
  ensure_config_dir,
307
303
  codegraphcontext_dotenv_at_cwd,
@@ -360,12 +356,12 @@ def _load_credentials(cli_context_flag: Optional[str] = None):
360
356
  except Exception as e:
361
357
  console.print(f"[yellow]Warning: Could not load global .env: {e}[/yellow]")
362
358
 
363
- # 2. Local project .env (project-specific overrides)
359
+ # 2. Local project .env (project-specific overrides - restricted to CWD only, no parent walk)
364
360
  try:
365
- dotenv_path = find_dotenv(usecwd=True, raise_error_if_not_found=False)
366
- if dotenv_path:
367
- with open(dotenv_path, "r", encoding="utf-8", errors="replace") as f:
368
- _append_source(str(dotenv_path), dotenv_values(stream=f))
361
+ local_dot_env = Path.cwd() / ".env"
362
+ if local_dot_env.exists() and local_dot_env.resolve() != global_env_path.resolve():
363
+ with open(local_dot_env, "r", encoding="utf-8", errors="replace") as f:
364
+ _append_source(str(local_dot_env), dotenv_values(stream=f))
369
365
  except Exception as e:
370
366
  console.print(f"[yellow]Warning: Could not load .env from current directory: {e}[/yellow]")
371
367
 
@@ -637,7 +633,7 @@ def bundle_export(
637
633
  if repo_path:
638
634
  console.print(f"[dim]Repository: {repo_path}[/dim]")
639
635
  else:
640
- console.print(f"[dim]Exporting all repositories[/dim]")
636
+ console.print("[dim]Exporting all repositories[/dim]")
641
637
 
642
638
  bundle = CGCBundle(db_manager)
643
639
  success, message = bundle.export_to_bundle(
@@ -748,7 +744,7 @@ def bundle_load(
748
744
 
749
745
  # Try to download from registry
750
746
  console.print(f"[yellow]Bundle '{bundle_name}' not found locally.[/yellow]")
751
- console.print(f"[cyan]Attempting to download from registry...[/cyan]")
747
+ console.print("[cyan]Attempting to download from registry...[/cyan]")
752
748
 
753
749
  try:
754
750
  from .registry_commands import download_bundle
@@ -768,7 +764,7 @@ def bundle_load(
768
764
 
769
765
  except Exception as e:
770
766
  console.print(f"[bold red]Error: {e}[/bold red]")
771
- console.print(f"[dim]Use 'cgc registry list' to see available bundles[/dim]")
767
+ console.print("[dim]Use 'cgc registry list' to see available bundles[/dim]")
772
768
  raise typer.Exit(code=1)
773
769
 
774
770
  # Shortcut commands at root level
@@ -888,7 +884,7 @@ def registry_download(
888
884
  bundle_path = download_bundle(name, output_dir, auto_load=load)
889
885
 
890
886
  if load and bundle_path:
891
- console.print(f"\n[cyan]Loading bundle...[/cyan]")
887
+ console.print("\n[cyan]Loading bundle...[/cyan]")
892
888
  bundle_import(bundle_path, clear=False)
893
889
 
894
890
  @registry_app.command("request")
@@ -941,13 +937,13 @@ def doctor():
941
937
  if config_manager.CONFIG_FILE.exists():
942
938
  console.print(f" [green]✓[/green] Config loaded from {config_manager.CONFIG_FILE}")
943
939
  else:
944
- console.print(f" [yellow]ℹ[/yellow] No .env config found, using defaults")
940
+ console.print(" [yellow]ℹ[/yellow] No .env config found, using defaults")
945
941
  console.print(f" [dim]Config will be created at: {config_manager.CONFIG_FILE}[/dim]")
946
942
 
947
943
  if config_manager.CONTEXT_CONFIG_FILE.exists():
948
944
  console.print(f" [green]✓[/green] Context config loaded from {config_manager.CONTEXT_CONFIG_FILE}")
949
945
  else:
950
- console.print(f" [yellow]ℹ[/yellow] No Context config found")
946
+ console.print(" [yellow]ℹ[/yellow] No Context config found")
951
947
  console.print(f" [dim]Context config will be auto-generated at: {config_manager.CONTEXT_CONFIG_FILE}[/dim]")
952
948
 
953
949
  # Validate each config value
@@ -958,12 +954,12 @@ def doctor():
958
954
  invalid_configs.append(f"{key}: {error_msg}")
959
955
 
960
956
  if invalid_configs:
961
- console.print(f" [red]✗[/red] Invalid configuration values found:")
957
+ console.print(" [red]✗[/red] Invalid configuration values found:")
962
958
  for err in invalid_configs:
963
959
  console.print(f" - {err}")
964
960
  all_checks_passed = False
965
961
  else:
966
- console.print(f" [green]✓[/green] All configuration values are valid")
962
+ console.print(" [green]✓[/green] All configuration values are valid")
967
963
  except Exception as e:
968
964
  console.print(f" [red]✗[/red] Configuration error: {e}")
969
965
  all_checks_passed = False
@@ -982,6 +978,7 @@ def doctor():
982
978
  password = os.environ.get("NEO4J_PASSWORD")
983
979
  database_name = os.environ.get("NEO4J_DATABASE")
984
980
 
981
+ from codegraphcontext.core.database import DatabaseManager
985
982
  missing = DatabaseManager.get_missing_credentials(uri, username, password)
986
983
  console.print(f" [cyan]Credential check:[/cyan] {'OK' if not missing else 'Missing ' + ', '.join(missing)}")
987
984
  if missing:
@@ -1003,10 +1000,10 @@ def doctor():
1003
1000
  console.print(" Start Neo4j Desktop or run: docker run -d -p 7687:7687 -p 7474:7474 neo4j")
1004
1001
  all_checks_passed = False
1005
1002
 
1006
- console.print(f" [cyan]Testing Neo4j authentication/query...[/cyan]")
1003
+ console.print(" [cyan]Testing Neo4j authentication/query...[/cyan]")
1007
1004
  is_connected, error_msg = DatabaseManager.test_connection(uri, username, password, database=database_name)
1008
1005
  if is_connected:
1009
- console.print(f" [green]✓[/green] Neo4j connection successful")
1006
+ console.print(" [green]✓[/green] Neo4j connection successful")
1010
1007
  else:
1011
1008
  console.print(f" [red]✗[/red] Neo4j connection failed (source: {db_source})")
1012
1009
  console.print(f" Reason: {error_msg}")
@@ -1015,28 +1012,28 @@ def doctor():
1015
1012
  from importlib.util import find_spec
1016
1013
 
1017
1014
  if find_spec("kuzu") is not None:
1018
- console.print(f" [green]✓[/green] KuzuDB is installed")
1015
+ console.print(" [green]✓[/green] KuzuDB is installed")
1019
1016
  else:
1020
- console.print(f" [red]✗[/red] KuzuDB is not installed")
1021
- console.print(f" Run: pip install kuzu")
1017
+ console.print(" [red]✗[/red] KuzuDB is not installed")
1018
+ console.print(" Run: pip install kuzu")
1022
1019
  all_checks_passed = False
1023
1020
  elif default_db == "ladybugdb":
1024
1021
  from importlib.util import find_spec
1025
1022
 
1026
1023
  if find_spec("ladybug") is not None:
1027
- console.print(f" [green]✓[/green] LadybugDB core (ladybug) is installed")
1024
+ console.print(" [green]✓[/green] LadybugDB core (ladybug) is installed")
1028
1025
  else:
1029
- console.print(f" [red]✗[/red] LadybugDB core (ladybug) is not installed")
1030
- console.print(f" Run: pip install ladybug")
1026
+ console.print(" [red]✗[/red] LadybugDB core (ladybug) is not installed")
1027
+ console.print(" Run: pip install ladybug")
1031
1028
  all_checks_passed = False
1032
1029
  else:
1033
1030
  # FalkorDB
1034
1031
  try:
1035
1032
  import falkordb
1036
- console.print(f" [green]✓[/green] FalkorDB Lite is installed")
1033
+ console.print(" [green]✓[/green] FalkorDB Lite is installed")
1037
1034
  except ImportError:
1038
- console.print(f" [yellow]⚠[/yellow] FalkorDB Lite not installed (Python 3.12+ only)")
1039
- console.print(f" Run: pip install falkordblite")
1035
+ console.print(" [yellow]⚠[/yellow] FalkorDB Lite not installed (Python 3.12+ only)")
1036
+ console.print(" Run: pip install falkordblite")
1040
1037
  except Exception as e:
1041
1038
  console.print(f" [red]✗[/red] Database check error: {e}")
1042
1039
  all_checks_passed = False
@@ -1045,11 +1042,11 @@ def doctor():
1045
1042
  console.print("\n[bold]3. Checking Tree-Sitter Installation...[/bold]")
1046
1043
  try:
1047
1044
  from tree_sitter import Language, Parser
1048
- console.print(f" [green]✓[/green] tree-sitter is installed")
1045
+ console.print(" [green]✓[/green] tree-sitter is installed")
1049
1046
 
1050
1047
  try:
1051
1048
  from tree_sitter_language_pack import get_language
1052
- console.print(f" [green]✓[/green] tree-sitter-language-pack is installed")
1049
+ console.print(" [green]✓[/green] tree-sitter-language-pack is installed")
1053
1050
 
1054
1051
  from codegraphcontext.utils.tree_sitter_manager import LANGUAGE_ALIASES, LANGUAGE_PACK_NAMES
1055
1052
  all_langs = sorted(set(LANGUAGE_ALIASES.values()))
@@ -1067,7 +1064,7 @@ def doctor():
1067
1064
  if unavailable:
1068
1065
  console.print(f" [yellow]⚠[/yellow] Unavailable: {', '.join(unavailable)}")
1069
1066
  except ImportError:
1070
- console.print(f" [red]✗[/red] tree-sitter-language-pack not installed")
1067
+ console.print(" [red]✗[/red] tree-sitter-language-pack not installed")
1071
1068
  all_checks_passed = False
1072
1069
  except ImportError as e:
1073
1070
  console.print(f" [red]✗[/red] tree-sitter not installed: {e}")
@@ -1085,12 +1082,12 @@ def doctor():
1085
1082
  try:
1086
1083
  test_file.touch()
1087
1084
  test_file.unlink()
1088
- console.print(f" [green]✓[/green] Config directory is writable")
1085
+ console.print(" [green]✓[/green] Config directory is writable")
1089
1086
  except Exception as e:
1090
1087
  console.print(f" [red]✗[/red] Config directory not writable: {e}")
1091
1088
  all_checks_passed = False
1092
1089
  else:
1093
- console.print(f" [yellow]⚠[/yellow] Config directory doesn't exist, will be created on first use")
1090
+ console.print(" [yellow]⚠[/yellow] Config directory doesn't exist, will be created on first use")
1094
1091
  except Exception as e:
1095
1092
  console.print(f" [red]✗[/red] Permission check error: {e}")
1096
1093
  all_checks_passed = False
@@ -1102,7 +1099,7 @@ def doctor():
1102
1099
  if cgc_path:
1103
1100
  console.print(f" [green]✓[/green] cgc command found at: {cgc_path}")
1104
1101
  else:
1105
- console.print(f" [yellow]⚠[/yellow] cgc command not in PATH (using python -m cgc)")
1102
+ console.print(" [yellow]⚠[/yellow] cgc command not in PATH (using python -m cgc)")
1106
1103
 
1107
1104
  # Final summary
1108
1105
  console.print("\n" + "=" * 60)
@@ -2393,7 +2390,7 @@ def analyze_dead_code(
2393
2390
  location_str
2394
2391
  )
2395
2392
 
2396
- console.print(f"\n[bold yellow]⚠️ Potentially Unused Functions:[/bold yellow]")
2393
+ console.print("\n[bold yellow]⚠️ Potentially Unused Functions:[/bold yellow]")
2397
2394
  console.print(table)
2398
2395
  console.print(f"\n[dim]Total: {len(unused_funcs)} function(s)[/dim]")
2399
2396
  console.print(f"[dim]Note: {results.get('note', '')}[/dim]")
@@ -11,7 +11,6 @@ from rich.table import Table
11
11
  from rich.progress import Progress, SpinnerColumn, TextColumn
12
12
  from pathlib import Path
13
13
  from typing import Optional, List, Dict, Any
14
- import time
15
14
 
16
15
  console = Console()
17
16
 
@@ -20,7 +19,7 @@ GITHUB_REPO = "CodeGraphContext"
20
19
 
21
20
 
22
21
  def fetch_available_bundles() -> List[Dict[str, Any]]:
23
- """Fetch all available bundles from GitHub Releases (delegates to core BundleRegistry)."""
22
+ """Fetch all available bundles from the Hugging Face registry (delegates to core BundleRegistry)."""
24
23
  from ..core.bundle_registry import BundleRegistry
25
24
  return BundleRegistry.fetch_available_bundles()
26
25
 
@@ -34,17 +33,27 @@ def _get_base_package_name(bundle_name: str) -> str:
34
33
  'flask-main-abc123' -> 'flask'
35
34
  'requests' -> 'requests'
36
35
  """
36
+ import re
37
37
  # Remove .cgc extension if present
38
38
  name = bundle_name.replace('.cgc', '')
39
39
 
40
- # Split by hyphen and take the first part
41
- # This assumes package names don't contain hyphens (may need refinement)
42
- parts = name.split('-')
43
-
44
- # For multi-word package names like 'python-bitcoin-utils',
45
- # we need smarter logic. For now, take first part.
46
- # TODO: Improve this with a known package list or better heuristics
47
- return parts[0]
40
+ # If the bundle name contains '-main-' or '-master-', split on that
41
+ for branch_indicator in ('-main-', '-master-'):
42
+ if branch_indicator in name:
43
+ return name.split(branch_indicator)[0]
44
+
45
+ # Try matching standard trailing branch and commit hash pattern
46
+ # e.g., -[branch]-[commit_hash] where commit_hash is a hex string
47
+ match = re.search(r'-(?:main|master|dev|development|release)-[a-fA-F0-9]{7,40}$', name)
48
+ if match:
49
+ return name[:match.start()]
50
+
51
+ # Try matching generic trailing branch and 7-8 char hex commit hash
52
+ match_generic = re.search(r'-([a-zA-Z0-9_]+)-([a-fA-F0-9]{7,8})$', name)
53
+ if match_generic:
54
+ return name[:match_generic.start()]
55
+
56
+ return name
48
57
 
49
58
 
50
59
  def list_bundles(verbose: bool = False, unique: bool = False):
@@ -168,6 +177,8 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
168
177
  and base names (e.g., 'python-bitcoin-utils' - picks most recent version).
169
178
  """
170
179
  console.print(f"[cyan]Looking for bundle '{name}'...[/cyan]")
180
+
181
+ lookup_name = name[:-4] if name.lower().endswith('.cgc') else name
171
182
 
172
183
  bundles = fetch_available_bundles()
173
184
 
@@ -178,7 +189,7 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
178
189
  # Strategy 1: Try exact match on full_name (with version)
179
190
  bundle = None
180
191
  for b in bundles:
181
- if b.get('full_name', '').lower() == name.lower():
192
+ if b.get('full_name', '').lower() == lookup_name.lower():
182
193
  bundle = b
183
194
  console.print(f"[dim]Found exact match: {b.get('full_name')}[/dim]")
184
195
  break
@@ -188,7 +199,7 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
188
199
  if not bundle:
189
200
  matching_bundles = []
190
201
  for b in bundles:
191
- if b.get('name', '').lower() == name.lower():
202
+ if b.get('name', '').lower() == lookup_name.lower():
192
203
  matching_bundles.append(b)
193
204
 
194
205
  if matching_bundles:
@@ -200,7 +211,7 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
200
211
  console.print(f"[cyan] → {bundle.get('full_name')}[/cyan]")
201
212
 
202
213
  if len(matching_bundles) > 1:
203
- console.print(f"\n[dim]Other available versions:[/dim]")
214
+ console.print("\n[dim]Other available versions:[/dim]")
204
215
  for b in matching_bundles[1:4]: # Show up to 3 alternatives
205
216
  console.print(f"[dim] • {b.get('full_name')}[/dim]")
206
217
  if len(matching_bundles) > 4:
@@ -211,7 +222,7 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
211
222
  if not bundle:
212
223
  # Find bundles with similar base names
213
224
  suggestions = []
214
- name_lower = name.lower()
225
+ name_lower = lookup_name.lower()
215
226
  for b in bundles:
216
227
  base_name = b.get('name', '').lower()
217
228
  full_name = b.get('full_name', '').lower()
@@ -253,7 +264,7 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
253
264
  if not typer.confirm("Overwrite?", default=False):
254
265
  console.print("[yellow]Download cancelled[/yellow]")
255
266
  if auto_load:
256
- console.print(f"[cyan]Using existing bundle for loading...[/cyan]")
267
+ console.print("[cyan]Using existing bundle for loading...[/cyan]")
257
268
  return str(output_path)
258
269
  return
259
270
  output_path.unlink()
@@ -324,14 +335,14 @@ def request_bundle(repo_url: str, wait: bool = False):
324
335
  console.print("[cyan]Please use one of these methods:[/cyan]\n")
325
336
 
326
337
  console.print("1. [bold]Via Website (Recommended):[/bold]")
327
- console.print(f" Visit: https://codegraphcontext.vercel.app")
338
+ console.print(" Visit: https://codegraphcontext.vercel.app")
328
339
  console.print(f" Enter: {repo_url}")
329
- console.print(f" Click 'Generate Bundle'\n")
340
+ console.print(" Click 'Generate Bundle'\n")
330
341
 
331
342
  console.print("2. [bold]Via GitHub Actions (Manual):[/bold]")
332
343
  console.print(f" Go to: https://github.com/{GITHUB_ORG}/{GITHUB_REPO}/actions")
333
- console.print(f" Select: 'Generate Bundle On-Demand'")
334
- console.print(f" Click: 'Run workflow'")
344
+ console.print(" Select: 'Generate Bundle On-Demand'")
345
+ console.print(" Click: 'Run workflow'")
335
346
  console.print(f" Enter: {repo_url}\n")
336
347
 
337
348
  console.print("[dim]Bundle generation typically takes 5-10 minutes.[/dim]")
@@ -92,8 +92,8 @@ def _generate_mcp_json(creds):
92
92
  "disabledTools": [],
93
93
  "disabled": False
94
94
  },
95
- "disabled": False,
96
- "alwaysAllow": []
95
+ "disabled": False
96
+
97
97
  }
98
98
  }
99
99
  }
@@ -256,7 +256,7 @@ def _configure_ide(mcp_config):
256
256
  questions = [
257
257
  {
258
258
  "type": "confirm",
259
- "message": "Automatically configure your IDE/CLI (VS Code, Cursor, Windsurf, Claude, Gemini, Cline, RooCode, ChatGPT Codex, Amazon Q Developer, Aider, Kiro, Goose, Antigravity, OpenCode)?",
259
+ "message": "Automatically configure your IDE/CLI (VS Code, Cursor, Windsurf, Zed, Claude, Gemini, Cline, RooCode, ChatGPT Codex, Amazon Q Developer, Aider, Kiro, Goose, Antigravity, OpenCode)?",
260
260
  "name": "configure_ide",
261
261
  "default": True,
262
262
  }
@@ -270,7 +270,7 @@ def _configure_ide(mcp_config):
270
270
  {
271
271
  "type": "list",
272
272
  "message": "Choose your IDE/CLI to configure:",
273
- "choices": ["VS Code", "Cursor", "Windsurf", "Claude code", "Gemini CLI", "ChatGPT Codex", "Cline", "RooCode", "Amazon Q Developer", "JetBrainsAI", "Aider", "Kiro", "Goose", "Antigravity", "OpenCode", "None of the above"],
273
+ "choices": ["VS Code", "Cursor", "Windsurf", "Zed", "Claude code", "Gemini CLI", "ChatGPT Codex", "Cline", "RooCode", "Amazon Q Developer", "JetBrainsAI", "Aider", "Kiro", "Goose", "Antigravity", "OpenCode", "None of the above"],
274
274
  "name": "ide_choice",
275
275
  }
276
276
  ]
@@ -289,7 +289,7 @@ def _configure_ide(mcp_config):
289
289
  )
290
290
  return
291
291
 
292
- if ide_choice in ["VS Code", "Cursor", "Windsurf", "Claude code", "Gemini CLI", "ChatGPT Codex", "Cline", "RooCode", "Amazon Q Developer", "JetBrainsAI", "Aider", "Kiro", "Goose", "Antigravity"]:
292
+ if ide_choice in ["VS Code", "Cursor", "Windsurf", "Zed", "Claude code", "Gemini CLI", "ChatGPT Codex", "Cline", "RooCode", "Amazon Q Developer", "JetBrainsAI", "Aider", "Kiro", "Goose", "Antigravity"]:
293
293
  console.print(f"\n[bold cyan]Configuring for {ide_choice}...[/bold cyan]")
294
294
 
295
295
  if ide_choice == "Amazon Q Developer":
@@ -320,6 +320,10 @@ def _configure_ide(mcp_config):
320
320
  Path.home() / "AppData" / "Roaming" / "windsurf" / "settings.json",
321
321
  Path.home() / ".config" / "Windsurf" / "User" / "settings.json",
322
322
  ],
323
+ "Zed": [
324
+ Path.home() / ".config" / "zed" / "settings.json",
325
+ Path.home() / "AppData" / "Roaming" / "Zed" / "settings.json"
326
+ ],
323
327
  "Claude code": [
324
328
  Path.home() / ".claude.json"
325
329
  ],
@@ -398,10 +402,14 @@ def _configure_ide(mcp_config):
398
402
  console.print(f"[red]Error: Configuration file at {target_path} is not a valid JSON object.[/red]")
399
403
  return
400
404
 
401
- if "mcpServers" not in settings:
402
- settings["mcpServers"] = {}
403
-
404
- settings["mcpServers"].update(mcp_config["mcpServers"])
405
+ if ide_choice == "Zed":
406
+ if "context_servers" not in settings:
407
+ settings["context_servers"] = {}
408
+ settings["context_servers"].update(mcp_config["mcpServers"])
409
+ else:
410
+ if "mcpServers" not in settings:
411
+ settings["mcpServers"] = {}
412
+ settings["mcpServers"].update(mcp_config["mcpServers"])
405
413
 
406
414
  try:
407
415
  with open(target_path, "w") as f:
@@ -677,6 +685,7 @@ def setup_existing_db():
677
685
 
678
686
  # Validate the user input
679
687
  console.print("\n[cyan]🔍 Validating configuration...[/cyan]")
688
+ from codegraphcontext.core.database import DatabaseManager
680
689
  is_valid, validation_error = DatabaseManager.validate_config(
681
690
  manual_creds.get("uri", ""),
682
691
  manual_creds.get("username", ""),
@@ -795,6 +804,7 @@ def setup_hosted_db():
795
804
 
796
805
  # Validate the user input
797
806
  console.print("\n[cyan]🔍 Validating configuration...[/cyan]")
807
+ from codegraphcontext.core.database import DatabaseManager
798
808
  is_valid, validation_error = DatabaseManager.validate_config(
799
809
  manual_creds.get("uri", ""),
800
810
  manual_creds.get("username", ""),
@@ -913,6 +923,7 @@ volumes:
913
923
 
914
924
  # Validate configuration format before attempting Docker operations
915
925
  console.print("\n[cyan]🔍 Validating configuration...[/cyan]")
926
+ from codegraphcontext.core.database import DatabaseManager
916
927
  is_valid, validation_error = DatabaseManager.validate_config(
917
928
  DEFAULT_NEO4J_URI,
918
929
  DEFAULT_NEO4J_USERNAME,
@@ -972,6 +983,7 @@ volumes:
972
983
 
973
984
  # updated test_connection method
974
985
  console.print(f"[yellow]Testing connection... (attempt {attempt + 1}/{max_attempts})[/yellow]")
986
+ from codegraphcontext.core.database import DatabaseManager
975
987
  is_connected, error_msg = DatabaseManager.test_connection(DEFAULT_NEO4J_URI, DEFAULT_NEO4J_USERNAME, password)
976
988
 
977
989
  if is_connected: