@adhisang/minecraft-modding-mcp 2.0.0 → 2.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 (37) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +109 -29
  3. package/dist/cli.js +31 -4
  4. package/dist/compat-stdio-transport.d.ts +2 -7
  5. package/dist/compat-stdio-transport.js +12 -154
  6. package/dist/index.js +392 -33
  7. package/dist/json-rpc-framing.d.ts +22 -0
  8. package/dist/json-rpc-framing.js +168 -0
  9. package/dist/mapping-pipeline-service.js +9 -1
  10. package/dist/mapping-service.d.ts +9 -0
  11. package/dist/mapping-service.js +183 -60
  12. package/dist/minecraft-explorer-service.d.ts +0 -1
  13. package/dist/minecraft-explorer-service.js +119 -23
  14. package/dist/mixin-validator.d.ts +24 -2
  15. package/dist/mixin-validator.js +223 -98
  16. package/dist/mod-decompile-service.d.ts +5 -0
  17. package/dist/mod-decompile-service.js +40 -5
  18. package/dist/mod-remap-service.js +142 -30
  19. package/dist/path-resolver.js +41 -4
  20. package/dist/registry-service.d.ts +10 -1
  21. package/dist/registry-service.js +154 -22
  22. package/dist/search-hit-accumulator.js +23 -2
  23. package/dist/source-jar-reader.js +16 -2
  24. package/dist/source-resolver.js +6 -7
  25. package/dist/source-service.d.ts +42 -4
  26. package/dist/source-service.js +781 -127
  27. package/dist/stdio-supervisor.d.ts +46 -0
  28. package/dist/stdio-supervisor.js +349 -0
  29. package/dist/storage/files-repo.d.ts +3 -9
  30. package/dist/storage/files-repo.js +66 -43
  31. package/dist/symbols/symbol-extractor.js +6 -4
  32. package/dist/tool-execution-gate.d.ts +15 -0
  33. package/dist/tool-execution-gate.js +58 -0
  34. package/dist/version-diff-service.js +10 -5
  35. package/dist/version-service.js +7 -2
  36. package/dist/workspace-mapping-service.js +12 -0
  37. package/package.json +1 -1
@@ -37,13 +37,21 @@ function addToSetMap(map, key, value) {
37
37
  map.set(normalizedKey, existing);
38
38
  }
39
39
  function normalizedVariants(symbol) {
40
- const variants = new Set();
41
- variants.add(symbol);
42
- const dotted = symbol.replace(/\//g, ".");
43
- variants.add(dotted);
44
- const slashed = symbol.replace(/\./g, "/");
45
- variants.add(slashed);
46
- return [...variants];
40
+ const variants = [symbol];
41
+ let dotted;
42
+ if (symbol.includes("/")) {
43
+ dotted = symbol.replace(/\//g, ".");
44
+ if (dotted !== symbol) {
45
+ variants.push(dotted);
46
+ }
47
+ }
48
+ if (symbol.includes(".")) {
49
+ const slashed = symbol.replace(/\./g, "/");
50
+ if (slashed !== symbol && slashed !== dotted) {
51
+ variants.push(slashed);
52
+ }
53
+ }
54
+ return variants;
47
55
  }
48
56
  function simpleName(symbol) {
49
57
  const trimmed = symbol.trim();
@@ -230,12 +238,42 @@ function pairKey(sourceMapping, targetMapping) {
230
238
  return `${sourceMapping}->${targetMapping}`;
231
239
  }
232
240
  function parsePairKey(key) {
233
- const [source, target] = key.split("->");
241
+ const separator = key.indexOf("->");
242
+ const source = separator >= 0 ? key.slice(0, separator) : key;
243
+ const target = separator >= 0 ? key.slice(separator + 2) : "";
234
244
  return {
235
245
  sourceMapping: source,
236
246
  targetMapping: target
237
247
  };
238
248
  }
249
+ function buildAdjacency(pairs) {
250
+ const adjacency = new Map();
251
+ for (const key of pairs.keys()) {
252
+ const { sourceMapping, targetMapping } = parsePairKey(key);
253
+ let neighbors = adjacency.get(sourceMapping);
254
+ if (!neighbors) {
255
+ neighbors = new Set();
256
+ adjacency.set(sourceMapping, neighbors);
257
+ }
258
+ neighbors.add(targetMapping);
259
+ }
260
+ return new Map([...adjacency.entries()].map(([mapping, neighbors]) => [mapping, [...neighbors]]));
261
+ }
262
+ function buildTargetRecordIndex(pairs) {
263
+ const recordsByTarget = new Map();
264
+ for (const [key, pair] of pairs.entries()) {
265
+ const { targetMapping } = parsePairKey(key);
266
+ let bucket = recordsByTarget.get(targetMapping);
267
+ if (!bucket) {
268
+ bucket = new Map();
269
+ recordsByTarget.set(targetMapping, bucket);
270
+ }
271
+ for (const record of pair.index.records.values()) {
272
+ bucket.set(buildSymbolKey(record), record);
273
+ }
274
+ }
275
+ return new Map([...recordsByTarget.entries()].map(([mapping, records]) => [mapping, [...records.values()]]));
276
+ }
239
277
  function ensurePairIndex(indexes, from, to) {
240
278
  const key = pairKey(from, to);
241
279
  const existing = indexes.get(key);
@@ -552,38 +590,48 @@ function mappingSourceOrder(priority) {
552
590
  }
553
591
  return ["loom-cache", "maven"];
554
592
  }
555
- function namespacePath(pairs, sourceMapping, targetMapping) {
593
+ function namespacePath(graph, sourceMapping, targetMapping) {
556
594
  if (sourceMapping === targetMapping) {
557
595
  return [sourceMapping];
558
596
  }
597
+ const key = pairKey(sourceMapping, targetMapping);
598
+ if (graph.pathCache.has(key)) {
599
+ return graph.pathCache.get(key);
600
+ }
601
+ const { adjacency } = graph;
559
602
  const queue = [sourceMapping];
603
+ let queueIndex = 0;
560
604
  const parent = new Map([[sourceMapping, undefined]]);
561
- while (queue.length > 0) {
562
- const current = queue.shift();
605
+ while (queueIndex < queue.length) {
606
+ const current = queue[queueIndex];
607
+ queueIndex += 1;
563
608
  if (current === targetMapping) {
564
609
  break;
565
610
  }
566
- for (const key of pairs.keys()) {
567
- const parsed = parsePairKey(key);
568
- if (parsed.sourceMapping !== current) {
569
- continue;
570
- }
571
- if (parent.has(parsed.targetMapping)) {
611
+ const neighbors = adjacency.get(current);
612
+ if (!neighbors) {
613
+ continue;
614
+ }
615
+ for (const neighbor of neighbors) {
616
+ if (parent.has(neighbor)) {
572
617
  continue;
573
618
  }
574
- parent.set(parsed.targetMapping, current);
575
- queue.push(parsed.targetMapping);
619
+ parent.set(neighbor, current);
620
+ queue.push(neighbor);
576
621
  }
577
622
  }
578
623
  if (!parent.has(targetMapping)) {
624
+ graph.pathCache.set(key, undefined);
579
625
  return undefined;
580
626
  }
581
- const path = [];
627
+ const reversedPath = [];
582
628
  let cursor = targetMapping;
583
629
  while (cursor) {
584
- path.unshift(cursor);
630
+ reversedPath.push(cursor);
585
631
  cursor = parent.get(cursor);
586
632
  }
633
+ const path = reversedPath.reverse();
634
+ graph.pathCache.set(key, path);
587
635
  return path;
588
636
  }
589
637
  function pathUsesSource(pairs, path, source) {
@@ -765,17 +813,7 @@ function applyDisambiguationHints(candidates, disambiguation) {
765
813
  return filtered;
766
814
  }
767
815
  function collectTargetRecords(graph, targetMapping) {
768
- const merged = new Map();
769
- for (const [key, pair] of graph.pairs.entries()) {
770
- const parsed = parsePairKey(key);
771
- if (parsed.targetMapping !== targetMapping) {
772
- continue;
773
- }
774
- for (const record of pair.index.records.values()) {
775
- merged.set(buildSymbolKey(record), record);
776
- }
777
- }
778
- return [...merged.values()];
816
+ return [...(graph.recordsByTarget.get(targetMapping) ?? [])];
779
817
  }
780
818
  function normalizeIncludedKinds(inputKinds) {
781
819
  const normalized = new Set();
@@ -823,6 +861,28 @@ function inferAmbiguityReasons(candidates, usedMojangClientMappings) {
823
861
  }
824
862
  return reasons;
825
863
  }
864
+ function clampCandidateLimit(limit) {
865
+ if (!Number.isFinite(limit) || limit == null) {
866
+ return MAX_CANDIDATES;
867
+ }
868
+ return Math.max(1, Math.min(MAX_CANDIDATES, Math.trunc(limit)));
869
+ }
870
+ function limitResolutionCandidates(candidates, requestedLimit) {
871
+ const candidateCount = candidates.length;
872
+ const limit = clampCandidateLimit(requestedLimit);
873
+ const limitedCandidates = candidateCount > limit ? candidates.slice(0, limit) : candidates;
874
+ return {
875
+ candidates: limitedCandidates,
876
+ candidateCount,
877
+ ...(limitedCandidates.length < candidateCount ? { candidatesTruncated: true } : {})
878
+ };
879
+ }
880
+ function clampRowLimit(limit) {
881
+ if (!Number.isFinite(limit) || limit == null) {
882
+ return undefined;
883
+ }
884
+ return Math.max(1, Math.min(5000, Math.trunc(limit)));
885
+ }
826
886
  export class MappingService {
827
887
  config;
828
888
  versionService;
@@ -845,7 +905,7 @@ export class MappingService {
845
905
  }
846
906
  });
847
907
  }
848
- const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input);
908
+ const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input, input.signatureMode);
849
909
  const sourceMapping = input.sourceMapping;
850
910
  const targetMapping = input.targetMapping;
851
911
  if (!SUPPORTED_MAPPINGS.has(sourceMapping) || !SUPPORTED_MAPPINGS.has(targetMapping)) {
@@ -872,18 +932,21 @@ export class MappingService {
872
932
  matchKind: "exact",
873
933
  confidence: 1
874
934
  });
935
+ const limited = limitResolutionCandidates([identity], input.maxCandidates);
875
936
  return {
876
937
  querySymbol,
877
938
  mappingContext,
878
939
  resolved: true,
879
940
  status: "resolved",
880
941
  resolvedSymbol: querySymbol,
881
- candidates: [identity],
942
+ candidates: limited.candidates,
943
+ candidateCount: limited.candidateCount,
944
+ candidatesTruncated: limited.candidatesTruncated,
882
945
  warnings: []
883
946
  };
884
947
  }
885
948
  const graph = await this.loadGraph(version, priority);
886
- const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
949
+ const path = namespacePath(graph, sourceMapping, targetMapping);
887
950
  if (!path) {
888
951
  return {
889
952
  querySymbol,
@@ -891,6 +954,7 @@ export class MappingService {
891
954
  resolved: false,
892
955
  status: "mapping_unavailable",
893
956
  candidates: [],
957
+ candidateCount: 0,
894
958
  warnings: [
895
959
  `No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
896
960
  ]
@@ -903,6 +967,7 @@ export class MappingService {
903
967
  warnings.push(`Disambiguation hints narrowed candidates from ${rawCandidates.length} to ${disambiguatedCandidates.length}.`);
904
968
  }
905
969
  const candidates = disambiguatedCandidates.map(toResolutionCandidate);
970
+ const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
906
971
  if (queryRecord.kind === "method" &&
907
972
  queryRecord.descriptor &&
908
973
  pathUsesSource(graph.pairs, path, "mojang-client-mappings") &&
@@ -925,7 +990,9 @@ export class MappingService {
925
990
  resolved: status === "resolved",
926
991
  status,
927
992
  resolvedSymbol: status === "resolved" ? candidates[0] : undefined,
928
- candidates,
993
+ candidates: limitedCandidates.candidates,
994
+ candidateCount: limitedCandidates.candidateCount,
995
+ candidatesTruncated: limitedCandidates.candidatesTruncated,
929
996
  warnings,
930
997
  provenance: this.provenanceForPath(graph, path),
931
998
  ambiguityReasons
@@ -963,7 +1030,7 @@ export class MappingService {
963
1030
  };
964
1031
  }
965
1032
  const graph = await this.loadGraph(version, priority);
966
- const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
1033
+ const path = namespacePath(graph, sourceMapping, targetMapping);
967
1034
  if (!path) {
968
1035
  throw createError({
969
1036
  code: ERROR_CODES.MAPPING_UNAVAILABLE,
@@ -1033,18 +1100,21 @@ export class MappingService {
1033
1100
  matchKind: "exact",
1034
1101
  confidence: 1
1035
1102
  });
1103
+ const limited = limitResolutionCandidates([resolvedCandidate], input.maxCandidates);
1036
1104
  return {
1037
1105
  querySymbol,
1038
1106
  mappingContext,
1039
1107
  resolved: true,
1040
1108
  status: "resolved",
1041
1109
  resolvedSymbol: resolvedCandidate,
1042
- candidates: [resolvedCandidate],
1110
+ candidates: limited.candidates,
1111
+ candidateCount: limited.candidateCount,
1112
+ candidatesTruncated: limited.candidatesTruncated,
1043
1113
  warnings: []
1044
1114
  };
1045
1115
  }
1046
1116
  const graph = await this.loadGraph(version, priority);
1047
- const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
1117
+ const path = namespacePath(graph, sourceMapping, targetMapping);
1048
1118
  if (!path) {
1049
1119
  return {
1050
1120
  querySymbol,
@@ -1052,6 +1122,7 @@ export class MappingService {
1052
1122
  resolved: false,
1053
1123
  status: "mapping_unavailable",
1054
1124
  candidates: [],
1125
+ candidateCount: 0,
1055
1126
  warnings: [
1056
1127
  `No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
1057
1128
  ]
@@ -1062,6 +1133,7 @@ export class MappingService {
1062
1133
  .mapCandidatesAlongPath(graph, path, queryRecord)
1063
1134
  .filter((candidate) => candidate.kind === "method");
1064
1135
  const candidates = rawCandidates.map(toResolutionCandidate);
1136
+ const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
1065
1137
  const strictCandidates = rawCandidates.filter((candidate) => candidate.descriptor === descriptor);
1066
1138
  if (strictCandidates.length === 1) {
1067
1139
  const resolved = toResolutionCandidate(strictCandidates[0]);
@@ -1071,7 +1143,9 @@ export class MappingService {
1071
1143
  resolved: true,
1072
1144
  status: "resolved",
1073
1145
  resolvedSymbol: resolved,
1074
- candidates,
1146
+ candidates: limitedCandidates.candidates,
1147
+ candidateCount: limitedCandidates.candidateCount,
1148
+ candidatesTruncated: limitedCandidates.candidatesTruncated,
1075
1149
  warnings,
1076
1150
  provenance: this.provenanceForPath(graph, path)
1077
1151
  };
@@ -1083,7 +1157,9 @@ export class MappingService {
1083
1157
  mappingContext,
1084
1158
  resolved: false,
1085
1159
  status: "ambiguous",
1086
- candidates,
1160
+ candidates: limitedCandidates.candidates,
1161
+ candidateCount: limitedCandidates.candidateCount,
1162
+ candidatesTruncated: limitedCandidates.candidatesTruncated,
1087
1163
  warnings,
1088
1164
  provenance: this.provenanceForPath(graph, path)
1089
1165
  };
@@ -1095,7 +1171,9 @@ export class MappingService {
1095
1171
  mappingContext,
1096
1172
  resolved: false,
1097
1173
  status: "mapping_unavailable",
1098
- candidates,
1174
+ candidates: limitedCandidates.candidates,
1175
+ candidateCount: limitedCandidates.candidateCount,
1176
+ candidatesTruncated: limitedCandidates.candidatesTruncated,
1099
1177
  warnings,
1100
1178
  provenance: this.provenanceForPath(graph, path)
1101
1179
  };
@@ -1105,7 +1183,9 @@ export class MappingService {
1105
1183
  mappingContext,
1106
1184
  resolved: false,
1107
1185
  status: "not_found",
1108
- candidates,
1186
+ candidates: limitedCandidates.candidates,
1187
+ candidateCount: limitedCandidates.candidateCount,
1188
+ candidatesTruncated: limitedCandidates.candidatesTruncated,
1109
1189
  warnings,
1110
1190
  provenance: this.provenanceForPath(graph, path)
1111
1191
  };
@@ -1137,6 +1217,19 @@ export class MappingService {
1137
1217
  const graph = await this.loadGraph(version, priority);
1138
1218
  const warnings = [...graph.warnings];
1139
1219
  const includeKinds = normalizeIncludedKinds(input.includeKinds);
1220
+ const pathCache = new Map();
1221
+ const resolvePath = (sourceMapping, targetMapping) => {
1222
+ if (sourceMapping === targetMapping) {
1223
+ return [sourceMapping];
1224
+ }
1225
+ const key = pairKey(sourceMapping, targetMapping);
1226
+ if (pathCache.has(key)) {
1227
+ return pathCache.get(key);
1228
+ }
1229
+ const path = namespacePath(graph, sourceMapping, targetMapping);
1230
+ pathCache.set(key, path);
1231
+ return path;
1232
+ };
1140
1233
  const classByMapping = {
1141
1234
  [classNameMapping]: createClassSymbolRecord(className)
1142
1235
  };
@@ -1144,7 +1237,7 @@ export class MappingService {
1144
1237
  if (mapping === classNameMapping) {
1145
1238
  continue;
1146
1239
  }
1147
- const mapped = this.mapRecordBetweenMappings(graph, classNameMapping, mapping, classByMapping[classNameMapping]);
1240
+ const mapped = this.mapRecordBetweenMappings(graph, classNameMapping, mapping, classByMapping[classNameMapping], resolvePath(classNameMapping, mapping));
1148
1241
  if (mapped.length > 1) {
1149
1242
  const competing = mapped.slice(0, 5).map((c) => c.symbol);
1150
1243
  warnings.push(`Class identity mapping to ${mapping} is ambiguous for "${className}": competing=[${competing.join(", ")}].`);
@@ -1167,6 +1260,7 @@ export class MappingService {
1167
1260
  yarn: classByMapping.yarn?.symbol
1168
1261
  },
1169
1262
  rows: [],
1263
+ rowCount: 0,
1170
1264
  warnings
1171
1265
  };
1172
1266
  }
@@ -1220,7 +1314,7 @@ export class MappingService {
1220
1314
  resolved = baseRecord;
1221
1315
  }
1222
1316
  else {
1223
- const mapped = this.mapRecordBetweenMappings(graph, baseMapping, mapping, baseRecord);
1317
+ const mapped = this.mapRecordBetweenMappings(graph, baseMapping, mapping, baseRecord, resolvePath(baseMapping, mapping));
1224
1318
  let filtered = mapped;
1225
1319
  if (baseRecord.kind !== "class" && classIdentity) {
1226
1320
  filtered = filtered.filter((candidate) => candidate.owner === classIdentity.symbol);
@@ -1255,6 +1349,9 @@ export class MappingService {
1255
1349
  ambiguousRowCount += 1;
1256
1350
  }
1257
1351
  }
1352
+ const rowCount = rows.length;
1353
+ const rowLimit = clampRowLimit(input.maxRows);
1354
+ const limitedRows = rowLimit != null && rowCount > rowLimit ? rows.slice(0, rowLimit) : rows;
1258
1355
  return {
1259
1356
  version,
1260
1357
  className,
@@ -1265,7 +1362,9 @@ export class MappingService {
1265
1362
  intermediary: classByMapping.intermediary?.symbol,
1266
1363
  yarn: classByMapping.yarn?.symbol
1267
1364
  },
1268
- rows,
1365
+ rows: limitedRows,
1366
+ rowCount,
1367
+ rowsTruncated: limitedRows.length < rowCount ? true : undefined,
1269
1368
  warnings,
1270
1369
  ambiguousRowCount: ambiguousRowCount > 0 ? ambiguousRowCount : undefined
1271
1370
  };
@@ -1346,18 +1445,22 @@ export class MappingService {
1346
1445
  resolved: false,
1347
1446
  status: "mapping_unavailable",
1348
1447
  candidates: [],
1448
+ candidateCount: 0,
1349
1449
  warnings
1350
1450
  };
1351
1451
  }
1352
1452
  const buildOutput = (querySymbol, matched, status) => {
1353
1453
  const candidates = matched.map((record) => toResolutionCandidate(toLookupCandidate(record)));
1454
+ const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
1354
1455
  return {
1355
1456
  querySymbol,
1356
1457
  mappingContext,
1357
1458
  resolved: status === "resolved",
1358
1459
  status,
1359
1460
  resolvedSymbol: status === "resolved" ? candidates[0] : undefined,
1360
- candidates,
1461
+ candidates: limitedCandidates.candidates,
1462
+ candidateCount: limitedCandidates.candidateCount,
1463
+ candidatesTruncated: limitedCandidates.candidatesTruncated,
1361
1464
  warnings
1362
1465
  };
1363
1466
  };
@@ -1408,11 +1511,11 @@ export class MappingService {
1408
1511
  }
1409
1512
  return buildOutput(querySymbol, [], "not_found");
1410
1513
  }
1411
- mapRecordBetweenMappings(graph, sourceMapping, targetMapping, record) {
1514
+ mapRecordBetweenMappings(graph, sourceMapping, targetMapping, record, resolvedPath) {
1412
1515
  if (sourceMapping === targetMapping) {
1413
1516
  return [record];
1414
1517
  }
1415
- const path = namespacePath(graph.pairs, sourceMapping, targetMapping);
1518
+ const path = resolvedPath ?? namespacePath(graph, sourceMapping, targetMapping);
1416
1519
  if (!path) {
1417
1520
  return [];
1418
1521
  }
@@ -1568,7 +1671,7 @@ export class MappingService {
1568
1671
  memberRemapAvailable = true;
1569
1672
  }
1570
1673
  else {
1571
- const path = namespacePath(graph.pairs, input.requestedMapping, "obfuscated");
1674
+ const path = namespacePath(graph, input.requestedMapping, "obfuscated");
1572
1675
  memberRemapAvailable = path != null && path.length > 1;
1573
1676
  if (!memberRemapAvailable) {
1574
1677
  degradations.push(`No mapping path from ${input.requestedMapping} to obfuscated; member remap will fail.`);
@@ -1611,6 +1714,9 @@ export class MappingService {
1611
1714
  version,
1612
1715
  priority,
1613
1716
  pairs: new Map(),
1717
+ adjacency: new Map(),
1718
+ pathCache: new Map(),
1719
+ recordsByTarget: new Map(),
1614
1720
  warnings: [
1615
1721
  `Version ${version} is unobfuscated; mapping graph is empty because the runtime already uses deobfuscated names.`
1616
1722
  ]
@@ -1620,27 +1726,38 @@ export class MappingService {
1620
1726
  version,
1621
1727
  priority,
1622
1728
  pairs: new Map(),
1729
+ adjacency: new Map(),
1730
+ pathCache: new Map(),
1731
+ recordsByTarget: new Map(),
1623
1732
  warnings: []
1624
1733
  };
1625
1734
  const mojangLoad = await this.loadMojangPairs(version);
1626
1735
  graph.warnings.push(...mojangLoad.warnings);
1627
1736
  this.mergePairs(graph.pairs, mojangLoad.pairs, "mojang-client-mappings", mojangLoad.mappingArtifact);
1628
1737
  let tinyLoaded = false;
1738
+ const deferredTinyWarnings = [];
1629
1739
  for (const source of mappingSourceOrder(priority)) {
1630
1740
  const tinyLoad = source === "loom-cache"
1631
1741
  ? await this.loadTinyPairsFromLoom(version)
1632
1742
  : await this.loadTinyPairsFromMaven(version);
1633
- graph.warnings.push(...tinyLoad.warnings);
1634
1743
  if (tinyLoad.pairs.size === 0) {
1744
+ deferredTinyWarnings.push(...tinyLoad.warnings);
1635
1745
  continue;
1636
1746
  }
1637
1747
  tinyLoaded = true;
1638
1748
  this.mergePairs(graph.pairs, tinyLoad.pairs, source, tinyLoad.mappingArtifact);
1749
+ graph.warnings.push(...tinyLoad.warnings);
1750
+ if (deferredTinyWarnings.length > 0) {
1751
+ graph.warnings.push(`Used ${source === "maven" ? "Maven" : "Loom cache"} tiny mappings for "${version}" after an earlier source lookup returned no data.`);
1752
+ }
1639
1753
  break;
1640
1754
  }
1641
1755
  if (!tinyLoaded) {
1756
+ graph.warnings.push(...deferredTinyWarnings);
1642
1757
  graph.warnings.push("No intermediary/yarn tiny mappings were found for this version.");
1643
1758
  }
1759
+ graph.adjacency = buildAdjacency(graph.pairs);
1760
+ graph.recordsByTarget = buildTargetRecordIndex(graph.pairs);
1644
1761
  return graph;
1645
1762
  }
1646
1763
  mergePairs(target, source, pairSource, mappingArtifact) {
@@ -1763,31 +1880,37 @@ export class MappingService {
1763
1880
  async loadTinyPairsFromMaven(version) {
1764
1881
  const warnings = [];
1765
1882
  const merged = new Map();
1766
- const attemptedArtifacts = [];
1767
1883
  const repos = this.config.sourceRepos;
1768
1884
  const intermediaryUrls = [];
1769
1885
  const yarnUrls = [];
1770
- for (const repo of repos) {
1771
- const base = repo.replace(/\/+$/, "");
1886
+ const repoBases = repos.map((repo) => repo.replace(/\/+$/, ""));
1887
+ const yarnCoordinatesByRepo = await Promise.all(repoBases.map(async (base) => ({
1888
+ base,
1889
+ yarnCoordinates: await this.fetchYarnCoordinates(base, version)
1890
+ })));
1891
+ for (const { base, yarnCoordinates } of yarnCoordinatesByRepo) {
1772
1892
  intermediaryUrls.push(`${base}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`, `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}.jar`);
1773
- const yarnCoordinates = await this.fetchYarnCoordinates(base, version);
1774
1893
  for (const coordinate of yarnCoordinates) {
1775
1894
  yarnUrls.push(`${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`, `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}.jar`);
1776
1895
  }
1777
1896
  }
1778
1897
  const allUrls = [...intermediaryUrls, ...yarnUrls];
1779
- for (const url of allUrls) {
1780
- attemptedArtifacts.push(url);
1898
+ const parsedResults = await Promise.allSettled(allUrls.map(async (url) => {
1781
1899
  const downloaded = await downloadToCache(url, defaultDownloadPath(this.config.cacheDir, url), {
1782
1900
  fetchFn: this.fetchFn,
1783
1901
  retries: this.config.fetchRetries,
1784
1902
  timeoutMs: this.config.fetchTimeoutMs
1785
1903
  });
1786
1904
  if (!downloaded.ok || !downloaded.path) {
1905
+ return undefined;
1906
+ }
1907
+ return this.parseTinyFromJar(downloaded.path);
1908
+ }));
1909
+ for (const result of parsedResults) {
1910
+ if (result.status !== "fulfilled" || !result.value) {
1787
1911
  continue;
1788
1912
  }
1789
- const parsed = await this.parseTinyFromJar(downloaded.path);
1790
- for (const [key, index] of parsed.entries()) {
1913
+ for (const [key, index] of result.value.entries()) {
1791
1914
  const existing = merged.get(key);
1792
1915
  if (!existing) {
1793
1916
  merged.set(key, index);
@@ -1803,7 +1926,7 @@ export class MappingService {
1803
1926
  return {
1804
1927
  pairs: merged,
1805
1928
  warnings,
1806
- mappingArtifact: attemptedArtifacts[0] ?? "maven:none"
1929
+ mappingArtifact: allUrls[0] ?? "maven:none"
1807
1930
  };
1808
1931
  }
1809
1932
  async parseTinyFromJar(jarPath) {
@@ -38,6 +38,5 @@ export declare class MinecraftExplorerService {
38
38
  getSignature(input: GetSignatureInput): Promise<GetSignatureOutput>;
39
39
  private contextForJar;
40
40
  private normalizeJarPathOrThrow;
41
- private trimSignatureCache;
42
41
  }
43
42
  export {};