@adhisang/minecraft-modding-mcp 3.2.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 (194) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +52 -32
  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 +59 -7
  7. package/dist/config.d.ts +10 -1
  8. package/dist/config.js +52 -1
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +18 -18
  10. package/dist/entry-tools/analyze-symbol-service.js +13 -2
  11. package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
  12. package/dist/entry-tools/batch-class-members-service.js +97 -0
  13. package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
  14. package/dist/entry-tools/batch-class-source-service.js +100 -0
  15. package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
  16. package/dist/entry-tools/batch-mappings-service.js +66 -0
  17. package/dist/entry-tools/batch-runner.d.ts +72 -0
  18. package/dist/entry-tools/batch-runner.js +90 -0
  19. package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
  20. package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
  21. package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
  22. package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
  23. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
  27. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
  28. package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
  29. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
  31. package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
  32. package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
  33. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
  34. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
  35. package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
  36. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
  37. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
  38. package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
  39. package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
  40. package/dist/entry-tools/inspect-minecraft-service.d.ts +213 -328
  41. package/dist/entry-tools/inspect-minecraft-service.js +20 -1238
  42. package/dist/entry-tools/manage-cache-service.d.ts +16 -16
  43. package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
  44. package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
  45. package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
  46. package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
  47. package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
  48. package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
  49. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +97 -0
  50. package/dist/entry-tools/validate-project/cases/project-summary.js +346 -0
  51. package/dist/entry-tools/validate-project/internal.d.ts +135 -0
  52. package/dist/entry-tools/validate-project/internal.js +287 -0
  53. package/dist/entry-tools/validate-project-service.d.ts +63 -47
  54. package/dist/entry-tools/validate-project-service.js +12 -482
  55. package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
  56. package/dist/entry-tools/verify-mixin-target-service.js +323 -0
  57. package/dist/error-mapping.d.ts +40 -0
  58. package/dist/error-mapping.js +139 -0
  59. package/dist/errors.d.ts +6 -0
  60. package/dist/errors.js +6 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +170 -1314
  63. package/dist/lru-list.d.ts +31 -0
  64. package/dist/lru-list.js +102 -0
  65. package/dist/mapping/internal-types.d.ts +54 -0
  66. package/dist/mapping/internal-types.js +14 -0
  67. package/dist/mapping/loaders/mojang.d.ts +2 -0
  68. package/dist/mapping/loaders/mojang.js +64 -0
  69. package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
  70. package/dist/mapping/loaders/tiny-loom.js +73 -0
  71. package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
  72. package/dist/mapping/loaders/tiny-maven.js +104 -0
  73. package/dist/mapping/loaders/types.d.ts +14 -0
  74. package/dist/mapping/loaders/types.js +2 -0
  75. package/dist/mapping/lookup.d.ts +52 -0
  76. package/dist/mapping/lookup.js +496 -0
  77. package/dist/mapping/parsers/normalize.d.ts +10 -0
  78. package/dist/mapping/parsers/normalize.js +52 -0
  79. package/dist/mapping/parsers/proguard.d.ts +20 -0
  80. package/dist/mapping/parsers/proguard.js +138 -0
  81. package/dist/mapping/parsers/symbol-records.d.ts +27 -0
  82. package/dist/mapping/parsers/symbol-records.js +216 -0
  83. package/dist/mapping/parsers/tiny.d.ts +9 -0
  84. package/dist/mapping/parsers/tiny.js +96 -0
  85. package/dist/mapping/types.d.ts +147 -0
  86. package/dist/mapping/types.js +2 -0
  87. package/dist/mapping-pipeline-service.d.ts +10 -1
  88. package/dist/mapping-pipeline-service.js +16 -3
  89. package/dist/mapping-service.d.ts +15 -144
  90. package/dist/mapping-service.js +179 -1119
  91. package/dist/mixin/access-validators.d.ts +9 -0
  92. package/dist/mixin/access-validators.js +257 -0
  93. package/dist/mixin/annotation-validators.d.ts +5 -0
  94. package/dist/mixin/annotation-validators.js +162 -0
  95. package/dist/mixin/helpers.d.ts +28 -0
  96. package/dist/mixin/helpers.js +315 -0
  97. package/dist/mixin/parsed-validator.d.ts +8 -0
  98. package/dist/mixin/parsed-validator.js +337 -0
  99. package/dist/mixin/types.d.ts +208 -0
  100. package/dist/mixin/types.js +28 -0
  101. package/dist/mixin-validator.d.ts +9 -201
  102. package/dist/mixin-validator.js +8 -1005
  103. package/dist/observability.d.ts +18 -1
  104. package/dist/observability.js +44 -1
  105. package/dist/response-utils.d.ts +44 -10
  106. package/dist/response-utils.js +131 -17
  107. package/dist/source/access-validate.d.ts +4 -0
  108. package/dist/source/access-validate.js +254 -0
  109. package/dist/source/artifact-resolver.d.ts +110 -0
  110. package/dist/source/artifact-resolver.js +1174 -0
  111. package/dist/source/cache-metrics.d.ts +26 -0
  112. package/dist/source/cache-metrics.js +172 -0
  113. package/dist/source/class-source/members-builder.d.ts +34 -0
  114. package/dist/source/class-source/members-builder.js +46 -0
  115. package/dist/source/class-source/snippet-builder.d.ts +19 -0
  116. package/dist/source/class-source/snippet-builder.js +46 -0
  117. package/dist/source/class-source-helpers.d.ts +34 -0
  118. package/dist/source/class-source-helpers.js +140 -0
  119. package/dist/source/class-source.d.ts +42 -0
  120. package/dist/source/class-source.js +883 -0
  121. package/dist/source/descriptor-utils.d.ts +6 -0
  122. package/dist/source/descriptor-utils.js +37 -0
  123. package/dist/source/file-access.d.ts +4 -0
  124. package/dist/source/file-access.js +102 -0
  125. package/dist/source/indexer.d.ts +82 -0
  126. package/dist/source/indexer.js +505 -0
  127. package/dist/source/lifecycle/diff-utils.d.ts +9 -0
  128. package/dist/source/lifecycle/diff-utils.js +107 -0
  129. package/dist/source/lifecycle/diff.d.ts +2 -0
  130. package/dist/source/lifecycle/diff.js +265 -0
  131. package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
  132. package/dist/source/lifecycle/mapping-helpers.js +327 -0
  133. package/dist/source/lifecycle/runtime-check.d.ts +2 -0
  134. package/dist/source/lifecycle/runtime-check.js +142 -0
  135. package/dist/source/lifecycle/trace.d.ts +2 -0
  136. package/dist/source/lifecycle/trace.js +231 -0
  137. package/dist/source/lifecycle.d.ts +4 -0
  138. package/dist/source/lifecycle.js +5 -0
  139. package/dist/source/search.d.ts +51 -0
  140. package/dist/source/search.js +676 -0
  141. package/dist/source/shared-utils.d.ts +6 -0
  142. package/dist/source/shared-utils.js +55 -0
  143. package/dist/source/state.d.ts +21 -0
  144. package/dist/source/state.js +19 -0
  145. package/dist/source/symbol-resolver.d.ts +3 -0
  146. package/dist/source/symbol-resolver.js +212 -0
  147. package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
  148. package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
  149. package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
  150. package/dist/source/validate-mixin/pipeline/parse.js +10 -0
  151. package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
  152. package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
  153. package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
  154. package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
  155. package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
  156. package/dist/source/validate-mixin/pipeline-context.js +93 -0
  157. package/dist/source/validate-mixin.d.ts +22 -0
  158. package/dist/source/validate-mixin.js +799 -0
  159. package/dist/source/workspace-target.d.ts +18 -0
  160. package/dist/source/workspace-target.js +305 -0
  161. package/dist/source-resolver.d.ts +9 -1
  162. package/dist/source-resolver.js +14 -6
  163. package/dist/source-service.d.ts +178 -105
  164. package/dist/source-service.js +72 -5312
  165. package/dist/stage-emitter.d.ts +13 -0
  166. package/dist/stage-emitter.js +30 -0
  167. package/dist/stdio-supervisor.d.ts +61 -0
  168. package/dist/stdio-supervisor.js +326 -9
  169. package/dist/storage/artifacts-repo.d.ts +4 -1
  170. package/dist/storage/artifacts-repo.js +33 -5
  171. package/dist/storage/files-repo.d.ts +0 -2
  172. package/dist/storage/files-repo.js +0 -11
  173. package/dist/storage/migrations.d.ts +1 -1
  174. package/dist/storage/migrations.js +10 -2
  175. package/dist/storage/schema.d.ts +2 -0
  176. package/dist/storage/schema.js +25 -0
  177. package/dist/tool-contract-manifest.d.ts +1 -1
  178. package/dist/tool-contract-manifest.js +23 -6
  179. package/dist/tool-guidance.d.ts +82 -0
  180. package/dist/tool-guidance.js +734 -0
  181. package/dist/tool-schema-registry.d.ts +16 -0
  182. package/dist/tool-schema-registry.js +37 -0
  183. package/dist/tool-schemas.d.ts +3518 -0
  184. package/dist/tool-schemas.js +813 -0
  185. package/dist/types.d.ts +39 -0
  186. package/dist/version-service.js +7 -6
  187. package/dist/workspace-context-cache.d.ts +32 -0
  188. package/dist/workspace-context-cache.js +66 -0
  189. package/dist/workspace-mapping-service.d.ts +16 -0
  190. package/dist/workspace-mapping-service.js +173 -1
  191. package/docs/README-ja.md +414 -0
  192. package/docs/examples.md +483 -0
  193. package/docs/tool-reference.md +459 -0
  194. package/package.json +5 -2
package/dist/types.d.ts CHANGED
@@ -5,12 +5,27 @@ export type RuntimeValidationNamespace = SourceMapping | AccessTransformerNamesp
5
5
  export type MappingSourcePriority = "loom-first" | "maven-first";
6
6
  export type ArtifactTargetKind = "version" | "jar" | "coordinate";
7
7
  export type ArtifactScope = "vanilla" | "merged" | "loader";
8
+ export type MappingVariant = "pass" | "mojang-remapped";
8
9
  export interface SourceTargetInput {
9
10
  kind: ArtifactTargetKind;
10
11
  value: string;
11
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;
12
26
  export interface ResolvedSourceArtifact {
13
27
  artifactId: string;
28
+ artifactAlias?: string;
14
29
  artifactSignature: string;
15
30
  origin: SourceOrigin;
16
31
  binaryJarPath?: string;
@@ -26,6 +41,26 @@ export interface ResolvedSourceArtifact {
26
41
  isDecompiled: boolean;
27
42
  resolvedAt: string;
28
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
+ }
29
64
  export interface ArtifactProvenance {
30
65
  target: SourceTargetInput;
31
66
  resolvedAt: string;
@@ -38,6 +73,9 @@ export interface ArtifactProvenance {
38
73
  repoUrl?: string;
39
74
  };
40
75
  transformChain: string[];
76
+ workspaceResolution?: WorkspaceResolutionProvenance;
77
+ dependencyResolution?: DependencyResolutionProvenance;
78
+ warnings?: string[];
41
79
  }
42
80
  export interface RuntimeValidationProvenance<TMapping extends RuntimeValidationNamespace = RuntimeValidationNamespace> {
43
81
  version: string;
@@ -102,6 +140,7 @@ export interface SourceSearchHit {
102
140
  }
103
141
  export interface ArtifactRow {
104
142
  artifactId: string;
143
+ alias: string | undefined;
105
144
  origin: SourceOrigin;
106
145
  coordinate: string | undefined;
107
146
  version: string | undefined;
@@ -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"], {