@adhisang/minecraft-modding-mcp 7.0.0-rc.0 → 7.0.0-rc.2

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 (58) hide show
  1. package/CHANGELOG.md +131 -49
  2. package/README.md +2 -0
  3. package/dist/cache-registry.d.ts +10 -0
  4. package/dist/cache-registry.js +44 -3
  5. package/dist/entry-tools/batch-class-members-service.js +8 -0
  6. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +21 -0
  7. package/dist/entry-tools/inspect-minecraft/internal.d.ts +4 -0
  8. package/dist/entry-tools/manage-cache-service.d.ts +2 -2
  9. package/dist/entry-tools/validate-project/cases/mixin.js +26 -6
  10. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
  11. package/dist/entry-tools/validate-project-service.d.ts +2 -2
  12. package/dist/entry-tools/verify-mixin-target-service.js +19 -1
  13. package/dist/error-mapping.d.ts +118 -0
  14. package/dist/error-mapping.js +177 -7
  15. package/dist/index.js +3 -1
  16. package/dist/json-rpc-framing.d.ts +53 -1
  17. package/dist/json-rpc-framing.js +190 -25
  18. package/dist/mapping/loaders/tiny-loom-selection.js +10 -3
  19. package/dist/mapping/parsers/tiny.js +4 -0
  20. package/dist/mapping-service.js +24 -3
  21. package/dist/maven-resolver.d.ts +61 -0
  22. package/dist/maven-resolver.js +95 -4
  23. package/dist/maven-token.d.ts +65 -0
  24. package/dist/maven-token.js +120 -0
  25. package/dist/mcp-helpers.js +14 -3
  26. package/dist/minecraft-explorer-service.d.ts +13 -1
  27. package/dist/minecraft-explorer-service.js +200 -7
  28. package/dist/repo-downloader.d.ts +260 -0
  29. package/dist/repo-downloader.js +1083 -14
  30. package/dist/source/artifact-resolver.d.ts +97 -3
  31. package/dist/source/artifact-resolver.js +228 -24
  32. package/dist/source/class-source.d.ts +15 -0
  33. package/dist/source/class-source.js +163 -18
  34. package/dist/source/indexer.js +14 -0
  35. package/dist/source/nested-jars.d.ts +16 -1
  36. package/dist/source/nested-jars.js +66 -3
  37. package/dist/source/workspace-target.js +39 -20
  38. package/dist/source-jar-reader.d.ts +30 -2
  39. package/dist/source-jar-reader.js +39 -8
  40. package/dist/source-resolver.d.ts +48 -0
  41. package/dist/source-resolver.js +735 -81
  42. package/dist/source-service.d.ts +10 -1
  43. package/dist/stdio-supervisor.d.ts +75 -1
  44. package/dist/stdio-supervisor.js +240 -14
  45. package/dist/storage/artifacts-repo.d.ts +2 -0
  46. package/dist/storage/artifacts-repo.js +22 -0
  47. package/dist/storage/db.js +32 -10
  48. package/dist/tool-guidance.js +17 -7
  49. package/dist/tool-schemas.d.ts +10 -2
  50. package/dist/tool-schemas.js +4 -0
  51. package/dist/v1-parity-schemas.js +16 -0
  52. package/dist/version-diff-service.js +25 -4
  53. package/dist/version-service.js +23 -8
  54. package/dist/warning-details.js +3 -1
  55. package/dist/workspace-mapping-service.d.ts +8 -1
  56. package/dist/workspace-mapping-service.js +28 -19
  57. package/docs/tool-reference.md +31 -7
  58. package/package.json +6 -4
@@ -2,6 +2,74 @@ import type { SourceService } from "../source-service.js";
2
2
  import type { ArtifactContentsSummary, ProbeMinecraftArtifactInput, ProbeMinecraftArtifactOutput, ResolveArtifactInput, ResolveArtifactOutput } from "../source-service.js";
3
3
  import type { AccessTransformerNamespace, ArtifactProvenance, ArtifactScope, ArtifactTargetKind, ResolvedSourceArtifact, RuntimeLoader, RuntimeValidationProvenance, SourceMapping } from "../types.js";
4
4
  import type { WorkspaceProjectLoader } from "../workspace-mapping-service.js";
5
+ /**
6
+ * Does this artifactId, on its own, name the Minecraft runtime rather than a
7
+ * library that merely shares Minecraft's group?
8
+ *
9
+ * This is the rule itself, held in one place so the two ways an artifact can be
10
+ * named cannot drift apart. `coordinateNamesMinecraftRuntime` applies it to a
11
+ * parsed Maven coordinate; `extractVersionFromPath` in
12
+ * `minecraft-explorer-service.ts` applies it to the artifactId DIRECTORY of a jar
13
+ * sitting in a dependency store, which is all a `kind:"jar"` target ever offers.
14
+ * When those two disagreed, `net.minecraft:launchwrapper:1.12` was a library on
15
+ * the coordinate route and the Minecraft runtime on the path route.
16
+ */
17
+ export declare function artifactIdNamesMinecraftRuntime(artifactId: string): boolean;
18
+ /**
19
+ * Does this Maven coordinate name the Minecraft runtime itself?
20
+ *
21
+ * Only then does its version segment carry a real Minecraft version, and only then
22
+ * may a version or namespace derived from the artifact be read as Minecraft's.
23
+ * `net.minecraft:launchwrapper:1.12` is a real library Mojang publishes on
24
+ * libraries.minecraft.net: judging by group alone read it as the runtime, so its
25
+ * own release number was reported as a Minecraft version and a class miss inside it
26
+ * was blamed on Minecraft obfuscation.
27
+ *
28
+ * A coordinate that cannot be parsed is not shown to name the runtime, so it
29
+ * answers `false`. That matches what both call sites did with a parse failure
30
+ * before this helper existed: the resolver left its group unread and fell through
31
+ * to "not Minecraft", and `isDependencyLikeArtifact` treated it as a library.
32
+ */
33
+ export declare function coordinateNamesMinecraftRuntime(coordinate: string): boolean;
34
+ /**
35
+ * Does this artifact hold a third-party library rather than the Minecraft runtime?
36
+ *
37
+ * Callers use this to decide whether any Minecraft version derived from the
38
+ * artifact's jar path or resolver metadata may be believed, and whether a
39
+ * class-not-found miss may be blamed on Minecraft obfuscation.
40
+ *
41
+ * `provenance.dependencyResolution` alone cannot answer it. That marker is written
42
+ * only when the same `resolveArtifact` call handled a `kind:"dependency"` target,
43
+ * yet `synthesizeDependencyTarget` rewrites such a target into `{kind:"coordinate"}`
44
+ * BEFORE `buildProvenance` runs — so a dependency-route artifact and one reached by
45
+ * naming the same Maven coordinate directly persist an identical `target` and
46
+ * `resolvedFrom.coordinate`. Keyed off the marker, the question "is this a
47
+ * dependency?" was really answering "was it reached by a dependency target in this
48
+ * very call?", and a coordinate target — or a later reuse of the stored artifact by
49
+ * `artifactId` — got the vanilla answer for a dependency jar.
50
+ *
51
+ * The coordinate is route-invariant, so it decides instead, through the same
52
+ * `coordinateNamesMinecraftRuntime` the resolver's own obfuscation gate uses: a
53
+ * coordinate that does not name the Minecraft runtime names a library whose paths
54
+ * and version segment say nothing about Minecraft. Sharing the one helper is
55
+ * deliberate — the two questions are the same question asked in opposite polarity,
56
+ * and answering them differently is how `net.minecraft:launchwrapper` came to be
57
+ * read as the runtime on one side while being a library on the other.
58
+ *
59
+ * One limit remains, and it predates this predicate: an artifact reached by
60
+ * `kind:"jar"` records no coordinate at all, so it falls to the absent case and
61
+ * keeps the path-derived behaviour it has always had. The absent case is vanilla
62
+ * because that is the pre-existing default for the routes that record no coordinate
63
+ * — not because a vanilla artifact necessarily lacks one; resolving
64
+ * `net.minecraft:client:26.1` by coordinate does store it, and the runtime-name
65
+ * check is what classifies it. (`extractVersionFromPath` in
66
+ * `minecraft-explorer-service.ts` refuses on its own for a jar served out of a
67
+ * dependency cache, which is what covers the no-coordinate dependency jar.)
68
+ */
69
+ export declare function isDependencyLikeArtifact(input: {
70
+ provenance?: ArtifactProvenance;
71
+ coordinate?: string;
72
+ }): boolean;
5
73
  export type VersionSourceDiscovery = {
6
74
  searchedPaths: string[];
7
75
  candidateArtifacts: string[];
@@ -42,6 +110,16 @@ export type MappingFallbackSuggestion = {
42
110
  nextAction: string;
43
111
  _suggestedCallPrimaryDropped?: boolean;
44
112
  };
113
+ /**
114
+ * Does this jar path look like it holds Minecraft itself?
115
+ *
116
+ * Loom stages Minecraft into maven-shaped local stores — its own `minecraftMaven`
117
+ * repository, and from there Gradle's dependency cache — so a path alone cannot be
118
+ * dismissed as a third-party dependency just because it sits in one. This is the
119
+ * override that keeps such a jar's version readable; see `extractVersionFromPath`
120
+ * in `minecraft-explorer-service.ts`.
121
+ */
122
+ export declare function looksLikeMinecraftArtifactPath(path: string): boolean;
45
123
  export declare function hasExactVersionToken(path: string, version: string): boolean;
46
124
  /**
47
125
  * True when a path carries the NeoForge/Forge loader version that corresponds
@@ -66,12 +144,17 @@ export declare function hasLoaderRuntimeVersionToken(path: string, mcVersion: st
66
144
  */
67
145
  export declare function inferRuntimeJarMinecraftVersion(path: string): string | undefined;
68
146
  /**
69
- * Loader a runtime jar belongs to, read from its path.
147
+ * Loader a runtime jar belongs to, read from the tool-written part of its path.
70
148
  *
71
149
  * A Loom cache holds NeoForge-patched jars under a `/neoforge/` segment
72
150
  * (`caches/fabric-loom/1.21.10/neoforge/21.10.50-beta/minecraft-merged-mojang-at-patched.jar`).
73
151
  * Serving one of those to a Fabric workspace silently validated a Fabric access
74
- * widener against NeoForge bytecode, so the loader has to travel with the jar.
152
+ * widener against NeoForge bytecode, so the loader has to travel with the jar —
153
+ * which is why NeoForge is still asked first and still wins inside a Loom cache.
154
+ *
155
+ * Fabric is asked before Forge because its markers name a specific tool
156
+ * (`fabric-loom`, `loom-cache`) while Forge's are a bare `forge` token and the
157
+ * three letters `srg`: inside a Loom cache the specific marker is the true one.
75
158
  */
76
159
  export declare function inferRuntimeJarLoader(path: string): RuntimeLoader;
77
160
  /**
@@ -169,10 +252,21 @@ export declare function resolveBinaryFallbackArtifact(svc: SourceService, input:
169
252
  /** Forwarded from the caller; unset keeps the historical decompile-on-demand behaviour. */
170
253
  allowDecompile?: boolean;
171
254
  }): Promise<ResolvedSourceArtifact | undefined>;
255
+ /**
256
+ * Describe what an artifact's indexed contents are.
257
+ *
258
+ * `sourceKind` is a derivation question — was the text produced by decompiling
259
+ * bytecode? — and only the persisted `isDecompiled` flag answers it. `origin`
260
+ * records provenance (where the bytes came from) and is deliberately not read:
261
+ * the two axes disagree in practice, most visibly for a Jar-in-Jar shell, whose
262
+ * row keeps origin "decompiled" while ingest clears the derivation flag. It
263
+ * stays in the input shape only because every caller already carries it
264
+ * alongside the fields that are read.
265
+ */
172
266
  export declare function buildArtifactContentsSummary(_svc: SourceService, input: {
173
267
  origin: ResolvedSourceArtifact["origin"];
174
268
  sourceJarPath?: string;
175
- isDecompiled?: boolean;
269
+ isDecompiled: boolean;
176
270
  qualityFlags: string[];
177
271
  }): ArtifactContentsSummary;
178
272
  export declare function buildMappingFallbackSuggestedCall(svc: SourceService, args: {
@@ -13,6 +13,121 @@ import { artifactIdForJar, resolveSourceTarget as resolveSourceTargetInternal }
13
13
  import { resolveTinyRemapperJar } from "../tiny-remapper-resolver.js";
14
14
  import { isUnobfuscatedVersion } from "../version-service.js";
15
15
  import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
16
+ /**
17
+ * The Maven group under which the Minecraft runtime artifact itself is published
18
+ * (`net.minecraft:client`, `net.minecraft:server`, the Loom merged jars).
19
+ *
20
+ * The group is necessary but NOT sufficient: Mojang also publishes ordinary
21
+ * libraries here, `net.minecraft:launchwrapper:1.12` being the long-standing one.
22
+ * Use `coordinateNamesMinecraftRuntime` rather than comparing against this
23
+ * constant directly.
24
+ */
25
+ const MINECRAFT_ARTIFACT_GROUP_ID = "net.minecraft";
26
+ /**
27
+ * ArtifactIds under MINECRAFT_ARTIFACT_GROUP_ID that name the runtime itself, as
28
+ * opposed to a library that merely shares the group. `client` and `server` are the
29
+ * published runtime jars; every Loom-staged jar this codebase recognizes elsewhere
30
+ * (`minecraft-merged`, `minecraft-common`, `minecraft-clientonly`,
31
+ * `minecraft-client`, `minecraft-server` — see `looksLikeMinecraftArtifactPath`)
32
+ * starts with `minecraft`, so that prefix covers them without having to enumerate
33
+ * Loom's naming.
34
+ */
35
+ const MINECRAFT_RUNTIME_ARTIFACT_IDS = new Set(["client", "server"]);
36
+ /**
37
+ * Does this artifactId, on its own, name the Minecraft runtime rather than a
38
+ * library that merely shares Minecraft's group?
39
+ *
40
+ * This is the rule itself, held in one place so the two ways an artifact can be
41
+ * named cannot drift apart. `coordinateNamesMinecraftRuntime` applies it to a
42
+ * parsed Maven coordinate; `extractVersionFromPath` in
43
+ * `minecraft-explorer-service.ts` applies it to the artifactId DIRECTORY of a jar
44
+ * sitting in a dependency store, which is all a `kind:"jar"` target ever offers.
45
+ * When those two disagreed, `net.minecraft:launchwrapper:1.12` was a library on
46
+ * the coordinate route and the Minecraft runtime on the path route.
47
+ */
48
+ export function artifactIdNamesMinecraftRuntime(artifactId) {
49
+ const normalized = artifactId.trim().toLowerCase();
50
+ return MINECRAFT_RUNTIME_ARTIFACT_IDS.has(normalized) || normalized.startsWith("minecraft");
51
+ }
52
+ /**
53
+ * Does this Maven coordinate name the Minecraft runtime itself?
54
+ *
55
+ * Only then does its version segment carry a real Minecraft version, and only then
56
+ * may a version or namespace derived from the artifact be read as Minecraft's.
57
+ * `net.minecraft:launchwrapper:1.12` is a real library Mojang publishes on
58
+ * libraries.minecraft.net: judging by group alone read it as the runtime, so its
59
+ * own release number was reported as a Minecraft version and a class miss inside it
60
+ * was blamed on Minecraft obfuscation.
61
+ *
62
+ * A coordinate that cannot be parsed is not shown to name the runtime, so it
63
+ * answers `false`. That matches what both call sites did with a parse failure
64
+ * before this helper existed: the resolver left its group unread and fell through
65
+ * to "not Minecraft", and `isDependencyLikeArtifact` treated it as a library.
66
+ */
67
+ export function coordinateNamesMinecraftRuntime(coordinate) {
68
+ let parsed;
69
+ try {
70
+ parsed = parseCoordinate(coordinate);
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ if (parsed.groupId !== MINECRAFT_ARTIFACT_GROUP_ID) {
76
+ return false;
77
+ }
78
+ return artifactIdNamesMinecraftRuntime(parsed.artifactId);
79
+ }
80
+ /**
81
+ * Does this artifact hold a third-party library rather than the Minecraft runtime?
82
+ *
83
+ * Callers use this to decide whether any Minecraft version derived from the
84
+ * artifact's jar path or resolver metadata may be believed, and whether a
85
+ * class-not-found miss may be blamed on Minecraft obfuscation.
86
+ *
87
+ * `provenance.dependencyResolution` alone cannot answer it. That marker is written
88
+ * only when the same `resolveArtifact` call handled a `kind:"dependency"` target,
89
+ * yet `synthesizeDependencyTarget` rewrites such a target into `{kind:"coordinate"}`
90
+ * BEFORE `buildProvenance` runs — so a dependency-route artifact and one reached by
91
+ * naming the same Maven coordinate directly persist an identical `target` and
92
+ * `resolvedFrom.coordinate`. Keyed off the marker, the question "is this a
93
+ * dependency?" was really answering "was it reached by a dependency target in this
94
+ * very call?", and a coordinate target — or a later reuse of the stored artifact by
95
+ * `artifactId` — got the vanilla answer for a dependency jar.
96
+ *
97
+ * The coordinate is route-invariant, so it decides instead, through the same
98
+ * `coordinateNamesMinecraftRuntime` the resolver's own obfuscation gate uses: a
99
+ * coordinate that does not name the Minecraft runtime names a library whose paths
100
+ * and version segment say nothing about Minecraft. Sharing the one helper is
101
+ * deliberate — the two questions are the same question asked in opposite polarity,
102
+ * and answering them differently is how `net.minecraft:launchwrapper` came to be
103
+ * read as the runtime on one side while being a library on the other.
104
+ *
105
+ * One limit remains, and it predates this predicate: an artifact reached by
106
+ * `kind:"jar"` records no coordinate at all, so it falls to the absent case and
107
+ * keeps the path-derived behaviour it has always had. The absent case is vanilla
108
+ * because that is the pre-existing default for the routes that record no coordinate
109
+ * — not because a vanilla artifact necessarily lacks one; resolving
110
+ * `net.minecraft:client:26.1` by coordinate does store it, and the runtime-name
111
+ * check is what classifies it. (`extractVersionFromPath` in
112
+ * `minecraft-explorer-service.ts` refuses on its own for a jar served out of a
113
+ * dependency cache, which is what covers the no-coordinate dependency jar.)
114
+ */
115
+ export function isDependencyLikeArtifact(input) {
116
+ if (input.provenance?.dependencyResolution != null) {
117
+ return true;
118
+ }
119
+ const coordinate = normalizeOptionalString(input.coordinate) ??
120
+ normalizeOptionalString(input.provenance?.resolvedFrom?.coordinate);
121
+ if (!coordinate) {
122
+ return false;
123
+ }
124
+ // Polarity: this predicate is TRUE for a library, so it is the negation of the
125
+ // runtime test. An unparseable coordinate answers `false` there and therefore
126
+ // `true` here — it cannot be shown to name the Minecraft runtime, so treat it as
127
+ // a dependency and suppress a Minecraft version we could not justify, rather than
128
+ // asserting one on the strength of a string we failed to read.
129
+ return !coordinateNamesMinecraftRuntime(coordinate);
130
+ }
16
131
  const VERSION_TOKEN_REGEX_CACHE = new Map();
17
132
  const LOADER_VERSION_TOKEN_REGEX_CACHE = new Map();
18
133
  const MAX_HELPER_REGEX_CACHE = 128;
@@ -32,10 +147,16 @@ function escapeRegexLiteral(value) {
32
147
  function hasPartialNetMinecraftCoverage(qualityFlags) {
33
148
  return qualityFlags.includes("partial-source-no-net-minecraft");
34
149
  }
35
- function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
36
- if (hasMinecraftNamespace) {
37
- return true;
38
- }
150
+ /**
151
+ * Does this jar path look like it holds Minecraft itself?
152
+ *
153
+ * Loom stages Minecraft into maven-shaped local stores — its own `minecraftMaven`
154
+ * repository, and from there Gradle's dependency cache — so a path alone cannot be
155
+ * dismissed as a third-party dependency just because it sits in one. This is the
156
+ * override that keeps such a jar's version readable; see `extractVersionFromPath`
157
+ * in `minecraft-explorer-service.ts`.
158
+ */
159
+ export function looksLikeMinecraftArtifactPath(path) {
39
160
  const normalizedPath = normalizePathStyle(path).toLowerCase();
40
161
  return (normalizedPath.includes("/minecraftmaven/") ||
41
162
  normalizedPath.includes("/net/minecraft/") ||
@@ -46,6 +167,9 @@ function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
46
167
  normalizedPath.includes("minecraft-client") ||
47
168
  normalizedPath.includes("minecraft-server"));
48
169
  }
170
+ function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
171
+ return hasMinecraftNamespace || looksLikeMinecraftArtifactPath(path);
172
+ }
49
173
  export function hasExactVersionToken(path, version) {
50
174
  const normalizedPath = normalizePathStyle(path).toLowerCase();
51
175
  const normalizedVersion = version.trim().toLowerCase();
@@ -96,24 +220,64 @@ export function inferRuntimeJarMinecraftVersion(path) {
96
220
  return RUNTIME_JAR_VERSION_REGEX.exec(normalizePathStyle(path))?.[1];
97
221
  }
98
222
  /**
99
- * Loader a runtime jar belongs to, read from its path.
223
+ * Segment that opens a directory tree a BUILD TOOL wrote: a dot-directory
224
+ * (`.gradle`, `.gradle-user-home`, `.m2`), Gradle's `caches` root, or a project
225
+ * `build` directory. Everything below the deepest such segment was laid out by a
226
+ * tool and names what it holds; everything above it is the user's own directory
227
+ * names and names nothing.
228
+ */
229
+ const RUNTIME_JAR_LAYOUT_ROOT_RE = /^(?:\.[^/]*|caches|build)$/;
230
+ /**
231
+ * The part of a runtime jar path that a build tool wrote: everything below the
232
+ * deepest layout root, or the bare file name when the path never enters one.
233
+ *
234
+ * Judged over the WHOLE path, an ancestor directory decided the loader — a
235
+ * Fabric workspace checked out under `~/dev/forge/` was served "forge", and
236
+ * `moddev-notes` or `srg-test` anywhere above the jar was enough to name a
237
+ * loader for an artifact that names none.
238
+ */
239
+ function runtimeJarLayoutRelativePath(path) {
240
+ const segments = normalizePathStyle(path)
241
+ .toLowerCase()
242
+ .split("/")
243
+ .filter((segment) => segment.length > 0);
244
+ for (let index = segments.length - 2; index >= 0; index -= 1) {
245
+ if (RUNTIME_JAR_LAYOUT_ROOT_RE.test(segments[index] ?? "")) {
246
+ return segments.slice(index + 1).join("/");
247
+ }
248
+ }
249
+ return segments[segments.length - 1] ?? "";
250
+ }
251
+ /**
252
+ * Loader a runtime jar belongs to, read from the tool-written part of its path.
100
253
  *
101
254
  * A Loom cache holds NeoForge-patched jars under a `/neoforge/` segment
102
255
  * (`caches/fabric-loom/1.21.10/neoforge/21.10.50-beta/minecraft-merged-mojang-at-patched.jar`).
103
256
  * Serving one of those to a Fabric workspace silently validated a Fabric access
104
- * widener against NeoForge bytecode, so the loader has to travel with the jar.
257
+ * widener against NeoForge bytecode, so the loader has to travel with the jar —
258
+ * which is why NeoForge is still asked first and still wins inside a Loom cache.
259
+ *
260
+ * Fabric is asked before Forge because its markers name a specific tool
261
+ * (`fabric-loom`, `loom-cache`) while Forge's are a bare `forge` token and the
262
+ * three letters `srg`: inside a Loom cache the specific marker is the true one.
105
263
  */
106
264
  export function inferRuntimeJarLoader(path) {
107
- const lower = normalizePathStyle(path).toLowerCase();
108
- if (lower.includes("neoforge") || lower.includes("neoform") || lower.includes("moddev")) {
265
+ const layoutPath = runtimeJarLayoutRelativePath(path);
266
+ if (layoutPath.includes("neoforge") ||
267
+ layoutPath.includes("neoform") ||
268
+ layoutPath.includes("moddev")) {
109
269
  return "neoforge";
110
270
  }
111
- if (/(^|[/\-_])forge([/\-_.]|$)/.test(lower) || lower.includes("forge_gradle") || lower.includes("srg")) {
112
- return "forge";
113
- }
114
- if (lower.includes("fabric-loom") || lower.includes("loom-cache") || lower.includes("intermediary")) {
271
+ if (layoutPath.includes("fabric-loom") ||
272
+ layoutPath.includes("loom-cache") ||
273
+ layoutPath.includes("intermediary")) {
115
274
  return "fabric";
116
275
  }
276
+ if (/(^|[/\-_])forge([/\-_.]|$)/.test(layoutPath) ||
277
+ layoutPath.includes("forge_gradle") ||
278
+ layoutPath.includes("srg")) {
279
+ return "forge";
280
+ }
117
281
  return "unknown";
118
282
  }
119
283
  /**
@@ -1032,8 +1196,19 @@ async function computeBinaryRemapGate(svc, input) {
1032
1196
  warnings: baseline.warnings
1033
1197
  };
1034
1198
  }
1199
+ /**
1200
+ * Describe what an artifact's indexed contents are.
1201
+ *
1202
+ * `sourceKind` is a derivation question — was the text produced by decompiling
1203
+ * bytecode? — and only the persisted `isDecompiled` flag answers it. `origin`
1204
+ * records provenance (where the bytes came from) and is deliberately not read:
1205
+ * the two axes disagree in practice, most visibly for a Jar-in-Jar shell, whose
1206
+ * row keeps origin "decompiled" while ingest clears the derivation flag. It
1207
+ * stays in the input shape only because every caller already carries it
1208
+ * alongside the fields that are read.
1209
+ */
1035
1210
  export function buildArtifactContentsSummary(_svc, input) {
1036
- const sourceKind = input.isDecompiled || input.origin === "decompiled" || !normalizeOptionalString(input.sourceJarPath)
1211
+ const sourceKind = input.isDecompiled || !normalizeOptionalString(input.sourceJarPath)
1037
1212
  ? "decompiled-binary"
1038
1213
  : "source-jar";
1039
1214
  const sourceCoverage = hasPartialNetMinecraftCoverage(input.qualityFlags) ? "partial" : "full";
@@ -1210,17 +1385,16 @@ export async function resolveArtifact(svc, input) {
1210
1385
  let resolvedTarget = { kind, value };
1211
1386
  let resolvedVersion;
1212
1387
  let versionSourceDiscovery;
1213
- let runtimeNamesUnobfuscated = false;
1214
1388
  if (kind === "version") {
1215
1389
  const versionJar = await svc.versionService.resolveVersionJar(value);
1216
1390
  resolvedVersion = versionJar.version;
1217
- runtimeNamesUnobfuscated = isUnobfuscatedVersion(resolvedVersion);
1218
1391
  resolvedTarget = {
1219
1392
  kind: "jar",
1220
1393
  value: versionJar.jarPath
1221
1394
  };
1222
1395
  warnings.push(`Resolved Minecraft ${versionJar.version} from ${versionJar.clientJarUrl}.`);
1223
1396
  }
1397
+ let coordinateIsMinecraftRuntime = false;
1224
1398
  if (kind === "coordinate") {
1225
1399
  try {
1226
1400
  resolvedVersion = parseCoordinate(value).version;
@@ -1228,15 +1402,32 @@ export async function resolveArtifact(svc, input) {
1228
1402
  catch {
1229
1403
  // coordinate validity is validated by resolver
1230
1404
  }
1405
+ // Swallows its own parse failure and answers false, which is what the
1406
+ // unread group left behind here before.
1407
+ coordinateIsMinecraftRuntime = coordinateNamesMinecraftRuntime(value);
1231
1408
  }
1232
- if (!runtimeNamesUnobfuscated && resolvedVersion && isUnobfuscatedVersion(resolvedVersion)) {
1233
- runtimeNamesUnobfuscated = true;
1234
- }
1409
+ // `isUnobfuscatedVersion` answers "does this MINECRAFT version ship unobfuscated
1410
+ // names in its runtime jar?", so it may only be asked about a string that really is
1411
+ // a Minecraft version. A kind="version" target always carries one. A coordinate
1412
+ // carries one only when it names the Minecraft runtime artifact itself
1413
+ // (net.minecraft:client:26.1); for any other coordinate the version segment is a
1414
+ // third-party library's own release number — including a library published under
1415
+ // Minecraft's own group, such as net.minecraft:launchwrapper, which is why the
1416
+ // group alone cannot answer this and `coordinateNamesMinecraftRuntime` checks the
1417
+ // artifactId too. Without this gate a dependency such as org.jetbrains:annotations:26.0.2
1418
+ // parses as an "unobfuscated Minecraft version" and short-circuits
1419
+ // applyMappingPipeline into reporting mappingApplied="mojang" with no remap
1420
+ // performed, no verification, and no dependency-mapping-unverified warning.
1421
+ // A kind="dependency" target is excluded outright regardless of its group,
1422
+ // because binary remap is force-disabled for it (see forceBinaryRemapDisabled
1423
+ // below), so its mapping is never actually enforced either way.
1424
+ const versionNamesMinecraft = kind === "version" ||
1425
+ (kind === "coordinate" && !dependencyOrigin && coordinateIsMinecraftRuntime);
1426
+ const minecraftVersion = versionNamesMinecraft ? resolvedVersion : undefined;
1427
+ const runtimeNamesUnobfuscated = minecraftVersion !== undefined && isUnobfuscatedVersion(minecraftVersion);
1235
1428
  let effectiveMapping = mapping;
1236
- if ((mapping === "intermediary" || mapping === "yarn") &&
1237
- resolvedVersion &&
1238
- isUnobfuscatedVersion(resolvedVersion)) {
1239
- warnings.push(`Version ${resolvedVersion} is unobfuscated; ${mapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
1429
+ if ((mapping === "intermediary" || mapping === "yarn") && runtimeNamesUnobfuscated) {
1430
+ warnings.push(`Version ${minecraftVersion} is unobfuscated; ${mapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
1240
1431
  effectiveMapping = "obfuscated";
1241
1432
  }
1242
1433
  if (kind === "version" &&
@@ -1382,7 +1573,10 @@ export async function resolveArtifact(svc, input) {
1382
1573
  resolved.sourceJarPath === versionSourceDiscovery.selectedSourceJarPath) {
1383
1574
  provenance.companionSourceJars = versionSourceDiscovery.companionSourceJarPaths;
1384
1575
  }
1385
- if (dependencyOrigin && dependencyRequestedMapping && dependencyRequestedMapping !== "obfuscated") {
1576
+ if (dependencyOrigin &&
1577
+ dependencyRequestedMapping &&
1578
+ dependencyRequestedMapping !== "obfuscated" &&
1579
+ mappingDecision.qualityFlags.includes("dependency-mapping-unverified")) {
1386
1580
  const coord = resolved.coordinate ?? value;
1387
1581
  warnings.push(`Dependency artifact ${coord} mapping "${dependencyRequestedMapping}" is not enforced (binary remap is disabled for non-vanilla artifacts); the JAR is returned in its native namespace and mappingApplied is reported as "obfuscated" with qualityFlag "dependency-mapping-unverified". Caller must validate symbol availability.`);
1388
1582
  }
@@ -1393,7 +1587,17 @@ export async function resolveArtifact(svc, input) {
1393
1587
  resolved.requestedMapping = effectiveMapping;
1394
1588
  resolved.mappingApplied = mappingDecision.mappingApplied;
1395
1589
  resolved.provenance = provenance;
1396
- resolved.qualityFlags = [...mappingDecision.qualityFlags];
1590
+ // MERGE, never assign. The mapping pipeline builds its list from an empty
1591
+ // array and knows only what mapping did, so anything the resolver already
1592
+ // observed about the artifact itself - "binary-jar-no-classes" is the first
1593
+ // such flag - is invisible to it. Overwriting the list here would drop those
1594
+ // observations before dedupe, before indexing, and before the response is
1595
+ // built, so the caller would never learn what the resolver saw. Same shape
1596
+ // as the binary-fallback path above.
1597
+ resolved.qualityFlags = dedupeQualityFlags([
1598
+ ...(resolved.qualityFlags ?? []),
1599
+ ...mappingDecision.qualityFlags
1600
+ ]);
1397
1601
  if (versionSourceDiscovery?.candidateArtifacts.length) {
1398
1602
  resolved.qualityFlags.push("source-jar-found");
1399
1603
  }
@@ -65,6 +65,21 @@ export declare function buildClassSourceNotFoundError(svc: SourceService, input:
65
65
  mappingApplied: SourceMapping;
66
66
  requestedMapping: SourceMapping;
67
67
  qualityFlags: string[];
68
+ /**
69
+ * The mapping the CALLER wrote in the request, undefined when they omitted it.
70
+ * Distinct from `requestedMapping`, which falls back to the artifact's own
71
+ * namespace: only the caller-supplied value can tell whether the obfuscated
72
+ * namespace hint would be re-asking for an argument that is already present.
73
+ */
74
+ callerSuppliedMapping?: SourceMapping;
75
+ /**
76
+ * Whether the REQUESTED artifact is a native dependency, as decided by
77
+ * `isDependencyLikeArtifact`. Such artifacts are handed
78
+ * `mappingApplied: "obfuscated"` by substitution rather than by being an
79
+ * obfuscated index, which the obfuscated namespace hint must not mistake for
80
+ * a missing mapping argument.
81
+ */
82
+ nativeDependency?: boolean;
68
83
  attemptedBinaryFallback: boolean;
69
84
  filePath?: string;
70
85
  targetKind?: string;