@adhisang/minecraft-modding-mcp 4.2.1 → 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 +69 -0
- package/README.md +5 -4
- package/dist/access-transformer-parser.d.ts +2 -1
- package/dist/access-transformer-parser.js +10 -1
- package/dist/access-widener-parser.d.ts +1 -0
- package/dist/access-widener-parser.js +6 -3
- package/dist/build-suggested-call.js +31 -10
- package/dist/cache-registry.d.ts +8 -0
- package/dist/cache-registry.js +50 -32
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -0
- package/dist/decompiler/vineflower.js +8 -2
- package/dist/entry-tools/analyze-mod-service.d.ts +24 -24
- package/dist/entry-tools/analyze-mod-service.js +2 -1
- package/dist/entry-tools/analyze-symbol-service.d.ts +57 -22
- package/dist/entry-tools/analyze-symbol-service.js +139 -34
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -1
- package/dist/entry-tools/batch-class-members-service.js +6 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -1
- package/dist/entry-tools/batch-class-source-service.js +5 -6
- package/dist/entry-tools/batch-mappings-service.d.ts +3 -1
- package/dist/entry-tools/batch-mappings-service.js +4 -5
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +3 -1
- package/dist/entry-tools/batch-symbol-exists-service.js +4 -5
- package/dist/entry-tools/compare-minecraft-service.d.ts +10 -10
- package/dist/entry-tools/compare-minecraft-service.js +1 -1
- package/dist/entry-tools/entry-tool-schema.d.ts +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +1 -3
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +4 -3
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +0 -5
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +26 -2
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +8 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +13 -2
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +3 -2
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +90 -89
- package/dist/entry-tools/inspect-minecraft/internal.js +32 -14
- package/dist/entry-tools/inspect-minecraft-service.d.ts +218 -218
- package/dist/entry-tools/inspect-minecraft-service.js +1 -1
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/response-contract.d.ts +3 -1
- package/dist/entry-tools/response-contract.js +3 -2
- package/dist/entry-tools/validate-project/cases/mixin.js +2 -1
- package/dist/entry-tools/validate-project/cases/project-summary.js +5 -3
- package/dist/entry-tools/validate-project-service.d.ts +42 -36
- package/dist/entry-tools/validate-project-service.js +3 -1
- package/dist/entry-tools/verify-mixin-target-service.d.ts +30 -0
- package/dist/entry-tools/verify-mixin-target-service.js +120 -29
- package/dist/error-mapping.d.ts +51 -0
- package/dist/error-mapping.js +157 -2
- package/dist/index.js +147 -76
- 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 +24 -0
- package/dist/mapping/loaders/tiny-maven.js +46 -20
- package/dist/mapping/lookup.d.ts +13 -2
- package/dist/mapping/lookup.js +60 -2
- package/dist/mapping/types.d.ts +6 -0
- package/dist/mapping-service.d.ts +26 -2
- package/dist/mapping-service.js +212 -55
- package/dist/mcp-helpers.d.ts +1 -0
- package/dist/mcp-helpers.js +25 -3
- package/dist/minecraft-explorer-service.js +24 -11
- package/dist/mixin/access-validators.js +33 -1
- package/dist/mixin/annotation-validators.js +73 -9
- package/dist/mixin/parsed-validator.js +3 -2
- package/dist/mixin-parser.js +119 -7
- package/dist/mod-decompile-service.d.ts +2 -0
- package/dist/mod-decompile-service.js +21 -3
- package/dist/mod-remap-service.d.ts +20 -1
- package/dist/mod-remap-service.js +101 -45
- package/dist/mojang-tiny-mapping-service.js +5 -2
- package/dist/nbt/java-nbt-codec.js +46 -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/observability.d.ts +6 -0
- package/dist/observability.js +10 -0
- package/dist/page-cursor.d.ts +17 -0
- package/dist/page-cursor.js +0 -0
- package/dist/path-converter.js +10 -3
- package/dist/registry-service.d.ts +2 -0
- package/dist/registry-service.js +41 -2
- package/dist/repo-downloader.js +4 -3
- package/dist/resources.js +45 -9
- package/dist/response-utils.d.ts +29 -4
- package/dist/response-utils.js +95 -5
- package/dist/source/access-validate.js +12 -2
- package/dist/source/artifact-resolver.js +2 -2
- package/dist/source/class-source/members-builder.d.ts +44 -1
- package/dist/source/class-source/members-builder.js +51 -4
- package/dist/source/class-source/snippet-builder.d.ts +13 -0
- package/dist/source/class-source/snippet-builder.js +54 -4
- package/dist/source/class-source-helpers.d.ts +4 -4
- package/dist/source/class-source-helpers.js +77 -20
- package/dist/source/class-source.d.ts +19 -0
- package/dist/source/class-source.js +144 -40
- package/dist/source/file-access.js +3 -2
- package/dist/source/indexer.js +29 -9
- package/dist/source/lifecycle/mapping-helpers.js +28 -3
- package/dist/source/lifecycle/runtime-check.js +20 -6
- package/dist/source/search.d.ts +1 -1
- package/dist/source/search.js +117 -30
- package/dist/source/validate-mixin/pipeline/resolve.js +23 -3
- package/dist/source/validate-mixin.d.ts +2 -1
- package/dist/source/validate-mixin.js +45 -4
- package/dist/source/workspace-target.js +17 -10
- package/dist/source-jar-reader.d.ts +54 -1
- package/dist/source-jar-reader.js +121 -10
- package/dist/source-resolver.js +2 -2
- package/dist/source-service.d.ts +28 -7
- 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 +15 -0
- package/dist/storage/files-repo.js +67 -37
- package/dist/storage/symbols-repo.d.ts +15 -0
- package/dist/storage/symbols-repo.js +53 -19
- package/dist/symbols/symbol-extractor.js +35 -6
- package/dist/text-truncate.d.ts +7 -0
- package/dist/text-truncate.js +21 -0
- package/dist/tiny-remapper-resolver.js +10 -0
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +12 -1
- package/dist/tool-guidance.d.ts +6 -2
- package/dist/tool-guidance.js +63 -13
- package/dist/tool-schemas.d.ts +561 -496
- package/dist/tool-schemas.js +175 -139
- package/dist/types.d.ts +1 -0
- package/dist/version-diff-service.js +5 -0
- package/dist/version-service.d.ts +3 -0
- package/dist/version-service.js +18 -2
- package/dist/warning-details.d.ts +30 -0
- package/dist/warning-details.js +80 -0
- package/dist/workspace-mapping-service.js +9 -0
- package/docs/README-ja.md +2 -2
- package/docs/examples.md +26 -2
- package/docs/tool-reference.md +22 -17
- package/package.json +1 -1
|
@@ -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 = [];
|
|
@@ -67,17 +68,73 @@ export function extractDecompiledMembers(className, filePath, content) {
|
|
|
67
68
|
}
|
|
68
69
|
return { constructors, fields, methods };
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Remove everything that must not count toward brace depth — line comments,
|
|
73
|
+
* (possibly multi-line) block comments, string literals, and char literals —
|
|
74
|
+
* in a single stateful pass so that block comments spanning lines and char
|
|
75
|
+
* literals like '{' / '}' never skew brace accounting. Line indices are
|
|
76
|
+
* preserved (one output entry per input line). Text blocks (""" … """) are not
|
|
77
|
+
* special-cased; they are vanishingly rare in decompiled output.
|
|
78
|
+
*/
|
|
79
|
+
function stripBraceNoise(lines) {
|
|
80
|
+
const out = new Array(lines.length);
|
|
81
|
+
let inBlockComment = false;
|
|
82
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
83
|
+
const line = lines[i] ?? "";
|
|
84
|
+
let result = "";
|
|
85
|
+
let j = 0;
|
|
86
|
+
while (j < line.length) {
|
|
87
|
+
const ch = line[j];
|
|
88
|
+
const next = line[j + 1];
|
|
89
|
+
if (inBlockComment) {
|
|
90
|
+
if (ch === "*" && next === "/") {
|
|
91
|
+
inBlockComment = false;
|
|
92
|
+
j += 2;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
j += 1;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (ch === "/" && next === "/") {
|
|
100
|
+
break; // line comment: ignore the rest of the line
|
|
101
|
+
}
|
|
102
|
+
if (ch === "/" && next === "*") {
|
|
103
|
+
inBlockComment = true;
|
|
104
|
+
j += 2;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (ch === '"' || ch === "'") {
|
|
108
|
+
const quote = ch;
|
|
109
|
+
j += 1;
|
|
110
|
+
while (j < line.length) {
|
|
111
|
+
if (line[j] === "\\") {
|
|
112
|
+
j += 2;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (line[j] === quote) {
|
|
116
|
+
j += 1;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
j += 1;
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
result += ch;
|
|
124
|
+
j += 1;
|
|
125
|
+
}
|
|
126
|
+
out[i] = result;
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
export function computeLineBraceDepths(lines, strippedLines) {
|
|
131
|
+
const stripped = strippedLines ?? stripBraceNoise(lines);
|
|
71
132
|
const depths = new Array(lines.length).fill(0);
|
|
72
133
|
let depth = 0;
|
|
73
|
-
for (let i = 0; i <
|
|
134
|
+
for (let i = 0; i < stripped.length; i += 1) {
|
|
74
135
|
// Entry depth for this line = depth observed before any brace on it.
|
|
75
136
|
depths[i] = depth;
|
|
76
|
-
const
|
|
77
|
-
.replace(/\/\/.*/g, "")
|
|
78
|
-
.replace(/"(?:\\.|[^"\\])*"/g, "\"\"")
|
|
79
|
-
.replace(/'(?:\\.|[^'\\])*'/g, "''");
|
|
80
|
-
for (const char of stripped) {
|
|
137
|
+
for (const char of stripped[i] ?? "") {
|
|
81
138
|
if (char === "{") {
|
|
82
139
|
depth += 1;
|
|
83
140
|
}
|
|
@@ -88,23 +145,23 @@ export function computeLineBraceDepths(lines) {
|
|
|
88
145
|
}
|
|
89
146
|
return depths;
|
|
90
147
|
}
|
|
91
|
-
export function computeBraceRange(lines, symbols, simpleName) {
|
|
148
|
+
export function computeBraceRange(lines, symbols, simpleName, strippedLines) {
|
|
92
149
|
const classSymbol = symbols.find((symbol) => (symbol.symbolKind === "class" || symbol.symbolKind === "interface"
|
|
93
150
|
|| symbol.symbolKind === "enum" || symbol.symbolKind === "record")
|
|
94
151
|
&& symbol.symbolName === simpleName);
|
|
95
152
|
if (!classSymbol) {
|
|
96
153
|
return undefined;
|
|
97
154
|
}
|
|
98
|
-
return scanBraceRange(lines, classSymbol.line);
|
|
155
|
+
return scanBraceRange(lines, classSymbol.line, strippedLines);
|
|
99
156
|
}
|
|
100
|
-
export function scanBraceRange(lines, declarationLine) {
|
|
157
|
+
export function scanBraceRange(lines, declarationLine, strippedLines) {
|
|
158
|
+
// Strip from the start of the file so multi-line block-comment state is correct
|
|
159
|
+
// by the time we reach declarationLine; indices stay aligned with `lines`.
|
|
160
|
+
const stripped = strippedLines ?? stripBraceNoise(lines);
|
|
101
161
|
let depth = 0;
|
|
102
162
|
let started = false;
|
|
103
|
-
for (let i = declarationLine - 1; i <
|
|
104
|
-
const
|
|
105
|
-
.replace(/\/\/.*/g, "")
|
|
106
|
-
.replace(/"(?:\\.|[^"\\])*"/g, "\"\"");
|
|
107
|
-
for (const char of stripped) {
|
|
163
|
+
for (let i = declarationLine - 1; i < stripped.length; i += 1) {
|
|
164
|
+
for (const char of stripped[i] ?? "") {
|
|
108
165
|
if (char === "{") {
|
|
109
166
|
depth += 1;
|
|
110
167
|
started = true;
|
|
@@ -119,7 +176,7 @@ export function scanBraceRange(lines, declarationLine) {
|
|
|
119
176
|
}
|
|
120
177
|
return { declarationLine, endLine: lines.length };
|
|
121
178
|
}
|
|
122
|
-
export function computeNestedTypeRanges(lines, symbols, outerBody) {
|
|
179
|
+
export function computeNestedTypeRanges(lines, symbols, outerBody, strippedLines) {
|
|
123
180
|
const ranges = [];
|
|
124
181
|
for (const candidate of symbols) {
|
|
125
182
|
if (candidate.symbolKind !== "class" && candidate.symbolKind !== "interface"
|
|
@@ -132,7 +189,7 @@ export function computeNestedTypeRanges(lines, symbols, outerBody) {
|
|
|
132
189
|
if (ranges.some((range) => candidate.line >= range.declarationLine && candidate.line <= range.endLine)) {
|
|
133
190
|
continue;
|
|
134
191
|
}
|
|
135
|
-
const nestedRange = scanBraceRange(lines, candidate.line);
|
|
192
|
+
const nestedRange = scanBraceRange(lines, candidate.line, strippedLines);
|
|
136
193
|
ranges.push(nestedRange);
|
|
137
194
|
}
|
|
138
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>;
|
|
@@ -5,9 +5,37 @@ import { ERROR_CODES, createError, isAppError } from "../errors.js";
|
|
|
5
5
|
import * as artifactResolver from "./artifact-resolver.js";
|
|
6
6
|
import * as classSourceHelpers from "./class-source-helpers.js";
|
|
7
7
|
import { buildClassSourceSnippet } from "./class-source/snippet-builder.js";
|
|
8
|
-
import { remapAndCountMembers, sliceMembersWithLimit } from "./class-source/members-builder.js";
|
|
8
|
+
import { remapAndCountMembers, sliceMembersWithLimit, projectMembersForWire } from "./class-source/members-builder.js";
|
|
9
|
+
import { buildPageContextKey, encodeOffsetCursor, resolveCursorOffset } from "../page-cursor.js";
|
|
9
10
|
import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
11
|
+
import { isUnobfuscatedVersion } from "../version-service.js";
|
|
10
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
|
+
}
|
|
11
39
|
function normalizeStrictPositiveInt(value, field) {
|
|
12
40
|
if (value == null) {
|
|
13
41
|
return undefined;
|
|
@@ -34,15 +62,6 @@ function normalizeMemberAccess(access) {
|
|
|
34
62
|
details: { access }
|
|
35
63
|
});
|
|
36
64
|
}
|
|
37
|
-
function buildResolveArtifactParams(target, extra = {}) {
|
|
38
|
-
return {
|
|
39
|
-
target: {
|
|
40
|
-
kind: target.kind,
|
|
41
|
-
value: target.value
|
|
42
|
-
},
|
|
43
|
-
...extra
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
65
|
function looksLikeDeobfuscatedClassName(value) {
|
|
47
66
|
const trimmed = value.trim();
|
|
48
67
|
if (!trimmed) {
|
|
@@ -226,7 +245,10 @@ export function buildDecompiledFallback(svc, artifactId, lookupClassName, member
|
|
|
226
245
|
let constructors = filterByPattern(extracted.constructors);
|
|
227
246
|
let fields = filterByPattern(extracted.fields);
|
|
228
247
|
let methods = filterByPattern(extracted.methods);
|
|
229
|
-
const
|
|
248
|
+
const constructorsBefore = constructors.length;
|
|
249
|
+
const fieldsBefore = fields.length;
|
|
250
|
+
const methodsBefore = methods.length;
|
|
251
|
+
const totalBefore = constructorsBefore + fieldsBefore + methodsBefore;
|
|
230
252
|
if (totalBefore === 0) {
|
|
231
253
|
return undefined;
|
|
232
254
|
}
|
|
@@ -250,13 +272,17 @@ export function buildDecompiledFallback(svc, artifactId, lookupClassName, member
|
|
|
250
272
|
origin: "source-extracted"
|
|
251
273
|
},
|
|
252
274
|
counts: {
|
|
253
|
-
constructors:
|
|
254
|
-
fields:
|
|
255
|
-
methods:
|
|
256
|
-
total:
|
|
257
|
-
}
|
|
275
|
+
constructors: constructorsBefore,
|
|
276
|
+
fields: fieldsBefore,
|
|
277
|
+
methods: methodsBefore,
|
|
278
|
+
total: totalBefore
|
|
279
|
+
},
|
|
280
|
+
truncated: totalBefore > maxMembers
|
|
258
281
|
};
|
|
259
282
|
}
|
|
283
|
+
// The class-like symbol kinds findClass returns. MUST stay in sync with the JS-side
|
|
284
|
+
// isTypeSymbol checks below; pushed down to SQL so non-type rows are never fetched.
|
|
285
|
+
const TYPE_SYMBOL_KINDS = ["class", "interface", "enum", "record"];
|
|
260
286
|
export function findClass(svc, input) {
|
|
261
287
|
const className = input.className.trim();
|
|
262
288
|
if (!className) {
|
|
@@ -278,12 +304,20 @@ export function findClass(svc, input) {
|
|
|
278
304
|
const warnings = [];
|
|
279
305
|
const isQualified = className.includes(".");
|
|
280
306
|
if (isQualified) {
|
|
281
|
-
|
|
307
|
+
// The innermost simple name (handles both dot- and $-separated inner types).
|
|
308
|
+
const simpleName = className.split(/[.$]/).at(-1) ?? className;
|
|
309
|
+
const directPath = `${classNameToClassPath(className)}.java`;
|
|
310
|
+
// The extractor stores ONE (outer) qualifiedName per file, so a nested type's
|
|
311
|
+
// FQCN never equals any stored qualifiedName and its synthetic file path
|
|
312
|
+
// (pkg/Outer/Inner.java) does not exist. resolveClassFilePath maps the FQCN —
|
|
313
|
+
// inner classes included — to the real outer file, which we match on.
|
|
314
|
+
const resolvedFilePath = resolveClassFilePath(svc, artifactId, className);
|
|
282
315
|
const result = svc.symbolsRepo.findScopedSymbols({
|
|
283
316
|
artifactId,
|
|
284
|
-
query:
|
|
317
|
+
query: simpleName,
|
|
285
318
|
match: "exact",
|
|
286
|
-
|
|
319
|
+
symbolKinds: TYPE_SYMBOL_KINDS,
|
|
320
|
+
limit: limit * 5
|
|
287
321
|
});
|
|
288
322
|
const matches = result.items
|
|
289
323
|
.filter((row) => {
|
|
@@ -292,14 +326,22 @@ export function findClass(svc, input) {
|
|
|
292
326
|
if (!isTypeSymbol)
|
|
293
327
|
return false;
|
|
294
328
|
const rowQualified = row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
295
|
-
return rowQualified === className ||
|
|
329
|
+
return (rowQualified === className ||
|
|
330
|
+
row.filePath === directPath ||
|
|
331
|
+
(resolvedFilePath != null && row.filePath === resolvedFilePath));
|
|
332
|
+
})
|
|
333
|
+
.map((row) => {
|
|
334
|
+
const rowQualified = row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
335
|
+
// For an inner-class match the stored qualifiedName is the outer type; the
|
|
336
|
+
// caller asked for the full nested FQCN, so report that.
|
|
337
|
+
const isInnerMatch = rowQualified !== className && row.filePath !== directPath;
|
|
338
|
+
return {
|
|
339
|
+
qualifiedName: isInnerMatch ? className : rowQualified,
|
|
340
|
+
filePath: row.filePath,
|
|
341
|
+
line: row.line,
|
|
342
|
+
symbolKind: row.symbolKind
|
|
343
|
+
};
|
|
296
344
|
})
|
|
297
|
-
.map((row) => ({
|
|
298
|
-
qualifiedName: row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", "."),
|
|
299
|
-
filePath: row.filePath,
|
|
300
|
-
line: row.line,
|
|
301
|
-
symbolKind: row.symbolKind
|
|
302
|
-
}))
|
|
303
345
|
.slice(0, limit);
|
|
304
346
|
const partialVanillaLookup = hasPartialNetMinecraftCoverage(artifact.qualityFlags) && looksLikeDeobfuscatedClassName(className);
|
|
305
347
|
const filteredMatches = partialVanillaLookup && matches.every((match) => !match.qualifiedName.startsWith("net.minecraft.") && !match.qualifiedName.startsWith("com.mojang."))
|
|
@@ -317,6 +359,7 @@ export function findClass(svc, input) {
|
|
|
317
359
|
artifactId,
|
|
318
360
|
query: className,
|
|
319
361
|
match: "exact",
|
|
362
|
+
symbolKinds: TYPE_SYMBOL_KINDS,
|
|
320
363
|
limit: limit * 5
|
|
321
364
|
});
|
|
322
365
|
const matches = [];
|
|
@@ -423,7 +466,7 @@ export async function getClassSource(svc, input) {
|
|
|
423
466
|
const artifact = svc.getArtifact(artifactId);
|
|
424
467
|
artifactId = artifact.artifactId;
|
|
425
468
|
origin = artifact.origin;
|
|
426
|
-
requestedMapping = artifact.requestedMapping ?? requestedMapping;
|
|
469
|
+
requestedMapping = input.mapping != null ? requestedMapping : (artifact.requestedMapping ?? requestedMapping);
|
|
427
470
|
mappingApplied = artifact.mappingApplied ?? requestedMapping;
|
|
428
471
|
provenance = artifact.provenance;
|
|
429
472
|
qualityFlags = artifact.qualityFlags;
|
|
@@ -440,6 +483,7 @@ export async function getClassSource(svc, input) {
|
|
|
440
483
|
preferProjectVersion: input.preferProjectVersion,
|
|
441
484
|
warnings
|
|
442
485
|
});
|
|
486
|
+
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
443
487
|
let activeArtifactId = artifactId;
|
|
444
488
|
let activeOrigin = origin;
|
|
445
489
|
let activeProvenance = provenance;
|
|
@@ -587,6 +631,27 @@ export async function getClassSource(svc, input) {
|
|
|
587
631
|
requestedMapping,
|
|
588
632
|
mappingApplied: activeMappingApplied
|
|
589
633
|
});
|
|
634
|
+
const nextStartLine = snippet.nextStartLine;
|
|
635
|
+
// Continuation guidance: when output was truncated and was not redirected to
|
|
636
|
+
// a file, hand the caller the next line to read plus a replayable call that
|
|
637
|
+
// re-reads from the already-resolved artifact (no re-resolution needed).
|
|
638
|
+
// nextStartLine is only set for snippet/full mode, so `mode` here is never
|
|
639
|
+
// "metadata". The caller's original endLine window is preserved so the
|
|
640
|
+
// continuation never reads past the requested range.
|
|
641
|
+
const continuation = nextStartLine != null && !resolvedOutputFile
|
|
642
|
+
? buildSuggestedCall({
|
|
643
|
+
tool: "get-class-source",
|
|
644
|
+
params: {
|
|
645
|
+
className,
|
|
646
|
+
target: { kind: "artifact", artifactId: activeArtifactId },
|
|
647
|
+
mode,
|
|
648
|
+
startLine: nextStartLine,
|
|
649
|
+
...(input.endLine != null ? { endLine: input.endLine } : {}),
|
|
650
|
+
...(maxLines != null ? { maxLines } : {}),
|
|
651
|
+
...(input.maxChars != null ? { maxChars: input.maxChars } : {})
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
: undefined;
|
|
590
655
|
return {
|
|
591
656
|
className,
|
|
592
657
|
mode,
|
|
@@ -598,6 +663,8 @@ export async function getClassSource(svc, input) {
|
|
|
598
663
|
},
|
|
599
664
|
truncated,
|
|
600
665
|
...(charsTruncated ? { charsTruncated } : {}),
|
|
666
|
+
...(snippet.outOfRange ? { outOfRange: true } : {}),
|
|
667
|
+
...(nextStartLine != null ? { nextStartLine } : {}),
|
|
601
668
|
origin: activeOrigin,
|
|
602
669
|
artifactId: activeArtifactId,
|
|
603
670
|
requestedMapping,
|
|
@@ -611,6 +678,7 @@ export async function getClassSource(svc, input) {
|
|
|
611
678
|
isDecompiled: activeOrigin === "decompiled",
|
|
612
679
|
qualityFlags: activeQualityFlags
|
|
613
680
|
}),
|
|
681
|
+
...(continuation?.suggestedCall ? { suggestedCall: continuation.suggestedCall } : {}),
|
|
614
682
|
...(resolvedOutputFile ? { outputFile: resolvedOutputFile } : {}),
|
|
615
683
|
warnings
|
|
616
684
|
};
|
|
@@ -629,7 +697,7 @@ export async function getClassMembers(svc, input) {
|
|
|
629
697
|
const includeInherited = input.includeInherited ?? false;
|
|
630
698
|
const memberPattern = normalizeOptionalString(input.memberPattern);
|
|
631
699
|
const parsedMaxMembers = normalizeStrictPositiveInt(input.maxMembers, "maxMembers");
|
|
632
|
-
const maxMembers = parsedMaxMembers == null ?
|
|
700
|
+
const maxMembers = parsedMaxMembers == null ? 150 : Math.min(parsedMaxMembers, 5000);
|
|
633
701
|
const normalizedArtifactId = normalizeOptionalString(input.artifactId);
|
|
634
702
|
if (normalizedArtifactId && input.target) {
|
|
635
703
|
throw createError({
|
|
@@ -701,6 +769,7 @@ export async function getClassMembers(svc, input) {
|
|
|
701
769
|
preferProjectVersion: input.preferProjectVersion,
|
|
702
770
|
warnings
|
|
703
771
|
});
|
|
772
|
+
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
704
773
|
if (requestedMapping !== "obfuscated" && !version) {
|
|
705
774
|
throw createError({
|
|
706
775
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
@@ -708,10 +777,7 @@ export async function getClassMembers(svc, input) {
|
|
|
708
777
|
details: {
|
|
709
778
|
mapping: requestedMapping,
|
|
710
779
|
nextAction: "Resolve with target: { kind: \"version\", value: ... } or specify a versioned coordinate.",
|
|
711
|
-
...buildSuggestedCall({
|
|
712
|
-
tool: "resolve-artifact",
|
|
713
|
-
params: buildResolveArtifactParams({ kind: "version", value: "latest" })
|
|
714
|
-
})
|
|
780
|
+
...buildSuggestedCall({ tool: "list-versions", params: {} })
|
|
715
781
|
}
|
|
716
782
|
});
|
|
717
783
|
}
|
|
@@ -759,7 +825,23 @@ export async function getClassMembers(svc, input) {
|
|
|
759
825
|
}
|
|
760
826
|
catch (error) {
|
|
761
827
|
if (isAppError(error) && error.code === ERROR_CODES.CLASS_NOT_FOUND) {
|
|
762
|
-
|
|
828
|
+
// Re-raise with the shared recovery shape (find-class/api-matrix
|
|
829
|
+
// suggestedCall, namespace + scope hints) instead of the sparse bytecode
|
|
830
|
+
// error, so members and source agree on CLASS_NOT_FOUND guidance.
|
|
831
|
+
throw buildClassSourceNotFoundError(svc, {
|
|
832
|
+
artifactId,
|
|
833
|
+
className,
|
|
834
|
+
lookupClassName,
|
|
835
|
+
mappingApplied,
|
|
836
|
+
requestedMapping,
|
|
837
|
+
qualityFlags,
|
|
838
|
+
attemptedBinaryFallback: true,
|
|
839
|
+
targetKind: input.target?.kind,
|
|
840
|
+
targetValue: input.target && "value" in input.target ? input.target.value : undefined,
|
|
841
|
+
scope: input.scope,
|
|
842
|
+
projectPath: input.projectPath,
|
|
843
|
+
version
|
|
844
|
+
});
|
|
763
845
|
}
|
|
764
846
|
binaryExtractionFailed = true;
|
|
765
847
|
binaryExtractionFailureReason = error instanceof Error ? error.message : String(error);
|
|
@@ -787,11 +869,29 @@ export async function getClassMembers(svc, input) {
|
|
|
787
869
|
warnings
|
|
788
870
|
});
|
|
789
871
|
const counts = remapped.counts;
|
|
790
|
-
|
|
872
|
+
// Offset cursor over the flat [constructors, fields, methods] member sequence.
|
|
873
|
+
// The context key ties a cursor to this exact query so a stale cursor restarts.
|
|
874
|
+
const memberCursorContext = buildPageContextKey([
|
|
875
|
+
artifactId,
|
|
876
|
+
lookupClassName,
|
|
877
|
+
requestedMapping,
|
|
878
|
+
mappingApplied,
|
|
879
|
+
access,
|
|
880
|
+
includeSynthetic,
|
|
881
|
+
includeInherited,
|
|
882
|
+
memberPattern
|
|
883
|
+
]);
|
|
884
|
+
const { offset: memberOffset, cursorIgnored: memberCursorIgnored } = resolveCursorOffset(input.cursor, memberCursorContext);
|
|
885
|
+
const sliced = sliceMembersWithLimit(remapped, counts.total, maxMembers, warnings, memberOffset);
|
|
791
886
|
const constructors = sliced.constructors;
|
|
792
887
|
const fields = sliced.fields;
|
|
793
888
|
const methods = sliced.methods;
|
|
889
|
+
// Slim the wire member shape: hoist a shared ownerFqn, drop accessFlags, omit
|
|
890
|
+
// isSynthetic:false, and drop FIELD jvmDescriptor unless includeDescriptors.
|
|
891
|
+
// Internal SignatureMember arrays above stay intact.
|
|
892
|
+
const projectedMembers = projectMembersForWire({ constructors, fields, methods }, includeInherited, input.includeDescriptors ?? false);
|
|
794
893
|
const truncated = sliced.truncated;
|
|
894
|
+
const nextCursor = sliced.nextOffset != null ? encodeOffsetCursor(sliced.nextOffset, memberCursorContext) : undefined;
|
|
795
895
|
const normalizedProvenance = provenance ??
|
|
796
896
|
buildFallbackProvenance(svc, {
|
|
797
897
|
artifactId,
|
|
@@ -819,6 +919,12 @@ export async function getClassMembers(svc, input) {
|
|
|
819
919
|
warnings.push("Bytecode member enumeration returned zero; populated decompiledFallback from decompiled source. "
|
|
820
920
|
+ "Descriptors and access modifiers are unavailable — use get-class-source for full details."
|
|
821
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
|
+
}
|
|
822
928
|
if (namespaceMismatch && memberPattern) {
|
|
823
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.`);
|
|
824
930
|
}
|
|
@@ -843,7 +949,7 @@ export async function getClassMembers(svc, input) {
|
|
|
843
949
|
suggestedCall = buildSuggestedCall({
|
|
844
950
|
tool: "get-class-source",
|
|
845
951
|
params: {
|
|
846
|
-
target: {
|
|
952
|
+
target: { kind: "artifact", artifactId },
|
|
847
953
|
className,
|
|
848
954
|
mode: "snippet",
|
|
849
955
|
mapping: requestedMapping
|
|
@@ -861,13 +967,11 @@ export async function getClassMembers(svc, input) {
|
|
|
861
967
|
}
|
|
862
968
|
return {
|
|
863
969
|
className,
|
|
864
|
-
members:
|
|
865
|
-
constructors,
|
|
866
|
-
fields,
|
|
867
|
-
methods
|
|
868
|
-
},
|
|
970
|
+
members: projectedMembers,
|
|
869
971
|
counts,
|
|
870
972
|
truncated,
|
|
973
|
+
...(nextCursor ? { nextCursor } : {}),
|
|
974
|
+
...(memberCursorIgnored ? { cursorIgnored: true } : {}),
|
|
871
975
|
context: signatureContext,
|
|
872
976
|
origin,
|
|
873
977
|
artifactId,
|
|
@@ -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
|
@@ -43,8 +43,8 @@ export async function indexArtifact(svc, input) {
|
|
|
43
43
|
}
|
|
44
44
|
const artifact = svc.getArtifact(artifactId);
|
|
45
45
|
const force = input.force ?? false;
|
|
46
|
-
const hasFiles = svc.filesRepo.listFiles(artifact.artifactId, { limit: 1 }).items.length > 0;
|
|
47
46
|
const meta = svc.indexMetaRepo.get(artifact.artifactId);
|
|
47
|
+
const hasFiles = meta ? meta.filesCount > 0 : false;
|
|
48
48
|
const expectedSignature = artifact.artifactSignature ?? fallbackArtifactSignature(artifact.artifactId);
|
|
49
49
|
const reason = resolveIndexRebuildReason({
|
|
50
50
|
force,
|
|
@@ -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
|
});
|
|
@@ -291,8 +294,11 @@ export function getArtifact(svc, artifactId) {
|
|
|
291
294
|
}
|
|
292
295
|
export async function ingestIfNeeded(svc, resolved) {
|
|
293
296
|
const existing = svc.artifactsRepo.getArtifact(resolved.artifactId);
|
|
294
|
-
const hasFiles = svc.filesRepo.listFiles(resolved.artifactId, { limit: 1 }).items.length > 0;
|
|
295
297
|
const meta = svc.indexMetaRepo.get(resolved.artifactId);
|
|
298
|
+
// Derive hasFiles from meta instead of a separate listFiles probe: when meta is
|
|
299
|
+
// absent the reason is "missing_meta" regardless of hasFiles, and when present
|
|
300
|
+
// meta.filesCount is the authoritative count written alongside the file rows.
|
|
301
|
+
const hasFiles = meta ? meta.filesCount > 0 : false;
|
|
296
302
|
const reason = resolveIndexRebuildReason({
|
|
297
303
|
force: false,
|
|
298
304
|
expectedSignature: resolved.artifactSignature,
|
|
@@ -333,6 +339,13 @@ export async function ingestIfNeeded(svc, resolved) {
|
|
|
333
339
|
const inflight = svc.state.inflightArtifactIngests.get(resolved.artifactId);
|
|
334
340
|
if (inflight) {
|
|
335
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
|
+
}
|
|
336
349
|
return;
|
|
337
350
|
}
|
|
338
351
|
const ingestPromise = rebuildMissingArtifactIndex(svc, resolved, reason);
|
|
@@ -426,9 +439,16 @@ export async function maybeRemapBinaryForMojang(svc, resolved, deps = defaultBin
|
|
|
426
439
|
if (inflight) {
|
|
427
440
|
return inflight;
|
|
428
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;
|
|
429
449
|
const remapPromise = runBinaryRemapWithDeps(svc, {
|
|
430
450
|
version: resolved.version,
|
|
431
|
-
inputJar
|
|
451
|
+
inputJar,
|
|
432
452
|
remappedDir,
|
|
433
453
|
remappedJarPath
|
|
434
454
|
}, deps);
|