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.
Files changed (82) hide show
  1. package/README.md +205 -371
  2. package/agents/AGENT_DEVSECOPS.md +32 -417
  3. package/agents/AGENT_INFRA.md +31 -0
  4. package/agents/AGENT_ORCHESTRATOR.md +39 -264
  5. package/agents/AGENT_QA.md +58 -186
  6. package/agents/AGENT_SCRUM.md +23 -190
  7. package/agents/AGENT_SRE.md +22 -173
  8. package/agents/AGENT_UIUX.md +26 -162
  9. package/docs/ARCHITECTURE.md +152 -148
  10. package/docs/CALCULATIONS.md +17 -17
  11. package/docs/CHANGELOG.md +112 -49
  12. package/docs/ENGINEERING_RULES.md +56 -252
  13. package/docs/IDEAS.md +17 -17
  14. package/docs/LOCAL_BACKLOG.md +20 -0
  15. package/docs/STATE.md +111 -43
  16. package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -58
  17. package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -58
  18. package/package.json +59 -47
  19. package/scripts/auditors/a11y_auditor.js +462 -0
  20. package/scripts/auditors/code_quality_auditor.js +444 -0
  21. package/scripts/auditors/contract_test_validator.js +396 -0
  22. package/scripts/auditors/eval_harness.js +213 -0
  23. package/scripts/auditors/git_hygiene_linter.js +391 -0
  24. package/scripts/auditors/pr_reviewer.js +627 -0
  25. package/scripts/auditors/sast_scanner.js +442 -0
  26. package/scripts/auditors/sca_dependency_auditor.js +673 -0
  27. package/scripts/{bootstrap_cli.js → bin/bootstrap_cli.js} +597 -468
  28. package/scripts/bin/domain_idea_miner.js +658 -0
  29. package/scripts/bin/smart_onboarding.js +905 -0
  30. package/scripts/{web_inspector.js → bin/web_inspector.js} +578 -355
  31. package/scripts/core/architecture_advisor.js +378 -0
  32. package/scripts/core/ast_skeletonizer.js +413 -0
  33. package/scripts/core/autonomous_remediation_engine.js +305 -0
  34. package/scripts/{circuit_breaker.js → core/circuit_breaker.js} +546 -330
  35. package/scripts/core/context_compressor.js +237 -0
  36. package/scripts/core/fast_feature_scaffold.js +840 -0
  37. package/scripts/core/hot_state_cache.js +238 -0
  38. package/scripts/core/knowledge_gateway.js +888 -0
  39. package/scripts/{magic_prompt_generator.js → core/magic_prompt_generator.js} +250 -205
  40. package/scripts/{multi_agent_orchestrator.js → core/multi_agent_orchestrator.js} +421 -258
  41. package/scripts/{otel_tracer.js → core/otel_tracer.js} +335 -228
  42. package/scripts/{perf_benchmark.js → core/perf_benchmark.js} +212 -153
  43. package/scripts/core/sarif_engine.js +299 -0
  44. package/scripts/{sre_remediation_engine.js → core/sre_remediation_engine.js} +236 -174
  45. package/scripts/{universal_packager.js → core/universal_packager.js} +357 -313
  46. package/scripts/integrations/atlassian_client.js +600 -0
  47. package/scripts/integrations/atlassian_sync_action.js +576 -0
  48. package/scripts/integrations/confluence_sync.js +684 -0
  49. package/scripts/integrations/credentials_guard.js +500 -0
  50. package/scripts/integrations/jira_manager.js +722 -0
  51. package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
  52. package/scripts/mcp/handlers/core_handlers.js +419 -0
  53. package/scripts/mcp/handlers/qa_handlers.js +502 -0
  54. package/scripts/mcp/handlers/rules_handlers.js +379 -0
  55. package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
  56. package/scripts/mcp/handlers/sre_handlers.js +443 -0
  57. package/scripts/mcp/resources_registry.js +453 -0
  58. package/scripts/mcp/server.js +408 -0
  59. package/scripts/mcp/tool_handlers.js +444 -0
  60. package/scripts/mcp/tools_registry.js +1123 -0
  61. package/scripts/mcp_server.js +236 -1884
  62. package/.cursorignore +0 -23
  63. package/agents/compact/AGENT_ORCHESTRATOR_COMPACTO.md +0 -53
  64. package/scripts/a11y_auditor.js +0 -187
  65. package/scripts/ast_skeletonizer.js +0 -220
  66. package/scripts/atlassian_client.js +0 -238
  67. package/scripts/atlassian_sync_action.js +0 -269
  68. package/scripts/code_quality_auditor.js +0 -142
  69. package/scripts/confluence_sync.js +0 -337
  70. package/scripts/context_compressor.js +0 -139
  71. package/scripts/contract_test_validator.js +0 -166
  72. package/scripts/credentials_guard.js +0 -216
  73. package/scripts/domain_idea_miner.js +0 -364
  74. package/scripts/eval_harness.js +0 -100
  75. package/scripts/fast_feature_scaffold.js +0 -338
  76. package/scripts/git_hygiene_linter.js +0 -186
  77. package/scripts/hot_state_cache.js +0 -95
  78. package/scripts/jira_manager.js +0 -408
  79. package/scripts/pr_reviewer.js +0 -373
  80. package/scripts/sast_scanner.js +0 -180
  81. package/scripts/sca_dependency_auditor.js +0 -167
  82. package/scripts/smart_onboarding.js +0 -610
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/auditors/a11y_auditor.js
4
+ *
5
+ * Auditor Funcional de Acessibilidade Digital & Design Tokens — WCAG 2.1 AA (AGENT_UIUX / AGENT_QA)
6
+ * Inspeciona componentes HTML, JSX e TSX através de um Parser DOM/AST defensivo e determinístico,
7
+ * executando regras como predicados funcionais puros e gerando relatórios Markdown e OASIS SARIF v2.1.0.
8
+ *
9
+ * Em conformidade com os 10 Princípios Canônicos, NIST SSDF e diretrizes de inclusão digital da W3C.
10
+ */
11
+ 'use strict';
12
+
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+ const { getKnowledgeGateway } = require('../core/knowledge_gateway.js');
16
+ const { createSarifReport } = require('../core/sarif_engine.js');
17
+
18
+ /**
19
+ * Representação imutável de nó na árvore sintática (DOM/AST).
20
+ */
21
+ class DomAstNode {
22
+ constructor(tag = '#root', attrs = {}, line = 1, raw = '') {
23
+ this.tag = String(tag || '').toLowerCase();
24
+ this.rawTag = tag;
25
+ const normalizedAttrs = {};
26
+ for (const [k, v] of Object.entries(attrs || {})) {
27
+ normalizedAttrs[k.toLowerCase()] = v;
28
+ }
29
+ this.attrs = Object.freeze(normalizedAttrs);
30
+ this.children = [];
31
+ this.parent = null;
32
+ this.line = Math.max(1, Number(line) || 1);
33
+ this.raw = raw || '';
34
+ this.textContent = '';
35
+ }
36
+
37
+ hasAttr(name) {
38
+ if (!name) return false;
39
+ const lower = name.toLowerCase();
40
+ return Object.prototype.hasOwnProperty.call(this.attrs, lower) ||
41
+ (lower === 'class' && Object.prototype.hasOwnProperty.call(this.attrs, 'classname')) ||
42
+ (lower === 'for' && Object.prototype.hasOwnProperty.call(this.attrs, 'htmlfor'));
43
+ }
44
+
45
+ getAttr(name) {
46
+ if (!name) return null;
47
+ const lower = name.toLowerCase();
48
+ if (Object.prototype.hasOwnProperty.call(this.attrs, lower)) {
49
+ return this.attrs[lower];
50
+ }
51
+ if (lower === 'class' && this.attrs.classname) return this.attrs.classname;
52
+ if (lower === 'for' && this.attrs.htmlfor) return this.attrs.htmlfor;
53
+ return null;
54
+ }
55
+ }
56
+
57
+ const VOID_TAGS = new Set([
58
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
59
+ 'link', 'meta', 'param', 'source', 'track', 'wbr'
60
+ ]);
61
+
62
+ const EXEMPT_INPUT_TYPES = new Set(['hidden', 'submit', 'button', 'image', 'reset']);
63
+
64
+ const VAGUE_LINK_TEXTS = new Set([
65
+ 'clique aqui', 'click here', 'saiba mais', 'read more', 'veja mais', 'link', 'aqui', 'here'
66
+ ]);
67
+
68
+ /**
69
+ * Remove blocos de comentários e código executável de apoio sem alterar o alinhamento de linhas.
70
+ * @param {string} rawCode
71
+ * @returns {string}
72
+ */
73
+ function sanitizeMarkupPreservingLines(rawCode) {
74
+ if (typeof rawCode !== 'string') return '';
75
+ return rawCode
76
+ // Remove comentários HTML: <!-- ... --> preservando \n
77
+ .replace(/<!--[\s\S]*?-->/g, match => match.replace(/[^\r\n]/g, ' '))
78
+ // Remove comentários de bloco JSX: {/* ... */} preservando \n
79
+ .replace(/\{\/\*[\s\S]*?\*\/\}/g, match => match.replace(/[^\r\n]/g, ' '))
80
+ // Remove blocos de script e style internos preservando \n
81
+ .replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, match => match.replace(/[^\r\n]/g, ' '));
82
+ }
83
+
84
+ /**
85
+ * Tokenizador linear O(N) imune a ReDoS (CWE-1333).
86
+ * Suporta arrow functions, URLs com barras e expressões JSX em atributos.
87
+ * @param {string} code
88
+ * @returns {Array<{ tag: string, start: number, end: number }>}
89
+ */
90
+ function tokenizeHtmlTags(code) {
91
+ const tags = [];
92
+ let i = 0;
93
+ const len = code.length;
94
+
95
+ while (i < len) {
96
+ if (code[i] === '<' && i + 1 < len) {
97
+ const nextChar = code[i + 1];
98
+ if (nextChar === '/' || /[a-zA-Z]/.test(nextChar)) {
99
+ const startIdx = i;
100
+ i++;
101
+ let inDoubleQuote = false;
102
+ let inSingleQuote = false;
103
+ let braceDepth = 0;
104
+
105
+ while (i < len) {
106
+ const char = code[i];
107
+ if (char === '"' && !inSingleQuote) {
108
+ inDoubleQuote = !inDoubleQuote;
109
+ } else if (char === "'" && !inDoubleQuote) {
110
+ inSingleQuote = !inSingleQuote;
111
+ } else if (char === '{' && !inDoubleQuote && !inSingleQuote) {
112
+ braceDepth++;
113
+ } else if (char === '}' && !inDoubleQuote && !inSingleQuote && braceDepth > 0) {
114
+ braceDepth--;
115
+ } else if (char === '>' && !inDoubleQuote && !inSingleQuote && braceDepth === 0) {
116
+ const fullTag = code.slice(startIdx, i + 1);
117
+ tags.push({ tag: fullTag, start: startIdx, end: i + 1 });
118
+ break;
119
+ }
120
+ i++;
121
+ }
122
+ }
123
+ }
124
+ i++;
125
+ }
126
+ return tags;
127
+ }
128
+
129
+ /**
130
+ * Parser de Árvore Sintática (AST / DOM Tree Lexer) para HTML, JSX e TSX.
131
+ * Executa tokenização linear defensiva imune a ReDoS (CWE-1333).
132
+ * @param {string} code
133
+ * @returns {DomAstNode} Raiz da árvore DOM
134
+ */
135
+ function parseHtmlAst(code) {
136
+ const safeCode = sanitizeMarkupPreservingLines(code);
137
+ const root = new DomAstNode('#root', {}, 1, '');
138
+ let currentParent = root;
139
+ const stack = [root];
140
+
141
+ const tagTokens = tokenizeHtmlTags(safeCode);
142
+ let lastIndex = 0;
143
+
144
+ for (const token of tagTokens) {
145
+ const textBefore = safeCode.slice(lastIndex, token.start);
146
+ if (textBefore.trim().length > 0 && currentParent) {
147
+ currentParent.textContent += (currentParent.textContent ? ' ' : '') + textBefore.trim();
148
+ }
149
+ lastIndex = token.end;
150
+
151
+ const fullTag = token.tag;
152
+ const isClosing = fullTag.startsWith('</');
153
+ const lineNum = safeCode.slice(0, token.start).split('\n').length;
154
+
155
+ if (isClosing) {
156
+ const tagNameMatch = fullTag.match(/^<\/\s*([a-zA-Z0-9_\-:]+)/);
157
+ if (tagNameMatch) {
158
+ const targetTag = tagNameMatch[1].toLowerCase();
159
+ for (let i = stack.length - 1; i > 0; i--) {
160
+ if (stack[i].tag === targetTag) {
161
+ stack.length = i;
162
+ currentParent = stack[stack.length - 1];
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ } else {
168
+ let tagInner = fullTag.slice(1, -1).trim();
169
+ let isSelfClosing = false;
170
+ if (tagInner.endsWith('/')) {
171
+ isSelfClosing = true;
172
+ tagInner = tagInner.slice(0, -1).trim();
173
+ }
174
+
175
+ const tagNameMatch = tagInner.match(/^([a-zA-Z0-9_\-:]+)/);
176
+ if (!tagNameMatch) continue;
177
+
178
+ const tagName = tagNameMatch[1];
179
+ const rawAttrs = tagInner.slice(tagName.length).trim();
180
+ if (VOID_TAGS.has(tagName.toLowerCase())) {
181
+ isSelfClosing = true;
182
+ }
183
+
184
+ const attrs = {};
185
+ const attrRegex = /([a-zA-Z0-9_\-:@]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}|([^>\s]+)))?/g;
186
+ let attrMatch;
187
+ while ((attrMatch = attrRegex.exec(rawAttrs)) !== null) {
188
+ const attrKey = attrMatch[1].toLowerCase();
189
+ const attrVal = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? attrMatch[5] ?? 'true';
190
+ attrs[attrKey] = attrVal;
191
+ }
192
+
193
+ const node = new DomAstNode(tagName, attrs, lineNum, fullTag);
194
+ node.parent = currentParent;
195
+ currentParent.children.push(node);
196
+
197
+ if (!isSelfClosing) {
198
+ stack.push(node);
199
+ currentParent = node;
200
+ }
201
+ }
202
+ }
203
+
204
+ if (lastIndex < safeCode.length && currentParent) {
205
+ const trailingText = safeCode.slice(lastIndex).trim();
206
+ if (trailingText) {
207
+ currentParent.textContent += (currentParent.textContent ? ' ' : '') + trailingText;
208
+ }
209
+ }
210
+
211
+ // Agregação funcional de texto recursivo
212
+ function aggregateText(node) {
213
+ for (const child of node.children) {
214
+ aggregateText(child);
215
+ if (child.textContent) {
216
+ node.textContent += (node.textContent ? ' ' : '') + child.textContent;
217
+ }
218
+ }
219
+ node.textContent = node.textContent.trim();
220
+ }
221
+ aggregateText(root);
222
+
223
+ return root;
224
+ }
225
+
226
+ /**
227
+ * Catálogo Canônico de Regras WCAG 2.1 Nível AA com Predicados Puros.
228
+ */
229
+ const WCAG_RULES = Object.freeze([
230
+ {
231
+ id: 'WCAG-1.1.1',
232
+ criterion: 'Non-text Content (Nível A)',
233
+ name: 'Imagem sem texto alternativo (alt)',
234
+ severity: 'CRÍTICA',
235
+ issue: 'Elemento de imagem não possui o atributo "alt" para tecnologias assistivas.',
236
+ remediation: 'Adicione alt="Descrição da imagem" ou alt="" acompanhado de role="presentation" para imagens puramente decorativas.',
237
+ check: (node) => (node.tag === 'img' || node.tag === 'image') && !node.hasAttr('alt')
238
+ },
239
+ {
240
+ id: 'WCAG-4.1.2-BTN',
241
+ criterion: 'Name, Role, Value (Nível A)',
242
+ name: 'Botão interativo sem rótulo acessível',
243
+ severity: 'ALTA',
244
+ issue: 'Botão contendo apenas ícone sem aria-label, aria-labelledby ou texto acessível.',
245
+ remediation: 'Inclua aria-label="Ação do botão" ou texto acessível visível/oculto via classe sr-only.',
246
+ check: (node) => {
247
+ if (node.tag !== 'button') return false;
248
+ const hasAccessibleLabel = node.hasAttr('aria-label') || node.hasAttr('aria-labelledby') || node.hasAttr('title');
249
+ const hasVisibleText = (node.textContent || '').trim().length > 0;
250
+ return !hasAccessibleLabel && !hasVisibleText;
251
+ }
252
+ },
253
+ {
254
+ id: 'WCAG-1.3.1-INPUT',
255
+ criterion: 'Info and Relationships (Nível A)',
256
+ name: 'Campo de entrada (input) sem rótulo associado',
257
+ severity: 'CRÍTICA',
258
+ issue: 'Elemento <input> não possui id para pareamento com <label htmlFor="..."> nem aria-label.',
259
+ remediation: 'Adicione um id único ao input pareado com <label htmlFor="id"> ou declare aria-label.',
260
+ check: (node) => {
261
+ if (node.tag !== 'input') return false;
262
+ const inputType = (node.getAttr('type') || 'text').toLowerCase();
263
+ if (EXEMPT_INPUT_TYPES.has(inputType)) return false;
264
+ const hasId = node.hasAttr('id');
265
+ const hasAria = node.hasAttr('aria-label') || node.hasAttr('aria-labelledby');
266
+ return !hasId && !hasAria;
267
+ }
268
+ },
269
+ {
270
+ id: 'WCAG-2.4.4-LINK',
271
+ criterion: 'Link Purpose (In Context) (Nível A)',
272
+ name: 'Link com texto vago ou não descritivo',
273
+ severity: 'MÉDIA',
274
+ issue: 'Texto de link ambíguo ("clique aqui", "saiba mais") que prejudica a navegação cega por links.',
275
+ remediation: 'Substitua por texto descritivo do destino (ex: "Consulte o catálogo de livros").',
276
+ check: (node) => {
277
+ if (node.tag !== 'a') return false;
278
+ const linkText = (node.textContent || '').trim().toLowerCase();
279
+ return VAGUE_LINK_TEXTS.has(linkText);
280
+ }
281
+ },
282
+ {
283
+ id: 'WCAG-2.4.7-FOCUS',
284
+ criterion: 'Focus Visible (Nível AA)',
285
+ name: 'Supressão de contorno de foco sem anel acessível',
286
+ severity: 'ALTA',
287
+ issue: 'Uso de outline-none sem definição de anel alternativo de foco (focus-visible / focus:ring).',
288
+ remediation: 'Mantenha o foco visível para usuários de teclado adicionando classes como "focus-visible:ring-2 focus-visible:ring-blue-500".',
289
+ check: (node) => {
290
+ const classNames = node.getAttr('class') || '';
291
+ if (!/\boutline-none\b/i.test(classNames)) return false;
292
+ const hasFocusRing = /\b(focus-visible:|focus:ring|focus:border|focus:outline)\b/i.test(classNames);
293
+ return !hasFocusRing;
294
+ }
295
+ },
296
+ {
297
+ id: 'WCAG-2.4.3-TABINDEX',
298
+ criterion: 'Focus Order (Nível A)',
299
+ name: 'Uso de tabindex positivo (anti-pattern)',
300
+ severity: 'MÉDIA',
301
+ issue: 'tabindex maior que zero altera a ordem natural de tabulação do DOM.',
302
+ remediation: 'Utilize tabindex="0" para incluir o elemento no fluxo natural ou reorganize a ordem dos elementos no DOM.',
303
+ check: (node) => {
304
+ if (!node.hasAttr('tabindex')) return false;
305
+ const tabVal = parseInt(node.getAttr('tabindex'), 10);
306
+ return !Number.isNaN(tabVal) && tabVal > 0;
307
+ }
308
+ },
309
+ {
310
+ id: 'WCAG-3.1.1-LANG',
311
+ criterion: 'Language of Page (Nível A)',
312
+ name: 'Documento HTML sem atributo de idioma (lang)',
313
+ severity: 'ALTA',
314
+ issue: 'Tag <html> sem atributo lang, impedindo sintetizadores de voz de carregar a pronúncia correta.',
315
+ remediation: 'Declare o idioma principal do documento: <html lang="pt-BR">.',
316
+ check: (node) => node.tag === 'html' && !node.hasAttr('lang')
317
+ }
318
+ ]);
319
+
320
+ /**
321
+ * Coleta recursiva de todos os nós em array plano (Pipeline Funcional).
322
+ * @param {DomAstNode} rootNode
323
+ * @returns {Array<DomAstNode>}
324
+ */
325
+ function flattenDomTree(rootNode) {
326
+ const nodes = [];
327
+ const collect = (node) => {
328
+ if (node.tag !== '#root') nodes.push(node);
329
+ for (const child of node.children) {
330
+ collect(child);
331
+ }
332
+ };
333
+ collect(rootNode);
334
+ return nodes;
335
+ }
336
+
337
+ class A11yAuditor {
338
+ constructor(rules = WCAG_RULES, options = {}) {
339
+ this.rules = rules;
340
+ this.gateway = options.gateway || getKnowledgeGateway(options);
341
+ }
342
+
343
+ /**
344
+ * Executa a auditoria funcional sobre a árvore sintática (DOM/AST).
345
+ * @param {string} code
346
+ * @param {string} [filename='component.tsx']
347
+ * @returns {object} Relatório canônico de acessibilidade
348
+ */
349
+ auditCode(code, filename = 'component.tsx') {
350
+ const ast = parseHtmlAst(code);
351
+ const nodes = flattenDomTree(ast);
352
+
353
+ // Avaliação Funcional Pura via FlatMap
354
+ const violations = nodes.flatMap(node => {
355
+ return this.rules
356
+ .filter(rule => typeof rule.check === 'function' && rule.check(node))
357
+ .map(rule => {
358
+ const spec = this.gateway ? this.gateway.getWcagSpec(rule.id) : null;
359
+ return {
360
+ ruleId: rule.id,
361
+ wcagCriterion: rule.criterion,
362
+ name: rule.name,
363
+ severity: rule.severity,
364
+ line: node.line,
365
+ snippet: (node.raw || `<${node.tag}>`).slice(0, 100),
366
+ issue: rule.issue,
367
+ remediation: rule.remediation,
368
+ authoritativeRef: spec?.authoritativeRef || null
369
+ };
370
+ });
371
+ });
372
+
373
+ const criticalCount = violations.filter(v => v.severity === 'CRÍTICA').length;
374
+ const highCount = violations.filter(v => v.severity === 'ALTA').length;
375
+ const mediumCount = violations.filter(v => v.severity === 'MÉDIA').length;
376
+
377
+ let verdict = 'APROVADO';
378
+ if (criticalCount > 0 || highCount > 0) {
379
+ verdict = 'REPROVADO';
380
+ } else if (mediumCount > 0) {
381
+ verdict = 'COM RESSALVAS';
382
+ }
383
+
384
+ return {
385
+ filename,
386
+ wcagTarget: 'WCAG 2.1 Nível AA',
387
+ verdict,
388
+ status: verdict === 'APROVADO' ? 'PASSED' : 'FAILED',
389
+ summary: {
390
+ totalViolations: violations.length,
391
+ criticalCount,
392
+ highCount,
393
+ mediumCount
394
+ },
395
+ violations
396
+ };
397
+ }
398
+
399
+ /**
400
+ * Exporta os apontamentos de acessibilidade em SARIF v2.1.0 oficial.
401
+ * @param {string} code
402
+ * @param {string} [filename='component.tsx']
403
+ * @returns {object} SARIF v2.1.0 JSON object
404
+ */
405
+ auditToSarif(code, filename = 'component.tsx') {
406
+ const auditRes = this.auditCode(code, filename);
407
+ const findings = auditRes.violations.map(v => ({
408
+ ruleId: v.ruleId,
409
+ name: v.name,
410
+ severity: v.severity,
411
+ filename: auditRes.filename,
412
+ line: v.line,
413
+ column: 1,
414
+ snippet: v.snippet,
415
+ message: `${v.wcagCriterion}: ${v.issue}`,
416
+ remediation: v.remediation
417
+ }));
418
+
419
+ return createSarifReport({
420
+ toolName: 'agent-master-a11y-auditor',
421
+ toolVersion: '5.1.0',
422
+ findings,
423
+ properties: {
424
+ wcagTarget: auditRes.wcagTarget,
425
+ verdict: auditRes.verdict,
426
+ status: auditRes.status,
427
+ summary: auditRes.summary
428
+ }
429
+ });
430
+ }
431
+
432
+ formatReport(audit) {
433
+ let md = `## ♿ Relatório de Auditoria de Acessibilidade (WCAG 2.1 AA — AGENT UI/UX)\n\n`;
434
+ md += `**Alvo:** \`${audit.filename}\` | **Veredito:** ${audit.verdict === 'APROVADO' ? '✅ APROVADO' : audit.verdict === 'REPROVADO' ? '❌ REPROVADO' : '⚠️ COM RESSALVAS'}\n\n`;
435
+ md += `### 📊 Sumário Executivo de Inclusão (DOM/AST Analysis)\n`;
436
+ md += `- **Violações Críticas (Impede Leitor de Tela):** ${audit.summary.criticalCount}\n`;
437
+ md += `- **Violações Altas (Navegação/Foco):** ${audit.summary.highCount}\n`;
438
+ md += `- **Ressalvas Médias:** ${audit.summary.mediumCount}\n\n`;
439
+
440
+ if (audit.violations.length > 0) {
441
+ md += `### ⚠️ Apontamentos de Acessibilidade Detectados\n\n`;
442
+ md += `| Linha | Critério WCAG | Severidade | Problema | Remediação Recomendada |\n`;
443
+ md += `| :---: | :--- | :---: | :--- | :--- |\n`;
444
+ for (const v of audit.violations) {
445
+ md += `| ${v.line} | **${v.wcagCriterion}** (${v.ruleId}) | **${v.severity}** | ${v.issue} | ${v.remediation} |\n`;
446
+ }
447
+ md += '\n';
448
+ } else {
449
+ md += `> 🌟 **Componente Acessível:** Nenhuma violação detectada. O componente atende às diretrizes WCAG 2.1 AA.\n\n`;
450
+ }
451
+
452
+ return md;
453
+ }
454
+ }
455
+
456
+ module.exports = {
457
+ A11yAuditor,
458
+ DomAstNode,
459
+ parseHtmlAst,
460
+ flattenDomTree,
461
+ WCAG_RULES
462
+ };