@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.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 (174) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +36 -23
  3. package/dist/build-suggested-call.d.ts +29 -0
  4. package/dist/build-suggested-call.js +58 -0
  5. package/dist/cache-registry.d.ts +3 -1
  6. package/dist/cache-registry.js +50 -6
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
  8. package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
  9. package/dist/entry-tools/batch-class-members-service.js +97 -0
  10. package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
  11. package/dist/entry-tools/batch-class-source-service.js +100 -0
  12. package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
  13. package/dist/entry-tools/batch-mappings-service.js +66 -0
  14. package/dist/entry-tools/batch-runner.d.ts +72 -0
  15. package/dist/entry-tools/batch-runner.js +90 -0
  16. package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
  17. package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
  18. package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
  19. package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
  20. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
  21. package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
  22. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
  23. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
  27. package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
  28. package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
  29. package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
  31. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
  32. package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
  33. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
  34. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
  35. package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
  36. package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
  37. package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
  38. package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
  39. package/dist/entry-tools/manage-cache-service.d.ts +16 -16
  40. package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
  41. package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
  42. package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
  43. package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
  44. package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
  45. package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
  46. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +97 -0
  47. package/dist/entry-tools/validate-project/cases/project-summary.js +346 -0
  48. package/dist/entry-tools/validate-project/internal.d.ts +135 -0
  49. package/dist/entry-tools/validate-project/internal.js +287 -0
  50. package/dist/entry-tools/validate-project-service.d.ts +63 -47
  51. package/dist/entry-tools/validate-project-service.js +12 -562
  52. package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
  53. package/dist/entry-tools/verify-mixin-target-service.js +323 -0
  54. package/dist/error-mapping.d.ts +40 -0
  55. package/dist/error-mapping.js +139 -0
  56. package/dist/errors.d.ts +6 -0
  57. package/dist/errors.js +6 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +142 -1352
  60. package/dist/mapping/internal-types.d.ts +54 -0
  61. package/dist/mapping/internal-types.js +14 -0
  62. package/dist/mapping/loaders/mojang.d.ts +2 -0
  63. package/dist/mapping/loaders/mojang.js +64 -0
  64. package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
  65. package/dist/mapping/loaders/tiny-loom.js +73 -0
  66. package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
  67. package/dist/mapping/loaders/tiny-maven.js +104 -0
  68. package/dist/mapping/loaders/types.d.ts +14 -0
  69. package/dist/mapping/loaders/types.js +2 -0
  70. package/dist/mapping/lookup.d.ts +52 -0
  71. package/dist/mapping/lookup.js +496 -0
  72. package/dist/mapping/parsers/normalize.d.ts +10 -0
  73. package/dist/mapping/parsers/normalize.js +52 -0
  74. package/dist/mapping/parsers/proguard.d.ts +20 -0
  75. package/dist/mapping/parsers/proguard.js +138 -0
  76. package/dist/mapping/parsers/symbol-records.d.ts +27 -0
  77. package/dist/mapping/parsers/symbol-records.js +216 -0
  78. package/dist/mapping/parsers/tiny.d.ts +9 -0
  79. package/dist/mapping/parsers/tiny.js +96 -0
  80. package/dist/mapping/types.d.ts +147 -0
  81. package/dist/mapping/types.js +2 -0
  82. package/dist/mapping-pipeline-service.js +3 -2
  83. package/dist/mapping-service.d.ts +3 -144
  84. package/dist/mapping-service.js +19 -1201
  85. package/dist/mixin/access-validators.d.ts +9 -0
  86. package/dist/mixin/access-validators.js +257 -0
  87. package/dist/mixin/annotation-validators.d.ts +5 -0
  88. package/dist/mixin/annotation-validators.js +162 -0
  89. package/dist/mixin/helpers.d.ts +28 -0
  90. package/dist/mixin/helpers.js +315 -0
  91. package/dist/mixin/parsed-validator.d.ts +8 -0
  92. package/dist/mixin/parsed-validator.js +337 -0
  93. package/dist/mixin/types.d.ts +208 -0
  94. package/dist/mixin/types.js +28 -0
  95. package/dist/mixin-validator.d.ts +9 -201
  96. package/dist/mixin-validator.js +8 -1020
  97. package/dist/source/access-validate.d.ts +4 -0
  98. package/dist/source/access-validate.js +254 -0
  99. package/dist/source/artifact-resolver.d.ts +110 -0
  100. package/dist/source/artifact-resolver.js +1174 -0
  101. package/dist/source/cache-metrics.d.ts +26 -0
  102. package/dist/source/cache-metrics.js +172 -0
  103. package/dist/source/class-source/members-builder.d.ts +34 -0
  104. package/dist/source/class-source/members-builder.js +46 -0
  105. package/dist/source/class-source/snippet-builder.d.ts +19 -0
  106. package/dist/source/class-source/snippet-builder.js +46 -0
  107. package/dist/source/class-source-helpers.d.ts +34 -0
  108. package/dist/source/class-source-helpers.js +140 -0
  109. package/dist/source/class-source.d.ts +42 -0
  110. package/dist/source/class-source.js +883 -0
  111. package/dist/source/descriptor-utils.d.ts +6 -0
  112. package/dist/source/descriptor-utils.js +37 -0
  113. package/dist/source/file-access.d.ts +4 -0
  114. package/dist/source/file-access.js +102 -0
  115. package/dist/source/indexer.d.ts +82 -0
  116. package/dist/source/indexer.js +505 -0
  117. package/dist/source/lifecycle/diff-utils.d.ts +9 -0
  118. package/dist/source/lifecycle/diff-utils.js +107 -0
  119. package/dist/source/lifecycle/diff.d.ts +2 -0
  120. package/dist/source/lifecycle/diff.js +265 -0
  121. package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
  122. package/dist/source/lifecycle/mapping-helpers.js +327 -0
  123. package/dist/source/lifecycle/runtime-check.d.ts +2 -0
  124. package/dist/source/lifecycle/runtime-check.js +142 -0
  125. package/dist/source/lifecycle/trace.d.ts +2 -0
  126. package/dist/source/lifecycle/trace.js +231 -0
  127. package/dist/source/lifecycle.d.ts +4 -0
  128. package/dist/source/lifecycle.js +5 -0
  129. package/dist/source/search.d.ts +51 -0
  130. package/dist/source/search.js +676 -0
  131. package/dist/source/shared-utils.d.ts +6 -0
  132. package/dist/source/shared-utils.js +55 -0
  133. package/dist/source/state.d.ts +21 -0
  134. package/dist/source/state.js +19 -0
  135. package/dist/source/symbol-resolver.d.ts +3 -0
  136. package/dist/source/symbol-resolver.js +212 -0
  137. package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
  138. package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
  139. package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
  140. package/dist/source/validate-mixin/pipeline/parse.js +10 -0
  141. package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
  142. package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
  143. package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
  144. package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
  145. package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
  146. package/dist/source/validate-mixin/pipeline-context.js +93 -0
  147. package/dist/source/validate-mixin.d.ts +22 -0
  148. package/dist/source/validate-mixin.js +799 -0
  149. package/dist/source/workspace-target.d.ts +18 -0
  150. package/dist/source/workspace-target.js +305 -0
  151. package/dist/source-service.d.ts +147 -170
  152. package/dist/source-service.js +67 -6116
  153. package/dist/stage-emitter.d.ts +13 -0
  154. package/dist/stage-emitter.js +30 -0
  155. package/dist/stdio-supervisor.d.ts +61 -0
  156. package/dist/stdio-supervisor.js +326 -9
  157. package/dist/tool-contract-manifest.d.ts +1 -1
  158. package/dist/tool-contract-manifest.js +23 -6
  159. package/dist/tool-guidance.d.ts +82 -0
  160. package/dist/tool-guidance.js +734 -0
  161. package/dist/tool-schema-registry.d.ts +16 -0
  162. package/dist/tool-schema-registry.js +37 -0
  163. package/dist/tool-schemas.d.ts +3518 -0
  164. package/dist/tool-schemas.js +813 -0
  165. package/dist/types.d.ts +36 -0
  166. package/dist/version-service.js +7 -6
  167. package/dist/workspace-context-cache.d.ts +32 -0
  168. package/dist/workspace-context-cache.js +66 -0
  169. package/dist/workspace-mapping-service.d.ts +16 -0
  170. package/dist/workspace-mapping-service.js +173 -1
  171. package/docs/README-ja.md +414 -0
  172. package/docs/examples.md +483 -0
  173. package/docs/tool-reference.md +459 -0
  174. package/package.json +3 -2
package/dist/types.d.ts CHANGED
@@ -10,6 +10,19 @@ export interface SourceTargetInput {
10
10
  kind: ArtifactTargetKind;
11
11
  value: string;
12
12
  }
13
+ export type WorkspaceTargetInput = {
14
+ kind: "workspace";
15
+ scope?: ArtifactScope;
16
+ strict?: boolean;
17
+ };
18
+ export type DependencyTargetInput = {
19
+ kind: "dependency";
20
+ group: string;
21
+ name: string;
22
+ version?: string;
23
+ versionFromProject?: boolean;
24
+ };
25
+ export type ResolveArtifactTargetInput = SourceTargetInput | WorkspaceTargetInput | DependencyTargetInput;
13
26
  export interface ResolvedSourceArtifact {
14
27
  artifactId: string;
15
28
  artifactAlias?: string;
@@ -28,6 +41,26 @@ export interface ResolvedSourceArtifact {
28
41
  isDecompiled: boolean;
29
42
  resolvedAt: string;
30
43
  }
44
+ export interface WorkspaceResolutionProvenance {
45
+ projectPath: string;
46
+ detected: {
47
+ minecraftVersion?: string;
48
+ compileMapping?: SourceMapping;
49
+ loader?: string;
50
+ };
51
+ source: string;
52
+ cacheHit: boolean;
53
+ warnings?: string[];
54
+ }
55
+ export interface DependencyResolutionProvenance {
56
+ group: string;
57
+ name: string;
58
+ resolvedVersion?: string;
59
+ source: string;
60
+ candidatesSeen?: string[];
61
+ attempts?: string[];
62
+ cacheHit: boolean;
63
+ }
31
64
  export interface ArtifactProvenance {
32
65
  target: SourceTargetInput;
33
66
  resolvedAt: string;
@@ -40,6 +73,9 @@ export interface ArtifactProvenance {
40
73
  repoUrl?: string;
41
74
  };
42
75
  transformChain: string[];
76
+ workspaceResolution?: WorkspaceResolutionProvenance;
77
+ dependencyResolution?: DependencyResolutionProvenance;
78
+ warnings?: string[];
43
79
  }
44
80
  export interface RuntimeValidationProvenance<TMapping extends RuntimeValidationNamespace = RuntimeValidationNamespace> {
45
81
  version: string;
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
+ import { buildSuggestedCall } from "./build-suggested-call.js";
4
5
  import { createError, ERROR_CODES } from "./errors.js";
5
6
  import { computeFileSha1 } from "./hash.js";
6
7
  import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
@@ -24,7 +25,7 @@ function ensureVersionDetail(value, version) {
24
25
  details: {
25
26
  version,
26
27
  nextAction: "Use list-versions to see available Minecraft versions.",
27
- suggestedCall: { tool: "list-versions", params: {} }
28
+ ...buildSuggestedCall({ tool: "list-versions", params: {} })
28
29
  }
29
30
  });
30
31
  }
@@ -112,7 +113,7 @@ export class VersionService {
112
113
  details: {
113
114
  version: normalizedVersion,
114
115
  nextAction: "Use list-versions to see available Minecraft versions.",
115
- suggestedCall: { tool: "list-versions", params: {} }
116
+ ...buildSuggestedCall({ tool: "list-versions", params: {} })
116
117
  }
117
118
  });
118
119
  }
@@ -144,7 +145,7 @@ export class VersionService {
144
145
  details: {
145
146
  version: normalizedVersion,
146
147
  nextAction: "Use list-versions to see available Minecraft versions.",
147
- suggestedCall: { tool: "list-versions", params: {} }
148
+ ...buildSuggestedCall({ tool: "list-versions", params: {} })
148
149
  }
149
150
  });
150
151
  }
@@ -157,7 +158,7 @@ export class VersionService {
157
158
  details: {
158
159
  version: normalizedVersion,
159
160
  nextAction: "Use list-versions to see available Minecraft versions.",
160
- suggestedCall: { tool: "list-versions", params: {} }
161
+ ...buildSuggestedCall({ tool: "list-versions", params: {} })
161
162
  }
162
163
  });
163
164
  }
@@ -249,7 +250,7 @@ export class VersionService {
249
250
  details: {
250
251
  version: normalizedVersion,
251
252
  nextAction: "Use list-versions to see available Minecraft versions.",
252
- suggestedCall: { tool: "list-versions", params: {} }
253
+ ...buildSuggestedCall({ tool: "list-versions", params: {} })
253
254
  }
254
255
  });
255
256
  }
@@ -262,7 +263,7 @@ export class VersionService {
262
263
  details: {
263
264
  version: normalizedVersion,
264
265
  nextAction: "Use list-versions to see available Minecraft versions.",
265
- suggestedCall: { tool: "list-versions", params: {} }
266
+ ...buildSuggestedCall({ tool: "list-versions", params: {} })
266
267
  }
267
268
  });
268
269
  }
@@ -0,0 +1,32 @@
1
+ import type { SourceMapping } from "./types.js";
2
+ import type { WorkspaceProjectLoader } from "./workspace-mapping-service.js";
3
+ export type WorkspaceContextEvidence = {
4
+ source: string;
5
+ field: string;
6
+ value?: string;
7
+ };
8
+ export type WorkspaceContext = {
9
+ projectPath: string;
10
+ minecraftVersion?: string;
11
+ compileMapping?: SourceMapping;
12
+ loader?: WorkspaceProjectLoader;
13
+ detectedAt: number;
14
+ evidence: WorkspaceContextEvidence[];
15
+ dependencyVersions: Map<string, string>;
16
+ partial?: boolean;
17
+ };
18
+ export interface WorkspaceContextCache {
19
+ read(projectPath: string): WorkspaceContext | undefined;
20
+ write(ctx: WorkspaceContext): void;
21
+ invalidate(projectPath: string): boolean;
22
+ list(): WorkspaceContext[];
23
+ clear(): void;
24
+ }
25
+ export type WorkspaceContextCacheOptions = {
26
+ maxEntries?: number;
27
+ ttlMs?: number;
28
+ clock?: () => number;
29
+ };
30
+ export declare function createWorkspaceContextCache(opts?: WorkspaceContextCacheOptions): WorkspaceContextCache;
31
+ export declare function getProcessWorkspaceContextCache(): WorkspaceContextCache;
32
+ export declare function resetProcessWorkspaceContextCacheForTesting(): void;
@@ -0,0 +1,66 @@
1
+ import { resolve as resolvePath } from "node:path";
2
+ import { LruList } from "./lru-list.js";
3
+ const DEFAULT_MAX_ENTRIES = 16;
4
+ const DEFAULT_TTL_MS = 5 * 60_000;
5
+ function normalizeKey(projectPath) {
6
+ return resolvePath(projectPath);
7
+ }
8
+ export function createWorkspaceContextCache(opts = {}) {
9
+ const maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
10
+ const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
11
+ const clock = opts.clock ?? (() => Date.now());
12
+ const lru = new LruList();
13
+ function isExpired(ctx) {
14
+ return clock() - ctx.detectedAt > ttlMs;
15
+ }
16
+ function evictExcess() {
17
+ while (lru.size > maxEntries) {
18
+ const oldest = lru.peekOldest();
19
+ if (!oldest) {
20
+ return;
21
+ }
22
+ lru.remove(oldest.key);
23
+ }
24
+ }
25
+ return {
26
+ read(projectPath) {
27
+ const key = normalizeKey(projectPath);
28
+ const value = lru.touch(key);
29
+ if (!value) {
30
+ return undefined;
31
+ }
32
+ if (isExpired(value)) {
33
+ lru.remove(key);
34
+ return undefined;
35
+ }
36
+ return value;
37
+ },
38
+ write(ctx) {
39
+ const key = normalizeKey(ctx.projectPath);
40
+ lru.upsert(key, { ...ctx, projectPath: key });
41
+ evictExcess();
42
+ },
43
+ invalidate(projectPath) {
44
+ const key = normalizeKey(projectPath);
45
+ return lru.remove(key) !== undefined;
46
+ },
47
+ list() {
48
+ const all = lru.toArray().map((entry) => entry.value);
49
+ return all.filter((value) => !isExpired(value));
50
+ },
51
+ clear() {
52
+ lru.clear();
53
+ }
54
+ };
55
+ }
56
+ let processCache;
57
+ export function getProcessWorkspaceContextCache() {
58
+ if (!processCache) {
59
+ processCache = createWorkspaceContextCache();
60
+ }
61
+ return processCache;
62
+ }
63
+ export function resetProcessWorkspaceContextCacheForTesting() {
64
+ processCache = undefined;
65
+ }
66
+ //# sourceMappingURL=workspace-context-cache.js.map
@@ -25,8 +25,24 @@ export type WorkspaceProjectLoaderOutput = {
25
25
  evidence: WorkspaceLoaderEvidence[];
26
26
  warnings: string[];
27
27
  };
28
+ export type DependencyVersionResolution = {
29
+ resolved: true;
30
+ version: string;
31
+ source: string;
32
+ candidatesSeen: string[];
33
+ attempts: string[];
34
+ } | {
35
+ resolved: false;
36
+ candidatesSeen: string[];
37
+ attempts: string[];
38
+ };
39
+ export type DependencyVersionOptions = {
40
+ includeSnapshots?: boolean;
41
+ };
42
+ export declare function isSafeMavenVersionToken(token: string): boolean;
28
43
  export declare class WorkspaceMappingService {
29
44
  detectCompileMapping(input: WorkspaceCompileMappingInput): Promise<WorkspaceCompileMappingOutput>;
30
45
  detectProjectMinecraftVersion(projectPath: string): Promise<string | undefined>;
46
+ detectDependencyVersion(projectPath: string, group: string, name: string, opts?: DependencyVersionOptions): Promise<DependencyVersionResolution>;
31
47
  detectProjectLoader(projectPath: string): Promise<WorkspaceProjectLoaderOutput>;
32
48
  }
@@ -1,4 +1,5 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
2
3
  import { resolve } from "node:path";
3
4
  import fastGlob from "fast-glob";
4
5
  import { mapWithConcurrencyLimit } from "./concurrency.js";
@@ -38,6 +39,93 @@ function detectMappingsFromContent(content) {
38
39
  }
39
40
  return detections;
40
41
  }
42
+ function camelCaseDependencyName(name) {
43
+ const parts = name.split(/[-_]/).filter((part) => part.length > 0);
44
+ if (parts.length === 0) {
45
+ return name;
46
+ }
47
+ const [first, ...rest] = parts;
48
+ const head = first ?? "";
49
+ return (head +
50
+ rest
51
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
52
+ .join(""));
53
+ }
54
+ function lastGroupSegment(group) {
55
+ const segments = group.split(".").filter((segment) => segment.length > 0);
56
+ return segments[segments.length - 1] ?? group;
57
+ }
58
+ function buildDependencyPropertyKeys(group, name) {
59
+ const camelName = camelCaseDependencyName(name);
60
+ const groupSegment = lastGroupSegment(group);
61
+ const camelGroupName = camelCaseDependencyName(`${groupSegment}_${name}`);
62
+ const keys = [
63
+ `${name}_version`,
64
+ `${camelName}Version`,
65
+ `${groupSegment}_${name}_version`,
66
+ `${camelGroupName}Version`
67
+ ];
68
+ const seen = new Set();
69
+ const deduped = [];
70
+ for (const key of keys) {
71
+ if (!seen.has(key)) {
72
+ seen.add(key);
73
+ deduped.push(key);
74
+ }
75
+ }
76
+ return deduped;
77
+ }
78
+ function readPropertyValue(content, key) {
79
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
80
+ const pattern = new RegExp(`^\\s*${escaped}\\s*=\\s*(.+?)\\s*$`, "m");
81
+ const match = content.match(pattern);
82
+ if (!match) {
83
+ return undefined;
84
+ }
85
+ const value = match[1]?.trim();
86
+ return value && value.length > 0 ? value : undefined;
87
+ }
88
+ function compareSemverDescending(left, right) {
89
+ const leftParts = left.split(/[.+-]/);
90
+ const rightParts = right.split(/[.+-]/);
91
+ const length = Math.max(leftParts.length, rightParts.length);
92
+ for (let index = 0; index < length; index += 1) {
93
+ const leftPart = leftParts[index] ?? "0";
94
+ const rightPart = rightParts[index] ?? "0";
95
+ const leftNum = /^\d+$/.test(leftPart) ? Number.parseInt(leftPart, 10) : Number.NaN;
96
+ const rightNum = /^\d+$/.test(rightPart) ? Number.parseInt(rightPart, 10) : Number.NaN;
97
+ if (!Number.isNaN(leftNum) && !Number.isNaN(rightNum)) {
98
+ if (leftNum !== rightNum) {
99
+ return rightNum - leftNum;
100
+ }
101
+ continue;
102
+ }
103
+ if (leftPart !== rightPart) {
104
+ return rightPart.localeCompare(leftPart);
105
+ }
106
+ }
107
+ return 0;
108
+ }
109
+ function isPathTraversalToken(token) {
110
+ return token.length === 0 || token.includes("/") || token.includes("\\") || token.includes("..") || token.includes("\0");
111
+ }
112
+ const SAFE_VERSION_TOKEN_RE = /^[A-Za-z0-9._+-]+$/;
113
+ export function isSafeMavenVersionToken(token) {
114
+ if (typeof token !== "string" || token.length === 0 || token.length > 200) {
115
+ return false;
116
+ }
117
+ if (token.startsWith(".") || token.includes("..")) {
118
+ return false;
119
+ }
120
+ return SAFE_VERSION_TOKEN_RE.test(token);
121
+ }
122
+ function resolveGradleUserHome() {
123
+ const configured = process.env.GRADLE_USER_HOME?.trim();
124
+ if (configured) {
125
+ return configured;
126
+ }
127
+ return resolve(homedir(), ".gradle");
128
+ }
41
129
  function detectLoadersFromContent(content) {
42
130
  const detections = [];
43
131
  if (/\bid\s*(?:\(\s*)?["']net\.neoforged\.moddev["']\s*\)?/i.test(content)) {
@@ -159,6 +247,90 @@ export class WorkspaceMappingService {
159
247
  }
160
248
  return undefined;
161
249
  }
250
+ async detectDependencyVersion(projectPath, group, name, opts) {
251
+ if (isPathTraversalToken(group) || isPathTraversalToken(name)) {
252
+ throw createError({
253
+ code: ERROR_CODES.INVALID_INPUT,
254
+ message: "Dependency group and name must not contain path traversal characters.",
255
+ details: { group, name }
256
+ });
257
+ }
258
+ const includeSnapshots = opts?.includeSnapshots === true;
259
+ const attempts = [];
260
+ const candidatesSeen = [];
261
+ const root = resolve(projectPath);
262
+ const propsPath = resolve(root, "gradle.properties");
263
+ let propsContent;
264
+ try {
265
+ propsContent = await readFile(propsPath, "utf8");
266
+ }
267
+ catch {
268
+ propsContent = undefined;
269
+ }
270
+ const keys = buildDependencyPropertyKeys(group, name);
271
+ if (propsContent !== undefined) {
272
+ for (const key of keys) {
273
+ attempts.push(`gradle.properties:${key}`);
274
+ const value = readPropertyValue(propsContent, key);
275
+ if (value) {
276
+ if (!isSafeMavenVersionToken(value)) {
277
+ attempts.push(`gradle.properties:${key}:rejected-unsafe-version`);
278
+ continue;
279
+ }
280
+ return {
281
+ resolved: true,
282
+ version: value,
283
+ source: `gradle.properties:${key}`,
284
+ candidatesSeen,
285
+ attempts
286
+ };
287
+ }
288
+ }
289
+ }
290
+ else {
291
+ for (const key of keys) {
292
+ attempts.push(`gradle.properties:${key}`);
293
+ }
294
+ }
295
+ const modulesDir = resolve(resolveGradleUserHome(), "caches", "modules-2", "files-2.1", group, name);
296
+ attempts.push(`modules-2:${modulesDir}`);
297
+ let entries = [];
298
+ try {
299
+ entries = await readdir(modulesDir);
300
+ }
301
+ catch {
302
+ return { resolved: false, candidatesSeen, attempts };
303
+ }
304
+ const filtered = entries.filter((entry) => {
305
+ if (!isSafeMavenVersionToken(entry)) {
306
+ return false;
307
+ }
308
+ if (includeSnapshots) {
309
+ return true;
310
+ }
311
+ const lower = entry.toLowerCase();
312
+ return !lower.endsWith("-snapshot") && !lower.endsWith("-dev");
313
+ });
314
+ const sorted = [...filtered].sort(compareSemverDescending);
315
+ candidatesSeen.push(...sorted);
316
+ if (sorted.length === 0) {
317
+ return { resolved: false, candidatesSeen, attempts };
318
+ }
319
+ if (sorted.length > 1) {
320
+ return { resolved: false, candidatesSeen, attempts };
321
+ }
322
+ const chosen = sorted[0];
323
+ if (!chosen) {
324
+ return { resolved: false, candidatesSeen, attempts };
325
+ }
326
+ return {
327
+ resolved: true,
328
+ version: chosen,
329
+ source: `modules-2:${modulesDir}`,
330
+ candidatesSeen,
331
+ attempts
332
+ };
333
+ }
162
334
  async detectProjectLoader(projectPath) {
163
335
  const root = resolve(projectPath);
164
336
  const buildFiles = (await fastGlob.glob(["build.gradle", "build.gradle.kts", "**/build.gradle", "**/build.gradle.kts"], {