codesearch-mcp 0.1.0-alpha → 0.2.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/README.md +141 -39
- package/dist/app.js +2 -2
- package/dist/src/commands/IndexCommand.js +42 -1
- 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/help.js +1 -1
- package/dist/src/interfaces/IDatabase.d.ts +17 -0
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/services/AstChunker.d.ts +11 -0
- package/dist/src/services/AstChunker.js +44 -0
- package/dist/src/services/DatabaseService.d.ts +4 -0
- package/dist/src/services/DatabaseService.js +46 -0
- 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 -0
- package/dist/src/services/IndexerService.js +27 -7
- 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/SearcherService.d.ts +15 -1
- package/dist/src/services/SearcherService.js +105 -23
- 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/docker/docker-compose.yml +5 -15
- package/docs/web-dashboard.md +36 -17
- package/package.json +1 -5
- package/dist/src/services/OllamaEmbedder.d.ts +0 -25
- package/dist/src/services/OllamaEmbedder.js +0 -111
package/dist/src/config-store.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CONFIG_DEFAULTS = exports.ALL_KEYS = exports.ENV_FILE = exports.SCHEMA_FILE = exports.CONFIG_FILE = exports.CONFIG_DIR = void 0;
|
|
37
37
|
exports.readConfigJson = readConfigJson;
|
|
38
|
+
exports.detectFormerOllamaConfig = detectFormerOllamaConfig;
|
|
38
39
|
exports.migrateRawConfig = migrateRawConfig;
|
|
39
40
|
exports.parseConfig = parseConfig;
|
|
40
41
|
exports.buildJsonSchema = buildJsonSchema;
|
|
@@ -48,6 +49,7 @@ const path = __importStar(require("path"));
|
|
|
48
49
|
const os = __importStar(require("os"));
|
|
49
50
|
const zod_1 = require("zod");
|
|
50
51
|
const constants_1 = require("./constants");
|
|
52
|
+
const config_error_1 = require("./config-error");
|
|
51
53
|
exports.CONFIG_DIR = path.join(os.homedir(), '.config', 'codesearch-mcp');
|
|
52
54
|
exports.CONFIG_FILE = path.join(exports.CONFIG_DIR, 'config.json');
|
|
53
55
|
exports.SCHEMA_FILE = path.join(exports.CONFIG_DIR, 'config.schema.json');
|
|
@@ -66,10 +68,10 @@ const jsonSchema = zod_1.z.object({
|
|
|
66
68
|
name: zod_1.z.string().describe('Database name; defaults to rag_<os-username>, auto-created on first run').optional(),
|
|
67
69
|
}).optional(),
|
|
68
70
|
embedding: zod_1.z.object({
|
|
69
|
-
provider: zod_1.z.enum(['
|
|
70
|
-
baseURL: zod_1.z.string().describe('Backend base URL.
|
|
71
|
+
provider: zod_1.z.enum(['openai-compatible', 'voyage', 'infinity']).describe('Embedding backend (default infinity)').optional(),
|
|
72
|
+
baseURL: zod_1.z.string().describe('Backend base URL. Infinity/TEI/vLLM/Ollama-via-openai-compatible: http://localhost:7997 (Infinity default) · OpenAI: https://api.openai.com/v1 · Voyage: https://api.voyageai.com/v1').optional(),
|
|
71
73
|
apiKey: zod_1.z.string().describe('API key. Use env:VAR_NAME to read from ~/.config/codesearch-mcp/.env or the environment (recommended). Empty for local servers.').optional(),
|
|
72
|
-
model: zod_1.z.string().describe('Embedding model (default
|
|
74
|
+
model: zod_1.z.string().describe('Embedding model (default jinaai/jina-embeddings-v2-base-code)').optional(),
|
|
73
75
|
dimensions: zod_1.z.number().int().describe('Vector width override; required for models whose native width exceeds 2000 (Matryoshka reduction). Omit to derive from the model/registry/probe.').optional(),
|
|
74
76
|
batchSize: zod_1.z.number().int().describe('Texts per batch embedding request (default 32)').optional(),
|
|
75
77
|
}).optional(),
|
|
@@ -87,16 +89,18 @@ const jsonSchema = zod_1.z.object({
|
|
|
87
89
|
}).optional(),
|
|
88
90
|
reranker: zod_1.z.object({
|
|
89
91
|
enabled: zod_1.z.boolean().describe('Enable cross-encoder reranking; requires reranker.model').optional(),
|
|
90
|
-
provider: zod_1.z.enum(['openai-compatible']).describe('Rerank backend. Only openai-compatible /rerank servers (Infinity, TEI, vLLM)
|
|
92
|
+
provider: zod_1.z.enum(['openai-compatible']).describe('Rerank backend. Only openai-compatible /rerank servers (Infinity, TEI, vLLM).').optional(),
|
|
91
93
|
baseURL: zod_1.z.string().describe('Rerank server base URL (defaults to embedding.baseURL — Infinity serves both).').optional(),
|
|
92
94
|
apiKey: zod_1.z.string().describe('API key, or env:VAR_NAME. Empty for local servers.').optional(),
|
|
93
95
|
model: zod_1.z.string().describe('Reranker model name (e.g. qllama/bge-reranker-v2-m3)').optional(),
|
|
94
96
|
url: zod_1.z.string().describe('Full URL of the rerank endpoint (default derived from provider + baseURL)').optional(),
|
|
95
|
-
overfetch: zod_1.z.number().int().min(1).max(20).describe('Candidate multiplier before reranking (limit × N, default
|
|
97
|
+
overfetch: zod_1.z.number().int().min(1).max(20).describe('Candidate multiplier before reranking (limit × N, default 5)').optional(),
|
|
98
|
+
candidateFloor: zod_1.z.number().int().min(1).describe('Minimum candidate pool fed to the reranker regardless of limit; fetchLimit = min(max(limit × overfetch, candidateFloor), 200) (default 80)').optional(),
|
|
96
99
|
}).optional(),
|
|
97
100
|
logging: zod_1.z.object({
|
|
98
101
|
level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).describe('File log level (default error)').optional(),
|
|
99
102
|
dir: zod_1.z.string().describe('Base log directory; "false" to disable file logging').optional(),
|
|
103
|
+
searchMetrics: zod_1.z.boolean().describe('Emit one structured search-tuning line (info level) per search, independent of level (default false)').optional(),
|
|
100
104
|
}).optional(),
|
|
101
105
|
workspaceRoot: zod_1.z.string().describe('Full path to the directory containing all your projects').optional(),
|
|
102
106
|
});
|
|
@@ -127,8 +131,10 @@ const FIELD_TYPES = {
|
|
|
127
131
|
'reranker.model': 'string',
|
|
128
132
|
'reranker.url': 'string',
|
|
129
133
|
'reranker.overfetch': 'number',
|
|
134
|
+
'reranker.candidateFloor': 'number',
|
|
130
135
|
'logging.level': 'string',
|
|
131
136
|
'logging.dir': 'string',
|
|
137
|
+
'logging.searchMetrics': 'boolean',
|
|
132
138
|
'workspaceRoot': 'string',
|
|
133
139
|
};
|
|
134
140
|
exports.ALL_KEYS = Object.keys(FIELD_TYPES);
|
|
@@ -146,7 +152,7 @@ exports.ALL_KEYS = Object.keys(FIELD_TYPES);
|
|
|
146
152
|
*/
|
|
147
153
|
exports.CONFIG_DEFAULTS = {
|
|
148
154
|
'db.port': 5433,
|
|
149
|
-
'embedding.provider': '
|
|
155
|
+
'embedding.provider': 'infinity',
|
|
150
156
|
'embedding.apiKey': '',
|
|
151
157
|
'embedding.model': constants_1.DEFAULT_EMBED_MODEL,
|
|
152
158
|
'embedding.batchSize': 32,
|
|
@@ -158,12 +164,14 @@ exports.CONFIG_DEFAULTS = {
|
|
|
158
164
|
'search.similarityThreshold': 0.6,
|
|
159
165
|
'search.defaultLimit': 5,
|
|
160
166
|
'search.maxLimit': 20,
|
|
161
|
-
'reranker.enabled':
|
|
167
|
+
'reranker.enabled': true,
|
|
162
168
|
'reranker.provider': 'openai-compatible',
|
|
163
169
|
'reranker.apiKey': '',
|
|
164
|
-
'reranker.model': '',
|
|
165
|
-
'reranker.overfetch':
|
|
170
|
+
'reranker.model': 'mixedbread-ai/mxbai-rerank-xsmall-v1',
|
|
171
|
+
'reranker.overfetch': 5,
|
|
172
|
+
'reranker.candidateFloor': 80,
|
|
166
173
|
'logging.level': 'error',
|
|
174
|
+
'logging.searchMetrics': false,
|
|
167
175
|
};
|
|
168
176
|
function readConfigJson() {
|
|
169
177
|
if (!fs.existsSync(exports.CONFIG_FILE))
|
|
@@ -183,14 +191,61 @@ function readConfigJson() {
|
|
|
183
191
|
throw new Error(`Invalid JSON in ${exports.CONFIG_FILE}: ${e.message}\n` +
|
|
184
192
|
`Fix the file manually or delete it and run: rag-setup`);
|
|
185
193
|
}
|
|
186
|
-
return parseConfig(
|
|
194
|
+
return parseConfig(parsed, exports.CONFIG_FILE);
|
|
195
|
+
}
|
|
196
|
+
const OLLAMA_MIGRATION_REMEDY = 'The "ollama" embedding provider has been removed. Point Ollama through the ' +
|
|
197
|
+
'"openai-compatible" provider instead — set embedding.provider to "openai-compatible" ' +
|
|
198
|
+
'and embedding.baseURL to "http://localhost:11434/v1" — via `rag-config edit` (or by ' +
|
|
199
|
+
'editing the config file directly) or by re-running `rag-setup`. After switching, rebuild ' +
|
|
200
|
+
'the index with `rag-index --reindex`.';
|
|
201
|
+
/** Strip a trailing `:tag` (Docker-style) from a model name, if present. */
|
|
202
|
+
function stripModelTag(model) {
|
|
203
|
+
return model.replace(/:[^:]*$/, '');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Detect a raw (pre-migration) config object that still selects the removed
|
|
207
|
+
* `ollama` embedding provider — explicitly, via a legacy top-level `ollama`
|
|
208
|
+
* block, or implicitly (no `provider` set, but `baseURL`/`model` point at
|
|
209
|
+
* Ollama's defaults) — and throw a `ConfigError` with migration guidance
|
|
210
|
+
* instead of letting it validate under a silently reinterpreted provider.
|
|
211
|
+
* Runs before `migrateRawConfig` so a legacy `ollama.*` block is never
|
|
212
|
+
* converted into an `embedding` block first. Pure — never mutates `raw`.
|
|
213
|
+
*/
|
|
214
|
+
function detectFormerOllamaConfig(raw) {
|
|
215
|
+
if (raw == null || typeof raw !== 'object')
|
|
216
|
+
return;
|
|
217
|
+
const obj = raw;
|
|
218
|
+
const embedding = obj.embedding;
|
|
219
|
+
if (embedding != null && typeof embedding === 'object') {
|
|
220
|
+
const e = embedding;
|
|
221
|
+
if (e.provider === 'ollama') {
|
|
222
|
+
throw new config_error_1.ConfigError(`Config selects the "ollama" embedding provider, which is no longer supported. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
223
|
+
}
|
|
224
|
+
if (e.provider === undefined) {
|
|
225
|
+
if (typeof e.baseURL === 'string') {
|
|
226
|
+
let port;
|
|
227
|
+
try {
|
|
228
|
+
port = new URL(e.baseURL).port;
|
|
229
|
+
}
|
|
230
|
+
catch { /* unparsable — not a signal */ }
|
|
231
|
+
if (port === '11434') {
|
|
232
|
+
throw new config_error_1.ConfigError(`Config's embedding.baseURL targets Ollama's default port (11434) with no embedding.provider set. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (typeof e.model === 'string' && constants_1.FORMER_OLLAMA_MODELS.includes(stripModelTag(e.model))) {
|
|
236
|
+
throw new config_error_1.ConfigError(`Config's embedding.model ("${e.model}") was formerly registered for Ollama, with no embedding.provider set. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (obj.ollama != null && typeof obj.ollama === 'object') {
|
|
242
|
+
throw new config_error_1.ConfigError(`Config has a legacy "ollama" configuration block. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
243
|
+
}
|
|
187
244
|
}
|
|
188
245
|
/**
|
|
189
246
|
* Migrate a legacy raw config object to the current shape, in place of a
|
|
190
247
|
* versioned migration system. Pure — returns a new object, never mutates input.
|
|
191
248
|
*
|
|
192
|
-
* - Legacy `ollama.host`/`ollama.model` → `embedding.{baseURL,model}` with
|
|
193
|
-
* `provider: 'ollama'` (only when `embedding` is not already present).
|
|
194
249
|
* - Removed translation keys (`search.autoTranslate`, `search.translateModel`)
|
|
195
250
|
* are dropped so they neither validate-fail nor take effect.
|
|
196
251
|
* - Legacy `reranker.provider: 'ollama'` (a dead value — Ollama has no rerank
|
|
@@ -198,21 +253,14 @@ function readConfigJson() {
|
|
|
198
253
|
* dropped so it re-derives to `/rerank`. Otherwise the tightened enum would
|
|
199
254
|
* fail validation on existing configs and lock the user out.
|
|
200
255
|
*
|
|
201
|
-
* `ollama`
|
|
256
|
+
* A legacy top-level `ollama` block reaches here only when `detectFormerOllamaConfig`
|
|
257
|
+
* did not already reject it (i.e. an `embedding` block is also present); it is
|
|
258
|
+
* dropped rather than merged. Exported for unit testing.
|
|
202
259
|
*/
|
|
203
260
|
function migrateRawConfig(raw) {
|
|
204
261
|
if (raw == null || typeof raw !== 'object')
|
|
205
262
|
return raw;
|
|
206
263
|
const obj = JSON.parse(JSON.stringify(raw));
|
|
207
|
-
const legacyOllama = obj.ollama;
|
|
208
|
-
if (!obj.embedding && legacyOllama && typeof legacyOllama === 'object') {
|
|
209
|
-
const embedding = { provider: 'ollama' };
|
|
210
|
-
if (typeof legacyOllama.host === 'string')
|
|
211
|
-
embedding.baseURL = legacyOllama.host;
|
|
212
|
-
if (typeof legacyOllama.model === 'string')
|
|
213
|
-
embedding.model = legacyOllama.model;
|
|
214
|
-
obj.embedding = embedding;
|
|
215
|
-
}
|
|
216
264
|
delete obj.ollama;
|
|
217
265
|
if (obj.search && typeof obj.search === 'object') {
|
|
218
266
|
delete obj.search.autoTranslate;
|
|
@@ -227,12 +275,19 @@ function migrateRawConfig(raw) {
|
|
|
227
275
|
return obj;
|
|
228
276
|
}
|
|
229
277
|
/**
|
|
230
|
-
* Validate a
|
|
231
|
-
*
|
|
232
|
-
*
|
|
278
|
+
* Validate a raw (pre-migration) config object against the Zod schema: first
|
|
279
|
+
* `detectFormerOllamaConfig` (a `ConfigError` on a former-Ollama config takes
|
|
280
|
+
* priority over both migration and the regular Zod enum rejection), then
|
|
281
|
+
* `migrateRawConfig`, then the schema itself. Unknown keys (e.g. the "$schema"
|
|
282
|
+
* editor reference) are stripped. Throws with a caller-friendly message on
|
|
283
|
+
* validation failure. The single entry point for both `readConfigJson` and
|
|
284
|
+
* `PUT /api/config`, so both apply identical rules with an identical message.
|
|
285
|
+
* Pure — no file I/O.
|
|
233
286
|
*/
|
|
234
287
|
function parseConfig(raw, source = exports.CONFIG_FILE) {
|
|
235
|
-
|
|
288
|
+
detectFormerOllamaConfig(raw);
|
|
289
|
+
const migrated = migrateRawConfig(raw);
|
|
290
|
+
const result = jsonSchema.safeParse(migrated);
|
|
236
291
|
if (!result.success) {
|
|
237
292
|
const issues = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n');
|
|
238
293
|
throw new Error(`Invalid config in ${source}:\n${issues}\n` +
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
1
|
+
import { ConfigError } from './config-error';
|
|
2
|
+
export { ConfigError };
|
|
4
3
|
/**
|
|
5
4
|
* Resolve a config value that may reference an environment variable. A value of
|
|
6
5
|
* the form `env:VAR_NAME` resolves to `process.env.VAR_NAME`; any other string
|
|
@@ -26,7 +25,7 @@ export declare class Config {
|
|
|
26
25
|
* locking the user out of `rag-web`/`rag-mcp`.
|
|
27
26
|
*/
|
|
28
27
|
get embedding(): {
|
|
29
|
-
provider: "
|
|
28
|
+
provider: "openai-compatible" | "voyage" | "infinity";
|
|
30
29
|
baseURL: string;
|
|
31
30
|
apiKey: string;
|
|
32
31
|
model: string;
|
|
@@ -42,6 +41,7 @@ export declare class Config {
|
|
|
42
41
|
model: string;
|
|
43
42
|
url: string;
|
|
44
43
|
overfetch: number;
|
|
44
|
+
candidateFloor: number;
|
|
45
45
|
};
|
|
46
46
|
get indexer(): {
|
|
47
47
|
chunkSize: number;
|
|
@@ -57,6 +57,7 @@ export declare class Config {
|
|
|
57
57
|
};
|
|
58
58
|
get logging(): {
|
|
59
59
|
level: "error" | "debug" | "info" | "warn";
|
|
60
|
+
searchMetrics: boolean;
|
|
60
61
|
dir?: string | undefined;
|
|
61
62
|
};
|
|
62
63
|
get workspaceRoot(): string;
|
package/dist/src/config.js
CHANGED
|
@@ -38,13 +38,8 @@ exports.resolveEnvRef = resolveEnvRef;
|
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
39
|
const zod_1 = require("zod");
|
|
40
40
|
const config_store_1 = require("./config-store");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
super(message);
|
|
44
|
-
this.name = 'ConfigError';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
exports.ConfigError = ConfigError;
|
|
41
|
+
const config_error_1 = require("./config-error");
|
|
42
|
+
Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return config_error_1.ConfigError; } });
|
|
48
43
|
/**
|
|
49
44
|
* Resolve a config value that may reference an environment variable. A value of
|
|
50
45
|
* the form `env:VAR_NAME` resolves to `process.env.VAR_NAME`; any other string
|
|
@@ -58,24 +53,23 @@ function resolveEnvRef(value, env = process.env) {
|
|
|
58
53
|
const name = value.slice(4).trim();
|
|
59
54
|
const resolved = name ? env[name] : undefined;
|
|
60
55
|
if (resolved == null || resolved === '') {
|
|
61
|
-
throw new ConfigError(`Config references environment variable "${name}" (as "env:${name}") but it is not set. ` +
|
|
56
|
+
throw new config_error_1.ConfigError(`Config references environment variable "${name}" (as "env:${name}") but it is not set. ` +
|
|
62
57
|
`Define ${name} in ${config_store_1.ENV_FILE} or export it in the environment.`);
|
|
63
58
|
}
|
|
64
59
|
return resolved;
|
|
65
60
|
}
|
|
66
61
|
const DEFAULT_BASE_URLS = {
|
|
67
|
-
'ollama': 'http://localhost:11434',
|
|
68
62
|
'openai-compatible': 'https://api.openai.com/v1',
|
|
69
63
|
'voyage': 'https://api.voyageai.com/v1',
|
|
70
64
|
'infinity': 'http://localhost:7997',
|
|
71
65
|
};
|
|
72
66
|
function defaultBaseURL(provider) {
|
|
73
|
-
return DEFAULT_BASE_URLS[provider] ?? DEFAULT_BASE_URLS.
|
|
67
|
+
return DEFAULT_BASE_URLS[provider] ?? DEFAULT_BASE_URLS.infinity;
|
|
74
68
|
}
|
|
75
69
|
// Reranking is served only by OpenAI-compatible `/rerank` endpoints (Infinity,
|
|
76
|
-
// TEI, vLLM, Jina/Cohere-shape)
|
|
77
|
-
//
|
|
78
|
-
//
|
|
70
|
+
// TEI, vLLM, Jina/Cohere-shape) — a single endpoint shape. Normalize a trailing
|
|
71
|
+
// `/embeddings` or `/v1` to the server root so a baseURL written with an
|
|
72
|
+
// OpenAI-style suffix still yields `<root>/rerank`.
|
|
79
73
|
function deriveRerankUrl(baseURL) {
|
|
80
74
|
const root = baseURL.replace(/\/+$/, '').replace(/\/(embeddings|v1)$/, '');
|
|
81
75
|
return `${root}/rerank`;
|
|
@@ -89,7 +83,7 @@ const resolvedSchema = zod_1.z.object({
|
|
|
89
83
|
database: zod_1.z.string().min(1),
|
|
90
84
|
}),
|
|
91
85
|
embedding: zod_1.z.object({
|
|
92
|
-
provider: zod_1.z.enum(['
|
|
86
|
+
provider: zod_1.z.enum(['openai-compatible', 'voyage', 'infinity']).default(config_store_1.CONFIG_DEFAULTS['embedding.provider']),
|
|
93
87
|
baseURL: zod_1.z.string().min(1),
|
|
94
88
|
apiKey: zod_1.z.string().default(config_store_1.CONFIG_DEFAULTS['embedding.apiKey']),
|
|
95
89
|
model: zod_1.z.string().min(1).default(config_store_1.CONFIG_DEFAULTS['embedding.model']),
|
|
@@ -104,6 +98,7 @@ const resolvedSchema = zod_1.z.object({
|
|
|
104
98
|
model: zod_1.z.string().default(config_store_1.CONFIG_DEFAULTS['reranker.model']),
|
|
105
99
|
url: zod_1.z.string().min(1),
|
|
106
100
|
overfetch: zod_1.z.number().int().min(1).max(20).default(config_store_1.CONFIG_DEFAULTS['reranker.overfetch']),
|
|
101
|
+
candidateFloor: zod_1.z.number().int().min(1).default(config_store_1.CONFIG_DEFAULTS['reranker.candidateFloor']),
|
|
107
102
|
}),
|
|
108
103
|
indexer: zod_1.z.object({
|
|
109
104
|
chunkSize: zod_1.z.number().int().positive().default(config_store_1.CONFIG_DEFAULTS['indexing.chunkSize']),
|
|
@@ -120,6 +115,7 @@ const resolvedSchema = zod_1.z.object({
|
|
|
120
115
|
logging: zod_1.z.object({
|
|
121
116
|
level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).default(config_store_1.CONFIG_DEFAULTS['logging.level']),
|
|
122
117
|
dir: zod_1.z.string().optional(),
|
|
118
|
+
searchMetrics: zod_1.z.boolean().default(config_store_1.CONFIG_DEFAULTS['logging.searchMetrics']),
|
|
123
119
|
}),
|
|
124
120
|
workspaceRoot: zod_1.z.string().default(''),
|
|
125
121
|
});
|
|
@@ -143,7 +139,7 @@ class Config {
|
|
|
143
139
|
constructor() {
|
|
144
140
|
const raw = (0, config_store_1.readConfigJson)();
|
|
145
141
|
if (!raw)
|
|
146
|
-
throw new ConfigError('Config not found. Run rag-setup.');
|
|
142
|
+
throw new config_error_1.ConfigError('Config not found. Run rag-setup.');
|
|
147
143
|
const embProvider = (raw.embedding?.provider ?? config_store_1.CONFIG_DEFAULTS['embedding.provider']);
|
|
148
144
|
const embBaseURL = raw.embedding?.baseURL ?? defaultBaseURL(embProvider);
|
|
149
145
|
const rerankBaseURL = raw.reranker?.baseURL ?? embBaseURL;
|
|
@@ -172,6 +168,7 @@ class Config {
|
|
|
172
168
|
model: raw.reranker?.model,
|
|
173
169
|
url: rerankUrl,
|
|
174
170
|
overfetch: raw.reranker?.overfetch,
|
|
171
|
+
candidateFloor: raw.reranker?.candidateFloor,
|
|
175
172
|
},
|
|
176
173
|
indexer: {
|
|
177
174
|
chunkSize: raw.indexing?.chunkSize,
|
|
@@ -188,6 +185,7 @@ class Config {
|
|
|
188
185
|
logging: {
|
|
189
186
|
level: raw.logging?.level,
|
|
190
187
|
dir: raw.logging?.dir,
|
|
188
|
+
searchMetrics: raw.logging?.searchMetrics,
|
|
191
189
|
},
|
|
192
190
|
workspaceRoot: raw.workspaceRoot,
|
|
193
191
|
});
|
|
@@ -195,7 +193,7 @@ class Config {
|
|
|
195
193
|
const issues = result.error.issues
|
|
196
194
|
.map(i => ` ${i.path.join('.')}: ${i.message}`)
|
|
197
195
|
.join('\n');
|
|
198
|
-
throw new ConfigError(`Invalid config:\n${issues}\nRun rag-setup or: rag-config set <key> <value>`);
|
|
196
|
+
throw new config_error_1.ConfigError(`Invalid config:\n${issues}\nRun rag-setup or: rag-config set <key> <value>`);
|
|
199
197
|
}
|
|
200
198
|
this.data = result.data;
|
|
201
199
|
}
|
package/dist/src/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type EmbeddingProvider = '
|
|
1
|
+
export type EmbeddingProvider = 'openai-compatible' | 'voyage' | 'infinity';
|
|
2
2
|
/**
|
|
3
3
|
* pgvector's HNSW / IVFFlat index hard limit. The effective embedding dimension
|
|
4
4
|
* must not exceed this; larger cloud models must reduce via the Matryoshka
|
|
@@ -23,7 +23,13 @@ export interface ModelInfo {
|
|
|
23
23
|
* time (see `resolveDimension`).
|
|
24
24
|
*/
|
|
25
25
|
export declare const MODEL_REGISTRY: Record<string, ModelInfo>;
|
|
26
|
-
export declare const DEFAULT_EMBED_MODEL = "
|
|
26
|
+
export declare const DEFAULT_EMBED_MODEL = "jinaai/jina-embeddings-v2-base-code";
|
|
27
|
+
/**
|
|
28
|
+
* Former Ollama registry model names, kept only so config validation can
|
|
29
|
+
* recognise an implicit (provider-less) config that still targets Ollama —
|
|
30
|
+
* see `detectFormerOllamaConfig` in `config-store.ts`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const FORMER_OLLAMA_MODELS: readonly ["bge-m3", "nomic-embed-text", "mxbai-embed-large"];
|
|
27
33
|
/** Advisory dimension for a model, or `undefined` when the model is unknown. */
|
|
28
34
|
export declare function registryDimension(model: string): number | undefined;
|
|
29
35
|
/**
|
package/dist/src/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_EMBED_MODEL = exports.MODEL_REGISTRY = exports.MAX_EMBED_DIM = void 0;
|
|
3
|
+
exports.FORMER_OLLAMA_MODELS = exports.DEFAULT_EMBED_MODEL = exports.MODEL_REGISTRY = exports.MAX_EMBED_DIM = void 0;
|
|
4
4
|
exports.registryDimension = registryDimension;
|
|
5
5
|
exports.resolveDimension = resolveDimension;
|
|
6
6
|
/**
|
|
@@ -16,9 +16,6 @@ exports.MAX_EMBED_DIM = 2000;
|
|
|
16
16
|
* time (see `resolveDimension`).
|
|
17
17
|
*/
|
|
18
18
|
exports.MODEL_REGISTRY = {
|
|
19
|
-
'bge-m3': { provider: 'ollama', dimensions: 1024 },
|
|
20
|
-
'nomic-embed-text': { provider: 'ollama', dimensions: 768 },
|
|
21
|
-
'mxbai-embed-large': { provider: 'ollama', dimensions: 1024 },
|
|
22
19
|
'text-embedding-3-small': { provider: 'openai-compatible', dimensions: 1536, dimConfigurable: true },
|
|
23
20
|
'text-embedding-3-large': { provider: 'openai-compatible', dimensions: 3072, dimConfigurable: true, maxIndexable: 2000 },
|
|
24
21
|
'text-embedding-ada-002': { provider: 'openai-compatible', dimensions: 1536 },
|
|
@@ -28,7 +25,13 @@ exports.MODEL_REGISTRY = {
|
|
|
28
25
|
'BAAI/bge-m3': { provider: 'infinity', dimensions: 1024 },
|
|
29
26
|
'jinaai/jina-embeddings-v2-base-code': { provider: 'infinity', dimensions: 768 },
|
|
30
27
|
};
|
|
31
|
-
exports.DEFAULT_EMBED_MODEL = '
|
|
28
|
+
exports.DEFAULT_EMBED_MODEL = 'jinaai/jina-embeddings-v2-base-code';
|
|
29
|
+
/**
|
|
30
|
+
* Former Ollama registry model names, kept only so config validation can
|
|
31
|
+
* recognise an implicit (provider-less) config that still targets Ollama —
|
|
32
|
+
* see `detectFormerOllamaConfig` in `config-store.ts`.
|
|
33
|
+
*/
|
|
34
|
+
exports.FORMER_OLLAMA_MODELS = ['bge-m3', 'nomic-embed-text', 'mxbai-embed-large'];
|
|
32
35
|
/** Advisory dimension for a model, or `undefined` when the model is unknown. */
|
|
33
36
|
function registryDimension(model) {
|
|
34
37
|
return exports.MODEL_REGISTRY[model]?.dimensions;
|
package/dist/src/help.js
CHANGED
|
@@ -29,7 +29,7 @@ function printHelp(command) {
|
|
|
29
29
|
case 'setup':
|
|
30
30
|
console.log('Usage: rag-setup');
|
|
31
31
|
console.log('');
|
|
32
|
-
console.log('Interactive setup: configure DB,
|
|
32
|
+
console.log('Interactive setup: configure DB, embedding backend, and workspaceRoot.');
|
|
33
33
|
console.log('Run once before using rag-index or rag-mcp.');
|
|
34
34
|
console.log('');
|
|
35
35
|
console.log('Options:');
|
|
@@ -24,4 +24,21 @@ export interface IDatabase {
|
|
|
24
24
|
ensureChunkTable(dimensions: number): Promise<void>;
|
|
25
25
|
/** Drop and recreate `code_chunk` at `dimensions` (dimension change). */
|
|
26
26
|
recreateChunkTable(dimensions: number): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Return all file paths referenced in any of the four index tables for `project`.
|
|
29
|
+
* Union of code_chunk.filePath, code_symbol.filePath, code_call.fromFile,
|
|
30
|
+
* code_dependency.fromFile. calleeFile and toFile are excluded (may belong to other projects).
|
|
31
|
+
*/
|
|
32
|
+
listKnownFiles(project: string): Promise<string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Delete rows for `filePaths` from all four index tables in one transaction.
|
|
35
|
+
* code_call rows where the file is either fromFile OR calleeFile are removed.
|
|
36
|
+
* code_dependency rows where the file is either fromFile OR toFile are removed.
|
|
37
|
+
* No-op when `filePaths` is empty.
|
|
38
|
+
*/
|
|
39
|
+
deleteFiles(project: string, filePaths: string[]): Promise<void>;
|
|
40
|
+
/** Return all project names referenced in any of the four index tables. */
|
|
41
|
+
listIndexedProjects(): Promise<string[]>;
|
|
42
|
+
/** Delete all rows for `name` from the four index tables in one transaction. */
|
|
43
|
+
deleteProject(name: string): Promise<void>;
|
|
27
44
|
}
|
|
@@ -7,4 +7,11 @@ export interface ILogger {
|
|
|
7
7
|
info(msg: string, data?: Record<string, unknown>): void;
|
|
8
8
|
warn(msg: string, data?: Record<string, unknown>): void;
|
|
9
9
|
error(msg: string, data?: Record<string, unknown>): void;
|
|
10
|
+
/**
|
|
11
|
+
* Structured telemetry emitted at `info` **independent of the configured log
|
|
12
|
+
* level** — so an operator can collect it in production without the full
|
|
13
|
+
* `debug` firehose. No-op when file logging is disabled (never writes to
|
|
14
|
+
* stdout, which the MCP transport owns).
|
|
15
|
+
*/
|
|
16
|
+
metric(msg: string, data?: Record<string, unknown>): void;
|
|
10
17
|
}
|
|
@@ -13,6 +13,17 @@ export declare class AstChunker implements IChunker {
|
|
|
13
13
|
private readonly chunkSize;
|
|
14
14
|
constructor(config: Config);
|
|
15
15
|
chunk(code: string, opts: ChunkOpts): Promise<ChunkPiece[]>;
|
|
16
|
+
private produce;
|
|
17
|
+
/**
|
|
18
|
+
* True iff every significant line (blank and comment lines stripped) belongs
|
|
19
|
+
* to an `import` / re-export statement and at least one is an import or
|
|
20
|
+
* re-export keyword — so the chunk carries no value/type declaration and is
|
|
21
|
+
* never a search answer. Any `export const|let|var|function|class|interface|
|
|
22
|
+
* enum|namespace|default|declare|async function|type X = …` disqualifies
|
|
23
|
+
* exclusion. Pure. Distinguishes `export { … } from` / `export type { … }`
|
|
24
|
+
* (re-export → drop) from `export type X = …` (declaration → keep).
|
|
25
|
+
*/
|
|
26
|
+
static isImportOrReexportOnly(content: string): boolean;
|
|
16
27
|
/** Character-splitter path for a whole file; line numbers from splitter metadata. */
|
|
17
28
|
private splitWholeFile;
|
|
18
29
|
private chunkByAst;
|
|
@@ -27,6 +27,15 @@ class AstChunker {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
async chunk(code, opts) {
|
|
30
|
+
const pieces = await this.produce(code, opts);
|
|
31
|
+
// Single post-chunk pass: drop pieces whose only significant content is
|
|
32
|
+
// import / re-export statements. Both chunk sources — AST "gap" regions and
|
|
33
|
+
// the whole-file character path for barrel files — can emit these, so we
|
|
34
|
+
// filter once over the final pieces. Dependency edges are untouched (they
|
|
35
|
+
// come from analyzeProject → code_dependency, not from chunk text).
|
|
36
|
+
return pieces.filter(p => !AstChunker.isImportOrReexportOnly(p.content));
|
|
37
|
+
}
|
|
38
|
+
async produce(code, opts) {
|
|
30
39
|
if (opts.isSchemaFile) {
|
|
31
40
|
return this.splitWholeFile(code, this.schemaSplitter);
|
|
32
41
|
}
|
|
@@ -35,6 +44,41 @@ class AstChunker {
|
|
|
35
44
|
}
|
|
36
45
|
return this.chunkByAst(code, opts.symbols);
|
|
37
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* True iff every significant line (blank and comment lines stripped) belongs
|
|
49
|
+
* to an `import` / re-export statement and at least one is an import or
|
|
50
|
+
* re-export keyword — so the chunk carries no value/type declaration and is
|
|
51
|
+
* never a search answer. Any `export const|let|var|function|class|interface|
|
|
52
|
+
* enum|namespace|default|declare|async function|type X = …` disqualifies
|
|
53
|
+
* exclusion. Pure. Distinguishes `export { … } from` / `export type { … }`
|
|
54
|
+
* (re-export → drop) from `export type X = …` (declaration → keep).
|
|
55
|
+
*/
|
|
56
|
+
static isImportOrReexportOnly(content) {
|
|
57
|
+
const lines = content
|
|
58
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
|
|
59
|
+
.split('\n')
|
|
60
|
+
.map(l => l.trim())
|
|
61
|
+
.filter(l => l !== '' && !l.startsWith('//') && !l.startsWith('*'));
|
|
62
|
+
if (lines.length === 0)
|
|
63
|
+
return false;
|
|
64
|
+
const IMPORT = /^import\b/;
|
|
65
|
+
const REEXPORT = /^export\s+(\*|type\s+\{|\{)/; // export * | export type { | export {
|
|
66
|
+
const DISQUALIFY = /^export\s+(default|async\b|function\b|const\b|let\b|var\b|class\b|abstract\b|interface\b|enum\b|namespace\b|declare\b|type\s+[A-Za-z_$])/;
|
|
67
|
+
const CONTINUATION = /^(\}?\s*from\s+['"][^'"]+['"];?|[{}]|(type\s+)?[A-Za-z_$][\w$]*(\s+as\s+[A-Za-z_$][\w$]*)?,?|['"][^'"]*['"];?)$/;
|
|
68
|
+
let keywords = 0;
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (DISQUALIFY.test(line))
|
|
71
|
+
return false;
|
|
72
|
+
if (IMPORT.test(line) || REEXPORT.test(line)) {
|
|
73
|
+
keywords++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (CONTINUATION.test(line))
|
|
77
|
+
continue;
|
|
78
|
+
return false; // unrecognized line → real code, keep the chunk
|
|
79
|
+
}
|
|
80
|
+
return keywords >= 1;
|
|
81
|
+
}
|
|
38
82
|
/** Character-splitter path for a whole file; line numbers from splitter metadata. */
|
|
39
83
|
async splitWholeFile(code, splitter) {
|
|
40
84
|
const docs = await splitter.createDocuments([code]);
|
|
@@ -27,6 +27,10 @@ export declare class DatabaseService implements IDatabase {
|
|
|
27
27
|
recreateChunkTable(dimensions: number): Promise<void>;
|
|
28
28
|
readIndexMeta(): Promise<IndexMeta | null>;
|
|
29
29
|
writeIndexMeta(meta: IndexMeta): Promise<void>;
|
|
30
|
+
listKnownFiles(project: string): Promise<string[]>;
|
|
31
|
+
deleteFiles(project: string, filePaths: string[]): Promise<void>;
|
|
32
|
+
listIndexedProjects(): Promise<string[]>;
|
|
33
|
+
deleteProject(name: string): Promise<void>;
|
|
30
34
|
/** Guard against SQL injection — the dimension is interpolated into DDL. */
|
|
31
35
|
private assertDim;
|
|
32
36
|
private ensureDatabaseExists;
|
|
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.DatabaseService = void 0;
|
|
37
37
|
const node_postgres_1 = require("drizzle-orm/node-postgres");
|
|
38
38
|
const pg_1 = require("pg");
|
|
39
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
39
40
|
const schema = __importStar(require("../db/schema"));
|
|
41
|
+
const db_1 = require("../db");
|
|
40
42
|
class DatabaseService {
|
|
41
43
|
config;
|
|
42
44
|
pool;
|
|
@@ -198,6 +200,50 @@ class DatabaseService {
|
|
|
198
200
|
dimensions = EXCLUDED.dimensions,
|
|
199
201
|
"indexedAt" = now()`, [meta.provider, meta.model, meta.dimensions]);
|
|
200
202
|
}
|
|
203
|
+
async listKnownFiles(project) {
|
|
204
|
+
const rows = await this.query(`SELECT "filePath" AS fp FROM code_chunk WHERE "serviceName" = $1
|
|
205
|
+
UNION
|
|
206
|
+
SELECT "filePath" AS fp FROM code_symbol WHERE project = $1
|
|
207
|
+
UNION
|
|
208
|
+
SELECT "fromFile" AS fp FROM code_call WHERE project = $1
|
|
209
|
+
UNION
|
|
210
|
+
SELECT "fromFile" AS fp FROM code_dependency WHERE project = $1`, [project]);
|
|
211
|
+
return rows.map(r => r.fp);
|
|
212
|
+
}
|
|
213
|
+
async deleteFiles(project, filePaths) {
|
|
214
|
+
if (filePaths.length === 0)
|
|
215
|
+
return;
|
|
216
|
+
// PostgreSQL limits ~32 767 bind params per statement; OR queries bind the
|
|
217
|
+
// array twice (fromFile + calleeFile), so cap each batch at 10 000 entries.
|
|
218
|
+
const BATCH = 10_000;
|
|
219
|
+
await this.transaction(async (tx) => {
|
|
220
|
+
for (let i = 0; i < filePaths.length; i += BATCH) {
|
|
221
|
+
const batch = filePaths.slice(i, i + BATCH);
|
|
222
|
+
await tx.delete(db_1.codeChunk).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.codeChunk.serviceName, project), (0, drizzle_orm_1.inArray)(db_1.codeChunk.filePath, batch)));
|
|
223
|
+
await tx.delete(db_1.codeSymbol).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.codeSymbol.project, project), (0, drizzle_orm_1.inArray)(db_1.codeSymbol.filePath, batch)));
|
|
224
|
+
await tx.delete(db_1.codeCall).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.codeCall.project, project), (0, drizzle_orm_1.or)((0, drizzle_orm_1.inArray)(db_1.codeCall.fromFile, batch), (0, drizzle_orm_1.inArray)(db_1.codeCall.calleeFile, batch))));
|
|
225
|
+
await tx.delete(db_1.codeDependency).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.codeDependency.project, project), (0, drizzle_orm_1.or)((0, drizzle_orm_1.inArray)(db_1.codeDependency.fromFile, batch), (0, drizzle_orm_1.inArray)(db_1.codeDependency.toFile, batch))));
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async listIndexedProjects() {
|
|
230
|
+
const rows = await this.query(`SELECT "serviceName" AS name FROM code_chunk
|
|
231
|
+
UNION
|
|
232
|
+
SELECT project AS name FROM code_symbol
|
|
233
|
+
UNION
|
|
234
|
+
SELECT project AS name FROM code_call
|
|
235
|
+
UNION
|
|
236
|
+
SELECT project AS name FROM code_dependency`);
|
|
237
|
+
return rows.map(r => r.name);
|
|
238
|
+
}
|
|
239
|
+
async deleteProject(name) {
|
|
240
|
+
await this.transaction(async (tx) => {
|
|
241
|
+
await tx.delete(db_1.codeChunk).where((0, drizzle_orm_1.eq)(db_1.codeChunk.serviceName, name));
|
|
242
|
+
await tx.delete(db_1.codeSymbol).where((0, drizzle_orm_1.eq)(db_1.codeSymbol.project, name));
|
|
243
|
+
await tx.delete(db_1.codeCall).where((0, drizzle_orm_1.eq)(db_1.codeCall.project, name));
|
|
244
|
+
await tx.delete(db_1.codeDependency).where((0, drizzle_orm_1.eq)(db_1.codeDependency.project, name));
|
|
245
|
+
});
|
|
246
|
+
}
|
|
201
247
|
/** Guard against SQL injection — the dimension is interpolated into DDL. */
|
|
202
248
|
assertDim(dimensions) {
|
|
203
249
|
if (!Number.isInteger(dimensions) || dimensions <= 0 || dimensions > 2000) {
|
|
@@ -4,6 +4,6 @@ import type { ILogger } from '../interfaces/ILogger';
|
|
|
4
4
|
import { type EmbedderDeps } from './OpenAICompatibleEmbedder';
|
|
5
5
|
/**
|
|
6
6
|
* Construct the `IEmbedder` implementation selected by `config.embedding.provider`.
|
|
7
|
-
* The default (`
|
|
7
|
+
* The default (`infinity`) is the recommended local backend.
|
|
8
8
|
*/
|
|
9
9
|
export declare function createEmbedder(config: Config, logger?: ILogger, deps?: EmbedderDeps): IEmbedder;
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createEmbedder = createEmbedder;
|
|
4
4
|
const LoggerService_1 = require("./LoggerService");
|
|
5
|
-
const OllamaEmbedder_1 = require("./OllamaEmbedder");
|
|
6
5
|
const OpenAICompatibleEmbedder_1 = require("./OpenAICompatibleEmbedder");
|
|
7
6
|
const InfinityEmbedder_1 = require("./InfinityEmbedder");
|
|
8
7
|
const VoyageEmbedder_1 = require("./VoyageEmbedder");
|
|
9
8
|
/**
|
|
10
9
|
* Construct the `IEmbedder` implementation selected by `config.embedding.provider`.
|
|
11
|
-
* The default (`
|
|
10
|
+
* The default (`infinity`) is the recommended local backend.
|
|
12
11
|
*/
|
|
13
12
|
function createEmbedder(config, logger = LoggerService_1.LoggerService.silent(), deps = {}) {
|
|
14
13
|
switch (config.embedding.provider) {
|
|
@@ -18,12 +17,10 @@ function createEmbedder(config, logger = LoggerService_1.LoggerService.silent(),
|
|
|
18
17
|
return new InfinityEmbedder_1.InfinityEmbedder(config, logger, deps);
|
|
19
18
|
case 'voyage':
|
|
20
19
|
return new VoyageEmbedder_1.VoyageEmbedder(config, logger, deps);
|
|
21
|
-
case 'ollama':
|
|
22
|
-
return new OllamaEmbedder_1.OllamaEmbedder(config, logger, deps);
|
|
23
20
|
default: {
|
|
24
21
|
// The Zod enum should already reject this; guard anyway for a clear message.
|
|
25
22
|
const p = config.embedding.provider;
|
|
26
|
-
throw new Error(`Unknown embedding provider: "${p}". Expected one of:
|
|
23
|
+
throw new Error(`Unknown embedding provider: "${p}". Expected one of: openai-compatible, voyage, infinity.`);
|
|
27
24
|
}
|
|
28
25
|
}
|
|
29
26
|
}
|
|
@@ -51,6 +51,10 @@ export declare class IndexerService {
|
|
|
51
51
|
scanAndIndex(dir: string, serviceName: string, force?: boolean, signal?: AbortSignal, preCollectedFiles?: string[], opts?: {
|
|
52
52
|
rootTsConfigPath?: string;
|
|
53
53
|
}): Promise<IndexStats>;
|
|
54
|
+
pruneProjects(keep: string[]): Promise<{
|
|
55
|
+
removed: string[];
|
|
56
|
+
failed: string[];
|
|
57
|
+
}>;
|
|
54
58
|
private static inferRole;
|
|
55
59
|
private getEnrichedPrompt;
|
|
56
60
|
private static hashContent;
|