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,905 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/bin/smart_onboarding.js
4
+ *
5
+ * Motor de Onboarding Bimodal Autônomo & Auto-Provisionamento da Tríade (Regra 16 - AGDEV-37).
6
+ * Detecta automaticamente se o projeto é Greenfield (novo do zero) ou Brownfield (existente),
7
+ * audita credenciais Atlassian, auto-provisiona Projeto no Jira e Espaço no Confluence via API (se ausentes),
8
+ * e publica um Portal Executivo de Arquitetura no Confluence sem jamais deixar a Homepage vazia.
9
+ * Implementa o Gate Compulsório da Regra 16 e exportação em padrão OASIS SARIF v2.1.0.
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('node:fs');
15
+ const path = require('node:path');
16
+ const { getAtlassianCredentials, requestAtlassian, checkGithubAuth } = require('../integrations/atlassian_client.js');
17
+ const { createSarifReport } = require('../core/sarif_engine.js');
18
+
19
+ // Diretórios e namespaces ignorados durante varreduras (imutável)
20
+ const IGNORED_DIRS = Object.freeze(new Set([
21
+ '.git',
22
+ '.gemini',
23
+ '.antigravity',
24
+ '.vscode',
25
+ '.idea',
26
+ 'node_modules',
27
+ 'agent-master-devsecops',
28
+ 'dist',
29
+ 'build',
30
+ 'target',
31
+ 'coverage',
32
+ '.gradle',
33
+ '.mvn'
34
+ ]));
35
+
36
+ // Manifestos canônicos de build e gerenciadores de dependência (imutável)
37
+ const BUILD_MANIFESTS = Object.freeze(new Set([
38
+ 'pom.xml',
39
+ 'build.gradle',
40
+ 'build.gradle.kts',
41
+ 'package.json',
42
+ 'go.mod',
43
+ 'Cargo.toml',
44
+ 'requirements.txt',
45
+ 'pyproject.toml',
46
+ 'Pipfile',
47
+ 'Gemfile',
48
+ 'composer.json',
49
+ 'Dockerfile',
50
+ 'docker-compose.yml',
51
+ 'docker-compose.yaml',
52
+ 'Makefile',
53
+ 'CMakeLists.txt',
54
+ 'mix.exs'
55
+ ]));
56
+
57
+ // Assinaturas tecnológicas de stack declarativas e imutáveis
58
+ const STACK_SIGNATURES = Object.freeze([
59
+ { manifest: 'pom.xml', stack: 'Java (Maven / Spring Boot)', hasMaven: true },
60
+ { manifest: 'build.gradle', stack: 'Java (Gradle)', hasGradle: true },
61
+ { manifest: 'build.gradle.kts', stack: 'Kotlin (Gradle Kotlin DSL)', hasGradle: true },
62
+ { manifest: 'package.json', stack: 'Node.js / TypeScript', hasNpm: true },
63
+ { manifest: 'go.mod', stack: 'Go' },
64
+ { manifest: 'Cargo.toml', stack: 'Rust' },
65
+ { manifest: 'requirements.txt', stack: 'Python' },
66
+ { manifest: 'pyproject.toml', stack: 'Python (Poetry / Modern)' },
67
+ { manifest: 'Dockerfile', stack: 'Docker', hasDocker: true },
68
+ { manifest: 'docker-compose.yml', stack: 'Docker Compose', hasDocker: true },
69
+ { manifest: 'docker-compose.yaml', stack: 'Docker Compose', hasDocker: true },
70
+ { manifest: 'Makefile', stack: 'C / Make / System' }
71
+ ]);
72
+
73
+ /**
74
+ * 1. Detecção Bimodal: Greenfield vs. Brownfield com tratamento defensivo
75
+ * @param {string} [targetDir]
76
+ * @returns {object}
77
+ */
78
+ function detectProjectMode(targetDir) {
79
+ if (targetDir === null || (typeof targetDir !== 'string' && targetDir !== undefined)) {
80
+ return {
81
+ mode: 'GREENFIELD',
82
+ isBrownfield: false,
83
+ isGreenfield: true,
84
+ targetDir: targetDir || 'unknown',
85
+ evidence: { foundManifests: [], foundSourceDirs: [], hasReadme: false, totalRelevantEntries: 0 }
86
+ };
87
+ }
88
+
89
+ const safeTargetDir = targetDir !== undefined ? path.resolve(targetDir) : process.cwd();
90
+
91
+ if (!fs.existsSync(safeTargetDir)) {
92
+ return {
93
+ mode: 'GREENFIELD',
94
+ isBrownfield: false,
95
+ isGreenfield: true,
96
+ targetDir: safeTargetDir,
97
+ evidence: { foundManifests: [], foundSourceDirs: [], hasReadme: false, totalRelevantEntries: 0 }
98
+ };
99
+ }
100
+
101
+ let allEntries = [];
102
+ try {
103
+ allEntries = fs.readdirSync(safeTargetDir, { withFileTypes: true });
104
+ } catch (_) {
105
+ return {
106
+ mode: 'GREENFIELD',
107
+ isBrownfield: false,
108
+ isGreenfield: true,
109
+ targetDir: safeTargetDir,
110
+ evidence: { foundManifests: [], foundSourceDirs: [], hasReadme: false, totalRelevantEntries: 0 }
111
+ };
112
+ }
113
+
114
+ const relevantEntries = allEntries.filter(e => !IGNORED_DIRS.has(e.name));
115
+ const foundManifests = [];
116
+ const foundSourceDirs = [];
117
+ let hasReadme = false;
118
+
119
+ for (const entry of relevantEntries) {
120
+ if (entry.isFile()) {
121
+ if (BUILD_MANIFESTS.has(entry.name)) {
122
+ foundManifests.push(entry.name);
123
+ }
124
+ if (entry.name.toLowerCase() === 'readme.md') {
125
+ hasReadme = true;
126
+ }
127
+ } else if (entry.isDirectory()) {
128
+ try {
129
+ const subEntries = fs.readdirSync(path.join(safeTargetDir, entry.name)).filter(f => !IGNORED_DIRS.has(f));
130
+ if (subEntries.length > 0) {
131
+ foundSourceDirs.push(entry.name);
132
+ }
133
+ } catch (err) {
134
+ void err;
135
+ }
136
+ }
137
+ }
138
+
139
+ const isBrownfield = foundManifests.length > 0 || foundSourceDirs.length > 0 || (hasReadme && relevantEntries.length > 1);
140
+ const mode = isBrownfield ? 'BROWNFIELD' : 'GREENFIELD';
141
+
142
+ return {
143
+ mode,
144
+ isBrownfield,
145
+ isGreenfield: !isBrownfield,
146
+ targetDir: safeTargetDir,
147
+ evidence: {
148
+ foundManifests,
149
+ foundSourceDirs,
150
+ hasReadme,
151
+ totalRelevantEntries: relevantEntries.length
152
+ }
153
+ };
154
+ }
155
+
156
+ /**
157
+ * 2. Engenharia Reversa Semântica de Projetos Brownfield com tratamento defensivo
158
+ * @param {string} [targetDir]
159
+ * @returns {object}
160
+ */
161
+ function inspectProjectContext(targetDir) {
162
+ if (targetDir === null || (typeof targetDir !== 'string' && targetDir !== undefined)) {
163
+ return {
164
+ mode: 'GREENFIELD',
165
+ name: 'project',
166
+ title: 'PROJECT',
167
+ description: 'Novo projeto Greenfield em inicialização.',
168
+ businessDomain: 'Geral',
169
+ stack: ['Agnóstico / Em definição'],
170
+ modules: [],
171
+ entryPoints: [],
172
+ hasDocker: false
173
+ };
174
+ }
175
+
176
+ const safeTargetDir = targetDir !== undefined ? path.resolve(targetDir) : process.cwd();
177
+
178
+ if (!fs.existsSync(safeTargetDir)) {
179
+ return {
180
+ mode: 'GREENFIELD',
181
+ name: path.basename(safeTargetDir) || 'project',
182
+ title: (path.basename(safeTargetDir) || 'PROJECT').toUpperCase(),
183
+ description: 'Novo projeto Greenfield em inicialização.',
184
+ businessDomain: 'Geral',
185
+ stack: ['Agnóstico / Em definição'],
186
+ modules: [],
187
+ entryPoints: [],
188
+ hasDocker: false
189
+ };
190
+ }
191
+
192
+ const detection = detectProjectMode(safeTargetDir);
193
+ const info = {
194
+ mode: detection.mode,
195
+ name: path.basename(safeTargetDir) || 'project',
196
+ title: '',
197
+ description: '',
198
+ businessDomain: 'Geral / Engenharia de Software',
199
+ stack: [],
200
+ modules: [],
201
+ entryPoints: [],
202
+ hasDocker: false
203
+ };
204
+
205
+ // Leitura segura do README
206
+ const readmePath = path.join(safeTargetDir, 'README.md');
207
+ if (fs.existsSync(readmePath)) {
208
+ try {
209
+ const readmeText = fs.readFileSync(readmePath, 'utf8');
210
+ const lines = readmeText.split(/\r?\n/).filter(Boolean);
211
+ for (const line of lines) {
212
+ if (!info.title && line.startsWith('# ')) {
213
+ info.title = line.replace(/^#\s+/, '').trim();
214
+ } else if (!info.description && !line.startsWith('#') && !line.startsWith('>')) {
215
+ info.description = line.trim();
216
+ }
217
+ }
218
+ } catch (err) {
219
+ void err;
220
+ }
221
+ }
222
+
223
+ if (!info.title) info.title = info.name.replace(/[-_]/g, ' ').toUpperCase();
224
+ if (!info.description) info.description = 'Ecossistema de software governado pelo Agent Master DevSecOps.';
225
+
226
+ // Mapeamento declarativo de Stack e Módulos
227
+ let entries = [];
228
+ try {
229
+ entries = fs.readdirSync(safeTargetDir, { withFileTypes: true }).filter(e => !IGNORED_DIRS.has(e.name));
230
+ } catch (_) {
231
+ entries = [];
232
+ }
233
+
234
+ for (const entry of entries) {
235
+ const fullPath = path.join(safeTargetDir, entry.name);
236
+ if (entry.isDirectory()) {
237
+ try {
238
+ const subFiles = fs.readdirSync(fullPath);
239
+ const hasModuleSign = subFiles.some(f => ['pom.xml', 'package.json', 'Dockerfile', 'src', 'go.mod', 'Cargo.toml'].includes(f));
240
+ if (hasModuleSign) {
241
+ info.modules.push({
242
+ name: entry.name,
243
+ hasDocker: subFiles.includes('Dockerfile'),
244
+ hasMaven: subFiles.includes('pom.xml'),
245
+ hasNpm: subFiles.includes('package.json')
246
+ });
247
+ }
248
+ } catch (err) {
249
+ void err;
250
+ }
251
+ } else if (entry.isFile()) {
252
+ const sig = STACK_SIGNATURES.find(s => s.manifest === entry.name);
253
+ if (sig) {
254
+ info.stack.push(sig.stack);
255
+ if (sig.hasDocker) info.hasDocker = true;
256
+ }
257
+ }
258
+ }
259
+
260
+ if (info.modules.some(m => m.hasMaven)) info.stack.push('Java / Spring Boot');
261
+ if (info.modules.some(m => m.hasNpm)) info.stack.push('Node.js');
262
+ if (info.modules.some(m => m.hasDocker)) info.hasDocker = true;
263
+
264
+ info.stack = Array.from(new Set(info.stack));
265
+ if (info.stack.length === 0) info.stack.push('Agnóstico / Em definição');
266
+
267
+ return info;
268
+ }
269
+
270
+ /**
271
+ * 3. Auto-Provisionamento do Projeto no Jira via API oficial
272
+ * @param {object} creds
273
+ * @param {string} projectKey
274
+ * @param {string} [projectName='DevSecOps Project']
275
+ * @returns {Promise<object>}
276
+ */
277
+ async function ensureJiraProject(creds, projectKey, projectName = 'DevSecOps Project') {
278
+ if (!creds || !creds.isConfigured) {
279
+ return { status: 'SKIPPED', reason: 'Credenciais Atlassian ausentes ou incompletas.' };
280
+ }
281
+
282
+ const cleanKey = String(projectKey || 'PROJ').toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 10);
283
+
284
+ // 1. Verifica se já existe
285
+ const checkRes = await requestAtlassian(creds, 'GET', `/rest/api/3/project/${cleanKey}`);
286
+ if (checkRes.status === 200) {
287
+ return {
288
+ status: 'EXISTS',
289
+ created: false,
290
+ key: cleanKey,
291
+ id: checkRes.body?.id,
292
+ name: checkRes.body?.name
293
+ };
294
+ }
295
+
296
+ // 2. Se não existir (404), obtém o accountId do líder
297
+ const myselfRes = await requestAtlassian(creds, 'GET', '/rest/api/3/myself');
298
+ const leadAccountId = myselfRes.body?.accountId;
299
+
300
+ if (!leadAccountId) {
301
+ return { status: 'FAILED', reason: 'Não foi possível recuperar o accountId do usuário líder.' };
302
+ }
303
+
304
+ const payload = {
305
+ key: cleanKey,
306
+ name: projectName,
307
+ projectTypeKey: 'software',
308
+ projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-scrum-classic',
309
+ description: `Projeto ${projectName} criado automaticamente pelo Agent Master DevSecOps (Regra 16).`,
310
+ leadAccountId,
311
+ assigneeType: 'PROJECT_LEAD'
312
+ };
313
+
314
+ const createRes = await requestAtlassian(creds, 'POST', '/rest/api/3/project', payload);
315
+ if (createRes.status === 200 || createRes.status === 201) {
316
+ return {
317
+ status: 'CREATED',
318
+ created: true,
319
+ key: cleanKey,
320
+ id: createRes.body?.id,
321
+ name: projectName
322
+ };
323
+ }
324
+
325
+ return { status: 'FAILED', httpStatus: createRes.status, error: createRes.body };
326
+ }
327
+
328
+ /**
329
+ * 4. Auto-Provisionamento do Espaço no Confluence via API oficial
330
+ * @param {object} creds
331
+ * @param {string} spaceKey
332
+ * @param {string} [spaceName='DevSecOps Documentation']
333
+ * @returns {Promise<object>}
334
+ */
335
+ async function ensureConfluenceSpace(creds, spaceKey, spaceName = 'DevSecOps Documentation') {
336
+ if (!creds || !creds.isConfigured) {
337
+ return { status: 'SKIPPED', reason: 'Credenciais Atlassian ausentes ou incompletas.' };
338
+ }
339
+
340
+ const cleanKey = String(spaceKey || 'PROJ').toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 10);
341
+
342
+ // 1. Verifica se já existe
343
+ const checkRes = await requestAtlassian(creds, 'GET', `/wiki/rest/api/space/${cleanKey}`);
344
+ if (checkRes.status === 200) {
345
+ const hpId = checkRes.body?.homepage?.id || checkRes.body?.homepageId;
346
+ return {
347
+ status: 'EXISTS',
348
+ created: false,
349
+ key: cleanKey,
350
+ name: checkRes.body?.name,
351
+ homepageId: hpId
352
+ };
353
+ }
354
+
355
+ // 2. Se não existir, cria o espaço
356
+ const payload = {
357
+ key: cleanKey,
358
+ name: spaceName,
359
+ description: {
360
+ plain: {
361
+ value: `Espaço de Documentação Técnica e Arquitetura do projeto ${spaceName} governado pelo Agent Master DevSecOps.`,
362
+ representation: 'plain'
363
+ }
364
+ }
365
+ };
366
+
367
+ const createRes = await requestAtlassian(creds, 'POST', '/wiki/rest/api/space', payload);
368
+ if (createRes.status === 200 || createRes.status === 201) {
369
+ const hpId = createRes.body?.homepage?.id || createRes.body?.homepageId;
370
+ return {
371
+ status: 'CREATED',
372
+ created: true,
373
+ key: cleanKey,
374
+ name: spaceName,
375
+ homepageId: hpId
376
+ };
377
+ }
378
+
379
+ return { status: 'FAILED', httpStatus: createRes.status, error: createRes.body };
380
+ }
381
+
382
+ /**
383
+ * 5. Publicação do Portal Executivo na Homepage (Zero Páginas Vazias)
384
+ * @param {object} creds
385
+ * @param {string} spaceKey
386
+ * @param {string} homepageId
387
+ * @param {object} projectContext
388
+ * @returns {Promise<object>}
389
+ */
390
+ async function publishExecutiveHomepage(creds, spaceKey, homepageId, projectContext = {}) {
391
+ if (!creds || !creds.isConfigured || !homepageId) {
392
+ return { status: 'SKIPPED', reason: 'Homepage ID ou credenciais ausentes.' };
393
+ }
394
+
395
+ const safeContext = {
396
+ title: projectContext.title || 'Sistema Enterprise',
397
+ description: projectContext.description || 'Ecossistema governado por agentes autônomos.',
398
+ mode: projectContext.mode || 'BROWNFIELD',
399
+ stack: Array.isArray(projectContext.stack) ? projectContext.stack : ['Nativo'],
400
+ modules: Array.isArray(projectContext.modules) ? projectContext.modules : []
401
+ };
402
+
403
+ // Recupera versão atual
404
+ const getRes = await requestAtlassian(creds, 'GET', `/wiki/rest/api/content/${homepageId}?expand=version`);
405
+ const currentVersion = getRes.body?.version?.number || 1;
406
+ const nextVersion = currentVersion + 1;
407
+
408
+ const stackHtml = safeContext.stack.map(s => `<li>${s}</li>`).join('');
409
+ const modulesHtml = safeContext.modules.length > 0
410
+ ? safeContext.modules.map(m => `<tr><td><code>${m.name}</code></td><td>${m.hasDocker ? 'Sim (Container)' : 'Aplicação'}</td><td>${m.hasMaven ? 'Maven' : (m.hasNpm ? 'NPM' : 'Nativo')}</td></tr>`).join('')
411
+ : '<tr><td><code>raiz</code></td><td>Aplicação Principal</td><td>Nativo</td></tr>';
412
+
413
+ const html = `
414
+ <h2>🏛️ Portal de Engenharia & Arquitetura — ${safeContext.title}</h2>
415
+ <p><strong>Status de Operação:</strong> Homologado sob Governança Multi-Agente Prescritiva (Regra 16).</p>
416
+ <p>${safeContext.description}</p>
417
+ <hr />
418
+ <h3>📌 Estrutura Canônica de Documentação (Tríade Viva)</h3>
419
+ <table>
420
+ <thead>
421
+ <tr>
422
+ <th>Seção de Documentação</th>
423
+ <th>Propósito de Governança</th>
424
+ <th>Padrão de Registro</th>
425
+ </tr>
426
+ </thead>
427
+ <tbody>
428
+ <tr>
429
+ <td><strong>🏛️ Decisões de Arquitetura (ADRs)</strong></td>
430
+ <td>Histórico imutável de decisões técnicas, trade-offs e racional econômico.</td>
431
+ <td>ADR-001 em diante</td>
432
+ </tr>
433
+ <tr>
434
+ <td><strong>📐 Especificações Técnicas (SPECs)</strong></td>
435
+ <td>Contratos públicos de API, modelos de dados, endpoints e DTOs.</td>
436
+ <td>SPEC-001 em diante</td>
437
+ </tr>
438
+ <tr>
439
+ <td><strong>🛡️ Governança & Padrões Multiagente</strong></td>
440
+ <td>Guias operacionais locais, esteiras CI/CD, Docker e Regras de Engenharia.</td>
441
+ <td>GOV-001 em diante</td>
442
+ </tr>
443
+ </tbody>
444
+ </table>
445
+ <hr />
446
+ <h3>🚀 Topologia Técnica do Projeto (${safeContext.mode})</h3>
447
+ <p><strong>Stack Tecnológica Identificada:</strong></p>
448
+ <ul>${stackHtml}</ul>
449
+ <p><strong>Módulos e Componentes Arquiteturais:</strong></p>
450
+ <table>
451
+ <thead>
452
+ <tr>
453
+ <th>Módulo</th>
454
+ <th>Tipo</th>
455
+ <th>Gerenciador de Build</th>
456
+ </tr>
457
+ </thead>
458
+ <tbody>
459
+ ${modulesHtml}
460
+ </tbody>
461
+ </table>
462
+ <hr />
463
+ <p><em>Página gerada e mantida autonomamente pelo Agent Master DevSecOps em conformidade com a Regra 16.</em></p>
464
+ `;
465
+
466
+ const updatePayload = {
467
+ version: { number: nextVersion },
468
+ title: `${safeContext.title} — Portal de Engenharia`,
469
+ type: 'page',
470
+ body: {
471
+ storage: {
472
+ value: html,
473
+ representation: 'storage'
474
+ }
475
+ }
476
+ };
477
+
478
+ const putRes = await requestAtlassian(creds, 'PUT', `/wiki/rest/api/content/${homepageId}`, updatePayload);
479
+ return {
480
+ status: putRes.status === 200 ? 'SUCCESS' : 'FAILED',
481
+ httpStatus: putRes.status,
482
+ homepageId,
483
+ version: nextVersion
484
+ };
485
+ }
486
+
487
+ /**
488
+ * 6. Orquestrador do Onboarding Inteligente
489
+ * @param {string} [targetDir=process.cwd()]
490
+ * @param {object} [options={}]
491
+ * @returns {Promise<object>}
492
+ */
493
+ async function runSmartOnboarding(targetDir = process.cwd(), options = {}) {
494
+ const safeTargetDir = (typeof targetDir === 'string' && targetDir) ? path.resolve(targetDir) : process.cwd();
495
+ const projectContext = inspectProjectContext(safeTargetDir);
496
+ const creds = getAtlassianCredentials(safeTargetDir);
497
+
498
+ const defaultKey = (process.env.JIRA_PROJECT_KEY || process.env.CONFLUENCE_SPACE_KEY || projectContext.name.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 6) || 'PROJ');
499
+
500
+ const report = {
501
+ targetDir: safeTargetDir,
502
+ mode: projectContext.mode,
503
+ projectContext,
504
+ credentialsConfigured: creds.isConfigured,
505
+ jira: { key: defaultKey, status: 'PENDING' },
506
+ confluence: { key: defaultKey, status: 'PENDING' },
507
+ homepage: { status: 'PENDING' }
508
+ };
509
+
510
+ if (!creds.isConfigured) {
511
+ report.jira.status = 'SKIPPED_NO_CREDENTIALS';
512
+ report.confluence.status = 'SKIPPED_NO_CREDENTIALS';
513
+ report.homepage.status = 'SKIPPED_NO_CREDENTIALS';
514
+ return report;
515
+ }
516
+
517
+ // Auto-provisiona Jira
518
+ const jiraRes = await ensureJiraProject(creds, defaultKey, `${projectContext.title} DevSecOps`);
519
+ report.jira = { ...jiraRes, key: defaultKey };
520
+
521
+ // Auto-provisiona Confluence
522
+ const confRes = await ensureConfluenceSpace(creds, defaultKey, `${projectContext.title} DevSecOps`);
523
+ report.confluence = { ...confRes, key: defaultKey };
524
+
525
+ // Popula Homepage (Zero Páginas Vazias)
526
+ if (confRes.homepageId) {
527
+ const hpRes = await publishExecutiveHomepage(creds, defaultKey, confRes.homepageId, projectContext);
528
+ report.homepage = hpRes;
529
+ }
530
+
531
+ return report;
532
+ }
533
+
534
+ /**
535
+ * Constrói o veredito e mensagem canônica de bloqueio ou liberação (DRY).
536
+ * @param {string[]} blockers
537
+ * @param {object} options
538
+ * @returns {object}
539
+ */
540
+ function buildTriadVerdict(blockers, options = {}) {
541
+ const isOffline = Boolean(options.offlineMode || process.env.OFFLINE_MODE === 'true');
542
+ const canDevelop = options.forcePermit ? true : (isOffline ? true : blockers.length === 0);
543
+ const verdict = canDevelop ? (isOffline && blockers.length > 0 ? 'MODO_LOCAL_OFFLINE' : 'LIBERADO') : 'BLOQUEADO_REGRA_16';
544
+
545
+ let gateMessage = '';
546
+ if (!canDevelop) {
547
+ gateMessage = `⛔ [GATE DE GOVERNANÇA BLOQUEADO — REGRA 16]\n` +
548
+ `O desenvolvimento de software (criação de código, endpoints ou refatoração) está ESTRITAMENTE BLOQUEADO.\n` +
549
+ `O Roteiro Inicial de certificação da Tríade (Git + Jira + Confluence) ainda não foi concluído:\n` +
550
+ blockers.map(b => `- ${b}`).join('\n') +
551
+ `\n\n👉 Ação Prescrita Obrigatória: Execute "node agent-master-devsecops/scripts/bin/bootstrap_cli.js" ou configure seu .env e autentique a Tríade antes de iniciar o desenvolvimento.\n` +
552
+ `💡 Dica de Resiliência: Para prosseguir em modo local desacoplado sem travas, ative options.offlineMode ou a flag --offline.`;
553
+ } else if (verdict === 'MODO_LOCAL_OFFLINE') {
554
+ gateMessage = `⚡ [GATE DE GOVERNANÇA: MODO LOCAL DESACOPLADO (OFFLINE)]\n` +
555
+ `Credenciais externas da Atlassian ausentes, mas o desenvolvimento local foi AUTORIZADO em modo offline-first.\n` +
556
+ `As alterações serão registradas no Git e em docs/CHANGELOG.md para posterior sincronização.`;
557
+ } else {
558
+ gateMessage = `✅ [GATE DE GOVERNANÇA LIBERADO — REGRA 16]\n` +
559
+ `Tríade (Git + Jira + Confluence) 100% verificada, autêntica e operando sem falhas. Desenvolvimento de software autorizado!`;
560
+ }
561
+
562
+ return { canDevelop, verdict, gateMessage };
563
+ }
564
+
565
+ /**
566
+ * 7. Gate Compulsório da Regra 16 (Versão Síncrona)
567
+ * Veta comandos de desenvolvimento enquanto a Tríade (Git + Jira + Confluence)
568
+ * não estiver 100% configurada ou em modo offline-first.
569
+ * @param {string} [targetDir=process.cwd()]
570
+ * @param {object} [options={}]
571
+ * @returns {object}
572
+ */
573
+ function verifyTriadGateSync(targetDir = process.cwd(), options = {}) {
574
+ const safeTargetDir = (typeof targetDir === 'string' && targetDir) ? path.resolve(targetDir) : process.cwd();
575
+ const gh = checkGithubAuth();
576
+ const creds = getAtlassianCredentials(safeTargetDir);
577
+ const projectContext = inspectProjectContext(safeTargetDir);
578
+
579
+ const blockers = [];
580
+ const defaultKey = process.env.JIRA_PROJECT_KEY || process.env.CONFLUENCE_SPACE_KEY || 'PROJ';
581
+
582
+ const isGhAuth = options.mockGithubAuthenticated !== undefined ? Boolean(options.mockGithubAuthenticated) : Boolean(gh.isAuthenticated);
583
+ const isAtlassianConfig = options.mockAtlassianConfigured !== undefined ? Boolean(options.mockAtlassianConfigured) : Boolean(creds.isConfigured);
584
+
585
+ const status = {
586
+ git: {
587
+ ready: isGhAuth,
588
+ user: gh.user || null,
589
+ details: gh.details || ''
590
+ },
591
+ jira: {
592
+ configured: isAtlassianConfig,
593
+ key: defaultKey,
594
+ projectExists: Boolean(options.mockJiraExists)
595
+ },
596
+ confluence: {
597
+ configured: isAtlassianConfig,
598
+ key: defaultKey,
599
+ spaceExists: Boolean(options.mockConfluenceExists),
600
+ homepageReady: Boolean(options.mockHomepageReady)
601
+ }
602
+ };
603
+
604
+ // 1. Checa Git / GitHub CLI
605
+ if (!isGhAuth) {
606
+ blockers.push('GitHub CLI não autenticado. Execute "gh auth login" no terminal.');
607
+ }
608
+
609
+ // 2. Checa Credenciais Atlassian
610
+ if (!isAtlassianConfig) {
611
+ blockers.push('Credenciais Atlassian (ATLASSIAN_DOMAIN, ATLASSIAN_EMAIL, ATLASSIAN_API_TOKEN) não configuradas no .env.');
612
+ }
613
+
614
+ const { canDevelop, verdict, gateMessage } = buildTriadVerdict(blockers, options);
615
+
616
+ return {
617
+ canDevelop,
618
+ verdict,
619
+ gateMessage,
620
+ blockers,
621
+ status,
622
+ projectContext
623
+ };
624
+ }
625
+
626
+ /**
627
+ * 8. Gate Compulsório da Regra 16 (Versão Assíncrona com Verificação de Rede)
628
+ * @param {string} [targetDir=process.cwd()]
629
+ * @param {object} [options={}]
630
+ * @returns {Promise<object>}
631
+ */
632
+ async function verifyTriadGate(targetDir = process.cwd(), options = {}) {
633
+ const safeTargetDir = (typeof targetDir === 'string' && targetDir) ? path.resolve(targetDir) : process.cwd();
634
+ const gh = checkGithubAuth();
635
+ const creds = getAtlassianCredentials(safeTargetDir);
636
+ const projectContext = inspectProjectContext(safeTargetDir);
637
+
638
+ const blockers = [];
639
+ const defaultKey = process.env.JIRA_PROJECT_KEY || process.env.CONFLUENCE_SPACE_KEY || 'PROJ';
640
+
641
+ const isGhAuth = options.mockGithubAuthenticated !== undefined ? Boolean(options.mockGithubAuthenticated) : Boolean(gh.isAuthenticated);
642
+ const isAtlassianConfig = options.mockAtlassianConfigured !== undefined ? Boolean(options.mockAtlassianConfigured) : Boolean(creds.isConfigured);
643
+
644
+ const status = {
645
+ git: {
646
+ ready: isGhAuth,
647
+ user: gh.user || null,
648
+ details: gh.details || ''
649
+ },
650
+ jira: {
651
+ configured: isAtlassianConfig,
652
+ key: defaultKey,
653
+ projectExists: false
654
+ },
655
+ confluence: {
656
+ configured: isAtlassianConfig,
657
+ key: defaultKey,
658
+ spaceExists: false,
659
+ homepageReady: false
660
+ }
661
+ };
662
+
663
+ // 1. Checa Git / GitHub CLI
664
+ if (!isGhAuth) {
665
+ blockers.push('GitHub CLI não autenticado. Execute "gh auth login" no terminal.');
666
+ }
667
+
668
+ // 2. Checa Credenciais Atlassian
669
+ if (!isAtlassianConfig) {
670
+ blockers.push('Credenciais Atlassian (ATLASSIAN_DOMAIN, ATLASSIAN_EMAIL, ATLASSIAN_API_TOKEN) não configuradas no .env.');
671
+ }
672
+
673
+ // 3. Se credenciais estiverem ok, valida Jira e Confluence via API
674
+ if (isAtlassianConfig && !options.skipNetwork) {
675
+ try {
676
+ const jiraRes = await requestAtlassian(creds, 'GET', `/rest/api/3/project/${defaultKey}`);
677
+ status.jira.projectExists = (jiraRes.status === 200);
678
+ if (!status.jira.projectExists) {
679
+ blockers.push(`Projeto Jira "${defaultKey}" não encontrado na nuvem.`);
680
+ }
681
+ } catch (_) {
682
+ blockers.push('Falha na comunicação de rede com o Jira Cloud.');
683
+ }
684
+
685
+ try {
686
+ const confRes = await requestAtlassian(creds, 'GET', `/wiki/rest/api/space/${defaultKey}`);
687
+ status.confluence.spaceExists = (confRes.status === 200);
688
+ if (!status.confluence.spaceExists) {
689
+ blockers.push(`Espaço Confluence "${defaultKey}" não encontrado na nuvem.`);
690
+ } else {
691
+ const hpId = confRes.body?.homepage?.id || confRes.body?.homepageId;
692
+ status.confluence.homepageReady = Boolean(hpId);
693
+ }
694
+ } catch (_) {
695
+ blockers.push('Falha na comunicação de rede com o Confluence Cloud.');
696
+ }
697
+ }
698
+
699
+ const { canDevelop, verdict, gateMessage } = buildTriadVerdict(blockers, options);
700
+
701
+ return {
702
+ canDevelop,
703
+ verdict,
704
+ gateMessage,
705
+ blockers,
706
+ status,
707
+ projectContext
708
+ };
709
+ }
710
+
711
+ /**
712
+ * 9. Formata relatório executivo em Markdown do onboarding.
713
+ * @param {object} report
714
+ * @returns {string}
715
+ */
716
+ function formatReport(report) {
717
+ if (!report || typeof report !== 'object') {
718
+ return '# 🏛️ Agent-Master DevSecOps — Smart Onboarding Report\n\nNenhum relatório de onboarding disponível.';
719
+ }
720
+
721
+ let output = '# 🏛️ Agent-Master DevSecOps — Smart Onboarding Report\n\n';
722
+ output += `**Modo Detectado (Regra 16):** \`${report.mode || 'UNKNOWN'}\`\n`;
723
+ output += `**Diretório Alvo:** \`${report.targetDir || 'N/A'}\`\n`;
724
+ if (report.projectContext) {
725
+ output += `**Projeto:** ${report.projectContext.title || 'N/A'}\n`;
726
+ output += `**Stack Mapeada:** ${(report.projectContext.stack || []).join(', ') || 'Nativa'}\n`;
727
+ }
728
+ output += `**Credenciais Configuradas:** ${report.credentialsConfigured ? 'SIM' : 'NÃO (Modo Offline / Pendente)'}\n\n`;
729
+
730
+ output += '### 🎯 Status da Tríade de Governança\n';
731
+ if (report.jira) {
732
+ output += `- **Jira Cloud:** [${report.jira.key || 'N/A'}] -> \`${report.jira.status || 'PENDING'}\`\n`;
733
+ }
734
+ if (report.confluence) {
735
+ output += `- **Confluence Cloud:** [${report.confluence.key || 'N/A'}] -> \`${report.confluence.status || 'PENDING'}\`\n`;
736
+ }
737
+ if (report.homepage) {
738
+ output += `- **Homepage de Arquitetura:** \`${report.homepage.status || 'PENDING'}\` (Zero Páginas Vazias)\n`;
739
+ }
740
+
741
+ return output;
742
+ }
743
+
744
+ /**
745
+ * 10. Exporta o status do Gate da Tríade no formato oficial OASIS SARIF v2.1.0.
746
+ * @param {string} [targetDir=process.cwd()]
747
+ * @param {object} [options={}]
748
+ * @returns {object} SARIF v2.1.0
749
+ */
750
+ function auditTriadToSarif(targetDir = process.cwd(), options = {}) {
751
+ const gateRes = verifyTriadGateSync(targetDir, options);
752
+ const findings = [];
753
+
754
+ for (const blocker of gateRes.blockers) {
755
+ let ruleId = 'REGRA-16-TRIAD-GATE';
756
+ let name = 'Violação de Gate da Tríade';
757
+ if (blocker.includes('GitHub')) {
758
+ ruleId = 'REGRA-16-GIT-AUTH';
759
+ name = 'GitHub CLI Não Autenticado';
760
+ } else if (blocker.includes('Atlassian') || blocker.includes('.env')) {
761
+ ruleId = 'REGRA-16-ATLASSIAN-CREDENTIALS';
762
+ name = 'Credenciais Atlassian Ausentes';
763
+ } else if (blocker.includes('Jira')) {
764
+ ruleId = 'REGRA-16-JIRA-PROJECT';
765
+ name = 'Projeto Jira Inexistente';
766
+ } else if (blocker.includes('Confluence')) {
767
+ ruleId = 'REGRA-16-CONFLUENCE-SPACE';
768
+ name = 'Espaço Confluence Inexistente';
769
+ }
770
+
771
+ findings.push({
772
+ ruleId,
773
+ name,
774
+ severity: 'HIGH',
775
+ message: blocker,
776
+ filename: path.join(gateRes.projectContext?.name || 'repo', '.env').replace(/\\/g, '/'),
777
+ line: 1,
778
+ column: 1
779
+ });
780
+ }
781
+
782
+ const rules = [
783
+ {
784
+ id: 'REGRA-16-GIT-AUTH',
785
+ name: 'GitHub CLI Não Autenticado',
786
+ severity: 'HIGH',
787
+ description: 'A Regra 16 exige rastreabilidade no Git antes de liberar o desenvolvimento de software.',
788
+ remediation: 'Execute "gh auth login" no terminal para autenticar sua conta.'
789
+ },
790
+ {
791
+ id: 'REGRA-16-ATLASSIAN-CREDENTIALS',
792
+ name: 'Credenciais Atlassian Ausentes',
793
+ severity: 'HIGH',
794
+ description: 'A Regra 16 exige sincronização viva com Jira e Confluence.',
795
+ remediation: 'Configure ATLASSIAN_DOMAIN, ATLASSIAN_EMAIL e ATLASSIAN_API_TOKEN no arquivo .env.'
796
+ },
797
+ {
798
+ id: 'REGRA-16-JIRA-PROJECT',
799
+ name: 'Projeto Jira Inexistente',
800
+ severity: 'HIGH',
801
+ description: 'O projeto Jira deve existir e estar vinculado para emissão de tickets.',
802
+ remediation: 'Provisione o projeto executando o comando agent-master-init ou crie-o no Jira.'
803
+ },
804
+ {
805
+ id: 'REGRA-16-CONFLUENCE-SPACE',
806
+ name: 'Espaço Confluence Inexistente',
807
+ severity: 'HIGH',
808
+ description: 'O espaço Confluence deve hospedar a documentação viva e o portal executivo.',
809
+ remediation: 'Provisione o espaço com homepage executiva via agent-onboard.'
810
+ }
811
+ ];
812
+
813
+ return createSarifReport({
814
+ toolName: 'agent-master-smart-onboarding',
815
+ toolVersion: '5.1.0',
816
+ rules,
817
+ findings,
818
+ properties: {
819
+ category: 'Governance Triad Gate',
820
+ verdict: gateRes.verdict,
821
+ canDevelop: gateRes.canDevelop
822
+ }
823
+ });
824
+ }
825
+
826
+ /**
827
+ * Classe Canônica SmartOnboarding
828
+ */
829
+ class SmartOnboarding {
830
+ static get IGNORED_DIRS() {
831
+ return IGNORED_DIRS;
832
+ }
833
+
834
+ static get BUILD_MANIFESTS() {
835
+ return BUILD_MANIFESTS;
836
+ }
837
+
838
+ static get STACK_SIGNATURES() {
839
+ return STACK_SIGNATURES;
840
+ }
841
+
842
+ constructor(options = {}) {
843
+ const safeOptions = (options && typeof options === 'object') ? options : {};
844
+ this.targetDir = path.resolve(safeOptions.targetDir || process.cwd());
845
+ this.options = Object.freeze({ ...safeOptions });
846
+ }
847
+
848
+ detectMode(targetDir = this.targetDir) {
849
+ return detectProjectMode(targetDir);
850
+ }
851
+
852
+ inspectContext(targetDir = this.targetDir) {
853
+ return inspectProjectContext(targetDir);
854
+ }
855
+
856
+ async runOnboarding(targetDir = this.targetDir, options = this.options) {
857
+ return runSmartOnboarding(targetDir, options);
858
+ }
859
+
860
+ verifyTriadGateSync(targetDir = this.targetDir, options = this.options) {
861
+ return verifyTriadGateSync(targetDir, options);
862
+ }
863
+
864
+ async verifyTriadGate(targetDir = this.targetDir, options = this.options) {
865
+ return verifyTriadGate(targetDir, options);
866
+ }
867
+
868
+ auditTriadToSarif(targetDir = this.targetDir, options = this.options) {
869
+ return auditTriadToSarif(targetDir, options);
870
+ }
871
+
872
+ formatReport(report) {
873
+ return formatReport(report);
874
+ }
875
+ }
876
+
877
+ if (require.main === module) {
878
+ console.log('\n================================================================');
879
+ console.log('⚡ AGENT MASTER DEVSECOPS — ONBOARDING BIMODAL & AUTO-PROVISIONING');
880
+ console.log('================================================================\n');
881
+
882
+ runSmartOnboarding().then(report => {
883
+ console.log(formatReport(report));
884
+ }).catch(err => {
885
+ console.error('Erro no onboarding:', err);
886
+ process.exit(1);
887
+ });
888
+ }
889
+
890
+ module.exports = {
891
+ SmartOnboarding,
892
+ verifyTriadGate,
893
+ verifyTriadGateSync,
894
+ detectProjectMode,
895
+ inspectProjectContext,
896
+ ensureJiraProject,
897
+ ensureConfluenceSpace,
898
+ publishExecutiveHomepage,
899
+ runSmartOnboarding,
900
+ formatReport,
901
+ auditTriadToSarif,
902
+ IGNORED_DIRS,
903
+ BUILD_MANIFESTS,
904
+ STACK_SIGNATURES
905
+ };