@justmpm/ai-tool 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,8 @@ import skott from "skott";
5
5
  function detectCategory(filePath) {
6
6
  const normalized = filePath.replace(/\\/g, "/").toLowerCase();
7
7
  const fileName = normalized.split("/").pop() || "";
8
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
9
- if (fileName.includes(".test.") || fileName.includes(".spec.") || normalized.includes("/__tests__/") || normalized.includes("/test/")) {
8
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs|vue|svelte|astro)$/, "");
9
+ if (fileName.includes(".test.") || fileName.includes(".spec.") || normalized.includes("/__tests__/") || normalized.includes("/test/") || normalized.includes("/tests/")) {
10
10
  return "test";
11
11
  }
12
12
  if (isConfigFile(fileName)) {
@@ -20,23 +20,55 @@ function detectCategory(filePath) {
20
20
  )) {
21
21
  return "page";
22
22
  }
23
- if (normalized.includes("/api/")) return "route";
24
- if (normalized.includes("/pages/")) return "page";
23
+ if (normalized.includes("/pages/") && !normalized.includes("/pages/api/")) {
24
+ return "page";
25
+ }
26
+ if (normalized.includes("/pages/") || normalized.includes("/views/") || normalized.includes("/screens/")) {
27
+ if (!normalized.includes("/components/")) {
28
+ return "page";
29
+ }
30
+ }
31
+ if (fileNameNoExt.endsWith("page") || fileNameNoExt.endsWith("view") || fileNameNoExt.endsWith("screen")) {
32
+ return "page";
33
+ }
34
+ if (normalized.includes("/routes/") && normalized.includes("/app/")) {
35
+ return "page";
36
+ }
37
+ if (normalized.includes("/layouts/")) {
38
+ return "layout";
39
+ }
40
+ if (fileNameNoExt === "+page" || fileNameNoExt === "+page.server") return "page";
41
+ if (fileNameNoExt === "+layout" || fileNameNoExt === "+layout.server") return "layout";
42
+ if (fileNameNoExt === "+server") return "route";
43
+ if (fileNameNoExt === "+error") return "page";
44
+ if (fileName.endsWith(".astro") && normalized.includes("/pages/")) {
45
+ return "page";
46
+ }
47
+ if (fileName.endsWith(".astro") && normalized.includes("/layouts/")) {
48
+ return "layout";
49
+ }
50
+ if (normalized.includes("/api/") || normalized.includes("/server/") || fileNameNoExt.endsWith(".server") || fileNameNoExt === "+server") {
51
+ return "route";
52
+ }
25
53
  if (fileNameNoExt.startsWith("use") && fileNameNoExt.length > 3) return "hook";
26
54
  if (normalized.includes("/hooks/")) return "hook";
27
- if (normalized.includes("/types/") || fileName.endsWith(".d.ts") || fileNameNoExt === "types" || fileNameNoExt === "interfaces") {
55
+ if (normalized.includes("/composables/")) return "hook";
56
+ if (normalized.includes("/stores/") && fileName.endsWith(".ts")) return "hook";
57
+ if (normalized.includes("/types/") || normalized.includes("/interfaces/") || fileName.endsWith(".d.ts") || fileNameNoExt === "types" || fileNameNoExt === "interfaces") {
28
58
  return "type";
29
59
  }
30
- if (normalized.includes("/services/") || fileNameNoExt.endsWith("service")) {
60
+ if (normalized.includes("/services/") || normalized.includes("/api-client/") || fileNameNoExt.endsWith("service") || fileNameNoExt.endsWith("api")) {
31
61
  return "service";
32
62
  }
33
- if (normalized.includes("/store/") || normalized.includes("/stores/") || normalized.includes("/context/") || normalized.includes("/contexts/") || normalized.includes("/providers/")) {
63
+ if (normalized.includes("/store/") || normalized.includes("/stores/") || normalized.includes("/context/") || normalized.includes("/contexts/") || normalized.includes("/providers/") || normalized.includes("/state/") || // Zustand, Redux, Pinia, etc
64
+ fileNameNoExt.endsWith("store") || fileNameNoExt.endsWith("slice") || fileNameNoExt.endsWith("reducer")) {
34
65
  return "store";
35
66
  }
36
- if (normalized.includes("/utils/") || normalized.includes("/lib/") || normalized.includes("/helpers/") || normalized.includes("/common/")) {
67
+ if (normalized.includes("/utils/") || normalized.includes("/lib/") || normalized.includes("/helpers/") || normalized.includes("/common/") || normalized.includes("/shared/") || fileNameNoExt.endsWith("utils") || fileNameNoExt.endsWith("helpers")) {
37
68
  return "util";
38
69
  }
39
- if (normalized.includes("/components/") || normalized.includes("/ui/") || normalized.includes("/features/")) {
70
+ if (normalized.includes("/components/") || normalized.includes("/ui/") || normalized.includes("/features/") || normalized.includes("/modules/") || // Vue/Svelte single file components
71
+ fileName.endsWith(".vue") || fileName.endsWith(".svelte")) {
40
72
  return "component";
41
73
  }
42
74
  return "other";
@@ -252,6 +284,18 @@ function formatDeadText(result) {
252
284
  `;
253
285
  }
254
286
  out += `
287
+ `;
288
+ }
289
+ if (result.filters?.firebase.detected) {
290
+ out += `\u{1F525} FIREBASE CLOUD FUNCTIONS
291
+ `;
292
+ out += ` Projeto Firebase detectado.
293
+ `;
294
+ if (result.filters.firebase.excludedCount > 0) {
295
+ out += ` ${result.filters.firebase.excludedCount} arquivo(s) filtrado(s) (exportados em functions/src/index.ts)
296
+ `;
297
+ }
298
+ out += `
255
299
  `;
256
300
  }
257
301
  out += `\u{1F4A1} SUGEST\xC3O
@@ -585,10 +629,271 @@ function formatContextText(result) {
585
629
  `;
586
630
  return out;
587
631
  }
632
+ function formatAreasText(result) {
633
+ let out = "";
634
+ out += `
635
+ `;
636
+ 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
637
+ `;
638
+ out += `\u2551 \u{1F4E6} PROJECT AREAS \u2551
639
+ `;
640
+ 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
641
+
642
+ `;
643
+ out += `\u{1F4CA} RESUMO
644
+ `;
645
+ out += ` \xC1reas: ${result.summary.totalAreas}
646
+ `;
647
+ out += ` Arquivos: ${result.summary.totalFiles}
648
+ `;
649
+ if (result.summary.unmappedCount > 0) {
650
+ out += ` \u26A0\uFE0F Sem \xE1rea: ${result.summary.unmappedCount}
651
+ `;
652
+ }
653
+ out += `
654
+ `;
655
+ 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
656
+
657
+ `;
658
+ out += `\u{1F4E6} \xC1REAS DETECTADAS
659
+
660
+ `;
661
+ for (const area2 of result.areas) {
662
+ const autoTag = area2.isAutoDetected ? " (auto)" : "";
663
+ out += ` ${area2.name.padEnd(25)} ${String(area2.fileCount).padStart(4)} arquivos${autoTag}
664
+ `;
665
+ const catSummary = Object.entries(area2.categories).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([cat, count]) => `${categoryIcons[cat]}${count}`).join(" ");
666
+ if (catSummary) {
667
+ out += ` ${catSummary}
668
+ `;
669
+ }
670
+ if (area2.description) {
671
+ out += ` ${area2.description}
672
+ `;
673
+ }
674
+ out += `
675
+ `;
676
+ }
677
+ if (result.unmapped.length > 0) {
678
+ 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
679
+
680
+ `;
681
+ out += `\u26A0\uFE0F ARQUIVOS SEM \xC1REA (${result.unmapped.length})
682
+
683
+ `;
684
+ for (const file of result.unmapped.slice(0, 10)) {
685
+ const icon = categoryIcons[file.category];
686
+ out += ` ${icon} ${file.path}
687
+ `;
688
+ }
689
+ if (result.unmapped.length > 10) {
690
+ out += ` ... e mais ${result.unmapped.length - 10}
691
+ `;
692
+ }
693
+ out += `
694
+ \u{1F4A1} Adicione padr\xF5es em .analyze/areas.config.json
695
+ `;
696
+ out += ` ou execute 'ai-tool areas init' para gerar configura\xE7\xE3o
697
+ `;
698
+ }
699
+ out += `
700
+ \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
701
+
702
+ `;
703
+ out += `\u{1F4A1} Use 'ai-tool area <nome>' para ver detalhes de uma \xE1rea
704
+ `;
705
+ out += ` Exemplo: ai-tool area meus-pets
706
+ `;
707
+ return out;
708
+ }
709
+ function formatAreaDetailText(result, options = {}) {
710
+ const { full = false, filterType } = options;
711
+ const { area: area2, byCategory } = result;
712
+ let out = "";
713
+ out += `
714
+ `;
715
+ 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
716
+ `;
717
+ out += `\u2551 \u{1F4E6} AREA DETAIL \u2551
718
+ `;
719
+ 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
720
+
721
+ `;
722
+ out += `\u{1F4E6} ${area2.name}
723
+ `;
724
+ if (area2.description) {
725
+ out += ` ${area2.description}
726
+ `;
727
+ }
728
+ out += `
729
+ `;
730
+ out += `\u{1F4CA} Resumo: ${area2.fileCount} arquivos
731
+ `;
732
+ const catOrder = [
733
+ "page",
734
+ "layout",
735
+ "route",
736
+ "component",
737
+ "hook",
738
+ "service",
739
+ "store",
740
+ "util",
741
+ "type",
742
+ "config",
743
+ "test",
744
+ "other"
745
+ ];
746
+ const catParts = [];
747
+ for (const cat of catOrder) {
748
+ const count = area2.categories[cat];
749
+ if (count) {
750
+ catParts.push(`${categoryIcons[cat]} ${cat}: ${count}`);
751
+ }
752
+ }
753
+ out += catParts.join(" ");
754
+ out += `
755
+
756
+ `;
757
+ 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
758
+
759
+ `;
760
+ if (filterType) {
761
+ const files = byCategory[filterType] || [];
762
+ out += `${categoryIcons[filterType]} ${filterType.toUpperCase()} (${files.length})
763
+
764
+ `;
765
+ for (const file of files) {
766
+ const desc = file.description ? ` ${file.description}` : "";
767
+ out += ` ${file.path}${desc}
768
+ `;
769
+ }
770
+ return out;
771
+ }
772
+ const maxFilesPerCategory = full ? 100 : 8;
773
+ for (const cat of catOrder) {
774
+ const files = byCategory[cat];
775
+ if (!files || files.length === 0) continue;
776
+ out += `${categoryIcons[cat]} ${cat.toUpperCase()} (${files.length})
777
+ `;
778
+ const filesToShow = files.slice(0, maxFilesPerCategory);
779
+ const remaining = files.length - filesToShow.length;
780
+ for (const file of filesToShow) {
781
+ const maxPathLen = 50;
782
+ let displayPath = file.path;
783
+ if (displayPath.length > maxPathLen) {
784
+ displayPath = "..." + displayPath.slice(-maxPathLen + 3);
785
+ }
786
+ const desc = file.description || "";
787
+ out += ` ${displayPath.padEnd(maxPathLen + 2)} ${desc}
788
+ `;
789
+ }
790
+ if (remaining > 0) {
791
+ out += ` ... e mais ${remaining}
792
+ `;
793
+ }
794
+ out += `
795
+ `;
796
+ }
797
+ if (!full && area2.fileCount > 20) {
798
+ out += `\u{1F4A1} Use --full para ver todos os arquivos
799
+ `;
800
+ }
801
+ return out;
802
+ }
803
+
804
+ // src/cache/index.ts
805
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, statSync, readdirSync } from "fs";
806
+ import { join as join2, extname } from "path";
807
+
808
+ // src/utils/firebase.ts
809
+ import { existsSync, readFileSync } from "fs";
810
+ import { join, relative, normalize } from "path";
811
+ var indexContentCache = null;
812
+ function isFirebaseProject(cwd) {
813
+ const firebaserc = join(cwd, ".firebaserc");
814
+ const firebaseJson = join(cwd, "firebase.json");
815
+ return existsSync(firebaserc) || existsSync(firebaseJson);
816
+ }
817
+ function hasFirebaseFunctions(cwd) {
818
+ if (!isFirebaseProject(cwd)) {
819
+ return false;
820
+ }
821
+ const functionsIndexPath = join(cwd, "functions", "src", "index.ts");
822
+ return existsSync(functionsIndexPath);
823
+ }
824
+ function getFunctionsExports(cwd) {
825
+ const functionsIndexPath = join(cwd, "functions", "src", "index.ts");
826
+ if (indexContentCache && indexContentCache.cwd === cwd) {
827
+ return indexContentCache.exports;
828
+ }
829
+ if (!existsSync(functionsIndexPath)) {
830
+ return /* @__PURE__ */ new Set();
831
+ }
832
+ try {
833
+ const content = readFileSync(functionsIndexPath, "utf-8");
834
+ const exports = /* @__PURE__ */ new Set();
835
+ const namedExportRegex = /export\s*\{[^}]+\}\s*from\s*["']\.\/([^"']+)["']/g;
836
+ let match;
837
+ while ((match = namedExportRegex.exec(content)) !== null) {
838
+ const modulePath = match[1].replace(/\.js$/, "").replace(/\.ts$/, "");
839
+ exports.add(modulePath);
840
+ }
841
+ const starExportRegex = /export\s*\*\s*from\s*["']\.\/([^"']+)["']/g;
842
+ while ((match = starExportRegex.exec(content)) !== null) {
843
+ const modulePath = match[1].replace(/\.js$/, "").replace(/\.ts$/, "");
844
+ exports.add(modulePath);
845
+ }
846
+ const importRegex = /import\s*(?:\{[^}]+\}|\*\s+as\s+\w+)\s*from\s*["']\.\/([^"']+)["']/g;
847
+ while ((match = importRegex.exec(content)) !== null) {
848
+ const modulePath = match[1].replace(/\.js$/, "").replace(/\.ts$/, "");
849
+ exports.add(modulePath);
850
+ }
851
+ indexContentCache = { cwd, content, exports };
852
+ return exports;
853
+ } catch {
854
+ return /* @__PURE__ */ new Set();
855
+ }
856
+ }
857
+ function isExportedCloudFunction(filePath, cwd) {
858
+ const normalized = normalize(filePath).replace(/\\/g, "/");
859
+ if (!normalized.includes("functions/src/")) {
860
+ return false;
861
+ }
862
+ if (normalized.endsWith("functions/src/index.ts")) {
863
+ return false;
864
+ }
865
+ const functionsDir = join(cwd, "functions", "src");
866
+ const relativePath = relative(functionsDir, join(cwd, filePath)).replace(/\\/g, "/").replace(/\.ts$/, "").replace(/\.js$/, "");
867
+ const exports = getFunctionsExports(cwd);
868
+ if (exports.has(relativePath)) {
869
+ return true;
870
+ }
871
+ const parts = relativePath.split("/");
872
+ if (parts.length > 1 && exports.has(parts[0])) {
873
+ return true;
874
+ }
875
+ return false;
876
+ }
877
+ function filterCloudFunctionsFalsePositives(files, cwd) {
878
+ if (!hasFirebaseFunctions(cwd)) {
879
+ return { filtered: files, excluded: [] };
880
+ }
881
+ const filtered = [];
882
+ const excluded = [];
883
+ for (const file of files) {
884
+ if (isExportedCloudFunction(file, cwd)) {
885
+ excluded.push(file);
886
+ } else {
887
+ filtered.push(file);
888
+ }
889
+ }
890
+ return { filtered, excluded };
891
+ }
892
+ function clearFirebaseCache() {
893
+ indexContentCache = null;
894
+ }
588
895
 
589
896
  // src/cache/index.ts
590
- import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync } from "fs";
591
- import { join, extname } from "path";
592
897
  var CACHE_DIR = ".analyze";
593
898
  var META_FILE = "meta.json";
594
899
  var GRAPH_FILE = "graph.json";
@@ -601,7 +906,7 @@ function calculateFilesHash(cwd) {
601
906
  try {
602
907
  const entries = readdirSync(dir, { withFileTypes: true });
603
908
  for (const entry of entries) {
604
- const fullPath = join(dir, entry.name);
909
+ const fullPath = join2(dir, entry.name);
605
910
  if (entry.isDirectory()) {
606
911
  if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".next" || entry.name === "dist" || entry.name === ".analyze") {
607
912
  continue;
@@ -626,16 +931,16 @@ function calculateFilesHash(cwd) {
626
931
  return `${timestamps.length}-${Math.floor(sum)}`;
627
932
  }
628
933
  function getCacheDir(cwd) {
629
- return join(cwd, CACHE_DIR);
934
+ return join2(cwd, CACHE_DIR);
630
935
  }
631
936
  function isCacheValid(cwd) {
632
937
  const cacheDir = getCacheDir(cwd);
633
- const metaPath = join(cacheDir, META_FILE);
634
- if (!existsSync(metaPath)) {
938
+ const metaPath = join2(cacheDir, META_FILE);
939
+ if (!existsSync2(metaPath)) {
635
940
  return false;
636
941
  }
637
942
  try {
638
- const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
943
+ const meta = JSON.parse(readFileSync2(metaPath, "utf-8"));
639
944
  const currentHash = calculateFilesHash(cwd);
640
945
  return meta.filesHash === currentHash;
641
946
  } catch {
@@ -643,22 +948,22 @@ function isCacheValid(cwd) {
643
948
  }
644
949
  }
645
950
  function readCache(cwd, file) {
646
- const cachePath = join(getCacheDir(cwd), file);
647
- if (!existsSync(cachePath)) {
951
+ const cachePath = join2(getCacheDir(cwd), file);
952
+ if (!existsSync2(cachePath)) {
648
953
  return null;
649
954
  }
650
955
  try {
651
- return JSON.parse(readFileSync(cachePath, "utf-8"));
956
+ return JSON.parse(readFileSync2(cachePath, "utf-8"));
652
957
  } catch {
653
958
  return null;
654
959
  }
655
960
  }
656
961
  function writeCache(cwd, file, data) {
657
962
  const cacheDir = getCacheDir(cwd);
658
- if (!existsSync(cacheDir)) {
963
+ if (!existsSync2(cacheDir)) {
659
964
  mkdirSync(cacheDir, { recursive: true });
660
965
  }
661
- const cachePath = join(cacheDir, file);
966
+ const cachePath = join2(cacheDir, file);
662
967
  writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
663
968
  }
664
969
  function updateCacheMeta(cwd) {
@@ -699,8 +1004,9 @@ function getCachedDeadResult(cwd) {
699
1004
  return readCache(cwd, DEAD_FILE);
700
1005
  }
701
1006
  function invalidateCache(cwd) {
702
- const metaPath = join(getCacheDir(cwd), META_FILE);
703
- if (existsSync(metaPath)) {
1007
+ const metaPath = join2(getCacheDir(cwd), META_FILE);
1008
+ clearFirebaseCache();
1009
+ if (existsSync2(metaPath)) {
704
1010
  try {
705
1011
  writeFileSync(metaPath, "{}", "utf-8");
706
1012
  } catch {
@@ -839,11 +1145,15 @@ async function dead(options = {}) {
839
1145
  knipOutput = {};
840
1146
  }
841
1147
  }
842
- const deadFiles = (knipOutput.files || []).map((file) => ({
1148
+ const rawFiles = knipOutput.files || [];
1149
+ const { filtered: filteredFiles, excluded: excludedFunctions } = filterCloudFunctionsFalsePositives(rawFiles, cwd);
1150
+ const deadFiles = filteredFiles.map((file) => ({
843
1151
  path: file,
844
1152
  category: detectCategory(file),
845
1153
  type: "file"
846
1154
  }));
1155
+ const hasFirebase = hasFirebaseFunctions(cwd);
1156
+ const firebaseInfo = hasFirebase ? { detected: true, excludedCount: excludedFunctions.length } : { detected: false, excludedCount: 0 };
847
1157
  const deadExports = [];
848
1158
  if (knipOutput.issues) {
849
1159
  for (const issue of knipOutput.issues) {
@@ -873,7 +1183,12 @@ async function dead(options = {}) {
873
1183
  },
874
1184
  files: deadFiles,
875
1185
  exports: deadExports,
876
- dependencies: deadDependencies
1186
+ dependencies: deadDependencies,
1187
+ // Metadata sobre filtros aplicados
1188
+ filters: {
1189
+ firebase: firebaseInfo,
1190
+ excludedFiles: excludedFunctions
1191
+ }
877
1192
  };
878
1193
  if (useCache) {
879
1194
  cacheDeadResult(cwd, result);
@@ -1456,8 +1771,8 @@ function levenshteinDistance2(a, b) {
1456
1771
  }
1457
1772
 
1458
1773
  // src/commands/context.ts
1459
- import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1460
- import { join as join2, resolve, basename, extname as extname2 } from "path";
1774
+ import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1775
+ import { join as join3, resolve, basename, extname as extname2 } from "path";
1461
1776
 
1462
1777
  // src/ts/extractor.ts
1463
1778
  import { Project, SyntaxKind } from "ts-morph";
@@ -1705,12 +2020,12 @@ var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".
1705
2020
  function findTargetFile3(target, cwd) {
1706
2021
  const normalizedTarget = target.replace(/\\/g, "/");
1707
2022
  const directPath = resolve(cwd, normalizedTarget);
1708
- if (existsSync2(directPath) && isCodeFile2(directPath)) {
2023
+ if (existsSync3(directPath) && isCodeFile2(directPath)) {
1709
2024
  return normalizedTarget;
1710
2025
  }
1711
2026
  for (const ext of CODE_EXTENSIONS2) {
1712
2027
  const withExt = directPath + ext;
1713
- if (existsSync2(withExt)) {
2028
+ if (existsSync3(withExt)) {
1714
2029
  return normalizedTarget + ext;
1715
2030
  }
1716
2031
  }
@@ -1740,7 +2055,7 @@ function getAllCodeFiles(dir, files = [], baseDir = dir) {
1740
2055
  try {
1741
2056
  const entries = readdirSync2(dir);
1742
2057
  for (const entry of entries) {
1743
- const fullPath = join2(dir, entry);
2058
+ const fullPath = join3(dir, entry);
1744
2059
  if (shouldIgnore(entry)) {
1745
2060
  continue;
1746
2061
  }
@@ -1831,14 +2146,1066 @@ function levenshteinDistance3(a, b) {
1831
2146
  return matrix[b.length][a.length];
1832
2147
  }
1833
2148
 
2149
+ // src/commands/areas.ts
2150
+ import { readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2151
+ import { join as join5, extname as extname3 } from "path";
2152
+
2153
+ // src/areas/config.ts
2154
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
2155
+ import { join as join4 } from "path";
2156
+ var CONFIG_FILE = "areas.config.json";
2157
+ var DEFAULT_CONFIG = {
2158
+ $schema: "./areas.schema.json",
2159
+ version: "1.0.0",
2160
+ areas: {},
2161
+ descriptions: {},
2162
+ settings: {
2163
+ autoDetect: true,
2164
+ inferDescriptions: true,
2165
+ groupByCategory: true
2166
+ }
2167
+ };
2168
+ function getConfigPath(cwd) {
2169
+ return join4(cwd, CACHE_DIR, CONFIG_FILE);
2170
+ }
2171
+ function configExists(cwd) {
2172
+ return existsSync4(getConfigPath(cwd));
2173
+ }
2174
+ function readConfig(cwd) {
2175
+ const configPath = getConfigPath(cwd);
2176
+ if (!existsSync4(configPath)) {
2177
+ return DEFAULT_CONFIG;
2178
+ }
2179
+ try {
2180
+ const content = readFileSync3(configPath, "utf-8");
2181
+ const config = JSON.parse(content);
2182
+ return {
2183
+ ...DEFAULT_CONFIG,
2184
+ ...config,
2185
+ settings: {
2186
+ ...DEFAULT_CONFIG.settings,
2187
+ ...config.settings
2188
+ }
2189
+ };
2190
+ } catch {
2191
+ return DEFAULT_CONFIG;
2192
+ }
2193
+ }
2194
+ function writeConfig(cwd, config) {
2195
+ const cacheDir = join4(cwd, CACHE_DIR);
2196
+ if (!existsSync4(cacheDir)) {
2197
+ mkdirSync2(cacheDir, { recursive: true });
2198
+ }
2199
+ const configPath = getConfigPath(cwd);
2200
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
2201
+ }
2202
+ function setArea(cwd, id, area2) {
2203
+ const config = readConfig(cwd);
2204
+ config.areas[id] = area2;
2205
+ writeConfig(cwd, config);
2206
+ }
2207
+ function removeArea(cwd, id) {
2208
+ const config = readConfig(cwd);
2209
+ delete config.areas[id];
2210
+ writeConfig(cwd, config);
2211
+ }
2212
+ function setFileDescription(cwd, filePath, description) {
2213
+ const config = readConfig(cwd);
2214
+ if (!config.descriptions) {
2215
+ config.descriptions = {};
2216
+ }
2217
+ config.descriptions[filePath] = description;
2218
+ writeConfig(cwd, config);
2219
+ }
2220
+ function getFileDescription(cwd, filePath) {
2221
+ const config = readConfig(cwd);
2222
+ return config.descriptions?.[filePath];
2223
+ }
2224
+
2225
+ // src/areas/detector.ts
2226
+ import { minimatch } from "minimatch";
2227
+
2228
+ // src/areas/patterns.ts
2229
+ var FOLDER_PATTERNS = [
2230
+ // ============================================================================
2231
+ // NEXT.JS APP ROUTER - Rotas específicas (alta prioridade)
2232
+ // ============================================================================
2233
+ { pattern: /app\/.*\/meus-pets\//, area: "meus-pets", priority: 100 },
2234
+ { pattern: /app\/.*\/pets\//, area: "meus-pets", priority: 100 },
2235
+ { pattern: /app\/.*\/consultas\//, area: "consultas-ia", priority: 100 },
2236
+ { pattern: /app\/.*\/dashboard\//, area: "dashboard", priority: 100 },
2237
+ { pattern: /app\/.*\/admin\//, area: "admin", priority: 100 },
2238
+ { pattern: /app\/.*\/assinatura\//, area: "stripe", priority: 100 },
2239
+ { pattern: /app\/.*\/guias\//, area: "training", priority: 100 },
2240
+ { pattern: /app\/.*\/treino\//, area: "training", priority: 100 },
2241
+ { pattern: /app\/.*\/login\//, area: "auth", priority: 100 },
2242
+ { pattern: /app\/.*\/cadastro\//, area: "auth", priority: 100 },
2243
+ { pattern: /app\/.*\/auth\//, area: "auth", priority: 100 },
2244
+ { pattern: /app\/.*\/perfil\//, area: "profile", priority: 100 },
2245
+ { pattern: /app\/.*\/configuracoes\//, area: "settings", priority: 100 },
2246
+ { pattern: /app\/.*\/settings\//, area: "settings", priority: 100 },
2247
+ { pattern: /app\/.*\/onboarding/, area: "onboarding", priority: 100 },
2248
+ { pattern: /app\/.*\/beta\//, area: "beta", priority: 100 },
2249
+ { pattern: /app\/.*\/precos\//, area: "pricing", priority: 100 },
2250
+ { pattern: /app\/.*\/pricing\//, area: "pricing", priority: 100 },
2251
+ { pattern: /app\/.*\/checkout\//, area: "checkout", priority: 100 },
2252
+ { pattern: /app\/.*\/cart\//, area: "cart", priority: 100 },
2253
+ { pattern: /app\/.*\/shop\//, area: "shop", priority: 100 },
2254
+ { pattern: /app\/.*\/products\//, area: "products", priority: 100 },
2255
+ { pattern: /app\/.*\/orders\//, area: "orders", priority: 100 },
2256
+ { pattern: /app\/.*\/blog\//, area: "blog", priority: 100 },
2257
+ { pattern: /app\/.*\/docs\//, area: "docs", priority: 100 },
2258
+ { pattern: /app\/.*\/legal\//, area: "legal", priority: 100 },
2259
+ { pattern: /app\/.*\/privacy\//, area: "legal", priority: 100 },
2260
+ { pattern: /app\/.*\/terms\//, area: "legal", priority: 100 },
2261
+ { pattern: /app\/.*\/about\//, area: "about", priority: 100 },
2262
+ { pattern: /app\/.*\/contact\//, area: "contact", priority: 100 },
2263
+ { pattern: /app\/.*\/faq\//, area: "faq", priority: 100 },
2264
+ { pattern: /app\/.*\/help\//, area: "help", priority: 100 },
2265
+ { pattern: /app\/.*\/support\//, area: "support", priority: 100 },
2266
+ { pattern: /app\/.*\/feedback\//, area: "feedback", priority: 100 },
2267
+ // ============================================================================
2268
+ // VITE / CRA - src/pages/ ou src/views/ (alta prioridade)
2269
+ // ============================================================================
2270
+ { pattern: /src\/pages\/[Dd]ashboard/, area: "dashboard", priority: 100 },
2271
+ { pattern: /src\/pages\/[Aa]dmin/, area: "admin", priority: 100 },
2272
+ { pattern: /src\/pages\/[Aa]uth/, area: "auth", priority: 100 },
2273
+ { pattern: /src\/pages\/[Ll]ogin/, area: "auth", priority: 100 },
2274
+ { pattern: /src\/pages\/[Rr]egister/, area: "auth", priority: 100 },
2275
+ { pattern: /src\/pages\/[Ss]ignup/, area: "auth", priority: 100 },
2276
+ { pattern: /src\/pages\/[Pp]rofile/, area: "profile", priority: 100 },
2277
+ { pattern: /src\/pages\/[Ss]ettings/, area: "settings", priority: 100 },
2278
+ { pattern: /src\/pages\/[Pp]ricing/, area: "pricing", priority: 100 },
2279
+ { pattern: /src\/pages\/[Cc]heckout/, area: "checkout", priority: 100 },
2280
+ { pattern: /src\/pages\/[Cc]art/, area: "cart", priority: 100 },
2281
+ { pattern: /src\/pages\/[Ss]hop/, area: "shop", priority: 100 },
2282
+ { pattern: /src\/pages\/[Pp]roducts?/, area: "products", priority: 100 },
2283
+ { pattern: /src\/pages\/[Oo]rders?/, area: "orders", priority: 100 },
2284
+ { pattern: /src\/pages\/[Bb]log/, area: "blog", priority: 100 },
2285
+ { pattern: /src\/views\/[Dd]ashboard/, area: "dashboard", priority: 100 },
2286
+ { pattern: /src\/views\/[Aa]dmin/, area: "admin", priority: 100 },
2287
+ { pattern: /src\/views\/[Aa]uth/, area: "auth", priority: 100 },
2288
+ // ============================================================================
2289
+ // REMIX - app/routes/
2290
+ // ============================================================================
2291
+ { pattern: /app\/routes\/dashboard/, area: "dashboard", priority: 100 },
2292
+ { pattern: /app\/routes\/admin/, area: "admin", priority: 100 },
2293
+ { pattern: /app\/routes\/auth/, area: "auth", priority: 100 },
2294
+ { pattern: /app\/routes\/login/, area: "auth", priority: 100 },
2295
+ { pattern: /app\/routes\/_auth/, area: "auth", priority: 100 },
2296
+ // ============================================================================
2297
+ // NUXT - pages/ (sem src/)
2298
+ // ============================================================================
2299
+ { pattern: /^pages\/dashboard/, area: "dashboard", priority: 100 },
2300
+ { pattern: /^pages\/admin/, area: "admin", priority: 100 },
2301
+ { pattern: /^pages\/auth/, area: "auth", priority: 100 },
2302
+ // ============================================================================
2303
+ // SVELTEKIT - src/routes/
2304
+ // ============================================================================
2305
+ { pattern: /src\/routes\/dashboard/, area: "dashboard", priority: 100 },
2306
+ { pattern: /src\/routes\/admin/, area: "admin", priority: 100 },
2307
+ { pattern: /src\/routes\/auth/, area: "auth", priority: 100 },
2308
+ { pattern: /src\/routes\/\(auth\)/, area: "auth", priority: 100 },
2309
+ { pattern: /src\/routes\/\(app\)/, area: "app", priority: 90 },
2310
+ // ============================================================================
2311
+ // ASTRO - src/pages/
2312
+ // ============================================================================
2313
+ { pattern: /src\/pages\/blog/, area: "blog", priority: 100 },
2314
+ { pattern: /src\/pages\/docs/, area: "docs", priority: 100 },
2315
+ // ============================================================================
2316
+ // COMPONENTS - Por subpasta (alta prioridade)
2317
+ // Funciona em qualquer framework (com ou sem src/)
2318
+ // ============================================================================
2319
+ { pattern: /components\/pets\//, area: "meus-pets", priority: 90 },
2320
+ { pattern: /components\/pet\//, area: "meus-pets", priority: 90 },
2321
+ { pattern: /components\/consultation\//, area: "consultas-ia", priority: 90 },
2322
+ { pattern: /components\/chat\//, area: "chat", priority: 90 },
2323
+ { pattern: /components\/training\//, area: "training", priority: 90 },
2324
+ { pattern: /components\/health\//, area: "health-tracking", priority: 90 },
2325
+ { pattern: /components\/auth\//, area: "auth", priority: 90 },
2326
+ { pattern: /components\/admin\//, area: "admin", priority: 90 },
2327
+ { pattern: /components\/landing\//, area: "landing", priority: 90 },
2328
+ { pattern: /components\/marketing\//, area: "landing", priority: 90 },
2329
+ { pattern: /components\/dashboard\//, area: "dashboard", priority: 90 },
2330
+ { pattern: /components\/subscription\//, area: "stripe", priority: 90 },
2331
+ { pattern: /components\/stripe\//, area: "stripe", priority: 90 },
2332
+ { pattern: /components\/payment\//, area: "payments", priority: 90 },
2333
+ { pattern: /components\/checkout\//, area: "checkout", priority: 90 },
2334
+ { pattern: /components\/cart\//, area: "cart", priority: 90 },
2335
+ { pattern: /components\/notification\//, area: "notifications", priority: 90 },
2336
+ { pattern: /components\/pdf\//, area: "pdf", priority: 90 },
2337
+ { pattern: /components\/seo\//, area: "seo", priority: 90 },
2338
+ { pattern: /components\/blog\//, area: "blog", priority: 90 },
2339
+ { pattern: /components\/docs\//, area: "docs", priority: 90 },
2340
+ { pattern: /components\/legal\//, area: "legal", priority: 90 },
2341
+ { pattern: /components\/feedback\//, area: "feedback", priority: 90 },
2342
+ { pattern: /components\/beta\//, area: "beta", priority: 90 },
2343
+ { pattern: /components\/onboarding\//, area: "onboarding", priority: 90 },
2344
+ { pattern: /components\/settings\//, area: "settings", priority: 90 },
2345
+ { pattern: /components\/profile\//, area: "profile", priority: 90 },
2346
+ { pattern: /components\/user\//, area: "user", priority: 90 },
2347
+ { pattern: /components\/products?\//, area: "products", priority: 90 },
2348
+ { pattern: /components\/orders?\//, area: "orders", priority: 90 },
2349
+ { pattern: /components\/shop\//, area: "shop", priority: 90 },
2350
+ { pattern: /components\/forms?\//, area: "forms", priority: 85 },
2351
+ { pattern: /components\/tables?\//, area: "tables", priority: 85 },
2352
+ { pattern: /components\/modals?\//, area: "modals", priority: 85 },
2353
+ { pattern: /components\/dialogs?\//, area: "modals", priority: 85 },
2354
+ // Componentes compartilhados (baixa prioridade)
2355
+ { pattern: /components\/ui\//, area: "shared-ui", priority: 30 },
2356
+ { pattern: /components\/common\//, area: "shared-ui", priority: 30 },
2357
+ { pattern: /components\/shared\//, area: "shared-ui", priority: 30 },
2358
+ { pattern: /components\/base\//, area: "shared-ui", priority: 30 },
2359
+ { pattern: /components\/core\//, area: "shared-ui", priority: 30 },
2360
+ { pattern: /components\/primitives\//, area: "shared-ui", priority: 30 },
2361
+ { pattern: /components\/providers\//, area: "core", priority: 40 },
2362
+ { pattern: /components\/layout\//, area: "core", priority: 40 },
2363
+ { pattern: /components\/layouts\//, area: "core", priority: 40 },
2364
+ // ============================================================================
2365
+ // FEATURES (padrão comum em projetos maiores)
2366
+ // ============================================================================
2367
+ { pattern: /features\/auth\//, area: "auth", priority: 95 },
2368
+ { pattern: /features\/dashboard\//, area: "dashboard", priority: 95 },
2369
+ { pattern: /features\/admin\//, area: "admin", priority: 95 },
2370
+ { pattern: /features\/checkout\//, area: "checkout", priority: 95 },
2371
+ { pattern: /features\/cart\//, area: "cart", priority: 95 },
2372
+ { pattern: /features\/products?\//, area: "products", priority: 95 },
2373
+ { pattern: /features\/orders?\//, area: "orders", priority: 95 },
2374
+ { pattern: /features\/user\//, area: "user", priority: 95 },
2375
+ { pattern: /features\/settings\//, area: "settings", priority: 95 },
2376
+ { pattern: /features\/notifications?\//, area: "notifications", priority: 95 },
2377
+ { pattern: /features\/blog\//, area: "blog", priority: 95 },
2378
+ // ============================================================================
2379
+ // MODULES (outro padrão comum)
2380
+ // ============================================================================
2381
+ { pattern: /modules\/auth\//, area: "auth", priority: 95 },
2382
+ { pattern: /modules\/dashboard\//, area: "dashboard", priority: 95 },
2383
+ { pattern: /modules\/admin\//, area: "admin", priority: 95 },
2384
+ { pattern: /modules\/checkout\//, area: "checkout", priority: 95 },
2385
+ { pattern: /modules\/products?\//, area: "products", priority: 95 },
2386
+ // ============================================================================
2387
+ // LIB - Módulos específicos
2388
+ // ============================================================================
2389
+ { pattern: /lib\/firebase\/ai\//, area: "firebase-ai", priority: 85 },
2390
+ { pattern: /lib\/firebase\/aiExtraction\//, area: "firebase-ai", priority: 85 },
2391
+ { pattern: /lib\/firebase\/firestore\//, area: "firebase-firestore", priority: 85 },
2392
+ { pattern: /lib\/firebase\/prompts\//, area: "firebase-ai", priority: 85 },
2393
+ { pattern: /lib\/firebase\/analytics\//, area: "analytics", priority: 85 },
2394
+ { pattern: /lib\/firebase\//, area: "firebase-core", priority: 80 },
2395
+ { pattern: /lib\/stripe\//, area: "stripe", priority: 80 },
2396
+ { pattern: /lib\/i18n\//, area: "i18n", priority: 80 },
2397
+ // ============================================================================
2398
+ // HOOKS - Por nome
2399
+ // ============================================================================
2400
+ { pattern: /hooks\/.*[Pp]et/, area: "meus-pets", priority: 70 },
2401
+ { pattern: /hooks\/.*[Aa]uth/, area: "auth", priority: 70 },
2402
+ { pattern: /hooks\/.*[Ss]ubscription/, area: "stripe", priority: 70 },
2403
+ { pattern: /hooks\/.*[Nn]otification/, area: "notifications", priority: 70 },
2404
+ { pattern: /hooks\/.*[Hh]ealth/, area: "health-tracking", priority: 70 },
2405
+ { pattern: /hooks\/.*[Tt]raining/, area: "training", priority: 70 },
2406
+ // ============================================================================
2407
+ // STORE - Por nome
2408
+ // ============================================================================
2409
+ { pattern: /store\/.*[Pp]et/, area: "meus-pets", priority: 70 },
2410
+ { pattern: /store\/.*[Aa]uth/, area: "auth", priority: 70 },
2411
+ { pattern: /store\/.*[Uu]ser/, area: "auth", priority: 70 },
2412
+ // ============================================================================
2413
+ // SCHEMAS - Por nome
2414
+ // ============================================================================
2415
+ { pattern: /schemas\/.*[Pp]et/, area: "meus-pets", priority: 70 },
2416
+ { pattern: /schemas\/.*[Aa]uth/, area: "auth", priority: 70 },
2417
+ // ============================================================================
2418
+ // TYPES - Por nome
2419
+ // ============================================================================
2420
+ { pattern: /types\/.*[Pp]et/, area: "meus-pets", priority: 70 },
2421
+ { pattern: /types\/.*[Aa]uth/, area: "auth", priority: 70 },
2422
+ { pattern: /types\/.*[Ss]tripe/, area: "stripe", priority: 70 },
2423
+ { pattern: /types\/.*[Ss]ubscription/, area: "stripe", priority: 70 },
2424
+ // ============================================================================
2425
+ // CLOUD FUNCTIONS
2426
+ // ============================================================================
2427
+ { pattern: /functions\/src\//, area: "cloud-functions", priority: 80 },
2428
+ // ============================================================================
2429
+ // OUTROS
2430
+ // ============================================================================
2431
+ { pattern: /messages\//, area: "i18n", priority: 60 },
2432
+ { pattern: /i18n\//, area: "i18n", priority: 60 },
2433
+ { pattern: /public\//, area: "assets", priority: 50 },
2434
+ { pattern: /scripts\//, area: "scripts", priority: 50 }
2435
+ ];
2436
+ var KEYWORD_PATTERNS = [
2437
+ // Pets
2438
+ { keyword: /[Pp]et/, area: "meus-pets", priority: 60 },
2439
+ { keyword: /[Vv]accin/, area: "meus-pets", priority: 60 },
2440
+ { keyword: /[Dd]eworm/, area: "meus-pets", priority: 60 },
2441
+ { keyword: /[Mm]edication/, area: "meus-pets", priority: 60 },
2442
+ { keyword: /[Ss]urgery/, area: "meus-pets", priority: 60 },
2443
+ { keyword: /[Vv]eterinary/, area: "meus-pets", priority: 60 },
2444
+ // Consultas IA
2445
+ { keyword: /[Cc]onsultation/, area: "consultas-ia", priority: 60 },
2446
+ { keyword: /[Cc]hat/, area: "consultas-ia", priority: 50 },
2447
+ { keyword: /[Gg]emini/, area: "firebase-ai", priority: 60 },
2448
+ // Health
2449
+ { keyword: /[Hh]ealth[Tt]racking/, area: "health-tracking", priority: 65 },
2450
+ { keyword: /[Hh]ome[Cc]are/, area: "health-tracking", priority: 65 },
2451
+ // Training
2452
+ { keyword: /[Tt]raining/, area: "training", priority: 60 },
2453
+ { keyword: /[Gg]uide/, area: "training", priority: 55 },
2454
+ { keyword: /[Aa]destramento/, area: "training", priority: 60 },
2455
+ // Auth
2456
+ { keyword: /[Aa]uth/, area: "auth", priority: 60 },
2457
+ { keyword: /[Ll]ogin/, area: "auth", priority: 60 },
2458
+ { keyword: /[Rr]egister/, area: "auth", priority: 60 },
2459
+ { keyword: /[Ss]ignup/, area: "auth", priority: 60 },
2460
+ { keyword: /[Ss]ignin/, area: "auth", priority: 60 },
2461
+ // Stripe
2462
+ { keyword: /[Ss]tripe/, area: "stripe", priority: 65 },
2463
+ { keyword: /[Ss]ubscription/, area: "stripe", priority: 60 },
2464
+ { keyword: /[Pp]ayment/, area: "stripe", priority: 60 },
2465
+ { keyword: /[Cc]heckout/, area: "stripe", priority: 60 },
2466
+ { keyword: /[Pp]rice/, area: "pricing", priority: 55 },
2467
+ { keyword: /[Pp]ricing/, area: "pricing", priority: 60 },
2468
+ // Notifications
2469
+ { keyword: /[Nn]otification/, area: "notifications", priority: 60 },
2470
+ { keyword: /[Ff][Cc][Mm]/, area: "notifications", priority: 65 },
2471
+ { keyword: /[Pp]ush/, area: "notifications", priority: 55 },
2472
+ // i18n
2473
+ { keyword: /[Ii]18n/, area: "i18n", priority: 60 },
2474
+ { keyword: /[Ll]ocale/, area: "i18n", priority: 55 },
2475
+ { keyword: /[Tt]ranslat/, area: "i18n", priority: 55 },
2476
+ // SEO
2477
+ { keyword: /[Ss][Ee][Oo]/, area: "seo", priority: 60 },
2478
+ { keyword: /[Ss]itemap/, area: "seo", priority: 60 },
2479
+ { keyword: /[Mm]eta/, area: "seo", priority: 50 },
2480
+ // Analytics
2481
+ { keyword: /[Aa]nalytics/, area: "analytics", priority: 60 },
2482
+ { keyword: /[Uu]tm/, area: "analytics", priority: 55 },
2483
+ { keyword: /[Tt]racking/, area: "analytics", priority: 50 },
2484
+ // Admin
2485
+ { keyword: /[Aa]dmin/, area: "admin", priority: 60 },
2486
+ // PWA
2487
+ { keyword: /[Pp][Ww][Aa]/, area: "pwa", priority: 60 },
2488
+ { keyword: /[Ss]ervice[Ww]orker/, area: "pwa", priority: 60 },
2489
+ { keyword: /[Mm]anifest/, area: "pwa", priority: 55 },
2490
+ { keyword: /[Oo]ffline/, area: "pwa", priority: 55 },
2491
+ // PDF
2492
+ { keyword: /[Pp]df/, area: "pdf", priority: 60 },
2493
+ { keyword: /[Rr]eport/, area: "pdf", priority: 50 }
2494
+ ];
2495
+ var AREA_NAMES = {
2496
+ // Áreas específicas de domínio
2497
+ "meus-pets": "Meus Pets",
2498
+ "consultas-ia": "Consultas IA",
2499
+ "health-tracking": "Health Tracking",
2500
+ training: "Adestramento",
2501
+ // Autenticação e usuário
2502
+ auth: "Autentica\xE7\xE3o",
2503
+ user: "Usu\xE1rio",
2504
+ profile: "Perfil",
2505
+ settings: "Configura\xE7\xF5es",
2506
+ onboarding: "Onboarding",
2507
+ // E-commerce
2508
+ stripe: "Pagamentos (Stripe)",
2509
+ payments: "Pagamentos",
2510
+ checkout: "Checkout",
2511
+ cart: "Carrinho",
2512
+ shop: "Loja",
2513
+ products: "Produtos",
2514
+ orders: "Pedidos",
2515
+ pricing: "Pre\xE7os",
2516
+ // Comunicação
2517
+ notifications: "Notifica\xE7\xF5es",
2518
+ chat: "Chat",
2519
+ feedback: "Feedback",
2520
+ support: "Suporte",
2521
+ help: "Ajuda",
2522
+ faq: "FAQ",
2523
+ contact: "Contato",
2524
+ // Firebase
2525
+ "firebase-core": "Firebase Core",
2526
+ "firebase-ai": "Firebase AI",
2527
+ "firebase-firestore": "Firestore",
2528
+ // Conteúdo
2529
+ blog: "Blog",
2530
+ docs: "Documenta\xE7\xE3o",
2531
+ legal: "P\xE1ginas Legais",
2532
+ about: "Sobre",
2533
+ // Marketing e SEO
2534
+ landing: "Landing Pages",
2535
+ seo: "SEO",
2536
+ analytics: "Analytics",
2537
+ beta: "Programa Beta",
2538
+ // Admin e Dashboard
2539
+ admin: "Admin",
2540
+ dashboard: "Dashboard",
2541
+ // Técnico
2542
+ i18n: "Internacionaliza\xE7\xE3o",
2543
+ pwa: "PWA",
2544
+ pdf: "PDF",
2545
+ core: "Core",
2546
+ "shared-ui": "UI Compartilhado",
2547
+ "cloud-functions": "Cloud Functions",
2548
+ assets: "Assets",
2549
+ scripts: "Scripts",
2550
+ // UI patterns
2551
+ forms: "Formul\xE1rios",
2552
+ tables: "Tabelas",
2553
+ modals: "Modais",
2554
+ // Genéricos
2555
+ app: "Aplica\xE7\xE3o"
2556
+ };
2557
+ var AREA_DESCRIPTIONS = {
2558
+ // Áreas específicas de domínio
2559
+ "meus-pets": "Gerenciamento completo de pets do usu\xE1rio",
2560
+ "consultas-ia": "Chat com IA para consultas veterin\xE1rias",
2561
+ "health-tracking": "Acompanhamento de sa\xFAde e sintomas",
2562
+ training: "Sistema de adestramento com guias e progresso",
2563
+ // Autenticação e usuário
2564
+ auth: "Autentica\xE7\xE3o e gerenciamento de sess\xE3o",
2565
+ user: "Gerenciamento de dados do usu\xE1rio",
2566
+ profile: "Perfil do usu\xE1rio",
2567
+ settings: "Configura\xE7\xF5es do usu\xE1rio",
2568
+ onboarding: "Fluxo de onboarding de novos usu\xE1rios",
2569
+ // E-commerce
2570
+ stripe: "Integra\xE7\xE3o Stripe para pagamentos e assinaturas",
2571
+ payments: "Sistema de pagamentos",
2572
+ checkout: "Fluxo de checkout e finaliza\xE7\xE3o de compra",
2573
+ cart: "Carrinho de compras",
2574
+ shop: "Loja e cat\xE1logo de produtos",
2575
+ products: "Gerenciamento de produtos",
2576
+ orders: "Gerenciamento de pedidos",
2577
+ pricing: "P\xE1gina de pre\xE7os e planos",
2578
+ // Comunicação
2579
+ notifications: "Sistema de notifica\xE7\xF5es push",
2580
+ chat: "Sistema de chat e mensagens",
2581
+ feedback: "Coleta de feedback dos usu\xE1rios",
2582
+ support: "Suporte ao cliente",
2583
+ help: "Central de ajuda",
2584
+ faq: "Perguntas frequentes",
2585
+ contact: "P\xE1gina de contato",
2586
+ // Firebase
2587
+ "firebase-core": "Configura\xE7\xE3o e servi\xE7os Firebase client-side",
2588
+ "firebase-ai": "Integra\xE7\xE3o com Firebase AI (Gemini)",
2589
+ "firebase-firestore": "Opera\xE7\xF5es CRUD no Firestore",
2590
+ // Conteúdo
2591
+ blog: "Blog e artigos",
2592
+ docs: "Documenta\xE7\xE3o e guias",
2593
+ legal: "Termos de uso, privacidade e pol\xEDticas",
2594
+ about: "P\xE1gina sobre a empresa",
2595
+ // Marketing e SEO
2596
+ landing: "Landing pages e marketing",
2597
+ seo: "SEO, meta tags e sitemaps",
2598
+ analytics: "Analytics e tracking de eventos",
2599
+ beta: "Programa de beta testers",
2600
+ // Admin e Dashboard
2601
+ admin: "Painel administrativo",
2602
+ dashboard: "Dashboard do usu\xE1rio",
2603
+ // Técnico
2604
+ i18n: "Internacionaliza\xE7\xE3o e tradu\xE7\xF5es",
2605
+ pwa: "Progressive Web App (offline, install)",
2606
+ pdf: "Gera\xE7\xE3o de relat\xF3rios PDF",
2607
+ core: "Providers e layout principal",
2608
+ "shared-ui": "Componentes de UI compartilhados",
2609
+ "cloud-functions": "Cloud Functions (serverless)",
2610
+ assets: "Assets p\xFAblicos (imagens, fontes)",
2611
+ scripts: "Scripts de automa\xE7\xE3o",
2612
+ // UI patterns
2613
+ forms: "Componentes de formul\xE1rio reutiliz\xE1veis",
2614
+ tables: "Componentes de tabela reutiliz\xE1veis",
2615
+ modals: "Modais e dialogs reutiliz\xE1veis",
2616
+ // Genéricos
2617
+ app: "\xC1rea principal da aplica\xE7\xE3o"
2618
+ };
2619
+
2620
+ // src/areas/detector.ts
2621
+ function detectFileAreas(filePath, config) {
2622
+ const normalizedPath = filePath.replace(/\\/g, "/");
2623
+ const matches = [];
2624
+ for (const [areaId, areaConfig] of Object.entries(config.areas)) {
2625
+ if (matchesAreaConfig(normalizedPath, areaConfig)) {
2626
+ matches.push({ area: areaId, priority: 200, source: "config" });
2627
+ }
2628
+ }
2629
+ for (const { pattern, area: area2, priority } of FOLDER_PATTERNS) {
2630
+ if (pattern.test(normalizedPath)) {
2631
+ if (!matches.some((m) => m.area === area2 && m.source === "config")) {
2632
+ matches.push({ area: area2, priority, source: "folder" });
2633
+ }
2634
+ }
2635
+ }
2636
+ const fileName = normalizedPath.split("/").pop() || "";
2637
+ for (const { keyword, area: area2, priority } of KEYWORD_PATTERNS) {
2638
+ if (keyword.test(fileName) || keyword.test(normalizedPath)) {
2639
+ if (!matches.some((m) => m.area === area2)) {
2640
+ matches.push({ area: area2, priority, source: "keyword" });
2641
+ }
2642
+ }
2643
+ }
2644
+ const sorted = matches.sort((a, b) => b.priority - a.priority);
2645
+ const unique = [...new Set(sorted.map((m) => m.area))];
2646
+ return unique;
2647
+ }
2648
+ function matchesAreaConfig(filePath, config) {
2649
+ if (config.exclude) {
2650
+ for (const pattern of config.exclude) {
2651
+ if (minimatch(filePath, pattern, { dot: true })) {
2652
+ return false;
2653
+ }
2654
+ }
2655
+ }
2656
+ for (const pattern of config.patterns) {
2657
+ if (minimatch(filePath, pattern, { dot: true })) {
2658
+ return true;
2659
+ }
2660
+ }
2661
+ if (config.keywords) {
2662
+ const lowerPath = filePath.toLowerCase();
2663
+ for (const keyword of config.keywords) {
2664
+ if (lowerPath.includes(keyword.toLowerCase())) {
2665
+ return true;
2666
+ }
2667
+ }
2668
+ }
2669
+ return false;
2670
+ }
2671
+ function getAreaName(areaId, config) {
2672
+ if (config.areas[areaId]?.name) {
2673
+ return config.areas[areaId].name;
2674
+ }
2675
+ if (AREA_NAMES[areaId]) {
2676
+ return AREA_NAMES[areaId];
2677
+ }
2678
+ return areaId.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2679
+ }
2680
+ function getAreaDescription(areaId, config) {
2681
+ if (config.areas[areaId]?.description) {
2682
+ return config.areas[areaId].description;
2683
+ }
2684
+ return AREA_DESCRIPTIONS[areaId];
2685
+ }
2686
+ function inferFileDescription(filePath, category) {
2687
+ const fileName = filePath.split("/").pop() || "";
2688
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2689
+ const patterns = [
2690
+ // Pages
2691
+ { pattern: /^page$/, description: () => "P\xE1gina principal" },
2692
+ { pattern: /^layout$/, description: () => "Layout" },
2693
+ { pattern: /^loading$/, description: () => "Estado de loading" },
2694
+ { pattern: /^error$/, description: () => "P\xE1gina de erro" },
2695
+ { pattern: /^not-found$/, description: () => "P\xE1gina 404" },
2696
+ // Components com sufixo
2697
+ { pattern: /(.+)PageClient$/, description: (m) => `Client component da p\xE1gina ${m[1]}` },
2698
+ { pattern: /(.+)Dialog$/, description: (m) => `Dialog de ${m[1].toLowerCase()}` },
2699
+ { pattern: /(.+)Modal$/, description: (m) => `Modal de ${m[1].toLowerCase()}` },
2700
+ { pattern: /(.+)Form$/, description: (m) => `Formul\xE1rio de ${m[1].toLowerCase()}` },
2701
+ { pattern: /(.+)Card$/, description: (m) => `Card de ${m[1].toLowerCase()}` },
2702
+ { pattern: /(.+)List$/, description: (m) => `Lista de ${m[1].toLowerCase()}` },
2703
+ { pattern: /(.+)Table$/, description: (m) => `Tabela de ${m[1].toLowerCase()}` },
2704
+ { pattern: /(.+)Manager$/, description: (m) => `Gerenciador de ${m[1].toLowerCase()}` },
2705
+ { pattern: /(.+)Provider$/, description: (m) => `Provider de ${m[1].toLowerCase()}` },
2706
+ { pattern: /(.+)Context$/, description: (m) => `Context de ${m[1].toLowerCase()}` },
2707
+ { pattern: /(.+)Step$/, description: (m) => `Step: ${m[1]}` },
2708
+ { pattern: /(.+)Tab$/, description: (m) => `Aba: ${m[1]}` },
2709
+ { pattern: /(.+)Section$/, description: (m) => `Se\xE7\xE3o: ${m[1]}` },
2710
+ { pattern: /(.+)Header$/, description: (m) => `Header de ${m[1].toLowerCase()}` },
2711
+ { pattern: /(.+)Footer$/, description: (m) => `Footer de ${m[1].toLowerCase()}` },
2712
+ { pattern: /(.+)Skeleton$/, description: (m) => `Skeleton de ${m[1].toLowerCase()}` },
2713
+ { pattern: /(.+)Chip$/, description: (m) => `Chip de ${m[1].toLowerCase()}` },
2714
+ { pattern: /(.+)Badge$/, description: (m) => `Badge de ${m[1].toLowerCase()}` },
2715
+ { pattern: /(.+)Button$/, description: (m) => `Bot\xE3o ${m[1].toLowerCase()}` },
2716
+ { pattern: /(.+)Icon$/, description: (m) => `\xCDcone ${m[1].toLowerCase()}` },
2717
+ // Hooks
2718
+ { pattern: /^use(.+)$/, description: (m) => `Hook de ${m[1].toLowerCase()}` },
2719
+ // Types/Schemas
2720
+ { pattern: /(.+)\.types$/, description: (m) => `Tipos de ${m[1].toLowerCase()}` },
2721
+ { pattern: /(.+)Schemas?$/, description: (m) => `Schema de ${m[1].toLowerCase()}` },
2722
+ // Utils
2723
+ { pattern: /(.+)Helpers?$/, description: (m) => `Helpers de ${m[1].toLowerCase()}` },
2724
+ { pattern: /(.+)Utils?$/, description: (m) => `Utilit\xE1rios de ${m[1].toLowerCase()}` },
2725
+ { pattern: /(.+)Formatters?$/, description: (m) => `Formatador de ${m[1].toLowerCase()}` },
2726
+ { pattern: /(.+)Validators?$/, description: (m) => `Validador de ${m[1].toLowerCase()}` },
2727
+ { pattern: /(.+)Mappers?$/, description: (m) => `Mapper de ${m[1].toLowerCase()}` },
2728
+ { pattern: /(.+)Converters?$/, description: (m) => `Conversor de ${m[1].toLowerCase()}` },
2729
+ // Services
2730
+ { pattern: /(.+)Service$/, description: (m) => `Servi\xE7o de ${m[1].toLowerCase()}` },
2731
+ // Index
2732
+ { pattern: /^index$/, description: () => "Export principal" }
2733
+ ];
2734
+ for (const { pattern, description } of patterns) {
2735
+ const match = fileNameNoExt.match(pattern);
2736
+ if (match) {
2737
+ return description(match);
2738
+ }
2739
+ }
2740
+ const categoryDescriptions = {
2741
+ page: "P\xE1gina",
2742
+ layout: "Layout",
2743
+ route: "API Route",
2744
+ component: "Componente",
2745
+ hook: "Hook",
2746
+ service: "Servi\xE7o",
2747
+ store: "Store",
2748
+ util: "Utilit\xE1rio",
2749
+ type: "Tipos",
2750
+ config: "Configura\xE7\xE3o",
2751
+ test: "Teste"
2752
+ };
2753
+ return categoryDescriptions[category];
2754
+ }
2755
+
2756
+ // src/commands/areas.ts
2757
+ var CODE_EXTENSIONS3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2758
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
2759
+ "node_modules",
2760
+ "dist",
2761
+ "build",
2762
+ ".git",
2763
+ ".next",
2764
+ ".cache",
2765
+ "coverage",
2766
+ ".turbo",
2767
+ ".vercel",
2768
+ ".analyze"
2769
+ ]);
2770
+ async function areas(options = {}) {
2771
+ const cwd = options.cwd || process.cwd();
2772
+ const format = options.format || "text";
2773
+ try {
2774
+ const config = readConfig(cwd);
2775
+ const allFiles = getAllCodeFiles2(cwd);
2776
+ const areaMap = /* @__PURE__ */ new Map();
2777
+ const unmapped = [];
2778
+ for (const filePath of allFiles) {
2779
+ const category = detectCategory(filePath);
2780
+ const areas2 = detectFileAreas(filePath, config);
2781
+ let description = getFileDescription(cwd, filePath);
2782
+ if (!description && config.settings?.inferDescriptions !== false) {
2783
+ description = inferFileDescription(filePath, category);
2784
+ }
2785
+ const areaFile = {
2786
+ path: filePath,
2787
+ category,
2788
+ description
2789
+ };
2790
+ if (areas2.length === 0) {
2791
+ unmapped.push(areaFile);
2792
+ } else {
2793
+ for (const areaId of areas2) {
2794
+ if (!areaMap.has(areaId)) {
2795
+ areaMap.set(areaId, []);
2796
+ }
2797
+ areaMap.get(areaId).push(areaFile);
2798
+ }
2799
+ }
2800
+ }
2801
+ const detectedAreas = [];
2802
+ for (const [areaId, files] of areaMap) {
2803
+ const categories = {};
2804
+ for (const file of files) {
2805
+ categories[file.category] = (categories[file.category] || 0) + 1;
2806
+ }
2807
+ detectedAreas.push({
2808
+ id: areaId,
2809
+ name: getAreaName(areaId, config),
2810
+ description: getAreaDescription(areaId, config),
2811
+ files,
2812
+ fileCount: files.length,
2813
+ categories,
2814
+ isAutoDetected: !config.areas[areaId]
2815
+ });
2816
+ }
2817
+ detectedAreas.sort((a, b) => b.fileCount - a.fileCount);
2818
+ const result = {
2819
+ version: "1.0.0",
2820
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2821
+ cwd,
2822
+ areas: detectedAreas,
2823
+ unmapped,
2824
+ summary: {
2825
+ totalAreas: detectedAreas.length,
2826
+ totalFiles: allFiles.length,
2827
+ unmappedCount: unmapped.length
2828
+ }
2829
+ };
2830
+ if (format === "json") {
2831
+ return JSON.stringify(result, null, 2);
2832
+ }
2833
+ return formatAreasText(result);
2834
+ } catch (error) {
2835
+ const message = error instanceof Error ? error.message : String(error);
2836
+ throw new Error(`Erro ao executar areas: ${message}`);
2837
+ }
2838
+ }
2839
+ function getAllCodeFiles2(dir, files = [], baseDir = dir) {
2840
+ try {
2841
+ const entries = readdirSync3(dir);
2842
+ for (const entry of entries) {
2843
+ const fullPath = join5(dir, entry);
2844
+ if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) {
2845
+ continue;
2846
+ }
2847
+ try {
2848
+ const stat = statSync3(fullPath);
2849
+ if (stat.isDirectory()) {
2850
+ getAllCodeFiles2(fullPath, files, baseDir);
2851
+ } else {
2852
+ const ext = extname3(entry).toLowerCase();
2853
+ if (CODE_EXTENSIONS3.has(ext)) {
2854
+ const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
2855
+ files.push(relativePath);
2856
+ }
2857
+ }
2858
+ } catch {
2859
+ }
2860
+ }
2861
+ } catch {
2862
+ }
2863
+ return files;
2864
+ }
2865
+
2866
+ // src/commands/area.ts
2867
+ import { readdirSync as readdirSync4, statSync as statSync4 } from "fs";
2868
+ import { join as join6, extname as extname4 } from "path";
2869
+ var CODE_EXTENSIONS4 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2870
+ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
2871
+ "node_modules",
2872
+ "dist",
2873
+ "build",
2874
+ ".git",
2875
+ ".next",
2876
+ ".cache",
2877
+ "coverage",
2878
+ ".turbo",
2879
+ ".vercel",
2880
+ ".analyze"
2881
+ ]);
2882
+ async function area(target, options = {}) {
2883
+ const cwd = options.cwd || process.cwd();
2884
+ const format = options.format || "text";
2885
+ const filterType = options.type;
2886
+ const full = options.full ?? false;
2887
+ if (!target) {
2888
+ throw new Error("Nome da \xE1rea \xE9 obrigat\xF3rio. Exemplo: ai-tool area meus-pets");
2889
+ }
2890
+ try {
2891
+ const config = readConfig(cwd);
2892
+ const allFiles = getAllCodeFiles3(cwd);
2893
+ const areaFiles = [];
2894
+ const targetLower = target.toLowerCase();
2895
+ for (const filePath of allFiles) {
2896
+ const fileAreas = detectFileAreas(filePath, config);
2897
+ const belongsToArea = fileAreas.some(
2898
+ (a) => a.toLowerCase() === targetLower || a.toLowerCase().includes(targetLower)
2899
+ );
2900
+ if (belongsToArea) {
2901
+ const category = detectCategory(filePath);
2902
+ if (filterType && category !== filterType) {
2903
+ continue;
2904
+ }
2905
+ let description = getFileDescription(cwd, filePath);
2906
+ if (!description && config.settings?.inferDescriptions !== false) {
2907
+ description = inferFileDescription(filePath, category);
2908
+ }
2909
+ areaFiles.push({
2910
+ path: filePath,
2911
+ category,
2912
+ description
2913
+ });
2914
+ }
2915
+ }
2916
+ if (areaFiles.length === 0) {
2917
+ const availableAreas = getAvailableAreas(allFiles, config);
2918
+ return formatAreaNotFound(target, availableAreas);
2919
+ }
2920
+ const byCategory = {};
2921
+ const categories = {};
2922
+ for (const file of areaFiles) {
2923
+ if (!byCategory[file.category]) {
2924
+ byCategory[file.category] = [];
2925
+ }
2926
+ byCategory[file.category].push(file);
2927
+ categories[file.category] = (categories[file.category] || 0) + 1;
2928
+ }
2929
+ for (const cat of Object.keys(byCategory)) {
2930
+ byCategory[cat].sort((a, b) => a.path.localeCompare(b.path));
2931
+ }
2932
+ const realAreaId = findRealAreaId(target, allFiles, config);
2933
+ const detectedArea = {
2934
+ id: realAreaId || target,
2935
+ name: getAreaName(realAreaId || target, config),
2936
+ description: getAreaDescription(realAreaId || target, config),
2937
+ files: areaFiles,
2938
+ fileCount: areaFiles.length,
2939
+ categories,
2940
+ isAutoDetected: !config.areas[realAreaId || target]
2941
+ };
2942
+ const result = {
2943
+ version: "1.0.0",
2944
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2945
+ area: detectedArea,
2946
+ byCategory
2947
+ };
2948
+ if (format === "json") {
2949
+ return JSON.stringify(result, null, 2);
2950
+ }
2951
+ return formatAreaDetailText(result, { full, filterType });
2952
+ } catch (error) {
2953
+ const message = error instanceof Error ? error.message : String(error);
2954
+ throw new Error(`Erro ao executar area: ${message}`);
2955
+ }
2956
+ }
2957
+ function findRealAreaId(target, allFiles, config) {
2958
+ const targetLower = target.toLowerCase();
2959
+ for (const areaId of Object.keys(config.areas)) {
2960
+ if (areaId.toLowerCase() === targetLower || areaId.toLowerCase().includes(targetLower)) {
2961
+ return areaId;
2962
+ }
2963
+ }
2964
+ const detectedAreas = /* @__PURE__ */ new Set();
2965
+ for (const filePath of allFiles) {
2966
+ const areas2 = detectFileAreas(filePath, config);
2967
+ for (const areaId of areas2) {
2968
+ if (areaId.toLowerCase() === targetLower || areaId.toLowerCase().includes(targetLower)) {
2969
+ detectedAreas.add(areaId);
2970
+ }
2971
+ }
2972
+ }
2973
+ return detectedAreas.size > 0 ? [...detectedAreas][0] : null;
2974
+ }
2975
+ function getAvailableAreas(allFiles, config) {
2976
+ const areaCounts = /* @__PURE__ */ new Map();
2977
+ for (const filePath of allFiles) {
2978
+ const areas2 = detectFileAreas(filePath, config);
2979
+ for (const areaId of areas2) {
2980
+ areaCounts.set(areaId, (areaCounts.get(areaId) || 0) + 1);
2981
+ }
2982
+ }
2983
+ return [...areaCounts.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => b.count - a.count);
2984
+ }
2985
+ function formatAreaNotFound(target, availableAreas) {
2986
+ let out = `
2987
+ \u274C \xC1rea n\xE3o encontrada: "${target}"
2988
+
2989
+ `;
2990
+ if (availableAreas.length > 0) {
2991
+ out += `\u{1F4E6} \xC1reas dispon\xEDveis:
2992
+
2993
+ `;
2994
+ for (const { id, count } of availableAreas.slice(0, 15)) {
2995
+ out += ` ${id.padEnd(25)} ${count} arquivos
2996
+ `;
2997
+ }
2998
+ if (availableAreas.length > 15) {
2999
+ out += ` ... e mais ${availableAreas.length - 15}
3000
+ `;
3001
+ }
3002
+ out += `
3003
+ `;
3004
+ }
3005
+ out += `\u{1F4A1} Dicas:
3006
+ `;
3007
+ out += ` - Use o ID exato da \xE1rea (ex: ai-tool area meus-pets)
3008
+ `;
3009
+ out += ` - Use 'ai-tool areas' para listar todas as \xE1reas
3010
+ `;
3011
+ return out;
3012
+ }
3013
+ function getAllCodeFiles3(dir, files = [], baseDir = dir) {
3014
+ try {
3015
+ const entries = readdirSync4(dir);
3016
+ for (const entry of entries) {
3017
+ const fullPath = join6(dir, entry);
3018
+ if (IGNORED_DIRS2.has(entry) || entry.startsWith(".")) {
3019
+ continue;
3020
+ }
3021
+ try {
3022
+ const stat = statSync4(fullPath);
3023
+ if (stat.isDirectory()) {
3024
+ getAllCodeFiles3(fullPath, files, baseDir);
3025
+ } else {
3026
+ const ext = extname4(entry).toLowerCase();
3027
+ if (CODE_EXTENSIONS4.has(ext)) {
3028
+ const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
3029
+ files.push(relativePath);
3030
+ }
3031
+ }
3032
+ } catch {
3033
+ }
3034
+ }
3035
+ } catch {
3036
+ }
3037
+ return files;
3038
+ }
3039
+
3040
+ // src/commands/areas-init.ts
3041
+ import { readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3042
+ import { join as join7, extname as extname5 } from "path";
3043
+ var CODE_EXTENSIONS5 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
3044
+ var IGNORED_DIRS3 = /* @__PURE__ */ new Set([
3045
+ "node_modules",
3046
+ "dist",
3047
+ "build",
3048
+ ".git",
3049
+ ".next",
3050
+ ".cache",
3051
+ "coverage",
3052
+ ".turbo",
3053
+ ".vercel",
3054
+ ".analyze"
3055
+ ]);
3056
+ async function areasInit(options = {}) {
3057
+ const cwd = options.cwd || process.cwd();
3058
+ const force = options.force ?? false;
3059
+ try {
3060
+ if (configExists(cwd) && !force) {
3061
+ return `
3062
+ \u26A0\uFE0F Arquivo de configura\xE7\xE3o j\xE1 existe: .analyze/areas.config.json
3063
+
3064
+ Use --force para sobrescrever:
3065
+ ai-tool areas init --force
3066
+
3067
+ Ou edite manualmente o arquivo existente.
3068
+ `.trim();
3069
+ }
3070
+ const allFiles = getAllCodeFiles4(cwd);
3071
+ const currentConfig = readConfig(cwd);
3072
+ const areaCounts = /* @__PURE__ */ new Map();
3073
+ for (const filePath of allFiles) {
3074
+ const areas2 = detectFileAreas(filePath, currentConfig);
3075
+ for (const areaId of areas2) {
3076
+ if (!areaCounts.has(areaId)) {
3077
+ areaCounts.set(areaId, /* @__PURE__ */ new Set());
3078
+ }
3079
+ areaCounts.get(areaId).add(filePath);
3080
+ }
3081
+ }
3082
+ const generatedAreas = {};
3083
+ for (const [areaId, files] of areaCounts) {
3084
+ const patterns = inferPatternsFromFiles([...files]);
3085
+ generatedAreas[areaId] = {
3086
+ name: getAreaName(areaId, currentConfig),
3087
+ description: getAreaDescription(areaId, currentConfig),
3088
+ patterns
3089
+ };
3090
+ }
3091
+ const newConfig = {
3092
+ $schema: "./areas.schema.json",
3093
+ version: "1.0.0",
3094
+ areas: generatedAreas,
3095
+ descriptions: {},
3096
+ settings: {
3097
+ autoDetect: true,
3098
+ inferDescriptions: true,
3099
+ groupByCategory: true
3100
+ }
3101
+ };
3102
+ writeConfig(cwd, newConfig);
3103
+ const sortedAreas = [...areaCounts.entries()].sort((a, b) => b[1].size - a[1].size);
3104
+ let out = `
3105
+ \u2705 Arquivo criado: .analyze/areas.config.json
3106
+
3107
+ \u{1F4E6} \xC1reas detectadas: ${sortedAreas.length}
3108
+
3109
+ `;
3110
+ for (const [areaId, files] of sortedAreas.slice(0, 15)) {
3111
+ const name = getAreaName(areaId, newConfig);
3112
+ out += ` ${name.padEnd(25)} ${files.size} arquivos
3113
+ `;
3114
+ }
3115
+ if (sortedAreas.length > 15) {
3116
+ out += ` ... e mais ${sortedAreas.length - 15}
3117
+ `;
3118
+ }
3119
+ const unmappedCount = allFiles.filter(
3120
+ (f) => detectFileAreas(f, currentConfig).length === 0
3121
+ ).length;
3122
+ if (unmappedCount > 0) {
3123
+ out += `
3124
+ \u26A0\uFE0F ${unmappedCount} arquivos sem \xE1rea definida
3125
+ Use 'ai-tool areas' para ver detalhes
3126
+ `;
3127
+ }
3128
+ out += `
3129
+ \u{1F4A1} Edite o arquivo para:
3130
+ - Renomear \xE1reas (campo "name")
3131
+ - Adicionar descri\xE7\xF5es (campo "description")
3132
+ - Ajustar padr\xF5es (campo "patterns")
3133
+ - Adicionar/remover \xE1reas
3134
+ - Definir descri\xE7\xF5es espec\xEDficas de arquivos (campo "descriptions")
3135
+ `;
3136
+ return out.trim();
3137
+ } catch (error) {
3138
+ const message = error instanceof Error ? error.message : String(error);
3139
+ throw new Error(`Erro ao executar areas init: ${message}`);
3140
+ }
3141
+ }
3142
+ function inferPatternsFromFiles(files) {
3143
+ const patterns = /* @__PURE__ */ new Set();
3144
+ const folderGroups = /* @__PURE__ */ new Map();
3145
+ for (const file of files) {
3146
+ const parts = file.split("/");
3147
+ if (parts.length > 1) {
3148
+ const folder = parts.slice(0, Math.min(3, parts.length - 1)).join("/");
3149
+ if (!folderGroups.has(folder)) {
3150
+ folderGroups.set(folder, []);
3151
+ }
3152
+ folderGroups.get(folder).push(file);
3153
+ }
3154
+ }
3155
+ for (const [folder, folderFiles] of folderGroups) {
3156
+ if (folderFiles.length >= 2) {
3157
+ patterns.add(`${folder}/**`);
3158
+ } else {
3159
+ patterns.add(folderFiles[0]);
3160
+ }
3161
+ }
3162
+ for (const file of files) {
3163
+ if (!file.includes("/")) {
3164
+ patterns.add(file);
3165
+ }
3166
+ }
3167
+ return [...patterns].sort();
3168
+ }
3169
+ function getAllCodeFiles4(dir, files = [], baseDir = dir) {
3170
+ try {
3171
+ const entries = readdirSync5(dir);
3172
+ for (const entry of entries) {
3173
+ const fullPath = join7(dir, entry);
3174
+ if (IGNORED_DIRS3.has(entry) || entry.startsWith(".")) {
3175
+ continue;
3176
+ }
3177
+ try {
3178
+ const stat = statSync5(fullPath);
3179
+ if (stat.isDirectory()) {
3180
+ getAllCodeFiles4(fullPath, files, baseDir);
3181
+ } else {
3182
+ const ext = extname5(entry).toLowerCase();
3183
+ if (CODE_EXTENSIONS5.has(ext)) {
3184
+ const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
3185
+ files.push(relativePath);
3186
+ }
3187
+ }
3188
+ } catch {
3189
+ }
3190
+ }
3191
+ } catch {
3192
+ }
3193
+ return files;
3194
+ }
3195
+
1834
3196
  // src/index.ts
1835
- var VERSION = "0.3.1";
3197
+ var VERSION = "0.4.0";
1836
3198
 
1837
3199
  export {
1838
3200
  detectCategory,
1839
3201
  categoryIcons,
1840
3202
  isEntryPoint,
1841
3203
  isCodeFile,
3204
+ isFirebaseProject,
3205
+ hasFirebaseFunctions,
3206
+ isExportedCloudFunction,
3207
+ filterCloudFunctionsFalsePositives,
3208
+ clearFirebaseCache,
1842
3209
  getCacheDir,
1843
3210
  isCacheValid,
1844
3211
  invalidateCache,
@@ -1848,5 +3215,23 @@ export {
1848
3215
  impact,
1849
3216
  suggest,
1850
3217
  context,
3218
+ configExists,
3219
+ readConfig,
3220
+ writeConfig,
3221
+ setArea,
3222
+ removeArea,
3223
+ setFileDescription,
3224
+ getFileDescription,
3225
+ FOLDER_PATTERNS,
3226
+ KEYWORD_PATTERNS,
3227
+ AREA_NAMES,
3228
+ AREA_DESCRIPTIONS,
3229
+ detectFileAreas,
3230
+ getAreaName,
3231
+ getAreaDescription,
3232
+ inferFileDescription,
3233
+ areas,
3234
+ area,
3235
+ areasInit,
1851
3236
  VERSION
1852
3237
  };