@adhisang/minecraft-modding-mcp 7.0.0 → 7.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 (52) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +3 -2
  3. package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
  4. package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
  5. package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
  6. package/dist/entry-tools/batch-class-members-service.js +20 -6
  7. package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
  8. package/dist/entry-tools/batch-class-source-service.js +10 -0
  9. package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
  10. package/dist/entry-tools/compare-minecraft-service.js +65 -4
  11. package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
  12. package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
  13. package/dist/entry-tools/manage-cache-service.d.ts +2 -2
  14. package/dist/entry-tools/validate-project/cases/project-summary.js +71 -12
  15. package/dist/entry-tools/validate-project-service.d.ts +2 -2
  16. package/dist/index.js +37 -15
  17. package/dist/source/artifact-resolver.d.ts +14 -0
  18. package/dist/source/artifact-resolver.js +106 -12
  19. package/dist/source/class-source/members-builder.d.ts +7 -0
  20. package/dist/source/class-source/members-builder.js +4 -1
  21. package/dist/source/class-source.d.ts +9 -2
  22. package/dist/source/class-source.js +173 -25
  23. package/dist/source/indexer.js +69 -1
  24. package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
  25. package/dist/source/lifecycle/mapping-helpers.js +29 -3
  26. package/dist/source/lifecycle/runtime-check.d.ts +25 -0
  27. package/dist/source/lifecycle/runtime-check.js +68 -39
  28. package/dist/source/symbol-resolver.js +88 -0
  29. package/dist/source-jar-reader.d.ts +33 -0
  30. package/dist/source-jar-reader.js +58 -0
  31. package/dist/source-resolver.d.ts +7 -0
  32. package/dist/source-resolver.js +20 -5
  33. package/dist/source-service.d.ts +5 -0
  34. package/dist/source-service.js +7 -0
  35. package/dist/storage/db.d.ts +62 -2
  36. package/dist/storage/db.js +181 -20
  37. package/dist/storage/sqlite.d.ts +31 -1
  38. package/dist/storage/sqlite.js +125 -16
  39. package/dist/tool-guidance.js +4 -1
  40. package/dist/tool-schemas.d.ts +64 -52
  41. package/dist/tool-schemas.js +9 -7
  42. package/dist/types.d.ts +9 -0
  43. package/dist/v1-parity-schemas.js +36 -2
  44. package/dist/version-diff-service.d.ts +23 -0
  45. package/dist/version-diff-service.js +101 -0
  46. package/dist/version-service.d.ts +14 -0
  47. package/dist/version-service.js +45 -3
  48. package/dist/workspace-mapping-service.d.ts +8 -0
  49. package/dist/workspace-mapping-service.js +35 -7
  50. package/docs/README-ja.md +2 -0
  51. package/docs/tool-reference.md +55 -11
  52. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { isAbsolute, resolve as resolvePath } from "node:path";
3
3
  import { buildSuggestedCall } from "../build-suggested-call.js";
4
4
  import { ERROR_CODES, createError, isAppError } from "../errors.js";
5
5
  import * as artifactResolver from "./artifact-resolver.js";
6
+ import { isUnobfuscatedIdentityPair } from "./lifecycle/mapping-helpers.js";
6
7
  import * as classSourceHelpers from "./class-source-helpers.js";
7
8
  import { buildClassSourceSnippet } from "./class-source/snippet-builder.js";
8
9
  import { remapAndCountMembers, sliceMembersWithLimit, projectMembersForWire, projectMembersByLevel } from "./class-source/members-builder.js";
@@ -80,12 +81,33 @@ function looksLikeDeobfuscatedClassName(value) {
80
81
  function obfuscatedNamespaceHint(className) {
81
82
  return `Artifact is indexed in obfuscated runtime names. Deobfuscated names like "${className}" usually require mapping="mojang" or a find-mapping lookup to obfuscated names.`;
82
83
  }
84
+ /**
85
+ * The artifact's Minecraft version when its runtime ships unobfuscated (26.1+)
86
+ * names, else undefined. A native dependency's version is its own release number,
87
+ * not a Minecraft version, so it is never put to `isUnobfuscatedVersion` (the same
88
+ * rule as the `reconcileUnobfuscatedNamespace` call sites).
89
+ */
90
+ function unobfuscatedMinecraftVersion(input) {
91
+ if (input.nativeDependency || !input.version || !isUnobfuscatedVersion(input.version)) {
92
+ return undefined;
93
+ }
94
+ return input.version;
95
+ }
96
+ /**
97
+ * Said where the obfuscated namespace hint would otherwise have been: on 26.1+
98
+ * the as-shipped names are the Mojang names, so no mapping choice finds another class.
99
+ */
100
+ function unobfuscatedRuntimeNamesNote(version) {
101
+ return `Minecraft ${version} ships Mojang names at runtime, so retrying with a different mapping will not change the class names.`;
102
+ }
83
103
  function hasPartialNetMinecraftCoverage(qualityFlags) {
84
104
  return qualityFlags.includes("partial-source-no-net-minecraft");
85
105
  }
86
106
  /**
87
- * Whether "this artifact is indexed in obfuscated names, ask for mapping=mojang"
88
- * is TRUE for this artifact, from the pieces of it the caller sees.
107
+ * Whether a miss has the shape "this artifact is indexed in obfuscated names, ask
108
+ * for mapping=mojang" answers, from the pieces of the artifact the caller sees.
109
+ * The claim can still be false on Minecraft 26.1+; `isObfuscatedNamespaceHintTrue`
110
+ * adds that gate.
89
111
  *
90
112
  * Two artifact kinds report `mappingApplied: "obfuscated"` without being an
91
113
  * obfuscated Minecraft index, and the hint is simply false for them:
@@ -99,22 +121,41 @@ function hasPartialNetMinecraftCoverage(qualityFlags) {
99
121
  * without these exclusions an ordinary library class such as "GameTest"
100
122
  * qualifies and the caller is told to remap a jar that was never obfuscated.
101
123
  */
102
- function isObfuscatedNamespaceHintTrue(input) {
124
+ function isObfuscatedLabelMiss(input) {
103
125
  return (input.mappingApplied === "obfuscated" &&
104
126
  !input.nativeDependency &&
105
127
  !input.qualityFlags.includes("shell-jar") &&
106
128
  looksLikeDeobfuscatedClassName(input.className));
107
129
  }
108
- function shouldSuggestObfuscatedMapping(artifact, className) {
109
- return isObfuscatedNamespaceHintTrue({
130
+ /**
131
+ * `isObfuscatedLabelMiss` is the shape of the miss the hint answers; this adds
132
+ * the one fact that makes its claim false even then. A Minecraft 26.1+ artifact
133
+ * labelled "obfuscated" is indexed in the as-shipped names, which ARE Mojang
134
+ * names: telling the caller to ask for mapping="mojang" sends them round a
135
+ * retry that returns the same names.
136
+ */
137
+ function isObfuscatedNamespaceHintTrue(input) {
138
+ return (isObfuscatedLabelMiss(input) &&
139
+ unobfuscatedMinecraftVersion({
140
+ version: input.minecraftVersion,
141
+ nativeDependency: input.nativeDependency
142
+ }) === undefined);
143
+ }
144
+ function describeArtifactForNamespaceHint(artifact, className) {
145
+ return {
110
146
  mappingApplied: artifact.mappingApplied,
111
147
  qualityFlags: artifact.qualityFlags,
112
148
  nativeDependency: artifactResolver.isDependencyLikeArtifact({
113
149
  provenance: artifact.provenance,
114
150
  coordinate: artifact.coordinate
115
151
  }),
116
- className
117
- });
152
+ className,
153
+ minecraftVersion: artifactResolver.inferVersionFromContext({
154
+ version: artifact.version,
155
+ provenance: artifact.provenance,
156
+ coordinate: artifact.coordinate
157
+ })
158
+ };
118
159
  }
119
160
  function classNameToClassPath(className) {
120
161
  const normalized = normalizePathStyle(className.trim()).replace(/\//g, ".");
@@ -149,10 +190,31 @@ export function resolveClassFilePath(svc, artifactId, className) {
149
190
  const expectedPrefix = lastSlash < 0 ? "" : classPath.slice(0, lastSlash + 1);
150
191
  return svc.filesRepo.findBestClassLookupPath(artifactId, [...candidates], normalizedClassName, simpleName, expectedPrefix);
151
192
  }
193
+ /**
194
+ * The class-name translation behind `svc.resolveClassNameForLookup`, whose callers
195
+ * (the lifecycle tools) pass a Minecraft version, so `version` also keys the 26.1+
196
+ * identity shortcut.
197
+ */
152
198
  export async function resolveClassNameForLookup(svc, input) {
199
+ return resolveArtifactClassNameForLookup(svc, { ...input, minecraftVersion: input.version });
200
+ }
201
+ /**
202
+ * `resolveClassNameForLookup` for an artifact the caller named, whose `version`
203
+ * need not be a Minecraft version: a dependency artifact's is its own release
204
+ * number. `version` still drives the mapping lookup, exactly as before; only
205
+ * `minecraftVersion`, which must be a proven Minecraft version, may take the 26.1+
206
+ * identity shortcut.
207
+ */
208
+ async function resolveArtifactClassNameForLookup(svc, input) {
153
209
  if (input.sourceMapping === input.targetMapping) {
154
210
  return input.className;
155
211
  }
212
+ // On 26.1+ the as-shipped ("obfuscated") names ARE the Mojang names and the
213
+ // mapping graph between them is empty by design, so the lookup can only miss
214
+ // and warn about a translation that is the identity.
215
+ if (isUnobfuscatedIdentityPair(input.minecraftVersion, input.sourceMapping, input.targetMapping)) {
216
+ return input.className;
217
+ }
156
218
  if (!input.version) {
157
219
  input.warnings.push(`Could not map class "${input.className}" from ${input.sourceMapping} to ${input.targetMapping} for ${input.context} because version is unavailable.`);
158
220
  return input.className;
@@ -177,6 +239,38 @@ export async function resolveClassNameForLookup(svc, input) {
177
239
  }
178
240
  return input.className;
179
241
  }
242
+ /**
243
+ * The provenance as returned, carrying `unobfuscatedRuntime` whenever the artifact
244
+ * it describes is a Minecraft 26.1+ runtime. The resolver stamps the flag on every
245
+ * fresh resolve, but a row indexed before the flag existed is served warm and never
246
+ * rewritten, and a row with no provenance gets `buildFallbackProvenance`. Only the
247
+ * provenance's OWN recorded version counts - never a project-derived one - and a
248
+ * dependency-like provenance is excluded, so a library's release number is never
249
+ * read as a Minecraft version. A jar row that predates the in-jar proof records no
250
+ * version and stays unflagged until a resolve proves the jar again, which backfills
251
+ * the row (`backfillResolvedVersion` in the indexer).
252
+ */
253
+ function withUnobfuscatedRuntimeFlag(provenance) {
254
+ if (provenance.unobfuscatedRuntime === true ||
255
+ provenance.nestedJar ||
256
+ artifactResolver.isDependencyLikeArtifact({ provenance })) {
257
+ return provenance;
258
+ }
259
+ // A persisted or stubbed object can be partial; with nothing recorded there is
260
+ // nothing to derive the flag from.
261
+ const partial = provenance;
262
+ if (!partial.resolvedFrom || !partial.target) {
263
+ return provenance;
264
+ }
265
+ const version = unobfuscatedMinecraftVersion({
266
+ version: artifactResolver.inferVersionFromContext({
267
+ provenance,
268
+ coordinate: partial.resolvedFrom.coordinate
269
+ }),
270
+ nativeDependency: false
271
+ });
272
+ return version ? { ...provenance, unobfuscatedRuntime: true } : provenance;
273
+ }
180
274
  export function buildFallbackProvenance(svc, input) {
181
275
  const artifact = svc.getArtifact(input.artifactId);
182
276
  const fallbackTarget = artifact.version
@@ -241,7 +335,21 @@ export function buildClassSourceNotFoundError(svc, input) {
241
335
  // jar. Candidates from the artifact the caller did not name carry its id.
242
336
  didYouMean: unionDidYouMeanCandidates(svc, requestedArtifactId, input.artifactId, input.className)
243
337
  };
338
+ const unobfuscatedVersion = unobfuscatedMinecraftVersion({
339
+ version: input.version,
340
+ nativeDependency: input.nativeDependency === true
341
+ });
244
342
  let nextAction = `Use find-class to resolve the correct fully-qualified name for "${simpleName}".`;
343
+ // On 26.1+ there is no namespace to switch to, so the most useful thing to say
344
+ // first is the nearest class the index does hold.
345
+ const nearestCandidate = unobfuscatedVersion
346
+ ? details.didYouMean[0]
347
+ : undefined;
348
+ if (nearestCandidate) {
349
+ nextAction =
350
+ `Did you mean "${nearestCandidate.className}"? didYouMean lists near-miss classes from the index; ` +
351
+ `otherwise use find-class to resolve the correct fully-qualified name for "${simpleName}".`;
352
+ }
245
353
  let suggestionSpec = {
246
354
  tool: "find-class",
247
355
  params: { className: simpleName, artifactId: requestedArtifactId }
@@ -291,16 +399,26 @@ export function buildClassSourceNotFoundError(svc, input) {
291
399
  // imperative "Provide/Pass mapping", and the sentence is concatenated into
292
400
  // the same `nextAction` string as the find-class guidance, which is published
293
401
  // as a single hint that no mid-string excision can repair.
402
+ //
403
+ // A third gate lives inside `isObfuscatedNamespaceHintTrue`: on Minecraft 26.1+
404
+ // the claim is false, and the same miss gets a note that no mapping will help.
294
405
  const callerAskedForNonObfuscatedMapping = input.callerSuppliedMapping != null && input.callerSuppliedMapping !== "obfuscated";
295
- if (!callerAskedForNonObfuscatedMapping &&
296
- isObfuscatedNamespaceHintTrue({
297
- mappingApplied: input.mappingApplied,
298
- qualityFlags: input.qualityFlags,
299
- nativeDependency: input.nativeDependency === true,
300
- className: input.className
301
- })) {
406
+ const namespaceHintInput = {
407
+ mappingApplied: input.mappingApplied,
408
+ qualityFlags: input.qualityFlags,
409
+ nativeDependency: input.nativeDependency === true,
410
+ className: input.className,
411
+ minecraftVersion: input.version
412
+ };
413
+ if (!callerAskedForNonObfuscatedMapping && isObfuscatedNamespaceHintTrue(namespaceHintInput)) {
302
414
  nextAction += ` ${obfuscatedNamespaceHint(input.className)}`;
303
415
  }
416
+ else if (!callerAskedForNonObfuscatedMapping &&
417
+ unobfuscatedVersion &&
418
+ !hasPartialNetMinecraftCoverage(input.qualityFlags) &&
419
+ isObfuscatedLabelMiss(namespaceHintInput)) {
420
+ nextAction += ` ${unobfuscatedRuntimeNamesNote(unobfuscatedVersion)}`;
421
+ }
304
422
  details.nextAction = nextAction;
305
423
  Object.assign(details, buildSuggestedCall(suggestionSpec));
306
424
  // Split-source workspaces can omit client-only classes from merged indexes;
@@ -609,8 +727,23 @@ function finishFindClass(svc, input) {
609
727
  }
610
728
  }).suggestedCall;
611
729
  }
612
- if (matches.length === 0 && shouldSuggestObfuscatedMapping(artifact, className)) {
613
- warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
730
+ if (matches.length === 0) {
731
+ const hintInput = describeArtifactForNamespaceHint(artifact, className);
732
+ const unobfuscatedVersion = unobfuscatedMinecraftVersion({
733
+ version: hintInput.minecraftVersion,
734
+ nativeDependency: hintInput.nativeDependency
735
+ });
736
+ if (isObfuscatedNamespaceHintTrue(hintInput)) {
737
+ warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
738
+ }
739
+ else if (unobfuscatedVersion && isObfuscatedLabelMiss(hintInput)) {
740
+ const nearest = collectDidYouMeanCandidates(svc, artifactId, className)
741
+ .slice(0, 3)
742
+ .map((candidate) => `"${candidate.className}"`);
743
+ warnings.push(`No exact class symbol matched "${className}".` +
744
+ (nearest.length > 0 ? ` Did you mean ${nearest.join(", ")}?` : "") +
745
+ ` ${unobfuscatedRuntimeNamesNote(unobfuscatedVersion)}`);
746
+ }
614
747
  }
615
748
  return {
616
749
  matches,
@@ -757,6 +890,12 @@ export async function getClassSource(svc, input) {
757
890
  if (!artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })) {
758
891
  mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
759
892
  }
893
+ // The same rule for the class-name lookup: its 26.1+ identity shortcut trusts only
894
+ // a Minecraft version, so a library's 26.x-shaped release number keeps the lookup
895
+ // (and its warnings) that any other library gets.
896
+ const minecraftVersion = artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })
897
+ ? undefined
898
+ : version;
760
899
  let activeArtifactId = artifactId;
761
900
  let activeOrigin = origin;
762
901
  let activeIsDecompiled = isDecompiled;
@@ -857,9 +996,10 @@ export async function getClassSource(svc, input) {
857
996
  warnings.push(`Class "${className}" lives in nested jar "${match.entryName}" bundled by the shell jar; the lookup was redirected there automatically.`);
858
997
  return true;
859
998
  };
860
- let activeLookupClassName = await svc.resolveClassNameForLookup({
999
+ let activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
861
1000
  className,
862
1001
  version,
1002
+ minecraftVersion,
863
1003
  sourceMapping: requestedMapping,
864
1004
  targetMapping: activeMappingApplied,
865
1005
  sourcePriority: input.sourcePriority,
@@ -872,9 +1012,10 @@ export async function getClassSource(svc, input) {
872
1012
  filePath = resolveClassFilePath(svc, activeArtifactId, activeLookupClassName);
873
1013
  }
874
1014
  if (!filePath && (await tryBinaryFallback())) {
875
- activeLookupClassName = await svc.resolveClassNameForLookup({
1015
+ activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
876
1016
  className,
877
1017
  version,
1018
+ minecraftVersion,
878
1019
  sourceMapping: requestedMapping,
879
1020
  targetMapping: activeMappingApplied,
880
1021
  sourcePriority: input.sourcePriority,
@@ -916,9 +1057,10 @@ export async function getClassSource(svc, input) {
916
1057
  }
917
1058
  }
918
1059
  if (!row && (await tryBinaryFallback())) {
919
- activeLookupClassName = await svc.resolveClassNameForLookup({
1060
+ activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
920
1061
  className,
921
1062
  version,
1063
+ minecraftVersion,
922
1064
  sourceMapping: requestedMapping,
923
1065
  targetMapping: activeMappingApplied,
924
1066
  sourcePriority: input.sourcePriority,
@@ -977,13 +1119,13 @@ export async function getClassSource(svc, input) {
977
1119
  resolvedOutputFile = outputPath;
978
1120
  sourceText = `[Written to ${outputPath}]`;
979
1121
  }
980
- const normalizedProvenance = activeProvenance ??
1122
+ const normalizedProvenance = withUnobfuscatedRuntimeFlag(activeProvenance ??
981
1123
  buildFallbackProvenance(svc, {
982
1124
  artifactId: activeArtifactId,
983
1125
  origin: activeOrigin,
984
1126
  requestedMapping,
985
1127
  mappingApplied: activeMappingApplied
986
- });
1128
+ }));
987
1129
  const nextStartLine = snippet.nextStartLine;
988
1130
  // Continuation guidance: when output was truncated and was not redirected to
989
1131
  // a file, hand the caller the next line to read plus a replayable call that
@@ -1152,10 +1294,14 @@ export async function getClassMembers(svc, input) {
1152
1294
  });
1153
1295
  // Gated on the same predicate as the source path above, and for the same
1154
1296
  // reason: a dependency's `version` is its own coordinate version, so the
1155
- // unobfuscated-runtime relabel must not reach it.
1297
+ // unobfuscated-runtime relabel must not reach it - nor the 26.1+ identity
1298
+ // shortcut of the class and member lookups below.
1156
1299
  if (!artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })) {
1157
1300
  mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
1158
1301
  }
1302
+ const minecraftVersion = artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })
1303
+ ? undefined
1304
+ : version;
1159
1305
  if (requestedMapping !== "obfuscated" && !version) {
1160
1306
  throw createError({
1161
1307
  code: ERROR_CODES.MAPPING_NOT_APPLIED,
@@ -1222,9 +1368,10 @@ export async function getClassMembers(svc, input) {
1222
1368
  }
1223
1369
  });
1224
1370
  }
1225
- const lookupClassName = await svc.resolveClassNameForLookup({
1371
+ const lookupClassName = await resolveArtifactClassNameForLookup(svc, {
1226
1372
  className,
1227
1373
  version,
1374
+ minecraftVersion,
1228
1375
  sourceMapping: requestedMapping,
1229
1376
  targetMapping: mappingApplied,
1230
1377
  sourcePriority: input.sourcePriority,
@@ -1347,6 +1494,7 @@ export async function getClassMembers(svc, input) {
1347
1494
  signatureFields,
1348
1495
  signatureMethods,
1349
1496
  version,
1497
+ minecraftVersion,
1350
1498
  mappingApplied,
1351
1499
  requestedMapping,
1352
1500
  sourcePriority: input.sourcePriority,
@@ -1379,13 +1527,13 @@ export async function getClassMembers(svc, input) {
1379
1527
  const projectedMembers = projectMembersByLevel(projectMembersForWire({ constructors, fields, methods }, includeInherited, input.includeDescriptors ?? false), projection);
1380
1528
  const truncated = sliced.truncated;
1381
1529
  const nextCursor = sliced.nextOffset != null ? encodeOffsetCursor(sliced.nextOffset, memberCursorContext) : undefined;
1382
- const baseProvenance = provenance ??
1530
+ const baseProvenance = withUnobfuscatedRuntimeFlag(provenance ??
1383
1531
  buildFallbackProvenance(svc, {
1384
1532
  artifactId,
1385
1533
  origin,
1386
1534
  requestedMapping,
1387
1535
  mappingApplied
1388
- });
1536
+ }));
1389
1537
  const normalizedProvenance = nestedJarRedirect
1390
1538
  ? { ...baseProvenance, nestedJar: nestedJarRedirect }
1391
1539
  : baseProvenance;
@@ -415,8 +415,11 @@ export async function ingestIfNeeded(svc, resolved) {
415
415
  if (resolved.artifactAlias && existing.alias !== resolved.artifactAlias) {
416
416
  svc.artifactsRepo.setAlias(resolved.artifactId, resolved.artifactAlias);
417
417
  }
418
- svc.metrics.recordArtifactCacheHit();
419
418
  const touchedAt = new Date().toISOString();
419
+ if (!existing.version) {
420
+ backfillResolvedVersion(svc, resolved, touchedAt);
421
+ }
422
+ svc.metrics.recordArtifactCacheHit();
420
423
  svc.artifactsRepo.touchArtifact(resolved.artifactId, touchedAt);
421
424
  touchCacheMetrics(svc, resolved.artifactId, touchedAt);
422
425
  return;
@@ -444,6 +447,71 @@ export async function ingestIfNeeded(svc, resolved) {
444
447
  }
445
448
  }
446
449
  }
450
+ /**
451
+ * Writes the version a warm resolve derived onto a stored row that records none,
452
+ * with the two provenance fields that follow from it.
453
+ *
454
+ * The artifactId is a content hash, so a row indexed before its version could be
455
+ * derived - a Minecraft 26.1+ runtime jar indexed before the in-jar proof existed -
456
+ * is served warm by every later resolve and never rewritten. resolve-artifact then
457
+ * reports the version while the row lacks it, and every lookup by artifactId reads
458
+ * the row: get-class-members refuses mapping "mojang" for want of a version, and a
459
+ * miss still gets the legacy "use mapping=mojang" hint.
460
+ *
461
+ * Only gaps are filled. A stored version is never replaced, and the row keeps the
462
+ * label, chain and target it was indexed under. Only a version the fresh provenance
463
+ * records as resolved from is written: resolve-artifact records the version the
464
+ * target itself names (version target, coordinate or in-jar proof), which describes
465
+ * these bytes, while the binary fallback of get-class-source re-ingests with a
466
+ * caller version that may come from the project's gradle.properties, under the
467
+ * stored provenance, so it never qualifies. The row is re-read in the same
468
+ * synchronous turn as the write, so a concurrent writer since `existing` was read
469
+ * is not overwritten.
470
+ * `upsertArtifact` is the repo's only writer for these columns; on an existing row
471
+ * it updates in place and leaves `created_at` and the file index alone.
472
+ */
473
+ function backfillResolvedVersion(svc, resolved, timestamp) {
474
+ const version = resolved.version;
475
+ if (!version || resolved.provenance?.resolvedFrom?.version !== version) {
476
+ return;
477
+ }
478
+ // Only a proven Minecraft 26.1+ runtime qualifies. A library version recorded by a
479
+ // dependency resolve must never land on a row whose stored provenance does not mark
480
+ // it as a dependency, or later lookups would read that version as Minecraft's.
481
+ if (resolved.provenance?.unobfuscatedRuntime !== true) {
482
+ return;
483
+ }
484
+ const current = svc.artifactsRepo.getArtifact(resolved.artifactId);
485
+ if (!current || current.version) {
486
+ return;
487
+ }
488
+ svc.artifactsRepo.upsertArtifact({
489
+ artifactId: current.artifactId,
490
+ alias: current.alias,
491
+ origin: current.origin,
492
+ coordinate: current.coordinate,
493
+ version,
494
+ binaryJarPath: current.binaryJarPath,
495
+ sourceJarPath: current.sourceJarPath,
496
+ repoUrl: current.repoUrl,
497
+ requestedMapping: current.requestedMapping,
498
+ mappingApplied: current.mappingApplied,
499
+ provenance: current.provenance
500
+ ? {
501
+ ...current.provenance,
502
+ resolvedFrom: {
503
+ ...current.provenance.resolvedFrom,
504
+ version: current.provenance.resolvedFrom?.version ?? version
505
+ },
506
+ unobfuscatedRuntime: true
507
+ }
508
+ : undefined,
509
+ qualityFlags: current.qualityFlags,
510
+ artifactSignature: current.artifactSignature,
511
+ isDecompiled: current.isDecompiled,
512
+ timestamp
513
+ });
514
+ }
447
515
  async function rebuildMissingArtifactIndex(svc, resolved, reason) {
448
516
  svc.metrics.recordArtifactCacheMiss();
449
517
  svc.metrics.recordReindex();
@@ -1,6 +1,16 @@
1
1
  import type { SignatureMember } from "../../minecraft-explorer-service.js";
2
2
  import type { SourceService } from "../../source-service.js";
3
3
  import type { MappingSourcePriority, SourceMapping } from "../../types.js";
4
+ /**
5
+ * On unobfuscated versions (26.1+) the as-shipped ("obfuscated") names are the Mojang
6
+ * names, so obfuscated<->mojang is the identity. The mapping graph is empty there, so
7
+ * asking it would only report every member as unmapped.
8
+ *
9
+ * `minecraftVersion` must be a proven Minecraft version. A dependency
10
+ * artifact's version is its own release number (`annotations:26.0.2`), so callers
11
+ * serving one pass undefined and keep the mapping lookup it always had.
12
+ */
13
+ export declare function isUnobfuscatedIdentityPair(minecraftVersion: string | undefined, sourceMapping: SourceMapping, targetMapping: SourceMapping): boolean;
4
14
  export declare function normalizeMapping(mapping: SourceMapping | undefined): SourceMapping;
5
15
  export declare function rejectLifecycleClassLikeInput(svc: SourceService, input: {
6
16
  symbol: string;
@@ -16,7 +26,16 @@ export declare function resolveToObfuscatedMemberName(svc: SourceService, name:
16
26
  name: string;
17
27
  descriptor?: string;
18
28
  }>;
19
- export declare function remapSignatureMembers(svc: SourceService, members: SignatureMember[], kind: "field" | "method", version: string, sourceMapping: SourceMapping, targetMapping: SourceMapping, sourcePriority: MappingSourcePriority | undefined, warnings: string[], projectPath?: string, gradleUserHome?: string): Promise<{
29
+ export declare function remapSignatureMembers(svc: SourceService, members: SignatureMember[], kind: "field" | "method", version: string, sourceMapping: SourceMapping, targetMapping: SourceMapping, sourcePriority: MappingSourcePriority | undefined, warnings: string[], projectPath?: string, gradleUserHome?: string,
30
+ /**
31
+ * The Minecraft version the obfuscated<->mojang identity shortcut may trust.
32
+ * Defaults to `version`, which the lifecycle, validate-mixin and access-widener
33
+ * callers pass as a Minecraft version; get-class-members passes undefined for a
34
+ * dependency artifact, whose `version` is the library's own.
35
+ */
36
+ identity?: {
37
+ minecraftVersion: string | undefined;
38
+ }): Promise<{
20
39
  members: SignatureMember[];
21
40
  failedNames: Set<string>;
22
41
  }>;
@@ -1,6 +1,24 @@
1
1
  import { buildSuggestedCall } from "../../build-suggested-call.js";
2
2
  import { ERROR_CODES, createError } from "../../errors.js";
3
+ import { isUnobfuscatedVersion } from "../../version-service.js";
3
4
  import { rebuildJavaSignature, remapJvmDescriptor } from "../descriptor-utils.js";
5
+ /**
6
+ * On unobfuscated versions (26.1+) the as-shipped ("obfuscated") names are the Mojang
7
+ * names, so obfuscated<->mojang is the identity. The mapping graph is empty there, so
8
+ * asking it would only report every member as unmapped.
9
+ *
10
+ * `minecraftVersion` must be a proven Minecraft version. A dependency
11
+ * artifact's version is its own release number (`annotations:26.0.2`), so callers
12
+ * serving one pass undefined and keep the mapping lookup it always had.
13
+ */
14
+ export function isUnobfuscatedIdentityPair(minecraftVersion, sourceMapping, targetMapping) {
15
+ const pair = new Set([sourceMapping, targetMapping]);
16
+ return (pair.size === 2 &&
17
+ pair.has("obfuscated") &&
18
+ pair.has("mojang") &&
19
+ minecraftVersion !== undefined &&
20
+ isUnobfuscatedVersion(minecraftVersion));
21
+ }
4
22
  export function normalizeMapping(mapping) {
5
23
  if (mapping == null) {
6
24
  return "obfuscated";
@@ -87,7 +105,7 @@ export async function resolveToObfuscatedClassName(svc, className, version, mapp
87
105
  });
88
106
  }
89
107
  export async function resolveToObfuscatedMemberName(svc, name, ownerInSourceMapping, descriptor, kind, version, mapping, sourcePriority, warnings, gradleUserHome) {
90
- if (mapping === "obfuscated") {
108
+ if (mapping === "obfuscated" || isUnobfuscatedIdentityPair(version, mapping, "obfuscated")) {
91
109
  return {
92
110
  name,
93
111
  descriptor: kind === "method" ? descriptor : undefined
@@ -167,9 +185,17 @@ export async function resolveToObfuscatedMemberName(svc, name, ownerInSourceMapp
167
185
  descriptor: kind === "method" ? descriptor : undefined
168
186
  };
169
187
  }
170
- export async function remapSignatureMembers(svc, members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings, projectPath, gradleUserHome) {
188
+ export async function remapSignatureMembers(svc, members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings, projectPath, gradleUserHome,
189
+ /**
190
+ * The Minecraft version the obfuscated<->mojang identity shortcut may trust.
191
+ * Defaults to `version`, which the lifecycle, validate-mixin and access-widener
192
+ * callers pass as a Minecraft version; get-class-members passes undefined for a
193
+ * dependency artifact, whose `version` is the library's own.
194
+ */
195
+ identity = { minecraftVersion: version }) {
171
196
  const failedNames = new Set();
172
- if (sourceMapping === targetMapping) {
197
+ if (sourceMapping === targetMapping ||
198
+ isUnobfuscatedIdentityPair(identity.minecraftVersion, sourceMapping, targetMapping)) {
173
199
  return { members, failedNames };
174
200
  }
175
201
  const memberKeyToRemapped = new Map();
@@ -1,2 +1,27 @@
1
1
  import type { CheckSymbolExistsInput, CheckSymbolExistsOutput, SourceService } from "../../source-service.js";
2
+ /**
3
+ * What a runtime-bytecode existence check established.
4
+ *
5
+ * `verified` is true when the runtime jar answered: the class loaded and the member
6
+ * lookup ran to completion, or the class was confirmed missing (CLASS_NOT_FOUND). It
7
+ * is false when the check could not be performed - a short class name, a member query
8
+ * without an owner, or a class that failed to load for any other reason (unreadable
9
+ * jar, malformed class file) - and `result` is then the fallback base with the
10
+ * explanatory warning appended, so it still carries the caller's own status.
11
+ */
12
+ export interface UnobfuscatedRuntimeCheck {
13
+ verified: boolean;
14
+ result: CheckSymbolExistsOutput;
15
+ }
16
+ /**
17
+ * check-symbol-exists's runtime fallback: the result of `runUnobfuscatedRuntimeCheck`
18
+ * alone. Its callers keep the fallback base's status whenever the check could not be
19
+ * performed, so they need no `verified` flag.
20
+ */
2
21
  export declare function checkSymbolExistsInUnobfuscatedRuntime(svc: SourceService, input: CheckSymbolExistsInput, fallbackBase: CheckSymbolExistsOutput): Promise<CheckSymbolExistsOutput | undefined>;
22
+ /**
23
+ * Checks a symbol against the Minecraft runtime jar of an unobfuscated version, whose
24
+ * names are the Mojang names. Undefined when the version or name is empty or the
25
+ * runtime jar cannot be resolved.
26
+ */
27
+ export declare function runUnobfuscatedRuntimeCheck(svc: SourceService, input: CheckSymbolExistsInput, fallbackBase: CheckSymbolExistsOutput): Promise<UnobfuscatedRuntimeCheck | undefined>;