@adhisang/minecraft-modding-mcp 5.0.0 → 6.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 +36 -0
- package/dist/access-widener-parser.d.ts +1 -0
- package/dist/access-widener-parser.js +6 -3
- package/dist/cache-registry.js +35 -31
- package/dist/decompiler/vineflower.js +8 -2
- package/dist/entry-tools/analyze-mod-service.js +2 -1
- package/dist/entry-tools/analyze-symbol-service.js +18 -9
- package/dist/entry-tools/compare-minecraft-service.js +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -1
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +26 -1
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +2 -1
- package/dist/entry-tools/inspect-minecraft/internal.js +2 -0
- package/dist/entry-tools/response-contract.d.ts +2 -0
- package/dist/entry-tools/response-contract.js +2 -2
- package/dist/entry-tools/validate-project-service.d.ts +4 -4
- package/dist/entry-tools/verify-mixin-target-service.js +7 -7
- package/dist/index.js +20 -16
- package/dist/java-process.js +1 -1
- package/dist/json-rpc-framing.d.ts +4 -0
- package/dist/json-rpc-framing.js +23 -1
- package/dist/mapping/internal-types.d.ts +17 -0
- package/dist/mapping/loaders/tiny-maven.js +46 -20
- package/dist/mapping/lookup.d.ts +12 -1
- package/dist/mapping/lookup.js +53 -1
- package/dist/mapping-service.js +36 -21
- package/dist/mixin/annotation-validators.js +21 -3
- package/dist/mixin/parsed-validator.js +1 -0
- package/dist/mixin-parser.js +71 -3
- package/dist/mod-decompile-service.d.ts +2 -0
- package/dist/mod-decompile-service.js +19 -2
- package/dist/mod-remap-service.js +6 -5
- package/dist/mojang-tiny-mapping-service.js +5 -2
- package/dist/nbt/java-nbt-codec.js +40 -16
- package/dist/nbt/json-patch.js +51 -12
- package/dist/nbt/typed-json.d.ts +1 -0
- package/dist/nbt/typed-json.js +12 -2
- package/dist/nbt/types.d.ts +2 -2
- package/dist/path-converter.js +10 -3
- package/dist/registry-service.d.ts +2 -0
- package/dist/registry-service.js +16 -1
- package/dist/repo-downloader.js +4 -3
- package/dist/source/artifact-resolver.js +1 -1
- package/dist/source/class-source/snippet-builder.js +6 -0
- package/dist/source/class-source-helpers.d.ts +4 -4
- package/dist/source/class-source-helpers.js +12 -11
- package/dist/source/class-source.d.ts +19 -0
- package/dist/source/class-source.js +48 -21
- package/dist/source/file-access.js +3 -2
- package/dist/source/indexer.js +24 -7
- package/dist/source/lifecycle/mapping-helpers.js +28 -3
- package/dist/source/lifecycle/runtime-check.js +20 -6
- package/dist/source/search.js +23 -8
- package/dist/source/validate-mixin/pipeline/resolve.js +23 -3
- package/dist/source/workspace-target.js +2 -1
- package/dist/source-resolver.js +2 -2
- package/dist/source-service.js +9 -1
- package/dist/stdio-supervisor.d.ts +2 -2
- package/dist/stdio-supervisor.js +29 -9
- package/dist/storage/db.js +24 -1
- package/dist/storage/files-repo.d.ts +3 -0
- package/dist/storage/files-repo.js +43 -37
- package/dist/storage/symbols-repo.d.ts +9 -0
- package/dist/storage/symbols-repo.js +47 -19
- package/dist/symbols/symbol-extractor.js +1 -1
- package/dist/tool-guidance.d.ts +2 -1
- package/dist/tool-guidance.js +26 -1
- package/dist/tool-schemas.d.ts +4 -4
- package/dist/tool-schemas.js +91 -91
- package/dist/warning-details.d.ts +12 -0
- package/dist/warning-details.js +17 -0
- package/dist/workspace-mapping-service.js +9 -0
- package/package.json +1 -1
|
@@ -54,7 +54,7 @@ export function hasExactVersionToken(path, version) {
|
|
|
54
54
|
// Avoid prefix false-positives like "1.21.1" matching "1.21.10".
|
|
55
55
|
const cached = VERSION_TOKEN_REGEX_CACHE.get(normalizedVersion);
|
|
56
56
|
const pattern = cached
|
|
57
|
-
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}([
|
|
57
|
+
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}(?![0-9a-z]|\\.[0-9])`, "i"));
|
|
58
58
|
return pattern.test(normalizedPath);
|
|
59
59
|
}
|
|
60
60
|
function inferMergedRuntimeNamespaceHint(path) {
|
|
@@ -53,6 +53,12 @@ export function buildClassSourceSnippet(input) {
|
|
|
53
53
|
sourceText = sliceToMaxCharsSafe(sourceText, input.maxChars);
|
|
54
54
|
charsTruncated = true;
|
|
55
55
|
truncated = true;
|
|
56
|
+
if (input.mode !== "metadata") {
|
|
57
|
+
const survivingNewlines = (sourceText.match(/\n/g) ?? []).length;
|
|
58
|
+
returnedEnd = sourceText.endsWith("\n")
|
|
59
|
+
? returnedStart + survivingNewlines - 1
|
|
60
|
+
: returnedStart + survivingNewlines;
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
let nextStartLine;
|
|
58
64
|
// Metadata mode returns a synthesized outline, not a line window into the
|
|
@@ -9,16 +9,16 @@ export declare function extractDecompiledMembers(className: string, filePath: st
|
|
|
9
9
|
fields: DecompiledMember[];
|
|
10
10
|
methods: DecompiledMember[];
|
|
11
11
|
};
|
|
12
|
-
export declare function computeLineBraceDepths(lines: string[]): number[];
|
|
12
|
+
export declare function computeLineBraceDepths(lines: string[], strippedLines?: string[]): number[];
|
|
13
13
|
export declare function computeBraceRange(lines: string[], symbols: Array<{
|
|
14
14
|
symbolKind: string;
|
|
15
15
|
symbolName: string;
|
|
16
16
|
line: number;
|
|
17
|
-
}>, simpleName: string): {
|
|
17
|
+
}>, simpleName: string, strippedLines?: string[]): {
|
|
18
18
|
declarationLine: number;
|
|
19
19
|
endLine: number;
|
|
20
20
|
} | undefined;
|
|
21
|
-
export declare function scanBraceRange(lines: string[], declarationLine: number): {
|
|
21
|
+
export declare function scanBraceRange(lines: string[], declarationLine: number, strippedLines?: string[]): {
|
|
22
22
|
declarationLine: number;
|
|
23
23
|
endLine: number;
|
|
24
24
|
};
|
|
@@ -28,7 +28,7 @@ export declare function computeNestedTypeRanges(lines: string[], symbols: Array<
|
|
|
28
28
|
}>, outerBody: {
|
|
29
29
|
declarationLine: number;
|
|
30
30
|
endLine: number;
|
|
31
|
-
}): Array<{
|
|
31
|
+
}, strippedLines?: string[]): Array<{
|
|
32
32
|
declarationLine: number;
|
|
33
33
|
endLine: number;
|
|
34
34
|
}>;
|
|
@@ -30,13 +30,14 @@ export function extractDecompiledMembers(className, filePath, content) {
|
|
|
30
30
|
const symbols = extractSymbolsFromSource(filePath, content);
|
|
31
31
|
const simpleName = className.split(/[.$]/).at(-1) ?? className;
|
|
32
32
|
const lines = content.split(/\r?\n/);
|
|
33
|
-
const
|
|
33
|
+
const stripped = stripBraceNoise(lines);
|
|
34
|
+
const body = computeBraceRange(lines, symbols, simpleName, stripped);
|
|
34
35
|
if (!body) {
|
|
35
36
|
return { constructors: [], fields: [], methods: [] };
|
|
36
37
|
}
|
|
37
|
-
const depths = computeLineBraceDepths(lines);
|
|
38
|
+
const depths = computeLineBraceDepths(lines, stripped);
|
|
38
39
|
const baseDepth = depths[body.declarationLine - 1] ?? 0;
|
|
39
|
-
const nestedRanges = computeNestedTypeRanges(lines, symbols, body);
|
|
40
|
+
const nestedRanges = computeNestedTypeRanges(lines, symbols, body, stripped);
|
|
40
41
|
const constructors = [];
|
|
41
42
|
const fields = [];
|
|
42
43
|
const methods = [];
|
|
@@ -126,8 +127,8 @@ function stripBraceNoise(lines) {
|
|
|
126
127
|
}
|
|
127
128
|
return out;
|
|
128
129
|
}
|
|
129
|
-
export function computeLineBraceDepths(lines) {
|
|
130
|
-
const stripped = stripBraceNoise(lines);
|
|
130
|
+
export function computeLineBraceDepths(lines, strippedLines) {
|
|
131
|
+
const stripped = strippedLines ?? stripBraceNoise(lines);
|
|
131
132
|
const depths = new Array(lines.length).fill(0);
|
|
132
133
|
let depth = 0;
|
|
133
134
|
for (let i = 0; i < stripped.length; i += 1) {
|
|
@@ -144,19 +145,19 @@ export function computeLineBraceDepths(lines) {
|
|
|
144
145
|
}
|
|
145
146
|
return depths;
|
|
146
147
|
}
|
|
147
|
-
export function computeBraceRange(lines, symbols, simpleName) {
|
|
148
|
+
export function computeBraceRange(lines, symbols, simpleName, strippedLines) {
|
|
148
149
|
const classSymbol = symbols.find((symbol) => (symbol.symbolKind === "class" || symbol.symbolKind === "interface"
|
|
149
150
|
|| symbol.symbolKind === "enum" || symbol.symbolKind === "record")
|
|
150
151
|
&& symbol.symbolName === simpleName);
|
|
151
152
|
if (!classSymbol) {
|
|
152
153
|
return undefined;
|
|
153
154
|
}
|
|
154
|
-
return scanBraceRange(lines, classSymbol.line);
|
|
155
|
+
return scanBraceRange(lines, classSymbol.line, strippedLines);
|
|
155
156
|
}
|
|
156
|
-
export function scanBraceRange(lines, declarationLine) {
|
|
157
|
+
export function scanBraceRange(lines, declarationLine, strippedLines) {
|
|
157
158
|
// Strip from the start of the file so multi-line block-comment state is correct
|
|
158
159
|
// by the time we reach declarationLine; indices stay aligned with `lines`.
|
|
159
|
-
const stripped = stripBraceNoise(lines);
|
|
160
|
+
const stripped = strippedLines ?? stripBraceNoise(lines);
|
|
160
161
|
let depth = 0;
|
|
161
162
|
let started = false;
|
|
162
163
|
for (let i = declarationLine - 1; i < stripped.length; i += 1) {
|
|
@@ -175,7 +176,7 @@ export function scanBraceRange(lines, declarationLine) {
|
|
|
175
176
|
}
|
|
176
177
|
return { declarationLine, endLine: lines.length };
|
|
177
178
|
}
|
|
178
|
-
export function computeNestedTypeRanges(lines, symbols, outerBody) {
|
|
179
|
+
export function computeNestedTypeRanges(lines, symbols, outerBody, strippedLines) {
|
|
179
180
|
const ranges = [];
|
|
180
181
|
for (const candidate of symbols) {
|
|
181
182
|
if (candidate.symbolKind !== "class" && candidate.symbolKind !== "interface"
|
|
@@ -188,7 +189,7 @@ export function computeNestedTypeRanges(lines, symbols, outerBody) {
|
|
|
188
189
|
if (ranges.some((range) => candidate.line >= range.declarationLine && candidate.line <= range.endLine)) {
|
|
189
190
|
continue;
|
|
190
191
|
}
|
|
191
|
-
const nestedRange = scanBraceRange(lines, candidate.line);
|
|
192
|
+
const nestedRange = scanBraceRange(lines, candidate.line, strippedLines);
|
|
192
193
|
ranges.push(nestedRange);
|
|
193
194
|
}
|
|
194
195
|
return ranges;
|
|
@@ -2,6 +2,24 @@ import { type AppError } from "../errors.js";
|
|
|
2
2
|
import type { SourceService } from "../source-service.js";
|
|
3
3
|
import type { DecompiledFallback, FindClassInput, FindClassOutput, GetClassMembersInput, GetClassMembersOutput, GetClassSourceInput, GetClassSourceOutput } from "../source-service.js";
|
|
4
4
|
import type { ArtifactProvenance, ArtifactScope, MappingSourcePriority, ResolvedSourceArtifact, SourceMapping } from "../types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Unobfuscated Minecraft versions (26.1+) ship their runtime and decompiled source
|
|
7
|
+
* in deobfuscated (mojang) names directly — there is no real obfuscated namespace to
|
|
8
|
+
* map to. An artifact whose stored namespace label is "obfuscated" is therefore a
|
|
9
|
+
* mislabel for a mojang request: the bytes already carry mojang names.
|
|
10
|
+
*
|
|
11
|
+
* Without this reconciliation, requestedMapping="mojang" !== mappingApplied="obfuscated"
|
|
12
|
+
* triggers a cascade of doomed work: resolveClassNameForLookup attempts a mojang->obfuscated
|
|
13
|
+
* class remap, remapSignatureMembers tries to remap every member obfuscated->mojang and drops
|
|
14
|
+
* them all (counts.total===0), which forces a spurious decompiledFallback, disables
|
|
15
|
+
* memberPattern, and floods the response with one "Could not remap ..." warning per member.
|
|
16
|
+
*
|
|
17
|
+
* Collapsing mappingApplied to the requested mojang namespace makes the whole pipeline an
|
|
18
|
+
* identity no-op: members are returned from bytecode with correct mojang names, memberPattern
|
|
19
|
+
* applies, and the per-member warning flood disappears. This is the single highest-impact
|
|
20
|
+
* token-efficiency fix for unobfuscated versions.
|
|
21
|
+
*/
|
|
22
|
+
export declare function reconcileUnobfuscatedNamespace(version: string | undefined, requestedMapping: SourceMapping, mappingApplied: SourceMapping): SourceMapping;
|
|
5
23
|
export declare function resolveClassFilePath(svc: SourceService, artifactId: string, className: string): string | undefined;
|
|
6
24
|
export declare function resolveClassNameForLookup(svc: SourceService, input: {
|
|
7
25
|
className: string;
|
|
@@ -37,6 +55,7 @@ export declare function buildClassSourceNotFoundError(_svc: SourceService, input
|
|
|
37
55
|
export declare function buildDecompiledFallback(svc: SourceService, artifactId: string, lookupClassName: string, memberPattern: string | undefined, maxMembers: number): {
|
|
38
56
|
fallback: DecompiledFallback;
|
|
39
57
|
counts: NonNullable<GetClassMembersOutput["decompiledMemberCounts"]>;
|
|
58
|
+
truncated: boolean;
|
|
40
59
|
} | undefined;
|
|
41
60
|
export declare function findClass(svc: SourceService, input: FindClassInput): FindClassOutput;
|
|
42
61
|
export declare function getClassSource(svc: SourceService, input: GetClassSourceInput): Promise<GetClassSourceOutput>;
|
|
@@ -8,7 +8,34 @@ import { buildClassSourceSnippet } from "./class-source/snippet-builder.js";
|
|
|
8
8
|
import { remapAndCountMembers, sliceMembersWithLimit, projectMembersForWire } from "./class-source/members-builder.js";
|
|
9
9
|
import { buildPageContextKey, encodeOffsetCursor, resolveCursorOffset } from "../page-cursor.js";
|
|
10
10
|
import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
11
|
+
import { isUnobfuscatedVersion } from "../version-service.js";
|
|
11
12
|
const MEMBERS_STATUS_LEGACY = process.env.MEMBERS_STATUS_LEGACY === "1";
|
|
13
|
+
/**
|
|
14
|
+
* Unobfuscated Minecraft versions (26.1+) ship their runtime and decompiled source
|
|
15
|
+
* in deobfuscated (mojang) names directly — there is no real obfuscated namespace to
|
|
16
|
+
* map to. An artifact whose stored namespace label is "obfuscated" is therefore a
|
|
17
|
+
* mislabel for a mojang request: the bytes already carry mojang names.
|
|
18
|
+
*
|
|
19
|
+
* Without this reconciliation, requestedMapping="mojang" !== mappingApplied="obfuscated"
|
|
20
|
+
* triggers a cascade of doomed work: resolveClassNameForLookup attempts a mojang->obfuscated
|
|
21
|
+
* class remap, remapSignatureMembers tries to remap every member obfuscated->mojang and drops
|
|
22
|
+
* them all (counts.total===0), which forces a spurious decompiledFallback, disables
|
|
23
|
+
* memberPattern, and floods the response with one "Could not remap ..." warning per member.
|
|
24
|
+
*
|
|
25
|
+
* Collapsing mappingApplied to the requested mojang namespace makes the whole pipeline an
|
|
26
|
+
* identity no-op: members are returned from bytecode with correct mojang names, memberPattern
|
|
27
|
+
* applies, and the per-member warning flood disappears. This is the single highest-impact
|
|
28
|
+
* token-efficiency fix for unobfuscated versions.
|
|
29
|
+
*/
|
|
30
|
+
export function reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied) {
|
|
31
|
+
if (version &&
|
|
32
|
+
requestedMapping === "mojang" &&
|
|
33
|
+
mappingApplied === "obfuscated" &&
|
|
34
|
+
isUnobfuscatedVersion(version)) {
|
|
35
|
+
return "mojang";
|
|
36
|
+
}
|
|
37
|
+
return mappingApplied;
|
|
38
|
+
}
|
|
12
39
|
function normalizeStrictPositiveInt(value, field) {
|
|
13
40
|
if (value == null) {
|
|
14
41
|
return undefined;
|
|
@@ -35,15 +62,6 @@ function normalizeMemberAccess(access) {
|
|
|
35
62
|
details: { access }
|
|
36
63
|
});
|
|
37
64
|
}
|
|
38
|
-
function buildResolveArtifactParams(target, extra = {}) {
|
|
39
|
-
return {
|
|
40
|
-
target: {
|
|
41
|
-
kind: target.kind,
|
|
42
|
-
value: target.value
|
|
43
|
-
},
|
|
44
|
-
...extra
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
65
|
function looksLikeDeobfuscatedClassName(value) {
|
|
48
66
|
const trimmed = value.trim();
|
|
49
67
|
if (!trimmed) {
|
|
@@ -227,7 +245,10 @@ export function buildDecompiledFallback(svc, artifactId, lookupClassName, member
|
|
|
227
245
|
let constructors = filterByPattern(extracted.constructors);
|
|
228
246
|
let fields = filterByPattern(extracted.fields);
|
|
229
247
|
let methods = filterByPattern(extracted.methods);
|
|
230
|
-
const
|
|
248
|
+
const constructorsBefore = constructors.length;
|
|
249
|
+
const fieldsBefore = fields.length;
|
|
250
|
+
const methodsBefore = methods.length;
|
|
251
|
+
const totalBefore = constructorsBefore + fieldsBefore + methodsBefore;
|
|
231
252
|
if (totalBefore === 0) {
|
|
232
253
|
return undefined;
|
|
233
254
|
}
|
|
@@ -251,11 +272,12 @@ export function buildDecompiledFallback(svc, artifactId, lookupClassName, member
|
|
|
251
272
|
origin: "source-extracted"
|
|
252
273
|
},
|
|
253
274
|
counts: {
|
|
254
|
-
constructors:
|
|
255
|
-
fields:
|
|
256
|
-
methods:
|
|
257
|
-
total:
|
|
258
|
-
}
|
|
275
|
+
constructors: constructorsBefore,
|
|
276
|
+
fields: fieldsBefore,
|
|
277
|
+
methods: methodsBefore,
|
|
278
|
+
total: totalBefore
|
|
279
|
+
},
|
|
280
|
+
truncated: totalBefore > maxMembers
|
|
259
281
|
};
|
|
260
282
|
}
|
|
261
283
|
// The class-like symbol kinds findClass returns. MUST stay in sync with the JS-side
|
|
@@ -444,7 +466,7 @@ export async function getClassSource(svc, input) {
|
|
|
444
466
|
const artifact = svc.getArtifact(artifactId);
|
|
445
467
|
artifactId = artifact.artifactId;
|
|
446
468
|
origin = artifact.origin;
|
|
447
|
-
requestedMapping = artifact.requestedMapping ?? requestedMapping;
|
|
469
|
+
requestedMapping = input.mapping != null ? requestedMapping : (artifact.requestedMapping ?? requestedMapping);
|
|
448
470
|
mappingApplied = artifact.mappingApplied ?? requestedMapping;
|
|
449
471
|
provenance = artifact.provenance;
|
|
450
472
|
qualityFlags = artifact.qualityFlags;
|
|
@@ -461,6 +483,7 @@ export async function getClassSource(svc, input) {
|
|
|
461
483
|
preferProjectVersion: input.preferProjectVersion,
|
|
462
484
|
warnings
|
|
463
485
|
});
|
|
486
|
+
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
464
487
|
let activeArtifactId = artifactId;
|
|
465
488
|
let activeOrigin = origin;
|
|
466
489
|
let activeProvenance = provenance;
|
|
@@ -624,7 +647,7 @@ export async function getClassSource(svc, input) {
|
|
|
624
647
|
mode,
|
|
625
648
|
startLine: nextStartLine,
|
|
626
649
|
...(input.endLine != null ? { endLine: input.endLine } : {}),
|
|
627
|
-
...(
|
|
650
|
+
...(maxLines != null ? { maxLines } : {}),
|
|
628
651
|
...(input.maxChars != null ? { maxChars: input.maxChars } : {})
|
|
629
652
|
}
|
|
630
653
|
})
|
|
@@ -746,6 +769,7 @@ export async function getClassMembers(svc, input) {
|
|
|
746
769
|
preferProjectVersion: input.preferProjectVersion,
|
|
747
770
|
warnings
|
|
748
771
|
});
|
|
772
|
+
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
749
773
|
if (requestedMapping !== "obfuscated" && !version) {
|
|
750
774
|
throw createError({
|
|
751
775
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
@@ -753,10 +777,7 @@ export async function getClassMembers(svc, input) {
|
|
|
753
777
|
details: {
|
|
754
778
|
mapping: requestedMapping,
|
|
755
779
|
nextAction: "Resolve with target: { kind: \"version\", value: ... } or specify a versioned coordinate.",
|
|
756
|
-
...buildSuggestedCall({
|
|
757
|
-
tool: "resolve-artifact",
|
|
758
|
-
params: buildResolveArtifactParams({ kind: "version", value: "latest" })
|
|
759
|
-
})
|
|
780
|
+
...buildSuggestedCall({ tool: "list-versions", params: {} })
|
|
760
781
|
}
|
|
761
782
|
});
|
|
762
783
|
}
|
|
@@ -898,6 +919,12 @@ export async function getClassMembers(svc, input) {
|
|
|
898
919
|
warnings.push("Bytecode member enumeration returned zero; populated decompiledFallback from decompiled source. "
|
|
899
920
|
+ "Descriptors and access modifiers are unavailable — use get-class-source for full details."
|
|
900
921
|
+ namespaceNote);
|
|
922
|
+
if (sourceFallback.truncated) {
|
|
923
|
+
const returnedTotal = decompiledFallback.constructors.length
|
|
924
|
+
+ decompiledFallback.fields.length
|
|
925
|
+
+ decompiledFallback.methods.length;
|
|
926
|
+
warnings.push(`Member list was truncated to ${returnedTotal} entries (from ${sourceFallback.counts.total}).`);
|
|
927
|
+
}
|
|
901
928
|
if (namespaceMismatch && memberPattern) {
|
|
902
929
|
warnings.push(`memberPattern="${memberPattern}" was not applied to decompiledFallback because the artifact namespace (${mappingApplied}) differs from the requested namespace (${requestedMapping}); filter the response client-side after mapping.`);
|
|
903
930
|
}
|
|
@@ -71,12 +71,13 @@ export async function listArtifactFiles(svc, input) {
|
|
|
71
71
|
const artifact = svc.getArtifact(input.artifactId);
|
|
72
72
|
const limit = clampLimit(input.limit, 200, 2000);
|
|
73
73
|
const warnings = [];
|
|
74
|
+
const prefix = input.prefix === undefined ? undefined : normalizePathStyle(input.prefix);
|
|
74
75
|
const page = svc.filesRepo.listFiles(artifact.artifactId, {
|
|
75
76
|
limit,
|
|
76
77
|
cursor: input.cursor,
|
|
77
|
-
prefix
|
|
78
|
+
prefix
|
|
78
79
|
});
|
|
79
|
-
const normalizedPrefix = normalizeOptionalString(
|
|
80
|
+
const normalizedPrefix = normalizeOptionalString(prefix);
|
|
80
81
|
if (normalizedPrefix &&
|
|
81
82
|
page.items.length === 0 &&
|
|
82
83
|
(normalizedPrefix.startsWith("assets/") || normalizedPrefix.startsWith("data/"))) {
|
package/dist/source/indexer.js
CHANGED
|
@@ -210,17 +210,20 @@ export async function buildRebuiltArtifactData(svc, resolved) {
|
|
|
210
210
|
}
|
|
211
211
|
catch (caughtError) {
|
|
212
212
|
if (isAppError(caughtError) && caughtError.code === ERROR_CODES.DECOMPILER_FAILED) {
|
|
213
|
+
// Decompilation failed BEFORE the artifact was ever upserted, so there is no
|
|
214
|
+
// queryable artifact. Deliberately omit artifactId from the error: exposing the
|
|
215
|
+
// would-be id led callers to pass it to find-class/get-class-*, which then failed
|
|
216
|
+
// with "Artifact not found. Resolve context first." (B5 state inconsistency).
|
|
217
|
+
const priorDetails = { ...(caughtError.details ?? {}) };
|
|
218
|
+
delete priorDetails.artifactId;
|
|
213
219
|
throw createError({
|
|
214
220
|
code: ERROR_CODES.DECOMPILER_FAILED,
|
|
215
221
|
message: caughtError.message,
|
|
216
222
|
details: {
|
|
217
|
-
...
|
|
218
|
-
artifactId: resolved.artifactId,
|
|
223
|
+
...priorDetails,
|
|
219
224
|
binaryJarPath: resolved.binaryJarPath,
|
|
220
|
-
producedJavaCount: typeof
|
|
221
|
-
|
|
222
|
-
: 0,
|
|
223
|
-
nextAction: "Verify Java runtime and Vineflower availability, then retry. If available, prefer source-backed artifacts.",
|
|
225
|
+
producedJavaCount: typeof priorDetails.producedJavaCount === "number" ? priorDetails.producedJavaCount : 0,
|
|
226
|
+
nextAction: "Decompilation failed, so no artifact was created. Verify Java runtime and Vineflower availability, then retry; prefer source-backed artifacts when available.",
|
|
224
227
|
recommendedCommand: "echo $MCP_VINEFLOWER_JAR_PATH"
|
|
225
228
|
}
|
|
226
229
|
});
|
|
@@ -336,6 +339,13 @@ export async function ingestIfNeeded(svc, resolved) {
|
|
|
336
339
|
const inflight = svc.state.inflightArtifactIngests.get(resolved.artifactId);
|
|
337
340
|
if (inflight) {
|
|
338
341
|
await inflight;
|
|
342
|
+
const transformChain = resolved.provenance?.transformChain ?? [];
|
|
343
|
+
if (transformChain.includes("binary-remap:obf->mojang") && resolved.binaryJarPath) {
|
|
344
|
+
const reconciledBinaryJarPath = await maybeRemapBinaryForMojang(svc, resolved);
|
|
345
|
+
if (reconciledBinaryJarPath !== resolved.binaryJarPath) {
|
|
346
|
+
resolved.binaryJarPath = reconciledBinaryJarPath;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
339
349
|
return;
|
|
340
350
|
}
|
|
341
351
|
const ingestPromise = rebuildMissingArtifactIndex(svc, resolved, reason);
|
|
@@ -429,9 +439,16 @@ export async function maybeRemapBinaryForMojang(svc, resolved, deps = defaultBin
|
|
|
429
439
|
if (inflight) {
|
|
430
440
|
return inflight;
|
|
431
441
|
}
|
|
442
|
+
// When a prior index-artifact persisted the remapped jar back into the
|
|
443
|
+
// artifacts row, binaryJarPath already equals remappedJarPath. Remapping that
|
|
444
|
+
// missing/corrupt path onto itself would fail permanently, so re-resolve the
|
|
445
|
+
// original obfuscated client jar to recover from out-of-band cache loss.
|
|
446
|
+
const inputJar = binaryJarPath === remappedJarPath
|
|
447
|
+
? (await svc.versionService.resolveVersionJar(resolved.version)).jarPath
|
|
448
|
+
: binaryJarPath;
|
|
432
449
|
const remapPromise = runBinaryRemapWithDeps(svc, {
|
|
433
450
|
version: resolved.version,
|
|
434
|
-
inputJar
|
|
451
|
+
inputJar,
|
|
435
452
|
remappedDir,
|
|
436
453
|
remappedJarPath
|
|
437
454
|
}, deps);
|
|
@@ -25,6 +25,22 @@ function looksLikeClassSegment(name) {
|
|
|
25
25
|
const trimmed = name.trim();
|
|
26
26
|
return /^[A-Z_$]/.test(trimmed);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Collapse N per-member remap failures into a single warning line. Preserves the
|
|
30
|
+
* original "Could not remap" / "Remap failed for" prefix so downstream classifiers
|
|
31
|
+
* (e.g. REMAP_WARNING_RE in validate-mixin, warningDetails coding) still recognise it,
|
|
32
|
+
* while bounding the per-member flood to one line plus a short sample of names.
|
|
33
|
+
*/
|
|
34
|
+
function pushAggregatedRemapWarning(warnings, prefix, names, kind, sourceMapping, targetMapping) {
|
|
35
|
+
if (names.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const unique = [...new Set(names)];
|
|
39
|
+
const sample = unique.slice(0, 3).join(", ");
|
|
40
|
+
const more = unique.length > 3 ? `, +${unique.length - 3} more` : "";
|
|
41
|
+
const plural = names.length === 1 ? "" : "s";
|
|
42
|
+
warnings.push(`${prefix} ${names.length} ${kind}${plural} from ${sourceMapping} to ${targetMapping} (${sample}${more}).`);
|
|
43
|
+
}
|
|
28
44
|
export function rejectLifecycleClassLikeInput(svc, input) {
|
|
29
45
|
void svc;
|
|
30
46
|
if (!looksLikeClassSegment(input.methodName)) {
|
|
@@ -234,6 +250,13 @@ export async function remapSignatureMembers(svc, members, kind, version, sourceM
|
|
|
234
250
|
"resolveMethodMappingExact" in svc.mappingService &&
|
|
235
251
|
typeof svc.mappingService.resolveMethodMappingExact === "function";
|
|
236
252
|
const memberEntries = [...memberKeyToRemapped.entries()];
|
|
253
|
+
// Collect per-member remap failures and emit a SINGLE aggregated warning each
|
|
254
|
+
// after the loop instead of one line per member. On namespace mismatches a large
|
|
255
|
+
// class can fail to remap every one of its ~150 members, which previously flooded
|
|
256
|
+
// both warnings[] and the 1:1 warningDetails[] with hundreds of near-identical
|
|
257
|
+
// lines (the dominant token sink for these responses).
|
|
258
|
+
const unremappedNames = [];
|
|
259
|
+
const remapErrorNames = [];
|
|
237
260
|
await Promise.all(memberEntries.map(async ([key, _sourceName]) => {
|
|
238
261
|
const [ownerFqn, name, descriptor] = key.split("\0");
|
|
239
262
|
try {
|
|
@@ -299,20 +322,22 @@ export async function remapSignatureMembers(svc, members, kind, version, sourceM
|
|
|
299
322
|
}
|
|
300
323
|
}
|
|
301
324
|
else {
|
|
302
|
-
|
|
325
|
+
unremappedNames.push(name);
|
|
303
326
|
failedNames.add(name);
|
|
304
327
|
}
|
|
305
328
|
}
|
|
306
329
|
else {
|
|
307
|
-
|
|
330
|
+
unremappedNames.push(name);
|
|
308
331
|
failedNames.add(name);
|
|
309
332
|
}
|
|
310
333
|
}
|
|
311
334
|
catch {
|
|
312
|
-
|
|
335
|
+
remapErrorNames.push(name);
|
|
313
336
|
failedNames.add(name);
|
|
314
337
|
}
|
|
315
338
|
}));
|
|
339
|
+
pushAggregatedRemapWarning(warnings, "Could not remap", unremappedNames, kind, sourceMapping, targetMapping);
|
|
340
|
+
pushAggregatedRemapWarning(warnings, "Remap failed for", remapErrorNames, kind, sourceMapping, targetMapping);
|
|
316
341
|
const isField = kind === "field";
|
|
317
342
|
return {
|
|
318
343
|
members: members.map((member) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ERROR_CODES, isAppError } from "../../errors.js";
|
|
1
2
|
export async function checkSymbolExistsInUnobfuscatedRuntime(svc, input, fallbackBase) {
|
|
2
3
|
const version = input.version.trim();
|
|
3
4
|
const name = input.name.trim();
|
|
@@ -50,16 +51,25 @@ export async function checkSymbolExistsInUnobfuscatedRuntime(svc, input, fallbac
|
|
|
50
51
|
signature = await svc.explorerService.getSignature({
|
|
51
52
|
fqn: targetClass,
|
|
52
53
|
jarPath,
|
|
53
|
-
access: "all"
|
|
54
|
+
access: "all",
|
|
55
|
+
// Method existence is asked relative to the owner type, so inherited methods count:
|
|
56
|
+
// `level.getGameTime()` exists on Level even though it is declared on a supertype.
|
|
57
|
+
// Scoped to methods only: fields already resolved correctly without an inheritance
|
|
58
|
+
// walk, and including inherited fields would let a shadowed field name match twice
|
|
59
|
+
// and flip a previously-resolved field to "ambiguous".
|
|
60
|
+
includeInherited: input.kind === "method"
|
|
54
61
|
});
|
|
55
62
|
}
|
|
56
|
-
catch {
|
|
63
|
+
catch (error) {
|
|
64
|
+
const classMissing = isAppError(error) && error.code === ERROR_CODES.CLASS_NOT_FOUND;
|
|
57
65
|
return {
|
|
58
66
|
...fallbackBase,
|
|
59
67
|
querySymbol,
|
|
60
68
|
warnings: [
|
|
61
69
|
...fallbackBase.warnings,
|
|
62
|
-
|
|
70
|
+
classMissing
|
|
71
|
+
? `Class "${targetClass}" was not found in the Minecraft ${version} runtime jar; it does not exist (or is not in this jar).`
|
|
72
|
+
: `Version ${version} is unobfuscated; runtime bytecode lookup could not load class "${targetClass}".`
|
|
63
73
|
]
|
|
64
74
|
};
|
|
65
75
|
}
|
|
@@ -114,9 +124,13 @@ export async function checkSymbolExistsInUnobfuscatedRuntime(svc, input, fallbac
|
|
|
114
124
|
});
|
|
115
125
|
}
|
|
116
126
|
const methodCandidates = signature.methods.filter((method) => method.name === name);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
const signatureMode = input.signatureMode ?? "name-only";
|
|
128
|
+
if (signatureMode === "name-only") {
|
|
129
|
+
// Existence semantics: any overload with this name means the method exists. Multiple
|
|
130
|
+
// overloads are not "ambiguous" for a name-only existence check — only zero matches
|
|
131
|
+
// is not_found. (Use signatureMode: "exact" with a descriptor to pin one overload.)
|
|
132
|
+
if (methodCandidates.length === 0) {
|
|
133
|
+
return buildUnresolved("not_found");
|
|
120
134
|
}
|
|
121
135
|
return buildResolved({
|
|
122
136
|
kind: "method",
|
package/dist/source/search.js
CHANGED
|
@@ -65,11 +65,13 @@ export function buildGlobRegex(pattern) {
|
|
|
65
65
|
while (i < pattern.length) {
|
|
66
66
|
const ch = pattern[i];
|
|
67
67
|
if (ch === "*" && pattern[i + 1] === "*") {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
if (pattern[i + 2] === "/") {
|
|
69
|
+
result += "(?:.*/)?";
|
|
70
|
+
i += 3;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
result += ".*";
|
|
74
|
+
i += 2;
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
else if (ch === "*") {
|
|
@@ -301,6 +303,9 @@ export async function searchClassSource(svc, input) {
|
|
|
301
303
|
searchWarnings.push(`queryNamespace=${input.queryNamespace}: translation failed (${caughtError instanceof Error ? caughtError.message : String(caughtError)}); running literal search instead.`);
|
|
302
304
|
}
|
|
303
305
|
}
|
|
306
|
+
else if (intent === "symbol") {
|
|
307
|
+
searchWarnings.push(`queryNamespace=${input.queryNamespace} requires a fully-qualified class name for translation; running literal search in ${artifactMapping} instead. Resolve "${originalQuery}" to an FQCN with find-class or find-mapping first.`);
|
|
308
|
+
}
|
|
304
309
|
else if (intent === "text" || intent === "path") {
|
|
305
310
|
searchWarnings.push(`queryNamespace=${input.queryNamespace} has no effect when intent="${intent}" — ${intent} search is a literal match against the artifact's ${artifactMapping} index. Use intent="symbol" for namespace translation.`);
|
|
306
311
|
}
|
|
@@ -676,7 +681,9 @@ export function searchPathIntent(svc, artifactId, query, match, scope, regexPatt
|
|
|
676
681
|
}
|
|
677
682
|
export function findSymbolHits(svc, artifactId, query, match, scope, regexPattern) {
|
|
678
683
|
if (match !== "regex") {
|
|
679
|
-
const filePathLike = scope?.fileGlob
|
|
684
|
+
const filePathLike = scope?.fileGlob && !scope.fileGlob.includes("**")
|
|
685
|
+
? globToSqlLike(normalizePathStyle(scope.fileGlob))
|
|
686
|
+
: undefined;
|
|
680
687
|
const scoped = svc.symbolsRepo.findScopedSymbols({
|
|
681
688
|
artifactId,
|
|
682
689
|
query,
|
|
@@ -688,8 +695,12 @@ export function findSymbolHits(svc, artifactId, query, match, scope, regexPatter
|
|
|
688
695
|
});
|
|
689
696
|
svc.metrics.recordSearchDbRoundtrip();
|
|
690
697
|
svc.metrics.recordSearchRowsScanned(scoped.items.length);
|
|
698
|
+
const globFilter = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
691
699
|
const result = [];
|
|
692
700
|
for (const symbol of scoped.items) {
|
|
701
|
+
if (globFilter && !globFilter.test(symbol.filePath)) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
693
704
|
if (!isSymbolKind(symbol.symbolKind)) {
|
|
694
705
|
continue;
|
|
695
706
|
}
|
|
@@ -705,12 +716,15 @@ export function findSymbolHits(svc, artifactId, query, match, scope, regexPatter
|
|
|
705
716
|
}
|
|
706
717
|
return result;
|
|
707
718
|
}
|
|
708
|
-
|
|
719
|
+
// Stream rows instead of materializing every symbol: regex scans on large
|
|
720
|
+
// artifacts only keep matching rows in memory this way.
|
|
721
|
+
const candidates = svc.symbolsRepo.iterateSymbolsForArtifact(artifactId, scope?.symbolKind);
|
|
709
722
|
svc.metrics.recordSearchDbRoundtrip();
|
|
710
|
-
|
|
723
|
+
let rowsScanned = 0;
|
|
711
724
|
const result = [];
|
|
712
725
|
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
713
726
|
for (const symbol of candidates) {
|
|
727
|
+
rowsScanned += 1;
|
|
714
728
|
if (!checkPackagePrefix(symbol.filePath, scope?.packagePrefix)) {
|
|
715
729
|
continue;
|
|
716
730
|
}
|
|
@@ -732,6 +746,7 @@ export function findSymbolHits(svc, artifactId, query, match, scope, regexPatter
|
|
|
732
746
|
matchIndex: index
|
|
733
747
|
});
|
|
734
748
|
}
|
|
749
|
+
svc.metrics.recordSearchRowsScanned(rowsScanned);
|
|
735
750
|
return result;
|
|
736
751
|
}
|
|
737
752
|
export function indexedCandidateLimit(svc) {
|
|
@@ -40,10 +40,22 @@ export async function runResolveStage(svc, ctx) {
|
|
|
40
40
|
scope: ctx.input.scope,
|
|
41
41
|
preferProjectVersion: false
|
|
42
42
|
});
|
|
43
|
-
ctx.jarPath = ctx.resolvedArtifact.binaryJarPath ?? (await svc.versionService.resolveVersionJar(ctx.version)).jarPath;
|
|
44
43
|
ctx.warnings.push(...ctx.resolvedArtifact.warnings);
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (ctx.resolvedArtifact.binaryJarPath) {
|
|
45
|
+
ctx.jarPath = ctx.resolvedArtifact.binaryJarPath;
|
|
46
|
+
ctx.mappingApplied = ctx.resolvedArtifact.mappingApplied;
|
|
47
|
+
ctx.signatureLookupMapping = ctx.resolvedArtifact.mappingApplied;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
ctx.jarPath = (await svc.versionService.resolveVersionJar(ctx.version)).jarPath;
|
|
51
|
+
ctx.signatureLookupMapping = "obfuscated";
|
|
52
|
+
ctx.scopeFallback = {
|
|
53
|
+
requested: ctx.input.scope,
|
|
54
|
+
applied: "vanilla",
|
|
55
|
+
reason: "Resolved artifact had no binary jar (sources only); falling back to vanilla client jar."
|
|
56
|
+
};
|
|
57
|
+
ctx.warnings.push(`Scope "${ctx.input.scope}" resolved without a binary jar; falling back to vanilla client jar.`);
|
|
58
|
+
}
|
|
47
59
|
if (ctx.resolvedArtifact.version) {
|
|
48
60
|
ctx.version = ctx.resolvedArtifact.version;
|
|
49
61
|
}
|
|
@@ -59,6 +71,14 @@ export async function runResolveStage(svc, ctx) {
|
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
else {
|
|
74
|
+
if (ctx.input.scope && ctx.input.scope !== "vanilla") {
|
|
75
|
+
ctx.scopeFallback = {
|
|
76
|
+
requested: ctx.input.scope,
|
|
77
|
+
applied: "vanilla",
|
|
78
|
+
reason: "projectPath required for merged/loader scope; falling back to vanilla client jar."
|
|
79
|
+
};
|
|
80
|
+
ctx.warnings.push(`Scope "${ctx.input.scope}" requires projectPath; falling back to vanilla client jar.`);
|
|
81
|
+
}
|
|
62
82
|
ctx.jarPath = (await svc.versionService.resolveVersionJar(ctx.version)).jarPath;
|
|
63
83
|
}
|
|
64
84
|
if (ctx.jarPath.includes("-sources.jar")) {
|
|
@@ -37,6 +37,7 @@ export async function loadOrDetectWorkspaceContext(svc, projectPath) {
|
|
|
37
37
|
value: loaderResult.loader
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
const latest = svc.workspaceContextCache.read(projectPath);
|
|
40
41
|
const ctx = {
|
|
41
42
|
projectPath,
|
|
42
43
|
minecraftVersion,
|
|
@@ -44,7 +45,7 @@ export async function loadOrDetectWorkspaceContext(svc, projectPath) {
|
|
|
44
45
|
loader: loaderResult?.resolved ? loaderResult.loader : undefined,
|
|
45
46
|
detectedAt: Date.now(),
|
|
46
47
|
evidence,
|
|
47
|
-
dependencyVersions: cached?.dependencyVersions ?? new Map(),
|
|
48
|
+
dependencyVersions: latest?.dependencyVersions ?? cached?.dependencyVersions ?? new Map(),
|
|
48
49
|
partial: false
|
|
49
50
|
};
|
|
50
51
|
svc.workspaceContextCache.write(ctx);
|
package/dist/source-resolver.js
CHANGED
|
@@ -260,7 +260,7 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
260
260
|
? false
|
|
261
261
|
: download.statusCode !== 404 && isTransientFailure(download.statusCode);
|
|
262
262
|
sawRemoteRepoFailure = sawRemoteRepoFailure || transient;
|
|
263
|
-
if (hasNextAttempt && transient) {
|
|
263
|
+
if (hasNextAttempt && (transient || download.ok)) {
|
|
264
264
|
options.onRepoFailover?.({
|
|
265
265
|
stage: "source",
|
|
266
266
|
repoUrl: sourceUrl,
|
|
@@ -320,7 +320,7 @@ export async function resolveSourceTarget(input, options, explicitConfig) {
|
|
|
320
320
|
? false
|
|
321
321
|
: downloaded.statusCode !== 404 && isTransientFailure(downloaded.statusCode);
|
|
322
322
|
sawRemoteRepoFailure = sawRemoteRepoFailure || transient;
|
|
323
|
-
if (hasNextAttempt && transient) {
|
|
323
|
+
if (hasNextAttempt && (transient || downloaded.ok)) {
|
|
324
324
|
options.onRepoFailover?.({
|
|
325
325
|
stage: "binary",
|
|
326
326
|
repoUrl: binaryUrl,
|