@adhisang/minecraft-modding-mcp 1.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 (106) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/dist/access-widener-parser.d.ts +24 -0
  5. package/dist/access-widener-parser.js +77 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +4 -0
  8. package/dist/config.d.ts +27 -0
  9. package/dist/config.js +178 -0
  10. package/dist/decompiler/vineflower.d.ts +15 -0
  11. package/dist/decompiler/vineflower.js +185 -0
  12. package/dist/errors.d.ts +50 -0
  13. package/dist/errors.js +49 -0
  14. package/dist/hash.d.ts +1 -0
  15. package/dist/hash.js +12 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.js +1447 -0
  18. package/dist/java-process.d.ts +16 -0
  19. package/dist/java-process.js +120 -0
  20. package/dist/logger.d.ts +3 -0
  21. package/dist/logger.js +21 -0
  22. package/dist/mapping-pipeline-service.d.ts +18 -0
  23. package/dist/mapping-pipeline-service.js +60 -0
  24. package/dist/mapping-service.d.ts +161 -0
  25. package/dist/mapping-service.js +1706 -0
  26. package/dist/maven-resolver.d.ts +22 -0
  27. package/dist/maven-resolver.js +122 -0
  28. package/dist/minecraft-explorer-service.d.ts +43 -0
  29. package/dist/minecraft-explorer-service.js +562 -0
  30. package/dist/mixin-parser.d.ts +34 -0
  31. package/dist/mixin-parser.js +194 -0
  32. package/dist/mixin-validator.d.ts +59 -0
  33. package/dist/mixin-validator.js +274 -0
  34. package/dist/mod-analyzer.d.ts +23 -0
  35. package/dist/mod-analyzer.js +346 -0
  36. package/dist/mod-decompile-service.d.ts +39 -0
  37. package/dist/mod-decompile-service.js +136 -0
  38. package/dist/mod-remap-service.d.ts +17 -0
  39. package/dist/mod-remap-service.js +186 -0
  40. package/dist/mod-search-service.d.ts +28 -0
  41. package/dist/mod-search-service.js +174 -0
  42. package/dist/mojang-tiny-mapping-service.d.ts +13 -0
  43. package/dist/mojang-tiny-mapping-service.js +351 -0
  44. package/dist/nbt/java-nbt-codec.d.ts +3 -0
  45. package/dist/nbt/java-nbt-codec.js +385 -0
  46. package/dist/nbt/json-patch.d.ts +3 -0
  47. package/dist/nbt/json-patch.js +352 -0
  48. package/dist/nbt/pipeline.d.ts +39 -0
  49. package/dist/nbt/pipeline.js +173 -0
  50. package/dist/nbt/typed-json.d.ts +10 -0
  51. package/dist/nbt/typed-json.js +205 -0
  52. package/dist/nbt/types.d.ts +66 -0
  53. package/dist/nbt/types.js +2 -0
  54. package/dist/observability.d.ts +88 -0
  55. package/dist/observability.js +165 -0
  56. package/dist/path-converter.d.ts +12 -0
  57. package/dist/path-converter.js +161 -0
  58. package/dist/path-resolver.d.ts +19 -0
  59. package/dist/path-resolver.js +78 -0
  60. package/dist/registry-service.d.ts +29 -0
  61. package/dist/registry-service.js +214 -0
  62. package/dist/repo-downloader.d.ts +15 -0
  63. package/dist/repo-downloader.js +111 -0
  64. package/dist/resources.d.ts +3 -0
  65. package/dist/resources.js +154 -0
  66. package/dist/search-hit-accumulator.d.ts +38 -0
  67. package/dist/search-hit-accumulator.js +153 -0
  68. package/dist/source-jar-reader.d.ts +13 -0
  69. package/dist/source-jar-reader.js +216 -0
  70. package/dist/source-resolver.d.ts +14 -0
  71. package/dist/source-resolver.js +274 -0
  72. package/dist/source-service.d.ts +404 -0
  73. package/dist/source-service.js +2881 -0
  74. package/dist/storage/artifacts-repo.d.ts +45 -0
  75. package/dist/storage/artifacts-repo.js +209 -0
  76. package/dist/storage/db.d.ts +14 -0
  77. package/dist/storage/db.js +132 -0
  78. package/dist/storage/files-repo.d.ts +78 -0
  79. package/dist/storage/files-repo.js +437 -0
  80. package/dist/storage/index-meta-repo.d.ts +35 -0
  81. package/dist/storage/index-meta-repo.js +97 -0
  82. package/dist/storage/migrations.d.ts +11 -0
  83. package/dist/storage/migrations.js +71 -0
  84. package/dist/storage/schema.d.ts +1 -0
  85. package/dist/storage/schema.js +160 -0
  86. package/dist/storage/sqlite.d.ts +20 -0
  87. package/dist/storage/sqlite.js +111 -0
  88. package/dist/storage/symbols-repo.d.ts +63 -0
  89. package/dist/storage/symbols-repo.js +401 -0
  90. package/dist/symbols/symbol-extractor.d.ts +7 -0
  91. package/dist/symbols/symbol-extractor.js +64 -0
  92. package/dist/tiny-remapper-resolver.d.ts +1 -0
  93. package/dist/tiny-remapper-resolver.js +62 -0
  94. package/dist/tiny-remapper-service.d.ts +16 -0
  95. package/dist/tiny-remapper-service.js +73 -0
  96. package/dist/types.d.ts +120 -0
  97. package/dist/types.js +2 -0
  98. package/dist/version-diff-service.d.ts +41 -0
  99. package/dist/version-diff-service.js +222 -0
  100. package/dist/version-service.d.ts +70 -0
  101. package/dist/version-service.js +411 -0
  102. package/dist/vineflower-resolver.d.ts +1 -0
  103. package/dist/vineflower-resolver.js +62 -0
  104. package/dist/workspace-mapping-service.d.ts +18 -0
  105. package/dist/workspace-mapping-service.js +89 -0
  106. package/package.json +61 -0
@@ -0,0 +1,401 @@
1
+ import { log } from "../logger.js";
2
+ function toSymbolRow(artifactId, item) {
3
+ return {
4
+ artifactId,
5
+ filePath: item.filePath,
6
+ symbolKind: item.symbolKind,
7
+ symbolName: item.symbolName,
8
+ qualifiedName: item.qualifiedName,
9
+ line: item.line
10
+ };
11
+ }
12
+ function encodeCursor(symbolName, filePath, line) {
13
+ return Buffer.from(JSON.stringify({ symbolName, filePath, line }), "utf8").toString("base64");
14
+ }
15
+ function decodeCursor(cursor) {
16
+ if (!cursor) {
17
+ return undefined;
18
+ }
19
+ try {
20
+ const decoded = Buffer.from(cursor, "base64").toString("utf8");
21
+ const parsed = JSON.parse(decoded);
22
+ if (typeof parsed.symbolName !== "string" ||
23
+ typeof parsed.filePath !== "string" ||
24
+ typeof parsed.line !== "number") {
25
+ return undefined;
26
+ }
27
+ return parsed;
28
+ }
29
+ catch {
30
+ log("warn", "storage.symbols.invalid_cursor", { cursor });
31
+ return undefined;
32
+ }
33
+ }
34
+ function isAfterCursor(row, cursor) {
35
+ const symbolCompare = row.symbol_name.localeCompare(cursor.symbolName);
36
+ if (symbolCompare > 0) {
37
+ return true;
38
+ }
39
+ if (symbolCompare < 0) {
40
+ return false;
41
+ }
42
+ const fileCompare = row.file_path.localeCompare(cursor.filePath);
43
+ if (fileCompare > 0) {
44
+ return true;
45
+ }
46
+ if (fileCompare < 0) {
47
+ return false;
48
+ }
49
+ return row.line > cursor.line;
50
+ }
51
+ function escapeLikeLiteral(value) {
52
+ return value.replace(/[\\%_]/g, "\\$&");
53
+ }
54
+ export class SymbolsRepo {
55
+ db;
56
+ deleteStmt;
57
+ insertStmt;
58
+ searchStmt;
59
+ searchExactStmt;
60
+ listByArtifactStmt;
61
+ listByArtifactKindStmt;
62
+ listByFileStmt;
63
+ constructor(db) {
64
+ this.db = db;
65
+ this.deleteStmt = this.db.prepare(`
66
+ DELETE FROM symbols WHERE artifact_id = ?
67
+ `);
68
+ this.insertStmt = this.db.prepare(`
69
+ INSERT INTO symbols (artifact_id, file_path, symbol_kind, symbol_name, qualified_name, line)
70
+ VALUES (?, ?, ?, ?, ?, ?)
71
+ `);
72
+ this.searchStmt = this.db.prepare(`
73
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
74
+ FROM symbols
75
+ WHERE artifact_id = ?
76
+ AND (symbol_kind = ? OR ? = '')
77
+ AND (symbol_name LIKE ? ESCAPE '\\')
78
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
79
+ LIMIT ?
80
+ `);
81
+ this.searchExactStmt = this.db.prepare(`
82
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
83
+ FROM symbols
84
+ WHERE artifact_id = ?
85
+ AND (symbol_kind = ? OR ? = '')
86
+ AND symbol_name = ?
87
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
88
+ LIMIT ?
89
+ `);
90
+ this.listByArtifactStmt = this.db.prepare(`
91
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
92
+ FROM symbols
93
+ WHERE artifact_id = ?
94
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
95
+ `);
96
+ this.listByArtifactKindStmt = this.db.prepare(`
97
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
98
+ FROM symbols
99
+ WHERE artifact_id = ? AND symbol_kind = ?
100
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
101
+ `);
102
+ this.listByFileStmt = this.db.prepare(`
103
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
104
+ FROM symbols
105
+ WHERE artifact_id = ? AND file_path = ?
106
+ ORDER BY line ASC, symbol_name ASC
107
+ `);
108
+ }
109
+ clearSymbolsForArtifact(artifactId) {
110
+ this.deleteStmt.run([artifactId]);
111
+ }
112
+ insertSymbolsForArtifact(artifactId, symbols) {
113
+ for (const item of symbols) {
114
+ this.insertStmt.run([
115
+ artifactId,
116
+ item.filePath,
117
+ item.symbolKind,
118
+ item.symbolName,
119
+ item.qualifiedName ?? null,
120
+ item.line
121
+ ]);
122
+ }
123
+ }
124
+ replaceSymbolsForArtifact(artifactId, symbols) {
125
+ const transaction = this.db.transaction(() => {
126
+ this.clearSymbolsForArtifact(artifactId);
127
+ this.insertSymbolsForArtifact(artifactId, symbols);
128
+ });
129
+ transaction();
130
+ }
131
+ findSymbols(options) {
132
+ const cursor = decodeCursor(options.cursor);
133
+ const symbolName = options.symbolNamePrefix ?? "";
134
+ const fetchLimit = options.limit + 1;
135
+ let rows;
136
+ if (cursor) {
137
+ const nameMatch = options.exact ? "AND symbol_name = ?" : "AND (symbol_name LIKE ? ESCAPE '\\')";
138
+ const sql = `
139
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
140
+ FROM symbols
141
+ WHERE artifact_id = ?
142
+ AND (symbol_kind = ? OR ? = '')
143
+ ${nameMatch}
144
+ AND (
145
+ symbol_name > ?
146
+ OR (symbol_name = ? AND file_path > ?)
147
+ OR (symbol_name = ? AND file_path = ? AND line > ?)
148
+ )
149
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
150
+ LIMIT ?
151
+ `;
152
+ const nameParam = options.exact ? symbolName : `${symbolName}%`;
153
+ rows = this.db.prepare(sql).all(options.artifactId, options.symbolKind ?? "", options.symbolKind ?? "", nameParam, cursor.symbolName, cursor.symbolName, cursor.filePath, cursor.symbolName, cursor.filePath, cursor.line, Math.max(1, fetchLimit));
154
+ }
155
+ else {
156
+ const queryRows = options.exact ? this.searchExactStmt : this.searchStmt;
157
+ rows = queryRows.all(options.artifactId, options.symbolKind ?? "", options.symbolKind ?? "", options.exact ? symbolName : `${symbolName}%`, Math.max(1, fetchLimit));
158
+ }
159
+ const page = rows.slice(0, options.limit);
160
+ const hasMore = rows.length > options.limit;
161
+ const nextCursor = page.length > 0 && hasMore
162
+ ? encodeCursor(page[page.length - 1].symbol_name, page[page.length - 1].file_path, page[page.length - 1].line)
163
+ : undefined;
164
+ return {
165
+ items: page.map((row) => toSymbolRow(options.artifactId, {
166
+ filePath: row.file_path,
167
+ symbolKind: row.symbol_kind,
168
+ symbolName: row.symbol_name,
169
+ qualifiedName: row.qualified_name ?? undefined,
170
+ line: row.line
171
+ })),
172
+ nextCursor
173
+ };
174
+ }
175
+ listSymbolsForArtifact(artifactId, symbolKind) {
176
+ const rows = (symbolKind
177
+ ? this.listByArtifactKindStmt.all(artifactId, symbolKind)
178
+ : this.listByArtifactStmt.all(artifactId));
179
+ return rows.map((row) => toSymbolRow(artifactId, {
180
+ filePath: row.file_path,
181
+ symbolKind: row.symbol_kind,
182
+ symbolName: row.symbol_name,
183
+ qualifiedName: row.qualified_name ?? undefined,
184
+ line: row.line
185
+ }));
186
+ }
187
+ listSymbolsForFile(artifactId, filePath) {
188
+ const rows = this.listByFileStmt.all(artifactId, filePath);
189
+ return rows.map((row) => toSymbolRow(artifactId, {
190
+ filePath: row.file_path,
191
+ symbolKind: row.symbol_kind,
192
+ symbolName: row.symbol_name,
193
+ qualifiedName: row.qualified_name ?? undefined,
194
+ line: row.line
195
+ }));
196
+ }
197
+ listSymbolsForFiles(artifactId, filePaths, symbolKind) {
198
+ const uniqueFilePaths = [...new Set(filePaths)];
199
+ if (uniqueFilePaths.length === 0) {
200
+ return new Map();
201
+ }
202
+ const placeholders = uniqueFilePaths.map(() => "?").join(", ");
203
+ const sql = `
204
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
205
+ FROM symbols
206
+ WHERE artifact_id = ?
207
+ AND file_path IN (${placeholders})
208
+ ${symbolKind ? "AND symbol_kind = ?" : ""}
209
+ ORDER BY file_path ASC, line ASC, symbol_name ASC
210
+ `;
211
+ const params = symbolKind ? [artifactId, ...uniqueFilePaths, symbolKind] : [artifactId, ...uniqueFilePaths];
212
+ const rows = this.db.prepare(sql).all(...params);
213
+ const byFile = new Map();
214
+ for (const row of rows) {
215
+ const bucket = byFile.get(row.file_path) ?? [];
216
+ bucket.push(toSymbolRow(artifactId, {
217
+ filePath: row.file_path,
218
+ symbolKind: row.symbol_kind,
219
+ symbolName: row.symbol_name,
220
+ qualifiedName: row.qualified_name ?? undefined,
221
+ line: row.line
222
+ }));
223
+ byFile.set(row.file_path, bucket);
224
+ }
225
+ return byFile;
226
+ }
227
+ findBySymbolNames(artifactId, symbolNames) {
228
+ const unique = [...new Set(symbolNames.map((name) => name.trim()).filter(Boolean))];
229
+ if (unique.length === 0) {
230
+ return [];
231
+ }
232
+ const placeholders = unique.map(() => "?").join(", ");
233
+ const rows = this.db
234
+ .prepare(`
235
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
236
+ FROM symbols
237
+ WHERE artifact_id = ?
238
+ AND lower(symbol_name) IN (${placeholders})
239
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
240
+ `)
241
+ .all(artifactId, ...unique.map((name) => name.toLowerCase()));
242
+ return rows.map((row) => toSymbolRow(artifactId, {
243
+ filePath: row.file_path,
244
+ symbolKind: row.symbol_kind,
245
+ symbolName: row.symbol_name,
246
+ qualifiedName: row.qualified_name ?? undefined,
247
+ line: row.line
248
+ }));
249
+ }
250
+ findScopedSymbols(options) {
251
+ const normalizedQuery = options.query.trim();
252
+ if (!normalizedQuery) {
253
+ return { items: [], nextCursor: undefined };
254
+ }
255
+ const normalizedLowerQuery = normalizedQuery.toLowerCase();
256
+ const escapedLowerQuery = escapeLikeLiteral(normalizedLowerQuery);
257
+ const params = [options.artifactId];
258
+ const where = [];
259
+ if (options.symbolKind) {
260
+ where.push("symbol_kind = ?");
261
+ params.push(options.symbolKind);
262
+ }
263
+ if (options.packagePrefix?.trim()) {
264
+ const normalizedPrefix = options.packagePrefix.replace(/\.+/g, "/").replace(/\/+$/, "");
265
+ where.push("file_path LIKE ? ESCAPE '\\'");
266
+ params.push(`${escapeLikeLiteral(normalizedPrefix)}/%`);
267
+ }
268
+ if (options.filePathLike?.trim()) {
269
+ where.push("file_path LIKE ? ESCAPE '\\'");
270
+ params.push(options.filePathLike);
271
+ }
272
+ if (options.match === "exact") {
273
+ where.push("symbol_name = ?");
274
+ params.push(normalizedQuery);
275
+ }
276
+ else if (options.match === "prefix") {
277
+ where.push("lower(symbol_name) LIKE ? ESCAPE '\\'");
278
+ params.push(`${escapedLowerQuery}%`);
279
+ }
280
+ else {
281
+ where.push("lower(symbol_name) LIKE ? ESCAPE '\\'");
282
+ params.push(`%${escapedLowerQuery}%`);
283
+ }
284
+ if (options.cursor) {
285
+ where.push(`(
286
+ symbol_name > ?
287
+ OR (symbol_name = ? AND file_path > ?)
288
+ OR (symbol_name = ? AND file_path = ? AND line > ?)
289
+ )`);
290
+ params.push(options.cursor.symbolName, options.cursor.symbolName, options.cursor.filePath, options.cursor.symbolName, options.cursor.filePath, options.cursor.line);
291
+ }
292
+ const rawLimit = Math.max(1, options.limit ?? 5000);
293
+ const fetchLimit = rawLimit + 1;
294
+ params.push(fetchLimit);
295
+ const sql = `
296
+ SELECT file_path, symbol_kind, symbol_name, qualified_name, line
297
+ FROM symbols
298
+ WHERE artifact_id = ?
299
+ ${where.length > 0 ? `AND ${where.join("\n AND ")}` : ""}
300
+ ORDER BY symbol_name ASC, file_path ASC, line ASC
301
+ LIMIT ?
302
+ `;
303
+ const rows = this.db.prepare(sql).all(...params);
304
+ const hasMore = rows.length > rawLimit;
305
+ const page = hasMore ? rows.slice(0, rawLimit) : rows;
306
+ const lastRow = page.length > 0 ? page[page.length - 1] : undefined;
307
+ const nextCursor = hasMore && lastRow
308
+ ? encodeCursor(lastRow.symbol_name, lastRow.file_path, lastRow.line)
309
+ : undefined;
310
+ return {
311
+ items: page.map((row) => toSymbolRow(options.artifactId, {
312
+ filePath: row.file_path,
313
+ symbolKind: row.symbol_kind,
314
+ symbolName: row.symbol_name,
315
+ qualifiedName: row.qualified_name ?? undefined,
316
+ line: row.line
317
+ })),
318
+ nextCursor
319
+ };
320
+ }
321
+ countScopedSymbols(options) {
322
+ const normalizedQuery = options.query.trim();
323
+ if (!normalizedQuery) {
324
+ return 0;
325
+ }
326
+ const normalizedLowerQuery = normalizedQuery.toLowerCase();
327
+ const escapedLowerQuery = escapeLikeLiteral(normalizedLowerQuery);
328
+ const params = [options.artifactId];
329
+ const where = [];
330
+ if (options.symbolKind) {
331
+ where.push("symbol_kind = ?");
332
+ params.push(options.symbolKind);
333
+ }
334
+ if (options.packagePrefix?.trim()) {
335
+ const normalizedPrefix = options.packagePrefix.replace(/\.+/g, "/").replace(/\/+$/, "");
336
+ where.push("file_path LIKE ? ESCAPE '\\'");
337
+ params.push(`${escapeLikeLiteral(normalizedPrefix)}/%`);
338
+ }
339
+ if (options.match === "exact") {
340
+ where.push("symbol_name = ?");
341
+ params.push(normalizedQuery);
342
+ }
343
+ else if (options.match === "prefix") {
344
+ where.push("lower(symbol_name) LIKE ? ESCAPE '\\'");
345
+ params.push(`${escapedLowerQuery}%`);
346
+ }
347
+ else {
348
+ where.push("lower(symbol_name) LIKE ? ESCAPE '\\'");
349
+ params.push(`%${escapedLowerQuery}%`);
350
+ }
351
+ const sql = `
352
+ SELECT COUNT(*) AS cnt
353
+ FROM symbols
354
+ WHERE artifact_id = ?
355
+ ${where.length > 0 ? `AND ${where.join("\n AND ")}` : ""}
356
+ `;
357
+ const row = this.db.prepare(sql).get(...params);
358
+ return row?.cnt ?? 0;
359
+ }
360
+ listDistinctFilePathsByKind(artifactId, symbolKind) {
361
+ const rows = this.db
362
+ .prepare(`
363
+ SELECT DISTINCT file_path
364
+ FROM symbols
365
+ WHERE artifact_id = ? AND symbol_kind = ?
366
+ ORDER BY file_path ASC
367
+ `)
368
+ .all(artifactId, symbolKind);
369
+ return rows.map((row) => row.file_path);
370
+ }
371
+ findBestClassFilePath(artifactId, className, simpleName) {
372
+ const normalizedClassName = className.trim();
373
+ const normalizedSimpleName = simpleName.trim();
374
+ if (!normalizedClassName || !normalizedSimpleName) {
375
+ return undefined;
376
+ }
377
+ const row = this.db
378
+ .prepare(`
379
+ SELECT file_path
380
+ FROM symbols
381
+ WHERE artifact_id = ?
382
+ AND symbol_kind = 'class'
383
+ AND (
384
+ qualified_name = ?
385
+ OR symbol_name = ?
386
+ OR qualified_name LIKE ?
387
+ )
388
+ ORDER BY
389
+ CASE
390
+ WHEN qualified_name = ? THEN 0
391
+ WHEN symbol_name = ? THEN 1
392
+ ELSE 2
393
+ END,
394
+ file_path ASC
395
+ LIMIT 1
396
+ `)
397
+ .get(artifactId, normalizedClassName, normalizedSimpleName, `%.${normalizedSimpleName}`, normalizedClassName, normalizedSimpleName);
398
+ return row?.file_path;
399
+ }
400
+ }
401
+ //# sourceMappingURL=symbols-repo.js.map
@@ -0,0 +1,7 @@
1
+ export interface ExtractedSymbol {
2
+ symbolKind: "class" | "interface" | "enum" | "record" | "method" | "field";
3
+ symbolName: string;
4
+ qualifiedName: string | undefined;
5
+ line: number;
6
+ }
7
+ export declare function extractSymbolsFromSource(filePath: string, content: string): Array<ExtractedSymbol>;
@@ -0,0 +1,64 @@
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
+ const METHOD_DECLARATION = /^(?:\s*@[\w.]+\s+)*[^{;]*?\b([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*(?:\{|;)/;
3
+ const FIELD_DECLARATION = /^(?:\s*@[\w.]+\s+)*[^\s][\w<>\[\],.?]+\s+([A-Za-z_$][\w$]*)\s*(?:=|;|,)/;
4
+ function normalizeLine(line) {
5
+ return line.replace(/\s+/g, " ").trim();
6
+ }
7
+ function isNoiseToken(token) {
8
+ return new Set(["if", "for", "while", "switch", "catch", "return", "new", "throw"]).has(token);
9
+ }
10
+ function lineIndexToLine(lineNo) {
11
+ return lineNo + 1;
12
+ }
13
+ export function extractSymbolsFromSource(filePath, content) {
14
+ const lines = content.split(/\r?\n/);
15
+ const symbols = [];
16
+ for (let index = 0; index < lines.length; index += 1) {
17
+ const rawLine = lines[index] ?? "";
18
+ const line = normalizeLine(rawLine);
19
+ if (!line) {
20
+ continue;
21
+ }
22
+ const classMatch = line.match(CLASS_DECLARATION);
23
+ if (classMatch) {
24
+ const symbolKind = classMatch[1];
25
+ const symbolName = classMatch[2];
26
+ if (symbolName && !isNoiseToken(symbolName)) {
27
+ symbols.push({
28
+ symbolKind,
29
+ symbolName,
30
+ qualifiedName: filePath.replace(/\.java$/, "").replaceAll("/", "."),
31
+ line: lineIndexToLine(index)
32
+ });
33
+ }
34
+ continue;
35
+ }
36
+ const methodMatch = line.match(METHOD_DECLARATION);
37
+ if (methodMatch) {
38
+ const symbolName = methodMatch[1];
39
+ if (symbolName && !isNoiseToken(symbolName)) {
40
+ symbols.push({
41
+ symbolKind: "method",
42
+ symbolName,
43
+ qualifiedName: filePath.replace(/\.java$/, "").replaceAll("/", "."),
44
+ line: lineIndexToLine(index)
45
+ });
46
+ }
47
+ continue;
48
+ }
49
+ const fieldMatch = line.match(FIELD_DECLARATION);
50
+ if (fieldMatch) {
51
+ const symbolName = fieldMatch[1];
52
+ if (symbolName && !isNoiseToken(symbolName)) {
53
+ symbols.push({
54
+ symbolKind: "field",
55
+ symbolName,
56
+ qualifiedName: filePath.replace(/\.java$/, "").replaceAll("/", "."),
57
+ line: lineIndexToLine(index)
58
+ });
59
+ }
60
+ }
61
+ }
62
+ return symbols;
63
+ }
64
+ //# sourceMappingURL=symbol-extractor.js.map
@@ -0,0 +1 @@
1
+ export declare function resolveTinyRemapperJar(cacheDir: string, overridePath: string | undefined, fetchFn?: typeof fetch): Promise<string>;
@@ -0,0 +1,62 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createError, ERROR_CODES } from "./errors.js";
4
+ import { log } from "./logger.js";
5
+ import { downloadToCache } from "./repo-downloader.js";
6
+ const DEFAULT_VERSION = "0.10.3";
7
+ const DOWNLOAD_TIMEOUT_MS = 120_000;
8
+ const inflightDownloads = new Map();
9
+ function tinyRemapperCachePath(cacheDir, version) {
10
+ return join(cacheDir, "resources", `tiny-remapper-${version}-fat.jar`);
11
+ }
12
+ function tinyRemapperDownloadUrl(version) {
13
+ return `https://maven.fabricmc.net/net/fabricmc/tiny-remapper/${version}/tiny-remapper-${version}-fat.jar`;
14
+ }
15
+ export async function resolveTinyRemapperJar(cacheDir, overridePath, fetchFn) {
16
+ // 1. Environment / config override
17
+ if (overridePath) {
18
+ return overridePath;
19
+ }
20
+ const version = process.env.MCP_TINY_REMAPPER_VERSION ?? DEFAULT_VERSION;
21
+ const cached = tinyRemapperCachePath(cacheDir, version);
22
+ // 2. Already cached
23
+ if (existsSync(cached)) {
24
+ return cached;
25
+ }
26
+ // 3. Download with per-target lock
27
+ const inflight = inflightDownloads.get(cached);
28
+ if (inflight) {
29
+ return inflight;
30
+ }
31
+ const downloadPromise = downloadTinyRemapper(version, cached, fetchFn);
32
+ inflightDownloads.set(cached, downloadPromise);
33
+ try {
34
+ return await downloadPromise;
35
+ }
36
+ finally {
37
+ inflightDownloads.delete(cached);
38
+ }
39
+ }
40
+ async function downloadTinyRemapper(version, destination, fetchFn) {
41
+ const url = tinyRemapperDownloadUrl(version);
42
+ log("info", "tiny-remapper.download.start", { version, url });
43
+ const result = await downloadToCache(url, destination, {
44
+ timeoutMs: DOWNLOAD_TIMEOUT_MS,
45
+ retries: 2,
46
+ ...(fetchFn ? { fetchFn } : {})
47
+ });
48
+ if (!result.ok || !result.path) {
49
+ throw createError({
50
+ code: ERROR_CODES.REMAPPER_UNAVAILABLE,
51
+ message: `Failed to download tiny-remapper ${version} from Fabric Maven.`,
52
+ details: { version, url, statusCode: result.statusCode }
53
+ });
54
+ }
55
+ log("info", "tiny-remapper.download.done", {
56
+ version,
57
+ path: result.path,
58
+ contentLength: result.contentLength
59
+ });
60
+ return result.path;
61
+ }
62
+ //# sourceMappingURL=tiny-remapper-resolver.js.map
@@ -0,0 +1,16 @@
1
+ export interface RemapOptions {
2
+ inputJar: string;
3
+ outputJar: string;
4
+ mappingsFile: string;
5
+ fromNamespace: string;
6
+ toNamespace: string;
7
+ threads?: number;
8
+ rebuildSourceFilenames?: boolean;
9
+ timeoutMs?: number;
10
+ maxMemoryMb?: number;
11
+ }
12
+ export interface RemapResult {
13
+ outputJar: string;
14
+ durationMs: number;
15
+ }
16
+ export declare function remapJar(tinyRemapperJarPath: string, options: RemapOptions): Promise<RemapResult>;
@@ -0,0 +1,73 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createError, ERROR_CODES } from "./errors.js";
3
+ import { assertJavaAvailable, runJavaProcess } from "./java-process.js";
4
+ import { log } from "./logger.js";
5
+ export async function remapJar(tinyRemapperJarPath, options) {
6
+ const { inputJar, outputJar, mappingsFile, fromNamespace, toNamespace, threads = 4, rebuildSourceFilenames = false, timeoutMs = 600_000, maxMemoryMb = 4096 } = options;
7
+ await assertJavaAvailable();
8
+ log("info", "remap.start", {
9
+ inputJar,
10
+ outputJar,
11
+ fromNamespace,
12
+ toNamespace,
13
+ threads
14
+ });
15
+ const startedAt = Date.now();
16
+ const args = [inputJar, outputJar, mappingsFile, fromNamespace, toNamespace];
17
+ if (threads !== 4) {
18
+ args.push(`--threads=${threads}`);
19
+ }
20
+ if (rebuildSourceFilenames) {
21
+ args.push("--rebuildSourceFilenames");
22
+ }
23
+ try {
24
+ const result = await runJavaProcess({
25
+ jarPath: tinyRemapperJarPath,
26
+ args,
27
+ timeoutMs,
28
+ maxMemoryMb,
29
+ normalizePathArgs: true
30
+ });
31
+ const durationMs = Date.now() - startedAt;
32
+ if (result.exitCode !== 0) {
33
+ throw createError({
34
+ code: ERROR_CODES.REMAP_FAILED,
35
+ message: `tiny-remapper exited with code ${result.exitCode}.`,
36
+ details: {
37
+ inputJar,
38
+ outputJar,
39
+ exitCode: result.exitCode,
40
+ stderrTail: result.stderrTail
41
+ }
42
+ });
43
+ }
44
+ if (!existsSync(outputJar)) {
45
+ throw createError({
46
+ code: ERROR_CODES.REMAP_FAILED,
47
+ message: "tiny-remapper did not produce an output JAR.",
48
+ details: {
49
+ inputJar,
50
+ outputJar,
51
+ stderrTail: result.stderrTail
52
+ }
53
+ });
54
+ }
55
+ log("info", "remap.done", {
56
+ inputJar,
57
+ outputJar,
58
+ durationMs
59
+ });
60
+ return { outputJar, durationMs };
61
+ }
62
+ catch (error) {
63
+ const durationMs = Date.now() - startedAt;
64
+ log("error", "remap.error", {
65
+ inputJar,
66
+ outputJar,
67
+ durationMs,
68
+ error: error instanceof Error ? error.message : String(error)
69
+ });
70
+ throw error;
71
+ }
72
+ }
73
+ //# sourceMappingURL=tiny-remapper-service.js.map