@adhisang/minecraft-modding-mcp 2.0.0 → 3.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +139 -30
  3. package/dist/cache-registry.d.ts +95 -0
  4. package/dist/cache-registry.js +541 -0
  5. package/dist/cli.js +31 -4
  6. package/dist/compat-stdio-transport.d.ts +2 -7
  7. package/dist/compat-stdio-transport.js +12 -154
  8. package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
  9. package/dist/entry-tools/analyze-mod-service.js +253 -0
  10. package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
  11. package/dist/entry-tools/analyze-symbol-service.js +304 -0
  12. package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
  13. package/dist/entry-tools/compare-minecraft-service.js +397 -0
  14. package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
  15. package/dist/entry-tools/entry-tool-schema.js +10 -0
  16. package/dist/entry-tools/inspect-minecraft-service.d.ts +1953 -0
  17. package/dist/entry-tools/inspect-minecraft-service.js +876 -0
  18. package/dist/entry-tools/manage-cache-service.d.ts +130 -0
  19. package/dist/entry-tools/manage-cache-service.js +229 -0
  20. package/dist/entry-tools/request-normalizers.d.ts +10 -0
  21. package/dist/entry-tools/request-normalizers.js +36 -0
  22. package/dist/entry-tools/response-contract.d.ts +44 -0
  23. package/dist/entry-tools/response-contract.js +96 -0
  24. package/dist/entry-tools/validate-project-service.d.ts +543 -0
  25. package/dist/entry-tools/validate-project-service.js +381 -0
  26. package/dist/index.js +495 -42
  27. package/dist/json-rpc-framing.d.ts +22 -0
  28. package/dist/json-rpc-framing.js +168 -0
  29. package/dist/mapping-pipeline-service.js +9 -1
  30. package/dist/mapping-service.d.ts +9 -0
  31. package/dist/mapping-service.js +183 -60
  32. package/dist/minecraft-explorer-service.d.ts +0 -1
  33. package/dist/minecraft-explorer-service.js +119 -23
  34. package/dist/mixin-validator.d.ts +24 -2
  35. package/dist/mixin-validator.js +223 -98
  36. package/dist/mod-decompile-service.d.ts +5 -0
  37. package/dist/mod-decompile-service.js +40 -5
  38. package/dist/mod-remap-service.js +142 -30
  39. package/dist/path-resolver.js +41 -4
  40. package/dist/registry-service.d.ts +10 -1
  41. package/dist/registry-service.js +154 -22
  42. package/dist/search-hit-accumulator.js +23 -2
  43. package/dist/source-jar-reader.js +16 -2
  44. package/dist/source-resolver.js +6 -7
  45. package/dist/source-service.d.ts +42 -4
  46. package/dist/source-service.js +781 -127
  47. package/dist/stdio-supervisor.d.ts +46 -0
  48. package/dist/stdio-supervisor.js +349 -0
  49. package/dist/storage/files-repo.d.ts +3 -9
  50. package/dist/storage/files-repo.js +66 -43
  51. package/dist/symbols/symbol-extractor.js +6 -4
  52. package/dist/tool-execution-gate.d.ts +15 -0
  53. package/dist/tool-execution-gate.js +58 -0
  54. package/dist/version-diff-service.js +10 -5
  55. package/dist/version-service.js +7 -2
  56. package/dist/workspace-mapping-service.js +12 -0
  57. package/package.json +1 -1
@@ -0,0 +1,381 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import fastGlob from "fast-glob";
4
+ import { z } from "zod";
5
+ import { createError, ERROR_CODES } from "../errors.js";
6
+ import { buildIncludeSchema, detailSchema } from "./entry-tool-schema.js";
7
+ import { buildEntryToolResult } from "./response-contract.js";
8
+ import { resolveDetail, resolveInclude } from "./request-normalizers.js";
9
+ const nonEmptyString = z.string().trim().min(1);
10
+ const INCLUDE_GROUPS = ["warnings", "issues", "workspace", "recovery"];
11
+ const mixinInputSchema = z.discriminatedUnion("mode", [
12
+ z.object({ mode: z.literal("inline"), source: nonEmptyString }),
13
+ z.object({ mode: z.literal("path"), path: nonEmptyString }),
14
+ z.object({ mode: z.literal("paths"), paths: z.array(nonEmptyString).min(1) }),
15
+ z.object({ mode: z.literal("config"), configPaths: z.array(nonEmptyString).min(1) }),
16
+ z.object({ mode: z.literal("project"), path: nonEmptyString })
17
+ ]);
18
+ const accessWidenerInputSchema = z.discriminatedUnion("mode", [
19
+ z.object({ mode: z.literal("inline"), content: nonEmptyString }),
20
+ z.object({ mode: z.literal("path"), path: nonEmptyString })
21
+ ]);
22
+ const subjectSchema = z.discriminatedUnion("kind", [
23
+ z.object({
24
+ kind: z.literal("workspace"),
25
+ projectPath: nonEmptyString,
26
+ discover: z.array(z.enum(["mixins", "access-wideners"])).optional()
27
+ }),
28
+ z.object({
29
+ kind: z.literal("mixin"),
30
+ input: mixinInputSchema
31
+ }),
32
+ z.object({
33
+ kind: z.literal("access-widener"),
34
+ input: accessWidenerInputSchema
35
+ })
36
+ ]);
37
+ export const validateProjectShape = {
38
+ task: z.enum(["project-summary", "mixin", "access-widener"]),
39
+ subject: subjectSchema,
40
+ version: nonEmptyString.optional(),
41
+ mapping: z.enum(["obfuscated", "mojang", "intermediary", "yarn"]).optional(),
42
+ sourcePriority: z.enum(["loom-first", "maven-first"]).optional(),
43
+ scope: z.enum(["vanilla", "merged", "loader"]).optional(),
44
+ preferProjectVersion: z.boolean().optional(),
45
+ preferProjectMapping: z.boolean().optional(),
46
+ detail: detailSchema.optional(),
47
+ include: buildIncludeSchema(INCLUDE_GROUPS),
48
+ sourceRoots: z.array(nonEmptyString).optional(),
49
+ configPaths: z.array(nonEmptyString).optional(),
50
+ minSeverity: z.enum(["error", "warning", "all"]).optional(),
51
+ hideUncertain: z.boolean().optional(),
52
+ explain: z.boolean().optional(),
53
+ warningMode: z.enum(["full", "aggregated"]).optional(),
54
+ warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional(),
55
+ treatInfoAsWarning: z.boolean().optional(),
56
+ includeIssues: z.boolean().optional()
57
+ };
58
+ export const validateProjectSchema = z.object(validateProjectShape).superRefine((value, ctx) => {
59
+ if (value.task === "project-summary" && value.subject.kind !== "workspace") {
60
+ ctx.addIssue({
61
+ code: z.ZodIssueCode.custom,
62
+ path: ["subject", "kind"],
63
+ message: "task=project-summary requires subject.kind=workspace."
64
+ });
65
+ }
66
+ if (value.task === "mixin" && value.subject.kind !== "mixin") {
67
+ ctx.addIssue({
68
+ code: z.ZodIssueCode.custom,
69
+ path: ["subject", "kind"],
70
+ message: "task=mixin requires subject.kind=mixin."
71
+ });
72
+ }
73
+ if (value.task === "access-widener" && value.subject.kind !== "access-widener") {
74
+ ctx.addIssue({
75
+ code: z.ZodIssueCode.custom,
76
+ path: ["subject", "kind"],
77
+ message: "task=access-widener requires subject.kind=access-widener."
78
+ });
79
+ }
80
+ if (value.configPaths?.length && value.task !== "project-summary") {
81
+ ctx.addIssue({
82
+ code: z.ZodIssueCode.custom,
83
+ path: ["configPaths"],
84
+ message: "configPaths is only supported for task=project-summary workspace discovery."
85
+ });
86
+ }
87
+ });
88
+ export async function discoverWorkspaceMixins(projectPath, configPaths) {
89
+ if (configPaths?.length) {
90
+ return [...configPaths];
91
+ }
92
+ return fastGlob.sync(["**/*.mixins.json"], {
93
+ cwd: projectPath,
94
+ absolute: true,
95
+ onlyFiles: true,
96
+ ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
97
+ });
98
+ }
99
+ export async function discoverWorkspaceAccessWideners(projectPath) {
100
+ const descriptorFiles = fastGlob.sync(["fabric.mod.json", "quilt.mod.json", "**/fabric.mod.json", "**/quilt.mod.json"], {
101
+ cwd: projectPath,
102
+ absolute: true,
103
+ onlyFiles: true,
104
+ ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
105
+ });
106
+ const discovered = new Set();
107
+ for (const descriptorPath of descriptorFiles) {
108
+ try {
109
+ const parsed = JSON.parse(await readFile(descriptorPath, "utf8"));
110
+ const relative = parsed.accessWidener ?? parsed.access_widener;
111
+ if (relative) {
112
+ discovered.add(resolve(descriptorPath, "..", relative));
113
+ }
114
+ }
115
+ catch {
116
+ // ignore malformed descriptors in discovery mode
117
+ }
118
+ }
119
+ return [...discovered].sort((left, right) => left.localeCompare(right));
120
+ }
121
+ export class ValidateProjectService {
122
+ deps;
123
+ constructor(deps) {
124
+ this.deps = deps;
125
+ }
126
+ async execute(input) {
127
+ const detail = resolveDetail(input.detail);
128
+ const include = resolveInclude(input.include);
129
+ switch (input.task) {
130
+ case "mixin": {
131
+ if (input.subject.kind !== "mixin") {
132
+ throw createError({
133
+ code: ERROR_CODES.INVALID_INPUT,
134
+ message: "task=mixin requires subject.kind=mixin."
135
+ });
136
+ }
137
+ const output = await this.deps.validateMixin({
138
+ input: input.subject.input,
139
+ version: input.version,
140
+ mapping: input.mapping,
141
+ sourcePriority: input.sourcePriority,
142
+ scope: input.scope,
143
+ preferProjectVersion: input.preferProjectVersion,
144
+ preferProjectMapping: input.preferProjectMapping,
145
+ sourceRoots: input.sourceRoots,
146
+ minSeverity: input.minSeverity,
147
+ hideUncertain: input.hideUncertain,
148
+ explain: input.explain,
149
+ warningMode: input.warningMode,
150
+ warningCategoryFilter: input.warningCategoryFilter,
151
+ treatInfoAsWarning: input.treatInfoAsWarning,
152
+ includeIssues: input.includeIssues
153
+ });
154
+ const summary = output.summary;
155
+ const invalidCount = summary?.invalid ?? 0;
156
+ const partialCount = summary?.partial ?? 0;
157
+ return {
158
+ ...buildEntryToolResult({
159
+ task: "mixin",
160
+ detail,
161
+ include,
162
+ summary: {
163
+ status: invalidCount > 0 ? "invalid" : partialCount > 0 ? "partial" : "ok",
164
+ headline: `Validated ${summary?.total ?? 0} mixin input(s).`,
165
+ counts: {
166
+ valid: summary?.valid ?? 0,
167
+ partial: partialCount,
168
+ invalid: invalidCount
169
+ }
170
+ },
171
+ blocks: {
172
+ project: {
173
+ summary
174
+ },
175
+ issues: include.includes("issues") || detail !== "summary" ? output.results : undefined
176
+ },
177
+ alwaysBlocks: ["project"]
178
+ }),
179
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
180
+ };
181
+ }
182
+ case "access-widener": {
183
+ if (input.subject.kind !== "access-widener") {
184
+ throw createError({
185
+ code: ERROR_CODES.INVALID_INPUT,
186
+ message: "task=access-widener requires subject.kind=access-widener."
187
+ });
188
+ }
189
+ const content = input.subject.input.mode === "inline"
190
+ ? input.subject.input.content
191
+ : await readFile(input.subject.input.path, "utf8");
192
+ const output = await this.deps.validateAccessWidener({
193
+ content,
194
+ version: input.version,
195
+ mapping: input.mapping,
196
+ sourcePriority: input.sourcePriority
197
+ });
198
+ return {
199
+ ...buildEntryToolResult({
200
+ task: "access-widener",
201
+ detail,
202
+ include,
203
+ summary: {
204
+ status: output.valid ? "ok" : "invalid",
205
+ headline: output.valid
206
+ ? "Access Widener is valid."
207
+ : "Access Widener contains validation issues.",
208
+ counts: {
209
+ valid: output.valid ? 1 : 0,
210
+ invalid: output.valid ? 0 : 1
211
+ }
212
+ },
213
+ blocks: {
214
+ project: {
215
+ summary: {
216
+ total: 1,
217
+ valid: output.valid ? 1 : 0,
218
+ invalid: output.valid ? 0 : 1
219
+ }
220
+ },
221
+ issues: include.includes("issues") || detail !== "summary" ? output.issues : undefined
222
+ },
223
+ alwaysBlocks: ["project"]
224
+ }),
225
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
226
+ };
227
+ }
228
+ case "project-summary": {
229
+ if (input.subject.kind !== "workspace") {
230
+ throw createError({
231
+ code: ERROR_CODES.INVALID_INPUT,
232
+ message: "task=project-summary requires subject.kind=workspace."
233
+ });
234
+ }
235
+ if (!input.version && !input.preferProjectVersion) {
236
+ return {
237
+ ...buildEntryToolResult({
238
+ task: "project-summary",
239
+ detail,
240
+ include,
241
+ summary: {
242
+ status: "blocked",
243
+ headline: "project-summary requires version or preferProjectVersion=true.",
244
+ nextActions: [
245
+ {
246
+ tool: "validate-project",
247
+ params: {
248
+ task: "project-summary",
249
+ version: "1.21.10",
250
+ subject: input.subject
251
+ }
252
+ }
253
+ ]
254
+ },
255
+ blocks: {
256
+ workspace: {
257
+ projectPath: input.subject.projectPath
258
+ }
259
+ }
260
+ }),
261
+ warnings: []
262
+ };
263
+ }
264
+ const projectPath = input.subject.projectPath;
265
+ const discover = input.subject.discover ?? ["mixins", "access-wideners"];
266
+ const [mixinConfigs, accessWideners] = await Promise.all([
267
+ discover.includes("mixins")
268
+ ? this.deps.discoverMixins(projectPath, input.configPaths)
269
+ : Promise.resolve([]),
270
+ discover.includes("access-wideners")
271
+ ? this.deps.discoverAccessWideners(projectPath)
272
+ : Promise.resolve([])
273
+ ]);
274
+ const warnings = [];
275
+ let validMixins = 0;
276
+ let partialMixins = 0;
277
+ let invalidMixins = 0;
278
+ for (const configPath of mixinConfigs) {
279
+ try {
280
+ const mixinResult = await this.deps.validateMixin({
281
+ input: {
282
+ mode: "config",
283
+ configPaths: [configPath]
284
+ },
285
+ version: input.version,
286
+ mapping: input.mapping,
287
+ sourcePriority: input.sourcePriority,
288
+ scope: input.scope,
289
+ preferProjectVersion: input.preferProjectVersion,
290
+ preferProjectMapping: input.preferProjectMapping,
291
+ sourceRoots: input.sourceRoots,
292
+ minSeverity: input.minSeverity,
293
+ hideUncertain: input.hideUncertain,
294
+ explain: input.explain,
295
+ warningMode: input.warningMode,
296
+ warningCategoryFilter: input.warningCategoryFilter,
297
+ treatInfoAsWarning: input.treatInfoAsWarning,
298
+ includeIssues: input.includeIssues
299
+ });
300
+ const summary = mixinResult.summary;
301
+ validMixins += summary?.valid ?? 0;
302
+ partialMixins += summary?.partial ?? 0;
303
+ invalidMixins += summary?.invalid ?? 0;
304
+ if (Array.isArray(mixinResult.warnings)) {
305
+ warnings.push(...mixinResult.warnings);
306
+ }
307
+ }
308
+ catch (error) {
309
+ invalidMixins += 1;
310
+ if (error instanceof Error) {
311
+ warnings.push(`${configPath}: ${error.message}`);
312
+ }
313
+ }
314
+ }
315
+ let validAw = 0;
316
+ let invalidAw = 0;
317
+ for (const awPath of accessWideners) {
318
+ try {
319
+ const output = await this.deps.validateAccessWidener({
320
+ content: await readFile(awPath, "utf8"),
321
+ version: input.version,
322
+ mapping: input.mapping,
323
+ sourcePriority: input.sourcePriority
324
+ });
325
+ if (output.valid) {
326
+ validAw += 1;
327
+ }
328
+ else {
329
+ invalidAw += 1;
330
+ }
331
+ if (Array.isArray(output.warnings)) {
332
+ warnings.push(...output.warnings);
333
+ }
334
+ }
335
+ catch (error) {
336
+ invalidAw += 1;
337
+ if (error instanceof Error) {
338
+ warnings.push(error.message);
339
+ }
340
+ }
341
+ }
342
+ const invalidCount = invalidMixins + invalidAw;
343
+ const partialCount = partialMixins;
344
+ const status = invalidCount > 0 ? "invalid" : partialCount > 0 ? "partial" : "ok";
345
+ return {
346
+ ...buildEntryToolResult({
347
+ task: "project-summary",
348
+ detail,
349
+ include,
350
+ summary: {
351
+ status,
352
+ headline: `Validated ${mixinConfigs.length} mixin config(s) and ${accessWideners.length} access widener(s).`,
353
+ counts: {
354
+ valid: validMixins + validAw,
355
+ partial: partialCount,
356
+ invalid: invalidCount
357
+ }
358
+ },
359
+ blocks: {
360
+ project: {
361
+ summary: {
362
+ valid: validMixins + validAw,
363
+ partial: partialCount,
364
+ invalid: invalidCount
365
+ }
366
+ },
367
+ workspace: {
368
+ projectPath,
369
+ mixinConfigs,
370
+ accessWideners
371
+ }
372
+ },
373
+ alwaysBlocks: ["project"]
374
+ }),
375
+ warnings
376
+ };
377
+ }
378
+ }
379
+ }
380
+ }
381
+ //# sourceMappingURL=validate-project-service.js.map