@justmpm/ai-tool 0.6.1 → 0.7.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.
@@ -47,6 +47,16 @@ function detectCategory(filePath) {
47
47
  if (fileName.endsWith(".astro") && normalized.includes("/layouts/")) {
48
48
  return "layout";
49
49
  }
50
+ if (normalized.includes("functions/src/") && !normalized.includes("/__tests__/")) {
51
+ if (!fileName.includes("index") && !normalized.includes("/types/")) {
52
+ return "cloud-function";
53
+ }
54
+ }
55
+ if (normalized.includes("/triggers/") || normalized.includes("/callable/") || normalized.includes("/scheduled/")) {
56
+ if (normalized.includes("functions/") || normalized.includes("firebase/")) {
57
+ return "cloud-function";
58
+ }
59
+ }
50
60
  if (normalized.includes("/api/") || normalized.includes("/server/") || fileNameNoExt.endsWith(".server") || fileNameNoExt === "+server") {
51
61
  return "route";
52
62
  }
@@ -106,6 +116,7 @@ var categoryIcons = {
106
116
  type: "\u{1F4DD}",
107
117
  config: "\u2699\uFE0F",
108
118
  test: "\u{1F9EA}",
119
+ "cloud-function": "\u26A1",
109
120
  other: "\u{1F4C1}"
110
121
  };
111
122
  function isEntryPoint(filePath) {
@@ -149,6 +160,7 @@ function formatMapSummary(result, areasInfo) {
149
160
  "layout",
150
161
  "route",
151
162
  "store",
163
+ "cloud-function",
152
164
  "other"
153
165
  ];
154
166
  const catParts = [];
@@ -167,6 +179,16 @@ function formatMapSummary(result, areasInfo) {
167
179
  }
168
180
  out += `
169
181
  `;
182
+ const cloudFunctionCount = result.summary.categories["cloud-function"] || 0;
183
+ if (cloudFunctionCount > 0) {
184
+ out += `\u{1F525} Firebase Cloud Functions detectado
185
+ `;
186
+ out += ` ${cloudFunctionCount} function(s) em functions/src/
187
+ `;
188
+ out += ` \u2192 Use 'ai-tool functions' para listar triggers
189
+
190
+ `;
191
+ }
170
192
  if (result.circularDependencies.length > 0) {
171
193
  out += `\u26A0\uFE0F ${result.circularDependencies.length} depend\xEAncia(s) circular(es) detectada(s)
172
194
  `;
@@ -225,13 +247,14 @@ function formatMapText(result) {
225
247
  "type",
226
248
  "config",
227
249
  "test",
250
+ "cloud-function",
228
251
  "other"
229
252
  ];
230
253
  for (const cat of catOrder) {
231
254
  const count = result.summary.categories[cat];
232
255
  if (count) {
233
256
  const icon = categoryIcons[cat];
234
- out += ` ${icon} ${cat.padEnd(12)} ${count}
257
+ out += ` ${icon} ${cat.padEnd(14)} ${count}
235
258
  `;
236
259
  }
237
260
  }
@@ -356,12 +379,93 @@ function formatDeadText(result) {
356
379
  out += `
357
380
  `;
358
381
  }
359
- 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:
391
+ `;
392
+ out += ` npx knip --fix
393
+
394
+ `;
395
+ out += ` 3. Ver detalhes em JSON:
396
+ `;
397
+ out += ` ai-tool dead --format=json
398
+ `;
399
+ const suggestions = generateIgnoreSuggestions(result);
400
+ if (suggestions.length > 0) {
401
+ out += `
402
+ \u{1F3AF} SUGEST\xD5ES INTELIGENTES
403
+
360
404
  `;
361
- out += ` Execute 'npx knip --fix' para remover automaticamente.
405
+ for (const suggestion of suggestions) {
406
+ out += ` ${suggestion.icon} ${suggestion.pattern}
362
407
  `;
408
+ out += ` Motivo: ${suggestion.reason}
409
+ `;
410
+ out += ` Arquivos afetados: ${suggestion.count}
411
+
412
+ `;
413
+ }
414
+ }
363
415
  return out;
364
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
+ }
365
469
  function formatImpactText(result) {
366
470
  let out = "";
367
471
  out += `
@@ -799,6 +903,7 @@ function formatAreaDetailText(result, options = {}) {
799
903
  "type",
800
904
  "config",
801
905
  "test",
906
+ "cloud-function",
802
907
  "other"
803
908
  ];
804
909
  const catParts = [];
@@ -869,20 +974,32 @@ function formatFindText(result) {
869
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
870
975
 
871
976
  `;
872
- out += `\u{1F50D} "${result.query}"`;
873
- if (result.filters.type) {
874
- out += ` [type: ${result.filters.type}]`;
875
- }
876
- if (result.filters.area) {
877
- 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
+ `;
878
992
  }
879
- 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}"
880
996
 
881
997
  `;
882
- if (!result.definition && result.references.length === 0) {
883
- out += `\u274C Nenhum resultado encontrado para "${result.query}"
998
+ } else {
999
+ out += `\u274C Nenhum s\xEDmbolo do tipo "${result.filters.type}" encontrado
884
1000
 
885
1001
  `;
1002
+ }
886
1003
  out += `\u{1F4A1} Dicas:
887
1004
  `;
888
1005
  out += ` \u2022 Verifique a ortografia
@@ -1057,6 +1174,26 @@ function formatAreaContextText(result) {
1057
1174
  `;
1058
1175
  out += ` state: ${st.state}
1059
1176
 
1177
+ `;
1178
+ }
1179
+ }
1180
+ if (result.triggers && result.triggers.length > 0) {
1181
+ out += `\u26A1 TRIGGERS (${result.triggers.length})
1182
+
1183
+ `;
1184
+ for (const t of result.triggers) {
1185
+ const filePart = t.file.split("/").pop() || t.file;
1186
+ out += ` ${t.name}`.padEnd(35) + `${t.triggerType.padEnd(25)}[${filePart}:${t.line}]
1187
+ `;
1188
+ if (t.triggerPath) {
1189
+ out += ` path: ${t.triggerPath}
1190
+ `;
1191
+ }
1192
+ if (t.triggerSchedule) {
1193
+ out += ` schedule: ${t.triggerSchedule}
1194
+ `;
1195
+ }
1196
+ out += `
1060
1197
  `;
1061
1198
  }
1062
1199
  }
@@ -1077,6 +1214,10 @@ function formatAreaContextText(result) {
1077
1214
  `;
1078
1215
  out += ` Stores: ${result.stores.length}
1079
1216
  `;
1217
+ if (result.triggers && result.triggers.length > 0) {
1218
+ out += ` Triggers: ${result.triggers.length}
1219
+ `;
1220
+ }
1080
1221
  return out;
1081
1222
  }
1082
1223
 
@@ -1559,9 +1700,17 @@ var FOLDER_PATTERNS = [
1559
1700
  { pattern: /store\/.*[Aa]uth/, area: "auth", priority: 70 },
1560
1701
  { pattern: /store\/.*[Uu]ser/, area: "user", priority: 70 },
1561
1702
  // ============================================================================
1562
- // CLOUD FUNCTIONS
1703
+ // FIREBASE CLOUD FUNCTIONS (expandido)
1563
1704
  // ============================================================================
1564
1705
  { pattern: /functions\/src\//, area: "cloud-functions", priority: 80 },
1706
+ { pattern: /functions\/src\/triggers\//, area: "triggers", priority: 95 },
1707
+ { pattern: /functions\/src\/callable\//, area: "callable", priority: 95 },
1708
+ { pattern: /functions\/src\/scheduled\//, area: "scheduled", priority: 95 },
1709
+ { pattern: /functions\/src\/firestore\//, area: "firestore-triggers", priority: 95 },
1710
+ { pattern: /functions\/src\/auth\//, area: "auth-triggers", priority: 95 },
1711
+ { pattern: /functions\/src\/storage\//, area: "storage-triggers", priority: 95 },
1712
+ { pattern: /functions\/src\/pubsub\//, area: "pubsub", priority: 95 },
1713
+ { pattern: /functions\/src\/https\//, area: "callable", priority: 95 },
1565
1714
  // ============================================================================
1566
1715
  // OUTROS
1567
1716
  // ============================================================================
@@ -1611,7 +1760,17 @@ var KEYWORD_PATTERNS = [
1611
1760
  { keyword: /[Mm]anifest/, area: "pwa", priority: 55 },
1612
1761
  // PDF (genérico)
1613
1762
  { keyword: /[Pp]df[Ee]xport/, area: "export", priority: 60 },
1614
- { keyword: /[Dd]ocx[Ee]xport/, area: "export", priority: 60 }
1763
+ { keyword: /[Dd]ocx[Ee]xport/, area: "export", priority: 60 },
1764
+ // Firebase Cloud Functions triggers
1765
+ { keyword: /[Oo]nCall/, area: "callable", priority: 70 },
1766
+ { keyword: /[Oo]nRequest/, area: "callable", priority: 70 },
1767
+ { keyword: /[Oo]nSchedule/, area: "scheduled", priority: 70 },
1768
+ { keyword: /[Oo]n[A-Z].*Created/, area: "triggers", priority: 70 },
1769
+ { keyword: /[Oo]n[A-Z].*Updated/, area: "triggers", priority: 70 },
1770
+ { keyword: /[Oo]n[A-Z].*Deleted/, area: "triggers", priority: 70 },
1771
+ { keyword: /[Oo]n[A-Z].*Written/, area: "triggers", priority: 70 },
1772
+ { keyword: /[Oo]nObject/, area: "storage-triggers", priority: 70 },
1773
+ { keyword: /[Oo]nMessage/, area: "pubsub", priority: 70 }
1615
1774
  ];
1616
1775
  var AREA_NAMES = {
1617
1776
  // Autenticação e usuário
@@ -1658,6 +1817,13 @@ var AREA_NAMES = {
1658
1817
  layout: "Layout",
1659
1818
  "shared-ui": "UI Compartilhada",
1660
1819
  "cloud-functions": "Cloud Functions",
1820
+ triggers: "Triggers Firebase",
1821
+ callable: "Callable Functions",
1822
+ scheduled: "Scheduled Functions",
1823
+ "firestore-triggers": "Firestore Triggers",
1824
+ "auth-triggers": "Auth Triggers",
1825
+ "storage-triggers": "Storage Triggers",
1826
+ pubsub: "Pub/Sub",
1661
1827
  assets: "Assets",
1662
1828
  scripts: "Scripts",
1663
1829
  // UI patterns
@@ -1711,7 +1877,14 @@ var AREA_DESCRIPTIONS = {
1711
1877
  core: "Providers e configura\xE7\xE3o principal",
1712
1878
  layout: "Layout e navega\xE7\xE3o",
1713
1879
  "shared-ui": "Componentes de UI compartilhados",
1714
- "cloud-functions": "Cloud Functions (serverless)",
1880
+ "cloud-functions": "Firebase Cloud Functions (serverless)",
1881
+ triggers: "Event-driven Firebase triggers",
1882
+ callable: "HTTPS callable functions (frontend SDK)",
1883
+ scheduled: "Scheduled/cron functions",
1884
+ "firestore-triggers": "Triggers para eventos do Firestore",
1885
+ "auth-triggers": "Triggers para eventos de autentica\xE7\xE3o",
1886
+ "storage-triggers": "Triggers para eventos do Storage",
1887
+ pubsub: "Triggers para mensagens Pub/Sub",
1715
1888
  assets: "Assets p\xFAblicos",
1716
1889
  scripts: "Scripts de automa\xE7\xE3o",
1717
1890
  // UI patterns
@@ -2748,6 +2921,12 @@ function collectSuggestions(targetPath, graph, allFiles, limit) {
2748
2921
  priority: "low"
2749
2922
  });
2750
2923
  }
2924
+ const cloudFunctionSuggestions = suggestFirebaseRules(targetPath, allFiles);
2925
+ for (const suggestion of cloudFunctionSuggestions) {
2926
+ if (addedPaths.has(suggestion.path)) continue;
2927
+ addedPaths.add(suggestion.path);
2928
+ suggestions.push(suggestion);
2929
+ }
2751
2930
  const priorityOrder = {
2752
2931
  critical: 0,
2753
2932
  high: 1,
@@ -2806,6 +2985,63 @@ function findTargetFile2(target, allFiles) {
2806
2985
  }
2807
2986
  return null;
2808
2987
  }
2988
+ function suggestFirebaseRules(targetPath, allFiles) {
2989
+ const suggestions = [];
2990
+ if (!targetPath.includes("functions/src/")) {
2991
+ return suggestions;
2992
+ }
2993
+ const isFirestoreTrigger = targetPath.toLowerCase().includes("firestore") || targetPath.toLowerCase().includes("document");
2994
+ const isStorageTrigger = targetPath.toLowerCase().includes("storage");
2995
+ for (const file of allFiles) {
2996
+ const fileName = file.split("/").pop()?.toLowerCase() || "";
2997
+ if (fileName === "firestore.rules" || file.endsWith("firestore.rules")) {
2998
+ if (isFirestoreTrigger) {
2999
+ suggestions.push({
3000
+ path: file,
3001
+ category: "config",
3002
+ reason: "Regras Firestore (trigger relacionado)",
3003
+ priority: "high"
3004
+ });
3005
+ } else {
3006
+ suggestions.push({
3007
+ path: file,
3008
+ category: "config",
3009
+ reason: "Regras Firestore (Cloud Function)",
3010
+ priority: "medium"
3011
+ });
3012
+ }
3013
+ }
3014
+ if (fileName === "storage.rules" || file.endsWith("storage.rules")) {
3015
+ if (isStorageTrigger) {
3016
+ suggestions.push({
3017
+ path: file,
3018
+ category: "config",
3019
+ reason: "Regras Storage (trigger relacionado)",
3020
+ priority: "high"
3021
+ });
3022
+ } else {
3023
+ suggestions.push({
3024
+ path: file,
3025
+ category: "config",
3026
+ reason: "Regras Storage (Cloud Function)",
3027
+ priority: "low"
3028
+ });
3029
+ }
3030
+ }
3031
+ }
3032
+ if (!targetPath.endsWith("index.ts")) {
3033
+ const indexFile = allFiles.find((f) => f.includes("functions/src/index"));
3034
+ if (indexFile) {
3035
+ suggestions.push({
3036
+ path: indexFile,
3037
+ category: "config",
3038
+ reason: "Exports de Cloud Functions",
3039
+ priority: "high"
3040
+ });
3041
+ }
3042
+ }
3043
+ return suggestions;
3044
+ }
2809
3045
  function formatNotFound2(target, allFiles) {
2810
3046
  return formatFileNotFound({ target, allFiles, command: "suggest" });
2811
3047
  }
@@ -2881,11 +3117,11 @@ function simplifyType(typeText) {
2881
3117
  return simplified;
2882
3118
  }
2883
3119
  function extractFunctions(sourceFile) {
2884
- const functions = [];
3120
+ const functions2 = [];
2885
3121
  for (const func of sourceFile.getFunctions()) {
2886
3122
  const name = func.getName();
2887
3123
  if (!name) continue;
2888
- functions.push({
3124
+ functions2.push({
2889
3125
  name,
2890
3126
  params: extractParams(func.getParameters()),
2891
3127
  returnType: simplifyType(func.getReturnType().getText()),
@@ -2903,7 +3139,7 @@ function extractFunctions(sourceFile) {
2903
3139
  if (init.getKind() === SyntaxKind.ArrowFunction) {
2904
3140
  const arrowFunc = init.asKind(SyntaxKind.ArrowFunction);
2905
3141
  if (!arrowFunc) continue;
2906
- functions.push({
3142
+ functions2.push({
2907
3143
  name: varDecl.getName(),
2908
3144
  params: extractParams(arrowFunc.getParameters()),
2909
3145
  returnType: simplifyType(arrowFunc.getReturnType().getText()),
@@ -2916,7 +3152,7 @@ function extractFunctions(sourceFile) {
2916
3152
  if (init.getKind() === SyntaxKind.FunctionExpression) {
2917
3153
  const funcExpr = init.asKind(SyntaxKind.FunctionExpression);
2918
3154
  if (!funcExpr) continue;
2919
- functions.push({
3155
+ functions2.push({
2920
3156
  name: varDecl.getName(),
2921
3157
  params: extractParams(funcExpr.getParameters()),
2922
3158
  returnType: simplifyType(funcExpr.getReturnType().getText()),
@@ -2928,7 +3164,7 @@ function extractFunctions(sourceFile) {
2928
3164
  }
2929
3165
  }
2930
3166
  }
2931
- return functions;
3167
+ return functions2;
2932
3168
  }
2933
3169
  function extractTypes(sourceFile) {
2934
3170
  const types = [];
@@ -3023,6 +3259,12 @@ import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3023
3259
  import { join as join4, extname as extname2, resolve } from "path";
3024
3260
  import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
3025
3261
  var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
3262
+ var DEBUG = process.env.DEBUG_ANALYZE === "true";
3263
+ function debugLog(...args) {
3264
+ if (DEBUG) {
3265
+ console.error("[analyze:debug]", ...args);
3266
+ }
3267
+ }
3026
3268
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
3027
3269
  "node_modules",
3028
3270
  "dist",
@@ -3035,8 +3277,69 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
3035
3277
  ".vercel",
3036
3278
  ".analyze"
3037
3279
  ]);
3280
+ var FIREBASE_V2_TRIGGERS = /* @__PURE__ */ new Set([
3281
+ // HTTPS (firebase-functions/v2/https)
3282
+ "onCall",
3283
+ "onRequest",
3284
+ // Firestore (firebase-functions/v2/firestore)
3285
+ "onDocumentCreated",
3286
+ "onDocumentCreatedWithAuthContext",
3287
+ "onDocumentUpdated",
3288
+ "onDocumentUpdatedWithAuthContext",
3289
+ "onDocumentDeleted",
3290
+ "onDocumentDeletedWithAuthContext",
3291
+ "onDocumentWritten",
3292
+ "onDocumentWrittenWithAuthContext",
3293
+ // Realtime Database (firebase-functions/v2/database)
3294
+ "onValueCreated",
3295
+ "onValueUpdated",
3296
+ "onValueDeleted",
3297
+ "onValueWritten",
3298
+ // Scheduler (firebase-functions/v2/scheduler)
3299
+ "onSchedule",
3300
+ // Storage (firebase-functions/v2/storage)
3301
+ "onObjectFinalized",
3302
+ "onObjectArchived",
3303
+ "onObjectDeleted",
3304
+ "onMetadataUpdated",
3305
+ // Pub/Sub (firebase-functions/v2/pubsub)
3306
+ "onMessagePublished",
3307
+ // Identity (firebase-functions/v2/identity)
3308
+ "beforeUserCreated",
3309
+ "beforeUserSignedIn",
3310
+ "beforeEmailSent",
3311
+ "beforeSmsSent",
3312
+ // Alerts - Crashlytics (firebase-functions/v2/alerts/crashlytics)
3313
+ "onNewFatalIssuePublished",
3314
+ "onNewNonfatalIssuePublished",
3315
+ "onNewAnrIssuePublished",
3316
+ "onRegressionAlertPublished",
3317
+ "onStabilityDigestPublished",
3318
+ "onVelocityAlertPublished",
3319
+ // Alerts - App Distribution (firebase-functions/v2/alerts/appDistribution)
3320
+ "onNewTesterIosDevicePublished",
3321
+ "onInAppFeedbackPublished",
3322
+ // Alerts - Performance (firebase-functions/v2/alerts/performance)
3323
+ "onThresholdAlertPublished",
3324
+ // Alerts - Billing (firebase-functions/v2/alerts/billing)
3325
+ "onPlanUpdatePublished",
3326
+ "onPlanAutomatedUpdatePublished",
3327
+ // Remote Config (firebase-functions/v2/remoteConfig)
3328
+ "onConfigUpdated",
3329
+ // Eventarc (firebase-functions/v2/eventarc)
3330
+ "onCustomEventPublished",
3331
+ // Tasks (firebase-functions/v2/tasks)
3332
+ "onTaskDispatched",
3333
+ // Test Lab (firebase-functions/v2/testLab)
3334
+ "onTestMatrixCompleted"
3335
+ ]);
3038
3336
  function indexProject(cwd) {
3039
3337
  const allFiles = getAllCodeFiles(cwd);
3338
+ debugLog(`Indexando ${allFiles.length} arquivos em ${cwd}`);
3339
+ const functionFiles = allFiles.filter((f) => f.includes("functions/src/"));
3340
+ if (functionFiles.length > 0) {
3341
+ debugLog(`Encontrados ${functionFiles.length} arquivos em functions/src/:`, functionFiles);
3342
+ }
3040
3343
  const project = createProject2(cwd);
3041
3344
  for (const file of allFiles) {
3042
3345
  try {
@@ -3136,6 +3439,55 @@ function indexProject(cwd) {
3136
3439
  if (isExported) {
3137
3440
  exports.push(name);
3138
3441
  }
3442
+ } else if (initKind === SyntaxKind2.CallExpression) {
3443
+ const triggerName = extractFirebaseTriggerName(init);
3444
+ if (DEBUG && filePath.includes("functions/src/")) {
3445
+ const initText = init.getText().slice(0, 50);
3446
+ debugLog(`[CF] Analisando: ${name} = ${initText}...`);
3447
+ if (triggerName) {
3448
+ debugLog(`[CF] \u2713 Trigger detectado: ${triggerName}`);
3449
+ }
3450
+ }
3451
+ if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
3452
+ const triggerInfo = extractTriggerInfo(init, triggerName);
3453
+ const symbol = {
3454
+ name,
3455
+ file: filePath,
3456
+ line: varDecl.getStartLineNumber(),
3457
+ kind: "trigger",
3458
+ signature: `${isExported ? "export " : ""}const ${name} = ${triggerName}(...)`,
3459
+ isExported,
3460
+ triggerInfo
3461
+ };
3462
+ symbols.push(symbol);
3463
+ if (!symbolsByName[name]) {
3464
+ symbolsByName[name] = [];
3465
+ }
3466
+ symbolsByName[name].push(symbol);
3467
+ if (isExported) {
3468
+ exports.push(name);
3469
+ }
3470
+ } else {
3471
+ const declKind = varStatement.getDeclarationKind();
3472
+ if (declKind.toString() === "const") {
3473
+ const symbol = {
3474
+ name,
3475
+ file: filePath,
3476
+ line: varDecl.getStartLineNumber(),
3477
+ kind: "const",
3478
+ signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
3479
+ isExported
3480
+ };
3481
+ symbols.push(symbol);
3482
+ if (!symbolsByName[name]) {
3483
+ symbolsByName[name] = [];
3484
+ }
3485
+ symbolsByName[name].push(symbol);
3486
+ if (isExported) {
3487
+ exports.push(name);
3488
+ }
3489
+ }
3490
+ }
3139
3491
  } else {
3140
3492
  const declKind = varStatement.getDeclarationKind();
3141
3493
  if (declKind.toString() !== "const") continue;
@@ -3290,6 +3642,44 @@ function inferSymbolKind(name, context2) {
3290
3642
  }
3291
3643
  return context2 === "function" ? "function" : "const";
3292
3644
  }
3645
+ function extractFirebaseTriggerName(init) {
3646
+ const text = init.getText();
3647
+ for (const trigger of FIREBASE_V2_TRIGGERS) {
3648
+ const pattern = new RegExp(`(?:^|\\.|\\s)${trigger}(?:<[^>]*>)?\\s*\\(`);
3649
+ if (pattern.test(text)) {
3650
+ return trigger;
3651
+ }
3652
+ }
3653
+ return null;
3654
+ }
3655
+ function extractTriggerInfo(init, triggerName) {
3656
+ const text = init.getText();
3657
+ const info = { triggerType: triggerName };
3658
+ if (triggerName.startsWith("onDocument") || triggerName.startsWith("onValue")) {
3659
+ const pathMatch = text.match(/\(\s*["'`]([^"'`]+)["'`]/);
3660
+ if (pathMatch) {
3661
+ info.triggerPath = pathMatch[1];
3662
+ }
3663
+ }
3664
+ if (triggerName === "onSchedule") {
3665
+ const scheduleMatch = text.match(/onSchedule\s*\(\s*["'`]([^"'`]+)["'`]/);
3666
+ if (scheduleMatch) {
3667
+ info.triggerSchedule = scheduleMatch[1];
3668
+ } else {
3669
+ const objectScheduleMatch = text.match(/schedule\s*:\s*["'`]([^"'`]+)["'`]/);
3670
+ if (objectScheduleMatch) {
3671
+ info.triggerSchedule = objectScheduleMatch[1];
3672
+ }
3673
+ }
3674
+ }
3675
+ if (triggerName.startsWith("onObject") || triggerName === "onMetadataUpdated") {
3676
+ const bucketMatch = text.match(/bucket\s*:\s*["'`]([^"'`]+)["'`]/);
3677
+ if (bucketMatch) {
3678
+ info.triggerPath = bucketMatch[1];
3679
+ }
3680
+ }
3681
+ return info;
3682
+ }
3293
3683
  function simplifyType2(typeText) {
3294
3684
  if (!typeText) return "unknown";
3295
3685
  let simplified = typeText.replace(/import\([^)]+\)\./g, "");
@@ -3354,7 +3744,7 @@ async function context(target, options = {}) {
3354
3744
  const absolutePath = resolve2(cwd, targetPath);
3355
3745
  const sourceFile = addSourceFile(project, absolutePath);
3356
3746
  const imports = extractImports(sourceFile);
3357
- const functions = extractFunctions(sourceFile);
3747
+ const functions2 = extractFunctions(sourceFile);
3358
3748
  const types = extractTypes(sourceFile);
3359
3749
  const exports = extractExports(sourceFile);
3360
3750
  const result = {
@@ -3364,7 +3754,7 @@ async function context(target, options = {}) {
3364
3754
  category: detectCategory(targetPath),
3365
3755
  imports,
3366
3756
  exports,
3367
- functions,
3757
+ functions: functions2,
3368
3758
  types
3369
3759
  };
3370
3760
  if (format === "json") {
@@ -3497,10 +3887,11 @@ async function areaContext(areaName, options = {}) {
3497
3887
  }
3498
3888
  const types = [];
3499
3889
  const hooks = [];
3500
- const functions = [];
3890
+ const functions2 = [];
3501
3891
  const components = [];
3502
3892
  const services = [];
3503
3893
  const stores = [];
3894
+ const triggers = [];
3504
3895
  for (const filePath of areaFiles) {
3505
3896
  const fileData = index.files[filePath];
3506
3897
  if (!fileData) continue;
@@ -3545,7 +3936,7 @@ async function areaContext(areaName, options = {}) {
3545
3936
  returns: symbol.returnType || "unknown"
3546
3937
  });
3547
3938
  } else {
3548
- functions.push({
3939
+ functions2.push({
3549
3940
  name: symbol.name,
3550
3941
  file: filePath,
3551
3942
  line: symbol.line,
@@ -3564,6 +3955,16 @@ async function areaContext(areaName, options = {}) {
3564
3955
  });
3565
3956
  }
3566
3957
  break;
3958
+ case "trigger":
3959
+ triggers.push({
3960
+ name: symbol.name,
3961
+ file: filePath,
3962
+ line: symbol.line,
3963
+ triggerType: symbol.triggerInfo?.triggerType || "unknown",
3964
+ triggerPath: symbol.triggerInfo?.triggerPath,
3965
+ triggerSchedule: symbol.triggerInfo?.triggerSchedule
3966
+ });
3967
+ break;
3567
3968
  }
3568
3969
  }
3569
3970
  }
@@ -3579,10 +3980,11 @@ async function areaContext(areaName, options = {}) {
3579
3980
  },
3580
3981
  types,
3581
3982
  hooks,
3582
- functions,
3983
+ functions: functions2,
3583
3984
  components,
3584
3985
  services,
3585
- stores
3986
+ stores,
3987
+ triggers
3586
3988
  };
3587
3989
  if (format === "json") {
3588
3990
  return JSON.stringify(result, null, 2);
@@ -3741,6 +4143,42 @@ var IGNORED_DIRS3 = /* @__PURE__ */ new Set([
3741
4143
  ".vercel",
3742
4144
  ".analyze"
3743
4145
  ]);
4146
+ function resolveAreaId(target, config, allFiles) {
4147
+ const targetLower = target.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4148
+ if (config.areas[target]) {
4149
+ return target;
4150
+ }
4151
+ for (const [id, areaConfig] of Object.entries(config.areas)) {
4152
+ const name = areaConfig.name?.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4153
+ if (name === targetLower) {
4154
+ return id;
4155
+ }
4156
+ }
4157
+ for (const [id, name] of Object.entries(AREA_NAMES)) {
4158
+ const nameNormalized = name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4159
+ if (nameNormalized === targetLower) {
4160
+ return id;
4161
+ }
4162
+ }
4163
+ for (const id of Object.keys(config.areas)) {
4164
+ if (id.toLowerCase().includes(targetLower)) {
4165
+ return id;
4166
+ }
4167
+ }
4168
+ const detectedAreas = /* @__PURE__ */ new Set();
4169
+ for (const filePath of allFiles) {
4170
+ const areas2 = detectFileAreas(filePath, config);
4171
+ for (const areaId of areas2) {
4172
+ if (areaId.toLowerCase().includes(targetLower)) {
4173
+ detectedAreas.add(areaId);
4174
+ }
4175
+ }
4176
+ }
4177
+ if (detectedAreas.size > 0) {
4178
+ return [...detectedAreas][0];
4179
+ }
4180
+ return target;
4181
+ }
3744
4182
  async function area(target, options = {}) {
3745
4183
  const cwd = options.cwd || process.cwd();
3746
4184
  const format = options.format || "text";
@@ -3752,9 +4190,10 @@ async function area(target, options = {}) {
3752
4190
  try {
3753
4191
  const config = readConfig(cwd);
3754
4192
  const allFiles = getAllCodeFiles4(cwd);
4193
+ const resolvedTarget = resolveAreaId(target, config, allFiles);
3755
4194
  const filteredFiles = allFiles.filter((filePath) => !isFileIgnored(filePath, config));
3756
4195
  const areaFiles = [];
3757
- const targetLower = target.toLowerCase();
4196
+ const targetLower = resolvedTarget.toLowerCase();
3758
4197
  for (const filePath of filteredFiles) {
3759
4198
  const fileAreas = detectFileAreas(filePath, config);
3760
4199
  const belongsToArea = fileAreas.some(
@@ -3792,15 +4231,19 @@ async function area(target, options = {}) {
3792
4231
  for (const cat of Object.keys(byCategory)) {
3793
4232
  byCategory[cat].sort((a, b) => a.path.localeCompare(b.path));
3794
4233
  }
3795
- const realAreaId = findRealAreaId(target, filteredFiles, config);
4234
+ const realAreaId = resolvedTarget !== target ? resolvedTarget : findRealAreaId(target, filteredFiles, config);
4235
+ const finalAreaId = realAreaId || resolvedTarget;
4236
+ const nameConversionMsg = resolvedTarget !== target ? `
4237
+ \u{1F4A1} Buscando \xE1rea "${getAreaName(finalAreaId, config)}" (ID: ${finalAreaId})
4238
+ ` : "";
3796
4239
  const detectedArea = {
3797
- id: realAreaId || target,
3798
- name: getAreaName(realAreaId || target, config),
3799
- description: getAreaDescription(realAreaId || target, config),
4240
+ id: finalAreaId,
4241
+ name: getAreaName(finalAreaId, config),
4242
+ description: getAreaDescription(finalAreaId, config),
3800
4243
  files: areaFiles,
3801
4244
  fileCount: areaFiles.length,
3802
4245
  categories,
3803
- isAutoDetected: !config.areas[realAreaId || target]
4246
+ isAutoDetected: !config.areas[finalAreaId]
3804
4247
  };
3805
4248
  const result = {
3806
4249
  version: "1.0.0",
@@ -3811,7 +4254,8 @@ async function area(target, options = {}) {
3811
4254
  if (format === "json") {
3812
4255
  return JSON.stringify(result, null, 2);
3813
4256
  }
3814
- return formatAreaDetailText(result, { full, filterType });
4257
+ const output = formatAreaDetailText(result, { full, filterType });
4258
+ return nameConversionMsg + output;
3815
4259
  } catch (error) {
3816
4260
  const message = error instanceof Error ? error.message : String(error);
3817
4261
  throw new Error(`Erro ao executar area: ${message}`);
@@ -3926,9 +4370,11 @@ Ou edite manualmente o arquivo existente.
3926
4370
  patterns
3927
4371
  };
3928
4372
  }
4373
+ const suggestedIgnore = detectSuggestedIgnorePatterns(allFiles);
3929
4374
  const newConfig = {
3930
4375
  $schema: "./areas.schema.json",
3931
4376
  version: "1.0.0",
4377
+ ignore: suggestedIgnore,
3932
4378
  areas: generatedAreas,
3933
4379
  descriptions: {},
3934
4380
  settings: {
@@ -3943,7 +4389,12 @@ Ou edite manualmente o arquivo existente.
3943
4389
  \u2705 Arquivo criado: .analyze/areas.config.json
3944
4390
 
3945
4391
  \u{1F4E6} \xC1reas detectadas: ${sortedAreas.length}
3946
-
4392
+ `;
4393
+ if (suggestedIgnore.length > 0) {
4394
+ out += `\u{1F6AB} Padr\xF5es ignorados: ${suggestedIgnore.length}
4395
+ `;
4396
+ }
4397
+ out += `
3947
4398
  `;
3948
4399
  for (const [areaId, files] of sortedAreas.slice(0, 15)) {
3949
4400
  const name = getAreaName(areaId, newConfig);
@@ -3963,6 +4414,15 @@ Ou edite manualmente o arquivo existente.
3963
4414
  Use 'ai-tool areas' para ver detalhes
3964
4415
  `;
3965
4416
  }
4417
+ if (suggestedIgnore.length > 0) {
4418
+ out += `
4419
+ \u{1F4CB} Padr\xF5es adicionados ao ignore:
4420
+ `;
4421
+ for (const pattern of suggestedIgnore) {
4422
+ out += ` \u2022 ${pattern}
4423
+ `;
4424
+ }
4425
+ }
3966
4426
  out += `
3967
4427
  \u{1F4A1} Edite o arquivo para:
3968
4428
  - Renomear \xE1reas (campo "name")
@@ -4004,6 +4464,28 @@ function inferPatternsFromFiles(files) {
4004
4464
  }
4005
4465
  return [...patterns].sort();
4006
4466
  }
4467
+ function detectSuggestedIgnorePatterns(files) {
4468
+ const patterns = [];
4469
+ if (files.some((f) => f.includes("functions/lib/"))) {
4470
+ patterns.push("functions/lib/**");
4471
+ }
4472
+ const testCount = files.filter((f) => /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f)).length;
4473
+ if (testCount > 3) {
4474
+ patterns.push("**/*.test.{ts,tsx,js,jsx}");
4475
+ patterns.push("**/*.spec.{ts,tsx,js,jsx}");
4476
+ }
4477
+ const dtsCount = files.filter((f) => f.endsWith(".d.ts")).length;
4478
+ if (dtsCount > 2) {
4479
+ patterns.push("**/*.d.ts");
4480
+ }
4481
+ const configCount = files.filter(
4482
+ (f) => /\.(config|conf)\.(ts|js|mjs|cjs)$/.test(f)
4483
+ ).length;
4484
+ if (configCount > 2) {
4485
+ patterns.push("**/*.config.{ts,js,mjs,cjs}");
4486
+ }
4487
+ return patterns;
4488
+ }
4007
4489
  function getAllCodeFiles5(dir, files = [], baseDir = dir) {
4008
4490
  try {
4009
4491
  const entries = readdirSync6(dir);
@@ -4031,6 +4513,442 @@ function getAllCodeFiles5(dir, files = [], baseDir = dir) {
4031
4513
  return files;
4032
4514
  }
4033
4515
 
4516
+ // src/commands/functions.ts
4517
+ async function functions(options = {}) {
4518
+ const cwd = options.cwd || process.cwd();
4519
+ const format = options.format || "text";
4520
+ const useCache = options.cache !== false;
4521
+ const filterTrigger = options.trigger;
4522
+ if (!isFirebaseProject(cwd)) {
4523
+ const errorMsg = "Este n\xE3o \xE9 um projeto Firebase.\nN\xE3o foi encontrado .firebaserc ou firebase.json";
4524
+ return format === "json" ? JSON.stringify({ error: errorMsg }) : `\u274C ${errorMsg}`;
4525
+ }
4526
+ if (!hasFirebaseFunctions(cwd)) {
4527
+ const errorMsg = "Projeto Firebase sem Cloud Functions.\nN\xE3o foi encontrado functions/src/index.ts";
4528
+ return format === "json" ? JSON.stringify({ error: errorMsg }) : `\u274C ${errorMsg}`;
4529
+ }
4530
+ try {
4531
+ let index;
4532
+ if (useCache && isCacheValid(cwd)) {
4533
+ const cached = getCachedSymbolsIndex(cwd);
4534
+ if (cached && cached.symbolsByName) {
4535
+ index = cached;
4536
+ } else {
4537
+ index = indexProject(cwd);
4538
+ cacheSymbolsIndex(cwd, index);
4539
+ updateCacheMeta(cwd);
4540
+ }
4541
+ } else {
4542
+ index = indexProject(cwd);
4543
+ if (useCache) {
4544
+ cacheSymbolsIndex(cwd, index);
4545
+ updateCacheMeta(cwd);
4546
+ }
4547
+ }
4548
+ const funcs = [];
4549
+ for (const fileData of Object.values(index.files)) {
4550
+ if (!fileData.path.includes("functions/src/")) continue;
4551
+ for (const symbol of fileData.symbols) {
4552
+ if (symbol.kind === "trigger") {
4553
+ funcs.push({
4554
+ name: symbol.name,
4555
+ file: symbol.file,
4556
+ line: symbol.line,
4557
+ triggerType: symbol.triggerInfo?.triggerType || "unknown",
4558
+ triggerPath: symbol.triggerInfo?.triggerPath,
4559
+ triggerSchedule: symbol.triggerInfo?.triggerSchedule,
4560
+ isExported: symbol.isExported
4561
+ });
4562
+ }
4563
+ }
4564
+ }
4565
+ const filtered = filterTrigger ? funcs.filter(
4566
+ (f) => f.triggerType.toLowerCase().includes(filterTrigger.toLowerCase())
4567
+ ) : funcs;
4568
+ const byTrigger = {};
4569
+ const triggerCounts = {};
4570
+ for (const func of filtered) {
4571
+ if (!byTrigger[func.triggerType]) {
4572
+ byTrigger[func.triggerType] = [];
4573
+ triggerCounts[func.triggerType] = 0;
4574
+ }
4575
+ byTrigger[func.triggerType].push(func);
4576
+ triggerCounts[func.triggerType]++;
4577
+ }
4578
+ const result = {
4579
+ version: "1.0.0",
4580
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4581
+ cwd,
4582
+ isFirebaseProject: true,
4583
+ hasFunctions: true,
4584
+ functions: filtered,
4585
+ byTrigger,
4586
+ summary: {
4587
+ total: filtered.length,
4588
+ exported: filtered.filter((f) => f.isExported).length,
4589
+ byTrigger: triggerCounts
4590
+ }
4591
+ };
4592
+ if (format === "json") {
4593
+ return JSON.stringify(result, null, 2);
4594
+ }
4595
+ return formatFunctionsText(result);
4596
+ } catch (error) {
4597
+ const message = error instanceof Error ? error.message : String(error);
4598
+ throw new Error(`Erro ao executar functions: ${message}`);
4599
+ }
4600
+ }
4601
+ function formatFunctionsText(result) {
4602
+ let out = "";
4603
+ out += `
4604
+ `;
4605
+ out += `\u2554\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\u2557
4606
+ `;
4607
+ out += `\u2551 \u26A1 CLOUD FUNCTIONS \u2551
4608
+ `;
4609
+ 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
4610
+
4611
+ `;
4612
+ out += `\u{1F4CA} RESUMO
4613
+ `;
4614
+ out += ` Total: ${result.summary.total} functions
4615
+ `;
4616
+ out += ` Exportadas: ${result.summary.exported}
4617
+ `;
4618
+ if (result.summary.total > 0) {
4619
+ out += `
4620
+ \u{1F4A1} Filtros dispon\xEDveis:
4621
+ `;
4622
+ out += ` ai-tool functions --trigger=onCall
4623
+ `;
4624
+ out += ` ai-tool functions --trigger=onDocumentCreated
4625
+ `;
4626
+ }
4627
+ out += `
4628
+ `;
4629
+ if (result.summary.total === 0) {
4630
+ out += ` \u26A0\uFE0F NENHUMA CLOUD FUNCTION DETECTADA
4631
+
4632
+ `;
4633
+ out += ` Poss\xEDveis causas:
4634
+ `;
4635
+ out += ` 1. O projeto n\xE3o \xE9 Firebase (n\xE3o encontrou .firebaserc ou firebase.json)
4636
+ `;
4637
+ out += ` 2. N\xE3o h\xE1 arquivo functions/src/index.ts
4638
+ `;
4639
+ out += ` 3. Os triggers n\xE3o usam padr\xF5es v2 (onCall, onDocumentCreated, etc)
4640
+ `;
4641
+ out += ` 4. O cache est\xE1 desatualizado \u2192 tente: ai-tool functions --no-cache
4642
+ `;
4643
+ out += ` 5. Para debug: DEBUG_ANALYZE=true ai-tool functions
4644
+
4645
+ `;
4646
+ out += ` Padr\xF5es suportados:
4647
+ `;
4648
+ out += ` export const minhaFunc = onCall((request) => { ... })
4649
+ `;
4650
+ out += ` export const minhaFunc = onDocumentCreated("path", (event) => { ... })
4651
+
4652
+ `;
4653
+ out += ` Documenta\xE7\xE3o: https://firebase.google.com/docs/functions
4654
+ `;
4655
+ return out;
4656
+ }
4657
+ out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
4658
+
4659
+ `;
4660
+ const triggerOrder = [
4661
+ // HTTPS
4662
+ "onCall",
4663
+ "onRequest",
4664
+ // Firestore
4665
+ "onDocumentCreated",
4666
+ "onDocumentCreatedWithAuthContext",
4667
+ "onDocumentUpdated",
4668
+ "onDocumentUpdatedWithAuthContext",
4669
+ "onDocumentDeleted",
4670
+ "onDocumentDeletedWithAuthContext",
4671
+ "onDocumentWritten",
4672
+ "onDocumentWrittenWithAuthContext",
4673
+ // Realtime Database
4674
+ "onValueCreated",
4675
+ "onValueUpdated",
4676
+ "onValueDeleted",
4677
+ "onValueWritten",
4678
+ // Scheduler
4679
+ "onSchedule",
4680
+ // Storage
4681
+ "onObjectFinalized",
4682
+ "onObjectArchived",
4683
+ "onObjectDeleted",
4684
+ "onMetadataUpdated",
4685
+ // Pub/Sub
4686
+ "onMessagePublished",
4687
+ // Identity
4688
+ "beforeUserCreated",
4689
+ "beforeUserSignedIn"
4690
+ ];
4691
+ const sortedTriggers = Object.keys(result.byTrigger).sort((a, b) => {
4692
+ const aIdx = triggerOrder.indexOf(a);
4693
+ const bIdx = triggerOrder.indexOf(b);
4694
+ if (aIdx === -1 && bIdx === -1) return a.localeCompare(b);
4695
+ if (aIdx === -1) return 1;
4696
+ if (bIdx === -1) return -1;
4697
+ return aIdx - bIdx;
4698
+ });
4699
+ for (const trigger of sortedTriggers) {
4700
+ const funcs = result.byTrigger[trigger];
4701
+ const icon = getTriggerIcon(trigger);
4702
+ out += `${icon} ${trigger} (${funcs.length})
4703
+
4704
+ `;
4705
+ for (const func of funcs) {
4706
+ const exportTag = func.isExported ? "" : " [n\xE3o exportada]";
4707
+ out += ` ${func.name}${exportTag}
4708
+ `;
4709
+ out += ` \u{1F4C1} ${func.file}:${func.line}
4710
+ `;
4711
+ if (func.triggerPath) {
4712
+ out += ` \u{1F4CD} path: ${func.triggerPath}
4713
+ `;
4714
+ }
4715
+ if (func.triggerSchedule) {
4716
+ out += ` \u23F0 schedule: ${func.triggerSchedule}
4717
+ `;
4718
+ }
4719
+ }
4720
+ out += `
4721
+ `;
4722
+ }
4723
+ return out;
4724
+ }
4725
+ function getTriggerIcon(trigger) {
4726
+ if (trigger.includes("Call") || trigger.includes("Request")) return "\u{1F310}";
4727
+ if (trigger.includes("Document") || trigger.includes("Value")) return "\u{1F525}";
4728
+ if (trigger.includes("Schedule")) return "\u23F0";
4729
+ if (trigger.includes("Object") || trigger.includes("Metadata")) return "\u{1F4E6}";
4730
+ if (trigger.includes("Message") || trigger.includes("Pub")) return "\u{1F4E8}";
4731
+ if (trigger.includes("User") || trigger.includes("before")) return "\u{1F464}";
4732
+ if (trigger.includes("Alert") || trigger.includes("Issue")) return "\u{1F6A8}";
4733
+ if (trigger.includes("Config")) return "\u2699\uFE0F";
4734
+ if (trigger.includes("Task")) return "\u{1F4CB}";
4735
+ if (trigger.includes("Test")) return "\u{1F9EA}";
4736
+ return "\u26A1";
4737
+ }
4738
+
4739
+ // src/commands/find.ts
4740
+ async function find(query, options = {}) {
4741
+ const cwd = options.cwd || process.cwd();
4742
+ const format = options.format || "text";
4743
+ const filterType = options.type || "all";
4744
+ const filterArea = options.area;
4745
+ const defOnly = options.def ?? false;
4746
+ const refsOnly = options.refs ?? false;
4747
+ const useCache = options.cache !== false;
4748
+ const listAllMode = !query && defOnly && filterType && filterType !== "all";
4749
+ if (!query && !listAllMode) {
4750
+ 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");
4751
+ }
4752
+ try {
4753
+ let index;
4754
+ let fromCache = false;
4755
+ if (useCache && isCacheValid(cwd)) {
4756
+ const cached = getCachedSymbolsIndex(cwd);
4757
+ if (cached && cached.symbolsByName) {
4758
+ index = cached;
4759
+ fromCache = true;
4760
+ } else {
4761
+ index = indexProject(cwd);
4762
+ cacheSymbolsIndex(cwd, index);
4763
+ updateCacheMeta(cwd);
4764
+ }
4765
+ } else {
4766
+ index = indexProject(cwd);
4767
+ if (useCache) {
4768
+ cacheSymbolsIndex(cwd, index);
4769
+ updateCacheMeta(cwd);
4770
+ }
4771
+ }
4772
+ let allowedFiles = null;
4773
+ if (filterArea) {
4774
+ const config = readConfig(cwd);
4775
+ const areaLower = filterArea.toLowerCase();
4776
+ allowedFiles = /* @__PURE__ */ new Set();
4777
+ for (const filePath of Object.keys(index.files)) {
4778
+ if (isFileIgnored(filePath, config)) continue;
4779
+ const fileAreas = detectFileAreas(filePath, config);
4780
+ const belongsToArea = fileAreas.some(
4781
+ (a) => a.toLowerCase() === areaLower || a.toLowerCase().includes(areaLower)
4782
+ );
4783
+ if (belongsToArea) {
4784
+ allowedFiles.add(filePath);
4785
+ }
4786
+ }
4787
+ if (allowedFiles.size === 0) {
4788
+ return format === "json" ? JSON.stringify({ error: `Nenhum arquivo encontrado na \xE1rea "${filterArea}"` }) : `\u274C Nenhum arquivo encontrado na \xE1rea "${filterArea}"`;
4789
+ }
4790
+ }
4791
+ const matches = searchInIndex(index, query, filterType, allowedFiles);
4792
+ let definition = null;
4793
+ let references = [];
4794
+ for (const match of matches) {
4795
+ if (match.matchType === "definition") {
4796
+ if (!definition) {
4797
+ definition = match;
4798
+ }
4799
+ } else {
4800
+ references.push(match);
4801
+ }
4802
+ }
4803
+ if (defOnly) {
4804
+ references = [];
4805
+ }
4806
+ if (refsOnly) {
4807
+ definition = null;
4808
+ }
4809
+ const uniqueFiles = /* @__PURE__ */ new Set();
4810
+ if (definition) uniqueFiles.add(definition.file);
4811
+ for (const ref of references) {
4812
+ uniqueFiles.add(ref.file);
4813
+ }
4814
+ const result = {
4815
+ version: "1.0.0",
4816
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4817
+ query,
4818
+ filters: {
4819
+ type: filterType !== "all" ? filterType : void 0,
4820
+ area: filterArea,
4821
+ defOnly,
4822
+ refsOnly
4823
+ },
4824
+ definition,
4825
+ references,
4826
+ summary: {
4827
+ definitions: definition ? 1 : 0,
4828
+ references: references.length,
4829
+ files: uniqueFiles.size
4830
+ },
4831
+ fromCache
4832
+ };
4833
+ if (format === "json") {
4834
+ return JSON.stringify(result, null, 2);
4835
+ }
4836
+ return formatFindText(result);
4837
+ } catch (error) {
4838
+ const message = error instanceof Error ? error.message : String(error);
4839
+ throw new Error(`Erro ao executar find: ${message}`);
4840
+ }
4841
+ }
4842
+ function searchInIndex(index, query, filterType, allowedFiles) {
4843
+ const matches = [];
4844
+ const queryLower = query?.toLowerCase() || "";
4845
+ const processedSymbols = /* @__PURE__ */ new Set();
4846
+ const listAllMode = !query && filterType && filterType !== "all";
4847
+ for (const [name, symbols] of Object.entries(index.symbolsByName)) {
4848
+ const nameLower = name.toLowerCase();
4849
+ const shouldInclude = listAllMode || nameLower === queryLower || nameLower.includes(queryLower);
4850
+ if (shouldInclude) {
4851
+ for (const symbol of symbols) {
4852
+ if (allowedFiles && !allowedFiles.has(symbol.file)) continue;
4853
+ if (!matchesType(symbol.kind, filterType)) continue;
4854
+ const key = `${symbol.file}:${symbol.line}:${symbol.name}`;
4855
+ if (processedSymbols.has(key)) continue;
4856
+ processedSymbols.add(key);
4857
+ const fileData = index.files[symbol.file];
4858
+ matches.push({
4859
+ file: symbol.file,
4860
+ line: symbol.line,
4861
+ column: 0,
4862
+ code: symbol.signature,
4863
+ matchType: "definition",
4864
+ symbolType: mapKindToSymbolType(symbol.kind),
4865
+ category: fileData?.category || "other"
4866
+ });
4867
+ }
4868
+ }
4869
+ }
4870
+ if (!listAllMode) {
4871
+ for (const [filePath, fileData] of Object.entries(index.files)) {
4872
+ if (allowedFiles && !allowedFiles.has(filePath)) continue;
4873
+ for (const imp of fileData.imports) {
4874
+ for (const spec of imp.specifiers) {
4875
+ const specLower = spec.toLowerCase();
4876
+ if (specLower === queryLower || specLower.includes(queryLower)) {
4877
+ const key = `import:${filePath}:${spec}:${imp.source}`;
4878
+ if (processedSymbols.has(key)) continue;
4879
+ processedSymbols.add(key);
4880
+ matches.push({
4881
+ file: filePath,
4882
+ line: 1,
4883
+ // Imports geralmente estão no topo
4884
+ column: 0,
4885
+ code: `import { ${spec} } from '${imp.source}'`,
4886
+ matchType: "import",
4887
+ symbolType: inferSymbolTypeFromName(spec),
4888
+ category: fileData.category
4889
+ });
4890
+ }
4891
+ }
4892
+ }
4893
+ }
4894
+ }
4895
+ matches.sort((a, b) => {
4896
+ const order = { definition: 0, import: 1, usage: 2 };
4897
+ const orderDiff = order[a.matchType] - order[b.matchType];
4898
+ if (orderDiff !== 0) return orderDiff;
4899
+ return a.file.localeCompare(b.file) || a.line - b.line;
4900
+ });
4901
+ return matches;
4902
+ }
4903
+ function matchesType(kind, filter) {
4904
+ if (filter === "all") return true;
4905
+ switch (filter) {
4906
+ case "function":
4907
+ return kind === "function" || kind === "trigger";
4908
+ case "type":
4909
+ return kind === "type" || kind === "interface" || kind === "enum";
4910
+ case "const":
4911
+ return kind === "const";
4912
+ case "component":
4913
+ return kind === "component";
4914
+ case "hook":
4915
+ return kind === "hook";
4916
+ case "trigger":
4917
+ return kind === "trigger";
4918
+ default:
4919
+ return true;
4920
+ }
4921
+ }
4922
+ function mapKindToSymbolType(kind) {
4923
+ switch (kind) {
4924
+ case "function":
4925
+ return "function";
4926
+ case "hook":
4927
+ return "hook";
4928
+ case "component":
4929
+ return "component";
4930
+ case "type":
4931
+ case "interface":
4932
+ case "enum":
4933
+ return "type";
4934
+ case "const":
4935
+ return "const";
4936
+ case "trigger":
4937
+ return "trigger";
4938
+ default:
4939
+ return "function";
4940
+ }
4941
+ }
4942
+ function inferSymbolTypeFromName(name) {
4943
+ if (name.startsWith("use") && name.length > 3 && name[3] === name[3].toUpperCase()) {
4944
+ return "hook";
4945
+ }
4946
+ if (name[0] === name[0].toUpperCase()) {
4947
+ return "type";
4948
+ }
4949
+ return "function";
4950
+ }
4951
+
4034
4952
  // src/index.ts
4035
4953
  import { createRequire } from "module";
4036
4954
  var require2 = createRequire(import.meta.url);
@@ -4042,7 +4960,6 @@ export {
4042
4960
  categoryIcons,
4043
4961
  isEntryPoint,
4044
4962
  isCodeFile,
4045
- formatFindText,
4046
4963
  isFirebaseProject,
4047
4964
  hasFirebaseFunctions,
4048
4965
  isExportedCloudFunction,
@@ -4050,10 +4967,7 @@ export {
4050
4967
  clearFirebaseCache,
4051
4968
  getCacheDir,
4052
4969
  isCacheValid,
4053
- updateCacheMeta,
4054
4970
  invalidateCache,
4055
- cacheSymbolsIndex,
4056
- getCachedSymbolsIndex,
4057
4971
  configExists,
4058
4972
  readConfig,
4059
4973
  writeConfig,
@@ -4065,7 +4979,6 @@ export {
4065
4979
  KEYWORD_PATTERNS,
4066
4980
  AREA_NAMES,
4067
4981
  AREA_DESCRIPTIONS,
4068
- isFileIgnored,
4069
4982
  detectFileAreas,
4070
4983
  getAreaName,
4071
4984
  getAreaDescription,
@@ -4084,11 +4997,12 @@ export {
4084
4997
  formatInvalidCommand,
4085
4998
  impact,
4086
4999
  suggest,
4087
- indexProject,
4088
5000
  context,
4089
5001
  areaContext,
4090
5002
  areas,
4091
5003
  area,
4092
5004
  areasInit,
5005
+ find,
5006
+ functions,
4093
5007
  VERSION
4094
5008
  };