diff --git a/java_codebase_rag/installer.py b/java_codebase_rag/installer.py index 60c4913..d4d90d6 100644 --- a/java_codebase_rag/installer.py +++ b/java_codebase_rag/installer.py @@ -1799,7 +1799,19 @@ def run_install( return e.code # Stage 2: Embedding model - resolved_model = resolve_model(model, non_interactive=non_interactive) + from java_codebase_rag.pipeline import vector_stack_installed + + if not vector_stack_installed(): + # Graph-only install (macOS Intel): no torch/lancedb, so there is no vector + # index to embed into — the embedding-model choice is inert here. Skip the + # prompt and let init build the graph (vectors phase auto-skipped). + print( + "Skipping embedding model selection: vector stack not installed on this " + "platform (graph-only mode)." + ) + resolved_model = "auto" + else: + resolved_model = resolve_model(model, non_interactive=non_interactive) # Stage 3-4: Agent host + scope + surface selection prior_surface = _prior_surface_from_marker(cwd) diff --git a/java_codebase_rag/pipeline.py b/java_codebase_rag/pipeline.py index a549cad..bcd15f8 100644 --- a/java_codebase_rag/pipeline.py +++ b/java_codebase_rag/pipeline.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +import importlib.util import os import shutil import subprocess @@ -200,6 +201,19 @@ def is_graph_preflight_blocker(proc: subprocess.CompletedProcess[str]) -> bool: return bool(proc.returncode in (126, 127) and len(getattr(proc, "args", ()) or ()) <= 1) +def vector_stack_installed() -> bool: + """True when the optional vector stack (cocoindex/lancedb/sentence-transformers) is importable. + + False on graph-only installs (macOS Intel), where PEP 508 markers exclude the trio. + Used to skip vector-only wizard steps (e.g. embedding-model selection) and to preflight + branching without spawning cocoindex. Probes all three since they are gated together. + """ + return all( + importlib.util.find_spec(m) is not None + for m in ("cocoindex", "lancedb", "sentence_transformers") + ) + + def _run_cocoindex_update_impl( env: dict[str, str], *, diff --git a/tests/test_graph_only_boot.py b/tests/test_graph_only_boot.py index c625476..f050b74 100644 --- a/tests/test_graph_only_boot.py +++ b/tests/test_graph_only_boot.py @@ -78,3 +78,25 @@ def test_preflight_blocker_detects_graph_only_install() -> None: # non-zero exit is a failure, not a skip. assert is_cocoindex_preflight_blocker(_completed(1, (exe, "update", "t", "-f"))) is False assert is_cocoindex_preflight_blocker(_completed(0, (exe, "update", "t", "-f"))) is False + + +def test_vector_stack_installed_reports_absent_when_blocked() -> None: + # The installer wizard gates its embedding-model step on vector_stack_installed(). + # Deterministic across platforms: blocking the modules in a fresh subprocess must + # report False (this is the macOS Intel reality). + proc = subprocess.run( + [ + sys.executable, + "-c", + "import sys\n" + "for m in ('lancedb','pylance','torch','sentence_transformers','cocoindex'):\n" + " sys.modules[m] = None\n" + "from java_codebase_rag.pipeline import vector_stack_installed\n" + "print('INSTALLED:' + str(vector_stack_installed()))", + ], + capture_output=True, + text=True, + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + assert "INSTALLED:False" in proc.stdout