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
@@ -1,337 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scripts/confluence_sync.js
4
- *
5
- * Sincronizador Automático Universal de ADRs, SPECs e Governança no Confluence em Tempo Real.
6
- * Lê arquivos Markdown em docs/adr/*.md, docs/specs/*.md e docs/governance/*.md,
7
- * converte para Confluence Storage Format (XHTML) e cria/atualiza páginas hierarquicamente.
8
- *
9
- * 100% Desacoplado: detecta dinamicamente credenciais, Space Key e pastas pai.
10
- * Governança: Tríade Git + Jira + Confluence (Regra 11 e Regra 15).
11
- */
12
-
13
- const fs = require('node:fs');
14
- const path = require('node:path');
15
-
16
- const { getAtlassianCredentials, requestAtlassian } = require('./atlassian_client.js');
17
-
18
- const CONFLUENCE_CONFIG = {
19
- spaceKey: process.env.CONFLUENCE_SPACE_KEY || process.env.ATLASSIAN_SPACE_KEY || 'DOCS',
20
- adrParentId: process.env.CONFLUENCE_ADR_PARENT_ID || null,
21
- specParentId: process.env.CONFLUENCE_SPEC_PARENT_ID || null,
22
- govParentId: process.env.CONFLUENCE_GOV_PARENT_ID || null
23
- };
24
-
25
- function escapeHtml(text) {
26
- return text
27
- .replaceAll('&', '&')
28
- .replaceAll('<', '&lt;')
29
- .replaceAll('>', '&gt;')
30
- .replaceAll('"', '&quot;')
31
- .replaceAll("'", '&#39;');
32
- }
33
-
34
- function parseLinks(text) {
35
- let result = '';
36
- let i = 0;
37
- while (i < text.length) {
38
- const openBracket = text.indexOf('[', i);
39
- if (openBracket === -1) {
40
- result += text.slice(i);
41
- break;
42
- }
43
- result += text.slice(i, openBracket);
44
- const closeBracket = text.indexOf(']', openBracket + 1);
45
- if (closeBracket !== -1 && text[closeBracket + 1] === '(') {
46
- const closeParen = text.indexOf(')', closeBracket + 2);
47
- if (closeParen !== -1) {
48
- const label = text.slice(openBracket + 1, closeBracket);
49
- const url = text.slice(closeBracket + 2, closeParen);
50
- result += `<a href="${url}">${label}</a>`;
51
- i = closeParen + 1;
52
- continue;
53
- }
54
- }
55
- result += '[';
56
- i = openBracket + 1;
57
- }
58
- return result;
59
- }
60
-
61
- function markdownToConfluenceStorage(markdown) {
62
- const lines = markdown.split(/\r?\n/);
63
- const elements = [];
64
- let inCodeBlock = false;
65
- let codeBuffer = [];
66
- let codeLanguage = 'text';
67
- let inList = false;
68
-
69
- for (let idx = 0; idx < lines.length; idx++) {
70
- const rawLine = lines[idx];
71
-
72
- // Fenced Code Blocks
73
- if (rawLine.trim().startsWith('```')) {
74
- if (inCodeBlock) {
75
- inCodeBlock = false;
76
- const codeContent = escapeHtml(codeBuffer.join('\n'));
77
- elements.push(
78
- `<ac:structured-macro ac:name="code" ac:schema-version="1">` +
79
- `<ac:parameter ac:name="language">${codeLanguage}</ac:parameter>` +
80
- `<ac:parameter ac:name="linenumbers">true</ac:parameter>` +
81
- `<ac:plain-text-body><![CDATA[${codeContent}]]></ac:plain-text-body>` +
82
- `</ac:structured-macro>`
83
- );
84
- codeBuffer = [];
85
- codeLanguage = 'text';
86
- } else {
87
- if (inList) {
88
- elements.push('</ul>');
89
- inList = false;
90
- }
91
- inCodeBlock = true;
92
- const match = rawLine.trim().match(/^```([a-zA-Z0-9_-]*)/);
93
- codeLanguage = match && match[1] ? match[1] : 'text';
94
- }
95
- continue;
96
- }
97
-
98
- if (inCodeBlock) {
99
- codeBuffer.push(rawLine);
100
- continue;
101
- }
102
-
103
- const trimmed = rawLine.trim();
104
-
105
- // Linha vazia
106
- if (!trimmed) {
107
- if (inList) {
108
- elements.push('</ul>');
109
- inList = false;
110
- }
111
- continue;
112
- }
113
-
114
- // Separador horizontal
115
- if (/^(\-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
116
- if (inList) { elements.push('</ul>'); inList = false; }
117
- elements.push('<hr />');
118
- continue;
119
- }
120
-
121
- // Blockquote
122
- if (trimmed.startsWith('>')) {
123
- if (inList) { elements.push('</ul>'); inList = false; }
124
- const quoteText = escapeHtml(trimmed.replace(/^>\s*/, ''));
125
- elements.push(`<blockquote><p>${parseLinks(quoteText)}</p></blockquote>`);
126
- continue;
127
- }
128
-
129
- // Cabeçalhos (h1 - h6)
130
- const headerMatch = trimmed.match(/^(#{1,6})\s+(.*)$/);
131
- if (headerMatch) {
132
- if (inList) { elements.push('</ul>'); inList = false; }
133
- const level = headerMatch[1].length;
134
- const text = escapeHtml(headerMatch[2]);
135
- elements.push(`<h${level}>${parseLinks(text)}</h${level}>`);
136
- continue;
137
- }
138
-
139
- // Itens de lista com marcadores (* ou -)
140
- const listMatch = trimmed.match(/^[-*]\s+(.*)$/);
141
- if (listMatch) {
142
- if (!inList) {
143
- elements.push('<ul>');
144
- inList = true;
145
- }
146
- const itemText = escapeHtml(listMatch[1]);
147
- const formatted = itemText
148
- .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
149
- .replace(/`([^`]+)`/g, '<code>$1</code>');
150
- elements.push(`<li>${parseLinks(formatted)}</li>`);
151
- continue;
152
- }
153
-
154
- // Parágrafo comum
155
- if (inList) {
156
- elements.push('</ul>');
157
- inList = false;
158
- }
159
-
160
- const formattedPara = escapeHtml(trimmed)
161
- .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
162
- .replace(/\*(.*?)\*/g, '<em>$1</em>')
163
- .replace(/`([^`]+)`/g, '<code>$1</code>');
164
-
165
- elements.push(`<p>${parseLinks(formattedPara)}</p>`);
166
- }
167
-
168
- if (inList) {
169
- elements.push('</ul>');
170
- }
171
-
172
- return elements.join('');
173
- }
174
-
175
- function extractTitle(markdown, fallback) {
176
- const lines = markdown.split(/\r?\n/);
177
- for (const line of lines) {
178
- const match = line.match(/^#\s+(.*)$/);
179
- if (match) {
180
- return match[1].trim();
181
- }
182
- }
183
- return fallback;
184
- }
185
-
186
- async function findOrCreatePage(creds, spaceKey, title, parentId = null) {
187
- const encodedTitle = encodeURIComponent(title);
188
- const pathUrl = `/wiki/rest/api/content?spaceKey=${spaceKey}&title=${encodedTitle}&expand=version,ancestors`;
189
- const searchRes = await requestAtlassian(creds, 'GET', pathUrl);
190
-
191
- if (searchRes.status === 200 && searchRes.body.results && searchRes.body.results.length > 0) {
192
- return searchRes.body.results[0];
193
- }
194
-
195
- // Se não existir e for pasta estrutural, cria
196
- if (parentId !== undefined) {
197
- const createPayload = {
198
- type: 'page',
199
- title,
200
- space: { key: spaceKey },
201
- body: {
202
- storage: {
203
- value: `<p>Seção de documentação gerada automaticamente pela Tríade Multiagente.</p>`,
204
- representation: 'storage'
205
- }
206
- },
207
- ...(parentId ? { ancestors: [{ id: parentId }] } : {})
208
- };
209
-
210
- const createRes = await requestAtlassian(creds, 'POST', '/wiki/rest/api/content', createPayload);
211
- if (createRes.status === 200 || createRes.status === 201) {
212
- return createRes.body;
213
- }
214
- }
215
-
216
- return null;
217
- }
218
-
219
- async function syncDocument(creds, filePath, parentId) {
220
- const rawMarkdown = fs.readFileSync(filePath, 'utf8');
221
- const filename = path.basename(filePath, '.md');
222
- const title = extractTitle(rawMarkdown, filename);
223
- const storageHtml = markdownToConfluenceStorage(rawMarkdown);
224
-
225
- const existingPage = await findOrCreatePage(creds, CONFLUENCE_CONFIG.spaceKey, title);
226
-
227
- if (existingPage) {
228
- const nextVersion = existingPage.version.number + 1;
229
- const updatePayload = {
230
- version: { number: nextVersion },
231
- title: title,
232
- type: 'page',
233
- space: { key: CONFLUENCE_CONFIG.spaceKey },
234
- body: {
235
- storage: {
236
- value: storageHtml,
237
- representation: 'storage'
238
- }
239
- },
240
- ...(parentId ? { ancestors: [{ id: parentId }] } : {})
241
- };
242
-
243
- const updateRes = await requestAtlassian(creds, 'PUT', `/wiki/rest/api/content/${existingPage.id}`, updatePayload);
244
- if (updateRes.status === 200) {
245
- console.log(`[Confluence Sync] ✔ Atualizada: "${title}" (ID: ${existingPage.id}, v${nextVersion})`);
246
- return { success: true, id: existingPage.id, action: 'updated' };
247
- } else {
248
- console.error(`[Confluence Sync] ✖ Falha ao atualizar "${title}": HTTP ${updateRes.status}`);
249
- return { success: false, error: updateRes.body };
250
- }
251
- } else {
252
- const createPayload = {
253
- type: 'page',
254
- title: title,
255
- space: { key: CONFLUENCE_CONFIG.spaceKey },
256
- body: {
257
- storage: {
258
- value: storageHtml,
259
- representation: 'storage'
260
- }
261
- },
262
- ...(parentId ? { ancestors: [{ id: parentId }] } : {})
263
- };
264
-
265
- const createRes = await requestAtlassian(creds, 'POST', '/wiki/rest/api/content', createPayload);
266
- if (createRes.status === 200 || createRes.status === 201) {
267
- console.log(`[Confluence Sync] 🚀 Criada: "${title}" (ID: ${createRes.body.id})`);
268
- return { success: true, id: createRes.body.id, action: 'created' };
269
- } else {
270
- console.error(`[Confluence Sync] ✖ Falha ao criar "${title}": HTTP ${createRes.status}`);
271
- return { success: false, error: createRes.body };
272
- }
273
- }
274
- }
275
-
276
- async function runSync() {
277
- const creds = getAtlassianCredentials();
278
- if (!creds) {
279
- console.error('[Confluence Sync] ✖ Credenciais Atlassian não configuradas. Sincronização ignorada.');
280
- return;
281
- }
282
-
283
- console.log(`[Confluence Sync] Espaço alvo: "${CONFLUENCE_CONFIG.spaceKey}".`);
284
-
285
- // Descoberta dinâmica de pastas estruturais
286
- let adrParent = CONFLUENCE_CONFIG.adrParentId;
287
- let specParent = CONFLUENCE_CONFIG.specParentId;
288
- let govParent = CONFLUENCE_CONFIG.govParentId;
289
-
290
- if (!adrParent) {
291
- const p = await findOrCreatePage(creds, CONFLUENCE_CONFIG.spaceKey, 'Decisões de Arquitetura (ADRs)');
292
- if (p) adrParent = p.id;
293
- }
294
- if (!specParent) {
295
- const p = await findOrCreatePage(creds, CONFLUENCE_CONFIG.spaceKey, 'Especificações Técnicas (SPECs)');
296
- if (p) specParent = p.id;
297
- }
298
- if (!govParent) {
299
- const p = await findOrCreatePage(creds, CONFLUENCE_CONFIG.spaceKey, 'Governança & Padrões Multiagente');
300
- if (p) govParent = p.id;
301
- }
302
-
303
- const targets = [
304
- { dir: path.resolve('docs/adr'), parentId: adrParent },
305
- { dir: path.resolve('docs/specs'), parentId: specParent },
306
- { dir: path.resolve('docs/governance'), parentId: govParent }
307
- ];
308
-
309
- let total = 0;
310
- for (const target of targets) {
311
- if (fs.existsSync(target.dir)) {
312
- const files = fs.readdirSync(target.dir).filter(f => f.endsWith('.md'));
313
- for (const f of files) {
314
- await syncDocument(creds, path.join(target.dir, f), target.parentId);
315
- total++;
316
- }
317
- }
318
- }
319
-
320
- console.log(`[Confluence Sync] Finalizado: ${total} documentos processados com sucesso.`);
321
- }
322
-
323
- if (require.main === module) {
324
- runSync().catch(err => {
325
- console.error('[Confluence Sync] Erro fatal:', err);
326
- process.exit(1);
327
- });
328
- }
329
-
330
- module.exports = {
331
- escapeHtml,
332
- parseLinks,
333
- markdownToConfluenceStorage,
334
- extractTitle,
335
- syncDocument,
336
- runSync
337
- };
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scripts/context_compressor.js
4
- *
5
- * Compactador Semântico de Contexto para Modelos Locais (AGDEV-22 / I-6).
6
- * Destila o prompt canônico e regras do Agent Master em menos de 1.500 tokens (~6.000 chars),
7
- * preservando 100% da autoridade normativa, as 15 Regras de Ouro, os 5 Modos e o Tríplice Registro.
8
- *
9
- * Otimizado para execução com baixa latência em Ollama (Llama 3.1 8B, Qwen 2.5 Coder, Mistral 7B).
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const ROOT_DIR = path.resolve(__dirname, '..');
16
-
17
- function estimateTokens(text) {
18
- if (!text) return 0;
19
- // Estimativa padrão da indústria para modelos baseados em BPE/WordPiece (~4 caracteres por token em média)
20
- return Math.ceil(text.trim().length / 4);
21
- }
22
-
23
- function generateCompactPrompt() {
24
- return `=== AGENT ORCHESTRATOR & SUÍTE MULTI-AGENTE (LOCAL HIGH-DENSITY EDITION v3.2) ===
25
- [DIRETIVA SUPREMA]: Você é o AGENT ORCHESTRATOR da Suíte Multi-Agente (Orchestrator, DevSecOps, QA, Scrum e SRE). Atue como Arquiteto, Engenheiro Sênior, Especialista em Segurança e Auditor Cético.
26
- [COMPATIBILIDADE]: Otimizado para modelos locais (Ollama, vLLM, LM Studio). Siga rigorosamente sem desvios.
27
-
28
- [15 REGRAS DE OURO NORMATIVAS]:
29
- R1. TDD Obrigatório: Escreva ou valide testes unitários ANTES de modificar código de produção.
30
- R2. Branching por Ticket: Trabalhe sempre em branch dedicada exclusivamente com o identificador <TICKET> (ex: PROJ-065878). Nunca comite direto em main.
31
- R3. Pull Request & CI: Todo merge exige PR formal no GitHub com 100% dos checks de CI (TruffleHog, testes e validação) verdes.
32
- R4. Zero Segredos (TruffleHog): NUNCA comite credenciais, arquivos .env, tokens de API ou chaves privadas.
33
- R5. Defesa DevSecOps & OWASP: Imunidade a CWE-78 (Cmd Injection), CWE-79 (XSS), CWE-89 (SQLi), CWE-116 (Sanitização) e CWE-1333 (ReDoS).
34
- R6. Observabilidade Nativa: Exponha /metrics no padrão Prometheus 0.0.4 (CPU, Heap/RSS, latências).
35
- R7. Protocolo Dual-Layer: Chat curto e objetivo com tópicos separados por LINHA EM BRANCO DUPLA (\\n\\n) e links markdown clicáveis.
36
- R8. Bíblia Confluence: Toda decisão arquitetural exige novo ADR canônico no Confluence.
37
- R9. Governança Jira: Mova cards para 'Em andamento' antes de iniciar; finalize com relatório técnico denso de 15 seções.
38
- R10. Zero Suposições: Se os requisitos forem ambíguos, esclareça com o usuário ou inspecione o código antes de agir.
39
- R11. Integridade de Documentação: Preserve comentários, docstrings e contratos de API pré-existentes.
40
- R12. Segurança Operacional: NUNCA execute comandos destrutivos (rm -rf, git reset --hard não planejado, drop database).
41
- R13. Ceticismo Adversário (AGENT_QA.md): Audite diffs com frieza cética; forneça testes de quebra e nunca aprove código sem testes.
42
- R14. Filtro de Sanidade de Ideias: Ideias em docs/IDEAS.md devem ter conexão 100% direta com o domínio e stack real do projeto.
43
- R15. Imutabilidade & Segredos Compulsórios: Requisitos do fluxo são imutáveis e inquebráveis. Valide e obtenha ativamente todas as credenciais (GitHub, Jira, Confluence) com zero falhas silenciosas.
44
-
45
- [TRÍPLICE REGISTRO]:
46
- 1. Git: Branch dedicada -> Testes locais -> Commit no formato [<TICKET>] - <Ações breves em português> -> PR formal -> CI verde -> Merge.
47
- 2. Jira: 'A fazer' -> 'Em andamento' -> Comentário Canônico (15 seções) -> 'Concluído'.
48
- 3. Confluence: Registro contínuo de novos ADRs na Bíblia do Projeto.
49
-
50
- [MODOS DE OPERAÇÃO]:
51
- - modo: orchestrator: Coordenação central, triagem e despacho de tarefas.
52
- - modo: devsecops: Implementação de código robusto, arquitetura limpa e esteiras CI/CD.
53
- - modo: qa: Auditoria adversária independente de PR e emissão de parecer canônico.
54
- - modo: scrum: Gestão ágil, melhoria contínua (PDCA/Ishikawa) e backlog recursivo.
55
- - modo: sre: Observabilidade contínua (/metrics), telemetria e auto-remediação.
56
- - modo: full-dev: Loop autônomo contínuo: estuda ideia -> implementa -> testa -> registra -> avança sem parar.
57
-
58
- [PROTOCOLO DE SAÍDA NO CHAT]:
59
- • **Feito 1** ([Jira TICKET](url) · [Commit abc](url))
60
- Descrição direta do feito.
61
-
62
- • **Feito 2** ([Confluence ADR](url) · [PR #X](url))
63
- Detalhe objetivo do teste ou entrega.
64
- (Obrigatória linha em branco dupla entre tópicos)`;
65
- }
66
-
67
- function compressCustomText(text, maxTokens = 1500) {
68
- if (!text) return '';
69
-
70
- // 1. Remove comentários de cabeçalho prolixos e tags html vazias
71
- let cleaned = text
72
- .replace(/<!--[\s\S]*?-->/g, '')
73
- .replace(/<[^>]+>/g, '')
74
- .replace(/\r\n/g, '\n');
75
-
76
- // 2. Remove emojis e caracteres decorativos excessivos
77
- cleaned = cleaned.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2700}-\u{27BF}\u{2600}-\u{26FF}]/gu, '');
78
-
79
- // 3. Compactação de linhas em branco consecutivas
80
- cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
81
-
82
- // 4. Se ainda ultrapassar maxTokens, aplica sumarização imperativa
83
- const currentTokens = estimateTokens(cleaned);
84
- if (currentTokens > maxTokens) {
85
- const compactBase = generateCompactPrompt();
86
- return compactBase;
87
- }
88
-
89
- return cleaned.trim();
90
- }
91
-
92
- function exportCompactAgent(targetDir = ROOT_DIR) {
93
- const compactDir = path.join(targetDir, 'agents', 'compact');
94
- if (!fs.existsSync(compactDir)) {
95
- fs.mkdirSync(compactDir, { recursive: true });
96
- }
97
-
98
- const promptContent = generateCompactPrompt();
99
- const filePath = path.join(compactDir, 'AGENT_ORCHESTRATOR_COMPACTO.md');
100
- fs.writeFileSync(filePath, promptContent, 'utf8');
101
-
102
- const tokens = estimateTokens(promptContent);
103
- return {
104
- filePath,
105
- tokenCount: tokens,
106
- charCount: promptContent.length,
107
- underBudget: tokens <= 1500
108
- };
109
- }
110
-
111
- async function runCli() {
112
- const args = process.argv.slice(2);
113
- const result = exportCompactAgent(ROOT_DIR);
114
-
115
- console.log('=== Compactador Semântico de Contexto para Modelos Locais (I-6) ===');
116
- console.log(`Arquivo Gerado: ${result.filePath}`);
117
- console.log(`Caracteres: ${result.charCount}`);
118
- console.log(`Tokens Estimados: ${result.tokenCount} (Orçamento máximo: 1.500)`);
119
- console.log(`Status de Conformidade: ${result.underBudget ? 'DENTRO DO ORÇAMENTO (APROVADO)' : 'ULTRA-PASSADO'}`);
120
-
121
- if (args.includes('--print')) {
122
- console.log('\n--- Conteúdo Destilado ---\n');
123
- console.log(fs.readFileSync(result.filePath, 'utf8'));
124
- }
125
- }
126
-
127
- if (require.main === module) {
128
- runCli().catch(err => {
129
- console.error(err);
130
- process.exit(1);
131
- });
132
- }
133
-
134
- module.exports = {
135
- estimateTokens,
136
- generateCompactPrompt,
137
- compressCustomText,
138
- exportCompactAgent
139
- };
@@ -1,166 +0,0 @@
1
- /**
2
- * scripts/contract_test_validator.js
3
- * Auditor de Contratos OpenAPI / Swagger & Detecção de Drift de API (Loop 2)
4
- *
5
- * Implementa validação arquitetural de contratos de dados:
6
- * - Validação sintática e estrutural de especificações OpenAPI 3.x / Swagger 2.0
7
- * - Verificação de completude (operações sem summary, responses ou operationId)
8
- * - Detecção bidirecional de Drift entre contrato e código:
9
- * a) Shadow APIs (rotas implementadas sem documentação no contrato)
10
- * b) Unimplemented Endpoints (rotas prometidas no contrato mas ausentes no código)
11
- * - Cálculo de cobertura de conformidade do contrato de dados.
12
- */
13
- 'use strict';
14
-
15
- const fs = require('fs');
16
-
17
- /**
18
- * Normaliza caminho de rota para comparação ignorando parâmetros de template ({id} vs :id)
19
- * @param {string} routePath
20
- * @returns {string}
21
- */
22
- function normalizeRoutePath(routePath) {
23
- if (!routePath || typeof routePath !== 'string') return '/';
24
- return routePath
25
- .trim()
26
- .replace(/\/+$/, '')
27
- .replace(/\{[^}]+\}/g, ':param')
28
- .replace(/:[a-zA-Z0-9_]+/g, ':param')
29
- .toLowerCase();
30
- }
31
-
32
- /**
33
- * Valida especificação OpenAPI e audita contra rotas implementadas.
34
- * @param {string|object} specInput
35
- * @param {Array<{method: string, path: string}>} [implementedRoutes=[]]
36
- * @returns {object}
37
- */
38
- function validateOpenApiContract(specInput, implementedRoutes = []) {
39
- let spec;
40
- try {
41
- if (typeof specInput === 'string') {
42
- if (fs.existsSync(specInput)) {
43
- spec = JSON.parse(fs.readFileSync(specInput, 'utf8'));
44
- } else {
45
- spec = JSON.parse(specInput);
46
- }
47
- } else if (typeof specInput === 'object' && specInput !== null) {
48
- spec = specInput;
49
- } else {
50
- throw new Error('Entrada da especificação inválida.');
51
- }
52
- } catch (err) {
53
- return {
54
- valid: false,
55
- compliant: false,
56
- error: `Erro ao processar contrato OpenAPI: ${err.message}`,
57
- drifts: []
58
- };
59
- }
60
-
61
- // 1. Validação Estrutural Básica
62
- const isOpenApi = Boolean(spec.openapi || spec.swagger);
63
- const paths = spec.paths || {};
64
- const pathsList = Object.keys(paths);
65
-
66
- const missingSummaries = [];
67
- const missingResponses = [];
68
- const documentedOperations = [];
69
-
70
- const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
71
-
72
- for (const [pathStr, pathItem] of Object.entries(paths)) {
73
- if (!pathItem || typeof pathItem !== 'object') continue;
74
-
75
- for (const method of HTTP_METHODS) {
76
- const op = pathItem[method];
77
- if (!op) continue;
78
-
79
- const opInfo = {
80
- method: method.toUpperCase(),
81
- path: pathStr,
82
- normalizedPath: normalizeRoutePath(pathStr),
83
- operationId: op.operationId || null,
84
- summary: op.summary || null
85
- };
86
- documentedOperations.push(opInfo);
87
-
88
- if (!op.summary) {
89
- missingSummaries.push(`${opInfo.method} ${pathStr}`);
90
- }
91
- if (!op.responses || Object.keys(op.responses).length === 0) {
92
- missingResponses.push(`${opInfo.method} ${pathStr}`);
93
- }
94
- }
95
- }
96
-
97
- // 2. Detecção de Drift com Código Implementado (se fornecido)
98
- const normalizedImplemented = (implementedRoutes || []).map(r => ({
99
- method: String(r.method || 'GET').toUpperCase(),
100
- path: r.path,
101
- normalizedPath: normalizeRoutePath(r.path)
102
- }));
103
-
104
- const shadowApis = [];
105
- const unimplementedEndpoints = [];
106
-
107
- // Checar Shadow APIs (código tem rota não declarada na spec)
108
- for (const imp of normalizedImplemented) {
109
- const foundInDoc = documentedOperations.some(
110
- doc => doc.method === imp.method && doc.normalizedPath === imp.normalizedPath
111
- );
112
- if (!foundInDoc) {
113
- shadowApis.push({
114
- method: imp.method,
115
- path: imp.path,
116
- reason: 'Rota implementada em código porém ausente na especificação OpenAPI (Shadow API / Risco de Segurança)'
117
- });
118
- }
119
- }
120
-
121
- // Checar Unimplemented Endpoints (spec declara rota ausente no código)
122
- if (normalizedImplemented.length > 0) {
123
- for (const doc of documentedOperations) {
124
- const foundInCode = normalizedImplemented.some(
125
- imp => imp.method === doc.method && imp.normalizedPath === doc.normalizedPath
126
- );
127
- if (!foundInCode) {
128
- unimplementedEndpoints.push({
129
- method: doc.method,
130
- path: doc.path,
131
- reason: 'Rota declarada no contrato OpenAPI porém ainda não implementada no código'
132
- });
133
- }
134
- }
135
- }
136
-
137
- const structuralIssues = missingResponses.length + (isOpenApi ? 0 : 1);
138
- const valid = structuralIssues === 0 && Boolean(spec.info);
139
- const compliant = valid && shadowApis.length === 0;
140
-
141
- const totalEvaluated = documentedOperations.length;
142
- const coveragePercent = totalEvaluated > 0
143
- ? Math.round(((totalEvaluated - unimplementedEndpoints.length) / totalEvaluated) * 100)
144
- : 100;
145
-
146
- return {
147
- valid,
148
- compliant,
149
- version: spec.openapi || spec.swagger || 'Desconhecida',
150
- title: (spec.info && spec.info.title) || 'Sem título',
151
- totalPaths: pathsList.length,
152
- totalOperations: documentedOperations.length,
153
- missingSummaries,
154
- missingResponses,
155
- shadowApis,
156
- unimplementedEndpoints,
157
- coveragePercent,
158
- driftsCount: shadowApis.length + unimplementedEndpoints.length,
159
- verdict: compliant ? 'CONFORME' : 'DRIFT_DETECTADO'
160
- };
161
- }
162
-
163
- module.exports = {
164
- validateOpenApiContract,
165
- normalizeRoutePath
166
- };