@reverse-craft/smart-fs 1.0.6 → 2.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 (40) hide show
  1. package/README.md +160 -13
  2. package/dist/analyzer.d.ts +91 -0
  3. package/dist/analyzer.d.ts.map +1 -0
  4. package/dist/beautifier.d.ts +120 -0
  5. package/dist/beautifier.d.ts.map +1 -0
  6. package/dist/index.d.ts +102 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +1267 -0
  9. package/dist/index.js.map +7 -0
  10. package/dist/languageDetector.d.ts +74 -0
  11. package/dist/languageDetector.d.ts.map +1 -0
  12. package/dist/llmConfig.d.ts +34 -0
  13. package/dist/llmConfig.d.ts.map +1 -0
  14. package/dist/searcher.d.ts +113 -0
  15. package/dist/searcher.d.ts.map +1 -0
  16. package/dist/server.d.ts +2 -0
  17. package/dist/server.d.ts.map +1 -0
  18. package/dist/server.js +571 -78
  19. package/dist/server.js.map +4 -4
  20. package/dist/tools/ToolDefinition.d.ts +24 -0
  21. package/dist/tools/ToolDefinition.d.ts.map +1 -0
  22. package/dist/tools/aiFindJsvmpDispatcher.d.ts +79 -0
  23. package/dist/tools/aiFindJsvmpDispatcher.d.ts.map +1 -0
  24. package/dist/tools/applyCustomTransform.d.ts +14 -0
  25. package/dist/tools/applyCustomTransform.d.ts.map +1 -0
  26. package/dist/tools/findUsageSmart.d.ts +16 -0
  27. package/dist/tools/findUsageSmart.d.ts.map +1 -0
  28. package/dist/tools/index.d.ts +43 -0
  29. package/dist/tools/index.d.ts.map +1 -0
  30. package/dist/tools/readCodeSmart.d.ts +13 -0
  31. package/dist/tools/readCodeSmart.d.ts.map +1 -0
  32. package/dist/tools/searchCodeSmart.d.ts +18 -0
  33. package/dist/tools/searchCodeSmart.d.ts.map +1 -0
  34. package/dist/transformer.d.ts +119 -0
  35. package/dist/transformer.d.ts.map +1 -0
  36. package/dist/truncator.d.ts +69 -0
  37. package/dist/truncator.d.ts.map +1 -0
  38. package/dist/types.d.ts +61 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/package.json +16 -16
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/server.ts
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z as z5 } from "zod";
6
+ import { z as z6 } from "zod";
7
7
 
8
8
  // src/tools/readCodeSmart.ts
9
9
  import { z } from "zod";
@@ -18,9 +18,82 @@ function defineTool(definition) {
18
18
  import * as esbuild from "esbuild";
19
19
  import * as crypto from "crypto";
20
20
  import * as fs from "fs/promises";
21
- import * as path from "path";
21
+ import * as path2 from "path";
22
22
  import * as os from "os";
23
- var TEMP_DIR = path.join(os.tmpdir(), "smart-fs-mcp-cache");
23
+
24
+ // src/languageDetector.ts
25
+ import * as path from "path";
26
+ var LANGUAGE_CONFIG = {
27
+ javascript: {
28
+ language: "javascript",
29
+ supportsAST: true,
30
+ supportsBeautify: true,
31
+ supportsSourceMap: true
32
+ },
33
+ typescript: {
34
+ language: "typescript",
35
+ supportsAST: true,
36
+ supportsBeautify: true,
37
+ supportsSourceMap: true
38
+ },
39
+ json: {
40
+ language: "json",
41
+ supportsAST: false,
42
+ supportsBeautify: true,
43
+ supportsSourceMap: false
44
+ },
45
+ html: {
46
+ language: "html",
47
+ supportsAST: false,
48
+ supportsBeautify: true,
49
+ supportsSourceMap: false
50
+ },
51
+ xml: {
52
+ language: "xml",
53
+ supportsAST: false,
54
+ supportsBeautify: true,
55
+ supportsSourceMap: false
56
+ },
57
+ css: {
58
+ language: "css",
59
+ supportsAST: false,
60
+ supportsBeautify: true,
61
+ supportsSourceMap: false
62
+ },
63
+ unknown: {
64
+ language: "unknown",
65
+ supportsAST: false,
66
+ supportsBeautify: false,
67
+ supportsSourceMap: false
68
+ }
69
+ };
70
+ var EXTENSION_MAP = {
71
+ ".js": "javascript",
72
+ ".mjs": "javascript",
73
+ ".cjs": "javascript",
74
+ ".jsx": "javascript",
75
+ ".ts": "typescript",
76
+ ".tsx": "typescript",
77
+ ".mts": "typescript",
78
+ ".cts": "typescript",
79
+ ".json": "json",
80
+ ".html": "html",
81
+ ".htm": "html",
82
+ ".xml": "xml",
83
+ ".svg": "xml",
84
+ ".css": "css"
85
+ };
86
+ function detectLanguage(filePath) {
87
+ const ext = path.extname(filePath).toLowerCase();
88
+ const language = EXTENSION_MAP[ext] ?? "unknown";
89
+ return LANGUAGE_CONFIG[language];
90
+ }
91
+ function getLanguageInfo(language) {
92
+ return LANGUAGE_CONFIG[language];
93
+ }
94
+
95
+ // src/beautifier.ts
96
+ var TEMP_DIR = path2.join(os.tmpdir(), "smart-fs-mcp-cache");
24
97
  async function ensureCacheDir() {
25
98
  await fs.mkdir(TEMP_DIR, { recursive: true });
26
99
  }
@@ -29,17 +102,17 @@ function calculateCacheKey(originalPath, mtimeMs) {
29
102
  return crypto.createHash("md5").update(fileKey).digest("hex");
30
103
  }
31
104
  function getCachePaths(originalPath, hash) {
32
- const fileName = path.basename(originalPath, ".js");
33
- const beautifiedPath = path.join(TEMP_DIR, `${fileName}.${hash}.beautified.js`);
105
+ const fileName = path2.basename(originalPath, ".js");
106
+ const beautifiedPath = path2.join(TEMP_DIR, `${fileName}.${hash}.beautified.js`);
34
107
  const mapPath = `${beautifiedPath}.map`;
35
108
  return { beautifiedPath, mapPath };
36
109
  }
37
110
  function getLocalPaths(originalPath) {
38
- const absolutePath = path.resolve(originalPath);
39
- const dir = path.dirname(absolutePath);
40
- const ext = path.extname(absolutePath);
41
- const baseName = path.basename(absolutePath, ext);
42
- const beautifiedPath = path.join(dir, `${baseName}.beautified.js`);
111
+ const absolutePath = path2.resolve(originalPath);
112
+ const dir = path2.dirname(absolutePath);
113
+ const ext = path2.extname(absolutePath);
114
+ const baseName = path2.basename(absolutePath, ext);
115
+ const beautifiedPath = path2.join(dir, `${baseName}.beautified.js`);
43
116
  const mapPath = `${beautifiedPath}.map`;
44
117
  return { beautifiedPath, mapPath };
45
118
  }
@@ -55,7 +128,7 @@ async function isCacheValid(beautifiedPath, mapPath) {
55
128
  }
56
129
  }
57
130
  async function isLocalCacheValid(originalPath) {
58
- const absolutePath = path.resolve(originalPath);
131
+ const absolutePath = path2.resolve(originalPath);
59
132
  const { beautifiedPath } = getLocalPaths(absolutePath);
60
133
  let originalStats;
61
134
  try {
@@ -89,15 +162,122 @@ async function isLocalCacheValid(originalPath) {
89
162
  isValid
90
163
  };
91
164
  }
165
+ function beautifyJson(content) {
166
+ try {
167
+ const parsed = JSON.parse(content);
168
+ return { code: JSON.stringify(parsed, null, 2), parseFailed: false };
169
+ } catch {
170
+ return { code: content, parseFailed: true };
171
+ }
172
+ }
173
+ function beautifyHtml(content) {
174
+ try {
175
+ let formatted = "";
176
+ let indent = 0;
177
+ const indentStr = " ";
178
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
179
+ const tokens = normalized.split(/(<[^>]+>)/g).filter((token) => token.trim() !== "");
180
+ for (const token of tokens) {
181
+ const trimmedToken = token.trim();
182
+ if (!trimmedToken) continue;
183
+ if (trimmedToken.startsWith("<")) {
184
+ if (trimmedToken.startsWith("<!") || trimmedToken.startsWith("<?") || trimmedToken.endsWith("/>")) {
185
+ formatted += indentStr.repeat(indent) + trimmedToken + "\n";
186
+ } else if (trimmedToken.startsWith("</")) {
187
+ indent = Math.max(0, indent - 1);
188
+ formatted += indentStr.repeat(indent) + trimmedToken + "\n";
189
+ } else {
190
+ formatted += indentStr.repeat(indent) + trimmedToken + "\n";
191
+ indent++;
192
+ }
193
+ } else {
194
+ const textLines = trimmedToken.split("\n");
195
+ for (const line of textLines) {
196
+ const trimmedLine = line.trim();
197
+ if (trimmedLine) {
198
+ formatted += indentStr.repeat(indent) + trimmedLine + "\n";
199
+ }
200
+ }
201
+ }
202
+ }
203
+ return formatted.trimEnd();
204
+ } catch {
205
+ return content;
206
+ }
207
+ }
208
+ function beautifyCss(content) {
209
+ try {
210
+ let formatted = content;
211
+ formatted = formatted.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
212
+ formatted = formatted.replace(/\{/g, " {\n");
213
+ formatted = formatted.replace(/\}/g, "\n}\n");
214
+ formatted = formatted.replace(/;/g, ";\n");
215
+ formatted = formatted.replace(/\n\s*\n/g, "\n");
216
+ const lines = formatted.split("\n");
217
+ let indent = 0;
218
+ const indentStr = " ";
219
+ const result = [];
220
+ for (const line of lines) {
221
+ const trimmed = line.trim();
222
+ if (!trimmed) continue;
223
+ if (trimmed.startsWith("}")) {
224
+ indent = Math.max(0, indent - 1);
225
+ }
226
+ result.push(indentStr.repeat(indent) + trimmed);
227
+ if (trimmed.endsWith("{")) {
228
+ indent++;
229
+ }
230
+ }
231
+ return result.join("\n");
232
+ } catch {
233
+ return content;
234
+ }
235
+ }
236
+ function beautifyCode(code, language) {
237
+ const langInfo = getLanguageInfo(language);
238
+ if (!langInfo.supportsBeautify) {
239
+ return { code, usedFallback: true };
240
+ }
241
+ switch (language) {
242
+ case "json": {
243
+ const result = beautifyJson(code);
244
+ return { code: result.code, usedFallback: result.parseFailed };
245
+ }
246
+ case "html":
247
+ case "xml":
248
+ return { code: beautifyHtml(code), usedFallback: false };
249
+ case "css":
250
+ return { code: beautifyCss(code), usedFallback: false };
251
+ case "javascript":
252
+ case "typescript":
253
+ return { code, usedFallback: true };
254
+ default:
255
+ return { code, usedFallback: true };
256
+ }
257
+ }
92
258
  async function ensureBeautified(originalPath, options) {
93
- const absolutePath = path.resolve(originalPath);
259
+ const absolutePath = path2.resolve(originalPath);
94
260
  let stats;
95
261
  try {
96
262
  stats = await fs.stat(absolutePath);
97
263
  } catch {
98
264
  throw new Error(`File not found: ${originalPath}`);
99
265
  }
266
+ const langInfo = options?.language ? getLanguageInfo(options.language) : detectLanguage(absolutePath);
267
+ const language = langInfo.language;
100
268
  const localPaths = getLocalPaths(absolutePath);
269
+ if (language !== "javascript" && language !== "typescript") {
270
+ const content = await fs.readFile(absolutePath, "utf-8");
271
+ const beautified = beautifyCode(content, language);
272
+ return {
273
+ code: beautified.code,
274
+ rawMap: null,
275
+ // No source map for non-JS/TS
276
+ localPath: localPaths.beautifiedPath,
277
+ localMapPath: localPaths.mapPath,
278
+ usedFallback: beautified.usedFallback
279
+ };
280
+ }
101
281
  const localCacheCheck = await isLocalCacheValid(absolutePath);
102
282
  if (localCacheCheck.isValid) {
103
283
  try {
@@ -109,7 +289,8 @@ async function ensureBeautified(originalPath, options) {
109
289
  code: code2,
110
290
  rawMap: JSON.parse(mapContent),
111
291
  localPath: localPaths.beautifiedPath,
112
- localMapPath: localPaths.mapPath
292
+ localMapPath: localPaths.mapPath,
293
+ usedFallback: false
113
294
  };
114
295
  } catch {
115
296
  }
@@ -126,7 +307,8 @@ async function ensureBeautified(originalPath, options) {
126
307
  code: code2,
127
308
  rawMap: JSON.parse(mapContent),
128
309
  localPath: localPaths.beautifiedPath,
129
- localMapPath: localPaths.mapPath
310
+ localMapPath: localPaths.mapPath,
311
+ usedFallback: false
130
312
  };
131
313
  await saveToLocal(result2, localPaths, mapContent);
132
314
  return result2;
@@ -147,13 +329,26 @@ async function ensureBeautified(originalPath, options) {
147
329
  treeShaking: false
148
330
  });
149
331
  } catch (err) {
150
- const message = err instanceof Error ? err.message : String(err);
151
- throw new Error(`Esbuild processing failed: ${message}`);
332
+ const content = await fs.readFile(absolutePath, "utf-8");
333
+ return {
334
+ code: content,
335
+ rawMap: null,
336
+ localPath: localPaths.beautifiedPath,
337
+ localMapPath: localPaths.mapPath,
338
+ usedFallback: true
339
+ };
152
340
  }
153
341
  const codeFile = esbuildResult.outputFiles?.find((f) => f.path.endsWith(".js"));
154
342
  const mapFile = esbuildResult.outputFiles?.find((f) => f.path.endsWith(".map"));
155
343
  if (!codeFile || !mapFile) {
156
- throw new Error("Esbuild processing failed: Missing output files");
344
+ const content = await fs.readFile(absolutePath, "utf-8");
345
+ return {
346
+ code: content,
347
+ rawMap: null,
348
+ localPath: localPaths.beautifiedPath,
349
+ localMapPath: localPaths.mapPath,
350
+ usedFallback: true
351
+ };
157
352
  }
158
353
  const code = codeFile.text;
159
354
  const rawMap = JSON.parse(mapFile.text);
@@ -166,7 +361,8 @@ async function ensureBeautified(originalPath, options) {
166
361
  code,
167
362
  rawMap,
168
363
  localPath: localPaths.beautifiedPath,
169
- localMapPath: localPaths.mapPath
364
+ localMapPath: localPaths.mapPath,
365
+ usedFallback: false
170
366
  };
171
367
  await saveToLocal(result, localPaths, mapText);
172
368
  return result;
@@ -182,9 +378,9 @@ async function saveToLocal(result, localPaths, mapText) {
182
378
  } catch (err) {
183
379
  const error = err;
184
380
  if (error.code === "EACCES" || error.code === "EPERM") {
185
- result.localSaveError = `Permission denied: Cannot write to ${path.dirname(localPaths.beautifiedPath)}`;
381
+ result.localSaveError = `Permission denied: Cannot write to ${path2.dirname(localPaths.beautifiedPath)}`;
186
382
  } else if (error.code === "ENOSPC") {
187
- result.localSaveError = `Insufficient disk space: Cannot write to ${path.dirname(localPaths.beautifiedPath)}`;
383
+ result.localSaveError = `Insufficient disk space: Cannot write to ${path2.dirname(localPaths.beautifiedPath)}`;
188
384
  } else {
189
385
  result.localSaveError = `Failed to save locally: ${error.message || String(err)}`;
190
386
  }
@@ -213,7 +409,7 @@ function createTruncatedString(original, previewLength) {
213
409
  const newlineStr = "\n".repeat(preservedNewlines);
214
410
  return `${start}${marker}${newlineStr}${end}`;
215
411
  }
216
- function truncateCodeHighPerf(sourceCode, limit = 200, previewLength = 50) {
412
+ function truncateCodeAST(sourceCode, limit = 200, previewLength = 50) {
217
413
  let ast;
218
414
  try {
219
415
  ast = parse(sourceCode, {
@@ -224,7 +420,7 @@ function truncateCodeHighPerf(sourceCode, limit = 200, previewLength = 50) {
224
420
  raw: true
225
421
  });
226
422
  } catch {
227
- return sourceCode;
423
+ return { code: sourceCode, parseFailed: true };
228
424
  }
229
425
  const magicString = new MagicString(sourceCode);
230
426
  walk(ast, {
@@ -251,7 +447,10 @@ function truncateCodeHighPerf(sourceCode, limit = 200, previewLength = 50) {
251
447
  }
252
448
  }
253
449
  });
254
- return magicString.toString();
450
+ return { code: magicString.toString(), parseFailed: false };
451
+ }
452
+ function truncateCodeHighPerf(sourceCode, limit = 200, previewLength = 50) {
453
+ return truncateCodeAST(sourceCode, limit, previewLength).code;
255
454
  }
256
455
  function truncateLongLines(code, maxLineChars = 500, previewRatio = 0.2) {
257
456
  if (!code) {
@@ -357,11 +556,11 @@ var readCodeSmart = defineTool({
357
556
  import { z as z2 } from "zod";
358
557
 
359
558
  // src/transformer.ts
360
- import * as path2 from "path";
559
+ import * as path3 from "path";
361
560
  import * as fs2 from "fs/promises";
362
561
  import { transformSync } from "@babel/core";
363
562
  function cleanBasename(filename) {
364
- const base = path2.basename(filename);
563
+ const base = path3.basename(filename);
365
564
  let name = base.endsWith(".js") ? base.slice(0, -3) : base;
366
565
  name = name.replace(/_deob[^/]*$/, "");
367
566
  if (name.endsWith(".beautified")) {
@@ -370,15 +569,15 @@ function cleanBasename(filename) {
370
569
  return name;
371
570
  }
372
571
  function getOutputPaths(targetFile, outputSuffix = "_deob") {
373
- const absolutePath = path2.resolve(targetFile);
374
- const dir = path2.dirname(absolutePath);
572
+ const absolutePath = path3.resolve(targetFile);
573
+ const dir = path3.dirname(absolutePath);
375
574
  const basename3 = cleanBasename(absolutePath);
376
- const outputPath = path2.join(dir, `${basename3}${outputSuffix}.js`);
575
+ const outputPath = path3.join(dir, `${basename3}${outputSuffix}.js`);
377
576
  const mapPath = `${outputPath}.map`;
378
577
  return { outputPath, mapPath };
379
578
  }
380
579
  async function loadBabelPlugin(scriptPath) {
381
- const absolutePath = path2.resolve(scriptPath);
580
+ const absolutePath = path3.resolve(scriptPath);
382
581
  try {
383
582
  await fs2.access(absolutePath);
384
583
  } catch {
@@ -436,7 +635,7 @@ function runBabelTransform(code, inputSourceMap, plugin, filename) {
436
635
  }
437
636
  async function applyCustomTransform(targetFile, options) {
438
637
  const { scriptPath, outputSuffix = "_deob" } = options;
439
- const absoluteTargetPath = path2.resolve(targetFile);
638
+ const absoluteTargetPath = path3.resolve(targetFile);
440
639
  try {
441
640
  await fs2.access(absoluteTargetPath);
442
641
  } catch {
@@ -452,12 +651,12 @@ async function applyCustomTransform(targetFile, options) {
452
651
  absoluteTargetPath
453
652
  );
454
653
  const { outputPath, mapPath } = getOutputPaths(absoluteTargetPath, outputSuffix);
455
- const mapFileName = path2.basename(mapPath);
654
+ const mapFileName = path3.basename(mapPath);
456
655
  const outputCode = `${transformResult.code}
457
656
  //# sourceMappingURL=${mapFileName}`;
458
657
  const outputMap = {
459
658
  ...transformResult.map,
460
- file: path2.basename(outputPath)
659
+ file: path3.basename(outputPath)
461
660
  };
462
661
  try {
463
662
  await Promise.all([
@@ -467,7 +666,7 @@ async function applyCustomTransform(targetFile, options) {
467
666
  } catch (err) {
468
667
  const error = err;
469
668
  if (error.code === "EACCES" || error.code === "EPERM") {
470
- throw new Error(`Permission denied: Cannot write to ${path2.dirname(outputPath)}`);
669
+ throw new Error(`Permission denied: Cannot write to ${path3.dirname(outputPath)}`);
471
670
  }
472
671
  throw new Error(`Failed to write output files: ${error.message || String(err)}`);
473
672
  }
@@ -658,22 +857,27 @@ function formatSourcePosition2(line, column) {
658
857
  }
659
858
  return "";
660
859
  }
860
+ function formatCodeLine2(lineNumber, sourcePos, code, maxLineNumWidth, prefix = " ") {
861
+ const lineNumStr = String(lineNumber).padStart(maxLineNumWidth, " ");
862
+ const srcPosPadded = sourcePos ? sourcePos.padEnd(10, " ") : " ";
863
+ return `${prefix}${lineNumStr} ${srcPosPadded} ${code}`;
864
+ }
661
865
  function formatSearchResult(filePath, query, caseSensitive, result, maxMatches = 50, isRegex = false) {
662
866
  const { matches, totalMatches, truncated } = result;
663
867
  const outputParts = [];
664
868
  const caseInfo = caseSensitive ? "case-sensitive" : "case-insensitive";
665
869
  const modeInfo = isRegex ? "regex" : "literal";
666
- outputParts.push(`FILE: ${filePath}`);
667
- outputParts.push(`QUERY: "${query}" (${modeInfo}, ${caseInfo})`);
870
+ outputParts.push(`${filePath}`);
871
+ outputParts.push(`Query="${query}" (${modeInfo}, ${caseInfo})`);
872
+ outputParts.push(`Src=original position for breakpoints`);
668
873
  if (totalMatches === 0) {
669
- outputParts.push("MATCHES: No matches found");
874
+ outputParts.push("Matches: None");
670
875
  return outputParts.join("\n");
671
876
  }
672
- const matchInfo = truncated ? `MATCHES: ${totalMatches} found (showing first ${maxMatches})` : `MATCHES: ${totalMatches} found`;
877
+ const matchInfo = truncated ? `Matches: ${totalMatches} (showing first ${maxMatches})` : `Matches: ${totalMatches}`;
673
878
  outputParts.push(matchInfo);
674
- outputParts.push("-".repeat(85));
675
879
  for (const match of matches) {
676
- outputParts.push(`--- Match at Line ${match.lineNumber} ---`);
880
+ outputParts.push(`--- Line ${match.lineNumber} ---`);
677
881
  const allLineNumbers = [
678
882
  ...match.contextBefore.map((c) => c.lineNumber),
679
883
  match.lineNumber,
@@ -681,25 +885,19 @@ function formatSearchResult(filePath, query, caseSensitive, result, maxMatches =
681
885
  ];
682
886
  const maxLineNumWidth = Math.max(...allLineNumbers.map((n) => String(n).length));
683
887
  for (const ctx of match.contextBefore) {
684
- const lineNumStr = String(ctx.lineNumber).padStart(maxLineNumWidth, " ");
685
888
  const srcPos = formatSourcePosition2(ctx.originalPosition.line, ctx.originalPosition.column);
686
- const srcPosPadded = srcPos ? `Src ${srcPos}` : "";
687
- outputParts.push(` ${lineNumStr} | [${srcPosPadded.padEnd(14, " ")}] | ${ctx.content}`);
889
+ outputParts.push(formatCodeLine2(ctx.lineNumber, srcPos, ctx.content, maxLineNumWidth, " "));
688
890
  }
689
- const matchLineNumStr = String(match.lineNumber).padStart(maxLineNumWidth, " ");
690
891
  const matchSrcPos = formatSourcePosition2(match.originalPosition.line, match.originalPosition.column);
691
- const matchSrcPosPadded = matchSrcPos ? `Src ${matchSrcPos}` : "";
692
- outputParts.push(`>> ${matchLineNumStr} | [${matchSrcPosPadded.padEnd(14, " ")}] | ${match.lineContent}`);
892
+ outputParts.push(formatCodeLine2(match.lineNumber, matchSrcPos, match.lineContent, maxLineNumWidth, ">>"));
693
893
  for (const ctx of match.contextAfter) {
694
- const lineNumStr = String(ctx.lineNumber).padStart(maxLineNumWidth, " ");
695
894
  const srcPos = formatSourcePosition2(ctx.originalPosition.line, ctx.originalPosition.column);
696
- const srcPosPadded = srcPos ? `Src ${srcPos}` : "";
697
- outputParts.push(` ${lineNumStr} | [${srcPosPadded.padEnd(14, " ")}] | ${ctx.content}`);
895
+ outputParts.push(formatCodeLine2(ctx.lineNumber, srcPos, ctx.content, maxLineNumWidth, " "));
698
896
  }
699
- outputParts.push("");
700
897
  }
701
898
  if (truncated) {
702
- outputParts.push(`... (${totalMatches - maxMatches} more matches not shown)`);
899
+ outputParts.push(`
900
+ ... (${totalMatches - maxMatches} more matches not shown)`);
703
901
  }
704
902
  return outputParts.join("\n");
705
903
  }
@@ -817,17 +1015,17 @@ async function analyzeBindings(code, rawMap, identifier, options) {
817
1015
  const traverse2 = await getTraverse();
818
1016
  try {
819
1017
  traverse2(ast, {
820
- Identifier(path3) {
821
- if (path3.node.name !== identifier) {
1018
+ Identifier(path4) {
1019
+ if (path4.node.name !== identifier) {
822
1020
  return;
823
1021
  }
824
1022
  if (isTargeted) {
825
- const nodeLoc = path3.node.loc;
1023
+ const nodeLoc = path4.node.loc;
826
1024
  if (!nodeLoc || nodeLoc.start.line !== targetLine) {
827
1025
  return;
828
1026
  }
829
1027
  }
830
- const binding = path3.scope.getBinding(identifier);
1028
+ const binding = path4.scope.getBinding(identifier);
831
1029
  if (!binding) {
832
1030
  return;
833
1031
  }
@@ -866,7 +1064,7 @@ async function analyzeBindings(code, rawMap, identifier, options) {
866
1064
  const limitedReferences = allReferences.slice(0, maxReferences);
867
1065
  let hitLocation;
868
1066
  if (isTargeted) {
869
- const nodeLoc = path3.node.loc;
1067
+ const nodeLoc = path4.node.loc;
870
1068
  hitLocation = createLocationInfo(
871
1069
  nodeLoc.start.line,
872
1070
  nodeLoc.start.column,
@@ -883,7 +1081,7 @@ async function analyzeBindings(code, rawMap, identifier, options) {
883
1081
  hitLocation
884
1082
  });
885
1083
  if (isTargeted) {
886
- path3.stop();
1084
+ path4.stop();
887
1085
  }
888
1086
  }
889
1087
  });
@@ -904,36 +1102,41 @@ function formatSourcePosition3(line, column) {
904
1102
  }
905
1103
  return "";
906
1104
  }
1105
+ function formatCodeLine3(lineNumber, sourcePos, code, prefix = " ") {
1106
+ const lineNumStr = String(lineNumber).padStart(5, " ");
1107
+ const srcPosPadded = sourcePos ? sourcePos.padEnd(10, " ") : " ";
1108
+ return `${prefix}${lineNumStr} ${srcPosPadded} ${code}`;
1109
+ }
907
1110
  function locationsMatch(loc1, loc2) {
908
1111
  return loc1.line === loc2.line && loc1.column === loc2.column;
909
1112
  }
910
1113
  function formatAnalysisResult(filePath, result, maxReferences = 10) {
911
1114
  const { bindings, identifier, isTargeted, targetLine } = result;
912
1115
  const outputParts = [];
913
- outputParts.push(`FILE: ${filePath}`);
914
- outputParts.push(`IDENTIFIER: "${identifier}"`);
1116
+ outputParts.push(`${filePath}`);
1117
+ outputParts.push(`Identifier="${identifier}"`);
1118
+ outputParts.push(`Src=original position for breakpoints`);
915
1119
  if (bindings.length === 0) {
916
1120
  if (isTargeted && targetLine !== void 0) {
917
- outputParts.push(`BINDINGS: No binding found for "${identifier}" at line ${targetLine}`);
1121
+ outputParts.push(`Bindings: None at line ${targetLine}`);
918
1122
  outputParts.push(`The variable may be global, externally defined, or not present at this line.`);
919
1123
  } else {
920
- outputParts.push("BINDINGS: No definitions or references found");
1124
+ outputParts.push("Bindings: None");
921
1125
  }
922
1126
  return outputParts.join("\n");
923
1127
  }
924
1128
  if (isTargeted) {
925
- outputParts.push(`BINDINGS: 1 found (Targeted Scope at line ${targetLine})`);
1129
+ outputParts.push(`Bindings: 1 (Targeted at line ${targetLine})`);
926
1130
  } else {
927
- const scopeInfo = bindings.length > 1 ? " (in different scopes)" : "";
928
- outputParts.push(`BINDINGS: ${bindings.length} found${scopeInfo}`);
1131
+ const scopeInfo = bindings.length > 1 ? " (different scopes)" : "";
1132
+ outputParts.push(`Bindings: ${bindings.length}${scopeInfo}`);
929
1133
  }
930
- outputParts.push("-".repeat(85));
931
1134
  for (let i = 0; i < bindings.length; i++) {
932
1135
  const binding = bindings[i];
933
1136
  if (isTargeted) {
934
- outputParts.push(`=== Targeted Scope (${binding.kind}) ===`);
1137
+ outputParts.push(`--- Targeted Scope (${binding.kind}) ---`);
935
1138
  } else {
936
- outputParts.push(`=== Scope #${i + 1} (${binding.kind}) ===`);
1139
+ outputParts.push(`--- Scope #${i + 1} (${binding.kind}) ---`);
937
1140
  }
938
1141
  const defIsHit = isTargeted && binding.hitLocation && locationsMatch(binding.definition, binding.hitLocation);
939
1142
  const defPrefix = defIsHit ? "\u{1F4CD} Definition (hit):" : "\u{1F4CD} Definition:";
@@ -942,11 +1145,8 @@ function formatAnalysisResult(filePath, result, maxReferences = 10) {
942
1145
  binding.definition.originalPosition.line,
943
1146
  binding.definition.originalPosition.column
944
1147
  );
945
- const defSrcPosPadded = defSrcPos ? `Src ${defSrcPos}` : "";
946
1148
  const defMarker = defIsHit ? " \u25C0\u2500\u2500 hit" : "";
947
- outputParts.push(
948
- ` ${binding.definition.line} | [${defSrcPosPadded.padEnd(14, " ")}] | ${binding.definition.lineContent}${defMarker}`
949
- );
1149
+ outputParts.push(formatCodeLine3(binding.definition.line, defSrcPos, binding.definition.lineContent + defMarker, " "));
950
1150
  const totalRefs = binding.totalReferences;
951
1151
  if (totalRefs === 0) {
952
1152
  outputParts.push("\u{1F50E} References: None");
@@ -958,18 +1158,14 @@ function formatAnalysisResult(filePath, result, maxReferences = 10) {
958
1158
  ref.originalPosition.line,
959
1159
  ref.originalPosition.column
960
1160
  );
961
- const refSrcPosPadded = refSrcPos ? `Src ${refSrcPos}` : "";
962
1161
  const refMarker = refIsHit ? " \u25C0\u2500\u2500 hit" : "";
963
- outputParts.push(
964
- ` ${ref.line} | [${refSrcPosPadded.padEnd(14, " ")}] | ${ref.lineContent}${refMarker}`
965
- );
1162
+ outputParts.push(formatCodeLine3(ref.line, refSrcPos, ref.lineContent + refMarker, " "));
966
1163
  }
967
1164
  if (totalRefs > maxReferences) {
968
1165
  const remaining = totalRefs - maxReferences;
969
- outputParts.push(` ... (${remaining} more references not shown)`);
1166
+ outputParts.push(` ... (${remaining} more references not shown)`);
970
1167
  }
971
1168
  }
972
- outputParts.push("");
973
1169
  }
974
1170
  return outputParts.join("\n");
975
1171
  }
@@ -1024,12 +1220,309 @@ var findUsageSmart = defineTool({
1024
1220
  }
1025
1221
  });
1026
1222
 
1223
+ // src/tools/aiFindJsvmpDispatcher.ts
1224
+ import { SourceMapConsumer as SourceMapConsumer4 } from "source-map-js";
1225
+ import { z as z5 } from "zod";
1226
+
1227
+ // src/llmConfig.ts
1228
+ function getLLMConfig() {
1229
+ const apiKey = process.env.OPENAI_API_KEY;
1230
+ if (!apiKey) {
1231
+ return null;
1232
+ }
1233
+ const baseUrl = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
1234
+ const model = process.env.OPENAI_MODEL || "gpt-4o-mini";
1235
+ return {
1236
+ apiKey,
1237
+ baseUrl,
1238
+ model
1239
+ };
1240
+ }
1241
+ function buildJSVMPSystemPrompt() {
1242
+ return `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684 JavaScript \u9006\u5411\u5DE5\u7A0B\u4E13\u5BB6\uFF0C\u4E13\u95E8\u8BC6\u522B JSVMP\uFF08JavaScript Virtual Machine Protection\uFF09\u4FDD\u62A4\u4EE3\u7801\u3002
1243
+
1244
+ JSVMP \u662F\u4E00\u79CD\u4EE3\u7801\u4FDD\u62A4\u6280\u672F\uFF0C\u5C06 JavaScript \u4EE3\u7801\u8F6C\u6362\u4E3A\u5B57\u8282\u7801\uFF0C\u5E76\u901A\u8FC7\u865A\u62DF\u673A\u6267\u884C\u3002\u5178\u578B\u7279\u5F81\u5305\u62EC\uFF1A
1245
+
1246
+ 1. **\u865A\u62DF\u6808\uFF08Virtual Stack\uFF09**\uFF1A\u4E2D\u592E\u6570\u7EC4\u7528\u4E8E\u5B58\u50A8\u64CD\u4F5C\u6570\u548C\u7ED3\u679C
1247
+ 2. **\u5206\u53D1\u5668\uFF08Dispatcher\uFF09**\uFF1A\u5927\u578B switch \u8BED\u53E5\u6216\u5D4C\u5957 if-else \u94FE\uFF0C\u6839\u636E\u6307\u4EE4\u7801\u6267\u884C\u4E0D\u540C\u64CD\u4F5C
1248
+ 3. **\u6307\u4EE4\u6570\u7EC4\uFF08Instruction Array\uFF09**\uFF1A\u5B58\u50A8\u5B57\u8282\u7801\u6307\u4EE4\u7684\u6570\u7EC4
1249
+ 4. **\u4E3B\u5FAA\u73AF\uFF08Main Loop\uFF09**\uFF1Awhile \u5FAA\u73AF\u6301\u7EED\u6267\u884C\u6307\u4EE4
1250
+
1251
+ \u68C0\u6D4B\u89C4\u5219\uFF1A
1252
+
1253
+ **Ultra High \u7F6E\u4FE1\u5EA6**\uFF1A
1254
+ - \u540C\u65F6\u51FA\u73B0\uFF1A\u4E3B\u5FAA\u73AF + \u5206\u53D1\u5668 + \u6808\u64CD\u4F5C
1255
+ - \u5206\u53D1\u5668\u6709 >20 \u4E2A case \u6216 >10 \u5C42\u5D4C\u5957
1256
+ - \u660E\u786E\u7684\u6808\u64CD\u4F5C\u6A21\u5F0F\uFF08push/pop/\u6570\u7EC4\u7D22\u5F15\uFF09
1257
+
1258
+ **High \u7F6E\u4FE1\u5EA6**\uFF1A
1259
+ - \u72EC\u7ACB\u7684\u5927\u578B\u5206\u53D1\u5668\u7ED3\u6784\uFF08>20 case \u7684 switch \u6216 >10 \u5C42\u5D4C\u5957\u7684 if-else\uFF09
1260
+ - \u660E\u786E\u7684\u6307\u4EE4\u6570\u7EC4\u548C\u7A0B\u5E8F\u8BA1\u6570\u5668\u6A21\u5F0F
1261
+
1262
+ **Medium \u7F6E\u4FE1\u5EA6**\uFF1A
1263
+ - \u5B64\u7ACB\u7684\u6808\u64CD\u4F5C\u6216\u53EF\u7591\u7684 while \u5FAA\u73AF
1264
+ - \u90E8\u5206 JSVMP \u7279\u5F81\u4F46\u4E0D\u5B8C\u6574
1265
+
1266
+ **Low \u7F6E\u4FE1\u5EA6**\uFF1A
1267
+ - \u901A\u7528\u6DF7\u6DC6\u6A21\u5F0F
1268
+ - \u53EF\u80FD\u76F8\u5173\u4F46\u4E0D\u786E\u5B9A\u7684\u7ED3\u6784
1269
+
1270
+ \u8BF7\u5206\u6790\u63D0\u4F9B\u7684\u4EE3\u7801\uFF0C\u8BC6\u522B JSVMP \u76F8\u5173\u533A\u57DF\u3002\u8FD4\u56DE JSON \u683C\u5F0F\uFF1A
1271
+
1272
+ {
1273
+ "summary": "\u5206\u6790\u6458\u8981\uFF08\u4E2D\u6587\uFF09",
1274
+ "regions": [
1275
+ {
1276
+ "start": \u8D77\u59CB\u884C\u53F7,
1277
+ "end": \u7ED3\u675F\u884C\u53F7,
1278
+ "type": "If-Else Dispatcher" | "Switch Dispatcher" | "Instruction Array" | "Stack Operation",
1279
+ "confidence": "ultra_high" | "high" | "medium" | "low",
1280
+ "description": "\u8BE6\u7EC6\u63CF\u8FF0\uFF08\u4E2D\u6587\uFF09"
1281
+ }
1282
+ ]
1283
+ }
1284
+
1285
+ \u5982\u679C\u6CA1\u6709\u68C0\u6D4B\u5230 JSVMP \u7279\u5F81\uFF0C\u8FD4\u56DE\u7A7A\u7684 regions \u6570\u7EC4\u3002`;
1286
+ }
1287
+ function createLLMClient(config) {
1288
+ return {
1289
+ async analyzeJSVMP(formattedCode) {
1290
+ const systemPrompt = buildJSVMPSystemPrompt();
1291
+ const requestBody = {
1292
+ model: config.model,
1293
+ messages: [
1294
+ {
1295
+ role: "system",
1296
+ content: systemPrompt
1297
+ },
1298
+ {
1299
+ role: "user",
1300
+ content: `\u8BF7\u5206\u6790\u4EE5\u4E0B\u4EE3\u7801\uFF0C\u8BC6\u522B JSVMP \u4FDD\u62A4\u7ED3\u6784\uFF1A
1301
+
1302
+ ${formattedCode}`
1303
+ }
1304
+ ],
1305
+ temperature: 0.1,
1306
+ response_format: { type: "json_object" }
1307
+ };
1308
+ try {
1309
+ const response = await fetch(`${config.baseUrl}/chat/completions`, {
1310
+ method: "POST",
1311
+ headers: {
1312
+ "Content-Type": "application/json",
1313
+ "Authorization": `Bearer ${config.apiKey}`
1314
+ },
1315
+ body: JSON.stringify(requestBody)
1316
+ });
1317
+ if (!response.ok) {
1318
+ const errorText = await response.text();
1319
+ throw new Error(`API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${errorText}`);
1320
+ }
1321
+ const data = await response.json();
1322
+ if (!data.choices || !data.choices[0] || !data.choices[0].message) {
1323
+ throw new Error("API \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF1A\u7F3A\u5C11 choices \u6216 message \u5B57\u6BB5");
1324
+ }
1325
+ const content = data.choices[0].message.content;
1326
+ if (typeof content !== "string") {
1327
+ throw new Error("API \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF1Amessage.content \u4E0D\u662F\u5B57\u7B26\u4E32");
1328
+ }
1329
+ return content;
1330
+ } catch (error) {
1331
+ if (error instanceof Error) {
1332
+ throw new Error(`LLM \u8BF7\u6C42\u5931\u8D25: ${error.message}`);
1333
+ }
1334
+ throw new Error(`LLM \u8BF7\u6C42\u5931\u8D25: ${String(error)}`);
1335
+ }
1336
+ }
1337
+ };
1338
+ }
1339
+
1340
+ // src/tools/aiFindJsvmpDispatcher.ts
1341
+ import { existsSync } from "fs";
1342
+ function formatSourcePosition4(line, column) {
1343
+ if (line !== null && column !== null) {
1344
+ return `L${line}:${column}`;
1345
+ }
1346
+ return "";
1347
+ }
1348
+ function formatCodeLine4(lineNumber, sourcePos, code) {
1349
+ const lineNumStr = String(lineNumber).padStart(5, " ");
1350
+ const srcPosPadded = sourcePos ? sourcePos.padEnd(10, " ") : " ";
1351
+ return `${lineNumStr} ${srcPosPadded} ${code}`;
1352
+ }
1353
+ async function formatCodeForAnalysis(filePath, startLine, endLine, charLimit = 300) {
1354
+ const beautifyResult = await ensureBeautified(filePath);
1355
+ const { code, rawMap } = beautifyResult;
1356
+ const truncatedCode = truncateCodeHighPerf(code, charLimit);
1357
+ const lines = truncatedCode.split("\n");
1358
+ const totalLines = lines.length;
1359
+ const effectiveStartLine = Math.max(1, Math.min(totalLines, startLine));
1360
+ const effectiveEndLine = Math.max(effectiveStartLine, Math.min(totalLines, endLine));
1361
+ const consumer = new SourceMapConsumer4({
1362
+ ...rawMap,
1363
+ version: String(rawMap.version)
1364
+ });
1365
+ const formattedLines = [];
1366
+ for (let lineNum = effectiveStartLine; lineNum <= effectiveEndLine; lineNum++) {
1367
+ const lineIndex = lineNum - 1;
1368
+ const lineContent = lines[lineIndex] ?? "";
1369
+ const originalPos = consumer.originalPositionFor({
1370
+ line: lineNum,
1371
+ column: 0
1372
+ });
1373
+ const sourcePos = formatSourcePosition4(originalPos.line, originalPos.column);
1374
+ formattedLines.push(formatCodeLine4(lineNum, sourcePos, lineContent));
1375
+ }
1376
+ return {
1377
+ content: formattedLines.join("\n"),
1378
+ totalLines,
1379
+ startLine: effectiveStartLine,
1380
+ endLine: effectiveEndLine
1381
+ };
1382
+ }
1383
+ var VALID_DETECTION_TYPES = [
1384
+ "If-Else Dispatcher",
1385
+ "Switch Dispatcher",
1386
+ "Instruction Array",
1387
+ "Stack Operation"
1388
+ ];
1389
+ var VALID_CONFIDENCE_LEVELS = [
1390
+ "ultra_high",
1391
+ "high",
1392
+ "medium",
1393
+ "low"
1394
+ ];
1395
+ function isValidDetectionType(value) {
1396
+ return VALID_DETECTION_TYPES.includes(value);
1397
+ }
1398
+ function isValidConfidenceLevel(value) {
1399
+ return VALID_CONFIDENCE_LEVELS.includes(value);
1400
+ }
1401
+ function parseDetectionResult(jsonString) {
1402
+ let parsed;
1403
+ try {
1404
+ parsed = JSON.parse(jsonString);
1405
+ } catch (error) {
1406
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 LLM \u54CD\u5E94: ${error instanceof Error ? error.message : String(error)}`);
1407
+ }
1408
+ if (typeof parsed !== "object" || parsed === null) {
1409
+ throw new Error("LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0C\u671F\u671B\u5BF9\u8C61\u7C7B\u578B");
1410
+ }
1411
+ if (typeof parsed.summary !== "string") {
1412
+ throw new Error("LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0C\u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: summary");
1413
+ }
1414
+ if (!Array.isArray(parsed.regions)) {
1415
+ throw new Error("LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0C\u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: regions");
1416
+ }
1417
+ const validatedRegions = [];
1418
+ for (let i = 0; i < parsed.regions.length; i++) {
1419
+ const region = parsed.regions[i];
1420
+ if (typeof region !== "object" || region === null) {
1421
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u4E0D\u662F\u5BF9\u8C61`);
1422
+ }
1423
+ if (typeof region.start !== "number") {
1424
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: start`);
1425
+ }
1426
+ if (typeof region.end !== "number") {
1427
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: end`);
1428
+ }
1429
+ if (typeof region.type !== "string") {
1430
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: type`);
1431
+ }
1432
+ if (typeof region.confidence !== "string") {
1433
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: confidence`);
1434
+ }
1435
+ if (typeof region.description !== "string") {
1436
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: description`);
1437
+ }
1438
+ if (!isValidDetectionType(region.type)) {
1439
+ throw new Error(
1440
+ `LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}].type \u503C\u65E0\u6548: "${region.type}". \u6709\u6548\u503C: ${VALID_DETECTION_TYPES.join(", ")}`
1441
+ );
1442
+ }
1443
+ if (!isValidConfidenceLevel(region.confidence)) {
1444
+ throw new Error(
1445
+ `LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}].confidence \u503C\u65E0\u6548: "${region.confidence}". \u6709\u6548\u503C: ${VALID_CONFIDENCE_LEVELS.join(", ")}`
1446
+ );
1447
+ }
1448
+ validatedRegions.push({
1449
+ start: region.start,
1450
+ end: region.end,
1451
+ type: region.type,
1452
+ confidence: region.confidence,
1453
+ description: region.description
1454
+ });
1455
+ }
1456
+ return {
1457
+ summary: parsed.summary,
1458
+ regions: validatedRegions
1459
+ };
1460
+ }
1461
+ function formatDetectionResult(result, filePath, startLine, endLine) {
1462
+ const lines = [];
1463
+ lines.push("=== JSVMP Dispatcher Detection Result ===");
1464
+ lines.push(`File: ${filePath} (${startLine}-${endLine})`);
1465
+ lines.push("");
1466
+ lines.push(`Summary: ${result.summary}`);
1467
+ lines.push("");
1468
+ if (result.regions.length > 0) {
1469
+ lines.push("Detected Regions:");
1470
+ for (const region of result.regions) {
1471
+ lines.push(`[${region.confidence}] Lines ${region.start}-${region.end}: ${region.type}`);
1472
+ lines.push(` ${region.description}`);
1473
+ lines.push("");
1474
+ }
1475
+ } else {
1476
+ lines.push("No JSVMP dispatcher patterns detected.");
1477
+ }
1478
+ return lines.join("\n");
1479
+ }
1480
+ var AiFindJsvmpDispatcherInputSchema = z5.object({
1481
+ file_path: z5.string().describe("Path to the JavaScript file to analyze"),
1482
+ start_line: z5.number().int().positive().describe("Start line number (1-based)"),
1483
+ end_line: z5.number().int().positive().describe("End line number (1-based)"),
1484
+ char_limit: z5.number().int().positive().optional().describe("Character limit for string truncation (default: 300)")
1485
+ });
1486
+ var aiFindJsvmpDispatcher = defineTool({
1487
+ name: "ai_find_jsvmp_dispatcher",
1488
+ description: "AI-powered tool to find JSVMP (JavaScript Virtual Machine Protection) dispatcher patterns in code using LLM analysis. Identifies dispatchers (switch/if-else), virtual stacks, instruction arrays, and other obfuscation patterns. Requires OPENAI_API_KEY environment variable.",
1489
+ schema: AiFindJsvmpDispatcherInputSchema.shape,
1490
+ handler: async (params) => {
1491
+ const { file_path, start_line, end_line, char_limit } = params;
1492
+ const config = getLLMConfig();
1493
+ if (!config) {
1494
+ return "\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E LLM\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF OPENAI_API_KEY \u4EE5\u542F\u7528 JSVMP dispatcher \u68C0\u6D4B\u529F\u80FD\u3002";
1495
+ }
1496
+ if (!existsSync(file_path)) {
1497
+ return `\u9519\u8BEF\uFF1A\u6587\u4EF6\u4E0D\u5B58\u5728: ${file_path}`;
1498
+ }
1499
+ try {
1500
+ const formattedCode = await formatCodeForAnalysis(
1501
+ file_path,
1502
+ start_line,
1503
+ end_line,
1504
+ char_limit
1505
+ );
1506
+ const client = createLLMClient(config);
1507
+ const llmResponse = await client.analyzeJSVMP(formattedCode.content);
1508
+ const result = parseDetectionResult(llmResponse);
1509
+ return formatDetectionResult(result, file_path, start_line, end_line);
1510
+ } catch (error) {
1511
+ if (error instanceof Error) {
1512
+ return `\u9519\u8BEF\uFF1A${error.message}`;
1513
+ }
1514
+ return `\u9519\u8BEF\uFF1A${String(error)}`;
1515
+ }
1516
+ }
1517
+ });
1518
+
1027
1519
  // src/tools/index.ts
1028
1520
  var tools = [
1029
1521
  readCodeSmart,
1030
1522
  applyCustomTransform2,
1031
1523
  searchCodeSmart,
1032
- findUsageSmart
1524
+ findUsageSmart,
1525
+ aiFindJsvmpDispatcher
1033
1526
  ];
1034
1527
 
1035
1528
  // src/server.ts
@@ -1038,7 +1531,7 @@ var server = new McpServer({
1038
1531
  version: "1.0.0"
1039
1532
  });
1040
1533
  function registerTool(tool) {
1041
- const zodSchema = z5.object(tool.schema);
1534
+ const zodSchema = z6.object(tool.schema);
1042
1535
  server.registerTool(
1043
1536
  tool.name,
1044
1537
  {