@adhisang/minecraft-modding-mcp 5.0.0 → 6.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 (83) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/access-widener-parser.d.ts +1 -0
  3. package/dist/access-widener-parser.js +6 -3
  4. package/dist/cache-registry.js +35 -31
  5. package/dist/config.js +1 -1
  6. package/dist/decompiler/vineflower.js +8 -2
  7. package/dist/entry-tools/analyze-mod-service.js +2 -1
  8. package/dist/entry-tools/analyze-symbol-service.js +18 -9
  9. package/dist/entry-tools/batch-class-members-service.d.ts +2 -0
  10. package/dist/entry-tools/batch-class-members-service.js +1 -0
  11. package/dist/entry-tools/compare-minecraft-service.js +1 -1
  12. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -1
  13. package/dist/entry-tools/inspect-minecraft/handlers/file.js +26 -1
  14. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +2 -1
  15. package/dist/entry-tools/inspect-minecraft/internal.js +2 -0
  16. package/dist/entry-tools/response-contract.d.ts +2 -0
  17. package/dist/entry-tools/response-contract.js +2 -2
  18. package/dist/entry-tools/validate-project/cases/project-summary.js +30 -12
  19. package/dist/entry-tools/validate-project-service.d.ts +4 -4
  20. package/dist/entry-tools/verify-mixin-target-service.js +7 -7
  21. package/dist/index.js +21 -16
  22. package/dist/java-process.js +1 -1
  23. package/dist/json-rpc-framing.d.ts +4 -0
  24. package/dist/json-rpc-framing.js +23 -1
  25. package/dist/mapping/internal-types.d.ts +17 -0
  26. package/dist/mapping/loaders/tiny-maven.js +46 -20
  27. package/dist/mapping/lookup.d.ts +12 -1
  28. package/dist/mapping/lookup.js +53 -1
  29. package/dist/mapping-service.js +37 -22
  30. package/dist/minecraft-explorer-service.js +2 -1
  31. package/dist/mixin/annotation-validators.js +21 -3
  32. package/dist/mixin/parsed-validator.js +1 -0
  33. package/dist/mixin-parser.js +71 -3
  34. package/dist/mod-decompile-service.d.ts +2 -0
  35. package/dist/mod-decompile-service.js +19 -2
  36. package/dist/mod-remap-service.js +6 -5
  37. package/dist/mojang-tiny-mapping-service.js +5 -2
  38. package/dist/nbt/java-nbt-codec.js +40 -16
  39. package/dist/nbt/json-patch.js +51 -12
  40. package/dist/nbt/typed-json.d.ts +1 -0
  41. package/dist/nbt/typed-json.js +12 -2
  42. package/dist/nbt/types.d.ts +2 -2
  43. package/dist/path-converter.js +10 -3
  44. package/dist/registry-service.d.ts +2 -0
  45. package/dist/registry-service.js +16 -1
  46. package/dist/repo-downloader.js +4 -3
  47. package/dist/source/artifact-resolver.js +1 -1
  48. package/dist/source/class-source/members-builder.d.ts +26 -0
  49. package/dist/source/class-source/members-builder.js +31 -4
  50. package/dist/source/class-source/snippet-builder.js +6 -0
  51. package/dist/source/class-source-helpers.d.ts +4 -4
  52. package/dist/source/class-source-helpers.js +12 -11
  53. package/dist/source/class-source.d.ts +19 -0
  54. package/dist/source/class-source.js +87 -26
  55. package/dist/source/file-access.js +3 -2
  56. package/dist/source/indexer.js +24 -7
  57. package/dist/source/lifecycle/mapping-helpers.js +28 -3
  58. package/dist/source/lifecycle/runtime-check.js +20 -6
  59. package/dist/source/member-pattern.d.ts +16 -0
  60. package/dist/source/member-pattern.js +27 -0
  61. package/dist/source/search.js +23 -8
  62. package/dist/source/validate-mixin/pipeline/resolve.js +23 -3
  63. package/dist/source/workspace-target.js +2 -1
  64. package/dist/source-resolver.js +2 -2
  65. package/dist/source-service.d.ts +8 -4
  66. package/dist/source-service.js +9 -1
  67. package/dist/stdio-supervisor.d.ts +2 -2
  68. package/dist/stdio-supervisor.js +29 -9
  69. package/dist/storage/db.js +24 -1
  70. package/dist/storage/files-repo.d.ts +3 -0
  71. package/dist/storage/files-repo.js +43 -37
  72. package/dist/storage/symbols-repo.d.ts +9 -0
  73. package/dist/storage/symbols-repo.js +47 -19
  74. package/dist/symbols/symbol-extractor.js +1 -1
  75. package/dist/tool-guidance.d.ts +2 -1
  76. package/dist/tool-guidance.js +27 -2
  77. package/dist/tool-schemas.d.ts +15 -4
  78. package/dist/tool-schemas.js +102 -97
  79. package/dist/warning-details.d.ts +12 -0
  80. package/dist/warning-details.js +17 -0
  81. package/dist/workspace-mapping-service.js +9 -0
  82. package/docs/tool-reference.md +23 -0
  83. package/package.json +1 -1
@@ -454,7 +454,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
454
454
  reportMode: "full" | "summary-first" | "compact";
455
455
  treatInfoAsWarning: boolean;
456
456
  includeIssues: boolean;
457
- task: "project-summary" | "mixin" | "access-widener" | "access-transformer";
457
+ task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
458
458
  subject: {
459
459
  kind: "workspace";
460
460
  projectPath: string;
@@ -510,7 +510,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
510
510
  include?: string[] | undefined;
511
511
  atNamespace?: "obfuscated" | "mojang" | "srg" | undefined;
512
512
  }, {
513
- task: "project-summary" | "mixin" | "access-widener" | "access-transformer";
513
+ task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
514
514
  subject: {
515
515
  kind: "workspace";
516
516
  projectPath: string;
@@ -580,7 +580,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
580
580
  reportMode: "full" | "summary-first" | "compact";
581
581
  treatInfoAsWarning: boolean;
582
582
  includeIssues: boolean;
583
- task: "project-summary" | "mixin" | "access-widener" | "access-transformer";
583
+ task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
584
584
  subject: {
585
585
  kind: "workspace";
586
586
  projectPath: string;
@@ -636,7 +636,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
636
636
  include?: string[] | undefined;
637
637
  atNamespace?: "obfuscated" | "mojang" | "srg" | undefined;
638
638
  }, {
639
- task: "project-summary" | "mixin" | "access-widener" | "access-transformer";
639
+ task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
640
640
  subject: {
641
641
  kind: "workspace";
642
642
  projectPath: string;
@@ -5,7 +5,7 @@ import { suggestSimilar } from "../mixin-validator.js";
5
5
  export const VERIFY_MIXIN_TARGET_OFF = process.env.VERIFY_MIXIN_TARGET_OFF === "1";
6
6
  const ACCESSOR_NAME_RE = /^(get|set|is)([A-Z]\w*)$/;
7
7
  const INVOKER_NAME_RE = /^(invoke|call)([A-Z]\w*)$/;
8
- function decodeAccessFlags(flags) {
8
+ function decodeAccessFlags(flags, kind) {
9
9
  const labels = [];
10
10
  if (flags == null) {
11
11
  return labels;
@@ -23,9 +23,9 @@ function decodeAccessFlags(flags) {
23
23
  if ((flags & 0x0020) !== 0)
24
24
  labels.push("synchronized");
25
25
  if ((flags & 0x0040) !== 0)
26
- labels.push("volatile");
26
+ labels.push(kind === "method" ? "bridge" : "volatile");
27
27
  if ((flags & 0x0080) !== 0)
28
- labels.push("transient");
28
+ labels.push(kind === "method" ? "varargs" : "transient");
29
29
  if ((flags & 0x0100) !== 0)
30
30
  labels.push("native");
31
31
  if ((flags & 0x0400) !== 0)
@@ -137,11 +137,11 @@ function deriveVisibility(accessFlags) {
137
137
  return "private";
138
138
  return "package-private";
139
139
  }
140
- function buildMatch(member) {
140
+ function buildMatch(member, kind) {
141
141
  return {
142
142
  name: member.name,
143
143
  descriptor: member.jvmDescriptor,
144
- accessFlags: decodeAccessFlags(member.accessFlags),
144
+ accessFlags: decodeAccessFlags(member.accessFlags, kind),
145
145
  javaSignature: member.javaSignature,
146
146
  ...(member.sourceLine ? { sourceLine: member.sourceLine } : {})
147
147
  };
@@ -267,7 +267,7 @@ export class VerifyMixinTargetService {
267
267
  if (member.descriptor) {
268
268
  const descriptorHits = exactNameHits.filter((m) => m.jvmDescriptor === member.descriptor);
269
269
  if (descriptorHits.length > 0) {
270
- matches = descriptorHits.map(buildMatch);
270
+ matches = descriptorHits.map((m) => buildMatch(m, member.kind));
271
271
  }
272
272
  else {
273
273
  matches = [];
@@ -279,7 +279,7 @@ export class VerifyMixinTargetService {
279
279
  }
280
280
  }
281
281
  else {
282
- matches = exactNameHits.map(buildMatch);
282
+ matches = exactNameHits.map((m) => buildMatch(m, member.kind));
283
283
  }
284
284
  if (matches.length === 0 && candidates.length === 0) {
285
285
  const suggestions = suggestSimilar(memberName, allMemberNames);
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { remapModJar } from "./mod-remap-service.js";
15
15
  import { registerResources } from "./resources.js";
16
16
  import { SourceService } from "./source-service.js";
17
17
  import { ToolExecutionGate } from "./tool-execution-gate.js";
18
- import { classifyWarnings } from "./warning-details.js";
18
+ import { capWarningDetailsForSummary, classifyWarnings } from "./warning-details.js";
19
19
  import { WorkspaceMappingService } from "./workspace-mapping-service.js";
20
20
  import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape } from "./entry-tools/inspect-minecraft-service.js";
21
21
  import { AnalyzeSymbolService, analyzeSymbolSchema, analyzeSymbolShape } from "./entry-tools/analyze-symbol-service.js";
@@ -90,7 +90,7 @@ server.validateToolInput = async (_tool, args) => args;
90
90
  // server.tool() but appends a note steering agents to the entry tools first.
91
91
  // Entry tools, batch tools, and the NBT/runtime utilities (which have no entry
92
92
  // equivalent) keep their plain descriptions via server.tool().
93
- const EXPERT_TOOL_NOTE = " Expert/follow-up tool: prefer the entry tools (inspect-minecraft, analyze-symbol, compare-minecraft, analyze-mod, validate-project) first; reach for this only for the narrow operation it names.";
93
+ const EXPERT_TOOL_NOTE = " Expert tool: prefer the entry tools (inspect-minecraft, analyze-symbol, compare-minecraft, analyze-mod, validate-project) first.";
94
94
  const expertTool = ((name, description, ...rest) => server.tool(name, description + EXPERT_TOOL_NOTE, ...rest));
95
95
  const config = loadConfig();
96
96
  const nbtLimits = {
@@ -385,18 +385,23 @@ async function runTool(tool, rawInput, schema, action) {
385
385
  if (DETAIL_ENABLED_TOOL_NAMES.has(tool)) {
386
386
  projectedResult = projectByDetail(tool, projectedResult, effectiveDetail, shapeInput.include);
387
387
  }
388
- // Entry, expert, and batch tools all report the applied detail/include shape in meta.
388
+ // Entry, expert, and batch tools report the applied detail/include shape in meta.
389
+ // detailApplied is omitted when it matches the tool's default to keep responses lean.
390
+ const defaultDetail = ENTRY_TOOL_NAMES.has(tool)
391
+ ? "summary"
392
+ : DEFAULT_DETAIL_BY_TOOL[tool] ?? "summary";
389
393
  const entryMeta = ENTRY_TOOL_NAMES.has(tool) ||
390
394
  DETAIL_ENABLED_TOOL_NAMES.has(tool) ||
391
395
  BATCH_DETAIL_TOOL_NAMES.has(tool)
392
396
  ? buildEntryToolMeta({
393
397
  detail: effectiveDetail,
398
+ defaultDetail,
394
399
  include: shapeInput.include.size > 0 ? [...shapeInput.include] : undefined
395
400
  })
396
401
  : undefined;
397
402
  const durationMs = Date.now() - startedAt;
398
403
  sourceService.recordToolCall(tool, durationMs);
399
- const warningDetails = classifyWarnings(warnings);
404
+ const warningDetails = capWarningDetailsForSummary(classifyWarnings(warnings), effectiveDetail === "summary");
400
405
  return objectResult({
401
406
  result: projectedResult,
402
407
  meta: {
@@ -405,7 +410,7 @@ async function runTool(tool, rawInput, schema, action) {
405
410
  requestId,
406
411
  tool,
407
412
  durationMs,
408
- warnings,
413
+ ...(warnings.length > 0 ? { warnings } : {}),
409
414
  ...(warningDetails.length > 0 ? { warningDetails } : {})
410
415
  }
411
416
  });
@@ -450,8 +455,7 @@ async function runTool(tool, rawInput, schema, action) {
450
455
  const errorMeta = {
451
456
  requestId,
452
457
  tool,
453
- durationMs: errorDurationMs,
454
- warnings: []
458
+ durationMs: errorDurationMs
455
459
  };
456
460
  applyErrorMetaExtensions(errorMeta, caughtError);
457
461
  return objectResult({
@@ -467,7 +471,7 @@ expertTool("list-versions", "List available Minecraft versions from Mojang manif
467
471
  registerToolSchema("list-versions", listVersionsSchema);
468
472
  server.tool("inspect-minecraft", "Top-level workflow tool for version discovery, artifact resolution, class inspection, source search, file reads, and file listings.", inspectMinecraftShape, { readOnlyHint: true }, async (args) => runTool("inspect-minecraft", args, inspectMinecraftSchema, async (input) => inspectMinecraftService.execute(input)));
469
473
  registerToolSchema("inspect-minecraft", inspectMinecraftSchema);
470
- server.tool("analyze-symbol", "Top-level workflow tool for symbol existence, mapping, lifecycle (with fromVersion/toVersion/maxVersions/includeTimeline range controls), workspace analysis, and API overview. subject.kind accepts 'symbol' to auto-detect class/field/method from the selector (inferred kind is returned as a warning).", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
474
+ server.tool("analyze-symbol", "Top-level workflow tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview. subject.kind='symbol' auto-detects class/field/method from the selector.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
471
475
  registerToolSchema("analyze-symbol", analyzeSymbolSchema);
472
476
  server.tool("compare-minecraft", "Top-level workflow tool for version comparisons, class diffs, registry diffs, and migration overviews.", compareMinecraftShape, { readOnlyHint: true }, async (args) => runTool("compare-minecraft", args, compareMinecraftSchema, async (input) => compareMinecraftService.execute(input)));
473
477
  registerToolSchema("compare-minecraft", compareMinecraftSchema);
@@ -497,16 +501,16 @@ if (!VERIFY_MIXIN_TARGET_OFF) {
497
501
  registerToolSchema("verify-mixin-target", verifyMixinTargetSchema);
498
502
  }
499
503
  if (!BATCH_TOOLS_OFF) {
500
- server.tool("batch-class-source", "Batch lookup: read source for many classes in one call, sharing a single resolved artifact. Returns per-entry { status, result?, error? } plus aggregate summary. Per-entry retry suggestions point at get-class-source. Not read-only: per-entry outputFile writes source files to disk.", batchClassSourceShape, { readOnlyHint: false }, async (args) => runTool("batch-class-source", args, batchClassSourceSchema, async (input) => batchClassSourceService.execute(input)));
504
+ server.tool("batch-class-source", "Batch get-class-source for many classes sharing one resolved artifact; returns per-entry status plus aggregate summary. Not read-only: per-entry outputFile writes to disk.", batchClassSourceShape, { readOnlyHint: false }, async (args) => runTool("batch-class-source", args, batchClassSourceSchema, async (input) => batchClassSourceService.execute(input)));
501
505
  registerToolSchema("batch-class-source", batchClassSourceSchema);
502
- server.tool("batch-class-members", "Batch lookup: list members for many classes in one call, sharing a single resolved artifact. Returns per-entry { status, result?, error? } plus aggregate summary. Per-entry retry suggestions point at get-class-members.", batchClassMembersShape, { readOnlyHint: true }, async (args) => runTool("batch-class-members", args, batchClassMembersSchema, async (input) => batchClassMembersService.execute(input)));
506
+ server.tool("batch-class-members", "Batch get-class-members for many classes sharing one resolved artifact; returns per-entry status plus aggregate summary.", batchClassMembersShape, { readOnlyHint: true }, async (args) => runTool("batch-class-members", args, batchClassMembersSchema, async (input) => batchClassMembersService.execute(input)));
503
507
  registerToolSchema("batch-class-members", batchClassMembersSchema);
504
- server.tool("batch-symbol-exists", "Batch existence/mapping query: probe many symbols in one call against a shared Minecraft-version artifact. Accepts target.kind=workspace or version only (other kinds carry library versions, not Minecraft versions). Per-entry retry suggestions point at check-symbol-exists.", batchSymbolExistsShape, { readOnlyHint: true }, async (args) => runTool("batch-symbol-exists", args, batchSymbolExistsSchema, async (input) => batchSymbolExistsService.execute(input)));
508
+ server.tool("batch-symbol-exists", "Batch check-symbol-exists for many symbols against one shared Minecraft version (target.kind=version or workspace only).", batchSymbolExistsShape, { readOnlyHint: true }, async (args) => runTool("batch-symbol-exists", args, batchSymbolExistsSchema, async (input) => batchSymbolExistsService.execute(input)));
505
509
  registerToolSchema("batch-symbol-exists", batchSymbolExistsSchema);
506
- server.tool("batch-mappings", "Batch mapping translation: resolve many symbols across mapping namespaces with one shared Minecraft version. Per-entry retry suggestions point at find-mapping.", batchMappingsShape, { readOnlyHint: true }, async (args) => runTool("batch-mappings", args, batchMappingsSchema, async (input) => batchMappingsService.execute(input)));
510
+ server.tool("batch-mappings", "Batch find-mapping: resolve many symbols across mapping namespaces with one shared Minecraft version.", batchMappingsShape, { readOnlyHint: true }, async (args) => runTool("batch-mappings", args, batchMappingsSchema, async (input) => batchMappingsService.execute(input)));
507
511
  registerToolSchema("batch-mappings", batchMappingsSchema);
508
512
  }
509
- expertTool("resolve-artifact", "Resolve source artifact from a target object ({ kind, value }) and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted; other adjacent *-sources.jar files are informational.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
513
+ expertTool("resolve-artifact", "Resolve a source artifact and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
510
514
  target: input.target,
511
515
  mapping: input.mapping,
512
516
  sourcePriority: input.sourcePriority,
@@ -532,7 +536,7 @@ expertTool("find-class", "Resolve a simple or qualified class name to fully-qual
532
536
  limit: input.limit
533
537
  })));
534
538
  registerToolSchema("find-class", findClassSchema);
535
- expertTool("get-class-source", "Get Java source for a class by target ({ kind: 'artifact', artifactId } or { kind: 'version'|'jar'|'coordinate'|'workspace'|'dependency', ... } — same shape as resolve-artifact). To read source text, pass mode=snippet (bounded excerpt) or mode=full (entire source); the default mode=metadata returns a symbol outline only, not the body. Not read-only: outputFile writes the source to disk.", getClassSourceShape, { readOnlyHint: false }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => {
539
+ expertTool("get-class-source", "Get Java source for a class. Default mode=metadata returns a symbol outline only; pass mode=snippet or mode=full to read source text. Not read-only: outputFile writes to disk.", getClassSourceShape, { readOnlyHint: false }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => {
536
540
  const normalizedTarget = normalizeSourceLookupTarget(input.target);
537
541
  return sourceService.getClassSource({
538
542
  className: input.className,
@@ -555,7 +559,7 @@ expertTool("get-class-source", "Get Java source for a class by target ({ kind: '
555
559
  });
556
560
  }));
557
561
  registerToolSchema("get-class-source", getClassSourceSchema);
558
- expertTool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode by target ({ kind: 'artifact', artifactId } or { kind: 'version'|'jar'|'coordinate'|'workspace'|'dependency', ... } — same shape as resolve-artifact).", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => {
562
+ expertTool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode.", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => {
559
563
  const normalizedTarget = normalizeSourceLookupTarget(input.target);
560
564
  return sourceService.getClassMembers({
561
565
  className: input.className,
@@ -569,6 +573,7 @@ expertTool("get-class-members", "Get fields/methods/constructors for one class f
569
573
  includeInherited: input.includeInherited,
570
574
  memberPattern: input.memberPattern,
571
575
  maxMembers: input.maxMembers,
576
+ projection: input.projection,
572
577
  cursor: input.cursor,
573
578
  projectPath: input.projectPath,
574
579
  gradleUserHome: input.gradleUserHome,
@@ -657,7 +662,7 @@ expertTool("find-mapping", "Find symbol mapping candidates between namespaces us
657
662
  maxCandidates: input.maxCandidates
658
663
  })));
659
664
  registerToolSchema("find-mapping", findMappingSchema);
660
- expertTool("resolve-method-mapping-exact", "Strict shortcut for find-mapping(kind=method, signatureMode=exact): resolve one method mapping by owner+name+descriptor between namespaces and report resolved/not_found/ambiguous. Stricter than find-mapping's exact mode — it requires a COMPLETE descriptor projection and returns mapping_unavailable when the descriptor's class references cannot all be projected to the target namespace (find-mapping is more lenient there). Use find-mapping kind=method signatureMode=exact unless you specifically need that strict-completeness guarantee.", resolveMethodMappingExactShape, { readOnlyHint: true }, async (args) => runTool("resolve-method-mapping-exact", args, resolveMethodMappingExactSchema, async (input) => sourceService.resolveMethodMappingExact({
665
+ expertTool("resolve-method-mapping-exact", "Strict variant of find-mapping(kind=method, signatureMode=exact): requires a COMPLETE descriptor projection and returns mapping_unavailable when any descriptor class reference cannot be projected. Prefer find-mapping unless you need that guarantee.", resolveMethodMappingExactShape, { readOnlyHint: true }, async (args) => runTool("resolve-method-mapping-exact", args, resolveMethodMappingExactSchema, async (input) => sourceService.resolveMethodMappingExact({
661
666
  version: input.version,
662
667
  name: input.name,
663
668
  owner: input.owner,
@@ -107,7 +107,7 @@ export function runJavaProcess(options) {
107
107
  }
108
108
  }));
109
109
  });
110
- proc.once("exit", (code) => {
110
+ proc.once("close", (code) => {
111
111
  clearTimeout(timer);
112
112
  resolve({
113
113
  exitCode: code ?? -1,
@@ -9,6 +9,9 @@ export declare function encodeJsonRpcMessage(message: JSONRPCMessage, mode: Conc
9
9
  export declare class JsonRpcFrameReader {
10
10
  private mode;
11
11
  private buffer;
12
+ private pendingChunks;
13
+ private pendingBytes;
14
+ private awaitedFrameEnd;
12
15
  get currentMode(): FramingMode;
13
16
  reset(): void;
14
17
  clear(): void;
@@ -16,6 +19,7 @@ export declare class JsonRpcFrameReader {
16
19
  onFrame: (frame: ParsedJsonRpcFrame) => void;
17
20
  onError: (error: Error) => void;
18
21
  }): void;
22
+ private canCompleteFrame;
19
23
  private detectMode;
20
24
  private readLineDelimitedMessage;
21
25
  private readContentLengthMessage;
@@ -25,21 +25,35 @@ export function encodeJsonRpcMessage(message, mode) {
25
25
  export class JsonRpcFrameReader {
26
26
  mode = "unknown";
27
27
  buffer = Buffer.alloc(0);
28
+ pendingChunks = [];
29
+ pendingBytes = 0;
30
+ awaitedFrameEnd = -1;
28
31
  get currentMode() {
29
32
  return this.mode;
30
33
  }
31
34
  reset() {
32
35
  this.mode = "unknown";
36
+ this.awaitedFrameEnd = -1;
33
37
  }
34
38
  clear() {
35
39
  this.mode = "unknown";
36
40
  this.buffer = Buffer.alloc(0);
41
+ this.pendingChunks = [];
42
+ this.pendingBytes = 0;
43
+ this.awaitedFrameEnd = -1;
37
44
  }
38
45
  processChunk(chunk, handlers) {
39
46
  if (chunk.length === 0) {
40
47
  return;
41
48
  }
42
- this.buffer = Buffer.concat([this.buffer, chunk]);
49
+ this.pendingChunks.push(chunk);
50
+ this.pendingBytes += chunk.length;
51
+ if (!this.canCompleteFrame(chunk)) {
52
+ return;
53
+ }
54
+ this.buffer = Buffer.concat([this.buffer, ...this.pendingChunks]);
55
+ this.pendingChunks = [];
56
+ this.pendingBytes = 0;
43
57
  while (true) {
44
58
  try {
45
59
  if (this.mode === "unknown") {
@@ -71,6 +85,12 @@ export class JsonRpcFrameReader {
71
85
  }
72
86
  }
73
87
  }
88
+ canCompleteFrame(chunk) {
89
+ if (this.mode === "content-length" && this.awaitedFrameEnd >= 0) {
90
+ return this.buffer.length + this.pendingBytes >= this.awaitedFrameEnd;
91
+ }
92
+ return chunk.includes(0x0a);
93
+ }
74
94
  detectMode() {
75
95
  while (this.buffer.length > 0) {
76
96
  if (this.buffer[0] === 0x0a) {
@@ -124,6 +144,7 @@ export class JsonRpcFrameReader {
124
144
  }
125
145
  }
126
146
  readContentLengthMessage() {
147
+ this.awaitedFrameEnd = -1;
127
148
  const headerBoundary = findHeaderBoundary(this.buffer);
128
149
  if (!headerBoundary) {
129
150
  return undefined;
@@ -158,6 +179,7 @@ export class JsonRpcFrameReader {
158
179
  const messageStart = headerBoundary.index + headerBoundary.delimiterBytes;
159
180
  const frameEnd = messageStart + contentLength;
160
181
  if (this.buffer.length < frameEnd) {
182
+ this.awaitedFrameEnd = frameEnd;
161
183
  return undefined;
162
184
  }
163
185
  const body = this.buffer.subarray(messageStart, frameEnd).toString("utf8");
@@ -20,6 +20,18 @@ export type DirectionIndex = {
20
20
  simple: Map<string, Set<string>>;
21
21
  records: Map<string, MappingSymbolRecord>;
22
22
  };
23
+ /**
24
+ * O(1) exact-lookup index over a single target namespace's records, used by
25
+ * existence checks. `byKey` is keyed by `kind|owner|name` (owner empty for
26
+ * classes, which key on their FQCN symbol); `classBySimpleName` resolves the
27
+ * auto-class short-name path. Values are arrays so callers can still tell a
28
+ * unique match (resolved) from multiple (ambiguous).
29
+ */
30
+ export type ExactRecordIndex = {
31
+ byKey: Map<string, MappingSymbolRecord[]>;
32
+ classBySimpleName: Map<string, MappingSymbolRecord[]>;
33
+ membersByOwner: Map<string, MappingSymbolRecord[]>;
34
+ };
23
35
  export type MappingLookupSource = "loom-cache" | "maven" | "mojang-client-mappings";
24
36
  export type PairRecord = {
25
37
  index: DirectionIndex;
@@ -50,6 +62,11 @@ export type LoadedGraph = {
50
62
  adjacency: Map<import("../types.js").SourceMapping, import("../types.js").SourceMapping[]>;
51
63
  pathCache: Map<PairKey, import("../types.js").SourceMapping[] | undefined>;
52
64
  recordsByTarget: Map<import("../types.js").SourceMapping, MappingSymbolRecord[]>;
65
+ /**
66
+ * Graph-scoped, lazily-built exact-lookup indexes keyed by target namespace.
67
+ * Populated on first existence check for a namespace; dies with graph eviction.
68
+ */
69
+ exactRecordIndex: Map<import("../types.js").SourceMapping, ExactRecordIndex>;
53
70
  /**
54
71
  * Graph-scoped cache of class-to-class descriptor projections, keyed by
55
72
  * `path.join(">") + NUL + internalName`. Value is the projected internal name,
@@ -53,36 +53,62 @@ export async function loadTinyPairsFromMaven(deps, version) {
53
53
  const warnings = [];
54
54
  const merged = new Map();
55
55
  const repos = deps.config.sourceRepos;
56
- const intermediaryUrls = [];
57
- const yarnUrls = [];
56
+ const attemptedUrls = [];
58
57
  const repoBases = repos.map((repo) => repo.replace(/\/+$/, ""));
59
58
  const yarnCoordinatesByRepo = await Promise.all(repoBases.map(async (base) => ({
60
59
  base,
61
60
  yarnCoordinates: await fetchYarnCoordinates(deps.fetchFn, base, version)
62
61
  })));
62
+ const tryUrls = async (urls) => {
63
+ for (const url of urls) {
64
+ attemptedUrls.push(url);
65
+ try {
66
+ const downloaded = await downloadToCache(url, defaultDownloadPath(deps.config.cacheDir, url), {
67
+ fetchFn: deps.fetchFn,
68
+ retries: deps.config.fetchRetries,
69
+ timeoutMs: deps.config.fetchTimeoutMs
70
+ });
71
+ if (!downloaded.ok || !downloaded.path) {
72
+ continue;
73
+ }
74
+ const parsed = await parseTinyFromJar(downloaded.path);
75
+ if (parsed.size > 0) {
76
+ return parsed;
77
+ }
78
+ }
79
+ catch {
80
+ // try the next candidate URL
81
+ }
82
+ }
83
+ return undefined;
84
+ };
85
+ let intermediaryParsed;
86
+ let yarnParsed;
63
87
  for (const { base, yarnCoordinates } of yarnCoordinatesByRepo) {
64
- intermediaryUrls.push(`${base}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`, `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}.jar`);
88
+ if (!intermediaryParsed) {
89
+ intermediaryParsed = await tryUrls([
90
+ `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`,
91
+ `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}.jar`
92
+ ]);
93
+ }
65
94
  for (const coordinate of yarnCoordinates) {
66
- yarnUrls.push(`${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`, `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}.jar`);
95
+ if (yarnParsed) {
96
+ break;
97
+ }
98
+ yarnParsed = await tryUrls([
99
+ `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`,
100
+ `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}.jar`
101
+ ]);
67
102
  }
68
- }
69
- const allUrls = [...intermediaryUrls, ...yarnUrls];
70
- const parsedResults = await Promise.allSettled(allUrls.map(async (url) => {
71
- const downloaded = await downloadToCache(url, defaultDownloadPath(deps.config.cacheDir, url), {
72
- fetchFn: deps.fetchFn,
73
- retries: deps.config.fetchRetries,
74
- timeoutMs: deps.config.fetchTimeoutMs
75
- });
76
- if (!downloaded.ok || !downloaded.path) {
77
- return undefined;
103
+ if (intermediaryParsed && yarnParsed) {
104
+ break;
78
105
  }
79
- return parseTinyFromJar(downloaded.path);
80
- }));
81
- for (const result of parsedResults) {
82
- if (result.status !== "fulfilled" || !result.value) {
106
+ }
107
+ for (const parsed of [intermediaryParsed, yarnParsed]) {
108
+ if (!parsed) {
83
109
  continue;
84
110
  }
85
- for (const [key, index] of result.value.entries()) {
111
+ for (const [key, index] of parsed.entries()) {
86
112
  const existing = merged.get(key);
87
113
  if (!existing) {
88
114
  merged.set(key, index);
@@ -98,7 +124,7 @@ export async function loadTinyPairsFromMaven(deps, version) {
98
124
  return {
99
125
  pairs: merged,
100
126
  warnings,
101
- mappingArtifact: allUrls[0] ?? "maven:none"
127
+ mappingArtifact: attemptedUrls[0] ?? "maven:none"
102
128
  };
103
129
  }
104
130
  //# sourceMappingURL=tiny-maven.js.map
@@ -1,5 +1,5 @@
1
1
  import type { MappingSourcePriority, SourceMapping } from "../types.js";
2
- import type { CandidateAccumulator, DirectionIndex, LoadedGraph, MappingLookupSource, MappingSymbolRecord, MatchRankKey, PairKey, PairRecord } from "./internal-types.js";
2
+ import type { CandidateAccumulator, DirectionIndex, ExactRecordIndex, LoadedGraph, MappingLookupSource, MappingSymbolRecord, MatchRankKey, PairKey, PairRecord } from "./internal-types.js";
3
3
  import { MAX_CANDIDATES } from "./internal-types.js";
4
4
  import type { ClassApiMatrixKind, DescriptorProjection, MappingLookupCandidate, ResolutionCandidate, SymbolQueryInput, SymbolReference } from "./types.js";
5
5
  export type { DescriptorProjection };
@@ -41,6 +41,17 @@ export declare function applyDisambiguationHints(candidates: MappingLookupCandid
41
41
  export declare function projectLookupCandidateDescriptor(candidate: MappingLookupCandidate, sourceDescriptor: string, targetDescriptor: string | undefined): MappingLookupCandidate;
42
42
  export declare function effectiveLoomSearchProjectPath(projectPath: string | undefined): string | undefined;
43
43
  export declare function collectTargetRecords(graph: LoadedGraph, targetMapping: SourceMapping): MappingSymbolRecord[];
44
+ /**
45
+ * Lazily build (and memoize on the graph) an O(1) exact-lookup index for a
46
+ * target namespace. Classes key on their FQCN symbol (owner left empty) and are
47
+ * additionally indexed by simple name for the auto-class short-name path;
48
+ * fields/methods key on `kind|owner|name`.
49
+ */
50
+ export declare function collectExactRecordIndex(graph: LoadedGraph, targetMapping: SourceMapping): ExactRecordIndex;
51
+ export declare function lookupExactClassBySymbol(index: ExactRecordIndex, symbol: string): MappingSymbolRecord[];
52
+ export declare function lookupExactMembersByOwner(index: ExactRecordIndex, owner: string): MappingSymbolRecord[];
53
+ export declare function lookupExactClassBySimpleName(index: ExactRecordIndex, name: string): MappingSymbolRecord[];
54
+ export declare function lookupExactMembers(index: ExactRecordIndex, kind: "field" | "method", owner: string, name: string): MappingSymbolRecord[];
44
55
  export declare function normalizeIncludedKinds(inputKinds: ClassApiMatrixKind[] | undefined): Set<ClassApiMatrixKind>;
45
56
  export declare function inferAmbiguityReasons(candidates: ResolutionCandidate[], usedMojangClientMappings: boolean): string[];
46
57
  export declare function clampCandidateLimit(limit: number | undefined): number;
@@ -429,7 +429,59 @@ export function effectiveLoomSearchProjectPath(projectPath) {
429
429
  return normalizeOptionalProjectPath(projectPath) ?? normalizeOptionalProjectPath(process.cwd());
430
430
  }
431
431
  export function collectTargetRecords(graph, targetMapping) {
432
- return [...(graph.recordsByTarget.get(targetMapping) ?? [])];
432
+ return graph.recordsByTarget.get(targetMapping) ?? [];
433
+ }
434
+ function exactRecordKey(kind, owner, name) {
435
+ return `${kind}|${owner}|${name}`;
436
+ }
437
+ /**
438
+ * Lazily build (and memoize on the graph) an O(1) exact-lookup index for a
439
+ * target namespace. Classes key on their FQCN symbol (owner left empty) and are
440
+ * additionally indexed by simple name for the auto-class short-name path;
441
+ * fields/methods key on `kind|owner|name`.
442
+ */
443
+ export function collectExactRecordIndex(graph, targetMapping) {
444
+ const cached = graph.exactRecordIndex.get(targetMapping);
445
+ if (cached) {
446
+ return cached;
447
+ }
448
+ const byKey = new Map();
449
+ const classBySimpleName = new Map();
450
+ const membersByOwner = new Map();
451
+ const push = (map, key, record) => {
452
+ const bucket = map.get(key);
453
+ if (bucket) {
454
+ bucket.push(record);
455
+ }
456
+ else {
457
+ map.set(key, [record]);
458
+ }
459
+ };
460
+ for (const record of collectTargetRecords(graph, targetMapping)) {
461
+ if (record.kind === "class") {
462
+ push(byKey, exactRecordKey("class", "", record.symbol), record);
463
+ push(classBySimpleName, record.name, record);
464
+ }
465
+ else {
466
+ push(byKey, exactRecordKey(record.kind, record.owner ?? "", record.name), record);
467
+ push(membersByOwner, record.owner ?? "", record);
468
+ }
469
+ }
470
+ const index = { byKey, classBySimpleName, membersByOwner };
471
+ graph.exactRecordIndex.set(targetMapping, index);
472
+ return index;
473
+ }
474
+ export function lookupExactClassBySymbol(index, symbol) {
475
+ return index.byKey.get(exactRecordKey("class", "", symbol)) ?? [];
476
+ }
477
+ export function lookupExactMembersByOwner(index, owner) {
478
+ return index.membersByOwner.get(owner) ?? [];
479
+ }
480
+ export function lookupExactClassBySimpleName(index, name) {
481
+ return index.classBySimpleName.get(name) ?? [];
482
+ }
483
+ export function lookupExactMembers(index, kind, owner, name) {
484
+ return index.byKey.get(exactRecordKey(kind, owner, name)) ?? [];
433
485
  }
434
486
  export function normalizeIncludedKinds(inputKinds) {
435
487
  const normalized = new Set();