@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.
@@ -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
+ };