@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.
- package/CHANGELOG.md +72 -0
- package/README.md +4 -3
- package/dist/access-transformer-parser.d.ts +8 -0
- package/dist/access-transformer-parser.js +8 -1
- package/dist/access-widener-parser.d.ts +17 -0
- package/dist/access-widener-parser.js +12 -1
- package/dist/cache-registry.js +19 -6
- package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-members-service.js +20 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-source-service.js +10 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
- package/dist/entry-tools/compare-minecraft-service.js +65 -4
- package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +15 -9
- package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.js +10 -14
- package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
- package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
- package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
- package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -8
- package/dist/index.js +38 -15
- package/dist/java-process.d.ts +1 -0
- package/dist/java-process.js +14 -0
- package/dist/mapping/lookup.js +16 -1
- package/dist/mapping-service.d.ts +14 -0
- package/dist/mapping-service.js +35 -15
- package/dist/minecraft-explorer-service.js +70 -8
- package/dist/mixin/access-validators.js +38 -2
- package/dist/mixin/annotation-validators.js +137 -43
- package/dist/mixin/parsed-validator.js +21 -7
- package/dist/mixin-parser.d.ts +52 -0
- package/dist/mixin-parser.js +709 -130
- package/dist/mod-decompile-service.js +11 -1
- package/dist/mod-remap-service.js +6 -6
- package/dist/nbt/java-nbt-codec.js +7 -1
- package/dist/source/access-validate.js +10 -0
- package/dist/source/artifact-resolver.d.ts +27 -3
- package/dist/source/artifact-resolver.js +235 -30
- package/dist/source/class-source/members-builder.d.ts +7 -0
- package/dist/source/class-source/members-builder.js +4 -1
- package/dist/source/class-source.d.ts +9 -2
- package/dist/source/class-source.js +186 -27
- package/dist/source/indexer.js +69 -1
- package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
- package/dist/source/lifecycle/mapping-helpers.js +29 -3
- package/dist/source/lifecycle/runtime-check.d.ts +25 -0
- package/dist/source/lifecycle/runtime-check.js +68 -39
- package/dist/source/nested-jars.d.ts +15 -1
- package/dist/source/nested-jars.js +14 -5
- package/dist/source/search.d.ts +10 -2
- package/dist/source/search.js +60 -13
- package/dist/source/symbol-resolver.js +88 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
- package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
- package/dist/source/validate-mixin.d.ts +5 -0
- package/dist/source/validate-mixin.js +136 -21
- package/dist/source/workspace-target.js +75 -7
- package/dist/source-jar-reader.d.ts +48 -1
- package/dist/source-jar-reader.js +93 -3
- package/dist/source-resolver.d.ts +7 -0
- package/dist/source-resolver.js +22 -14
- package/dist/source-service.d.ts +5 -0
- package/dist/source-service.js +7 -0
- package/dist/stdio-supervisor.d.ts +35 -1
- package/dist/stdio-supervisor.js +77 -2
- package/dist/storage/db.d.ts +62 -2
- package/dist/storage/db.js +181 -20
- package/dist/storage/files-repo.d.ts +7 -0
- package/dist/storage/files-repo.js +17 -4
- package/dist/storage/sqlite.d.ts +31 -1
- package/dist/storage/sqlite.js +125 -16
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +2 -1
- package/dist/tool-guidance.js +4 -1
- package/dist/tool-schemas.d.ts +64 -52
- package/dist/tool-schemas.js +9 -7
- package/dist/types.d.ts +9 -0
- package/dist/v1-parity-schemas.js +36 -2
- package/dist/version-diff-service.d.ts +23 -0
- package/dist/version-diff-service.js +101 -0
- package/dist/version-service.d.ts +14 -0
- package/dist/version-service.js +52 -3
- package/dist/workspace-context-cache.d.ts +25 -0
- package/dist/workspace-context-cache.js +52 -2
- package/dist/workspace-mapping-service.d.ts +8 -0
- package/dist/workspace-mapping-service.js +151 -21
- package/docs/README-ja.md +3 -1
- package/docs/tool-reference.md +69 -22
- package/package.json +1 -1
|
@@ -1,12 +1,121 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import fastGlob from "fast-glob";
|
|
5
5
|
import { mapWithConcurrencyLimit } from "./concurrency.js";
|
|
6
6
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
7
|
+
import { resolveGradleUserHomePath } from "./gradle-paths.js";
|
|
7
8
|
import { isSafeMavenSegment, isSafeMavenVersionToken } from "./maven-token.js";
|
|
8
9
|
const WORKSPACE_FILE_READ_CONCURRENCY = 4;
|
|
9
|
-
function
|
|
10
|
+
function gradleScriptKind(filePath) {
|
|
11
|
+
return filePath.endsWith(".kts") ? "kotlin" : "groovy";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Blank out Gradle Groovy/Kotlin line and block comments so a commented-out
|
|
15
|
+
* alternative (`// mappings loom.officialMojangMappings()`) cannot count as a
|
|
16
|
+
* mapping declaration — a Yarn project that kept such a note used to look like it
|
|
17
|
+
* declared two mappings and resolved to none.
|
|
18
|
+
*
|
|
19
|
+
* Literals are tracked rather than skipped over, because a `//` inside
|
|
20
|
+
* `"https://..."` opens no comment and a `/*` inside a string must not swallow the
|
|
21
|
+
* rest of the script. Newlines inside a removed block comment are preserved so the
|
|
22
|
+
* remaining declarations keep their line structure.
|
|
23
|
+
*
|
|
24
|
+
* The two dialects differ in three places, which is why `scriptKind` is threaded in
|
|
25
|
+
* from the file name rather than guessed from the text: Kotlin block comments NEST
|
|
26
|
+
* (Groovy's do not), a Kotlin raw string `"""..."""` has NO escape sequences (so
|
|
27
|
+
* honoring `\` there would let a literal ending in a backslash hide its own
|
|
28
|
+
* terminator), and the dollar-slashy literal `$/ ... /$` is Groovy-only.
|
|
29
|
+
*
|
|
30
|
+
* When a construct is ambiguous the scanner keeps the text instead of dropping it: a
|
|
31
|
+
* false mapping conflict is a smaller failure than a declaration that never reaches
|
|
32
|
+
* the detectors.
|
|
33
|
+
*/
|
|
34
|
+
function stripGradleComments(content, scriptKind) {
|
|
35
|
+
let output = "";
|
|
36
|
+
let index = 0;
|
|
37
|
+
while (index < content.length) {
|
|
38
|
+
const char = content[index];
|
|
39
|
+
if (char === "/" && content[index + 1] === "/") {
|
|
40
|
+
while (index < content.length && content[index] !== "\n") {
|
|
41
|
+
index += 1;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (char === "/" && content[index + 1] === "*") {
|
|
46
|
+
let depth = 1;
|
|
47
|
+
index += 2;
|
|
48
|
+
while (index < content.length && depth > 0) {
|
|
49
|
+
if (scriptKind === "kotlin" && content[index] === "/" && content[index + 1] === "*") {
|
|
50
|
+
depth += 1;
|
|
51
|
+
index += 2;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (content[index] === "*" && content[index + 1] === "/") {
|
|
55
|
+
depth -= 1;
|
|
56
|
+
index += 2;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (content[index] === "\n") {
|
|
60
|
+
output += "\n";
|
|
61
|
+
}
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Groovy dollar-slashy literal: `$/ ... /$`, whose only escapes are `$$` and `$/`.
|
|
67
|
+
// The plain slashy form `/.../` is deliberately NOT recognized — `/` is also the
|
|
68
|
+
// division operator, so `a / b / c` is indistinguishable from a literal without
|
|
69
|
+
// parsing expressions, and mistaking code for a literal would hide real comments.
|
|
70
|
+
if (scriptKind === "groovy" && char === "$" && content[index + 1] === "/") {
|
|
71
|
+
output += "$/";
|
|
72
|
+
index += 2;
|
|
73
|
+
while (index < content.length) {
|
|
74
|
+
if (content[index] === "$" && (content[index + 1] === "$" || content[index + 1] === "/")) {
|
|
75
|
+
output += content.slice(index, index + 2);
|
|
76
|
+
index += 2;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (content[index] === "/" && content[index + 1] === "$") {
|
|
80
|
+
output += "/$";
|
|
81
|
+
index += 2;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
output += content[index];
|
|
85
|
+
index += 1;
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (char === '"' || char === "'") {
|
|
90
|
+
// Triple-quoted Groovy/Kotlin blocks close only on the matching triple.
|
|
91
|
+
const tripled = content.startsWith(char.repeat(3), index);
|
|
92
|
+
const quote = tripled ? char.repeat(3) : char;
|
|
93
|
+
const honorsEscapes = !(tripled && scriptKind === "kotlin");
|
|
94
|
+
output += quote;
|
|
95
|
+
index += quote.length;
|
|
96
|
+
while (index < content.length) {
|
|
97
|
+
if (honorsEscapes && content[index] === "\\") {
|
|
98
|
+
output += content.slice(index, index + 2);
|
|
99
|
+
index += 2;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (content.startsWith(quote, index)) {
|
|
103
|
+
output += quote;
|
|
104
|
+
index += quote.length;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
output += content[index];
|
|
108
|
+
index += 1;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
output += char;
|
|
113
|
+
index += 1;
|
|
114
|
+
}
|
|
115
|
+
return output;
|
|
116
|
+
}
|
|
117
|
+
function detectMappingsFromContent(rawContent, scriptKind) {
|
|
118
|
+
const content = stripGradleComments(rawContent, scriptKind);
|
|
10
119
|
const detections = [];
|
|
11
120
|
if (/officialMojangMappings\s*\(/i.test(content)) {
|
|
12
121
|
detections.push({
|
|
@@ -148,7 +257,7 @@ async function adoptSubmoduleVersionFromUmbrellaPom(args) {
|
|
|
148
257
|
if (!umbrellaVersion) {
|
|
149
258
|
return undefined;
|
|
150
259
|
}
|
|
151
|
-
const umbrellaDir = resolve(
|
|
260
|
+
const umbrellaDir = resolve(resolveGradleUserHomePath(), "caches", "modules-2", "files-2.1", group, groupSegment, umbrellaVersion);
|
|
152
261
|
attempts.push(`umbrella-pom:${umbrellaDir}`);
|
|
153
262
|
let hashDirs = [];
|
|
154
263
|
try {
|
|
@@ -224,14 +333,8 @@ function compareSemverDescending(left, right) {
|
|
|
224
333
|
* working; there is still exactly one definition.
|
|
225
334
|
*/
|
|
226
335
|
export { isSafeMavenVersionToken };
|
|
227
|
-
function
|
|
228
|
-
const
|
|
229
|
-
if (configured) {
|
|
230
|
-
return configured;
|
|
231
|
-
}
|
|
232
|
-
return resolve(homedir(), ".gradle");
|
|
233
|
-
}
|
|
234
|
-
function detectLoadersFromContent(content) {
|
|
336
|
+
function detectLoadersFromContent(rawContent, scriptKind) {
|
|
337
|
+
const content = stripGradleComments(rawContent, scriptKind);
|
|
235
338
|
const detections = [];
|
|
236
339
|
if (/\bid\s*(?:\(\s*)?["']net\.neoforged\.moddev["']\s*\)?/i.test(content)) {
|
|
237
340
|
detections.push({
|
|
@@ -271,6 +374,15 @@ function detectLoadersFromContent(content) {
|
|
|
271
374
|
}
|
|
272
375
|
return detections;
|
|
273
376
|
}
|
|
377
|
+
/** The build.gradle(.kts) files `detectCompileMapping` scans under `root`, sorted. */
|
|
378
|
+
async function listCompileMappingBuildScripts(root) {
|
|
379
|
+
return (await fastGlob.glob(["build.gradle", "build.gradle.kts", "**/build.gradle", "**/build.gradle.kts"], {
|
|
380
|
+
cwd: root,
|
|
381
|
+
absolute: true,
|
|
382
|
+
onlyFiles: true,
|
|
383
|
+
ignore: ["**/.git/**", "**/.gradle/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
384
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
385
|
+
}
|
|
274
386
|
export class WorkspaceMappingService {
|
|
275
387
|
async detectCompileMapping(input) {
|
|
276
388
|
const projectPath = input.projectPath?.trim();
|
|
@@ -284,12 +396,7 @@ export class WorkspaceMappingService {
|
|
|
284
396
|
});
|
|
285
397
|
}
|
|
286
398
|
const root = resolve(projectPath);
|
|
287
|
-
const files =
|
|
288
|
-
cwd: root,
|
|
289
|
-
absolute: true,
|
|
290
|
-
onlyFiles: true,
|
|
291
|
-
ignore: ["**/.git/**", "**/.gradle/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
292
|
-
})).sort((left, right) => left.localeCompare(right));
|
|
399
|
+
const files = await listCompileMappingBuildScripts(root);
|
|
293
400
|
const evidence = (await mapWithConcurrencyLimit(files, WORKSPACE_FILE_READ_CONCURRENCY, async (filePath) => {
|
|
294
401
|
let content;
|
|
295
402
|
try {
|
|
@@ -298,7 +405,7 @@ export class WorkspaceMappingService {
|
|
|
298
405
|
catch {
|
|
299
406
|
return [];
|
|
300
407
|
}
|
|
301
|
-
return detectMappingsFromContent(content).map((detection) => ({
|
|
408
|
+
return detectMappingsFromContent(content, gradleScriptKind(filePath)).map((detection) => ({
|
|
302
409
|
filePath,
|
|
303
410
|
mapping: detection.mapping,
|
|
304
411
|
reason: detection.reason
|
|
@@ -328,6 +435,29 @@ export class WorkspaceMappingService {
|
|
|
328
435
|
warnings: []
|
|
329
436
|
};
|
|
330
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Whether `projectPath` holds at least one build.gradle(.kts) that
|
|
440
|
+
* `detectCompileMapping` reads: the same files, found the same way, and readable.
|
|
441
|
+
* `detectCompileMapping` answers "no evidence" both when a build declares no
|
|
442
|
+
* mappings and when there was no build to read (a missing, mistyped or empty
|
|
443
|
+
* directory); this tells the two apart without changing that output.
|
|
444
|
+
*/
|
|
445
|
+
async hasReadableBuildScript(projectPath) {
|
|
446
|
+
const trimmed = projectPath.trim();
|
|
447
|
+
if (!trimmed) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
for (const filePath of await listCompileMappingBuildScripts(resolve(trimmed))) {
|
|
451
|
+
try {
|
|
452
|
+
await access(filePath, constants.R_OK);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
// detectCompileMapping skips an unreadable script too.
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
331
461
|
async detectProjectMinecraftVersion(projectPath) {
|
|
332
462
|
const root = resolve(projectPath);
|
|
333
463
|
const propsPath = resolve(root, "gradle.properties");
|
|
@@ -411,7 +541,7 @@ export class WorkspaceMappingService {
|
|
|
411
541
|
attempts.push(`gradle.properties:${key}`);
|
|
412
542
|
}
|
|
413
543
|
}
|
|
414
|
-
const modulesDir = resolve(
|
|
544
|
+
const modulesDir = resolve(resolveGradleUserHomePath(), "caches", "modules-2", "files-2.1", group, name);
|
|
415
545
|
attempts.push(`modules-2:${modulesDir}`);
|
|
416
546
|
let entries = [];
|
|
417
547
|
try {
|
|
@@ -492,7 +622,7 @@ export class WorkspaceMappingService {
|
|
|
492
622
|
catch {
|
|
493
623
|
return [];
|
|
494
624
|
}
|
|
495
|
-
return detectLoadersFromContent(content).map((detection) => ({
|
|
625
|
+
return detectLoadersFromContent(content, gradleScriptKind(filePath)).map((detection) => ({
|
|
496
626
|
filePath,
|
|
497
627
|
loader: detection.loader,
|
|
498
628
|
reason: detection.reason
|
package/docs/README-ja.md
CHANGED
|
@@ -161,6 +161,8 @@ stdio トランスポートは、改行区切り形式と `Content-Length` フ
|
|
|
161
161
|
- ワークスペースのソースカバレッジが部分的な場合でも、バニラクラスを確認できます。`inspect-minecraft task="list-files"` は、その場合に部分的な結果とフォローアップガイダンスを返します。
|
|
162
162
|
- `analyze-mod` と `validate-project` は、オブジェクト形式の `subject` と正規の `include` グループを要求します。古い文字列形式の `subject` やドメイン名形式の `include` には `ERR_INVALID_INPUT` と、再試行しやすい `suggestedCall` を返します。
|
|
163
163
|
- `validate-mixin` と `validate-project` は、`obfuscated` / `mojang` 検証では `mapping-health` を軽量に保ちます。`intermediary` / `yarn` 名前空間を要求しない限り、完全な Tiny マッピンググラフは読み込みません。
|
|
164
|
+
- `validate-project task="project-summary"` は `version` を省略すると `gradle.properties` から Minecraft バージョンを推定し、推定したバージョンを `warnings` に示します。推定を止めるには `preferProjectVersion: false` を指定します。その場合、`version` のない呼び出しは `status: "blocked"` を返します。解決したバージョン(明示指定または推定)は、検出したすべての Mixin / Access Widener / Access Transformer の検証に渡されます。ファイルを検出したのにバージョンを推定できない場合は、推測せずに、明示的な `version` を求める再試行案付きで `status: "blocked"` を返します。
|
|
165
|
+
- `validate-project task="project-summary"` が検証対象のファイルを 1 つも検出しなかった場合、`status` は `"ok"` のままですが、headline が `Nothing to validate: ...` となり、`warnings` にも何も検証していないことが示されます。`"ok"` だけでは、いずれかのファイルが検証に通ったことを意味しません。
|
|
164
166
|
- `validate-project task="project-summary"` の `tasks["minecraft.artifact.resolved"]` は軽量なアーティファクト probe です。probe 状態を返すためだけに Minecraft のデコンパイルやソースインデックス再構築は行いません。追加の `tasks` フィールドを省きたい場合は `VALIDATE_PROJECT_TASKS_OFF=1` を使います。
|
|
165
167
|
|
|
166
168
|
### あるバージョンの Minecraft ソースを確認する
|
|
@@ -262,7 +264,7 @@ stdio トランスポートは、改行区切り形式と `Content-Length` フ
|
|
|
262
264
|
| `compare-minecraft` | バージョン差分、クラス差分、レジストリ差分、移行向け概要を比較する |
|
|
263
265
|
| `analyze-mod` | Mod メタデータの要約、Mod コードのデコンパイル / 検索、クラスソース確認、リマップのプレビュー / 実行を扱う |
|
|
264
266
|
| `validate-project` | ワークスペース要約と、Mixin / Access Widener / Access Transformer の直接検証を行う |
|
|
265
|
-
| `manage-cache` |
|
|
267
|
+
| `manage-cache` | キャッシュの一覧、検証、クリーンアップのプレビュー / 実行を行う |
|
|
266
268
|
<!-- END GENERATED TOOL TABLE: top-level-workflow-tools -->
|
|
267
269
|
|
|
268
270
|
### ソース探索
|
package/docs/tool-reference.md
CHANGED
|
@@ -40,6 +40,7 @@ Start here when you are not sure which tool to reach for. In every row, the left
|
|
|
40
40
|
- `ERR_CLASS_NOT_FOUND` errors from the class tools carry a top-level `didYouMean` array (parallel to `suggestedCall`) with ranked near-miss candidates from the artifact's symbol index: each entry is `{ className, matchReason }` where `matchReason` is `"exact-simple-name"` (same simple name in another package — the moved-class case, ranked first), `"case-insensitive"`, or `"edit-distance:N"`. Candidates come from the symbol index of the artifact the caller REQUESTED first, followed by any the artifact the lookup ended on contributes, deduplicated by FQN. A candidate found outside the requested artifact carries an extra `artifactId` naming where it was found; an entry WITHOUT that field is always from the artifact the caller asked about. The union matters because the requested artifact is, in the partial-source fallback scenario, the one with no `net.minecraft` symbols — which is why the fallback fired — so collecting from it alone returns `[]` in exactly the case the fallback exists to serve. Candidates are hints, never assertions that the class exists at the suggested location; the array is empty when neither index has anything usable. All same-simple-name FQNs are enumerated rather than collapsed. When an internal redirect resolved a different artifact while trying to answer the call — the binary fallback, or the nested-jar redirect that follows a shell jar's bundled inner jar — the error carries `details.fallbackArtifactId` naming it, alongside `details.binaryFallbackAttempted` for the binary case. `details.artifactId`, `details.mapping` and `details.qualityFlags` all describe the REQUESTED artifact, so identity, namespace and quality never disagree about which artifact is being reported. `fallbackArtifactId` and `binaryFallbackAttempted` are internal `AppError.details` fields for diagnosis and are NOT published on the wire; the envelope reports the requested artifact as `error.context.artifactId`.
|
|
41
41
|
- `find-class` searches the nested `.class` inventories of Jar-in-Jar shell artifacts such as the Fabric API umbrella JAR. It accepts simple or qualified names, returns dotted names for inner classes, deduplicates a class bundled more than once, and honors `limit`. The returned source path is inferred from the outer class. For top-level matches, `get-class-source` and `get-class-members` resolve the actual containing nested JAR before reading content; dotted inner-class matches are also readable through `get-class-source`.
|
|
42
42
|
- Source-oriented tools expose `artifactContents` so callers can tell whether the backing artifact is a `source-jar` or a `decompiled-binary`. `get-class-source`, `get-class-members`, `search-class-source`, and `get-artifact-file` also expose `returnedNamespace`.
|
|
43
|
+
- Known issue: `get-class-source` with `mode: "metadata"` returns a generated outline of the class's symbols in `sourceText`, not a slice of its source lines, yet reports `returnedRange: { start: 1, end: totalLines }` and, unless `maxChars` cuts the outline, `truncated: false`. Those values describe the whole source file the outline was built from, not the outline text, so do not use them to address lines of `sourceText`; use `mode: "snippet"` or `"full"` when you need line numbers. They are kept for now because changing a response field is a breaking change under this project's semantic-versioning policy; a future major release will replace them with fields specific to the metadata mode, and the CHANGELOG will announce it as a breaking change.
|
|
43
44
|
- When `mapping` is omitted, `get-class-source`, `get-class-members`, and `batch-class-members` all inherit the mapping the target artifact was resolved with, and report it as `returnedNamespace`. The `mapping` parameter's advertised schema description still says the default is `obfuscated`; that wording is pinned by the frozen legacy `inputSchema` bytes and is accurate only for targets that carry no resolved mapping of their own. An explicit `mapping` is never overridden.
|
|
44
45
|
- Cache-backed source, mapping, validation, batch, and workflow tools accept `gradleUserHome?: string` when they need Loom cache data. Use it for builds that used an isolated `GRADLE_USER_HOME`; the server searches `<gradleUserHome>/loom-cache` and `<gradleUserHome>/caches/fabric-loom` before the MCP process default. The value selects a Gradle User Home, not arbitrary Loom cache roots.
|
|
45
46
|
- `resolve-artifact` adds the `binary-jar-no-classes` quality flag when the binary jar it accepted opens cleanly but holds no `.class` entry — a `yarn`/`intermediary` `v2` mapping jar, a resource-only mod jar, or a jar of nothing but directory entries. The flag describes such an artifact and never refuses it, so an empty decompile can be told apart from a decompiler fault. It is set for `target.kind="coordinate"`, `"jar"` and `"version"`. A class-free jar that is not a Jar-in-Jar shell still fails during indexing with `ERR_DECOMPILER_FAILED`, so the flag does not reach the response in that case.
|
|
@@ -174,7 +175,7 @@ through as `mappingApplied: "obfuscated"` with `"source-backed"` or
|
|
|
174
175
|
For repeated lookups against the same dependency, call `resolve-artifact` once
|
|
175
176
|
and reuse the returned `artifactId` via `target: { kind: "artifact", artifactId }`.
|
|
176
177
|
|
|
177
|
-
Workspace detection is memoised in a process-resident `WorkspaceContextCache` (16-entry LRU, 5-minute TTL). The cache is observable through `manage-cache` with `cacheKinds: ["workspace"]`, and individual entries can be invalidated via `selector.projectPath`.
|
|
178
|
+
Workspace detection is memoised in a process-resident `WorkspaceContextCache` (16-entry LRU, 5-minute TTL). A cache hit also re-stats the project files the detection read: unconditionally, the project root's `gradle.properties`, `build.gradle`, and `build.gradle.kts` (present or not, stat taken before detection runs so even an edit racing the read is caught); plus any subproject `build.gradle`/`build.gradle.kts` or mod descriptor (`fabric.mod.json`, `quilt.mod.json`, `META-INF/mods.toml`, `META-INF/neoforge.mods.toml`) that actually contributed a compile-mapping or loader declaration (stat taken after detection returns, since which files those are is not known beforehand - an edit landing in the narrow window between detection reading such a file and this later stat is not guaranteed to be caught immediately, though the next read() re-stats it again). If a covered file was edited, created, or deleted since the entry was written, the hit is discarded and the workspace is re-detected, even within the TTL. Two things are NOT covered by any of this: a subproject build script or descriptor that was scanned but declared nothing (editing one to newly declare a mapping/loader is only picked up once the 5-minute TTL expires and the workspace is re-detected from scratch); and `settings.gradle(.kts)`/`libs.versions.toml`, which detection does not read at all, at any point - editing either changes nothing detection reports, regardless of caching or TTL. The cache is observable through `manage-cache` with `cacheKinds: ["workspace"]`, and individual entries can still be invalidated manually via `selector.projectPath`.
|
|
178
179
|
|
|
179
180
|
`target.kind="dependency"` resolution probes up to six de-duplicated `gradle.properties` keys in order — `name_version`, `snake_case(name)_version`, `camelCaseVersion`, `lastSegment(group)_name_version`, `snake_case(lastSegment(group)_name)_version`, and `camelCase(lastSegment(group)_name)Version`. Hyphens become underscores in the snake_case forms (`fabric-api` probes `fabric_api_version`); for hyphen-less names the snake_case forms deduplicate into the raw keys, leaving four. The probe falls back to the modules-2 cache layout `~/.gradle/caches/modules-2/files-2.1/<group>/<name>/`. `group` and `name` are held to the coordinate route's identifier rule (`[A-Za-z0-9._+-]`, no leading `.`, no `..`) **before** either probe runs, so no directory is listed on behalf of a coordinate that would be refused later — a blocklist of `/`, `\`, `..` and NUL was not enough, since `group="D:"` with `name="."` carries none of them and is drive-relative on Windows. Version tokens that contain path separators, `..`, NUL, control characters, or any character outside `[A-Za-z0-9._+-]` and the space are rejected; in `gradle.properties` the rejection is recorded under `attempts[]` as `gradle.properties:<key>:rejected-unsafe-version` and the next key is tried. An explicit `target.version` is trimmed before it is checked, matching the coordinate route. Snapshot and dev directories are excluded by default. The modules-2 fallback resolves directly when exactly one valid entry remains. When several entries remain and the dependency is a submodule of an umbrella package (artifact name differs from the group's last segment, e.g. `net.fabricmc.fabric-api:fabric-screen-handler-api-v1`), the declared umbrella version property (`fabric_api_version` / `fabricApiVersion`) locates the cached umbrella POM and the submodule adopts the version that POM names — the resolving response records `provenance.submoduleVersionSource: "umbrella-pom"` with the POM path in `provenance.source` (workspace-context-cache hits within the TTL return the cached version with `provenance.source: "workspace-context-cache"` instead). Umbrella properties are never adopted verbatim as a submodule's version. Every remaining ambiguity raises `ERR_DEPENDENCY_VERSION_UNRESOLVED` with `candidatesSeen` so a global cache cannot supply a version the workspace did not declare.
|
|
180
181
|
|
|
@@ -186,16 +187,18 @@ Workspace detection is memoised in a process-resident `WorkspaceContextCache` (1
|
|
|
186
187
|
- `analyze-symbol` infers an omitted `version` from `projectPath` (gradle.properties `minecraft_version`/`mc_version`); the response then carries `versionInference { version, source }` and a warning. An explicit `version` always wins. `inspect-minecraft` direct subjects without `subject.artifact` auto-resolve only when exactly one workspace is known to the process (provenance warning attached); several candidates are refused with `workspaceCandidates`. That unique workspace is then resolved through `target.kind="workspace"` like any other workspace subject, so it picks up the project's compile mapping and loader scope; the separately detected Minecraft version is retained only as the "is this workspace usable at all" guard.
|
|
187
188
|
|
|
188
189
|
- Loom split-source workspaces publish a version as a `minecraft-common` / `minecraft-clientOnly` sources-jar pair with no merged jar. Version-target resolution indexes both halves (the companion jar appears in `provenance.companionSourceJars`), so client-only classes are queryable under the merged scope. If a class still cannot be found, the error's `exampleCalls` carries a `scope: "vanilla"` retry — the decompiled client jar also contains client-only classes.
|
|
189
|
-
- `mapping="mojang"` requires source-backed artifacts on legacy obfuscated versions. For unobfuscated releases such as `26.1+`, the runtime/decompile path is accepted directly for version and
|
|
190
|
+
- `mapping="mojang"` requires source-backed artifacts on legacy obfuscated versions. For unobfuscated releases such as `26.1+`, the runtime/decompile path is accepted directly for version targets, Minecraft runtime coordinates such as `net.minecraft:client:26.1`, and jar targets proven to be a 26.1+ Minecraft runtime jar (see [Lookup Rules](#lookup-rules)). When source jars are not available but the version's Mojang tiny mappings, the tiny-remapper jar, and `MappingService.checkMappingHealth` are all healthy, `resolve-artifact` with `target.kind="version"` will transparently tiny-remap the binary jar (`obfuscated -> mojang`) and decompile the result, and the response carries `qualityFlags` `"binary-remapped"` and `"decompiled"` plus `provenance.transformChain` `"binary-remap:obf->mojang"` and `"decompile:vineflower"`. Coordinate and jar targets are not eligible for this fallback; a legacy coordinate or a jar that is not proven to be a 26.1+ runtime jar still surfaces `ERR_MAPPING_NOT_APPLIED`. Source-backed and obfuscated artifacts keep their existing `artifactId` hashes — only the new mojang-remapped variant lives in a separate cache slot.
|
|
190
191
|
- Mojang binary-remap cache entries live under `<cacheDir>/remapped`. `manage-cache` lists valid files plus corrupt directories and leftover temp entries under `cacheKinds: ["binary-remap"]`; corrupt entries carry `status: "corrupt"` and `meta.artifactId`, so callers can preview or apply cleanup with `selector.artifactId`.
|
|
191
192
|
- Jar-in-Jar shell jars (near-zero own classes with bundled `META-INF/jars/*.jar`, e.g. the Fabric API umbrella jar) no longer dead-end in `ERR_DECOMPILER_FAILED`. `resolve-artifact` detects the shell, skips decompilation, and returns the artifact with `qualityFlags: ["shell-jar"]` and the bundled inventory in `provenance.nestedJars`; `analyze-mod-jar` reports the same inventory as `nestedJars`. `get-class-source` / `get-class-members` lookups against a shell automatically redirect to the single nested jar containing the class and mark the response with `provenance.nestedJar` (`entryName` + `shellArtifactId`); a class found in several nested jars raises `ERR_NESTED_JAR_AMBIGUOUS` with `nestedJarCandidates` and per-candidate `suggestedCall` examples instead of picking one. Class-not-found errors on a shell carry the `nestedJars` inventory, on every access path that publishes one — the tool result envelope, `batch-*` entries, and `mc://` resource reads — with `nestedJarsTruncated: true` beside it when caps shortened the published list. That flag is omitted entirely when the inventory is complete, so read its absence as "complete", never as "unknown". The redirect is deliberately single-level: a nested jar that is itself a shell surfaces its own inventory when resolved directly as its own artifact. Extracted nested jars are content-addressed under `<cacheDir>/nested-jars`, observable through `manage-cache` with `cacheKinds: ["nested-jar"]`.
|
|
192
193
|
- `list-artifact-files` indexes Java source paths only — `assets/` and `data/` prefixes list nothing. Text files under those prefixes ARE retrievable by exact path with `get-artifact-file`: when the index has no row, the tool reads the entry directly from the backing jar (`deliveryMode: "jar-read-through"` marks such responses). This works for any artifact with a backing jar — vanilla client jars and mod jars alike; for Jar-in-Jar shells it reads the shell's own entries only (resolve a nested jar as its own artifact to reach its resources). Read-through delivery is text-only with a 512 KiB per-file cap (`truncated: true` beyond it); binary entries (e.g. `.png`, `.ogg`) answer with size metadata plus `contentOmittedReason` instead of content. Traversal-shaped paths (`..`, absolute) are rejected with `ERR_INVALID_INPUT`, and a miss returns `ERR_FILE_NOT_FOUND` with `nearbyPaths` naming same-named entries elsewhere in the jar (directory layouts move between versions, e.g. `assets/minecraft/models/item/*` → `assets/minecraft/items/*`).
|
|
193
194
|
- `search-class-source` defaults to `queryMode="auto"`. Use `queryMode="literal"` for explicit substring scans. `match="regex"` enforces `query.length <= 200` and caps results at `100`.
|
|
194
195
|
- `search-class-source` returns compact hits only. Use `get-artifact-file` or `get-class-source` to inspect returned files.
|
|
195
|
-
- `
|
|
196
|
+
- Indexed `intent="text"` and `intent="path"` searches (any `match` except `"regex"`; for text, any `queryMode` except `"literal"`) read one bounded candidate window from the index in index-relevance order and verify only those candidates: 500 for `match="exact"`/`"prefix"`, and five times the maximum hit count (500 to 5,000; 1,000 by default) for `"contains"`. `scope.packagePrefix`, or the literal directory prefix before the first `*`/`?` of `scope.fileGlob`, narrows that window inside the index, so in-scope matches are found even when many out-of-scope files also match. A glob without a literal prefix, such as `**/*Entity.java`, is applied only to the candidates already in the window, so its hits stay limited to that window. When the index matched more candidates than the window holds and the returned page is shorter than `limit`, a `warnings` entry says the search examined only its candidate window and results may be incomplete; a page that returns `limit` hits carries no such warning even when the window was full. To search past the window, use `queryMode="literal"` with `intent="text"` (a full substring scan, still bounded by the scan byte budget; a `warnings` entry reports when that budget is reached), or `match="regex"` for paths.
|
|
197
|
+
- On legacy (1.x) versions, `find-class` and `get-class-source` on `mapping="obfuscated"` expect Mojang obfuscated names. Deobfuscated queries warn and usually need `mapping="mojang"` or a `find-mapping` step first. On `26.1+` the `obfuscated` names already are Mojang names, so no mapping step applies; a miss points at near-miss class names instead (see [Lookup Rules](#lookup-rules)).
|
|
196
198
|
- `get-class-members` exposes `annotationDefault` on annotation-type members (the `default` value of an `@interface` member) whenever the classfile carries it, and accepts `includeAnnotations: true` to additionally list runtime-visible member annotations such as `@java.lang.Deprecated` per member. The leaner `names`/`signatures` projections drop annotation fields. `analyze-mod` `task="members"` includes both without a flag.
|
|
197
199
|
- On unobfuscated versions, `find-mapping`, `resolve-method-mapping-exact`, and `check-symbol-exists` report two structured `mappingContext` flags instead of per-response warning sentences (`get-class-api-matrix` reports a top-level `unobfuscatedRuntime: true` — its non-mojang columns are empty by design there): `unobfuscatedRuntime: true` replaces "Version X is unobfuscated; mapping graph is empty because the runtime already uses deobfuscated names.", and `runtimeValidated: true` replaces "Version X is unobfuscated; validated symbol existence against runtime bytecode." Do not pattern-match the old sentences.
|
|
198
200
|
- `check-symbol-exists` defaults to strict FQCN class lookup. Use `nameMode="auto"` for short class names.
|
|
201
|
+
- On unobfuscated versions, the runtime-bytecode check behind `check-symbol-exists` answers a method query named `<init>` from the class's own constructors, so an existing constructor descriptor resolves instead of returning `not_found`.
|
|
199
202
|
- On unobfuscated versions, every `not_found` and `mapping_unavailable` verdict from the mapping graph — classes and fields included, not just methods — is re-validated against runtime bytecode before being returned, so a graph that lacks a record for a real symbol (e.g. `EntityType.ITEM`) no longer produces false negatives. Genuinely-missing symbols still return `not_found` after the runtime check. Bytecode-derived response contexts report `mappingNamespace: "mojang"` on unobfuscated versions instead of a hardcoded `"obfuscated"`.
|
|
200
203
|
- `check-symbol-exists` can use `signatureMode="name-only"` for overload discovery, but exact `descriptor` matching is still the most reliable path.
|
|
201
204
|
- Inherited-member expansion (`get-class-members` with `includeInherited: true`, and the `check-symbol-exists` runtime-bytecode fallback that reuses it) suppresses super-class and interface resolution warnings for platform packages that are never inside a Minecraft jar (`java.*`, `javax.*`, `jdk.*`, `sun.*`, `com.mojang.serialization.*`). A warning about any other unresolved super type — including `com.mojang.blaze3d.*` — is a real signal.
|
|
@@ -242,14 +245,15 @@ Output shape:
|
|
|
242
245
|
|
|
243
246
|
| # | `member.kind` | target visibility | `mixinMemberName` regex | `suggestedAnnotation` |
|
|
244
247
|
|---|---|---|---|---|
|
|
245
|
-
| 1 |
|
|
246
|
-
| 2 | `
|
|
247
|
-
| 3 | `field` | `private` |
|
|
248
|
-
| 4 | `
|
|
249
|
-
| 5 | `method` | `private` |
|
|
250
|
-
| 6 | `method` | `private`
|
|
248
|
+
| 1 | `field` | `public` / `protected` | (any) | `"@Shadow"` (no `@Accessor` needed for outside access, but the mixin still needs `@Shadow` to reference the field) |
|
|
249
|
+
| 2 | `method` | `public` / `protected` | (any) | `"@Inject-only"` (target already visible to mixin; no `@Invoker` needed) |
|
|
250
|
+
| 3 | `field` | `private` / `package-private` | `^get[A-Z]\w*$` / `^set[A-Z]\w*$` / `^is[A-Z]\w*$` | `"@Accessor"` (target field name inferred via prefix removal) |
|
|
251
|
+
| 4 | `field` | `private` / `package-private` | (anything else, including absent) | `"@Shadow"` (or `"@Shadow @Final"` when target is `final`) |
|
|
252
|
+
| 5 | `method` | `private` / `package-private` | `^invoke[A-Z]\w*$` / `^call[A-Z]\w*$` | `"@Invoker"` |
|
|
253
|
+
| 6 | `method` | `private` / `package-private` | any other non-empty value | `"@Shadow"` |
|
|
254
|
+
| 7 | `method` | `private` / `package-private` | (absent) | `null` + `candidates: [@Shadow, @Invoker]` |
|
|
251
255
|
|
|
252
|
-
`"@Inject-only"` is a pseudo-tag, NOT a real Mixin annotation
|
|
256
|
+
`"@Inject-only"` is a pseudo-tag, NOT a real Mixin annotation, and only applies to visible (`public`/`protected`) **methods**. It signals "no `@Invoker` is needed because the target method is already accessible to the mixin"; use `@Inject` to hook it, or `@Shadow` to call it directly from mixin code. A visible **field** gets a concrete `"@Shadow"` recommendation instead — no `@Accessor` is needed to read/write it from outside, but mixin code still needs `@Shadow` to reference it. `accessorAdvice.exampleSnippet` is a deterministic Java fragment built from the recommendation; the tool does NOT compile-check the snippet — run a Gradle build before committing.
|
|
253
257
|
|
|
254
258
|
Errors:
|
|
255
259
|
|
|
@@ -259,6 +263,19 @@ Errors:
|
|
|
259
263
|
|
|
260
264
|
Set `VERIFY_MIXIN_TARGET_OFF=1` at process start to remove the tool from `tools/list` entirely and reject direct calls. Use as a rollback path while the accessor-inference rules stabilize.
|
|
261
265
|
|
|
266
|
+
## compare-minecraft migration-overview: `libraries`
|
|
267
|
+
|
|
268
|
+
`compare-minecraft` with `task="migration-overview"` includes a `migration.libraries` block (the `migration` block is returned at `detail: "standard"` or `"full"`) diffing the two versions' per-version JSON `libraries` array — the dependencies Minecraft itself ships with (LWJGL, Netty, etc.), which a class or registry diff cannot see. This is how a library swap such as LWJGL's GLFW binding being replaced by SDL surfaces, instead of silently changing behavior underneath a migration.
|
|
269
|
+
|
|
270
|
+
Entries are keyed by `group:artifact` (the Maven coordinate with the version, any trailing `@extension`, and any platform `natives-*` classifier stripped), so a routine version bump or the same library's per-platform native jars never read as churn. Comparison is order-independent: it does not matter what order the two sides' `libraries` arrays list their entries in, and a single side may legitimately carry more than one version for the same `group:artifact` key (Mojang manifests can list different versions on different platform rules) — every version seen on a side is tracked, not just the first.
|
|
271
|
+
|
|
272
|
+
- `migration.libraries.added` / `removed` — sorted arrays of `group:artifact:<versions>` strings for libraries present on only one side, where `<versions>` is the sorted, comma-joined list of distinct versions seen for that key on that side. A key with one version renders as before (e.g. `"org.libsdl:sdl3:3.2.0"`); a key with several platform-specific versions renders as e.g. `"g:a:1,2"`.
|
|
273
|
+
- `migration.libraries.addedCount` / `removedCount` — counts of the above.
|
|
274
|
+
- `migration.libraries.versionChangedCount` — count of `group:artifact` keys present on both sides whose version sets differ. No list is included.
|
|
275
|
+
- `summary.counts.librariesAdded` / `librariesRemoved` — the same added/removed counts, promoted into the top-level summary, so they are visible at `detail: "summary"` too.
|
|
276
|
+
|
|
277
|
+
The block is entirely additive: `summary.status` and `migration.impact` remain computed only from class and registry signals, unaffected by library changes. When per-version library details cannot be fetched (offline, or a fetch failure), the `libraries` block is omitted and a warning is added to the response instead of failing the whole `migration-overview` call. Fetching both sides' library lists is itself bounded by a short internal deadline (5 seconds, not configurable via any tool parameter or environment variable) so that an unreachable network — e.g. right after a restart, when class/registry data is already served from cached jars — cannot stall `migration-overview` for the full underlying fetch timeout; a deadline expiry is reported the same way as any other library-fetch failure: the block is omitted and a "timed out" warning is added. The library fetch already in flight is left to finish in the background and may still warm the cache for a later call.
|
|
278
|
+
|
|
262
279
|
## Batch lookup contract
|
|
263
280
|
|
|
264
281
|
`batch-class-source`, `batch-class-members`, `batch-symbol-exists`, and `batch-mappings` share one envelope. Each call sends a fixed shortlist (1..50 entries) and receives a per-entry result plus an aggregate summary. The batch runs `entries.length` underlying calls but resolves the shared artifact ONCE (where applicable), so the round-trip cost is `1 resolve + N per-entry` rather than `N × (resolve + per-entry)`.
|
|
@@ -269,7 +286,7 @@ Common input fields:
|
|
|
269
286
|
- `concurrency: number` (1..8, default 4) — passed to the worker pool. Above 8 is rejected with `ERR_INVALID_INPUT` (`fieldErrors[0].path === "concurrency"`).
|
|
270
287
|
- `failFast: boolean` (default `false`) — when `true`, the first per-entry error sets an abort flag. Workers that have not yet picked up an entry short-circuit with `error.code === "ERR_BATCH_ABORTED"`. **In-flight workers continue to completion** — they are not cancelled (no AbortSignal wiring). Already-completed `ok` entries are still returned in `results`.
|
|
271
288
|
- `detail: "summary" | "standard" | "full"` (default `summary`) + `include[]` — applies the same per-tool projection the corresponding single tool applies at that detail level to each entry's `result`. `detail: "summary"` matches the old `compact: true` per-entry shape; `detail: "full"` matches the old `compact: false` (byte-identical to the single tool's full output).
|
|
272
|
-
- Per-tool shared inputs (`target`, `mapping`, `projectPath`, `version`, etc.) follow the same shape as the matching single tool.
|
|
289
|
+
- Per-tool shared inputs (`target`, `mapping`, `projectPath`, `version`, etc.) follow the same shape as the matching single tool. On `batch-class-source` and `batch-class-members`, `target` additionally accepts `{ "kind": "artifact", "artifactId": "..." }` to reuse an already-resolved artifact instead of resolving one — the same reuse shape `get-class-source` / `get-class-members` accept, and the exact shape their own per-entry `suggestedCall` proposes for a retry (see the table below). When `target.kind` is `"artifact"`, the shared `resolveArtifact` call is skipped entirely: `summary.sharedArtifactProvenance` is omitted (no provenance is invented for a reused artifact), and an unknown `artifactId` surfaces per-entry with the same `ERR_SOURCE_NOT_FOUND` a single-tool call would raise, rather than aborting the batch.
|
|
273
290
|
|
|
274
291
|
Resource behavior: `concurrency` limits per-entry dispatch for one batch call. Within one MCP server process, entries or calls that need the same artifact index or decompiled fallback share the in-flight rebuild by `artifactId`. Separate MCP server processes sharing the same cache are not coordinated by this process-local guard.
|
|
275
292
|
|
|
@@ -374,8 +391,8 @@ Rejections name the offending node twice: `error.fieldErrors[0].path` is the RFC
|
|
|
374
391
|
- `ERR_TOOL_TIMEOUT` — Synthetic `CallToolResult` produced only for `validate-project` when its supervisor-owned end-to-end deadline expires. The default is 120,000 ms and includes supervisor queue time. `meta.timeout.phase` is `"queue"` when the call expired before dispatch and `"running"` after dispatch. A queue timeout does not restart the worker; a running timeout isolates the worker process tree, initiates replacement, and sets `meta.timeout.workerRestartInitiated: true`. Cancellation suppresses the timeout result while retaining the deadline for worker cleanup.
|
|
375
392
|
- `ERR_MIXIN_PARSE_FAILED` — Reserved code for `validate-mixin` parse-stage failures in single / inline mode. Today the runtime parser is permissive (no hard parse failure path), but emitting this code keeps the contract stable for future strict-parse modes.
|
|
376
393
|
- `ERR_STAGE_BUDGET_PRE_PARSE` — Raised when a pre-parse stage (`resolve`, `mapping-health`, or `parse` itself) exhausts its independent soft-deadline before validation can proceed. The error carries `failedStage: <stage name>`, `meta.stageBudgetExhausted: true`, and the `budgetMs` / `elapsedMs` for the offending stage. Recovery: shrink `mixinConfigPath` (e.g. validate one config file at a time) or set `MIXIN_STAGE_BUDGETS_OFF=1` to disable budgets for diagnostic reruns.
|
|
377
|
-
- `ERR_WORKSPACE_VERSION_UNRESOLVED` — Raised by `resolve-artifact`, `get-class-source`, `get-class-members`, and `inspect-minecraft` (workspace subject, every artifact-context task) when `target.kind="workspace"` cannot detect a Minecraft version from `gradle.properties`. Both `strict: true` and `strict: false` raise; `details.strict` records which value was supplied. The error carries `details.projectPath` and
|
|
378
|
-
- `ERR_DEPENDENCY_VERSION_UNRESOLVED` — Raised by
|
|
394
|
+
- `ERR_WORKSPACE_VERSION_UNRESOLVED` — Raised by `resolve-artifact`, `get-class-source`, `get-class-members`, and `inspect-minecraft` (workspace subject, every artifact-context task) when `target.kind="workspace"` cannot detect a Minecraft version from `gradle.properties`. Both `strict: true` and `strict: false` raise; `details.strict` records which value was supplied. The error carries `details.projectPath` and `exampleCalls` with `target: { kind: "version", value: "<your-mc-version>" }` so the caller can fall back to an explicit version target.
|
|
395
|
+
- `ERR_DEPENDENCY_VERSION_UNRESOLVED` — Raised by `resolve-artifact`, `get-class-source`, and `get-class-members` (not `inspect-minecraft`, whose target schema has no `"dependency"` kind) when `target.kind="dependency"` cannot infer a version from `gradle.properties` or `~/.gradle/caches/modules-2/files-2.1/<group>/<name>/`. Multiple non-snapshot entries in modules-2 also raise this error: the synthesizer refuses to pick without project-specific evidence, sets `details.ambiguous=true`, and lists the cached versions in `details.candidatesSeen`. The error carries `details.attempts` (the gradle.properties keys that were probed) plus `exampleCalls` with `target: { kind: "dependency", group, name, version: "<your-version>" }`.
|
|
379
396
|
|
|
380
397
|
### Error classification: `retryClass` and `issueOrigin`
|
|
381
398
|
|
|
@@ -387,9 +404,9 @@ Every `ProblemDetails` built from a caught error carries both fields, on every a
|
|
|
387
404
|
Because only `issueOrigin` is overridable, the two axes can disagree, and one pairing does so by design. `ERR_CONTEXT_UNRESOLVED` raised on an artifact that carries no binary jar publishes `retryClass: "input"` together with `issueOrigin: "tool_issue"` whenever the tool, not the caller, chose that artifact. `issueOrigin` is the one to act on there: replaying the same artifact cannot produce a different answer, whatever `retryClass` suggests. Which requests count as tool-chosen:
|
|
388
405
|
|
|
389
406
|
- `get-class-members` — every `target.kind` except `"jar"`, `{ "kind": "artifact", "artifactId": ... }` included. An `artifactId` is an opaque handle: its holder cannot tell from it whether the artifact has a binary jar, and cannot re-resolve it either, since `resolve-artifact` does not accept an `artifactId`. Only `target: { "kind": "jar", ... }`, where the caller named the exact jar, reports `issueOrigin: "code_issue"`.
|
|
390
|
-
- `batch-class-members` — same rule, applied once to the shared target and repeated on every failed entry
|
|
407
|
+
- `batch-class-members` — same rule, applied once to the shared target and repeated on every failed entry, including its own `{ "kind": "artifact", "artifactId": ... }` variant: only `"jar"` reports `code_issue`.
|
|
391
408
|
- `inspect-minecraft` `task="class-members"` — same rule once more: only a subject whose artifact reference is `target: { "kind": "jar", ... }` reports `code_issue`. A subject naming a resolved artifact (`{ "type": "resolved-id", "artifactId": ... }`) reports `tool_issue`, matching the equivalent `get-class-members` target: it is the same opaque handle from an earlier resolve, and wrapping it in a subject does not give the caller any way to vet the artifact's binary jar.
|
|
392
|
-
- `verify-mixin-target` — the same rule on its own `target`: the tool reads the target's members from bytecode, so every `target.kind` except `"jar"` reports `tool_issue`.
|
|
409
|
+
- `verify-mixin-target` — the same rule on its own `target`: the tool reads the target's members from bytecode, so every `target.kind` except `"jar"` reports `tool_issue`. Its target schema has no `"artifact"` kind at all (unlike `batch-class-members`), so only `"jar"` reports `code_issue`.
|
|
393
410
|
|
|
394
411
|
To check whether an artifact carries a binary jar before retrying, call `manage-cache` with `action: "inspect"`, `selector: { "artifactId": ... }` and `include: ["cacheEntries"]`. The `cacheEntries` block reports that artifact's stored `meta.binaryJarPath` — read from the same row the members lookup reads, and absent when the row has none. The `include` is not optional: `manage-cache` defaults to `detail: "summary"`, which drops `cacheEntries` entirely, so without it the reply never shows the field (`detail: "standard"` or `"full"` opts the block back in as well).
|
|
395
412
|
|
|
@@ -410,7 +427,7 @@ The optional `error.exampleCalls?: Array<{ tool: string; params: Record<string,
|
|
|
410
427
|
- `meta.restart` (synthetic worker-restart responses only) — emitted exclusively when the supervisor returns an `ERR_WORKER_RESTART` envelope. Shape: `{ tool, durationMs, lastStage, lastStageElapsedMs, lastStageMeta, exit: { code, signal }, retryRecommendation }`. `retryRecommendation` is one of `"narrow-query" | "clear-cache" | "report-bug" | "same-request"`, decided by the supervisor from `lastStage`, `exit.signal`, and the recent restart cadence (3 restarts within 60 s of the same tool → `"report-bug"`).
|
|
411
428
|
- `meta.timeout` (`ERR_TOOL_TIMEOUT` only) — exact shape: `{ tool: "validate-project", phase: "queue" | "running", durationMs, deadlineMs, lastStage, lastStageElapsedMs, lastStageMeta, redactedToolArgs, redactedToolArgsModified, retryRecommendation, workerRestartInitiated }`. Stage and argument diagnostics use the supervisor's bounded redaction rules. `workerRestartInitiated` reports initiation only; it does not claim that replacement startup or initialization replay completed.
|
|
412
429
|
- `meta.queue` (supervisor queue overflow only) — `{ reason: "supervisor-request-queue", maxQueued: 2, queuedCount: 2 }`. The FIFO retains at most two total worker-bound requests; a queued `validate-project` barrier consumes one slot, while a running barrier is outside the FIFO. Overflowing `tools/call` receives a synthetic `ERR_LIMIT_EXCEEDED` result. An overflowing non-tool request receives raw JSON-RPC `-32000` with message `"MCP supervisor request queue is full."`; notifications do not consume slots.
|
|
413
|
-
- Replacement startup/replay uses a 10–30 second internal watchdog and bounded retry backoff. Successful replay releases queued work. Spawn, pre-ready, replay, or watchdog failure terminalizes queued requests with the existing worker-restart response shapes. At the two-slot live-generation cap, new worker-bound requests, including `initialize`, fail immediately instead of entering the FIFO; notifications that cannot be delivered are emitted as structured supervisor warnings and dropped. Tree-cleanup helper attempts are bounded to five seconds so recovery and shutdown cannot wait forever on `taskkill` or an equivalent platform operation. On POSIX, `ESRCH` while signaling the saved process group means the group is already gone and is treated as completed cleanup.
|
|
430
|
+
- Replacement startup/replay uses a 10–30 second internal watchdog and bounded retry backoff. Successful replay releases queued work. Spawn, pre-ready, replay, or watchdog failure terminalizes queued requests with the existing worker-restart response shapes. At the two-slot live-generation cap, new worker-bound requests, including `initialize`, fail immediately instead of entering the FIFO; notifications that cannot be delivered are emitted as structured supervisor warnings and dropped. Tree-cleanup helper attempts are bounded to five seconds so recovery and shutdown cannot wait forever on `taskkill` or an equivalent platform operation. On POSIX, `ESRCH` while signaling the saved process group means the group is already gone and is treated as completed cleanup. When a worker exits on its own (a crash or an external kill), the supervisor also signals that worker's process group on POSIX, so descendants such as a Java process are not left running; this does not delay the replacement. On Windows, descendants of a worker that exited on its own are not reaped, because `taskkill /T` cannot walk a tree whose root has already exited.
|
|
414
431
|
- `meta.stageBudgetExhausted` (`ERR_STAGE_BUDGET_PRE_PARSE` errors only) — set to `true` to flag that the failure is budget-driven rather than a genuine resolve / mapping-health / parse error. Pair with `failedStage` and `error.detail` (`"Stage <name> exhausted budget before parse completed."`) for diagnostics. The companion fields `meta.budgetMs` (the stage budget that was exceeded) and `meta.elapsedMs` (the actual stage elapsed time) are emitted alongside it on the same envelope so callers can decide between retry, input-shrink (`mixinConfigPath`), or `MIXIN_STAGE_BUDGETS_OFF=1` rollback without parsing message text.
|
|
415
432
|
- `validate-mixin` batch-mode entries (`results[i]`, when invoked with `input.mode = "paths" | "config" | "project"`) preserve typed error metadata when an entry fails: optional `errorCode` (e.g. `"ERR_STAGE_BUDGET_PRE_PARSE"`) and `errorDetails` (the `failedStage` / `stageBudgetExhausted` / `budgetMs` / `elapsedMs` shape from the underlying AppError) sit alongside the legacy `error` string. The shape is additive — callers that only read `error` continue to see the same human-readable message.
|
|
416
433
|
|
|
@@ -444,6 +461,10 @@ These environment variables are read once at worker startup and provide rollback
|
|
|
444
461
|
- `analyze-mod` `task="members"` (subject `{ kind: "class", jarPath, className }`) reads a mod class's constructors/fields/methods (all access levels, including private and protected) straight from bytecode — no decompiler runs on this path, unlike `task="class-source"` which costs a full decompile. Responses mark `extractionMethod: "bytecode-only"`; a class missing from the jar returns `ERR_CLASS_NOT_FOUND`.
|
|
445
462
|
- Start with `validate-project` for workspace summaries and direct Mixin, Access Widener, or Access Transformer validation before using `validate-mixin`, `validate-access-widener`, or `validate-access-transformer` directly.
|
|
446
463
|
- `validate-project task="project-summary"` discovers mixins and access wideners by default. Add `discover: ["access-transformers"]` when you also want Access Transformer files included in the workspace summary.
|
|
464
|
+
- `validate-project task="project-summary"` infers an omitted `version` from `gradle.properties` (`minecraft_version`, `mc_version`, or `minecraftVersion`) and adds the warning `version was inferred from the workspace: <version> (source: projectPath:gradle.properties (<projectPath>)).`; the run then behaves exactly like one with `preferProjectVersion: true`. An explicit `version` is used as given unless `preferProjectVersion: true` is also set, which replaces it with the detected project version. The resolved version is passed to every discovered Mixin, Access Widener, and Access Transformer check.
|
|
465
|
+
- `preferProjectVersion: false` turns inference off. Without a `version`, the summary then returns `status: "blocked"` with the headline `project-summary requires version or preferProjectVersion=true.` and a `summary.nextActions` retry that sets `preferProjectVersion: true`.
|
|
466
|
+
- When inference finds no version and discovery found files, the summary returns `status: "blocked"` with the headline `Could not resolve Minecraft version for discovered workspace validators.` Its `summary.nextActions` entry carries `version: "<minecraft-version>"`; replace the placeholder with the project's Minecraft version before retrying.
|
|
467
|
+
- When discovery finds no files, the summary keeps `status: "ok"` but reports `Nothing to validate: no <kinds> were found.` as the headline, where `<kinds>` names only the file kinds `subject.discover` searched, and adds a `warnings` entry beginning `Nothing was validated:`. An `"ok"` status alone therefore does not mean any file passed validation.
|
|
447
468
|
- `validate-project task="project-summary"` returns an additive `tasks` field alongside the existing aggregate `result.summary` / `result.project` blocks. The headline `result.summary.status` is unchanged; the new field reports per-probe status so a `status: "blocked"` headline still preserves which probes succeeded.
|
|
448
469
|
|
|
449
470
|
| Probe key | What it checks | `status: "ok"` evidence | Other states |
|
|
@@ -644,13 +665,34 @@ Tooling note: `pnpm test:manual:stdio-smoke` runs against the production supervi
|
|
|
644
665
|
|
|
645
666
|
| Namespace | Description |
|
|
646
667
|
| --- | --- |
|
|
647
|
-
| `obfuscated` | Mojang obfuscated names such as `a`, `b`, `c` |
|
|
668
|
+
| `obfuscated` | The runtime jar's names as shipped. On legacy versions these are Mojang obfuscated names such as `a`, `b`, `c`; on unobfuscated releases (`26.1+`) the runtime ships Mojang names, so `obfuscated` and `mojang` name the same classes |
|
|
648
669
|
| `mojang` | Mojang deobfuscated names from `client_mappings.txt` such as `net.minecraft.server.Main` |
|
|
649
670
|
| `intermediary` | Fabric stable intermediary names such as `net.minecraft.class_1234` and `method_5678` |
|
|
650
671
|
| `yarn` | Fabric community human-readable names such as `net.minecraft.server.MinecraftServer` and `tick` |
|
|
651
672
|
|
|
652
673
|
The legacy public namespace name `official` was removed. Requests that still send `official` now fail validation and should be updated to `obfuscated`.
|
|
653
674
|
|
|
675
|
+
On an unobfuscated runtime (`26.1+`), `mappingApplied` keeps the label the request asked for: an omitted or `obfuscated` mapping reports `"obfuscated"`, and `mapping="mojang"` reports `"mojang"`. Both read the same Mojang names. The artifact's `provenance` then carries `unobfuscatedRuntime: true`; the field is absent for every other artifact. It is set for 26.1+ version targets, 26.1+ Minecraft runtime coordinates, and jar targets proven to be a 26.1+ Minecraft runtime jar, and it appears wherever the artifact provenance is returned: `resolve-artifact` `provenance` (omitted at its default `detail: "summary"`; pass `include: ["provenance"]` or `detail: "standard"` / `"full"`), `get-class-source` / `get-class-members` `provenance` (omitted at their default `detail: "standard"`; pass `include: ["provenance"]` or `detail: "full"`), and `sharedArtifactProvenance` on `batch-class-source`, `batch-class-members`, and `batch-symbol-exists`. A 26.1+ jar that an earlier release indexed without a version gains the version and the flag the next time `resolve-artifact` proves it; lookups by that `artifactId` then accept `mapping="mojang"`. It is the same flag name as `mappingContext.unobfuscatedRuntime`.
|
|
676
|
+
|
|
677
|
+
### Known issue: obfuscated label on unobfuscated runtimes
|
|
678
|
+
|
|
679
|
+
On Minecraft `26.1+`, responses label the runtime's names `obfuscated` even though those names are Mojang names. The label is misleading for these versions, because nothing in them is obfuscated. It is kept unchanged for now, and a future major release will correct it.
|
|
680
|
+
|
|
681
|
+
The label appears whenever a request omits `mapping` or passes `mapping: "obfuscated"`:
|
|
682
|
+
|
|
683
|
+
- `mappingApplied: "obfuscated"` from `resolve-artifact`, `get-class-source`, `get-class-members`, and the batch tools, including `target.kind="workspace"` on a 26.1+ project that declares no `mappings` line.
|
|
684
|
+
- `query.mapping: "obfuscated"` from `diff-class-signatures` and `compare-minecraft` with `task="class-diff"`.
|
|
685
|
+
- `mapping: "obfuscated"` in the `tasks["minecraft.artifact.resolved"]` entry of `validate-project`.
|
|
686
|
+
|
|
687
|
+
`list-versions` reports these versions with `unobfuscated: true`, so the two responses disagree about the same names. The label is not changed yet because changing a response value that clients may already compare is a breaking change under this project's semantic-versioning policy.
|
|
688
|
+
|
|
689
|
+
Until the correction ships:
|
|
690
|
+
|
|
691
|
+
- Read `provenance.unobfuscatedRuntime: true` (or `mappingContext.unobfuscatedRuntime: true`) as the signal that the names are Mojang names, whatever `mappingApplied` says. `provenance` is omitted at the default detail levels described above.
|
|
692
|
+
- Pass `mapping: "mojang"` to get responses labelled `"mojang"`. On these versions it reads the same names.
|
|
693
|
+
|
|
694
|
+
Planned correction: a future major release will stop reporting `obfuscated` for runtimes whose names are Mojang names. The replacement is not decided yet: responses could report `mojang`, or a separate namespace value could be added. The CHANGELOG will announce it as a breaking change.
|
|
695
|
+
|
|
654
696
|
### Lookup Rules
|
|
655
697
|
|
|
656
698
|
`find-mapping` supports lookup across `obfuscated`, `mojang`, `intermediary`, and `yarn`.
|
|
@@ -661,14 +703,17 @@ Symbol query inputs use `kind` plus `name` plus optional `owner` and `descriptor
|
|
|
661
703
|
- field: `kind="field"`, `owner="a.b.C"`, `name="fieldName"`
|
|
662
704
|
- method: `kind="method"`, `owner="a.b.C"`, `name="methodName"`, `descriptor="(I)V"`
|
|
663
705
|
|
|
664
|
-
`mapping="mojang"` requires a source-backed artifact on legacy obfuscated versions. On unobfuscated releases such as `26.1+`, decompile-only/runtime paths are accepted directly for version and
|
|
706
|
+
`mapping="mojang"` requires a source-backed artifact on legacy obfuscated versions. On unobfuscated releases such as `26.1+`, decompile-only/runtime paths are accepted directly for version targets, Minecraft runtime coordinates such as `net.minecraft:client:26.1`, and jar targets proven to be a 26.1+ Minecraft runtime jar.
|
|
707
|
+
|
|
708
|
+
A `target.kind="jar"` path counts as a 26.1+ Minecraft runtime jar only when the jar's own contents prove it: it has a top-level `version.json` whose `id` is an unobfuscated Minecraft version id such as `26.2`, `26.1-rc1`, or `26w14a`, it contains `net/minecraft/SharedConstants.class`, and it contains no `.java` sources. The file path is never used as evidence. Vanilla 26.x client jars and Loom's 26.x Minecraft jars carry all three. A proven jar reports that `id` as its `version`; a jar reached through a `target.kind="dependency"` target is never treated as a runtime jar. A jar that is not proven keeps the legacy rule: `mapping="mojang"` needs a source jar.
|
|
665
709
|
|
|
666
710
|
`resolve-artifact`, `get-class-members`, `trace-symbol-lifecycle`, and `diff-class-signatures` accept `obfuscated | mojang | intermediary | yarn` with these constraints:
|
|
667
711
|
|
|
668
712
|
- `intermediary` and `yarn` require a resolvable Minecraft version context such as `target.kind="version"` or a versioned Maven coordinate.
|
|
669
|
-
- For unobfuscated versions such as `26.1+`, requesting `intermediary` or `yarn` falls back to `obfuscated` with a warning.
|
|
713
|
+
- For unobfuscated versions such as `26.1+`, including proven 26.1+ runtime jar targets, requesting `intermediary` or `yarn` falls back to `obfuscated` with a warning.
|
|
670
714
|
- On legacy obfuscated versions, `mojang` requires source-backed artifacts and decompile-only paths are rejected with `ERR_MAPPING_NOT_APPLIED`.
|
|
671
|
-
- On unobfuscated versions such as `26.1+`, `mojang` uses the runtime/decompile path directly
|
|
715
|
+
- On unobfuscated versions such as `26.1+`, both `obfuscated` and `mojang` are accepted for version targets, Minecraft runtime coordinates, and proven 26.1+ runtime jar targets; `mojang` uses the runtime/decompile path directly (`provenance.transformChain` includes `"mapping:mojang-runtime-unobfuscated"`) and skips Loom source-jar approximation.
|
|
716
|
+
- On unobfuscated versions such as `26.1+`, `obfuscated` and `mojang` name the same runtime names. With `mapping="mojang"`, `diff-class-signatures` (`compare-minecraft task="class-diff"`) and `trace-symbol-lifecycle` therefore use member names exactly as read from the runtime jar. They do not look them up in the empty mapping graph, so members are no longer reported with "Could not remap" or "Could not map" warnings.
|
|
672
717
|
|
|
673
718
|
When `trace-symbol-lifecycle` omits `descriptor`, the server resolves methods by owner and name and warns if overload ambiguity prevents a unique answer.
|
|
674
719
|
|
|
@@ -680,9 +725,9 @@ If callers accidentally append an inline signature suffix to `trace-symbol-lifec
|
|
|
680
725
|
|
|
681
726
|
`trace-symbol-lifecycle`, `check-symbol-exists`, and `find-mapping` skip intermediary/yarn Tiny graph loading when the request only needs Mojang/obfuscated names, so cold `mojang <-> obfuscated` lifecycle and existence lookups no longer pay the full named-namespace graph cost.
|
|
682
727
|
|
|
683
|
-
For decompile-only `ERR_MAPPING_NOT_APPLIED` failures, error details include `artifactOrigin`, `nextAction`, and `suggestedCall` so clients can recover without guessing.
|
|
728
|
+
For decompile-only `ERR_MAPPING_NOT_APPLIED` failures, error details include `artifactOrigin`, `nextAction`, and `suggestedCall` so clients can recover without guessing. For a jar target that is not proven to be a 26.1+ runtime jar, a refused request that did not set `scope="vanilla"` gets a `nextAction` saying that `mapping=obfuscated` reads the jar's names as shipped (already Mojang names on Minecraft 26.1+) and that a vanilla Minecraft release is read through `target.kind="version"`; its `suggestedCall` retries the same jar with `mapping: "obfuscated"` and the request's `scope`. A refused `scope="vanilla"` request keeps the scope guidance instead: without a `projectPath`, `nextAction` says that `scope=vanilla` blocks Loom cache discovery and `suggestedCall` retries with `mapping: "obfuscated"` and `scope: "vanilla"`; with a `projectPath`, `suggestedCall` retries with `mapping: "mojang"` and `scope: "merged"`. The one exception is a `projectPath` whose `gradle.properties` names a 26.1+ Minecraft version: that merged retry would repeat the refused request, so a jar or coordinate target is retried with `mapping: "obfuscated"` and `scope: "vanilla"` (a jar with the as-shipped wording above).
|
|
684
729
|
|
|
685
|
-
If `find-class` or `get-class-source` returns no hit on an obfuscated Minecraft runtime artifact for names like `net.minecraft.world.item.Item`, the tool warns that `obfuscated` means Mojang's runtime names and recommends retrying with `mapping="mojang"` or translating via `find-mapping`.
|
|
730
|
+
If `find-class` or `get-class-source` returns no hit on an obfuscated legacy (1.x) Minecraft runtime artifact for names like `net.minecraft.world.item.Item`, the tool warns that `obfuscated` means Mojang's runtime names and recommends retrying with `mapping="mojang"` or translating via `find-mapping`. Neither tool issues that advice for dependency-resolved or Jar-in-Jar shell artifacts, whose native names are not evidence of Minecraft obfuscation, or for a 26.1+ Minecraft artifact, whose `obfuscated` names already are Mojang names. On a 26.1+ artifact, a `get-class-source` / `get-class-members` miss starts `nextAction` with the nearest `didYouMean` candidate (`Did you mean "<class>"?`) when the index has one, then points at `find-class`; when the artifact is labelled `obfuscated` and the caller did not pass a non-obfuscated `mapping`, it adds that retrying with a different mapping will not change the class names. A `find-class` miss on a 26.1+ artifact labelled `obfuscated` warns with up to three near-miss class names and the same note.
|
|
686
731
|
|
|
687
732
|
Method descriptor precision is best on Tiny-backed paths (`intermediary` and `yarn`). For `obfuscated <-> mojang`, Mojang `client_mappings` do not carry JVM descriptors, so descriptor queries may fall back to name matching and emit a warning.
|
|
688
733
|
|
|
@@ -698,6 +743,8 @@ Use `find-mapping` `disambiguation.ownerHint` and `disambiguation.descriptorHint
|
|
|
698
743
|
|
|
699
744
|
Use `resolve-workspace-symbol` when you need compile-visible names from actual Gradle Loom mappings in a workspace.
|
|
700
745
|
|
|
746
|
+
A Loom project for an unobfuscated version such as `26.1+` has no `mappings` line, because it compiles against the runtime (Mojang) names. When `build.gradle(.kts)` declares no mappings and the version is unobfuscated, `resolve-workspace-symbol` and `analyze-symbol task="workspace"` look the symbol up by name in the runtime jar's bytecode. They no longer return `mapping_unavailable` (`analyze-symbol` status `partial`). The response reports `workspaceDetection.resolved: true` and `mappingApplied: "mojang"` with empty `evidence`, and `mappingContext` reports `targetMapping: "mojang"` and `unobfuscatedRuntime: true`. A warning explains that no mappings declaration is needed. `sourceMapping` is echoed as requested, so the default `obfuscated` stays `obfuscated`. A class that is not in the Minecraft runtime jar, such as the library class `org.lwjgl.glfw.GLFW`, returns `not_found` with a warning that names the jar, and so does a member that the loaded class does not have. A method query named `<init>` is checked against the class's own constructors. `not_found` is reserved for those two answers: a lookup the runtime jar could not answer - a short (not fully qualified) class name, or a class that failed to load for any reason other than being absent - returns `mapping_unavailable` with a warning that gives the reason. `sourceMapping` `intermediary` or `yarn` returns `mapping_unavailable`, because those names do not exist on these versions. If the runtime jar cannot be resolved, the result also stays `mapping_unavailable`. The runtime lookup applies only when `projectPath` holds a readable `build.gradle(.kts)`: a missing, mistyped, or empty project directory keeps the `mapping_unavailable` result with the "No compile-time mapping declaration" warning. Projects that declare mappings, and legacy versions, keep the behavior described above.
|
|
747
|
+
|
|
701
748
|
## Environment Variables
|
|
702
749
|
|
|
703
750
|
Path-based overrides treat blank values and the literal strings `undefined` and `null` as unset, so accidental client serialization does not create `./undefined` or `./null` cache roots or broken JAR override paths.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhisang/minecraft-modding-mcp",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"description": "MCP server for AI-assisted Minecraft modding: inspect decompiled source, resolve Mojang/Yarn/Intermediary mappings, diff versions, analyze Fabric/Forge/NeoForge mod JARs, and validate Mixin, Access Widener, and Access Transformer files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.30.1",
|