Architecture

Hexagonal layout. application/ services (IndexingService, ProjectIndexer, DocsSearch, ApiSearch, PackageLookup, ModuleInspector, LookupService, ReferenceService, TreeService, OverviewService, DecisionService, MultiProjectSearch) depend only on Protocols defined in storage/protocols.pyChunkStore, PackageStore, ModuleMemberStore, DocumentTreeStore, ReferenceStore, UnitOfWork, TextSearchable, VectorSearchable, HybridSearchable, FilterAdapter, plus the newer GraphSearchable, NodeScoreStore, DecisionStore, and MultiVectorStore — and in retrieval/protocols.py (Embedder, ResultFuser). Concrete adapters (Sqlite* repositories, TurboQuantVectorStore / TurboQuantUnitOfWork, the SearchBackend / SqliteCompositeBackend capability factory, CompositeUnitOfWork) live behind them.

The composition root (server.py + __main__.py + storage/factories.py) builds one uow_factory closure and threads it through every service. Rust acceleration sits behind a substitution boundary: _fast.py resolves to _native (compiled) or _fallback.py (pure Python) with identical signatures on both sides, so the package works with or without the compiled extension.

pydocs-mcp/
├── Cargo.toml                  # Rust dependencies
├── pyproject.toml              # Python package config (maturin mixed layout)
├── src/lib.rs                  # Rust: walker, hasher, parser, file readers (PyO3)
└── python/pydocs_mcp/
    ├── __main__.py             # CLI entry (serve / index / search / symbol / refs / …)
    ├── _fast.py / _fallback.py # Rust-or-pure-Python substitution boundary
    ├── db.py                   # SQLite schema + cache lifecycle + FTS rebuild
    ├── deps.py                 # Dependency resolution
    ├── models.py               # Value objects (Package / Chunk / SearchQuery, content hashing)
    ├── filters.py              # Canonical backend-neutral filter-tree module
    ├── multirepo.py            # Multi-repo bundle loading (serve --workspace / --db)
    ├── extraction/             # Chunkers, member extractors, ingestion pipeline, embedders
    ├── application/            # Use-case services (indexing, search, lookup, references, trees)
    ├── storage/                # SQLite + TurboQuant adapters, Protocols, UnitOfWork
    ├── retrieval/              # RetrieverStep ABC + RetrieverPipeline + steps/
    ├── serve/                  # File watcher (pydocs-mcp watch / serve --watch)
    ├── ask_your_docs/          # Optional [harness-ask-your-docs] extra: LangGraph agent + Streamlit UI
    ├── defaults/               # Shipped default_config.yaml
    ├── pipelines/              # Built-in pipeline YAML blueprints
    └── server.py               # MCP server (nine task-shaped tools)

Every layer boundary is a Protocol and every swappable component has a registry, so new backends and steps land as one file each with no modification to existing code. The full extension menu — vector-store backends, filter formats, pipeline steps, fusion algorithms, planned features — is catalogued in EXTENSIONS.md; the contributor-facing rules (SOLID, async patterns, MCP API rules, single-source-of-truth defaults) are in CLAUDE.md.