Skip to content

refactor(core): extract SearchReader and SemanticSearch from the search repository - #1565

Merged
phernandez merged 1 commit into
feat/1558-scoped-search-2-readerfrom
feat/1558-scoped-search-2b-reader
Sep 15, 2026
Merged

phernandez merged 1 commit into
feat/1558-scoped-search-2-readerfrom
feat/1558-scoped-search-2b-reader

Conversation

@phernandez

Copy link
Copy Markdown
Member

Part of #1558. Third PR in the stack, on top of #1563 (which is on #1562).

What changes

Retrieval leaves SearchRepositoryBase.

  • SearchReader(scope, fts, semantic=None) runs one PreparedSearchQuery over one ProjectScope in whichever mode it asks for: the engine's FtsBackend for FTS, SemanticSearch for vector and hybrid. It raises SemanticSearchDisabledError when a semantic mode is asked of a reader built without the semantic stack, and the same ValueError as before when a semantic mode has nothing to embed.
  • SemanticSearch(session_maker, scope, fts, vector, rerank=None) owns the vector and hybrid pipeline: adapter nearest-neighbour lookup, manifest hydration, the structured filter pass through candidate_keys, score fusion, reranking, and pagination. It is constructed only when a VectorRetrieval is present, so no stage re-checks availability.
  • VectorRetrieval and Reranking are frozen bundles of the live semantic stack. Hydrated chunks are a typed HydratedChunk, which retires the best_distance compatibility branch and the hasattr(self, "_semantic_vector_index") and _prepare_vector_session branches on the read path.
  • The repository keeps what only it knows: whether semantic search is enabled and whether its vector tables and adapter exist. search() runs that gate, then builds a reader from its current attributes for each call, because the semantic flag can flip at runtime ([BUG] Fatal crash on startup when python.org Python 3.12 is present alongside Homebrew on macOS #711) and tests retune thresholds between searches.
  • PreparedSearchQuery.has_filters names the predicate the vector path uses to decide whether to run the filter pass.
  • _distance_to_similarity is removed from the base and both repositories; the adapters already return calibrated similarity.
  • parse_chunk_key, vector_eligible, rerank_document_text, demote_tail, and current_vector_manifest_predicate are module functions in search_reader.

No query behavior changes. search_repository_base.py goes from 3002 to 1819 lines.

Tests

Test doubles construct the pipeline directly instead of subclassing the repository and patching its private hooks. FakeFts is a typed FtsBackend double that records the queries it was asked, and fake_vector_retrieval() is a VectorRetrieval over a stubbed adapter. Six repository subclasses and the _run_vector_query / _ensure_vector_tables / _prepare_vector_session patches across about fourteen files go away. The tests that still drive a real repository patch SemanticSearch._run_vector_query at class level.

tests/repository/test_search_reader.py is new and pins reader dispatch plus the pipeline edges the migrated tests do not reach. search_reader.py is at 100% line coverage in the unit suites.

Check Result
just fast-check passed
tests/repository, SQLite 1100 passed, 38 skipped
tests/repository, Postgres (testcontainers) 1073 passed, 41 skipped
tests/services, tests/api, tests/mcp, tests/cli, SQLite 3476 passed, 9 skipped
just doctor passed

Next in the stack

PR 3 adds QUERY /v2/search/ and a scoped service method that builds a SearchReader over a multi-project ProjectScope without a repository.

🤖 Generated with Claude Code

https://claude.ai/code/session_019YW9ysxugGGBCNEGzsxtFV

…ch repository

Retrieval leaves SearchRepositoryBase. SearchReader runs one prepared query over
one ProjectScope in whichever mode it asks for; SemanticSearch owns vector and
hybrid retrieval (adapter lookup, manifest hydration, the structured filter pass,
score fusion, reranking, pagination) over a VectorRetrieval that is present or
absent instead of probed with hasattr. The repository keeps what only it knows,
whether semantic search is enabled and its vector tables exist, and builds a
reader per call from its current state.

Hydrated chunks are a typed HydratedChunk, which retires the best_distance
compatibility branch and the per-backend _distance_to_similarity hooks the
adapters had already replaced. PreparedSearchQuery gains has_filters, the
predicate the vector path uses to decide whether to run the filter pass.

Test doubles construct the pipeline directly (a FakeFts backend and a
VectorRetrieval over a stubbed adapter) instead of subclassing the repository
and patching its private hooks. No query behavior changes.

Part of #1558.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_019YW9ysxugGGBCNEGzsxtFV
Signed-off-by: phernandez <[email protected]>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@phernandez
phernandez added this pull request to stack #1564 September 15, 2026 19:12
@phernandez
phernandez merged commit 0a57d35 into main Sep 15, 2026
34 checks passed
@phernandez
phernandez deleted the feat/1558-scoped-search-2b-reader branch September 15, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant