@justmpm/ai-tool 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # ai-tool
2
+
3
+ Ferramenta de análise de dependências e impacto para projetos TypeScript/JavaScript.
4
+
5
+ Usa [Skott](https://github.com/antoine-coulon/skott) + [Knip](https://knip.dev) internamente para análise precisa.
6
+
7
+ ## Instalação
8
+
9
+ ```bash
10
+ # Via npx (sem instalar)
11
+ npx ai-tool map
12
+ npx ai-tool dead
13
+ npx ai-tool impact Button
14
+
15
+ # Ou instalar globalmente
16
+ npm install -g ai-tool
17
+
18
+ # Ou como devDependency
19
+ npm install -D ai-tool
20
+ ```
21
+
22
+ ## Comandos
23
+
24
+ ### `map` - Mapa do Projeto
25
+
26
+ Gera um mapa completo do projeto com categorização de arquivos.
27
+
28
+ ```bash
29
+ ai-tool map
30
+ ai-tool map --format=json
31
+ ```
32
+
33
+ **Output:**
34
+ - Total de arquivos e pastas
35
+ - Categorização automática (component, hook, service, util, etc.)
36
+ - Estrutura de pastas
37
+ - Dependências circulares detectadas
38
+
39
+ ### `dead` - Código Morto
40
+
41
+ Detecta arquivos, exports e dependências não utilizados.
42
+
43
+ ```bash
44
+ ai-tool dead
45
+ ai-tool dead --format=json
46
+ ai-tool dead --fix # Remove automaticamente
47
+ ```
48
+
49
+ **Detecta:**
50
+ - Arquivos órfãos (ninguém importa)
51
+ - Exports não utilizados
52
+ - Dependências npm não usadas
53
+
54
+ ### `impact` - Análise de Impacto
55
+
56
+ Analisa o impacto de modificar um arquivo específico.
57
+
58
+ ```bash
59
+ ai-tool impact Button
60
+ ai-tool impact src/components/Button.tsx
61
+ ai-tool impact useAuth --format=json
62
+ ```
63
+
64
+ **Output:**
65
+ - **Upstream**: Quem importa este arquivo (afetados por mudanças)
66
+ - **Downstream**: O que este arquivo importa (dependências)
67
+ - **Riscos**: Arquivo crítico, dependências circulares, etc.
68
+ - **Sugestões**: Recomendações para modificação segura
69
+
70
+ ## Uso Programático
71
+
72
+ ```typescript
73
+ import { map, dead, impact } from "ai-tool";
74
+
75
+ // Mapa do projeto
76
+ const projectMap = await map({ format: "json" });
77
+
78
+ // Código morto
79
+ const deadCode = await dead({ format: "json" });
80
+
81
+ // Análise de impacto
82
+ const analysis = await impact("src/components/Button.tsx", {
83
+ format: "json"
84
+ });
85
+ ```
86
+
87
+ ## Opções
88
+
89
+ | Opção | Descrição | Default |
90
+ |-------|-----------|---------|
91
+ | `--format=text\|json` | Formato de saída | `text` |
92
+ | `--cwd=<path>` | Diretório do projeto | `process.cwd()` |
93
+ | `--fix` | Remove código morto (só para `dead`) | `false` |
94
+
95
+ ## Categorias de Arquivos
96
+
97
+ O ai-tool categoriza automaticamente os arquivos:
98
+
99
+ | Categoria | Descrição |
100
+ |-----------|-----------|
101
+ | `page` | Páginas (Next.js, etc.) |
102
+ | `layout` | Layouts |
103
+ | `route` | Rotas de API |
104
+ | `component` | Componentes React/Vue |
105
+ | `hook` | React Hooks |
106
+ | `service` | Serviços/API |
107
+ | `store` | Estado global (Redux, Zustand, Context) |
108
+ | `util` | Utilitários |
109
+ | `type` | Tipos TypeScript |
110
+ | `config` | Configurações |
111
+ | `test` | Testes |
112
+ | `other` | Outros |
113
+
114
+ ## Integração com IA
115
+
116
+ Este pacote foi criado para ser usado com ferramentas de IA como Claude Code, OpenCode, etc.
117
+
118
+ Exemplo de tool para OpenCode:
119
+
120
+ ```typescript
121
+ import { tool } from "@opencode-ai/plugin";
122
+ import { execSync } from "child_process";
123
+
124
+ export default tool({
125
+ description: `Analisa dependências e impacto do projeto.
126
+
127
+ COMANDOS:
128
+ - map: Mapa do projeto
129
+ - dead: Código morto
130
+ - impact <arquivo>: Análise de impacto`,
131
+
132
+ args: {
133
+ command: tool.schema.enum(["map", "dead", "impact"]),
134
+ target: tool.schema.string().optional(),
135
+ format: tool.schema.enum(["text", "json"]).optional()
136
+ },
137
+
138
+ async execute({ command, target, format }) {
139
+ const fmt = format || "text";
140
+ const cmd = target
141
+ ? `npx ai-tool ${command} "${target}" --format=${fmt}`
142
+ : `npx ai-tool ${command} --format=${fmt}`;
143
+
144
+ return execSync(cmd, { encoding: "utf-8" });
145
+ }
146
+ });
147
+ ```
148
+
149
+ ## Requisitos
150
+
151
+ - Node.js >= 18.0.0
152
+ - TypeScript/JavaScript project
153
+
154
+ ## Créditos
155
+
156
+ - [Skott](https://github.com/antoine-coulon/skott) - Análise de dependências
157
+ - [Knip](https://knip.dev) - Detecção de código morto
158
+
159
+ ## Licença
160
+
161
+ MIT - [Koda AI Studio](https://kodaai.app)
@@ -0,0 +1,764 @@
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
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ VERSION,
4
+ dead,
5
+ deadFix,
6
+ impact,
7
+ map
8
+ } from "./chunk-EGBEXF4G.js";
9
+
10
+ // src/cli.ts
11
+ var HELP = `
12
+ ai-tool v${VERSION} - An\xE1lise de depend\xEAncias e impacto
13
+
14
+ COMANDOS:
15
+ map Mapa completo do projeto (usa Skott)
16
+ dead Detecta c\xF3digo morto (usa Knip)
17
+ dead --fix Remove c\xF3digo morto automaticamente
18
+ impact <arquivo> An\xE1lise de impacto antes de modificar
19
+
20
+ OP\xC7\xD5ES:
21
+ --format=text|json Formato de sa\xEDda (default: text)
22
+ --cwd=<path> Diret\xF3rio do projeto (default: cwd)
23
+ --help, -h Mostra esta ajuda
24
+ --version, -v Mostra vers\xE3o
25
+
26
+ EXEMPLOS:
27
+ ai-tool map
28
+ ai-tool map --format=json
29
+ ai-tool dead
30
+ ai-tool dead --fix
31
+ ai-tool impact Button
32
+ ai-tool impact src/hooks/useAuth.ts
33
+ ai-tool impact src/components/Header.tsx --format=json
34
+
35
+ SOBRE:
36
+ Criado por Koda AI Studio (kodaai.app)
37
+ Usa Skott para an\xE1lise de depend\xEAncias e Knip para dead code detection.
38
+ `;
39
+ async function main() {
40
+ const args = process.argv.slice(2);
41
+ const flags = {};
42
+ const positional = [];
43
+ for (const arg of args) {
44
+ if (arg.startsWith("--")) {
45
+ const [key, value] = arg.slice(2).split("=");
46
+ flags[key] = value ?? true;
47
+ } else if (arg.startsWith("-")) {
48
+ const key = arg.slice(1);
49
+ flags[key] = true;
50
+ } else {
51
+ positional.push(arg);
52
+ }
53
+ }
54
+ if (flags.help || flags.h || positional.length === 0) {
55
+ console.log(HELP);
56
+ process.exit(0);
57
+ }
58
+ if (flags.version || flags.v) {
59
+ console.log(`ai-tool v${VERSION}`);
60
+ process.exit(0);
61
+ }
62
+ const command = positional[0];
63
+ const target = positional[1];
64
+ const format = flags.format || "text";
65
+ const cwd = flags.cwd || process.cwd();
66
+ try {
67
+ let result;
68
+ switch (command) {
69
+ case "map":
70
+ result = await map({ format, cwd });
71
+ break;
72
+ case "dead":
73
+ if (flags.fix) {
74
+ result = await deadFix({ cwd });
75
+ } else {
76
+ result = await dead({ format, cwd });
77
+ }
78
+ break;
79
+ case "impact":
80
+ if (!target) {
81
+ console.error("\u274C Erro: arquivo alvo \xE9 obrigat\xF3rio para o comando impact");
82
+ console.error(" Exemplo: ai-tool impact src/components/Button.tsx");
83
+ console.error(" Exemplo: ai-tool impact Button");
84
+ process.exit(1);
85
+ }
86
+ result = await impact(target, { format, cwd });
87
+ break;
88
+ default:
89
+ console.error(`\u274C Comando desconhecido: ${command}`);
90
+ console.error(" Use 'ai-tool --help' para ver comandos dispon\xEDveis.");
91
+ process.exit(1);
92
+ }
93
+ console.log(result);
94
+ } catch (error) {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ console.error(`\u274C Erro: ${message}`);
97
+ process.exit(1);
98
+ }
99
+ }
100
+ main();
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Tipos para o ai-tool
3
+ */
4
+ type OutputFormat = "text" | "json";
5
+ type FileCategory = "page" | "layout" | "route" | "component" | "hook" | "service" | "store" | "util" | "type" | "config" | "test" | "other";
6
+ interface CommandOptions {
7
+ format?: OutputFormat;
8
+ cwd?: string;
9
+ save?: boolean;
10
+ }
11
+ interface MapOptions extends CommandOptions {
12
+ trackDependencies?: boolean;
13
+ }
14
+ interface DeadOptions extends CommandOptions {
15
+ include?: string[];
16
+ exclude?: string[];
17
+ fix?: boolean;
18
+ }
19
+ interface ImpactOptions extends CommandOptions {
20
+ depth?: number;
21
+ }
22
+ interface FileInfo {
23
+ path: string;
24
+ category: FileCategory;
25
+ size: number;
26
+ }
27
+ interface FolderStats {
28
+ path: string;
29
+ fileCount: number;
30
+ categories: Partial<Record<FileCategory, number>>;
31
+ }
32
+ interface MapResult {
33
+ version: string;
34
+ timestamp: string;
35
+ cwd: string;
36
+ summary: {
37
+ totalFiles: number;
38
+ totalFolders: number;
39
+ categories: Partial<Record<FileCategory, number>>;
40
+ };
41
+ folders: FolderStats[];
42
+ files: FileInfo[];
43
+ circularDependencies: string[][];
44
+ }
45
+ interface DeadFile {
46
+ path: string;
47
+ category: FileCategory;
48
+ type: "file" | "export" | "dependency";
49
+ }
50
+ interface DeadResult {
51
+ version: string;
52
+ timestamp: string;
53
+ cwd: string;
54
+ summary: {
55
+ totalDead: number;
56
+ byType: {
57
+ files: number;
58
+ exports: number;
59
+ dependencies: number;
60
+ };
61
+ };
62
+ files: DeadFile[];
63
+ exports: Array<{
64
+ file: string;
65
+ export: string;
66
+ }>;
67
+ dependencies: string[];
68
+ }
69
+ interface ImpactFile {
70
+ path: string;
71
+ category: FileCategory;
72
+ isDirect: boolean;
73
+ }
74
+ interface RiskInfo {
75
+ type: "widely-used" | "circular" | "deep-chain";
76
+ severity: "low" | "medium" | "high";
77
+ message: string;
78
+ }
79
+ interface ImpactResult {
80
+ version: string;
81
+ timestamp: string;
82
+ target: string;
83
+ category: FileCategory;
84
+ upstream: {
85
+ direct: ImpactFile[];
86
+ indirect: ImpactFile[];
87
+ total: number;
88
+ };
89
+ downstream: {
90
+ direct: ImpactFile[];
91
+ indirect: ImpactFile[];
92
+ total: number;
93
+ };
94
+ risks: RiskInfo[];
95
+ suggestions: string[];
96
+ }
97
+
98
+ /**
99
+ * Comando MAP - Mapa do projeto usando Skott
100
+ */
101
+
102
+ /**
103
+ * Executa o comando MAP
104
+ */
105
+ declare function map(options?: MapOptions): Promise<string>;
106
+
107
+ /**
108
+ * Comando DEAD - Detecção de código morto usando Knip
109
+ */
110
+
111
+ /**
112
+ * Executa o comando DEAD
113
+ */
114
+ declare function dead(options?: DeadOptions): Promise<string>;
115
+ /**
116
+ * Executa fix automático do Knip
117
+ */
118
+ declare function deadFix(options?: DeadOptions): Promise<string>;
119
+
120
+ /**
121
+ * Comando IMPACT - Análise de impacto usando Skott API
122
+ */
123
+
124
+ /**
125
+ * Executa o comando IMPACT
126
+ */
127
+ declare function impact(target: string, options?: ImpactOptions): Promise<string>;
128
+
129
+ /**
130
+ * Utilitários para detecção e classificação de arquivos
131
+ */
132
+
133
+ /**
134
+ * Detecta a categoria de um arquivo baseado no path
135
+ */
136
+ declare function detectCategory(filePath: string): FileCategory;
137
+ /**
138
+ * Ícones para cada categoria (para output text)
139
+ */
140
+ declare const categoryIcons: Record<FileCategory, string>;
141
+ /**
142
+ * Verifica se um arquivo é entry point
143
+ */
144
+ declare function isEntryPoint(filePath: string): boolean;
145
+ /**
146
+ * Verifica se é um arquivo de código
147
+ */
148
+ declare function isCodeFile(filePath: string): boolean;
149
+
150
+ /**
151
+ * ai-tool - Ferramenta de análise de dependências e impacto
152
+ *
153
+ * Usa Skott + Knip internamente para análise precisa.
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * import { map, dead, impact } from "ai-tool";
158
+ *
159
+ * // Mapa do projeto
160
+ * const projectMap = await map({ format: "json" });
161
+ *
162
+ * // Código morto
163
+ * const deadCode = await dead({ format: "json" });
164
+ *
165
+ * // Análise de impacto
166
+ * const analysis = await impact("src/components/Button.tsx", { format: "json" });
167
+ * ```
168
+ */
169
+
170
+ declare const VERSION = "0.1.0";
171
+
172
+ export { type CommandOptions, type DeadFile, type DeadOptions, type DeadResult, type FileCategory, type FileInfo, type FolderStats, type ImpactFile, type ImpactOptions, type ImpactResult, type MapOptions, type MapResult, type OutputFormat, type RiskInfo, VERSION, categoryIcons, dead, deadFix, detectCategory, impact, isCodeFile, isEntryPoint, map };
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ import {
2
+ VERSION,
3
+ categoryIcons,
4
+ dead,
5
+ deadFix,
6
+ detectCategory,
7
+ impact,
8
+ isCodeFile,
9
+ isEntryPoint,
10
+ map
11
+ } from "./chunk-EGBEXF4G.js";
12
+ export {
13
+ VERSION,
14
+ categoryIcons,
15
+ dead,
16
+ deadFix,
17
+ detectCategory,
18
+ impact,
19
+ isCodeFile,
20
+ isEntryPoint,
21
+ map
22
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@justmpm/ai-tool",
3
+ "version": "0.1.0",
4
+ "description": "Ferramenta de análise de dependências e impacto para projetos TypeScript/JavaScript. Usa Skott + Knip internamente.",
5
+ "keywords": [
6
+ "dependency-analysis",
7
+ "impact-analysis",
8
+ "dead-code",
9
+ "typescript",
10
+ "javascript",
11
+ "skott",
12
+ "knip",
13
+ "ai",
14
+ "claude",
15
+ "opencode"
16
+ ],
17
+ "author": "Koda AI Studio <studio.kodaai@gmail.com>",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/anthropic-studio/ai-tool"
22
+ },
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "bin": {
27
+ "ai-tool": "./dist/cli.js"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/index.js",
32
+ "types": "./dist/index.d.ts"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
40
+ "dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch",
41
+ "prepublishOnly": "npm run build",
42
+ "test": "node --test",
43
+ "typecheck": "tsc --noEmit"
44
+ },
45
+ "dependencies": {
46
+ "knip": "^5.44.0",
47
+ "skott": "^0.35.2"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.15.21",
51
+ "tsup": "^8.5.0",
52
+ "typescript": "^5.8.3"
53
+ },
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ }
57
+ }