@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +40 -23
  3. package/dist/build-suggested-call.d.ts +29 -0
  4. package/dist/build-suggested-call.js +58 -0
  5. package/dist/cache-registry.d.ts +3 -1
  6. package/dist/cache-registry.js +50 -6
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
  8. package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
  9. package/dist/entry-tools/batch-class-members-service.js +97 -0
  10. package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
  11. package/dist/entry-tools/batch-class-source-service.js +100 -0
  12. package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
  13. package/dist/entry-tools/batch-mappings-service.js +66 -0
  14. package/dist/entry-tools/batch-runner.d.ts +72 -0
  15. package/dist/entry-tools/batch-runner.js +90 -0
  16. package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
  17. package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
  18. package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
  19. package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
  20. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
  21. package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
  22. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
  23. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
  27. package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
  28. package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
  29. package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
  31. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
  32. package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
  33. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
  34. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
  35. package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
  36. package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
  37. package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
  38. package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
  39. package/dist/entry-tools/manage-cache-service.d.ts +16 -16
  40. package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
  41. package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
  42. package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
  43. package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
  44. package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
  45. package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
  46. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +102 -0
  47. package/dist/entry-tools/validate-project/cases/project-summary.js +415 -0
  48. package/dist/entry-tools/validate-project/internal.d.ts +142 -0
  49. package/dist/entry-tools/validate-project/internal.js +303 -0
  50. package/dist/entry-tools/validate-project-service.d.ts +67 -47
  51. package/dist/entry-tools/validate-project-service.js +13 -563
  52. package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
  53. package/dist/entry-tools/verify-mixin-target-service.js +323 -0
  54. package/dist/error-mapping.d.ts +40 -0
  55. package/dist/error-mapping.js +139 -0
  56. package/dist/errors.d.ts +6 -0
  57. package/dist/errors.js +6 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +147 -1354
  60. package/dist/mapping/internal-types.d.ts +54 -0
  61. package/dist/mapping/internal-types.js +14 -0
  62. package/dist/mapping/loaders/mojang.d.ts +2 -0
  63. package/dist/mapping/loaders/mojang.js +64 -0
  64. package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
  65. package/dist/mapping/loaders/tiny-loom.js +73 -0
  66. package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
  67. package/dist/mapping/loaders/tiny-maven.js +104 -0
  68. package/dist/mapping/loaders/types.d.ts +14 -0
  69. package/dist/mapping/loaders/types.js +2 -0
  70. package/dist/mapping/lookup.d.ts +52 -0
  71. package/dist/mapping/lookup.js +496 -0
  72. package/dist/mapping/parsers/normalize.d.ts +10 -0
  73. package/dist/mapping/parsers/normalize.js +52 -0
  74. package/dist/mapping/parsers/proguard.d.ts +20 -0
  75. package/dist/mapping/parsers/proguard.js +138 -0
  76. package/dist/mapping/parsers/symbol-records.d.ts +27 -0
  77. package/dist/mapping/parsers/symbol-records.js +216 -0
  78. package/dist/mapping/parsers/tiny.d.ts +9 -0
  79. package/dist/mapping/parsers/tiny.js +96 -0
  80. package/dist/mapping/types.d.ts +147 -0
  81. package/dist/mapping/types.js +2 -0
  82. package/dist/mapping-pipeline-service.js +3 -2
  83. package/dist/mapping-service.d.ts +8 -145
  84. package/dist/mapping-service.js +30 -1207
  85. package/dist/mixin/access-validators.d.ts +9 -0
  86. package/dist/mixin/access-validators.js +257 -0
  87. package/dist/mixin/annotation-validators.d.ts +5 -0
  88. package/dist/mixin/annotation-validators.js +162 -0
  89. package/dist/mixin/helpers.d.ts +28 -0
  90. package/dist/mixin/helpers.js +315 -0
  91. package/dist/mixin/parsed-validator.d.ts +8 -0
  92. package/dist/mixin/parsed-validator.js +337 -0
  93. package/dist/mixin/types.d.ts +208 -0
  94. package/dist/mixin/types.js +28 -0
  95. package/dist/mixin-validator.d.ts +9 -201
  96. package/dist/mixin-validator.js +8 -1020
  97. package/dist/source/access-validate.d.ts +4 -0
  98. package/dist/source/access-validate.js +254 -0
  99. package/dist/source/artifact-resolver.d.ts +111 -0
  100. package/dist/source/artifact-resolver.js +1271 -0
  101. package/dist/source/cache-metrics.d.ts +26 -0
  102. package/dist/source/cache-metrics.js +172 -0
  103. package/dist/source/class-source/members-builder.d.ts +34 -0
  104. package/dist/source/class-source/members-builder.js +46 -0
  105. package/dist/source/class-source/snippet-builder.d.ts +19 -0
  106. package/dist/source/class-source/snippet-builder.js +46 -0
  107. package/dist/source/class-source-helpers.d.ts +34 -0
  108. package/dist/source/class-source-helpers.js +140 -0
  109. package/dist/source/class-source.d.ts +42 -0
  110. package/dist/source/class-source.js +883 -0
  111. package/dist/source/descriptor-utils.d.ts +6 -0
  112. package/dist/source/descriptor-utils.js +37 -0
  113. package/dist/source/file-access.d.ts +4 -0
  114. package/dist/source/file-access.js +102 -0
  115. package/dist/source/indexer.d.ts +82 -0
  116. package/dist/source/indexer.js +522 -0
  117. package/dist/source/lifecycle/diff-utils.d.ts +9 -0
  118. package/dist/source/lifecycle/diff-utils.js +107 -0
  119. package/dist/source/lifecycle/diff.d.ts +2 -0
  120. package/dist/source/lifecycle/diff.js +265 -0
  121. package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
  122. package/dist/source/lifecycle/mapping-helpers.js +327 -0
  123. package/dist/source/lifecycle/runtime-check.d.ts +2 -0
  124. package/dist/source/lifecycle/runtime-check.js +142 -0
  125. package/dist/source/lifecycle/trace.d.ts +2 -0
  126. package/dist/source/lifecycle/trace.js +231 -0
  127. package/dist/source/lifecycle.d.ts +4 -0
  128. package/dist/source/lifecycle.js +5 -0
  129. package/dist/source/search.d.ts +51 -0
  130. package/dist/source/search.js +676 -0
  131. package/dist/source/shared-utils.d.ts +6 -0
  132. package/dist/source/shared-utils.js +55 -0
  133. package/dist/source/state.d.ts +26 -0
  134. package/dist/source/state.js +24 -0
  135. package/dist/source/symbol-resolver.d.ts +3 -0
  136. package/dist/source/symbol-resolver.js +212 -0
  137. package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
  138. package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
  139. package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
  140. package/dist/source/validate-mixin/pipeline/parse.js +10 -0
  141. package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
  142. package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
  143. package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
  144. package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
  145. package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
  146. package/dist/source/validate-mixin/pipeline-context.js +93 -0
  147. package/dist/source/validate-mixin.d.ts +22 -0
  148. package/dist/source/validate-mixin.js +799 -0
  149. package/dist/source/workspace-target.d.ts +18 -0
  150. package/dist/source/workspace-target.js +305 -0
  151. package/dist/source-resolver.d.ts +1 -0
  152. package/dist/source-resolver.js +1 -1
  153. package/dist/source-service.d.ts +164 -170
  154. package/dist/source-service.js +70 -6116
  155. package/dist/stage-emitter.d.ts +13 -0
  156. package/dist/stage-emitter.js +30 -0
  157. package/dist/stdio-supervisor.d.ts +61 -0
  158. package/dist/stdio-supervisor.js +326 -9
  159. package/dist/tool-contract-manifest.d.ts +1 -1
  160. package/dist/tool-contract-manifest.js +23 -6
  161. package/dist/tool-guidance.d.ts +82 -0
  162. package/dist/tool-guidance.js +734 -0
  163. package/dist/tool-schema-registry.d.ts +16 -0
  164. package/dist/tool-schema-registry.js +37 -0
  165. package/dist/tool-schemas.d.ts +3518 -0
  166. package/dist/tool-schemas.js +813 -0
  167. package/dist/types.d.ts +36 -0
  168. package/dist/version-service.js +7 -6
  169. package/dist/workspace-context-cache.d.ts +32 -0
  170. package/dist/workspace-context-cache.js +66 -0
  171. package/dist/workspace-mapping-service.d.ts +16 -0
  172. package/dist/workspace-mapping-service.js +173 -1
  173. package/docs/README-ja.md +416 -0
  174. package/docs/examples.md +483 -0
  175. package/docs/tool-reference.md +462 -0
  176. package/package.json +17 -4
@@ -0,0 +1,1271 @@
1
+ import fastGlob from "fast-glob";
2
+ import { buildArtifactAlias } from "../config.js";
3
+ import { buildSuggestedCall } from "../build-suggested-call.js";
4
+ import { ERROR_CODES, createError, isAppError } from "../errors.js";
5
+ import { buildLoaderRuntimeSearchRoots, buildVersionSourceSearchRoots, normalizeOptionalProjectPath } from "../gradle-paths.js";
6
+ import { log } from "../logger.js";
7
+ import { applyMappingPipeline } from "../mapping-pipeline-service.js";
8
+ import { parseCoordinate } from "../maven-resolver.js";
9
+ import { resolveMojangTinyFile } from "../mojang-tiny-mapping-service.js";
10
+ import { artifactSignatureFromFile } from "../path-resolver.js";
11
+ import { detectFabricLikeInputNamespace, listJavaEntries } from "../source-jar-reader.js";
12
+ import { artifactIdForJar, resolveSourceTarget as resolveSourceTargetInternal } from "../source-resolver.js";
13
+ import { resolveTinyRemapperJar } from "../tiny-remapper-resolver.js";
14
+ import { isUnobfuscatedVersion } from "../version-service.js";
15
+ import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
16
+ const VERSION_TOKEN_REGEX_CACHE = new Map();
17
+ const MAX_HELPER_REGEX_CACHE = 128;
18
+ function rememberCachedRegex(cache, key, regex) {
19
+ if (cache.size >= MAX_HELPER_REGEX_CACHE) {
20
+ const oldestKey = cache.keys().next().value;
21
+ if (oldestKey) {
22
+ cache.delete(oldestKey);
23
+ }
24
+ }
25
+ cache.set(key, regex);
26
+ return regex;
27
+ }
28
+ function escapeRegexLiteral(value) {
29
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30
+ }
31
+ function hasPartialNetMinecraftCoverage(qualityFlags) {
32
+ return qualityFlags.includes("partial-source-no-net-minecraft");
33
+ }
34
+ function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
35
+ if (hasMinecraftNamespace) {
36
+ return true;
37
+ }
38
+ const normalizedPath = normalizePathStyle(path).toLowerCase();
39
+ return (normalizedPath.includes("/minecraftmaven/") ||
40
+ normalizedPath.includes("/net/minecraft/") ||
41
+ /(?:^|\/)minecraft(?:-[a-z0-9._+]+)*-sources\.jar$/i.test(normalizedPath) ||
42
+ normalizedPath.includes("minecraft-merged") ||
43
+ normalizedPath.includes("minecraft-common") ||
44
+ normalizedPath.includes("minecraft-clientonly") ||
45
+ normalizedPath.includes("minecraft-client") ||
46
+ normalizedPath.includes("minecraft-server"));
47
+ }
48
+ export function hasExactVersionToken(path, version) {
49
+ const normalizedPath = normalizePathStyle(path).toLowerCase();
50
+ const normalizedVersion = version.trim().toLowerCase();
51
+ if (!normalizedVersion) {
52
+ return false;
53
+ }
54
+ // Avoid prefix false-positives like "1.21.1" matching "1.21.10".
55
+ const cached = VERSION_TOKEN_REGEX_CACHE.get(normalizedVersion);
56
+ const pattern = cached
57
+ ?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}([^0-9a-z]|$)`, "i"));
58
+ return pattern.test(normalizedPath);
59
+ }
60
+ function inferMergedRuntimeNamespaceHint(path) {
61
+ const normalizedPath = normalizePathStyle(path).toLowerCase();
62
+ if (normalizedPath.includes("merged-intermediary-v2") ||
63
+ normalizedPath.includes("merged-intermediary")) {
64
+ return "intermediary";
65
+ }
66
+ if (normalizedPath.includes("minecraft-merged-mojang") ||
67
+ normalizedPath.includes("merged-mojang")) {
68
+ return "mojang";
69
+ }
70
+ if (normalizedPath.includes("merged-named")) {
71
+ return "named";
72
+ }
73
+ return undefined;
74
+ }
75
+ function runtimeJarNamespaceHintScore(hint) {
76
+ if (hint === "intermediary" || hint === "mojang") {
77
+ return 8_000;
78
+ }
79
+ if (hint === "named") {
80
+ return 1_000;
81
+ }
82
+ return 0;
83
+ }
84
+ function normalizeAccessTransformerNamespace(namespace) {
85
+ const normalized = namespace?.trim().toLowerCase();
86
+ if (!normalized) {
87
+ return undefined;
88
+ }
89
+ if (normalized === "srg" || normalized === "mojang" || normalized === "obfuscated") {
90
+ return normalized;
91
+ }
92
+ return undefined;
93
+ }
94
+ async function pathExists(filePath) {
95
+ try {
96
+ const { access } = await import("node:fs/promises");
97
+ await access(filePath);
98
+ return true;
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ function buildResolveArtifactParams(target, extra = {}) {
105
+ return {
106
+ target: {
107
+ kind: target.kind,
108
+ value: target.value
109
+ },
110
+ ...extra
111
+ };
112
+ }
113
+ function buildProvenance(input) {
114
+ const provenance = {
115
+ target: input.requestedTarget,
116
+ resolvedAt: input.resolved.resolvedAt,
117
+ resolvedFrom: {
118
+ origin: input.resolved.origin,
119
+ sourceJarPath: input.resolved.sourceJarPath,
120
+ binaryJarPath: input.resolved.binaryJarPath,
121
+ coordinate: input.resolved.coordinate,
122
+ version: input.resolved.version,
123
+ repoUrl: input.resolved.repoUrl
124
+ },
125
+ transformChain: [...input.transformChain]
126
+ };
127
+ if (!provenance.resolvedAt || !provenance.target.kind || !provenance.target.value) {
128
+ throw createError({
129
+ code: ERROR_CODES.PROVENANCE_INCOMPLETE,
130
+ message: "Artifact provenance is incomplete.",
131
+ details: {
132
+ artifactId: input.resolved.artifactId,
133
+ provenance
134
+ }
135
+ });
136
+ }
137
+ return provenance;
138
+ }
139
+ export async function discoverVersionSourceJar(_svc, input) {
140
+ const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
141
+ const searchRoots = buildVersionSourceSearchRoots(normalizedProjectPath);
142
+ const searchedPaths = [];
143
+ const candidates = [];
144
+ const seen = new Set();
145
+ for (const root of searchRoots) {
146
+ searchedPaths.push(root);
147
+ let discovered = [];
148
+ try {
149
+ discovered = await fastGlob.glob("**/*sources.jar", {
150
+ cwd: root,
151
+ absolute: true,
152
+ onlyFiles: true
153
+ });
154
+ }
155
+ catch {
156
+ continue;
157
+ }
158
+ for (const candidatePath of discovered) {
159
+ const normalizedPath = normalizePathStyle(candidatePath);
160
+ if (seen.has(normalizedPath)) {
161
+ continue;
162
+ }
163
+ seen.add(normalizedPath);
164
+ const lower = normalizedPath.toLowerCase();
165
+ if (!lower.includes(input.version.toLowerCase()) && !lower.includes("minecraft")) {
166
+ continue;
167
+ }
168
+ let javaEntries = [];
169
+ try {
170
+ javaEntries = await listJavaEntries(normalizedPath);
171
+ }
172
+ catch {
173
+ continue;
174
+ }
175
+ if (javaEntries.length === 0) {
176
+ continue;
177
+ }
178
+ const hasMinecraftNamespace = javaEntries.some((entry) => normalizePathStyle(entry).startsWith("net/minecraft/"));
179
+ const looksLikeMinecraftArtifact = looksLikeMinecraftSourceArtifact(normalizedPath, hasMinecraftNamespace);
180
+ const exactVersionMatch = hasExactVersionToken(normalizedPath, input.version);
181
+ const score = (looksLikeMinecraftArtifact ? 20_000 : 0) +
182
+ (hasMinecraftNamespace ? 10_000 : 0) +
183
+ (lower.includes("minecraft-merged") ? 2_000 : 0) +
184
+ (exactVersionMatch ? 1_000 : 0) +
185
+ Math.min(javaEntries.length, 500);
186
+ candidates.push({
187
+ jarPath: normalizedPath,
188
+ javaEntryCount: javaEntries.length,
189
+ hasMinecraftNamespace,
190
+ looksLikeMinecraftArtifact,
191
+ score
192
+ });
193
+ }
194
+ }
195
+ candidates.sort((left, right) => {
196
+ if (right.score !== left.score) {
197
+ return right.score - left.score;
198
+ }
199
+ return left.jarPath.localeCompare(right.jarPath);
200
+ });
201
+ const selected = candidates.find((candidate) => candidate.looksLikeMinecraftArtifact && candidate.hasMinecraftNamespace) ??
202
+ candidates.find((candidate) => candidate.looksLikeMinecraftArtifact);
203
+ const candidateArtifacts = candidates
204
+ .slice(0, 20)
205
+ .map((candidate) => `${candidate.jarPath}#java=${candidate.javaEntryCount}#net_minecraft=${candidate.hasMinecraftNamespace ? 1 : 0}`);
206
+ return {
207
+ searchedPaths,
208
+ candidateArtifacts,
209
+ selectedSourceJarPath: selected?.jarPath,
210
+ selectedHasMinecraftNamespace: selected?.hasMinecraftNamespace
211
+ };
212
+ }
213
+ export async function probeMinecraftArtifact(svc, input) {
214
+ let value = input.target.value.trim();
215
+ const warnings = [];
216
+ const requestedMapping = normalizeMapping(input.mapping);
217
+ if (input.preferProjectVersion && input.projectPath) {
218
+ const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(input.projectPath);
219
+ if (detected && detected !== value) {
220
+ warnings.push(`Overriding version "${value}" with project version "${detected}" from gradle.properties.`);
221
+ }
222
+ value = detected ?? value;
223
+ }
224
+ if (!value) {
225
+ throw createError({
226
+ code: ERROR_CODES.INVALID_INPUT,
227
+ message: "target.value must be non-empty.",
228
+ details: { target: input.target }
229
+ });
230
+ }
231
+ const versionJar = await svc.versionService.resolveVersionJar(value);
232
+ const resolvedVersion = versionJar.version;
233
+ const runtimeNamesUnobfuscated = isUnobfuscatedVersion(resolvedVersion);
234
+ warnings.push(`Resolved Minecraft ${versionJar.version} from ${versionJar.clientJarUrl}.`);
235
+ let effectiveMapping = requestedMapping;
236
+ if ((requestedMapping === "intermediary" || requestedMapping === "yarn") &&
237
+ runtimeNamesUnobfuscated) {
238
+ warnings.push(`Version ${resolvedVersion} is unobfuscated; ${requestedMapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
239
+ effectiveMapping = "obfuscated";
240
+ }
241
+ if ((effectiveMapping === "intermediary" || effectiveMapping === "yarn") &&
242
+ !runtimeNamesUnobfuscated) {
243
+ throw createError({
244
+ code: ERROR_CODES.MAPPING_NOT_APPLIED,
245
+ message: `Lightweight artifact probe cannot verify ${effectiveMapping} mapping availability without running the full resolver.`,
246
+ details: {
247
+ requestedMapping: effectiveMapping,
248
+ version: resolvedVersion,
249
+ nextAction: "Use a direct validation task for mapping-sensitive checks, or use mapping=obfuscated for the project-summary artifact probe."
250
+ }
251
+ });
252
+ }
253
+ if (effectiveMapping === "mojang" && !runtimeNamesUnobfuscated) {
254
+ // Match validate-mixin's resolve stage: omitted scope defaults to vanilla,
255
+ // avoiding a workspace-wide source-jar scan that validate-mixin would skip.
256
+ const effectiveScope = input.scope ?? "vanilla";
257
+ if (effectiveScope === "vanilla") {
258
+ throw createError({
259
+ code: ERROR_CODES.MAPPING_NOT_APPLIED,
260
+ message: "Lightweight artifact probe cannot verify mojang mapping with scope=vanilla on obfuscated runtime versions.",
261
+ details: {
262
+ requestedMapping: effectiveMapping,
263
+ version: resolvedVersion,
264
+ nextAction: "Retry with scope=merged and projectPath so the probe can use a Loom source jar, or use mapping=obfuscated."
265
+ }
266
+ });
267
+ }
268
+ const versionSourceDiscovery = await svc.discoverVersionSourceJar({
269
+ version: resolvedVersion,
270
+ projectPath: input.projectPath
271
+ });
272
+ if (!versionSourceDiscovery.selectedSourceJarPath) {
273
+ throw createError({
274
+ code: ERROR_CODES.MAPPING_NOT_APPLIED,
275
+ message: "Lightweight artifact probe cannot verify mojang mapping without a source-backed Loom artifact.",
276
+ details: {
277
+ requestedMapping: effectiveMapping,
278
+ version: resolvedVersion,
279
+ searchedPaths: versionSourceDiscovery.searchedPaths,
280
+ candidateArtifacts: versionSourceDiscovery.candidateArtifacts,
281
+ nextAction: "Use mapping=obfuscated for project-summary, or run a direct validation task when full source resolution is required."
282
+ }
283
+ });
284
+ }
285
+ const selectedSourceJarPath = versionSourceDiscovery.selectedSourceJarPath;
286
+ const sourceSignature = artifactSignatureFromFile(selectedSourceJarPath).signature;
287
+ const artifactId = artifactIdForJar("jar", selectedSourceJarPath, sourceSignature);
288
+ warnings.push(`Resolved source-backed artifact from Loom cache candidate: ${selectedSourceJarPath}.`);
289
+ if (versionSourceDiscovery.selectedHasMinecraftNamespace === false) {
290
+ warnings.push(`Source coverage does not include net.minecraft for ${selectedSourceJarPath}; class lookups may fall back to the binary artifact.`);
291
+ }
292
+ if (!hasExactVersionToken(selectedSourceJarPath, value)) {
293
+ warnings.push(`Requested version "${value}" but resolved source jar does not contain exact version string: ${selectedSourceJarPath}`);
294
+ }
295
+ return {
296
+ artifactId,
297
+ mappingApplied: "mojang",
298
+ ...(warnings.length > 0 ? { warnings } : {})
299
+ };
300
+ }
301
+ const binarySignature = artifactSignatureFromFile(versionJar.jarPath).signature;
302
+ const artifactId = artifactIdForJar("jar", versionJar.jarPath, `${binarySignature}:decompile`);
303
+ return {
304
+ artifactId,
305
+ mappingApplied: effectiveMapping,
306
+ ...(warnings.length > 0 ? { warnings } : {})
307
+ };
308
+ }
309
+ export async function discoverAccessWidenerRuntimeCandidates(_svc, input) {
310
+ const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
311
+ const normalizedProjectPathLower = normalizedProjectPath
312
+ ? normalizePathStyle(normalizedProjectPath).toLowerCase()
313
+ : undefined;
314
+ const searchRoots = buildVersionSourceSearchRoots(normalizedProjectPath);
315
+ const searchedPaths = [];
316
+ const candidates = [];
317
+ const seen = new Set();
318
+ for (const root of searchRoots) {
319
+ searchedPaths.push(root);
320
+ let discovered = [];
321
+ try {
322
+ discovered = await fastGlob.glob(["**/*minecraft*.jar", "**/*merged*.jar"], {
323
+ cwd: root,
324
+ absolute: true,
325
+ onlyFiles: true,
326
+ ignore: ["**/*sources.jar", "**/node_modules/**", "**/.git/**", "**/build/**", "**/out/**"]
327
+ });
328
+ }
329
+ catch {
330
+ continue;
331
+ }
332
+ for (const candidatePath of discovered) {
333
+ const normalizedPath = normalizePathStyle(candidatePath);
334
+ if (seen.has(normalizedPath)) {
335
+ continue;
336
+ }
337
+ seen.add(normalizedPath);
338
+ const lower = normalizedPath.toLowerCase();
339
+ if (!lower.includes("minecraft")) {
340
+ continue;
341
+ }
342
+ const exactVersionMatch = hasExactVersionToken(normalizedPath, input.version);
343
+ const looksMerged = lower.includes("minecraft-merged") || lower.includes("/merged/") || lower.includes("-merged");
344
+ const namespaceHint = inferMergedRuntimeNamespaceHint(normalizedPath);
345
+ const appliedScope = looksMerged
346
+ ? "merged"
347
+ : input.requestedScope === "loader"
348
+ ? "merged"
349
+ : input.requestedScope;
350
+ const score = (exactVersionMatch ? 5_000 : 0) +
351
+ (looksMerged ? 4_000 : 0) +
352
+ runtimeJarNamespaceHintScore(namespaceHint) +
353
+ (normalizedProjectPathLower && lower.startsWith(normalizedProjectPathLower) ? 2_000 : 0) +
354
+ (lower.includes("loom-cache") || lower.includes("/caches/fabric-loom/") ? 500 : 0) +
355
+ (lower.includes("minecraft-client") || lower.includes("client") ? 100 : 0);
356
+ candidates.push({
357
+ jarPath: normalizedPath,
358
+ score,
359
+ appliedScope,
360
+ origin: lower.includes("loom-cache") || lower.includes("/caches/fabric-loom/")
361
+ ? "loom-cache"
362
+ : "local-jar",
363
+ namespaceHint
364
+ });
365
+ }
366
+ }
367
+ candidates.sort((left, right) => {
368
+ if (right.score !== left.score) {
369
+ return right.score - left.score;
370
+ }
371
+ return left.jarPath.localeCompare(right.jarPath);
372
+ });
373
+ return {
374
+ searchedPaths,
375
+ candidateArtifacts: candidates.slice(0, 20).map((candidate) => candidate.jarPath),
376
+ selected: candidates[0]
377
+ };
378
+ }
379
+ export async function discoverAccessTransformerRuntimeCandidates(_svc, input) {
380
+ const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
381
+ const normalizedProjectPathLower = normalizedProjectPath
382
+ ? normalizePathStyle(normalizedProjectPath).toLowerCase()
383
+ : undefined;
384
+ const searchRoots = buildLoaderRuntimeSearchRoots(normalizedProjectPath);
385
+ const searchedPaths = [];
386
+ const candidates = [];
387
+ const seen = new Set();
388
+ const globs = [
389
+ "**/*minecraft*.jar",
390
+ "**/*patched*.jar",
391
+ "**/*srg*.jar",
392
+ "**/*joined*.jar",
393
+ "**/*client-extra*.jar",
394
+ "**/*forge*.jar",
395
+ "**/*neoforge*.jar",
396
+ "**/*moddev*.jar",
397
+ "**/*neoform*.jar"
398
+ ];
399
+ for (const root of searchRoots) {
400
+ searchedPaths.push(root);
401
+ if (!(await pathExists(root))) {
402
+ continue;
403
+ }
404
+ let discovered = [];
405
+ try {
406
+ discovered = await fastGlob.glob(globs, {
407
+ cwd: root,
408
+ absolute: true,
409
+ onlyFiles: true,
410
+ ignore: ["**/*sources.jar", "**/node_modules/**", "**/.git/**", "**/out/**"]
411
+ });
412
+ }
413
+ catch {
414
+ continue;
415
+ }
416
+ for (const candidatePath of discovered) {
417
+ const normalizedPath = normalizePathStyle(candidatePath);
418
+ if (seen.has(normalizedPath)) {
419
+ continue;
420
+ }
421
+ seen.add(normalizedPath);
422
+ const lower = normalizedPath.toLowerCase();
423
+ if (!hasExactVersionToken(normalizedPath, input.version)) {
424
+ continue;
425
+ }
426
+ const looksMerged = lower.includes("merged");
427
+ const looksSrg = lower.includes("srg");
428
+ const looksForge = lower.includes("forge");
429
+ const looksNeoForge = lower.includes("neoforge") || lower.includes("moddev") || lower.includes("neoform");
430
+ const looksPatchedRuntime = lower.includes("patched") || lower.includes("client-extra") || lower.includes("joined");
431
+ const appliedScope = looksMerged
432
+ ? "merged"
433
+ : "loader";
434
+ if (input.atNamespace === "srg" && !looksSrg) {
435
+ continue;
436
+ }
437
+ if (input.loader === "forge" && !looksForge && !looksSrg && !looksPatchedRuntime) {
438
+ continue;
439
+ }
440
+ if (input.loader === "neoforge" && !looksNeoForge && !looksPatchedRuntime && !lower.includes("minecraft")) {
441
+ continue;
442
+ }
443
+ const score = 10_000 +
444
+ (normalizedProjectPathLower && lower.startsWith(normalizedProjectPathLower) ? 4_000 : 0) +
445
+ (looksPatchedRuntime ? 3_000 : 0) +
446
+ (looksSrg ? 2_500 : 0) +
447
+ (input.loader === "forge" && looksForge ? 1_500 : 0) +
448
+ (input.loader === "neoforge" && looksNeoForge ? 1_500 : 0) +
449
+ (input.requestedScope === appliedScope ? 1_000 : 0) +
450
+ (looksMerged ? -500 : 0);
451
+ candidates.push({
452
+ jarPath: normalizedPath,
453
+ score,
454
+ appliedScope,
455
+ origin: "local-jar"
456
+ });
457
+ }
458
+ }
459
+ candidates.sort((left, right) => {
460
+ if (right.score !== left.score) {
461
+ return right.score - left.score;
462
+ }
463
+ return left.jarPath.localeCompare(right.jarPath);
464
+ });
465
+ return {
466
+ searchedPaths,
467
+ candidateArtifacts: candidates.slice(0, 20).map((candidate) => candidate.jarPath),
468
+ selected: candidates[0]
469
+ };
470
+ }
471
+ export async function resolveAccessWidenerRuntimeArtifact(svc, input) {
472
+ const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
473
+ let version = input.version;
474
+ if (input.preferProjectVersion && normalizedProjectPath) {
475
+ const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
476
+ version = detected ?? version;
477
+ }
478
+ const requestedScope = input.scope ?? (normalizedProjectPath ? "loader" : "vanilla");
479
+ if (requestedScope === "vanilla") {
480
+ const versionJar = await svc.versionService.resolveVersionJar(version);
481
+ return {
482
+ version: versionJar.version,
483
+ jarPath: versionJar.jarPath,
484
+ requestedScope,
485
+ appliedScope: "vanilla",
486
+ requestedMapping: input.awNamespace,
487
+ mappingApplied: "obfuscated",
488
+ origin: "version-jar"
489
+ };
490
+ }
491
+ const discovery = await svc.discoverAccessWidenerRuntimeCandidates({
492
+ version,
493
+ projectPath: normalizedProjectPath,
494
+ requestedScope
495
+ });
496
+ if (!discovery.selected) {
497
+ throw createError({
498
+ code: ERROR_CODES.CONTEXT_UNRESOLVED,
499
+ message: "Could not resolve a runtime jar for Access Widener validation.",
500
+ details: {
501
+ version,
502
+ requestedScope,
503
+ projectPath: normalizedProjectPath,
504
+ searchedPaths: discovery.searchedPaths,
505
+ candidateArtifacts: discovery.candidateArtifacts,
506
+ nextAction: "Provide projectPath for a Loom workspace with generated runtime jars, or run Gradle tasks that populate the Loom cache before retrying.",
507
+ ...buildSuggestedCall({
508
+ tool: "validate-access-widener",
509
+ params: {
510
+ version,
511
+ scope: requestedScope,
512
+ ...(normalizedProjectPath ? { projectPath: normalizedProjectPath } : {})
513
+ }
514
+ })
515
+ }
516
+ });
517
+ }
518
+ const appliedScope = discovery.selected.appliedScope;
519
+ const scopeFallback = requestedScope !== appliedScope
520
+ ? {
521
+ requested: requestedScope,
522
+ applied: appliedScope,
523
+ reason: requestedScope === "loader"
524
+ ? "Fabric loader runtime validation currently reuses the merged runtime jar."
525
+ : "Selected runtime jar matched a nearby merged artifact."
526
+ }
527
+ : undefined;
528
+ let detectedMapping;
529
+ const notes = [];
530
+ if (scopeFallback) {
531
+ notes.push(scopeFallback.reason);
532
+ }
533
+ if (isUnobfuscatedVersion(version)) {
534
+ detectedMapping = "obfuscated";
535
+ }
536
+ else if (discovery.selected.namespaceHint === "intermediary" ||
537
+ discovery.selected.namespaceHint === "mojang") {
538
+ detectedMapping = discovery.selected.namespaceHint;
539
+ }
540
+ else {
541
+ const detection = await detectFabricLikeInputNamespace(discovery.selected.jarPath);
542
+ detectedMapping = detection.fromNamespace;
543
+ if (detection.warnings.length > 0) {
544
+ notes.push(...detection.warnings);
545
+ }
546
+ }
547
+ return {
548
+ version,
549
+ jarPath: discovery.selected.jarPath,
550
+ requestedScope,
551
+ appliedScope,
552
+ requestedMapping: input.awNamespace,
553
+ mappingApplied: detectedMapping,
554
+ origin: discovery.selected.origin,
555
+ resolutionNotes: notes.length > 0 ? notes : undefined,
556
+ scopeFallback
557
+ };
558
+ }
559
+ export async function resolveAccessTransformerNamespace(svc, input) {
560
+ const explicit = normalizeAccessTransformerNamespace(input.atNamespace);
561
+ if (explicit) {
562
+ return explicit;
563
+ }
564
+ const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
565
+ if (!normalizedProjectPath) {
566
+ throw createError({
567
+ code: ERROR_CODES.INVALID_INPUT,
568
+ message: "atNamespace is required when projectPath is not provided.",
569
+ details: {
570
+ nextAction: "Pass atNamespace explicitly, or provide projectPath for a Forge/NeoForge workspace so the namespace can be inferred."
571
+ }
572
+ });
573
+ }
574
+ const loaderDetection = await svc.workspaceMappingService.detectProjectLoader(normalizedProjectPath);
575
+ if (loaderDetection.resolved && loaderDetection.loader === "forge") {
576
+ return "srg";
577
+ }
578
+ if (loaderDetection.resolved && loaderDetection.loader === "neoforge") {
579
+ return "mojang";
580
+ }
581
+ throw createError({
582
+ code: ERROR_CODES.INVALID_INPUT,
583
+ message: "Could not infer atNamespace from the workspace.",
584
+ details: {
585
+ projectPath: normalizedProjectPath,
586
+ evidence: loaderDetection.evidence,
587
+ warnings: loaderDetection.warnings,
588
+ nextAction: "Pass atNamespace explicitly, or point projectPath at a Forge/NeoForge workspace."
589
+ }
590
+ });
591
+ }
592
+ export async function resolveAccessTransformerRuntimeArtifact(svc, input) {
593
+ const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
594
+ let version = input.version;
595
+ if (input.preferProjectVersion && normalizedProjectPath) {
596
+ const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
597
+ version = detected ?? version;
598
+ }
599
+ const requestedScope = input.scope ?? (normalizedProjectPath ? "loader" : "vanilla");
600
+ if (requestedScope === "vanilla") {
601
+ if (input.atNamespace === "srg") {
602
+ throw createError({
603
+ code: ERROR_CODES.INVALID_INPUT,
604
+ message: "atNamespace=srg requires projectPath and scope=loader so a Forge runtime jar can be resolved."
605
+ });
606
+ }
607
+ const versionJar = await svc.versionService.resolveVersionJar(version);
608
+ return {
609
+ version: versionJar.version,
610
+ jarPath: versionJar.jarPath,
611
+ requestedScope,
612
+ appliedScope: "vanilla",
613
+ requestedMapping: input.atNamespace,
614
+ mappingApplied: "obfuscated",
615
+ origin: "version-jar"
616
+ };
617
+ }
618
+ const loaderDetection = normalizedProjectPath
619
+ ? await svc.workspaceMappingService.detectProjectLoader(normalizedProjectPath)
620
+ : { resolved: false, loader: undefined, evidence: [], warnings: [] };
621
+ const loader = loaderDetection.resolved ? loaderDetection.loader ?? "unknown" : "unknown";
622
+ const discovery = await svc.discoverAccessTransformerRuntimeCandidates({
623
+ version,
624
+ projectPath: normalizedProjectPath,
625
+ requestedScope,
626
+ atNamespace: input.atNamespace,
627
+ loader
628
+ });
629
+ if (!discovery.selected) {
630
+ throw createError({
631
+ code: ERROR_CODES.CONTEXT_UNRESOLVED,
632
+ message: "Could not resolve a runtime jar for Access Transformer validation.",
633
+ details: {
634
+ version,
635
+ requestedScope,
636
+ atNamespace: input.atNamespace,
637
+ projectPath: normalizedProjectPath,
638
+ searchedPaths: discovery.searchedPaths,
639
+ candidateArtifacts: discovery.candidateArtifacts,
640
+ loaderEvidence: loaderDetection.evidence,
641
+ loaderWarnings: loaderDetection.warnings,
642
+ nextAction: "Provide projectPath for a Forge/NeoForge workspace with generated runtime jars, or run the Gradle tasks that populate transformed runtime artifacts before retrying."
643
+ }
644
+ });
645
+ }
646
+ const selected = discovery.selected;
647
+ const selectedLower = selected.jarPath.toLowerCase();
648
+ const mappingApplied = input.atNamespace === "srg" || selectedLower.includes("srg")
649
+ ? "srg"
650
+ : loader === "neoforge" || selectedLower.includes("moddev") || selectedLower.includes("neoforge")
651
+ ? "mojang"
652
+ : "obfuscated";
653
+ const scopeFallback = requestedScope !== selected.appliedScope
654
+ ? {
655
+ requested: requestedScope,
656
+ applied: selected.appliedScope,
657
+ reason: selected.appliedScope === "merged"
658
+ ? "Resolved a nearby merged runtime jar because no transformed loader artifact was available."
659
+ : "Resolved the closest transformed runtime artifact for validation."
660
+ }
661
+ : undefined;
662
+ return {
663
+ version,
664
+ jarPath: selected.jarPath,
665
+ requestedScope,
666
+ appliedScope: selected.appliedScope,
667
+ requestedMapping: input.atNamespace,
668
+ mappingApplied,
669
+ origin: selected.origin,
670
+ resolutionNotes: scopeFallback ? [scopeFallback.reason] : undefined,
671
+ scopeFallback
672
+ };
673
+ }
674
+ export function inferVersionFromContext(input) {
675
+ const direct = normalizeOptionalString(input.version);
676
+ if (direct) {
677
+ return direct;
678
+ }
679
+ const resolvedFromVersion = normalizeOptionalString(input.provenance?.resolvedFrom.version);
680
+ if (resolvedFromVersion) {
681
+ return resolvedFromVersion;
682
+ }
683
+ if (input.provenance?.target.kind === "version") {
684
+ const targetVersion = normalizeOptionalString(input.provenance.target.value);
685
+ if (targetVersion) {
686
+ return targetVersion;
687
+ }
688
+ }
689
+ const coordinate = normalizeOptionalString(input.coordinate);
690
+ if (coordinate) {
691
+ try {
692
+ return parseCoordinate(coordinate).version;
693
+ }
694
+ catch {
695
+ return undefined;
696
+ }
697
+ }
698
+ return undefined;
699
+ }
700
+ export async function resolveVersionContext(svc, input) {
701
+ const inferredVersion = inferVersionFromContext(input);
702
+ if (inferredVersion) {
703
+ return inferredVersion;
704
+ }
705
+ if (!input.preferProjectVersion || !input.projectPath) {
706
+ return undefined;
707
+ }
708
+ const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(input.projectPath);
709
+ if (detected) {
710
+ input.warnings.push(`Using project version "${detected}" from gradle.properties because the artifact metadata did not include a version.`);
711
+ }
712
+ return detected;
713
+ }
714
+ export async function resolveBinaryFallbackArtifact(svc, input) {
715
+ const binaryJarPath = normalizeOptionalString(input.binaryJarPath);
716
+ if (!binaryJarPath) {
717
+ return undefined;
718
+ }
719
+ try {
720
+ const fallbackResolved = await resolveSourceTargetInternal({ kind: "jar", value: binaryJarPath }, { allowDecompile: true, preferBinaryOnly: true }, svc.config);
721
+ fallbackResolved.version = fallbackResolved.version ?? input.version;
722
+ fallbackResolved.coordinate = fallbackResolved.coordinate ?? input.coordinate;
723
+ fallbackResolved.requestedMapping = input.requestedMapping;
724
+ fallbackResolved.mappingApplied = input.mappingApplied;
725
+ fallbackResolved.provenance = input.provenance;
726
+ fallbackResolved.qualityFlags = dedupeQualityFlags([
727
+ ...(fallbackResolved.qualityFlags ?? []),
728
+ ...input.qualityFlags,
729
+ "binary-fallback"
730
+ ]);
731
+ await svc.ingestIfNeeded(fallbackResolved);
732
+ return fallbackResolved;
733
+ }
734
+ catch {
735
+ return undefined;
736
+ }
737
+ }
738
+ function buildVersionSourceRecoveryCommand(projectPath) {
739
+ const normalizedProjectPath = normalizeOptionalProjectPath(projectPath);
740
+ const prefix = normalizedProjectPath
741
+ ? `cd ${JSON.stringify(normalizedProjectPath)} && `
742
+ : "";
743
+ return `${prefix}./gradlew genSources --no-daemon`;
744
+ }
745
+ async function computeBinaryRemapGate(svc, input) {
746
+ const baseline = {
747
+ allowBinaryRemap: false,
748
+ mappingVariant: "pass",
749
+ warnings: []
750
+ };
751
+ if (input.forceBinaryRemapDisabled === true) {
752
+ return baseline;
753
+ }
754
+ if (input.requestedMapping !== "mojang" ||
755
+ input.runtimeNamesUnobfuscated ||
756
+ !input.version) {
757
+ return baseline;
758
+ }
759
+ if (input.targetKind !== "version") {
760
+ return baseline;
761
+ }
762
+ let tinyRemapperJarPath;
763
+ try {
764
+ tinyRemapperJarPath = await resolveTinyRemapperJar(svc.config.cacheDir, svc.config.tinyRemapperJarPath);
765
+ }
766
+ catch (caughtError) {
767
+ log("warn", "binary-remap.gate.tiny-remapper-unavailable", {
768
+ version: input.version,
769
+ error: caughtError instanceof Error ? caughtError.message : String(caughtError)
770
+ });
771
+ return baseline;
772
+ }
773
+ let mojangTinyFilePath;
774
+ try {
775
+ const mojangTiny = await resolveMojangTinyFile(input.version, svc.config);
776
+ mojangTinyFilePath = mojangTiny.path;
777
+ if (mojangTiny.warnings.length > 0) {
778
+ baseline.warnings.push(...mojangTiny.warnings);
779
+ }
780
+ }
781
+ catch (caughtError) {
782
+ log("warn", "binary-remap.gate.mojang-tiny-unavailable", {
783
+ version: input.version,
784
+ error: caughtError instanceof Error ? caughtError.message : String(caughtError)
785
+ });
786
+ return baseline;
787
+ }
788
+ let mojangAvailable = false;
789
+ try {
790
+ const health = await svc.mappingService.checkMappingHealth({
791
+ version: input.version,
792
+ requestedMapping: "mojang",
793
+ sourcePriority: input.sourcePriority
794
+ });
795
+ mojangAvailable = health.mojangMappingsAvailable;
796
+ }
797
+ catch (caughtError) {
798
+ log("warn", "binary-remap.gate.health-check-failed", {
799
+ version: input.version,
800
+ error: caughtError instanceof Error ? caughtError.message : String(caughtError)
801
+ });
802
+ return baseline;
803
+ }
804
+ if (!mojangAvailable) {
805
+ return baseline;
806
+ }
807
+ return {
808
+ allowBinaryRemap: true,
809
+ mappingVariant: "mojang-remapped",
810
+ tinyRemapperJarPath,
811
+ mojangTinyFilePath,
812
+ warnings: baseline.warnings
813
+ };
814
+ }
815
+ export function buildArtifactContentsSummary(_svc, input) {
816
+ const sourceKind = input.isDecompiled || input.origin === "decompiled" || !normalizeOptionalString(input.sourceJarPath)
817
+ ? "decompiled-binary"
818
+ : "source-jar";
819
+ const sourceCoverage = hasPartialNetMinecraftCoverage(input.qualityFlags) ? "partial" : "full";
820
+ return {
821
+ sourceKind,
822
+ indexedContentKinds: ["java-source"],
823
+ resourcesIncluded: false,
824
+ sourceCoverage
825
+ };
826
+ }
827
+ export async function buildMappingFallbackSuggestedCall(svc, args) {
828
+ const { input, kind, value, scope, effectiveMapping } = args;
829
+ const isVanillaMojang = scope === "vanilla" && effectiveMapping === "mojang";
830
+ if (process.env.WORKSPACE_FALLBACK_LEGACY === "1") {
831
+ return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath: input.projectPath });
832
+ }
833
+ const projectPath = input.projectPath?.trim();
834
+ if (!projectPath) {
835
+ return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath: undefined });
836
+ }
837
+ if (kind !== "version") {
838
+ return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath });
839
+ }
840
+ const cached = svc.workspaceContextCache.read(projectPath);
841
+ if (cached &&
842
+ !cached.partial &&
843
+ cached.compileMapping &&
844
+ cached.compileMapping !== "obfuscated" &&
845
+ cached.minecraftVersion === value) {
846
+ return {
847
+ ...buildSuggestedCall({
848
+ tool: "resolve-artifact",
849
+ params: {
850
+ target: { kind: "workspace" },
851
+ projectPath,
852
+ mapping: cached.compileMapping
853
+ }
854
+ }),
855
+ nextAction: `Workspace at ${projectPath} maps as ${cached.compileMapping}. Retry with target.kind="workspace" to use the project's compile mapping.`
856
+ };
857
+ }
858
+ if (!cached) {
859
+ try {
860
+ const detectedVersion = await svc.workspaceMappingService.detectProjectMinecraftVersion(projectPath);
861
+ if (!detectedVersion || detectedVersion !== value) {
862
+ return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath });
863
+ }
864
+ const detection = await svc.workspaceMappingService.detectCompileMapping({ projectPath });
865
+ if (detection.resolved && detection.mappingApplied && detection.mappingApplied !== "obfuscated") {
866
+ const partial = {
867
+ projectPath,
868
+ minecraftVersion: detectedVersion,
869
+ compileMapping: detection.mappingApplied,
870
+ detectedAt: Date.now(),
871
+ evidence: detection.evidence.map((entry) => ({
872
+ source: entry.filePath,
873
+ field: "compileMapping",
874
+ value: entry.mapping
875
+ })),
876
+ dependencyVersions: new Map(),
877
+ partial: true
878
+ };
879
+ svc.workspaceContextCache.write(partial);
880
+ return {
881
+ ...buildSuggestedCall({
882
+ tool: "resolve-artifact",
883
+ params: {
884
+ target: { kind: "workspace" },
885
+ projectPath,
886
+ mapping: detection.mappingApplied
887
+ }
888
+ }),
889
+ nextAction: `Workspace at ${projectPath} maps as ${detection.mappingApplied}. Retry with target.kind="workspace" to use the project's compile mapping.`
890
+ };
891
+ }
892
+ }
893
+ catch {
894
+ // bounded detection failed; fall back to legacy
895
+ }
896
+ }
897
+ return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath });
898
+ }
899
+ function buildLegacyMappingFallback(args) {
900
+ const { kind, value, scope, isVanillaMojang, projectPath } = args;
901
+ if (isVanillaMojang && projectPath) {
902
+ return {
903
+ ...buildSuggestedCall({
904
+ tool: "resolve-artifact",
905
+ params: buildResolveArtifactParams({ kind, value }, { mapping: "mojang", scope: "merged", projectPath })
906
+ }),
907
+ nextAction: "scope=vanilla blocks Loom cache discovery needed for mojang mapping. " +
908
+ "Retry with scope=merged to allow source-jar resolution from the project cache."
909
+ };
910
+ }
911
+ if (isVanillaMojang) {
912
+ return {
913
+ ...buildSuggestedCall({
914
+ tool: "resolve-artifact",
915
+ params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", scope: "vanilla" })
916
+ }),
917
+ nextAction: "scope=vanilla blocks Loom cache discovery needed for mojang mapping. " +
918
+ "Without a projectPath, use mapping=obfuscated to read vanilla runtime names directly."
919
+ };
920
+ }
921
+ return {
922
+ ...buildSuggestedCall({
923
+ tool: "resolve-artifact",
924
+ params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", ...(scope ? { scope } : {}) })
925
+ }),
926
+ nextAction: "Retry with mapping=obfuscated to use the runtime obfuscated namespace."
927
+ };
928
+ }
929
+ export async function resolveArtifact(svc, input) {
930
+ let workspaceProvenance;
931
+ let dependencyProvenance;
932
+ let dependencyOrigin = false;
933
+ let dependencyRequestedMapping;
934
+ const synthesisWarnings = [];
935
+ if (input.target.kind === "workspace") {
936
+ const synthesized = await svc.synthesizeWorkspaceTarget(input, input.target);
937
+ workspaceProvenance = synthesized.provenance;
938
+ synthesisWarnings.push(...synthesized.warnings);
939
+ input = {
940
+ ...input,
941
+ target: synthesized.target,
942
+ scope: synthesized.scope ?? input.scope,
943
+ mapping: synthesized.mapping
944
+ };
945
+ }
946
+ else if (input.target.kind === "dependency") {
947
+ const synthesized = await svc.synthesizeDependencyTarget(input, input.target);
948
+ dependencyProvenance = synthesized.provenance;
949
+ dependencyOrigin = true;
950
+ dependencyRequestedMapping = synthesized.requestedMapping;
951
+ synthesisWarnings.push(...synthesized.warnings);
952
+ input = { ...input, target: synthesized.target };
953
+ }
954
+ const target = input.target;
955
+ const kind = target.kind;
956
+ let value = target.value?.trim();
957
+ const mapping = normalizeMapping(input.mapping);
958
+ const scope = input.scope;
959
+ const warnings = [...synthesisWarnings];
960
+ if (input.preferProjectVersion && input.projectPath && kind === "version") {
961
+ const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(input.projectPath);
962
+ if (detected && detected !== value) {
963
+ warnings.push(`Overriding version "${value}" with project version "${detected}" from gradle.properties.`);
964
+ }
965
+ value = detected ?? value;
966
+ }
967
+ if (!value) {
968
+ throw createError({
969
+ code: ERROR_CODES.INVALID_INPUT,
970
+ message: "target.value must be non-empty.",
971
+ details: { target: input.target }
972
+ });
973
+ }
974
+ if (kind !== "jar" && kind !== "coordinate" && kind !== "version") {
975
+ throw createError({
976
+ code: ERROR_CODES.INVALID_INPUT,
977
+ message: `Unsupported target kind "${kind}".`,
978
+ details: { target: input.target }
979
+ });
980
+ }
981
+ if (kind === "jar" && !value.toLowerCase().endsWith(".jar")) {
982
+ throw createError({
983
+ code: ERROR_CODES.INVALID_INPUT,
984
+ message: "target.kind=jar requires a .jar path.",
985
+ details: { target: input.target }
986
+ });
987
+ }
988
+ const startedAt = Date.now();
989
+ try {
990
+ let resolvedTarget = { kind, value };
991
+ let resolvedVersion;
992
+ let versionSourceDiscovery;
993
+ let runtimeNamesUnobfuscated = false;
994
+ if (kind === "version") {
995
+ const versionJar = await svc.versionService.resolveVersionJar(value);
996
+ resolvedVersion = versionJar.version;
997
+ runtimeNamesUnobfuscated = isUnobfuscatedVersion(resolvedVersion);
998
+ resolvedTarget = {
999
+ kind: "jar",
1000
+ value: versionJar.jarPath
1001
+ };
1002
+ warnings.push(`Resolved Minecraft ${versionJar.version} from ${versionJar.clientJarUrl}.`);
1003
+ }
1004
+ if (kind === "coordinate") {
1005
+ try {
1006
+ resolvedVersion = parseCoordinate(value).version;
1007
+ }
1008
+ catch {
1009
+ // coordinate validity is validated by resolver
1010
+ }
1011
+ }
1012
+ if (!runtimeNamesUnobfuscated && resolvedVersion && isUnobfuscatedVersion(resolvedVersion)) {
1013
+ runtimeNamesUnobfuscated = true;
1014
+ }
1015
+ let effectiveMapping = mapping;
1016
+ if ((mapping === "intermediary" || mapping === "yarn") &&
1017
+ resolvedVersion &&
1018
+ isUnobfuscatedVersion(resolvedVersion)) {
1019
+ warnings.push(`Version ${resolvedVersion} is unobfuscated; ${mapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
1020
+ effectiveMapping = "obfuscated";
1021
+ }
1022
+ if (kind === "version" &&
1023
+ resolvedVersion &&
1024
+ effectiveMapping === "mojang" &&
1025
+ !runtimeNamesUnobfuscated &&
1026
+ scope !== "vanilla") {
1027
+ versionSourceDiscovery = await svc.discoverVersionSourceJar({
1028
+ version: resolvedVersion,
1029
+ projectPath: input.projectPath
1030
+ });
1031
+ if (versionSourceDiscovery.selectedSourceJarPath) {
1032
+ resolvedTarget = {
1033
+ kind: "jar",
1034
+ value: versionSourceDiscovery.selectedSourceJarPath
1035
+ };
1036
+ warnings.push(`Resolved source-backed artifact from Loom cache candidate: ${versionSourceDiscovery.selectedSourceJarPath}.`);
1037
+ }
1038
+ }
1039
+ const sourceJarPreSelected = Boolean(versionSourceDiscovery?.selectedSourceJarPath);
1040
+ const binaryRemapGate = sourceJarPreSelected
1041
+ ? { allowBinaryRemap: false, mappingVariant: "pass", warnings: [] }
1042
+ : await computeBinaryRemapGate(svc, {
1043
+ requestedMapping: effectiveMapping,
1044
+ runtimeNamesUnobfuscated,
1045
+ version: resolvedVersion,
1046
+ targetKind: kind,
1047
+ sourcePriority: input.sourcePriority,
1048
+ forceBinaryRemapDisabled: dependencyOrigin
1049
+ });
1050
+ if (binaryRemapGate.warnings.length > 0) {
1051
+ warnings.push(...binaryRemapGate.warnings);
1052
+ }
1053
+ const resolved = await resolveSourceTargetInternal(resolvedTarget, {
1054
+ allowDecompile: effectiveMapping === "mojang" ? true : input.allowDecompile ?? true,
1055
+ mappingVariant: binaryRemapGate.mappingVariant,
1056
+ onRepoFailover: (event) => {
1057
+ svc.metrics.recordRepoFailover();
1058
+ log("warn", "repo.failover", {
1059
+ stage: event.stage,
1060
+ repoUrl: event.repoUrl,
1061
+ statusCode: event.statusCode,
1062
+ reason: event.reason,
1063
+ attempt: event.attempt,
1064
+ totalAttempts: event.totalAttempts
1065
+ });
1066
+ }
1067
+ }, svc.config);
1068
+ resolved.version = resolvedVersion;
1069
+ let mappingDecision;
1070
+ try {
1071
+ mappingDecision = applyMappingPipeline({
1072
+ requestedMapping: effectiveMapping,
1073
+ target: { kind, value },
1074
+ resolved,
1075
+ runtimeNamesUnobfuscated,
1076
+ allowBinaryRemap: binaryRemapGate.allowBinaryRemap
1077
+ });
1078
+ }
1079
+ catch (caughtError) {
1080
+ if (dependencyOrigin &&
1081
+ isAppError(caughtError) &&
1082
+ caughtError.code === ERROR_CODES.MAPPING_NOT_APPLIED) {
1083
+ mappingDecision = {
1084
+ mappingApplied: "obfuscated",
1085
+ transformChain: [],
1086
+ qualityFlags: [
1087
+ ...(resolved.qualityFlags ?? []),
1088
+ "dependency-mapping-unverified"
1089
+ ]
1090
+ };
1091
+ }
1092
+ else if (isAppError(caughtError) && caughtError.code === ERROR_CODES.MAPPING_NOT_APPLIED) {
1093
+ const fallback = await svc.buildMappingFallbackSuggestedCall({
1094
+ input,
1095
+ kind,
1096
+ value,
1097
+ scope,
1098
+ effectiveMapping
1099
+ });
1100
+ const { nextAction, ...fallbackGated } = fallback;
1101
+ throw createError({
1102
+ code: ERROR_CODES.MAPPING_NOT_APPLIED,
1103
+ message: caughtError.message,
1104
+ details: {
1105
+ ...(caughtError.details ?? {}),
1106
+ artifactOrigin: resolved.origin,
1107
+ searchedPaths: versionSourceDiscovery?.searchedPaths ?? [],
1108
+ candidateArtifacts: versionSourceDiscovery?.candidateArtifacts ?? resolved.adjacentSourceCandidates ?? [],
1109
+ recommendedCommand: buildVersionSourceRecoveryCommand(input.projectPath),
1110
+ nextAction,
1111
+ ...fallbackGated
1112
+ }
1113
+ });
1114
+ }
1115
+ else {
1116
+ throw caughtError;
1117
+ }
1118
+ }
1119
+ const additionalTransformChain = [];
1120
+ if (!dependencyOrigin && (effectiveMapping === "intermediary" || effectiveMapping === "yarn")) {
1121
+ if (!resolved.version) {
1122
+ throw createError({
1123
+ code: ERROR_CODES.MAPPING_NOT_APPLIED,
1124
+ message: `Requested ${effectiveMapping} mapping cannot be guaranteed because artifact version is unknown.`,
1125
+ details: {
1126
+ mapping: effectiveMapping,
1127
+ target: { kind, value },
1128
+ nextAction: "Use target: { kind: \"version\", value } or a versioned Maven coordinate so mapping artifacts can be resolved.",
1129
+ ...buildSuggestedCall({
1130
+ tool: "resolve-artifact",
1131
+ params: buildResolveArtifactParams({ kind: "version", value }, { ...(scope ? { scope } : {}) })
1132
+ })
1133
+ }
1134
+ });
1135
+ }
1136
+ const mappingAvailability = await svc.mappingService.ensureMappingAvailable({
1137
+ version: resolved.version,
1138
+ sourceMapping: "obfuscated",
1139
+ targetMapping: effectiveMapping,
1140
+ sourcePriority: input.sourcePriority
1141
+ });
1142
+ additionalTransformChain.push(...mappingAvailability.transformChain);
1143
+ if (mappingAvailability.warnings.length > 0) {
1144
+ warnings.push(...mappingAvailability.warnings);
1145
+ }
1146
+ }
1147
+ const provenance = buildProvenance({
1148
+ requestedTarget: { kind, value },
1149
+ resolved,
1150
+ transformChain: [...mappingDecision.transformChain, ...additionalTransformChain]
1151
+ });
1152
+ if (workspaceProvenance) {
1153
+ provenance.workspaceResolution = workspaceProvenance;
1154
+ }
1155
+ if (dependencyProvenance) {
1156
+ provenance.dependencyResolution = dependencyProvenance;
1157
+ }
1158
+ if (dependencyOrigin && dependencyRequestedMapping && dependencyRequestedMapping !== "obfuscated") {
1159
+ const coord = resolved.coordinate ?? value;
1160
+ warnings.push(`Dependency artifact ${coord} mapping "${dependencyRequestedMapping}" is not enforced (binary remap is disabled for non-vanilla artifacts); the JAR is returned in its native namespace and mappingApplied is reported as "obfuscated" with qualityFlag "dependency-mapping-unverified". Caller must validate symbol availability.`);
1161
+ }
1162
+ const provenanceWarnings = [...synthesisWarnings];
1163
+ if (provenanceWarnings.length > 0) {
1164
+ provenance.warnings = [...(provenance.warnings ?? []), ...provenanceWarnings];
1165
+ }
1166
+ resolved.requestedMapping = effectiveMapping;
1167
+ resolved.mappingApplied = mappingDecision.mappingApplied;
1168
+ resolved.provenance = provenance;
1169
+ resolved.qualityFlags = [...mappingDecision.qualityFlags];
1170
+ if (versionSourceDiscovery?.candidateArtifacts.length) {
1171
+ resolved.qualityFlags.push("source-jar-found");
1172
+ }
1173
+ if (versionSourceDiscovery?.selectedSourceJarPath) {
1174
+ resolved.qualityFlags.push("source-jar-validated");
1175
+ if (versionSourceDiscovery.selectedHasMinecraftNamespace === false) {
1176
+ resolved.qualityFlags.push("partial-source-no-net-minecraft");
1177
+ warnings.push(`Source coverage does not include net.minecraft for ${versionSourceDiscovery.selectedSourceJarPath}; class lookups may fall back to the binary artifact.`);
1178
+ }
1179
+ if (kind === "version" && !hasExactVersionToken(versionSourceDiscovery.selectedSourceJarPath, value)) {
1180
+ if (input.strictVersion) {
1181
+ throw createError({
1182
+ code: ERROR_CODES.VERSION_NOT_FOUND,
1183
+ message: `Strict version match failed: requested "${value}" but nearest source jar is for a different version.`,
1184
+ details: {
1185
+ requestedVersion: value,
1186
+ selectedSourceJar: versionSourceDiscovery.selectedSourceJarPath,
1187
+ candidateArtifacts: versionSourceDiscovery.candidateArtifacts,
1188
+ nextAction: "Use strictVersion=false (default) to allow approximation, or ensure the exact version source jar is in the Loom cache.",
1189
+ ...buildSuggestedCall({
1190
+ tool: "resolve-artifact",
1191
+ params: buildResolveArtifactParams({ kind: "version", value }, { strictVersion: false })
1192
+ })
1193
+ }
1194
+ });
1195
+ }
1196
+ resolved.qualityFlags.push("version-approximated");
1197
+ warnings.push(`Requested version "${value}" but resolved source jar does not contain exact version string: ${versionSourceDiscovery.selectedSourceJarPath}`);
1198
+ }
1199
+ }
1200
+ resolved.qualityFlags = dedupeQualityFlags(resolved.qualityFlags);
1201
+ const aliasValue = kind === "jar"
1202
+ ? (resolved.sourceJarPath ?? resolved.binaryJarPath ?? value)
1203
+ : value;
1204
+ const artifactAlias = buildArtifactAlias({
1205
+ artifactId: resolved.artifactId,
1206
+ kind,
1207
+ value: aliasValue,
1208
+ mappingVariant: binaryRemapGate.mappingVariant,
1209
+ resolvedVersion: resolvedVersion ?? resolved.version,
1210
+ coordinate: resolved.coordinate
1211
+ });
1212
+ resolved.artifactAlias = artifactAlias;
1213
+ await svc.ingestIfNeeded(resolved);
1214
+ let sampleEntries;
1215
+ if (input.compact === false && resolved.sourceJarPath) {
1216
+ try {
1217
+ const javaEntries = await listJavaEntries(resolved.sourceJarPath);
1218
+ const MAX_SAMPLE = 10;
1219
+ sampleEntries = javaEntries.slice(0, MAX_SAMPLE);
1220
+ if (javaEntries.length > MAX_SAMPLE) {
1221
+ sampleEntries.push(`... and ${javaEntries.length - MAX_SAMPLE} more .java entries`);
1222
+ }
1223
+ }
1224
+ catch {
1225
+ // non-fatal
1226
+ }
1227
+ }
1228
+ return {
1229
+ artifactId: resolved.artifactId,
1230
+ artifactAlias,
1231
+ origin: resolved.origin,
1232
+ isDecompiled: resolved.isDecompiled,
1233
+ resolvedSourceJarPath: resolved.sourceJarPath,
1234
+ adjacentSourceCandidates: resolved.adjacentSourceCandidates,
1235
+ binaryJarPath: resolved.binaryJarPath,
1236
+ coordinate: resolved.coordinate,
1237
+ version: resolved.version,
1238
+ requestedMapping: effectiveMapping,
1239
+ mappingApplied: mappingDecision.mappingApplied,
1240
+ provenance,
1241
+ qualityFlags: resolved.qualityFlags,
1242
+ repoUrl: resolved.repoUrl,
1243
+ artifactContents: svc.buildArtifactContentsSummary({
1244
+ origin: resolved.origin,
1245
+ sourceJarPath: resolved.sourceJarPath,
1246
+ isDecompiled: resolved.isDecompiled,
1247
+ qualityFlags: resolved.qualityFlags
1248
+ }),
1249
+ warnings,
1250
+ sampleEntries
1251
+ };
1252
+ }
1253
+ catch (caughtError) {
1254
+ if (isAppError(caughtError)) {
1255
+ throw caughtError;
1256
+ }
1257
+ throw createError({
1258
+ code: ERROR_CODES.ARTIFACT_RESOLUTION_FAILED,
1259
+ message: "Failed to resolve artifact.",
1260
+ details: {
1261
+ target: input.target,
1262
+ mapping,
1263
+ reason: caughtError instanceof Error ? caughtError.message : String(caughtError)
1264
+ }
1265
+ });
1266
+ }
1267
+ finally {
1268
+ svc.metrics.recordDuration("resolve_duration_ms", Date.now() - startedAt);
1269
+ }
1270
+ }
1271
+ //# sourceMappingURL=artifact-resolver.js.map