@justmpm/ai-tool 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/chunk-BIT47QXF.js +1852 -0
- package/dist/cli.js +63 -14
- package/dist/index.d.ts +89 -2
- package/dist/index.js +13 -3
- package/dist/server-OAPPO22L.js +343 -0
- package/package.json +7 -4
- package/dist/chunk-EGBEXF4G.js +0 -764
|
@@ -0,0 +1,1852 @@
|
|
|
1
|
+
// src/commands/map.ts
|
|
2
|
+
import skott from "skott";
|
|
3
|
+
|
|
4
|
+
// src/utils/detect.ts
|
|
5
|
+
function detectCategory(filePath) {
|
|
6
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
7
|
+
const fileName = normalized.split("/").pop() || "";
|
|
8
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
9
|
+
if (fileName.includes(".test.") || fileName.includes(".spec.") || normalized.includes("/__tests__/") || normalized.includes("/test/")) {
|
|
10
|
+
return "test";
|
|
11
|
+
}
|
|
12
|
+
if (isConfigFile(fileName)) {
|
|
13
|
+
return "config";
|
|
14
|
+
}
|
|
15
|
+
if (fileNameNoExt === "page") return "page";
|
|
16
|
+
if (fileNameNoExt === "layout") return "layout";
|
|
17
|
+
if (fileNameNoExt === "route") return "route";
|
|
18
|
+
if (["error", "global-error", "not-found", "loading", "template", "middleware", "default"].includes(
|
|
19
|
+
fileNameNoExt
|
|
20
|
+
)) {
|
|
21
|
+
return "page";
|
|
22
|
+
}
|
|
23
|
+
if (normalized.includes("/api/")) return "route";
|
|
24
|
+
if (normalized.includes("/pages/")) return "page";
|
|
25
|
+
if (fileNameNoExt.startsWith("use") && fileNameNoExt.length > 3) return "hook";
|
|
26
|
+
if (normalized.includes("/hooks/")) return "hook";
|
|
27
|
+
if (normalized.includes("/types/") || fileName.endsWith(".d.ts") || fileNameNoExt === "types" || fileNameNoExt === "interfaces") {
|
|
28
|
+
return "type";
|
|
29
|
+
}
|
|
30
|
+
if (normalized.includes("/services/") || fileNameNoExt.endsWith("service")) {
|
|
31
|
+
return "service";
|
|
32
|
+
}
|
|
33
|
+
if (normalized.includes("/store/") || normalized.includes("/stores/") || normalized.includes("/context/") || normalized.includes("/contexts/") || normalized.includes("/providers/")) {
|
|
34
|
+
return "store";
|
|
35
|
+
}
|
|
36
|
+
if (normalized.includes("/utils/") || normalized.includes("/lib/") || normalized.includes("/helpers/") || normalized.includes("/common/")) {
|
|
37
|
+
return "util";
|
|
38
|
+
}
|
|
39
|
+
if (normalized.includes("/components/") || normalized.includes("/ui/") || normalized.includes("/features/")) {
|
|
40
|
+
return "component";
|
|
41
|
+
}
|
|
42
|
+
return "other";
|
|
43
|
+
}
|
|
44
|
+
function isConfigFile(fileName) {
|
|
45
|
+
const patterns = [
|
|
46
|
+
/eslint\.config\./,
|
|
47
|
+
/prettier\.config\./,
|
|
48
|
+
/tailwind\.config\./,
|
|
49
|
+
/next\.config\./,
|
|
50
|
+
/vite\.config\./,
|
|
51
|
+
/tsconfig/,
|
|
52
|
+
/jest\.config/,
|
|
53
|
+
/vitest\.config/,
|
|
54
|
+
/postcss\.config/,
|
|
55
|
+
/babel\.config/,
|
|
56
|
+
/webpack\.config/,
|
|
57
|
+
/firebase-messaging-sw/,
|
|
58
|
+
/sw\./,
|
|
59
|
+
/service-worker/,
|
|
60
|
+
/knip\.config/,
|
|
61
|
+
/\.env/
|
|
62
|
+
];
|
|
63
|
+
return patterns.some((p) => p.test(fileName));
|
|
64
|
+
}
|
|
65
|
+
var categoryIcons = {
|
|
66
|
+
page: "\u{1F4C4}",
|
|
67
|
+
layout: "\u{1F532}",
|
|
68
|
+
route: "\u{1F6E3}\uFE0F",
|
|
69
|
+
component: "\u{1F9E9}",
|
|
70
|
+
hook: "\u{1FA9D}",
|
|
71
|
+
store: "\u{1F5C4}\uFE0F",
|
|
72
|
+
service: "\u2699\uFE0F",
|
|
73
|
+
util: "\u{1F527}",
|
|
74
|
+
type: "\u{1F4DD}",
|
|
75
|
+
config: "\u2699\uFE0F",
|
|
76
|
+
test: "\u{1F9EA}",
|
|
77
|
+
other: "\u{1F4C1}"
|
|
78
|
+
};
|
|
79
|
+
function isEntryPoint(filePath) {
|
|
80
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
81
|
+
const fileName = normalized.split("/").pop() || "";
|
|
82
|
+
const entryPoints = [
|
|
83
|
+
"main.tsx",
|
|
84
|
+
"main.ts",
|
|
85
|
+
"main.jsx",
|
|
86
|
+
"main.js",
|
|
87
|
+
"index.tsx",
|
|
88
|
+
"index.ts",
|
|
89
|
+
"app.tsx",
|
|
90
|
+
"app.ts"
|
|
91
|
+
];
|
|
92
|
+
if (entryPoints.includes(fileName)) {
|
|
93
|
+
const depth = normalized.split("/").length;
|
|
94
|
+
if (depth <= 2) return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
var CODE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
99
|
+
function isCodeFile(filePath) {
|
|
100
|
+
return CODE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/formatters/text.ts
|
|
104
|
+
function formatMapText(result) {
|
|
105
|
+
let out = "";
|
|
106
|
+
out += `
|
|
107
|
+
`;
|
|
108
|
+
out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
109
|
+
`;
|
|
110
|
+
out += `\u2551 \u{1F4C1} PROJECT MAP \u2551
|
|
111
|
+
`;
|
|
112
|
+
out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
113
|
+
|
|
114
|
+
`;
|
|
115
|
+
out += `\u{1F4CA} RESUMO
|
|
116
|
+
`;
|
|
117
|
+
out += ` Arquivos: ${result.summary.totalFiles}
|
|
118
|
+
`;
|
|
119
|
+
out += ` Pastas: ${result.summary.totalFolders}
|
|
120
|
+
`;
|
|
121
|
+
out += ` Diret\xF3rio: ${result.cwd}
|
|
122
|
+
|
|
123
|
+
`;
|
|
124
|
+
out += `\u{1F4C2} CATEGORIAS
|
|
125
|
+
`;
|
|
126
|
+
const catOrder = [
|
|
127
|
+
"page",
|
|
128
|
+
"layout",
|
|
129
|
+
"route",
|
|
130
|
+
"component",
|
|
131
|
+
"hook",
|
|
132
|
+
"service",
|
|
133
|
+
"store",
|
|
134
|
+
"util",
|
|
135
|
+
"type",
|
|
136
|
+
"config",
|
|
137
|
+
"test",
|
|
138
|
+
"other"
|
|
139
|
+
];
|
|
140
|
+
for (const cat of catOrder) {
|
|
141
|
+
const count = result.summary.categories[cat];
|
|
142
|
+
if (count) {
|
|
143
|
+
const icon = categoryIcons[cat];
|
|
144
|
+
out += ` ${icon} ${cat.padEnd(12)} ${count}
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
out += `
|
|
149
|
+
\u{1F4C1} ESTRUTURA (Top 15 pastas)
|
|
150
|
+
`;
|
|
151
|
+
const topFolders = result.folders.sort((a, b) => b.fileCount - a.fileCount).slice(0, 15);
|
|
152
|
+
for (const folder of topFolders) {
|
|
153
|
+
out += ` ${folder.path}/ (${folder.fileCount} arquivos)
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
if (result.folders.length > 15) {
|
|
157
|
+
out += ` ... e mais ${result.folders.length - 15} pastas
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
if (result.circularDependencies.length > 0) {
|
|
161
|
+
out += `
|
|
162
|
+
\u26A0\uFE0F DEPEND\xCANCIAS CIRCULARES (${result.circularDependencies.length})
|
|
163
|
+
`;
|
|
164
|
+
for (const cycle of result.circularDependencies.slice(0, 5)) {
|
|
165
|
+
out += ` ${cycle.join(" \u2192 ")}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
if (result.circularDependencies.length > 5) {
|
|
169
|
+
out += ` ... e mais ${result.circularDependencies.length - 5}
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
function formatDeadText(result) {
|
|
176
|
+
let out = "";
|
|
177
|
+
out += `
|
|
178
|
+
`;
|
|
179
|
+
out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
180
|
+
`;
|
|
181
|
+
out += `\u2551 \u{1F480} DEAD CODE \u2551
|
|
182
|
+
`;
|
|
183
|
+
out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
184
|
+
|
|
185
|
+
`;
|
|
186
|
+
if (result.summary.totalDead === 0) {
|
|
187
|
+
out += `\u2705 Nenhum c\xF3digo morto encontrado!
|
|
188
|
+
`;
|
|
189
|
+
out += ` Todos os arquivos e exports est\xE3o sendo utilizados.
|
|
190
|
+
`;
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
out += `\u{1F4CA} RESUMO
|
|
194
|
+
`;
|
|
195
|
+
out += ` Total: ${result.summary.totalDead} itens n\xE3o utilizados
|
|
196
|
+
`;
|
|
197
|
+
out += ` Arquivos \xF3rf\xE3os: ${result.summary.byType.files}
|
|
198
|
+
`;
|
|
199
|
+
out += ` Exports n\xE3o usados: ${result.summary.byType.exports}
|
|
200
|
+
`;
|
|
201
|
+
out += ` Depend\xEAncias n\xE3o usadas: ${result.summary.byType.dependencies}
|
|
202
|
+
|
|
203
|
+
`;
|
|
204
|
+
if (result.files.length > 0) {
|
|
205
|
+
out += `\u{1F5D1}\uFE0F ARQUIVOS \xD3RF\xC3OS (${result.files.length})
|
|
206
|
+
`;
|
|
207
|
+
out += ` Arquivos que ningu\xE9m importa:
|
|
208
|
+
|
|
209
|
+
`;
|
|
210
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
211
|
+
for (const file of result.files) {
|
|
212
|
+
if (!byCategory.has(file.category)) {
|
|
213
|
+
byCategory.set(file.category, []);
|
|
214
|
+
}
|
|
215
|
+
byCategory.get(file.category).push(file);
|
|
216
|
+
}
|
|
217
|
+
for (const [category, files] of byCategory) {
|
|
218
|
+
const icon = categoryIcons[category];
|
|
219
|
+
out += ` ${icon} ${category}/ (${files.length})
|
|
220
|
+
`;
|
|
221
|
+
for (const file of files.slice(0, 5)) {
|
|
222
|
+
out += ` ${file.path}
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
if (files.length > 5) {
|
|
226
|
+
out += ` ... e mais ${files.length - 5}
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
out += `
|
|
230
|
+
`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (result.exports.length > 0) {
|
|
234
|
+
out += `\u{1F4E4} EXPORTS N\xC3O USADOS (${result.exports.length})
|
|
235
|
+
`;
|
|
236
|
+
for (const exp of result.exports.slice(0, 10)) {
|
|
237
|
+
out += ` ${exp.file}: ${exp.export}
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
if (result.exports.length > 10) {
|
|
241
|
+
out += ` ... e mais ${result.exports.length - 10}
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
out += `
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
if (result.dependencies.length > 0) {
|
|
248
|
+
out += `\u{1F4E6} DEPEND\xCANCIAS N\xC3O USADAS (${result.dependencies.length})
|
|
249
|
+
`;
|
|
250
|
+
for (const dep of result.dependencies) {
|
|
251
|
+
out += ` ${dep}
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
out += `
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
out += `\u{1F4A1} SUGEST\xC3O
|
|
258
|
+
`;
|
|
259
|
+
out += ` Execute 'npx knip --fix' para remover automaticamente.
|
|
260
|
+
`;
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
function formatImpactText(result) {
|
|
264
|
+
let out = "";
|
|
265
|
+
out += `
|
|
266
|
+
`;
|
|
267
|
+
out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
268
|
+
`;
|
|
269
|
+
out += `\u2551 \u{1F3AF} IMPACT ANALYSIS \u2551
|
|
270
|
+
`;
|
|
271
|
+
out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
272
|
+
|
|
273
|
+
`;
|
|
274
|
+
const icon = categoryIcons[result.category];
|
|
275
|
+
out += `\u{1F4CD} ARQUIVO: ${result.target}
|
|
276
|
+
`;
|
|
277
|
+
out += ` ${icon} ${result.category}
|
|
278
|
+
|
|
279
|
+
`;
|
|
280
|
+
out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
281
|
+
|
|
282
|
+
`;
|
|
283
|
+
out += `\u2B06\uFE0F USADO POR (${result.upstream.total} arquivo${result.upstream.total !== 1 ? "s" : ""} \xFAnico${result.upstream.total !== 1 ? "s" : ""})
|
|
284
|
+
`;
|
|
285
|
+
if (result.upstream.direct.length > 0 || result.upstream.indirect.length > 0) {
|
|
286
|
+
out += ` \u{1F4CD} ${result.upstream.direct.length} direto${result.upstream.direct.length !== 1 ? "s" : ""} + ${result.upstream.indirect.length} indireto${result.upstream.indirect.length !== 1 ? "s" : ""}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
out += ` Quem importa este arquivo:
|
|
290
|
+
|
|
291
|
+
`;
|
|
292
|
+
if (result.upstream.total === 0) {
|
|
293
|
+
out += ` Ningu\xE9m importa este arquivo diretamente.
|
|
294
|
+
`;
|
|
295
|
+
} else {
|
|
296
|
+
for (const file of result.upstream.direct.slice(0, 10)) {
|
|
297
|
+
const fileIcon = categoryIcons[file.category];
|
|
298
|
+
out += ` ${fileIcon} ${file.path}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
if (result.upstream.direct.length > 10) {
|
|
302
|
+
out += ` ... e mais ${result.upstream.direct.length - 10} diretos
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
if (result.upstream.indirect.length > 0) {
|
|
306
|
+
out += `
|
|
307
|
+
Indiretos: ${result.upstream.indirect.slice(0, 5).map((f) => f.path.split("/").pop()).join(", ")}`;
|
|
308
|
+
if (result.upstream.indirect.length > 5) {
|
|
309
|
+
out += ` (+${result.upstream.indirect.length - 5})`;
|
|
310
|
+
}
|
|
311
|
+
out += `
|
|
312
|
+
`;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
out += `
|
|
316
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
317
|
+
|
|
318
|
+
`;
|
|
319
|
+
out += `\u2B07\uFE0F DEPEND\xCANCIAS (${result.downstream.total} arquivo${result.downstream.total !== 1 ? "s" : ""} \xFAnico${result.downstream.total !== 1 ? "s" : ""})
|
|
320
|
+
`;
|
|
321
|
+
if (result.downstream.direct.length > 0 || result.downstream.indirect.length > 0) {
|
|
322
|
+
out += ` \u{1F4CD} ${result.downstream.direct.length} direto${result.downstream.direct.length !== 1 ? "s" : ""} + ${result.downstream.indirect.length} indireto${result.downstream.indirect.length !== 1 ? "s" : ""}
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
out += ` O que este arquivo importa:
|
|
326
|
+
|
|
327
|
+
`;
|
|
328
|
+
if (result.downstream.total === 0) {
|
|
329
|
+
out += ` Este arquivo n\xE3o importa nenhum arquivo local.
|
|
330
|
+
`;
|
|
331
|
+
} else {
|
|
332
|
+
for (const file of result.downstream.direct.slice(0, 10)) {
|
|
333
|
+
const fileIcon = categoryIcons[file.category];
|
|
334
|
+
out += ` ${fileIcon} ${file.path}
|
|
335
|
+
`;
|
|
336
|
+
}
|
|
337
|
+
if (result.downstream.direct.length > 10) {
|
|
338
|
+
out += ` ... e mais ${result.downstream.direct.length - 10}
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
out += `
|
|
343
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
344
|
+
|
|
345
|
+
`;
|
|
346
|
+
out += `\u{1F4CA} M\xC9TRICAS DE IMPACTO
|
|
347
|
+
|
|
348
|
+
`;
|
|
349
|
+
out += ` Arquivos que importam este (upstream): ${result.upstream.total} \xFAnico${result.upstream.total !== 1 ? "s" : ""}
|
|
350
|
+
`;
|
|
351
|
+
out += ` Arquivos que este importa (downstream): ${result.downstream.total} \xFAnico${result.downstream.total !== 1 ? "s" : ""}
|
|
352
|
+
`;
|
|
353
|
+
if (result.risks.length > 0) {
|
|
354
|
+
out += `
|
|
355
|
+
\u26A0\uFE0F RISCOS IDENTIFICADOS (${result.risks.length})
|
|
356
|
+
|
|
357
|
+
`;
|
|
358
|
+
for (const risk of result.risks) {
|
|
359
|
+
const severity = risk.severity === "high" ? "\u{1F534}" : risk.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
360
|
+
out += ` ${severity} ${risk.severity.toUpperCase()}: ${risk.message}
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (result.suggestions.length > 0) {
|
|
365
|
+
out += `
|
|
366
|
+
\u{1F4A1} SUGEST\xD5ES
|
|
367
|
+
|
|
368
|
+
`;
|
|
369
|
+
for (const suggestion of result.suggestions) {
|
|
370
|
+
out += ` \u2022 ${suggestion}
|
|
371
|
+
`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
function formatSuggestText(result) {
|
|
377
|
+
let out = "";
|
|
378
|
+
out += `
|
|
379
|
+
`;
|
|
380
|
+
out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
381
|
+
`;
|
|
382
|
+
out += `\u2551 \u{1F4DA} SUGGEST \u2551
|
|
383
|
+
`;
|
|
384
|
+
out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
385
|
+
|
|
386
|
+
`;
|
|
387
|
+
const icon = categoryIcons[result.category];
|
|
388
|
+
out += `\u{1F4CD} Antes de modificar: ${result.target}
|
|
389
|
+
`;
|
|
390
|
+
out += ` ${icon} ${result.category}
|
|
391
|
+
|
|
392
|
+
`;
|
|
393
|
+
if (result.suggestions.length === 0) {
|
|
394
|
+
out += `\u2705 Nenhuma sugestao de leitura.
|
|
395
|
+
`;
|
|
396
|
+
out += ` Este arquivo nao tem dependencias ou arquivos relacionados.
|
|
397
|
+
`;
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
401
|
+
|
|
402
|
+
`;
|
|
403
|
+
const byPriority = {
|
|
404
|
+
critical: result.suggestions.filter((s) => s.priority === "critical"),
|
|
405
|
+
high: result.suggestions.filter((s) => s.priority === "high"),
|
|
406
|
+
medium: result.suggestions.filter((s) => s.priority === "medium"),
|
|
407
|
+
low: result.suggestions.filter((s) => s.priority === "low")
|
|
408
|
+
};
|
|
409
|
+
if (byPriority.critical.length > 0) {
|
|
410
|
+
out += `\u{1F534} LEITURA CRITICA (${byPriority.critical.length})
|
|
411
|
+
`;
|
|
412
|
+
out += ` Tipos e interfaces que voce DEVE entender:
|
|
413
|
+
|
|
414
|
+
`;
|
|
415
|
+
for (const s of byPriority.critical) {
|
|
416
|
+
const sIcon = categoryIcons[s.category];
|
|
417
|
+
out += ` ${sIcon} ${s.path}
|
|
418
|
+
`;
|
|
419
|
+
out += ` ${s.reason}
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
out += `
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
if (byPriority.high.length > 0) {
|
|
426
|
+
out += `\u{1F7E0} LEITURA IMPORTANTE (${byPriority.high.length})
|
|
427
|
+
`;
|
|
428
|
+
out += ` Arquivos importados diretamente:
|
|
429
|
+
|
|
430
|
+
`;
|
|
431
|
+
for (const s of byPriority.high) {
|
|
432
|
+
const sIcon = categoryIcons[s.category];
|
|
433
|
+
out += ` ${sIcon} ${s.path}
|
|
434
|
+
`;
|
|
435
|
+
out += ` ${s.reason}
|
|
436
|
+
`;
|
|
437
|
+
}
|
|
438
|
+
out += `
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
if (byPriority.medium.length > 0) {
|
|
442
|
+
out += `\u{1F7E1} LEITURA RECOMENDADA (${byPriority.medium.length})
|
|
443
|
+
`;
|
|
444
|
+
out += ` Arquivos que usam este arquivo:
|
|
445
|
+
|
|
446
|
+
`;
|
|
447
|
+
for (const s of byPriority.medium) {
|
|
448
|
+
const sIcon = categoryIcons[s.category];
|
|
449
|
+
out += ` ${sIcon} ${s.path}
|
|
450
|
+
`;
|
|
451
|
+
out += ` ${s.reason}
|
|
452
|
+
`;
|
|
453
|
+
}
|
|
454
|
+
out += `
|
|
455
|
+
`;
|
|
456
|
+
}
|
|
457
|
+
if (byPriority.low.length > 0) {
|
|
458
|
+
out += `\u{1F7E2} LEITURA OPCIONAL (${byPriority.low.length})
|
|
459
|
+
`;
|
|
460
|
+
out += ` Testes relacionados:
|
|
461
|
+
|
|
462
|
+
`;
|
|
463
|
+
for (const s of byPriority.low) {
|
|
464
|
+
const sIcon = categoryIcons[s.category];
|
|
465
|
+
out += ` ${sIcon} ${s.path}
|
|
466
|
+
`;
|
|
467
|
+
out += ` ${s.reason}
|
|
468
|
+
`;
|
|
469
|
+
}
|
|
470
|
+
out += `
|
|
471
|
+
`;
|
|
472
|
+
}
|
|
473
|
+
out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
474
|
+
|
|
475
|
+
`;
|
|
476
|
+
out += `\u{1F4CA} RESUMO
|
|
477
|
+
`;
|
|
478
|
+
out += ` Total de arquivos sugeridos: ${result.suggestions.length}
|
|
479
|
+
`;
|
|
480
|
+
if (byPriority.critical.length > 0) {
|
|
481
|
+
out += ` \u{1F534} Criticos: ${byPriority.critical.length}
|
|
482
|
+
`;
|
|
483
|
+
}
|
|
484
|
+
if (byPriority.high.length > 0) {
|
|
485
|
+
out += ` \u{1F7E0} Importantes: ${byPriority.high.length}
|
|
486
|
+
`;
|
|
487
|
+
}
|
|
488
|
+
if (byPriority.medium.length > 0) {
|
|
489
|
+
out += ` \u{1F7E1} Recomendados: ${byPriority.medium.length}
|
|
490
|
+
`;
|
|
491
|
+
}
|
|
492
|
+
if (byPriority.low.length > 0) {
|
|
493
|
+
out += ` \u{1F7E2} Opcionais: ${byPriority.low.length}
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
return out;
|
|
497
|
+
}
|
|
498
|
+
function formatContextText(result) {
|
|
499
|
+
let out = "";
|
|
500
|
+
out += `
|
|
501
|
+
`;
|
|
502
|
+
out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
503
|
+
`;
|
|
504
|
+
out += `\u2551 \u{1F4C4} CONTEXT \u2551
|
|
505
|
+
`;
|
|
506
|
+
out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
507
|
+
|
|
508
|
+
`;
|
|
509
|
+
const icon = categoryIcons[result.category];
|
|
510
|
+
out += `\u{1F4CD} ARQUIVO: ${result.file}
|
|
511
|
+
`;
|
|
512
|
+
out += ` ${icon} ${result.category}
|
|
513
|
+
|
|
514
|
+
`;
|
|
515
|
+
out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
516
|
+
|
|
517
|
+
`;
|
|
518
|
+
if (result.imports.length > 0) {
|
|
519
|
+
out += `\u{1F4E5} IMPORTS (${result.imports.length})
|
|
520
|
+
|
|
521
|
+
`;
|
|
522
|
+
for (const imp of result.imports) {
|
|
523
|
+
const typeLabel = imp.isTypeOnly ? " [type]" : "";
|
|
524
|
+
out += ` ${imp.source}${typeLabel}
|
|
525
|
+
`;
|
|
526
|
+
if (imp.specifiers.length > 0) {
|
|
527
|
+
out += ` { ${imp.specifiers.join(", ")} }
|
|
528
|
+
`;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
out += `
|
|
532
|
+
`;
|
|
533
|
+
}
|
|
534
|
+
if (result.exports.length > 0) {
|
|
535
|
+
out += `\u{1F4E4} EXPORTS (${result.exports.length})
|
|
536
|
+
`;
|
|
537
|
+
out += ` ${result.exports.join(", ")}
|
|
538
|
+
|
|
539
|
+
`;
|
|
540
|
+
}
|
|
541
|
+
if (result.types.length > 0) {
|
|
542
|
+
out += `\u{1F3F7}\uFE0F TYPES (${result.types.length})
|
|
543
|
+
|
|
544
|
+
`;
|
|
545
|
+
for (const t of result.types) {
|
|
546
|
+
const exported = t.isExported ? "export " : "";
|
|
547
|
+
out += ` ${exported}${t.kind} ${t.name}
|
|
548
|
+
`;
|
|
549
|
+
out += ` ${t.definition}
|
|
550
|
+
|
|
551
|
+
`;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (result.functions.length > 0) {
|
|
555
|
+
out += `\u26A1 FUNCTIONS (${result.functions.length})
|
|
556
|
+
|
|
557
|
+
`;
|
|
558
|
+
for (const fn of result.functions) {
|
|
559
|
+
const exported = fn.isExported ? "export " : "";
|
|
560
|
+
const async = fn.isAsync ? "async " : "";
|
|
561
|
+
const arrow = fn.isArrowFunction ? " =>" : "";
|
|
562
|
+
const params = fn.params.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
563
|
+
out += ` ${exported}${async}${fn.name}(${params})${arrow}: ${fn.returnType}
|
|
564
|
+
`;
|
|
565
|
+
if (fn.jsdoc) {
|
|
566
|
+
out += ` /** ${fn.jsdoc} */
|
|
567
|
+
`;
|
|
568
|
+
}
|
|
569
|
+
out += `
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
574
|
+
|
|
575
|
+
`;
|
|
576
|
+
out += `\u{1F4CA} RESUMO
|
|
577
|
+
`;
|
|
578
|
+
out += ` Imports: ${result.imports.length}
|
|
579
|
+
`;
|
|
580
|
+
out += ` Exports: ${result.exports.length}
|
|
581
|
+
`;
|
|
582
|
+
out += ` Types: ${result.types.length}
|
|
583
|
+
`;
|
|
584
|
+
out += ` Functions: ${result.functions.length}
|
|
585
|
+
`;
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/cache/index.ts
|
|
590
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync } from "fs";
|
|
591
|
+
import { join, extname } from "path";
|
|
592
|
+
var CACHE_DIR = ".analyze";
|
|
593
|
+
var META_FILE = "meta.json";
|
|
594
|
+
var GRAPH_FILE = "graph.json";
|
|
595
|
+
var MAP_FILE = "map.json";
|
|
596
|
+
var DEAD_FILE = "dead.json";
|
|
597
|
+
function calculateFilesHash(cwd) {
|
|
598
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
599
|
+
const timestamps = [];
|
|
600
|
+
function scanDir(dir) {
|
|
601
|
+
try {
|
|
602
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
603
|
+
for (const entry of entries) {
|
|
604
|
+
const fullPath = join(dir, entry.name);
|
|
605
|
+
if (entry.isDirectory()) {
|
|
606
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".next" || entry.name === "dist" || entry.name === ".analyze") {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
scanDir(fullPath);
|
|
610
|
+
} else if (entry.isFile()) {
|
|
611
|
+
const ext = extname(entry.name).toLowerCase();
|
|
612
|
+
if (extensions.includes(ext)) {
|
|
613
|
+
try {
|
|
614
|
+
const stat = statSync(fullPath);
|
|
615
|
+
timestamps.push(stat.mtimeMs);
|
|
616
|
+
} catch {
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
scanDir(cwd);
|
|
625
|
+
const sum = timestamps.reduce((a, b) => a + b, 0);
|
|
626
|
+
return `${timestamps.length}-${Math.floor(sum)}`;
|
|
627
|
+
}
|
|
628
|
+
function getCacheDir(cwd) {
|
|
629
|
+
return join(cwd, CACHE_DIR);
|
|
630
|
+
}
|
|
631
|
+
function isCacheValid(cwd) {
|
|
632
|
+
const cacheDir = getCacheDir(cwd);
|
|
633
|
+
const metaPath = join(cacheDir, META_FILE);
|
|
634
|
+
if (!existsSync(metaPath)) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
639
|
+
const currentHash = calculateFilesHash(cwd);
|
|
640
|
+
return meta.filesHash === currentHash;
|
|
641
|
+
} catch {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function readCache(cwd, file) {
|
|
646
|
+
const cachePath = join(getCacheDir(cwd), file);
|
|
647
|
+
if (!existsSync(cachePath)) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
return JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
652
|
+
} catch {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function writeCache(cwd, file, data) {
|
|
657
|
+
const cacheDir = getCacheDir(cwd);
|
|
658
|
+
if (!existsSync(cacheDir)) {
|
|
659
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
660
|
+
}
|
|
661
|
+
const cachePath = join(cacheDir, file);
|
|
662
|
+
writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
|
|
663
|
+
}
|
|
664
|
+
function updateCacheMeta(cwd) {
|
|
665
|
+
const meta = {
|
|
666
|
+
version: "1.0.0",
|
|
667
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
668
|
+
lastCheck: (/* @__PURE__ */ new Date()).toISOString(),
|
|
669
|
+
filesHash: calculateFilesHash(cwd)
|
|
670
|
+
};
|
|
671
|
+
writeCache(cwd, META_FILE, meta);
|
|
672
|
+
}
|
|
673
|
+
function cacheGraph(cwd, graph) {
|
|
674
|
+
writeCache(cwd, GRAPH_FILE, graph);
|
|
675
|
+
updateCacheMeta(cwd);
|
|
676
|
+
}
|
|
677
|
+
function getCachedGraph(cwd) {
|
|
678
|
+
if (!isCacheValid(cwd)) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
return readCache(cwd, GRAPH_FILE);
|
|
682
|
+
}
|
|
683
|
+
function cacheMapResult(cwd, result) {
|
|
684
|
+
writeCache(cwd, MAP_FILE, result);
|
|
685
|
+
}
|
|
686
|
+
function getCachedMapResult(cwd) {
|
|
687
|
+
if (!isCacheValid(cwd)) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
return readCache(cwd, MAP_FILE);
|
|
691
|
+
}
|
|
692
|
+
function cacheDeadResult(cwd, result) {
|
|
693
|
+
writeCache(cwd, DEAD_FILE, result);
|
|
694
|
+
}
|
|
695
|
+
function getCachedDeadResult(cwd) {
|
|
696
|
+
if (!isCacheValid(cwd)) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
return readCache(cwd, DEAD_FILE);
|
|
700
|
+
}
|
|
701
|
+
function invalidateCache(cwd) {
|
|
702
|
+
const metaPath = join(getCacheDir(cwd), META_FILE);
|
|
703
|
+
if (existsSync(metaPath)) {
|
|
704
|
+
try {
|
|
705
|
+
writeFileSync(metaPath, "{}", "utf-8");
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/commands/map.ts
|
|
712
|
+
async function map(options = {}) {
|
|
713
|
+
const cwd = options.cwd || process.cwd();
|
|
714
|
+
const format = options.format || "text";
|
|
715
|
+
const useCache = options.cache !== false;
|
|
716
|
+
if (useCache && isCacheValid(cwd)) {
|
|
717
|
+
const cached = getCachedMapResult(cwd);
|
|
718
|
+
if (cached) {
|
|
719
|
+
const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
|
|
720
|
+
if (format === "json") {
|
|
721
|
+
return JSON.stringify(result, null, 2);
|
|
722
|
+
}
|
|
723
|
+
return formatMapText(result) + "\n\n\u{1F4E6} (resultado do cache)";
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const { getStructure, useGraph } = await skott({
|
|
728
|
+
cwd,
|
|
729
|
+
includeBaseDir: false,
|
|
730
|
+
dependencyTracking: {
|
|
731
|
+
thirdParty: options.trackDependencies ?? false,
|
|
732
|
+
builtin: false,
|
|
733
|
+
typeOnly: false
|
|
734
|
+
},
|
|
735
|
+
fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
736
|
+
tsConfigPath: "tsconfig.json"
|
|
737
|
+
});
|
|
738
|
+
const structure = getStructure();
|
|
739
|
+
const graphApi = useGraph();
|
|
740
|
+
const { findCircularDependencies } = graphApi;
|
|
741
|
+
if (useCache) {
|
|
742
|
+
const graphData = {
|
|
743
|
+
graph: structure.graph,
|
|
744
|
+
files: Object.keys(structure.graph),
|
|
745
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
746
|
+
};
|
|
747
|
+
cacheGraph(cwd, graphData);
|
|
748
|
+
}
|
|
749
|
+
const files = Object.entries(structure.graph).map(([path]) => ({
|
|
750
|
+
path,
|
|
751
|
+
category: detectCategory(path),
|
|
752
|
+
size: 0
|
|
753
|
+
// Skott não fornece tamanho
|
|
754
|
+
}));
|
|
755
|
+
const folderMap = /* @__PURE__ */ new Map();
|
|
756
|
+
for (const file of files) {
|
|
757
|
+
const parts = file.path.split("/");
|
|
758
|
+
if (parts.length > 1) {
|
|
759
|
+
const folder = parts.slice(0, -1).join("/");
|
|
760
|
+
if (!folderMap.has(folder)) {
|
|
761
|
+
folderMap.set(folder, {
|
|
762
|
+
path: folder,
|
|
763
|
+
fileCount: 0,
|
|
764
|
+
categories: {}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
const stats = folderMap.get(folder);
|
|
768
|
+
stats.fileCount++;
|
|
769
|
+
stats.categories[file.category] = (stats.categories[file.category] || 0) + 1;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const categories = {};
|
|
773
|
+
for (const file of files) {
|
|
774
|
+
categories[file.category] = (categories[file.category] || 0) + 1;
|
|
775
|
+
}
|
|
776
|
+
const circular = findCircularDependencies();
|
|
777
|
+
const result = {
|
|
778
|
+
version: "1.0.0",
|
|
779
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
780
|
+
cwd,
|
|
781
|
+
summary: {
|
|
782
|
+
totalFiles: files.length,
|
|
783
|
+
totalFolders: folderMap.size,
|
|
784
|
+
categories
|
|
785
|
+
},
|
|
786
|
+
folders: Array.from(folderMap.values()),
|
|
787
|
+
files,
|
|
788
|
+
circularDependencies: circular
|
|
789
|
+
};
|
|
790
|
+
if (useCache) {
|
|
791
|
+
cacheMapResult(cwd, result);
|
|
792
|
+
updateCacheMeta(cwd);
|
|
793
|
+
}
|
|
794
|
+
if (format === "json") {
|
|
795
|
+
return JSON.stringify(result, null, 2);
|
|
796
|
+
}
|
|
797
|
+
return formatMapText(result);
|
|
798
|
+
} catch (error) {
|
|
799
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
800
|
+
throw new Error(`Erro ao executar map: ${message}`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/commands/dead.ts
|
|
805
|
+
import { execSync } from "child_process";
|
|
806
|
+
async function dead(options = {}) {
|
|
807
|
+
const cwd = options.cwd || process.cwd();
|
|
808
|
+
const format = options.format || "text";
|
|
809
|
+
const useCache = options.cache !== false;
|
|
810
|
+
if (useCache && isCacheValid(cwd)) {
|
|
811
|
+
const cached = getCachedDeadResult(cwd);
|
|
812
|
+
if (cached) {
|
|
813
|
+
const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
|
|
814
|
+
if (format === "json") {
|
|
815
|
+
return JSON.stringify(result, null, 2);
|
|
816
|
+
}
|
|
817
|
+
return formatDeadText(result) + "\n\n\u{1F4E6} (resultado do cache)";
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
let knipOutput;
|
|
822
|
+
try {
|
|
823
|
+
const output = execSync("npx knip --reporter=json", {
|
|
824
|
+
cwd,
|
|
825
|
+
encoding: "utf-8",
|
|
826
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
827
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
828
|
+
});
|
|
829
|
+
knipOutput = JSON.parse(output || "{}");
|
|
830
|
+
} catch (execError) {
|
|
831
|
+
const error = execError;
|
|
832
|
+
if (error.stdout) {
|
|
833
|
+
try {
|
|
834
|
+
knipOutput = JSON.parse(error.stdout);
|
|
835
|
+
} catch {
|
|
836
|
+
knipOutput = {};
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
knipOutput = {};
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const deadFiles = (knipOutput.files || []).map((file) => ({
|
|
843
|
+
path: file,
|
|
844
|
+
category: detectCategory(file),
|
|
845
|
+
type: "file"
|
|
846
|
+
}));
|
|
847
|
+
const deadExports = [];
|
|
848
|
+
if (knipOutput.issues) {
|
|
849
|
+
for (const issue of knipOutput.issues) {
|
|
850
|
+
if (issue.symbol && issue.symbolType === "export") {
|
|
851
|
+
deadExports.push({
|
|
852
|
+
file: issue.file,
|
|
853
|
+
export: issue.symbol
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const deadDependencies = [
|
|
859
|
+
...knipOutput.dependencies || [],
|
|
860
|
+
...knipOutput.devDependencies || []
|
|
861
|
+
];
|
|
862
|
+
const result = {
|
|
863
|
+
version: "1.0.0",
|
|
864
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
865
|
+
cwd,
|
|
866
|
+
summary: {
|
|
867
|
+
totalDead: deadFiles.length + deadExports.length + deadDependencies.length,
|
|
868
|
+
byType: {
|
|
869
|
+
files: deadFiles.length,
|
|
870
|
+
exports: deadExports.length,
|
|
871
|
+
dependencies: deadDependencies.length
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
files: deadFiles,
|
|
875
|
+
exports: deadExports,
|
|
876
|
+
dependencies: deadDependencies
|
|
877
|
+
};
|
|
878
|
+
if (useCache) {
|
|
879
|
+
cacheDeadResult(cwd, result);
|
|
880
|
+
updateCacheMeta(cwd);
|
|
881
|
+
}
|
|
882
|
+
if (format === "json") {
|
|
883
|
+
return JSON.stringify(result, null, 2);
|
|
884
|
+
}
|
|
885
|
+
return formatDeadText(result);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
888
|
+
throw new Error(`Erro ao executar dead: ${message}`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async function deadFix(options = {}) {
|
|
892
|
+
const cwd = options.cwd || process.cwd();
|
|
893
|
+
try {
|
|
894
|
+
const output = execSync("npx knip --fix", {
|
|
895
|
+
cwd,
|
|
896
|
+
encoding: "utf-8",
|
|
897
|
+
maxBuffer: 50 * 1024 * 1024
|
|
898
|
+
});
|
|
899
|
+
return `\u2705 Fix executado com sucesso!
|
|
900
|
+
|
|
901
|
+
${output}`;
|
|
902
|
+
} catch (error) {
|
|
903
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
904
|
+
throw new Error(`Erro ao executar fix: ${message}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/commands/impact.ts
|
|
909
|
+
import skott2 from "skott";
|
|
910
|
+
async function impact(target, options = {}) {
|
|
911
|
+
const cwd = options.cwd || process.cwd();
|
|
912
|
+
const format = options.format || "text";
|
|
913
|
+
const useCache = options.cache !== false;
|
|
914
|
+
if (!target) {
|
|
915
|
+
throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
|
|
916
|
+
}
|
|
917
|
+
try {
|
|
918
|
+
let graph;
|
|
919
|
+
let allFiles = [];
|
|
920
|
+
let findCircular = () => [];
|
|
921
|
+
let fromCache = false;
|
|
922
|
+
if (useCache && isCacheValid(cwd)) {
|
|
923
|
+
const cached = getCachedGraph(cwd);
|
|
924
|
+
if (cached) {
|
|
925
|
+
graph = cached.graph;
|
|
926
|
+
allFiles = cached.files;
|
|
927
|
+
findCircular = () => findCircularFromGraph(graph);
|
|
928
|
+
fromCache = true;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (!graph) {
|
|
932
|
+
const { getStructure, useGraph } = await skott2({
|
|
933
|
+
cwd,
|
|
934
|
+
includeBaseDir: false,
|
|
935
|
+
dependencyTracking: {
|
|
936
|
+
thirdParty: false,
|
|
937
|
+
builtin: false,
|
|
938
|
+
typeOnly: false
|
|
939
|
+
},
|
|
940
|
+
fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
941
|
+
tsConfigPath: "tsconfig.json"
|
|
942
|
+
});
|
|
943
|
+
const structure = getStructure();
|
|
944
|
+
const graphApi = useGraph();
|
|
945
|
+
graph = structure.graph;
|
|
946
|
+
allFiles = Object.keys(graph);
|
|
947
|
+
findCircular = () => graphApi.findCircularDependencies();
|
|
948
|
+
if (useCache) {
|
|
949
|
+
cacheGraph(cwd, {
|
|
950
|
+
graph,
|
|
951
|
+
files: allFiles,
|
|
952
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
953
|
+
});
|
|
954
|
+
updateCacheMeta(cwd);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const targetPath = findTargetFile(target, allFiles);
|
|
958
|
+
if (!targetPath) {
|
|
959
|
+
return formatNotFound(target, allFiles);
|
|
960
|
+
}
|
|
961
|
+
const { directUpstream, indirectUpstream, directDownstream, indirectDownstream } = calculateDependencies(targetPath, graph);
|
|
962
|
+
const dependingOn = [...directUpstream, ...indirectUpstream];
|
|
963
|
+
const dependencies = [...directDownstream, ...indirectDownstream];
|
|
964
|
+
const risks = detectRisks(targetPath, dependingOn, dependencies, findCircular);
|
|
965
|
+
const suggestions = generateSuggestions(dependingOn, dependencies, risks);
|
|
966
|
+
const result = {
|
|
967
|
+
version: "1.0.0",
|
|
968
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
969
|
+
target: targetPath,
|
|
970
|
+
category: detectCategory(targetPath),
|
|
971
|
+
upstream: {
|
|
972
|
+
direct: directUpstream.map(toImpactFile(true)),
|
|
973
|
+
indirect: indirectUpstream.map(toImpactFile(false)),
|
|
974
|
+
total: dependingOn.length
|
|
975
|
+
},
|
|
976
|
+
downstream: {
|
|
977
|
+
direct: directDownstream.map(toImpactFile(true)),
|
|
978
|
+
indirect: indirectDownstream.map(toImpactFile(false)),
|
|
979
|
+
total: dependencies.length
|
|
980
|
+
},
|
|
981
|
+
risks,
|
|
982
|
+
suggestions
|
|
983
|
+
};
|
|
984
|
+
if (format === "json") {
|
|
985
|
+
return JSON.stringify(result, null, 2);
|
|
986
|
+
}
|
|
987
|
+
const output = formatImpactText(result);
|
|
988
|
+
return fromCache ? output + "\n\n\u{1F4E6} (grafo do cache)" : output;
|
|
989
|
+
} catch (error) {
|
|
990
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
991
|
+
throw new Error(`Erro ao executar impact: ${message}`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function calculateDependencies(targetPath, graph) {
|
|
995
|
+
const targetNode = graph[targetPath];
|
|
996
|
+
const directDownstream = targetNode ? targetNode.adjacentTo : [];
|
|
997
|
+
const allDownstream = /* @__PURE__ */ new Set();
|
|
998
|
+
const visited = /* @__PURE__ */ new Set();
|
|
999
|
+
function collectDownstream(file) {
|
|
1000
|
+
if (visited.has(file)) return;
|
|
1001
|
+
visited.add(file);
|
|
1002
|
+
const node = graph[file];
|
|
1003
|
+
if (node) {
|
|
1004
|
+
for (const dep of node.adjacentTo) {
|
|
1005
|
+
allDownstream.add(dep);
|
|
1006
|
+
collectDownstream(dep);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
collectDownstream(targetPath);
|
|
1011
|
+
const indirectDownstream = Array.from(allDownstream).filter(
|
|
1012
|
+
(f) => !directDownstream.includes(f)
|
|
1013
|
+
);
|
|
1014
|
+
const directUpstream = [];
|
|
1015
|
+
const allUpstream = /* @__PURE__ */ new Set();
|
|
1016
|
+
for (const [file, node] of Object.entries(graph)) {
|
|
1017
|
+
if (node.adjacentTo.includes(targetPath)) {
|
|
1018
|
+
directUpstream.push(file);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
visited.clear();
|
|
1022
|
+
function collectUpstream(file) {
|
|
1023
|
+
if (visited.has(file)) return;
|
|
1024
|
+
visited.add(file);
|
|
1025
|
+
for (const [f, node] of Object.entries(graph)) {
|
|
1026
|
+
if (node.adjacentTo.includes(file) && !visited.has(f)) {
|
|
1027
|
+
allUpstream.add(f);
|
|
1028
|
+
collectUpstream(f);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
for (const file of directUpstream) {
|
|
1033
|
+
collectUpstream(file);
|
|
1034
|
+
}
|
|
1035
|
+
const indirectUpstream = Array.from(allUpstream).filter(
|
|
1036
|
+
(f) => !directUpstream.includes(f)
|
|
1037
|
+
);
|
|
1038
|
+
return {
|
|
1039
|
+
directUpstream,
|
|
1040
|
+
indirectUpstream,
|
|
1041
|
+
directDownstream,
|
|
1042
|
+
indirectDownstream
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function findCircularFromGraph(graph) {
|
|
1046
|
+
const cycles = [];
|
|
1047
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1048
|
+
const stack = /* @__PURE__ */ new Set();
|
|
1049
|
+
const path = [];
|
|
1050
|
+
function dfs(node) {
|
|
1051
|
+
if (stack.has(node)) {
|
|
1052
|
+
const cycleStart = path.indexOf(node);
|
|
1053
|
+
if (cycleStart !== -1) {
|
|
1054
|
+
cycles.push(path.slice(cycleStart));
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (visited.has(node)) return;
|
|
1059
|
+
visited.add(node);
|
|
1060
|
+
stack.add(node);
|
|
1061
|
+
path.push(node);
|
|
1062
|
+
const nodeData = graph[node];
|
|
1063
|
+
if (nodeData) {
|
|
1064
|
+
for (const dep of nodeData.adjacentTo) {
|
|
1065
|
+
dfs(dep);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
path.pop();
|
|
1069
|
+
stack.delete(node);
|
|
1070
|
+
}
|
|
1071
|
+
for (const node of Object.keys(graph)) {
|
|
1072
|
+
dfs(node);
|
|
1073
|
+
}
|
|
1074
|
+
return cycles;
|
|
1075
|
+
}
|
|
1076
|
+
function findTargetFile(target, allFiles) {
|
|
1077
|
+
const normalizedTarget = target.replace(/\\/g, "/");
|
|
1078
|
+
if (allFiles.includes(normalizedTarget)) {
|
|
1079
|
+
return normalizedTarget;
|
|
1080
|
+
}
|
|
1081
|
+
const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
|
|
1082
|
+
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1083
|
+
const matches = [];
|
|
1084
|
+
for (const file of allFiles) {
|
|
1085
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1086
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1087
|
+
if (fileNameNoExt === targetNameNoExt) {
|
|
1088
|
+
matches.unshift(file);
|
|
1089
|
+
} else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
|
|
1090
|
+
matches.push(file);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (matches.length === 1) {
|
|
1094
|
+
return matches[0];
|
|
1095
|
+
}
|
|
1096
|
+
if (matches.length > 1) {
|
|
1097
|
+
return matches[0];
|
|
1098
|
+
}
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
function formatNotFound(target, allFiles) {
|
|
1102
|
+
const normalizedTarget = target.toLowerCase();
|
|
1103
|
+
const similar = allFiles.filter((f) => {
|
|
1104
|
+
const fileName = f.split("/").pop()?.toLowerCase() || "";
|
|
1105
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1106
|
+
return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance(fileNameNoExt, normalizedTarget) <= 3;
|
|
1107
|
+
}).slice(0, 5);
|
|
1108
|
+
let out = `\u274C Arquivo n\xE3o encontrado no \xEDndice: "${target}"
|
|
1109
|
+
|
|
1110
|
+
`;
|
|
1111
|
+
out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
|
|
1112
|
+
|
|
1113
|
+
`;
|
|
1114
|
+
if (similar.length > 0) {
|
|
1115
|
+
out += `\u{1F4DD} Arquivos com nome similar:
|
|
1116
|
+
`;
|
|
1117
|
+
for (const s of similar) {
|
|
1118
|
+
out += ` \u2022 ${s}
|
|
1119
|
+
`;
|
|
1120
|
+
}
|
|
1121
|
+
out += `
|
|
1122
|
+
`;
|
|
1123
|
+
}
|
|
1124
|
+
out += `\u{1F4A1} Dicas:
|
|
1125
|
+
`;
|
|
1126
|
+
out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
|
|
1127
|
+
`;
|
|
1128
|
+
out += ` \u2022 Ou apenas o nome do arquivo: Header
|
|
1129
|
+
`;
|
|
1130
|
+
out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
|
|
1131
|
+
`;
|
|
1132
|
+
return out;
|
|
1133
|
+
}
|
|
1134
|
+
function toImpactFile(isDirect) {
|
|
1135
|
+
return (path) => ({
|
|
1136
|
+
path,
|
|
1137
|
+
category: detectCategory(path),
|
|
1138
|
+
isDirect
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
function detectRisks(targetPath, upstream, downstream, findCircular) {
|
|
1142
|
+
const risks = [];
|
|
1143
|
+
if (upstream.length >= 15) {
|
|
1144
|
+
risks.push({
|
|
1145
|
+
type: "widely-used",
|
|
1146
|
+
severity: "high",
|
|
1147
|
+
message: `Arquivo CR\xCDTICO: usado por ${upstream.length} arquivos \xFAnicos`
|
|
1148
|
+
});
|
|
1149
|
+
} else if (upstream.length >= 5) {
|
|
1150
|
+
risks.push({
|
|
1151
|
+
type: "widely-used",
|
|
1152
|
+
severity: "medium",
|
|
1153
|
+
message: `Arquivo compartilhado: usado por ${upstream.length} arquivos \xFAnicos`
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
if (downstream.length >= 20) {
|
|
1157
|
+
risks.push({
|
|
1158
|
+
type: "deep-chain",
|
|
1159
|
+
severity: "medium",
|
|
1160
|
+
message: `Arquivo importa ${downstream.length} depend\xEAncias (considere dividir)`
|
|
1161
|
+
});
|
|
1162
|
+
} else if (downstream.length >= 10) {
|
|
1163
|
+
risks.push({
|
|
1164
|
+
type: "deep-chain",
|
|
1165
|
+
severity: "low",
|
|
1166
|
+
message: `Arquivo importa ${downstream.length} depend\xEAncias`
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
const circular = findCircular();
|
|
1170
|
+
const targetCircular = circular.filter((cycle) => cycle.includes(targetPath));
|
|
1171
|
+
if (targetCircular.length > 0) {
|
|
1172
|
+
risks.push({
|
|
1173
|
+
type: "circular",
|
|
1174
|
+
severity: "medium",
|
|
1175
|
+
message: `Envolvido em ${targetCircular.length} depend\xEAncia${targetCircular.length > 1 ? "s" : ""} circular${targetCircular.length > 1 ? "es" : ""}`
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
return risks;
|
|
1179
|
+
}
|
|
1180
|
+
function generateSuggestions(upstream, downstream, risks) {
|
|
1181
|
+
const suggestions = [];
|
|
1182
|
+
if (upstream.length > 0) {
|
|
1183
|
+
suggestions.push(
|
|
1184
|
+
`Verifique os ${upstream.length} arquivo(s) que importam este antes de modificar`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
if (upstream.length >= 10) {
|
|
1188
|
+
suggestions.push(`Considere criar testes para garantir que mudan\xE7as n\xE3o quebrem dependentes`);
|
|
1189
|
+
}
|
|
1190
|
+
if (downstream.length > 0) {
|
|
1191
|
+
suggestions.push(`Teste as ${downstream.length} depend\xEAncia(s) ap\xF3s mudan\xE7as`);
|
|
1192
|
+
}
|
|
1193
|
+
if (risks.some((r) => r.type === "circular")) {
|
|
1194
|
+
suggestions.push(`Considere resolver as depend\xEAncias circulares antes de refatorar`);
|
|
1195
|
+
}
|
|
1196
|
+
if (risks.some((r) => r.type === "widely-used" && r.severity === "high")) {
|
|
1197
|
+
suggestions.push(`Este arquivo \xE9 cr\xEDtico - planeje mudan\xE7as com cuidado`);
|
|
1198
|
+
}
|
|
1199
|
+
return suggestions;
|
|
1200
|
+
}
|
|
1201
|
+
function levenshteinDistance(a, b) {
|
|
1202
|
+
const matrix = [];
|
|
1203
|
+
for (let i = 0; i <= b.length; i++) {
|
|
1204
|
+
matrix[i] = [i];
|
|
1205
|
+
}
|
|
1206
|
+
for (let j = 0; j <= a.length; j++) {
|
|
1207
|
+
matrix[0][j] = j;
|
|
1208
|
+
}
|
|
1209
|
+
for (let i = 1; i <= b.length; i++) {
|
|
1210
|
+
for (let j = 1; j <= a.length; j++) {
|
|
1211
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1212
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1213
|
+
} else {
|
|
1214
|
+
matrix[i][j] = Math.min(
|
|
1215
|
+
matrix[i - 1][j - 1] + 1,
|
|
1216
|
+
matrix[i][j - 1] + 1,
|
|
1217
|
+
matrix[i - 1][j] + 1
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return matrix[b.length][a.length];
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// src/commands/suggest.ts
|
|
1226
|
+
import skott3 from "skott";
|
|
1227
|
+
async function suggest(target, options = {}) {
|
|
1228
|
+
const cwd = options.cwd || process.cwd();
|
|
1229
|
+
const format = options.format || "text";
|
|
1230
|
+
const useCache = options.cache !== false;
|
|
1231
|
+
const limit = options.limit || 10;
|
|
1232
|
+
if (!target) {
|
|
1233
|
+
throw new Error("Target e obrigatorio. Exemplo: ai-tool suggest src/components/Button.tsx");
|
|
1234
|
+
}
|
|
1235
|
+
try {
|
|
1236
|
+
let graph;
|
|
1237
|
+
let allFiles = [];
|
|
1238
|
+
let fromCache = false;
|
|
1239
|
+
if (useCache && isCacheValid(cwd)) {
|
|
1240
|
+
const cached = getCachedGraph(cwd);
|
|
1241
|
+
if (cached) {
|
|
1242
|
+
graph = cached.graph;
|
|
1243
|
+
allFiles = cached.files;
|
|
1244
|
+
fromCache = true;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (!graph) {
|
|
1248
|
+
const { getStructure } = await skott3({
|
|
1249
|
+
cwd,
|
|
1250
|
+
includeBaseDir: false,
|
|
1251
|
+
dependencyTracking: {
|
|
1252
|
+
thirdParty: false,
|
|
1253
|
+
builtin: false,
|
|
1254
|
+
typeOnly: false
|
|
1255
|
+
},
|
|
1256
|
+
fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
1257
|
+
tsConfigPath: "tsconfig.json"
|
|
1258
|
+
});
|
|
1259
|
+
const structure = getStructure();
|
|
1260
|
+
graph = structure.graph;
|
|
1261
|
+
allFiles = Object.keys(graph);
|
|
1262
|
+
if (useCache) {
|
|
1263
|
+
cacheGraph(cwd, {
|
|
1264
|
+
graph,
|
|
1265
|
+
files: allFiles,
|
|
1266
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1267
|
+
});
|
|
1268
|
+
updateCacheMeta(cwd);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const targetPath = findTargetFile2(target, allFiles);
|
|
1272
|
+
if (!targetPath) {
|
|
1273
|
+
return formatNotFound2(target, allFiles);
|
|
1274
|
+
}
|
|
1275
|
+
const suggestions = collectSuggestions(targetPath, graph, allFiles, limit);
|
|
1276
|
+
const result = {
|
|
1277
|
+
version: "1.0.0",
|
|
1278
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1279
|
+
target: targetPath,
|
|
1280
|
+
category: detectCategory(targetPath),
|
|
1281
|
+
suggestions
|
|
1282
|
+
};
|
|
1283
|
+
if (format === "json") {
|
|
1284
|
+
return JSON.stringify(result, null, 2);
|
|
1285
|
+
}
|
|
1286
|
+
const output = formatSuggestText(result);
|
|
1287
|
+
return fromCache ? output + "\n\n(grafo do cache)" : output;
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1290
|
+
throw new Error(`Erro ao executar suggest: ${message}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function collectSuggestions(targetPath, graph, allFiles, limit) {
|
|
1294
|
+
const suggestions = [];
|
|
1295
|
+
const addedPaths = /* @__PURE__ */ new Set();
|
|
1296
|
+
const targetNode = graph[targetPath];
|
|
1297
|
+
if (!targetNode) {
|
|
1298
|
+
return suggestions;
|
|
1299
|
+
}
|
|
1300
|
+
for (const dep of targetNode.adjacentTo) {
|
|
1301
|
+
if (addedPaths.has(dep)) continue;
|
|
1302
|
+
addedPaths.add(dep);
|
|
1303
|
+
const category = detectCategory(dep);
|
|
1304
|
+
if (category === "type") {
|
|
1305
|
+
suggestions.push({
|
|
1306
|
+
path: dep,
|
|
1307
|
+
category,
|
|
1308
|
+
reason: "Define tipos usados",
|
|
1309
|
+
priority: "critical"
|
|
1310
|
+
});
|
|
1311
|
+
} else {
|
|
1312
|
+
suggestions.push({
|
|
1313
|
+
path: dep,
|
|
1314
|
+
category,
|
|
1315
|
+
reason: "Importado diretamente",
|
|
1316
|
+
priority: "high"
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const upstream = findUpstream(targetPath, graph);
|
|
1321
|
+
const upstreamLimited = upstream.slice(0, 5);
|
|
1322
|
+
for (const file of upstreamLimited) {
|
|
1323
|
+
if (addedPaths.has(file)) continue;
|
|
1324
|
+
addedPaths.add(file);
|
|
1325
|
+
suggestions.push({
|
|
1326
|
+
path: file,
|
|
1327
|
+
category: detectCategory(file),
|
|
1328
|
+
reason: "Usa este arquivo",
|
|
1329
|
+
priority: "medium"
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
const relatedTests = findRelatedTests(targetPath, allFiles);
|
|
1333
|
+
for (const testFile of relatedTests) {
|
|
1334
|
+
if (addedPaths.has(testFile)) continue;
|
|
1335
|
+
addedPaths.add(testFile);
|
|
1336
|
+
suggestions.push({
|
|
1337
|
+
path: testFile,
|
|
1338
|
+
category: "test",
|
|
1339
|
+
reason: "Testa este arquivo",
|
|
1340
|
+
priority: "low"
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
const priorityOrder = {
|
|
1344
|
+
critical: 0,
|
|
1345
|
+
high: 1,
|
|
1346
|
+
medium: 2,
|
|
1347
|
+
low: 3
|
|
1348
|
+
};
|
|
1349
|
+
suggestions.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
1350
|
+
return suggestions.slice(0, limit);
|
|
1351
|
+
}
|
|
1352
|
+
function findUpstream(targetPath, graph) {
|
|
1353
|
+
const upstream = [];
|
|
1354
|
+
for (const [file, node] of Object.entries(graph)) {
|
|
1355
|
+
if (node.adjacentTo.includes(targetPath)) {
|
|
1356
|
+
upstream.push(file);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return upstream;
|
|
1360
|
+
}
|
|
1361
|
+
function findRelatedTests(targetPath, allFiles) {
|
|
1362
|
+
const targetName = targetPath.split("/").pop() || "";
|
|
1363
|
+
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1364
|
+
const tests = [];
|
|
1365
|
+
for (const file of allFiles) {
|
|
1366
|
+
const fileName = file.split("/").pop() || "";
|
|
1367
|
+
if (fileName.includes(".test.") || fileName.includes(".spec.") || file.includes("/__tests__/")) {
|
|
1368
|
+
const testNameNoExt = fileName.replace(/\.(test|spec)\..*$/, "").replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1369
|
+
if (testNameNoExt.toLowerCase() === targetNameNoExt.toLowerCase()) {
|
|
1370
|
+
tests.push(file);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return tests;
|
|
1375
|
+
}
|
|
1376
|
+
function findTargetFile2(target, allFiles) {
|
|
1377
|
+
const normalizedTarget = target.replace(/\\/g, "/");
|
|
1378
|
+
if (allFiles.includes(normalizedTarget)) {
|
|
1379
|
+
return normalizedTarget;
|
|
1380
|
+
}
|
|
1381
|
+
const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
|
|
1382
|
+
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1383
|
+
const matches = [];
|
|
1384
|
+
for (const file of allFiles) {
|
|
1385
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1386
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1387
|
+
if (fileNameNoExt === targetNameNoExt) {
|
|
1388
|
+
matches.unshift(file);
|
|
1389
|
+
} else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
|
|
1390
|
+
matches.push(file);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (matches.length === 1) {
|
|
1394
|
+
return matches[0];
|
|
1395
|
+
}
|
|
1396
|
+
if (matches.length > 1) {
|
|
1397
|
+
return matches[0];
|
|
1398
|
+
}
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
function formatNotFound2(target, allFiles) {
|
|
1402
|
+
const normalizedTarget = target.toLowerCase();
|
|
1403
|
+
const similar = allFiles.filter((f) => {
|
|
1404
|
+
const fileName = f.split("/").pop()?.toLowerCase() || "";
|
|
1405
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1406
|
+
return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance2(fileNameNoExt, normalizedTarget) <= 3;
|
|
1407
|
+
}).slice(0, 5);
|
|
1408
|
+
let out = `Arquivo nao encontrado no indice: "${target}"
|
|
1409
|
+
|
|
1410
|
+
`;
|
|
1411
|
+
out += `Total de arquivos indexados: ${allFiles.length}
|
|
1412
|
+
|
|
1413
|
+
`;
|
|
1414
|
+
if (similar.length > 0) {
|
|
1415
|
+
out += `Arquivos com nome similar:
|
|
1416
|
+
`;
|
|
1417
|
+
for (const s of similar) {
|
|
1418
|
+
out += ` - ${s}
|
|
1419
|
+
`;
|
|
1420
|
+
}
|
|
1421
|
+
out += `
|
|
1422
|
+
`;
|
|
1423
|
+
}
|
|
1424
|
+
out += `Dicas:
|
|
1425
|
+
`;
|
|
1426
|
+
out += ` - Use o caminho relativo: src/components/Header.tsx
|
|
1427
|
+
`;
|
|
1428
|
+
out += ` - Ou apenas o nome do arquivo: Header
|
|
1429
|
+
`;
|
|
1430
|
+
out += ` - Verifique se o arquivo esta em uma pasta incluida no scan
|
|
1431
|
+
`;
|
|
1432
|
+
return out;
|
|
1433
|
+
}
|
|
1434
|
+
function levenshteinDistance2(a, b) {
|
|
1435
|
+
const matrix = [];
|
|
1436
|
+
for (let i = 0; i <= b.length; i++) {
|
|
1437
|
+
matrix[i] = [i];
|
|
1438
|
+
}
|
|
1439
|
+
for (let j = 0; j <= a.length; j++) {
|
|
1440
|
+
matrix[0][j] = j;
|
|
1441
|
+
}
|
|
1442
|
+
for (let i = 1; i <= b.length; i++) {
|
|
1443
|
+
for (let j = 1; j <= a.length; j++) {
|
|
1444
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1445
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1446
|
+
} else {
|
|
1447
|
+
matrix[i][j] = Math.min(
|
|
1448
|
+
matrix[i - 1][j - 1] + 1,
|
|
1449
|
+
matrix[i][j - 1] + 1,
|
|
1450
|
+
matrix[i - 1][j] + 1
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return matrix[b.length][a.length];
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/commands/context.ts
|
|
1459
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1460
|
+
import { join as join2, resolve, basename, extname as extname2 } from "path";
|
|
1461
|
+
|
|
1462
|
+
// src/ts/extractor.ts
|
|
1463
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
1464
|
+
function createProject(cwd) {
|
|
1465
|
+
return new Project({
|
|
1466
|
+
tsConfigFilePath: `${cwd}/tsconfig.json`,
|
|
1467
|
+
skipAddingFilesFromTsConfig: true
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function addSourceFile(project, filePath) {
|
|
1471
|
+
return project.addSourceFileAtPath(filePath);
|
|
1472
|
+
}
|
|
1473
|
+
function extractImports(sourceFile) {
|
|
1474
|
+
const imports = [];
|
|
1475
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
1476
|
+
const specifiers = [];
|
|
1477
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
1478
|
+
if (defaultImport) {
|
|
1479
|
+
specifiers.push(defaultImport.getText());
|
|
1480
|
+
}
|
|
1481
|
+
for (const namedImport of importDecl.getNamedImports()) {
|
|
1482
|
+
const alias = namedImport.getAliasNode();
|
|
1483
|
+
if (alias) {
|
|
1484
|
+
specifiers.push(`${namedImport.getName()} as ${alias.getText()}`);
|
|
1485
|
+
} else {
|
|
1486
|
+
specifiers.push(namedImport.getName());
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
const namespaceImport = importDecl.getNamespaceImport();
|
|
1490
|
+
if (namespaceImport) {
|
|
1491
|
+
specifiers.push(`* as ${namespaceImport.getText()}`);
|
|
1492
|
+
}
|
|
1493
|
+
imports.push({
|
|
1494
|
+
source: importDecl.getModuleSpecifierValue(),
|
|
1495
|
+
specifiers,
|
|
1496
|
+
isTypeOnly: importDecl.isTypeOnly()
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
return imports;
|
|
1500
|
+
}
|
|
1501
|
+
function getJsDocDescription(node) {
|
|
1502
|
+
const jsDocs = node.getChildrenOfKind(SyntaxKind.JSDoc);
|
|
1503
|
+
if (jsDocs.length === 0) return void 0;
|
|
1504
|
+
const firstJsDoc = jsDocs[0];
|
|
1505
|
+
const comment = firstJsDoc.getComment();
|
|
1506
|
+
if (typeof comment === "string") {
|
|
1507
|
+
return comment.trim() || void 0;
|
|
1508
|
+
}
|
|
1509
|
+
if (Array.isArray(comment)) {
|
|
1510
|
+
const text = comment.filter((c) => c !== void 0).map((c) => c.getText()).join("").trim();
|
|
1511
|
+
return text || void 0;
|
|
1512
|
+
}
|
|
1513
|
+
return void 0;
|
|
1514
|
+
}
|
|
1515
|
+
function extractParams(params) {
|
|
1516
|
+
return params.map((p) => ({
|
|
1517
|
+
name: p.getName(),
|
|
1518
|
+
type: simplifyType(p.getType().getText())
|
|
1519
|
+
}));
|
|
1520
|
+
}
|
|
1521
|
+
function simplifyType(typeText) {
|
|
1522
|
+
let simplified = typeText.replace(/import\([^)]+\)\./g, "");
|
|
1523
|
+
if (simplified.length > 80) {
|
|
1524
|
+
simplified = simplified.slice(0, 77) + "...";
|
|
1525
|
+
}
|
|
1526
|
+
return simplified;
|
|
1527
|
+
}
|
|
1528
|
+
function extractFunctions(sourceFile) {
|
|
1529
|
+
const functions = [];
|
|
1530
|
+
for (const func of sourceFile.getFunctions()) {
|
|
1531
|
+
const name = func.getName();
|
|
1532
|
+
if (!name) continue;
|
|
1533
|
+
functions.push({
|
|
1534
|
+
name,
|
|
1535
|
+
params: extractParams(func.getParameters()),
|
|
1536
|
+
returnType: simplifyType(func.getReturnType().getText()),
|
|
1537
|
+
isAsync: func.isAsync(),
|
|
1538
|
+
isExported: func.isExported(),
|
|
1539
|
+
isArrowFunction: false,
|
|
1540
|
+
jsdoc: getJsDocDescription(func)
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
for (const varStatement of sourceFile.getVariableStatements()) {
|
|
1544
|
+
const isExported = varStatement.isExported();
|
|
1545
|
+
for (const varDecl of varStatement.getDeclarations()) {
|
|
1546
|
+
const init = varDecl.getInitializer();
|
|
1547
|
+
if (!init) continue;
|
|
1548
|
+
if (init.getKind() === SyntaxKind.ArrowFunction) {
|
|
1549
|
+
const arrowFunc = init.asKind(SyntaxKind.ArrowFunction);
|
|
1550
|
+
if (!arrowFunc) continue;
|
|
1551
|
+
functions.push({
|
|
1552
|
+
name: varDecl.getName(),
|
|
1553
|
+
params: extractParams(arrowFunc.getParameters()),
|
|
1554
|
+
returnType: simplifyType(arrowFunc.getReturnType().getText()),
|
|
1555
|
+
isAsync: arrowFunc.isAsync(),
|
|
1556
|
+
isExported,
|
|
1557
|
+
isArrowFunction: true,
|
|
1558
|
+
jsdoc: getJsDocDescription(varStatement)
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
if (init.getKind() === SyntaxKind.FunctionExpression) {
|
|
1562
|
+
const funcExpr = init.asKind(SyntaxKind.FunctionExpression);
|
|
1563
|
+
if (!funcExpr) continue;
|
|
1564
|
+
functions.push({
|
|
1565
|
+
name: varDecl.getName(),
|
|
1566
|
+
params: extractParams(funcExpr.getParameters()),
|
|
1567
|
+
returnType: simplifyType(funcExpr.getReturnType().getText()),
|
|
1568
|
+
isAsync: funcExpr.isAsync(),
|
|
1569
|
+
isExported,
|
|
1570
|
+
isArrowFunction: false,
|
|
1571
|
+
jsdoc: getJsDocDescription(varStatement)
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return functions;
|
|
1577
|
+
}
|
|
1578
|
+
function extractTypes(sourceFile) {
|
|
1579
|
+
const types = [];
|
|
1580
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
1581
|
+
types.push({
|
|
1582
|
+
name: iface.getName(),
|
|
1583
|
+
kind: "interface",
|
|
1584
|
+
definition: formatInterfaceDefinition(iface),
|
|
1585
|
+
isExported: iface.isExported()
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
1589
|
+
types.push({
|
|
1590
|
+
name: typeAlias.getName(),
|
|
1591
|
+
kind: "type",
|
|
1592
|
+
definition: simplifyType(typeAlias.getType().getText()),
|
|
1593
|
+
isExported: typeAlias.isExported()
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
for (const enumDecl of sourceFile.getEnums()) {
|
|
1597
|
+
types.push({
|
|
1598
|
+
name: enumDecl.getName(),
|
|
1599
|
+
kind: "enum",
|
|
1600
|
+
definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
|
|
1601
|
+
isExported: enumDecl.isExported()
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
return types;
|
|
1605
|
+
}
|
|
1606
|
+
function formatInterfaceDefinition(iface) {
|
|
1607
|
+
const parts = [];
|
|
1608
|
+
const extendsClauses = iface.getExtends();
|
|
1609
|
+
if (extendsClauses.length > 0) {
|
|
1610
|
+
parts.push(`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`);
|
|
1611
|
+
}
|
|
1612
|
+
const props = iface.getProperties();
|
|
1613
|
+
for (const prop of props) {
|
|
1614
|
+
const propType = simplifyType(prop.getType().getText());
|
|
1615
|
+
parts.push(`${prop.getName()}: ${propType}`);
|
|
1616
|
+
}
|
|
1617
|
+
const methods = iface.getMethods();
|
|
1618
|
+
for (const method of methods) {
|
|
1619
|
+
const returnType = simplifyType(method.getReturnType().getText());
|
|
1620
|
+
parts.push(`${method.getName()}(): ${returnType}`);
|
|
1621
|
+
}
|
|
1622
|
+
return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
|
|
1623
|
+
}
|
|
1624
|
+
function extractExports(sourceFile) {
|
|
1625
|
+
const exports = [];
|
|
1626
|
+
for (const exportDecl of sourceFile.getExportDeclarations()) {
|
|
1627
|
+
for (const namedExport of exportDecl.getNamedExports()) {
|
|
1628
|
+
exports.push(namedExport.getName());
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
for (const func of sourceFile.getFunctions()) {
|
|
1632
|
+
if (func.isExported() && func.getName()) {
|
|
1633
|
+
exports.push(func.getName());
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
for (const varStatement of sourceFile.getVariableStatements()) {
|
|
1637
|
+
if (varStatement.isExported()) {
|
|
1638
|
+
for (const decl of varStatement.getDeclarations()) {
|
|
1639
|
+
exports.push(decl.getName());
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
1644
|
+
if (iface.isExported()) {
|
|
1645
|
+
exports.push(iface.getName());
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
1649
|
+
if (typeAlias.isExported()) {
|
|
1650
|
+
exports.push(typeAlias.getName());
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
for (const enumDecl of sourceFile.getEnums()) {
|
|
1654
|
+
if (enumDecl.isExported()) {
|
|
1655
|
+
exports.push(enumDecl.getName());
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
1659
|
+
if (classDecl.isExported() && classDecl.getName()) {
|
|
1660
|
+
exports.push(classDecl.getName());
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return [...new Set(exports)];
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/commands/context.ts
|
|
1667
|
+
async function context(target, options = {}) {
|
|
1668
|
+
const cwd = options.cwd || process.cwd();
|
|
1669
|
+
const format = options.format || "text";
|
|
1670
|
+
if (!target) {
|
|
1671
|
+
throw new Error("Target e obrigatorio. Exemplo: ai-tool context src/components/Button.tsx");
|
|
1672
|
+
}
|
|
1673
|
+
try {
|
|
1674
|
+
const targetPath = findTargetFile3(target, cwd);
|
|
1675
|
+
if (!targetPath) {
|
|
1676
|
+
return formatNotFound3(target, cwd);
|
|
1677
|
+
}
|
|
1678
|
+
const project = createProject(cwd);
|
|
1679
|
+
const absolutePath = resolve(cwd, targetPath);
|
|
1680
|
+
const sourceFile = addSourceFile(project, absolutePath);
|
|
1681
|
+
const imports = extractImports(sourceFile);
|
|
1682
|
+
const functions = extractFunctions(sourceFile);
|
|
1683
|
+
const types = extractTypes(sourceFile);
|
|
1684
|
+
const exports = extractExports(sourceFile);
|
|
1685
|
+
const result = {
|
|
1686
|
+
version: "1.0.0",
|
|
1687
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1688
|
+
file: targetPath,
|
|
1689
|
+
category: detectCategory(targetPath),
|
|
1690
|
+
imports,
|
|
1691
|
+
exports,
|
|
1692
|
+
functions,
|
|
1693
|
+
types
|
|
1694
|
+
};
|
|
1695
|
+
if (format === "json") {
|
|
1696
|
+
return JSON.stringify(result, null, 2);
|
|
1697
|
+
}
|
|
1698
|
+
return formatContextText(result);
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1701
|
+
throw new Error(`Erro ao executar context: ${message}`);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
1705
|
+
function findTargetFile3(target, cwd) {
|
|
1706
|
+
const normalizedTarget = target.replace(/\\/g, "/");
|
|
1707
|
+
const directPath = resolve(cwd, normalizedTarget);
|
|
1708
|
+
if (existsSync2(directPath) && isCodeFile2(directPath)) {
|
|
1709
|
+
return normalizedTarget;
|
|
1710
|
+
}
|
|
1711
|
+
for (const ext of CODE_EXTENSIONS2) {
|
|
1712
|
+
const withExt = directPath + ext;
|
|
1713
|
+
if (existsSync2(withExt)) {
|
|
1714
|
+
return normalizedTarget + ext;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
const targetName = basename(normalizedTarget).toLowerCase();
|
|
1718
|
+
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1719
|
+
const allFiles = getAllCodeFiles(cwd);
|
|
1720
|
+
const matches = [];
|
|
1721
|
+
for (const file of allFiles) {
|
|
1722
|
+
const fileName = basename(file).toLowerCase();
|
|
1723
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1724
|
+
if (fileNameNoExt === targetNameNoExt) {
|
|
1725
|
+
matches.unshift(file);
|
|
1726
|
+
} else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
|
|
1727
|
+
matches.push(file);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (matches.length > 0) {
|
|
1731
|
+
return matches[0];
|
|
1732
|
+
}
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
function isCodeFile2(filePath) {
|
|
1736
|
+
const ext = extname2(filePath);
|
|
1737
|
+
return CODE_EXTENSIONS2.has(ext);
|
|
1738
|
+
}
|
|
1739
|
+
function getAllCodeFiles(dir, files = [], baseDir = dir) {
|
|
1740
|
+
try {
|
|
1741
|
+
const entries = readdirSync2(dir);
|
|
1742
|
+
for (const entry of entries) {
|
|
1743
|
+
const fullPath = join2(dir, entry);
|
|
1744
|
+
if (shouldIgnore(entry)) {
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
const stat = statSync2(fullPath);
|
|
1749
|
+
if (stat.isDirectory()) {
|
|
1750
|
+
getAllCodeFiles(fullPath, files, baseDir);
|
|
1751
|
+
} else if (isCodeFile2(entry)) {
|
|
1752
|
+
const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
|
|
1753
|
+
files.push(relativePath);
|
|
1754
|
+
}
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
} catch {
|
|
1759
|
+
}
|
|
1760
|
+
return files;
|
|
1761
|
+
}
|
|
1762
|
+
function shouldIgnore(name) {
|
|
1763
|
+
const ignoredDirs = [
|
|
1764
|
+
"node_modules",
|
|
1765
|
+
"dist",
|
|
1766
|
+
"build",
|
|
1767
|
+
".git",
|
|
1768
|
+
".next",
|
|
1769
|
+
".cache",
|
|
1770
|
+
"coverage",
|
|
1771
|
+
".turbo",
|
|
1772
|
+
".vercel"
|
|
1773
|
+
];
|
|
1774
|
+
return ignoredDirs.includes(name) || name.startsWith(".");
|
|
1775
|
+
}
|
|
1776
|
+
function formatNotFound3(target, cwd) {
|
|
1777
|
+
const normalizedTarget = target.toLowerCase();
|
|
1778
|
+
const allFiles = getAllCodeFiles(cwd);
|
|
1779
|
+
const similar = allFiles.filter((f) => {
|
|
1780
|
+
const fileName = basename(f).toLowerCase();
|
|
1781
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
1782
|
+
return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance3(fileNameNoExt, normalizedTarget) <= 3;
|
|
1783
|
+
}).slice(0, 5);
|
|
1784
|
+
let out = `Arquivo nao encontrado: "${target}"
|
|
1785
|
+
|
|
1786
|
+
`;
|
|
1787
|
+
out += `Total de arquivos no projeto: ${allFiles.length}
|
|
1788
|
+
|
|
1789
|
+
`;
|
|
1790
|
+
if (similar.length > 0) {
|
|
1791
|
+
out += `Arquivos com nome similar:
|
|
1792
|
+
`;
|
|
1793
|
+
for (const s of similar) {
|
|
1794
|
+
out += ` - ${s}
|
|
1795
|
+
`;
|
|
1796
|
+
}
|
|
1797
|
+
out += `
|
|
1798
|
+
`;
|
|
1799
|
+
}
|
|
1800
|
+
out += `Dicas:
|
|
1801
|
+
`;
|
|
1802
|
+
out += ` - Use o caminho relativo: src/components/Header.tsx
|
|
1803
|
+
`;
|
|
1804
|
+
out += ` - Ou apenas o nome do arquivo: Header
|
|
1805
|
+
`;
|
|
1806
|
+
out += ` - Verifique se o arquivo existe e e um arquivo .ts/.tsx/.js/.jsx
|
|
1807
|
+
`;
|
|
1808
|
+
return out;
|
|
1809
|
+
}
|
|
1810
|
+
function levenshteinDistance3(a, b) {
|
|
1811
|
+
const matrix = [];
|
|
1812
|
+
for (let i = 0; i <= b.length; i++) {
|
|
1813
|
+
matrix[i] = [i];
|
|
1814
|
+
}
|
|
1815
|
+
for (let j = 0; j <= a.length; j++) {
|
|
1816
|
+
matrix[0][j] = j;
|
|
1817
|
+
}
|
|
1818
|
+
for (let i = 1; i <= b.length; i++) {
|
|
1819
|
+
for (let j = 1; j <= a.length; j++) {
|
|
1820
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1821
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1822
|
+
} else {
|
|
1823
|
+
matrix[i][j] = Math.min(
|
|
1824
|
+
matrix[i - 1][j - 1] + 1,
|
|
1825
|
+
matrix[i][j - 1] + 1,
|
|
1826
|
+
matrix[i - 1][j] + 1
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return matrix[b.length][a.length];
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/index.ts
|
|
1835
|
+
var VERSION = "0.3.0";
|
|
1836
|
+
|
|
1837
|
+
export {
|
|
1838
|
+
detectCategory,
|
|
1839
|
+
categoryIcons,
|
|
1840
|
+
isEntryPoint,
|
|
1841
|
+
isCodeFile,
|
|
1842
|
+
getCacheDir,
|
|
1843
|
+
isCacheValid,
|
|
1844
|
+
invalidateCache,
|
|
1845
|
+
map,
|
|
1846
|
+
dead,
|
|
1847
|
+
deadFix,
|
|
1848
|
+
impact,
|
|
1849
|
+
suggest,
|
|
1850
|
+
context,
|
|
1851
|
+
VERSION
|
|
1852
|
+
};
|