@adhisang/minecraft-modding-mcp 5.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/access-widener-parser.d.ts +1 -0
  3. package/dist/access-widener-parser.js +6 -3
  4. package/dist/cache-registry.js +35 -31
  5. package/dist/config.js +1 -1
  6. package/dist/decompiler/vineflower.js +8 -2
  7. package/dist/entry-tools/analyze-mod-service.js +2 -1
  8. package/dist/entry-tools/analyze-symbol-service.js +18 -9
  9. package/dist/entry-tools/batch-class-members-service.d.ts +2 -0
  10. package/dist/entry-tools/batch-class-members-service.js +1 -0
  11. package/dist/entry-tools/compare-minecraft-service.js +1 -1
  12. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -1
  13. package/dist/entry-tools/inspect-minecraft/handlers/file.js +26 -1
  14. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +2 -1
  15. package/dist/entry-tools/inspect-minecraft/internal.js +2 -0
  16. package/dist/entry-tools/response-contract.d.ts +2 -0
  17. package/dist/entry-tools/response-contract.js +2 -2
  18. package/dist/entry-tools/validate-project/cases/project-summary.js +30 -12
  19. package/dist/entry-tools/validate-project-service.d.ts +4 -4
  20. package/dist/entry-tools/verify-mixin-target-service.js +7 -7
  21. package/dist/index.js +21 -16
  22. package/dist/java-process.js +1 -1
  23. package/dist/json-rpc-framing.d.ts +4 -0
  24. package/dist/json-rpc-framing.js +23 -1
  25. package/dist/mapping/internal-types.d.ts +17 -0
  26. package/dist/mapping/loaders/tiny-maven.js +46 -20
  27. package/dist/mapping/lookup.d.ts +12 -1
  28. package/dist/mapping/lookup.js +53 -1
  29. package/dist/mapping-service.js +37 -22
  30. package/dist/minecraft-explorer-service.js +2 -1
  31. package/dist/mixin/annotation-validators.js +21 -3
  32. package/dist/mixin/parsed-validator.js +1 -0
  33. package/dist/mixin-parser.js +71 -3
  34. package/dist/mod-decompile-service.d.ts +2 -0
  35. package/dist/mod-decompile-service.js +19 -2
  36. package/dist/mod-remap-service.js +6 -5
  37. package/dist/mojang-tiny-mapping-service.js +5 -2
  38. package/dist/nbt/java-nbt-codec.js +40 -16
  39. package/dist/nbt/json-patch.js +51 -12
  40. package/dist/nbt/typed-json.d.ts +1 -0
  41. package/dist/nbt/typed-json.js +12 -2
  42. package/dist/nbt/types.d.ts +2 -2
  43. package/dist/path-converter.js +10 -3
  44. package/dist/registry-service.d.ts +2 -0
  45. package/dist/registry-service.js +16 -1
  46. package/dist/repo-downloader.js +4 -3
  47. package/dist/source/artifact-resolver.js +1 -1
  48. package/dist/source/class-source/members-builder.d.ts +26 -0
  49. package/dist/source/class-source/members-builder.js +31 -4
  50. package/dist/source/class-source/snippet-builder.js +6 -0
  51. package/dist/source/class-source-helpers.d.ts +4 -4
  52. package/dist/source/class-source-helpers.js +12 -11
  53. package/dist/source/class-source.d.ts +19 -0
  54. package/dist/source/class-source.js +87 -26
  55. package/dist/source/file-access.js +3 -2
  56. package/dist/source/indexer.js +24 -7
  57. package/dist/source/lifecycle/mapping-helpers.js +28 -3
  58. package/dist/source/lifecycle/runtime-check.js +20 -6
  59. package/dist/source/member-pattern.d.ts +16 -0
  60. package/dist/source/member-pattern.js +27 -0
  61. package/dist/source/search.js +23 -8
  62. package/dist/source/validate-mixin/pipeline/resolve.js +23 -3
  63. package/dist/source/workspace-target.js +2 -1
  64. package/dist/source-resolver.js +2 -2
  65. package/dist/source-service.d.ts +8 -4
  66. package/dist/source-service.js +9 -1
  67. package/dist/stdio-supervisor.d.ts +2 -2
  68. package/dist/stdio-supervisor.js +29 -9
  69. package/dist/storage/db.js +24 -1
  70. package/dist/storage/files-repo.d.ts +3 -0
  71. package/dist/storage/files-repo.js +43 -37
  72. package/dist/storage/symbols-repo.d.ts +9 -0
  73. package/dist/storage/symbols-repo.js +47 -19
  74. package/dist/symbols/symbol-extractor.js +1 -1
  75. package/dist/tool-guidance.d.ts +2 -1
  76. package/dist/tool-guidance.js +27 -2
  77. package/dist/tool-schemas.d.ts +15 -4
  78. package/dist/tool-schemas.js +102 -97
  79. package/dist/warning-details.d.ts +12 -0
  80. package/dist/warning-details.js +17 -0
  81. package/dist/workspace-mapping-service.js +9 -0
  82. package/docs/tool-reference.md +23 -0
  83. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
2
  import { createError, ERROR_CODES } from "../errors.js";
3
- import { assertValidTypedNbtDocument, validateTypedNbtDocument } from "./typed-json.js";
3
+ import { assertValidTypedNbtDocument, validateTypedNbtDocument, validateTypedNbtNode } from "./typed-json.js";
4
4
  function isRecord(value) {
5
5
  return (typeof value === "object" &&
6
6
  value !== null &&
@@ -136,6 +136,9 @@ function parseArrayIndex(token, length, options) {
136
136
  }
137
137
  return index;
138
138
  }
139
+ function isTypedNbtNode(value) {
140
+ return isRecord(value) && typeof value.type === "string";
141
+ }
139
142
  function resolveParent(root, tokens, opIndex, path) {
140
143
  if (tokens.length === 0) {
141
144
  patchConflict("Operation path does not reference a child location.", {
@@ -144,6 +147,8 @@ function resolveParent(root, tokens, opIndex, path) {
144
147
  });
145
148
  }
146
149
  let cursor = root;
150
+ let enclosingNode;
151
+ let enclosingPointer = "";
147
152
  for (let i = 0; i < tokens.length - 1; i += 1) {
148
153
  const token = tokens[i];
149
154
  const pointer = `/${tokens.slice(0, i + 1).join("/")}`;
@@ -154,6 +159,10 @@ function resolveParent(root, tokens, opIndex, path) {
154
159
  path: pointer
155
160
  });
156
161
  cursor = cursor[index];
162
+ if (isTypedNbtNode(cursor)) {
163
+ enclosingNode = cursor;
164
+ enclosingPointer = pointer;
165
+ }
157
166
  continue;
158
167
  }
159
168
  if (!isRecord(cursor)) {
@@ -169,8 +178,12 @@ function resolveParent(root, tokens, opIndex, path) {
169
178
  });
170
179
  }
171
180
  cursor = cursor[token];
181
+ if (isTypedNbtNode(cursor)) {
182
+ enclosingNode = cursor;
183
+ enclosingPointer = pointer;
184
+ }
172
185
  }
173
- return { parent: cursor, key: tokens[tokens.length - 1] };
186
+ return { parent: cursor, key: tokens[tokens.length - 1], enclosingNode, enclosingPointer };
174
187
  }
175
188
  function readValueAtPath(root, tokens, opIndex, path) {
176
189
  if (tokens.length === 0) {
@@ -205,8 +218,10 @@ function readValueAtPath(root, tokens, opIndex, path) {
205
218
  }
206
219
  return cursor;
207
220
  }
208
- function assertTypedNbtInvariant(root, opIndex, path) {
209
- const validation = validateTypedNbtDocument(root);
221
+ function assertTypedNbtInvariant(subtree, opIndex, path) {
222
+ const validation = "root" in subtree
223
+ ? validateTypedNbtDocument(subtree.root)
224
+ : validateTypedNbtNode(subtree.node, subtree.pointer);
210
225
  if (!validation.ok) {
211
226
  patchConflict("JSON Patch operation produced invalid typed NBT JSON.", {
212
227
  opIndex,
@@ -241,11 +256,13 @@ export function applyJsonPatch(document, patch) {
241
256
  }
242
257
  if (operation.op === "add") {
243
258
  const nextValue = structuredClone(operation.value);
259
+ let subtree = { root: working };
244
260
  if (tokens.length === 0) {
245
261
  working = nextValue;
262
+ subtree = { root: working };
246
263
  }
247
264
  else {
248
- const { parent, key } = resolveParent(working, tokens, i, operation.path);
265
+ const { parent, key, enclosingNode, enclosingPointer } = resolveParent(working, tokens, i, operation.path);
249
266
  if (Array.isArray(parent)) {
250
267
  const index = parseArrayIndex(key, parent.length, {
251
268
  allowAppend: true,
@@ -255,7 +272,12 @@ export function applyJsonPatch(document, patch) {
255
272
  parent.splice(index, 0, nextValue);
256
273
  }
257
274
  else if (isRecord(parent)) {
258
- parent[key] = nextValue;
275
+ Object.defineProperty(parent, key, {
276
+ value: nextValue,
277
+ enumerable: true,
278
+ writable: true,
279
+ configurable: true
280
+ });
259
281
  }
260
282
  else {
261
283
  patchConflict("Add target parent is not a container.", {
@@ -263,9 +285,13 @@ export function applyJsonPatch(document, patch) {
263
285
  jsonPointer: operation.path
264
286
  });
265
287
  }
288
+ subtree =
289
+ enclosingNode === undefined
290
+ ? { root: working }
291
+ : { node: enclosingNode, pointer: enclosingPointer };
266
292
  }
267
293
  changed = true;
268
- assertTypedNbtInvariant(working, i, operation.path);
294
+ assertTypedNbtInvariant(subtree, i, operation.path);
269
295
  continue;
270
296
  }
271
297
  if (operation.op === "remove") {
@@ -275,7 +301,7 @@ export function applyJsonPatch(document, patch) {
275
301
  jsonPointer: operation.path
276
302
  });
277
303
  }
278
- const { parent, key } = resolveParent(working, tokens, i, operation.path);
304
+ const { parent, key, enclosingNode, enclosingPointer } = resolveParent(working, tokens, i, operation.path);
279
305
  if (Array.isArray(parent)) {
280
306
  const index = parseArrayIndex(key, parent.length, {
281
307
  allowAppend: false,
@@ -300,16 +326,20 @@ export function applyJsonPatch(document, patch) {
300
326
  });
301
327
  }
302
328
  changed = true;
303
- assertTypedNbtInvariant(working, i, operation.path);
329
+ assertTypedNbtInvariant(enclosingNode === undefined
330
+ ? { root: working }
331
+ : { node: enclosingNode, pointer: enclosingPointer }, i, operation.path);
304
332
  continue;
305
333
  }
306
334
  if (operation.op === "replace") {
307
335
  const nextValue = structuredClone(operation.value);
336
+ let subtree = { root: working };
308
337
  if (tokens.length === 0) {
309
338
  working = nextValue;
339
+ subtree = { root: working };
310
340
  }
311
341
  else {
312
- const { parent, key } = resolveParent(working, tokens, i, operation.path);
342
+ const { parent, key, enclosingNode, enclosingPointer } = resolveParent(working, tokens, i, operation.path);
313
343
  if (Array.isArray(parent)) {
314
344
  const index = parseArrayIndex(key, parent.length, {
315
345
  allowAppend: false,
@@ -325,7 +355,12 @@ export function applyJsonPatch(document, patch) {
325
355
  jsonPointer: operation.path
326
356
  });
327
357
  }
328
- parent[key] = nextValue;
358
+ Object.defineProperty(parent, key, {
359
+ value: nextValue,
360
+ enumerable: true,
361
+ writable: true,
362
+ configurable: true
363
+ });
329
364
  }
330
365
  else {
331
366
  patchConflict("Replace target parent is not a container.", {
@@ -333,9 +368,13 @@ export function applyJsonPatch(document, patch) {
333
368
  jsonPointer: operation.path
334
369
  });
335
370
  }
371
+ subtree =
372
+ enclosingNode === undefined
373
+ ? { root: working }
374
+ : { node: enclosingNode, pointer: enclosingPointer };
336
375
  }
337
376
  changed = true;
338
- assertTypedNbtInvariant(working, i, operation.path);
377
+ assertTypedNbtInvariant(subtree, i, operation.path);
339
378
  continue;
340
379
  }
341
380
  }
@@ -5,6 +5,7 @@ type ValidationResult = {
5
5
  ok: false;
6
6
  issue: TypedNbtValidationIssue;
7
7
  };
8
+ export declare function validateTypedNbtNode(value: unknown, pointer: string): ValidationResult;
8
9
  export declare function validateTypedNbtDocument(value: unknown): ValidationResult;
9
10
  export declare function assertValidTypedNbtDocument(value: unknown): asserts value is TypedNbtDocument;
10
11
  export type { TypedNbtDocument, TypedNbtValidationIssue } from "./types.js";
@@ -16,6 +16,7 @@ const NODE_TYPES = [
16
16
  const LIST_ELEMENT_TYPES = ["end", ...NODE_TYPES];
17
17
  const NODE_TYPE_SET = new Set(NODE_TYPES);
18
18
  const LIST_ELEMENT_TYPE_SET = new Set(LIST_ELEMENT_TYPES);
19
+ const NON_FINITE_FLOAT_SENTINELS = new Set(["NaN", "Infinity", "-Infinity"]);
19
20
  const INT64_MIN = -9223372036854775808n;
20
21
  const INT64_MAX = 9223372036854775807n;
21
22
  function isRecord(value) {
@@ -119,8 +120,14 @@ function validateNode(value, pointer) {
119
120
  return validateLongString(node.value, `${pointer}/value`);
120
121
  case "float":
121
122
  case "double":
122
- if (typeof node.value !== "number" || !Number.isFinite(node.value)) {
123
- return fail(`${pointer}/value`, "finite-number", node.value);
123
+ if (typeof node.value === "string") {
124
+ if (!NON_FINITE_FLOAT_SENTINELS.has(node.value)) {
125
+ return fail(`${pointer}/value`, "number-or-non-finite-sentinel", node.value);
126
+ }
127
+ return { ok: true };
128
+ }
129
+ if (typeof node.value !== "number") {
130
+ return fail(`${pointer}/value`, "number-or-non-finite-sentinel", node.value);
124
131
  }
125
132
  return { ok: true };
126
133
  case "string":
@@ -180,6 +187,9 @@ function validateNode(value, pointer) {
180
187
  return fail(`${pointer}/type`, "nbt-node-type", nodeType);
181
188
  }
182
189
  }
190
+ export function validateTypedNbtNode(value, pointer) {
191
+ return validateNode(value, pointer);
192
+ }
183
193
  export function validateTypedNbtDocument(value) {
184
194
  if (!isRecord(value)) {
185
195
  return fail("", "typed-nbt-document", value);
@@ -15,10 +15,10 @@ export type NbtNode = {
15
15
  value: string;
16
16
  } | {
17
17
  type: "float";
18
- value: number;
18
+ value: number | "NaN" | "Infinity" | "-Infinity";
19
19
  } | {
20
20
  type: "double";
21
- value: number;
21
+ value: number | "NaN" | "Infinity" | "-Infinity";
22
22
  } | {
23
23
  type: "byteArray";
24
24
  value: number[];
@@ -3,7 +3,7 @@ import { createError, ERROR_CODES } from "./errors.js";
3
3
  const WINDOWS_DRIVE_PATH = /^[A-Za-z]:[\\/]/;
4
4
  const MALFORMED_WINDOWS_DRIVE_PATH = /^[A-Za-z]:(?![\\/])/;
5
5
  const WSL_MOUNT_PATH = /^\/mnt\/[a-z](?:\/|$)/i;
6
- const UNC_WSL_PATH = /^(?:\\\\wsl\$\\|\/\/wsl\$\/)/i;
6
+ const UNC_WSL_PATH = /^(?:\\\\(?:wsl\$|wsl\.localhost)\\|\/\/(?:wsl\$|wsl\.localhost)\/)/i;
7
7
  function normalizeToUnixSlashes(value) {
8
8
  return value.replace(/\\/g, "/");
9
9
  }
@@ -12,10 +12,11 @@ function normalizeToWindowsSlashes(value) {
12
12
  }
13
13
  function parseUncWslPath(pathValue) {
14
14
  const normalized = normalizeToUnixSlashes(pathValue);
15
- if (!normalized.toLowerCase().startsWith("//wsl$/")) {
15
+ const prefixMatch = normalized.match(/^\/\/(?:wsl\$|wsl\.localhost)\//i);
16
+ if (!prefixMatch) {
16
17
  return undefined;
17
18
  }
18
- const remainder = normalized.slice("//wsl$/".length).replace(/^\/+/, "");
19
+ const remainder = normalized.slice(prefixMatch[0].length).replace(/^\/+/, "");
19
20
  const slashIndex = remainder.indexOf("/");
20
21
  if (slashIndex < 0) {
21
22
  return {
@@ -99,6 +100,12 @@ function toWslPath(pathValue, runtime, field) {
99
100
  }
100
101
  const parsed = parseUncWslPath(pathValue);
101
102
  if (parsed) {
103
+ const distro = runtime.wslDistro?.trim();
104
+ if (!distro || parsed.distro.toLowerCase() !== distro.toLowerCase()) {
105
+ throwInvalidPath(pathValue, distro
106
+ ? `UNC WSL path targets distro "${parsed.distro}", but this server runs in distro "${distro}".`
107
+ : `Cannot resolve UNC WSL path for distro "${parsed.distro}" without a known WSL distro name.`, runtime, field);
108
+ }
102
109
  const rest = parsed.innerPath.replace(/^\/+/, "");
103
110
  return rest ? `/${rest}` : "/";
104
111
  }
@@ -28,9 +28,11 @@ export declare class RegistryService {
28
28
  private readonly config;
29
29
  private readonly versionService;
30
30
  private readonly registryCache;
31
+ private readonly loadLocks;
31
32
  constructor(config: Config, versionService: VersionService);
32
33
  getRegistryData(input: GetRegistryDataInput): Promise<GetRegistryDataOutput>;
33
34
  private loadRegistries;
35
+ private loadRegistriesInternal;
34
36
  private loadExistingRegistries;
35
37
  private readRegistryFileOrThrow;
36
38
  private isInvalidRegistrySnapshot;
@@ -126,7 +126,7 @@ function runDataGen(serverJarPath, outputDir, version) {
126
126
  ];
127
127
  log("info", "registry.datagen.start", { version, isLegacy, serverJarPath, outputDir });
128
128
  const proc = spawn("java", args, {
129
- stdio: ["ignore", "pipe", "pipe"],
129
+ stdio: ["ignore", "ignore", "pipe"],
130
130
  cwd: outputDir
131
131
  });
132
132
  let stderr = "";
@@ -194,6 +194,7 @@ export class RegistryService {
194
194
  config;
195
195
  versionService;
196
196
  registryCache = new Map();
197
+ loadLocks = new Map();
197
198
  constructor(config, versionService) {
198
199
  this.config = config;
199
200
  this.versionService = versionService;
@@ -270,6 +271,20 @@ export class RegistryService {
270
271
  };
271
272
  }
272
273
  async loadRegistries(version, warnings) {
274
+ const existingLock = this.loadLocks.get(version);
275
+ if (existingLock) {
276
+ return existingLock;
277
+ }
278
+ const loadPromise = this.loadRegistriesInternal(version, warnings);
279
+ this.loadLocks.set(version, loadPromise);
280
+ try {
281
+ return await loadPromise;
282
+ }
283
+ finally {
284
+ this.loadLocks.delete(version);
285
+ }
286
+ }
287
+ async loadRegistriesInternal(version, warnings) {
273
288
  const cached = this.registryCache.get(version);
274
289
  if (cached) {
275
290
  // LRU touch: move to most-recently-used so the bound evicts the true
@@ -40,7 +40,6 @@ export async function downloadToCache(url, destinationPath, opts = {}) {
40
40
  const timer = setTimeout(() => timeout.abort(), timeoutMs);
41
41
  try {
42
42
  const response = await fetchFn(url, { signal: timeout.signal });
43
- clearTimeout(timer);
44
43
  const status = response.status;
45
44
  if (status === 404) {
46
45
  return { ok: false, statusCode: status };
@@ -71,7 +70,7 @@ export async function downloadToCache(url, destinationPath, opts = {}) {
71
70
  }
72
71
  else {
73
72
  const readable = Readable.fromWeb(response.body);
74
- await pipeline(readable, createWriteStream(tempPath));
73
+ await pipeline(readable, createWriteStream(tempPath), { signal: timeout.signal });
75
74
  }
76
75
  const contentLength = statSync(tempPath).size;
77
76
  renameSync(tempPath, destinationPath);
@@ -95,13 +94,15 @@ export async function downloadToCache(url, destinationPath, opts = {}) {
95
94
  }
96
95
  }
97
96
  catch (caughtError) {
98
- clearTimeout(timer);
99
97
  if (attempt >= maxRetries) {
100
98
  throw caughtError instanceof Error ? caughtError : new Error(String(caughtError));
101
99
  }
102
100
  await sleep(retryDelay(200, attempt));
103
101
  attempt += 1;
104
102
  }
103
+ finally {
104
+ clearTimeout(timer);
105
+ }
105
106
  }
106
107
  }
107
108
  export function defaultDownloadPath(cacheDir, url) {
@@ -54,7 +54,7 @@ export function hasExactVersionToken(path, version) {
54
54
  // Avoid prefix false-positives like "1.21.1" matching "1.21.10".
55
55
  const cached = VERSION_TOKEN_REGEX_CACHE.get(normalizedVersion);
56
56
  const pattern = cached
57
- ?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}([^0-9a-z]|$)`, "i"));
57
+ ?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}(?![0-9a-z]|\\.[0-9])`, "i"));
58
58
  return pattern.test(normalizedPath);
59
59
  }
60
60
  function inferMergedRuntimeNamespaceHint(path) {
@@ -76,3 +76,29 @@ export declare function projectMembersForWire(slice: {
76
76
  fields: SignatureMember[];
77
77
  methods: SignatureMember[];
78
78
  }, includeInherited: boolean, keepFieldDescriptors?: boolean): WireMembersBlock;
79
+ /**
80
+ * Token-projection level for a member listing, narrowing the per-member fields
81
+ * an agent receives:
82
+ * - `full` (default): the complete {@link WireMember} shape (current behavior).
83
+ * - `signatures`: name + javaSignature (drops jvmDescriptor) — enough to read
84
+ * the API without overload descriptors.
85
+ * - `names`: member name only — enough for an "does this member exist?" check.
86
+ * The block-level hoisted `ownerFqn` and any per-member `ownerFqn` (the
87
+ * multi-owner / includeInherited case) are preserved in every level so members
88
+ * stay attributable.
89
+ */
90
+ export type MemberProjection = "names" | "signatures" | "full";
91
+ export type ProjectedMember = {
92
+ name: string;
93
+ javaSignature?: string;
94
+ jvmDescriptor?: string;
95
+ ownerFqn?: string;
96
+ isSynthetic?: boolean;
97
+ };
98
+ export type ProjectedMembersBlock = {
99
+ ownerFqn?: string;
100
+ constructors: ProjectedMember[];
101
+ fields: ProjectedMember[];
102
+ methods: ProjectedMember[];
103
+ };
104
+ export declare function projectMembersByLevel(block: WireMembersBlock, level: MemberProjection): ProjectedMembersBlock;
@@ -1,3 +1,4 @@
1
+ import { matchesMemberPattern } from "../member-pattern.js";
1
2
  export async function remapAndCountMembers(svc, input) {
2
3
  const remap = async (members, kind) => {
3
4
  if (input.version == null) {
@@ -10,10 +11,10 @@ export async function remapAndCountMembers(svc, input) {
10
11
  let fields = await remap(input.signatureFields, "field");
11
12
  let methods = await remap(input.signatureMethods, "method");
12
13
  if (input.requestedMapping !== input.mappingApplied && input.memberPattern) {
13
- const lowerPattern = input.memberPattern.toLowerCase();
14
- constructors = constructors.filter((m) => m.name.toLowerCase().includes(lowerPattern));
15
- fields = fields.filter((m) => m.name.toLowerCase().includes(lowerPattern));
16
- methods = methods.filter((m) => m.name.toLowerCase().includes(lowerPattern));
14
+ const pattern = input.memberPattern;
15
+ constructors = constructors.filter((m) => matchesMemberPattern(m.name, pattern));
16
+ fields = fields.filter((m) => matchesMemberPattern(m.name, pattern));
17
+ methods = methods.filter((m) => matchesMemberPattern(m.name, pattern));
17
18
  }
18
19
  const counts = {
19
20
  constructors: constructors.length,
@@ -90,4 +91,30 @@ export function projectMembersForWire(slice, includeInherited, keepFieldDescript
90
91
  methods: slice.methods.map((m) => toWire(m, true))
91
92
  };
92
93
  }
94
+ export function projectMembersByLevel(block, level) {
95
+ if (level === "full") {
96
+ return block;
97
+ }
98
+ const reduce = (m) => {
99
+ if (level === "names") {
100
+ return {
101
+ name: m.name,
102
+ ...(m.ownerFqn ? { ownerFqn: m.ownerFqn } : {})
103
+ };
104
+ }
105
+ // "signatures": keep the readable signature, drop the JVM descriptor.
106
+ return {
107
+ name: m.name,
108
+ javaSignature: m.javaSignature,
109
+ ...(m.ownerFqn ? { ownerFqn: m.ownerFqn } : {}),
110
+ ...(m.isSynthetic ? { isSynthetic: true } : {})
111
+ };
112
+ };
113
+ return {
114
+ ...(block.ownerFqn ? { ownerFqn: block.ownerFqn } : {}),
115
+ constructors: block.constructors.map(reduce),
116
+ fields: block.fields.map(reduce),
117
+ methods: block.methods.map(reduce)
118
+ };
119
+ }
93
120
  //# sourceMappingURL=members-builder.js.map
@@ -53,6 +53,12 @@ export function buildClassSourceSnippet(input) {
53
53
  sourceText = sliceToMaxCharsSafe(sourceText, input.maxChars);
54
54
  charsTruncated = true;
55
55
  truncated = true;
56
+ if (input.mode !== "metadata") {
57
+ const survivingNewlines = (sourceText.match(/\n/g) ?? []).length;
58
+ returnedEnd = sourceText.endsWith("\n")
59
+ ? returnedStart + survivingNewlines - 1
60
+ : returnedStart + survivingNewlines;
61
+ }
56
62
  }
57
63
  let nextStartLine;
58
64
  // Metadata mode returns a synthesized outline, not a line window into the
@@ -9,16 +9,16 @@ export declare function extractDecompiledMembers(className: string, filePath: st
9
9
  fields: DecompiledMember[];
10
10
  methods: DecompiledMember[];
11
11
  };
12
- export declare function computeLineBraceDepths(lines: string[]): number[];
12
+ export declare function computeLineBraceDepths(lines: string[], strippedLines?: string[]): number[];
13
13
  export declare function computeBraceRange(lines: string[], symbols: Array<{
14
14
  symbolKind: string;
15
15
  symbolName: string;
16
16
  line: number;
17
- }>, simpleName: string): {
17
+ }>, simpleName: string, strippedLines?: string[]): {
18
18
  declarationLine: number;
19
19
  endLine: number;
20
20
  } | undefined;
21
- export declare function scanBraceRange(lines: string[], declarationLine: number): {
21
+ export declare function scanBraceRange(lines: string[], declarationLine: number, strippedLines?: string[]): {
22
22
  declarationLine: number;
23
23
  endLine: number;
24
24
  };
@@ -28,7 +28,7 @@ export declare function computeNestedTypeRanges(lines: string[], symbols: Array<
28
28
  }>, outerBody: {
29
29
  declarationLine: number;
30
30
  endLine: number;
31
- }): Array<{
31
+ }, strippedLines?: string[]): Array<{
32
32
  declarationLine: number;
33
33
  endLine: number;
34
34
  }>;
@@ -30,13 +30,14 @@ export function extractDecompiledMembers(className, filePath, content) {
30
30
  const symbols = extractSymbolsFromSource(filePath, content);
31
31
  const simpleName = className.split(/[.$]/).at(-1) ?? className;
32
32
  const lines = content.split(/\r?\n/);
33
- const body = computeBraceRange(lines, symbols, simpleName);
33
+ const stripped = stripBraceNoise(lines);
34
+ const body = computeBraceRange(lines, symbols, simpleName, stripped);
34
35
  if (!body) {
35
36
  return { constructors: [], fields: [], methods: [] };
36
37
  }
37
- const depths = computeLineBraceDepths(lines);
38
+ const depths = computeLineBraceDepths(lines, stripped);
38
39
  const baseDepth = depths[body.declarationLine - 1] ?? 0;
39
- const nestedRanges = computeNestedTypeRanges(lines, symbols, body);
40
+ const nestedRanges = computeNestedTypeRanges(lines, symbols, body, stripped);
40
41
  const constructors = [];
41
42
  const fields = [];
42
43
  const methods = [];
@@ -126,8 +127,8 @@ function stripBraceNoise(lines) {
126
127
  }
127
128
  return out;
128
129
  }
129
- export function computeLineBraceDepths(lines) {
130
- const stripped = stripBraceNoise(lines);
130
+ export function computeLineBraceDepths(lines, strippedLines) {
131
+ const stripped = strippedLines ?? stripBraceNoise(lines);
131
132
  const depths = new Array(lines.length).fill(0);
132
133
  let depth = 0;
133
134
  for (let i = 0; i < stripped.length; i += 1) {
@@ -144,19 +145,19 @@ export function computeLineBraceDepths(lines) {
144
145
  }
145
146
  return depths;
146
147
  }
147
- export function computeBraceRange(lines, symbols, simpleName) {
148
+ export function computeBraceRange(lines, symbols, simpleName, strippedLines) {
148
149
  const classSymbol = symbols.find((symbol) => (symbol.symbolKind === "class" || symbol.symbolKind === "interface"
149
150
  || symbol.symbolKind === "enum" || symbol.symbolKind === "record")
150
151
  && symbol.symbolName === simpleName);
151
152
  if (!classSymbol) {
152
153
  return undefined;
153
154
  }
154
- return scanBraceRange(lines, classSymbol.line);
155
+ return scanBraceRange(lines, classSymbol.line, strippedLines);
155
156
  }
156
- export function scanBraceRange(lines, declarationLine) {
157
+ export function scanBraceRange(lines, declarationLine, strippedLines) {
157
158
  // Strip from the start of the file so multi-line block-comment state is correct
158
159
  // by the time we reach declarationLine; indices stay aligned with `lines`.
159
- const stripped = stripBraceNoise(lines);
160
+ const stripped = strippedLines ?? stripBraceNoise(lines);
160
161
  let depth = 0;
161
162
  let started = false;
162
163
  for (let i = declarationLine - 1; i < stripped.length; i += 1) {
@@ -175,7 +176,7 @@ export function scanBraceRange(lines, declarationLine) {
175
176
  }
176
177
  return { declarationLine, endLine: lines.length };
177
178
  }
178
- export function computeNestedTypeRanges(lines, symbols, outerBody) {
179
+ export function computeNestedTypeRanges(lines, symbols, outerBody, strippedLines) {
179
180
  const ranges = [];
180
181
  for (const candidate of symbols) {
181
182
  if (candidate.symbolKind !== "class" && candidate.symbolKind !== "interface"
@@ -188,7 +189,7 @@ export function computeNestedTypeRanges(lines, symbols, outerBody) {
188
189
  if (ranges.some((range) => candidate.line >= range.declarationLine && candidate.line <= range.endLine)) {
189
190
  continue;
190
191
  }
191
- const nestedRange = scanBraceRange(lines, candidate.line);
192
+ const nestedRange = scanBraceRange(lines, candidate.line, strippedLines);
192
193
  ranges.push(nestedRange);
193
194
  }
194
195
  return ranges;
@@ -2,6 +2,24 @@ import { type AppError } from "../errors.js";
2
2
  import type { SourceService } from "../source-service.js";
3
3
  import type { DecompiledFallback, FindClassInput, FindClassOutput, GetClassMembersInput, GetClassMembersOutput, GetClassSourceInput, GetClassSourceOutput } from "../source-service.js";
4
4
  import type { ArtifactProvenance, ArtifactScope, MappingSourcePriority, ResolvedSourceArtifact, SourceMapping } from "../types.js";
5
+ /**
6
+ * Unobfuscated Minecraft versions (26.1+) ship their runtime and decompiled source
7
+ * in deobfuscated (mojang) names directly — there is no real obfuscated namespace to
8
+ * map to. An artifact whose stored namespace label is "obfuscated" is therefore a
9
+ * mislabel for a mojang request: the bytes already carry mojang names.
10
+ *
11
+ * Without this reconciliation, requestedMapping="mojang" !== mappingApplied="obfuscated"
12
+ * triggers a cascade of doomed work: resolveClassNameForLookup attempts a mojang->obfuscated
13
+ * class remap, remapSignatureMembers tries to remap every member obfuscated->mojang and drops
14
+ * them all (counts.total===0), which forces a spurious decompiledFallback, disables
15
+ * memberPattern, and floods the response with one "Could not remap ..." warning per member.
16
+ *
17
+ * Collapsing mappingApplied to the requested mojang namespace makes the whole pipeline an
18
+ * identity no-op: members are returned from bytecode with correct mojang names, memberPattern
19
+ * applies, and the per-member warning flood disappears. This is the single highest-impact
20
+ * token-efficiency fix for unobfuscated versions.
21
+ */
22
+ export declare function reconcileUnobfuscatedNamespace(version: string | undefined, requestedMapping: SourceMapping, mappingApplied: SourceMapping): SourceMapping;
5
23
  export declare function resolveClassFilePath(svc: SourceService, artifactId: string, className: string): string | undefined;
6
24
  export declare function resolveClassNameForLookup(svc: SourceService, input: {
7
25
  className: string;
@@ -37,6 +55,7 @@ export declare function buildClassSourceNotFoundError(_svc: SourceService, input
37
55
  export declare function buildDecompiledFallback(svc: SourceService, artifactId: string, lookupClassName: string, memberPattern: string | undefined, maxMembers: number): {
38
56
  fallback: DecompiledFallback;
39
57
  counts: NonNullable<GetClassMembersOutput["decompiledMemberCounts"]>;
58
+ truncated: boolean;
40
59
  } | undefined;
41
60
  export declare function findClass(svc: SourceService, input: FindClassInput): FindClassOutput;
42
61
  export declare function getClassSource(svc: SourceService, input: GetClassSourceInput): Promise<GetClassSourceOutput>;