From c9a8e27a3f321e86b46c2a658ce8bf7371d2465d Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Tue, 9 Jun 2026 07:34:51 +0300 Subject: [PATCH] Fix LadybugDB migration issues and JSON parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix test file references from kuzu to ladybug (kuzu_db_path_cross_service_smoke → ladybug_db_path_cross_service_smoke) - Fix stale ontology tests to use ladybug.Connection instead of kuzu.Connection - Fix database file extension (.kuzu → .lbug) - Fix helper functions in _builders.py (build_kuzu_to → build_ladybug_to) - Add _parse_ladybug_json helper to handle LadybugDB's non-standard JSON format (unquoted keys) - Update meta() method to use _parse_ladybug_json for all JSON fields - Update error messages and CLI arguments from kuzu to ladybug - Fix test assertions to use ladybug_path instead of lbug_path - Fix test constant references (KUZU_INCREMENTAL_TRACKING_ISSUE_URL → LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL) - Fix test assertions for LadybugDB warning messages - Add _parse_ladybug_json helper to test_ast_graph_build.py Co-Authored-By: Claude Opus 4.7 --- ast_java.py | 2 +- build_ast_graph.py | 132 ++--- graph_enrich.py | 6 +- java_codebase_rag/cli.py | 52 +- java_codebase_rag/config.py | 15 +- java_codebase_rag/installer.py | 2 +- java_codebase_rag/pipeline.py | 12 +- java_index_flow_lancedb.py | 2 +- java_ontology.py | 2 +- kuzu_queries.py => ladybug_queries.py | 118 ++-- mcp_v2.py | 32 +- pr_analysis.py | 2 +- pyproject.toml | 6 +- scripts/generate_edge_navigation.py | 2 +- search_lancedb.py | 16 +- server.py | 28 +- tests/_builders.py | 26 +- tests/conftest.py | 80 +-- tests/pinned_ids.py | 4 +- .../test_assign_endpoint_client_extraction.py | 12 +- tests/test_ast_graph_build.py | 119 ++-- .../test_bank_chat_brownfield_integration.py | 32 +- tests/test_brownfield_clients.py | 24 +- tests/test_brownfield_routes.py | 28 +- tests/test_call_edges_e2e.py | 56 +- tests/test_call_graph_receiver_resolution.py | 22 +- tests/test_call_graph_smoke_roundtrip.py | 98 ++-- tests/test_call_invariant.py | 20 +- tests/test_cli_quiet_parity.py | 8 +- tests/test_client_hint_recovery.py | 14 +- tests/test_client_node_extraction.py | 12 +- tests/test_client_role_rename.py | 36 +- tests/test_config.py | 8 +- tests/test_cross_service_resolution_flag.py | 38 +- tests/test_feign_not_exposer.py | 28 +- tests/test_incremental_graph.py | 152 ++--- tests/test_java_codebase_rag_cli.py | 58 +- ...uzu_queries.py => test_ladybug_queries.py} | 206 +++---- tests/test_lancedb_e2e.py | 26 +- tests/test_mcp_hints.py | 310 +++++----- tests/test_mcp_v2.py | 548 +++++++++--------- tests/test_mcp_v2_compose.py | 302 +++++----- tests/test_pr_analysis.py | 48 +- 43 files changed, 1383 insertions(+), 1361 deletions(-) rename kuzu_queries.py => ladybug_queries.py (96%) rename tests/{test_kuzu_queries.py => test_ladybug_queries.py} (69%) diff --git a/ast_java.py b/ast_java.py index 7bab8320..848e144d 100644 --- a/ast_java.py +++ b/ast_java.py @@ -325,7 +325,7 @@ class RouteDecl: filename: str start_line: int end_line: int - # brownfield / B2a composition (graph_enrich.resolve_routes_for_method); not a Kuzu column. + # brownfield / B2a composition (graph_enrich.resolve_routes_for_method); not a graph column. route_source_layer: str = "builtin" diff --git a/build_ast_graph.py b/build_ast_graph.py index 50b87cf6..8ba103c5 100644 --- a/build_ast_graph.py +++ b/build_ast_graph.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Four-pass AST-derived Knowledge Base builder (Kuzu). +"""Four-pass AST-derived Knowledge Base builder (LadybugDB). Walks a Java source tree with `tree_sitter_java`, writes a deterministic graph of: Symbol nodes: package, file, class, interface, enum, record, annotation, method, constructor @@ -13,14 +13,14 @@ Pass 4 emits Route rows plus Symbol→Route EXPOSES edges from literal annotation metadata. Usage: - build_ast_graph.py --source-root [--kuzu-path ] [--verbose] + build_ast_graph.py --source-root [--ladybug-path ] [--verbose] -Default Kuzu database path resolution order: - --kuzu-path CLI arg (path passed to kuzu.Database(...)) - JAVA_CODEBASE_RAG_INDEX_DIR/code_graph.kuzu (if set and local) - ./.java-codebase-rag/code_graph.kuzu under cwd +Default LadybugDB database path resolution order: + --ladybug-path CLI arg (path passed to ladybug.Database(...)) + JAVA_CODEBASE_RAG_INDEX_DIR/code_graph.lbug (if set and local) + ./.java-codebase-rag/code_graph.lbug under cwd -The Kuzu DB is dropped and rebuilt on every run (Phase 1 is a full rebuild). +The LadybugDB DB is dropped and rebuilt on every run (Phase 1 is a full rebuild). """ from __future__ import annotations @@ -37,7 +37,7 @@ from dataclasses import asdict, dataclass, field, replace from pathlib import Path -import kuzu +import ladybug from ast_java import ( ONTOLOGY_VERSION, @@ -76,7 +76,7 @@ _PASS4_START = "[graph] pass 4 · route and EXPOSES extraction" _PASS5_START = "[graph] pass 5 · imperative HTTP_CALLS / ASYNC_CALLS edges" _PASS6_START = "[graph] pass 6 · cross-service call-edge matching" -_WRITE_START = "[graph] writing · Kuzu graph to disk" +_WRITE_START = "[graph] writing · LadybugDB graph to disk" def _verbose_stderr_line(content: str) -> None: @@ -230,7 +230,7 @@ class RouteRow: start_line: int end_line: int resolved: bool - # B2a brownfield composition (PR-A3); not persisted on Kuzu `Route` nodes. + # B2a brownfield composition (PR-A3); not persisted on LadybugDB `Route` nodes. source_layer: str = "builtin" @@ -499,8 +499,8 @@ def _hash_file(abs_path: Path) -> str: # ---------- incremental rebuild helpers ---------- -def _load_existing_types(conn: kuzu.Connection, tables: GraphTables, exclude_files: set[str] | None = None) -> None: - """Load type entries from existing Kuzu graph into tables for cross-file resolution. +def _load_existing_types(conn: ladybug.Connection, tables: GraphTables, exclude_files: set[str] | None = None) -> None: + """Load type entries from existing LadybugDB graph into tables for cross-file resolution. When exclude_files is provided, only load types from files NOT in the set. """ @@ -543,8 +543,8 @@ def _load_existing_types(conn: kuzu.Connection, tables: GraphTables, exclude_fil tables.by_package.setdefault(package, []).append(entry) -def _load_existing_members(conn: kuzu.Connection, tables: GraphTables, exclude_files: set[str] | None = None) -> None: - """Load member entries from existing Kuzu graph into tables.members. +def _load_existing_members(conn: ladybug.Connection, tables: GraphTables, exclude_files: set[str] | None = None) -> None: + """Load member entries from existing LadybugDB graph into tables.members. When exclude_files is provided, only load members from files NOT in the set. """ @@ -588,7 +588,7 @@ def _load_existing_members(conn: kuzu.Connection, tables: GraphTables, exclude_f )) -def _find_dependents(conn: kuzu.Connection, changed_node_ids: set[str]) -> set[str]: +def _find_dependents(conn: ladybug.Connection, changed_node_ids: set[str]) -> set[str]: """Find files whose nodes have edges pointing into changed nodes. Returns set of filenames.""" dependent_files: set[str] = set() @@ -612,14 +612,14 @@ def _find_dependents(conn: kuzu.Connection, changed_node_ids: set[str]) -> set[s return dependent_files -def _delete_file_scope(conn: kuzu.Connection, filenames: set[str]) -> None: +def _delete_file_scope(conn: ladybug.Connection, filenames: set[str]) -> None: """Delete all nodes and edges originating from the given files. Skip phantom nodes (filename=""). Deletes ALL edge types in Phase 1, then nodes in subsequent phases. Route/Client/Producer nodes use DETACH DELETE as a safety net for any edges missed in Phase 1. - Edges are deleted in batch across all filenames first to avoid Kuzu + Edges are deleted in batch across all filenames first to avoid LadybugDB "has connected edges" errors when edges from one file point to nodes in another file within the same scope. """ @@ -627,7 +627,7 @@ def _delete_file_scope(conn: kuzu.Connection, filenames: set[str]) -> None: # Phase 1: Delete ALL edges from ALL scope files at once. # This avoids ordering issues where file A has an edge from file B - # pointing into it; if we delete A's nodes before B's edges, Kuzu + # pointing into it; if we delete A's nodes before B's edges, LadybugDB # raises "has connected edges" errors. edge_tables = [ "EXTENDS", "IMPLEMENTS", "INJECTS", "CALLS", "DECLARES", "OVERRIDES", @@ -680,10 +680,10 @@ def _delete_file_scope(conn: kuzu.Connection, filenames: set[str]) -> None: ) -def _scoped_write(conn: kuzu.Connection, tables: GraphTables, *, project_root: Path, meta_chain: dict[str, frozenset[str]] | None) -> None: - """Write nodes and edges to existing Kuzu database without drop/create schema. +def _scoped_write(conn: ladybug.Connection, tables: GraphTables, *, project_root: Path, meta_chain: dict[str, frozenset[str]] | None) -> None: + """Write nodes and edges to existing LadybugDB database without drop/create schema. - Like write_kuzu() but without _drop_all()/_create_schema(). The caller is + Like write_ladybug() but without _drop_all()/_create_schema(). The caller is responsible for calling _populate_declares_rows() and _populate_overrides_rows() before invoking this function. @@ -715,13 +715,13 @@ def _scoped_write(conn: kuzu.Connection, tables: GraphTables, *, project_root: P def _write_nodes_merge( - conn: kuzu.Connection, + conn: ladybug.Connection, tables: GraphTables, *, project_root: Path, meta_chain: dict[str, frozenset[str]] | None, ) -> None: - """Write nodes to existing Kuzu database using MERGE to handle existing nodes.""" + """Write nodes to existing LadybugDB database using MERGE to handle existing nodes.""" _write_nodes_impl(conn, tables, project_root=project_root, meta_chain=meta_chain, symbol_query=_MERGE_SYMBOL) @@ -2664,7 +2664,7 @@ def _micro_factor(member: MemberEntry | None) -> float: ) -# ---------- Kuzu write ---------- +# ---------- LadybugDB write ---------- _SCHEMA_NODE = ( @@ -2685,7 +2685,7 @@ def _micro_factor(member: MemberEntry | None) -> float: "ontology_version INT64, built_at INT64, source_root STRING, " "counts_json STRING, parse_errors INT64, " "routes_total INT64, exposes_total INT64, " - # JSON map {framework: count}; STRING avoids Kuzu Python MAP↔STRUCT binder mismatch. + # JSON map {framework: count}; STRING avoids LadybugDB Python MAP↔STRUCT binder mismatch. "routes_by_framework STRING, " "routes_resolved_pct DOUBLE, " "routes_from_brownfield_pct DOUBLE, " @@ -2798,7 +2798,7 @@ def _micro_factor(member: MemberEntry | None) -> float: ) -def _drop_all(conn: kuzu.Connection) -> None: +def _drop_all(conn: ladybug.Connection) -> None: for stmt in ( "DROP TABLE IF EXISTS DECLARES_CLIENT", "DROP TABLE IF EXISTS DECLARES_PRODUCER", @@ -2825,7 +2825,7 @@ def _drop_all(conn: kuzu.Connection) -> None: pass -def _create_schema(conn: kuzu.Connection) -> None: +def _create_schema(conn: ladybug.Connection) -> None: for stmt in ( _SCHEMA_NODE, _SCHEMA_UNRESOLVED_CALL_SITE, @@ -2885,7 +2885,7 @@ def _node_row(**kwargs) -> dict: def _write_nodes_impl( - conn: kuzu.Connection, + conn: ladybug.Connection, tables: GraphTables, *, project_root: Path, @@ -2952,7 +2952,7 @@ def _write_nodes_impl( def _write_nodes( - conn: kuzu.Connection, + conn: ladybug.Connection, tables: GraphTables, *, project_root: Path, @@ -3064,7 +3064,7 @@ def _direct_supertype_ids(tables: GraphTables, type_id: str) -> list[str]: def _populate_overrides_rows(tables: GraphTables) -> None: """Materialize (subtype_method)-[:OVERRIDES]->(supertype_method) for one supertype hop. - Matches ``KuzuGraph.override_axis_rollup_for`` (direct ``IMPLEMENTS`` / ``EXTENDS`` + Matches ``LadybugDBGraph.override_axis_rollup_for`` (direct ``IMPLEMENTS`` / ``EXTENDS`` only, same ``signature``, distinct method ids, non-static instance methods). """ by_declaring_type: dict[str, list[MemberEntry]] = defaultdict(list) @@ -3099,7 +3099,7 @@ def _build_file_by_node_id(tables: GraphTables) -> dict[str, str]: return lookup -def _write_edges(conn: kuzu.Connection, tables: GraphTables, _file_by_node_id: dict[str, str] | None = None) -> None: +def _write_edges(conn: ladybug.Connection, tables: GraphTables, _file_by_node_id: dict[str, str] | None = None) -> None: # Build node_id -> file_path lookup for source_file resolution. if _file_by_node_id is None: _file_by_node_id = _build_file_by_node_id(tables) @@ -3193,7 +3193,7 @@ def _write_edges(conn: kuzu.Connection, tables: GraphTables, _file_by_node_id: d }) -def _write_routes_and_exposes(conn: kuzu.Connection, tables: GraphTables, _file_by_node_id: dict[str, str] | None = None) -> None: +def _write_routes_and_exposes(conn: ladybug.Connection, tables: GraphTables, _file_by_node_id: dict[str, str] | None = None) -> None: # Build node_id -> file_path lookup for source_file resolution (for Symbol sources). if _file_by_node_id is None: _file_by_node_id = _build_file_by_node_id(tables) @@ -3276,7 +3276,7 @@ def _write_routes_and_exposes(conn: kuzu.Connection, tables: GraphTables, _file_ }) -def _write_meta(conn: kuzu.Connection, tables: GraphTables, source_root: Path) -> None: +def _write_meta(conn: ladybug.Connection, tables: GraphTables, source_root: Path) -> None: seen_calls: set[tuple[str, str, int, int]] = set() calls_unique = 0 for row in tables.calls_rows: @@ -3392,12 +3392,12 @@ def _write_meta(conn: kuzu.Connection, tables: GraphTables, source_root: Path) - def incremental_rebuild( source_root: Path, - kuzu_path: Path, + ladybug_path: Path, *, verbose: bool, expansion_cap: int = 50, ) -> IncrementalResult: - """Incrementally rebuild the Kuzu graph, processing only changed files and their dependents. + """Incrementally rebuild the LadybugDB graph, processing only changed files and their dependents. Returns IncrementalResult with statistics about the rebuild. Falls back to full rebuild if: @@ -3409,7 +3409,7 @@ def incremental_rebuild( t_start = time.time() # Step 1: Load existing graph and detect changes - if not kuzu_path.exists(): + if not ladybug_path.exists(): if verbose: _verbose_stderr_line("[increment] no existing graph; falling back to full rebuild") # Fall back to full rebuild @@ -3420,7 +3420,7 @@ def incremental_rebuild( pass4_routes(tables, asts, source_root=source_root, verbose=verbose) pass5_imperative_edges(tables, asts, source_root=source_root, verbose=verbose) pass6_match_edges(tables, verbose=verbose) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=verbose) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=verbose) return IncrementalResult( mode="full_fallback", @@ -3431,8 +3431,8 @@ def incremental_rebuild( elapsed_sec=time.time() - t_start, ) - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) # Check ontology version try: @@ -3445,7 +3445,7 @@ def incremental_rebuild( _verbose_stderr_line(f"[increment] ontology version {version} < 17; falling back to full rebuild") conn.close() del conn, db - return _fallback_to_full(source_root, kuzu_path, verbose, t_start) + return _fallback_to_full(source_root, ladybug_path, verbose, t_start) except Exception as e: if verbose: _verbose_stderr_line(f"[increment] failed to read ontology version: {e}; falling back to full rebuild") @@ -3454,9 +3454,9 @@ def incremental_rebuild( except Exception: pass del conn, db - return _fallback_to_full(source_root, kuzu_path, verbose, t_start) + return _fallback_to_full(source_root, ladybug_path, verbose, t_start) - index_dir = kuzu_path.parent + index_dir = ladybug_path.parent tracker = FileHashTracker(index_dir) tracker.load() @@ -3488,7 +3488,7 @@ def incremental_rebuild( _verbose_stderr_line("[increment] crash marker exists; falling back to full rebuild") conn.close() crash_marker_path.unlink(missing_ok=True) - return _fallback_to_full(source_root, kuzu_path, verbose, t_start) + return _fallback_to_full(source_root, ladybug_path, verbose, t_start) # Write crash marker crash_marker_path.write_text("", encoding="utf-8") @@ -3516,7 +3516,7 @@ def incremental_rebuild( _verbose_stderr_line(f"[increment] dependent expansion cap ({expansion_cap}) exceeded ({len(scope_files)} files); falling back to full rebuild") conn.close() crash_marker_path.unlink(missing_ok=True) - return _fallback_to_full(source_root, kuzu_path, verbose, t_start) + return _fallback_to_full(source_root, ladybug_path, verbose, t_start) if verbose: _verbose_stderr_line(f"[increment] processing {len(scope_files)} files ({len(changed_files)} changed + {len(dependent_files)} dependents)") @@ -3612,12 +3612,12 @@ def incremental_rebuild( _verbose_stderr_line(f"[increment] error during incremental rebuild: {e}; falling back to full rebuild") conn.close() crash_marker_path.unlink(missing_ok=True) - return _fallback_to_full(source_root, kuzu_path, verbose, t_start) + return _fallback_to_full(source_root, ladybug_path, verbose, t_start) -def _init_hash_tracker(source_root: Path, kuzu_path: Path) -> int: +def _init_hash_tracker(source_root: Path, ladybug_path: Path) -> int: """Initialize hash tracker for all Java files. Returns number of files hashed.""" - index_dir = kuzu_path.parent + index_dir = ladybug_path.parent tracker = FileHashTracker(index_dir) tracker.load() ignore = LayeredIgnore(source_root) @@ -3635,7 +3635,7 @@ def _init_hash_tracker(source_root: Path, kuzu_path: Path) -> int: return len(all_files) -def _fallback_to_full(source_root: Path, kuzu_path: Path, verbose: bool, t_start: float) -> IncrementalResult: +def _fallback_to_full(source_root: Path, ladybug_path: Path, verbose: bool, t_start: float) -> IncrementalResult: """Fallback to full rebuild.""" tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=verbose) @@ -3644,7 +3644,7 @@ def _fallback_to_full(source_root: Path, kuzu_path: Path, verbose: bool, t_start pass4_routes(tables, asts, source_root=source_root, verbose=verbose) pass5_imperative_edges(tables, asts, source_root=source_root, verbose=verbose) pass6_match_edges(tables, verbose=verbose) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=verbose) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=verbose) return IncrementalResult( mode="full_fallback", @@ -3656,12 +3656,12 @@ def _fallback_to_full(source_root: Path, kuzu_path: Path, verbose: bool, t_start ) -def _write_clients_producers_and_calls(conn: kuzu.Connection, tables: GraphTables) -> None: - """Write Route, Client, Producer, and cross-service edges to Kuzu. +def _write_clients_producers_and_calls(conn: ladybug.Connection, tables: GraphTables) -> None: + """Write Route, Client, Producer, and cross-service edges to LadybugDB. Used by the incremental rebuild's global pass 5-6 step. Writes phantom Route nodes (created by pass5 for cross-service calls) that wouldn't - otherwise exist in Kuzu. + otherwise exist in LadybugDB. """ # Write phantom routes that don't already exist (pass5 creates these for cross-service calls) for row in tables.routes_rows: @@ -3739,7 +3739,7 @@ def _write_clients_producers_and_calls(conn: kuzu.Connection, tables: GraphTable }) -def write_kuzu( +def write_ladybug( db_path: Path, tables: GraphTables, *, @@ -3755,8 +3755,8 @@ def write_kuzu( _verbose_stderr_line(_WRITE_START) with _VerbosePassHeartbeats("[graph] writing", verbose=verbose): db_path.parent.mkdir(parents=True, exist_ok=True) - db = kuzu.Database(str(db_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path)) + conn = ladybug.Connection(db) _drop_all(conn) _create_schema(conn) t0 = time.time() @@ -3787,22 +3787,22 @@ def write_kuzu( # ---------- CLI ---------- -def _default_kuzu_path() -> Path: +def _default_ladybug_path() -> Path: idx = os.environ.get("JAVA_CODEBASE_RAG_INDEX_DIR", "").strip() if idx and not idx.startswith(("s3://", "gs://", "az://")): - return Path(os.path.expanduser(idx.rstrip("/"))) / "code_graph.kuzu" - return Path.cwd() / ".java-codebase-rag" / "code_graph.kuzu" + return Path(os.path.expanduser(idx.rstrip("/"))) / "code_graph.lbug" + return Path.cwd() / ".java-codebase-rag" / "code_graph.lbug" def main() -> int: - parser = argparse.ArgumentParser(description="Build an AST-derived Kuzu graph for Java sources.") + parser = argparse.ArgumentParser(description="Build an AST-derived LadybugDB graph for Java sources.") parser.add_argument("--source-root", default=None, help="Repository / monorepo root to scan for .java (defaults to current working directory)") parser.add_argument( - "--kuzu-path", + "--ladybug-path", default=None, help=( - "Kuzu database path (file/dir as used by kuzu.Database; " - "default: $JAVA_CODEBASE_RAG_INDEX_DIR/code_graph.kuzu or ./.java-codebase-rag/code_graph.kuzu)" + "LadybugDB database path (file/dir as used by ladybug.Database; " + "default: $JAVA_CODEBASE_RAG_INDEX_DIR/code_graph.lbug or ./.java-codebase-rag/code_graph.lbug)" ), ) parser.add_argument("--verbose", action="store_true") @@ -3814,10 +3814,10 @@ def main() -> int: print(f"source-root not a directory: {root}", file=sys.stderr) return 2 - kuzu_path = Path(args.kuzu_path).expanduser() if args.kuzu_path else _default_kuzu_path() + ladybug_path = Path(args.ladybug_path).expanduser() if args.ladybug_path else _default_ladybug_path() if args.incremental: - result = incremental_rebuild(root, kuzu_path, verbose=args.verbose) + result = incremental_rebuild(root, ladybug_path, verbose=args.verbose) print(json.dumps({ "mode": result.mode, "files_changed": result.files_changed, @@ -3837,9 +3837,9 @@ def main() -> int: pass4_routes(tables, asts, source_root=root, verbose=args.verbose) pass5_imperative_edges(tables, asts, source_root=root, verbose=args.verbose) pass6_match_edges(tables, verbose=args.verbose) - write_kuzu(kuzu_path, tables, source_root=root, verbose=args.verbose) + write_ladybug(ladybug_path, tables, source_root=root, verbose=args.verbose) if args.verbose: - _verbose_stderr_line(f"[graph] done · kuzu at {kuzu_path}") + _verbose_stderr_line(f"[graph] done · ladybug at {ladybug_path}") return 0 diff --git a/graph_enrich.py b/graph_enrich.py index c2d7b70d..02119b27 100644 --- a/graph_enrich.py +++ b/graph_enrich.py @@ -334,7 +334,7 @@ def collect_annotation_meta_chain( ) -> dict[str, frozenset[str]]: """Map annotation simple name → built-in simple names reachable via meta-annotations. - Single source of truth for Layer A: both the Kuzu writer and Lance chunk + Single source of truth for Layer A: both the LadybugDB writer and Lance chunk enrichment must use this; they must not derive `meta_chain` from separate filesystem walks. See ``PLAN-BROWNFIELD-ROLE-OVERRIDES`` § *Single source of truth (REQUIRED — read before implementation)*. @@ -350,7 +350,7 @@ def annotation_meta_decls_from_graph_tables( """From `build_ast_graph.GraphTables.types`, map @interface simple name -> meta anns. Used for diagnostics; Layer A in production uses `collect_annotation_meta_chain` - (disk) so Kuzu and Lance share one index. + (disk) so LadybugDB and Lance share one index. """ decls: dict[str, tuple[str, ...]] = {} first_fqn: dict[str, str] = {} @@ -1702,7 +1702,7 @@ def enrich_chunk( def symbol_id(kind: str, fqn: str, file_path: str = "", start_byte: int = 0) -> str: - """Deterministic SHA1-based id for Kuzu Symbol nodes.""" + """Deterministic SHA1-based id for LadybugDB Symbol nodes.""" key = f"{kind}|{fqn}|{file_path}|{start_byte}".encode("utf-8") return hashlib.sha1(key).hexdigest() diff --git a/java_codebase_rag/cli.py b/java_codebase_rag/cli.py index 555e89cc..b288655e 100644 --- a/java_codebase_rag/cli.py +++ b/java_codebase_rag/cli.py @@ -24,10 +24,10 @@ from java_codebase_rag.pipeline import clip, run_build_ast_graph, run_cocoindex_drop, run_cocoindex_update, run_incremental_graph from java_ontology import VALID_UNRESOLVED_CALL_REASONS -KUZU_INCREMENTAL_TRACKING_ISSUE_URL = "https://github.com/HumanBean17/java-codebase-rag/issues/73" +LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL = "https://github.com/HumanBean17/java-codebase-rag/issues/73" _INCREMENT_WARNING_LINES = ( - "WARNING: AST graph (Kuzu) incremental rebuild is not yet implemented.", + "WARNING: AST graph (LadybugDB) incremental rebuild is not yet implemented.", "The graph reflects the index state from the last `init` or `reprocess`,", "which means `find`, `neighbors`, and `describe` may return stale results", "for files changed since then.", @@ -37,8 +37,8 @@ "For an up-to-date graph, run:", " java-codebase-rag reprocess", "", - "Track progress on Kuzu incremental rebuild:", - f" {KUZU_INCREMENTAL_TRACKING_ISSUE_URL}", + "Track progress on LadybugDB incremental rebuild:", + f" {LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL}", ) _REFRESH_DEPRECATION = ( @@ -47,7 +47,7 @@ ) _REPROCESS_DRIFT_VECTORS_ONLY = ( - "java-codebase-rag reprocess: rebuilt vectors only; graph (code_graph.kuzu) was NOT rebuilt " + "java-codebase-rag reprocess: rebuilt vectors only; graph (code_graph.lbug) was NOT rebuilt " "and may now reflect a stale source snapshot." ) @@ -178,7 +178,7 @@ def _emit(value: Any) -> None: print(json.dumps(payload, default=_jsonable, sort_keys=True, indent=None)) -def _emit_increment_kuzu_warning() -> None: +def _emit_increment_ladybug_warning() -> None: for line in _INCREMENT_WARNING_LINES: print(line, file=sys.stderr) @@ -289,7 +289,7 @@ def work() -> int: print(file=sys.stderr, flush=True) g = run_build_ast_graph( source_root=cfg.source_root, - kuzu_path=cfg.kuzu_path, + ladybug_path=cfg.ladybug_path, verbose=verbose, quiet=bool(args.quiet), env=env, @@ -319,7 +319,7 @@ def _cmd_increment(args: argparse.Namespace) -> int: # Check for --vectors-only flag vectors_only = bool(getattr(args, "vectors_only", False)) if vectors_only: - _emit_increment_kuzu_warning() + _emit_increment_ladybug_warning() def work() -> int: env = cfg.subprocess_env() @@ -350,7 +350,7 @@ def work() -> int: # Run incremental graph update g = run_incremental_graph( source_root=cfg.source_root, - kuzu_path=cfg.kuzu_path, + ladybug_path=cfg.ladybug_path, verbose=bool(args.verbose), quiet=bool(args.quiet), env=env, @@ -437,7 +437,7 @@ def work() -> int: if graph_only: g = run_build_ast_graph( source_root=cfg.source_root, - kuzu_path=cfg.kuzu_path, + ladybug_path=cfg.ladybug_path, verbose=verbose, quiet=bool(args.quiet), env=env, @@ -509,7 +509,7 @@ def _cmd_erase(args: argparse.Namespace) -> int: cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - to_describe: list[Path] = [cfg.kuzu_path, cfg.cocoindex_db] + to_describe: list[Path] = [cfg.ladybug_path, cfg.cocoindex_db] if cfg.index_dir.is_dir(): try: import lancedb @@ -546,8 +546,8 @@ def work() -> int: ) elif drop.returncode != 0: print(clip(drop.stderr, 4000), file=sys.stderr) - if cfg.kuzu_path.exists(): - shutil.rmtree(cfg.kuzu_path, ignore_errors=True) + if cfg.ladybug_path.exists(): + shutil.rmtree(cfg.ladybug_path, ignore_errors=True) if cfg.cocoindex_db.exists(): try: cfg.cocoindex_db.unlink() @@ -577,17 +577,17 @@ def _cmd_meta(args: argparse.Namespace) -> int: cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - from kuzu_queries import KuzuGraph # lazy + from ladybug_queries import LadybugGraph # lazy - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None payload = server._graph_meta_output().model_dump() payload["embedding_model"] = cfg.embedding_model payload["embedding_device"] = cfg.embedding_device payload["embedding_model_source"] = cfg.embedding_model_source payload["embedding_device_source"] = cfg.embedding_device_source payload["index_dir"] = str(cfg.index_dir.resolve()) - payload["kuzu_path"] = str(cfg.kuzu_path.resolve()) + payload["ladybug_path"] = str(cfg.ladybug_path.resolve()) payload["index_dir_source"] = cfg.index_dir_source payload["hints_enabled"] = cfg.hints_enabled payload["hints_enabled_source"] = cfg.hints_enabled_source @@ -637,12 +637,12 @@ def _cmd_unresolved_calls_list(args: argparse.Namespace) -> int: cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - from kuzu_queries import KuzuGraph # lazy + from ladybug_queries import LadybugGraph # lazy - if not KuzuGraph.exists(): + if not LadybugGraph.exists(): _emit({"success": False, "message": "Kuzu graph not found"}) return 1 - graph = KuzuGraph.get() + graph = LadybugGraph.get() rows = graph.list_unresolved_call_sites( method_id=args.method_id, reason=args.reason, @@ -658,12 +658,12 @@ def _cmd_unresolved_calls_stats(args: argparse.Namespace) -> int: cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - from kuzu_queries import KuzuGraph # lazy + from ladybug_queries import LadybugGraph # lazy - if not KuzuGraph.exists(): + if not LadybugGraph.exists(): _emit({"success": False, "message": "Kuzu graph not found"}) return 1 - graph = KuzuGraph.get() + graph = LadybugGraph.get() buckets = graph.stats_unresolved_call_sites(by=args.by) total = sum(int(r.get("n") or 0) for r in buckets) _emit({"success": True, "total": total, "by": args.by, "buckets": buckets}) @@ -683,12 +683,12 @@ def _cmd_analyze_pr(args: argparse.Namespace) -> int: _emit({"success": False, "message": "Diff is empty"}) return 1 import pr_analysis # lazy - from kuzu_queries import KuzuGraph # lazy + from ladybug_queries import LadybugGraph # lazy - if not KuzuGraph.exists(): + if not LadybugGraph.exists(): _emit({"success": False, "message": "Kuzu graph not found"}) return 1 - graph = KuzuGraph.get() + graph = LadybugGraph.get() report = pr_analysis.analyze_pr_pipeline(graph, diff_text) _emit(pr_analysis.pr_report_to_dict(report)) return 0 diff --git a/java_codebase_rag/config.py b/java_codebase_rag/config.py index 3504fbd2..bb05d9a7 100644 --- a/java_codebase_rag/config.py +++ b/java_codebase_rag/config.py @@ -67,10 +67,9 @@ def resolved_sbert_model_for_process_env(import_time_default: str) -> str: # Legacy env keys: never honored; detection-only hints name the replacement (if any). _LEGACY_ENV_HINTS: tuple[tuple[str, str], ...] = ( ("LANCEDB_URI", "JAVA_CODEBASE_RAG_INDEX_DIR"), - ("KUZU_DB_PATH", "JAVA_CODEBASE_RAG_INDEX_DIR (Kuzu lives at /code_graph.kuzu)"), ("LANCEDB_MCP_PROJECT_ROOT", "cwd or --source-root (no env replacement)"), ("LANCEDB_MCP_ALLOW_REFRESH", "(removed; use init / increment / reprocess / erase)"), - ("LANCEDB_MCP_GRAPH_ENABLED", "(removed; graph is used when code_graph.kuzu exists)"), + ("LANCEDB_MCP_GRAPH_ENABLED", "(removed; graph is used when code_graph.lbug exists)"), ("LANCEDB_MCP_MICROSERVICE_ROOTS", "microservice_roots: in .java-codebase-rag.yml"), ("LANCEDB_MCP_DEBUG_CONTEXT", ENV_DEBUG_CONTEXT), ("LANCEDB_MCP_RUN_HEAVY", ENV_RUN_HEAVY), @@ -182,7 +181,7 @@ def load_yaml_mapping(source_root: Path) -> dict[str, Any]: class ResolvedOperatorConfig: source_root: Path index_dir: Path - kuzu_path: Path + ladybug_path: Path cocoindex_db: Path embedding_model: str embedding_device: str | None @@ -193,7 +192,7 @@ class ResolvedOperatorConfig: hints_enabled_source: SettingSource def apply_to_os_environ(self) -> None: - """Make downstream modules (server, kuzu_queries, flows) see a consistent environment. + """Make downstream modules (server, ladybug_queries, flows) see a consistent environment. When ``embedding_device`` is unset, ``SBERT_DEVICE`` is not removed from ``os.environ`` so a long-lived host process is not mutated for unrelated callers; subprocesses still use @@ -369,12 +368,12 @@ def resolve_operator_config( yaml_path=("hints", "enabled"), default=True, ) - ku = index_dir / "code_graph.kuzu" + ku = index_dir / "code_graph.lbug" coco = index_dir / "cocoindex.db" return ResolvedOperatorConfig( source_root=root, index_dir=index_dir, - kuzu_path=ku, + ladybug_path=ku, cocoindex_db=coco, embedding_model=model, embedding_device=device, @@ -387,9 +386,9 @@ def resolve_operator_config( def index_dir_has_existing_artifacts(index_dir: Path) -> tuple[bool, list[str]]: - """True if Kuzu graph dir or any Lance table already exists under index_dir.""" + """True if graph dir or any Lance table already exists under index_dir.""" paths: list[str] = [] - ku = index_dir / "code_graph.kuzu" + ku = index_dir / "code_graph.lbug" if ku.exists(): paths.append(str(ku.resolve())) if index_dir.is_dir(): diff --git a/java_codebase_rag/installer.py b/java_codebase_rag/installer.py index dad4264d..2dcfc649 100644 --- a/java_codebase_rag/installer.py +++ b/java_codebase_rag/installer.py @@ -791,7 +791,7 @@ def run_init_if_needed( # Run AST graph build g = run_build_ast_graph( source_root=cfg.source_root, - kuzu_path=cfg.kuzu_path, + ladybug_path=cfg.ladybug_path, verbose=not quiet, quiet=quiet, env=env, diff --git a/java_codebase_rag/pipeline.py b/java_codebase_rag/pipeline.py index bc2d172e..f37bfe81 100644 --- a/java_codebase_rag/pipeline.py +++ b/java_codebase_rag/pipeline.py @@ -201,7 +201,7 @@ def run_cocoindex_drop(env: dict[str, str], *, quiet: bool) -> subprocess.Comple def run_build_ast_graph( *, source_root: Path, - kuzu_path: Path, + ladybug_path: Path, verbose: bool, quiet: bool = False, env: dict[str, str] | None = None, @@ -219,8 +219,8 @@ def run_build_ast_graph( str(builder), "--source-root", str(source_root), - "--kuzu-path", - str(kuzu_path), + "--ladybug-path", + str(ladybug_path), ] # Three-tier: --quiet (silent) / default (filtered progress) / --verbose (raw). # Default passes --verbose so the builder emits per-pass progress lines, @@ -254,7 +254,7 @@ def run_build_ast_graph( def run_incremental_graph( *, source_root: Path, - kuzu_path: Path, + ladybug_path: Path, verbose: bool, quiet: bool = False, env: dict[str, str] | None = None, @@ -273,8 +273,8 @@ def run_incremental_graph( str(builder), "--source-root", str(source_root), - "--kuzu-path", - str(kuzu_path), + "--ladybug-path", + str(ladybug_path), "--incremental", ] # Three-tier: --quiet (silent) / default (filtered progress) / --verbose (raw). diff --git a/java_index_flow_lancedb.py b/java_index_flow_lancedb.py index a89f19b1..91af5b9a 100644 --- a/java_index_flow_lancedb.py +++ b/java_index_flow_lancedb.py @@ -4,7 +4,7 @@ LanceDB requires a single primary key per table; each chunk gets a UUID `id`. Environment: - JAVA_CODEBASE_RAG_INDEX_DIR — Lance tables + Kuzu + cocoindex state (default: ./.java-codebase-rag) + JAVA_CODEBASE_RAG_INDEX_DIR — Lance tables + LadybugDB + cocoindex state (default: ./.java-codebase-rag) JAVA_CODEBASE_RAG_SOURCE_ROOT — Java repo root for indexing (optional; else cocoindex cwd) SBERT_MODEL / SBERT_DEVICE — embedding (optional; YAML also supported via java-codebase-rag CLI) diff --git a/java_ontology.py b/java_ontology.py index 2f170129..140c459a 100644 --- a/java_ontology.py +++ b/java_ontology.py @@ -141,7 +141,7 @@ @dataclass(frozen=True) class EdgeAttr: name: str - kuzu_type: str + graph_type: str purpose: str diff --git a/kuzu_queries.py b/ladybug_queries.py similarity index 96% rename from kuzu_queries.py rename to ladybug_queries.py index 67c9e16c..77ae32bd 100644 --- a/kuzu_queries.py +++ b/ladybug_queries.py @@ -1,9 +1,9 @@ -"""Read-only Cypher helpers over the Kuzu AST graph built by `build_ast_graph.py`. +"""Read-only Cypher helpers over the Ladybug AST graph built by `build_ast_graph.py`. -Each function opens a Kuzu connection on demand and returns plain JSON-ish dicts +Each function opens a Ladybug connection on demand and returns plain JSON-ish dicts so the MCP server can serialize them without further mapping. -The Kuzu database is opened read-only and cached per-process. This module is +The Ladybug database is opened read-only and cached per-process. This module is intentionally dependency-light: nothing here imports LanceDB or sentence-transformers. Cypher pitfalls (see also ``AGENTS.md``): avoid ``label(e) IN $list`` in ``WHERE`` for @@ -16,17 +16,37 @@ import json import logging import os +import re import threading from dataclasses import asdict, dataclass from pathlib import Path from typing import Any, Literal -import kuzu +import ladybug from ast_java import ONTOLOGY_VERSION as _ONTOLOGY_VERSION log = logging.getLogger(__name__) + +def _parse_ladybug_json(raw: str | None) -> dict[str, Any]: + """Parse JSON from LadybugDB which returns unquoted keys like {key: value}.""" + if not raw: + return {} + # LadybugDB returns JSON without quotes around keys: {packages: 1, files: 2} + # Convert to standard JSON: {"packages": 1, "files": 2} + # This regex matches word characters followed by ':' at the start of a key + quoted = re.sub(r'(\w+):', r'"\1":', raw) + try: + return json.loads(quoted) + except Exception: + try: + # Fallback: try parsing as-is (for standard JSON) + return json.loads(raw) + except Exception: + log.warning("Failed to parse counts_json: %s", raw[:100]) + return {} + # Composed describe / neighbors dot-keys (not stored graph edge labels). _MEMBER_EDGE_COMPOSED_REL_MAP: tuple[tuple[str, str], ...] = ( ("DECLARES.DECLARES_CLIENT", "DECLARES_CLIENT"), @@ -46,7 +66,7 @@ def _coerce_id_list(raw: Any) -> list[str]: - """Normalize Kuzu ``collect(DISTINCT ...)`` list results to string ids.""" + """Normalize Ladybug ``collect(DISTINCT ...)`` list results to string ids.""" if raw is None: return [] if isinstance(raw, list): @@ -56,8 +76,8 @@ def _coerce_id_list(raw: Any) -> list[str]: __all__ = [ - "KuzuGraph", - "resolve_kuzu_path", + "LadybugGraph", + "resolve_ladybug_path", "SymbolHit", "EdgeHit", "CallEdge", @@ -68,14 +88,14 @@ def _coerce_id_list(raw: Any) -> list[str]: ] -def resolve_kuzu_path(explicit: str | None = None) -> str: - """Resolve the Kuzu DB path the same way the builder does.""" +def resolve_ladybug_path(explicit: str | None = None) -> str: + """Resolve the Ladybug DB path the same way the builder does.""" if explicit: return str(Path(explicit).expanduser()) idx = os.environ.get("JAVA_CODEBASE_RAG_INDEX_DIR", "").strip() if idx and not idx.startswith(("s3://", "gs://", "az://")): - return str(Path(os.path.expanduser(idx.rstrip("/"))) / "code_graph.kuzu") - return str((Path.cwd() / ".java-codebase-rag" / "code_graph.kuzu").resolve()) + return str(Path(os.path.expanduser(idx.rstrip("/"))) / "code_graph.lbug") + return str((Path.cwd() / ".java-codebase-rag" / "code_graph.lbug").resolve()) @dataclass @@ -165,10 +185,10 @@ class RouteCaller: def _symbol_return_for(alias: str) -> str: - """Kuzu RETURN projection for Symbol properties, using the given node alias. + """Ladybug RETURN projection for Symbol properties, using the given node alias. Centralised so queries that bind Symbol under a non-`s` alias (e.g. `n` in - graph-expansion / flow-tracing) don't emit `s.*` references that Kuzu + graph-expansion / flow-tracing) don't emit `s.*` references that Ladybug rejects with `Variable s is not in scope`. """ return ( @@ -198,7 +218,7 @@ def _scope_filters( Mutates `params` to bind `$module` / `$microservice` only when the corresponding filter is set, so unused names don't leak into the - Kuzu plan. + Ladybug plan. """ out: list[str] = [] if module: @@ -274,7 +294,7 @@ def _row_to_symbol(row: dict[str, Any]) -> SymbolHit: def find_symbols_in_file_range( - graph: "KuzuGraph", + graph: "LadybugGraph", *, filename: str, start_line: int, @@ -324,25 +344,25 @@ def _call_graph_needle_phantom_arity_alt(needle: str) -> str | None: return needle[:i] + "(?)" -class KuzuGraph: - """Thin wrapper around a read-only Kuzu connection. +class LadybugGraph: + """Thin wrapper around a read-only Ladybug connection. Safe to share across threads: we hold a single `Connection`, guarded by a lock. """ _lock = threading.Lock() - _instance: "KuzuGraph | None" = None + _instance: "LadybugGraph | None" = None _instance_path: str | None = None def __init__(self, db_path: str) -> None: self.db_path = db_path - self._db = kuzu.Database(db_path, read_only=True) - self._conn = kuzu.Connection(self._db) + self._db = ladybug.Database(db_path, read_only=True) + self._conn = ladybug.Connection(self._db) self._conn_lock = threading.Lock() @classmethod - def get(cls, db_path: str | None = None) -> "KuzuGraph": - resolved = resolve_kuzu_path(db_path) + def get(cls, db_path: str | None = None) -> "LadybugGraph": + resolved = resolve_ladybug_path(db_path) with cls._lock: if cls._instance is None or cls._instance_path != resolved: instance = cls(resolved) @@ -354,7 +374,7 @@ def get(cls, db_path: str | None = None) -> "KuzuGraph": f"required version {_ONTOLOGY_VERSION}. " "Rebuild the graph: `python build_ast_graph.py --source-root `, " "or run `java-codebase-rag reprocess --source-root ` for a full " - "Lance+Kuzu re-index." + "Lance+Ladybug re-index." ) cls._instance = instance cls._instance_path = resolved @@ -362,11 +382,11 @@ def get(cls, db_path: str | None = None) -> "KuzuGraph": @classmethod def exists(cls, db_path: str | None = None) -> bool: - resolved = resolve_kuzu_path(db_path) + resolved = resolve_ladybug_path(db_path) p = Path(resolved) if not p.exists(): return False - # Kuzu represents DB as a directory; allow file form too (single-file DBs). + # Ladybug represents DB as a directory; allow file form too (single-file DBs). return True # ---- low-level ---- @@ -481,11 +501,15 @@ def meta(self) -> dict[str, Any]: if not rows: return {"error": "no GraphMeta node"} row = rows[0] - counts: dict[str, Any] - try: - counts = json.loads(row.get("counts_json") or "{}") - except Exception: - counts = {} + counts: dict[str, Any] = _parse_ladybug_json(row.get("counts_json")) + # Ensure counts has expected keys even if empty + if not counts: + counts = { + "packages": 0, "files": 0, "types": 0, "members": 0, "phantoms": 0, + "extends": 0, "implements": 0, "injects": 0, "declares": 0, "overrides": 0, + "calls": 0, "routes": 0, "exposes": 0, "clients": 0, "declares_client": 0, + "producers": 0, "declares_producer": 0, "http_calls": 0, "async_calls": 0, + } routes_total = exposes_total = 0 routes_resolved_pct = 0.0 routes_by_framework: dict[str, Any] = {} @@ -507,10 +531,7 @@ def meta(self) -> dict[str, Any]: cross_service_resolution: str | None = None if meta_mode != "legacy": rfw_raw = row.get("routes_by_framework") or "{}" - try: - routes_by_framework = json.loads(rfw_raw) if isinstance(rfw_raw, str) else (rfw_raw or {}) - except Exception: - routes_by_framework = {} + routes_by_framework = _parse_ladybug_json(rfw_raw) if isinstance(rfw_raw, str) else (rfw_raw or {}) if not isinstance(routes_by_framework, dict): routes_by_framework = {} routes_total = int(row.get("routes_total") or 0) @@ -519,26 +540,17 @@ def meta(self) -> dict[str, Any]: if meta_mode in ("pr_f1", "pr_e3", "pre_e3"): routes_from_brownfield_pct = float(row.get("routes_from_brownfield_pct") or 0.0) rbl_raw = row.get("routes_by_layer") or "{}" - try: - routes_by_layer = json.loads(rbl_raw) if isinstance(rbl_raw, str) else (rbl_raw or {}) - except Exception: - routes_by_layer = {} + routes_by_layer = _parse_ladybug_json(rbl_raw) if isinstance(rbl_raw, str) else (rbl_raw or {}) if not isinstance(routes_by_layer, dict): routes_by_layer = {} http_calls_total = int(row.get("http_calls_total") or 0) async_calls_total = int(row.get("async_calls_total") or 0) hbs_raw = row.get("http_calls_by_strategy") or "{}" abs_raw = row.get("async_calls_by_strategy") or "{}" - try: - http_calls_by_strategy = json.loads(hbs_raw) if isinstance(hbs_raw, str) else (hbs_raw or {}) - except Exception: - http_calls_by_strategy = {} + http_calls_by_strategy = _parse_ladybug_json(hbs_raw) if isinstance(hbs_raw, str) else (hbs_raw or {}) if not isinstance(http_calls_by_strategy, dict): http_calls_by_strategy = {} - try: - async_calls_by_strategy = json.loads(abs_raw) if isinstance(abs_raw, str) else (abs_raw or {}) - except Exception: - async_calls_by_strategy = {} + async_calls_by_strategy = _parse_ladybug_json(abs_raw) if isinstance(abs_raw, str) else (abs_raw or {}) if not isinstance(async_calls_by_strategy, dict): async_calls_by_strategy = {} http_calls_resolved_pct = float(row.get("http_calls_resolved_pct") or 0.0) @@ -547,16 +559,10 @@ def meta(self) -> dict[str, Any]: async_producers_from_brownfield_pct = float(row.get("async_producers_from_brownfield_pct") or 0.0) hmb_raw = row.get("http_calls_match_breakdown") or "{}" amb_raw = row.get("async_calls_match_breakdown") or "{}" - try: - http_calls_match_breakdown = json.loads(hmb_raw) if isinstance(hmb_raw, str) else (hmb_raw or {}) - except Exception: - http_calls_match_breakdown = {} + http_calls_match_breakdown = _parse_ladybug_json(hmb_raw) if isinstance(hmb_raw, str) else (hmb_raw or {}) if not isinstance(http_calls_match_breakdown, dict): http_calls_match_breakdown = {} - try: - async_calls_match_breakdown = json.loads(amb_raw) if isinstance(amb_raw, str) else (amb_raw or {}) - except Exception: - async_calls_match_breakdown = {} + async_calls_match_breakdown = _parse_ladybug_json(amb_raw) if isinstance(amb_raw, str) else (amb_raw or {}) if not isinstance(async_calls_match_breakdown, dict): async_calls_match_breakdown = {} cross_service_calls_total = int(row.get("cross_service_calls_total") or 0) @@ -1013,7 +1019,7 @@ def list_by_annotation(self, annotation: str, *, module: str | None = None, microservice: str | None = None, capability: str | None = None, limit: int = 100) -> list[SymbolHit]: - # Kuzu supports `list_contains` for STRING[]. + # Ladybug supports `list_contains` for STRING[]. filters = ["list_contains(s.annotations, $ann)"] params: dict[str, Any] = {"ann": annotation} if capability: @@ -1454,7 +1460,7 @@ def _run_seed_query(entry_roles: tuple[str, ...] | None) -> list[SymbolHit]: )) if entry_roles: params["entry_roles"] = list(entry_roles) - # Kuzu 0.11.x does not support parameterized lists inside ANY + # Ladybug 0.17.x does not support parameterized lists inside ANY # comprehensions, so we expand the fixed capability set as # individual list_contains predicates ORed together. cap_predicates = " OR ".join( diff --git a/mcp_v2.py b/mcp_v2.py index 3828211e..597bdd3e 100644 --- a/mcp_v2.py +++ b/mcp_v2.py @@ -30,7 +30,7 @@ from index_common import SBERT_MODEL from java_codebase_rag.config import resolved_sbert_model_for_process_env from java_ontology import EDGE_SCHEMA, ResolveReason -from kuzu_queries import KuzuGraph, OVERRIDE_AXIS_COMPOSED_EDGE_TYPES +from ladybug_queries import LadybugGraph, OVERRIDE_AXIS_COMPOSED_EDGE_TYPES from mcp_hints import generate_hints, MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION from search_lancedb import TABLES, run_search @@ -604,7 +604,7 @@ def _node_kind_from_id( def _resolve_node_kind( - graph: KuzuGraph, + graph: LadybugGraph, node_id: str, ) -> Literal["symbol", "route", "client", "producer", "unresolved_call_site"]: try: @@ -733,7 +733,7 @@ def _node_ref_from_row(kind: Literal["symbol", "route", "client", "producer"], r def _load_node_record( - graph: KuzuGraph, node_id: str, kind: Literal["symbol", "route", "client", "producer"], + graph: LadybugGraph, node_id: str, kind: Literal["symbol", "route", "client", "producer"], ) -> dict[str, Any] | None: if kind == "symbol": projection = ( @@ -807,7 +807,7 @@ def _merge_overrides_edge_summary( def _edge_summary_for_node( - graph: KuzuGraph, node_id: str, *, kind: str, row: dict[str, Any] + graph: LadybugGraph, node_id: str, *, kind: str, row: dict[str, Any] ) -> dict[str, dict[str, int]]: summary = dict(graph.edge_counts_for(node_id)) sym_kind = str(row.get("kind") or "") @@ -887,7 +887,7 @@ def search_v2( offset: int = 0, path_contains: str | None = None, filter: NodeFilter | dict[str, Any] | str | None = None, - graph: KuzuGraph | None = None, + graph: LadybugGraph | None = None, ) -> SearchOutput: try: raw_filter = _coerce_filter(filter) @@ -967,10 +967,10 @@ def find_v2( filter: NodeFilter | dict[str, Any] | str, limit: int = 25, offset: int = 0, - graph: KuzuGraph | None = None, + graph: LadybugGraph | None = None, ) -> FindOutput: try: - g = graph or KuzuGraph.get() + g = graph or LadybugGraph.get() raw_filter = _coerce_filter(filter) if raw_filter is None: raw_filter = {} @@ -1063,10 +1063,10 @@ def find_v2( def describe_v2( id: str | None = None, fqn: str | None = None, - graph: KuzuGraph | None = None, + graph: LadybugGraph | None = None, ) -> DescribeOutput: try: - g = graph or KuzuGraph.get() + g = graph or LadybugGraph.get() has_id = bool(id and str(id).strip()) has_fqn = bool(fqn and str(fqn).strip()) if not has_id and not has_fqn: @@ -1171,7 +1171,7 @@ def _resolve_parse_microservice_route(identifier: str) -> tuple[str, str, str] | def _resolve_symbol_candidates( - g: KuzuGraph, + g: LadybugGraph, identifier: str, ) -> list[tuple[NodeRef, ResolveReason, int]]: out: list[tuple[NodeRef, ResolveReason, int]] = [] @@ -1213,7 +1213,7 @@ def _resolve_symbol_candidates( def _resolve_route_candidates( - g: KuzuGraph, + g: LadybugGraph, identifier: str, ) -> list[tuple[NodeRef, ResolveReason, int]]: out: list[tuple[NodeRef, ResolveReason, int]] = [] @@ -1265,7 +1265,7 @@ def _resolve_route_candidates( def _resolve_client_candidates( - g: KuzuGraph, + g: LadybugGraph, identifier: str, ) -> list[tuple[NodeRef, ResolveReason, int]]: out: list[tuple[NodeRef, ResolveReason, int]] = [] @@ -1304,7 +1304,7 @@ def _resolve_client_candidates( def _resolve_producer_candidates( - g: KuzuGraph, + g: LadybugGraph, identifier: str, ) -> list[tuple[NodeRef, ResolveReason, int]]: out: list[tuple[NodeRef, ResolveReason, int]] = [] @@ -1462,7 +1462,7 @@ def _resolve_finalize_success( def resolve_v2( identifier: str, hint_kind: Literal["symbol", "route", "client", "producer"] | None = None, - graph: KuzuGraph | None = None, + graph: LadybugGraph | None = None, ) -> ResolveOutput: try: trimmed, err = _resolve_validate_identifier(identifier) @@ -1481,7 +1481,7 @@ def resolve_v2( if "*" in trimmed or "?" in trimmed: return _resolve_finalize_success(trimmed, hint_kind, []) - g = graph or KuzuGraph.get() + g = graph or LadybugGraph.get() raw: list[tuple[NodeRef, ResolveReason, int]] = [] for kind in _resolve_kinds_to_search(hint_kind): if kind == "symbol": @@ -1726,7 +1726,7 @@ def neighbors_v2( declares_composed = [k for k in composed_keys if k in _MEMBER_COMPOSED_EDGE_TYPES] override_composed = [k for k in composed_keys if k in _OVERRIDE_COMPOSED_EDGE_TYPES] ordered_composed = declares_composed + override_composed - g = graph or KuzuGraph.get() + g = graph or LadybugGraph.get() try: raw_filter = _coerce_filter(filter) nf = ( diff --git a/pr_analysis.py b/pr_analysis.py index 8c0e26ac..874d24c5 100644 --- a/pr_analysis.py +++ b/pr_analysis.py @@ -12,7 +12,7 @@ from unidiff import PatchSet from unidiff.errors import UnidiffParseError -from kuzu_queries import SymbolHit, find_symbols_in_file_range, _row_to_symbol +from ladybug_queries import SymbolHit, find_symbols_in_file_range, _row_to_symbol @dataclass diff --git a/pyproject.toml b/pyproject.toml index a18d1e20..d73285ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ license = "MIT" authors = [ { name = "HumanBean17" }, ] -keywords = ["mcp", "java", "rag", "code-search", "graph", "lancedb", "kuzu"] +keywords = ["mcp", "java", "rag", "code-search", "graph", "lancedb", "ladybug"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "cocoindex[lancedb]>=1.0.0a43,<2", - "kuzu>=0.11.3,<0.12", + "ladybug>=0.17.1,<0.18", "lancedb>=0.25.3,<0.31", "mcp>=1.27.0,<2", "numpy>=1.26.4,<2.5", @@ -67,7 +67,7 @@ py-modules = [ "java_index_flow_lancedb", "java_index_v1_common", "java_ontology", - "kuzu_queries", + "ladybug_queries", "mcp_hints", "mcp_v2", "path_filtering", diff --git a/scripts/generate_edge_navigation.py b/scripts/generate_edge_navigation.py index cbeba913..048191d7 100644 --- a/scripts/generate_edge_navigation.py +++ b/scripts/generate_edge_navigation.py @@ -81,7 +81,7 @@ def _render_edge(spec: EdgeSpec) -> list[str]: lines.append("**Attributes**:") lines.append("") for attr in spec.attrs: - lines.append(f"- `{attr.name}` (`{attr.kuzu_type}`) — {attr.purpose}") + lines.append(f"- `{attr.name}` (`{attr.graph_type}`) — {attr.purpose}") lines.append("") else: lines.append("**Attributes**: _(none)_") diff --git a/search_lancedb.py b/search_lancedb.py index 1850cff5..9029c6a5 100644 --- a/search_lancedb.py +++ b/search_lancedb.py @@ -675,16 +675,16 @@ def _graph_expand_merge( limit: int, extra_predicates: list[str], expand_depth: int, - kuzu_path: str | None, + ladybug_path: str | None, ) -> list[dict]: """Expand vector top-k through the Kuzu graph and fuse (RRF) with the original list.""" # Lazy import so the module works without kuzu installed when graph_expand=False. try: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph except Exception: return vector_rows - if not KuzuGraph.exists(kuzu_path): + if not LadybugGraph.exists(ladybug_path): return vector_rows seed_fqns = sorted({r.get("primary_type_fqn") for r in vector_rows if r.get("primary_type_fqn")}) @@ -692,7 +692,7 @@ def _graph_expand_merge( return vector_rows try: - graph = KuzuGraph.get(kuzu_path) + graph = LadybugGraph.get(ladybug_path) structural = graph.expand_fqns(seed_fqns, depth=expand_depth) method_pairs = graph.expand_methods( seed_fqns, depth=expand_depth, exclude_external=True, @@ -804,7 +804,7 @@ def run_search( package_prefix: str | None = None, graph_expand: bool = False, expand_depth: int = 1, - kuzu_path: str | None = None, + ladybug_path: str | None = None, context_neighbors: int = 0, role_in: list[str] | None = None, exclude_roles: list[str] | None = None, @@ -890,7 +890,7 @@ def run_search( limit=need, extra_predicates=extra_java, expand_depth=expand_depth, - kuzu_path=kuzu_path, + ladybug_path=ladybug_path, ) window = rows[offset : offset + limit] @@ -966,7 +966,7 @@ def main() -> None: parser.add_argument("--package-prefix", default=None) parser.add_argument("--graph-expand", action="store_true") parser.add_argument("--expand-depth", type=int, default=1) - parser.add_argument("--kuzu-path", default=None) + parser.add_argument("--ladybug-path", default=None) parser.add_argument( "--context-neighbors", type=int, default=0, help="Attach N adjacent chunks per hit as surrounding context (Java only).", @@ -1010,7 +1010,7 @@ def main() -> None: package_prefix=args.package_prefix, graph_expand=args.graph_expand, expand_depth=args.expand_depth, - kuzu_path=args.kuzu_path, + ladybug_path=args.ladybug_path, context_neighbors=args.context_neighbors, ) except Exception as e: diff --git a/server.py b/server.py index 9dc44adb..5ac83dd1 100644 --- a/server.py +++ b/server.py @@ -22,17 +22,17 @@ resolved_sbert_model_for_process_env, resolve_operator_config, ) -from kuzu_queries import KuzuGraph, resolve_kuzu_path +from ladybug_queries import LadybugGraph, resolve_ladybug_path from mcp.server.fastmcp import FastMCP from pydantic import BaseModel, Field from search_lancedb import TABLES _COCOINDEX_TARGET = "java_index_flow_lancedb.py:JavaCodeIndexLance" _INSTRUCTIONS = ( - "Java codebase graph navigator (LanceDB + Kuzu). " + "Java codebase graph navigator (LanceDB + Ladybug). " "Tools: search (NL/code locate), find (structured NodeFilter), describe (one node + edge_summary: stored edge-label counts and optional composed keys for type Symbols and override-axis virtual keys for method Symbols), " "neighbors (one hop; you MUST pass direction in|out AND edge_types list — no defaults), " - "resolve (identifier-shaped lookup for symbol/route/client/producer — three statuses one|many|none). " + "resolve (identifier-shaped lookup for symbol/route/client/producer — three statuses one|many/none). " "NodeFilter `filter` is a JSON object (preferred); a JSON-encoded string is also accepted as a fallback. " "Unknown filter keys and populated fields not applicable to the effective node kind fail with success=false and message. " "Edge labels: EXTENDS, IMPLEMENTS, INJECTS, OVERRIDES, DECLARES, DECLARES_CLIENT, DECLARES_PRODUCER, CALLS, EXPOSES, HTTP_CALLS, ASYNC_CALLS; " @@ -169,32 +169,32 @@ def _cocoindex_subprocess_env(project_root: Path) -> dict[str, str]: def _graph_enabled() -> bool: - return KuzuGraph.exists() + return LadybugGraph.exists() def _graph_meta_output() -> GraphMetaOutput: - if not KuzuGraph.exists(): + if not LadybugGraph.exists(): return GraphMetaOutput( success=True, enabled=False, - db_path=resolve_kuzu_path(), - message="Kuzu graph not present; run java-codebase-rag reprocess or build_ast_graph.py", + db_path=resolve_ladybug_path(), + message="Ladybug graph not present; run java-codebase-rag reprocess or build_ast_graph.py", ) try: - graph = KuzuGraph.get() + graph = LadybugGraph.get() meta = graph.meta() except Exception as e: return GraphMetaOutput( success=False, enabled=_graph_enabled(), - db_path=resolve_kuzu_path(), - message=f"Kuzu open failed: {e}", + db_path=resolve_ladybug_path(), + message=f"Ladybug open failed: {e}", ) if "error" in meta: return GraphMetaOutput( success=False, enabled=_graph_enabled(), - db_path=meta.get("db_path", resolve_kuzu_path()), + db_path=meta.get("db_path", resolve_ladybug_path()), message=str(meta["error"]), ) try: @@ -212,7 +212,7 @@ def _graph_meta_output() -> GraphMetaOutput: return GraphMetaOutput( success=True, enabled=_graph_enabled(), - db_path=meta.get("db_path", resolve_kuzu_path()), + db_path=meta.get("db_path", resolve_ladybug_path()), ontology_version=int(meta.get("ontology_version") or 0), built_at=int(meta.get("built_at") or 0), source_root=str(meta.get("source_root") or ""), @@ -337,8 +337,8 @@ async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) -> str(builder), "--source-root", str(root), - "--kuzu-path", - resolve_kuzu_path(), + "--ladybug-path", + resolve_ladybug_path(), ] if not quiet: graph_args.append("--verbose") diff --git a/tests/_builders.py b/tests/_builders.py index abd8412b..4dae30f3 100644 --- a/tests/_builders.py +++ b/tests/_builders.py @@ -12,7 +12,7 @@ pass4_routes, pass5_imperative_edges, pass6_match_edges, - write_kuzu, + write_ladybug, ) @@ -36,23 +36,23 @@ def build_graph_tables_to(corpus_root: Path, *, max_pass: int) -> GraphTables: return tables -def build_kuzu_to(corpus_root: Path, db_path: Path, *, max_pass: int) -> Path: - """Build through ``max_pass``, ``write_kuzu`` to ``db_path``; return ``db_path``.""" +def build_ladybug_to(corpus_root: Path, db_path: Path, *, max_pass: int) -> Path: + """Build through ``max_pass``, ``write_ladybug`` to ``db_path``; return ``db_path``.""" tables = build_graph_tables_to(corpus_root, max_pass=max_pass) - write_kuzu(db_path, tables, source_root=corpus_root, verbose=False) + write_ladybug(db_path, tables, source_root=corpus_root, verbose=False) return db_path -def build_kuzu_into(corpus_root: Path, db_path: Path) -> Path: - """Tier-3 helper: pass1–4 + ``write_kuzu`` (mutable per-test corpus under ``corpus_root``).""" - return build_kuzu_to(corpus_root, db_path, max_pass=4) +def build_ladybug_into(corpus_root: Path, db_path: Path) -> Path: + """Tier-3 helper: pass1–4 + ``write_ladybug`` (mutable per-test corpus under ``corpus_root``).""" + return build_ladybug_to(corpus_root, db_path, max_pass=4) -def build_kuzu_imperative_into(corpus_root: Path, db_path: Path) -> Path: - """pass1–5 + ``write_kuzu`` (no pass6).""" - return build_kuzu_to(corpus_root, db_path, max_pass=5) +def build_ladybug_imperative_into(corpus_root: Path, db_path: Path) -> Path: + """pass1–5 + ``write_ladybug`` (no pass6).""" + return build_ladybug_to(corpus_root, db_path, max_pass=5) -def build_kuzu_full_into(corpus_root: Path, db_path: Path) -> Path: - """pass1–6 + ``write_kuzu``.""" - return build_kuzu_to(corpus_root, db_path, max_pass=6) +def build_ladybug_full_into(corpus_root: Path, db_path: Path) -> Path: + """pass1–6 + ``write_ladybug``.""" + return build_ladybug_to(corpus_root, db_path, max_pass=6) diff --git a/tests/conftest.py b/tests/conftest.py index 24cd7728..e42b4ccd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ """Shared pytest fixtures for the mcp_lancedb_bundle test suite. Session-scoped graphs are built once per static corpus (see ``tests/README.md``). -The bank-chat chain ``corpus_root → kuzu_db_path → mcp_env → kuzu_graph → mcp_server`` +The bank-chat chain ``corpus_root → ladybug_db_path → mcp_env → ladybug_graph → mcp_server`` runs pass1–5 + ``write_kuzu`` (no pass6) so Tier-1 caller-edge tests match the pre-refactor bank pipeline while avoiding a second full parse for MCP tests. @@ -45,20 +45,20 @@ def corpus_root() -> Path: def _session_db_path(tmp_path_factory: pytest.TempPathFactory, name: str) -> Path: base = tmp_path_factory.mktemp(f"kuzu_{name}") - return base / "code_graph.kuzu" + return base / "code_graph.lbug" @pytest.fixture(scope="session") -def kuzu_db_path(tmp_path_factory, corpus_root: Path) -> Path: +def ladybug_db_path(tmp_path_factory, corpus_root: Path) -> Path: """Bank-chat Kuzu DB: pass1–5 + ``write_kuzu`` (no pass6).""" - import kuzu + import ladybug - from _builders import build_kuzu_to + from _builders import build_ladybug_to db_path = _session_db_path(tmp_path_factory, "bank_chat") - build_kuzu_to(corpus_root, db_path, max_pass=5) + build_ladybug_to(corpus_root, db_path, max_pass=5) - conn = kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) n_types = 0 r = conn.execute("MATCH (s:Symbol) WHERE s.kind = 'class' RETURN count(*) AS n") if r.has_next(): @@ -71,14 +71,14 @@ def kuzu_db_path(tmp_path_factory, corpus_root: Path) -> Path: @pytest.fixture(scope="session") -def mcp_env(kuzu_db_path: Path, tmp_path_factory) -> dict[str, str]: +def mcp_env(ladybug_db_path: Path, tmp_path_factory) -> dict[str, str]: """Configure env vars the MCP server reads on startup. - ``JAVA_CODEBASE_RAG_INDEX_DIR`` is the parent of ``code_graph.kuzu`` so - ``resolve_kuzu_path()`` matches the session graph fixture. Lance tables + ``JAVA_CODEBASE_RAG_INDEX_DIR`` is the parent of ``code_graph.lbug`` so + ``resolve_ladybug_path()`` matches the session graph fixture. Lance tables are not required for graph-only tools. """ - idx_dir = kuzu_db_path.parent + idx_dir = ladybug_db_path.parent env = { "JAVA_CODEBASE_RAG_INDEX_DIR": str(idx_dir), "JAVA_CODEBASE_RAG_SOURCE_ROOT": str(CORPUS_ROOT), @@ -89,17 +89,17 @@ def mcp_env(kuzu_db_path: Path, tmp_path_factory) -> dict[str, str]: @pytest.fixture(scope="session") -def kuzu_graph(mcp_env, kuzu_db_path: Path): - """Read-only KuzuGraph singleton bound to the session DB.""" - from kuzu_queries import KuzuGraph +def ladybug_graph(mcp_env, ladybug_db_path: Path): + """Read-only LadybugGraph singleton bound to the session DB.""" + from ladybug_queries import LadybugGraph - KuzuGraph._instance = None - KuzuGraph._instance_path = None - return KuzuGraph.get(str(kuzu_db_path)) + LadybugGraph._instance = None + LadybugGraph._instance_path = None + return LadybugGraph.get(str(ladybug_db_path)) @pytest.fixture(scope="session") -def mcp_server(mcp_env, kuzu_graph): +def mcp_server(mcp_env, ladybug_graph): """A FastMCP server instance with all tools registered.""" from server import create_mcp_server @@ -110,68 +110,68 @@ def mcp_server(mcp_env, kuzu_graph): @pytest.fixture(scope="session") -def kuzu_db_path_call_graph_smoke(tmp_path_factory) -> Path: - from _builders import build_kuzu_to +def ladybug_db_path_call_graph_smoke(tmp_path_factory) -> Path: + from _builders import build_ladybug_to root = TESTS_DIR / "fixtures" / "call_graph_smoke" assert root.is_dir(), root db_path = _session_db_path(tmp_path_factory, "call_graph_smoke") - return build_kuzu_to(root, db_path, max_pass=3) + return build_ladybug_to(root, db_path, max_pass=3) @pytest.fixture(scope="session") -def kuzu_db_path_route_extraction_smoke(tmp_path_factory) -> Path: - from _builders import build_kuzu_to +def ladybug_db_path_route_extraction_smoke(tmp_path_factory) -> Path: + from _builders import build_ladybug_to root = TESTS_DIR / "fixtures" / "route_extraction_smoke" assert root.is_dir(), root db_path = _session_db_path(tmp_path_factory, "route_extraction_smoke") - return build_kuzu_to(root, db_path, max_pass=4) + return build_ladybug_to(root, db_path, max_pass=4) @pytest.fixture(scope="session") -def kuzu_graph_route_extraction_smoke(kuzu_db_path_route_extraction_smoke: Path): - """Read-only ``KuzuGraph`` for ``route_extraction_smoke`` (own DB path; not ``KuzuGraph.get``).""" - from kuzu_queries import KuzuGraph +def ladybug_graph_route_extraction_smoke(ladybug_db_path_route_extraction_smoke: Path): + """Read-only ``LadybugGraph`` for ``route_extraction_smoke`` (own DB path; not ``LadybugGraph.get``).""" + from ladybug_queries import LadybugGraph - return KuzuGraph(str(kuzu_db_path_route_extraction_smoke)) + return LadybugGraph(str(ladybug_db_path_route_extraction_smoke)) @pytest.fixture(scope="session") -def kuzu_db_path_cross_service_smoke(tmp_path_factory) -> Path: - from _builders import build_kuzu_to +def ladybug_db_path_cross_service_smoke(tmp_path_factory) -> Path: + from _builders import build_ladybug_to root = TESTS_DIR / "fixtures" / "cross_service_smoke" assert root.is_dir(), root db_path = _session_db_path(tmp_path_factory, "cross_service_smoke") - return build_kuzu_to(root, db_path, max_pass=6) + return build_ladybug_to(root, db_path, max_pass=6) @pytest.fixture(scope="session") -def kuzu_db_path_fqn_collision_smoke(tmp_path_factory) -> Path: - from _builders import build_kuzu_to +def ladybug_db_path_fqn_collision_smoke(tmp_path_factory) -> Path: + from _builders import build_ladybug_to root = TESTS_DIR / "fixtures" / "fqn_collision_smoke" assert root.is_dir(), root db_path = _session_db_path(tmp_path_factory, "fqn_collision_smoke") - return build_kuzu_to(root, db_path, max_pass=3) + return build_ladybug_to(root, db_path, max_pass=3) @pytest.fixture(scope="session") -def kuzu_graph_fqn_collision_smoke(kuzu_db_path_fqn_collision_smoke: Path): - from kuzu_queries import KuzuGraph +def ladybug_graph_fqn_collision_smoke(ladybug_db_path_fqn_collision_smoke: Path): + from ladybug_queries import LadybugGraph - return KuzuGraph(str(kuzu_db_path_fqn_collision_smoke)) + return LadybugGraph(str(ladybug_db_path_fqn_collision_smoke)) @pytest.fixture(scope="session") -def kuzu_db_path_http_caller_smoke(tmp_path_factory) -> Path: - from _builders import build_kuzu_to +def ladybug_db_path_http_caller_smoke(tmp_path_factory) -> Path: + from _builders import build_ladybug_to root = TESTS_DIR / "fixtures" / "http_caller_smoke" assert root.is_dir(), root db_path = _session_db_path(tmp_path_factory, "http_caller_smoke") - return build_kuzu_to(root, db_path, max_pass=5) + return build_ladybug_to(root, db_path, max_pass=5) @pytest.fixture(scope="session") diff --git a/tests/pinned_ids.py b/tests/pinned_ids.py index 99cfe422..1396e863 100644 --- a/tests/pinned_ids.py +++ b/tests/pinned_ids.py @@ -10,10 +10,10 @@ ) if TYPE_CHECKING: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph -def client_message_processor_process_id(graph: KuzuGraph) -> str: +def client_message_processor_process_id(graph: LadybugGraph) -> str: rows = graph._rows( # noqa: SLF001 "MATCH (m:Symbol {fqn: $fqn}) RETURN m.id AS id LIMIT 1", {"fqn": CLIENT_MESSAGE_PROCESSOR_PROCESS_FQN}, diff --git a/tests/test_assign_endpoint_client_extraction.py b/tests/test_assign_endpoint_client_extraction.py index 2f72de53..15caf968 100644 --- a/tests/test_assign_endpoint_client_extraction.py +++ b/tests/test_assign_endpoint_client_extraction.py @@ -5,7 +5,7 @@ import shutil from pathlib import Path -import kuzu +import ladybug from ast_java import ONTOLOGY_VERSION from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain @@ -25,16 +25,16 @@ def _build(tmp: Path, yml: str | None, extra_files: dict[str, str]) -> Path: p = tmp / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(body, encoding="utf-8") - from _builders import build_kuzu_full_into + from _builders import build_ladybug_full_into - db_path = tmp / "g.kuzu" - build_kuzu_full_into(tmp, db_path) + db_path = tmp / "g.lbug" + build_ladybug_full_into(tmp, db_path) return db_path def _rows(db_path: Path, query: str) -> list[tuple]: - db = kuzu.Database(str(db_path), read_only=True) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path), read_only=True) + conn = ladybug.Connection(db) r = conn.execute(query) out: list[tuple] = [] while r.has_next(): diff --git a/tests/test_ast_graph_build.py b/tests/test_ast_graph_build.py index 53662152..b78a1ac4 100644 --- a/tests/test_ast_graph_build.py +++ b/tests/test_ast_graph_build.py @@ -9,31 +9,48 @@ from __future__ import annotations import json +import re import subprocess import sys from pathlib import Path -import kuzu +import ladybug import pytest -from _builders import build_kuzu_to +from _builders import build_ladybug_to from ast_java import ONTOLOGY_VERSION from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain -def _connect(db_path: Path) -> kuzu.Connection: - db = kuzu.Database(str(db_path), read_only=True) - return kuzu.Connection(db) +def _parse_ladybug_json(raw: str | None) -> dict: + """Parse JSON from LadybugDB which returns unquoted keys like {key: value}.""" + if not raw: + return {} + # Convert {key: value} to {"key": value} + quoted = re.sub(r'(\w+):', r'"\1":', raw) + try: + return json.loads(quoted) + except Exception: + try: + # Fallback: try parsing as-is (for standard JSON) + return json.loads(raw) + except Exception: + return {} + + +def _connect(db_path: Path) -> ladybug.Connection: + db = ladybug.Database(str(db_path), read_only=True) + return ladybug.Connection(db) -def _scalar(conn: kuzu.Connection, query: str) -> int: +def _scalar(conn: ladybug.Connection, query: str) -> int: r = conn.execute(query) if not r.has_next(): return 0 return int(r.get_next()[0] or 0) -def _column(conn: kuzu.Connection, query: str, idx: int = 0) -> list: +def _column(conn: ladybug.Connection, query: str, idx: int = 0) -> list: r = conn.execute(query) out: list = [] while r.has_next(): @@ -41,12 +58,12 @@ def _column(conn: kuzu.Connection, query: str, idx: int = 0) -> list: return out -def test_kuzu_db_directory_exists(kuzu_db_path: Path) -> None: - assert kuzu_db_path.exists() +def test_kuzu_db_directory_exists(ladybug_db_path: Path) -> None: + assert ladybug_db_path.exists() -def test_schema_has_all_expected_tables(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_schema_has_all_expected_tables(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) # `CALL show_tables() RETURN *;` returns rows (id, name, type, ...) — name is at index 1. tables = set(_column(conn, "CALL show_tables() RETURN *;", idx=1)) # We only assert the tables we depend on are present. The builder is @@ -60,8 +77,8 @@ def test_schema_has_all_expected_tables(kuzu_db_path: Path) -> None: assert not missing, f"missing schema tables: {missing}; saw {tables}" -def test_graph_meta_unresolved_counters_present(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_graph_meta_unresolved_counters_present(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) r = conn.execute( "MATCH (m:GraphMeta) RETURN m.pass3_unresolved_phantom_receiver, " "m.pass3_unresolved_chained" @@ -107,7 +124,7 @@ class Caller { " LegacyServiceMarker: SERVICE\n", encoding="utf-8", ) - db_path = build_kuzu_to(root, tmp_path / "g.kuzu", max_pass=3) + db_path = build_ladybug_to(root, tmp_path / "g.lbug", max_pass=3) conn = _connect(db_path) mismatches = _scalar( conn, @@ -127,9 +144,9 @@ class Caller { assert roles == ["SERVICE"] -def test_pass3_callee_declaring_role_bank_annotated_types(kuzu_db_path: Path) -> None: +def test_pass3_callee_declaring_role_bank_annotated_types(ladybug_db_path: Path) -> None: """CALLS to methods on @Service declaring types carry callee_declaring_role=SERVICE.""" - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) rows = _column( conn, "MATCH (src:Symbol)-[c:CALLS]->(dst:Symbol) " @@ -150,8 +167,8 @@ def test_pass3_callee_declaring_role_bank_annotated_types(kuzu_db_path: Path) -> assert all(str(r) == "REPOSITORY" for r in repo_rows), repo_rows -def test_graph_meta_present_and_versioned(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_graph_meta_present_and_versioned(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) r = conn.execute( "MATCH (m:GraphMeta) RETURN m.ontology_version, m.built_at, " "m.source_root, m.parse_errors, m.counts_json, " @@ -181,51 +198,51 @@ def test_graph_meta_present_and_versioned(kuzu_db_path: Path) -> None: # accidental tree-sitter regressions that break every file at once. assert int(parse_errors) <= 0 # bank-chat-system is hand-written, no errors expected assert counts_json and counts_json.startswith("{") - counts = json.loads(counts_json) + counts = _parse_ladybug_json(counts_json) assert counts.get("routes", 0) >= 1 assert int(routes_total) >= 1 assert int(exposes_total) >= 1 assert float(routes_resolved_pct) >= 0.0 - by_fw = json.loads(routes_by_framework_raw) + by_fw = _parse_ladybug_json(routes_by_framework_raw) assert isinstance(by_fw, dict) assert len(by_fw) >= 1 assert float(routes_from_brownfield_pct) >= 0.0 - by_layer = json.loads(routes_by_layer_raw) + by_layer = _parse_ladybug_json(routes_by_layer_raw) assert isinstance(by_layer, dict) -def test_each_node_kind_present(kuzu_db_path: Path) -> None: +def test_each_node_kind_present(ladybug_db_path: Path) -> None: """Builder must emit at least one node of every Phase-1 kind we care about. Exact counts are a moving target; non-zero is the meaningful invariant. """ - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) kinds = set(_column(conn, "MATCH (s:Symbol) RETURN DISTINCT s.kind")) for required in ("package", "file", "class", "interface", "method", "constructor"): assert required in kinds, f"missing node kind: {required}; saw {kinds}" -def test_each_edge_type_populated(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_each_edge_type_populated(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) assert _scalar(conn, "MATCH ()-[e:EXTENDS]->() RETURN count(e)") > 0 assert _scalar(conn, "MATCH ()-[e:IMPLEMENTS]->() RETURN count(e)") > 0 assert _scalar(conn, "MATCH ()-[e:INJECTS]->() RETURN count(e)") > 0 -def test_calls_and_declares_edges_populated(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_calls_and_declares_edges_populated(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) assert _scalar(conn, "MATCH ()-[e:CALLS]->() RETURN count(e)") > 0 assert _scalar(conn, "MATCH ()-[e:DECLARES]->() RETURN count(e)") > 0 -def test_module_inference_recognises_both_layouts(kuzu_graph) -> None: +def test_module_inference_recognises_both_layouts(ladybug_graph) -> None: """`module_for_path` must find each Maven module's name. The corpus exercises both a single-module project (chat-assign) and a multi-module reactor (chat-core/{chat-app,chat-engine,chat-domain, chat-contracts}). The MCP must handle both shapes. """ - counts = kuzu_graph.module_counts() + counts = ladybug_graph.module_counts() # Defensive: don't pin every module to >0 in case the corpus is # trimmed; instead require both styles to be represented. assert counts.get("chat-assign", 0) > 0, counts @@ -237,7 +254,7 @@ def test_module_inference_recognises_both_layouts(kuzu_graph) -> None: ) -def test_microservice_inference_groups_multi_module_reactor(kuzu_graph) -> None: +def test_microservice_inference_groups_multi_module_reactor(ladybug_graph) -> None: """Multi-module reactor child modules must collapse to one microservice key. `chat-core` is the outermost build-marker ancestor for every @@ -246,7 +263,7 @@ def test_microservice_inference_groups_multi_module_reactor(kuzu_graph) -> None: `chat-assign` is single-module so its module and microservice names coincide. """ - counts = kuzu_graph.microservice_counts() + counts = ladybug_graph.microservice_counts() assert counts.get("chat-assign", 0) > 0, counts assert counts.get("chat-core", 0) > 0, counts # Inner module names must NOT appear at the microservice level — that @@ -255,20 +272,20 @@ def test_microservice_inference_groups_multi_module_reactor(kuzu_graph) -> None: assert not (inner & set(counts)), counts -def test_phantom_nodes_for_external_types(kuzu_db_path: Path) -> None: +def test_phantom_nodes_for_external_types(ladybug_db_path: Path) -> None: """Spring Data repositories extend `JpaRepository` (an external type). The builder must materialise that as a *phantom* (unresolved) Symbol so EXTENDS/IMPLEMENTS edges are never dangling. """ - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) n_phantoms = _scalar( conn, "MATCH (s:Symbol) WHERE s.resolved = false RETURN count(s)" ) assert n_phantoms > 0, "no phantom nodes — external type resolution may be silently dropping edges" -def test_injects_edges_have_mechanism(kuzu_db_path: Path) -> None: +def test_injects_edges_have_mechanism(ladybug_db_path: Path) -> None: """Every INJECTS edge should record *how* the injection happens. The bank-chat-system uses constructor injection throughout @@ -277,20 +294,20 @@ def test_injects_edges_have_mechanism(kuzu_db_path: Path) -> None: edges are constructor-injected to leave room for future Lombok / setter samples. """ - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) mechanisms = set(_column(conn, "MATCH ()-[e:INJECTS]->() RETURN DISTINCT e.mechanism")) assert "constructor" in mechanisms, mechanisms -def test_routes_and_exposes_populated(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_routes_and_exposes_populated(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) assert _scalar(conn, "MATCH (r:Route) RETURN count(r)") >= 1 assert _scalar(conn, "MATCH ()-[e:EXPOSES]->() RETURN count(e)") >= 1 -def test_route_id_includes_microservice(kuzu_db_path_route_extraction_smoke: Path) -> None: +def test_route_id_includes_microservice(ladybug_db_path_route_extraction_smoke: Path) -> None: """Same HTTP path in two declared microservices → distinct Route primary keys.""" - db_path = kuzu_db_path_route_extraction_smoke + db_path = ladybug_db_path_route_extraction_smoke conn = _connect(db_path) ids = _column( conn, @@ -300,8 +317,8 @@ def test_route_id_includes_microservice(kuzu_db_path_route_extraction_smoke: Pat assert len(set(ids)) >= 2, ids -def test_exposes_edge_direction(kuzu_db_path_route_extraction_smoke: Path) -> None: - db_path = kuzu_db_path_route_extraction_smoke +def test_exposes_edge_direction(ladybug_db_path_route_extraction_smoke: Path) -> None: + db_path = ladybug_db_path_route_extraction_smoke conn = _connect(db_path) fwd = _scalar(conn, "MATCH (s:Symbol)-[:EXPOSES]->(r:Route) RETURN count(*)") rev = _scalar(conn, "MATCH (r:Route)-[:EXPOSES]->(s:Symbol) RETURN count(*)") @@ -309,9 +326,9 @@ def test_exposes_edge_direction(kuzu_db_path_route_extraction_smoke: Path) -> No assert rev == 0 -def test_symbol_has_capabilities_column(kuzu_db_path: Path) -> None: +def test_symbol_has_capabilities_column(ladybug_db_path: Path) -> None: """Symbol nodes must have a `capabilities` STRING[] column (ontology v4).""" - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) # Simply SELECT a capabilities value — if the column doesn't exist Kuzu raises. try: r = conn.execute( @@ -330,14 +347,14 @@ def test_cli_entrypoint_runs(tmp_path: Path, corpus_root: Path) -> None: This is an integration smoke test — it calls the script as a user would (via the venv Python) and asserts a non-empty Kuzu DB is written. """ - target = tmp_path / "graph.kuzu" + target = tmp_path / "graph.lbug" script = Path(__file__).resolve().parent.parent / "build_ast_graph.py" proc = subprocess.run( [ sys.executable, str(script), "--source-root", str(corpus_root), - "--kuzu-path", str(target), + "--ladybug-path", str(target), ], capture_output=True, text=True, @@ -349,9 +366,9 @@ def test_cli_entrypoint_runs(tmp_path: Path, corpus_root: Path) -> None: assert _scalar(conn, "MATCH (s:Symbol) RETURN count(s)") > 0 -def test_pass3_no_phantom_chained_calls_rows(kuzu_db_path: Path) -> None: +def test_pass3_no_phantom_chained_calls_rows(ladybug_db_path: Path) -> None: """HV19 — receiver-failure strategies must not appear on CALLS after PR-3.""" - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) n = _scalar( conn, "MATCH ()-[c:CALLS]->() " @@ -360,8 +377,8 @@ def test_pass3_no_phantom_chained_calls_rows(kuzu_db_path: Path) -> None: assert n == 0, f"expected zero phantom/chained_receiver CALLS rows, got {n}" -def test_pass3_unresolved_call_site_emitted(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_pass3_unresolved_call_site_emitted(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) n_ucs = _scalar(conn, "MATCH (u:UnresolvedCallSite) RETURN count(u)") n_rel = _scalar(conn, "MATCH ()-[:UNRESOLVED_AT]->() RETURN count(*)") assert n_ucs >= 1, "bank fixture should emit UnresolvedCallSite rows" @@ -376,9 +393,9 @@ def test_pass3_unresolved_call_site_emitted(kuzu_db_path: Path) -> None: assert len(reasons) >= 1 -def test_pass3_known_external_calls_preserved(kuzu_db_path: Path) -> None: +def test_pass3_known_external_calls_preserved(ladybug_db_path: Path) -> None: """HV37 — JDK/external callee stays on CALLS with resolved=False, not phantom strategy.""" - conn = _connect(kuzu_db_path) + conn = _connect(ladybug_db_path) rows = conn.execute( "MATCH (src:Symbol)-[c:CALLS]->(dst:Symbol) " "WHERE c.resolved = false AND c.strategy <> 'overload_ambiguous' " diff --git a/tests/test_bank_chat_brownfield_integration.py b/tests/test_bank_chat_brownfield_integration.py index fba8e917..1062d307 100644 --- a/tests/test_bank_chat_brownfield_integration.py +++ b/tests/test_bank_chat_brownfield_integration.py @@ -7,21 +7,21 @@ from pathlib import Path -import kuzu +import ladybug -from kuzu_queries import KuzuGraph +from ladybug_queries import LadybugGraph -def _connect(db_path: Path) -> kuzu.Connection: - return kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) +def _connect(db_path: Path) -> ladybug.Connection: + return ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) -def _scalar(conn: kuzu.Connection, query: str, params: dict | None = None) -> int: +def _scalar(conn: ladybug.Connection, query: str, params: dict | None = None) -> int: r = conn.execute(query, params or {}) return int(r.get_next()[0] or 0) if r.has_next() else 0 -def _column(conn: kuzu.Connection, query: str, params: dict | None = None) -> list: +def _column(conn: ladybug.Connection, query: str, params: dict | None = None) -> list: r = conn.execute(query, params or {}) out: list = [] while r.has_next(): @@ -29,15 +29,15 @@ def _column(conn: kuzu.Connection, query: str, params: dict | None = None) -> li return out -def test_bank_graph_meta_brownfield_percentages_positive(kuzu_db_path: Path) -> None: - meta = KuzuGraph(str(kuzu_db_path)).meta() +def test_bank_graph_meta_brownfield_percentages_positive(ladybug_db_path: Path) -> None: + meta = LadybugGraph(str(ladybug_db_path)).meta() assert float(meta.get("routes_from_brownfield_pct") or 0.0) > 0.0 assert float(meta.get("http_clients_from_brownfield_pct") or 0.0) > 0.0 assert float(meta.get("async_producers_from_brownfield_pct") or 0.0) > 0.0 -def test_bank_brownfield_http_route_on_chat_events(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_bank_brownfield_http_route_on_chat_events(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) rows = _column( conn, "MATCH (rt:Route) " @@ -54,8 +54,8 @@ def test_bank_brownfield_http_route_on_chat_events(kuzu_db_path: Path) -> None: assert n_exposes >= 1 -def test_bank_codebase_http_client_on_join_operator(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_bank_codebase_http_client_on_join_operator(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) rows = _column( conn, "MATCH (c:Client) " @@ -69,8 +69,8 @@ def test_bank_codebase_http_client_on_join_operator(kuzu_db_path: Path) -> None: assert str(layer) == "layer_c_source" -def test_bank_compliance_listener_async_route_layer_c(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_bank_compliance_listener_async_route_layer_c(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) topics = _column( conn, "MATCH (m:Symbol)-[:EXPOSES]->(rt:Route) " @@ -82,8 +82,8 @@ def test_bank_compliance_listener_async_route_layer_c(kuzu_db_path: Path) -> Non assert topic_set == {"banking.chat.compliance.review"} -def test_bank_event_stream_bridge_codebase_producers_container(kuzu_db_path: Path) -> None: - conn = _connect(kuzu_db_path) +def test_bank_event_stream_bridge_codebase_producers_container(ladybug_db_path: Path) -> None: + conn = _connect(ladybug_db_path) rows = _column( conn, "MATCH (m:Symbol)-[:DECLARES_PRODUCER]->(pr:Producer) " diff --git a/tests/test_brownfield_clients.py b/tests/test_brownfield_clients.py index e58d42ec..b02d6a98 100644 --- a/tests/test_brownfield_clients.py +++ b/tests/test_brownfield_clients.py @@ -6,7 +6,7 @@ from contextlib import redirect_stderr from pathlib import Path -import kuzu +import ladybug import pytest from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain @@ -33,16 +33,16 @@ def _build(tmp: Path, yml: str | None, extra_files: dict[str, str]) -> Path: p = tmp / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(body, encoding="utf-8") - from _builders import build_kuzu_imperative_into + from _builders import build_ladybug_imperative_into - db_path = tmp / "g.kuzu" - build_kuzu_imperative_into(tmp, db_path) + db_path = tmp / "g.lbug" + build_ladybug_imperative_into(tmp, db_path) return db_path def _http_calls(db_path: Path) -> list[dict]: - db = kuzu.Database(str(db_path), read_only=True) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path), read_only=True) + conn = ladybug.Connection(db) r = conn.execute( "MATCH (c:Client)-[h:HTTP_CALLS]->(rt:Route) " "RETURN c.member_fqn AS fqn, h.strategy AS strategy, h.method_call AS method_call, " @@ -64,8 +64,8 @@ def _http_calls(db_path: Path) -> list[dict]: def _async_calls(db_path: Path) -> list[dict]: - db = kuzu.Database(str(db_path), read_only=True) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path), read_only=True) + conn = ladybug.Connection(db) r = conn.execute( "MATCH (pr:Producer)-[c:ASYNC_CALLS]->(rt:Route) " "RETURN pr.member_fqn AS fqn, c.strategy AS strategy, rt.topic AS topic ORDER BY fqn, topic", @@ -78,11 +78,11 @@ def _async_calls(db_path: Path) -> list[dict]: def _meta(db_path: Path) -> dict: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - KuzuGraph._instance = None - KuzuGraph._instance_path = None - return KuzuGraph.get(str(db_path)).meta() + LadybugGraph._instance = None + LadybugGraph._instance_path = None + return LadybugGraph.get(str(db_path)).meta() def test_20_layer_b_annotation_http_client(tmp_path: Path) -> None: diff --git a/tests/test_brownfield_routes.py b/tests/test_brownfield_routes.py index 5624be32..acab183c 100644 --- a/tests/test_brownfield_routes.py +++ b/tests/test_brownfield_routes.py @@ -6,7 +6,7 @@ from contextlib import redirect_stderr from pathlib import Path -import kuzu +import ladybug import pytest from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain @@ -33,16 +33,16 @@ def _build(tmp: Path, yml: str | None, extra_files: dict[str, str]) -> Path: p = tmp / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(body, encoding="utf-8") - from _builders import build_kuzu_into + from _builders import build_ladybug_into - db_path = tmp / "g.kuzu" - build_kuzu_into(tmp, db_path) + db_path = tmp / "g.lbug" + build_ladybug_into(tmp, db_path) return db_path def _route_paths(db_path: Path) -> list[str]: - db = kuzu.Database(str(db_path), read_only=True) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path), read_only=True) + conn = ladybug.Connection(db) r = conn.execute("MATCH (rt:Route) RETURN rt.path ORDER BY rt.path") out: list[str] = [] while r.has_next(): @@ -51,8 +51,8 @@ def _route_paths(db_path: Path) -> list[str]: def _route_ids(db_path: Path) -> list[str]: - db = kuzu.Database(str(db_path), read_only=True) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path), read_only=True) + conn = ladybug.Connection(db) r = conn.execute("MATCH (rt:Route) RETURN rt.id ORDER BY rt.id") out: list[str] = [] while r.has_next(): @@ -61,11 +61,11 @@ def _route_ids(db_path: Path) -> list[str]: def _meta(db_path: Path) -> dict: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - KuzuGraph._instance = None - KuzuGraph._instance_path = None - return KuzuGraph.get(str(db_path)).meta() + LadybugGraph._instance = None + LadybugGraph._instance_path = None + return LadybugGraph.get(str(db_path)).meta() def test_19_layer_b_annotation_route(tmp_path: Path) -> None: @@ -110,7 +110,7 @@ def test_20_layer_b_fqn_seeds_all_methods(tmp_path: Path) -> None: ), } db = _build(tmp_path, yml, java) - conn = kuzu.Connection(kuzu.Database(str(db), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db), read_only=True)) r = conn.execute( "MATCH (s:Symbol)-[:EXPOSES]->(r:Route) " "WHERE r.path = '/legacy/users' RETURN count(*)", @@ -330,7 +330,7 @@ def test_31_layer_c_http_replaces_builtin_spring_row(tmp_path: Path) -> None: ), } db = _build(tmp_path, None, java) - conn = kuzu.Connection(kuzu.Database(str(db), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db), read_only=True)) n = int( conn.execute( "MATCH (rt:Route) WHERE rt.path = '/x' AND rt.method = 'GET' RETURN count(*)", diff --git a/tests/test_call_edges_e2e.py b/tests/test_call_edges_e2e.py index 99ffaaea..b34985e4 100644 --- a/tests/test_call_edges_e2e.py +++ b/tests/test_call_edges_e2e.py @@ -3,16 +3,16 @@ import shutil from pathlib import Path -import kuzu +import ladybug from ast_java import ONTOLOGY_VERSION -from kuzu_queries import KuzuGraph +from ladybug_queries import LadybugGraph _STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" def _scalar(db_path: Path, query: str) -> int: - conn = kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) r = conn.execute(query) return int(r.get_next()[0] or 0) if r.has_next() else 0 @@ -29,28 +29,28 @@ def _build_repeatable_clients(tmp_path: Path) -> Path: "}) void m() {} }", encoding="utf-8", ) - from _builders import build_kuzu_full_into + from _builders import build_ladybug_full_into - db_path = tmp_path / "g.kuzu" - build_kuzu_full_into(tmp_path, db_path) + db_path = tmp_path / "g.lbug" + build_ladybug_full_into(tmp_path, db_path) return db_path -def test_http_calls_table_built_on_bank_chat(kuzu_db_path: Path) -> None: - assert _scalar(kuzu_db_path, "MATCH (:Client)-[r:HTTP_CALLS]->(:Route) RETURN count(r)") >= 2 +def test_http_calls_table_built_on_bank_chat(ladybug_db_path: Path) -> None: + assert _scalar(ladybug_db_path, "MATCH (:Client)-[r:HTTP_CALLS]->(:Route) RETURN count(r)") >= 2 -def test_async_calls_table_built_on_bank_chat(kuzu_db_path: Path) -> None: - assert _scalar(kuzu_db_path, "MATCH (:Producer)-[r:ASYNC_CALLS]->(:Route) RETURN count(r)") >= 5 +def test_async_calls_table_built_on_bank_chat(ladybug_db_path: Path) -> None: + assert _scalar(ladybug_db_path, "MATCH (:Producer)-[r:ASYNC_CALLS]->(:Route) RETURN count(r)") >= 5 -def test_pr_d1_emits_unresolved_match_for_all(kuzu_db_path: Path) -> None: - assert _scalar(kuzu_db_path, "MATCH ()-[r:HTTP_CALLS]->() WHERE r.match <> 'unresolved' RETURN count(r)") == 0 - assert _scalar(kuzu_db_path, "MATCH ()-[r:ASYNC_CALLS]->() WHERE r.match <> 'unresolved' RETURN count(r)") == 0 +def test_pr_d1_emits_unresolved_match_for_all(ladybug_db_path: Path) -> None: + assert _scalar(ladybug_db_path, "MATCH ()-[r:HTTP_CALLS]->() WHERE r.match <> 'unresolved' RETURN count(r)") == 0 + assert _scalar(ladybug_db_path, "MATCH ()-[r:ASYNC_CALLS]->() WHERE r.match <> 'unresolved' RETURN count(r)") == 0 -def test_phantom_routes_dedup_across_call_sites(kuzu_db_path_http_caller_smoke: Path) -> None: - db = kuzu_db_path_http_caller_smoke +def test_phantom_routes_dedup_across_call_sites(ladybug_db_path_http_caller_smoke: Path) -> None: + db = ladybug_db_path_http_caller_smoke route_ids = _scalar( db, "MATCH (c:Client)-[r:HTTP_CALLS]->(rt:Route) " @@ -65,8 +65,8 @@ def test_phantom_routes_dedup_across_call_sites(kuzu_db_path_http_caller_smoke: assert edges >= 2 -def test_graph_meta_call_edge_counters(kuzu_db_path: Path) -> None: - m = KuzuGraph(str(kuzu_db_path)).meta() +def test_graph_meta_call_edge_counters(ladybug_db_path: Path) -> None: + m = LadybugGraph(str(ladybug_db_path)).meta() assert m["http_calls_total"] > 0 assert m["async_calls_total"] > 0 assert isinstance(m["http_calls_by_strategy"], dict) @@ -75,12 +75,12 @@ def test_graph_meta_call_edge_counters(kuzu_db_path: Path) -> None: assert 0.0 <= m["async_calls_resolved_pct"] <= 1.0 -def test_ontology_version_matches_graph_meta(kuzu_db_path: Path) -> None: - assert KuzuGraph(str(kuzu_db_path)).meta()["ontology_version"] == ONTOLOGY_VERSION +def test_ontology_version_matches_graph_meta(ladybug_db_path: Path) -> None: + assert LadybugGraph(str(ladybug_db_path)).meta()["ontology_version"] == ONTOLOGY_VERSION -def test_call_edges_client_outbound_http_calls_returns_routes(kuzu_db_path_http_caller_smoke: Path) -> None: - db = kuzu_db_path_http_caller_smoke +def test_call_edges_client_outbound_http_calls_returns_routes(ladybug_db_path_http_caller_smoke: Path) -> None: + db = ladybug_db_path_http_caller_smoke n = _scalar( db, "MATCH (c:Client) WHERE c.path_template='/api/users' AND c.method='GET' " @@ -98,8 +98,8 @@ def test_call_edges_method_two_http_clients_two_routes(tmp_path: Path) -> None: assert client_routes >= 2 -def test_call_edges_cross_service_http_four_hop(kuzu_db_path_cross_service_smoke: Path) -> None: - db = kuzu_db_path_cross_service_smoke +def test_call_edges_cross_service_http_four_hop(ladybug_db_path_cross_service_smoke: Path) -> None: + db = ladybug_db_path_cross_service_smoke n = _scalar( db, "MATCH (m:Symbol)-[:DECLARES_CLIENT]->(c:Client)-[:HTTP_CALLS]->(rt:Route)" @@ -113,10 +113,10 @@ def _build_producer_stub(tmp_path: Path, java_body: str) -> Path: java_dir = tmp_path / "p" java_dir.mkdir(parents=True, exist_ok=True) (java_dir / "X.java").write_text(java_body, encoding="utf-8") - from _builders import build_kuzu_full_into + from _builders import build_ladybug_full_into - db_path = tmp_path / "g.kuzu" - build_kuzu_full_into(tmp_path, db_path) + db_path = tmp_path / "g.lbug" + build_ladybug_full_into(tmp_path, db_path) return db_path @@ -179,8 +179,8 @@ def test_call_edges_unresolved_producer_empty_async_out(tmp_path: Path) -> None: assert outbound >= 0 -def test_call_edges_cross_service_async_four_hop(kuzu_db_path_cross_service_smoke: Path) -> None: - db = kuzu_db_path_cross_service_smoke +def test_call_edges_cross_service_async_four_hop(ladybug_db_path_cross_service_smoke: Path) -> None: + db = ladybug_db_path_cross_service_smoke n = _scalar( db, "MATCH (m:Symbol)-[:DECLARES_PRODUCER]->(pr:Producer)-[:ASYNC_CALLS]->(rt:Route)" diff --git a/tests/test_call_graph_receiver_resolution.py b/tests/test_call_graph_receiver_resolution.py index aab7a604..92d7e108 100644 --- a/tests/test_call_graph_receiver_resolution.py +++ b/tests/test_call_graph_receiver_resolution.py @@ -1,6 +1,6 @@ """Isolated call-graph resolution checks (minimal Java trees under tmp_path). -The session `kuzu_graph` fixture uses bank-chat-system only; these tests build +The session `ladybug_graph` fixture uses bank-chat-system only; these tests build tiny graphs so we can assert on a single known failure mode without coupling to the large corpus. """ @@ -8,13 +8,13 @@ from pathlib import Path -import kuzu +import ladybug -from _builders import build_kuzu_to +from _builders import build_ladybug_to -def _connect(db_path: Path) -> kuzu.Connection: - return kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) +def _connect(db_path: Path) -> ladybug.Connection: + return ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) def test_resolved_receiver_unindexed_callee_preserves_strategy(tmp_path: Path) -> None: @@ -42,8 +42,8 @@ def test_resolved_receiver_unindexed_callee_preserves_strategy(tmp_path: Path) - encoding="utf-8", ) - db_path = tmp_path / "b3.kuzu" - build_kuzu_to(root, db_path, max_pass=3) + db_path = tmp_path / "b3.lbug" + build_ladybug_to(root, db_path, max_pass=3) conn = _connect(db_path) r = conn.execute( @@ -101,8 +101,8 @@ def test_receiver_disambiguation_uses_type_index_not_method_unique(tmp_path: Pat encoding="utf-8", ) - db_path = tmp_path / "cg.kuzu" - build_kuzu_to(root, db_path, max_pass=3) + db_path = tmp_path / "cg.lbug" + build_ladybug_to(root, db_path, max_pass=3) conn = _connect(db_path) r = conn.execute( @@ -143,8 +143,8 @@ def test_uppercase_local_receiver_not_treated_as_static_qualifier(tmp_path: Path encoding="utf-8", ) - db_path = tmp_path / "n3.kuzu" - build_kuzu_to(root, db_path, max_pass=3) + db_path = tmp_path / "n3.lbug" + build_ladybug_to(root, db_path, max_pass=3) conn = _connect(db_path) r = conn.execute( diff --git a/tests/test_call_graph_smoke_roundtrip.py b/tests/test_call_graph_smoke_roundtrip.py index 24c5303d..3aafe20c 100644 --- a/tests/test_call_graph_smoke_roundtrip.py +++ b/tests/test_call_graph_smoke_roundtrip.py @@ -3,18 +3,18 @@ from pathlib import Path -import kuzu +import ladybug -from kuzu_queries import KuzuGraph +from ladybug_queries import LadybugGraph _FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "call_graph_smoke" -def _connect(db_path: Path) -> kuzu.Connection: - return kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) +def _connect(db_path: Path) -> ladybug.Connection: + return ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) -def _rows(conn: kuzu.Connection, q: str) -> list: +def _rows(conn: ladybug.Connection, q: str) -> list: r = conn.execute(q) out: list = [] while r.has_next(): @@ -26,9 +26,9 @@ def test_smoke_fixture_root_exists() -> None: assert _FIXTURE_ROOT.is_dir(), _FIXTURE_ROOT -def test_this_super_field_chain_resolves_receiver_d6(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_this_super_field_chain_resolves_receiver_d6(ladybug_db_path_call_graph_smoke: Path) -> None: """D6: `this.root.mid.inner.target()` / `super.root.mid.inner.target()` — field chain, no calls in receiver.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) for type_prefix, method_name in ( ("smoke.FieldChainReceivers#", "byThisChain"), @@ -48,9 +48,9 @@ def test_this_super_field_chain_resolves_receiver_d6(kuzu_db_path_call_graph_smo assert all(float(r[0]) >= 0.94 for r in rows), rows -def test_scope_receivers_calls_resolved_import_map(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_scope_receivers_calls_resolved_import_map(ladybug_db_path_call_graph_smoke: Path) -> None: """§7.1 #4–6: field / param / local `Svc` receiver → `Svc.work` via scope + import_map.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) for method in ("byField", "byParam", "byLocal"): q = ( @@ -64,9 +64,9 @@ def test_scope_receivers_calls_resolved_import_map(kuzu_db_path_call_graph_smoke assert n >= 1, f"expected import_map CALLS for {method}" -def test_local_shadows_field_same_name_resolves_receiver(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_local_shadows_field_same_name_resolves_receiver(ladybug_db_path_call_graph_smoke: Path) -> None: """Local `dup` shadows field `dup` (String): `dup.work()` must target smoke.Svc.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) n = int( _rows( @@ -81,9 +81,9 @@ def test_local_shadows_field_same_name_resolves_receiver(kuzu_db_path_call_graph assert n >= 1 -def test_wildcard_static_import_strategy(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_wildcard_static_import_strategy(ladybug_db_path_call_graph_smoke: Path) -> None: """§7.1 #15: `import static …*` bare call → static_import_wildcard.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -97,10 +97,10 @@ def test_wildcard_static_import_strategy(kuzu_db_path_call_graph_smoke: Path) -> def test_pass3_supertype_dedup_jpa_repository_save_one_row( - kuzu_db_path_call_graph_smoke: Path, + ladybug_db_path_call_graph_smoke: Path, ) -> None: """SupertypeDedupPatterns: interface + concrete save → one CALLS row, REPOSITORY role.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -114,9 +114,9 @@ def test_pass3_supertype_dedup_jpa_repository_save_one_row( assert str(rows[0][1]) == "REPOSITORY", rows -def test_pass3_overload_ambiguous_still_n_rows(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_pass3_overload_ambiguous_still_n_rows(ladybug_db_path_call_graph_smoke: Path) -> None: """overload_ambiguous sites keep N rows after supertype dedup (OverloadPatterns#sameArity).""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -128,9 +128,9 @@ def test_pass3_overload_ambiguous_still_n_rows(kuzu_db_path_call_graph_smoke: Pa assert len(rows) == 2, f"expected 2 overload_ambiguous targets, got {rows}" -def test_overload_sameArity_emits_two_overload_ambiguous_edges(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_overload_sameArity_emits_two_overload_ambiguous_edges(ladybug_db_path_call_graph_smoke: Path) -> None: """§7.1 #13: two one-arg overloads → two resolved edges tagged overload_ambiguous.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -142,9 +142,9 @@ def test_overload_sameArity_emits_two_overload_ambiguous_edges(kuzu_db_path_call assert len(rows) == 2, f"expected 2 overload_ambiguous targets, got {rows}" -def test_overload_distinct_arities_single_targets(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_overload_distinct_arities_single_targets(ladybug_db_path_call_graph_smoke: Path) -> None: """§7.1 #12: arity distinguishes overloads (no overload_ambiguous on arity()).""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) amb = _rows( conn, @@ -166,9 +166,9 @@ def test_overload_distinct_arities_single_targets(kuzu_db_path_call_graph_smoke: assert n == 2, "ovl(1) and ovl(1,2) should each resolve" -def test_expr_qualified_method_ref_chained_receiver(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_expr_qualified_method_ref_chained_receiver(ladybug_db_path_call_graph_smoke: Path) -> None: """§7.1 #18 (graph): expression-qualified `getX()::trim` → chained_receiver UnresolvedCallSite.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) calls = _rows( conn, @@ -187,9 +187,9 @@ def test_expr_qualified_method_ref_chained_receiver(kuzu_db_path_call_graph_smok assert any(str(r[0]) == "chained_receiver" for r in ucs), ucs -def test_anonymous_class_calls_attributed_to_synthetic_member(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_anonymous_class_calls_attributed_to_synthetic_member(ladybug_db_path_call_graph_smoke: Path) -> None: """D3: `pingFromAnon()` inside `new Runnable(){ run(){...}}` → CALLS from synthetic `run()`, not NestedCalls#m.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) outer = _rows( conn, @@ -208,13 +208,13 @@ def test_anonymous_class_calls_attributed_to_synthetic_member(kuzu_db_path_call_ assert int(inner[0][0]) >= 1, "expected CALLS from synthetic anonymous run() to pingFromAnon" -def test_find_callers_external_java_util_needle_lists_internal_callers(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_find_callers_external_java_util_needle_lists_internal_callers(ladybug_db_path_call_graph_smoke: Path) -> None: """exclude_external filters callers (src) only: JDK needle still returns in-repo callers.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke try: - KuzuGraph._instance = None - KuzuGraph._instance_path = None - g = KuzuGraph(str(db)) + LadybugGraph._instance = None + LadybugGraph._instance_path = None + g = LadybugGraph(str(db)) edges = g.find_callers( "java.util.Objects#requireNonNull(1)", depth=1, @@ -225,16 +225,16 @@ def test_find_callers_external_java_util_needle_lists_internal_callers(kuzu_db_p assert any("StaticImportTest" in e.src.fqn for e in edges), [e.src.fqn for e in edges] assert all(not e.src.fqn.startswith("java.") for e in edges) finally: - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None # ---- B1: implicit default constructor resolution ---- -def test_implicit_default_ctor_is_resolved(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_implicit_default_ctor_is_resolved(ladybug_db_path_call_graph_smoke: Path) -> None: """B1: `new Svc()` (Svc has no explicit ctor) resolves to Svc#() with strategy='constructor' and resolved=true, not a phantom.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) for caller_method in ("byLocal", "shadowLocalOverField"): rows = _rows( @@ -254,10 +254,10 @@ def test_implicit_default_ctor_is_resolved(kuzu_db_path_call_graph_smoke: Path) # ---- B2: implicit super to java.lang.Object ---- -def test_implicit_super_to_object_uses_implicit_super_strategy(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_implicit_super_to_object_uses_implicit_super_strategy(ladybug_db_path_call_graph_smoke: Path) -> None: """B2: WildUtils() has no extends clause; its synthesized implicit-super call must use strategy='implicit_super' and confidence=0.90, not phantom/0.0.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -279,11 +279,11 @@ def test_implicit_super_to_object_uses_implicit_super_strategy(kuzu_db_path_call # ---- B3: static-import to JDK keeps high confidence ---- -def test_static_import_to_jdk_keeps_high_confidence(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_static_import_to_jdk_keeps_high_confidence(ladybug_db_path_call_graph_smoke: Path) -> None: """B3: StaticImportTest.m calls requireNonNull via explicit static import. The edge must carry strategy='static_import', confidence>=0.95, resolved=false (callee is JDK phantom), not phantom/0.0.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -300,15 +300,15 @@ def test_static_import_to_jdk_keeps_high_confidence(kuzu_db_path_call_graph_smok def test_min_confidence_filter_keeps_high_confidence_static_import_callers( - kuzu_db_path_call_graph_smoke: Path, + ladybug_db_path_call_graph_smoke: Path, ) -> None: """B3: find_callers with min_confidence=0.9 must still return StaticImportTest for the JDK requireNonNull needle (previously returned empty because edge was 0.0).""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke try: - KuzuGraph._instance = None - KuzuGraph._instance_path = None - g = KuzuGraph(str(db)) + LadybugGraph._instance = None + LadybugGraph._instance_path = None + g = LadybugGraph(str(db)) edges = g.find_callers( "java.util.Objects#requireNonNull(1)", depth=1, @@ -325,13 +325,13 @@ def test_min_confidence_filter_keeps_high_confidence_static_import_callers( e.dst.fqn for e in edges ] finally: - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None -def test_d1_phantom_method_ref_and_invocation_share_symbol(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_d1_phantom_method_ref_and_invocation_share_symbol(ladybug_db_path_call_graph_smoke: Path) -> None: """D1: method ref (arg_count=-1) and normal call to same unindexed callee share one dst Symbol.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, @@ -350,9 +350,9 @@ def test_d1_phantom_method_ref_and_invocation_share_symbol(kuzu_db_path_call_gra assert set(arities) == {-1, 0}, f"edges should keep site arities on CALLS; got {arities}" -def test_d2_method_ref_unambiguous_emits_resolved_arity_on_calls_edge(kuzu_db_path_call_graph_smoke: Path) -> None: +def test_d2_method_ref_unambiguous_emits_resolved_arity_on_calls_edge(ladybug_db_path_call_graph_smoke: Path) -> None: """D2: single matching method for a :: ref — CALLS.arg_count is the method arity, not -1.""" - db = kuzu_db_path_call_graph_smoke + db = ladybug_db_path_call_graph_smoke conn = _connect(db) rows = _rows( conn, diff --git a/tests/test_call_invariant.py b/tests/test_call_invariant.py index eac2d974..e4638121 100644 --- a/tests/test_call_invariant.py +++ b/tests/test_call_invariant.py @@ -2,19 +2,19 @@ from pathlib import Path -import kuzu +import ladybug -from kuzu_queries import KuzuGraph +from ladybug_queries import LadybugGraph def _scalar(db_path: Path, query: str) -> int: - conn = kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) r = conn.execute(query) return int(r.get_next()[0] or 0) if r.has_next() else 0 -def test_call_invariant_blocks_cross_microservice_edges(kuzu_db_path_fqn_collision_smoke: Path) -> None: - db = kuzu_db_path_fqn_collision_smoke +def test_call_invariant_blocks_cross_microservice_edges(ladybug_db_path_fqn_collision_smoke: Path) -> None: + db = ladybug_db_path_fqn_collision_smoke cross_calls = _scalar( db, "MATCH (a:Symbol)-[:CALLS]->(b:Symbol) " @@ -23,12 +23,12 @@ def test_call_invariant_blocks_cross_microservice_edges(kuzu_db_path_fqn_collisi "RETURN count(*)", ) assert cross_calls == 0 - assert KuzuGraph(str(db)).meta()["pass3_skipped_cross_service"] >= 1 + assert LadybugGraph(str(db)).meta()["pass3_skipped_cross_service"] >= 1 -def test_call_invariant_inert_on_clean_fixtures(kuzu_db_path_cross_service_smoke: Path) -> None: - assert KuzuGraph(str(kuzu_db_path_cross_service_smoke)).meta()["pass3_skipped_cross_service"] == 0 +def test_call_invariant_inert_on_clean_fixtures(ladybug_db_path_cross_service_smoke: Path) -> None: + assert LadybugGraph(str(ladybug_db_path_cross_service_smoke)).meta()["pass3_skipped_cross_service"] == 0 -def test_call_invariant_inert_on_bank_chat_system(kuzu_graph: KuzuGraph) -> None: - assert kuzu_graph.meta()["pass3_skipped_cross_service"] == 0 +def test_call_invariant_inert_on_bank_chat_system(ladybug_graph: LadybugGraph) -> None: + assert ladybug_graph.meta()["pass3_skipped_cross_service"] == 0 diff --git a/tests/test_cli_quiet_parity.py b/tests/test_cli_quiet_parity.py index f3182a9a..14800186 100644 --- a/tests/test_cli_quiet_parity.py +++ b/tests/test_cli_quiet_parity.py @@ -31,7 +31,7 @@ def _assert_quiet_stderr_no_progress_markers(stderr: str) -> None: def test_pass_heartbeat_fires_when_pass_slowed(tmp_path: Path) -> None: - kuzu = tmp_path / "g.kuzu" + kuzu = tmp_path / "g.lbug" env = os.environ.copy() env["JAVA_CODEBASE_RAG_TEST_GRAPH_SLOW_SEC"] = "6" proc = subprocess.run( @@ -40,7 +40,7 @@ def test_pass_heartbeat_fires_when_pass_slowed(tmp_path: Path) -> None: str(BUILDER), "--source-root", str(FIXTURE_ROOT), - "--kuzu-path", + "--ladybug-path", str(kuzu), "--verbose", ], @@ -59,14 +59,14 @@ def test_pass_heartbeat_fires_when_pass_slowed(tmp_path: Path) -> None: def test_pass_start_before_pass_body(tmp_path: Path) -> None: - kuzu = tmp_path / "g2.kuzu" + kuzu = tmp_path / "g2.lbug" proc = subprocess.run( [ sys.executable, str(BUILDER), "--source-root", str(FIXTURE_ROOT), - "--kuzu-path", + "--ladybug-path", str(kuzu), "--verbose", ], diff --git a/tests/test_client_hint_recovery.py b/tests/test_client_hint_recovery.py index 9ff8d585..a794eb6e 100644 --- a/tests/test_client_hint_recovery.py +++ b/tests/test_client_hint_recovery.py @@ -3,8 +3,8 @@ from pathlib import Path from unittest.mock import patch -from build_ast_graph import GraphTables, _match_call_edge, pass6_match_edges, write_kuzu -from kuzu_queries import KuzuGraph +from build_ast_graph import GraphTables, _match_call_edge, pass6_match_edges, write_ladybug +from ladybug_queries import LadybugGraph _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" _HTTP_CALLER = Path(__file__).resolve().parent / "fixtures" / "http_caller_smoke" @@ -66,11 +66,11 @@ def test_cross_service_match_outcome_unchanged_after_client_migration() -> None: def test_find_route_callers_still_returns_expected_feign_caller(tmp_path: Path) -> None: tables = _build_tables() pass6_match_edges(tables, verbose=False) - db_path = tmp_path / "client_hints.kuzu" - write_kuzu(db_path, tables, source_root=_FIXTURE, verbose=False) - KuzuGraph._instance = None - KuzuGraph._instance_path = None - g = KuzuGraph(str(db_path)) + db_path = tmp_path / "client_hints.lbug" + write_ladybug(db_path, tables, source_root=_FIXTURE, verbose=False) + LadybugGraph._instance = None + LadybugGraph._instance_path = None + g = LadybugGraph(str(db_path)) caller_id = _member_id( tables, parent_fqn="smoke.a.BFeignClient", diff --git a/tests/test_client_node_extraction.py b/tests/test_client_node_extraction.py index fb3e8ab3..7fb2afca 100644 --- a/tests/test_client_node_extraction.py +++ b/tests/test_client_node_extraction.py @@ -3,7 +3,7 @@ import shutil from pathlib import Path -import kuzu +import ladybug from ast_java import ONTOLOGY_VERSION from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain @@ -23,16 +23,16 @@ def _build(tmp: Path, yml: str | None, extra_files: dict[str, str]) -> Path: p = tmp / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(body, encoding="utf-8") - from _builders import build_kuzu_full_into + from _builders import build_ladybug_full_into - db_path = tmp / "g.kuzu" - build_kuzu_full_into(tmp, db_path) + db_path = tmp / "g.lbug" + build_ladybug_full_into(tmp, db_path) return db_path def _rows(db_path: Path, query: str) -> list[tuple]: - db = kuzu.Database(str(db_path), read_only=True) - conn = kuzu.Connection(db) + db = ladybug.Database(str(db_path), read_only=True) + conn = ladybug.Connection(db) r = conn.execute(query) out: list[tuple] = [] while r.has_next(): diff --git a/tests/test_client_role_rename.py b/tests/test_client_role_rename.py index 8393c321..34dd63ca 100644 --- a/tests/test_client_role_rename.py +++ b/tests/test_client_role_rename.py @@ -7,10 +7,10 @@ import pytest -from _builders import build_graph_tables_to, build_kuzu_to +from _builders import build_graph_tables_to, build_ladybug_to from build_ast_graph import GraphTables from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain -from kuzu_queries import KuzuGraph +from ladybug_queries import LadybugGraph _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" @@ -20,8 +20,8 @@ def _clear_caches() -> object: yield _load_brownfield_overrides.cache_clear() collect_annotation_meta_chain.cache_clear() - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None def _copy_fixture(dest: Path) -> None: @@ -33,9 +33,9 @@ def _build_tables(project_root: Path) -> GraphTables: return build_graph_tables_to(project_root, max_pass=6) -def _build_graph(project_root: Path, db_path: Path) -> KuzuGraph: - build_kuzu_to(project_root, db_path, max_pass=6) - return KuzuGraph(str(db_path)) +def _build_graph(project_root: Path, db_path: Path) -> LadybugGraph: + build_ladybug_to(project_root, db_path, max_pass=6) + return LadybugGraph(str(db_path)) def _symbol_by_fqn(symbols, fqn: str): @@ -48,7 +48,7 @@ def _symbol_by_fqn(symbols, fqn: str): def test_feign_client_emits_client_role(tmp_path: Path) -> None: root = tmp_path / "proj" _copy_fixture(root) - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") sym = _symbol_by_fqn(g.list_by_role("CLIENT"), "smoke.a.BFeignClient") assert sym is not None assert sym.role == "CLIENT" @@ -58,14 +58,14 @@ def test_feign_client_emits_client_role(tmp_path: Path) -> None: def test_no_legacy_feign_client_role_in_graph(tmp_path: Path) -> None: root = tmp_path / "proj" _copy_fixture(root) - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") assert g.list_by_role("FEIGN_CLIENT") == [] def test_resttemplate_class_gets_client_role_from_messaging(tmp_path: Path) -> None: root = tmp_path / "proj" _copy_fixture(root) - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") sym = _symbol_by_fqn(g.find_by_name_or_fqn("smoke.a.ClientA"), "smoke.a.ClientA") assert sym is not None # ClientA injects KafkaTemplate → CLIENT role (symmetric with CONTROLLER) @@ -88,7 +88,7 @@ def test_brownfield_feign_client_role_dropped(tmp_path: Path) -> None: ) buf = io.StringIO() with redirect_stderr(buf): - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") stderr = buf.getvalue().lower() assert "unknown role" in stderr and "feign_client" in stderr sym = _symbol_by_fqn(g.find_by_name_or_fqn("smoke.a.BFeignClient"), "smoke.a.BFeignClient") @@ -108,7 +108,7 @@ def test_brownfield_client_role_accepted(tmp_path: Path) -> None: ) buf = io.StringIO() with redirect_stderr(buf): - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") assert "unknown role" not in buf.getvalue().lower() sym = _symbol_by_fqn(g.find_by_name_or_fqn("smoke.a.ClientA"), "smoke.a.ClientA") assert sym is not None @@ -127,7 +127,7 @@ def test_brownfield_http_client_capability_accepted(tmp_path: Path) -> None: ) buf = io.StringIO() with redirect_stderr(buf): - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") assert "unknown capability" not in buf.getvalue().lower() sym = _symbol_by_fqn(g.find_by_name_or_fqn("smoke.a.ClientA"), "smoke.a.ClientA") assert sym is not None @@ -137,7 +137,7 @@ def test_brownfield_http_client_capability_accepted(tmp_path: Path) -> None: def test_message_producer_capability_unchanged(tmp_path: Path) -> None: root = tmp_path / "proj" _copy_fixture(root) - g = _build_graph(root, tmp_path / "g.kuzu") + g = _build_graph(root, tmp_path / "g.lbug") sym = _symbol_by_fqn(g.find_by_name_or_fqn("smoke.a.ClientA"), "smoke.a.ClientA") assert sym is not None assert "MESSAGE_PRODUCER" in sym.capabilities @@ -146,13 +146,13 @@ def test_message_producer_capability_unchanged(tmp_path: Path) -> None: def test_trace_flow_includes_client_in_stage_2(tmp_path: Path) -> None: root = tmp_path / "proj" _copy_fixture(root) - g = _build_graph(root, tmp_path / "g.kuzu") - assert "CLIENT" in KuzuGraph._FLOW_STAGES[2] + g = _build_graph(root, tmp_path / "g.lbug") + assert "CLIENT" in LadybugGraph._FLOW_STAGES[2] stages = g.trace_flow(["smoke.a.BFeignClient"], depth=2, stage_limit=20) if len(stages) >= 3: assert any(s.symbol.role == "CLIENT" for s in stages[2]) def test_codebase_search_entry_roles_includes_client() -> None: - assert "CLIENT" in KuzuGraph._ENTRYPOINT_ROLES - assert "FEIGN_CLIENT" not in KuzuGraph._ENTRYPOINT_ROLES + assert "CLIENT" in LadybugGraph._ENTRYPOINT_ROLES + assert "FEIGN_CLIENT" not in LadybugGraph._ENTRYPOINT_ROLES diff --git a/tests/test_config.py b/tests/test_config.py index 3da68f70..b9403fb4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -86,7 +86,7 @@ def test_discover_project_root_finds_nonempty_index_dir(self, tmp_path): subdir.mkdir() idx = tmp_path / ".java-codebase-rag" idx.mkdir() - (idx / "code_graph.kuzu").write_bytes(b"\x00" * 16) + (idx / "code_graph.lbug").write_bytes(b"\x00" * 16) result = discover_project_root(subdir) assert result == tmp_path @@ -101,7 +101,7 @@ def test_discover_project_root_skips_empty_index_dir(self, tmp_path): # Real index at parent level real_idx = tmp_path / ".java-codebase-rag" real_idx.mkdir() - (real_idx / "code_graph.kuzu").write_bytes(b"\x00" * 16) + (real_idx / "code_graph.lbug").write_bytes(b"\x00" * 16) result = discover_project_root(subdir) assert result == tmp_path @@ -113,7 +113,7 @@ def test_discover_project_root_config_wins_over_index_dir(self, tmp_path): # Index dir at tmp_path level idx = tmp_path / ".java-codebase-rag" idx.mkdir() - (idx / "code_graph.kuzu").write_bytes(b"\x00" * 16) + (idx / "code_graph.lbug").write_bytes(b"\x00" * 16) # Config at subdir level config_file = subdir / YAML_CONFIG_FILENAMES[0] config_file.write_text("# child config") @@ -131,7 +131,7 @@ def test_discover_project_root_both_markers_same_level(self, tmp_path): config_file.write_text("# config") idx = tmp_path / ".java-codebase-rag" idx.mkdir() - (idx / "code_graph.kuzu").write_bytes(b"\x00" * 16) + (idx / "code_graph.lbug").write_bytes(b"\x00" * 16) result = discover_project_root(tmp_path) assert result == tmp_path diff --git a/tests/test_cross_service_resolution_flag.py b/tests/test_cross_service_resolution_flag.py index 591161fd..bdc161c3 100644 --- a/tests/test_cross_service_resolution_flag.py +++ b/tests/test_cross_service_resolution_flag.py @@ -6,10 +6,10 @@ from contextlib import redirect_stderr from pathlib import Path -import kuzu +import ladybug import pytest -from _builders import build_graph_tables_to, build_kuzu_to +from _builders import build_graph_tables_to, build_ladybug_to from build_ast_graph import GraphTables from graph_enrich import _load_config_cross_service_resolution @@ -49,7 +49,7 @@ def _build_tables(project_root: Path) -> GraphTables: def _build_db(project_root: Path, db_path: Path) -> None: - build_kuzu_to(project_root, db_path, max_pass=6) + build_ladybug_to(project_root, db_path, max_pass=6) def test_cross_service_resolution_auto_default(tmp_path: Path) -> None: @@ -125,7 +125,7 @@ def test_brownfield_only_suppresses_feign_auto_cross_service(tmp_path: Path) -> def test_meta_reports_cross_service_resolution(tmp_path: Path) -> None: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph root = tmp_path / "proj" _copy_fixture(root) @@ -133,27 +133,27 @@ def test_meta_reports_cross_service_resolution(tmp_path: Path) -> None: "cross_service_resolution: brownfield_only\n", encoding="utf-8", ) - db = tmp_path / "g.kuzu" + db = tmp_path / "g.lbug" _build_db(root, db) - KuzuGraph._instance = None - KuzuGraph._instance_path = None - assert KuzuGraph(str(db)).meta()["cross_service_resolution"] == "brownfield_only" + LadybugGraph._instance = None + LadybugGraph._instance_path = None + assert LadybugGraph(str(db)).meta()["cross_service_resolution"] == "brownfield_only" root2 = tmp_path / "proj2" _copy_fixture(root2) - db2 = tmp_path / "g2.kuzu" + db2 = tmp_path / "g2.lbug" _build_db(root2, db2) - KuzuGraph._instance = None - KuzuGraph._instance_path = None - assert KuzuGraph(str(db2)).meta()["cross_service_resolution"] == "auto" + LadybugGraph._instance = None + LadybugGraph._instance_path = None + assert LadybugGraph(str(db2)).meta()["cross_service_resolution"] == "auto" def test_meta_resolution_null_for_old_graphs(tmp_path: Path) -> None: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - db_path = tmp_path / "legacy.kuzu" - db = kuzu.Database(str(db_path)) - conn = kuzu.Connection(db) + db_path = tmp_path / "legacy.lbug" + db = ladybug.Database(str(db_path)) + conn = ladybug.Connection(db) conn.execute( "CREATE NODE TABLE GraphMeta(" "key STRING PRIMARY KEY, " @@ -218,9 +218,9 @@ def test_meta_resolution_null_for_old_graphs(tmp_path: Path) -> None: }, ) conn.close() - KuzuGraph._instance = None - KuzuGraph._instance_path = None - assert KuzuGraph(str(db_path)).meta()["cross_service_resolution"] is None + LadybugGraph._instance = None + LadybugGraph._instance_path = None + assert LadybugGraph(str(db_path)).meta()["cross_service_resolution"] is None def test_unknown_value_falls_back_to_auto(tmp_path: Path) -> None: diff --git a/tests/test_feign_not_exposer.py b/tests/test_feign_not_exposer.py index a4837211..9a7bcf9b 100644 --- a/tests/test_feign_not_exposer.py +++ b/tests/test_feign_not_exposer.py @@ -2,10 +2,10 @@ from pathlib import Path -import kuzu +import ladybug -from build_ast_graph import GraphTables, write_kuzu -from kuzu_queries import KuzuGraph +from build_ast_graph import GraphTables, write_ladybug +from ladybug_queries import LadybugGraph _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" @@ -65,18 +65,18 @@ def test_feign_route_node_is_not_emitted(graph_tables_cross_service_smoke: Graph def test_meta_reports_exposes_suppressed_feign_count(tmp_path: Path, graph_tables_cross_service_smoke: GraphTables) -> None: - db_path = tmp_path / "feign_meta.kuzu" + db_path = tmp_path / "feign_meta.lbug" tables = graph_tables_cross_service_smoke - write_kuzu(db_path, tables, source_root=_FIXTURE, verbose=False) - KuzuGraph._instance = None - KuzuGraph._instance_path = None - assert KuzuGraph(str(db_path)).meta()["pass4_exposes_suppressed_feign"] == 0 + write_ladybug(db_path, tables, source_root=_FIXTURE, verbose=False) + LadybugGraph._instance = None + LadybugGraph._instance_path = None + assert LadybugGraph(str(db_path)).meta()["pass4_exposes_suppressed_feign"] == 0 def test_meta_returns_none_for_old_graphs(tmp_path: Path) -> None: - db_path = tmp_path / "legacy_meta.kuzu" - db = kuzu.Database(str(db_path)) - conn = kuzu.Connection(db) + db_path = tmp_path / "legacy_meta.lbug" + db = ladybug.Database(str(db_path)) + conn = ladybug.Connection(db) conn.execute( "CREATE NODE TABLE GraphMeta(" "key STRING PRIMARY KEY, " @@ -144,9 +144,9 @@ def test_meta_returns_none_for_old_graphs(tmp_path: Path) -> None: }, ) conn.close() - KuzuGraph._instance = None - KuzuGraph._instance_path = None - assert KuzuGraph(str(db_path)).meta()["pass4_exposes_suppressed_feign"] is None + LadybugGraph._instance = None + LadybugGraph._instance_path = None + assert LadybugGraph(str(db_path)).meta()["pass4_exposes_suppressed_feign"] is None def test_no_change_to_async_routes(graph_tables_cross_service_smoke: GraphTables) -> None: diff --git a/tests/test_incremental_graph.py b/tests/test_incremental_graph.py index b529a678..894212c1 100644 --- a/tests/test_incremental_graph.py +++ b/tests/test_incremental_graph.py @@ -6,7 +6,7 @@ from pathlib import Path -import kuzu +import ladybug from ast_java import ONTOLOGY_VERSION from build_ast_graph import FileHashTracker, GraphTables, pass1_parse, pass2_edges @@ -163,13 +163,13 @@ class TestEdgeSchema: def test_edge_schema_has_source_file(self, tmp_path: Path) -> None: """Build a full graph, query each edge table for source_file column existence and non-empty values.""" - from _builders import build_kuzu_full_into + from _builders import build_ladybug_full_into corpus_root = Path(__file__).parent / "bank-chat-system" - db_path = tmp_path / "test_graph.kuzu" - build_kuzu_full_into(corpus_root, db_path) + db_path = tmp_path / "test_graph.lbug" + build_ladybug_full_into(corpus_root, db_path) - conn = kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) # All 12 edge tables should have source_file column edge_tables = [ @@ -190,13 +190,13 @@ def test_edge_schema_has_source_file(self, tmp_path: Path) -> None: def test_source_file_value_matches_symbol_filename(self, tmp_path: Path) -> None: """For edges originating from Symbol nodes, edge's source_file equals source Symbol's filename.""" - from _builders import build_kuzu_full_into + from _builders import build_ladybug_full_into corpus_root = Path(__file__).parent / "bank-chat-system" - db_path = tmp_path / "test_graph.kuzu" - build_kuzu_full_into(corpus_root, db_path) + db_path = tmp_path / "test_graph.lbug" + build_ladybug_full_into(corpus_root, db_path) - conn = kuzu.Connection(kuzu.Database(str(db_path), read_only=True)) + conn = ladybug.Connection(ladybug.Database(str(db_path), read_only=True)) # Test CALLS edge: source_file should match caller Symbol's filename query = """ @@ -236,7 +236,7 @@ def test_incremental_single_file_change(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create initial files (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") @@ -248,9 +248,9 @@ def test_incremental_single_file_change(self, tmp_path: Path) -> None: assert len(asts) == 2 # Build full graph (pass2 needed for EXTENDS edges) - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug pass2_edges(tables, asts, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -264,7 +264,7 @@ def test_incremental_single_file_change(self, tmp_path: Path) -> None: (source_root / "A.java").write_text("package pkg; class A { void foo() {} }", encoding="utf-8") # Run incremental - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" assert result.files_changed == 1 @@ -278,16 +278,16 @@ def test_incremental_new_file(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create initial file (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -301,7 +301,7 @@ def test_incremental_new_file(self, tmp_path: Path) -> None: # Run incremental from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" assert result.files_changed == 0 @@ -313,17 +313,17 @@ def test_incremental_deleted_file(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create initial files (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") (source_root / "B.java").write_text("package pkg; class B {}", encoding="utf-8") # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -337,7 +337,7 @@ def test_incremental_deleted_file(self, tmp_path: Path) -> None: # Run incremental from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" assert result.files_changed == 0 @@ -345,8 +345,8 @@ def test_incremental_deleted_file(self, tmp_path: Path) -> None: assert result.files_removed == 1 # Verify B's nodes are deleted - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) check_result = conn.execute("MATCH (s:Symbol) WHERE s.fqn = 'pkg.B' RETURN count(*)") if check_result.has_next(): count = check_result.get_next()[0] @@ -358,7 +358,7 @@ def test_incremental_phantom_nodes_preserved(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create file with external reference (source_root / "A.java").write_text( @@ -367,14 +367,14 @@ def test_incremental_phantom_nodes_preserved(self, tmp_path: Path) -> None: ) # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Count phantom nodes before - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) phantom_count_before = 0 phantom_result = conn.execute("MATCH (s:Symbol) WHERE s.filename = '' RETURN count(*)") if phantom_result.has_next(): @@ -397,11 +397,11 @@ def test_incremental_phantom_nodes_preserved(self, tmp_path: Path) -> None: # Run incremental from build_ast_graph import incremental_rebuild - incremental_rebuild(source_root, kuzu_path, verbose=False) + incremental_rebuild(source_root, ladybug_path, verbose=False) # Verify phantom nodes still exist - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) phantom_count_after = 0 phantom_result = conn.execute("MATCH (s:Symbol) WHERE s.filename = '' RETURN count(*)") if phantom_result.has_next(): @@ -415,7 +415,7 @@ def test_incremental_dependent_expansion(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create files with inheritance (source_root / "Base.java").write_text("package pkg; class Base {}", encoding="utf-8") @@ -424,11 +424,11 @@ def test_incremental_dependent_expansion(self, tmp_path: Path) -> None: ) # Initial build (pass2 needed for EXTENDS edges) - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -444,7 +444,7 @@ def test_incremental_dependent_expansion(self, tmp_path: Path) -> None: # Run incremental from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) # Derived.java should be reprocessed due to EXTENDS edge assert result.dependents_reprocessed >= 1 @@ -455,7 +455,7 @@ def test_incremental_expansion_cap_fallback(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create base class and many derived classes (source_root / "Base.java").write_text("package pkg; class Base {}", encoding="utf-8") @@ -465,11 +465,11 @@ def test_incremental_expansion_cap_fallback(self, tmp_path: Path) -> None: ) # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -486,7 +486,7 @@ def test_incremental_expansion_cap_fallback(self, tmp_path: Path) -> None: # Run incremental with low expansion cap from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False, expansion_cap=2) + result = incremental_rebuild(source_root, ladybug_path, verbose=False, expansion_cap=2) # Should fall back to full rebuild due to cap exceeded assert result.mode == "full_fallback" @@ -497,16 +497,16 @@ def test_incremental_crash_marker_triggers_fallback(self, tmp_path: Path) -> Non source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create file (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -526,7 +526,7 @@ def test_incremental_crash_marker_triggers_fallback(self, tmp_path: Path) -> Non # Run incremental - should fall back to full rebuild from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "full_fallback" # Crash marker should be removed @@ -538,16 +538,16 @@ def test_incremental_crash_marker_removed_on_success(self, tmp_path: Path) -> No source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create file (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -563,7 +563,7 @@ def test_incremental_crash_marker_removed_on_success(self, tmp_path: Path) -> No # Run incremental from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" @@ -577,20 +577,20 @@ def test_incremental_no_changes_is_noop(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create file (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Get node count before - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) count_before_result = conn.execute("MATCH (s:Symbol) RETURN count(*)") count_before = 0 if count_before_result.has_next(): @@ -606,14 +606,14 @@ def test_incremental_no_changes_is_noop(self, tmp_path: Path) -> None: # Run incremental with no changes from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" assert result.files_changed == 0 # Verify node count unchanged - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) count_after_result = conn.execute("MATCH (s:Symbol) RETURN count(*)") count_after = 0 if count_after_result.has_next(): @@ -628,16 +628,16 @@ def test_incremental_pass5_6_always_global(self, tmp_path: Path) -> None: source_root.mkdir() index_dir = tmp_path / "index" index_dir.mkdir() - kuzu_path = index_dir / "code_graph.kuzu" + ladybug_path = index_dir / "code_graph.lbug" # Create files (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Initialize hash tracker tracker = FileHashTracker(index_dir) @@ -653,13 +653,13 @@ def test_incremental_pass5_6_always_global(self, tmp_path: Path) -> None: # Run incremental from build_ast_graph import incremental_rebuild - result = incremental_rebuild(source_root, kuzu_path, verbose=False) + result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" # Verify graph is still valid (Client/Producer tables exist even if empty) - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) # Check that Client and Producer node tables exist by querying them client_result = conn.execute("MATCH (c:Client) RETURN count(*)") @@ -675,21 +675,21 @@ def test_load_existing_types_populates_indexes(self, tmp_path: Path) -> None: source_root = tmp_path / "src" source_root.mkdir() - kuzu_path = tmp_path / "code_graph.kuzu" + ladybug_path = tmp_path / "code_graph.lbug" # Create file (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Build full graph - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Load existing types into empty tables new_tables = GraphTables() - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) _load_existing_types(conn, new_tables) conn.close() @@ -704,7 +704,7 @@ def test_find_dependents_returns_incoming_edge_sources(self, tmp_path: Path) -> source_root = tmp_path / "src" source_root.mkdir() - kuzu_path = tmp_path / "code_graph.kuzu" + ladybug_path = tmp_path / "code_graph.lbug" # Create files (source_root / "Base.java").write_text("package pkg; class Base {}", encoding="utf-8") @@ -713,15 +713,15 @@ def test_find_dependents_returns_incoming_edge_sources(self, tmp_path: Path) -> ) # Build full graph - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Get Base node ID - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) base_result = conn.execute("MATCH (s:Symbol) WHERE s.fqn = 'pkg.Base' RETURN s.id") base_id = None if base_result.has_next(): @@ -743,21 +743,21 @@ def test_delete_file_scope_removes_only_matching(self, tmp_path: Path) -> None: source_root = tmp_path / "src" source_root.mkdir() - kuzu_path = tmp_path / "code_graph.kuzu" + ladybug_path = tmp_path / "code_graph.lbug" # Create files (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") (source_root / "B.java").write_text("package pkg; class B {}", encoding="utf-8") # Build full graph - from build_ast_graph import write_kuzu + from build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) - write_kuzu(kuzu_path, tables, source_root=source_root, verbose=False) + write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) # Get node count before - db = kuzu.Database(str(kuzu_path)) - conn = kuzu.Connection(db) + db = ladybug.Database(str(ladybug_path)) + conn = ladybug.Connection(db) conn.execute("MATCH (s:Symbol) RETURN count(*)") # Delete only A.java's scope diff --git a/tests/test_java_codebase_rag_cli.py b/tests/test_java_codebase_rag_cli.py index 7e8c5920..d1342f1b 100644 --- a/tests/test_java_codebase_rag_cli.py +++ b/tests/test_java_codebase_rag_cli.py @@ -34,11 +34,11 @@ def _cocoindex_available() -> bool: return (Path(sys.executable).parent / "cocoindex").is_file() -def _base_env(corpus_root: Path, kuzu_db_path: Path | None = None) -> dict[str, str]: +def _base_env(corpus_root: Path, ladybug_db_path: Path | None = None) -> dict[str, str]: env = os.environ.copy() env["JAVA_CODEBASE_RAG_SOURCE_ROOT"] = str(corpus_root) - if kuzu_db_path is not None: - env["JAVA_CODEBASE_RAG_INDEX_DIR"] = str(kuzu_db_path.parent) + if ladybug_db_path is not None: + env["JAVA_CODEBASE_RAG_INDEX_DIR"] = str(ladybug_db_path.parent) return env @@ -66,7 +66,7 @@ def _run_cli(args: list[str], *, env: dict[str, str], stdin: str | None = None) def test_cli_init_refuses_when_index_paths_non_empty(tmp_path: Path) -> None: idx = tmp_path / "idx" idx.mkdir() - (idx / "code_graph.kuzu").mkdir() + (idx / "code_graph.lbug").mkdir() env = os.environ.copy() env["JAVA_CODEBASE_RAG_INDEX_DIR"] = str(idx) env["JAVA_CODEBASE_RAG_SOURCE_ROOT"] = str(tmp_path) @@ -287,10 +287,10 @@ def test_index_dir_precedence_cli_over_env_over_yaml_over_default( assert r3.index_dir == b.resolve() -def test_kuzu_path_derived_as_index_dir_code_graph_kuzu(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: +def test_ladybug_path_derived_as_index_dir_code_graph_kuzu(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("JAVA_CODEBASE_RAG_INDEX_DIR", raising=False) r = resolve_operator_config(source_root=tmp_path, cli_index_dir=str(tmp_path / "idx")) - assert r.kuzu_path == r.index_dir / "code_graph.kuzu" + assert r.ladybug_path == r.index_dir / "code_graph.lbug" def test_help_output_includes_three_group_labels() -> None: @@ -347,11 +347,11 @@ def test_increment_emits_kuzu_stale_warning_block( # Should NOT contain old stale warning assert "WARNING: AST graph (Kuzu) incremental rebuild is not yet implemented." not in err assert "java-codebase-rag reprocess" not in err - assert cli_mod.KUZU_INCREMENTAL_TRACKING_ISSUE_URL not in err + assert cli_mod.LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL not in err -def test_meta_reports_embedding_setting_source(corpus_root: Path, kuzu_db_path: Path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_meta_reports_embedding_setting_source(corpus_root: Path, ladybug_db_path: Path) -> None: + env = _base_env(corpus_root, ladybug_db_path) env["SBERT_MODEL"] = "env-model" proc = _run_cli( ["meta", "--source-root", str(corpus_root), "--embedding-model", "cli-model"], @@ -478,9 +478,9 @@ def test_increment_vectors_only_skips_graph( assert rc == 0 err = buf.getvalue() # Should contain stale warning - assert "WARNING: AST graph (Kuzu) incremental rebuild is not yet implemented." in err + assert "WARNING: AST graph (LadybugDB) incremental rebuild is not yet implemented." in err assert "java-codebase-rag reprocess" in err - assert cli_mod.KUZU_INCREMENTAL_TRACKING_ISSUE_URL in err + assert cli_mod.LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL in err def test_increment_cli_help_mentions_vectors_only( @@ -590,16 +590,16 @@ def test_increment_updates_lance_after_touch_java_file(corpus_root: Path, tmp_pa assert "CliScenariosTouchMarker" in joined -def test_cli_meta_outputs_valid_json_when_piped(corpus_root, kuzu_db_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_meta_outputs_valid_json_when_piped(corpus_root, ladybug_db_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) proc = _run_cli(["meta", "--source-root", str(corpus_root)], env=env) assert proc.returncode == 0, proc.stderr payload = json.loads(proc.stdout) assert "edge_counts" in payload -def test_cli_tables_lists_known_table(corpus_root, kuzu_db_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_tables_lists_known_table(corpus_root, ladybug_db_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) proc = _run_cli(["tables", "--source-root", str(corpus_root)], env=env) assert proc.returncode == 0, proc.stderr payload = json.loads(proc.stdout) @@ -607,8 +607,8 @@ def test_cli_tables_lists_known_table(corpus_root, kuzu_db_path) -> None: assert "graph" in payload -def test_cli_unresolved_calls_list_and_stats(corpus_root, kuzu_db_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_unresolved_calls_list_and_stats(corpus_root, ladybug_db_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) stats_proc = _run_cli( ["unresolved-calls", "stats", "--source-root", str(corpus_root), "--by", "reason"], env=env, @@ -654,8 +654,8 @@ def test_cli_unresolved_calls_list_and_stats(corpus_root, kuzu_db_path) -> None: assert bad_reason.returncode != 0 -def test_cli_diagnose_ignore_walked_path(corpus_root, kuzu_db_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_diagnose_ignore_walked_path(corpus_root, ladybug_db_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) path = "chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java" proc = _run_cli(["diagnose-ignore", "--source-root", str(corpus_root), path], env=env) assert proc.returncode == 0, proc.stderr @@ -663,16 +663,16 @@ def test_cli_diagnose_ignore_walked_path(corpus_root, kuzu_db_path) -> None: assert payload["ignored"] is False -def test_cli_diagnose_ignore_unconditional_prune(corpus_root, kuzu_db_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_diagnose_ignore_unconditional_prune(corpus_root, ladybug_db_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) proc = _run_cli(["diagnose-ignore", "--source-root", str(corpus_root), ".git/foo"], env=env) assert proc.returncode == 0, proc.stderr payload = json.loads(proc.stdout) assert payload["ignored"] is True -def test_cli_analyze_pr_with_diff_file(corpus_root, kuzu_db_path, tmp_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_analyze_pr_with_diff_file(corpus_root, ladybug_db_path, tmp_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) diff_path = tmp_path / "sample.diff" diff_path.write_text( """diff --git a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java @@ -698,8 +698,8 @@ def test_cli_analyze_pr_with_diff_file(corpus_root, kuzu_db_path, tmp_path) -> N assert "blast_radius_total" in payload -def test_cli_analyze_pr_with_diff_stdin(corpus_root, kuzu_db_path) -> None: - env = _base_env(corpus_root, kuzu_db_path) +def test_cli_analyze_pr_with_diff_stdin(corpus_root, ladybug_db_path) -> None: + env = _base_env(corpus_root, ladybug_db_path) diff_text = """diff --git a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java --- a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java +++ b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java @@ -862,7 +862,7 @@ def fake_coco(*_a: object, **_k: object) -> subprocess.CompletedProcess[str]: ["reprocess", "--source-root", str(tmp_path), "--index-dir", str(idx), "--vectors-only"], ) assert rc == 0 - assert "code_graph.kuzu" in err.getvalue() + assert "code_graph.lbug" in err.getvalue() def test_reprocess_graph_only_emits_vectors_stale_warning( @@ -1019,7 +1019,7 @@ def isatty(self) -> bool: assert "Skipped: graph" in text -def test_cli_reprocess_builds_kuzu_path(corpus_root, tmp_path) -> None: +def test_cli_reprocess_builds_ladybug_path(corpus_root, tmp_path) -> None: if not _cocoindex_available(): pytest.skip("cocoindex CLI missing") idx = tmp_path / "rep_idx" @@ -1030,7 +1030,7 @@ def test_cli_reprocess_builds_kuzu_path(corpus_root, tmp_path) -> None: ["erase", "--source-root", str(corpus_root), "--index-dir", str(idx), "--yes"], env=env, ) - kuzu_path = idx / "code_graph.kuzu" + ladybug_path = idx / "code_graph.lbug" proc = _run_cli( [ "reprocess", @@ -1043,7 +1043,7 @@ def test_cli_reprocess_builds_kuzu_path(corpus_root, tmp_path) -> None: env=env, ) assert proc.returncode == 0, proc.stdout + proc.stderr - assert kuzu_path.exists() + assert ladybug_path.exists() meta_proc = _run_cli( ["meta", "--source-root", str(corpus_root), "--index-dir", str(idx)], env=env, diff --git a/tests/test_kuzu_queries.py b/tests/test_ladybug_queries.py similarity index 69% rename from tests/test_kuzu_queries.py rename to tests/test_ladybug_queries.py index 067ffc13..260bcc6f 100644 --- a/tests/test_kuzu_queries.py +++ b/tests/test_ladybug_queries.py @@ -1,6 +1,6 @@ -"""Tests for the read-only `KuzuGraph` helpers used by the MCP server. +"""Tests for the read-only `LadybugGraph` helpers used by the MCP server. -We exercise every public method on `kuzu_queries.KuzuGraph` against the +We exercise every public method on `kuzu_queries.LadybugGraph` against the bank-chat-system corpus. The fixture provides: * one **interface with multiple in-corpus implementations**: @@ -20,11 +20,11 @@ from pathlib import Path -import kuzu +import ladybug import pytest from ast_java import ONTOLOGY_VERSION -from kuzu_queries import KuzuGraph, _is_external_fqn +from ladybug_queries import LadybugGraph, _is_external_fqn def _names(symbols) -> set[str]: @@ -42,8 +42,8 @@ def _microservices(symbols) -> set[str]: # ---------------- meta ---------------- -def test_meta(kuzu_graph) -> None: - meta = kuzu_graph.meta() +def test_meta(ladybug_graph) -> None: + meta = ladybug_graph.meta() assert "error" not in meta, meta assert meta["ontology_version"] >= 1 assert meta["built_at"] > 0 @@ -55,16 +55,16 @@ def test_meta(kuzu_graph) -> None: assert isinstance(meta.get("routes_by_framework"), dict) -def test_module_counts_keys(kuzu_graph) -> None: - counts = kuzu_graph.module_counts() +def test_module_counts_keys(ladybug_graph) -> None: + counts = ladybug_graph.module_counts() assert counts.get("chat-assign", 0) > 0 # Multi-module reactor child modules should appear by their build-marker # directory name. assert any(k in counts for k in ("chat-app", "chat-engine", "chat-domain")) -def test_microservice_counts_keys(kuzu_graph) -> None: - counts = kuzu_graph.microservice_counts() +def test_microservice_counts_keys(ladybug_graph) -> None: + counts = ladybug_graph.microservice_counts() # Both microservice roots should be represented; the multi-module # reactor (`chat-core`) groups chat-app/chat-engine/chat-domain/ # chat-contracts under one microservice key. @@ -75,13 +75,13 @@ def test_microservice_counts_keys(kuzu_graph) -> None: # ---------------- find_by_name_or_fqn ---------------- -def test_find_by_name_or_fqn_simple_name(kuzu_graph) -> None: - rows = kuzu_graph.find_by_name_or_fqn("ChatManagementService") +def test_find_by_name_or_fqn_simple_name(ladybug_graph) -> None: + rows = ladybug_graph.find_by_name_or_fqn("ChatManagementService") assert any(r.kind == "class" and r.fqn.endswith(".ChatManagementService") for r in rows), rows -def test_find_by_name_or_fqn_fqn(kuzu_graph) -> None: - rows = kuzu_graph.find_by_name_or_fqn( +def test_find_by_name_or_fqn_fqn(ladybug_graph) -> None: + rows = ladybug_graph.find_by_name_or_fqn( "com.bank.chat.assign.service.ChatManagementService" ) assert len(rows) == 1 @@ -94,9 +94,9 @@ def test_find_by_name_or_fqn_fqn(kuzu_graph) -> None: # ---------------- find_implementors / find_subclasses ---------------- -def test_find_implementors_event_processor(kuzu_graph) -> None: +def test_find_implementors_event_processor(ladybug_graph) -> None: """`EventProcessor` is implemented by all *Processor classes in chat-engine.""" - rows = kuzu_graph.find_implementors("EventProcessor") + rows = ladybug_graph.find_implementors("EventProcessor") names = _names(rows) # We assert the *existence* of multiple impls and a couple of # canonical ones, not the exact set — the fixture may grow. @@ -109,13 +109,13 @@ def test_find_implementors_event_processor(kuzu_graph) -> None: assert _microservices(rows) == {"chat-core"} -def test_find_subclasses_via_jpa_repository_phantom(kuzu_graph) -> None: +def test_find_subclasses_via_jpa_repository_phantom(ladybug_graph) -> None: """Spring Data repositories EXTEND `JpaRepository` (a phantom). We exercise `find_subclasses` against the phantom to prove the helper works even when the parent is an external/unresolved type. """ - rows = kuzu_graph.find_subclasses("JpaRepository") + rows = ladybug_graph.find_subclasses("JpaRepository") names = _names(rows) # chat-assign/repo defines five JpaRepository subinterfaces; we only # require >=2 to stay robust to fixture changes. @@ -125,9 +125,9 @@ def test_find_subclasses_via_jpa_repository_phantom(kuzu_graph) -> None: # ---------------- find_injectors ---------------- -def test_find_injectors_for_repository(kuzu_graph) -> None: +def test_find_injectors_for_repository(ladybug_graph) -> None: """`AssignChatRepository` is injected via constructor into the service layer.""" - edges = kuzu_graph.find_injectors("AssignChatRepository") + edges = ladybug_graph.find_injectors("AssignChatRepository") assert len(edges) >= 1, edges consumers = {e.src.name for e in edges} assert "ChatManagementService" in consumers, consumers @@ -137,23 +137,23 @@ def test_find_injectors_for_repository(kuzu_graph) -> None: assert e.mechanism in {"constructor", "field", "setter", "lombok_required_args"} -def test_find_injectors_module_filter(kuzu_graph) -> None: - edges_in_assign = kuzu_graph.find_injectors( +def test_find_injectors_module_filter(ladybug_graph) -> None: + edges_in_assign = ladybug_graph.find_injectors( "AssignChatRepository", module="chat-assign" ) - edges_in_other = kuzu_graph.find_injectors( + edges_in_other = ladybug_graph.find_injectors( "AssignChatRepository", module="chat-engine" ) assert edges_in_assign, edges_in_assign assert edges_in_other == [] -def test_find_injectors_microservice_filter(kuzu_graph) -> None: +def test_find_injectors_microservice_filter(ladybug_graph) -> None: """Microservice scoping must isolate chat-assign from chat-core.""" - edges_in_assign = kuzu_graph.find_injectors( + edges_in_assign = ladybug_graph.find_injectors( "AssignChatRepository", microservice="chat-assign" ) - edges_in_core = kuzu_graph.find_injectors( + edges_in_core = ladybug_graph.find_injectors( "AssignChatRepository", microservice="chat-core" ) assert edges_in_assign, edges_in_assign @@ -163,8 +163,8 @@ def test_find_injectors_microservice_filter(kuzu_graph) -> None: # ---------------- list_by_role / list_by_annotation ---------------- -def test_list_by_role_controller(kuzu_graph) -> None: - controllers = kuzu_graph.list_by_role("CONTROLLER") +def test_list_by_role_controller(ladybug_graph) -> None: + controllers = ladybug_graph.list_by_role("CONTROLLER") names = _names(controllers) # Both microservices contribute controllers; we only require >=2 to # stay loose, plus check a representative one is present. @@ -172,35 +172,35 @@ def test_list_by_role_controller(kuzu_graph) -> None: assert any(n.endswith("Controller") for n in names), names -def test_list_by_role_repository_is_empty_or_phantoms_only(kuzu_graph) -> None: +def test_list_by_role_repository_is_empty_or_phantoms_only(ladybug_graph) -> None: """Spring Data repositories in the corpus aren't @Repository-annotated. This pins behaviour the README documents: role inference is annotation-driven. If you ever change it to also tag interfaces that extend Repository / JpaRepository, expect to update this test. """ - rows = kuzu_graph.list_by_role("REPOSITORY") + rows = ladybug_graph.list_by_role("REPOSITORY") # Assert the helper *runs*; permit 0 results because the fixture has # no @Repository annotation, and that's the documented contract. assert isinstance(rows, list) -def test_list_by_annotation_transactional(kuzu_graph) -> None: +def test_list_by_annotation_transactional(ladybug_graph) -> None: """`@Transactional` is on methods inside ChatManagementService etc. The graph stores annotations on the type *and* on each method, so we expect to find at least one symbol carrying the annotation. """ - rows = kuzu_graph.list_by_annotation("Transactional") + rows = ladybug_graph.list_by_annotation("Transactional") assert len(rows) >= 1, rows # ---------------- neighbors / impact_analysis ---------------- -def test_neighbors_walks_inject_chain(kuzu_graph) -> None: +def test_neighbors_walks_inject_chain(ladybug_graph) -> None: """`ChatManagementService` injects 6+ collaborators (constructor params).""" - rows = kuzu_graph.neighbors( + rows = ladybug_graph.neighbors( "ChatManagementService", depth=1, edge_types=["INJECTS"], @@ -209,9 +209,9 @@ def test_neighbors_walks_inject_chain(kuzu_graph) -> None: assert len(rows) >= 3, _names(rows) -def test_neighbors_direction_in_for_repository(kuzu_graph) -> None: +def test_neighbors_direction_in_for_repository(ladybug_graph) -> None: """Reverse direction: who points *at* AssignChatRepository?""" - rows = kuzu_graph.neighbors( + rows = ladybug_graph.neighbors( "AssignChatRepository", depth=1, edge_types=["INJECTS"], @@ -221,27 +221,27 @@ def test_neighbors_direction_in_for_repository(kuzu_graph) -> None: assert "ChatManagementService" in names, names -def test_impact_analysis_finds_consumers(kuzu_graph) -> None: +def test_impact_analysis_finds_consumers(ladybug_graph) -> None: """`AssignChatRepository` consumers should appear in impact_analysis (depth=2).""" - rows = kuzu_graph.impact_analysis("AssignChatRepository", depth=2) + rows = ladybug_graph.impact_analysis("AssignChatRepository", depth=2) assert "ChatManagementService" in _names(rows) # ---------------- expand_fqns / trace_flow ---------------- -def test_expand_fqns_returns_neighbor_fqns(kuzu_graph) -> None: - fqns = kuzu_graph.expand_fqns( +def test_expand_fqns_returns_neighbor_fqns(ladybug_graph) -> None: + fqns = ladybug_graph.expand_fqns( ["com.bank.chat.assign.service.ChatManagementService"], depth=1, ) assert any(f.endswith("AssignChatRepository") for f in fqns), fqns -def test_trace_flow_from_controller_seed(kuzu_graph) -> None: +def test_trace_flow_from_controller_seed(ladybug_graph) -> None: """A CONTROLLER-stage seed must produce subsequent SERVICE / integration stages.""" seeds = ["com.bank.chat.assign.web.ChatManagementController"] - stages = kuzu_graph.trace_flow(seeds, depth=2, stage_limit=20) + stages = ladybug_graph.trace_flow(seeds, depth=2, stage_limit=20) assert stages, "trace_flow returned no stages for a known controller seed" stage0 = stages[0] assert any(s.symbol.role == "CONTROLLER" for s in stage0) @@ -260,9 +260,9 @@ def _type_part_fqn(method_fqn: str) -> str: return method_fqn.split("#", 1)[0] -def test_trace_flow_follow_calls_false_type_only_edges(kuzu_graph) -> None: +def test_trace_flow_follow_calls_false_type_only_edges(ladybug_graph) -> None: seeds = ["com.bank.chat.assign.web.ChatManagementController"] - stages = kuzu_graph.trace_flow(seeds, depth=2, stage_limit=20, follow_calls=False) + stages = ladybug_graph.trace_flow(seeds, depth=2, stage_limit=20, follow_calls=False) assert stages for later in stages[1:]: for entry in later: @@ -270,7 +270,7 @@ def test_trace_flow_follow_calls_false_type_only_edges(kuzu_graph) -> None: assert v.edge_type in {"INJECTS", "EXTENDS", "IMPLEMENTS"} -def test_trace_flow_structural_edges_not_starved_by_calls(kuzu_graph) -> None: +def test_trace_flow_structural_edges_not_starved_by_calls(ladybug_graph) -> None: """Structural-first budget contract: per hop INJECTS/EXTENDS/IMPLEMENTS fill `stage_limit` first, and the CALLS branch only tops up the remaining slots. @@ -279,7 +279,7 @@ def test_trace_flow_structural_edges_not_starved_by_calls(kuzu_graph) -> None: structural via-edge — i.e. CALLS does not squeeze INJECTS out of the bucket. """ seeds = ["com.bank.chat.assign.web.ChatManagementController"] - stages = kuzu_graph.trace_flow(seeds, depth=2, stage_limit=4, follow_calls=True) + stages = ladybug_graph.trace_flow(seeds, depth=2, stage_limit=4, follow_calls=True) assert len(stages) >= 2, stages stage1 = stages[1] assert stage1, "stage-1 should be non-empty for a known controller seed" @@ -294,51 +294,51 @@ def test_trace_flow_structural_edges_not_starved_by_calls(kuzu_graph) -> None: ] -def test_find_callers_no_phantom_chained_strategy(kuzu_graph) -> None: - edges = kuzu_graph.find_callers("save", depth=1, limit=100) +def test_find_callers_no_phantom_chained_strategy(ladybug_graph) -> None: + edges = ladybug_graph.find_callers("save", depth=1, limit=100) for e in edges: assert e.strategy not in ("phantom", "chained_receiver") -def test_find_callers_assign_method(kuzu_graph) -> None: +def test_find_callers_assign_method(ladybug_graph) -> None: needle = "com.bank.chat.assign.service.ChatManagementService#assign(AssignmentRequest)" - edges = kuzu_graph.find_callers(needle, depth=1, limit=50) + edges = ladybug_graph.find_callers(needle, depth=1, limit=50) caller_types = {_type_part_fqn(e.src.fqn) for e in edges} assert "com.bank.chat.assign.web.ChatManagementController" in caller_types, caller_types -def test_find_callees_assign_method(kuzu_graph) -> None: +def test_find_callees_assign_method(ladybug_graph) -> None: needle = "com.bank.chat.assign.service.ChatManagementService#assign(AssignmentRequest)" - edges = kuzu_graph.find_callees(needle, depth=1, limit=80) + edges = ladybug_graph.find_callees(needle, depth=1, limit=80) callee_names = {e.dst.name for e in edges} assert "save" in callee_names or "findByConversationId" in callee_names or "resolveSplitName" in callee_names, ( callee_names ) -def test_find_callers_type_form_via_declares(kuzu_graph) -> None: - edges = kuzu_graph.find_callers("com.bank.chat.assign.repo.AssignChatRepository", depth=1, limit=100) +def test_find_callers_type_form_via_declares(ladybug_graph) -> None: + edges = ladybug_graph.find_callers("com.bank.chat.assign.repo.AssignChatRepository", depth=1, limit=100) assert edges, "expected at least one caller of a repository method" assert any("ChatManagement" in e.src.fqn for e in edges), [e.src.fqn for e in edges] -def test_min_confidence_filter_drops_edges(kuzu_graph) -> None: +def test_min_confidence_filter_drops_edges(ladybug_graph) -> None: needle = "com.bank.chat.assign.service.ChatManagementService#assign(AssignmentRequest)" - all_e = kuzu_graph.find_callees(needle, depth=2, limit=200, min_confidence=0.0) - hi = kuzu_graph.find_callees(needle, depth=2, limit=200, min_confidence=0.99) + all_e = ladybug_graph.find_callees(needle, depth=2, limit=200, min_confidence=0.0) + hi = ladybug_graph.find_callees(needle, depth=2, limit=200, min_confidence=0.99) assert len(all_e) >= len(hi) -def test_exclude_external_filters_known_prefix(kuzu_graph) -> None: +def test_exclude_external_filters_known_prefix(ladybug_graph) -> None: needle = "com.bank.chat.assign.service.ChatManagementService#assign(AssignmentRequest)" - with_ext = kuzu_graph.find_callees(needle, depth=3, limit=300, exclude_external=False) - no_ext = kuzu_graph.find_callees(needle, depth=3, limit=300, exclude_external=True) + with_ext = ladybug_graph.find_callees(needle, depth=3, limit=300, exclude_external=False) + no_ext = ladybug_graph.find_callees(needle, depth=3, limit=300, exclude_external=True) assert len(with_ext) >= len(no_ext) assert not any(_is_external_fqn(e.dst.fqn) for e in no_ext) -def test_expand_methods_from_service_seed(kuzu_graph) -> None: - extra = kuzu_graph.expand_methods( +def test_expand_methods_from_service_seed(ladybug_graph) -> None: + extra = ladybug_graph.expand_methods( ["com.bank.chat.assign.service.ChatManagementService"], depth=1, limit=50, @@ -353,8 +353,8 @@ def test_expand_methods_from_service_seed(kuzu_graph) -> None: assert any(conf > 0.0 for _, conf in extra), extra -def test_expand_methods_default_excludes_external_prefixes(kuzu_graph) -> None: - extra = kuzu_graph.expand_methods( +def test_expand_methods_default_excludes_external_prefixes(ladybug_graph) -> None: + extra = ladybug_graph.expand_methods( ["com.bank.chat.assign.service.ChatManagementService"], depth=2, limit=200, @@ -362,20 +362,20 @@ def test_expand_methods_default_excludes_external_prefixes(kuzu_graph) -> None: assert not any(_is_external_fqn(t[0]) for t in extra), extra -def test_expand_methods_exclude_external_false_can_include_more(kuzu_graph) -> None: +def test_expand_methods_exclude_external_false_can_include_more(ladybug_graph) -> None: seed = ["com.bank.chat.assign.service.ChatManagementService"] - with_ext = kuzu_graph.expand_methods(seed, depth=2, limit=300, exclude_external=False) - no_ext = kuzu_graph.expand_methods(seed, depth=2, limit=300, exclude_external=True) + with_ext = ladybug_graph.expand_methods(seed, depth=2, limit=300, exclude_external=False) + no_ext = ladybug_graph.expand_methods(seed, depth=2, limit=300, exclude_external=True) assert len(with_ext) >= len(no_ext) -def test_trace_flow_empty_seeds_returns_empty(kuzu_graph) -> None: - assert kuzu_graph.trace_flow([], depth=1) == [] +def test_trace_flow_empty_seeds_returns_empty(ladybug_graph) -> None: + assert ladybug_graph.trace_flow([], depth=1) == [] def _open_stale_ontology_graph(tmp_path: Path, ontology_version: int) -> Path: - db_path = tmp_path / f"stale_ontology_{ontology_version}.kuzu" - conn = kuzu.Connection(kuzu.Database(str(db_path))) + db_path = tmp_path / f"stale_ontology_{ontology_version}.lbug" + conn = ladybug.Connection(ladybug.Database(str(db_path))) conn.execute( "CREATE NODE TABLE GraphMeta(" "key STRING PRIMARY KEY, " @@ -390,50 +390,50 @@ def _open_stale_ontology_graph(tmp_path: Path, ontology_version: int) -> Path: return db_path -def test_kuzu_graph_refuses_ontology_version_below_required(tmp_path: Path) -> None: +def test_ladybug_graph_refuses_ontology_version_below_required(tmp_path: Path) -> None: """v13 graphs refuse to open when ``ONTOLOGY_VERSION`` is current (e.g. 15). - Overlaps ``test_kuzu_graph_get_raises_when_graph_ontology_too_old`` when + Overlaps ``test_ladybug_graph_get_raises_when_graph_ontology_too_old`` when ``ONTOLOGY_VERSION - 1 == 13``; kept as an explicit v13 regression anchor. """ assert ONTOLOGY_VERSION >= 14 db_path = _open_stale_ontology_graph(tmp_path, 13) - prev_inst = KuzuGraph._instance - prev_path = KuzuGraph._instance_path + prev_inst = LadybugGraph._instance + prev_path = LadybugGraph._instance_path try: - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None ver = ONTOLOGY_VERSION with pytest.raises( RuntimeError, match=rf"(?i)ontology.*{ver}|required version {ver}", ): - KuzuGraph.get(str(db_path)) + LadybugGraph.get(str(db_path)) finally: - KuzuGraph._instance = prev_inst - KuzuGraph._instance_path = prev_path + LadybugGraph._instance = prev_inst + LadybugGraph._instance_path = prev_path -def test_kuzu_graph_get_raises_when_graph_ontology_too_old(tmp_path: Path) -> None: +def test_ladybug_graph_get_raises_when_graph_ontology_too_old(tmp_path: Path) -> None: """N4 / proposal §5.3: stale graphs must fail loudly on open.""" stale = max(0, ONTOLOGY_VERSION - 1) db_path = _open_stale_ontology_graph(tmp_path, stale) - prev_inst = KuzuGraph._instance - prev_path = KuzuGraph._instance_path + prev_inst = LadybugGraph._instance + prev_path = LadybugGraph._instance_path try: - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None with pytest.raises(RuntimeError, match="(?i)ontology"): - KuzuGraph.get(str(db_path)) + LadybugGraph.get(str(db_path)) finally: - KuzuGraph._instance = prev_inst - KuzuGraph._instance_path = prev_path + LadybugGraph._instance = prev_inst + LadybugGraph._instance_path = prev_path -def test_list_routes_filter_by_framework(kuzu_graph_route_extraction_smoke) -> None: - g = kuzu_graph_route_extraction_smoke +def test_list_routes_filter_by_framework(ladybug_graph_route_extraction_smoke) -> None: + g = ladybug_graph_route_extraction_smoke feign = g.list_routes(framework="feign", limit=200) assert feign == [] mvc = g.list_routes(framework="spring_mvc", limit=50) @@ -441,8 +441,8 @@ def test_list_routes_filter_by_framework(kuzu_graph_route_extraction_smoke) -> N assert all(r["framework"] == "spring_mvc" for r in mvc) -def test_find_route_handlers_endpoint_route(kuzu_graph_route_extraction_smoke) -> None: - g = kuzu_graph_route_extraction_smoke +def test_find_route_handlers_endpoint_route(ladybug_graph_route_extraction_smoke) -> None: + g = ladybug_graph_route_extraction_smoke rows = g.list_routes( framework="spring_mvc", microservice="service-a", @@ -458,14 +458,14 @@ def test_find_route_handlers_endpoint_route(kuzu_graph_route_extraction_smoke) - assert len(fqns) == 1 -def test_find_route_handlers_feign_route_returns_empty(kuzu_graph_route_extraction_smoke) -> None: - g = kuzu_graph_route_extraction_smoke +def test_find_route_handlers_feign_route_returns_empty(ladybug_graph_route_extraction_smoke) -> None: + g = ladybug_graph_route_extraction_smoke rows = g.list_routes(framework="feign", path_prefix="/dupbase/same", limit=10) assert rows == [] -def test_get_route_by_path_microservice_isolated(kuzu_graph_route_extraction_smoke) -> None: - g = kuzu_graph_route_extraction_smoke +def test_get_route_by_path_microservice_isolated(ladybug_graph_route_extraction_smoke) -> None: + g = ladybug_graph_route_extraction_smoke tpl = "/api/users" ra = g.get_route_by_path(microservice="service-a", path_template=tpl, method="GET") rb = g.get_route_by_path(microservice="service-b", path_template=tpl, method="GET") @@ -475,8 +475,8 @@ def test_get_route_by_path_microservice_isolated(kuzu_graph_route_extraction_smo assert ra["id"] != rb["id"] -def test_find_route_callers_includes_producer_callers(kuzu_db_path_cross_service_smoke: Path) -> None: - g = KuzuGraph(str(kuzu_db_path_cross_service_smoke)) +def test_find_route_callers_includes_producer_callers(ladybug_db_path_cross_service_smoke: Path) -> None: + g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) topic_routes = [r for r in g.list_routes(limit=100) if str(r.get("topic") or "")] callers: list = [] for route in topic_routes: @@ -486,10 +486,10 @@ def test_find_route_callers_includes_producer_callers(kuzu_db_path_cross_service assert any(c.caller_node_kind == "producer" for c in callers) -def test_find_route_callers_returns_route_caller_client_node(kuzu_db_path_cross_service_smoke: Path) -> None: - from kuzu_queries import RouteCaller +def test_find_route_callers_returns_route_caller_client_node(ladybug_db_path_cross_service_smoke: Path) -> None: + from ladybug_queries import RouteCaller - g = KuzuGraph(str(kuzu_db_path_cross_service_smoke)) + g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) routes = g.list_routes(limit=50) callers: list[RouteCaller] = [] for route in routes: @@ -502,8 +502,8 @@ def test_find_route_callers_returns_route_caller_client_node(kuzu_db_path_cross_ assert all(c.caller_node_id for c in http_callers) -def test_trace_request_flow_inbound_includes_caller_node_id(kuzu_db_path_cross_service_smoke: Path) -> None: - g = KuzuGraph(str(kuzu_db_path_cross_service_smoke)) +def test_trace_request_flow_inbound_includes_caller_node_id(ladybug_db_path_cross_service_smoke: Path) -> None: + g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) route_id = None for route in g.list_routes(limit=50): flow = g.trace_request_flow(route["id"], max_hops=2) diff --git a/tests/test_lancedb_e2e.py b/tests/test_lancedb_e2e.py index 2d3641ee..4b70e4a4 100644 --- a/tests/test_lancedb_e2e.py +++ b/tests/test_lancedb_e2e.py @@ -123,8 +123,8 @@ def lance_index(tmp_path_factory, corpus_root: Path) -> Path: str(builder), "--source-root", str(corpus_root), - "--kuzu-path", - str(index_dir / "code_graph.kuzu"), + "--ladybug-path", + str(index_dir / "code_graph.lbug"), ], env=env, capture_output=True, @@ -181,8 +181,8 @@ def lance_index_capability_smoke(tmp_path_factory) -> Path: str(builder), "--source-root", str(CAPABILITY_SMOKE_ROOT), - "--kuzu-path", - str(index_dir / "code_graph.kuzu"), + "--ladybug-path", + str(index_dir / "code_graph.lbug"), ], env=env, capture_output=True, @@ -197,10 +197,10 @@ async def test_search_returns_hits(lance_index: Path, monkeypatch) -> None: monkeypatch.setenv("JAVA_CODEBASE_RAG_INDEX_DIR", str(lance_index)) monkeypatch.delenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", raising=False) - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None from server import create_mcp_server @@ -230,10 +230,10 @@ async def test_search_capability_filter_e2e( monkeypatch.setenv("JAVA_CODEBASE_RAG_INDEX_DIR", str(lance_index_capability_smoke)) monkeypatch.delenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", raising=False) - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None from server import create_mcp_server @@ -318,10 +318,10 @@ async def test_search_returns_multiple_hits(lance_index: Path, monkeypatch) -> N monkeypatch.setenv("JAVA_CODEBASE_RAG_INDEX_DIR", str(lance_index)) monkeypatch.delenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", raising=False) - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - KuzuGraph._instance = None - KuzuGraph._instance_path = None + LadybugGraph._instance = None + LadybugGraph._instance_path = None from server import create_mcp_server diff --git a/tests/test_mcp_hints.py b/tests/test_mcp_hints.py index ba9d2d25..c2fe3ccc 100644 --- a/tests/test_mcp_hints.py +++ b/tests/test_mcp_hints.py @@ -6,9 +6,9 @@ import pytest -from _builders import build_kuzu_to +from _builders import build_ladybug_to from java_ontology import FUZZY_STRATEGY_SET -from kuzu_queries import KuzuGraph +from ladybug_queries import LadybugGraph from mcp_hints import ( _StructuredHint, finalize_structured_hints, @@ -37,14 +37,14 @@ def _hints(output_kind, payload): @pytest.fixture -def override_axis_graph(tmp_path: Path) -> KuzuGraph: - db_path = tmp_path / "code_graph.kuzu" - build_kuzu_to(_OVERRIDE_AXIS_FIXTURE, db_path, max_pass=5) - return KuzuGraph(str(db_path)) +def override_axis_graph(tmp_path: Path) -> LadybugGraph: + db_path = tmp_path / "code_graph.lbug" + build_ladybug_to(_OVERRIDE_AXIS_FIXTURE, db_path, max_pass=5) + return LadybugGraph(str(db_path)) -def _type_symbol_id_with_member_clients(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _type_symbol_id_with_member_clients(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[:DECLARES_CLIENT]->(:Client) " "WHERE t.kind IN $kinds " "RETURN t.id AS id ORDER BY t.fqn LIMIT 1", @@ -54,8 +54,8 @@ def _type_symbol_id_with_member_clients(kuzu_graph) -> str: return str(rows[0]["id"]) -def _controller_class_id_with_exposes(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _controller_class_id_with_exposes(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[:EXPOSES]->(:Route) " "WHERE t.role = 'CONTROLLER' AND t.kind = 'class' " "RETURN t.id AS id LIMIT 1", @@ -64,8 +64,8 @@ def _controller_class_id_with_exposes(kuzu_graph) -> str: return str(rows[0]["id"]) -def _type_symbol_id_with_member_producers(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _type_symbol_id_with_member_producers(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[:DECLARES_PRODUCER]->(:Producer) " "WHERE t.kind IN $kinds " "RETURN t.id AS id ORDER BY t.fqn LIMIT 1", @@ -76,8 +76,8 @@ def _type_symbol_id_with_member_producers(kuzu_graph) -> str: return str(rows[0]["id"]) -def _interface_method_with_override_rollups(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _interface_method_with_override_rollups(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (iface:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'requestAssignment' " "RETURN m.id AS id LIMIT 1", @@ -87,26 +87,26 @@ def _interface_method_with_override_rollups(kuzu_graph) -> str: return str(rows[0]["id"]) -def _method_id_declares_client_and_other_out_edge(kuzu_graph) -> str | None: +def _method_id_declares_client_and_other_out_edge(ladybug_graph) -> str | None: for pattern in ( "MATCH (m:Symbol {kind: 'method'})-[:DECLARES_CLIENT]->() MATCH (m)-[:CALLS]->() RETURN m.id AS id LIMIT 1", "MATCH (m:Symbol {kind: 'method'})-[:DECLARES_CLIENT]->(:Client)-[:HTTP_CALLS]->() RETURN m.id AS id LIMIT 1", ): - rows = kuzu_graph._rows(pattern) # noqa: SLF001 + rows = ladybug_graph._rows(pattern) # noqa: SLF001 if rows: return str(rows[0]["id"]) return None -def _method_declares_client(kuzu_graph) -> str: - mid = _method_id_declares_client_and_other_out_edge(kuzu_graph) +def _method_declares_client(ladybug_graph) -> str: + mid = _method_id_declares_client_and_other_out_edge(ladybug_graph) if mid is None: pytest.skip("no method with DECLARES_CLIENT + outbound edge in fixture") return mid -def _method_id_without_dispatch_rollups(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_id_without_dispatch_rollups(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol) " "WHERE m.kind = 'method' " "AND NOT list_contains(COALESCE(m.modifiers, []), 'static') " @@ -124,20 +124,20 @@ def _method_id_without_dispatch_rollups(kuzu_graph) -> str: return str(rows[0]["id"]) -def _method_id_with_empty_describe_hints(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_id_with_empty_describe_hints(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol) WHERE m.kind = 'method' RETURN m.id AS id LIMIT 100", ) for row in rows: mid = str(row["id"]) - out = describe_v2(mid, graph=kuzu_graph) + out = describe_v2(mid, graph=ladybug_graph) if out.success and out.record and out.hints_structured == [] and out.advisories == []: return mid pytest.fail("no method with empty describe hints in fixture") -def _controller_method_many_calls(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _controller_method_many_calls(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[e:CALLS]->() WHERE m.kind = 'method' " "WITH m, count(e) AS nout WHERE nout >= 10 RETURN m.id AS id LIMIT 1", ) @@ -145,24 +145,24 @@ def _controller_method_many_calls(kuzu_graph) -> str: return str(rows[0]["id"]) -def _route_id(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _route_id(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (r:Route) RETURN r.id AS id ORDER BY r.id LIMIT 1" ) assert rows return str(rows[0]["id"]) -def _client_id(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _client_id(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (c:Client) RETURN c.id AS id ORDER BY c.id LIMIT 1" ) assert rows return str(rows[0]["id"]) -def _class_symbol_id(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _class_symbol_id(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol) WHERE t.kind = 'class' RETURN t.id AS id LIMIT 1") assert rows return str(rows[0]["id"]) @@ -255,8 +255,8 @@ def _edge_result(*, strategy: str | None = None, edge_type: str = "DECLARES_CLIE } -def _method_id_with_fuzzy_calls(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_id_with_fuzzy_calls(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[e:CALLS]->() " "WHERE e.strategy IN $strategies " "RETURN m.id AS id LIMIT 1", @@ -269,23 +269,23 @@ def _method_id_with_fuzzy_calls(kuzu_graph) -> str: -def _producer_id(kuzu_graph) -> str: - rows = kuzu_graph._rows("MATCH (p:Producer) RETURN p.id AS id ORDER BY p.id LIMIT 1") # noqa: SLF001 +def _producer_id(ladybug_graph) -> str: + rows = ladybug_graph._rows("MATCH (p:Producer) RETURN p.id AS id ORDER BY p.id LIMIT 1") # noqa: SLF001 if not rows: pytest.fail("session fixture lacks Producer nodes (post-flip SCHEMA required)") return str(rows[0]["id"]) -def _method_id(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_id(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol) WHERE m.kind = 'method' RETURN m.id AS id LIMIT 1", ) assert rows return str(rows[0]["id"]) -def _annotation_symbol_id(kuzu_graph) -> str | None: - rows = kuzu_graph._rows( # noqa: SLF001 +def _annotation_symbol_id(ladybug_graph) -> str | None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.kind = 'annotation' RETURN s.id AS id LIMIT 1", ) if not rows: @@ -313,54 +313,54 @@ def _annotation_symbol_id(kuzu_graph) -> str | None: -def test_hints_clean_outputs_empty(kuzu_graph) -> None: - mid = _method_id_with_empty_describe_hints(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_hints_clean_outputs_empty(ladybug_graph) -> None: + mid = _method_id_with_empty_describe_hints(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record assert out.hints_structured == [] assert out.advisories == [] - count_rows = kuzu_graph._rows( # noqa: SLF001 + count_rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.role = 'CONTROLLER' RETURN count(*) AS n", ) n_controllers = int(count_rows[0]["n"]) assert n_controllers > 0 assert n_controllers <= 500, "fixture has >500 CONTROLLER symbols; narrow filter for clean find hints" - fout = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph, limit=500, offset=0) + fout = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph, limit=500, offset=0) assert fout.success and len(fout.results) == n_controllers assert fout.hints_structured == [] assert fout.advisories == [] -def _resolve_symbol_id_status_one(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _resolve_symbol_id_status_one(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.kind = 'class' RETURN s.id AS id LIMIT 1", ) assert rows sym_id = str(rows[0]["id"]) - out = resolve_v2(sym_id, hint_kind="symbol", graph=kuzu_graph) + out = resolve_v2(sym_id, hint_kind="symbol", graph=ladybug_graph) if not (out.success and out.status == "one"): pytest.fail(f"expected status one for symbol id {sym_id!r}, got {out.status!r}") return sym_id -def _resolve_symbol_short_name_status_many(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _resolve_symbol_short_name_status_many(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.kind = 'method' RETURN s.name AS name", ) counts = Counter(str(r["name"]) for r in rows if r.get("name")) dup_name = next((name for name, c in counts.items() if c >= 2), None) if dup_name is None: pytest.fail("no duplicated method short names in bank-chat fixture") - out = resolve_v2(dup_name, hint_kind="symbol", graph=kuzu_graph) + out = resolve_v2(dup_name, hint_kind="symbol", graph=ladybug_graph) if not (out.success and out.status == "many" and len(out.candidates) >= 2): pytest.fail(f"expected status many for short name {dup_name!r}, got {out.status!r}") return dup_name -def _resolve_symbol_identifier_status_none(kuzu_graph) -> str: +def _resolve_symbol_identifier_status_none(ladybug_graph) -> str: ident = "com.nonexistent.ZzzMissing" - out = resolve_v2(ident, hint_kind="symbol", graph=kuzu_graph) + out = resolve_v2(ident, hint_kind="symbol", graph=ladybug_graph) if not (out.success and out.status == "none"): pytest.fail(f"expected status none for {ident!r}, got {out.status!r}") return ident @@ -381,8 +381,8 @@ def _resolve_symbol_identifier_status_none(kuzu_graph) -> str: # --------------------------------------------------------------------------- -def _interface_with_implements_in(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _interface_with_implements_in(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (iface:Symbol)<-[:IMPLEMENTS]-(impl:Symbol) " "WHERE iface.kind = 'interface' " "WITH iface, count(impl) AS nin WHERE nin > 0 " @@ -393,8 +393,8 @@ def _interface_with_implements_in(kuzu_graph) -> str: return str(rows[0]["id"]) -def _class_with_implements_out(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _class_with_implements_out(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (cls:Symbol)-[:IMPLEMENTS]->(iface:Symbol) " "WHERE cls.kind = 'class' " "WITH cls, count(iface) AS nout WHERE nout > 0 " @@ -406,7 +406,7 @@ def _class_with_implements_out(kuzu_graph) -> str: # (DECLARES_CLIENT/EXPOSES/DECLARES_PRODUCER suppresses IMPLEMENTS). for row in rows: tid = str(row["id"]) - out = describe_v2(tid, graph=kuzu_graph) + out = describe_v2(tid, graph=ladybug_graph) if any( h.tool == "neighbors" and h.args.get("edge_types") == ["IMPLEMENTS"] for h in out.hints_structured @@ -415,8 +415,8 @@ def _class_with_implements_out(kuzu_graph) -> str: pytest.skip("no class with unsuppressed IMPLEMENTS hint in fixture") -def _service_with_injects_out(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _service_with_injects_out(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (cls:Symbol)-[:INJECTS]->(dep:Symbol) " "WHERE cls.kind = 'class' AND cls.role = 'SERVICE' " "RETURN cls.id AS id LIMIT 1", @@ -426,8 +426,8 @@ def _service_with_injects_out(kuzu_graph) -> str: return str(rows[0]["id"]) -def _type_with_injects_in(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _type_with_injects_in(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (dep:Symbol)<-[:INJECTS]-(cls:Symbol) " "WHERE dep.kind IN ['interface', 'class'] " "RETURN DISTINCT dep.id AS id LIMIT 1", @@ -437,8 +437,8 @@ def _type_with_injects_in(kuzu_graph) -> str: return str(rows[0]["id"]) -def _method_with_mid_calls_out(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_with_mid_calls_out(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[c:CALLS]->() WHERE m.kind = 'method' " "WITH m, count(c) AS nout WHERE nout >= 3 AND nout <= 9 " "RETURN m.id AS id LIMIT 1", @@ -448,8 +448,8 @@ def _method_with_mid_calls_out(kuzu_graph) -> str: return str(rows[0]["id"]) -def _method_with_overrides_out(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_with_overrides_out(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[:OVERRIDES]->() WHERE m.kind = 'method' " "RETURN m.id AS id LIMIT 1", ) @@ -458,15 +458,15 @@ def _method_with_overrides_out(kuzu_graph) -> str: return str(rows[0]["id"]) -def _method_with_unresolved(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_with_unresolved(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[c:CALLS]->() WHERE m.kind = 'method' " "WITH m, count(c) AS nout WHERE nout >= 1 " "RETURN m.id AS id, m.fqn AS fqn LIMIT 200", ) for r in rows: mid = str(r["id"]) - out = describe_v2(mid, graph=kuzu_graph) + out = describe_v2(mid, graph=ladybug_graph) if out.record and isinstance(out.record.data, dict): unc = int(out.record.data.get("unresolved_call_sites_total") or 0) if unc > 0: @@ -474,8 +474,8 @@ def _method_with_unresolved(kuzu_graph) -> str: pytest.skip("no method with unresolved_call_sites_total > 0 in fixture") -def _client_with_http_calls_out(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _client_with_http_calls_out(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (c:Client)-[:HTTP_CALLS]->() RETURN DISTINCT c.id AS id LIMIT 1", ) if not rows: @@ -483,8 +483,8 @@ def _client_with_http_calls_out(kuzu_graph) -> str: return str(rows[0]["id"]) -def _producer_with_async_calls_out(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _producer_with_async_calls_out(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (p:Producer)-[:ASYNC_CALLS]->() RETURN DISTINCT p.id AS id LIMIT 1", ) if not rows: @@ -529,9 +529,9 @@ def _struct(output_kind, payload) -> list[_StructuredHint]: # --- Describe structured hints --- -def test_structured_hint_describe_type_rollup_clients(kuzu_graph) -> None: - tid = _type_symbol_id_with_member_clients(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hint_describe_type_rollup_clients(ladybug_graph) -> None: + tid = _type_symbol_id_with_member_clients(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -541,9 +541,9 @@ def test_structured_hint_describe_type_rollup_clients(kuzu_graph) -> None: ) -def test_structured_hint_describe_type_rollup_routes(kuzu_graph) -> None: - tid = _controller_class_id_with_exposes(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hint_describe_type_rollup_routes(ladybug_graph) -> None: + tid = _controller_class_id_with_exposes(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -553,9 +553,9 @@ def test_structured_hint_describe_type_rollup_routes(kuzu_graph) -> None: ) -def test_structured_hint_describe_type_rollup_producers(kuzu_graph) -> None: - tid = _type_symbol_id_with_member_producers(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hint_describe_type_rollup_producers(ladybug_graph) -> None: + tid = _type_symbol_id_with_member_producers(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -565,9 +565,9 @@ def test_structured_hint_describe_type_rollup_producers(kuzu_graph) -> None: ) -def test_structured_hint_describe_method_overriders(kuzu_graph) -> None: - mid = _interface_method_with_override_rollups(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hint_describe_method_overriders(ladybug_graph) -> None: + mid = _interface_method_with_override_rollups(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -577,9 +577,9 @@ def test_structured_hint_describe_method_overriders(kuzu_graph) -> None: ) -def test_structured_hint_describe_method_clients_in_overriders(kuzu_graph) -> None: - mid = _interface_method_with_override_rollups(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hint_describe_method_clients_in_overriders(ladybug_graph) -> None: + mid = _interface_method_with_override_rollups(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -589,7 +589,7 @@ def test_structured_hint_describe_method_clients_in_overriders(kuzu_graph) -> No ) -def test_structured_hint_describe_method_producers_in_overriders(override_axis_graph: KuzuGraph) -> None: +def test_structured_hint_describe_method_producers_in_overriders(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'publish' " @@ -608,7 +608,7 @@ def test_structured_hint_describe_method_producers_in_overriders(override_axis_g ) -def test_structured_hint_describe_method_routes_in_overriders(override_axis_graph: KuzuGraph) -> None: +def test_structured_hint_describe_method_routes_in_overriders(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'handle' " @@ -627,9 +627,9 @@ def test_structured_hint_describe_method_routes_in_overriders(override_axis_grap ) -def test_structured_hint_describe_method_outbound_client(kuzu_graph) -> None: - mid = _method_declares_client(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hint_describe_method_outbound_client(ladybug_graph) -> None: + mid = _method_declares_client(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -639,15 +639,15 @@ def test_structured_hint_describe_method_outbound_client(kuzu_graph) -> None: ) -def test_structured_hint_describe_method_outbound_producer(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_structured_hint_describe_method_outbound_producer(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[:DECLARES_PRODUCER]->(:Producer) WHERE m.kind = 'method' " "RETURN m.id AS id LIMIT 1", ) if not rows: pytest.skip("no method with DECLARES_PRODUCER in fixture") mid = str(rows[0]["id"]) - out = describe_v2(mid, graph=kuzu_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -657,13 +657,13 @@ def test_structured_hint_describe_method_outbound_producer(kuzu_graph) -> None: ) -def test_structured_hint_describe_method_inbound_route(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_structured_hint_describe_method_inbound_route(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[:EXPOSES]->(:Route) WHERE m.kind = 'method' RETURN m.id AS id LIMIT 1", ) assert rows mid = str(rows[0]["id"]) - out = describe_v2(mid, graph=kuzu_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -673,9 +673,9 @@ def test_structured_hint_describe_method_inbound_route(kuzu_graph) -> None: ) -def test_structured_hint_describe_route_declaring(kuzu_graph) -> None: - rid = _route_id(kuzu_graph) - out = describe_v2(rid, graph=kuzu_graph) +def test_structured_hint_describe_route_declaring(ladybug_graph) -> None: + rid = _route_id(ladybug_graph) + out = describe_v2(rid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -685,9 +685,9 @@ def test_structured_hint_describe_route_declaring(kuzu_graph) -> None: ) -def test_structured_hint_describe_client_declaring(kuzu_graph) -> None: - cid = _client_id(kuzu_graph) - out = describe_v2(cid, graph=kuzu_graph) +def test_structured_hint_describe_client_declaring(ladybug_graph) -> None: + cid = _client_id(ladybug_graph) + out = describe_v2(cid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -697,9 +697,9 @@ def test_structured_hint_describe_client_declaring(kuzu_graph) -> None: ) -def test_structured_hint_describe_producer_declaring(kuzu_graph) -> None: - pid = _producer_id(kuzu_graph) - out = describe_v2(pid, graph=kuzu_graph) +def test_structured_hint_describe_producer_declaring(ladybug_graph) -> None: + pid = _producer_id(ladybug_graph) + out = describe_v2(pid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -712,9 +712,9 @@ def test_structured_hint_describe_producer_declaring(kuzu_graph) -> None: # --- Describe structural structured hints --- -def test_structured_hints_describe_interface_implementors(kuzu_graph) -> None: - tid = _interface_with_implements_in(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hints_describe_interface_implementors(ladybug_graph) -> None: + tid = _interface_with_implements_in(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -724,9 +724,9 @@ def test_structured_hints_describe_interface_implementors(kuzu_graph) -> None: ) -def test_structured_hints_describe_class_implements(kuzu_graph) -> None: - tid = _class_with_implements_out(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hints_describe_class_implements(ladybug_graph) -> None: + tid = _class_with_implements_out(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -736,9 +736,9 @@ def test_structured_hints_describe_class_implements(kuzu_graph) -> None: ) -def test_structured_hints_describe_service_dependencies(kuzu_graph) -> None: - tid = _service_with_injects_out(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hints_describe_service_dependencies(ladybug_graph) -> None: + tid = _service_with_injects_out(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -748,9 +748,9 @@ def test_structured_hints_describe_service_dependencies(kuzu_graph) -> None: ) -def test_structured_hints_describe_type_injectors(kuzu_graph) -> None: - tid = _type_with_injects_in(kuzu_graph) - out = describe_v2(tid, graph=kuzu_graph) +def test_structured_hints_describe_type_injectors(ladybug_graph) -> None: + tid = _type_with_injects_in(ladybug_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -760,9 +760,9 @@ def test_structured_hints_describe_type_injectors(kuzu_graph) -> None: ) -def test_structured_hints_describe_method_outbound_calls(kuzu_graph) -> None: - mid = _method_with_mid_calls_out(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hints_describe_method_outbound_calls(ladybug_graph) -> None: + mid = _method_with_mid_calls_out(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -772,9 +772,9 @@ def test_structured_hints_describe_method_outbound_calls(kuzu_graph) -> None: ) -def test_structured_hints_describe_method_super_declaration(kuzu_graph) -> None: - mid = _method_with_overrides_out(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hints_describe_method_super_declaration(ladybug_graph) -> None: + mid = _method_with_overrides_out(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -784,9 +784,9 @@ def test_structured_hints_describe_method_super_declaration(kuzu_graph) -> None: ) -def test_structured_hints_describe_method_unresolved(kuzu_graph) -> None: - mid = _method_with_unresolved(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hints_describe_method_unresolved(ladybug_graph) -> None: + mid = _method_with_unresolved(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -796,9 +796,9 @@ def test_structured_hints_describe_method_unresolved(kuzu_graph) -> None: ) -def test_structured_hints_describe_client_http_targets(kuzu_graph) -> None: - cid = _client_with_http_calls_out(kuzu_graph) - out = describe_v2(cid, graph=kuzu_graph) +def test_structured_hints_describe_client_http_targets(ladybug_graph) -> None: + cid = _client_with_http_calls_out(ladybug_graph) + out = describe_v2(cid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -808,9 +808,9 @@ def test_structured_hints_describe_client_http_targets(kuzu_graph) -> None: ) -def test_structured_hints_describe_producer_async_targets(kuzu_graph) -> None: - pid = _producer_with_async_calls_out(kuzu_graph) - out = describe_v2(pid, graph=kuzu_graph) +def test_structured_hints_describe_producer_async_targets(ladybug_graph) -> None: + pid = _producer_with_async_calls_out(ladybug_graph) + out = describe_v2(pid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -823,8 +823,8 @@ def test_structured_hints_describe_producer_async_targets(kuzu_graph) -> None: # --- Find structured hints --- -def test_structured_hint_find_route_handler(kuzu_graph) -> None: - out = find_v2("route", {"path_prefix": "/api"}, graph=kuzu_graph, limit=500, offset=0) +def test_structured_hint_find_route_handler(ladybug_graph) -> None: + out = find_v2("route", {"path_prefix": "/api"}, graph=ladybug_graph, limit=500, offset=0) assert out.success and out.results rid = out.results[0].id _assert_structured_hint( @@ -835,8 +835,8 @@ def test_structured_hint_find_route_handler(kuzu_graph) -> None: ) -def test_structured_hint_find_client_http_targets(kuzu_graph) -> None: - out = find_v2("client", {"target_service": "smartcare-assign-chat"}, graph=kuzu_graph, limit=500) +def test_structured_hint_find_client_http_targets(ladybug_graph) -> None: + out = find_v2("client", {"target_service": "smartcare-assign-chat"}, graph=ladybug_graph, limit=500) if not out.results: pytest.skip("no client with that target in fixture") cid = out.results[0].id @@ -848,8 +848,8 @@ def test_structured_hint_find_client_http_targets(kuzu_graph) -> None: ) -def test_structured_hint_find_producer_async_targets(kuzu_graph) -> None: - out = find_v2("producer", {}, graph=kuzu_graph, limit=500) +def test_structured_hint_find_producer_async_targets(ladybug_graph) -> None: + out = find_v2("producer", {}, graph=ladybug_graph, limit=500) if not out.results: pytest.skip("no producers in fixture") pid = out.results[0].id @@ -861,8 +861,8 @@ def test_structured_hint_find_producer_async_targets(kuzu_graph) -> None: ) -def test_structured_hint_find_empty_resolve(kuzu_graph) -> None: - out = find_v2("client", {"target_service": "__no_such_target_service__"}, graph=kuzu_graph) +def test_structured_hint_find_empty_resolve(ladybug_graph) -> None: + out = find_v2("client", {"target_service": "__no_such_target_service__"}, graph=ladybug_graph) assert out.success is True assert out.results == [] _assert_structured_hint( @@ -936,9 +936,9 @@ def test_structured_hint_neighbors_empty_wrong_kind() -> None: assert h.actionable is False -def test_structured_hint_neighbors_success_declares_dot_key_clients(kuzu_graph) -> None: - class_id = _class_symbol_id(kuzu_graph) - out = neighbors_v2(class_id, direction="out", edge_types=["DECLARES"], graph=kuzu_graph, limit=50) +def test_structured_hint_neighbors_success_declares_dot_key_clients(ladybug_graph) -> None: + class_id = _class_symbol_id(ladybug_graph) + out = neighbors_v2(class_id, direction="out", edge_types=["DECLARES"], graph=ladybug_graph, limit=50) assert out.success and out.results _assert_structured_hint( out.hints_structured, @@ -948,9 +948,9 @@ def test_structured_hint_neighbors_success_declares_dot_key_clients(kuzu_graph) ) -def test_structured_hint_neighbors_success_declares_dot_key_routes(kuzu_graph) -> None: - class_id = _class_symbol_id(kuzu_graph) - out = neighbors_v2(class_id, direction="out", edge_types=["DECLARES"], graph=kuzu_graph, limit=50) +def test_structured_hint_neighbors_success_declares_dot_key_routes(ladybug_graph) -> None: + class_id = _class_symbol_id(ladybug_graph) + out = neighbors_v2(class_id, direction="out", edge_types=["DECLARES"], graph=ladybug_graph, limit=50) assert out.success and out.results _assert_structured_hint( out.hints_structured, @@ -1048,9 +1048,9 @@ def test_structured_hint_prose_only_not_actionable() -> None: assert fanout, "expected actionable=False CALLS fanout hint" -def test_structured_hint_describe_many_calls_not_actionable(kuzu_graph) -> None: - mid = _controller_method_many_calls(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_structured_hint_describe_many_calls_not_actionable(ladybug_graph) -> None: + mid = _controller_method_many_calls(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record _assert_structured_hint( out.hints_structured, @@ -1098,10 +1098,10 @@ def test_structured_hints_dedup() -> None: -def test_structured_hint_round_trip(kuzu_graph) -> None: +def test_structured_hint_round_trip(ladybug_graph) -> None: """Integration: build structured hint args into an actual neighbors_v2 call.""" - rid = _route_id(kuzu_graph) - out = describe_v2(rid, graph=kuzu_graph) + rid = _route_id(ladybug_graph) + out = describe_v2(rid, graph=ladybug_graph) assert out.success and out.record assert out.hints_structured h = out.hints_structured[0] @@ -1113,7 +1113,7 @@ def test_structured_hint_round_trip(kuzu_graph) -> None: ids=h.args["ids"], direction=h.args["direction"], edge_types=h.args["edge_types"], - graph=kuzu_graph, + graph=ladybug_graph, ) assert nout.success diff --git a/tests/test_mcp_v2.py b/tests/test_mcp_v2.py index 1d80ea43..4ceb829a 100644 --- a/tests/test_mcp_v2.py +++ b/tests/test_mcp_v2.py @@ -46,41 +46,41 @@ def _assert_no_pr2_sentinels(label: str, text: str, *, is_resolve_tool: bool) -> assert match is None, f"{label}: forbidden pattern {pat.pattern!r} matched {match.group(0)!r}" -def _method_id_with_calls(kuzu_graph, direction: str) -> str: +def _method_id_with_calls(ladybug_graph, direction: str) -> str: if direction == "in": - rows = kuzu_graph._rows( # noqa: SLF001 + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (src:Symbol)-[:CALLS]->(dst:Symbol) RETURN dst.id AS id LIMIT 1" ) else: - rows = kuzu_graph._rows( # noqa: SLF001 + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (src:Symbol)-[:CALLS]->(dst:Symbol) RETURN src.id AS id LIMIT 1" ) assert rows return str(rows[0]["id"]) -def _method_id_declares_client_and_other_out_edge(kuzu_graph) -> str | None: +def _method_id_declares_client_and_other_out_edge(ladybug_graph) -> str | None: """A method with DECLARES_CLIENT plus another out-label (Kuzu #119 strict-subset case).""" for pattern in ( "MATCH (m:Symbol {kind: 'method'})-[:DECLARES_CLIENT]->() MATCH (m)-[:CALLS]->() RETURN m.id AS id LIMIT 1", "MATCH (m:Symbol {kind: 'method'})-[:DECLARES_CLIENT]->(:Client)-[:HTTP_CALLS]->() RETURN m.id AS id LIMIT 1", ): - rows = kuzu_graph._rows(pattern) # noqa: SLF001 + rows = ladybug_graph._rows(pattern) # noqa: SLF001 if rows: return str(rows[0]["id"]) return None -def _first_route_with_handler(kuzu_graph) -> str: - for route in kuzu_graph.list_routes(limit=200): - if kuzu_graph.find_route_handlers(route_id=route["id"]): +def _first_route_with_handler(ladybug_graph) -> str: + for route in ladybug_graph.list_routes(limit=200): + if ladybug_graph.find_route_handlers(route_id=route["id"]): return route["id"] raise AssertionError("expected a route with at least one handler") -def _first_route_with_callers(kuzu_graph) -> str: - for route in kuzu_graph.list_routes(limit=200): - if kuzu_graph.find_route_callers(route["id"]): +def _first_route_with_callers(ladybug_graph) -> str: + for route in ladybug_graph.list_routes(limit=200): + if ladybug_graph.find_route_callers(route["id"]): return route["id"] raise AssertionError("expected a route with at least one caller") @@ -116,96 +116,96 @@ def _fake_search_rows() -> list[dict[str, Any]]: ] -def test_search_basic_returns_hits_with_symbol_id(monkeypatch, kuzu_graph) -> None: +def test_search_basic_returns_hits_with_symbol_id(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - out = search_v2("ChatService", graph=kuzu_graph) + out = search_v2("ChatService", graph=ladybug_graph) assert out.success is True assert out.results assert out.results[0].symbol_id is not None -def test_search_filter_microservice(monkeypatch, kuzu_graph) -> None: +def test_search_filter_microservice(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - out = search_v2("ChatService", filter={"microservice": "chat-assign"}, graph=kuzu_graph) + out = search_v2("ChatService", filter={"microservice": "chat-assign"}, graph=ladybug_graph) assert out.success is True assert out.results assert {h.microservice for h in out.results} == {"chat-assign"} -def test_search_path_contains_filter(monkeypatch, kuzu_graph) -> None: +def test_search_path_contains_filter(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - out = search_v2("ChatAssign", path_contains="ChatAssign", graph=kuzu_graph) + out = search_v2("ChatAssign", path_contains="ChatAssign", graph=ladybug_graph) assert out.success is True assert len(out.results) == 1 -def test_find_symbol_by_role(kuzu_graph) -> None: - out = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph) +def test_find_symbol_by_role(ladybug_graph) -> None: + out = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph) assert out.success is True assert out.results assert all(r.role == "CONTROLLER" for r in out.results if r.role is not None) -def test_find_symbol_empty_filter_returns_results(kuzu_graph) -> None: - out = find_v2("symbol", {}, graph=kuzu_graph) +def test_find_symbol_empty_filter_returns_results(ladybug_graph) -> None: + out = find_v2("symbol", {}, graph=ladybug_graph) assert out.success is True assert out.results # Regression guard: Symbol rows can include non-declaration kinds (e.g., package/file). assert all(isinstance(r.symbol_kind, str) and r.symbol_kind for r in out.results) -def test_find_symbol_by_symbol_kind_method(kuzu_graph) -> None: - out = find_v2("symbol", {"symbol_kind": "method"}, graph=kuzu_graph) +def test_find_symbol_by_symbol_kind_method(ladybug_graph) -> None: + out = find_v2("symbol", {"symbol_kind": "method"}, graph=ladybug_graph) assert out.success is True assert out.results assert all(r.symbol_kind == "method" for r in out.results) -def test_find_symbol_by_symbol_kind_interface(kuzu_graph) -> None: - out = find_v2("symbol", {"symbol_kind": "interface"}, graph=kuzu_graph) +def test_find_symbol_by_symbol_kind_interface(ladybug_graph) -> None: + out = find_v2("symbol", {"symbol_kind": "interface"}, graph=ladybug_graph) assert out.success is True if not out.results: pytest.skip("fixture has no interface symbols") assert all(r.symbol_kind == "interface" for r in out.results) -def test_find_symbol_by_symbol_kinds_type_level(kuzu_graph) -> None: +def test_find_symbol_by_symbol_kinds_type_level(ladybug_graph) -> None: type_level_kinds = ["class", "interface", "enum", "record", "annotation"] - out = find_v2("symbol", {"symbol_kinds": type_level_kinds}, graph=kuzu_graph) + out = find_v2("symbol", {"symbol_kinds": type_level_kinds}, graph=ladybug_graph) assert out.success is True assert out.results assert all(r.symbol_kind in set(type_level_kinds) for r in out.results) -def test_find_symbol_projection_includes_symbol_kind(kuzu_graph) -> None: - out = find_v2("symbol", {"symbol_kind": "method"}, graph=kuzu_graph) +def test_find_symbol_projection_includes_symbol_kind(ladybug_graph) -> None: + out = find_v2("symbol", {"symbol_kind": "method"}, graph=ladybug_graph) assert out.success is True assert out.results assert all(isinstance(r.symbol_kind, str) and r.symbol_kind for r in out.results) -def test_find_route_by_path_prefix(kuzu_graph) -> None: - out = find_v2("route", {"path_prefix": "/api"}, graph=kuzu_graph) +def test_find_route_by_path_prefix(ladybug_graph) -> None: + out = find_v2("route", {"path_prefix": "/api"}, graph=ladybug_graph) assert out.success is True assert isinstance(out.results, list) -def test_find_kind_producer_returns_producer_nodes(kuzu_graph) -> None: - out = find_v2("producer", filter={}, graph=kuzu_graph) +def test_find_kind_producer_returns_producer_nodes(ladybug_graph) -> None: + out = find_v2("producer", filter={}, graph=ladybug_graph) if not out.results: pytest.skip("no Producer nodes in session fixture") assert out.success is True assert all(r.kind == "producer" for r in out.results) -def test_resolve_hint_kind_producer(kuzu_graph) -> None: - rows = kuzu_graph.list_producers(limit=10) +def test_resolve_hint_kind_producer(ladybug_graph) -> None: + rows = ladybug_graph.list_producers(limit=10) if not rows: pytest.skip("no Producer nodes in session fixture") topic = str(rows[0].get("topic") or "") if not topic: pytest.skip("producer row missing topic") - out = resolve_v2(topic, hint_kind="producer", graph=kuzu_graph) + out = resolve_v2(topic, hint_kind="producer", graph=ladybug_graph) assert out.success is True assert out.status in {"one", "many"} if out.status == "one": @@ -213,12 +213,12 @@ def test_resolve_hint_kind_producer(kuzu_graph) -> None: assert out.node.kind == "producer" -def test_find_client_by_client_kind(kuzu_graph) -> None: - rows = kuzu_graph.list_clients(client_kind="feign_method", limit=500) +def test_find_client_by_client_kind(ladybug_graph) -> None: + rows = ladybug_graph.list_clients(client_kind="feign_method", limit=500) if not rows: pytest.skip("fixture has no feign_method client rows") by_id = {str(r["id"]): r for r in rows} - out = find_v2("client", {"client_kind": "feign_method"}, graph=kuzu_graph) + out = find_v2("client", {"client_kind": "feign_method"}, graph=ladybug_graph) assert out.success is True assert out.results for ref in out.results: @@ -227,20 +227,20 @@ def test_find_client_by_client_kind(kuzu_graph) -> None: assert str(row.get("client_kind") or "") == "feign_method" -def test_find_client_by_target_service(kuzu_graph) -> None: - rows = kuzu_graph.list_clients(limit=500) +def test_find_client_by_target_service(ladybug_graph) -> None: + rows = ladybug_graph.list_clients(limit=500) seed = next((r for r in rows if str(r.get("target_service") or "").strip()), None) if seed is None: pytest.skip("no client rows with target_service in fixture") target_service = str(seed["target_service"]) - out = find_v2("client", {"target_service": target_service}, graph=kuzu_graph) + out = find_v2("client", {"target_service": target_service}, graph=ladybug_graph) assert out.success is True assert out.results assert all(r.fqn.startswith(f"{target_service} ") for r in out.results) -def test_find_client_by_path_prefix(kuzu_graph) -> None: - all_clients = find_v2("client", {}, graph=kuzu_graph) +def test_find_client_by_path_prefix(ladybug_graph) -> None: + all_clients = find_v2("client", {}, graph=ladybug_graph) assert all_clients.success is True sample = next((r for r in all_clients.results if "/" in r.fqn), None) if sample is None: @@ -250,7 +250,7 @@ def test_find_client_by_path_prefix(kuzu_graph) -> None: if not path.startswith("/"): pytest.skip("sample client path is unavailable") prefix = path[: min(len(path), 5)] - out = find_v2("client", {"target_path_prefix": prefix}, graph=kuzu_graph) + out = find_v2("client", {"target_path_prefix": prefix}, graph=ladybug_graph) assert out.success is True assert out.results for ref in out.results: @@ -259,32 +259,32 @@ def test_find_client_by_path_prefix(kuzu_graph) -> None: assert bits[-1].startswith(prefix) -def test_find_cross_kind_filter_fields_return_failure(kuzu_graph) -> None: - out = find_v2("symbol", {"path_prefix": "/api"}, graph=kuzu_graph) +def test_find_cross_kind_filter_fields_return_failure(ladybug_graph) -> None: + out = find_v2("symbol", {"path_prefix": "/api"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "path_prefix" in out.message assert "kind='symbol'" in out.message -def test_find_unknown_filter_key_returns_failure(kuzu_graph) -> None: - out = find_v2("symbol", {"typo_key": "x"}, graph=kuzu_graph) +def test_find_unknown_filter_key_returns_failure(ladybug_graph) -> None: + out = find_v2("symbol", {"typo_key": "x"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "Invalid filter" in out.message assert "typo_key" in out.message -def test_find_symbol_only_field_with_kind_client_returns_failure(kuzu_graph) -> None: - out = find_v2("client", {"fqn_prefix": "com.example"}, graph=kuzu_graph) +def test_find_symbol_only_field_with_kind_client_returns_failure(ladybug_graph) -> None: + out = find_v2("client", {"fqn_prefix": "com.example"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "fqn_prefix" in out.message assert "kind='client'" in out.message -def test_find_client_only_field_with_kind_symbol_returns_failure(kuzu_graph) -> None: - out = find_v2("symbol", {"client_kind": "feign_method"}, graph=kuzu_graph) +def test_find_client_only_field_with_kind_symbol_returns_failure(ladybug_graph) -> None: + out = find_v2("symbol", {"client_kind": "feign_method"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "client_kind" in out.message @@ -297,31 +297,31 @@ def test_nodefilter_applicability_table_covers_all_fields() -> None: assert declared == covered -def test_http_method_field_applies_to_route_kind(kuzu_graph) -> None: - routes = kuzu_graph.list_routes(limit=2000) +def test_http_method_field_applies_to_route_kind(ladybug_graph) -> None: + routes = ladybug_graph.list_routes(limit=2000) post_ids = {str(r["id"]) for r in routes if str(r.get("method") or "").upper() == "POST"} if not post_ids: pytest.skip("fixture has no POST routes") - out = find_v2("route", {"http_method": "POST"}, graph=kuzu_graph, limit=500) + out = find_v2("route", {"http_method": "POST"}, graph=ladybug_graph, limit=500) assert out.success is True assert out.results assert {r.id for r in out.results} <= post_ids assert all(r.fqn == "POST" or r.fqn.startswith("POST ") for r in out.results) -def test_http_method_field_applies_to_client_kind(kuzu_graph) -> None: - clients = kuzu_graph.list_clients(limit=2000) +def test_http_method_field_applies_to_client_kind(ladybug_graph) -> None: + clients = ladybug_graph.list_clients(limit=2000) post_ids = {str(r["id"]) for r in clients if str(r.get("method") or "").upper() == "POST"} if not post_ids: pytest.skip("fixture has no POST clients") - out = find_v2("client", {"http_method": "POST"}, graph=kuzu_graph, limit=500) + out = find_v2("client", {"http_method": "POST"}, graph=ladybug_graph, limit=500) assert out.success is True assert out.results assert {r.id for r in out.results} <= post_ids -def test_http_method_field_inapplicable_to_symbol(kuzu_graph) -> None: - out = find_v2("symbol", {"http_method": "POST"}, graph=kuzu_graph) +def test_http_method_field_inapplicable_to_symbol(ladybug_graph) -> None: + out = find_v2("symbol", {"http_method": "POST"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "http_method" in out.message @@ -339,23 +339,23 @@ async def test_find_missing_filter_rejected(mcp_server) -> None: await mcp_server.call_tool("find", {"kind": "symbol"}) -def test_describe_symbol_returns_record(kuzu_graph) -> None: - symbol = kuzu_graph.list_by_role("SERVICE", limit=1)[0] - out = describe_v2(symbol.id, graph=kuzu_graph) +def test_describe_symbol_returns_record(ladybug_graph) -> None: + symbol = ladybug_graph.list_by_role("SERVICE", limit=1)[0] + out = describe_v2(symbol.id, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.kind == "symbol" -def test_describe_route_returns_record(kuzu_graph) -> None: - route = kuzu_graph.list_routes(limit=1)[0] - out = describe_v2(route["id"], graph=kuzu_graph) +def test_describe_route_returns_record(ladybug_graph) -> None: + route = ladybug_graph.list_routes(limit=1)[0] + out = describe_v2(route["id"], graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.kind == "route" -def test_describe_client_returns_record(kuzu_graph) -> None: +def test_describe_client_returns_record(ladybug_graph) -> None: class FakeGraph: def edge_counts_for(self, node_id: str) -> dict[str, dict[str, int]]: return {} @@ -390,65 +390,65 @@ def _rows(self, query: str, params: dict[str, Any] | None = None) -> list[dict[s assert out.record.kind == "client" -def test_describe_unknown_id_returns_error(kuzu_graph) -> None: - out = describe_v2("bogus:1", graph=kuzu_graph) +def test_describe_unknown_id_returns_error(ladybug_graph) -> None: + out = describe_v2("bogus:1", graph=ladybug_graph) assert out.success is False assert out.message -def test_describe_package_or_file_symbol_succeeds(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_package_or_file_symbol_succeeds(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.kind IN ['package', 'file'] RETURN s.id AS id LIMIT 1" ) if not rows: pytest.skip("fixture has no package/file symbol rows") - out = describe_v2(str(rows[0]["id"]), graph=kuzu_graph) + out = describe_v2(str(rows[0]["id"]), graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.kind == "symbol" assert out.record.data.get("kind") in {"package", "file"} -def test_neighbors_in_calls(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "in") - out = neighbors_v2(mid, direction="in", edge_types=["CALLS"], graph=kuzu_graph) +def test_neighbors_in_calls(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "in") + out = neighbors_v2(mid, direction="in", edge_types=["CALLS"], graph=ladybug_graph) assert out.success is True assert isinstance(out.results, list) -def test_neighbors_out_calls(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") - out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], graph=kuzu_graph) +def test_neighbors_out_calls(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") + out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], graph=ladybug_graph) assert out.success is True assert isinstance(out.results, list) -def test_neighbors_edge_types_strict_subset_respects_label_filter(kuzu_graph) -> None: +def test_neighbors_edge_types_strict_subset_respects_label_filter(ladybug_graph) -> None: """Regression (#119): Kuzu can drop `label(e) IN $list`; use OR of `label(e) = $p` instead.""" - mid = _method_id_declares_client_and_other_out_edge(kuzu_graph) + mid = _method_id_declares_client_and_other_out_edge(ladybug_graph) if mid is None: pytest.skip("no method with DECLARES_CLIENT and CALLS or HTTP_CALLS out-edges") - dc_rows = kuzu_graph._rows( # noqa: SLF001 + dc_rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[e:DECLARES_CLIENT]->() WHERE m.id = $id RETURN count(e) AS n", {"id": mid}, ) assert dc_rows want_dc = int(dc_rows[0]["n"]) assert want_dc >= 1 - out = neighbors_v2(mid, direction="out", edge_types=["DECLARES_CLIENT"], graph=kuzu_graph) + out = neighbors_v2(mid, direction="out", edge_types=["DECLARES_CLIENT"], graph=ladybug_graph) assert out.success is True assert all(e.edge_type == "DECLARES_CLIENT" for e in out.results) assert len(out.results) == want_dc -def test_neighbors_route_in_exposes_returns_handler(kuzu_graph) -> None: - route_id = _first_route_with_handler(kuzu_graph) - out = neighbors_v2(route_id, direction="in", edge_types=["EXPOSES"], graph=kuzu_graph) +def test_neighbors_route_in_exposes_returns_handler(ladybug_graph) -> None: + route_id = _first_route_with_handler(ladybug_graph) + out = neighbors_v2(route_id, direction="in", edge_types=["EXPOSES"], graph=ladybug_graph) assert out.success is True assert out.results -def test_neighbors_route_in_http_calls_returns_callers(kuzu_graph) -> None: +def test_neighbors_route_in_http_calls_returns_callers(ladybug_graph) -> None: class FakeGraph: def _rows(self, query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]: if ( @@ -516,43 +516,43 @@ def _rows(self, query: str, params: dict[str, Any] | None = None) -> list[dict[s assert out.results[0].other.kind == "client" -def test_neighbors_batch_ids_carries_origin_id(kuzu_graph) -> None: - one = _method_id_with_calls(kuzu_graph, "out") - two = _method_id_with_calls(kuzu_graph, "in") - out = neighbors_v2([one, two], direction="out", edge_types=["CALLS"], graph=kuzu_graph) +def test_neighbors_batch_ids_carries_origin_id(ladybug_graph) -> None: + one = _method_id_with_calls(ladybug_graph, "out") + two = _method_id_with_calls(ladybug_graph, "in") + out = neighbors_v2([one, two], direction="out", edge_types=["CALLS"], graph=ladybug_graph) assert out.success is True assert {e.origin_id for e in out.results} <= {one, two} -def test_neighbors_missing_direction_rejected(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_missing_direction_rejected(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") with pytest.raises(ValidationError): - neighbors_v2(mid, edge_types=["CALLS"], graph=kuzu_graph) + neighbors_v2(mid, edge_types=["CALLS"], graph=ladybug_graph) -def test_neighbors_missing_edge_types_rejected(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_missing_edge_types_rejected(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") with pytest.raises(ValidationError): - neighbors_v2(mid, direction="in", graph=kuzu_graph) + neighbors_v2(mid, direction="in", graph=ladybug_graph) -def test_neighbors_empty_edge_types_rejected(kuzu_graph) -> None: +def test_neighbors_empty_edge_types_rejected(ladybug_graph) -> None: # Exercises TypeAdapter(min_length=1), distinct from missing edge_types (validate_call / Field(...)). - mid = _method_id_with_calls(kuzu_graph, "out") + mid = _method_id_with_calls(ladybug_graph, "out") with pytest.raises(ValidationError): - neighbors_v2(mid, direction="in", edge_types=[], graph=kuzu_graph) + neighbors_v2(mid, direction="in", edge_types=[], graph=ladybug_graph) -def test_neighbors_invalid_direction_rejected(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_invalid_direction_rejected(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") with pytest.raises(ValidationError): - neighbors_v2(mid, direction="upstream", edge_types=["CALLS"], graph=kuzu_graph) + neighbors_v2(mid, direction="upstream", edge_types=["CALLS"], graph=ladybug_graph) -def test_neighbors_invalid_edge_type_rejected(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_invalid_edge_type_rejected(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") with pytest.raises(ValidationError): - neighbors_v2(mid, direction="in", edge_types=["calls"], graph=kuzu_graph) + neighbors_v2(mid, direction="in", edge_types=["calls"], graph=ladybug_graph) async def test_find_invalid_kind_rejected(mcp_server) -> None: @@ -565,152 +565,152 @@ async def test_search_invalid_table_rejected(mcp_server) -> None: await mcp_server.call_tool("search", {"query": "foo", "table": "code"}) -def test_search_filter_accepts_json_string(monkeypatch, kuzu_graph) -> None: +def test_search_filter_accepts_json_string(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) want = {"microservice": "chat-assign"} - out_dict = search_v2("ChatService", filter=want, graph=kuzu_graph) - out_str = search_v2("ChatService", filter='{"microservice":"chat-assign"}', graph=kuzu_graph) + out_dict = search_v2("ChatService", filter=want, graph=ladybug_graph) + out_str = search_v2("ChatService", filter='{"microservice":"chat-assign"}', graph=ladybug_graph) assert out_dict.success is True assert out_str.success is True assert out_dict.results == out_str.results -def test_search_unknown_filter_key_returns_failure(monkeypatch, kuzu_graph) -> None: +def test_search_unknown_filter_key_returns_failure(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - out = search_v2("ChatService", filter={"typo_key": "x"}, graph=kuzu_graph) + out = search_v2("ChatService", filter={"typo_key": "x"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "Invalid filter" in out.message assert "typo_key" in out.message -def test_search_cross_kind_filter_returns_failure(monkeypatch, kuzu_graph) -> None: +def test_search_cross_kind_filter_returns_failure(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - out = search_v2("ChatService", filter={"path_prefix": "/api"}, graph=kuzu_graph) + out = search_v2("ChatService", filter={"path_prefix": "/api"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "path_prefix" in out.message assert "kind='symbol'" in out.message -def test_search_filter_empty_string_treated_as_none(monkeypatch, kuzu_graph) -> None: +def test_search_filter_empty_string_treated_as_none(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - baseline = search_v2("ChatService", graph=kuzu_graph) - empty = search_v2("ChatService", filter="", graph=kuzu_graph) - whitespace = search_v2("ChatService", filter=" ", graph=kuzu_graph) + baseline = search_v2("ChatService", graph=ladybug_graph) + empty = search_v2("ChatService", filter="", graph=ladybug_graph) + whitespace = search_v2("ChatService", filter=" ", graph=ladybug_graph) assert baseline.success is True assert empty.success is True assert whitespace.success is True assert baseline.results == empty.results == whitespace.results -def test_search_filter_json_null_treated_as_none(monkeypatch, kuzu_graph) -> None: +def test_search_filter_json_null_treated_as_none(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - baseline = search_v2("ChatService", graph=kuzu_graph) - out = search_v2("ChatService", filter="null", graph=kuzu_graph) + baseline = search_v2("ChatService", graph=ladybug_graph) + out = search_v2("ChatService", filter="null", graph=ladybug_graph) assert baseline.success is True assert out.success is True assert baseline.results == out.results -def test_find_filter_json_null_treated_as_empty_filter(kuzu_graph) -> None: - empty = find_v2("symbol", {}, graph=kuzu_graph) - out = find_v2("symbol", "null", graph=kuzu_graph) +def test_find_filter_json_null_treated_as_empty_filter(ladybug_graph) -> None: + empty = find_v2("symbol", {}, graph=ladybug_graph) + out = find_v2("symbol", "null", graph=ladybug_graph) assert empty.success is True assert out.success is True assert empty.results == out.results -def test_find_filter_accepts_json_string(kuzu_graph) -> None: - out_dict = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph) - out_str = find_v2("symbol", '{"role":"CONTROLLER"}', graph=kuzu_graph) +def test_find_filter_accepts_json_string(ladybug_graph) -> None: + out_dict = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph) + out_str = find_v2("symbol", '{"role":"CONTROLLER"}', graph=ladybug_graph) assert out_dict.success is True assert out_str.success is True assert out_dict.results == out_str.results -def test_find_symbol_kind_filter_accepts_json_string(kuzu_graph) -> None: - out_dict = find_v2("symbol", {"symbol_kind": "method"}, graph=kuzu_graph) - out_str = find_v2("symbol", '{"symbol_kind":"method"}', graph=kuzu_graph) +def test_find_symbol_kind_filter_accepts_json_string(ladybug_graph) -> None: + out_dict = find_v2("symbol", {"symbol_kind": "method"}, graph=ladybug_graph) + out_str = find_v2("symbol", '{"symbol_kind":"method"}', graph=ladybug_graph) assert out_dict.success is True assert out_str.success is True assert out_dict.results == out_str.results -def test_neighbors_filter_accepts_json_string(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_filter_accepts_json_string(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") flt = {"role": "SERVICE"} - out_dict = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter=flt, graph=kuzu_graph) - out_str = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter='{"role":"SERVICE"}', graph=kuzu_graph) + out_dict = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter=flt, graph=ladybug_graph) + out_str = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter='{"role":"SERVICE"}', graph=ladybug_graph) assert out_dict.success is True assert out_str.success is True assert out_dict.results == out_str.results -def test_neighbors_filter_unknown_key_returns_failure(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") - out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter={"typo_key": "x"}, graph=kuzu_graph) +def test_neighbors_filter_unknown_key_returns_failure(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") + out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter={"typo_key": "x"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "Invalid filter" in out.message assert "typo_key" in out.message -def test_neighbors_filter_cross_kind_on_neighbor_returns_failure(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") - out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter={"path_prefix": "/api"}, graph=kuzu_graph) +def test_neighbors_filter_cross_kind_on_neighbor_returns_failure(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") + out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], filter={"path_prefix": "/api"}, graph=ladybug_graph) assert out.success is False assert out.message is not None assert "path_prefix" in out.message assert "kind='symbol'" in out.message -def test_neighbors_validate_call_still_raises(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_validate_call_still_raises(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") with pytest.raises(ValidationError): - neighbors_v2(mid, direction="upstream", edge_types=["CALLS"], graph=kuzu_graph) + neighbors_v2(mid, direction="upstream", edge_types=["CALLS"], graph=ladybug_graph) -def test_filter_invalid_json_returns_failure(monkeypatch, kuzu_graph) -> None: +def test_filter_invalid_json_returns_failure(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) - out = search_v2("ChatService", filter="{not json", graph=kuzu_graph) + out = search_v2("ChatService", filter="{not json", graph=ladybug_graph) assert out.success is False assert out.message is not None assert "JSON" in out.message -def test_wildcard_in_fqn_prefix_rejected(kuzu_graph) -> None: - out = find_v2("symbol", {"fqn_prefix": "com.foo.*"}, graph=kuzu_graph) +def test_wildcard_in_fqn_prefix_rejected(ladybug_graph) -> None: + out = find_v2("symbol", {"fqn_prefix": "com.foo.*"}, graph=ladybug_graph) assert out.success is False assert out.message assert "fqn_prefix" in out.message assert "search(query=..." in out.message -def test_wildcard_in_path_prefix_rejected(kuzu_graph) -> None: - out = find_v2("route", {"path_prefix": "/api/*"}, graph=kuzu_graph) +def test_wildcard_in_path_prefix_rejected(ladybug_graph) -> None: + out = find_v2("route", {"path_prefix": "/api/*"}, graph=ladybug_graph) assert out.success is False assert out.message assert "path_prefix" in out.message assert "search(query=..." in out.message -def test_wildcard_in_target_path_prefix_rejected(kuzu_graph) -> None: - out = find_v2("client", {"target_path_prefix": "/api/*"}, graph=kuzu_graph) +def test_wildcard_in_target_path_prefix_rejected(ladybug_graph) -> None: + out = find_v2("client", {"target_path_prefix": "/api/*"}, graph=ladybug_graph) assert out.success is False assert out.message assert "target_path_prefix" in out.message assert "search(query=..." in out.message -def test_wildcard_question_mark_in_fqn_prefix_rejected(kuzu_graph) -> None: - out = find_v2("symbol", {"fqn_prefix": "com.foo.?"}, graph=kuzu_graph) +def test_wildcard_question_mark_in_fqn_prefix_rejected(ladybug_graph) -> None: + out = find_v2("symbol", {"fqn_prefix": "com.foo.?"}, graph=ladybug_graph) assert out.success is False assert out.message assert "fqn_prefix" in out.message -def test_search_wildcard_in_fqn_prefix_rejected_without_run_search(monkeypatch, kuzu_graph) -> None: +def test_search_wildcard_in_fqn_prefix_rejected_without_run_search(monkeypatch, ladybug_graph) -> None: calls: list[int] = [] def boom(*_a, **_k): @@ -718,14 +718,14 @@ def boom(*_a, **_k): return _fake_search_rows() monkeypatch.setattr("mcp_v2.run_search", boom) - out = search_v2("anything", filter={"fqn_prefix": "com.*"}, graph=kuzu_graph) + out = search_v2("anything", filter={"fqn_prefix": "com.*"}, graph=ladybug_graph) assert out.success is False assert out.message assert "fqn_prefix" in out.message assert calls == [] -def test_neighbors_wildcard_in_filter_rejected_before_graph_query(kuzu_graph) -> None: +def test_neighbors_wildcard_in_filter_rejected_before_graph_query(ladybug_graph) -> None: class ExplodeGraph: def _rows(self, *_a, **_k) -> list: raise AssertionError("graph must not be queried when wildcard rejects filter") @@ -742,9 +742,9 @@ def _rows(self, *_a, **_k) -> list: assert "fqn_prefix" in out.message -def test_describe_by_fqn_returns_symbol(kuzu_graph) -> None: - symbol = kuzu_graph.list_by_role("SERVICE", limit=1)[0] - out = describe_v2(fqn=symbol.fqn, graph=kuzu_graph) +def test_describe_by_fqn_returns_symbol(ladybug_graph) -> None: + symbol = ladybug_graph.list_by_role("SERVICE", limit=1)[0] + out = describe_v2(fqn=symbol.fqn, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.id == symbol.id @@ -752,16 +752,16 @@ def test_describe_by_fqn_returns_symbol(kuzu_graph) -> None: assert out.message is None -def test_describe_by_fqn_unknown_returns_error(kuzu_graph) -> None: - out = describe_v2(fqn="com.nonexistent.Foo", graph=kuzu_graph) +def test_describe_by_fqn_unknown_returns_error(ladybug_graph) -> None: + out = describe_v2(fqn="com.nonexistent.Foo", graph=ladybug_graph) assert out.success is False assert out.message == "No Symbol found for fqn='com.nonexistent.Foo'" -def test_describe_by_fqn_id_takes_precedence(kuzu_graph) -> None: - svc = kuzu_graph.list_by_role("SERVICE", limit=1)[0] - ctrl = kuzu_graph.list_by_role("CONTROLLER", limit=1)[0] - out = describe_v2(id=svc.id, fqn=ctrl.fqn, graph=kuzu_graph) +def test_describe_by_fqn_id_takes_precedence(ladybug_graph) -> None: + svc = ladybug_graph.list_by_role("SERVICE", limit=1)[0] + ctrl = ladybug_graph.list_by_role("CONTROLLER", limit=1)[0] + out = describe_v2(id=svc.id, fqn=ctrl.fqn, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.id == svc.id @@ -829,21 +829,21 @@ async def _run() -> None: asyncio.run(_run()) -def test_describe_by_fqn_requires_id_or_fqn(kuzu_graph) -> None: - out = describe_v2(graph=kuzu_graph) +def test_describe_by_fqn_requires_id_or_fqn(ladybug_graph) -> None: + out = describe_v2(graph=ladybug_graph) assert out.success is False assert out.message == "id or fqn required" -def test_multi_value_symbol_kinds_or_semantics(kuzu_graph) -> None: - out = find_v2("symbol", {"symbol_kinds": ["class", "interface"]}, graph=kuzu_graph, limit=200) +def test_multi_value_symbol_kinds_or_semantics(ladybug_graph) -> None: + out = find_v2("symbol", {"symbol_kinds": ["class", "interface"]}, graph=ladybug_graph, limit=200) assert out.success is True assert out.results assert all(r.symbol_kind in {"class", "interface"} for r in out.results) -def test_cross_field_and_semantics(kuzu_graph) -> None: - controllers = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph, limit=50) +def test_cross_field_and_semantics(ladybug_graph) -> None: + controllers = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph, limit=50) assert controllers.success is True assert controllers.results ms = next((r.microservice for r in controllers.results if r.microservice), None) @@ -852,7 +852,7 @@ def test_cross_field_and_semantics(kuzu_graph) -> None: out = find_v2( "symbol", {"microservice": ms, "role": "CONTROLLER"}, - graph=kuzu_graph, + graph=ladybug_graph, limit=200, ) assert out.success is True @@ -861,68 +861,68 @@ def test_cross_field_and_semantics(kuzu_graph) -> None: assert all((r.role or "") == "CONTROLLER" for r in out.results) -def test_exclude_roles_negation_predicate(kuzu_graph) -> None: - out = find_v2("symbol", {"exclude_roles": ["CONTROLLER"]}, graph=kuzu_graph, limit=500) +def test_exclude_roles_negation_predicate(ladybug_graph) -> None: + out = find_v2("symbol", {"exclude_roles": ["CONTROLLER"]}, graph=ladybug_graph, limit=500) assert out.success is True assert out.results assert not any(r.role == "CONTROLLER" for r in out.results) -def test_empty_filter_returns_full_result_set(kuzu_graph) -> None: - out = find_v2("client", {}, graph=kuzu_graph) +def test_empty_filter_returns_full_result_set(ladybug_graph) -> None: + out = find_v2("client", {}, graph=ladybug_graph) assert out.success is True assert out.results -def test_fail_loud_counter_increments_on_applicability_error(kuzu_graph) -> None: +def test_fail_loud_counter_increments_on_applicability_error(ladybug_graph) -> None: before = filter_frame_counters().get("applicability", 0) - out = find_v2("symbol", {"path_prefix": "/api"}, graph=kuzu_graph) + out = find_v2("symbol", {"path_prefix": "/api"}, graph=ladybug_graph) assert out.success is False assert filter_frame_counters().get("applicability", 0) == before + 1 -def test_fail_loud_counter_increments_on_wildcard_rejection(kuzu_graph) -> None: +def test_fail_loud_counter_increments_on_wildcard_rejection(ladybug_graph) -> None: before = filter_frame_counters().get("wildcard", 0) - out = find_v2("symbol", {"fqn_prefix": "com.foo.*"}, graph=kuzu_graph) + out = find_v2("symbol", {"fqn_prefix": "com.foo.*"}, graph=ladybug_graph) assert out.success is False assert filter_frame_counters().get("wildcard", 0) == before + 1 -def test_fail_loud_counter_categories_are_distinct(kuzu_graph) -> None: +def test_fail_loud_counter_categories_are_distinct(ladybug_graph) -> None: b_app = filter_frame_counters().get("applicability", 0) b_wild = filter_frame_counters().get("wildcard", 0) - find_v2("symbol", {"path_prefix": "/x"}, graph=kuzu_graph) - find_v2("symbol", {"fqn_prefix": "com.*"}, graph=kuzu_graph) + find_v2("symbol", {"path_prefix": "/x"}, graph=ladybug_graph) + find_v2("symbol", {"fqn_prefix": "com.*"}, graph=ladybug_graph) assert filter_frame_counters().get("applicability", 0) == b_app + 1 assert filter_frame_counters().get("wildcard", 0) == b_wild + 1 -def test_fail_loud_counter_survives_multiple_calls(kuzu_graph) -> None: +def test_fail_loud_counter_survives_multiple_calls(ladybug_graph) -> None: before = filter_frame_counters().get("applicability", 0) - find_v2("symbol", {"http_method": "GET"}, graph=kuzu_graph) - find_v2("symbol", {"http_method": "GET"}, graph=kuzu_graph) + find_v2("symbol", {"http_method": "GET"}, graph=ladybug_graph) + find_v2("symbol", {"http_method": "GET"}, graph=ladybug_graph) assert filter_frame_counters().get("applicability", 0) >= before + 2 # --- resolve (PR-RESOLVE-1) --- -def test_resolve_exact_id_symbol_returns_one(kuzu_graph) -> None: - seed = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph, limit=1) +def test_resolve_exact_id_symbol_returns_one(ladybug_graph) -> None: + seed = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph, limit=1) assert seed.success and seed.results sym_id = seed.results[0].id - out = resolve_v2(sym_id, hint_kind="symbol", graph=kuzu_graph) + out = resolve_v2(sym_id, hint_kind="symbol", graph=ladybug_graph) assert out.success is True assert out.status == "one" assert out.node is not None assert out.node.id == sym_id -def test_resolve_exact_fqn_symbol_returns_one(kuzu_graph) -> None: - controllers = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph, limit=50) +def test_resolve_exact_fqn_symbol_returns_one(ladybug_graph) -> None: + controllers = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph, limit=50) assert controllers.success and controllers.results fqn = controllers.results[0].fqn - out = resolve_v2(fqn, hint_kind="symbol", graph=kuzu_graph) + out = resolve_v2(fqn, hint_kind="symbol", graph=ladybug_graph) assert out.success is True assert out.status == "one" assert out.node is not None @@ -930,12 +930,12 @@ def test_resolve_exact_fqn_symbol_returns_one(kuzu_graph) -> None: def test_resolve_fqn_collision_across_microservices_returns_many( - kuzu_graph_fqn_collision_smoke, + ladybug_graph_fqn_collision_smoke, ) -> None: out = resolve_v2( "com.example.SharedDto#process()", hint_kind="symbol", - graph=kuzu_graph_fqn_collision_smoke, + graph=ladybug_graph_fqn_collision_smoke, ) assert out.success is True assert out.status == "many" @@ -945,37 +945,37 @@ def test_resolve_fqn_collision_across_microservices_returns_many( assert any(c.reason == "exact_fqn" for c in out.candidates) -def test_resolve_short_name_ambiguity_returns_many(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_resolve_short_name_ambiguity_returns_many(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.kind = 'method' RETURN s.name AS name" ) counts = Counter(str(r["name"]) for r in rows if r.get("name")) dup_name = next((name for name, c in counts.items() if c >= 2), None) if dup_name is None: pytest.skip("no duplicated method short names in bank-chat fixture") - out = resolve_v2(dup_name, hint_kind="symbol", graph=kuzu_graph) + out = resolve_v2(dup_name, hint_kind="symbol", graph=ladybug_graph) assert out.success is True assert out.status == "many" assert any(c.reason == "short_name" for c in out.candidates) -def test_resolve_status_none_returns_nonempty_message(kuzu_graph) -> None: - out = resolve_v2("com.nonexistent.ZzzMissing", hint_kind="symbol", graph=kuzu_graph) +def test_resolve_status_none_returns_nonempty_message(ladybug_graph) -> None: + out = resolve_v2("com.nonexistent.ZzzMissing", hint_kind="symbol", graph=ladybug_graph) assert out.success is True assert out.status == "none" assert out.message assert "search" in out.message.lower() -def test_resolve_empty_identifier_success_false(kuzu_graph) -> None: - out = resolve_v2("", graph=kuzu_graph) +def test_resolve_empty_identifier_success_false(ladybug_graph) -> None: + out = resolve_v2("", graph=ladybug_graph) assert out.success is False assert out.status == "none" assert out.message and out.message.startswith("Invalid identifier:") -def test_resolve_whitespace_identifier_success_false(kuzu_graph) -> None: - out = resolve_v2(" ", graph=kuzu_graph) +def test_resolve_whitespace_identifier_success_false(ladybug_graph) -> None: + out = resolve_v2(" ", graph=ladybug_graph) assert out.success is False assert out.status == "none" assert out.message and out.message.startswith("Invalid identifier:") @@ -1055,11 +1055,11 @@ def _rows(self, query: str, params: dict | None = None) -> list: assert out.node.id == "sym:com.fixture.DedupeMe" -def test_resolve_route_method_path_returns_one(kuzu_graph_route_extraction_smoke) -> None: +def test_resolve_route_method_path_returns_one(ladybug_graph_route_extraction_smoke) -> None: out = resolve_v2( "service-a GET /api/users", hint_kind="route", - graph=kuzu_graph_route_extraction_smoke, + graph=ladybug_graph_route_extraction_smoke, ) assert out.success is True assert out.status == "one" @@ -1068,11 +1068,11 @@ def test_resolve_route_method_path_returns_one(kuzu_graph_route_extraction_smoke assert out.node.microservice == "service-a" -def test_resolve_route_template_returns_one_or_many(kuzu_graph_route_extraction_smoke) -> None: +def test_resolve_route_template_returns_one_or_many(ladybug_graph_route_extraction_smoke) -> None: out = resolve_v2( "/api/users", hint_kind="route", - graph=kuzu_graph_route_extraction_smoke, + graph=ladybug_graph_route_extraction_smoke, ) assert out.success is True assert out.status in {"one", "many"} @@ -1083,14 +1083,14 @@ def test_resolve_route_template_returns_one_or_many(kuzu_graph_route_extraction_ assert out.node is not None -def test_resolve_client_target_service(kuzu_graph, kuzu_db_path_http_caller_smoke) -> None: - from kuzu_queries import KuzuGraph +def test_resolve_client_target_service(ladybug_graph, ladybug_db_path_http_caller_smoke) -> None: + from ladybug_queries import LadybugGraph - graph = kuzu_graph + graph = ladybug_graph rows = graph.list_clients(limit=500) seed = next((r for r in rows if str(r.get("target_service") or "").strip()), None) if seed is None: - graph = KuzuGraph(str(kuzu_db_path_http_caller_smoke)) + graph = LadybugGraph(str(ladybug_db_path_http_caller_smoke)) rows = graph.list_clients(limit=500) seed = next((r for r in rows if str(r.get("target_service") or "").strip()), None) if seed is None: @@ -1105,10 +1105,10 @@ def test_resolve_client_target_service(kuzu_graph, kuzu_db_path_http_caller_smok assert out.node is not None -def test_resolve_client_target_path_pair(kuzu_graph, kuzu_db_path_http_caller_smoke) -> None: - from kuzu_queries import KuzuGraph +def test_resolve_client_target_path_pair(ladybug_graph, ladybug_db_path_http_caller_smoke) -> None: + from ladybug_queries import LadybugGraph - def _seed_client(g: KuzuGraph) -> dict | None: + def _seed_client(g: LadybugGraph) -> dict | None: rows = g.list_clients(limit=500) return next( ( @@ -1120,10 +1120,10 @@ def _seed_client(g: KuzuGraph) -> dict | None: None, ) - graph = kuzu_graph + graph = ladybug_graph seed = _seed_client(graph) if seed is None: - graph = KuzuGraph(str(kuzu_db_path_http_caller_smoke)) + graph = LadybugGraph(str(ladybug_db_path_http_caller_smoke)) seed = _seed_client(graph) if seed is None: pytest.skip("no client with target_service and path in fixture") @@ -1140,17 +1140,17 @@ def _seed_client(g: KuzuGraph) -> dict | None: assert out.node is not None -def test_resolve_natural_language_sentence_returns_none(kuzu_graph) -> None: +def test_resolve_natural_language_sentence_returns_none(ladybug_graph) -> None: out = resolve_v2( "the client that handles smartcare assignments", - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is True assert out.status == "none" -def test_resolve_wildcard_identifier_returns_none(kuzu_graph) -> None: - out = resolve_v2("com.foo.*Service", hint_kind="symbol", graph=kuzu_graph) +def test_resolve_wildcard_identifier_returns_none(ladybug_graph) -> None: + out = resolve_v2("com.foo.*Service", hint_kind="symbol", graph=ladybug_graph) assert out.success is True assert out.status == "none" @@ -1285,11 +1285,11 @@ def _rows(self, query: str, params: dict | None = None) -> list: assert seen == set(VALID_RESOLVE_REASONS) -def test_resolve_success_output_invariants(kuzu_graph, kuzu_graph_fqn_collision_smoke) -> None: +def test_resolve_success_output_invariants(ladybug_graph, ladybug_graph_fqn_collision_smoke) -> None: one = resolve_v2( "com.nonexistent.ZzzMissing", hint_kind="symbol", - graph=kuzu_graph, + graph=ladybug_graph, ) assert one.success is True assert one.status == "none" @@ -1300,16 +1300,16 @@ def test_resolve_success_output_invariants(kuzu_graph, kuzu_graph_fqn_collision_ many = resolve_v2( "com.example.SharedDto#process()", hint_kind="symbol", - graph=kuzu_graph_fqn_collision_smoke, + graph=ladybug_graph_fqn_collision_smoke, ) assert many.success is True assert many.status == "many" assert many.node is None assert len(many.candidates) >= 2 - sym = find_v2("symbol", {"role": "CONTROLLER"}, graph=kuzu_graph, limit=1) + sym = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph, limit=1) assert sym.success and sym.results - single = resolve_v2(sym.results[0].id, hint_kind="symbol", graph=kuzu_graph) + single = resolve_v2(sym.results[0].id, hint_kind="symbol", graph=ladybug_graph) assert single.success is True assert single.status == "one" assert single.node is not None @@ -1321,9 +1321,9 @@ def test_resolve_success_output_invariants(kuzu_graph, kuzu_graph_fqn_collision_ ) -def test_neighbors_calls_ordered_by_call_site(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) - out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], limit=500, graph=kuzu_graph) +def test_neighbors_calls_ordered_by_call_site(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) + out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], limit=500, graph=ladybug_graph) assert out.success is True assert len(out.results) >= 2 sites = [ @@ -1333,15 +1333,15 @@ def test_neighbors_calls_ordered_by_call_site(kuzu_graph) -> None: assert sites == sorted(sites) -def test_neighbors_calls_edge_filter_callee_declaring_role(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) +def test_neighbors_calls_edge_filter_callee_declaring_role(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) out = neighbors_v2( mid, direction="out", edge_types=["CALLS"], edge_filter={"callee_declaring_role": "SERVICE"}, limit=500, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is True assert out.results @@ -1349,22 +1349,22 @@ def test_neighbors_calls_edge_filter_callee_declaring_role(kuzu_graph) -> None: assert edge.attrs.get("callee_declaring_role") == "SERVICE" -def test_neighbors_calls_edge_filter_pushdown_in_cypher(kuzu_graph, monkeypatch) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_calls_edge_filter_pushdown_in_cypher(ladybug_graph, monkeypatch) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") captured: list[str] = [] - orig_rows = kuzu_graph._rows + orig_rows = ladybug_graph._rows def _capture_rows(query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]: captured.append(query) return orig_rows(query, params) - monkeypatch.setattr(kuzu_graph, "_rows", _capture_rows) + monkeypatch.setattr(ladybug_graph, "_rows", _capture_rows) out = neighbors_v2( mid, direction="out", edge_types=["CALLS"], edge_filter={"callee_declaring_role": "SERVICE", "min_confidence": 0.5}, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is True calls_queries = [q for q in captured if "ORDER BY e.call_site_line" in q] @@ -1374,10 +1374,10 @@ def _capture_rows(query: str, params: dict[str, Any] | None = None) -> list[dict assert "confidence" in q -def test_neighbors_calls_edge_filter_before_limit(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) +def test_neighbors_calls_edge_filter_before_limit(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) unfiltered = neighbors_v2( - mid, direction="out", edge_types=["CALLS"], limit=500, graph=kuzu_graph + mid, direction="out", edge_types=["CALLS"], limit=500, graph=ladybug_graph ) assert unfiltered.success is True non_other_total = sum( @@ -1385,7 +1385,7 @@ def test_neighbors_calls_edge_filter_before_limit(kuzu_graph) -> None: ) assert non_other_total >= 6 unfiltered_cap = neighbors_v2( - mid, direction="out", edge_types=["CALLS"], limit=5, graph=kuzu_graph + mid, direction="out", edge_types=["CALLS"], limit=5, graph=ladybug_graph ) assert unfiltered_cap.success is True assert len(unfiltered_cap.results) == 5 @@ -1398,7 +1398,7 @@ def test_neighbors_calls_edge_filter_before_limit(kuzu_graph) -> None: edge_types=["CALLS"], edge_filter={"exclude_callee_declaring_roles": ["OTHER"]}, limit=5, - graph=kuzu_graph, + graph=ladybug_graph, ) assert filtered.success is True assert len(filtered.results) == 5 @@ -1406,14 +1406,14 @@ def test_neighbors_calls_edge_filter_before_limit(kuzu_graph) -> None: assert other_in_cap >= 1 -def test_neighbors_calls_edge_filter_mixed_types_fail_loud(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_calls_edge_filter_mixed_types_fail_loud(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") out = neighbors_v2( mid, direction="out", edge_types=["CALLS", "OVERRIDES"], edge_filter={"callee_declaring_role": "SERVICE"}, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert out.message @@ -1421,8 +1421,8 @@ def test_neighbors_calls_edge_filter_mixed_types_fail_loud(kuzu_graph) -> None: assert "OVERRIDES" in out.message -def test_neighbors_calls_edge_filter_composed_types_fail_loud(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_neighbors_calls_edge_filter_composed_types_fail_loud(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[e:EXPOSES]->(:Route) " "WHERE t.role = 'CONTROLLER' AND t.kind = 'class' " "RETURN t.id AS id LIMIT 1", @@ -1434,7 +1434,7 @@ def test_neighbors_calls_edge_filter_composed_types_fail_loud(kuzu_graph) -> Non direction="out", edge_types=["CALLS", "DECLARES.EXPOSES"], edge_filter={"callee_declaring_role": "SERVICE"}, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert out.message @@ -1442,8 +1442,8 @@ def test_neighbors_calls_edge_filter_composed_types_fail_loud(kuzu_graph) -> Non assert "DECLARES.EXPOSES" in out.message -def test_neighbors_calls_edge_filter_role_axes_xor(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_calls_edge_filter_role_axes_xor(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") out = neighbors_v2( mid, direction="out", @@ -1452,21 +1452,21 @@ def test_neighbors_calls_edge_filter_role_axes_xor(kuzu_graph) -> None: "callee_declaring_role": "SERVICE", "exclude_callee_declaring_roles": ["OTHER"], }, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert out.message assert "mutually exclusive" in out.message.lower() -def test_neighbors_calls_edge_filter_strategy_xor(kuzu_graph) -> None: - mid = _method_id_with_calls(kuzu_graph, "out") +def test_neighbors_calls_edge_filter_strategy_xor(ladybug_graph) -> None: + mid = _method_id_with_calls(ladybug_graph, "out") out = neighbors_v2( mid, direction="out", edge_types=["CALLS"], edge_filter={"include_strategies": ["exact"], "exclude_strategies": ["phantom"]}, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert out.message @@ -1477,15 +1477,15 @@ def test_neighbors_calls_edge_filter_strategy_xor(kuzu_graph) -> None: os.environ.get("JAVA_CODEBASE_RAG_RUN_HEAVY", "").strip() != "1", reason="perf gate; set JAVA_CODEBASE_RAG_RUN_HEAVY=1", ) -def test_neighbors_calls_perf_empty_filter_client_message_processor(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) +def test_neighbors_calls_perf_empty_filter_client_message_processor(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) baseline = json.loads(_PERF_BASELINES_PATH.read_text())[ "neighbors_calls_empty_filter_client_message_processor" ]["median_sec"] times: list[float] = [] for _ in range(5): t0 = time.perf_counter() - out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], limit=500, graph=kuzu_graph) + out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], limit=500, graph=ladybug_graph) times.append(time.perf_counter() - t0) assert out.success is True assert out.results @@ -1493,15 +1493,15 @@ def test_neighbors_calls_perf_empty_filter_client_message_processor(kuzu_graph) assert median_sec <= float(baseline) * 1.5 -def test_neighbors_include_unresolved_interleaved_order(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) +def test_neighbors_include_unresolved_interleaved_order(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) out = neighbors_v2( mid, direction="out", edge_types=["CALLS"], include_unresolved=True, limit=500, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is True assert out.results @@ -1525,22 +1525,22 @@ def test_neighbors_include_unresolved_interleaved_order(kuzu_graph) -> None: assert keys == sorted(keys) -def test_neighbors_include_unresolved_edge_filter_mutex(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) +def test_neighbors_include_unresolved_edge_filter_mutex(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) out = neighbors_v2( mid, direction="out", edge_types=["CALLS"], include_unresolved=True, edge_filter={"min_confidence": 0.0}, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert "incompatible" in (out.message or "").lower() -def test_neighbors_dedup_calls_collapses_identical_dst(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_neighbors_dedup_calls_collapses_identical_dst(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol)-[c:CALLS]->(dst:Symbol) " "WITH m, dst, collect(c.call_site_line) AS lines " "WHERE size(lines) > 1 " @@ -1550,7 +1550,7 @@ def test_neighbors_dedup_calls_collapses_identical_dst(kuzu_graph) -> None: pytest.skip("no duplicate (caller,callee) CALLS pair in bank fixture") mid = str(rows[0]["mid"]) flat = neighbors_v2( - mid, direction="out", edge_types=["CALLS"], limit=500, graph=kuzu_graph, + mid, direction="out", edge_types=["CALLS"], limit=500, graph=ladybug_graph, ) deduped = neighbors_v2( mid, @@ -1558,7 +1558,7 @@ def test_neighbors_dedup_calls_collapses_identical_dst(kuzu_graph) -> None: edge_types=["CALLS"], dedup_calls=True, limit=500, - graph=kuzu_graph, + graph=ladybug_graph, ) assert flat.success and deduped.success assert len(deduped.results) < len(flat.results) @@ -1566,22 +1566,22 @@ def test_neighbors_dedup_calls_collapses_identical_dst(kuzu_graph) -> None: assert multi, "dedup_calls should emit call_site_count on collapsed rows" -def test_describe_ucs_id_not_describable(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_ucs_id_not_describable(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (u:UnresolvedCallSite) RETURN u.id AS id LIMIT 1", ) assert rows ucs_id = str(rows[0]["id"]) assert ucs_id.startswith("ucs:") - out = describe_v2(ucs_id, graph=kuzu_graph) + out = describe_v2(ucs_id, graph=ladybug_graph) assert out.success is False assert out.record is None assert "not describable" in (out.message or "").lower() assert "unresolved-calls" in (out.message or "").lower() -def test_neighbors_dedup_calls_include_unresolved(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) +def test_neighbors_dedup_calls_include_unresolved(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) out = neighbors_v2( mid, direction="out", @@ -1589,7 +1589,7 @@ def test_neighbors_dedup_calls_include_unresolved(kuzu_graph) -> None: include_unresolved=True, dedup_calls=True, limit=500, - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is True kinds = {str((e.attrs or {}).get("row_kind") or "resolved") for e in out.results} @@ -1609,9 +1609,9 @@ def _calls_transcript_sort_key_from_edge(edge: Edge) -> tuple[int, int, int]: return (line, byte, kind_rank) -def test_describe_unresolved_call_sites_rollup_cap_footer_and_total(kuzu_graph) -> None: - mid = client_message_processor_process_id(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_describe_unresolved_call_sites_rollup_cap_footer_and_total(ladybug_graph) -> None: + mid = client_message_processor_process_id(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success and out.record data = out.record.data total = int(data.get("unresolved_call_sites_total") or 0) diff --git a/tests/test_mcp_v2_compose.py b/tests/test_mcp_v2_compose.py index 6c060924..d3f31bb5 100644 --- a/tests/test_mcp_v2_compose.py +++ b/tests/test_mcp_v2_compose.py @@ -5,8 +5,8 @@ import pytest -from _builders import build_kuzu_to -from kuzu_queries import KuzuGraph +from _builders import build_ladybug_to +from ladybug_queries import LadybugGraph from mcp_v2 import ( _NEIGHBOR_EDGE_TYPES_ADAPTER, _TYPE_SYMBOL_KINDS_FOR_EDGE_ROLLUP, @@ -37,10 +37,10 @@ @pytest.fixture -def override_axis_graph(tmp_path: Path) -> KuzuGraph: - db_path = tmp_path / "code_graph.kuzu" - build_kuzu_to(_OVERRIDE_AXIS_FIXTURE, db_path, max_pass=5) - return KuzuGraph(str(db_path)) +def override_axis_graph(tmp_path: Path) -> LadybugGraph: + db_path = tmp_path / "code_graph.lbug" + build_ladybug_to(_OVERRIDE_AXIS_FIXTURE, db_path, max_pass=5) + return LadybugGraph(str(db_path)) def _collect_ids(cell: Any) -> list[str]: @@ -52,7 +52,7 @@ def _collect_ids(cell: Any) -> list[str]: return [s] if s else [] -def _dispatch_down_override_method_ids(graph: KuzuGraph, method_id: str) -> list[str]: +def _dispatch_down_override_method_ids(graph: LadybugGraph, method_id: str) -> list[str]: rows = graph._rows( # noqa: SLF001 "MATCH (m:Symbol {id: $id})<-[:DECLARES]-(t:Symbol) " "MATCH (impl:Symbol)-[:IMPLEMENTS|EXTENDS]->(t) " @@ -66,7 +66,7 @@ def _dispatch_down_override_method_ids(graph: KuzuGraph, method_id: str) -> list return list(dict.fromkeys(_collect_ids(rows[0].get("ids")))) -def _dispatch_up_declaration_method_ids(graph: KuzuGraph, method_id: str) -> list[str]: +def _dispatch_up_declaration_method_ids(graph: LadybugGraph, method_id: str) -> list[str]: rows = graph._rows( # noqa: SLF001 "MATCH (m:Symbol {id: $id})<-[:DECLARES]-(impl:Symbol) " "MATCH (impl)-[:IMPLEMENTS|EXTENDS]->(parent:Symbol) " @@ -80,7 +80,7 @@ def _dispatch_up_declaration_method_ids(graph: KuzuGraph, method_id: str) -> lis return list(dict.fromkeys(_collect_ids(rows[0].get("ids")))) -def _edge_row_count_from_methods(graph: KuzuGraph, method_ids: list[str], rel: str) -> int: +def _edge_row_count_from_methods(graph: LadybugGraph, method_ids: list[str], rel: str) -> int: total = 0 for mid in method_ids: rows = graph._rows( # noqa: SLF001 @@ -91,8 +91,8 @@ def _edge_row_count_from_methods(graph: KuzuGraph, method_ids: list[str], rel: s return total -def _method_id_without_dispatch_rollups(kuzu_graph: KuzuGraph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_id_without_dispatch_rollups(ladybug_graph: LadybugGraph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol) " "WHERE m.kind = 'method' " "AND NOT list_contains(COALESCE(m.modifiers, []), 'static') " @@ -110,8 +110,8 @@ def _method_id_without_dispatch_rollups(kuzu_graph: KuzuGraph) -> str: return str(rows[0]["id"]) -def _controller_method_with_calls(kuzu_graph) -> tuple[str, str]: - rows = kuzu_graph._rows( # noqa: SLF001 +def _controller_method_with_calls(ladybug_graph) -> tuple[str, str]: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol) " "WHERE t.role = 'CONTROLLER' AND m.kind IN ['method', 'constructor'] " "AND (EXISTS { MATCH (:Symbol)-[:CALLS]->(m) } OR EXISTS { MATCH (m)-[:CALLS]->(:Symbol) }) " @@ -122,14 +122,14 @@ def _controller_method_with_calls(kuzu_graph) -> tuple[str, str]: return str(rows[0]["id"]), str(rows[0]["fqn"]) -def _method_with_incoming_calls(kuzu_graph) -> tuple[str, str]: - rows = kuzu_graph._rows( # noqa: SLF001 +def _method_with_incoming_calls(ladybug_graph) -> tuple[str, str]: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (src:Symbol)-[:CALLS]->(dst:Symbol) " "RETURN dst.id AS id LIMIT 1" ) assert rows node_id = str(rows[0]["id"]) - fqn_rows = kuzu_graph._rows( # noqa: SLF001 + fqn_rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (s:Symbol) WHERE s.id = $id RETURN s.fqn AS fqn LIMIT 1", {"id": node_id}, ) @@ -137,37 +137,37 @@ def _method_with_incoming_calls(kuzu_graph) -> tuple[str, str]: return node_id, str(fqn_rows[0]["fqn"]) -def _route_with_handler(kuzu_graph) -> str: - rows = kuzu_graph._rows( # noqa: SLF001 +def _route_with_handler(ladybug_graph) -> str: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (:Symbol)-[:EXPOSES]->(r:Route) RETURN r.id AS id ORDER BY r.id LIMIT 1" ) assert rows return str(rows[0]["id"]) -def test_describe_edge_summary_for_controller(kuzu_graph) -> None: - node_id, fqn = _controller_method_with_calls(kuzu_graph) - out = describe_v2(node_id, graph=kuzu_graph) +def test_describe_edge_summary_for_controller(ladybug_graph) -> None: + node_id, fqn = _controller_method_with_calls(ladybug_graph) + out = describe_v2(node_id, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None calls = out.record.edge_summary.get("CALLS", {"in": 0, "out": 0}) - callers = kuzu_graph.find_callers(fqn, limit=1000, exclude_external=False) - callees = kuzu_graph.find_callees(fqn, limit=1000, exclude_external=False) + callers = ladybug_graph.find_callers(fqn, limit=1000, exclude_external=False) + callees = ladybug_graph.find_callees(fqn, limit=1000, exclude_external=False) assert int(calls.get("in", 0)) == len(callers) assert int(calls.get("out", 0)) == len(callees) -def test_describe_edge_summary_omits_zero_count_types(kuzu_graph) -> None: - node_id, _ = _controller_method_with_calls(kuzu_graph) - out = describe_v2(node_id, graph=kuzu_graph) +def test_describe_edge_summary_omits_zero_count_types(ladybug_graph) -> None: + node_id, _ = _controller_method_with_calls(ladybug_graph) + out = describe_v2(node_id, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None for edge_type in _EDGE_TYPES: if edge_type in out.record.edge_summary: continue - rows = kuzu_graph._rows( # noqa: SLF001 + rows = ladybug_graph._rows( # noqa: SLF001 f"MATCH (n {{id: $id}})-[e:{edge_type}]->() RETURN count(e) AS n " f"UNION ALL " f"MATCH (n {{id: $id}})<-[e:{edge_type}]-() RETURN count(e) AS n", @@ -176,9 +176,9 @@ def test_describe_edge_summary_omits_zero_count_types(kuzu_graph) -> None: assert sum(int(r.get("n") or 0) for r in rows) == 0 -def test_describe_edge_summary_for_route(kuzu_graph) -> None: - route_id = _route_with_handler(kuzu_graph) - out = describe_v2(route_id, graph=kuzu_graph) +def test_describe_edge_summary_for_route(ladybug_graph) -> None: + route_id = _route_with_handler(ladybug_graph) + out = describe_v2(route_id, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.kind == "route" @@ -187,7 +187,7 @@ def test_describe_edge_summary_for_route(kuzu_graph) -> None: assert int(exposes.get("in", 0)) >= 1 -def test_search_populates_symbol_id_when_chunk_rooted_in_symbol(monkeypatch, kuzu_graph) -> None: +def test_search_populates_symbol_id_when_chunk_rooted_in_symbol(monkeypatch, ladybug_graph) -> None: rows: list[dict[str, Any]] = [ { "filename": "A.java", @@ -225,7 +225,7 @@ def test_search_populates_symbol_id_when_chunk_rooted_in_symbol(monkeypatch, kuz }, ] monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: rows) - out = search_v2("query", graph=kuzu_graph) + out = search_v2("query", graph=ladybug_graph) assert out.success is True rooted = [hit for hit in out.results if hit.fqn is not None] assert rooted @@ -239,9 +239,9 @@ def test_meta_returns_per_edge_type_counts() -> None: assert all(int(v) >= 0 for v in out.edge_counts.values()) -def test_search_describe_neighbors_chain_end_to_end(kuzu_graph, monkeypatch) -> None: - node_id, _ = _method_with_incoming_calls(kuzu_graph) - rows = kuzu_graph._rows( # noqa: SLF001 +def test_search_describe_neighbors_chain_end_to_end(ladybug_graph, monkeypatch) -> None: + node_id, _ = _method_with_incoming_calls(ladybug_graph) + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol {id: $id}) RETURN m.fqn AS fqn, m.role AS role, m.module AS module, " "m.microservice AS microservice, m.filename AS filename", {"id": node_id}, @@ -265,21 +265,21 @@ def test_search_describe_neighbors_chain_end_to_end(kuzu_graph, monkeypatch) -> } ], ) - search_out = search_v2("assign", graph=kuzu_graph) + search_out = search_v2("assign", graph=ladybug_graph) assert search_out.success is True assert search_out.results top_symbol_id = search_out.results[0].symbol_id assert top_symbol_id is not None - describe_out = describe_v2(top_symbol_id, graph=kuzu_graph) + describe_out = describe_v2(top_symbol_id, graph=ladybug_graph) assert describe_out.success is True assert describe_out.record is not None - neighbors_out = neighbors_v2(top_symbol_id, direction="in", edge_types=["CALLS"], graph=kuzu_graph) + neighbors_out = neighbors_v2(top_symbol_id, direction="in", edge_types=["CALLS"], graph=ladybug_graph) assert neighbors_out.success is True assert neighbors_out.results -def test_describe_type_rollups_include_declares_producer(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_type_rollups_include_declares_producer(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[e:DECLARES_PRODUCER]->(:Producer) " "WHERE t.kind IN $kinds " "RETURN t.id AS id, count(e) AS n ORDER BY n DESC LIMIT 1", @@ -290,15 +290,15 @@ def test_describe_type_rollups_include_declares_producer(kuzu_graph) -> None: tid = str(rows[0]["id"]) n = int(rows[0]["n"] or 0) assert n >= 1 - out = describe_v2(tid, graph=kuzu_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None assert out.record.edge_summary["DECLARES.DECLARES_PRODUCER"]["out"] == n -def test_describe_class_with_brownfield_clients_emits_composed_key(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_class_with_brownfield_clients_emits_composed_key(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[e:DECLARES_CLIENT]->(:Client) " "WHERE t.kind IN $kinds " "RETURN t.id AS id, count(e) AS n ORDER BY n DESC LIMIT 1", @@ -308,15 +308,15 @@ def test_describe_class_with_brownfield_clients_emits_composed_key(kuzu_graph) - tid = str(rows[0]["id"]) n = int(rows[0]["n"] or 0) assert n >= 1 - out = describe_v2(tid, graph=kuzu_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None assert out.record.edge_summary["DECLARES.DECLARES_CLIENT"]["out"] == n -def test_describe_controller_class_emits_composed_exposes(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_controller_class_emits_composed_exposes(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[e:EXPOSES]->(:Route) " "WHERE t.role = 'CONTROLLER' AND t.kind = 'class' " "RETURN t.id AS id, count(e) AS n ORDER BY n DESC LIMIT 1", @@ -325,16 +325,16 @@ def test_describe_controller_class_emits_composed_exposes(kuzu_graph) -> None: tid = str(rows[0]["id"]) n = int(rows[0]["n"] or 0) assert n >= 1 - out = describe_v2(tid, graph=kuzu_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None assert out.record.edge_summary["DECLARES.EXPOSES"]["out"] == n -def test_describe_method_symbol_no_composed_keys(kuzu_graph) -> None: - node_id, _ = _controller_method_with_calls(kuzu_graph) - out = describe_v2(node_id, graph=kuzu_graph) +def test_describe_method_symbol_no_composed_keys(ladybug_graph) -> None: + node_id, _ = _controller_method_with_calls(ladybug_graph) + out = describe_v2(node_id, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None @@ -343,8 +343,8 @@ def test_describe_method_symbol_no_composed_keys(kuzu_graph) -> None: assert "DECLARES.EXPOSES" not in es -def test_describe_pojo_no_composed_keys(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_pojo_no_composed_keys(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(:Symbol) " "WHERE t.kind IN $kinds " "AND NOT EXISTS { MATCH (t)-[:DECLARES]->(m:Symbol)-[:DECLARES_CLIENT]->() } " @@ -354,7 +354,7 @@ def test_describe_pojo_no_composed_keys(kuzu_graph) -> None: ) assert rows tid = str(rows[0]["id"]) - out = describe_v2(tid, graph=kuzu_graph) + out = describe_v2(tid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None @@ -363,8 +363,8 @@ def test_describe_pojo_no_composed_keys(kuzu_graph) -> None: assert "DECLARES.EXPOSES" not in es -def test_describe_interface_method_with_annotated_impl_emits_rollup(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_interface_method_with_annotated_impl_emits_rollup(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (iface:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'requestAssignment' " "RETURN m.id AS id LIMIT 1", @@ -372,23 +372,23 @@ def test_describe_interface_method_with_annotated_impl_emits_rollup(kuzu_graph) ) assert rows mid = str(rows[0]["id"]) - impl_ids = _dispatch_down_override_method_ids(kuzu_graph, mid) + impl_ids = _dispatch_down_override_method_ids(ladybug_graph, mid) assert impl_ids want_ob = len(impl_ids) - want_dc = _edge_row_count_from_methods(kuzu_graph, impl_ids, "DECLARES_CLIENT") - out = describe_v2(mid, graph=kuzu_graph) + want_dc = _edge_row_count_from_methods(ladybug_graph, impl_ids, "DECLARES_CLIENT") + out = describe_v2(mid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None es = out.record.edge_summary assert es.get("OVERRIDDEN_BY") == {"in": 0, "out": want_ob} assert es.get("OVERRIDDEN_BY.DECLARES_CLIENT") == {"in": 0, "out": want_dc} - out_ob = neighbors_v2(mid, direction="out", edge_types=["OVERRIDDEN_BY"], graph=kuzu_graph) + out_ob = neighbors_v2(mid, direction="out", edge_types=["OVERRIDDEN_BY"], graph=ladybug_graph) assert out_ob.success is True -def test_describe_concrete_override_emits_overrides_rollup(kuzu_graph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_describe_concrete_override_emits_overrides_rollup(ladybug_graph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'requestAssignment' " "RETURN m.id AS id LIMIT 1", @@ -396,19 +396,19 @@ def test_describe_concrete_override_emits_overrides_rollup(kuzu_graph) -> None: ) assert rows mid = str(rows[0]["id"]) - decl_ids = _dispatch_up_declaration_method_ids(kuzu_graph, mid) + decl_ids = _dispatch_up_declaration_method_ids(ladybug_graph, mid) assert decl_ids want_ov = len(decl_ids) - out = describe_v2(mid, graph=kuzu_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None assert out.record.edge_summary.get("OVERRIDES") == {"in": 0, "out": want_ov} -def test_describe_method_no_overrides_silent(kuzu_graph) -> None: - mid = _method_id_without_dispatch_rollups(kuzu_graph) - out = describe_v2(mid, graph=kuzu_graph) +def test_describe_method_no_overrides_silent(ladybug_graph) -> None: + mid = _method_id_without_dispatch_rollups(ladybug_graph) + out = describe_v2(mid, graph=ladybug_graph) assert out.success is True assert out.record is not None assert out.record.edge_summary is not None @@ -420,7 +420,7 @@ def test_describe_method_no_overrides_silent(kuzu_graph) -> None: def test_describe_abstract_method_with_producer_override_emits_declares_producer( - override_axis_graph: KuzuGraph, + override_axis_graph: LadybugGraph, ) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " @@ -444,7 +444,7 @@ def test_describe_abstract_method_with_producer_override_emits_declares_producer assert es.get("OVERRIDDEN_BY.DECLARES_PRODUCER") == {"in": 0, "out": want_dp} -def test_describe_abstract_method_with_route_override_emits_exposes(override_axis_graph: KuzuGraph) -> None: +def test_describe_abstract_method_with_route_override_emits_exposes(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'handle' " @@ -468,7 +468,7 @@ def test_describe_abstract_method_with_route_override_emits_exposes(override_axi def test_describe_method_edge_summary_overrides_merges_stored_in_with_dispatch_up_out( - override_axis_graph: KuzuGraph, + override_axis_graph: LadybugGraph, ) -> None: """Middle override: incoming [:OVERRIDES] from subclass + rollup dispatch-up must not zero `in`.""" rows = override_axis_graph._rows( # noqa: SLF001 @@ -487,7 +487,7 @@ def test_describe_method_edge_summary_overrides_merges_stored_in_with_dispatch_u def test_describe_interface_method_diamond_override_counts_once_per_upstream( - override_axis_graph: KuzuGraph, + override_axis_graph: LadybugGraph, ) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " @@ -506,7 +506,7 @@ def test_describe_interface_method_diamond_override_counts_once_per_upstream( assert out.record.edge_summary.get("OVERRIDES") == {"in": 0, "out": 2} -def test_overrides_stored_neighbors_in_matches_override_axis_impl_ids(override_axis_graph: KuzuGraph) -> None: +def test_overrides_stored_neighbors_in_matches_override_axis_impl_ids(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'handle' " @@ -522,7 +522,7 @@ def test_overrides_stored_neighbors_in_matches_override_axis_impl_ids(override_a assert got == want -def test_overrides_stored_neighbors_out_matches_override_axis_decl_ids(override_axis_graph: KuzuGraph) -> None: +def test_overrides_stored_neighbors_out_matches_override_axis_decl_ids(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'shared' " @@ -538,10 +538,10 @@ def test_overrides_stored_neighbors_out_matches_override_axis_decl_ids(override_ assert got == want -def test_overrides_rel_schema_round_trips(override_axis_graph: KuzuGraph) -> None: - import kuzu +def test_overrides_rel_schema_round_trips(override_axis_graph: LadybugGraph) -> None: + import ladybug - conn = kuzu.Connection(kuzu.Database(override_axis_graph.db_path, read_only=True)) + conn = ladybug.Connection(ladybug.Database(override_axis_graph.db_path, read_only=True)) tables = set() r = conn.execute("CALL show_tables() RETURN *;") while r.has_next(): @@ -567,7 +567,7 @@ def test_neighbors_edge_type_adapter_accepts_overrides() -> None: ) -def _request_assignment_method_id(graph: KuzuGraph) -> str: +def _request_assignment_method_id(graph: LadybugGraph) -> str: rows = graph._rows( # noqa: SLF001 "MATCH (iface:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'requestAssignment' " @@ -579,12 +579,12 @@ def _request_assignment_method_id(graph: KuzuGraph) -> str: def test_override_axis_rollup_dispatch_matches_signature_walk_on_fixtures( - kuzu_graph: KuzuGraph, - override_axis_graph: KuzuGraph, + ladybug_graph: LadybugGraph, + override_axis_graph: LadybugGraph, ) -> None: """Guard: stored [:OVERRIDES] dispatch ids stay aligned with legacy signature walk on fixtures.""" cases = [ - (kuzu_graph, _request_assignment_method_id(kuzu_graph)), + (ladybug_graph, _request_assignment_method_id(ladybug_graph)), ( override_axis_graph, _override_axis_smoke_method_id( @@ -605,12 +605,12 @@ def test_neighbors_accepts_overridden_by_dot_keys() -> None: _NEIGHBOR_EDGE_TYPES_ADAPTER.validate_python([key]) -def test_neighbors_overridden_by_dot_key_returns_overriders(kuzu_graph: KuzuGraph) -> None: - mid = _request_assignment_method_id(kuzu_graph) - want = sorted(_dispatch_down_override_method_ids(kuzu_graph, mid)) +def test_neighbors_overridden_by_dot_key_returns_overriders(ladybug_graph: LadybugGraph) -> None: + mid = _request_assignment_method_id(ladybug_graph) + want = sorted(_dispatch_down_override_method_ids(ladybug_graph, mid)) assert want - out_virtual = neighbors_v2(mid, direction="out", edge_types=["OVERRIDDEN_BY"], graph=kuzu_graph) - out_stored = neighbors_v2(mid, direction="in", edge_types=["OVERRIDES"], graph=kuzu_graph) + out_virtual = neighbors_v2(mid, direction="out", edge_types=["OVERRIDDEN_BY"], graph=ladybug_graph) + out_stored = neighbors_v2(mid, direction="in", edge_types=["OVERRIDES"], graph=ladybug_graph) assert out_virtual.success is True assert out_stored.success is True got_virtual = sorted({e.other.id for e in out_virtual.results}) @@ -622,10 +622,10 @@ def test_neighbors_overridden_by_dot_key_returns_overriders(kuzu_graph: KuzuGrap assert all("via_id" not in e.attrs for e in out_virtual.results) -def test_neighbors_overridden_by_dot_key_declares_client(kuzu_graph: KuzuGraph) -> None: - mid = _request_assignment_method_id(kuzu_graph) +def test_neighbors_overridden_by_dot_key_declares_client(ladybug_graph: LadybugGraph) -> None: + mid = _request_assignment_method_id(ladybug_graph) out = neighbors_v2( - mid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=kuzu_graph, limit=500 + mid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=ladybug_graph, limit=500 ) assert out.success is True assert len(out.results) >= 1 @@ -634,7 +634,7 @@ def test_neighbors_overridden_by_dot_key_declares_client(kuzu_graph: KuzuGraph) assert all(e.other.kind == "client" for e in out.results) -def test_neighbors_overridden_by_dot_key_declares_producer(override_axis_graph: KuzuGraph) -> None: +def test_neighbors_overridden_by_dot_key_declares_producer(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'publish' " @@ -657,7 +657,7 @@ def test_neighbors_overridden_by_dot_key_declares_producer(override_axis_graph: assert all(e.other.kind == "producer" for e in out.results) -def test_neighbors_overridden_by_dot_key_exposes(override_axis_graph: KuzuGraph) -> None: +def test_neighbors_overridden_by_dot_key_exposes(override_axis_graph: LadybugGraph) -> None: rows = override_axis_graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = 'handle' " @@ -680,14 +680,14 @@ def test_neighbors_overridden_by_dot_key_exposes(override_axis_graph: KuzuGraph) assert all(e.other.kind == "route" for e in out.results) -def test_neighbors_overridden_by_dot_key_count_matches_edge_summary(kuzu_graph: KuzuGraph) -> None: - mid = _request_assignment_method_id(kuzu_graph) - d = describe_v2(mid, graph=kuzu_graph) +def test_neighbors_overridden_by_dot_key_count_matches_edge_summary(ladybug_graph: LadybugGraph) -> None: + mid = _request_assignment_method_id(ladybug_graph) + d = describe_v2(mid, graph=ladybug_graph) n = neighbors_v2( mid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], - graph=kuzu_graph, + graph=ladybug_graph, limit=500, ) assert d.success and d.record and d.record.edge_summary @@ -697,44 +697,44 @@ def test_neighbors_overridden_by_dot_key_count_matches_edge_summary(kuzu_graph: assert len(n.results) == summary["out"] -def test_neighbors_overridden_by_dot_key_type_origin_rejected(kuzu_graph: KuzuGraph) -> None: - tid, _ = _type_id_with_composed_key(kuzu_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") +def test_neighbors_overridden_by_dot_key_type_origin_rejected(ladybug_graph: LadybugGraph) -> None: + tid, _ = _type_id_with_composed_key(ladybug_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") out = neighbors_v2( - tid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=kuzu_graph + tid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=ladybug_graph ) assert out.success is False assert out.message is not None assert "method Symbol origin" in out.message -def test_neighbors_mixed_composed_families_on_type_rejected(kuzu_graph: KuzuGraph) -> None: - tid, _ = _type_id_with_composed_key(kuzu_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") +def test_neighbors_mixed_composed_families_on_type_rejected(ladybug_graph: LadybugGraph) -> None: + tid, _ = _type_id_with_composed_key(ladybug_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") out = neighbors_v2( tid, direction="out", edge_types=["DECLARES.DECLARES_CLIENT", "OVERRIDDEN_BY.DECLARES_CLIENT"], - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert out.message is not None assert "method Symbol origin" in out.message -def test_neighbors_mixed_composed_families_on_method_rejected(kuzu_graph: KuzuGraph) -> None: - mid = _request_assignment_method_id(kuzu_graph) +def test_neighbors_mixed_composed_families_on_method_rejected(ladybug_graph: LadybugGraph) -> None: + mid = _request_assignment_method_id(ladybug_graph) out = neighbors_v2( mid, direction="out", edge_types=["DECLARES.DECLARES_CLIENT", "OVERRIDDEN_BY.DECLARES_CLIENT"], - graph=kuzu_graph, + graph=ladybug_graph, ) assert out.success is False assert out.message is not None assert "type Symbol origin" in out.message -def test_neighbors_overridden_by_dot_key_static_method_rejected(kuzu_graph: KuzuGraph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_neighbors_overridden_by_dot_key_static_method_rejected(ladybug_graph: LadybugGraph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (m:Symbol) " "WHERE m.kind = 'method' AND list_contains(COALESCE(m.modifiers, []), 'static') " "RETURN m.id AS id LIMIT 1", @@ -742,15 +742,15 @@ def test_neighbors_overridden_by_dot_key_static_method_rejected(kuzu_graph: Kuzu assert rows mid = str(rows[0]["id"]) out = neighbors_v2( - mid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=kuzu_graph + mid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=ladybug_graph ) assert out.success is False assert out.message is not None assert "non-static" in out.message -def test_neighbors_overridden_by_dot_key_constructor_rejected(kuzu_graph: KuzuGraph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_neighbors_overridden_by_dot_key_constructor_rejected(ladybug_graph: LadybugGraph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(c:Symbol) " "WHERE c.kind = 'constructor' " "RETURN c.id AS id LIMIT 1", @@ -758,24 +758,24 @@ def test_neighbors_overridden_by_dot_key_constructor_rejected(kuzu_graph: KuzuGr assert rows cid = str(rows[0]["id"]) out = neighbors_v2( - cid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=kuzu_graph + cid, direction="out", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=ladybug_graph ) assert out.success is False assert out.message is not None assert "constructor" in out.message -def test_neighbors_overridden_by_dot_key_inbound_rejected(kuzu_graph: KuzuGraph) -> None: - mid = _request_assignment_method_id(kuzu_graph) +def test_neighbors_overridden_by_dot_key_inbound_rejected(ladybug_graph: LadybugGraph) -> None: + mid = _request_assignment_method_id(ladybug_graph) out = neighbors_v2( - mid, direction="in", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=kuzu_graph + mid, direction="in", edge_types=["OVERRIDDEN_BY.DECLARES_CLIENT"], graph=ladybug_graph ) assert out.success is False assert out.message is not None assert 'direction="out"' in out.message -def _override_axis_smoke_method_id(graph: KuzuGraph, *, fqn: str, method_name: str) -> str: +def _override_axis_smoke_method_id(graph: LadybugGraph, *, fqn: str, method_name: str) -> str: rows = graph._rows( # noqa: SLF001 "MATCH (t:Symbol {fqn: $fqn})-[:DECLARES]->(m:Symbol) " "WHERE m.kind = 'method' AND m.name = $name " @@ -788,11 +788,11 @@ def _override_axis_smoke_method_id(graph: KuzuGraph, *, fqn: str, method_name: s def _override_parity_graph_method_pairs( composed_key: str, - kuzu_graph: KuzuGraph, - override_axis_graph: KuzuGraph, -) -> list[tuple[KuzuGraph, str]]: + ladybug_graph: LadybugGraph, + override_axis_graph: LadybugGraph, +) -> list[tuple[LadybugGraph, str]]: # OVERRIDDEN_BY.DECLARES_CLIENT: bank-chat only — smoke corpus has no DECLARES_CLIENT on overriders. - pairs: list[tuple[KuzuGraph, str]] = [(kuzu_graph, _request_assignment_method_id(kuzu_graph))] + pairs: list[tuple[LadybugGraph, str]] = [(ladybug_graph, _request_assignment_method_id(ladybug_graph))] if composed_key in ("OVERRIDDEN_BY", "OVERRIDDEN_BY.EXPOSES"): pairs.append( ( @@ -820,13 +820,13 @@ def _override_parity_graph_method_pairs( @pytest.mark.parametrize("composed_key", _OVERRIDE_AXIS_COMPOSED_KEYS) def test_neighbors_overridden_by_rollup_traversal_parity_blocking( - kuzu_graph: KuzuGraph, - override_axis_graph: KuzuGraph, + ladybug_graph: LadybugGraph, + override_axis_graph: LadybugGraph, composed_key: str, ) -> None: checked = False for graph, mid in _override_parity_graph_method_pairs( - composed_key, kuzu_graph, override_axis_graph + composed_key, ladybug_graph, override_axis_graph ): d = describe_v2(mid, graph=graph) n = neighbors_v2( @@ -842,8 +842,8 @@ def test_neighbors_overridden_by_rollup_traversal_parity_blocking( assert checked, f"no fixture method with non-zero {composed_key} rollup" -def _type_id_with_composed_key(kuzu_graph: KuzuGraph, rel: str, composed_key: str) -> tuple[str, int]: - rows = kuzu_graph._rows( # noqa: SLF001 +def _type_id_with_composed_key(ladybug_graph: LadybugGraph, rel: str, composed_key: str) -> tuple[str, int]: + rows = ladybug_graph._rows( # noqa: SLF001 f"MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[e:{rel}]->() " "WHERE t.kind IN $kinds " "RETURN t.id AS id, count(e) AS n ORDER BY n DESC LIMIT 1", @@ -853,9 +853,9 @@ def _type_id_with_composed_key(kuzu_graph: KuzuGraph, rel: str, composed_key: st return str(rows[0]["id"]), int(rows[0]["n"] or 0) -def test_neighbors_declares_dot_key_client(kuzu_graph: KuzuGraph) -> None: - tid, _ = _type_id_with_composed_key(kuzu_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") - out = neighbors_v2(tid, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"], graph=kuzu_graph, limit=500) +def test_neighbors_declares_dot_key_client(ladybug_graph: LadybugGraph) -> None: + tid, _ = _type_id_with_composed_key(ladybug_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") + out = neighbors_v2(tid, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"], graph=ladybug_graph, limit=500) assert out.success is True assert len(out.results) >= 1 assert all(e.edge_type == "DECLARES.DECLARES_CLIENT" for e in out.results) @@ -863,12 +863,12 @@ def test_neighbors_declares_dot_key_client(kuzu_graph: KuzuGraph) -> None: assert all(e.other.kind == "client" for e in out.results) -def test_neighbors_declares_dot_key_producer(kuzu_graph: KuzuGraph) -> None: +def test_neighbors_declares_dot_key_producer(ladybug_graph: LadybugGraph) -> None: tid, _ = _type_id_with_composed_key( - kuzu_graph, "DECLARES_PRODUCER", "DECLARES.DECLARES_PRODUCER" + ladybug_graph, "DECLARES_PRODUCER", "DECLARES.DECLARES_PRODUCER" ) out = neighbors_v2( - tid, direction="out", edge_types=["DECLARES.DECLARES_PRODUCER"], graph=kuzu_graph, limit=500 + tid, direction="out", edge_types=["DECLARES.DECLARES_PRODUCER"], graph=ladybug_graph, limit=500 ) assert out.success is True assert len(out.results) >= 1 @@ -877,15 +877,15 @@ def test_neighbors_declares_dot_key_producer(kuzu_graph: KuzuGraph) -> None: assert all(e.other.kind == "producer" for e in out.results) -def test_neighbors_declares_dot_key_exposes(kuzu_graph: KuzuGraph) -> None: - rows = kuzu_graph._rows( # noqa: SLF001 +def test_neighbors_declares_dot_key_exposes(ladybug_graph: LadybugGraph) -> None: + rows = ladybug_graph._rows( # noqa: SLF001 "MATCH (t:Symbol)-[:DECLARES]->(m:Symbol)-[e:EXPOSES]->(:Route) " "WHERE t.role = 'CONTROLLER' AND t.kind = 'class' " "RETURN t.id AS id, count(e) AS n ORDER BY n DESC LIMIT 1", ) assert rows tid = str(rows[0]["id"]) - out = neighbors_v2(tid, direction="out", edge_types=["DECLARES.EXPOSES"], graph=kuzu_graph, limit=500) + out = neighbors_v2(tid, direction="out", edge_types=["DECLARES.EXPOSES"], graph=ladybug_graph, limit=500) assert out.success is True assert len(out.results) >= 1 assert all(e.edge_type == "DECLARES.EXPOSES" for e in out.results) @@ -893,13 +893,13 @@ def test_neighbors_declares_dot_key_exposes(kuzu_graph: KuzuGraph) -> None: assert all(e.other.kind == "route" for e in out.results) -def test_neighbors_dot_key_mixed_with_flat(kuzu_graph: KuzuGraph) -> None: - tid, _ = _type_id_with_composed_key(kuzu_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") +def test_neighbors_dot_key_mixed_with_flat(ladybug_graph: LadybugGraph) -> None: + tid, _ = _type_id_with_composed_key(ladybug_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") out = neighbors_v2( tid, direction="out", edge_types=["DECLARES", "DECLARES.DECLARES_CLIENT"], - graph=kuzu_graph, + graph=ladybug_graph, limit=500, ) assert out.success is True @@ -910,29 +910,29 @@ def test_neighbors_dot_key_mixed_with_flat(kuzu_graph: KuzuGraph) -> None: assert any(e.other.kind == "client" for e in out.results) -def test_neighbors_dot_key_inbound_rejected(kuzu_graph: KuzuGraph) -> None: - tid, _ = _type_id_with_composed_key(kuzu_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") - out = neighbors_v2(tid, direction="in", edge_types=["DECLARES.DECLARES_CLIENT"], graph=kuzu_graph) +def test_neighbors_dot_key_inbound_rejected(ladybug_graph: LadybugGraph) -> None: + tid, _ = _type_id_with_composed_key(ladybug_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") + out = neighbors_v2(tid, direction="in", edge_types=["DECLARES.DECLARES_CLIENT"], graph=ladybug_graph) assert out.success is False assert out.message is not None assert 'direction="out"' in out.message -def test_neighbors_dot_key_method_origin_rejected(kuzu_graph: KuzuGraph) -> None: - node_id, _ = _controller_method_with_calls(kuzu_graph) +def test_neighbors_dot_key_method_origin_rejected(ladybug_graph: LadybugGraph) -> None: + node_id, _ = _controller_method_with_calls(ladybug_graph) out = neighbors_v2( - node_id, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"], graph=kuzu_graph + node_id, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"], graph=ladybug_graph ) assert out.success is False assert out.message is not None assert "type Symbol origin" in out.message -def test_neighbors_dot_key_count_matches_edge_summary(kuzu_graph: KuzuGraph) -> None: - tid, _ = _type_id_with_composed_key(kuzu_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") - d = describe_v2(tid, graph=kuzu_graph) +def test_neighbors_dot_key_count_matches_edge_summary(ladybug_graph: LadybugGraph) -> None: + tid, _ = _type_id_with_composed_key(ladybug_graph, "DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT") + d = describe_v2(tid, graph=ladybug_graph) n = neighbors_v2( - tid, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"], graph=kuzu_graph, limit=500 + tid, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"], graph=ladybug_graph, limit=500 ) assert d.success and d.record and d.record.edge_summary summary = d.record.edge_summary.get("DECLARES.DECLARES_CLIENT") @@ -943,26 +943,26 @@ def test_neighbors_dot_key_count_matches_edge_summary(kuzu_graph: KuzuGraph) -> def test_overrides_edge_set_deterministic_double_build(tmp_path: Path) -> None: def edge_pairs(db_path: Path) -> list[tuple[str, str]]: - g = KuzuGraph(str(db_path)) + g = LadybugGraph(str(db_path)) rows = g._rows( # noqa: SLF001 "MATCH (a:Symbol)-[e:OVERRIDES]->(b:Symbol) " "RETURN a.id AS src, b.id AS dst ORDER BY src, dst", ) return [(str(r["src"]), str(r["dst"])) for r in rows] - p1 = tmp_path / "g1.kuzu" - p2 = tmp_path / "g2.kuzu" - build_kuzu_to(_OVERRIDE_AXIS_FIXTURE, p1, max_pass=5) - build_kuzu_to(_OVERRIDE_AXIS_FIXTURE, p2, max_pass=5) + p1 = tmp_path / "g1.lbug" + p2 = tmp_path / "g2.lbug" + build_ladybug_to(_OVERRIDE_AXIS_FIXTURE, p1, max_pass=5) + build_ladybug_to(_OVERRIDE_AXIS_FIXTURE, p2, max_pass=5) assert edge_pairs(p1) == edge_pairs(p2) def test_describe_client_edge_summary_includes_http_calls_out( - kuzu_db_path_cross_service_smoke: Path, + ladybug_db_path_cross_service_smoke: Path, ) -> None: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph - g = KuzuGraph(str(kuzu_db_path_cross_service_smoke)) + g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) rows = g._rows( # noqa: SLF001 "MATCH (c:Client)-[:HTTP_CALLS]->() RETURN c.id AS id LIMIT 1", {}, diff --git a/tests/test_pr_analysis.py b/tests/test_pr_analysis.py index afdf3419..3f0e4440 100644 --- a/tests/test_pr_analysis.py +++ b/tests/test_pr_analysis.py @@ -4,7 +4,7 @@ from pathlib import Path from types import SimpleNamespace -from kuzu_queries import find_symbols_in_file_range +from ladybug_queries import find_symbols_in_file_range from pr_analysis import ( DiffHunk, analyze_pr_pipeline, @@ -52,7 +52,7 @@ def test_32_parse_unified_diff_multi_file() -> None: assert paths == {"a.java", "b.java"} -def test_33_map_hunks_to_symbols_chat_management_service_assign(kuzu_graph) -> None: +def test_33_map_hunks_to_symbols_chat_management_service_assign(ladybug_graph) -> None: diff = """diff --git a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java --- a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java +++ b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java @@ -64,25 +64,25 @@ def test_33_map_hunks_to_symbols_chat_management_service_assign(kuzu_graph) -> N throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "conversationId required"); } """ - changed = map_hunks_to_symbols(kuzu_graph, parse_unified_diff(diff)) + changed = map_hunks_to_symbols(ladybug_graph, parse_unified_diff(diff)) fqns = {c.fqn for c in changed} assert any(f.endswith("ChatManagementService#assign(AssignmentRequest)") for f in fqns), fqns assign = next(c for c in changed if c.fqn.endswith("assign(AssignmentRequest)")) assert assign.change_type == "modified" -def test_34_compute_risk_leaf_private_method_low_blast(kuzu_graph) -> None: +def test_34_compute_risk_leaf_private_method_low_blast(ladybug_graph) -> None: from pr_analysis import ChangedSymbol sym = find_symbols_in_file_range( - kuzu_graph, + ladybug_graph, filename="chat-assign/src/main/java/com/bank/chat/assign/service/DistributionChunkService.java", start_line=89, end_line=95, ) pick = next(s for s in sym if s.fqn.endswith("pickEligibleOperator(UUID)")) rep = compute_risk( - kuzu_graph, + ladybug_graph, [ ChangedSymbol( symbol_id=pick.id, @@ -99,7 +99,7 @@ def test_34_compute_risk_leaf_private_method_low_blast(kuzu_graph) -> None: def test_35_compute_risk_controller_route_and_high_band_when_saturated( - monkeypatch, kuzu_graph, + monkeypatch, ladybug_graph, ) -> None: """Fixture corpus: controller diff surfaces EXPOSES routes; `high` needs saturated metrics.""" from pr_analysis import ChangedSymbol @@ -115,11 +115,11 @@ def test_35_compute_risk_controller_route_and_high_band_when_saturated( return ResponseEntity.accepted().build(); } """ - rep0 = analyze_pr_pipeline(kuzu_graph, diff) + rep0 = analyze_pr_pipeline(ladybug_graph, diff) assert rep0.routes_touched, rep0 assert any("assign(AssignmentRequest)" in s.fqn for s in rep0.changed_symbols), rep0 - hit = kuzu_graph.impact_analysis("AssignChatRepository", depth=2, limit=10)[0] + hit = ladybug_graph.impact_analysis("AssignChatRepository", depth=2, limit=10)[0] def fake_ia(self, name, **kwargs): del name, kwargs @@ -137,13 +137,13 @@ def fake_fc(self, name, **kwargs): ) return out - monkeypatch.setattr(type(kuzu_graph), "impact_analysis", fake_ia) - monkeypatch.setattr(type(kuzu_graph), "find_callers", fake_fc) + monkeypatch.setattr(type(ladybug_graph), "impact_analysis", fake_ia) + monkeypatch.setattr(type(ladybug_graph), "find_callers", fake_fc) sym = next( s for s in find_symbols_in_file_range( - kuzu_graph, + ladybug_graph, filename="chat-assign/src/main/java/com/bank/chat/assign/web/ChatManagementController.java", start_line=25, end_line=29, @@ -151,7 +151,7 @@ def fake_fc(self, name, **kwargs): if s.fqn.endswith("assign(AssignmentRequest)") ) rep = compute_risk( - kuzu_graph, + ladybug_graph, [ ChangedSymbol( symbol_id=sym.id, @@ -304,13 +304,13 @@ def find_callers(self, name, **kwargs): def test_pr_analysis_changed_methods_finds_routes_via_declares_client( - kuzu_db_path_cross_service_smoke: Path, + ladybug_db_path_cross_service_smoke: Path, ) -> None: - from kuzu_queries import KuzuGraph + from ladybug_queries import LadybugGraph from pr_analysis import ChangedSymbol, compute_risk - g = KuzuGraph(str(kuzu_db_path_cross_service_smoke)) + g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) route_rows = g._rows( # noqa: SLF001 "MATCH (r:Route) " "WHERE r.microservice = 'svc-b' AND r.path_template = '/chat/joinOperator' " @@ -341,7 +341,7 @@ def test_pr_analysis_changed_methods_finds_routes_via_declares_client( assert rep.changed_symbols[0].cross_service_callers_count >= 1 -def test_36_removed_symbol_from_minus_only_hunk(kuzu_graph) -> None: +def test_36_removed_symbol_from_minus_only_hunk(ladybug_graph) -> None: diff = """diff --git a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java --- a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java +++ b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java @@ -352,14 +352,14 @@ def test_36_removed_symbol_from_minus_only_hunk(kuzu_graph) -> None: - .ifPresent(assignChatRepository::delete); - } """ - changed = map_hunks_to_symbols(kuzu_graph, parse_unified_diff(diff)) + changed = map_hunks_to_symbols(ladybug_graph, parse_unified_diff(diff)) fqns = {c.fqn for c in changed} assert any(f.endswith("closeChat(String)") for f in fqns), fqns close = next(c for c in changed if c.fqn.endswith("closeChat(String)")) assert close.change_type == "removed" -def test_37_added_method_surfaces_not_yet_indexed_note(kuzu_graph) -> None: +def test_37_added_method_surfaces_not_yet_indexed_note(ladybug_graph) -> None: diff = """diff --git a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java --- a/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java +++ b/chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java @@ -376,14 +376,14 @@ def test_37_added_method_surfaces_not_yet_indexed_note(kuzu_graph) -> None: + } } """ - rep = analyze_pr_pipeline(kuzu_graph, diff) + rep = analyze_pr_pipeline(ladybug_graph, diff) joined = " ".join(rep.notes).lower() assert "not yet indexed" in joined, rep.notes -def test_find_symbols_in_file_range_query(kuzu_graph) -> None: +def test_find_symbols_in_file_range_query(ladybug_graph) -> None: rows = find_symbols_in_file_range( - kuzu_graph, + ladybug_graph, filename="chat-assign/src/main/java/com/bank/chat/assign/service/ChatManagementService.java", start_line=47, end_line=50, @@ -391,7 +391,7 @@ def test_find_symbols_in_file_range_query(kuzu_graph) -> None: assert any("assign(AssignmentRequest)" in r.fqn for r in rows) -def test_binary_and_rename_diffs_do_not_crash(kuzu_graph) -> None: +def test_binary_and_rename_diffs_do_not_crash(ladybug_graph) -> None: diff = """diff --git a/x.bin b/x.bin index 111..222 100644 Binary files a/x.bin and b/x.bin differ @@ -408,7 +408,7 @@ def test_binary_and_rename_diffs_do_not_crash(kuzu_graph) -> None: +c d """ - rep = analyze_pr_pipeline(kuzu_graph, diff) + rep = analyze_pr_pipeline(ladybug_graph, diff) assert rep.risk_band in ("low", "medium", "high") notes = " ".join(rep.notes).lower() assert "binary" in notes or "skipped binary" in notes