@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.
- package/README.md +83 -9
- package/dist/{chunk-33DHCY2H.js → chunk-IIUNJRGG.js} +1408 -1314
- package/dist/cli.js +9 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/{server-62WVGNJD.js → server-FG4LYSEG.js} +19 -248
- package/package.json +1 -1
|
@@ -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/
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
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
|
-
|
|
1139
|
+
config.descriptions[filePath] = description;
|
|
1140
|
+
writeConfig(cwd, config);
|
|
1390
1141
|
}
|
|
1391
|
-
function
|
|
1392
|
-
const
|
|
1393
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
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
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
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
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
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
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
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 (
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
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
|
|
2771
|
+
return files;
|
|
2670
2772
|
}
|
|
2671
|
-
function
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
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
|
|
2681
|
-
|
|
2682
|
-
|
|
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
|
-
|
|
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
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|