@adhisang/minecraft-modding-mcp 3.1.1 → 3.2.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 (41) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +20 -8
  3. package/dist/access-transformer-parser.d.ts +17 -0
  4. package/dist/access-transformer-parser.js +97 -0
  5. package/dist/concurrency.d.ts +1 -0
  6. package/dist/concurrency.js +24 -0
  7. package/dist/decompiler/vineflower.js +22 -21
  8. package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +20 -20
  10. package/dist/entry-tools/inspect-minecraft-service.d.ts +148 -148
  11. package/dist/entry-tools/validate-project-service.d.ts +153 -16
  12. package/dist/entry-tools/validate-project-service.js +360 -23
  13. package/dist/gradle-paths.d.ts +4 -0
  14. package/dist/gradle-paths.js +57 -0
  15. package/dist/index.js +65 -13
  16. package/dist/mapping-pipeline-service.d.ts +3 -1
  17. package/dist/mapping-pipeline-service.js +16 -1
  18. package/dist/mapping-service.d.ts +4 -0
  19. package/dist/mapping-service.js +155 -60
  20. package/dist/minecraft-explorer-service.d.ts +13 -0
  21. package/dist/minecraft-explorer-service.js +8 -4
  22. package/dist/mixin-validator.d.ts +33 -2
  23. package/dist/mixin-validator.js +197 -11
  24. package/dist/mod-analyzer.d.ts +1 -0
  25. package/dist/mod-analyzer.js +17 -1
  26. package/dist/mod-decompile-service.js +4 -4
  27. package/dist/mod-remap-service.js +1 -54
  28. package/dist/mod-search-service.d.ts +1 -0
  29. package/dist/mod-search-service.js +84 -51
  30. package/dist/response-utils.d.ts +35 -0
  31. package/dist/response-utils.js +113 -0
  32. package/dist/source-jar-reader.d.ts +16 -0
  33. package/dist/source-jar-reader.js +103 -1
  34. package/dist/source-resolver.js +9 -10
  35. package/dist/source-service.d.ts +22 -2
  36. package/dist/source-service.js +914 -105
  37. package/dist/tool-contract-manifest.js +8 -6
  38. package/dist/types.d.ts +17 -0
  39. package/dist/workspace-mapping-service.d.ts +13 -0
  40. package/dist/workspace-mapping-service.js +146 -14
  41. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.2.0] - 2026-04-12
9
+
10
+ ### Added
11
+ - `resolve-artifact`, `find-mapping`, `resolve-method-mapping-exact`, `resolve-workspace-symbol`, and `check-symbol-exists` now accept an optional `compact` parameter (default `false`). When `true`, empty arrays, null values, and empty objects are stripped from the top-level response. For `resolve-artifact`, compact mode additionally omits `provenance`, `artifactContents`, `sampleEntries`, `adjacentSourceCandidates`, `binaryJarPath`, `coordinate`, `repoUrl`, and `resolvedSourceJarPath`. For mapping tools, compact mode omits the redundant `candidates` array when the result is a single full-confidence exact-match resolution.
12
+ - `validate-access-widener` now accepts `projectPath`, `scope`, and `preferProjectVersion` for runtime-aware validation against Loom runtime jars, and reports additive `provenance`, `resolvedInRuntime`, and `resolvedRuntimeAccess` evidence for matched class, method, and field entries.
13
+ - Added `validate-access-transformer` for Forge / NeoForge Access Transformer validation, including `atNamespace` support, workspace-driven namespace inference, runtime artifact provenance, and per-entry runtime access evidence.
14
+ - `analyze-mod-jar` now surfaces packaged Access Transformer paths alongside mod metadata.
15
+
16
+ ### Fixed
17
+ - `validate-access-widener` and `validate-access-transformer` runtime-aware method validation now preserves remapped JVM descriptors through exact and fallback member remaps, eliminating false-negative `method not found` results for entries whose descriptors reference remapped Minecraft classes such as `Level#setBlock(BlockPos, BlockState, int)`.
18
+ - `validate-access-widener` runtime-aware `scope: "merged"` now prefers explicit `*merged-intermediary*` / `*merged-mojang*` runtime jars over ambiguous `minecraft-merged.jar` candidates, and Loom tiny lookup now scans workspace and Gradle user-home caches so runtime validation can resolve descriptor remaps in normal Loom setups.
19
+ - `validate-project` now forwards workspace runtime context to discovered Access Widener validation so project-summary runs can use the same runtime-aware behavior as direct `validate-access-widener` calls.
20
+ - `validate-project` now discovers Access Transformer files, supports `task="access-transformer"`, and can include those validations in workspace summaries when `discover` requests them.
21
+ - `resolve-artifact`, `get-class-source`, `get-class-members`, and `inspect-minecraft` source/class-member flows now treat unobfuscated releases such as `26.1+` as native `mojang` runtime namespaces for both version and versioned-coordinate targets, avoiding false `ERR_MAPPING_NOT_APPLIED` failures and wrong-version Loom source-jar approximations when no exact source jar is present.
22
+ - `validate-mixin` tool-health diagnostics now treat unobfuscated `mojang` runtime names as available, while still flagging `intermediary`/`yarn` as unavailable on `26.1+`.
23
+ - `validate-project task="project-summary"` now pre-resolves `preferProjectVersion=true` consistently across discovered Access Widener and workspace Mixin checks, and returns a blocked summary with version-agnostic recovery guidance when discovered validators need a version but neither the request nor `gradle.properties` can supply one.
24
+ - `check-symbol-exists` and `analyze-symbol task="exists"` now fall back to unobfuscated runtime bytecode for `mojang`/runtime-name existence checks on `26.1+`, preserve the original `mapping_unavailable` result when the runtime JAR cannot be resolved, and return a targeted warning when callers provide only a short class name.
25
+ - Loom tiny mapping discovery no longer performs unbounded directory traversal when the version-specific cache directory is absent, preventing excessive memory consumption on systems with large Gradle caches.
26
+ - Mapping graph cache eviction after lifecycle scans now correctly releases all cached entries for a version instead of silently skipping them.
27
+
28
+ ### Performance
29
+ - `resolve-artifact` source-jar detection and mapping tiny-jar loading now reuse a single ZIP walk per jar probe instead of reopening and fully enumerating matching archives on each helper call.
30
+ - `search-mod-source`, `decompile-mod-jar`, `get-mod-class-source`, `validate-project` workspace discovery, and workspace mapping detection now avoid synchronous hot-path file/glob reads and use bounded concurrent text reads where safe, reducing event-loop stalls on larger decompiled outputs and multi-module workspaces.
31
+
8
32
  ## [3.1.1] - 2026-03-21
9
33
 
10
34
  ### Fixed
package/README.md CHANGED
@@ -20,8 +20,8 @@
20
20
  - **Source Exploration**: browse and search decompiled Minecraft source code with line-level precision and cursor-paginated file listing
21
21
  - **Multi-Mapping Conversion**: translate class, field, and method names between `obfuscated`, `mojang`, `intermediary`, and `yarn`
22
22
  - **Version Comparison**: diff class signatures and registry entries between Minecraft versions
23
- - **Mod JAR Analysis**: extract metadata, dependencies, entrypoints, and Mixin configs from Fabric, Forge, and NeoForge mod JARs
24
- - **Mixin and Access Widener Validation**: validate source and `.accesswidener` files against a target Minecraft version
23
+ - **Mod JAR Analysis**: extract metadata, dependencies, entrypoints, Mixin configs, and packaged Access Transformer paths from Fabric, Forge, and NeoForge mod JARs
24
+ - **Mixin, Access Widener, and Access Transformer Validation**: validate source, `.accesswidener`, and Forge/NeoForge access transformer files against a target Minecraft version
25
25
  - **NBT Round-Trip**: decode NBT binary to typed JSON, apply RFC 6902 patches, and encode it back to NBT
26
26
  - **Registry Data and Runtime Metrics**: query generated registry snapshots and inspect cache and latency counters
27
27
  - **MCP Resources**: expose versions, class source, artifact metadata, and mappings through URI-based resources
@@ -136,7 +136,7 @@ All six return `result.summary` first, and can include `summary.nextActions` whe
136
136
  | `analyze-symbol` | symbol existence checks, mapping conversion, lifecycle tracing, and workspace symbol resolution |
137
137
  | `compare-minecraft` | version-pair diffs, class diffs, registry diffs, and migration-oriented overviews |
138
138
  | `analyze-mod` | mod metadata, decompile/search flows, class source, and safe remap preview/apply |
139
- | `validate-project` | workspace summaries plus direct Mixin and Access Widener validation |
139
+ | `validate-project` | workspace summaries plus direct Mixin, Access Widener, and Access Transformer validation |
140
140
  | `manage-cache` | cache inventory, verification, and preview/apply cleanup workflows |
141
141
 
142
142
  ### Workflow Notes
@@ -147,7 +147,10 @@ Keep only the high-frequency notes here. For the full pitfall list, exact contra
147
147
  - If you do not already have an artifact, prefer `subject.kind="workspace"` for `inspect-minecraft` instead of guessing artifact details. When artifact context is the only missing input, a retryable `suggestedCall` preserves the requested task.
148
148
  - `trace-symbol-lifecycle` expects `Class.method` in `symbol`. Keep exact overload matching in the separate `descriptor` field.
149
149
  - Workspace inspection can still confirm vanilla classes when source coverage is partial, and `inspect-minecraft task="list-files"` reports a partial result with follow-up guidance when that happens.
150
+ - `check-symbol-exists` and `analyze-symbol task="exists"` now validate `mojang` lookups on unobfuscated releases such as `26.1+` against runtime bytecode when no mapping graph exists, preserve the original `mapping_unavailable` result if the runtime JAR itself cannot be resolved, and return a targeted warning when callers provide only a short class name.
150
151
  - `analyze-mod` and `validate-project` still require structured `subject` objects and canonical `include` groups, but stale string-subject or domain-include payloads now return `ERR_INVALID_INPUT` with a retryable `suggestedCall`.
152
+ - `validate-project task="project-summary"` now pre-resolves `preferProjectVersion=true` consistently across discovered Access Widener, Access Transformer, and Mixin checks, and blocks with version-agnostic recovery guidance when discovered validators need a version but neither the request nor `gradle.properties` can supply one.
153
+ - Local source-jar probing, decompiled mod source reads, and workspace discovery now avoid synchronous hot-path ZIP/file scans and use bounded concurrent reads where safe, so cold `resolve-artifact`, `analyze-mod`, and `validate-project` workflows stay more responsive on larger jars and multi-module workspaces.
151
154
 
152
155
  ### Inspect Minecraft source from a version
153
156
 
@@ -216,7 +219,7 @@ Keep only the high-frequency notes here. For the full pitfall list, exact contra
216
219
  "subject": {
217
220
  "kind": "workspace",
218
221
  "projectPath": "/workspace/modid",
219
- "discover": ["mixins", "access-wideners"]
222
+ "discover": ["mixins", "access-wideners", "access-transformers"]
220
223
  },
221
224
  "preferProjectVersion": true,
222
225
  "preferProjectMapping": true
@@ -224,6 +227,8 @@ Keep only the high-frequency notes here. For the full pitfall list, exact contra
224
227
  }
225
228
  ```
226
229
 
230
+ Workspace summaries still default to discovering mixins and access wideners. Add `"access-transformers"` to `subject.discover` when you want Access Transformer files included in the summary run.
231
+
227
232
  ## Documentation
228
233
 
229
234
  - [Detailed example requests](docs/examples.md) for copyable payloads and common workflows
@@ -243,7 +248,7 @@ Start with these top-level workflow tools unless you already know the exact spec
243
248
  | `analyze-symbol` | Handle symbol existence checks, namespace mapping, lifecycle tracing, workspace symbol resolution, and API overviews |
244
249
  | `compare-minecraft` | Compare version pairs, class diffs, registry diffs, and migration-oriented summaries |
245
250
  | `analyze-mod` | Summarize mod metadata, decompile and search mod code, inspect class source, and preview or apply remaps |
246
- | `validate-project` | Summarize workspaces and run direct Mixin or Access Widener validation |
251
+ | `validate-project` | Summarize workspaces and run direct Mixin, Access Widener, or Access Transformer validation |
247
252
  | `manage-cache` | List, verify, and preview or apply cache cleanup and rebuild operations |
248
253
  <!-- END GENERATED TOOL TABLE: v3-entry-tools -->
249
254
 
@@ -265,6 +270,8 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading o
265
270
  | `index-artifact` | Rebuild indexed metadata for an existing artifact |
266
271
  <!-- END GENERATED TOOL TABLE: source-exploration -->
267
272
 
273
+ For unobfuscated releases such as `26.1+`, `mapping="mojang"` now uses the runtime/decompile path directly for version and versioned-coordinate targets and skips Loom source-jar discovery entirely, while `intermediary` and `yarn` still fall back to `obfuscated` with a warning.
274
+
268
275
  ### Version Comparison & Symbol Tracking
269
276
 
270
277
  Tools for comparing class and registry changes across Minecraft versions and tracing symbol existence over time.
@@ -291,6 +298,8 @@ Tools for converting symbol names between namespaces and checking symbol existen
291
298
  | `check-symbol-exists` | Check whether a class, field, or method exists in a namespace |
292
299
  <!-- END GENERATED TOOL TABLE: mapping-symbols -->
293
300
 
301
+ `resolve-artifact`, `find-mapping`, `resolve-method-mapping-exact`, `resolve-workspace-symbol`, and `check-symbol-exists` accept an optional `compact` parameter (default `false`). When `true`, empty arrays, null values, and empty objects are stripped from the top-level response to reduce token overhead. For `resolve-artifact`, compact mode additionally omits diagnostic fields (`provenance`, `artifactContents`, `sampleEntries`, `adjacentSourceCandidates`, `binaryJarPath`, `coordinate`, `repoUrl`, `resolvedSourceJarPath`), returning only the essential fields needed for downstream tool calls. For mapping tools, compact mode omits the redundant `candidates` array when the result is a single full-confidence exact-match resolution (`resolved=true`, `resolvedSymbol` present, `candidates.length=1`, `candidateCount=1`, `!candidatesTruncated`, `matchKind="exact"`, `confidence` missing or `1`).
302
+
294
303
  ### NBT Utilities
295
304
 
296
305
  Tools for decoding, patching, and encoding Java Edition NBT binary data using a typed JSON representation.
@@ -310,7 +319,7 @@ Tools for extracting metadata from mod JARs, decompiling mod source, searching m
310
319
  <!-- BEGIN GENERATED TOOL TABLE: mod-analysis -->
311
320
  | Tool | Purpose |
312
321
  | --- | --- |
313
- | `analyze-mod-jar` | Extract mod metadata, dependencies, entrypoints, and mixin config info from a JAR |
322
+ | `analyze-mod-jar` | Extract mod metadata, dependencies, entrypoints, mixin config info, and packaged access transformer paths from a JAR |
314
323
  | `decompile-mod-jar` | Decompile a mod JAR and optionally return one class source |
315
324
  | `get-mod-class-source` | Read one class source from the decompiled mod cache |
316
325
  | `search-mod-source` | Search decompiled mod source by class, method, field, or content |
@@ -319,13 +328,16 @@ Tools for extracting metadata from mod JARs, decompiling mod source, searching m
319
328
 
320
329
  ### Validation
321
330
 
322
- Tools for validating Mixin source and Access Widener files against a target Minecraft version.
331
+ Tools for validating Mixin source, Access Widener files, and Forge/NeoForge Access Transformer files against a target Minecraft version.
332
+ `validate-access-widener` keeps vanilla bytecode validation by default, and now also supports runtime-aware validation through `projectPath`, `scope`, and `preferProjectVersion`, returning runtime `provenance` plus per-entry `resolvedRuntimeAccess` evidence when that mode is used. Runtime-aware method validation now preserves remapped JVM descriptors across namespace changes, searches Loom tiny mappings from workspace and Gradle user-home caches, and prefers explicit `*merged-intermediary*` / `*merged-mojang*` jars over ambiguous `minecraft-merged.jar` candidates.
333
+ `validate-access-transformer` infers `atNamespace` from Forge or NeoForge workspace context when `projectPath` is provided, validates packaged or inline AT content, and uses loader/runtime artifacts for `scope="loader"` instead of treating loader as a merged-only alias.
323
334
 
324
335
  <!-- BEGIN GENERATED TOOL TABLE: validation -->
325
336
  | Tool | Purpose |
326
337
  | --- | --- |
327
338
  | `validate-mixin` | Validate Mixin source against a target Minecraft version |
328
- | `validate-access-widener` | Validate Access Widener content against a target Minecraft version |
339
+ | `validate-access-widener` | Validate Access Widener content against a target Minecraft version, optionally using runtime-aware Loom artifacts |
340
+ | `validate-access-transformer` | Validate Access Transformer content against a target Minecraft version, optionally using Forge/NeoForge runtime artifacts |
329
341
  <!-- END GENERATED TOOL TABLE: validation -->
330
342
 
331
343
  ### Registry & Diagnostics
@@ -0,0 +1,17 @@
1
+ export type AccessTransformerAccessAction = "public" | "protected" | "package-private" | "private";
2
+ export type AccessTransformerFinalAction = "add" | "remove";
3
+ export type AccessTransformerEntry = {
4
+ line: number;
5
+ targetKind: "class" | "field" | "method";
6
+ owner: string;
7
+ target: string;
8
+ name?: string;
9
+ descriptor?: string;
10
+ accessAction: AccessTransformerAccessAction;
11
+ finalAction?: AccessTransformerFinalAction;
12
+ };
13
+ export type ParsedAccessTransformer = {
14
+ entries: AccessTransformerEntry[];
15
+ parseWarnings: string[];
16
+ };
17
+ export declare function parseAccessTransformer(content: string): ParsedAccessTransformer;
@@ -0,0 +1,97 @@
1
+ function parseAccessDeclaration(raw) {
2
+ const match = raw.match(/^(public|protected|default|private)([+-]f)?$/i);
3
+ if (!match) {
4
+ return undefined;
5
+ }
6
+ const finalModifier = match[2]?.toLowerCase();
7
+ return {
8
+ accessAction: match[1]?.toLowerCase() === "default"
9
+ ? "package-private"
10
+ : match[1]?.toLowerCase(),
11
+ ...(finalModifier === "+f" ? { finalAction: "add" } : {}),
12
+ ...(finalModifier === "-f" ? { finalAction: "remove" } : {})
13
+ };
14
+ }
15
+ function splitMemberToken(tokens) {
16
+ if (tokens.length === 0) {
17
+ return { targetKind: "class" };
18
+ }
19
+ if (tokens.length === 1) {
20
+ const token = tokens[0] ?? "";
21
+ const descriptorStart = token.indexOf("(");
22
+ if (descriptorStart >= 0) {
23
+ return {
24
+ targetKind: "method",
25
+ name: token.slice(0, descriptorStart),
26
+ descriptor: token.slice(descriptorStart)
27
+ };
28
+ }
29
+ return {
30
+ targetKind: "field",
31
+ name: token
32
+ };
33
+ }
34
+ return {
35
+ targetKind: "method",
36
+ name: tokens[0],
37
+ descriptor: tokens.slice(1).join("")
38
+ };
39
+ }
40
+ export function parseAccessTransformer(content) {
41
+ const entries = [];
42
+ const parseWarnings = [];
43
+ const lines = content.split(/\r?\n/);
44
+ for (let index = 0; index < lines.length; index++) {
45
+ const lineNumber = index + 1;
46
+ const rawLine = (lines[index] ?? "").trim();
47
+ if (!rawLine || rawLine.startsWith("#")) {
48
+ continue;
49
+ }
50
+ const withoutComment = rawLine.replace(/\s+#.*$/, "").trim();
51
+ if (!withoutComment) {
52
+ continue;
53
+ }
54
+ const parts = withoutComment.split(/\s+/);
55
+ if (parts.length < 2) {
56
+ parseWarnings.push(`Line ${lineNumber}: Incomplete access transformer entry "${withoutComment}".`);
57
+ continue;
58
+ }
59
+ const declaration = parseAccessDeclaration(parts[0] ?? "");
60
+ if (!declaration) {
61
+ parseWarnings.push(`Line ${lineNumber}: Unsupported access declaration "${parts[0]}".`);
62
+ continue;
63
+ }
64
+ const owner = parts[1] ?? "";
65
+ if (!owner) {
66
+ parseWarnings.push(`Line ${lineNumber}: Incomplete access transformer entry "${withoutComment}".`);
67
+ continue;
68
+ }
69
+ const member = splitMemberToken(parts.slice(2));
70
+ if (member.targetKind === "method" && (!member.name || !member.descriptor)) {
71
+ parseWarnings.push(`Line ${lineNumber}: Method entry requires a method name and JVM descriptor.`);
72
+ continue;
73
+ }
74
+ if ((member.targetKind === "field" || member.targetKind === "method") && !member.name) {
75
+ parseWarnings.push(`Line ${lineNumber}: Member entry requires a target name.`);
76
+ continue;
77
+ }
78
+ const target = member.targetKind === "class"
79
+ ? owner
80
+ : `${owner}#${member.name}${member.descriptor ?? ""}`;
81
+ entries.push({
82
+ line: lineNumber,
83
+ owner,
84
+ target,
85
+ targetKind: member.targetKind,
86
+ ...(member.name ? { name: member.name } : {}),
87
+ ...(member.descriptor ? { descriptor: member.descriptor } : {}),
88
+ accessAction: declaration.accessAction,
89
+ ...(declaration.finalAction ? { finalAction: declaration.finalAction } : {})
90
+ });
91
+ }
92
+ return {
93
+ entries,
94
+ parseWarnings
95
+ };
96
+ }
97
+ //# sourceMappingURL=access-transformer-parser.js.map
@@ -0,0 +1 @@
1
+ export declare function mapWithConcurrencyLimit<T, R>(items: readonly T[], limit: number, mapper: (item: T, index: number) => Promise<R>): Promise<R[]>;
@@ -0,0 +1,24 @@
1
+ export async function mapWithConcurrencyLimit(items, limit, mapper) {
2
+ if (!Number.isInteger(limit) || limit < 1) {
3
+ throw new Error("limit must be a positive integer");
4
+ }
5
+ if (items.length === 0) {
6
+ return [];
7
+ }
8
+ const results = new Array(items.length);
9
+ let nextIndex = 0;
10
+ const workerCount = Math.min(limit, items.length);
11
+ const worker = async () => {
12
+ while (true) {
13
+ const currentIndex = nextIndex;
14
+ nextIndex += 1;
15
+ if (currentIndex >= items.length) {
16
+ return;
17
+ }
18
+ results[currentIndex] = await mapper(items[currentIndex], currentIndex);
19
+ }
20
+ };
21
+ await Promise.all(Array.from({ length: workerCount }, async () => worker()));
22
+ return results;
23
+ }
24
+ //# sourceMappingURL=concurrency.js.map
@@ -1,11 +1,13 @@
1
- import { access, constants } from "node:fs/promises";
2
- import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
1
+ import { access, constants, mkdir, readFile, readdir, stat } from "node:fs/promises";
2
+ import { mkdirSync, rmSync } from "node:fs";
3
3
  import { createHash } from "node:crypto";
4
4
  import { basename, join, relative, sep } from "node:path";
5
+ import { mapWithConcurrencyLimit } from "../concurrency.js";
5
6
  import { createError, ERROR_CODES, isAppError } from "../errors.js";
6
7
  import { assertJavaAvailable, runJavaProcess } from "../java-process.js";
7
8
  import { log } from "../logger.js";
8
9
  const DEFAULT_TIMEOUT_MS = 120_000;
10
+ const DECOMPILED_JAVA_READ_CONCURRENCY = 8;
9
11
  const VINEFLOWER_FLAG_PROFILES = [
10
12
  { label: "default", flags: ["-din=1", "-rbr=1", "-dgs=1"] },
11
13
  { label: "relaxed", flags: ["-din=1", "-rbr=0", "-dgs=0"] },
@@ -47,14 +49,14 @@ async function assertVineflowerAvailable(vineflowerJarPath) {
47
49
  });
48
50
  }
49
51
  }
50
- function collectJavaFilesSync(baseDir, currentDir = "") {
52
+ async function collectJavaFilesRecursive(baseDir, currentDir = "") {
51
53
  const absoluteBase = currentDir ? join(baseDir, currentDir) : baseDir;
52
- const entries = readdirSync(absoluteBase, { withFileTypes: true });
54
+ const entries = await readdir(absoluteBase, { withFileTypes: true });
53
55
  const result = [];
54
56
  for (const entry of entries) {
55
57
  const next = currentDir ? join(currentDir, entry.name) : entry.name;
56
58
  if (entry.isDirectory()) {
57
- result.push(...collectJavaFilesSync(baseDir, next));
59
+ result.push(...await collectJavaFilesRecursive(baseDir, next));
58
60
  continue;
59
61
  }
60
62
  if (entry.isFile() && entry.name.endsWith(".java")) {
@@ -66,23 +68,21 @@ function collectJavaFilesSync(baseDir, currentDir = "") {
66
68
  async function collectJavaFiles(baseDir) {
67
69
  try {
68
70
  const fastGlobModule = (await import("fast-glob"));
69
- const sync = fastGlobModule.default?.sync;
70
- if (typeof sync === "function") {
71
- return sync("**/*.java", { cwd: baseDir, onlyFiles: true });
71
+ const glob = fastGlobModule.default?.glob;
72
+ if (typeof glob === "function") {
73
+ return (await glob("**/*.java", { cwd: baseDir, onlyFiles: true }))
74
+ .sort((left, right) => left.localeCompare(right));
72
75
  }
73
76
  }
74
77
  catch {
75
78
  // optional dependency: fallback to recursive traversal
76
79
  }
77
- return collectJavaFilesSync(baseDir).map((candidate) => candidate.split(sep).join("/"));
80
+ return (await collectJavaFilesRecursive(baseDir))
81
+ .map((candidate) => candidate.split(sep).join("/"))
82
+ .sort((left, right) => left.localeCompare(right));
78
83
  }
79
84
  function readFileTreeText(filePath) {
80
- return new Promise((resolve, reject) => {
81
- import("node:fs/promises")
82
- .then((fs) => fs.readFile(filePath, "utf8"))
83
- .then(resolve)
84
- .catch(reject);
85
- });
85
+ return readFile(filePath, "utf8");
86
86
  }
87
87
  function decompileOutputDir(cacheDir, binaryJarPath, signature) {
88
88
  const digest = createHash("sha256").update(binaryJarPath).update(signature).digest("hex");
@@ -130,17 +130,18 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
130
130
  const signature = options.signature ?? basename(normalizedBinaryJarPath);
131
131
  const outputDir = decompileOutputDir(cacheDir, normalizedBinaryJarPath, signature).replace(/[/\\]$/, "");
132
132
  try {
133
- mkdirSync(outputDir, { recursive: true });
134
- if (statSync(outputDir, { throwIfNoEntry: false })) {
133
+ await mkdir(outputDir, { recursive: true });
134
+ const outputDirStats = await stat(outputDir).catch(() => undefined);
135
+ if (outputDirStats) {
135
136
  const existingJavaFiles = await collectJavaFiles(outputDir);
136
137
  if (existingJavaFiles.length > 0) {
137
- const results = await Promise.all(existingJavaFiles.map(async (candidate) => {
138
+ const results = await mapWithConcurrencyLimit(existingJavaFiles, DECOMPILED_JAVA_READ_CONCURRENCY, async (candidate) => {
138
139
  const abs = join(outputDir, candidate);
139
140
  return {
140
141
  filePath: normalizeOutputPath(outputDir, abs),
141
142
  content: await readFileTreeText(abs)
142
143
  };
143
- }));
144
+ });
144
145
  emitDecompileLog("decompile.done", {
145
146
  durationMs: Date.now() - startedAt,
146
147
  artifactIdCandidate: options.artifactIdCandidate,
@@ -181,13 +182,13 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
181
182
  }
182
183
  });
183
184
  }
184
- const javaFiles = await Promise.all(javaFileNames.map(async (candidate) => {
185
+ const javaFiles = await mapWithConcurrencyLimit(javaFileNames, DECOMPILED_JAVA_READ_CONCURRENCY, async (candidate) => {
185
186
  const abs = join(outputDir, candidate);
186
187
  return {
187
188
  filePath: normalizeOutputPath(outputDir, abs),
188
189
  content: await readFileTreeText(abs)
189
190
  };
190
- }));
191
+ });
191
192
  emitDecompileLog("decompile.done", {
192
193
  durationMs: Date.now() - startedAt,
193
194
  artifactIdCandidate: options.artifactIdCandidate,
@@ -75,7 +75,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
75
75
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
76
76
  }, "strip", z.ZodTypeAny, {
77
77
  limit: number;
78
- searchType: "class" | "method" | "field" | "all" | "content";
78
+ searchType: "class" | "field" | "method" | "all" | "content";
79
79
  task: "search" | "remap" | "summary" | "class-source" | "decompile";
80
80
  subject: {
81
81
  kind: "jar";
@@ -111,7 +111,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
111
111
  targetMapping?: "mojang" | "yarn" | undefined;
112
112
  outputJar?: string | undefined;
113
113
  query?: string | undefined;
114
- searchType?: "class" | "method" | "field" | "all" | "content" | undefined;
114
+ searchType?: "class" | "field" | "method" | "all" | "content" | undefined;
115
115
  detail?: "full" | "summary" | "standard" | undefined;
116
116
  include?: string[] | undefined;
117
117
  includeFiles?: boolean | undefined;
@@ -119,7 +119,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
119
119
  executionMode?: "preview" | "apply" | undefined;
120
120
  }>, {
121
121
  limit: number;
122
- searchType: "class" | "method" | "field" | "all" | "content";
122
+ searchType: "class" | "field" | "method" | "all" | "content";
123
123
  task: "search" | "remap" | "summary" | "class-source" | "decompile";
124
124
  subject: {
125
125
  kind: "jar";
@@ -155,7 +155,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
155
155
  targetMapping?: "mojang" | "yarn" | undefined;
156
156
  outputJar?: string | undefined;
157
157
  query?: string | undefined;
158
- searchType?: "class" | "method" | "field" | "all" | "content" | undefined;
158
+ searchType?: "class" | "field" | "method" | "all" | "content" | undefined;
159
159
  detail?: "full" | "summary" | "standard" | undefined;
160
160
  include?: string[] | undefined;
161
161
  includeFiles?: boolean | undefined;
@@ -8,15 +8,15 @@ export declare const analyzeSymbolShape: {
8
8
  owner: z.ZodOptional<z.ZodString>;
9
9
  descriptor: z.ZodOptional<z.ZodString>;
10
10
  }, "strip", z.ZodTypeAny, {
11
- kind: "symbol" | "class" | "method" | "field";
12
11
  name: string;
13
- owner?: string | undefined;
12
+ kind: "symbol" | "class" | "field" | "method";
14
13
  descriptor?: string | undefined;
14
+ owner?: string | undefined;
15
15
  }, {
16
- kind: "symbol" | "class" | "method" | "field";
17
16
  name: string;
18
- owner?: string | undefined;
17
+ kind: "symbol" | "class" | "field" | "method";
19
18
  descriptor?: string | undefined;
19
+ owner?: string | undefined;
20
20
  }>;
21
21
  version: z.ZodOptional<z.ZodString>;
22
22
  sourceMapping: z.ZodOptional<z.ZodEnum<["obfuscated", "mojang", "intermediary", "yarn"]>>;
@@ -39,15 +39,15 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
39
39
  owner: z.ZodOptional<z.ZodString>;
40
40
  descriptor: z.ZodOptional<z.ZodString>;
41
41
  }, "strip", z.ZodTypeAny, {
42
- kind: "symbol" | "class" | "method" | "field";
43
42
  name: string;
44
- owner?: string | undefined;
43
+ kind: "symbol" | "class" | "field" | "method";
45
44
  descriptor?: string | undefined;
45
+ owner?: string | undefined;
46
46
  }, {
47
- kind: "symbol" | "class" | "method" | "field";
48
47
  name: string;
49
- owner?: string | undefined;
48
+ kind: "symbol" | "class" | "field" | "method";
50
49
  descriptor?: string | undefined;
50
+ owner?: string | undefined;
51
51
  }>;
52
52
  version: z.ZodOptional<z.ZodString>;
53
53
  sourceMapping: z.ZodOptional<z.ZodEnum<["obfuscated", "mojang", "intermediary", "yarn"]>>;
@@ -65,10 +65,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
65
65
  nameMode: "auto" | "fqcn";
66
66
  task: "map" | "workspace" | "exists" | "exact-map" | "lifecycle" | "api-overview";
67
67
  subject: {
68
- kind: "symbol" | "class" | "method" | "field";
69
68
  name: string;
70
- owner?: string | undefined;
69
+ kind: "symbol" | "class" | "field" | "method";
71
70
  descriptor?: string | undefined;
71
+ owner?: string | undefined;
72
72
  };
73
73
  signatureMode: "exact" | "name-only";
74
74
  maxCandidates: number;
@@ -79,15 +79,15 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
79
79
  classNameMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
80
80
  detail?: "full" | "summary" | "standard" | undefined;
81
81
  include?: string[] | undefined;
82
- includeKinds?: ("class" | "method" | "field")[] | undefined;
82
+ includeKinds?: ("class" | "field" | "method")[] | undefined;
83
83
  maxRows?: number | undefined;
84
84
  }, {
85
85
  task: "map" | "workspace" | "exists" | "exact-map" | "lifecycle" | "api-overview";
86
86
  subject: {
87
- kind: "symbol" | "class" | "method" | "field";
88
87
  name: string;
89
- owner?: string | undefined;
88
+ kind: "symbol" | "class" | "field" | "method";
90
89
  descriptor?: string | undefined;
90
+ owner?: string | undefined;
91
91
  };
92
92
  projectPath?: string | undefined;
93
93
  version?: string | undefined;
@@ -98,17 +98,17 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
98
98
  detail?: "full" | "summary" | "standard" | undefined;
99
99
  include?: string[] | undefined;
100
100
  signatureMode?: "exact" | "name-only" | undefined;
101
- includeKinds?: ("class" | "method" | "field")[] | undefined;
101
+ includeKinds?: ("class" | "field" | "method")[] | undefined;
102
102
  maxRows?: number | undefined;
103
103
  maxCandidates?: number | undefined;
104
104
  }>, {
105
105
  nameMode: "auto" | "fqcn";
106
106
  task: "map" | "workspace" | "exists" | "exact-map" | "lifecycle" | "api-overview";
107
107
  subject: {
108
- kind: "symbol" | "class" | "method" | "field";
109
108
  name: string;
110
- owner?: string | undefined;
109
+ kind: "symbol" | "class" | "field" | "method";
111
110
  descriptor?: string | undefined;
111
+ owner?: string | undefined;
112
112
  };
113
113
  signatureMode: "exact" | "name-only";
114
114
  maxCandidates: number;
@@ -119,15 +119,15 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
119
119
  classNameMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
120
120
  detail?: "full" | "summary" | "standard" | undefined;
121
121
  include?: string[] | undefined;
122
- includeKinds?: ("class" | "method" | "field")[] | undefined;
122
+ includeKinds?: ("class" | "field" | "method")[] | undefined;
123
123
  maxRows?: number | undefined;
124
124
  }, {
125
125
  task: "map" | "workspace" | "exists" | "exact-map" | "lifecycle" | "api-overview";
126
126
  subject: {
127
- kind: "symbol" | "class" | "method" | "field";
128
127
  name: string;
129
- owner?: string | undefined;
128
+ kind: "symbol" | "class" | "field" | "method";
130
129
  descriptor?: string | undefined;
130
+ owner?: string | undefined;
131
131
  };
132
132
  projectPath?: string | undefined;
133
133
  version?: string | undefined;
@@ -138,7 +138,7 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
138
138
  detail?: "full" | "summary" | "standard" | undefined;
139
139
  include?: string[] | undefined;
140
140
  signatureMode?: "exact" | "name-only" | undefined;
141
- includeKinds?: ("class" | "method" | "field")[] | undefined;
141
+ includeKinds?: ("class" | "field" | "method")[] | undefined;
142
142
  maxRows?: number | undefined;
143
143
  maxCandidates?: number | undefined;
144
144
  }>;