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
|
@@ -38,10 +38,15 @@ exports.mergeConfigForSave = mergeConfigForSave;
|
|
|
38
38
|
exports.maskConfig = maskConfig;
|
|
39
39
|
const http = __importStar(require("http"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
|
+
const config_1 = require("../config");
|
|
41
42
|
const config_store_1 = require("../config-store");
|
|
42
43
|
const constants_1 = require("../constants");
|
|
43
44
|
const graph_1 = require("../graph");
|
|
44
45
|
const dashboard_1 = require("../web/dashboard");
|
|
46
|
+
const infinity_provisioning_1 = require("./infinity-provisioning");
|
|
47
|
+
function sameModelSet(a, b) {
|
|
48
|
+
return a.join(';') === b.join(';');
|
|
49
|
+
}
|
|
45
50
|
/** Placeholder shown to the browser instead of a real secret. */
|
|
46
51
|
exports.PASSWORD_MASK = '********';
|
|
47
52
|
const MAX_BODY = 1_000_000; // 1 MB — config payloads are tiny
|
|
@@ -100,11 +105,22 @@ class WebServer {
|
|
|
100
105
|
logger;
|
|
101
106
|
embedder;
|
|
102
107
|
server;
|
|
103
|
-
|
|
108
|
+
composePath;
|
|
109
|
+
provisioningDeps;
|
|
110
|
+
readSavedConfig;
|
|
111
|
+
// Background Infinity-recreate task state (D6). In-memory, per-process — see
|
|
112
|
+
// `GET /api/backend-status`. `pending` holds at most one queued follow-up.
|
|
113
|
+
backendStatus = { state: 'idle', message: null, startedAt: null, finishedAt: null };
|
|
114
|
+
taskInFlight = false;
|
|
115
|
+
pending = null;
|
|
116
|
+
constructor(searcher, config, logger, embedder, deps = {}) {
|
|
104
117
|
this.searcher = searcher;
|
|
105
118
|
this.config = config;
|
|
106
119
|
this.logger = logger;
|
|
107
120
|
this.embedder = embedder;
|
|
121
|
+
this.composePath = deps.composePath ?? path.join(config_store_1.CONFIG_DIR, 'docker-compose.yml');
|
|
122
|
+
this.provisioningDeps = deps.provisioning ?? {};
|
|
123
|
+
this.readSavedConfig = deps.readSavedConfig ?? (() => new config_1.Config());
|
|
108
124
|
}
|
|
109
125
|
async start(port, host) {
|
|
110
126
|
this.server = http.createServer((req, res) => {
|
|
@@ -149,6 +165,8 @@ class WebServer {
|
|
|
149
165
|
return this.json(res, 200, this.maskedConfig());
|
|
150
166
|
if (m === 'PUT' && p === '/api/config')
|
|
151
167
|
return this.saveConfig(req, res);
|
|
168
|
+
if (m === 'GET' && p === '/api/backend-status')
|
|
169
|
+
return this.json(res, 200, this.backendStatus);
|
|
152
170
|
if (m === 'GET' && p === '/api/projects')
|
|
153
171
|
return this.json(res, 200, await this.searcher.listProjects());
|
|
154
172
|
if (m === 'GET' && p === '/api/graph')
|
|
@@ -186,7 +204,77 @@ class WebServer {
|
|
|
186
204
|
}
|
|
187
205
|
(0, config_store_1.writeConfigJson)(validated);
|
|
188
206
|
this.logger.info('WebServer: config updated');
|
|
189
|
-
|
|
207
|
+
const backend = await this.evaluateBackendAfterSave(validated);
|
|
208
|
+
this.json(res, 200, { ok: true, backend });
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* After a successful config write, decide the Infinity backend action per
|
|
212
|
+
* the first-match decision table (`local-backend-provisioning`). Computes
|
|
213
|
+
* the required set from the SAVED config (a fresh `Config` re-read from the
|
|
214
|
+
* file just written — never the server's startup config/embedder). Never
|
|
215
|
+
* throws — an unexpected error becomes `{ action: 'failed', message }`; the
|
|
216
|
+
* saved config and `GET /api/backend-status` are unaffected either way.
|
|
217
|
+
*/
|
|
218
|
+
async evaluateBackendAfterSave(saved) {
|
|
219
|
+
const provider = saved.embedding?.provider ?? config_store_1.CONFIG_DEFAULTS['embedding.provider'];
|
|
220
|
+
if (provider !== 'infinity') {
|
|
221
|
+
this.pending = null;
|
|
222
|
+
return { action: 'not-needed', message: null };
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const freshConfig = this.readSavedConfig();
|
|
226
|
+
const baseURL = freshConfig.embedding.baseURL;
|
|
227
|
+
const composePath = this.composePath;
|
|
228
|
+
const modelSet = (0, infinity_provisioning_1.requiredModelSet)(freshConfig);
|
|
229
|
+
const managed = (0, infinity_provisioning_1.isManagedInfinity)(baseURL, composePath);
|
|
230
|
+
if (this.taskInFlight) {
|
|
231
|
+
if (managed) {
|
|
232
|
+
this.pending = { modelSet, baseURL, composePath };
|
|
233
|
+
return { action: 'already-running', message: 'A backend recreate is already in progress; this configuration will be applied next.' };
|
|
234
|
+
}
|
|
235
|
+
// No longer targets the managed container the in-flight task owns.
|
|
236
|
+
this.pending = null;
|
|
237
|
+
}
|
|
238
|
+
const evaluation = await (0, infinity_provisioning_1.evaluateBackend)(baseURL, composePath, modelSet, this.provisioningDeps);
|
|
239
|
+
switch (evaluation.action) {
|
|
240
|
+
case 'served':
|
|
241
|
+
return { action: 'not-needed', message: null };
|
|
242
|
+
case 'missing-external':
|
|
243
|
+
case 'unreachable-external':
|
|
244
|
+
return { action: 'manual-required', message: (0, infinity_provisioning_1.manualInstructionMessage)(evaluation, (0, infinity_provisioning_1.serverRoot)(baseURL), modelSet) };
|
|
245
|
+
case 'missing-managed':
|
|
246
|
+
case 'unreachable-managed':
|
|
247
|
+
this.startBackgroundTask(baseURL, composePath, modelSet);
|
|
248
|
+
return { action: 'started', message: 'Recreating the managed Infinity container in the background.' };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
return { action: 'failed', message: err instanceof Error ? err.message : String(err) };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Kick off (or continue, via a follow-up) a background Infinity recreate. */
|
|
256
|
+
startBackgroundTask(baseURL, composePath, modelSet) {
|
|
257
|
+
this.taskInFlight = true;
|
|
258
|
+
this.backendStatus = { state: 'recreating', message: null, startedAt: new Date().toISOString(), finishedAt: null };
|
|
259
|
+
(0, infinity_provisioning_1.ensureModelSetServed)(baseURL, composePath, modelSet, this.provisioningDeps)
|
|
260
|
+
.then(() => {
|
|
261
|
+
this.backendStatus = { ...this.backendStatus, state: 'ready', message: null, finishedAt: new Date().toISOString() };
|
|
262
|
+
})
|
|
263
|
+
.catch((err) => {
|
|
264
|
+
this.backendStatus = {
|
|
265
|
+
...this.backendStatus, state: 'failed',
|
|
266
|
+
message: err instanceof Error ? err.message : String(err),
|
|
267
|
+
finishedAt: new Date().toISOString(),
|
|
268
|
+
};
|
|
269
|
+
})
|
|
270
|
+
.finally(() => {
|
|
271
|
+
this.taskInFlight = false;
|
|
272
|
+
const next = this.pending;
|
|
273
|
+
this.pending = null;
|
|
274
|
+
if (next && !sameModelSet(next.modelSet, modelSet)) {
|
|
275
|
+
this.startBackgroundTask(next.baseURL, next.composePath, next.modelSet);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
190
278
|
}
|
|
191
279
|
/**
|
|
192
280
|
* Choice lists for model fields. Both are non-strict comboboxes (free text
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Config } from '../config';
|
|
2
|
+
/** Result of a shell command run, mirroring the parts of `spawnSync` we use. */
|
|
3
|
+
export interface CommandResult {
|
|
4
|
+
status: number | null;
|
|
5
|
+
stdout?: string;
|
|
6
|
+
stderr?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Injection seams for the provisioning primitive. All optional, defaulted to the real implementations. */
|
|
9
|
+
export interface ProvisioningDeps {
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
/** Runs a shell command (docker). Defaults to `spawnSync` with captured output. */
|
|
12
|
+
runCommand?: (cmd: string, args: string[]) => CommandResult;
|
|
13
|
+
/** Sleep between readiness polls (ms), capped at 5000. Defaults to 2000; tests pass 0. */
|
|
14
|
+
pollIntervalMs?: number;
|
|
15
|
+
/** Readiness wait timeout (ms). Defaults to 1,800,000 (30 min); tests override to run fast. */
|
|
16
|
+
readinessTimeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Strip trailing slashes and a trailing `/embeddings` or `/v1` to get the server
|
|
20
|
+
* root — Infinity serves `/health`, `/models`, `/rerank`, `/embeddings` from the
|
|
21
|
+
* root, so a baseURL written with an OpenAI-style suffix must be normalized here
|
|
22
|
+
* to keep the probe/models URLs aligned with the embeddings URL.
|
|
23
|
+
*/
|
|
24
|
+
export declare function serverRoot(baseURL: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Probe an Infinity-style `/health` endpoint. Resolves `true` when the server
|
|
27
|
+
* answers with a 2xx, `false` on any error, non-2xx, or timeout.
|
|
28
|
+
*/
|
|
29
|
+
export declare function probeInfinityHealth(baseURL: string, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* List the model ids an Infinity server currently serves via its OpenAI-style
|
|
32
|
+
* `/models` listing (`{ data: [{ id }] }`). Returns `[]` on any error, non-2xx,
|
|
33
|
+
* or timeout — callers treat an empty/unverifiable listing as "served".
|
|
34
|
+
*/
|
|
35
|
+
export declare function listInfinityModels(baseURL: string, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Two URLs target the same server when scheme, (loopback-normalised) host, and
|
|
38
|
+
* port (explicit or scheme default) all match; path is ignored. An unparsable
|
|
39
|
+
* URL is never the same server as another.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sameServer(a: string, b: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* "Managed" per the managed-vs-external requirement: the compose file exists,
|
|
44
|
+
* declares an `infinity` service, the base URL host is loopback, and its port
|
|
45
|
+
* (explicit or scheme default) equals the service's published host port.
|
|
46
|
+
* Never throws — an unreadable/unparsable file or an unrecognised port form
|
|
47
|
+
* is simply "external".
|
|
48
|
+
*/
|
|
49
|
+
export declare function isManagedInfinity(baseURL: string, composePath: string): boolean;
|
|
50
|
+
/** The literal (non-`${…}`) `INFINITY_MODEL_ID` value in the compose file's `infinity` service, or null. */
|
|
51
|
+
export declare function extractModelIdValue(composeContent: string): string | null;
|
|
52
|
+
/** Write `content` to `targetPath` via a same-directory temp file + rename; removes the temp file on failure. */
|
|
53
|
+
export declare function atomicWriteFile(targetPath: string, content: string, mode: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* Write `modelSet` (embedding model first) as the literal `INFINITY_MODEL_ID`
|
|
56
|
+
* of the compose file's `infinity` service, and remove a `profiles:` entry of
|
|
57
|
+
* that service if present (a file written by an earlier version). At most
|
|
58
|
+
* those two targeted edits — every other byte unchanged. Atomic (temp file +
|
|
59
|
+
* rename), keeps the original file's mode. Throws (no write, original file
|
|
60
|
+
* unchanged) when the file cannot be read, has no `infinity` service, has no
|
|
61
|
+
* `INFINITY_MODEL_ID:` line, or the write itself fails.
|
|
62
|
+
*/
|
|
63
|
+
export declare function writeComposeModelSet(composePath: string, modelSet: string[]): void;
|
|
64
|
+
/** True when an NVIDIA GPU + `nvidia-smi` is available on this host. */
|
|
65
|
+
export declare function hasNvidiaGpu(): boolean;
|
|
66
|
+
/** The `-f` file args a docker compose invocation against `composePath` should use (base, plus GPU override when present + detected). */
|
|
67
|
+
export declare function composeFileArgs(composePath: string): string[];
|
|
68
|
+
export type BackendAction = 'served' | 'missing-managed' | 'missing-external' | 'unreachable-managed' | 'unreachable-external';
|
|
69
|
+
export interface BackendEvaluation {
|
|
70
|
+
action: BackendAction;
|
|
71
|
+
managed: boolean;
|
|
72
|
+
/** Required models absent from the served listing; empty for `served` or an unverifiable listing. */
|
|
73
|
+
missingModels: string[];
|
|
74
|
+
/** The full served listing at evaluation time; empty for an unverifiable listing or when unreachable. */
|
|
75
|
+
servedModels: string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Classify the backend against `requiredModels` without touching Docker:
|
|
79
|
+
* reachable+served (or unverifiable listing) → `served`; reachable+missing →
|
|
80
|
+
* `missing-managed`/`missing-external`; unreachable → `unreachable-managed`/
|
|
81
|
+
* `unreachable-external`. Shared by `ensureBackendReady`, the web-dashboard
|
|
82
|
+
* decision table, and `rag-setup`'s declined-Docker branch.
|
|
83
|
+
*/
|
|
84
|
+
export declare function evaluateBackend(baseURL: string, composePath: string, requiredModels: string[], deps?: ProvisioningDeps): Promise<BackendEvaluation>;
|
|
85
|
+
/** Human-readable manual instructions for a `missing-external`/`unreachable-external` evaluation. */
|
|
86
|
+
export declare function manualInstructionMessage(evaluation: BackendEvaluation, base: string, modelSetToWrite: string[]): string;
|
|
87
|
+
export interface EnsureBackendReadyOpts {
|
|
88
|
+
baseURL: string;
|
|
89
|
+
/** Models that must be served for the call to succeed without a recreate/instructions. */
|
|
90
|
+
requiredModels: string[];
|
|
91
|
+
/** Full model set to write into the compose file and bind at startup when a start/recreate is needed. */
|
|
92
|
+
modelSetToWrite: string[];
|
|
93
|
+
composePath: string;
|
|
94
|
+
deps?: ProvisioningDeps;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Bring the Infinity backend to a state serving `requiredModels`, per the
|
|
98
|
+
* managed-vs-external decision tree:
|
|
99
|
+
* served → return (no Docker)
|
|
100
|
+
* missing-managed → recreate (writes modelSetToWrite) + wait
|
|
101
|
+
* missing-external → throw (manual --model-id instructions)
|
|
102
|
+
* unreachable-managed → (re)create + wait
|
|
103
|
+
* unreachable-external → throw (manual launch instructions)
|
|
104
|
+
*/
|
|
105
|
+
export declare function ensureBackendReady(opts: EnsureBackendReadyOpts): Promise<void>;
|
|
106
|
+
/** `ensureBackendReady` with the full set as both the requirement and the write (rag-setup, web dashboard). */
|
|
107
|
+
export declare function ensureModelSetServed(baseURL: string, composePath: string, modelSet: string[], deps?: ProvisioningDeps): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* The model set a managed Infinity container must serve, computed from a
|
|
110
|
+
* `Config`: the embedding model, followed by the reranker model only when
|
|
111
|
+
* reranking is enabled with a model on the same server as embeddings (the
|
|
112
|
+
* effective `reranker.url`, which `Config` has already resolved from an
|
|
113
|
+
* explicit value or derived from `reranker.baseURL`).
|
|
114
|
+
*/
|
|
115
|
+
export declare function requiredModelSet(cfg: Config): string[];
|