@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.
- package/README.md +174 -0
- package/dist/chunk-4MPFPILM.js +1342 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +10 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +10 -0
- package/package.json +60 -0
|
@@ -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
|
+
};
|