@adhisang/minecraft-modding-mcp 7.0.0 → 7.1.1

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 (95) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +4 -3
  3. package/dist/access-transformer-parser.d.ts +8 -0
  4. package/dist/access-transformer-parser.js +8 -1
  5. package/dist/access-widener-parser.d.ts +17 -0
  6. package/dist/access-widener-parser.js +12 -1
  7. package/dist/cache-registry.js +19 -6
  8. package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
  10. package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
  11. package/dist/entry-tools/batch-class-members-service.js +20 -6
  12. package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
  13. package/dist/entry-tools/batch-class-source-service.js +10 -0
  14. package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
  15. package/dist/entry-tools/compare-minecraft-service.js +65 -4
  16. package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
  17. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +15 -9
  18. package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
  19. package/dist/entry-tools/manage-cache-service.d.ts +2 -2
  20. package/dist/entry-tools/manage-cache-service.js +10 -14
  21. package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
  22. package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
  23. package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
  24. package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
  25. package/dist/entry-tools/validate-project-service.d.ts +2 -2
  26. package/dist/entry-tools/verify-mixin-target-service.js +19 -8
  27. package/dist/index.js +38 -15
  28. package/dist/java-process.d.ts +1 -0
  29. package/dist/java-process.js +14 -0
  30. package/dist/mapping/lookup.js +16 -1
  31. package/dist/mapping-service.d.ts +14 -0
  32. package/dist/mapping-service.js +35 -15
  33. package/dist/minecraft-explorer-service.js +70 -8
  34. package/dist/mixin/access-validators.js +38 -2
  35. package/dist/mixin/annotation-validators.js +137 -43
  36. package/dist/mixin/parsed-validator.js +21 -7
  37. package/dist/mixin-parser.d.ts +52 -0
  38. package/dist/mixin-parser.js +709 -130
  39. package/dist/mod-decompile-service.js +11 -1
  40. package/dist/mod-remap-service.js +6 -6
  41. package/dist/nbt/java-nbt-codec.js +7 -1
  42. package/dist/source/access-validate.js +10 -0
  43. package/dist/source/artifact-resolver.d.ts +27 -3
  44. package/dist/source/artifact-resolver.js +235 -30
  45. package/dist/source/class-source/members-builder.d.ts +7 -0
  46. package/dist/source/class-source/members-builder.js +4 -1
  47. package/dist/source/class-source.d.ts +9 -2
  48. package/dist/source/class-source.js +186 -27
  49. package/dist/source/indexer.js +69 -1
  50. package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
  51. package/dist/source/lifecycle/mapping-helpers.js +29 -3
  52. package/dist/source/lifecycle/runtime-check.d.ts +25 -0
  53. package/dist/source/lifecycle/runtime-check.js +68 -39
  54. package/dist/source/nested-jars.d.ts +15 -1
  55. package/dist/source/nested-jars.js +14 -5
  56. package/dist/source/search.d.ts +10 -2
  57. package/dist/source/search.js +60 -13
  58. package/dist/source/symbol-resolver.js +88 -0
  59. package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
  60. package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
  61. package/dist/source/validate-mixin.d.ts +5 -0
  62. package/dist/source/validate-mixin.js +136 -21
  63. package/dist/source/workspace-target.js +75 -7
  64. package/dist/source-jar-reader.d.ts +48 -1
  65. package/dist/source-jar-reader.js +93 -3
  66. package/dist/source-resolver.d.ts +7 -0
  67. package/dist/source-resolver.js +22 -14
  68. package/dist/source-service.d.ts +5 -0
  69. package/dist/source-service.js +7 -0
  70. package/dist/stdio-supervisor.d.ts +35 -1
  71. package/dist/stdio-supervisor.js +77 -2
  72. package/dist/storage/db.d.ts +62 -2
  73. package/dist/storage/db.js +181 -20
  74. package/dist/storage/files-repo.d.ts +7 -0
  75. package/dist/storage/files-repo.js +17 -4
  76. package/dist/storage/sqlite.d.ts +31 -1
  77. package/dist/storage/sqlite.js +125 -16
  78. package/dist/tool-contract-manifest.js +2 -2
  79. package/dist/tool-execution-gate.js +2 -1
  80. package/dist/tool-guidance.js +4 -1
  81. package/dist/tool-schemas.d.ts +64 -52
  82. package/dist/tool-schemas.js +9 -7
  83. package/dist/types.d.ts +9 -0
  84. package/dist/v1-parity-schemas.js +36 -2
  85. package/dist/version-diff-service.d.ts +23 -0
  86. package/dist/version-diff-service.js +101 -0
  87. package/dist/version-service.d.ts +14 -0
  88. package/dist/version-service.js +52 -3
  89. package/dist/workspace-context-cache.d.ts +25 -0
  90. package/dist/workspace-context-cache.js +52 -2
  91. package/dist/workspace-mapping-service.d.ts +8 -0
  92. package/dist/workspace-mapping-service.js +151 -21
  93. package/docs/README-ja.md +3 -1
  94. package/docs/tool-reference.md +69 -22
  95. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,78 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [7.1.1] - 2026-09-25
11
+
12
+ ### Fixed
13
+
14
+ - `validate-mixin` checks each member against the targets of its own `@Mixin` class. In a file with more than one `@Mixin` class, nested or top-level, every `@Shadow`, `@Accessor`, `@Invoker` and injection was checked against every target in the file, so members were reported missing from the other classes' targets. When a target cannot be mapped or loaded, only the members of the class that names it are skipped. For files with one `@Mixin` class, this per-class checking gives the same results as before. A `@Mixin` that follows other code on the same line, such as `@Pseudo @Mixin(A.class)`, and a second `@Mixin` class on a line that starts with `@Mixin` are now recognized. `@Mixin(value = Foo.class, targets = "pkg.Bar")` checks `pkg.Bar` as well as `Foo`, the only target checked before, and a string target containing `.class`, such as `targets = "net.minecraft.class_1234"`, no longer adds a nonexistent `net.minecraft` target that was reported missing.
15
+ - `validate-mixin` no longer drops members because of how their annotations are written. It reads `@Shadow`, `@Inject` and the other injection annotations, `@Accessor` and `@Invoker` when another annotation precedes them on the same line (`@Final @Shadow private int foo;`, `@Environment(EnvType.CLIENT) @Inject(...)`), when their arguments wrap onto later lines, and, wherever they appear, when an argument string contains a parenthesis, such as `args = "stringValue=)"`. It also keeps a member whose declaration shares the closing line of a wrapped annotation. Such members were skipped, so a mixin with a misspelled `@Shadow` after another annotation was reported clean, their `prefix` and `aliases` were ignored, or their `method` was reported missing. `@Shadow`-like text inside a Java text block (`"""…"""`) no longer creates members, a `'"'` character literal no longer makes the comments after it misread, and annotation-like text inside a preceding annotation's string argument is still ignored.
16
+ - `validate-mixin` reads target-selector quantifiers the way Mixin does: `*`, `+`, `{n}`, `{n,}` and `{n,m}` count the methods of exactly that name. They matched any method whose name started with the text, so `render*` against a class with only `renderItem` was reported valid; it is now reported as a missing method. A descriptor in the selector is still checked, also in name-less selectors such as `*(D)V`. When `+` or `{n}` finds fewer methods than required, the result is an uncertain warning. Whitespace inside a quantifier (`tick{1, 3}`) is accepted, a quantifier that allows no match (`{}`, `{0}`) is flagged as malformed, one whose maximum is below its minimum (`{3,1}`) requires the minimum as in Mixin, and regex selectors (`/pattern/`) are reported as skipped rather than missing. A `method = {"tick{2}", "jump"}` array whose selectors contain braces is read instead of being reported as empty, and a `method` value that is a constant or an expression such as `"a" + "b"` gets a warning that it could not be parsed instead of a check of a fragment of it.
17
+ - `validate-mixin` no longer reports these target members as missing: a `@Shadow` method written with Mixin's `shadow$` prefix or an explicit `prefix = "..."`, a name listed in `aliases`, a synthetic injector target such as the lambda body `lambda$tick$0`, and a synthetic `@Shadow`, `@Accessor` or `@Invoker` member such as an enum's `$VALUES` or an inner class's `this$0`. An explicit `prefix = ""` keeps the declared name, and a `@Shadow` field keeps its declared name because Mixin allows the prefix only on methods. Synthetic and bridge methods also count toward quantified selectors, as in Mixin.
18
+ - `validate-mixin` no longer reports a mixin as valid because `warningCategoryFilter` hid its only error. The filter now narrows `issues`, `structuredWarnings` and the `summary` counts only: `valid`, `validationStatus` and `quickSummary` are what the same call reports without the filter, `unfilteredSummary` keeps the true counts, and the automatic retry with Maven-first mapping sources is decided as if no category filter were set. `toolHealth.overallHealthy` now reflects only the mappings the request needs: Mojang mappings for `mojang`, Tiny mappings for `yarn` and `intermediary`, and only the game jar for `obfuscated`. It reported unhealthy whenever Mojang mappings were missing, which lowered the confidence score and turned descriptor mismatches into warnings even for requests that never use them.
19
+ - `validate-mixin` and `validate-project` validate a nested mixin listed in a mixins config as `Outer$Inner` (or `Outer$Mid$Inner`): the class is read from `Outer.java`, and only its own members, `@Mixin` targets and priority are checked. Such an entry was reported as a missing `Outer$Inner.java`. A nested name that `Outer.java` does not declare is reported as a processing error naming the class and the file; a class declared inside a method cannot be addressed this way. Every config entry now validates only its own class, so an entry `Outer` no longer checks the nested mixins declared inside `Outer.java`, whether or not they are listed as their own entries.
20
+ - `validate-mixin` in config mode rejects a config that is not a JSON object, whose `mixins`, `client` or `server` is not an array of strings, or whose `package` is not a string, with `ERR_INVALID_INPUT` naming the config path and the offending key. Such configs crashed with `ERR_INTERNAL` or were silently misread.
21
+ - `validate-project` with `task: "mixin"` or `task: "project-summary"` reports `status: "partial"` when mixin inputs could not be loaded, counts them under `counts.processingErrors`, and says how many could not be processed (in the headline for `project-summary`). Both reported `status: "ok"`, and `task: "mixin"` said `Validated N mixin input(s).`
22
+ - `validate-project` with `task: "access-widener"` returns the invalid entries in its `issues` block under `detail: "standard"` or `"full"`, or with `include: ["issues"]`. The block was always omitted, so the counts said `invalid: 1` with no way to learn which line failed.
23
+ - `validate-access-widener` and `validate-project` with `task: "access-widener"` report a file invalid when it breaks the Fabric access-widener grammar: `mutable` on a class or method, `extendable` on a field, a `transitive-` entry under a `v1` header, an unsupported header version, a missing header, or a line before the header that is neither blank nor a comment. Such files were reported valid whenever their symbols existed. `validate-access-widener`, `validate-access-transformer` and their `validate-project` tasks also report a file invalid when it contains a line they cannot understand, naming the line; a file whose only line was malformed was reported valid with zero entries. A file with a `classTweaker` header is reported as an unsupported format rather than an invalid access widener: `ERR_INVALID_INPUT` from `validate-access-widener` and from `validate-project` with `task: "access-widener"`, and a processing error with a warning in `validate-project` `project-summary`.
24
+ - `verify-mixin-target` suggests `@Shadow` for a public or protected field, since mixin code needs `@Shadow` to reference any target field; it suggested `@Inject-only`, which applies only to methods. For a visible method, the reasoning says no `@Invoker` is needed and that `@Inject` hooks it while `@Shadow` calls it. The `@Shadow` example for a static field keeps `static`. A package-private target is named `package-private` in the reasoning text instead of private; `suggestedAnnotation` is unchanged. The rule matrix in `docs/tool-reference.md` lists fields and methods separately and lists package-private alongside private.
25
+ - Mapping lookups return only symbols of the kind that was asked for. A field whose name equals a same-class method's name, such as a record component and its accessor or a reused obfuscated name, was remapped to the method's mapped name with no warning, which reached `get-class-members`, `validate-mixin`, `validate-access-widener` and `diff-class-signatures`. A `find-mapping` field query whose name equals a class's simple name resolved to the class.
26
+ - A mapped method's `symbol` string and `descriptor` are now in the same namespace. After the descriptor was translated, `symbol` still embedded the source-namespace descriptor.
27
+ - `find-mapping` with `signatureMode: "exact"` and `resolve-method-mapping-exact` resolve a Mojang-to-Yarn method whose mappings are split across separate per-namespace files (a Mojang ProGuard file plus `official → intermediary` and `intermediary → named` Tiny files). They answered `not_found`.
28
+ - A pre-release Minecraft jar is no longer served as an exact match for the release it precedes: `1.21` no longer matches a `1.21-rc1` artifact and `26.1` no longer matches `26.1-pre-1`, so `strictVersion: true` refuses the substitution and the served version is flagged as approximate instead of being accepted silently.
29
+ - Runtime jars for Minecraft 26.1 and later are recognized by version, so `validate-access-widener`, `validate-access-transformer` and `resolve-artifact` provenance report the version actually served, with `requestedVersion` and the approximation flag, instead of echoing the requested one. A jar named `minecraft-merged-26.2-pre-6.jar` is read as its full version, `26.2-pre-6`. Neither a loader's own version number, such as Forge `47.3.0`, nor a version in the name of a directory above the build tool's own folders, such as a project checked out as `mymod-1.21`, is taken for the jar's Minecraft version; that directory name made the access-widener and access-transformer checks look up another version's mappings and report a false approximation.
30
+ - Workspace detection ignores commented-out Gradle lines. `resolve-workspace-symbol` and the other workspace-aware tools skip commented-out `mappings` declarations in `build.gradle` and `build.gradle.kts`, so a Yarn project that keeps a `// mappings loom.officialMojangMappings()` line is detected as Yarn instead of refused with conflicting mappings. Kotlin nested block comments, Kotlin raw strings and Groovy `$/…/$` strings are read by their own rules; text inside ordinary string literals, such as a repository URL, is still treated as code. Loader detection for workspace targets and `validate-project` no longer counts a `// id 'fabric-loom'` line or an `accessTransformer` mention inside a block comment as evidence of a loader.
31
+ - A workspace's detected Minecraft version, mapping and loader are refreshed as soon as `gradle.properties`, the project's root `build.gradle` or `build.gradle.kts`, or a subproject build script or mod descriptor that declared a mapping or loader changes, instead of being served from the 5-minute workspace cache. Other subproject build scripts are still picked up when the cache expires; the tool reference lists the exact files.
32
+ - Windows-style and WSL paths are translated on WSL in three more places. `projectPath` on `target.kind: "workspace"` and `"dependency"` targets and with `preferProjectVersion` is accepted as on the other routes; `C:\dev\mymod` on a WSL host failed to find `gradle.properties`. A `GRADLE_USER_HOME` written as `C:\...` or `\\wsl$\...` is used wherever the Gradle cache is searched; resolving a Maven coordinate from the Gradle cache and inferring a `target.kind: "dependency"` version found nothing. The recovery advice after a mapping could not be applied suggests retrying against the workspace for a Windows or UNC `projectPath`, as it already did for a Linux path, instead of generic advice.
33
+ - Text search (`search-class-source`, `inspect-minecraft` with `task: "search"`) finds a query whose words are full-text search operators, such as `BooleanOp.AND`. Such a query silently returned no hits, and a query like `a NOT b` quietly searched for something else.
34
+ - Text and path search (`search-class-source`, `inspect-minecraft` with `task: "search"`) with `scope.packagePrefix`, or with a `scope.fileGlob` that starts with a literal directory such as `net/minecraft/world/**`, finds in-scope matches even when many out-of-scope files also match; those matches could come back as no hits at all. A glob without a literal directory prefix still filters only the bounded set of candidates a search checks; the tool reference describes that limit and how to search past it. A `search-class-source` text or path search that returns fewer hits than `limit` while the index matched more files than the search checks in one pass (500 for `match: "exact"`/`"prefix"`, 1,000 by default for `"contains"`) now says so in `warnings`, suggesting `queryMode: "literal"` for text, `match: "regex"` for paths, or a narrower `packagePrefix` or literal-prefixed `fileGlob`. It returned the short page silently; the hits and their order are unchanged.
35
+ - `get-signature` and every tool built on it report a truncated or corrupted class file, including a bad magic number, an unsupported constant-pool tag, a class cut short inside its last attribute, or a malformed member descriptor in the bytecode, as `ERR_CLASS_NOT_FOUND` naming the class and jar. These cases answered `ERR_INTERNAL`, `ERR_INVALID_INPUT` (blaming the caller for a third-party jar's content), or parsed the truncated class as if it were complete.
36
+ - `get-class-members` honors `includeAnnotations: true`. The option was accepted but had no effect when called through the MCP tool, so member annotations such as `@Deprecated` were never returned.
37
+ - `find-class` reports `nested: true` and `enclosingClass` for an inner class looked up by a qualified name such as `a.b.Outer.Inner`, and for one found through a Jar-in-Jar shell jar. It already did so for an unqualified lookup and for a class found in the artifact's own index.
38
+ - An unusable file path reports `ERR_INVALID_INPUT`, naming the path and the OS error code, instead of `ERR_INTERNAL`: in `get-class-source` and `get-mod-class-source` when the `outputFile` path cannot be written because its parent directory does not exist, it is a directory, or it is not writable, and in `validate-project` with a `task` of `access-widener` or `access-transformer` and a `subject.input.mode` of `path` when the file cannot be read.
39
+ - Recovery suggestions now propose calls that can succeed. When a `mapping` request on a `target.kind: "jar"` source jar is refused because the jar's Minecraft version is unknown, the advice is a fillable `resolve-artifact` example with a version placeholder; it fed the jar path back as a Minecraft version. `ERR_WORKSPACE_VERSION_UNRESOLVED` carries `exampleCalls` with a `target.kind: "version"` retry to fill in; it carried no retry suggestion at all, although the tool reference said it did, and the tool reference now also names the `exampleCalls` field for `ERR_DEPENDENCY_VERSION_UNRESOLVED`. `inspect-minecraft` with `task: "versions"` omits its follow-up suggestion when the version manifest lists no release, instead of suggesting a call with no version.
40
+ - `manage-cache` with `action: "rebuild"` reports that it rebuilt nothing and points at `action: "delete"` instead, since deleted entries are rebuilt from source the next time they are needed. It reported `status: "changed"` and a count of rebuilt entries without rebuilding anything, and the preview offered an apply follow-up that would change nothing. The README tool table no longer lists rebuild among the cache operations.
41
+ - `manage-cache` with `action: "verify"` no longer reports every artifact-index entry as `in_use` while the server has its cache database open, so artifact-index entries show their real health (`healthy`, `stale`, `partial` or `orphaned`) and a `selector.status` other than `in_use` matches them, including for `delete` and `prune`. Deleting or pruning file caches no longer opens and integrity-checks the whole cache database when no artifact-index entry was selected.
42
+ - `remap-mod-jar` without `outputJar` returns the same output path, next to the input jar, whether or not the remapped jar was already cached. A cached result was returned from the server's internal cache directory instead.
43
+ - Detecting whether a mod jar uses intermediary or Mojang names no longer falls back to a low-confidence result for a jar that repeats an entry name.
44
+ - When the server's worker process restarts and the client re-sends `initialize`, tool calls that queued in the meantime are no longer delivered to the replacement worker ahead of the handshake. The re-sent `initialize` now goes first, and the queued calls are released once it is answered.
45
+ - When the server's worker process crashes or is killed from outside on Linux or macOS, the processes it started, such as a Java decompiler, are stopped with it instead of being left running. On Windows, processes started by a worker that has already exited are still not cleaned up. A Java subprocess (the decompiler and the other Java helpers) that is stopped for exceeding its time limit but ignores the stop request is now force-killed after 3 seconds; it was left running.
46
+ - A failed Minecraft version-manifest request releases its response body instead of holding the connection until garbage collection, and a nested (Jar-in-Jar) jar that cannot be moved into the cache after extraction no longer leaves its temporary file there.
47
+
48
+ ### Documentation
49
+
50
+ - The tool reference documents a known issue: `get-class-source` with `mode: "metadata"` returns a generated outline of the class's symbols, yet reports `returnedRange` as the whole source file and, unless `maxChars` cut the outline, `truncated: false`. Do not use those values to address lines of the outline; use `mode: "snippet"` or `"full"` for line numbers. A future major release will replace them with fields specific to the metadata mode.
51
+
52
+ ## [7.1.0] - 2026-09-19
53
+
54
+ ### Added
55
+
56
+ - `batch-class-source` and `batch-class-members` accept `target: { "kind": "artifact", "artifactId": "..." }`, the same target `get-class-source` and `get-class-members` accept, so a batch can reuse an artifact that an earlier call already resolved instead of resolving it again from its jar, version, or coordinate. This target was previously rejected with `ERR_INVALID_INPUT`. With it, `summary.sharedArtifactProvenance` is omitted, and an unknown `artifactId` fails each entry with `ERR_SOURCE_NOT_FOUND` rather than failing the whole batch.
57
+ - `compare-minecraft` with `task="migration-overview"` reports which libraries shipped with Minecraft itself were added or removed between the two versions, such as LWJGL's GLFW binding being replaced by SDL, which class and registry diffs do not show. At `detail: "standard"` or `"full"`, the new `migration.libraries` block lists added and removed libraries as `group:artifact:version` and counts libraries whose version changed; `summary.counts.librariesAdded` and `librariesRemoved` carry the same counts at every detail level. A version bump or a per-platform native jar of a library already present is not reported as an addition or removal. When the version details cannot be fetched, or not within 5 seconds, the block is left out with a warning and the rest of the result is unchanged.
58
+ - `provenance.unobfuscatedRuntime: true` marks an artifact whose runtime ships Mojang names (Minecraft 26.1+): version targets, Minecraft runtime coordinates, and jar targets proven to be a 26.1+ runtime jar. `resolve-artifact`, `get-class-source`, `get-class-members`, and the batch tools' `sharedArtifactProvenance` carry it; `provenance` itself is returned only with `include: ["provenance"]` or a fuller `detail` level. `mappingApplied` still reports the mapping you asked for, so `"obfuscated"` on such an artifact means the names as shipped, which are already Mojang names.
59
+
60
+ ### Changed
61
+
62
+ - `validate-project` with `task="project-summary"` infers an omitted `version` from the project's `gradle.properties` (`minecraft_version`, `mc_version`, or `minecraftVersion`) and names the inferred version in `warnings`; it previously returned `status: "blocked"`. To keep the previous behavior, pass `preferProjectVersion: false`: a call without `version` then returns `blocked`, and the retry suggestions of an invalid-input error keep that explicit `false`. When files are found but no version can be inferred, the blocked reply asks for an explicit `version` instead of suggesting the same call again.
63
+
64
+ ### Performance
65
+
66
+ - The first tool call after the server starts no longer stalls on a full consistency check of the local cache database. The check on open now uses SQLite's `quick_check` instead of `integrity_check`: on a 3.6 GB cache it takes about 2 s instead of about 19 s. A damaged cache file is still detected at startup, backed up and rebuilt. Rarer damage that the lighter check misses can surface later during a tool call; a call that hits it directly now fails with `ERR_DB_FAILURE` and restart guidance instead of `ERR_INTERNAL`, and the next start runs the full `integrity_check`. If the server cannot schedule that check, the error explains how to reset the cache by hand. A call where the damage surfaces inside another step may still report that step's own error.
67
+
68
+ ### Fixed
69
+
70
+ - `validate-project` with `task="project-summary"`: a run that finds no Mixin configs, access wideners, or access transformers now reports `Nothing to validate: …`, naming only the file kinds it searched, with a warning that nothing was validated, instead of "Validated 0 mixin config(s), 0 access widener(s), and 0 access transformer(s)." `status` stays `"ok"`.
71
+ - Minecraft 26.x snapshot, pre-release and release-candidate ids such as `26.3-snapshot-6`, `26.2-pre-6`, `26.2-rc-2` and `26.1.2-rc-1` are recognized as unobfuscated. They were treated as obfuscated legacy versions, so `mapping: "mojang"` was refused on them, `intermediary` and `yarn` did not fall back to `obfuscated`, and symbol checks did not use the runtime jar. Previously only weekly ids such as `26w14a` and unhyphenated forms such as `26.1-rc1`, which Mojang does not publish, were recognized.
72
+ - `mapping: "mojang"` is accepted on a `target.kind: "jar"` that points at a Minecraft 26.1+ runtime jar, such as a jar from the Loom cache; it was refused with `ERR_MAPPING_NOT_APPLIED` and a hint to switch to `obfuscated`. A jar counts as a 26.1+ runtime jar when it has a top-level `version.json` with a 26.1+ `id`, contains `net/minecraft/SharedConstants.class`, and has no `.java` sources. Such a jar reports that `id` as its version and falls back from `intermediary` or `yarn` to `obfuscated` like a version target. A jar indexed by an earlier release has its version recorded on its next resolve, after which calls by `artifactId` accept `mojang` as well.
73
+ - On Minecraft 26.1+ artifacts, class-not-found errors from `get-class-source` and `get-class-members`, and `find-class` misses, lead with the nearest class-name suggestion instead of telling you to retry with `mapping="mojang"`. When `mapping: "mojang"` is refused on a jar that is not a proven runtime jar, the error explains that `mapping: "obfuscated"` reads the names as shipped, and in a 26.1+ project it no longer suggests repeating the refused request. Hints for 1.x versions are unchanged.
74
+ - `resolve-workspace-symbol` and `analyze-symbol` with `task="workspace"` resolve symbols in a Minecraft 26.1+ Loom project that declares no `mappings` line by checking the name against the runtime jar; they returned `mapping_unavailable` (`partial` in `analyze-symbol`). A class that is not in the Minecraft jar returns `not_found`, and a lookup that could not be checked returns `mapping_unavailable` with the reason. A path without a Gradle build script behaves as before.
75
+ - On Minecraft 26.1+, `compare-minecraft` with `task="class-diff"` (and `diff-class-signatures`) and `trace-symbol-lifecycle` with `mapping: "mojang"` no longer mark every member as unmapped with "Could not remap" or "Could not map" warnings.
76
+ - `check-symbol-exists` on Minecraft 26.1+ finds constructors: a method query named `<init>` with an existing descriptor resolves instead of returning `not_found`.
77
+
78
+ ### Documentation
79
+
80
+ - The tool reference documents a known issue: on Minecraft 26.1+, responses label the runtime's Mojang names `obfuscated`, for example `mappingApplied: "obfuscated"`, whenever a request omits `mapping` or asks for `obfuscated`. The label stays until a future major release corrects it. Until then, `provenance.unobfuscatedRuntime: true` marks an artifact whose names are Mojang names, and `mapping: "mojang"` returns the same names labelled `mojang`.
81
+
10
82
  ## [7.0.0] - 2026-09-13
11
83
 
12
84
  ### Fixed
package/README.md CHANGED
@@ -170,7 +170,8 @@ These notes cover high-frequency decisions during onboarding. For the full pitfa
170
170
  - `trace-symbol-lifecycle` expects `Class.method` in `symbol`. Keep exact overload matching in the separate `descriptor` field.
171
171
  - For unobfuscated releases such as `26.1+`, `check-symbol-exists` and `analyze-symbol task="exists"` validate `mojang` lookups against runtime bytecode when no mapping graph exists, and return `mapping_unavailable` when the runtime JAR itself is unreachable.
172
172
  - `analyze-mod` and `validate-project` require structured `subject` objects and canonical `include` groups; stale string-subject or domain-include payloads return `ERR_INVALID_INPUT` with a retryable `suggestedCall`.
173
- - `validate-project task="project-summary"` propagates `preferProjectVersion=true` across discovered Mixin, Access Widener, and Access Transformer checks. If no version can be resolved from the request or `gradle.properties`, the summary returns recovery guidance instead of guessing.
173
+ - `validate-project task="project-summary"` infers an omitted `version` from `gradle.properties` and names the inferred version in `warnings`; pass `preferProjectVersion: false` to turn inference off, in which case a call without `version` returns `status: "blocked"`. The resolved version is passed to every discovered Mixin, Access Widener, and Access Transformer check. If files were discovered but no version can be inferred, the summary returns `status: "blocked"` with a retry that asks for an explicit `version` instead of guessing.
174
+ - When `validate-project task="project-summary"` discovers no files, `status` stays `"ok"` but the headline reads `Nothing to validate: ...` and `warnings` states that nothing was validated, so an `"ok"` status alone does not mean any file passed.
174
175
  - `validate-mixin` and `validate-project` keep `mapping-health` lightweight for `obfuscated` and `mojang` validation, avoiding full Tiny mapping graph loads unless `intermediary` or `yarn` namespaces are requested.
175
176
  - `validate-project task="project-summary"` uses a lightweight artifact probe for `tasks["minecraft.artifact.resolved"]`; it does not decompile Minecraft or rebuild the source index just to report per-probe status. It does read the runtime jar's bytes to derive the `artifactId` it reports, and repeat probes of the same jar in one process reuse that digest. Set `VALIDATE_PROJECT_TASKS_OFF=1` to omit the additive `tasks` field.
176
177
  - `validate-project` has a supervisor-owned end-to-end deadline of 120 seconds, including queue time. Set `MCP_VALIDATE_PROJECT_TIMEOUT_MS` to an ASCII-decimal value from `10000` through `600000` to override it. A timeout returns `ERR_TOOL_TIMEOUT`; a running timeout restarts the isolated worker before queued calls resume, while a queue timeout leaves the current worker untouched.
@@ -278,7 +279,7 @@ Start with these top-level workflow tools unless you already know the exact spec
278
279
  | `compare-minecraft` | Compare version pairs, class diffs, registry diffs, and migration-oriented summaries |
279
280
  | `analyze-mod` | Summarize mod metadata, decompile and search mod code, inspect class source, read class members from bytecode, and preview or apply remaps |
280
281
  | `validate-project` | Summarize workspaces and run direct Mixin, Access Widener, or Access Transformer validation |
281
- | `manage-cache` | List, verify, and preview or apply cache cleanup and rebuild operations |
282
+ | `manage-cache` | List, verify, and preview or apply cache cleanup operations |
282
283
  <!-- END GENERATED TOOL TABLE: top-level-workflow-tools -->
283
284
 
284
285
  ### Source Exploration
@@ -301,7 +302,7 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading o
301
302
 
302
303
  `find-class` accepts either an `artifactId` or the shared object `target` shape. For a workspace-relative dependency, pass `target: { kind: "dependency", group, name, versionFromProject: true }` with top-level `projectPath`. Fabric-style umbrella JARs are searched through their nested `.class` inventories; a top-level class match can then be passed to `get-class-source` or `get-class-members`, which resolve the containing nested JAR. Dotted inner-class matches are also readable through `get-class-source`. An empty result still means that the requested class name is absent from the resolved dependency version.
303
304
 
304
- For unobfuscated releases such as `26.1+`, `mapping="mojang"` uses the runtime/decompile path directly and skips Loom source-jar discovery, while `intermediary` and `yarn` fall back to `obfuscated` with a warning.
305
+ For unobfuscated releases such as `26.1+`, `obfuscated` names the runtime jar's names as shipped, which are already Mojang names: both `obfuscated` and `mojang` are accepted, `mappingApplied` reports the label you asked for, and `provenance.unobfuscatedRuntime: true` marks the artifact. `mapping="mojang"` uses the runtime/decompile path directly and skips Loom source-jar discovery, while `intermediary` and `yarn` fall back to `obfuscated` with a warning. This applies to version targets, Minecraft runtime coordinates, and JAR targets whose own contents (a top-level `version.json` with a 26.1+ `id`, `net/minecraft/SharedConstants.class`, and no `.java` sources) prove a 26.1+ Minecraft runtime jar; see [docs/tool-reference.md → Lookup Rules](docs/tool-reference.md#lookup-rules). Labelling these Mojang names `obfuscated` is a known issue, kept unchanged for compatibility until a future major release corrects it; see [docs/tool-reference.md → Known issue: obfuscated label on unobfuscated runtimes](docs/tool-reference.md#known-issue-obfuscated-label-on-unobfuscated-runtimes).
305
306
 
306
307
  ### Version Comparison & Symbol Tracking
307
308
 
@@ -14,5 +14,13 @@ export type AccessTransformerEntry = {
14
14
  export type ParsedAccessTransformer = {
15
15
  entries: AccessTransformerEntry[];
16
16
  parseWarnings: string[];
17
+ /**
18
+ * Count of lines the parser had to drop for malformed syntax (unsupported
19
+ * access declaration, incomplete entry, missing member name/descriptor). A
20
+ * caller-visible validator uses this to force its verdict invalid even when
21
+ * `entries` is empty. Optional (defaults to 0) so hand-built
22
+ * `ParsedAccessTransformer` fixtures that predate this field keep working.
23
+ */
24
+ rejectedEntryCount?: number;
17
25
  };
18
26
  export declare function parseAccessTransformer(content: string): ParsedAccessTransformer;
@@ -50,6 +50,7 @@ export function parseAccessTransformer(content) {
50
50
  const entries = [];
51
51
  const parseWarnings = [];
52
52
  const lines = content.split(/\r?\n/);
53
+ let rejectedEntryCount = 0;
53
54
  for (let index = 0; index < lines.length; index++) {
54
55
  const lineNumber = index + 1;
55
56
  const rawLine = (lines[index] ?? "").trim();
@@ -63,25 +64,30 @@ export function parseAccessTransformer(content) {
63
64
  const parts = withoutComment.split(/\s+/);
64
65
  if (parts.length < 2) {
65
66
  parseWarnings.push(`Line ${lineNumber}: Incomplete access transformer entry "${withoutComment}".`);
67
+ rejectedEntryCount++;
66
68
  continue;
67
69
  }
68
70
  const declaration = parseAccessDeclaration(parts[0] ?? "");
69
71
  if (!declaration) {
70
72
  parseWarnings.push(`Line ${lineNumber}: Unsupported access declaration "${parts[0]}".`);
73
+ rejectedEntryCount++;
71
74
  continue;
72
75
  }
73
76
  const owner = parts[1] ?? "";
74
77
  if (!owner) {
75
78
  parseWarnings.push(`Line ${lineNumber}: Incomplete access transformer entry "${withoutComment}".`);
79
+ rejectedEntryCount++;
76
80
  continue;
77
81
  }
78
82
  const member = splitMemberToken(parts.slice(2));
79
83
  if (member.targetKind === "method" && (!member.name || !member.descriptor)) {
80
84
  parseWarnings.push(`Line ${lineNumber}: Method entry requires a method name and JVM descriptor.`);
85
+ rejectedEntryCount++;
81
86
  continue;
82
87
  }
83
88
  if ((member.targetKind === "field" || member.targetKind === "method") && !member.name) {
84
89
  parseWarnings.push(`Line ${lineNumber}: Member entry requires a target name.`);
90
+ rejectedEntryCount++;
85
91
  continue;
86
92
  }
87
93
  const target = member.targetKind === "class"
@@ -100,7 +106,8 @@ export function parseAccessTransformer(content) {
100
106
  }
101
107
  return {
102
108
  entries,
103
- parseWarnings
109
+ parseWarnings,
110
+ rejectedEntryCount
104
111
  };
105
112
  }
106
113
  //# sourceMappingURL=access-transformer-parser.js.map
@@ -21,5 +21,22 @@ export type ParsedAccessWidener = {
21
21
  namespace: string;
22
22
  entries: AccessWidenerEntry[];
23
23
  parseWarnings: string[];
24
+ /**
25
+ * Count of entry lines the parser had to drop for malformed syntax (unknown
26
+ * kind/target kind, incomplete entry, missing descriptor), plus any
27
+ * non-comment line preceding the header. Excludes the header-line warnings
28
+ * of a file that has no header at all. A caller-visible validator
29
+ * uses this to force its verdict invalid even when `entries` is empty.
30
+ * Optional (defaults to 0) so hand-built `ParsedAccessWidener` fixtures
31
+ * that predate this field keep working.
32
+ */
33
+ rejectedEntryCount?: number;
34
+ /**
35
+ * The line number the `accessWidener <version> <namespace>` header was
36
+ * found on, when a syntactically well-formed header line was found (set
37
+ * even when its version is unsupported). Undefined when no header line
38
+ * matched the expected format at all; callers fall back to line 1.
39
+ */
40
+ headerLine?: number;
24
41
  };
25
42
  export declare function parseAccessWidener(content: string): ParsedAccessWidener;
@@ -14,6 +14,10 @@ export function parseAccessWidener(content) {
14
14
  const entries = [];
15
15
  let headerVersion = "";
16
16
  let namespace = "";
17
+ let rejectedEntryCount = 0;
18
+ let headerLine;
19
+ // Fabric requires the header first, so lines before a later header are rejected once it appears.
20
+ let preHeaderLineCount = 0;
17
21
  for (let i = 0; i < lines.length; i++) {
18
22
  const raw = lines[i].trim();
19
23
  const lineNum = i + 1;
@@ -26,9 +30,12 @@ export function parseAccessWidener(content) {
26
30
  if (parts[0] === "accessWidener" && parts.length >= 3) {
27
31
  headerVersion = parts[1];
28
32
  namespace = parts[2];
33
+ headerLine = lineNum;
34
+ rejectedEntryCount += preHeaderLineCount;
29
35
  }
30
36
  else {
31
37
  parseWarnings.push(`Line ${lineNum}: Expected accessWidener header, got: "${raw}"`);
38
+ preHeaderLineCount++;
32
39
  }
33
40
  continue;
34
41
  }
@@ -36,6 +43,7 @@ export function parseAccessWidener(content) {
36
43
  const parts = raw.split(/\s+/);
37
44
  if (parts.length < 3) {
38
45
  parseWarnings.push(`Line ${lineNum}: Incomplete entry: "${raw}"`);
46
+ rejectedEntryCount++;
39
47
  continue;
40
48
  }
41
49
  const kind = parts[0];
@@ -44,10 +52,12 @@ export function parseAccessWidener(content) {
44
52
  const baseKind = transitive ? kind.slice("transitive-".length) : kind;
45
53
  if (!VALID_KINDS.has(baseKind)) {
46
54
  parseWarnings.push(`Line ${lineNum}: Unknown access kind "${kind}".`);
55
+ rejectedEntryCount++;
47
56
  continue;
48
57
  }
49
58
  if (!VALID_TARGET_KINDS.has(targetKind)) {
50
59
  parseWarnings.push(`Line ${lineNum}: Unknown target kind "${targetKind}".`);
60
+ rejectedEntryCount++;
51
61
  continue;
52
62
  }
53
63
  const validKind = baseKind;
@@ -70,11 +80,12 @@ export function parseAccessWidener(content) {
70
80
  }
71
81
  else {
72
82
  parseWarnings.push(`Line ${lineNum}: ${validTargetKind} entry requires owner, name, and descriptor.`);
83
+ rejectedEntryCount++;
73
84
  }
74
85
  }
75
86
  if (!headerVersion) {
76
87
  parseWarnings.push("Missing accessWidener header.");
77
88
  }
78
- return { headerVersion, namespace, entries, parseWarnings };
89
+ return { headerVersion, namespace, entries, parseWarnings, rejectedEntryCount, headerLine };
79
90
  }
80
91
  //# sourceMappingURL=access-widener-parser.js.map
@@ -414,7 +414,6 @@ async function artifactIndexEntries(config) {
414
414
  ON artifact_content_bytes.artifact_id = artifacts.artifact_id
415
415
  ORDER BY artifacts.updated_at DESC
416
416
  `).all();
417
- const dbInUse = existsSync(`${config.sqlitePath}-wal`) || existsSync(`${config.sqlitePath}-journal`);
418
417
  return rows.map((row) => {
419
418
  const qualityFlags = parseStringArray(row.quality_flags_json);
420
419
  const binaryJarPath = row.binary_jar_path ?? undefined;
@@ -437,7 +436,9 @@ async function artifactIndexEntries(config) {
437
436
  projectPath: inferProjectPath(binaryJarPath ?? sourceJarPath, config.pathRuntimeInfo),
438
437
  scope: inferScope(binaryJarPath, sourceJarPath, ...qualityFlags) ?? "vanilla",
439
438
  partial: qualityFlags.some((flag) => flag.includes("partial")),
440
- inUse: dbInUse
439
+ // A WAL file only shows that a connection (usually ours) has the database open.
440
+ // SQLite transactions already serialize row deletes; it says nothing about this entry.
441
+ inUse: false
441
442
  }
442
443
  };
443
444
  });
@@ -722,7 +723,11 @@ export function createCacheRegistry(config) {
722
723
  downloadIdentities.set(entry.path, await readDownloadEntryIdentity(entry.path));
723
724
  }
724
725
  }
725
- const db = openDb(config);
726
+ // Opening the index runs a PRAGMA quick_check over the whole file, so
727
+ // only pay for it when a row of it is actually going to be deleted.
728
+ const db = entries.some((entry) => entry.cacheKind === "artifact-index")
729
+ ? openDb(config)
730
+ : undefined;
726
731
  try {
727
732
  for (const entry of entries) {
728
733
  if (entry.cacheKind === "artifact-index") {
@@ -791,10 +796,18 @@ export function createCacheRegistry(config) {
791
796
  return this.deleteEntries(input);
792
797
  },
793
798
  async rebuildEntries(input) {
794
- const entries = await collectEntries(input.cacheKinds, input.selector);
799
+ // No cache kind knows how to rebuild itself from here: the previous
800
+ // implementation only counted the entries a selector matched and reported
801
+ // that count as "rebuilt", so apply mode claimed a change it never made.
802
+ // Validate the selector the way every other action does - the inventory
803
+ // itself would be thrown away - then say what actually happened.
804
+ prepareSelector(input.selector, config.pathRuntimeInfo);
805
+ const selectedKinds = input.cacheKinds?.length ? input.cacheKinds : [...PUBLIC_CACHE_KINDS];
795
806
  return {
796
- rebuiltEntries: entries.length,
797
- warnings: []
807
+ rebuiltEntries: 0,
808
+ warnings: [
809
+ `rebuild is not implemented for cache kind(s): ${selectedKinds.join(", ")}. Nothing was rebuilt. Delete the matching entries instead (manage-cache action="delete"); each one is rebuilt from source the next time it is needed.`
810
+ ]
798
811
  };
799
812
  }
800
813
  };
@@ -40,9 +40,9 @@ export declare const analyzeModShape: {
40
40
  apply: "apply";
41
41
  }>>;
42
42
  detail: z.ZodOptional<z.ZodEnum<{
43
+ full: "full";
43
44
  summary: "summary";
44
45
  standard: "standard";
45
- full: "full";
46
46
  }>>;
47
47
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
48
48
  [x: string]: string;
@@ -88,9 +88,9 @@ export declare const analyzeModSchema: z.ZodObject<{
88
88
  apply: "apply";
89
89
  }>>;
90
90
  detail: z.ZodOptional<z.ZodEnum<{
91
+ full: "full";
91
92
  summary: "summary";
92
93
  standard: "standard";
93
- full: "full";
94
94
  }>>;
95
95
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
96
96
  [x: string]: string;
@@ -62,9 +62,9 @@ export declare const analyzeSymbolShape: {
62
62
  maxRows: z.ZodOptional<z.ZodNumber>;
63
63
  maxCandidates: z.ZodDefault<z.ZodNumber>;
64
64
  detail: z.ZodOptional<z.ZodEnum<{
65
+ full: "full";
65
66
  summary: "summary";
66
67
  standard: "standard";
67
- full: "full";
68
68
  }>>;
69
69
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
70
70
  [x: string]: string;
@@ -132,9 +132,9 @@ export declare const analyzeSymbolSchema: z.ZodObject<{
132
132
  maxRows: z.ZodOptional<z.ZodNumber>;
133
133
  maxCandidates: z.ZodDefault<z.ZodNumber>;
134
134
  detail: z.ZodOptional<z.ZodEnum<{
135
+ full: "full";
135
136
  summary: "summary";
136
137
  standard: "standard";
137
- full: "full";
138
138
  }>>;
139
139
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
140
140
  [x: string]: string;
@@ -1,6 +1,7 @@
1
1
  import { type ResponseDetailLevel } from "../response-utils.js";
2
2
  import type { GetClassMembersInput, GetClassMembersOutput, ResolveArtifactInput, ResolveArtifactOutput } from "../source-service.js";
3
- import type { ArtifactScope, MappingSourcePriority, ResolveArtifactTargetInput, SourceMapping } from "../types.js";
3
+ import type { ArtifactScope, MappingSourcePriority, SourceMapping } from "../types.js";
4
+ import type { SourceLookupTargetInput } from "../tool-schemas.js";
4
5
  import type { MemberProjection } from "../source/class-source/members-builder.js";
5
6
  import { type BatchOutput } from "./batch-runner.js";
6
7
  export type BatchClassMembersDeps = {
@@ -16,7 +17,7 @@ export type BatchClassMembersEntry = {
16
17
  maxMembers?: number;
17
18
  };
18
19
  export type BatchClassMembersInput = {
19
- target: ResolveArtifactTargetInput;
20
+ target: SourceLookupTargetInput;
20
21
  mapping?: SourceMapping;
21
22
  sourcePriority?: MappingSourcePriority;
22
23
  allowDecompile?: boolean;
@@ -16,6 +16,16 @@ export class BatchClassMembersService {
16
16
  concurrency,
17
17
  failFast,
18
18
  resolveSharedArtifact: async () => {
19
+ // target.kind === "artifact" reuses an already-resolved artifactId,
20
+ // short-circuiting resolution exactly as get-class-members's own
21
+ // `kind:"artifact"` target does (src/index.ts normalizeSourceLookupTarget).
22
+ // No provenance is invented for a reused artifact: an unknown id is left
23
+ // to surface per-entry (the same SOURCE_NOT_FOUND getClassMembers raises
24
+ // for an unknown artifactId), since resolveSharedArtifact has no way to
25
+ // validate existence without calling deps.resolveArtifact.
26
+ if (input.target.kind === "artifact") {
27
+ return { artifactId: input.target.artifactId };
28
+ }
19
29
  const resolved = await this.deps.resolveArtifact({
20
30
  target: input.target,
21
31
  mapping: input.mapping,
@@ -49,12 +59,16 @@ export class BatchClassMembersService {
49
59
  const raw = (await this.deps.getClassMembers({
50
60
  artifactId: sharedArtifact.artifactId,
51
61
  // This artifactId is OURS, not the caller's: the shared target was
52
- // resolved above and every entry is dispatched by the result. Without
53
- // saying so, get-class-members reads the bare presence of an
54
- // artifactId as the caller having named the artifact, and reports a
55
- // missing binary jar as their mistake - once per entry - though
56
- // `target` here cannot name an artifact at all. Only a jar the caller
57
- // named themselves is genuinely their choice.
62
+ // resolved (or, for target.kind==="artifact", reused) above and every
63
+ // entry is dispatched by the result. Without saying so, get-class-members
64
+ // reads the bare presence of an artifactId as the caller having named
65
+ // the artifact, and reports a missing binary jar as their mistake - once
66
+ // per entry. A `kind:"artifact"` target is a resolved-id handle, not a
67
+ // caller choice either (mirrors resolveClassArtifactReference's
68
+ // artifactSelectedByFor in inspect-minecraft/handlers/class-members.ts):
69
+ // it says nothing about whether the artifact carries a binary jar and
70
+ // cannot be re-resolved into one that does. Only a jar the caller named
71
+ // themselves is genuinely their choice.
58
72
  artifactSelectedBy: input.target.kind === "jar" ? "caller" : "tool",
59
73
  className: entry.className,
60
74
  access: entry.access,
@@ -1,6 +1,7 @@
1
1
  import { type ResponseDetailLevel } from "../response-utils.js";
2
2
  import type { GetClassSourceInput, GetClassSourceOutput, ResolveArtifactInput, ResolveArtifactOutput } from "../source-service.js";
3
- import type { ArtifactScope, MappingSourcePriority, ResolveArtifactTargetInput, SourceMapping } from "../types.js";
3
+ import type { ArtifactScope, MappingSourcePriority, SourceMapping } from "../types.js";
4
+ import type { SourceLookupTargetInput } from "../tool-schemas.js";
4
5
  import { type BatchOutput } from "./batch-runner.js";
5
6
  type SourceMode = "metadata" | "snippet" | "full";
6
7
  export type BatchClassSourceEntry = {
@@ -13,7 +14,7 @@ export type BatchClassSourceEntry = {
13
14
  outputFile?: string;
14
15
  };
15
16
  export type BatchClassSourceInput = {
16
- target: ResolveArtifactTargetInput;
17
+ target: SourceLookupTargetInput;
17
18
  mapping?: SourceMapping;
18
19
  sourcePriority?: MappingSourcePriority;
19
20
  allowDecompile?: boolean;
@@ -16,6 +16,16 @@ export class BatchClassSourceService {
16
16
  concurrency,
17
17
  failFast,
18
18
  resolveSharedArtifact: async () => {
19
+ // target.kind === "artifact" reuses an already-resolved artifactId,
20
+ // short-circuiting resolution exactly as get-class-source's own
21
+ // `kind:"artifact"` target does (src/index.ts normalizeSourceLookupTarget).
22
+ // No provenance is invented for a reused artifact: an unknown id is left
23
+ // to surface per-entry (the same SOURCE_NOT_FOUND getClassSource raises
24
+ // for an unknown artifactId), since resolveSharedArtifact has no way to
25
+ // validate existence without calling deps.resolveArtifact.
26
+ if (input.target.kind === "artifact") {
27
+ return { artifactId: input.target.artifactId };
28
+ }
19
29
  const resolved = await this.deps.resolveArtifact({
20
30
  target: input.target,
21
31
  mapping: input.mapping,
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import type { DiffClassSignaturesOutput } from "../source-service.js";
3
- import type { CompareVersionsOutput } from "../version-diff-service.js";
3
+ import { type CompareVersionsOutput } from "../version-diff-service.js";
4
4
  import type { GetRegistryDataOutput } from "../registry-service.js";
5
5
  export declare const compareMinecraftShape: {
6
6
  task: z.ZodOptional<z.ZodEnum<{
@@ -38,9 +38,9 @@ export declare const compareMinecraftShape: {
38
38
  registry: z.ZodOptional<z.ZodString>;
39
39
  }, z.core.$strip>], "kind">;
40
40
  detail: z.ZodOptional<z.ZodEnum<{
41
+ full: "full";
41
42
  summary: "summary";
42
43
  standard: "standard";
43
- full: "full";
44
44
  }>>;
45
45
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
46
46
  [x: string]: string;
@@ -86,9 +86,9 @@ export declare const compareMinecraftSchema: z.ZodObject<{
86
86
  registry: z.ZodOptional<z.ZodString>;
87
87
  }, z.core.$strip>], "kind">;
88
88
  detail: z.ZodOptional<z.ZodEnum<{
89
+ full: "full";
89
90
  summary: "summary";
90
91
  standard: "standard";
91
- full: "full";
92
92
  }>>;
93
93
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
94
94
  [x: string]: string;
@@ -122,10 +122,33 @@ type CompareMinecraftDeps = {
122
122
  includeData?: boolean;
123
123
  maxEntriesPerRegistry?: number;
124
124
  }) => Promise<GetRegistryDataOutput>;
125
+ /**
126
+ * Raw `libraries[].name` coordinates for one version, used only by
127
+ * migration-overview to surface library swaps (e.g. LWJGL GLFW replaced by
128
+ * SDL) that a class/registry diff cannot see. Optional so existing callers
129
+ * that do not wire it keep working: migration-overview simply omits the
130
+ * libraries block when this is absent.
131
+ */
132
+ getVersionLibraries?: (input: {
133
+ version: string;
134
+ }) => Promise<string[]>;
135
+ };
136
+ export type CompareMinecraftOptions = {
137
+ /**
138
+ * Deadline in milliseconds for fetching both sides' library lists during
139
+ * migration-overview, so a restart with cached jars but no network access
140
+ * fails fast instead of waiting the full underlying fetch timeout. On
141
+ * expiry the libraries block is omitted and a warning is added; the
142
+ * in-flight fetch is left to continue in the background (it may still
143
+ * warm the version-detail cache). Test-only injection point — never read
144
+ * from an environment variable.
145
+ */
146
+ libraryDiffDeadlineMs?: number;
125
147
  };
126
148
  export declare class CompareMinecraftService {
127
149
  private readonly deps;
128
- constructor(deps: CompareMinecraftDeps);
150
+ private readonly libraryDiffDeadlineMs;
151
+ constructor(deps: CompareMinecraftDeps, options?: CompareMinecraftOptions);
129
152
  execute(input: CompareMinecraftInput): Promise<Record<string, unknown> & {
130
153
  warnings?: string[];
131
154
  }>;