@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
package/dist/chunk-EGBEXF4G.js
DELETED
|
@@ -1,764 +0,0 @@
|
|
|
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
|
-
|
|
377
|
-
// src/commands/map.ts
|
|
378
|
-
async function map(options = {}) {
|
|
379
|
-
const cwd = options.cwd || process.cwd();
|
|
380
|
-
const format = options.format || "text";
|
|
381
|
-
try {
|
|
382
|
-
const { getStructure, useGraph } = await skott({
|
|
383
|
-
cwd,
|
|
384
|
-
includeBaseDir: false,
|
|
385
|
-
dependencyTracking: {
|
|
386
|
-
thirdParty: options.trackDependencies ?? false,
|
|
387
|
-
builtin: false,
|
|
388
|
-
typeOnly: false
|
|
389
|
-
},
|
|
390
|
-
fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
391
|
-
tsConfigPath: "tsconfig.json"
|
|
392
|
-
});
|
|
393
|
-
const structure = getStructure();
|
|
394
|
-
const { findCircularDependencies } = useGraph();
|
|
395
|
-
const files = Object.entries(structure.graph).map(([path]) => ({
|
|
396
|
-
path,
|
|
397
|
-
category: detectCategory(path),
|
|
398
|
-
size: 0
|
|
399
|
-
// Skott não fornece tamanho
|
|
400
|
-
}));
|
|
401
|
-
const folderMap = /* @__PURE__ */ new Map();
|
|
402
|
-
for (const file of files) {
|
|
403
|
-
const parts = file.path.split("/");
|
|
404
|
-
if (parts.length > 1) {
|
|
405
|
-
const folder = parts.slice(0, -1).join("/");
|
|
406
|
-
if (!folderMap.has(folder)) {
|
|
407
|
-
folderMap.set(folder, {
|
|
408
|
-
path: folder,
|
|
409
|
-
fileCount: 0,
|
|
410
|
-
categories: {}
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
const stats = folderMap.get(folder);
|
|
414
|
-
stats.fileCount++;
|
|
415
|
-
stats.categories[file.category] = (stats.categories[file.category] || 0) + 1;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
const categories = {};
|
|
419
|
-
for (const file of files) {
|
|
420
|
-
categories[file.category] = (categories[file.category] || 0) + 1;
|
|
421
|
-
}
|
|
422
|
-
const circular = findCircularDependencies();
|
|
423
|
-
const result = {
|
|
424
|
-
version: "1.0.0",
|
|
425
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
426
|
-
cwd,
|
|
427
|
-
summary: {
|
|
428
|
-
totalFiles: files.length,
|
|
429
|
-
totalFolders: folderMap.size,
|
|
430
|
-
categories
|
|
431
|
-
},
|
|
432
|
-
folders: Array.from(folderMap.values()),
|
|
433
|
-
files,
|
|
434
|
-
circularDependencies: circular
|
|
435
|
-
};
|
|
436
|
-
if (format === "json") {
|
|
437
|
-
return JSON.stringify(result, null, 2);
|
|
438
|
-
}
|
|
439
|
-
return formatMapText(result);
|
|
440
|
-
} catch (error) {
|
|
441
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
442
|
-
throw new Error(`Erro ao executar map: ${message}`);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// src/commands/dead.ts
|
|
447
|
-
import { execSync } from "child_process";
|
|
448
|
-
async function dead(options = {}) {
|
|
449
|
-
const cwd = options.cwd || process.cwd();
|
|
450
|
-
const format = options.format || "text";
|
|
451
|
-
try {
|
|
452
|
-
let knipOutput;
|
|
453
|
-
try {
|
|
454
|
-
const output = execSync("npx knip --reporter=json", {
|
|
455
|
-
cwd,
|
|
456
|
-
encoding: "utf-8",
|
|
457
|
-
maxBuffer: 50 * 1024 * 1024,
|
|
458
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
459
|
-
});
|
|
460
|
-
knipOutput = JSON.parse(output || "{}");
|
|
461
|
-
} catch (execError) {
|
|
462
|
-
const error = execError;
|
|
463
|
-
if (error.stdout) {
|
|
464
|
-
try {
|
|
465
|
-
knipOutput = JSON.parse(error.stdout);
|
|
466
|
-
} catch {
|
|
467
|
-
knipOutput = {};
|
|
468
|
-
}
|
|
469
|
-
} else {
|
|
470
|
-
knipOutput = {};
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
const deadFiles = (knipOutput.files || []).map((file) => ({
|
|
474
|
-
path: file,
|
|
475
|
-
category: detectCategory(file),
|
|
476
|
-
type: "file"
|
|
477
|
-
}));
|
|
478
|
-
const deadExports = [];
|
|
479
|
-
if (knipOutput.issues) {
|
|
480
|
-
for (const issue of knipOutput.issues) {
|
|
481
|
-
if (issue.symbol && issue.symbolType === "export") {
|
|
482
|
-
deadExports.push({
|
|
483
|
-
file: issue.file,
|
|
484
|
-
export: issue.symbol
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const deadDependencies = [
|
|
490
|
-
...knipOutput.dependencies || [],
|
|
491
|
-
...knipOutput.devDependencies || []
|
|
492
|
-
];
|
|
493
|
-
const result = {
|
|
494
|
-
version: "1.0.0",
|
|
495
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
496
|
-
cwd,
|
|
497
|
-
summary: {
|
|
498
|
-
totalDead: deadFiles.length + deadExports.length + deadDependencies.length,
|
|
499
|
-
byType: {
|
|
500
|
-
files: deadFiles.length,
|
|
501
|
-
exports: deadExports.length,
|
|
502
|
-
dependencies: deadDependencies.length
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
files: deadFiles,
|
|
506
|
-
exports: deadExports,
|
|
507
|
-
dependencies: deadDependencies
|
|
508
|
-
};
|
|
509
|
-
if (format === "json") {
|
|
510
|
-
return JSON.stringify(result, null, 2);
|
|
511
|
-
}
|
|
512
|
-
return formatDeadText(result);
|
|
513
|
-
} catch (error) {
|
|
514
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
515
|
-
throw new Error(`Erro ao executar dead: ${message}`);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
async function deadFix(options = {}) {
|
|
519
|
-
const cwd = options.cwd || process.cwd();
|
|
520
|
-
try {
|
|
521
|
-
const output = execSync("npx knip --fix", {
|
|
522
|
-
cwd,
|
|
523
|
-
encoding: "utf-8",
|
|
524
|
-
maxBuffer: 50 * 1024 * 1024
|
|
525
|
-
});
|
|
526
|
-
return `\u2705 Fix executado com sucesso!
|
|
527
|
-
|
|
528
|
-
${output}`;
|
|
529
|
-
} catch (error) {
|
|
530
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
531
|
-
throw new Error(`Erro ao executar fix: ${message}`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// src/commands/impact.ts
|
|
536
|
-
import skott2 from "skott";
|
|
537
|
-
async function impact(target, options = {}) {
|
|
538
|
-
const cwd = options.cwd || process.cwd();
|
|
539
|
-
const format = options.format || "text";
|
|
540
|
-
if (!target) {
|
|
541
|
-
throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
|
|
542
|
-
}
|
|
543
|
-
try {
|
|
544
|
-
const { getStructure, useGraph } = await skott2({
|
|
545
|
-
cwd,
|
|
546
|
-
includeBaseDir: false,
|
|
547
|
-
dependencyTracking: {
|
|
548
|
-
thirdParty: false,
|
|
549
|
-
builtin: false,
|
|
550
|
-
typeOnly: false
|
|
551
|
-
},
|
|
552
|
-
fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
553
|
-
tsConfigPath: "tsconfig.json"
|
|
554
|
-
});
|
|
555
|
-
const structure = getStructure();
|
|
556
|
-
const graphApi = useGraph();
|
|
557
|
-
const allFiles = Object.keys(structure.graph);
|
|
558
|
-
const targetPath = findTargetFile(target, allFiles);
|
|
559
|
-
if (!targetPath) {
|
|
560
|
-
return formatNotFound(target, allFiles);
|
|
561
|
-
}
|
|
562
|
-
const dependingOnNodes = graphApi.collectFilesDependingOn(targetPath, "deep");
|
|
563
|
-
const dependingOnShallow = graphApi.collectFilesDependingOn(targetPath, "shallow");
|
|
564
|
-
const dependenciesNodes = graphApi.collectFilesDependencies(targetPath, "deep");
|
|
565
|
-
const dependenciesShallow = graphApi.collectFilesDependencies(targetPath, "shallow");
|
|
566
|
-
const dependingOn = dependingOnNodes.map((n) => n.id);
|
|
567
|
-
const dependencies = dependenciesNodes.map((n) => n.id);
|
|
568
|
-
const directUpstream = dependingOnShallow.map((n) => n.id);
|
|
569
|
-
const directDownstream = dependenciesShallow.map((n) => n.id);
|
|
570
|
-
const indirectUpstream = dependingOn.filter((f) => !directUpstream.includes(f));
|
|
571
|
-
const indirectDownstream = dependencies.filter((f) => !directDownstream.includes(f));
|
|
572
|
-
const findCircular = () => graphApi.findCircularDependencies();
|
|
573
|
-
const risks = detectRisks(targetPath, dependingOn, dependencies, findCircular);
|
|
574
|
-
const suggestions = generateSuggestions(dependingOn, dependencies, risks);
|
|
575
|
-
const result = {
|
|
576
|
-
version: "1.0.0",
|
|
577
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
578
|
-
target: targetPath,
|
|
579
|
-
category: detectCategory(targetPath),
|
|
580
|
-
upstream: {
|
|
581
|
-
direct: directUpstream.map(toImpactFile(true)),
|
|
582
|
-
indirect: indirectUpstream.map(toImpactFile(false)),
|
|
583
|
-
total: dependingOn.length
|
|
584
|
-
},
|
|
585
|
-
downstream: {
|
|
586
|
-
direct: directDownstream.map(toImpactFile(true)),
|
|
587
|
-
indirect: indirectDownstream.map(toImpactFile(false)),
|
|
588
|
-
total: dependencies.length
|
|
589
|
-
},
|
|
590
|
-
risks,
|
|
591
|
-
suggestions
|
|
592
|
-
};
|
|
593
|
-
if (format === "json") {
|
|
594
|
-
return JSON.stringify(result, null, 2);
|
|
595
|
-
}
|
|
596
|
-
return formatImpactText(result);
|
|
597
|
-
} catch (error) {
|
|
598
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
599
|
-
throw new Error(`Erro ao executar impact: ${message}`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
function findTargetFile(target, allFiles) {
|
|
603
|
-
const normalizedTarget = target.replace(/\\/g, "/");
|
|
604
|
-
if (allFiles.includes(normalizedTarget)) {
|
|
605
|
-
return normalizedTarget;
|
|
606
|
-
}
|
|
607
|
-
const targetName = normalizedTarget.split("/").pop()?.toLowerCase() || "";
|
|
608
|
-
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
609
|
-
const matches = [];
|
|
610
|
-
for (const file of allFiles) {
|
|
611
|
-
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
612
|
-
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
613
|
-
if (fileNameNoExt === targetNameNoExt) {
|
|
614
|
-
matches.unshift(file);
|
|
615
|
-
} else if (file.toLowerCase().includes(normalizedTarget.toLowerCase())) {
|
|
616
|
-
matches.push(file);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
if (matches.length === 1) {
|
|
620
|
-
return matches[0];
|
|
621
|
-
}
|
|
622
|
-
if (matches.length > 1) {
|
|
623
|
-
return matches[0];
|
|
624
|
-
}
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
|
-
function formatNotFound(target, allFiles) {
|
|
628
|
-
const normalizedTarget = target.toLowerCase();
|
|
629
|
-
const similar = allFiles.filter((f) => {
|
|
630
|
-
const fileName = f.split("/").pop()?.toLowerCase() || "";
|
|
631
|
-
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
632
|
-
return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance(fileNameNoExt, normalizedTarget) <= 3;
|
|
633
|
-
}).slice(0, 5);
|
|
634
|
-
let out = `\u274C Arquivo n\xE3o encontrado no \xEDndice: "${target}"
|
|
635
|
-
|
|
636
|
-
`;
|
|
637
|
-
out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
|
|
638
|
-
|
|
639
|
-
`;
|
|
640
|
-
if (similar.length > 0) {
|
|
641
|
-
out += `\u{1F4DD} Arquivos com nome similar:
|
|
642
|
-
`;
|
|
643
|
-
for (const s of similar) {
|
|
644
|
-
out += ` \u2022 ${s}
|
|
645
|
-
`;
|
|
646
|
-
}
|
|
647
|
-
out += `
|
|
648
|
-
`;
|
|
649
|
-
}
|
|
650
|
-
out += `\u{1F4A1} Dicas:
|
|
651
|
-
`;
|
|
652
|
-
out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
|
|
653
|
-
`;
|
|
654
|
-
out += ` \u2022 Ou apenas o nome do arquivo: Header
|
|
655
|
-
`;
|
|
656
|
-
out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
|
|
657
|
-
`;
|
|
658
|
-
return out;
|
|
659
|
-
}
|
|
660
|
-
function toImpactFile(isDirect) {
|
|
661
|
-
return (path) => ({
|
|
662
|
-
path,
|
|
663
|
-
category: detectCategory(path),
|
|
664
|
-
isDirect
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
function detectRisks(targetPath, upstream, downstream, findCircular) {
|
|
668
|
-
const risks = [];
|
|
669
|
-
if (upstream.length >= 15) {
|
|
670
|
-
risks.push({
|
|
671
|
-
type: "widely-used",
|
|
672
|
-
severity: "high",
|
|
673
|
-
message: `Arquivo CR\xCDTICO: usado por ${upstream.length} arquivos \xFAnicos`
|
|
674
|
-
});
|
|
675
|
-
} else if (upstream.length >= 5) {
|
|
676
|
-
risks.push({
|
|
677
|
-
type: "widely-used",
|
|
678
|
-
severity: "medium",
|
|
679
|
-
message: `Arquivo compartilhado: usado por ${upstream.length} arquivos \xFAnicos`
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (downstream.length >= 20) {
|
|
683
|
-
risks.push({
|
|
684
|
-
type: "deep-chain",
|
|
685
|
-
severity: "medium",
|
|
686
|
-
message: `Arquivo importa ${downstream.length} depend\xEAncias (considere dividir)`
|
|
687
|
-
});
|
|
688
|
-
} else if (downstream.length >= 10) {
|
|
689
|
-
risks.push({
|
|
690
|
-
type: "deep-chain",
|
|
691
|
-
severity: "low",
|
|
692
|
-
message: `Arquivo importa ${downstream.length} depend\xEAncias`
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
const circular = findCircular();
|
|
696
|
-
const targetCircular = circular.filter((cycle) => cycle.includes(targetPath));
|
|
697
|
-
if (targetCircular.length > 0) {
|
|
698
|
-
risks.push({
|
|
699
|
-
type: "circular",
|
|
700
|
-
severity: "medium",
|
|
701
|
-
message: `Envolvido em ${targetCircular.length} depend\xEAncia${targetCircular.length > 1 ? "s" : ""} circular${targetCircular.length > 1 ? "es" : ""}`
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
return risks;
|
|
705
|
-
}
|
|
706
|
-
function generateSuggestions(upstream, downstream, risks) {
|
|
707
|
-
const suggestions = [];
|
|
708
|
-
if (upstream.length > 0) {
|
|
709
|
-
suggestions.push(
|
|
710
|
-
`Verifique os ${upstream.length} arquivo(s) que importam este antes de modificar`
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
if (upstream.length >= 10) {
|
|
714
|
-
suggestions.push(`Considere criar testes para garantir que mudan\xE7as n\xE3o quebrem dependentes`);
|
|
715
|
-
}
|
|
716
|
-
if (downstream.length > 0) {
|
|
717
|
-
suggestions.push(`Teste as ${downstream.length} depend\xEAncia(s) ap\xF3s mudan\xE7as`);
|
|
718
|
-
}
|
|
719
|
-
if (risks.some((r) => r.type === "circular")) {
|
|
720
|
-
suggestions.push(`Considere resolver as depend\xEAncias circulares antes de refatorar`);
|
|
721
|
-
}
|
|
722
|
-
if (risks.some((r) => r.type === "widely-used" && r.severity === "high")) {
|
|
723
|
-
suggestions.push(`Este arquivo \xE9 cr\xEDtico - planeje mudan\xE7as com cuidado`);
|
|
724
|
-
}
|
|
725
|
-
return suggestions;
|
|
726
|
-
}
|
|
727
|
-
function levenshteinDistance(a, b) {
|
|
728
|
-
const matrix = [];
|
|
729
|
-
for (let i = 0; i <= b.length; i++) {
|
|
730
|
-
matrix[i] = [i];
|
|
731
|
-
}
|
|
732
|
-
for (let j = 0; j <= a.length; j++) {
|
|
733
|
-
matrix[0][j] = j;
|
|
734
|
-
}
|
|
735
|
-
for (let i = 1; i <= b.length; i++) {
|
|
736
|
-
for (let j = 1; j <= a.length; j++) {
|
|
737
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
738
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
739
|
-
} else {
|
|
740
|
-
matrix[i][j] = Math.min(
|
|
741
|
-
matrix[i - 1][j - 1] + 1,
|
|
742
|
-
matrix[i][j - 1] + 1,
|
|
743
|
-
matrix[i - 1][j] + 1
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
return matrix[b.length][a.length];
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// src/index.ts
|
|
752
|
-
var VERSION = "0.1.0";
|
|
753
|
-
|
|
754
|
-
export {
|
|
755
|
-
detectCategory,
|
|
756
|
-
categoryIcons,
|
|
757
|
-
isEntryPoint,
|
|
758
|
-
isCodeFile,
|
|
759
|
-
map,
|
|
760
|
-
dead,
|
|
761
|
-
deadFix,
|
|
762
|
-
impact,
|
|
763
|
-
VERSION
|
|
764
|
-
};
|