wegho-agentes 4.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/.agents/AGENT_WORKFLOW.md +528 -0
- package/.agents/AI_COMPATIBILITY.md +332 -0
- package/.agents/CLI.md +222 -0
- package/.agents/README.md +130 -0
- package/.agents/cli.js +389 -0
- package/.agents/code-auditor-agent.ts +333 -0
- package/.agents/config.ts +145 -0
- package/.agents/context-loader.ts +300 -0
- package/.agents/core/agent-parallelizer.test.ts +94 -0
- package/.agents/core/agent-parallelizer.ts +108 -0
- package/.agents/core/architecture-agent.ts +237 -0
- package/.agents/core/base-agent.ts +311 -0
- package/.agents/core/cache-manager.test.ts +147 -0
- package/.agents/core/cache-manager.ts +184 -0
- package/.agents/core/documentation-agent.ts +183 -0
- package/.agents/core/feedback-collector.ts +207 -0
- package/.agents/core/frontend-agent.ts +210 -0
- package/.agents/core/inventory-agent.ts +582 -0
- package/.agents/core/memory-system.ts +397 -0
- package/.agents/core/quality-agent.ts +268 -0
- package/.agents/core/retry-utility.test.ts +165 -0
- package/.agents/core/retry-utility.ts +140 -0
- package/.agents/core/security-agent.ts +217 -0
- package/.agents/core/workflow-validator.test.ts +171 -0
- package/.agents/core/workflow-validator.ts +158 -0
- package/.agents/domains/README.md +53 -0
- package/.agents/domains/logistics/route-agent.ts +177 -0
- package/.agents/domains/news/cms-agent.ts +158 -0
- package/.agents/domains/news/seo-agent.ts +170 -0
- package/.agents/domains/production/production-control-agent.ts +169 -0
- package/.agents/example-learning-system.js +118 -0
- package/.agents/init.ts +164 -0
- package/.agents/memory/architecture-agent/failures.json +1 -0
- package/.agents/memory/architecture-agent/learnings.json +1 -0
- package/.agents/memory/architecture-agent/specialty.md +31 -0
- package/.agents/memory/architecture-agent/successes.json +16 -0
- package/.agents/memory/cms-agent/failures.json +1 -0
- package/.agents/memory/cms-agent/learnings.json +1 -0
- package/.agents/memory/cms-agent/specialty.md +30 -0
- package/.agents/memory/cms-agent/successes.json +16 -0
- package/.agents/memory/documentation-agent/failures.json +1 -0
- package/.agents/memory/documentation-agent/learnings.json +1 -0
- package/.agents/memory/documentation-agent/specialty.md +33 -0
- package/.agents/memory/documentation-agent/successes.json +16 -0
- package/.agents/memory/frontend-agent/failures.json +1 -0
- package/.agents/memory/frontend-agent/learnings.json +1 -0
- package/.agents/memory/frontend-agent/specialty.md +30 -0
- package/.agents/memory/frontend-agent/successes.json +16 -0
- package/.agents/memory/inventory-agent/failures.json +1 -0
- package/.agents/memory/inventory-agent/inventory/index.json +8 -0
- package/.agents/memory/inventory-agent/inventory/types.json +77716 -0
- package/.agents/memory/inventory-agent/inventory/variables.json +405 -0
- package/.agents/memory/inventory-agent/learnings.json +1 -0
- package/.agents/memory/inventory-agent/specialty.md +129 -0
- package/.agents/memory/inventory-agent/successes.json +30 -0
- package/.agents/memory/production-control-agent/failures.json +1 -0
- package/.agents/memory/production-control-agent/learnings.json +1 -0
- package/.agents/memory/production-control-agent/specialty.md +29 -0
- package/.agents/memory/production-control-agent/successes.json +16 -0
- package/.agents/memory/quality-agent/failures.json +16 -0
- package/.agents/memory/quality-agent/learnings.json +1 -0
- package/.agents/memory/quality-agent/specialty.md +31 -0
- package/.agents/memory/quality-agent/successes.json +1 -0
- package/.agents/memory/reference-repositories.json +271 -0
- package/.agents/memory/route-agent/failures.json +1 -0
- package/.agents/memory/route-agent/learnings.json +1 -0
- package/.agents/memory/route-agent/specialty.md +29 -0
- package/.agents/memory/route-agent/successes.json +16 -0
- package/.agents/memory/security-agent/failures.json +1 -0
- package/.agents/memory/security-agent/learnings.json +1 -0
- package/.agents/memory/security-agent/specialty.md +31 -0
- package/.agents/memory/security-agent/successes.json +16 -0
- package/.agents/memory/seo-agent/failures.json +1 -0
- package/.agents/memory/seo-agent/learnings.json +1 -0
- package/.agents/memory/seo-agent/specialty.md +31 -0
- package/.agents/memory/seo-agent/successes.json +16 -0
- package/.agents/orchestrator.ts +438 -0
- package/.agents/project-discovery-agent.ts +342 -0
- package/.agents/security/pentesting-agent.py +387 -0
- package/.agents/security/python-bridge.ts +193 -0
- package/.agents/security/vulnerability-db.json +201 -0
- package/.agents/task-analyzer-agent.ts +346 -0
- package/.agents/test-init-context.js +67 -0
- package/INSTALL.md +300 -0
- package/LICENSE +21 -0
- package/README.md +315 -0
- package/docs/AGENT_RULES.md +292 -0
- package/docs/BUILD_HISTORY.md +65 -0
- package/docs/DESIGN_SYSTEM.md +256 -0
- package/docs/LEARNING_SYSTEM.md +326 -0
- package/docs/SYMBOLS_TREE.md +182 -0
- package/docs/VERSION.md +6 -0
- package/docs/architecture.md +111 -0
- package/package.json +60 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { BaseAgent, TaskContext, TaskResult } from './base-agent';
|
|
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('check') || taskDescription.includes('verificar')) {
|
|
115
|
+
const itemName = this.extractItemName(taskDescription);
|
|
116
|
+
const check = await this.checkDuplication(itemName, 'component');
|
|
117
|
+
return {
|
|
118
|
+
success: !check.exists,
|
|
119
|
+
details: check.exists
|
|
120
|
+
? `Item "${itemName}" já existe`
|
|
121
|
+
: `Nenhuma duplicação encontrada para "${itemName}"`,
|
|
122
|
+
warnings: check.exists ? [`Duplicação detectada: ${itemName}`] : undefined,
|
|
123
|
+
recommendations: check.suggestions,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Scan padrão
|
|
128
|
+
await this.scanProject();
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
details: `Scan completo: ${this.inventory.size} itens catalogados`,
|
|
132
|
+
};
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
details: `Erro ao executar tarefa: ${(error as Error).message}`,
|
|
137
|
+
issues: [(error as Error).message],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================
|
|
143
|
+
// 🔍 MÉTODOS DE BUSCA (Para outros agentes)
|
|
144
|
+
// ============================================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Busca um único item no inventário
|
|
148
|
+
*/
|
|
149
|
+
async find(query: InventoryQuery): Promise<InventoryItem | null> {
|
|
150
|
+
const results = await this.findAll(query);
|
|
151
|
+
return results[0] || null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Busca todos os itens que correspondem à query
|
|
156
|
+
*/
|
|
157
|
+
async findAll(query: InventoryQuery): Promise<InventoryItem[]> {
|
|
158
|
+
await this.loadInventory();
|
|
159
|
+
|
|
160
|
+
let results = Array.from(this.inventory.values());
|
|
161
|
+
|
|
162
|
+
// Filtrar por tipo
|
|
163
|
+
if (query.type) {
|
|
164
|
+
results = results.filter((item) => item.type === query.type);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Filtrar por nome
|
|
168
|
+
if (query.name) {
|
|
169
|
+
if (query.fuzzy) {
|
|
170
|
+
// Busca fuzzy (case-insensitive, parcial)
|
|
171
|
+
const searchTerm = query.name.toLowerCase();
|
|
172
|
+
results = results.filter((item) => item.name.toLowerCase().includes(searchTerm));
|
|
173
|
+
} else {
|
|
174
|
+
// Busca exata
|
|
175
|
+
results = results.filter((item) => item.name === query.name);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Filtrar por path
|
|
180
|
+
if (query.path) {
|
|
181
|
+
results = results.filter((item) => item.path.includes(query.path!));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return results;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Busca textual em todo o inventário
|
|
189
|
+
*/
|
|
190
|
+
async search(text: string): Promise<InventoryItem[]> {
|
|
191
|
+
await this.loadInventory();
|
|
192
|
+
|
|
193
|
+
const searchTerm = text.toLowerCase();
|
|
194
|
+
return Array.from(this.inventory.values()).filter(
|
|
195
|
+
(item) =>
|
|
196
|
+
item.name.toLowerCase().includes(searchTerm) ||
|
|
197
|
+
item.path.toLowerCase().includes(searchTerm) ||
|
|
198
|
+
JSON.stringify(item.metadata).toLowerCase().includes(searchTerm)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================
|
|
203
|
+
// 🛡️ VERIFICAÇÃO ANTI-DUPLICAÇÃO (CRÍTICO!)
|
|
204
|
+
// ============================================================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Verifica se um item já existe antes de criar
|
|
208
|
+
* TODOS os agentes DEVEM chamar isso antes de criar algo novo!
|
|
209
|
+
*/
|
|
210
|
+
async checkDuplication(name: string, type: InventoryItem['type']): Promise<DuplicationCheck> {
|
|
211
|
+
await this.loadInventory();
|
|
212
|
+
|
|
213
|
+
// Busca exata
|
|
214
|
+
const exact = await this.find({ name, type });
|
|
215
|
+
|
|
216
|
+
// Busca fuzzy (similares)
|
|
217
|
+
const similar = await this.findAll({ name, type, fuzzy: true });
|
|
218
|
+
|
|
219
|
+
const exists = exact !== null;
|
|
220
|
+
const duplicates = similar.filter((item) => item.name !== name);
|
|
221
|
+
|
|
222
|
+
const suggestions: string[] = [];
|
|
223
|
+
|
|
224
|
+
if (exists) {
|
|
225
|
+
suggestions.push(`⚠️ Item "${name}" já existe em: ${exact.path}`);
|
|
226
|
+
suggestions.push(`💡 Use o existente em vez de criar novo`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (duplicates.length > 0) {
|
|
230
|
+
suggestions.push(`⚠️ Encontrados ${duplicates.length} item(ns) similar(es):`);
|
|
231
|
+
duplicates.forEach((dup) => {
|
|
232
|
+
suggestions.push(` - ${dup.name} (${dup.path})`);
|
|
233
|
+
});
|
|
234
|
+
suggestions.push(`💡 Considere reutilizar um desses em vez de criar novo`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!exists && duplicates.length === 0) {
|
|
238
|
+
suggestions.push(`✅ Nenhuma duplicação encontrada. Seguro criar "${name}"`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
exists,
|
|
243
|
+
duplicates,
|
|
244
|
+
suggestions,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ============================================================
|
|
249
|
+
// 📊 MÉTODOS DE SCAN
|
|
250
|
+
// ============================================================
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Escaneia o projeto inteiro e atualiza o inventário
|
|
254
|
+
*/
|
|
255
|
+
async scanProject(): Promise<void> {
|
|
256
|
+
console.log('🔍 [inventory-agent] Escaneando projeto...');
|
|
257
|
+
|
|
258
|
+
this.inventory.clear();
|
|
259
|
+
|
|
260
|
+
await Promise.all([
|
|
261
|
+
this.scanComponents(),
|
|
262
|
+
this.scanVariables(),
|
|
263
|
+
this.scanEndpoints(),
|
|
264
|
+
this.scanTypes(),
|
|
265
|
+
this.scanDatabase(),
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
await this.saveInventory();
|
|
269
|
+
|
|
270
|
+
console.log(`✅ [inventory-agent] Scan completo: ${this.inventory.size} itens catalogados`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Escaneia componentes React/Next.js
|
|
275
|
+
*/
|
|
276
|
+
private async scanComponents(): Promise<void> {
|
|
277
|
+
const patterns = ['**/*.tsx', '**/*.jsx', '!node_modules/**', '!.next/**', '!.agents/**'];
|
|
278
|
+
|
|
279
|
+
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
280
|
+
|
|
281
|
+
for (const file of files) {
|
|
282
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
283
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
284
|
+
|
|
285
|
+
// Detectar componentes exportados
|
|
286
|
+
const exportMatches = content.matchAll(/export\s+(?:default\s+)?(?:function|const|class)\s+(\w+)/g);
|
|
287
|
+
|
|
288
|
+
for (const match of exportMatches) {
|
|
289
|
+
const componentName = match[1];
|
|
290
|
+
|
|
291
|
+
// Extrair props
|
|
292
|
+
const propsMatch = content.match(new RegExp(`${componentName}.*?\\(\\s*{([^}]+)}`, 's'));
|
|
293
|
+
const props = propsMatch
|
|
294
|
+
? propsMatch[1]
|
|
295
|
+
.split(',')
|
|
296
|
+
.map((p) => p.trim().split(':')[0].trim())
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
: [];
|
|
299
|
+
|
|
300
|
+
// Extrair imports (dependências)
|
|
301
|
+
const importMatches = content.matchAll(/import\s+.*?from\s+['"]([^'"]+)['"]/g);
|
|
302
|
+
const dependencies = Array.from(importMatches, (m) => m[1]);
|
|
303
|
+
|
|
304
|
+
const component: Component = {
|
|
305
|
+
id: `component:${file}:${componentName}`,
|
|
306
|
+
name: componentName,
|
|
307
|
+
type: 'component',
|
|
308
|
+
path: file,
|
|
309
|
+
exports: [componentName],
|
|
310
|
+
props,
|
|
311
|
+
dependencies,
|
|
312
|
+
metadata: {
|
|
313
|
+
isDefaultExport: content.includes(`export default ${componentName}`),
|
|
314
|
+
},
|
|
315
|
+
usedBy: [], // Será preenchido em scan futuro
|
|
316
|
+
createdAt: new Date().toISOString(),
|
|
317
|
+
lastModified: new Date().toISOString(),
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
this.inventory.set(component.id, component);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Escaneia variáveis globais e exportadas
|
|
327
|
+
*/
|
|
328
|
+
private async scanVariables(): Promise<void> {
|
|
329
|
+
const patterns = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '!node_modules/**', '!.next/**'];
|
|
330
|
+
|
|
331
|
+
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
332
|
+
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
335
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
336
|
+
|
|
337
|
+
// Detectar variáveis exportadas
|
|
338
|
+
const varMatches = content.matchAll(/export\s+const\s+(\w+)\s*[:=]\s*(.+?)(?:[;\n])/g);
|
|
339
|
+
|
|
340
|
+
for (const match of varMatches) {
|
|
341
|
+
const [, varName, value] = match;
|
|
342
|
+
|
|
343
|
+
const variable: Variable = {
|
|
344
|
+
id: `variable:${file}:${varName}`,
|
|
345
|
+
name: varName,
|
|
346
|
+
type: 'variable',
|
|
347
|
+
path: file,
|
|
348
|
+
scope: 'exported',
|
|
349
|
+
value: value.trim(),
|
|
350
|
+
dataType: this.inferType(value),
|
|
351
|
+
metadata: {},
|
|
352
|
+
usedBy: [],
|
|
353
|
+
createdAt: new Date().toISOString(),
|
|
354
|
+
lastModified: new Date().toISOString(),
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
this.inventory.set(variable.id, variable);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Escaneia endpoints de API
|
|
364
|
+
*/
|
|
365
|
+
private async scanEndpoints(): Promise<void> {
|
|
366
|
+
const patterns = ['**/api/**/*.ts', '**/api/**/*.js', '!node_modules/**'];
|
|
367
|
+
|
|
368
|
+
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
369
|
+
|
|
370
|
+
for (const file of files) {
|
|
371
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
372
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
373
|
+
|
|
374
|
+
// Detectar métodos HTTP
|
|
375
|
+
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const;
|
|
376
|
+
|
|
377
|
+
for (const method of methods) {
|
|
378
|
+
const regex = new RegExp(`export\\s+async\\s+function\\s+${method}`, 'g');
|
|
379
|
+
if (regex.test(content)) {
|
|
380
|
+
const endpoint: Endpoint = {
|
|
381
|
+
id: `endpoint:${file}:${method}`,
|
|
382
|
+
name: file.replace(/.*\/api\//, '/api/').replace(/\/route\.(ts|js)/, ''),
|
|
383
|
+
type: 'endpoint',
|
|
384
|
+
path: file,
|
|
385
|
+
method,
|
|
386
|
+
authentication: content.includes('auth') || content.includes('session'),
|
|
387
|
+
metadata: {},
|
|
388
|
+
usedBy: [],
|
|
389
|
+
createdAt: new Date().toISOString(),
|
|
390
|
+
lastModified: new Date().toISOString(),
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
this.inventory.set(endpoint.id, endpoint);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Escaneia tipos TypeScript
|
|
401
|
+
*/
|
|
402
|
+
private async scanTypes(): Promise<void> {
|
|
403
|
+
const patterns = ['**/types/**/*.ts', '**/*.d.ts', '!node_modules/**'];
|
|
404
|
+
|
|
405
|
+
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
406
|
+
|
|
407
|
+
for (const file of files) {
|
|
408
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
409
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
410
|
+
|
|
411
|
+
// Detectar interfaces e types
|
|
412
|
+
const typeMatches = content.matchAll(/(?:export\s+)?(?:interface|type)\s+(\w+)/g);
|
|
413
|
+
|
|
414
|
+
for (const match of typeMatches) {
|
|
415
|
+
const typeName = match[1];
|
|
416
|
+
|
|
417
|
+
const typeItem: InventoryItem = {
|
|
418
|
+
id: `type:${file}:${typeName}`,
|
|
419
|
+
name: typeName,
|
|
420
|
+
type: 'type',
|
|
421
|
+
path: file,
|
|
422
|
+
metadata: {
|
|
423
|
+
isInterface: content.includes(`interface ${typeName}`),
|
|
424
|
+
isType: content.includes(`type ${typeName}`),
|
|
425
|
+
},
|
|
426
|
+
usedBy: [],
|
|
427
|
+
createdAt: new Date().toISOString(),
|
|
428
|
+
lastModified: new Date().toISOString(),
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
this.inventory.set(typeItem.id, typeItem);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Escaneia schema do banco de dados (Supabase/PostgreSQL)
|
|
438
|
+
*/
|
|
439
|
+
private async scanDatabase(): Promise<void> {
|
|
440
|
+
// Procurar por arquivos de schema ou migrations
|
|
441
|
+
const patterns = ['**/supabase/migrations/**/*.sql', '**/prisma/schema.prisma', '!node_modules/**'];
|
|
442
|
+
|
|
443
|
+
const files = await glob(patterns, { cwd: this.projectRoot });
|
|
444
|
+
|
|
445
|
+
for (const file of files) {
|
|
446
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
447
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
448
|
+
|
|
449
|
+
// Detectar CREATE TABLE
|
|
450
|
+
const tableMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s*\(/gi);
|
|
451
|
+
|
|
452
|
+
for (const match of tableMatches) {
|
|
453
|
+
const tableName = match[1];
|
|
454
|
+
|
|
455
|
+
const table: DatabaseTable = {
|
|
456
|
+
id: `table:${tableName}`,
|
|
457
|
+
name: tableName,
|
|
458
|
+
type: 'table',
|
|
459
|
+
path: file,
|
|
460
|
+
schema: 'public',
|
|
461
|
+
columns: [], // Seria necessário parser SQL completo
|
|
462
|
+
relationships: [],
|
|
463
|
+
metadata: {},
|
|
464
|
+
usedBy: [],
|
|
465
|
+
createdAt: new Date().toISOString(),
|
|
466
|
+
lastModified: new Date().toISOString(),
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
this.inventory.set(table.id, table);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ============================================================
|
|
475
|
+
// 💾 PERSISTÊNCIA
|
|
476
|
+
// ============================================================
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Salva inventário em arquivos JSON
|
|
480
|
+
*/
|
|
481
|
+
private async saveInventory(): Promise<void> {
|
|
482
|
+
await fs.mkdir(this.inventoryPath, { recursive: true });
|
|
483
|
+
|
|
484
|
+
// Agrupar por tipo
|
|
485
|
+
const byType = new Map<string, InventoryItem[]>();
|
|
486
|
+
|
|
487
|
+
for (const item of this.inventory.values()) {
|
|
488
|
+
if (!byType.has(item.type)) {
|
|
489
|
+
byType.set(item.type, []);
|
|
490
|
+
}
|
|
491
|
+
byType.get(item.type)!.push(item);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Salvar cada tipo em arquivo separado
|
|
495
|
+
for (const [type, items] of byType) {
|
|
496
|
+
const filePath = path.join(this.inventoryPath, `${type}s.json`);
|
|
497
|
+
await fs.writeFile(filePath, JSON.stringify(items, null, 2));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Salvar índice geral
|
|
501
|
+
const index = {
|
|
502
|
+
totalItems: this.inventory.size,
|
|
503
|
+
byType: Object.fromEntries(
|
|
504
|
+
Array.from(byType.entries()).map(([type, items]) => [type, items.length])
|
|
505
|
+
),
|
|
506
|
+
lastUpdated: new Date().toISOString(),
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
await fs.writeFile(path.join(this.inventoryPath, 'index.json'), JSON.stringify(index, null, 2));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Carrega inventário dos arquivos JSON
|
|
514
|
+
*/
|
|
515
|
+
private async loadInventory(): Promise<void> {
|
|
516
|
+
try {
|
|
517
|
+
const indexPath = path.join(this.inventoryPath, 'index.json');
|
|
518
|
+
const indexExists = await fs
|
|
519
|
+
.access(indexPath)
|
|
520
|
+
.then(() => true)
|
|
521
|
+
.catch(() => false);
|
|
522
|
+
|
|
523
|
+
if (!indexExists) {
|
|
524
|
+
// Inventário vazio, fazer scan inicial
|
|
525
|
+
await this.scanProject();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
this.inventory.clear();
|
|
530
|
+
|
|
531
|
+
const types = ['component', 'variable', 'table', 'endpoint', 'type'];
|
|
532
|
+
|
|
533
|
+
for (const type of types) {
|
|
534
|
+
const filePath = path.join(this.inventoryPath, `${type}s.json`);
|
|
535
|
+
const fileExists = await fs
|
|
536
|
+
.access(filePath)
|
|
537
|
+
.then(() => true)
|
|
538
|
+
.catch(() => false);
|
|
539
|
+
|
|
540
|
+
if (fileExists) {
|
|
541
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
542
|
+
const items = JSON.parse(content) as InventoryItem[];
|
|
543
|
+
|
|
544
|
+
for (const item of items) {
|
|
545
|
+
this.inventory.set(item.id, item);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.warn('[inventory-agent] Erro ao carregar inventário:', error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ============================================================
|
|
555
|
+
// 🛠️ UTILITÁRIOS
|
|
556
|
+
// ============================================================
|
|
557
|
+
|
|
558
|
+
private inferType(value: string): string {
|
|
559
|
+
if (value.startsWith("'") || value.startsWith('"') || value.startsWith('`')) return 'string';
|
|
560
|
+
if (value === 'true' || value === 'false') return 'boolean';
|
|
561
|
+
if (!isNaN(Number(value))) return 'number';
|
|
562
|
+
if (value.startsWith('[')) return 'array';
|
|
563
|
+
if (value.startsWith('{')) return 'object';
|
|
564
|
+
return 'unknown';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private parseQuery(description: string): InventoryQuery {
|
|
568
|
+
const query: InventoryQuery = { fuzzy: true };
|
|
569
|
+
|
|
570
|
+
if (description.includes('component')) query.type = 'component';
|
|
571
|
+
if (description.includes('variable')) query.type = 'variable';
|
|
572
|
+
if (description.includes('endpoint')) query.type = 'endpoint';
|
|
573
|
+
if (description.includes('table')) query.type = 'table';
|
|
574
|
+
|
|
575
|
+
return query;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private extractItemName(description: string): string {
|
|
579
|
+
const match = description.match(/["']([^"']+)["']/);
|
|
580
|
+
return match ? match[1] : '';
|
|
581
|
+
}
|
|
582
|
+
}
|