@adhisang/minecraft-modding-mcp 3.1.0 → 3.2.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 (44) hide show
  1. package/CHANGELOG.md +62 -34
  2. package/README.md +79 -100
  3. package/dist/access-transformer-parser.d.ts +17 -0
  4. package/dist/access-transformer-parser.js +97 -0
  5. package/dist/concurrency.d.ts +1 -0
  6. package/dist/concurrency.js +24 -0
  7. package/dist/config.js +19 -11
  8. package/dist/decompiler/vineflower.js +22 -21
  9. package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
  10. package/dist/entry-tools/analyze-symbol-service.d.ts +22 -20
  11. package/dist/entry-tools/analyze-symbol-service.js +6 -3
  12. package/dist/entry-tools/inspect-minecraft-service.d.ts +166 -149
  13. package/dist/entry-tools/inspect-minecraft-service.js +318 -55
  14. package/dist/entry-tools/validate-project-service.d.ts +153 -16
  15. package/dist/entry-tools/validate-project-service.js +360 -23
  16. package/dist/gradle-paths.d.ts +4 -0
  17. package/dist/gradle-paths.js +57 -0
  18. package/dist/index.js +274 -13
  19. package/dist/mapping-pipeline-service.d.ts +3 -1
  20. package/dist/mapping-pipeline-service.js +16 -1
  21. package/dist/mapping-service.d.ts +5 -0
  22. package/dist/mapping-service.js +200 -84
  23. package/dist/minecraft-explorer-service.d.ts +13 -0
  24. package/dist/minecraft-explorer-service.js +8 -4
  25. package/dist/mixin-validator.d.ts +33 -2
  26. package/dist/mixin-validator.js +197 -11
  27. package/dist/mod-analyzer.d.ts +1 -0
  28. package/dist/mod-analyzer.js +17 -1
  29. package/dist/mod-decompile-service.js +4 -4
  30. package/dist/mod-remap-service.js +1 -54
  31. package/dist/mod-search-service.d.ts +1 -0
  32. package/dist/mod-search-service.js +84 -51
  33. package/dist/response-utils.d.ts +35 -0
  34. package/dist/response-utils.js +113 -0
  35. package/dist/source-jar-reader.d.ts +16 -0
  36. package/dist/source-jar-reader.js +103 -1
  37. package/dist/source-resolver.js +9 -10
  38. package/dist/source-service.d.ts +24 -2
  39. package/dist/source-service.js +1052 -139
  40. package/dist/tool-contract-manifest.js +74 -74
  41. package/dist/types.d.ts +17 -0
  42. package/dist/workspace-mapping-service.d.ts +13 -0
  43. package/dist/workspace-mapping-service.js +146 -14
  44. package/package.json +1 -1
@@ -2,12 +2,14 @@ import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import fastGlob from "fast-glob";
4
4
  import { z } from "zod";
5
+ import { mapWithConcurrencyLimit } from "../concurrency.js";
5
6
  import { createError, ERROR_CODES } from "../errors.js";
6
7
  import { buildIncludeSchema, detailSchema } from "./entry-tool-schema.js";
7
8
  import { buildEntryToolResult, createSummarySubject } from "./response-contract.js";
8
9
  import { resolveDetail, resolveInclude } from "./request-normalizers.js";
9
10
  const nonEmptyString = z.string().trim().min(1);
10
11
  const INCLUDE_GROUPS = ["warnings", "issues", "workspace", "recovery"];
12
+ const WORKSPACE_TEXT_FILE_READ_CONCURRENCY = 4;
11
13
  const mixinInputSchema = z.discriminatedUnion("mode", [
12
14
  z.object({ mode: z.literal("inline"), source: nonEmptyString }),
13
15
  z.object({ mode: z.literal("path"), path: nonEmptyString }),
@@ -19,11 +21,15 @@ const accessWidenerInputSchema = z.discriminatedUnion("mode", [
19
21
  z.object({ mode: z.literal("inline"), content: nonEmptyString }),
20
22
  z.object({ mode: z.literal("path"), path: nonEmptyString })
21
23
  ]);
24
+ const accessTransformerInputSchema = z.discriminatedUnion("mode", [
25
+ z.object({ mode: z.literal("inline"), content: nonEmptyString }),
26
+ z.object({ mode: z.literal("path"), path: nonEmptyString })
27
+ ]);
22
28
  const subjectSchema = z.discriminatedUnion("kind", [
23
29
  z.object({
24
30
  kind: z.literal("workspace"),
25
31
  projectPath: nonEmptyString,
26
- discover: z.array(z.enum(["mixins", "access-wideners"])).optional()
32
+ discover: z.array(z.enum(["mixins", "access-wideners", "access-transformers"])).optional()
27
33
  }),
28
34
  z.object({
29
35
  kind: z.literal("mixin"),
@@ -32,13 +38,18 @@ const subjectSchema = z.discriminatedUnion("kind", [
32
38
  z.object({
33
39
  kind: z.literal("access-widener"),
34
40
  input: accessWidenerInputSchema
41
+ }),
42
+ z.object({
43
+ kind: z.literal("access-transformer"),
44
+ input: accessTransformerInputSchema
35
45
  })
36
46
  ]);
37
47
  export const validateProjectShape = {
38
- task: z.enum(["project-summary", "mixin", "access-widener"]),
48
+ task: z.enum(["project-summary", "mixin", "access-widener", "access-transformer"]),
39
49
  subject: subjectSchema,
40
50
  version: nonEmptyString.optional(),
41
51
  mapping: z.enum(["obfuscated", "mojang", "intermediary", "yarn"]).optional(),
52
+ atNamespace: z.enum(["srg", "mojang", "obfuscated"]).optional(),
42
53
  sourcePriority: z.enum(["loom-first", "maven-first"]).optional(),
43
54
  scope: z.enum(["vanilla", "merged", "loader"]).optional(),
44
55
  preferProjectVersion: z.boolean().optional(),
@@ -77,6 +88,13 @@ export const validateProjectSchema = z.object(validateProjectShape).superRefine(
77
88
  message: "task=access-widener requires subject.kind=access-widener."
78
89
  });
79
90
  }
91
+ if (value.task === "access-transformer" && value.subject.kind !== "access-transformer") {
92
+ ctx.addIssue({
93
+ code: z.ZodIssueCode.custom,
94
+ path: ["subject", "kind"],
95
+ message: "task=access-transformer requires subject.kind=access-transformer."
96
+ });
97
+ }
80
98
  if (value.configPaths?.length && value.task !== "project-summary") {
81
99
  ctx.addIssue({
82
100
  code: z.ZodIssueCode.custom,
@@ -89,33 +107,153 @@ export async function discoverWorkspaceMixins(projectPath, configPaths) {
89
107
  if (configPaths?.length) {
90
108
  return [...configPaths];
91
109
  }
92
- return fastGlob.sync(["**/*.mixins.json"], {
110
+ return (await fastGlob.glob(["**/*.mixins.json"], {
93
111
  cwd: projectPath,
94
112
  absolute: true,
95
113
  onlyFiles: true,
96
114
  ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
97
- });
115
+ })).sort((left, right) => left.localeCompare(right));
98
116
  }
99
117
  export async function discoverWorkspaceAccessWideners(projectPath) {
100
- const descriptorFiles = fastGlob.sync(["fabric.mod.json", "quilt.mod.json", "**/fabric.mod.json", "**/quilt.mod.json"], {
118
+ const descriptorFiles = (await fastGlob.glob(["fabric.mod.json", "quilt.mod.json", "**/fabric.mod.json", "**/quilt.mod.json"], {
101
119
  cwd: projectPath,
102
120
  absolute: true,
103
121
  onlyFiles: true,
104
122
  ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
105
- });
123
+ })).sort((left, right) => left.localeCompare(right));
106
124
  const discovered = new Set();
107
- for (const descriptorPath of descriptorFiles) {
125
+ const matches = await mapWithConcurrencyLimit(descriptorFiles, WORKSPACE_TEXT_FILE_READ_CONCURRENCY, async (descriptorPath) => {
108
126
  try {
109
127
  const parsed = JSON.parse(await readFile(descriptorPath, "utf8"));
110
128
  const relative = parsed.accessWidener ?? parsed.access_widener;
111
- if (relative) {
112
- discovered.add(resolve(descriptorPath, "..", relative));
129
+ return relative ? [resolve(descriptorPath, "..", relative)] : [];
130
+ }
131
+ catch {
132
+ return [];
133
+ }
134
+ });
135
+ for (const matchList of matches) {
136
+ for (const match of matchList) {
137
+ discovered.add(match);
138
+ }
139
+ }
140
+ return [...discovered].sort((left, right) => left.localeCompare(right));
141
+ }
142
+ function addDiscoveredPath(discovered, filePath, relativePath) {
143
+ const trimmed = relativePath?.trim();
144
+ if (!trimmed) {
145
+ return;
146
+ }
147
+ discovered.add(resolve(filePath, "..", trimmed));
148
+ }
149
+ function collectFileArgumentMatches(content, filePath, discovered, pattern) {
150
+ for (const match of content.matchAll(pattern)) {
151
+ addDiscoveredPath(discovered, filePath, match[2]);
152
+ }
153
+ }
154
+ function extractNamedDslBlocks(content, blockName) {
155
+ const blocks = [];
156
+ const blockPattern = new RegExp(`${blockName}\\s*\\{`, "g");
157
+ for (const match of content.matchAll(blockPattern)) {
158
+ const blockStart = (match.index ?? -1) + match[0].length;
159
+ if (blockStart < match[0].length) {
160
+ continue;
161
+ }
162
+ let depth = 1;
163
+ for (let index = blockStart; index < content.length; index++) {
164
+ const char = content[index];
165
+ if (char === "{") {
166
+ depth++;
113
167
  }
168
+ else if (char === "}") {
169
+ depth--;
170
+ }
171
+ if (depth === 0) {
172
+ blocks.push(content.slice(blockStart, index));
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ return blocks;
178
+ }
179
+ function collectTomlAccessTransformerEntries(content, filePath, discovered) {
180
+ const lines = content.split(/\r?\n/);
181
+ let currentBlock = [];
182
+ const flushBlock = () => {
183
+ if (currentBlock.length === 0) {
184
+ return;
185
+ }
186
+ for (const match of currentBlock.join("\n").matchAll(/^\s*file\s*=\s*(["'])(.+?)\1\s*$/gm)) {
187
+ addDiscoveredPath(discovered, filePath, match[2]);
188
+ }
189
+ currentBlock = [];
190
+ };
191
+ for (const line of lines) {
192
+ if (/^\s*\[\[accessTransformers\]\]\s*$/.test(line)) {
193
+ flushBlock();
194
+ currentBlock.push(line);
195
+ continue;
196
+ }
197
+ if (currentBlock.length > 0 && /^\s*(?:\[\[.*\]\]|\[[^\[])/.test(line)) {
198
+ flushBlock();
199
+ }
200
+ if (currentBlock.length > 0) {
201
+ currentBlock.push(line);
202
+ }
203
+ }
204
+ flushBlock();
205
+ }
206
+ export async function discoverWorkspaceAccessTransformers(projectPath) {
207
+ const discovered = new Set();
208
+ const textFiles = (await fastGlob.glob([
209
+ "build.gradle",
210
+ "build.gradle.kts",
211
+ "META-INF/mods.toml",
212
+ "META-INF/neoforge.mods.toml",
213
+ "**/build.gradle",
214
+ "**/build.gradle.kts",
215
+ "**/META-INF/mods.toml",
216
+ "**/META-INF/neoforge.mods.toml"
217
+ ], {
218
+ cwd: projectPath,
219
+ absolute: true,
220
+ onlyFiles: true,
221
+ ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
222
+ })).sort((left, right) => left.localeCompare(right));
223
+ const discoveredByFile = await mapWithConcurrencyLimit(textFiles, WORKSPACE_TEXT_FILE_READ_CONCURRENCY, async (filePath) => {
224
+ let content;
225
+ try {
226
+ content = await readFile(filePath, "utf8");
114
227
  }
115
228
  catch {
116
- // ignore malformed descriptors in discovery mode
229
+ return [];
230
+ }
231
+ const perFileDiscovered = new Set();
232
+ collectFileArgumentMatches(content, filePath, perFileDiscovered, /accessTransformer\s*=\s*file\(\s*(["'])(.+?)\1\s*\)/g);
233
+ collectFileArgumentMatches(content, filePath, perFileDiscovered, /accessTransformers\.from\s*\(\s*file\(\s*(["'])(.+?)\1\s*\)\s*\)/g);
234
+ for (const block of extractNamedDslBlocks(content, "accessTransformers")) {
235
+ collectFileArgumentMatches(block, filePath, perFileDiscovered, /file\(\s*(["'])(.+?)\1\s*\)/g);
236
+ }
237
+ collectTomlAccessTransformerEntries(content, filePath, perFileDiscovered);
238
+ return [...perFileDiscovered];
239
+ });
240
+ for (const matchList of discoveredByFile) {
241
+ for (const match of matchList) {
242
+ discovered.add(match);
117
243
  }
118
244
  }
245
+ for (const fallbackPath of (await fastGlob.glob([
246
+ "**/META-INF/accesstransformer.cfg",
247
+ "**/*_at.cfg",
248
+ "**/accesstransformer*.cfg"
249
+ ], {
250
+ cwd: projectPath,
251
+ absolute: true,
252
+ onlyFiles: true,
253
+ ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
254
+ })).sort((left, right) => left.localeCompare(right))) {
255
+ discovered.add(fallbackPath);
256
+ }
119
257
  return [...discovered].sort((left, right) => left.localeCompare(right));
120
258
  }
121
259
  export class ValidateProjectService {
@@ -202,7 +340,9 @@ export class ValidateProjectService {
202
340
  content,
203
341
  version: input.version,
204
342
  mapping: input.mapping,
205
- sourcePriority: input.sourcePriority
343
+ sourcePriority: input.sourcePriority,
344
+ scope: input.scope,
345
+ preferProjectVersion: input.preferProjectVersion
206
346
  });
207
347
  return {
208
348
  ...buildEntryToolResult({
@@ -242,6 +382,77 @@ export class ValidateProjectService {
242
382
  warnings: Array.isArray(output.warnings) ? output.warnings : []
243
383
  };
244
384
  }
385
+ case "access-transformer": {
386
+ if (input.subject.kind !== "access-transformer") {
387
+ throw createError({
388
+ code: ERROR_CODES.INVALID_INPUT,
389
+ message: "task=access-transformer requires subject.kind=access-transformer."
390
+ });
391
+ }
392
+ const content = input.subject.input.mode === "inline"
393
+ ? input.subject.input.content
394
+ : await readFile(input.subject.input.path, "utf8");
395
+ if (!this.deps.validateAccessTransformer) {
396
+ throw createError({
397
+ code: ERROR_CODES.CONTEXT_UNRESOLVED,
398
+ message: "Access Transformer validation is not configured."
399
+ });
400
+ }
401
+ const output = await this.deps.validateAccessTransformer({
402
+ content,
403
+ version: input.version,
404
+ atNamespace: input.atNamespace,
405
+ sourcePriority: input.sourcePriority,
406
+ scope: input.scope,
407
+ preferProjectVersion: input.preferProjectVersion
408
+ });
409
+ const issueEntries = Array.isArray(output.entries)
410
+ ? output.entries.filter((entry) => {
411
+ if (!entry || typeof entry !== "object" || !("valid" in entry)) {
412
+ return true;
413
+ }
414
+ return entry.valid !== true;
415
+ })
416
+ : undefined;
417
+ return {
418
+ ...buildEntryToolResult({
419
+ task: "access-transformer",
420
+ detail,
421
+ include,
422
+ summary: {
423
+ status: output.valid ? "ok" : "invalid",
424
+ headline: output.valid
425
+ ? "Access Transformer is valid."
426
+ : "Access Transformer contains validation issues.",
427
+ subject: createSummarySubject({
428
+ task: "access-transformer",
429
+ kind: input.subject.kind,
430
+ input: input.subject.input,
431
+ version: input.version,
432
+ sourcePriority: input.sourcePriority,
433
+ scope: input.scope,
434
+ atNamespace: input.atNamespace
435
+ }),
436
+ counts: {
437
+ valid: output.valid ? 1 : 0,
438
+ invalid: output.valid ? 0 : 1
439
+ }
440
+ },
441
+ blocks: {
442
+ project: {
443
+ summary: {
444
+ total: 1,
445
+ valid: output.valid ? 1 : 0,
446
+ invalid: output.valid ? 0 : 1
447
+ }
448
+ },
449
+ issues: include.includes("issues") || detail !== "summary" ? issueEntries : undefined
450
+ },
451
+ alwaysBlocks: ["project"]
452
+ }),
453
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
454
+ };
455
+ }
245
456
  case "project-summary": {
246
457
  if (input.subject.kind !== "workspace") {
247
458
  throw createError({
@@ -269,10 +480,12 @@ export class ValidateProjectService {
269
480
  tool: "validate-project",
270
481
  params: {
271
482
  task: "project-summary",
272
- version: "1.21.10",
273
483
  subject: input.subject
274
484
  }
275
485
  }
486
+ ],
487
+ notes: [
488
+ "Pass version explicitly, or retry with preferProjectVersion=true when gradle.properties declares the Minecraft version."
276
489
  ]
277
490
  },
278
491
  blocks: {
@@ -285,15 +498,98 @@ export class ValidateProjectService {
285
498
  };
286
499
  }
287
500
  const projectPath = input.subject.projectPath;
501
+ const detectedProjectVersion = input.preferProjectVersion
502
+ ? await this.deps.detectProjectMinecraftVersion?.(projectPath)
503
+ : undefined;
504
+ const resolvedVersion = detectedProjectVersion ?? input.version;
288
505
  const discover = input.subject.discover ?? ["mixins", "access-wideners"];
289
- const [mixinConfigs, accessWideners] = await Promise.all([
506
+ const [mixinConfigs, accessWideners, accessTransformers] = await Promise.all([
290
507
  discover.includes("mixins")
291
508
  ? this.deps.discoverMixins(projectPath, input.configPaths)
292
509
  : Promise.resolve([]),
293
510
  discover.includes("access-wideners")
294
511
  ? this.deps.discoverAccessWideners(projectPath)
512
+ : Promise.resolve([]),
513
+ discover.includes("access-transformers")
514
+ ? this.deps.discoverAccessTransformers?.(projectPath) ?? Promise.resolve([])
295
515
  : Promise.resolve([])
296
516
  ]);
517
+ if (!resolvedVersion && (mixinConfigs.length > 0 || accessWideners.length > 0 || accessTransformers.length > 0)) {
518
+ return {
519
+ ...buildEntryToolResult({
520
+ task: "project-summary",
521
+ detail,
522
+ include,
523
+ summary: {
524
+ status: "blocked",
525
+ headline: "Could not resolve Minecraft version for discovered workspace validators.",
526
+ subject: createSummarySubject({
527
+ task: "project-summary",
528
+ kind: input.subject.kind,
529
+ projectPath,
530
+ discover: input.subject.discover,
531
+ mapping: input.mapping,
532
+ sourcePriority: input.sourcePriority,
533
+ scope: input.scope
534
+ }),
535
+ nextActions: [
536
+ {
537
+ tool: "validate-project",
538
+ params: {
539
+ task: "project-summary",
540
+ subject: input.subject
541
+ }
542
+ }
543
+ ],
544
+ notes: [
545
+ "Pass version explicitly, or make sure gradle.properties declares the Minecraft version before using preferProjectVersion=true."
546
+ ]
547
+ },
548
+ blocks: {
549
+ workspace: {
550
+ projectPath
551
+ }
552
+ }
553
+ }),
554
+ warnings: [
555
+ "Could not resolve Minecraft version from gradle.properties for discovered workspace validators."
556
+ ]
557
+ };
558
+ }
559
+ if (!resolvedVersion) {
560
+ return {
561
+ ...buildEntryToolResult({
562
+ task: "project-summary",
563
+ detail,
564
+ include,
565
+ summary: {
566
+ status: "ok",
567
+ headline: `Validated ${mixinConfigs.length} mixin config(s), ${accessWideners.length} access widener(s), and ${accessTransformers.length} access transformer(s).`,
568
+ subject: createSummarySubject({
569
+ task: "project-summary",
570
+ kind: input.subject.kind,
571
+ projectPath,
572
+ discover: input.subject.discover,
573
+ mapping: input.mapping,
574
+ sourcePriority: input.sourcePriority,
575
+ scope: input.scope
576
+ }),
577
+ counts: {
578
+ valid: 0,
579
+ partial: 0,
580
+ invalid: 0
581
+ }
582
+ },
583
+ blocks: {
584
+ workspace: {
585
+ projectPath
586
+ }
587
+ }
588
+ }),
589
+ warnings: []
590
+ };
591
+ }
592
+ const validationVersion = resolvedVersion;
297
593
  const warnings = [];
298
594
  let validMixins = 0;
299
595
  let partialMixins = 0;
@@ -305,11 +601,12 @@ export class ValidateProjectService {
305
601
  mode: "config",
306
602
  configPaths: [configPath]
307
603
  },
308
- version: input.version,
604
+ version: validationVersion,
309
605
  mapping: input.mapping,
310
606
  sourcePriority: input.sourcePriority,
311
607
  scope: input.scope,
312
- preferProjectVersion: input.preferProjectVersion,
608
+ projectPath,
609
+ preferProjectVersion: false,
313
610
  preferProjectMapping: input.preferProjectMapping,
314
611
  sourceRoots: input.sourceRoots,
315
612
  minSeverity: input.minSeverity,
@@ -341,9 +638,12 @@ export class ValidateProjectService {
341
638
  try {
342
639
  const output = await this.deps.validateAccessWidener({
343
640
  content: await readFile(awPath, "utf8"),
344
- version: input.version,
641
+ version: validationVersion,
345
642
  mapping: input.mapping,
346
- sourcePriority: input.sourcePriority
643
+ sourcePriority: input.sourcePriority,
644
+ projectPath,
645
+ scope: input.scope,
646
+ preferProjectVersion: input.preferProjectVersion
347
647
  });
348
648
  if (output.valid) {
349
649
  validAw += 1;
@@ -362,7 +662,43 @@ export class ValidateProjectService {
362
662
  }
363
663
  }
364
664
  }
365
- const invalidCount = invalidMixins + invalidAw;
665
+ let validAt = 0;
666
+ let invalidAt = 0;
667
+ for (const atPath of accessTransformers) {
668
+ try {
669
+ if (!this.deps.validateAccessTransformer) {
670
+ throw createError({
671
+ code: ERROR_CODES.CONTEXT_UNRESOLVED,
672
+ message: "Access Transformer validation is not configured."
673
+ });
674
+ }
675
+ const output = await this.deps.validateAccessTransformer({
676
+ content: await readFile(atPath, "utf8"),
677
+ version: validationVersion,
678
+ atNamespace: input.atNamespace,
679
+ sourcePriority: input.sourcePriority,
680
+ projectPath,
681
+ scope: input.scope,
682
+ preferProjectVersion: input.preferProjectVersion
683
+ });
684
+ if (output.valid) {
685
+ validAt += 1;
686
+ }
687
+ else {
688
+ invalidAt += 1;
689
+ }
690
+ if (Array.isArray(output.warnings)) {
691
+ warnings.push(...output.warnings);
692
+ }
693
+ }
694
+ catch (error) {
695
+ invalidAt += 1;
696
+ if (error instanceof Error) {
697
+ warnings.push(error.message);
698
+ }
699
+ }
700
+ }
701
+ const invalidCount = invalidMixins + invalidAw + invalidAt;
366
702
  const partialCount = partialMixins;
367
703
  const status = invalidCount > 0 ? "invalid" : partialCount > 0 ? "partial" : "ok";
368
704
  return {
@@ -372,19 +708,19 @@ export class ValidateProjectService {
372
708
  include,
373
709
  summary: {
374
710
  status,
375
- headline: `Validated ${mixinConfigs.length} mixin config(s) and ${accessWideners.length} access widener(s).`,
711
+ headline: `Validated ${mixinConfigs.length} mixin config(s), ${accessWideners.length} access widener(s), and ${accessTransformers.length} access transformer(s).`,
376
712
  subject: createSummarySubject({
377
713
  task: "project-summary",
378
714
  kind: input.subject.kind,
379
715
  projectPath,
380
716
  discover: input.subject.discover,
381
- version: input.version,
717
+ version: resolvedVersion,
382
718
  mapping: input.mapping,
383
719
  sourcePriority: input.sourcePriority,
384
720
  scope: input.scope
385
721
  }),
386
722
  counts: {
387
- valid: validMixins + validAw,
723
+ valid: validMixins + validAw + validAt,
388
724
  partial: partialCount,
389
725
  invalid: invalidCount
390
726
  }
@@ -392,7 +728,7 @@ export class ValidateProjectService {
392
728
  blocks: {
393
729
  project: {
394
730
  summary: {
395
- valid: validMixins + validAw,
731
+ valid: validMixins + validAw + validAt,
396
732
  partial: partialCount,
397
733
  invalid: invalidCount
398
734
  }
@@ -400,7 +736,8 @@ export class ValidateProjectService {
400
736
  workspace: {
401
737
  projectPath,
402
738
  mixinConfigs,
403
- accessWideners
739
+ accessWideners,
740
+ accessTransformers
404
741
  }
405
742
  },
406
743
  alwaysBlocks: ["project"]
@@ -0,0 +1,4 @@
1
+ export declare function normalizeOptionalProjectPath(projectPath: string | undefined): string | undefined;
2
+ export declare function resolveGradleUserHomePath(): string;
3
+ export declare function buildVersionSourceSearchRoots(projectPath: string | undefined): string[];
4
+ export declare function buildLoaderRuntimeSearchRoots(projectPath: string | undefined): string[];
@@ -0,0 +1,57 @@
1
+ import { homedir } from "node:os";
2
+ import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
3
+ import { normalizePathForHost } from "./path-converter.js";
4
+ export function normalizeOptionalProjectPath(projectPath) {
5
+ if (!projectPath) {
6
+ return undefined;
7
+ }
8
+ const trimmed = projectPath.trim();
9
+ if (!trimmed) {
10
+ return undefined;
11
+ }
12
+ const normalized = normalizePathForHost(trimmed, undefined, "projectPath");
13
+ return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
14
+ }
15
+ export function resolveGradleUserHomePath() {
16
+ const configured = process.env.GRADLE_USER_HOME?.trim();
17
+ if (!configured) {
18
+ return resolvePath(homedir(), ".gradle");
19
+ }
20
+ const normalized = normalizePathForHost(configured, undefined, "GRADLE_USER_HOME");
21
+ return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
22
+ }
23
+ export function buildVersionSourceSearchRoots(projectPath) {
24
+ const roots = new Set();
25
+ if (projectPath) {
26
+ roots.add(resolvePath(projectPath, ".gradle", "loom-cache"));
27
+ roots.add(resolvePath(projectPath, ".gradle-user", "caches", "fabric-loom"));
28
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "fabric-loom"));
29
+ const projectParent = dirname(projectPath);
30
+ roots.add(resolvePath(projectParent, ".gradle-user-home", "loom-cache"));
31
+ roots.add(resolvePath(projectParent, ".gradle-user-home", "caches", "fabric-loom"));
32
+ }
33
+ const homeGradle = resolveGradleUserHomePath();
34
+ roots.add(resolvePath(homeGradle, "loom-cache"));
35
+ roots.add(resolvePath(homeGradle, "caches", "fabric-loom"));
36
+ return [...roots];
37
+ }
38
+ export function buildLoaderRuntimeSearchRoots(projectPath) {
39
+ const roots = new Set();
40
+ if (projectPath) {
41
+ roots.add(resolvePath(projectPath, "build"));
42
+ roots.add(resolvePath(projectPath, ".gradle"));
43
+ roots.add(resolvePath(projectPath, ".gradle", "forge-userdev"));
44
+ roots.add(resolvePath(projectPath, ".gradle", "neogradle"));
45
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "forge_gradle"));
46
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "neogradle"));
47
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "neoformruntime"));
48
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "moddev"));
49
+ }
50
+ const homeGradle = resolveGradleUserHomePath();
51
+ roots.add(resolvePath(homeGradle, "caches", "forge_gradle"));
52
+ roots.add(resolvePath(homeGradle, "caches", "neogradle"));
53
+ roots.add(resolvePath(homeGradle, "caches", "neoformruntime"));
54
+ roots.add(resolvePath(homeGradle, "caches", "moddev"));
55
+ return [...roots];
56
+ }
57
+ //# sourceMappingURL=gradle-paths.js.map