@justmpm/ai-tool 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 += `
@@ -1014,1216 +1072,80 @@ 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 = [
@@ -2617,143 +1539,1312 @@ var AREA_DESCRIPTIONS = {
2617
1539
  app: "\xC1rea principal da aplica\xE7\xE3o"
2618
1540
  };
2619
1541
 
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" });
1542
+ // src/areas/detector.ts
1543
+ function detectFileAreas(filePath, config) {
1544
+ const normalizedPath = filePath.replace(/\\/g, "/");
1545
+ const matches = [];
1546
+ for (const [areaId, areaConfig] of Object.entries(config.areas)) {
1547
+ if (matchesAreaConfig(normalizedPath, areaConfig)) {
1548
+ matches.push({ area: areaId, priority: 200, source: "config" });
1549
+ }
1550
+ }
1551
+ for (const { pattern, area: area2, priority } of FOLDER_PATTERNS) {
1552
+ if (pattern.test(normalizedPath)) {
1553
+ if (!matches.some((m) => m.area === area2 && m.source === "config")) {
1554
+ matches.push({ area: area2, priority, source: "folder" });
1555
+ }
1556
+ }
1557
+ }
1558
+ const fileName = normalizedPath.split("/").pop() || "";
1559
+ for (const { keyword, area: area2, priority } of KEYWORD_PATTERNS) {
1560
+ if (keyword.test(fileName) || keyword.test(normalizedPath)) {
1561
+ if (!matches.some((m) => m.area === area2)) {
1562
+ matches.push({ area: area2, priority, source: "keyword" });
1563
+ }
1564
+ }
1565
+ }
1566
+ const sorted = matches.sort((a, b) => b.priority - a.priority);
1567
+ const unique = [...new Set(sorted.map((m) => m.area))];
1568
+ return unique;
1569
+ }
1570
+ function matchesAreaConfig(filePath, config) {
1571
+ if (config.exclude) {
1572
+ for (const pattern of config.exclude) {
1573
+ if (minimatch(filePath, pattern, { dot: true })) {
1574
+ return false;
1575
+ }
1576
+ }
1577
+ }
1578
+ for (const pattern of config.patterns) {
1579
+ if (minimatch(filePath, pattern, { dot: true })) {
1580
+ return true;
1581
+ }
1582
+ }
1583
+ if (config.keywords) {
1584
+ const lowerPath = filePath.toLowerCase();
1585
+ for (const keyword of config.keywords) {
1586
+ if (lowerPath.includes(keyword.toLowerCase())) {
1587
+ return true;
1588
+ }
1589
+ }
1590
+ }
1591
+ return false;
1592
+ }
1593
+ function getAreaName(areaId, config) {
1594
+ if (config.areas[areaId]?.name) {
1595
+ return config.areas[areaId].name;
1596
+ }
1597
+ if (AREA_NAMES[areaId]) {
1598
+ return AREA_NAMES[areaId];
1599
+ }
1600
+ return areaId.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1601
+ }
1602
+ function getAreaDescription(areaId, config) {
1603
+ if (config.areas[areaId]?.description) {
1604
+ return config.areas[areaId].description;
1605
+ }
1606
+ return AREA_DESCRIPTIONS[areaId];
1607
+ }
1608
+ function inferFileDescription(filePath, category) {
1609
+ const fileName = filePath.split("/").pop() || "";
1610
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1611
+ const patterns = [
1612
+ // Pages
1613
+ { pattern: /^page$/, description: () => "P\xE1gina principal" },
1614
+ { pattern: /^layout$/, description: () => "Layout" },
1615
+ { pattern: /^loading$/, description: () => "Estado de loading" },
1616
+ { pattern: /^error$/, description: () => "P\xE1gina de erro" },
1617
+ { pattern: /^not-found$/, description: () => "P\xE1gina 404" },
1618
+ // Components com sufixo
1619
+ { pattern: /(.+)PageClient$/, description: (m) => `Client component da p\xE1gina ${m[1]}` },
1620
+ { pattern: /(.+)Dialog$/, description: (m) => `Dialog de ${m[1].toLowerCase()}` },
1621
+ { pattern: /(.+)Modal$/, description: (m) => `Modal de ${m[1].toLowerCase()}` },
1622
+ { pattern: /(.+)Form$/, description: (m) => `Formul\xE1rio de ${m[1].toLowerCase()}` },
1623
+ { pattern: /(.+)Card$/, description: (m) => `Card de ${m[1].toLowerCase()}` },
1624
+ { pattern: /(.+)List$/, description: (m) => `Lista de ${m[1].toLowerCase()}` },
1625
+ { pattern: /(.+)Table$/, description: (m) => `Tabela de ${m[1].toLowerCase()}` },
1626
+ { pattern: /(.+)Manager$/, description: (m) => `Gerenciador de ${m[1].toLowerCase()}` },
1627
+ { pattern: /(.+)Provider$/, description: (m) => `Provider de ${m[1].toLowerCase()}` },
1628
+ { pattern: /(.+)Context$/, description: (m) => `Context de ${m[1].toLowerCase()}` },
1629
+ { pattern: /(.+)Step$/, description: (m) => `Step: ${m[1]}` },
1630
+ { pattern: /(.+)Tab$/, description: (m) => `Aba: ${m[1]}` },
1631
+ { pattern: /(.+)Section$/, description: (m) => `Se\xE7\xE3o: ${m[1]}` },
1632
+ { pattern: /(.+)Header$/, description: (m) => `Header de ${m[1].toLowerCase()}` },
1633
+ { pattern: /(.+)Footer$/, description: (m) => `Footer de ${m[1].toLowerCase()}` },
1634
+ { pattern: /(.+)Skeleton$/, description: (m) => `Skeleton de ${m[1].toLowerCase()}` },
1635
+ { pattern: /(.+)Chip$/, description: (m) => `Chip de ${m[1].toLowerCase()}` },
1636
+ { pattern: /(.+)Badge$/, description: (m) => `Badge de ${m[1].toLowerCase()}` },
1637
+ { pattern: /(.+)Button$/, description: (m) => `Bot\xE3o ${m[1].toLowerCase()}` },
1638
+ { pattern: /(.+)Icon$/, description: (m) => `\xCDcone ${m[1].toLowerCase()}` },
1639
+ // Hooks
1640
+ { pattern: /^use(.+)$/, description: (m) => `Hook de ${m[1].toLowerCase()}` },
1641
+ // Types/Schemas
1642
+ { pattern: /(.+)\.types$/, description: (m) => `Tipos de ${m[1].toLowerCase()}` },
1643
+ { pattern: /(.+)Schemas?$/, description: (m) => `Schema de ${m[1].toLowerCase()}` },
1644
+ // Utils
1645
+ { pattern: /(.+)Helpers?$/, description: (m) => `Helpers de ${m[1].toLowerCase()}` },
1646
+ { pattern: /(.+)Utils?$/, description: (m) => `Utilit\xE1rios de ${m[1].toLowerCase()}` },
1647
+ { pattern: /(.+)Formatters?$/, description: (m) => `Formatador de ${m[1].toLowerCase()}` },
1648
+ { pattern: /(.+)Validators?$/, description: (m) => `Validador de ${m[1].toLowerCase()}` },
1649
+ { pattern: /(.+)Mappers?$/, description: (m) => `Mapper de ${m[1].toLowerCase()}` },
1650
+ { pattern: /(.+)Converters?$/, description: (m) => `Conversor de ${m[1].toLowerCase()}` },
1651
+ // Services
1652
+ { pattern: /(.+)Service$/, description: (m) => `Servi\xE7o de ${m[1].toLowerCase()}` },
1653
+ // Index
1654
+ { pattern: /^index$/, description: () => "Export principal" }
1655
+ ];
1656
+ for (const { pattern, description } of patterns) {
1657
+ const match = fileNameNoExt.match(pattern);
1658
+ if (match) {
1659
+ return description(match);
1660
+ }
1661
+ }
1662
+ const categoryDescriptions = {
1663
+ page: "P\xE1gina",
1664
+ layout: "Layout",
1665
+ route: "API Route",
1666
+ component: "Componente",
1667
+ hook: "Hook",
1668
+ service: "Servi\xE7o",
1669
+ store: "Store",
1670
+ util: "Utilit\xE1rio",
1671
+ type: "Tipos",
1672
+ config: "Configura\xE7\xE3o",
1673
+ test: "Teste"
1674
+ };
1675
+ return categoryDescriptions[category];
1676
+ }
1677
+
1678
+ // src/commands/map.ts
1679
+ async function map(options = {}) {
1680
+ const cwd = options.cwd || process.cwd();
1681
+ const format = options.format || "text";
1682
+ const useCache = options.cache !== false;
1683
+ const full = options.full ?? false;
1684
+ if (useCache && isCacheValid(cwd)) {
1685
+ const cached = getCachedMapResult(cwd);
1686
+ if (cached) {
1687
+ const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
1688
+ if (format === "json") {
1689
+ return JSON.stringify(result, null, 2);
1690
+ }
1691
+ if (full) {
1692
+ return formatMapText(result) + "\n\n\u{1F4E6} (resultado do cache)";
1693
+ }
1694
+ const areasInfo = detectAreasInfo(cwd, result.files.map((f) => f.path));
1695
+ return formatMapSummary(result, areasInfo) + "\n\u{1F4E6} (cache)";
1696
+ }
1697
+ }
1698
+ try {
1699
+ const { getStructure, useGraph } = await skott({
1700
+ cwd,
1701
+ includeBaseDir: false,
1702
+ dependencyTracking: {
1703
+ thirdParty: options.trackDependencies ?? false,
1704
+ builtin: false,
1705
+ typeOnly: false
1706
+ },
1707
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1708
+ tsConfigPath: "tsconfig.json"
1709
+ });
1710
+ const structure = getStructure();
1711
+ const graphApi = useGraph();
1712
+ const { findCircularDependencies } = graphApi;
1713
+ if (useCache) {
1714
+ const graphData = {
1715
+ graph: structure.graph,
1716
+ files: Object.keys(structure.graph),
1717
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1718
+ };
1719
+ cacheGraph(cwd, graphData);
1720
+ }
1721
+ const files = Object.entries(structure.graph).map(([path]) => ({
1722
+ path,
1723
+ category: detectCategory(path),
1724
+ size: 0
1725
+ // Skott não fornece tamanho
1726
+ }));
1727
+ const folderMap = /* @__PURE__ */ new Map();
1728
+ for (const file of files) {
1729
+ const parts = file.path.split("/");
1730
+ if (parts.length > 1) {
1731
+ const folder = parts.slice(0, -1).join("/");
1732
+ if (!folderMap.has(folder)) {
1733
+ folderMap.set(folder, {
1734
+ path: folder,
1735
+ fileCount: 0,
1736
+ categories: {}
1737
+ });
1738
+ }
1739
+ const stats = folderMap.get(folder);
1740
+ stats.fileCount++;
1741
+ stats.categories[file.category] = (stats.categories[file.category] || 0) + 1;
1742
+ }
1743
+ }
1744
+ const categories = {};
1745
+ for (const file of files) {
1746
+ categories[file.category] = (categories[file.category] || 0) + 1;
1747
+ }
1748
+ const circular = findCircularDependencies();
1749
+ const result = {
1750
+ version: "1.0.0",
1751
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1752
+ cwd,
1753
+ summary: {
1754
+ totalFiles: files.length,
1755
+ totalFolders: folderMap.size,
1756
+ categories
1757
+ },
1758
+ folders: Array.from(folderMap.values()),
1759
+ files,
1760
+ circularDependencies: circular
1761
+ };
1762
+ if (useCache) {
1763
+ cacheMapResult(cwd, result);
1764
+ updateCacheMeta(cwd);
1765
+ }
1766
+ if (format === "json") {
1767
+ return JSON.stringify(result, null, 2);
1768
+ }
1769
+ if (full) {
1770
+ return formatMapText(result);
1771
+ }
1772
+ const areasInfo = detectAreasInfo(cwd, files.map((f) => f.path));
1773
+ return formatMapSummary(result, areasInfo);
1774
+ } catch (error) {
1775
+ const message = error instanceof Error ? error.message : String(error);
1776
+ throw new Error(`Erro ao executar map: ${message}`);
1777
+ }
1778
+ }
1779
+ function detectAreasInfo(cwd, filePaths) {
1780
+ try {
1781
+ const config = readConfig(cwd);
1782
+ const areaSet = /* @__PURE__ */ new Set();
1783
+ let unmappedCount = 0;
1784
+ for (const filePath of filePaths) {
1785
+ const areas2 = detectFileAreas(filePath, config);
1786
+ if (areas2.length === 0) {
1787
+ unmappedCount++;
1788
+ } else {
1789
+ for (const areaId of areas2) {
1790
+ areaSet.add(areaId);
1791
+ }
1792
+ }
1793
+ }
1794
+ const areaIds = Array.from(areaSet).sort();
1795
+ const names = areaIds.map((id) => getAreaName(id, config));
1796
+ return {
1797
+ names,
1798
+ total: areaIds.length,
1799
+ unmappedCount
1800
+ };
1801
+ } catch {
1802
+ return { names: [], total: 0, unmappedCount: 0 };
1803
+ }
1804
+ }
1805
+
1806
+ // src/commands/dead.ts
1807
+ import { execSync } from "child_process";
1808
+ async function dead(options = {}) {
1809
+ const cwd = options.cwd || process.cwd();
1810
+ const format = options.format || "text";
1811
+ const useCache = options.cache !== false;
1812
+ if (useCache && isCacheValid(cwd)) {
1813
+ const cached = getCachedDeadResult(cwd);
1814
+ if (cached) {
1815
+ const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
1816
+ if (format === "json") {
1817
+ return JSON.stringify(result, null, 2);
1818
+ }
1819
+ return formatDeadText(result) + "\n\n\u{1F4E6} (resultado do cache)";
1820
+ }
1821
+ }
1822
+ try {
1823
+ let knipOutput;
1824
+ try {
1825
+ const output = execSync("npx knip --reporter=json", {
1826
+ cwd,
1827
+ encoding: "utf-8",
1828
+ maxBuffer: 50 * 1024 * 1024,
1829
+ stdio: ["pipe", "pipe", "pipe"]
1830
+ });
1831
+ knipOutput = JSON.parse(output || "{}");
1832
+ } catch (execError) {
1833
+ const error = execError;
1834
+ if (error.stdout) {
1835
+ try {
1836
+ knipOutput = JSON.parse(error.stdout);
1837
+ } catch {
1838
+ knipOutput = {};
1839
+ }
1840
+ } else {
1841
+ knipOutput = {};
1842
+ }
1843
+ }
1844
+ const rawFiles = knipOutput.files || [];
1845
+ const { filtered: filteredFiles, excluded: excludedFunctions } = filterCloudFunctionsFalsePositives(rawFiles, cwd);
1846
+ const deadFiles = filteredFiles.map((file) => ({
1847
+ path: file,
1848
+ category: detectCategory(file),
1849
+ type: "file"
1850
+ }));
1851
+ const hasFirebase = hasFirebaseFunctions(cwd);
1852
+ const firebaseInfo = hasFirebase ? { detected: true, excludedCount: excludedFunctions.length } : { detected: false, excludedCount: 0 };
1853
+ const deadExports = [];
1854
+ if (knipOutput.issues) {
1855
+ for (const issue of knipOutput.issues) {
1856
+ if (issue.symbol && issue.symbolType === "export") {
1857
+ deadExports.push({
1858
+ file: issue.file,
1859
+ export: issue.symbol
1860
+ });
1861
+ }
1862
+ }
1863
+ }
1864
+ const deadDependencies = [
1865
+ ...knipOutput.dependencies || [],
1866
+ ...knipOutput.devDependencies || []
1867
+ ];
1868
+ const result = {
1869
+ version: "1.0.0",
1870
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1871
+ cwd,
1872
+ summary: {
1873
+ totalDead: deadFiles.length + deadExports.length + deadDependencies.length,
1874
+ byType: {
1875
+ files: deadFiles.length,
1876
+ exports: deadExports.length,
1877
+ dependencies: deadDependencies.length
1878
+ }
1879
+ },
1880
+ files: deadFiles,
1881
+ exports: deadExports,
1882
+ dependencies: deadDependencies,
1883
+ // Metadata sobre filtros aplicados
1884
+ filters: {
1885
+ firebase: firebaseInfo,
1886
+ excludedFiles: excludedFunctions
1887
+ }
1888
+ };
1889
+ if (useCache) {
1890
+ cacheDeadResult(cwd, result);
1891
+ updateCacheMeta(cwd);
1892
+ }
1893
+ if (format === "json") {
1894
+ return JSON.stringify(result, null, 2);
1895
+ }
1896
+ return formatDeadText(result);
1897
+ } catch (error) {
1898
+ const message = error instanceof Error ? error.message : String(error);
1899
+ throw new Error(`Erro ao executar dead: ${message}`);
1900
+ }
1901
+ }
1902
+ async function deadFix(options = {}) {
1903
+ const cwd = options.cwd || process.cwd();
1904
+ try {
1905
+ const output = execSync("npx knip --fix", {
1906
+ cwd,
1907
+ encoding: "utf-8",
1908
+ maxBuffer: 50 * 1024 * 1024
1909
+ });
1910
+ return `\u2705 Fix executado com sucesso!
1911
+
1912
+ ${output}`;
1913
+ } catch (error) {
1914
+ const message = error instanceof Error ? error.message : String(error);
1915
+ throw new Error(`Erro ao executar fix: ${message}`);
1916
+ }
1917
+ }
1918
+
1919
+ // src/commands/impact.ts
1920
+ import skott2 from "skott";
1921
+ async function impact(target, options = {}) {
1922
+ const cwd = options.cwd || process.cwd();
1923
+ const format = options.format || "text";
1924
+ const useCache = options.cache !== false;
1925
+ if (!target) {
1926
+ throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
1927
+ }
1928
+ try {
1929
+ let graph;
1930
+ let allFiles = [];
1931
+ let findCircular = () => [];
1932
+ let fromCache = false;
1933
+ if (useCache && isCacheValid(cwd)) {
1934
+ const cached = getCachedGraph(cwd);
1935
+ if (cached) {
1936
+ graph = cached.graph;
1937
+ allFiles = cached.files;
1938
+ findCircular = () => findCircularFromGraph(graph);
1939
+ fromCache = true;
1940
+ }
1941
+ }
1942
+ if (!graph) {
1943
+ const { getStructure, useGraph } = await skott2({
1944
+ cwd,
1945
+ includeBaseDir: false,
1946
+ dependencyTracking: {
1947
+ thirdParty: false,
1948
+ builtin: false,
1949
+ typeOnly: false
1950
+ },
1951
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1952
+ tsConfigPath: "tsconfig.json"
1953
+ });
1954
+ const structure = getStructure();
1955
+ const graphApi = useGraph();
1956
+ graph = structure.graph;
1957
+ allFiles = Object.keys(graph);
1958
+ findCircular = () => graphApi.findCircularDependencies();
1959
+ if (useCache) {
1960
+ cacheGraph(cwd, {
1961
+ graph,
1962
+ files: allFiles,
1963
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1964
+ });
1965
+ updateCacheMeta(cwd);
1966
+ }
1967
+ }
1968
+ const targetPath = findTargetFile(target, allFiles);
1969
+ if (!targetPath) {
1970
+ return formatNotFound(target, allFiles);
1971
+ }
1972
+ const { directUpstream, indirectUpstream, directDownstream, indirectDownstream } = calculateDependencies(targetPath, graph);
1973
+ const dependingOn = [...directUpstream, ...indirectUpstream];
1974
+ const dependencies = [...directDownstream, ...indirectDownstream];
1975
+ const risks = detectRisks(targetPath, dependingOn, dependencies, findCircular);
1976
+ const suggestions = generateSuggestions(dependingOn, dependencies, risks);
1977
+ const result = {
1978
+ version: "1.0.0",
1979
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1980
+ target: targetPath,
1981
+ category: detectCategory(targetPath),
1982
+ upstream: {
1983
+ direct: directUpstream.map(toImpactFile(true)),
1984
+ indirect: indirectUpstream.map(toImpactFile(false)),
1985
+ total: dependingOn.length
1986
+ },
1987
+ downstream: {
1988
+ direct: directDownstream.map(toImpactFile(true)),
1989
+ indirect: indirectDownstream.map(toImpactFile(false)),
1990
+ total: dependencies.length
1991
+ },
1992
+ risks,
1993
+ suggestions
1994
+ };
1995
+ if (format === "json") {
1996
+ return JSON.stringify(result, null, 2);
1997
+ }
1998
+ const output = formatImpactText(result);
1999
+ return fromCache ? output + "\n\n\u{1F4E6} (grafo do cache)" : output;
2000
+ } catch (error) {
2001
+ const message = error instanceof Error ? error.message : String(error);
2002
+ throw new Error(`Erro ao executar impact: ${message}`);
2003
+ }
2004
+ }
2005
+ function calculateDependencies(targetPath, graph) {
2006
+ const targetNode = graph[targetPath];
2007
+ const directDownstream = targetNode ? targetNode.adjacentTo : [];
2008
+ const allDownstream = /* @__PURE__ */ new Set();
2009
+ const visited = /* @__PURE__ */ new Set();
2010
+ function collectDownstream(file) {
2011
+ if (visited.has(file)) return;
2012
+ visited.add(file);
2013
+ const node = graph[file];
2014
+ if (node) {
2015
+ for (const dep of node.adjacentTo) {
2016
+ allDownstream.add(dep);
2017
+ collectDownstream(dep);
2018
+ }
2019
+ }
2020
+ }
2021
+ collectDownstream(targetPath);
2022
+ const indirectDownstream = Array.from(allDownstream).filter(
2023
+ (f) => !directDownstream.includes(f)
2024
+ );
2025
+ const directUpstream = [];
2026
+ const allUpstream = /* @__PURE__ */ new Set();
2027
+ for (const [file, node] of Object.entries(graph)) {
2028
+ if (node.adjacentTo.includes(targetPath)) {
2029
+ directUpstream.push(file);
2030
+ }
2031
+ }
2032
+ visited.clear();
2033
+ function collectUpstream(file) {
2034
+ if (visited.has(file)) return;
2035
+ visited.add(file);
2036
+ for (const [f, node] of Object.entries(graph)) {
2037
+ if (node.adjacentTo.includes(file) && !visited.has(f)) {
2038
+ allUpstream.add(f);
2039
+ collectUpstream(f);
2040
+ }
2041
+ }
2042
+ }
2043
+ for (const file of directUpstream) {
2044
+ collectUpstream(file);
2045
+ }
2046
+ const indirectUpstream = Array.from(allUpstream).filter(
2047
+ (f) => !directUpstream.includes(f)
2048
+ );
2049
+ return {
2050
+ directUpstream,
2051
+ indirectUpstream,
2052
+ directDownstream,
2053
+ indirectDownstream
2054
+ };
2055
+ }
2056
+ function findCircularFromGraph(graph) {
2057
+ const cycles = [];
2058
+ const visited = /* @__PURE__ */ new Set();
2059
+ const stack = /* @__PURE__ */ new Set();
2060
+ const path = [];
2061
+ function dfs(node) {
2062
+ if (stack.has(node)) {
2063
+ const cycleStart = path.indexOf(node);
2064
+ if (cycleStart !== -1) {
2065
+ cycles.push(path.slice(cycleStart));
2066
+ }
2067
+ return;
2068
+ }
2069
+ if (visited.has(node)) return;
2070
+ visited.add(node);
2071
+ stack.add(node);
2072
+ path.push(node);
2073
+ const nodeData = graph[node];
2074
+ if (nodeData) {
2075
+ for (const dep of nodeData.adjacentTo) {
2076
+ dfs(dep);
2077
+ }
2078
+ }
2079
+ path.pop();
2080
+ stack.delete(node);
2081
+ }
2082
+ for (const node of Object.keys(graph)) {
2083
+ dfs(node);
2084
+ }
2085
+ return cycles;
2086
+ }
2087
+ function findTargetFile(target, allFiles) {
2088
+ const normalizedTarget = target.replace(/\\/g, "/");
2089
+ if (allFiles.includes(normalizedTarget)) {
2090
+ return normalizedTarget;
2091
+ }
2092
+ const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
2093
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2094
+ const matches = [];
2095
+ for (const file of allFiles) {
2096
+ const fileName = file.split("/").pop()?.toLowerCase() || "";
2097
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2098
+ if (fileNameNoExt === targetNameNoExt) {
2099
+ matches.unshift(file);
2100
+ } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2101
+ matches.push(file);
2102
+ }
2103
+ }
2104
+ if (matches.length === 1) {
2105
+ return matches[0];
2106
+ }
2107
+ if (matches.length > 1) {
2108
+ return matches[0];
2109
+ }
2110
+ return null;
2111
+ }
2112
+ function formatNotFound(target, allFiles) {
2113
+ const normalizedTarget = target.toLowerCase();
2114
+ const similar = allFiles.filter((f) => {
2115
+ const fileName = f.split("/").pop()?.toLowerCase() || "";
2116
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2117
+ return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance(fileNameNoExt, normalizedTarget) <= 3;
2118
+ }).slice(0, 5);
2119
+ let out = `\u274C Arquivo n\xE3o encontrado no \xEDndice: "${target}"
2120
+
2121
+ `;
2122
+ out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
2123
+
2124
+ `;
2125
+ if (similar.length > 0) {
2126
+ out += `\u{1F4DD} Arquivos com nome similar:
2127
+ `;
2128
+ for (const s of similar) {
2129
+ out += ` \u2022 ${s}
2130
+ `;
2131
+ }
2132
+ out += `
2133
+ `;
2134
+ }
2135
+ out += `\u{1F4A1} Dicas:
2136
+ `;
2137
+ out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
2138
+ `;
2139
+ out += ` \u2022 Ou apenas o nome do arquivo: Header
2140
+ `;
2141
+ out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
2142
+ `;
2143
+ return out;
2144
+ }
2145
+ function toImpactFile(isDirect) {
2146
+ return (path) => ({
2147
+ path,
2148
+ category: detectCategory(path),
2149
+ isDirect
2150
+ });
2151
+ }
2152
+ function detectRisks(targetPath, upstream, downstream, findCircular) {
2153
+ const risks = [];
2154
+ if (upstream.length >= 15) {
2155
+ risks.push({
2156
+ type: "widely-used",
2157
+ severity: "high",
2158
+ message: `Arquivo CR\xCDTICO: usado por ${upstream.length} arquivos \xFAnicos`
2159
+ });
2160
+ } else if (upstream.length >= 5) {
2161
+ risks.push({
2162
+ type: "widely-used",
2163
+ severity: "medium",
2164
+ message: `Arquivo compartilhado: usado por ${upstream.length} arquivos \xFAnicos`
2165
+ });
2166
+ }
2167
+ if (downstream.length >= 20) {
2168
+ risks.push({
2169
+ type: "deep-chain",
2170
+ severity: "medium",
2171
+ message: `Arquivo importa ${downstream.length} depend\xEAncias (considere dividir)`
2172
+ });
2173
+ } else if (downstream.length >= 10) {
2174
+ risks.push({
2175
+ type: "deep-chain",
2176
+ severity: "low",
2177
+ message: `Arquivo importa ${downstream.length} depend\xEAncias`
2178
+ });
2179
+ }
2180
+ const circular = findCircular();
2181
+ const targetCircular = circular.filter((cycle) => cycle.includes(targetPath));
2182
+ if (targetCircular.length > 0) {
2183
+ risks.push({
2184
+ type: "circular",
2185
+ severity: "medium",
2186
+ message: `Envolvido em ${targetCircular.length} depend\xEAncia${targetCircular.length > 1 ? "s" : ""} circular${targetCircular.length > 1 ? "es" : ""}`
2187
+ });
2188
+ }
2189
+ return risks;
2190
+ }
2191
+ function generateSuggestions(upstream, downstream, risks) {
2192
+ const suggestions = [];
2193
+ if (upstream.length > 0) {
2194
+ suggestions.push(
2195
+ `Verifique os ${upstream.length} arquivo(s) que importam este antes de modificar`
2196
+ );
2197
+ }
2198
+ if (upstream.length >= 10) {
2199
+ suggestions.push(`Considere criar testes para garantir que mudan\xE7as n\xE3o quebrem dependentes`);
2200
+ }
2201
+ if (downstream.length > 0) {
2202
+ suggestions.push(`Teste as ${downstream.length} depend\xEAncia(s) ap\xF3s mudan\xE7as`);
2203
+ }
2204
+ if (risks.some((r) => r.type === "circular")) {
2205
+ suggestions.push(`Considere resolver as depend\xEAncias circulares antes de refatorar`);
2206
+ }
2207
+ if (risks.some((r) => r.type === "widely-used" && r.severity === "high")) {
2208
+ suggestions.push(`Este arquivo \xE9 cr\xEDtico - planeje mudan\xE7as com cuidado`);
2209
+ }
2210
+ return suggestions;
2211
+ }
2212
+ function levenshteinDistance(a, b) {
2213
+ const matrix = [];
2214
+ for (let i = 0; i <= b.length; i++) {
2215
+ matrix[i] = [i];
2216
+ }
2217
+ for (let j = 0; j <= a.length; j++) {
2218
+ matrix[0][j] = j;
2219
+ }
2220
+ for (let i = 1; i <= b.length; i++) {
2221
+ for (let j = 1; j <= a.length; j++) {
2222
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2223
+ matrix[i][j] = matrix[i - 1][j - 1];
2224
+ } else {
2225
+ matrix[i][j] = Math.min(
2226
+ matrix[i - 1][j - 1] + 1,
2227
+ matrix[i][j - 1] + 1,
2228
+ matrix[i - 1][j] + 1
2229
+ );
2230
+ }
2231
+ }
2232
+ }
2233
+ return matrix[b.length][a.length];
2234
+ }
2235
+
2236
+ // src/commands/suggest.ts
2237
+ import skott3 from "skott";
2238
+ async function suggest(target, options = {}) {
2239
+ const cwd = options.cwd || process.cwd();
2240
+ const format = options.format || "text";
2241
+ const useCache = options.cache !== false;
2242
+ const limit = options.limit || 10;
2243
+ if (!target) {
2244
+ throw new Error("Target e obrigatorio. Exemplo: ai-tool suggest src/components/Button.tsx");
2245
+ }
2246
+ try {
2247
+ let graph;
2248
+ let allFiles = [];
2249
+ let fromCache = false;
2250
+ if (useCache && isCacheValid(cwd)) {
2251
+ const cached = getCachedGraph(cwd);
2252
+ if (cached) {
2253
+ graph = cached.graph;
2254
+ allFiles = cached.files;
2255
+ fromCache = true;
2256
+ }
2257
+ }
2258
+ if (!graph) {
2259
+ const { getStructure } = await skott3({
2260
+ cwd,
2261
+ includeBaseDir: false,
2262
+ dependencyTracking: {
2263
+ thirdParty: false,
2264
+ builtin: false,
2265
+ typeOnly: false
2266
+ },
2267
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
2268
+ tsConfigPath: "tsconfig.json"
2269
+ });
2270
+ const structure = getStructure();
2271
+ graph = structure.graph;
2272
+ allFiles = Object.keys(graph);
2273
+ if (useCache) {
2274
+ cacheGraph(cwd, {
2275
+ graph,
2276
+ files: allFiles,
2277
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2278
+ });
2279
+ updateCacheMeta(cwd);
2280
+ }
2281
+ }
2282
+ const targetPath = findTargetFile2(target, allFiles);
2283
+ if (!targetPath) {
2284
+ return formatNotFound2(target, allFiles);
2285
+ }
2286
+ const suggestions = collectSuggestions(targetPath, graph, allFiles, limit);
2287
+ const result = {
2288
+ version: "1.0.0",
2289
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2290
+ target: targetPath,
2291
+ category: detectCategory(targetPath),
2292
+ suggestions
2293
+ };
2294
+ if (format === "json") {
2295
+ return JSON.stringify(result, null, 2);
2296
+ }
2297
+ const output = formatSuggestText(result);
2298
+ return fromCache ? output + "\n\n(grafo do cache)" : output;
2299
+ } catch (error) {
2300
+ const message = error instanceof Error ? error.message : String(error);
2301
+ throw new Error(`Erro ao executar suggest: ${message}`);
2302
+ }
2303
+ }
2304
+ function collectSuggestions(targetPath, graph, allFiles, limit) {
2305
+ const suggestions = [];
2306
+ const addedPaths = /* @__PURE__ */ new Set();
2307
+ const targetNode = graph[targetPath];
2308
+ if (!targetNode) {
2309
+ return suggestions;
2310
+ }
2311
+ for (const dep of targetNode.adjacentTo) {
2312
+ if (addedPaths.has(dep)) continue;
2313
+ addedPaths.add(dep);
2314
+ const category = detectCategory(dep);
2315
+ if (category === "type") {
2316
+ suggestions.push({
2317
+ path: dep,
2318
+ category,
2319
+ reason: "Define tipos usados",
2320
+ priority: "critical"
2321
+ });
2322
+ } else {
2323
+ suggestions.push({
2324
+ path: dep,
2325
+ category,
2326
+ reason: "Importado diretamente",
2327
+ priority: "high"
2328
+ });
2329
+ }
2330
+ }
2331
+ const upstream = findUpstream(targetPath, graph);
2332
+ const upstreamLimited = upstream.slice(0, 5);
2333
+ for (const file of upstreamLimited) {
2334
+ if (addedPaths.has(file)) continue;
2335
+ addedPaths.add(file);
2336
+ suggestions.push({
2337
+ path: file,
2338
+ category: detectCategory(file),
2339
+ reason: "Usa este arquivo",
2340
+ priority: "medium"
2341
+ });
2342
+ }
2343
+ const relatedTests = findRelatedTests(targetPath, allFiles);
2344
+ for (const testFile of relatedTests) {
2345
+ if (addedPaths.has(testFile)) continue;
2346
+ addedPaths.add(testFile);
2347
+ suggestions.push({
2348
+ path: testFile,
2349
+ category: "test",
2350
+ reason: "Testa este arquivo",
2351
+ priority: "low"
2352
+ });
2353
+ }
2354
+ const priorityOrder = {
2355
+ critical: 0,
2356
+ high: 1,
2357
+ medium: 2,
2358
+ low: 3
2359
+ };
2360
+ suggestions.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
2361
+ return suggestions.slice(0, limit);
2362
+ }
2363
+ function findUpstream(targetPath, graph) {
2364
+ const upstream = [];
2365
+ for (const [file, node] of Object.entries(graph)) {
2366
+ if (node.adjacentTo.includes(targetPath)) {
2367
+ upstream.push(file);
2368
+ }
2369
+ }
2370
+ return upstream;
2371
+ }
2372
+ function findRelatedTests(targetPath, allFiles) {
2373
+ const targetName = targetPath.split("/").pop() || "";
2374
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2375
+ const tests = [];
2376
+ for (const file of allFiles) {
2377
+ const fileName = file.split("/").pop() || "";
2378
+ if (fileName.includes(".test.") || fileName.includes(".spec.") || file.includes("/__tests__/")) {
2379
+ const testNameNoExt = fileName.replace(/\.(test|spec)\..*$/, "").replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2380
+ if (testNameNoExt.toLowerCase() === targetNameNoExt.toLowerCase()) {
2381
+ tests.push(file);
2382
+ }
2383
+ }
2384
+ }
2385
+ return tests;
2386
+ }
2387
+ function findTargetFile2(target, allFiles) {
2388
+ const normalizedTarget = target.replace(/\\/g, "/");
2389
+ if (allFiles.includes(normalizedTarget)) {
2390
+ return normalizedTarget;
2391
+ }
2392
+ const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
2393
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2394
+ const matches = [];
2395
+ for (const file of allFiles) {
2396
+ const fileName = file.split("/").pop()?.toLowerCase() || "";
2397
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2398
+ if (fileNameNoExt === targetNameNoExt) {
2399
+ matches.unshift(file);
2400
+ } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2401
+ matches.push(file);
2402
+ }
2403
+ }
2404
+ if (matches.length === 1) {
2405
+ return matches[0];
2406
+ }
2407
+ if (matches.length > 1) {
2408
+ return matches[0];
2409
+ }
2410
+ return null;
2411
+ }
2412
+ function formatNotFound2(target, allFiles) {
2413
+ const normalizedTarget = target.toLowerCase();
2414
+ const similar = allFiles.filter((f) => {
2415
+ const fileName = f.split("/").pop()?.toLowerCase() || "";
2416
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2417
+ return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance2(fileNameNoExt, normalizedTarget) <= 3;
2418
+ }).slice(0, 5);
2419
+ let out = `Arquivo nao encontrado no indice: "${target}"
2420
+
2421
+ `;
2422
+ out += `Total de arquivos indexados: ${allFiles.length}
2423
+
2424
+ `;
2425
+ if (similar.length > 0) {
2426
+ out += `Arquivos com nome similar:
2427
+ `;
2428
+ for (const s of similar) {
2429
+ out += ` - ${s}
2430
+ `;
2431
+ }
2432
+ out += `
2433
+ `;
2434
+ }
2435
+ out += `Dicas:
2436
+ `;
2437
+ out += ` - Use o caminho relativo: src/components/Header.tsx
2438
+ `;
2439
+ out += ` - Ou apenas o nome do arquivo: Header
2440
+ `;
2441
+ out += ` - Verifique se o arquivo esta em uma pasta incluida no scan
2442
+ `;
2443
+ return out;
2444
+ }
2445
+ function levenshteinDistance2(a, b) {
2446
+ const matrix = [];
2447
+ for (let i = 0; i <= b.length; i++) {
2448
+ matrix[i] = [i];
2449
+ }
2450
+ for (let j = 0; j <= a.length; j++) {
2451
+ matrix[0][j] = j;
2452
+ }
2453
+ for (let i = 1; i <= b.length; i++) {
2454
+ for (let j = 1; j <= a.length; j++) {
2455
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2456
+ matrix[i][j] = matrix[i - 1][j - 1];
2457
+ } else {
2458
+ matrix[i][j] = Math.min(
2459
+ matrix[i - 1][j - 1] + 1,
2460
+ matrix[i][j - 1] + 1,
2461
+ matrix[i - 1][j] + 1
2462
+ );
2463
+ }
2464
+ }
2465
+ }
2466
+ return matrix[b.length][a.length];
2467
+ }
2468
+
2469
+ // src/commands/context.ts
2470
+ import { existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2471
+ import { join as join4, resolve, basename, extname as extname2 } from "path";
2472
+
2473
+ // src/ts/extractor.ts
2474
+ import { Project, SyntaxKind } from "ts-morph";
2475
+ function createProject(cwd) {
2476
+ return new Project({
2477
+ tsConfigFilePath: `${cwd}/tsconfig.json`,
2478
+ skipAddingFilesFromTsConfig: true
2479
+ });
2480
+ }
2481
+ function addSourceFile(project, filePath) {
2482
+ return project.addSourceFileAtPath(filePath);
2483
+ }
2484
+ function extractImports(sourceFile) {
2485
+ const imports = [];
2486
+ for (const importDecl of sourceFile.getImportDeclarations()) {
2487
+ const specifiers = [];
2488
+ const defaultImport = importDecl.getDefaultImport();
2489
+ if (defaultImport) {
2490
+ specifiers.push(defaultImport.getText());
2627
2491
  }
2492
+ for (const namedImport of importDecl.getNamedImports()) {
2493
+ const alias = namedImport.getAliasNode();
2494
+ if (alias) {
2495
+ specifiers.push(`${namedImport.getName()} as ${alias.getText()}`);
2496
+ } else {
2497
+ specifiers.push(namedImport.getName());
2498
+ }
2499
+ }
2500
+ const namespaceImport = importDecl.getNamespaceImport();
2501
+ if (namespaceImport) {
2502
+ specifiers.push(`* as ${namespaceImport.getText()}`);
2503
+ }
2504
+ imports.push({
2505
+ source: importDecl.getModuleSpecifierValue(),
2506
+ specifiers,
2507
+ isTypeOnly: importDecl.isTypeOnly()
2508
+ });
2628
2509
  }
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" });
2510
+ return imports;
2511
+ }
2512
+ function getJsDocDescription(node) {
2513
+ const jsDocs = node.getChildrenOfKind(SyntaxKind.JSDoc);
2514
+ if (jsDocs.length === 0) return void 0;
2515
+ const firstJsDoc = jsDocs[0];
2516
+ const comment = firstJsDoc.getComment();
2517
+ if (typeof comment === "string") {
2518
+ return comment.trim() || void 0;
2519
+ }
2520
+ if (Array.isArray(comment)) {
2521
+ const text = comment.filter((c) => c !== void 0).map((c) => c.getText()).join("").trim();
2522
+ return text || void 0;
2523
+ }
2524
+ return void 0;
2525
+ }
2526
+ function extractParams(params) {
2527
+ return params.map((p) => ({
2528
+ name: p.getName(),
2529
+ type: simplifyType(p.getType().getText())
2530
+ }));
2531
+ }
2532
+ function simplifyType(typeText) {
2533
+ let simplified = typeText.replace(/import\([^)]+\)\./g, "");
2534
+ if (simplified.length > 80) {
2535
+ simplified = simplified.slice(0, 77) + "...";
2536
+ }
2537
+ return simplified;
2538
+ }
2539
+ function extractFunctions(sourceFile) {
2540
+ const functions = [];
2541
+ for (const func of sourceFile.getFunctions()) {
2542
+ const name = func.getName();
2543
+ if (!name) continue;
2544
+ functions.push({
2545
+ name,
2546
+ params: extractParams(func.getParameters()),
2547
+ returnType: simplifyType(func.getReturnType().getText()),
2548
+ isAsync: func.isAsync(),
2549
+ isExported: func.isExported(),
2550
+ isArrowFunction: false,
2551
+ jsdoc: getJsDocDescription(func)
2552
+ });
2553
+ }
2554
+ for (const varStatement of sourceFile.getVariableStatements()) {
2555
+ const isExported = varStatement.isExported();
2556
+ for (const varDecl of varStatement.getDeclarations()) {
2557
+ const init = varDecl.getInitializer();
2558
+ if (!init) continue;
2559
+ if (init.getKind() === SyntaxKind.ArrowFunction) {
2560
+ const arrowFunc = init.asKind(SyntaxKind.ArrowFunction);
2561
+ if (!arrowFunc) continue;
2562
+ functions.push({
2563
+ name: varDecl.getName(),
2564
+ params: extractParams(arrowFunc.getParameters()),
2565
+ returnType: simplifyType(arrowFunc.getReturnType().getText()),
2566
+ isAsync: arrowFunc.isAsync(),
2567
+ isExported,
2568
+ isArrowFunction: true,
2569
+ jsdoc: getJsDocDescription(varStatement)
2570
+ });
2571
+ }
2572
+ if (init.getKind() === SyntaxKind.FunctionExpression) {
2573
+ const funcExpr = init.asKind(SyntaxKind.FunctionExpression);
2574
+ if (!funcExpr) continue;
2575
+ functions.push({
2576
+ name: varDecl.getName(),
2577
+ params: extractParams(funcExpr.getParameters()),
2578
+ returnType: simplifyType(funcExpr.getReturnType().getText()),
2579
+ isAsync: funcExpr.isAsync(),
2580
+ isExported,
2581
+ isArrowFunction: false,
2582
+ jsdoc: getJsDocDescription(varStatement)
2583
+ });
2584
+ }
2585
+ }
2586
+ }
2587
+ return functions;
2588
+ }
2589
+ function extractTypes(sourceFile) {
2590
+ const types = [];
2591
+ for (const iface of sourceFile.getInterfaces()) {
2592
+ types.push({
2593
+ name: iface.getName(),
2594
+ kind: "interface",
2595
+ definition: formatInterfaceDefinition(iface),
2596
+ isExported: iface.isExported()
2597
+ });
2598
+ }
2599
+ for (const typeAlias of sourceFile.getTypeAliases()) {
2600
+ types.push({
2601
+ name: typeAlias.getName(),
2602
+ kind: "type",
2603
+ definition: simplifyType(typeAlias.getType().getText()),
2604
+ isExported: typeAlias.isExported()
2605
+ });
2606
+ }
2607
+ for (const enumDecl of sourceFile.getEnums()) {
2608
+ types.push({
2609
+ name: enumDecl.getName(),
2610
+ kind: "enum",
2611
+ definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
2612
+ isExported: enumDecl.isExported()
2613
+ });
2614
+ }
2615
+ return types;
2616
+ }
2617
+ function formatInterfaceDefinition(iface) {
2618
+ const parts = [];
2619
+ const extendsClauses = iface.getExtends();
2620
+ if (extendsClauses.length > 0) {
2621
+ parts.push(`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`);
2622
+ }
2623
+ const props = iface.getProperties();
2624
+ for (const prop of props) {
2625
+ const propType = simplifyType(prop.getType().getText());
2626
+ parts.push(`${prop.getName()}: ${propType}`);
2627
+ }
2628
+ const methods = iface.getMethods();
2629
+ for (const method of methods) {
2630
+ const returnType = simplifyType(method.getReturnType().getText());
2631
+ parts.push(`${method.getName()}(): ${returnType}`);
2632
+ }
2633
+ return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
2634
+ }
2635
+ function extractExports(sourceFile) {
2636
+ const exports = [];
2637
+ for (const exportDecl of sourceFile.getExportDeclarations()) {
2638
+ for (const namedExport of exportDecl.getNamedExports()) {
2639
+ exports.push(namedExport.getName());
2640
+ }
2641
+ }
2642
+ for (const func of sourceFile.getFunctions()) {
2643
+ if (func.isExported() && func.getName()) {
2644
+ exports.push(func.getName());
2645
+ }
2646
+ }
2647
+ for (const varStatement of sourceFile.getVariableStatements()) {
2648
+ if (varStatement.isExported()) {
2649
+ for (const decl of varStatement.getDeclarations()) {
2650
+ exports.push(decl.getName());
2633
2651
  }
2634
2652
  }
2635
2653
  }
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
- }
2654
+ for (const iface of sourceFile.getInterfaces()) {
2655
+ if (iface.isExported()) {
2656
+ exports.push(iface.getName());
2657
+ }
2658
+ }
2659
+ for (const typeAlias of sourceFile.getTypeAliases()) {
2660
+ if (typeAlias.isExported()) {
2661
+ exports.push(typeAlias.getName());
2662
+ }
2663
+ }
2664
+ for (const enumDecl of sourceFile.getEnums()) {
2665
+ if (enumDecl.isExported()) {
2666
+ exports.push(enumDecl.getName());
2667
+ }
2668
+ }
2669
+ for (const classDecl of sourceFile.getClasses()) {
2670
+ if (classDecl.isExported() && classDecl.getName()) {
2671
+ exports.push(classDecl.getName());
2672
+ }
2673
+ }
2674
+ return [...new Set(exports)];
2675
+ }
2676
+
2677
+ // src/commands/context.ts
2678
+ async function context(target, options = {}) {
2679
+ const cwd = options.cwd || process.cwd();
2680
+ const format = options.format || "text";
2681
+ if (!target) {
2682
+ throw new Error("Target e obrigatorio. Exemplo: ai-tool context src/components/Button.tsx");
2683
+ }
2684
+ try {
2685
+ const targetPath = findTargetFile3(target, cwd);
2686
+ if (!targetPath) {
2687
+ return formatNotFound3(target, cwd);
2688
+ }
2689
+ const project = createProject(cwd);
2690
+ const absolutePath = resolve(cwd, targetPath);
2691
+ const sourceFile = addSourceFile(project, absolutePath);
2692
+ const imports = extractImports(sourceFile);
2693
+ const functions = extractFunctions(sourceFile);
2694
+ const types = extractTypes(sourceFile);
2695
+ const exports = extractExports(sourceFile);
2696
+ const result = {
2697
+ version: "1.0.0",
2698
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2699
+ file: targetPath,
2700
+ category: detectCategory(targetPath),
2701
+ imports,
2702
+ exports,
2703
+ functions,
2704
+ types
2705
+ };
2706
+ if (format === "json") {
2707
+ return JSON.stringify(result, null, 2);
2642
2708
  }
2709
+ return formatContextText(result);
2710
+ } catch (error) {
2711
+ const message = error instanceof Error ? error.message : String(error);
2712
+ throw new Error(`Erro ao executar context: ${message}`);
2643
2713
  }
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
2714
  }
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
- }
2715
+ var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2716
+ function findTargetFile3(target, cwd) {
2717
+ const normalizedTarget = target.replace(/\\/g, "/");
2718
+ const directPath = resolve(cwd, normalizedTarget);
2719
+ if (existsSync4(directPath) && isCodeFile2(directPath)) {
2720
+ return normalizedTarget;
2721
+ }
2722
+ for (const ext of CODE_EXTENSIONS2) {
2723
+ const withExt = directPath + ext;
2724
+ if (existsSync4(withExt)) {
2725
+ return normalizedTarget + ext;
2654
2726
  }
2655
2727
  }
2656
- for (const pattern of config.patterns) {
2657
- if (minimatch(filePath, pattern, { dot: true })) {
2658
- return true;
2728
+ const targetName = basename(normalizedTarget).toLowerCase();
2729
+ const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2730
+ const allFiles = getAllCodeFiles(cwd);
2731
+ const matches = [];
2732
+ for (const file of allFiles) {
2733
+ const fileName = basename(file).toLowerCase();
2734
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2735
+ if (fileNameNoExt === targetNameNoExt) {
2736
+ matches.unshift(file);
2737
+ } else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
2738
+ matches.push(file);
2659
2739
  }
2660
2740
  }
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;
2741
+ if (matches.length > 0) {
2742
+ return matches[0];
2743
+ }
2744
+ return null;
2745
+ }
2746
+ function isCodeFile2(filePath) {
2747
+ const ext = extname2(filePath);
2748
+ return CODE_EXTENSIONS2.has(ext);
2749
+ }
2750
+ function getAllCodeFiles(dir, files = [], baseDir = dir) {
2751
+ try {
2752
+ const entries = readdirSync2(dir);
2753
+ for (const entry of entries) {
2754
+ const fullPath = join4(dir, entry);
2755
+ if (shouldIgnore(entry)) {
2756
+ continue;
2757
+ }
2758
+ try {
2759
+ const stat = statSync2(fullPath);
2760
+ if (stat.isDirectory()) {
2761
+ getAllCodeFiles(fullPath, files, baseDir);
2762
+ } else if (isCodeFile2(entry)) {
2763
+ const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
2764
+ files.push(relativePath);
2765
+ }
2766
+ } catch {
2666
2767
  }
2667
2768
  }
2769
+ } catch {
2668
2770
  }
2669
- return false;
2771
+ return files;
2670
2772
  }
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(" ");
2773
+ function shouldIgnore(name) {
2774
+ const ignoredDirs = [
2775
+ "node_modules",
2776
+ "dist",
2777
+ "build",
2778
+ ".git",
2779
+ ".next",
2780
+ ".cache",
2781
+ "coverage",
2782
+ ".turbo",
2783
+ ".vercel"
2784
+ ];
2785
+ return ignoredDirs.includes(name) || name.startsWith(".");
2679
2786
  }
2680
- function getAreaDescription(areaId, config) {
2681
- if (config.areas[areaId]?.description) {
2682
- return config.areas[areaId].description;
2787
+ function formatNotFound3(target, cwd) {
2788
+ const normalizedTarget = target.toLowerCase();
2789
+ const allFiles = getAllCodeFiles(cwd);
2790
+ const similar = allFiles.filter((f) => {
2791
+ const fileName = basename(f).toLowerCase();
2792
+ const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2793
+ return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance3(fileNameNoExt, normalizedTarget) <= 3;
2794
+ }).slice(0, 5);
2795
+ let out = `Arquivo nao encontrado: "${target}"
2796
+
2797
+ `;
2798
+ out += `Total de arquivos no projeto: ${allFiles.length}
2799
+
2800
+ `;
2801
+ if (similar.length > 0) {
2802
+ out += `Arquivos com nome similar:
2803
+ `;
2804
+ for (const s of similar) {
2805
+ out += ` - ${s}
2806
+ `;
2807
+ }
2808
+ out += `
2809
+ `;
2683
2810
  }
2684
- return AREA_DESCRIPTIONS[areaId];
2811
+ out += `Dicas:
2812
+ `;
2813
+ out += ` - Use o caminho relativo: src/components/Header.tsx
2814
+ `;
2815
+ out += ` - Ou apenas o nome do arquivo: Header
2816
+ `;
2817
+ out += ` - Verifique se o arquivo existe e e um arquivo .ts/.tsx/.js/.jsx
2818
+ `;
2819
+ return out;
2685
2820
  }
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);
2821
+ function levenshteinDistance3(a, b) {
2822
+ const matrix = [];
2823
+ for (let i = 0; i <= b.length; i++) {
2824
+ matrix[i] = [i];
2825
+ }
2826
+ for (let j = 0; j <= a.length; j++) {
2827
+ matrix[0][j] = j;
2828
+ }
2829
+ for (let i = 1; i <= b.length; i++) {
2830
+ for (let j = 1; j <= a.length; j++) {
2831
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
2832
+ matrix[i][j] = matrix[i - 1][j - 1];
2833
+ } else {
2834
+ matrix[i][j] = Math.min(
2835
+ matrix[i - 1][j - 1] + 1,
2836
+ matrix[i][j - 1] + 1,
2837
+ matrix[i - 1][j] + 1
2838
+ );
2839
+ }
2738
2840
  }
2739
2841
  }
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];
2842
+ return matrix[b.length][a.length];
2754
2843
  }
2755
2844
 
2756
2845
  // src/commands/areas.ts
2846
+ import { readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2847
+ import { join as join5, extname as extname3 } from "path";
2757
2848
  var CODE_EXTENSIONS3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2758
2849
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
2759
2850
  "node_modules",
@@ -3194,7 +3285,10 @@ function getAllCodeFiles4(dir, files = [], baseDir = dir) {
3194
3285
  }
3195
3286
 
3196
3287
  // src/index.ts
3197
- var VERSION = "0.4.0";
3288
+ import { createRequire } from "module";
3289
+ var require2 = createRequire(import.meta.url);
3290
+ var pkg = require2("../package.json");
3291
+ var VERSION = pkg.version;
3198
3292
 
3199
3293
  export {
3200
3294
  detectCategory,
@@ -3209,12 +3303,6 @@ export {
3209
3303
  getCacheDir,
3210
3304
  isCacheValid,
3211
3305
  invalidateCache,
3212
- map,
3213
- dead,
3214
- deadFix,
3215
- impact,
3216
- suggest,
3217
- context,
3218
3306
  configExists,
3219
3307
  readConfig,
3220
3308
  writeConfig,
@@ -3230,6 +3318,12 @@ export {
3230
3318
  getAreaName,
3231
3319
  getAreaDescription,
3232
3320
  inferFileDescription,
3321
+ map,
3322
+ dead,
3323
+ deadFix,
3324
+ impact,
3325
+ suggest,
3326
+ context,
3233
3327
  areas,
3234
3328
  area,
3235
3329
  areasInit,