@justmpm/supergrep 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1342 @@
1
+ // src/server.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+
5
+ // src/tools.ts
6
+ import { z } from "zod";
7
+
8
+ // src/tools/find.ts
9
+ import { parse } from "@ast-grep/napi";
10
+ import { readFile, readdir } from "fs/promises";
11
+ import { join, relative, resolve, extname } from "path";
12
+
13
+ // src/utils/lang.ts
14
+ import { Lang } from "@ast-grep/napi";
15
+ var LANG_ALIASES = {
16
+ // JavaScript
17
+ javascript: Lang.JavaScript,
18
+ js: Lang.JavaScript,
19
+ cjs: Lang.JavaScript,
20
+ mjs: Lang.JavaScript,
21
+ // TypeScript
22
+ typescript: Lang.TypeScript,
23
+ ts: Lang.TypeScript,
24
+ // TSX
25
+ tsx: Lang.Tsx,
26
+ // CSS
27
+ css: Lang.Css,
28
+ // HTML
29
+ html: Lang.Html
30
+ };
31
+ var EXT_TO_LANG_ALIAS = {
32
+ ".js": "javascript",
33
+ ".mjs": "javascript",
34
+ ".cjs": "javascript",
35
+ ".jsx": "javascript",
36
+ ".ts": "typescript",
37
+ ".tsx": "tsx",
38
+ ".css": "css",
39
+ ".scss": "css",
40
+ ".less": "css",
41
+ ".html": "html",
42
+ ".htm": "html"
43
+ };
44
+ var SUPPORTED_EXTENSIONS = new Set(Object.keys(EXT_TO_LANG_ALIAS));
45
+ function resolveLang(input) {
46
+ const normalized = input.toLowerCase().replace(/^\./, "");
47
+ return LANG_ALIASES[normalized];
48
+ }
49
+ function detectLangFromFile(filePath) {
50
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
51
+ const alias = EXT_TO_LANG_ALIAS[ext];
52
+ return alias ? resolveLang(alias) : void 0;
53
+ }
54
+ function listSupportedLanguages() {
55
+ return ["JavaScript", "TypeScript", "TSX", "CSS", "HTML"];
56
+ }
57
+ function langName(lang) {
58
+ const langStr = String(lang);
59
+ const friendly = {
60
+ Html: "HTML",
61
+ JavaScript: "JavaScript",
62
+ Tsx: "TSX",
63
+ Css: "CSS",
64
+ TypeScript: "TypeScript"
65
+ };
66
+ return friendly[langStr] ?? langStr;
67
+ }
68
+ function isSupportedExtension(filePath) {
69
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
70
+ return SUPPORTED_EXTENSIONS.has(ext);
71
+ }
72
+ var IGNORED_EXTENSIONS_SUBSTRINGS = [
73
+ ".min.js",
74
+ ".min.css",
75
+ ".bundle.js",
76
+ ".chunk.js",
77
+ ".map",
78
+ ".d.ts"
79
+ ];
80
+ function shouldIgnoreFile(filePath) {
81
+ const lower = filePath.toLowerCase();
82
+ for (const ext of IGNORED_EXTENSIONS_SUBSTRINGS) {
83
+ if (lower.includes(ext)) return true;
84
+ }
85
+ return false;
86
+ }
87
+ var IGNORED_DIRS = [
88
+ "node_modules",
89
+ "dist",
90
+ "build",
91
+ ".git",
92
+ ".next",
93
+ ".turbo",
94
+ "coverage",
95
+ "out"
96
+ ];
97
+
98
+ // src/tools/find.ts
99
+ var META_VAR_RE = /\$(\$\$)?([A-Z_][A-Z0-9_]*)/g;
100
+ async function findPattern(options) {
101
+ const startTime = Date.now();
102
+ const warnings = [];
103
+ const cwd = options.cwd ?? process.cwd();
104
+ const lang = resolveLangForOptions(options, cwd, warnings);
105
+ if (!lang) {
106
+ throw new LanguageNotDetectedError(options.lang ?? "desconhecida");
107
+ }
108
+ validatePattern(options.pattern, lang);
109
+ const searchDir = resolve(cwd, options.path ?? ".");
110
+ const files = await findCodeFiles(searchDir, cwd, lang);
111
+ warnings.push(...files.warnings);
112
+ const metaVarNames = extractMetaVarNames(options.pattern);
113
+ const matches = [];
114
+ for (const filePath of files.paths) {
115
+ try {
116
+ const source = await readFile(filePath, "utf-8");
117
+ const ast = parse(lang, source);
118
+ const root = ast.root();
119
+ const foundNodes = root.findAll(options.pattern);
120
+ for (const node of foundNodes) {
121
+ const range = node.range();
122
+ const match = {
123
+ file: relative(cwd, filePath).replace(/\\/g, "/"),
124
+ line: range.start.line,
125
+ column: range.start.column,
126
+ endLine: range.end.line,
127
+ endColumn: range.end.column,
128
+ text: node.text(),
129
+ metaVariables: {},
130
+ context: extractContextLines(source, range.start.line, 2)
131
+ };
132
+ for (const varName of metaVarNames) {
133
+ const metaNode = node.getMatch(varName);
134
+ if (metaNode) {
135
+ match.metaVariables[varName] = metaNode.text();
136
+ }
137
+ }
138
+ matches.push(match);
139
+ }
140
+ } catch {
141
+ continue;
142
+ }
143
+ }
144
+ const uniqueFiles = new Set(matches.map((m) => m.file));
145
+ const executionTimeMs = Date.now() - startTime;
146
+ return {
147
+ result: {
148
+ pattern: options.pattern,
149
+ lang,
150
+ matches,
151
+ filesScanned: files.paths.length,
152
+ filesWithMatches: uniqueFiles.size,
153
+ executionTimeMs
154
+ },
155
+ warnings
156
+ };
157
+ }
158
+ function extractMetaVarNames(pattern) {
159
+ const names = /* @__PURE__ */ new Set();
160
+ let match;
161
+ const re = new RegExp(META_VAR_RE.source, "g");
162
+ while ((match = re.exec(pattern)) !== null) {
163
+ names.add(match[2]);
164
+ }
165
+ return [...names];
166
+ }
167
+ function extractContextLines(source, line, contextLines) {
168
+ const lines = source.split("\n");
169
+ const start = Math.max(0, line - contextLines);
170
+ const end = Math.min(lines.length, line + contextLines + 1);
171
+ return lines.slice(start, end);
172
+ }
173
+ function resolveLangForOptions(options, _cwd, warnings) {
174
+ if (options.lang) {
175
+ const lang = resolveLang(options.lang);
176
+ if (!lang) {
177
+ warnings.push(`Linguagem "${options.lang}" nao reconhecida. Tentando autodeteccao...`);
178
+ } else {
179
+ return lang;
180
+ }
181
+ }
182
+ if (options.path) {
183
+ const ext = extname(options.path).toLowerCase();
184
+ if (ext && isSupportedExtension(options.path)) {
185
+ const detected = detectLangFromFile(options.path);
186
+ if (detected) return detected;
187
+ }
188
+ }
189
+ warnings.push("Linguagem nao especificada, usando TypeScript como default.");
190
+ return resolveLang("typescript");
191
+ }
192
+ function validatePattern(pattern, lang) {
193
+ try {
194
+ parse(lang, pattern);
195
+ } catch {
196
+ try {
197
+ parse(lang, `const _x = ${pattern}`);
198
+ } catch {
199
+ throw new PatternParseError(pattern, lang);
200
+ }
201
+ }
202
+ }
203
+ async function findCodeFiles(searchDir, _cwd, _lang) {
204
+ const warnings = [];
205
+ try {
206
+ const { stat: stat3 } = await import("fs/promises");
207
+ const dirStat = await stat3(searchDir);
208
+ if (dirStat.isFile()) {
209
+ if (!isSupportedExtension(searchDir)) {
210
+ const fileName = searchDir.split(/[/\\]/).pop() ?? searchDir;
211
+ warnings.push(
212
+ `Arquivo ${fileName}: extensao pode nao ser suportada para ${langName(_lang)}`
213
+ );
214
+ }
215
+ return { paths: [searchDir], warnings };
216
+ }
217
+ } catch {
218
+ warnings.push(`Path nao encontrado: ${searchDir}. Usando diretorio atual.`);
219
+ return { paths: [], warnings };
220
+ }
221
+ const paths = [];
222
+ async function walk(dir) {
223
+ try {
224
+ const entries = await readdir(dir, { withFileTypes: true });
225
+ for (const entry of entries) {
226
+ const fullPath = join(dir, entry.name);
227
+ if (entry.isDirectory()) {
228
+ if (IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
229
+ continue;
230
+ }
231
+ await walk(fullPath);
232
+ } else if (entry.isFile()) {
233
+ if (shouldIgnoreFile(fullPath)) continue;
234
+ if (!isSupportedExtension(fullPath)) continue;
235
+ paths.push(fullPath);
236
+ }
237
+ }
238
+ } catch {
239
+ }
240
+ }
241
+ await walk(searchDir);
242
+ if (paths.length > 5e3) {
243
+ warnings.push(
244
+ `Muitos arquivos encontrados (${paths.length}). Limitando a 5000. Use --path para focar em um diretorio.`
245
+ );
246
+ return { paths: paths.slice(0, 5e3), warnings };
247
+ }
248
+ if (paths.length === 0) {
249
+ warnings.push(
250
+ `Nenhum arquivo de codigo encontrado em: ${relative(_cwd, searchDir)}`
251
+ );
252
+ }
253
+ return { paths, warnings };
254
+ }
255
+ var PatternParseError = class extends Error {
256
+ pattern;
257
+ lang;
258
+ constructor(pattern, lang) {
259
+ super(`Erro ao parsear padrao "${pattern}" para ${langName(lang)}`);
260
+ this.name = "PatternParseError";
261
+ this.pattern = pattern;
262
+ this.lang = lang;
263
+ }
264
+ };
265
+ var LanguageNotDetectedError = class extends Error {
266
+ input;
267
+ constructor(input) {
268
+ super(`Nao foi possivel detectar a linguagem: "${input}"`);
269
+ this.name = "LanguageNotDetectedError";
270
+ this.input = input;
271
+ }
272
+ };
273
+
274
+ // src/tools/tree.ts
275
+ import { parse as parse2 } from "@ast-grep/napi";
276
+ import { readFile as readFile2, stat } from "fs/promises";
277
+ import { resolve as resolve2, relative as relative2, extname as extname2 } from "path";
278
+ async function exploreTree(options) {
279
+ const warnings = [];
280
+ const cwd = options.cwd ?? process.cwd();
281
+ let source;
282
+ let filePath;
283
+ let detectedLang;
284
+ if (options.code) {
285
+ source = options.code;
286
+ filePath = "<inline>";
287
+ if (options.lang) {
288
+ detectedLang = resolveLang(options.lang);
289
+ if (!detectedLang) {
290
+ warnings.push(`Linguagem "${options.lang}" nao reconhecida, usando TypeScript.`);
291
+ detectedLang = resolveLang("typescript");
292
+ }
293
+ } else {
294
+ warnings.push("Linguagem nao especificada para codigo inline, usando TypeScript.");
295
+ detectedLang = resolveLang("typescript");
296
+ }
297
+ } else if (options.path) {
298
+ filePath = resolve2(cwd, options.path);
299
+ try {
300
+ await stat(filePath);
301
+ } catch {
302
+ throw new FileNotFoundError(filePath);
303
+ }
304
+ source = await readFile2(filePath, "utf-8");
305
+ if (options.lang) {
306
+ detectedLang = resolveLang(options.lang);
307
+ if (!detectedLang) {
308
+ warnings.push(`Linguagem "${options.lang}" nao reconhecida. Tentando detectar...`);
309
+ }
310
+ }
311
+ if (!detectedLang) {
312
+ detectedLang = detectLangFromFile(filePath);
313
+ if (!detectedLang) {
314
+ warnings.push(
315
+ `Nao foi possivel detectar linguagem da extensao "${extname2(filePath)}". Usando TypeScript.`
316
+ );
317
+ detectedLang = resolveLang("typescript");
318
+ }
319
+ }
320
+ } else {
321
+ throw new Error("Informe path (arquivo) ou code (snippet) para analisar.");
322
+ }
323
+ const lang = detectedLang;
324
+ let ast;
325
+ try {
326
+ ast = parse2(lang, source);
327
+ } catch (parseErr) {
328
+ const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
329
+ throw new TreeParseError(filePath, lang, msg);
330
+ }
331
+ const root = ast.root();
332
+ const kindCounts = /* @__PURE__ */ new Map();
333
+ let totalNodes = 0;
334
+ walkAST(root, kindCounts, { count: 0 });
335
+ totalNodes = kindCounts.size > 0 ? [...kindCounts.values()].reduce((a, b) => a + b, 0) : 0;
336
+ const displayPath = options.path ? relative2(cwd, filePath).replace(/\\/g, "/") : filePath;
337
+ const result = {
338
+ file: displayPath,
339
+ lang,
340
+ totalNodes,
341
+ uniqueKinds: [...kindCounts.keys()].sort(),
342
+ kindCounts: Object.fromEntries(kindCounts),
343
+ rootKind: String(root.kind())
344
+ };
345
+ return { result, warnings };
346
+ }
347
+ function walkAST(node, kindCounts, state) {
348
+ state.count++;
349
+ const kind = String(node.kind());
350
+ kindCounts.set(kind, (kindCounts.get(kind) ?? 0) + 1);
351
+ if (state.count > 1e5) {
352
+ return;
353
+ }
354
+ for (const child of node.children()) {
355
+ walkAST(child, kindCounts, state);
356
+ }
357
+ }
358
+ var FileNotFoundError = class extends Error {
359
+ filePath;
360
+ constructor(filePath) {
361
+ super(`Arquivo nao encontrado: ${filePath}`);
362
+ this.name = "FileNotFoundError";
363
+ this.filePath = filePath;
364
+ }
365
+ };
366
+ var TreeParseError = class extends Error {
367
+ filePath;
368
+ lang;
369
+ constructor(filePath, lang, detail) {
370
+ super(`Erro ao parsear "${filePath}" como ${langName(lang)}: ${detail}`);
371
+ this.name = "TreeParseError";
372
+ this.filePath = filePath;
373
+ this.lang = lang;
374
+ }
375
+ };
376
+
377
+ // src/tools/replace.ts
378
+ import { parse as parse3 } from "@ast-grep/napi";
379
+ import { readFile as readFile3, readdir as readdir2, stat as stat2 } from "fs/promises";
380
+ import { join as join2, resolve as resolve3, relative as relative3 } from "path";
381
+ var META_VAR_RE2 = /\$(\$\$)?([A-Z_][A-Z0-9_]*)/g;
382
+ async function replacePreview(options) {
383
+ const warnings = [];
384
+ const cwd = options.cwd ?? process.cwd();
385
+ let lang;
386
+ if (options.lang) {
387
+ const resolved = resolveLang(options.lang);
388
+ if (!resolved) {
389
+ throw new ReplaceLangError(options.lang);
390
+ }
391
+ lang = resolved;
392
+ } else if (options.path) {
393
+ const detected = detectLangFromFile(options.path);
394
+ if (!detected) {
395
+ throw new ReplaceLangError(options.path);
396
+ }
397
+ lang = detected;
398
+ warnings.push(`Linguagem detectada automaticamente: ${langName(lang)}`);
399
+ } else {
400
+ warnings.push("Linguagem nao especificada, usando TypeScript como default.");
401
+ lang = resolveLang("typescript");
402
+ }
403
+ validateReplacePattern(options.pattern, lang);
404
+ const searchDir = resolve3(cwd, options.path ?? ".");
405
+ const files = await findCodeFiles2(searchDir);
406
+ const metaVarNames = extractMetaVarNames2(options.pattern);
407
+ const changes = [];
408
+ for (const filePath of files.paths) {
409
+ try {
410
+ const source = await readFile3(filePath, "utf-8");
411
+ const ast = parse3(lang, source);
412
+ const root = ast.root();
413
+ const foundNodes = root.findAll(options.pattern);
414
+ for (const node of foundNodes) {
415
+ const metaVars = {};
416
+ for (const varName of metaVarNames) {
417
+ const metaNode = node.getMatch(varName);
418
+ if (metaNode) {
419
+ metaVars[varName] = metaNode.text();
420
+ }
421
+ }
422
+ let interpolated = options.rewrite;
423
+ for (const [name, value] of Object.entries(metaVars)) {
424
+ interpolated = interpolated.replaceAll(
425
+ new RegExp(`\\$${name}\\b`, "g"),
426
+ value
427
+ );
428
+ }
429
+ const range = node.range();
430
+ changes.push({
431
+ file: relative3(cwd, filePath).replace(/\\/g, "/"),
432
+ line: range.start.line,
433
+ column: range.start.column,
434
+ original: node.text(),
435
+ replacement: interpolated
436
+ });
437
+ }
438
+ } catch {
439
+ continue;
440
+ }
441
+ }
442
+ const uniqueFiles = new Set(changes.map((c) => c.file));
443
+ return {
444
+ result: {
445
+ pattern: options.pattern,
446
+ rewrite: options.rewrite,
447
+ lang,
448
+ changes,
449
+ filesScanned: files.paths.length,
450
+ filesAffected: uniqueFiles.size
451
+ },
452
+ warnings
453
+ };
454
+ }
455
+ function extractMetaVarNames2(pattern) {
456
+ const names = /* @__PURE__ */ new Set();
457
+ let match;
458
+ const re = new RegExp(META_VAR_RE2.source, "g");
459
+ while ((match = re.exec(pattern)) !== null) {
460
+ names.add(match[2]);
461
+ }
462
+ return [...names];
463
+ }
464
+ function validateReplacePattern(pattern, lang) {
465
+ try {
466
+ parse3(lang, pattern);
467
+ } catch {
468
+ try {
469
+ parse3(lang, `const _x = ${pattern}`);
470
+ } catch {
471
+ throw new ReplacePatternError(pattern, lang);
472
+ }
473
+ }
474
+ }
475
+ async function findCodeFiles2(searchDir) {
476
+ const warnings = [];
477
+ try {
478
+ const dirStat = await stat2(searchDir);
479
+ if (dirStat.isFile()) {
480
+ return { paths: [searchDir], warnings };
481
+ }
482
+ } catch {
483
+ return { paths: [], warnings };
484
+ }
485
+ const paths = [];
486
+ async function walk(dir) {
487
+ try {
488
+ const entries = await readdir2(dir, { withFileTypes: true });
489
+ for (const entry of entries) {
490
+ const fullPath = join2(dir, entry.name);
491
+ if (entry.isDirectory()) {
492
+ if (IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
493
+ continue;
494
+ }
495
+ await walk(fullPath);
496
+ } else if (entry.isFile()) {
497
+ if (shouldIgnoreFile(fullPath)) continue;
498
+ if (!isSupportedExtension(fullPath)) continue;
499
+ paths.push(fullPath);
500
+ }
501
+ }
502
+ } catch {
503
+ }
504
+ }
505
+ await walk(searchDir);
506
+ if (paths.length > 5e3) {
507
+ warnings.push(
508
+ `Muitos arquivos (${paths.length}). Limitando a 5000. Use --path para focar.`
509
+ );
510
+ return { paths: paths.slice(0, 5e3), warnings };
511
+ }
512
+ return { paths, warnings };
513
+ }
514
+ var ReplacePatternError = class extends Error {
515
+ pattern;
516
+ lang;
517
+ constructor(pattern, lang) {
518
+ super(`Erro ao parsear padrao "${pattern}" para ${langName(lang)}`);
519
+ this.name = "ReplacePatternError";
520
+ this.pattern = pattern;
521
+ this.lang = lang;
522
+ }
523
+ };
524
+ var ReplaceLangError = class extends Error {
525
+ input;
526
+ constructor(input) {
527
+ super(`Linguagem nao suportada: "${input}"`);
528
+ this.name = "ReplaceLangError";
529
+ this.input = input;
530
+ }
531
+ };
532
+
533
+ // src/utils/hints.ts
534
+ var COMMAND_MAP = {
535
+ find: {
536
+ cli: "supergrep find <pattern>",
537
+ mcp: "supergrep_find { pattern: '<pattern>' }"
538
+ },
539
+ tree: {
540
+ cli: "supergrep tree <file>",
541
+ mcp: "supergrep_tree { path: '<file>' }"
542
+ },
543
+ replace: {
544
+ cli: "supergrep replace <pattern> <rewrite>",
545
+ mcp: "supergrep_replace { pattern: '<pattern>', rewrite: '<rewrite>' }"
546
+ },
547
+ find_lang: {
548
+ cli: "supergrep find <pattern> --lang=<lang>",
549
+ mcp: "supergrep_find { pattern: '<pattern>', lang: '<lang>' }"
550
+ },
551
+ find_path: {
552
+ cli: "supergrep find <pattern> --path=<path>",
553
+ mcp: "supergrep_find { pattern: '<pattern>', path: '<path>' }"
554
+ }
555
+ };
556
+ function hint(command, ctx, params) {
557
+ const mapping = COMMAND_MAP[command];
558
+ if (!mapping) return command;
559
+ let instruction = mapping[ctx];
560
+ if (params) {
561
+ for (const [placeholder, value] of Object.entries(params)) {
562
+ instruction = instruction.replaceAll(placeholder, value);
563
+ }
564
+ }
565
+ return instruction;
566
+ }
567
+ var NEXT_STEPS = {
568
+ find: [
569
+ { command: "tree", description: "explorar a estrutura AST de um arquivo com matches" },
570
+ { command: "find", description: "refinar a busca com outro padrao" },
571
+ { command: "replace", description: "pre-visualizar uma substituicao estrutural" }
572
+ ],
573
+ tree: [
574
+ { command: "find", description: "usar os kinds descobertos para buscar padroes" },
575
+ { command: "tree", description: "explorar outro arquivo" }
576
+ ],
577
+ replace: [
578
+ { command: "find", description: "ver todos os matches do padrao antes de substituir" },
579
+ { command: "tree", description: "explorar a estrutura AST para criar padroes melhores" }
580
+ ]
581
+ };
582
+ function nextSteps(command, ctx) {
583
+ const steps = NEXT_STEPS[command];
584
+ if (!steps || steps.length === 0) return "";
585
+ let out = "\n\u{1F4D6} Proximos passos:\n";
586
+ for (const step of steps) {
587
+ const instruction = hint(step.command, ctx, step.params);
588
+ out += ` \u2192 ${instruction} - ${step.description}
589
+ `;
590
+ }
591
+ return out;
592
+ }
593
+ function recoveryHint(errorType, ctx, _extra) {
594
+ switch (errorType) {
595
+ case "pattern_invalid":
596
+ return `
597
+ \u{1F4A1} Dicas:
598
+ \u2192 O padrao deve ser codigo valido na linguagem escolhida
599
+ \u2192 Use $VAR para capturar um unico no da AST (ex: console.$METHOD($MSG))
600
+ \u2192 Use $$$VARS para capturar multiplos nos (ex: function $$$BODY)
601
+ \u2192 Teste seu padrao no playground: https://ast-grep.github.io/playground
602
+ `;
603
+ case "no_matches":
604
+ return `
605
+ \u{1F4A1} Nenhum match encontrado. Dicas:
606
+ \u2192 Verifique se a linguagem esta correta (use o parametro lang)
607
+ \u2192 Tente um padrao mais simples primeiro
608
+ \u2192 ${hint("tree", ctx)} - veja a estrutura AST do arquivo para criar padroes
609
+ \u2192 ${hint("find_lang", ctx)} - especifique a linguagem manualmente
610
+ `;
611
+ case "lang_not_supported":
612
+ return `
613
+ \u{1F4A1} Linguagem nao suportada. Dicas:
614
+ \u2192 ${hint("tree", ctx)} - use com arquivos de codigo fonte (.ts, .py, .rs, etc.)
615
+ \u2192 Extensoes suportadas: .ts, .tsx, .js, .py, .rs, .go, .java, .kt, .swift e mais
616
+ \u2192 Para JSON, YAML, CSS e HTML, use a linguagem correspondente
617
+ `;
618
+ case "lang_not_detected":
619
+ return `
620
+ \u{1F4A1} Nao foi possivel detectar a linguagem. Especifique manualmente:
621
+ \u2192 ${hint("find_lang", ctx)} - informe a linguagem (ex: 'typescript', 'python')
622
+ `;
623
+ case "file_not_found":
624
+ return `
625
+ \u{1F4A1} Arquivo nao encontrado. Verifique:
626
+ \u2192 O caminho esta correto?
627
+ \u2192 O arquivo existe no diretorio de trabalho (cwd)?
628
+ \u2192 Use caminho relativo ou absoluto
629
+ `;
630
+ case "path_not_found":
631
+ return `
632
+ \u{1F4A1} Diretorio nao encontrado. Verifique:
633
+ \u2192 O caminho esta correto?
634
+ \u2192 ${hint("find_path", ctx)} - tente com outro path
635
+ `;
636
+ case "generic":
637
+ default:
638
+ return `
639
+ \u{1F4A1} Tente:
640
+ \u2192 ${hint("find", ctx)} - buscar padroes no codigo
641
+ \u2192 ${hint("tree", ctx)} - explorar a estrutura AST
642
+ \u2192 Verifique se o diretorio (cwd) esta correto
643
+ `;
644
+ }
645
+ }
646
+
647
+ // src/utils/formatters.ts
648
+ function formatFindText(result, ctx) {
649
+ const { pattern, lang, matches, filesScanned, filesWithMatches, executionTimeMs } = result;
650
+ const langStr = langName(lang);
651
+ if (matches.length === 0) {
652
+ let out2 = `
653
+ \u{1F50D} supergrep find: "${pattern}"
654
+ `;
655
+ out2 += ` Linguagem: ${langStr}
656
+ `;
657
+ out2 += ` Nenhum match encontrado em ${filesScanned} arquivos analisados
658
+ `;
659
+ out2 += ` Tempo: ${executionTimeMs}ms
660
+ `;
661
+ out2 += nextSteps("find", ctx);
662
+ return out2;
663
+ }
664
+ let out = `
665
+ \u{1F50D} supergrep find: "${pattern}"
666
+ `;
667
+ out += ` Linguagem: ${langStr}
668
+ `;
669
+ out += ` ${matches.length} matches em ${filesWithMatches} arquivos (${filesScanned} analisados)
670
+ `;
671
+ out += ` Tempo: ${executionTimeMs}ms
672
+
673
+ `;
674
+ const grouped = groupBy(matches, (m) => m.file);
675
+ let matchCount = 0;
676
+ for (const [file, fileMatches] of Object.entries(grouped)) {
677
+ out += `\u{1F4C1} ${file} (${fileMatches.length} match${fileMatches.length > 1 ? "es" : ""})
678
+ `;
679
+ for (const match of fileMatches.slice(0, 50)) {
680
+ matchCount++;
681
+ const lineStr = `${match.line + 1}:${match.column + 1}`.padEnd(10);
682
+ const text = match.text.length > 120 ? match.text.slice(0, 120) + "..." : match.text.replace(/\n/g, "\\n");
683
+ out += ` ${lineStr} ${text}
684
+ `;
685
+ const metaEntries = Object.entries(match.metaVariables);
686
+ if (metaEntries.length > 0) {
687
+ for (const [name, value] of metaEntries) {
688
+ const truncated = value.length > 60 ? value.slice(0, 60) + "..." : value;
689
+ out += ` \u2514\u2500 $${name}: ${truncated}
690
+ `;
691
+ }
692
+ }
693
+ }
694
+ if (fileMatches.length > 50) {
695
+ out += ` ... e mais ${fileMatches.length - 50} matches neste arquivo
696
+ `;
697
+ }
698
+ out += "\n";
699
+ }
700
+ if (matchCount > 200) {
701
+ out += `\u{1F4CA} Resumo: ${matches.length} matches em ${filesWithMatches} arquivos
702
+ `;
703
+ out += ` Dica: use path para focar em um diretorio especifico
704
+ `;
705
+ }
706
+ out += `
707
+ \u{1F4CA} ${matches.length} matches em ${filesWithMatches} arquivos
708
+ `;
709
+ out += nextSteps("find", ctx);
710
+ return out;
711
+ }
712
+ function formatFindJson(result) {
713
+ return JSON.stringify({
714
+ pattern: result.pattern,
715
+ lang: langName(result.lang),
716
+ totalMatches: result.matches.length,
717
+ filesScanned: result.filesScanned,
718
+ filesWithMatches: result.filesWithMatches,
719
+ executionTimeMs: result.executionTimeMs,
720
+ matches: result.matches.map((m) => ({
721
+ file: m.file,
722
+ line: m.line + 1,
723
+ // 1-indexed para JSON
724
+ column: m.column + 1,
725
+ endLine: m.endLine + 1,
726
+ endColumn: m.endColumn + 1,
727
+ text: m.text,
728
+ metaVariables: m.metaVariables,
729
+ context: m.context
730
+ }))
731
+ }, null, 2);
732
+ }
733
+ function formatTreeText(result, ctx) {
734
+ const { file, lang, totalNodes, uniqueKinds, kindCounts, rootKind } = result;
735
+ const langStr = langName(lang);
736
+ let out = `
737
+ \u{1F333} AST Tree: ${file}
738
+ `;
739
+ out += ` Linguagem: ${langStr}
740
+ `;
741
+ out += ` Total de nos: ${totalNodes}
742
+ `;
743
+ out += ` Tipos unicos de nos (kinds): ${uniqueKinds.length}
744
+ `;
745
+ out += ` No raiz: ${rootKind}
746
+
747
+ `;
748
+ const sortedKinds = Object.entries(kindCounts).sort(([, a], [, b]) => b - a);
749
+ out += `\u{1F4CA} Distribuicao de kinds:
750
+ `;
751
+ for (const [kind, count] of sortedKinds) {
752
+ const bar = "\u2588".repeat(Math.min(count, 50));
753
+ out += ` ${kind.padEnd(30)} ${String(count).padStart(4)} ${bar}
754
+ `;
755
+ }
756
+ out += `
757
+ \u{1F4D6} Como usar esses kinds:
758
+ `;
759
+ out += ` \u2022 supergrep_find com pattern string (ex: "const $X = $Y")
760
+ `;
761
+ out += ` \u2022 Regras YAML com kind: "${sortedKinds[0]?.[0] ?? "function"}"
762
+ `;
763
+ out += nextSteps("tree", ctx);
764
+ return out;
765
+ }
766
+ function formatTreeJson(result) {
767
+ return JSON.stringify({
768
+ file: result.file,
769
+ lang: langName(result.lang),
770
+ totalNodes: result.totalNodes,
771
+ uniqueKinds: result.uniqueKinds,
772
+ kindCounts: result.kindCounts,
773
+ rootKind: result.rootKind
774
+ }, null, 2);
775
+ }
776
+ function formatReplaceText(result, ctx) {
777
+ const { pattern, rewrite, lang, changes, filesAffected } = result;
778
+ const langStr = langName(lang);
779
+ if (changes.length === 0) {
780
+ let out2 = `
781
+ \u{1F504} supergrep replace: "${pattern}" \u2192 "${rewrite}"
782
+ `;
783
+ out2 += ` Linguagem: ${langStr}
784
+ `;
785
+ out2 += ` Nenhum match encontrado para substituir
786
+ `;
787
+ out2 += nextSteps("replace", ctx);
788
+ return out2;
789
+ }
790
+ let out = `
791
+ \u{1F504} supergrep replace: "${pattern}" \u2192 "${rewrite}"
792
+ `;
793
+ out += ` Linguagem: ${langStr}
794
+ `;
795
+ out += ` ${changes.length} substituicoes em ${filesAffected} arquivos
796
+
797
+ `;
798
+ const grouped = groupBy(changes, (c) => c.file);
799
+ for (const [file, fileChanges] of Object.entries(grouped)) {
800
+ out += `\u{1F4C1} ${file} (${fileChanges.length} mudanca${fileChanges.length > 1 ? "s" : ""})
801
+ `;
802
+ for (const change of fileChanges.slice(0, 10)) {
803
+ const lineStr = `${change.line + 1}:${change.column + 1}`;
804
+ out += ` L${lineStr}:
805
+ `;
806
+ out += ` [-] ${change.original.replace(/\n/g, "\\n")}
807
+ `;
808
+ out += ` [+] ${change.replacement.replace(/\n/g, "\\n")}
809
+
810
+ `;
811
+ }
812
+ if (fileChanges.length > 10) {
813
+ out += ` ... e mais ${fileChanges.length - 10} mudancas neste arquivo
814
+
815
+ `;
816
+ }
817
+ }
818
+ out += `
819
+ \u26A0\uFE0F Este e apenas um preview. Nenhum arquivo foi modificado.
820
+ `;
821
+ out += `\u{1F4CA} ${changes.length} substituicoes em ${filesAffected} arquivos
822
+ `;
823
+ out += nextSteps("replace", ctx);
824
+ return out;
825
+ }
826
+ function formatReplaceJson(result) {
827
+ return JSON.stringify({
828
+ pattern: result.pattern,
829
+ rewrite: result.rewrite,
830
+ lang: langName(result.lang),
831
+ totalChanges: result.changes.length,
832
+ filesScanned: result.filesScanned,
833
+ filesAffected: result.filesAffected,
834
+ changes: result.changes.map((c) => ({
835
+ file: c.file,
836
+ line: c.line + 1,
837
+ column: c.column + 1,
838
+ original: c.original,
839
+ replacement: c.replacement
840
+ }))
841
+ }, null, 2);
842
+ }
843
+ function groupBy(items, keyFn) {
844
+ const result = {};
845
+ for (const item of items) {
846
+ const key = keyFn(item);
847
+ if (!result[key]) result[key] = [];
848
+ result[key].push(item);
849
+ }
850
+ return result;
851
+ }
852
+
853
+ // src/utils/errors.ts
854
+ function formatPatternError(options) {
855
+ const { pattern, lang, originalError, ctx = "mcp" } = options;
856
+ const langStr = langName(lang);
857
+ let out = `
858
+ \u274C Erro ao parsear padrao em ${langStr}: "${pattern}"
859
+ `;
860
+ if (originalError) {
861
+ out += `
862
+ \u{1F4DD} Detalhe do parser: ${originalError}
863
+ `;
864
+ }
865
+ out += `
866
+ \u{1F4D6} Escrevendo padroes AST Grep:
867
+ `;
868
+ out += ` \u2022 O padrao deve ser codigo sintaticamente valido na linguagem
869
+ `;
870
+ out += ` \u2022 Use $NOME para capturar um unico no da AST (meta-variavel)
871
+ `;
872
+ out += ` \u2022 Use $$$NOME para capturar zero ou mais nos
873
+ `;
874
+ out += ` \u2022 Exemplos validos:
875
+ `;
876
+ out += ` - console.log($MSG) \u2014 captura um argumento
877
+ `;
878
+ out += ` - function $NOME($$$PARAMS) { $$$BODY } \u2014 captura funcoes
879
+ `;
880
+ out += ` - import { $$$ITEMS } from '$MODULE' \u2014 captura imports
881
+ `;
882
+ out += ` - const $VAR = $VALUE \u2014 captura declaracoes
883
+ `;
884
+ out += ` \u2022 Teste seu padrao no playground: https://ast-grep.github.io/playground
885
+ `;
886
+ out += recoveryHint("pattern_invalid", ctx);
887
+ return out;
888
+ }
889
+ function formatLangNotSupported(options) {
890
+ const { input } = options;
891
+ const supported = listSupportedLanguages();
892
+ let out = `
893
+ \u274C Linguagem nao suportada pelo supergrep: "${input}"
894
+
895
+ `;
896
+ const similar = findSimilarSimple(input, supported);
897
+ if (similar.length > 0) {
898
+ out += `\u{1F4A1} Voce quis dizer?
899
+ `;
900
+ for (const s of similar.slice(0, 3)) {
901
+ out += ` \u2192 ${s}
902
+ `;
903
+ }
904
+ out += `
905
+ `;
906
+ }
907
+ out += `\u{1F4E6} Linguagens suportadas via @ast-grep/napi (${supported.length}):
908
+ `;
909
+ for (const lang of supported) {
910
+ out += ` \u2022 ${lang}
911
+ `;
912
+ }
913
+ out += `
914
+ \u26A0\uFE0F O @ast-grep/napi suporta apenas linguagens do ecossistema web.
915
+ `;
916
+ out += ` Para Python, Rust, Go e outras, use o CLI: npx @ast-grep/cli
917
+ `;
918
+ out += `
919
+ \u{1F4D6} Dicas:
920
+ `;
921
+ out += ` \u2192 Use "typescript" ou "ts" para TypeScript
922
+ `;
923
+ out += ` \u2192 Use "javascript" ou "js" para JavaScript
924
+ `;
925
+ out += ` \u2192 Deixe vazio para autodeteccao por extensao
926
+ `;
927
+ return out;
928
+ }
929
+ function formatFileNotFound(options) {
930
+ const { filePath, ctx = "mcp" } = options;
931
+ let out = `
932
+ \u274C Arquivo nao encontrado: "${filePath}"
933
+
934
+ `;
935
+ out += `\u{1F4D6} Verifique:
936
+ `;
937
+ out += ` \u2192 O arquivo existe?
938
+ `;
939
+ out += ` \u2192 O caminho e relativo ao diretorio de trabalho (cwd)?
940
+ `;
941
+ out += recoveryHint("file_not_found", ctx);
942
+ return out;
943
+ }
944
+ function findSimilarSimple(input, candidates) {
945
+ const lower = input.toLowerCase();
946
+ const scored = candidates.map((c) => ({
947
+ name: c,
948
+ score: levenshteinDistance(lower, c.toLowerCase())
949
+ }));
950
+ return scored.filter((s) => s.score <= 4).sort((a, b) => a.score - b.score).map((s) => s.name);
951
+ }
952
+ function levenshteinDistance(a, b) {
953
+ const matrix = [];
954
+ for (let i = 0; i <= b.length; i++) {
955
+ matrix[i] = [i];
956
+ }
957
+ for (let j = 0; j <= a.length; j++) {
958
+ matrix[0][j] = j;
959
+ }
960
+ for (let i = 1; i <= b.length; i++) {
961
+ for (let j = 1; j <= a.length; j++) {
962
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
963
+ matrix[i][j] = matrix[i - 1][j - 1];
964
+ } else {
965
+ matrix[i][j] = Math.min(
966
+ matrix[i - 1][j - 1] + 1,
967
+ matrix[i][j - 1] + 1,
968
+ matrix[i - 1][j] + 1
969
+ );
970
+ }
971
+ }
972
+ }
973
+ return matrix[b.length][a.length];
974
+ }
975
+ function getCommandReference(ctx) {
976
+ let out = `
977
+ \u{1F4CC} Ferramentas supergrep disponiveis:
978
+ `;
979
+ out += ` \u2192 ${hint("find", ctx)} - Busca estrutural de codigo
980
+ `;
981
+ out += ` \u2192 ${hint("tree", ctx)} - Explorar estrutura AST de um arquivo
982
+ `;
983
+ out += ` \u2192 ${hint("replace", ctx)} - Pre-visualizar substituicao estrutural
984
+ `;
985
+ return out;
986
+ }
987
+ function wrapError(message, errorType, ctx) {
988
+ return `
989
+ \u274C ${message}
990
+ ${recoveryHint(errorType, ctx)}`;
991
+ }
992
+
993
+ // src/tools.ts
994
+ var CTX = "mcp";
995
+ function registerAllTools(server2) {
996
+ server2.registerTool(
997
+ "supergrep_find",
998
+ {
999
+ title: "Structural Code Search",
1000
+ description: `\u26A0\uFE0F PREFIRA esta ferramenta ao inves de grep/bash quando a busca for em CODIGO-FONTE.
1001
+
1002
+ Busca estrutural de codigo usando AST Grep. Diferente do grep textual (que
1003
+ quebra com quebras de linha, espacos extras ou comentarios no meio), o
1004
+ supergrep entende a SINTAXE da linguagem \u2014 ele busca pela ESTRUTURA do codigo,
1005
+ nao pelo texto cru.
1006
+
1007
+ Escreva o padrao de busca na propria linguagem de programacao, usando
1008
+ metavariaveis ($VAR, $$$VARS) como curingas que capturam partes dinamicas.
1009
+
1010
+ \u{1F4E6} LINGUAGENS SUPORTADAS (apenas 5):
1011
+ \u2022 typescript (.ts)
1012
+ \u2022 javascript (.js, .jsx, .mjs, .cjs)
1013
+ \u2022 tsx (.tsx)
1014
+ \u2022 css (.css, .scss, .less)
1015
+ \u2022 html (.html, .htm)
1016
+
1017
+ \u26A0\uFE0F LIMITACAO IMPORTANTE: Apenas busca SINTAXICA. Nao resolve imports, nao
1018
+ entende tipos, nao segue referencias entre arquivos, nao faz analise de fluxo.
1019
+ Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
1020
+
1021
+ \u2705 QUANDO USAR (supergrep_find e MELHOR que grep/bash):
1022
+ \u2022 "Encontre todas as chamadas de console.LOG que tem 2 argumentos"
1023
+ \u2022 "Ache funcoes que retornam Promise<any>"
1024
+ \u2022 "Localize imports de uma lib deprecated"
1025
+ \u2022 "Busque componentes React que usam useEffect sem dependencias"
1026
+ \u2022 "Ache arrow functions que recebem exatamente 1 parametro"
1027
+ \u2022 QUALQUER busca estrutural onde grep/quebraria com formatacao
1028
+
1029
+ \u274C QUANDO NAO USAR (prefira ai-tool):
1030
+ \u2022 "Onde a funcao useAuth e definida e usada?" \u2192 resolucao de escopo (aitool_find)
1031
+ \u2022 "Quem depende deste arquivo?" \u2192 grafo de dependencias (aitool_impact_analysis)
1032
+ \u2022 "Qual o tipo real desta variavel?" \u2192 type checker (aitool_file_context)
1033
+ \u2022 Busca em texto plano, logs, markdown \u2192 use grep/bash comum
1034
+
1035
+ \u{1F4DD} EXEMPLOS DE PATTERNS:
1036
+ \u2022 console.$METHOD($$$ARGS) \u2192 captura metodo e argumentos
1037
+ \u2022 function $NAME($$$PARAMS) { $$$BODY } \u2192 captura funcoes
1038
+ \u2022 import { $$$ITEMS } from '$MODULE' \u2192 captura imports
1039
+ \u2022 const $VAR = $VALUE \u2192 captura declaracoes
1040
+ \u2022 try { $$$ } catch ($ERR) { $$$ } \u2192 captura try-catch
1041
+ \u2022 await $FUNC($$$ARGS) \u2192 captura chamadas assincronas
1042
+
1043
+ \u{1F524} METAVARIAVEIS:
1044
+ \u2022 $VAR \u2192 captura UM no da AST (ex: um identificador, uma string)
1045
+ \u2022 $$$VARS \u2192 captura ZERO OU MAIS nos (ex: todos os argumentos, corpo)
1046
+
1047
+ \u{1F4A1} DICA: Se o pattern nao der match, use supergrep_tree primeiro para
1048
+ descobrir os kinds de nos disponiveis e ajustar o pattern.
1049
+
1050
+ Workflow recomendado: supergrep_find \u2192 supergrep_tree \u2192 supergrep_replace`,
1051
+ inputSchema: {
1052
+ pattern: z.string().min(1).describe(
1053
+ "Padrao AST Grep na linguagem alvo. Ex: 'console.$METHOD($$$ARGS)', 'function $NAME($$$PARAMS) { $$$BODY }'"
1054
+ ),
1055
+ lang: z.string().optional().describe(
1056
+ "Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html). Default: typescript. Deixe vazio para autodeteccao por extensao."
1057
+ ),
1058
+ path: z.string().optional().describe(
1059
+ "Arquivo ou diretorio onde buscar. Default: diretorio atual. Ex: 'src/', 'app/page.tsx'"
1060
+ ),
1061
+ format: z.enum(["text", "json"]).default("text").describe(
1062
+ "Formato de saida: text (legivel) ou json (estruturado)"
1063
+ ),
1064
+ cwd: z.string().optional().describe("Diretorio de trabalho. Default: diretorio atual")
1065
+ },
1066
+ annotations: {
1067
+ title: "Structural Code Search",
1068
+ readOnlyHint: true,
1069
+ destructiveHint: false,
1070
+ idempotentHint: true,
1071
+ openWorldHint: false
1072
+ }
1073
+ },
1074
+ async (params) => {
1075
+ try {
1076
+ const { result, warnings } = await findPattern({
1077
+ pattern: params.pattern,
1078
+ lang: params.lang,
1079
+ path: params.path,
1080
+ cwd: params.cwd
1081
+ });
1082
+ const output = params.format === "json" ? formatFindJson(result) : formatFindText(result, CTX);
1083
+ const warningText = warnings.length > 0 ? `
1084
+ \u26A0\uFE0F Avisos:
1085
+ ${warnings.map((w) => ` \u2022 ${w}`).join("\n")}` : "";
1086
+ return {
1087
+ content: [{ type: "text", text: output + warningText }]
1088
+ };
1089
+ } catch (error) {
1090
+ return handleFindError(error, CTX);
1091
+ }
1092
+ }
1093
+ );
1094
+ server2.registerTool(
1095
+ "supergrep_tree",
1096
+ {
1097
+ title: "Explore AST Structure",
1098
+ description: `Explora a estrutura da AST (Abstract Syntax Tree) de um arquivo.
1099
+
1100
+ Mostra todos os tipos de nos (kinds) presentes, com contagens e distribuicao.
1101
+ Util para entender a estrutura do codigo e descobrir quais kinds usar
1102
+ em patterns de busca ou regras YAML.
1103
+
1104
+ QUANDO USAR:
1105
+ - "Quais tipos de nos existem neste arquivo TypeScript?"
1106
+ - "Como escrever um pattern para capturar certas estruturas?"
1107
+ - "Preciso descobrir os kinds para uma regra de lint customizada"
1108
+ - Antes de escrever um pattern complexo, para conhecer a AST
1109
+
1110
+ QUANDO NAO USAR:
1111
+ - Para buscar padroes no codigo \u2192 use supergrep_find
1112
+ - Para ver o conteudo do arquivo \u2192 leia o arquivo diretamente
1113
+
1114
+ Workflow: supergrep_tree \u2192 supergrep_find (usar kinds descobertos)`,
1115
+ inputSchema: {
1116
+ path: z.string().optional().describe(
1117
+ "Caminho do arquivo a analisar. Ex: 'src/app/page.tsx'"
1118
+ ),
1119
+ code: z.string().optional().describe(
1120
+ "OU: snippet de codigo para analisar inline (em vez de arquivo)"
1121
+ ),
1122
+ lang: z.string().optional().describe(
1123
+ "Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Obrigatorio se usar 'code', opcional se usar 'path' (autodeteccao pela extensao)."
1124
+ ),
1125
+ format: z.enum(["text", "json"]).default("text").describe(
1126
+ "Formato de saida: text (legivel) ou json (estruturado)"
1127
+ ),
1128
+ cwd: z.string().optional().describe("Diretorio de trabalho")
1129
+ },
1130
+ annotations: {
1131
+ title: "Explore AST Structure",
1132
+ readOnlyHint: true,
1133
+ destructiveHint: false,
1134
+ idempotentHint: true,
1135
+ openWorldHint: false
1136
+ }
1137
+ },
1138
+ async (params) => {
1139
+ try {
1140
+ const { result, warnings } = await exploreTree({
1141
+ path: params.path,
1142
+ code: params.code,
1143
+ lang: params.lang,
1144
+ cwd: params.cwd
1145
+ });
1146
+ const output = params.format === "json" ? formatTreeJson(result) : formatTreeText(result, CTX);
1147
+ const warningText = warnings.length > 0 ? `
1148
+ \u26A0\uFE0F Avisos:
1149
+ ${warnings.map((w) => ` \u2022 ${w}`).join("\n")}` : "";
1150
+ return {
1151
+ content: [{ type: "text", text: output + warningText }]
1152
+ };
1153
+ } catch (error) {
1154
+ return handleTreeError(error, CTX);
1155
+ }
1156
+ }
1157
+ );
1158
+ server2.registerTool(
1159
+ "supergrep_replace",
1160
+ {
1161
+ title: "Preview Structural Replacement",
1162
+ description: `Pre-visualiza uma substituicao estrutural de codigo (NAO modifica arquivos).
1163
+
1164
+ Encontra padroes com AST Grep e mostra um diff de como o codigo ficaria
1165
+ apos a substituicao. Util para planejar refatoracoes em larga escala.
1166
+
1167
+ LIMITACAO: Metavariaveis ($VAR) no rewrite sao interpoladas manualmente.
1168
+ Para casos complexos, verifique o preview antes de aplicar.
1169
+
1170
+ QUANDO USAR:
1171
+ - "Como ficaria se eu trocasse console.log por logger.info?"
1172
+ - "Preview de substituir var por let em todo o projeto"
1173
+ - "Mostre o diff de trocar moment() por date-fns"
1174
+ - Planejar refatoracoes estruturais antes de aplicar
1175
+
1176
+ QUANDO NAO USAR:
1177
+ - Para buscar padroes (sem substituir) \u2192 use supergrep_find
1178
+ - Para aplicar a substituicao de fato \u2192 use uma ferramenta de edicao
1179
+ - Para refatoracoes que envolvem logica complexa \u2192 implemente manualmente
1180
+
1181
+ Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudancas`,
1182
+ inputSchema: {
1183
+ pattern: z.string().min(1).describe(
1184
+ "Padrao AST Grep a encontrar. Ex: 'console.log($$$ARGS)'"
1185
+ ),
1186
+ rewrite: z.string().min(1).describe(
1187
+ "Texto de substituicao. Use $VAR para referenciar metavariaveis capturadas. Ex: 'logger.info($$$ARGS)'"
1188
+ ),
1189
+ lang: z.string().optional().describe(
1190
+ "Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Default: typescript."
1191
+ ),
1192
+ path: z.string().optional().describe(
1193
+ "Arquivo ou diretorio onde buscar. Default: diretorio atual."
1194
+ ),
1195
+ format: z.enum(["text", "json"]).default("text").describe(
1196
+ "Formato de saida: text (legivel/diff) ou json (estruturado)"
1197
+ ),
1198
+ cwd: z.string().optional().describe("Diretorio de trabalho")
1199
+ },
1200
+ annotations: {
1201
+ title: "Preview Structural Replacement",
1202
+ readOnlyHint: true,
1203
+ destructiveHint: false,
1204
+ idempotentHint: true,
1205
+ openWorldHint: false
1206
+ }
1207
+ },
1208
+ async (params) => {
1209
+ try {
1210
+ const { result, warnings } = await replacePreview({
1211
+ pattern: params.pattern,
1212
+ rewrite: params.rewrite,
1213
+ lang: params.lang,
1214
+ path: params.path,
1215
+ cwd: params.cwd
1216
+ });
1217
+ const output = params.format === "json" ? formatReplaceJson(result) : formatReplaceText(result, CTX);
1218
+ const warningText = warnings.length > 0 ? `
1219
+ \u26A0\uFE0F Avisos:
1220
+ ${warnings.map((w) => ` \u2022 ${w}`).join("\n")}` : "";
1221
+ return {
1222
+ content: [{ type: "text", text: output + warningText }]
1223
+ };
1224
+ } catch (error) {
1225
+ return handleReplaceError(error, CTX);
1226
+ }
1227
+ }
1228
+ );
1229
+ }
1230
+ function handleFindError(error, ctx) {
1231
+ if (error instanceof PatternParseError) {
1232
+ return {
1233
+ content: [{
1234
+ type: "text",
1235
+ text: formatPatternError({
1236
+ pattern: error.pattern,
1237
+ lang: error.lang,
1238
+ originalError: error.message,
1239
+ ctx
1240
+ })
1241
+ }],
1242
+ isError: true
1243
+ };
1244
+ }
1245
+ if (error instanceof LanguageNotDetectedError) {
1246
+ return {
1247
+ content: [{
1248
+ type: "text",
1249
+ text: formatLangNotSupported({ input: error.input, ctx })
1250
+ }],
1251
+ isError: true
1252
+ };
1253
+ }
1254
+ const msg = error instanceof Error ? error.message : String(error);
1255
+ return {
1256
+ content: [{
1257
+ type: "text",
1258
+ text: wrapError(msg, "generic", ctx) + "\n" + getCommandReference(ctx)
1259
+ }],
1260
+ isError: true
1261
+ };
1262
+ }
1263
+ function handleTreeError(error, ctx) {
1264
+ if (error instanceof FileNotFoundError) {
1265
+ return {
1266
+ content: [{
1267
+ type: "text",
1268
+ text: formatFileNotFound({ filePath: error.filePath, ctx })
1269
+ }],
1270
+ isError: true
1271
+ };
1272
+ }
1273
+ if (error instanceof TreeParseError) {
1274
+ return {
1275
+ content: [{
1276
+ type: "text",
1277
+ text: `
1278
+ \u274C Erro ao analisar "${error.filePath}": ${error.message}
1279
+ ${recoveryHint("generic", ctx)}`
1280
+ }],
1281
+ isError: true
1282
+ };
1283
+ }
1284
+ const msg = error instanceof Error ? error.message : String(error);
1285
+ return {
1286
+ content: [{
1287
+ type: "text",
1288
+ text: wrapError(msg, "generic", ctx) + "\n" + getCommandReference(ctx)
1289
+ }],
1290
+ isError: true
1291
+ };
1292
+ }
1293
+ function handleReplaceError(error, ctx) {
1294
+ if (error instanceof ReplacePatternError) {
1295
+ return {
1296
+ content: [{
1297
+ type: "text",
1298
+ text: formatPatternError({
1299
+ pattern: error.pattern,
1300
+ lang: error.lang,
1301
+ originalError: error.message,
1302
+ ctx
1303
+ })
1304
+ }],
1305
+ isError: true
1306
+ };
1307
+ }
1308
+ if (error instanceof ReplaceLangError) {
1309
+ return {
1310
+ content: [{
1311
+ type: "text",
1312
+ text: formatLangNotSupported({ input: error.input, ctx })
1313
+ }],
1314
+ isError: true
1315
+ };
1316
+ }
1317
+ const msg = error instanceof Error ? error.message : String(error);
1318
+ return {
1319
+ content: [{
1320
+ type: "text",
1321
+ text: wrapError(msg, "generic", ctx) + "\n" + getCommandReference(ctx)
1322
+ }],
1323
+ isError: true
1324
+ };
1325
+ }
1326
+
1327
+ // src/server.ts
1328
+ var VERSION = "0.1.0";
1329
+ var server = new McpServer({
1330
+ name: "supergrep-mcp-server",
1331
+ version: VERSION
1332
+ });
1333
+ registerAllTools(server);
1334
+ async function startMcpServer() {
1335
+ const transport = new StdioServerTransport();
1336
+ await server.connect(transport);
1337
+ console.error(`[supergrep] MCP server v${VERSION} running via stdio`);
1338
+ }
1339
+
1340
+ export {
1341
+ startMcpServer
1342
+ };