wegho-agentes 7.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/skills/wegho-global-rules/SKILL.md +20 -0
- package/.agent/workflows/PROMPT_GUIDE.md +7 -4
- package/.agents/cli.ts +29 -21
- package/.agents/core/base-agent.ts +28 -12
- package/.agents/core/memory-system.ts +45 -13
- package/.agents/core/report-generator.ts +26 -19
- package/.agents/orchestrator.ts +104 -74
- package/.clinerules +318 -1229
- package/.cursorrules +0 -189
- package/README.md +3 -3
- package/docs/VERSION.md +3 -2
- package/package.json +3 -3
- package/.agents/agents/ai-agents/README.md +0 -175
- package/.agents/agents/ai-agents/agent.ts +0 -83
- package/.agents/agents/ai-agents/specialty.md +0 -14
- package/.agents/agents/architecture/README.md +0 -61
- package/.agents/agents/architecture/agent.ts +0 -238
- package/.agents/agents/architecture/memory.md +0 -111
- package/.agents/agents/architecture/specialty.md +0 -31
- package/.agents/agents/automation/README.md +0 -60
- package/.agents/agents/automation/agent.ts +0 -61
- package/.agents/agents/automation/specialty.md +0 -14
- package/.agents/agents/backend/README.md +0 -155
- package/.agents/agents/backend/agent.ts +0 -100
- package/.agents/agents/backend/specialty.md +0 -14
- package/.agents/agents/cloud/README.md +0 -73
- package/.agents/agents/cloud/agent.ts +0 -53
- package/.agents/agents/cloud/specialty.md +0 -14
- package/.agents/agents/code-auditor/README.md +0 -37
- package/.agents/agents/code-auditor/agent.ts +0 -334
- package/.agents/agents/code-auditor/specialty.md +0 -14
- package/.agents/agents/cro/README.md +0 -200
- package/.agents/agents/cro/agent.ts +0 -61
- package/.agents/agents/cro/specialty.md +0 -14
- package/.agents/agents/database/README.md +0 -67
- package/.agents/agents/database/agent.ts +0 -93
- package/.agents/agents/database/specialty.md +0 -14
- package/.agents/agents/devops/README.md +0 -84
- package/.agents/agents/devops/agent.ts +0 -54
- package/.agents/agents/devops/specialty.md +0 -14
- package/.agents/agents/documentation/README.md +0 -107
- package/.agents/agents/documentation/agent.ts +0 -253
- package/.agents/agents/documentation/memory.md +0 -56
- package/.agents/agents/documentation/specialty.md +0 -33
- package/.agents/agents/frontend/README.md +0 -188
- package/.agents/agents/frontend/agent.ts +0 -211
- package/.agents/agents/frontend/memory.md +0 -139
- package/.agents/agents/frontend/specialty.md +0 -30
- package/.agents/agents/inventory/README.md +0 -35
- package/.agents/agents/inventory/agent.ts +0 -758
- package/.agents/agents/inventory/memory.md +0 -50
- package/.agents/agents/inventory/specialty.md +0 -129
- package/.agents/agents/nextjs/README.md +0 -58
- package/.agents/agents/nextjs/agent.ts +0 -114
- package/.agents/agents/nextjs/specialty.md +0 -14
- package/.agents/agents/pentest/README.md +0 -228
- package/.agents/agents/pentest/agent.ts +0 -96
- package/.agents/agents/pentest/specialty.md +0 -14
- package/.agents/agents/planning/README.md +0 -107
- package/.agents/agents/planning/agent.ts +0 -389
- package/.agents/agents/planning/specialty.md +0 -14
- package/.agents/agents/project-discovery/README.md +0 -35
- package/.agents/agents/project-discovery/agent.ts +0 -344
- package/.agents/agents/project-discovery/specialty.md +0 -14
- package/.agents/agents/quality/README.md +0 -81
- package/.agents/agents/quality/agent.ts +0 -269
- package/.agents/agents/quality/memory.md +0 -110
- package/.agents/agents/quality/specialty.md +0 -31
- package/.agents/agents/rag/README.md +0 -41
- package/.agents/agents/rag/agent.ts +0 -85
- package/.agents/agents/rag/specialty.md +0 -14
- package/.agents/agents/security/README.md +0 -152
- package/.agents/agents/security/agent.ts +0 -218
- package/.agents/agents/security/memory.md +0 -91
- package/.agents/agents/security/specialty.md +0 -31
- package/.agents/agents/task-analyzer/README.md +0 -36
- package/.agents/agents/task-analyzer/agent.ts +0 -462
- package/.agents/agents/task-analyzer/specialty.md +0 -14
- package/.agents/agents/testing/README.md +0 -161
- package/.agents/agents/testing/agent.ts +0 -61
- package/.agents/agents/testing/specialty.md +0 -14
- package/.agents/agents/uiux/README.md +0 -68
- package/.agents/agents/uiux/agent.ts +0 -95
- package/.agents/agents/uiux/specialty.md +0 -14
- package/.agents/base/base-agent.ts +0 -331
- package/.agents/base/memory-system.ts +0 -397
- package/.agents/base/skill-manager.ts +0 -95
- package/.agents/convert-memory.ps1 +0 -153
- package/.agents/examples/reporting-example.md +0 -203
- package/.agents/managers/build-manager.ts +0 -304
- package/.agents/managers/cache-manager.ts +0 -184
- package/.agents/managers/checkpoint-manager.ts +0 -299
- package/.agents/migrate-agents.ps1 +0 -117
- package/.agents/templates/change-report.md +0 -55
- package/.agents/templates/execution-plan.md +0 -36
- package/.agents/unmapped-skills.txt +0 -0
- package/.agents/utils/agent-migrator.ts +0 -360
- package/.agents/utils/agent-monitor.ts +0 -102
- package/.agents/utils/agent-parallelizer.ts +0 -108
- package/.agents/utils/context-monitor.ts +0 -140
- package/.agents/utils/feedback-collector.ts +0 -207
- package/.agents/utils/file-generator.ts +0 -304
- package/.agents/utils/memory-converter.ts +0 -217
- package/.agents/utils/memory-dashboard.ts +0 -147
- package/.agents/utils/performance-tracker.ts +0 -275
- package/.agents/utils/report-generator.ts +0 -193
- package/.agents/utils/retry-utility.ts +0 -140
- package/.agents/utils/workflow-validator.ts +0 -158
|
@@ -1,758 +0,0 @@
|
|
|
1
|
-
import { BaseAgent, TaskContext, TaskResult } from '../../base/base-agent.js';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { glob } from 'glob';
|
|
5
|
-
|
|
6
|
-
// ============================================================
|
|
7
|
-
// ðŸ INVENTORY AGENT - ALMOXARIFADO CENTRAL DO SISTEMA
|
|
8
|
-
// ============================================================
|
|
9
|
-
// Responsável por:
|
|
10
|
-
// 1. Mapear TUDO no projeto (componentes, variáveis, banco, APIs)
|
|
11
|
-
// 2. Fornecer busca rápida para outros agentes
|
|
12
|
-
// 3. IMPEDIR duplicação de código
|
|
13
|
-
// 4. Organizar e catalogar recursos
|
|
14
|
-
// ============================================================
|
|
15
|
-
|
|
16
|
-
interface InventoryItem {
|
|
17
|
-
id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
type: 'component' | 'variable' | 'table' | 'endpoint' | 'type' | 'asset' | 'function';
|
|
20
|
-
path: string;
|
|
21
|
-
line?: number;
|
|
22
|
-
metadata: Record<string, any>;
|
|
23
|
-
usedBy: string[];
|
|
24
|
-
createdAt: string;
|
|
25
|
-
lastModified: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface Component extends InventoryItem {
|
|
29
|
-
type: 'component';
|
|
30
|
-
exports: string[];
|
|
31
|
-
props: string[];
|
|
32
|
-
dependencies: string[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface Variable extends InventoryItem {
|
|
36
|
-
type: 'variable';
|
|
37
|
-
scope: 'global' | 'local' | 'exported';
|
|
38
|
-
value?: string;
|
|
39
|
-
dataType: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface DatabaseTable extends InventoryItem {
|
|
43
|
-
type: 'table';
|
|
44
|
-
schema: string;
|
|
45
|
-
columns: Array<{
|
|
46
|
-
name: string;
|
|
47
|
-
type: string;
|
|
48
|
-
nullable: boolean;
|
|
49
|
-
primaryKey?: boolean;
|
|
50
|
-
foreignKey?: string;
|
|
51
|
-
}>;
|
|
52
|
-
relationships: Array<{
|
|
53
|
-
table: string;
|
|
54
|
-
type: 'one-to-one' | 'one-to-many' | 'many-to-many';
|
|
55
|
-
foreignKey: string;
|
|
56
|
-
}>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface Endpoint extends InventoryItem {
|
|
60
|
-
type: 'endpoint';
|
|
61
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
62
|
-
params?: string[];
|
|
63
|
-
body?: Record<string, string>;
|
|
64
|
-
response?: string;
|
|
65
|
-
authentication: boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
interface InventoryQuery {
|
|
69
|
-
type?: InventoryItem['type'];
|
|
70
|
-
name?: string;
|
|
71
|
-
path?: string;
|
|
72
|
-
fuzzy?: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface DuplicationCheck {
|
|
76
|
-
exists: boolean;
|
|
77
|
-
duplicates: InventoryItem[];
|
|
78
|
-
suggestions: string[];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export class InventoryAgent extends BaseAgent {
|
|
82
|
-
private inventory: Map<string, InventoryItem> = new Map();
|
|
83
|
-
private inventoryPath: string;
|
|
84
|
-
private projectRoot: string;
|
|
85
|
-
|
|
86
|
-
constructor(projectRoot: string, memoryBasePath: string = '.agents/memory') {
|
|
87
|
-
super('inventory-agent', memoryBasePath);
|
|
88
|
-
this.projectRoot = projectRoot;
|
|
89
|
-
this.inventoryPath = path.join(memoryBasePath, 'inventory-agent', 'inventory');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async executeTask(taskDescription: string, context: TaskContext): Promise<TaskResult> {
|
|
93
|
-
try {
|
|
94
|
-
// Determinar ação baseada na descrição da tarefa
|
|
95
|
-
if (taskDescription.includes('scan') || taskDescription.includes('escanear')) {
|
|
96
|
-
await this.scanProject();
|
|
97
|
-
return {
|
|
98
|
-
success: true,
|
|
99
|
-
details: `Inventário completo: ${this.inventory.size} itens catalogados`,
|
|
100
|
-
recommendations: [`Total de ${this.inventory.size} itens no almoxarifado`],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (taskDescription.includes('find') || taskDescription.includes('buscar')) {
|
|
105
|
-
const query = this.parseQuery(taskDescription);
|
|
106
|
-
const results = await this.findAll(query);
|
|
107
|
-
return {
|
|
108
|
-
success: true,
|
|
109
|
-
details: `Encontrados ${results.length} itens`,
|
|
110
|
-
recommendations: results.map((r) => `${r.type}: ${r.name} (${r.path})`),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (taskDescription.includes('cleanup') || taskDescription.includes('limpeza') || taskDescription.includes('unused')) {
|
|
115
|
-
await this.scanProject(); // Garantir dados frescos
|
|
116
|
-
await this.analyzeUsage(); // Analisar uso
|
|
117
|
-
const unused = await this.findUnusedItems();
|
|
118
|
-
|
|
119
|
-
// Gerar relatório
|
|
120
|
-
const reportPath = path.join(this.projectRoot, 'docs', 'CLEANUP_REPORT.md');
|
|
121
|
-
const reportContent = this.generateCleanupReport(unused);
|
|
122
|
-
|
|
123
|
-
// Garantir diretório docs
|
|
124
|
-
const docsDir = path.join(this.projectRoot, 'docs');
|
|
125
|
-
try {
|
|
126
|
-
await fs.mkdir(docsDir, { recursive: true });
|
|
127
|
-
} catch { }
|
|
128
|
-
|
|
129
|
-
await fs.writeFile(reportPath, reportContent);
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
success: true,
|
|
133
|
-
details: `Análise de limpeza concluÃda. ${unused.length} itens potencialmente não utilizados.`,
|
|
134
|
-
recommendations: [
|
|
135
|
-
`Relatório salvo em: ${reportPath}`,
|
|
136
|
-
`Revise os itens antes de excluir (Next.js pages/layouts são falsos positivos comuns se não detectados corretamente)`
|
|
137
|
-
],
|
|
138
|
-
issues: unused.length > 0 ? unused.slice(0, 5).map(i => `Não utilizado: ${i.name} (${i.type}) - ${i.path}`) : undefined
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (taskDescription.includes('check') || taskDescription.includes('verificar')) {
|
|
143
|
-
const itemName = this.extractItemName(taskDescription);
|
|
144
|
-
const check = await this.checkDuplication(itemName, 'component');
|
|
145
|
-
return {
|
|
146
|
-
success: !check.exists,
|
|
147
|
-
details: check.exists
|
|
148
|
-
? `Item "${itemName}" já existe`
|
|
149
|
-
: `Nenhuma duplicação encontrada para "${itemName}"`,
|
|
150
|
-
warnings: check.exists ? [`Duplicação detectada: ${itemName}`] : undefined,
|
|
151
|
-
recommendations: check.suggestions,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Scan padrão
|
|
156
|
-
await this.scanProject();
|
|
157
|
-
return {
|
|
158
|
-
success: true,
|
|
159
|
-
details: `Scan completo: ${this.inventory.size} itens catalogados`,
|
|
160
|
-
};
|
|
161
|
-
} catch (error) {
|
|
162
|
-
return {
|
|
163
|
-
success: false,
|
|
164
|
-
details: `Erro ao executar tarefa: ${(error as Error).message}`,
|
|
165
|
-
issues: [(error as Error).message],
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ============================================================
|
|
171
|
-
// 🔠MÉTODOS DE BUSCA (Para outros agentes)
|
|
172
|
-
// ============================================================
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Busca um único item no inventário
|
|
176
|
-
*/
|
|
177
|
-
async find(query: InventoryQuery): Promise<InventoryItem | null> {
|
|
178
|
-
const results = await this.findAll(query);
|
|
179
|
-
return results[0] || null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Busca todos os itens que correspondem à query
|
|
184
|
-
*/
|
|
185
|
-
async findAll(query: InventoryQuery): Promise<InventoryItem[]> {
|
|
186
|
-
await this.loadInventory();
|
|
187
|
-
|
|
188
|
-
let results = Array.from(this.inventory.values());
|
|
189
|
-
|
|
190
|
-
// Filtrar por tipo
|
|
191
|
-
if (query.type) {
|
|
192
|
-
results = results.filter((item) => item.type === query.type);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Filtrar por nome
|
|
196
|
-
if (query.name) {
|
|
197
|
-
if (query.fuzzy) {
|
|
198
|
-
// Busca fuzzy (case-insensitive, parcial)
|
|
199
|
-
const searchTerm = query.name.toLowerCase();
|
|
200
|
-
results = results.filter((item) => item.name.toLowerCase().includes(searchTerm));
|
|
201
|
-
} else {
|
|
202
|
-
// Busca exata
|
|
203
|
-
results = results.filter((item) => item.name === query.name);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Filtrar por path
|
|
208
|
-
if (query.path) {
|
|
209
|
-
results = results.filter((item) => item.path.includes(query.path!));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return results;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Busca textual em todo o inventário
|
|
217
|
-
*/
|
|
218
|
-
async search(text: string): Promise<InventoryItem[]> {
|
|
219
|
-
await this.loadInventory();
|
|
220
|
-
|
|
221
|
-
const searchTerm = text.toLowerCase();
|
|
222
|
-
return Array.from(this.inventory.values()).filter(
|
|
223
|
-
(item) =>
|
|
224
|
-
item.name.toLowerCase().includes(searchTerm) ||
|
|
225
|
-
item.path.toLowerCase().includes(searchTerm) ||
|
|
226
|
-
JSON.stringify(item.metadata).toLowerCase().includes(searchTerm)
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ============================================================
|
|
231
|
-
// ðŸ›¡ï¸ VERIFICAÇÃO ANTI-DUPLICAÇÃO (CRÃTICO!)
|
|
232
|
-
// ============================================================
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Verifica se um item já existe antes de criar
|
|
236
|
-
* TODOS os agentes DEVEM chamar isso antes de criar algo novo!
|
|
237
|
-
*/
|
|
238
|
-
async checkDuplication(name: string, type: InventoryItem['type']): Promise<DuplicationCheck> {
|
|
239
|
-
await this.loadInventory();
|
|
240
|
-
|
|
241
|
-
// Busca exata
|
|
242
|
-
const exact = await this.find({ name, type });
|
|
243
|
-
|
|
244
|
-
// Busca fuzzy (similares)
|
|
245
|
-
const similar = await this.findAll({ name, type, fuzzy: true });
|
|
246
|
-
|
|
247
|
-
const exists = exact !== null;
|
|
248
|
-
const duplicates = similar.filter((item) => item.name !== name);
|
|
249
|
-
|
|
250
|
-
const suggestions: string[] = [];
|
|
251
|
-
|
|
252
|
-
if (exists) {
|
|
253
|
-
suggestions.push(`âš ï¸ Item "${name}" já existe em: ${exact.path}`);
|
|
254
|
-
suggestions.push(`💡 Use o existente em vez de criar novo`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (duplicates.length > 0) {
|
|
258
|
-
suggestions.push(`âš ï¸ Encontrados ${duplicates.length} item(ns) similar(es):`);
|
|
259
|
-
duplicates.forEach((dup) => {
|
|
260
|
-
suggestions.push(` - ${dup.name} (${dup.path})`);
|
|
261
|
-
});
|
|
262
|
-
suggestions.push(`💡 Considere reutilizar um desses em vez de criar novo`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!exists && duplicates.length === 0) {
|
|
266
|
-
suggestions.push(`✅ Nenhuma duplicação encontrada. Seguro criar "${name}"`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
exists,
|
|
271
|
-
duplicates,
|
|
272
|
-
suggestions,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// ============================================================
|
|
277
|
-
// 📊 MÉTODOS DE SCAN
|
|
278
|
-
// ============================================================
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Escaneia o projeto inteiro e atualiza o inventário
|
|
282
|
-
*/
|
|
283
|
-
async scanProject(): Promise<void> {
|
|
284
|
-
console.log('🔠[inventory-agent] Escaneando projeto...');
|
|
285
|
-
|
|
286
|
-
this.inventory.clear();
|
|
287
|
-
|
|
288
|
-
await Promise.all([
|
|
289
|
-
this.scanComponents(),
|
|
290
|
-
this.scanVariables(),
|
|
291
|
-
this.scanEndpoints(),
|
|
292
|
-
this.scanTypes(),
|
|
293
|
-
this.scanDatabase(),
|
|
294
|
-
]);
|
|
295
|
-
|
|
296
|
-
await this.saveInventory();
|
|
297
|
-
|
|
298
|
-
console.log(`✅ [inventory-agent] Scan completo: ${this.inventory.size} itens catalogados`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Escaneia componentes React/Next.js
|
|
303
|
-
*/
|
|
304
|
-
private async scanComponents(): Promise<void> {
|
|
305
|
-
const patterns = ['**/*.tsx', '**/*.jsx', '!node_modules/**', '!.next/**', '!.agents/**'];
|
|
306
|
-
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
307
|
-
|
|
308
|
-
for (const file of files) {
|
|
309
|
-
const fullPath = path.join(this.projectRoot, file);
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
const stat = await fs.stat(fullPath);
|
|
313
|
-
if (stat.isDirectory()) continue;
|
|
314
|
-
|
|
315
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
316
|
-
|
|
317
|
-
// Detectar componentes exportados
|
|
318
|
-
const exportMatches = content.matchAll(/export\s+(?:default\s+)?(?:function|const|class)\s+(\w+)/g);
|
|
319
|
-
|
|
320
|
-
for (const match of exportMatches) {
|
|
321
|
-
const componentName = match[1];
|
|
322
|
-
|
|
323
|
-
// Extrair props
|
|
324
|
-
const propsMatch = content.match(new RegExp(`${componentName}.*?\\(\\s*{([^}]+)}`, 's'));
|
|
325
|
-
const props = propsMatch
|
|
326
|
-
? propsMatch[1]
|
|
327
|
-
.split(',')
|
|
328
|
-
.map((p) => p.trim().split(':')[0].trim())
|
|
329
|
-
.filter(Boolean)
|
|
330
|
-
: [];
|
|
331
|
-
|
|
332
|
-
// Extrair imports (dependências)
|
|
333
|
-
const importMatches = content.matchAll(/import\s+.*?from\s+['"]([^'"]+)['"]/g);
|
|
334
|
-
const dependencies = Array.from(importMatches, (m) => m[1]);
|
|
335
|
-
|
|
336
|
-
const component: Component = {
|
|
337
|
-
id: `component:${file}:${componentName}`,
|
|
338
|
-
name: componentName,
|
|
339
|
-
type: 'component',
|
|
340
|
-
path: file,
|
|
341
|
-
exports: [componentName],
|
|
342
|
-
props,
|
|
343
|
-
dependencies,
|
|
344
|
-
metadata: {
|
|
345
|
-
isDefaultExport: content.includes(`export default ${componentName}`),
|
|
346
|
-
},
|
|
347
|
-
usedBy: [],
|
|
348
|
-
createdAt: new Date().toISOString(),
|
|
349
|
-
lastModified: new Date().toISOString(),
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
this.inventory.set(component.id, component);
|
|
353
|
-
}
|
|
354
|
-
} catch (err) {
|
|
355
|
-
console.warn(`[inventory-agent] Erro ao ler componente ${file}: ${(err as Error).message}`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Escaneia variáveis globais e exportadas
|
|
362
|
-
*/
|
|
363
|
-
private async scanVariables(): Promise<void> {
|
|
364
|
-
const patterns = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '!node_modules/**', '!.next/**'];
|
|
365
|
-
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
366
|
-
|
|
367
|
-
for (const file of files) {
|
|
368
|
-
const fullPath = path.join(this.projectRoot, file);
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
const stat = await fs.stat(fullPath);
|
|
372
|
-
if (stat.isDirectory()) continue;
|
|
373
|
-
|
|
374
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
375
|
-
|
|
376
|
-
// Detectar variáveis exportadas
|
|
377
|
-
const varMatches = content.matchAll(/export\s+const\s+(\w+)\s*[:=]\s*(.+?)(?:[;\n])/g);
|
|
378
|
-
|
|
379
|
-
for (const match of varMatches) {
|
|
380
|
-
const [, varName, value] = match;
|
|
381
|
-
|
|
382
|
-
const variable: Variable = {
|
|
383
|
-
id: `variable:${file}:${varName}`,
|
|
384
|
-
name: varName,
|
|
385
|
-
type: 'variable',
|
|
386
|
-
path: file,
|
|
387
|
-
scope: 'exported',
|
|
388
|
-
value: value.trim(),
|
|
389
|
-
dataType: this.inferType(value),
|
|
390
|
-
metadata: {},
|
|
391
|
-
usedBy: [],
|
|
392
|
-
createdAt: new Date().toISOString(),
|
|
393
|
-
lastModified: new Date().toISOString(),
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
this.inventory.set(variable.id, variable);
|
|
397
|
-
}
|
|
398
|
-
} catch (err) {
|
|
399
|
-
console.warn(`[inventory-agent] Erro ao ler variável ${file}: ${(err as Error).message}`);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Escaneia endpoints de API
|
|
406
|
-
*/
|
|
407
|
-
private async scanEndpoints(): Promise<void> {
|
|
408
|
-
const patterns = ['**/api/**/*.ts', '**/api/**/*.js', '!node_modules/**'];
|
|
409
|
-
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
410
|
-
|
|
411
|
-
for (const file of files) {
|
|
412
|
-
const fullPath = path.join(this.projectRoot, file);
|
|
413
|
-
|
|
414
|
-
try {
|
|
415
|
-
const stat = await fs.stat(fullPath);
|
|
416
|
-
if (stat.isDirectory()) continue;
|
|
417
|
-
|
|
418
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
419
|
-
|
|
420
|
-
// Detectar métodos HTTP
|
|
421
|
-
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const;
|
|
422
|
-
|
|
423
|
-
for (const method of methods) {
|
|
424
|
-
const regex = new RegExp(`export\\s+async\\s+function\\s+${method}`, 'g');
|
|
425
|
-
if (regex.test(content)) {
|
|
426
|
-
const endpoint: Endpoint = {
|
|
427
|
-
id: `endpoint:${file}:${method}`,
|
|
428
|
-
name: file.replace(/.*\/api\//, '/api/').replace(/\/route\.(ts|js)/, ''),
|
|
429
|
-
type: 'endpoint',
|
|
430
|
-
path: file,
|
|
431
|
-
method,
|
|
432
|
-
authentication: content.includes('auth') || content.includes('session'),
|
|
433
|
-
metadata: {},
|
|
434
|
-
usedBy: [],
|
|
435
|
-
createdAt: new Date().toISOString(),
|
|
436
|
-
lastModified: new Date().toISOString(),
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
this.inventory.set(endpoint.id, endpoint);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
} catch (err) {
|
|
443
|
-
console.warn(`[inventory-agent] Erro ao ler endpoint ${file}: ${(err as Error).message}`);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Escaneia tipos TypeScript
|
|
450
|
-
*/
|
|
451
|
-
private async scanTypes(): Promise<void> {
|
|
452
|
-
const patterns = ['**/types/**/*.ts', '**/*.d.ts', '!node_modules/**'];
|
|
453
|
-
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
454
|
-
|
|
455
|
-
for (const file of files) {
|
|
456
|
-
const fullPath = path.join(this.projectRoot, file);
|
|
457
|
-
|
|
458
|
-
try {
|
|
459
|
-
const stat = await fs.stat(fullPath);
|
|
460
|
-
if (stat.isDirectory()) continue;
|
|
461
|
-
|
|
462
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
463
|
-
|
|
464
|
-
// Detectar interfaces e types
|
|
465
|
-
const typeMatches = content.matchAll(/(?:export\s+)?(?:interface|type)\s+(\w+)/g);
|
|
466
|
-
|
|
467
|
-
for (const match of typeMatches) {
|
|
468
|
-
const typeName = match[1];
|
|
469
|
-
|
|
470
|
-
const typeItem: InventoryItem = {
|
|
471
|
-
id: `type:${file}:${typeName}`,
|
|
472
|
-
name: typeName,
|
|
473
|
-
type: 'type',
|
|
474
|
-
path: file,
|
|
475
|
-
metadata: {
|
|
476
|
-
isInterface: content.includes(`interface ${typeName}`),
|
|
477
|
-
isType: content.includes(`type ${typeName}`),
|
|
478
|
-
},
|
|
479
|
-
usedBy: [],
|
|
480
|
-
createdAt: new Date().toISOString(),
|
|
481
|
-
lastModified: new Date().toISOString(),
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
this.inventory.set(typeItem.id, typeItem);
|
|
485
|
-
}
|
|
486
|
-
} catch (err) {
|
|
487
|
-
console.warn(`[inventory-agent] Erro ao ler tipo ${file}: ${(err as Error).message}`);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Escaneia schema do banco de dados (Supabase/PostgreSQL)
|
|
494
|
-
*/
|
|
495
|
-
private async scanDatabase(): Promise<void> {
|
|
496
|
-
const patterns = ['**/supabase/migrations/**/*.sql', '**/prisma/schema.prisma', '!node_modules/**'];
|
|
497
|
-
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
498
|
-
|
|
499
|
-
for (const file of files) {
|
|
500
|
-
const fullPath = path.join(this.projectRoot, file);
|
|
501
|
-
|
|
502
|
-
try {
|
|
503
|
-
const stat = await fs.stat(fullPath);
|
|
504
|
-
if (stat.isDirectory()) continue;
|
|
505
|
-
|
|
506
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
507
|
-
|
|
508
|
-
// Detectar CREATE TABLE
|
|
509
|
-
const tableMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s*\(/gi);
|
|
510
|
-
|
|
511
|
-
for (const match of tableMatches) {
|
|
512
|
-
const tableName = match[1];
|
|
513
|
-
|
|
514
|
-
const table: DatabaseTable = {
|
|
515
|
-
id: `table:${tableName}`,
|
|
516
|
-
name: tableName,
|
|
517
|
-
type: 'table',
|
|
518
|
-
path: file,
|
|
519
|
-
schema: 'public',
|
|
520
|
-
columns: [],
|
|
521
|
-
relationships: [],
|
|
522
|
-
metadata: {},
|
|
523
|
-
usedBy: [],
|
|
524
|
-
createdAt: new Date().toISOString(),
|
|
525
|
-
lastModified: new Date().toISOString(),
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
this.inventory.set(table.id, table);
|
|
529
|
-
}
|
|
530
|
-
} catch (err) {
|
|
531
|
-
console.warn(`[inventory-agent] Erro ao ler schema ${file}: ${(err as Error).message}`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// ============================================================
|
|
537
|
-
// 💾 PERSISTÊNCIA
|
|
538
|
-
// ============================================================
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Salva inventário em arquivos JSON
|
|
542
|
-
*/
|
|
543
|
-
private async saveInventory(): Promise<void> {
|
|
544
|
-
await fs.mkdir(this.inventoryPath, { recursive: true });
|
|
545
|
-
|
|
546
|
-
// Agrupar por tipo
|
|
547
|
-
const byType = new Map<string, InventoryItem[]>();
|
|
548
|
-
|
|
549
|
-
for (const item of this.inventory.values()) {
|
|
550
|
-
if (!byType.has(item.type)) {
|
|
551
|
-
byType.set(item.type, []);
|
|
552
|
-
}
|
|
553
|
-
byType.get(item.type)!.push(item);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Salvar cada tipo em arquivo separado
|
|
557
|
-
for (const [type, items] of byType) {
|
|
558
|
-
const filePath = path.join(this.inventoryPath, `${type}s.json`);
|
|
559
|
-
await fs.writeFile(filePath, JSON.stringify(items, null, 2));
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Salvar Ãndice geral
|
|
563
|
-
const index = {
|
|
564
|
-
totalItems: this.inventory.size,
|
|
565
|
-
byType: Object.fromEntries(
|
|
566
|
-
Array.from(byType.entries()).map(([type, items]) => [type, items.length])
|
|
567
|
-
),
|
|
568
|
-
lastUpdated: new Date().toISOString(),
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
await fs.writeFile(path.join(this.inventoryPath, 'index.json'), JSON.stringify(index, null, 2));
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Carrega inventário dos arquivos JSON
|
|
576
|
-
*/
|
|
577
|
-
private async loadInventory(): Promise<void> {
|
|
578
|
-
try {
|
|
579
|
-
const indexPath = path.join(this.inventoryPath, 'index.json');
|
|
580
|
-
const indexExists = await fs
|
|
581
|
-
.access(indexPath)
|
|
582
|
-
.then(() => true)
|
|
583
|
-
.catch(() => false);
|
|
584
|
-
|
|
585
|
-
if (!indexExists) {
|
|
586
|
-
await this.scanProject();
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
this.inventory.clear();
|
|
591
|
-
const types = ['component', 'variable', 'table', 'endpoint', 'type'];
|
|
592
|
-
|
|
593
|
-
for (const type of types) {
|
|
594
|
-
const filePath = path.join(this.inventoryPath, `${type}s.json`);
|
|
595
|
-
const fileExists = await fs
|
|
596
|
-
.access(filePath)
|
|
597
|
-
.then(() => true)
|
|
598
|
-
.catch(() => false);
|
|
599
|
-
|
|
600
|
-
if (fileExists) {
|
|
601
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
602
|
-
const items = JSON.parse(content) as InventoryItem[];
|
|
603
|
-
|
|
604
|
-
for (const item of items) {
|
|
605
|
-
this.inventory.set(item.id, item);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
} catch (error) {
|
|
610
|
-
console.warn('[inventory-agent] Erro ao carregar inventário:', error);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// ============================================================
|
|
615
|
-
// ðŸ› ï¸ UTILITÃRIOS
|
|
616
|
-
// ============================================================
|
|
617
|
-
|
|
618
|
-
private inferType(value: string): string {
|
|
619
|
-
if (value.startsWith("'") || value.startsWith('"') || value.startsWith('`')) return 'string';
|
|
620
|
-
if (value === 'true' || value === 'false') return 'boolean';
|
|
621
|
-
if (!isNaN(Number(value))) return 'number';
|
|
622
|
-
if (value.startsWith('[')) return 'array';
|
|
623
|
-
if (value.startsWith('{')) return 'object';
|
|
624
|
-
return 'unknown';
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
private parseQuery(description: string): InventoryQuery {
|
|
628
|
-
const query: InventoryQuery = { fuzzy: true };
|
|
629
|
-
|
|
630
|
-
if (description.includes('component')) query.type = 'component';
|
|
631
|
-
if (description.includes('variable')) query.type = 'variable';
|
|
632
|
-
if (description.includes('endpoint')) query.type = 'endpoint';
|
|
633
|
-
if (description.includes('table')) query.type = 'table';
|
|
634
|
-
|
|
635
|
-
return query;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
private extractItemName(description: string): string {
|
|
639
|
-
const match = description.match(/["']([^"']+)["']/);
|
|
640
|
-
return match ? match[1] : '';
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// ============================================================
|
|
644
|
-
// 🧹 LIMPEZA E ANÃLISE DE USO (OTIMIZADO)
|
|
645
|
-
// ============================================================
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Analisa o uso de cada item no projeto - OTIMIZADO
|
|
649
|
-
*/
|
|
650
|
-
async analyzeUsage(): Promise<void> {
|
|
651
|
-
console.log('ðŸ•µï¸ [inventory-agent] Analisando referências cruzadas (Modo Otimizado)...');
|
|
652
|
-
|
|
653
|
-
await this.loadInventory();
|
|
654
|
-
const items = Array.from(this.inventory.values());
|
|
655
|
-
|
|
656
|
-
// Resetar contadores
|
|
657
|
-
items.forEach(i => i.usedBy = []);
|
|
658
|
-
|
|
659
|
-
const patterns = ['**/*.tsx', '**/*.jsx', '**/*.ts', '**/*.js', '!node_modules/**', '!.next/**', '!.agents/**'];
|
|
660
|
-
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
661
|
-
|
|
662
|
-
// Mapa para busca rápida de itens por nome
|
|
663
|
-
// Tratando colisão de nomes: Map<Name, Item[]>
|
|
664
|
-
const itemsByName = new Map<string, InventoryItem[]>();
|
|
665
|
-
for (const item of items) {
|
|
666
|
-
if (!itemsByName.has(item.name)) {
|
|
667
|
-
itemsByName.set(item.name, []);
|
|
668
|
-
}
|
|
669
|
-
itemsByName.get(item.name)!.push(item);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
let processedFiles = 0;
|
|
673
|
-
|
|
674
|
-
for (const file of files) {
|
|
675
|
-
const fullPath = path.join(this.projectRoot, file);
|
|
676
|
-
try {
|
|
677
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
678
|
-
|
|
679
|
-
// Extrair todos os "tokens" (palavras) do arquivo
|
|
680
|
-
// Isso é muito mais rápido que rodar milhares de regex
|
|
681
|
-
const tokens = new Set(content.match(/\b\w+\b/g) || []);
|
|
682
|
-
|
|
683
|
-
// Verificar quais itens estão presentes nos tokens
|
|
684
|
-
for (const [name, potentialItems] of itemsByName) {
|
|
685
|
-
if (tokens.has(name)) {
|
|
686
|
-
for (const item of potentialItems) {
|
|
687
|
-
// Evitar auto-referência (arquivo definindo o item)
|
|
688
|
-
// Apenas se o path for exatamente o mesmo
|
|
689
|
-
if (item.path !== file) {
|
|
690
|
-
item.usedBy.push(file);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
processedFiles++;
|
|
697
|
-
} catch (err) {
|
|
698
|
-
console.warn(`Erro ao ler arquivo ${file}: ${(err as Error).message}`);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// Salvar inventário atualizado com contagem de uso
|
|
703
|
-
await this.saveInventory();
|
|
704
|
-
console.log(`✅ [inventory-agent] Análise completa. ${files.length} arquivos processados.`);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Encontra itens não utilizados (com whitelist)
|
|
709
|
-
*/
|
|
710
|
-
async findUnusedItems(): Promise<InventoryItem[]> {
|
|
711
|
-
await this.loadInventory();
|
|
712
|
-
const items = Array.from(this.inventory.values());
|
|
713
|
-
|
|
714
|
-
return items.filter(item => {
|
|
715
|
-
// Filtrar quem tem uso
|
|
716
|
-
if (item.usedBy.length > 0) return false;
|
|
717
|
-
|
|
718
|
-
// Whitelists (Falsos positivos conhecidos)
|
|
719
|
-
|
|
720
|
-
// 1. Next.js Pages/Layouts/API Routes (Sempre usados pelo framework)
|
|
721
|
-
if (item.path.includes('page.tsx') || item.path.includes('layout.tsx') || item.path.includes('route.ts')) return false;
|
|
722
|
-
|
|
723
|
-
// 2. Config files
|
|
724
|
-
if (item.name.includes('Config') || item.path.includes('config')) return false;
|
|
725
|
-
|
|
726
|
-
// 3. Types (muitas vezes importados globalmente ou d.ts)
|
|
727
|
-
if (item.type === 'type' && item.path.endsWith('.d.ts')) return false;
|
|
728
|
-
|
|
729
|
-
return true;
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
private generateCleanupReport(unused: InventoryItem[]): string {
|
|
734
|
-
let report = '# 🧹 Relatório de Limpeza de Código\n\n';
|
|
735
|
-
report += `**Data**: ${new Date().toLocaleDateString()}\n`;
|
|
736
|
-
report += `**Total de Itens Analisados**: ${this.inventory.size}\n`;
|
|
737
|
-
report += `**Potencialmente Não Utilizados**: ${unused.length}\n\n`;
|
|
738
|
-
|
|
739
|
-
report += '> âš ï¸ **Atenção**: Esta análise busca referências textuais exatas. Valide manualmente antes de excluir, pois podem haver usos dinâmicos ou indiretos.\n\n';
|
|
740
|
-
|
|
741
|
-
const byType = new Map<string, InventoryItem[]>();
|
|
742
|
-
unused.forEach(i => {
|
|
743
|
-
if (!byType.has(i.type)) byType.set(i.type, []);
|
|
744
|
-
byType.get(i.type)!.push(i);
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
for (const [type, items] of byType) {
|
|
748
|
-
report += `## ${type.toUpperCase()} (${items.length})\n`;
|
|
749
|
-
items.forEach(i => {
|
|
750
|
-
report += `- **${i.name}**\n - Path: \`${i.path}\`\n - Last Modified: ${new Date(i.lastModified).toLocaleDateString()}\n`;
|
|
751
|
-
});
|
|
752
|
-
report += '\n';
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return report;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|