@adhisang/minecraft-modding-mcp 6.3.0 → 7.0.0-rc.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 +62 -0
- package/README.md +13 -3
- package/dist/cache-policy.d.ts +71 -0
- package/dist/cache-policy.js +83 -0
- package/dist/cache-registry.js +6 -6
- package/dist/cli.js +74 -3
- package/dist/compat-stdio-transport.d.ts +1 -1
- package/dist/compat-stdio-transport.js +13 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -2
- package/dist/decompiler/vineflower.d.ts +1 -0
- package/dist/decompiler/vineflower.js +8 -5
- package/dist/entry-tools/analyze-mod-service.d.ts +70 -136
- package/dist/entry-tools/analyze-symbol-service.d.ts +112 -150
- package/dist/entry-tools/compare-minecraft-service.d.ts +59 -145
- package/dist/entry-tools/entry-tool-schema.d.ts +38 -4
- package/dist/entry-tools/entry-tool-schema.js +4 -1
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +235 -799
- package/dist/entry-tools/inspect-minecraft/internal.js +50 -13
- package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
- package/dist/entry-tools/manage-cache-service.d.ts +81 -91
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +164 -592
- package/dist/entry-tools/verify-mixin-target-service.d.ts +3 -19
- package/dist/era-classifier.d.ts +161 -0
- package/dist/era-classifier.js +292 -0
- package/dist/error-mapping.js +9 -2
- package/dist/index.d.ts +42 -4
- package/dist/index.js +636 -473
- package/dist/java-process.d.ts +2 -0
- package/dist/java-process.js +22 -2
- package/dist/json-rpc-framing.d.ts +77 -1
- package/dist/json-rpc-framing.js +249 -13
- package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
- package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
- package/dist/mapping/loaders/tiny-loom.js +45 -33
- package/dist/mapping/loaders/tiny-maven.js +6 -11
- package/dist/mapping/parsers/tiny.d.ts +57 -0
- package/dist/mapping/parsers/tiny.js +99 -22
- package/dist/mapping-service.d.ts +19 -0
- package/dist/mapping-service.js +93 -9
- package/dist/mcp-helpers.d.ts +19 -2
- package/dist/mcp-helpers.js +48 -6
- package/dist/minecraft-explorer-service.d.ts +1 -1
- package/dist/mixin/types.d.ts +8 -0
- package/dist/mod-analyzer.js +7 -7
- package/dist/mod-decompile-service.js +1 -0
- package/dist/nbt/java-nbt-codec.js +12 -2
- package/dist/nbt/json-patch.js +14 -3
- package/dist/nbt/pipeline.js +40 -3
- package/dist/nbt/typed-json.js +26 -1
- package/dist/registration-adapter.d.ts +32 -0
- package/dist/registration-adapter.js +52 -0
- package/dist/request-context.d.ts +7 -0
- package/dist/request-context.js +9 -0
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +25 -19
- package/dist/server-identity.d.ts +27 -0
- package/dist/server-identity.js +26 -0
- package/dist/source/access-validate.js +53 -0
- package/dist/source/artifact-resolver.d.ts +69 -1
- package/dist/source/artifact-resolver.js +215 -14
- package/dist/source/class-source.d.ts +21 -0
- package/dist/source/class-source.js +125 -28
- package/dist/source/did-you-mean.d.ts +12 -1
- package/dist/source/did-you-mean.js +6 -2
- package/dist/source/file-access.js +150 -46
- package/dist/source/indexer.js +1 -0
- package/dist/source/shared-utils.d.ts +21 -0
- package/dist/source/shared-utils.js +23 -0
- package/dist/source-service.d.ts +11 -0
- package/dist/stdio-supervisor.d.ts +357 -2
- package/dist/stdio-supervisor.js +1031 -80
- package/dist/storage/db.d.ts +2 -1
- package/dist/storage/db.js +15 -8
- package/dist/synthetic-decorator.d.ts +24 -0
- package/dist/synthetic-decorator.js +48 -0
- package/dist/tool-guidance.d.ts +17 -1
- package/dist/tool-guidance.js +309 -11
- package/dist/tool-schema-registry.d.ts +2 -0
- package/dist/tool-schema-registry.js +4 -0
- package/dist/tool-schemas.d.ts +2212 -3919
- package/dist/tool-schemas.js +33 -7
- package/dist/types.d.ts +35 -0
- package/dist/v1-parity-schemas.d.ts +7 -0
- package/dist/v1-parity-schemas.js +5584 -0
- package/dist/version-diff-service.d.ts +33 -0
- package/dist/version-diff-service.js +148 -3
- package/dist/version-service.js +36 -14
- package/dist/warning-details.js +18 -1
- package/docs/README-ja.md +5 -3
- package/docs/tool-reference.md +194 -19
- package/package.json +8 -5
|
@@ -14,6 +14,7 @@ import { resolveTinyRemapperJar } from "../tiny-remapper-resolver.js";
|
|
|
14
14
|
import { isUnobfuscatedVersion } from "../version-service.js";
|
|
15
15
|
import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
16
16
|
const VERSION_TOKEN_REGEX_CACHE = new Map();
|
|
17
|
+
const LOADER_VERSION_TOKEN_REGEX_CACHE = new Map();
|
|
17
18
|
const MAX_HELPER_REGEX_CACHE = 128;
|
|
18
19
|
function rememberCachedRegex(cache, key, regex) {
|
|
19
20
|
if (cache.size >= MAX_HELPER_REGEX_CACHE) {
|
|
@@ -57,6 +58,97 @@ export function hasExactVersionToken(path, version) {
|
|
|
57
58
|
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}(?![0-9a-z]|\\.[0-9])`, "i"));
|
|
58
59
|
return pattern.test(normalizedPath);
|
|
59
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* True when a path carries the NeoForge/Forge loader version that corresponds
|
|
63
|
+
* 1:1 to `mcVersion`.
|
|
64
|
+
*
|
|
65
|
+
* ModDevGradle names every artifact after the LOADER version and never after
|
|
66
|
+
* Minecraft: MC 1.21.11 produces `build/moddev/artifacts/neoforge-21.11.38-beta-merged.jar`.
|
|
67
|
+
* A plain `hasExactVersionToken(path, "1.21.11")` therefore rejected every
|
|
68
|
+
* artifact of the canonical NeoForge workspace, which made
|
|
69
|
+
* validate-access-transformer unusable there.
|
|
70
|
+
*
|
|
71
|
+
* NeoForge derives its version from Minecraft as `<minor>.<patch>.<build>`, so
|
|
72
|
+
* `1.21.11` -> `21.11.<build>` and `1.21` -> `21.0.<build>`. The trailing dot
|
|
73
|
+
* before the build number keeps `21.1.` from matching `21.10.5`.
|
|
74
|
+
*/
|
|
75
|
+
export function hasLoaderRuntimeVersionToken(path, mcVersion) {
|
|
76
|
+
const match = /^1\.(\d+)(?:\.(\d+))?$/.exec(mcVersion.trim());
|
|
77
|
+
if (!match) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const minor = match[1];
|
|
81
|
+
const patch = match[2] ?? "0";
|
|
82
|
+
const normalizedPath = normalizePathStyle(path).toLowerCase();
|
|
83
|
+
const cacheKey = `loader:${minor}.${patch}`;
|
|
84
|
+
const pattern = LOADER_VERSION_TOKEN_REGEX_CACHE.get(cacheKey)
|
|
85
|
+
?? rememberCachedRegex(LOADER_VERSION_TOKEN_REGEX_CACHE, cacheKey, new RegExp(`(^|[^0-9a-z.])${minor}\\.${patch}\\.\\d`, "i"));
|
|
86
|
+
return pattern.test(normalizedPath);
|
|
87
|
+
}
|
|
88
|
+
const RUNTIME_JAR_VERSION_REGEX = /(?:^|[^0-9.])(1\.\d+(?:\.\d+)?)(?![0-9.])/;
|
|
89
|
+
/**
|
|
90
|
+
* Minecraft version a runtime jar path carries, or undefined when the path
|
|
91
|
+
* names none. Loom lays its cache out as
|
|
92
|
+
* `<gradle>/caches/fabric-loom/<mcVersion>/...`, so the first `1.x[.y]` token
|
|
93
|
+
* of the path is the version the jar was built for.
|
|
94
|
+
*/
|
|
95
|
+
export function inferRuntimeJarMinecraftVersion(path) {
|
|
96
|
+
return RUNTIME_JAR_VERSION_REGEX.exec(normalizePathStyle(path))?.[1];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Loader a runtime jar belongs to, read from its path.
|
|
100
|
+
*
|
|
101
|
+
* A Loom cache holds NeoForge-patched jars under a `/neoforge/` segment
|
|
102
|
+
* (`caches/fabric-loom/1.21.10/neoforge/21.10.50-beta/minecraft-merged-mojang-at-patched.jar`).
|
|
103
|
+
* Serving one of those to a Fabric workspace silently validated a Fabric access
|
|
104
|
+
* widener against NeoForge bytecode, so the loader has to travel with the jar.
|
|
105
|
+
*/
|
|
106
|
+
export function inferRuntimeJarLoader(path) {
|
|
107
|
+
const lower = normalizePathStyle(path).toLowerCase();
|
|
108
|
+
if (lower.includes("neoforge") || lower.includes("neoform") || lower.includes("moddev")) {
|
|
109
|
+
return "neoforge";
|
|
110
|
+
}
|
|
111
|
+
if (/(^|[/\-_])forge([/\-_.]|$)/.test(lower) || lower.includes("forge_gradle") || lower.includes("srg")) {
|
|
112
|
+
return "forge";
|
|
113
|
+
}
|
|
114
|
+
if (lower.includes("fabric-loom") || lower.includes("loom-cache") || lower.includes("intermediary")) {
|
|
115
|
+
return "fabric";
|
|
116
|
+
}
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Fills in the truthful version/loader half of a runtime provenance record.
|
|
121
|
+
*
|
|
122
|
+
* `version` becomes the version the SERVED jar carries; the caller's original
|
|
123
|
+
* request is preserved under `requestedVersion` and flagged. A served loader
|
|
124
|
+
* that contradicts a KNOWN expected loader is flagged too — never silently
|
|
125
|
+
* dropped.
|
|
126
|
+
*/
|
|
127
|
+
export function describeServedRuntimeJar(input) {
|
|
128
|
+
const notes = [];
|
|
129
|
+
const servedVersion = inferRuntimeJarMinecraftVersion(input.jarPath);
|
|
130
|
+
const versionApproximated = servedVersion !== undefined && servedVersion !== input.requestedVersion.trim();
|
|
131
|
+
const servedLoader = inferRuntimeJarLoader(input.jarPath);
|
|
132
|
+
const expectedLoader = input.expectedLoader;
|
|
133
|
+
const loaderMismatch = expectedLoader !== undefined &&
|
|
134
|
+
expectedLoader !== "unknown" &&
|
|
135
|
+
servedLoader !== "unknown" &&
|
|
136
|
+
servedLoader !== expectedLoader;
|
|
137
|
+
if (versionApproximated) {
|
|
138
|
+
notes.push(`Runtime jar is Minecraft ${servedVersion}, not the requested ${input.requestedVersion}; results are approximate.`);
|
|
139
|
+
}
|
|
140
|
+
if (loaderMismatch) {
|
|
141
|
+
notes.push(`Runtime jar is a ${servedLoader} artifact while the workspace is ${expectedLoader}; its bytecode differs from the ${expectedLoader} runtime.`);
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
version: versionApproximated && servedVersion ? servedVersion : input.requestedVersion,
|
|
145
|
+
...(versionApproximated ? { requestedVersion: input.requestedVersion, versionApproximated: true } : {}),
|
|
146
|
+
servedLoader,
|
|
147
|
+
...(expectedLoader ? { expectedLoader } : {}),
|
|
148
|
+
...(loaderMismatch ? { loaderMismatch: true } : {}),
|
|
149
|
+
notes
|
|
150
|
+
};
|
|
151
|
+
}
|
|
60
152
|
function inferMergedRuntimeNamespaceHint(path) {
|
|
61
153
|
const normalizedPath = normalizePathStyle(path).toLowerCase();
|
|
62
154
|
if (normalizedPath.includes("merged-intermediary-v2") ||
|
|
@@ -407,6 +499,11 @@ export async function discoverAccessTransformerRuntimeCandidates(_svc, input) {
|
|
|
407
499
|
const normalizedProjectPathLower = normalizedProjectPath
|
|
408
500
|
? normalizePathStyle(normalizedProjectPath).toLowerCase()
|
|
409
501
|
: undefined;
|
|
502
|
+
// A jar inside the caller's own build directory belongs to the version that
|
|
503
|
+
// workspace declares, whatever its filename says.
|
|
504
|
+
const projectAnchorAllowed = normalizedProjectPathLower !== undefined &&
|
|
505
|
+
input.projectMinecraftVersion !== undefined &&
|
|
506
|
+
input.projectMinecraftVersion.trim() === input.version.trim();
|
|
410
507
|
const searchRoots = buildLoaderRuntimeSearchRoots({
|
|
411
508
|
projectPath: normalizedProjectPath,
|
|
412
509
|
gradleUserHome: input.gradleUserHome
|
|
@@ -449,7 +546,17 @@ export async function discoverAccessTransformerRuntimeCandidates(_svc, input) {
|
|
|
449
546
|
}
|
|
450
547
|
seen.add(normalizedPath);
|
|
451
548
|
const lower = normalizedPath.toLowerCase();
|
|
452
|
-
|
|
549
|
+
const insideProject = normalizedProjectPathLower !== undefined && lower.startsWith(normalizedProjectPathLower);
|
|
550
|
+
// Positive version evidence is REQUIRED; the three forms are ranked so a
|
|
551
|
+
// verbatim Minecraft version always beats an inferred one.
|
|
552
|
+
const versionEvidence = hasExactVersionToken(normalizedPath, input.version)
|
|
553
|
+
? "exact-token"
|
|
554
|
+
: hasLoaderRuntimeVersionToken(normalizedPath, input.version)
|
|
555
|
+
? "loader-token"
|
|
556
|
+
: projectAnchorAllowed && insideProject
|
|
557
|
+
? "project-anchored"
|
|
558
|
+
: undefined;
|
|
559
|
+
if (!versionEvidence) {
|
|
453
560
|
continue;
|
|
454
561
|
}
|
|
455
562
|
const looksMerged = lower.includes("merged");
|
|
@@ -457,6 +564,11 @@ export async function discoverAccessTransformerRuntimeCandidates(_svc, input) {
|
|
|
457
564
|
const looksForge = lower.includes("forge");
|
|
458
565
|
const looksNeoForge = lower.includes("neoforge") || lower.includes("moddev") || lower.includes("neoform");
|
|
459
566
|
const looksPatchedRuntime = lower.includes("patched") || lower.includes("client-extra") || lower.includes("joined");
|
|
567
|
+
// client-extra / *-minecraft-resources jars carry resources ONLY, so they
|
|
568
|
+
// can never answer a class lookup. They stay selectable (a workspace that
|
|
569
|
+
// has nothing else still gets an answer) but must lose to any real
|
|
570
|
+
// classes jar.
|
|
571
|
+
const looksResourcesOnly = lower.includes("client-extra") || lower.includes("minecraft-resources");
|
|
460
572
|
const appliedScope = looksMerged
|
|
461
573
|
? "merged"
|
|
462
574
|
: "loader";
|
|
@@ -470,18 +582,21 @@ export async function discoverAccessTransformerRuntimeCandidates(_svc, input) {
|
|
|
470
582
|
continue;
|
|
471
583
|
}
|
|
472
584
|
const score = 10_000 +
|
|
473
|
-
(
|
|
585
|
+
(versionEvidence === "exact-token" ? 5_000 : versionEvidence === "loader-token" ? 3_500 : 3_000) +
|
|
586
|
+
(insideProject ? 4_000 : 0) +
|
|
474
587
|
(looksPatchedRuntime ? 3_000 : 0) +
|
|
475
588
|
(looksSrg ? 2_500 : 0) +
|
|
476
589
|
(input.loader === "forge" && looksForge ? 1_500 : 0) +
|
|
477
590
|
(input.loader === "neoforge" && looksNeoForge ? 1_500 : 0) +
|
|
478
591
|
(input.requestedScope === appliedScope ? 1_000 : 0) +
|
|
479
|
-
(looksMerged ? -500 : 0)
|
|
592
|
+
(looksMerged ? -500 : 0) +
|
|
593
|
+
(looksResourcesOnly ? -6_000 : 0);
|
|
480
594
|
candidates.push({
|
|
481
595
|
jarPath: normalizedPath,
|
|
482
596
|
score,
|
|
483
597
|
appliedScope,
|
|
484
|
-
origin: "local-jar"
|
|
598
|
+
origin: "local-jar",
|
|
599
|
+
versionEvidence
|
|
485
600
|
});
|
|
486
601
|
}
|
|
487
602
|
}
|
|
@@ -504,6 +619,9 @@ export async function resolveAccessWidenerRuntimeArtifact(svc, input) {
|
|
|
504
619
|
const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
|
|
505
620
|
version = detected ?? version;
|
|
506
621
|
}
|
|
622
|
+
const expectedLoader = normalizedProjectPath
|
|
623
|
+
? await detectWorkspaceRuntimeLoader(svc, normalizedProjectPath)
|
|
624
|
+
: undefined;
|
|
507
625
|
const requestedScope = input.scope ?? (normalizedProjectPath ? "loader" : "vanilla");
|
|
508
626
|
if (requestedScope === "vanilla") {
|
|
509
627
|
const versionJar = await svc.versionService.resolveVersionJar(version);
|
|
@@ -574,9 +692,21 @@ export async function resolveAccessWidenerRuntimeArtifact(svc, input) {
|
|
|
574
692
|
notes.push(...detection.warnings);
|
|
575
693
|
}
|
|
576
694
|
}
|
|
695
|
+
// Provenance must describe the jar that was SERVED, never the request.
|
|
696
|
+
const served = describeServedRuntimeJar({
|
|
697
|
+
jarPath: discovery.selected.jarPath,
|
|
698
|
+
requestedVersion: version,
|
|
699
|
+
expectedLoader
|
|
700
|
+
});
|
|
701
|
+
notes.push(...served.notes);
|
|
577
702
|
return {
|
|
578
|
-
version,
|
|
703
|
+
version: served.version,
|
|
579
704
|
jarPath: discovery.selected.jarPath,
|
|
705
|
+
...(served.requestedVersion ? { requestedVersion: served.requestedVersion } : {}),
|
|
706
|
+
...(served.versionApproximated ? { versionApproximated: true } : {}),
|
|
707
|
+
servedLoader: served.servedLoader,
|
|
708
|
+
...(served.expectedLoader ? { expectedLoader: served.expectedLoader } : {}),
|
|
709
|
+
...(served.loaderMismatch ? { loaderMismatch: true } : {}),
|
|
580
710
|
requestedScope,
|
|
581
711
|
appliedScope,
|
|
582
712
|
requestedMapping: input.awNamespace,
|
|
@@ -586,6 +716,28 @@ export async function resolveAccessWidenerRuntimeArtifact(svc, input) {
|
|
|
586
716
|
scopeFallback
|
|
587
717
|
};
|
|
588
718
|
}
|
|
719
|
+
/**
|
|
720
|
+
* Loader a workspace declares, normalized onto {@link RuntimeLoader}. Quilt is
|
|
721
|
+
* Fabric-compatible for runtime-jar purposes; anything undetected stays
|
|
722
|
+
* "unknown" so no mismatch is ever asserted on a guess.
|
|
723
|
+
*/
|
|
724
|
+
async function detectWorkspaceRuntimeLoader(svc, projectPath) {
|
|
725
|
+
const detection = await svc.workspaceMappingService.detectProjectLoader(projectPath);
|
|
726
|
+
if (!detection.resolved) {
|
|
727
|
+
return "unknown";
|
|
728
|
+
}
|
|
729
|
+
switch (detection.loader) {
|
|
730
|
+
case "fabric":
|
|
731
|
+
case "quilt":
|
|
732
|
+
return "fabric";
|
|
733
|
+
case "forge":
|
|
734
|
+
return "forge";
|
|
735
|
+
case "neoforge":
|
|
736
|
+
return "neoforge";
|
|
737
|
+
default:
|
|
738
|
+
return "unknown";
|
|
739
|
+
}
|
|
740
|
+
}
|
|
589
741
|
export async function resolveAccessTransformerNamespace(svc, input) {
|
|
590
742
|
const explicit = normalizeAccessTransformerNamespace(input.atNamespace);
|
|
591
743
|
if (explicit) {
|
|
@@ -622,9 +774,15 @@ export async function resolveAccessTransformerNamespace(svc, input) {
|
|
|
622
774
|
export async function resolveAccessTransformerRuntimeArtifact(svc, input) {
|
|
623
775
|
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
624
776
|
let version = input.version;
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
777
|
+
// The workspace's own declared version is read whenever a project is given:
|
|
778
|
+
// preferProjectVersion decides whether it OVERRIDES the requested version,
|
|
779
|
+
// but discovery needs it either way to anchor project-local artifacts whose
|
|
780
|
+
// filenames carry only a loader version.
|
|
781
|
+
const projectMinecraftVersion = normalizedProjectPath
|
|
782
|
+
? await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath)
|
|
783
|
+
: undefined;
|
|
784
|
+
if (input.preferProjectVersion && projectMinecraftVersion) {
|
|
785
|
+
version = projectMinecraftVersion;
|
|
628
786
|
}
|
|
629
787
|
const requestedScope = input.scope ?? (normalizedProjectPath ? "loader" : "vanilla");
|
|
630
788
|
if (requestedScope === "vanilla") {
|
|
@@ -655,7 +813,8 @@ export async function resolveAccessTransformerRuntimeArtifact(svc, input) {
|
|
|
655
813
|
gradleUserHome: input.gradleUserHome,
|
|
656
814
|
requestedScope,
|
|
657
815
|
atNamespace: input.atNamespace,
|
|
658
|
-
loader
|
|
816
|
+
loader,
|
|
817
|
+
projectMinecraftVersion
|
|
659
818
|
});
|
|
660
819
|
if (!discovery.selected) {
|
|
661
820
|
throw createError({
|
|
@@ -670,7 +829,13 @@ export async function resolveAccessTransformerRuntimeArtifact(svc, input) {
|
|
|
670
829
|
candidateArtifacts: discovery.candidateArtifacts,
|
|
671
830
|
loaderEvidence: loaderDetection.evidence,
|
|
672
831
|
loaderWarnings: loaderDetection.warnings,
|
|
673
|
-
|
|
832
|
+
...(projectMinecraftVersion ? { projectMinecraftVersion } : {}),
|
|
833
|
+
// An error must never ask for something the caller already sent.
|
|
834
|
+
nextAction: normalizedProjectPath
|
|
835
|
+
? `Searched the workspace "${normalizedProjectPath}" and the Gradle caches but found no runtime jar for Minecraft ${version}${projectMinecraftVersion && projectMinecraftVersion !== version
|
|
836
|
+
? ` (the workspace declares ${projectMinecraftVersion}; retry with version="${projectMinecraftVersion}" or preferProjectVersion=true)`
|
|
837
|
+
: ""}. Run the Gradle task that populates transformed runtime artifacts (NeoForge/ModDevGradle writes them under build/moddev/artifacts), then retry.`
|
|
838
|
+
: "Provide projectPath for a Forge/NeoForge workspace with generated runtime jars, or run the Gradle tasks that populate transformed runtime artifacts before retrying."
|
|
674
839
|
}
|
|
675
840
|
});
|
|
676
841
|
}
|
|
@@ -690,15 +855,38 @@ export async function resolveAccessTransformerRuntimeArtifact(svc, input) {
|
|
|
690
855
|
: "Resolved the closest transformed runtime artifact for validation."
|
|
691
856
|
}
|
|
692
857
|
: undefined;
|
|
858
|
+
const resolutionNotes = [
|
|
859
|
+
...(scopeFallback ? [scopeFallback.reason] : []),
|
|
860
|
+
...(selected.versionEvidence && selected.versionEvidence !== "exact-token"
|
|
861
|
+
? [
|
|
862
|
+
selected.versionEvidence === "loader-token"
|
|
863
|
+
? `Runtime jar matched Minecraft ${version} through its loader version token (ModDevGradle names artifacts after the loader, not Minecraft).`
|
|
864
|
+
: `Runtime jar matched Minecraft ${version} because it lives in the workspace build directory of a project declaring that version.`
|
|
865
|
+
]
|
|
866
|
+
: [])
|
|
867
|
+
];
|
|
868
|
+
const servedAt = describeServedRuntimeJar({
|
|
869
|
+
jarPath: selected.jarPath,
|
|
870
|
+
requestedVersion: version,
|
|
871
|
+
// An access transformer is a Forge/NeoForge artifact; the workspace loader
|
|
872
|
+
// refines that when it is known.
|
|
873
|
+
expectedLoader: loader === "forge" ? "forge" : loader === "neoforge" ? "neoforge" : undefined
|
|
874
|
+
});
|
|
875
|
+
resolutionNotes.push(...servedAt.notes);
|
|
693
876
|
return {
|
|
694
|
-
version,
|
|
877
|
+
version: servedAt.version,
|
|
695
878
|
jarPath: selected.jarPath,
|
|
879
|
+
...(servedAt.requestedVersion ? { requestedVersion: servedAt.requestedVersion } : {}),
|
|
880
|
+
...(servedAt.versionApproximated ? { versionApproximated: true } : {}),
|
|
881
|
+
servedLoader: servedAt.servedLoader,
|
|
882
|
+
...(servedAt.expectedLoader ? { expectedLoader: servedAt.expectedLoader } : {}),
|
|
883
|
+
...(servedAt.loaderMismatch ? { loaderMismatch: true } : {}),
|
|
696
884
|
requestedScope,
|
|
697
885
|
appliedScope: selected.appliedScope,
|
|
698
886
|
requestedMapping: input.atNamespace,
|
|
699
887
|
mappingApplied,
|
|
700
888
|
origin: selected.origin,
|
|
701
|
-
resolutionNotes:
|
|
889
|
+
resolutionNotes: resolutionNotes.length > 0 ? resolutionNotes : undefined,
|
|
702
890
|
scopeFallback
|
|
703
891
|
};
|
|
704
892
|
}
|
|
@@ -748,7 +936,7 @@ export async function resolveBinaryFallbackArtifact(svc, input) {
|
|
|
748
936
|
return undefined;
|
|
749
937
|
}
|
|
750
938
|
try {
|
|
751
|
-
const fallbackResolved = await resolveSourceTargetInternal({ kind: "jar", value: binaryJarPath }, { allowDecompile: true, preferBinaryOnly: true }, svc.config);
|
|
939
|
+
const fallbackResolved = await resolveSourceTargetInternal({ kind: "jar", value: binaryJarPath }, { allowDecompile: input.allowDecompile ?? true, preferBinaryOnly: true }, svc.config);
|
|
752
940
|
fallbackResolved.version = fallbackResolved.version ?? input.version;
|
|
753
941
|
fallbackResolved.coordinate = fallbackResolved.coordinate ?? input.coordinate;
|
|
754
942
|
fallbackResolved.requestedMapping = input.requestedMapping;
|
|
@@ -1233,7 +1421,20 @@ export async function resolveArtifact(svc, input) {
|
|
|
1233
1421
|
});
|
|
1234
1422
|
}
|
|
1235
1423
|
resolved.qualityFlags.push("version-approximated");
|
|
1236
|
-
|
|
1424
|
+
// Provenance must name the version that was SERVED. Echoing the request
|
|
1425
|
+
// here made a fallback indistinguishable from an exact hit, and left
|
|
1426
|
+
// downstream version context pointing at a version the jar is not.
|
|
1427
|
+
const servedVersion = inferRuntimeJarMinecraftVersion(versionSourceDiscovery.selectedSourceJarPath);
|
|
1428
|
+
provenance.versionApproximation = {
|
|
1429
|
+
requestedVersion: value,
|
|
1430
|
+
...(servedVersion ? { servedVersion } : {}),
|
|
1431
|
+
sourceJarPath: versionSourceDiscovery.selectedSourceJarPath
|
|
1432
|
+
};
|
|
1433
|
+
if (servedVersion) {
|
|
1434
|
+
provenance.resolvedFrom.version = servedVersion;
|
|
1435
|
+
}
|
|
1436
|
+
warnings.push(`Requested version "${value}" but resolved source jar does not contain exact version string: ${versionSourceDiscovery.selectedSourceJarPath}` +
|
|
1437
|
+
(servedVersion ? ` (serving Minecraft ${servedVersion})` : ""));
|
|
1237
1438
|
}
|
|
1238
1439
|
}
|
|
1239
1440
|
resolved.qualityFlags = dedupeQualityFlags(resolved.qualityFlags);
|
|
@@ -40,7 +40,28 @@ export declare function buildFallbackProvenance(svc: SourceService, input: {
|
|
|
40
40
|
export declare function buildClassSourceNotFoundError(svc: SourceService, input: {
|
|
41
41
|
className: string;
|
|
42
42
|
lookupClassName: string;
|
|
43
|
+
/**
|
|
44
|
+
* Artifact the lookup ended on. TWO internal paths move it off the requested
|
|
45
|
+
* artifact: the binary fallback, and the nested-jar redirect that follows a
|
|
46
|
+
* shell jar's bundled inner jar.
|
|
47
|
+
*/
|
|
43
48
|
artifactId: string;
|
|
49
|
+
/**
|
|
50
|
+
* Artifact the caller actually asked about. Both internal redirects swap the
|
|
51
|
+
* active artifact mid-lookup, but the error must keep answering about the
|
|
52
|
+
* requested one: reporting the redirect target sends the caller to an artifact
|
|
53
|
+
* they never named. Defaults to `artifactId` for paths with no redirect.
|
|
54
|
+
*/
|
|
55
|
+
requestedArtifactId?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Mapping and quality flags OF THE REQUESTED ARTIFACT. `details.artifactId`
|
|
58
|
+
* names the requested artifact, so `details.mapping` (which reaches
|
|
59
|
+
* `error.context` through the allowlist) and `details.qualityFlags` have to
|
|
60
|
+
* describe that same artifact. The nested-jar redirect REPLACES the active
|
|
61
|
+
* values with the inner jar's, which would otherwise publish one artifact's
|
|
62
|
+
* identity beside another's namespace and quality — and point the
|
|
63
|
+
* `suggestedCall` at an index holding neither the class nor its siblings.
|
|
64
|
+
*/
|
|
44
65
|
mappingApplied: SourceMapping;
|
|
45
66
|
requestedMapping: SourceMapping;
|
|
46
67
|
qualityFlags: string[];
|
|
@@ -10,7 +10,7 @@ import { collectDidYouMeanCandidates } from "./did-you-mean.js";
|
|
|
10
10
|
import { matchesMemberPattern } from "./member-pattern.js";
|
|
11
11
|
import { findNestedJarClasses, resolveUniqueNestedJarForClass } from "./nested-jars.js";
|
|
12
12
|
import { buildPageContextKey, encodeOffsetCursor, resolveCursorOffset } from "../page-cursor.js";
|
|
13
|
-
import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
13
|
+
import { dedupeQualityFlags, inheritArtifactMapping, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
14
14
|
import { isUnobfuscatedVersion } from "../version-service.js";
|
|
15
15
|
const MEMBERS_STATUS_LEGACY = process.env.MEMBERS_STATUS_LEGACY === "1";
|
|
16
16
|
/**
|
|
@@ -175,10 +175,26 @@ export function buildFallbackProvenance(svc, input) {
|
|
|
175
175
|
transformChain
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Near-miss candidates from the requested artifact's index followed by any the
|
|
180
|
+
* lookup's final artifact contributes, deduplicated by FQN so a class present in
|
|
181
|
+
* both is reported once, under the requested artifact.
|
|
182
|
+
*/
|
|
183
|
+
function unionDidYouMeanCandidates(svc, requestedArtifactId, endedOnArtifactId, className) {
|
|
184
|
+
const requested = collectDidYouMeanCandidates(svc, requestedArtifactId, className);
|
|
185
|
+
if (endedOnArtifactId === requestedArtifactId) {
|
|
186
|
+
return requested;
|
|
187
|
+
}
|
|
188
|
+
const seen = new Set(requested.map((candidate) => candidate.className));
|
|
189
|
+
const fromFallback = collectDidYouMeanCandidates(svc, endedOnArtifactId, className, endedOnArtifactId).filter((candidate) => !seen.has(candidate.className));
|
|
190
|
+
return [...requested, ...fromFallback];
|
|
191
|
+
}
|
|
178
192
|
export function buildClassSourceNotFoundError(svc, input) {
|
|
179
193
|
const simpleName = input.className.split(/[.$]/).at(-1) ?? input.className;
|
|
194
|
+
const requestedArtifactId = input.requestedArtifactId ?? input.artifactId;
|
|
180
195
|
const details = {
|
|
181
|
-
artifactId:
|
|
196
|
+
artifactId: requestedArtifactId,
|
|
197
|
+
...(input.artifactId !== requestedArtifactId ? { fallbackArtifactId: input.artifactId } : {}),
|
|
182
198
|
className: input.className,
|
|
183
199
|
mapping: input.mappingApplied,
|
|
184
200
|
qualityFlags: input.qualityFlags,
|
|
@@ -191,12 +207,18 @@ export function buildClassSourceNotFoundError(svc, input) {
|
|
|
191
207
|
...(input.nestedJars && input.nestedJars.length > 0 ? { nestedJars: input.nestedJars } : {}),
|
|
192
208
|
// Candidates are hints from the symbol index, never assertions that the
|
|
193
209
|
// class exists at the suggested location; empty when nothing usable.
|
|
194
|
-
|
|
210
|
+
//
|
|
211
|
+
// Both indexes are consulted, requested artifact first. Collecting from the
|
|
212
|
+
// requested artifact ALONE is empty by construction in the scenario the
|
|
213
|
+
// partial-source fallback exists to serve: that artifact is the one without
|
|
214
|
+
// net.minecraft symbols, which is why the fallback fired and indexed the other
|
|
215
|
+
// jar. Candidates from the artifact the caller did not name carry its id.
|
|
216
|
+
didYouMean: unionDidYouMeanCandidates(svc, requestedArtifactId, input.artifactId, input.className)
|
|
195
217
|
};
|
|
196
218
|
let nextAction = `Use find-class to resolve the correct fully-qualified name for "${simpleName}".`;
|
|
197
219
|
let suggestionSpec = {
|
|
198
220
|
tool: "find-class",
|
|
199
|
-
params: { className: simpleName, artifactId:
|
|
221
|
+
params: { className: simpleName, artifactId: requestedArtifactId }
|
|
200
222
|
};
|
|
201
223
|
if (input.targetKind === "version" && input.scope && input.scope !== "merged" && !input.projectPath) {
|
|
202
224
|
nextAction +=
|
|
@@ -225,7 +247,7 @@ export function buildClassSourceNotFoundError(svc, input) {
|
|
|
225
247
|
else {
|
|
226
248
|
suggestionSpec = {
|
|
227
249
|
tool: "find-class",
|
|
228
|
-
params: { className: simpleName, artifactId:
|
|
250
|
+
params: { className: simpleName, artifactId: requestedArtifactId }
|
|
229
251
|
};
|
|
230
252
|
}
|
|
231
253
|
}
|
|
@@ -403,13 +425,14 @@ export function findClass(svc, input) {
|
|
|
403
425
|
const filteredMatches = partialVanillaLookup && matches.every((match) => !match.qualifiedName.startsWith("net.minecraft.") && !match.qualifiedName.startsWith("com.mojang."))
|
|
404
426
|
? []
|
|
405
427
|
: matches;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
428
|
+
return finishFindClass(svc, {
|
|
429
|
+
artifact,
|
|
430
|
+
artifactId,
|
|
431
|
+
className,
|
|
432
|
+
matches: filteredMatches,
|
|
433
|
+
partialVanillaLookup,
|
|
434
|
+
warnings
|
|
435
|
+
});
|
|
413
436
|
}
|
|
414
437
|
const result = svc.symbolsRepo.findScopedSymbols({
|
|
415
438
|
artifactId,
|
|
@@ -418,32 +441,82 @@ export function findClass(svc, input) {
|
|
|
418
441
|
symbolKinds: TYPE_SYMBOL_KINDS,
|
|
419
442
|
limit: limit * 5
|
|
420
443
|
});
|
|
421
|
-
|
|
444
|
+
// The extractor stores ONE qualifiedName per FILE (the top-level type), so a
|
|
445
|
+
// nested type's row carries its OUTER type's FQN. Reporting that verbatim
|
|
446
|
+
// returned a qualifiedName that did not contain the searched token at all —
|
|
447
|
+
// feeding match[0] into get-class-source then fetched the wrong class.
|
|
448
|
+
const candidates = [];
|
|
422
449
|
for (const row of result.items) {
|
|
423
|
-
if (matches.length >= limit)
|
|
424
|
-
break;
|
|
425
450
|
const isTypeSymbol = row.symbolKind === "class" || row.symbolKind === "interface" ||
|
|
426
451
|
row.symbolKind === "enum" || row.symbolKind === "record";
|
|
427
452
|
if (!isTypeSymbol)
|
|
428
453
|
continue;
|
|
429
|
-
|
|
430
|
-
|
|
454
|
+
const enclosingQualifiedName = row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
455
|
+
const enclosingSimpleName = enclosingQualifiedName.split(".").at(-1) ?? enclosingQualifiedName;
|
|
456
|
+
const nested = enclosingSimpleName !== row.symbolName;
|
|
457
|
+
candidates.push({
|
|
458
|
+
qualifiedName: nested ? `${enclosingQualifiedName}.${row.symbolName}` : enclosingQualifiedName,
|
|
431
459
|
filePath: row.filePath,
|
|
432
460
|
line: row.line,
|
|
433
|
-
symbolKind: row.symbolKind
|
|
461
|
+
symbolKind: row.symbolKind,
|
|
462
|
+
...(nested ? { nested: true, enclosingClass: enclosingQualifiedName } : {})
|
|
434
463
|
});
|
|
435
464
|
}
|
|
465
|
+
// A top-level type named exactly like the query is what the caller almost
|
|
466
|
+
// always means; a nested type that merely shares the simple name comes after.
|
|
467
|
+
candidates.sort((left, right) => {
|
|
468
|
+
const leftNested = left.nested === true ? 1 : 0;
|
|
469
|
+
const rightNested = right.nested === true ? 1 : 0;
|
|
470
|
+
if (leftNested !== rightNested)
|
|
471
|
+
return leftNested - rightNested;
|
|
472
|
+
return left.qualifiedName.localeCompare(right.qualifiedName);
|
|
473
|
+
});
|
|
474
|
+
const matches = candidates.slice(0, limit);
|
|
436
475
|
const partialVanillaLookup = hasPartialNetMinecraftCoverage(artifact.qualityFlags) && looksLikeDeobfuscatedClassName(className);
|
|
437
476
|
const filteredMatches = partialVanillaLookup && matches.every((match) => !match.qualifiedName.startsWith("net.minecraft.") && !match.qualifiedName.startsWith("com.mojang."))
|
|
438
477
|
? []
|
|
439
478
|
: matches;
|
|
440
|
-
|
|
479
|
+
return finishFindClass(svc, {
|
|
480
|
+
artifact,
|
|
481
|
+
artifactId,
|
|
482
|
+
className,
|
|
483
|
+
matches: filteredMatches,
|
|
484
|
+
partialVanillaLookup,
|
|
485
|
+
warnings
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Shared tail for both findClass branches: coverage/namespace warnings plus a
|
|
490
|
+
* machine-usable recovery route.
|
|
491
|
+
*
|
|
492
|
+
* An empty result on an artifact whose index excludes net.minecraft is
|
|
493
|
+
* internally consistent but externally contradictory — get-class-source answers
|
|
494
|
+
* the SAME class from the binary fallback. The caller therefore gets both the
|
|
495
|
+
* reason and the exact call that works.
|
|
496
|
+
*/
|
|
497
|
+
function finishFindClass(svc, input) {
|
|
498
|
+
const { artifact, artifactId, className, matches, partialVanillaLookup, warnings } = input;
|
|
499
|
+
let suggestedCall;
|
|
500
|
+
if (matches.length === 0 && partialVanillaLookup) {
|
|
441
501
|
warnings.push(`Artifact source coverage is partial and excludes net.minecraft; returning non-vanilla matches for "${className}" would be misleading. Use get-class-source/get-class-members for binary fallback or get-class-api-matrix for mapped API inspection.`);
|
|
502
|
+
suggestedCall = buildSuggestedCall({
|
|
503
|
+
tool: "get-class-source",
|
|
504
|
+
params: {
|
|
505
|
+
className,
|
|
506
|
+
target: { kind: "artifact", artifactId },
|
|
507
|
+
mode: "metadata"
|
|
508
|
+
}
|
|
509
|
+
}).suggestedCall;
|
|
442
510
|
}
|
|
443
|
-
if (
|
|
511
|
+
if (matches.length === 0 && shouldSuggestObfuscatedMapping(artifact, className)) {
|
|
444
512
|
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
445
513
|
}
|
|
446
|
-
return {
|
|
514
|
+
return {
|
|
515
|
+
matches,
|
|
516
|
+
total: matches.length,
|
|
517
|
+
warnings,
|
|
518
|
+
...(suggestedCall ? { suggestedCall } : {})
|
|
519
|
+
};
|
|
447
520
|
}
|
|
448
521
|
export async function findClassIncludingNested(svc, input) {
|
|
449
522
|
const indexed = findClass(svc, input);
|
|
@@ -550,7 +623,7 @@ export async function getClassSource(svc, input) {
|
|
|
550
623
|
const artifact = svc.getArtifact(artifactId);
|
|
551
624
|
artifactId = artifact.artifactId;
|
|
552
625
|
origin = artifact.origin;
|
|
553
|
-
requestedMapping = input.mapping
|
|
626
|
+
requestedMapping = inheritArtifactMapping(input.mapping, artifact);
|
|
554
627
|
mappingApplied = artifact.mappingApplied ?? requestedMapping;
|
|
555
628
|
provenance = artifact.provenance;
|
|
556
629
|
qualityFlags = artifact.qualityFlags;
|
|
@@ -595,7 +668,11 @@ export async function getClassSource(svc, input) {
|
|
|
595
668
|
requestedMapping,
|
|
596
669
|
mappingApplied,
|
|
597
670
|
provenance: activeProvenance,
|
|
598
|
-
qualityFlags: activeQualityFlags
|
|
671
|
+
qualityFlags: activeQualityFlags,
|
|
672
|
+
// A caller who declined decompilation must not pay for one here: the
|
|
673
|
+
// fallback used to hardcode allowDecompile:true and silently ran a full
|
|
674
|
+
// Vineflower pass on the binary jar.
|
|
675
|
+
allowDecompile: input.allowDecompile
|
|
599
676
|
});
|
|
600
677
|
if (!fallbackResolved || fallbackResolved.artifactId === activeArtifactId) {
|
|
601
678
|
return false;
|
|
@@ -689,11 +766,15 @@ export async function getClassSource(svc, input) {
|
|
|
689
766
|
if (!filePath) {
|
|
690
767
|
throw buildClassSourceNotFoundError(svc, {
|
|
691
768
|
artifactId: activeArtifactId,
|
|
769
|
+
requestedArtifactId: artifactId,
|
|
692
770
|
className,
|
|
693
771
|
lookupClassName: activeLookupClassName,
|
|
694
|
-
|
|
772
|
+
// The REQUESTED artifact's namespace and quality, matching the artifactId this
|
|
773
|
+
// error reports. The nested-jar redirect replaces the active values with the
|
|
774
|
+
// inner jar's, which would otherwise describe an artifact the caller never named.
|
|
775
|
+
mappingApplied,
|
|
695
776
|
requestedMapping,
|
|
696
|
-
qualityFlags
|
|
777
|
+
qualityFlags,
|
|
697
778
|
attemptedBinaryFallback,
|
|
698
779
|
targetKind: input.target?.kind,
|
|
699
780
|
targetValue: input.target && "value" in input.target ? input.target.value : undefined,
|
|
@@ -728,11 +809,15 @@ export async function getClassSource(svc, input) {
|
|
|
728
809
|
if (!row) {
|
|
729
810
|
throw buildClassSourceNotFoundError(svc, {
|
|
730
811
|
artifactId: activeArtifactId,
|
|
812
|
+
requestedArtifactId: artifactId,
|
|
731
813
|
className,
|
|
732
814
|
lookupClassName: activeLookupClassName,
|
|
733
|
-
|
|
815
|
+
// The REQUESTED artifact's namespace and quality, matching the artifactId this
|
|
816
|
+
// error reports. The nested-jar redirect replaces the active values with the
|
|
817
|
+
// inner jar's, which would otherwise describe an artifact the caller never named.
|
|
818
|
+
mappingApplied,
|
|
734
819
|
requestedMapping,
|
|
735
|
-
qualityFlags
|
|
820
|
+
qualityFlags,
|
|
736
821
|
attemptedBinaryFallback,
|
|
737
822
|
filePath,
|
|
738
823
|
targetKind: input.target?.kind,
|
|
@@ -910,6 +995,7 @@ export async function getClassMembers(svc, input) {
|
|
|
910
995
|
const artifact = svc.getArtifact(artifactId);
|
|
911
996
|
artifactId = artifact.artifactId;
|
|
912
997
|
origin = artifact.origin;
|
|
998
|
+
requestedMapping = inheritArtifactMapping(input.mapping, artifact);
|
|
913
999
|
mappingApplied = artifact.mappingApplied ?? requestedMapping;
|
|
914
1000
|
provenance = artifact.provenance;
|
|
915
1001
|
qualityFlags = artifact.qualityFlags;
|
|
@@ -1179,7 +1265,18 @@ export async function getClassMembers(svc, input) {
|
|
|
1179
1265
|
truncated,
|
|
1180
1266
|
...(nextCursor ? { nextCursor } : {}),
|
|
1181
1267
|
...(memberCursorIgnored ? { cursorIgnored: true } : {}),
|
|
1182
|
-
context:
|
|
1268
|
+
context: {
|
|
1269
|
+
...signatureContext,
|
|
1270
|
+
// The jar-derived context describes the BYTECODE the reader opened. The
|
|
1271
|
+
// members handed back have already been remapped into requestedMapping,
|
|
1272
|
+
// so echoing the jar namespace here contradicted `returnedNamespace` in
|
|
1273
|
+
// the same payload. Report what the response actually contains, and fill
|
|
1274
|
+
// the version the resolver established when the jar path yielded none.
|
|
1275
|
+
...(signatureContext.minecraftVersion === "unknown" && version
|
|
1276
|
+
? { minecraftVersion: version }
|
|
1277
|
+
: {}),
|
|
1278
|
+
mappingNamespace: requestedMapping
|
|
1279
|
+
},
|
|
1183
1280
|
origin,
|
|
1184
1281
|
artifactId,
|
|
1185
1282
|
requestedMapping,
|