agent-master-devsecops 3.9.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +205 -371
  2. package/agents/AGENT_DEVSECOPS.md +32 -417
  3. package/agents/AGENT_INFRA.md +31 -0
  4. package/agents/AGENT_ORCHESTRATOR.md +39 -264
  5. package/agents/AGENT_QA.md +58 -186
  6. package/agents/AGENT_SCRUM.md +23 -190
  7. package/agents/AGENT_SRE.md +22 -173
  8. package/agents/AGENT_UIUX.md +26 -162
  9. package/docs/ARCHITECTURE.md +152 -148
  10. package/docs/CALCULATIONS.md +17 -17
  11. package/docs/CHANGELOG.md +112 -49
  12. package/docs/ENGINEERING_RULES.md +56 -252
  13. package/docs/IDEAS.md +17 -17
  14. package/docs/LOCAL_BACKLOG.md +20 -0
  15. package/docs/STATE.md +111 -43
  16. package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -58
  17. package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -58
  18. package/package.json +59 -47
  19. package/scripts/auditors/a11y_auditor.js +462 -0
  20. package/scripts/auditors/code_quality_auditor.js +444 -0
  21. package/scripts/auditors/contract_test_validator.js +396 -0
  22. package/scripts/auditors/eval_harness.js +213 -0
  23. package/scripts/auditors/git_hygiene_linter.js +391 -0
  24. package/scripts/auditors/pr_reviewer.js +627 -0
  25. package/scripts/auditors/sast_scanner.js +442 -0
  26. package/scripts/auditors/sca_dependency_auditor.js +673 -0
  27. package/scripts/{bootstrap_cli.js → bin/bootstrap_cli.js} +597 -468
  28. package/scripts/bin/domain_idea_miner.js +658 -0
  29. package/scripts/bin/smart_onboarding.js +905 -0
  30. package/scripts/{web_inspector.js → bin/web_inspector.js} +578 -355
  31. package/scripts/core/architecture_advisor.js +378 -0
  32. package/scripts/core/ast_skeletonizer.js +413 -0
  33. package/scripts/core/autonomous_remediation_engine.js +305 -0
  34. package/scripts/{circuit_breaker.js → core/circuit_breaker.js} +546 -330
  35. package/scripts/core/context_compressor.js +237 -0
  36. package/scripts/core/fast_feature_scaffold.js +840 -0
  37. package/scripts/core/hot_state_cache.js +238 -0
  38. package/scripts/core/knowledge_gateway.js +888 -0
  39. package/scripts/{magic_prompt_generator.js → core/magic_prompt_generator.js} +250 -205
  40. package/scripts/{multi_agent_orchestrator.js → core/multi_agent_orchestrator.js} +421 -258
  41. package/scripts/{otel_tracer.js → core/otel_tracer.js} +335 -228
  42. package/scripts/{perf_benchmark.js → core/perf_benchmark.js} +212 -153
  43. package/scripts/core/sarif_engine.js +299 -0
  44. package/scripts/{sre_remediation_engine.js → core/sre_remediation_engine.js} +236 -174
  45. package/scripts/{universal_packager.js → core/universal_packager.js} +357 -313
  46. package/scripts/integrations/atlassian_client.js +600 -0
  47. package/scripts/integrations/atlassian_sync_action.js +576 -0
  48. package/scripts/integrations/confluence_sync.js +684 -0
  49. package/scripts/integrations/credentials_guard.js +500 -0
  50. package/scripts/integrations/jira_manager.js +722 -0
  51. package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
  52. package/scripts/mcp/handlers/core_handlers.js +419 -0
  53. package/scripts/mcp/handlers/qa_handlers.js +502 -0
  54. package/scripts/mcp/handlers/rules_handlers.js +379 -0
  55. package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
  56. package/scripts/mcp/handlers/sre_handlers.js +443 -0
  57. package/scripts/mcp/resources_registry.js +453 -0
  58. package/scripts/mcp/server.js +408 -0
  59. package/scripts/mcp/tool_handlers.js +444 -0
  60. package/scripts/mcp/tools_registry.js +1123 -0
  61. package/scripts/mcp_server.js +236 -1884
  62. package/.cursorignore +0 -23
  63. package/agents/compact/AGENT_ORCHESTRATOR_COMPACTO.md +0 -53
  64. package/scripts/a11y_auditor.js +0 -187
  65. package/scripts/ast_skeletonizer.js +0 -220
  66. package/scripts/atlassian_client.js +0 -238
  67. package/scripts/atlassian_sync_action.js +0 -269
  68. package/scripts/code_quality_auditor.js +0 -142
  69. package/scripts/confluence_sync.js +0 -337
  70. package/scripts/context_compressor.js +0 -139
  71. package/scripts/contract_test_validator.js +0 -166
  72. package/scripts/credentials_guard.js +0 -216
  73. package/scripts/domain_idea_miner.js +0 -364
  74. package/scripts/eval_harness.js +0 -100
  75. package/scripts/fast_feature_scaffold.js +0 -338
  76. package/scripts/git_hygiene_linter.js +0 -186
  77. package/scripts/hot_state_cache.js +0 -95
  78. package/scripts/jira_manager.js +0 -408
  79. package/scripts/pr_reviewer.js +0 -373
  80. package/scripts/sast_scanner.js +0 -180
  81. package/scripts/sca_dependency_auditor.js +0 -167
  82. package/scripts/smart_onboarding.js +0 -610
@@ -1,238 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scripts/atlassian_client.js
4
- *
5
- * Cliente Unificado e Utilitários de Comunicação com APIs REST Atlassian (Jira e Confluence).
6
- * Centraliza autenticação, sanitização de domínio, introspecção de git e execução de requisições HTTPS.
7
- *
8
- * Governança: Tríade Git + Jira + Confluence (Regra 11 e Regra 15).
9
- */
10
-
11
- const fs = require('node:fs');
12
- const path = require('node:path');
13
- const https = require('node:https');
14
- const { execSync, execFileSync } = require('node:child_process');
15
-
16
- function sanitizeDomain(domainStr) {
17
- if (!domainStr) return null;
18
- return domainStr.replace(/^https?:\/\//, '').replace(/\/+$/, '');
19
- }
20
-
21
- function resolveMcpConfig() {
22
- const homeDir = process.env.USERPROFILE || process.env.HOME || '';
23
- const candidatePaths = [
24
- path.join(homeDir, '.gemini', 'config', 'mcp_config.json'),
25
- path.join(homeDir, '.antigravity', 'config', 'mcp_config.json'),
26
- path.resolve(process.cwd(), '.mcp_config.json')
27
- ];
28
-
29
- for (const p of candidatePaths) {
30
- if (fs.existsSync(p)) {
31
- try {
32
- return JSON.parse(fs.readFileSync(p, 'utf8'));
33
- } catch (_) {}
34
- }
35
- }
36
- return null;
37
- }
38
-
39
- function getAtlassianCredentials(targetDir = process.cwd()) {
40
- let token = process.env.ATLASSIAN_API_TOKEN || process.env.JIRA_API_TOKEN || process.env.CONFLUENCE_API_TOKEN || process.env.PAT;
41
- let email = process.env.ATLASSIAN_EMAIL || process.env.JIRA_EMAIL || process.env.CONFLUENCE_EMAIL || process.env.CONFLUENCE_USERNAME;
42
- let rawDomain = process.env.ATLASSIAN_DOMAIN || process.env.JIRA_DOMAIN || process.env.CONFLUENCE_DOMAIN || process.env.JIRA_BASE_URL || process.env.CONFLUENCE_BASE_URL;
43
- let source = (token && email && rawDomain) ? 'environment_variables' : null;
44
-
45
- if (!process.env.IGNORE_LOCAL_ENV && (!token || !email || !rawDomain)) {
46
- const isLocalWorkspace = path.resolve(targetDir) === path.resolve(process.cwd());
47
- const candidateFiles = isLocalWorkspace
48
- ? [
49
- path.resolve(targetDir, '.env'),
50
- path.resolve(targetDir, '..', '.env'),
51
- path.resolve(process.cwd(), '.env'),
52
- path.resolve(process.cwd(), '.env.local')
53
- ]
54
- : [
55
- path.resolve(targetDir, '.env'),
56
- path.resolve(targetDir, '.env.local')
57
- ];
58
-
59
- for (const envPath of candidateFiles) {
60
- if (fs.existsSync(envPath)) {
61
- try {
62
- const raw = fs.readFileSync(envPath, 'utf8');
63
- const dMatch = raw.match(/(?:ATLASSIAN_DOMAIN|JIRA_DOMAIN|CONFLUENCE_DOMAIN)=(.*)/);
64
- const eMatch = raw.match(/(?:ATLASSIAN_EMAIL|JIRA_EMAIL|CONFLUENCE_EMAIL)=(.*)/);
65
- const tMatch = raw.match(/(?:ATLASSIAN_API_TOKEN|JIRA_API_TOKEN|CONFLUENCE_API_TOKEN)=(.*)/);
66
- if (dMatch && dMatch[1] && !rawDomain) rawDomain = dMatch[1].trim();
67
- if (eMatch && eMatch[1] && !email) email = eMatch[1].trim();
68
- if (tMatch && tMatch[1] && !token) token = tMatch[1].trim();
69
- if (rawDomain && email && token) {
70
- source = source || `local_file:${path.basename(envPath)}`;
71
- break;
72
- }
73
- } catch (_) {}
74
- }
75
- }
76
- }
77
-
78
- const isLocalTarget = path.resolve(targetDir) === path.resolve(process.cwd());
79
- if ((!token || !email || !rawDomain) && isLocalTarget) {
80
- const mcp = resolveMcpConfig();
81
- if (mcp && mcp.mcpServers) {
82
- if (mcp.mcpServers.jira && mcp.mcpServers.jira.env) {
83
- token = token || mcp.mcpServers.jira.env.JIRA_API_TOKEN;
84
- email = email || mcp.mcpServers.jira.env.JIRA_EMAIL;
85
- rawDomain = rawDomain || mcp.mcpServers.jira.env.JIRA_BASE_URL;
86
- if (token && email && rawDomain) source = source || 'mcp_config:jira';
87
- }
88
- if (mcp.mcpServers.confluence && mcp.mcpServers.confluence.env) {
89
- token = token || mcp.mcpServers.confluence.env.PAT;
90
- if (token) source = source || 'mcp_config:confluence';
91
- }
92
- }
93
- }
94
-
95
- const cleanDomain = sanitizeDomain(rawDomain);
96
- const isConfigured = Boolean(token && email && cleanDomain);
97
- const auth = isConfigured ? Buffer.from(`${email}:${token}`).toString('base64') : null;
98
-
99
- return {
100
- domain: cleanDomain,
101
- email: email || null,
102
- token: token || null,
103
- auth,
104
- authBase64: auth,
105
- isConfigured,
106
- source
107
- };
108
- }
109
-
110
- function checkGithubAuth(options = {}) {
111
- const { globalStateCache } = require('./hot_state_cache.js');
112
- const cacheKey = `gh_auth_${process.env.GITHUB_TOKEN || ''}_${process.env.GH_TOKEN || ''}`;
113
- if (options && options.forceFresh) {
114
- globalStateCache.invalidate(cacheKey);
115
- }
116
-
117
- return globalStateCache.wrap(cacheKey, () => {
118
- let token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
119
- let isAuthenticated = false;
120
- let user = null;
121
- let details = '';
122
-
123
- try {
124
- const stdout = execFileSync('gh', ['auth', 'status'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
125
- isAuthenticated = true;
126
- details = stdout.trim();
127
- const userMatch = stdout.match(/Logged in to [^\s]+ account ([^\s\(\)]+)/i) || stdout.match(/account ([^\s\(\)]+)/i);
128
- if (userMatch) user = userMatch[1];
129
- } catch (err) {
130
- const output = (err.stdout || '') + (err.stderr || '');
131
- if (/Logged in to/i.test(output)) {
132
- isAuthenticated = true;
133
- details = output.trim();
134
- const userMatch = output.match(/account ([^\s\(\)]+)/i);
135
- if (userMatch) user = userMatch[1];
136
- } else if (token) {
137
- isAuthenticated = true;
138
- user = 'token-authenticated';
139
- details = 'Autenticado via GITHUB_TOKEN.';
140
- } else {
141
- isAuthenticated = false;
142
- details = output.trim() || 'Usuário não autenticado no GitHub CLI.';
143
- }
144
- }
145
-
146
- return {
147
- installed: true,
148
- authenticated: isAuthenticated,
149
- isAuthenticated,
150
- token: token || null,
151
- user,
152
- details
153
- };
154
- }, 10000); // 10s TTL
155
- }
156
-
157
- function getGitRepositoryUrl() {
158
- if (process.env.GITHUB_REPOSITORY) {
159
- return `https://github.com/${process.env.GITHUB_REPOSITORY}`;
160
- }
161
-
162
- try {
163
- const rawRemote = execFileSync('git', ['config', '--get', 'remote.origin.url'], { encoding: 'utf8' }).trim();
164
- if (rawRemote.startsWith('git@github.com:')) {
165
- return `https://github.com/${rawRemote.replace('git@github.com:', '').replace(/\.git$/, '')}`;
166
- } else if (rawRemote.startsWith('https://github.com/')) {
167
- return rawRemote.replace(/\.git$/, '');
168
- }
169
- } catch (_) {}
170
-
171
- return 'https://github.com/ContalApps/agent-master-devsecops';
172
- }
173
-
174
- function getGitMetadata(options = {}) {
175
- const { globalStateCache } = require('./hot_state_cache.js');
176
- if (options && options.forceFresh) {
177
- globalStateCache.invalidate('git_metadata');
178
- }
179
-
180
- return globalStateCache.wrap('git_metadata', () => {
181
- let sha = '';
182
- let branch = '';
183
- let message = '';
184
-
185
- try {
186
- sha = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
187
- branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8' }).trim();
188
- message = execFileSync('git', ['log', '-1', '--format=%s', sha], { encoding: 'utf8' }).trim();
189
- } catch (_) {}
190
-
191
- return {
192
- sha,
193
- branch,
194
- message,
195
- repoUrl: getGitRepositoryUrl()
196
- };
197
- }, 5000); // 5s TTL
198
- }
199
-
200
- async function requestAtlassian(credentials, method, endpointPath, bodyData = null) {
201
- return new Promise((resolve, reject) => {
202
- const postData = bodyData ? JSON.stringify(bodyData) : null;
203
- const req = https.request({
204
- hostname: credentials.domain,
205
- path: endpointPath,
206
- method,
207
- headers: {
208
- 'Authorization': `Basic ${credentials.auth || credentials.authBase64}`,
209
- 'Content-Type': 'application/json',
210
- 'Accept': 'application/json',
211
- ...(postData ? { 'Content-Length': Buffer.byteLength(postData) } : {})
212
- }
213
- }, res => {
214
- let data = '';
215
- res.on('data', chunk => { data += chunk; });
216
- res.on('end', () => {
217
- try {
218
- resolve({ status: res.statusCode, body: data ? JSON.parse(data) : {} });
219
- } catch (_) {
220
- resolve({ status: res.statusCode, body: data });
221
- }
222
- });
223
- });
224
-
225
- req.on('error', reject);
226
- if (postData) req.write(postData);
227
- req.end();
228
- });
229
- }
230
-
231
- module.exports = {
232
- sanitizeDomain,
233
- getAtlassianCredentials,
234
- checkGithubAuth,
235
- getGitRepositoryUrl,
236
- getGitMetadata,
237
- requestAtlassian
238
- };
@@ -1,269 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scripts/atlassian_sync_action.js
4
- *
5
- * Sincronizador Automático Git ↔ Atlassian via Actions & Local (AGDEV-23 / AGDEV-35).
6
- * Conecta automaticamente commits, branches e Pull Requests aos cards do Jira
7
- * utilizando a API oficial de Remote Links do Jira Cloud (REST API v3).
8
- *
9
- * Blindado pela Regra 15: Imutabilidade de Requisitos & Zero Falhas Silenciosas.
10
- */
11
-
12
- const { execFileSync } = require('child_process');
13
- const path = require('path');
14
- const {
15
- getAtlassianConfig,
16
- getGitMetadata,
17
- getGitRepositoryUrl,
18
- validateRule15
19
- } = require('./credentials_guard');
20
-
21
- function getAuth() {
22
- const cfg = getAtlassianConfig();
23
- return cfg.authBase64;
24
- }
25
-
26
- function extractIssueKeys(text) {
27
- if (!text) return [];
28
- const matches = text.match(/\b[A-Z]{2,10}-\d+\b/g) || [];
29
- return [...new Set(matches.map(k => k.toUpperCase()))];
30
- }
31
-
32
- function extractPrNumber(text) {
33
- if (!text) return null;
34
- const match = text.match(/#(\d+)\b/) || text.match(/pull request #(\d+)/i);
35
- return match ? match[1] : null;
36
- }
37
-
38
- function formatRemoteLinkPayload(url, title, type = 'commit', globalId = null, summary = null) {
39
- let iconUrl = 'https://github.githubassets.com/favicons/favicon.png';
40
- let relationship = 'causes';
41
-
42
- if (type === 'pr') {
43
- relationship = 'relates to';
44
- } else if (type === 'branch') {
45
- relationship = 'builds';
46
- }
47
-
48
- const payload = {
49
- relationship,
50
- object: {
51
- url,
52
- title,
53
- icon: {
54
- url16x16: iconUrl,
55
- title: `GitHub ${type.toUpperCase()}`
56
- }
57
- }
58
- };
59
-
60
- if (globalId) {
61
- payload.globalId = globalId;
62
- }
63
- if (summary) {
64
- payload.object.summary = summary;
65
- }
66
-
67
- return payload;
68
- }
69
-
70
- async function linkUrlToJira(issueKey, url, title, type = 'commit', authOverride = null, globalId = null, summary = null) {
71
- const atlassian = getAtlassianConfig();
72
- const auth = authOverride || atlassian.authBase64;
73
- if (!auth) {
74
- return { status: 'FAILED', message: 'Credenciais Atlassian não configuradas no ambiente (Regra 15).' };
75
- }
76
-
77
- const cleanDomain = atlassian.domain;
78
- if (!cleanDomain) {
79
- return { status: 'FAILED', message: 'Domínio Atlassian não configurado para este projeto (Regra 15).' };
80
- }
81
- const payload = formatRemoteLinkPayload(url, title, type, globalId, summary);
82
- const endpoint = `https://${cleanDomain}/rest/api/3/issue/${issueKey}/remotelink`;
83
-
84
- try {
85
- const res = await fetch(endpoint, {
86
- method: 'POST',
87
- headers: {
88
- 'Authorization': `Basic ${auth}`,
89
- 'Content-Type': 'application/json',
90
- 'Accept': 'application/json'
91
- },
92
- body: JSON.stringify(payload)
93
- });
94
-
95
- if (res.ok || res.status === 201 || res.status === 200) {
96
- return { status: 'SUCCESS', issueKey, url, type, httpStatus: res.status };
97
- } else {
98
- const errText = await res.text();
99
- return { status: 'FAILED', issueKey, error: `HTTP ${res.status}: ${errText}` };
100
- }
101
- } catch (err) {
102
- return { status: 'ERROR', issueKey, error: err.message };
103
- }
104
- }
105
-
106
- async function backfillHistory(limit = 30) {
107
- console.log(`[Atlassian Sync] Iniciando sincronização retroativa dos últimos ${limit} commits...`);
108
- const atlassian = getAtlassianConfig();
109
- if (!atlassian.isConfigured) {
110
- throw new Error('Credenciais Atlassian ausentes para sincronização retroativa.');
111
- }
112
-
113
- const repoUrl = getGitRepositoryUrl();
114
- const repoSlug = repoUrl.replace('https://github.com/', '');
115
-
116
- let rawLog = '';
117
- try {
118
- rawLog = execFileSync('git', ['log', `-${limit}`, '--format=%H|%s'], { encoding: 'utf8' });
119
- } catch (err) {
120
- console.error('[Atlassian Sync] Falha ao extrair histórico git:', err.message);
121
- return;
122
- }
123
-
124
- const lines = rawLog.split('\n').filter(l => l.trim().length > 0);
125
- const results = [];
126
-
127
- for (const line of lines) {
128
- const [sha, ...subjectParts] = line.split('|');
129
- const subject = subjectParts.join('|');
130
- const keys = extractIssueKeys(subject);
131
- const prNumber = extractPrNumber(subject);
132
-
133
- for (const key of keys) {
134
- // 1. Link Commit
135
- const commitGlobalId = `system=github&repo=${repoSlug}&type=commit&sha=${sha}`;
136
- const commitRes = await linkUrlToJira(
137
- key,
138
- `${repoUrl}/commit/${sha}`,
139
- `GitHub Commit: ${sha.slice(0, 7)} (${subject})`,
140
- 'commit',
141
- atlassian.authBase64,
142
- commitGlobalId
143
- );
144
- results.push({ key, type: 'commit', ref: sha.slice(0, 7), status: commitRes.status });
145
-
146
- // 2. Link PR if present in commit title
147
- if (prNumber) {
148
- const prGlobalId = `system=github&repo=${repoSlug}&type=pr&id=${prNumber}`;
149
- const prRes = await linkUrlToJira(
150
- key,
151
- `${repoUrl}/pull/${prNumber}`,
152
- `GitHub Pull Request #${prNumber}`,
153
- 'pr',
154
- atlassian.authBase64,
155
- prGlobalId,
156
- subject
157
- );
158
- results.push({ key, type: 'pr', ref: `#${prNumber}`, status: prRes.status });
159
- }
160
- }
161
- }
162
-
163
- const successCount = results.filter(r => r.status === 'SUCCESS').length;
164
- console.log(`[Atlassian Sync] Sincronização retroativa concluída: ${successCount}/${results.length} links processados com sucesso.`);
165
- return results;
166
- }
167
-
168
- async function runCli() {
169
- const isCI = process.env.GITHUB_ACTIONS === 'true';
170
-
171
- // Validação Compulsória de Requisitos (Regra 15)
172
- // Em CI, a ausência de secrets agora é ERRO CRÍTICO para impedir perda silenciosa
173
- const validation = validateRule15({ failFast: isCI, requireGitHub: false, requireAtlassian: true });
174
- if (!validation.valid) {
175
- console.error(`[Atlassian Sync] Falha de validação da Regra 15:\n ${validation.missing.join(', ')}`);
176
- console.error(` Remediação: ${validation.remediation.join('\n ')}`);
177
- if (isCI) {
178
- process.exit(1);
179
- }
180
- return;
181
- }
182
-
183
- const args = process.argv.slice(2);
184
- if (args.includes('--retroactive') || args.includes('-r')) {
185
- const limitIdx = args.findIndex(a => a === '--retroactive' || a === '-r');
186
- const limitVal = parseInt(args[limitIdx + 1], 10) || 30;
187
- await backfillHistory(limitVal);
188
- return;
189
- }
190
-
191
- const atlassian = getAtlassianConfig();
192
- const gitMeta = getGitMetadata();
193
- const repoUrl = gitMeta.repoUrl;
194
- const repoSlug = repoUrl.replace('https://github.com/', '');
195
-
196
- const sha = gitMeta.sha;
197
- const branch = gitMeta.branch;
198
- const message = gitMeta.message;
199
- const prNumber = process.env.PR_NUMBER || extractPrNumber(process.env.PR_TITLE || '') || extractPrNumber(message);
200
-
201
- const combined = `${branch} ${message} ${process.env.PR_TITLE || ''}`;
202
- const keys = extractIssueKeys(combined);
203
-
204
- if (keys.length === 0) {
205
- console.log('[Atlassian Sync] Nenhuma chave Jira encontrada na branch ou mensagem de commit.');
206
- return;
207
- }
208
-
209
- console.log(`[Atlassian Sync] Chaves Jira identificadas: ${keys.join(', ')}`);
210
-
211
- for (const key of keys) {
212
- if (sha && repoUrl) {
213
- const commitGlobalId = `system=github&repo=${repoSlug}&type=commit&sha=${sha}`;
214
- const commitRes = await linkUrlToJira(
215
- key,
216
- `${repoUrl}/commit/${sha}`,
217
- `GitHub Commit: ${sha.slice(0, 7)} (${message})`,
218
- 'commit',
219
- atlassian.authBase64,
220
- commitGlobalId
221
- );
222
- console.log(`[Atlassian Sync] Commit ${sha.slice(0, 7)} vinculado a ${key}: ${commitRes.status}`);
223
- }
224
-
225
- if (branch && branch !== 'main' && branch !== 'HEAD' && repoUrl) {
226
- const branchGlobalId = `system=github&repo=${repoSlug}&type=branch&name=${branch}`;
227
- const branchRes = await linkUrlToJira(
228
- key,
229
- `${repoUrl}/tree/${branch}`,
230
- `GitHub Branch: ${branch}`,
231
- 'branch',
232
- atlassian.authBase64,
233
- branchGlobalId
234
- );
235
- console.log(`[Atlassian Sync] Branch ${branch} vinculada a ${key}: ${branchRes.status}`);
236
- }
237
-
238
- if (prNumber && repoUrl) {
239
- const prGlobalId = `system=github&repo=${repoSlug}&type=pr&id=${prNumber}`;
240
- const prRes = await linkUrlToJira(
241
- key,
242
- `${repoUrl}/pull/${prNumber}`,
243
- `GitHub Pull Request #${prNumber}`,
244
- 'pr',
245
- atlassian.authBase64,
246
- prGlobalId,
247
- process.env.PR_TITLE || message
248
- );
249
- console.log(`[Atlassian Sync] PR #${prNumber} vinculado a ${key}: ${prRes.status}`);
250
- }
251
- }
252
- }
253
-
254
- if (require.main === module) {
255
- runCli().catch(err => {
256
- console.error('[Atlassian Sync] Erro fatal na execução:', err);
257
- process.exit(1);
258
- });
259
- }
260
-
261
- module.exports = {
262
- getAuth,
263
- extractIssueKeys,
264
- extractPrNumber,
265
- formatRemoteLinkPayload,
266
- linkUrlToJira,
267
- backfillHistory,
268
- runCli
269
- };
@@ -1,142 +0,0 @@
1
- /**
2
- * Code Quality & Cyclomatic Complexity Auditor
3
- * Auditoria independente de qualidade de software, complexidade ciclomática,
4
- * profundidade de aninhamento e detecção de anti-patterns de código.
5
- * Em conformidade com a Regra 13 (Estrutura de Código) e Matriz de Severidade.
6
- */
7
-
8
- const fs = require('node:fs');
9
- const path = require('node:path');
10
-
11
- class CodeQualityAuditor {
12
- constructor(options = {}) {
13
- this.maxComplexity = options.maxComplexity || 15;
14
- this.maxNesting = options.maxNesting || 4;
15
- this.maxFileLines = options.maxFileLines || 300;
16
- }
17
-
18
- auditCode(code, filename = 'snippet.js') {
19
- const lines = code.split('\n');
20
- const totalLines = lines.length;
21
- let commentLines = 0;
22
- let blankLines = 0;
23
- let maxNestingObserved = 0;
24
- let currentNesting = 0;
25
- const issues = [];
26
-
27
- // Contadores de decisão para complexidade ciclomática
28
- let complexityScore = 1;
29
- const decisionPatterns = [
30
- /\bif\s*\(/g,
31
- /\belse\s+if\s*\(/g,
32
- /\bfor\s*\(/g,
33
- /\bwhile\s*\(/g,
34
- /\bcatch\s*\(/g,
35
- /\bcase\s+[^:]+:/g,
36
- /&&/g,
37
- /\|\|/g,
38
- /\?[^:]+:/g
39
- ];
40
-
41
- lines.forEach((line, index) => {
42
- const lineNum = index + 1;
43
- const trimmed = line.trim();
44
-
45
- if (!trimmed) {
46
- blankLines++;
47
- return;
48
- }
49
-
50
- if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
51
- commentLines++;
52
- return;
53
- }
54
-
55
- // Análise de aninhamento de chaves
56
- for (const char of trimmed) {
57
- if (char === '{') currentNesting++;
58
- if (char === '}') currentNesting = Math.max(0, currentNesting - 1);
59
- if (currentNesting > maxNestingObserved) {
60
- maxNestingObserved = currentNesting;
61
- }
62
- }
63
-
64
- // Contagem de complexidade ciclomática
65
- for (const pattern of decisionPatterns) {
66
- const matches = trimmed.match(pattern);
67
- if (matches) {
68
- complexityScore += matches.length;
69
- }
70
- }
71
-
72
- // Detecção de empty catch block (anti-pattern de engolir exceções)
73
- if (/catch\s*\([^\)]*\)\s*\{\s*\}/.test(trimmed)) {
74
- issues.push({
75
- type: 'EMPTY_CATCH',
76
- line: lineNum,
77
- severity: 'HIGH',
78
- message: 'Bloco catch vazio detectado: erros não devem ser suprimidos silenciosamente (Regra 5).'
79
- });
80
- }
81
-
82
- // Detecção de comentários TODO ou código comentado
83
- if (/\b(?:TODO|FIXME|HACK)\b/i.test(trimmed)) {
84
- issues.push({
85
- type: 'PENDING_TODO',
86
- line: lineNum,
87
- severity: 'LOW',
88
- message: `Marcação pendente detectada: "${trimmed}". Código pronto não deve deixar TODOs (Regra 7).`
89
- });
90
- }
91
- });
92
-
93
- // Verificação de limites
94
- if (totalLines > this.maxFileLines) {
95
- issues.push({
96
- type: 'FILE_TOO_LONG',
97
- line: totalLines,
98
- severity: 'MEDIUM',
99
- message: `Arquivo possui ${totalLines} linhas (limite canônico recomendado: ${this.maxFileLines} linhas - Regra 13).`
100
- });
101
- }
102
-
103
- if (maxNestingObserved > this.maxNesting) {
104
- issues.push({
105
- type: 'DEEP_NESTING',
106
- line: 1,
107
- severity: 'MEDIUM',
108
- message: `Nível máximo de aninhamento (${maxNestingObserved}) excede o limiar tolerado (${this.maxNesting}).`
109
- });
110
- }
111
-
112
- if (complexityScore > this.maxComplexity) {
113
- issues.push({
114
- type: 'HIGH_CYCLOMATIC_COMPLEXITY',
115
- line: 1,
116
- severity: 'MEDIUM',
117
- message: `Complexidade ciclomática (${complexityScore}) excede o limite aceitável de ${this.maxComplexity}.`
118
- });
119
- }
120
-
121
- const isClean = issues.filter(i => i.severity === 'HIGH' || i.severity === 'CRITICAL').length === 0;
122
-
123
- return {
124
- filename,
125
- status: isClean ? (issues.length === 0 ? 'OPTIMAL' : 'ACCEPTABLE_WITH_NOTES') : 'REFACTOR_REQUIRED',
126
- metrics: {
127
- totalLines,
128
- codeLines: totalLines - commentLines - blankLines,
129
- commentLines,
130
- blankLines,
131
- cyclomaticComplexity: complexityScore,
132
- maxNestingDepth: maxNestingObserved
133
- },
134
- qualityScore: Math.max(0, 100 - (issues.length * 10) - (complexityScore > this.maxComplexity ? 15 : 0)),
135
- issues
136
- };
137
- }
138
- }
139
-
140
- module.exports = {
141
- CodeQualityAuditor
142
- };