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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testes para RetryUtility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RetryUtility } from './retry-utility';
|
|
6
|
+
|
|
7
|
+
describe('RetryUtility', () => {
|
|
8
|
+
// Aumentar timeout para testes assíncronos
|
|
9
|
+
jest.setTimeout(10000);
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// Limpar todos os timers após cada teste
|
|
13
|
+
jest.clearAllTimers();
|
|
14
|
+
jest.useRealTimers();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('executeWithRetry', () => {
|
|
18
|
+
test('deve executar função com sucesso na primeira tentativa', async () => {
|
|
19
|
+
const mockFn = jest.fn().mockResolvedValue('success');
|
|
20
|
+
|
|
21
|
+
const result = await RetryUtility.executeWithRetry(mockFn, { maxRetries: 3 });
|
|
22
|
+
|
|
23
|
+
expect(result).toBe('success');
|
|
24
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('deve fazer retry em caso de falha', async () => {
|
|
28
|
+
const mockFn = jest.fn()
|
|
29
|
+
.mockRejectedValueOnce(new Error('Falha 1'))
|
|
30
|
+
.mockRejectedValueOnce(new Error('Falha 2'))
|
|
31
|
+
.mockResolvedValue('success');
|
|
32
|
+
|
|
33
|
+
const result = await RetryUtility.executeWithRetry(mockFn, {
|
|
34
|
+
maxRetries: 3,
|
|
35
|
+
initialDelay: 50,
|
|
36
|
+
backoffMultiplier: 1 // Sem backoff para teste rápido
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(result).toBe('success');
|
|
40
|
+
expect(mockFn).toHaveBeenCalledTimes(3);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('deve lançar erro após esgotar retries', async () => {
|
|
44
|
+
const mockFn = jest.fn().mockRejectedValue(new Error('Sempre falha'));
|
|
45
|
+
|
|
46
|
+
await expect(
|
|
47
|
+
RetryUtility.executeWithRetry(mockFn, {
|
|
48
|
+
maxRetries: 2,
|
|
49
|
+
initialDelay: 50,
|
|
50
|
+
backoffMultiplier: 1
|
|
51
|
+
})
|
|
52
|
+
).rejects.toThrow('Falha após 2 tentativas');
|
|
53
|
+
|
|
54
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('deve respeitar delays entre retries', async () => {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
const mockFn = jest.fn()
|
|
60
|
+
.mockRejectedValueOnce(new Error('Falha 1'))
|
|
61
|
+
.mockResolvedValue('success');
|
|
62
|
+
|
|
63
|
+
await RetryUtility.executeWithRetry(mockFn, {
|
|
64
|
+
maxRetries: 2,
|
|
65
|
+
initialDelay: 100,
|
|
66
|
+
backoffMultiplier: 1
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const totalTime = Date.now() - startTime;
|
|
70
|
+
// Deve ter esperado pelo menos 100ms
|
|
71
|
+
expect(totalTime).toBeGreaterThanOrEqual(90); // Margem de erro
|
|
72
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('deve incluir contexto na mensagem de erro', async () => {
|
|
76
|
+
const mockFn = jest.fn().mockRejectedValue(new Error('Erro'));
|
|
77
|
+
|
|
78
|
+
await expect(
|
|
79
|
+
RetryUtility.executeWithRetry(
|
|
80
|
+
mockFn,
|
|
81
|
+
{ maxRetries: 1, initialDelay: 10 },
|
|
82
|
+
'test-context'
|
|
83
|
+
)
|
|
84
|
+
).rejects.toThrow('test-context');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('executeWithTimeout', () => {
|
|
89
|
+
test('deve executar função dentro do timeout', async () => {
|
|
90
|
+
const mockFn = jest.fn().mockResolvedValue('success');
|
|
91
|
+
|
|
92
|
+
const result = await RetryUtility.executeWithTimeout(mockFn, 1000);
|
|
93
|
+
|
|
94
|
+
expect(result).toBe('success');
|
|
95
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('deve lançar erro de timeout se exceder tempo', async () => {
|
|
99
|
+
const mockFn = jest.fn().mockImplementation(
|
|
100
|
+
() => new Promise(resolve => setTimeout(() => resolve('success'), 500))
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await expect(
|
|
104
|
+
RetryUtility.executeWithTimeout(mockFn, 100)
|
|
105
|
+
).rejects.toThrow('Timeout após 100ms');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('deve incluir contexto na mensagem de timeout', async () => {
|
|
109
|
+
const mockFn = jest.fn().mockImplementation(
|
|
110
|
+
() => new Promise(resolve => setTimeout(() => resolve('success'), 500))
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
await expect(
|
|
114
|
+
RetryUtility.executeWithTimeout(mockFn, 100, 'test-operation')
|
|
115
|
+
).rejects.toThrow('test-operation');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('executeWithTimeoutAndRetry', () => {
|
|
120
|
+
test('deve combinar timeout e retry', async () => {
|
|
121
|
+
const mockFn = jest.fn()
|
|
122
|
+
.mockRejectedValueOnce(new Error('Falha'))
|
|
123
|
+
.mockResolvedValue('success');
|
|
124
|
+
|
|
125
|
+
const result = await RetryUtility.executeWithTimeoutAndRetry(
|
|
126
|
+
mockFn,
|
|
127
|
+
1000,
|
|
128
|
+
{ maxRetries: 2, initialDelay: 50, backoffMultiplier: 1 }
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(result).toBe('success');
|
|
132
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('deve falhar se timeout ocorrer', async () => {
|
|
136
|
+
const mockFn = jest.fn().mockImplementation(
|
|
137
|
+
() => new Promise(resolve => setTimeout(() => resolve('success'), 500))
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await expect(
|
|
141
|
+
RetryUtility.executeWithTimeoutAndRetry(
|
|
142
|
+
mockFn,
|
|
143
|
+
100,
|
|
144
|
+
{ maxRetries: 1, initialDelay: 10 }
|
|
145
|
+
)
|
|
146
|
+
).rejects.toThrow('Timeout');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('deve incluir nome do agente no contexto', async () => {
|
|
150
|
+
const mockFn = jest.fn().mockImplementation(
|
|
151
|
+
() => new Promise(resolve => setTimeout(() => resolve('success'), 500))
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await expect(
|
|
155
|
+
RetryUtility.executeWithTimeoutAndRetry(
|
|
156
|
+
mockFn,
|
|
157
|
+
100,
|
|
158
|
+
{ maxRetries: 1, initialDelay: 10 },
|
|
159
|
+
'my-agent'
|
|
160
|
+
)
|
|
161
|
+
).rejects.toThrow('my-agent');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Utility - Implementa retry logic com backoff exponencial
|
|
3
|
+
*
|
|
4
|
+
* Funcionalidades:
|
|
5
|
+
* - Retry automático em caso de falha
|
|
6
|
+
* - Backoff exponencial (delay aumenta a cada tentativa)
|
|
7
|
+
* - Configurável por tipo de erro
|
|
8
|
+
* - Logging de tentativas
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface RetryOptions {
|
|
12
|
+
maxRetries: number;
|
|
13
|
+
initialDelay: number;
|
|
14
|
+
maxDelay: number;
|
|
15
|
+
backoffMultiplier: number;
|
|
16
|
+
retryableErrors?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
|
20
|
+
maxRetries: 3,
|
|
21
|
+
initialDelay: 1000,
|
|
22
|
+
maxDelay: 10000,
|
|
23
|
+
backoffMultiplier: 2,
|
|
24
|
+
retryableErrors: ['ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND']
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class RetryUtility {
|
|
28
|
+
/**
|
|
29
|
+
* Executa função com retry automático
|
|
30
|
+
*/
|
|
31
|
+
static async executeWithRetry<T>(
|
|
32
|
+
fn: () => Promise<T>,
|
|
33
|
+
options: Partial<RetryOptions> = {},
|
|
34
|
+
context?: string
|
|
35
|
+
): Promise<T> {
|
|
36
|
+
const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
37
|
+
let lastError: Error;
|
|
38
|
+
|
|
39
|
+
for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
|
|
40
|
+
try {
|
|
41
|
+
return await fn();
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
lastError = error;
|
|
44
|
+
|
|
45
|
+
// Verificar se erro é retryable
|
|
46
|
+
if (!this.isRetryableError(error, opts.retryableErrors)) {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Última tentativa - não fazer retry
|
|
51
|
+
if (attempt === opts.maxRetries) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Calcular delay com backoff exponencial
|
|
56
|
+
const delay = Math.min(
|
|
57
|
+
opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt - 1),
|
|
58
|
+
opts.maxDelay
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
console.log(
|
|
62
|
+
`⚠️ [Retry] Tentativa ${attempt}/${opts.maxRetries} falhou` +
|
|
63
|
+
(context ? ` (${context})` : '') +
|
|
64
|
+
`. Retentando em ${delay}ms...`
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await this.sleep(delay);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Falha após ${opts.maxRetries} tentativas` +
|
|
73
|
+
(context ? ` (${context})` : '') +
|
|
74
|
+
`: ${lastError!.message}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Verifica se erro é retryable
|
|
80
|
+
*/
|
|
81
|
+
private static isRetryableError(error: any, retryableErrors?: string[]): boolean {
|
|
82
|
+
if (!retryableErrors || retryableErrors.length === 0) {
|
|
83
|
+
return true; // Retry em todos os erros se não especificado
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const errorCode = error.code || error.name || '';
|
|
87
|
+
const errorMessage = error.message || '';
|
|
88
|
+
|
|
89
|
+
return retryableErrors.some(
|
|
90
|
+
retryable =>
|
|
91
|
+
errorCode.includes(retryable) ||
|
|
92
|
+
errorMessage.includes(retryable)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Sleep helper
|
|
98
|
+
*/
|
|
99
|
+
private static sleep(ms: number): Promise<void> {
|
|
100
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Executa com timeout
|
|
105
|
+
*/
|
|
106
|
+
static async executeWithTimeout<T>(
|
|
107
|
+
fn: () => Promise<T>,
|
|
108
|
+
timeoutMs: number,
|
|
109
|
+
context?: string
|
|
110
|
+
): Promise<T> {
|
|
111
|
+
return Promise.race([
|
|
112
|
+
fn(),
|
|
113
|
+
new Promise<T>((_, reject) =>
|
|
114
|
+
setTimeout(
|
|
115
|
+
() => reject(new Error(
|
|
116
|
+
`Timeout após ${timeoutMs}ms` +
|
|
117
|
+
(context ? ` (${context})` : '')
|
|
118
|
+
)),
|
|
119
|
+
timeoutMs
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Executa com timeout E retry
|
|
127
|
+
*/
|
|
128
|
+
static async executeWithTimeoutAndRetry<T>(
|
|
129
|
+
fn: () => Promise<T>,
|
|
130
|
+
timeoutMs: number,
|
|
131
|
+
retryOptions: Partial<RetryOptions> = {},
|
|
132
|
+
context?: string
|
|
133
|
+
): Promise<T> {
|
|
134
|
+
return this.executeWithRetry(
|
|
135
|
+
() => this.executeWithTimeout(fn, timeoutMs, context),
|
|
136
|
+
retryOptions,
|
|
137
|
+
context
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Agent (com Memória)
|
|
3
|
+
*
|
|
4
|
+
* Responsabilidades:
|
|
5
|
+
* - Detectar vulnerabilidades (XSS, CSRF, SQL Injection)
|
|
6
|
+
* - Validar sanitização de inputs
|
|
7
|
+
* - Verificar proteção de rotas
|
|
8
|
+
* - Validar uso seguro de variáveis de ambiente
|
|
9
|
+
* - Detectar exposição de dados sensíveis
|
|
10
|
+
* - Aprender com vulnerabilidades detectadas
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import { BaseAgent, TaskContext, TaskResult } from './base-agent';
|
|
15
|
+
|
|
16
|
+
export interface SecurityCheckResult extends TaskResult {
|
|
17
|
+
vulnerabilities: Array<{
|
|
18
|
+
severity: 'P0' | 'P1' | 'P2';
|
|
19
|
+
type: string;
|
|
20
|
+
file: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
message: string;
|
|
23
|
+
suggestion: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class SecurityAgent extends BaseAgent {
|
|
28
|
+
constructor(memoryBasePath: string = '.agents/memory') {
|
|
29
|
+
super('security-agent', memoryBasePath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Implementação da validação de segurança
|
|
34
|
+
*/
|
|
35
|
+
async executeTask(taskDescription: string, context: TaskContext): Promise<TaskResult> {
|
|
36
|
+
const vulnerabilities: SecurityCheckResult['vulnerabilities'] = [];
|
|
37
|
+
|
|
38
|
+
for (const filePath of context.files) {
|
|
39
|
+
if (!fs.existsSync(filePath)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
44
|
+
const lines = content.split('\n');
|
|
45
|
+
|
|
46
|
+
// 1. dangerouslySetInnerHTML sem sanitização (P0)
|
|
47
|
+
if (content.includes('dangerouslySetInnerHTML') && !content.includes('DOMPurify')) {
|
|
48
|
+
vulnerabilities.push({
|
|
49
|
+
severity: 'P0',
|
|
50
|
+
type: 'xss-vulnerability',
|
|
51
|
+
file: filePath,
|
|
52
|
+
message: 'dangerouslySetInnerHTML sem sanitização detectado',
|
|
53
|
+
suggestion: 'Usar DOMPurify.sanitize() antes de renderizar HTML',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. service_role no cliente (P0)
|
|
58
|
+
if (content.match(/service_role|SUPABASE_SERVICE_ROLE/i) && !filePath.includes('server')) {
|
|
59
|
+
vulnerabilities.push({
|
|
60
|
+
severity: 'P0',
|
|
61
|
+
type: 'security-critical',
|
|
62
|
+
file: filePath,
|
|
63
|
+
message: 'service_role key no código cliente (NUNCA expor no browser!)',
|
|
64
|
+
suggestion: 'Mover para variável de ambiente server-side ou usar anon key',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. console.log com dados sensíveis (P0)
|
|
69
|
+
lines.forEach((line, index) => {
|
|
70
|
+
if (line.match(/console\.(log|debug|info).*?(password|token|secret|key|credential)/i)) {
|
|
71
|
+
vulnerabilities.push({
|
|
72
|
+
severity: 'P0',
|
|
73
|
+
type: 'security-leak',
|
|
74
|
+
file: filePath,
|
|
75
|
+
line: index + 1,
|
|
76
|
+
message: 'console.log com dados sensíveis detectado',
|
|
77
|
+
suggestion: 'Remover log ou usar técnica de mascaramento',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 4. NEXT_PUBLIC_ com dados sensíveis (P0)
|
|
83
|
+
if (content.match(/NEXT_PUBLIC_.*?(SECRET|KEY|PASSWORD|TOKEN)/i)) {
|
|
84
|
+
vulnerabilities.push({
|
|
85
|
+
severity: 'P0',
|
|
86
|
+
type: 'security-critical',
|
|
87
|
+
file: filePath,
|
|
88
|
+
message: 'Variável sensível exposta com NEXT_PUBLIC_',
|
|
89
|
+
suggestion: 'Remover NEXT_PUBLIC_ e usar apenas no server-side',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 5. Inputs sem validação (P1)
|
|
94
|
+
if (content.includes('<input') && !content.match(/zod|yup|validator/i)) {
|
|
95
|
+
vulnerabilities.push({
|
|
96
|
+
severity: 'P1',
|
|
97
|
+
type: 'missing-validation',
|
|
98
|
+
file: filePath,
|
|
99
|
+
message: 'Inputs detectados sem validação (Zod/Yup)',
|
|
100
|
+
suggestion: 'Adicionar schema de validação com Zod ou Yup',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 6. fetch() sem tratamento de erro (P1)
|
|
105
|
+
lines.forEach((line, index) => {
|
|
106
|
+
if (line.includes('fetch(') || line.includes('axios.')) {
|
|
107
|
+
const nextLines = lines.slice(index, index + 10).join('\n');
|
|
108
|
+
if (!nextLines.match(/\.catch|try|error/i)) {
|
|
109
|
+
vulnerabilities.push({
|
|
110
|
+
severity: 'P1',
|
|
111
|
+
type: 'missing-error-handling',
|
|
112
|
+
file: filePath,
|
|
113
|
+
line: index + 1,
|
|
114
|
+
message: 'fetch() ou axios sem tratamento de erro',
|
|
115
|
+
suggestion: 'Adicionar .catch() ou try-catch',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const p0Count = vulnerabilities.filter(v => v.severity === 'P0').length;
|
|
123
|
+
const success = p0Count === 0;
|
|
124
|
+
|
|
125
|
+
const details = this.formatVulnerabilities(vulnerabilities);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success,
|
|
129
|
+
details,
|
|
130
|
+
issues: vulnerabilities.filter(v => v.severity === 'P0').map(v => v.message),
|
|
131
|
+
warnings: vulnerabilities.filter(v => v.severity === 'P1').map(v => v.message),
|
|
132
|
+
recommendations: vulnerabilities.filter(v => v.severity === 'P2').map(v => v.message),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Valida segurança de arquivos (método de conveniência)
|
|
138
|
+
*/
|
|
139
|
+
async validateSecurity(files: string[]): Promise<SecurityCheckResult> {
|
|
140
|
+
const context: TaskContext = {
|
|
141
|
+
files,
|
|
142
|
+
areas: ['security'],
|
|
143
|
+
complexity: 'medium',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const result = await this.executeWithMemory(
|
|
147
|
+
`Validar segurança de ${files.length} arquivo(s)`,
|
|
148
|
+
context
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...result,
|
|
153
|
+
vulnerabilities: this.extractVulnerabilities(result),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Formata vulnerabilidades
|
|
159
|
+
*/
|
|
160
|
+
private formatVulnerabilities(vulnerabilities: SecurityCheckResult['vulnerabilities']): string {
|
|
161
|
+
if (vulnerabilities.length === 0) {
|
|
162
|
+
return 'Nenhuma vulnerabilidade detectada';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const p0 = vulnerabilities.filter(v => v.severity === 'P0').length;
|
|
166
|
+
const p1 = vulnerabilities.filter(v => v.severity === 'P1').length;
|
|
167
|
+
const p2 = vulnerabilities.filter(v => v.severity === 'P2').length;
|
|
168
|
+
|
|
169
|
+
return `Vulnerabilidades: P0=${p0}, P1=${p1}, P2=${p2}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extrai vulnerabilidades do resultado
|
|
174
|
+
*/
|
|
175
|
+
private extractVulnerabilities(result: TaskResult): SecurityCheckResult['vulnerabilities'] {
|
|
176
|
+
// Implementação simplificada - em produção, seria mais robusto
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Especialidade padrão do Security Agent
|
|
182
|
+
*/
|
|
183
|
+
protected getDefaultSpecialty(): string {
|
|
184
|
+
return `# Security Agent - Especialidade
|
|
185
|
+
|
|
186
|
+
## Responsabilidades
|
|
187
|
+
- Detectar vulnerabilidades (XSS, CSRF, SQL Injection)
|
|
188
|
+
- Validar sanitização de inputs
|
|
189
|
+
- Verificar proteção de rotas
|
|
190
|
+
- Validar uso seguro de variáveis de ambiente
|
|
191
|
+
- Detectar exposição de dados sensíveis
|
|
192
|
+
|
|
193
|
+
## Expertise
|
|
194
|
+
- OWASP Top 10
|
|
195
|
+
- XSS, CSRF, SQL Injection
|
|
196
|
+
- Autenticação e Autorização
|
|
197
|
+
- Criptografia e Hashing
|
|
198
|
+
- Segurança de APIs
|
|
199
|
+
- Row Level Security (RLS)
|
|
200
|
+
|
|
201
|
+
## Regras
|
|
202
|
+
- NUNCA expor service_role no cliente
|
|
203
|
+
- Sempre sanitizar HTML com DOMPurify
|
|
204
|
+
- Validar todos os inputs (Zod/Yup)
|
|
205
|
+
- Usar HTTPS em produção
|
|
206
|
+
- Não logar dados sensíveis
|
|
207
|
+
- Variáveis sensíveis apenas server-side
|
|
208
|
+
|
|
209
|
+
## Tarefas Típicas
|
|
210
|
+
- Auditar código para vulnerabilidades
|
|
211
|
+
- Validar autenticação e autorização
|
|
212
|
+
- Verificar sanitização de inputs
|
|
213
|
+
- Revisar uso de variáveis de ambiente
|
|
214
|
+
- Detectar exposição de dados sensíveis
|
|
215
|
+
`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testes para WorkflowValidator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WorkflowValidator, WorkflowStep } from './workflow-validator';
|
|
6
|
+
|
|
7
|
+
describe('WorkflowValidator', () => {
|
|
8
|
+
let validator: WorkflowValidator;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
validator = new WorkflowValidator();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('validateAgentExecution', () => {
|
|
15
|
+
test('deve validar workflow completo com todos os passos', () => {
|
|
16
|
+
const steps: WorkflowStep[] = [
|
|
17
|
+
{ name: 'Consultar Memória', required: true, completed: true, timestamp: new Date() },
|
|
18
|
+
{ name: 'Obter Recomendações', required: true, completed: true, timestamp: new Date() },
|
|
19
|
+
{ name: 'Consultar Repositórios', required: true, completed: true, timestamp: new Date() },
|
|
20
|
+
{ name: 'Aplicar Aprendizados', required: true, completed: true, timestamp: new Date() },
|
|
21
|
+
{ name: 'Registrar Resultado', required: true, completed: true, timestamp: new Date() }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const result = validator.validateAgentExecution('test-agent', steps);
|
|
25
|
+
|
|
26
|
+
expect(result.compliant).toBe(true);
|
|
27
|
+
expect(result.missingSteps).toHaveLength(0);
|
|
28
|
+
expect(result.completedSteps).toHaveLength(5);
|
|
29
|
+
expect(result.totalRequired).toBe(5);
|
|
30
|
+
expect(result.totalCompleted).toBe(5);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('deve detectar passos faltantes', () => {
|
|
34
|
+
const steps: WorkflowStep[] = [
|
|
35
|
+
{ name: 'Consultar Memória', required: true, completed: true },
|
|
36
|
+
{ name: 'Obter Recomendações', required: true, completed: false },
|
|
37
|
+
{ name: 'Consultar Repositórios', required: true, completed: false }
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const result = validator.validateAgentExecution('test-agent', steps);
|
|
41
|
+
|
|
42
|
+
expect(result.compliant).toBe(false);
|
|
43
|
+
expect(result.missingSteps).toContain('Obter Recomendações');
|
|
44
|
+
expect(result.missingSteps).toContain('Consultar Repositórios');
|
|
45
|
+
expect(result.totalCompleted).toBe(1);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('deve ignorar passos opcionais não completados', () => {
|
|
49
|
+
const steps: WorkflowStep[] = [
|
|
50
|
+
{ name: 'Consultar Memória', required: true, completed: true },
|
|
51
|
+
{ name: 'Obter Recomendações', required: true, completed: true },
|
|
52
|
+
{ name: 'Consultar Repositórios', required: true, completed: true },
|
|
53
|
+
{ name: 'Aplicar Aprendizados', required: true, completed: true },
|
|
54
|
+
{ name: 'Registrar Resultado', required: true, completed: true },
|
|
55
|
+
{ name: 'Gerar Aprendizados', required: false, completed: false }
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const result = validator.validateAgentExecution('test-agent', steps);
|
|
59
|
+
|
|
60
|
+
expect(result.compliant).toBe(true);
|
|
61
|
+
expect(result.missingSteps).toHaveLength(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('deve gerar warnings para passos críticos faltantes', () => {
|
|
65
|
+
const steps: WorkflowStep[] = [
|
|
66
|
+
{ name: 'Obter Recomendações', required: true, completed: true },
|
|
67
|
+
{ name: 'Consultar Repositórios', required: true, completed: true }
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const result = validator.validateAgentExecution('test-agent', steps);
|
|
71
|
+
|
|
72
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
73
|
+
expect(result.warnings.some(w => w.includes('Consultar Memória'))).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('deve detectar passos fora de ordem', () => {
|
|
77
|
+
const now = new Date();
|
|
78
|
+
const steps: WorkflowStep[] = [
|
|
79
|
+
{ name: 'Registrar Resultado', required: true, completed: true, timestamp: new Date(now.getTime() - 1000) },
|
|
80
|
+
{ name: 'Consultar Memória', required: true, completed: true, timestamp: now }
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const result = validator.validateAgentExecution('test-agent', steps);
|
|
84
|
+
|
|
85
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
86
|
+
expect(result.warnings.some(w => w.includes('fora de ordem'))).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('generateValidationReport', () => {
|
|
91
|
+
test('deve gerar relatório para workflow compliant', () => {
|
|
92
|
+
const validation = {
|
|
93
|
+
compliant: true,
|
|
94
|
+
missingSteps: [],
|
|
95
|
+
warnings: [],
|
|
96
|
+
completedSteps: ['Consultar Memória', 'Obter Recomendações'],
|
|
97
|
+
totalRequired: 2,
|
|
98
|
+
totalCompleted: 2
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const report = validator.generateValidationReport('test-agent', validation);
|
|
102
|
+
|
|
103
|
+
expect(report).toContain('test-agent');
|
|
104
|
+
expect(report).toContain('✅ COMPLIANT');
|
|
105
|
+
expect(report).toContain('Consultar Memória');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('deve gerar relatório para workflow non-compliant', () => {
|
|
109
|
+
const validation = {
|
|
110
|
+
compliant: false,
|
|
111
|
+
missingSteps: ['Consultar Repositórios'],
|
|
112
|
+
warnings: ['Teste de warning'],
|
|
113
|
+
completedSteps: ['Consultar Memória'],
|
|
114
|
+
totalRequired: 2,
|
|
115
|
+
totalCompleted: 1
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const report = validator.generateValidationReport('test-agent', validation);
|
|
119
|
+
|
|
120
|
+
expect(report).toContain('❌ NON-COMPLIANT');
|
|
121
|
+
expect(report).toContain('Consultar Repositórios');
|
|
122
|
+
expect(report).toContain('Teste de warning');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('validateMultipleAgents', () => {
|
|
127
|
+
test('deve validar múltiplos agentes', () => {
|
|
128
|
+
const executions = [
|
|
129
|
+
{
|
|
130
|
+
agentName: 'agent-1',
|
|
131
|
+
steps: [
|
|
132
|
+
{ name: 'Consultar Memória', required: true, completed: true },
|
|
133
|
+
{ name: 'Registrar Resultado', required: true, completed: true }
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
agentName: 'agent-2',
|
|
138
|
+
steps: [
|
|
139
|
+
{ name: 'Consultar Memória', required: true, completed: false }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
const result = validator.validateMultipleAgents(executions);
|
|
145
|
+
|
|
146
|
+
expect(result.allCompliant).toBe(false);
|
|
147
|
+
expect(result.compliantCount).toBe(1);
|
|
148
|
+
expect(result.nonCompliantCount).toBe(1);
|
|
149
|
+
expect(result.details).toHaveLength(2);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('deve retornar allCompliant true quando todos passam', () => {
|
|
153
|
+
const executions = [
|
|
154
|
+
{
|
|
155
|
+
agentName: 'agent-1',
|
|
156
|
+
steps: [{ name: 'Step 1', required: true, completed: true }]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
agentName: 'agent-2',
|
|
160
|
+
steps: [{ name: 'Step 1', required: true, completed: true }]
|
|
161
|
+
}
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const result = validator.validateMultipleAgents(executions);
|
|
165
|
+
|
|
166
|
+
expect(result.allCompliant).toBe(true);
|
|
167
|
+
expect(result.compliantCount).toBe(2);
|
|
168
|
+
expect(result.nonCompliantCount).toBe(0);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|