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