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,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/core/autonomous_remediation_engine.js
|
|
3
|
+
* Motor Autônomo de Autocura DevSecOps em Ciclo OODA (Observe-Orient-Decide-Act-Verify)
|
|
4
|
+
*
|
|
5
|
+
* Executa o loop de remediação adaptativa sobre repositórios ou arquivos reais:
|
|
6
|
+
* 1. OBSERVE: Varredura SARIF v2.1.0 e checagem de suíte de testes do host.
|
|
7
|
+
* 2. ORIENT: Análise sintática e semântica do finding (CWE/CVE, linha, contexto).
|
|
8
|
+
* 3. DECIDE: Síntese de estratégia de patch cirúrgico anti-regressão.
|
|
9
|
+
* 4. ACT: Aplicação física do patch no disco com salvaguarda de rollback atômico.
|
|
10
|
+
* 5. VERIFY: Re-execução da auditoria de segurança E dos testes do projeto alvo.
|
|
11
|
+
* 6. LOOP/ROLLBACK: Re-planejamento em caso de falha ou restauração do estado original.
|
|
12
|
+
*/
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { spawnSync } = require('node:child_process');
|
|
18
|
+
const { SastScanner } = require('../auditors/sast_scanner.js');
|
|
19
|
+
const { parseSarifReport, validateSarif } = require('./sarif_engine.js');
|
|
20
|
+
|
|
21
|
+
class AutonomousRemediationEngine {
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.sastScanner = options.sastScanner || new SastScanner();
|
|
24
|
+
this.maxIterations = options.maxIterations || 3;
|
|
25
|
+
this.timeoutMs = options.timeoutMs || 30000;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Executa comandos da toolchain de forma segura e síncrona.
|
|
30
|
+
*/
|
|
31
|
+
_runCommand(cmd, args = [], cwd = process.cwd()) {
|
|
32
|
+
try {
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
return spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', cmd, ...args], {
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
timeout: this.timeoutMs,
|
|
37
|
+
cwd
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return spawnSync(cmd, args, {
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
timeout: this.timeoutMs,
|
|
43
|
+
cwd
|
|
44
|
+
});
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return { status: -1, stdout: '', stderr: err.message, error: err };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
runProjectTests(testCommand, cwd) {
|
|
51
|
+
if (!testCommand) return { passed: true, output: 'Nenhum comando de teste fornecido (verificação ignorada).', exitCode: 0 };
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const cleanEnv = { ...process.env };
|
|
55
|
+
delete cleanEnv.NODE_TEST_CONTEXT;
|
|
56
|
+
|
|
57
|
+
const res = spawnSync(testCommand, {
|
|
58
|
+
shell: true,
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
cwd: cwd || process.cwd(),
|
|
61
|
+
timeout: this.timeoutMs,
|
|
62
|
+
env: cleanEnv
|
|
63
|
+
});
|
|
64
|
+
const passed = res.status === 0;
|
|
65
|
+
const output = (res.stdout || '') + (res.stderr || '');
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
passed,
|
|
69
|
+
output: output.trim(),
|
|
70
|
+
exitCode: res.status
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
passed: false,
|
|
75
|
+
output: err.message,
|
|
76
|
+
exitCode: -1
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Sintetiza uma correção cirúrgica baseada na taxonomia da vulnerabilidade.
|
|
83
|
+
* @param {object} finding Finding normalizado (SARIF ou scanCode)
|
|
84
|
+
* @param {string} currentContent Conteúdo atual do arquivo
|
|
85
|
+
* @returns {{ patchedContent: string, strategy: string } | null}
|
|
86
|
+
*/
|
|
87
|
+
synthesizePatch(finding, currentContent) {
|
|
88
|
+
const lines = currentContent.split('\n');
|
|
89
|
+
const targetLineIdx = Math.max(0, (finding.line || 1) - 1);
|
|
90
|
+
const targetLine = lines[targetLineIdx] || '';
|
|
91
|
+
|
|
92
|
+
// Estratégia 1: Command Injection (CWE-78)
|
|
93
|
+
// Converte exec(`...${var}`) ou exec(cmd) em spawnSync seguro ou execFile seguro
|
|
94
|
+
if (finding.cwe === 'CWE-78' || finding.ruleId?.includes('CWE-78')) {
|
|
95
|
+
if (/child_process\s*\.\s*exec\s*\(|(?<![\w$.])exec\s*\(/.test(targetLine)) {
|
|
96
|
+
let patchedLine = targetLine;
|
|
97
|
+
if (/(?:child_process\s*\.\s*)?exec\s*\(\s*`\s*([a-zA-Z0-9_\-\.]+)\s+\$\{([^}]+)\}`(?:\s*,\s*([\s\S]+?))?\s*\)/.test(targetLine)) {
|
|
98
|
+
patchedLine = targetLine.replace(
|
|
99
|
+
/(?:child_process\s*\.\s*)?exec\s*\(\s*`\s*([a-zA-Z0-9_\-\.]+)\s+\$\{([^}]+)\}`(?:\s*,\s*([\s\S]+?))?\s*\)/,
|
|
100
|
+
(m, bin, param, cb) => cb ? `execFile('${bin}', [${param}], ${cb})` : `execFile('${bin}', [${param}])`
|
|
101
|
+
);
|
|
102
|
+
} else if (/(?:child_process\s*\.\s*)?exec\s*\(([^,)]+)(?:\s*,\s*([\s\S]+?))?\s*\)/.test(targetLine)) {
|
|
103
|
+
patchedLine = targetLine.replace(
|
|
104
|
+
/(?:child_process\s*\.\s*)?exec\s*\(([^,)]+)(?:\s*,\s*([\s\S]+?))?\s*\)/,
|
|
105
|
+
(m, cmd, cb) => cb ? `execFile('sh', ['-c', ${cmd}], ${cb})` : `execFile('sh', ['-c', ${cmd}])`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Garante que execFile esteja importado se child_process estiver na página
|
|
110
|
+
lines[targetLineIdx] = patchedLine;
|
|
111
|
+
let newContent = lines.join('\n');
|
|
112
|
+
if (!newContent.includes('execFile') && newContent.includes("require('child_process')")) {
|
|
113
|
+
newContent = newContent.replace("require('child_process')", "require('child_process'); const { execFile } = require('child_process')");
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
patchedContent: newContent,
|
|
117
|
+
strategy: 'Substituição de invocação de shell por execFile() com argumentos em array sanitizados contra CWE-78'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Estratégia 2: SQL Injection (CWE-89)
|
|
123
|
+
// Converte query concatenada para query parametrizada
|
|
124
|
+
if (finding.cwe === 'CWE-89' || finding.ruleId?.includes('CWE-89')) {
|
|
125
|
+
if (/SELECT|INSERT|UPDATE|DELETE/i.test(targetLine) && /\+|\$\{/.test(targetLine)) {
|
|
126
|
+
let patchedLine = targetLine;
|
|
127
|
+
// Ex: `SELECT * FROM users WHERE id = ${userId}` -> 'SELECT * FROM users WHERE id = ?', [userId]
|
|
128
|
+
if (/`\s*(SELECT\b[\s\S]*?WHERE\s+[a-zA-Z0-9_]+\s*=)\s*\$\{([^}]+)\}`/.test(targetLine)) {
|
|
129
|
+
patchedLine = targetLine.replace(
|
|
130
|
+
/`\s*(SELECT\b[\s\S]*?WHERE\s+[a-zA-Z0-9_]+\s*=)\s*\$\{([^}]+)\}`/,
|
|
131
|
+
"'$1 ?', [$2]"
|
|
132
|
+
);
|
|
133
|
+
} else if (/['"]\s*(SELECT\b[\s\S]*?WHERE\s+[a-zA-Z0-9_]+\s*=\s*)['"]\s*\+\s*([a-zA-Z0-9_$]+)/.test(targetLine)) {
|
|
134
|
+
patchedLine = targetLine.replace(
|
|
135
|
+
/['"]\s*(SELECT\b[\s\S]*?WHERE\s+[a-zA-Z0-9_]+\s*=\s*)['"]\s*\+\s*([a-zA-Z0-9_$]+)/,
|
|
136
|
+
"'$1 ?', [$2]"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
lines[targetLineIdx] = patchedLine;
|
|
140
|
+
return {
|
|
141
|
+
patchedContent: lines.join('\n'),
|
|
142
|
+
strategy: 'Conversão de interpolação SQL em Prepared Statement parametrizado com placeholders contra CWE-89'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Estratégia 3: Hardcoded Credentials / Segredos (CWE-798)
|
|
148
|
+
// Converte segredo em texto plano para process.env
|
|
149
|
+
if (finding.cwe === 'CWE-798' || finding.ruleId?.includes('CWE-798')) {
|
|
150
|
+
if (/(?:api[_-]?key|secret|password|token)\s*[:=]\s*['"][^'"]{8,}['"]/i.test(targetLine)) {
|
|
151
|
+
const envVarName = 'APP_SECRET_' + Math.floor(Math.random() * 1000);
|
|
152
|
+
const patchedLine = targetLine.replace(
|
|
153
|
+
/((?:api[_-]?key|secret|password|token)\s*[:=]\s*)['"][^'"]+['"]/i,
|
|
154
|
+
`$1process.env.${envVarName}`
|
|
155
|
+
);
|
|
156
|
+
lines[targetLineIdx] = patchedLine;
|
|
157
|
+
return {
|
|
158
|
+
patchedContent: lines.join('\n'),
|
|
159
|
+
strategy: 'Remoção de credencial em texto plano e injeção de variável de ambiente segura (process.env) contra CWE-798'
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Estratégia 4: XSS (CWE-79)
|
|
165
|
+
if (finding.cwe === 'CWE-79' || finding.ruleId?.includes('CWE-79')) {
|
|
166
|
+
if (/innerHTML\s*=/.test(targetLine)) {
|
|
167
|
+
const patchedLine = targetLine.replace(/innerHTML\s*=/, 'textContent =');
|
|
168
|
+
lines[targetLineIdx] = patchedLine;
|
|
169
|
+
return {
|
|
170
|
+
patchedContent: lines.join('\n'),
|
|
171
|
+
strategy: 'Substituição de innerHTML por textContent para mitigar XSS refletido (CWE-79)'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Executa o Loop Autônomo de Remediação (OODA) sobre um arquivo alvo.
|
|
181
|
+
* @param {object} params
|
|
182
|
+
* @param {string} params.targetFile Caminho do arquivo a ser remediado
|
|
183
|
+
* @param {string} [params.testCommand] Comando para rodar testes (ex: "node --test")
|
|
184
|
+
* @param {string} [params.projectRoot] Diretório raiz do projeto
|
|
185
|
+
* @returns {Promise<object>} Relatório final da autocura
|
|
186
|
+
*/
|
|
187
|
+
async remediateFile({ targetFile, testCommand, projectRoot }) {
|
|
188
|
+
if (!fs.existsSync(targetFile)) {
|
|
189
|
+
throw new Error(`Arquivo alvo não encontrado: ${targetFile}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const cwd = projectRoot || path.dirname(targetFile);
|
|
193
|
+
const originalContent = fs.readFileSync(targetFile, 'utf8');
|
|
194
|
+
const snapshotPath = targetFile + '.pre_remediation.bak';
|
|
195
|
+
fs.writeFileSync(snapshotPath, originalContent, 'utf8');
|
|
196
|
+
|
|
197
|
+
let currentContent = originalContent;
|
|
198
|
+
let iteration = 0;
|
|
199
|
+
const history = [];
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
while (iteration < this.maxIterations) {
|
|
203
|
+
iteration++;
|
|
204
|
+
|
|
205
|
+
// 1. OBSERVE: Scan SARIF
|
|
206
|
+
const sarifReport = this.sastScanner.scanCodeToSarif(currentContent, path.basename(targetFile));
|
|
207
|
+
const parsed = parseSarifReport(sarifReport);
|
|
208
|
+
const activeFindings = parsed.findings.filter(f => f.level === 'error' || f.severity === 'HIGH' || f.severity === 'CRITICAL');
|
|
209
|
+
|
|
210
|
+
if (activeFindings.length === 0) {
|
|
211
|
+
// Sem vulnerabilidades de segurança. Agora verifica não-regressão funcional
|
|
212
|
+
const testRes = this.runProjectTests(testCommand, cwd);
|
|
213
|
+
if (testRes.passed) {
|
|
214
|
+
// Sucesso absoluto! Código seguro e testes verdes
|
|
215
|
+
if (fs.existsSync(snapshotPath)) fs.unlinkSync(snapshotPath);
|
|
216
|
+
return {
|
|
217
|
+
status: 'REMEDIATION_CONFIRMED_GREEN',
|
|
218
|
+
success: true,
|
|
219
|
+
iterationsNeeded: iteration,
|
|
220
|
+
targetFile,
|
|
221
|
+
findingsResolved: originalContent !== currentContent,
|
|
222
|
+
testValidation: testRes,
|
|
223
|
+
history
|
|
224
|
+
};
|
|
225
|
+
} else {
|
|
226
|
+
// Regressão nos testes! Rollback imediato
|
|
227
|
+
fs.writeFileSync(targetFile, originalContent, 'utf8');
|
|
228
|
+
if (fs.existsSync(snapshotPath)) fs.unlinkSync(snapshotPath);
|
|
229
|
+
return {
|
|
230
|
+
status: 'REGRESSION_DETECTED_ROLLED_BACK',
|
|
231
|
+
success: false,
|
|
232
|
+
targetFile,
|
|
233
|
+
error: 'O patch introduziu falha nos testes automatizados do projeto alvo.',
|
|
234
|
+
testOutput: testRes.output,
|
|
235
|
+
history
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 2. ORIENT & DECIDE
|
|
241
|
+
const primaryFinding = activeFindings[0];
|
|
242
|
+
const patchResult = this.synthesizePatch(primaryFinding, currentContent);
|
|
243
|
+
|
|
244
|
+
if (!patchResult) {
|
|
245
|
+
// Não foi possível deduzir estratégia segura
|
|
246
|
+
fs.writeFileSync(targetFile, originalContent, 'utf8');
|
|
247
|
+
if (fs.existsSync(snapshotPath)) fs.unlinkSync(snapshotPath);
|
|
248
|
+
return {
|
|
249
|
+
status: 'UNRESOLVED_NO_SAFE_STRATEGY',
|
|
250
|
+
success: false,
|
|
251
|
+
targetFile,
|
|
252
|
+
unresolvedFinding: primaryFinding,
|
|
253
|
+
history
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 3. ACT: Aplica patch no arquivo
|
|
258
|
+
currentContent = patchResult.patchedContent;
|
|
259
|
+
fs.writeFileSync(targetFile, currentContent, 'utf8');
|
|
260
|
+
|
|
261
|
+
// 4. VERIFY: Roda testes para checagem imediata
|
|
262
|
+
const stepTest = this.runProjectTests(testCommand, cwd);
|
|
263
|
+
history.push({
|
|
264
|
+
iteration,
|
|
265
|
+
findingTargeted: primaryFinding.ruleId,
|
|
266
|
+
strategy: patchResult.strategy,
|
|
267
|
+
testPassed: stepTest.passed
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!stepTest.passed) {
|
|
271
|
+
// O patch quebrou a sintaxe ou quebrou os testes! Restaura e tenta abortar
|
|
272
|
+
fs.writeFileSync(targetFile, originalContent, 'utf8');
|
|
273
|
+
if (fs.existsSync(snapshotPath)) fs.unlinkSync(snapshotPath);
|
|
274
|
+
return {
|
|
275
|
+
status: 'REMEDIATION_FAILED_TEST_BROKEN',
|
|
276
|
+
success: false,
|
|
277
|
+
targetFile,
|
|
278
|
+
failedStrategy: patchResult.strategy,
|
|
279
|
+
testOutput: stepTest.output,
|
|
280
|
+
history
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Se estourou as iterações sem zerar os findings
|
|
286
|
+
fs.writeFileSync(targetFile, originalContent, 'utf8');
|
|
287
|
+
if (fs.existsSync(snapshotPath)) fs.unlinkSync(snapshotPath);
|
|
288
|
+
return {
|
|
289
|
+
status: 'MAX_ITERATIONS_EXCEEDED_ROLLED_BACK',
|
|
290
|
+
success: false,
|
|
291
|
+
targetFile,
|
|
292
|
+
history
|
|
293
|
+
};
|
|
294
|
+
} catch (err) {
|
|
295
|
+
// Falha inesperada: rollback garantido
|
|
296
|
+
fs.writeFileSync(targetFile, originalContent, 'utf8');
|
|
297
|
+
if (fs.existsSync(snapshotPath)) fs.unlinkSync(snapshotPath);
|
|
298
|
+
throw err;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
AutonomousRemediationEngine
|
|
305
|
+
};
|