codesearch-mcp 0.1.0-alpha → 0.2.1-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 +39 -0
- package/README.md +146 -41
- package/dist/app.js +4 -19
- package/dist/src/commands/IndexCommand.js +68 -7
- package/dist/src/config-error.d.ts +7 -0
- package/dist/src/config-error.js +14 -0
- package/dist/src/config-store.d.ts +30 -12
- package/dist/src/config-store.js +81 -26
- package/dist/src/config.d.ts +5 -4
- package/dist/src/config.js +14 -16
- package/dist/src/constants.d.ts +8 -2
- package/dist/src/constants.js +8 -5
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +75 -0
- package/dist/src/db/schema.js +10 -1
- package/dist/src/enriched-prompt.d.ts +8 -0
- package/dist/src/enriched-prompt.js +87 -0
- package/dist/src/help.js +1 -1
- package/dist/src/interfaces/IAstParser.d.ts +18 -2
- package/dist/src/interfaces/IDatabase.d.ts +31 -0
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/interfaces/ISearcher.d.ts +3 -0
- package/dist/src/services/AstChunker.d.ts +11 -0
- package/dist/src/services/AstChunker.js +44 -0
- package/dist/src/services/AstParserService.d.ts +39 -7
- package/dist/src/services/AstParserService.js +504 -104
- package/dist/src/services/DatabaseService.d.ts +17 -0
- package/dist/src/services/DatabaseService.js +130 -2
- package/dist/src/services/EmbedderFactory.d.ts +1 -1
- package/dist/src/services/EmbedderFactory.js +2 -5
- package/dist/src/services/IndexerService.d.ts +4 -14
- package/dist/src/services/IndexerService.js +54 -156
- package/dist/src/services/InfinityEmbedder.d.ts +14 -55
- package/dist/src/services/InfinityEmbedder.js +30 -141
- package/dist/src/services/LoggerService.d.ts +3 -1
- package/dist/src/services/LoggerService.js +17 -3
- package/dist/src/services/McpServer.js +3 -3
- package/dist/src/services/SearcherService.d.ts +39 -4
- package/dist/src/services/SearcherService.js +254 -81
- package/dist/src/services/WebServerService.d.ts +41 -3
- package/dist/src/services/WebServerService.js +90 -2
- 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 +3 -3
- package/dist/src/services/reranker.js +3 -3
- package/dist/src/setup.d.ts +37 -0
- package/dist/src/setup.js +111 -157
- package/dist/src/symbol-name.d.ts +44 -0
- package/dist/src/symbol-name.js +68 -0
- package/dist/src/symbol-rows.d.ts +12 -0
- package/dist/src/symbol-rows.js +38 -0
- package/dist/src/tools/FindSymbolTool.js +30 -7
- package/dist/src/tools/GetCallersTool.js +20 -5
- package/docker/docker-compose.yml +5 -15
- package/docs/architecture.svg +103 -0
- package/docs/web-dashboard.md +36 -17
- package/package.json +1 -6
- package/dist/src/commands/BackfillCommand.d.ts +0 -7
- package/dist/src/commands/BackfillCommand.js +0 -37
- package/dist/src/services/OllamaEmbedder.d.ts +0 -25
- package/dist/src/services/OllamaEmbedder.js +0 -111
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,45 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.1-alpha] - 2026-10-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`find_symbol` indexes every member kind, not just top-level declarations** — class methods (`abstract`, `static`, `async`, generators, `#private`, computed and string-literal names), get/set accessors, constructors, class properties initialised with a function, object-literal methods, and interface/type-literal method signatures all get a `code_symbol` row now, with the dotted `container` of their enclosing scopes (`RepairsService`, `NS.Parser`, `api.handlers`) and a `static` flag. Classes are collected from the whole file — inside namespaces, as class expressions, as `export default class {}`, and inside functions or methods — not only at the top level.
|
|
13
|
+
- **Qualified, quoted and computed lookup** — `find_symbol` accepts `Class.method` to disambiguate same-named members (exact container first, then a `.`-boundary suffix match), a quoted name for string-literal members (`Router.'kebab-name'`), and a computed name (`Stack.[Symbol.iterator]`), alongside the existing bare-name lookup. `find_symbol` falls back to grep whenever the index returns **no hits** (not only when it errors), and marks grep-sourced hits so the caller knows they may be stale.
|
|
14
|
+
- **Qualified call graph** — `code_call.fromSymbol`/`calleeSymbol` now carry the qualified name of the enclosing/resolved declaration (`RepairsService.createRepairOrder`, including constructors and accessors, which previously had no `fromSymbol` at all). `get_callers` accepts a qualified symbol with the same suffix-matching rules as `find_symbol`.
|
|
15
|
+
- **Per-project symbol-index version, enforced full re-index** — `rag-index` without `--reindex` now refuses to run on a project indexed before member indexing (mixed old/new-format rows would silently hide methods from `find_symbol`); `find_symbol`/`get_callers` show a warning when a project in scope is outdated.
|
|
16
|
+
|
|
17
|
+
### Changed (breaking)
|
|
18
|
+
|
|
19
|
+
- **Requires PostgreSQL 15+** — the symbol index's new unique index relies on `NULLS NOT DISTINCT`, added in PostgreSQL 15. `rag-index` checks the server version before any schema change and refuses on an older server.
|
|
20
|
+
- **Every project must be re-indexed** — run `rag-index --reindex` on each project after upgrading. Old-format rows (no members, bare call-graph names) are incompatible with the new lookup and are cleared by `--reindex`.
|
|
21
|
+
|
|
22
|
+
## [0.2.0-alpha] - 2026-09-29
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **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.
|
|
27
|
+
- **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.
|
|
28
|
+
- **`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.
|
|
29
|
+
- **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.
|
|
30
|
+
- **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.
|
|
31
|
+
- **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.
|
|
32
|
+
- **`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.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **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.
|
|
37
|
+
- **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`.
|
|
38
|
+
|
|
39
|
+
### Changed (breaking)
|
|
40
|
+
|
|
41
|
+
- **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.
|
|
42
|
+
- **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`.
|
|
43
|
+
- **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.
|
|
44
|
+
- **`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.
|
|
45
|
+
- **`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.
|
|
46
|
+
|
|
8
47
|
## [0.1.0-alpha] - 2026-07-12
|
|
9
48
|
|
|
10
49
|
### Added
|
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,20 @@ See `CHANGELOG.md` for version history.
|
|
|
10
19
|
|
|
11
20
|
## How it works
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
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
25
|
2. `rag-mcp` exposes an MCP server — Claude calls it automatically when you ask about code
|
|
15
26
|
3. Incremental — only changed files are re-indexed on subsequent runs
|
|
27
|
+
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
28
|
|
|
17
|
-
**Stack:** TypeScript · Drizzle ORM · PostgreSQL + pgvector ·
|
|
29
|
+
**Stack:** TypeScript · Drizzle ORM · PostgreSQL + pgvector · Infinity (jina-embeddings-v2-base-code) · LangChain text splitters · ts-morph
|
|
18
30
|
|
|
19
31
|
## Requirements
|
|
20
32
|
|
|
21
33
|
- Node.js 18+
|
|
22
|
-
-
|
|
34
|
+
- PostgreSQL 15+ with pgvector (the symbol index's unique index relies on `NULLS NOT DISTINCT`, added in PostgreSQL 15)
|
|
35
|
+
- Docker (for local DB + Infinity) — or bring your own PostgreSQL with pgvector + an embedding backend (see [Embedding providers](#embedding-providers))
|
|
23
36
|
|
|
24
37
|
## Installation
|
|
25
38
|
|
|
@@ -35,11 +48,19 @@ rag-setup
|
|
|
35
48
|
|
|
36
49
|
Walks you through:
|
|
37
50
|
- **Database** — enter connection details; optionally starts Docker containers automatically
|
|
38
|
-
- **Embedding backend** — pick a provider (`
|
|
51
|
+
- **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
52
|
- **Logging** — choose log level and directory (defaults: `error` level, `~/.config/codesearch-mcp/logs/`)
|
|
40
53
|
|
|
41
54
|
Config is saved to `~/.config/codesearch-mcp/config.json`.
|
|
42
55
|
|
|
56
|
+
## Register with Claude Code
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
claude mcp add codesearch-mcp -s user -- rag-mcp
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then ask Claude anything about your code — it will use the tools below automatically.
|
|
63
|
+
|
|
43
64
|
## Index your projects
|
|
44
65
|
|
|
45
66
|
```bash
|
|
@@ -62,14 +83,6 @@ If `workspaceRoot` is an [Nx](https://nx.dev) workspace (detected by `nx.json`),
|
|
|
62
83
|
|
|
63
84
|
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
85
|
|
|
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
86
|
## Web dashboard
|
|
74
87
|
|
|
75
88
|
```bash
|
|
@@ -78,23 +91,23 @@ rag-web # http://127.0.0.1:4319 (--port to change)
|
|
|
78
91
|
|
|
79
92
|
A local web UI (bound to `127.0.0.1` only) with a schema-driven config editor,
|
|
80
93
|
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 (
|
|
94
|
+
config form shows the applied default for any unset key and suggests dimension-compatible
|
|
95
|
+
models in the model fields (free text is always accepted).
|
|
83
96
|
See [docs/web-dashboard.md](docs/web-dashboard.md) for the full reference.
|
|
84
97
|
|
|
85
98
|
## MCP Tools
|
|
86
99
|
|
|
87
100
|
| Tool | Description |
|
|
88
101
|
|------|-------------|
|
|
89
|
-
| `search_codebase` | Semantic search across all indexed projects. Accepts `include_graph_context` to append direct imports/dependents to each result,
|
|
102
|
+
| `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
103
|
| `list_projects` | List projects with metadata and chunk counts |
|
|
91
|
-
| `find_symbol` | Find a
|
|
104
|
+
| `find_symbol` | Find a declaration or member by exact name — bare, qualified (`Class.method`), quoted, or computed — faster than semantic search |
|
|
92
105
|
| `list_files` | Browse the directory tree of a project |
|
|
93
106
|
| `read_file` | Read a complete file by absolute path |
|
|
94
107
|
| `read_file_range` | Read a specific line range from an indexed file |
|
|
95
108
|
| `get_dependencies` | Show what files a given file imports (direct or transitive) |
|
|
96
109
|
| `get_dependents` | Show which files import a given file (direct or transitive) |
|
|
97
|
-
| `get_callers` | Find all call sites of a function or method by exact name |
|
|
110
|
+
| `get_callers` | Find all call sites of a function or method by exact name (bare or qualified) |
|
|
98
111
|
|
|
99
112
|
## Project metadata
|
|
100
113
|
|
|
@@ -104,6 +117,7 @@ Add `.ragmeta.json` to a project root to enrich `list_projects` and generate a v
|
|
|
104
117
|
{
|
|
105
118
|
"description": "Main API server",
|
|
106
119
|
"stack": "NestJS, GraphQL, PostgreSQL",
|
|
120
|
+
"notes": "Uses optimistic locking — see order-service/docs/concurrency.md",
|
|
107
121
|
"relations": {
|
|
108
122
|
"consumed_by": ["crm-frontend"],
|
|
109
123
|
"consumes": ["billing-service", "auth-service"]
|
|
@@ -111,7 +125,7 @@ Add `.ragmeta.json` to a project root to enrich `list_projects` and generate a v
|
|
|
111
125
|
}
|
|
112
126
|
```
|
|
113
127
|
|
|
114
|
-
After each indexing run an interactive `project-graph.html` is written to the workspace root.
|
|
128
|
+
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
129
|
|
|
116
130
|
## Graph context
|
|
117
131
|
|
|
@@ -166,6 +180,7 @@ rag-config show # print current config (password redacted)
|
|
|
166
180
|
rag-config get <key> # print a single value
|
|
167
181
|
rag-config set <key> <value> # update a value
|
|
168
182
|
rag-config reset <key> # remove key (reverts to default)
|
|
183
|
+
rag-config edit # open config in $EDITOR; validates on save (use this to recover an invalid config)
|
|
169
184
|
```
|
|
170
185
|
|
|
171
186
|
| Key | Default | Description |
|
|
@@ -176,33 +191,40 @@ rag-config reset <key> # remove key (reverts to default)
|
|
|
176
191
|
| `db.password` | — | PostgreSQL password |
|
|
177
192
|
| `db.name` | `rag_<os-username>` | Database name — auto-created on first run |
|
|
178
193
|
| `workspaceRoot` | — | Root of your projects directory |
|
|
179
|
-
| `embedding.provider` | `
|
|
180
|
-
| `embedding.baseURL` | per-provider | Backend base URL (
|
|
194
|
+
| `embedding.provider` | `infinity` | Embedding backend: `infinity` \| `openai-compatible` \| `voyage`. A stored `ollama` value fails validation with migration guidance (see [Embedding providers](#embedding-providers)) |
|
|
195
|
+
| `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`) |
|
|
181
196
|
| `embedding.apiKey` | `''` | API key, or `env:VAR_NAME` to read from `.env`/environment. Empty for local servers |
|
|
182
|
-
| `embedding.model` | `
|
|
197
|
+
| `embedding.model` | `jinaai/jina-embeddings-v2-base-code` | Embedding model |
|
|
183
198
|
| `embedding.dimensions` | (derived) | Vector width override; required for models whose native width exceeds 2000 (Matryoshka). Otherwise derived from model → registry → runtime probe |
|
|
184
199
|
| `embedding.batchSize` | `32` | Texts per batch embedding request |
|
|
185
200
|
| `search.similarityThreshold` | `0.6` | Max cosine distance — raise if search returns nothing |
|
|
186
|
-
| `reranker.enabled` | `
|
|
187
|
-
| `reranker.provider` | `openai-compatible` | Rerank backend — `openai-compatible` `/rerank` only (Infinity, TEI, vLLM)
|
|
201
|
+
| `reranker.enabled` | `true` | Enable cross-encoder reranking; requires `reranker.model` |
|
|
202
|
+
| `reranker.provider` | `openai-compatible` | Rerank backend — `openai-compatible` `/rerank` only (Infinity, TEI, vLLM) |
|
|
188
203
|
| `reranker.baseURL` | `embedding.baseURL` | Rerank server base URL (Infinity serves embed + rerank from one server) |
|
|
189
|
-
| `reranker.model` | `
|
|
204
|
+
| `reranker.model` | `mixedbread-ai/mxbai-rerank-xsmall-v1` | Reranker model name |
|
|
205
|
+
| `reranker.overfetch` | `5` | Candidate multiplier before reranking (`limit × N`) |
|
|
206
|
+
| `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 |
|
|
207
|
+
| `indexing.chunkSize` | `1500` | Max characters per chunk |
|
|
208
|
+
| `indexing.chunkOverlap` | `200` | Overlap between chunks |
|
|
209
|
+
| `indexing.embedBatchSize` | `5` | Parallel embedding requests per batch |
|
|
190
210
|
| `indexing.astBatchSize` | `3000` | Max files per ts-morph batch — lower if indexing large projects causes OOM |
|
|
191
211
|
| `indexing.chunkStrategy` | `ast` | How TS/JS files are chunked: `ast` = boundaries aligned to functions/classes, `character` = fixed-size splitter. Re-index to apply |
|
|
212
|
+
| `search.defaultLimit` | `5` | Default result count returned by `search_codebase` |
|
|
213
|
+
| `search.maxLimit` | `20` | Hard cap on result count |
|
|
192
214
|
| `logging.level` | `error` | Log level written to file: `error` \| `warn` \| `info` \| `debug` |
|
|
193
215
|
| `logging.dir` | `~/.config/codesearch-mcp/logs` | Base log directory, or `"false"` to disable |
|
|
216
|
+
| `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 |
|
|
194
217
|
|
|
195
218
|
## Embedding providers
|
|
196
219
|
|
|
197
|
-
`embedding.provider` selects the backend. All
|
|
220
|
+
`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).
|
|
198
221
|
|
|
199
222
|
### Choosing a provider
|
|
200
223
|
|
|
201
224
|
| Provider | When to use | Needs |
|
|
202
225
|
|----------|-------------|-------|
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `openai-compatible` | OpenAI cloud, or any OpenAI-style server (TEI, vLLM, LM Studio, LocalAI). | `baseURL` (+ `apiKey` for cloud) |
|
|
226
|
+
| `infinity` | Default. Local, best quality **plus a real reranker** in one container; GPU or CPU. | Docker (managed) or a running Infinity server |
|
|
227
|
+
| `openai-compatible` | OpenAI cloud, or any OpenAI-style server (TEI, vLLM, LM Studio, LocalAI, **or Ollama via its `/v1` endpoint**). | `baseURL` (+ `apiKey` for cloud) |
|
|
206
228
|
| `voyage` | Voyage AI cloud, code-specialized embeddings. | Voyage `apiKey` |
|
|
207
229
|
|
|
208
230
|
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).
|
|
@@ -212,11 +234,19 @@ Easiest path: `rag-setup` asks for the provider, model, and any API key. Haven't
|
|
|
212
234
|
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.
|
|
213
235
|
|
|
214
236
|
```bash
|
|
215
|
-
# Switch
|
|
237
|
+
# Switch to OpenAI cloud
|
|
216
238
|
rag-config set embedding.provider openai-compatible
|
|
217
239
|
rag-config set embedding.baseURL https://api.openai.com/v1
|
|
218
240
|
rag-config set embedding.model text-embedding-3-small
|
|
219
241
|
rag-config set embedding.apiKey env:OPENAI_API_KEY # key lives in .env, not config.json
|
|
242
|
+
rag-config set reranker.enabled false # OpenAI has no /rerank endpoint
|
|
243
|
+
rag-index --reindex
|
|
244
|
+
|
|
245
|
+
# Switch to Voyage AI
|
|
246
|
+
rag-config set embedding.provider voyage
|
|
247
|
+
rag-config set embedding.model voyage-code-3
|
|
248
|
+
rag-config set embedding.apiKey env:VOYAGE_API_KEY # key lives in .env, not config.json
|
|
249
|
+
rag-config set reranker.enabled false # Voyage has no /rerank endpoint
|
|
220
250
|
rag-index --reindex
|
|
221
251
|
|
|
222
252
|
# Change only the model on the current provider
|
|
@@ -224,31 +254,106 @@ rag-config set embedding.model <model-name>
|
|
|
224
254
|
rag-index --reindex
|
|
225
255
|
```
|
|
226
256
|
|
|
257
|
+
### Breaking change: the `ollama` provider was removed
|
|
258
|
+
|
|
259
|
+
> **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.
|
|
260
|
+
>
|
|
261
|
+
> 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.
|
|
262
|
+
>
|
|
263
|
+
> 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.
|
|
264
|
+
|
|
227
265
|
Per-provider notes:
|
|
228
266
|
|
|
229
|
-
- **
|
|
230
|
-
- **
|
|
231
|
-
- **openai-compatible
|
|
232
|
-
- **voyage** — set `embedding.apiKey
|
|
267
|
+
- **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`).
|
|
268
|
+
- **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)).
|
|
269
|
+
- **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.
|
|
270
|
+
- **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)).
|
|
233
271
|
|
|
234
272
|
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.
|
|
235
273
|
|
|
236
274
|
### Infinity — local embeddings + reranking in one container
|
|
237
275
|
|
|
238
|
-
|
|
276
|
+
`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.
|
|
277
|
+
|
|
278
|
+
Managed vs. external is decided by a `/health` probe at `embedding.baseURL` (default `http://localhost:7997`) plus the managed compose file — no config flag:
|
|
279
|
+
|
|
280
|
+
- **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.
|
|
281
|
+
- **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.
|
|
282
|
+
|
|
283
|
+
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.
|
|
239
284
|
|
|
240
|
-
|
|
285
|
+
### Reranker with a cloud or external embedder
|
|
241
286
|
|
|
242
|
-
|
|
243
|
-
- **External** — if a server already answers (your own Infinity, remote host, CI), it is used as-is and no Docker command runs. Point `embedding.baseURL` at it.
|
|
287
|
+
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:
|
|
244
288
|
|
|
245
|
-
|
|
289
|
+
**Option A — disable reranking** (simplest, recommended for cloud-only setups):
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
rag-config set reranker.enabled false
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Option B — point the reranker at a separate server** (e.g. a local Infinity or TEI instance while using Voyage/OpenAI for embeddings):
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
rag-config set reranker.baseURL http://localhost:7997
|
|
299
|
+
rag-config set reranker.model mixedbread-ai/mxbai-rerank-xsmall-v1
|
|
300
|
+
# reranker.enabled is already true
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The embedding and reranking servers are fully independent — any combination works: Voyage embeddings + local Infinity reranker, OpenAI embeddings + remote TEI reranker, etc.
|
|
246
304
|
|
|
247
305
|
### Secrets & external embedders
|
|
248
306
|
|
|
249
307
|
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.
|
|
250
308
|
|
|
251
|
-
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.
|
|
309
|
+
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.
|
|
310
|
+
|
|
311
|
+
## Tuning search recall
|
|
312
|
+
|
|
313
|
+
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:
|
|
314
|
+
|
|
315
|
+
- **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.
|
|
316
|
+
- **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.
|
|
317
|
+
- **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.
|
|
318
|
+
|
|
319
|
+
To size these for **your** hardware, turn on telemetry:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
rag-config set logging.searchMetrics true
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
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:
|
|
326
|
+
|
|
327
|
+
- **`poolSaturated: true`** — more chunks were under the threshold than the window took → recall is being truncated; raise `candidateFloor`/`overfetch`.
|
|
328
|
+
- **`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.
|
|
329
|
+
|
|
330
|
+
## Index consistency
|
|
331
|
+
|
|
332
|
+
`rag-index` keeps the four index tables (`code_chunk`, `code_symbol`, `code_call`, `code_dependency`) consistent after every run:
|
|
333
|
+
|
|
334
|
+
| Event | What happens |
|
|
335
|
+
|---|---|
|
|
336
|
+
| **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`. |
|
|
337
|
+
| **File matched by `.ragignore`** | Same as deleted — treated as not collected, removed from all four tables. |
|
|
338
|
+
| **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)`. |
|
|
339
|
+
| **`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. |
|
|
340
|
+
|
|
341
|
+
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.
|
|
342
|
+
|
|
343
|
+
## Architecture
|
|
344
|
+
|
|
345
|
+
**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.
|
|
346
|
+
|
|
347
|
+
**Design:**
|
|
348
|
+
- **Composition root + manual DI** — `src/container.ts` builds every service once and wires it through constructors; no module-level singletons, no DI framework.
|
|
349
|
+
- **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.
|
|
350
|
+
- **Command pattern** — each CLI mode (`rag-index`, `rag-mcp`, `rag-config`, `rag-web`) is its own `ICommand`; `app.ts` only dispatches.
|
|
351
|
+
- **Registry / Open-Closed** — MCP tools are `IMcpToolHandler` implementations registered in a `Map`; adding a tool never touches `McpServer`.
|
|
352
|
+
- **Fallback chains** — vector dimension: config override → model registry → runtime probe; `find_symbol`/`get_dependents`: indexed table → grep; failed rerank → vector order.
|
|
353
|
+
- **Incremental indexing** — mtime fast path, SHA-256 fallback for clones/transfers, AST-aligned chunk boundaries so declarations aren't split arbitrarily.
|
|
354
|
+
- **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.
|
|
355
|
+
|
|
356
|
+
Full internals (per-file responsibilities, every pattern with file:line references) are documented in `CLAUDE.md`.
|
|
252
357
|
|
|
253
358
|
## License
|
|
254
359
|
|
package/dist/app.js
CHANGED
|
@@ -42,7 +42,6 @@ const config_1 = require("./src/config");
|
|
|
42
42
|
const config_store_1 = require("./src/config-store");
|
|
43
43
|
const IndexCommand_1 = require("./src/commands/IndexCommand");
|
|
44
44
|
const McpCommand_1 = require("./src/commands/McpCommand");
|
|
45
|
-
const BackfillCommand_1 = require("./src/commands/BackfillCommand");
|
|
46
45
|
const ConfigCommand_1 = require("./src/commands/ConfigCommand");
|
|
47
46
|
const WebCommand_1 = require("./src/commands/WebCommand");
|
|
48
47
|
const setup_1 = require("./src/setup");
|
|
@@ -87,9 +86,7 @@ async function main() {
|
|
|
87
86
|
const isSetup = argv1 === 'rag-setup' || process.argv[2] === 'setup' || process.argv[2] === 'rag-setup';
|
|
88
87
|
const isConfig = argv1 === 'rag-config' || process.argv[2] === 'config' || process.argv[2] === 'rag-config';
|
|
89
88
|
const isWeb = argv1 === 'rag-web' || process.argv[2] === 'web' || process.argv[2] === 'rag-web';
|
|
90
|
-
const
|
|
91
|
-
(process.argv[2] === 'rag-index' && process.argv[3] === 'backfill');
|
|
92
|
-
const mode = isBackfill ? 'backfill' : isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : isWeb ? 'web' : (process.argv[2] ?? '');
|
|
89
|
+
const mode = isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : isWeb ? 'web' : (process.argv[2] ?? '');
|
|
93
90
|
const isBinary = ['rag-index', 'rag-mcp', 'rag-setup', 'rag-config', 'rag-web'].includes(argv1);
|
|
94
91
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
95
92
|
(0, help_1.printHelp)(isBinary ? argv1 : mode);
|
|
@@ -131,27 +128,16 @@ async function main() {
|
|
|
131
128
|
process.exit(1);
|
|
132
129
|
}
|
|
133
130
|
}
|
|
134
|
-
else if (mode === 'backfill') {
|
|
135
|
-
const knownBackfillFlags = new Set(['--calls']);
|
|
136
|
-
const backfillIdx = process.argv.indexOf('backfill');
|
|
137
|
-
const backfillArgs = backfillIdx >= 0 ? process.argv.slice(backfillIdx + 1) : [];
|
|
138
|
-
const unknown = backfillArgs.filter(a => !knownBackfillFlags.has(a));
|
|
139
|
-
if (unknown.length > 0) {
|
|
140
|
-
console.error(`Unexpected argument: ${unknown[0]}\n`);
|
|
141
|
-
(0, help_1.printHelp)('rag-index');
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
131
|
if (mode === 'setup') {
|
|
146
132
|
await (0, setup_1.runSetup)();
|
|
147
|
-
process.exit(0);
|
|
133
|
+
process.exit(process.exitCode ?? 0);
|
|
148
134
|
}
|
|
149
135
|
if (mode === 'config') {
|
|
150
136
|
await new ConfigCommand_1.ConfigCommand(effectiveArgs).execute();
|
|
151
137
|
process.exit(0);
|
|
152
138
|
}
|
|
153
139
|
// All other modes require a valid config.json
|
|
154
|
-
const loggerName = mode === 'index'
|
|
140
|
+
const loggerName = mode === 'index' ? 'indexer' : mode === 'mcp' ? 'mcp' : mode === 'web' ? 'web' : undefined;
|
|
155
141
|
let container;
|
|
156
142
|
try {
|
|
157
143
|
container = (0, container_1.createContainer)(loggerName);
|
|
@@ -176,7 +162,6 @@ async function main() {
|
|
|
176
162
|
const factories = {
|
|
177
163
|
index: () => new IndexCommand_1.IndexCommand(container, argv1),
|
|
178
164
|
mcp: () => new McpCommand_1.McpCommand(container),
|
|
179
|
-
backfill: () => new BackfillCommand_1.BackfillCommand(container),
|
|
180
165
|
web: () => new WebCommand_1.WebCommand(container, effectiveArgs),
|
|
181
166
|
};
|
|
182
167
|
const factory = factories[mode];
|
|
@@ -189,6 +174,6 @@ async function main() {
|
|
|
189
174
|
}
|
|
190
175
|
await factory().execute();
|
|
191
176
|
await container.db.destroy();
|
|
192
|
-
process.exit(0);
|
|
177
|
+
process.exit(process.exitCode ?? 0);
|
|
193
178
|
}
|
|
194
179
|
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;
|
|
@@ -19,7 +20,57 @@ class IndexCommand {
|
|
|
19
20
|
const force = args.includes('--reindex');
|
|
20
21
|
const forceSelect = args.includes('--select');
|
|
21
22
|
await db.initDbWithSchema();
|
|
22
|
-
//
|
|
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
|
+
const discovered = discovery.discover(config.workspaceRoot);
|
|
47
|
+
const selected = await new project_selector_1.ProjectSelector(config.workspaceRoot).select(discovered, forceSelect);
|
|
48
|
+
if (selected.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
// Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
|
|
51
|
+
// sub-projects; ordinary projects pass through unchanged.
|
|
52
|
+
const projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
|
|
53
|
+
// A project indexed before member extraction mixes old-format rows (no
|
|
54
|
+
// members, bare calleeSymbol) with new ones if left to an incremental run.
|
|
55
|
+
// Refuse before any backend readiness check, Docker command, or schema
|
|
56
|
+
// work — the same pattern as the legacy-Ollama guard above.
|
|
57
|
+
if (!force) {
|
|
58
|
+
let outdated;
|
|
59
|
+
try {
|
|
60
|
+
outdated = await db.listOutdatedProjects(projects.map(p => p.name));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.error(`\n${err.message}`);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (outdated.length > 0) {
|
|
68
|
+
console.error(`\nIndexed before member indexing: ${[...outdated].sort().join(', ')} — run \`rag-index --reindex\`.`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Ensure the local model is served (Infinity only — no-op for cloud), then
|
|
23
74
|
// resolve the active embedding dimension and reconcile it with index_meta
|
|
24
75
|
// (recreate code_chunk on a dimension change, clear rows on a model change)
|
|
25
76
|
// before scanning. A clear error here beats a mid-run vector width mismatch.
|
|
@@ -30,18 +81,28 @@ class IndexCommand {
|
|
|
30
81
|
}
|
|
31
82
|
catch (err) {
|
|
32
83
|
console.error(`\nEmbedding setup failed: ${err.message}`);
|
|
84
|
+
process.exitCode = 1;
|
|
33
85
|
return;
|
|
34
86
|
}
|
|
35
87
|
const controller = new AbortController();
|
|
36
88
|
const teardown = (0, interrupt_1.setupInterrupt)(controller);
|
|
37
89
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
90
|
+
let pruneResult;
|
|
91
|
+
try {
|
|
92
|
+
pruneResult = await indexer.pruneProjects(projects.map(p => p.name));
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error(`\nPruning failed: ${err.message}`);
|
|
96
|
+
process.exitCode = 1;
|
|
41
97
|
return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
98
|
+
}
|
|
99
|
+
const { removed, failed } = pruneResult;
|
|
100
|
+
for (const name of removed) {
|
|
101
|
+
console.log(`Removed project ${name} (not selected)`);
|
|
102
|
+
}
|
|
103
|
+
if (failed.length > 0) {
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
45
106
|
const filesPerProject = projects.map(p => (0, file_utils_1.collectFiles)(p.path, (0, file_utils_1.loadIgnore)(p.path), p.path));
|
|
46
107
|
const totalFiles = filesPerProject.reduce((sum, f) => sum + f.length, 0);
|
|
47
108
|
console.log(`\n${projects.length} projects — ${totalFiles} files total`);
|
|
@@ -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;
|