agent-master-devsecops 0.0.0-stage → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorignore +23 -0
- package/README.md +371 -3
- package/agents/AGENT_DEVSECOPS.md +417 -0
- package/agents/AGENT_ORCHESTRATOR.md +264 -0
- package/agents/AGENT_QA.md +186 -0
- package/agents/AGENT_SCRUM.md +190 -0
- package/agents/AGENT_SRE.md +173 -0
- package/agents/AGENT_UIUX.md +162 -0
- package/agents/compact/AGENT_ORCHESTRATOR_COMPACTO.md +53 -0
- package/docs/ARCHITECTURE.md +148 -0
- package/docs/CALCULATIONS.md +17 -0
- package/docs/CHANGELOG.md +49 -0
- package/docs/ENGINEERING_RULES.md +252 -0
- package/docs/IDEAS.md +17 -0
- package/docs/STATE.md +43 -0
- package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -0
- package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -0
- package/package.json +47 -6
- package/scripts/a11y_auditor.js +187 -0
- package/scripts/ast_skeletonizer.js +220 -0
- package/scripts/atlassian_client.js +238 -0
- package/scripts/atlassian_sync_action.js +269 -0
- package/scripts/bootstrap_cli.js +468 -0
- package/scripts/circuit_breaker.js +330 -0
- package/scripts/code_quality_auditor.js +142 -0
- package/scripts/confluence_sync.js +337 -0
- package/scripts/context_compressor.js +139 -0
- package/scripts/contract_test_validator.js +166 -0
- package/scripts/credentials_guard.js +216 -0
- package/scripts/domain_idea_miner.js +364 -0
- package/scripts/eval_harness.js +100 -0
- package/scripts/fast_feature_scaffold.js +338 -0
- package/scripts/git_hygiene_linter.js +186 -0
- package/scripts/hot_state_cache.js +95 -0
- package/scripts/jira_manager.js +408 -0
- package/scripts/magic_prompt_generator.js +205 -0
- package/scripts/mcp_server.js +1884 -0
- package/scripts/multi_agent_orchestrator.js +258 -0
- package/scripts/otel_tracer.js +228 -0
- package/scripts/perf_benchmark.js +153 -0
- package/scripts/pr_reviewer.js +373 -0
- package/scripts/sast_scanner.js +180 -0
- package/scripts/sca_dependency_auditor.js +167 -0
- package/scripts/smart_onboarding.js +610 -0
- package/scripts/sre_remediation_engine.js +174 -0
- package/scripts/universal_packager.js +313 -0
- package/scripts/web_inspector.js +355 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/circuit_breaker.js
|
|
4
|
+
*
|
|
5
|
+
* Circuit Breaker Inteligente Multi-Provedor para LLMs (AGDEV-9 / I-10).
|
|
6
|
+
* Abstração de resiliência e auto-cura (CLOSED, OPEN, HALF_OPEN) com fallback automático
|
|
7
|
+
* entre provedores de IA (Anthropic, OpenAI, Groq, Ollama) diante de erros HTTP 429/5xx ou timeouts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const STATES = {
|
|
11
|
+
CLOSED: 'CLOSED', // Operação normal
|
|
12
|
+
OPEN: 'OPEN', // Circuito rompido; chamadas desviadas para fallback
|
|
13
|
+
HALF_OPEN: 'HALF_OPEN' // Sonda de teste para verificar recuperação
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
class CircuitBreaker {
|
|
17
|
+
constructor(name, options = {}) {
|
|
18
|
+
this.name = name;
|
|
19
|
+
this.failureThreshold = options.failureThreshold || 3;
|
|
20
|
+
this.recoveryTimeoutMs = options.recoveryTimeoutMs || 30000;
|
|
21
|
+
this.state = STATES.CLOSED;
|
|
22
|
+
this.failureCount = 0;
|
|
23
|
+
this.successCount = 0;
|
|
24
|
+
this.lastFailureTime = null;
|
|
25
|
+
this.lastStateChange = Date.now();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isOpen() {
|
|
29
|
+
if (this.state === STATES.OPEN) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
if (now - this.lastFailureTime >= this.recoveryTimeoutMs) {
|
|
32
|
+
this.state = STATES.HALF_OPEN;
|
|
33
|
+
this.lastStateChange = now;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
recordSuccess() {
|
|
42
|
+
this.failureCount = 0;
|
|
43
|
+
this.successCount++;
|
|
44
|
+
if (this.state === STATES.HALF_OPEN) {
|
|
45
|
+
this.state = STATES.CLOSED;
|
|
46
|
+
this.lastStateChange = Date.now();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
recordFailure(error) {
|
|
51
|
+
this.failureCount++;
|
|
52
|
+
this.lastFailureTime = Date.now();
|
|
53
|
+
|
|
54
|
+
if (this.state === STATES.HALF_OPEN || this.failureCount >= this.failureThreshold) {
|
|
55
|
+
this.state = STATES.OPEN;
|
|
56
|
+
this.lastStateChange = Date.now();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async execute(primaryFn, fallbackFn = null) {
|
|
61
|
+
if (this.isOpen()) {
|
|
62
|
+
if (fallbackFn) {
|
|
63
|
+
return {
|
|
64
|
+
result: await fallbackFn(new Error(`Circuito ${this.name} está ABERTO (OPEN). Redirecionando para fallback.`)),
|
|
65
|
+
circuit: this.name,
|
|
66
|
+
state: this.state,
|
|
67
|
+
fallbackExecuted: true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Circuito ${this.name} está ABERTO (OPEN). Nenhuma chamada permitida.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = await primaryFn();
|
|
75
|
+
this.recordSuccess();
|
|
76
|
+
return {
|
|
77
|
+
result,
|
|
78
|
+
circuit: this.name,
|
|
79
|
+
state: this.state,
|
|
80
|
+
fallbackExecuted: false
|
|
81
|
+
};
|
|
82
|
+
} catch (err) {
|
|
83
|
+
this.recordFailure(err);
|
|
84
|
+
if (fallbackFn) {
|
|
85
|
+
return {
|
|
86
|
+
result: await fallbackFn(err),
|
|
87
|
+
circuit: this.name,
|
|
88
|
+
state: this.state,
|
|
89
|
+
fallbackExecuted: true,
|
|
90
|
+
error: err.message
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
executeSync(primaryFn, fallbackFn = null) {
|
|
98
|
+
if (this.isOpen()) {
|
|
99
|
+
if (fallbackFn) {
|
|
100
|
+
return {
|
|
101
|
+
result: fallbackFn(new Error(`Circuito ${this.name} está ABERTO (OPEN). Redirecionando para fallback.`)),
|
|
102
|
+
circuit: this.name,
|
|
103
|
+
state: this.state,
|
|
104
|
+
fallbackExecuted: true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Circuito ${this.name} está ABERTO (OPEN). Nenhuma chamada permitida.`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const result = primaryFn();
|
|
112
|
+
this.recordSuccess();
|
|
113
|
+
return {
|
|
114
|
+
result,
|
|
115
|
+
circuit: this.name,
|
|
116
|
+
state: this.state,
|
|
117
|
+
fallbackExecuted: false
|
|
118
|
+
};
|
|
119
|
+
} catch (err) {
|
|
120
|
+
this.recordFailure(err);
|
|
121
|
+
if (fallbackFn) {
|
|
122
|
+
return {
|
|
123
|
+
result: fallbackFn(err),
|
|
124
|
+
circuit: this.name,
|
|
125
|
+
state: this.state,
|
|
126
|
+
fallbackExecuted: true,
|
|
127
|
+
error: err.message
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
reset() {
|
|
135
|
+
this.state = STATES.CLOSED;
|
|
136
|
+
this.failureCount = 0;
|
|
137
|
+
this.successCount = 0;
|
|
138
|
+
this.lastFailureTime = null;
|
|
139
|
+
this.lastStateChange = Date.now();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getStatus() {
|
|
143
|
+
return {
|
|
144
|
+
name: this.name,
|
|
145
|
+
state: this.state,
|
|
146
|
+
failureCount: this.failureCount,
|
|
147
|
+
failureThreshold: this.failureThreshold,
|
|
148
|
+
recoveryTimeoutMs: this.recoveryTimeoutMs,
|
|
149
|
+
isOpen: this.isOpen()
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class MultiProviderRouter {
|
|
155
|
+
constructor(providers = ['anthropic', 'openai', 'groq', 'ollama'], options = {}) {
|
|
156
|
+
this.circuits = new Map();
|
|
157
|
+
providers.forEach(p => {
|
|
158
|
+
this.circuits.set(p, new CircuitBreaker(p, options));
|
|
159
|
+
});
|
|
160
|
+
this.providerChain = providers;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async dispatch(taskFnMap) {
|
|
164
|
+
const attempted = [];
|
|
165
|
+
|
|
166
|
+
for (const provider of this.providerChain) {
|
|
167
|
+
const circuit = this.circuits.get(provider);
|
|
168
|
+
attempted.push(provider);
|
|
169
|
+
|
|
170
|
+
if (!circuit.isOpen()) {
|
|
171
|
+
const fn = taskFnMap[provider] || taskFnMap['default'];
|
|
172
|
+
if (!fn) continue;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const res = await circuit.execute(fn);
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
providerUsed: provider,
|
|
179
|
+
attempted,
|
|
180
|
+
result: res.result,
|
|
181
|
+
circuitStates: this.getAllStatuses()
|
|
182
|
+
};
|
|
183
|
+
} catch (err) {
|
|
184
|
+
// Erro registrado pelo circuit breaker; continua para o próximo provedor na cadeia
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: 'Todos os provedores de IA estão indisponíveis ou com circuito ABERTO.',
|
|
193
|
+
attempted,
|
|
194
|
+
circuitStates: this.getAllStatuses()
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
dispatchSync(taskFnMap) {
|
|
199
|
+
const attempted = [];
|
|
200
|
+
|
|
201
|
+
for (const provider of this.providerChain) {
|
|
202
|
+
const circuit = this.circuits.get(provider);
|
|
203
|
+
attempted.push(provider);
|
|
204
|
+
|
|
205
|
+
if (!circuit.isOpen()) {
|
|
206
|
+
const fn = taskFnMap[provider] || taskFnMap['default'];
|
|
207
|
+
if (!fn) continue;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const res = circuit.executeSync(fn);
|
|
211
|
+
return {
|
|
212
|
+
success: true,
|
|
213
|
+
providerUsed: provider,
|
|
214
|
+
attempted,
|
|
215
|
+
result: res.result,
|
|
216
|
+
circuitStates: this.getAllStatuses()
|
|
217
|
+
};
|
|
218
|
+
} catch (err) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: 'Todos os provedores de IA estão indisponíveis ou com circuito ABERTO.',
|
|
227
|
+
attempted,
|
|
228
|
+
circuitStates: this.getAllStatuses()
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getAllStatuses() {
|
|
233
|
+
const statuses = {};
|
|
234
|
+
for (const [name, circuit] of this.circuits.entries()) {
|
|
235
|
+
statuses[name] = circuit.getStatus();
|
|
236
|
+
}
|
|
237
|
+
return statuses;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
resetAll() {
|
|
241
|
+
for (const circuit of this.circuits.values()) {
|
|
242
|
+
circuit.reset();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function runCli() {
|
|
248
|
+
console.log('=== Simulação de Resiliência Multi-Provedor com Circuit Breaker (I-10) ===');
|
|
249
|
+
const router = new MultiProviderRouter(['anthropic', 'openai', 'groq', 'ollama'], { failureThreshold: 2 });
|
|
250
|
+
|
|
251
|
+
// Simula indisponibilidade em anthropic e openai
|
|
252
|
+
const taskMap = {
|
|
253
|
+
anthropic: async () => { throw new Error('HTTP 429: Too Many Requests (Rate limit exceeded)'); },
|
|
254
|
+
openai: async () => { throw new Error('HTTP 503: Service Unavailable'); },
|
|
255
|
+
groq: async () => { return 'Resposta gerada com sucesso via Groq (Llama 3.3 70B)'; },
|
|
256
|
+
ollama: async () => { return 'Resposta local via Ollama'; }
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const dispatch1 = await router.dispatch(taskMap);
|
|
260
|
+
console.log('\nDespacho 1 (Anthropic & OpenAI falham -> Fallback para Groq):');
|
|
261
|
+
console.log(`Provedor Utilizado: ${dispatch1.providerUsed}`);
|
|
262
|
+
console.log(`Tentativas: ${dispatch1.attempted.join(' -> ')}`);
|
|
263
|
+
console.log(`Resultado: ${dispatch1.result}`);
|
|
264
|
+
|
|
265
|
+
console.log('\nStatus dos Circuitos:');
|
|
266
|
+
console.log(JSON.stringify(dispatch1.circuitStates, null, 2));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (require.main === module) {
|
|
270
|
+
runCli().catch(console.error);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class AntiThrashingGuard {
|
|
275
|
+
constructor(options = {}) {
|
|
276
|
+
this.maxIdenticalFailures = options.maxIdenticalFailures || 2;
|
|
277
|
+
this.history = [];
|
|
278
|
+
this.thrashingTripped = false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
normalizeError(error) {
|
|
282
|
+
if (!error) return 'unknown_error';
|
|
283
|
+
const msg = typeof error === 'string' ? error : (error.message || String(error));
|
|
284
|
+
return msg
|
|
285
|
+
.replace(/:\d+:\d+/g, '')
|
|
286
|
+
.replace(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\b/g, '')
|
|
287
|
+
.replace(/0x[0-9a-fA-F]+/g, '')
|
|
288
|
+
.trim();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
recordAttempt(error = null) {
|
|
292
|
+
if (!error) {
|
|
293
|
+
this.history = [];
|
|
294
|
+
this.thrashingTripped = false;
|
|
295
|
+
return { isThrashing: false, count: 0 };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const norm = this.normalizeError(error);
|
|
299
|
+
this.history.push({ signature: norm, timestamp: Date.now() });
|
|
300
|
+
|
|
301
|
+
if (this.history.length >= this.maxIdenticalFailures) {
|
|
302
|
+
const recent = this.history.slice(-this.maxIdenticalFailures);
|
|
303
|
+
const allIdentical = recent.every(h => h.signature === norm);
|
|
304
|
+
if (allIdentical) {
|
|
305
|
+
this.thrashingTripped = true;
|
|
306
|
+
return {
|
|
307
|
+
isThrashing: true,
|
|
308
|
+
count: this.history.length,
|
|
309
|
+
signature: norm,
|
|
310
|
+
message: `[ANTI-THRASHING FINOPS VETO]: Detectadas ${this.maxIdenticalFailures} falhas consecutivas com erro idêntico ("${norm.substring(0, 80)}..."). Execução autônoma interrompida para evitar queima descontrolada de tokens.`
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return { isThrashing: false, count: this.history.length, signature: norm };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
reset() {
|
|
319
|
+
this.history = [];
|
|
320
|
+
this.thrashingTripped = false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
module.exports = {
|
|
326
|
+
STATES,
|
|
327
|
+
CircuitBreaker,
|
|
328
|
+
MultiProviderRouter,
|
|
329
|
+
AntiThrashingGuard
|
|
330
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Quality & Cyclomatic Complexity Auditor
|
|
3
|
+
* Auditoria independente de qualidade de software, complexidade ciclomática,
|
|
4
|
+
* profundidade de aninhamento e detecção de anti-patterns de código.
|
|
5
|
+
* Em conformidade com a Regra 13 (Estrutura de Código) e Matriz de Severidade.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
|
|
11
|
+
class CodeQualityAuditor {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.maxComplexity = options.maxComplexity || 15;
|
|
14
|
+
this.maxNesting = options.maxNesting || 4;
|
|
15
|
+
this.maxFileLines = options.maxFileLines || 300;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
auditCode(code, filename = 'snippet.js') {
|
|
19
|
+
const lines = code.split('\n');
|
|
20
|
+
const totalLines = lines.length;
|
|
21
|
+
let commentLines = 0;
|
|
22
|
+
let blankLines = 0;
|
|
23
|
+
let maxNestingObserved = 0;
|
|
24
|
+
let currentNesting = 0;
|
|
25
|
+
const issues = [];
|
|
26
|
+
|
|
27
|
+
// Contadores de decisão para complexidade ciclomática
|
|
28
|
+
let complexityScore = 1;
|
|
29
|
+
const decisionPatterns = [
|
|
30
|
+
/\bif\s*\(/g,
|
|
31
|
+
/\belse\s+if\s*\(/g,
|
|
32
|
+
/\bfor\s*\(/g,
|
|
33
|
+
/\bwhile\s*\(/g,
|
|
34
|
+
/\bcatch\s*\(/g,
|
|
35
|
+
/\bcase\s+[^:]+:/g,
|
|
36
|
+
/&&/g,
|
|
37
|
+
/\|\|/g,
|
|
38
|
+
/\?[^:]+:/g
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
lines.forEach((line, index) => {
|
|
42
|
+
const lineNum = index + 1;
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
|
|
45
|
+
if (!trimmed) {
|
|
46
|
+
blankLines++;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
51
|
+
commentLines++;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Análise de aninhamento de chaves
|
|
56
|
+
for (const char of trimmed) {
|
|
57
|
+
if (char === '{') currentNesting++;
|
|
58
|
+
if (char === '}') currentNesting = Math.max(0, currentNesting - 1);
|
|
59
|
+
if (currentNesting > maxNestingObserved) {
|
|
60
|
+
maxNestingObserved = currentNesting;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Contagem de complexidade ciclomática
|
|
65
|
+
for (const pattern of decisionPatterns) {
|
|
66
|
+
const matches = trimmed.match(pattern);
|
|
67
|
+
if (matches) {
|
|
68
|
+
complexityScore += matches.length;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Detecção de empty catch block (anti-pattern de engolir exceções)
|
|
73
|
+
if (/catch\s*\([^\)]*\)\s*\{\s*\}/.test(trimmed)) {
|
|
74
|
+
issues.push({
|
|
75
|
+
type: 'EMPTY_CATCH',
|
|
76
|
+
line: lineNum,
|
|
77
|
+
severity: 'HIGH',
|
|
78
|
+
message: 'Bloco catch vazio detectado: erros não devem ser suprimidos silenciosamente (Regra 5).'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Detecção de comentários TODO ou código comentado
|
|
83
|
+
if (/\b(?:TODO|FIXME|HACK)\b/i.test(trimmed)) {
|
|
84
|
+
issues.push({
|
|
85
|
+
type: 'PENDING_TODO',
|
|
86
|
+
line: lineNum,
|
|
87
|
+
severity: 'LOW',
|
|
88
|
+
message: `Marcação pendente detectada: "${trimmed}". Código pronto não deve deixar TODOs (Regra 7).`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Verificação de limites
|
|
94
|
+
if (totalLines > this.maxFileLines) {
|
|
95
|
+
issues.push({
|
|
96
|
+
type: 'FILE_TOO_LONG',
|
|
97
|
+
line: totalLines,
|
|
98
|
+
severity: 'MEDIUM',
|
|
99
|
+
message: `Arquivo possui ${totalLines} linhas (limite canônico recomendado: ${this.maxFileLines} linhas - Regra 13).`
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (maxNestingObserved > this.maxNesting) {
|
|
104
|
+
issues.push({
|
|
105
|
+
type: 'DEEP_NESTING',
|
|
106
|
+
line: 1,
|
|
107
|
+
severity: 'MEDIUM',
|
|
108
|
+
message: `Nível máximo de aninhamento (${maxNestingObserved}) excede o limiar tolerado (${this.maxNesting}).`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (complexityScore > this.maxComplexity) {
|
|
113
|
+
issues.push({
|
|
114
|
+
type: 'HIGH_CYCLOMATIC_COMPLEXITY',
|
|
115
|
+
line: 1,
|
|
116
|
+
severity: 'MEDIUM',
|
|
117
|
+
message: `Complexidade ciclomática (${complexityScore}) excede o limite aceitável de ${this.maxComplexity}.`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const isClean = issues.filter(i => i.severity === 'HIGH' || i.severity === 'CRITICAL').length === 0;
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
filename,
|
|
125
|
+
status: isClean ? (issues.length === 0 ? 'OPTIMAL' : 'ACCEPTABLE_WITH_NOTES') : 'REFACTOR_REQUIRED',
|
|
126
|
+
metrics: {
|
|
127
|
+
totalLines,
|
|
128
|
+
codeLines: totalLines - commentLines - blankLines,
|
|
129
|
+
commentLines,
|
|
130
|
+
blankLines,
|
|
131
|
+
cyclomaticComplexity: complexityScore,
|
|
132
|
+
maxNestingDepth: maxNestingObserved
|
|
133
|
+
},
|
|
134
|
+
qualityScore: Math.max(0, 100 - (issues.length * 10) - (complexityScore > this.maxComplexity ? 15 : 0)),
|
|
135
|
+
issues
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
CodeQualityAuditor
|
|
142
|
+
};
|