@adhisang/minecraft-modding-mcp 1.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 (106) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/dist/access-widener-parser.d.ts +24 -0
  5. package/dist/access-widener-parser.js +77 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +4 -0
  8. package/dist/config.d.ts +27 -0
  9. package/dist/config.js +178 -0
  10. package/dist/decompiler/vineflower.d.ts +15 -0
  11. package/dist/decompiler/vineflower.js +185 -0
  12. package/dist/errors.d.ts +50 -0
  13. package/dist/errors.js +49 -0
  14. package/dist/hash.d.ts +1 -0
  15. package/dist/hash.js +12 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.js +1447 -0
  18. package/dist/java-process.d.ts +16 -0
  19. package/dist/java-process.js +120 -0
  20. package/dist/logger.d.ts +3 -0
  21. package/dist/logger.js +21 -0
  22. package/dist/mapping-pipeline-service.d.ts +18 -0
  23. package/dist/mapping-pipeline-service.js +60 -0
  24. package/dist/mapping-service.d.ts +161 -0
  25. package/dist/mapping-service.js +1706 -0
  26. package/dist/maven-resolver.d.ts +22 -0
  27. package/dist/maven-resolver.js +122 -0
  28. package/dist/minecraft-explorer-service.d.ts +43 -0
  29. package/dist/minecraft-explorer-service.js +562 -0
  30. package/dist/mixin-parser.d.ts +34 -0
  31. package/dist/mixin-parser.js +194 -0
  32. package/dist/mixin-validator.d.ts +59 -0
  33. package/dist/mixin-validator.js +274 -0
  34. package/dist/mod-analyzer.d.ts +23 -0
  35. package/dist/mod-analyzer.js +346 -0
  36. package/dist/mod-decompile-service.d.ts +39 -0
  37. package/dist/mod-decompile-service.js +136 -0
  38. package/dist/mod-remap-service.d.ts +17 -0
  39. package/dist/mod-remap-service.js +186 -0
  40. package/dist/mod-search-service.d.ts +28 -0
  41. package/dist/mod-search-service.js +174 -0
  42. package/dist/mojang-tiny-mapping-service.d.ts +13 -0
  43. package/dist/mojang-tiny-mapping-service.js +351 -0
  44. package/dist/nbt/java-nbt-codec.d.ts +3 -0
  45. package/dist/nbt/java-nbt-codec.js +385 -0
  46. package/dist/nbt/json-patch.d.ts +3 -0
  47. package/dist/nbt/json-patch.js +352 -0
  48. package/dist/nbt/pipeline.d.ts +39 -0
  49. package/dist/nbt/pipeline.js +173 -0
  50. package/dist/nbt/typed-json.d.ts +10 -0
  51. package/dist/nbt/typed-json.js +205 -0
  52. package/dist/nbt/types.d.ts +66 -0
  53. package/dist/nbt/types.js +2 -0
  54. package/dist/observability.d.ts +88 -0
  55. package/dist/observability.js +165 -0
  56. package/dist/path-converter.d.ts +12 -0
  57. package/dist/path-converter.js +161 -0
  58. package/dist/path-resolver.d.ts +19 -0
  59. package/dist/path-resolver.js +78 -0
  60. package/dist/registry-service.d.ts +29 -0
  61. package/dist/registry-service.js +214 -0
  62. package/dist/repo-downloader.d.ts +15 -0
  63. package/dist/repo-downloader.js +111 -0
  64. package/dist/resources.d.ts +3 -0
  65. package/dist/resources.js +154 -0
  66. package/dist/search-hit-accumulator.d.ts +38 -0
  67. package/dist/search-hit-accumulator.js +153 -0
  68. package/dist/source-jar-reader.d.ts +13 -0
  69. package/dist/source-jar-reader.js +216 -0
  70. package/dist/source-resolver.d.ts +14 -0
  71. package/dist/source-resolver.js +274 -0
  72. package/dist/source-service.d.ts +404 -0
  73. package/dist/source-service.js +2881 -0
  74. package/dist/storage/artifacts-repo.d.ts +45 -0
  75. package/dist/storage/artifacts-repo.js +209 -0
  76. package/dist/storage/db.d.ts +14 -0
  77. package/dist/storage/db.js +132 -0
  78. package/dist/storage/files-repo.d.ts +78 -0
  79. package/dist/storage/files-repo.js +437 -0
  80. package/dist/storage/index-meta-repo.d.ts +35 -0
  81. package/dist/storage/index-meta-repo.js +97 -0
  82. package/dist/storage/migrations.d.ts +11 -0
  83. package/dist/storage/migrations.js +71 -0
  84. package/dist/storage/schema.d.ts +1 -0
  85. package/dist/storage/schema.js +160 -0
  86. package/dist/storage/sqlite.d.ts +20 -0
  87. package/dist/storage/sqlite.js +111 -0
  88. package/dist/storage/symbols-repo.d.ts +63 -0
  89. package/dist/storage/symbols-repo.js +401 -0
  90. package/dist/symbols/symbol-extractor.d.ts +7 -0
  91. package/dist/symbols/symbol-extractor.js +64 -0
  92. package/dist/tiny-remapper-resolver.d.ts +1 -0
  93. package/dist/tiny-remapper-resolver.js +62 -0
  94. package/dist/tiny-remapper-service.d.ts +16 -0
  95. package/dist/tiny-remapper-service.js +73 -0
  96. package/dist/types.d.ts +120 -0
  97. package/dist/types.js +2 -0
  98. package/dist/version-diff-service.d.ts +41 -0
  99. package/dist/version-diff-service.js +222 -0
  100. package/dist/version-service.d.ts +70 -0
  101. package/dist/version-service.js +411 -0
  102. package/dist/vineflower-resolver.d.ts +1 -0
  103. package/dist/vineflower-resolver.js +62 -0
  104. package/dist/workspace-mapping-service.d.ts +18 -0
  105. package/dist/workspace-mapping-service.js +89 -0
  106. package/package.json +61 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Lightweight regex-based parser for Fabric Mixin Java sources.
3
+ * No AST required — pattern-matches annotations and member declarations.
4
+ */
5
+ /* ------------------------------------------------------------------ */
6
+ /* Regex patterns */
7
+ /* ------------------------------------------------------------------ */
8
+ const CLASS_DECL_RE = /(?:public\s+)?(?:abstract\s+)?class\s+(\w+)/;
9
+ // @Mixin(Foo.class) or @Mixin({Foo.class, Bar.class}) or @Mixin(value = Foo.class)
10
+ // Also handles @Mixin(value = {Foo.class, Bar.class}, priority = 900)
11
+ const MIXIN_ANNOTATION_START_RE = /^\s*@Mixin\s*\(/;
12
+ const MIXIN_TARGET_RE = /(\w[\w.]*?)\.class/g;
13
+ const MIXIN_PRIORITY_RE = /priority\s*=\s*(\d+)/;
14
+ // Injection annotations: @Inject, @Redirect, @ModifyArg, @ModifyVariable, @ModifyConstant, @ModifyExpressionValue
15
+ const INJECTION_ANNOTATION_RE = /^\s*@(Inject|Redirect|ModifyArg|ModifyVariable|ModifyConstant|ModifyExpressionValue)\s*\(/;
16
+ const METHOD_ATTR_RE = /method\s*=\s*"([^"]+)"/;
17
+ // @Shadow field / method
18
+ const SHADOW_ANNOTATION_RE = /^\s*@Shadow\b/;
19
+ const FIELD_DECL_RE = /(?:private|protected|public)?\s*(?:static\s+)?(?:final\s+)?(\w[\w<>,\s]*?)\s+(\w+)\s*[;=]/;
20
+ const METHOD_DECL_RE = /(?:private|protected|public)?\s*(?:static\s+)?(?:abstract\s+)?(?:native\s+)?(\w[\w<>,\s]*?)\s+(\w+)\s*\(/;
21
+ // @Accessor / @Invoker
22
+ const ACCESSOR_ANNOTATION_RE = /^\s*@(Accessor|Invoker)\s*(?:\(\s*"([^"]+)"\s*\))?\s*$/;
23
+ const ACCESSOR_ANNOTATION_START_RE = /^\s*@(Accessor|Invoker)\s*\(/;
24
+ const ACCESSOR_EXPLICIT_RE = /"([^"]+)"/;
25
+ // Naming conventions for accessor/invoker target inference
26
+ const GETTER_PREFIX_RE = /^(?:get|is)([A-Z].*)/;
27
+ const SETTER_PREFIX_RE = /^set([A-Z].*)/;
28
+ const INVOKER_PREFIX_RE = /^(?:invoke|call)([A-Z].*)/;
29
+ /* ------------------------------------------------------------------ */
30
+ /* Helpers */
31
+ /* ------------------------------------------------------------------ */
32
+ function collectMultilineAnnotation(lines, startIndex) {
33
+ let depth = 0;
34
+ let text = "";
35
+ for (let i = startIndex; i < lines.length; i++) {
36
+ const line = lines[i];
37
+ text += (i === startIndex ? "" : "\n") + line;
38
+ for (const ch of line) {
39
+ if (ch === "(")
40
+ depth++;
41
+ if (ch === ")")
42
+ depth--;
43
+ }
44
+ if (depth <= 0) {
45
+ return { text, endIndex: i };
46
+ }
47
+ }
48
+ return { text, endIndex: lines.length - 1 };
49
+ }
50
+ function inferAccessorTarget(methodName) {
51
+ const getterMatch = GETTER_PREFIX_RE.exec(methodName);
52
+ if (getterMatch) {
53
+ return getterMatch[1].charAt(0).toLowerCase() + getterMatch[1].slice(1);
54
+ }
55
+ const setterMatch = SETTER_PREFIX_RE.exec(methodName);
56
+ if (setterMatch) {
57
+ return setterMatch[1].charAt(0).toLowerCase() + setterMatch[1].slice(1);
58
+ }
59
+ const invokerMatch = INVOKER_PREFIX_RE.exec(methodName);
60
+ if (invokerMatch) {
61
+ return invokerMatch[1].charAt(0).toLowerCase() + invokerMatch[1].slice(1);
62
+ }
63
+ return methodName;
64
+ }
65
+ /* ------------------------------------------------------------------ */
66
+ /* Main parser */
67
+ /* ------------------------------------------------------------------ */
68
+ export function parseMixinSource(source) {
69
+ const lines = source.split(/\r?\n/);
70
+ const parseWarnings = [];
71
+ const targets = [];
72
+ const injections = [];
73
+ const shadows = [];
74
+ const accessors = [];
75
+ let className = "";
76
+ let priority;
77
+ // --- Pass 1: find @Mixin annotation and class name ---
78
+ let i = 0;
79
+ while (i < lines.length) {
80
+ if (MIXIN_ANNOTATION_START_RE.test(lines[i])) {
81
+ const { text: mixinText, endIndex } = collectMultilineAnnotation(lines, i);
82
+ MIXIN_TARGET_RE.lastIndex = 0;
83
+ let match;
84
+ while ((match = MIXIN_TARGET_RE.exec(mixinText)) !== null) {
85
+ targets.push({ className: match[1] });
86
+ }
87
+ const priorityMatch = MIXIN_PRIORITY_RE.exec(mixinText);
88
+ if (priorityMatch) {
89
+ priority = parseInt(priorityMatch[1], 10);
90
+ }
91
+ i = endIndex + 1;
92
+ continue;
93
+ }
94
+ const classMatch = CLASS_DECL_RE.exec(lines[i]);
95
+ if (classMatch && !className) {
96
+ className = classMatch[1];
97
+ }
98
+ i++;
99
+ }
100
+ if (targets.length === 0) {
101
+ parseWarnings.push("No @Mixin annotation target found.");
102
+ }
103
+ // --- Pass 2: scan member annotations ---
104
+ i = 0;
105
+ while (i < lines.length) {
106
+ const line = lines[i];
107
+ const lineNum = i + 1;
108
+ // --- @Inject / @Redirect / @ModifyArg etc. ---
109
+ const injMatch = INJECTION_ANNOTATION_RE.exec(line);
110
+ if (injMatch) {
111
+ const annotation = injMatch[1];
112
+ const { text: fullAnnotation, endIndex } = collectMultilineAnnotation(lines, i);
113
+ const methodMatch = METHOD_ATTR_RE.exec(fullAnnotation);
114
+ if (methodMatch) {
115
+ injections.push({ annotation, method: methodMatch[1], line: lineNum });
116
+ }
117
+ else {
118
+ parseWarnings.push(`Line ${lineNum}: @${annotation} missing method attribute.`);
119
+ }
120
+ i = endIndex + 1;
121
+ continue;
122
+ }
123
+ // --- @Shadow ---
124
+ if (SHADOW_ANNOTATION_RE.test(line)) {
125
+ // Advance past @Shadow line to find the declaration
126
+ let declLine = i + 1;
127
+ // Skip additional annotations between @Shadow and declaration
128
+ while (declLine < lines.length && /^\s*@/.test(lines[declLine])) {
129
+ declLine++;
130
+ }
131
+ if (declLine < lines.length) {
132
+ const declText = lines[declLine];
133
+ const methodDeclMatch = METHOD_DECL_RE.exec(declText);
134
+ const fieldDeclMatch = FIELD_DECL_RE.exec(declText);
135
+ // Method if it has parentheses
136
+ if (declText.includes("(") && methodDeclMatch) {
137
+ shadows.push({ kind: "method", name: methodDeclMatch[2], line: lineNum });
138
+ }
139
+ else if (fieldDeclMatch) {
140
+ shadows.push({ kind: "field", name: fieldDeclMatch[2], line: lineNum });
141
+ }
142
+ else {
143
+ parseWarnings.push(`Line ${lineNum}: Could not parse @Shadow member declaration.`);
144
+ }
145
+ }
146
+ i = declLine + 1;
147
+ continue;
148
+ }
149
+ // --- @Accessor / @Invoker ---
150
+ const accessorMatch = ACCESSOR_ANNOTATION_RE.exec(line);
151
+ const accessorStartMatch = !accessorMatch ? ACCESSOR_ANNOTATION_START_RE.exec(line) : null;
152
+ if (accessorMatch || accessorStartMatch) {
153
+ const annotation = (accessorMatch?.[1] ?? accessorStartMatch?.[1]);
154
+ let explicitTarget = accessorMatch?.[2];
155
+ if (!explicitTarget && accessorStartMatch) {
156
+ const { text: fullAnnotation, endIndex } = collectMultilineAnnotation(lines, i);
157
+ const explicitMatch = ACCESSOR_EXPLICIT_RE.exec(fullAnnotation);
158
+ if (explicitMatch) {
159
+ explicitTarget = explicitMatch[1];
160
+ }
161
+ i = endIndex;
162
+ }
163
+ // Find the method declaration following the annotation
164
+ let methodLine = i + 1;
165
+ while (methodLine < lines.length && /^\s*@/.test(lines[methodLine])) {
166
+ methodLine++;
167
+ }
168
+ if (methodLine < lines.length) {
169
+ const methodDeclMatch = METHOD_DECL_RE.exec(lines[methodLine]);
170
+ if (methodDeclMatch) {
171
+ const methodName = methodDeclMatch[2];
172
+ const targetName = explicitTarget ?? inferAccessorTarget(methodName);
173
+ accessors.push({ annotation, name: methodName, targetName, line: lineNum });
174
+ }
175
+ else {
176
+ parseWarnings.push(`Line ${lineNum}: Could not parse @${annotation} method declaration.`);
177
+ }
178
+ }
179
+ i = methodLine + 1;
180
+ continue;
181
+ }
182
+ i++;
183
+ }
184
+ return {
185
+ className,
186
+ targets,
187
+ priority,
188
+ injections,
189
+ shadows,
190
+ accessors,
191
+ parseWarnings
192
+ };
193
+ }
194
+ //# sourceMappingURL=mixin-parser.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Validation engine for parsed Mixin sources and Access Widener files.
3
+ * Compares parsed annotations against resolved Minecraft bytecode signatures.
4
+ */
5
+ import type { SignatureMember } from "./minecraft-explorer-service.js";
6
+ import type { ParsedMixin } from "./mixin-parser.js";
7
+ import type { ParsedAccessWidener, AccessWidenerEntry } from "./access-widener-parser.js";
8
+ export type ValidationIssue = {
9
+ severity: "error" | "warning";
10
+ kind: "target-not-found" | "method-not-found" | "field-not-found" | "descriptor-mismatch" | "access-mismatch" | "unknown-annotation";
11
+ annotation: string;
12
+ target: string;
13
+ message: string;
14
+ suggestions?: string[];
15
+ line?: number;
16
+ };
17
+ export type ValidationSummary = {
18
+ injections: number;
19
+ shadows: number;
20
+ accessors: number;
21
+ total: number;
22
+ errors: number;
23
+ warnings: number;
24
+ };
25
+ export type MixinValidationResult = {
26
+ className: string;
27
+ targets: string[];
28
+ priority?: number;
29
+ valid: boolean;
30
+ issues: ValidationIssue[];
31
+ summary: ValidationSummary;
32
+ warnings: string[];
33
+ };
34
+ export type ResolvedTargetMembers = {
35
+ className: string;
36
+ constructors: SignatureMember[];
37
+ methods: SignatureMember[];
38
+ fields: SignatureMember[];
39
+ };
40
+ export type AccessWidenerValidationResult = {
41
+ headerVersion: string;
42
+ namespace: string;
43
+ valid: boolean;
44
+ entries: Array<AccessWidenerEntry & {
45
+ valid: boolean;
46
+ issue?: string;
47
+ suggestions?: string[];
48
+ }>;
49
+ summary: {
50
+ total: number;
51
+ valid: number;
52
+ invalid: number;
53
+ };
54
+ warnings: string[];
55
+ };
56
+ export declare function levenshteinDistance(a: string, b: string): number;
57
+ export declare function suggestSimilar(name: string, candidates: string[], maxDistance?: number, maxResults?: number): string[];
58
+ export declare function validateParsedMixin(parsed: ParsedMixin, targetMembers: Map<string, ResolvedTargetMembers>, warnings: string[]): MixinValidationResult;
59
+ export declare function validateParsedAccessWidener(parsed: ParsedAccessWidener, membersByClass: Map<string, ResolvedTargetMembers>, warnings: string[]): AccessWidenerValidationResult;
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Validation engine for parsed Mixin sources and Access Widener files.
3
+ * Compares parsed annotations against resolved Minecraft bytecode signatures.
4
+ */
5
+ /* ------------------------------------------------------------------ */
6
+ /* Levenshtein distance */
7
+ /* ------------------------------------------------------------------ */
8
+ export function levenshteinDistance(a, b) {
9
+ const la = a.length;
10
+ const lb = b.length;
11
+ if (la === 0)
12
+ return lb;
13
+ if (lb === 0)
14
+ return la;
15
+ // Single-row DP
16
+ const prev = new Array(lb + 1);
17
+ for (let j = 0; j <= lb; j++)
18
+ prev[j] = j;
19
+ for (let i = 1; i <= la; i++) {
20
+ let diagPrev = prev[0];
21
+ prev[0] = i;
22
+ for (let j = 1; j <= lb; j++) {
23
+ const temp = prev[j];
24
+ if (a[i - 1] === b[j - 1]) {
25
+ prev[j] = diagPrev;
26
+ }
27
+ else {
28
+ prev[j] = 1 + Math.min(diagPrev, prev[j - 1], prev[j]);
29
+ }
30
+ diagPrev = temp;
31
+ }
32
+ }
33
+ return prev[lb];
34
+ }
35
+ export function suggestSimilar(name, candidates, maxDistance = 3, maxResults = 3) {
36
+ const scored = [];
37
+ for (const candidate of candidates) {
38
+ const distance = levenshteinDistance(name.toLowerCase(), candidate.toLowerCase());
39
+ if (distance <= maxDistance && distance > 0) {
40
+ scored.push({ candidate, distance });
41
+ }
42
+ }
43
+ scored.sort((a, b) => a.distance - b.distance);
44
+ return scored.slice(0, maxResults).map((s) => s.candidate);
45
+ }
46
+ /* ------------------------------------------------------------------ */
47
+ /* Mixin validation */
48
+ /* ------------------------------------------------------------------ */
49
+ function allMethodNames(members) {
50
+ return [
51
+ ...members.constructors.map((m) => m.name),
52
+ ...members.methods.map((m) => m.name)
53
+ ];
54
+ }
55
+ function allFieldNames(members) {
56
+ return members.fields.map((m) => m.name);
57
+ }
58
+ function allMemberNames(members) {
59
+ return [...allMethodNames(members), ...allFieldNames(members)];
60
+ }
61
+ function validateInjection(inj, targetMembers, targetNames, issues) {
62
+ for (const targetName of targetNames) {
63
+ const members = targetMembers.get(targetName);
64
+ if (!members)
65
+ continue;
66
+ const methodNames = allMethodNames(members);
67
+ // Support method references like "<init>" and simple names
68
+ const methodRef = inj.method.replace(/<init>/g, "<init>");
69
+ if (!methodNames.includes(methodRef)) {
70
+ const suggestions = suggestSimilar(methodRef, methodNames);
71
+ issues.push({
72
+ severity: "error",
73
+ kind: "method-not-found",
74
+ annotation: `@${inj.annotation}`,
75
+ target: `${targetName}#${inj.method}`,
76
+ message: `Method "${inj.method}" not found in target class "${targetName}".`,
77
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
78
+ line: inj.line
79
+ });
80
+ }
81
+ }
82
+ }
83
+ function validateShadow(shadow, targetMembers, targetNames, issues) {
84
+ for (const targetName of targetNames) {
85
+ const members = targetMembers.get(targetName);
86
+ if (!members)
87
+ continue;
88
+ if (shadow.kind === "field") {
89
+ const fieldNames = allFieldNames(members);
90
+ if (!fieldNames.includes(shadow.name)) {
91
+ const suggestions = suggestSimilar(shadow.name, fieldNames);
92
+ issues.push({
93
+ severity: "error",
94
+ kind: "field-not-found",
95
+ annotation: "@Shadow",
96
+ target: `${targetName}#${shadow.name}`,
97
+ message: `Field "${shadow.name}" not found in target class "${targetName}".`,
98
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
99
+ line: shadow.line
100
+ });
101
+ }
102
+ }
103
+ else {
104
+ const methodNames = allMethodNames(members);
105
+ if (!methodNames.includes(shadow.name)) {
106
+ const suggestions = suggestSimilar(shadow.name, methodNames);
107
+ issues.push({
108
+ severity: "error",
109
+ kind: "method-not-found",
110
+ annotation: "@Shadow",
111
+ target: `${targetName}#${shadow.name}`,
112
+ message: `Method "${shadow.name}" not found in target class "${targetName}".`,
113
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
114
+ line: shadow.line
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
120
+ function validateAccessor(accessor, targetMembers, targetNames, issues) {
121
+ for (const targetName of targetNames) {
122
+ const members = targetMembers.get(targetName);
123
+ if (!members)
124
+ continue;
125
+ const allNames = allMemberNames(members);
126
+ if (!allNames.includes(accessor.targetName)) {
127
+ const suggestions = suggestSimilar(accessor.targetName, allNames);
128
+ issues.push({
129
+ severity: "error",
130
+ kind: accessor.annotation === "Invoker" ? "method-not-found" : "field-not-found",
131
+ annotation: `@${accessor.annotation}`,
132
+ target: `${targetName}#${accessor.targetName}`,
133
+ message: `Target "${accessor.targetName}" (inferred from "${accessor.name}") not found in class "${targetName}".`,
134
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
135
+ line: accessor.line
136
+ });
137
+ }
138
+ }
139
+ }
140
+ export function validateParsedMixin(parsed, targetMembers, warnings) {
141
+ const issues = [];
142
+ const targetNames = parsed.targets.map((t) => t.className);
143
+ // Check target classes exist
144
+ for (const target of parsed.targets) {
145
+ if (!targetMembers.has(target.className)) {
146
+ issues.push({
147
+ severity: "error",
148
+ kind: "target-not-found",
149
+ annotation: "@Mixin",
150
+ target: target.className,
151
+ message: `Target class "${target.className}" not found in game jar.`
152
+ });
153
+ }
154
+ }
155
+ // Only validate members against targets that were resolved
156
+ const resolvedTargetNames = targetNames.filter((t) => targetMembers.has(t));
157
+ for (const inj of parsed.injections) {
158
+ validateInjection(inj, targetMembers, resolvedTargetNames, issues);
159
+ }
160
+ for (const shadow of parsed.shadows) {
161
+ validateShadow(shadow, targetMembers, resolvedTargetNames, issues);
162
+ }
163
+ for (const accessor of parsed.accessors) {
164
+ validateAccessor(accessor, targetMembers, resolvedTargetNames, issues);
165
+ }
166
+ // Add parse warnings
167
+ warnings.push(...parsed.parseWarnings);
168
+ const errorCount = issues.filter((i) => i.severity === "error").length;
169
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
170
+ return {
171
+ className: parsed.className,
172
+ targets: targetNames,
173
+ priority: parsed.priority,
174
+ valid: errorCount === 0,
175
+ issues,
176
+ summary: {
177
+ injections: parsed.injections.length,
178
+ shadows: parsed.shadows.length,
179
+ accessors: parsed.accessors.length,
180
+ total: parsed.injections.length + parsed.shadows.length + parsed.accessors.length,
181
+ errors: errorCount,
182
+ warnings: warningCount
183
+ },
184
+ warnings
185
+ };
186
+ }
187
+ /* ------------------------------------------------------------------ */
188
+ /* Access Widener validation */
189
+ /* ------------------------------------------------------------------ */
190
+ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
191
+ warnings.push(...parsed.parseWarnings);
192
+ const validatedEntries = [];
193
+ let validCount = 0;
194
+ let invalidCount = 0;
195
+ for (const entry of parsed.entries) {
196
+ const ownerFqn = entry.target.replace(/\//g, ".");
197
+ if (entry.targetKind === "class") {
198
+ if (membersByClass.has(ownerFqn)) {
199
+ validatedEntries.push({ ...entry, valid: true });
200
+ validCount++;
201
+ }
202
+ else {
203
+ validatedEntries.push({
204
+ ...entry,
205
+ valid: false,
206
+ issue: `Class "${ownerFqn}" not found in game jar.`
207
+ });
208
+ invalidCount++;
209
+ }
210
+ continue;
211
+ }
212
+ // method or field
213
+ const members = membersByClass.get(ownerFqn);
214
+ if (!members) {
215
+ validatedEntries.push({
216
+ ...entry,
217
+ valid: false,
218
+ issue: `Owner class "${ownerFqn}" not found in game jar.`
219
+ });
220
+ invalidCount++;
221
+ continue;
222
+ }
223
+ if (entry.targetKind === "method") {
224
+ const methodNames = allMethodNames(members);
225
+ const found = members.methods.some((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor)) || members.constructors.some((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
226
+ if (found) {
227
+ validatedEntries.push({ ...entry, valid: true });
228
+ validCount++;
229
+ }
230
+ else {
231
+ const suggestions = entry.name ? suggestSimilar(entry.name, methodNames) : [];
232
+ validatedEntries.push({
233
+ ...entry,
234
+ valid: false,
235
+ issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
236
+ suggestions: suggestions.length > 0 ? suggestions : undefined
237
+ });
238
+ invalidCount++;
239
+ }
240
+ }
241
+ else {
242
+ // field
243
+ const fieldNames = allFieldNames(members);
244
+ const found = members.fields.some((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
245
+ if (found) {
246
+ validatedEntries.push({ ...entry, valid: true });
247
+ validCount++;
248
+ }
249
+ else {
250
+ const suggestions = entry.name ? suggestSimilar(entry.name, fieldNames) : [];
251
+ validatedEntries.push({
252
+ ...entry,
253
+ valid: false,
254
+ issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
255
+ suggestions: suggestions.length > 0 ? suggestions : undefined
256
+ });
257
+ invalidCount++;
258
+ }
259
+ }
260
+ }
261
+ return {
262
+ headerVersion: parsed.headerVersion,
263
+ namespace: parsed.namespace,
264
+ valid: invalidCount === 0,
265
+ entries: validatedEntries,
266
+ summary: {
267
+ total: parsed.entries.length,
268
+ valid: validCount,
269
+ invalid: invalidCount
270
+ },
271
+ warnings
272
+ };
273
+ }
274
+ //# sourceMappingURL=mixin-validator.js.map
@@ -0,0 +1,23 @@
1
+ export type ModLoader = "fabric" | "quilt" | "forge" | "neoforge" | "unknown";
2
+ export interface ModDependency {
3
+ modId: string;
4
+ versionRange?: string;
5
+ kind: "required" | "optional" | "recommends" | "conflicts";
6
+ }
7
+ export interface ModAnalysisResult {
8
+ loader: ModLoader;
9
+ modId?: string;
10
+ modName?: string;
11
+ modVersion?: string;
12
+ description?: string;
13
+ entrypoints?: Record<string, string[]>;
14
+ mixinConfigs?: string[];
15
+ accessWidener?: string;
16
+ dependencies?: ModDependency[];
17
+ classCount: number;
18
+ classes?: string[];
19
+ }
20
+ export interface AnalyzeModOptions {
21
+ includeClasses?: boolean;
22
+ }
23
+ export declare function analyzeModJar(jarPath: string, options?: AnalyzeModOptions): Promise<ModAnalysisResult>;