codegraphcontext 0.4.9__py3-none-any.whl → 0.4.11__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 (120) hide show
  1. codegraphcontext/api/__init__.py +0 -0
  2. codegraphcontext/api/app.py +65 -0
  3. codegraphcontext/api/mcp_sse.py +64 -0
  4. codegraphcontext/api/router.py +102 -0
  5. codegraphcontext/api/schemas.py +27 -0
  6. codegraphcontext/cli/__init__.py +1 -1
  7. codegraphcontext/cli/cli_helpers.py +37 -0
  8. codegraphcontext/cli/config_manager.py +23 -5
  9. codegraphcontext/cli/main.py +61 -10
  10. codegraphcontext/cli/setup_wizard.py +5 -5
  11. codegraphcontext/cli/visualizer.py +1 -0
  12. codegraphcontext/core/__init__.py +10 -3
  13. codegraphcontext/core/bundle_registry.py +1 -0
  14. codegraphcontext/core/cgc_bundle.py +25 -2
  15. codegraphcontext/core/cgcignore.py +1 -0
  16. codegraphcontext/core/database_falkordb.py +12 -1
  17. codegraphcontext/core/database_kuzu.py +238 -134
  18. codegraphcontext/core/database_ladybug.py +1138 -0
  19. codegraphcontext/core/falkor_worker.py +1 -0
  20. codegraphcontext/core/watcher.py +6 -4
  21. codegraphcontext/prompts.py +2 -2
  22. codegraphcontext/tool_definitions.py +1 -0
  23. codegraphcontext/tools/__init__.py +1 -0
  24. codegraphcontext/tools/advanced_language_query_tool.py +1 -0
  25. codegraphcontext/tools/datasources/__init__.py +1 -0
  26. codegraphcontext/tools/datasources/cassandra_ingester.py +1 -0
  27. codegraphcontext/tools/datasources/mysql_ingester.py +1 -0
  28. codegraphcontext/tools/datasources/redis_ingester.py +1 -0
  29. codegraphcontext/tools/graph_builder.py +16 -37
  30. codegraphcontext/tools/handlers/analysis_handlers.py +1 -0
  31. codegraphcontext/tools/handlers/indexing_handlers.py +1 -0
  32. codegraphcontext/tools/handlers/management_handlers.py +1 -0
  33. codegraphcontext/tools/handlers/query_handlers.py +1 -0
  34. codegraphcontext/tools/handlers/watcher_handlers.py +1 -0
  35. codegraphcontext/tools/indexing/__init__.py +1 -0
  36. codegraphcontext/tools/indexing/constants.py +1 -0
  37. codegraphcontext/tools/indexing/discovery.py +73 -18
  38. codegraphcontext/tools/indexing/embeddings.py +1 -0
  39. codegraphcontext/tools/indexing/persistence/__init__.py +1 -0
  40. codegraphcontext/tools/indexing/persistence/writer.py +102 -27
  41. codegraphcontext/tools/indexing/pipeline.py +45 -20
  42. codegraphcontext/tools/indexing/pre_scan.py +1 -0
  43. codegraphcontext/tools/indexing/resolution/__init__.py +1 -0
  44. codegraphcontext/tools/indexing/resolution/calls.py +112 -12
  45. codegraphcontext/tools/indexing/resolution/inheritance.py +1 -0
  46. codegraphcontext/tools/indexing/resolution/post_resolution.py +1 -0
  47. codegraphcontext/tools/indexing/sanitize.py +1 -0
  48. codegraphcontext/tools/indexing/schema.py +19 -18
  49. codegraphcontext/tools/indexing/schema_contract.py +1 -0
  50. codegraphcontext/tools/indexing/scip_pipeline.py +12 -8
  51. codegraphcontext/tools/indexing/vector_resolver.py +1 -0
  52. codegraphcontext/tools/languages/c.py +1 -0
  53. codegraphcontext/tools/languages/cpp.py +1 -0
  54. codegraphcontext/tools/languages/csharp.py +1 -0
  55. codegraphcontext/tools/languages/css.py +1 -0
  56. codegraphcontext/tools/languages/dart.py +1 -0
  57. codegraphcontext/tools/languages/elixir.py +1 -0
  58. codegraphcontext/tools/languages/go.py +1 -0
  59. codegraphcontext/tools/languages/gradle.py +1 -0
  60. codegraphcontext/tools/languages/haskell.py +427 -426
  61. codegraphcontext/tools/languages/html.py +1 -0
  62. codegraphcontext/tools/languages/java.py +1 -0
  63. codegraphcontext/tools/languages/javascript.py +1 -0
  64. codegraphcontext/tools/languages/kotlin.py +1 -0
  65. codegraphcontext/tools/languages/lua.py +1 -0
  66. codegraphcontext/tools/languages/maven.py +1 -0
  67. codegraphcontext/tools/languages/mybatis.py +1 -0
  68. codegraphcontext/tools/languages/perl.py +1 -0
  69. codegraphcontext/tools/languages/php.py +1 -0
  70. codegraphcontext/tools/languages/python.py +1 -0
  71. codegraphcontext/tools/languages/ruby.py +1 -0
  72. codegraphcontext/tools/languages/rust.py +1 -0
  73. codegraphcontext/tools/languages/scala.py +1 -0
  74. codegraphcontext/tools/languages/swift.py +1 -0
  75. codegraphcontext/tools/languages/typescript.py +4 -3
  76. codegraphcontext/tools/languages/typescriptjsx.py +1 -0
  77. codegraphcontext/tools/query_tool_languages/c_toolkit.py +1 -0
  78. codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +1 -0
  79. codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +1 -0
  80. codegraphcontext/tools/query_tool_languages/dart_toolkit.py +1 -0
  81. codegraphcontext/tools/query_tool_languages/go_toolkit.py +1 -0
  82. codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -4
  83. codegraphcontext/tools/query_tool_languages/java_toolkit.py +1 -0
  84. codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +1 -0
  85. codegraphcontext/tools/query_tool_languages/perl_toolkit.py +1 -0
  86. codegraphcontext/tools/query_tool_languages/python_toolkit.py +1 -0
  87. codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +1 -0
  88. codegraphcontext/tools/query_tool_languages/rust_toolkit.py +1 -0
  89. codegraphcontext/tools/query_tool_languages/scala_toolkit.py +1 -0
  90. codegraphcontext/tools/query_tool_languages/swift_toolkit.py +1 -0
  91. codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +1 -0
  92. codegraphcontext/tools/report_generator.py +1 -0
  93. codegraphcontext/tools/scip_indexer.py +10 -0
  94. codegraphcontext/tools/scip_pb2.py +1 -0
  95. codegraphcontext/tools/tree_sitter_parser.py +1 -0
  96. codegraphcontext/tools/type_utils.py +1 -0
  97. codegraphcontext/utils/debug_log.py +1 -0
  98. codegraphcontext/utils/git_utils.py +1 -0
  99. codegraphcontext/utils/path_ignore.py +1 -0
  100. codegraphcontext/utils/repo_path.py +1 -0
  101. codegraphcontext/utils/tool_limits.py +1 -0
  102. codegraphcontext/utils/tree_sitter_manager.py +1 -0
  103. codegraphcontext/utils/visualize_graph.py +1 -0
  104. codegraphcontext/viz/dist/assets/{index-BJT3EMmQ.js → index-C-187lf0.js} +363 -347
  105. codegraphcontext/viz/dist/assets/index-fNAa6jgv.css +1 -0
  106. codegraphcontext/viz/dist/assets/parser-pyodide.worker-BgsDfaad.js +370 -0
  107. codegraphcontext/viz/dist/assets/{parser.worker-CZgm11E5.js → parser.worker-_nvrecvj.js} +35 -10
  108. codegraphcontext/viz/dist/cgcIcon.png +0 -0
  109. codegraphcontext/viz/dist/index.html +3 -2
  110. codegraphcontext/viz/dist/logo-icon.svg +85 -0
  111. codegraphcontext/viz/dist/logo.svg +100 -0
  112. codegraphcontext/viz/server.py +1 -0
  113. {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.11.dist-info}/METADATA +11 -10
  114. codegraphcontext-0.4.11.dist-info/RECORD +160 -0
  115. codegraphcontext/viz/dist/assets/index-DjDPHWki.css +0 -1
  116. codegraphcontext-0.4.9.dist-info/RECORD +0 -150
  117. {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.11.dist-info}/WHEEL +0 -0
  118. {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.11.dist-info}/entry_points.txt +0 -0
  119. {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.11.dist-info}/licenses/LICENSE +0 -0
  120. {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.11.dist-info}/top_level.txt +0 -0
File without changes
@@ -0,0 +1,65 @@
1
+ # src/codegraphcontext/api/app.py
2
+ import os
3
+ from fastapi import FastAPI
4
+ from fastapi.responses import HTMLResponse
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from .router import router
7
+ from .mcp_sse import handle_sse, handle_messages
8
+
9
+ def create_app() -> FastAPI:
10
+ app = FastAPI(
11
+ title="CodeGraphContext Gateway",
12
+ description="HTTP API gateway for CodeGraphContext MCP server. Enables integration with ChatGPT Actions, Claude, and web frontends.",
13
+ version="0.1.0"
14
+ )
15
+
16
+ # Enable CORS for the website/frontend
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["*"], # In production, restrict this
20
+ allow_credentials=True,
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ app.include_router(router, prefix="/api/v1")
26
+
27
+ # MCP-over-SSE Endpoints
28
+ app.add_api_route("/api/v1/mcp/sse", handle_sse, methods=["GET"])
29
+ app.add_api_route("/api/v1/mcp/messages", handle_messages, methods=["POST"])
30
+
31
+ @app.get("/", response_class=HTMLResponse)
32
+ async def root():
33
+ return """
34
+ <!DOCTYPE html>
35
+ <html>
36
+ <head>
37
+ <title>CGC Gateway</title>
38
+ <style>
39
+ body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #0f172a; color: white; margin: 0; }
40
+ .card { background: #1e293b; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); text-align: center; max-width: 400px; border: 1px solid #334155; }
41
+ h1 { color: #38bdf8; margin-top: 0; }
42
+ p { color: #94a3b8; line-height: 1.6; }
43
+ .btn { display: inline-block; background: #38bdf8; color: #0f172a; padding: 0.75rem 1.5rem; border-radius: 0.5rem; text-decoration: none; font-weight: bold; margin-top: 1rem; transition: background 0.2s; }
44
+ .btn:hover { background: #7dd3fc; }
45
+ .links { margin-top: 1.5rem; font-size: 0.9rem; }
46
+ .links a { color: #38bdf8; text-decoration: none; margin: 0 0.5rem; }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <div class="card">
51
+ <h1>CGC Gateway</h1>
52
+ <p>CodeGraphContext HTTP API is running. This gateway allows ChatGPT and Claude to interact with your code graph.</p>
53
+ <a href="/docs" class="btn">View API Docs</a>
54
+ <div class="links">
55
+ <a href="/openapi.json">OpenAPI Spec</a>
56
+ <a href="https://github.com/Shashankss1205/CodeGraphContext" target="_blank">GitHub</a>
57
+ </div>
58
+ </div>
59
+ </body>
60
+ </html>
61
+ """
62
+
63
+ return app
64
+
65
+ app = create_app()
@@ -0,0 +1,64 @@
1
+ # src/codegraphcontext/api/mcp_sse.py
2
+ import json
3
+ import asyncio
4
+ from fastapi import Request
5
+ from mcp.server import Server
6
+ from mcp.server.models import InitializationOptions
7
+ from mcp.types import Tool, TextContent, ServerCapabilities, ToolsCapability
8
+ from mcp.server.sse import SseServerTransport
9
+
10
+ from codegraphcontext.api.router import get_server
11
+ from codegraphcontext.tool_definitions import TOOLS
12
+
13
+ # Create the MCP Server instance using the SDK
14
+ mcp_server = Server("CodeGraphContext")
15
+
16
+ @mcp_server.list_tools()
17
+ async def handle_list_tools() -> list[Tool]:
18
+ """List available tools."""
19
+ tools = []
20
+ for name, defn in TOOLS.items():
21
+ tools.append(Tool(
22
+ name=name,
23
+ description=defn["description"],
24
+ inputSchema=defn["inputSchema"]
25
+ ))
26
+ return tools
27
+
28
+ @mcp_server.call_tool()
29
+ async def handle_call_tool(name: str, arguments: dict | None) -> list[TextContent]:
30
+ """Handle tool execution."""
31
+ server = get_server()
32
+ args = arguments or {}
33
+
34
+ # Execute via the existing handler logic
35
+ result = await server.handle_tool_call(name, args)
36
+
37
+ if "error" in result:
38
+ return [TextContent(type="text", text=f"Error: {result['error']}")]
39
+
40
+ # Format result as JSON string for the AI
41
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
42
+
43
+ # Create the SSE transport.
44
+ # The messages_url is where the client will POST JSON-RPC messages.
45
+ sse = SseServerTransport("/api/v1/mcp/messages")
46
+
47
+ async def handle_sse(request: Request):
48
+ """Entry point for the SSE connection."""
49
+ async with sse.connect_sse(request.scope, request.receive, request._send) as (read_stream, write_stream):
50
+ await mcp_server.run(
51
+ read_stream,
52
+ write_stream,
53
+ InitializationOptions(
54
+ server_name="CodeGraphContext",
55
+ server_version="0.1.0",
56
+ capabilities=ServerCapabilities(
57
+ tools=ToolsCapability(listChanged=False)
58
+ )
59
+ )
60
+ )
61
+
62
+ async def handle_messages(request: Request):
63
+ """Endpoint for receiving messages from the client."""
64
+ await sse.handle_post_message(request.scope, request.receive, request._send)
@@ -0,0 +1,102 @@
1
+ # src/codegraphcontext/api/router.py
2
+ import asyncio
3
+ from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
4
+ from typing import Dict, Any, List
5
+ from pathlib import Path
6
+
7
+ from .schemas import (
8
+ IndexRequest,
9
+ QueryRequest,
10
+ SearchRequest,
11
+ ToolCallRequest,
12
+ ApiResponse
13
+ )
14
+ from codegraphcontext.server import MCPServer
15
+
16
+ router = APIRouter()
17
+
18
+ # Global server instance (initialized on startup)
19
+ _server_instance: MCPServer = None
20
+
21
+ def get_server() -> MCPServer:
22
+ global _server_instance
23
+ if _server_instance is None:
24
+ # Note: In a real production app, we'd handle initialization better
25
+ _server_instance = MCPServer(cwd=Path.cwd())
26
+ return _server_instance
27
+
28
+ @router.get("/status", response_model=ApiResponse)
29
+ async def get_status(server: MCPServer = Depends(get_server)):
30
+ status = server.db_manager.is_connected()
31
+ return ApiResponse(
32
+ status="ok",
33
+ message="Connected" if status else "Disconnected",
34
+ data={"database": server.resolved_context.database}
35
+ )
36
+
37
+ @router.get("/tools", response_model=ApiResponse)
38
+ async def list_tools(server: MCPServer = Depends(get_server)):
39
+ return ApiResponse(
40
+ status="ok",
41
+ data={"tools": list(server.tools.values())}
42
+ )
43
+
44
+ @router.post("/tools/call", response_model=ApiResponse)
45
+ async def call_tool(
46
+ request: ToolCallRequest,
47
+ server: MCPServer = Depends(get_server)
48
+ ):
49
+ try:
50
+ result = await server.handle_tool_call(request.name, request.arguments)
51
+ if "error" in result:
52
+ return ApiResponse(status="error", error=result["error"])
53
+ return ApiResponse(status="ok", data=result)
54
+ except Exception as e:
55
+ return ApiResponse(status="error", error=str(e))
56
+
57
+ @router.post("/index", response_model=ApiResponse)
58
+ async def index_repository(
59
+ request: IndexRequest,
60
+ background_tasks: BackgroundTasks,
61
+ server: MCPServer = Depends(get_server)
62
+ ):
63
+ # Map to add_code_to_graph tool
64
+ args = {
65
+ "path": request.path,
66
+ "repo_name": request.repo_name,
67
+ "branch": request.branch,
68
+ "force": request.force
69
+ }
70
+
71
+ # We call handle_tool_call which is async
72
+ # But add_code_to_graph starts a background job anyway
73
+ result = await server.handle_tool_call("add_code_to_graph", args)
74
+
75
+ if "error" in result:
76
+ raise HTTPException(status_code=400, detail=result["error"])
77
+
78
+ return ApiResponse(
79
+ status="ok",
80
+ message="Indexing job started",
81
+ data=result
82
+ )
83
+
84
+ @router.post("/query", response_model=ApiResponse)
85
+ async def execute_query(
86
+ request: QueryRequest,
87
+ server: MCPServer = Depends(get_server)
88
+ ):
89
+ result = await server.handle_tool_call("execute_cypher_query", {
90
+ "query": request.query,
91
+ "params": request.params
92
+ })
93
+
94
+ if "error" in result:
95
+ return ApiResponse(status="error", error=result["error"])
96
+
97
+ return ApiResponse(status="ok", data=result)
98
+
99
+ @router.get("/repositories", response_model=ApiResponse)
100
+ async def list_repositories(server: MCPServer = Depends(get_server)):
101
+ result = await server.handle_tool_call("list_indexed_repositories", {})
102
+ return ApiResponse(status="ok", data=result)
@@ -0,0 +1,27 @@
1
+ # src/codegraphcontext/api/schemas.py
2
+ from pydantic import BaseModel, Field
3
+ from typing import Dict, Any, List, Optional
4
+
5
+ class IndexRequest(BaseModel):
6
+ path: str = Field(..., description="Local path to the repository or file to index")
7
+ repo_name: Optional[str] = Field(None, description="Optional name for the repository")
8
+ branch: str = "main"
9
+ force: bool = False
10
+
11
+ class QueryRequest(BaseModel):
12
+ query: str = Field(..., description="Cypher query to execute")
13
+ params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the query")
14
+
15
+ class SearchRequest(BaseModel):
16
+ query: str = Field(..., description="Search query")
17
+ top_k: int = 10
18
+
19
+ class ToolCallRequest(BaseModel):
20
+ name: str = Field(..., description="Name of the MCP tool to call")
21
+ arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments for the tool")
22
+
23
+ class ApiResponse(BaseModel):
24
+ status: str
25
+ message: Optional[str] = None
26
+ data: Optional[Any] = None
27
+ error: Optional[str] = None
@@ -1 +1 @@
1
- # src/codegraphcontext/cli/__init__.py
1
+ # src/codegraphcontext/cli/__init__.py
@@ -1,3 +1,4 @@
1
+ # src/codegraphcontext/cli/cli_helpers.py
1
2
  import asyncio
2
3
  import json
3
4
  import uuid
@@ -867,3 +868,39 @@ def list_watching_helper():
867
868
  console.print(f"\n[cyan]To see watched directories in MCP mode:[/cyan]")
868
869
  console.print(f" 1. Start the MCP server: cgc mcp start")
869
870
  console.print(f" 2. Use the 'list_watched_paths' MCP tool from your IDE")
871
+
872
+
873
+ def setup_scip_helper() -> None:
874
+ """Diagnostic and setup helper for SCIP indexers."""
875
+ from ..tools.scip_indexer import EXTENSION_TO_SCIP
876
+ import shutil
877
+
878
+ console.print("[bold cyan]🔍 Checking SCIP Indexer Availability...[/bold cyan]\n")
879
+
880
+ table = Table(show_header=True, header_style="bold magenta")
881
+ table.add_column("Language", style="cyan")
882
+ table.add_column("Binary", style="yellow")
883
+ table.add_column("Status", style="green")
884
+ table.add_column("Install Hint", style="dim")
885
+
886
+ langs = {}
887
+ for ext, (lang, binary, hint, docker) in EXTENSION_TO_SCIP.items():
888
+ if lang not in langs:
889
+ langs[lang] = (binary, hint, docker)
890
+
891
+ for lang, (binary, hint, docker) in sorted(langs.items()):
892
+ is_installed = shutil.which(binary) is not None
893
+ status = "[green]✓ Installed[/green]" if is_installed else "[red]✗ Not Found[/red]"
894
+ table.add_row(lang, binary, status, hint)
895
+
896
+ console.print(table)
897
+
898
+ # Check Docker
899
+ has_docker = shutil.which("docker") is not None
900
+ if has_docker:
901
+ console.print("\n[green]✓ Docker is available (Auto-fallback enabled)[/green]")
902
+ else:
903
+ console.print("\n[yellow]⚠ Docker not found. Local binaries are required for SCIP.[/yellow]")
904
+
905
+ console.print("\n[dim]To enable SCIP indexing, run:[/dim]")
906
+ console.print("[bold white]cgc config set SCIP_INDEXER true[/bold white]")
@@ -1,3 +1,4 @@
1
+ # src/codegraphcontext/cli/config_manager.py
1
2
  """
2
3
  Configuration management for CodeGraphContext.
3
4
  Handles reading, writing, and validating configuration settings.
@@ -29,6 +30,7 @@ DEFAULT_CONFIG = {
29
30
  "DEFAULT_DATABASE": "falkordb",
30
31
  "FALKORDB_PATH": str(CONFIG_DIR / "global" / "db" / "falkordb"),
31
32
  "FALKORDB_SOCKET_PATH": str(CONFIG_DIR / "global" / "db" / "falkordb.sock"),
33
+ "LADYBUGDB_PATH": str(CONFIG_DIR / "global" / "db" / "ladybugdb"),
32
34
  "INDEX_VARIABLES": "true",
33
35
  "ALLOW_DB_DELETION": "false",
34
36
  "DEBUG_LOGS": "false",
@@ -64,9 +66,10 @@ DEFAULT_CONFIG = {
64
66
 
65
67
  # Configuration key descriptions
66
68
  CONFIG_DESCRIPTIONS = {
67
- "DEFAULT_DATABASE": "Default database backend (neo4j|falkordb|kuzudb|nornic)",
69
+ "DEFAULT_DATABASE": "Default database backend (neo4j|falkordb|falkordb-remote|kuzudb|nornic|ladybugdb)",
68
70
  "FALKORDB_PATH": "Path to FalkorDB database file",
69
71
  "FALKORDB_SOCKET_PATH": "Path to FalkorDB Unix socket",
72
+ "LADYBUGDB_PATH": "Path to LadybugDB database directory",
70
73
  "INDEX_VARIABLES": "Index variable nodes in the graph (lighter graph if false)",
71
74
  "ALLOW_DB_DELETION": "Allow full database deletion commands",
72
75
  "DEBUG_LOGS": "Enable debug logging (for development/troubleshooting)",
@@ -124,7 +127,7 @@ CONFIG_DESCRIPTIONS = {
124
127
 
125
128
  # Valid values for each config key
126
129
  CONFIG_VALIDATORS = {
127
- "DEFAULT_DATABASE": ["neo4j", "falkordb", "falkordb-remote", "kuzudb", "nornic"],
130
+ "DEFAULT_DATABASE": ["neo4j", "falkordb", "falkordb-remote", "kuzudb", "nornic", "ladybugdb"],
128
131
  "INDEX_VARIABLES": ["true", "false"],
129
132
  "ALLOW_DB_DELETION": ["true", "false"],
130
133
  "DEBUG_LOGS": ["true", "false"],
@@ -170,6 +173,21 @@ coverage/
170
173
  """
171
174
 
172
175
 
176
+ def normalize_config_path(value: str, *, absolute: bool = False, base_dir: Optional[Path] = None) -> str:
177
+ """Normalize config path values.
178
+
179
+ - Expands ``~`` and environment variables.
180
+ - Optionally resolves to an absolute path.
181
+ """
182
+ expanded = os.path.expandvars(os.path.expanduser(str(value)))
183
+ path_obj = Path(expanded)
184
+ if absolute and not path_obj.is_absolute():
185
+ path_obj = (base_dir or Path.cwd()) / path_obj
186
+ if absolute:
187
+ return str(path_obj.resolve())
188
+ return str(path_obj)
189
+
190
+
173
191
  def ensure_config_dir(path: Path = CONFIG_DIR):
174
192
  """
175
193
  Ensure that the configuration directory exists.
@@ -423,15 +441,15 @@ def validate_config_value(key: str, value: str) -> tuple[bool, Optional[str]]:
423
441
 
424
442
  if key in ("LOG_FILE_PATH", "DEBUG_LOG_PATH"):
425
443
  # Validate path is writable
426
- log_path = Path(value)
444
+ log_path = Path(normalize_config_path(value, absolute=True))
427
445
  try:
428
446
  log_path.parent.mkdir(parents=True, exist_ok=True)
429
447
  except Exception as e:
430
448
  return False, f"Cannot create log directory: {e}"
431
449
 
432
- if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH"):
450
+ if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH", "LADYBUGDB_PATH"):
433
451
  # Validate path is writable
434
- db_path = Path(value)
452
+ db_path = Path(normalize_config_path(value, absolute=True))
435
453
  try:
436
454
  db_path.parent.mkdir(parents=True, exist_ok=True)
437
455
  except Exception as e:
@@ -41,6 +41,7 @@ from .cli_helpers import (
41
41
  watch_helper,
42
42
  unwatch_helper,
43
43
  list_watching_helper,
44
+ setup_scip_helper,
44
45
  )
45
46
 
46
47
  # Set the log level for the noisy neo4j, asyncio, and urllib3 loggers to keep the output clean.
@@ -77,7 +78,7 @@ from .visualizer import (
77
78
  # Initialize the Typer app and Rich console for formatted output.
78
79
  app = typer.Typer(
79
80
  name="cgc",
80
- help="CodeGraphContext: An MCP server for AI-powered code analysis.\n\n[DEPRECATED] 'cgc start' is deprecated. Use 'cgc mcp start' instead.",
81
+ help="CodeGraphContext: An MCP server for AI-powered code analysis.",
81
82
  add_completion=True,
82
83
  )
83
84
  console = Console(stderr=True)
@@ -304,6 +305,7 @@ def _load_credentials():
304
305
  from codegraphcontext.cli.config_manager import (
305
306
  ensure_config_dir,
306
307
  codegraphcontext_dotenv_at_cwd,
308
+ normalize_config_path,
307
309
  )
308
310
 
309
311
  # Ensure config directory exists (lazy initialization)
@@ -337,6 +339,14 @@ def _load_credentials():
337
339
  with open(mcp_file_path, "r") as f:
338
340
  mcp_config = json.load(f)
339
341
  server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
342
+ if isinstance(server_env, dict):
343
+ normalized_env = {}
344
+ for env_key, env_value in server_env.items():
345
+ if env_value is not None and "PATH" in env_key:
346
+ normalized_env[env_key] = normalize_config_path(str(env_value), absolute=True)
347
+ else:
348
+ normalized_env[env_key] = env_value
349
+ server_env = normalized_env
340
350
  _append_source("mcp.json", server_env)
341
351
  except Exception as e:
342
352
  console.print(f"[yellow]Warning: Could not load mcp.json: {e}[/yellow]")
@@ -383,6 +393,8 @@ def _load_credentials():
383
393
  if value is not None: # Only set non-None values
384
394
  if key in runtime_env:
385
395
  continue
396
+ if "PATH" in key:
397
+ value = normalize_config_path(str(value), absolute=True)
386
398
  os.environ[key] = str(value)
387
399
 
388
400
  # Report what was loaded
@@ -543,9 +555,9 @@ def config_db(backend: str = typer.Argument(..., help="Database backend: 'neo4j'
543
555
  cgc config db kuzudb
544
556
  """
545
557
  backend = backend.lower()
546
- if backend not in ['falkordb', 'falkordb-remote', 'neo4j', 'kuzudb']:
558
+ if backend not in ['falkordb', 'falkordb-remote', 'neo4j', 'kuzudb', 'ladybugdb']:
547
559
  console.print(f"[bold red]Invalid backend: {backend}[/bold red]")
548
- console.print("Must be 'falkordb', 'falkordb-remote', 'neo4j', or 'kuzudb'")
560
+ console.print("Must be 'falkordb', 'falkordb-remote', 'neo4j', 'kuzudb', or 'ladybugdb'")
549
561
  raise typer.Exit(code=1)
550
562
 
551
563
  updated = config_manager.set_config_value("DEFAULT_DATABASE", backend)
@@ -760,6 +772,29 @@ registry_app = typer.Typer(
760
772
  app.add_typer(registry_app, name="registry")
761
773
 
762
774
 
775
+ # Create API command group
776
+ api_app = typer.Typer(help="CGC Gateway (HTTP API) commands")
777
+ app.add_typer(api_app, name="api")
778
+
779
+ @api_app.command("start")
780
+ def api_start(
781
+ host: str = typer.Option("0.0.0.0", help="Host to bind the server to"),
782
+ port: int = typer.Option(8000, help="Port to bind the server to"),
783
+ reload: bool = typer.Option(False, help="Enable auto-reload (development only)"),
784
+ ):
785
+ """
786
+ Start the CGC Gateway HTTP API server.
787
+
788
+ This server provides a REST API that can be used by ChatGPT Actions,
789
+ Claude, or web frontends to interact with the CodeGraphContext graph.
790
+ """
791
+ import uvicorn
792
+ console.print(f"[bold green]Starting CGC Gateway on {host}:{port}...[/bold green]")
793
+ _load_credentials()
794
+ uvicorn.run("codegraphcontext.api.app:app", host=host, port=port, reload=reload)
795
+
796
+
797
+
763
798
  @registry_app.callback()
764
799
  def registry_callback(ctx: typer.Context):
765
800
  """Browse and download bundles from the registry."""
@@ -956,6 +991,15 @@ def doctor():
956
991
  console.print(f" [red]✗[/red] KuzuDB is not installed")
957
992
  console.print(f" Run: pip install kuzu")
958
993
  all_checks_passed = False
994
+ elif default_db == "ladybugdb":
995
+ from importlib.util import find_spec
996
+
997
+ if find_spec("ladybug") is not None:
998
+ console.print(f" [green]✓[/green] LadybugDB core (ladybug) is installed")
999
+ else:
1000
+ console.print(f" [red]✗[/red] LadybugDB core (ladybug) is not installed")
1001
+ console.print(f" Run: pip install ladybug")
1002
+ all_checks_passed = False
959
1003
  else:
960
1004
  # FalkorDB
961
1005
  try:
@@ -1046,13 +1090,6 @@ def doctor():
1046
1090
 
1047
1091
 
1048
1092
 
1049
- @app.command()
1050
- def start():
1051
- """
1052
- [DEPRECATED] Use 'cgc mcp start' instead. This command will be removed in a future version.
1053
- """
1054
- console.print("[yellow]⚠️ 'cgc start' is deprecated. Use 'cgc mcp start' instead.[/yellow]")
1055
- mcp_start()
1056
1093
 
1057
1094
 
1058
1095
  @app.command()
@@ -1106,6 +1143,16 @@ def stats(
1106
1143
  path = str(Path(path).resolve())
1107
1144
  stats_helper(path, context)
1108
1145
 
1146
+ @app.command("setup-scip")
1147
+ def setup_scip():
1148
+ """
1149
+ Check availability of SCIP indexers and provide installation hints.
1150
+
1151
+ This command audits your system for SCIP binaries (like scip-python, scip-go)
1152
+ and checks if Docker is available for fallback indexing.
1153
+ """
1154
+ setup_scip_helper()
1155
+
1109
1156
  @app.command()
1110
1157
  def delete(
1111
1158
  path: Optional[str] = typer.Argument(None, help="Path of the repository to delete from the code graph."),
@@ -2581,6 +2628,10 @@ def main(
2581
2628
  if version_:
2582
2629
  console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
2583
2630
  raise typer.Exit()
2631
+
2632
+ if help_:
2633
+ typer.echo(ctx.get_help())
2634
+ raise typer.Exit()
2584
2635
 
2585
2636
  if ctx.invoked_subcommand is None:
2586
2637
  console.print("[bold green]👋 Welcome to CodeGraphContext (cgc)![/bold green]\n")
@@ -1,3 +1,4 @@
1
+ # src/codegraphcontext/cli/setup_wizard.py
1
2
  from InquirerPy import prompt
2
3
  from rich.console import Console
3
4
  import subprocess
@@ -10,6 +11,7 @@ import sys
10
11
  import shutil
11
12
  import yaml
12
13
  from codegraphcontext.core.database import DatabaseManager
14
+ from codegraphcontext.cli.config_manager import normalize_config_path
13
15
 
14
16
  console = Console()
15
17
 
@@ -513,17 +515,15 @@ def configure_mcp_client():
513
515
  except Exception:
514
516
  pass
515
517
 
516
- # Add all configuration values, converting relative paths to absolute
518
+ # Add all configuration values, normalizing path-related settings
517
519
  for key, value in config.items():
518
520
  # Skip database credentials (already added above)
519
521
  if key in ["NEO4J_URI", "NEO4J_USERNAME", "NEO4J_PASSWORD"]:
520
522
  continue
521
523
 
522
- # Convert relative paths to absolute for path-related configs
524
+ # Expand ~/$VARS and convert relative paths to absolute for MCP env
523
525
  if "PATH" in key and value:
524
- path_obj = Path(value)
525
- if not path_obj.is_absolute():
526
- value = str(path_obj.resolve())
526
+ value = normalize_config_path(value, absolute=True)
527
527
 
528
528
  env_vars[key] = value
529
529
 
@@ -1,3 +1,4 @@
1
+ # src/codegraphcontext/cli/visualizer.py
1
2
  import urllib.parse
2
3
  from typing import Optional, List, Dict, Any, Set
3
4
  from pathlib import Path
@@ -60,7 +60,7 @@ def _is_nornic_configured() -> bool:
60
60
  os.getenv('NORNIC_PASSWORD')
61
61
  ])
62
62
 
63
- def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'NornicDBManager']:
63
+ def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'NornicDBManager', 'LadybugDBManager']:
64
64
  """
65
65
  Factory function to get the appropriate database manager based on configuration.
66
66
 
@@ -126,8 +126,14 @@ def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManage
126
126
  from .database_nornic import NornicDBManager
127
127
  info_logger("Using Nornic DB (explicit)")
128
128
  return NornicDBManager()
129
+ elif db_type == 'ladybugdb':
130
+ if not _is_kuzudb_available():
131
+ raise ValueError("Database set to 'ladybugdb' but LadybugDB core (kuzu) is not installed.\nRun 'pip install kuzu'")
132
+ from .database_ladybug import LadybugDBManager
133
+ info_logger(f"Using LadybugDB (explicit) at {db_path or 'default path'}")
134
+ return LadybugDBManager(db_path=db_path)
129
135
  else:
130
- raise ValueError(f"Unknown database type: '{db_type}'. Use 'kuzudb', 'falkordb', 'falkordb-remote', 'neo4j', or 'nornic'.")
136
+ raise ValueError(f"Unknown database type: '{db_type}'. Use 'kuzudb', 'ladybugdb', 'falkordb', 'falkordb-remote', 'neo4j', or 'nornic'.")
131
137
 
132
138
  # Implicit: remote FalkorDB when FALKORDB_HOST is set (explicit infra signal)
133
139
  if _is_falkordb_remote_configured():
@@ -182,6 +188,7 @@ from .database import DatabaseManager
182
188
  from .database_falkordb import FalkorDBManager
183
189
  from .database_falkordb_remote import FalkorDBRemoteManager
184
190
  from .database_kuzu import KuzuDBManager
191
+ from .database_ladybug import LadybugDBManager
185
192
  from .database_nornic import NornicDBManager
186
193
 
187
- __all__ = ['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'NornicDBManager', 'get_database_manager']
194
+ __all__ = ['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'LadybugDBManager', 'NornicDBManager', 'get_database_manager']
@@ -1,3 +1,4 @@
1
+ # src/codegraphcontext/core/bundle_registry.py
1
2
  import requests
2
3
  from pathlib import Path
3
4
  from typing import Optional, List, Dict, Any, Tuple
@@ -280,8 +280,15 @@ class CGCBundle:
280
280
  if hasattr(node, attr):
281
281
  repo[attr] = getattr(node, attr)
282
282
 
283
- metadata["repo"] = repo.get('name', str(repo_path))
284
- metadata["repo_path"] = repo.get('path')
283
+ metadata["repo"] = repo.get('name', str(repo_path.name if repo_path else 'unknown'))
284
+ # Clean up absolute path prefix to keep it relative
285
+ meta_path = repo.get('path', '')
286
+ if repo_path and meta_path.startswith(str(repo_path.resolve())):
287
+ repo_str = str(repo_path.resolve())
288
+ rel = meta_path[len(repo_str):].lstrip('/')
289
+ metadata["repo_path"] = "./" + rel if rel else "."
290
+ else:
291
+ metadata["repo_path"] = meta_path
285
292
  metadata["is_dependency"] = repo.get('is_dependency', False)
286
293
  else:
287
294
  # All repositories
@@ -410,6 +417,14 @@ class CGCBundle:
410
417
  elif hasattr(node, 'properties'):
411
418
  node_dict = dict(node.properties)
412
419
 
420
+ # Clean up absolute path prefix to keep it relative
421
+ if repo_path:
422
+ repo_str = str(repo_path.resolve())
423
+ for key, val in list(node_dict.items()):
424
+ if isinstance(val, str) and val.startswith(repo_str):
425
+ rel = val[len(repo_str):].lstrip('/')
426
+ node_dict[key] = "./" + rel if rel else "."
427
+
413
428
  node_dict['_labels'] = labels
414
429
 
415
430
  # Store internal ID for reference
@@ -480,6 +495,14 @@ class CGCBundle:
480
495
  elif hasattr(rel, 'properties'):
481
496
  rel_props = dict(rel.properties)
482
497
 
498
+ # Clean up absolute path prefix inside edge properties
499
+ if repo_path:
500
+ repo_str = str(repo_path.resolve())
501
+ for key, val in list(rel_props.items()):
502
+ if isinstance(val, str) and val.startswith(repo_str):
503
+ rel = val[len(repo_str):].lstrip('/')
504
+ rel_props[key] = "./" + rel if rel else "."
505
+
483
506
  # Create edge representation
484
507
  edge_dict = {
485
508
  'from': from_id,
@@ -1,3 +1,4 @@
1
+ # src/codegraphcontext/core/cgcignore.py
1
2
  from pathlib import Path
2
3
  from typing import Iterable, Optional, Tuple
3
4
  from pathspec import PathSpec