@justmpm/ai-tool 0.4.2 → 0.5.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.
@@ -133,6 +133,64 @@ function isCodeFile(filePath) {
133
133
  }
134
134
 
135
135
  // src/formatters/text.ts
136
+ function formatMapSummary(result, areasInfo) {
137
+ let out = "";
138
+ out += `\u{1F4CA} ${result.summary.totalFiles} arquivos | ${result.summary.totalFolders} pastas
139
+ `;
140
+ const catOrder = [
141
+ "component",
142
+ "hook",
143
+ "page",
144
+ "service",
145
+ "util",
146
+ "type",
147
+ "config",
148
+ "test",
149
+ "layout",
150
+ "route",
151
+ "store",
152
+ "other"
153
+ ];
154
+ const catParts = [];
155
+ for (const cat of catOrder) {
156
+ const count = result.summary.categories[cat];
157
+ if (count) {
158
+ catParts.push(`${count} ${cat}s`);
159
+ }
160
+ }
161
+ out += ` ${catParts.join(", ")}
162
+ `;
163
+ if (areasInfo && areasInfo.total > 0) {
164
+ out += `
165
+ \u{1F5C2}\uFE0F \xC1reas: ${areasInfo.names.join(", ")}
166
+ `;
167
+ }
168
+ out += `
169
+ `;
170
+ if (result.circularDependencies.length > 0) {
171
+ out += `\u26A0\uFE0F ${result.circularDependencies.length} depend\xEAncia(s) circular(es) detectada(s)
172
+ `;
173
+ out += ` \u2192 Use impact <arquivo> para investigar
174
+
175
+ `;
176
+ }
177
+ if (areasInfo && areasInfo.unmappedCount > 0) {
178
+ out += `\u26A0\uFE0F ${areasInfo.unmappedCount} arquivo(s) sem \xE1rea definida
179
+ `;
180
+ out += ` \u2192 Use areas init para configurar
181
+
182
+ `;
183
+ }
184
+ out += `\u{1F4D6} Pr\xF3ximos passos:
185
+ `;
186
+ out += ` \u2192 area <nome> - ver arquivos de uma \xE1rea
187
+ `;
188
+ out += ` \u2192 suggest <arquivo> - o que ler antes de editar
189
+ `;
190
+ out += ` \u2192 context <arquivo> - ver API de um arquivo
191
+ `;
192
+ return out;
193
+ }
136
194
  function formatMapText(result) {
137
195
  let out = "";
138
196
  out += `
@@ -702,7 +760,7 @@ function formatAreasText(result) {
702
760
  `;
703
761
  out += `\u{1F4A1} Use 'ai-tool area <nome>' para ver detalhes de uma \xE1rea
704
762
  `;
705
- out += ` Exemplo: ai-tool area meus-pets
763
+ out += ` Exemplo: ai-tool area auth
706
764
  `;
707
765
  return out;
708
766
  }
@@ -1014,1238 +1072,98 @@ function invalidateCache(cwd) {
1014
1072
  }
1015
1073
  }
1016
1074
 
1017
- // src/commands/map.ts
1018
- async function map(options = {}) {
1019
- const cwd = options.cwd || process.cwd();
1020
- const format = options.format || "text";
1021
- const useCache = options.cache !== false;
1022
- if (useCache && isCacheValid(cwd)) {
1023
- const cached = getCachedMapResult(cwd);
1024
- if (cached) {
1025
- const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
1026
- if (format === "json") {
1027
- return JSON.stringify(result, null, 2);
1028
- }
1029
- return formatMapText(result) + "\n\n\u{1F4E6} (resultado do cache)";
1030
- }
1031
- }
1032
- try {
1033
- const { getStructure, useGraph } = await skott({
1034
- cwd,
1035
- includeBaseDir: false,
1036
- dependencyTracking: {
1037
- thirdParty: options.trackDependencies ?? false,
1038
- builtin: false,
1039
- typeOnly: false
1040
- },
1041
- fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1042
- tsConfigPath: "tsconfig.json"
1043
- });
1044
- const structure = getStructure();
1045
- const graphApi = useGraph();
1046
- const { findCircularDependencies } = graphApi;
1047
- if (useCache) {
1048
- const graphData = {
1049
- graph: structure.graph,
1050
- files: Object.keys(structure.graph),
1051
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1052
- };
1053
- cacheGraph(cwd, graphData);
1054
- }
1055
- const files = Object.entries(structure.graph).map(([path]) => ({
1056
- path,
1057
- category: detectCategory(path),
1058
- size: 0
1059
- // Skott não fornece tamanho
1060
- }));
1061
- const folderMap = /* @__PURE__ */ new Map();
1062
- for (const file of files) {
1063
- const parts = file.path.split("/");
1064
- if (parts.length > 1) {
1065
- const folder = parts.slice(0, -1).join("/");
1066
- if (!folderMap.has(folder)) {
1067
- folderMap.set(folder, {
1068
- path: folder,
1069
- fileCount: 0,
1070
- categories: {}
1071
- });
1072
- }
1073
- const stats = folderMap.get(folder);
1074
- stats.fileCount++;
1075
- stats.categories[file.category] = (stats.categories[file.category] || 0) + 1;
1076
- }
1077
- }
1078
- const categories = {};
1079
- for (const file of files) {
1080
- categories[file.category] = (categories[file.category] || 0) + 1;
1081
- }
1082
- const circular = findCircularDependencies();
1083
- const result = {
1084
- version: "1.0.0",
1085
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1086
- cwd,
1087
- summary: {
1088
- totalFiles: files.length,
1089
- totalFolders: folderMap.size,
1090
- categories
1091
- },
1092
- folders: Array.from(folderMap.values()),
1093
- files,
1094
- circularDependencies: circular
1095
- };
1096
- if (useCache) {
1097
- cacheMapResult(cwd, result);
1098
- updateCacheMeta(cwd);
1099
- }
1100
- if (format === "json") {
1101
- return JSON.stringify(result, null, 2);
1102
- }
1103
- return formatMapText(result);
1104
- } catch (error) {
1105
- const message = error instanceof Error ? error.message : String(error);
1106
- throw new Error(`Erro ao executar map: ${message}`);
1075
+ // src/areas/config.ts
1076
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1077
+ import { join as join3 } from "path";
1078
+ var CONFIG_FILE = "areas.config.json";
1079
+ var DEFAULT_CONFIG = {
1080
+ $schema: "./areas.schema.json",
1081
+ version: "1.0.0",
1082
+ areas: {},
1083
+ descriptions: {},
1084
+ settings: {
1085
+ autoDetect: true,
1086
+ inferDescriptions: true,
1087
+ groupByCategory: true
1107
1088
  }
1089
+ };
1090
+ function getConfigPath(cwd) {
1091
+ return join3(cwd, CACHE_DIR, CONFIG_FILE);
1108
1092
  }
1109
-
1110
- // src/commands/dead.ts
1111
- import { execSync } from "child_process";
1112
- async function dead(options = {}) {
1113
- const cwd = options.cwd || process.cwd();
1114
- const format = options.format || "text";
1115
- const useCache = options.cache !== false;
1116
- if (useCache && isCacheValid(cwd)) {
1117
- const cached = getCachedDeadResult(cwd);
1118
- if (cached) {
1119
- const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
1120
- if (format === "json") {
1121
- return JSON.stringify(result, null, 2);
1122
- }
1123
- return formatDeadText(result) + "\n\n\u{1F4E6} (resultado do cache)";
1124
- }
1093
+ function configExists(cwd) {
1094
+ return existsSync3(getConfigPath(cwd));
1095
+ }
1096
+ function readConfig(cwd) {
1097
+ const configPath = getConfigPath(cwd);
1098
+ if (!existsSync3(configPath)) {
1099
+ return DEFAULT_CONFIG;
1125
1100
  }
1126
1101
  try {
1127
- let knipOutput;
1128
- try {
1129
- const output = execSync("npx knip --reporter=json", {
1130
- cwd,
1131
- encoding: "utf-8",
1132
- maxBuffer: 50 * 1024 * 1024,
1133
- stdio: ["pipe", "pipe", "pipe"]
1134
- });
1135
- knipOutput = JSON.parse(output || "{}");
1136
- } catch (execError) {
1137
- const error = execError;
1138
- if (error.stdout) {
1139
- try {
1140
- knipOutput = JSON.parse(error.stdout);
1141
- } catch {
1142
- knipOutput = {};
1143
- }
1144
- } else {
1145
- knipOutput = {};
1146
- }
1147
- }
1148
- const rawFiles = knipOutput.files || [];
1149
- const { filtered: filteredFiles, excluded: excludedFunctions } = filterCloudFunctionsFalsePositives(rawFiles, cwd);
1150
- const deadFiles = filteredFiles.map((file) => ({
1151
- path: file,
1152
- category: detectCategory(file),
1153
- type: "file"
1154
- }));
1155
- const hasFirebase = hasFirebaseFunctions(cwd);
1156
- const firebaseInfo = hasFirebase ? { detected: true, excludedCount: excludedFunctions.length } : { detected: false, excludedCount: 0 };
1157
- const deadExports = [];
1158
- if (knipOutput.issues) {
1159
- for (const issue of knipOutput.issues) {
1160
- if (issue.symbol && issue.symbolType === "export") {
1161
- deadExports.push({
1162
- file: issue.file,
1163
- export: issue.symbol
1164
- });
1165
- }
1166
- }
1167
- }
1168
- const deadDependencies = [
1169
- ...knipOutput.dependencies || [],
1170
- ...knipOutput.devDependencies || []
1171
- ];
1172
- const result = {
1173
- version: "1.0.0",
1174
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1175
- cwd,
1176
- summary: {
1177
- totalDead: deadFiles.length + deadExports.length + deadDependencies.length,
1178
- byType: {
1179
- files: deadFiles.length,
1180
- exports: deadExports.length,
1181
- dependencies: deadDependencies.length
1182
- }
1183
- },
1184
- files: deadFiles,
1185
- exports: deadExports,
1186
- dependencies: deadDependencies,
1187
- // Metadata sobre filtros aplicados
1188
- filters: {
1189
- firebase: firebaseInfo,
1190
- excludedFiles: excludedFunctions
1102
+ const content = readFileSync3(configPath, "utf-8");
1103
+ const config = JSON.parse(content);
1104
+ return {
1105
+ ...DEFAULT_CONFIG,
1106
+ ...config,
1107
+ settings: {
1108
+ ...DEFAULT_CONFIG.settings,
1109
+ ...config.settings
1191
1110
  }
1192
1111
  };
1193
- if (useCache) {
1194
- cacheDeadResult(cwd, result);
1195
- updateCacheMeta(cwd);
1196
- }
1197
- if (format === "json") {
1198
- return JSON.stringify(result, null, 2);
1199
- }
1200
- return formatDeadText(result);
1201
- } catch (error) {
1202
- const message = error instanceof Error ? error.message : String(error);
1203
- throw new Error(`Erro ao executar dead: ${message}`);
1112
+ } catch {
1113
+ return DEFAULT_CONFIG;
1204
1114
  }
1205
1115
  }
1206
- async function deadFix(options = {}) {
1207
- const cwd = options.cwd || process.cwd();
1208
- try {
1209
- const output = execSync("npx knip --fix", {
1210
- cwd,
1211
- encoding: "utf-8",
1212
- maxBuffer: 50 * 1024 * 1024
1213
- });
1214
- return `\u2705 Fix executado com sucesso!
1215
-
1216
- ${output}`;
1217
- } catch (error) {
1218
- const message = error instanceof Error ? error.message : String(error);
1219
- throw new Error(`Erro ao executar fix: ${message}`);
1116
+ function writeConfig(cwd, config) {
1117
+ const cacheDir = join3(cwd, CACHE_DIR);
1118
+ if (!existsSync3(cacheDir)) {
1119
+ mkdirSync2(cacheDir, { recursive: true });
1220
1120
  }
1121
+ const configPath = getConfigPath(cwd);
1122
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
1221
1123
  }
1222
-
1223
- // src/commands/impact.ts
1224
- import skott2 from "skott";
1225
- async function impact(target, options = {}) {
1226
- const cwd = options.cwd || process.cwd();
1227
- const format = options.format || "text";
1228
- const useCache = options.cache !== false;
1229
- if (!target) {
1230
- throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
1231
- }
1232
- try {
1233
- let graph;
1234
- let allFiles = [];
1235
- let findCircular = () => [];
1236
- let fromCache = false;
1237
- if (useCache && isCacheValid(cwd)) {
1238
- const cached = getCachedGraph(cwd);
1239
- if (cached) {
1240
- graph = cached.graph;
1241
- allFiles = cached.files;
1242
- findCircular = () => findCircularFromGraph(graph);
1243
- fromCache = true;
1244
- }
1245
- }
1246
- if (!graph) {
1247
- const { getStructure, useGraph } = await skott2({
1248
- cwd,
1249
- includeBaseDir: false,
1250
- dependencyTracking: {
1251
- thirdParty: false,
1252
- builtin: false,
1253
- typeOnly: false
1254
- },
1255
- fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1256
- tsConfigPath: "tsconfig.json"
1257
- });
1258
- const structure = getStructure();
1259
- const graphApi = useGraph();
1260
- graph = structure.graph;
1261
- allFiles = Object.keys(graph);
1262
- findCircular = () => graphApi.findCircularDependencies();
1263
- if (useCache) {
1264
- cacheGraph(cwd, {
1265
- graph,
1266
- files: allFiles,
1267
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1268
- });
1269
- updateCacheMeta(cwd);
1270
- }
1271
- }
1272
- const targetPath = findTargetFile(target, allFiles);
1273
- if (!targetPath) {
1274
- return formatNotFound(target, allFiles);
1275
- }
1276
- const { directUpstream, indirectUpstream, directDownstream, indirectDownstream } = calculateDependencies(targetPath, graph);
1277
- const dependingOn = [...directUpstream, ...indirectUpstream];
1278
- const dependencies = [...directDownstream, ...indirectDownstream];
1279
- const risks = detectRisks(targetPath, dependingOn, dependencies, findCircular);
1280
- const suggestions = generateSuggestions(dependingOn, dependencies, risks);
1281
- const result = {
1282
- version: "1.0.0",
1283
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1284
- target: targetPath,
1285
- category: detectCategory(targetPath),
1286
- upstream: {
1287
- direct: directUpstream.map(toImpactFile(true)),
1288
- indirect: indirectUpstream.map(toImpactFile(false)),
1289
- total: dependingOn.length
1290
- },
1291
- downstream: {
1292
- direct: directDownstream.map(toImpactFile(true)),
1293
- indirect: indirectDownstream.map(toImpactFile(false)),
1294
- total: dependencies.length
1295
- },
1296
- risks,
1297
- suggestions
1298
- };
1299
- if (format === "json") {
1300
- return JSON.stringify(result, null, 2);
1301
- }
1302
- const output = formatImpactText(result);
1303
- return fromCache ? output + "\n\n\u{1F4E6} (grafo do cache)" : output;
1304
- } catch (error) {
1305
- const message = error instanceof Error ? error.message : String(error);
1306
- throw new Error(`Erro ao executar impact: ${message}`);
1307
- }
1124
+ function setArea(cwd, id, area2) {
1125
+ const config = readConfig(cwd);
1126
+ config.areas[id] = area2;
1127
+ writeConfig(cwd, config);
1308
1128
  }
1309
- function calculateDependencies(targetPath, graph) {
1310
- const targetNode = graph[targetPath];
1311
- const directDownstream = targetNode ? targetNode.adjacentTo : [];
1312
- const allDownstream = /* @__PURE__ */ new Set();
1313
- const visited = /* @__PURE__ */ new Set();
1314
- function collectDownstream(file) {
1315
- if (visited.has(file)) return;
1316
- visited.add(file);
1317
- const node = graph[file];
1318
- if (node) {
1319
- for (const dep of node.adjacentTo) {
1320
- allDownstream.add(dep);
1321
- collectDownstream(dep);
1322
- }
1323
- }
1324
- }
1325
- collectDownstream(targetPath);
1326
- const indirectDownstream = Array.from(allDownstream).filter(
1327
- (f) => !directDownstream.includes(f)
1328
- );
1329
- const directUpstream = [];
1330
- const allUpstream = /* @__PURE__ */ new Set();
1331
- for (const [file, node] of Object.entries(graph)) {
1332
- if (node.adjacentTo.includes(targetPath)) {
1333
- directUpstream.push(file);
1334
- }
1335
- }
1336
- visited.clear();
1337
- function collectUpstream(file) {
1338
- if (visited.has(file)) return;
1339
- visited.add(file);
1340
- for (const [f, node] of Object.entries(graph)) {
1341
- if (node.adjacentTo.includes(file) && !visited.has(f)) {
1342
- allUpstream.add(f);
1343
- collectUpstream(f);
1344
- }
1345
- }
1346
- }
1347
- for (const file of directUpstream) {
1348
- collectUpstream(file);
1349
- }
1350
- const indirectUpstream = Array.from(allUpstream).filter(
1351
- (f) => !directUpstream.includes(f)
1352
- );
1353
- return {
1354
- directUpstream,
1355
- indirectUpstream,
1356
- directDownstream,
1357
- indirectDownstream
1358
- };
1129
+ function removeArea(cwd, id) {
1130
+ const config = readConfig(cwd);
1131
+ delete config.areas[id];
1132
+ writeConfig(cwd, config);
1359
1133
  }
1360
- function findCircularFromGraph(graph) {
1361
- const cycles = [];
1362
- const visited = /* @__PURE__ */ new Set();
1363
- const stack = /* @__PURE__ */ new Set();
1364
- const path = [];
1365
- function dfs(node) {
1366
- if (stack.has(node)) {
1367
- const cycleStart = path.indexOf(node);
1368
- if (cycleStart !== -1) {
1369
- cycles.push(path.slice(cycleStart));
1370
- }
1371
- return;
1372
- }
1373
- if (visited.has(node)) return;
1374
- visited.add(node);
1375
- stack.add(node);
1376
- path.push(node);
1377
- const nodeData = graph[node];
1378
- if (nodeData) {
1379
- for (const dep of nodeData.adjacentTo) {
1380
- dfs(dep);
1381
- }
1382
- }
1383
- path.pop();
1384
- stack.delete(node);
1385
- }
1386
- for (const node of Object.keys(graph)) {
1387
- dfs(node);
1134
+ function setFileDescription(cwd, filePath, description) {
1135
+ const config = readConfig(cwd);
1136
+ if (!config.descriptions) {
1137
+ config.descriptions = {};
1388
1138
  }
1389
- return cycles;
1139
+ config.descriptions[filePath] = description;
1140
+ writeConfig(cwd, config);
1390
1141
  }
1391
- function findTargetFile(target, allFiles) {
1392
- const normalizedTarget = target.replace(/\\/g, "/");
1393
- if (allFiles.includes(normalizedTarget)) {
1394
- return normalizedTarget;
1395
- }
1396
- const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
1397
- const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1398
- const matches = [];
1399
- for (const file of allFiles) {
1400
- const fileName = file.split("/").pop()?.toLowerCase() || "";
1401
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1402
- if (fileNameNoExt === targetNameNoExt) {
1403
- matches.unshift(file);
1404
- } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
1405
- matches.push(file);
1406
- }
1407
- }
1408
- if (matches.length === 1) {
1409
- return matches[0];
1410
- }
1411
- if (matches.length > 1) {
1412
- return matches[0];
1413
- }
1414
- return null;
1142
+ function getFileDescription(cwd, filePath) {
1143
+ const config = readConfig(cwd);
1144
+ return config.descriptions?.[filePath];
1415
1145
  }
1416
- function formatNotFound(target, allFiles) {
1417
- const normalizedTarget = target.toLowerCase();
1418
- const similar = allFiles.filter((f) => {
1419
- const fileName = f.split("/").pop()?.toLowerCase() || "";
1420
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1421
- return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance(fileNameNoExt, normalizedTarget) <= 3;
1422
- }).slice(0, 5);
1423
- let out = `\u274C Arquivo n\xE3o encontrado no \xEDndice: "${target}"
1424
1146
 
1425
- `;
1426
- out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
1427
-
1428
- `;
1429
- if (similar.length > 0) {
1430
- out += `\u{1F4DD} Arquivos com nome similar:
1431
- `;
1432
- for (const s of similar) {
1433
- out += ` \u2022 ${s}
1434
- `;
1435
- }
1436
- out += `
1437
- `;
1438
- }
1439
- out += `\u{1F4A1} Dicas:
1440
- `;
1441
- out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
1442
- `;
1443
- out += ` \u2022 Ou apenas o nome do arquivo: Header
1444
- `;
1445
- out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
1446
- `;
1447
- return out;
1448
- }
1449
- function toImpactFile(isDirect) {
1450
- return (path) => ({
1451
- path,
1452
- category: detectCategory(path),
1453
- isDirect
1454
- });
1455
- }
1456
- function detectRisks(targetPath, upstream, downstream, findCircular) {
1457
- const risks = [];
1458
- if (upstream.length >= 15) {
1459
- risks.push({
1460
- type: "widely-used",
1461
- severity: "high",
1462
- message: `Arquivo CR\xCDTICO: usado por ${upstream.length} arquivos \xFAnicos`
1463
- });
1464
- } else if (upstream.length >= 5) {
1465
- risks.push({
1466
- type: "widely-used",
1467
- severity: "medium",
1468
- message: `Arquivo compartilhado: usado por ${upstream.length} arquivos \xFAnicos`
1469
- });
1470
- }
1471
- if (downstream.length >= 20) {
1472
- risks.push({
1473
- type: "deep-chain",
1474
- severity: "medium",
1475
- message: `Arquivo importa ${downstream.length} depend\xEAncias (considere dividir)`
1476
- });
1477
- } else if (downstream.length >= 10) {
1478
- risks.push({
1479
- type: "deep-chain",
1480
- severity: "low",
1481
- message: `Arquivo importa ${downstream.length} depend\xEAncias`
1482
- });
1483
- }
1484
- const circular = findCircular();
1485
- const targetCircular = circular.filter((cycle) => cycle.includes(targetPath));
1486
- if (targetCircular.length > 0) {
1487
- risks.push({
1488
- type: "circular",
1489
- severity: "medium",
1490
- message: `Envolvido em ${targetCircular.length} depend\xEAncia${targetCircular.length > 1 ? "s" : ""} circular${targetCircular.length > 1 ? "es" : ""}`
1491
- });
1492
- }
1493
- return risks;
1494
- }
1495
- function generateSuggestions(upstream, downstream, risks) {
1496
- const suggestions = [];
1497
- if (upstream.length > 0) {
1498
- suggestions.push(
1499
- `Verifique os ${upstream.length} arquivo(s) que importam este antes de modificar`
1500
- );
1501
- }
1502
- if (upstream.length >= 10) {
1503
- suggestions.push(`Considere criar testes para garantir que mudan\xE7as n\xE3o quebrem dependentes`);
1504
- }
1505
- if (downstream.length > 0) {
1506
- suggestions.push(`Teste as ${downstream.length} depend\xEAncia(s) ap\xF3s mudan\xE7as`);
1507
- }
1508
- if (risks.some((r) => r.type === "circular")) {
1509
- suggestions.push(`Considere resolver as depend\xEAncias circulares antes de refatorar`);
1510
- }
1511
- if (risks.some((r) => r.type === "widely-used" && r.severity === "high")) {
1512
- suggestions.push(`Este arquivo \xE9 cr\xEDtico - planeje mudan\xE7as com cuidado`);
1513
- }
1514
- return suggestions;
1515
- }
1516
- function levenshteinDistance(a, b) {
1517
- const matrix = [];
1518
- for (let i = 0; i <= b.length; i++) {
1519
- matrix[i] = [i];
1520
- }
1521
- for (let j = 0; j <= a.length; j++) {
1522
- matrix[0][j] = j;
1523
- }
1524
- for (let i = 1; i <= b.length; i++) {
1525
- for (let j = 1; j <= a.length; j++) {
1526
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
1527
- matrix[i][j] = matrix[i - 1][j - 1];
1528
- } else {
1529
- matrix[i][j] = Math.min(
1530
- matrix[i - 1][j - 1] + 1,
1531
- matrix[i][j - 1] + 1,
1532
- matrix[i - 1][j] + 1
1533
- );
1534
- }
1535
- }
1536
- }
1537
- return matrix[b.length][a.length];
1538
- }
1539
-
1540
- // src/commands/suggest.ts
1541
- import skott3 from "skott";
1542
- async function suggest(target, options = {}) {
1543
- const cwd = options.cwd || process.cwd();
1544
- const format = options.format || "text";
1545
- const useCache = options.cache !== false;
1546
- const limit = options.limit || 10;
1547
- if (!target) {
1548
- throw new Error("Target e obrigatorio. Exemplo: ai-tool suggest src/components/Button.tsx");
1549
- }
1550
- try {
1551
- let graph;
1552
- let allFiles = [];
1553
- let fromCache = false;
1554
- if (useCache && isCacheValid(cwd)) {
1555
- const cached = getCachedGraph(cwd);
1556
- if (cached) {
1557
- graph = cached.graph;
1558
- allFiles = cached.files;
1559
- fromCache = true;
1560
- }
1561
- }
1562
- if (!graph) {
1563
- const { getStructure } = await skott3({
1564
- cwd,
1565
- includeBaseDir: false,
1566
- dependencyTracking: {
1567
- thirdParty: false,
1568
- builtin: false,
1569
- typeOnly: false
1570
- },
1571
- fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1572
- tsConfigPath: "tsconfig.json"
1573
- });
1574
- const structure = getStructure();
1575
- graph = structure.graph;
1576
- allFiles = Object.keys(graph);
1577
- if (useCache) {
1578
- cacheGraph(cwd, {
1579
- graph,
1580
- files: allFiles,
1581
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1582
- });
1583
- updateCacheMeta(cwd);
1584
- }
1585
- }
1586
- const targetPath = findTargetFile2(target, allFiles);
1587
- if (!targetPath) {
1588
- return formatNotFound2(target, allFiles);
1589
- }
1590
- const suggestions = collectSuggestions(targetPath, graph, allFiles, limit);
1591
- const result = {
1592
- version: "1.0.0",
1593
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1594
- target: targetPath,
1595
- category: detectCategory(targetPath),
1596
- suggestions
1597
- };
1598
- if (format === "json") {
1599
- return JSON.stringify(result, null, 2);
1600
- }
1601
- const output = formatSuggestText(result);
1602
- return fromCache ? output + "\n\n(grafo do cache)" : output;
1603
- } catch (error) {
1604
- const message = error instanceof Error ? error.message : String(error);
1605
- throw new Error(`Erro ao executar suggest: ${message}`);
1606
- }
1607
- }
1608
- function collectSuggestions(targetPath, graph, allFiles, limit) {
1609
- const suggestions = [];
1610
- const addedPaths = /* @__PURE__ */ new Set();
1611
- const targetNode = graph[targetPath];
1612
- if (!targetNode) {
1613
- return suggestions;
1614
- }
1615
- for (const dep of targetNode.adjacentTo) {
1616
- if (addedPaths.has(dep)) continue;
1617
- addedPaths.add(dep);
1618
- const category = detectCategory(dep);
1619
- if (category === "type") {
1620
- suggestions.push({
1621
- path: dep,
1622
- category,
1623
- reason: "Define tipos usados",
1624
- priority: "critical"
1625
- });
1626
- } else {
1627
- suggestions.push({
1628
- path: dep,
1629
- category,
1630
- reason: "Importado diretamente",
1631
- priority: "high"
1632
- });
1633
- }
1634
- }
1635
- const upstream = findUpstream(targetPath, graph);
1636
- const upstreamLimited = upstream.slice(0, 5);
1637
- for (const file of upstreamLimited) {
1638
- if (addedPaths.has(file)) continue;
1639
- addedPaths.add(file);
1640
- suggestions.push({
1641
- path: file,
1642
- category: detectCategory(file),
1643
- reason: "Usa este arquivo",
1644
- priority: "medium"
1645
- });
1646
- }
1647
- const relatedTests = findRelatedTests(targetPath, allFiles);
1648
- for (const testFile of relatedTests) {
1649
- if (addedPaths.has(testFile)) continue;
1650
- addedPaths.add(testFile);
1651
- suggestions.push({
1652
- path: testFile,
1653
- category: "test",
1654
- reason: "Testa este arquivo",
1655
- priority: "low"
1656
- });
1657
- }
1658
- const priorityOrder = {
1659
- critical: 0,
1660
- high: 1,
1661
- medium: 2,
1662
- low: 3
1663
- };
1664
- suggestions.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
1665
- return suggestions.slice(0, limit);
1666
- }
1667
- function findUpstream(targetPath, graph) {
1668
- const upstream = [];
1669
- for (const [file, node] of Object.entries(graph)) {
1670
- if (node.adjacentTo.includes(targetPath)) {
1671
- upstream.push(file);
1672
- }
1673
- }
1674
- return upstream;
1675
- }
1676
- function findRelatedTests(targetPath, allFiles) {
1677
- const targetName = targetPath.split("/").pop() || "";
1678
- const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1679
- const tests = [];
1680
- for (const file of allFiles) {
1681
- const fileName = file.split("/").pop() || "";
1682
- if (fileName.includes(".test.") || fileName.includes(".spec.") || file.includes("/__tests__/")) {
1683
- const testNameNoExt = fileName.replace(/\.(test|spec)\..*$/, "").replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1684
- if (testNameNoExt.toLowerCase() === targetNameNoExt.toLowerCase()) {
1685
- tests.push(file);
1686
- }
1687
- }
1688
- }
1689
- return tests;
1690
- }
1691
- function findTargetFile2(target, allFiles) {
1692
- const normalizedTarget = target.replace(/\\/g, "/");
1693
- if (allFiles.includes(normalizedTarget)) {
1694
- return normalizedTarget;
1695
- }
1696
- const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
1697
- const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1698
- const matches = [];
1699
- for (const file of allFiles) {
1700
- const fileName = file.split("/").pop()?.toLowerCase() || "";
1701
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1702
- if (fileNameNoExt === targetNameNoExt) {
1703
- matches.unshift(file);
1704
- } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
1705
- matches.push(file);
1706
- }
1707
- }
1708
- if (matches.length === 1) {
1709
- return matches[0];
1710
- }
1711
- if (matches.length > 1) {
1712
- return matches[0];
1713
- }
1714
- return null;
1715
- }
1716
- function formatNotFound2(target, allFiles) {
1717
- const normalizedTarget = target.toLowerCase();
1718
- const similar = allFiles.filter((f) => {
1719
- const fileName = f.split("/").pop()?.toLowerCase() || "";
1720
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1721
- return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance2(fileNameNoExt, normalizedTarget) <= 3;
1722
- }).slice(0, 5);
1723
- let out = `Arquivo nao encontrado no indice: "${target}"
1724
-
1725
- `;
1726
- out += `Total de arquivos indexados: ${allFiles.length}
1727
-
1728
- `;
1729
- if (similar.length > 0) {
1730
- out += `Arquivos com nome similar:
1731
- `;
1732
- for (const s of similar) {
1733
- out += ` - ${s}
1734
- `;
1735
- }
1736
- out += `
1737
- `;
1738
- }
1739
- out += `Dicas:
1740
- `;
1741
- out += ` - Use o caminho relativo: src/components/Header.tsx
1742
- `;
1743
- out += ` - Ou apenas o nome do arquivo: Header
1744
- `;
1745
- out += ` - Verifique se o arquivo esta em uma pasta incluida no scan
1746
- `;
1747
- return out;
1748
- }
1749
- function levenshteinDistance2(a, b) {
1750
- const matrix = [];
1751
- for (let i = 0; i <= b.length; i++) {
1752
- matrix[i] = [i];
1753
- }
1754
- for (let j = 0; j <= a.length; j++) {
1755
- matrix[0][j] = j;
1756
- }
1757
- for (let i = 1; i <= b.length; i++) {
1758
- for (let j = 1; j <= a.length; j++) {
1759
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
1760
- matrix[i][j] = matrix[i - 1][j - 1];
1761
- } else {
1762
- matrix[i][j] = Math.min(
1763
- matrix[i - 1][j - 1] + 1,
1764
- matrix[i][j - 1] + 1,
1765
- matrix[i - 1][j] + 1
1766
- );
1767
- }
1768
- }
1769
- }
1770
- return matrix[b.length][a.length];
1771
- }
1772
-
1773
- // src/commands/context.ts
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";
1776
-
1777
- // src/ts/extractor.ts
1778
- import { Project, SyntaxKind } from "ts-morph";
1779
- function createProject(cwd) {
1780
- return new Project({
1781
- tsConfigFilePath: `${cwd}/tsconfig.json`,
1782
- skipAddingFilesFromTsConfig: true
1783
- });
1784
- }
1785
- function addSourceFile(project, filePath) {
1786
- return project.addSourceFileAtPath(filePath);
1787
- }
1788
- function extractImports(sourceFile) {
1789
- const imports = [];
1790
- for (const importDecl of sourceFile.getImportDeclarations()) {
1791
- const specifiers = [];
1792
- const defaultImport = importDecl.getDefaultImport();
1793
- if (defaultImport) {
1794
- specifiers.push(defaultImport.getText());
1795
- }
1796
- for (const namedImport of importDecl.getNamedImports()) {
1797
- const alias = namedImport.getAliasNode();
1798
- if (alias) {
1799
- specifiers.push(`${namedImport.getName()} as ${alias.getText()}`);
1800
- } else {
1801
- specifiers.push(namedImport.getName());
1802
- }
1803
- }
1804
- const namespaceImport = importDecl.getNamespaceImport();
1805
- if (namespaceImport) {
1806
- specifiers.push(`* as ${namespaceImport.getText()}`);
1807
- }
1808
- imports.push({
1809
- source: importDecl.getModuleSpecifierValue(),
1810
- specifiers,
1811
- isTypeOnly: importDecl.isTypeOnly()
1812
- });
1813
- }
1814
- return imports;
1815
- }
1816
- function getJsDocDescription(node) {
1817
- const jsDocs = node.getChildrenOfKind(SyntaxKind.JSDoc);
1818
- if (jsDocs.length === 0) return void 0;
1819
- const firstJsDoc = jsDocs[0];
1820
- const comment = firstJsDoc.getComment();
1821
- if (typeof comment === "string") {
1822
- return comment.trim() || void 0;
1823
- }
1824
- if (Array.isArray(comment)) {
1825
- const text = comment.filter((c) => c !== void 0).map((c) => c.getText()).join("").trim();
1826
- return text || void 0;
1827
- }
1828
- return void 0;
1829
- }
1830
- function extractParams(params) {
1831
- return params.map((p) => ({
1832
- name: p.getName(),
1833
- type: simplifyType(p.getType().getText())
1834
- }));
1835
- }
1836
- function simplifyType(typeText) {
1837
- let simplified = typeText.replace(/import\([^)]+\)\./g, "");
1838
- if (simplified.length > 80) {
1839
- simplified = simplified.slice(0, 77) + "...";
1840
- }
1841
- return simplified;
1842
- }
1843
- function extractFunctions(sourceFile) {
1844
- const functions = [];
1845
- for (const func of sourceFile.getFunctions()) {
1846
- const name = func.getName();
1847
- if (!name) continue;
1848
- functions.push({
1849
- name,
1850
- params: extractParams(func.getParameters()),
1851
- returnType: simplifyType(func.getReturnType().getText()),
1852
- isAsync: func.isAsync(),
1853
- isExported: func.isExported(),
1854
- isArrowFunction: false,
1855
- jsdoc: getJsDocDescription(func)
1856
- });
1857
- }
1858
- for (const varStatement of sourceFile.getVariableStatements()) {
1859
- const isExported = varStatement.isExported();
1860
- for (const varDecl of varStatement.getDeclarations()) {
1861
- const init = varDecl.getInitializer();
1862
- if (!init) continue;
1863
- if (init.getKind() === SyntaxKind.ArrowFunction) {
1864
- const arrowFunc = init.asKind(SyntaxKind.ArrowFunction);
1865
- if (!arrowFunc) continue;
1866
- functions.push({
1867
- name: varDecl.getName(),
1868
- params: extractParams(arrowFunc.getParameters()),
1869
- returnType: simplifyType(arrowFunc.getReturnType().getText()),
1870
- isAsync: arrowFunc.isAsync(),
1871
- isExported,
1872
- isArrowFunction: true,
1873
- jsdoc: getJsDocDescription(varStatement)
1874
- });
1875
- }
1876
- if (init.getKind() === SyntaxKind.FunctionExpression) {
1877
- const funcExpr = init.asKind(SyntaxKind.FunctionExpression);
1878
- if (!funcExpr) continue;
1879
- functions.push({
1880
- name: varDecl.getName(),
1881
- params: extractParams(funcExpr.getParameters()),
1882
- returnType: simplifyType(funcExpr.getReturnType().getText()),
1883
- isAsync: funcExpr.isAsync(),
1884
- isExported,
1885
- isArrowFunction: false,
1886
- jsdoc: getJsDocDescription(varStatement)
1887
- });
1888
- }
1889
- }
1890
- }
1891
- return functions;
1892
- }
1893
- function extractTypes(sourceFile) {
1894
- const types = [];
1895
- for (const iface of sourceFile.getInterfaces()) {
1896
- types.push({
1897
- name: iface.getName(),
1898
- kind: "interface",
1899
- definition: formatInterfaceDefinition(iface),
1900
- isExported: iface.isExported()
1901
- });
1902
- }
1903
- for (const typeAlias of sourceFile.getTypeAliases()) {
1904
- types.push({
1905
- name: typeAlias.getName(),
1906
- kind: "type",
1907
- definition: simplifyType(typeAlias.getType().getText()),
1908
- isExported: typeAlias.isExported()
1909
- });
1910
- }
1911
- for (const enumDecl of sourceFile.getEnums()) {
1912
- types.push({
1913
- name: enumDecl.getName(),
1914
- kind: "enum",
1915
- definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
1916
- isExported: enumDecl.isExported()
1917
- });
1918
- }
1919
- return types;
1920
- }
1921
- function formatInterfaceDefinition(iface) {
1922
- const parts = [];
1923
- const extendsClauses = iface.getExtends();
1924
- if (extendsClauses.length > 0) {
1925
- parts.push(`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`);
1926
- }
1927
- const props = iface.getProperties();
1928
- for (const prop of props) {
1929
- const propType = simplifyType(prop.getType().getText());
1930
- parts.push(`${prop.getName()}: ${propType}`);
1931
- }
1932
- const methods = iface.getMethods();
1933
- for (const method of methods) {
1934
- const returnType = simplifyType(method.getReturnType().getText());
1935
- parts.push(`${method.getName()}(): ${returnType}`);
1936
- }
1937
- return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
1938
- }
1939
- function extractExports(sourceFile) {
1940
- const exports = [];
1941
- for (const exportDecl of sourceFile.getExportDeclarations()) {
1942
- for (const namedExport of exportDecl.getNamedExports()) {
1943
- exports.push(namedExport.getName());
1944
- }
1945
- }
1946
- for (const func of sourceFile.getFunctions()) {
1947
- if (func.isExported() && func.getName()) {
1948
- exports.push(func.getName());
1949
- }
1950
- }
1951
- for (const varStatement of sourceFile.getVariableStatements()) {
1952
- if (varStatement.isExported()) {
1953
- for (const decl of varStatement.getDeclarations()) {
1954
- exports.push(decl.getName());
1955
- }
1956
- }
1957
- }
1958
- for (const iface of sourceFile.getInterfaces()) {
1959
- if (iface.isExported()) {
1960
- exports.push(iface.getName());
1961
- }
1962
- }
1963
- for (const typeAlias of sourceFile.getTypeAliases()) {
1964
- if (typeAlias.isExported()) {
1965
- exports.push(typeAlias.getName());
1966
- }
1967
- }
1968
- for (const enumDecl of sourceFile.getEnums()) {
1969
- if (enumDecl.isExported()) {
1970
- exports.push(enumDecl.getName());
1971
- }
1972
- }
1973
- for (const classDecl of sourceFile.getClasses()) {
1974
- if (classDecl.isExported() && classDecl.getName()) {
1975
- exports.push(classDecl.getName());
1976
- }
1977
- }
1978
- return [...new Set(exports)];
1979
- }
1980
-
1981
- // src/commands/context.ts
1982
- async function context(target, options = {}) {
1983
- const cwd = options.cwd || process.cwd();
1984
- const format = options.format || "text";
1985
- if (!target) {
1986
- throw new Error("Target e obrigatorio. Exemplo: ai-tool context src/components/Button.tsx");
1987
- }
1988
- try {
1989
- const targetPath = findTargetFile3(target, cwd);
1990
- if (!targetPath) {
1991
- return formatNotFound3(target, cwd);
1992
- }
1993
- const project = createProject(cwd);
1994
- const absolutePath = resolve(cwd, targetPath);
1995
- const sourceFile = addSourceFile(project, absolutePath);
1996
- const imports = extractImports(sourceFile);
1997
- const functions = extractFunctions(sourceFile);
1998
- const types = extractTypes(sourceFile);
1999
- const exports = extractExports(sourceFile);
2000
- const result = {
2001
- version: "1.0.0",
2002
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2003
- file: targetPath,
2004
- category: detectCategory(targetPath),
2005
- imports,
2006
- exports,
2007
- functions,
2008
- types
2009
- };
2010
- if (format === "json") {
2011
- return JSON.stringify(result, null, 2);
2012
- }
2013
- return formatContextText(result);
2014
- } catch (error) {
2015
- const message = error instanceof Error ? error.message : String(error);
2016
- throw new Error(`Erro ao executar context: ${message}`);
2017
- }
2018
- }
2019
- var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2020
- function findTargetFile3(target, cwd) {
2021
- const normalizedTarget = target.replace(/\\/g, "/");
2022
- const directPath = resolve(cwd, normalizedTarget);
2023
- if (existsSync3(directPath) && isCodeFile2(directPath)) {
2024
- return normalizedTarget;
2025
- }
2026
- for (const ext of CODE_EXTENSIONS2) {
2027
- const withExt = directPath + ext;
2028
- if (existsSync3(withExt)) {
2029
- return normalizedTarget + ext;
2030
- }
2031
- }
2032
- const targetName = basename(normalizedTarget).toLowerCase();
2033
- const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2034
- const allFiles = getAllCodeFiles(cwd);
2035
- const matches = [];
2036
- for (const file of allFiles) {
2037
- const fileName = basename(file).toLowerCase();
2038
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2039
- if (fileNameNoExt === targetNameNoExt) {
2040
- matches.unshift(file);
2041
- } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2042
- matches.push(file);
2043
- }
2044
- }
2045
- if (matches.length > 0) {
2046
- return matches[0];
2047
- }
2048
- return null;
2049
- }
2050
- function isCodeFile2(filePath) {
2051
- const ext = extname2(filePath);
2052
- return CODE_EXTENSIONS2.has(ext);
2053
- }
2054
- function getAllCodeFiles(dir, files = [], baseDir = dir) {
2055
- try {
2056
- const entries = readdirSync2(dir);
2057
- for (const entry of entries) {
2058
- const fullPath = join3(dir, entry);
2059
- if (shouldIgnore(entry)) {
2060
- continue;
2061
- }
2062
- try {
2063
- const stat = statSync2(fullPath);
2064
- if (stat.isDirectory()) {
2065
- getAllCodeFiles(fullPath, files, baseDir);
2066
- } else if (isCodeFile2(entry)) {
2067
- const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
2068
- files.push(relativePath);
2069
- }
2070
- } catch {
2071
- }
2072
- }
2073
- } catch {
2074
- }
2075
- return files;
2076
- }
2077
- function shouldIgnore(name) {
2078
- const ignoredDirs = [
2079
- "node_modules",
2080
- "dist",
2081
- "build",
2082
- ".git",
2083
- ".next",
2084
- ".cache",
2085
- "coverage",
2086
- ".turbo",
2087
- ".vercel"
2088
- ];
2089
- return ignoredDirs.includes(name) || name.startsWith(".");
2090
- }
2091
- function formatNotFound3(target, cwd) {
2092
- const normalizedTarget = target.toLowerCase();
2093
- const allFiles = getAllCodeFiles(cwd);
2094
- const similar = allFiles.filter((f) => {
2095
- const fileName = basename(f).toLowerCase();
2096
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2097
- return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance3(fileNameNoExt, normalizedTarget) <= 3;
2098
- }).slice(0, 5);
2099
- let out = `Arquivo nao encontrado: "${target}"
2100
-
2101
- `;
2102
- out += `Total de arquivos no projeto: ${allFiles.length}
2103
-
2104
- `;
2105
- if (similar.length > 0) {
2106
- out += `Arquivos com nome similar:
2107
- `;
2108
- for (const s of similar) {
2109
- out += ` - ${s}
2110
- `;
2111
- }
2112
- out += `
2113
- `;
2114
- }
2115
- out += `Dicas:
2116
- `;
2117
- out += ` - Use o caminho relativo: src/components/Header.tsx
2118
- `;
2119
- out += ` - Ou apenas o nome do arquivo: Header
2120
- `;
2121
- out += ` - Verifique se o arquivo existe e e um arquivo .ts/.tsx/.js/.jsx
2122
- `;
2123
- return out;
2124
- }
2125
- function levenshteinDistance3(a, b) {
2126
- const matrix = [];
2127
- for (let i = 0; i <= b.length; i++) {
2128
- matrix[i] = [i];
2129
- }
2130
- for (let j = 0; j <= a.length; j++) {
2131
- matrix[0][j] = j;
2132
- }
2133
- for (let i = 1; i <= b.length; i++) {
2134
- for (let j = 1; j <= a.length; j++) {
2135
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
2136
- matrix[i][j] = matrix[i - 1][j - 1];
2137
- } else {
2138
- matrix[i][j] = Math.min(
2139
- matrix[i - 1][j - 1] + 1,
2140
- matrix[i][j - 1] + 1,
2141
- matrix[i - 1][j] + 1
2142
- );
2143
- }
2144
- }
2145
- }
2146
- return matrix[b.length][a.length];
2147
- }
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";
1147
+ // src/areas/detector.ts
1148
+ import { minimatch } from "minimatch";
2227
1149
 
2228
1150
  // src/areas/patterns.ts
2229
1151
  var FOLDER_PATTERNS = [
2230
1152
  // ============================================================================
2231
- // NEXT.JS APP ROUTER - Rotas específicas (alta prioridade)
1153
+ // NEXT.JS APP ROUTER - Rotas genéricas (alta prioridade)
2232
1154
  // ============================================================================
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
1155
  { pattern: /app\/.*\/dashboard\//, area: "dashboard", priority: 100 },
2237
1156
  { 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
1157
  { pattern: /app\/.*\/login\//, area: "auth", priority: 100 },
2242
1158
  { pattern: /app\/.*\/cadastro\//, area: "auth", priority: 100 },
1159
+ { pattern: /app\/.*\/signup\//, area: "auth", priority: 100 },
1160
+ { pattern: /app\/.*\/register\//, area: "auth", priority: 100 },
2243
1161
  { pattern: /app\/.*\/auth\//, area: "auth", priority: 100 },
2244
1162
  { pattern: /app\/.*\/perfil\//, area: "profile", priority: 100 },
1163
+ { pattern: /app\/.*\/profile\//, area: "profile", priority: 100 },
2245
1164
  { pattern: /app\/.*\/configuracoes\//, area: "settings", priority: 100 },
2246
1165
  { pattern: /app\/.*\/settings\//, area: "settings", priority: 100 },
2247
1166
  { pattern: /app\/.*\/onboarding/, area: "onboarding", priority: 100 },
2248
- { pattern: /app\/.*\/beta\//, area: "beta", priority: 100 },
2249
1167
  { pattern: /app\/.*\/precos\//, area: "pricing", priority: 100 },
2250
1168
  { pattern: /app\/.*\/pricing\//, area: "pricing", priority: 100 },
2251
1169
  { pattern: /app\/.*\/checkout\//, area: "checkout", priority: 100 },
@@ -2263,7 +1181,6 @@ var FOLDER_PATTERNS = [
2263
1181
  { pattern: /app\/.*\/faq\//, area: "faq", priority: 100 },
2264
1182
  { pattern: /app\/.*\/help\//, area: "help", priority: 100 },
2265
1183
  { pattern: /app\/.*\/support\//, area: "support", priority: 100 },
2266
- { pattern: /app\/.*\/feedback\//, area: "feedback", priority: 100 },
2267
1184
  // ============================================================================
2268
1185
  // VITE / CRA - src/pages/ ou src/views/ (alta prioridade)
2269
1186
  // ============================================================================
@@ -2316,30 +1233,22 @@ var FOLDER_PATTERNS = [
2316
1233
  // COMPONENTS - Por subpasta (alta prioridade)
2317
1234
  // Funciona em qualquer framework (com ou sem src/)
2318
1235
  // ============================================================================
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
1236
  { 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
1237
  { pattern: /components\/auth\//, area: "auth", priority: 90 },
2326
1238
  { pattern: /components\/admin\//, area: "admin", priority: 90 },
2327
1239
  { pattern: /components\/landing\//, area: "landing", priority: 90 },
2328
1240
  { pattern: /components\/marketing\//, area: "landing", priority: 90 },
2329
1241
  { 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 },
1242
+ { pattern: /components\/subscription\//, area: "billing", priority: 90 },
1243
+ { pattern: /components\/stripe\//, area: "billing", priority: 90 },
1244
+ { pattern: /components\/payment\//, area: "billing", priority: 90 },
2333
1245
  { pattern: /components\/checkout\//, area: "checkout", priority: 90 },
2334
1246
  { pattern: /components\/cart\//, area: "cart", priority: 90 },
2335
1247
  { pattern: /components\/notification\//, area: "notifications", priority: 90 },
2336
- { pattern: /components\/pdf\//, area: "pdf", priority: 90 },
2337
1248
  { pattern: /components\/seo\//, area: "seo", priority: 90 },
2338
1249
  { pattern: /components\/blog\//, area: "blog", priority: 90 },
2339
1250
  { pattern: /components\/docs\//, area: "docs", priority: 90 },
2340
1251
  { pattern: /components\/legal\//, area: "legal", priority: 90 },
2341
- { pattern: /components\/feedback\//, area: "feedback", priority: 90 },
2342
- { pattern: /components\/beta\//, area: "beta", priority: 90 },
2343
1252
  { pattern: /components\/onboarding\//, area: "onboarding", priority: 90 },
2344
1253
  { pattern: /components\/settings\//, area: "settings", priority: 90 },
2345
1254
  { pattern: /components\/profile\//, area: "profile", priority: 90 },
@@ -2359,8 +1268,8 @@ var FOLDER_PATTERNS = [
2359
1268
  { pattern: /components\/core\//, area: "shared-ui", priority: 30 },
2360
1269
  { pattern: /components\/primitives\//, area: "shared-ui", priority: 30 },
2361
1270
  { pattern: /components\/providers\//, area: "core", priority: 40 },
2362
- { pattern: /components\/layout\//, area: "core", priority: 40 },
2363
- { pattern: /components\/layouts\//, area: "core", priority: 40 },
1271
+ { pattern: /components\/layout\//, area: "layout", priority: 40 },
1272
+ { pattern: /components\/layouts\//, area: "layout", priority: 40 },
2364
1273
  // ============================================================================
2365
1274
  // FEATURES (padrão comum em projetos maiores)
2366
1275
  // ============================================================================
@@ -2386,41 +1295,21 @@ var FOLDER_PATTERNS = [
2386
1295
  // ============================================================================
2387
1296
  // LIB - Módulos específicos
2388
1297
  // ============================================================================
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 },
1298
+ { pattern: /lib\/firebase\//, area: "firebase", priority: 80 },
1299
+ { pattern: /lib\/stripe\//, area: "billing", priority: 80 },
2396
1300
  { pattern: /lib\/i18n\//, area: "i18n", priority: 80 },
1301
+ { pattern: /lib\/analytics\//, area: "analytics", priority: 80 },
2397
1302
  // ============================================================================
2398
- // HOOKS - Por nome
1303
+ // HOOKS - Por nome genérico
2399
1304
  // ============================================================================
2400
- { pattern: /hooks\/.*[Pp]et/, area: "meus-pets", priority: 70 },
2401
1305
  { pattern: /hooks\/.*[Aa]uth/, area: "auth", priority: 70 },
2402
- { pattern: /hooks\/.*[Ss]ubscription/, area: "stripe", priority: 70 },
1306
+ { pattern: /hooks\/.*[Ss]ubscription/, area: "billing", priority: 70 },
2403
1307
  { 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
1308
  // ============================================================================
2407
- // STORE - Por nome
1309
+ // STORE - Por nome genérico
2408
1310
  // ============================================================================
2409
- { pattern: /store\/.*[Pp]et/, area: "meus-pets", priority: 70 },
2410
1311
  { 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 },
1312
+ { pattern: /store\/.*[Uu]ser/, area: "user", priority: 70 },
2424
1313
  // ============================================================================
2425
1314
  // CLOUD FUNCTIONS
2426
1315
  // ============================================================================
@@ -2430,83 +1319,61 @@ var FOLDER_PATTERNS = [
2430
1319
  // ============================================================================
2431
1320
  { pattern: /messages\//, area: "i18n", priority: 60 },
2432
1321
  { pattern: /i18n\//, area: "i18n", priority: 60 },
1322
+ { pattern: /locales\//, area: "i18n", priority: 60 },
2433
1323
  { pattern: /public\//, area: "assets", priority: 50 },
2434
1324
  { pattern: /scripts\//, area: "scripts", priority: 50 }
2435
1325
  ];
2436
1326
  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 },
1327
+ // Auth (genérico)
1328
+ { keyword: /[Aa]uth(?!or)/, area: "auth", priority: 60 },
1329
+ // auth mas não author
2457
1330
  { keyword: /[Ll]ogin/, area: "auth", priority: 60 },
2458
1331
  { keyword: /[Rr]egister/, area: "auth", priority: 60 },
2459
1332
  { keyword: /[Ss]ignup/, area: "auth", priority: 60 },
2460
1333
  { 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 },
1334
+ { keyword: /[Ss]ignout/, area: "auth", priority: 60 },
1335
+ { keyword: /[Ll]ogout/, area: "auth", priority: 60 },
1336
+ // Billing/Payments (genérico)
1337
+ { keyword: /[Ss]tripe/, area: "billing", priority: 65 },
1338
+ { keyword: /[Ss]ubscription/, area: "billing", priority: 60 },
1339
+ { keyword: /[Pp]ayment/, area: "billing", priority: 60 },
1340
+ { keyword: /[Bb]illing/, area: "billing", priority: 65 },
1341
+ { keyword: /[Ii]nvoice/, area: "billing", priority: 60 },
1342
+ // Checkout (genérico)
1343
+ { keyword: /[Cc]heckout/, area: "checkout", priority: 60 },
1344
+ // Pricing (genérico)
2467
1345
  { keyword: /[Pp]ricing/, area: "pricing", priority: 60 },
2468
- // Notifications
1346
+ // Notifications (genérico)
2469
1347
  { keyword: /[Nn]otification/, area: "notifications", priority: 60 },
2470
1348
  { keyword: /[Ff][Cc][Mm]/, area: "notifications", priority: 65 },
2471
- { keyword: /[Pp]ush/, area: "notifications", priority: 55 },
2472
- // i18n
1349
+ // i18n (genérico)
2473
1350
  { keyword: /[Ii]18n/, area: "i18n", priority: 60 },
2474
1351
  { keyword: /[Ll]ocale/, area: "i18n", priority: 55 },
2475
1352
  { keyword: /[Tt]ranslat/, area: "i18n", priority: 55 },
2476
- // SEO
1353
+ // SEO (genérico)
2477
1354
  { keyword: /[Ss][Ee][Oo]/, area: "seo", priority: 60 },
2478
1355
  { keyword: /[Ss]itemap/, area: "seo", priority: 60 },
2479
- { keyword: /[Mm]eta/, area: "seo", priority: 50 },
2480
- // Analytics
1356
+ // Analytics (genérico)
2481
1357
  { 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
1358
+ // Admin (genérico)
1359
+ { keyword: /[Aa]dmin/, area: "admin", priority: 55 },
1360
+ // PWA (genérico)
2487
1361
  { keyword: /[Pp][Ww][Aa]/, area: "pwa", priority: 60 },
2488
1362
  { keyword: /[Ss]ervice[Ww]orker/, area: "pwa", priority: 60 },
2489
1363
  { 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 }
1364
+ // PDF (genérico)
1365
+ { keyword: /[Pp]df[Ee]xport/, area: "export", priority: 60 },
1366
+ { keyword: /[Dd]ocx[Ee]xport/, area: "export", priority: 60 }
2494
1367
  ];
2495
1368
  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
1369
  // Autenticação e usuário
2502
1370
  auth: "Autentica\xE7\xE3o",
2503
1371
  user: "Usu\xE1rio",
2504
1372
  profile: "Perfil",
2505
1373
  settings: "Configura\xE7\xF5es",
2506
1374
  onboarding: "Onboarding",
2507
- // E-commerce
2508
- stripe: "Pagamentos (Stripe)",
2509
- payments: "Pagamentos",
1375
+ // E-commerce / Billing
1376
+ billing: "Pagamentos",
2510
1377
  checkout: "Checkout",
2511
1378
  cart: "Carrinho",
2512
1379
  shop: "Loja",
@@ -2522,9 +1389,7 @@ var AREA_NAMES = {
2522
1389
  faq: "FAQ",
2523
1390
  contact: "Contato",
2524
1391
  // Firebase
2525
- "firebase-core": "Firebase Core",
2526
- "firebase-ai": "Firebase AI",
2527
- "firebase-firestore": "Firestore",
1392
+ firebase: "Firebase",
2528
1393
  // Conteúdo
2529
1394
  blog: "Blog",
2530
1395
  docs: "Documenta\xE7\xE3o",
@@ -2534,16 +1399,16 @@ var AREA_NAMES = {
2534
1399
  landing: "Landing Pages",
2535
1400
  seo: "SEO",
2536
1401
  analytics: "Analytics",
2537
- beta: "Programa Beta",
2538
1402
  // Admin e Dashboard
2539
1403
  admin: "Admin",
2540
1404
  dashboard: "Dashboard",
2541
1405
  // Técnico
2542
1406
  i18n: "Internacionaliza\xE7\xE3o",
2543
1407
  pwa: "PWA",
2544
- pdf: "PDF",
1408
+ export: "Exporta\xE7\xE3o",
2545
1409
  core: "Core",
2546
- "shared-ui": "UI Compartilhado",
1410
+ layout: "Layout",
1411
+ "shared-ui": "UI Compartilhada",
2547
1412
  "cloud-functions": "Cloud Functions",
2548
1413
  assets: "Assets",
2549
1414
  scripts: "Scripts",
@@ -2555,205 +1420,1371 @@ var AREA_NAMES = {
2555
1420
  app: "Aplica\xE7\xE3o"
2556
1421
  };
2557
1422
  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
1423
  // Autenticação e usuário
2564
1424
  auth: "Autentica\xE7\xE3o e gerenciamento de sess\xE3o",
2565
1425
  user: "Gerenciamento de dados do usu\xE1rio",
2566
1426
  profile: "Perfil do usu\xE1rio",
2567
1427
  settings: "Configura\xE7\xF5es do usu\xE1rio",
2568
1428
  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",
1429
+ // E-commerce / Billing
1430
+ billing: "Sistema de pagamentos e assinaturas",
1431
+ checkout: "Fluxo de checkout e finaliza\xE7\xE3o",
2573
1432
  cart: "Carrinho de compras",
2574
- shop: "Loja e cat\xE1logo de produtos",
1433
+ shop: "Loja e cat\xE1logo",
2575
1434
  products: "Gerenciamento de produtos",
2576
1435
  orders: "Gerenciamento de pedidos",
2577
1436
  pricing: "P\xE1gina de pre\xE7os e planos",
2578
1437
  // Comunicação
2579
- notifications: "Sistema de notifica\xE7\xF5es push",
1438
+ notifications: "Sistema de notifica\xE7\xF5es",
2580
1439
  chat: "Sistema de chat e mensagens",
2581
- feedback: "Coleta de feedback dos usu\xE1rios",
1440
+ feedback: "Coleta de feedback",
2582
1441
  support: "Suporte ao cliente",
2583
1442
  help: "Central de ajuda",
2584
1443
  faq: "Perguntas frequentes",
2585
1444
  contact: "P\xE1gina de contato",
2586
1445
  // 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",
1446
+ firebase: "Configura\xE7\xE3o e servi\xE7os Firebase",
2590
1447
  // Conteúdo
2591
1448
  blog: "Blog e artigos",
2592
1449
  docs: "Documenta\xE7\xE3o e guias",
2593
1450
  legal: "Termos de uso, privacidade e pol\xEDticas",
2594
- about: "P\xE1gina sobre a empresa",
1451
+ about: "P\xE1gina sobre",
2595
1452
  // Marketing e SEO
2596
1453
  landing: "Landing pages e marketing",
2597
1454
  seo: "SEO, meta tags e sitemaps",
2598
- analytics: "Analytics e tracking de eventos",
2599
- beta: "Programa de beta testers",
1455
+ analytics: "Analytics e tracking",
2600
1456
  // Admin e Dashboard
2601
1457
  admin: "Painel administrativo",
2602
1458
  dashboard: "Dashboard do usu\xE1rio",
2603
1459
  // Técnico
2604
1460
  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",
1461
+ pwa: "Progressive Web App",
1462
+ export: "Exporta\xE7\xE3o de documentos",
1463
+ core: "Providers e configura\xE7\xE3o principal",
1464
+ layout: "Layout e navega\xE7\xE3o",
2608
1465
  "shared-ui": "Componentes de UI compartilhados",
2609
1466
  "cloud-functions": "Cloud Functions (serverless)",
2610
- assets: "Assets p\xFAblicos (imagens, fontes)",
1467
+ assets: "Assets p\xFAblicos",
2611
1468
  scripts: "Scripts de automa\xE7\xE3o",
2612
1469
  // UI patterns
2613
- forms: "Componentes de formul\xE1rio reutiliz\xE1veis",
2614
- tables: "Componentes de tabela reutiliz\xE1veis",
2615
- modals: "Modais e dialogs reutiliz\xE1veis",
1470
+ forms: "Componentes de formul\xE1rio",
1471
+ tables: "Componentes de tabela",
1472
+ modals: "Modais e dialogs",
2616
1473
  // Genéricos
2617
1474
  app: "\xC1rea principal da aplica\xE7\xE3o"
2618
1475
  };
2619
1476
 
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" });
1477
+ // src/areas/detector.ts
1478
+ function detectFileAreas(filePath, config) {
1479
+ const normalizedPath = filePath.replace(/\\/g, "/");
1480
+ const matches = [];
1481
+ const autoDetect = config.settings?.autoDetect !== false;
1482
+ for (const [areaId, areaConfig] of Object.entries(config.areas)) {
1483
+ if (matchesAreaConfig(normalizedPath, areaConfig)) {
1484
+ matches.push({ area: areaId, priority: 200, source: "config" });
1485
+ }
1486
+ }
1487
+ if (!autoDetect) {
1488
+ const unique2 = [...new Set(matches.map((m) => m.area))];
1489
+ return unique2;
1490
+ }
1491
+ for (const { pattern, area: area2, priority } of FOLDER_PATTERNS) {
1492
+ if (pattern.test(normalizedPath)) {
1493
+ if (!matches.some((m) => m.area === area2 && m.source === "config")) {
1494
+ matches.push({ area: area2, priority, source: "folder" });
1495
+ }
1496
+ }
1497
+ }
1498
+ const fileName = normalizedPath.split("/").pop() || "";
1499
+ for (const { keyword, area: area2, priority } of KEYWORD_PATTERNS) {
1500
+ if (keyword.test(fileName) || keyword.test(normalizedPath)) {
1501
+ if (!matches.some((m) => m.area === area2)) {
1502
+ matches.push({ area: area2, priority, source: "keyword" });
1503
+ }
1504
+ }
1505
+ }
1506
+ const sorted = matches.sort((a, b) => b.priority - a.priority);
1507
+ const unique = [...new Set(sorted.map((m) => m.area))];
1508
+ return unique;
1509
+ }
1510
+ function matchesAreaConfig(filePath, config) {
1511
+ if (config.exclude) {
1512
+ for (const pattern of config.exclude) {
1513
+ if (minimatch(filePath, pattern, { dot: true })) {
1514
+ return false;
1515
+ }
1516
+ }
1517
+ }
1518
+ for (const pattern of config.patterns) {
1519
+ if (minimatch(filePath, pattern, { dot: true })) {
1520
+ return true;
1521
+ }
1522
+ }
1523
+ if (config.keywords) {
1524
+ const lowerPath = filePath.toLowerCase();
1525
+ for (const keyword of config.keywords) {
1526
+ if (lowerPath.includes(keyword.toLowerCase())) {
1527
+ return true;
1528
+ }
1529
+ }
1530
+ }
1531
+ return false;
1532
+ }
1533
+ function getAreaName(areaId, config) {
1534
+ if (config.areas[areaId]?.name) {
1535
+ return config.areas[areaId].name;
1536
+ }
1537
+ if (AREA_NAMES[areaId]) {
1538
+ return AREA_NAMES[areaId];
1539
+ }
1540
+ return areaId.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1541
+ }
1542
+ function getAreaDescription(areaId, config) {
1543
+ if (config.areas[areaId]?.description) {
1544
+ return config.areas[areaId].description;
1545
+ }
1546
+ return AREA_DESCRIPTIONS[areaId];
1547
+ }
1548
+ function inferFileDescription(filePath, category) {
1549
+ const fileName = filePath.split("/").pop() || "";
1550
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1551
+ const patterns = [
1552
+ // Pages
1553
+ { pattern: /^page$/, description: () => "P\xE1gina principal" },
1554
+ { pattern: /^layout$/, description: () => "Layout" },
1555
+ { pattern: /^loading$/, description: () => "Estado de loading" },
1556
+ { pattern: /^error$/, description: () => "P\xE1gina de erro" },
1557
+ { pattern: /^not-found$/, description: () => "P\xE1gina 404" },
1558
+ // Components com sufixo
1559
+ { pattern: /(.+)PageClient$/, description: (m) => `Client component da p\xE1gina ${m[1]}` },
1560
+ { pattern: /(.+)Dialog$/, description: (m) => `Dialog de ${m[1].toLowerCase()}` },
1561
+ { pattern: /(.+)Modal$/, description: (m) => `Modal de ${m[1].toLowerCase()}` },
1562
+ { pattern: /(.+)Form$/, description: (m) => `Formul\xE1rio de ${m[1].toLowerCase()}` },
1563
+ { pattern: /(.+)Card$/, description: (m) => `Card de ${m[1].toLowerCase()}` },
1564
+ { pattern: /(.+)List$/, description: (m) => `Lista de ${m[1].toLowerCase()}` },
1565
+ { pattern: /(.+)Table$/, description: (m) => `Tabela de ${m[1].toLowerCase()}` },
1566
+ { pattern: /(.+)Manager$/, description: (m) => `Gerenciador de ${m[1].toLowerCase()}` },
1567
+ { pattern: /(.+)Provider$/, description: (m) => `Provider de ${m[1].toLowerCase()}` },
1568
+ { pattern: /(.+)Context$/, description: (m) => `Context de ${m[1].toLowerCase()}` },
1569
+ { pattern: /(.+)Step$/, description: (m) => `Step: ${m[1]}` },
1570
+ { pattern: /(.+)Tab$/, description: (m) => `Aba: ${m[1]}` },
1571
+ { pattern: /(.+)Section$/, description: (m) => `Se\xE7\xE3o: ${m[1]}` },
1572
+ { pattern: /(.+)Header$/, description: (m) => `Header de ${m[1].toLowerCase()}` },
1573
+ { pattern: /(.+)Footer$/, description: (m) => `Footer de ${m[1].toLowerCase()}` },
1574
+ { pattern: /(.+)Skeleton$/, description: (m) => `Skeleton de ${m[1].toLowerCase()}` },
1575
+ { pattern: /(.+)Chip$/, description: (m) => `Chip de ${m[1].toLowerCase()}` },
1576
+ { pattern: /(.+)Badge$/, description: (m) => `Badge de ${m[1].toLowerCase()}` },
1577
+ { pattern: /(.+)Button$/, description: (m) => `Bot\xE3o ${m[1].toLowerCase()}` },
1578
+ { pattern: /(.+)Icon$/, description: (m) => `\xCDcone ${m[1].toLowerCase()}` },
1579
+ // Hooks
1580
+ { pattern: /^use(.+)$/, description: (m) => `Hook de ${m[1].toLowerCase()}` },
1581
+ // Types/Schemas
1582
+ { pattern: /(.+)\.types$/, description: (m) => `Tipos de ${m[1].toLowerCase()}` },
1583
+ { pattern: /(.+)Schemas?$/, description: (m) => `Schema de ${m[1].toLowerCase()}` },
1584
+ // Utils
1585
+ { pattern: /(.+)Helpers?$/, description: (m) => `Helpers de ${m[1].toLowerCase()}` },
1586
+ { pattern: /(.+)Utils?$/, description: (m) => `Utilit\xE1rios de ${m[1].toLowerCase()}` },
1587
+ { pattern: /(.+)Formatters?$/, description: (m) => `Formatador de ${m[1].toLowerCase()}` },
1588
+ { pattern: /(.+)Validators?$/, description: (m) => `Validador de ${m[1].toLowerCase()}` },
1589
+ { pattern: /(.+)Mappers?$/, description: (m) => `Mapper de ${m[1].toLowerCase()}` },
1590
+ { pattern: /(.+)Converters?$/, description: (m) => `Conversor de ${m[1].toLowerCase()}` },
1591
+ // Services
1592
+ { pattern: /(.+)Service$/, description: (m) => `Servi\xE7o de ${m[1].toLowerCase()}` },
1593
+ // Index
1594
+ { pattern: /^index$/, description: () => "Export principal" }
1595
+ ];
1596
+ for (const { pattern, description } of patterns) {
1597
+ const match = fileNameNoExt.match(pattern);
1598
+ if (match) {
1599
+ return description(match);
1600
+ }
1601
+ }
1602
+ const categoryDescriptions = {
1603
+ page: "P\xE1gina",
1604
+ layout: "Layout",
1605
+ route: "API Route",
1606
+ component: "Componente",
1607
+ hook: "Hook",
1608
+ service: "Servi\xE7o",
1609
+ store: "Store",
1610
+ util: "Utilit\xE1rio",
1611
+ type: "Tipos",
1612
+ config: "Configura\xE7\xE3o",
1613
+ test: "Teste"
1614
+ };
1615
+ return categoryDescriptions[category];
1616
+ }
1617
+
1618
+ // src/commands/map.ts
1619
+ async function map(options = {}) {
1620
+ const cwd = options.cwd || process.cwd();
1621
+ const format = options.format || "text";
1622
+ const useCache = options.cache !== false;
1623
+ const full = options.full ?? false;
1624
+ if (useCache && isCacheValid(cwd)) {
1625
+ const cached = getCachedMapResult(cwd);
1626
+ if (cached) {
1627
+ const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
1628
+ if (format === "json") {
1629
+ return JSON.stringify(result, null, 2);
1630
+ }
1631
+ if (full) {
1632
+ return formatMapText(result) + "\n\n\u{1F4E6} (resultado do cache)";
1633
+ }
1634
+ const areasInfo = detectAreasInfo(cwd, result.files.map((f) => f.path));
1635
+ return formatMapSummary(result, areasInfo) + "\n\u{1F4E6} (cache)";
1636
+ }
1637
+ }
1638
+ try {
1639
+ const { getStructure, useGraph } = await skott({
1640
+ cwd,
1641
+ includeBaseDir: false,
1642
+ dependencyTracking: {
1643
+ thirdParty: options.trackDependencies ?? false,
1644
+ builtin: false,
1645
+ typeOnly: false
1646
+ },
1647
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1648
+ tsConfigPath: "tsconfig.json"
1649
+ });
1650
+ const structure = getStructure();
1651
+ const graphApi = useGraph();
1652
+ const { findCircularDependencies } = graphApi;
1653
+ if (useCache) {
1654
+ const graphData = {
1655
+ graph: structure.graph,
1656
+ files: Object.keys(structure.graph),
1657
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1658
+ };
1659
+ cacheGraph(cwd, graphData);
1660
+ }
1661
+ const files = Object.entries(structure.graph).map(([path]) => ({
1662
+ path,
1663
+ category: detectCategory(path),
1664
+ size: 0
1665
+ // Skott não fornece tamanho
1666
+ }));
1667
+ const folderMap = /* @__PURE__ */ new Map();
1668
+ for (const file of files) {
1669
+ const parts = file.path.split("/");
1670
+ if (parts.length > 1) {
1671
+ const folder = parts.slice(0, -1).join("/");
1672
+ if (!folderMap.has(folder)) {
1673
+ folderMap.set(folder, {
1674
+ path: folder,
1675
+ fileCount: 0,
1676
+ categories: {}
1677
+ });
1678
+ }
1679
+ const stats = folderMap.get(folder);
1680
+ stats.fileCount++;
1681
+ stats.categories[file.category] = (stats.categories[file.category] || 0) + 1;
1682
+ }
1683
+ }
1684
+ const categories = {};
1685
+ for (const file of files) {
1686
+ categories[file.category] = (categories[file.category] || 0) + 1;
1687
+ }
1688
+ const circular = findCircularDependencies();
1689
+ const result = {
1690
+ version: "1.0.0",
1691
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1692
+ cwd,
1693
+ summary: {
1694
+ totalFiles: files.length,
1695
+ totalFolders: folderMap.size,
1696
+ categories
1697
+ },
1698
+ folders: Array.from(folderMap.values()),
1699
+ files,
1700
+ circularDependencies: circular
1701
+ };
1702
+ if (useCache) {
1703
+ cacheMapResult(cwd, result);
1704
+ updateCacheMeta(cwd);
1705
+ }
1706
+ if (format === "json") {
1707
+ return JSON.stringify(result, null, 2);
1708
+ }
1709
+ if (full) {
1710
+ return formatMapText(result);
1711
+ }
1712
+ const areasInfo = detectAreasInfo(cwd, files.map((f) => f.path));
1713
+ return formatMapSummary(result, areasInfo);
1714
+ } catch (error) {
1715
+ const message = error instanceof Error ? error.message : String(error);
1716
+ throw new Error(`Erro ao executar map: ${message}`);
1717
+ }
1718
+ }
1719
+ function detectAreasInfo(cwd, filePaths) {
1720
+ try {
1721
+ const config = readConfig(cwd);
1722
+ const areaSet = /* @__PURE__ */ new Set();
1723
+ let unmappedCount = 0;
1724
+ for (const filePath of filePaths) {
1725
+ const areas2 = detectFileAreas(filePath, config);
1726
+ if (areas2.length === 0) {
1727
+ unmappedCount++;
1728
+ } else {
1729
+ for (const areaId of areas2) {
1730
+ areaSet.add(areaId);
1731
+ }
1732
+ }
1733
+ }
1734
+ const areaIds = Array.from(areaSet).sort();
1735
+ const names = areaIds.map((id) => getAreaName(id, config));
1736
+ return {
1737
+ names,
1738
+ total: areaIds.length,
1739
+ unmappedCount
1740
+ };
1741
+ } catch {
1742
+ return { names: [], total: 0, unmappedCount: 0 };
1743
+ }
1744
+ }
1745
+
1746
+ // src/commands/dead.ts
1747
+ import { execSync } from "child_process";
1748
+ async function dead(options = {}) {
1749
+ const cwd = options.cwd || process.cwd();
1750
+ const format = options.format || "text";
1751
+ const useCache = options.cache !== false;
1752
+ if (useCache && isCacheValid(cwd)) {
1753
+ const cached = getCachedDeadResult(cwd);
1754
+ if (cached) {
1755
+ const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
1756
+ if (format === "json") {
1757
+ return JSON.stringify(result, null, 2);
1758
+ }
1759
+ return formatDeadText(result) + "\n\n\u{1F4E6} (resultado do cache)";
1760
+ }
1761
+ }
1762
+ try {
1763
+ let knipOutput;
1764
+ try {
1765
+ const output = execSync("npx knip --reporter=json", {
1766
+ cwd,
1767
+ encoding: "utf-8",
1768
+ maxBuffer: 50 * 1024 * 1024,
1769
+ stdio: ["pipe", "pipe", "pipe"]
1770
+ });
1771
+ knipOutput = JSON.parse(output || "{}");
1772
+ } catch (execError) {
1773
+ const error = execError;
1774
+ if (error.stdout) {
1775
+ try {
1776
+ knipOutput = JSON.parse(error.stdout);
1777
+ } catch {
1778
+ knipOutput = {};
1779
+ }
1780
+ } else {
1781
+ knipOutput = {};
1782
+ }
1783
+ }
1784
+ const rawFiles = knipOutput.files || [];
1785
+ const { filtered: filteredFiles, excluded: excludedFunctions } = filterCloudFunctionsFalsePositives(rawFiles, cwd);
1786
+ const deadFiles = filteredFiles.map((file) => ({
1787
+ path: file,
1788
+ category: detectCategory(file),
1789
+ type: "file"
1790
+ }));
1791
+ const hasFirebase = hasFirebaseFunctions(cwd);
1792
+ const firebaseInfo = hasFirebase ? { detected: true, excludedCount: excludedFunctions.length } : { detected: false, excludedCount: 0 };
1793
+ const deadExports = [];
1794
+ if (knipOutput.issues) {
1795
+ for (const issue of knipOutput.issues) {
1796
+ if (issue.symbol && issue.symbolType === "export") {
1797
+ deadExports.push({
1798
+ file: issue.file,
1799
+ export: issue.symbol
1800
+ });
1801
+ }
1802
+ }
1803
+ }
1804
+ const deadDependencies = [
1805
+ ...knipOutput.dependencies || [],
1806
+ ...knipOutput.devDependencies || []
1807
+ ];
1808
+ const result = {
1809
+ version: "1.0.0",
1810
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1811
+ cwd,
1812
+ summary: {
1813
+ totalDead: deadFiles.length + deadExports.length + deadDependencies.length,
1814
+ byType: {
1815
+ files: deadFiles.length,
1816
+ exports: deadExports.length,
1817
+ dependencies: deadDependencies.length
1818
+ }
1819
+ },
1820
+ files: deadFiles,
1821
+ exports: deadExports,
1822
+ dependencies: deadDependencies,
1823
+ // Metadata sobre filtros aplicados
1824
+ filters: {
1825
+ firebase: firebaseInfo,
1826
+ excludedFiles: excludedFunctions
1827
+ }
1828
+ };
1829
+ if (useCache) {
1830
+ cacheDeadResult(cwd, result);
1831
+ updateCacheMeta(cwd);
1832
+ }
1833
+ if (format === "json") {
1834
+ return JSON.stringify(result, null, 2);
1835
+ }
1836
+ return formatDeadText(result);
1837
+ } catch (error) {
1838
+ const message = error instanceof Error ? error.message : String(error);
1839
+ throw new Error(`Erro ao executar dead: ${message}`);
1840
+ }
1841
+ }
1842
+ async function deadFix(options = {}) {
1843
+ const cwd = options.cwd || process.cwd();
1844
+ try {
1845
+ const output = execSync("npx knip --fix", {
1846
+ cwd,
1847
+ encoding: "utf-8",
1848
+ maxBuffer: 50 * 1024 * 1024
1849
+ });
1850
+ return `\u2705 Fix executado com sucesso!
1851
+
1852
+ ${output}`;
1853
+ } catch (error) {
1854
+ const message = error instanceof Error ? error.message : String(error);
1855
+ throw new Error(`Erro ao executar fix: ${message}`);
1856
+ }
1857
+ }
1858
+
1859
+ // src/commands/impact.ts
1860
+ import skott2 from "skott";
1861
+ async function impact(target, options = {}) {
1862
+ const cwd = options.cwd || process.cwd();
1863
+ const format = options.format || "text";
1864
+ const useCache = options.cache !== false;
1865
+ if (!target) {
1866
+ throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
1867
+ }
1868
+ try {
1869
+ let graph;
1870
+ let allFiles = [];
1871
+ let findCircular = () => [];
1872
+ let fromCache = false;
1873
+ if (useCache && isCacheValid(cwd)) {
1874
+ const cached = getCachedGraph(cwd);
1875
+ if (cached) {
1876
+ graph = cached.graph;
1877
+ allFiles = cached.files;
1878
+ findCircular = () => findCircularFromGraph(graph);
1879
+ fromCache = true;
1880
+ }
1881
+ }
1882
+ if (!graph) {
1883
+ const { getStructure, useGraph } = await skott2({
1884
+ cwd,
1885
+ includeBaseDir: false,
1886
+ dependencyTracking: {
1887
+ thirdParty: false,
1888
+ builtin: false,
1889
+ typeOnly: false
1890
+ },
1891
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1892
+ tsConfigPath: "tsconfig.json"
1893
+ });
1894
+ const structure = getStructure();
1895
+ const graphApi = useGraph();
1896
+ graph = structure.graph;
1897
+ allFiles = Object.keys(graph);
1898
+ findCircular = () => graphApi.findCircularDependencies();
1899
+ if (useCache) {
1900
+ cacheGraph(cwd, {
1901
+ graph,
1902
+ files: allFiles,
1903
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1904
+ });
1905
+ updateCacheMeta(cwd);
1906
+ }
1907
+ }
1908
+ const targetPath = findTargetFile(target, allFiles);
1909
+ if (!targetPath) {
1910
+ return formatNotFound(target, allFiles);
1911
+ }
1912
+ const { directUpstream, indirectUpstream, directDownstream, indirectDownstream } = calculateDependencies(targetPath, graph);
1913
+ const dependingOn = [...directUpstream, ...indirectUpstream];
1914
+ const dependencies = [...directDownstream, ...indirectDownstream];
1915
+ const risks = detectRisks(targetPath, dependingOn, dependencies, findCircular);
1916
+ const suggestions = generateSuggestions(dependingOn, dependencies, risks);
1917
+ const result = {
1918
+ version: "1.0.0",
1919
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1920
+ target: targetPath,
1921
+ category: detectCategory(targetPath),
1922
+ upstream: {
1923
+ direct: directUpstream.map(toImpactFile(true)),
1924
+ indirect: indirectUpstream.map(toImpactFile(false)),
1925
+ total: dependingOn.length
1926
+ },
1927
+ downstream: {
1928
+ direct: directDownstream.map(toImpactFile(true)),
1929
+ indirect: indirectDownstream.map(toImpactFile(false)),
1930
+ total: dependencies.length
1931
+ },
1932
+ risks,
1933
+ suggestions
1934
+ };
1935
+ if (format === "json") {
1936
+ return JSON.stringify(result, null, 2);
1937
+ }
1938
+ const output = formatImpactText(result);
1939
+ return fromCache ? output + "\n\n\u{1F4E6} (grafo do cache)" : output;
1940
+ } catch (error) {
1941
+ const message = error instanceof Error ? error.message : String(error);
1942
+ throw new Error(`Erro ao executar impact: ${message}`);
1943
+ }
1944
+ }
1945
+ function calculateDependencies(targetPath, graph) {
1946
+ const targetNode = graph[targetPath];
1947
+ const directDownstream = targetNode ? targetNode.adjacentTo : [];
1948
+ const allDownstream = /* @__PURE__ */ new Set();
1949
+ const visited = /* @__PURE__ */ new Set();
1950
+ function collectDownstream(file) {
1951
+ if (visited.has(file)) return;
1952
+ visited.add(file);
1953
+ const node = graph[file];
1954
+ if (node) {
1955
+ for (const dep of node.adjacentTo) {
1956
+ allDownstream.add(dep);
1957
+ collectDownstream(dep);
1958
+ }
1959
+ }
1960
+ }
1961
+ collectDownstream(targetPath);
1962
+ const indirectDownstream = Array.from(allDownstream).filter(
1963
+ (f) => !directDownstream.includes(f)
1964
+ );
1965
+ const directUpstream = [];
1966
+ const allUpstream = /* @__PURE__ */ new Set();
1967
+ for (const [file, node] of Object.entries(graph)) {
1968
+ if (node.adjacentTo.includes(targetPath)) {
1969
+ directUpstream.push(file);
1970
+ }
1971
+ }
1972
+ visited.clear();
1973
+ function collectUpstream(file) {
1974
+ if (visited.has(file)) return;
1975
+ visited.add(file);
1976
+ for (const [f, node] of Object.entries(graph)) {
1977
+ if (node.adjacentTo.includes(file) && !visited.has(f)) {
1978
+ allUpstream.add(f);
1979
+ collectUpstream(f);
1980
+ }
1981
+ }
1982
+ }
1983
+ for (const file of directUpstream) {
1984
+ collectUpstream(file);
1985
+ }
1986
+ const indirectUpstream = Array.from(allUpstream).filter(
1987
+ (f) => !directUpstream.includes(f)
1988
+ );
1989
+ return {
1990
+ directUpstream,
1991
+ indirectUpstream,
1992
+ directDownstream,
1993
+ indirectDownstream
1994
+ };
1995
+ }
1996
+ function findCircularFromGraph(graph) {
1997
+ const cycles = [];
1998
+ const visited = /* @__PURE__ */ new Set();
1999
+ const stack = /* @__PURE__ */ new Set();
2000
+ const path = [];
2001
+ function dfs(node) {
2002
+ if (stack.has(node)) {
2003
+ const cycleStart = path.indexOf(node);
2004
+ if (cycleStart !== -1) {
2005
+ cycles.push(path.slice(cycleStart));
2006
+ }
2007
+ return;
2008
+ }
2009
+ if (visited.has(node)) return;
2010
+ visited.add(node);
2011
+ stack.add(node);
2012
+ path.push(node);
2013
+ const nodeData = graph[node];
2014
+ if (nodeData) {
2015
+ for (const dep of nodeData.adjacentTo) {
2016
+ dfs(dep);
2017
+ }
2018
+ }
2019
+ path.pop();
2020
+ stack.delete(node);
2021
+ }
2022
+ for (const node of Object.keys(graph)) {
2023
+ dfs(node);
2024
+ }
2025
+ return cycles;
2026
+ }
2027
+ function findTargetFile(target, allFiles) {
2028
+ const normalizedTarget = target.replace(/\\/g, "/");
2029
+ if (allFiles.includes(normalizedTarget)) {
2030
+ return normalizedTarget;
2031
+ }
2032
+ const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
2033
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2034
+ const matches = [];
2035
+ for (const file of allFiles) {
2036
+ const fileName = file.split("/").pop()?.toLowerCase() || "";
2037
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2038
+ if (fileNameNoExt === targetNameNoExt) {
2039
+ matches.unshift(file);
2040
+ } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2041
+ matches.push(file);
2042
+ }
2043
+ }
2044
+ if (matches.length === 1) {
2045
+ return matches[0];
2046
+ }
2047
+ if (matches.length > 1) {
2048
+ return matches[0];
2049
+ }
2050
+ return null;
2051
+ }
2052
+ function formatNotFound(target, allFiles) {
2053
+ const normalizedTarget = target.toLowerCase();
2054
+ const similar = allFiles.filter((f) => {
2055
+ const fileName = f.split("/").pop()?.toLowerCase() || "";
2056
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2057
+ return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance(fileNameNoExt, normalizedTarget) <= 3;
2058
+ }).slice(0, 5);
2059
+ let out = `\u274C Arquivo n\xE3o encontrado no \xEDndice: "${target}"
2060
+
2061
+ `;
2062
+ out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
2063
+
2064
+ `;
2065
+ if (similar.length > 0) {
2066
+ out += `\u{1F4DD} Arquivos com nome similar:
2067
+ `;
2068
+ for (const s of similar) {
2069
+ out += ` \u2022 ${s}
2070
+ `;
2071
+ }
2072
+ out += `
2073
+ `;
2074
+ }
2075
+ out += `\u{1F4A1} Dicas:
2076
+ `;
2077
+ out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
2078
+ `;
2079
+ out += ` \u2022 Ou apenas o nome do arquivo: Header
2080
+ `;
2081
+ out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
2082
+ `;
2083
+ return out;
2084
+ }
2085
+ function toImpactFile(isDirect) {
2086
+ return (path) => ({
2087
+ path,
2088
+ category: detectCategory(path),
2089
+ isDirect
2090
+ });
2091
+ }
2092
+ function detectRisks(targetPath, upstream, downstream, findCircular) {
2093
+ const risks = [];
2094
+ if (upstream.length >= 15) {
2095
+ risks.push({
2096
+ type: "widely-used",
2097
+ severity: "high",
2098
+ message: `Arquivo CR\xCDTICO: usado por ${upstream.length} arquivos \xFAnicos`
2099
+ });
2100
+ } else if (upstream.length >= 5) {
2101
+ risks.push({
2102
+ type: "widely-used",
2103
+ severity: "medium",
2104
+ message: `Arquivo compartilhado: usado por ${upstream.length} arquivos \xFAnicos`
2105
+ });
2106
+ }
2107
+ if (downstream.length >= 20) {
2108
+ risks.push({
2109
+ type: "deep-chain",
2110
+ severity: "medium",
2111
+ message: `Arquivo importa ${downstream.length} depend\xEAncias (considere dividir)`
2112
+ });
2113
+ } else if (downstream.length >= 10) {
2114
+ risks.push({
2115
+ type: "deep-chain",
2116
+ severity: "low",
2117
+ message: `Arquivo importa ${downstream.length} depend\xEAncias`
2118
+ });
2119
+ }
2120
+ const circular = findCircular();
2121
+ const targetCircular = circular.filter((cycle) => cycle.includes(targetPath));
2122
+ if (targetCircular.length > 0) {
2123
+ risks.push({
2124
+ type: "circular",
2125
+ severity: "medium",
2126
+ message: `Envolvido em ${targetCircular.length} depend\xEAncia${targetCircular.length > 1 ? "s" : ""} circular${targetCircular.length > 1 ? "es" : ""}`
2127
+ });
2128
+ }
2129
+ return risks;
2130
+ }
2131
+ function generateSuggestions(upstream, downstream, risks) {
2132
+ const suggestions = [];
2133
+ if (upstream.length > 0) {
2134
+ suggestions.push(
2135
+ `Verifique os ${upstream.length} arquivo(s) que importam este antes de modificar`
2136
+ );
2137
+ }
2138
+ if (upstream.length >= 10) {
2139
+ suggestions.push(`Considere criar testes para garantir que mudan\xE7as n\xE3o quebrem dependentes`);
2140
+ }
2141
+ if (downstream.length > 0) {
2142
+ suggestions.push(`Teste as ${downstream.length} depend\xEAncia(s) ap\xF3s mudan\xE7as`);
2143
+ }
2144
+ if (risks.some((r) => r.type === "circular")) {
2145
+ suggestions.push(`Considere resolver as depend\xEAncias circulares antes de refatorar`);
2146
+ }
2147
+ if (risks.some((r) => r.type === "widely-used" && r.severity === "high")) {
2148
+ suggestions.push(`Este arquivo \xE9 cr\xEDtico - planeje mudan\xE7as com cuidado`);
2149
+ }
2150
+ return suggestions;
2151
+ }
2152
+ function levenshteinDistance(a, b) {
2153
+ const matrix = [];
2154
+ for (let i = 0; i <= b.length; i++) {
2155
+ matrix[i] = [i];
2156
+ }
2157
+ for (let j = 0; j <= a.length; j++) {
2158
+ matrix[0][j] = j;
2159
+ }
2160
+ for (let i = 1; i <= b.length; i++) {
2161
+ for (let j = 1; j <= a.length; j++) {
2162
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2163
+ matrix[i][j] = matrix[i - 1][j - 1];
2164
+ } else {
2165
+ matrix[i][j] = Math.min(
2166
+ matrix[i - 1][j - 1] + 1,
2167
+ matrix[i][j - 1] + 1,
2168
+ matrix[i - 1][j] + 1
2169
+ );
2170
+ }
2171
+ }
2172
+ }
2173
+ return matrix[b.length][a.length];
2174
+ }
2175
+
2176
+ // src/commands/suggest.ts
2177
+ import skott3 from "skott";
2178
+ async function suggest(target, options = {}) {
2179
+ const cwd = options.cwd || process.cwd();
2180
+ const format = options.format || "text";
2181
+ const useCache = options.cache !== false;
2182
+ const limit = options.limit || 10;
2183
+ if (!target) {
2184
+ throw new Error("Target e obrigatorio. Exemplo: ai-tool suggest src/components/Button.tsx");
2185
+ }
2186
+ try {
2187
+ let graph;
2188
+ let allFiles = [];
2189
+ let fromCache = false;
2190
+ if (useCache && isCacheValid(cwd)) {
2191
+ const cached = getCachedGraph(cwd);
2192
+ if (cached) {
2193
+ graph = cached.graph;
2194
+ allFiles = cached.files;
2195
+ fromCache = true;
2196
+ }
2197
+ }
2198
+ if (!graph) {
2199
+ const { getStructure } = await skott3({
2200
+ cwd,
2201
+ includeBaseDir: false,
2202
+ dependencyTracking: {
2203
+ thirdParty: false,
2204
+ builtin: false,
2205
+ typeOnly: false
2206
+ },
2207
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
2208
+ tsConfigPath: "tsconfig.json"
2209
+ });
2210
+ const structure = getStructure();
2211
+ graph = structure.graph;
2212
+ allFiles = Object.keys(graph);
2213
+ if (useCache) {
2214
+ cacheGraph(cwd, {
2215
+ graph,
2216
+ files: allFiles,
2217
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2218
+ });
2219
+ updateCacheMeta(cwd);
2220
+ }
2221
+ }
2222
+ const targetPath = findTargetFile2(target, allFiles);
2223
+ if (!targetPath) {
2224
+ return formatNotFound2(target, allFiles);
2225
+ }
2226
+ const suggestions = collectSuggestions(targetPath, graph, allFiles, limit);
2227
+ const result = {
2228
+ version: "1.0.0",
2229
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2230
+ target: targetPath,
2231
+ category: detectCategory(targetPath),
2232
+ suggestions
2233
+ };
2234
+ if (format === "json") {
2235
+ return JSON.stringify(result, null, 2);
2236
+ }
2237
+ const output = formatSuggestText(result);
2238
+ return fromCache ? output + "\n\n(grafo do cache)" : output;
2239
+ } catch (error) {
2240
+ const message = error instanceof Error ? error.message : String(error);
2241
+ throw new Error(`Erro ao executar suggest: ${message}`);
2242
+ }
2243
+ }
2244
+ function collectSuggestions(targetPath, graph, allFiles, limit) {
2245
+ const suggestions = [];
2246
+ const addedPaths = /* @__PURE__ */ new Set();
2247
+ const targetNode = graph[targetPath];
2248
+ if (!targetNode) {
2249
+ return suggestions;
2250
+ }
2251
+ for (const dep of targetNode.adjacentTo) {
2252
+ if (addedPaths.has(dep)) continue;
2253
+ addedPaths.add(dep);
2254
+ const category = detectCategory(dep);
2255
+ if (category === "type") {
2256
+ suggestions.push({
2257
+ path: dep,
2258
+ category,
2259
+ reason: "Define tipos usados",
2260
+ priority: "critical"
2261
+ });
2262
+ } else {
2263
+ suggestions.push({
2264
+ path: dep,
2265
+ category,
2266
+ reason: "Importado diretamente",
2267
+ priority: "high"
2268
+ });
2269
+ }
2270
+ }
2271
+ const upstream = findUpstream(targetPath, graph);
2272
+ const upstreamLimited = upstream.slice(0, 5);
2273
+ for (const file of upstreamLimited) {
2274
+ if (addedPaths.has(file)) continue;
2275
+ addedPaths.add(file);
2276
+ suggestions.push({
2277
+ path: file,
2278
+ category: detectCategory(file),
2279
+ reason: "Usa este arquivo",
2280
+ priority: "medium"
2281
+ });
2282
+ }
2283
+ const relatedTests = findRelatedTests(targetPath, allFiles);
2284
+ for (const testFile of relatedTests) {
2285
+ if (addedPaths.has(testFile)) continue;
2286
+ addedPaths.add(testFile);
2287
+ suggestions.push({
2288
+ path: testFile,
2289
+ category: "test",
2290
+ reason: "Testa este arquivo",
2291
+ priority: "low"
2292
+ });
2293
+ }
2294
+ const priorityOrder = {
2295
+ critical: 0,
2296
+ high: 1,
2297
+ medium: 2,
2298
+ low: 3
2299
+ };
2300
+ suggestions.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
2301
+ return suggestions.slice(0, limit);
2302
+ }
2303
+ function findUpstream(targetPath, graph) {
2304
+ const upstream = [];
2305
+ for (const [file, node] of Object.entries(graph)) {
2306
+ if (node.adjacentTo.includes(targetPath)) {
2307
+ upstream.push(file);
2308
+ }
2309
+ }
2310
+ return upstream;
2311
+ }
2312
+ function findRelatedTests(targetPath, allFiles) {
2313
+ const targetName = targetPath.split("/").pop() || "";
2314
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2315
+ const tests = [];
2316
+ for (const file of allFiles) {
2317
+ const fileName = file.split("/").pop() || "";
2318
+ if (fileName.includes(".test.") || fileName.includes(".spec.") || file.includes("/__tests__/")) {
2319
+ const testNameNoExt = fileName.replace(/\.(test|spec)\..*$/, "").replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2320
+ if (testNameNoExt.toLowerCase() === targetNameNoExt.toLowerCase()) {
2321
+ tests.push(file);
2322
+ }
2323
+ }
2324
+ }
2325
+ return tests;
2326
+ }
2327
+ function findTargetFile2(target, allFiles) {
2328
+ const normalizedTarget = target.replace(/\\/g, "/");
2329
+ if (allFiles.includes(normalizedTarget)) {
2330
+ return normalizedTarget;
2331
+ }
2332
+ const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
2333
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2334
+ const matches = [];
2335
+ for (const file of allFiles) {
2336
+ const fileName = file.split("/").pop()?.toLowerCase() || "";
2337
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2338
+ if (fileNameNoExt === targetNameNoExt) {
2339
+ matches.unshift(file);
2340
+ } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2341
+ matches.push(file);
2342
+ }
2343
+ }
2344
+ if (matches.length === 1) {
2345
+ return matches[0];
2346
+ }
2347
+ if (matches.length > 1) {
2348
+ return matches[0];
2349
+ }
2350
+ return null;
2351
+ }
2352
+ function formatNotFound2(target, allFiles) {
2353
+ const normalizedTarget = target.toLowerCase();
2354
+ const similar = allFiles.filter((f) => {
2355
+ const fileName = f.split("/").pop()?.toLowerCase() || "";
2356
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2357
+ return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance2(fileNameNoExt, normalizedTarget) <= 3;
2358
+ }).slice(0, 5);
2359
+ let out = `Arquivo nao encontrado no indice: "${target}"
2360
+
2361
+ `;
2362
+ out += `Total de arquivos indexados: ${allFiles.length}
2363
+
2364
+ `;
2365
+ if (similar.length > 0) {
2366
+ out += `Arquivos com nome similar:
2367
+ `;
2368
+ for (const s of similar) {
2369
+ out += ` - ${s}
2370
+ `;
2371
+ }
2372
+ out += `
2373
+ `;
2374
+ }
2375
+ out += `Dicas:
2376
+ `;
2377
+ out += ` - Use o caminho relativo: src/components/Header.tsx
2378
+ `;
2379
+ out += ` - Ou apenas o nome do arquivo: Header
2380
+ `;
2381
+ out += ` - Verifique se o arquivo esta em uma pasta incluida no scan
2382
+ `;
2383
+ return out;
2384
+ }
2385
+ function levenshteinDistance2(a, b) {
2386
+ const matrix = [];
2387
+ for (let i = 0; i <= b.length; i++) {
2388
+ matrix[i] = [i];
2389
+ }
2390
+ for (let j = 0; j <= a.length; j++) {
2391
+ matrix[0][j] = j;
2392
+ }
2393
+ for (let i = 1; i <= b.length; i++) {
2394
+ for (let j = 1; j <= a.length; j++) {
2395
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2396
+ matrix[i][j] = matrix[i - 1][j - 1];
2397
+ } else {
2398
+ matrix[i][j] = Math.min(
2399
+ matrix[i - 1][j - 1] + 1,
2400
+ matrix[i][j - 1] + 1,
2401
+ matrix[i - 1][j] + 1
2402
+ );
2403
+ }
2404
+ }
2405
+ }
2406
+ return matrix[b.length][a.length];
2407
+ }
2408
+
2409
+ // src/commands/context.ts
2410
+ import { existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2411
+ import { join as join4, resolve, basename, extname as extname2 } from "path";
2412
+
2413
+ // src/ts/extractor.ts
2414
+ import { Project, SyntaxKind } from "ts-morph";
2415
+ function createProject(cwd) {
2416
+ return new Project({
2417
+ tsConfigFilePath: `${cwd}/tsconfig.json`,
2418
+ skipAddingFilesFromTsConfig: true
2419
+ });
2420
+ }
2421
+ function addSourceFile(project, filePath) {
2422
+ return project.addSourceFileAtPath(filePath);
2423
+ }
2424
+ function extractImports(sourceFile) {
2425
+ const imports = [];
2426
+ for (const importDecl of sourceFile.getImportDeclarations()) {
2427
+ const specifiers = [];
2428
+ const defaultImport = importDecl.getDefaultImport();
2429
+ if (defaultImport) {
2430
+ specifiers.push(defaultImport.getText());
2627
2431
  }
2432
+ for (const namedImport of importDecl.getNamedImports()) {
2433
+ const alias = namedImport.getAliasNode();
2434
+ if (alias) {
2435
+ specifiers.push(`${namedImport.getName()} as ${alias.getText()}`);
2436
+ } else {
2437
+ specifiers.push(namedImport.getName());
2438
+ }
2439
+ }
2440
+ const namespaceImport = importDecl.getNamespaceImport();
2441
+ if (namespaceImport) {
2442
+ specifiers.push(`* as ${namespaceImport.getText()}`);
2443
+ }
2444
+ imports.push({
2445
+ source: importDecl.getModuleSpecifierValue(),
2446
+ specifiers,
2447
+ isTypeOnly: importDecl.isTypeOnly()
2448
+ });
2628
2449
  }
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" });
2450
+ return imports;
2451
+ }
2452
+ function getJsDocDescription(node) {
2453
+ const jsDocs = node.getChildrenOfKind(SyntaxKind.JSDoc);
2454
+ if (jsDocs.length === 0) return void 0;
2455
+ const firstJsDoc = jsDocs[0];
2456
+ const comment = firstJsDoc.getComment();
2457
+ if (typeof comment === "string") {
2458
+ return comment.trim() || void 0;
2459
+ }
2460
+ if (Array.isArray(comment)) {
2461
+ const text = comment.filter((c) => c !== void 0).map((c) => c.getText()).join("").trim();
2462
+ return text || void 0;
2463
+ }
2464
+ return void 0;
2465
+ }
2466
+ function extractParams(params) {
2467
+ return params.map((p) => ({
2468
+ name: p.getName(),
2469
+ type: simplifyType(p.getType().getText())
2470
+ }));
2471
+ }
2472
+ function simplifyType(typeText) {
2473
+ let simplified = typeText.replace(/import\([^)]+\)\./g, "");
2474
+ if (simplified.length > 80) {
2475
+ simplified = simplified.slice(0, 77) + "...";
2476
+ }
2477
+ return simplified;
2478
+ }
2479
+ function extractFunctions(sourceFile) {
2480
+ const functions = [];
2481
+ for (const func of sourceFile.getFunctions()) {
2482
+ const name = func.getName();
2483
+ if (!name) continue;
2484
+ functions.push({
2485
+ name,
2486
+ params: extractParams(func.getParameters()),
2487
+ returnType: simplifyType(func.getReturnType().getText()),
2488
+ isAsync: func.isAsync(),
2489
+ isExported: func.isExported(),
2490
+ isArrowFunction: false,
2491
+ jsdoc: getJsDocDescription(func)
2492
+ });
2493
+ }
2494
+ for (const varStatement of sourceFile.getVariableStatements()) {
2495
+ const isExported = varStatement.isExported();
2496
+ for (const varDecl of varStatement.getDeclarations()) {
2497
+ const init = varDecl.getInitializer();
2498
+ if (!init) continue;
2499
+ if (init.getKind() === SyntaxKind.ArrowFunction) {
2500
+ const arrowFunc = init.asKind(SyntaxKind.ArrowFunction);
2501
+ if (!arrowFunc) continue;
2502
+ functions.push({
2503
+ name: varDecl.getName(),
2504
+ params: extractParams(arrowFunc.getParameters()),
2505
+ returnType: simplifyType(arrowFunc.getReturnType().getText()),
2506
+ isAsync: arrowFunc.isAsync(),
2507
+ isExported,
2508
+ isArrowFunction: true,
2509
+ jsdoc: getJsDocDescription(varStatement)
2510
+ });
2511
+ }
2512
+ if (init.getKind() === SyntaxKind.FunctionExpression) {
2513
+ const funcExpr = init.asKind(SyntaxKind.FunctionExpression);
2514
+ if (!funcExpr) continue;
2515
+ functions.push({
2516
+ name: varDecl.getName(),
2517
+ params: extractParams(funcExpr.getParameters()),
2518
+ returnType: simplifyType(funcExpr.getReturnType().getText()),
2519
+ isAsync: funcExpr.isAsync(),
2520
+ isExported,
2521
+ isArrowFunction: false,
2522
+ jsdoc: getJsDocDescription(varStatement)
2523
+ });
2524
+ }
2525
+ }
2526
+ }
2527
+ return functions;
2528
+ }
2529
+ function extractTypes(sourceFile) {
2530
+ const types = [];
2531
+ for (const iface of sourceFile.getInterfaces()) {
2532
+ types.push({
2533
+ name: iface.getName(),
2534
+ kind: "interface",
2535
+ definition: formatInterfaceDefinition(iface),
2536
+ isExported: iface.isExported()
2537
+ });
2538
+ }
2539
+ for (const typeAlias of sourceFile.getTypeAliases()) {
2540
+ types.push({
2541
+ name: typeAlias.getName(),
2542
+ kind: "type",
2543
+ definition: simplifyType(typeAlias.getType().getText()),
2544
+ isExported: typeAlias.isExported()
2545
+ });
2546
+ }
2547
+ for (const enumDecl of sourceFile.getEnums()) {
2548
+ types.push({
2549
+ name: enumDecl.getName(),
2550
+ kind: "enum",
2551
+ definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
2552
+ isExported: enumDecl.isExported()
2553
+ });
2554
+ }
2555
+ return types;
2556
+ }
2557
+ function formatInterfaceDefinition(iface) {
2558
+ const parts = [];
2559
+ const extendsClauses = iface.getExtends();
2560
+ if (extendsClauses.length > 0) {
2561
+ parts.push(`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`);
2562
+ }
2563
+ const props = iface.getProperties();
2564
+ for (const prop of props) {
2565
+ const propType = simplifyType(prop.getType().getText());
2566
+ parts.push(`${prop.getName()}: ${propType}`);
2567
+ }
2568
+ const methods = iface.getMethods();
2569
+ for (const method of methods) {
2570
+ const returnType = simplifyType(method.getReturnType().getText());
2571
+ parts.push(`${method.getName()}(): ${returnType}`);
2572
+ }
2573
+ return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
2574
+ }
2575
+ function extractExports(sourceFile) {
2576
+ const exports = [];
2577
+ for (const exportDecl of sourceFile.getExportDeclarations()) {
2578
+ for (const namedExport of exportDecl.getNamedExports()) {
2579
+ exports.push(namedExport.getName());
2580
+ }
2581
+ }
2582
+ for (const func of sourceFile.getFunctions()) {
2583
+ if (func.isExported() && func.getName()) {
2584
+ exports.push(func.getName());
2585
+ }
2586
+ }
2587
+ for (const varStatement of sourceFile.getVariableStatements()) {
2588
+ if (varStatement.isExported()) {
2589
+ for (const decl of varStatement.getDeclarations()) {
2590
+ exports.push(decl.getName());
2633
2591
  }
2634
2592
  }
2635
2593
  }
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
- }
2594
+ for (const iface of sourceFile.getInterfaces()) {
2595
+ if (iface.isExported()) {
2596
+ exports.push(iface.getName());
2597
+ }
2598
+ }
2599
+ for (const typeAlias of sourceFile.getTypeAliases()) {
2600
+ if (typeAlias.isExported()) {
2601
+ exports.push(typeAlias.getName());
2602
+ }
2603
+ }
2604
+ for (const enumDecl of sourceFile.getEnums()) {
2605
+ if (enumDecl.isExported()) {
2606
+ exports.push(enumDecl.getName());
2607
+ }
2608
+ }
2609
+ for (const classDecl of sourceFile.getClasses()) {
2610
+ if (classDecl.isExported() && classDecl.getName()) {
2611
+ exports.push(classDecl.getName());
2612
+ }
2613
+ }
2614
+ return [...new Set(exports)];
2615
+ }
2616
+
2617
+ // src/commands/context.ts
2618
+ async function context(target, options = {}) {
2619
+ const cwd = options.cwd || process.cwd();
2620
+ const format = options.format || "text";
2621
+ if (!target) {
2622
+ throw new Error("Target e obrigatorio. Exemplo: ai-tool context src/components/Button.tsx");
2623
+ }
2624
+ try {
2625
+ const targetPath = findTargetFile3(target, cwd);
2626
+ if (!targetPath) {
2627
+ return formatNotFound3(target, cwd);
2628
+ }
2629
+ const project = createProject(cwd);
2630
+ const absolutePath = resolve(cwd, targetPath);
2631
+ const sourceFile = addSourceFile(project, absolutePath);
2632
+ const imports = extractImports(sourceFile);
2633
+ const functions = extractFunctions(sourceFile);
2634
+ const types = extractTypes(sourceFile);
2635
+ const exports = extractExports(sourceFile);
2636
+ const result = {
2637
+ version: "1.0.0",
2638
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2639
+ file: targetPath,
2640
+ category: detectCategory(targetPath),
2641
+ imports,
2642
+ exports,
2643
+ functions,
2644
+ types
2645
+ };
2646
+ if (format === "json") {
2647
+ return JSON.stringify(result, null, 2);
2642
2648
  }
2649
+ return formatContextText(result);
2650
+ } catch (error) {
2651
+ const message = error instanceof Error ? error.message : String(error);
2652
+ throw new Error(`Erro ao executar context: ${message}`);
2643
2653
  }
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
2654
  }
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
- }
2655
+ var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2656
+ function findTargetFile3(target, cwd) {
2657
+ const normalizedTarget = target.replace(/\\/g, "/");
2658
+ const directPath = resolve(cwd, normalizedTarget);
2659
+ if (existsSync4(directPath) && isCodeFile2(directPath)) {
2660
+ return normalizedTarget;
2661
+ }
2662
+ for (const ext of CODE_EXTENSIONS2) {
2663
+ const withExt = directPath + ext;
2664
+ if (existsSync4(withExt)) {
2665
+ return normalizedTarget + ext;
2654
2666
  }
2655
2667
  }
2656
- for (const pattern of config.patterns) {
2657
- if (minimatch(filePath, pattern, { dot: true })) {
2658
- return true;
2668
+ const targetName = basename(normalizedTarget).toLowerCase();
2669
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2670
+ const allFiles = getAllCodeFiles(cwd);
2671
+ const matches = [];
2672
+ for (const file of allFiles) {
2673
+ const fileName = basename(file).toLowerCase();
2674
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2675
+ if (fileNameNoExt === targetNameNoExt) {
2676
+ matches.unshift(file);
2677
+ } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2678
+ matches.push(file);
2659
2679
  }
2660
2680
  }
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;
2681
+ if (matches.length > 0) {
2682
+ return matches[0];
2683
+ }
2684
+ return null;
2685
+ }
2686
+ function isCodeFile2(filePath) {
2687
+ const ext = extname2(filePath);
2688
+ return CODE_EXTENSIONS2.has(ext);
2689
+ }
2690
+ function getAllCodeFiles(dir, files = [], baseDir = dir) {
2691
+ try {
2692
+ const entries = readdirSync2(dir);
2693
+ for (const entry of entries) {
2694
+ const fullPath = join4(dir, entry);
2695
+ if (shouldIgnore(entry)) {
2696
+ continue;
2697
+ }
2698
+ try {
2699
+ const stat = statSync2(fullPath);
2700
+ if (stat.isDirectory()) {
2701
+ getAllCodeFiles(fullPath, files, baseDir);
2702
+ } else if (isCodeFile2(entry)) {
2703
+ const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
2704
+ files.push(relativePath);
2705
+ }
2706
+ } catch {
2666
2707
  }
2667
2708
  }
2709
+ } catch {
2668
2710
  }
2669
- return false;
2711
+ return files;
2670
2712
  }
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(" ");
2713
+ function shouldIgnore(name) {
2714
+ const ignoredDirs = [
2715
+ "node_modules",
2716
+ "dist",
2717
+ "build",
2718
+ ".git",
2719
+ ".next",
2720
+ ".cache",
2721
+ "coverage",
2722
+ ".turbo",
2723
+ ".vercel"
2724
+ ];
2725
+ return ignoredDirs.includes(name) || name.startsWith(".");
2679
2726
  }
2680
- function getAreaDescription(areaId, config) {
2681
- if (config.areas[areaId]?.description) {
2682
- return config.areas[areaId].description;
2727
+ function formatNotFound3(target, cwd) {
2728
+ const normalizedTarget = target.toLowerCase();
2729
+ const allFiles = getAllCodeFiles(cwd);
2730
+ const similar = allFiles.filter((f) => {
2731
+ const fileName = basename(f).toLowerCase();
2732
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2733
+ return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance3(fileNameNoExt, normalizedTarget) <= 3;
2734
+ }).slice(0, 5);
2735
+ let out = `Arquivo nao encontrado: "${target}"
2736
+
2737
+ `;
2738
+ out += `Total de arquivos no projeto: ${allFiles.length}
2739
+
2740
+ `;
2741
+ if (similar.length > 0) {
2742
+ out += `Arquivos com nome similar:
2743
+ `;
2744
+ for (const s of similar) {
2745
+ out += ` - ${s}
2746
+ `;
2747
+ }
2748
+ out += `
2749
+ `;
2683
2750
  }
2684
- return AREA_DESCRIPTIONS[areaId];
2751
+ out += `Dicas:
2752
+ `;
2753
+ out += ` - Use o caminho relativo: src/components/Header.tsx
2754
+ `;
2755
+ out += ` - Ou apenas o nome do arquivo: Header
2756
+ `;
2757
+ out += ` - Verifique se o arquivo existe e e um arquivo .ts/.tsx/.js/.jsx
2758
+ `;
2759
+ return out;
2685
2760
  }
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);
2761
+ function levenshteinDistance3(a, b) {
2762
+ const matrix = [];
2763
+ for (let i = 0; i <= b.length; i++) {
2764
+ matrix[i] = [i];
2765
+ }
2766
+ for (let j = 0; j <= a.length; j++) {
2767
+ matrix[0][j] = j;
2768
+ }
2769
+ for (let i = 1; i <= b.length; i++) {
2770
+ for (let j = 1; j <= a.length; j++) {
2771
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2772
+ matrix[i][j] = matrix[i - 1][j - 1];
2773
+ } else {
2774
+ matrix[i][j] = Math.min(
2775
+ matrix[i - 1][j - 1] + 1,
2776
+ matrix[i][j - 1] + 1,
2777
+ matrix[i - 1][j] + 1
2778
+ );
2779
+ }
2738
2780
  }
2739
2781
  }
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];
2782
+ return matrix[b.length][a.length];
2754
2783
  }
2755
2784
 
2756
2785
  // src/commands/areas.ts
2786
+ import { readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2787
+ import { join as join5, extname as extname3 } from "path";
2757
2788
  var CODE_EXTENSIONS3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2758
2789
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
2759
2790
  "node_modules",
@@ -2885,7 +2916,7 @@ async function area(target, options = {}) {
2885
2916
  const filterType = options.type;
2886
2917
  const full = options.full ?? false;
2887
2918
  if (!target) {
2888
- throw new Error("Nome da \xE1rea \xE9 obrigat\xF3rio. Exemplo: ai-tool area meus-pets");
2919
+ throw new Error("Nome da \xE1rea \xE9 obrigat\xF3rio. Exemplo: ai-tool area auth");
2889
2920
  }
2890
2921
  try {
2891
2922
  const config = readConfig(cwd);
@@ -3004,7 +3035,7 @@ function formatAreaNotFound(target, availableAreas) {
3004
3035
  }
3005
3036
  out += `\u{1F4A1} Dicas:
3006
3037
  `;
3007
- out += ` - Use o ID exato da \xE1rea (ex: ai-tool area meus-pets)
3038
+ out += ` - Use o ID exato da \xE1rea (ex: ai-tool area auth)
3008
3039
  `;
3009
3040
  out += ` - Use 'ai-tool areas' para listar todas as \xE1reas
3010
3041
  `;
@@ -3212,12 +3243,6 @@ export {
3212
3243
  getCacheDir,
3213
3244
  isCacheValid,
3214
3245
  invalidateCache,
3215
- map,
3216
- dead,
3217
- deadFix,
3218
- impact,
3219
- suggest,
3220
- context,
3221
3246
  configExists,
3222
3247
  readConfig,
3223
3248
  writeConfig,
@@ -3233,6 +3258,12 @@ export {
3233
3258
  getAreaName,
3234
3259
  getAreaDescription,
3235
3260
  inferFileDescription,
3261
+ map,
3262
+ dead,
3263
+ deadFix,
3264
+ impact,
3265
+ suggest,
3266
+ context,
3236
3267
  areas,
3237
3268
  area,
3238
3269
  areasInit,