@adhisang/minecraft-modding-mcp 6.1.1 → 6.3.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 (56) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +12 -4
  3. package/dist/cache-registry.d.ts +1 -1
  4. package/dist/cache-registry.js +3 -0
  5. package/dist/entry-tools/analyze-mod-service.d.ts +12 -6
  6. package/dist/entry-tools/analyze-mod-service.js +37 -3
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +6 -4
  8. package/dist/entry-tools/analyze-symbol-service.js +37 -2
  9. package/dist/entry-tools/inspect-minecraft/internal.d.ts +7 -3
  10. package/dist/entry-tools/inspect-minecraft/internal.js +43 -15
  11. package/dist/entry-tools/inspect-minecraft-service.d.ts +12 -12
  12. package/dist/entry-tools/inspect-minecraft-service.js +1 -1
  13. package/dist/entry-tools/manage-cache-service.d.ts +4 -4
  14. package/dist/error-mapping.d.ts +13 -0
  15. package/dist/error-mapping.js +35 -2
  16. package/dist/errors.d.ts +2 -0
  17. package/dist/errors.js +2 -0
  18. package/dist/index.js +39 -20
  19. package/dist/mapping/internal-types.d.ts +7 -0
  20. package/dist/mapping/types.d.ts +18 -0
  21. package/dist/mapping-service.js +16 -3
  22. package/dist/minecraft-explorer-service.d.ts +4 -0
  23. package/dist/minecraft-explorer-service.js +156 -17
  24. package/dist/mod-analyzer.d.ts +7 -0
  25. package/dist/mod-analyzer.js +28 -7
  26. package/dist/source/artifact-resolver.d.ts +2 -0
  27. package/dist/source/artifact-resolver.js +24 -1
  28. package/dist/source/class-source/members-builder.d.ts +4 -0
  29. package/dist/source/class-source/members-builder.js +3 -1
  30. package/dist/source/class-source.d.ts +3 -1
  31. package/dist/source/class-source.js +192 -19
  32. package/dist/source/did-you-mean.d.ts +14 -0
  33. package/dist/source/did-you-mean.js +79 -0
  34. package/dist/source/file-access.js +159 -3
  35. package/dist/source/indexer.js +72 -2
  36. package/dist/source/lifecycle/runtime-check.js +9 -5
  37. package/dist/source/nested-jars.d.ts +78 -0
  38. package/dist/source/nested-jars.js +267 -0
  39. package/dist/source/workspace-target.js +5 -2
  40. package/dist/source-jar-reader.d.ts +16 -0
  41. package/dist/source-jar-reader.js +82 -0
  42. package/dist/source-service.d.ts +37 -0
  43. package/dist/source-service.js +52 -6
  44. package/dist/stage-emitter.js +24 -8
  45. package/dist/stdio-supervisor.d.ts +92 -9
  46. package/dist/stdio-supervisor.js +915 -103
  47. package/dist/tool-contract-manifest.js +2 -2
  48. package/dist/tool-guidance.js +115 -7
  49. package/dist/tool-schemas.d.ts +1343 -149
  50. package/dist/tool-schemas.js +39 -7
  51. package/dist/types.d.ts +23 -0
  52. package/dist/workspace-mapping-service.d.ts +1 -0
  53. package/dist/workspace-mapping-service.js +120 -8
  54. package/docs/README-ja.md +4 -0
  55. package/docs/tool-reference.md +92 -6
  56. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -5,7 +5,46 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [6.3.0] - 2026-07-18
9
+
10
+ ### Added
11
+
12
+ - `find-class` accepts top-level `projectPath` for workspace-relative `target.kind="workspace"` and dependency targets using `versionFromProject`, and searches simple or qualified class names across nested `.class` inventories when the resolved artifact is a Jar-in-Jar shell such as the Fabric API umbrella JAR. Nested matches retain the existing response shape, deduplicate repeated qualified names, and honor `limit`; top-level matches remain directly usable by the source/member tools.
13
+
14
+ ### Fixed
15
+
16
+ - `inspect-minecraft` now describes `task`, structured workspace `subject.focus`, and each class/search/file focus branch in `tools/list`. A string focus remains invalid instead of being guessed or coerced, but its `ERR_INVALID_INPUT` response now includes three schema-validated `exampleCalls` that preserve valid workspace context and show the exact object shapes for retrying.
17
+ - `find-class` no longer interprets a native dependency or Jar-in-Jar shell miss as evidence of obfuscated Minecraft runtime names, so it does not recommend `mapping="mojang"` for those artifacts. Dotted inner-class names discovered in a shell also continue through `get-class-source` instead of missing the binary `$` entry.
18
+
19
+ ## [6.2.0] - 2026-07-11
20
+
21
+ ### Added
22
+
23
+ - Workspace ergonomics for artifact addressing, all additive: (1) `find-class`, `get-artifact-file`, `list-artifact-files`, `search-class-source`, and `index-artifact` accept the shared `target` shape as an alternative to a flat `artifactId` (mutually exclusive — exactly one must be supplied; existing `artifactId` calls are unchanged). (2) `analyze-symbol` infers an omitted `version` from a supplied `projectPath` instead of rejecting at schema time; inferred runs always carry a `versionInference { version, source }` block plus a warning, an explicit `version` is never overridden, and an undetectable version fails with `ERR_WORKSPACE_VERSION_UNRESOLVED`. (3) `inspect-minecraft` direct subjects (`class`/`file`/`search`) without `subject.artifact` auto-resolve through the workspace only when exactly one workspace is known, always with a provenance warning; several known workspaces refuse with `workspaceCandidates`, and an explicit `subject.artifact` is never overridden.
24
+
25
+ - Bytecode member reads now surface annotation metadata: annotation-type classes (`@interface`) expose each member's default value as `annotationDefault` (rendered from the `AnnotationDefault` attribute, including string/enum/class/array/nested-annotation and numeric constants), and `get-class-members` gains an optional `includeAnnotations` flag that adds runtime-visible member annotations (e.g. `@java.lang.Deprecated`) to each member. Defaults are always present when the classfile carries them; member annotations appear only under the opt-in flag. `analyze-mod` `task="members"` includes both without a flag. No new dependencies — the existing classfile reader parses the attribute bodies.
26
+
27
+ - `analyze-mod` gains a `members` task (`subject: { kind: "class", jarPath, className }`) that reads a mod class's constructors, fields, and methods (all access levels, including private and protected) directly from bytecode — no decompiler invocation on this path, unlike `task: "class-source"`. Responses carry `extractionMethod: "bytecode-only"`. Additive: existing tasks and inputs are unchanged, and the tool count stays at 41.
28
+
29
+ - `get-artifact-file` serves `assets/**` and `data/**` text files (including vanilla `data/minecraft/dimension_type/*.json`, and resources in any jar-backed artifact such as mod jars) directly from the backing jar when the source index has no row for them — responses carry `deliveryMode: "jar-read-through"`. Delivery is text-only with a 512 KiB per-file cap and explicit `truncated` flag; binary entries answer with size metadata and a `contentOmittedReason` instead of content; traversal-shaped paths are rejected with `ERR_INVALID_INPUT`; misses return `ERR_FILE_NOT_FOUND` with `nearbyPaths` hints that surface directory relocations such as the `assets/minecraft/models/item/*` → `assets/minecraft/items/*` move. The `list-artifact-files` assets/data warning now points at this read-through path.
30
+
31
+ - Class-not-found errors (`ERR_CLASS_NOT_FOUND`) from `get-class-source`, `get-class-members`, and their batch variants now include a top-level `didYouMean` array with ranked near-miss candidates from the artifact's symbol index — `{ className, matchReason }` with reasons `exact-simple-name` (moved class, ranked first), `case-insensitive`, and `edit-distance:N`. A stale fully-qualified name like a pre-move `ItemTransform` package now names the current location instead of dead-ending. The array is empty when the index has no usable candidates, and candidates are phrased as hints, not assertions.
32
+
33
+ - Jar-in-Jar shell jars (near-zero own classes, content bundled under `META-INF/jars`, like the Fabric API umbrella jar) resolve instead of failing with `ERR_DECOMPILER_FAILED`. The resolved artifact carries `qualityFlags: ["shell-jar"]` and the nested-jar inventory in `provenance.nestedJars` (also surfaced by `analyze-mod-jar` as `nestedJars`); `get-class-source` and `get-class-members` redirect automatically to the single nested jar containing the requested class, marking the response with `provenance.nestedJar`. A class present in several nested jars returns the new `ERR_NESTED_JAR_AMBIGUOUS` error with `nestedJarCandidates` instead of a silent pick, and class-not-found errors on shells list the inventory. Extracted nested jars live in a content-addressed `nested-jars` cache directory, manageable via `manage-cache` `cacheKinds: ["nested-jar"]`.
34
+
35
+ - `validate-project` calls now have a supervisor-owned 120-second end-to-end deadline, configurable with `MCP_VALIDATE_PROJECT_TIMEOUT_MS` from 10,000 through 600,000 ms. Expiry returns the standard synthetic `{ error, meta }` tool-result envelope with `ERR_TOOL_TIMEOUT`, queue/running phase diagnostics, redacted progress context, and restart-initiation state. Queue expiry leaves the active worker untouched; running expiry replaces the isolated worker and preserves admitted later requests when initialization replay succeeds. Replacement startup/replay failure terminalizes queued work with existing restart errors. The supervisor FIFO holds at most two worker-bound requests and returns `ERR_LIMIT_EXCEEDED` (tool calls) or JSON-RPC `-32000` (non-tool requests) on overflow; cap-blocked requests fail immediately and undeliverable notifications are warning-dropped. POSIX process-group cleanup treats an already-gone process group as clean so stale cleanup tokens do not block later validation.
36
+
37
+ ### Changed
38
+
39
+ - The two constant per-response warning sentences on unobfuscated versions are replaced by structured flags on `mappingContext` — agents that pattern-matched the old strings should read the flags instead. `"Version X is unobfuscated; mapping graph is empty because the runtime already uses deobfuscated names."` is now `mappingContext.unobfuscatedRuntime: true`, and `"Version X is unobfuscated; validated symbol existence against runtime bytecode."` is now `mappingContext.runtimeValidated: true` (`get-class-api-matrix`, which has no `mappingContext`, reports a top-level `unobfuscatedRuntime: true`). No information is removed: the flags carry the same facts, the full former wording stays documented in `docs/tool-reference.md`, and all other warnings are untouched.
40
+
41
+ ### Fixed
42
+
43
+ - Loom split-source workspaces (a `minecraft-common` / `minecraft-clientOnly` sources-jar pair with no merged jar) no longer lose one half's classes to single-jar selection: version-target resolution with project Loom sources indexes the best-scored jar of the other half too (`provenance.companionSourceJars` names it), so client-only classes like `net.minecraft.client.renderer.block.model.ItemTransform` are indexed and findable via symbol search. Class-not-found errors for version targets additionally carry an `exampleCalls` retry using `scope: "vanilla"` (existing enum values only), whose client-jar decompilation also contains client-only classes.
44
+ - Inherited-member expansion no longer emits `Could not resolve super class ...` warnings for platform packages that are never inside a Minecraft jar (`java.*`, `javax.*`, `jdk.*`, `sun.*`, and the separately-shipped `com.mojang.serialization.*` — each verified against real 26.x and 1.21.10 jars). Suppression is allowlist-only: resolution failures for real Minecraft classes, including `com.mojang.blaze3d.*` client-jar content, stay visible.
45
+ - `check-symbol-exists` no longer reports false negatives for fields and classes on unobfuscated versions: the runtime-bytecode fallback introduced in 6.0.0 only re-checked `not_found` verdicts for methods, so a mapping graph lacking a record for a real field like `EntityType.ITEM` returned `not_found` without consulting the jar. Every `not_found` verdict is now runtime-checked (genuinely-missing symbols stay `not_found`), and bytecode-derived response contexts report `mappingNamespace: "mojang"` on unobfuscated versions instead of a hardcoded `"obfuscated"`.
46
+ - Dependency version detection probes the snake_case transforms of hyphenated artifact names (`fabric_api_version` and the compound `fabric_api_fabric_api_version` for `net.fabricmc.fabric-api:fabric-api`). Previously only submodules received a snake_case umbrella fallback key, so resolving the umbrella artifact itself against a standard Fabric template that declares `fabric_api_version` failed with `ERR_DEPENDENCY_VERSION_UNRESOLVED`.
47
+ - Umbrella submodule dependency targets (e.g. `net.fabricmc.fabric-api:fabric-gametest-api-v1`) no longer copy the umbrella's version into the submodule coordinate, which synthesized nonexistent coordinates like `fabric-gametest-api-v1:0.153.0+26.2` and failed downstream. Resolution now adopts the single cached modules-2 version, or — when several are cached — the version named by the cached umbrella POM's `<dependency>` entry (recorded as `provenance.submoduleVersionSource: "umbrella-pom"`). Anything still ambiguous fails closed with `ERR_DEPENDENCY_VERSION_UNRESOLVED` and `candidatesSeen` instead of guessing.
9
48
 
10
49
  ## [6.1.1] - 2026-07-04
11
50
 
package/README.md CHANGED
@@ -147,7 +147,7 @@ All six return `result.summary` first and can include `summary.nextActions` when
147
147
  | `inspect-minecraft` | versions, artifacts, classes, files, and source search |
148
148
  | `analyze-symbol` | symbol existence checks, mapping conversion, lifecycle tracing, and workspace symbol resolution |
149
149
  | `compare-minecraft` | version-pair diffs, class diffs, registry diffs, and migration-oriented overviews |
150
- | `analyze-mod` | mod metadata, decompile/search flows, class source, and safe remap preview/apply |
150
+ | `analyze-mod` | mod metadata, decompile/search flows, class source, bytecode member queries, and safe remap preview/apply |
151
151
  | `validate-project` | workspace summaries plus direct Mixin, Access Widener, and Access Transformer validation |
152
152
  | `manage-cache` | cache inventory, verification, and preview/apply cleanup workflows |
153
153
 
@@ -156,13 +156,15 @@ All six return `result.summary` first and can include `summary.nextActions` when
156
156
  These notes cover high-frequency decisions during onboarding. For the full pitfall list, exact contracts, migration notes, and environment variables, see [docs/tool-reference.md](docs/tool-reference.md).
157
157
 
158
158
  - `search-class-source` defaults to `queryMode="auto"` and keeps separator queries such as `foo.bar`, `foo_bar`, and `foo$bar` on the indexed path. Use `queryMode="literal"` for an explicit full substring scan.
159
- - If you do not already have an artifact, prefer `subject.kind="workspace"` for `inspect-minecraft` instead of guessing artifact details. When artifact context is the only missing input, a retryable `suggestedCall` preserves the requested task.
159
+ - If you do not already have an artifact, prefer `subject.kind="workspace"` for `inspect-minecraft` instead of guessing artifact details. `subject.focus` is an object, not a string: use `{ "kind": "class", "className": "..." }`, `{ "kind": "search", "query": "..." }`, or `{ "kind": "file", "filePath": "..." }`. `task="auto"` dispatches only from `subject.kind` and `focus.kind`; it is not a natural-language planner. Invalid string focus returns class/search/file `exampleCalls`, while missing artifact context keeps the requested task in a retryable `suggestedCall`.
160
160
  - `trace-symbol-lifecycle` expects `Class.method` in `symbol`. Keep exact overload matching in the separate `descriptor` field.
161
161
  - For unobfuscated releases such as `26.1+`, `check-symbol-exists` and `analyze-symbol task="exists"` validate `mojang` lookups against runtime bytecode when no mapping graph exists, and return `mapping_unavailable` when the runtime JAR itself is unreachable.
162
162
  - `analyze-mod` and `validate-project` require structured `subject` objects and canonical `include` groups; stale string-subject or domain-include payloads return `ERR_INVALID_INPUT` with a retryable `suggestedCall`.
163
163
  - `validate-project task="project-summary"` propagates `preferProjectVersion=true` across discovered Mixin, Access Widener, and Access Transformer checks. If no version can be resolved from the request or `gradle.properties`, the summary returns recovery guidance instead of guessing.
164
164
  - `validate-mixin` and `validate-project` keep `mapping-health` lightweight for `obfuscated` and `mojang` validation, avoiding full Tiny mapping graph loads unless `intermediary` or `yarn` namespaces are requested.
165
165
  - `validate-project task="project-summary"` uses a lightweight artifact probe for `tasks["minecraft.artifact.resolved"]`; it does not decompile Minecraft or rebuild the source index just to report per-probe status. Set `VALIDATE_PROJECT_TASKS_OFF=1` to omit the additive `tasks` field.
166
+ - `validate-project` has a supervisor-owned end-to-end deadline of 120 seconds, including queue time. Set `MCP_VALIDATE_PROJECT_TIMEOUT_MS` to an ASCII-decimal value from `10000` through `600000` to override it. A timeout returns `ERR_TOOL_TIMEOUT`; a running timeout restarts the isolated worker before queued calls resume, while a queue timeout leaves the current worker untouched.
167
+ - Queued calls resume only after the replacement worker completes initialization replay. If replacement startup or replay fails, queued tool calls terminate with `ERR_WORKER_RESTART` instead of waiting indefinitely. If unresolved process-tree cleanup fills the supervisor's two live-generation slots, new requests fail with the existing restart envelope and unavailable notifications are warning-dropped until cleanup or reconnect. On POSIX, an already-gone process group counts as cleaned up rather than leaving a stale cleanup token.
166
168
  - If a workspace was built with `GRADLE_USER_HOME=/tmp/...` or another isolated Gradle home, pass that path as `gradleUserHome` so source, mapping, runtime, and project validation lookups use the same Loom cache instead of stale caches under the MCP process home.
167
169
  - `manage-cache` reports corrupt Mojang binary-remap cache directories under `cacheKinds: ["binary-remap"]` with `status: "corrupt"`, and can delete them by `selector.artifactId` in preview/apply workflows.
168
170
 
@@ -262,7 +264,7 @@ Start with these top-level workflow tools unless you already know the exact spec
262
264
  | `inspect-minecraft` | Inspect versions, artifacts, classes, files, source text, and workspace-aware lookup flows |
263
265
  | `analyze-symbol` | Handle symbol existence checks, namespace mapping, lifecycle tracing, workspace symbol resolution, and API overviews |
264
266
  | `compare-minecraft` | Compare version pairs, class diffs, registry diffs, and migration-oriented summaries |
265
- | `analyze-mod` | Summarize mod metadata, decompile and search mod code, inspect class source, and preview or apply remaps |
267
+ | `analyze-mod` | Summarize mod metadata, decompile and search mod code, inspect class source, read class members from bytecode, and preview or apply remaps |
266
268
  | `validate-project` | Summarize workspaces and run direct Mixin, Access Widener, or Access Transformer validation |
267
269
  | `manage-cache` | List, verify, and preview or apply cache cleanup and rebuild operations |
268
270
  <!-- END GENERATED TOOL TABLE: top-level-workflow-tools -->
@@ -276,7 +278,7 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading o
276
278
  | --- | --- |
277
279
  | `list-versions` | List available Minecraft versions from Mojang metadata and local cache |
278
280
  | `resolve-artifact` | Resolve source artifacts from versions, JAR paths, or Maven coordinates |
279
- | `find-class` | Find simple or fully-qualified class names inside an artifact |
281
+ | `find-class` | Find simple or fully-qualified class names, including classes bundled in nested JARs |
280
282
  | `get-class-source` | Read class source from an artifact or resolve the backing artifact on demand |
281
283
  | `get-class-members` | List constructors, fields, and methods from bytecode |
282
284
  | `search-class-source` | Search indexed class source by symbol, text, or path |
@@ -285,6 +287,8 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading o
285
287
  | `index-artifact` | Rebuild indexed metadata for an existing artifact |
286
288
  <!-- END GENERATED TOOL TABLE: source-exploration -->
287
289
 
290
+ `find-class` accepts either an `artifactId` or the shared object `target` shape. For a workspace-relative dependency, pass `target: { kind: "dependency", group, name, versionFromProject: true }` with top-level `projectPath`. Fabric-style umbrella JARs are searched through their nested `.class` inventories; a top-level class match can then be passed to `get-class-source` or `get-class-members`, which resolve the containing nested JAR. Dotted inner-class matches are also readable through `get-class-source`. An empty result still means that the requested class name is absent from the resolved dependency version.
291
+
288
292
  For unobfuscated releases such as `26.1+`, `mapping="mojang"` uses the runtime/decompile path directly and skips Loom source-jar discovery, while `intermediary` and `yarn` fall back to `obfuscated` with a warning.
289
293
 
290
294
  ### Version Comparison & Symbol Tracking
@@ -413,12 +417,16 @@ pnpm test
413
417
 
414
418
  Run these when relevant:
415
419
 
420
+ - `pnpm test:file <path...>` for focused ordinary suites
421
+ - `pnpm test:grep <name-pattern>` for recursive ordinary suites filtered by test name
416
422
  - `pnpm test:manual:stdio-smoke` for MCP transport, registration, or manual workflow changes
417
423
  - `pnpm test:manual:package-smoke` when checking packaged install and distribution behavior
418
424
  - `pnpm test:perf` for search, index, or performance-sensitive changes
419
425
  - `pnpm test:coverage` or `pnpm test:coverage:lcov` for coverage checks (`lines=80`, `branches=70`, `functions=80`)
420
426
  - `pnpm validate` for the full local validation suite
421
427
 
428
+ Ordinary `.test.ts` files live in domain directories under `tests/` (for example `source-service/`, `entry-tools/`, `mapping/`, `mixin/`, `integration/mcp-tools/`, `contracts/`, and `utils/`). Keep helper-only modules in `tests/helpers`; manual smoke, performance, resource, and smoke-only files remain in their existing special directories.
429
+
422
430
  ## License
423
431
 
424
432
  [MIT](LICENSE)
@@ -1,6 +1,6 @@
1
1
  import { type PathRuntimeInfo } from "./path-converter.js";
2
2
  import { type WorkspaceContextCache } from "./workspace-context-cache.js";
3
- export declare const PUBLIC_CACHE_KINDS: readonly ["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap", "binary-remap", "workspace"];
3
+ export declare const PUBLIC_CACHE_KINDS: readonly ["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap", "binary-remap", "nested-jar", "workspace"];
4
4
  export type PublicCacheKind = (typeof PUBLIC_CACHE_KINDS)[number];
5
5
  export declare const CACHE_HEALTH_STATES: readonly ["healthy", "partial", "stale", "orphaned", "corrupt", "in_use"];
6
6
  export type CacheHealthState = (typeof CACHE_HEALTH_STATES)[number];
@@ -14,6 +14,7 @@ export const PUBLIC_CACHE_KINDS = [
14
14
  "decompiled-source",
15
15
  "mod-remap",
16
16
  "binary-remap",
17
+ "nested-jar",
17
18
  "workspace"
18
19
  ];
19
20
  export const CACHE_HEALTH_STATES = [
@@ -44,6 +45,8 @@ function kindRoot(config, cacheKind) {
44
45
  return join(config.cacheDir, "remapped-mods");
45
46
  case "binary-remap":
46
47
  return join(config.cacheDir, "remapped");
48
+ case "nested-jar":
49
+ return join(config.cacheDir, "nested-jars");
47
50
  case "workspace":
48
51
  return "<in-memory:workspace-context-cache>";
49
52
  }
@@ -1,7 +1,7 @@
1
1
  import type { AnalyzeModOptions, ModAnalysisResult } from "../mod-analyzer.js";
2
2
  import { z } from "zod";
3
3
  export declare const analyzeModShape: {
4
- task: z.ZodEnum<["summary", "decompile", "search", "class-source", "remap"]>;
4
+ task: z.ZodEnum<["summary", "decompile", "search", "class-source", "members", "remap"]>;
5
5
  subject: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
6
6
  kind: z.ZodLiteral<"jar">;
7
7
  jarPath: z.ZodString;
@@ -38,7 +38,7 @@ export declare const analyzeModShape: {
38
38
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
39
39
  };
40
40
  export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
41
- task: z.ZodEnum<["summary", "decompile", "search", "class-source", "remap"]>;
41
+ task: z.ZodEnum<["summary", "decompile", "search", "class-source", "members", "remap"]>;
42
42
  subject: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
43
43
  kind: z.ZodLiteral<"jar">;
44
44
  jarPath: z.ZodString;
@@ -75,7 +75,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
75
75
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
76
76
  }, "strip", z.ZodTypeAny, {
77
77
  limit: number;
78
- task: "search" | "summary" | "class-source" | "remap" | "decompile";
78
+ task: "search" | "summary" | "class-source" | "members" | "remap" | "decompile";
79
79
  subject: {
80
80
  kind: "jar";
81
81
  jarPath: string;
@@ -96,7 +96,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
96
96
  outputJar?: string | undefined;
97
97
  maxFiles?: number | undefined;
98
98
  }, {
99
- task: "search" | "summary" | "class-source" | "remap" | "decompile";
99
+ task: "search" | "summary" | "class-source" | "members" | "remap" | "decompile";
100
100
  subject: {
101
101
  kind: "jar";
102
102
  jarPath: string;
@@ -119,7 +119,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
119
119
  maxFiles?: number | undefined;
120
120
  }>, {
121
121
  limit: number;
122
- task: "search" | "summary" | "class-source" | "remap" | "decompile";
122
+ task: "search" | "summary" | "class-source" | "members" | "remap" | "decompile";
123
123
  subject: {
124
124
  kind: "jar";
125
125
  jarPath: string;
@@ -140,7 +140,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
140
140
  outputJar?: string | undefined;
141
141
  maxFiles?: number | undefined;
142
142
  }, {
143
- task: "search" | "summary" | "class-source" | "remap" | "decompile";
143
+ task: "search" | "summary" | "class-source" | "members" | "remap" | "decompile";
144
144
  subject: {
145
145
  kind: "jar";
146
146
  jarPath: string;
@@ -196,6 +196,12 @@ type AnalyzeModDeps = {
196
196
  }) => Promise<Record<string, unknown> & {
197
197
  warnings?: string[];
198
198
  }>;
199
+ getModClassMembers: (input: {
200
+ jarPath: string;
201
+ className: string;
202
+ }) => Promise<Record<string, unknown> & {
203
+ warnings?: string[];
204
+ }>;
199
205
  };
200
206
  export declare class AnalyzeModService {
201
207
  private readonly deps;
@@ -20,7 +20,7 @@ const subjectSchema = z.discriminatedUnion("kind", [
20
20
  })
21
21
  ]);
22
22
  export const analyzeModShape = {
23
- task: z.enum(["summary", "decompile", "search", "class-source", "remap"]),
23
+ task: z.enum(["summary", "decompile", "search", "class-source", "members", "remap"]),
24
24
  subject: subjectSchema,
25
25
  query: nonEmptyString.optional(),
26
26
  searchType: z.enum(["class", "method", "field", "content", "all"]).default("all"),
@@ -43,11 +43,11 @@ export const analyzeModSchema = z.object(analyzeModShape).superRefine((value, ct
43
43
  message: `${value.task} requires subject.kind=jar.`
44
44
  });
45
45
  }
46
- if (value.task === "class-source" && value.subject.kind !== "class") {
46
+ if ((value.task === "class-source" || value.task === "members") && value.subject.kind !== "class") {
47
47
  ctx.addIssue({
48
48
  code: z.ZodIssueCode.custom,
49
49
  path: ["subject", "kind"],
50
- message: "class-source requires subject.kind=class."
50
+ message: `${value.task} requires subject.kind=class.`
51
51
  });
52
52
  }
53
53
  if (value.task === "search" && !value.query) {
@@ -206,6 +206,40 @@ export class AnalyzeModService {
206
206
  warnings: Array.isArray(output.warnings) ? output.warnings : []
207
207
  };
208
208
  }
209
+ case "members": {
210
+ if (input.subject.kind !== "class") {
211
+ throw createError({
212
+ code: ERROR_CODES.INVALID_INPUT,
213
+ message: "members requires subject.kind=class."
214
+ });
215
+ }
216
+ const output = await this.deps.getModClassMembers({
217
+ jarPath: input.subject.jarPath,
218
+ className: input.subject.className
219
+ });
220
+ return {
221
+ ...buildEntryToolResult({
222
+ task: "members",
223
+ detail,
224
+ include,
225
+ summary: {
226
+ status: "ok",
227
+ headline: `Read bytecode members of ${input.subject.className}.`,
228
+ subject: createSummarySubject({
229
+ task: "members",
230
+ kind: input.subject.kind,
231
+ jarPath: input.subject.jarPath,
232
+ className: input.subject.className
233
+ })
234
+ },
235
+ blocks: {
236
+ members: output
237
+ },
238
+ alwaysBlocks: ["members"]
239
+ }),
240
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
241
+ };
242
+ }
209
243
  case "remap": {
210
244
  const normalizedInputJar = normalizePathForHost(input.subject.jarPath, undefined, "jarPath");
211
245
  const analysis = await this.deps.analyzeModJar(normalizedInputJar);
@@ -90,10 +90,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
90
90
  detail?: "summary" | "standard" | "full" | undefined;
91
91
  include?: string[] | undefined;
92
92
  targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
93
+ gradleUserHome?: string | undefined;
93
94
  maxVersions?: number | undefined;
94
95
  sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
95
96
  classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
96
- gradleUserHome?: string | undefined;
97
97
  includeSnapshots?: boolean | undefined;
98
98
  fromVersion?: string | undefined;
99
99
  toVersion?: string | undefined;
@@ -114,10 +114,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
114
114
  detail?: "summary" | "standard" | "full" | undefined;
115
115
  include?: string[] | undefined;
116
116
  targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
117
+ gradleUserHome?: string | undefined;
117
118
  maxVersions?: number | undefined;
118
119
  sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
119
120
  classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
120
- gradleUserHome?: string | undefined;
121
121
  nameMode?: "auto" | "fqcn" | undefined;
122
122
  includeSnapshots?: boolean | undefined;
123
123
  fromVersion?: string | undefined;
@@ -142,10 +142,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
142
142
  detail?: "summary" | "standard" | "full" | undefined;
143
143
  include?: string[] | undefined;
144
144
  targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
145
+ gradleUserHome?: string | undefined;
145
146
  maxVersions?: number | undefined;
146
147
  sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
147
148
  classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
148
- gradleUserHome?: string | undefined;
149
149
  includeSnapshots?: boolean | undefined;
150
150
  fromVersion?: string | undefined;
151
151
  toVersion?: string | undefined;
@@ -166,10 +166,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
166
166
  detail?: "summary" | "standard" | "full" | undefined;
167
167
  include?: string[] | undefined;
168
168
  targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
169
+ gradleUserHome?: string | undefined;
169
170
  maxVersions?: number | undefined;
170
171
  sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
171
172
  classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
172
- gradleUserHome?: string | undefined;
173
173
  nameMode?: "auto" | "fqcn" | undefined;
174
174
  includeSnapshots?: boolean | undefined;
175
175
  fromVersion?: string | undefined;
@@ -180,6 +180,7 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
180
180
  }>;
181
181
  export type AnalyzeSymbolInput = z.infer<typeof analyzeSymbolSchema>;
182
182
  type AnalyzeSymbolDeps = {
183
+ detectProjectMinecraftVersion: (projectPath: string) => Promise<string | undefined>;
183
184
  checkSymbolExists: (input: {
184
185
  version: string;
185
186
  kind: "class" | "field" | "method";
@@ -254,5 +255,6 @@ export declare class AnalyzeSymbolService {
254
255
  execute(input: AnalyzeSymbolInput): Promise<Record<string, unknown> & {
255
256
  warnings?: string[];
256
257
  }>;
258
+ private dispatch;
257
259
  }
258
260
  export {};
@@ -112,11 +112,11 @@ const LIFECYCLE_ONLY_FIELDS = [
112
112
  "includeSnapshots"
113
113
  ];
114
114
  export const analyzeSymbolSchema = z.object(analyzeSymbolShape).superRefine((value, ctx) => {
115
- if (value.task !== "lifecycle" && !value.version) {
115
+ if (value.task !== "lifecycle" && !value.version && !value.projectPath) {
116
116
  ctx.addIssue({
117
117
  code: z.ZodIssueCode.custom,
118
118
  path: ["version"],
119
- message: "version is required for non-lifecycle tasks."
119
+ message: "version is required for non-lifecycle tasks (or pass projectPath so the version can be inferred from the workspace)."
120
120
  });
121
121
  }
122
122
  if (value.task === "lifecycle" && !value.version && !value.toVersion) {
@@ -189,6 +189,41 @@ export class AnalyzeSymbolService {
189
189
  const detail = resolveDetail(input.detail);
190
190
  const include = resolveInclude(input.include);
191
191
  const { kind: subjectKind, warning: inferenceWarning } = inferSubjectKind(input.subject);
192
+ // Omitted version with a supplied projectPath: infer from the workspace.
193
+ // Inference always carries provenance (versionInference block + warning);
194
+ // an explicit version is never overridden.
195
+ let versionInference;
196
+ if (input.task !== "lifecycle" && !input.version && input.projectPath) {
197
+ const detected = await this.deps.detectProjectMinecraftVersion(input.projectPath);
198
+ if (!detected) {
199
+ throw createError({
200
+ code: ERROR_CODES.WORKSPACE_VERSION_UNRESOLVED,
201
+ message: `Could not infer a Minecraft version from ${input.projectPath}.`,
202
+ details: {
203
+ projectPath: input.projectPath,
204
+ nextAction: "Declare minecraft_version (or mc_version) in gradle.properties, or pass version explicitly."
205
+ }
206
+ });
207
+ }
208
+ versionInference = {
209
+ version: detected,
210
+ source: `projectPath:gradle.properties (${input.projectPath})`
211
+ };
212
+ input = { ...input, version: detected };
213
+ }
214
+ const inferenceWarnings = versionInference
215
+ ? [
216
+ `version was inferred from the workspace: ${versionInference.version} (source: ${versionInference.source}).`
217
+ ]
218
+ : [];
219
+ const result = await this.dispatch(input, detail, include, subjectKind, inferenceWarning);
220
+ if (versionInference) {
221
+ result.versionInference = versionInference;
222
+ result.warnings = [...(result.warnings ?? []), ...inferenceWarnings];
223
+ }
224
+ return result;
225
+ }
226
+ async dispatch(input, detail, include, subjectKind, inferenceWarning) {
192
227
  switch (input.task) {
193
228
  case "exists": {
194
229
  const output = await this.deps.checkSymbolExists({
@@ -401,6 +401,7 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
401
401
  scope?: "vanilla" | "merged" | "loader" | undefined;
402
402
  preferProjectVersion?: boolean | undefined;
403
403
  strictVersion?: boolean | undefined;
404
+ gradleUserHome?: string | undefined;
404
405
  artifact?: {
405
406
  artifactId: string;
406
407
  type: "resolved-id";
@@ -411,7 +412,6 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
411
412
  };
412
413
  type: "resolve-target";
413
414
  } | undefined;
414
- gradleUserHome?: string | undefined;
415
415
  }, {
416
416
  kind: "class";
417
417
  className: string;
@@ -420,6 +420,7 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
420
420
  scope?: "vanilla" | "merged" | "loader" | undefined;
421
421
  preferProjectVersion?: boolean | undefined;
422
422
  strictVersion?: boolean | undefined;
423
+ gradleUserHome?: string | undefined;
423
424
  artifact?: {
424
425
  artifactId: string;
425
426
  type: "resolved-id";
@@ -430,7 +431,6 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
430
431
  };
431
432
  type: "resolve-target";
432
433
  } | undefined;
433
- gradleUserHome?: string | undefined;
434
434
  }>, z.ZodObject<{
435
435
  kind: z.ZodLiteral<"file">;
436
436
  filePath: z.ZodString;
@@ -1015,6 +1015,10 @@ export type InspectMinecraftDeps = {
1015
1015
  cursor?: string;
1016
1016
  }) => Promise<ListArtifactFilesOutput>;
1017
1017
  detectProjectMinecraftVersion: (projectPath: string) => Promise<string | undefined>;
1018
+ listWorkspaceContexts: () => Array<{
1019
+ projectPath: string;
1020
+ minecraftVersion?: string;
1021
+ }>;
1018
1022
  };
1019
1023
  export declare function requireWorkspaceClassFocus(subject: Subject): WorkspaceClassFocus;
1020
1024
  export declare function requireWorkspaceSearchFocus(subject: Subject): WorkspaceSearchFocus;
@@ -1040,7 +1044,7 @@ export declare function resolveWorkspaceArtifactReference(deps: InspectMinecraft
1040
1044
  version?: string;
1041
1045
  warnings: string[];
1042
1046
  }>;
1043
- export declare function resolveTask(task: InspectMinecraftTask | undefined, subject: Subject | undefined): "search" | "class-source" | "artifact" | "file" | "versions" | "class-members" | "class-overview" | "list-files";
1047
+ export declare function resolveTask(task: InspectMinecraftTask | undefined, subject: Subject | undefined): "search" | "class-source" | "file" | "class-overview" | "class-members" | "artifact" | "versions" | "list-files";
1044
1048
  export declare function summarizeRequestedSubject(subject: Subject): Record<string, unknown>;
1045
1049
  export declare function taskForSubject(subject: Subject): ConcreteInspectMinecraftTask;
1046
1050
  export declare function invalidTaskSubjectError(task: ConcreteInspectMinecraftTask, subject: Subject): never;
@@ -30,27 +30,27 @@ export const artifactRefSchema = z.discriminatedUnion("type", [
30
30
  ]);
31
31
  export const workspaceFocusSchema = z.discriminatedUnion("kind", [
32
32
  z.object({
33
- kind: z.literal("class"),
34
- className: nonEmptyString,
35
- artifact: artifactRefSchema.optional()
36
- }),
33
+ kind: z.literal("class").describe("Select class inspection."),
34
+ className: nonEmptyString.describe("Fully-qualified class name to inspect."),
35
+ artifact: artifactRefSchema.optional().describe("Optional explicit artifact override; otherwise resolve it from the workspace.")
36
+ }).describe('Class focus object: {"kind":"class","className":"net.minecraft.world.item.Item"}. task=auto dispatches it to class-overview; select class-source or class-members explicitly when needed.'),
37
37
  z.object({
38
- kind: z.literal("file"),
39
- filePath: nonEmptyString,
40
- artifact: artifactRefSchema.optional()
41
- }),
38
+ kind: z.literal("file").describe("Select an artifact-relative file read."),
39
+ filePath: nonEmptyString.describe("Artifact-relative file path to read."),
40
+ artifact: artifactRefSchema.optional().describe("Optional explicit artifact override; otherwise resolve it from the workspace.")
41
+ }).describe('File focus object: {"kind":"file","filePath":"net/minecraft/world/item/Item.java"}. task=auto dispatches it to file.'),
42
42
  z.object({
43
- kind: z.literal("search"),
44
- query: nonEmptyString,
45
- artifact: artifactRefSchema.optional(),
43
+ kind: z.literal("search").describe("Select source search."),
44
+ query: nonEmptyString.describe("Source, symbol, or path query to search for."),
45
+ artifact: artifactRefSchema.optional().describe("Optional explicit artifact override; otherwise resolve it from the workspace."),
46
46
  intent: z.enum(["symbol", "text", "path"]).optional(),
47
47
  match: z.enum(["exact", "prefix", "contains", "regex"]).optional(),
48
48
  symbolKind: z.enum(["class", "interface", "enum", "record", "method", "field"]).optional(),
49
49
  packagePrefix: nonEmptyString.optional(),
50
50
  fileGlob: nonEmptyString.optional(),
51
51
  queryMode: z.enum(["auto", "token", "literal"]).default("auto")
52
- })
53
- ]);
52
+ }).describe('Search focus object: {"kind":"search","query":"CreativeModeTab"}. task=auto dispatches it to search.')
53
+ ]).describe('Structured workspace focus. Object, not string. Choose {kind:"class",className}, {kind:"file",filePath}, or {kind:"search",query}.');
54
54
  export const subjectSchema = z.discriminatedUnion("kind", [
55
55
  z.object({
56
56
  kind: z.literal("version"),
@@ -108,8 +108,8 @@ export const subjectSchema = z.discriminatedUnion("kind", [
108
108
  preferProjectVersion: z.boolean().optional(),
109
109
  strictVersion: z.boolean().optional(),
110
110
  focus: workspaceFocusSchema.optional()
111
- })
112
- ]);
111
+ }).describe("Workspace subject. Resolves Minecraft artifact context from projectPath; add a structured focus object for class, file, or search work.")
112
+ ]).describe("Structured inspection subject. Use kind=workspace with a structured focus object when the workspace should resolve artifact context.");
113
113
  export function hasPartialVanillaCoverage(artifact) {
114
114
  return artifact?.qualityFlags.includes("partial-source-no-net-minecraft") === true
115
115
  || artifact?.artifactContents.sourceCoverage === "partial";
@@ -392,6 +392,31 @@ export async function resolveArtifactReference(deps, subject, task) {
392
392
  }
393
393
  if (subject.kind === "class" || subject.kind === "file" || subject.kind === "search") {
394
394
  if (!subject.artifact) {
395
+ // Guardrailed auto-resolution: an omitted subject.artifact resolves
396
+ // through the workspace ONLY when exactly one workspace is known, and
397
+ // the response always carries a provenance warning. An explicit
398
+ // subject.artifact is never overridden (this branch requires absence).
399
+ const workspaces = deps.listWorkspaceContexts();
400
+ const unique = workspaces.length === 1 ? workspaces[0] : undefined;
401
+ if (unique) {
402
+ const version = unique.minecraftVersion ??
403
+ (await deps.detectProjectMinecraftVersion(unique.projectPath));
404
+ if (version) {
405
+ const artifact = await deps.resolveArtifact({
406
+ target: { kind: "version", value: version },
407
+ projectPath: unique.projectPath
408
+ });
409
+ return {
410
+ artifactId: artifact.artifactId,
411
+ artifact,
412
+ version,
413
+ warnings: [
414
+ `subject.artifact was omitted; auto-resolved through the unique known workspace ${unique.projectPath} (Minecraft ${version}). Pass subject.artifact to target a different artifact.`,
415
+ ...artifact.warnings
416
+ ]
417
+ };
418
+ }
419
+ }
395
420
  const suggestedTask = task
396
421
  ?? (subject.kind === "class"
397
422
  ? "class-overview"
@@ -403,6 +428,9 @@ export async function resolveArtifactReference(deps, subject, task) {
403
428
  message: `${subject.kind} subject requires artifact context.`,
404
429
  details: {
405
430
  nextAction: "Add subject.artifact or use subject.kind=workspace so inspect-minecraft can resolve the artifact first.",
431
+ ...(workspaces.length > 1
432
+ ? { workspaceCandidates: workspaces.map((workspace) => workspace.projectPath) }
433
+ : {}),
406
434
  ...(await buildArtifactContextSuggestedCall(deps, suggestedTask, subject))
407
435
  }
408
436
  });