@adhisang/minecraft-modding-mcp 2.0.0 → 2.1.0
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 +44 -0
- package/README.md +109 -29
- package/dist/cli.js +31 -4
- package/dist/compat-stdio-transport.d.ts +2 -7
- package/dist/compat-stdio-transport.js +12 -154
- package/dist/index.js +392 -33
- package/dist/json-rpc-framing.d.ts +22 -0
- package/dist/json-rpc-framing.js +168 -0
- package/dist/mapping-pipeline-service.js +9 -1
- package/dist/mapping-service.d.ts +9 -0
- package/dist/mapping-service.js +183 -60
- package/dist/minecraft-explorer-service.d.ts +0 -1
- package/dist/minecraft-explorer-service.js +119 -23
- package/dist/mixin-validator.d.ts +24 -2
- package/dist/mixin-validator.js +223 -98
- package/dist/mod-decompile-service.d.ts +5 -0
- package/dist/mod-decompile-service.js +40 -5
- package/dist/mod-remap-service.js +142 -30
- package/dist/path-resolver.js +41 -4
- package/dist/registry-service.d.ts +10 -1
- package/dist/registry-service.js +154 -22
- package/dist/search-hit-accumulator.js +23 -2
- package/dist/source-jar-reader.js +16 -2
- package/dist/source-resolver.js +6 -7
- package/dist/source-service.d.ts +42 -4
- package/dist/source-service.js +781 -127
- package/dist/stdio-supervisor.d.ts +46 -0
- package/dist/stdio-supervisor.js +349 -0
- package/dist/storage/files-repo.d.ts +3 -9
- package/dist/storage/files-repo.js +66 -43
- package/dist/symbols/symbol-extractor.js +6 -4
- package/dist/tool-execution-gate.d.ts +15 -0
- package/dist/tool-execution-gate.js +58 -0
- package/dist/version-diff-service.js +10 -5
- package/dist/version-service.js +7 -2
- package/dist/workspace-mapping-service.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
type SupervisorOptions = {
|
|
2
|
+
entryFile: string;
|
|
3
|
+
};
|
|
4
|
+
export declare class StdioSupervisor {
|
|
5
|
+
private readonly entryFile;
|
|
6
|
+
private readonly clientReader;
|
|
7
|
+
private readonly workerReader;
|
|
8
|
+
private readonly queuedMessages;
|
|
9
|
+
private readonly pendingRequests;
|
|
10
|
+
private child;
|
|
11
|
+
private childReady;
|
|
12
|
+
private shuttingDown;
|
|
13
|
+
private restartTimer;
|
|
14
|
+
private workerStderrBuffer;
|
|
15
|
+
private clientMode;
|
|
16
|
+
private initializeRequest;
|
|
17
|
+
private initializedNotification;
|
|
18
|
+
private clientInitialized;
|
|
19
|
+
private replayingInitialization;
|
|
20
|
+
private initializeSentToWorker;
|
|
21
|
+
constructor(options: SupervisorOptions);
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
private readonly handleClientData;
|
|
24
|
+
private readonly handleClientError;
|
|
25
|
+
private readonly handleClientClosed;
|
|
26
|
+
private readonly handleTerminateSignal;
|
|
27
|
+
private handleClientMessage;
|
|
28
|
+
private forwardToWorker;
|
|
29
|
+
private spawnWorker;
|
|
30
|
+
private readonly handleWorkerData;
|
|
31
|
+
private readonly handleWorkerStdinError;
|
|
32
|
+
private readonly handleWorkerStderr;
|
|
33
|
+
private readonly handleWorkerProcessError;
|
|
34
|
+
private readonly handleWorkerExit;
|
|
35
|
+
private handleWorkerMessage;
|
|
36
|
+
private handleWorkerReady;
|
|
37
|
+
private isInitializationResponse;
|
|
38
|
+
private flushQueue;
|
|
39
|
+
private failPendingRequestsOnWorkerExit;
|
|
40
|
+
private writeToClient;
|
|
41
|
+
private scheduleRestart;
|
|
42
|
+
private detachChild;
|
|
43
|
+
private shutdown;
|
|
44
|
+
}
|
|
45
|
+
export declare const STDIO_WORKER_MODE_ENV = "MCP_STDIO_WORKER_MODE";
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { encodeJsonRpcMessage, JsonRpcFrameReader } from "./json-rpc-framing.js";
|
|
4
|
+
import { log } from "./logger.js";
|
|
5
|
+
const DEFAULT_CLIENT_MODE = "line";
|
|
6
|
+
const WORKER_MODE_ENV = "MCP_STDIO_WORKER_MODE";
|
|
7
|
+
const WORKER_READY_MARKER = "__MCP_STDIO_WORKER_READY__";
|
|
8
|
+
const SUPERVISOR_DEBUG_ENABLED = process.env.MCP_SUPERVISOR_DEBUG === "1";
|
|
9
|
+
function isRequest(message) {
|
|
10
|
+
return "method" in message && "id" in message;
|
|
11
|
+
}
|
|
12
|
+
function isNotification(message) {
|
|
13
|
+
return "method" in message && !("id" in message);
|
|
14
|
+
}
|
|
15
|
+
function isResponse(message) {
|
|
16
|
+
return !("method" in message) && "id" in message;
|
|
17
|
+
}
|
|
18
|
+
function getTrackedRequestId(message) {
|
|
19
|
+
return typeof message.id === "string" || typeof message.id === "number"
|
|
20
|
+
? message.id
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
23
|
+
function requestKey(id) {
|
|
24
|
+
return `${typeof id}:${String(id)}`;
|
|
25
|
+
}
|
|
26
|
+
function buildWorkerRestartError(id) {
|
|
27
|
+
return {
|
|
28
|
+
jsonrpc: "2.0",
|
|
29
|
+
id,
|
|
30
|
+
error: {
|
|
31
|
+
code: -32603,
|
|
32
|
+
message: "MCP worker restarted while handling the request. Retry the request."
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function debugSupervisor(event, details) {
|
|
37
|
+
if (!SUPERVISOR_DEBUG_ENABLED) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
log("info", `supervisor.debug.${event}`, details);
|
|
41
|
+
}
|
|
42
|
+
export class StdioSupervisor {
|
|
43
|
+
entryFile;
|
|
44
|
+
clientReader = new JsonRpcFrameReader();
|
|
45
|
+
workerReader = new JsonRpcFrameReader();
|
|
46
|
+
queuedMessages = [];
|
|
47
|
+
pendingRequests = new Map();
|
|
48
|
+
child;
|
|
49
|
+
childReady = false;
|
|
50
|
+
shuttingDown = false;
|
|
51
|
+
restartTimer;
|
|
52
|
+
workerStderrBuffer = "";
|
|
53
|
+
clientMode = DEFAULT_CLIENT_MODE;
|
|
54
|
+
initializeRequest;
|
|
55
|
+
initializedNotification;
|
|
56
|
+
clientInitialized = false;
|
|
57
|
+
replayingInitialization = false;
|
|
58
|
+
initializeSentToWorker = false;
|
|
59
|
+
constructor(options) {
|
|
60
|
+
this.entryFile = options.entryFile;
|
|
61
|
+
}
|
|
62
|
+
async start() {
|
|
63
|
+
process.stdin.on("data", this.handleClientData);
|
|
64
|
+
process.stdin.on("error", this.handleClientError);
|
|
65
|
+
process.stdin.on("end", this.handleClientClosed);
|
|
66
|
+
process.stdin.on("close", this.handleClientClosed);
|
|
67
|
+
process.stdin.resume();
|
|
68
|
+
process.on("SIGINT", this.handleTerminateSignal);
|
|
69
|
+
process.on("SIGTERM", this.handleTerminateSignal);
|
|
70
|
+
this.spawnWorker();
|
|
71
|
+
}
|
|
72
|
+
handleClientData = (chunk) => {
|
|
73
|
+
this.clientReader.processChunk(chunk, {
|
|
74
|
+
onFrame: ({ message, mode }) => {
|
|
75
|
+
this.clientMode = mode;
|
|
76
|
+
this.handleClientMessage(message);
|
|
77
|
+
},
|
|
78
|
+
onError: (error) => {
|
|
79
|
+
log("warn", "supervisor.client_parse_error", { message: error.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
handleClientError = (error) => {
|
|
84
|
+
log("warn", "supervisor.client_stream_error", { message: error.message });
|
|
85
|
+
};
|
|
86
|
+
handleClientClosed = () => {
|
|
87
|
+
void this.shutdown();
|
|
88
|
+
};
|
|
89
|
+
handleTerminateSignal = () => {
|
|
90
|
+
void this.shutdown();
|
|
91
|
+
};
|
|
92
|
+
handleClientMessage(message) {
|
|
93
|
+
debugSupervisor("client_message", {
|
|
94
|
+
hasMethod: "method" in message,
|
|
95
|
+
method: "method" in message ? message.method : undefined,
|
|
96
|
+
id: "id" in message ? message.id : undefined,
|
|
97
|
+
childReady: this.childReady
|
|
98
|
+
});
|
|
99
|
+
if (isRequest(message) && message.method === "initialize") {
|
|
100
|
+
this.initializeRequest = message;
|
|
101
|
+
this.clientInitialized = false;
|
|
102
|
+
}
|
|
103
|
+
else if (isNotification(message) && message.method === "notifications/initialized") {
|
|
104
|
+
this.initializedNotification = message;
|
|
105
|
+
}
|
|
106
|
+
if (!this.childReady) {
|
|
107
|
+
this.queuedMessages.push(message);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.forwardToWorker(message);
|
|
111
|
+
}
|
|
112
|
+
forwardToWorker(message) {
|
|
113
|
+
const child = this.child;
|
|
114
|
+
if (!child || child.stdin.destroyed) {
|
|
115
|
+
this.queuedMessages.push(message);
|
|
116
|
+
if (!this.shuttingDown) {
|
|
117
|
+
this.scheduleRestart();
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (isRequest(message)) {
|
|
122
|
+
const id = getTrackedRequestId(message);
|
|
123
|
+
if (id !== undefined) {
|
|
124
|
+
this.pendingRequests.set(requestKey(id), {
|
|
125
|
+
id,
|
|
126
|
+
method: message.method
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (message.method === "initialize") {
|
|
130
|
+
this.initializeSentToWorker = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
debugSupervisor("forward_to_worker", {
|
|
134
|
+
method: "method" in message ? message.method : undefined,
|
|
135
|
+
id: "id" in message ? message.id : undefined
|
|
136
|
+
});
|
|
137
|
+
child.stdin.write(encodeJsonRpcMessage(message, "content-length"));
|
|
138
|
+
}
|
|
139
|
+
spawnWorker() {
|
|
140
|
+
if (this.shuttingDown) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const child = spawn(process.execPath, [...process.execArgv, this.entryFile], {
|
|
144
|
+
env: {
|
|
145
|
+
...process.env,
|
|
146
|
+
[WORKER_MODE_ENV]: "1"
|
|
147
|
+
},
|
|
148
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
149
|
+
});
|
|
150
|
+
this.child = child;
|
|
151
|
+
this.childReady = false;
|
|
152
|
+
this.initializeSentToWorker = false;
|
|
153
|
+
this.workerReader.clear();
|
|
154
|
+
this.workerStderrBuffer = "";
|
|
155
|
+
child.stdout.on("data", this.handleWorkerData);
|
|
156
|
+
child.stderr.on("data", this.handleWorkerStderr);
|
|
157
|
+
child.stdin.on("error", this.handleWorkerStdinError);
|
|
158
|
+
child.once("error", this.handleWorkerProcessError);
|
|
159
|
+
child.once("exit", this.handleWorkerExit);
|
|
160
|
+
log("info", "supervisor.worker_spawn", { pid: child.pid });
|
|
161
|
+
}
|
|
162
|
+
handleWorkerData = (chunk) => {
|
|
163
|
+
this.workerReader.processChunk(chunk, {
|
|
164
|
+
onFrame: ({ message }) => {
|
|
165
|
+
this.handleWorkerMessage(message);
|
|
166
|
+
},
|
|
167
|
+
onError: (error) => {
|
|
168
|
+
log("warn", "supervisor.worker_parse_error", { message: error.message });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
handleWorkerStdinError = (error) => {
|
|
173
|
+
if (error.code === "EPIPE") {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
log("warn", "supervisor.worker_stdin_error", { message: error.message });
|
|
177
|
+
};
|
|
178
|
+
handleWorkerStderr = (chunk) => {
|
|
179
|
+
this.workerStderrBuffer += chunk.toString();
|
|
180
|
+
const lines = this.workerStderrBuffer.split(/\r?\n/);
|
|
181
|
+
this.workerStderrBuffer = lines.pop() ?? "";
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
if (line === WORKER_READY_MARKER) {
|
|
184
|
+
this.handleWorkerReady();
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
process.stderr.write(`${line}\n`);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
handleWorkerProcessError = (error) => {
|
|
191
|
+
log("error", "supervisor.worker_process_error", { message: error.message });
|
|
192
|
+
};
|
|
193
|
+
handleWorkerExit = (code, signal) => {
|
|
194
|
+
const childPid = this.child?.pid;
|
|
195
|
+
this.detachChild();
|
|
196
|
+
if (this.shuttingDown) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
log("warn", "supervisor.worker_exit", {
|
|
200
|
+
pid: childPid,
|
|
201
|
+
code,
|
|
202
|
+
signal,
|
|
203
|
+
pendingRequests: this.pendingRequests.size
|
|
204
|
+
});
|
|
205
|
+
this.failPendingRequestsOnWorkerExit();
|
|
206
|
+
this.scheduleRestart();
|
|
207
|
+
};
|
|
208
|
+
handleWorkerMessage(message) {
|
|
209
|
+
debugSupervisor("worker_message", {
|
|
210
|
+
hasMethod: "method" in message,
|
|
211
|
+
method: "method" in message ? message.method : undefined,
|
|
212
|
+
id: "id" in message ? message.id : undefined,
|
|
213
|
+
replayingInitialization: this.replayingInitialization
|
|
214
|
+
});
|
|
215
|
+
if (this.isInitializationResponse(message)) {
|
|
216
|
+
const id = getTrackedRequestId(message);
|
|
217
|
+
if (id !== undefined) {
|
|
218
|
+
this.pendingRequests.delete(requestKey(id));
|
|
219
|
+
}
|
|
220
|
+
if (this.replayingInitialization) {
|
|
221
|
+
this.replayingInitialization = false;
|
|
222
|
+
if (this.initializedNotification) {
|
|
223
|
+
this.forwardToWorker(this.initializedNotification);
|
|
224
|
+
}
|
|
225
|
+
this.childReady = true;
|
|
226
|
+
this.flushQueue();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this.clientInitialized = true;
|
|
230
|
+
this.childReady = true;
|
|
231
|
+
this.writeToClient(message);
|
|
232
|
+
this.flushQueue();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (isResponse(message)) {
|
|
236
|
+
const id = getTrackedRequestId(message);
|
|
237
|
+
if (id !== undefined) {
|
|
238
|
+
this.pendingRequests.delete(requestKey(id));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
this.writeToClient(message);
|
|
242
|
+
}
|
|
243
|
+
handleWorkerReady() {
|
|
244
|
+
debugSupervisor("worker_ready", {
|
|
245
|
+
hasInitializeRequest: this.initializeRequest !== undefined,
|
|
246
|
+
clientInitialized: this.clientInitialized
|
|
247
|
+
});
|
|
248
|
+
if (!this.initializeRequest) {
|
|
249
|
+
this.childReady = true;
|
|
250
|
+
this.flushQueue();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.replayingInitialization = this.clientInitialized;
|
|
254
|
+
this.forwardToWorker(this.initializeRequest);
|
|
255
|
+
}
|
|
256
|
+
isInitializationResponse(message) {
|
|
257
|
+
const id = isResponse(message) ? getTrackedRequestId(message) : undefined;
|
|
258
|
+
const initializeId = this.initializeRequest
|
|
259
|
+
? getTrackedRequestId(this.initializeRequest)
|
|
260
|
+
: undefined;
|
|
261
|
+
return (id !== undefined &&
|
|
262
|
+
initializeId !== undefined &&
|
|
263
|
+
requestKey(id) === requestKey(initializeId));
|
|
264
|
+
}
|
|
265
|
+
flushQueue() {
|
|
266
|
+
if (!this.childReady || this.queuedMessages.length === 0) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const pending = this.queuedMessages.splice(0, this.queuedMessages.length);
|
|
270
|
+
for (const message of pending) {
|
|
271
|
+
if (this.initializeSentToWorker &&
|
|
272
|
+
isRequest(message) &&
|
|
273
|
+
message.method === "initialize" &&
|
|
274
|
+
this.initializeRequest !== undefined &&
|
|
275
|
+
getTrackedRequestId(message) === getTrackedRequestId(this.initializeRequest)) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
this.forwardToWorker(message);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
failPendingRequestsOnWorkerExit() {
|
|
282
|
+
const preservedInitializeKey = this.initializeRequest && !this.clientInitialized
|
|
283
|
+
? requestKey(this.initializeRequest.id)
|
|
284
|
+
: undefined;
|
|
285
|
+
for (const [key, pending] of [...this.pendingRequests.entries()]) {
|
|
286
|
+
if (key === preservedInitializeKey) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
this.pendingRequests.delete(key);
|
|
290
|
+
this.writeToClient(buildWorkerRestartError(pending.id));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
writeToClient(message) {
|
|
294
|
+
debugSupervisor("write_to_client", {
|
|
295
|
+
hasMethod: "method" in message,
|
|
296
|
+
method: "method" in message ? message.method : undefined,
|
|
297
|
+
id: "id" in message ? message.id : undefined,
|
|
298
|
+
clientMode: this.clientMode
|
|
299
|
+
});
|
|
300
|
+
const frame = encodeJsonRpcMessage(message, this.clientMode);
|
|
301
|
+
process.stdout.write(frame);
|
|
302
|
+
}
|
|
303
|
+
scheduleRestart() {
|
|
304
|
+
if (this.restartTimer || this.shuttingDown) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
this.restartTimer = setTimeout(() => {
|
|
308
|
+
this.restartTimer = undefined;
|
|
309
|
+
this.spawnWorker();
|
|
310
|
+
}, 100);
|
|
311
|
+
}
|
|
312
|
+
detachChild() {
|
|
313
|
+
const child = this.child;
|
|
314
|
+
if (!child) {
|
|
315
|
+
this.childReady = false;
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
child.stdout.off("data", this.handleWorkerData);
|
|
319
|
+
child.stderr.off("data", this.handleWorkerStderr);
|
|
320
|
+
child.stdin.off("error", this.handleWorkerStdinError);
|
|
321
|
+
child.off("error", this.handleWorkerProcessError);
|
|
322
|
+
child.off("exit", this.handleWorkerExit);
|
|
323
|
+
this.child = undefined;
|
|
324
|
+
this.childReady = false;
|
|
325
|
+
}
|
|
326
|
+
async shutdown() {
|
|
327
|
+
if (this.shuttingDown) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.shuttingDown = true;
|
|
331
|
+
if (this.restartTimer) {
|
|
332
|
+
clearTimeout(this.restartTimer);
|
|
333
|
+
this.restartTimer = undefined;
|
|
334
|
+
}
|
|
335
|
+
process.stdin.off("data", this.handleClientData);
|
|
336
|
+
process.stdin.off("error", this.handleClientError);
|
|
337
|
+
process.stdin.off("end", this.handleClientClosed);
|
|
338
|
+
process.stdin.off("close", this.handleClientClosed);
|
|
339
|
+
process.off("SIGINT", this.handleTerminateSignal);
|
|
340
|
+
process.off("SIGTERM", this.handleTerminateSignal);
|
|
341
|
+
const child = this.child;
|
|
342
|
+
this.detachChild();
|
|
343
|
+
if (child && child.exitCode === null) {
|
|
344
|
+
child.kill("SIGTERM");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
export const STDIO_WORKER_MODE_ENV = WORKER_MODE_ENV;
|
|
349
|
+
//# sourceMappingURL=stdio-supervisor.js.map
|
|
@@ -33,12 +33,6 @@ export interface SearchFileCandidateResult {
|
|
|
33
33
|
export interface SearchFilesWithContentResult extends SearchFilesResult {
|
|
34
34
|
content: string;
|
|
35
35
|
}
|
|
36
|
-
export interface FileContentPrefixRow {
|
|
37
|
-
artifactId: string;
|
|
38
|
-
filePath: string;
|
|
39
|
-
contentPrefix: string;
|
|
40
|
-
truncated: boolean;
|
|
41
|
-
}
|
|
42
36
|
export interface ListFileRowsOptions {
|
|
43
37
|
limit: number;
|
|
44
38
|
cursor?: string;
|
|
@@ -56,7 +50,7 @@ export declare class FilesRepo {
|
|
|
56
50
|
private readonly searchPathStmt;
|
|
57
51
|
private readonly searchFtsStmt;
|
|
58
52
|
private readonly getByPathsStmtCache;
|
|
59
|
-
private readonly
|
|
53
|
+
private readonly classLookupStmtCache;
|
|
60
54
|
constructor(db: SqliteDatabase);
|
|
61
55
|
clearFilesForArtifact(artifactId: string): void;
|
|
62
56
|
insertFilesForArtifact(artifactId: string, files: IndexedFile[]): void;
|
|
@@ -66,7 +60,6 @@ export declare class FilesRepo {
|
|
|
66
60
|
listFiles(artifactId: string, options: ListFilesOptions): PagedResult<string>;
|
|
67
61
|
listFileRows(artifactId: string, options: ListFileRowsOptions): PagedResult<FileRow>;
|
|
68
62
|
getFileContentsByPaths(artifactId: string, filePaths: string[]): FileRow[];
|
|
69
|
-
getFileContentPrefixesByPaths(artifactId: string, filePaths: string[], maxChars: number): FileContentPrefixRow[];
|
|
70
63
|
searchFileCandidates(artifactId: string, options: SearchFilesOptions): PagedResult<SearchFileCandidateResult> & {
|
|
71
64
|
scannedRows: number;
|
|
72
65
|
dbRoundtrips: number;
|
|
@@ -78,10 +71,11 @@ export declare class FilesRepo {
|
|
|
78
71
|
countTextCandidates(artifactId: string, query: string): number;
|
|
79
72
|
countPathCandidates(artifactId: string, query: string): number;
|
|
80
73
|
findFirstFilePathByName(artifactId: string, fileName: string): string | undefined;
|
|
74
|
+
private getClassLookupStmt;
|
|
75
|
+
findBestClassLookupPath(artifactId: string, exactFilePaths: string[], qualifiedClassName: string, simpleName: string, expectedPrefix: string): string | undefined;
|
|
81
76
|
searchFiles(artifactId: string, options: SearchFilesOptions): PagedResult<SearchFilesResult>;
|
|
82
77
|
totalContentBytes(): number;
|
|
83
78
|
contentBytesForArtifact(artifactId: string): number;
|
|
84
79
|
private getFileContentsByPathsStmt;
|
|
85
|
-
private getFileContentPrefixesByPathsStmt;
|
|
86
80
|
}
|
|
87
81
|
export {};
|
|
@@ -114,7 +114,7 @@ export class FilesRepo {
|
|
|
114
114
|
searchPathStmt;
|
|
115
115
|
searchFtsStmt;
|
|
116
116
|
getByPathsStmtCache = new Map();
|
|
117
|
-
|
|
117
|
+
classLookupStmtCache = new Map();
|
|
118
118
|
constructor(db) {
|
|
119
119
|
this.db = db;
|
|
120
120
|
this.deleteStmt = this.db.prepare(`
|
|
@@ -250,27 +250,6 @@ export class FilesRepo {
|
|
|
250
250
|
]));
|
|
251
251
|
return uniquePaths.map((path) => byPath.get(path)).filter((row) => row != null);
|
|
252
252
|
}
|
|
253
|
-
getFileContentPrefixesByPaths(artifactId, filePaths, maxChars) {
|
|
254
|
-
if (filePaths.length === 0) {
|
|
255
|
-
return [];
|
|
256
|
-
}
|
|
257
|
-
const normalizedMaxChars = Math.max(1, Math.trunc(maxChars));
|
|
258
|
-
const uniquePaths = [...new Set(filePaths)];
|
|
259
|
-
const stmt = this.getFileContentPrefixesByPathsStmt(uniquePaths.length, normalizedMaxChars);
|
|
260
|
-
const rows = stmt.all(normalizedMaxChars, artifactId, ...uniquePaths);
|
|
261
|
-
const byPath = new Map(rows.map((row) => [
|
|
262
|
-
row.file_path,
|
|
263
|
-
{
|
|
264
|
-
artifactId: row.artifact_id,
|
|
265
|
-
filePath: row.file_path,
|
|
266
|
-
contentPrefix: row.content_prefix,
|
|
267
|
-
truncated: row.content_length > row.content_prefix.length
|
|
268
|
-
}
|
|
269
|
-
]));
|
|
270
|
-
return uniquePaths
|
|
271
|
-
.map((path) => byPath.get(path))
|
|
272
|
-
.filter((row) => row != null);
|
|
273
|
-
}
|
|
274
253
|
searchFileCandidates(artifactId, options) {
|
|
275
254
|
const normalized = options.query.trim();
|
|
276
255
|
if (!normalized) {
|
|
@@ -438,6 +417,65 @@ export class FilesRepo {
|
|
|
438
417
|
.get(artifactId, normalized, `%/${normalized}`);
|
|
439
418
|
return row?.file_path;
|
|
440
419
|
}
|
|
420
|
+
getClassLookupStmt(exactPathCount) {
|
|
421
|
+
const cached = this.classLookupStmtCache.get(exactPathCount);
|
|
422
|
+
if (cached) {
|
|
423
|
+
return cached;
|
|
424
|
+
}
|
|
425
|
+
const exactPlaceholders = Array.from({ length: exactPathCount }, () => "?").join(", ");
|
|
426
|
+
const statement = this.db.prepare(`
|
|
427
|
+
WITH ranked_candidates AS (
|
|
428
|
+
SELECT file_path, 0 AS rank
|
|
429
|
+
FROM files
|
|
430
|
+
WHERE artifact_id = ?
|
|
431
|
+
AND file_path IN (${exactPlaceholders})
|
|
432
|
+
|
|
433
|
+
UNION ALL
|
|
434
|
+
|
|
435
|
+
SELECT file_path,
|
|
436
|
+
CASE
|
|
437
|
+
WHEN qualified_name = ? THEN 1
|
|
438
|
+
WHEN symbol_name = ? THEN 2
|
|
439
|
+
ELSE 3
|
|
440
|
+
END AS rank
|
|
441
|
+
FROM symbols
|
|
442
|
+
WHERE artifact_id = ?
|
|
443
|
+
AND symbol_kind = 'class'
|
|
444
|
+
AND (
|
|
445
|
+
qualified_name = ?
|
|
446
|
+
OR symbol_name = ?
|
|
447
|
+
OR qualified_name LIKE ?
|
|
448
|
+
)
|
|
449
|
+
AND (? = '' OR instr(file_path, ?) = 1)
|
|
450
|
+
|
|
451
|
+
UNION ALL
|
|
452
|
+
|
|
453
|
+
SELECT file_path, 4 AS rank
|
|
454
|
+
FROM files
|
|
455
|
+
WHERE artifact_id = ?
|
|
456
|
+
AND (file_path = ? OR file_path LIKE ? ESCAPE '\\')
|
|
457
|
+
AND (? = '' OR instr(file_path, ?) = 1)
|
|
458
|
+
)
|
|
459
|
+
SELECT file_path
|
|
460
|
+
FROM ranked_candidates
|
|
461
|
+
ORDER BY rank ASC, file_path ASC
|
|
462
|
+
LIMIT 1
|
|
463
|
+
`);
|
|
464
|
+
this.classLookupStmtCache.set(exactPathCount, statement);
|
|
465
|
+
return statement;
|
|
466
|
+
}
|
|
467
|
+
findBestClassLookupPath(artifactId, exactFilePaths, qualifiedClassName, simpleName, expectedPrefix) {
|
|
468
|
+
const normalizedExactFilePaths = [...new Set(exactFilePaths.map((entry) => entry.trim()).filter(Boolean))];
|
|
469
|
+
const normalizedClassName = qualifiedClassName.trim();
|
|
470
|
+
const normalizedSimpleName = simpleName.trim();
|
|
471
|
+
if (normalizedExactFilePaths.length === 0 || !normalizedClassName || !normalizedSimpleName) {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
const stmt = this.getClassLookupStmt(normalizedExactFilePaths.length);
|
|
475
|
+
const fileName = `${normalizedSimpleName}.java`;
|
|
476
|
+
const row = stmt.get(artifactId, ...normalizedExactFilePaths, normalizedClassName, normalizedSimpleName, artifactId, normalizedClassName, normalizedSimpleName, `%.${normalizedSimpleName}`, expectedPrefix, expectedPrefix, artifactId, fileName, `%/${fileName}`, expectedPrefix, expectedPrefix);
|
|
477
|
+
return row?.file_path;
|
|
478
|
+
}
|
|
441
479
|
searchFiles(artifactId, options) {
|
|
442
480
|
const page = this.searchFilesWithContent(artifactId, options);
|
|
443
481
|
return {
|
|
@@ -468,10 +506,15 @@ export class FilesRepo {
|
|
|
468
506
|
const normalizedCount = Math.max(1, Math.trunc(pathCount));
|
|
469
507
|
const cached = this.getByPathsStmtCache.get(normalizedCount);
|
|
470
508
|
if (cached) {
|
|
509
|
+
this.getByPathsStmtCache.delete(normalizedCount);
|
|
510
|
+
this.getByPathsStmtCache.set(normalizedCount, cached);
|
|
471
511
|
return cached;
|
|
472
512
|
}
|
|
473
513
|
if (this.getByPathsStmtCache.size >= 64) {
|
|
474
|
-
this.getByPathsStmtCache.
|
|
514
|
+
const oldestKey = this.getByPathsStmtCache.keys().next().value;
|
|
515
|
+
if (oldestKey !== undefined) {
|
|
516
|
+
this.getByPathsStmtCache.delete(oldestKey);
|
|
517
|
+
}
|
|
475
518
|
}
|
|
476
519
|
const placeholders = Array.from({ length: normalizedCount }, () => "?").join(", ");
|
|
477
520
|
const stmt = this.db.prepare(`
|
|
@@ -482,25 +525,5 @@ export class FilesRepo {
|
|
|
482
525
|
this.getByPathsStmtCache.set(normalizedCount, stmt);
|
|
483
526
|
return stmt;
|
|
484
527
|
}
|
|
485
|
-
getFileContentPrefixesByPathsStmt(pathCount, maxChars) {
|
|
486
|
-
const normalizedCount = Math.max(1, Math.trunc(pathCount));
|
|
487
|
-
const normalizedMaxChars = Math.max(1, Math.trunc(maxChars));
|
|
488
|
-
const cacheKey = `${normalizedCount}:${normalizedMaxChars}`;
|
|
489
|
-
const cached = this.getPrefixByPathsStmtCache.get(cacheKey);
|
|
490
|
-
if (cached) {
|
|
491
|
-
return cached;
|
|
492
|
-
}
|
|
493
|
-
if (this.getPrefixByPathsStmtCache.size >= 128) {
|
|
494
|
-
this.getPrefixByPathsStmtCache.clear();
|
|
495
|
-
}
|
|
496
|
-
const placeholders = Array.from({ length: normalizedCount }, () => "?").join(", ");
|
|
497
|
-
const stmt = this.db.prepare(`
|
|
498
|
-
SELECT artifact_id, file_path, substr(content, 1, ?) AS content_prefix, length(content) AS content_length
|
|
499
|
-
FROM files
|
|
500
|
-
WHERE artifact_id = ? AND file_path IN (${placeholders})
|
|
501
|
-
`);
|
|
502
|
-
this.getPrefixByPathsStmtCache.set(cacheKey, stmt);
|
|
503
|
-
return stmt;
|
|
504
|
-
}
|
|
505
528
|
}
|
|
506
529
|
//# sourceMappingURL=files-repo.js.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const CLASS_DECLARATION = /^(?:\s*@[\w.]+\s+)*(?:\s*(?:public|private|protected|abstract|final|sealed|non-sealed|static)\s+)*\s*(class|interface|enum|record)\s+([A-Za-z_$][\w$]*)/;
|
|
2
2
|
const METHOD_DECLARATION = /^(?:\s*@[\w.]+\s+)*[^{;]*?\b([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*(?:\{|;)/;
|
|
3
3
|
const FIELD_DECLARATION = /^(?:\s*@[\w.]+\s+)*[^\s][\w<>\[\],.?]+\s+([A-Za-z_$][\w$]*)\s*(?:=|;|,)/;
|
|
4
|
+
const NOISE_TOKENS = new Set(["if", "for", "while", "switch", "catch", "return", "new", "throw"]);
|
|
4
5
|
function normalizeLine(line) {
|
|
5
6
|
return line.replace(/\s+/g, " ").trim();
|
|
6
7
|
}
|
|
7
8
|
function isNoiseToken(token) {
|
|
8
|
-
return
|
|
9
|
+
return NOISE_TOKENS.has(token);
|
|
9
10
|
}
|
|
10
11
|
function lineIndexToLine(lineNo) {
|
|
11
12
|
return lineNo + 1;
|
|
@@ -13,6 +14,7 @@ function lineIndexToLine(lineNo) {
|
|
|
13
14
|
export function extractSymbolsFromSource(filePath, content) {
|
|
14
15
|
const lines = content.split(/\r?\n/);
|
|
15
16
|
const symbols = [];
|
|
17
|
+
const qualifiedName = filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
16
18
|
for (let index = 0; index < lines.length; index += 1) {
|
|
17
19
|
const rawLine = lines[index] ?? "";
|
|
18
20
|
const line = normalizeLine(rawLine);
|
|
@@ -27,7 +29,7 @@ export function extractSymbolsFromSource(filePath, content) {
|
|
|
27
29
|
symbols.push({
|
|
28
30
|
symbolKind,
|
|
29
31
|
symbolName,
|
|
30
|
-
qualifiedName
|
|
32
|
+
qualifiedName,
|
|
31
33
|
line: lineIndexToLine(index)
|
|
32
34
|
});
|
|
33
35
|
}
|
|
@@ -40,7 +42,7 @@ export function extractSymbolsFromSource(filePath, content) {
|
|
|
40
42
|
symbols.push({
|
|
41
43
|
symbolKind: "method",
|
|
42
44
|
symbolName,
|
|
43
|
-
qualifiedName
|
|
45
|
+
qualifiedName,
|
|
44
46
|
line: lineIndexToLine(index)
|
|
45
47
|
});
|
|
46
48
|
}
|
|
@@ -53,7 +55,7 @@ export function extractSymbolsFromSource(filePath, content) {
|
|
|
53
55
|
symbols.push({
|
|
54
56
|
symbolKind: "field",
|
|
55
57
|
symbolName,
|
|
56
|
-
qualifiedName
|
|
58
|
+
qualifiedName,
|
|
57
59
|
line: lineIndexToLine(index)
|
|
58
60
|
});
|
|
59
61
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type GateOptions = {
|
|
2
|
+
maxConcurrent: number;
|
|
3
|
+
maxQueue: number;
|
|
4
|
+
};
|
|
5
|
+
export declare class ToolExecutionGate {
|
|
6
|
+
private readonly maxConcurrent;
|
|
7
|
+
private readonly maxQueue;
|
|
8
|
+
private activeCount;
|
|
9
|
+
private readonly queue;
|
|
10
|
+
constructor(options?: Partial<GateOptions>);
|
|
11
|
+
run<T>(tool: string, task: () => Promise<T>): Promise<T>;
|
|
12
|
+
private execute;
|
|
13
|
+
private drain;
|
|
14
|
+
}
|
|
15
|
+
export {};
|