@adhisang/minecraft-modding-mcp 2.0.0 → 3.0.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +139 -30
  3. package/dist/cache-registry.d.ts +95 -0
  4. package/dist/cache-registry.js +541 -0
  5. package/dist/cli.js +31 -4
  6. package/dist/compat-stdio-transport.d.ts +2 -7
  7. package/dist/compat-stdio-transport.js +12 -154
  8. package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
  9. package/dist/entry-tools/analyze-mod-service.js +253 -0
  10. package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
  11. package/dist/entry-tools/analyze-symbol-service.js +304 -0
  12. package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
  13. package/dist/entry-tools/compare-minecraft-service.js +397 -0
  14. package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
  15. package/dist/entry-tools/entry-tool-schema.js +10 -0
  16. package/dist/entry-tools/inspect-minecraft-service.d.ts +1953 -0
  17. package/dist/entry-tools/inspect-minecraft-service.js +876 -0
  18. package/dist/entry-tools/manage-cache-service.d.ts +130 -0
  19. package/dist/entry-tools/manage-cache-service.js +229 -0
  20. package/dist/entry-tools/request-normalizers.d.ts +10 -0
  21. package/dist/entry-tools/request-normalizers.js +36 -0
  22. package/dist/entry-tools/response-contract.d.ts +44 -0
  23. package/dist/entry-tools/response-contract.js +96 -0
  24. package/dist/entry-tools/validate-project-service.d.ts +543 -0
  25. package/dist/entry-tools/validate-project-service.js +381 -0
  26. package/dist/index.js +495 -42
  27. package/dist/json-rpc-framing.d.ts +22 -0
  28. package/dist/json-rpc-framing.js +168 -0
  29. package/dist/mapping-pipeline-service.js +9 -1
  30. package/dist/mapping-service.d.ts +9 -0
  31. package/dist/mapping-service.js +183 -60
  32. package/dist/minecraft-explorer-service.d.ts +0 -1
  33. package/dist/minecraft-explorer-service.js +119 -23
  34. package/dist/mixin-validator.d.ts +24 -2
  35. package/dist/mixin-validator.js +223 -98
  36. package/dist/mod-decompile-service.d.ts +5 -0
  37. package/dist/mod-decompile-service.js +40 -5
  38. package/dist/mod-remap-service.js +142 -30
  39. package/dist/path-resolver.js +41 -4
  40. package/dist/registry-service.d.ts +10 -1
  41. package/dist/registry-service.js +154 -22
  42. package/dist/search-hit-accumulator.js +23 -2
  43. package/dist/source-jar-reader.js +16 -2
  44. package/dist/source-resolver.js +6 -7
  45. package/dist/source-service.d.ts +42 -4
  46. package/dist/source-service.js +781 -127
  47. package/dist/stdio-supervisor.d.ts +46 -0
  48. package/dist/stdio-supervisor.js +349 -0
  49. package/dist/storage/files-repo.d.ts +3 -9
  50. package/dist/storage/files-repo.js +66 -43
  51. package/dist/symbols/symbol-extractor.js +6 -4
  52. package/dist/tool-execution-gate.d.ts +15 -0
  53. package/dist/tool-execution-gate.js +58 -0
  54. package/dist/version-diff-service.js +10 -5
  55. package/dist/version-service.js +7 -2
  56. package/dist/workspace-mapping-service.js +12 -0
  57. 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 getPrefixByPathsStmtCache;
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
- getPrefixByPathsStmtCache = new Map();
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.clear();
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 new Set(["if", "for", "while", "switch", "catch", "return", "new", "throw"]).has(token);
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: filePath.replace(/\.java$/, "").replaceAll("/", "."),
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: filePath.replace(/\.java$/, "").replaceAll("/", "."),
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: filePath.replace(/\.java$/, "").replaceAll("/", "."),
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 {};