@adhisang/minecraft-modding-mcp 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +139 -30
  3. package/dist/cache-registry.d.ts +95 -0
  4. package/dist/cache-registry.js +541 -0
  5. package/dist/cli.js +31 -4
  6. package/dist/compat-stdio-transport.d.ts +2 -7
  7. package/dist/compat-stdio-transport.js +12 -154
  8. package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
  9. package/dist/entry-tools/analyze-mod-service.js +253 -0
  10. package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
  11. package/dist/entry-tools/analyze-symbol-service.js +304 -0
  12. package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
  13. package/dist/entry-tools/compare-minecraft-service.js +397 -0
  14. package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
  15. package/dist/entry-tools/entry-tool-schema.js +10 -0
  16. package/dist/entry-tools/inspect-minecraft-service.d.ts +1953 -0
  17. package/dist/entry-tools/inspect-minecraft-service.js +876 -0
  18. package/dist/entry-tools/manage-cache-service.d.ts +130 -0
  19. package/dist/entry-tools/manage-cache-service.js +229 -0
  20. package/dist/entry-tools/request-normalizers.d.ts +10 -0
  21. package/dist/entry-tools/request-normalizers.js +36 -0
  22. package/dist/entry-tools/response-contract.d.ts +44 -0
  23. package/dist/entry-tools/response-contract.js +96 -0
  24. package/dist/entry-tools/validate-project-service.d.ts +543 -0
  25. package/dist/entry-tools/validate-project-service.js +381 -0
  26. package/dist/index.js +495 -42
  27. package/dist/json-rpc-framing.d.ts +22 -0
  28. package/dist/json-rpc-framing.js +168 -0
  29. package/dist/mapping-pipeline-service.js +9 -1
  30. package/dist/mapping-service.d.ts +9 -0
  31. package/dist/mapping-service.js +183 -60
  32. package/dist/minecraft-explorer-service.d.ts +0 -1
  33. package/dist/minecraft-explorer-service.js +119 -23
  34. package/dist/mixin-validator.d.ts +24 -2
  35. package/dist/mixin-validator.js +223 -98
  36. package/dist/mod-decompile-service.d.ts +5 -0
  37. package/dist/mod-decompile-service.js +40 -5
  38. package/dist/mod-remap-service.js +142 -30
  39. package/dist/path-resolver.js +41 -4
  40. package/dist/registry-service.d.ts +10 -1
  41. package/dist/registry-service.js +154 -22
  42. package/dist/search-hit-accumulator.js +23 -2
  43. package/dist/source-jar-reader.js +16 -2
  44. package/dist/source-resolver.js +6 -7
  45. package/dist/source-service.d.ts +42 -4
  46. package/dist/source-service.js +781 -127
  47. package/dist/stdio-supervisor.d.ts +46 -0
  48. package/dist/stdio-supervisor.js +349 -0
  49. package/dist/storage/files-repo.d.ts +3 -9
  50. package/dist/storage/files-repo.js +66 -43
  51. package/dist/symbols/symbol-extractor.js +6 -4
  52. package/dist/tool-execution-gate.d.ts +15 -0
  53. package/dist/tool-execution-gate.js +58 -0
  54. package/dist/version-diff-service.js +10 -5
  55. package/dist/version-service.js +7 -2
  56. package/dist/workspace-mapping-service.js +12 -0
  57. package/package.json +1 -1
@@ -1,29 +1,14 @@
1
1
  import process from "node:process";
2
- import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
3
- function findHeaderBoundary(buffer) {
4
- const crlfBoundary = buffer.indexOf("\r\n\r\n");
5
- if (crlfBoundary !== -1) {
6
- return { index: crlfBoundary, delimiterBytes: 4 };
7
- }
8
- const lfBoundary = buffer.indexOf("\n\n");
9
- if (lfBoundary !== -1) {
10
- return { index: lfBoundary, delimiterBytes: 2 };
11
- }
12
- return undefined;
13
- }
14
- function parseJsonRpcMessage(json) {
15
- return JSONRPCMessageSchema.parse(JSON.parse(json));
16
- }
2
+ import { JsonRpcFrameReader, encodeJsonRpcMessage } from "./json-rpc-framing.js";
17
3
  function asError(value) {
18
4
  return value instanceof Error ? value : new Error(String(value));
19
5
  }
20
6
  export class CompatStdioServerTransport {
21
7
  stdin;
22
8
  stdout;
9
+ frameReader = new JsonRpcFrameReader();
23
10
  started = false;
24
11
  closed = false;
25
- mode = "unknown";
26
- buffer = Buffer.alloc(0);
27
12
  onclose;
28
13
  onerror;
29
14
  onmessage;
@@ -43,10 +28,7 @@ export class CompatStdioServerTransport {
43
28
  this.stdin.resume();
44
29
  }
45
30
  async send(message) {
46
- const json = JSON.stringify(message);
47
- const frame = this.mode === "content-length"
48
- ? `Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`
49
- : `${json}\n`;
31
+ const frame = encodeJsonRpcMessage(message, this.frameReader.currentMode === "content-length" ? "content-length" : "line");
50
32
  await new Promise((resolve) => {
51
33
  if (this.stdout.write(frame)) {
52
34
  resolve();
@@ -63,15 +45,18 @@ export class CompatStdioServerTransport {
63
45
  if (this.stdin.listenerCount("data") === 0) {
64
46
  this.stdin.pause();
65
47
  }
66
- this.buffer = Buffer.alloc(0);
48
+ this.frameReader.clear();
67
49
  this.emitCloseOnce();
68
50
  }
69
51
  handleData = (chunk) => {
70
- if (chunk.length === 0) {
71
- return;
72
- }
73
- this.buffer = Buffer.concat([this.buffer, chunk]);
74
- this.processReadBuffer();
52
+ this.frameReader.processChunk(chunk, {
53
+ onFrame: ({ message }) => {
54
+ this.onmessage?.(message);
55
+ },
56
+ onError: (error) => {
57
+ this.onerror?.(error);
58
+ }
59
+ });
75
60
  };
76
61
  handleStreamError = (error) => {
77
62
  this.onerror?.(error);
@@ -86,132 +71,5 @@ export class CompatStdioServerTransport {
86
71
  this.closed = true;
87
72
  this.onclose?.();
88
73
  }
89
- processReadBuffer() {
90
- while (true) {
91
- try {
92
- if (this.mode === "unknown") {
93
- const detected = this.detectMode();
94
- if (!detected) {
95
- return;
96
- }
97
- this.mode = detected;
98
- continue;
99
- }
100
- const modeBefore = this.mode;
101
- const message = this.mode === "content-length"
102
- ? this.readContentLengthMessage()
103
- : this.readLineDelimitedMessage();
104
- if (!message) {
105
- // readLineDelimitedMessage may switch mode to "content-length"
106
- // mid-stream; retry with the new parser instead of stopping.
107
- if (this.mode !== modeBefore) {
108
- continue;
109
- }
110
- return;
111
- }
112
- this.onmessage?.(message);
113
- }
114
- catch (caughtError) {
115
- this.onerror?.(asError(caughtError));
116
- this.mode = "unknown";
117
- }
118
- }
119
- }
120
- detectMode() {
121
- // Skip blank leading lines that some clients may emit.
122
- while (this.buffer.length > 0) {
123
- if (this.buffer[0] === 0x0a) {
124
- this.buffer = this.buffer.subarray(1);
125
- continue;
126
- }
127
- if (this.buffer.length >= 2 && this.buffer[0] === 0x0d && this.buffer[1] === 0x0a) {
128
- this.buffer = this.buffer.subarray(2);
129
- continue;
130
- }
131
- break;
132
- }
133
- if (this.buffer.length === 0) {
134
- return undefined;
135
- }
136
- const prefix = this.buffer
137
- .subarray(0, Math.min(this.buffer.length, 32))
138
- .toString("utf8")
139
- .toLowerCase();
140
- if (prefix.startsWith("content-length")) {
141
- return "content-length";
142
- }
143
- const firstNewline = this.buffer.indexOf(0x0a);
144
- if (firstNewline === -1) {
145
- return undefined;
146
- }
147
- const firstLine = this.buffer.subarray(0, firstNewline).toString("utf8").replace(/\r$/, "");
148
- if (/^\s*content-length\s*:/i.test(firstLine)) {
149
- return "content-length";
150
- }
151
- return "line";
152
- }
153
- readLineDelimitedMessage() {
154
- while (true) {
155
- const newlineIndex = this.buffer.indexOf(0x0a);
156
- if (newlineIndex === -1) {
157
- return undefined;
158
- }
159
- const line = this.buffer.subarray(0, newlineIndex).toString("utf8").replace(/\r$/, "");
160
- this.buffer = this.buffer.subarray(newlineIndex + 1);
161
- if (line.trim().length === 0) {
162
- continue;
163
- }
164
- if (/^\s*content-length\s*:/i.test(line)) {
165
- // Reconstruct the header with the correct line ending so that
166
- // findHeaderBoundary can locate \r\n\r\n or \n\n reliably.
167
- const sep = this.buffer.length > 0 && this.buffer[0] === 0x0d ? "\r\n" : "\n";
168
- this.buffer = Buffer.concat([Buffer.from(`${line}${sep}`, "utf8"), this.buffer]);
169
- this.mode = "content-length";
170
- return undefined;
171
- }
172
- return parseJsonRpcMessage(line);
173
- }
174
- }
175
- readContentLengthMessage() {
176
- const headerBoundary = findHeaderBoundary(this.buffer);
177
- if (!headerBoundary) {
178
- return undefined;
179
- }
180
- const headersRaw = this.buffer.subarray(0, headerBoundary.index).toString("utf8");
181
- const headerLines = headersRaw
182
- .split(/\r?\n/)
183
- .map((line) => line.trim())
184
- .filter((line) => line.length > 0);
185
- let contentLength;
186
- for (const headerLine of headerLines) {
187
- const separatorIndex = headerLine.indexOf(":");
188
- if (separatorIndex === -1) {
189
- this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
190
- throw new Error(`Malformed header line: ${headerLine}`);
191
- }
192
- const headerName = headerLine.slice(0, separatorIndex).trim().toLowerCase();
193
- const headerValue = headerLine.slice(separatorIndex + 1).trim();
194
- if (headerName === "content-length") {
195
- const parsed = Number.parseInt(headerValue, 10);
196
- if (!Number.isFinite(parsed) || parsed < 0) {
197
- this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
198
- throw new Error(`Invalid Content-Length header value: ${headerValue}`);
199
- }
200
- contentLength = parsed;
201
- }
202
- }
203
- if (contentLength === undefined) {
204
- this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
205
- throw new Error("Missing Content-Length header.");
206
- }
207
- const messageStart = headerBoundary.index + headerBoundary.delimiterBytes;
208
- const frameEnd = messageStart + contentLength;
209
- if (this.buffer.length < frameEnd) {
210
- return undefined;
211
- }
212
- const body = this.buffer.subarray(messageStart, frameEnd).toString("utf8");
213
- this.buffer = this.buffer.subarray(frameEnd);
214
- return parseJsonRpcMessage(body);
215
- }
216
74
  }
217
75
  //# sourceMappingURL=compat-stdio-transport.js.map
@@ -0,0 +1,207 @@
1
+ import type { AnalyzeModOptions, ModAnalysisResult } from "../mod-analyzer.js";
2
+ import { z } from "zod";
3
+ export declare const analyzeModShape: {
4
+ task: z.ZodEnum<["summary", "decompile", "search", "class-source", "remap"]>;
5
+ subject: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
6
+ kind: z.ZodLiteral<"jar">;
7
+ jarPath: z.ZodString;
8
+ }, "strip", z.ZodTypeAny, {
9
+ kind: "jar";
10
+ jarPath: string;
11
+ }, {
12
+ kind: "jar";
13
+ jarPath: string;
14
+ }>, z.ZodObject<{
15
+ kind: z.ZodLiteral<"class">;
16
+ jarPath: z.ZodString;
17
+ className: z.ZodString;
18
+ }, "strip", z.ZodTypeAny, {
19
+ kind: "class";
20
+ jarPath: string;
21
+ className: string;
22
+ }, {
23
+ kind: "class";
24
+ jarPath: string;
25
+ className: string;
26
+ }>]>;
27
+ query: z.ZodOptional<z.ZodString>;
28
+ searchType: z.ZodOptional<z.ZodEnum<["class", "method", "field", "content", "all"]>>;
29
+ limit: z.ZodOptional<z.ZodNumber>;
30
+ includeFiles: z.ZodOptional<z.ZodBoolean>;
31
+ maxFiles: z.ZodOptional<z.ZodNumber>;
32
+ maxLines: z.ZodOptional<z.ZodNumber>;
33
+ maxChars: z.ZodOptional<z.ZodNumber>;
34
+ targetMapping: z.ZodOptional<z.ZodEnum<["yarn", "mojang"]>>;
35
+ outputJar: z.ZodOptional<z.ZodString>;
36
+ executionMode: z.ZodOptional<z.ZodEnum<["preview", "apply"]>>;
37
+ detail: z.ZodOptional<z.ZodEnum<["summary", "standard", "full"]>>;
38
+ include: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
39
+ };
40
+ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
41
+ task: z.ZodEnum<["summary", "decompile", "search", "class-source", "remap"]>;
42
+ subject: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
43
+ kind: z.ZodLiteral<"jar">;
44
+ jarPath: z.ZodString;
45
+ }, "strip", z.ZodTypeAny, {
46
+ kind: "jar";
47
+ jarPath: string;
48
+ }, {
49
+ kind: "jar";
50
+ jarPath: string;
51
+ }>, z.ZodObject<{
52
+ kind: z.ZodLiteral<"class">;
53
+ jarPath: z.ZodString;
54
+ className: z.ZodString;
55
+ }, "strip", z.ZodTypeAny, {
56
+ kind: "class";
57
+ jarPath: string;
58
+ className: string;
59
+ }, {
60
+ kind: "class";
61
+ jarPath: string;
62
+ className: string;
63
+ }>]>;
64
+ query: z.ZodOptional<z.ZodString>;
65
+ searchType: z.ZodOptional<z.ZodEnum<["class", "method", "field", "content", "all"]>>;
66
+ limit: z.ZodOptional<z.ZodNumber>;
67
+ includeFiles: z.ZodOptional<z.ZodBoolean>;
68
+ maxFiles: z.ZodOptional<z.ZodNumber>;
69
+ maxLines: z.ZodOptional<z.ZodNumber>;
70
+ maxChars: z.ZodOptional<z.ZodNumber>;
71
+ targetMapping: z.ZodOptional<z.ZodEnum<["yarn", "mojang"]>>;
72
+ outputJar: z.ZodOptional<z.ZodString>;
73
+ executionMode: z.ZodOptional<z.ZodEnum<["preview", "apply"]>>;
74
+ detail: z.ZodOptional<z.ZodEnum<["summary", "standard", "full"]>>;
75
+ include: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
76
+ }, "strip", z.ZodTypeAny, {
77
+ task: "search" | "remap" | "summary" | "class-source" | "decompile";
78
+ subject: {
79
+ kind: "jar";
80
+ jarPath: string;
81
+ } | {
82
+ kind: "class";
83
+ jarPath: string;
84
+ className: string;
85
+ };
86
+ limit?: number | undefined;
87
+ maxLines?: number | undefined;
88
+ maxChars?: number | undefined;
89
+ targetMapping?: "mojang" | "yarn" | undefined;
90
+ outputJar?: string | undefined;
91
+ query?: string | undefined;
92
+ searchType?: "class" | "method" | "field" | "all" | "content" | undefined;
93
+ detail?: "full" | "summary" | "standard" | undefined;
94
+ include?: string[] | undefined;
95
+ includeFiles?: boolean | undefined;
96
+ maxFiles?: number | undefined;
97
+ executionMode?: "preview" | "apply" | undefined;
98
+ }, {
99
+ task: "search" | "remap" | "summary" | "class-source" | "decompile";
100
+ subject: {
101
+ kind: "jar";
102
+ jarPath: string;
103
+ } | {
104
+ kind: "class";
105
+ jarPath: string;
106
+ className: string;
107
+ };
108
+ limit?: number | undefined;
109
+ maxLines?: number | undefined;
110
+ maxChars?: number | undefined;
111
+ targetMapping?: "mojang" | "yarn" | undefined;
112
+ outputJar?: string | undefined;
113
+ query?: string | undefined;
114
+ searchType?: "class" | "method" | "field" | "all" | "content" | undefined;
115
+ detail?: "full" | "summary" | "standard" | undefined;
116
+ include?: string[] | undefined;
117
+ includeFiles?: boolean | undefined;
118
+ maxFiles?: number | undefined;
119
+ executionMode?: "preview" | "apply" | undefined;
120
+ }>, {
121
+ task: "search" | "remap" | "summary" | "class-source" | "decompile";
122
+ subject: {
123
+ kind: "jar";
124
+ jarPath: string;
125
+ } | {
126
+ kind: "class";
127
+ jarPath: string;
128
+ className: string;
129
+ };
130
+ limit?: number | undefined;
131
+ maxLines?: number | undefined;
132
+ maxChars?: number | undefined;
133
+ targetMapping?: "mojang" | "yarn" | undefined;
134
+ outputJar?: string | undefined;
135
+ query?: string | undefined;
136
+ searchType?: "class" | "method" | "field" | "all" | "content" | undefined;
137
+ detail?: "full" | "summary" | "standard" | undefined;
138
+ include?: string[] | undefined;
139
+ includeFiles?: boolean | undefined;
140
+ maxFiles?: number | undefined;
141
+ executionMode?: "preview" | "apply" | undefined;
142
+ }, {
143
+ task: "search" | "remap" | "summary" | "class-source" | "decompile";
144
+ subject: {
145
+ kind: "jar";
146
+ jarPath: string;
147
+ } | {
148
+ kind: "class";
149
+ jarPath: string;
150
+ className: string;
151
+ };
152
+ limit?: number | undefined;
153
+ maxLines?: number | undefined;
154
+ maxChars?: number | undefined;
155
+ targetMapping?: "mojang" | "yarn" | undefined;
156
+ outputJar?: string | undefined;
157
+ query?: string | undefined;
158
+ searchType?: "class" | "method" | "field" | "all" | "content" | undefined;
159
+ detail?: "full" | "summary" | "standard" | undefined;
160
+ include?: string[] | undefined;
161
+ includeFiles?: boolean | undefined;
162
+ maxFiles?: number | undefined;
163
+ executionMode?: "preview" | "apply" | undefined;
164
+ }>;
165
+ export type AnalyzeModInput = z.infer<typeof analyzeModSchema>;
166
+ type AnalyzeModDeps = {
167
+ analyzeModJar: (jarPath: string, options?: AnalyzeModOptions) => Promise<ModAnalysisResult>;
168
+ decompileModJar: (input: {
169
+ jarPath: string;
170
+ includeFiles?: boolean;
171
+ maxFiles?: number;
172
+ }) => Promise<Record<string, unknown> & {
173
+ warnings?: string[];
174
+ }>;
175
+ getModClassSource: (input: {
176
+ jarPath: string;
177
+ className: string;
178
+ maxLines?: number;
179
+ maxChars?: number;
180
+ }) => Promise<Record<string, unknown> & {
181
+ warnings?: string[];
182
+ }>;
183
+ searchModSource: (input: {
184
+ jarPath: string;
185
+ query: string;
186
+ searchType?: "class" | "method" | "field" | "content" | "all";
187
+ limit?: number;
188
+ }) => Promise<Record<string, unknown> & {
189
+ warnings?: string[];
190
+ }>;
191
+ remapModJar: (input: {
192
+ inputJar: string;
193
+ outputJar?: string;
194
+ mcVersion?: string;
195
+ targetMapping: "yarn" | "mojang";
196
+ }) => Promise<Record<string, unknown> & {
197
+ warnings?: string[];
198
+ }>;
199
+ };
200
+ export declare class AnalyzeModService {
201
+ private readonly deps;
202
+ constructor(deps: AnalyzeModDeps);
203
+ execute(input: AnalyzeModInput): Promise<Record<string, unknown> & {
204
+ warnings?: string[];
205
+ }>;
206
+ }
207
+ export {};
@@ -0,0 +1,253 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { normalizePathForHost } from "../path-converter.js";
4
+ import { createError, ERROR_CODES } from "../errors.js";
5
+ import { z } from "zod";
6
+ import { buildIncludeSchema, detailSchema, executionModeSchema, positiveIntSchema } from "./entry-tool-schema.js";
7
+ import { buildEntryToolResult } from "./response-contract.js";
8
+ import { resolveDetail, resolveInclude } from "./request-normalizers.js";
9
+ const nonEmptyString = z.string().trim().min(1);
10
+ const INCLUDE_GROUPS = ["warnings", "files", "source", "samples", "timings"];
11
+ const subjectSchema = z.discriminatedUnion("kind", [
12
+ z.object({
13
+ kind: z.literal("jar"),
14
+ jarPath: nonEmptyString
15
+ }),
16
+ z.object({
17
+ kind: z.literal("class"),
18
+ jarPath: nonEmptyString,
19
+ className: nonEmptyString
20
+ })
21
+ ]);
22
+ export const analyzeModShape = {
23
+ task: z.enum(["summary", "decompile", "search", "class-source", "remap"]),
24
+ subject: subjectSchema,
25
+ query: nonEmptyString.optional(),
26
+ searchType: z.enum(["class", "method", "field", "content", "all"]).optional(),
27
+ limit: positiveIntSchema.optional(),
28
+ includeFiles: z.boolean().optional(),
29
+ maxFiles: positiveIntSchema.optional(),
30
+ maxLines: positiveIntSchema.optional(),
31
+ maxChars: positiveIntSchema.optional(),
32
+ targetMapping: z.enum(["yarn", "mojang"]).optional(),
33
+ outputJar: nonEmptyString.optional(),
34
+ executionMode: executionModeSchema.optional(),
35
+ detail: detailSchema.optional(),
36
+ include: buildIncludeSchema(INCLUDE_GROUPS)
37
+ };
38
+ export const analyzeModSchema = z.object(analyzeModShape).superRefine((value, ctx) => {
39
+ if ((value.task === "summary" || value.task === "decompile" || value.task === "search" || value.task === "remap") && value.subject.kind !== "jar") {
40
+ ctx.addIssue({
41
+ code: z.ZodIssueCode.custom,
42
+ path: ["subject", "kind"],
43
+ message: `${value.task} requires subject.kind=jar.`
44
+ });
45
+ }
46
+ if (value.task === "class-source" && value.subject.kind !== "class") {
47
+ ctx.addIssue({
48
+ code: z.ZodIssueCode.custom,
49
+ path: ["subject", "kind"],
50
+ message: "class-source requires subject.kind=class."
51
+ });
52
+ }
53
+ if (value.task === "search" && !value.query) {
54
+ ctx.addIssue({
55
+ code: z.ZodIssueCode.custom,
56
+ path: ["query"],
57
+ message: "search requires query."
58
+ });
59
+ }
60
+ if (value.task === "remap" && !value.targetMapping) {
61
+ ctx.addIssue({
62
+ code: z.ZodIssueCode.custom,
63
+ path: ["targetMapping"],
64
+ message: "remap requires targetMapping."
65
+ });
66
+ }
67
+ });
68
+ function deriveOutputJar(inputJar, analysis, targetMapping) {
69
+ return join(dirname(inputJar), `${analysis.modId ?? "mod"}-${analysis.modVersion ?? "0"}-${targetMapping}.jar`);
70
+ }
71
+ export class AnalyzeModService {
72
+ deps;
73
+ constructor(deps) {
74
+ this.deps = deps;
75
+ }
76
+ async execute(input) {
77
+ const detail = resolveDetail(input.detail);
78
+ const include = resolveInclude(input.include);
79
+ switch (input.task) {
80
+ case "summary": {
81
+ const analysis = await this.deps.analyzeModJar(input.subject.jarPath, {
82
+ includeClasses: detail !== "summary" || include.includes("files")
83
+ });
84
+ return {
85
+ ...buildEntryToolResult({
86
+ task: "summary",
87
+ detail,
88
+ include,
89
+ summary: {
90
+ status: "ok",
91
+ headline: `Summarized ${analysis.loader} mod metadata.`,
92
+ counts: {
93
+ classes: analysis.classCount,
94
+ dependencies: analysis.dependencies?.length ?? 0
95
+ }
96
+ },
97
+ blocks: {
98
+ metadata: analysis
99
+ }
100
+ }),
101
+ warnings: []
102
+ };
103
+ }
104
+ case "decompile": {
105
+ const output = await this.deps.decompileModJar({
106
+ jarPath: input.subject.jarPath,
107
+ includeFiles: input.includeFiles,
108
+ maxFiles: input.maxFiles
109
+ });
110
+ return {
111
+ ...buildEntryToolResult({
112
+ task: "decompile",
113
+ detail,
114
+ include,
115
+ summary: {
116
+ status: "ok",
117
+ headline: `Decompiled ${input.subject.jarPath}.`
118
+ },
119
+ blocks: {
120
+ decompile: output
121
+ }
122
+ }),
123
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
124
+ };
125
+ }
126
+ case "search": {
127
+ const output = await this.deps.searchModSource({
128
+ jarPath: input.subject.jarPath,
129
+ query: input.query,
130
+ searchType: input.searchType,
131
+ limit: input.limit
132
+ });
133
+ return {
134
+ ...buildEntryToolResult({
135
+ task: "search",
136
+ detail,
137
+ include,
138
+ summary: {
139
+ status: "ok",
140
+ headline: `Searched ${input.subject.jarPath} for ${input.query}.`
141
+ },
142
+ blocks: {
143
+ hits: output
144
+ }
145
+ }),
146
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
147
+ };
148
+ }
149
+ case "class-source": {
150
+ if (input.subject.kind !== "class") {
151
+ throw createError({
152
+ code: ERROR_CODES.INVALID_INPUT,
153
+ message: "class-source requires subject.kind=class."
154
+ });
155
+ }
156
+ const output = await this.deps.getModClassSource({
157
+ jarPath: input.subject.jarPath,
158
+ className: input.subject.className,
159
+ maxLines: input.maxLines,
160
+ maxChars: input.maxChars
161
+ });
162
+ return {
163
+ ...buildEntryToolResult({
164
+ task: "class-source",
165
+ detail,
166
+ include,
167
+ summary: {
168
+ status: "ok",
169
+ headline: `Loaded class source for ${input.subject.className}.`
170
+ },
171
+ blocks: {
172
+ source: output
173
+ }
174
+ }),
175
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
176
+ };
177
+ }
178
+ case "remap": {
179
+ const normalizedInputJar = normalizePathForHost(input.subject.jarPath, undefined, "jarPath");
180
+ const analysis = await this.deps.analyzeModJar(normalizedInputJar);
181
+ const outputJar = input.outputJar
182
+ ? normalizePathForHost(input.outputJar, undefined, "outputJar")
183
+ : deriveOutputJar(normalizedInputJar, analysis, input.targetMapping);
184
+ if (outputJar === normalizedInputJar) {
185
+ throw createError({
186
+ code: ERROR_CODES.INVALID_INPUT,
187
+ message: "outputJar must differ from the input jar."
188
+ });
189
+ }
190
+ if ((input.executionMode ?? "preview") === "apply" && existsSync(outputJar)) {
191
+ throw createError({
192
+ code: ERROR_CODES.INVALID_INPUT,
193
+ message: "outputJar already exists. Choose a new destination."
194
+ });
195
+ }
196
+ if ((input.executionMode ?? "preview") === "preview") {
197
+ return {
198
+ ...buildEntryToolResult({
199
+ task: "remap",
200
+ detail,
201
+ include,
202
+ summary: {
203
+ status: "unchanged",
204
+ headline: `Previewed remap output for ${normalizedInputJar}.`
205
+ },
206
+ blocks: {
207
+ metadata: {
208
+ loader: analysis.loader,
209
+ modId: analysis.modId,
210
+ modVersion: analysis.modVersion
211
+ },
212
+ operation: {
213
+ executionMode: "preview",
214
+ outputJar,
215
+ targetMapping: input.targetMapping
216
+ }
217
+ },
218
+ alwaysBlocks: ["operation"]
219
+ }),
220
+ warnings: []
221
+ };
222
+ }
223
+ const output = await this.deps.remapModJar({
224
+ inputJar: normalizedInputJar,
225
+ outputJar,
226
+ targetMapping: input.targetMapping
227
+ });
228
+ return {
229
+ ...buildEntryToolResult({
230
+ task: "remap",
231
+ detail,
232
+ include,
233
+ summary: {
234
+ status: "changed",
235
+ headline: `Remapped ${normalizedInputJar} to ${input.targetMapping}.`
236
+ },
237
+ blocks: {
238
+ operation: {
239
+ executionMode: "apply",
240
+ outputJar,
241
+ targetMapping: input.targetMapping
242
+ },
243
+ metadata: output
244
+ },
245
+ alwaysBlocks: ["operation"]
246
+ }),
247
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
248
+ };
249
+ }
250
+ }
251
+ }
252
+ }
253
+ //# sourceMappingURL=analyze-mod-service.js.map