codesearch-mcp 0.0.17-alpha.1 → 0.1.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 +48 -0
- package/README.md +95 -6
- package/dist/app.js +70 -10
- package/dist/src/commands/ConfigCommand.d.ts +1 -0
- package/dist/src/commands/ConfigCommand.js +84 -4
- package/dist/src/commands/IndexCommand.js +35 -7
- package/dist/src/commands/McpCommand.js +9 -4
- package/dist/src/commands/{SetupCommand.d.ts → WebCommand.d.ts} +3 -2
- package/dist/src/commands/WebCommand.js +56 -0
- package/dist/src/config-store.d.ts +85 -4
- package/dist/src/config-store.js +157 -32
- package/dist/src/config.d.ts +32 -8
- package/dist/src/config.js +93 -30
- package/dist/src/constants.d.ts +35 -0
- package/dist/src/constants.js +57 -4
- package/dist/src/container.d.ts +10 -6
- package/dist/src/container.js +10 -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 +111 -0
- package/dist/src/db/schema.js +15 -1
- package/dist/src/graph.d.ts +60 -0
- package/dist/src/graph.js +239 -120
- package/dist/src/help.js +20 -5
- package/dist/src/interfaces/IAstParser.d.ts +20 -3
- package/dist/src/interfaces/IChunker.d.ts +24 -0
- package/dist/src/interfaces/IChunker.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +27 -0
- package/dist/src/interfaces/IDatabase.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +15 -2
- package/dist/src/interfaces/ILogger.d.ts +10 -0
- package/dist/src/interfaces/ILogger.js +2 -0
- package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
- package/dist/src/interfaces/IMcpToolHandler.js +2 -0
- package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
- package/dist/src/interfaces/IMonorepoResolver.js +2 -0
- package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
- package/dist/src/interfaces/IProjectDiscovery.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +37 -0
- package/dist/src/interfaces/ISearcher.js +2 -0
- package/dist/src/interfaces/index.d.ts +8 -0
- package/dist/src/interfaces/index.js +2 -0
- package/dist/src/owner-path.d.ts +10 -0
- package/dist/src/owner-path.js +27 -0
- package/dist/src/project-selector.d.ts +1 -1
- package/dist/src/services/AstChunker.d.ts +31 -0
- package/dist/src/services/AstChunker.js +114 -0
- package/dist/src/services/AstParserService.d.ts +10 -6
- package/dist/src/services/AstParserService.js +60 -15
- package/dist/src/services/DatabaseService.d.ts +14 -1
- package/dist/src/services/DatabaseService.js +70 -25
- package/dist/src/services/EmbedderFactory.d.ts +9 -0
- package/dist/src/services/EmbedderFactory.js +29 -0
- package/dist/src/services/IndexerService.d.ts +22 -12
- package/dist/src/services/IndexerService.js +106 -46
- package/dist/src/services/InfinityEmbedder.d.ts +79 -0
- package/dist/src/services/InfinityEmbedder.js +199 -0
- package/dist/src/services/LoggerService.d.ts +2 -1
- package/dist/src/services/McpServer.d.ts +16 -7
- package/dist/src/services/McpServer.js +54 -367
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
- package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
- package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
- package/dist/src/services/NxMonorepoResolver.js +308 -0
- package/dist/src/services/OllamaEmbedder.d.ts +19 -5
- package/dist/src/services/OllamaEmbedder.js +91 -36
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
- package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
- package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
- package/dist/src/{env-loader.js → services/ProjectDiscoveryService.js} +16 -5
- package/dist/src/services/SearcherService.d.ts +46 -7
- package/dist/src/services/SearcherService.js +183 -53
- package/dist/src/services/VoyageEmbedder.d.ts +22 -0
- package/dist/src/services/VoyageEmbedder.js +72 -0
- package/dist/src/services/WebServerService.d.ts +77 -0
- package/dist/src/services/WebServerService.js +267 -0
- package/dist/src/services/http-retry.d.ts +19 -0
- package/dist/src/services/http-retry.js +54 -0
- package/dist/src/services/reranker.d.ts +15 -0
- package/dist/src/services/reranker.js +44 -0
- package/dist/src/setup.d.ts +6 -0
- package/dist/src/setup.js +249 -31
- package/dist/src/tools/FindSymbolTool.d.ts +8 -0
- package/dist/src/tools/FindSymbolTool.js +45 -0
- package/dist/src/tools/GetCallersTool.d.ts +8 -0
- package/dist/src/tools/GetCallersTool.js +42 -0
- package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
- package/dist/src/tools/GetDependenciesTool.js +38 -0
- package/dist/src/tools/GetDependentsTool.d.ts +8 -0
- package/dist/src/tools/GetDependentsTool.js +57 -0
- package/dist/src/tools/ListFilesTool.d.ts +8 -0
- package/dist/src/tools/ListFilesTool.js +39 -0
- package/dist/src/tools/ListProjectsTool.d.ts +8 -0
- package/dist/src/tools/ListProjectsTool.js +44 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
- package/dist/src/tools/ReadFileRangeTool.js +44 -0
- package/dist/src/tools/ReadFileTool.d.ts +8 -0
- package/dist/src/tools/ReadFileTool.js +42 -0
- package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
- package/dist/src/tools/SearchCodebaseTool.js +81 -0
- package/dist/src/tools/format-helpers.d.ts +8 -0
- package/dist/src/tools/format-helpers.js +58 -0
- package/dist/src/tools/index.d.ts +9 -0
- package/dist/src/tools/index.js +21 -0
- package/dist/src/web/dashboard.d.ts +9 -0
- package/dist/src/web/dashboard.js +342 -0
- package/docker/docker-compose.infinity-gpu.yml +19 -0
- package/docker/docker-compose.yml +25 -0
- package/docs/web-dashboard.md +187 -0
- package/package.json +25 -3
- package/dist/src/ast.d.ts +0 -2
- package/dist/src/ast.js +0 -110
- 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/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/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,54 @@
|
|
|
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
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.0-alpha] - 2026-07-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`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.
|
|
13
|
+
- **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.
|
|
14
|
+
- **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.
|
|
15
|
+
- **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.
|
|
16
|
+
- **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).
|
|
17
|
+
- **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.
|
|
18
|
+
- **`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.
|
|
19
|
+
- **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.
|
|
20
|
+
- **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`).
|
|
21
|
+
- **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.
|
|
22
|
+
- **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.
|
|
23
|
+
- **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.
|
|
24
|
+
|
|
25
|
+
### Changed (breaking)
|
|
26
|
+
|
|
27
|
+
- **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.
|
|
28
|
+
- **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.
|
|
29
|
+
- **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.
|
|
30
|
+
|
|
31
|
+
## [0.0.18-alpha] - 2026-07-04
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Web dashboard (`rag-web`)** — a local browser UI at `http://127.0.0.1:4319` (`--port` to change), bound to `127.0.0.1` only, no auth (single-user local tool). Three tabs: a **schema-driven config editor** (form generated from the config JSON Schema; shows the applied default for any unset key, a strict dropdown for the embedding model, and comboboxes suggesting the user's installed Ollama models for the translate/reranker models), a **project overview**, and the **interactive dependency graph** (the same page as `project-graph.html`, embedded via iframe — filters, legend, and project toggles included). The DB password never reaches the browser (masked as `********`, preserved on save). Requires a valid `config.json`; a failed DB/Ollama connection is non-fatal (config editing still works). Full reference: `docs/web-dashboard.md`.
|
|
36
|
+
- **Config JSON Schema + `rag-config edit`** — `config.json` now gets a generated `config.schema.json` sibling, referenced via `"$schema"`, so schema-aware editors (VS Code, JetBrains) provide key autocomplete, type hints, and inline validation. `rag-config edit` opens the config in `$VISUAL` / `$EDITOR` (falls back to `nano` / `notepad`) and re-validates on save.
|
|
37
|
+
- **Nx monorepo support** — when `workspaceRoot` is an Nx workspace (detected by `nx.json`), projects under `apps/`, `libs/`, and `packages/` are discovered as separate indexed projects instead of a single flat scan. Cross-project imports (`@scope/lib`) resolve through the workspace-wide `tsconfig.base.json` path aliases, so `get_dependencies` / `get_dependents` see edges across projects. Non-Nx (flat) workspaces are unaffected.
|
|
38
|
+
- **`IMonorepoResolver` interface + `NxMonorepoResolver`** — resolver abstraction for monorepo tooling. `NxMonorepoResolver` detects `nx.json`, discovers projects via `workspace.json` / `angular.json` or an `apps/`/`libs/`/`packages/` scan for `project.json` / `package.json`, and resolves the root tsconfig (`tsconfig.base.json`, falling back to `tsconfig.json`).
|
|
39
|
+
- **`MonorepoAwareDiscoveryService`** — implements `IProjectDiscovery` and is wired as the default discovery in the container. Uses the resolver when a monorepo is detected; otherwise delegates to `ProjectDiscoveryService` (flat `package.json` scan). Exposes `analyzeWorkspace()` for full workspace metadata.
|
|
40
|
+
- **`DiscoveredProject` metadata** — discovery now carries optional `packageName` (npm scope + name), `type` (`app` | `lib`), and `rootTsConfigPath` per project.
|
|
41
|
+
- **Nested Nx monorepo under a flat workspace** — when `workspaceRoot` is a flat directory but one of its top-level subdirectories is itself an Nx repo, that repo is discovered as a single selectable unit whose sub-projects (`<repo>/<project>`, e.g. `micros-learning/account`) are expanded and indexed individually. `DiscoveredProject` gains an optional `children` field; the exported `toLeafProjects()` helper expands units into their leaf projects and is shared by `IndexCommand` (indexing loop) and `SearcherService` (name → path resolution). Each sub-project carries the repo's `tsconfig.base.json` as `rootTsConfigPath` for cross-project alias resolution.
|
|
42
|
+
- **Root-level Nx libs via `tsconfig.base.json` paths** — `NxMonorepoResolver` now also discovers projects referenced only through `compilerOptions.paths` aliases (e.g. `contracts/`, `interfaces/` living at the repo root), which the `apps/`/`libs/`/`packages/` scan misses. Only targets that resolve to a real project (with `project.json` / `package.json`) are added; the `tsconfig` reader tolerates JSONC (comments, trailing commas).
|
|
43
|
+
- **Tabbed project graph** — `project-graph.html` and the dashboard's `/graph` now have inner tabs: a **Workspace** tab (flat projects + each nested Nx repo collapsed to one node, double-click to open) and **one tab per Nx repo** showing its sub-projects with real import edges between them. Intra-repo edges come from `SearcherService.getProjectEdges()`, which aggregates `code_dependency` (file → file) into project → project links (most-specific owner wins). `getProjectEdges` is exposed on `ISearcher`.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **Single source of truth for config defaults** — built-in defaults now live once in `CONFIG_DEFAULTS` (`config-store.ts`); `config.ts` references them in its schema `.default(...)` calls, so the runtime default and the value the dashboard shows for an unset key cannot diverge. No behavior change.
|
|
48
|
+
- **`analyzeProject` / `scanAndIndex` accept `rootTsConfigPath`** — for monorepo projects the AST parser loads the workspace-wide tsconfig (`tsconfig.base.json`) so ts-morph resolves cross-project path aliases; flat-workspace projects continue to use their local `tsconfig.json`.
|
|
49
|
+
- **`workspace.json` parsing hardened** — a project entry missing a valid `root` is skipped with a warning instead of aborting discovery of the remaining projects in that file.
|
|
50
|
+
- **Nx project discovery deduplicated across strategies** — `workspace.json` / `angular.json`, the directory scan, `tsconfig` paths, and the root-level fallback are now merged and deduped by absolute project path, so a project surfaced by more than one strategy is reported once.
|
|
51
|
+
- **`SearcherService` resolves project names to real roots** — takes the discovery service and builds a lazily-cached `name → filesystem root` map, so nested-monorepo names (`repo/app`) map to their true path (`repo/apps/app`) instead of a naïve `workspaceRoot/name` join. The cache rebuilds on a miss, so projects added or renamed while a long-running `rag-mcp` / `rag-web` server is up are picked up without a restart. `listProjects`, `grepSymbol`, `find_symbol` grep fallback, and `list_files` use it.
|
|
52
|
+
- **Project graph build is non-fatal in `rag-index`** — a failure while computing import edges or writing `project-graph.html` after a successful index is now caught and logged instead of failing the whole run.
|
|
53
|
+
|
|
6
54
|
## [0.0.17-alpha] - 2026-05-13
|
|
7
55
|
|
|
8
56
|
### Added
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ rag-setup
|
|
|
35
35
|
|
|
36
36
|
Walks you through:
|
|
37
37
|
- **Database** — enter connection details; optionally starts Docker containers automatically
|
|
38
|
-
- **
|
|
38
|
+
- **Embedding backend** — pick a provider (`ollama` / `infinity` / `openai-compatible` / `voyage`), its model, and an API key if the provider needs one (see [Embedding providers](#embedding-providers))
|
|
39
39
|
- **Logging** — choose log level and directory (defaults: `error` level, `~/.config/codesearch-mcp/logs/`)
|
|
40
40
|
|
|
41
41
|
Config is saved to `~/.config/codesearch-mcp/config.json`.
|
|
@@ -54,6 +54,14 @@ rag-index --reindex # force re-embed all files (use after model change)
|
|
|
54
54
|
rag-index --select --reindex
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
## Monorepo support
|
|
58
|
+
|
|
59
|
+
If `workspaceRoot` is an [Nx](https://nx.dev) workspace (detected by `nx.json`), `rag-index` discovers each project under `apps/`, `libs/`, and `packages/` as a **separate indexed project** rather than scanning the workspace as one blob. Cross-project imports like `@scope/shared` resolve through the workspace `tsconfig.base.json` path aliases, so `get_dependencies` / `get_dependents` report edges **across** projects.
|
|
60
|
+
|
|
61
|
+
**Nested Nx repos.** `workspaceRoot` may also be a plain directory that *contains* an Nx repo in one of its immediate subdirectories (alongside ordinary projects). That nested repo is detected and expanded into its own projects, named `<repo>/<project>` (e.g. `micros-learning/account`), each resolved with the repo's own `tsconfig.base.json`. In the project selector the whole Nx repo appears as **one** selectable entry (so `.ragprojects` stores just `micros-learning`); selecting it indexes all its sub-projects. Ordinary sibling directories are unaffected.
|
|
62
|
+
|
|
63
|
+
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
|
+
|
|
57
65
|
## Register with Claude Code
|
|
58
66
|
|
|
59
67
|
```bash
|
|
@@ -62,6 +70,18 @@ claude mcp add codesearch-mcp -s user -- rag-mcp
|
|
|
62
70
|
|
|
63
71
|
Then ask Claude anything about your code — it will use the tools below automatically.
|
|
64
72
|
|
|
73
|
+
## Web dashboard
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
rag-web # http://127.0.0.1:4319 (--port to change)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A local web UI (bound to `127.0.0.1` only) with a schema-driven config editor,
|
|
80
|
+
an overview of indexed projects, and the interactive dependency graph. The
|
|
81
|
+
config form shows the applied default for any unset key and offers dropdowns for
|
|
82
|
+
model fields (the embedding model is restricted to dimension-compatible models).
|
|
83
|
+
See [docs/web-dashboard.md](docs/web-dashboard.md) for the full reference.
|
|
84
|
+
|
|
65
85
|
## MCP Tools
|
|
66
86
|
|
|
67
87
|
| Tool | Description |
|
|
@@ -98,7 +118,9 @@ After each indexing run an interactive `project-graph.html` is written to the wo
|
|
|
98
118
|
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:
|
|
99
119
|
|
|
100
120
|
```
|
|
101
|
-
**File:** src/services/InvoiceService.ts
|
|
121
|
+
**File:** src/services/InvoiceService.ts
|
|
122
|
+
**Symbol:** InvoiceService.create
|
|
123
|
+
**Line:** 45
|
|
102
124
|
...code...
|
|
103
125
|
**Relevance:** 0.32
|
|
104
126
|
**Imports:** src/repositories/InvoiceRepo.ts · src/dto/CreateInvoiceDto.ts
|
|
@@ -107,6 +129,8 @@ Pass `include_graph_context: true` to `search_codebase` to see direct imports an
|
|
|
107
129
|
|
|
108
130
|
Useful for immediately understanding a file's role in the dependency graph.
|
|
109
131
|
|
|
132
|
+
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).
|
|
133
|
+
|
|
110
134
|
## Excluding files
|
|
111
135
|
|
|
112
136
|
Create `.ragignore` in a project root — same syntax as `.gitignore`:
|
|
@@ -152,15 +176,80 @@ rag-config reset <key> # remove key (reverts to default)
|
|
|
152
176
|
| `db.password` | — | PostgreSQL password |
|
|
153
177
|
| `db.name` | `rag_<os-username>` | Database name — auto-created on first run |
|
|
154
178
|
| `workspaceRoot` | — | Root of your projects directory |
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
179
|
+
| `embedding.provider` | `ollama` | Embedding backend: `ollama` \| `infinity` \| `openai-compatible` \| `voyage` |
|
|
180
|
+
| `embedding.baseURL` | per-provider | Backend base URL (ollama `http://localhost:11434`, OpenAI `https://api.openai.com/v1`, Infinity/TEI/vLLM `http://localhost:7997`, Voyage `https://api.voyageai.com/v1`) |
|
|
181
|
+
| `embedding.apiKey` | `''` | API key, or `env:VAR_NAME` to read from `.env`/environment. Empty for local servers |
|
|
182
|
+
| `embedding.model` | `bge-m3` | Embedding model |
|
|
183
|
+
| `embedding.dimensions` | (derived) | Vector width override; required for models whose native width exceeds 2000 (Matryoshka). Otherwise derived from model → registry → runtime probe |
|
|
184
|
+
| `embedding.batchSize` | `32` | Texts per batch embedding request |
|
|
157
185
|
| `search.similarityThreshold` | `0.6` | Max cosine distance — raise if search returns nothing |
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
186
|
+
| `reranker.enabled` | `false` | Enable cross-encoder reranking; requires `reranker.model` |
|
|
187
|
+
| `reranker.provider` | `openai-compatible` | Rerank backend — `openai-compatible` `/rerank` only (Infinity, TEI, vLLM). Ollama has no rerank API |
|
|
188
|
+
| `reranker.baseURL` | `embedding.baseURL` | Rerank server base URL (Infinity serves embed + rerank from one server) |
|
|
189
|
+
| `reranker.model` | `''` | Reranker model name (e.g. `qllama/bge-reranker-v2-m3`) |
|
|
160
190
|
| `indexing.astBatchSize` | `3000` | Max files per ts-morph batch — lower if indexing large projects causes OOM |
|
|
191
|
+
| `indexing.chunkStrategy` | `ast` | How TS/JS files are chunked: `ast` = boundaries aligned to functions/classes, `character` = fixed-size splitter. Re-index to apply |
|
|
161
192
|
| `logging.level` | `error` | Log level written to file: `error` \| `warn` \| `info` \| `debug` |
|
|
162
193
|
| `logging.dir` | `~/.config/codesearch-mcp/logs` | Base log directory, or `"false"` to disable |
|
|
163
194
|
|
|
195
|
+
## Embedding providers
|
|
196
|
+
|
|
197
|
+
`embedding.provider` selects the backend. All four produce vectors the same way; they differ in where they run and which models they offer.
|
|
198
|
+
|
|
199
|
+
### Choosing a provider
|
|
200
|
+
|
|
201
|
+
| Provider | When to use | Needs |
|
|
202
|
+
|----------|-------------|-------|
|
|
203
|
+
| `ollama` | Default. Local, zero-setup, general-purpose (`bge-m3`); auto-pulls the model. | Ollama running (Docker or native) |
|
|
204
|
+
| `infinity` | Local, best quality **plus a real reranker** in one container; GPU or CPU. | Docker (managed) or a running Infinity server |
|
|
205
|
+
| `openai-compatible` | OpenAI cloud, or any OpenAI-style server (TEI, vLLM, LM Studio, LocalAI). | `baseURL` (+ `apiKey` for cloud) |
|
|
206
|
+
| `voyage` | Voyage AI cloud, code-specialized embeddings. | Voyage `apiKey` |
|
|
207
|
+
|
|
208
|
+
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).
|
|
209
|
+
|
|
210
|
+
### Switching provider or changing the model
|
|
211
|
+
|
|
212
|
+
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
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Switch provider (example: OpenAI cloud)
|
|
216
|
+
rag-config set embedding.provider openai-compatible
|
|
217
|
+
rag-config set embedding.baseURL https://api.openai.com/v1
|
|
218
|
+
rag-config set embedding.model text-embedding-3-small
|
|
219
|
+
rag-config set embedding.apiKey env:OPENAI_API_KEY # key lives in .env, not config.json
|
|
220
|
+
rag-index --reindex
|
|
221
|
+
|
|
222
|
+
# Change only the model on the current provider
|
|
223
|
+
rag-config set embedding.model <model-name>
|
|
224
|
+
rag-index --reindex
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Per-provider notes:
|
|
228
|
+
|
|
229
|
+
- **ollama** — `rag-index` auto-pulls the model if it isn't present locally (`bge-m3`, `nomic-embed-text`, `mxbai-embed-large`, …). No API key. Change model: `rag-config set embedding.model nomic-embed-text`.
|
|
230
|
+
- **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-index`. Defaults: `jinaai/jina-embeddings-v2-base-code` + reranker `Alibaba-NLP/gte-reranker-modernbert-base`.
|
|
231
|
+
- **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). E.g. `text-embedding-3-small`, `text-embedding-3-large`.
|
|
232
|
+
- **voyage** — set `embedding.apiKey`; e.g. `voyage-code-3`, `voyage-3`.
|
|
233
|
+
|
|
234
|
+
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
|
+
|
|
236
|
+
### Infinity — local embeddings + reranking in one container
|
|
237
|
+
|
|
238
|
+
Select `infinity` in `rag-setup` (or set `embedding.provider = infinity`) to run [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, which Ollama does not offer. During setup you're asked for an embedding model (default `jinaai/jina-embeddings-v2-base-code`, 768 dims) and an optional reranker model (default `Alibaba-NLP/gte-reranker-modernbert-base`); when a reranker is given, `reranker.*` is auto-wired to the same server.
|
|
239
|
+
|
|
240
|
+
Managed vs. external is decided by a `/health` probe at `embedding.baseURL` (default `http://localhost:7997`) — no config flag:
|
|
241
|
+
|
|
242
|
+
- **Managed** — if nothing answers the probe, `rag-setup` (and `rag-index`) bring up the bundled `infinity` Docker service with your model set. 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). Because Infinity binds its models at startup, changing `embedding.model`/reranker model **recreates** the container.
|
|
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.
|
|
244
|
+
|
|
245
|
+
The same "already running?" probe now also applies to Ollama: a reachable backend defaults `rag-setup`'s "start Docker?" prompt to no.
|
|
246
|
+
|
|
247
|
+
### Secrets & external embedders
|
|
248
|
+
|
|
249
|
+
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
|
+
|
|
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. Staying on Ollama + `bge-m3` keeps dimension 1024 and forces no re-index.
|
|
252
|
+
|
|
164
253
|
## License
|
|
165
254
|
|
|
166
255
|
MIT
|
package/dist/app.js
CHANGED
|
@@ -34,25 +34,63 @@ 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");
|
|
47
|
+
const WebCommand_1 = require("./src/commands/WebCommand");
|
|
45
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
|
+
}
|
|
46
82
|
async function main() {
|
|
83
|
+
loadDotEnv(config_store_1.ENV_FILE);
|
|
47
84
|
const argv1 = path.basename(process.argv[1] ?? '');
|
|
48
85
|
const isIndex = argv1 === 'rag-index' || process.argv[2] === 'index' || process.argv[2] === 'rag-index';
|
|
49
86
|
const isMcp = argv1 === 'rag-mcp' || process.argv[2] === 'mcp' || process.argv[2] === 'rag-mcp';
|
|
50
87
|
const isSetup = argv1 === 'rag-setup' || process.argv[2] === 'setup' || process.argv[2] === 'rag-setup';
|
|
51
88
|
const isConfig = argv1 === 'rag-config' || process.argv[2] === 'config' || process.argv[2] === 'rag-config';
|
|
89
|
+
const isWeb = argv1 === 'rag-web' || process.argv[2] === 'web' || process.argv[2] === 'rag-web';
|
|
52
90
|
const isBackfill = process.argv[2] === 'backfill' ||
|
|
53
91
|
(process.argv[2] === 'rag-index' && process.argv[3] === 'backfill');
|
|
54
|
-
const mode = isBackfill ? 'backfill' : isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : (process.argv[2] ?? '');
|
|
55
|
-
const isBinary = ['rag-index', 'rag-mcp', 'rag-setup', 'rag-config'].includes(argv1);
|
|
92
|
+
const mode = isBackfill ? 'backfill' : isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : isWeb ? 'web' : (process.argv[2] ?? '');
|
|
93
|
+
const isBinary = ['rag-index', 'rag-mcp', 'rag-setup', 'rag-config', 'rag-web'].includes(argv1);
|
|
56
94
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
57
95
|
(0, help_1.printHelp)(isBinary ? argv1 : mode);
|
|
58
96
|
process.exit(0);
|
|
@@ -75,6 +113,24 @@ async function main() {
|
|
|
75
113
|
process.exit(1);
|
|
76
114
|
}
|
|
77
115
|
}
|
|
116
|
+
else if (mode === 'web') {
|
|
117
|
+
for (let i = 0; i < effectiveArgs.length; i++) {
|
|
118
|
+
if (effectiveArgs[i] === '--port') {
|
|
119
|
+
const raw = effectiveArgs[i + 1];
|
|
120
|
+
const n = Number(raw);
|
|
121
|
+
if (!raw || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
122
|
+
console.error(`Invalid --port value: ${raw ?? '(missing)'}\n`);
|
|
123
|
+
(0, help_1.printHelp)(isBinary ? argv1 : 'rag-web');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
i++;
|
|
127
|
+
continue; // skip its (validated) value
|
|
128
|
+
}
|
|
129
|
+
console.error(`Unexpected argument: ${effectiveArgs[i]}\n`);
|
|
130
|
+
(0, help_1.printHelp)(isBinary ? argv1 : 'rag-web');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
78
134
|
else if (mode === 'backfill') {
|
|
79
135
|
const knownBackfillFlags = new Set(['--calls']);
|
|
80
136
|
const backfillIdx = process.argv.indexOf('backfill');
|
|
@@ -95,7 +151,7 @@ async function main() {
|
|
|
95
151
|
process.exit(0);
|
|
96
152
|
}
|
|
97
153
|
// All other modes require a valid config.json
|
|
98
|
-
const loggerName = mode === 'index' || mode === 'backfill' ? 'indexer' : mode === 'mcp' ? 'mcp' : undefined;
|
|
154
|
+
const loggerName = mode === 'index' || mode === 'backfill' ? 'indexer' : mode === 'mcp' ? 'mcp' : mode === 'web' ? 'web' : undefined;
|
|
99
155
|
let container;
|
|
100
156
|
try {
|
|
101
157
|
container = (0, container_1.createContainer)(loggerName);
|
|
@@ -114,20 +170,24 @@ async function main() {
|
|
|
114
170
|
console.error(`workspaceRoot is not configured. ${hint}`);
|
|
115
171
|
process.exit(1);
|
|
116
172
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
173
|
+
// Construct only the selected command — building every command eagerly would
|
|
174
|
+
// run each constructor's arg parsing (e.g. WebCommand's --port) for modes that
|
|
175
|
+
// never use it.
|
|
176
|
+
const factories = {
|
|
177
|
+
index: () => new IndexCommand_1.IndexCommand(container, argv1),
|
|
178
|
+
mcp: () => new McpCommand_1.McpCommand(container),
|
|
179
|
+
backfill: () => new BackfillCommand_1.BackfillCommand(container),
|
|
180
|
+
web: () => new WebCommand_1.WebCommand(container, effectiveArgs),
|
|
121
181
|
};
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
182
|
+
const factory = factories[mode];
|
|
183
|
+
if (!factory) {
|
|
124
184
|
const hasUnknownArg = mode !== '';
|
|
125
185
|
if (hasUnknownArg)
|
|
126
186
|
console.error(`Unknown command: ${mode}\n`);
|
|
127
187
|
(0, help_1.printHelp)('default');
|
|
128
188
|
process.exit(hasUnknownArg ? 1 : 0);
|
|
129
189
|
}
|
|
130
|
-
await
|
|
190
|
+
await factory().execute();
|
|
131
191
|
await container.db.destroy();
|
|
132
192
|
process.exit(0);
|
|
133
193
|
}
|
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.ALL_KEYS = exports.ConfigCommand = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
4
39
|
const config_store_1 = require("../config-store");
|
|
5
40
|
Object.defineProperty(exports, "ALL_KEYS", { enumerable: true, get: function () { return config_store_1.ALL_KEYS; } });
|
|
6
41
|
const help_1 = require("../help");
|
|
@@ -16,6 +51,7 @@ class ConfigCommand {
|
|
|
16
51
|
case 'get': return this.get(key);
|
|
17
52
|
case 'set': return this.set(key, value);
|
|
18
53
|
case 'reset': return this.reset(key);
|
|
54
|
+
case 'edit': return this.edit();
|
|
19
55
|
default:
|
|
20
56
|
if (sub)
|
|
21
57
|
console.error(`Unknown subcommand: ${sub}\n`);
|
|
@@ -29,7 +65,7 @@ class ConfigCommand {
|
|
|
29
65
|
cfg = (0, config_store_1.readConfigJson)();
|
|
30
66
|
}
|
|
31
67
|
catch (e) {
|
|
32
|
-
console.error(e.message);
|
|
68
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
33
69
|
process.exit(1);
|
|
34
70
|
}
|
|
35
71
|
if (!cfg) {
|
|
@@ -52,7 +88,7 @@ class ConfigCommand {
|
|
|
52
88
|
cfg = (0, config_store_1.readConfigJson)();
|
|
53
89
|
}
|
|
54
90
|
catch (e) {
|
|
55
|
-
console.error(e.message);
|
|
91
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
56
92
|
process.exit(1);
|
|
57
93
|
}
|
|
58
94
|
if (!cfg) {
|
|
@@ -74,7 +110,7 @@ class ConfigCommand {
|
|
|
74
110
|
console.log(`${key} = ${value}`);
|
|
75
111
|
}
|
|
76
112
|
catch (e) {
|
|
77
|
-
console.error(e.message);
|
|
113
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
78
114
|
process.exit(1);
|
|
79
115
|
}
|
|
80
116
|
}
|
|
@@ -90,7 +126,51 @@ class ConfigCommand {
|
|
|
90
126
|
console.log(`${key} reset to default`);
|
|
91
127
|
}
|
|
92
128
|
catch (e) {
|
|
93
|
-
console.error(e.message);
|
|
129
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
edit() {
|
|
134
|
+
// Ensure config + schema exist so the editor has something to open and
|
|
135
|
+
// resolve the schema against.
|
|
136
|
+
try {
|
|
137
|
+
if (!fs.existsSync(config_store_1.CONFIG_FILE)) {
|
|
138
|
+
(0, config_store_1.writeConfigJson)({});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
(0, config_store_1.writeSchemaFile)();
|
|
142
|
+
// Migrate existing configs: re-add the "$schema" reference if absent
|
|
143
|
+
// (rewrite through writeConfigJson) so editors bind to the schema.
|
|
144
|
+
// Skip silently if the file is currently invalid — the user will fix
|
|
145
|
+
// it in the editor and validation runs on save.
|
|
146
|
+
try {
|
|
147
|
+
const cfg = (0, config_store_1.readConfigJson)();
|
|
148
|
+
if (cfg)
|
|
149
|
+
(0, config_store_1.writeConfigJson)(cfg);
|
|
150
|
+
}
|
|
151
|
+
catch { /* invalid file — leave as-is for the user to edit */ }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const editor = process.env.VISUAL ||
|
|
159
|
+
process.env.EDITOR ||
|
|
160
|
+
(process.platform === 'win32' ? 'notepad' : 'nano');
|
|
161
|
+
const res = (0, child_process_1.spawnSync)(editor, [config_store_1.CONFIG_FILE], { stdio: 'inherit' });
|
|
162
|
+
if (res.error) {
|
|
163
|
+
console.error(`Cannot launch editor "${editor}": ${res.error.message}`);
|
|
164
|
+
console.error('Set $EDITOR or edit the file directly: ' + config_store_1.CONFIG_FILE);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
// Validate what the user saved; readConfigJson throws with a helpful message.
|
|
168
|
+
try {
|
|
169
|
+
(0, config_store_1.readConfigJson)();
|
|
170
|
+
console.log('Config OK');
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
94
174
|
process.exit(1);
|
|
95
175
|
}
|
|
96
176
|
}
|
|
@@ -4,6 +4,7 @@ exports.IndexCommand = void 0;
|
|
|
4
4
|
const interrupt_1 = require("../interrupt");
|
|
5
5
|
const project_selector_1 = require("../project-selector");
|
|
6
6
|
const graph_1 = require("../graph");
|
|
7
|
+
const MonorepoAwareDiscoveryService_1 = require("../services/MonorepoAwareDiscoveryService");
|
|
7
8
|
const file_utils_1 = require("../file-utils");
|
|
8
9
|
class IndexCommand {
|
|
9
10
|
container;
|
|
@@ -13,18 +14,34 @@ class IndexCommand {
|
|
|
13
14
|
this.argv1 = argv1;
|
|
14
15
|
}
|
|
15
16
|
async execute() {
|
|
16
|
-
const { db, indexer, config } = this.container;
|
|
17
|
+
const { db, indexer, searcher, config, discovery, embedder } = this.container;
|
|
17
18
|
const args = process.argv.slice(this.argv1 === 'rag-index' ? 2 : 3);
|
|
18
19
|
const force = args.includes('--reindex');
|
|
19
20
|
const forceSelect = args.includes('--select');
|
|
20
21
|
await db.initDbWithSchema();
|
|
22
|
+
// Ensure the local model is pulled (Ollama only — no-op for cloud), then
|
|
23
|
+
// resolve the active embedding dimension and reconcile it with index_meta
|
|
24
|
+
// (recreate code_chunk on a dimension change, clear rows on a model change)
|
|
25
|
+
// before scanning. A clear error here beats a mid-run vector width mismatch.
|
|
26
|
+
try {
|
|
27
|
+
await embedder.ensureModelAvailable?.();
|
|
28
|
+
const meta = await indexer.prepareEmbeddingSchema();
|
|
29
|
+
console.log(`Embedding: ${meta.provider} / ${meta.model} (${meta.dimensions} dims)`);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error(`\nEmbedding setup failed: ${err.message}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
21
35
|
const controller = new AbortController();
|
|
22
36
|
const teardown = (0, interrupt_1.setupInterrupt)(controller);
|
|
23
37
|
try {
|
|
24
|
-
const discovered =
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
38
|
+
const discovered = discovery.discover(config.workspaceRoot);
|
|
39
|
+
const selected = await new project_selector_1.ProjectSelector(config.workspaceRoot).select(discovered, forceSelect);
|
|
40
|
+
if (selected.length === 0)
|
|
27
41
|
return;
|
|
42
|
+
// Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
|
|
43
|
+
// sub-projects; ordinary projects pass through unchanged.
|
|
44
|
+
const projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
|
|
28
45
|
const filesPerProject = projects.map(p => (0, file_utils_1.collectFiles)(p.path, (0, file_utils_1.loadIgnore)(p.path), p.path));
|
|
29
46
|
const totalFiles = filesPerProject.reduce((sum, f) => sum + f.length, 0);
|
|
30
47
|
console.log(`\n${projects.length} projects — ${totalFiles} files total`);
|
|
@@ -34,7 +51,7 @@ class IndexCommand {
|
|
|
34
51
|
for (let i = 0; i < projects.length; i++) {
|
|
35
52
|
if (controller.signal.aborted)
|
|
36
53
|
break;
|
|
37
|
-
const stats = await indexer.scanAndIndex(projects[i].path, projects[i].name, force, controller.signal, filesPerProject[i]);
|
|
54
|
+
const stats = await indexer.scanAndIndex(projects[i].path, projects[i].name, force, controller.signal, filesPerProject[i], { rootTsConfigPath: projects[i].rootTsConfigPath ?? undefined });
|
|
38
55
|
allStats.push(stats);
|
|
39
56
|
const remaining = projects.length - allStats.length;
|
|
40
57
|
if (remaining > 0 && !controller.signal.aborted) {
|
|
@@ -45,8 +62,19 @@ class IndexCommand {
|
|
|
45
62
|
if (controller.signal.aborted)
|
|
46
63
|
console.log('\nIndexing interrupted.');
|
|
47
64
|
this.printSummary(allStats);
|
|
48
|
-
if (!controller.signal.aborted)
|
|
49
|
-
|
|
65
|
+
if (!controller.signal.aborted) {
|
|
66
|
+
// The graph is a post-index nicety — a failure here (DB dropped after a
|
|
67
|
+
// long run, unreadable dir) must not fail the whole run: indexing already
|
|
68
|
+
// succeeded and the summary is printed.
|
|
69
|
+
try {
|
|
70
|
+
// Real import edges (code_dependency, just written) → intra-repo graph links.
|
|
71
|
+
const projectEdges = await searcher.getProjectEdges();
|
|
72
|
+
(0, graph_1.generateProjectGraph)(projects, projectEdges, config.workspaceRoot);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error(` ⚠ Could not build project graph: ${err.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
50
78
|
}
|
|
51
79
|
finally {
|
|
52
80
|
teardown();
|
|
@@ -7,20 +7,25 @@ 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
|
-
startupError = err;
|
|
17
|
-
logger.error('McpCommand:
|
|
20
|
+
startupError = err instanceof Error ? err : new Error(String(err));
|
|
21
|
+
logger.error('McpCommand: startup check failed', { reason: startupError.message });
|
|
18
22
|
}
|
|
19
23
|
try {
|
|
20
24
|
await mcp.start(startupError);
|
|
21
25
|
}
|
|
22
26
|
catch (err) {
|
|
23
|
-
|
|
27
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
28
|
+
logger.error('McpCommand: MCP server failed to start', { reason });
|
|
24
29
|
return;
|
|
25
30
|
}
|
|
26
31
|
// server.connect() resolves immediately after setup; keep the process alive
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Container } from '../container';
|
|
2
2
|
import type { ICommand } from './ICommand';
|
|
3
|
-
export declare class
|
|
3
|
+
export declare class WebCommand implements ICommand {
|
|
4
4
|
private container;
|
|
5
|
-
|
|
5
|
+
private readonly port;
|
|
6
|
+
constructor(container: Container, args?: string[]);
|
|
6
7
|
execute(): Promise<void>;
|
|
7
8
|
}
|