@esankhan3/code-search-mcp 0.0.2 → 0.2.0

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.
Files changed (50) hide show
  1. package/README.md +343 -248
  2. package/dist/__tests__/config-resolver.test.js +153 -0
  3. package/dist/__tests__/daemon-rpc.test.js +100 -0
  4. package/dist/__tests__/observability.test.js +69 -0
  5. package/dist/__tests__/projects.test.js +141 -0
  6. package/dist/backends/daemon-client.js +87 -0
  7. package/dist/backends/in-process.js +104 -0
  8. package/dist/backends/index.js +29 -0
  9. package/dist/cli/index.js +231 -0
  10. package/dist/core/config.js +362 -49
  11. package/dist/core/env-config.js +45 -43
  12. package/dist/daemon/index.js +119 -0
  13. package/dist/daemon/rpc-server.js +99 -0
  14. package/dist/daemon/watcher.js +105 -0
  15. package/dist/index.js +7 -0
  16. package/dist/observability/logger.js +66 -0
  17. package/dist/observability/metrics.js +138 -0
  18. package/dist/projects/registry.js +146 -0
  19. package/dist/resources/resources.js +3 -3
  20. package/dist/server.js +6 -2
  21. package/dist/tools/graph.js +1 -1
  22. package/dist/tools/index-tools.js +1 -1
  23. package/dist/tools/profile.js +2 -2
  24. package/dist/tools/search.js +1 -1
  25. package/dist/transports/http-transport.js +38 -0
  26. package/package.json +31 -12
  27. package/scripts/smoke/run-smoke.sh +167 -0
  28. package/dist/core/ast-graph-builder.js +0 -800
  29. package/dist/core/chunker.js +0 -368
  30. package/dist/core/claude-runner.js +0 -173
  31. package/dist/core/cross-repo-detector.js +0 -887
  32. package/dist/core/embedder.js +0 -334
  33. package/dist/core/file-walker.js +0 -186
  34. package/dist/core/git-diff.js +0 -160
  35. package/dist/core/graph-metrics.js +0 -233
  36. package/dist/core/indexer.js +0 -552
  37. package/dist/core/project-graph-builder.js +0 -202
  38. package/dist/core/query-classifier.js +0 -144
  39. package/dist/core/query-router.js +0 -210
  40. package/dist/core/rag-evaluator.js +0 -213
  41. package/dist/core/repo-profiler.js +0 -383
  42. package/dist/core/reranker.js +0 -218
  43. package/dist/core/retriever.js +0 -242
  44. package/dist/core/semantic-edge-detector.js +0 -147
  45. package/dist/core/service-mesh-inferrer.js +0 -241
  46. package/dist/core/structural-hasher.js +0 -316
  47. package/dist/core/tree-sitter-parser.js +0 -539
  48. package/dist/core/vector-store.js +0 -259
  49. package/dist/core/workspace-detector.js +0 -481
  50. /package/dist/{core → backends}/types.js +0 -0
package/README.md CHANGED
@@ -1,333 +1,428 @@
1
- <div align="center">
1
+ # @esankhan3/code-search-mcp
2
2
 
3
- <br/>
3
+ **Your whole codebase, in any MCP client.**
4
4
 
5
- # Code Search MCP
6
-
7
- **Multi-repo code intelligence via the Model Context Protocol**
8
-
9
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](../../LICENSE)
10
- [![MCP](https://img.shields.io/badge/protocol-MCP%201.0-7C3AED?style=flat-square)](https://modelcontextprotocol.io)
11
- [![Node](https://img.shields.io/badge/node-%E2%89%A5%2020-339933?style=flat-square&logo=node.js&logoColor=white)]()
12
- [![Docker](https://img.shields.io/badge/docker-ready-2496ED?style=flat-square&logo=docker&logoColor=white)]()
13
-
14
- [Quick Start](#quick-start) · [Tools](#tools) · [Deploy](#deployment) · [Configuration](#configuration)
15
-
16
- </div>
17
-
18
- ---
19
-
20
- ## What You Get
21
-
22
- Ask questions in natural language. The server searches, traces, and analyzes across all your repos.
23
-
24
- | Query | Tool | Result |
25
- |:------|:-----|:-------|
26
- | "How does authentication work across services?" | `search_code` | 8 results from 3 repos, ranked by relevance |
27
- | "What calls handlePayment?" | `find_callers` | 12 call sites traced across api-gateway and billing-service |
28
- | "If I change user.ts, what breaks?" | `impact_analysis` | 4 files in 2 repos identified as affected |
29
-
30
- Works with Claude Code, Claude Desktop, Cursor, and any MCP client.
5
+ A Model Context Protocol server that wraps `@anvil/knowledge-core`
6
+ behind an MCP-compliant tool surface. Point Claude Code, Claude
7
+ Desktop, or Cursor at it and your agent gains hybrid search,
8
+ caller tracing, and impact analysis across every repo you've
9
+ indexed — local or remote.
31
10
 
32
11
  ---
33
12
 
34
- ## Architecture
35
-
36
- ```mermaid
37
- graph LR
38
- A[Your AI Client] -- stdio / http --> B[Code Search MCP Server]
39
- B --> C[LanceDB vectors]
40
- B --> D[AST Graph]
41
- B --> E[Git Repos]
42
- ```
43
-
44
- ---
13
+ ## Why "code search" needs more than grep
45
14
 
46
- ## Quick Start
15
+ MCP gave us a clean way to expose tools to LLM agents. What's
16
+ missing is a *good* tool to expose. `grep` over your repo answers
17
+ "where is this string." Vector search over your repo answers
18
+ "what's semantically near this." Neither answers "who calls this
19
+ function and which other repos depend on it."
47
20
 
48
- <details open>
49
- <summary><strong>Local mode</strong> -- run on your machine</summary>
21
+ **code-search-mcp gives agents the answer.** Same hybrid retriever
22
+ that powers Anvil's pipelines, exposed through MCP. Agents get
23
+ AST-aware chunks, cross-repo graph traversal, and a whole-project
24
+ view — without you writing a single tool.
50
25
 
51
- ```bash
52
- claude mcp add code-search -- npx @esankhan3/code-search-mcp --local /path/to/repos
53
- ```
54
-
55
- For GitHub orgs:
56
-
57
- ```bash
58
- claude mcp add code-search -- npx @esankhan3/code-search-mcp --local github:your-org --token ghp_xxx
59
- ```
60
-
61
- </details>
62
-
63
- <details>
64
- <summary><strong>Connect to a team server</strong></summary>
65
-
66
- **Claude Code:**
67
-
68
- ```bash
69
- claude mcp add code-search \
70
- -e CODE_SEARCH_SERVER=https://your-server:3100 \
71
- -e CODE_SEARCH_API_KEY=your-api-key \
72
- -- npx @esankhan3/code-search-mcp
73
- ```
74
-
75
- **Claude Desktop / Cursor (JSON config):**
76
-
77
- ```json
26
+ ```jsonc
27
+ // claude_desktop_config.json
78
28
  {
79
29
  "mcpServers": {
80
30
  "code-search": {
81
31
  "command": "code-search-mcp",
82
- "env": {
83
- "CODE_SEARCH_SERVER": "https://your-server:3100",
84
- "CODE_SEARCH_API_KEY": "your-api-key"
85
- }
32
+ "args": ["--local", "/path/to/your/projects"]
86
33
  }
87
34
  }
88
35
  }
89
36
  ```
90
37
 
91
- </details>
38
+ That's the whole setup. Claude Desktop now knows your codebase.
92
39
 
93
- <details>
94
- <summary><strong>Deploy the server</strong> (Docker)</summary>
40
+ ---
95
41
 
96
- ```bash
97
- cp .env.example .env
98
- docker compose up
42
+ ## Three bin entries (one install)
43
+
44
+ The npm package ships three binaries. Pick the one that matches the
45
+ job — none of them need a separate install.
46
+
47
+ | Bin | Use case |
48
+ |---|---|
49
+ | `code-search-mcp` | The MCP-server entry. Three operating modes below. |
50
+ | `code-search` | Standalone CLI: `index` / `query` / `status` / `reset` / `daemon` / `serve` / `mcp`. |
51
+ | `code-search-daemon` | Long-running indexer with file-watcher and JSON-RPC socket. |
52
+
53
+ ### Standalone CLI
54
+ ```sh
55
+ code-search index . # index current dir
56
+ code-search query "where do we verify JWTs" # hybrid by default
57
+ code-search query "createPet" --mode bm25 --top-k 5 # exact-identifier mode
58
+ code-search status --project petshop # JSON report
59
+ code-search reset --project petshop # drop the index
60
+ code-search --print-config # resolved config (redacted)
99
61
  ```
62
+ Every `CodeSearchConfig` leaf is settable as `--<dotted-path>` —
63
+ e.g. `--embedding.provider codestral`, `--retrieval.max-chunks 12`,
64
+ `--no-indexing.auto-index`.
100
65
 
101
- Index repos via the admin API (no restart needed):
102
-
103
- ```bash
104
- curl -X POST http://localhost:3100/index \
105
- -H 'Content-Type: application/json' \
106
- -d '{"path": "/repos/my-service", "project": "my-project"}'
66
+ ### Daemon mode (long-running indexer)
67
+ ```sh
68
+ code-search-daemon --workspace /path/to/repos
107
69
  ```
108
-
109
- Enable automatic reindexing:
110
-
111
- ```bash
112
- CODE_SEARCH_REINDEX_INTERVAL=1h # 0 = disabled (default)
70
+ Builds the index once, then watches the workspace and debounce-
71
+ reindexes on file events. Listens on a UDS socket at
72
+ `<dataDir>/daemon/<project>.sock` (named pipe on Windows). The CLI
73
+ and MCP server automatically prefer the daemon when it's alive,
74
+ falling back to in-process when it isn't.
75
+
76
+ ### MCP — three modes (`code-search-mcp` argv)
77
+
78
+ **Remote proxy (default)**
79
+ ```sh
80
+ code-search-mcp # default — proxies to remote
81
+ code-search-mcp --remote URL
113
82
  ```
83
+ Spawns a stdio MCP server that forwards to a remote HTTP server.
84
+ Zero local setup, zero local index — perfect for hosted
85
+ deployments where the index lives in the cloud and dev machines
86
+ stay light. Auth via `CODE_SEARCH_API_KEY`.
87
+
88
+ **Local**
89
+ ```sh
90
+ code-search-mcp --local /path/to/repos
91
+ code-search-mcp --local github:my-org/my-pattern
92
+ ```
93
+ Discovers every repo under a path (or clones a GitHub org), builds
94
+ the knowledge base, and serves over stdio. Works fully offline if
95
+ your embedder + reranker are local (Ollama).
114
96
 
115
- </details>
97
+ **Serve**
98
+ ```sh
99
+ code-search-mcp --serve --port 4000 --auth api-key
100
+ ```
101
+ Boots an HTTP server (Streamable HTTP transport, SSE optional)
102
+ with `/mcp`, `/health`, `/ready`, `/version`, `/status`,
103
+ `/metrics` (Prometheus), `/admin/api/status`, and an admin
104
+ `POST /index`. Use this to host one index for a whole team — every
105
+ dev points their client at the same URL.
116
106
 
117
107
  ---
118
108
 
119
- ## Tools
109
+ ## Tool surface
110
+
111
+ Eleven tools across four categories. Every one of them maps to a
112
+ function in `@anvil/knowledge-core` — you're getting the same
113
+ retrieval pipeline that powers the Anvil dashboard.
114
+
115
+ ### Search
116
+ | Tool | What it does |
117
+ |---|---|
118
+ | `search_code` | Hybrid retrieval — vector + BM25 + graph + rerank |
119
+ | `search_semantic` | Vector-only (paraphrases, intent) |
120
+ | `search_exact` | BM25-only (identifiers, error codes) |
121
+
122
+ ### Graph
123
+ | Tool | What it does |
124
+ |---|---|
125
+ | `get_repo_graph` | Single-repo AST graph |
126
+ | `get_cross_repo_edges` | Inter-repo edges (Kafka, HTTP, gRPC, shared types, …) |
127
+ | `find_callers` | Who calls this function |
128
+ | `find_dependencies` | What this function calls |
129
+ | `impact_analysis` | What breaks if this changes |
130
+
131
+ ### Profiles
132
+ | Tool | What it does |
133
+ |---|---|
134
+ | `list_repos` | All indexed repos with profiles |
135
+ | `get_repo_profile` | Single repo's tech stack, structure, key entry points |
136
+
137
+ ### Index
138
+ | Tool | What it does |
139
+ |---|---|
140
+ | `index_status` | Last indexed SHA, chunk count, embedding provider |
141
+
142
+ Plus four MCP resources via `code-search://`:
143
+ `repos`, `system-graph`, `repo/{name}/profile`, `repo/{name}/graph`.
120
144
 
121
- **Search** -- `search_code` · `search_semantic` · `search_exact`
122
- Hybrid vector+BM25, semantic-only, and keyword search across all indexed repos.
123
-
124
- **Graph** -- `get_repo_graph` · `find_callers` · `find_dependencies` · `impact_analysis` · `get_cross_repo_edges`
125
- AST knowledge graph, caller tracing, dependency lookup, change impact, and cross-repo connections.
145
+ ---
126
146
 
127
- **Profiles** -- `list_repos` · `get_repo_profile`
128
- Indexed repo listing and LLM-generated repo profiles with role, stack, and endpoints.
147
+ ## What makes the search good
129
148
 
130
- **Index** -- `index_status`
131
- Chunk count, embedding provider, repo list, and last-indexed timestamp.
149
+ This isn't a thin wrapper. The retrieval pipeline runs:
132
150
 
133
- ### Server Admin API
151
+ 1. **Vector ⫽ BM25 in parallel** — semantic + lexical recall.
152
+ 2. **Reciprocal Rank Fusion** — combine without one dominating.
153
+ 3. **AST tripartite expansion** — pull in callers, callees, type
154
+ refs via the project graph.
155
+ 4. **Cross-encoder rerank** — Qwen3-Reranker by default; Cohere /
156
+ Voyage / OpenAI-compatible swappable.
134
157
 
135
- | Endpoint | Description |
136
- |:---------|:------------|
137
- | `POST /index` | Index repos at a path. Body: `{"path": "...", "project": "...", "force": false}` |
138
- | `GET /health` | Server status, active sessions, index readiness |
139
- | `GET /status` | Live indexing progress, phase, percent, history |
158
+ A query classifier picks adaptive weights — identifier queries
159
+ lean BM25, natural-language leans vector, error codes lean both.
160
+ You don't tune anything; the retriever does it per query.
140
161
 
141
162
  ---
142
163
 
143
- ## Under the Hood
164
+ ## Multi-repo by design
165
+
166
+ Unlike single-repo code-search MCPs, this one is built for the
167
+ real world: a team has *many* repos, and the interesting
168
+ questions cross them. Where does this Kafka topic get consumed?
169
+ Which services depend on this proto? What service-mesh edges
170
+ exist between web and api?
144
171
 
145
- <details>
146
- <summary>Incremental Indexing -- 4-layer cost optimization</summary>
172
+ Fourteen cross-repo edge strategies cover shared types, Kafka,
173
+ HTTP, gRPC, databases, env vars, npm/workspace deps, k8s,
174
+ docker-compose, proto, Redis, S3, and shared constants. Plus an
175
+ LLM-inferred semantic edge layer. `find_callers` works *across
176
+ repos*. So does `impact_analysis`.
147
177
 
148
- | Layer | Scope | What it does |
149
- |:------|:------|:-------------|
150
- | 1. Git SHA skip | Repo | Compares HEAD against last indexed SHA; skips unchanged repos |
151
- | 2. Git diff via Merkle DAG | File | Only processes added/modified/deleted files from `git diff` |
152
- | 3. Content hash SHA-256 | File | Fallback dedup when git diff is unavailable |
153
- | 4. Embedding diff against LanceDB | Chunk | Only new/changed chunks sent to the embedding provider |
178
+ ---
179
+
180
+ ## Observability
154
181
 
155
- **Before:** 2 files changed out of 1,000 -- re-embed all 1,000 chunks.
156
- **After:** 2 files changed -- 5 chunks re-embedded, 995 preserved.
182
+ `code-search-mcp --serve` exposes these endpoints out of the box —
183
+ no scrape config or APM agent required.
157
184
 
158
- Scheduled reindexing (`CODE_SEARCH_REINDEX_INTERVAL=1h`) is practical even with paid providers -- most runs complete in seconds with zero API calls.
185
+ | Endpoint | Returns |
186
+ |---|---|
187
+ | `GET /health` | Liveness ping with project + uptime |
188
+ | `GET /ready` | `200` only after first index completes |
189
+ | `GET /version` | Package + Node + platform |
190
+ | `GET /status` | Current indexing phase + last-success history |
191
+ | `GET /metrics` | Prometheus text format (queries, latency histogram, chunks, embedding/LLM calls, errors) |
192
+ | `GET /admin/api/status` | JSON status dashboard for the admin UI |
159
193
 
160
- </details>
194
+ Set `CODE_SEARCH_STRUCTURED_LOGS=1` for one-JSON-line-per-event
195
+ stderr logs, and `CODE_SEARCH_TELEMETRY_RECORD_QUERIES=1` to opt
196
+ into recording query text in spans (off by default — queries are
197
+ PII-shaped).
161
198
 
162
- <details>
163
- <summary>Embedding Providers</summary>
199
+ ---
164
200
 
165
- | Provider | Env var | Model |
166
- |:---------|:--------|:------|
167
- | Ollama (local, free) | `OLLAMA_HOST` | `bge-m3` |
168
- | Mistral / Codestral | `MISTRAL_API_KEY` | `codestral-embed-2505` |
169
- | OpenAI | `OPENAI_API_KEY` | `text-embedding-3-large` |
170
- | Voyage AI | `VOYAGE_API_KEY` | `voyage-code-3` |
171
- | Gemini | OAuth via `~/.gemini/` | `text-embedding-004` |
172
- | Any OpenAI-compatible | `CODE_SEARCH_EMBEDDING_BASE_URL` | Custom |
201
+ ## Multi-project / multi-tenant
173
202
 
174
- **Bring Your Own Provider** -- any service with an OpenAI-compatible `/v1/embeddings` endpoint:
203
+ Drop project entries under `<dataDir>/projects/<name>/project.yaml`:
175
204
 
176
- ```bash
177
- CODE_SEARCH_EMBEDDING_PROVIDER=custom
178
- CODE_SEARCH_EMBEDDING_BASE_URL=https://api.together.xyz
179
- CODE_SEARCH_EMBEDDING_MODEL=togethercomputer/m2-bert-80M-8k-retrieval
180
- CODE_SEARCH_EMBEDDING_API_KEY=your-key
205
+ ```yaml
206
+ workspace: /Users/me/repos/petshop
207
+ scopes: [team-shop]
208
+ quotas:
209
+ max_queries_per_minute: 60
210
+ max_embedding_cost_usd: 5
181
211
  ```
182
212
 
183
- </details>
213
+ `ProjectRegistry` reads them at boot. `projectAccessAllowed`
214
+ gates by scope (`*` is admin). `checkProjectQuota` enforces a
215
+ sliding 1-minute window per `(identity, project)`.
184
216
 
185
- <details>
186
- <summary>LLM Configuration -- repo profiling and service mesh inference</summary>
217
+ ---
187
218
 
188
- The server uses an LLM for repo profiling and service mesh detection. Mode is auto-detected:
219
+ ## Server features
189
220
 
190
- - API key present -- `api` mode
191
- - Claude CLI available -- `cli` mode
192
- - Neither -- `none` (LLM features disabled)
221
+ ### Auth, three flavors
222
+ `none` (local-only, default 127.0.0.1 binding), `api-key`
223
+ (`Authorization: Bearer ...`, timing-safe compare), `jwt` (HS256,
224
+ signature + `exp` + `iss` validated). Public binding without auth
225
+ logs a warning, because that's what should happen.
193
226
 
194
- Override with environment variables:
227
+ ### Rate limiting
228
+ In-memory sliding 1-minute window keyed by identity subject. Tune
229
+ per-deployment.
195
230
 
196
- ```bash
197
- CODE_SEARCH_LLM_MODE=api # api | cli | none
198
- CODE_SEARCH_LLM_API_KEY=sk-...
199
- CODE_SEARCH_LLM_PROVIDER=anthropic # anthropic | openai | ollama | custom
200
- ```
231
+ ### Auto-reindex
232
+ `CODE_SEARCH_REINDEX_INTERVAL=30m` (or `1h`, `6h`, `0` to disable)
233
+ runs an incremental re-index in the background. Skips itself if a
234
+ manual index is already running. The timer is `unref`'d so it
235
+ doesn't keep the process alive.
201
236
 
202
- </details>
237
+ ### Status tracking
238
+ Every index call lands in a 50-entry FIFO history with start
239
+ timestamp, success / error, last duration. Available at
240
+ `GET /status`.
203
241
 
204
- <details>
205
- <summary>Cross-Repo Detection -- 14 automatic strategies</summary>
242
+ ### Streamable HTTP sessions
243
+ Per-session `StreamableHTTPServerTransport`, max 100 concurrent
244
+ sessions, 30 min TTL. The remote proxy captures `mcp-session-id`
245
+ for continuity. SSE transport available as a fallback.
206
246
 
207
- npm dependencies, TypeScript shared types, HTTP route matching, Kafka topics, gRPC services, database tables, environment variables, Docker Compose links, Redis keys, S3 buckets, protobuf imports, shared constants, API schemas (OpenAPI/Swagger), and Kubernetes service references.
247
+ ### Admin index endpoint
248
+ `POST /index` with `{ project, dirPath?, opts? }` triggers a
249
+ fresh index. Gated against concurrent runs. Auth-required when
250
+ auth is on.
208
251
 
209
- </details>
252
+ ---
210
253
 
211
- <details>
212
- <summary>Supported Languages</summary>
254
+ ## How it fits with the rest of Anvil
213
255
 
214
- **Tree-sitter AST parsing:** TypeScript, JavaScript, Go, Python, Rust, Java, PHP, C/C++
256
+ ```
257
+ ┌─────────────────────────┐
258
+ │ MCP client │
259
+ │ (Claude Code, │
260
+ │ Claude Desktop, │
261
+ │ Cursor, …) │
262
+ └────────────┬────────────┘
263
+ │ stdio / HTTP
264
+ ▼
265
+ ┌─────────────────────────┐
266
+ │ code-search-mcp │
267
+ │ (this package) │
268
+ │ tools + resources │
269
+ └────────────┬────────────┘
270
+ │ wraps
271
+ ▼
272
+ ┌─────────────────────────┐
273
+ │ @anvil/knowledge-core │
274
+ │ AST chunks + │
275
+ │ project graph + │
276
+ │ hybrid retriever │
277
+ └────────────┬────────────┘
278
+ │ on disk
279
+ ▼
280
+ ~/.anvil/knowledge-base/<project>/
281
+ ```
215
282
 
216
- All other text files are indexed with BM25 keyword search.
283
+ Three different fronts, one knowledge stack:
217
284
 
218
- </details>
285
+ - **The CLI** indexes via `anvil index` and retrieves during
286
+ pipelines.
287
+ - **The dashboard** browses the project graph and surfaces
288
+ retrieval results in pipeline UI.
289
+ - **code-search-mcp** exposes the same retriever to any MCP
290
+ client.
219
291
 
220
- <details>
221
- <summary>Monitoring -- GET /status</summary>
292
+ If you've already indexed a project with `anvil index`, this
293
+ server picks it up — same `~/.anvil/knowledge-base/` path, same
294
+ LanceDB store, same graph files.
222
295
 
223
- ```json
224
- {
225
- "indexing": {
226
- "status": "indexing",
227
- "phase": "embedding",
228
- "message": "Embedding: 12/15 new (~3s remaining)",
229
- "percent": 85,
230
- "elapsedMs": 4200,
231
- "lastSuccess": "2026-04-18T14:30:00Z",
232
- "lastDurationMs": 8500,
233
- "history": [
234
- {"type": "start", "message": "auto-reindex: started..."},
235
- {"type": "complete", "message": "Completed: 1200 chunks, 4 repos in 8s"}
236
- ]
237
- }
238
- }
296
+ ---
297
+
298
+ ## Configuration — defaults → file → env → CLI flags
299
+
300
+ Single resolver in `src/core/config.ts`. Five precedence layers
301
+ (lowest → highest):
302
+
303
+ 1. compiled-in `DEFAULTS`
304
+ 2. `~/.code-search/config.yaml` (user-global)
305
+ 3. `<workspaceDir>/.code-search.yaml` (per-workspace overlay)
306
+ 4. `CODE_SEARCH_*` env vars
307
+ 5. CLI flags via `--<dotted-path>` (e.g. `--embedding.provider codestral`)
308
+
309
+ Run `code-search --print-config` or `code-search-mcp --print-config`
310
+ any time to see the resolved struct (secrets redacted).
311
+
312
+ | Var (`CODE_SEARCH_*`) | Effect |
313
+ |---|---|
314
+ | `SERVER` | Remote URL (proxy mode) |
315
+ | `API_KEY` | API key for proxy or serve modes |
316
+ | `DATA_DIR` | Override the index storage root |
317
+ | `PORT` / `HOST` / `TRANSPORT` | HTTP server bind |
318
+ | `AUTH_MODE` / `AUTH_API_KEYS` / `AUTH_JWT_SECRET` | Server auth |
319
+ | `REINDEX_INTERVAL` | `30m` / `1h` / `6h` / `0` |
320
+ | `EMBEDDING_PROVIDER` | `auto` / `codestral` / `voyage` / `openai` / `ollama` / `openai-compatible` / `custom` / `gemini-oauth` |
321
+ | `EMBEDDING_MODEL` / `EMBEDDING_DIMENSIONS` | Model + dims |
322
+ | `EMBEDDING_API_KEY` / `EMBEDDING_BASE_URL` | Credentials |
323
+ | `OLLAMA_HOST` | Default `http://localhost:11434` |
324
+ | `RERANKER_PROVIDER` | `ollama` (default) / `cohere` / `voyage` / `openai-compatible` / `custom` / `none` |
325
+ | `RERANKER_MODEL` / `RERANKER_API_KEY` / `RERANKER_BASE_URL` | Reranker config |
326
+ | `RETRIEVAL_MAX_CHUNKS` / `RETRIEVAL_MAX_TOKENS` | Retrieval tuning |
327
+ | `AUTO_INDEX` | `false` to disable startup auto-index |
328
+ | `DAEMON_DISABLED` / `DAEMON_AUTO_SPAWN` | Daemon backend control |
329
+ | `LLM_MODE` / `LLM_PROVIDER` / `LLM_MODEL` / `LLM_API_KEY` / `LLM_BASE_URL` | Profiling + service-mesh LLM. Pre-promoted to `ANVIL_LLM_*` so the agent-core integration is silent. |
330
+ | `INDEXING_DEBOUNCE_MS` / `CHUNKING_MAX_TOKENS` | Indexer knobs |
331
+ | `METRICS_ENABLED` / `STRUCTURED_LOGS` / `TELEMETRY_RECORD_QUERIES` | Observability |
332
+
333
+ ### Per-workspace config
334
+
335
+ Drop a `.code-search.yaml` at the root of your workspace:
336
+
337
+ ```yaml
338
+ embedding:
339
+ provider: codestral
340
+ model: codestral-embed-2505
341
+ dimensions: 1024
342
+ reranker:
343
+ provider: ollama
344
+ model: qwen2.5-coder:7b # the default that actually answers
345
+ retrieval:
346
+ max_chunks: 12
347
+ hybrid_weights: { vector: 0.55, bm25: 0.3, graph: 0.15 }
348
+ indexing:
349
+ auto_index: true
350
+ debounce_ms: 500
239
351
  ```
240
352
 
241
- </details>
353
+ CLI flags override env, env overrides this file, this file overrides
354
+ the user-global `~/.code-search/config.yaml`, that overrides
355
+ `DEFAULTS`.
242
356
 
243
357
  ---
244
358
 
245
- ## Configuration
359
+ ## Philosophy
246
360
 
247
- ### Essential
361
+ **Multi-repo or nothing.** Real codebases span repos. Code search
362
+ that doesn't is a toy.
248
363
 
249
- | Variable | Role | Description |
250
- |:---------|:-----|:------------|
251
- | `CODE_SEARCH_SERVER` | Client | Remote server URL |
252
- | `CODE_SEARCH_API_KEY` | Client | API key for remote server |
253
- | `CODE_SEARCH_EMBEDDING_PROVIDER` | Server | `auto` \| `codestral` \| `openai` \| `voyage` \| `ollama` \| `custom` |
254
- | `CODE_SEARCH_LLM_API_KEY` | Server | API key for LLM provider (repo profiling, service mesh) |
255
- | `CODE_SEARCH_AUTH_MODE` | Server | `none` \| `api-key` \| `jwt` |
364
+ **No vendor LLM SDK.** Anything LLM-driven (repo profiling,
365
+ semantic edges) routes through `@anvil/agent-core`'s single-shot —
366
+ the same router, retries, and cost ledger as the rest of Anvil.
256
367
 
257
- <details>
258
- <summary>Full Configuration Reference</summary>
368
+ **Three modes, one binary.** Remote proxy for hosted, local for
369
+ solo, serve for teams. The dispatcher is `src/index.ts:argv`.
259
370
 
260
- **Server CLI flags:**
371
+ **Stateless sessions.** HTTP sessions are in-memory by design.
372
+ Restart drops them; clients re-init on first request. No persistent
373
+ session store to maintain.
261
374
 
262
- ```
263
- code-search-mcp --serve [options]
264
- --port <port> HTTP port (default: 3100)
265
- --auth <mode> none | api-key | jwt
266
- --transport <mode> streamable-http | sse
267
-
268
- code-search-mcp --local [source] [options]
269
- --project <name> Project name (default: derived from source)
270
- --token <token> GitHub token (or GITHUB_TOKEN env)
271
- --force Force full re-index
272
- ```
273
-
274
- **All environment variables:**
275
-
276
- | Variable | Description | Default |
277
- |:---------|:------------|:--------|
278
- | `CODE_SEARCH_TRANSPORT` | Transport mode | `streamable-http` |
279
- | `CODE_SEARCH_PORT` | HTTP port | `3100` |
280
- | `CODE_SEARCH_HOST` | Bind address | `0.0.0.0` |
281
- | `CODE_SEARCH_AUTH_MODE` | Auth mode | `none` |
282
- | `CODE_SEARCH_AUTH_API_KEYS` | Comma-separated API keys | -- |
283
- | `CODE_SEARCH_AUTH_JWT_SECRET` | JWT signing secret | -- |
284
- | `CODE_SEARCH_EMBEDDING_PROVIDER` | Embedding provider | `auto` |
285
- | `CODE_SEARCH_EMBEDDING_API_KEY` | Unified embedding API key | -- |
286
- | `CODE_SEARCH_EMBEDDING_BASE_URL` | Custom embedding endpoint | -- |
287
- | `CODE_SEARCH_EMBEDDING_MODEL` | Custom embedding model | -- |
288
- | `CODE_SEARCH_RERANKER_PROVIDER` | `ollama` \| `cohere` \| `voyage` \| `custom` \| `none` | `ollama` |
289
- | `CODE_SEARCH_RERANKER_BASE_URL` | Custom reranker endpoint | -- |
290
- | `CODE_SEARCH_RERANKER_MODEL` | Custom reranker model | -- |
291
- | `CODE_SEARCH_DATA_DIR` | Data directory override | -- |
292
- | `CODE_SEARCH_REINDEX_INTERVAL` | Auto-reindex schedule (`30m`, `1h`, `6h`, `0`) | `0` |
293
- | `CODE_SEARCH_RATE_LIMIT_PER_MINUTE` | Rate limit per identity | `100` |
294
- | `CODE_SEARCH_LLM_MODE` | `cli` \| `api` \| `none` | `cli` |
295
- | `CODE_SEARCH_LLM_PROVIDER` | `anthropic` \| `openai` \| `ollama` \| `custom` | `anthropic` |
296
- | `CODE_SEARCH_LLM_API_KEY` | API key for LLM provider | -- |
297
- | `CODE_SEARCH_LLM_MODEL` | Model for profiling + service mesh | `claude-sonnet-4-6` |
298
- | `CODE_SEARCH_LLM_BASE_URL` | Custom LLM endpoint | -- |
299
-
300
- </details>
375
+ **Security defaults that don't bite.** Default bind is `127.0.0.1`
376
+ when auth is `none`. API keys compared timing-safe. JWT
377
+ signature + exp + iss validated. Public binding without auth
378
+ logs a warning instead of pretending it's fine.
301
379
 
302
380
  ---
303
381
 
304
- ## Requirements
382
+ ## End-to-end smoke test (T1–T8)
383
+
384
+ The eight-case smoke that pins the standalone product contract lives
385
+ at `scripts/smoke/run-smoke.sh`. It spins up a tiny `pet-shop` git
386
+ fixture under `/tmp/cs-smoke/`, then runs:
305
387
 
306
- - **Node.js >= 20**
307
- - One embedding provider: Ollama (free, local) or any supported API key
388
+ | # | Verifies |
389
+ |---|---|
390
+ | **T1** | `--print-config` env + CLI flag layering, redaction |
391
+ | **T2** | `code-search index` produces a real Ollama-backed index; no stale legacy-env warning |
392
+ | **T3** | `code-search query --mode vector` and `--mode bm25` both return the right file |
393
+ | **T4** | `code-search status` reports provider + chunk count |
394
+ | **T5** | Issue-#6 reproduction: `CODE_SEARCH_EMBEDDING_PROVIDER=openai` actually constructs the OpenAI embedder (skipped when `OPENAI_API_KEY` is unset) |
395
+ | **T6** | Vector-space mismatch guard hard-errors on cross-provider query |
396
+ | **T7** | Daemon round-trip: spawn, UDS socket, query through RPC, file-watcher debounce-reindex |
397
+ | **T8** | HTTP serve: `/health` `/ready` `/version` `/metrics` `/admin/api/status` all 200; Prom text format |
308
398
 
309
- ```bash
310
- # Recommended: Ollama for free local embeddings
311
- brew install ollama && ollama pull bge-m3
399
+ Run it locally:
400
+
401
+ ```sh
402
+ npm -w @esankhan3/code-search-mcp run build # required first
403
+ npm -w @esankhan3/code-search-mcp run smoke # T1–T8
312
404
  ```
313
405
 
314
- ### Authentication Modes
406
+ Requirements: Ollama on `$OLLAMA_HOST` (default `localhost:11434`)
407
+ with `bge-m3` pulled. Pass `OPENAI_API_KEY=...` to exercise the T5
408
+ provider-switch path against the real OpenAI endpoint.
315
409
 
316
- | Mode | How it works |
317
- |:-----|:-------------|
318
- | `none` | No auth -- process boundary is the security boundary (default for stdio) |
319
- | `api-key` | `Authorization: Bearer <key>` checked against allowlist |
320
- | `jwt` | HS256 JWT verification with expiry and issuer validation |
410
+ The smoke runs **outside CI** — it requires Ollama + real models —
411
+ so it's a hand-rolled local check, not a release gate. The release
412
+ workflow does enforce a static guard that every bin entry
413
+ (`code-search-mcp` / `code-search` / `code-search-daemon`) is in the
414
+ built tarball before publishing.
321
415
 
322
416
  ---
323
417
 
324
- ## License
325
-
326
- MIT
418
+ ## Status
327
419
 
328
- <div align="center">
329
- <br/>
420
+ Stable. The retrieval and graph layers move with
421
+ `@anvil/knowledge-core`; the MCP surface is locked. New tools
422
+ land additively. P0–P8 of the standalone-product plan
423
+ (`docs/CODE-SEARCH-MCP-STANDALONE-PLAN.md`) have landed; F1–F4
424
+ follow-up fixes verified by `scripts/smoke/run-smoke.sh`.
330
425
 
331
- Built with [Model Context Protocol](https://modelcontextprotocol.io) · [LanceDB](https://lancedb.com) · [Tree-sitter](https://tree-sitter.github.io) · [Graphology](https://graphology.github.io)
426
+ ---
332
427
 
333
- </div>
428
+ ## Part of [Anvil](../../) — the AI development pipeline.