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,337 @@
|
|
|
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('<', '<')
|
|
29
|
+
.replaceAll('>', '>')
|
|
30
|
+
.replaceAll('"', '"')
|
|
31
|
+
.replaceAll("'", ''');
|
|
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
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
};
|