@justmpm/supergrep 0.1.0 → 0.2.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 CHANGED
@@ -16,7 +16,7 @@ npx @justmpm/supergrep
16
16
 
17
17
  ```bash
18
18
  npm install -g @justmpm/supergrep
19
- # ou use direto:
19
+ # ou use direto sem instalar:
20
20
  npx @justmpm/supergrep
21
21
  ```
22
22
 
@@ -24,6 +24,8 @@ npx @justmpm/supergrep
24
24
 
25
25
  ### OpenCode / Claude Code
26
26
 
27
+ #### Via npx (sem instalação)
28
+
27
29
  Adicione ao `.mcp.json` do projeto:
28
30
 
29
31
  ```json
@@ -37,11 +39,29 @@ Adicione ao `.mcp.json` do projeto:
37
39
  }
38
40
  ```
39
41
 
42
+ #### Com instalação local
43
+
44
+ ```bash
45
+ npm install -g @justmpm/supergrep
46
+ ```
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "supergrep": {
52
+ "command": "supergrep"
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
40
58
  ### Claude Desktop
41
59
 
42
60
  **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
43
61
  **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
44
62
 
63
+ #### Via npx (sem instalação)
64
+
45
65
  ```json
46
66
  {
47
67
  "mcpServers": {
@@ -53,6 +73,22 @@ Adicione ao `.mcp.json` do projeto:
53
73
  }
54
74
  ```
55
75
 
76
+ #### Com instalação local
77
+
78
+ ```bash
79
+ npm install -g @justmpm/supergrep
80
+ ```
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "supergrep": {
86
+ "command": "supergrep"
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
56
92
  ---
57
93
 
58
94
  ## Ferramentas MCP
@@ -6,9 +6,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6
6
  import { z } from "zod";
7
7
 
8
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";
9
+ import { findInFiles } from "@ast-grep/napi";
10
+ import { relative, resolve } from "path";
11
+ import { stat } from "fs/promises";
12
12
 
13
13
  // src/utils/lang.ts
14
14
  import { Lang } from "@ast-grep/napi";
@@ -65,192 +65,165 @@ function langName(lang) {
65
65
  };
66
66
  return friendly[langStr] ?? langStr;
67
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
68
 
98
69
  // src/tools/find.ts
99
- var META_VAR_RE = /\$(\$\$)?([A-Z_][A-Z0-9_]*)/g;
70
+ var MULTI_META_RE = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
71
+ var SINGLE_META_RE = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
100
72
  async function findPattern(options) {
101
73
  const startTime = Date.now();
102
74
  const warnings = [];
103
75
  const cwd = options.cwd ?? process.cwd();
104
- const lang = resolveLangForOptions(options, cwd, warnings);
105
- if (!lang) {
76
+ let lang;
77
+ if (options.lang) {
78
+ lang = resolveLang(options.lang);
79
+ if (!lang) {
80
+ warnings.push(`Linguagem "${options.lang}" nao reconhecida. Usando autodeteccao...`);
81
+ }
82
+ }
83
+ const searchPath = resolve(cwd, options.path ?? ".");
84
+ try {
85
+ await stat(searchPath);
86
+ } catch {
87
+ warnings.push(`Path nao encontrado: ${searchPath}. Usando diretorio atual.`);
106
88
  throw new LanguageNotDetectedError(options.lang ?? "desconhecida");
107
89
  }
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);
90
+ const metaVars = extractMetaVarInfo(options.pattern);
91
+ const napiConfig = {
92
+ rule: { pattern: options.pattern }
93
+ };
94
+ const findConfig = {
95
+ paths: [searchPath],
96
+ matcher: napiConfig
97
+ };
98
+ if (lang) {
99
+ findConfig.languageGlobs = langToGlobs(lang);
100
+ }
101
+ const langForSearch = lang ?? resolveLang("typescript");
113
102
  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) {
103
+ try {
104
+ const fileResults = await findInFilesWrapper(langForSearch, findConfig);
105
+ for (const { file, nodes } of fileResults) {
106
+ for (const node of nodes) {
121
107
  const range = node.range();
108
+ const text = node.text();
122
109
  const match = {
123
- file: relative(cwd, filePath).replace(/\\/g, "/"),
110
+ file: relative(cwd, file).replace(/\\/g, "/"),
124
111
  line: range.start.line,
125
112
  column: range.start.column,
126
113
  endLine: range.end.line,
127
114
  endColumn: range.end.column,
128
- text: node.text(),
129
- metaVariables: {},
130
- context: extractContextLines(source, range.start.line, 2)
115
+ text,
116
+ metaVariables: {}
131
117
  };
132
- for (const varName of metaVarNames) {
133
- const metaNode = node.getMatch(varName);
134
- if (metaNode) {
135
- match.metaVariables[varName] = metaNode.text();
118
+ for (const { name, isMulti } of metaVars) {
119
+ if (isMulti) {
120
+ const multiNodes = node.getMultipleMatches(name);
121
+ if (multiNodes.length > 0) {
122
+ match.metaVariables[name] = multiNodes.map((n) => n.text()).join(", ");
123
+ }
124
+ } else {
125
+ const metaNode = node.getMatch(name);
126
+ if (metaNode) {
127
+ match.metaVariables[name] = metaNode.text();
128
+ }
136
129
  }
137
130
  }
138
131
  matches.push(match);
139
132
  }
140
- } catch {
141
- continue;
142
133
  }
134
+ } catch (err) {
135
+ const msg = err instanceof Error ? err.message : String(err);
136
+ if (lang) {
137
+ throw new PatternParseError(options.pattern, lang);
138
+ }
139
+ throw new Error(`Erro na busca: ${msg}`);
143
140
  }
144
- const uniqueFiles = new Set(matches.map((m) => m.file));
141
+ const filesWithMatchesSet = new Set(matches.map((m) => m.file));
145
142
  const executionTimeMs = Date.now() - startTime;
146
143
  return {
147
144
  result: {
148
145
  pattern: options.pattern,
149
- lang,
146
+ lang: langForSearch,
150
147
  matches,
151
- filesScanned: files.paths.length,
152
- filesWithMatches: uniqueFiles.size,
148
+ filesScanned: matches.length > 0 ? filesWithMatchesSet.size : 0,
149
+ filesWithMatches: filesWithMatchesSet.size,
153
150
  executionTimeMs
154
151
  },
155
152
  warnings
156
153
  };
157
154
  }
158
- function extractMetaVarNames(pattern) {
159
- const names = /* @__PURE__ */ new Set();
155
+ function extractMetaVarInfo(pattern) {
156
+ const seen = /* @__PURE__ */ new Set();
157
+ const result = [];
160
158
  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;
159
+ const multiRe = new RegExp(MULTI_META_RE.source, "g");
160
+ while ((match = multiRe.exec(pattern)) !== null) {
161
+ const name = match[1];
162
+ if (!seen.has(name)) {
163
+ seen.add(name);
164
+ result.push({ name, isMulti: true });
180
165
  }
181
166
  }
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;
167
+ const singleRe = new RegExp(SINGLE_META_RE.source, "g");
168
+ while ((match = singleRe.exec(pattern)) !== null) {
169
+ const name = match[1];
170
+ if (!seen.has(name)) {
171
+ seen.add(name);
172
+ result.push({ name, isMulti: false });
187
173
  }
188
174
  }
189
- warnings.push("Linguagem nao especificada, usando TypeScript como default.");
190
- return resolveLang("typescript");
175
+ return result;
191
176
  }
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
- }
177
+ var IGNORED_DIR_PATTERNS = [
178
+ /[/\\]node_modules[/\\]/,
179
+ /[/\\]dist[/\\]/,
180
+ /[/\\]build[/\\]/,
181
+ /[/\\]\.git[/\\]/,
182
+ /[/\\]\.next[/\\]/,
183
+ /[/\\]\.turbo[/\\]/,
184
+ /[/\\]coverage[/\\]/,
185
+ /[/\\]out[/\\]/
186
+ ];
187
+ function isIgnoredPath(filePath) {
188
+ const normalized = filePath.replace(/\\/g, "/");
189
+ const wrapped = `/${normalized}/`;
190
+ return IGNORED_DIR_PATTERNS.some((re) => re.test(wrapped));
202
191
  }
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 {
192
+ async function findInFilesWrapper(lang, config) {
193
+ const results = [];
194
+ let callbacksReceived = 0;
195
+ const totalFiles = await findInFiles(
196
+ lang,
197
+ config,
198
+ (err, nodes) => {
199
+ callbacksReceived++;
200
+ if (err || nodes.length === 0) return;
201
+ const file = nodes[0].getRoot()?.filename() ?? "unknown";
202
+ if (isIgnoredPath(file)) return;
203
+ results.push({ file, nodes });
239
204
  }
205
+ );
206
+ while (callbacksReceived < totalFiles) {
207
+ await new Promise((r) => setTimeout(r, 10));
240
208
  }
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
- );
209
+ return results;
210
+ }
211
+ function langToGlobs(lang) {
212
+ const langStr = String(lang);
213
+ switch (langStr) {
214
+ case "TypeScript":
215
+ return ["*.ts"];
216
+ case "JavaScript":
217
+ return ["*.js", "*.jsx", "*.mjs", "*.cjs"];
218
+ case "Tsx":
219
+ return ["*.tsx"];
220
+ case "Css":
221
+ return ["*.css", "*.scss", "*.less"];
222
+ case "Html":
223
+ return ["*.html", "*.htm"];
224
+ default:
225
+ return [];
252
226
  }
253
- return { paths, warnings };
254
227
  }
255
228
  var PatternParseError = class extends Error {
256
229
  pattern;
@@ -272,9 +245,9 @@ var LanguageNotDetectedError = class extends Error {
272
245
  };
273
246
 
274
247
  // 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";
248
+ import { parse } from "@ast-grep/napi";
249
+ import { readFile, stat as stat2 } from "fs/promises";
250
+ import { resolve as resolve2, relative as relative2, extname } from "path";
278
251
  async function exploreTree(options) {
279
252
  const warnings = [];
280
253
  const cwd = options.cwd ?? process.cwd();
@@ -287,21 +260,19 @@ async function exploreTree(options) {
287
260
  if (options.lang) {
288
261
  detectedLang = resolveLang(options.lang);
289
262
  if (!detectedLang) {
290
- warnings.push(`Linguagem "${options.lang}" nao reconhecida, usando TypeScript.`);
291
263
  detectedLang = resolveLang("typescript");
292
264
  }
293
265
  } else {
294
- warnings.push("Linguagem nao especificada para codigo inline, usando TypeScript.");
295
266
  detectedLang = resolveLang("typescript");
296
267
  }
297
268
  } else if (options.path) {
298
269
  filePath = resolve2(cwd, options.path);
299
270
  try {
300
- await stat(filePath);
271
+ await stat2(filePath);
301
272
  } catch {
302
273
  throw new FileNotFoundError(filePath);
303
274
  }
304
- source = await readFile2(filePath, "utf-8");
275
+ source = await readFile(filePath, "utf-8");
305
276
  if (options.lang) {
306
277
  detectedLang = resolveLang(options.lang);
307
278
  if (!detectedLang) {
@@ -312,7 +283,7 @@ async function exploreTree(options) {
312
283
  detectedLang = detectLangFromFile(filePath);
313
284
  if (!detectedLang) {
314
285
  warnings.push(
315
- `Nao foi possivel detectar linguagem da extensao "${extname2(filePath)}". Usando TypeScript.`
286
+ `Nao foi possivel detectar linguagem da extensao "${extname(filePath)}". Usando TypeScript.`
316
287
  );
317
288
  detectedLang = resolveLang("typescript");
318
289
  }
@@ -323,7 +294,7 @@ async function exploreTree(options) {
323
294
  const lang = detectedLang;
324
295
  let ast;
325
296
  try {
326
- ast = parse2(lang, source);
297
+ ast = parse(lang, source);
327
298
  } catch (parseErr) {
328
299
  const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
329
300
  throw new TreeParseError(filePath, lang, msg);
@@ -375,10 +346,11 @@ var TreeParseError = class extends Error {
375
346
  };
376
347
 
377
348
  // 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;
349
+ import { findInFiles as findInFiles2 } from "@ast-grep/napi";
350
+ import { resolve as resolve3, relative as relative3 } from "path";
351
+ import { stat as stat3 } from "fs/promises";
352
+ var MULTI_META_RE2 = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
353
+ var SINGLE_META_RE2 = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
382
354
  async function replacePreview(options) {
383
355
  const warnings = [];
384
356
  const cwd = options.cwd ?? process.cwd();
@@ -397,30 +369,46 @@ async function replacePreview(options) {
397
369
  lang = detected;
398
370
  warnings.push(`Linguagem detectada automaticamente: ${langName(lang)}`);
399
371
  } else {
400
- warnings.push("Linguagem nao especificada, usando TypeScript como default.");
401
372
  lang = resolveLang("typescript");
402
373
  }
403
- validateReplacePattern(options.pattern, lang);
404
- const searchDir = resolve3(cwd, options.path ?? ".");
405
- const files = await findCodeFiles2(searchDir);
406
- const metaVarNames = extractMetaVarNames2(options.pattern);
374
+ const searchPath = resolve3(cwd, options.path ?? ".");
375
+ try {
376
+ await stat3(searchPath);
377
+ } catch {
378
+ throw new ReplaceLangError(
379
+ `Path nao encontrado: ${searchPath}`
380
+ );
381
+ }
382
+ const metaVars = extractMetaVarInfo2(options.pattern);
383
+ const napiConfig = {
384
+ rule: { pattern: options.pattern }
385
+ };
386
+ const findConfig = {
387
+ paths: [searchPath],
388
+ matcher: napiConfig,
389
+ languageGlobs: langToGlobs2(lang)
390
+ };
407
391
  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();
392
+ try {
393
+ const fileResults = await findInFilesWrapper2(lang, findConfig);
394
+ for (const { file, nodes } of fileResults) {
395
+ for (const node of nodes) {
396
+ const metaVarValues = {};
397
+ for (const { name, isMulti } of metaVars) {
398
+ if (isMulti) {
399
+ const multiNodes = node.getMultipleMatches(name);
400
+ if (multiNodes.length > 0) {
401
+ metaVarValues[name] = multiNodes.map((n) => n.text()).join(", ");
402
+ }
403
+ } else {
404
+ const metaNode = node.getMatch(name);
405
+ if (metaNode) {
406
+ metaVarValues[name] = metaNode.text();
407
+ }
420
408
  }
421
409
  }
422
410
  let interpolated = options.rewrite;
423
- for (const [name, value] of Object.entries(metaVars)) {
411
+ for (const [name, value] of Object.entries(metaVarValues)) {
424
412
  interpolated = interpolated.replaceAll(
425
413
  new RegExp(`\\$${name}\\b`, "g"),
426
414
  value
@@ -428,16 +416,16 @@ async function replacePreview(options) {
428
416
  }
429
417
  const range = node.range();
430
418
  changes.push({
431
- file: relative3(cwd, filePath).replace(/\\/g, "/"),
419
+ file: relative3(cwd, file).replace(/\\/g, "/"),
432
420
  line: range.start.line,
433
421
  column: range.start.column,
434
422
  original: node.text(),
435
423
  replacement: interpolated
436
424
  });
437
425
  }
438
- } catch {
439
- continue;
440
426
  }
427
+ } catch {
428
+ throw new ReplacePatternError(options.pattern, lang);
441
429
  }
442
430
  const uniqueFiles = new Set(changes.map((c) => c.file));
443
431
  return {
@@ -446,70 +434,84 @@ async function replacePreview(options) {
446
434
  rewrite: options.rewrite,
447
435
  lang,
448
436
  changes,
449
- filesScanned: files.paths.length,
437
+ filesScanned: changes.length > 0 ? uniqueFiles.size : 0,
450
438
  filesAffected: uniqueFiles.size
451
439
  },
452
440
  warnings
453
441
  };
454
442
  }
455
- function extractMetaVarNames2(pattern) {
456
- const names = /* @__PURE__ */ new Set();
443
+ function extractMetaVarInfo2(pattern) {
444
+ const seen = /* @__PURE__ */ new Set();
445
+ const result = [];
457
446
  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);
447
+ const multiRe = new RegExp(MULTI_META_RE2.source, "g");
448
+ while ((match = multiRe.exec(pattern)) !== null) {
449
+ const name = match[1];
450
+ if (!seen.has(name)) {
451
+ seen.add(name);
452
+ result.push({ name, isMulti: true });
472
453
  }
473
454
  }
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 };
455
+ const singleRe = new RegExp(SINGLE_META_RE2.source, "g");
456
+ while ((match = singleRe.exec(pattern)) !== null) {
457
+ const name = match[1];
458
+ if (!seen.has(name)) {
459
+ seen.add(name);
460
+ result.push({ name, isMulti: false });
481
461
  }
482
- } catch {
483
- return { paths: [], warnings };
484
462
  }
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 {
463
+ return result;
464
+ }
465
+ var IGNORED_DIR_PATTERNS2 = [
466
+ /[/\\]node_modules[/\\]/,
467
+ /[/\\]dist[/\\]/,
468
+ /[/\\]build[/\\]/,
469
+ /[/\\]\.git[/\\]/,
470
+ /[/\\]\.next[/\\]/,
471
+ /[/\\]\.turbo[/\\]/,
472
+ /[/\\]coverage[/\\]/,
473
+ /[/\\]out[/\\]/
474
+ ];
475
+ function isIgnoredPath2(filePath) {
476
+ const normalized = filePath.replace(/\\/g, "/");
477
+ const wrapped = `/${normalized}/`;
478
+ return IGNORED_DIR_PATTERNS2.some((re) => re.test(wrapped));
479
+ }
480
+ async function findInFilesWrapper2(lang, config) {
481
+ const results = [];
482
+ let callbacksReceived = 0;
483
+ const totalFiles = await findInFiles2(
484
+ lang,
485
+ config,
486
+ (err, nodes) => {
487
+ callbacksReceived++;
488
+ if (err || nodes.length === 0) return;
489
+ const file = nodes[0].getRoot()?.filename() ?? "unknown";
490
+ if (isIgnoredPath2(file)) return;
491
+ results.push({ file, nodes });
503
492
  }
493
+ );
494
+ while (callbacksReceived < totalFiles) {
495
+ await new Promise((r) => setTimeout(r, 10));
504
496
  }
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 };
497
+ return results;
498
+ }
499
+ function langToGlobs2(lang) {
500
+ const langStr = String(lang);
501
+ switch (langStr) {
502
+ case "TypeScript":
503
+ return ["*.ts"];
504
+ case "JavaScript":
505
+ return ["*.js", "*.jsx", "*.mjs", "*.cjs"];
506
+ case "Tsx":
507
+ return ["*.tsx"];
508
+ case "Css":
509
+ return ["*.css", "*.scss", "*.less"];
510
+ case "Html":
511
+ return ["*.html", "*.htm"];
512
+ default:
513
+ return [];
511
514
  }
512
- return { paths, warnings };
513
515
  }
514
516
  var ReplacePatternError = class extends Error {
515
517
  pattern;
@@ -566,17 +568,18 @@ function hint(command, ctx, params) {
566
568
  }
567
569
  var NEXT_STEPS = {
568
570
  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" }
571
+ { command: "tree", description: "explorar a AST do arquivo com matches para entender a estrutura" },
572
+ { command: "find", description: "refinar a busca com um pattern mais especifico" },
573
+ { command: "replace", description: "pre-visualizar como ficaria uma substituicao nos matches" }
572
574
  ],
573
575
  tree: [
574
- { command: "find", description: "usar os kinds descobertos para buscar padroes" },
575
- { command: "tree", description: "explorar outro arquivo" }
576
+ { command: "find", description: "usar os kinds descobertos para criar um pattern de busca (ex: $FUNC($$$ARGS) para call_expression)" },
577
+ { command: "tree", description: "explorar outro arquivo ou snippet" }
576
578
  ],
577
579
  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
+ { command: "find", description: "ver todos os matches do pattern antes de substituir" },
581
+ { command: "replace", description: "testar outra string de rewrite no preview" },
582
+ { command: "tree", description: "explorar a AST para criar patterns mais precisos" }
580
583
  ]
581
584
  };
582
585
  function nextSteps(command, ctx) {
@@ -611,9 +614,10 @@ function recoveryHint(errorType, ctx, _extra) {
611
614
  case "lang_not_supported":
612
615
  return `
613
616
  \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
+ \u2192 O @ast-grep/napi suporta apenas 5 linguagens: TypeScript, JavaScript, TSX, CSS, HTML
618
+ \u2192 Para Python, Rust, Go e outras, use o CLI: npx @ast-grep/cli
619
+ \u2192 ${hint("tree", ctx)} - explore a AST de um arquivo existente
620
+ \u2192 ${hint("find_lang", ctx)} - especifique a linguagem manualmente
617
621
  `;
618
622
  case "lang_not_detected":
619
623
  return `
@@ -997,7 +1001,9 @@ function registerAllTools(server2) {
997
1001
  "supergrep_find",
998
1002
  {
999
1003
  title: "Structural Code Search",
1000
- description: `\u26A0\uFE0F PREFIRA esta ferramenta ao inves de grep/bash quando a busca for em CODIGO-FONTE.
1004
+ description: `\u26A0\uFE0F Use esta ferramenta em vez de grep/bash quando a busca for em CODIGO-FONTE
1005
+ ESTRUTURADO (TS/JS/TSX/CSS/HTML). Para texto plano, logs, markdown ou JSON,
1006
+ prefira grep comum.
1001
1007
 
1002
1008
  Busca estrutural de codigo usando AST Grep. Diferente do grep textual (que
1003
1009
  quebra com quebras de linha, espacos extras ou comentarios no meio), o
@@ -1005,15 +1011,23 @@ supergrep entende a SINTAXE da linguagem \u2014 ele busca pela ESTRUTURA do codi
1005
1011
  nao pelo texto cru.
1006
1012
 
1007
1013
  Escreva o padrao de busca na propria linguagem de programacao, usando
1008
- metavariaveis ($VAR, $$$VARS) como curingas que capturam partes dinamicas.
1014
+ metavariaveis ($VAR, $$VAR, $$$VARS) como curingas que capturam partes dinamicas.
1009
1015
 
1010
1016
  \u{1F4E6} LINGUAGENS SUPORTADAS (apenas 5):
1011
- \u2022 typescript (.ts)
1017
+ \u2022 typescript (.ts) \u2014 apenas .ts puro (sem JSX)
1012
1018
  \u2022 javascript (.js, .jsx, .mjs, .cjs)
1013
- \u2022 tsx (.tsx)
1019
+ \u2022 tsx (.tsx) \u2014 .tsx COM JSX/React
1014
1020
  \u2022 css (.css, .scss, .less)
1015
1021
  \u2022 html (.html, .htm)
1016
1022
 
1023
+ \u{1F916} AUTODETECCAO: Se o parametro 'lang' NAO for especificado, o motor
1024
+ detecta a linguagem automaticamente pela extensao de cada arquivo.
1025
+ Voce pode buscar em um diretorio com .ts e .tsx misturados \u2014 o motor
1026
+ usa o parser correto para cada arquivo.
1027
+
1028
+ Use 'lang' apenas quando quiser RESTRINGIR a busca a uma linguagem
1029
+ especifica (ex: forcar TSX em todo o diretorio).
1030
+
1017
1031
  \u26A0\uFE0F LIMITACAO IMPORTANTE: Apenas busca SINTAXICA. Nao resolve imports, nao
1018
1032
  entende tipos, nao segue referencias entre arquivos, nao faz analise de fluxo.
1019
1033
  Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
@@ -1024,7 +1038,7 @@ Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
1024
1038
  \u2022 "Localize imports de uma lib deprecated"
1025
1039
  \u2022 "Busque componentes React que usam useEffect sem dependencias"
1026
1040
  \u2022 "Ache arrow functions que recebem exatamente 1 parametro"
1027
- \u2022 QUALQUER busca estrutural onde grep/quebraria com formatacao
1041
+ \u2022 QUALQUER busca estrutural onde grep quebraria com formatacao
1028
1042
 
1029
1043
  \u274C QUANDO NAO USAR (prefira ai-tool):
1030
1044
  \u2022 "Onde a funcao useAuth e definida e usada?" \u2192 resolucao de escopo (aitool_find)
@@ -1032,17 +1046,31 @@ Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
1032
1046
  \u2022 "Qual o tipo real desta variavel?" \u2192 type checker (aitool_file_context)
1033
1047
  \u2022 Busca em texto plano, logs, markdown \u2192 use grep/bash comum
1034
1048
 
1049
+ \u26A0\uFE0F O QUE O GREP NAO RESOLVE (anti-exemplos):
1050
+ \u2022 grep falha com quebra de linha:
1051
+ \u274C GREP: console\\n .log("oi") \u2192 nao encontra
1052
+ \u2705 SUPERGREP: console.log("oi") \u2192 encontra (ignora formatacao)
1053
+ \u2022 grep falha com espacos extras:
1054
+ \u274C GREP: const x = 1 \u2192 precisa de regex complexa
1055
+ \u2705 SUPERGREP: const x = 1 \u2192 resolve pela estrutura
1056
+
1035
1057
  \u{1F4DD} EXEMPLOS DE PATTERNS:
1036
- \u2022 console.$METHOD($$$ARGS) \u2192 captura metodo e argumentos
1037
- \u2022 function $NAME($$$PARAMS) { $$$BODY } \u2192 captura funcoes
1058
+ \u2022 console.$METHOD($MSG) \u2192 captura metodo e 1 argumento
1059
+ \u2022 console.$METHOD($$$ARGS) \u2192 captura metodo e todos os argumentos
1060
+ \u2022 function $NAME($$$PARAMS) { $$$BODY } \u2192 captura funcoes completas
1038
1061
  \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
1062
+ \u2022 const $VAR = $VALUE \u2192 captura declaracoes
1063
+ \u2022 try { $$$ } catch ($ERR) { $$$ } \u2192 captura try-catch
1064
+ \u2022 await $FUNC($$$ARGS) \u2192 captura chamadas assincronas
1042
1065
 
1043
1066
  \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)
1067
+ \u2022 $VAR \u2192 captura UM no nomeado (ex: um identificador, uma string, um numero)
1068
+ \u2022 $$VAR \u2192 captura UM no (incluindo nos nao-nomeados como operadores +, -)
1069
+ \u2022 $$$VARS \u2192 captura ZERO OU MAIS nos (ex: todos os args, corpo da funcao)
1070
+
1071
+ \u26A0\uFE0F $$$VARS captura multiplos nos \u2014 o texto de cada no e concatenado com
1072
+ ", " no resultado da metavariable. Para multi-nos complexos (onde a
1073
+ concatenacao simples nao basta), use supergrep_tree para inspecionar a AST.
1046
1074
 
1047
1075
  \u{1F4A1} DICA: Se o pattern nao der match, use supergrep_tree primeiro para
1048
1076
  descobrir os kinds de nos disponiveis e ajustar o pattern.
@@ -1053,7 +1081,7 @@ Workflow recomendado: supergrep_find \u2192 supergrep_tree \u2192 supergrep_repl
1053
1081
  "Padrao AST Grep na linguagem alvo. Ex: 'console.$METHOD($$$ARGS)', 'function $NAME($$$PARAMS) { $$$BODY }'"
1054
1082
  ),
1055
1083
  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."
1084
+ "Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html). Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo."
1057
1085
  ),
1058
1086
  path: z.string().optional().describe(
1059
1087
  "Arquivo ou diretorio onde buscar. Default: diretorio atual. Ex: 'src/', 'app/page.tsx'"
@@ -1095,21 +1123,38 @@ ${warnings.map((w) => ` \u2022 ${w}`).join("\n")}` : "";
1095
1123
  "supergrep_tree",
1096
1124
  {
1097
1125
  title: "Explore AST Structure",
1098
- description: `Explora a estrutura da AST (Abstract Syntax Tree) de um arquivo.
1126
+ description: `Explora a estrutura da AST (Abstract Syntax Tree) de um arquivo ou snippet.
1099
1127
 
1100
1128
  Mostra todos os tipos de nos (kinds) presentes, com contagens e distribuicao.
1101
1129
  Util para entender a estrutura do codigo e descobrir quais kinds usar
1102
- em patterns de busca ou regras YAML.
1130
+ em patterns de busca.
1131
+
1132
+ \u{1F4D6} O QUE E UM "KIND":
1133
+ Um kind e o tipo de um no na arvore sintatica. Exemplos:
1134
+ \u2022 function_declaration \u2014 declaracao de funcao
1135
+ \u2022 call_expression \u2014 chamada de funcao/metodo
1136
+ \u2022 identifier \u2014 nome de variavel, funcao, etc.
1137
+ \u2022 string_fragment \u2014 literal de string
1138
+ Cada estrutura de codigo vira um kind diferente na AST.
1139
+
1140
+ \u{1F4A1} Como usar os kinds: ao ver call_expression no tree, voce pode buscar
1141
+ chamadas de funcao com o pattern: $FUNC($$$ARGS)
1142
+
1143
+ \u{1F916} AUTODETECCAO: se usar 'path', a linguagem e detectada automaticamente
1144
+ pela extensao do arquivo. O parametro 'lang' so e necessario ao usar
1145
+ 'code' (snippet inline) ou para forcar uma linguagem especifica.
1103
1146
 
1104
1147
  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
1148
+ - "Quais tipos de nos existem neste arquivo TypeScript?"
1149
+ - "Como escrever um pattern para capturar certas estruturas?"
1150
+ - "Preciso descobrir os kinds para uma regra de lint customizada"
1151
+ - Antes de escrever um pattern complexo, para conhecer a AST
1152
+ - Testar um snippet de codigo sem criar arquivo (use o parametro code)
1109
1153
 
1110
1154
  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
1155
+ - Para buscar padroes no codigo \u2192 use supergrep_find
1156
+ - Para ver o conteudo do arquivo \u2192 leia o arquivo diretamente
1157
+ - Para diretorios (analisar multiplos arquivos) \u2192 use supergrep_find com o pattern desejado. supergrep_tree analisa UM arquivo ou snippet por vez.
1113
1158
 
1114
1159
  Workflow: supergrep_tree \u2192 supergrep_find (usar kinds descobertos)`,
1115
1160
  inputSchema: {
@@ -1158,27 +1203,39 @@ ${warnings.map((w) => ` \u2022 ${w}`).join("\n")}` : "";
1158
1203
  server2.registerTool(
1159
1204
  "supergrep_replace",
1160
1205
  {
1161
- title: "Preview Structural Replacement",
1162
- description: `Pre-visualiza uma substituicao estrutural de codigo (NAO modifica arquivos).
1206
+ title: "Preview Structural Replacement (read-only)",
1207
+ description: `\u{1F504} PREVIEW de substituicao estrutural de codigo (NAO modifica arquivos).
1163
1208
 
1164
1209
  Encontra padroes com AST Grep e mostra um diff de como o codigo ficaria
1165
1210
  apos a substituicao. Util para planejar refatoracoes em larga escala.
1166
1211
 
1167
- LIMITACAO: Metavariaveis ($VAR) no rewrite sao interpoladas manualmente.
1168
- Para casos complexos, verifique o preview antes de aplicar.
1212
+ \u26A0\uFE0F LIMITACAO DE METAVARIAVEIS NO REWRITE:
1213
+ \u2022 $VAR (single) \u2192 interpola corretamente \u2705
1214
+ Ex: console.$METHOD($MSG) \u2192 logger.$METHOD($MSG)
1215
+ \u2022 $$$VARS (multi) \u2192 interpolado como texto concatenado dos nos \u26A0\uFE0F
1216
+ Ex: console.log($$$ARGS) \u2192 logger.info(texto, dos, args)
1217
+ Para casos complexos, verifique o preview antes de aplicar.
1218
+
1219
+ A API napi do AST Grep nao interpola metavariaveis automaticamente.
1220
+ O supergrep faz interpolacao manual: textos dos nos sao concatenados
1221
+ com ", " e injetados no lugar do $VAR no rewrite.
1222
+
1223
+ \u{1F916} AUTODETECCAO DE LINGUAGEM: se 'lang' nao for especificado, o motor
1224
+ detecta automaticamente pela extensao de cada arquivo. Use 'lang' apenas
1225
+ para RESTRINGIR a busca a uma linguagem especifica.
1169
1226
 
1170
1227
  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
1228
+ - "Como ficaria se eu trocasse console.log por logger.info?"
1229
+ - "Preview de substituir var por let em todo o projeto"
1230
+ - "Mostre o diff de trocar moment() por date-fns"
1231
+ - Planejar refatoracoes estruturais antes de aplicar
1175
1232
 
1176
1233
  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
1234
+ - Para buscar padroes (sem substituir) \u2192 use supergrep_find
1235
+ - Para aplicar a substituicao de fato \u2192 use edit (substituicao exata) ou write (arquivo completo). O preview do supergrep_replace serve como guia.
1236
+ - Para refatoracoes que envolvem logica complexa \u2192 implemente manualmente
1180
1237
 
1181
- Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudancas`,
1238
+ Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudancas com edit/write`,
1182
1239
  inputSchema: {
1183
1240
  pattern: z.string().min(1).describe(
1184
1241
  "Padrao AST Grep a encontrar. Ex: 'console.log($$$ARGS)'"
@@ -1187,7 +1244,7 @@ Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudan
1187
1244
  "Texto de substituicao. Use $VAR para referenciar metavariaveis capturadas. Ex: 'logger.info($$$ARGS)'"
1188
1245
  ),
1189
1246
  lang: z.string().optional().describe(
1190
- "Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Default: typescript."
1247
+ "Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo."
1191
1248
  ),
1192
1249
  path: z.string().optional().describe(
1193
1250
  "Arquivo ou diretorio onde buscar. Default: diretorio atual."
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "./chunk-4MPFPILM.js";
4
+ } from "./chunk-5PJJZ4CK.js";
5
5
 
6
6
  // src/cli.ts
7
7
  startMcpServer().catch((err) => {
package/dist/index.d.ts CHANGED
@@ -33,7 +33,6 @@ declare function startMcpServer(): Promise<void>;
33
33
  * await startMcpServer();
34
34
  */
35
35
 
36
- /** Versao do pacote */
37
- declare const VERSION = "0.1.0";
36
+ declare const VERSION: string;
38
37
 
39
38
  export { VERSION, startMcpServer };
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  startMcpServer
3
- } from "./chunk-4MPFPILM.js";
3
+ } from "./chunk-5PJJZ4CK.js";
4
4
 
5
5
  // src/index.ts
6
- var VERSION = "0.1.0";
6
+ import { createRequire } from "module";
7
+ var require2 = createRequire(import.meta.url);
8
+ var pkg = require2("../package.json");
9
+ var VERSION = pkg.version;
7
10
  export {
8
11
  VERSION,
9
12
  startMcpServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/supergrep",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server para busca estrutural de código com AST Grep. Encontra padrões de código com precisão sintática, ignorando formatação e comentários. Suporta 20+ linguagens via Tree-sitter.",
5
5
  "keywords": [
6
6
  "ast-grep",