codesearch-mcp 0.2.3-alpha → 0.3.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 +31 -1
- package/README.md +46 -26
- package/dist/app.js +5 -19
- package/dist/src/chunking/AstChunker.d.ts +12 -0
- package/dist/src/chunking/AstChunker.js +30 -0
- package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
- package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
- package/dist/src/chunking/FragmentMerger.d.ts +11 -0
- package/dist/src/chunking/FragmentMerger.js +61 -0
- package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
- package/dist/src/chunking/ImportOnlyFilter.js +14 -0
- package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
- package/dist/src/chunking/LangchainTextSplitter.js +133 -0
- package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
- package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
- package/dist/src/chunking/RunPacker.d.ts +12 -0
- package/dist/src/chunking/RunPacker.js +84 -0
- package/dist/src/chunking/create-chunker.d.ts +3 -0
- package/dist/src/chunking/create-chunker.js +15 -0
- package/dist/src/chunking/interfaces.d.ts +27 -0
- package/dist/src/chunking/interfaces.js +2 -0
- package/dist/src/commands/ConfigCommand.js +1 -9
- package/dist/src/commands/EvalCommand.d.ts +4 -3
- package/dist/src/commands/EvalCommand.js +85 -52
- package/dist/src/commands/IndexCommand.js +0 -22
- package/dist/src/commands/McpCommand.js +2 -8
- package/dist/src/commands/WebCommand.js +2 -5
- package/dist/src/config-error.d.ts +0 -4
- package/dist/src/config-error.js +0 -4
- package/dist/src/config-store.d.ts +1 -54
- package/dist/src/config-store.js +8 -69
- package/dist/src/config.d.ts +8 -17
- package/dist/src/config.js +35 -34
- package/dist/src/constants.d.ts +2 -28
- package/dist/src/constants.js +6 -28
- package/dist/src/container.d.ts +6 -0
- package/dist/src/container.js +12 -3
- package/dist/src/db/schema.d.ts +17 -0
- package/dist/src/db/schema.js +1 -6
- package/dist/src/declarations/DeclarationTree.d.ts +19 -0
- package/dist/src/declarations/DeclarationTree.js +66 -0
- package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
- package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
- package/dist/src/declarations/SourceLines.d.ts +37 -0
- package/dist/src/declarations/SourceLines.js +415 -0
- package/dist/src/dependency-traversal.d.ts +0 -6
- package/dist/src/dependency-traversal.js +0 -6
- package/dist/src/enriched-prompt.d.ts +0 -6
- package/dist/src/enriched-prompt.js +0 -6
- package/dist/src/error-record.d.ts +12 -0
- package/dist/src/error-record.js +248 -0
- package/dist/src/eval-corpus.d.ts +16 -0
- package/dist/src/eval-corpus.js +169 -0
- package/dist/src/eval-results.d.ts +6 -3
- package/dist/src/eval-results.js +309 -26
- package/dist/src/file-utils.d.ts +0 -5
- package/dist/src/file-utils.js +0 -5
- package/dist/src/graph.d.ts +0 -25
- package/dist/src/graph.js +0 -28
- package/dist/src/hnsw-settings.d.ts +3 -0
- package/dist/src/hnsw-settings.js +24 -0
- package/dist/src/interfaces/IAstParser.d.ts +2 -39
- package/dist/src/interfaces/IChunker.d.ts +3 -12
- package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
- package/dist/src/interfaces/ICorpusInspector.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +2 -22
- package/dist/src/interfaces/IEmbedder.d.ts +0 -9
- package/dist/src/interfaces/ILogger.d.ts +1 -11
- package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
- package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
- package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
- package/dist/src/interfaces/ISearcher.d.ts +16 -5
- package/dist/src/interrupt.js +0 -2
- package/dist/src/owner-path.d.ts +9 -10
- package/dist/src/owner-path.js +11 -23
- package/dist/src/project-scope.d.ts +0 -12
- package/dist/src/project-scope.js +0 -12
- package/dist/src/project-selector.d.ts +0 -5
- package/dist/src/project-selector.js +0 -11
- package/dist/src/retrieval-eval.d.ts +14 -6
- package/dist/src/retrieval-eval.js +345 -59
- package/dist/src/search/result-shaping.d.ts +4 -0
- package/dist/src/search/result-shaping.js +19 -0
- package/dist/src/services/AstParserService.d.ts +2 -62
- package/dist/src/services/AstParserService.js +25 -137
- package/dist/src/services/DatabaseService.d.ts +2 -7
- package/dist/src/services/DatabaseService.js +42 -29
- package/dist/src/services/EmbedderFactory.d.ts +0 -4
- package/dist/src/services/EmbedderFactory.js +0 -5
- package/dist/src/services/IndexerService.d.ts +0 -16
- package/dist/src/services/IndexerService.js +16 -45
- package/dist/src/services/InfinityEmbedder.d.ts +0 -21
- package/dist/src/services/InfinityEmbedder.js +0 -15
- package/dist/src/services/LoggerService.d.ts +7 -5
- package/dist/src/services/LoggerService.js +37 -41
- package/dist/src/services/McpServer.d.ts +22 -14
- package/dist/src/services/McpServer.js +70 -37
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
- package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
- package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
- package/dist/src/services/NxMonorepoResolver.js +5 -56
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
- package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
- package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
- package/dist/src/services/ProjectDiscoveryService.js +0 -4
- package/dist/src/services/SearcherService.d.ts +9 -116
- package/dist/src/services/SearcherService.js +95 -176
- package/dist/src/services/VoyageEmbedder.d.ts +0 -6
- package/dist/src/services/VoyageEmbedder.js +0 -6
- package/dist/src/services/WebServerService.d.ts +1 -53
- package/dist/src/services/WebServerService.js +11 -57
- package/dist/src/services/http-retry.d.ts +0 -7
- package/dist/src/services/http-retry.js +1 -10
- package/dist/src/services/infinity-provisioning.d.ts +0 -74
- package/dist/src/services/infinity-provisioning.js +5 -88
- package/dist/src/services/legacy-ollama-index.d.ts +0 -16
- package/dist/src/services/legacy-ollama-index.js +0 -23
- package/dist/src/services/reranker.d.ts +0 -9
- package/dist/src/services/reranker.js +46 -12
- package/dist/src/setup.d.ts +11 -23
- package/dist/src/setup.js +98 -71
- package/dist/src/symbol-name.d.ts +0 -12
- package/dist/src/symbol-name.js +0 -13
- package/dist/src/symbol-rows.d.ts +0 -14
- package/dist/src/symbol-rows.js +0 -14
- package/dist/src/tool-call-log.d.ts +2 -0
- package/dist/src/tool-call-log.js +24 -0
- package/dist/src/tools/GetDependenciesTool.js +1 -1
- package/dist/src/tools/GetDependentsTool.js +1 -1
- package/dist/src/tools/SearchCodebaseTool.js +6 -3
- package/dist/src/tools/format-helpers.d.ts +0 -6
- package/dist/src/tools/format-helpers.js +87 -14
- package/dist/src/web/dashboard.d.ts +0 -8
- package/dist/src/web/dashboard.js +7 -10
- package/package.json +2 -2
- package/dist/src/services/AstChunker.d.ts +0 -42
- package/dist/src/services/AstChunker.js +0 -158
- package/docs/web-dashboard.md +0 -206
package/dist/src/setup.js
CHANGED
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.EMBEDDING_PROVIDER_CHOICES = void 0;
|
|
37
37
|
exports.dockerPromptDefault = dockerPromptDefault;
|
|
38
|
+
exports.rerankerChoices = rerankerChoices;
|
|
39
|
+
exports.promptReranker = promptReranker;
|
|
38
40
|
exports.prepareComposeAndStartDb = prepareComposeAndStartDb;
|
|
39
41
|
exports.provisionInfinity = provisionInfinity;
|
|
40
42
|
exports.runSetup = runSetup;
|
|
@@ -43,6 +45,7 @@ const fs = __importStar(require("fs"));
|
|
|
43
45
|
const path = __importStar(require("path"));
|
|
44
46
|
const os = __importStar(require("os"));
|
|
45
47
|
const child_process_1 = require("child_process");
|
|
48
|
+
const constants_1 = require("./constants");
|
|
46
49
|
const config_store_1 = require("./config-store");
|
|
47
50
|
const config_1 = require("./config");
|
|
48
51
|
const infinity_provisioning_1 = require("./services/infinity-provisioning");
|
|
@@ -68,51 +71,115 @@ function expandHome(p) {
|
|
|
68
71
|
}
|
|
69
72
|
const PROVIDER_DEFAULTS = {
|
|
70
73
|
'openai-compatible': { baseURL: 'https://api.openai.com/v1', model: 'text-embedding-3-small', envVar: 'OPENAI_API_KEY' },
|
|
71
|
-
'voyage': { baseURL: 'https://api.voyageai.com/v1', model: 'voyage-code-
|
|
74
|
+
'voyage': { baseURL: 'https://api.voyageai.com/v1', model: 'voyage-code-4', envVar: 'VOYAGE_API_KEY' },
|
|
72
75
|
'infinity': { baseURL: 'http://localhost:7997', model: 'jinaai/jina-embeddings-v2-base-code', envVar: '' },
|
|
73
76
|
};
|
|
74
|
-
/** Providers the wizard offers, in prompt order; the first is the preselected default. Exported for unit testing. */
|
|
75
77
|
exports.EMBEDDING_PROVIDER_CHOICES = ['infinity', 'openai-compatible', 'voyage'];
|
|
76
|
-
/** Providers that run a local inference backend we can probe / provision. */
|
|
77
78
|
function isLocalBackend(p) {
|
|
78
79
|
return p === 'infinity';
|
|
79
80
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Probe whether the embedding backend is already reachable, so the "start
|
|
82
|
-
* Docker?" prompt can default to no (mirrors the DB `checkRemoteDb` flow).
|
|
83
|
-
* Cloud providers have no local backend → always "reachable" (nothing to start).
|
|
84
|
-
*/
|
|
85
81
|
async function checkEmbeddingBackend(provider, baseURL) {
|
|
86
82
|
if (!isLocalBackend(provider))
|
|
87
83
|
return true;
|
|
88
84
|
return (0, infinity_provisioning_1.probeInfinityHealth)(baseURL);
|
|
89
85
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Default answer for the "start Docker containers?" prompt: no only when both
|
|
92
|
-
* the database and the embedding backend are already reachable. Pure — exported
|
|
93
|
-
* for unit testing.
|
|
94
|
-
*/
|
|
95
86
|
function dockerPromptDefault(dbReachable, embReachable) {
|
|
96
87
|
return (dbReachable && embReachable) ? 'n' : 'y';
|
|
97
88
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Write (or replace) a single `KEY=VALUE` line in `~/.config/codesearch-mcp/.env`,
|
|
100
|
-
* chmod 600. API keys live here — never in config.json — referenced as `env:KEY`.
|
|
101
|
-
*/
|
|
102
89
|
function writeEnvKey(name, value) {
|
|
103
90
|
fs.mkdirSync(config_store_1.CONFIG_DIR, { recursive: true });
|
|
104
91
|
let existing = '';
|
|
105
92
|
try {
|
|
106
93
|
existing = fs.readFileSync(config_store_1.ENV_FILE, 'utf-8');
|
|
107
94
|
}
|
|
108
|
-
catch {
|
|
95
|
+
catch { }
|
|
109
96
|
const kept = existing.split(/\r?\n/).filter(l => l.trim() && !l.trim().startsWith(`${name}=`));
|
|
110
97
|
kept.push(`${name}=${value}`);
|
|
111
98
|
fs.writeFileSync(config_store_1.ENV_FILE, kept.join('\n') + '\n', { mode: 0o600 });
|
|
112
99
|
try {
|
|
113
100
|
fs.chmodSync(config_store_1.ENV_FILE, 0o600);
|
|
114
101
|
}
|
|
115
|
-
catch {
|
|
102
|
+
catch { }
|
|
103
|
+
}
|
|
104
|
+
const VOYAGE_RERANK_ENV = 'VOYAGE_RERANK_API_KEY';
|
|
105
|
+
const RERANKER_ENV = 'RERANKER_API_KEY';
|
|
106
|
+
function rerankerChoices(embProvider) {
|
|
107
|
+
if (embProvider === 'infinity')
|
|
108
|
+
return ['infinity', 'voyage', 'openai-compatible', 'none'];
|
|
109
|
+
if (embProvider === 'voyage')
|
|
110
|
+
return ['voyage', 'openai-compatible', 'none'];
|
|
111
|
+
return ['none', 'voyage', 'openai-compatible'];
|
|
112
|
+
}
|
|
113
|
+
async function askRequired(io, question) {
|
|
114
|
+
for (;;) {
|
|
115
|
+
const answer = (await io.ask(question)).trim();
|
|
116
|
+
if (answer)
|
|
117
|
+
return answer;
|
|
118
|
+
io.log(' A value is required.');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isHttpUrl(value) {
|
|
122
|
+
try {
|
|
123
|
+
const { protocol, username, password } = new URL(value);
|
|
124
|
+
return (protocol === 'http:' || protocol === 'https:') && !username && !password;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function askHttpUrl(io, question) {
|
|
131
|
+
for (;;) {
|
|
132
|
+
const answer = await askRequired(io, question);
|
|
133
|
+
if (isHttpUrl(answer))
|
|
134
|
+
return answer;
|
|
135
|
+
io.log(' Enter an absolute http(s):// URL without credentials, e.g. http://localhost:7997.');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function promptReranker(embProvider, embBaseURL, io) {
|
|
139
|
+
const choices = rerankerChoices(embProvider);
|
|
140
|
+
io.log('\n--- Reranker ---');
|
|
141
|
+
if (choices.includes('infinity'))
|
|
142
|
+
io.log(' infinity — served by the same Infinity container');
|
|
143
|
+
io.log(' voyage — Voyage AI cloud (needs a Voyage API key)');
|
|
144
|
+
io.log(' openai-compatible — any /rerank server (Infinity, vLLM)');
|
|
145
|
+
io.log(' none — no reranking');
|
|
146
|
+
let choice;
|
|
147
|
+
for (;;) {
|
|
148
|
+
choice = (await io.prompt(`Reranker (${choices.join('/')})`, choices[0])).trim();
|
|
149
|
+
if (choices.includes(choice))
|
|
150
|
+
break;
|
|
151
|
+
io.log(` Invalid reranker. Choose: ${choices.join(', ')}`);
|
|
152
|
+
}
|
|
153
|
+
if (choice === 'none')
|
|
154
|
+
return { enabled: false };
|
|
155
|
+
if (choice === 'infinity') {
|
|
156
|
+
const model = (await io.prompt('Reranker model (served by the same Infinity container)', config_store_1.CONFIG_DEFAULTS['reranker.model'])).trim();
|
|
157
|
+
return { enabled: true, provider: 'openai-compatible', baseURL: embBaseURL, model };
|
|
158
|
+
}
|
|
159
|
+
if (choice === 'voyage') {
|
|
160
|
+
const model = (await io.prompt(`Reranker model (${constants_1.VOYAGE_RERANK_MODELS.join(' / ')})`, constants_1.VOYAGE_RERANK_MODELS[0])).trim();
|
|
161
|
+
for (;;) {
|
|
162
|
+
const key = (await io.ask(`Voyage reranker API key (stored in ${config_store_1.ENV_FILE} as ${VOYAGE_RERANK_ENV}): `)).trim();
|
|
163
|
+
if (key) {
|
|
164
|
+
io.writeEnvKey(VOYAGE_RERANK_ENV, key);
|
|
165
|
+
io.log(` Saved reranker API key to ${config_store_1.ENV_FILE} as ${VOYAGE_RERANK_ENV}.`);
|
|
166
|
+
return { enabled: true, provider: 'voyage', model, apiKey: `env:${VOYAGE_RERANK_ENV}` };
|
|
167
|
+
}
|
|
168
|
+
io.log(' The Voyage reranker is not configured without an API key.');
|
|
169
|
+
const disable = (await io.prompt('Disable the reranker? (y/n)', 'y')).trim().toLowerCase();
|
|
170
|
+
if (disable === 'y')
|
|
171
|
+
return { enabled: false };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const baseURL = await askHttpUrl(io, 'Rerank server base URL: ');
|
|
175
|
+
const model = await askRequired(io, 'Reranker model: ');
|
|
176
|
+
const key = (await io.ask(`Reranker API key (stored in ${config_store_1.ENV_FILE} as ${RERANKER_ENV}, blank for none): `)).trim();
|
|
177
|
+
if (key)
|
|
178
|
+
io.writeEnvKey(RERANKER_ENV, key);
|
|
179
|
+
return {
|
|
180
|
+
enabled: true, provider: 'openai-compatible', baseURL, model,
|
|
181
|
+
...(key ? { apiKey: `env:${RERANKER_ENV}` } : {}),
|
|
182
|
+
};
|
|
116
183
|
}
|
|
117
184
|
async function waitForDb(host, port, user, pass, db, maxWaitSec = 60, logger, steps) {
|
|
118
185
|
const { Client } = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
@@ -147,18 +214,10 @@ async function checkRemoteDb(host, port, user, pass, db, logger, steps) {
|
|
|
147
214
|
catch (err) {
|
|
148
215
|
const msg = err instanceof Error ? err.message : String(err);
|
|
149
216
|
console.error(` Connection failed: ${msg}`);
|
|
150
|
-
logger?.error('checkRemoteDb: connection failed', {
|
|
217
|
+
logger?.error('checkRemoteDb: connection failed', { err, steps: JSON.stringify(steps) });
|
|
151
218
|
return false;
|
|
152
219
|
}
|
|
153
220
|
}
|
|
154
|
-
/**
|
|
155
|
-
* Regenerate `CONFIG_DIR/docker-compose.yml` from the shipped template
|
|
156
|
-
* (carrying over an existing literal `INFINITY_MODEL_ID`, written atomically),
|
|
157
|
-
* copy the GPU override, and start the DB container when requested. Runs
|
|
158
|
-
* BEFORE the config file is written — its failure aborts setup unsaved,
|
|
159
|
-
* matching the existing "abort without saving" behavior of the DB step. Does
|
|
160
|
-
* NOT touch Infinity provisioning (moved after `writeConfigJson` — see D6a).
|
|
161
|
-
*/
|
|
162
221
|
async function prepareComposeAndStartDb(opts) {
|
|
163
222
|
const { dbPort, dbUser, dbPass, startDb, logger, steps } = opts;
|
|
164
223
|
console.log('\nWriting docker-compose.yml to', DOCKER_COMPOSE_DST);
|
|
@@ -168,14 +227,11 @@ async function prepareComposeAndStartDb(opts) {
|
|
|
168
227
|
.replace('POSTGRES_USER: dev', `POSTGRES_USER: ${yamlQuote(dbUser)}`)
|
|
169
228
|
.replace('POSTGRES_PASSWORD: dev', `POSTGRES_PASSWORD: ${yamlQuote(dbPass)}`)
|
|
170
229
|
.replace('"5433:5432"', `"${dbPort}:5432"`);
|
|
171
|
-
// Carry over an existing literal INFINITY_MODEL_ID — never the set of the
|
|
172
|
-
// config still being entered — so a setup that aborts before the config is
|
|
173
|
-
// written leaves a file consistent with the previously saved config.
|
|
174
230
|
let existingModelId = null;
|
|
175
231
|
try {
|
|
176
232
|
existingModelId = (0, infinity_provisioning_1.extractModelIdValue)(fs.readFileSync(DOCKER_COMPOSE_DST, 'utf-8'));
|
|
177
233
|
}
|
|
178
|
-
catch {
|
|
234
|
+
catch { }
|
|
179
235
|
if (existingModelId) {
|
|
180
236
|
composeContent = composeContent.replace(/INFINITY_MODEL_ID:\s*"[^"]*"/, `INFINITY_MODEL_ID: "${existingModelId}"`);
|
|
181
237
|
}
|
|
@@ -184,13 +240,12 @@ async function prepareComposeAndStartDb(opts) {
|
|
|
184
240
|
try {
|
|
185
241
|
existingMode = fs.statSync(DOCKER_COMPOSE_DST).mode;
|
|
186
242
|
}
|
|
187
|
-
catch {
|
|
243
|
+
catch { }
|
|
188
244
|
(0, infinity_provisioning_1.atomicWriteFile)(DOCKER_COMPOSE_DST, composeContent, existingMode);
|
|
189
|
-
// The GPU override is copied verbatim so the managed Infinity path can layer it.
|
|
190
245
|
try {
|
|
191
246
|
fs.copyFileSync(DOCKER_GPU_OVERRIDE_SRC, DOCKER_GPU_OVERRIDE_DST);
|
|
192
247
|
}
|
|
193
|
-
catch {
|
|
248
|
+
catch { }
|
|
194
249
|
if (!startDb)
|
|
195
250
|
return;
|
|
196
251
|
console.log('\nStarting the database container...');
|
|
@@ -202,30 +257,19 @@ async function prepareComposeAndStartDb(opts) {
|
|
|
202
257
|
}
|
|
203
258
|
await waitForDb('localhost', parseInt(dbPort), dbUser, dbPass, 'postgres', 60, logger, steps);
|
|
204
259
|
}
|
|
205
|
-
/**
|
|
206
|
-
* Infinity provisioning (D6a) — runs AFTER the config file is written, so no
|
|
207
|
-
* outcome here can leave the config unsaved. `savedConfig` is a fresh read of
|
|
208
|
-
* the just-written file (never the process's own startup config). Exit-status
|
|
209
|
-
* rules: 0 for served / timeout (warning) / external instructions / declined
|
|
210
|
-
* Docker; 1 for any other provisioning failure — the cause is printed and the
|
|
211
|
-
* remaining setup steps (DB schema init) still run.
|
|
212
|
-
*/
|
|
213
260
|
async function provisionInfinity(savedConfig, startDocker, logger, deps = {}, composePathOverride) {
|
|
214
261
|
const composePath = composePathOverride ?? DOCKER_COMPOSE_DST;
|
|
215
262
|
const baseURL = savedConfig.embedding.baseURL;
|
|
216
263
|
const base = (0, infinity_provisioning_1.serverRoot)(baseURL);
|
|
217
264
|
const set = (0, infinity_provisioning_1.requiredModelSet)(savedConfig);
|
|
218
265
|
const managed = (0, infinity_provisioning_1.isManagedInfinity)(baseURL, composePath);
|
|
219
|
-
// Targeted write happens for a managed backend regardless of what follows
|
|
220
|
-
// (yes/no, served/missing), so the compose file always matches the saved
|
|
221
|
-
// config — even when the running container already serves the set.
|
|
222
266
|
if (managed) {
|
|
223
267
|
try {
|
|
224
268
|
(0, infinity_provisioning_1.writeComposeModelSet)(composePath, set);
|
|
225
269
|
}
|
|
226
270
|
catch (err) {
|
|
227
271
|
console.error(`\nFailed to write the managed compose file: ${err.message}`);
|
|
228
|
-
logger?.error('provisionInfinity: compose write failed', {
|
|
272
|
+
logger?.error('provisionInfinity: compose write failed', { err });
|
|
229
273
|
process.exitCode = 1;
|
|
230
274
|
}
|
|
231
275
|
}
|
|
@@ -239,17 +283,16 @@ async function provisionInfinity(savedConfig, startDocker, logger, deps = {}, co
|
|
|
239
283
|
if (/did not become ready within/.test(msg)) {
|
|
240
284
|
console.warn(`\nWarning: ${msg}`);
|
|
241
285
|
console.warn('The container is running and likely still warming up. Check again once /health responds, then run: rag-index');
|
|
242
|
-
logger?.warn('provisionInfinity: readiness timeout (non-fatal)', {
|
|
286
|
+
logger?.warn('provisionInfinity: readiness timeout (non-fatal)', { err });
|
|
243
287
|
}
|
|
244
288
|
else {
|
|
245
289
|
console.error(`\nInfinity provisioning failed: ${msg}`);
|
|
246
|
-
logger?.error('provisionInfinity: failed', {
|
|
290
|
+
logger?.error('provisionInfinity: failed', { err });
|
|
247
291
|
process.exitCode = 1;
|
|
248
292
|
}
|
|
249
293
|
}
|
|
250
294
|
return;
|
|
251
295
|
}
|
|
252
|
-
// Docker declined — no Docker command; print what would apply the set.
|
|
253
296
|
const evaluation = await (0, infinity_provisioning_1.evaluateBackend)(baseURL, composePath, set, deps);
|
|
254
297
|
if (evaluation.action === 'served')
|
|
255
298
|
return;
|
|
@@ -311,12 +354,6 @@ async function runSetup(logger) {
|
|
|
311
354
|
const pd = PROVIDER_DEFAULTS[embProvider];
|
|
312
355
|
const embBaseURL = await loggedPrompt('Base URL', pd.baseURL);
|
|
313
356
|
const embModel = await loggedPrompt('Embedding model', pd.model);
|
|
314
|
-
// Infinity serves a reranker from the same container — offer to provision one.
|
|
315
|
-
let rerankModel = '';
|
|
316
|
-
if (embProvider === 'infinity') {
|
|
317
|
-
rerankModel = (await loggedPrompt(`Reranker model (served by the same Infinity container; blank to disable)`, config_store_1.CONFIG_DEFAULTS['reranker.model'])).trim();
|
|
318
|
-
}
|
|
319
|
-
// API keys go into .env (chmod 600); config.json only stores the env: reference.
|
|
320
357
|
let apiKeyRef = '';
|
|
321
358
|
if (!isLocalBackend(embProvider)) {
|
|
322
359
|
const key = (await loggedAsk(`API key (stored in ${config_store_1.ENV_FILE}, leave blank to set later): `)).trim();
|
|
@@ -330,6 +367,9 @@ async function runSetup(logger) {
|
|
|
330
367
|
console.log(` No key entered — set it later: echo '${pd.envVar}=sk-…' >> ${config_store_1.ENV_FILE}`);
|
|
331
368
|
}
|
|
332
369
|
}
|
|
370
|
+
const reranker = await promptReranker(embProvider, embBaseURL, {
|
|
371
|
+
prompt: loggedPrompt, ask: loggedAsk, writeEnvKey, log: msg => console.log(msg),
|
|
372
|
+
});
|
|
333
373
|
console.log('\n--- Database ---');
|
|
334
374
|
const dbHost = await loggedPrompt('DB host', 'localhost');
|
|
335
375
|
const dbPort = await loggedPrompt('DB port', '5433');
|
|
@@ -339,8 +379,6 @@ async function runSetup(logger) {
|
|
|
339
379
|
const dbReachable = await checkRemoteDb(dbHost, dbPort, dbUser, dbPass, 'postgres', logger, steps);
|
|
340
380
|
if (dbReachable)
|
|
341
381
|
console.log(' Database is already reachable.');
|
|
342
|
-
// Symmetric to the DB probe: a reachable embedding backend defaults "start
|
|
343
|
-
// Docker?" to no. Cloud providers have no local backend to start.
|
|
344
382
|
process.stdout.write('Checking embedding backend...');
|
|
345
383
|
const embReachable = await checkEmbeddingBackend(embProvider, embBaseURL);
|
|
346
384
|
if (isLocalBackend(embProvider)) {
|
|
@@ -400,26 +438,15 @@ async function runSetup(logger) {
|
|
|
400
438
|
model: embModel,
|
|
401
439
|
...(apiKeyRef ? { apiKey: apiKeyRef } : {}),
|
|
402
440
|
},
|
|
441
|
+
reranker,
|
|
403
442
|
logging: { level: logLevel },
|
|
404
443
|
workspaceRoot,
|
|
405
444
|
};
|
|
406
|
-
// Infinity serves the reranker from the same container — auto-wire reranker.*
|
|
407
|
-
// to that server. `url` derives from provider + baseURL in the Config constructor.
|
|
408
|
-
if (embProvider === 'infinity' && rerankModel) {
|
|
409
|
-
cfg.reranker = {
|
|
410
|
-
enabled: true,
|
|
411
|
-
provider: 'openai-compatible',
|
|
412
|
-
baseURL: embBaseURL,
|
|
413
|
-
model: rerankModel,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
445
|
if (logDir)
|
|
417
446
|
cfg.logging.dir = logDir;
|
|
418
447
|
(0, config_store_1.writeConfigJson)(cfg);
|
|
419
448
|
console.log(`\nConfig saved to ${config_store_1.CONFIG_FILE}`);
|
|
420
449
|
rl.close();
|
|
421
|
-
// Infinity provisioning happens only now — after the config is safely on
|
|
422
|
-
// disk — so a provisioning failure never leaves the wizard's answers unsaved.
|
|
423
450
|
if (embProvider === 'infinity') {
|
|
424
451
|
await provisionInfinity(new config_1.Config(), startDocker, logger);
|
|
425
452
|
}
|
|
@@ -438,7 +465,7 @@ async function runSetup(logger) {
|
|
|
438
465
|
}
|
|
439
466
|
if (initError) {
|
|
440
467
|
console.error(`Database schema init failed: ${initError.message}`);
|
|
441
|
-
logger?.error('runSetup: database schema init failed', {
|
|
468
|
+
logger?.error('runSetup: database schema init failed', { err: initError, steps: JSON.stringify(steps) });
|
|
442
469
|
process.exit(1);
|
|
443
470
|
}
|
|
444
471
|
console.log('\nSetup complete!');
|
|
@@ -16,22 +16,11 @@ export type FindSymbolQuery = {
|
|
|
16
16
|
container: string | null;
|
|
17
17
|
name: string;
|
|
18
18
|
};
|
|
19
|
-
/** Split `P.n` into container `P` and last segment `n`. Requires a qualified name. */
|
|
20
19
|
export declare function splitQualified(input: string): {
|
|
21
20
|
container: string;
|
|
22
21
|
name: string;
|
|
23
22
|
};
|
|
24
|
-
/**
|
|
25
|
-
* Parse a `find_symbol` name into one of the four accepted forms (bare,
|
|
26
|
-
* qualified, quoted, computed), or null when it matches none — the caller
|
|
27
|
-
* should reject with `Invalid symbol name` without querying anything.
|
|
28
|
-
*/
|
|
29
23
|
export declare function parseFindSymbolName(input: string): FindSymbolQuery | null;
|
|
30
|
-
/**
|
|
31
|
-
* Parse a `get_callers` symbol into bare or qualified form only — the quoted
|
|
32
|
-
* and computed forms of `find_symbol` are meaningless here (calls through
|
|
33
|
-
* element access are never recorded in the call graph).
|
|
34
|
-
*/
|
|
35
24
|
export declare function parseCallerSymbol(input: string): {
|
|
36
25
|
form: 'bare';
|
|
37
26
|
name: string;
|
|
@@ -40,5 +29,4 @@ export declare function parseCallerSymbol(input: string): {
|
|
|
40
29
|
container: string;
|
|
41
30
|
name: string;
|
|
42
31
|
} | null;
|
|
43
|
-
/** Escape `%`, `_` and `\` for a `LIKE ... ESCAPE '\'` pattern. */
|
|
44
32
|
export declare function escapeLike(s: string): string;
|
package/dist/src/symbol-name.js
CHANGED
|
@@ -11,22 +11,15 @@ const PFX_SRC = '(?:#?[\\w$]+(?:\\.#?[\\w$]+)*\\.)?';
|
|
|
11
11
|
const QUOTED_RE = new RegExp(`^(${PFX_SRC})('[^'\\r\\n]+'|"[^"\\r\\n]+")$`);
|
|
12
12
|
const COMPUTED_RE = new RegExp(`^(${PFX_SRC})(\\[[^\\]\\r\\n]+\\])$`);
|
|
13
13
|
const MAX_QUOTED_COMPUTED_LEN = 256;
|
|
14
|
-
/** Strip a qualified name's trailing `.` container prefix, if present. */
|
|
15
14
|
function stripTrailingDot(prefix) {
|
|
16
15
|
if (!prefix)
|
|
17
16
|
return null;
|
|
18
17
|
return prefix.slice(0, -1);
|
|
19
18
|
}
|
|
20
|
-
/** Split `P.n` into container `P` and last segment `n`. Requires a qualified name. */
|
|
21
19
|
function splitQualified(input) {
|
|
22
20
|
const idx = input.lastIndexOf('.');
|
|
23
21
|
return { container: input.slice(0, idx), name: input.slice(idx + 1) };
|
|
24
22
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Parse a `find_symbol` name into one of the four accepted forms (bare,
|
|
27
|
-
* qualified, quoted, computed), or null when it matches none — the caller
|
|
28
|
-
* should reject with `Invalid symbol name` without querying anything.
|
|
29
|
-
*/
|
|
30
23
|
function parseFindSymbolName(input) {
|
|
31
24
|
if (input.length <= MAX_QUOTED_COMPUTED_LEN) {
|
|
32
25
|
const quoted = input.match(QUOTED_RE);
|
|
@@ -47,11 +40,6 @@ function parseFindSymbolName(input) {
|
|
|
47
40
|
}
|
|
48
41
|
return null;
|
|
49
42
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Parse a `get_callers` symbol into bare or qualified form only — the quoted
|
|
52
|
-
* and computed forms of `find_symbol` are meaningless here (calls through
|
|
53
|
-
* element access are never recorded in the call graph).
|
|
54
|
-
*/
|
|
55
43
|
function parseCallerSymbol(input) {
|
|
56
44
|
if (exports.QUALIFIED_NAME_RE.test(input)) {
|
|
57
45
|
const { container, name } = splitQualified(input);
|
|
@@ -62,7 +50,6 @@ function parseCallerSymbol(input) {
|
|
|
62
50
|
}
|
|
63
51
|
return null;
|
|
64
52
|
}
|
|
65
|
-
/** Escape `%`, `_` and `\` for a `LIKE ... ESCAPE '\'` pattern. */
|
|
66
53
|
function escapeLike(s) {
|
|
67
54
|
return s.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
68
55
|
}
|
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
import type { SymbolEntry } from './interfaces/IAstParser';
|
|
2
2
|
export type CodeSymbolRow = Pick<SymbolEntry, 'project' | 'filePath' | 'name' | 'kind' | 'signature' | 'line' | 'col' | 'container' | 'isStatic' | 'target'>;
|
|
3
|
-
/**
|
|
4
|
-
* Combine top-level symbols, members and GraphQL-named rows into the rows
|
|
5
|
-
* written to `code_symbol`. Overload signatures of one declaration — same
|
|
6
|
-
* `(container, name, kind, isStatic)` — collapse to a single representative
|
|
7
|
-
* row: the one with a body (the implementation) when present, otherwise the
|
|
8
|
-
* earliest by line (the first signature in source order). Static and instance
|
|
9
|
-
* members sharing a name are never collapsed into each other (`isStatic` is
|
|
10
|
-
* part of the group key). `graphqlNames` bypass this collapse — decorators
|
|
11
|
-
* cannot sit on overload signatures, so there is nothing to collapse, and the
|
|
12
|
-
* collapse's `(container, name, kind, isStatic)` key would wrongly merge two
|
|
13
|
-
* different methods exposing the same GraphQL name from two resolver classes
|
|
14
|
-
* (both with container `Query`). A final dedup by `(container, name, kind,
|
|
15
|
-
* line)` matches the DB's unique index.
|
|
16
|
-
*/
|
|
17
3
|
export declare function buildSymbolRows(symbols: SymbolEntry[], members: SymbolEntry[], graphqlNames?: SymbolEntry[]): CodeSymbolRow[];
|
package/dist/src/symbol-rows.js
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildSymbolRows = buildSymbolRows;
|
|
4
|
-
/**
|
|
5
|
-
* Combine top-level symbols, members and GraphQL-named rows into the rows
|
|
6
|
-
* written to `code_symbol`. Overload signatures of one declaration — same
|
|
7
|
-
* `(container, name, kind, isStatic)` — collapse to a single representative
|
|
8
|
-
* row: the one with a body (the implementation) when present, otherwise the
|
|
9
|
-
* earliest by line (the first signature in source order). Static and instance
|
|
10
|
-
* members sharing a name are never collapsed into each other (`isStatic` is
|
|
11
|
-
* part of the group key). `graphqlNames` bypass this collapse — decorators
|
|
12
|
-
* cannot sit on overload signatures, so there is nothing to collapse, and the
|
|
13
|
-
* collapse's `(container, name, kind, isStatic)` key would wrongly merge two
|
|
14
|
-
* different methods exposing the same GraphQL name from two resolver classes
|
|
15
|
-
* (both with container `Query`). A final dedup by `(container, name, kind,
|
|
16
|
-
* line)` matches the DB's unique index.
|
|
17
|
-
*/
|
|
18
4
|
function buildSymbolRows(symbols, members, graphqlNames = []) {
|
|
19
5
|
const groups = new Map();
|
|
20
6
|
for (const entry of [...symbols, ...members]) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.truncateForLog = truncateForLog;
|
|
4
|
+
exports.resolveSession = resolveSession;
|
|
5
|
+
const MAX_STRING = 500;
|
|
6
|
+
const MAX_ITEMS = 20;
|
|
7
|
+
const MAX_DEPTH = 4;
|
|
8
|
+
function truncateForLog(value, depth = 1) {
|
|
9
|
+
if (typeof value === 'string') {
|
|
10
|
+
return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}…(+${value.length - MAX_STRING})` : value;
|
|
11
|
+
}
|
|
12
|
+
if (value === null || typeof value !== 'object')
|
|
13
|
+
return value;
|
|
14
|
+
if (depth > MAX_DEPTH)
|
|
15
|
+
return '[depth]';
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
const kept = value.slice(0, MAX_ITEMS).map(v => truncateForLog(v, depth + 1));
|
|
18
|
+
return value.length > MAX_ITEMS ? [...kept, `…(+${value.length - MAX_ITEMS})`] : kept;
|
|
19
|
+
}
|
|
20
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, truncateForLog(v, depth + 1)]));
|
|
21
|
+
}
|
|
22
|
+
function resolveSession(env) {
|
|
23
|
+
return env.CLAUDE_CODE_SESSION_ID ?? null;
|
|
24
|
+
}
|
|
@@ -12,7 +12,7 @@ class GetDependenciesTool {
|
|
|
12
12
|
definition() {
|
|
13
13
|
return {
|
|
14
14
|
name: 'get_dependencies',
|
|
15
|
-
description: 'Show what files a given file imports (direct or transitive), in any project. Use to understand what a file depends on before modifying it.
|
|
15
|
+
description: 'Show what files a given file imports (direct or transitive), in any project. Use to understand what a file depends on before modifying it. The direct section prints one line per imported file, with the names imported from it.',
|
|
16
16
|
inputSchema: {
|
|
17
17
|
type: 'object',
|
|
18
18
|
properties: {
|
|
@@ -12,7 +12,7 @@ class GetDependentsTool {
|
|
|
12
12
|
definition() {
|
|
13
13
|
return {
|
|
14
14
|
name: 'get_dependents',
|
|
15
|
-
description: 'Show which files import a given file (reverse graph), in any project. Use before refactoring to find all callers and avoid breaking changes.
|
|
15
|
+
description: 'Show which files import a given file (reverse graph), in any project. Use before refactoring to find all callers and avoid breaking changes. The direct section prints one line per importing file, with the names imported and the enclosing declarations that use them — only those resolved unambiguously, so the list can be partial or absent.',
|
|
16
16
|
inputSchema: {
|
|
17
17
|
type: 'object',
|
|
18
18
|
properties: {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SearchCodebaseTool = void 0;
|
|
4
4
|
const format_helpers_1 = require("./format-helpers");
|
|
5
|
+
const result_shaping_1 = require("../search/result-shaping");
|
|
5
6
|
class SearchCodebaseTool {
|
|
6
7
|
searcher;
|
|
7
8
|
defaultLimit;
|
|
@@ -28,7 +29,8 @@ class SearchCodebaseTool {
|
|
|
28
29
|
include_neighbors: { type: 'boolean', description: 'Also include the chunk before and after each result from the same file (Small-to-Big retrieval). Useful when results look truncated.' },
|
|
29
30
|
include_graph_context: { type: 'boolean', description: 'Append direct imports and dependents list to each result. Shows "Imports: X · Y" and "Used by: Z" without extra tool calls.' },
|
|
30
31
|
threshold: { type: 'number', description: `Max cosine distance (0–1). Lower = stricter. Default: ${this.defaultThreshold}. Use 0.40–0.45 for precise technical queries, 0.55–0.60 for broad exploration.` },
|
|
31
|
-
|
|
32
|
+
per_file: { type: 'number', description: `Max results from one file (integer ≥ 1, default ${result_shaping_1.DEFAULT_PER_FILE}). Skipped results are backfilled from the next candidates. Raise it when one file is expected to hold several answers (e.g. several methods of one class).` },
|
|
33
|
+
rerank: { type: 'boolean', description: 'Override whether this call reranks, regardless of config. false skips reranking (use for "what does X do" / "where is X" lookups described without an exact name — reranking measurably hurts these; if you know the exact symbol name, use find_symbol instead). true forces reranking on if a reranker is configured (no-op otherwise). Omit to use config.reranker.enabled (fine for conceptual/architectural queries, where reranking is roughly neutral).' },
|
|
32
34
|
},
|
|
33
35
|
required: ['query'],
|
|
34
36
|
},
|
|
@@ -47,11 +49,12 @@ class SearchCodebaseTool {
|
|
|
47
49
|
? Math.min(1, Math.max(0, parsedThreshold))
|
|
48
50
|
: undefined;
|
|
49
51
|
const rerank = typeof args.rerank === 'boolean' ? args.rerank : undefined;
|
|
52
|
+
const perFile = Number.isInteger(args.per_file) && args.per_file >= 1 ? args.per_file : result_shaping_1.DEFAULT_PER_FILE;
|
|
50
53
|
const { query, translated } = await this.searcher.prepareQuery(raw);
|
|
51
|
-
const results = await this.searcher.searchChunks(query, limit, project, threshold, rerank);
|
|
54
|
+
const results = await this.searcher.searchChunks(query, limit, project, threshold, rerank, perFile);
|
|
52
55
|
this.logger.debug('SearchCodebaseTool: handle', {
|
|
53
56
|
query: raw.slice(0, 120), project, threshold: threshold ?? this.defaultThreshold,
|
|
54
|
-
limit, translated, count: results.length, ms: Date.now() - t0,
|
|
57
|
+
limit, perFile, translated, count: results.length, ms: Date.now() - t0,
|
|
55
58
|
});
|
|
56
59
|
if (results.length === 0) {
|
|
57
60
|
return (0, format_helpers_1.textResponse)('No relevant results found.');
|
|
@@ -9,15 +9,9 @@ export declare function formatDependencies(filePath: string, rows: DependencyRow
|
|
|
9
9
|
export declare function formatDependents(filePath: string, rows: DependencyRow[], transitive: boolean): string;
|
|
10
10
|
export type PathKind = 'file' | 'directory';
|
|
11
11
|
export declare const PATH_EXAMPLE: Record<PathKind, string>;
|
|
12
|
-
/** Description of a path argument; `kind` names what the path points at. */
|
|
13
12
|
export declare function pathArgDescription(kind?: PathKind): string;
|
|
14
13
|
export type PathError = PathRefusal | PathFailure | Extract<FileRangeResult, {
|
|
15
14
|
reason: 'past_end' | 'bad_range';
|
|
16
15
|
}>;
|
|
17
|
-
/**
|
|
18
|
-
* Text of a path error. `input` is the raw argument (echoed by refusals);
|
|
19
|
-
* `relPath` its canonical form, printed by every other message.
|
|
20
|
-
*/
|
|
21
16
|
export declare function pathErrorText(argName: string, input: string, relPath: string, err: PathError, kind?: PathKind): string;
|
|
22
|
-
/** Error response for a path error, logged at `warn` — a caller or index-freshness problem, not a server fault. */
|
|
23
17
|
export declare function pathErrorResponse(logger: ILogger, tool: string, argName: string, input: string, relPath: string, err: PathError, kind?: PathKind): ToolResponse;
|