@adhisang/minecraft-modding-mcp 5.0.0 → 6.0.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.
- package/CHANGELOG.md +36 -0
- package/dist/access-widener-parser.d.ts +1 -0
- package/dist/access-widener-parser.js +6 -3
- package/dist/cache-registry.js +35 -31
- package/dist/decompiler/vineflower.js +8 -2
- package/dist/entry-tools/analyze-mod-service.js +2 -1
- package/dist/entry-tools/analyze-symbol-service.js +18 -9
- package/dist/entry-tools/compare-minecraft-service.js +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -1
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +26 -1
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +2 -1
- package/dist/entry-tools/inspect-minecraft/internal.js +2 -0
- package/dist/entry-tools/response-contract.d.ts +2 -0
- package/dist/entry-tools/response-contract.js +2 -2
- package/dist/entry-tools/validate-project-service.d.ts +4 -4
- package/dist/entry-tools/verify-mixin-target-service.js +7 -7
- package/dist/index.js +20 -16
- package/dist/java-process.js +1 -1
- package/dist/json-rpc-framing.d.ts +4 -0
- package/dist/json-rpc-framing.js +23 -1
- package/dist/mapping/internal-types.d.ts +17 -0
- package/dist/mapping/loaders/tiny-maven.js +46 -20
- package/dist/mapping/lookup.d.ts +12 -1
- package/dist/mapping/lookup.js +53 -1
- package/dist/mapping-service.js +36 -21
- package/dist/mixin/annotation-validators.js +21 -3
- package/dist/mixin/parsed-validator.js +1 -0
- package/dist/mixin-parser.js +71 -3
- package/dist/mod-decompile-service.d.ts +2 -0
- package/dist/mod-decompile-service.js +19 -2
- package/dist/mod-remap-service.js +6 -5
- package/dist/mojang-tiny-mapping-service.js +5 -2
- package/dist/nbt/java-nbt-codec.js +40 -16
- package/dist/nbt/json-patch.js +51 -12
- package/dist/nbt/typed-json.d.ts +1 -0
- package/dist/nbt/typed-json.js +12 -2
- package/dist/nbt/types.d.ts +2 -2
- package/dist/path-converter.js +10 -3
- package/dist/registry-service.d.ts +2 -0
- package/dist/registry-service.js +16 -1
- package/dist/repo-downloader.js +4 -3
- package/dist/source/artifact-resolver.js +1 -1
- package/dist/source/class-source/snippet-builder.js +6 -0
- package/dist/source/class-source-helpers.d.ts +4 -4
- package/dist/source/class-source-helpers.js +12 -11
- package/dist/source/class-source.d.ts +19 -0
- package/dist/source/class-source.js +48 -21
- package/dist/source/file-access.js +3 -2
- package/dist/source/indexer.js +24 -7
- package/dist/source/lifecycle/mapping-helpers.js +28 -3
- package/dist/source/lifecycle/runtime-check.js +20 -6
- package/dist/source/search.js +23 -8
- package/dist/source/validate-mixin/pipeline/resolve.js +23 -3
- package/dist/source/workspace-target.js +2 -1
- package/dist/source-resolver.js +2 -2
- package/dist/source-service.js +9 -1
- package/dist/stdio-supervisor.d.ts +2 -2
- package/dist/stdio-supervisor.js +29 -9
- package/dist/storage/db.js +24 -1
- package/dist/storage/files-repo.d.ts +3 -0
- package/dist/storage/files-repo.js +43 -37
- package/dist/storage/symbols-repo.d.ts +9 -0
- package/dist/storage/symbols-repo.js +47 -19
- package/dist/symbols/symbol-extractor.js +1 -1
- package/dist/tool-guidance.d.ts +2 -1
- package/dist/tool-guidance.js +26 -1
- package/dist/tool-schemas.d.ts +4 -4
- package/dist/tool-schemas.js +91 -91
- package/dist/warning-details.d.ts +12 -0
- package/dist/warning-details.js +17 -0
- package/dist/workspace-mapping-service.js +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [6.0.0] - 2026-06-25
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Breaking:** tool responses omit `meta.warnings` entirely when there are no warnings (previously always emitted as `[]`), and omit `meta.detailApplied` when it equals the tool's default detail level (entry tools: `summary`; expert/batch tools: their per-tool default). Clients must treat a missing `meta.warnings` as "no warnings" and a missing `meta.detailApplied` as "the default detail was applied". `meta.includeApplied` semantics are unchanged (already omitted when empty).
|
|
15
|
+
- `tools/list` payload slimmed by ~12% (93.2 KB → 82.2 KB, roughly 2,700 fewer tokens held in an agent's context): parameter descriptions that merely echoed enum values (e.g. `"obfuscated | mojang | intermediary | yarn"`) are removed since the JSON Schema `enum` already carries them, the shared `detail`/`include`/`target`/`scope`/`gradleUserHome` descriptions are tightened, the expert-tool steering note is shortened, and the longest tool descriptions (`resolve-method-mapping-exact`, `get-class-source`, `get-class-members`, the `batch-*` family) no longer repeat information already present in their input schemas. Wording-only — no input shapes, defaults, or validation rules changed.
|
|
16
|
+
- Per-member remap-failure warnings are aggregated into a single line per namespace direction (e.g. `Could not remap 12 methods from obfuscated to mojang (foo, bar, baz, +9 more).`) instead of one line per member, and the structured `meta.warningDetails[]` companion is capped to a representative set (5 entries) at `detail: "summary"` (the full text still lives in `meta.warnings[]`, so each kept entry's `index` still dereferences it). On unobfuscated versions a single core class could previously emit hundreds of near-identical remap-warning lines — the dominant token sink for those responses.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `validate-mixin` / mixin parsing: `@Mixin(My$GeneratedClass.class)` class-literal targets containing `$` (a legal Java identifier character) are now captured as the full class name; previously the target regex started the capture mid-name and produced a wrong target.
|
|
21
|
+
- **Unobfuscated Minecraft versions (26.1+):** `get-class-members` / `get-class-source` no longer mislabel the already-deobfuscated runtime namespace as `obfuscated` for a `mapping: "mojang"` request when reusing a previously-resolved artifact. The mislabel made `requestedMapping ("mojang") !== mappingApplied ("obfuscated")`, which triggered a doomed mojang↔obfuscated remap that dropped every member (forcing a spurious `decompiledFallback`), disabled `memberPattern`, and flooded the response with per-member remap warnings. The namespace is now reconciled to `mojang` (identity), so members come back from bytecode with correct names, `memberPattern` applies, and the warning flood disappears.
|
|
22
|
+
- **`batch-symbol-exists` / `check-symbol-exists` on unobfuscated versions:** method existence checks returned `not_found` for methods that genuinely exist, because the empty mapping graph reported `not_found` and skipped the runtime-bytecode fallback. The fallback now runs for `not_found` method queries; name-only lookups resolve when any overload matches (no longer reported as `ambiguous` for an existence check), and inherited methods are included (`level.getGameTime()` resolves on `Level`).
|
|
23
|
+
- `check-symbol-exists`: a class genuinely absent from the runtime jar now yields a clear `… was not found in the Minecraft <version> runtime jar` message instead of a generic load-failure note.
|
|
24
|
+
- `resolve-artifact`: a decompilation failure (`ERR_DECOMPILER_FAILED`) no longer advertises an `artifactId` for an artifact that was never persisted — the leaked id previously caused a follow-up `find-class` / `get-class-*` to fail with "Artifact not found. Resolve context first."
|
|
25
|
+
- Workspace dependency version detection falls back to the umbrella package's version property (e.g. `fabricApiVersion` / `fabric_api_version`) for submodules that don't declare their own (e.g. `net.fabricmc.fabric-api:fabric-screen-handler-api-v1`), instead of failing with `ERR_DEPENDENCY_VERSION_UNRESOLVED`. The submodule's own property still takes precedence when present.
|
|
26
|
+
- `get-class-source` now honors an explicit `mapping` argument instead of forcing the artifact's stored namespace (matching `get-class-members`), so a `className` supplied in a declared namespace no longer fails with `CLASS_NOT_FOUND`. Several recovery `suggestedCall`s are corrected: the snippet continuation no longer drops the default `maxLines` (which produced an unbounded follow-up read), the `decompiledFallback` truncation flag is reported accurately, and a hardcoded `"latest"` version that `resolve-artifact` rejects is replaced with the concrete resolved version. A method query with an omitted `signatureMode` is treated as a non-exact match in the runtime check (descriptorless methods are no longer guaranteed `not_found`), binary jars are remapped from the source jar (not the absent output) after an index loss, the `globToSqlLike` `**` push-down no longer drops direct children, the vanilla-jar fallback no longer retains the named-signature mapping, and the workspace-context write-back is serialized so it stops racing dependency-target synthesis.
|
|
27
|
+
- `resolve-method-mapping-exact` mirrors `find-mapping`'s obfuscated-namespace acceptance set, so a single-hop `intermediary → yarn` lookup whose candidate carries an obfuscated-namespace descriptor no longer returns a false `not_found`. Loading yarn Tiny pairs from Maven no longer unions three builds (or fetches unusable v1 jars), removing the spurious `ambiguous` results that produced. Tiny caches (`extractTinyFromJar` / `resolveMojangTinyFile`) are now written atomically (temp file + `rename`) so a crash mid-write cannot leave a truncated `.tiny` behind a permanent `existsSync` fast path, and `get-class-api-matrix` keys its cursor context on `gradleUserHome` so a cursor minted against one dependency graph cannot replay against another.
|
|
28
|
+
- `validate-mixin` / mixin parsing no longer matches the word `class` inside comments or javadoc (which yielded a wrong `className`) and no longer parses annotations inside block comments (which produced phantom `@Shadow` / `@Accessor` members and false definite errors); both passes now skip comment regions. The injection-point wildcard quantifier is anchored so an exact-match target no longer triggers a false positive.
|
|
29
|
+
- `search-class-source` / file search no longer re-emits `both`-tier hits as duplicate `content`-tier hits on later pages (they are deduped across the cursor), escapes the `LIKE` needle in path search, push-down, and count queries so the declared `ESCAPE` clause actually applies, and stops returning a `nextCursor` on the final partial page (which previously forced an extra empty-page fetch).
|
|
30
|
+
- `inspect-minecraft`: `task: "versions"` no longer drops the entire `versions` block at the default detail level, and `task: "file"` content is reachable now that `file` is a valid `include` group. `task: "api-overview"` honors `maxRows` and flags truncation instead of silently capping the matrix at 25 rows while reporting `rowsTruncated: false`. The `file` / `search` / `list-files` tasks return the friendly "blocked" summary (carrying the version-inference warning) instead of forwarding an empty `artifactId`, `task: "workspace"` no longer passes the literal `"unknown"` as the Minecraft version, the `get-class-members` truncation `nextAction` raises the member limit so the suggested retry no longer reproduces the same cut, and the `remap-mod-jar` preview `nextAction` preserves an explicit `outputJar`.
|
|
31
|
+
- stdio supervisor: a supervisor-replayed `initialize` no longer emits a stray error response when the worker crashes again after the client is already initialized; a worker spawn error that previously stalled the supervisor permanently now recovers; and a stale worker exit no longer detaches and orphans the replacement worker. The Java subprocess runner drains trailing stdout/stderr before resolving on child exit, so the final output is no longer lost.
|
|
32
|
+
- NBT JSON Patch: an `add` / `replace` op with key `__proto__` is assigned as an own enumerable property instead of silently mutating the prototype and dropping the member. Non-finite floats round-trip correctly — decode emits a sentinel string rather than a raw number, and the typed-JSON layer no longer rejects otherwise-valid NBT containing `NaN` / `Infinity`.
|
|
33
|
+
- Data generation no longer deadlocks once the child's stdout pipe buffer fills (the output is now drained), the download timeout now covers the response body stream instead of being cleared after headers (removing an indefinite hang), and concurrent identical `remap-mod-jar` calls no longer race on a non-atomic cache copy.
|
|
34
|
+
- Access Widener parsing accepts valid `transitive-` kinds — the prefix is stripped, the base kind validated, and transitivity recorded on the entry — instead of rejecting them as unknown. Path conversion recognizes the `wsl.localhost` UNC form (not only `wsl$`) and rejects a UNC WSL path that targets a different distro than the server runs in, instead of silently mapping it.
|
|
35
|
+
- `validate-mixin` recovery suggestions no longer read `input.path` before checking the mode (which turned a project-mode call into an executable path-mode `suggestedCall`), and the access-transformer suggestion no longer hardcodes the access-widener subject kind. The `batch-class-source` `outputFile` dedupe guard canonicalizes paths unconditionally so absolute-path aliases (`/a/../b`, `//a`) collide as intended and cannot race `writeFile` on the same inode.
|
|
36
|
+
|
|
37
|
+
### Performance
|
|
38
|
+
|
|
39
|
+
- SQLite repositories cache prepared statements for dynamically built queries (cursor-paginated symbol/file lookups, scoped symbol search, count queries, `IN (...)`-list lookups) in a small per-repo LRU instead of re-preparing the SQL on every call.
|
|
40
|
+
- Symbol search with regex/glob/package filters now streams rows from SQLite instead of materializing every symbol of the artifact in memory first; only matching rows are retained.
|
|
41
|
+
- Cache-registry operations no longer read and JSON-parse every registry file from disk on each call; the parsed registry state is cached and invalidated on write.
|
|
42
|
+
- `get-class-members` / `get-class-source` no longer re-strip the whole decompiled file once per nested type when extracting members (`extractDecompiledMembers` was quadratic in the nested-type count).
|
|
43
|
+
- NBT patch validation revalidates only the touched subtree per patch op instead of the whole document (previously quadratic), and the array encoders reuse a single buffer cursor instead of allocating a `Buffer` per scalar write.
|
|
44
|
+
- `check-symbol-exists` indexes its lookups instead of scanning the full target namespace on every call.
|
|
45
|
+
|
|
10
46
|
## [5.0.0] - 2026-06-04
|
|
11
47
|
|
|
12
48
|
### Changed
|
|
@@ -40,7 +40,9 @@ export function parseAccessWidener(content) {
|
|
|
40
40
|
}
|
|
41
41
|
const kind = parts[0];
|
|
42
42
|
const targetKind = parts[1];
|
|
43
|
-
|
|
43
|
+
const transitive = kind.startsWith("transitive-");
|
|
44
|
+
const baseKind = transitive ? kind.slice("transitive-".length) : kind;
|
|
45
|
+
if (!VALID_KINDS.has(baseKind)) {
|
|
44
46
|
parseWarnings.push(`Line ${lineNum}: Unknown access kind "${kind}".`);
|
|
45
47
|
continue;
|
|
46
48
|
}
|
|
@@ -48,16 +50,17 @@ export function parseAccessWidener(content) {
|
|
|
48
50
|
parseWarnings.push(`Line ${lineNum}: Unknown target kind "${targetKind}".`);
|
|
49
51
|
continue;
|
|
50
52
|
}
|
|
51
|
-
const validKind =
|
|
53
|
+
const validKind = baseKind;
|
|
52
54
|
const validTargetKind = targetKind;
|
|
53
55
|
if (validTargetKind === "class") {
|
|
54
|
-
entries.push({ line: lineNum, kind: validKind, targetKind: validTargetKind, target: parts[2] });
|
|
56
|
+
entries.push({ line: lineNum, kind: validKind, transitive, targetKind: validTargetKind, target: parts[2] });
|
|
55
57
|
}
|
|
56
58
|
else if (parts.length >= 5) {
|
|
57
59
|
// method/field: <kind> <targetKind> <owner> <name> <descriptor>
|
|
58
60
|
entries.push({
|
|
59
61
|
line: lineNum,
|
|
60
62
|
kind: validKind,
|
|
63
|
+
transitive,
|
|
61
64
|
targetKind: validTargetKind,
|
|
62
65
|
target: parts[2],
|
|
63
66
|
owner: parts[2],
|
package/dist/cache-registry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync
|
|
2
|
-
import { readdir, rm, stat } from "node:fs/promises";
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, readdir, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { mapWithConcurrencyLimit } from "./concurrency.js";
|
|
4
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
5
6
|
import { normalizeOptionalPathForHost } from "./path-converter.js";
|
|
6
7
|
import Database from "./storage/sqlite.js";
|
|
@@ -25,6 +26,7 @@ export const CACHE_HEALTH_STATES = [
|
|
|
25
26
|
];
|
|
26
27
|
const STALE_ENTRY_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
27
28
|
const CURSOR_VERSION = 1;
|
|
29
|
+
const CACHE_STAT_CONCURRENCY = 8;
|
|
28
30
|
const STATUS_PRIORITY = ["in_use", "corrupt", "orphaned", "stale", "partial", "healthy"];
|
|
29
31
|
function kindRoot(config, cacheKind) {
|
|
30
32
|
switch (cacheKind) {
|
|
@@ -165,12 +167,12 @@ function inferProjectPath(pathValue, runtimeInfo) {
|
|
|
165
167
|
}
|
|
166
168
|
return undefined;
|
|
167
169
|
}
|
|
168
|
-
function isCorruptRegistryJson(filePath) {
|
|
170
|
+
async function isCorruptRegistryJson(filePath) {
|
|
169
171
|
if (!filePath.endsWith(".json")) {
|
|
170
172
|
return false;
|
|
171
173
|
}
|
|
172
174
|
try {
|
|
173
|
-
JSON.parse(
|
|
175
|
+
JSON.parse(await readFile(filePath, "utf8"));
|
|
174
176
|
return false;
|
|
175
177
|
}
|
|
176
178
|
catch {
|
|
@@ -242,7 +244,7 @@ function candidatePathsForEntry(entry) {
|
|
|
242
244
|
function entryUpdatedAt(entry) {
|
|
243
245
|
return typeof entry.meta?.updatedAt === "string" ? entry.meta.updatedAt : undefined;
|
|
244
246
|
}
|
|
245
|
-
function deriveEntryStatus(entry, config, now) {
|
|
247
|
+
function deriveEntryStatus(entry, config, now, entryPathExists = false) {
|
|
246
248
|
const maybeMeta = entry.meta ?? {};
|
|
247
249
|
if (maybeMeta.inUse === true) {
|
|
248
250
|
return "in_use";
|
|
@@ -251,7 +253,7 @@ function deriveEntryStatus(entry, config, now) {
|
|
|
251
253
|
return "corrupt";
|
|
252
254
|
}
|
|
253
255
|
const candidatePaths = candidatePathsForEntry(entry);
|
|
254
|
-
const existingPaths = candidatePaths.filter((candidate) => existsSync(candidate));
|
|
256
|
+
const existingPaths = candidatePaths.filter((candidate) => (entryPathExists && candidate === entry.path) || existsSync(candidate));
|
|
255
257
|
if (entry.cacheKind === "artifact-index" && !existsSync(config.sqlitePath)) {
|
|
256
258
|
return "orphaned";
|
|
257
259
|
}
|
|
@@ -275,10 +277,9 @@ function deriveEntryStatus(entry, config, now) {
|
|
|
275
277
|
}
|
|
276
278
|
function sortEntries(entries) {
|
|
277
279
|
return [...entries].sort((left, right) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return left.entryId.localeCompare(right.entryId);
|
|
280
|
+
const leftKey = entrySortKey(left);
|
|
281
|
+
const rightKey = entrySortKey(right);
|
|
282
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
282
283
|
});
|
|
283
284
|
}
|
|
284
285
|
function entrySortKey(entry) {
|
|
@@ -367,17 +368,19 @@ function matchesSelector(entry, selector, runtimeInfo) {
|
|
|
367
368
|
return false;
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
if (selector.normalizedProjectPath) {
|
|
377
|
-
const projectMatch = normalizedPaths.some((candidate) => candidate === selector.normalizedProjectPath || candidate.startsWith(`${selector.normalizedProjectPath}/`));
|
|
378
|
-
if (!projectMatch) {
|
|
371
|
+
if (selector.normalizedJarPath || selector.normalizedProjectPath) {
|
|
372
|
+
const normalizedPaths = candidatePathsForEntry(entry)
|
|
373
|
+
.map((candidate) => normalizePathKey(candidate, runtimeInfo))
|
|
374
|
+
.filter((candidate) => Boolean(candidate));
|
|
375
|
+
if (selector.normalizedJarPath && !normalizedPaths.includes(selector.normalizedJarPath)) {
|
|
379
376
|
return false;
|
|
380
377
|
}
|
|
378
|
+
if (selector.normalizedProjectPath) {
|
|
379
|
+
const projectMatch = normalizedPaths.some((candidate) => candidate === selector.normalizedProjectPath || candidate.startsWith(`${selector.normalizedProjectPath}/`));
|
|
380
|
+
if (!projectMatch) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
381
384
|
}
|
|
382
385
|
return true;
|
|
383
386
|
}
|
|
@@ -455,18 +458,17 @@ function workspaceCacheEntries(workspaceCache) {
|
|
|
455
458
|
}
|
|
456
459
|
}));
|
|
457
460
|
}
|
|
458
|
-
async function fileBackedEntries(config, cacheKind) {
|
|
461
|
+
async function fileBackedEntries(config, cacheKind, detectCorruption) {
|
|
459
462
|
if (cacheKind === "binary-remap") {
|
|
460
463
|
return binaryRemapEntries(config);
|
|
461
464
|
}
|
|
462
465
|
const root = kindRoot(config, cacheKind);
|
|
463
466
|
const files = await listFilesRecursive(root);
|
|
464
|
-
|
|
465
|
-
for (const filePath of files) {
|
|
467
|
+
return mapWithConcurrencyLimit(files, CACHE_STAT_CONCURRENCY, async (filePath) => {
|
|
466
468
|
const fileStat = await stat(filePath);
|
|
467
469
|
const normalizedEntryId = filePath.slice(root.length + 1);
|
|
468
470
|
const inferredScope = inferScope(filePath, normalizedEntryId) ?? (cacheKind === "decompiled-source" ? "vanilla" : undefined);
|
|
469
|
-
|
|
471
|
+
return {
|
|
470
472
|
cacheKind,
|
|
471
473
|
entryId: normalizedEntryId,
|
|
472
474
|
path: filePath,
|
|
@@ -479,7 +481,7 @@ async function fileBackedEntries(config, cacheKind) {
|
|
|
479
481
|
scope: inferredScope,
|
|
480
482
|
projectPath: inferProjectPath(filePath, config.pathRuntimeInfo),
|
|
481
483
|
partial: fileStat.size === 0,
|
|
482
|
-
corrupt: cacheKind === "registry" ? isCorruptRegistryJson(filePath) : false,
|
|
484
|
+
corrupt: cacheKind === "registry" && detectCorruption ? await isCorruptRegistryJson(filePath) : false,
|
|
483
485
|
inUse: filePath.endsWith(".lock") ||
|
|
484
486
|
filePath.endsWith(".wal") ||
|
|
485
487
|
filePath.endsWith(".journal"),
|
|
@@ -487,9 +489,8 @@ async function fileBackedEntries(config, cacheKind) {
|
|
|
487
489
|
? { jarPath: filePath }
|
|
488
490
|
: {})
|
|
489
491
|
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
return entries;
|
|
492
|
+
};
|
|
493
|
+
});
|
|
493
494
|
}
|
|
494
495
|
/**
|
|
495
496
|
* Binary-remap cache entries are keyed by the final artifact id even when the
|
|
@@ -570,9 +571,10 @@ async function directoryFileSizeBytes(root) {
|
|
|
570
571
|
}
|
|
571
572
|
export function createCacheRegistry(config) {
|
|
572
573
|
const workspaceCache = config.workspaceContextCache ?? getProcessWorkspaceContextCache();
|
|
573
|
-
async function collectEntries(cacheKinds, selector) {
|
|
574
|
+
async function collectEntries(cacheKinds, selector, detectCorruption = false) {
|
|
574
575
|
const selectedKinds = cacheKinds?.length ? cacheKinds : [...PUBLIC_CACHE_KINDS];
|
|
575
576
|
const preparedSelector = prepareSelector(selector, config.pathRuntimeInfo);
|
|
577
|
+
const detectCorruptionForKinds = detectCorruption || selector?.status === "corrupt";
|
|
576
578
|
const now = Date.now();
|
|
577
579
|
const entries = await Promise.all(selectedKinds.map((cacheKind) => {
|
|
578
580
|
if (cacheKind === "artifact-index") {
|
|
@@ -581,13 +583,15 @@ export function createCacheRegistry(config) {
|
|
|
581
583
|
if (cacheKind === "workspace") {
|
|
582
584
|
return Promise.resolve(workspaceCacheEntries(workspaceCache));
|
|
583
585
|
}
|
|
584
|
-
return fileBackedEntries(config, cacheKind);
|
|
586
|
+
return fileBackedEntries(config, cacheKind, detectCorruptionForKinds);
|
|
585
587
|
}));
|
|
586
588
|
const enriched = entries
|
|
587
589
|
.flat()
|
|
588
590
|
.map((entry) => ({
|
|
589
591
|
...entry,
|
|
590
|
-
status: entry.cacheKind === "workspace"
|
|
592
|
+
status: entry.cacheKind === "workspace"
|
|
593
|
+
? entry.status
|
|
594
|
+
: deriveEntryStatus(entry, config, now, entry.cacheKind !== "artifact-index")
|
|
591
595
|
}));
|
|
592
596
|
return sortEntries(enriched.filter((entry) => matchesSelector(entry, preparedSelector, config.pathRuntimeInfo)));
|
|
593
597
|
}
|
|
@@ -628,7 +632,7 @@ export function createCacheRegistry(config) {
|
|
|
628
632
|
return entries.slice(0, limit);
|
|
629
633
|
},
|
|
630
634
|
async verifyEntries(input) {
|
|
631
|
-
const entries = await collectEntries(input.cacheKinds, input.selector);
|
|
635
|
+
const entries = await collectEntries(input.cacheKinds, input.selector, true);
|
|
632
636
|
const unhealthy = entries.filter((entry) => entry.status !== "healthy");
|
|
633
637
|
const warningStatuses = [...new Set(unhealthy.map((entry) => entry.status))];
|
|
634
638
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, constants, mkdir, readFile, readdir, stat } from "node:fs/promises";
|
|
1
|
+
import { access, constants, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { mkdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { basename, join, relative, sep } from "node:path";
|
|
@@ -8,6 +8,7 @@ import { assertJavaAvailable, runJavaProcess } from "../java-process.js";
|
|
|
8
8
|
import { log } from "../logger.js";
|
|
9
9
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
10
10
|
const DECOMPILED_JAVA_READ_CONCURRENCY = 8;
|
|
11
|
+
const DECOMPILE_COMPLETE_MARKER = ".decompile-complete";
|
|
11
12
|
const VINEFLOWER_FLAG_PROFILES = [
|
|
12
13
|
{ label: "default", flags: ["-din=1", "-rbr=1", "-dgs=1"] },
|
|
13
14
|
{ label: "relaxed", flags: ["-din=1", "-rbr=0", "-dgs=0"] },
|
|
@@ -132,7 +133,10 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
|
|
|
132
133
|
try {
|
|
133
134
|
await mkdir(outputDir, { recursive: true });
|
|
134
135
|
const outputDirStats = await stat(outputDir).catch(() => undefined);
|
|
135
|
-
|
|
136
|
+
const markerPresent = outputDirStats
|
|
137
|
+
? await access(join(outputDir, DECOMPILE_COMPLETE_MARKER), constants.F_OK).then(() => true, () => false)
|
|
138
|
+
: false;
|
|
139
|
+
if (outputDirStats && markerPresent) {
|
|
136
140
|
const existingJavaFiles = await collectJavaFiles(outputDir);
|
|
137
141
|
if (existingJavaFiles.length > 0) {
|
|
138
142
|
const results = await mapWithConcurrencyLimit(existingJavaFiles, DECOMPILED_JAVA_READ_CONCURRENCY, async (candidate) => {
|
|
@@ -153,6 +157,7 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
|
|
|
153
157
|
};
|
|
154
158
|
}
|
|
155
159
|
}
|
|
160
|
+
clearOutputDir(outputDir);
|
|
156
161
|
await assertVineflowerAvailable(options.vineflowerJarPath);
|
|
157
162
|
await assertJavaAvailable();
|
|
158
163
|
const profilesAttempted = [];
|
|
@@ -189,6 +194,7 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
|
|
|
189
194
|
content: await readFileTreeText(abs)
|
|
190
195
|
};
|
|
191
196
|
});
|
|
197
|
+
await writeFile(join(outputDir, DECOMPILE_COMPLETE_MARKER), profile.label, "utf8");
|
|
192
198
|
emitDecompileLog("decompile.done", {
|
|
193
199
|
durationMs: Date.now() - startedAt,
|
|
194
200
|
artifactIdCandidate: options.artifactIdCandidate,
|
|
@@ -248,7 +248,8 @@ export class AnalyzeModService {
|
|
|
248
248
|
jarPath: input.subject.jarPath
|
|
249
249
|
},
|
|
250
250
|
executionMode: "apply",
|
|
251
|
-
targetMapping: input.targetMapping
|
|
251
|
+
targetMapping: input.targetMapping,
|
|
252
|
+
...(input.outputJar !== undefined ? { outputJar } : {})
|
|
252
253
|
})
|
|
253
254
|
]
|
|
254
255
|
},
|
|
@@ -72,7 +72,7 @@ export const analyzeSymbolShape = {
|
|
|
72
72
|
}),
|
|
73
73
|
version: nonEmptyString
|
|
74
74
|
.optional()
|
|
75
|
-
.describe("Point-in-time MC version for task=exists/map/exact-map/api-overview. For task=lifecycle it is accepted as a back-compat alias for toVersion (range end)."),
|
|
75
|
+
.describe("Point-in-time MC version for task=exists/map/exact-map/workspace/api-overview. For task=lifecycle it is accepted as a back-compat alias for toVersion (range end)."),
|
|
76
76
|
fromVersion: nonEmptyString
|
|
77
77
|
.optional()
|
|
78
78
|
.describe("task=lifecycle only: range start (oldest) version; defaults to the oldest version in the manifest."),
|
|
@@ -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 !== "
|
|
115
|
+
if (value.task !== "lifecycle" && !value.version) {
|
|
116
116
|
ctx.addIssue({
|
|
117
117
|
code: z.ZodIssueCode.custom,
|
|
118
118
|
path: ["version"],
|
|
119
|
-
message: "version is required for non-
|
|
119
|
+
message: "version is required for non-lifecycle tasks."
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
if (value.task === "lifecycle" && !value.version && !value.toVersion) {
|
|
@@ -379,7 +379,7 @@ export class AnalyzeSymbolService {
|
|
|
379
379
|
case "workspace": {
|
|
380
380
|
const output = await this.deps.resolveWorkspaceSymbol({
|
|
381
381
|
projectPath: input.projectPath,
|
|
382
|
-
version: input.version
|
|
382
|
+
version: input.version,
|
|
383
383
|
kind: subjectKind,
|
|
384
384
|
name: input.subject.name,
|
|
385
385
|
owner: input.subject.owner,
|
|
@@ -405,7 +405,7 @@ export class AnalyzeSymbolService {
|
|
|
405
405
|
owner: input.subject.owner,
|
|
406
406
|
descriptor: input.subject.descriptor,
|
|
407
407
|
projectPath: input.projectPath,
|
|
408
|
-
version: input.version
|
|
408
|
+
version: input.version,
|
|
409
409
|
sourceMapping: input.sourceMapping ?? "obfuscated"
|
|
410
410
|
}),
|
|
411
411
|
counts: {
|
|
@@ -433,6 +433,11 @@ export class AnalyzeSymbolService {
|
|
|
433
433
|
...(input.gradleUserHome !== undefined ? { gradleUserHome: input.gradleUserHome } : {}),
|
|
434
434
|
maxRows: input.maxRows
|
|
435
435
|
});
|
|
436
|
+
const includeMatrix = include.includes("matrix") || detail !== "summary";
|
|
437
|
+
const matrixRowCap = input.maxRows ?? 25;
|
|
438
|
+
const matrixCapWarning = includeMatrix && output.rows.length > matrixRowCap
|
|
439
|
+
? `Matrix rows were truncated to ${matrixRowCap} of ${output.rowCount}. Raise maxRows to include more rows.`
|
|
440
|
+
: undefined;
|
|
436
441
|
return {
|
|
437
442
|
...buildEntryToolResult({
|
|
438
443
|
task: "api-overview",
|
|
@@ -458,16 +463,20 @@ export class AnalyzeSymbolService {
|
|
|
458
463
|
className: output.className,
|
|
459
464
|
classIdentity: output.classIdentity
|
|
460
465
|
},
|
|
461
|
-
matrix:
|
|
466
|
+
matrix: includeMatrix
|
|
462
467
|
? {
|
|
463
468
|
rowCount: output.rowCount,
|
|
464
|
-
rowsTruncated: output.rowsTruncated,
|
|
465
|
-
rows: output.rows.slice(0,
|
|
469
|
+
rowsTruncated: output.rowsTruncated || output.rows.length > matrixRowCap,
|
|
470
|
+
rows: output.rows.slice(0, matrixRowCap)
|
|
466
471
|
}
|
|
467
472
|
: undefined
|
|
468
473
|
}
|
|
469
474
|
}),
|
|
470
|
-
warnings:
|
|
475
|
+
warnings: [
|
|
476
|
+
...(inferenceWarning ? [inferenceWarning] : []),
|
|
477
|
+
...output.warnings,
|
|
478
|
+
...(matrixCapWarning ? [matrixCapWarning] : [])
|
|
479
|
+
]
|
|
471
480
|
};
|
|
472
481
|
}
|
|
473
482
|
}
|
|
@@ -64,7 +64,7 @@ export class CompareMinecraftService {
|
|
|
64
64
|
: {
|
|
65
65
|
fromVersion: input.subject.fromVersion,
|
|
66
66
|
toVersion: input.subject.toVersion,
|
|
67
|
-
packageFilter:
|
|
67
|
+
packageFilter: undefined
|
|
68
68
|
};
|
|
69
69
|
const output = await this.deps.compareVersions({
|
|
70
70
|
fromVersion: subject.fromVersion,
|
|
@@ -63,12 +63,13 @@ export async function handleClassMembers(deps, subject, detail, include, limit)
|
|
|
63
63
|
include,
|
|
64
64
|
warnings: [...artifact.warnings, ...members.warnings],
|
|
65
65
|
truncated: createTruncationMeta({
|
|
66
|
-
omittedGroups: ["members"],
|
|
66
|
+
omittedGroups: detail === "summary" ? ["members"] : [],
|
|
67
67
|
nextActions: [
|
|
68
68
|
createNextAction("inspect-minecraft", {
|
|
69
69
|
task: "class-members",
|
|
70
70
|
detail: "full",
|
|
71
71
|
include: ["members"],
|
|
72
|
+
limit: Math.min(members.counts.total, 5000),
|
|
72
73
|
subject
|
|
73
74
|
})
|
|
74
75
|
]
|
|
@@ -8,6 +8,31 @@ export async function handleFile(deps, subject, detail, include) {
|
|
|
8
8
|
const artifact = subject.kind === "file"
|
|
9
9
|
? await resolveArtifactReference(deps, subject, "file")
|
|
10
10
|
: await resolveWorkspaceArtifactReference(deps, subject, fileSubject.artifact);
|
|
11
|
+
if (!artifact.artifactId) {
|
|
12
|
+
const summary = {
|
|
13
|
+
status: "blocked",
|
|
14
|
+
headline: `Could not resolve artifact context for ${fileSubject.filePath}.`,
|
|
15
|
+
subject: createSummarySubject({
|
|
16
|
+
task: "file",
|
|
17
|
+
filePath: fileSubject.filePath
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
...buildEntryToolResult({
|
|
22
|
+
task: "file",
|
|
23
|
+
summary,
|
|
24
|
+
detail,
|
|
25
|
+
include,
|
|
26
|
+
blocks: {
|
|
27
|
+
subject: {
|
|
28
|
+
requested: subject
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
alwaysBlocks: ["subject"]
|
|
32
|
+
}),
|
|
33
|
+
warnings: artifact.warnings
|
|
34
|
+
};
|
|
35
|
+
}
|
|
11
36
|
const file = await deps.getArtifactFile({
|
|
12
37
|
artifactId: artifact.artifactId,
|
|
13
38
|
filePath: fileSubject.filePath
|
|
@@ -45,7 +70,7 @@ export async function handleFile(deps, subject, detail, include) {
|
|
|
45
70
|
content: include.includes("source") || detail !== "summary" ? file.content : undefined
|
|
46
71
|
}
|
|
47
72
|
},
|
|
48
|
-
alwaysBlocks: ["subject"]
|
|
73
|
+
alwaysBlocks: ["subject", "file"]
|
|
49
74
|
}),
|
|
50
75
|
warnings: [...artifact.warnings]
|
|
51
76
|
};
|
|
@@ -41,7 +41,8 @@ export async function handleVersions(deps, input, detail, include) {
|
|
|
41
41
|
snapshots: input.includeSnapshots ? versions.snapshots : undefined,
|
|
42
42
|
cached: versions.cached
|
|
43
43
|
}
|
|
44
|
-
}
|
|
44
|
+
},
|
|
45
|
+
alwaysBlocks: ["versions"]
|
|
45
46
|
}),
|
|
46
47
|
warnings: versions.warnings ?? []
|
|
47
48
|
};
|
|
@@ -210,6 +210,7 @@ export async function resolveWorkspaceArtifactReference(deps, subject, artifactR
|
|
|
210
210
|
return {
|
|
211
211
|
artifactId: artifact.artifactId,
|
|
212
212
|
artifact,
|
|
213
|
+
version: artifact.version,
|
|
213
214
|
warnings: [...artifact.warnings]
|
|
214
215
|
};
|
|
215
216
|
}
|
|
@@ -468,6 +469,7 @@ async function resolveArtifactRef(deps, ref, subject) {
|
|
|
468
469
|
return {
|
|
469
470
|
artifactId: artifact.artifactId,
|
|
470
471
|
artifact,
|
|
472
|
+
version: artifact.version,
|
|
471
473
|
warnings: [...artifact.warnings]
|
|
472
474
|
};
|
|
473
475
|
}
|
|
@@ -30,6 +30,8 @@ export declare function createTruncationMeta(input: {
|
|
|
30
30
|
}): TruncationMeta;
|
|
31
31
|
export declare function buildEntryToolMeta(input: {
|
|
32
32
|
detail: DetailLevel;
|
|
33
|
+
/** When set and equal to `detail`, detailApplied is omitted (the default needs no echo). */
|
|
34
|
+
defaultDetail?: DetailLevel;
|
|
33
35
|
include?: readonly string[];
|
|
34
36
|
warnings?: readonly string[];
|
|
35
37
|
truncated?: TruncationMeta;
|
|
@@ -71,8 +71,8 @@ export function createTruncationMeta(input) {
|
|
|
71
71
|
}
|
|
72
72
|
export function buildEntryToolMeta(input) {
|
|
73
73
|
return {
|
|
74
|
-
warnings: [...
|
|
75
|
-
detailApplied: input.detail,
|
|
74
|
+
...(input.warnings && input.warnings.length > 0 ? { warnings: [...input.warnings] } : {}),
|
|
75
|
+
...(input.detail === input.defaultDetail ? {} : { detailApplied: input.detail }),
|
|
76
76
|
...(input.include && input.include.length > 0
|
|
77
77
|
? { includeApplied: normalizeIncludeGroups(input.include) }
|
|
78
78
|
: {}),
|
|
@@ -454,7 +454,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
|
|
|
454
454
|
reportMode: "full" | "summary-first" | "compact";
|
|
455
455
|
treatInfoAsWarning: boolean;
|
|
456
456
|
includeIssues: boolean;
|
|
457
|
-
task: "project-summary" | "mixin" | "access-
|
|
457
|
+
task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
|
|
458
458
|
subject: {
|
|
459
459
|
kind: "workspace";
|
|
460
460
|
projectPath: string;
|
|
@@ -510,7 +510,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
|
|
|
510
510
|
include?: string[] | undefined;
|
|
511
511
|
atNamespace?: "obfuscated" | "mojang" | "srg" | undefined;
|
|
512
512
|
}, {
|
|
513
|
-
task: "project-summary" | "mixin" | "access-
|
|
513
|
+
task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
|
|
514
514
|
subject: {
|
|
515
515
|
kind: "workspace";
|
|
516
516
|
projectPath: string;
|
|
@@ -580,7 +580,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
|
|
|
580
580
|
reportMode: "full" | "summary-first" | "compact";
|
|
581
581
|
treatInfoAsWarning: boolean;
|
|
582
582
|
includeIssues: boolean;
|
|
583
|
-
task: "project-summary" | "mixin" | "access-
|
|
583
|
+
task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
|
|
584
584
|
subject: {
|
|
585
585
|
kind: "workspace";
|
|
586
586
|
projectPath: string;
|
|
@@ -636,7 +636,7 @@ export declare const validateProjectSchema: z.ZodEffects<z.ZodObject<{
|
|
|
636
636
|
include?: string[] | undefined;
|
|
637
637
|
atNamespace?: "obfuscated" | "mojang" | "srg" | undefined;
|
|
638
638
|
}, {
|
|
639
|
-
task: "project-summary" | "mixin" | "access-
|
|
639
|
+
task: "project-summary" | "mixin" | "access-transformer" | "access-widener";
|
|
640
640
|
subject: {
|
|
641
641
|
kind: "workspace";
|
|
642
642
|
projectPath: string;
|
|
@@ -5,7 +5,7 @@ import { suggestSimilar } from "../mixin-validator.js";
|
|
|
5
5
|
export const VERIFY_MIXIN_TARGET_OFF = process.env.VERIFY_MIXIN_TARGET_OFF === "1";
|
|
6
6
|
const ACCESSOR_NAME_RE = /^(get|set|is)([A-Z]\w*)$/;
|
|
7
7
|
const INVOKER_NAME_RE = /^(invoke|call)([A-Z]\w*)$/;
|
|
8
|
-
function decodeAccessFlags(flags) {
|
|
8
|
+
function decodeAccessFlags(flags, kind) {
|
|
9
9
|
const labels = [];
|
|
10
10
|
if (flags == null) {
|
|
11
11
|
return labels;
|
|
@@ -23,9 +23,9 @@ function decodeAccessFlags(flags) {
|
|
|
23
23
|
if ((flags & 0x0020) !== 0)
|
|
24
24
|
labels.push("synchronized");
|
|
25
25
|
if ((flags & 0x0040) !== 0)
|
|
26
|
-
labels.push("volatile");
|
|
26
|
+
labels.push(kind === "method" ? "bridge" : "volatile");
|
|
27
27
|
if ((flags & 0x0080) !== 0)
|
|
28
|
-
labels.push("transient");
|
|
28
|
+
labels.push(kind === "method" ? "varargs" : "transient");
|
|
29
29
|
if ((flags & 0x0100) !== 0)
|
|
30
30
|
labels.push("native");
|
|
31
31
|
if ((flags & 0x0400) !== 0)
|
|
@@ -137,11 +137,11 @@ function deriveVisibility(accessFlags) {
|
|
|
137
137
|
return "private";
|
|
138
138
|
return "package-private";
|
|
139
139
|
}
|
|
140
|
-
function buildMatch(member) {
|
|
140
|
+
function buildMatch(member, kind) {
|
|
141
141
|
return {
|
|
142
142
|
name: member.name,
|
|
143
143
|
descriptor: member.jvmDescriptor,
|
|
144
|
-
accessFlags: decodeAccessFlags(member.accessFlags),
|
|
144
|
+
accessFlags: decodeAccessFlags(member.accessFlags, kind),
|
|
145
145
|
javaSignature: member.javaSignature,
|
|
146
146
|
...(member.sourceLine ? { sourceLine: member.sourceLine } : {})
|
|
147
147
|
};
|
|
@@ -267,7 +267,7 @@ export class VerifyMixinTargetService {
|
|
|
267
267
|
if (member.descriptor) {
|
|
268
268
|
const descriptorHits = exactNameHits.filter((m) => m.jvmDescriptor === member.descriptor);
|
|
269
269
|
if (descriptorHits.length > 0) {
|
|
270
|
-
matches = descriptorHits.map(buildMatch);
|
|
270
|
+
matches = descriptorHits.map((m) => buildMatch(m, member.kind));
|
|
271
271
|
}
|
|
272
272
|
else {
|
|
273
273
|
matches = [];
|
|
@@ -279,7 +279,7 @@ export class VerifyMixinTargetService {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
else {
|
|
282
|
-
matches = exactNameHits.map(buildMatch);
|
|
282
|
+
matches = exactNameHits.map((m) => buildMatch(m, member.kind));
|
|
283
283
|
}
|
|
284
284
|
if (matches.length === 0 && candidates.length === 0) {
|
|
285
285
|
const suggestions = suggestSimilar(memberName, allMemberNames);
|