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,58 @@
|
|
|
1
|
+
# ⚡ PLAYBOOK DE SRE: CIRCUIT BREAKER INTELIGENTE MULTI-PROVEDOR PARA LLMS
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
playbook: sre-circuit-breaker-llm
|
|
5
|
+
versao: 1.0.0
|
|
6
|
+
proprietario: Agente SRE & Observabilidade (AGENT_SRE.md)
|
|
7
|
+
referencia: AGDEV-9 (I-10) / Padrão de Engenharia de Resiliência Michael Nygard
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🎯 1. OBJETIVO & ESCOPO
|
|
11
|
+
Este playbook estabelece a estratégia de continuidade de negócio e tolerância a falhas para agentes de inteligência artificial diante de instabilidades de provedores de LLM externos (erros HTTP 429 - Rate Limit, 503 - Service Unavailable, 504 - Gateway Timeout ou latência excessiva).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🔄 2. MÁQUINA DE ESTADOS DO CIRCUIT BREAKER (TRANSIÇÃO LINEAR DE FLUXO)
|
|
16
|
+
|
|
17
|
+
Cada provedor de IA possui seu próprio disjuntor com 3 estados canônicos, operando através de uma sequência linear determinística em texto:
|
|
18
|
+
|
|
19
|
+
1. **Estado Inicial: CLOSED (Fechado - Operação Normal)**
|
|
20
|
+
- O tráfego de requisições flui normalmente para o provedor primário.
|
|
21
|
+
- Cada chamada bem-sucedida mantém o circuito fechado e zera o contador de erros.
|
|
22
|
+
- Chamadas com falha consecutiva (erros HTTP 429, 500, 502, 503, 504 ou timeout) incrementam o contador.
|
|
23
|
+
- **Gatilho de Transição:** Ao atingir o limite de 3 falhas consecutivas (`failureThreshold >= 3`), o estado transita linearmente para **OPEN**.
|
|
24
|
+
|
|
25
|
+
2. **Estado de Proteção: OPEN (Aberto - Circuito Rompido)**
|
|
26
|
+
- O tráfego para este provedor é bloqueado de forma imediata (Fail-Fast local), sem efetuar chamadas de rede externas.
|
|
27
|
+
- Toda requisição é desviada instantaneamente para o próximo provedor da cadeia de fallback.
|
|
28
|
+
- Um temporizador de recuperação (`recoveryTimeout`) de 30 segundos é iniciado.
|
|
29
|
+
- **Gatilho de Transição:** Quando o tempo decorrido atinge ou supera 30 segundos (`tempo >= 30s`), o estado avança linearmente para **HALF_OPEN**.
|
|
30
|
+
|
|
31
|
+
3. **Estado de Sondagem: HALF_OPEN (Meio-Aberto - Teste de Recuperação)**
|
|
32
|
+
- O circuito autoriza uma única requisição de teste para avaliar se o provedor remoto se recuperou da instabilidade.
|
|
33
|
+
- **Bifurcação Linear de Retorno:**
|
|
34
|
+
- **Caso de Sucesso:** Se a requisição de teste for concluída com êxito, o disjuntor é rearmado, o contador de falhas é resetado para zero e o estado retorna para **CLOSED**.
|
|
35
|
+
- **Caso de Falha:** Se a requisição de teste falhar, o disjuntor desarma novamente, o temporizador de cooldown de 30 segundos é reiniciado e o estado retorna para **OPEN**.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## ⛓️ 3. CADEIA CANÔNICA DE FALLBACK MULTI-PROVEDOR
|
|
40
|
+
A ordem de comutação automática respeita a relação de capacidade, latência e custo:
|
|
41
|
+
|
|
42
|
+
1. **Primário (Fronteira Analítica)**: Anthropic (`claude-3-5-sonnet`) / OpenAI (`gpt-4o`)
|
|
43
|
+
- Em caso de HTTP 429 ou 5xx após 3 tentativas:
|
|
44
|
+
2. **Secundário (Alta Velocidade / Fallback de Fronteira)**: Groq (`llama-3.3-70b-versatile` / `deepseek-r1-distill-llama-70b`)
|
|
45
|
+
- Em caso de falha de conexão:
|
|
46
|
+
3. **Terciário (Resiliência Local / Offline)**: Ollama (`qwen2.5-coder:7b` / `llama3.1:8b`)
|
|
47
|
+
- Execução local em hardware do desenvolvedor com o prompt compactado (`AGENT_ORCHESTRATOR_COMPACTO.md`).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 📊 4. OBSERVABILIDADE & ALERTAS PROMETHEUS
|
|
52
|
+
O módulo expõe as seguintes métricas canônicas:
|
|
53
|
+
- `llm_circuit_breaker_state{provider="anthropic"}`: 0=CLOSED, 1=OPEN, 2=HALF_OPEN
|
|
54
|
+
- `llm_circuit_breaker_fallbacks_total{from="anthropic",to="groq"}`: contador acumulado de chaveamentos
|
|
55
|
+
- `llm_provider_latency_seconds{provider="groq"}`: histograma de latência
|
|
56
|
+
|
|
57
|
+
### Alerta SRE: `LLMAllCircuitsOpen`
|
|
58
|
+
Se todos os provedores da cadeia estiverem com circuito `OPEN`, o agente emite notificação de severidade CRÍTICA no Jira e ativa o modo local estrito.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# 📡 PLAYBOOK DE SRE: TRACING DISTRIBUÍDO COM OPENTELEMETRY & ANÁLISE DE FLAMEGRAPHS
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
playbook: sre-opentelemetry-tracing
|
|
5
|
+
versao: 1.0.0
|
|
6
|
+
proprietario: Agente SRE & Observabilidade (AGENT_SRE.md)
|
|
7
|
+
referencia: AGDEV-24 (I-8) / RFC W3C TraceContext
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🎯 1. OBJETIVO & ESCOPO
|
|
11
|
+
Este playbook estabelece o procedimento operacional padrão (SOP) para instrumentação, propagação de contexto de rastreamento distribuído e diagnóstico de latência crítica em requisições lentas (P95/P99) utilizando a especificação **OpenTelemetry (OTel)** e **W3C TraceContext**.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🌐 2. PADRÃO W3C TRACECONTEXT (`traceparent`)
|
|
16
|
+
Toda requisição externa, chamada entre microserviços ou handover entre agentes DEVE propagar o cabeçalho canônico:
|
|
17
|
+
|
|
18
|
+
```http
|
|
19
|
+
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
|
|
20
|
+
│ └───────────────┬────────────────┘ └───────┬──────┘ └─┬┘
|
|
21
|
+
Versão Trace ID Span ID Flags (01 = Sampled)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
1. **Trace ID (16 bytes / 32 hex chars)**: Identificador único global da transação.
|
|
25
|
+
2. **Span ID (8 bytes / 16 hex chars)**: Identificador da operação específica em execução.
|
|
26
|
+
3. **Parent Span ID**: Vincula a sub-operação à operação chamadora para montagem do DAG de execução.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🔥 3. ANÁLISE DE FLAMEGRAPHS & GARGALOS (CRITICAL PATH)
|
|
31
|
+
A visualização em Flamegraph permite identificar visualmente onde o tempo da requisição foi consumido:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
=== FLAMEGRAPH DISTRIBUÍDO [Trace: 6be1fab9a7fb6cef75cb1eb13e532b47] (105ms) ===
|
|
35
|
+
[01] █████████ architect_plan (25ms)
|
|
36
|
+
[02] ██████████████████████ devsecops_implementation (60ms) [GARGALO: 57%]
|
|
37
|
+
[03] █████ revisor_audit (15ms)
|
|
38
|
+
[04] ████████████████████████████████████████ user_goal_execution (105ms)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Regras de Classificação de Gargalo:
|
|
42
|
+
- **CRÍTICA**: Span individual consome $\ge 70\%$ do tempo total da transação.
|
|
43
|
+
- **ALTA**: Span individual consome entre $40\%$ e $69\%$ do tempo total.
|
|
44
|
+
- **MÉDIA**: Span individual excede 50ms em chamadas de I/O de banco de dados ou RPCs.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🛠️ 4. FLUXO OPERACIONAL DE AUTO-REMEDIAÇÃO SRE
|
|
49
|
+
Quando o alerta de latência P99 (`LatencyDegradedP99 > 500ms`) for disparado no Prometheus:
|
|
50
|
+
|
|
51
|
+
1. **Localização do Trace**: Consultar os spans com `status.code === 'ERROR'` ou maior duração no Tempo/Jaeger via `traceId`.
|
|
52
|
+
2. **Isolamento de Causa-Raiz (RCA)**:
|
|
53
|
+
- Se o span for de banco de dados (`db.system === 'sqlite' | 'postgres'`): inspecionar queries sem índice ou contenção de lock.
|
|
54
|
+
- Se o span for de LLM (`model.provider === 'openai' | 'groq'`): verificar latência de rede e acionar Circuit Breaker (I-10).
|
|
55
|
+
- Se o span for de compilação ou build: escalonar recursos de CPU/RAM.
|
|
56
|
+
3. **Ação Automatizada**:
|
|
57
|
+
- Purgar caches intermediários ou conexões travadas.
|
|
58
|
+
- Registrar incidente rastreável no Jira com o flamegraph ASCII anexado.
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent-master-devsecops",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-master-devsecops",
|
|
3
|
+
"version": "3.9.0",
|
|
4
|
+
"description": "Hub Universal de Agentes Autônomos de Engenharia & Governança Prescritiva com Servidor MCP Nativo",
|
|
5
|
+
"main": "scripts/mcp_server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-master-devsecops": "scripts/bootstrap_cli.js",
|
|
8
|
+
"agent-master-mcp": "scripts/mcp_server.js",
|
|
9
|
+
"agent-master-init": "scripts/bootstrap_cli.js",
|
|
10
|
+
"agent-bootstrap": "scripts/bootstrap_cli.js",
|
|
11
|
+
"agent-inspector": "scripts/web_inspector.js",
|
|
12
|
+
"agent-onboard": "scripts/smart_onboarding.js",
|
|
13
|
+
"agent-mine-ideas": "scripts/domain_idea_miner.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test test/**/*test*.js",
|
|
17
|
+
"onboard": "node scripts/smart_onboarding.js",
|
|
18
|
+
"mine:ideas": "node scripts/domain_idea_miner.js",
|
|
19
|
+
"mcp": "node scripts/mcp_server.js",
|
|
20
|
+
"ui": "node scripts/web_inspector.js",
|
|
21
|
+
"sync:confluence": "node scripts/confluence_sync.js",
|
|
22
|
+
"track:jira": "node scripts/jira_manager.js --track",
|
|
23
|
+
"standardize:kanban": "node scripts/jira_manager.js"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"devsecops",
|
|
29
|
+
"ai-agents",
|
|
30
|
+
"code-review",
|
|
31
|
+
"governance",
|
|
32
|
+
"prescriptive-autonomy",
|
|
33
|
+
"triad-sync",
|
|
34
|
+
"finops",
|
|
35
|
+
"late-binding",
|
|
36
|
+
"smart-onboarding"
|
|
37
|
+
],
|
|
38
|
+
"author": "ContalApps",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/ContalApps/agent-master-devsecops.git"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/a11y_auditor.js
|
|
4
|
+
*
|
|
5
|
+
* Auditor de Acessibilidade Digital & Design Tokens — WCAG 2.1 AA (AGENT_UIUX / AGENT_QA).
|
|
6
|
+
* Inspeciona componentes HTML, JSX e TSX contra violações de acessibilidade, contraste,
|
|
7
|
+
* semântica WAI-ARIA, foco visível e padrões de usabilidade (Heurísticas de Nielsen).
|
|
8
|
+
*
|
|
9
|
+
* Em estrita conformidade com a persona AGENT_UIUX.md e diretrizes de inclusão digital.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
|
|
17
|
+
const WCAG_RULES = [
|
|
18
|
+
{
|
|
19
|
+
id: 'WCAG-1.1.1',
|
|
20
|
+
criterion: 'Non-text Content (Nível A)',
|
|
21
|
+
name: 'Imagem sem texto alternativo (alt)',
|
|
22
|
+
severity: 'CRÍTICA',
|
|
23
|
+
pattern: /<(?:img|Image)\b(?![^>]*\balt\s*=)[^>]*>/i,
|
|
24
|
+
issue: 'Elemento de imagem não possui o atributo "alt" para tecnologias assistivas.',
|
|
25
|
+
remediation: 'Adicione alt="Descrição da imagem" ou alt="" acompanhado de role="presentation" para imagens puramente decorativas.'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'WCAG-4.1.2-BTN',
|
|
29
|
+
criterion: 'Name, Role, Value (Nível A)',
|
|
30
|
+
name: 'Botão interativo sem rótulo acessível',
|
|
31
|
+
severity: 'ALTA',
|
|
32
|
+
pattern: /<(?:button)\b(?![^>]*\b(?:aria-label|aria-labelledby|title)\s*=)[^>]*>\s*<(?:svg|i|span)\b[^>]*>\s*<\/(?:svg|i|span)>\s*<\/button>/i,
|
|
33
|
+
issue: 'Botão contendo apenas ícone sem aria-label, aria-labelledby ou texto acessível.',
|
|
34
|
+
remediation: 'Inclua aria-label="Ação do botão" ou texto acessível visível/oculto via classe sr-only.'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'WCAG-1.3.1-INPUT',
|
|
38
|
+
criterion: 'Info and Relationships (Nível A)',
|
|
39
|
+
name: 'Campo de entrada (input) sem rótulo associado',
|
|
40
|
+
severity: 'CRÍTICA',
|
|
41
|
+
pattern: /<(?:input)\b(?![^>]*\b(?:type=["'](?:hidden|submit|button|image)["']))(?![^>]*\b(?:aria-label|aria-labelledby|id\s*=))[^>]*>/i,
|
|
42
|
+
issue: 'Elemento <input> não possui id para pareamento com <label htmlFor="..."> nem aria-label.',
|
|
43
|
+
remediation: 'Adicione um id único ao input pareado com <label htmlFor="id"> ou declare aria-label.'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'WCAG-2.4.4-LINK',
|
|
47
|
+
criterion: 'Link Purpose (In Context) (Nível A)',
|
|
48
|
+
name: 'Link com texto vago ou não descritivo',
|
|
49
|
+
severity: 'MÉDIA',
|
|
50
|
+
pattern: /<a\b[^>]*>\s*(?:clique aqui|click here|saiba mais|read more|veja mais|link)\s*<\/a>/i,
|
|
51
|
+
issue: 'Texto de link ambíguo ("clique aqui", "saiba mais") que prejudica a navegação cega por links.',
|
|
52
|
+
remediation: 'Substitua por texto descritivo do destino (ex: "Consulte o catálogo de livros").'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'WCAG-2.4.7-FOCUS',
|
|
56
|
+
criterion: 'Focus Visible (Nível AA)',
|
|
57
|
+
name: 'Supressão de contorno de foco sem anel acessível',
|
|
58
|
+
severity: 'ALTA',
|
|
59
|
+
pattern: /className=["'][^"']*\boutline-none\b(?![^"']*\b(?:focus-visible:|focus:ring|focus:border))[^"']*["']/i,
|
|
60
|
+
issue: 'Uso de outline-none sem definição de anel alternativo de foco (focus-visible / focus:ring).',
|
|
61
|
+
remediation: 'Mantenha o foco visível para usuários de teclado adicionando classes como "focus-visible:ring-2 focus-visible:ring-blue-500".'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'WCAG-2.4.3-TABINDEX',
|
|
65
|
+
criterion: 'Focus Order (Nível A)',
|
|
66
|
+
name: 'Uso de tabindex positivo (anti-pattern)',
|
|
67
|
+
severity: 'MÉDIA',
|
|
68
|
+
pattern: /\btabIndex\s*=\s*(?:\{[1-9][0-9]*\}|["'][1-9][0-9]*["'])/i,
|
|
69
|
+
issue: 'tabindex maior que zero altera a ordem natural de tabulação do DOM.',
|
|
70
|
+
remediation: 'Utilize tabindex="0" para incluir o elemento no fluxo natural ou reorganize a ordem dos elementos no DOM.'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'WCAG-3.1.1-LANG',
|
|
74
|
+
criterion: 'Language of Page (Nível A)',
|
|
75
|
+
name: 'Documento HTML sem atributo de idioma (lang)',
|
|
76
|
+
severity: 'ALTA',
|
|
77
|
+
pattern: /<html\b(?![^>]*\blang\s*=)[^>]*>/i,
|
|
78
|
+
issue: 'Tag <html> sem atributo lang, impedindo sintetizadores de voz de carregar a pronúncia correta.',
|
|
79
|
+
remediation: 'Declare o idioma principal do documento: <html lang="pt-BR">.'
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
class A11yAuditor {
|
|
84
|
+
constructor(rules = WCAG_RULES) {
|
|
85
|
+
this.rules = rules;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
auditCode(code, filename = 'component.tsx') {
|
|
89
|
+
const lines = code.split('\n');
|
|
90
|
+
const violations = [];
|
|
91
|
+
|
|
92
|
+
// 1. Varredura por linha para regras com correspondência em linha única
|
|
93
|
+
lines.forEach((line, index) => {
|
|
94
|
+
const lineNum = index + 1;
|
|
95
|
+
for (const rule of this.rules) {
|
|
96
|
+
if (rule.pattern.test(line)) {
|
|
97
|
+
violations.push({
|
|
98
|
+
ruleId: rule.id,
|
|
99
|
+
wcagCriterion: rule.criterion,
|
|
100
|
+
name: rule.name,
|
|
101
|
+
severity: rule.severity,
|
|
102
|
+
line: lineNum,
|
|
103
|
+
snippet: line.trim().slice(0, 100),
|
|
104
|
+
issue: rule.issue,
|
|
105
|
+
remediation: rule.remediation
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// 2. Varredura no bloco completo (para tags multilinhas)
|
|
112
|
+
for (const rule of this.rules) {
|
|
113
|
+
if (rule.pattern.global) {
|
|
114
|
+
const matches = code.matchAll(rule.pattern);
|
|
115
|
+
for (const match of matches) {
|
|
116
|
+
const charIndex = match.index || 0;
|
|
117
|
+
const lineNum = code.slice(0, charIndex).split('\n').length;
|
|
118
|
+
if (!violations.some(v => v.ruleId === rule.id && v.line === lineNum)) {
|
|
119
|
+
violations.push({
|
|
120
|
+
ruleId: rule.id,
|
|
121
|
+
wcagCriterion: rule.criterion,
|
|
122
|
+
name: rule.name,
|
|
123
|
+
severity: rule.severity,
|
|
124
|
+
line: lineNum,
|
|
125
|
+
snippet: match[0].trim().slice(0, 100),
|
|
126
|
+
issue: rule.issue,
|
|
127
|
+
remediation: rule.remediation
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const criticalCount = violations.filter(v => v.severity === 'CRÍTICA').length;
|
|
135
|
+
const highCount = violations.filter(v => v.severity === 'ALTA').length;
|
|
136
|
+
const mediumCount = violations.filter(v => v.severity === 'MÉDIA').length;
|
|
137
|
+
|
|
138
|
+
let verdict = 'APROVADO';
|
|
139
|
+
if (criticalCount > 0 || highCount > 0) {
|
|
140
|
+
verdict = 'REPROVADO';
|
|
141
|
+
} else if (mediumCount > 0) {
|
|
142
|
+
verdict = 'COM RESSALVAS';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
filename,
|
|
147
|
+
wcagTarget: 'WCAG 2.1 Nível AA',
|
|
148
|
+
verdict,
|
|
149
|
+
status: verdict === 'APROVADO' ? 'PASSED' : 'FAILED',
|
|
150
|
+
summary: {
|
|
151
|
+
totalViolations: violations.length,
|
|
152
|
+
criticalCount,
|
|
153
|
+
highCount,
|
|
154
|
+
mediumCount
|
|
155
|
+
},
|
|
156
|
+
violations
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
formatReport(audit) {
|
|
161
|
+
let md = `## ♿ Relatório de Auditoria de Acessibilidade (WCAG 2.1 AA — AGENT UI/UX)\n\n`;
|
|
162
|
+
md += `**Alvo:** \`${audit.filename}\` | **Veredito:** ${audit.verdict === 'APROVADO' ? '✅ APROVADO' : audit.verdict === 'REPROVADO' ? '❌ REPROVADO' : '⚠️ COM RESSALVAS'}\n\n`;
|
|
163
|
+
md += `### 📊 Sumário Executivo de Inclusão\n`;
|
|
164
|
+
md += `- **Violações Críticas (Impede Leitor de Tela):** ${audit.summary.criticalCount}\n`;
|
|
165
|
+
md += `- **Violações Altas (Navegação/Foco):** ${audit.summary.highCount}\n`;
|
|
166
|
+
md += `- **Ressalvas Médias:** ${audit.summary.mediumCount}\n\n`;
|
|
167
|
+
|
|
168
|
+
if (audit.violations.length > 0) {
|
|
169
|
+
md += `### ⚠️ Apontamentos de Acessibilidade Detectados\n\n`;
|
|
170
|
+
md += `| Linha | Critério WCAG | Severidade | Problema | Remediação Recomendada |\n`;
|
|
171
|
+
md += `| :---: | :--- | :---: | :--- | :--- |\n`;
|
|
172
|
+
for (const v of audit.violations) {
|
|
173
|
+
md += `| ${v.line} | **${v.wcagCriterion}** (${v.ruleId}) | **${v.severity}** | ${v.issue} | ${v.remediation} |\n`;
|
|
174
|
+
}
|
|
175
|
+
md += '\n';
|
|
176
|
+
} else {
|
|
177
|
+
md += `> 🌟 **Componente Acessível:** Nenhuma violação detectada. O componente atende às diretrizes WCAG 2.1 AA.\n\n`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return md;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
A11yAuditor,
|
|
186
|
+
WCAG_RULES
|
|
187
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/ast_skeletonizer.js
|
|
3
|
+
*
|
|
4
|
+
* Extrator Canônico de Esqueleto de Código & Contratos de Interface (Rule 9 — Work Loop).
|
|
5
|
+
* "From other modules, read only what they expose (types, function signatures, schemas), never their inner code."
|
|
6
|
+
*
|
|
7
|
+
* Remove corpos de métodos e implementações internas de classes, gerando um esqueleto com alta
|
|
8
|
+
* densidade semântica para prompts de agentes, economizando entre 80% e 92% de tokens de contexto.
|
|
9
|
+
* Suporta JavaScript/TypeScript, Java e Python.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
class AstSkeletonizer {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.charsPerToken = options.charsPerToken || 3.8;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
estimateTokens(text = '') {
|
|
20
|
+
if (!text) return 0;
|
|
21
|
+
return Math.max(1, Math.ceil(text.length / this.charsPerToken));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Esqueletiza código Java (Remove corpos de métodos mantendo anotações, assinaturas e interfaces)
|
|
26
|
+
*/
|
|
27
|
+
skeletonizeJava(code = '') {
|
|
28
|
+
const lines = code.split(/\r?\n/);
|
|
29
|
+
const resultLines = [];
|
|
30
|
+
let insideMethodBody = false;
|
|
31
|
+
let braceDepth = 0;
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
|
|
37
|
+
if (!insideMethodBody) {
|
|
38
|
+
if (trimmed.startsWith('package ') || trimmed.startsWith('import ') || trimmed.startsWith('@') ||
|
|
39
|
+
trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') ||
|
|
40
|
+
trimmed.includes('class ') || trimmed.includes('interface ') || trimmed.includes('enum ') ||
|
|
41
|
+
trimmed === '' || trimmed === '}') {
|
|
42
|
+
resultLines.push(line);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isMethodSignature = /\b(public|protected|private|static|final|default)\b.*\(.*\).*/.test(trimmed);
|
|
47
|
+
if (isMethodSignature) {
|
|
48
|
+
if (trimmed.endsWith('{')) {
|
|
49
|
+
const signature = line.substring(0, line.lastIndexOf('{')).trimEnd();
|
|
50
|
+
resultLines.push(`${signature} { /* implementation omitted */ }`);
|
|
51
|
+
insideMethodBody = true;
|
|
52
|
+
braceDepth = 1;
|
|
53
|
+
continue;
|
|
54
|
+
} else if (trimmed.endsWith(';')) {
|
|
55
|
+
resultLines.push(line);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resultLines.push(line);
|
|
61
|
+
} else {
|
|
62
|
+
for (const char of line) {
|
|
63
|
+
if (char === '{') braceDepth++;
|
|
64
|
+
if (char === '}') braceDepth--;
|
|
65
|
+
}
|
|
66
|
+
if (braceDepth <= 0) {
|
|
67
|
+
insideMethodBody = false;
|
|
68
|
+
braceDepth = 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return resultLines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Esqueletiza código JS/TS (Mantém exports, interfaces, types e assinaturas, remove corpos de função)
|
|
78
|
+
*/
|
|
79
|
+
skeletonizeJsTs(code = '') {
|
|
80
|
+
const lines = code.split(/\r?\n/);
|
|
81
|
+
const resultLines = [];
|
|
82
|
+
let insideFunction = false;
|
|
83
|
+
let braceDepth = 0;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < lines.length; i++) {
|
|
86
|
+
const line = lines[i];
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
|
|
89
|
+
if (!insideFunction) {
|
|
90
|
+
// Preserva imports, declarações de classe, interfaces, types e comentários
|
|
91
|
+
if (trimmed.startsWith('import ') || trimmed.startsWith('export type ') || trimmed.startsWith('export interface ') ||
|
|
92
|
+
trimmed.startsWith('type ') || trimmed.startsWith('interface ') || trimmed.startsWith('//') ||
|
|
93
|
+
trimmed.startsWith('/*') || trimmed.startsWith('*') || (trimmed.startsWith('const ') && trimmed.includes('require(')) ||
|
|
94
|
+
trimmed.includes('class ') || trimmed === '' || trimmed === '}') {
|
|
95
|
+
resultLines.push(line);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Detecta funções soltas ou métodos de classe com abertura de chaves
|
|
100
|
+
const isFuncOrMethod = /\b(function|async|get|set|static)\b|\b[\w$]+\s*\([^)]*\)\s*[:\w\s<>]*\{/.test(trimmed);
|
|
101
|
+
if (isFuncOrMethod && trimmed.includes('{')) {
|
|
102
|
+
const lastBrace = line.lastIndexOf('{');
|
|
103
|
+
const signature = line.substring(0, lastBrace).trimEnd();
|
|
104
|
+
resultLines.push(`${signature} { /* implementation */ }`);
|
|
105
|
+
|
|
106
|
+
// Checa se a função abriu e fechou na mesma linha
|
|
107
|
+
let lineDepth = 0;
|
|
108
|
+
for (let c = lastBrace; c < line.length; c++) {
|
|
109
|
+
if (line[c] === '{') lineDepth++;
|
|
110
|
+
if (line[c] === '}') lineDepth--;
|
|
111
|
+
}
|
|
112
|
+
if (lineDepth > 0) {
|
|
113
|
+
insideFunction = true;
|
|
114
|
+
braceDepth = lineDepth;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
resultLines.push(line);
|
|
120
|
+
} else {
|
|
121
|
+
for (const char of line) {
|
|
122
|
+
if (char === '{') braceDepth++;
|
|
123
|
+
if (char === '}') braceDepth--;
|
|
124
|
+
}
|
|
125
|
+
if (braceDepth <= 0) {
|
|
126
|
+
insideFunction = false;
|
|
127
|
+
braceDepth = 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return resultLines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Esqueletiza código Python (Mantém defs, classes e docstrings; substitui corpos por ...)
|
|
137
|
+
*/
|
|
138
|
+
skeletonizePython(code = '') {
|
|
139
|
+
const lines = code.split(/\r?\n/);
|
|
140
|
+
const resultLines = [];
|
|
141
|
+
let insideFunction = false;
|
|
142
|
+
let functionIndent = 0;
|
|
143
|
+
|
|
144
|
+
for (let i = 0; i < lines.length; i++) {
|
|
145
|
+
const line = lines[i];
|
|
146
|
+
const trimmed = line.trim();
|
|
147
|
+
const currentIndent = (line.match(/^\s*/) || [''])[0].length;
|
|
148
|
+
|
|
149
|
+
if (insideFunction) {
|
|
150
|
+
if (trimmed === '') continue;
|
|
151
|
+
if (currentIndent <= functionIndent && !trimmed.startsWith('#')) {
|
|
152
|
+
insideFunction = false;
|
|
153
|
+
} else {
|
|
154
|
+
// Preserva docstring de 1 linha logo após o def se houver
|
|
155
|
+
if ((trimmed.startsWith('"""') || trimmed.startsWith("'''")) && !resultLines[resultLines.length - 1].includes('...')) {
|
|
156
|
+
resultLines.push(line);
|
|
157
|
+
resultLines.push(' '.repeat(functionIndent + 4) + '...');
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (trimmed.startsWith('def ') && trimmed.endsWith(':')) {
|
|
164
|
+
resultLines.push(line);
|
|
165
|
+
insideFunction = true;
|
|
166
|
+
functionIndent = currentIndent;
|
|
167
|
+
|
|
168
|
+
// Se a próxima linha for docstring, será tratada na iteração seguinte; caso contrário insere ...
|
|
169
|
+
const nextLine = lines[i + 1] ? lines[i + 1].trim() : '';
|
|
170
|
+
if (!nextLine.startsWith('"""') && !nextLine.startsWith("'''")) {
|
|
171
|
+
resultLines.push(' '.repeat(functionIndent + 4) + '...');
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
resultLines.push(line);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return resultLines.join('\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
skeletonize(code = '', language = 'javascript') {
|
|
183
|
+
const lang = String(language).toLowerCase().trim();
|
|
184
|
+
let skeleton = '';
|
|
185
|
+
|
|
186
|
+
if (lang === 'java' || lang === 'jvm') {
|
|
187
|
+
skeleton = this.skeletonizeJava(code);
|
|
188
|
+
} else if (lang.includes('python') || lang.includes('py')) {
|
|
189
|
+
skeleton = this.skeletonizePython(code);
|
|
190
|
+
} else {
|
|
191
|
+
skeleton = this.skeletonizeJsTs(code);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const originalTokens = this.estimateTokens(code);
|
|
195
|
+
const skeletonTokens = this.estimateTokens(skeleton);
|
|
196
|
+
const tokensSaved = Math.max(0, originalTokens - skeletonTokens);
|
|
197
|
+
const reductionPercent = originalTokens > 0 ? Math.round((tokensSaved / originalTokens) * 100) : 0;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
language: lang,
|
|
201
|
+
originalLines: code.split(/\r?\n/).length,
|
|
202
|
+
skeletonLines: skeleton.split(/\r?\n/).length,
|
|
203
|
+
originalTokens,
|
|
204
|
+
skeletonTokens,
|
|
205
|
+
tokensSaved,
|
|
206
|
+
reductionPercent,
|
|
207
|
+
skeletonCode: skeleton
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function extractCodeSkeleton(code, language = 'javascript') {
|
|
213
|
+
const skeletonizer = new AstSkeletonizer();
|
|
214
|
+
return skeletonizer.skeletonize(code, language);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
AstSkeletonizer,
|
|
219
|
+
extractCodeSkeleton
|
|
220
|
+
};
|