codesearch-mcp 0.0.18-alpha → 0.2.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -1
- package/README.md +194 -23
- package/dist/app.js +38 -2
- package/dist/src/commands/IndexCommand.js +55 -1
- package/dist/src/commands/McpCommand.js +6 -2
- package/dist/src/config-error.d.ts +7 -0
- package/dist/src/config-error.js +14 -0
- package/dist/src/config-store.d.ts +73 -17
- package/dist/src/config-store.js +142 -27
- package/dist/src/config.d.ts +36 -11
- package/dist/src/config.js +89 -27
- package/dist/src/constants.d.ts +42 -2
- package/dist/src/constants.js +61 -7
- package/dist/src/container.js +5 -3
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +111 -0
- package/dist/src/db/schema.js +15 -1
- package/dist/src/help.js +6 -5
- package/dist/src/interfaces/IAstParser.d.ts +14 -0
- package/dist/src/interfaces/IChunker.d.ts +24 -0
- package/dist/src/interfaces/IChunker.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +31 -0
- package/dist/src/interfaces/IEmbedder.d.ts +14 -2
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/interfaces/ISearcher.d.ts +1 -0
- package/dist/src/interfaces/index.d.ts +2 -1
- package/dist/src/owner-path.d.ts +10 -0
- package/dist/src/owner-path.js +27 -0
- package/dist/src/services/AstChunker.d.ts +42 -0
- package/dist/src/services/AstChunker.js +158 -0
- package/dist/src/services/AstParserService.d.ts +6 -0
- package/dist/src/services/AstParserService.js +31 -2
- package/dist/src/services/DatabaseService.d.ts +17 -1
- package/dist/src/services/DatabaseService.js +116 -25
- package/dist/src/services/EmbedderFactory.d.ts +9 -0
- package/dist/src/services/EmbedderFactory.js +26 -0
- package/dist/src/services/IndexerService.d.ts +21 -5
- package/dist/src/services/IndexerService.js +127 -44
- package/dist/src/services/InfinityEmbedder.d.ts +38 -0
- package/dist/src/services/InfinityEmbedder.js +88 -0
- package/dist/src/services/LoggerService.d.ts +3 -1
- package/dist/src/services/LoggerService.js +17 -3
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
- package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
- package/dist/src/services/SearcherService.d.ts +21 -1
- package/dist/src/services/SearcherService.js +129 -28
- package/dist/src/services/VoyageEmbedder.d.ts +22 -0
- package/dist/src/services/VoyageEmbedder.js +72 -0
- package/dist/src/services/WebServerService.d.ts +57 -14
- package/dist/src/services/WebServerService.js +134 -26
- package/dist/src/services/http-retry.d.ts +19 -0
- package/dist/src/services/http-retry.js +54 -0
- package/dist/src/services/infinity-provisioning.d.ts +115 -0
- package/dist/src/services/infinity-provisioning.js +516 -0
- package/dist/src/services/legacy-ollama-index.d.ts +24 -0
- package/dist/src/services/legacy-ollama-index.js +61 -0
- package/dist/src/services/reranker.d.ts +15 -0
- package/dist/src/services/reranker.js +44 -0
- package/dist/src/setup.d.ts +43 -0
- package/dist/src/setup.js +227 -55
- package/dist/src/tools/SearchCodebaseTool.js +1 -1
- package/dist/src/tools/format-helpers.js +2 -1
- package/dist/src/web/dashboard.js +5 -0
- package/docker/docker-compose.infinity-gpu.yml +19 -0
- package/docker/docker-compose.yml +20 -5
- package/docs/web-dashboard.md +36 -17
- package/package.json +18 -3
- package/dist/src/ast.d.ts +0 -2
- package/dist/src/ast.js +0 -110
- package/dist/src/commands/SetupCommand.d.ts +0 -7
- package/dist/src/commands/SetupCommand.js +0 -14
- package/dist/src/db/data-source.d.ts +0 -2
- package/dist/src/db/data-source.js +0 -13
- package/dist/src/db/entities/code-chunk.entity.d.ts +0 -9
- package/dist/src/db/entities/code-chunk.entity.js +0 -54
- package/dist/src/db/entities/code-dependency.entity.d.ts +0 -9
- package/dist/src/db/entities/code-dependency.entity.js +0 -54
- package/dist/src/db/entities/code-symbol.entity.d.ts +0 -10
- package/dist/src/db/entities/code-symbol.entity.js +0 -59
- package/dist/src/db/init.d.ts +0 -2
- package/dist/src/db/init.js +0 -62
- package/dist/src/env-loader.d.ts +0 -1
- package/dist/src/env-loader.js +0 -41
- package/dist/src/indexer.d.ts +0 -17
- package/dist/src/indexer.js +0 -242
- package/dist/src/mcp.d.ts +0 -1
- package/dist/src/mcp.js +0 -403
- package/dist/src/ollama.d.ts +0 -2
- package/dist/src/ollama.js +0 -6
- package/dist/src/searcher.d.ts +0 -74
- package/dist/src/searcher.js +0 -446
- package/dist/src/services/OllamaEmbedder.d.ts +0 -14
- package/dist/src/services/OllamaEmbedder.js +0 -63
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,53 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.2.0-alpha] - 2026-09-29
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **Index consistency — orphan cleanup and project pruning** — `rag-index` now removes orphaned rows left by deleted files, model changes, and deselected projects. Deleted files and `.ragignore`-excluded files are removed from all four index tables (`code_chunk`, `code_symbol`, `code_call`, `code_dependency`) in one transaction per project. `find_symbol`, `get_callers`, `get_dependencies`, and `get_dependents` no longer return results from files that no longer exist on disk. Projects outside the current selection are automatically pruned from the index; each gets a `Removed project <name> (not selected)` log line. A failure pruning one project does not block others or stop indexing, but exits non-zero.
|
|
11
|
+
- **Candidate floor for reranking** — the reranker's candidate pool no longer scales only with `limit`. A new `reranker.candidateFloor` (default `80`) guarantees a minimum pool regardless of `limit`: `fetchLimit = min(max(limit × overfetch, candidateFloor), 200)` when reranking is enabled. Fixes recall starvation at small limits (the default `limit = 5` previously fed the reranker only 15 candidates, so its best matches never entered the window). `reranker.overfetch` default raised `3 → 5`. Applies immediately to search — no re-index.
|
|
12
|
+
- **`logging.searchMetrics` search-tuning telemetry** — a new boolean (default `false`) that emits one structured line per `search_codebase` query at `info`, **independent of `logging.level`**, with the signals needed to size the pool for your hardware: `fetchLimit`, `candidatesReturned`, `poolSaturated`, `maxCosineRankSurvived`, `distinctFilesInResult`, `topFileShare`, `best/worstResultScore`, and `embed/rerank/db/totalMs`. Never logs chunk content, embeddings, or credentials.
|
|
13
|
+
- **Infinity auto-recreates on a missing model, verifying embed + reranker together** — a managed Infinity container that doesn't yet serve the required model set is now automatically recreated (previously only the embedding model was checked, and only in the unreachable branch loaded the reranker). `rag-setup` and the web dashboard verify the full set (embedding + reranker, when on the same server); indexing verifies the embedding model only, so a bad reranker model never blocks or restarts indexing. Classification of managed-vs-external now also checks the compose file's actual published port (not just that a compose file exists), and a crashed or restart-looping container fails fast during the readiness wait instead of hanging out the full 30-minute timeout.
|
|
14
|
+
- **Managed compose file is now the durable source of truth for Infinity's model set** — the model set is written into `INFINITY_MODEL_ID` as a literal (no more `${INFINITY_MODEL_ID:-…}` env interpolation), so a plain `docker compose up -d` after a host reboot or manual restart serves the configured models instead of falling back to a hard-coded default. The write is atomic and preserves the file's permissions.
|
|
15
|
+
- **Web dashboard recreates Infinity in the background** — saving a config that changes the required Infinity model set (e.g. enabling reranking) now triggers the recreate automatically, without blocking the `PUT /api/config` response. New `GET /api/backend-status` endpoint reports progress (`idle`/`recreating`/`ready`/`failed`); the `PUT` response gains a `backend: { action, message }` field. Concurrent saves queue safely — one Docker run at a time, with at most one follow-up applying the latest save.
|
|
16
|
+
- **`InfinityEmbedder` implements `listModels()`** — lists served models via `/models` (2s limit), so the dashboard's model suggestions keep working for the default provider now that `OllamaEmbedder` is gone.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Reranking enabled by default** — `reranker.enabled` is now `true` by default. The shipped `docker-compose.yml` template includes both the embedding model (`jinaai/jina-embeddings-v2-base-code`) and the reranker model (`mixedbread-ai/mxbai-rerank-xsmall-v1`) in `INFINITY_MODEL_ID`, so a fresh `rag-setup` serves both without extra steps. Cloud embedding providers (OpenAI, Voyage) don't expose a `/rerank` endpoint — disable reranking with `rag-config set reranker.enabled false` or point `reranker.baseURL` at a separate server.
|
|
21
|
+
- **Import/re-export-only chunks are no longer indexed** — the AST chunker now drops chunks whose only significant content is `import` / `export … from` / `export *` / `export {…}` statements (≈5–8% of chunks on a typical corpus). They are never a search answer and previously crowded out real results. The dependency graph is unaffected (edges come from the parser, not chunk text). Takes effect on the next per-file change or `rag-index --reindex`.
|
|
22
|
+
|
|
23
|
+
### Changed (breaking)
|
|
24
|
+
|
|
25
|
+
- **The `ollama` embedding provider is removed** — `OllamaEmbedder` and the `ollama` provider enum value are gone; the managed `ollama` Docker service is removed from the shipped `docker-compose.yml`. Ollama remains fully usable by pointing `embedding.provider: openai-compatible` at `http://localhost:11434/v1` (it already exposes an OpenAI-style `/v1/embeddings` endpoint) — there is no capability loss, only the dedicated code path and the auto-pull-on-index convenience.
|
|
26
|
+
- **Default provider is now `infinity`, default model `jinaai/jina-embeddings-v2-base-code`** (was `ollama` / `bge-m3`); default `reranker.model` is now `mixedbread-ai/mxbai-rerank-xsmall-v1`.
|
|
27
|
+
- **A stored config that still selects Ollama now fails to load, with guidance, instead of being silently reinterpreted.** `embedding.provider: ollama`, a legacy top-level `ollama` block, or a provider-less `embedding` block that still points at Ollama's defaults (port `11434`, or a model name Ollama used to own such as `bge-m3`) all raise a clear error naming the fix (`openai-compatible` at `http://localhost:11434/v1`, via `rag-config edit` or `rag-setup`) and the required `rag-index --reindex` afterward. `rag-config set`/`rag-config reset` cannot fix it (they load the same invalid config); `rag-config edit` can, since it opens the file even while invalid.
|
|
28
|
+
- **`rag-index` refuses to silently rebuild an index that was built by `ollama`** — including an install predating the `index_meta` table (treated as a legacy `ollama`/`bge-m3` index). A plain `rag-index` against such an index now prints an explanation to stderr and exits non-zero before touching the backend or the database; `rag-index --reindex` is the explicit confirmation to rebuild it under the new provider. More generally, `rag-index` now exits non-zero on **any** backend-readiness or schema-reconciliation failure — previously it printed "Embedding setup failed" and exited 0, which a script could not detect. `rag-mcp`/`search_codebase` apply the same "pre-`index_meta` index is `ollama`/`bge-m3`" interpretation read-only, so a stale local install fails with the re-index instruction instead of an opaque pgvector dimension-mismatch error.
|
|
29
|
+
- **`rag-setup` now saves the config before provisioning Infinity** — previously Infinity provisioning ran first, so a Docker failure there could abandon the whole wizard without ever writing `config.json`. The database-container step keeps its original position and abort-without-saving behavior.
|
|
30
|
+
|
|
31
|
+
## [0.1.0-alpha] - 2026-07-12
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **`infinity` embedding provider** — a first-class local backend (`embedding.provider = infinity`) that serves both `/embeddings` and `/rerank` from a single [Infinity](https://github.com/michaelfeil/infinity) container (e.g. `jinaai/jina-embeddings-v2-base-code` + `Alibaba-NLP/gte-reranker-modernbert-base`), so local reranking works out of the box — something Ollama does not provide. Reuses the OpenAI-compatible HTTP path and adds only container-readiness behavior. Selecting `infinity` in `rag-setup` optionally provisions a reranker model and auto-wires `reranker.*` to the same server. Managed provisioning vs. an external/already-running server is decided by a `/health` probe (no config flag) — the same "already running?" probe now also defaults `rag-setup`'s "start Docker?" prompt to no for a reachable Ollama. The managed container prefers an NVIDIA GPU and falls back to CPU, and is recreated when the configured model set changes (Infinity binds models at startup). The managed container also gets `restart: unless-stopped` (survives host reboot) and disables Infinity's model warmup on CPU (a multi-minute benchmark at every boot; kept on for GPU where it is cheap). `jinaai/jina-embeddings-v2-base-code` (768 dims, the Infinity default) and `BAAI/bge-m3` (1024) are added to the model registry so their dimension resolves without a probe.
|
|
36
|
+
- **Reranking is `openai-compatible`-only** — `reranker.provider` no longer accepts `ollama` (Ollama has no rerank API — the previous `/api/rerank` path always 404'd and silently fell back to vector order); the only valid backend is an OpenAI-compatible `/rerank` server (Infinity, TEI, vLLM). The default provider is now `openai-compatible`, and an existing `reranker.provider: 'ollama'` is migrated automatically on read. The embedding provider stays independent of the rerank server, so Ollama embeddings + an Infinity reranker is a valid combination.
|
|
37
|
+
- **Pluggable embedding backends** — the embedder is now selected by `embedding.provider` via a factory, with three implementations: `ollama` (native `/api/embed`, the **backward-compatible default**), `openai-compatible` (one code path covering **OpenAI cloud, Infinity, TEI, vLLM, LM Studio, LocalAI** via a configurable `embedding.baseURL`), and `voyage` (native schema with mandatory `input_type`). To run a faster local backend or a cloud embedder you now edit config, not code.
|
|
38
|
+
- **Batch embedding with retry** — `IEmbedder` gains `embedBatch(texts, inputType?)`; indexing sends one request per batch (`embedding.batchSize`, default 32) instead of one call per chunk, with bounded exponential backoff on HTTP 429/5xx. Makes cloud/Infinity indexing of a whole monorepo practical.
|
|
39
|
+
- **Query/document input-type asymmetry** — `embed`/`embedBatch` take an optional `inputType` (`query` | `document`); search embeds as `query`, indexing as `document`. Voyage forwards it as `input_type`; Ollama and the OpenAI-compatible `/embeddings` endpoint ignore it (OpenAI has no such field).
|
|
40
|
+
- **Flexible vector dimension** — the `code_chunk.embedding` column width now derives from the active model (`embedding.dimensions` override → model registry → one-shot runtime probe) instead of a hardcoded 1024, capped at ≤ 2000 (pgvector HNSW limit); larger cloud models reduce via the Matryoshka `dimensions`/`output_dimension` request parameter.
|
|
41
|
+
- **`index_meta` source-of-truth table + mismatch guard** — a single-row table records `{ provider, model, dimensions }`. `search_codebase` (before embedding a query) and `rag-mcp` (at start) refuse with a clear "run `rag-index --reindex`" error when the configured model disagrees with the indexed one, instead of returning noise from a foreign vector space. A dimension change drops+recreates `code_chunk` at the new width on re-index; a same-dimension model change clears its rows.
|
|
42
|
+
- **API-key secrets via `.env`** — keys live in `~/.config/codesearch-mcp/.env` (chmod 600), loaded into the environment at startup before config is read (a real env var wins over the file), so the client-spawned `rag-mcp` sees them too. Config values support an `env:VAR_NAME` reference; a missing referenced variable fails with a clear error. The dashboard shows `✔ set via <VAR>` for `env:` refs and masks raw keys.
|
|
43
|
+
- **Auto-pull of local Ollama models with live progress** — `rag-index` now pulls the configured Ollama model if it is not present locally (only for `provider: ollama`; a no-op for cloud providers), so switching to (or back to) a local model that was never pulled just works without re-running `rag-setup`. The pull streams progress: a live single-line bar on a TTY (`downloading [██████░░░░] 62% (245.7/393.2 MB)`), de-duplicated status lines otherwise. `rag-setup`'s in-container pull now renders Ollama's native progress bar (allocates a TTY via `docker exec -t` when interactive) and pulls the model you actually chose (previously hardcoded to `bge-m3`).
|
|
44
|
+
- **Cloud providers skip the Ollama container** — when a non-Ollama provider is selected, `rag-setup`'s Docker helper starts only the database service (`docker compose up -d db`) and skips the Ollama wait + model pull, instead of bringing up an unused Ollama container.
|
|
45
|
+
- **Owner-symbol breadcrumb in search results** — each `search_codebase` result now shows a `Symbol:` line naming the declaration the chunk belongs to (`IndexerService.ingestFile`, `collectFiles`, …), so the enclosing class/method is visible without opening the file. Computed at index time into a new nullable `code_chunk.ownerPath` column (added via `ADD COLUMN IF NOT EXISTS`): the deepest declaration containing the chunk's start line wins, so a method beats its enclosing class. Method/constructor/accessor ranges are extracted purely for this breadcrumb and are **not** written to `code_symbol`, so `find_symbol` is unchanged. Module-scope chunks (imports) and rows indexed before this release show no breadcrumb; re-index to populate. No embedding-dimension change.
|
|
46
|
+
- **AST-aware chunking** — TS/JS files are now chunked along top-level declaration boundaries (functions, classes, interfaces, types, exported variables) instead of at fixed character offsets, so a declaration is no longer split across chunks and search-hit line ranges map to coherent units of code. Declarations larger than `indexing.chunkSize` and the gaps between them (imports, top-level code) are sub-split by the recursive splitter, so no chunk exceeds the size limit and no source line is dropped. Reuses the symbol ranges already produced during indexing — no extra parse. New config key `indexing.chunkStrategy` (`ast` \| `character`, default `ast`); set it to `character` to restore the previous fixed-size behavior. Schema files (`.prisma`/`.graphql`/`.gql`) and files without symbol analysis keep the character splitter. Takes effect on the next re-index; no embedding-dimension or DB-schema change.
|
|
47
|
+
|
|
48
|
+
### Changed (breaking)
|
|
49
|
+
|
|
50
|
+
- **Config restructure** — the `ollama.*` section is replaced by `embedding.*` (`provider`, `baseURL`, `apiKey`, `model`, `dimensions`, `batchSize`); `reranker.*` gains `provider`/`baseURL`/`apiKey` mirroring the embedding taxonomy (Infinity can serve both from one server). **Legacy `ollama.host`/`ollama.model` configs are migrated to `embedding.*` automatically on read**, so existing installs keep working without manual edits.
|
|
51
|
+
- **Query translation removed** — the `IEmbedder.translate` method and the `search.autoTranslate` / `search.translateModel` config keys are gone; queries are embedded as-is. The keys are silently ignored if still present.
|
|
52
|
+
- **Re-index required on model/dimension change** — switching the embedding model, provider, or dimension now requires `rag-index --reindex` (a dimension change recreates `code_chunk`). Staying on Ollama + `bge-m3` keeps dimension 1024, backfills `index_meta`, and forces no re-index. The mismatch guard makes any disagreement loud rather than silently corrupting results.
|
|
7
53
|
|
|
8
54
|
## [0.0.18-alpha] - 2026-07-04
|
|
9
55
|
|
package/README.md
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
# codesearch-mcp
|
|
2
2
|
|
|
3
|
-
Semantic
|
|
3
|
+
Semantic code search for AI agents — finds code by **meaning**, not keywords. Indexes your local projects into a PostgreSQL vector database and exposes search via the Model Context Protocol (MCP), so Claude can search your entire codebase automatically.
|
|
4
4
|
|
|
5
|
-
Ask your AI agent things like *"where is the payment retry logic?"* or *"show me how auth tokens are validated"* —
|
|
5
|
+
Ask your AI agent things like *"where is the payment retry logic?"* or *"show me how auth tokens are validated"* — it will find the right code even if those exact words never appear.
|
|
6
|
+
|
|
7
|
+
**Why codesearch-mcp:**
|
|
8
|
+
|
|
9
|
+
- **Dramatically fewer tokens** — without RAG, answering a cross-project question means discovery calls, directory listings, and reading several wrong files before finding the right one; a workspace of 10 projects can easily mean 50 000+ lines to sift through. With RAG, one `search_codebase` call returns 5 targeted chunks (~200–500 lines total) from across *all* projects at once — the model gets the right code immediately, with no exploration overhead
|
|
10
|
+
- **Reranker narrows the result further** — a cross-encoder reranker (enabled by default) re-scores the candidate pool so the top results are the most relevant, not just the closest vector; fewer results means less context consumed per answer
|
|
11
|
+
- **Semantic, not grep** — embeddings capture intent; queries in plain English or pseudocode find the relevant code regardless of naming
|
|
12
|
+
- **Local and private** — default setup runs entirely on your machine (Infinity embeddings + PostgreSQL); no code leaves your network
|
|
13
|
+
- **Code-structure aware** — chunks align to function and class boundaries; each result shows which declaration it belongs to (`InvoiceService.create`, `collectFiles`, …), so the model understands context without reading the surrounding file
|
|
14
|
+
- **Import graph** — `include_graph_context` appends direct imports and dependents inline with each result, replacing separate `get_dependencies` / `get_dependents` calls
|
|
6
15
|
|
|
7
16
|
> **Alpha** — works well locally, API may change before 1.0.
|
|
8
17
|
|
|
@@ -10,16 +19,17 @@ See `CHANGELOG.md` for version history.
|
|
|
10
19
|
|
|
11
20
|
## How it works
|
|
12
21
|
|
|
13
|
-
1. `rag-index` scans your projects, chunks source files, embeds them
|
|
22
|
+
1. `rag-index` scans your projects, chunks source files, embeds them using your configured embedding backend (default: [jina-embeddings-v2-base-code](https://huggingface.co/jinaai/jina-embeddings-v2-base-code) via a local [Infinity](https://github.com/michaelfeil/infinity) container), and stores vectors in PostgreSQL
|
|
14
23
|
2. `rag-mcp` exposes an MCP server — Claude calls it automatically when you ask about code
|
|
15
24
|
3. Incremental — only changed files are re-indexed on subsequent runs
|
|
25
|
+
4. Index stays consistent — deleted files, `.ragignore`-excluded files, and deselected projects are removed from all four index tables on the next `rag-index` run
|
|
16
26
|
|
|
17
|
-
**Stack:** TypeScript · Drizzle ORM · PostgreSQL + pgvector ·
|
|
27
|
+
**Stack:** TypeScript · Drizzle ORM · PostgreSQL + pgvector · Infinity (jina-embeddings-v2-base-code) · LangChain text splitters · ts-morph
|
|
18
28
|
|
|
19
29
|
## Requirements
|
|
20
30
|
|
|
21
31
|
- Node.js 18+
|
|
22
|
-
- Docker (for local DB +
|
|
32
|
+
- Docker (for local DB + Infinity) — or bring your own PostgreSQL with pgvector + an embedding backend (see [Embedding providers](#embedding-providers))
|
|
23
33
|
|
|
24
34
|
## Installation
|
|
25
35
|
|
|
@@ -35,11 +45,19 @@ rag-setup
|
|
|
35
45
|
|
|
36
46
|
Walks you through:
|
|
37
47
|
- **Database** — enter connection details; optionally starts Docker containers automatically
|
|
38
|
-
- **
|
|
48
|
+
- **Embedding backend** — pick a provider (`infinity` / `openai-compatible` / `voyage`), its model, and an API key if the provider needs one (see [Embedding providers](#embedding-providers))
|
|
39
49
|
- **Logging** — choose log level and directory (defaults: `error` level, `~/.config/codesearch-mcp/logs/`)
|
|
40
50
|
|
|
41
51
|
Config is saved to `~/.config/codesearch-mcp/config.json`.
|
|
42
52
|
|
|
53
|
+
## Register with Claude Code
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add codesearch-mcp -s user -- rag-mcp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then ask Claude anything about your code — it will use the tools below automatically.
|
|
60
|
+
|
|
43
61
|
## Index your projects
|
|
44
62
|
|
|
45
63
|
```bash
|
|
@@ -62,14 +80,6 @@ If `workspaceRoot` is an [Nx](https://nx.dev) workspace (detected by `nx.json`),
|
|
|
62
80
|
|
|
63
81
|
No configuration needed — detection is automatic. Plain (non-Nx) workspaces keep the original behavior: each immediate subdirectory with a `package.json` is one project.
|
|
64
82
|
|
|
65
|
-
## Register with Claude Code
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
claude mcp add codesearch-mcp -s user -- rag-mcp
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Then ask Claude anything about your code — it will use the tools below automatically.
|
|
72
|
-
|
|
73
83
|
## Web dashboard
|
|
74
84
|
|
|
75
85
|
```bash
|
|
@@ -78,15 +88,15 @@ rag-web # http://127.0.0.1:4319 (--port to change)
|
|
|
78
88
|
|
|
79
89
|
A local web UI (bound to `127.0.0.1` only) with a schema-driven config editor,
|
|
80
90
|
an overview of indexed projects, and the interactive dependency graph. The
|
|
81
|
-
config form shows the applied default for any unset key and
|
|
82
|
-
model fields (
|
|
91
|
+
config form shows the applied default for any unset key and suggests dimension-compatible
|
|
92
|
+
models in the model fields (free text is always accepted).
|
|
83
93
|
See [docs/web-dashboard.md](docs/web-dashboard.md) for the full reference.
|
|
84
94
|
|
|
85
95
|
## MCP Tools
|
|
86
96
|
|
|
87
97
|
| Tool | Description |
|
|
88
98
|
|------|-------------|
|
|
89
|
-
| `search_codebase` | Semantic search across all indexed projects. Accepts `include_graph_context` to append direct imports/dependents to each result,
|
|
99
|
+
| `search_codebase` | Semantic search across all indexed projects. Accepts `include_graph_context` to append direct imports/dependents to each result, `include_neighbors` for surrounding chunks, and `threshold` to override the similarity threshold for that query |
|
|
90
100
|
| `list_projects` | List projects with metadata and chunk counts |
|
|
91
101
|
| `find_symbol` | Find a function, class, or type by exact name — faster than semantic search |
|
|
92
102
|
| `list_files` | Browse the directory tree of a project |
|
|
@@ -104,6 +114,7 @@ Add `.ragmeta.json` to a project root to enrich `list_projects` and generate a v
|
|
|
104
114
|
{
|
|
105
115
|
"description": "Main API server",
|
|
106
116
|
"stack": "NestJS, GraphQL, PostgreSQL",
|
|
117
|
+
"notes": "Uses optimistic locking — see order-service/docs/concurrency.md",
|
|
107
118
|
"relations": {
|
|
108
119
|
"consumed_by": ["crm-frontend"],
|
|
109
120
|
"consumes": ["billing-service", "auth-service"]
|
|
@@ -111,14 +122,16 @@ Add `.ragmeta.json` to a project root to enrich `list_projects` and generate a v
|
|
|
111
122
|
}
|
|
112
123
|
```
|
|
113
124
|
|
|
114
|
-
After each indexing run an interactive `project-graph.html` is written to the workspace root.
|
|
125
|
+
After each indexing run an interactive `project-graph.html` is written to the workspace root. The same visualization is available in the web dashboard's Graph tab (`rag-web`).
|
|
115
126
|
|
|
116
127
|
## Graph context
|
|
117
128
|
|
|
118
129
|
Pass `include_graph_context: true` to `search_codebase` to see direct imports and dependents inline with each result — without extra `get_dependencies` / `get_dependents` calls:
|
|
119
130
|
|
|
120
131
|
```
|
|
121
|
-
**File:** src/services/InvoiceService.ts
|
|
132
|
+
**File:** src/services/InvoiceService.ts
|
|
133
|
+
**Symbol:** InvoiceService.create
|
|
134
|
+
**Line:** 45
|
|
122
135
|
...code...
|
|
123
136
|
**Relevance:** 0.32
|
|
124
137
|
**Imports:** src/repositories/InvoiceRepo.ts · src/dto/CreateInvoiceDto.ts
|
|
@@ -127,6 +140,8 @@ Pass `include_graph_context: true` to `search_codebase` to see direct imports an
|
|
|
127
140
|
|
|
128
141
|
Useful for immediately understanding a file's role in the dependency graph.
|
|
129
142
|
|
|
143
|
+
Each result also shows a **Symbol** breadcrumb — the declaration the chunk belongs to (`InvoiceService.create`, `collectFiles`, …) — so you can see the enclosing class/method without opening the file. It is omitted for module-scope chunks and for projects indexed before this feature (re-index to populate).
|
|
144
|
+
|
|
130
145
|
## Excluding files
|
|
131
146
|
|
|
132
147
|
Create `.ragignore` in a project root — same syntax as `.gitignore`:
|
|
@@ -162,6 +177,7 @@ rag-config show # print current config (password redacted)
|
|
|
162
177
|
rag-config get <key> # print a single value
|
|
163
178
|
rag-config set <key> <value> # update a value
|
|
164
179
|
rag-config reset <key> # remove key (reverts to default)
|
|
180
|
+
rag-config edit # open config in $EDITOR; validates on save (use this to recover an invalid config)
|
|
165
181
|
```
|
|
166
182
|
|
|
167
183
|
| Key | Default | Description |
|
|
@@ -172,14 +188,169 @@ rag-config reset <key> # remove key (reverts to default)
|
|
|
172
188
|
| `db.password` | — | PostgreSQL password |
|
|
173
189
|
| `db.name` | `rag_<os-username>` | Database name — auto-created on first run |
|
|
174
190
|
| `workspaceRoot` | — | Root of your projects directory |
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
191
|
+
| `embedding.provider` | `infinity` | Embedding backend: `infinity` \| `openai-compatible` \| `voyage`. A stored `ollama` value fails validation with migration guidance (see [Embedding providers](#embedding-providers)) |
|
|
192
|
+
| `embedding.baseURL` | per-provider | Backend base URL (Infinity/TEI/vLLM `http://localhost:7997`, OpenAI `https://api.openai.com/v1`, Voyage `https://api.voyageai.com/v1`; Ollama via `openai-compatible` at `http://localhost:11434/v1`) |
|
|
193
|
+
| `embedding.apiKey` | `''` | API key, or `env:VAR_NAME` to read from `.env`/environment. Empty for local servers |
|
|
194
|
+
| `embedding.model` | `jinaai/jina-embeddings-v2-base-code` | Embedding model |
|
|
195
|
+
| `embedding.dimensions` | (derived) | Vector width override; required for models whose native width exceeds 2000 (Matryoshka). Otherwise derived from model → registry → runtime probe |
|
|
196
|
+
| `embedding.batchSize` | `32` | Texts per batch embedding request |
|
|
177
197
|
| `search.similarityThreshold` | `0.6` | Max cosine distance — raise if search returns nothing |
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
198
|
+
| `reranker.enabled` | `true` | Enable cross-encoder reranking; requires `reranker.model` |
|
|
199
|
+
| `reranker.provider` | `openai-compatible` | Rerank backend — `openai-compatible` `/rerank` only (Infinity, TEI, vLLM) |
|
|
200
|
+
| `reranker.baseURL` | `embedding.baseURL` | Rerank server base URL (Infinity serves embed + rerank from one server) |
|
|
201
|
+
| `reranker.model` | `mixedbread-ai/mxbai-rerank-xsmall-v1` | Reranker model name |
|
|
202
|
+
| `reranker.overfetch` | `5` | Candidate multiplier before reranking (`limit × N`) |
|
|
203
|
+
| `reranker.candidateFloor` | `80` | Minimum candidate pool fed to the reranker regardless of `limit` — `fetchLimit = min(max(limit × overfetch, candidateFloor), 200)`. Raise on GPU/fast hosts for better recall; lower on weak CPU to cut latency |
|
|
204
|
+
| `indexing.chunkSize` | `1500` | Max characters per chunk |
|
|
205
|
+
| `indexing.chunkOverlap` | `200` | Overlap between chunks |
|
|
206
|
+
| `indexing.embedBatchSize` | `5` | Parallel embedding requests per batch |
|
|
180
207
|
| `indexing.astBatchSize` | `3000` | Max files per ts-morph batch — lower if indexing large projects causes OOM |
|
|
208
|
+
| `indexing.chunkStrategy` | `ast` | How TS/JS files are chunked: `ast` = boundaries aligned to functions/classes, `character` = fixed-size splitter. Re-index to apply |
|
|
209
|
+
| `search.defaultLimit` | `5` | Default result count returned by `search_codebase` |
|
|
210
|
+
| `search.maxLimit` | `20` | Hard cap on result count |
|
|
181
211
|
| `logging.level` | `error` | Log level written to file: `error` \| `warn` \| `info` \| `debug` |
|
|
182
212
|
| `logging.dir` | `~/.config/codesearch-mcp/logs` | Base log directory, or `"false"` to disable |
|
|
213
|
+
| `logging.searchMetrics` | `false` | Emit one structured search-tuning line per query (at `info`, independent of `logging.level`) — use it to size `candidateFloor`/`overfetch`/`threshold` for your hardware |
|
|
214
|
+
|
|
215
|
+
## Embedding providers
|
|
216
|
+
|
|
217
|
+
`embedding.provider` selects the backend. All three produce vectors the same way; they differ in where they run and which models they offer. There is no dedicated Ollama provider — Ollama exposes an OpenAI-compatible `/v1/embeddings` endpoint, so it's reached through `openai-compatible` (see below).
|
|
218
|
+
|
|
219
|
+
### Choosing a provider
|
|
220
|
+
|
|
221
|
+
| Provider | When to use | Needs |
|
|
222
|
+
|----------|-------------|-------|
|
|
223
|
+
| `infinity` | Default. Local, best quality **plus a real reranker** in one container; GPU or CPU. | Docker (managed) or a running Infinity server |
|
|
224
|
+
| `openai-compatible` | OpenAI cloud, or any OpenAI-style server (TEI, vLLM, LM Studio, LocalAI, **or Ollama via its `/v1` endpoint**). | `baseURL` (+ `apiKey` for cloud) |
|
|
225
|
+
| `voyage` | Voyage AI cloud, code-specialized embeddings. | Voyage `apiKey` |
|
|
226
|
+
|
|
227
|
+
Easiest path: `rag-setup` asks for the provider, model, and any API key. Haven't run setup? It's the place to start. To change things later, edit config directly (below).
|
|
228
|
+
|
|
229
|
+
### Switching provider or changing the model
|
|
230
|
+
|
|
231
|
+
Any change to the provider, model, or dimension requires a **re-index** — the mismatch guard blocks search until you do it. The index is a rebuildable cache, so there's no data migration.
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Switch to OpenAI cloud
|
|
235
|
+
rag-config set embedding.provider openai-compatible
|
|
236
|
+
rag-config set embedding.baseURL https://api.openai.com/v1
|
|
237
|
+
rag-config set embedding.model text-embedding-3-small
|
|
238
|
+
rag-config set embedding.apiKey env:OPENAI_API_KEY # key lives in .env, not config.json
|
|
239
|
+
rag-config set reranker.enabled false # OpenAI has no /rerank endpoint
|
|
240
|
+
rag-index --reindex
|
|
241
|
+
|
|
242
|
+
# Switch to Voyage AI
|
|
243
|
+
rag-config set embedding.provider voyage
|
|
244
|
+
rag-config set embedding.model voyage-code-3
|
|
245
|
+
rag-config set embedding.apiKey env:VOYAGE_API_KEY # key lives in .env, not config.json
|
|
246
|
+
rag-config set reranker.enabled false # Voyage has no /rerank endpoint
|
|
247
|
+
rag-index --reindex
|
|
248
|
+
|
|
249
|
+
# Change only the model on the current provider
|
|
250
|
+
rag-config set embedding.model <model-name>
|
|
251
|
+
rag-index --reindex
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Breaking change: the `ollama` provider was removed
|
|
255
|
+
|
|
256
|
+
> **Migrating from an older version?** A config with `embedding.provider: ollama` (or an old-style top-level `ollama` block, or `embedding.baseURL`/`embedding.model` still pointing at Ollama's defaults with no `provider` set) now **fails to load** with an error explaining the fix.
|
|
257
|
+
>
|
|
258
|
+
> Fix it with `rag-config edit` (opens the file even while it's invalid) or by re-running `rag-setup`: set `embedding.provider` to `openai-compatible` and `embedding.baseURL` to `http://localhost:11434/v1` to keep using Ollama, or switch to `infinity` for a managed local backend.
|
|
259
|
+
>
|
|
260
|
+
> Either way, run `rag-index --reindex` afterward. A plain `rag-index` additionally refuses (non-zero exit, no changes made) to silently rebuild an index built by the old `ollama` provider — including an older install with no `index_meta` row, which is assumed to be a pre-migration Ollama/`bge-m3` index.
|
|
261
|
+
|
|
262
|
+
Per-provider notes:
|
|
263
|
+
|
|
264
|
+
- **infinity** — the model set is bound at container startup, so changing `embedding.model` (or the reranker model) **recreates** the managed container on the next `rag-setup`/`rag-index`/dashboard save. Defaults: `jinaai/jina-embeddings-v2-base-code` + reranker `mixedbread-ai/mxbai-rerank-xsmall-v1`. Both models are included in the compose template and reranking is **enabled by default** (`reranker.enabled: true`).
|
|
265
|
+
- **openai-compatible** — point `embedding.baseURL` at the server; set `embedding.apiKey` (as `env:VAR`) for cloud, leave empty for local servers (TEI/vLLM/LM Studio/**Ollama**). E.g. `text-embedding-3-small`, `text-embedding-3-large`. Cloud endpoints (OpenAI, Azure) don't expose `/rerank` — disable the reranker or point it at a separate server (see [Reranker with a cloud embedder](#reranker-with-a-cloud-or-external-embedder)).
|
|
266
|
+
- **Ollama** (via `openai-compatible`) — set `embedding.baseURL` to `http://localhost:11434/v1` and `embedding.model` to any model you've pulled (e.g. `bge-m3`). No dedicated code path, no auto-pull — pull the model yourself with `ollama pull <model>` first.
|
|
267
|
+
- **voyage** — set `embedding.apiKey` (as `env:VAR`); e.g. `voyage-code-3`, `voyage-3`. Voyage doesn't expose a `/rerank` endpoint in the OpenAI-compatible format — disable the reranker or point it at a separate server (see [Reranker with a cloud embedder](#reranker-with-a-cloud-or-external-embedder)).
|
|
268
|
+
|
|
269
|
+
The vector **dimension** is derived automatically (model registry → one-shot probe); set `embedding.dimensions` only to force a Matryoshka-reduced width for models whose native size exceeds 2000.
|
|
270
|
+
|
|
271
|
+
### Infinity — local embeddings + reranking in one container
|
|
272
|
+
|
|
273
|
+
`infinity` is the default provider (select it in `rag-setup`, or set `embedding.provider = infinity`) — it runs [Infinity](https://github.com/michaelfeil/infinity), which serves both `/embeddings` and `/rerank` from a single container, so you get a real cross-encoder reranker locally. During setup you're asked for an embedding model (default `jinaai/jina-embeddings-v2-base-code`, 768 dims) and a reranker model (default `mixedbread-ai/mxbai-rerank-xsmall-v1`, enabled by default); `reranker.*` is auto-wired to the same server.
|
|
274
|
+
|
|
275
|
+
Managed vs. external is decided by a `/health` probe at `embedding.baseURL` (default `http://localhost:7997`) plus the managed compose file — no config flag:
|
|
276
|
+
|
|
277
|
+
- **Managed** — the app-owned compose file (`~/.config/codesearch-mcp/docker-compose.yml`) declares the `infinity` service and `embedding.baseURL` points at its published port on `localhost`/`127.0.0.1`/`[::1]`. If nothing answers the probe, or the running container doesn't serve the configured model set, `rag-setup`, `rag-index`, and the web dashboard bring up (or recreate) that container. The full model set (embedding + reranker, when on the same server) is written into the compose file first — a plain `docker compose up -d` afterward serves the same set, no env var needed. It **prefers an NVIDIA GPU and falls back to CPU** automatically (GPU layered via `docker-compose.infinity-gpu.yml` only when `nvidia-smi` is present). To opt out of management entirely, remove the `infinity` service from that compose file.
|
|
278
|
+
- **External** — any other `embedding.baseURL` (a remote host, a server on a different port, or a managed compose file that's absent/unreadable) is used as-is; no Docker command ever runs. If it's missing a required model, you're shown the `--model-id` flag to restart it with.
|
|
279
|
+
|
|
280
|
+
The same "already running?" probe applies everywhere: a reachable backend defaults `rag-setup`'s "start Docker?" prompt to no, and a web-dashboard save that doesn't change the served model set never touches Docker.
|
|
281
|
+
|
|
282
|
+
### Reranker with a cloud or external embedder
|
|
283
|
+
|
|
284
|
+
The reranker is **enabled by default** (`reranker.enabled: true`) and `reranker.baseURL` defaults to `embedding.baseURL`. Cloud embedding endpoints (OpenAI, Voyage, Azure) don't serve `/rerank` — the reranker call will fail silently and fall back to vector order on every query. Fix it one of two ways:
|
|
285
|
+
|
|
286
|
+
**Option A — disable reranking** (simplest, recommended for cloud-only setups):
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
rag-config set reranker.enabled false
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Option B — point the reranker at a separate server** (e.g. a local Infinity or TEI instance while using Voyage/OpenAI for embeddings):
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
rag-config set reranker.baseURL http://localhost:7997
|
|
296
|
+
rag-config set reranker.model mixedbread-ai/mxbai-rerank-xsmall-v1
|
|
297
|
+
# reranker.enabled is already true
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The embedding and reranking servers are fully independent — any combination works: Voyage embeddings + local Infinity reranker, OpenAI embeddings + remote TEI reranker, etc.
|
|
301
|
+
|
|
302
|
+
### Secrets & external embedders
|
|
303
|
+
|
|
304
|
+
API keys live in `~/.config/codesearch-mcp/.env` (chmod 600), **not** in `config.json`. Reference them with `env:VAR_NAME`, e.g. `embedding.apiKey = env:OPENAI_API_KEY` with `OPENAI_API_KEY=sk-…` in `.env`. The file is loaded into the environment at startup (a real environment variable wins over the file), so all binaries — including the client-spawned `rag-mcp` — see it identically.
|
|
305
|
+
|
|
306
|
+
Switching the embedding model, provider, or dimension is applied by **re-indexing** (`rag-index --reindex`) — the index is a rebuildable cache, so there is no data migration. A single-row `index_meta` table records which model built the index; `search_codebase` and `rag-mcp` refuse with a clear "re-index required" error when the configured model disagrees, rather than returning noise from a foreign vector space.
|
|
307
|
+
|
|
308
|
+
## Tuning search recall
|
|
309
|
+
|
|
310
|
+
When reranking is enabled, `search_codebase` fetches a **candidate pool** by cosine distance, reranks it with the cross-encoder, and returns the top `limit`. Three knobs and one relationship govern how good the results are and how long they take:
|
|
311
|
+
|
|
312
|
+
- **Pool size** — `fetchLimit = min(max(limit × overfetch, candidateFloor), 200)`. `candidateFloor` (default `80`) guarantees the reranker a large enough menu even at the default `limit = 5`; without it a small `limit` starves the reranker (only `5 × 5 = 25` candidates) and its best matches never enter the window.
|
|
313
|
+
- **Threshold ↔ window** — raising `search.similarityThreshold` lets *more* chunks under the bar, so the pool must be wide enough to reach them; a strict threshold with a small pool silently truncates recall. Widen `candidateFloor`/`overfetch` when you raise the threshold.
|
|
314
|
+
- **Latency** — the reranker cost grows with pool size (on CPU, tens of seconds; a rough guide is a large fixed cost plus a per-candidate increment). Bigger pool = better recall, slower query. GPU/fast hosts can raise `candidateFloor`; ultra-constrained hosts lower it.
|
|
315
|
+
|
|
316
|
+
To size these for **your** hardware, turn on telemetry:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
rag-config set logging.searchMetrics true
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Each search then writes one `info` line (independent of `logging.level`) with, among others: `fetchLimit`, `candidatesReturned`, `poolSaturated`, `maxCosineRankSurvived`, `distinctFilesInResult`, `topFileShare`, and `embedMs`/`rerankMs`/`dbMs`/`totalMs`. Read them as:
|
|
323
|
+
|
|
324
|
+
- **`poolSaturated: true`** — more chunks were under the threshold than the window took → recall is being truncated; raise `candidateFloor`/`overfetch`.
|
|
325
|
+
- **`maxCosineRankSurvived`** — the deepest cosine rank that survived reranking. If it stays well below `fetchLimit`, the pool is bigger than needed → shrink it to save latency; if it approaches `fetchLimit`, widen.
|
|
326
|
+
|
|
327
|
+
## Index consistency
|
|
328
|
+
|
|
329
|
+
`rag-index` keeps the four index tables (`code_chunk`, `code_symbol`, `code_call`, `code_dependency`) consistent after every run:
|
|
330
|
+
|
|
331
|
+
| Event | What happens |
|
|
332
|
+
|---|---|
|
|
333
|
+
| **File deleted from disk** | All rows for that file are removed from the four tables in one transaction on the next `rag-index`. The file disappears from `find_symbol`, `get_callers`, `get_dependencies`, and `get_dependents`. |
|
|
334
|
+
| **File matched by `.ragignore`** | Same as deleted — treated as not collected, removed from all four tables. |
|
|
335
|
+
| **Project deselected** | All rows for that project are removed from the four tables. To bring it back, re-select and re-index — the index is a cache. The removal is printed as `Removed project <name> (not selected)`. |
|
|
336
|
+
| **`workspaceRoot` changed** | All projects indexed under the old root (their paths no longer match anything under the new root) are pruned. Each gets its own `Removed project` line. No confirmation is required — the index is a cache that `rag-index` rebuilds. |
|
|
337
|
+
|
|
338
|
+
A failure pruning one project does not block the others or stop indexing of the selected projects, but `rag-index` exits non-zero to signal the partial failure.
|
|
339
|
+
|
|
340
|
+
## Architecture
|
|
341
|
+
|
|
342
|
+
**Stack:** TypeScript on Node.js 18+ · PostgreSQL 17 + pgvector (HNSW, cosine distance) via Drizzle ORM · ts-morph for AST/symbol/call-graph extraction · `@langchain/textsplitters` for chunk fallback · MCP SDK over stdio · Zod v4 for config validation and JSON-Schema generation · pino + pino-roll for logging · plain `node:http` dashboard with vis-network · Vitest · Docker Compose for Postgres + Infinity.
|
|
343
|
+
|
|
344
|
+
**Design:**
|
|
345
|
+
- **Composition root + manual DI** — `src/container.ts` builds every service once and wires it through constructors; no module-level singletons, no DI framework.
|
|
346
|
+
- **Dependency inversion** — services depend on interfaces (`IEmbedder`, `IDatabase`, `IAstParser`, `IChunker`, `ISearcher`, `IProjectDiscovery`, `IMonorepoResolver`, `IMcpToolHandler`), so providers (OpenAI-compatible/Voyage/Infinity — Ollama via `openai-compatible`), chunking strategy (`ast`/`character`), and monorepo resolvers are swappable.
|
|
347
|
+
- **Command pattern** — each CLI mode (`rag-index`, `rag-mcp`, `rag-config`, `rag-web`) is its own `ICommand`; `app.ts` only dispatches.
|
|
348
|
+
- **Registry / Open-Closed** — MCP tools are `IMcpToolHandler` implementations registered in a `Map`; adding a tool never touches `McpServer`.
|
|
349
|
+
- **Fallback chains** — vector dimension: config override → model registry → runtime probe; `find_symbol`/`get_dependents`: indexed table → grep; failed rerank → vector order.
|
|
350
|
+
- **Incremental indexing** — mtime fast path, SHA-256 fallback for clones/transfers, AST-aligned chunk boundaries so declarations aren't split arbitrarily.
|
|
351
|
+
- **Index/model consistency guard** — `index_meta` tracks the embedding model that built the index; search refuses with a clear re-index message on mismatch instead of returning noise.
|
|
352
|
+
|
|
353
|
+
Full internals (per-file responsibilities, every pattern with file:line references) are documented in `CLAUDE.md`.
|
|
183
354
|
|
|
184
355
|
## License
|
|
185
356
|
|
package/dist/app.js
CHANGED
|
@@ -34,17 +34,53 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
37
38
|
const path = __importStar(require("path"));
|
|
38
39
|
const help_1 = require("./src/help");
|
|
39
40
|
const container_1 = require("./src/container");
|
|
40
41
|
const config_1 = require("./src/config");
|
|
42
|
+
const config_store_1 = require("./src/config-store");
|
|
41
43
|
const IndexCommand_1 = require("./src/commands/IndexCommand");
|
|
42
44
|
const McpCommand_1 = require("./src/commands/McpCommand");
|
|
43
45
|
const BackfillCommand_1 = require("./src/commands/BackfillCommand");
|
|
44
46
|
const ConfigCommand_1 = require("./src/commands/ConfigCommand");
|
|
45
47
|
const WebCommand_1 = require("./src/commands/WebCommand");
|
|
46
48
|
const setup_1 = require("./src/setup");
|
|
49
|
+
/**
|
|
50
|
+
* Load `~/.config/codesearch-mcp/.env` into `process.env` before config is read.
|
|
51
|
+
* All five binaries share this file — `rag-mcp` in particular is spawned by the
|
|
52
|
+
* MCP client and does not inherit shell exports. A real `process.env` value
|
|
53
|
+
* always wins over the file. Minimal `KEY=VALUE` parser (no `dotenv` dep):
|
|
54
|
+
* skips blanks and `#` comments, strips one layer of surrounding quotes.
|
|
55
|
+
*/
|
|
56
|
+
function loadDotEnv(file) {
|
|
57
|
+
let text;
|
|
58
|
+
try {
|
|
59
|
+
text = fs.readFileSync(file, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return; // absent / unreadable → no-op
|
|
63
|
+
}
|
|
64
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
65
|
+
const line = rawLine.trim();
|
|
66
|
+
if (!line || line.startsWith('#'))
|
|
67
|
+
continue;
|
|
68
|
+
const eq = line.indexOf('=');
|
|
69
|
+
if (eq === -1)
|
|
70
|
+
continue;
|
|
71
|
+
const key = line.slice(0, eq).trim();
|
|
72
|
+
if (!key)
|
|
73
|
+
continue;
|
|
74
|
+
let val = line.slice(eq + 1).trim();
|
|
75
|
+
if (val.length >= 2 && ((val[0] === '"' && val.at(-1) === '"') || (val[0] === "'" && val.at(-1) === "'"))) {
|
|
76
|
+
val = val.slice(1, -1);
|
|
77
|
+
}
|
|
78
|
+
if (!(key in process.env))
|
|
79
|
+
process.env[key] = val; // real env wins
|
|
80
|
+
}
|
|
81
|
+
}
|
|
47
82
|
async function main() {
|
|
83
|
+
loadDotEnv(config_store_1.ENV_FILE);
|
|
48
84
|
const argv1 = path.basename(process.argv[1] ?? '');
|
|
49
85
|
const isIndex = argv1 === 'rag-index' || process.argv[2] === 'index' || process.argv[2] === 'rag-index';
|
|
50
86
|
const isMcp = argv1 === 'rag-mcp' || process.argv[2] === 'mcp' || process.argv[2] === 'rag-mcp';
|
|
@@ -108,7 +144,7 @@ async function main() {
|
|
|
108
144
|
}
|
|
109
145
|
if (mode === 'setup') {
|
|
110
146
|
await (0, setup_1.runSetup)();
|
|
111
|
-
process.exit(0);
|
|
147
|
+
process.exit(process.exitCode ?? 0);
|
|
112
148
|
}
|
|
113
149
|
if (mode === 'config') {
|
|
114
150
|
await new ConfigCommand_1.ConfigCommand(effectiveArgs).execute();
|
|
@@ -153,6 +189,6 @@ async function main() {
|
|
|
153
189
|
}
|
|
154
190
|
await factory().execute();
|
|
155
191
|
await container.db.destroy();
|
|
156
|
-
process.exit(0);
|
|
192
|
+
process.exit(process.exitCode ?? 0);
|
|
157
193
|
}
|
|
158
194
|
main().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -6,6 +6,7 @@ const project_selector_1 = require("../project-selector");
|
|
|
6
6
|
const graph_1 = require("../graph");
|
|
7
7
|
const MonorepoAwareDiscoveryService_1 = require("../services/MonorepoAwareDiscoveryService");
|
|
8
8
|
const file_utils_1 = require("../file-utils");
|
|
9
|
+
const legacy_ollama_index_1 = require("../services/legacy-ollama-index");
|
|
9
10
|
class IndexCommand {
|
|
10
11
|
container;
|
|
11
12
|
argv1;
|
|
@@ -14,11 +15,48 @@ class IndexCommand {
|
|
|
14
15
|
this.argv1 = argv1;
|
|
15
16
|
}
|
|
16
17
|
async execute() {
|
|
17
|
-
const { db, indexer, searcher, config, discovery } = this.container;
|
|
18
|
+
const { db, indexer, searcher, config, discovery, embedder } = this.container;
|
|
18
19
|
const args = process.argv.slice(this.argv1 === 'rag-index' ? 2 : 3);
|
|
19
20
|
const force = args.includes('--reindex');
|
|
20
21
|
const forceSelect = args.includes('--select');
|
|
21
22
|
await db.initDbWithSchema();
|
|
23
|
+
// A former Ollama installation the config layer cannot recognise (old
|
|
24
|
+
// defaults, no `embedding`/`ollama` block) is protected here: without an
|
|
25
|
+
// explicit `--reindex`, refuse to touch an index built by `ollama` (or a
|
|
26
|
+
// pre-`index_meta` install, which is indistinguishable from one) before
|
|
27
|
+
// any backend readiness check, Docker command, or schema work.
|
|
28
|
+
if (!force) {
|
|
29
|
+
let identity;
|
|
30
|
+
try {
|
|
31
|
+
identity = await (0, legacy_ollama_index_1.resolveIndexedIdentity)(db);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error(`\n${err.message}`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (identity?.provider === 'ollama') {
|
|
39
|
+
console.error(`\nThe existing index was built with ${identity.provider}/${identity.model}, but "ollama" is no ` +
|
|
40
|
+
`longer a supported embedding provider (configured: ${config.embedding.provider}/${config.embedding.model}). ` +
|
|
41
|
+
`Run \`rag-index --reindex\` to confirm rebuilding the index under the configured provider.`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Ensure the local model is served (Infinity only — no-op for cloud), then
|
|
47
|
+
// resolve the active embedding dimension and reconcile it with index_meta
|
|
48
|
+
// (recreate code_chunk on a dimension change, clear rows on a model change)
|
|
49
|
+
// before scanning. A clear error here beats a mid-run vector width mismatch.
|
|
50
|
+
try {
|
|
51
|
+
await embedder.ensureModelAvailable?.();
|
|
52
|
+
const meta = await indexer.prepareEmbeddingSchema();
|
|
53
|
+
console.log(`Embedding: ${meta.provider} / ${meta.model} (${meta.dimensions} dims)`);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(`\nEmbedding setup failed: ${err.message}`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
22
60
|
const controller = new AbortController();
|
|
23
61
|
const teardown = (0, interrupt_1.setupInterrupt)(controller);
|
|
24
62
|
try {
|
|
@@ -29,6 +67,22 @@ class IndexCommand {
|
|
|
29
67
|
// Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
|
|
30
68
|
// sub-projects; ordinary projects pass through unchanged.
|
|
31
69
|
const projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
|
|
70
|
+
let pruneResult;
|
|
71
|
+
try {
|
|
72
|
+
pruneResult = await indexer.pruneProjects(projects.map(p => p.name));
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error(`\nPruning failed: ${err.message}`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const { removed, failed } = pruneResult;
|
|
80
|
+
for (const name of removed) {
|
|
81
|
+
console.log(`Removed project ${name} (not selected)`);
|
|
82
|
+
}
|
|
83
|
+
if (failed.length > 0) {
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
32
86
|
const filesPerProject = projects.map(p => (0, file_utils_1.collectFiles)(p.path, (0, file_utils_1.loadIgnore)(p.path), p.path));
|
|
33
87
|
const totalFiles = filesPerProject.reduce((sum, f) => sum + f.length, 0);
|
|
34
88
|
console.log(`\n${projects.length} projects — ${totalFiles} files total`);
|
|
@@ -7,14 +7,18 @@ class McpCommand {
|
|
|
7
7
|
this.container = container;
|
|
8
8
|
}
|
|
9
9
|
async execute() {
|
|
10
|
-
const { db, mcp, logger } = this.container;
|
|
10
|
+
const { db, mcp, searcher, logger } = this.container;
|
|
11
11
|
let startupError;
|
|
12
12
|
try {
|
|
13
13
|
await db.initDb();
|
|
14
|
+
// Refuse to serve searches embedded into a foreign vector space — surface
|
|
15
|
+
// the mismatch through the existing startup-error plumbing so every tool
|
|
16
|
+
// call reports it clearly.
|
|
17
|
+
await searcher.assertIndexMatches();
|
|
14
18
|
}
|
|
15
19
|
catch (err) {
|
|
16
20
|
startupError = err instanceof Error ? err : new Error(String(err));
|
|
17
|
-
logger.error('McpCommand:
|
|
21
|
+
logger.error('McpCommand: startup check failed', { reason: startupError.message });
|
|
18
22
|
}
|
|
19
23
|
try {
|
|
20
24
|
await mcp.start(startupError);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Own module (not `config.ts`) so `config-store.ts` can throw it too without
|
|
6
|
+
* an import cycle — `config.ts` imports from `config-store.ts`.
|
|
7
|
+
*/
|
|
8
|
+
class ConfigError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ConfigError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.ConfigError = ConfigError;
|