codegraphcontext 0.4.11__py3-none-any.whl → 0.4.13__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/router.py +1 -1
- codegraphcontext/cli/config_manager.py +10 -1
- codegraphcontext/cli/main.py +166 -109
- codegraphcontext/cli/registry_commands.py +45 -24
- codegraphcontext/cli/setup_wizard.py +2 -2
- codegraphcontext/core/__init__.py +16 -3
- codegraphcontext/core/bundle_registry.py +6 -44
- codegraphcontext/core/cgc_bundle.py +142 -46
- codegraphcontext/core/database_falkordb.py +13 -6
- codegraphcontext/core/database_nornic.py +11 -3
- codegraphcontext/tools/advanced_language_query_tool.py +11 -12
- codegraphcontext/tools/code_finder.py +17 -0
- codegraphcontext/tools/graph_builder.py +4 -0
- codegraphcontext/tools/indexing/persistence/writer.py +7 -7
- codegraphcontext/tools/indexing/pre_scan.py +2 -0
- codegraphcontext/tools/indexing/resolution/calls.py +2 -1
- codegraphcontext/tools/languages/elisp.py +682 -0
- codegraphcontext/tools/query_tool_languages/elisp_toolkit.py +73 -0
- codegraphcontext/tools/scip_indexer.py +1 -11
- codegraphcontext/tools/tree_sitter_parser.py +4 -0
- codegraphcontext/utils/git_utils.py +13 -0
- codegraphcontext/utils/tree_sitter_manager.py +5 -0
- {codegraphcontext-0.4.11.dist-info → codegraphcontext-0.4.13.dist-info}/METADATA +14 -13
- {codegraphcontext-0.4.11.dist-info → codegraphcontext-0.4.13.dist-info}/RECORD +28 -26
- {codegraphcontext-0.4.11.dist-info → codegraphcontext-0.4.13.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.4.11.dist-info → codegraphcontext-0.4.13.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.4.11.dist-info → codegraphcontext-0.4.13.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.4.11.dist-info → codegraphcontext-0.4.13.dist-info}/top_level.txt +0 -0
codegraphcontext/api/router.py
CHANGED
|
@@ -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",
|
|
@@ -62,6 +63,8 @@ DEFAULT_CONFIG = {
|
|
|
62
63
|
"ENABLE_VECTOR_RESOLVE": "false",
|
|
63
64
|
"CGC_EMBEDDING_MODEL": "local",
|
|
64
65
|
"CGC_EMBEDDING_BATCH_SIZE": "256",
|
|
66
|
+
# Default fuzzy matching behavior for `cgc find name` (overridable per-command with --fuzzy/--no-fuzzy)
|
|
67
|
+
"FUZZY_SEARCH": "true",
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
# Configuration key descriptions
|
|
@@ -70,6 +73,7 @@ CONFIG_DESCRIPTIONS = {
|
|
|
70
73
|
"FALKORDB_PATH": "Path to FalkorDB database file",
|
|
71
74
|
"FALKORDB_SOCKET_PATH": "Path to FalkorDB Unix socket",
|
|
72
75
|
"LADYBUGDB_PATH": "Path to LadybugDB database directory",
|
|
76
|
+
"KUZUDB_PATH": "Path to KuzuDB database directory",
|
|
73
77
|
"INDEX_VARIABLES": "Index variable nodes in the graph (lighter graph if false)",
|
|
74
78
|
"ALLOW_DB_DELETION": "Allow full database deletion commands",
|
|
75
79
|
"DEBUG_LOGS": "Enable debug logging (for development/troubleshooting)",
|
|
@@ -123,6 +127,10 @@ CONFIG_DESCRIPTIONS = {
|
|
|
123
127
|
"Number of function texts to embed per batch when ENABLE_VECTOR_RESOLVE=true. "
|
|
124
128
|
"Larger values are faster but use more RAM. Default: 256. Reduce to 64 if you hit memory errors."
|
|
125
129
|
),
|
|
130
|
+
"FUZZY_SEARCH": (
|
|
131
|
+
"Enable fuzzy matching by default for `cgc find name` (true|false). "
|
|
132
|
+
"Per-invocation overrides are available via --fuzzy / --no-fuzzy."
|
|
133
|
+
),
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
# Valid values for each config key
|
|
@@ -143,6 +151,7 @@ CONFIG_VALIDATORS = {
|
|
|
143
151
|
"ENABLE_INHERIT_RESOLVE": ["true", "false"],
|
|
144
152
|
"ENABLE_VECTOR_RESOLVE": ["true", "false"],
|
|
145
153
|
"CGC_EMBEDDING_MODEL": ["local", "openai"],
|
|
154
|
+
"FUZZY_SEARCH": ["true", "false"],
|
|
146
155
|
}
|
|
147
156
|
DEFAULT_CGCIGNORE_PATTERNS = """\
|
|
148
157
|
# Default .cgcignore patterns
|
|
@@ -447,7 +456,7 @@ def validate_config_value(key: str, value: str) -> tuple[bool, Optional[str]]:
|
|
|
447
456
|
except Exception as e:
|
|
448
457
|
return False, f"Cannot create log directory: {e}"
|
|
449
458
|
|
|
450
|
-
if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH", "LADYBUGDB_PATH"):
|
|
459
|
+
if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH", "LADYBUGDB_PATH", "KUZUDB_PATH"):
|
|
451
460
|
# Validate path is writable
|
|
452
461
|
db_path = Path(normalize_config_path(value, absolute=True))
|
|
453
462
|
try:
|
codegraphcontext/cli/main.py
CHANGED
|
@@ -250,7 +250,7 @@ def context_list():
|
|
|
250
250
|
@context_app.command("create")
|
|
251
251
|
def context_create(
|
|
252
252
|
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."),
|
|
253
|
+
database: str = typer.Option(None, "--database", "--db", "-db", "-d", help="Database backend (falkordb, kuzudb, neo4j). Defaults to DEFAULT_DATABASE from config."),
|
|
254
254
|
db_path: str = typer.Option(None, "--db-path", help="Explicit path for the DB (defaults to ~/.codegraphcontext/contexts/<name>/db)"),
|
|
255
255
|
):
|
|
256
256
|
"""Create a new logical context."""
|
|
@@ -285,23 +285,20 @@ def context_default(
|
|
|
285
285
|
# CREDENTIALS LOADING PRECEDENCE
|
|
286
286
|
# ============================================================================
|
|
287
287
|
|
|
288
|
-
def _load_credentials():
|
|
288
|
+
def _load_credentials(cli_context_flag: Optional[str] = None):
|
|
289
289
|
"""
|
|
290
290
|
Loads configuration and credentials from various sources into environment variables.
|
|
291
291
|
Uses per-variable precedence - each variable is loaded from the highest priority source.
|
|
292
292
|
Priority order (highest to lowest):
|
|
293
293
|
1. Runtime environment variables (shell/CI)
|
|
294
|
-
2. Local `.env` in project directory (project-specific overrides)
|
|
294
|
+
2. Local `.codegraphcontext/.env` and `.env` in the current project directory (project-specific overrides)
|
|
295
295
|
3. Global `~/.codegraphcontext/.env` (user defaults, including `cgc config set`)
|
|
296
296
|
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
297
|
|
|
301
|
-
|
|
302
|
-
Arbitrary
|
|
298
|
+
Duplicate loading is skipped when the local file resolves to the same path as the global file.
|
|
299
|
+
Arbitrary parent directory `.env` files are not loaded—ensuring isolation.
|
|
303
300
|
"""
|
|
304
|
-
from dotenv import dotenv_values
|
|
301
|
+
from dotenv import dotenv_values
|
|
305
302
|
from codegraphcontext.cli.config_manager import (
|
|
306
303
|
ensure_config_dir,
|
|
307
304
|
codegraphcontext_dotenv_at_cwd,
|
|
@@ -336,7 +333,7 @@ def _load_credentials():
|
|
|
336
333
|
mcp_file_path = Path.cwd() / "mcp.json"
|
|
337
334
|
if mcp_file_path.exists():
|
|
338
335
|
try:
|
|
339
|
-
with open(mcp_file_path, "r") as f:
|
|
336
|
+
with open(mcp_file_path, "r", encoding="utf-8", errors="replace") as f:
|
|
340
337
|
mcp_config = json.load(f)
|
|
341
338
|
server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
|
|
342
339
|
if isinstance(server_env, dict):
|
|
@@ -355,15 +352,17 @@ def _load_credentials():
|
|
|
355
352
|
global_env_path = Path.home() / ".codegraphcontext" / ".env"
|
|
356
353
|
if global_env_path.exists():
|
|
357
354
|
try:
|
|
358
|
-
|
|
355
|
+
with open(global_env_path, "r", encoding="utf-8", errors="replace") as f:
|
|
356
|
+
_append_source(str(global_env_path), dotenv_values(stream=f))
|
|
359
357
|
except Exception as e:
|
|
360
358
|
console.print(f"[yellow]Warning: Could not load global .env: {e}[/yellow]")
|
|
361
359
|
|
|
362
|
-
# 2. Local project .env (project-specific overrides)
|
|
360
|
+
# 2. Local project .env (project-specific overrides - restricted to CWD only, no parent walk)
|
|
363
361
|
try:
|
|
364
|
-
|
|
365
|
-
if
|
|
366
|
-
|
|
362
|
+
local_dot_env = Path.cwd() / ".env"
|
|
363
|
+
if local_dot_env.exists() and local_dot_env.resolve() != global_env_path.resolve():
|
|
364
|
+
with open(local_dot_env, "r", encoding="utf-8", errors="replace") as f:
|
|
365
|
+
_append_source(str(local_dot_env), dotenv_values(stream=f))
|
|
367
366
|
except Exception as e:
|
|
368
367
|
console.print(f"[yellow]Warning: Could not load .env from current directory: {e}[/yellow]")
|
|
369
368
|
|
|
@@ -371,8 +370,9 @@ def _load_credentials():
|
|
|
371
370
|
try:
|
|
372
371
|
local_cgc_env = codegraphcontext_dotenv_at_cwd(Path.cwd())
|
|
373
372
|
if local_cgc_env and local_cgc_env.resolve() != global_env_path.resolve():
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
with open(local_cgc_env, "r", encoding="utf-8", errors="replace") as f:
|
|
374
|
+
vals = dotenv_values(stream=f)
|
|
375
|
+
_append_source(str(local_cgc_env), vals)
|
|
376
376
|
except Exception as e:
|
|
377
377
|
console.print(
|
|
378
378
|
f"[yellow]Warning: Could not load .codegraphcontext/.env at cwd: {e}[/yellow]"
|
|
@@ -418,31 +418,62 @@ def _load_credentials():
|
|
|
418
418
|
)
|
|
419
419
|
|
|
420
420
|
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
421
|
+
# Detect the context to see if it specifies a custom database
|
|
422
|
+
if cli_context_flag is None:
|
|
423
|
+
import sys
|
|
424
|
+
for i, arg in enumerate(sys.argv):
|
|
425
|
+
if arg in ("--context", "-c"):
|
|
426
|
+
if i + 1 < len(sys.argv):
|
|
427
|
+
cli_context_flag = sys.argv[i + 1]
|
|
428
|
+
break
|
|
429
|
+
elif arg.startswith("--context="):
|
|
430
|
+
cli_context_flag = arg.split("=", 1)[1]
|
|
431
|
+
break
|
|
432
|
+
|
|
433
|
+
from codegraphcontext.cli.config_manager import resolve_context
|
|
434
|
+
ctx = None
|
|
435
|
+
try:
|
|
436
|
+
ctx = resolve_context(cli_context_flag)
|
|
437
|
+
except Exception:
|
|
438
|
+
pass
|
|
427
439
|
|
|
428
|
-
|
|
440
|
+
# Determine if there is a runtime database override.
|
|
441
|
+
runtime_db = os.environ.get("CGC_RUNTIME_DB_TYPE")
|
|
442
|
+
has_runtime_override = (
|
|
443
|
+
runtime_db is not None
|
|
444
|
+
or "DATABASE_TYPE" in runtime_env
|
|
445
|
+
or "DEFAULT_DATABASE" in runtime_env
|
|
446
|
+
)
|
|
429
447
|
|
|
448
|
+
# If there is no runtime override, but the context defines a database,
|
|
449
|
+
# set DEFAULT_DATABASE to the context database to ensure that's what gets initialized.
|
|
450
|
+
if not has_runtime_override and ctx and ctx.mode != "global" and ctx.database:
|
|
451
|
+
os.environ["DEFAULT_DATABASE"] = ctx.database
|
|
452
|
+
|
|
453
|
+
# Now select the database based on precedence:
|
|
454
|
+
# 1. CGC_RUNTIME_DB_TYPE
|
|
455
|
+
# 2. DATABASE_TYPE or DEFAULT_DATABASE from runtime environment (shell variables)
|
|
456
|
+
# 3. Context database
|
|
457
|
+
# 4. DATABASE_TYPE or DEFAULT_DATABASE from merged config files (.env files)
|
|
458
|
+
# 5. Auto-detect fallback
|
|
430
459
|
if runtime_db:
|
|
460
|
+
default_db = runtime_db.lower()
|
|
431
461
|
db_source = "runtime-env (CGC_RUNTIME_DB_TYPE)"
|
|
432
462
|
elif "DATABASE_TYPE" in runtime_env:
|
|
463
|
+
default_db = runtime_env["DATABASE_TYPE"].lower()
|
|
433
464
|
db_source = "environment (DATABASE_TYPE)"
|
|
434
465
|
elif "DEFAULT_DATABASE" in runtime_env:
|
|
466
|
+
default_db = runtime_env["DEFAULT_DATABASE"].lower()
|
|
435
467
|
db_source = "environment (DEFAULT_DATABASE)"
|
|
436
|
-
elif
|
|
468
|
+
elif not has_runtime_override and ctx and ctx.mode != "global" and ctx.database:
|
|
469
|
+
default_db = ctx.database.lower()
|
|
470
|
+
db_source = f"context ({ctx.context_name or 'resolved'})"
|
|
471
|
+
elif os.environ.get("DATABASE_TYPE") and "DATABASE_TYPE" in key_source_map:
|
|
472
|
+
default_db = os.environ["DATABASE_TYPE"].lower()
|
|
437
473
|
db_source = key_source_map["DATABASE_TYPE"]
|
|
438
|
-
elif
|
|
474
|
+
elif os.environ.get("DEFAULT_DATABASE") and "DEFAULT_DATABASE" in key_source_map:
|
|
475
|
+
default_db = os.environ["DEFAULT_DATABASE"].lower()
|
|
439
476
|
db_source = key_source_map["DEFAULT_DATABASE"]
|
|
440
|
-
else:
|
|
441
|
-
db_source = "auto-detect"
|
|
442
|
-
explicit_db = runtime_db or os.environ.get("DEFAULT_DATABASE")
|
|
443
|
-
|
|
444
|
-
if explicit_db:
|
|
445
|
-
default_db = explicit_db.lower()
|
|
446
477
|
else:
|
|
447
478
|
# No explicit choice — ask the factory which backend it will use
|
|
448
479
|
try:
|
|
@@ -455,6 +486,7 @@ def _load_credentials():
|
|
|
455
486
|
default_db = "falkordb" if _is_falkordb_available() else "kuzudb"
|
|
456
487
|
db_source = "auto-detect"
|
|
457
488
|
|
|
489
|
+
# Print selection banner
|
|
458
490
|
if default_db == "neo4j":
|
|
459
491
|
has_neo4j_creds = all([
|
|
460
492
|
os.environ.get("NEO4J_URI"),
|
|
@@ -473,20 +505,14 @@ def _load_credentials():
|
|
|
473
505
|
console.print(f"[cyan]Using database: falkordb (source: {db_source})[/cyan]")
|
|
474
506
|
elif default_db == "kuzudb":
|
|
475
507
|
console.print(f"[cyan]Using database: kuzudb (source: {db_source})[/cyan]")
|
|
508
|
+
elif default_db == "ladybugdb":
|
|
509
|
+
console.print(f"[cyan]Using database: ladybugdb (source: {db_source})[/cyan]")
|
|
476
510
|
elif default_db == "falkordb-remote":
|
|
477
511
|
host = os.environ.get("FALKORDB_HOST")
|
|
478
512
|
if host:
|
|
479
513
|
console.print(f"[cyan]Using database: falkordb-remote (source: {db_source}, host: {host})[/cyan]")
|
|
480
514
|
else:
|
|
481
515
|
console.print("[yellow]⚠ DATABASE_TYPE=falkordb-remote but FALKORDB_HOST not set.[/yellow]")
|
|
482
|
-
elif default_db == "falkordb":
|
|
483
|
-
if os.environ.get("FALKORDB_HOST"):
|
|
484
|
-
console.print(f"[cyan]Using database: falkordb-remote (source: {db_source}, host: {os.environ.get('FALKORDB_HOST')})[/cyan]")
|
|
485
|
-
else:
|
|
486
|
-
console.print(f"[cyan]Using database: falkordb (source: {db_source})[/cyan]")
|
|
487
|
-
console.print(
|
|
488
|
-
"[yellow]⚠ DEFAULT_DATABASE=falkordb-remote but FALKORDB_HOST not set.[/yellow]"
|
|
489
|
-
)
|
|
490
516
|
else:
|
|
491
517
|
console.print(f"[cyan]Using database: {default_db} (source: {db_source})[/cyan]")
|
|
492
518
|
|
|
@@ -543,7 +569,7 @@ def config_reset():
|
|
|
543
569
|
console.print("[yellow]Reset cancelled[/yellow]")
|
|
544
570
|
|
|
545
571
|
@config_app.command("db")
|
|
546
|
-
def config_db(backend: str = typer.Argument(..., help="Database backend: 'neo4j', 'falkordb', 'falkordb-remote', or '
|
|
572
|
+
def config_db(backend: str = typer.Argument(..., help="Database backend: 'neo4j', 'falkordb', 'falkordb-remote', 'kuzudb', or 'ladybugdb'")):
|
|
547
573
|
"""
|
|
548
574
|
Quickly switch the default database backend.
|
|
549
575
|
|
|
@@ -608,7 +634,7 @@ def bundle_export(
|
|
|
608
634
|
if repo_path:
|
|
609
635
|
console.print(f"[dim]Repository: {repo_path}[/dim]")
|
|
610
636
|
else:
|
|
611
|
-
console.print(
|
|
637
|
+
console.print("[dim]Exporting all repositories[/dim]")
|
|
612
638
|
|
|
613
639
|
bundle = CGCBundle(db_manager)
|
|
614
640
|
success, message = bundle.export_to_bundle(
|
|
@@ -719,7 +745,7 @@ def bundle_load(
|
|
|
719
745
|
|
|
720
746
|
# Try to download from registry
|
|
721
747
|
console.print(f"[yellow]Bundle '{bundle_name}' not found locally.[/yellow]")
|
|
722
|
-
console.print(
|
|
748
|
+
console.print("[cyan]Attempting to download from registry...[/cyan]")
|
|
723
749
|
|
|
724
750
|
try:
|
|
725
751
|
from .registry_commands import download_bundle
|
|
@@ -739,7 +765,7 @@ def bundle_load(
|
|
|
739
765
|
|
|
740
766
|
except Exception as e:
|
|
741
767
|
console.print(f"[bold red]Error: {e}[/bold red]")
|
|
742
|
-
console.print(
|
|
768
|
+
console.print("[dim]Use 'cgc registry list' to see available bundles[/dim]")
|
|
743
769
|
raise typer.Exit(code=1)
|
|
744
770
|
|
|
745
771
|
# Shortcut commands at root level
|
|
@@ -859,7 +885,7 @@ def registry_download(
|
|
|
859
885
|
bundle_path = download_bundle(name, output_dir, auto_load=load)
|
|
860
886
|
|
|
861
887
|
if load and bundle_path:
|
|
862
|
-
console.print(
|
|
888
|
+
console.print("\n[cyan]Loading bundle...[/cyan]")
|
|
863
889
|
bundle_import(bundle_path, clear=False)
|
|
864
890
|
|
|
865
891
|
@registry_app.command("request")
|
|
@@ -912,13 +938,13 @@ def doctor():
|
|
|
912
938
|
if config_manager.CONFIG_FILE.exists():
|
|
913
939
|
console.print(f" [green]✓[/green] Config loaded from {config_manager.CONFIG_FILE}")
|
|
914
940
|
else:
|
|
915
|
-
console.print(
|
|
941
|
+
console.print(" [yellow]ℹ[/yellow] No .env config found, using defaults")
|
|
916
942
|
console.print(f" [dim]Config will be created at: {config_manager.CONFIG_FILE}[/dim]")
|
|
917
943
|
|
|
918
944
|
if config_manager.CONTEXT_CONFIG_FILE.exists():
|
|
919
945
|
console.print(f" [green]✓[/green] Context config loaded from {config_manager.CONTEXT_CONFIG_FILE}")
|
|
920
946
|
else:
|
|
921
|
-
console.print(
|
|
947
|
+
console.print(" [yellow]ℹ[/yellow] No Context config found")
|
|
922
948
|
console.print(f" [dim]Context config will be auto-generated at: {config_manager.CONTEXT_CONFIG_FILE}[/dim]")
|
|
923
949
|
|
|
924
950
|
# Validate each config value
|
|
@@ -929,12 +955,12 @@ def doctor():
|
|
|
929
955
|
invalid_configs.append(f"{key}: {error_msg}")
|
|
930
956
|
|
|
931
957
|
if invalid_configs:
|
|
932
|
-
console.print(
|
|
958
|
+
console.print(" [red]✗[/red] Invalid configuration values found:")
|
|
933
959
|
for err in invalid_configs:
|
|
934
960
|
console.print(f" - {err}")
|
|
935
961
|
all_checks_passed = False
|
|
936
962
|
else:
|
|
937
|
-
console.print(
|
|
963
|
+
console.print(" [green]✓[/green] All configuration values are valid")
|
|
938
964
|
except Exception as e:
|
|
939
965
|
console.print(f" [red]✗[/red] Configuration error: {e}")
|
|
940
966
|
all_checks_passed = False
|
|
@@ -974,10 +1000,10 @@ def doctor():
|
|
|
974
1000
|
console.print(" Start Neo4j Desktop or run: docker run -d -p 7687:7687 -p 7474:7474 neo4j")
|
|
975
1001
|
all_checks_passed = False
|
|
976
1002
|
|
|
977
|
-
console.print(
|
|
1003
|
+
console.print(" [cyan]Testing Neo4j authentication/query...[/cyan]")
|
|
978
1004
|
is_connected, error_msg = DatabaseManager.test_connection(uri, username, password, database=database_name)
|
|
979
1005
|
if is_connected:
|
|
980
|
-
console.print(
|
|
1006
|
+
console.print(" [green]✓[/green] Neo4j connection successful")
|
|
981
1007
|
else:
|
|
982
1008
|
console.print(f" [red]✗[/red] Neo4j connection failed (source: {db_source})")
|
|
983
1009
|
console.print(f" Reason: {error_msg}")
|
|
@@ -986,28 +1012,28 @@ def doctor():
|
|
|
986
1012
|
from importlib.util import find_spec
|
|
987
1013
|
|
|
988
1014
|
if find_spec("kuzu") is not None:
|
|
989
|
-
console.print(
|
|
1015
|
+
console.print(" [green]✓[/green] KuzuDB is installed")
|
|
990
1016
|
else:
|
|
991
|
-
console.print(
|
|
992
|
-
console.print(
|
|
1017
|
+
console.print(" [red]✗[/red] KuzuDB is not installed")
|
|
1018
|
+
console.print(" Run: pip install kuzu")
|
|
993
1019
|
all_checks_passed = False
|
|
994
1020
|
elif default_db == "ladybugdb":
|
|
995
1021
|
from importlib.util import find_spec
|
|
996
1022
|
|
|
997
1023
|
if find_spec("ladybug") is not None:
|
|
998
|
-
console.print(
|
|
1024
|
+
console.print(" [green]✓[/green] LadybugDB core (ladybug) is installed")
|
|
999
1025
|
else:
|
|
1000
|
-
console.print(
|
|
1001
|
-
console.print(
|
|
1026
|
+
console.print(" [red]✗[/red] LadybugDB core (ladybug) is not installed")
|
|
1027
|
+
console.print(" Run: pip install ladybug")
|
|
1002
1028
|
all_checks_passed = False
|
|
1003
1029
|
else:
|
|
1004
1030
|
# FalkorDB
|
|
1005
1031
|
try:
|
|
1006
1032
|
import falkordb
|
|
1007
|
-
console.print(
|
|
1033
|
+
console.print(" [green]✓[/green] FalkorDB Lite is installed")
|
|
1008
1034
|
except ImportError:
|
|
1009
|
-
console.print(
|
|
1010
|
-
console.print(
|
|
1035
|
+
console.print(" [yellow]⚠[/yellow] FalkorDB Lite not installed (Python 3.12+ only)")
|
|
1036
|
+
console.print(" Run: pip install falkordblite")
|
|
1011
1037
|
except Exception as e:
|
|
1012
1038
|
console.print(f" [red]✗[/red] Database check error: {e}")
|
|
1013
1039
|
all_checks_passed = False
|
|
@@ -1016,11 +1042,11 @@ def doctor():
|
|
|
1016
1042
|
console.print("\n[bold]3. Checking Tree-Sitter Installation...[/bold]")
|
|
1017
1043
|
try:
|
|
1018
1044
|
from tree_sitter import Language, Parser
|
|
1019
|
-
console.print(
|
|
1045
|
+
console.print(" [green]✓[/green] tree-sitter is installed")
|
|
1020
1046
|
|
|
1021
1047
|
try:
|
|
1022
1048
|
from tree_sitter_language_pack import get_language
|
|
1023
|
-
console.print(
|
|
1049
|
+
console.print(" [green]✓[/green] tree-sitter-language-pack is installed")
|
|
1024
1050
|
|
|
1025
1051
|
from codegraphcontext.utils.tree_sitter_manager import LANGUAGE_ALIASES, LANGUAGE_PACK_NAMES
|
|
1026
1052
|
all_langs = sorted(set(LANGUAGE_ALIASES.values()))
|
|
@@ -1038,7 +1064,7 @@ def doctor():
|
|
|
1038
1064
|
if unavailable:
|
|
1039
1065
|
console.print(f" [yellow]⚠[/yellow] Unavailable: {', '.join(unavailable)}")
|
|
1040
1066
|
except ImportError:
|
|
1041
|
-
console.print(
|
|
1067
|
+
console.print(" [red]✗[/red] tree-sitter-language-pack not installed")
|
|
1042
1068
|
all_checks_passed = False
|
|
1043
1069
|
except ImportError as e:
|
|
1044
1070
|
console.print(f" [red]✗[/red] tree-sitter not installed: {e}")
|
|
@@ -1056,12 +1082,12 @@ def doctor():
|
|
|
1056
1082
|
try:
|
|
1057
1083
|
test_file.touch()
|
|
1058
1084
|
test_file.unlink()
|
|
1059
|
-
console.print(
|
|
1085
|
+
console.print(" [green]✓[/green] Config directory is writable")
|
|
1060
1086
|
except Exception as e:
|
|
1061
1087
|
console.print(f" [red]✗[/red] Config directory not writable: {e}")
|
|
1062
1088
|
all_checks_passed = False
|
|
1063
1089
|
else:
|
|
1064
|
-
console.print(
|
|
1090
|
+
console.print(" [yellow]⚠[/yellow] Config directory doesn't exist, will be created on first use")
|
|
1065
1091
|
except Exception as e:
|
|
1066
1092
|
console.print(f" [red]✗[/red] Permission check error: {e}")
|
|
1067
1093
|
all_checks_passed = False
|
|
@@ -1073,7 +1099,7 @@ def doctor():
|
|
|
1073
1099
|
if cgc_path:
|
|
1074
1100
|
console.print(f" [green]✓[/green] cgc command found at: {cgc_path}")
|
|
1075
1101
|
else:
|
|
1076
|
-
console.print(
|
|
1102
|
+
console.print(" [yellow]⚠[/yellow] cgc command not in PATH (using python -m cgc)")
|
|
1077
1103
|
|
|
1078
1104
|
# Final summary
|
|
1079
1105
|
console.print("\n" + "=" * 60)
|
|
@@ -1388,17 +1414,22 @@ app.add_typer(find_app, name="find")
|
|
|
1388
1414
|
@find_app.command("name")
|
|
1389
1415
|
def find_by_name(
|
|
1390
1416
|
ctx: typer.Context,
|
|
1391
|
-
name: str = typer.Argument(..., help="
|
|
1417
|
+
name: str = typer.Argument(..., help="Name to search for"),
|
|
1392
1418
|
type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by type (function, class, file, module)"),
|
|
1419
|
+
fuzzy: Optional[bool] = typer.Option(None, "--fuzzy/--no-fuzzy", help="Enable/disable fuzzy matching for this command. Overrides the FUZZY_SEARCH config value (default: true)."),
|
|
1393
1420
|
visual: bool = typer.Option(False, "--visual", "--viz", "-V", help="Show results as interactive graph visualization"),
|
|
1394
1421
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
|
|
1395
1422
|
):
|
|
1396
1423
|
"""
|
|
1397
|
-
Find code elements by
|
|
1398
|
-
|
|
1424
|
+
Find code elements by name.
|
|
1425
|
+
|
|
1426
|
+
Fuzzy matching is enabled by default (configurable via the FUZZY_SEARCH
|
|
1427
|
+
config key, or per-invocation with --fuzzy / --no-fuzzy).
|
|
1428
|
+
|
|
1399
1429
|
Examples:
|
|
1400
1430
|
cgc find name MyClass
|
|
1401
1431
|
cgc find name calculate --type function
|
|
1432
|
+
cgc find name MyClass --no-fuzzy
|
|
1402
1433
|
cgc find name MyClass --visual
|
|
1403
1434
|
"""
|
|
1404
1435
|
_load_credentials()
|
|
@@ -1406,14 +1437,22 @@ def find_by_name(
|
|
|
1406
1437
|
if not all(services[:3]):
|
|
1407
1438
|
return
|
|
1408
1439
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1409
|
-
|
|
1440
|
+
|
|
1441
|
+
# Resolve effective fuzzy setting: CLI flag wins, else config, else true.
|
|
1442
|
+
if fuzzy is None:
|
|
1443
|
+
from codegraphcontext.cli.config_manager import load_config
|
|
1444
|
+
cfg_value = load_config().get("FUZZY_SEARCH", "true")
|
|
1445
|
+
fuzzy_search = str(cfg_value).strip().lower() == "true"
|
|
1446
|
+
else:
|
|
1447
|
+
fuzzy_search = fuzzy
|
|
1448
|
+
|
|
1410
1449
|
try:
|
|
1411
1450
|
results = []
|
|
1412
|
-
|
|
1451
|
+
|
|
1413
1452
|
# Search based on type filter
|
|
1414
1453
|
if type is None or type.lower() == 'all':
|
|
1415
|
-
funcs = code_finder.find_by_function_name(name, fuzzy_search=
|
|
1416
|
-
classes = code_finder.find_by_class_name(name, fuzzy_search=
|
|
1454
|
+
funcs = code_finder.find_by_function_name(name, fuzzy_search=fuzzy_search)
|
|
1455
|
+
classes = code_finder.find_by_class_name(name, fuzzy_search=fuzzy_search)
|
|
1417
1456
|
variables = code_finder.find_by_variable_name(name)
|
|
1418
1457
|
modules = code_finder.find_by_module_name(name)
|
|
1419
1458
|
imports = code_finder.find_imports(name)
|
|
@@ -1445,11 +1484,11 @@ def find_by_name(
|
|
|
1445
1484
|
results.append(row)
|
|
1446
1485
|
|
|
1447
1486
|
elif type.lower() == 'function':
|
|
1448
|
-
results = code_finder.find_by_function_name(name, fuzzy_search=
|
|
1487
|
+
results = code_finder.find_by_function_name(name, fuzzy_search=fuzzy_search)
|
|
1449
1488
|
for r in results: r['type'] = 'Function'
|
|
1450
|
-
|
|
1489
|
+
|
|
1451
1490
|
elif type.lower() == 'class':
|
|
1452
|
-
results = code_finder.find_by_class_name(name, fuzzy_search=
|
|
1491
|
+
results = code_finder.find_by_class_name(name, fuzzy_search=fuzzy_search)
|
|
1453
1492
|
for r in results: r['type'] = 'Class'
|
|
1454
1493
|
|
|
1455
1494
|
elif type.lower() == 'variable':
|
|
@@ -2226,10 +2265,10 @@ def analyze_inheritance_tree(
|
|
|
2226
2265
|
|
|
2227
2266
|
@analyze_app.command("complexity")
|
|
2228
2267
|
def analyze_complexity(
|
|
2229
|
-
path: Optional[str] = typer.Argument(None, help="
|
|
2268
|
+
path: Optional[str] = typer.Argument(None, help="Function name or file path to analyze"),
|
|
2230
2269
|
threshold: int = typer.Option(10, "--threshold", "-t", help="Complexity threshold for warnings"),
|
|
2231
2270
|
limit: int = typer.Option(20, "--limit", "-l", help="Maximum results to show"),
|
|
2232
|
-
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path
|
|
2271
|
+
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path to scope analysis"),
|
|
2233
2272
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
|
|
2234
2273
|
):
|
|
2235
2274
|
"""
|
|
@@ -2240,16 +2279,55 @@ def analyze_complexity(
|
|
|
2240
2279
|
cgc analyze complexity --threshold 15 # Functions over threshold
|
|
2241
2280
|
cgc analyze complexity my_function # Specific function
|
|
2242
2281
|
cgc analyze complexity my_function -f file.py # Specific function in file
|
|
2282
|
+
cgc analyze complexity src/main.py # Most complex functions in file
|
|
2283
|
+
cgc analyze complexity main.py # Most complex functions in file
|
|
2284
|
+
cgc analyze complexity --file src/main.py # Alternative file syntax
|
|
2243
2285
|
"""
|
|
2244
2286
|
_load_credentials()
|
|
2245
2287
|
services = _initialize_services(context)
|
|
2246
2288
|
if not all(services[:3]):
|
|
2247
2289
|
return
|
|
2248
2290
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2249
|
-
|
|
2291
|
+
|
|
2292
|
+
_FILE_EXTENSIONS = ('.py', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs', '.rb',
|
|
2293
|
+
'.java', '.cpp', '.c', '.cs', '.swift', '.kt', '.scala',
|
|
2294
|
+
'.php', '.lua', '.zig', '.ex', '.exs', '.r', '.m', '.sh')
|
|
2295
|
+
|
|
2296
|
+
def _is_file_path(value: str) -> bool:
|
|
2297
|
+
if '/' in value or '\\' in value:
|
|
2298
|
+
return True
|
|
2299
|
+
return any(value.endswith(ext) for ext in _FILE_EXTENSIONS)
|
|
2300
|
+
|
|
2301
|
+
def _render_complexity_table(results, title):
|
|
2302
|
+
if not results:
|
|
2303
|
+
console.print("[yellow]No complexity data available for this file[/yellow]")
|
|
2304
|
+
return
|
|
2305
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
2306
|
+
table.add_column("Function", style="cyan")
|
|
2307
|
+
table.add_column("Complexity", style="yellow", justify="right")
|
|
2308
|
+
table.add_column("Location", style="dim", overflow="fold")
|
|
2309
|
+
for func in results:
|
|
2310
|
+
complexity = func.get('complexity', 0)
|
|
2311
|
+
color = "red" if complexity > threshold else "yellow" if complexity > threshold/2 else "green"
|
|
2312
|
+
fpath = func.get('path', '')
|
|
2313
|
+
line_str = str(func.get('line_number', ''))
|
|
2314
|
+
location_str = f"{fpath}:{line_str}" if line_str else fpath
|
|
2315
|
+
table.add_row(
|
|
2316
|
+
func.get('function_name', ''),
|
|
2317
|
+
f"[{color}]{complexity}[/{color}]",
|
|
2318
|
+
location_str
|
|
2319
|
+
)
|
|
2320
|
+
console.print(f"\n[bold cyan]{title}[/bold cyan]")
|
|
2321
|
+
console.print(table)
|
|
2322
|
+
console.print(f"\n[dim]{len([f for f in results if f.get('complexity', 0) > threshold])} function(s) exceed threshold[/dim]")
|
|
2323
|
+
|
|
2250
2324
|
try:
|
|
2251
|
-
if path:
|
|
2252
|
-
#
|
|
2325
|
+
if path and _is_file_path(path):
|
|
2326
|
+
# File path provided as positional argument
|
|
2327
|
+
results = code_finder.find_most_complex_functions_in_file(path, limit)
|
|
2328
|
+
_render_complexity_table(results, f"Most Complex Functions in '{path}' (threshold: {threshold}):")
|
|
2329
|
+
elif path:
|
|
2330
|
+
# Specific function name
|
|
2253
2331
|
result = code_finder.get_cyclomatic_complexity(path, file)
|
|
2254
2332
|
if result:
|
|
2255
2333
|
console.print(f"\n[bold cyan]Complexity for '{path}':[/bold cyan]")
|
|
@@ -2258,35 +2336,14 @@ def analyze_complexity(
|
|
|
2258
2336
|
console.print(f" Line: [dim]{result.get('line_number', '')}[/dim]")
|
|
2259
2337
|
else:
|
|
2260
2338
|
console.print(f"[yellow]Function '{path}' not found or has no complexity data[/yellow]")
|
|
2339
|
+
elif file:
|
|
2340
|
+
# --file option without positional arg
|
|
2341
|
+
results = code_finder.find_most_complex_functions_in_file(file, limit)
|
|
2342
|
+
_render_complexity_table(results, f"Most Complex Functions in '{file}' (threshold: {threshold}):")
|
|
2261
2343
|
else:
|
|
2262
|
-
#
|
|
2344
|
+
# Global - most complex functions
|
|
2263
2345
|
results = code_finder.find_most_complex_functions(limit)
|
|
2264
|
-
|
|
2265
|
-
if not results:
|
|
2266
|
-
console.print("[yellow]No complexity data available[/yellow]")
|
|
2267
|
-
return
|
|
2268
|
-
|
|
2269
|
-
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
|
2270
|
-
table.add_column("Function", style="cyan")
|
|
2271
|
-
table.add_column("Complexity", style="yellow", justify="right")
|
|
2272
|
-
table.add_column("Location", style="dim", overflow="fold")
|
|
2273
|
-
|
|
2274
|
-
for func in results:
|
|
2275
|
-
complexity = func.get('complexity', 0)
|
|
2276
|
-
color = "red" if complexity > threshold else "yellow" if complexity > threshold/2 else "green"
|
|
2277
|
-
path = func.get('path', '')
|
|
2278
|
-
line_str = str(func.get('line_number', ''))
|
|
2279
|
-
location_str = f"{path}:{line_str}" if line_str else path
|
|
2280
|
-
|
|
2281
|
-
table.add_row(
|
|
2282
|
-
func.get('function_name', ''),
|
|
2283
|
-
f"[{color}]{complexity}[/{color}]",
|
|
2284
|
-
location_str
|
|
2285
|
-
)
|
|
2286
|
-
|
|
2287
|
-
console.print(f"\n[bold cyan]Most Complex Functions (threshold: {threshold}):[/bold cyan]")
|
|
2288
|
-
console.print(table)
|
|
2289
|
-
console.print(f"\n[dim]{len([f for f in results if f.get('complexity', 0) > threshold])} function(s) exceed threshold[/dim]")
|
|
2346
|
+
_render_complexity_table(results, f"Most Complex Functions (threshold: {threshold}):")
|
|
2290
2347
|
finally:
|
|
2291
2348
|
db_manager.close_driver()
|
|
2292
2349
|
|
|
@@ -2333,7 +2390,7 @@ def analyze_dead_code(
|
|
|
2333
2390
|
location_str
|
|
2334
2391
|
)
|
|
2335
2392
|
|
|
2336
|
-
console.print(
|
|
2393
|
+
console.print("\n[bold yellow]⚠️ Potentially Unused Functions:[/bold yellow]")
|
|
2337
2394
|
console.print(table)
|
|
2338
2395
|
console.print(f"\n[dim]Total: {len(unused_funcs)} function(s)[/dim]")
|
|
2339
2396
|
console.print(f"[dim]Note: {results.get('note', '')}[/dim]")
|