From 6f9850e8c2ff14245401fff10f3eb9a0a581afa8 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Fri, 10 Jul 2026 01:08:28 +0300 Subject: [PATCH 1/2] refactor: move to src/ layout, fold loose modules into layer subpackages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of a two-phase reorganization. The repo was a hybrid of one package (java_codebase_rag/) and 23 loose top-level modules; everything now lives under src/java_codebase_rag/ with the engine modules grouped into 7 layer subpackages. Layout: src/java_codebase_rag/{ast,graph,absence,search,index,mcp,analysis}/ App-shell modules (cli, jrag, config, pipeline, installer, ...) stay flat at the package root, so their java_codebase_rag.X self-imports are unchanged. Imports: every `import ` / `from import` (including all deferred, cycle-breaking ones) rewritten to absolute `java_codebase_rag..` across 87 files. cocoindex flow + builder (loaded by FILE PATH, not import): resolve paths via __file__ arithmetic in pipeline.py and mcp/server.py (_FLOW_DIR/_FLOW_FILE/ _BUILDER_FILE) — kept import-free since pipeline.py must stay free of heavy ML imports at load time. The flow/builder's own imports are now absolute package paths, so they load under the editable install regardless of cwd. Kept the relative `file:Class` cocoindex target with cwd = flow dir. Config: pyproject -> [tool.setuptools.packages.find] where=["src"]; entry point server:main -> java_codebase_rag.mcp.server:main. conftest editable-install check + sys.path -> src/. pytest.ini +pythonpath = src. Other rewrites: string-form monkeypatch/mock.patch targets (8 test files), test_search_lexical regex + exec'd subprocess source, ~12 .py path-string literals in tests, scripts/generate_edge_navigation.py (sys.path -> src/), scripts/sync_agent_artifacts.py (install_data -> src/...). Phase 2 (tests/ subfolder reorg to mirror src/) follows in a separate commit. Verification: imports clean; jrag/java-codebase-rag/java-codebase-rag-mcp all work; generate_edge_navigation.py --check passes. Full suite (CI-equivalent, no heavy gate): 1293 passed, 14 skipped, 0 failed. Co-Authored-By: Claude --- pyproject.toml | 31 +------ pytest.ini | 1 + scripts/generate_edge_navigation.py | 7 +- scripts/sync_agent_artifacts.py | 6 +- .../java_codebase_rag}/__init__.py | 0 .../java_codebase_rag}/_fdlimit.py | 0 .../java_codebase_rag}/_stdio.py | 0 .../java_codebase_rag}/_version.py | 0 .../java_codebase_rag/absence}/__init__.py | 0 .../absence/absence_diagnosis.py | 8 +- .../absence/absence_types.py | 2 +- .../absence/absence_vocab.py | 8 +- src/java_codebase_rag/analysis/__init__.py | 0 .../java_codebase_rag/analysis/pr_analysis.py | 2 +- .../analysis/resolve_service.py | 14 +-- src/java_codebase_rag/ast/__init__.py | 0 .../java_codebase_rag/ast/ast_java.py | 8 +- .../ast/brownfield_events.py | 0 .../java_codebase_rag/ast/chunk_heuristics.py | 0 .../java_codebase_rag}/cli.py | 24 ++--- .../java_codebase_rag}/cli_format.py | 0 .../java_codebase_rag}/cli_progress.py | 0 .../java_codebase_rag}/config.py | 0 src/java_codebase_rag/graph/__init__.py | 0 .../graph/build_ast_graph.py | 12 +-- .../java_codebase_rag/graph/graph_enrich.py | 6 +- .../java_codebase_rag/graph/graph_types.py | 4 +- .../java_codebase_rag/graph/java_ontology.py | 2 +- .../graph/ladybug_queries.py | 2 +- .../java_codebase_rag/graph/path_filtering.py | 0 src/java_codebase_rag/index/__init__.py | 0 .../index/java_index_flow_lancedb.py | 8 +- .../index/java_index_v1_common.py | 0 .../install_data/__init__.py | 0 .../install_data/agents/explorer-rag-cli.md | 0 .../agents/explorer-rag-enhanced.md | 0 .../skills/explore-codebase-cli/SKILL.md | 0 .../skills/explore-codebase/SKILL.md | 0 .../java_codebase_rag}/installer.py | 0 .../java_codebase_rag}/jrag.py | 36 ++++---- .../java_codebase_rag}/jrag_envelope.py | 8 +- .../java_codebase_rag}/jrag_hints.py | 2 +- .../java_codebase_rag}/jrag_render.py | 2 +- .../java_codebase_rag}/lance_optimize.py | 0 src/java_codebase_rag/mcp/__init__.py | 0 .../java_codebase_rag/mcp/mcp_hints.py | 2 +- .../java_codebase_rag/mcp/mcp_v2.py | 22 ++--- .../java_codebase_rag/mcp/server.py | 42 +++++---- .../java_codebase_rag}/pipeline.py | 21 +++-- .../java_codebase_rag}/progress.py | 0 src/java_codebase_rag/search/__init__.py | 0 .../java_codebase_rag/search/index_common.py | 0 .../search/search_lancedb.py | 8 +- .../search/search_lexical.py | 6 +- .../search/search_scoring.py | 0 tests/_builders.py | 2 +- tests/conftest.py | 20 ++-- tests/pinned_ids.py | 2 +- tests/test_absence_diagnosis.py | 8 +- tests/test_absence_mcp_integration.py | 14 +-- tests/test_absence_types.py | 6 +- tests/test_absence_vocab.py | 22 ++--- tests/test_agent_skills_static.py | 4 +- .../test_assign_endpoint_client_extraction.py | 4 +- tests/test_ast_graph_build.py | 16 ++-- tests/test_ast_java_calls.py | 2 +- tests/test_ast_java_capabilities.py | 4 +- tests/test_ast_java_thread_safety.py | 2 +- .../test_bank_chat_brownfield_integration.py | 2 +- tests/test_brownfield_clients.py | 4 +- tests/test_brownfield_events.py | 2 +- tests/test_brownfield_overrides.py | 18 ++-- tests/test_brownfield_routes.py | 4 +- tests/test_call_edge_matching.py | 6 +- tests/test_call_edges_e2e.py | 4 +- tests/test_call_graph_smoke_roundtrip.py | 2 +- tests/test_call_invariant.py | 2 +- tests/test_cli_progress_stdout_invariant.py | 6 +- tests/test_cli_quiet_parity.py | 2 +- tests/test_client_hint_recovery.py | 6 +- tests/test_client_node_extraction.py | 4 +- tests/test_client_role_rename.py | 6 +- tests/test_cross_service_resolution_flag.py | 8 +- tests/test_feign_not_exposer.py | 4 +- tests/test_generated_detection.py | 4 +- tests/test_generated_filter.py | 6 +- tests/test_generated_surface.py | 12 +-- tests/test_graph_enrich.py | 2 +- tests/test_graph_generated_node.py | 8 +- tests/test_graph_only_boot.py | 6 +- tests/test_incremental_graph.py | 92 +++++++++---------- tests/test_install_data_sync.py | 20 ++-- tests/test_java_codebase_rag_cli.py | 6 +- tests/test_jrag_auto_scope.py | 2 +- tests/test_jrag_enum_choices.py | 4 +- tests/test_jrag_envelope.py | 18 ++-- tests/test_jrag_envelope_absence.py | 14 +-- tests/test_jrag_orientation.py | 26 +++--- tests/test_jrag_render.py | 2 +- tests/test_ladybug_queries.py | 8 +- tests/test_lance_optimize.py | 2 +- tests/test_lancedb_e2e.py | 24 ++--- tests/test_lancedb_generated_column.py | 2 +- tests/test_mcp_hints.py | 10 +- tests/test_mcp_server_project_root.py | 10 +- tests/test_mcp_tools.py | 4 +- tests/test_mcp_v2.py | 52 +++++------ tests/test_mcp_v2_compose.py | 12 +-- tests/test_meta_chain_core.py | 2 +- tests/test_microservice_scope.py | 38 ++++---- tests/test_outgoing_call_extraction.py | 2 +- tests/test_path_filtering.py | 2 +- tests/test_pr_analysis.py | 20 ++-- .../test_resolve_routes_messaging_layer_c.py | 4 +- tests/test_resolve_service.py | 4 +- tests/test_route_extraction.py | 4 +- tests/test_schema_consistency.py | 10 +- tests/test_search_lancedb.py | 18 ++-- tests/test_search_lancedb_capability.py | 6 +- tests/test_search_lexical.py | 22 ++--- tests/test_string_value_atoms.py | 2 +- tests/test_vectors_progress.py | 10 +- 122 files changed, 479 insertions(+), 491 deletions(-) rename {java_codebase_rag => src/java_codebase_rag}/__init__.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/_fdlimit.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/_stdio.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/_version.py (100%) rename {java_codebase_rag/install_data => src/java_codebase_rag/absence}/__init__.py (100%) rename absence_diagnosis.py => src/java_codebase_rag/absence/absence_diagnosis.py (98%) rename absence_types.py => src/java_codebase_rag/absence/absence_types.py (98%) rename absence_vocab.py => src/java_codebase_rag/absence/absence_vocab.py (98%) create mode 100644 src/java_codebase_rag/analysis/__init__.py rename pr_analysis.py => src/java_codebase_rag/analysis/pr_analysis.py (99%) rename resolve_service.py => src/java_codebase_rag/analysis/resolve_service.py (98%) create mode 100644 src/java_codebase_rag/ast/__init__.py rename ast_java.py => src/java_codebase_rag/ast/ast_java.py (99%) rename brownfield_events.py => src/java_codebase_rag/ast/brownfield_events.py (100%) rename chunk_heuristics.py => src/java_codebase_rag/ast/chunk_heuristics.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/cli.py (98%) rename {java_codebase_rag => src/java_codebase_rag}/cli_format.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/cli_progress.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/config.py (100%) create mode 100644 src/java_codebase_rag/graph/__init__.py rename build_ast_graph.py => src/java_codebase_rag/graph/build_ast_graph.py (99%) rename graph_enrich.py => src/java_codebase_rag/graph/graph_enrich.py (99%) rename graph_types.py => src/java_codebase_rag/graph/graph_types.py (97%) rename java_ontology.py => src/java_codebase_rag/graph/java_ontology.py (99%) rename ladybug_queries.py => src/java_codebase_rag/graph/ladybug_queries.py (99%) rename path_filtering.py => src/java_codebase_rag/graph/path_filtering.py (100%) create mode 100644 src/java_codebase_rag/index/__init__.py rename java_index_flow_lancedb.py => src/java_codebase_rag/index/java_index_flow_lancedb.py (99%) rename java_index_v1_common.py => src/java_codebase_rag/index/java_index_v1_common.py (100%) create mode 100644 src/java_codebase_rag/install_data/__init__.py rename {java_codebase_rag => src/java_codebase_rag}/install_data/agents/explorer-rag-cli.md (100%) rename {java_codebase_rag => src/java_codebase_rag}/install_data/agents/explorer-rag-enhanced.md (100%) rename {java_codebase_rag => src/java_codebase_rag}/install_data/skills/explore-codebase-cli/SKILL.md (100%) rename {java_codebase_rag => src/java_codebase_rag}/install_data/skills/explore-codebase/SKILL.md (100%) rename {java_codebase_rag => src/java_codebase_rag}/installer.py (100%) rename {java_codebase_rag => src/java_codebase_rag}/jrag.py (99%) rename {java_codebase_rag => src/java_codebase_rag}/jrag_envelope.py (99%) rename {java_codebase_rag => src/java_codebase_rag}/jrag_hints.py (99%) rename {java_codebase_rag => src/java_codebase_rag}/jrag_render.py (99%) rename {java_codebase_rag => src/java_codebase_rag}/lance_optimize.py (100%) create mode 100644 src/java_codebase_rag/mcp/__init__.py rename mcp_hints.py => src/java_codebase_rag/mcp/mcp_hints.py (99%) rename mcp_v2.py => src/java_codebase_rag/mcp/mcp_v2.py (98%) rename server.py => src/java_codebase_rag/mcp/server.py (97%) rename {java_codebase_rag => src/java_codebase_rag}/pipeline.py (95%) rename {java_codebase_rag => src/java_codebase_rag}/progress.py (100%) create mode 100644 src/java_codebase_rag/search/__init__.py rename index_common.py => src/java_codebase_rag/search/index_common.py (100%) rename search_lancedb.py => src/java_codebase_rag/search/search_lancedb.py (99%) rename search_lexical.py => src/java_codebase_rag/search/search_lexical.py (98%) rename search_scoring.py => src/java_codebase_rag/search/search_scoring.py (100%) diff --git a/pyproject.toml b/pyproject.toml index a23aeb62..fb5bfbc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,36 +64,11 @@ Issues = "https://github.com/HumanBean17/java-codebase-rag/issues" [project.scripts] java-codebase-rag = "java_codebase_rag.cli:_console_script_main" -java-codebase-rag-mcp = "server:main" +java-codebase-rag-mcp = "java_codebase_rag.mcp.server:main" jrag = "java_codebase_rag.jrag:_console_script_main" -[tool.setuptools] -packages = ["java_codebase_rag"] -py-modules = [ - "absence_types", - "absence_vocab", - "absence_diagnosis", - "ast_java", - "brownfield_events", - "build_ast_graph", - "chunk_heuristics", - "graph_enrich", - "graph_types", - "index_common", - "java_index_flow_lancedb", - "java_index_v1_common", - "java_ontology", - "ladybug_queries", - "mcp_hints", - "mcp_v2", - "path_filtering", - "pr_analysis", - "resolve_service", - "search_lancedb", - "search_lexical", - "search_scoring", - "server", -] +[tool.setuptools.packages.find] +where = ["src"] [tool.ruff] line-length = 100 diff --git a/pytest.ini b/pytest.ini index 78c5011f..e75524a8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] asyncio_mode = auto testpaths = tests +pythonpath = src diff --git a/scripts/generate_edge_navigation.py b/scripts/generate_edge_navigation.py index 048191d7..17200dec 100644 --- a/scripts/generate_edge_navigation.py +++ b/scripts/generate_edge_navigation.py @@ -7,10 +7,11 @@ from pathlib import Path _REPO_ROOT = Path(__file__).resolve().parent.parent -if str(_REPO_ROOT) not in sys.path: - sys.path.insert(0, str(_REPO_ROOT)) +_SRC = _REPO_ROOT / "src" +if str(_SRC) not in sys.path: + sys.path.insert(0, str(_SRC)) -from java_ontology import ( # noqa: E402 +from java_codebase_rag.graph.java_ontology import ( # noqa: E402 EDGE_SCHEMA, EdgeSpec, _COMPOSED_MEMBER_TYPE_TRAVERSAL, diff --git a/scripts/sync_agent_artifacts.py b/scripts/sync_agent_artifacts.py index 4df3ac85..e48c31f0 100644 --- a/scripts/sync_agent_artifacts.py +++ b/scripts/sync_agent_artifacts.py @@ -27,9 +27,9 @@ # Mapping of source (dev) paths to destination (install_data) paths # Only these subtrees are shipped - skills/README.md is explicitly excluded SYNC_MAP: list[tuple[Path, Path]] = [ - (Path("skills/explore-codebase"), Path("java_codebase_rag/install_data/skills/explore-codebase")), - (Path("skills/explore-codebase-cli"), Path("java_codebase_rag/install_data/skills/explore-codebase-cli")), - (Path("agents"), Path("java_codebase_rag/install_data/agents")), + (Path("skills/explore-codebase"), Path("src/java_codebase_rag/install_data/skills/explore-codebase")), + (Path("skills/explore-codebase-cli"), Path("src/java_codebase_rag/install_data/skills/explore-codebase-cli")), + (Path("agents"), Path("src/java_codebase_rag/install_data/agents")), ] diff --git a/java_codebase_rag/__init__.py b/src/java_codebase_rag/__init__.py similarity index 100% rename from java_codebase_rag/__init__.py rename to src/java_codebase_rag/__init__.py diff --git a/java_codebase_rag/_fdlimit.py b/src/java_codebase_rag/_fdlimit.py similarity index 100% rename from java_codebase_rag/_fdlimit.py rename to src/java_codebase_rag/_fdlimit.py diff --git a/java_codebase_rag/_stdio.py b/src/java_codebase_rag/_stdio.py similarity index 100% rename from java_codebase_rag/_stdio.py rename to src/java_codebase_rag/_stdio.py diff --git a/java_codebase_rag/_version.py b/src/java_codebase_rag/_version.py similarity index 100% rename from java_codebase_rag/_version.py rename to src/java_codebase_rag/_version.py diff --git a/java_codebase_rag/install_data/__init__.py b/src/java_codebase_rag/absence/__init__.py similarity index 100% rename from java_codebase_rag/install_data/__init__.py rename to src/java_codebase_rag/absence/__init__.py diff --git a/absence_diagnosis.py b/src/java_codebase_rag/absence/absence_diagnosis.py similarity index 98% rename from absence_diagnosis.py rename to src/java_codebase_rag/absence/absence_diagnosis.py index 9d42f81f..5e060d9c 100644 --- a/absence_diagnosis.py +++ b/src/java_codebase_rag/absence/absence_diagnosis.py @@ -30,7 +30,7 @@ from difflib import SequenceMatcher from typing import Any, Literal -from absence_types import ( +from java_codebase_rag.absence.absence_types import ( AbsenceDiagnosis, AbsenceProof, ExternalIdentity, @@ -38,9 +38,9 @@ FilterRelaxationDim, VocabularyContext, ) -from absence_vocab import SymbolRecord, VocabularyIndex, _normalize_name -from graph_types import NodeRef -from mcp_hints import _IDENTIFIER_FILTER_FIELDS +from java_codebase_rag.absence.absence_vocab import SymbolRecord, VocabularyIndex, _normalize_name +from java_codebase_rag.graph.graph_types import NodeRef +from java_codebase_rag.mcp.mcp_hints import _IDENTIFIER_FILTER_FIELDS log = logging.getLogger(__name__) diff --git a/absence_types.py b/src/java_codebase_rag/absence/absence_types.py similarity index 98% rename from absence_types.py rename to src/java_codebase_rag/absence/absence_types.py index 98fec95f..7ae0813f 100644 --- a/absence_types.py +++ b/src/java_codebase_rag/absence/absence_types.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field -from graph_types import NodeRef +from java_codebase_rag.graph.graph_types import NodeRef __all__ = [ "AbsenceVerdict", diff --git a/absence_vocab.py b/src/java_codebase_rag/absence/absence_vocab.py similarity index 98% rename from absence_vocab.py rename to src/java_codebase_rag/absence/absence_vocab.py index 3fc1017c..6d041e08 100644 --- a/absence_vocab.py +++ b/src/java_codebase_rag/absence/absence_vocab.py @@ -204,7 +204,7 @@ def load(cls, path: Path) -> "VocabularyIndex": VocabIndexStale: If sidecar format_version or ontology_version doesn't match expected """ - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION with open(path, encoding="utf-8") as f: data = json.load(f) @@ -304,7 +304,7 @@ def is_external(self, name: str) -> tuple[bool, str | None]: Returns: (is_external, reason) tuple """ - from ladybug_queries import _is_external_fqn, _EXTERNAL_PREFIXES + from java_codebase_rag.graph.ladybug_queries import _is_external_fqn, _EXTERNAL_PREFIXES # First, check if it's an external prefix (highest priority) if _is_external_fqn(name): @@ -354,7 +354,7 @@ def get_vocabulary_index(graph: Any, cfg: Any) -> VocabularyIndex: Returns: VocabularyIndex (cached or newly built) """ - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION # Determine graph db path for cache key db_path = graph.db_path if hasattr(graph, 'db_path') else str(cfg.ladybug_path) @@ -403,7 +403,7 @@ def reset_cache() -> None: def _row_to_symbol_record(row: dict[str, Any]) -> SymbolRecord: """Convert a Ladybug row to a SymbolRecord.""" - from ladybug_queries import _type_part_fqn + from java_codebase_rag.graph.ladybug_queries import _type_part_fqn fqn = row.get("fqn") or "" name = row.get("name") or "" diff --git a/src/java_codebase_rag/analysis/__init__.py b/src/java_codebase_rag/analysis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pr_analysis.py b/src/java_codebase_rag/analysis/pr_analysis.py similarity index 99% rename from pr_analysis.py rename to src/java_codebase_rag/analysis/pr_analysis.py index e0b49c4c..34ff7780 100644 --- a/pr_analysis.py +++ b/src/java_codebase_rag/analysis/pr_analysis.py @@ -12,7 +12,7 @@ from unidiff import PatchSet from unidiff.errors import UnidiffParseError -from ladybug_queries import SymbolHit, find_symbols_in_file_range, _row_to_symbol +from java_codebase_rag.graph.ladybug_queries import SymbolHit, find_symbols_in_file_range, _row_to_symbol @dataclass diff --git a/resolve_service.py b/src/java_codebase_rag/analysis/resolve_service.py similarity index 98% rename from resolve_service.py rename to src/java_codebase_rag/analysis/resolve_service.py index 332e4663..723b6f77 100644 --- a/resolve_service.py +++ b/src/java_codebase_rag/analysis/resolve_service.py @@ -10,10 +10,10 @@ from pydantic import BaseModel, ConfigDict, Field -from absence_types import AbsenceDiagnosis -from absence_diagnosis import diagnose -from absence_vocab import get_vocabulary_index -from graph_types import ( +from java_codebase_rag.absence.absence_types import AbsenceDiagnosis +from java_codebase_rag.absence.absence_diagnosis import diagnose +from java_codebase_rag.absence.absence_vocab import get_vocabulary_index +from java_codebase_rag.graph.graph_types import ( NodeRef, StructuredHint, _hints_or_skip, @@ -21,9 +21,9 @@ _to_structured_hints, set_hints_enabled, ) -from java_ontology import ResolveReason -from ladybug_queries import LadybugGraph -from mcp_hints import MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION +from java_codebase_rag.graph.java_ontology import ResolveReason +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.mcp.mcp_hints import MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION __all__ = [ "resolve_v2", diff --git a/src/java_codebase_rag/ast/__init__.py b/src/java_codebase_rag/ast/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ast_java.py b/src/java_codebase_rag/ast/ast_java.py similarity index 99% rename from ast_java.py rename to src/java_codebase_rag/ast/ast_java.py index 95fb96d8..c10e8e7a 100644 --- a/ast_java.py +++ b/src/java_codebase_rag/ast/ast_java.py @@ -20,7 +20,7 @@ import tree_sitter_java as _ts_java -from brownfield_events import ( +from java_codebase_rag.ast.brownfield_events import ( emit_brownfield_exclusivity_shadowing, emit_brownfield_method_string_literal, ) @@ -1617,7 +1617,7 @@ def _parse_codebase_http_client_annotation( val, vkind = _annotation_value(pairs["clientKind"], src) if val and vkind == "enum": kind_val = str(val) - from java_ontology import VALID_CLIENT_KINDS # deferred: java_ontology imports ast_java + from java_codebase_rag.graph.java_ontology import VALID_CLIENT_KINDS # deferred: java_ontology imports ast_java if kind_val in VALID_CLIENT_KINDS: client_kind = kind_val else: @@ -1697,7 +1697,7 @@ def _parse_codebase_producer_annotation( val, vkind = _annotation_value(kind_node, src) if val and vkind == "enum": kind_val = str(val) - from java_ontology import VALID_PRODUCER_KINDS # deferred: java_ontology imports ast_java + from java_codebase_rag.graph.java_ontology import VALID_PRODUCER_KINDS # deferred: java_ontology imports ast_java if kind_val in VALID_PRODUCER_KINDS: client_kind = kind_val else: @@ -1868,7 +1868,7 @@ def _collect_outgoing_calls( file_rel: str, ) -> list[OutgoingCallDecl]: del project_root - from java_ontology import ( # deferred: java_ontology imports ast_java + from java_codebase_rag.graph.java_ontology import ( # deferred: java_ontology imports ast_java CLIENT_KIND_FEIGN_METHOD, CLIENT_KIND_REST_TEMPLATE, CLIENT_KIND_WEB_CLIENT, diff --git a/brownfield_events.py b/src/java_codebase_rag/ast/brownfield_events.py similarity index 100% rename from brownfield_events.py rename to src/java_codebase_rag/ast/brownfield_events.py diff --git a/chunk_heuristics.py b/src/java_codebase_rag/ast/chunk_heuristics.py similarity index 100% rename from chunk_heuristics.py rename to src/java_codebase_rag/ast/chunk_heuristics.py diff --git a/java_codebase_rag/cli.py b/src/java_codebase_rag/cli.py similarity index 98% rename from java_codebase_rag/cli.py rename to src/java_codebase_rag/cli.py index 93cf1855..54a40233 100644 --- a/java_codebase_rag/cli.py +++ b/src/java_codebase_rag/cli.py @@ -36,7 +36,7 @@ run_cocoindex_update, run_incremental_graph, ) -from java_ontology import VALID_UNRESOLVED_CALL_REASONS +from java_codebase_rag.graph.java_ontology import VALID_UNRESOLVED_CALL_REASONS LADYBUG_INCREMENTAL_TRACKING_ISSUE_URL = "https://github.com/HumanBean17/java-codebase-rag/issues/73" @@ -602,7 +602,7 @@ def work(progress: "PipelineProgress | None") -> int: _emit_reprocess_outcome(payload, selective_tty_mode="graph" if ok else None) return _reprocess_exit_code(payload) - import server # lazy: pulls sentence_transformers/torch/lancedb/ladybug + from java_codebase_rag.mcp import server # lazy: pulls sentence_transformers/torch/lancedb/ladybug result = asyncio.run( server.run_refresh_pipeline( @@ -677,7 +677,7 @@ def _cmd_erase(args: argparse.Namespace) -> int: # tree_sitter (~54ms), and these filenames are only needed on the erase path. # Keeping it out of the top-level import lets `java-codebase-rag --help` (and # every other command) stay fast -- see the lazy-import invariant atop this file. - from build_ast_graph import BUILDER_OWNED_INDEX_FILES + from java_codebase_rag.graph.build_ast_graph import BUILDER_OWNED_INDEX_FILES builder_paths = [cfg.ladybug_path.parent / name for name in BUILDER_OWNED_INDEX_FILES] operator_paths = [cfg.index_dir / name for name in OPERATOR_OWNED_INDEX_FILES] to_describe: list[Path] = [ @@ -768,12 +768,12 @@ def work(progress: "PipelineProgress | None") -> int: def _cmd_meta(args: argparse.Namespace) -> int: - import server # lazy + from java_codebase_rag.mcp import server # lazy cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - from ladybug_queries import LadybugGraph # lazy + from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy LadybugGraph._instance = None LadybugGraph._instance_path = None @@ -792,7 +792,7 @@ def _cmd_meta(args: argparse.Namespace) -> int: def _cmd_tables(args: argparse.Namespace) -> int: - import server # lazy + from java_codebase_rag.mcp import server # lazy cfg = _resolved_from_ns(args) _startup_hints(cfg) @@ -803,8 +803,8 @@ def _cmd_tables(args: argparse.Namespace) -> int: def _cmd_diagnose_ignore(args: argparse.Namespace) -> int: - import server # lazy - from path_filtering import LayeredIgnore # lazy + from java_codebase_rag.mcp import server # lazy + from java_codebase_rag.graph.path_filtering import LayeredIgnore # lazy cfg = _resolved_from_ns(args) _startup_hints(cfg) @@ -833,7 +833,7 @@ def _cmd_unresolved_calls_list(args: argparse.Namespace) -> int: cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - from ladybug_queries import LadybugGraph # lazy + from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy if not LadybugGraph.exists(): _emit({"success": False, "message": "LadybugDB graph not found"}) @@ -854,7 +854,7 @@ def _cmd_unresolved_calls_stats(args: argparse.Namespace) -> int: cfg = _resolved_from_ns(args) _startup_hints(cfg) cfg.apply_to_os_environ() - from ladybug_queries import LadybugGraph # lazy + from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy if not LadybugGraph.exists(): _emit({"success": False, "message": "LadybugDB graph not found"}) @@ -878,8 +878,8 @@ def _cmd_analyze_pr(args: argparse.Namespace) -> int: if not diff_text.strip(): _emit({"success": False, "message": "Diff is empty"}) return 1 - import pr_analysis # lazy - from ladybug_queries import LadybugGraph # lazy + from java_codebase_rag.analysis import pr_analysis # lazy + from java_codebase_rag.graph.ladybug_queries import LadybugGraph # lazy if not LadybugGraph.exists(): _emit({"success": False, "message": "LadybugDB graph not found"}) diff --git a/java_codebase_rag/cli_format.py b/src/java_codebase_rag/cli_format.py similarity index 100% rename from java_codebase_rag/cli_format.py rename to src/java_codebase_rag/cli_format.py diff --git a/java_codebase_rag/cli_progress.py b/src/java_codebase_rag/cli_progress.py similarity index 100% rename from java_codebase_rag/cli_progress.py rename to src/java_codebase_rag/cli_progress.py diff --git a/java_codebase_rag/config.py b/src/java_codebase_rag/config.py similarity index 100% rename from java_codebase_rag/config.py rename to src/java_codebase_rag/config.py diff --git a/src/java_codebase_rag/graph/__init__.py b/src/java_codebase_rag/graph/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build_ast_graph.py b/src/java_codebase_rag/graph/build_ast_graph.py similarity index 99% rename from build_ast_graph.py rename to src/java_codebase_rag/graph/build_ast_graph.py index 066fa8bf..d4a041b2 100644 --- a/build_ast_graph.py +++ b/src/java_codebase_rag/graph/build_ast_graph.py @@ -41,7 +41,7 @@ import ladybug import pyarrow as pa -from ast_java import ( +from java_codebase_rag.ast.ast_java import ( ONTOLOGY_VERSION, CallSite, JavaFileAst, @@ -52,7 +52,7 @@ lombok_required_args_annotations, parse_java, ) -from graph_enrich import ( +from java_codebase_rag.graph.graph_enrich import ( _load_config_cross_service_resolution, classify_java_file, collect_annotation_meta_chain, @@ -67,8 +67,8 @@ resolve_routes_for_method, symbol_id, ) -from path_filtering import LayeredIgnore, iter_java_source_files -from java_ontology import ( +from java_codebase_rag.graph.path_filtering import LayeredIgnore, iter_java_source_files +from java_codebase_rag.graph.java_ontology import ( CLIENT_KIND_FEIGN_METHOD, CLIENT_KIND_REST_TEMPLATE, VALID_CLIENT_KINDS, @@ -4256,8 +4256,8 @@ def _try_build_vocabulary_index(db_path: Path, source_root: Path, verbose: bool) verbose: Whether to emit verbose progress """ try: - from absence_vocab import VocabularyIndex, VOCAB_INDEX_FILENAME - from ladybug_queries import LadybugGraph + from java_codebase_rag.absence.absence_vocab import VocabularyIndex, VOCAB_INDEX_FILENAME + from java_codebase_rag.graph.ladybug_queries import LadybugGraph t0 = time.time() if verbose: diff --git a/graph_enrich.py b/src/java_codebase_rag/graph/graph_enrich.py similarity index 99% rename from graph_enrich.py rename to src/java_codebase_rag/graph/graph_enrich.py index a7a1449c..38c5ec0f 100644 --- a/graph_enrich.py +++ b/src/java_codebase_rag/graph/graph_enrich.py @@ -25,7 +25,7 @@ from functools import lru_cache from pathlib import Path from typing import Any, TypeVar -from ast_java import ( +from java_codebase_rag.ast.ast_java import ( AnnotationRef, JavaFileAst, MethodDecl, @@ -43,7 +43,7 @@ _METHOD_ANN_TO_CAPABILITY, _TYPE_ANN_TO_CAPABILITY, ) -from java_ontology import ( +from java_codebase_rag.graph.java_ontology import ( CLIENT_KIND_REST_TEMPLATE, VALID_CAPABILITIES, VALID_CLIENT_KINDS, @@ -52,7 +52,7 @@ VALID_ROUTE_FRAMEWORKS, VALID_ROUTE_KINDS, ) -from path_filtering import LayeredIgnore, iter_java_source_files +from java_codebase_rag.graph.path_filtering import LayeredIgnore, iter_java_source_files __all__ = [ "AnnotationDecl", diff --git a/graph_types.py b/src/java_codebase_rag/graph/graph_types.py similarity index 97% rename from graph_types.py rename to src/java_codebase_rag/graph/graph_types.py index d10b744a..d067f12b 100644 --- a/graph_types.py +++ b/src/java_codebase_rag/graph/graph_types.py @@ -10,8 +10,8 @@ from pydantic import BaseModel -from ladybug_queries import LadybugGraph -from mcp_hints import generate_hints +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.mcp.mcp_hints import generate_hints __all__ = [ "NodeRef", diff --git a/java_ontology.py b/src/java_codebase_rag/graph/java_ontology.py similarity index 99% rename from java_ontology.py rename to src/java_codebase_rag/graph/java_ontology.py index e42588f0..282ec982 100644 --- a/java_ontology.py +++ b/src/java_codebase_rag/graph/java_ontology.py @@ -7,7 +7,7 @@ from dataclasses import dataclass from typing import Literal -from ast_java import ( +from java_codebase_rag.ast.ast_java import ( ROLE_ANNOTATIONS, _INJECTED_TYPES_TO_CAPABILITY, _METHOD_ANN_TO_CAPABILITY, diff --git a/ladybug_queries.py b/src/java_codebase_rag/graph/ladybug_queries.py similarity index 99% rename from ladybug_queries.py rename to src/java_codebase_rag/graph/ladybug_queries.py index f082c38f..d820f2d3 100644 --- a/ladybug_queries.py +++ b/src/java_codebase_rag/graph/ladybug_queries.py @@ -24,7 +24,7 @@ import ladybug -from ast_java import ONTOLOGY_VERSION as _ONTOLOGY_VERSION +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION as _ONTOLOGY_VERSION log = logging.getLogger(__name__) diff --git a/path_filtering.py b/src/java_codebase_rag/graph/path_filtering.py similarity index 100% rename from path_filtering.py rename to src/java_codebase_rag/graph/path_filtering.py diff --git a/src/java_codebase_rag/index/__init__.py b/src/java_codebase_rag/index/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/java_index_flow_lancedb.py b/src/java_codebase_rag/index/java_index_flow_lancedb.py similarity index 99% rename from java_index_flow_lancedb.py rename to src/java_codebase_rag/index/java_index_flow_lancedb.py index 089963f9..aa7160e2 100644 --- a/java_index_flow_lancedb.py +++ b/src/java_codebase_rag/index/java_index_flow_lancedb.py @@ -41,7 +41,7 @@ from java_codebase_rag.config import resolved_sbert_model_for_process_env from java_codebase_rag.lance_optimize import LANCE_TABLE_NAMES -from java_index_v1_common import ( +from java_codebase_rag.index.java_index_v1_common import ( JAVA_CHUNK, SBERT_MODEL, SQL_CHUNK, @@ -49,9 +49,9 @@ chunk_key_range, position_to_json, ) -from path_filtering import LayeredIgnore -from ast_java import ONTOLOGY_VERSION, parse_java -from graph_enrich import ( +from java_codebase_rag.graph.path_filtering import LayeredIgnore +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION, parse_java +from java_codebase_rag.graph.graph_enrich import ( classify_java_file, collect_annotation_meta_chain, enrich_chunk, diff --git a/java_index_v1_common.py b/src/java_codebase_rag/index/java_index_v1_common.py similarity index 100% rename from java_index_v1_common.py rename to src/java_codebase_rag/index/java_index_v1_common.py diff --git a/src/java_codebase_rag/install_data/__init__.py b/src/java_codebase_rag/install_data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/java_codebase_rag/install_data/agents/explorer-rag-cli.md b/src/java_codebase_rag/install_data/agents/explorer-rag-cli.md similarity index 100% rename from java_codebase_rag/install_data/agents/explorer-rag-cli.md rename to src/java_codebase_rag/install_data/agents/explorer-rag-cli.md diff --git a/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md b/src/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md similarity index 100% rename from java_codebase_rag/install_data/agents/explorer-rag-enhanced.md rename to src/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md diff --git a/java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md b/src/java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md similarity index 100% rename from java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md rename to src/java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md diff --git a/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md b/src/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md similarity index 100% rename from java_codebase_rag/install_data/skills/explore-codebase/SKILL.md rename to src/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md diff --git a/java_codebase_rag/installer.py b/src/java_codebase_rag/installer.py similarity index 100% rename from java_codebase_rag/installer.py rename to src/java_codebase_rag/installer.py diff --git a/java_codebase_rag/jrag.py b/src/java_codebase_rag/jrag.py similarity index 99% rename from java_codebase_rag/jrag.py rename to src/java_codebase_rag/jrag.py index ceba0aa9..bbb43ff1 100644 --- a/java_codebase_rag/jrag.py +++ b/src/java_codebase_rag/jrag.py @@ -134,7 +134,7 @@ def _apply_auto_scope(args: argparse.Namespace, cfg, graph) -> None: source_root = cfg.source_root if cfg.source_root else None if not source_root: return - from graph_enrich import detect_microservice_from_path + from java_codebase_rag.graph.graph_enrich import detect_microservice_from_path candidate = detect_microservice_from_path(Path.cwd(), Path(source_root)) if not candidate: @@ -1217,7 +1217,7 @@ def _load_graph(cfg): # type: ignore[no-untyped-def] * ontology-mismatch (``RuntimeError`` from ``LadybugGraph.get``) -> ``_IndexStale`` (caught in ``main`` -> envelope with a rebuild hint). """ - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph ladybug_path = str(cfg.ladybug_path) if not LadybugGraph.exists(ladybug_path): @@ -1233,8 +1233,8 @@ def _load_graph(cfg): # type: ignore[no-untyped-def] def _cmd_vocab_index(args: argparse.Namespace) -> int: """Rebuild the vocabulary index sidecar from the Ladybug graph.""" - from ast_java import ONTOLOGY_VERSION - from absence_vocab import VocabularyIndex, VOCAB_INDEX_FILENAME + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION + from java_codebase_rag.absence.absence_vocab import VocabularyIndex, VOCAB_INDEX_FILENAME cfg = _resolve_cfg(args) try: @@ -1565,7 +1565,7 @@ def _build_node_filter_or_error(filter_dict: dict): A bad enum (e.g. ``--role FOO``) should be a user-facing validation error, not an internal crash. Returns ``(node_filter, None)`` on success. """ - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag_envelope import Envelope from pydantic import ValidationError @@ -1591,7 +1591,7 @@ def _cmd_find_filter_mode( limit: int, ) -> int: """Find filter mode: build NodeFilter and call find_v2.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag_envelope import Envelope, next_actions_hook, normalize_enum, to_envelope_rows from java_codebase_rag.jrag_render import render @@ -1674,7 +1674,7 @@ def _cmd_find_filter_mode( def _cmd_inspect(args: argparse.Namespace) -> int: - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag_envelope import Envelope, next_actions_hook, resolve_query from java_codebase_rag.jrag_render import render @@ -2574,7 +2574,7 @@ def _cmd_callees(args: argparse.Namespace) -> int: # Producer root -> ASYNC_CALLS out (Producer -> :Route, the kafka_topic # Route this producer publishes to — NOT a :Producer node). if node.kind in ("client", "producer"): - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 edge_types = ["HTTP_CALLS"] if node.kind == "client" else ["ASYNC_CALLS"] out = mcp_v2.neighbors_v2( @@ -2710,7 +2710,7 @@ def _cmd_callees(args: argparse.Namespace) -> int: def _cmd_hierarchy(args: argparse.Namespace) -> int: - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 cfg, graph, rc = _load_graph_or_error(args) if rc: @@ -2863,7 +2863,7 @@ def _cmd_subclasses(args: argparse.Namespace) -> int: def _cmd_overrides(args: argparse.Namespace) -> int: - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 cfg, graph, rc = _load_graph_or_error(args) if rc: @@ -2916,7 +2916,7 @@ def _cmd_overrides(args: argparse.Namespace) -> int: def _cmd_overridden_by(args: argparse.Namespace) -> int: - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 cfg, graph, rc = _load_graph_or_error(args) if rc: @@ -3224,7 +3224,7 @@ def _cmd_flow(args: argparse.Namespace) -> int: def _cmd_dependencies(args: argparse.Namespace) -> int: - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 cfg, graph, rc = _load_graph_or_error(args) if rc: @@ -3559,7 +3559,7 @@ def _cmd_outline(args: argparse.Namespace) -> int: start_line<1). ``--limit`` caps the entry count (the file's symbol table is otherwise unbounded); ``truncated`` is set when more entries exist. """ - from ladybug_queries import find_symbols_in_file_range + from java_codebase_rag.graph.ladybug_queries import find_symbols_in_file_range from java_codebase_rag.jrag_envelope import Envelope, mark_truncated, next_actions_hook from java_codebase_rag.jrag_render import render @@ -3629,8 +3629,8 @@ def _cmd_imports(args: argparse.Namespace) -> int: a node per import: resolved graph Symbol when resolve_v2 hits (status=one), or an unresolved placeholder carrying the raw FQN otherwise. """ - from ast_java import parse_java - from resolve_service import resolve_v2 + from java_codebase_rag.ast.ast_java import parse_java + from java_codebase_rag.analysis.resolve_service import resolve_v2 from java_codebase_rag.jrag_envelope import Envelope, next_actions_hook from java_codebase_rag.jrag_render import render @@ -4195,7 +4195,7 @@ def _zero_result_guidance(args: argparse.Namespace, graph) -> str | None: empty (truly no matches for this query), or the probe itself errored (non-fatal — the empty result still renders). """ - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from collections import Counter from java_codebase_rag.jrag_envelope import normalize_enum @@ -4250,7 +4250,7 @@ def _cmd_search(args: argparse.Namespace) -> int: truncation, and renders. --fuzzy is rejected IN-HANDLER (not argparse-exit) so the error carries the canonical envelope shape. """ - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag_envelope import Envelope, mark_truncated, next_actions_hook, normalize_enum from java_codebase_rag.jrag_render import render @@ -4375,7 +4375,7 @@ def _cmd_search(args: argparse.Namespace) -> int: # explain renderer from there would crash `jrag search ... --explain` on the # exact Intel install that runs the lexical path. search_scoring is # dependency-free and always installed, so the explain import works everywhere. - from search_scoring import explain_score_components + from java_codebase_rag.search.search_scoring import explain_score_components comps = d.get("score_components") d["explain"] = explain_score_components( comps, diff --git a/java_codebase_rag/jrag_envelope.py b/src/java_codebase_rag/jrag_envelope.py similarity index 99% rename from java_codebase_rag/jrag_envelope.py rename to src/java_codebase_rag/jrag_envelope.py index d7217014..cc7249c2 100644 --- a/java_codebase_rag/jrag_envelope.py +++ b/src/java_codebase_rag/jrag_envelope.py @@ -18,8 +18,8 @@ from dataclasses import dataclass, field from typing import Any, Literal -from absence_types import AbsenceDiagnosis -from graph_types import NodeRef +from java_codebase_rag.absence.absence_types import AbsenceDiagnosis +from java_codebase_rag.graph.graph_types import NodeRef __all__ = [ "Envelope", @@ -532,10 +532,10 @@ def resolve_query( means ``--service`` disambiguates which microservice's route is selected. """ # Lazy imports — keeps build_parser() / `jrag --help` free of resolve/ladybug. - from resolve_service import resolve_v2 + from java_codebase_rag.analysis.resolve_service import resolve_v2 if graph is None: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph graph = LadybugGraph.get(str(cfg.ladybug_path)) diff --git a/java_codebase_rag/jrag_hints.py b/src/java_codebase_rag/jrag_hints.py similarity index 99% rename from java_codebase_rag/jrag_hints.py rename to src/java_codebase_rag/jrag_hints.py index c0b3e5a3..487fcd92 100644 --- a/java_codebase_rag/jrag_hints.py +++ b/src/java_codebase_rag/jrag_hints.py @@ -143,7 +143,7 @@ def next_actions( # EDGE_SCHEMA is the canonical label set; we use it to skip labels we don't # recognize (avoids emitting hints for spurious / future edge types the # command map doesn't cover). - from java_ontology import EDGE_SCHEMA + from java_codebase_rag.graph.java_ontology import EDGE_SCHEMA # Known virtual labels not in EDGE_SCHEMA (describe-time rollup constructs). _VIRTUAL_LABELS = frozenset({"OVERRIDDEN_BY"}) diff --git a/java_codebase_rag/jrag_render.py b/src/java_codebase_rag/jrag_render.py similarity index 99% rename from java_codebase_rag/jrag_render.py rename to src/java_codebase_rag/jrag_render.py index f010d8f4..5c4ab0a5 100644 --- a/java_codebase_rag/jrag_render.py +++ b/src/java_codebase_rag/jrag_render.py @@ -12,7 +12,7 @@ from typing import Any -from absence_types import AbsenceDiagnosis +from java_codebase_rag.absence.absence_types import AbsenceDiagnosis from java_codebase_rag.jrag_envelope import Envelope, project_envelope, simple_name __all__ = ["render", "tiered_name", "display_name"] diff --git a/java_codebase_rag/lance_optimize.py b/src/java_codebase_rag/lance_optimize.py similarity index 100% rename from java_codebase_rag/lance_optimize.py rename to src/java_codebase_rag/lance_optimize.py diff --git a/src/java_codebase_rag/mcp/__init__.py b/src/java_codebase_rag/mcp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mcp_hints.py b/src/java_codebase_rag/mcp/mcp_hints.py similarity index 99% rename from mcp_hints.py rename to src/java_codebase_rag/mcp/mcp_hints.py index da76c518..9e117efd 100644 --- a/mcp_hints.py +++ b/src/java_codebase_rag/mcp/mcp_hints.py @@ -13,7 +13,7 @@ import json from typing import Any, Literal, NamedTuple -from java_ontology import EDGE_SCHEMA, FUZZY_STRATEGY_SET +from java_codebase_rag.graph.java_ontology import EDGE_SCHEMA, FUZZY_STRATEGY_SET # Normative schema description (propose §3.1) — imported by ``mcp_v2`` for Field(description=...). MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION = ( diff --git a/mcp_v2.py b/src/java_codebase_rag/mcp/mcp_v2.py similarity index 98% rename from mcp_v2.py rename to src/java_codebase_rag/mcp/mcp_v2.py index 00942fa4..3cccedfa 100644 --- a/mcp_v2.py +++ b/src/java_codebase_rag/mcp/mcp_v2.py @@ -31,10 +31,10 @@ # installs ship without torch/lancedb); it is imported lazily in _get_sentence_transformer. from sentence_transformers import SentenceTransformer -from absence_types import AbsenceDiagnosis -from absence_diagnosis import diagnose -from absence_vocab import get_vocabulary_index -from graph_types import ( +from java_codebase_rag.absence.absence_types import AbsenceDiagnosis +from java_codebase_rag.absence.absence_diagnosis import diagnose +from java_codebase_rag.absence.absence_vocab import get_vocabulary_index +from java_codebase_rag.graph.graph_types import ( NodeRef, StructuredHint, _hints_or_skip, @@ -43,11 +43,11 @@ _to_structured_hints, set_hints_enabled, ) -from index_common import SBERT_MODEL +from java_codebase_rag.search.index_common import SBERT_MODEL from java_codebase_rag.config import resolved_sbert_model_for_process_env -from java_ontology import EDGE_SCHEMA -from ladybug_queries import LadybugGraph, OVERRIDE_AXIS_COMPOSED_EDGE_TYPES -from mcp_hints import MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION +from java_codebase_rag.graph.java_ontology import EDGE_SCHEMA +from java_codebase_rag.graph.ladybug_queries import LadybugGraph, OVERRIDE_AXIS_COMPOSED_EDGE_TYPES +from java_codebase_rag.mcp.mcp_hints import MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION # The vector stack (lancedb/torch, reached via search_lancedb) is optional — it is absent on # graph-only installs (macOS Intel). Import eagerly when available so ``run_search``/``TABLES`` @@ -55,7 +55,7 @@ # fall back to sentinels on ImportError so importing this module never fails and ``search_v2`` # can return a clean "vector search unavailable" envelope instead of crashing. try: - from search_lancedb import TABLES, run_search + from java_codebase_rag.search.search_lancedb import TABLES, run_search except ImportError: # graph-only install: no torch/lancedb TABLES = {} run_search = None @@ -924,7 +924,7 @@ def search_v2( # try -> success=False. It returns [] for sql/yaml (advisory below) and for # empty-but-valid results. try: - from search_lexical import run_lexical_search + from java_codebase_rag.search.search_lexical import run_lexical_search except ImportError: # pragma: no cover - search_lexical has no heavy deps run_lexical_search = None # type: ignore[assignment] if run_lexical_search is None: @@ -1850,7 +1850,7 @@ def neighbors_v2( # Re-export resolve symbols from resolve_service.py (imported here to avoid circular import) -from resolve_service import ( # noqa: E402 +from java_codebase_rag.analysis.resolve_service import ( # noqa: E402 ResolveCandidate, ResolveOutput, ResolveStatus, diff --git a/server.py b/src/java_codebase_rag/mcp/server.py similarity index 97% rename from server.py rename to src/java_codebase_rag/mcp/server.py index 9d9a41f3..8feded4a 100644 --- a/server.py +++ b/src/java_codebase_rag/mcp/server.py @@ -9,9 +9,9 @@ from pathlib import Path from typing import Literal -import mcp_v2 -import resolve_service -from index_common import SBERT_MODEL +from java_codebase_rag.mcp import mcp_v2 +from java_codebase_rag.analysis import resolve_service +from java_codebase_rag.search.index_common import SBERT_MODEL from java_codebase_rag.cli_progress import ( accumulate_and_relay_subprocess_streams, ) @@ -25,13 +25,20 @@ resolved_sbert_model_for_process_env, resolve_operator_config, ) -from ladybug_queries import LadybugGraph, resolve_ladybug_path +from java_codebase_rag.graph.ladybug_queries import LadybugGraph, resolve_ladybug_path from mcp.server.fastmcp import FastMCP from pydantic import BaseModel, Field # NOTE: search_lancedb.TABLES is imported lazily in list_code_index_tables_payload() — it # pulls lancedb/torch and is unavailable on graph-only installs (macOS Intel). _COCOINDEX_TARGET = "java_index_flow_lancedb.py:JavaCodeIndexLance" + +# Package-internal locations of the cocoindex flow and the graph builder, both +# executed by file path (see java_codebase_rag.pipeline). Derived from this +# file's location so they resolve under editable and wheel installs alike. +_PKG_DIR = Path(__file__).resolve().parent.parent +_FLOW_FILE = _PKG_DIR / "index" / "java_index_flow_lancedb.py" +_BUILDER_FILE = _PKG_DIR / "graph" / "build_ast_graph.py" _INSTRUCTIONS = ( "Java codebase graph navigator over an indexed Java codebase. " "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), " @@ -114,7 +121,7 @@ def __init__(self, source_root: Path): self._log_detection() def _detect_scope(self) -> str | None: - from graph_enrich import detect_microservice_from_path + from java_codebase_rag.graph.graph_enrich import detect_microservice_from_path candidate = detect_microservice_from_path(Path.cwd(), self.source_root) if candidate is None: @@ -300,7 +307,7 @@ def _graph_meta_output() -> GraphMetaOutput: def list_code_index_tables_payload() -> IndexInfoOutput: try: - from search_lancedb import TABLES + from java_codebase_rag.search.search_lancedb import TABLES tables = dict(TABLES) except ImportError: @@ -333,7 +340,7 @@ async def _run_graph_phase( A missing builder or a spawn failure returns ``started=False`` with the graph code carrying the reason (``None`` for missing builder, ``-1`` for spawn error). """ - builder = Path(__file__).resolve().parent / "build_ast_graph.py" + builder = _BUILDER_FILE if not builder.is_file(): return None, "", "", False try: @@ -426,20 +433,15 @@ async def run_refresh_pipeline( message=f"cocoindex not found next to Python: {cocoindex_bin}", phases_run=[], ) - flow_path = root / "java_index_flow_lancedb.py" - bundle_dir = Path(__file__).resolve().parent + flow_path = _FLOW_FILE if not flow_path.is_file(): - fallback = bundle_dir / "java_index_flow_lancedb.py" - if fallback.is_file(): - flow_path = fallback - else: - if on_progress is not None: - on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None)) - return RefreshIndexOutput( - success=False, - message=f"java_index_flow_lancedb.py not found under {root} nor {bundle_dir}", - phases_run=[], - ) + if on_progress is not None: + on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None)) + return RefreshIndexOutput( + success=False, + message=f"java_index_flow_lancedb.py not found at {flow_path}", + phases_run=[], + ) proc: asyncio.subprocess.Process | None = None out_b, err_b = b"", b"" # DROP the Lance target tables so the update takes the fast INSERT path diff --git a/java_codebase_rag/pipeline.py b/src/java_codebase_rag/pipeline.py similarity index 95% rename from java_codebase_rag/pipeline.py rename to src/java_codebase_rag/pipeline.py index 1a21eeb3..5601ce7f 100644 --- a/java_codebase_rag/pipeline.py +++ b/src/java_codebase_rag/pipeline.py @@ -29,8 +29,17 @@ ) -def bundle_dir() -> Path: - return Path(__file__).resolve().parent.parent +# Package-internal locations of the cocoindex flow definition and the graph +# builder. Both are executed *by file path* — cocoindex loads the flow via a +# ``file:Class`` target (COCOINDEX_TARGET below) and the builder runs as +# ``python `` — so we must resolve their paths without importing them +# (this module deliberately stays free of heavy ML imports at import time). +# Derived from this file's location so they resolve under both editable and +# wheel installs. +_PKG_DIR = Path(__file__).resolve().parent +_FLOW_DIR = _PKG_DIR / "index" +_FLOW_FILE = _FLOW_DIR / "java_index_flow_lancedb.py" +_BUILDER_FILE = _PKG_DIR / "graph" / "build_ast_graph.py" def cocoindex_bin() -> Path: @@ -266,7 +275,7 @@ def _run_cocoindex_update_impl( stdout="", stderr=f"cocoindex not found: {exe}", ) - bd = bundle_dir() + bd = _FLOW_DIR flow = bd / "java_index_flow_lancedb.py" if not flow.is_file(): if on_progress is not None: @@ -343,7 +352,7 @@ def run_cocoindex_drop(env: dict[str, str], *, quiet: bool) -> subprocess.Comple stdout="", stderr=f"cocoindex not found: {exe}", ) - bd = bundle_dir() + bd = _FLOW_DIR cmd = [str(exe), "drop", COCOINDEX_TARGET, "-f"] if quiet: cmd.append("-q") @@ -366,7 +375,7 @@ def run_build_ast_graph( on_progress: Callable[[ProgressEvent], None] | None = None, on_progress_console: object | None = None, ) -> subprocess.CompletedProcess[str]: - builder = bundle_dir() / "build_ast_graph.py" + builder = _BUILDER_FILE if not builder.is_file(): return subprocess.CompletedProcess( args=[], @@ -424,7 +433,7 @@ def run_incremental_graph( on_progress_console: object | None = None, ) -> subprocess.CompletedProcess[str]: """Run incremental graph rebuild by passing --incremental flag to build_ast_graph.py.""" - builder = bundle_dir() / "build_ast_graph.py" + builder = _BUILDER_FILE if not builder.is_file(): return subprocess.CompletedProcess( args=[], diff --git a/java_codebase_rag/progress.py b/src/java_codebase_rag/progress.py similarity index 100% rename from java_codebase_rag/progress.py rename to src/java_codebase_rag/progress.py diff --git a/src/java_codebase_rag/search/__init__.py b/src/java_codebase_rag/search/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/index_common.py b/src/java_codebase_rag/search/index_common.py similarity index 100% rename from index_common.py rename to src/java_codebase_rag/search/index_common.py diff --git a/search_lancedb.py b/src/java_codebase_rag/search/search_lancedb.py similarity index 99% rename from search_lancedb.py rename to src/java_codebase_rag/search/search_lancedb.py index c5387b1f..9f74cc71 100644 --- a/search_lancedb.py +++ b/src/java_codebase_rag/search/search_lancedb.py @@ -15,15 +15,15 @@ import numpy as np from sentence_transformers import SentenceTransformer -from chunk_heuristics import analyze_chunk, looks_like_code_identifier -from index_common import SBERT_MODEL +from java_codebase_rag.ast.chunk_heuristics import analyze_chunk, looks_like_code_identifier +from java_codebase_rag.search.index_common import SBERT_MODEL from java_codebase_rag.config import maybe_expand_embedding_model_path, resolved_sbert_model_for_process_env # Scoring & dedup primitives live in `search_scoring` (dependency-free — no # lancedb/torch) so the lexical backend `search_lexical` can share them on # graph-only (macOS Intel) installs where this module is unimportable. Re-exported # here for backward compatibility (`from search_lancedb import _clamp01`, etc.). -from search_scoring import ( # noqa: F401 +from java_codebase_rag.search.search_scoring import ( # noqa: F401 DEDUP_OVERFETCH, _ACTION_VERB_BONUS, _ACTION_VERB_PREFIXES, @@ -522,7 +522,7 @@ def _graph_expand_merge( """Expand vector top-k through the LadybugDB graph and fuse (RRF) with the original list.""" # Lazy import so the module works without ladybug installed when graph_expand=False. try: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph except Exception: return vector_rows diff --git a/search_lexical.py b/src/java_codebase_rag/search/search_lexical.py similarity index 98% rename from search_lexical.py rename to src/java_codebase_rag/search/search_lexical.py index 4c5f42c5..0d1ca17c 100644 --- a/search_lexical.py +++ b/src/java_codebase_rag/search/search_lexical.py @@ -21,8 +21,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -from ladybug_queries import LadybugGraph -from search_scoring import ( +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.search.search_scoring import ( _ROLE_SCORE_WEIGHTS, _TYPE_MATCH_BONUS_CAP, _TYPE_MATCH_BONUS_PER_HIT, @@ -33,7 +33,7 @@ ) if TYPE_CHECKING: - from mcp_v2 import NodeFilter + from java_codebase_rag.mcp.mcp_v2 import NodeFilter # Lexical relevance weights. The class/file name is the strongest discovery # signal (mirrors the type-name bonus rationale in search_scoring); fqn/package diff --git a/search_scoring.py b/src/java_codebase_rag/search/search_scoring.py similarity index 100% rename from search_scoring.py rename to src/java_codebase_rag/search/search_scoring.py diff --git a/tests/_builders.py b/tests/_builders.py index 4dae30f3..e7c9fa53 100644 --- a/tests/_builders.py +++ b/tests/_builders.py @@ -4,7 +4,7 @@ from pathlib import Path -from build_ast_graph import ( +from java_codebase_rag.graph.build_ast_graph import ( GraphTables, pass1_parse, pass2_edges, diff --git a/tests/conftest.py b/tests/conftest.py index 2d60fc2d..240858a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,15 +19,15 @@ import pytest if TYPE_CHECKING: - from build_ast_graph import GraphTables + from java_codebase_rag.graph.build_ast_graph import GraphTables BUNDLE_DIR = Path(__file__).resolve().parent.parent TESTS_DIR = Path(__file__).resolve().parent CORPUS_ROOT = TESTS_DIR / "bank-chat-system" -# Make the bundle importable when running `pytest` from the repo root. -if str(BUNDLE_DIR) not in sys.path: - sys.path.insert(0, str(BUNDLE_DIR)) +# Make the package importable when running `pytest` from the repo root (src-layout). +if str(BUNDLE_DIR / "src") not in sys.path: + sys.path.insert(0, str(BUNDLE_DIR / "src")) def pytest_configure(config) -> None: @@ -62,7 +62,7 @@ def _enforce_editable_install() -> None: # Check against the source package dir, NOT BUNDLE_DIR: the venv lives at # BUNDLE_DIR/.venv, so a site-packages copy is also "under" BUNDLE_DIR and # would pass a naive is_relative_to(BUNDLE_DIR) check. - src_pkg = BUNDLE_DIR / "java_codebase_rag" + src_pkg = BUNDLE_DIR / "src" / "java_codebase_rag" if resolved and Path(resolved).resolve().is_relative_to(src_pkg): return where = resolved or f"import failed (rc={res.returncode}): {res.stderr.strip()}" @@ -151,7 +151,7 @@ def mcp_env(ladybug_db_path: Path, tmp_path_factory) -> dict[str, str]: @pytest.fixture(scope="session") def ladybug_graph(mcp_env, ladybug_db_path: Path): """Read-only LadybugGraph singleton bound to the session DB.""" - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph LadybugGraph._instance = None LadybugGraph._instance_path = None @@ -161,7 +161,7 @@ def ladybug_graph(mcp_env, ladybug_db_path: Path): @pytest.fixture(scope="session") def mcp_server(mcp_env, ladybug_graph): """A FastMCP server instance with all tools registered.""" - from server import create_mcp_server + from java_codebase_rag.mcp.server import create_mcp_server return create_mcp_server() @@ -192,7 +192,7 @@ def ladybug_db_path_route_extraction_smoke(tmp_path_factory) -> Path: @pytest.fixture(scope="session") 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 + from java_codebase_rag.graph.ladybug_queries import LadybugGraph return LadybugGraph(str(ladybug_db_path_route_extraction_smoke)) @@ -219,7 +219,7 @@ def ladybug_db_path_fqn_collision_smoke(tmp_path_factory) -> Path: @pytest.fixture(scope="session") def ladybug_graph_fqn_collision_smoke(ladybug_db_path_fqn_collision_smoke: Path): - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph return LadybugGraph(str(ladybug_db_path_fqn_collision_smoke)) @@ -247,7 +247,7 @@ def ladybug_db_path_generated_smoke(tmp_path_factory) -> Path: @pytest.fixture(scope="session") def ladybug_graph_generated_smoke(ladybug_db_path_generated_smoke: Path): """Read-only ``LadybugGraph`` for ``generated_samples`` (one generated + one hand-written type).""" - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph return LadybugGraph(str(ladybug_db_path_generated_smoke)) diff --git a/tests/pinned_ids.py b/tests/pinned_ids.py index 1396e863..0b44d582 100644 --- a/tests/pinned_ids.py +++ b/tests/pinned_ids.py @@ -10,7 +10,7 @@ ) if TYPE_CHECKING: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph def client_message_processor_process_id(graph: LadybugGraph) -> str: diff --git a/tests/test_absence_diagnosis.py b/tests/test_absence_diagnosis.py index 6bc3a6bc..477c99cd 100644 --- a/tests/test_absence_diagnosis.py +++ b/tests/test_absence_diagnosis.py @@ -17,10 +17,10 @@ import pytest # These imports will fail until absence_diagnosis.py is created (RED). -from absence_diagnosis import _neighbors_meaningful_empty, diagnose -from absence_types import AbsenceDiagnosis -from absence_vocab import VocabularyIndex -from graph_types import NodeRef +from java_codebase_rag.absence.absence_diagnosis import _neighbors_meaningful_empty, diagnose +from java_codebase_rag.absence.absence_types import AbsenceDiagnosis +from java_codebase_rag.absence.absence_vocab import VocabularyIndex +from java_codebase_rag.graph.graph_types import NodeRef # ---- helpers ----------------------------------------------------------------- diff --git a/tests/test_absence_mcp_integration.py b/tests/test_absence_mcp_integration.py index ac774376..a3578290 100644 --- a/tests/test_absence_mcp_integration.py +++ b/tests/test_absence_mcp_integration.py @@ -9,9 +9,9 @@ import pytest -from mcp_v2 import describe_v2, find_v2, neighbors_v2, search_v2 -from resolve_service import resolve_v2 -from absence_types import AbsenceVerdict +from java_codebase_rag.mcp.mcp_v2 import describe_v2, find_v2, neighbors_v2, search_v2 +from java_codebase_rag.analysis.resolve_service import resolve_v2 +from java_codebase_rag.absence.absence_types import AbsenceVerdict def _vector_stack_available() -> bool: @@ -35,7 +35,7 @@ def _vector_stack_available() -> bool: def test_search_empty_result_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None: """Empty search result should have absence field populated with diagnosis.""" # Monkeypatch run_search to return empty results - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: []) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: []) out = search_v2("zzzNoSuchClass123", graph=ladybug_graph) assert out.success is True @@ -52,7 +52,7 @@ def test_search_empty_result_has_absence_diagnosis(ladybug_graph, monkeypatch) - @needs_vectors def test_search_typo_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None: """Search with a typo should have refine_query verdict with closest symbols.""" - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: []) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: []) out = search_v2("ChatServic", graph=ladybug_graph) # typo: missing 'e' assert out.success is True @@ -66,7 +66,7 @@ def test_search_typo_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None: @needs_vectors def test_search_external_dependency_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None: """Search for an external dependency should have external_dependency verdict.""" - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: []) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: []) out = search_v2("java.util.List", graph=ladybug_graph) assert out.success is True @@ -96,7 +96,7 @@ def test_search_non_empty_result_has_no_absence(ladybug_graph, monkeypatch) -> N "end": {"byte_offset": 30}, }, ] - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: fake_rows) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: fake_rows) out = search_v2("ChatService", graph=ladybug_graph) assert out.success is True diff --git a/tests/test_absence_types.py b/tests/test_absence_types.py index 05291d09..7f5341c9 100644 --- a/tests/test_absence_types.py +++ b/tests/test_absence_types.py @@ -4,7 +4,7 @@ from pydantic import ValidationError # These imports will fail until absence_types.py is created -from absence_types import ( +from java_codebase_rag.absence.absence_types import ( AbsenceDiagnosis, AbsenceProof, ExternalIdentity, @@ -17,8 +17,8 @@ ) # Import the 5 MCP output models -from mcp_v2 import SearchOutput, FindOutput, DescribeOutput, NeighborsOutput -from resolve_service import ResolveOutput +from java_codebase_rag.mcp.mcp_v2 import SearchOutput, FindOutput, DescribeOutput, NeighborsOutput +from java_codebase_rag.analysis.resolve_service import ResolveOutput class TestAbsenceTypesDTOs: diff --git a/tests/test_absence_vocab.py b/tests/test_absence_vocab.py index eb2ec50e..3031f954 100644 --- a/tests/test_absence_vocab.py +++ b/tests/test_absence_vocab.py @@ -7,7 +7,7 @@ import pytest # These imports will fail until absence_vocab.py is created -from absence_vocab import ( +from java_codebase_rag.absence.absence_vocab import ( SymbolRecord, VocabularyIndex, VocabIndexStale, @@ -79,7 +79,7 @@ def test_save_creates_json_sidecar(self, ladybug_graph, tmp_path): index = VocabularyIndex.build(ladybug_graph, q=3) sidecar_path = tmp_path / VOCAB_INDEX_FILENAME - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION index.save(sidecar_path, ontology_version=ONTOLOGY_VERSION) assert sidecar_path.exists() @@ -96,7 +96,7 @@ def test_save_creates_json_sidecar(self, ladybug_graph, tmp_path): def test_save_load_roundtrip(self, ladybug_graph, tmp_path): """save() then load() round-trips: symbol_count equal; a known symbol present.""" - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION original = VocabularyIndex.build(ladybug_graph, q=3) sidecar_path = tmp_path / VOCAB_INDEX_FILENAME @@ -114,7 +114,7 @@ def test_save_load_roundtrip(self, ladybug_graph, tmp_path): def test_load_stale_ontology_version_raises(self, ladybug_graph, tmp_path): """load() on a sidecar with stale ontology_version raises VocabIndexStale.""" - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION index = VocabularyIndex.build(ladybug_graph, q=3) sidecar_path = tmp_path / VOCAB_INDEX_FILENAME @@ -196,7 +196,7 @@ class TestGetVocabularyIndex: def test_first_call_builds_and_saves(self, ladybug_graph, tmp_path, monkeypatch): """First call with no sidecar builds + saves the index.""" - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION from java_codebase_rag.config import resolve_operator_config import os @@ -269,13 +269,13 @@ class TestBuildFailureResilience: def test_write_ladybug_succeeds_when_vocab_build_fails(self, corpus_root, tmp_path): """Monkeypatch build to raise → write_ladybug still succeeds (graph written; sidecar absent).""" - from build_ast_graph import write_ladybug, GraphTables + from java_codebase_rag.graph.build_ast_graph import write_ladybug, GraphTables db_path = tmp_path / "code_graph.lbug" tables = GraphTables() # Empty tables for this test # Mock VocabularyIndex.build to raise - with mock.patch("absence_vocab.VocabularyIndex.build", side_effect=RuntimeError("Build failed")): + with mock.patch("java_codebase_rag.absence.absence_vocab.VocabularyIndex.build", side_effect=RuntimeError("Build failed")): # write_ladybug should not raise write_ladybug( db_path=db_path, @@ -293,7 +293,7 @@ def test_write_ladybug_succeeds_when_vocab_build_fails(self, corpus_root, tmp_pa def test_try_build_vocabulary_index_failure_is_logged(self, corpus_root, tmp_path, caplog): """_try_build_vocabulary_index logs on failure but doesn't raise.""" - from build_ast_graph import _try_build_vocabulary_index + from java_codebase_rag.graph.build_ast_graph import _try_build_vocabulary_index db_path = tmp_path / "code_graph.lbug" # Create a dummy db file for testing @@ -301,7 +301,7 @@ def test_try_build_vocabulary_index_failure_is_logged(self, corpus_root, tmp_pat db_path.touch() # Mock VocabularyIndex.build to raise - with mock.patch("absence_vocab.VocabularyIndex.build", side_effect=RuntimeError("Build failed")): + with mock.patch("java_codebase_rag.absence.absence_vocab.VocabularyIndex.build", side_effect=RuntimeError("Build failed")): # Should not raise _try_build_vocabulary_index(db_path, corpus_root, verbose=False) @@ -314,7 +314,7 @@ class TestEnvVarWiring: def test_build_hook_reads_env_var(self, ladybug_db_path, corpus_root, monkeypatch): """_try_build_vocabulary_index reads JAVA_CODEBASE_RAG_ABSENCE_NGRAM_Q from env.""" - from build_ast_graph import _try_build_vocabulary_index + from java_codebase_rag.graph.build_ast_graph import _try_build_vocabulary_index # Set env var to q=2 monkeypatch.setenv("JAVA_CODEBASE_RAG_ABSENCE_NGRAM_Q", "2") @@ -334,7 +334,7 @@ def test_build_hook_reads_env_var(self, ladybug_db_path, corpus_root, monkeypatc def test_build_hook_default_q_when_env_var_invalid(self, ladybug_db_path, corpus_root, monkeypatch): """Build hook falls back to q=3 when env var is invalid.""" - from build_ast_graph import _try_build_vocabulary_index + from java_codebase_rag.graph.build_ast_graph import _try_build_vocabulary_index # Set env var to invalid value monkeypatch.setenv("JAVA_CODEBASE_RAG_ABSENCE_NGRAM_Q", "not_a_number") diff --git a/tests/test_agent_skills_static.py b/tests/test_agent_skills_static.py index f8f88b42..d5aa9b63 100644 --- a/tests/test_agent_skills_static.py +++ b/tests/test_agent_skills_static.py @@ -16,8 +16,8 @@ from pathlib import Path from typing import get_args -from java_ontology import NodeKind -from mcp_v2 import ComposedEdgeType, EdgeType +from java_codebase_rag.graph.java_ontology import NodeKind +from java_codebase_rag.mcp.mcp_v2 import ComposedEdgeType, EdgeType # --------------------------------------------------------------------------- # Allowlists sourced from production code diff --git a/tests/test_assign_endpoint_client_extraction.py b/tests/test_assign_endpoint_client_extraction.py index 15caf968..c04542fc 100644 --- a/tests/test_assign_endpoint_client_extraction.py +++ b/tests/test_assign_endpoint_client_extraction.py @@ -7,8 +7,8 @@ import ladybug -from ast_java import ONTOLOGY_VERSION -from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" diff --git a/tests/test_ast_graph_build.py b/tests/test_ast_graph_build.py index 844ac095..f6b95776 100644 --- a/tests/test_ast_graph_build.py +++ b/tests/test_ast_graph_build.py @@ -18,8 +18,8 @@ import pytest from _builders import build_ladybug_into, build_ladybug_to -from ast_java import ONTOLOGY_VERSION -from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain def _parse_ladybug_json(raw: str | None) -> dict: @@ -348,7 +348,7 @@ def test_cli_entrypoint_runs(tmp_path: Path, corpus_root: Path) -> None: (via the venv Python) and asserts a non-empty Kuzu DB is written. """ target = tmp_path / "graph.lbug" - script = Path(__file__).resolve().parent.parent / "build_ast_graph.py" + script = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" proc = subprocess.run( [ sys.executable, @@ -413,7 +413,7 @@ def test_pass3_known_external_calls_preserved(ladybug_db_path: Path) -> None: def _run_builder_verbose(corpus_root: Path, target_db: Path, *, extra_args: list[str] | None = None) -> subprocess.CompletedProcess: """Run build_ast_graph.py --verbose and return the CompletedProcess.""" - script = Path(__file__).resolve().parent.parent / "build_ast_graph.py" + script = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" cmd = [ sys.executable, str(script), @@ -430,7 +430,7 @@ def _progress_lines(stderr: str) -> list[str]: def _count_filtered_java_files(corpus_root: Path) -> int: - from path_filtering import LayeredIgnore, iter_java_source_files + from java_codebase_rag.graph.path_filtering import LayeredIgnore, iter_java_source_files return sum(1 for _ in iter_java_source_files(corpus_root.resolve(), ignore=LayeredIgnore(corpus_root.resolve()))) @@ -489,7 +489,7 @@ def test_build_ast_graph_passes_2_to_6_emit_step_progress(corpus_root: Path, tmp def test_build_ast_graph_quiet_emits_no_progress(corpus_root: Path, tmp_path: Path) -> None: """Without --verbose the builder emits no JCIRAG_PROGRESS lines.""" - script = Path(__file__).resolve().parent.parent / "build_ast_graph.py" + script = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" target = tmp_path / "quiet.lbug" proc = subprocess.run( [ @@ -515,7 +515,7 @@ def test_pass1_parse_incremental_total_excludes_removed_files(tmp_path: Path, ca ``pass1_total`` makes ``done`` undercount then two-way-clamp on completion. The fix: the total is ``len(scope_files - removed_files)``. """ - import build_ast_graph + from java_codebase_rag.graph import build_ast_graph root = tmp_path / "proj" java_dir = root / "src/main/java/smoke" @@ -679,7 +679,7 @@ def test_bulk_write_preserves_calls_dedup_and_callee_declaring_role(ladybug_db_p def test_bulk_write_empty_rel_table_is_noop(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: """Bulk COPY FROM with empty rows list is a no-op; corpus with no EXTENDS edges should not error.""" - import build_ast_graph + from java_codebase_rag.graph import build_ast_graph root = tmp_path / "proj" java_dir = root / "src/main/java/smoke" diff --git a/tests/test_ast_java_calls.py b/tests/test_ast_java_calls.py index 9e8e8a4c..2e487e51 100644 --- a/tests/test_ast_java_calls.py +++ b/tests/test_ast_java_calls.py @@ -3,7 +3,7 @@ from pathlib import Path -from ast_java import parse_java +from java_codebase_rag.ast.ast_java import parse_java _FIXTURE_JAVA = ( Path(__file__).resolve().parent diff --git a/tests/test_ast_java_capabilities.py b/tests/test_ast_java_capabilities.py index 237c4581..53f30426 100644 --- a/tests/test_ast_java_capabilities.py +++ b/tests/test_ast_java_capabilities.py @@ -5,7 +5,7 @@ """ from __future__ import annotations -from ast_java import parse_java +from java_codebase_rag.ast.ast_java import parse_java def _caps(source: str) -> list[str]: @@ -55,7 +55,7 @@ def test_service_with_kafka_template_field_is_producer_not_listener() -> None: t = ast.top_level_types[0] assert t.capabilities == ["MESSAGE_PRODUCER"] # role should still be SERVICE (separate axis) - from ast_java import infer_role_for_type + from java_codebase_rag.ast.ast_java import infer_role_for_type assert infer_role_for_type(t) == "SERVICE" diff --git a/tests/test_ast_java_thread_safety.py b/tests/test_ast_java_thread_safety.py index dc5e3f2f..f378236b 100644 --- a/tests/test_ast_java_thread_safety.py +++ b/tests/test_ast_java_thread_safety.py @@ -13,7 +13,7 @@ from concurrent.futures import ThreadPoolExecutor -from ast_java import parse_java +from java_codebase_rag.ast.ast_java import parse_java _SRC_A = b""" package com.example.alpha; diff --git a/tests/test_bank_chat_brownfield_integration.py b/tests/test_bank_chat_brownfield_integration.py index 1062d307..8b2029d3 100644 --- a/tests/test_bank_chat_brownfield_integration.py +++ b/tests/test_bank_chat_brownfield_integration.py @@ -9,7 +9,7 @@ import ladybug -from ladybug_queries import LadybugGraph +from java_codebase_rag.graph.ladybug_queries import LadybugGraph def _connect(db_path: Path) -> ladybug.Connection: diff --git a/tests/test_brownfield_clients.py b/tests/test_brownfield_clients.py index 5f7ab5f9..cceab380 100644 --- a/tests/test_brownfield_clients.py +++ b/tests/test_brownfield_clients.py @@ -9,7 +9,7 @@ import ladybug import pytest -from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain +from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" @@ -78,7 +78,7 @@ def _async_calls(db_path: Path) -> list[dict]: def _meta(db_path: Path) -> dict: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph LadybugGraph._instance = None LadybugGraph._instance_path = None diff --git a/tests/test_brownfield_events.py b/tests/test_brownfield_events.py index 8fca9c22..cceac819 100644 --- a/tests/test_brownfield_events.py +++ b/tests/test_brownfield_events.py @@ -5,7 +5,7 @@ import json from contextlib import redirect_stderr -from brownfield_events import ( +from java_codebase_rag.ast.brownfield_events import ( emit_brownfield_exclusivity_shadowing, emit_brownfield_method_string_literal, emit_structured_brownfield_event, diff --git a/tests/test_brownfield_overrides.py b/tests/test_brownfield_overrides.py index f62b067a..a8e26257 100644 --- a/tests/test_brownfield_overrides.py +++ b/tests/test_brownfield_overrides.py @@ -7,8 +7,8 @@ import pytest -from ast_java import parse_java -from graph_enrich import ( +from java_codebase_rag.ast.ast_java import parse_java +from java_codebase_rag.graph.graph_enrich import ( BrownfieldOverrides, _load_brownfield_overrides, collect_annotation_meta_chain, @@ -43,7 +43,7 @@ class C {} """ ) r, c = resolve_role_and_capabilities(t, overrides=_empty(), meta_chain=None) - from ast_java import infer_capabilities_for_type, infer_role_for_type + from java_codebase_rag.ast.ast_java import infer_capabilities_for_type, infer_role_for_type assert r == infer_role_for_type(t) assert c == sorted(set(infer_capabilities_for_type(t))) @@ -463,7 +463,7 @@ def test_legacy_string_codebase_capability_warns_and_is_ignored(self) -> None: def test_fqn_fires_with_enrich_chunk_lance_path(tmp_path: Path) -> None: """Regression: role_overrides fqn + enrich_chunk feeds capabilities to callers.""" - from graph_enrich import enrich_chunk + from java_codebase_rag.graph.graph_enrich import enrich_chunk y = tmp_path / ".java-codebase-rag.yaml" y.write_text( @@ -486,10 +486,10 @@ def test_fqn_fires_with_enrich_chunk_lance_path(tmp_path: Path) -> None: def test_tier1_java_lance_chunk_capabilities_list_type_matches_other_lists() -> None: """Pre-flight tier 1: `capabilities` uses the same Arrow list as other list cols.""" pytest.importorskip("cocoindex") # java_index_flow_lancedb pulls cocoindex at import - import java_index_flow_lancedb as java_lance + from java_codebase_rag.index import java_index_flow_lancedb as java_lance from typing import Annotated, get_args, get_origin, get_type_hints - from java_index_flow_lancedb import JavaLanceChunk + from java_codebase_rag.index.java_index_flow_lancedb import JavaLanceChunk def lance_anno(ftype: object) -> object: if get_origin(ftype) is not Annotated: @@ -515,9 +515,9 @@ def test_tier2_lance_row_carries_enrich_capabilities_without_lancedb() -> None: pytest.importorskip("cocoindex") # java_index_flow_lancedb pulls cocoindex at import import numpy as np - from graph_enrich import enrich_chunk - from java_index_flow_lancedb import JavaLanceChunk - from ast_java import ONTOLOGY_VERSION + from java_codebase_rag.graph.graph_enrich import enrich_chunk + from java_codebase_rag.index.java_index_flow_lancedb import JavaLanceChunk + from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION # Default SBERT (all-MiniLM-L6-v2) embedding size — no model download in CI. z = np.zeros((384,), dtype=np.float32) diff --git a/tests/test_brownfield_routes.py b/tests/test_brownfield_routes.py index acab183c..79110d01 100644 --- a/tests/test_brownfield_routes.py +++ b/tests/test_brownfield_routes.py @@ -9,7 +9,7 @@ import ladybug import pytest -from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain +from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_route_stubs" @@ -61,7 +61,7 @@ def _route_ids(db_path: Path) -> list[str]: def _meta(db_path: Path) -> dict: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph LadybugGraph._instance = None LadybugGraph._instance_path = None diff --git a/tests/test_call_edge_matching.py b/tests/test_call_edge_matching.py index c85b854b..abf8968c 100644 --- a/tests/test_call_edge_matching.py +++ b/tests/test_call_edge_matching.py @@ -3,8 +3,8 @@ import re from pathlib import Path -from ast_java import OutgoingCallDecl -from build_ast_graph import GraphTables, RouteRow, _match_call_edge +from java_codebase_rag.ast.ast_java import OutgoingCallDecl +from java_codebase_rag.graph.build_ast_graph import GraphTables, RouteRow, _match_call_edge def _mk_call(**kwargs) -> OutgoingCallDecl: @@ -115,7 +115,7 @@ def test_phantom_routes_cleaned_up_when_real_match_found(graph_tables_cross_serv def test_graph_enrich_has_single_route_strategy_ladder() -> None: - graph_enrich = Path(__file__).resolve().parent.parent / "graph_enrich.py" + graph_enrich = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "graph_enrich.py" source = graph_enrich.read_text(encoding="utf-8") matches = re.findall(r"annotation.*spel.*constant_ref", source) assert len(matches) == 1 diff --git a/tests/test_call_edges_e2e.py b/tests/test_call_edges_e2e.py index b34985e4..19f423ad 100644 --- a/tests/test_call_edges_e2e.py +++ b/tests/test_call_edges_e2e.py @@ -5,8 +5,8 @@ import ladybug -from ast_java import ONTOLOGY_VERSION -from ladybug_queries import LadybugGraph +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.graph.ladybug_queries import LadybugGraph _STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" diff --git a/tests/test_call_graph_smoke_roundtrip.py b/tests/test_call_graph_smoke_roundtrip.py index 3aafe20c..18ff68dd 100644 --- a/tests/test_call_graph_smoke_roundtrip.py +++ b/tests/test_call_graph_smoke_roundtrip.py @@ -5,7 +5,7 @@ import ladybug -from ladybug_queries import LadybugGraph +from java_codebase_rag.graph.ladybug_queries import LadybugGraph _FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "call_graph_smoke" diff --git a/tests/test_call_invariant.py b/tests/test_call_invariant.py index e4638121..67a692f4 100644 --- a/tests/test_call_invariant.py +++ b/tests/test_call_invariant.py @@ -4,7 +4,7 @@ import ladybug -from ladybug_queries import LadybugGraph +from java_codebase_rag.graph.ladybug_queries import LadybugGraph def _scalar(db_path: Path, query: str) -> int: diff --git a/tests/test_cli_progress_stdout_invariant.py b/tests/test_cli_progress_stdout_invariant.py index 33dfc6d5..45a87200 100644 --- a/tests/test_cli_progress_stdout_invariant.py +++ b/tests/test_cli_progress_stdout_invariant.py @@ -87,7 +87,7 @@ def capture_write(data: bytes | bytearray) -> int: def test_refresh_pipeline_quiet_stderr_baseline(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - import server + from java_codebase_rag.mcp import server # This test simulates a FULL (vectors-capable) install by faking the cocoindex # binary below; it must also report the vector stack as installed, otherwise @@ -160,9 +160,9 @@ def test_cli_lifecycle_stdout_invariant_reprocess( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: - import server + from java_codebase_rag.mcp import server from java_codebase_rag import cli as cli_mod - from server import RefreshIndexOutput + from java_codebase_rag.mcp.server import RefreshIndexOutput baseline = (_FIXTURE_DIR / "reprocess_quiet_success.stdout.txt").read_text(encoding="utf-8") diff --git a/tests/test_cli_quiet_parity.py b/tests/test_cli_quiet_parity.py index 14800186..6839929f 100644 --- a/tests/test_cli_quiet_parity.py +++ b/tests/test_cli_quiet_parity.py @@ -14,7 +14,7 @@ from java_codebase_rag import cli as cli_mod REPO = Path(__file__).resolve().parent.parent -BUILDER = REPO / "build_ast_graph.py" +BUILDER = REPO / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" FIXTURE_ROOT = REPO / "tests" / "fixtures" / "call_graph_smoke" _PASS1_START = "[graph] pass 1 · parsing Java files" diff --git a/tests/test_client_hint_recovery.py b/tests/test_client_hint_recovery.py index a794eb6e..7992f868 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_ladybug -from ladybug_queries import LadybugGraph +from java_codebase_rag.graph.build_ast_graph import GraphTables, _match_call_edge, pass6_match_edges, write_ladybug +from java_codebase_rag.graph.ladybug_queries import LadybugGraph _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" _HTTP_CALLER = Path(__file__).resolve().parent / "fixtures" / "http_caller_smoke" @@ -101,7 +101,7 @@ def capture(call, routes, caller_microservice): seen_kinds.append(call.client_kind) return _match_call_edge(call, routes, caller_microservice) - with patch("build_ast_graph._match_call_edge", capture): + with patch("java_codebase_rag.graph.build_ast_graph._match_call_edge", capture): pass6_match_edges(tables, verbose=False) assert "stream_bridge_send" in seen_kinds diff --git a/tests/test_client_node_extraction.py b/tests/test_client_node_extraction.py index 7fb2afca..d5cb6b9e 100644 --- a/tests/test_client_node_extraction.py +++ b/tests/test_client_node_extraction.py @@ -5,8 +5,8 @@ import ladybug -from ast_java import ONTOLOGY_VERSION -from graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" diff --git a/tests/test_client_role_rename.py b/tests/test_client_role_rename.py index 34dd63ca..55dd1a19 100644 --- a/tests/test_client_role_rename.py +++ b/tests/test_client_role_rename.py @@ -8,9 +8,9 @@ import pytest 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 ladybug_queries import LadybugGraph +from java_codebase_rag.graph.build_ast_graph import GraphTables +from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain +from java_codebase_rag.graph.ladybug_queries import LadybugGraph _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" diff --git a/tests/test_cross_service_resolution_flag.py b/tests/test_cross_service_resolution_flag.py index 98e8bc21..f8dac141 100644 --- a/tests/test_cross_service_resolution_flag.py +++ b/tests/test_cross_service_resolution_flag.py @@ -10,8 +10,8 @@ import pytest 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 +from java_codebase_rag.graph.build_ast_graph import GraphTables +from java_codebase_rag.graph.graph_enrich import _load_config_cross_service_resolution _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" @@ -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 ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph root = tmp_path / "proj" _copy_fixture(root) @@ -149,7 +149,7 @@ def test_meta_reports_cross_service_resolution(tmp_path: Path) -> None: def test_meta_resolution_null_for_old_graphs(tmp_path: Path) -> None: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph db_path = tmp_path / "legacy.lbug" db = ladybug.Database(str(db_path)) diff --git a/tests/test_feign_not_exposer.py b/tests/test_feign_not_exposer.py index 183c39f5..37db40b2 100644 --- a/tests/test_feign_not_exposer.py +++ b/tests/test_feign_not_exposer.py @@ -4,8 +4,8 @@ import ladybug -from build_ast_graph import GraphTables, write_ladybug -from ladybug_queries import LadybugGraph +from java_codebase_rag.graph.build_ast_graph import GraphTables, write_ladybug +from java_codebase_rag.graph.ladybug_queries import LadybugGraph _FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" diff --git a/tests/test_generated_detection.py b/tests/test_generated_detection.py index 275bdb8b..52c02b76 100644 --- a/tests/test_generated_detection.py +++ b/tests/test_generated_detection.py @@ -4,8 +4,8 @@ from pathlib import Path import pytest -from graph_enrich import classify_java_file, GeneratedDetectionConfig, load_generated_detection -from ast_java import parse_java +from java_codebase_rag.graph.graph_enrich import classify_java_file, GeneratedDetectionConfig, load_generated_detection +from java_codebase_rag.ast.ast_java import parse_java FIXTURE_DIR = Path(__file__).parent / "fixtures" / "generated_samples" diff --git a/tests/test_generated_filter.py b/tests/test_generated_filter.py index 6597994a..8998011f 100644 --- a/tests/test_generated_filter.py +++ b/tests/test_generated_filter.py @@ -10,11 +10,11 @@ import pytest -from mcp_v2 import find_v2, NodeFilter +from java_codebase_rag.mcp.mcp_v2 import find_v2, NodeFilter # Skip LanceDB tests if we're in a graph-only environment pytest.importorskip("lancedb") -import search_lancedb +from java_codebase_rag.search import search_lancedb FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "generated_samples" @@ -52,7 +52,7 @@ def lancedb_with_generated_index(tmp_path): bundle_dir = Path(__file__).resolve().parent.parent # Get the flow specifier - app_spec = _cocoindex_flow_specifier(bundle_dir, FIXTURE_ROOT) + app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", FIXTURE_ROOT) # Locate cocoindex binary import sys diff --git a/tests/test_generated_surface.py b/tests/test_generated_surface.py index 541f4c8e..2fcb79a9 100644 --- a/tests/test_generated_surface.py +++ b/tests/test_generated_surface.py @@ -13,8 +13,8 @@ import pytest -from mcp_v2 import SearchHit, Edge, find_v2, describe_v2, neighbors_v2, search_v2 -from graph_types import NodeRef +from java_codebase_rag.mcp.mcp_v2 import SearchHit, Edge, find_v2, describe_v2, neighbors_v2, search_v2 +from java_codebase_rag.graph.graph_types import NodeRef def _fake_search_rows_with_generated() -> list[dict[str, Any]]: @@ -56,7 +56,7 @@ def _fake_search_rows_with_generated() -> list[dict[str, Any]]: def test_search_surfaces_generated_fields(monkeypatch, ladybug_graph) -> None: """Test that search returns SearchHit with generated/generated_by set.""" pytest.importorskip("lancedb") # vector-search path; N/A on graph-only installs - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows_with_generated()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows_with_generated()) out = search_v2("OpenAPIModel", graph=ladybug_graph) assert out.success is True assert out.results @@ -93,7 +93,7 @@ def test_search_handles_missing_generated_fields(monkeypatch, ladybug_graph) -> # Note: no generated/generated_by fields }, ] - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: rows_without_generated) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: rows_without_generated) out = search_v2("OldType", graph=ladybug_graph) assert out.success is True assert out.results @@ -210,7 +210,7 @@ def test_neighbors_surfaces_generated_fields(monkeypatch, ladybug_graph) -> None # Mock _load_node_record to return a symbol with generated/generated_by original_load_node_record = None - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 original_load_node_record = mcp_v2._load_node_record def _mock_load_node_record(graph, node_id, kind): @@ -242,7 +242,7 @@ def _mock_load_node_record(graph, node_id, kind): # For the origin node, use the original function return original_load_node_record(graph, node_id, kind) - monkeypatch.setattr("mcp_v2._load_node_record", _mock_load_node_record) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2._load_node_record", _mock_load_node_record) out = neighbors_v2(method_id, direction="out", edge_types=["CALLS"], graph=ladybug_graph) assert out.success is True diff --git a/tests/test_graph_enrich.py b/tests/test_graph_enrich.py index 296da42e..737b55b8 100644 --- a/tests/test_graph_enrich.py +++ b/tests/test_graph_enrich.py @@ -10,7 +10,7 @@ import pytest -from graph_enrich import ( +from java_codebase_rag.graph.graph_enrich import ( _load_config_microservice_roots, microservice_for_path, module_for_path, diff --git a/tests/test_graph_generated_node.py b/tests/test_graph_generated_node.py index bb89d0b4..36357b78 100644 --- a/tests/test_graph_generated_node.py +++ b/tests/test_graph_generated_node.py @@ -89,9 +89,9 @@ def test_incremental_rebuild_preserves_generated_fields( tmp_path: Path, ) -> None: """Incremental rebuild: a preserved (unchanged) generated type retains its generated/generated_by.""" - from build_ast_graph import FileHashTracker, GraphTables, incremental_rebuild, pass1_parse, write_ladybug - from graph_enrich import load_generated_detection - from path_filtering import LayeredIgnore + from java_codebase_rag.graph.build_ast_graph import FileHashTracker, GraphTables, incremental_rebuild, pass1_parse, write_ladybug + from java_codebase_rag.graph.graph_enrich import load_generated_detection + from java_codebase_rag.graph.path_filtering import LayeredIgnore load_generated_detection.cache_clear() @@ -130,7 +130,7 @@ def test_incremental_rebuild_preserves_generated_fields( # Initial build tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) - from build_ast_graph import pass2_edges + from java_codebase_rag.graph.build_ast_graph import pass2_edges pass2_edges(tables, asts, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) diff --git a/tests/test_graph_only_boot.py b/tests/test_graph_only_boot.py index 940d886a..d5510bc7 100644 --- a/tests/test_graph_only_boot.py +++ b/tests/test_graph_only_boot.py @@ -25,12 +25,12 @@ for _m in {modules!r}: sys.modules[_m] = None # force ImportError on import - import server # must not pull the vector stack at module load + from java_codebase_rag.mcp import server # must not pull the vector stack at module load srv = server.create_mcp_server() # registers tools + runs ScopeManager setup loaded = [m for m in {vector!r} if sys.modules.get(m) is not None] tools = sorted(t.name for t in asyncio.run(srv.list_tools())) - from mcp_v2 import search_v2 + from java_codebase_rag.mcp.mcp_v2 import search_v2 out = search_v2(query="x") print("TOOLS:" + ",".join(tools)) print("LOADED_AT_BOOT:" + (",".join(loaded) or "none")) @@ -123,7 +123,7 @@ def test_refresh_pipeline_skips_vectors_and_builds_graph_on_graph_only( from contextlib import redirect_stderr from pathlib import Path - import server + from java_codebase_rag.mcp import server monkeypatch.setattr(server, "vector_stack_installed", lambda: False) diff --git a/tests/test_incremental_graph.py b/tests/test_incremental_graph.py index 0a3f7cc3..d44068b6 100644 --- a/tests/test_incremental_graph.py +++ b/tests/test_incremental_graph.py @@ -10,9 +10,9 @@ import ladybug -from ast_java import ONTOLOGY_VERSION -from build_ast_graph import FileHashTracker, GraphTables, pass1_parse, pass2_edges, pass4_routes -from path_filtering import LayeredIgnore +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.graph.build_ast_graph import FileHashTracker, GraphTables, pass1_parse, pass2_edges, pass4_routes +from java_codebase_rag.graph.path_filtering import LayeredIgnore class TestFileHashTracker: @@ -232,7 +232,7 @@ class TestIncrementalOrchestrator: def test_incremental_single_file_change(self, tmp_path: Path) -> None: """Change one .java file, run incremental, verify only that file's nodes changed.""" - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild source_root = tmp_path / "src" source_root.mkdir() @@ -250,7 +250,7 @@ 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_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug pass2_edges(tables, asts, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -286,7 +286,7 @@ def test_incremental_new_file(self, tmp_path: Path) -> None: (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -302,7 +302,7 @@ def test_incremental_new_file(self, tmp_path: Path) -> None: (source_root / "B.java").write_text("package pkg; class B {}", encoding="utf-8") # Run incremental - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" @@ -322,7 +322,7 @@ def test_incremental_deleted_file(self, tmp_path: Path) -> None: (source_root / "B.java").write_text("package pkg; class B {}", encoding="utf-8") # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -338,7 +338,7 @@ def test_incremental_deleted_file(self, tmp_path: Path) -> None: (source_root / "B.java").unlink() # Run incremental - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" @@ -369,7 +369,7 @@ def test_incremental_phantom_nodes_preserved(self, tmp_path: Path) -> None: ) # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -399,7 +399,7 @@ def test_incremental_phantom_nodes_preserved(self, tmp_path: Path) -> None: ) # Run incremental - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild incremental_rebuild(source_root, ladybug_path, verbose=False) # Verify phantom nodes still exist @@ -427,7 +427,7 @@ def test_incremental_dependent_expansion(self, tmp_path: Path) -> None: ) # Initial build (pass2 needed for EXTENDS edges) - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) @@ -446,7 +446,7 @@ def test_incremental_dependent_expansion(self, tmp_path: Path) -> None: ) # Run incremental - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) # Derived.java should be reprocessed due to EXTENDS edge @@ -468,7 +468,7 @@ def test_incremental_expansion_cap_fallback(self, tmp_path: Path) -> None: ) # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) @@ -488,7 +488,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 + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False, expansion_cap=2) # Should fall back to full rebuild due to cap exceeded @@ -506,7 +506,7 @@ def test_incremental_crash_marker_triggers_fallback(self, tmp_path: Path) -> Non (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -528,7 +528,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 + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "full_fallback" @@ -547,7 +547,7 @@ def test_incremental_crash_marker_removed_on_success(self, tmp_path: Path) -> No (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -565,7 +565,7 @@ def test_incremental_crash_marker_removed_on_success(self, tmp_path: Path) -> No ) # Run incremental - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" @@ -586,7 +586,7 @@ def test_incremental_no_changes_is_noop(self, tmp_path: Path) -> None: (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -609,7 +609,7 @@ def test_incremental_no_changes_is_noop(self, tmp_path: Path) -> None: tracker.save() # Run incremental with no changes - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" @@ -638,7 +638,7 @@ def test_incremental_pass5_6_always_global(self, tmp_path: Path) -> None: (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Initial build - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -656,7 +656,7 @@ def test_incremental_pass5_6_always_global(self, tmp_path: Path) -> None: ) # Run incremental - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild result = incremental_rebuild(source_root, ladybug_path, verbose=False) assert result.mode == "incremental" @@ -675,7 +675,7 @@ def test_incremental_pass5_6_always_global(self, tmp_path: Path) -> None: def test_load_existing_types_populates_indexes(self, tmp_path: Path) -> None: """Build full graph, then load existing types into empty GraphTables, verify types/by_simple_name/by_package populated.""" - from build_ast_graph import _load_existing_types + from java_codebase_rag.graph.build_ast_graph import _load_existing_types source_root = tmp_path / "src" source_root.mkdir() @@ -685,7 +685,7 @@ def test_load_existing_types_populates_indexes(self, tmp_path: Path) -> None: (source_root / "A.java").write_text("package pkg; class A {}", encoding="utf-8") # Build full graph - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -704,7 +704,7 @@ def test_load_existing_types_populates_indexes(self, tmp_path: Path) -> None: def test_find_dependents_returns_incoming_edge_sources(self, tmp_path: Path) -> None: """Seed graph with EXTENDS edge from file B to file A, change file A, verify _find_dependents returns file B's filename.""" - from build_ast_graph import _find_dependents + from java_codebase_rag.graph.build_ast_graph import _find_dependents source_root = tmp_path / "src" source_root.mkdir() @@ -717,7 +717,7 @@ def test_find_dependents_returns_incoming_edge_sources(self, tmp_path: Path) -> ) # Build full graph - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) @@ -747,7 +747,7 @@ def test_delete_file_scope_removes_only_matching(self, tmp_path: Path) -> None: Uses the new (changed_files, dependent_files) signature with an empty dependent set so behavior matches the legacy single-file case. """ - from build_ast_graph import _delete_file_scope + from java_codebase_rag.graph.build_ast_graph import _delete_file_scope source_root = tmp_path / "src" source_root.mkdir() @@ -758,7 +758,7 @@ def test_delete_file_scope_removes_only_matching(self, tmp_path: Path) -> None: (source_root / "B.java").write_text("package pkg; class B {}", encoding="utf-8") # Build full graph - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug tables = GraphTables() pass1_parse(source_root, tables, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -796,7 +796,7 @@ def test_delete_file_scope_preserves_dependent_nodes(self, tmp_path: Path) -> No no exception, A's node is gone, B and C nodes are preserved, and the out-of-scope C->B CALLS edge survives. """ - from build_ast_graph import _delete_file_scope, write_ladybug + from java_codebase_rag.graph.build_ast_graph import _delete_file_scope, write_ladybug source_root = tmp_path / "src" source_root.mkdir() @@ -828,7 +828,7 @@ def test_delete_file_scope_preserves_dependent_nodes(self, tmp_path: Path) -> No tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) - from build_ast_graph import pass3_calls + from java_codebase_rag.graph.build_ast_graph import pass3_calls pass3_calls(tables, asts, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -892,7 +892,7 @@ def test_incremental_preserves_incoming_edges_to_dependent(self, tmp_path: Path) After the fix: mode is "incremental", B's node survives, and the C->B CALLS edge is preserved. """ - from build_ast_graph import incremental_rebuild, write_ladybug + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild, write_ladybug source_root = tmp_path / "src" source_root.mkdir() @@ -926,7 +926,7 @@ def test_incremental_preserves_incoming_edges_to_dependent(self, tmp_path: Path) tables = GraphTables() asts = pass1_parse(source_root, tables, verbose=False) pass2_edges(tables, asts, verbose=False) - from build_ast_graph import pass3_calls + from java_codebase_rag.graph.build_ast_graph import pass3_calls pass3_calls(tables, asts, verbose=False) write_ladybug(ladybug_path, tables, source_root=source_root, verbose=False) @@ -992,7 +992,7 @@ def test_incremental_bulk_write_equivalent_to_full_rebuild(self, tmp_path: Path) state (bulk) and asserts node count, per-type edge counts, and GraphMeta counters are identical. """ - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild from _builders import build_ladybug_full_into source_root = tmp_path / "src" @@ -1175,8 +1175,8 @@ def test_incremental_global_pass_runs_pass4_on_global_tables(self, tmp_path: Pat changed-files one. Removing the global pass4 call (incremental_rebuild step 6) leaves only one tables instance -> failure. """ - import build_ast_graph as bag - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph import build_ast_graph as bag + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild from _builders import build_ladybug_full_into source_root = tmp_path / "src" @@ -1243,8 +1243,8 @@ def test_incremental_refreshes_dependent_role_on_meta_chain_change( handle. The CLI runs each increment as a fresh process (cold meta-chain cache); the test mirrors that so the increment sees the updated chain. """ - from build_ast_graph import incremental_rebuild - from graph_enrich import collect_annotation_meta_chain + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.graph_enrich import collect_annotation_meta_chain from _builders import build_ladybug_full_into source_root = tmp_path / "src" @@ -1321,8 +1321,8 @@ def test_incremental_pulls_in_annotation_users_on_def_change( so this isolates the scope-tracking fix from the preserved-dependent property refresh. """ - from build_ast_graph import incremental_rebuild - from graph_enrich import collect_annotation_meta_chain + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.graph_enrich import collect_annotation_meta_chain from _builders import build_ladybug_full_into source_root = tmp_path / "src" @@ -1397,7 +1397,7 @@ def test_incremental_overrides_not_duplicated_for_non_scope_subtype( unrelated `Other.java` change triggers the increment so `TImpl`/`T` are loaded as stubs. The increment must keep exactly one OVERRIDES edge. """ - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild from _builders import build_ladybug_full_into source_root = tmp_path / "src" @@ -1464,7 +1464,7 @@ def test_incremental_route_merge_dedup_preserved(self, tmp_path: Path) -> None: the name is kept for plan-reference continuity. The SET branch is what dedups against routes written during the scoped step here. """ - from build_ast_graph import incremental_rebuild, write_ladybug + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild, write_ladybug source_root = tmp_path / "src" source_root.mkdir() @@ -1550,7 +1550,7 @@ def test_init_hash_tracker_prunes_stale_entries(self, tmp_path: Path) -> None: Without pruning, a stale entry is re-detected as 'removed' on every ``increment``, sustaining an endless full-rebuild loop. """ - from build_ast_graph import write_ladybug + from java_codebase_rag.graph.build_ast_graph import write_ladybug source_root = tmp_path / "src" source_root.mkdir() @@ -1588,7 +1588,7 @@ def test_incremental_with_http_clients_does_not_fall_back(self, tmp_path: Path) (forcing full_fallback). After the fix: mode is "incremental". """ from _builders import build_ladybug_full_into - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild corpus = Path(__file__).parent / "fixtures" / "http_caller_smoke" source_root = tmp_path / "src" @@ -1632,7 +1632,7 @@ def test_reprocess_graph_only_then_increment_is_noop(self, tmp_path: Path) -> No ``reprocess --graph-only`` does) followed by ``increment`` with no source changes must be a no-op, not a second full rebuild.""" from _builders import build_ladybug_full_into - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild corpus = Path(__file__).parent / "fixtures" / "http_caller_smoke" source_root = tmp_path / "src" @@ -1658,7 +1658,7 @@ def test_incremental_ghost_entry_then_next_run_is_noop(self, tmp_path: Path) -> hashes). Before the fixes this fell back to full and preserved the ghost. """ from _builders import build_ladybug_full_into - from build_ast_graph import incremental_rebuild + from java_codebase_rag.graph.build_ast_graph import incremental_rebuild corpus = Path(__file__).parent / "fixtures" / "http_caller_smoke" source_root = tmp_path / "src" diff --git a/tests/test_install_data_sync.py b/tests/test_install_data_sync.py index cbfd6028..24303321 100644 --- a/tests/test_install_data_sync.py +++ b/tests/test_install_data_sync.py @@ -83,18 +83,18 @@ def _seed_dev_source(tmp_path: Path, *, cli_skill_content: str = "# test") -> No def _seed_install_data(tmp_path: Path, *, extra: list[Path] | None = None) -> None: """Create the matching install_data tree (no drift) for the SYNC_MAP.""" - tmp_install_agents = tmp_path / "java_codebase_rag" / "install_data" / "agents" + tmp_install_agents = tmp_path / "src" / "java_codebase_rag" / "install_data" / "agents" tmp_install_agents.mkdir(parents=True, exist_ok=True) (tmp_install_agents / "explorer-rag-enhanced.md").write_text("# test") tmp_install_mcp_skill = ( - tmp_path / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase" + tmp_path / "src" / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase" ) tmp_install_mcp_skill.mkdir(parents=True, exist_ok=True) (tmp_install_mcp_skill / "SKILL.md").write_text("# test") tmp_install_cli_skill = ( - tmp_path / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase-cli" + tmp_path / "src" / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase-cli" ) tmp_install_cli_skill.mkdir(parents=True, exist_ok=True) (tmp_install_cli_skill / "SKILL.md").write_text("# test") @@ -151,18 +151,18 @@ def test_sync_script_detects_drift(): # Also create the install_data directory structure in temp # so the script has something to compare against - tmp_install = tmp_path / "java_codebase_rag" / "install_data" / "agents" + tmp_install = tmp_path / "src" / "java_codebase_rag" / "install_data" / "agents" tmp_install.mkdir(parents=True) # Copy the unmutated file to install_data (tmp_install / "explorer-rag-enhanced.md").write_bytes(real_dev_file.read_bytes()) - tmp_install_skills = tmp_path / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase" + tmp_install_skills = tmp_path / "src" / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase" tmp_install_skills.mkdir(parents=True) (tmp_install_skills / "SKILL.md").write_bytes(real_skill_file.read_bytes()) tmp_install_cli_skills = ( - tmp_path / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase-cli" + tmp_path / "src" / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase-cli" ) tmp_install_cli_skills.mkdir(parents=True) (tmp_install_cli_skills / "SKILL.md").write_text("# test") @@ -196,7 +196,7 @@ def test_sync_script_detects_extra_files(): # Create install_data with an extra file _seed_install_data( tmp_path, - extra=[tmp_path / "java_codebase_rag" / "install_data" / "agents" / "extra_file.md"], + extra=[tmp_path / "src" / "java_codebase_rag" / "install_data" / "agents" / "extra_file.md"], ) result = run_sync_script(check=True, cwd=tmp_path) @@ -223,14 +223,14 @@ def test_sync_script_detects_missing_files(): _seed_dev_source(tmp_path) # Create empty install_data (missing the files) - tmp_install = tmp_path / "java_codebase_rag" / "install_data" / "agents" + tmp_install = tmp_path / "src" / "java_codebase_rag" / "install_data" / "agents" tmp_install.mkdir(parents=True) - tmp_install_skills = tmp_path / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase" + tmp_install_skills = tmp_path / "src" / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase" tmp_install_skills.mkdir(parents=True) tmp_install_cli_skills = ( - tmp_path / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase-cli" + tmp_path / "src" / "java_codebase_rag" / "install_data" / "skills" / "explore-codebase-cli" ) tmp_install_cli_skills.mkdir(parents=True) diff --git a/tests/test_java_codebase_rag_cli.py b/tests/test_java_codebase_rag_cli.py index bae193e2..54b86f8b 100644 --- a/tests/test_java_codebase_rag_cli.py +++ b/tests/test_java_codebase_rag_cli.py @@ -1180,7 +1180,7 @@ def fake_graph(**_kwargs: object) -> subprocess.CompletedProcess[str]: def test_reprocess_no_flag_cocoindex_failure_records_vectors_only( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: - import server as server_mod + from java_codebase_rag.mcp import server as server_mod idx = tmp_path / "idx_nf" idx.mkdir() @@ -1293,7 +1293,7 @@ def test_mcp_server_loads_yaml_config_at_startup( and applies the result to os.environ. Uses mocks to avoid loading real models or leaking env state (e.g. SBERT_DEVICE=cuda) to subsequent tests. """ - import server as server_mod + from java_codebase_rag.mcp import server as server_mod from unittest.mock import MagicMock fake_cfg = MagicMock() @@ -1325,7 +1325,7 @@ def test_mcp_server_yaml_config_precedence_env_over_yaml( This test verifies that main() delegates to resolve_operator_config with the correct source root, which handles precedence internally. """ - import server as server_mod + from java_codebase_rag.mcp import server as server_mod from unittest.mock import MagicMock fake_cfg = MagicMock() diff --git a/tests/test_jrag_auto_scope.py b/tests/test_jrag_auto_scope.py index 6def54bc..3ab2e59e 100644 --- a/tests/test_jrag_auto_scope.py +++ b/tests/test_jrag_auto_scope.py @@ -148,7 +148,7 @@ def test_search_filter_carries_auto_scope( monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path ) -> None: """search builds ``filter.microservice`` from the cwd-derived default.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(corpus_root)) diff --git a/tests/test_jrag_enum_choices.py b/tests/test_jrag_enum_choices.py index f6a2f400..84720055 100644 --- a/tests/test_jrag_enum_choices.py +++ b/tests/test_jrag_enum_choices.py @@ -15,8 +15,8 @@ import pytest -import java_ontology -import mcp_v2 +from java_codebase_rag.graph import java_ontology +from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import ( _CAPABILITY_CHOICES, diff --git a/tests/test_jrag_envelope.py b/tests/test_jrag_envelope.py index 7549d3b4..a89f817f 100644 --- a/tests/test_jrag_envelope.py +++ b/tests/test_jrag_envelope.py @@ -10,7 +10,7 @@ import pytest -from graph_types import NodeRef +from java_codebase_rag.graph.graph_types import NodeRef from java_codebase_rag.jrag_envelope import ( Envelope, mark_truncated, @@ -21,7 +21,7 @@ resolve_query, to_envelope_rows, ) -from resolve_service import ResolveCandidate, ResolveOutput +from java_codebase_rag.analysis.resolve_service import ResolveCandidate, ResolveOutput # ----- Test 1: to_dict omits empty optionals ----- @@ -131,7 +131,7 @@ def fake_resolve_v2(identifier, hint_kind=None, graph=None, **kwargs): assert identifier == "doStuff" return fake_output - monkeypatch.setattr("resolve_service.resolve_v2", fake_resolve_v2) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", fake_resolve_v2) graph = _graph_returning_file_location("src/Foo.java", 42) cfg = MagicMock() cfg.ladybug_path = "/tmp/x/code_graph.lbug" @@ -158,7 +158,7 @@ def test_resolve_query_one_blocked_by_post_filter_returns_not_found( ) -> None: node = _make_node(role="SERVICE") fake_output = ResolveOutput(success=True, status="one", node=node) - monkeypatch.setattr("resolve_service.resolve_v2", lambda *a, **kw: fake_output) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", lambda *a, **kw: fake_output) graph = _graph_returning_file_location("src/Foo.java", 1) cfg = MagicMock() @@ -189,7 +189,7 @@ def test_resolve_query_many_returns_candidates_with_reason(monkeypatch: pytest.M ResolveCandidate(node=n2, score=0.5, reason="short_name"), ], ) - monkeypatch.setattr("resolve_service.resolve_v2", lambda *a, **kw: fake_output) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", lambda *a, **kw: fake_output) graph = MagicMock() cfg = MagicMock() @@ -228,7 +228,7 @@ def test_resolve_query_many_post_filter_collapses_to_one(monkeypatch: pytest.Mon ResolveCandidate(node=n_other, score=0.5, reason="short_name"), ], ) - monkeypatch.setattr("resolve_service.resolve_v2", lambda *a, **kw: fake_output) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", lambda *a, **kw: fake_output) graph = _graph_returning_file_location("Foo.java", 7) cfg = MagicMock() @@ -260,7 +260,7 @@ def test_resolve_query_many_caps_candidates_at_ten(monkeypatch: pytest.MonkeyPat for i in range(12) ] fake_output = ResolveOutput(success=True, status="many", candidates=cands) - monkeypatch.setattr("resolve_service.resolve_v2", lambda *a, **kw: fake_output) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", lambda *a, **kw: fake_output) graph = MagicMock() cfg = MagicMock() @@ -292,7 +292,7 @@ def test_resolve_query_many_post_filter_rejects_all_is_not_found( ResolveCandidate(node=n2, score=0.5, reason="short_name"), ], ) - monkeypatch.setattr("resolve_service.resolve_v2", lambda *a, **kw: fake_output) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", lambda *a, **kw: fake_output) graph = MagicMock() cfg = MagicMock() @@ -320,7 +320,7 @@ def test_resolve_query_none_is_not_found_with_search_hint(monkeypatch: pytest.Mo status="none", message="No matches for identifier; use search(query=...) for ranked fuzzy lookup.", ) - monkeypatch.setattr("resolve_service.resolve_v2", lambda *a, **kw: fake_output) + monkeypatch.setattr("java_codebase_rag.analysis.resolve_service.resolve_v2", lambda *a, **kw: fake_output) graph = MagicMock() cfg = MagicMock() diff --git a/tests/test_jrag_envelope_absence.py b/tests/test_jrag_envelope_absence.py index 2c31e0e2..df2342e6 100644 --- a/tests/test_jrag_envelope_absence.py +++ b/tests/test_jrag_envelope_absence.py @@ -12,11 +12,11 @@ import pytest -from absence_types import AbsenceDiagnosis, AbsenceProof, ExternalIdentity -from graph_types import NodeRef +from java_codebase_rag.absence.absence_types import AbsenceDiagnosis, AbsenceProof, ExternalIdentity +from java_codebase_rag.graph.graph_types import NodeRef from java_codebase_rag.jrag_envelope import Envelope, resolve_query from java_codebase_rag.jrag_render import render -from resolve_service import ResolveOutput +from java_codebase_rag.analysis.resolve_service import ResolveOutput # ----- Test 1: Envelope.absence serialization ----- @@ -223,9 +223,9 @@ def test_resolve_none_carries_absence_to_envelope() -> None: ) # Mock resolve_v2 to return our test output - from resolve_service import resolve_v2 + from java_codebase_rag.analysis.resolve_service import resolve_v2 original_resolve = resolve_v2 - import resolve_service + from java_codebase_rag.analysis import resolve_service resolve_service.resolve_v2 = lambda *args, **kwargs: mock_output try: @@ -259,9 +259,9 @@ def test_resolve_without_absence() -> None: absence=None, # No diagnosis ) - from resolve_service import resolve_v2 + from java_codebase_rag.analysis.resolve_service import resolve_v2 original_resolve = resolve_v2 - import resolve_service + from java_codebase_rag.analysis import resolve_service resolve_service.resolve_v2 = lambda *args, **kwargs: mock_output try: diff --git a/tests/test_jrag_orientation.py b/tests/test_jrag_orientation.py index 95b0aee2..0593efd3 100644 --- a/tests/test_jrag_orientation.py +++ b/tests/test_jrag_orientation.py @@ -209,7 +209,7 @@ def test_search_returns_ranked_hits( monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path ) -> None: """search returns ranked hits from search_v2 (mocked to avoid Lance dependency).""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -243,7 +243,7 @@ def test_search_hybrid_calls_hybrid_path( monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path ) -> None: """--hybrid flag passes hybrid=True to search_v2.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -271,7 +271,7 @@ def test_search_table_all_runs_three_tables( monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path ) -> None: """--table all passes table='all' to search_v2 (java+sql+yaml).""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -298,7 +298,7 @@ def test_search_offset_paginates( monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path ) -> None: """--offset paginates: passes offset to search_v2 and renders next_offset hint.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -366,7 +366,7 @@ def test_search_min_score_drops_negative_noise( Scores are now unified to [0,1] across all modes. The default floor of 0.0 drops weak hits; --min-score 0.5 keeps only the stronger half. """ - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -417,7 +417,7 @@ def test_search_hit_carries_file_path( """Each rendered search hit includes a `file` locator (filename:start_line) so an agent can jump to the hit. SearchHit now carries filename/start_line; the projector folds them into the `file` display field.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -862,7 +862,7 @@ def test_search_explain_calls_search_v2_with_explain_true( monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path ) -> None: """--explain flag passes explain=True to search_v2.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -888,7 +888,7 @@ def mock_search_v2(query, **kwargs): def test_search_dedup_default_collapses_same_fqn(monkeypatch, corpus_root: Path, ladybug_db_path: Path) -> None: """Default search (dedup ON) collapses multiple chunks of same FQN into one node.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -933,7 +933,7 @@ def mock_search_v2(query, **kwargs): def test_search_chunks_flag_passes_dedup_false(monkeypatch, corpus_root: Path, ladybug_db_path: Path) -> None: """--chunks flag passes dedup=False to search_v2, disabling dedup.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -978,7 +978,7 @@ def mock_search_v2(query, **kwargs): def test_search_limit_zero_returns_empty_not_truncated(monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path) -> None: """--limit 0 returns clean empty page (truncated:false) WITHOUT calling search_v2.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -1006,7 +1006,7 @@ def mock_search_v2(query, **kwargs): def test_search_zero_results_with_role_filter_emits_guidance(monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path) -> None: """Filtered search returning 0 results emits guidance when matches exist under other filter values.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -1068,7 +1068,7 @@ def mock_search_v2(query, **kwargs): def test_search_zero_results_no_filter_no_guidance(monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path) -> None: """Unfiltered search returning 0 results does NOT run probe or emit guidance.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) @@ -1099,7 +1099,7 @@ def mock_search_v2(query, **kwargs): def test_search_probe_failure_does_not_break_empty_rendering(monkeypatch, capsys, corpus_root: Path, ladybug_db_path: Path) -> None: """When probe fails (raises), the empty result still renders successfully.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 from java_codebase_rag.jrag import main env_index = str(ladybug_db_path.parent) diff --git a/tests/test_jrag_render.py b/tests/test_jrag_render.py index ea6aec93..ea9e7064 100644 --- a/tests/test_jrag_render.py +++ b/tests/test_jrag_render.py @@ -385,7 +385,7 @@ def test_simple_name_derived_from_fqn() -> None: A pydantic NodeRef crosses the model_dump boundary as a dict, then simple_name extracts the simple name from the FQN. """ - from graph_types import NodeRef + from java_codebase_rag.graph.graph_types import NodeRef ref = NodeRef(id="sym:1", kind="symbol", fqn="com.example.MyClass.handle") row = ref.model_dump() diff --git a/tests/test_ladybug_queries.py b/tests/test_ladybug_queries.py index 82485f76..8bb24768 100644 --- a/tests/test_ladybug_queries.py +++ b/tests/test_ladybug_queries.py @@ -23,8 +23,8 @@ import ladybug import pytest -from ast_java import ONTOLOGY_VERSION -from ladybug_queries import LadybugGraph, _is_external_fqn +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.graph.ladybug_queries import LadybugGraph, _is_external_fqn def _names(symbols) -> set[str]: @@ -523,7 +523,7 @@ def test_find_route_callers_includes_producer_callers(ladybug_db_path_cross_serv def test_find_route_callers_returns_route_caller_client_node(ladybug_db_path_cross_service_smoke: Path) -> None: - from ladybug_queries import RouteCaller + from java_codebase_rag.graph.ladybug_queries import RouteCaller g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) routes = g.list_routes(limit=50) @@ -562,7 +562,7 @@ def test_parse_ladybug_json_handles_colon_in_values() -> None: word-colon run (e.g. a URL) is not corrupted (issue #359). The prior regex matched ``(\\w+):`` anywhere, which turned {url: "https://x"} into junk and fell back to {}.""" - from ladybug_queries import _parse_ladybug_json + from java_codebase_rag.graph.ladybug_queries import _parse_ladybug_json # Standard unquoted keys (LadybugDB style). assert _parse_ladybug_json("{packages: 1, files: 2}") == {"packages": 1, "files": 2} diff --git a/tests/test_lance_optimize.py b/tests/test_lance_optimize.py index ee128956..ded5a553 100644 --- a/tests/test_lance_optimize.py +++ b/tests/test_lance_optimize.py @@ -274,7 +274,7 @@ def test_lance_table_names_constant_matches_search_lancedb_tables() -> None: # Imported lazily to avoid pulling sentence-transformers at collection time. sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) try: - from search_lancedb import TABLES + from java_codebase_rag.search.search_lancedb import TABLES finally: sys.path.pop(0) assert set(LANCE_TABLE_NAMES) == set(TABLES.values()) diff --git a/tests/test_lancedb_e2e.py b/tests/test_lancedb_e2e.py index 0e1d8c5d..373070bc 100644 --- a/tests/test_lancedb_e2e.py +++ b/tests/test_lancedb_e2e.py @@ -91,7 +91,7 @@ def lance_index(tmp_path_factory, corpus_root: Path) -> Path: index_dir = work / ".java-codebase-rag" index_dir.mkdir(parents=True) - app_spec = _cocoindex_flow_specifier(bundle_dir, Path(corpus_root)) + app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", Path(corpus_root)) env = { **os.environ, @@ -126,7 +126,7 @@ def lance_index(tmp_path_factory, corpus_root: Path) -> Path: f"the in-flow background optimize race may have regressed:\n{proc.stderr}" ) - builder = bundle_dir / "build_ast_graph.py" + builder = bundle_dir / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" proc = subprocess.run( [ sys.executable, @@ -161,7 +161,7 @@ def lance_index_capability_smoke(tmp_path_factory) -> Path: work = tmp_path_factory.mktemp("lance_cap_smoke") index_dir = work / ".java-codebase-rag" index_dir.mkdir(parents=True) - app_spec = _cocoindex_flow_specifier(bundle_dir, Path(CAPABILITY_SMOKE_ROOT)) + app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", Path(CAPABILITY_SMOKE_ROOT)) env = { **os.environ, "JAVA_CODEBASE_RAG_INDEX_DIR": str(index_dir.resolve()), @@ -184,7 +184,7 @@ def lance_index_capability_smoke(tmp_path_factory) -> Path: assert proc.returncode == 0, ( f"cocoindex failed: stdout={proc.stdout}\nstderr={proc.stderr}" ) - builder = bundle_dir / "build_ast_graph.py" + builder = bundle_dir / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" proc = subprocess.run( [ sys.executable, @@ -207,12 +207,12 @@ 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 ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph LadybugGraph._instance = None LadybugGraph._instance_path = None - from server import create_mcp_server + from java_codebase_rag.mcp.server import create_mcp_server server = create_mcp_server() @@ -240,12 +240,12 @@ 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 ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph LadybugGraph._instance = None LadybugGraph._instance_path = None - from server import create_mcp_server + from java_codebase_rag.mcp.server import create_mcp_server server = create_mcp_server() @@ -295,7 +295,7 @@ def test_lancedb_ignore_file_reduces_indexed_java_files(tmp_path_factory) -> Non def run_coco(corpus: Path) -> Path: index_dir = corpus / ".java-codebase-rag" index_dir.mkdir(parents=True) - app_spec = _cocoindex_flow_specifier(bundle_dir, corpus) + app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", corpus) env = { **os.environ, "JAVA_CODEBASE_RAG_INDEX_DIR": str(index_dir.resolve()), @@ -328,12 +328,12 @@ 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 ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph LadybugGraph._instance = None LadybugGraph._instance_path = None - from server import create_mcp_server + from java_codebase_rag.mcp.server import create_mcp_server server = create_mcp_server() @@ -360,7 +360,7 @@ def test_layered_ignore_provided_once_per_flow() -> None: the process boundary to instrument LayeredIgnore.__init__. """ bundle_dir = Path(__file__).resolve().parent.parent - flow_file = bundle_dir / "java_index_flow_lancedb.py" + flow_file = bundle_dir / "src" / "java_codebase_rag" / "index" / "java_index_flow_lancedb.py" if not flow_file.is_file(): pytest.skip(f"Flow file not found: {flow_file}") diff --git a/tests/test_lancedb_generated_column.py b/tests/test_lancedb_generated_column.py index 1b069031..a0d774b0 100644 --- a/tests/test_lancedb_generated_column.py +++ b/tests/test_lancedb_generated_column.py @@ -66,7 +66,7 @@ def test_lance_chunk_generated_columns( bundle_dir = Path(__file__).resolve().parent.parent # Get the flow specifier - app_spec = _cocoindex_flow_specifier(bundle_dir, FIXTURE_ROOT) + app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", FIXTURE_ROOT) # Locate cocoindex binary import sys diff --git a/tests/test_mcp_hints.py b/tests/test_mcp_hints.py index e4914f7c..c366cc79 100644 --- a/tests/test_mcp_hints.py +++ b/tests/test_mcp_hints.py @@ -7,14 +7,14 @@ import pytest from _builders import build_ladybug_to -from java_ontology import FUZZY_STRATEGY_SET -from ladybug_queries import LadybugGraph -from mcp_hints import ( +from java_codebase_rag.graph.java_ontology import FUZZY_STRATEGY_SET +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.mcp.mcp_hints import ( _StructuredHint, finalize_structured_hints, generate_hints, ) -from mcp_v2 import ( +from java_codebase_rag.mcp.mcp_v2 import ( FindOutput, SearchOutput, _hints_or_skip, @@ -1245,7 +1245,7 @@ def test_structured_hints_reason_char_cap() -> None: def test_no_string_hints_field() -> None: """Verify no output model has hints field.""" - from mcp_v2 import ( + from java_codebase_rag.mcp.mcp_v2 import ( DescribeOutput, NeighborsOutput, ResolveOutput, diff --git a/tests/test_mcp_server_project_root.py b/tests/test_mcp_server_project_root.py index fb35bc80..158b1c8b 100644 --- a/tests/test_mcp_server_project_root.py +++ b/tests/test_mcp_server_project_root.py @@ -23,7 +23,7 @@ def test_project_root_uses_discover_when_env_unset(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) # Import _project_root after setting up the environment - from server import _project_root + from java_codebase_rag.mcp.server import _project_root result = _project_root() assert result == tmp_path @@ -43,7 +43,7 @@ class TestSourceRootForOperatorConfig: def test_returns_none_when_env_unset(self, tmp_path, monkeypatch): monkeypatch.delenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", raising=False) - from server import _source_root_for_operator_config + from java_codebase_rag.mcp.server import _source_root_for_operator_config assert _source_root_for_operator_config() is None @@ -51,7 +51,7 @@ def test_returns_env_path_when_set(self, tmp_path, monkeypatch): explicit = tmp_path / "explicit-root" explicit.mkdir() monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(explicit)) - from server import _source_root_for_operator_config + from java_codebase_rag.mcp.server import _source_root_for_operator_config assert _source_root_for_operator_config() == explicit.resolve() @@ -73,7 +73,7 @@ def test_mcp_and_init_resolve_identically_for_nested_config(self, tmp_path, monk ) monkeypatch.chdir(config_dir) - from server import _source_root_for_operator_config + from java_codebase_rag.mcp.server import _source_root_for_operator_config mcp = resolve_operator_config(source_root=_source_root_for_operator_config()) cli = resolve_operator_config(source_root=None) @@ -111,7 +111,7 @@ def test_mcp_and_init_resolve_identically_via_pointer(self, tmp_path, monkeypatc microservice.mkdir(parents=True) monkeypatch.chdir(microservice) - from server import _source_root_for_operator_config + from java_codebase_rag.mcp.server import _source_root_for_operator_config mcp = resolve_operator_config(source_root=_source_root_for_operator_config()) cli = resolve_operator_config(source_root=None) diff --git a/tests/test_mcp_tools.py b/tests/test_mcp_tools.py index fabbac5d..6fedb9b5 100644 --- a/tests/test_mcp_tools.py +++ b/tests/test_mcp_tools.py @@ -84,7 +84,7 @@ async def test_tool_input_schema_includes_expected_enums(mcp_server) -> None: def test_cocoindex_subprocess_env_sets_project_root(monkeypatch, tmp_path) -> None: - import server + from java_codebase_rag.mcp import server monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", "/should/be/overwritten") monkeypatch.setenv("PRESERVE_ME_FOR_SUBPROCESS", "ok") @@ -103,7 +103,7 @@ def test_cocoindex_subprocess_env_applies_inflight_default(monkeypatch, tmp_path ``COCOINDEX_MAX_INFLIGHT_COMPONENTS`` (default 1024 -> capped to 256), not the non-existent ``COCOINDEX_SOURCE_MAX_INFLIGHT_ROWS`` from the broken #293 fix. """ - import server + from java_codebase_rag.mcp import server proj = tmp_path / "external-java-repo" proj.mkdir() diff --git a/tests/test_mcp_v2.py b/tests/test_mcp_v2.py index d4757b7d..593fa360 100644 --- a/tests/test_mcp_v2.py +++ b/tests/test_mcp_v2.py @@ -14,9 +14,9 @@ from pydantic import ValidationError from mcp.server.fastmcp.exceptions import ToolError -from java_ontology import VALID_RESOLVE_REASONS +from java_codebase_rag.graph.java_ontology import VALID_RESOLVE_REASONS -from mcp_v2 import ( +from java_codebase_rag.mcp.mcp_v2 import ( Edge, NodeFilter, _NODEFILTER_APPLICABLE_FIELDS, @@ -136,7 +136,7 @@ def _fake_search_rows() -> list[dict[str, Any]]: @needs_vectors 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()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", graph=ladybug_graph) assert out.success is True assert out.results @@ -145,7 +145,7 @@ def test_search_basic_returns_hits_with_symbol_id(monkeypatch, ladybug_graph) -> @needs_vectors def test_search_filter_microservice(monkeypatch, ladybug_graph) -> None: - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", filter={"microservice": "chat-assign"}, graph=ladybug_graph) assert out.success is True assert out.results @@ -154,7 +154,7 @@ def test_search_filter_microservice(monkeypatch, ladybug_graph) -> None: @needs_vectors def test_search_path_contains_filter(monkeypatch, ladybug_graph) -> None: - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatAssign", path_contains="ChatAssign", graph=ladybug_graph) assert out.success is True assert len(out.results) == 1 @@ -592,7 +592,7 @@ async def test_search_invalid_table_rejected(mcp_server) -> None: @needs_vectors def test_search_filter_accepts_json_string(monkeypatch, ladybug_graph) -> None: - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) want = {"microservice": "chat-assign"} out_dict = search_v2("ChatService", filter=want, graph=ladybug_graph) out_str = search_v2("ChatService", filter='{"microservice":"chat-assign"}', graph=ladybug_graph) @@ -603,7 +603,7 @@ def test_search_filter_accepts_json_string(monkeypatch, ladybug_graph) -> None: @needs_vectors def test_search_unknown_filter_key_returns_failure(monkeypatch, ladybug_graph) -> None: - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", filter={"typo_key": "x"}, graph=ladybug_graph) assert out.success is False assert out.message is not None @@ -643,7 +643,7 @@ def fake_run_search(query, **kwargs): captured.update(kwargs) return _fake_search_rows() - monkeypatch.setattr("mcp_v2.run_search", fake_run_search) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", fake_run_search) out = search_v2( "ChatService", filter={ @@ -689,7 +689,7 @@ def fake_run_search(query, *, hybrid, **kwargs): call_count["vector"] += 1 return _fake_search_rows() - monkeypatch.setattr("mcp_v2.run_search", fake_run_search) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", fake_run_search) out = search_v2("server port", table="yaml", hybrid=True, graph=ladybug_graph) # Should succeed with vector-only fallback @@ -714,7 +714,7 @@ def test_search_hybrid_non_fts_error_still_fails(monkeypatch, ladybug_graph) -> def fake_run_search(query, **kwargs): raise RuntimeError("Some other LanceDB error") - monkeypatch.setattr("mcp_v2.run_search", fake_run_search) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", fake_run_search) out = search_v2("query", table="java", hybrid=True, graph=ladybug_graph) # Should fail with the error message @@ -730,7 +730,7 @@ def test_unresolved_call_site_noderef_carries_callee_name() -> None: (issue #354) — NodeRef previously had no `name` field, so pydantic's default extra='ignore' silently dropped `name=callee`, leaving the structured ref with fqn='' and no human-readable callee (clients had to dig into attrs).""" - from mcp_v2 import _unresolved_site_to_edge + from java_codebase_rag.mcp.mcp_v2 import _unresolved_site_to_edge edge = _unresolved_site_to_edge( "origin:1", @@ -769,7 +769,7 @@ def test_neighbors_flat_labels_select_columns_per_edge_type() -> None: RETURNs only that type's columns (issue #356) — never a fixed superset that references columns absent on some matched type (the typed-union RETURN anti-pattern that errors on stricter binders like Kuzu).""" - from mcp_v2 import _FLAT_EDGE_ATTR_COLUMNS + from java_codebase_rag.mcp.mcp_v2 import _FLAT_EDGE_ATTR_COLUMNS issued: list[tuple[str, dict[str, Any]]] = [] @@ -800,7 +800,7 @@ def _rows(self, query: str, params: dict[str, Any] | None = None) -> list[dict[s @needs_vectors def test_search_cross_kind_filter_returns_failure(monkeypatch, ladybug_graph) -> None: - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", filter={"path_contains": "/api"}, graph=ladybug_graph) assert out.success is False assert out.message is not None @@ -810,7 +810,7 @@ def test_search_cross_kind_filter_returns_failure(monkeypatch, ladybug_graph) -> @needs_vectors 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()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) baseline = search_v2("ChatService", graph=ladybug_graph) empty = search_v2("ChatService", filter="", graph=ladybug_graph) whitespace = search_v2("ChatService", filter=" ", graph=ladybug_graph) @@ -822,7 +822,7 @@ def test_search_filter_empty_string_treated_as_none(monkeypatch, ladybug_graph) @needs_vectors 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()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) baseline = search_v2("ChatService", graph=ladybug_graph) out = search_v2("ChatService", filter="null", graph=ladybug_graph) assert baseline.success is True @@ -916,7 +916,7 @@ def test_neighbors_validate_call_still_raises(ladybug_graph) -> None: @needs_vectors def test_filter_invalid_json_returns_failure(monkeypatch, ladybug_graph) -> None: - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", filter="{not json", graph=ladybug_graph) assert out.success is False assert out.message is not None @@ -997,7 +997,7 @@ def edge_counts_for(self, node_id: str) -> dict[str, dict[str, int]]: def test_server_tool_descriptions_no_pre_resolve_fallback() -> None: - from server import _INSTRUCTIONS, create_mcp_server + from java_codebase_rag.mcp.server import _INSTRUCTIONS, create_mcp_server async def _run() -> None: mcp = create_mcp_server() @@ -1258,7 +1258,7 @@ def test_resolve_route_template_returns_one_or_many(ladybug_graph_route_extracti def test_resolve_client_target_service(ladybug_graph, ladybug_db_path_http_caller_smoke) -> None: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph graph = ladybug_graph rows = graph.list_clients(limit=500) @@ -1280,7 +1280,7 @@ def test_resolve_client_target_service(ladybug_graph, ladybug_db_path_http_calle def test_resolve_client_target_path_pair(ladybug_graph, ladybug_db_path_http_caller_smoke) -> None: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph def _seed_client(g: LadybugGraph) -> dict | None: rows = g.list_clients(limit=500) @@ -1334,7 +1334,7 @@ def test_resolve_wildcard_identifier_rejected(ladybug_graph) -> None: def test_resolve_every_reason_in_closed_set_appears() -> None: - from resolve_service import ( + from java_codebase_rag.analysis.resolve_service import ( _resolve_client_candidates, _resolve_producer_candidates, _resolve_route_candidates, @@ -1833,7 +1833,7 @@ def test_describe_unresolved_call_sites_rollup_cap_footer_and_total(ladybug_grap def test_search_hit_has_score_components_field() -> None: """SearchHit model includes score_components field (default None).""" - from mcp_v2 import SearchHit + from java_codebase_rag.mcp.mcp_v2 import SearchHit hit = SearchHit( chunk_id="chunk:1", symbol_id="sym:1", @@ -1869,7 +1869,7 @@ def fake_rows_with_components(): } ] - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: fake_rows_with_components()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: fake_rows_with_components()) out = search_v2("ChatService", explain=True, graph=ladybug_graph) assert out.success is True assert out.results @@ -1906,7 +1906,7 @@ def fake_rows_with_components(): } ] - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: fake_rows_with_components()) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: fake_rows_with_components()) # Test with explain=False explicitly out = search_v2("ChatService", explain=False, graph=ladybug_graph) assert out.success is True @@ -1950,7 +1950,7 @@ def mock_run_search(query, **kwargs): } ] - monkeypatch.setattr("mcp_v2.run_search", mock_run_search) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", mock_run_search) out = search_v2("TypeA", graph=ladybug_graph) assert out.success is True @@ -1980,7 +1980,7 @@ def mock_run_search(query, **kwargs): } ] - monkeypatch.setattr("mcp_v2.run_search", mock_run_search) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", mock_run_search) out = search_v2("TypeA", graph=ladybug_graph) assert out.success is True @@ -2015,7 +2015,7 @@ def mock_run_search(query, **kwargs): } ] - monkeypatch.setattr("mcp_v2.run_search", mock_run_search) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", mock_run_search) out = search_v2("TypeA", dedup=False, graph=ladybug_graph) assert out.success is True diff --git a/tests/test_mcp_v2_compose.py b/tests/test_mcp_v2_compose.py index 91d36b20..accfa66d 100644 --- a/tests/test_mcp_v2_compose.py +++ b/tests/test_mcp_v2_compose.py @@ -7,15 +7,15 @@ import pytest from _builders import build_ladybug_to -from ladybug_queries import LadybugGraph -from mcp_v2 import ( +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.mcp.mcp_v2 import ( _NEIGHBOR_EDGE_TYPES_ADAPTER, _TYPE_SYMBOL_KINDS_FOR_EDGE_ROLLUP, describe_v2, neighbors_v2, search_v2, ) -from server import _graph_meta_output +from java_codebase_rag.mcp.server import _graph_meta_output def _vector_stack_available() -> bool: @@ -242,7 +242,7 @@ def test_search_populates_symbol_id_when_chunk_rooted_in_symbol(monkeypatch, lad "text": "raw", }, ] - monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: rows) + monkeypatch.setattr("java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: rows) 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] @@ -268,7 +268,7 @@ def test_search_describe_neighbors_chain_end_to_end(ladybug_graph, monkeypatch) assert rows row = rows[0] monkeypatch.setattr( - "mcp_v2.run_search", + "java_codebase_rag.mcp.mcp_v2.run_search", lambda *args, **kwargs: [ { "filename": str(row.get("filename") or "x.java"), @@ -979,7 +979,7 @@ def edge_pairs(db_path: Path) -> list[tuple[str, str]]: def test_describe_client_edge_summary_includes_http_calls_out( ladybug_db_path_cross_service_smoke: Path, ) -> None: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) rows = g._rows( # noqa: SLF001 diff --git a/tests/test_meta_chain_core.py b/tests/test_meta_chain_core.py index 1690c9dd..281574c8 100644 --- a/tests/test_meta_chain_core.py +++ b/tests/test_meta_chain_core.py @@ -1,7 +1,7 @@ """Unit tests for iterative `_build_meta_chain` and `collect_annotation_meta_chain`.""" from __future__ import annotations -from graph_enrich import ( +from java_codebase_rag.graph.graph_enrich import ( AnnotationDecl, _build_meta_chain, _meta_builtins, diff --git a/tests/test_microservice_scope.py b/tests/test_microservice_scope.py index 01b14185..da7d48b8 100644 --- a/tests/test_microservice_scope.py +++ b/tests/test_microservice_scope.py @@ -1,6 +1,6 @@ """Tests for microservice scope detection and ScopeManager.""" -from graph_enrich import detect_microservice_from_path +from java_codebase_rag.graph.graph_enrich import detect_microservice_from_path class TestDetectMicroserviceFromPath: @@ -69,7 +69,7 @@ def test_apply_scope_when_filter_none(self, tmp_path): ms_dir.mkdir(parents=True, exist_ok=True) (ms_dir / "pom.xml").write_text("") - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager mgr = ScopeManager(tmp_path) mgr.default_scope = "microservice-a" # Simulate detection @@ -79,11 +79,11 @@ def test_apply_scope_when_filter_none(self, tmp_path): def test_apply_scope_when_filter_exists_no_microservice(self, tmp_path): """Filter without microservice gets auto-scope injected.""" - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager mgr = ScopeManager(tmp_path) mgr.default_scope = "microservice-b" # Simulate detection - from mcp_v2 import NodeFilter + from java_codebase_rag.mcp.mcp_v2 import NodeFilter result = mgr.apply_auto_scope(NodeFilter(role="CONTROLLER")) assert result is not None assert result.role == "CONTROLLER" @@ -91,22 +91,22 @@ def test_apply_scope_when_filter_exists_no_microservice(self, tmp_path): def test_apply_scope_preserves_explicit_microservice(self, tmp_path): """Explicit microservice not overridden.""" - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager mgr = ScopeManager(tmp_path) mgr.default_scope = "microservice-a" # Simulate detection - from mcp_v2 import NodeFilter + from java_codebase_rag.mcp.mcp_v2 import NodeFilter result = mgr.apply_auto_scope(NodeFilter(microservice="microservice-c")) assert result is not None assert result.microservice == "microservice-c" def test_apply_scope_no_default(self, tmp_path): """No auto-detected scope leaves filter unchanged.""" - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager mgr = ScopeManager(tmp_path) mgr.default_scope = None # No detection - from mcp_v2 import NodeFilter + from java_codebase_rag.mcp.mcp_v2 import NodeFilter nf = NodeFilter(role="CONTROLLER") result = mgr.apply_auto_scope(nf) assert result is nf @@ -123,7 +123,7 @@ def test_detect_scope_with_yaml_overrides(self, tmp_path): custom_ms_dir.mkdir() # Even without a build marker, the YAML override should detect this as a microservice - from graph_enrich import detect_microservice_from_path + from java_codebase_rag.graph.graph_enrich import detect_microservice_from_path result = detect_microservice_from_path(custom_ms_dir, tmp_path) # Should detect the microservice based on YAML override @@ -137,7 +137,7 @@ def test_detect_scope_integration(self, tmp_path): (ms_dir / "pom.xml").write_text("") # Create a ScopeManager with real detection (no manual override) - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager mgr = ScopeManager(tmp_path) # The detection should have found the microservice @@ -146,7 +146,7 @@ def test_detect_scope_integration(self, tmp_path): assert mgr.default_scope is None # Test that apply_auto_scope doesn't inject when no scope detected - from mcp_v2 import NodeFilter + from java_codebase_rag.mcp.mcp_v2 import NodeFilter nf = NodeFilter(role="CONTROLLER") result = mgr.apply_auto_scope(nf) assert result is nf @@ -167,7 +167,7 @@ class TestScopeManagerAutoScopeValidation: @staticmethod def _stub_index(monkeypatch, microservices: set[str]) -> None: """Make ScopeManager._indexed_microservices() see a fake graph.""" - import server + from java_codebase_rag.mcp import server class _FakeGraph: def microservice_counts(self): @@ -182,7 +182,7 @@ def microservice_counts(self): def test_context_dir_not_detected_as_microservice(self, tmp_path, monkeypatch): """Launching from a codeless context dir must NOT auto-scope (the bug).""" - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager # Reported layout: source_root holds both the context dir and a real # microservice; the server is launched from the context dir. @@ -201,7 +201,7 @@ def test_context_dir_not_detected_as_microservice(self, tmp_path, monkeypatch): def test_real_microservice_dir_still_scopes(self, tmp_path, monkeypatch): """Launching from inside an indexed microservice keeps auto-scope.""" - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager ms_dir = tmp_path / "microservice-a" (ms_dir / "src").mkdir(parents=True) @@ -215,7 +215,7 @@ def test_real_microservice_dir_still_scopes(self, tmp_path, monkeypatch): def test_empty_index_keeps_detection(self, tmp_path, monkeypatch): """When the index is missing (exists()->False), keep detection.""" - from server import ScopeManager + from java_codebase_rag.mcp.server import ScopeManager ms_dir = tmp_path / "microservice-a" ms_dir.mkdir() @@ -236,8 +236,8 @@ def test_empty_graph_present_keeps_detection(self, tmp_path, monkeypatch): paths must converge to keeping the detected scope rather than silently disabling auto-scope. """ - import server - from server import ScopeManager + from java_codebase_rag.mcp import server + from java_codebase_rag.mcp.server import ScopeManager ms_dir = tmp_path / "microservice-a" ms_dir.mkdir() @@ -256,8 +256,8 @@ def microservice_counts(self): def test_indexed_microservices_extracts_nonempty_keys(self, tmp_path, monkeypatch): """_indexed_microservices drops empty-string buckets, keeps the rest.""" - import server - from server import ScopeManager + from java_codebase_rag.mcp import server + from java_codebase_rag.mcp.server import ScopeManager class _FakeGraph: def microservice_counts(self): diff --git a/tests/test_outgoing_call_extraction.py b/tests/test_outgoing_call_extraction.py index dc4bcda1..28437ee4 100644 --- a/tests/test_outgoing_call_extraction.py +++ b/tests/test_outgoing_call_extraction.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ast_java import parse_java +from java_codebase_rag.ast.ast_java import parse_java def _calls(src: str, *, type_name: str, method_name: str): diff --git a/tests/test_path_filtering.py b/tests/test_path_filtering.py index f44640a4..14aba730 100644 --- a/tests/test_path_filtering.py +++ b/tests/test_path_filtering.py @@ -4,7 +4,7 @@ import warnings from pathlib import Path -from path_filtering import ( +from java_codebase_rag.graph.path_filtering import ( COMMON_EXCLUDED_PATH_PATTERNS, LayeredIgnore, compile_excluded_glob_patterns, diff --git a/tests/test_pr_analysis.py b/tests/test_pr_analysis.py index 3f0e4440..e36c5798 100644 --- a/tests/test_pr_analysis.py +++ b/tests/test_pr_analysis.py @@ -4,8 +4,8 @@ from pathlib import Path from types import SimpleNamespace -from ladybug_queries import find_symbols_in_file_range -from pr_analysis import ( +from java_codebase_rag.graph.ladybug_queries import find_symbols_in_file_range +from java_codebase_rag.analysis.pr_analysis import ( DiffHunk, analyze_pr_pipeline, compute_risk, @@ -72,7 +72,7 @@ def test_33_map_hunks_to_symbols_chat_management_service_assign(ladybug_graph) - def test_34_compute_risk_leaf_private_method_low_blast(ladybug_graph) -> None: - from pr_analysis import ChangedSymbol + from java_codebase_rag.analysis.pr_analysis import ChangedSymbol sym = find_symbols_in_file_range( ladybug_graph, @@ -102,7 +102,7 @@ def test_35_compute_risk_controller_route_and_high_band_when_saturated( monkeypatch, ladybug_graph, ) -> None: """Fixture corpus: controller diff surfaces EXPOSES routes; `high` needs saturated metrics.""" - from pr_analysis import ChangedSymbol + from java_codebase_rag.analysis.pr_analysis import ChangedSymbol diff = """diff --git a/chat-assign/src/main/java/com/bank/chat/assign/web/ChatManagementController.java b/chat-assign/src/main/java/com/bank/chat/assign/web/ChatManagementController.java --- a/chat-assign/src/main/java/com/bank/chat/assign/web/ChatManagementController.java @@ -168,7 +168,7 @@ def fake_fc(self, name, **kwargs): def test_35a_compute_risk_cross_service_bonus_saturates_to_one(monkeypatch) -> None: - from pr_analysis import ChangedSymbol + from java_codebase_rag.analysis.pr_analysis import ChangedSymbol class _FakeGraph: def _rows(self, query, params): @@ -208,7 +208,7 @@ def find_callers(self, name, **kwargs): del name, kwargs return [] - monkeypatch.setattr("pr_analysis._route_ids_for_symbol", lambda graph, sid: ["route-1"]) + monkeypatch.setattr("java_codebase_rag.analysis.pr_analysis._route_ids_for_symbol", lambda graph, sid: ["route-1"]) rep = compute_risk( _FakeGraph(), [ @@ -226,7 +226,7 @@ def find_callers(self, name, **kwargs): def test_35b_compute_risk_single_cross_service_bonus_is_point_two(monkeypatch) -> None: - from pr_analysis import ChangedSymbol + from java_codebase_rag.analysis.pr_analysis import ChangedSymbol class _FakeGraph: def __init__(self, *, include_callers: bool) -> None: @@ -271,7 +271,7 @@ def find_callers(self, name, **kwargs): del name, kwargs return [] - monkeypatch.setattr("pr_analysis._route_ids_for_symbol", lambda graph, sid: ["route-1"]) + monkeypatch.setattr("java_codebase_rag.analysis.pr_analysis._route_ids_for_symbol", lambda graph, sid: ["route-1"]) rep = compute_risk( _FakeGraph(include_callers=True), [ @@ -306,9 +306,9 @@ def find_callers(self, name, **kwargs): def test_pr_analysis_changed_methods_finds_routes_via_declares_client( ladybug_db_path_cross_service_smoke: Path, ) -> None: - from ladybug_queries import LadybugGraph + from java_codebase_rag.graph.ladybug_queries import LadybugGraph - from pr_analysis import ChangedSymbol, compute_risk + from java_codebase_rag.analysis.pr_analysis import ChangedSymbol, compute_risk g = LadybugGraph(str(ladybug_db_path_cross_service_smoke)) route_rows = g._rows( # noqa: SLF001 diff --git a/tests/test_resolve_routes_messaging_layer_c.py b/tests/test_resolve_routes_messaging_layer_c.py index 7586900c..bd6acf4a 100644 --- a/tests/test_resolve_routes_messaging_layer_c.py +++ b/tests/test_resolve_routes_messaging_layer_c.py @@ -1,9 +1,9 @@ """Layer C `@CodebaseAsyncRoute` replaces same-method `@KafkaListener` auto routes.""" from __future__ import annotations -from graph_enrich import BrownfieldOverrides, resolve_routes_for_method +from java_codebase_rag.graph.graph_enrich import BrownfieldOverrides, resolve_routes_for_method -from ast_java import parse_java +from java_codebase_rag.ast.ast_java import parse_java def _type_and_method(src: str): diff --git a/tests/test_resolve_service.py b/tests/test_resolve_service.py index 55bcff4e..03ebbb48 100644 --- a/tests/test_resolve_service.py +++ b/tests/test_resolve_service.py @@ -8,8 +8,8 @@ """ from pathlib import Path -from ladybug_queries import LadybugGraph -from resolve_service import ResolveCandidate, ResolveOutput, ResolveStatus, resolve_v2 +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.analysis.resolve_service import ResolveCandidate, ResolveOutput, ResolveStatus, resolve_v2 # Known bank-chat fixture symbol (verified via test_jrag_locate.test_find_by_fqn_exact). _KNOWN_CLASS_FQN = "com.bank.chat.assign.ChatAssignApplication" diff --git a/tests/test_route_extraction.py b/tests/test_route_extraction.py index f1b19f6e..37a5c20f 100644 --- a/tests/test_route_extraction.py +++ b/tests/test_route_extraction.py @@ -5,8 +5,8 @@ import json from contextlib import redirect_stderr -from ast_java import parse_java -from build_ast_graph import _normalize_path, _route_id +from java_codebase_rag.ast.ast_java import parse_java +from java_codebase_rag.graph.build_ast_graph import _normalize_path, _route_id def _routes(src: str, *, filename: str = "Smoke.java") -> list: diff --git a/tests/test_schema_consistency.py b/tests/test_schema_consistency.py index 74880133..1aabeb2c 100644 --- a/tests/test_schema_consistency.py +++ b/tests/test_schema_consistency.py @@ -8,10 +8,10 @@ import re from pathlib import Path -from java_ontology import BROWNFIELD_RESOLVER_STRATEGY_SET, EDGE_SCHEMA +from java_codebase_rag.graph.java_ontology import BROWNFIELD_RESOLVER_STRATEGY_SET, EDGE_SCHEMA _REPO_ROOT = Path(__file__).resolve().parent.parent -_BUILD_AST_GRAPH = _REPO_ROOT / "build_ast_graph.py" +_BUILD_AST_GRAPH = _REPO_ROOT / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" _REL_DDL_RE = re.compile( r'CREATE REL TABLE (\w+)\(FROM (\w+) TO (\w+)', @@ -20,9 +20,9 @@ r"""(?:strategy|resolution_strategy|edge_strat)\s*=\s*["']([a-z_]+)["']""", ) _EMITTER_FILES = ( - "build_ast_graph.py", - "graph_enrich.py", - "ast_java.py", + "src/java_codebase_rag/graph/build_ast_graph.py", + "src/java_codebase_rag/graph/graph_enrich.py", + "src/java_codebase_rag/ast/ast_java.py", ) # Stored graph rels that are not MCP ``EdgeType`` / ``EDGE_SCHEMA`` entries. diff --git a/tests/test_search_lancedb.py b/tests/test_search_lancedb.py index db037606..aea1c0f2 100644 --- a/tests/test_search_lancedb.py +++ b/tests/test_search_lancedb.py @@ -10,8 +10,8 @@ pytest.importorskip("lancedb") pytest.importorskip("sentence_transformers") -import search_lancedb -from search_lancedb import JAVA_ENRICHED_COLUMNS, _rrf_merge +from java_codebase_rag.search import search_lancedb +from java_codebase_rag.search.search_lancedb import JAVA_ENRICHED_COLUMNS, _rrf_merge def test_rrf_merge_weights_second_list_by_row() -> None: @@ -126,7 +126,7 @@ def test_vector_displayed_score_is_rank_monotonic() -> None: The honest score uses the adjusted distance (distance + import_penalty - role_weight - symbol_bonus). This matches the sort key, so the displayed score is monotonic. After clamping, scores are in [0,1]. """ - from search_lancedb import _effective_distance, l2_distance_to_score, _clamp01 + from java_codebase_rag.search.search_lancedb import _effective_distance, l2_distance_to_score, _clamp01 # Build controlled rows with varying distances and bonuses rows = [ @@ -208,7 +208,7 @@ def test_hybrid_score_normalized_to_unit_range() -> None: The composite sort metric (raw_rrf * import_factor + role_weight + symbol_bonus) must produce displayed scores that are non-increasing with rank. """ - from search_lancedb import _hybrid_sort_key, _hybrid_post_sort_normalization + from java_codebase_rag.search.search_lancedb import _hybrid_sort_key, _hybrid_post_sort_normalization # Build controlled hybrid rows with varying raw scores and components. # Row 2 should rank highest due to large role_weight despite lower raw RRF. @@ -285,7 +285,7 @@ def test_hybrid_import_penalty_rendered_as_additive_penalty() -> None: The normalization function uses the constant directly, so changing the stored value is safe and doesn't affect the actual hybrid score. """ - from search_lancedb import _hybrid_sort_key, explain_score_components + from java_codebase_rag.search.search_lancedb import _hybrid_sort_key, explain_score_components # Create a row with import_heavy hint row = { @@ -324,7 +324,7 @@ def test_run_search_dedup_collapses_by_fqn() -> None: First-seen-wins (rows are pre-sorted, so first is best chunk). Each survivor gets _chunks_collapsed count (>=1). """ - from search_lancedb import _dedup_by_fqn + from java_codebase_rag.search.search_lancedb import _dedup_by_fqn # Build controlled sorted rows: 3 rows FQN=A (best first), 1 row FQN=B, 2 rows FQN=C rows = [ @@ -399,7 +399,7 @@ def test_run_search_dedup_offset_pagination() -> None: With limit=5, offset=5, dedup_by_fqn=True → should return FQNs #6..#10 (5 unique). Over-fetch (4x) ensures we fetch enough to fill the page after dedup. """ - from search_lancedb import _dedup_by_fqn + from java_codebase_rag.search.search_lancedb import _dedup_by_fqn # Build 8 FQNs × 3 chunks each = 24 rows, pre-sorted by FQN then score rows = [] @@ -456,7 +456,7 @@ def test_run_search_dedup_passes_through_sql_yaml() -> None: Each row without primary_type_fqn gets a unique dedup key __id: so they pass through unchanged. """ - from search_lancedb import _dedup_by_fqn + from java_codebase_rag.search.search_lancedb import _dedup_by_fqn rows = [ { @@ -521,7 +521,7 @@ def test_run_search_dedup_off_is_byte_identical() -> None: The non-dedup path should be byte-identical to today's behavior. This test ensures we don't accidentally change behavior when dedup is off. """ - from search_lancedb import _dedup_by_fqn + from java_codebase_rag.search.search_lancedb import _dedup_by_fqn rows = [ { diff --git a/tests/test_search_lancedb_capability.py b/tests/test_search_lancedb_capability.py index d6edc4e1..1068919e 100644 --- a/tests/test_search_lancedb_capability.py +++ b/tests/test_search_lancedb_capability.py @@ -11,9 +11,9 @@ from sentence_transformers import SentenceTransformer -from ast_java import ONTOLOGY_VERSION -from index_common import SBERT_MODEL -from search_lancedb import TABLES, _query_vector, run_search +from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION +from java_codebase_rag.search.index_common import SBERT_MODEL +from java_codebase_rag.search.search_lancedb import TABLES, _query_vector, run_search def _one_java_row_built_for_capability_filter( diff --git a/tests/test_search_lexical.py b/tests/test_search_lexical.py index e14f21db..ededbaa7 100644 --- a/tests/test_search_lexical.py +++ b/tests/test_search_lexical.py @@ -15,10 +15,10 @@ import pytest -from ladybug_queries import LadybugGraph -from mcp_v2 import NodeFilter, _node_matches_filter, _symbol_where_from_filter -from search_lexical import _lexical_where, _read_snippet, run_lexical_search -from search_scoring import explain_score_components +from java_codebase_rag.graph.ladybug_queries import LadybugGraph +from java_codebase_rag.mcp.mcp_v2 import NodeFilter, _node_matches_filter, _symbol_where_from_filter +from java_codebase_rag.search.search_lexical import _lexical_where, _read_snippet, run_lexical_search +from java_codebase_rag.search.search_scoring import explain_score_components def _build_corpus(tmp_path: Path) -> Path: @@ -212,7 +212,7 @@ def _spy(cypher, params): # capture the LIMIT param the backend sends to the gr g._rows = _spy # type: ignore[method-assign] - from search_lexical import _CANDIDATE_LIMIT_CAP + from java_codebase_rag.search.search_lexical import _CANDIDATE_LIMIT_CAP # Default page and a larger page must BOTH request the same cap-sized fetch — the # buggy formula would have produced 20 (limit=5) and 80 (limit=20). @@ -229,7 +229,7 @@ def test_cap_truncation_advisory_fires_at_cap(monkeypatch, tmp_path: Path) -> No """Correctness review: when the fetch hits the safety cap, deeper matches are never ranked (the scan has no ORDER BY). Surface an advisory instead of silently returning a storage-order-dependent subset. Lower the cap so a small fixture triggers it.""" - import search_lexical + from java_codebase_rag.search import search_lexical db = _build_corpus(tmp_path) g = _graph(db) @@ -276,8 +276,8 @@ def test_explain_import_survives_graph_only_env() -> None: that the CLI source imports from search_scoring AND the invariant: blocking the vector stack makes search_lancedb unimportable while search_scoring (and the lexical explain renderer) stays importable.""" - jrag_py = Path(__file__).resolve().parent.parent / "java_codebase_rag" / "jrag.py" - m = re.search(r"from (search_\w+) import explain_score_components", jrag_py.read_text(encoding="utf-8")) + jrag_py = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "jrag.py" + m = re.search(r"from java_codebase_rag\.search\.(search_\w+) import explain_score_components", jrag_py.read_text(encoding="utf-8")) assert m, "explain_score_components import not found in jrag.py" assert m.group(1) == "search_scoring", ( f"jrag.py imports explain_score_components from {m.group(1)!r}; it MUST be " @@ -292,11 +292,11 @@ def test_explain_import_survives_graph_only_env() -> None: "for m in ('lancedb','pylance','torch','sentence_transformers','cocoindex'):\n" " sys.modules[m] = None\n" "try:\n" - " import search_lancedb\n" + " from java_codebase_rag.search import search_lancedb\n" " print('LANCEDB:imported')\n" "except ModuleNotFoundError:\n" " print('LANCEDB:blocked')\n" - "from search_scoring import explain_score_components\n" + "from java_codebase_rag.search.search_scoring import explain_score_components\n" "print('SCORING:ok')\n" "r = explain_score_components(" "{'name_match':1.0,'type_match':0.1,'fqn_match':0.2," @@ -321,7 +321,7 @@ def test_search_v2_lexical_dispatch_end_to_end(monkeypatch, tmp_path: Path) -> N converges on the shared row->hit loop, sets lexical_mode, emits the graph-only mode advisory, and that SearchHit carries the expected fqn — none of which the direct run_lexical_search unit tests reach. Also covers --explain score_components end-to-end.""" - import mcp_v2 + from java_codebase_rag.mcp import mcp_v2 db = _build_corpus(tmp_path) g = _graph(db) diff --git a/tests/test_string_value_atoms.py b/tests/test_string_value_atoms.py index 08b1b28e..e68f348f 100644 --- a/tests/test_string_value_atoms.py +++ b/tests/test_string_value_atoms.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ast_java import parse_java +from java_codebase_rag.ast.ast_java import parse_java def test_string_value_atoms_renamed_call_sites_still_work() -> None: diff --git a/tests/test_vectors_progress.py b/tests/test_vectors_progress.py index 8c9a7545..4ccebbf4 100644 --- a/tests/test_vectors_progress.py +++ b/tests/test_vectors_progress.py @@ -162,7 +162,7 @@ def test_cli_reprocess_optimize_phase_progress( import contextlib from java_codebase_rag import cli as cli_mod - import server + from java_codebase_rag.mcp import server idx = tmp_path / "idx_optimize_prog" idx.mkdir() @@ -175,7 +175,7 @@ async def _fake_refresh(*, quiet=False, verbose=True, on_progress=None, on_progr if on_progress is not None: on_progress(ProgressEvent(kind="optimize", phase=None, pass_=None, done=None, total=None, status="running", elapsed_s=None)) on_progress(ProgressEvent(kind="optimize", phase=None, pass_=None, done=None, total=None, status="done", elapsed_s=1.2)) - from server import RefreshIndexOutput + from java_codebase_rag.mcp.server import RefreshIndexOutput return RefreshIndexOutput(success=True, message=None, phases_run=["vectors", "graph"]) @@ -212,12 +212,12 @@ def test_spinner_removed_and_emit_vectors_helpers_removed() -> None: # And no remaining references anywhere in the production tree. repo_root = Path(__file__).resolve().parent.parent offenders: list[str] = [] - for py in (repo_root / "java_codebase_rag").rglob("*.py"): + for py in (repo_root / "src" / "java_codebase_rag").rglob("*.py"): text = py.read_text(encoding="utf-8") # Word-boundary match for the retired Spinner class (not rich's SpinnerColumn). if re.search(r"\bSpinner\b", text): offenders.append(str(py)) - server_py = repo_root / "server.py" + server_py = repo_root / "src" / "java_codebase_rag" / "mcp" / "server.py" if server_py.is_file(): text = server_py.read_text(encoding="utf-8") if re.search(r"\bSpinner\b", text) or "emit_vectors_start" in text or "emit_vectors_finish" in text: @@ -254,7 +254,7 @@ def _run_cocoindex_update(corpus_root: Path, index_dir: Path) -> subprocess.Comp if not cocoindex_bin.is_file(): pytest.skip(f"cocoindex not installed in venv: {cocoindex_bin}") bundle_dir = Path(__file__).resolve().parent.parent - flow = (bundle_dir / "java_index_flow_lancedb.py").resolve() + flow = (bundle_dir / "src" / "java_codebase_rag" / "index" / "java_index_flow_lancedb.py").resolve() start = Path(corpus_root).resolve() relp = os.path.relpath(str(flow), start=str(start)) relp = Path(relp).as_posix() From d7b4656bd5927898de6b17178bfce57e57f42e64 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Fri, 10 Jul 2026 01:16:40 +0300 Subject: [PATCH 2/2] refactor(tests): reorganize tests/ into layer subfolders mirroring src/ Phase 2 of the src-layout reorganization. - Move 81 test files into tests/{ast,graph,absence,search,index,mcp,analysis, package,integration,_meta}/ mirroring the src/ layer structure. conftest.py, _builders.py, pinned_ids.py stay at tests/ root (filenames unchanged). - pytest.ini: pythonpath = src tests so _builders/_pinned_ids resolve from subfolders under pytest's prepend import mode. - Bump Path(__file__)-rooted path depth by +1 in 34 moved test files (moving a file down one dir means every absolute target is one .parent further; .parents[N] index bumped +1 too). Provably correct for __file__-rooted paths; conftest at root left untouched. cwd-relative strings and tmp_path unchanged. Full suite (CI-equivalent): 1293 passed, 14 skipped, 0 failed. Co-Authored-By: Claude --- pytest.ini | 2 +- tests/{ => _meta}/test_agent_skills_static.py | 6 +++--- tests/{ => _meta}/test_edge_navigation_doc.py | 2 +- tests/{ => _meta}/test_packaging_metadata.py | 2 +- tests/{ => absence}/test_absence_diagnosis.py | 0 tests/{ => absence}/test_absence_mcp_integration.py | 0 tests/{ => absence}/test_absence_types.py | 0 tests/{ => absence}/test_absence_vocab.py | 0 tests/{ => analysis}/test_jrag_envelope.py | 0 tests/{ => analysis}/test_jrag_envelope_absence.py | 0 tests/{ => analysis}/test_pr_analysis.py | 0 tests/{ => analysis}/test_resolve_service.py | 0 tests/{ => ast}/test_ast_java_calls.py | 6 +++--- tests/{ => ast}/test_ast_java_capabilities.py | 0 tests/{ => ast}/test_ast_java_thread_safety.py | 0 tests/{ => ast}/test_brownfield_events.py | 0 tests/{ => ast}/test_outgoing_call_extraction.py | 0 tests/{ => ast}/test_string_value_atoms.py | 0 .../test_assign_endpoint_client_extraction.py | 2 +- tests/{ => graph}/test_brownfield_clients.py | 2 +- tests/{ => graph}/test_brownfield_overrides.py | 0 tests/{ => graph}/test_brownfield_routes.py | 2 +- tests/{ => graph}/test_call_edge_matching.py | 2 +- .../test_call_graph_receiver_resolution.py | 0 tests/{ => graph}/test_call_graph_smoke_roundtrip.py | 2 +- tests/{ => graph}/test_call_invariant.py | 0 tests/{ => graph}/test_client_hint_recovery.py | 4 ++-- tests/{ => graph}/test_client_node_extraction.py | 2 +- tests/{ => graph}/test_client_role_rename.py | 2 +- .../test_cross_service_resolution_flag.py | 2 +- tests/{ => graph}/test_feign_not_exposer.py | 2 +- tests/{ => graph}/test_generated_detection.py | 2 +- tests/{ => graph}/test_graph_enrich.py | 0 tests/{ => graph}/test_graph_generated_node.py | 0 tests/{ => graph}/test_ladybug_queries.py | 0 tests/{ => graph}/test_meta_chain_core.py | 0 tests/{ => graph}/test_microservice_scope.py | 0 tests/{ => graph}/test_path_filtering.py | 0 .../test_resolve_routes_messaging_layer_c.py | 0 tests/{ => graph}/test_route_extraction.py | 0 tests/{ => graph}/test_schema_consistency.py | 2 +- tests/{ => index}/test_generated_filter.py | 4 ++-- tests/{ => index}/test_lancedb_generated_column.py | 4 ++-- tests/{ => integration}/test_ast_graph_build.py | 8 ++++---- .../test_bank_chat_brownfield_integration.py | 0 tests/{ => integration}/test_call_edges_e2e.py | 2 +- tests/{ => integration}/test_incremental_graph.py | 10 +++++----- .../{ => integration}/test_installer_integration.py | 0 tests/{ => integration}/test_lancedb_e2e.py | 12 ++++++------ tests/{ => integration}/test_vectors_progress.py | 4 ++-- tests/{ => mcp}/test_generated_surface.py | 0 tests/{ => mcp}/test_mcp_hints.py | 2 +- tests/{ => mcp}/test_mcp_server_project_root.py | 0 tests/{ => mcp}/test_mcp_tools.py | 0 tests/{ => mcp}/test_mcp_v2.py | 2 +- tests/{ => mcp}/test_mcp_v2_compose.py | 2 +- .../test_cli_progress_stdout_invariant.py | 6 +++--- tests/{ => package}/test_cli_quiet_parity.py | 2 +- tests/{ => package}/test_config.py | 0 tests/{ => package}/test_fd_limit.py | 0 tests/{ => package}/test_graph_only_boot.py | 2 +- tests/{ => package}/test_install_data_sync.py | 4 ++-- tests/{ => package}/test_installer.py | 0 tests/{ => package}/test_installer_surface.py | 0 tests/{ => package}/test_java_codebase_rag_cli.py | 4 ++-- tests/{ => package}/test_jrag_auto_scope.py | 0 tests/{ => package}/test_jrag_enum_choices.py | 0 tests/{ => package}/test_jrag_listing.py | 0 tests/{ => package}/test_jrag_locate.py | 2 +- tests/{ => package}/test_jrag_orientation.py | 0 tests/{ => package}/test_jrag_render.py | 0 tests/{ => package}/test_jrag_status.py | 0 tests/{ => package}/test_jrag_token_budget.py | 0 tests/{ => package}/test_jrag_traversal_compose.py | 0 tests/{ => package}/test_jrag_traversal_direct.py | 0 tests/{ => package}/test_lance_optimize.py | 2 +- tests/{ => package}/test_pipeline.py | 0 tests/{ => package}/test_progress.py | 0 tests/{ => package}/test_version_flag.py | 0 tests/{ => search}/test_search_lancedb.py | 0 tests/{ => search}/test_search_lancedb_capability.py | 0 tests/{ => search}/test_search_lexical.py | 2 +- 82 files changed, 59 insertions(+), 59 deletions(-) rename tests/{ => _meta}/test_agent_skills_static.py (97%) rename tests/{ => _meta}/test_edge_navigation_doc.py (94%) rename tests/{ => _meta}/test_packaging_metadata.py (96%) rename tests/{ => absence}/test_absence_diagnosis.py (100%) rename tests/{ => absence}/test_absence_mcp_integration.py (100%) rename tests/{ => absence}/test_absence_types.py (100%) rename tests/{ => absence}/test_absence_vocab.py (100%) rename tests/{ => analysis}/test_jrag_envelope.py (100%) rename tests/{ => analysis}/test_jrag_envelope_absence.py (100%) rename tests/{ => analysis}/test_pr_analysis.py (100%) rename tests/{ => analysis}/test_resolve_service.py (100%) rename tests/{ => ast}/test_ast_java_calls.py (98%) rename tests/{ => ast}/test_ast_java_capabilities.py (100%) rename tests/{ => ast}/test_ast_java_thread_safety.py (100%) rename tests/{ => ast}/test_brownfield_events.py (100%) rename tests/{ => ast}/test_outgoing_call_extraction.py (100%) rename tests/{ => ast}/test_string_value_atoms.py (100%) rename tests/{ => graph}/test_assign_endpoint_client_extraction.py (98%) rename tests/{ => graph}/test_brownfield_clients.py (99%) rename tests/{ => graph}/test_brownfield_overrides.py (100%) rename tests/{ => graph}/test_brownfield_routes.py (99%) rename tests/{ => graph}/test_call_edge_matching.py (97%) rename tests/{ => graph}/test_call_graph_receiver_resolution.py (100%) rename tests/{ => graph}/test_call_graph_smoke_roundtrip.py (99%) rename tests/{ => graph}/test_call_invariant.py (100%) rename tests/{ => graph}/test_client_hint_recovery.py (96%) rename tests/{ => graph}/test_client_node_extraction.py (98%) rename tests/{ => graph}/test_client_role_rename.py (98%) rename tests/{ => graph}/test_cross_service_resolution_flag.py (99%) rename tests/{ => graph}/test_feign_not_exposer.py (98%) rename tests/{ => graph}/test_generated_detection.py (99%) rename tests/{ => graph}/test_graph_enrich.py (100%) rename tests/{ => graph}/test_graph_generated_node.py (100%) rename tests/{ => graph}/test_ladybug_queries.py (100%) rename tests/{ => graph}/test_meta_chain_core.py (100%) rename tests/{ => graph}/test_microservice_scope.py (100%) rename tests/{ => graph}/test_path_filtering.py (100%) rename tests/{ => graph}/test_resolve_routes_messaging_layer_c.py (100%) rename tests/{ => graph}/test_route_extraction.py (100%) rename tests/{ => graph}/test_schema_consistency.py (98%) rename tests/{ => index}/test_generated_filter.py (98%) rename tests/{ => index}/test_lancedb_generated_column.py (97%) rename tests/{ => integration}/test_ast_graph_build.py (98%) rename tests/{ => integration}/test_bank_chat_brownfield_integration.py (100%) rename tests/{ => integration}/test_call_edges_e2e.py (98%) rename tests/{ => integration}/test_incremental_graph.py (99%) rename tests/{ => integration}/test_installer_integration.py (100%) rename tests/{ => integration}/test_lancedb_e2e.py (96%) rename tests/{ => integration}/test_vectors_progress.py (99%) rename tests/{ => mcp}/test_generated_surface.py (100%) rename tests/{ => mcp}/test_mcp_hints.py (99%) rename tests/{ => mcp}/test_mcp_server_project_root.py (100%) rename tests/{ => mcp}/test_mcp_tools.py (100%) rename tests/{ => mcp}/test_mcp_v2.py (99%) rename tests/{ => mcp}/test_mcp_v2_compose.py (99%) rename tests/{ => package}/test_cli_progress_stdout_invariant.py (97%) rename tests/{ => package}/test_cli_quiet_parity.py (99%) rename tests/{ => package}/test_config.py (100%) rename tests/{ => package}/test_fd_limit.py (100%) rename tests/{ => package}/test_graph_only_boot.py (99%) rename tests/{ => package}/test_install_data_sync.py (98%) rename tests/{ => package}/test_installer.py (100%) rename tests/{ => package}/test_installer_surface.py (100%) rename tests/{ => package}/test_java_codebase_rag_cli.py (99%) rename tests/{ => package}/test_jrag_auto_scope.py (100%) rename tests/{ => package}/test_jrag_enum_choices.py (100%) rename tests/{ => package}/test_jrag_listing.py (100%) rename tests/{ => package}/test_jrag_locate.py (99%) rename tests/{ => package}/test_jrag_orientation.py (100%) rename tests/{ => package}/test_jrag_render.py (100%) rename tests/{ => package}/test_jrag_status.py (100%) rename tests/{ => package}/test_jrag_token_budget.py (100%) rename tests/{ => package}/test_jrag_traversal_compose.py (100%) rename tests/{ => package}/test_jrag_traversal_direct.py (100%) rename tests/{ => package}/test_lance_optimize.py (99%) rename tests/{ => package}/test_pipeline.py (100%) rename tests/{ => package}/test_progress.py (100%) rename tests/{ => package}/test_version_flag.py (100%) rename tests/{ => search}/test_search_lancedb.py (100%) rename tests/{ => search}/test_search_lancedb_capability.py (100%) rename tests/{ => search}/test_search_lexical.py (99%) diff --git a/pytest.ini b/pytest.ini index e75524a8..14817beb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] asyncio_mode = auto testpaths = tests -pythonpath = src +pythonpath = src tests diff --git a/tests/test_agent_skills_static.py b/tests/_meta/test_agent_skills_static.py similarity index 97% rename from tests/test_agent_skills_static.py rename to tests/_meta/test_agent_skills_static.py index d5aa9b63..7cbd466e 100644 --- a/tests/test_agent_skills_static.py +++ b/tests/_meta/test_agent_skills_static.py @@ -35,7 +35,7 @@ # Helpers # --------------------------------------------------------------------------- -SKILLS_DIR = Path(__file__).resolve().parent.parent / "skills" +SKILLS_DIR = Path(__file__).resolve().parent.parent.parent / "skills" SKILL_NAME = "explore-codebase" # PR-JRAG-5: the CLI surface ships its own skill (explore-codebase-cli) with a # shell vocabulary, not the MCP vocabulary. The static-validation tests in this @@ -251,7 +251,7 @@ class TestAgentGuideConsistency: def test_guide_has_navigation_patterns_table(self): """The copy-paste block must include a navigation patterns section.""" - guide = Path(__file__).resolve().parent.parent / "docs" / "AGENT-GUIDE.md" + guide = Path(__file__).resolve().parent.parent.parent / "docs" / "AGENT-GUIDE.md" text = guide.read_text(encoding="utf-8") begin = text.find("") end = text.find("") @@ -266,7 +266,7 @@ def test_guide_has_navigation_patterns_table(self): def test_guide_copy_block_does_not_reference_skills_dir(self): """The copy-paste block must not reference skills/ — it won't exist in the consumer's project.""" - guide = Path(__file__).resolve().parent.parent / "docs" / "AGENT-GUIDE.md" + guide = Path(__file__).resolve().parent.parent.parent / "docs" / "AGENT-GUIDE.md" text = guide.read_text(encoding="utf-8") begin = text.find("") end = text.find("") diff --git a/tests/test_edge_navigation_doc.py b/tests/_meta/test_edge_navigation_doc.py similarity index 94% rename from tests/test_edge_navigation_doc.py rename to tests/_meta/test_edge_navigation_doc.py index 0e54e9a1..0b97daab 100644 --- a/tests/test_edge_navigation_doc.py +++ b/tests/_meta/test_edge_navigation_doc.py @@ -5,7 +5,7 @@ import sys from pathlib import Path -_REPO_ROOT = Path(__file__).resolve().parent.parent +_REPO_ROOT = Path(__file__).resolve().parent.parent.parent _GENERATOR = _REPO_ROOT / "scripts" / "generate_edge_navigation.py" _COMMITTED = _REPO_ROOT / "docs" / "EDGE-NAVIGATION.md" diff --git a/tests/test_packaging_metadata.py b/tests/_meta/test_packaging_metadata.py similarity index 96% rename from tests/test_packaging_metadata.py rename to tests/_meta/test_packaging_metadata.py index 43e79489..8e708da7 100644 --- a/tests/test_packaging_metadata.py +++ b/tests/_meta/test_packaging_metadata.py @@ -14,7 +14,7 @@ def _deps() -> list[str]: - data = tomllib.loads((Path(__file__).resolve().parents[1] / "pyproject.toml").read_text()) + data = tomllib.loads((Path(__file__).resolve().parents[2] / "pyproject.toml").read_text()) return data["project"]["dependencies"] diff --git a/tests/test_absence_diagnosis.py b/tests/absence/test_absence_diagnosis.py similarity index 100% rename from tests/test_absence_diagnosis.py rename to tests/absence/test_absence_diagnosis.py diff --git a/tests/test_absence_mcp_integration.py b/tests/absence/test_absence_mcp_integration.py similarity index 100% rename from tests/test_absence_mcp_integration.py rename to tests/absence/test_absence_mcp_integration.py diff --git a/tests/test_absence_types.py b/tests/absence/test_absence_types.py similarity index 100% rename from tests/test_absence_types.py rename to tests/absence/test_absence_types.py diff --git a/tests/test_absence_vocab.py b/tests/absence/test_absence_vocab.py similarity index 100% rename from tests/test_absence_vocab.py rename to tests/absence/test_absence_vocab.py diff --git a/tests/test_jrag_envelope.py b/tests/analysis/test_jrag_envelope.py similarity index 100% rename from tests/test_jrag_envelope.py rename to tests/analysis/test_jrag_envelope.py diff --git a/tests/test_jrag_envelope_absence.py b/tests/analysis/test_jrag_envelope_absence.py similarity index 100% rename from tests/test_jrag_envelope_absence.py rename to tests/analysis/test_jrag_envelope_absence.py diff --git a/tests/test_pr_analysis.py b/tests/analysis/test_pr_analysis.py similarity index 100% rename from tests/test_pr_analysis.py rename to tests/analysis/test_pr_analysis.py diff --git a/tests/test_resolve_service.py b/tests/analysis/test_resolve_service.py similarity index 100% rename from tests/test_resolve_service.py rename to tests/analysis/test_resolve_service.py diff --git a/tests/test_ast_java_calls.py b/tests/ast/test_ast_java_calls.py similarity index 98% rename from tests/test_ast_java_calls.py rename to tests/ast/test_ast_java_calls.py index 2e487e51..12fdd0e9 100644 --- a/tests/test_ast_java_calls.py +++ b/tests/ast/test_ast_java_calls.py @@ -6,7 +6,7 @@ from java_codebase_rag.ast.ast_java import parse_java _FIXTURE_JAVA = ( - Path(__file__).resolve().parent + Path(__file__).resolve().parent.parent / "fixtures" / "call_graph_smoke" / "src" @@ -258,7 +258,7 @@ class C { def test_wildcard_static_import_fixture_file_proposal_7_1_case_15() -> None: """§7.1 #15: wildcard static import recorded on the compilation unit.""" path = ( - Path(__file__).resolve().parent + Path(__file__).resolve().parent.parent / "fixtures" / "call_graph_smoke" / "src" @@ -324,7 +324,7 @@ def test_default_ctor_not_synthesized_for_lombok_required_args() -> None: def test_nested_calls_fixture_file_parse_proposal_7_1_11_16_18() -> None: """Parse on-disk NestedCalls.java (smoke fixture) for combined #11 / #16 / #18 shapes.""" path = ( - Path(__file__).resolve().parent + Path(__file__).resolve().parent.parent / "fixtures" / "call_graph_smoke" / "src" diff --git a/tests/test_ast_java_capabilities.py b/tests/ast/test_ast_java_capabilities.py similarity index 100% rename from tests/test_ast_java_capabilities.py rename to tests/ast/test_ast_java_capabilities.py diff --git a/tests/test_ast_java_thread_safety.py b/tests/ast/test_ast_java_thread_safety.py similarity index 100% rename from tests/test_ast_java_thread_safety.py rename to tests/ast/test_ast_java_thread_safety.py diff --git a/tests/test_brownfield_events.py b/tests/ast/test_brownfield_events.py similarity index 100% rename from tests/test_brownfield_events.py rename to tests/ast/test_brownfield_events.py diff --git a/tests/test_outgoing_call_extraction.py b/tests/ast/test_outgoing_call_extraction.py similarity index 100% rename from tests/test_outgoing_call_extraction.py rename to tests/ast/test_outgoing_call_extraction.py diff --git a/tests/test_string_value_atoms.py b/tests/ast/test_string_value_atoms.py similarity index 100% rename from tests/test_string_value_atoms.py rename to tests/ast/test_string_value_atoms.py diff --git a/tests/test_assign_endpoint_client_extraction.py b/tests/graph/test_assign_endpoint_client_extraction.py similarity index 98% rename from tests/test_assign_endpoint_client_extraction.py rename to tests/graph/test_assign_endpoint_client_extraction.py index c04542fc..4fd9aa59 100644 --- a/tests/test_assign_endpoint_client_extraction.py +++ b/tests/graph/test_assign_endpoint_client_extraction.py @@ -10,7 +10,7 @@ from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain -STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" +STUB_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "brownfield_client_stubs" def _copy_stubs(dest: Path) -> None: diff --git a/tests/test_brownfield_clients.py b/tests/graph/test_brownfield_clients.py similarity index 99% rename from tests/test_brownfield_clients.py rename to tests/graph/test_brownfield_clients.py index cceab380..da061e16 100644 --- a/tests/test_brownfield_clients.py +++ b/tests/graph/test_brownfield_clients.py @@ -11,7 +11,7 @@ from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain -STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" +STUB_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "brownfield_client_stubs" @pytest.fixture(autouse=True) diff --git a/tests/test_brownfield_overrides.py b/tests/graph/test_brownfield_overrides.py similarity index 100% rename from tests/test_brownfield_overrides.py rename to tests/graph/test_brownfield_overrides.py diff --git a/tests/test_brownfield_routes.py b/tests/graph/test_brownfield_routes.py similarity index 99% rename from tests/test_brownfield_routes.py rename to tests/graph/test_brownfield_routes.py index 79110d01..5a37e624 100644 --- a/tests/test_brownfield_routes.py +++ b/tests/graph/test_brownfield_routes.py @@ -11,7 +11,7 @@ from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain -STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_route_stubs" +STUB_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "brownfield_route_stubs" @pytest.fixture(autouse=True) diff --git a/tests/test_call_edge_matching.py b/tests/graph/test_call_edge_matching.py similarity index 97% rename from tests/test_call_edge_matching.py rename to tests/graph/test_call_edge_matching.py index abf8968c..a57fa0bf 100644 --- a/tests/test_call_edge_matching.py +++ b/tests/graph/test_call_edge_matching.py @@ -115,7 +115,7 @@ def test_phantom_routes_cleaned_up_when_real_match_found(graph_tables_cross_serv def test_graph_enrich_has_single_route_strategy_ladder() -> None: - graph_enrich = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "graph_enrich.py" + graph_enrich = Path(__file__).resolve().parent.parent.parent / "src" / "java_codebase_rag" / "graph" / "graph_enrich.py" source = graph_enrich.read_text(encoding="utf-8") matches = re.findall(r"annotation.*spel.*constant_ref", source) assert len(matches) == 1 diff --git a/tests/test_call_graph_receiver_resolution.py b/tests/graph/test_call_graph_receiver_resolution.py similarity index 100% rename from tests/test_call_graph_receiver_resolution.py rename to tests/graph/test_call_graph_receiver_resolution.py diff --git a/tests/test_call_graph_smoke_roundtrip.py b/tests/graph/test_call_graph_smoke_roundtrip.py similarity index 99% rename from tests/test_call_graph_smoke_roundtrip.py rename to tests/graph/test_call_graph_smoke_roundtrip.py index 18ff68dd..1e399a81 100644 --- a/tests/test_call_graph_smoke_roundtrip.py +++ b/tests/graph/test_call_graph_smoke_roundtrip.py @@ -7,7 +7,7 @@ from java_codebase_rag.graph.ladybug_queries import LadybugGraph -_FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "call_graph_smoke" +_FIXTURE_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "call_graph_smoke" def _connect(db_path: Path) -> ladybug.Connection: diff --git a/tests/test_call_invariant.py b/tests/graph/test_call_invariant.py similarity index 100% rename from tests/test_call_invariant.py rename to tests/graph/test_call_invariant.py diff --git a/tests/test_client_hint_recovery.py b/tests/graph/test_client_hint_recovery.py similarity index 96% rename from tests/test_client_hint_recovery.py rename to tests/graph/test_client_hint_recovery.py index 7992f868..b78aa8b3 100644 --- a/tests/test_client_hint_recovery.py +++ b/tests/graph/test_client_hint_recovery.py @@ -6,8 +6,8 @@ from java_codebase_rag.graph.build_ast_graph import GraphTables, _match_call_edge, pass6_match_edges, write_ladybug from java_codebase_rag.graph.ladybug_queries import LadybugGraph -_FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" -_HTTP_CALLER = Path(__file__).resolve().parent / "fixtures" / "http_caller_smoke" +_FIXTURE = Path(__file__).resolve().parent.parent / "fixtures" / "cross_service_smoke" +_HTTP_CALLER = Path(__file__).resolve().parent.parent / "fixtures" / "http_caller_smoke" def _build_tables() -> GraphTables: diff --git a/tests/test_client_node_extraction.py b/tests/graph/test_client_node_extraction.py similarity index 98% rename from tests/test_client_node_extraction.py rename to tests/graph/test_client_node_extraction.py index d5cb6b9e..88337612 100644 --- a/tests/test_client_node_extraction.py +++ b/tests/graph/test_client_node_extraction.py @@ -8,7 +8,7 @@ from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain -STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" +STUB_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "brownfield_client_stubs" def _copy_stubs(dest: Path) -> None: diff --git a/tests/test_client_role_rename.py b/tests/graph/test_client_role_rename.py similarity index 98% rename from tests/test_client_role_rename.py rename to tests/graph/test_client_role_rename.py index 55dd1a19..51014cab 100644 --- a/tests/test_client_role_rename.py +++ b/tests/graph/test_client_role_rename.py @@ -12,7 +12,7 @@ from java_codebase_rag.graph.graph_enrich import _load_brownfield_overrides, collect_annotation_meta_chain from java_codebase_rag.graph.ladybug_queries import LadybugGraph -_FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" +_FIXTURE = Path(__file__).resolve().parent.parent / "fixtures" / "cross_service_smoke" @pytest.fixture(autouse=True) diff --git a/tests/test_cross_service_resolution_flag.py b/tests/graph/test_cross_service_resolution_flag.py similarity index 99% rename from tests/test_cross_service_resolution_flag.py rename to tests/graph/test_cross_service_resolution_flag.py index f8dac141..9a1765b6 100644 --- a/tests/test_cross_service_resolution_flag.py +++ b/tests/graph/test_cross_service_resolution_flag.py @@ -13,7 +13,7 @@ from java_codebase_rag.graph.build_ast_graph import GraphTables from java_codebase_rag.graph.graph_enrich import _load_config_cross_service_resolution -_FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" +_FIXTURE = Path(__file__).resolve().parent.parent / "fixtures" / "cross_service_smoke" @pytest.fixture(autouse=True) diff --git a/tests/test_feign_not_exposer.py b/tests/graph/test_feign_not_exposer.py similarity index 98% rename from tests/test_feign_not_exposer.py rename to tests/graph/test_feign_not_exposer.py index 37db40b2..05839dee 100644 --- a/tests/test_feign_not_exposer.py +++ b/tests/graph/test_feign_not_exposer.py @@ -7,7 +7,7 @@ from java_codebase_rag.graph.build_ast_graph import GraphTables, write_ladybug from java_codebase_rag.graph.ladybug_queries import LadybugGraph -_FIXTURE = Path(__file__).resolve().parent / "fixtures" / "cross_service_smoke" +_FIXTURE = Path(__file__).resolve().parent.parent / "fixtures" / "cross_service_smoke" def _member_id(tables: GraphTables, *, parent_fqn: str, method_name: str) -> str: diff --git a/tests/test_generated_detection.py b/tests/graph/test_generated_detection.py similarity index 99% rename from tests/test_generated_detection.py rename to tests/graph/test_generated_detection.py index 52c02b76..eb0f22f4 100644 --- a/tests/test_generated_detection.py +++ b/tests/graph/test_generated_detection.py @@ -8,7 +8,7 @@ from java_codebase_rag.ast.ast_java import parse_java -FIXTURE_DIR = Path(__file__).parent / "fixtures" / "generated_samples" +FIXTURE_DIR = Path(__file__).parent.parent / "fixtures" / "generated_samples" def _parse_fixture(filename: str): diff --git a/tests/test_graph_enrich.py b/tests/graph/test_graph_enrich.py similarity index 100% rename from tests/test_graph_enrich.py rename to tests/graph/test_graph_enrich.py diff --git a/tests/test_graph_generated_node.py b/tests/graph/test_graph_generated_node.py similarity index 100% rename from tests/test_graph_generated_node.py rename to tests/graph/test_graph_generated_node.py diff --git a/tests/test_ladybug_queries.py b/tests/graph/test_ladybug_queries.py similarity index 100% rename from tests/test_ladybug_queries.py rename to tests/graph/test_ladybug_queries.py diff --git a/tests/test_meta_chain_core.py b/tests/graph/test_meta_chain_core.py similarity index 100% rename from tests/test_meta_chain_core.py rename to tests/graph/test_meta_chain_core.py diff --git a/tests/test_microservice_scope.py b/tests/graph/test_microservice_scope.py similarity index 100% rename from tests/test_microservice_scope.py rename to tests/graph/test_microservice_scope.py diff --git a/tests/test_path_filtering.py b/tests/graph/test_path_filtering.py similarity index 100% rename from tests/test_path_filtering.py rename to tests/graph/test_path_filtering.py diff --git a/tests/test_resolve_routes_messaging_layer_c.py b/tests/graph/test_resolve_routes_messaging_layer_c.py similarity index 100% rename from tests/test_resolve_routes_messaging_layer_c.py rename to tests/graph/test_resolve_routes_messaging_layer_c.py diff --git a/tests/test_route_extraction.py b/tests/graph/test_route_extraction.py similarity index 100% rename from tests/test_route_extraction.py rename to tests/graph/test_route_extraction.py diff --git a/tests/test_schema_consistency.py b/tests/graph/test_schema_consistency.py similarity index 98% rename from tests/test_schema_consistency.py rename to tests/graph/test_schema_consistency.py index 1aabeb2c..498eef27 100644 --- a/tests/test_schema_consistency.py +++ b/tests/graph/test_schema_consistency.py @@ -10,7 +10,7 @@ from java_codebase_rag.graph.java_ontology import BROWNFIELD_RESOLVER_STRATEGY_SET, EDGE_SCHEMA -_REPO_ROOT = Path(__file__).resolve().parent.parent +_REPO_ROOT = Path(__file__).resolve().parent.parent.parent _BUILD_AST_GRAPH = _REPO_ROOT / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" _REL_DDL_RE = re.compile( diff --git a/tests/test_generated_filter.py b/tests/index/test_generated_filter.py similarity index 98% rename from tests/test_generated_filter.py rename to tests/index/test_generated_filter.py index 8998011f..ef973aec 100644 --- a/tests/test_generated_filter.py +++ b/tests/index/test_generated_filter.py @@ -17,7 +17,7 @@ from java_codebase_rag.search import search_lancedb -FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "generated_samples" +FIXTURE_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "generated_samples" def _require_cocoindex_runtime_deps() -> None: @@ -49,7 +49,7 @@ def lancedb_with_generated_index(tmp_path): _require_cocoindex_runtime_deps() # Locate the bundle dir (repo root) - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent # Get the flow specifier app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", FIXTURE_ROOT) diff --git a/tests/test_lancedb_generated_column.py b/tests/index/test_lancedb_generated_column.py similarity index 97% rename from tests/test_lancedb_generated_column.py rename to tests/index/test_lancedb_generated_column.py index a0d774b0..2edd923e 100644 --- a/tests/test_lancedb_generated_column.py +++ b/tests/index/test_lancedb_generated_column.py @@ -14,7 +14,7 @@ lancedb = pytest.importorskip("lancedb") -FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "generated_samples" +FIXTURE_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "generated_samples" def _require_cocoindex_runtime_deps() -> None: @@ -63,7 +63,7 @@ def test_lance_chunk_generated_columns( _require_cocoindex_runtime_deps() # Locate the bundle dir (repo root) - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent # Get the flow specifier app_spec = _cocoindex_flow_specifier(bundle_dir / "src" / "java_codebase_rag" / "index", FIXTURE_ROOT) diff --git a/tests/test_ast_graph_build.py b/tests/integration/test_ast_graph_build.py similarity index 98% rename from tests/test_ast_graph_build.py rename to tests/integration/test_ast_graph_build.py index f6b95776..3e274fd8 100644 --- a/tests/test_ast_graph_build.py +++ b/tests/integration/test_ast_graph_build.py @@ -348,7 +348,7 @@ def test_cli_entrypoint_runs(tmp_path: Path, corpus_root: Path) -> None: (via the venv Python) and asserts a non-empty Kuzu DB is written. """ target = tmp_path / "graph.lbug" - script = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" + script = Path(__file__).resolve().parent.parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" proc = subprocess.run( [ sys.executable, @@ -413,7 +413,7 @@ def test_pass3_known_external_calls_preserved(ladybug_db_path: Path) -> None: def _run_builder_verbose(corpus_root: Path, target_db: Path, *, extra_args: list[str] | None = None) -> subprocess.CompletedProcess: """Run build_ast_graph.py --verbose and return the CompletedProcess.""" - script = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" + script = Path(__file__).resolve().parent.parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" cmd = [ sys.executable, str(script), @@ -489,7 +489,7 @@ def test_build_ast_graph_passes_2_to_6_emit_step_progress(corpus_root: Path, tmp def test_build_ast_graph_quiet_emits_no_progress(corpus_root: Path, tmp_path: Path) -> None: """Without --verbose the builder emits no JCIRAG_PROGRESS lines.""" - script = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" + script = Path(__file__).resolve().parent.parent.parent / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" target = tmp_path / "quiet.lbug" proc = subprocess.run( [ @@ -549,7 +549,7 @@ def test_pass1_parse_incremental_total_excludes_removed_files(tmp_path: Path, ca def _load_baseline() -> dict: """Load the baseline fixture generated from the per-row _write_edges implementation.""" - baseline_path = Path(__file__).resolve().parent / "fixtures" / "graph_baseline_bank_chat.json" + baseline_path = Path(__file__).resolve().parent.parent / "fixtures" / "graph_baseline_bank_chat.json" with open(baseline_path, encoding="utf-8") as f: return json.load(f) diff --git a/tests/test_bank_chat_brownfield_integration.py b/tests/integration/test_bank_chat_brownfield_integration.py similarity index 100% rename from tests/test_bank_chat_brownfield_integration.py rename to tests/integration/test_bank_chat_brownfield_integration.py diff --git a/tests/test_call_edges_e2e.py b/tests/integration/test_call_edges_e2e.py similarity index 98% rename from tests/test_call_edges_e2e.py rename to tests/integration/test_call_edges_e2e.py index 19f423ad..476564f0 100644 --- a/tests/test_call_edges_e2e.py +++ b/tests/integration/test_call_edges_e2e.py @@ -8,7 +8,7 @@ from java_codebase_rag.ast.ast_java import ONTOLOGY_VERSION from java_codebase_rag.graph.ladybug_queries import LadybugGraph -_STUB_ROOT = Path(__file__).resolve().parent / "fixtures" / "brownfield_client_stubs" +_STUB_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "brownfield_client_stubs" def _scalar(db_path: Path, query: str) -> int: diff --git a/tests/test_incremental_graph.py b/tests/integration/test_incremental_graph.py similarity index 99% rename from tests/test_incremental_graph.py rename to tests/integration/test_incremental_graph.py index d44068b6..67e11f97 100644 --- a/tests/test_incremental_graph.py +++ b/tests/integration/test_incremental_graph.py @@ -167,7 +167,7 @@ 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_ladybug_full_into - corpus_root = Path(__file__).parent / "bank-chat-system" + corpus_root = Path(__file__).parent.parent / "bank-chat-system" db_path = tmp_path / "test_graph.lbug" build_ladybug_full_into(corpus_root, db_path) @@ -194,7 +194,7 @@ 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_ladybug_full_into - corpus_root = Path(__file__).parent / "bank-chat-system" + corpus_root = Path(__file__).parent.parent / "bank-chat-system" db_path = tmp_path / "test_graph.lbug" build_ladybug_full_into(corpus_root, db_path) @@ -1590,7 +1590,7 @@ def test_incremental_with_http_clients_does_not_fall_back(self, tmp_path: Path) from _builders import build_ladybug_full_into from java_codebase_rag.graph.build_ast_graph import incremental_rebuild - corpus = Path(__file__).parent / "fixtures" / "http_caller_smoke" + corpus = Path(__file__).parent.parent / "fixtures" / "http_caller_smoke" source_root = tmp_path / "src" shutil.copytree(corpus, source_root) index_dir = tmp_path / "index" @@ -1634,7 +1634,7 @@ def test_reprocess_graph_only_then_increment_is_noop(self, tmp_path: Path) -> No from _builders import build_ladybug_full_into from java_codebase_rag.graph.build_ast_graph import incremental_rebuild - corpus = Path(__file__).parent / "fixtures" / "http_caller_smoke" + corpus = Path(__file__).parent.parent / "fixtures" / "http_caller_smoke" source_root = tmp_path / "src" shutil.copytree(corpus, source_root) index_dir = tmp_path / "index" @@ -1660,7 +1660,7 @@ def test_incremental_ghost_entry_then_next_run_is_noop(self, tmp_path: Path) -> from _builders import build_ladybug_full_into from java_codebase_rag.graph.build_ast_graph import incremental_rebuild - corpus = Path(__file__).parent / "fixtures" / "http_caller_smoke" + corpus = Path(__file__).parent.parent / "fixtures" / "http_caller_smoke" source_root = tmp_path / "src" shutil.copytree(corpus, source_root) index_dir = tmp_path / "index" diff --git a/tests/test_installer_integration.py b/tests/integration/test_installer_integration.py similarity index 100% rename from tests/test_installer_integration.py rename to tests/integration/test_installer_integration.py diff --git a/tests/test_lancedb_e2e.py b/tests/integration/test_lancedb_e2e.py similarity index 96% rename from tests/test_lancedb_e2e.py rename to tests/integration/test_lancedb_e2e.py index 373070bc..e85ebc28 100644 --- a/tests/test_lancedb_e2e.py +++ b/tests/integration/test_lancedb_e2e.py @@ -32,8 +32,8 @@ pytest.mark.lance_e2e, ] -CAPABILITY_SMOKE_ROOT = Path(__file__).resolve().parent / "fixtures" / "capability_smoke" -IGNORE_SMOKE_ROOT = Path(__file__).resolve().parent / "fixtures" / "lancedb_ignore_smoke" +CAPABILITY_SMOKE_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "capability_smoke" +IGNORE_SMOKE_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "lancedb_ignore_smoke" def _require_cocoindex_runtime_deps() -> None: @@ -79,7 +79,7 @@ def _structured(result): def lance_index(tmp_path_factory, corpus_root: Path) -> Path: """Build a real LanceDB index over the corpus via cocoindex.""" _require_cocoindex_runtime_deps() - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent cocoindex_bin = Path(sys.executable).parent / "cocoindex" if not cocoindex_bin.is_file(): pytest.skip( @@ -149,7 +149,7 @@ def lance_index(tmp_path_factory, corpus_root: Path) -> Path: def lance_index_capability_smoke(tmp_path_factory) -> Path: """Tiny project with @KafkaListener — indexes fast; tests capability filter in search.""" _require_cocoindex_runtime_deps() - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent if not CAPABILITY_SMOKE_ROOT.is_dir(): pytest.skip(f"capability smoke fixture missing: {CAPABILITY_SMOKE_ROOT}") cocoindex_bin = Path(sys.executable).parent / "cocoindex" @@ -278,7 +278,7 @@ def test_lancedb_ignore_file_reduces_indexed_java_files(tmp_path_factory) -> Non _require_cocoindex_runtime_deps() if not IGNORE_SMOKE_ROOT.is_dir(): pytest.skip(f"missing fixture tree: {IGNORE_SMOKE_ROOT}") - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent cocoindex_bin = Path(sys.executable).parent / "cocoindex" if not cocoindex_bin.is_file(): pytest.skip( @@ -359,7 +359,7 @@ def test_layered_ignore_provided_once_per_flow() -> None: stubbing the embedder/LanceDB setup, and subprocess-based testing cannot cross the process boundary to instrument LayeredIgnore.__init__. """ - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent flow_file = bundle_dir / "src" / "java_codebase_rag" / "index" / "java_index_flow_lancedb.py" if not flow_file.is_file(): pytest.skip(f"Flow file not found: {flow_file}") diff --git a/tests/test_vectors_progress.py b/tests/integration/test_vectors_progress.py similarity index 99% rename from tests/test_vectors_progress.py rename to tests/integration/test_vectors_progress.py index 4ccebbf4..75219507 100644 --- a/tests/test_vectors_progress.py +++ b/tests/integration/test_vectors_progress.py @@ -210,7 +210,7 @@ def test_spinner_removed_and_emit_vectors_helpers_removed() -> None: assert not hasattr(cli_progress, "emit_vectors_start"), "emit_vectors_start should have been removed" assert not hasattr(cli_progress, "emit_vectors_finish"), "emit_vectors_finish should have been removed" # And no remaining references anywhere in the production tree. - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent offenders: list[str] = [] for py in (repo_root / "src" / "java_codebase_rag").rglob("*.py"): text = py.read_text(encoding="utf-8") @@ -253,7 +253,7 @@ def _run_cocoindex_update(corpus_root: Path, index_dir: Path) -> subprocess.Comp cocoindex_bin = _cocoindex_bin() if not cocoindex_bin.is_file(): pytest.skip(f"cocoindex not installed in venv: {cocoindex_bin}") - bundle_dir = Path(__file__).resolve().parent.parent + bundle_dir = Path(__file__).resolve().parent.parent.parent flow = (bundle_dir / "src" / "java_codebase_rag" / "index" / "java_index_flow_lancedb.py").resolve() start = Path(corpus_root).resolve() relp = os.path.relpath(str(flow), start=str(start)) diff --git a/tests/test_generated_surface.py b/tests/mcp/test_generated_surface.py similarity index 100% rename from tests/test_generated_surface.py rename to tests/mcp/test_generated_surface.py diff --git a/tests/test_mcp_hints.py b/tests/mcp/test_mcp_hints.py similarity index 99% rename from tests/test_mcp_hints.py rename to tests/mcp/test_mcp_hints.py index c366cc79..e2db4091 100644 --- a/tests/test_mcp_hints.py +++ b/tests/mcp/test_mcp_hints.py @@ -27,7 +27,7 @@ _TYPE_KINDS = frozenset({"class", "interface", "enum", "record", "annotation"}) -_OVERRIDE_AXIS_FIXTURE = Path(__file__).resolve().parent / "fixtures" / "override_axis_rollup_smoke" +_OVERRIDE_AXIS_FIXTURE = Path(__file__).resolve().parent.parent / "fixtures" / "override_axis_rollup_smoke" def _hints(output_kind, payload): diff --git a/tests/test_mcp_server_project_root.py b/tests/mcp/test_mcp_server_project_root.py similarity index 100% rename from tests/test_mcp_server_project_root.py rename to tests/mcp/test_mcp_server_project_root.py diff --git a/tests/test_mcp_tools.py b/tests/mcp/test_mcp_tools.py similarity index 100% rename from tests/test_mcp_tools.py rename to tests/mcp/test_mcp_tools.py diff --git a/tests/test_mcp_v2.py b/tests/mcp/test_mcp_v2.py similarity index 99% rename from tests/test_mcp_v2.py rename to tests/mcp/test_mcp_v2.py index 593fa360..a6f614aa 100644 --- a/tests/test_mcp_v2.py +++ b/tests/mcp/test_mcp_v2.py @@ -1524,7 +1524,7 @@ def test_resolve_success_output_invariants(ladybug_graph, ladybug_graph_fqn_coll _PERF_BASELINES_PATH = ( - Path(__file__).resolve().parent / "fixtures" / "perf_baselines.json" + Path(__file__).resolve().parent.parent / "fixtures" / "perf_baselines.json" ) diff --git a/tests/test_mcp_v2_compose.py b/tests/mcp/test_mcp_v2_compose.py similarity index 99% rename from tests/test_mcp_v2_compose.py rename to tests/mcp/test_mcp_v2_compose.py index accfa66d..df974ae7 100644 --- a/tests/test_mcp_v2_compose.py +++ b/tests/mcp/test_mcp_v2_compose.py @@ -50,7 +50,7 @@ def _vector_stack_available() -> bool: _ROLLUP_TYPE_KINDS = sorted(_TYPE_SYMBOL_KINDS_FOR_EDGE_ROLLUP) -_OVERRIDE_AXIS_FIXTURE = Path(__file__).resolve().parent / "fixtures" / "override_axis_rollup_smoke" +_OVERRIDE_AXIS_FIXTURE = Path(__file__).resolve().parent.parent / "fixtures" / "override_axis_rollup_smoke" @pytest.fixture diff --git a/tests/test_cli_progress_stdout_invariant.py b/tests/package/test_cli_progress_stdout_invariant.py similarity index 97% rename from tests/test_cli_progress_stdout_invariant.py rename to tests/package/test_cli_progress_stdout_invariant.py index 45a87200..e842f12e 100644 --- a/tests/test_cli_progress_stdout_invariant.py +++ b/tests/package/test_cli_progress_stdout_invariant.py @@ -14,7 +14,7 @@ from java_codebase_rag.cli_progress import accumulate_and_relay_subprocess_streams -_FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures" / "cli_progress_stdout" +_FIXTURE_DIR = Path(__file__).resolve().parent.parent / "fixtures" / "cli_progress_stdout" def _cocoindex_available() -> bool: @@ -95,7 +95,7 @@ def test_refresh_pipeline_quiet_stderr_baseline(monkeypatch: pytest.MonkeyPatch, # path this test exercises. monkeypatch.setattr(server, "vector_stack_installed", lambda: True) - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent idx = tmp_path / "idx_q" idx.mkdir(parents=True) monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(repo_root)) @@ -183,7 +183,7 @@ async def fake_refresh(*, quiet: bool = False, verbose: bool = True, on_progress monkeypatch.setattr(server, "run_refresh_pipeline", fake_refresh) - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent idx = tmp_path / "idx_rep_stdout" idx.mkdir(parents=True) monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(repo_root)) diff --git a/tests/test_cli_quiet_parity.py b/tests/package/test_cli_quiet_parity.py similarity index 99% rename from tests/test_cli_quiet_parity.py rename to tests/package/test_cli_quiet_parity.py index 6839929f..88f9f46f 100644 --- a/tests/test_cli_quiet_parity.py +++ b/tests/package/test_cli_quiet_parity.py @@ -13,7 +13,7 @@ from java_codebase_rag import cli as cli_mod -REPO = Path(__file__).resolve().parent.parent +REPO = Path(__file__).resolve().parent.parent.parent BUILDER = REPO / "src" / "java_codebase_rag" / "graph" / "build_ast_graph.py" FIXTURE_ROOT = REPO / "tests" / "fixtures" / "call_graph_smoke" _PASS1_START = "[graph] pass 1 · parsing Java files" diff --git a/tests/test_config.py b/tests/package/test_config.py similarity index 100% rename from tests/test_config.py rename to tests/package/test_config.py diff --git a/tests/test_fd_limit.py b/tests/package/test_fd_limit.py similarity index 100% rename from tests/test_fd_limit.py rename to tests/package/test_fd_limit.py diff --git a/tests/test_graph_only_boot.py b/tests/package/test_graph_only_boot.py similarity index 99% rename from tests/test_graph_only_boot.py rename to tests/package/test_graph_only_boot.py index d5510bc7..df3ce554 100644 --- a/tests/test_graph_only_boot.py +++ b/tests/package/test_graph_only_boot.py @@ -136,7 +136,7 @@ async def fake_graph_phase(root, *, quiet, verbose, on_progress, on_progress_con monkeypatch.setattr(server, "_run_graph_phase", fake_graph_phase) - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(repo_root)) monkeypatch.setenv("JAVA_CODEBASE_RAG_INDEX_DIR", str(tmp_path / "idx")) diff --git a/tests/test_install_data_sync.py b/tests/package/test_install_data_sync.py similarity index 98% rename from tests/test_install_data_sync.py rename to tests/package/test_install_data_sync.py index 24303321..88e4e0d8 100644 --- a/tests/test_install_data_sync.py +++ b/tests/package/test_install_data_sync.py @@ -28,7 +28,7 @@ def run_sync_script(*, check: bool = False, cwd: Path | None = None) -> subproce Returns: CompletedProcess with stdout/stderr captured as text. """ - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent if cwd is None: cwd = repo_root @@ -114,7 +114,7 @@ def test_sync_script_detects_drift(): 4. Asserts --check exits non-zero AND names the offending file 5. Restores by temp dir auto-cleanup (no repo mutation) """ - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent # Copy a real file (agents/explorer-rag-enhanced.md) to temp workspace real_dev_file = repo_root / "agents" / "explorer-rag-enhanced.md" diff --git a/tests/test_installer.py b/tests/package/test_installer.py similarity index 100% rename from tests/test_installer.py rename to tests/package/test_installer.py diff --git a/tests/test_installer_surface.py b/tests/package/test_installer_surface.py similarity index 100% rename from tests/test_installer_surface.py rename to tests/package/test_installer_surface.py diff --git a/tests/test_java_codebase_rag_cli.py b/tests/package/test_java_codebase_rag_cli.py similarity index 99% rename from tests/test_java_codebase_rag_cli.py rename to tests/package/test_java_codebase_rag_cli.py index 54b86f8b..c7ad431f 100644 --- a/tests/test_java_codebase_rag_cli.py +++ b/tests/package/test_java_codebase_rag_cli.py @@ -26,7 +26,7 @@ def _install_java_codebase_rag_entrypoint() -> None: Session-scoped: one ``pip install -e`` per pytest run (slow but matches real entrypoints). """ - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent subprocess.run( [sys.executable, "-m", "pip", "install", "-e", str(repo_root)], check=True, @@ -1396,7 +1396,7 @@ def test_console_script_entry_point_routes_through_wrapper() -> None: """``[project.scripts]`` must point ``java-codebase-rag`` at ``_console_script_main`` (not ``main``) so the deterministic-exit path is the one the installed CLI actually uses.""" - pyproject = (Path(__file__).resolve().parent.parent / "pyproject.toml").read_text(encoding="utf-8") + pyproject = (Path(__file__).resolve().parent.parent.parent / "pyproject.toml").read_text(encoding="utf-8") assert 'java-codebase-rag = "java_codebase_rag.cli:_console_script_main"' in pyproject assert 'java-codebase-rag = "java-codebase-rag:main"' not in pyproject assert 'java-codebase-rag = "java_codebase_rag.cli:main"' not in pyproject diff --git a/tests/test_jrag_auto_scope.py b/tests/package/test_jrag_auto_scope.py similarity index 100% rename from tests/test_jrag_auto_scope.py rename to tests/package/test_jrag_auto_scope.py diff --git a/tests/test_jrag_enum_choices.py b/tests/package/test_jrag_enum_choices.py similarity index 100% rename from tests/test_jrag_enum_choices.py rename to tests/package/test_jrag_enum_choices.py diff --git a/tests/test_jrag_listing.py b/tests/package/test_jrag_listing.py similarity index 100% rename from tests/test_jrag_listing.py rename to tests/package/test_jrag_listing.py diff --git a/tests/test_jrag_locate.py b/tests/package/test_jrag_locate.py similarity index 99% rename from tests/test_jrag_locate.py rename to tests/package/test_jrag_locate.py index 7ebe5b74..94d60ae7 100644 --- a/tests/test_jrag_locate.py +++ b/tests/package/test_jrag_locate.py @@ -455,7 +455,7 @@ def test_inspect_service_flag_disambiguates_collision( SAME fqn ``com.example.SharedDto``, so the graph builder merges them into a single node and the baseline resolve is already ``ok``.) """ - fixture_root = Path(__file__).parent / "fixtures" / "route_extraction_smoke" + fixture_root = Path(__file__).parent.parent / "fixtures" / "route_extraction_smoke" env = os.environ.copy() env["JAVA_CODEBASE_RAG_SOURCE_ROOT"] = str(fixture_root) env["JAVA_CODEBASE_RAG_INDEX_DIR"] = str(ladybug_db_path_route_extraction_smoke.parent) diff --git a/tests/test_jrag_orientation.py b/tests/package/test_jrag_orientation.py similarity index 100% rename from tests/test_jrag_orientation.py rename to tests/package/test_jrag_orientation.py diff --git a/tests/test_jrag_render.py b/tests/package/test_jrag_render.py similarity index 100% rename from tests/test_jrag_render.py rename to tests/package/test_jrag_render.py diff --git a/tests/test_jrag_status.py b/tests/package/test_jrag_status.py similarity index 100% rename from tests/test_jrag_status.py rename to tests/package/test_jrag_status.py diff --git a/tests/test_jrag_token_budget.py b/tests/package/test_jrag_token_budget.py similarity index 100% rename from tests/test_jrag_token_budget.py rename to tests/package/test_jrag_token_budget.py diff --git a/tests/test_jrag_traversal_compose.py b/tests/package/test_jrag_traversal_compose.py similarity index 100% rename from tests/test_jrag_traversal_compose.py rename to tests/package/test_jrag_traversal_compose.py diff --git a/tests/test_jrag_traversal_direct.py b/tests/package/test_jrag_traversal_direct.py similarity index 100% rename from tests/test_jrag_traversal_direct.py rename to tests/package/test_jrag_traversal_direct.py diff --git a/tests/test_lance_optimize.py b/tests/package/test_lance_optimize.py similarity index 99% rename from tests/test_lance_optimize.py rename to tests/package/test_lance_optimize.py index ded5a553..4ace91af 100644 --- a/tests/test_lance_optimize.py +++ b/tests/package/test_lance_optimize.py @@ -272,7 +272,7 @@ def test_lance_table_names_constant_matches_search_lancedb_tables() -> None: from java_codebase_rag.lance_optimize import LANCE_TABLE_NAMES # Imported lazily to avoid pulling sentence-transformers at collection time. - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) try: from java_codebase_rag.search.search_lancedb import TABLES finally: diff --git a/tests/test_pipeline.py b/tests/package/test_pipeline.py similarity index 100% rename from tests/test_pipeline.py rename to tests/package/test_pipeline.py diff --git a/tests/test_progress.py b/tests/package/test_progress.py similarity index 100% rename from tests/test_progress.py rename to tests/package/test_progress.py diff --git a/tests/test_version_flag.py b/tests/package/test_version_flag.py similarity index 100% rename from tests/test_version_flag.py rename to tests/package/test_version_flag.py diff --git a/tests/test_search_lancedb.py b/tests/search/test_search_lancedb.py similarity index 100% rename from tests/test_search_lancedb.py rename to tests/search/test_search_lancedb.py diff --git a/tests/test_search_lancedb_capability.py b/tests/search/test_search_lancedb_capability.py similarity index 100% rename from tests/test_search_lancedb_capability.py rename to tests/search/test_search_lancedb_capability.py diff --git a/tests/test_search_lexical.py b/tests/search/test_search_lexical.py similarity index 99% rename from tests/test_search_lexical.py rename to tests/search/test_search_lexical.py index ededbaa7..924f954a 100644 --- a/tests/test_search_lexical.py +++ b/tests/search/test_search_lexical.py @@ -276,7 +276,7 @@ def test_explain_import_survives_graph_only_env() -> None: that the CLI source imports from search_scoring AND the invariant: blocking the vector stack makes search_lancedb unimportable while search_scoring (and the lexical explain renderer) stays importable.""" - jrag_py = Path(__file__).resolve().parent.parent / "src" / "java_codebase_rag" / "jrag.py" + jrag_py = Path(__file__).resolve().parent.parent.parent / "src" / "java_codebase_rag" / "jrag.py" m = re.search(r"from java_codebase_rag\.search\.(search_\w+) import explain_score_components", jrag_py.read_text(encoding="utf-8")) assert m, "explain_score_components import not found in jrag.py" assert m.group(1) == "search_scoring", (