@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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/dist/access-widener-parser.d.ts +24 -0
- package/dist/access-widener-parser.js +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +178 -0
- package/dist/decompiler/vineflower.d.ts +15 -0
- package/dist/decompiler/vineflower.js +185 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +49 -0
- package/dist/hash.d.ts +1 -0
- package/dist/hash.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1447 -0
- package/dist/java-process.d.ts +16 -0
- package/dist/java-process.js +120 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +21 -0
- package/dist/mapping-pipeline-service.d.ts +18 -0
- package/dist/mapping-pipeline-service.js +60 -0
- package/dist/mapping-service.d.ts +161 -0
- package/dist/mapping-service.js +1706 -0
- package/dist/maven-resolver.d.ts +22 -0
- package/dist/maven-resolver.js +122 -0
- package/dist/minecraft-explorer-service.d.ts +43 -0
- package/dist/minecraft-explorer-service.js +562 -0
- package/dist/mixin-parser.d.ts +34 -0
- package/dist/mixin-parser.js +194 -0
- package/dist/mixin-validator.d.ts +59 -0
- package/dist/mixin-validator.js +274 -0
- package/dist/mod-analyzer.d.ts +23 -0
- package/dist/mod-analyzer.js +346 -0
- package/dist/mod-decompile-service.d.ts +39 -0
- package/dist/mod-decompile-service.js +136 -0
- package/dist/mod-remap-service.d.ts +17 -0
- package/dist/mod-remap-service.js +186 -0
- package/dist/mod-search-service.d.ts +28 -0
- package/dist/mod-search-service.js +174 -0
- package/dist/mojang-tiny-mapping-service.d.ts +13 -0
- package/dist/mojang-tiny-mapping-service.js +351 -0
- package/dist/nbt/java-nbt-codec.d.ts +3 -0
- package/dist/nbt/java-nbt-codec.js +385 -0
- package/dist/nbt/json-patch.d.ts +3 -0
- package/dist/nbt/json-patch.js +352 -0
- package/dist/nbt/pipeline.d.ts +39 -0
- package/dist/nbt/pipeline.js +173 -0
- package/dist/nbt/typed-json.d.ts +10 -0
- package/dist/nbt/typed-json.js +205 -0
- package/dist/nbt/types.d.ts +66 -0
- package/dist/nbt/types.js +2 -0
- package/dist/observability.d.ts +88 -0
- package/dist/observability.js +165 -0
- package/dist/path-converter.d.ts +12 -0
- package/dist/path-converter.js +161 -0
- package/dist/path-resolver.d.ts +19 -0
- package/dist/path-resolver.js +78 -0
- package/dist/registry-service.d.ts +29 -0
- package/dist/registry-service.js +214 -0
- package/dist/repo-downloader.d.ts +15 -0
- package/dist/repo-downloader.js +111 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.js +154 -0
- package/dist/search-hit-accumulator.d.ts +38 -0
- package/dist/search-hit-accumulator.js +153 -0
- package/dist/source-jar-reader.d.ts +13 -0
- package/dist/source-jar-reader.js +216 -0
- package/dist/source-resolver.d.ts +14 -0
- package/dist/source-resolver.js +274 -0
- package/dist/source-service.d.ts +404 -0
- package/dist/source-service.js +2881 -0
- package/dist/storage/artifacts-repo.d.ts +45 -0
- package/dist/storage/artifacts-repo.js +209 -0
- package/dist/storage/db.d.ts +14 -0
- package/dist/storage/db.js +132 -0
- package/dist/storage/files-repo.d.ts +78 -0
- package/dist/storage/files-repo.js +437 -0
- package/dist/storage/index-meta-repo.d.ts +35 -0
- package/dist/storage/index-meta-repo.js +97 -0
- package/dist/storage/migrations.d.ts +11 -0
- package/dist/storage/migrations.js +71 -0
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.js +160 -0
- package/dist/storage/sqlite.d.ts +20 -0
- package/dist/storage/sqlite.js +111 -0
- package/dist/storage/symbols-repo.d.ts +63 -0
- package/dist/storage/symbols-repo.js +401 -0
- package/dist/symbols/symbol-extractor.d.ts +7 -0
- package/dist/symbols/symbol-extractor.js +64 -0
- package/dist/tiny-remapper-resolver.d.ts +1 -0
- package/dist/tiny-remapper-resolver.js +62 -0
- package/dist/tiny-remapper-service.d.ts +16 -0
- package/dist/tiny-remapper-service.js +73 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.js +2 -0
- package/dist/version-diff-service.d.ts +41 -0
- package/dist/version-diff-service.js +222 -0
- package/dist/version-service.d.ts +70 -0
- package/dist/version-service.js +411 -0
- package/dist/vineflower-resolver.d.ts +1 -0
- package/dist/vineflower-resolver.js +62 -0
- package/dist/workspace-mapping-service.d.ts +18 -0
- package/dist/workspace-mapping-service.js +89 -0
- 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
|