codegraphcontext 0.4.18__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/app.py +4 -1
- codegraphcontext/api/mcp_sse.py +63 -22
- codegraphcontext/api/router.py +3 -6
- codegraphcontext/cli/cli_helpers.py +87 -13
- codegraphcontext/cli/config_manager.py +152 -38
- codegraphcontext/cli/hook_manager.py +1 -1
- codegraphcontext/cli/main.py +172 -42
- codegraphcontext/cli/registry_commands.py +11 -4
- codegraphcontext/cli/setup_macos.py +6 -1
- codegraphcontext/cli/setup_wizard.py +61 -56
- codegraphcontext/cli/visualizer.py +25 -1
- codegraphcontext/core/__init__.py +71 -10
- codegraphcontext/core/cgc_bundle.py +1 -1
- codegraphcontext/core/cgcignore.py +4 -1
- codegraphcontext/core/database_embedded_kuzu.py +1265 -0
- codegraphcontext/core/database_falkordb.py +41 -2
- codegraphcontext/core/database_kuzu.py +37 -1163
- codegraphcontext/core/database_ladybug.py +37 -1145
- codegraphcontext/core/graph_query.py +45 -0
- codegraphcontext/core/jobs.py +7 -2
- codegraphcontext/core/watcher.py +114 -188
- codegraphcontext/prompts.py +1 -1
- codegraphcontext/server.py +111 -19
- codegraphcontext/stdlibs.py +18 -0
- codegraphcontext/tool_definitions.py +188 -79
- codegraphcontext/tools/code_finder.py +33 -19
- codegraphcontext/tools/graph_builder.py +266 -10
- codegraphcontext/tools/handlers/analysis_handlers.py +5 -0
- codegraphcontext/tools/handlers/indexing_handlers.py +4 -1
- codegraphcontext/tools/handlers/management_handlers.py +9 -10
- codegraphcontext/tools/handlers/query_handlers.py +21 -3
- codegraphcontext/tools/handlers/watcher_handlers.py +2 -2
- codegraphcontext/tools/indexing/embeddings.py +63 -49
- codegraphcontext/tools/indexing/persistence/writer.py +437 -49
- codegraphcontext/tools/indexing/pipeline.py +66 -1
- codegraphcontext/tools/indexing/resolution/__init__.py +11 -1
- codegraphcontext/tools/indexing/resolution/calls.py +527 -70
- codegraphcontext/tools/indexing/resolution/inheritance.py +448 -4
- codegraphcontext/tools/indexing/resolution/post_resolution.py +40 -15
- codegraphcontext/tools/indexing/schema.py +5 -0
- codegraphcontext/tools/indexing/schema_contract.py +7 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +25 -10
- codegraphcontext/tools/indexing/vector_resolver.py +72 -57
- codegraphcontext/tools/languages/c.py +154 -2
- codegraphcontext/tools/languages/cpp.py +13 -1
- codegraphcontext/tools/languages/csharp.py +1 -0
- codegraphcontext/tools/languages/css.py +13 -4
- codegraphcontext/tools/languages/dart.py +170 -41
- codegraphcontext/tools/languages/elixir.py +9 -6
- codegraphcontext/tools/languages/go.py +56 -16
- codegraphcontext/tools/languages/haskell.py +80 -8
- codegraphcontext/tools/languages/html.py +23 -8
- codegraphcontext/tools/languages/javascript.py +2 -2
- codegraphcontext/tools/languages/lua.py +61 -19
- codegraphcontext/tools/languages/perl.py +37 -4
- codegraphcontext/tools/languages/php.py +56 -4
- codegraphcontext/tools/languages/python.py +13 -3
- codegraphcontext/tools/languages/rust.py +70 -6
- codegraphcontext/tools/languages/swift.py +61 -11
- codegraphcontext/tools/languages/typescript.py +63 -6
- codegraphcontext/tools/package_resolver.py +159 -364
- 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/system.py +8 -1
- 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.4.18.dist-info → codegraphcontext-0.5.2.dist-info}/METADATA +201 -23
- codegraphcontext-0.5.2.dist-info/RECORD +132 -0
- {codegraphcontext-0.4.18.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.4.18.dist-info/RECORD +0 -166
- {codegraphcontext-0.4.18.dist-info → codegraphcontext-0.5.2.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.4.18.dist-info → codegraphcontext-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.4.18.dist-info → codegraphcontext-0.5.2.dist-info}/top_level.txt +0 -0
|
@@ -73,7 +73,7 @@ def install_hooks(start: Path | str | None = None, *, force: bool = False) -> Ho
|
|
|
73
73
|
)
|
|
74
74
|
hook_path.write_text(script, encoding="utf-8")
|
|
75
75
|
mode = hook_path.stat().st_mode
|
|
76
|
-
hook_path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP
|
|
76
|
+
hook_path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP)
|
|
77
77
|
|
|
78
78
|
_ensure_gitattributes(repo.root)
|
|
79
79
|
_configure_merge_driver(repo.root)
|
codegraphcontext/cli/main.py
CHANGED
|
@@ -85,8 +85,9 @@ app = typer.Typer(
|
|
|
85
85
|
)
|
|
86
86
|
console = Console(stderr=True)
|
|
87
87
|
|
|
88
|
-
# Configure basic logging for the application.
|
|
89
|
-
|
|
88
|
+
# Configure basic logging for the application. Default to WARNING so CLI
|
|
89
|
+
# output stays clean; the root --debug flag switches this to DEBUG.
|
|
90
|
+
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
def get_version() -> str:
|
|
@@ -142,6 +143,7 @@ def mcp_start():
|
|
|
142
143
|
# This typically happens if credentials are still not found after all checks.
|
|
143
144
|
console.print(f"[bold red]Configuration Error:[/bold red] {e}")
|
|
144
145
|
console.print("Please run `cgc neo4j setup` or use FalkorDB (default).")
|
|
146
|
+
raise typer.Exit(code=1) from e
|
|
145
147
|
except KeyboardInterrupt:
|
|
146
148
|
# Handle graceful shutdown on Ctrl+C.
|
|
147
149
|
console.print("\n[bold yellow]Server stopped by user.[/bold yellow]")
|
|
@@ -177,8 +179,10 @@ def mcp_tools():
|
|
|
177
179
|
except ValueError as e:
|
|
178
180
|
console.print(f"[bold red]Error loading tools:[/bold red] {e}")
|
|
179
181
|
console.print("Please ensure your database is configured correctly.")
|
|
182
|
+
raise typer.Exit(code=1) from e
|
|
180
183
|
except Exception as e:
|
|
181
184
|
console.print(f"[bold red]An unexpected error occurred:[/bold red] {e}")
|
|
185
|
+
raise typer.Exit(code=1) from e
|
|
182
186
|
|
|
183
187
|
# Abbreviation for mcp setup
|
|
184
188
|
@app.command("m", rich_help_panel="Shortcuts")
|
|
@@ -391,7 +395,6 @@ def _load_credentials(cli_context_flag: Optional[str] = None):
|
|
|
391
395
|
# IMPORTANT: DB-selection keys set in the shell must win over .env defaults.
|
|
392
396
|
# E.g. `DEFAULT_DATABASE=falkordb cgc index …` must not be overridden by
|
|
393
397
|
# DEFAULT_DATABASE=neo4j sitting in ~/.codegraphcontext/.env
|
|
394
|
-
DB_OVERRIDE_KEYS = {"CGC_RUNTIME_DB_TYPE", "DEFAULT_DATABASE"}
|
|
395
398
|
for key, value in merged_config.items():
|
|
396
399
|
if value is not None: # Only set non-None values
|
|
397
400
|
if key in runtime_env:
|
|
@@ -627,7 +630,7 @@ def bundle_export(
|
|
|
627
630
|
|
|
628
631
|
services = _initialize_services(context)
|
|
629
632
|
if not all(services[:3]):
|
|
630
|
-
|
|
633
|
+
raise typer.Exit(code=1)
|
|
631
634
|
db_manager, _, code_finder = services[:3]
|
|
632
635
|
|
|
633
636
|
try:
|
|
@@ -697,7 +700,7 @@ def bundle_import(
|
|
|
697
700
|
|
|
698
701
|
services = _initialize_services(context)
|
|
699
702
|
if not all(services[:3]):
|
|
700
|
-
|
|
703
|
+
raise typer.Exit(code=1)
|
|
701
704
|
db_manager, graph_builder, code_finder = services[:3]
|
|
702
705
|
|
|
703
706
|
try:
|
|
@@ -790,6 +793,53 @@ def bundle_load(
|
|
|
790
793
|
console.print("[dim]Use 'cgc registry list' to see available bundles[/dim]")
|
|
791
794
|
raise typer.Exit(code=1)
|
|
792
795
|
|
|
796
|
+
@bundle_app.command("merge")
|
|
797
|
+
def bundle_merge(
|
|
798
|
+
ancestor: str = typer.Argument(..., help="Common ancestor version of the bundle (%O)"),
|
|
799
|
+
current: str = typer.Argument(..., help="Current branch version of the bundle (%A); also the merge result"),
|
|
800
|
+
other: str = typer.Argument(..., help="Other branch version of the bundle (%B)"),
|
|
801
|
+
):
|
|
802
|
+
"""
|
|
803
|
+
Git merge driver for .cgc bundle files.
|
|
804
|
+
|
|
805
|
+
Registered by `cgc hook install` as `merge.cgc-bundle.driver = cgc bundle
|
|
806
|
+
merge %O %A %B`. Git invokes it with temp files holding the ancestor (%O),
|
|
807
|
+
current (%A) and other (%B) versions; the merge result must be left in the
|
|
808
|
+
%A file and the driver must exit 0 for the merge to proceed.
|
|
809
|
+
|
|
810
|
+
Bundles are binary snapshots that cannot be merged line-by-line, so the
|
|
811
|
+
strategy is: if both sides are identical, accept either; otherwise keep
|
|
812
|
+
the current branch's version (already in the %A file) and warn that the
|
|
813
|
+
bundle should be regenerated with `cgc export` after the merge.
|
|
814
|
+
"""
|
|
815
|
+
current_path = Path(current)
|
|
816
|
+
other_path = Path(other)
|
|
817
|
+
|
|
818
|
+
def _read_bytes(p: Path) -> bytes:
|
|
819
|
+
# A missing or empty temp file means the side has no content
|
|
820
|
+
# (e.g. the bundle is a new file on both branches → empty ancestor).
|
|
821
|
+
try:
|
|
822
|
+
return p.read_bytes() if p.exists() else b""
|
|
823
|
+
except OSError:
|
|
824
|
+
return b""
|
|
825
|
+
|
|
826
|
+
current_bytes = _read_bytes(current_path)
|
|
827
|
+
other_bytes = _read_bytes(other_path)
|
|
828
|
+
|
|
829
|
+
if current_bytes == other_bytes:
|
|
830
|
+
# Both branches have identical bundle contents; nothing to do.
|
|
831
|
+
raise typer.Exit(code=0)
|
|
832
|
+
|
|
833
|
+
# Keep the current branch's version (the %A file already contains it).
|
|
834
|
+
console.print(
|
|
835
|
+
"[yellow]⚠ Conflicting changes to a .cgc bundle were detected during merge. "
|
|
836
|
+
"Keeping the current branch's version.[/yellow]"
|
|
837
|
+
)
|
|
838
|
+
console.print(
|
|
839
|
+
"[yellow]The bundle may be stale — regenerate it with 'cgc export' after the merge.[/yellow]"
|
|
840
|
+
)
|
|
841
|
+
raise typer.Exit(code=0)
|
|
842
|
+
|
|
793
843
|
# ============================================================================
|
|
794
844
|
# HOOK COMMAND GROUP - Git integration
|
|
795
845
|
# ============================================================================
|
|
@@ -1146,11 +1196,18 @@ def doctor():
|
|
|
1146
1196
|
all_checks_passed = False
|
|
1147
1197
|
elif default_db == "falkordb":
|
|
1148
1198
|
try:
|
|
1149
|
-
|
|
1150
|
-
|
|
1199
|
+
from codegraphcontext.core import is_falkordb_usable
|
|
1200
|
+
|
|
1201
|
+
if is_falkordb_usable():
|
|
1202
|
+
console.print(" [green]✓[/green] FalkorDB Lite is installed")
|
|
1203
|
+
else:
|
|
1204
|
+
raise ImportError("FalkorDB Lite is not available on this platform")
|
|
1151
1205
|
except ImportError:
|
|
1152
|
-
|
|
1206
|
+
# falkordb is the configured/default backend, so a missing
|
|
1207
|
+
# FalkorDB Lite means the database cannot work — fail the check.
|
|
1208
|
+
console.print(" [red]✗[/red] FalkorDB Lite not installed (Python 3.12+ only)")
|
|
1153
1209
|
console.print(" Run: pip install falkordblite")
|
|
1210
|
+
all_checks_passed = False
|
|
1154
1211
|
else:
|
|
1155
1212
|
console.print(f" [yellow]⚠[/yellow] No connectivity probe for backend '{default_db}'")
|
|
1156
1213
|
except Exception as e:
|
|
@@ -1238,27 +1295,61 @@ def doctor():
|
|
|
1238
1295
|
|
|
1239
1296
|
|
|
1240
1297
|
|
|
1298
|
+
@app.command()
|
|
1241
1299
|
@app.command()
|
|
1242
1300
|
def index(
|
|
1243
1301
|
path: Optional[str] = typer.Argument(None, help="Path to the directory or file to index. Defaults to the current directory."),
|
|
1244
1302
|
force: bool = typer.Option(False, "--force", "-f", help="Force re-index (delete existing and rebuild)"),
|
|
1245
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"),
|
|
1246
1305
|
):
|
|
1247
1306
|
"""
|
|
1248
1307
|
Indexes a directory or file by adding it to the code graph.
|
|
1249
1308
|
If no path is provided, it indexes the current directory.
|
|
1250
|
-
|
|
1309
|
+
|
|
1251
1310
|
Use --force to delete the existing index and rebuild from scratch.
|
|
1311
|
+
Use --summarize to display a summary after indexing.
|
|
1252
1312
|
"""
|
|
1253
1313
|
_load_credentials()
|
|
1254
1314
|
if path is None:
|
|
1255
1315
|
path = str(Path.cwd())
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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]")
|
|
1262
1353
|
|
|
1263
1354
|
@app.command()
|
|
1264
1355
|
def update(
|
|
@@ -1275,7 +1366,7 @@ def update(
|
|
|
1275
1366
|
_load_credentials()
|
|
1276
1367
|
if path is None:
|
|
1277
1368
|
path = str(Path.cwd())
|
|
1278
|
-
update_helper(path, context)
|
|
1369
|
+
update_helper(path, context, quiet=quiet)
|
|
1279
1370
|
|
|
1280
1371
|
@app.command()
|
|
1281
1372
|
def clean(
|
|
@@ -1344,7 +1435,7 @@ def delete(
|
|
|
1344
1435
|
# Delete all repositories
|
|
1345
1436
|
services = _initialize_services(context)
|
|
1346
1437
|
if not all(services[:3]):
|
|
1347
|
-
|
|
1438
|
+
raise typer.Exit(code=1)
|
|
1348
1439
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1349
1440
|
|
|
1350
1441
|
try:
|
|
@@ -1409,7 +1500,14 @@ def delete(
|
|
|
1409
1500
|
console.print("[red]Error: Please provide a path or use --all to delete all repositories[/red]")
|
|
1410
1501
|
console.print("Usage: cgc delete <path> or cgc delete --all")
|
|
1411
1502
|
raise typer.Exit(code=1)
|
|
1412
|
-
|
|
1503
|
+
|
|
1504
|
+
if not config_manager.is_db_deletion_allowed():
|
|
1505
|
+
console.print(
|
|
1506
|
+
"[bold red]Error:[/bold red] Repository deletion is disabled. "
|
|
1507
|
+
"Set ALLOW_DB_DELETION=true in config to enable."
|
|
1508
|
+
)
|
|
1509
|
+
raise typer.Exit(code=1)
|
|
1510
|
+
|
|
1413
1511
|
delete_helper(path, context)
|
|
1414
1512
|
|
|
1415
1513
|
|
|
@@ -1455,6 +1553,7 @@ def report(
|
|
|
1455
1553
|
@app.command()
|
|
1456
1554
|
def visualize(
|
|
1457
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."),
|
|
1458
1557
|
port: int = typer.Option(8000, "--port", "-p", help="Port to run the visualizer server on."),
|
|
1459
1558
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use")
|
|
1460
1559
|
):
|
|
@@ -1462,7 +1561,7 @@ def visualize(
|
|
|
1462
1561
|
Launches the interactive UI to visualize the code graph.
|
|
1463
1562
|
"""
|
|
1464
1563
|
_load_credentials()
|
|
1465
|
-
visualize_helper(repo, port, context)
|
|
1564
|
+
visualize_helper(repo, host, port, context)
|
|
1466
1565
|
|
|
1467
1566
|
@app.command("list")
|
|
1468
1567
|
def list_repositories(
|
|
@@ -1501,6 +1600,14 @@ def watch(
|
|
|
1501
1600
|
"--poll",
|
|
1502
1601
|
help="Use watchdog's polling observer for Docker bind mounts and network filesystems.",
|
|
1503
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
|
+
),
|
|
1504
1611
|
):
|
|
1505
1612
|
"""
|
|
1506
1613
|
Watch a directory for file changes and automatically update the code graph.
|
|
@@ -1511,6 +1618,7 @@ def watch(
|
|
|
1511
1618
|
|
|
1512
1619
|
The watcher will:
|
|
1513
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
|
|
1514
1622
|
- Monitor for file creation, modification, deletion, and moves
|
|
1515
1623
|
- Automatically re-index affected files and update relationships
|
|
1516
1624
|
|
|
@@ -1520,12 +1628,13 @@ def watch(
|
|
|
1520
1628
|
cgc watch . # Watch current directory
|
|
1521
1629
|
cgc watch /path/to/project # Watch specific directory
|
|
1522
1630
|
cgc watch --poll . # Use polling for Docker/NFS/SMB mounts
|
|
1631
|
+
cgc watch --sync-on-start . # Reconcile current files before watching
|
|
1523
1632
|
cgc w . # Using shortcut alias
|
|
1524
1633
|
|
|
1525
1634
|
Set CGC_WATCH_POLLING=1 to use polling without passing --poll.
|
|
1526
1635
|
"""
|
|
1527
1636
|
_load_credentials()
|
|
1528
|
-
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)
|
|
1529
1638
|
|
|
1530
1639
|
@app.command()
|
|
1531
1640
|
def unwatch(
|
|
@@ -1593,7 +1702,7 @@ def find_by_name(
|
|
|
1593
1702
|
_load_credentials()
|
|
1594
1703
|
services = _initialize_services(context)
|
|
1595
1704
|
if not all(services[:3]):
|
|
1596
|
-
|
|
1705
|
+
raise typer.Exit(code=1)
|
|
1597
1706
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1598
1707
|
|
|
1599
1708
|
# Resolve effective fuzzy setting: CLI flag wins, else config, else true.
|
|
@@ -1725,7 +1834,7 @@ def find_by_pattern(
|
|
|
1725
1834
|
_load_credentials()
|
|
1726
1835
|
services = _initialize_services(context)
|
|
1727
1836
|
if not all(services[:3]):
|
|
1728
|
-
|
|
1837
|
+
raise typer.Exit(code=1)
|
|
1729
1838
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1730
1839
|
|
|
1731
1840
|
try:
|
|
@@ -1818,7 +1927,7 @@ def find_by_type(
|
|
|
1818
1927
|
_load_credentials()
|
|
1819
1928
|
services = _initialize_services(context)
|
|
1820
1929
|
if not all(services[:3]):
|
|
1821
|
-
|
|
1930
|
+
raise typer.Exit(code=1)
|
|
1822
1931
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1823
1932
|
|
|
1824
1933
|
try:
|
|
@@ -1873,7 +1982,7 @@ def find_by_variable(
|
|
|
1873
1982
|
_load_credentials()
|
|
1874
1983
|
services = _initialize_services(context)
|
|
1875
1984
|
if not all(services[:3]):
|
|
1876
|
-
|
|
1985
|
+
raise typer.Exit(code=1)
|
|
1877
1986
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1878
1987
|
|
|
1879
1988
|
try:
|
|
@@ -1919,7 +2028,7 @@ def find_by_content_search(
|
|
|
1919
2028
|
_load_credentials()
|
|
1920
2029
|
services = _initialize_services(context)
|
|
1921
2030
|
if not all(services[:3]):
|
|
1922
|
-
|
|
2031
|
+
raise typer.Exit(code=1)
|
|
1923
2032
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1924
2033
|
|
|
1925
2034
|
try:
|
|
@@ -1978,7 +2087,7 @@ def find_by_decorator_search(
|
|
|
1978
2087
|
_load_credentials()
|
|
1979
2088
|
services = _initialize_services(context)
|
|
1980
2089
|
if not all(services[:3]):
|
|
1981
|
-
|
|
2090
|
+
raise typer.Exit(code=1)
|
|
1982
2091
|
db_manager, graph_builder, code_finder = services[:3]
|
|
1983
2092
|
|
|
1984
2093
|
try:
|
|
@@ -2026,7 +2135,7 @@ def find_by_argument_search(
|
|
|
2026
2135
|
_load_credentials()
|
|
2027
2136
|
services = _initialize_services(context)
|
|
2028
2137
|
if not all(services[:3]):
|
|
2029
|
-
|
|
2138
|
+
raise typer.Exit(code=1)
|
|
2030
2139
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2031
2140
|
|
|
2032
2141
|
try:
|
|
@@ -2082,7 +2191,7 @@ def analyze_calls(
|
|
|
2082
2191
|
_load_credentials()
|
|
2083
2192
|
services = _initialize_services(context)
|
|
2084
2193
|
if not all(services[:3]):
|
|
2085
|
-
|
|
2194
|
+
raise typer.Exit(code=1)
|
|
2086
2195
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2087
2196
|
|
|
2088
2197
|
try:
|
|
@@ -2138,7 +2247,7 @@ def analyze_callers(
|
|
|
2138
2247
|
_load_credentials()
|
|
2139
2248
|
services = _initialize_services(context)
|
|
2140
2249
|
if not all(services[:3]):
|
|
2141
|
-
|
|
2250
|
+
raise typer.Exit(code=1)
|
|
2142
2251
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2143
2252
|
|
|
2144
2253
|
try:
|
|
@@ -2199,7 +2308,7 @@ def analyze_chain(
|
|
|
2199
2308
|
_load_credentials()
|
|
2200
2309
|
services = _initialize_services(context)
|
|
2201
2310
|
if not all(services[:3]):
|
|
2202
|
-
|
|
2311
|
+
raise typer.Exit(code=1)
|
|
2203
2312
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2204
2313
|
|
|
2205
2314
|
try:
|
|
@@ -2269,7 +2378,7 @@ def analyze_kotlin_call_audit(
|
|
|
2269
2378
|
_load_credentials()
|
|
2270
2379
|
services = _initialize_services(context)
|
|
2271
2380
|
if not all(services[:3]):
|
|
2272
|
-
|
|
2381
|
+
raise typer.Exit(code=1)
|
|
2273
2382
|
db_manager, _, code_finder = services[:3]
|
|
2274
2383
|
|
|
2275
2384
|
try:
|
|
@@ -2329,7 +2438,7 @@ def analyze_dependencies(
|
|
|
2329
2438
|
_load_credentials()
|
|
2330
2439
|
services = _initialize_services(context)
|
|
2331
2440
|
if not all(services[:3]):
|
|
2332
|
-
|
|
2441
|
+
raise typer.Exit(code=1)
|
|
2333
2442
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2334
2443
|
|
|
2335
2444
|
try:
|
|
@@ -2394,7 +2503,7 @@ def analyze_inheritance_tree(
|
|
|
2394
2503
|
_load_credentials()
|
|
2395
2504
|
services = _initialize_services(context)
|
|
2396
2505
|
if not all(services[:3]):
|
|
2397
|
-
|
|
2506
|
+
raise typer.Exit(code=1)
|
|
2398
2507
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2399
2508
|
|
|
2400
2509
|
try:
|
|
@@ -2444,7 +2553,7 @@ def analyze_inheritance_tree(
|
|
|
2444
2553
|
@analyze_app.command("complexity")
|
|
2445
2554
|
def analyze_complexity(
|
|
2446
2555
|
path: Optional[str] = typer.Argument(None, help="Function name or file path to analyze"),
|
|
2447
|
-
threshold: int = typer.Option(
|
|
2556
|
+
threshold: Optional[int] = typer.Option(None, "--threshold", "-t", help="Complexity threshold for warnings (default: from config or 10)"),
|
|
2448
2557
|
limit: int = typer.Option(20, "--limit", "-l", help="Maximum results to show"),
|
|
2449
2558
|
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path to scope analysis"),
|
|
2450
2559
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
|
|
@@ -2464,9 +2573,20 @@ def analyze_complexity(
|
|
|
2464
2573
|
_load_credentials()
|
|
2465
2574
|
services = _initialize_services(context)
|
|
2466
2575
|
if not all(services[:3]):
|
|
2467
|
-
|
|
2576
|
+
raise typer.Exit(code=1)
|
|
2468
2577
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2469
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
|
+
|
|
2470
2590
|
_FILE_EXTENSIONS = ('.py', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs', '.rb',
|
|
2471
2591
|
'.java', '.cpp', '.c', '.cs', '.swift', '.kt', '.scala',
|
|
2472
2592
|
'.php', '.lua', '.zig', '.ex', '.exs', '.r', '.m', '.sh')
|
|
@@ -2541,7 +2661,7 @@ def analyze_dead_code(
|
|
|
2541
2661
|
_load_credentials()
|
|
2542
2662
|
services = _initialize_services(context)
|
|
2543
2663
|
if not all(services[:3]):
|
|
2544
|
-
|
|
2664
|
+
raise typer.Exit(code=1)
|
|
2545
2665
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2546
2666
|
|
|
2547
2667
|
try:
|
|
@@ -2595,7 +2715,7 @@ def analyze_overrides(
|
|
|
2595
2715
|
_load_credentials()
|
|
2596
2716
|
services = _initialize_services(context)
|
|
2597
2717
|
if not all(services[:3]):
|
|
2598
|
-
|
|
2718
|
+
raise typer.Exit(code=1)
|
|
2599
2719
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2600
2720
|
|
|
2601
2721
|
try:
|
|
@@ -2650,7 +2770,7 @@ def analyze_variable_usage(
|
|
|
2650
2770
|
_load_credentials()
|
|
2651
2771
|
services = _initialize_services(context)
|
|
2652
2772
|
if not all(services[:3]):
|
|
2653
|
-
|
|
2773
|
+
raise typer.Exit(code=1)
|
|
2654
2774
|
db_manager, graph_builder, code_finder = services[:3]
|
|
2655
2775
|
|
|
2656
2776
|
try:
|
|
@@ -2782,9 +2902,22 @@ def visualize_abbrev(
|
|
|
2782
2902
|
def watch_abbrev(
|
|
2783
2903
|
path: str = typer.Argument(".", help="Path to watch"),
|
|
2784
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
|
+
),
|
|
2785
2918
|
):
|
|
2786
2919
|
"""Shortcut for 'cgc watch'"""
|
|
2787
|
-
watch(path, context=context)
|
|
2920
|
+
watch(path, context=context, poll=poll, sync_on_start=sync_on_start)
|
|
2788
2921
|
|
|
2789
2922
|
|
|
2790
2923
|
# ============================================================================
|
|
@@ -2854,9 +2987,6 @@ def main(
|
|
|
2854
2987
|
os.environ["CGC_RUNTIME_DB_TYPE"] = database
|
|
2855
2988
|
# Initialize context object for sharing state with subcommands
|
|
2856
2989
|
ctx.ensure_object(dict)
|
|
2857
|
-
|
|
2858
|
-
if database:
|
|
2859
|
-
os.environ["CGC_RUNTIME_DB_TYPE"] = database
|
|
2860
2990
|
|
|
2861
2991
|
# Store visual flag in context for subcommands to access
|
|
2862
2992
|
if visual:
|
|
@@ -289,9 +289,16 @@ def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool
|
|
|
289
289
|
if auto_load:
|
|
290
290
|
console.print("[cyan]Using existing bundle for loading...[/cyan]")
|
|
291
291
|
return str(output_path)
|
|
292
|
-
return
|
|
292
|
+
return False
|
|
293
293
|
output_path.unlink()
|
|
294
|
-
|
|
294
|
+
|
|
295
|
+
from ..utils.path_sandbox import is_safe_download_url
|
|
296
|
+
|
|
297
|
+
if not is_safe_download_url(download_url):
|
|
298
|
+
console.print("[bold red]Refusing to download from untrusted URL.[/bold red]")
|
|
299
|
+
console.print("[dim]Only HTTPS downloads from approved hosts are allowed.[/dim]")
|
|
300
|
+
raise typer.Exit(code=1)
|
|
301
|
+
|
|
295
302
|
# Download with progress bar
|
|
296
303
|
try:
|
|
297
304
|
console.print(f"[cyan]Downloading {clean_filename}...[/cyan]")
|
|
@@ -435,8 +442,8 @@ def load_bundle_command(bundle_name: str, clear_existing: bool = False):
|
|
|
435
442
|
stats["nodes"] = int(part.split(":")[1].strip().replace(",", ""))
|
|
436
443
|
elif "Edges:" in part:
|
|
437
444
|
stats["edges"] = int(part.split(":")[1].strip().replace(",", ""))
|
|
438
|
-
except:
|
|
439
|
-
|
|
445
|
+
except Exception as parse_exc:
|
|
446
|
+
console.print(f"[dim]Could not parse bundle stats from message: {parse_exc}[/dim]")
|
|
440
447
|
|
|
441
448
|
return (True, message, stats)
|
|
442
449
|
else:
|
|
@@ -16,12 +16,17 @@ def _brew_install_neo4j(run_command, console) -> str:
|
|
|
16
16
|
def _brew_start(service: str, run_command, console) -> bool:
|
|
17
17
|
return run_command(["brew", "services", "start", service], console, check=False) is not None
|
|
18
18
|
|
|
19
|
+
def _escape_cypher_password(password: str) -> str:
|
|
20
|
+
return password.replace("\\", "\\\\").replace("'", "\\'")
|
|
21
|
+
|
|
22
|
+
|
|
19
23
|
def _set_initial_password(new_pw: str, run_command, console) -> bool:
|
|
24
|
+
escaped_pw = _escape_cypher_password(new_pw)
|
|
20
25
|
cmd = [
|
|
21
26
|
"cypher-shell",
|
|
22
27
|
"-u", "neo4j",
|
|
23
28
|
"-p", "neo4j",
|
|
24
|
-
f"ALTER CURRENT USER SET PASSWORD FROM 'neo4j' TO '{
|
|
29
|
+
f"ALTER CURRENT USER SET PASSWORD FROM 'neo4j' TO '{escaped_pw}'",
|
|
25
30
|
]
|
|
26
31
|
return run_command(cmd, console, check=False) is not None
|
|
27
32
|
|