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