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.
Files changed (64) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +146 -41
  3. package/dist/app.js +4 -19
  4. package/dist/src/commands/IndexCommand.js +68 -7
  5. package/dist/src/config-error.d.ts +7 -0
  6. package/dist/src/config-error.js +14 -0
  7. package/dist/src/config-store.d.ts +30 -12
  8. package/dist/src/config-store.js +81 -26
  9. package/dist/src/config.d.ts +5 -4
  10. package/dist/src/config.js +14 -16
  11. package/dist/src/constants.d.ts +8 -2
  12. package/dist/src/constants.js +8 -5
  13. package/dist/src/db/index.d.ts +1 -1
  14. package/dist/src/db/index.js +2 -1
  15. package/dist/src/db/schema.d.ts +75 -0
  16. package/dist/src/db/schema.js +10 -1
  17. package/dist/src/enriched-prompt.d.ts +8 -0
  18. package/dist/src/enriched-prompt.js +87 -0
  19. package/dist/src/help.js +1 -1
  20. package/dist/src/interfaces/IAstParser.d.ts +18 -2
  21. package/dist/src/interfaces/IDatabase.d.ts +31 -0
  22. package/dist/src/interfaces/ILogger.d.ts +7 -0
  23. package/dist/src/interfaces/ISearcher.d.ts +3 -0
  24. package/dist/src/services/AstChunker.d.ts +11 -0
  25. package/dist/src/services/AstChunker.js +44 -0
  26. package/dist/src/services/AstParserService.d.ts +39 -7
  27. package/dist/src/services/AstParserService.js +504 -104
  28. package/dist/src/services/DatabaseService.d.ts +17 -0
  29. package/dist/src/services/DatabaseService.js +130 -2
  30. package/dist/src/services/EmbedderFactory.d.ts +1 -1
  31. package/dist/src/services/EmbedderFactory.js +2 -5
  32. package/dist/src/services/IndexerService.d.ts +4 -14
  33. package/dist/src/services/IndexerService.js +54 -156
  34. package/dist/src/services/InfinityEmbedder.d.ts +14 -55
  35. package/dist/src/services/InfinityEmbedder.js +30 -141
  36. package/dist/src/services/LoggerService.d.ts +3 -1
  37. package/dist/src/services/LoggerService.js +17 -3
  38. package/dist/src/services/McpServer.js +3 -3
  39. package/dist/src/services/SearcherService.d.ts +39 -4
  40. package/dist/src/services/SearcherService.js +254 -81
  41. package/dist/src/services/WebServerService.d.ts +41 -3
  42. package/dist/src/services/WebServerService.js +90 -2
  43. package/dist/src/services/infinity-provisioning.d.ts +115 -0
  44. package/dist/src/services/infinity-provisioning.js +516 -0
  45. package/dist/src/services/legacy-ollama-index.d.ts +24 -0
  46. package/dist/src/services/legacy-ollama-index.js +61 -0
  47. package/dist/src/services/reranker.d.ts +3 -3
  48. package/dist/src/services/reranker.js +3 -3
  49. package/dist/src/setup.d.ts +37 -0
  50. package/dist/src/setup.js +111 -157
  51. package/dist/src/symbol-name.d.ts +44 -0
  52. package/dist/src/symbol-name.js +68 -0
  53. package/dist/src/symbol-rows.d.ts +12 -0
  54. package/dist/src/symbol-rows.js +38 -0
  55. package/dist/src/tools/FindSymbolTool.js +30 -7
  56. package/dist/src/tools/GetCallersTool.js +20 -5
  57. package/docker/docker-compose.yml +5 -15
  58. package/docs/architecture.svg +103 -0
  59. package/docs/web-dashboard.md +36 -17
  60. package/package.json +1 -6
  61. package/dist/src/commands/BackfillCommand.d.ts +0 -7
  62. package/dist/src/commands/BackfillCommand.js +0 -37
  63. package/dist/src/services/OllamaEmbedder.d.ts +0 -25
  64. package/dist/src/services/OllamaEmbedder.js +0 -111
@@ -10,11 +10,15 @@ class GetCallersTool {
10
10
  definition() {
11
11
  return {
12
12
  name: 'get_callers',
13
- description: 'Find all call sites of a function or method by exact name. Uses the call graph index — faster and more precise than grep. Use after find_symbol to trace where a symbol is actually called. Returns empty if rag-index backfill --calls has not been run.',
13
+ description: 'Find all call sites of a function or method by exact name. Accepts a bare name (every call site with that ' +
14
+ 'callee name) or a qualified name (`Class.method`, from find_symbol output) to disambiguate same-named methods ' +
15
+ 'of different classes. Uses the call graph index — faster and more precise than grep. A call through an ' +
16
+ 'interface-typed receiver is recorded under the interface member (e.g. `IEmbedder.embedBatch`), not the ' +
17
+ 'implementing class. Use after find_symbol to trace where a symbol is actually called.',
14
18
  inputSchema: {
15
19
  type: 'object',
16
20
  properties: {
17
- symbol: { type: 'string', description: 'Exact function or method name (case-sensitive)' },
21
+ symbol: { type: 'string', description: 'Bare or qualified (Class.method) function/method name (case-sensitive)' },
18
22
  project: { type: 'string', description: 'Project name (from list_projects)' },
19
23
  callee_file: { type: 'string', description: 'Optional: filter by definition file path (as returned by find_symbol) to disambiguate overloaded names' },
20
24
  },
@@ -26,11 +30,22 @@ class GetCallersTool {
26
30
  const symbol = String(args.symbol ?? '');
27
31
  const project = String(args.project ?? '');
28
32
  const calleeFile = args.callee_file ? String(args.callee_file) : null;
29
- const rows = await this.searcher.getCallers(symbol, project, calleeFile);
33
+ let rows;
34
+ try {
35
+ rows = await this.searcher.getCallers(symbol, project, calleeFile);
36
+ }
37
+ catch (err) {
38
+ return (0, format_helpers_1.errorResponse)(err.message);
39
+ }
40
+ const notice = await this.searcher.outdatedNotice(project);
41
+ const lines = [];
42
+ if (notice)
43
+ lines.push(notice + '\n');
30
44
  if (rows.length === 0) {
31
- return (0, format_helpers_1.textResponse)(`No call sites found for \`${symbol}\` in project \`${project}\`. Run \`rag-index backfill --calls\` if the call graph has not been populated yet.`);
45
+ lines.push(`No call sites found for \`${symbol}\` in project \`${project}\`. Run \`rag-index --reindex\` if the call graph has not been populated yet.`);
46
+ return (0, format_helpers_1.textResponse)(lines.join('\n'));
32
47
  }
33
- const lines = [`**Call sites of \`${symbol}\`** (${rows.length} found)\n`];
48
+ lines.push(`**Call sites of \`${symbol}\`** (${rows.length} found)\n`);
34
49
  for (const r of rows) {
35
50
  const loc = r.line != null ? ` line ${r.line}` : '';
36
51
  const ctx = r.fromSymbol ? `, in \`${r.fromSymbol}\`` : '';
@@ -12,19 +12,12 @@ services:
12
12
  volumes:
13
13
  - pgdata:/var/lib/postgresql/data
14
14
 
15
- ollama:
16
- image: ollama/ollama
17
- ports:
18
- - "11434:11434"
19
- volumes:
20
- - ollama_data:/root/.ollama
21
-
22
15
  # One Infinity container serves both /embeddings and /rerank. The model set is
23
16
  # bound at startup via INFINITY_MODEL_ID (";"-separated for multiple models,
24
- # e.g. "BAAI/bge-m3;BAAI/bge-reranker-v2-m3"). Defaults to CPU so it starts on
25
- # any host; GPU is opt-in via docker-compose.infinity-gpu.yml (see rag-setup).
26
- # Behind an "infinity" profile so `docker compose up -d` (ollama path) doesn't
27
- # pull this multi-GB image — it starts only when explicitly targeted.
17
+ # e.g. "BAAI/bge-m3;BAAI/bge-reranker-v2-m3") and rewritten in place by the app
18
+ # (a literal, not an env interpolation) so a plain `docker compose up -d`
19
+ # always serves the configured set. Defaults to CPU so it starts on any host;
20
+ # GPU is opt-in via docker-compose.infinity-gpu.yml (see rag-setup).
28
21
  # Model warmup defaults OFF here: on CPU it runs multi-minute benchmark
29
22
  # batches at every boot (a pure perf measurement, not required to serve). The
30
23
  # GPU override turns it back on, where it costs seconds.
@@ -34,16 +27,13 @@ services:
34
27
  ports:
35
28
  - "7997:7997"
36
29
  environment:
37
- INFINITY_MODEL_ID: ${INFINITY_MODEL_ID:-jinaai/jina-embeddings-v2-base-code}
30
+ INFINITY_MODEL_ID: "jinaai/jina-embeddings-v2-base-code;mixedbread-ai/mxbai-rerank-xsmall-v1"
38
31
  INFINITY_MODEL_WARMUP: ${INFINITY_MODEL_WARMUP:-false}
39
32
  INFINITY_PORT: "7997"
40
33
  command: ["v2"]
41
34
  volumes:
42
35
  - infinity_cache:/app/.cache
43
- profiles:
44
- - infinity
45
36
 
46
37
  volumes:
47
38
  pgdata:
48
- ollama_data:
49
39
  infinity_cache:
@@ -0,0 +1,103 @@
1
+ <svg width="840" height="320" viewBox="0 0 840 320" xmlns="http://www.w3.org/2000/svg" font-family="system-ui,-apple-system,sans-serif">
2
+ <defs>
3
+ <marker id="a" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
4
+ <polygon points="0 0,8 3,0 6" fill="#64748b"/>
5
+ </marker>
6
+ <marker id="ad" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
7
+ <polygon points="0 0,8 3,0 6" fill="#94a3b8"/>
8
+ </marker>
9
+ </defs>
10
+
11
+ <!-- Background -->
12
+ <rect width="840" height="320" fill="#f8fafc" rx="12"/>
13
+
14
+ <!-- ── INDEXING lane ───────────────────────────────────────────── -->
15
+ <rect x="12" y="12" width="816" height="128" rx="8" fill="#eff6ff" stroke="#bfdbfe" stroke-width="1"/>
16
+ <text x="24" y="30" font-size="10" fill="#6b7280" font-weight="700" letter-spacing="1.2">INDEXING</text>
17
+
18
+ <!-- Source files -->
19
+ <rect x="30" y="42" width="104" height="52" rx="6" fill="#fff" stroke="#3b82f6" stroke-width="1.5"/>
20
+ <text x="82" y="64" font-size="11" text-anchor="middle" fill="#1e40af" font-weight="600">Source files</text>
21
+ <text x="82" y="80" font-size="10" text-anchor="middle" fill="#94a3b8">.ts · .tsx · .js</text>
22
+
23
+ <line x1="134" y1="68" x2="158" y2="68" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
24
+
25
+ <!-- AST parser -->
26
+ <rect x="160" y="42" width="120" height="52" rx="6" fill="#fff" stroke="#3b82f6" stroke-width="1.5"/>
27
+ <text x="220" y="64" font-size="11" text-anchor="middle" fill="#1e40af" font-weight="600">AST parser</text>
28
+ <text x="220" y="80" font-size="10" text-anchor="middle" fill="#94a3b8">symbols · imports</text>
29
+
30
+ <line x1="280" y1="68" x2="304" y2="68" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
31
+
32
+ <!-- Chunker -->
33
+ <rect x="306" y="42" width="110" height="52" rx="6" fill="#fff" stroke="#3b82f6" stroke-width="1.5"/>
34
+ <text x="361" y="64" font-size="11" text-anchor="middle" fill="#1e40af" font-weight="600">Chunker</text>
35
+ <text x="361" y="80" font-size="10" text-anchor="middle" fill="#94a3b8">symbol-aligned</text>
36
+
37
+ <line x1="416" y1="68" x2="440" y2="68" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
38
+
39
+ <!-- Embedder -->
40
+ <rect x="442" y="42" width="128" height="52" rx="6" fill="#fff" stroke="#3b82f6" stroke-width="1.5"/>
41
+ <text x="506" y="61" font-size="11" text-anchor="middle" fill="#1e40af" font-weight="600">Embedder</text>
42
+ <text x="506" y="76" font-size="10" text-anchor="middle" fill="#94a3b8">Infinity · OpenAI</text>
43
+ <text x="506" y="89" font-size="10" text-anchor="middle" fill="#94a3b8">Voyage</text>
44
+
45
+ <line x1="570" y1="68" x2="610" y2="68" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
46
+
47
+ <!-- PostgreSQL -->
48
+ <rect x="612" y="36" width="200" height="116" rx="8" fill="#faf5ff" stroke="#7c3aed" stroke-width="1.5"/>
49
+ <text x="712" y="58" font-size="11" text-anchor="middle" fill="#5b21b6" font-weight="700">PostgreSQL + pgvector</text>
50
+ <line x1="628" y1="66" x2="796" y2="66" stroke="#c4b5fd" stroke-width="1"/>
51
+ <text x="712" y="84" font-size="10" text-anchor="middle" fill="#7c3aed">code_chunk (vectors)</text>
52
+ <text x="712" y="100" font-size="10" text-anchor="middle" fill="#7c3aed">code_symbol</text>
53
+ <text x="712" y="116" font-size="10" text-anchor="middle" fill="#7c3aed">code_dependency · code_call</text>
54
+
55
+ <!-- AST → DB (graph data) dashed -->
56
+ <path d="M220 94 Q220 136 612 136" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,3" fill="none" marker-end="url(#ad)"/>
57
+ <text x="400" y="150" font-size="10" text-anchor="middle" fill="#94a3b8">symbols · import graph · call graph</text>
58
+
59
+ <!-- ── QUERY lane ──────────────────────────────────────────────── -->
60
+ <rect x="12" y="182" width="816" height="126" rx="8" fill="#f0fdf4" stroke="#bbf7d0" stroke-width="1"/>
61
+ <text x="24" y="200" font-size="10" fill="#6b7280" font-weight="700" letter-spacing="1.2">QUERY</text>
62
+
63
+ <!-- Claude -->
64
+ <ellipse cx="72" cy="248" rx="52" ry="28" fill="#fff" stroke="#f97316" stroke-width="1.5"/>
65
+ <text x="72" y="244" font-size="11" text-anchor="middle" fill="#9a3412" font-weight="600">Claude</text>
66
+ <text x="72" y="259" font-size="10" text-anchor="middle" fill="#94a3b8">AI agent</text>
67
+
68
+ <line x1="124" y1="248" x2="152" y2="248" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
69
+
70
+ <!-- rag-mcp -->
71
+ <rect x="154" y="224" width="110" height="48" rx="6" fill="#fff" stroke="#f97316" stroke-width="1.5"/>
72
+ <text x="209" y="244" font-size="11" text-anchor="middle" fill="#9a3412" font-weight="600">rag-mcp</text>
73
+ <text x="209" y="260" font-size="10" text-anchor="middle" fill="#94a3b8">MCP server</text>
74
+
75
+ <line x1="264" y1="248" x2="292" y2="248" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
76
+
77
+ <!-- Embed query -->
78
+ <rect x="294" y="224" width="110" height="48" rx="6" fill="#fff" stroke="#22c55e" stroke-width="1.5"/>
79
+ <text x="349" y="244" font-size="11" text-anchor="middle" fill="#166534" font-weight="600">Embed query</text>
80
+ <text x="349" y="260" font-size="10" text-anchor="middle" fill="#94a3b8">query vector</text>
81
+
82
+ <line x1="404" y1="248" x2="432" y2="248" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
83
+
84
+ <!-- HNSW search -->
85
+ <rect x="434" y="224" width="118" height="48" rx="6" fill="#fff" stroke="#22c55e" stroke-width="1.5"/>
86
+ <text x="493" y="244" font-size="11" text-anchor="middle" fill="#166534" font-weight="600">HNSW search</text>
87
+ <text x="493" y="260" font-size="10" text-anchor="middle" fill="#94a3b8">cosine distance</text>
88
+
89
+ <!-- DB → HNSW dashed -->
90
+ <line x1="712" y1="152" x2="712" y2="178" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,3"/>
91
+ <line x1="712" y1="178" x2="550" y2="224" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#ad)"/>
92
+
93
+ <line x1="552" y1="248" x2="580" y2="248" stroke="#64748b" stroke-width="1.5" marker-end="url(#a)"/>
94
+
95
+ <!-- Reranker -->
96
+ <rect x="582" y="224" width="118" height="48" rx="6" fill="#fff" stroke="#22c55e" stroke-width="1.5"/>
97
+ <text x="641" y="244" font-size="11" text-anchor="middle" fill="#166534" font-weight="600">Reranker</text>
98
+ <text x="641" y="260" font-size="10" text-anchor="middle" fill="#94a3b8">cross-encoder</text>
99
+
100
+ <!-- Reranker → Claude return arc -->
101
+ <path d="M700 262 Q760 296 720 296 Q440 296 140 296 Q114 296 72 276" stroke="#64748b" stroke-width="1.5" fill="none" marker-end="url(#a)"/>
102
+ <text x="430" y="309" font-size="10" text-anchor="middle" fill="#94a3b8">top results + symbol breadcrumbs</text>
103
+ </svg>
@@ -49,7 +49,7 @@ can only reach the dashboard while the process is alive.
49
49
  `rag-config edit` (CLI) instead.
50
50
  - A failed **DB** connection is non-fatal: the config editor still works; the
51
51
  Projects and Graph tabs show an in-page error.
52
- - A failed **Ollama** connection only affects model suggestions (see
52
+ - A failed **embedding backend** connection only affects model suggestions (see
53
53
  *Field options*); everything else works.
54
54
 
55
55
  ---
@@ -105,16 +105,15 @@ Some settings have a constrained set of valid values, so the form offers a list
105
105
  rather than free text. `GET /api/field-options` returns
106
106
  `{ [dottedKey]: { values: string[], strict: boolean } }`:
107
107
 
108
- - **`ollama.model` — strict.** The embedding model is tied to the vector column
109
- dimension (`bge-m3` → 1024). Values come from `SUPPORTED_EMBED_MODELS`
110
- (`src/constants.ts`); the dropdown does **not** allow free text, so a
111
- dimension-incompatible model cannot be set. To add a model, confirm its
112
- dimension matches and append it to `SUPPORTED_EMBED_MODELS`.
113
- - **`search.translateModel`, `reranker.model` — non-strict.** Suggestions are the
114
- user's installed Ollama models (`ollama.list()`), but free text is allowed
115
- because these are not dimension-bound and the user may pull a new model later.
116
- If Ollama is unreachable the list is empty and the field degrades to plain
117
- text.
108
+ - **`embedding.model`, `reranker.model` — non-strict.** `embedding.model`
109
+ suggests `MODEL_REGISTRY` (`src/constants.ts`) entries plus any model the
110
+ backend currently serves; `reranker.model` suggests only served models. Both
111
+ allow free text — a model outside the registry still works (its dimension is
112
+ probed at index time). Served models come from `IEmbedder.listModels()`,
113
+ implemented only by `InfinityEmbedder` (2-second-limited `/models` listing);
114
+ if the backend is unreachable, or the provider doesn't implement `listModels`
115
+ (`openai-compatible`, `voyage`), the served-model list is empty and the field
116
+ degrades to registry suggestions only (or plain text for `reranker.model`).
118
117
 
119
118
  A strict `<select>` preserves an unknown current value as an extra option, so
120
119
  saving never silently drops a value that isn't in the list.
@@ -132,19 +131,39 @@ All routes are served from `127.0.0.1:<port>`.
132
131
  | `GET` | `/api/defaults` | `CONFIG_DEFAULTS` — dotted key → default value |
133
132
  | `GET` | `/api/field-options` | dotted key → `{ values, strict }` |
134
133
  | `GET` | `/api/config` | Current config, `db.password` masked |
135
- | `PUT` | `/api/config` | Save. Body = config JSON. `200 {ok:true}` or `400 {error}` |
134
+ | `PUT` | `/api/config` | Save. Body = config JSON. `200 {ok:true, backend:{action,message}}` or `400 {error}`. For `embedding.provider: infinity`, `backend.action` reports whether the Infinity container needed a background recreate (`started`/`already-running`/`not-needed`/`manual-required`/`failed`) — see *Infinity backend recreate* below |
135
+ | `GET` | `/api/backend-status` | `{ state, message, startedAt, finishedAt }` — progress of the most recent background Infinity recreate this server process started |
136
136
  | `GET` | `/api/projects` | `SearcherService.listProjects()` |
137
137
  | `GET` | `/api/graph` | `{ nodes, edges }` JSON (`buildGraphData()`) |
138
138
  | `GET` | `/graph` | Full interactive graph page (`buildHtml()`), embedded by the Graph tab's iframe |
139
139
 
140
140
  ---
141
141
 
142
+ ## Infinity backend recreate
143
+
144
+ When a save's `embedding.provider` is `infinity`, the server computes the
145
+ required model set from the **saved** config (freshly re-read from disk, not
146
+ the embedder the process started with) and compares it against what the
147
+ backend currently serves. If a managed container needs to be started or
148
+ recreated to serve the new set, that runs as an in-process background task —
149
+ the `PUT` responds immediately (`action: "started"`) without waiting for it.
150
+ Poll `GET /api/backend-status` for progress (`state: "recreating"` →
151
+ `"ready"`/`"failed"`). Only one `docker compose` run is active at a time; a
152
+ save that arrives while a recreate is already in flight for a managed backend
153
+ responds `already-running`, and at most one follow-up run applies the latest
154
+ such save once the in-flight one finishes. An external (unmanaged) backend
155
+ missing a required model never triggers Docker — the response's `message`
156
+ carries manual `--model-id`/launch instructions instead.
157
+
158
+ ---
159
+
142
160
  ## Architecture
143
161
 
144
162
  | File | Responsibility |
145
163
  |---|---|
146
164
  | `src/commands/WebCommand.ts` | CLI entry. `db.initDb()`, starts the server, keeps `execute()` pending until `SIGINT`/`SIGTERM` (mirrors `McpCommand`), then `server.stop()`. Parses `--port` (default `4319`). |
147
- | `src/services/WebServerService.ts` | `WebServer(searcher, config, logger)` — `node:http` server, routing, and the API handlers. Exports the pure `mergeConfigForSave()` and the `FieldOption` type. |
165
+ | `src/services/WebServerService.ts` | `WebServer(searcher, config, logger, embedder, deps?)` — `node:http` server, routing, the API handlers, and the background Infinity recreate + `/api/backend-status` state. Exports the pure `mergeConfigForSave()` and the `FieldOption` type. |
166
+ | `src/services/infinity-provisioning.ts` | The readiness/recreate primitive the background task delegates to (shared with `InfinityEmbedder` and `rag-setup`) — classification, compose-file rewrite, `docker compose` invocation, readiness polling. |
148
167
  | `src/web/dashboard.ts` | `renderDashboardHtml()` — the entire page (HTML + CSS + JS) as one self-contained string, mirroring `graph.ts:buildHtml`. |
149
168
 
150
169
  Reused primitives (no duplication):
@@ -153,7 +172,8 @@ Reused primitives (no duplication):
153
172
  `CONFIG_DEFAULTS` — `src/config-store.ts`
154
173
  - `buildGraphData` — `src/graph.ts`
155
174
  - `listProjects` — `src/services/SearcherService.ts`
156
- - `SUPPORTED_EMBED_MODELS` — `src/constants.ts`
175
+ - `MODEL_REGISTRY` — `src/constants.ts`
176
+ - `evaluateBackend` / `ensureModelSetServed` / `requiredModelSet` — `src/services/infinity-provisioning.ts`
157
177
 
158
178
  Wiring: `app.ts` detects the `web` mode and dispatches
159
179
  `new WebCommand(container, effectiveArgs)`. The command uses the shared
@@ -182,6 +202,5 @@ changes were needed.
182
202
  `.default(...)` calls, so the runtime default and the dashboard-displayed
183
203
  default cannot diverge — no manual sync needed. (A unit test still asserts
184
204
  every key is a valid config key.)
185
- - `SUPPORTED_EMBED_MODELS` (`constants.ts`) must contain only models whose
186
- embedding dimension matches the `vector(...)` column — this one is still a
187
- manual invariant, guarded only at the membership level by a unit test.
205
+ - `MODEL_REGISTRY` (`constants.ts`) is advisory, not exhaustive — a model
206
+ outside it still works via the runtime dimension probe at index time.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codesearch-mcp",
3
- "version": "0.1.0-alpha",
3
+ "version": "0.2.1-alpha",
4
4
  "description": "Local semantic codebase search via MCP — indexes projects into pgvector, exposes search to Claude",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -18,9 +18,6 @@
18
18
  "engines": {
19
19
  "node": ">=18"
20
20
  },
21
- "publishConfig": {
22
- "tag": "alpha"
23
- },
24
21
  "main": "dist/app.js",
25
22
  "bin": {
26
23
  "rag-index": "dist/app.js",
@@ -45,7 +42,6 @@
45
42
  "start:web": "ts-node app.ts web",
46
43
  "index:workspace": "ts-node app.ts index",
47
44
  "index:reindex": "ts-node app.ts index --reindex",
48
- "index:backfill": "ts-node app.ts backfill",
49
45
  "typecheck": "tsc --noEmit",
50
46
  "test": "vitest run",
51
47
  "test:watch": "vitest",
@@ -58,7 +54,6 @@
58
54
  "cli-progress": "^3.12.0",
59
55
  "drizzle-orm": "^0.45.2",
60
56
  "ignore": "^7.0.5",
61
- "ollama": "^0.5.0",
62
57
  "pg": "^8.13.0",
63
58
  "pino": "^10.3.1",
64
59
  "pino-roll": "^4.0.0",
@@ -1,7 +0,0 @@
1
- import type { Container } from '../container';
2
- import type { ICommand } from './ICommand';
3
- export declare class BackfillCommand implements ICommand {
4
- private container;
5
- constructor(container: Container);
6
- execute(): Promise<void>;
7
- }
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BackfillCommand = void 0;
4
- const interrupt_1 = require("../interrupt");
5
- class BackfillCommand {
6
- container;
7
- constructor(container) {
8
- this.container = container;
9
- }
10
- async execute() {
11
- const { db, indexer } = this.container;
12
- await db.initDbWithSchema();
13
- const doCalls = process.argv.includes('--calls');
14
- const controller = new AbortController();
15
- const teardown = (0, interrupt_1.setupInterrupt)(controller);
16
- try {
17
- if (doCalls) {
18
- console.log('Backfilling call graph...\n');
19
- const stats = await indexer.backfillCalls(controller.signal);
20
- if (controller.signal.aborted)
21
- console.log('\nBackfill interrupted.');
22
- console.log(`\nDone. Total: ${stats.total}, Processed: ${stats.processed}, Skipped (not on disk): ${stats.skipped}, Failed: ${stats.failed}`);
23
- }
24
- else {
25
- console.log('Backfilling symbol index...\n');
26
- const stats = await indexer.backfillSymbols(controller.signal);
27
- if (controller.signal.aborted)
28
- console.log('\nBackfill interrupted.');
29
- console.log(`\nDone. Total: ${stats.total}, Processed: ${stats.processed}, Skipped (not on disk): ${stats.skipped}, Failed: ${stats.failed}`);
30
- }
31
- }
32
- finally {
33
- teardown();
34
- }
35
- }
36
- }
37
- exports.BackfillCommand = BackfillCommand;
@@ -1,25 +0,0 @@
1
- import type { Config } from '../config';
2
- import type { IEmbedder, InputType } from '../interfaces';
3
- import type { ILogger } from '../interfaces';
4
- import type { EmbedderDeps } from './OpenAICompatibleEmbedder';
5
- export declare class OllamaEmbedder implements IEmbedder {
6
- private config;
7
- private logger;
8
- readonly dimensions: number;
9
- private client;
10
- rerank?: (query: string, documents: string[], topN: number) => Promise<number[]>;
11
- constructor(config: Config, logger?: ILogger, deps?: EmbedderDeps);
12
- embed(text: string, _inputType?: InputType): Promise<number[]>;
13
- embedBatch(texts: string[], _inputType?: InputType): Promise<number[][]>;
14
- listModels(): Promise<string[]>;
15
- /**
16
- * Pull the configured model if it is not already present locally, so a plain
17
- * `rag-index` works after switching to (or back to) a local model that was
18
- * never pulled — `rag-setup` is no longer the only place that fetches models.
19
- * A tagless model name (`bge-m3`) matches its `:latest`/tagged install.
20
- * Streams pull progress to the console: a live single-line bar on a TTY, or
21
- * de-duplicated status lines otherwise.
22
- */
23
- ensureModelAvailable(): Promise<void>;
24
- private static formatPullProgress;
25
- }
@@ -1,111 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OllamaEmbedder = void 0;
4
- const ollama_1 = require("ollama");
5
- const LoggerService_1 = require("./LoggerService");
6
- const reranker_1 = require("./reranker");
7
- const constants_1 = require("../constants");
8
- class OllamaEmbedder {
9
- config;
10
- logger;
11
- dimensions;
12
- client;
13
- rerank;
14
- constructor(config, logger = LoggerService_1.LoggerService.silent(), deps = {}) {
15
- this.config = config;
16
- this.logger = logger;
17
- this.client = new ollama_1.Ollama({ host: config.embedding.baseURL });
18
- this.dimensions = config.embedding.dimensions ?? (0, constants_1.registryDimension)(config.embedding.model) ?? 0;
19
- this.rerank = (0, reranker_1.makeReranker)(config, logger, deps);
20
- }
21
- // Ollama has no query/document asymmetry — inputType is accepted and ignored.
22
- async embed(text, _inputType) {
23
- const { embedding } = await this.client.embeddings({
24
- model: this.config.embedding.model,
25
- prompt: text,
26
- });
27
- return embedding;
28
- }
29
- async embedBatch(texts, _inputType) {
30
- if (texts.length === 0)
31
- return [];
32
- try {
33
- // /api/embed accepts an array input and returns one vector per input.
34
- const res = await this.client.embed({ model: this.config.embedding.model, input: texts });
35
- if (res.embeddings?.length === texts.length)
36
- return res.embeddings;
37
- this.logger.warn('OllamaEmbedder: batch embed count mismatch, falling back to sequential', {
38
- expected: texts.length, got: res.embeddings?.length ?? 0,
39
- });
40
- }
41
- catch (err) {
42
- this.logger.warn('OllamaEmbedder: batch embed failed, falling back to sequential', {
43
- reason: err instanceof Error ? err.message : String(err),
44
- });
45
- }
46
- // Sequential fallback — the contract (one vector per input, in order) holds.
47
- const out = [];
48
- for (const t of texts)
49
- out.push(await this.embed(t));
50
- return out;
51
- }
52
- async listModels() {
53
- const res = await this.client.list();
54
- return res.models.map(m => m.name).filter(Boolean).sort();
55
- }
56
- /**
57
- * Pull the configured model if it is not already present locally, so a plain
58
- * `rag-index` works after switching to (or back to) a local model that was
59
- * never pulled — `rag-setup` is no longer the only place that fetches models.
60
- * A tagless model name (`bge-m3`) matches its `:latest`/tagged install.
61
- * Streams pull progress to the console: a live single-line bar on a TTY, or
62
- * de-duplicated status lines otherwise.
63
- */
64
- async ensureModelAvailable() {
65
- const model = this.config.embedding.model;
66
- try {
67
- const res = await this.client.list();
68
- const present = res.models.some(m => m.name === model || m.name.startsWith(`${model}:`));
69
- if (present)
70
- return;
71
- }
72
- catch (err) {
73
- // Can't list (Ollama unreachable?) — try the pull; its error is clearer.
74
- this.logger.warn('OllamaEmbedder: could not list models before pull', {
75
- reason: err instanceof Error ? err.message : String(err),
76
- });
77
- }
78
- console.log(`Pulling Ollama model "${model}" (not found locally)...`);
79
- this.logger.info('OllamaEmbedder: pulling model', { model });
80
- const stream = await this.client.pull({ model, stream: true });
81
- const isTty = Boolean(process.stdout.isTTY);
82
- let lastLen = 0;
83
- let lastLog = '';
84
- for await (const part of stream) {
85
- const out = ' ' + OllamaEmbedder.formatPullProgress(part);
86
- if (isTty) {
87
- process.stdout.write('\r' + out.padEnd(lastLen));
88
- lastLen = out.length;
89
- }
90
- else if (out !== lastLog) {
91
- console.log(out);
92
- lastLog = out;
93
- }
94
- }
95
- if (isTty && lastLen > 0)
96
- process.stdout.write('\r' + ' '.repeat(lastLen) + '\r');
97
- console.log(` ✓ ${model} ready.`);
98
- }
99
- static formatPullProgress(p) {
100
- const status = p.status ?? 'pulling';
101
- if (p.total && p.total > 0) {
102
- const pct = Math.min(100, Math.floor(((p.completed ?? 0) / p.total) * 100));
103
- const width = 24;
104
- const filled = Math.round((pct / 100) * width);
105
- const bar = '[' + '█'.repeat(filled) + '░'.repeat(width - filled) + ']';
106
- return `${status} ${bar} ${pct}% (${((p.completed ?? 0) / 1e6).toFixed(1)}/${(p.total / 1e6).toFixed(1)} MB)`;
107
- }
108
- return status;
109
- }
110
- }
111
- exports.OllamaEmbedder = OllamaEmbedder;