agent-master-devsecops 3.9.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -371
- package/agents/AGENT_DEVSECOPS.md +32 -417
- package/agents/AGENT_INFRA.md +31 -0
- package/agents/AGENT_ORCHESTRATOR.md +39 -264
- package/agents/AGENT_QA.md +58 -186
- package/agents/AGENT_SCRUM.md +23 -190
- package/agents/AGENT_SRE.md +22 -173
- package/agents/AGENT_UIUX.md +26 -162
- package/docs/ARCHITECTURE.md +152 -148
- package/docs/CALCULATIONS.md +17 -17
- package/docs/CHANGELOG.md +112 -49
- package/docs/ENGINEERING_RULES.md +56 -252
- package/docs/IDEAS.md +17 -17
- package/docs/LOCAL_BACKLOG.md +20 -0
- package/docs/STATE.md +111 -43
- package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -58
- package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -58
- package/package.json +59 -47
- package/scripts/auditors/a11y_auditor.js +462 -0
- package/scripts/auditors/code_quality_auditor.js +444 -0
- package/scripts/auditors/contract_test_validator.js +396 -0
- package/scripts/auditors/eval_harness.js +213 -0
- package/scripts/auditors/git_hygiene_linter.js +391 -0
- package/scripts/auditors/pr_reviewer.js +627 -0
- package/scripts/auditors/sast_scanner.js +442 -0
- package/scripts/auditors/sca_dependency_auditor.js +673 -0
- package/scripts/{bootstrap_cli.js → bin/bootstrap_cli.js} +597 -468
- package/scripts/bin/domain_idea_miner.js +658 -0
- package/scripts/bin/smart_onboarding.js +905 -0
- package/scripts/{web_inspector.js → bin/web_inspector.js} +578 -355
- package/scripts/core/architecture_advisor.js +378 -0
- package/scripts/core/ast_skeletonizer.js +413 -0
- package/scripts/core/autonomous_remediation_engine.js +305 -0
- package/scripts/{circuit_breaker.js → core/circuit_breaker.js} +546 -330
- package/scripts/core/context_compressor.js +237 -0
- package/scripts/core/fast_feature_scaffold.js +840 -0
- package/scripts/core/hot_state_cache.js +238 -0
- package/scripts/core/knowledge_gateway.js +888 -0
- package/scripts/{magic_prompt_generator.js → core/magic_prompt_generator.js} +250 -205
- package/scripts/{multi_agent_orchestrator.js → core/multi_agent_orchestrator.js} +421 -258
- package/scripts/{otel_tracer.js → core/otel_tracer.js} +335 -228
- package/scripts/{perf_benchmark.js → core/perf_benchmark.js} +212 -153
- package/scripts/core/sarif_engine.js +299 -0
- package/scripts/{sre_remediation_engine.js → core/sre_remediation_engine.js} +236 -174
- package/scripts/{universal_packager.js → core/universal_packager.js} +357 -313
- package/scripts/integrations/atlassian_client.js +600 -0
- package/scripts/integrations/atlassian_sync_action.js +576 -0
- package/scripts/integrations/confluence_sync.js +684 -0
- package/scripts/integrations/credentials_guard.js +500 -0
- package/scripts/integrations/jira_manager.js +722 -0
- package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
- package/scripts/mcp/handlers/core_handlers.js +419 -0
- package/scripts/mcp/handlers/qa_handlers.js +502 -0
- package/scripts/mcp/handlers/rules_handlers.js +379 -0
- package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
- package/scripts/mcp/handlers/sre_handlers.js +443 -0
- package/scripts/mcp/resources_registry.js +453 -0
- package/scripts/mcp/server.js +408 -0
- package/scripts/mcp/tool_handlers.js +444 -0
- package/scripts/mcp/tools_registry.js +1123 -0
- package/scripts/mcp_server.js +236 -1884
- package/.cursorignore +0 -23
- package/agents/compact/AGENT_ORCHESTRATOR_COMPACTO.md +0 -53
- package/scripts/a11y_auditor.js +0 -187
- package/scripts/ast_skeletonizer.js +0 -220
- package/scripts/atlassian_client.js +0 -238
- package/scripts/atlassian_sync_action.js +0 -269
- package/scripts/code_quality_auditor.js +0 -142
- package/scripts/confluence_sync.js +0 -337
- package/scripts/context_compressor.js +0 -139
- package/scripts/contract_test_validator.js +0 -166
- package/scripts/credentials_guard.js +0 -216
- package/scripts/domain_idea_miner.js +0 -364
- package/scripts/eval_harness.js +0 -100
- package/scripts/fast_feature_scaffold.js +0 -338
- package/scripts/git_hygiene_linter.js +0 -186
- package/scripts/hot_state_cache.js +0 -95
- package/scripts/jira_manager.js +0 -408
- package/scripts/pr_reviewer.js +0 -373
- package/scripts/sast_scanner.js +0 -180
- package/scripts/sca_dependency_auditor.js +0 -167
- package/scripts/smart_onboarding.js +0 -610
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/auditors/code_quality_auditor.js
|
|
3
|
+
* Code Quality & Cyclomatic Complexity Auditor — Versão 5.1 Enterprise
|
|
4
|
+
*
|
|
5
|
+
* Auditoria independente de qualidade de software, complexidade ciclomática de McCabe,
|
|
6
|
+
* profundidade de aninhamento e detecção de anti-patterns de código.
|
|
7
|
+
* Em conformidade com a Regra 13 (Estrutura de Código), Princípio 4 (Qualidade & DoD),
|
|
8
|
+
* Princípio 5 (DevSecOps) e padrão OASIS SARIF v2.1.0.
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('node:fs');
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
const { createSarifReport } = require('../core/sarif_engine.js');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Scanner léxico linear O(N) imune a ReDoS (CWE-1333).
|
|
18
|
+
* Sanitiza strings e comentários preservando exatamente todas as quebras de linha (\n)
|
|
19
|
+
* e delimitadores de controle de fluxo para cálculo determinístico de métricas.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} sourceCode
|
|
22
|
+
* @returns {{ sanitized: string, comments: Array<{ line: number, text: string, isBlock: boolean }>, totalLines: number }}
|
|
23
|
+
*/
|
|
24
|
+
function lexicalScan(sourceCode) {
|
|
25
|
+
const code = String(sourceCode || '');
|
|
26
|
+
const len = code.length;
|
|
27
|
+
const sanitizedChars = [];
|
|
28
|
+
const comments = [];
|
|
29
|
+
|
|
30
|
+
let state = 'CODE'; // CODE, LINE_COMMENT, BLOCK_COMMENT, SINGLE_QUOTE, DOUBLE_QUOTE, TEMPLATE
|
|
31
|
+
const templateStack = []; // pilha para balancear interpolações ${...} dentro de templates
|
|
32
|
+
let currentComment = '';
|
|
33
|
+
let commentStartLine = 1;
|
|
34
|
+
let currentLine = 1;
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < len; i++) {
|
|
37
|
+
const ch = code[i];
|
|
38
|
+
const next = i + 1 < len ? code[i + 1] : '';
|
|
39
|
+
|
|
40
|
+
if (ch === '\n') {
|
|
41
|
+
currentLine++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (state === 'CODE') {
|
|
45
|
+
if (ch === '/' && next === '/') {
|
|
46
|
+
state = 'LINE_COMMENT';
|
|
47
|
+
commentStartLine = currentLine;
|
|
48
|
+
currentComment = '//';
|
|
49
|
+
sanitizedChars.push(' ', ' ');
|
|
50
|
+
i++; // consome proximo caractere
|
|
51
|
+
} else if (ch === '/' && next === '*') {
|
|
52
|
+
state = 'BLOCK_COMMENT';
|
|
53
|
+
commentStartLine = currentLine;
|
|
54
|
+
currentComment = '/*';
|
|
55
|
+
sanitizedChars.push(' ', ' ');
|
|
56
|
+
i++; // consome proximo caractere
|
|
57
|
+
} else if (ch === "'") {
|
|
58
|
+
state = 'SINGLE_QUOTE';
|
|
59
|
+
sanitizedChars.push(' ');
|
|
60
|
+
} else if (ch === '"') {
|
|
61
|
+
state = 'DOUBLE_QUOTE';
|
|
62
|
+
sanitizedChars.push(' ');
|
|
63
|
+
} else if (ch === '`') {
|
|
64
|
+
state = 'TEMPLATE';
|
|
65
|
+
sanitizedChars.push(' ');
|
|
66
|
+
} else if (ch === '}' && templateStack.length > 0) {
|
|
67
|
+
templateStack[templateStack.length - 1]--;
|
|
68
|
+
if (templateStack[templateStack.length - 1] === 0) {
|
|
69
|
+
templateStack.pop();
|
|
70
|
+
state = 'TEMPLATE';
|
|
71
|
+
sanitizedChars.push(' ');
|
|
72
|
+
} else {
|
|
73
|
+
sanitizedChars.push(ch);
|
|
74
|
+
}
|
|
75
|
+
} else if (ch === '{' && templateStack.length > 0) {
|
|
76
|
+
templateStack[templateStack.length - 1]++;
|
|
77
|
+
sanitizedChars.push(ch);
|
|
78
|
+
} else {
|
|
79
|
+
sanitizedChars.push(ch);
|
|
80
|
+
}
|
|
81
|
+
} else if (state === 'LINE_COMMENT') {
|
|
82
|
+
if (ch === '\n') {
|
|
83
|
+
comments.push({ line: commentStartLine, text: currentComment.trim(), isBlock: false });
|
|
84
|
+
currentComment = '';
|
|
85
|
+
state = 'CODE';
|
|
86
|
+
sanitizedChars.push('\n');
|
|
87
|
+
} else {
|
|
88
|
+
currentComment += ch;
|
|
89
|
+
sanitizedChars.push(' ');
|
|
90
|
+
}
|
|
91
|
+
} else if (state === 'BLOCK_COMMENT') {
|
|
92
|
+
if (ch === '*' && next === '/') {
|
|
93
|
+
currentComment += '*/';
|
|
94
|
+
comments.push({ line: commentStartLine, text: currentComment.trim(), isBlock: true });
|
|
95
|
+
currentComment = '';
|
|
96
|
+
state = 'CODE';
|
|
97
|
+
sanitizedChars.push(' ', ' ');
|
|
98
|
+
i++; // consome proximo caractere
|
|
99
|
+
} else {
|
|
100
|
+
currentComment += ch;
|
|
101
|
+
sanitizedChars.push(ch === '\n' ? '\n' : ' ');
|
|
102
|
+
}
|
|
103
|
+
} else if (state === 'SINGLE_QUOTE') {
|
|
104
|
+
if (ch === '\\' && i + 1 < len) {
|
|
105
|
+
sanitizedChars.push(' ', ' ');
|
|
106
|
+
i++;
|
|
107
|
+
} else if (ch === "'") {
|
|
108
|
+
state = 'CODE';
|
|
109
|
+
sanitizedChars.push(' ');
|
|
110
|
+
} else {
|
|
111
|
+
sanitizedChars.push(ch === '\n' ? '\n' : ' ');
|
|
112
|
+
}
|
|
113
|
+
} else if (state === 'DOUBLE_QUOTE') {
|
|
114
|
+
if (ch === '\\' && i + 1 < len) {
|
|
115
|
+
sanitizedChars.push(' ', ' ');
|
|
116
|
+
i++;
|
|
117
|
+
} else if (ch === '"') {
|
|
118
|
+
state = 'CODE';
|
|
119
|
+
sanitizedChars.push(' ');
|
|
120
|
+
} else {
|
|
121
|
+
sanitizedChars.push(ch === '\n' ? '\n' : ' ');
|
|
122
|
+
}
|
|
123
|
+
} else if (state === 'TEMPLATE') {
|
|
124
|
+
if (ch === '\\' && i + 1 < len) {
|
|
125
|
+
sanitizedChars.push(' ', ' ');
|
|
126
|
+
i++;
|
|
127
|
+
} else if (ch === '$' && next === '{') {
|
|
128
|
+
templateStack.push(1);
|
|
129
|
+
state = 'CODE';
|
|
130
|
+
sanitizedChars.push(' ', ' ');
|
|
131
|
+
i++; // consome '{'
|
|
132
|
+
} else if (ch === '`') {
|
|
133
|
+
state = 'CODE';
|
|
134
|
+
sanitizedChars.push(' ');
|
|
135
|
+
} else {
|
|
136
|
+
sanitizedChars.push(ch === '\n' ? '\n' : ' ');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (state === 'LINE_COMMENT' && currentComment) {
|
|
142
|
+
comments.push({ line: commentStartLine, text: currentComment.trim(), isBlock: false });
|
|
143
|
+
} else if (state === 'BLOCK_COMMENT' && currentComment) {
|
|
144
|
+
comments.push({ line: commentStartLine, text: currentComment.trim(), isBlock: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
sanitized: sanitizedChars.join(''),
|
|
149
|
+
comments,
|
|
150
|
+
totalLines: currentLine
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Calcula a complexidade ciclomática de McCabe v(G) sobre código sanitizado.
|
|
156
|
+
* v(G) = 1 + número de pontos de decisão binária.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} sanitizedCode
|
|
159
|
+
* @returns {number}
|
|
160
|
+
*/
|
|
161
|
+
function calculateCyclomaticComplexity(sanitizedCode) {
|
|
162
|
+
let complexity = 1;
|
|
163
|
+
|
|
164
|
+
// Pontos de decisão de controle de fluxo (sem dupla contagem de 'else if')
|
|
165
|
+
const decisionRules = [
|
|
166
|
+
/\b(?:if|for|while)\s*\(/g,
|
|
167
|
+
/\bcatch\s*(?:\([^\)]*\))?\s*\{/g,
|
|
168
|
+
/\bcase\s+[^:]+:/g,
|
|
169
|
+
/&&/g,
|
|
170
|
+
/\|\|/g,
|
|
171
|
+
/\?\?/g,
|
|
172
|
+
/\?[^:]+:/g
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const regex of decisionRules) {
|
|
176
|
+
const matches = sanitizedCode.match(regex);
|
|
177
|
+
if (matches) {
|
|
178
|
+
complexity += matches.length;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return complexity;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Calcula a profundidade máxima de aninhamento de blocos ({ ... }) no código sanitizado.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} sanitizedCode
|
|
189
|
+
* @returns {number}
|
|
190
|
+
*/
|
|
191
|
+
function calculateMaxNestingDepth(sanitizedCode) {
|
|
192
|
+
let maxNesting = 0;
|
|
193
|
+
let currentNesting = 0;
|
|
194
|
+
|
|
195
|
+
for (const ch of sanitizedCode) {
|
|
196
|
+
if (ch === '{') {
|
|
197
|
+
currentNesting++;
|
|
198
|
+
if (currentNesting > maxNesting) {
|
|
199
|
+
maxNesting = currentNesting;
|
|
200
|
+
}
|
|
201
|
+
} else if (ch === '}') {
|
|
202
|
+
currentNesting = Math.max(0, currentNesting - 1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return maxNesting;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Classe principal do Auditor de Qualidade de Código.
|
|
211
|
+
*/
|
|
212
|
+
class CodeQualityAuditor {
|
|
213
|
+
constructor(options = {}) {
|
|
214
|
+
this.maxComplexity = options.maxComplexity || 15;
|
|
215
|
+
this.maxNesting = options.maxNesting || 6;
|
|
216
|
+
this.maxFileLines = options.maxFileLines || 300;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
auditFile(filePath) {
|
|
220
|
+
if (!fs.existsSync(filePath)) {
|
|
221
|
+
throw new Error(`Arquivo não encontrado para auditoria de qualidade: ${filePath}`);
|
|
222
|
+
}
|
|
223
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
224
|
+
return this.auditCode(content, path.basename(filePath));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
auditCode(code, filename = 'snippet.js') {
|
|
228
|
+
const scan = lexicalScan(code);
|
|
229
|
+
const rawLines = String(code || '').split('\n');
|
|
230
|
+
const totalLines = rawLines.length;
|
|
231
|
+
|
|
232
|
+
// Rastreamento das linhas de comentário
|
|
233
|
+
const commentLinesSet = new Set();
|
|
234
|
+
for (const c of scan.comments) {
|
|
235
|
+
const lineCount = c.text.split('\n').length;
|
|
236
|
+
for (let l = 0; l < lineCount; l++) {
|
|
237
|
+
commentLinesSet.add(c.line + l);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let blankLines = 0;
|
|
242
|
+
let commentLines = 0;
|
|
243
|
+
|
|
244
|
+
rawLines.forEach((line, idx) => {
|
|
245
|
+
const lineNum = idx + 1;
|
|
246
|
+
const trimmed = line.trim();
|
|
247
|
+
if (!trimmed) {
|
|
248
|
+
blankLines++;
|
|
249
|
+
} else if (commentLinesSet.has(lineNum)) {
|
|
250
|
+
// Se a linha contém apenas comentário
|
|
251
|
+
const sanitizedLine = scan.sanitized.split('\n')[idx] || '';
|
|
252
|
+
if (!sanitizedLine.trim()) {
|
|
253
|
+
commentLines++;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const issues = [];
|
|
259
|
+
|
|
260
|
+
// 1. Verificação de pendências em comentários (TODO, FIXME, HACK)
|
|
261
|
+
for (const c of scan.comments) {
|
|
262
|
+
const todoMatch = c.text.match(/\b(?:TODO|FIXME|HACK)\b[^\r\n]*/);
|
|
263
|
+
if (todoMatch) {
|
|
264
|
+
issues.push({
|
|
265
|
+
ruleId: 'QUALITY-TODO-001',
|
|
266
|
+
type: 'PENDING_TODO',
|
|
267
|
+
line: c.line,
|
|
268
|
+
severity: 'LOW',
|
|
269
|
+
message: `Marcação pendente detectada: "${todoMatch[0].trim()}". Código pronto não deve deixar TODOs (Regra 7).`,
|
|
270
|
+
remediation: 'Resolva a pendência técnica ou crie um ticket no backlog formal.'
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 2. Detecção de blocos catch vazios (anti-pattern de engolir exceções - Regra 5)
|
|
276
|
+
const emptyCatchRegex = /\bcatch\s*(?:\([^\)]*\))?\s*\{\s*\}/g;
|
|
277
|
+
let match;
|
|
278
|
+
while ((match = emptyCatchRegex.exec(scan.sanitized)) !== null) {
|
|
279
|
+
const upToMatch = scan.sanitized.slice(0, match.index);
|
|
280
|
+
const lineNum = upToMatch.split('\n').length;
|
|
281
|
+
issues.push({
|
|
282
|
+
ruleId: 'QUALITY-EMPTY-CATCH',
|
|
283
|
+
type: 'EMPTY_CATCH',
|
|
284
|
+
line: lineNum,
|
|
285
|
+
severity: 'HIGH',
|
|
286
|
+
message: 'Bloco catch vazio detectado: erros não devem ser suprimidos silenciosamente (Regra 5).',
|
|
287
|
+
remediation: 'Adicione tratamento do erro, logging com contexto ou propague a exceção.'
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 3. Detecção de blocos finally vazios
|
|
292
|
+
const emptyFinallyRegex = /\bfinally\s*\{\s*\}/g;
|
|
293
|
+
while ((match = emptyFinallyRegex.exec(scan.sanitized)) !== null) {
|
|
294
|
+
const upToMatch = scan.sanitized.slice(0, match.index);
|
|
295
|
+
const lineNum = upToMatch.split('\n').length;
|
|
296
|
+
issues.push({
|
|
297
|
+
ruleId: 'QUALITY-EMPTY-FINALLY',
|
|
298
|
+
type: 'EMPTY_FINALLY',
|
|
299
|
+
line: lineNum,
|
|
300
|
+
severity: 'MEDIUM',
|
|
301
|
+
message: 'Bloco finally vazio detectado: remova o bloco redundante ou implemente o cleanup apropriado.',
|
|
302
|
+
remediation: 'Remova o bloco finally vazio caso não execute fechamento de recursos.'
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 4. Detecção de declaração 'debugger' deixada para trás
|
|
307
|
+
const debuggerRegex = /\bdebugger\s*(?:;|$)/g;
|
|
308
|
+
while ((match = debuggerRegex.exec(scan.sanitized)) !== null) {
|
|
309
|
+
const upToMatch = scan.sanitized.slice(0, match.index);
|
|
310
|
+
const lineNum = upToMatch.split('\n').length;
|
|
311
|
+
issues.push({
|
|
312
|
+
ruleId: 'QUALITY-DEBUGGER-STMT',
|
|
313
|
+
type: 'DEBUGGER_STATEMENT',
|
|
314
|
+
line: lineNum,
|
|
315
|
+
severity: 'HIGH',
|
|
316
|
+
message: 'Instrução debugger detectada: código de depuração não deve ser commitado para produção.',
|
|
317
|
+
remediation: 'Remova a instrução debugger antes de prosseguir com o commit.'
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Métricas de Complexidade e Aninhamento
|
|
322
|
+
const cyclomaticComplexity = calculateCyclomaticComplexity(scan.sanitized);
|
|
323
|
+
const maxNestingDepth = calculateMaxNestingDepth(scan.sanitized);
|
|
324
|
+
|
|
325
|
+
// 5. Verificação de limites de tamanho e complexidade
|
|
326
|
+
if (totalLines > this.maxFileLines) {
|
|
327
|
+
issues.push({
|
|
328
|
+
ruleId: 'QUALITY-FILE-LINES',
|
|
329
|
+
type: 'FILE_TOO_LONG',
|
|
330
|
+
line: totalLines,
|
|
331
|
+
severity: 'MEDIUM',
|
|
332
|
+
message: `Arquivo possui ${totalLines} linhas (limite canônico recomendado: ${this.maxFileLines} linhas - Regra 13).`,
|
|
333
|
+
remediation: 'Divida o módulo em componentes menores com responsabilidade única (SRP).'
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (maxNestingDepth > this.maxNesting) {
|
|
338
|
+
issues.push({
|
|
339
|
+
ruleId: 'QUALITY-DEEP-NESTING',
|
|
340
|
+
type: 'DEEP_NESTING',
|
|
341
|
+
line: 1,
|
|
342
|
+
severity: 'MEDIUM',
|
|
343
|
+
message: `Nível máximo de aninhamento (${maxNestingDepth}) excede o limiar tolerado (${this.maxNesting}).`,
|
|
344
|
+
remediation: 'Aplique cláusulas de guarda (early return) e extraia métodos auxiliares.'
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (cyclomaticComplexity > this.maxComplexity) {
|
|
349
|
+
issues.push({
|
|
350
|
+
ruleId: 'QUALITY-HIGH-COMPLEXITY',
|
|
351
|
+
type: 'HIGH_CYCLOMATIC_COMPLEXITY',
|
|
352
|
+
line: 1,
|
|
353
|
+
severity: 'MEDIUM',
|
|
354
|
+
message: `Complexidade ciclomática (${cyclomaticComplexity}) excede o limite aceitável de ${this.maxComplexity}.`,
|
|
355
|
+
remediation: 'Decomponha funções complexas e substitua lógicas condicionais densas por polimorfismo ou tabelas de despacho.'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const isClean = issues.filter(i => i.severity === 'HIGH' || i.severity === 'CRITICAL').length === 0;
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
filename,
|
|
363
|
+
status: isClean ? (issues.length === 0 ? 'OPTIMAL' : 'ACCEPTABLE_WITH_NOTES') : 'REFACTOR_REQUIRED',
|
|
364
|
+
metrics: {
|
|
365
|
+
totalLines,
|
|
366
|
+
codeLines: Math.max(0, totalLines - commentLines - blankLines),
|
|
367
|
+
commentLines,
|
|
368
|
+
blankLines,
|
|
369
|
+
cyclomaticComplexity,
|
|
370
|
+
maxNestingDepth
|
|
371
|
+
},
|
|
372
|
+
qualityScore: Math.max(0, 100 - (issues.length * 10) - (cyclomaticComplexity > this.maxComplexity ? 15 : 0)),
|
|
373
|
+
issues
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Exporta a auditoria de qualidade em OASIS SARIF v2.1.0 oficial.
|
|
379
|
+
* @param {string} code
|
|
380
|
+
* @param {string} [filename='snippet.js']
|
|
381
|
+
* @returns {object} SARIF v2.1.0 JSON object
|
|
382
|
+
*/
|
|
383
|
+
auditToSarif(code, filename = 'snippet.js') {
|
|
384
|
+
const auditRes = this.auditCode(code, filename);
|
|
385
|
+
const findings = auditRes.issues.map(iss => ({
|
|
386
|
+
ruleId: iss.ruleId || iss.type,
|
|
387
|
+
name: iss.type,
|
|
388
|
+
severity: iss.severity,
|
|
389
|
+
filename: auditRes.filename,
|
|
390
|
+
line: iss.line || 1,
|
|
391
|
+
column: 1,
|
|
392
|
+
snippet: '',
|
|
393
|
+
message: iss.message,
|
|
394
|
+
remediation: iss.remediation || 'Refatore o código para eliminar a violação de qualidade.'
|
|
395
|
+
}));
|
|
396
|
+
|
|
397
|
+
return createSarifReport({
|
|
398
|
+
toolName: 'agent-master-code-quality-auditor',
|
|
399
|
+
toolVersion: '5.1.0',
|
|
400
|
+
findings,
|
|
401
|
+
properties: {
|
|
402
|
+
status: auditRes.status,
|
|
403
|
+
qualityScore: auditRes.qualityScore,
|
|
404
|
+
metrics: auditRes.metrics
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Formata relatório executivo em Markdown para os Agentes e PR Reviewer.
|
|
411
|
+
* @param {object} audit
|
|
412
|
+
* @returns {string}
|
|
413
|
+
*/
|
|
414
|
+
formatReport(audit) {
|
|
415
|
+
let md = `## 🔬 Relatório de Qualidade de Código & Complexidade (AGENT DEVSECOPS & QA)\n\n`;
|
|
416
|
+
md += `**Alvo:** \`${audit.filename}\` | **Status:** ${audit.status === 'OPTIMAL' ? '✅ EXCELENTE (OPTIMAL)' : audit.status === 'ACCEPTABLE_WITH_NOTES' ? '⚠️ ACEITÁVEL COM NOTAS' : '❌ REFATORAÇÃO NECESSÁRIA'}\n\n`;
|
|
417
|
+
md += `### 📊 Métricas de Engenharia de Software (McCabe & Aninhamento)\n`;
|
|
418
|
+
md += `- **Complexidade Ciclomática v(G):** ${audit.metrics.cyclomaticComplexity} (Limite: ${this.maxComplexity})\n`;
|
|
419
|
+
md += `- **Profundidade Máxima de Aninhamento:** ${audit.metrics.maxNestingDepth} (Limite: ${this.maxNesting})\n`;
|
|
420
|
+
md += `- **Linhas de Código Efetivas:** ${audit.metrics.codeLines} / Total: ${audit.metrics.totalLines}\n`;
|
|
421
|
+
md += `- **Quality Score:** ${audit.qualityScore}/100\n\n`;
|
|
422
|
+
|
|
423
|
+
if (audit.issues.length > 0) {
|
|
424
|
+
md += `### ⚠️ Apontamentos de Qualidade & Débito Técnico\n\n`;
|
|
425
|
+
md += `| Linha | Tipo | Severidade | Mensagem | Remediação Recomendada |\n`;
|
|
426
|
+
md += `| :---: | :--- | :---: | :--- | :--- |\n`;
|
|
427
|
+
for (const iss of audit.issues) {
|
|
428
|
+
md += `| ${iss.line} | **${iss.type}** | **${iss.severity}** | ${iss.message} | ${iss.remediation || 'Refatoração requerida'} |\n`;
|
|
429
|
+
}
|
|
430
|
+
md += '\n';
|
|
431
|
+
} else {
|
|
432
|
+
md += `> 🌟 **Código Limpo:** Nenhuma violação ou anti-pattern detectado. Arquivo atende aos padrões de Clean Code.\n\n`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return md;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = {
|
|
440
|
+
CodeQualityAuditor,
|
|
441
|
+
lexicalScan,
|
|
442
|
+
calculateCyclomaticComplexity,
|
|
443
|
+
calculateMaxNestingDepth
|
|
444
|
+
};
|