codegraphcontext 0.5.1__py3-none-any.whl → 0.5.2__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.
- codegraphcontext/api/mcp_sse.py +55 -19
- codegraphcontext/cli/cli_helpers.py +57 -5
- codegraphcontext/cli/config_manager.py +103 -8
- codegraphcontext/cli/main.py +80 -11
- codegraphcontext/cli/setup_wizard.py +61 -56
- codegraphcontext/core/cgcignore.py +4 -1
- codegraphcontext/core/database_embedded_kuzu.py +1265 -0
- codegraphcontext/core/database_kuzu.py +37 -1203
- codegraphcontext/core/database_ladybug.py +37 -1177
- codegraphcontext/core/graph_query.py +45 -0
- codegraphcontext/core/watcher.py +100 -31
- codegraphcontext/server.py +57 -14
- codegraphcontext/tools/code_finder.py +16 -19
- codegraphcontext/tools/graph_builder.py +6 -2
- codegraphcontext/tools/indexing/embeddings.py +63 -49
- codegraphcontext/tools/indexing/persistence/writer.py +116 -58
- codegraphcontext/tools/indexing/pipeline.py +44 -0
- codegraphcontext/tools/indexing/resolution/calls.py +54 -29
- codegraphcontext/tools/indexing/resolution/inheritance.py +9 -1
- codegraphcontext/tools/indexing/scip_pipeline.py +25 -10
- codegraphcontext/tools/indexing/vector_resolver.py +72 -57
- codegraphcontext/tools/languages/python.py +2 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +1 -1
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +2 -2
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +2 -2
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +1 -1
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +3 -3
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +1 -1
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +2 -2
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +1 -1
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +1 -1
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +1 -1
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +2 -2
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +2 -2
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +1 -1
- codegraphcontext/tools/scip_indexer.py +45 -3
- codegraphcontext/tools/tree_sitter_parser.py +41 -96
- codegraphcontext/utils/debug_log.py +5 -0
- codegraphcontext/utils/gcf_encoder.py +58 -0
- codegraphcontext/utils/visualize_graph.py +461 -0
- codegraphcontext/viz/server.py +348 -32
- {codegraphcontext-0.5.1.dist-info → codegraphcontext-0.5.2.dist-info}/METADATA +190 -18
- {codegraphcontext-0.5.1.dist-info → codegraphcontext-0.5.2.dist-info}/RECORD +47 -82
- {codegraphcontext-0.5.1.dist-info → codegraphcontext-0.5.2.dist-info}/WHEEL +1 -1
- codegraphcontext/viz/dist/assets/__vite-browser-external-9wXp6ZBx.js +0 -1
- codegraphcontext/viz/dist/assets/function-calls-BtRHrqa2.png +0 -0
- codegraphcontext/viz/dist/assets/graph-total-D1fBAugo.png +0 -0
- codegraphcontext/viz/dist/assets/hero-graph-2voMJp2a.jpg +0 -0
- codegraphcontext/viz/dist/assets/hierarchy-DGADo0YT.png +0 -0
- codegraphcontext/viz/dist/assets/index-C-187lf0.js +0 -5587
- codegraphcontext/viz/dist/assets/index-fNAa6jgv.css +0 -1
- codegraphcontext/viz/dist/assets/parser-pyodide.worker-BgsDfaad.js +0 -370
- codegraphcontext/viz/dist/assets/parser.worker-_nvrecvj.js +0 -233
- codegraphcontext/viz/dist/assets/tree-sitter-qKYAACSa.wasm +0 -0
- codegraphcontext/viz/dist/cgcIcon.png +0 -0
- codegraphcontext/viz/dist/favicon.ico +0 -0
- codegraphcontext/viz/dist/index.html +0 -32
- codegraphcontext/viz/dist/logo-icon.svg +0 -85
- codegraphcontext/viz/dist/logo.svg +0 -100
- codegraphcontext/viz/dist/placeholder.svg +0 -1
- codegraphcontext/viz/dist/preview-image.png +0 -0
- codegraphcontext/viz/dist/robots.txt +0 -14
- codegraphcontext/viz/dist/wasm/tree-sitter-c.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-core.js +0 -1
- codegraphcontext/viz/dist/wasm/tree-sitter-cpp.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-dart.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-go.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-java.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-javascript.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-kotlin.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-perl.wasm +0 -1
- codegraphcontext/viz/dist/wasm/tree-sitter-php.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-python.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-ruby.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-rust.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-swift.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-tsx.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter-typescript.wasm +0 -0
- codegraphcontext/viz/dist/wasm/tree-sitter.wasm +0 -0
- codegraphcontext/viz/dist/wasm/web-tree-sitter.js +0 -4007
- codegraphcontext/viz/dist/wasm/web-tree-sitter.wasm +0 -0
- {codegraphcontext-0.5.1.dist-info → codegraphcontext-0.5.2.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.5.1.dist-info → codegraphcontext-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.5.1.dist-info → codegraphcontext-0.5.2.dist-info}/top_level.txt +0 -0
codegraphcontext/api/mcp_sse.py
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# src/codegraphcontext/api/mcp_sse.py
|
|
2
2
|
import json
|
|
3
3
|
import asyncio
|
|
4
|
-
|
|
4
|
+
import logging
|
|
5
|
+
import anyio
|
|
6
|
+
from fastapi import Request, Response
|
|
5
7
|
from mcp.server import Server
|
|
6
8
|
from mcp.server.models import InitializationOptions
|
|
7
9
|
from mcp.types import Tool, TextContent, ServerCapabilities, ToolsCapability
|
|
8
10
|
from mcp.server.sse import SseServerTransport
|
|
9
|
-
|
|
10
11
|
from codegraphcontext.api.router import get_server
|
|
11
12
|
from codegraphcontext.server import _strip_workspace_prefix, _apply_response_token_limit
|
|
12
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
13
16
|
# Create the MCP Server instance using the SDK
|
|
14
17
|
mcp_server = Server("CodeGraphContext")
|
|
15
18
|
|
|
@@ -32,38 +35,71 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[TextConten
|
|
|
32
35
|
server = get_server()
|
|
33
36
|
args = arguments or {}
|
|
34
37
|
|
|
35
|
-
# Execute via the existing handler logic
|
|
36
38
|
result = await server.handle_tool_call(name, args)
|
|
37
39
|
result = _strip_workspace_prefix(result)
|
|
38
40
|
|
|
39
41
|
if "error" in result:
|
|
40
42
|
return [TextContent(type="text", text=f"Error: {result['error']}")]
|
|
41
43
|
|
|
42
|
-
# Format result as JSON string for the AI, with the same token budget
|
|
43
|
-
# the stdio transport applies.
|
|
44
44
|
response_text = json.dumps(result, indent=2)
|
|
45
45
|
response_text = _apply_response_token_limit(name, response_text)
|
|
46
46
|
return [TextContent(type="text", text=response_text)]
|
|
47
47
|
|
|
48
|
-
# Create the SSE transport.
|
|
49
|
-
# The messages_url is where the client will POST JSON-RPC messages.
|
|
48
|
+
# Create the SSE transport.
|
|
50
49
|
sse = SseServerTransport("/api/v1/mcp/messages")
|
|
51
50
|
|
|
52
51
|
async def handle_sse(request: Request):
|
|
53
52
|
"""Entry point for the SSE connection."""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
logger.info("SSE client connected")
|
|
54
|
+
try:
|
|
55
|
+
async with sse.connect_sse(request.scope, request.receive, request._send) as (read_stream, write_stream):
|
|
56
|
+
await mcp_server.run(
|
|
57
|
+
read_stream,
|
|
58
|
+
write_stream,
|
|
59
|
+
InitializationOptions(
|
|
60
|
+
server_name="CodeGraphContext",
|
|
61
|
+
server_version="0.1.0",
|
|
62
|
+
capabilities=ServerCapabilities(
|
|
63
|
+
tools=ToolsCapability(listChanged=False)
|
|
64
|
+
)
|
|
63
65
|
)
|
|
64
66
|
)
|
|
65
|
-
|
|
67
|
+
except anyio.EndOfStream:
|
|
68
|
+
logger.debug("SSE client disconnected cleanly (stream ended)")
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
logger.debug("SSE connection closed: %s", type(exc).__name__)
|
|
71
|
+
finally:
|
|
72
|
+
logger.info("SSE client disconnected — handler exited, resources freed")
|
|
73
|
+
|
|
66
74
|
|
|
67
75
|
async def handle_messages(request: Request):
|
|
68
|
-
"""Endpoint for receiving messages from the client.
|
|
69
|
-
|
|
76
|
+
"""Endpoint for receiving messages from the client.
|
|
77
|
+
|
|
78
|
+
Uses a buffer framing collector to ensure the full JSON-RPC payload
|
|
79
|
+
is received before processing. This prevents crashes caused by large
|
|
80
|
+
responses being split across SSE line boundaries.
|
|
81
|
+
"""
|
|
82
|
+
# Buffer the complete request body before any parsing occurs.
|
|
83
|
+
# request.body() accumulates all chunks, preventing partial JSON reads.
|
|
84
|
+
raw_body = await request.body()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
json.loads(raw_body)
|
|
88
|
+
except json.JSONDecodeError as e:
|
|
89
|
+
return Response(
|
|
90
|
+
content=json.dumps({
|
|
91
|
+
"jsonrpc": "2.0",
|
|
92
|
+
"error": {
|
|
93
|
+
"code": -32700,
|
|
94
|
+
"message": f"Parse error: incomplete or malformed JSON-RPC message: {e}"
|
|
95
|
+
},
|
|
96
|
+
"id": None
|
|
97
|
+
}),
|
|
98
|
+
status_code=400,
|
|
99
|
+
media_type="application/json"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
await sse.handle_post_message(request.scope, request.receive, request._send)
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
logger.debug("Message handler closed: %s", type(exc).__name__)
|
|
@@ -9,6 +9,7 @@ import time
|
|
|
9
9
|
import os
|
|
10
10
|
from typing import Optional, List, Dict, Any
|
|
11
11
|
import typer
|
|
12
|
+
from rich import box
|
|
12
13
|
from rich.console import Console
|
|
13
14
|
from rich.table import Table
|
|
14
15
|
from rich.progress import (
|
|
@@ -85,6 +86,42 @@ def _print_call_resolution_diagnostics(graph_builder: GraphBuilder, limit: int =
|
|
|
85
86
|
console.print(table)
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def _format_extension_counts(files_by_extension: Dict[str, int]) -> str:
|
|
90
|
+
if not files_by_extension:
|
|
91
|
+
return "None"
|
|
92
|
+
return ", ".join(
|
|
93
|
+
f"{extension}: {count}" for extension, count in files_by_extension.items()
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _print_index_execution_summary(graph_builder: GraphBuilder) -> None:
|
|
98
|
+
summary = getattr(graph_builder, "last_index_summary", None)
|
|
99
|
+
if not summary:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
table = Table(
|
|
103
|
+
title="CGC Index Execution Summary",
|
|
104
|
+
show_header=True,
|
|
105
|
+
header_style="bold cyan",
|
|
106
|
+
box=box.ASCII,
|
|
107
|
+
)
|
|
108
|
+
table.add_column("Metric", style="cyan", no_wrap=True)
|
|
109
|
+
table.add_column("Value", style="green", overflow="fold")
|
|
110
|
+
table.add_row("Total scanned files", str(summary.get("total_scanned_files", 0)))
|
|
111
|
+
table.add_row(
|
|
112
|
+
"Files by extension",
|
|
113
|
+
_format_extension_counts(summary.get("files_by_extension", {})),
|
|
114
|
+
)
|
|
115
|
+
table.add_row("Function nodes", str(summary.get("function_nodes", 0)))
|
|
116
|
+
table.add_row("Class nodes", str(summary.get("class_nodes", 0)))
|
|
117
|
+
table.add_row("CALLS edges", str(summary.get("call_edges", 0)))
|
|
118
|
+
table.add_row(
|
|
119
|
+
"Serialization seconds",
|
|
120
|
+
f"{summary.get('serialization_seconds', 0.0):.2f}",
|
|
121
|
+
)
|
|
122
|
+
console.print(table)
|
|
123
|
+
|
|
124
|
+
|
|
88
125
|
def _initialize_services(
|
|
89
126
|
cli_context_flag: Optional[str] = None,
|
|
90
127
|
cwd: Optional[Path] = None,
|
|
@@ -315,6 +352,7 @@ def index_helper(path: str, context: Optional[str] = None):
|
|
|
315
352
|
time_end = time.time()
|
|
316
353
|
elapsed = time_end - time_start
|
|
317
354
|
_print_call_resolution_diagnostics(graph_builder)
|
|
355
|
+
_print_index_execution_summary(graph_builder)
|
|
318
356
|
console.print(f"[green]Successfully finished indexing: {path} in {elapsed:.2f} seconds[/green]")
|
|
319
357
|
|
|
320
358
|
# Check if auto-watch is enabled
|
|
@@ -363,6 +401,7 @@ def add_package_helper(package_name: str, language: str, context: Optional[str]
|
|
|
363
401
|
try:
|
|
364
402
|
asyncio.run(_run_index_with_progress(graph_builder, package_path, is_dependency=True, cgcignore_path=ctx.cgcignore_path))
|
|
365
403
|
_print_call_resolution_diagnostics(graph_builder)
|
|
404
|
+
_print_index_execution_summary(graph_builder)
|
|
366
405
|
console.print(f"[green]Successfully finished indexing package: {package_name}[/green]")
|
|
367
406
|
except Exception as e:
|
|
368
407
|
console.print(f"[bold red]An error occurred during package indexing:[/bold red] {e}")
|
|
@@ -527,6 +566,7 @@ from ..viz.server import run_server, set_db_manager
|
|
|
527
566
|
|
|
528
567
|
def visualize_helper(
|
|
529
568
|
repo_path: Optional[str] = None,
|
|
569
|
+
host: str = "127.0.0.1",
|
|
530
570
|
port: int = 8000,
|
|
531
571
|
context: Optional[str] = None,
|
|
532
572
|
cypher_query: Optional[str] = None,
|
|
@@ -621,7 +661,7 @@ def visualize_helper(
|
|
|
621
661
|
threading.Thread(target=open_browser, daemon=True).start()
|
|
622
662
|
|
|
623
663
|
try:
|
|
624
|
-
run_server(host=
|
|
664
|
+
run_server(host=host, port=port, static_dir=str(static_dir))
|
|
625
665
|
except Exception as e:
|
|
626
666
|
console.print(f"[bold red]An error occurred while running the server:[/bold red] {e}")
|
|
627
667
|
raise typer.Exit(code=1)
|
|
@@ -666,6 +706,7 @@ def reindex_helper(path: str, context: Optional[str] = None):
|
|
|
666
706
|
time_end = time.time()
|
|
667
707
|
elapsed = time_end - time_start
|
|
668
708
|
_print_call_resolution_diagnostics(graph_builder)
|
|
709
|
+
_print_index_execution_summary(graph_builder)
|
|
669
710
|
console.print(f"[green]Successfully re-indexed: {path} in {elapsed:.2f} seconds[/green]")
|
|
670
711
|
except Exception as e:
|
|
671
712
|
console.print(f"[bold red]An error occurred during re-indexing:[/bold red] {e}")
|
|
@@ -850,7 +891,12 @@ def stats_helper(path: str = None, context: Optional[str] = None):
|
|
|
850
891
|
db_manager.close_driver()
|
|
851
892
|
|
|
852
893
|
|
|
853
|
-
def watch_helper(
|
|
894
|
+
def watch_helper(
|
|
895
|
+
path: str,
|
|
896
|
+
context: Optional[str] = None,
|
|
897
|
+
use_polling: Optional[bool] = None,
|
|
898
|
+
sync_on_start: bool = False,
|
|
899
|
+
):
|
|
854
900
|
"""Watch a directory for changes and auto-update the graph (blocking mode)."""
|
|
855
901
|
import logging
|
|
856
902
|
from ..core.watcher import CodeWatcher
|
|
@@ -913,11 +959,17 @@ def watch_helper(path: str, context: Optional[str] = None, use_polling: Optional
|
|
|
913
959
|
|
|
914
960
|
# Add the directory to watch
|
|
915
961
|
if is_indexed:
|
|
916
|
-
|
|
962
|
+
if sync_on_start:
|
|
963
|
+
console.print("[green]✓[/green] Already indexed. Synchronizing current files...")
|
|
964
|
+
else:
|
|
965
|
+
console.print("[green]✓[/green] Already indexed. Watching for future changes only.")
|
|
966
|
+
console.print(
|
|
967
|
+
"[dim]Use 'cgc index --force' or 'cgc watch --sync-on-start' to reconcile existing changes.[/dim]"
|
|
968
|
+
)
|
|
917
969
|
watcher.watch_directory(
|
|
918
970
|
str(path_obj),
|
|
919
971
|
perform_initial_scan=False,
|
|
920
|
-
sync_on_start=
|
|
972
|
+
sync_on_start=sync_on_start,
|
|
921
973
|
cgcignore_path=ctx.cgcignore_path,
|
|
922
974
|
)
|
|
923
975
|
else:
|
|
@@ -960,7 +1012,7 @@ def watch_helper(path: str, context: Optional[str] = None, use_polling: Optional
|
|
|
960
1012
|
finally:
|
|
961
1013
|
watcher.stop()
|
|
962
1014
|
db_manager.close_driver()
|
|
963
|
-
console.print("[green]✓[/green] Watcher stopped.
|
|
1015
|
+
console.print("[green]✓[/green] Watcher stopped.")
|
|
964
1016
|
|
|
965
1017
|
|
|
966
1018
|
|
|
@@ -8,13 +8,50 @@ Also manages the context system (config.yaml) alongside the existing .env file.
|
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Optional, Dict, Any, List
|
|
11
|
-
|
|
12
|
-
from rich.
|
|
11
|
+
try:
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
console = Console()
|
|
15
|
+
except Exception:
|
|
16
|
+
# Lightweight fallback when 'rich' is not installed (tests or minimal envs)
|
|
17
|
+
class _TableFallback:
|
|
18
|
+
def __init__(self, show_header=True, header_style=None):
|
|
19
|
+
self._cols = []
|
|
20
|
+
self._rows = []
|
|
21
|
+
|
|
22
|
+
def add_column(self, name, **_kwargs):
|
|
23
|
+
self._cols.append(name)
|
|
24
|
+
|
|
25
|
+
def add_row(self, *cells):
|
|
26
|
+
self._rows.append([str(c) for c in cells])
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
# Simple text table rendering
|
|
30
|
+
out = []
|
|
31
|
+
if self._cols:
|
|
32
|
+
out.append(" | ".join(self._cols))
|
|
33
|
+
out.append("-" * max(10, len(out[0])))
|
|
34
|
+
for r in self._rows:
|
|
35
|
+
out.append(" | ".join(r))
|
|
36
|
+
return "\n".join(out)
|
|
37
|
+
|
|
38
|
+
class _ConsoleFallback:
|
|
39
|
+
def print(self, *args, **kwargs):
|
|
40
|
+
# Mimic rich.console.Console.print by delegating to built-in print
|
|
41
|
+
end = kwargs.get("end", "\n")
|
|
42
|
+
sep = kwargs.get("sep", " ")
|
|
43
|
+
for a in args:
|
|
44
|
+
if isinstance(a, _TableFallback):
|
|
45
|
+
built = str(a)
|
|
46
|
+
print(built, end=end)
|
|
47
|
+
else:
|
|
48
|
+
print(a, end=end)
|
|
49
|
+
|
|
50
|
+
Table = _TableFallback
|
|
51
|
+
console = _ConsoleFallback()
|
|
13
52
|
import os
|
|
14
53
|
import yaml
|
|
15
54
|
|
|
16
|
-
console = Console()
|
|
17
|
-
|
|
18
55
|
|
|
19
56
|
def _atomic_write_text(path: Path, content: str, *, secure: bool = False) -> None:
|
|
20
57
|
"""Write *content* to *path* atomically (temp file + replace)."""
|
|
@@ -72,9 +109,14 @@ DEFAULT_CONFIG = {
|
|
|
72
109
|
# SCIP indexer feature flag (default off — existing Tree-sitter behaviour unchanged)
|
|
73
110
|
"SCIP_INDEXER": "false",
|
|
74
111
|
"SCIP_LANGUAGES": "python,typescript,javascript,go,rust,java,dart,cpp,c,csharp",
|
|
112
|
+
"SCIP_LOCAL_INDEXER_TIMEOUT_SECONDS": "300",
|
|
75
113
|
"SKIP_EXTERNAL_RESOLUTION": "false",
|
|
76
114
|
# 0 = unlimited; any positive integer caps MCP tool response size.
|
|
77
115
|
"MAX_TOOL_RESPONSE_TOKENS": "0",
|
|
116
|
+
# 0 = unlimited; any positive integer caps response size in raw characters.
|
|
117
|
+
# When both MAX_TOOL_RESPONSE_TOKENS and MAX_PROMPT_CHARS are set, the
|
|
118
|
+
# stricter (smaller) effective character budget wins.
|
|
119
|
+
"MAX_PROMPT_CHARS": "0",
|
|
78
120
|
# JSON object mapping tool names to integer result-count limits.
|
|
79
121
|
# Example: {"find_code": 20, "analyze_code_relationships": 10, "find_dead_code": 30}
|
|
80
122
|
"TOOL_RESULT_LIMITS": "{}",
|
|
@@ -85,6 +127,9 @@ DEFAULT_CONFIG = {
|
|
|
85
127
|
"CGC_EMBEDDING_BATCH_SIZE": "256",
|
|
86
128
|
# Default fuzzy matching behavior for `cgc find name` (overridable per-command with --fuzzy/--no-fuzzy)
|
|
87
129
|
"FUZZY_SEARCH": "true",
|
|
130
|
+
# Default LLM model names used for graph queries when no value is explicitly configured
|
|
131
|
+
"OPENAI_MODEL": "gpt-4o",
|
|
132
|
+
"ANTHROPIC_MODEL": "claude-3-5-sonnet-20241022",
|
|
88
133
|
}
|
|
89
134
|
|
|
90
135
|
# Configuration key descriptions
|
|
@@ -113,8 +158,10 @@ CONFIG_DESCRIPTIONS = {
|
|
|
113
158
|
"INDEX_SOURCE": "Store full source code in graph database (for faster indexing use false, for better performance use true)",
|
|
114
159
|
"SCIP_INDEXER": "Use SCIP-based indexing for higher accuracy call/inheritance resolution (requires scip-<lang> tools installed)",
|
|
115
160
|
"SCIP_LANGUAGES": "Comma-separated languages to index via SCIP when SCIP_INDEXER=true (python,typescript,javascript,go,rust,java,dart,cpp,c,csharp)",
|
|
161
|
+
"SCIP_LOCAL_INDEXER_TIMEOUT_SECONDS": "Timeout in seconds for the local SCIP indexer subprocess (default 300). Raise it for large repositories whose indexer runs longer than 5 minutes; a value <= 0 or non-numeric falls back to 300.",
|
|
116
162
|
"SKIP_EXTERNAL_RESOLUTION": "Skip resolution attempts for external library method calls (recommended for enterprise large Java/Spring codebases)",
|
|
117
163
|
"MAX_TOOL_RESPONSE_TOKENS": "Maximum tokens per MCP tool response (0 = unlimited). Truncates oversized payloads and appends a notice.",
|
|
164
|
+
"MAX_PROMPT_CHARS": "Maximum characters per MCP tool response (0 = unlimited). When set alongside MAX_TOOL_RESPONSE_TOKENS the stricter limit wins. Truncated payloads receive a visible [CGC] notice and a stdout warning is emitted.",
|
|
118
165
|
"TOOL_RESULT_LIMITS": "JSON object mapping tool names to max result counts, e.g. {\"find_code\": 20, \"analyze_code_relationships\": 10}. Missing keys use built-in defaults.",
|
|
119
166
|
# Post-indexing resolution phases
|
|
120
167
|
"ENABLE_INHERIT_RESOLVE": (
|
|
@@ -151,6 +198,16 @@ CONFIG_DESCRIPTIONS = {
|
|
|
151
198
|
"Enable fuzzy matching by default for `cgc find name` (true|false). "
|
|
152
199
|
"Per-invocation overrides are available via --fuzzy / --no-fuzzy."
|
|
153
200
|
),
|
|
201
|
+
"OPENAI_MODEL": (
|
|
202
|
+
"Default OpenAI model used for graph queries. "
|
|
203
|
+
"Requires OPENAI_API_KEY environment variable. "
|
|
204
|
+
"Default: gpt-4o"
|
|
205
|
+
),
|
|
206
|
+
"ANTHROPIC_MODEL": (
|
|
207
|
+
"Default Anthropic model used for graph queries. "
|
|
208
|
+
"Requires ANTHROPIC_API_KEY environment variable. "
|
|
209
|
+
"Default: claude-3-5-sonnet-20241022"
|
|
210
|
+
),
|
|
154
211
|
}
|
|
155
212
|
|
|
156
213
|
# Valid values for each config key
|
|
@@ -210,6 +267,25 @@ coverage/
|
|
|
210
267
|
"""
|
|
211
268
|
|
|
212
269
|
|
|
270
|
+
def resolve_model_name(provider: str, configured_value: Optional[str] = None) -> str:
|
|
271
|
+
"""Get the model it should use for an LLM provider.
|
|
272
|
+
|
|
273
|
+
If the user configured a model, it uses that. Otherwise, it falls back to the default
|
|
274
|
+
model for the provider (like OPENAI_MODEL or ANTHROPIC_MODEL).
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
provider: The name of the provider, like "openai" or "anthropic" (case-insensitive).
|
|
278
|
+
configured_value: The model name from the user's config, if they set one.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
The model name it should use, or an empty string if it doesn't know the provider.
|
|
282
|
+
"""
|
|
283
|
+
if configured_value and configured_value.strip():
|
|
284
|
+
return configured_value.strip()
|
|
285
|
+
key = f"{provider.upper()}_MODEL"
|
|
286
|
+
return DEFAULT_CONFIG.get(key, "")
|
|
287
|
+
|
|
288
|
+
|
|
213
289
|
def normalize_config_path(value: str, *, absolute: bool = False, base_dir: Optional[Path] = None) -> str:
|
|
214
290
|
"""Normalize config path values.
|
|
215
291
|
|
|
@@ -480,6 +556,14 @@ def validate_config_value(key: str, value: str) -> tuple[bool, Optional[str]]:
|
|
|
480
556
|
except ValueError:
|
|
481
557
|
return False, "MAX_TOOL_RESPONSE_TOKENS must be an integer (0 = unlimited)"
|
|
482
558
|
|
|
559
|
+
if key == "MAX_PROMPT_CHARS":
|
|
560
|
+
try:
|
|
561
|
+
limit = int(value)
|
|
562
|
+
if limit < 0:
|
|
563
|
+
return False, "MAX_PROMPT_CHARS must be 0 (unlimited) or a positive integer"
|
|
564
|
+
except ValueError:
|
|
565
|
+
return False, "MAX_PROMPT_CHARS must be an integer (0 = unlimited)"
|
|
566
|
+
|
|
483
567
|
if key == "TOOL_RESULT_LIMITS":
|
|
484
568
|
import json as _json
|
|
485
569
|
try:
|
|
@@ -727,10 +811,17 @@ def _default_global_db_path(database: str) -> str:
|
|
|
727
811
|
|
|
728
812
|
New layout: ``~/.codegraphcontext/global/db/<backend>/``
|
|
729
813
|
For backward-compat, we check:
|
|
730
|
-
1.
|
|
731
|
-
2.
|
|
732
|
-
3.
|
|
814
|
+
1. CGC_RUNTIME_DB_PATH environment variable (highest priority — works for all DBs)
|
|
815
|
+
2. FALKORDB_PATH in config (if database is falkordb)
|
|
816
|
+
3. Legacy flat path
|
|
817
|
+
4. New layout default
|
|
733
818
|
"""
|
|
819
|
+
# Generic env var override — highest priority for any backend.
|
|
820
|
+
# Useful for relocating the global DB to a path that avoids platform-specific
|
|
821
|
+
# encoding issues (e.g. non-ASCII characters in the Windows user profile path).
|
|
822
|
+
env_override = os.environ.get("CGC_RUNTIME_DB_PATH")
|
|
823
|
+
if env_override:
|
|
824
|
+
return env_override
|
|
734
825
|
if database == "falkordb":
|
|
735
826
|
custom_path = load_config().get("FALKORDB_PATH")
|
|
736
827
|
if custom_path:
|
|
@@ -922,7 +1013,11 @@ def resolve_context(
|
|
|
922
1013
|
if local_cgc is not None:
|
|
923
1014
|
# Read local config.yaml if present
|
|
924
1015
|
local_yaml = local_cgc / "config.yaml"
|
|
925
|
-
local_db =
|
|
1016
|
+
local_db = (
|
|
1017
|
+
os.environ.get("CGC_RUNTIME_DB_TYPE")
|
|
1018
|
+
or os.environ.get("DEFAULT_DATABASE")
|
|
1019
|
+
or load_config().get("DEFAULT_DATABASE", "falkordb")
|
|
1020
|
+
)
|
|
926
1021
|
if local_yaml.exists():
|
|
927
1022
|
try:
|
|
928
1023
|
with open(local_yaml, encoding="utf-8") as f:
|
codegraphcontext/cli/main.py
CHANGED
|
@@ -1295,27 +1295,61 @@ def doctor():
|
|
|
1295
1295
|
|
|
1296
1296
|
|
|
1297
1297
|
|
|
1298
|
+
@app.command()
|
|
1298
1299
|
@app.command()
|
|
1299
1300
|
def index(
|
|
1300
1301
|
path: Optional[str] = typer.Argument(None, help="Path to the directory or file to index. Defaults to the current directory."),
|
|
1301
1302
|
force: bool = typer.Option(False, "--force", "-f", help="Force re-index (delete existing and rebuild)"),
|
|
1302
1303
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use (overrides mode/default)"),
|
|
1304
|
+
summarize: bool = typer.Option(False, "--summarize", "-s", help="Show a summary of the indexed codebase after indexing"),
|
|
1303
1305
|
):
|
|
1304
1306
|
"""
|
|
1305
1307
|
Indexes a directory or file by adding it to the code graph.
|
|
1306
1308
|
If no path is provided, it indexes the current directory.
|
|
1307
|
-
|
|
1309
|
+
|
|
1308
1310
|
Use --force to delete the existing index and rebuild from scratch.
|
|
1311
|
+
Use --summarize to display a summary after indexing.
|
|
1309
1312
|
"""
|
|
1310
1313
|
_load_credentials()
|
|
1311
1314
|
if path is None:
|
|
1312
1315
|
path = str(Path.cwd())
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1316
|
+
|
|
1317
|
+
try:
|
|
1318
|
+
if force:
|
|
1319
|
+
console.print("[yellow]Force re-indexing (--force flag detected)[/yellow]")
|
|
1320
|
+
reindex_helper(path, context)
|
|
1321
|
+
else:
|
|
1322
|
+
index_helper(path, context)
|
|
1323
|
+
except Exception as e:
|
|
1324
|
+
if str(e):
|
|
1325
|
+
console.print(f"[red]An error occurred during indexing: {e}[/red]")
|
|
1326
|
+
|
|
1327
|
+
if summarize:
|
|
1328
|
+
import os
|
|
1329
|
+
|
|
1330
|
+
py_files = []
|
|
1331
|
+
for root, dirs, files in os.walk(path):
|
|
1332
|
+
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['venv', '__pycache__', 'node_modules']]
|
|
1333
|
+
for file in files:
|
|
1334
|
+
if file.endswith('.py'):
|
|
1335
|
+
py_files.append(os.path.join(root, file))
|
|
1336
|
+
|
|
1337
|
+
total_lines = 0
|
|
1338
|
+
for f in py_files:
|
|
1339
|
+
try:
|
|
1340
|
+
with open(f, 'r', encoding='utf-8', errors='ignore') as fp:
|
|
1341
|
+
total_lines += len(fp.readlines())
|
|
1342
|
+
except:
|
|
1343
|
+
pass
|
|
1344
|
+
|
|
1345
|
+
console.print("\n[bold cyan]📊 Codebase Summary:[/bold cyan]")
|
|
1346
|
+
console.print(f" • Path indexed : [green]{path}[/green]")
|
|
1347
|
+
console.print(f" • Python files : [yellow]{len(py_files)}[/yellow]")
|
|
1348
|
+
console.print(f" • Total lines : [yellow]{total_lines}[/yellow]")
|
|
1349
|
+
console.print(f"\n • Run [bold]cgc analyze complexity[/bold] to find complex functions")
|
|
1350
|
+
console.print(f" • Run [bold]cgc analyze dead-code[/bold] to find unused code")
|
|
1351
|
+
console.print(f" • Run [bold]cgc list[/bold] to see all indexed repositories")
|
|
1352
|
+
console.print("\n[dim]Tip: Use --summarize anytime after indexing to see this.[/dim]")
|
|
1319
1353
|
|
|
1320
1354
|
@app.command()
|
|
1321
1355
|
def update(
|
|
@@ -1519,6 +1553,7 @@ def report(
|
|
|
1519
1553
|
@app.command()
|
|
1520
1554
|
def visualize(
|
|
1521
1555
|
repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Path to the repository to visualize."),
|
|
1556
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host interface to bind to."),
|
|
1522
1557
|
port: int = typer.Option(8000, "--port", "-p", help="Port to run the visualizer server on."),
|
|
1523
1558
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use")
|
|
1524
1559
|
):
|
|
@@ -1526,7 +1561,7 @@ def visualize(
|
|
|
1526
1561
|
Launches the interactive UI to visualize the code graph.
|
|
1527
1562
|
"""
|
|
1528
1563
|
_load_credentials()
|
|
1529
|
-
visualize_helper(repo, port, context)
|
|
1564
|
+
visualize_helper(repo, host, port, context)
|
|
1530
1565
|
|
|
1531
1566
|
@app.command("list")
|
|
1532
1567
|
def list_repositories(
|
|
@@ -1565,6 +1600,14 @@ def watch(
|
|
|
1565
1600
|
"--poll",
|
|
1566
1601
|
help="Use watchdog's polling observer for Docker bind mounts and network filesystems.",
|
|
1567
1602
|
),
|
|
1603
|
+
sync_on_start: bool = typer.Option(
|
|
1604
|
+
False,
|
|
1605
|
+
"--sync-on-start",
|
|
1606
|
+
help=(
|
|
1607
|
+
"Synchronize already-indexed files before watching. "
|
|
1608
|
+
"Defaults off; use 'cgc index --force' for a full re-index."
|
|
1609
|
+
),
|
|
1610
|
+
),
|
|
1568
1611
|
):
|
|
1569
1612
|
"""
|
|
1570
1613
|
Watch a directory for file changes and automatically update the code graph.
|
|
@@ -1575,6 +1618,7 @@ def watch(
|
|
|
1575
1618
|
|
|
1576
1619
|
The watcher will:
|
|
1577
1620
|
- Perform an initial scan if the directory is not yet indexed
|
|
1621
|
+
- Attach immediately for already-indexed directories unless --sync-on-start is passed
|
|
1578
1622
|
- Monitor for file creation, modification, deletion, and moves
|
|
1579
1623
|
- Automatically re-index affected files and update relationships
|
|
1580
1624
|
|
|
@@ -1584,12 +1628,13 @@ def watch(
|
|
|
1584
1628
|
cgc watch . # Watch current directory
|
|
1585
1629
|
cgc watch /path/to/project # Watch specific directory
|
|
1586
1630
|
cgc watch --poll . # Use polling for Docker/NFS/SMB mounts
|
|
1631
|
+
cgc watch --sync-on-start . # Reconcile current files before watching
|
|
1587
1632
|
cgc w . # Using shortcut alias
|
|
1588
1633
|
|
|
1589
1634
|
Set CGC_WATCH_POLLING=1 to use polling without passing --poll.
|
|
1590
1635
|
"""
|
|
1591
1636
|
_load_credentials()
|
|
1592
|
-
watch_helper(path, context, use_polling=poll or None)
|
|
1637
|
+
watch_helper(path, context, use_polling=poll or None, sync_on_start=sync_on_start)
|
|
1593
1638
|
|
|
1594
1639
|
@app.command()
|
|
1595
1640
|
def unwatch(
|
|
@@ -2508,7 +2553,7 @@ def analyze_inheritance_tree(
|
|
|
2508
2553
|
@analyze_app.command("complexity")
|
|
2509
2554
|
def analyze_complexity(
|
|
2510
2555
|
path: Optional[str] = typer.Argument(None, help="Function name or file path to analyze"),
|
|
2511
|
-
threshold: int = typer.Option(
|
|
2556
|
+
threshold: Optional[int] = typer.Option(None, "--threshold", "-t", help="Complexity threshold for warnings (default: from config or 10)"),
|
|
2512
2557
|
limit: int = typer.Option(20, "--limit", "-l", help="Maximum results to show"),
|
|
2513
2558
|
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path to scope analysis"),
|
|
2514
2559
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
|
|
@@ -2531,6 +2576,17 @@ def analyze_complexity(
|
|
|
2531
2576
|
raise typer.Exit(code=1)
|
|
2532
2577
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2533
2578
|
|
|
2579
|
+
# Read threshold from config if not explicitly provided via CLI
|
|
2580
|
+
if threshold is None:
|
|
2581
|
+
configured = config_manager.get_config_value("COMPLEXITY_THRESHOLD")
|
|
2582
|
+
if configured is not None:
|
|
2583
|
+
try:
|
|
2584
|
+
threshold = int(configured)
|
|
2585
|
+
except (ValueError, TypeError):
|
|
2586
|
+
threshold = 10
|
|
2587
|
+
else:
|
|
2588
|
+
threshold = 10
|
|
2589
|
+
|
|
2534
2590
|
_FILE_EXTENSIONS = ('.py', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs', '.rb',
|
|
2535
2591
|
'.java', '.cpp', '.c', '.cs', '.swift', '.kt', '.scala',
|
|
2536
2592
|
'.php', '.lua', '.zig', '.ex', '.exs', '.r', '.m', '.sh')
|
|
@@ -2846,9 +2902,22 @@ def visualize_abbrev(
|
|
|
2846
2902
|
def watch_abbrev(
|
|
2847
2903
|
path: str = typer.Argument(".", help="Path to watch"),
|
|
2848
2904
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
|
|
2905
|
+
poll: bool = typer.Option(
|
|
2906
|
+
False,
|
|
2907
|
+
"--poll",
|
|
2908
|
+
help="Use watchdog's polling observer for Docker bind mounts and network filesystems.",
|
|
2909
|
+
),
|
|
2910
|
+
sync_on_start: bool = typer.Option(
|
|
2911
|
+
False,
|
|
2912
|
+
"--sync-on-start",
|
|
2913
|
+
help=(
|
|
2914
|
+
"Synchronize already-indexed files before watching. "
|
|
2915
|
+
"Defaults off; use 'cgc index --force' for a full re-index."
|
|
2916
|
+
),
|
|
2917
|
+
),
|
|
2849
2918
|
):
|
|
2850
2919
|
"""Shortcut for 'cgc watch'"""
|
|
2851
|
-
watch(path, context=context)
|
|
2920
|
+
watch(path, context=context, poll=poll, sync_on_start=sync_on_start)
|
|
2852
2921
|
|
|
2853
2922
|
|
|
2854
2923
|
# ============================================================================
|