@justmpm/ai-tool 0.7.0 → 0.7.2

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.
@@ -379,12 +379,93 @@ function formatDeadText(result) {
379
379
  out += `
380
380
  `;
381
381
  }
382
- out += `\u{1F4A1} SUGEST\xC3O
382
+ out += `\u{1F4A1} COMO RESOLVER
383
+
384
+ `;
385
+ out += ` 1. Falsos positivos? Adicione ao .analyze/areas.config.json:
386
+ `;
387
+ out += ` { "ignore": ["functions/lib/**", "**/*.test.ts"] }
388
+
389
+ `;
390
+ out += ` 2. Remover automaticamente:
383
391
  `;
384
- out += ` Execute 'npx knip --fix' para remover automaticamente.
392
+ out += ` npx knip --fix
393
+
394
+ `;
395
+ out += ` 3. Ver detalhes em JSON:
396
+ `;
397
+ out += ` ai-tool dead --format=json
385
398
  `;
399
+ const suggestions = generateIgnoreSuggestions(result);
400
+ if (suggestions.length > 0) {
401
+ out += `
402
+ \u{1F3AF} SUGEST\xD5ES INTELIGENTES
403
+
404
+ `;
405
+ for (const suggestion of suggestions) {
406
+ out += ` ${suggestion.icon} ${suggestion.pattern}
407
+ `;
408
+ out += ` Motivo: ${suggestion.reason}
409
+ `;
410
+ out += ` Arquivos afetados: ${suggestion.count}
411
+
412
+ `;
413
+ }
414
+ }
386
415
  return out;
387
416
  }
417
+ function generateIgnoreSuggestions(result) {
418
+ const suggestions = [];
419
+ const files = result.files.map((f) => f.path);
420
+ const libFiles = files.filter((f) => f.includes("functions/lib/"));
421
+ if (libFiles.length > 0) {
422
+ suggestions.push({
423
+ icon: "\u{1F4E6}",
424
+ pattern: "functions/lib/**",
425
+ reason: "Build compilado do Firebase Functions",
426
+ count: libFiles.length
427
+ });
428
+ }
429
+ const testFiles = files.filter((f) => /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f));
430
+ if (testFiles.length > 3) {
431
+ suggestions.push({
432
+ icon: "\u{1F9EA}",
433
+ pattern: "**/*.(test|spec).(ts|tsx|js|jsx)",
434
+ reason: "Arquivos de teste geralmente s\xE3o entry points pr\xF3prios",
435
+ count: testFiles.length
436
+ });
437
+ }
438
+ const configFiles = files.filter(
439
+ (f) => f.includes("vite.config") || f.includes("next.config") || f.includes("tailwind.config") || f.includes("jest.config") || f.includes("eslint.config")
440
+ );
441
+ if (configFiles.length > 0) {
442
+ suggestions.push({
443
+ icon: "\u2699\uFE0F",
444
+ pattern: "**/*.config.(ts|js|mjs|cjs)",
445
+ reason: "Arquivos de configura\xE7\xE3o s\xE3o entry points",
446
+ count: configFiles.length
447
+ });
448
+ }
449
+ const dtsFiles = files.filter((f) => f.endsWith(".d.ts"));
450
+ if (dtsFiles.length > 0) {
451
+ suggestions.push({
452
+ icon: "\u{1F4D8}",
453
+ pattern: "**/*.d.ts",
454
+ reason: "Arquivos de defini\xE7\xE3o TypeScript",
455
+ count: dtsFiles.length
456
+ });
457
+ }
458
+ const scriptFiles = files.filter((f) => f.startsWith("scripts/") || f.includes("/scripts/"));
459
+ if (scriptFiles.length > 0) {
460
+ suggestions.push({
461
+ icon: "\u{1F4DC}",
462
+ pattern: "scripts/**",
463
+ reason: "Scripts de automa\xE7\xE3o s\xE3o entry points",
464
+ count: scriptFiles.length
465
+ });
466
+ }
467
+ return suggestions;
468
+ }
388
469
  function formatImpactText(result) {
389
470
  let out = "";
390
471
  out += `
@@ -893,20 +974,32 @@ function formatFindText(result) {
893
974
  out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
894
975
 
895
976
  `;
896
- out += `\u{1F50D} "${result.query}"`;
897
- if (result.filters.type) {
898
- out += ` [type: ${result.filters.type}]`;
899
- }
900
- if (result.filters.area) {
901
- out += ` [area: ${result.filters.area}]`;
977
+ if (result.query) {
978
+ out += `\u{1F50D} "${result.query}"`;
979
+ if (result.filters.type) {
980
+ out += ` [type: ${result.filters.type}]`;
981
+ }
982
+ if (result.filters.area) {
983
+ out += ` [area: ${result.filters.area}]`;
984
+ }
985
+ out += `
986
+
987
+ `;
988
+ } else {
989
+ out += `\u{1F4CB} Listando todos os s\xEDmbolos do tipo: ${result.filters.type || "all"}
990
+
991
+ `;
902
992
  }
903
- out += `
993
+ if (!result.definition && result.references.length === 0 && result.summary.definitions === 0) {
994
+ if (result.query) {
995
+ out += `\u274C Nenhum resultado encontrado para "${result.query}"
904
996
 
905
997
  `;
906
- if (!result.definition && result.references.length === 0) {
907
- out += `\u274C Nenhum resultado encontrado para "${result.query}"
998
+ } else {
999
+ out += `\u274C Nenhum s\xEDmbolo do tipo "${result.filters.type}" encontrado
908
1000
 
909
1001
  `;
1002
+ }
910
1003
  out += `\u{1F4A1} Dicas:
911
1004
  `;
912
1005
  out += ` \u2022 Verifique a ortografia
@@ -1234,25 +1327,52 @@ var GRAPH_FILE = "graph.json";
1234
1327
  var MAP_FILE = "map.json";
1235
1328
  var DEAD_FILE = "dead.json";
1236
1329
  var SYMBOLS_FILE = "symbols.json";
1330
+ var HASH_IGNORED_DIRS = /* @__PURE__ */ new Set([
1331
+ "node_modules",
1332
+ ".git",
1333
+ ".next",
1334
+ "dist",
1335
+ "build",
1336
+ ".analyze",
1337
+ ".vercel",
1338
+ ".turbo",
1339
+ ".cache",
1340
+ "coverage",
1341
+ "functions/lib",
1342
+ "lib",
1343
+ ".output",
1344
+ "out",
1345
+ ".firebase"
1346
+ ]);
1237
1347
  function calculateFilesHash(cwd) {
1238
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1239
- const timestamps = [];
1240
- function scanDir(dir) {
1348
+ const extensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
1349
+ let hashAccumulator = 0;
1350
+ let fileCount = 0;
1351
+ let maxTimestamp = 0;
1352
+ const MAX_DEPTH = 6;
1353
+ function scanDir(dir, depth) {
1354
+ if (depth > MAX_DEPTH) return;
1241
1355
  try {
1242
1356
  const entries = readdirSync(dir, { withFileTypes: true });
1243
1357
  for (const entry of entries) {
1358
+ if (entry.name.startsWith(".")) continue;
1244
1359
  const fullPath = join2(dir, entry.name);
1245
1360
  if (entry.isDirectory()) {
1246
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".next" || entry.name === "dist" || entry.name === ".analyze") {
1247
- continue;
1361
+ if (HASH_IGNORED_DIRS.has(entry.name)) continue;
1362
+ if (depth < MAX_DEPTH) {
1363
+ scanDir(fullPath, depth + 1);
1248
1364
  }
1249
- scanDir(fullPath);
1250
1365
  } else if (entry.isFile()) {
1251
1366
  const ext = extname(entry.name).toLowerCase();
1252
- if (extensions.includes(ext)) {
1367
+ if (extensions.has(ext)) {
1253
1368
  try {
1254
1369
  const stat = statSync(fullPath);
1255
- timestamps.push(stat.mtimeMs);
1370
+ const mtime = stat.mtimeMs;
1371
+ hashAccumulator ^= Math.floor(mtime);
1372
+ fileCount++;
1373
+ if (mtime > maxTimestamp) {
1374
+ maxTimestamp = mtime;
1375
+ }
1256
1376
  } catch {
1257
1377
  }
1258
1378
  }
@@ -1261,17 +1381,16 @@ function calculateFilesHash(cwd) {
1261
1381
  } catch {
1262
1382
  }
1263
1383
  }
1264
- scanDir(cwd);
1384
+ scanDir(cwd, 0);
1265
1385
  try {
1266
1386
  const configPath = join2(cwd, CACHE_DIR, "areas.config.json");
1267
1387
  if (existsSync2(configPath)) {
1268
1388
  const stat = statSync(configPath);
1269
- timestamps.push(stat.mtimeMs);
1389
+ hashAccumulator ^= Math.floor(stat.mtimeMs);
1270
1390
  }
1271
1391
  } catch {
1272
1392
  }
1273
- const sum = timestamps.reduce((a, b) => a + b, 0);
1274
- return `${timestamps.length}-${Math.floor(sum)}`;
1393
+ return `${fileCount}-${hashAccumulator}-${maxTimestamp}`;
1275
1394
  }
1276
1395
  function getCacheDir(cwd) {
1277
1396
  return join2(cwd, CACHE_DIR);
@@ -2615,27 +2734,49 @@ function findCircularFromGraph(graph) {
2615
2734
  return cycles;
2616
2735
  }
2617
2736
  function findTargetFile(target, allFiles) {
2618
- const normalizedTarget = target.replace(/\\/g, "/");
2737
+ const normalizedTarget = target.replace(/\\/g, "/").toLowerCase();
2619
2738
  if (allFiles.includes(normalizedTarget)) {
2620
2739
  return normalizedTarget;
2621
2740
  }
2622
- const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
2741
+ const exactMatch = allFiles.find((f) => f.toLowerCase() === normalizedTarget);
2742
+ if (exactMatch) {
2743
+ return exactMatch;
2744
+ }
2745
+ const targetParts = normalizedTarget.split("/");
2746
+ const targetName = targetParts.pop() || "";
2623
2747
  const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2748
+ const targetDir = targetParts.join("/");
2624
2749
  const matches = [];
2625
2750
  for (const file of allFiles) {
2626
- const fileName = file.split("/").pop()?.toLowerCase() || "";
2751
+ const fileLower = file.toLowerCase();
2752
+ const fileParts = fileLower.split("/");
2753
+ const fileName = fileParts.pop() || "";
2627
2754
  const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2628
- if (fileNameNoExt === targetNameNoExt) {
2629
- matches.unshift(file);
2630
- } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2631
- matches.push(file);
2755
+ const fileDir = fileParts.join("/");
2756
+ if (fileLower === normalizedTarget) {
2757
+ matches.push({ file, priority: 1 });
2758
+ } else if (fileNameNoExt === targetNameNoExt) {
2759
+ if (targetDir && fileDir.includes(targetDir)) {
2760
+ matches.push({ file, priority: 2 });
2761
+ } else if (targetDir && normalizedTarget.includes(fileDir)) {
2762
+ matches.push({ file, priority: 3 });
2763
+ } else {
2764
+ matches.push({ file, priority: 4 });
2765
+ }
2766
+ } else if (fileLower.includes(normalizedTarget)) {
2767
+ matches.push({ file, priority: 5 });
2632
2768
  }
2633
2769
  }
2634
- if (matches.length === 1) {
2635
- return matches[0];
2770
+ if (matches.length === 0) {
2771
+ for (const file of allFiles) {
2772
+ if (file.toLowerCase().includes(targetNameNoExt)) {
2773
+ matches.push({ file, priority: 6 });
2774
+ }
2775
+ }
2636
2776
  }
2637
- if (matches.length > 1) {
2638
- return matches[0];
2777
+ if (matches.length > 0) {
2778
+ matches.sort((a, b) => a.priority - b.priority);
2779
+ return matches[0].file;
2639
2780
  }
2640
2781
  return null;
2641
2782
  }
@@ -2868,27 +3009,49 @@ function findRelatedTests(targetPath, allFiles) {
2868
3009
  return tests;
2869
3010
  }
2870
3011
  function findTargetFile2(target, allFiles) {
2871
- const normalizedTarget = target.replace(/\\/g, "/");
3012
+ const normalizedTarget = target.replace(/\\/g, "/").toLowerCase();
2872
3013
  if (allFiles.includes(normalizedTarget)) {
2873
3014
  return normalizedTarget;
2874
3015
  }
2875
- const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
3016
+ const exactMatch = allFiles.find((f) => f.toLowerCase() === normalizedTarget);
3017
+ if (exactMatch) {
3018
+ return exactMatch;
3019
+ }
3020
+ const targetParts = normalizedTarget.split("/");
3021
+ const targetName = targetParts.pop() || "";
2876
3022
  const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
3023
+ const targetDir = targetParts.join("/");
2877
3024
  const matches = [];
2878
3025
  for (const file of allFiles) {
2879
- const fileName = file.split("/").pop()?.toLowerCase() || "";
3026
+ const fileLower = file.toLowerCase();
3027
+ const fileParts = fileLower.split("/");
3028
+ const fileName = fileParts.pop() || "";
2880
3029
  const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2881
- if (fileNameNoExt === targetNameNoExt) {
2882
- matches.unshift(file);
2883
- } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2884
- matches.push(file);
3030
+ const fileDir = fileParts.join("/");
3031
+ if (fileLower === normalizedTarget) {
3032
+ matches.push({ file, priority: 1 });
3033
+ } else if (fileNameNoExt === targetNameNoExt) {
3034
+ if (targetDir && fileDir.includes(targetDir)) {
3035
+ matches.push({ file, priority: 2 });
3036
+ } else if (targetDir && normalizedTarget.includes(fileDir)) {
3037
+ matches.push({ file, priority: 3 });
3038
+ } else {
3039
+ matches.push({ file, priority: 4 });
3040
+ }
3041
+ } else if (fileLower.includes(normalizedTarget)) {
3042
+ matches.push({ file, priority: 5 });
2885
3043
  }
2886
3044
  }
2887
- if (matches.length === 1) {
2888
- return matches[0];
3045
+ if (matches.length === 0) {
3046
+ for (const file of allFiles) {
3047
+ if (file.toLowerCase().includes(targetNameNoExt)) {
3048
+ matches.push({ file, priority: 6 });
3049
+ }
3050
+ }
2889
3051
  }
2890
- if (matches.length > 1) {
2891
- return matches[0];
3052
+ if (matches.length > 0) {
3053
+ matches.sort((a, b) => a.priority - b.priority);
3054
+ return matches[0].file;
2892
3055
  }
2893
3056
  return null;
2894
3057
  }
@@ -3166,6 +3329,12 @@ import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3166
3329
  import { join as join4, extname as extname2, resolve } from "path";
3167
3330
  import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
3168
3331
  var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
3332
+ var DEBUG = process.env.DEBUG_ANALYZE === "true";
3333
+ function debugLog(...args) {
3334
+ if (DEBUG) {
3335
+ console.error("[analyze:debug]", ...args);
3336
+ }
3337
+ }
3169
3338
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
3170
3339
  "node_modules",
3171
3340
  "dist",
@@ -3176,7 +3345,17 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
3176
3345
  "coverage",
3177
3346
  ".turbo",
3178
3347
  ".vercel",
3179
- ".analyze"
3348
+ ".analyze",
3349
+ // Firebase Functions output
3350
+ "functions/lib",
3351
+ "lib",
3352
+ // Outros outputs comuns
3353
+ ".output",
3354
+ "out",
3355
+ ".firebase",
3356
+ "firebase-debug.log",
3357
+ "firestore-debug.log",
3358
+ "pubsub-debug.log"
3180
3359
  ]);
3181
3360
  var FIREBASE_V2_TRIGGERS = /* @__PURE__ */ new Set([
3182
3361
  // HTTPS (firebase-functions/v2/https)
@@ -3236,6 +3415,11 @@ var FIREBASE_V2_TRIGGERS = /* @__PURE__ */ new Set([
3236
3415
  ]);
3237
3416
  function indexProject(cwd) {
3238
3417
  const allFiles = getAllCodeFiles(cwd);
3418
+ debugLog(`Indexando ${allFiles.length} arquivos em ${cwd}`);
3419
+ const functionFiles = allFiles.filter((f) => f.includes("functions/src/"));
3420
+ if (functionFiles.length > 0) {
3421
+ debugLog(`Encontrados ${functionFiles.length} arquivos em functions/src/:`, functionFiles);
3422
+ }
3239
3423
  const project = createProject2(cwd);
3240
3424
  for (const file of allFiles) {
3241
3425
  try {
@@ -3337,6 +3521,13 @@ function indexProject(cwd) {
3337
3521
  }
3338
3522
  } else if (initKind === SyntaxKind2.CallExpression) {
3339
3523
  const triggerName = extractFirebaseTriggerName(init);
3524
+ if (DEBUG && filePath.includes("functions/src/")) {
3525
+ const initText = init.getText().slice(0, 50);
3526
+ debugLog(`[CF] Analisando: ${name} = ${initText}...`);
3527
+ if (triggerName) {
3528
+ debugLog(`[CF] \u2713 Trigger detectado: ${triggerName}`);
3529
+ }
3530
+ }
3340
3531
  if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
3341
3532
  const triggerInfo = extractTriggerInfo(init, triggerName);
3342
3533
  const symbol = {
@@ -3534,7 +3725,7 @@ function inferSymbolKind(name, context2) {
3534
3725
  function extractFirebaseTriggerName(init) {
3535
3726
  const text = init.getText();
3536
3727
  for (const trigger of FIREBASE_V2_TRIGGERS) {
3537
- const pattern = new RegExp(`(?:^|\\.)${trigger}\\s*\\(`);
3728
+ const pattern = new RegExp(`(?:^|\\.|\\s)${trigger}(?:<[^>]*>)?\\s*\\(`);
3538
3729
  if (pattern.test(text)) {
3539
3730
  return trigger;
3540
3731
  }
@@ -4032,6 +4223,42 @@ var IGNORED_DIRS3 = /* @__PURE__ */ new Set([
4032
4223
  ".vercel",
4033
4224
  ".analyze"
4034
4225
  ]);
4226
+ function resolveAreaId(target, config, allFiles) {
4227
+ const targetLower = target.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4228
+ if (config.areas[target]) {
4229
+ return target;
4230
+ }
4231
+ for (const [id, areaConfig] of Object.entries(config.areas)) {
4232
+ const name = areaConfig.name?.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4233
+ if (name === targetLower) {
4234
+ return id;
4235
+ }
4236
+ }
4237
+ for (const [id, name] of Object.entries(AREA_NAMES)) {
4238
+ const nameNormalized = name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4239
+ if (nameNormalized === targetLower) {
4240
+ return id;
4241
+ }
4242
+ }
4243
+ for (const id of Object.keys(config.areas)) {
4244
+ if (id.toLowerCase().includes(targetLower)) {
4245
+ return id;
4246
+ }
4247
+ }
4248
+ const detectedAreas = /* @__PURE__ */ new Set();
4249
+ for (const filePath of allFiles) {
4250
+ const areas2 = detectFileAreas(filePath, config);
4251
+ for (const areaId of areas2) {
4252
+ if (areaId.toLowerCase().includes(targetLower)) {
4253
+ detectedAreas.add(areaId);
4254
+ }
4255
+ }
4256
+ }
4257
+ if (detectedAreas.size > 0) {
4258
+ return [...detectedAreas][0];
4259
+ }
4260
+ return target;
4261
+ }
4035
4262
  async function area(target, options = {}) {
4036
4263
  const cwd = options.cwd || process.cwd();
4037
4264
  const format = options.format || "text";
@@ -4043,9 +4270,10 @@ async function area(target, options = {}) {
4043
4270
  try {
4044
4271
  const config = readConfig(cwd);
4045
4272
  const allFiles = getAllCodeFiles4(cwd);
4273
+ const resolvedTarget = resolveAreaId(target, config, allFiles);
4046
4274
  const filteredFiles = allFiles.filter((filePath) => !isFileIgnored(filePath, config));
4047
4275
  const areaFiles = [];
4048
- const targetLower = target.toLowerCase();
4276
+ const targetLower = resolvedTarget.toLowerCase();
4049
4277
  for (const filePath of filteredFiles) {
4050
4278
  const fileAreas = detectFileAreas(filePath, config);
4051
4279
  const belongsToArea = fileAreas.some(
@@ -4083,15 +4311,19 @@ async function area(target, options = {}) {
4083
4311
  for (const cat of Object.keys(byCategory)) {
4084
4312
  byCategory[cat].sort((a, b) => a.path.localeCompare(b.path));
4085
4313
  }
4086
- const realAreaId = findRealAreaId(target, filteredFiles, config);
4314
+ const realAreaId = resolvedTarget !== target ? resolvedTarget : findRealAreaId(target, filteredFiles, config);
4315
+ const finalAreaId = realAreaId || resolvedTarget;
4316
+ const nameConversionMsg = resolvedTarget !== target ? `
4317
+ \u{1F4A1} Buscando \xE1rea "${getAreaName(finalAreaId, config)}" (ID: ${finalAreaId})
4318
+ ` : "";
4087
4319
  const detectedArea = {
4088
- id: realAreaId || target,
4089
- name: getAreaName(realAreaId || target, config),
4090
- description: getAreaDescription(realAreaId || target, config),
4320
+ id: finalAreaId,
4321
+ name: getAreaName(finalAreaId, config),
4322
+ description: getAreaDescription(finalAreaId, config),
4091
4323
  files: areaFiles,
4092
4324
  fileCount: areaFiles.length,
4093
4325
  categories,
4094
- isAutoDetected: !config.areas[realAreaId || target]
4326
+ isAutoDetected: !config.areas[finalAreaId]
4095
4327
  };
4096
4328
  const result = {
4097
4329
  version: "1.0.0",
@@ -4102,7 +4334,8 @@ async function area(target, options = {}) {
4102
4334
  if (format === "json") {
4103
4335
  return JSON.stringify(result, null, 2);
4104
4336
  }
4105
- return formatAreaDetailText(result, { full, filterType });
4337
+ const output = formatAreaDetailText(result, { full, filterType });
4338
+ return nameConversionMsg + output;
4106
4339
  } catch (error) {
4107
4340
  const message = error instanceof Error ? error.message : String(error);
4108
4341
  throw new Error(`Erro ao executar area: ${message}`);
@@ -4217,9 +4450,11 @@ Ou edite manualmente o arquivo existente.
4217
4450
  patterns
4218
4451
  };
4219
4452
  }
4453
+ const suggestedIgnore = detectSuggestedIgnorePatterns(allFiles);
4220
4454
  const newConfig = {
4221
4455
  $schema: "./areas.schema.json",
4222
4456
  version: "1.0.0",
4457
+ ignore: suggestedIgnore,
4223
4458
  areas: generatedAreas,
4224
4459
  descriptions: {},
4225
4460
  settings: {
@@ -4234,7 +4469,12 @@ Ou edite manualmente o arquivo existente.
4234
4469
  \u2705 Arquivo criado: .analyze/areas.config.json
4235
4470
 
4236
4471
  \u{1F4E6} \xC1reas detectadas: ${sortedAreas.length}
4237
-
4472
+ `;
4473
+ if (suggestedIgnore.length > 0) {
4474
+ out += `\u{1F6AB} Padr\xF5es ignorados: ${suggestedIgnore.length}
4475
+ `;
4476
+ }
4477
+ out += `
4238
4478
  `;
4239
4479
  for (const [areaId, files] of sortedAreas.slice(0, 15)) {
4240
4480
  const name = getAreaName(areaId, newConfig);
@@ -4254,6 +4494,15 @@ Ou edite manualmente o arquivo existente.
4254
4494
  Use 'ai-tool areas' para ver detalhes
4255
4495
  `;
4256
4496
  }
4497
+ if (suggestedIgnore.length > 0) {
4498
+ out += `
4499
+ \u{1F4CB} Padr\xF5es adicionados ao ignore:
4500
+ `;
4501
+ for (const pattern of suggestedIgnore) {
4502
+ out += ` \u2022 ${pattern}
4503
+ `;
4504
+ }
4505
+ }
4257
4506
  out += `
4258
4507
  \u{1F4A1} Edite o arquivo para:
4259
4508
  - Renomear \xE1reas (campo "name")
@@ -4295,6 +4544,28 @@ function inferPatternsFromFiles(files) {
4295
4544
  }
4296
4545
  return [...patterns].sort();
4297
4546
  }
4547
+ function detectSuggestedIgnorePatterns(files) {
4548
+ const patterns = [];
4549
+ if (files.some((f) => f.includes("functions/lib/"))) {
4550
+ patterns.push("functions/lib/**");
4551
+ }
4552
+ const testCount = files.filter((f) => /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f)).length;
4553
+ if (testCount > 3) {
4554
+ patterns.push("**/*.test.{ts,tsx,js,jsx}");
4555
+ patterns.push("**/*.spec.{ts,tsx,js,jsx}");
4556
+ }
4557
+ const dtsCount = files.filter((f) => f.endsWith(".d.ts")).length;
4558
+ if (dtsCount > 2) {
4559
+ patterns.push("**/*.d.ts");
4560
+ }
4561
+ const configCount = files.filter(
4562
+ (f) => /\.(config|conf)\.(ts|js|mjs|cjs)$/.test(f)
4563
+ ).length;
4564
+ if (configCount > 2) {
4565
+ patterns.push("**/*.config.{ts,js,mjs,cjs}");
4566
+ }
4567
+ return patterns;
4568
+ }
4298
4569
  function getAllCodeFiles5(dir, files = [], baseDir = dir) {
4299
4570
  try {
4300
4571
  const entries = readdirSync6(dir);
@@ -4423,12 +4694,43 @@ function formatFunctionsText(result) {
4423
4694
  out += ` Total: ${result.summary.total} functions
4424
4695
  `;
4425
4696
  out += ` Exportadas: ${result.summary.exported}
4426
-
4697
+ `;
4698
+ if (result.summary.total > 0) {
4699
+ out += `
4700
+ \u{1F4A1} Filtros dispon\xEDveis:
4701
+ `;
4702
+ out += ` ai-tool functions --trigger=onCall
4703
+ `;
4704
+ out += ` ai-tool functions --trigger=onDocumentCreated
4705
+ `;
4706
+ }
4707
+ out += `
4427
4708
  `;
4428
4709
  if (result.summary.total === 0) {
4429
- out += ` \u26A0\uFE0F Nenhuma Cloud Function detectada.
4710
+ out += ` \u26A0\uFE0F NENHUMA CLOUD FUNCTION DETECTADA
4711
+
4712
+ `;
4713
+ out += ` Poss\xEDveis causas:
4714
+ `;
4715
+ out += ` 1. O projeto n\xE3o \xE9 Firebase (n\xE3o encontrou .firebaserc ou firebase.json)
4716
+ `;
4717
+ out += ` 2. N\xE3o h\xE1 arquivo functions/src/index.ts
4718
+ `;
4719
+ out += ` 3. Os triggers n\xE3o usam padr\xF5es v2 (onCall, onDocumentCreated, etc)
4720
+ `;
4721
+ out += ` 4. O cache est\xE1 desatualizado \u2192 tente: ai-tool functions --no-cache
4722
+ `;
4723
+ out += ` 5. Para debug: DEBUG_ANALYZE=true ai-tool functions
4724
+
4430
4725
  `;
4431
- out += ` Verifique se seus triggers usam padr\xE3o Firebase v2 (onCall, onDocumentCreated, etc).
4726
+ out += ` Padr\xF5es suportados:
4727
+ `;
4728
+ out += ` export const minhaFunc = onCall((request) => { ... })
4729
+ `;
4730
+ out += ` export const minhaFunc = onDocumentCreated("path", (event) => { ... })
4731
+
4732
+ `;
4733
+ out += ` Documenta\xE7\xE3o: https://firebase.google.com/docs/functions
4432
4734
  `;
4433
4735
  return out;
4434
4736
  }
@@ -4523,8 +4825,9 @@ async function find(query, options = {}) {
4523
4825
  const defOnly = options.def ?? false;
4524
4826
  const refsOnly = options.refs ?? false;
4525
4827
  const useCache = options.cache !== false;
4526
- if (!query || query.trim().length === 0) {
4527
- throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool find useAuth");
4828
+ const listAllMode = !query && defOnly && filterType && filterType !== "all";
4829
+ if (!query && !listAllMode) {
4830
+ throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool find useAuth\n Ou use --def para listar todos de um tipo: ai-tool find --type=trigger --def");
4528
4831
  }
4529
4832
  try {
4530
4833
  let index;
@@ -4618,11 +4921,13 @@ async function find(query, options = {}) {
4618
4921
  }
4619
4922
  function searchInIndex(index, query, filterType, allowedFiles) {
4620
4923
  const matches = [];
4621
- const queryLower = query.toLowerCase();
4924
+ const queryLower = query?.toLowerCase() || "";
4622
4925
  const processedSymbols = /* @__PURE__ */ new Set();
4926
+ const listAllMode = !query && filterType && filterType !== "all";
4623
4927
  for (const [name, symbols] of Object.entries(index.symbolsByName)) {
4624
4928
  const nameLower = name.toLowerCase();
4625
- if (nameLower === queryLower || nameLower.includes(queryLower)) {
4929
+ const shouldInclude = listAllMode || nameLower === queryLower || nameLower.includes(queryLower);
4930
+ if (shouldInclude) {
4626
4931
  for (const symbol of symbols) {
4627
4932
  if (allowedFiles && !allowedFiles.has(symbol.file)) continue;
4628
4933
  if (!matchesType(symbol.kind, filterType)) continue;
@@ -4642,25 +4947,27 @@ function searchInIndex(index, query, filterType, allowedFiles) {
4642
4947
  }
4643
4948
  }
4644
4949
  }
4645
- for (const [filePath, fileData] of Object.entries(index.files)) {
4646
- if (allowedFiles && !allowedFiles.has(filePath)) continue;
4647
- for (const imp of fileData.imports) {
4648
- for (const spec of imp.specifiers) {
4649
- const specLower = spec.toLowerCase();
4650
- if (specLower === queryLower || specLower.includes(queryLower)) {
4651
- const key = `import:${filePath}:${spec}:${imp.source}`;
4652
- if (processedSymbols.has(key)) continue;
4653
- processedSymbols.add(key);
4654
- matches.push({
4655
- file: filePath,
4656
- line: 1,
4657
- // Imports geralmente estão no topo
4658
- column: 0,
4659
- code: `import { ${spec} } from '${imp.source}'`,
4660
- matchType: "import",
4661
- symbolType: inferSymbolTypeFromName(spec),
4662
- category: fileData.category
4663
- });
4950
+ if (!listAllMode) {
4951
+ for (const [filePath, fileData] of Object.entries(index.files)) {
4952
+ if (allowedFiles && !allowedFiles.has(filePath)) continue;
4953
+ for (const imp of fileData.imports) {
4954
+ for (const spec of imp.specifiers) {
4955
+ const specLower = spec.toLowerCase();
4956
+ if (specLower === queryLower || specLower.includes(queryLower)) {
4957
+ const key = `import:${filePath}:${spec}:${imp.source}`;
4958
+ if (processedSymbols.has(key)) continue;
4959
+ processedSymbols.add(key);
4960
+ matches.push({
4961
+ file: filePath,
4962
+ line: 1,
4963
+ // Imports geralmente estão no topo
4964
+ column: 0,
4965
+ code: `import { ${spec} } from '${imp.source}'`,
4966
+ matchType: "import",
4967
+ symbolType: inferSymbolTypeFromName(spec),
4968
+ category: fileData.category
4969
+ });
4970
+ }
4664
4971
  }
4665
4972
  }
4666
4973
  }
package/dist/cli.js CHANGED
@@ -13,9 +13,10 @@ import {
13
13
  impact,
14
14
  map,
15
15
  suggest
16
- } from "./chunk-CRVOX3U4.js";
16
+ } from "./chunk-XEFS66GU.js";
17
17
 
18
18
  // src/cli.ts
19
+ import { resolve } from "path";
19
20
  var HELP = `
20
21
  ai-tool v${VERSION} - Analise de dependencias e impacto
21
22
 
@@ -107,7 +108,7 @@ async function main() {
107
108
  }
108
109
  }
109
110
  if (flags.mcp) {
110
- const { startMcpServer } = await import("./server-SZHX26N6.js");
111
+ const { startMcpServer } = await import("./server-XR6C3XM3.js");
111
112
  await startMcpServer();
112
113
  return;
113
114
  }
@@ -122,7 +123,7 @@ async function main() {
122
123
  const command = positional[0];
123
124
  const target = positional[1];
124
125
  const format = flags.format || "text";
125
- const cwd = flags.cwd || process.cwd();
126
+ const cwd = flags.cwd ? resolve(flags.cwd) : process.cwd();
126
127
  const cache = !flags["no-cache"];
127
128
  try {
128
129
  let result;
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ import {
47
47
  setFileDescription,
48
48
  suggest,
49
49
  writeConfig
50
- } from "./chunk-CRVOX3U4.js";
50
+ } from "./chunk-XEFS66GU.js";
51
51
  export {
52
52
  AREA_DESCRIPTIONS,
53
53
  AREA_NAMES,
@@ -11,7 +11,7 @@ import {
11
11
  impact,
12
12
  map,
13
13
  suggest
14
- } from "./chunk-CRVOX3U4.js";
14
+ } from "./chunk-XEFS66GU.js";
15
15
 
16
16
  // src/mcp/server.ts
17
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/ai-tool",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Ferramenta de análise de dependências e impacto para projetos TypeScript/JavaScript. Usa Skott + Knip internamente.",
5
5
  "keywords": [
6
6
  "dependency-analysis",