@adhisang/minecraft-modding-mcp 7.0.0 → 7.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +4 -3
- package/dist/access-transformer-parser.d.ts +8 -0
- package/dist/access-transformer-parser.js +8 -1
- package/dist/access-widener-parser.d.ts +17 -0
- package/dist/access-widener-parser.js +12 -1
- package/dist/cache-registry.js +19 -6
- package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-members-service.js +20 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-source-service.js +10 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
- package/dist/entry-tools/compare-minecraft-service.js +65 -4
- package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +15 -9
- package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.js +10 -14
- package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
- package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
- package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
- package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -8
- package/dist/index.js +38 -15
- package/dist/java-process.d.ts +1 -0
- package/dist/java-process.js +14 -0
- package/dist/mapping/lookup.js +16 -1
- package/dist/mapping-service.d.ts +14 -0
- package/dist/mapping-service.js +35 -15
- package/dist/minecraft-explorer-service.js +70 -8
- package/dist/mixin/access-validators.js +38 -2
- package/dist/mixin/annotation-validators.js +137 -43
- package/dist/mixin/parsed-validator.js +21 -7
- package/dist/mixin-parser.d.ts +52 -0
- package/dist/mixin-parser.js +709 -130
- package/dist/mod-decompile-service.js +11 -1
- package/dist/mod-remap-service.js +6 -6
- package/dist/nbt/java-nbt-codec.js +7 -1
- package/dist/source/access-validate.js +10 -0
- package/dist/source/artifact-resolver.d.ts +27 -3
- package/dist/source/artifact-resolver.js +235 -30
- package/dist/source/class-source/members-builder.d.ts +7 -0
- package/dist/source/class-source/members-builder.js +4 -1
- package/dist/source/class-source.d.ts +9 -2
- package/dist/source/class-source.js +186 -27
- package/dist/source/indexer.js +69 -1
- package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
- package/dist/source/lifecycle/mapping-helpers.js +29 -3
- package/dist/source/lifecycle/runtime-check.d.ts +25 -0
- package/dist/source/lifecycle/runtime-check.js +68 -39
- package/dist/source/nested-jars.d.ts +15 -1
- package/dist/source/nested-jars.js +14 -5
- package/dist/source/search.d.ts +10 -2
- package/dist/source/search.js +60 -13
- package/dist/source/symbol-resolver.js +88 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
- package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
- package/dist/source/validate-mixin.d.ts +5 -0
- package/dist/source/validate-mixin.js +136 -21
- package/dist/source/workspace-target.js +75 -7
- package/dist/source-jar-reader.d.ts +48 -1
- package/dist/source-jar-reader.js +93 -3
- package/dist/source-resolver.d.ts +7 -0
- package/dist/source-resolver.js +22 -14
- package/dist/source-service.d.ts +5 -0
- package/dist/source-service.js +7 -0
- package/dist/stdio-supervisor.d.ts +35 -1
- package/dist/stdio-supervisor.js +77 -2
- package/dist/storage/db.d.ts +62 -2
- package/dist/storage/db.js +181 -20
- package/dist/storage/files-repo.d.ts +7 -0
- package/dist/storage/files-repo.js +17 -4
- package/dist/storage/sqlite.d.ts +31 -1
- package/dist/storage/sqlite.js +125 -16
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +2 -1
- package/dist/tool-guidance.js +4 -1
- package/dist/tool-schemas.d.ts +64 -52
- package/dist/tool-schemas.js +9 -7
- package/dist/types.d.ts +9 -0
- package/dist/v1-parity-schemas.js +36 -2
- package/dist/version-diff-service.d.ts +23 -0
- package/dist/version-diff-service.js +101 -0
- package/dist/version-service.d.ts +14 -0
- package/dist/version-service.js +52 -3
- package/dist/workspace-context-cache.d.ts +25 -0
- package/dist/workspace-context-cache.js +52 -2
- package/dist/workspace-mapping-service.d.ts +8 -0
- package/dist/workspace-mapping-service.js +151 -21
- package/docs/README-ja.md +3 -1
- package/docs/tool-reference.md +69 -22
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { diffLibraries } from "../version-diff-service.js";
|
|
2
3
|
import { createError, ERROR_CODES } from "../errors.js";
|
|
3
4
|
import { buildIncludeSchema, detailSchema, positiveIntSchema } from "./entry-tool-schema.js";
|
|
4
5
|
import { buildEntryToolMeta, buildEntryToolResult, createNextAction, createSummarySubject, createTruncationMeta } from "./response-contract.js";
|
|
@@ -42,10 +43,37 @@ export const compareMinecraftSchema = z.object(compareMinecraftShape);
|
|
|
42
43
|
function compareStatusFromCounts(changedCount) {
|
|
43
44
|
return changedCount > 0 ? "changed" : "unchanged";
|
|
44
45
|
}
|
|
46
|
+
/** Default bound on migration-overview's library enrichment (see `libraryDiffDeadlineMs`). */
|
|
47
|
+
const DEFAULT_LIBRARY_DIFF_DEADLINE_MS = 5000;
|
|
48
|
+
class LibraryDiffTimeoutError extends Error {
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Races `promise` against a `ms` timer. The timer is always cleared (on
|
|
52
|
+
* either settlement) and unref'd so it can never keep the process alive.
|
|
53
|
+
*/
|
|
54
|
+
function withDeadline(promise, ms) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
reject(new LibraryDiffTimeoutError(`timed out after ${ms}ms`));
|
|
58
|
+
}, ms);
|
|
59
|
+
if (typeof timer.unref === "function") {
|
|
60
|
+
timer.unref();
|
|
61
|
+
}
|
|
62
|
+
promise.then((value) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve(value);
|
|
65
|
+
}, (error) => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
reject(error);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
45
71
|
export class CompareMinecraftService {
|
|
46
72
|
deps;
|
|
47
|
-
|
|
73
|
+
libraryDiffDeadlineMs;
|
|
74
|
+
constructor(deps, options) {
|
|
48
75
|
this.deps = deps;
|
|
76
|
+
this.libraryDiffDeadlineMs = options?.libraryDiffDeadlineMs ?? DEFAULT_LIBRARY_DIFF_DEADLINE_MS;
|
|
49
77
|
}
|
|
50
78
|
async execute(input) {
|
|
51
79
|
const task = input.task && input.task !== "auto"
|
|
@@ -362,6 +390,32 @@ export class CompareMinecraftService {
|
|
|
362
390
|
const registrySignals = compare.registry?.summary.registriesChanged ?? 0;
|
|
363
391
|
const status = compareStatusFromCounts(classSignals + registrySignals);
|
|
364
392
|
const representativeClassName = compare.classes?.added[0] ?? compare.classes?.removed[0];
|
|
393
|
+
// Library changes (e.g. LWJGL GLFW replaced by SDL) never show up in the
|
|
394
|
+
// class/registry diff above, so fetch and diff them separately. This must
|
|
395
|
+
// never fail the whole migration-overview: a missing dependency wiring, a
|
|
396
|
+
// fetch failure, or a deadline expiry (e.g. offline after a restart, with
|
|
397
|
+
// only cached jars) just omits the block and adds a warning. On a
|
|
398
|
+
// timeout, the in-flight fetch is left running — it may still warm the
|
|
399
|
+
// version-detail cache for a later call — we simply stop waiting on it.
|
|
400
|
+
let librariesResult;
|
|
401
|
+
const libraryWarnings = [];
|
|
402
|
+
if (this.deps.getVersionLibraries) {
|
|
403
|
+
try {
|
|
404
|
+
const [fromLibraries, toLibraries] = await withDeadline(Promise.all([
|
|
405
|
+
this.deps.getVersionLibraries({ version: subject.fromVersion }),
|
|
406
|
+
this.deps.getVersionLibraries({ version: subject.toVersion })
|
|
407
|
+
]), this.libraryDiffDeadlineMs);
|
|
408
|
+
librariesResult = diffLibraries(fromLibraries, toLibraries);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
if (error instanceof LibraryDiffTimeoutError) {
|
|
412
|
+
libraryWarnings.push(`Library comparison between ${subject.fromVersion} and ${subject.toVersion} timed out after ${this.libraryDiffDeadlineMs}ms.`);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
libraryWarnings.push(`Could not compare libraries between ${subject.fromVersion} and ${subject.toVersion}: ${error instanceof Error ? error.message : String(error)}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
365
419
|
const nextActions = representativeClassName
|
|
366
420
|
? [
|
|
367
421
|
createNextAction("compare-minecraft", {
|
|
@@ -399,7 +453,13 @@ export class CompareMinecraftService {
|
|
|
399
453
|
}),
|
|
400
454
|
counts: {
|
|
401
455
|
classSignals,
|
|
402
|
-
registrySignals
|
|
456
|
+
registrySignals,
|
|
457
|
+
...(librariesResult
|
|
458
|
+
? {
|
|
459
|
+
librariesAdded: librariesResult.addedCount,
|
|
460
|
+
librariesRemoved: librariesResult.removedCount
|
|
461
|
+
}
|
|
462
|
+
: {})
|
|
403
463
|
},
|
|
404
464
|
nextActions
|
|
405
465
|
},
|
|
@@ -413,11 +473,12 @@ export class CompareMinecraftService {
|
|
|
413
473
|
: registrySignals > 0
|
|
414
474
|
? "registry"
|
|
415
475
|
: "minimal",
|
|
416
|
-
nextActions
|
|
476
|
+
nextActions,
|
|
477
|
+
...(librariesResult ? { libraries: librariesResult } : {})
|
|
417
478
|
}
|
|
418
479
|
}
|
|
419
480
|
}),
|
|
420
|
-
warnings: compare.warnings
|
|
481
|
+
warnings: [...compare.warnings, ...libraryWarnings]
|
|
421
482
|
};
|
|
422
483
|
}
|
|
423
484
|
default:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const detailSchema: z.ZodEnum<{
|
|
3
|
+
full: "full";
|
|
3
4
|
summary: "summary";
|
|
4
5
|
standard: "standard";
|
|
5
|
-
full: "full";
|
|
6
6
|
}>;
|
|
7
7
|
export declare const includeGroupSchema: z.ZodEnum<{
|
|
8
8
|
workspace: "workspace";
|
|
@@ -17,6 +17,7 @@ export declare const includeGroupSchema: z.ZodEnum<{
|
|
|
17
17
|
samples: "samples";
|
|
18
18
|
timings: "timings";
|
|
19
19
|
matrix: "matrix";
|
|
20
|
+
artifact: "artifact";
|
|
20
21
|
provenance: "provenance";
|
|
21
22
|
descriptors: "descriptors";
|
|
22
23
|
diff: "diff";
|
|
@@ -27,7 +28,6 @@ export declare const includeGroupSchema: z.ZodEnum<{
|
|
|
27
28
|
paths: "paths";
|
|
28
29
|
owners: "owners";
|
|
29
30
|
cacheEntries: "cacheEntries";
|
|
30
|
-
artifact: "artifact";
|
|
31
31
|
classes: "classes";
|
|
32
32
|
}>;
|
|
33
33
|
export declare const executionModeSchema: z.ZodEnum<{
|
|
@@ -5,6 +5,10 @@ export async function handleVersions(deps, input, detail, include) {
|
|
|
5
5
|
includeSnapshots: input.includeSnapshots,
|
|
6
6
|
limit: input.limit
|
|
7
7
|
});
|
|
8
|
+
// versions.latest.release and releases[0] can both be absent for an effectively
|
|
9
|
+
// empty version manifest; omit the nextAction rather than suggest an unusable
|
|
10
|
+
// follow-up call with no version.
|
|
11
|
+
const resolvedNextVersion = versions.latest.release ?? versions.releases[0]?.id;
|
|
8
12
|
const summary = {
|
|
9
13
|
status: "ok",
|
|
10
14
|
headline: `Found ${versions.totalAvailable} Minecraft versions.`,
|
|
@@ -18,15 +22,17 @@ export async function handleVersions(deps, input, detail, include) {
|
|
|
18
22
|
releases: versions.releases.length,
|
|
19
23
|
snapshots: versions.snapshots?.length ?? 0
|
|
20
24
|
},
|
|
21
|
-
nextActions: nextActionsOrUndefined(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
nextActions: nextActionsOrUndefined(resolvedNextVersion !== undefined
|
|
26
|
+
? [
|
|
27
|
+
createNextAction("inspect-minecraft", {
|
|
28
|
+
task: "artifact",
|
|
29
|
+
subject: {
|
|
30
|
+
kind: "version",
|
|
31
|
+
version: resolvedNextVersion
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
: [])
|
|
30
36
|
};
|
|
31
37
|
return {
|
|
32
38
|
...buildEntryToolResult({
|
|
@@ -251,9 +251,9 @@ export declare const inspectMinecraftShape: {
|
|
|
251
251
|
}, z.core.$strip>], "kind">>;
|
|
252
252
|
includeSnapshots: z.ZodDefault<z.ZodBoolean>;
|
|
253
253
|
detail: z.ZodOptional<z.ZodEnum<{
|
|
254
|
+
full: "full";
|
|
254
255
|
summary: "summary";
|
|
255
256
|
standard: "standard";
|
|
256
|
-
full: "full";
|
|
257
257
|
}>>;
|
|
258
258
|
include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
259
259
|
[x: string]: string;
|
|
@@ -512,9 +512,9 @@ export declare const inspectMinecraftSchema: z.ZodObject<{
|
|
|
512
512
|
}, z.core.$strip>], "kind">>;
|
|
513
513
|
includeSnapshots: z.ZodDefault<z.ZodBoolean>;
|
|
514
514
|
detail: z.ZodOptional<z.ZodEnum<{
|
|
515
|
+
full: "full";
|
|
515
516
|
summary: "summary";
|
|
516
517
|
standard: "standard";
|
|
517
|
-
full: "full";
|
|
518
518
|
}>>;
|
|
519
519
|
include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
520
520
|
[x: string]: string;
|
|
@@ -40,9 +40,9 @@ export declare const manageCacheShape: {
|
|
|
40
40
|
projectPath: z.ZodOptional<z.ZodString>;
|
|
41
41
|
}, z.core.$strip>>;
|
|
42
42
|
detail: z.ZodOptional<z.ZodEnum<{
|
|
43
|
+
full: "full";
|
|
43
44
|
summary: "summary";
|
|
44
45
|
standard: "standard";
|
|
45
|
-
full: "full";
|
|
46
46
|
}>>;
|
|
47
47
|
include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
48
48
|
[x: string]: string;
|
|
@@ -94,9 +94,9 @@ export declare const manageCacheSchema: z.ZodObject<{
|
|
|
94
94
|
projectPath: z.ZodOptional<z.ZodString>;
|
|
95
95
|
}, z.core.$strip>>;
|
|
96
96
|
detail: z.ZodOptional<z.ZodEnum<{
|
|
97
|
+
full: "full";
|
|
97
98
|
summary: "summary";
|
|
98
99
|
standard: "standard";
|
|
99
|
-
full: "full";
|
|
100
100
|
}>>;
|
|
101
101
|
include: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
102
102
|
[x: string]: string;
|
|
@@ -225,23 +225,19 @@ export class ManageCacheService {
|
|
|
225
225
|
include,
|
|
226
226
|
summary: {
|
|
227
227
|
status: output.rebuiltEntries > 0 && executionMode === "apply" ? "changed" : "unchanged",
|
|
228
|
-
headline
|
|
228
|
+
// Neither headline may read as work done: rebuild is not
|
|
229
|
+
// implemented for any cache kind, so apply changes nothing and
|
|
230
|
+
// preview has nothing to promise.
|
|
231
|
+
headline: executionMode === "apply"
|
|
232
|
+
? `Rebuild is not implemented for ${cacheKinds.length} cache kind(s); nothing changed.`
|
|
233
|
+
: `Previewed rebuild for ${cacheKinds.length} cache kind(s): nothing would change.`,
|
|
229
234
|
subject: summarySubject,
|
|
230
235
|
counts: {
|
|
231
236
|
rebuiltEntries: output.rebuiltEntries
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
createNextAction("manage-cache", {
|
|
237
|
-
action: "rebuild",
|
|
238
|
-
cacheKinds,
|
|
239
|
-
executionMode: "apply",
|
|
240
|
-
selector: input.selector
|
|
241
|
-
})
|
|
242
|
-
]
|
|
243
|
-
}
|
|
244
|
-
: {})
|
|
237
|
+
}
|
|
238
|
+
// No apply follow-up: rebuild is not implemented for any cache
|
|
239
|
+
// kind, so advertising one would promise a call that changes
|
|
240
|
+
// nothing. The registry's warning names what to do instead.
|
|
245
241
|
},
|
|
246
242
|
blocks: {
|
|
247
243
|
operation: {
|
|
@@ -34,9 +34,28 @@ export async function handleAccessTransformer(deps, input, detail, include) {
|
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
let content;
|
|
38
|
+
if (input.subject.input.mode === "inline") {
|
|
39
|
+
content = input.subject.input.content;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const atPath = input.subject.input.path;
|
|
43
|
+
try {
|
|
44
|
+
content = await readFile(atPath, "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const osCode = err?.code ?? "UNKNOWN";
|
|
48
|
+
throw createError({
|
|
49
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
50
|
+
message: `Could not read access transformer file "${atPath}": ${osCode} - ${err instanceof Error ? err.message : String(err)}`,
|
|
51
|
+
details: {
|
|
52
|
+
task: "access-transformer",
|
|
53
|
+
failedStage: "input-validation",
|
|
54
|
+
nextAction: `Verify the file exists and is readable at "${atPath}".`
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
40
59
|
if (!deps.validateAccessTransformer) {
|
|
41
60
|
throw createError({
|
|
42
61
|
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
@@ -34,9 +34,28 @@ export async function handleAccessWidener(deps, input, detail, include) {
|
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
let content;
|
|
38
|
+
if (input.subject.input.mode === "inline") {
|
|
39
|
+
content = input.subject.input.content;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const awPath = input.subject.input.path;
|
|
43
|
+
try {
|
|
44
|
+
content = await readFile(awPath, "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const osCode = err?.code ?? "UNKNOWN";
|
|
48
|
+
throw createError({
|
|
49
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
50
|
+
message: `Could not read access widener file "${awPath}": ${osCode} - ${err instanceof Error ? err.message : String(err)}`,
|
|
51
|
+
details: {
|
|
52
|
+
task: "access-widener",
|
|
53
|
+
failedStage: "input-validation",
|
|
54
|
+
nextAction: `Verify the file exists and is readable at "${awPath}".`
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
40
59
|
const output = await deps.validateAccessWidener({
|
|
41
60
|
content,
|
|
42
61
|
version: input.version,
|
|
@@ -45,6 +64,14 @@ export async function handleAccessWidener(deps, input, detail, include) {
|
|
|
45
64
|
scope: input.scope,
|
|
46
65
|
preferProjectVersion: input.preferProjectVersion
|
|
47
66
|
});
|
|
67
|
+
const issueEntries = Array.isArray(output.entries)
|
|
68
|
+
? output.entries.filter((entry) => {
|
|
69
|
+
if (!entry || typeof entry !== "object" || !("valid" in entry)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return entry.valid !== true;
|
|
73
|
+
})
|
|
74
|
+
: undefined;
|
|
48
75
|
return {
|
|
49
76
|
...buildEntryToolResult({
|
|
50
77
|
task: "access-widener",
|
|
@@ -76,7 +103,7 @@ export async function handleAccessWidener(deps, input, detail, include) {
|
|
|
76
103
|
invalid: output.valid ? 0 : 1
|
|
77
104
|
}
|
|
78
105
|
},
|
|
79
|
-
issues: include.includes("issues") || detail !== "summary" ?
|
|
106
|
+
issues: include.includes("issues") || detail !== "summary" ? issueEntries : undefined
|
|
80
107
|
},
|
|
81
108
|
alwaysBlocks: ["project"]
|
|
82
109
|
}),
|
|
@@ -74,14 +74,21 @@ export async function handleMixin(deps, input, detail, include) {
|
|
|
74
74
|
const summary = output.summary;
|
|
75
75
|
const invalidCount = summary?.invalid ?? 0;
|
|
76
76
|
const partialCount = summary?.partial ?? 0;
|
|
77
|
+
const processingErrorsCount = summary?.processingErrors ?? 0;
|
|
77
78
|
return {
|
|
78
79
|
...buildEntryToolResult({
|
|
79
80
|
task: "mixin",
|
|
80
81
|
detail,
|
|
81
82
|
include,
|
|
82
83
|
summary: {
|
|
83
|
-
status: invalidCount > 0
|
|
84
|
-
|
|
84
|
+
status: invalidCount > 0
|
|
85
|
+
? "invalid"
|
|
86
|
+
: partialCount > 0 || processingErrorsCount > 0
|
|
87
|
+
? "partial"
|
|
88
|
+
: "ok",
|
|
89
|
+
headline: processingErrorsCount > 0
|
|
90
|
+
? `Validated ${summary?.total ?? 0} mixin input(s); ${processingErrorsCount} could not be processed.`
|
|
91
|
+
: `Validated ${summary?.total ?? 0} mixin input(s).`,
|
|
85
92
|
subject: createSummarySubject({
|
|
86
93
|
task: "mixin",
|
|
87
94
|
kind: input.subject.kind,
|
|
@@ -94,7 +101,8 @@ export async function handleMixin(deps, input, detail, include) {
|
|
|
94
101
|
counts: {
|
|
95
102
|
valid: summary?.valid ?? 0,
|
|
96
103
|
partial: partialCount,
|
|
97
|
-
invalid: invalidCount
|
|
104
|
+
invalid: invalidCount,
|
|
105
|
+
processingErrors: processingErrorsCount
|
|
98
106
|
}
|
|
99
107
|
},
|
|
100
108
|
blocks: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { mapWithConcurrencyLimit } from "../../../concurrency.js";
|
|
3
3
|
import { buildEntryToolResult, createSummarySubject } from "../../response-contract.js";
|
|
4
|
-
import { ERROR_CODES, createError } from "../../../errors.js";
|
|
4
|
+
import { ERROR_CODES, createError, isAppError } from "../../../errors.js";
|
|
5
5
|
import { buildEarlyTasksForBlocked, buildFullTaskStatusReport } from "../internal.js";
|
|
6
6
|
// Telemetry failures must not change validation outcomes; swallow rejections
|
|
7
7
|
// so a broken emitter does not abort the summary or count as a validation error.
|
|
@@ -15,6 +15,40 @@ async function safeEmit(emitter, stage, payload) {
|
|
|
15
15
|
// swallow telemetry failure
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
const DISCOVER_KIND_LABELS = [
|
|
19
|
+
["mixins", "mixin configs"],
|
|
20
|
+
["access-wideners", "access wideners"],
|
|
21
|
+
["access-transformers", "access transformers"]
|
|
22
|
+
];
|
|
23
|
+
function joinWithOr(items) {
|
|
24
|
+
if (items.length <= 2) {
|
|
25
|
+
return items.join(" or ");
|
|
26
|
+
}
|
|
27
|
+
return `${items.slice(0, -1).join(", ")}, or ${items[items.length - 1]}`;
|
|
28
|
+
}
|
|
29
|
+
// A run that discovered no files keeps status "ok" (nothing failed), so the
|
|
30
|
+
// headline and a warning must say that nothing was checked; otherwise the
|
|
31
|
+
// result reads as a pass. Only the kinds subject.discover searched are named.
|
|
32
|
+
function describeNothingToValidate(discover, projectPath) {
|
|
33
|
+
const searched = DISCOVER_KIND_LABELS
|
|
34
|
+
.filter(([kind]) => discover.includes(kind))
|
|
35
|
+
.map(([, label]) => label);
|
|
36
|
+
const notOk = "so status \"ok\" does not mean any file passed validation.";
|
|
37
|
+
const accessTransformerHint = discover.includes("access-transformers")
|
|
38
|
+
? ""
|
|
39
|
+
: " Access Transformer files are searched only when subject.discover includes \"access-transformers\".";
|
|
40
|
+
if (searched.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
headline: "Nothing to validate: subject.discover selected no file kinds to search.",
|
|
43
|
+
warning: `Nothing was validated: subject.discover selected no file kinds to search, ${notOk}${accessTransformerHint}`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const list = joinWithOr(searched);
|
|
47
|
+
return {
|
|
48
|
+
headline: `Nothing to validate: no ${list} were found.`,
|
|
49
|
+
warning: `Nothing was validated: no ${list} were found under ${projectPath}, ${notOk}${accessTransformerHint}`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
18
52
|
export async function handleProjectSummary(deps, input, detail, include, options = {}) {
|
|
19
53
|
// Forwarded emitter for nested validators and probes; same swallow contract
|
|
20
54
|
// as safeEmit so a rejecting raw emitter cannot leak into their outcomes.
|
|
@@ -27,7 +61,9 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
27
61
|
message: "task=project-summary requires subject.kind=workspace."
|
|
28
62
|
});
|
|
29
63
|
}
|
|
30
|
-
|
|
64
|
+
// An omitted version is inferred from the project below; only an explicit
|
|
65
|
+
// preferProjectVersion=false opts out of that inference.
|
|
66
|
+
if (!input.version && input.preferProjectVersion === false) {
|
|
31
67
|
const baseResult = buildEntryToolResult({
|
|
32
68
|
task: "project-summary",
|
|
33
69
|
detail,
|
|
@@ -52,7 +88,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
52
88
|
}
|
|
53
89
|
],
|
|
54
90
|
notes: [
|
|
55
|
-
"Pass version explicitly, or retry
|
|
91
|
+
"Version inference was suppressed by preferProjectVersion=false. Pass version explicitly, or retry without preferProjectVersion=false to infer it from gradle.properties. The suggested retry sets preferProjectVersion=true for you."
|
|
56
92
|
]
|
|
57
93
|
},
|
|
58
94
|
blocks: {
|
|
@@ -79,10 +115,21 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
79
115
|
projectPath,
|
|
80
116
|
discover
|
|
81
117
|
});
|
|
82
|
-
|
|
118
|
+
// preferProjectVersion=true overrides an explicit version; an omitted version is
|
|
119
|
+
// inferred (the explicit-false opt-out returned above).
|
|
120
|
+
const detectedProjectVersion = input.preferProjectVersion === true || !input.version
|
|
83
121
|
? await deps.detectProjectMinecraftVersion?.(projectPath)
|
|
84
122
|
: undefined;
|
|
85
123
|
const resolvedVersion = detectedProjectVersion ?? input.version;
|
|
124
|
+
const versionInferred = !input.version && Boolean(detectedProjectVersion);
|
|
125
|
+
// An inferred version is a project-version resolution: sub-validators and the
|
|
126
|
+
// artifact probe see exactly what an explicit preferProjectVersion=true run sees.
|
|
127
|
+
const effectivePreferProjectVersion = versionInferred ? true : input.preferProjectVersion;
|
|
128
|
+
const versionInferenceWarnings = versionInferred
|
|
129
|
+
? [
|
|
130
|
+
`version was inferred from the workspace: ${detectedProjectVersion} (source: projectPath:gradle.properties (${projectPath})).`
|
|
131
|
+
]
|
|
132
|
+
: [];
|
|
86
133
|
const [mixinConfigs, accessWideners, accessTransformers] = await Promise.all([
|
|
87
134
|
discover.includes("mixins")
|
|
88
135
|
? deps.discoverMixins(projectPath, input.configPaths)
|
|
@@ -111,17 +158,20 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
111
158
|
sourcePriority: input.sourcePriority,
|
|
112
159
|
scope: input.scope
|
|
113
160
|
}),
|
|
161
|
+
// Retrying without a version would infer, fail, and block again, so the
|
|
162
|
+
// recovery asks for an explicit version instead.
|
|
114
163
|
nextActions: [
|
|
115
164
|
{
|
|
116
165
|
tool: "validate-project",
|
|
117
166
|
params: {
|
|
118
167
|
task: "project-summary",
|
|
119
|
-
subject: input.subject
|
|
168
|
+
subject: input.subject,
|
|
169
|
+
version: "<minecraft-version>"
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
172
|
],
|
|
123
173
|
notes: [
|
|
124
|
-
"
|
|
174
|
+
"The Minecraft version could not be inferred from gradle.properties (minecraft_version, mc_version, or minecraftVersion). Retry with version set explicitly: replace \"<minecraft-version>\" in the suggested call with the project's Minecraft version, or declare it in gradle.properties."
|
|
125
175
|
]
|
|
126
176
|
},
|
|
127
177
|
blocks: {
|
|
@@ -150,14 +200,16 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
150
200
|
]
|
|
151
201
|
};
|
|
152
202
|
}
|
|
203
|
+
// Reached only when discovery found nothing (the branch above handles found files).
|
|
153
204
|
if (!resolvedVersion) {
|
|
205
|
+
const nothingToValidate = describeNothingToValidate(discover, projectPath);
|
|
154
206
|
const baseResult = buildEntryToolResult({
|
|
155
207
|
task: "project-summary",
|
|
156
208
|
detail,
|
|
157
209
|
include,
|
|
158
210
|
summary: {
|
|
159
211
|
status: "ok",
|
|
160
|
-
headline:
|
|
212
|
+
headline: nothingToValidate.headline,
|
|
161
213
|
subject: createSummarySubject({
|
|
162
214
|
task: "project-summary",
|
|
163
215
|
kind: input.subject.kind,
|
|
@@ -187,15 +239,16 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
187
239
|
return {
|
|
188
240
|
...baseResult,
|
|
189
241
|
...(tasks ? { tasks } : {}),
|
|
190
|
-
warnings: []
|
|
242
|
+
warnings: [nothingToValidate.warning]
|
|
191
243
|
};
|
|
192
244
|
}
|
|
193
245
|
const validationVersion = resolvedVersion;
|
|
194
|
-
const warnings = [];
|
|
246
|
+
const warnings = [...versionInferenceWarnings];
|
|
195
247
|
const mixinDurationStart = Date.now();
|
|
196
248
|
let validMixins = 0;
|
|
197
249
|
let partialMixins = 0;
|
|
198
250
|
let invalidMixins = 0;
|
|
251
|
+
let mixinProcessingErrors = 0;
|
|
199
252
|
let mixinCaughtErrors = 0;
|
|
200
253
|
await safeEmit(options.stageEmitter, "validate-project:mixin-validation", {
|
|
201
254
|
targetTotal: mixinConfigs.length
|
|
@@ -244,6 +297,8 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
244
297
|
valid: summary?.valid ?? 0,
|
|
245
298
|
partial: summary?.partial ?? 0,
|
|
246
299
|
invalid: summary?.invalid ?? 0,
|
|
300
|
+
// Mixin sources that failed to load count in none of valid/partial/invalid.
|
|
301
|
+
processingErrors: summary?.processingErrors ?? 0,
|
|
247
302
|
caught: 0,
|
|
248
303
|
warnings: Array.isArray(mixinResult.warnings) ? [...mixinResult.warnings] : []
|
|
249
304
|
};
|
|
@@ -253,6 +308,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
253
308
|
valid: 0,
|
|
254
309
|
partial: 0,
|
|
255
310
|
invalid: 1,
|
|
311
|
+
processingErrors: 0,
|
|
256
312
|
caught: 1,
|
|
257
313
|
warnings: error instanceof Error ? [`${configPath}: ${error.message}`] : []
|
|
258
314
|
};
|
|
@@ -262,6 +318,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
262
318
|
validMixins += result.valid;
|
|
263
319
|
partialMixins += result.partial;
|
|
264
320
|
invalidMixins += result.invalid;
|
|
321
|
+
mixinProcessingErrors += result.processingErrors;
|
|
265
322
|
mixinCaughtErrors += result.caught;
|
|
266
323
|
warnings.push(...result.warnings);
|
|
267
324
|
}
|
|
@@ -270,6 +327,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
270
327
|
let validAw = 0;
|
|
271
328
|
let invalidAw = 0;
|
|
272
329
|
let awCaughtErrors = 0;
|
|
330
|
+
let awProcessingErrors = 0;
|
|
273
331
|
await safeEmit(options.stageEmitter, "validate-project:access-widener-validation", {
|
|
274
332
|
targetTotal: accessWideners.length
|
|
275
333
|
});
|
|
@@ -288,7 +346,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
288
346
|
projectPath,
|
|
289
347
|
gradleUserHome,
|
|
290
348
|
scope: input.scope,
|
|
291
|
-
preferProjectVersion:
|
|
349
|
+
preferProjectVersion: effectivePreferProjectVersion
|
|
292
350
|
});
|
|
293
351
|
if (output.valid) {
|
|
294
352
|
validAw += 1;
|
|
@@ -301,6 +359,12 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
301
359
|
}
|
|
302
360
|
}
|
|
303
361
|
catch (error) {
|
|
362
|
+
if (isAppError(error) && error.details?.unsupportedFormat === "classTweaker") {
|
|
363
|
+
// A format this server does not read is not a verdict on the file.
|
|
364
|
+
awProcessingErrors += 1;
|
|
365
|
+
warnings.push(`${awPath}: ${error.message}`);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
304
368
|
invalidAw += 1;
|
|
305
369
|
awCaughtErrors += 1;
|
|
306
370
|
if (error instanceof Error) {
|
|
@@ -337,7 +401,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
337
401
|
projectPath,
|
|
338
402
|
gradleUserHome,
|
|
339
403
|
scope: input.scope,
|
|
340
|
-
preferProjectVersion:
|
|
404
|
+
preferProjectVersion: effectivePreferProjectVersion
|
|
341
405
|
});
|
|
342
406
|
if (output.valid) {
|
|
343
407
|
validAt += 1;
|
|
@@ -360,14 +424,26 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
360
424
|
const atDurationMs = Date.now() - atDurationStart;
|
|
361
425
|
const invalidCount = invalidMixins + invalidAw + invalidAt;
|
|
362
426
|
const partialCount = partialMixins;
|
|
363
|
-
const
|
|
427
|
+
const processingErrors = mixinProcessingErrors + awProcessingErrors;
|
|
428
|
+
const status = invalidCount > 0
|
|
429
|
+
? "invalid"
|
|
430
|
+
: partialCount > 0 || processingErrors > 0
|
|
431
|
+
? "partial"
|
|
432
|
+
: "ok";
|
|
433
|
+
const nothingToValidate = mixinConfigs.length === 0 && accessWideners.length === 0 && accessTransformers.length === 0
|
|
434
|
+
? describeNothingToValidate(discover, projectPath)
|
|
435
|
+
: undefined;
|
|
436
|
+
if (nothingToValidate) {
|
|
437
|
+
warnings.push(nothingToValidate.warning);
|
|
438
|
+
}
|
|
364
439
|
const baseResult = buildEntryToolResult({
|
|
365
440
|
task: "project-summary",
|
|
366
441
|
detail,
|
|
367
442
|
include,
|
|
368
443
|
summary: {
|
|
369
444
|
status,
|
|
370
|
-
headline:
|
|
445
|
+
headline: nothingToValidate?.headline ??
|
|
446
|
+
`Validated ${mixinConfigs.length} mixin config(s), ${accessWideners.length} access widener(s), and ${accessTransformers.length} access transformer(s)${mixinProcessingErrors > 0 ? `; ${mixinProcessingErrors} mixin input(s) could not be processed` : ""}${awProcessingErrors > 0 ? `; ${awProcessingErrors} access widener(s) could not be processed` : ""}.`,
|
|
371
447
|
subject: createSummarySubject({
|
|
372
448
|
task: "project-summary",
|
|
373
449
|
kind: input.subject.kind,
|
|
@@ -381,7 +457,8 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
381
457
|
counts: {
|
|
382
458
|
valid: validMixins + validAw + validAt,
|
|
383
459
|
partial: partialCount,
|
|
384
|
-
invalid: invalidCount
|
|
460
|
+
invalid: invalidCount,
|
|
461
|
+
processingErrors
|
|
385
462
|
}
|
|
386
463
|
},
|
|
387
464
|
blocks: {
|
|
@@ -389,7 +466,8 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
389
466
|
summary: {
|
|
390
467
|
valid: validMixins + validAw + validAt,
|
|
391
468
|
partial: partialCount,
|
|
392
|
-
invalid: invalidCount
|
|
469
|
+
invalid: invalidCount,
|
|
470
|
+
processingErrors
|
|
393
471
|
}
|
|
394
472
|
},
|
|
395
473
|
workspace: {
|
|
@@ -416,7 +494,7 @@ export async function handleProjectSummary(deps, input, detail, include, options
|
|
|
416
494
|
sourcePriority: input.sourcePriority,
|
|
417
495
|
gradleUserHome,
|
|
418
496
|
scope: input.scope,
|
|
419
|
-
preferProjectVersion:
|
|
497
|
+
preferProjectVersion: effectivePreferProjectVersion,
|
|
420
498
|
mixinDiscoveryCount: mixinConfigs.length,
|
|
421
499
|
mixinCaughtErrors,
|
|
422
500
|
mixinCounts: { ok: validMixins, partial: partialMixins, invalid: invalidMixins },
|