About this project

# QMD - Query Markup Documents QMD is an on-device search engine designed for personal knowledge bases, documentation, meeting notes, and any markdown-based content. It runs entirely locally, combining BM25 full-text search, vector semantic search, and LLM-based reranking to deliver high-quality results without sending data to external services. ## Core Features - **Hybrid Search Pipeline**: Combines BM25 (FTS5), vector similarity search, and LLM reranking. Query expansion generates typed sub-queries (`lex` for keywords, `vec` for dense vectors, `hyde` for hypothetical document embeddings) that are routed to appropriate backends, fused via Reciprocal Rank Fusion (RRF), and reranked by an LLM. - **Context Tree**: Add hierarchical context to collections (e.g., `qmd://notes` → "Personal notes and ideas") that is returned alongside matching documents, helping LLMs make better contextual decisions. - **Local Models**: Uses GGUF models downloaded from HuggingFace and cached locally. Default embedding model is `embeddinggemma-300M-Q8_0` (~300MB). Custom models can be set via `QMD_EMBED_MODEL` environment variable (e.g., for multilingual corpora). - **AST-Aware Chunking**: Optional tree-sitter-based chunking for code files (TypeScript, JavaScript, Python, Go, Rust) produces higher-quality chunks; other file types use regex-based chunking. - **MCP Server**: Exposes a Model Context Protocol server with tools for querying, retrieving documents, batch retrieval, and status checks. Supports stdio and HTTP transports with security features (origin/host validation to prevent DNS rebinding attacks). - **SDK**: Programmatic access via a TypeScript/JavaScript SDK with methods for search, document retrieval, context management, and query expansion. ## Quick Start ```sh # Install globally (Node or Bun) npm install -g @tobilu/qmd # or bun install -g @tobilu/qmd # Create collections qmd collection add ~/notes --name notes qmd collection add ~/Documents/meetings --name meetings # Add context qmd context add qmd://notes "Personal notes and ideas" # Generate embeddings qmd embed # Search qmd search "project timeline" # Fast keyword search qmd vsearch "how to deploy" # Semantic search qmd query "quarterly planning process" # Hybrid + reranking (best quality) ``` ## CLI Commands - `qmd collection add <path> --name <name> [--mask <glob>]` — Add a collection - `qmd collection show <name>` — Show collection details - `qmd collection include/exclude <name>` — Toggle collection inclusion - `qmd collection update-cmd <name> '<command>'` — Set update command - `qmd embed [--chunk-strategy auto]` — Generate vector embeddings - `qmd search <query> [-c <collection>] [--json] [--files] [--min-score <n>]` — Keyword search - `qmd vsearch <query>` — Semantic search - `qmd query <query>` — Hybrid search with reranking - `qmd get <path|docid>` — Retrieve a document - `qmd multi-get <glob>` — Retrieve multiple documents - `qmd mcp [--http] [--port <n>] [--host <addr>] [--daemon]` — Start MCP server - `qmd status` — Show index health and MCP status ## MCP Server Exposed tools: - `query` — Search with typed sub-queries, RRF fusion, and optional reranking - `get` — Retrieve document by path, docid, or line range - `multi_get` — Batch retrieve by glob, comma-separated list, or docids - `status` — Index health and collection info HTTP transport (default port 8181) provides: - `POST /mcp` — MCP Streamable HTTP - `POST /query` (alias `/search`) — Structured search without MCP protocol - `GET /health` — Liveness check Security: Requests with non-loopback `Origin` headers are rejected (403). `Host` validation prevents DNS rebinding. Environment variables `QMD_ALLOWED_ORIGINS` and `QMD_ALLOWED_HOSTS` can extend allowed origins/hosts. ## SDK Usage ```js const { QmdStore } = require('@tobilu/qmd') const store = new QmdStore({ dbPath: './qmd.db', collections: { notes: { path: '/path/to/notes' } } }) // Simple search (auto-expanded) const results = await store.search({ query: 'authentication flow' }) // Structured query with typed sub-queries const results2 = await store.search({ queries: [ { type: 'vec', query: 'why do database connections time out under load' }, { type: 'lex', query: 'connection timeout' } ], collections: ['docs', 'notes'] }) // Disable reranking for speed const fast = await store.search({ query: 'auth', rerank: false }) // Metadata filtering const published = await store.search({ query: 'typescript', filter: { key: 'topics', operator: 'all', value: ['typescript'] } }) // Direct backend access const bm25Results = await store.bm25Search('auth') const vectorResults = await store.vectorSearch('auth') // Query expansion const expanded = await store.expandQuery('auth flow', { intent: 'user login' }) // Document retrieval const doc = await store.get('docs/readme.md') const body = await store.getDocumentBody('docs/readme.md', { maxLines: 100 }) // Context management await store.addContext('docs', '/api', 'REST API reference documentation') await store.removeContext('docs', '/api') ``` ## Search Pipeline Details 1. **Query Expansion**: Original query (weighted ×2) + 1 LLM variation 2. **Parallel Retrieval**: Each query searches both FTS and vector indexes 3. **Top-Rank Bonus**: Documents ranking #1 in any list get +0.05, #2-3 get +0.02 4. **Top-K Selection**: Take top 30 candidates for reranking 5. **Re-ranking**: LLM scores each document (yes/no with logprobs confidence) Score ranges: 0.0–0.2 low relevance, higher values indicate better matches. ## Model Configuration Default models: - Embedding: `embeddinggemma-300M-Q8_0` (~300MB) - Reranking: LLM-based (downloaded on demand) Custom embedding model example: ```sh export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf" ``` Note: Changing embedding models requires re-embedding all collections since vectors are not cross-compatible. ## Requirements - Node.js or Bun runtime - Sufficient local storage for models and embeddings - Optional: GPU/VRAM for faster LLM inference (models stay loaded in VRAM across requests) ## License Open-source software. See repository for details.