codesearch-mcp 0.1.0-alpha → 0.2.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +146 -41
- package/dist/app.js +4 -19
- package/dist/src/commands/IndexCommand.js +68 -7
- package/dist/src/config-error.d.ts +7 -0
- package/dist/src/config-error.js +14 -0
- package/dist/src/config-store.d.ts +30 -12
- package/dist/src/config-store.js +81 -26
- package/dist/src/config.d.ts +5 -4
- package/dist/src/config.js +14 -16
- package/dist/src/constants.d.ts +8 -2
- package/dist/src/constants.js +8 -5
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +75 -0
- package/dist/src/db/schema.js +10 -1
- package/dist/src/enriched-prompt.d.ts +8 -0
- package/dist/src/enriched-prompt.js +87 -0
- package/dist/src/help.js +1 -1
- package/dist/src/interfaces/IAstParser.d.ts +18 -2
- package/dist/src/interfaces/IDatabase.d.ts +31 -0
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/interfaces/ISearcher.d.ts +3 -0
- package/dist/src/services/AstChunker.d.ts +11 -0
- package/dist/src/services/AstChunker.js +44 -0
- package/dist/src/services/AstParserService.d.ts +39 -7
- package/dist/src/services/AstParserService.js +504 -104
- package/dist/src/services/DatabaseService.d.ts +17 -0
- package/dist/src/services/DatabaseService.js +130 -2
- package/dist/src/services/EmbedderFactory.d.ts +1 -1
- package/dist/src/services/EmbedderFactory.js +2 -5
- package/dist/src/services/IndexerService.d.ts +4 -14
- package/dist/src/services/IndexerService.js +54 -156
- package/dist/src/services/InfinityEmbedder.d.ts +14 -55
- package/dist/src/services/InfinityEmbedder.js +30 -141
- package/dist/src/services/LoggerService.d.ts +3 -1
- package/dist/src/services/LoggerService.js +17 -3
- package/dist/src/services/McpServer.js +3 -3
- package/dist/src/services/SearcherService.d.ts +39 -4
- package/dist/src/services/SearcherService.js +254 -81
- package/dist/src/services/WebServerService.d.ts +41 -3
- package/dist/src/services/WebServerService.js +90 -2
- package/dist/src/services/infinity-provisioning.d.ts +115 -0
- package/dist/src/services/infinity-provisioning.js +516 -0
- package/dist/src/services/legacy-ollama-index.d.ts +24 -0
- package/dist/src/services/legacy-ollama-index.js +61 -0
- package/dist/src/services/reranker.d.ts +3 -3
- package/dist/src/services/reranker.js +3 -3
- package/dist/src/setup.d.ts +37 -0
- package/dist/src/setup.js +111 -157
- package/dist/src/symbol-name.d.ts +44 -0
- package/dist/src/symbol-name.js +68 -0
- package/dist/src/symbol-rows.d.ts +12 -0
- package/dist/src/symbol-rows.js +38 -0
- package/dist/src/tools/FindSymbolTool.js +30 -7
- package/dist/src/tools/GetCallersTool.js +20 -5
- package/docker/docker-compose.yml +5 -15
- package/docs/architecture.svg +103 -0
- package/docs/web-dashboard.md +36 -17
- package/package.json +1 -6
- package/dist/src/commands/BackfillCommand.d.ts +0 -7
- package/dist/src/commands/BackfillCommand.js +0 -37
- package/dist/src/services/OllamaEmbedder.d.ts +0 -25
- package/dist/src/services/OllamaEmbedder.js +0 -111
|
@@ -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.
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
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:
|
|
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>
|
package/docs/web-dashboard.md
CHANGED
|
@@ -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 **
|
|
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
|
-
- **`
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
186
|
-
|
|
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
|
|
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,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;
|