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.
- package/README.md +205 -371
- package/agents/AGENT_DEVSECOPS.md +32 -417
- package/agents/AGENT_INFRA.md +31 -0
- package/agents/AGENT_ORCHESTRATOR.md +39 -264
- package/agents/AGENT_QA.md +58 -186
- package/agents/AGENT_SCRUM.md +23 -190
- package/agents/AGENT_SRE.md +22 -173
- package/agents/AGENT_UIUX.md +26 -162
- package/docs/ARCHITECTURE.md +152 -148
- package/docs/CALCULATIONS.md +17 -17
- package/docs/CHANGELOG.md +112 -49
- package/docs/ENGINEERING_RULES.md +56 -252
- package/docs/IDEAS.md +17 -17
- package/docs/LOCAL_BACKLOG.md +20 -0
- package/docs/STATE.md +111 -43
- package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -58
- package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -58
- package/package.json +59 -47
- package/scripts/auditors/a11y_auditor.js +462 -0
- package/scripts/auditors/code_quality_auditor.js +444 -0
- package/scripts/auditors/contract_test_validator.js +396 -0
- package/scripts/auditors/eval_harness.js +213 -0
- package/scripts/auditors/git_hygiene_linter.js +391 -0
- package/scripts/auditors/pr_reviewer.js +627 -0
- package/scripts/auditors/sast_scanner.js +442 -0
- package/scripts/auditors/sca_dependency_auditor.js +673 -0
- package/scripts/{bootstrap_cli.js → bin/bootstrap_cli.js} +597 -468
- package/scripts/bin/domain_idea_miner.js +658 -0
- package/scripts/bin/smart_onboarding.js +905 -0
- package/scripts/{web_inspector.js → bin/web_inspector.js} +578 -355
- package/scripts/core/architecture_advisor.js +378 -0
- package/scripts/core/ast_skeletonizer.js +413 -0
- package/scripts/core/autonomous_remediation_engine.js +305 -0
- package/scripts/{circuit_breaker.js → core/circuit_breaker.js} +546 -330
- package/scripts/core/context_compressor.js +237 -0
- package/scripts/core/fast_feature_scaffold.js +840 -0
- package/scripts/core/hot_state_cache.js +238 -0
- package/scripts/core/knowledge_gateway.js +888 -0
- package/scripts/{magic_prompt_generator.js → core/magic_prompt_generator.js} +250 -205
- package/scripts/{multi_agent_orchestrator.js → core/multi_agent_orchestrator.js} +421 -258
- package/scripts/{otel_tracer.js → core/otel_tracer.js} +335 -228
- package/scripts/{perf_benchmark.js → core/perf_benchmark.js} +212 -153
- package/scripts/core/sarif_engine.js +299 -0
- package/scripts/{sre_remediation_engine.js → core/sre_remediation_engine.js} +236 -174
- package/scripts/{universal_packager.js → core/universal_packager.js} +357 -313
- package/scripts/integrations/atlassian_client.js +600 -0
- package/scripts/integrations/atlassian_sync_action.js +576 -0
- package/scripts/integrations/confluence_sync.js +684 -0
- package/scripts/integrations/credentials_guard.js +500 -0
- package/scripts/integrations/jira_manager.js +722 -0
- package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
- package/scripts/mcp/handlers/core_handlers.js +419 -0
- package/scripts/mcp/handlers/qa_handlers.js +502 -0
- package/scripts/mcp/handlers/rules_handlers.js +379 -0
- package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
- package/scripts/mcp/handlers/sre_handlers.js +443 -0
- package/scripts/mcp/resources_registry.js +453 -0
- package/scripts/mcp/server.js +408 -0
- package/scripts/mcp/tool_handlers.js +444 -0
- package/scripts/mcp/tools_registry.js +1123 -0
- package/scripts/mcp_server.js +236 -1884
- package/.cursorignore +0 -23
- package/agents/compact/AGENT_ORCHESTRATOR_COMPACTO.md +0 -53
- package/scripts/a11y_auditor.js +0 -187
- package/scripts/ast_skeletonizer.js +0 -220
- package/scripts/atlassian_client.js +0 -238
- package/scripts/atlassian_sync_action.js +0 -269
- package/scripts/code_quality_auditor.js +0 -142
- package/scripts/confluence_sync.js +0 -337
- package/scripts/context_compressor.js +0 -139
- package/scripts/contract_test_validator.js +0 -166
- package/scripts/credentials_guard.js +0 -216
- package/scripts/domain_idea_miner.js +0 -364
- package/scripts/eval_harness.js +0 -100
- package/scripts/fast_feature_scaffold.js +0 -338
- package/scripts/git_hygiene_linter.js +0 -186
- package/scripts/hot_state_cache.js +0 -95
- package/scripts/jira_manager.js +0 -408
- package/scripts/pr_reviewer.js +0 -373
- package/scripts/sast_scanner.js +0 -180
- package/scripts/sca_dependency_auditor.js +0 -167
- 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
|
+
};
|