@justmpm/supergrep 0.1.1 → 0.2.1

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.
@@ -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 as resolve2 } 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,10 +65,6 @@ 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
68
  var IGNORED_EXTENSIONS_SUBSTRINGS = [
73
69
  ".min.js",
74
70
  ".min.css",
@@ -84,51 +80,148 @@ function shouldIgnoreFile(filePath) {
84
80
  }
85
81
  return false;
86
82
  }
87
- var IGNORED_DIRS = [
83
+
84
+ // src/utils/find-fallback.ts
85
+ import { parse } from "@ast-grep/napi";
86
+ import { readdir, readFile, stat as fsStat } from "fs/promises";
87
+ import { join, extname, resolve } from "path";
88
+ var LANG_EXTENSIONS = {
89
+ Html: [".html", ".htm"],
90
+ Css: [".css", ".scss", ".less"]
91
+ };
92
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
88
93
  "node_modules",
94
+ ".git",
89
95
  "dist",
90
96
  "build",
91
- ".git",
92
97
  ".next",
93
98
  ".turbo",
94
99
  "coverage",
95
100
  "out"
96
- ];
101
+ ]);
102
+ async function findInFilesFallback(lang, config) {
103
+ const langStr = String(lang);
104
+ const extensions = LANG_EXTENSIONS[langStr];
105
+ if (!extensions) {
106
+ return [];
107
+ }
108
+ const results = [];
109
+ for (const searchPath of config.paths) {
110
+ const absPath = resolve(searchPath);
111
+ try {
112
+ const pathStat = await fsStat(absPath);
113
+ if (pathStat.isFile()) {
114
+ const ext = extname(absPath).toLowerCase();
115
+ if (extensions.some((e) => ext === e)) {
116
+ await processFile(absPath, lang, config.matcher, results);
117
+ }
118
+ } else if (pathStat.isDirectory()) {
119
+ await walkDir(absPath, lang, extensions, config.matcher, results);
120
+ }
121
+ } catch {
122
+ continue;
123
+ }
124
+ }
125
+ return results;
126
+ }
127
+ async function processFile(filePath, lang, matcher, results) {
128
+ if (shouldIgnoreFile(filePath)) return;
129
+ let source;
130
+ try {
131
+ source = await readFile(filePath, "utf-8");
132
+ } catch {
133
+ return;
134
+ }
135
+ let ast;
136
+ try {
137
+ ast = parse(lang, source);
138
+ } catch {
139
+ return;
140
+ }
141
+ try {
142
+ const nodes = [...ast.root().findAll(matcher)];
143
+ if (nodes.length > 0) {
144
+ results.push({ file: filePath, nodes });
145
+ }
146
+ } catch {
147
+ return;
148
+ }
149
+ }
150
+ async function walkDir(dirPath, lang, extensions, matcher, results) {
151
+ let entries;
152
+ try {
153
+ entries = await readdir(dirPath, { withFileTypes: true });
154
+ } catch {
155
+ return;
156
+ }
157
+ for (const entry of entries) {
158
+ const fullPath = join(dirPath, entry.name);
159
+ if (entry.isDirectory()) {
160
+ if (IGNORED_DIRS.has(entry.name)) continue;
161
+ if (entry.name.startsWith(".")) continue;
162
+ await walkDir(fullPath, lang, extensions, matcher, results);
163
+ } else if (entry.isFile()) {
164
+ const ext = extname(entry.name).toLowerCase();
165
+ if (extensions.includes(ext)) {
166
+ await processFile(fullPath, lang, matcher, results);
167
+ }
168
+ }
169
+ }
170
+ }
97
171
 
98
172
  // src/tools/find.ts
173
+ function isHtmlCssLang(lang) {
174
+ const langStr = String(lang);
175
+ return langStr === "Html" || langStr === "Css";
176
+ }
99
177
  var MULTI_META_RE = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
100
178
  var SINGLE_META_RE = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
101
179
  async function findPattern(options) {
102
180
  const startTime = Date.now();
103
181
  const warnings = [];
104
182
  const cwd = options.cwd ?? process.cwd();
105
- const lang = resolveLangForOptions(options, cwd, warnings);
106
- if (!lang) {
183
+ let lang;
184
+ if (options.lang) {
185
+ lang = resolveLang(options.lang);
186
+ if (!lang) {
187
+ warnings.push(`Linguagem "${options.lang}" nao reconhecida. Usando autodeteccao...`);
188
+ }
189
+ }
190
+ const searchPath = resolve2(cwd, options.path ?? ".");
191
+ try {
192
+ await stat(searchPath);
193
+ } catch {
194
+ warnings.push(`Path nao encontrado: ${searchPath}. Usando diretorio atual.`);
107
195
  throw new LanguageNotDetectedError(options.lang ?? "desconhecida");
108
196
  }
109
- validatePattern(options.pattern, lang);
110
- const searchDir = resolve(cwd, options.path ?? ".");
111
- const files = await findCodeFiles(searchDir, cwd, lang);
112
- warnings.push(...files.warnings);
113
197
  const metaVars = extractMetaVarInfo(options.pattern);
198
+ const napiConfig = {
199
+ rule: { pattern: options.pattern }
200
+ };
201
+ const findConfig = {
202
+ paths: [searchPath],
203
+ matcher: napiConfig
204
+ };
205
+ if (lang) {
206
+ findConfig.languageGlobs = langToGlobs(lang);
207
+ }
208
+ const langForSearch = lang ?? resolveLang("typescript");
114
209
  const matches = [];
115
- for (const filePath of files.paths) {
116
- try {
117
- const source = await readFile(filePath, "utf-8");
118
- const ast = parse(lang, source);
119
- const root = ast.root();
120
- const foundNodes = root.findAll(options.pattern);
121
- for (const node of foundNodes) {
210
+ const useFallback = lang !== void 0 && isHtmlCssLang(lang);
211
+ try {
212
+ const fileResults = useFallback ? await findInFilesFallback(lang, { paths: [searchPath], matcher: napiConfig }) : await findInFilesWrapper(langForSearch, findConfig);
213
+ for (const { file, nodes } of fileResults) {
214
+ for (const node of nodes) {
122
215
  const range = node.range();
216
+ const text = node.text();
123
217
  const match = {
124
- file: relative(cwd, filePath).replace(/\\/g, "/"),
218
+ file: relative(cwd, file).replace(/\\/g, "/"),
125
219
  line: range.start.line,
126
220
  column: range.start.column,
127
221
  endLine: range.end.line,
128
222
  endColumn: range.end.column,
129
- text: node.text(),
130
- metaVariables: {},
131
- context: extractContextLines(source, range.start.line, 2)
223
+ text,
224
+ metaVariables: {}
132
225
  };
133
226
  for (const { name, isMulti } of metaVars) {
134
227
  if (isMulti) {
@@ -145,19 +238,23 @@ async function findPattern(options) {
145
238
  }
146
239
  matches.push(match);
147
240
  }
148
- } catch {
149
- continue;
150
241
  }
242
+ } catch (err) {
243
+ const msg = err instanceof Error ? err.message : String(err);
244
+ if (lang) {
245
+ throw new PatternParseError(options.pattern, lang);
246
+ }
247
+ throw new Error(`Erro na busca: ${msg}`);
151
248
  }
152
- const uniqueFiles = new Set(matches.map((m) => m.file));
249
+ const filesWithMatchesSet = new Set(matches.map((m) => m.file));
153
250
  const executionTimeMs = Date.now() - startTime;
154
251
  return {
155
252
  result: {
156
253
  pattern: options.pattern,
157
- lang,
254
+ lang: langForSearch,
158
255
  matches,
159
- filesScanned: files.paths.length,
160
- filesWithMatches: uniqueFiles.size,
256
+ filesScanned: matches.length > 0 ? filesWithMatchesSet.size : 0,
257
+ filesWithMatches: filesWithMatchesSet.size,
161
258
  executionTimeMs
162
259
  },
163
260
  warnings
@@ -185,92 +282,56 @@ function extractMetaVarInfo(pattern) {
185
282
  }
186
283
  return result;
187
284
  }
188
- function extractContextLines(source, line, contextLines) {
189
- const lines = source.split("\n");
190
- const start = Math.max(0, line - contextLines);
191
- const end = Math.min(lines.length, line + contextLines + 1);
192
- return lines.slice(start, end);
193
- }
194
- function resolveLangForOptions(options, _cwd, warnings) {
195
- if (options.lang) {
196
- const lang = resolveLang(options.lang);
197
- if (!lang) {
198
- warnings.push(`Linguagem "${options.lang}" nao reconhecida. Tentando autodeteccao...`);
199
- } else {
200
- return lang;
201
- }
202
- }
203
- if (options.path) {
204
- const ext = extname(options.path).toLowerCase();
205
- if (ext && isSupportedExtension(options.path)) {
206
- const detected = detectLangFromFile(options.path);
207
- if (detected) return detected;
208
- }
209
- }
210
- return resolveLang("typescript");
285
+ var IGNORED_DIR_PATTERNS = [
286
+ /[/\\]node_modules[/\\]/,
287
+ /[/\\]dist[/\\]/,
288
+ /[/\\]build[/\\]/,
289
+ /[/\\]\.git[/\\]/,
290
+ /[/\\]\.next[/\\]/,
291
+ /[/\\]\.turbo[/\\]/,
292
+ /[/\\]coverage[/\\]/,
293
+ /[/\\]out[/\\]/
294
+ ];
295
+ function isIgnoredPath(filePath) {
296
+ const normalized = filePath.replace(/\\/g, "/");
297
+ const wrapped = `/${normalized}/`;
298
+ return IGNORED_DIR_PATTERNS.some((re) => re.test(wrapped));
211
299
  }
212
- function validatePattern(pattern, lang) {
213
- try {
214
- parse(lang, pattern);
215
- } catch {
216
- try {
217
- parse(lang, `const _x = ${pattern}`);
218
- } catch {
219
- throw new PatternParseError(pattern, lang);
300
+ async function findInFilesWrapper(lang, config) {
301
+ const results = [];
302
+ let callbacksReceived = 0;
303
+ const totalFiles = await findInFiles(
304
+ lang,
305
+ config,
306
+ (err, nodes) => {
307
+ callbacksReceived++;
308
+ if (err || nodes.length === 0) return;
309
+ const file = nodes[0].getRoot()?.filename() ?? "unknown";
310
+ if (isIgnoredPath(file)) return;
311
+ results.push({ file, nodes });
220
312
  }
313
+ );
314
+ while (callbacksReceived < totalFiles) {
315
+ await new Promise((r) => setTimeout(r, 10));
221
316
  }
317
+ return results;
222
318
  }
223
- async function findCodeFiles(searchDir, _cwd, _lang) {
224
- const warnings = [];
225
- try {
226
- const { stat: stat3 } = await import("fs/promises");
227
- const dirStat = await stat3(searchDir);
228
- if (dirStat.isFile()) {
229
- if (!isSupportedExtension(searchDir)) {
230
- const fileName = searchDir.split(/[/\\]/).pop() ?? searchDir;
231
- warnings.push(
232
- `Arquivo ${fileName}: extensao pode nao ser suportada para ${langName(_lang)}`
233
- );
234
- }
235
- return { paths: [searchDir], warnings };
236
- }
237
- } catch {
238
- warnings.push(`Path nao encontrado: ${searchDir}. Usando diretorio atual.`);
239
- return { paths: [], warnings };
240
- }
241
- const paths = [];
242
- async function walk(dir) {
243
- try {
244
- const entries = await readdir(dir, { withFileTypes: true });
245
- for (const entry of entries) {
246
- const fullPath = join(dir, entry.name);
247
- if (entry.isDirectory()) {
248
- if (IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
249
- continue;
250
- }
251
- await walk(fullPath);
252
- } else if (entry.isFile()) {
253
- if (shouldIgnoreFile(fullPath)) continue;
254
- if (!isSupportedExtension(fullPath)) continue;
255
- paths.push(fullPath);
256
- }
257
- }
258
- } catch {
259
- }
260
- }
261
- await walk(searchDir);
262
- if (paths.length > 5e3) {
263
- warnings.push(
264
- `Muitos arquivos encontrados (${paths.length}). Limitando a 5000. Use --path para focar em um diretorio.`
265
- );
266
- return { paths: paths.slice(0, 5e3), warnings };
267
- }
268
- if (paths.length === 0) {
269
- warnings.push(
270
- `Nenhum arquivo de codigo encontrado em: ${relative(_cwd, searchDir)}`
271
- );
319
+ function langToGlobs(lang) {
320
+ const langStr = String(lang);
321
+ switch (langStr) {
322
+ case "TypeScript":
323
+ return ["*.ts"];
324
+ case "JavaScript":
325
+ return ["*.js", "*.jsx", "*.mjs", "*.cjs"];
326
+ case "Tsx":
327
+ return ["*.tsx"];
328
+ case "Css":
329
+ return ["*.css", "*.scss", "*.less"];
330
+ case "Html":
331
+ return ["*.html", "*.htm"];
332
+ default:
333
+ return [];
272
334
  }
273
- return { paths, warnings };
274
335
  }
275
336
  var PatternParseError = class extends Error {
276
337
  pattern;
@@ -293,8 +354,8 @@ var LanguageNotDetectedError = class extends Error {
293
354
 
294
355
  // src/tools/tree.ts
295
356
  import { parse as parse2 } from "@ast-grep/napi";
296
- import { readFile as readFile2, stat } from "fs/promises";
297
- import { resolve as resolve2, relative as relative2, extname as extname2 } from "path";
357
+ import { readFile as readFile2, stat as stat2 } from "fs/promises";
358
+ import { resolve as resolve3, relative as relative2, extname as extname2 } from "path";
298
359
  async function exploreTree(options) {
299
360
  const warnings = [];
300
361
  const cwd = options.cwd ?? process.cwd();
@@ -313,9 +374,9 @@ async function exploreTree(options) {
313
374
  detectedLang = resolveLang("typescript");
314
375
  }
315
376
  } else if (options.path) {
316
- filePath = resolve2(cwd, options.path);
377
+ filePath = resolve3(cwd, options.path);
317
378
  try {
318
- await stat(filePath);
379
+ await stat2(filePath);
319
380
  } catch {
320
381
  throw new FileNotFoundError(filePath);
321
382
  }
@@ -393,9 +454,13 @@ var TreeParseError = class extends Error {
393
454
  };
394
455
 
395
456
  // src/tools/replace.ts
396
- import { parse as parse3 } from "@ast-grep/napi";
397
- import { readFile as readFile3, readdir as readdir2, stat as stat2 } from "fs/promises";
398
- import { join as join2, resolve as resolve3, relative as relative3 } from "path";
457
+ import { findInFiles as findInFiles2 } from "@ast-grep/napi";
458
+ import { resolve as resolve4, relative as relative3 } from "path";
459
+ import { stat as stat3 } from "fs/promises";
460
+ function isHtmlCssLang2(lang) {
461
+ const langStr = String(lang);
462
+ return langStr === "Html" || langStr === "Css";
463
+ }
399
464
  var MULTI_META_RE2 = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
400
465
  var SINGLE_META_RE2 = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
401
466
  async function replacePreview(options) {
@@ -418,18 +483,29 @@ async function replacePreview(options) {
418
483
  } else {
419
484
  lang = resolveLang("typescript");
420
485
  }
421
- validateReplacePattern(options.pattern, lang);
422
- const searchDir = resolve3(cwd, options.path ?? ".");
423
- const files = await findCodeFiles2(searchDir);
486
+ const searchPath = resolve4(cwd, options.path ?? ".");
487
+ try {
488
+ await stat3(searchPath);
489
+ } catch {
490
+ throw new ReplaceLangError(
491
+ `Path nao encontrado: ${searchPath}`
492
+ );
493
+ }
424
494
  const metaVars = extractMetaVarInfo2(options.pattern);
495
+ const napiConfig = {
496
+ rule: { pattern: options.pattern }
497
+ };
498
+ const findConfig = {
499
+ paths: [searchPath],
500
+ matcher: napiConfig,
501
+ languageGlobs: langToGlobs2(lang)
502
+ };
425
503
  const changes = [];
426
- for (const filePath of files.paths) {
427
- try {
428
- const source = await readFile3(filePath, "utf-8");
429
- const ast = parse3(lang, source);
430
- const root = ast.root();
431
- const foundNodes = root.findAll(options.pattern);
432
- for (const node of foundNodes) {
504
+ const useFallback = isHtmlCssLang2(lang);
505
+ try {
506
+ const fileResults = useFallback ? await findInFilesFallback(lang, { paths: [searchPath], matcher: napiConfig }) : await findInFilesWrapper2(lang, findConfig);
507
+ for (const { file, nodes } of fileResults) {
508
+ for (const node of nodes) {
433
509
  const metaVarValues = {};
434
510
  for (const { name, isMulti } of metaVars) {
435
511
  if (isMulti) {
@@ -447,22 +523,22 @@ async function replacePreview(options) {
447
523
  let interpolated = options.rewrite;
448
524
  for (const [name, value] of Object.entries(metaVarValues)) {
449
525
  interpolated = interpolated.replaceAll(
450
- new RegExp(`\\$${name}\\b`, "g"),
526
+ new RegExp(`\\$+${name}\\b`, "g"),
451
527
  value
452
528
  );
453
529
  }
454
530
  const range = node.range();
455
531
  changes.push({
456
- file: relative3(cwd, filePath).replace(/\\/g, "/"),
532
+ file: relative3(cwd, file).replace(/\\/g, "/"),
457
533
  line: range.start.line,
458
534
  column: range.start.column,
459
535
  original: node.text(),
460
536
  replacement: interpolated
461
537
  });
462
538
  }
463
- } catch {
464
- continue;
465
539
  }
540
+ } catch {
541
+ throw new ReplacePatternError(options.pattern, lang);
466
542
  }
467
543
  const uniqueFiles = new Set(changes.map((c) => c.file));
468
544
  return {
@@ -471,7 +547,7 @@ async function replacePreview(options) {
471
547
  rewrite: options.rewrite,
472
548
  lang,
473
549
  changes,
474
- filesScanned: files.paths.length,
550
+ filesScanned: changes.length > 0 ? uniqueFiles.size : 0,
475
551
  filesAffected: uniqueFiles.size
476
552
  },
477
553
  warnings
@@ -499,55 +575,56 @@ function extractMetaVarInfo2(pattern) {
499
575
  }
500
576
  return result;
501
577
  }
502
- function validateReplacePattern(pattern, lang) {
503
- try {
504
- parse3(lang, pattern);
505
- } catch {
506
- try {
507
- parse3(lang, `const _x = ${pattern}`);
508
- } catch {
509
- throw new ReplacePatternError(pattern, lang);
510
- }
511
- }
578
+ var IGNORED_DIR_PATTERNS2 = [
579
+ /[/\\]node_modules[/\\]/,
580
+ /[/\\]dist[/\\]/,
581
+ /[/\\]build[/\\]/,
582
+ /[/\\]\.git[/\\]/,
583
+ /[/\\]\.next[/\\]/,
584
+ /[/\\]\.turbo[/\\]/,
585
+ /[/\\]coverage[/\\]/,
586
+ /[/\\]out[/\\]/
587
+ ];
588
+ function isIgnoredPath2(filePath) {
589
+ const normalized = filePath.replace(/\\/g, "/");
590
+ const wrapped = `/${normalized}/`;
591
+ return IGNORED_DIR_PATTERNS2.some((re) => re.test(wrapped));
512
592
  }
513
- async function findCodeFiles2(searchDir) {
514
- const warnings = [];
515
- try {
516
- const dirStat = await stat2(searchDir);
517
- if (dirStat.isFile()) {
518
- return { paths: [searchDir], warnings };
519
- }
520
- } catch {
521
- return { paths: [], warnings };
522
- }
523
- const paths = [];
524
- async function walk(dir) {
525
- try {
526
- const entries = await readdir2(dir, { withFileTypes: true });
527
- for (const entry of entries) {
528
- const fullPath = join2(dir, entry.name);
529
- if (entry.isDirectory()) {
530
- if (IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
531
- continue;
532
- }
533
- await walk(fullPath);
534
- } else if (entry.isFile()) {
535
- if (shouldIgnoreFile(fullPath)) continue;
536
- if (!isSupportedExtension(fullPath)) continue;
537
- paths.push(fullPath);
538
- }
539
- }
540
- } catch {
593
+ async function findInFilesWrapper2(lang, config) {
594
+ const results = [];
595
+ let callbacksReceived = 0;
596
+ const totalFiles = await findInFiles2(
597
+ lang,
598
+ config,
599
+ (err, nodes) => {
600
+ callbacksReceived++;
601
+ if (err || nodes.length === 0) return;
602
+ const file = nodes[0].getRoot()?.filename() ?? "unknown";
603
+ if (isIgnoredPath2(file)) return;
604
+ results.push({ file, nodes });
541
605
  }
606
+ );
607
+ while (callbacksReceived < totalFiles) {
608
+ await new Promise((r) => setTimeout(r, 10));
542
609
  }
543
- await walk(searchDir);
544
- if (paths.length > 5e3) {
545
- warnings.push(
546
- `Muitos arquivos (${paths.length}). Limitando a 5000. Use --path para focar.`
547
- );
548
- return { paths: paths.slice(0, 5e3), warnings };
610
+ return results;
611
+ }
612
+ function langToGlobs2(lang) {
613
+ const langStr = String(lang);
614
+ switch (langStr) {
615
+ case "TypeScript":
616
+ return ["*.ts"];
617
+ case "JavaScript":
618
+ return ["*.js", "*.jsx", "*.mjs", "*.cjs"];
619
+ case "Tsx":
620
+ return ["*.tsx"];
621
+ case "Css":
622
+ return ["*.css", "*.scss", "*.less"];
623
+ case "Html":
624
+ return ["*.html", "*.htm"];
625
+ default:
626
+ return [];
549
627
  }
550
- return { paths, warnings };
551
628
  }
552
629
  var ReplacePatternError = class extends Error {
553
630
  pattern;
@@ -1052,9 +1129,17 @@ metavariaveis ($VAR, $$VAR, $$$VARS) como curingas que capturam partes dinamicas
1052
1129
  \u{1F4E6} LINGUAGENS SUPORTADAS (apenas 5):
1053
1130
  \u2022 typescript (.ts) \u2014 apenas .ts puro (sem JSX)
1054
1131
  \u2022 javascript (.js, .jsx, .mjs, .cjs)
1055
- \u2022 tsx (.tsx) \u2014 .tsx COM JSX/React (requer lang: 'tsx')
1056
- \u2022 css (.css, .scss, .less)
1057
- \u2022 html (.html, .htm)
1132
+ \u2022 tsx (.tsx) \u2014 .tsx COM JSX/React
1133
+ \u2022 css (.css, .scss, .less) \u26A0\uFE0F Motor usa fallback manual (findInFiles napi nao suporta nativamente)
1134
+ \u2022 html (.html, .htm) \u26A0\uFE0F Motor usa fallback manual (findInFiles napi nao suporta nativamente)
1135
+
1136
+ \u{1F916} AUTODETECCAO: Se o parametro 'lang' NAO for especificado, o motor
1137
+ detecta a linguagem automaticamente pela extensao de cada arquivo.
1138
+ Voce pode buscar em um diretorio com .ts e .tsx misturados \u2014 o motor
1139
+ usa o parser correto para cada arquivo.
1140
+
1141
+ Use 'lang' apenas quando quiser RESTRINGIR a busca a uma linguagem
1142
+ especifica (ex: forcar TSX em todo o diretorio).
1058
1143
 
1059
1144
  \u26A0\uFE0F LIMITACAO IMPORTANTE: Apenas busca SINTAXICA. Nao resolve imports, nao
1060
1145
  entende tipos, nao segue referencias entre arquivos, nao faz analise de fluxo.
@@ -1085,12 +1170,30 @@ Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
1085
1170
  \u{1F4DD} EXEMPLOS DE PATTERNS:
1086
1171
  \u2022 console.$METHOD($MSG) \u2192 captura metodo e 1 argumento
1087
1172
  \u2022 console.$METHOD($$$ARGS) \u2192 captura metodo e todos os argumentos
1088
- \u2022 function $NAME($$$PARAMS) { $$$BODY } \u2192 captura funcoes completas
1173
+ \u2022 $FUNC($$$ARGS) \u2192 captura qualquer chamada de funcao (use para call_expression)
1089
1174
  \u2022 import { $$$ITEMS } from '$MODULE' \u2192 captura imports
1090
- \u2022 const $VAR = $VALUE \u2192 captura declaracoes
1175
+ \u2022 const $VAR = $VALUE \u2192 captura declaracoes simples
1176
+ \u2022 const $VAR: $TYPE = $VALUE \u2192 captura declaracoes com tipo (sempre inclua o =)
1091
1177
  \u2022 try { $$$ } catch ($ERR) { $$$ } \u2192 captura try-catch
1092
1178
  \u2022 await $FUNC($$$ARGS) \u2192 captura chamadas assincronas
1093
1179
 
1180
+ \u26A0\uFE0F PATTERNS PARA FUNCOES TYPESCRIPT (IMPORTANTE):
1181
+ Funcoes TS frequentemente tem tipo de retorno (: Type) como no nomeado extra.
1182
+ Se o pattern nao incluir o tipo de retorno, o match FALHA. Use:
1183
+ \u2705 function $NAME($$$PARAMS): $_RET { $$$BODY }
1184
+ \u2705 function $NAME($$$PARAMS): $_RET_TYPE { $$$BODY }
1185
+ \u2705 const $NAME = ($PARAM: $TYPE): $_RET => { $$$BODY }
1186
+ \u274C function $NAME($$$PARAMS) { $$$BODY } \u2014 falha se houver tipo de retorno
1187
+ O $_RET (com underscore) age como "coringa nao-capturador" para o tipo de retorno.
1188
+
1189
+ \u26A0\uFE0F PATTERNS CSS PRECISAM DE CONTEXTO:
1190
+ Padroes CSS sem contexto sao ambiguos para o parser. Use:
1191
+ \u2705 $SELECTOR { $$$ } \u2014 captura qualquer regra CSS completa
1192
+ \u2705 $PROP: $VALUE \u2014 captura propriedade (dentro de uma regra)
1193
+ \u2705 * { $$$ } \u2014 qualquer regra CSS
1194
+ \u274C .className { $$$ } \u2014 falha (ponto no inicio e ambiguo)
1195
+ \u274C margin: 0 \u2014 sem contexto, use $PROP: $VALUE
1196
+
1094
1197
  \u{1F524} METAVARIAVEIS:
1095
1198
  \u2022 $VAR \u2192 captura UM no nomeado (ex: um identificador, uma string, um numero)
1096
1199
  \u2022 $$VAR \u2192 captura UM no (incluindo nos nao-nomeados como operadores +, -)
@@ -1109,7 +1212,7 @@ Workflow recomendado: supergrep_find \u2192 supergrep_tree \u2192 supergrep_repl
1109
1212
  "Padrao AST Grep na linguagem alvo. Ex: 'console.$METHOD($$$ARGS)', 'function $NAME($$$PARAMS) { $$$BODY }'"
1110
1213
  ),
1111
1214
  lang: z.string().optional().describe(
1112
- "Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html). Default: typescript. Deixe vazio para autodeteccao por extensao."
1215
+ "Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html). Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo. Para HTML/CSS, o motor usa fallback parse direto (findInFiles nao suporta nativamente)."
1113
1216
  ),
1114
1217
  path: z.string().optional().describe(
1115
1218
  "Arquivo ou diretorio onde buscar. Default: diretorio atual. Ex: 'src/', 'app/page.tsx'"
@@ -1168,6 +1271,10 @@ Cada estrutura de codigo vira um kind diferente na AST.
1168
1271
  \u{1F4A1} Como usar os kinds: ao ver call_expression no tree, voce pode buscar
1169
1272
  chamadas de funcao com o pattern: $FUNC($$$ARGS)
1170
1273
 
1274
+ \u{1F916} AUTODETECCAO: se usar 'path', a linguagem e detectada automaticamente
1275
+ pela extensao do arquivo. O parametro 'lang' so e necessario ao usar
1276
+ 'code' (snippet inline) ou para forcar uma linguagem especifica.
1277
+
1171
1278
  QUANDO USAR:
1172
1279
  - "Quais tipos de nos existem neste arquivo TypeScript?"
1173
1280
  - "Como escrever um pattern para capturar certas estruturas?"
@@ -1244,6 +1351,10 @@ apos a substituicao. Util para planejar refatoracoes em larga escala.
1244
1351
  O supergrep faz interpolacao manual: textos dos nos sao concatenados
1245
1352
  com ", " e injetados no lugar do $VAR no rewrite.
1246
1353
 
1354
+ \u{1F916} AUTODETECCAO DE LINGUAGEM: se 'lang' nao for especificado, o motor
1355
+ detecta automaticamente pela extensao de cada arquivo. Use 'lang' apenas
1356
+ para RESTRINGIR a busca a uma linguagem especifica.
1357
+
1247
1358
  QUANDO USAR:
1248
1359
  - "Como ficaria se eu trocasse console.log por logger.info?"
1249
1360
  - "Preview de substituir var por let em todo o projeto"
@@ -1264,7 +1375,7 @@ Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudan
1264
1375
  "Texto de substituicao. Use $VAR para referenciar metavariaveis capturadas. Ex: 'logger.info($$$ARGS)'"
1265
1376
  ),
1266
1377
  lang: z.string().optional().describe(
1267
- "Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Default: typescript."
1378
+ "Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo. Para HTML/CSS, o motor usa fallback parse direto (findInFiles nao suporta nativamente)."
1268
1379
  ),
1269
1380
  path: z.string().optional().describe(
1270
1381
  "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-SENMQNTX.js";
4
+ } from "./chunk-LUKDYG3I.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-SENMQNTX.js";
3
+ } from "./chunk-LUKDYG3I.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.1",
3
+ "version": "0.2.1",
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",