musubi-sdd 6.2.0 → 6.2.1
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.ja.md +60 -1
- package/README.md +60 -1
- package/bin/musubi-dashboard.js +340 -0
- package/package.json +3 -2
- package/src/cli/dashboard-cli.js +536 -0
- package/src/constitutional/checker.js +633 -0
- package/src/constitutional/ci-reporter.js +336 -0
- package/src/constitutional/index.js +22 -0
- package/src/constitutional/phase-minus-one.js +404 -0
- package/src/constitutional/steering-sync.js +473 -0
- package/src/dashboard/index.js +20 -0
- package/src/dashboard/sprint-planner.js +361 -0
- package/src/dashboard/sprint-reporter.js +378 -0
- package/src/dashboard/transition-recorder.js +209 -0
- package/src/dashboard/workflow-dashboard.js +434 -0
- package/src/enterprise/error-recovery.js +524 -0
- package/src/enterprise/experiment-report.js +573 -0
- package/src/enterprise/index.js +57 -4
- package/src/enterprise/rollback-manager.js +584 -0
- package/src/enterprise/tech-article.js +509 -0
- package/src/traceability/extractor.js +294 -0
- package/src/traceability/gap-detector.js +230 -0
- package/src/traceability/index.js +15 -0
- package/src/traceability/matrix-storage.js +368 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Recovery Handler
|
|
3
|
+
*
|
|
4
|
+
* Provides recovery guidance for workflow failures.
|
|
5
|
+
*
|
|
6
|
+
* Requirement: IMP-6.2-008-01
|
|
7
|
+
*
|
|
8
|
+
* @module enterprise/error-recovery
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs').promises;
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Error category enum
|
|
16
|
+
*/
|
|
17
|
+
const ERROR_CATEGORY = {
|
|
18
|
+
TEST_FAILURE: 'test-failure',
|
|
19
|
+
VALIDATION_ERROR: 'validation-error',
|
|
20
|
+
BUILD_ERROR: 'build-error',
|
|
21
|
+
LINT_ERROR: 'lint-error',
|
|
22
|
+
TYPE_ERROR: 'type-error',
|
|
23
|
+
DEPENDENCY_ERROR: 'dependency-error',
|
|
24
|
+
CONFIGURATION_ERROR: 'configuration-error',
|
|
25
|
+
RUNTIME_ERROR: 'runtime-error',
|
|
26
|
+
UNKNOWN: 'unknown'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Recovery action enum
|
|
31
|
+
*/
|
|
32
|
+
const RECOVERY_ACTION = {
|
|
33
|
+
FIX_CODE: 'fix-code',
|
|
34
|
+
UPDATE_TEST: 'update-test',
|
|
35
|
+
INSTALL_DEPS: 'install-deps',
|
|
36
|
+
UPDATE_CONFIG: 'update-config',
|
|
37
|
+
ROLLBACK: 'rollback',
|
|
38
|
+
MANUAL_REVIEW: 'manual-review',
|
|
39
|
+
RETRY: 'retry'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Error Recovery Handler
|
|
44
|
+
*/
|
|
45
|
+
class ErrorRecoveryHandler {
|
|
46
|
+
/**
|
|
47
|
+
* Create a new ErrorRecoveryHandler
|
|
48
|
+
* @param {Object} config - Configuration options
|
|
49
|
+
*/
|
|
50
|
+
constructor(config = {}) {
|
|
51
|
+
this.config = {
|
|
52
|
+
storageDir: config.storageDir || 'storage/errors',
|
|
53
|
+
maxHistorySize: config.maxHistorySize || 100,
|
|
54
|
+
enableAutoAnalysis: config.enableAutoAnalysis !== false,
|
|
55
|
+
...config
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.errorHistory = [];
|
|
59
|
+
this.recoveryPatterns = this.loadRecoveryPatterns();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load recovery patterns
|
|
64
|
+
* @returns {Object} Recovery patterns
|
|
65
|
+
*/
|
|
66
|
+
loadRecoveryPatterns() {
|
|
67
|
+
return {
|
|
68
|
+
[ERROR_CATEGORY.TEST_FAILURE]: {
|
|
69
|
+
patterns: [
|
|
70
|
+
{ match: /expect.*toEqual/i, cause: 'Assertion mismatch', action: RECOVERY_ACTION.FIX_CODE },
|
|
71
|
+
{ match: /undefined is not/i, cause: 'Null reference', action: RECOVERY_ACTION.FIX_CODE },
|
|
72
|
+
{ match: /timeout/i, cause: 'Test timeout', action: RECOVERY_ACTION.UPDATE_TEST },
|
|
73
|
+
{ match: /cannot find module/i, cause: 'Missing import', action: RECOVERY_ACTION.INSTALL_DEPS }
|
|
74
|
+
],
|
|
75
|
+
defaultAction: RECOVERY_ACTION.MANUAL_REVIEW
|
|
76
|
+
},
|
|
77
|
+
[ERROR_CATEGORY.VALIDATION_ERROR]: {
|
|
78
|
+
patterns: [
|
|
79
|
+
{ match: /ears.*format/i, cause: 'EARS format violation', action: RECOVERY_ACTION.FIX_CODE },
|
|
80
|
+
{ match: /traceability/i, cause: 'Missing traceability', action: RECOVERY_ACTION.FIX_CODE },
|
|
81
|
+
{ match: /constitutional/i, cause: 'Constitutional violation', action: RECOVERY_ACTION.MANUAL_REVIEW }
|
|
82
|
+
],
|
|
83
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
84
|
+
},
|
|
85
|
+
[ERROR_CATEGORY.BUILD_ERROR]: {
|
|
86
|
+
patterns: [
|
|
87
|
+
{ match: /syntax.*error/i, cause: 'Syntax error', action: RECOVERY_ACTION.FIX_CODE },
|
|
88
|
+
{ match: /cannot resolve/i, cause: 'Module resolution failed', action: RECOVERY_ACTION.INSTALL_DEPS },
|
|
89
|
+
{ match: /out of memory/i, cause: 'Memory limit exceeded', action: RECOVERY_ACTION.UPDATE_CONFIG }
|
|
90
|
+
],
|
|
91
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
92
|
+
},
|
|
93
|
+
[ERROR_CATEGORY.LINT_ERROR]: {
|
|
94
|
+
patterns: [
|
|
95
|
+
{ match: /parsing error/i, cause: 'Parse error', action: RECOVERY_ACTION.FIX_CODE },
|
|
96
|
+
{ match: /no-unused/i, cause: 'Unused code', action: RECOVERY_ACTION.FIX_CODE },
|
|
97
|
+
{ match: /prefer-const/i, cause: 'Style violation', action: RECOVERY_ACTION.FIX_CODE }
|
|
98
|
+
],
|
|
99
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
100
|
+
},
|
|
101
|
+
[ERROR_CATEGORY.TYPE_ERROR]: {
|
|
102
|
+
patterns: [
|
|
103
|
+
{ match: /type.*not assignable/i, cause: 'Type mismatch', action: RECOVERY_ACTION.FIX_CODE },
|
|
104
|
+
{ match: /property.*does not exist/i, cause: 'Missing property', action: RECOVERY_ACTION.FIX_CODE },
|
|
105
|
+
{ match: /cannot find name/i, cause: 'Undefined identifier', action: RECOVERY_ACTION.FIX_CODE }
|
|
106
|
+
],
|
|
107
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
108
|
+
},
|
|
109
|
+
[ERROR_CATEGORY.DEPENDENCY_ERROR]: {
|
|
110
|
+
patterns: [
|
|
111
|
+
{ match: /peer dep/i, cause: 'Peer dependency conflict', action: RECOVERY_ACTION.INSTALL_DEPS },
|
|
112
|
+
{ match: /not found in npm registry/i, cause: 'Package not found', action: RECOVERY_ACTION.UPDATE_CONFIG },
|
|
113
|
+
{ match: /version.*incompatible/i, cause: 'Version conflict', action: RECOVERY_ACTION.INSTALL_DEPS }
|
|
114
|
+
],
|
|
115
|
+
defaultAction: RECOVERY_ACTION.INSTALL_DEPS
|
|
116
|
+
},
|
|
117
|
+
[ERROR_CATEGORY.CONFIGURATION_ERROR]: {
|
|
118
|
+
patterns: [
|
|
119
|
+
{ match: /invalid.*config/i, cause: 'Invalid configuration', action: RECOVERY_ACTION.UPDATE_CONFIG },
|
|
120
|
+
{ match: /missing.*required/i, cause: 'Missing required field', action: RECOVERY_ACTION.UPDATE_CONFIG }
|
|
121
|
+
],
|
|
122
|
+
defaultAction: RECOVERY_ACTION.UPDATE_CONFIG
|
|
123
|
+
},
|
|
124
|
+
[ERROR_CATEGORY.RUNTIME_ERROR]: {
|
|
125
|
+
patterns: [
|
|
126
|
+
{ match: /enoent/i, cause: 'File not found', action: RECOVERY_ACTION.FIX_CODE },
|
|
127
|
+
{ match: /eacces/i, cause: 'Permission denied', action: RECOVERY_ACTION.UPDATE_CONFIG },
|
|
128
|
+
{ match: /econnrefused/i, cause: 'Connection refused', action: RECOVERY_ACTION.RETRY }
|
|
129
|
+
],
|
|
130
|
+
defaultAction: RECOVERY_ACTION.MANUAL_REVIEW
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Analyze error and provide recovery guidance
|
|
137
|
+
* @param {Error|Object} error - Error object
|
|
138
|
+
* @param {Object} context - Error context
|
|
139
|
+
* @returns {Object} Analysis result with recovery guidance
|
|
140
|
+
*/
|
|
141
|
+
analyze(error, context = {}) {
|
|
142
|
+
const errorInfo = this.normalizeError(error);
|
|
143
|
+
const category = this.categorizeError(errorInfo, context);
|
|
144
|
+
const rootCause = this.identifyRootCause(errorInfo, category);
|
|
145
|
+
const remediation = this.generateRemediation(category, rootCause, context);
|
|
146
|
+
|
|
147
|
+
const analysis = {
|
|
148
|
+
id: this.generateId(),
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
error: errorInfo,
|
|
151
|
+
category,
|
|
152
|
+
rootCause,
|
|
153
|
+
remediation,
|
|
154
|
+
context,
|
|
155
|
+
confidence: this.calculateConfidence(rootCause)
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Record in history
|
|
159
|
+
this.recordError(analysis);
|
|
160
|
+
|
|
161
|
+
return analysis;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Normalize error object
|
|
166
|
+
* @param {Error|Object} error - Error input
|
|
167
|
+
* @returns {Object} Normalized error
|
|
168
|
+
*/
|
|
169
|
+
normalizeError(error) {
|
|
170
|
+
if (error instanceof Error) {
|
|
171
|
+
return {
|
|
172
|
+
message: error.message,
|
|
173
|
+
name: error.name,
|
|
174
|
+
stack: error.stack,
|
|
175
|
+
code: error.code
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
message: error.message || String(error),
|
|
180
|
+
name: error.name || 'Error',
|
|
181
|
+
stack: error.stack || '',
|
|
182
|
+
code: error.code || ''
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Categorize error
|
|
188
|
+
* @param {Object} errorInfo - Error info
|
|
189
|
+
* @param {Object} context - Error context
|
|
190
|
+
* @returns {string} Error category
|
|
191
|
+
*/
|
|
192
|
+
categorizeError(errorInfo, context) {
|
|
193
|
+
const message = errorInfo.message.toLowerCase();
|
|
194
|
+
const name = errorInfo.name.toLowerCase();
|
|
195
|
+
|
|
196
|
+
// Context-based categorization
|
|
197
|
+
if (context.stage === 'test') return ERROR_CATEGORY.TEST_FAILURE;
|
|
198
|
+
if (context.stage === 'validation') return ERROR_CATEGORY.VALIDATION_ERROR;
|
|
199
|
+
if (context.stage === 'build') return ERROR_CATEGORY.BUILD_ERROR;
|
|
200
|
+
if (context.stage === 'lint') return ERROR_CATEGORY.LINT_ERROR;
|
|
201
|
+
|
|
202
|
+
// Message-based categorization
|
|
203
|
+
if (message.includes('assert') || name.includes('assert')) return ERROR_CATEGORY.TEST_FAILURE;
|
|
204
|
+
if (message.includes('type') || name.includes('type')) return ERROR_CATEGORY.TYPE_ERROR;
|
|
205
|
+
if (message.includes('lint') || message.includes('eslint')) return ERROR_CATEGORY.LINT_ERROR;
|
|
206
|
+
if (message.includes('dependency') || message.includes('npm') || message.includes('package')) {
|
|
207
|
+
return ERROR_CATEGORY.DEPENDENCY_ERROR;
|
|
208
|
+
}
|
|
209
|
+
if (message.includes('config')) return ERROR_CATEGORY.CONFIGURATION_ERROR;
|
|
210
|
+
if (message.includes('build') || message.includes('compile')) return ERROR_CATEGORY.BUILD_ERROR;
|
|
211
|
+
if (errorInfo.code?.startsWith('E')) return ERROR_CATEGORY.RUNTIME_ERROR;
|
|
212
|
+
|
|
213
|
+
return ERROR_CATEGORY.UNKNOWN;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Identify root cause
|
|
218
|
+
* @param {Object} errorInfo - Error info
|
|
219
|
+
* @param {string} category - Error category
|
|
220
|
+
* @returns {Object} Root cause analysis
|
|
221
|
+
*/
|
|
222
|
+
identifyRootCause(errorInfo, category) {
|
|
223
|
+
const patterns = this.recoveryPatterns[category];
|
|
224
|
+
|
|
225
|
+
if (!patterns) {
|
|
226
|
+
return {
|
|
227
|
+
cause: 'Unknown error',
|
|
228
|
+
action: RECOVERY_ACTION.MANUAL_REVIEW,
|
|
229
|
+
matched: false
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const message = errorInfo.message;
|
|
234
|
+
|
|
235
|
+
for (const pattern of patterns.patterns) {
|
|
236
|
+
if (pattern.match.test(message)) {
|
|
237
|
+
return {
|
|
238
|
+
cause: pattern.cause,
|
|
239
|
+
action: pattern.action,
|
|
240
|
+
matched: true,
|
|
241
|
+
pattern: pattern.match.toString()
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
cause: 'Unrecognized error pattern',
|
|
248
|
+
action: patterns.defaultAction,
|
|
249
|
+
matched: false
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generate remediation steps
|
|
255
|
+
* @param {string} category - Error category
|
|
256
|
+
* @param {Object} rootCause - Root cause info
|
|
257
|
+
* @param {Object} context - Error context
|
|
258
|
+
* @returns {Object} Remediation guidance
|
|
259
|
+
*/
|
|
260
|
+
generateRemediation(category, rootCause, context) {
|
|
261
|
+
const steps = [];
|
|
262
|
+
const commands = [];
|
|
263
|
+
|
|
264
|
+
switch (rootCause.action) {
|
|
265
|
+
case RECOVERY_ACTION.FIX_CODE:
|
|
266
|
+
steps.push('1. Locate the error in the source file');
|
|
267
|
+
steps.push(`2. Fix the issue: ${rootCause.cause}`);
|
|
268
|
+
steps.push('3. Run tests to verify the fix');
|
|
269
|
+
if (context.file) {
|
|
270
|
+
steps.push(`4. Target file: ${context.file}`);
|
|
271
|
+
}
|
|
272
|
+
commands.push('npm test');
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case RECOVERY_ACTION.UPDATE_TEST:
|
|
276
|
+
steps.push('1. Review the failing test case');
|
|
277
|
+
steps.push('2. Update test expectations if behavior changed');
|
|
278
|
+
steps.push('3. Consider increasing timeout if needed');
|
|
279
|
+
commands.push('npm test -- --verbose');
|
|
280
|
+
break;
|
|
281
|
+
|
|
282
|
+
case RECOVERY_ACTION.INSTALL_DEPS:
|
|
283
|
+
steps.push('1. Check package.json for missing dependencies');
|
|
284
|
+
steps.push('2. Run npm install to update packages');
|
|
285
|
+
steps.push('3. Clear npm cache if issues persist');
|
|
286
|
+
commands.push('npm install');
|
|
287
|
+
commands.push('npm cache clean --force');
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case RECOVERY_ACTION.UPDATE_CONFIG:
|
|
291
|
+
steps.push('1. Review configuration file');
|
|
292
|
+
steps.push('2. Validate JSON/YAML syntax');
|
|
293
|
+
steps.push('3. Check for missing required fields');
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case RECOVERY_ACTION.ROLLBACK:
|
|
297
|
+
steps.push('1. Identify the last known good state');
|
|
298
|
+
steps.push('2. Use rollback manager to revert changes');
|
|
299
|
+
steps.push('3. Verify system stability after rollback');
|
|
300
|
+
commands.push('git log --oneline -10');
|
|
301
|
+
break;
|
|
302
|
+
|
|
303
|
+
case RECOVERY_ACTION.RETRY:
|
|
304
|
+
steps.push('1. Wait a moment and retry the operation');
|
|
305
|
+
steps.push('2. Check network connectivity');
|
|
306
|
+
steps.push('3. Verify external service availability');
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case RECOVERY_ACTION.MANUAL_REVIEW:
|
|
310
|
+
default:
|
|
311
|
+
steps.push('1. Review the error message and stack trace');
|
|
312
|
+
steps.push('2. Search for similar issues in documentation');
|
|
313
|
+
steps.push('3. Consult with team if needed');
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
action: rootCause.action,
|
|
319
|
+
steps,
|
|
320
|
+
commands,
|
|
321
|
+
estimatedTime: this.estimateRecoveryTime(rootCause.action),
|
|
322
|
+
priority: this.determinePriority(category, context)
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Estimate recovery time
|
|
328
|
+
* @param {string} action - Recovery action
|
|
329
|
+
* @returns {string} Time estimate
|
|
330
|
+
*/
|
|
331
|
+
estimateRecoveryTime(action) {
|
|
332
|
+
const estimates = {
|
|
333
|
+
[RECOVERY_ACTION.FIX_CODE]: '15-30 minutes',
|
|
334
|
+
[RECOVERY_ACTION.UPDATE_TEST]: '10-20 minutes',
|
|
335
|
+
[RECOVERY_ACTION.INSTALL_DEPS]: '5-10 minutes',
|
|
336
|
+
[RECOVERY_ACTION.UPDATE_CONFIG]: '5-15 minutes',
|
|
337
|
+
[RECOVERY_ACTION.ROLLBACK]: '10-30 minutes',
|
|
338
|
+
[RECOVERY_ACTION.RETRY]: '1-5 minutes',
|
|
339
|
+
[RECOVERY_ACTION.MANUAL_REVIEW]: '30-60 minutes'
|
|
340
|
+
};
|
|
341
|
+
return estimates[action] || 'Unknown';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Determine priority
|
|
346
|
+
* @param {string} category - Error category
|
|
347
|
+
* @param {Object} context - Context
|
|
348
|
+
* @returns {string} Priority level
|
|
349
|
+
*/
|
|
350
|
+
determinePriority(category, context) {
|
|
351
|
+
if (context.blocking) return 'critical';
|
|
352
|
+
|
|
353
|
+
const highPriority = [ERROR_CATEGORY.BUILD_ERROR, ERROR_CATEGORY.TEST_FAILURE];
|
|
354
|
+
const mediumPriority = [ERROR_CATEGORY.TYPE_ERROR, ERROR_CATEGORY.LINT_ERROR];
|
|
355
|
+
|
|
356
|
+
if (highPriority.includes(category)) return 'high';
|
|
357
|
+
if (mediumPriority.includes(category)) return 'medium';
|
|
358
|
+
return 'low';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Calculate confidence score
|
|
363
|
+
* @param {Object} rootCause - Root cause info
|
|
364
|
+
* @returns {number} Confidence (0-1)
|
|
365
|
+
*/
|
|
366
|
+
calculateConfidence(rootCause) {
|
|
367
|
+
if (rootCause.matched) return 0.9;
|
|
368
|
+
return 0.5;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Record error in history
|
|
373
|
+
* @param {Object} analysis - Error analysis
|
|
374
|
+
*/
|
|
375
|
+
recordError(analysis) {
|
|
376
|
+
this.errorHistory.unshift(analysis);
|
|
377
|
+
|
|
378
|
+
// Trim history
|
|
379
|
+
if (this.errorHistory.length > this.config.maxHistorySize) {
|
|
380
|
+
this.errorHistory = this.errorHistory.slice(0, this.config.maxHistorySize);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get error history
|
|
386
|
+
* @param {Object} filter - Filter options
|
|
387
|
+
* @returns {Array} Filtered history
|
|
388
|
+
*/
|
|
389
|
+
getHistory(filter = {}) {
|
|
390
|
+
let history = [...this.errorHistory];
|
|
391
|
+
|
|
392
|
+
if (filter.category) {
|
|
393
|
+
history = history.filter(e => e.category === filter.category);
|
|
394
|
+
}
|
|
395
|
+
if (filter.since) {
|
|
396
|
+
const since = new Date(filter.since);
|
|
397
|
+
history = history.filter(e => new Date(e.timestamp) >= since);
|
|
398
|
+
}
|
|
399
|
+
if (filter.limit) {
|
|
400
|
+
history = history.slice(0, filter.limit);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return history;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Save error analysis to file
|
|
408
|
+
* @param {Object} analysis - Error analysis
|
|
409
|
+
* @returns {Promise<string>} File path
|
|
410
|
+
*/
|
|
411
|
+
async saveAnalysis(analysis) {
|
|
412
|
+
await this.ensureStorageDir();
|
|
413
|
+
|
|
414
|
+
const fileName = `error-${analysis.id}.json`;
|
|
415
|
+
const filePath = path.join(this.config.storageDir, fileName);
|
|
416
|
+
|
|
417
|
+
await fs.writeFile(filePath, JSON.stringify(analysis, null, 2), 'utf-8');
|
|
418
|
+
return filePath;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Load error analysis from file
|
|
423
|
+
* @param {string} id - Error ID
|
|
424
|
+
* @returns {Promise<Object|null>} Analysis or null
|
|
425
|
+
*/
|
|
426
|
+
async loadAnalysis(id) {
|
|
427
|
+
const fileName = `error-${id}.json`;
|
|
428
|
+
const filePath = path.join(this.config.storageDir, fileName);
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
432
|
+
return JSON.parse(content);
|
|
433
|
+
} catch {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Ensure storage directory exists
|
|
440
|
+
* @returns {Promise<void>}
|
|
441
|
+
*/
|
|
442
|
+
async ensureStorageDir() {
|
|
443
|
+
await fs.mkdir(this.config.storageDir, { recursive: true });
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Generate unique ID
|
|
448
|
+
* @returns {string} Unique ID
|
|
449
|
+
*/
|
|
450
|
+
generateId() {
|
|
451
|
+
return `err-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Generate recovery report
|
|
456
|
+
* @param {Object} analysis - Error analysis
|
|
457
|
+
* @returns {string} Markdown report
|
|
458
|
+
*/
|
|
459
|
+
generateReport(analysis) {
|
|
460
|
+
const lines = [];
|
|
461
|
+
|
|
462
|
+
lines.push('# Error Recovery Report');
|
|
463
|
+
lines.push('');
|
|
464
|
+
lines.push(`**Error ID**: ${analysis.id}`);
|
|
465
|
+
lines.push(`**Timestamp**: ${analysis.timestamp}`);
|
|
466
|
+
lines.push(`**Category**: ${analysis.category}`);
|
|
467
|
+
lines.push(`**Confidence**: ${(analysis.confidence * 100).toFixed(0)}%`);
|
|
468
|
+
lines.push('');
|
|
469
|
+
|
|
470
|
+
lines.push('## Error Details');
|
|
471
|
+
lines.push('');
|
|
472
|
+
lines.push(`**Name**: ${analysis.error.name}`);
|
|
473
|
+
lines.push(`**Message**: ${analysis.error.message}`);
|
|
474
|
+
lines.push('');
|
|
475
|
+
|
|
476
|
+
lines.push('## Root Cause Analysis');
|
|
477
|
+
lines.push('');
|
|
478
|
+
lines.push(`**Identified Cause**: ${analysis.rootCause.cause}`);
|
|
479
|
+
lines.push(`**Pattern Matched**: ${analysis.rootCause.matched ? 'Yes' : 'No'}`);
|
|
480
|
+
lines.push('');
|
|
481
|
+
|
|
482
|
+
lines.push('## Remediation');
|
|
483
|
+
lines.push('');
|
|
484
|
+
lines.push(`**Recommended Action**: ${analysis.remediation.action}`);
|
|
485
|
+
lines.push(`**Priority**: ${analysis.remediation.priority}`);
|
|
486
|
+
lines.push(`**Estimated Time**: ${analysis.remediation.estimatedTime}`);
|
|
487
|
+
lines.push('');
|
|
488
|
+
|
|
489
|
+
lines.push('### Steps');
|
|
490
|
+
lines.push('');
|
|
491
|
+
for (const step of analysis.remediation.steps) {
|
|
492
|
+
lines.push(step);
|
|
493
|
+
}
|
|
494
|
+
lines.push('');
|
|
495
|
+
|
|
496
|
+
if (analysis.remediation.commands.length > 0) {
|
|
497
|
+
lines.push('### Commands');
|
|
498
|
+
lines.push('');
|
|
499
|
+
lines.push('```bash');
|
|
500
|
+
for (const cmd of analysis.remediation.commands) {
|
|
501
|
+
lines.push(cmd);
|
|
502
|
+
}
|
|
503
|
+
lines.push('```');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return lines.join('\n');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Create a new ErrorRecoveryHandler instance
|
|
512
|
+
* @param {Object} config - Configuration options
|
|
513
|
+
* @returns {ErrorRecoveryHandler}
|
|
514
|
+
*/
|
|
515
|
+
function createErrorRecoveryHandler(config = {}) {
|
|
516
|
+
return new ErrorRecoveryHandler(config);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
module.exports = {
|
|
520
|
+
ErrorRecoveryHandler,
|
|
521
|
+
createErrorRecoveryHandler,
|
|
522
|
+
ERROR_CATEGORY,
|
|
523
|
+
RECOVERY_ACTION
|
|
524
|
+
};
|