musubi-sdd 6.2.1 → 6.3.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.ja.md +3 -3
- package/README.md +3 -3
- package/bin/musubi-dashboard.js +23 -14
- package/bin/musubi-design.js +3 -3
- package/bin/musubi-gaps.js +9 -9
- package/bin/musubi-init.js +14 -1310
- package/bin/musubi-requirements.js +1 -1
- package/bin/musubi-tasks.js +5 -5
- package/bin/musubi-trace.js +23 -23
- package/bin/musubi-upgrade.js +400 -0
- package/bin/musubi.js +16 -1
- package/package.json +4 -3
- package/src/analyzers/gap-detector.js +3 -3
- package/src/analyzers/traceability.js +17 -17
- package/src/cli/dashboard-cli.js +54 -60
- package/src/cli/init-generators.js +464 -0
- package/src/cli/init-helpers.js +884 -0
- package/src/constitutional/checker.js +67 -65
- package/src/constitutional/ci-reporter.js +58 -49
- package/src/constitutional/index.js +2 -2
- package/src/constitutional/phase-minus-one.js +24 -27
- package/src/constitutional/steering-sync.js +29 -40
- package/src/dashboard/index.js +2 -2
- package/src/dashboard/sprint-planner.js +17 -19
- package/src/dashboard/sprint-reporter.js +47 -38
- package/src/dashboard/transition-recorder.js +12 -18
- package/src/dashboard/workflow-dashboard.js +28 -39
- package/src/enterprise/error-recovery.js +109 -49
- package/src/enterprise/experiment-report.js +63 -37
- package/src/enterprise/index.js +5 -5
- package/src/enterprise/rollback-manager.js +28 -29
- package/src/enterprise/tech-article.js +41 -35
- package/src/generators/design.js +3 -3
- package/src/generators/requirements.js +5 -3
- package/src/generators/tasks.js +2 -2
- package/src/integrations/platforms.js +1 -1
- package/src/templates/agents/claude-code/CLAUDE.md +1 -1
- package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +132 -113
- package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +85 -56
- package/src/templates/agents/codex/AGENTS.md +2 -2
- package/src/templates/agents/cursor/AGENTS.md +2 -2
- package/src/templates/agents/gemini-cli/GEMINI.md +2 -2
- package/src/templates/agents/github-copilot/AGENTS.md +2 -2
- package/src/templates/agents/github-copilot/commands/sdd-requirements.prompt.md +23 -4
- package/src/templates/agents/qwen-code/QWEN.md +2 -2
- package/src/templates/agents/shared/AGENTS.md +1 -1
- package/src/templates/agents/windsurf/AGENTS.md +2 -2
- package/src/templates/skills/browser-agent.md +1 -1
- package/src/traceability/extractor.js +22 -21
- package/src/traceability/gap-detector.js +19 -17
- package/src/traceability/index.js +2 -2
- package/src/traceability/matrix-storage.js +20 -22
- package/src/validators/constitution.js +5 -2
- package/src/validators/critic-system.js +6 -6
- package/src/validators/traceability-validator.js +3 -3
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error Recovery Handler
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Provides recovery guidance for workflow failures.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Requirement: IMP-6.2-008-01
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @module enterprise/error-recovery
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ const ERROR_CATEGORY = {
|
|
|
23
23
|
DEPENDENCY_ERROR: 'dependency-error',
|
|
24
24
|
CONFIGURATION_ERROR: 'configuration-error',
|
|
25
25
|
RUNTIME_ERROR: 'runtime-error',
|
|
26
|
-
UNKNOWN: 'unknown'
|
|
26
|
+
UNKNOWN: 'unknown',
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -36,7 +36,7 @@ const RECOVERY_ACTION = {
|
|
|
36
36
|
UPDATE_CONFIG: 'update-config',
|
|
37
37
|
ROLLBACK: 'rollback',
|
|
38
38
|
MANUAL_REVIEW: 'manual-review',
|
|
39
|
-
RETRY: 'retry'
|
|
39
|
+
RETRY: 'retry',
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -52,7 +52,7 @@ class ErrorRecoveryHandler {
|
|
|
52
52
|
storageDir: config.storageDir || 'storage/errors',
|
|
53
53
|
maxHistorySize: config.maxHistorySize || 100,
|
|
54
54
|
enableAutoAnalysis: config.enableAutoAnalysis !== false,
|
|
55
|
-
...config
|
|
55
|
+
...config,
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
this.errorHistory = [];
|
|
@@ -67,68 +67,128 @@ class ErrorRecoveryHandler {
|
|
|
67
67
|
return {
|
|
68
68
|
[ERROR_CATEGORY.TEST_FAILURE]: {
|
|
69
69
|
patterns: [
|
|
70
|
-
{
|
|
70
|
+
{
|
|
71
|
+
match: /expect.*toEqual/i,
|
|
72
|
+
cause: 'Assertion mismatch',
|
|
73
|
+
action: RECOVERY_ACTION.FIX_CODE,
|
|
74
|
+
},
|
|
71
75
|
{ match: /undefined is not/i, cause: 'Null reference', action: RECOVERY_ACTION.FIX_CODE },
|
|
72
76
|
{ match: /timeout/i, cause: 'Test timeout', action: RECOVERY_ACTION.UPDATE_TEST },
|
|
73
|
-
{
|
|
77
|
+
{
|
|
78
|
+
match: /cannot find module/i,
|
|
79
|
+
cause: 'Missing import',
|
|
80
|
+
action: RECOVERY_ACTION.INSTALL_DEPS,
|
|
81
|
+
},
|
|
74
82
|
],
|
|
75
|
-
defaultAction: RECOVERY_ACTION.MANUAL_REVIEW
|
|
83
|
+
defaultAction: RECOVERY_ACTION.MANUAL_REVIEW,
|
|
76
84
|
},
|
|
77
85
|
[ERROR_CATEGORY.VALIDATION_ERROR]: {
|
|
78
86
|
patterns: [
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
{
|
|
88
|
+
match: /ears.*format/i,
|
|
89
|
+
cause: 'EARS format violation',
|
|
90
|
+
action: RECOVERY_ACTION.FIX_CODE,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
match: /traceability/i,
|
|
94
|
+
cause: 'Missing traceability',
|
|
95
|
+
action: RECOVERY_ACTION.FIX_CODE,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
match: /constitutional/i,
|
|
99
|
+
cause: 'Constitutional violation',
|
|
100
|
+
action: RECOVERY_ACTION.MANUAL_REVIEW,
|
|
101
|
+
},
|
|
82
102
|
],
|
|
83
|
-
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
103
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE,
|
|
84
104
|
},
|
|
85
105
|
[ERROR_CATEGORY.BUILD_ERROR]: {
|
|
86
106
|
patterns: [
|
|
87
107
|
{ match: /syntax.*error/i, cause: 'Syntax error', action: RECOVERY_ACTION.FIX_CODE },
|
|
88
|
-
{
|
|
89
|
-
|
|
108
|
+
{
|
|
109
|
+
match: /cannot resolve/i,
|
|
110
|
+
cause: 'Module resolution failed',
|
|
111
|
+
action: RECOVERY_ACTION.INSTALL_DEPS,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
match: /out of memory/i,
|
|
115
|
+
cause: 'Memory limit exceeded',
|
|
116
|
+
action: RECOVERY_ACTION.UPDATE_CONFIG,
|
|
117
|
+
},
|
|
90
118
|
],
|
|
91
|
-
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
119
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE,
|
|
92
120
|
},
|
|
93
121
|
[ERROR_CATEGORY.LINT_ERROR]: {
|
|
94
122
|
patterns: [
|
|
95
123
|
{ match: /parsing error/i, cause: 'Parse error', action: RECOVERY_ACTION.FIX_CODE },
|
|
96
124
|
{ 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 }
|
|
125
|
+
{ match: /prefer-const/i, cause: 'Style violation', action: RECOVERY_ACTION.FIX_CODE },
|
|
98
126
|
],
|
|
99
|
-
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
127
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE,
|
|
100
128
|
},
|
|
101
129
|
[ERROR_CATEGORY.TYPE_ERROR]: {
|
|
102
130
|
patterns: [
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
131
|
+
{
|
|
132
|
+
match: /type.*not assignable/i,
|
|
133
|
+
cause: 'Type mismatch',
|
|
134
|
+
action: RECOVERY_ACTION.FIX_CODE,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
match: /property.*does not exist/i,
|
|
138
|
+
cause: 'Missing property',
|
|
139
|
+
action: RECOVERY_ACTION.FIX_CODE,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
match: /cannot find name/i,
|
|
143
|
+
cause: 'Undefined identifier',
|
|
144
|
+
action: RECOVERY_ACTION.FIX_CODE,
|
|
145
|
+
},
|
|
106
146
|
],
|
|
107
|
-
defaultAction: RECOVERY_ACTION.FIX_CODE
|
|
147
|
+
defaultAction: RECOVERY_ACTION.FIX_CODE,
|
|
108
148
|
},
|
|
109
149
|
[ERROR_CATEGORY.DEPENDENCY_ERROR]: {
|
|
110
150
|
patterns: [
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
{
|
|
152
|
+
match: /peer dep/i,
|
|
153
|
+
cause: 'Peer dependency conflict',
|
|
154
|
+
action: RECOVERY_ACTION.INSTALL_DEPS,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
match: /not found in npm registry/i,
|
|
158
|
+
cause: 'Package not found',
|
|
159
|
+
action: RECOVERY_ACTION.UPDATE_CONFIG,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
match: /version.*incompatible/i,
|
|
163
|
+
cause: 'Version conflict',
|
|
164
|
+
action: RECOVERY_ACTION.INSTALL_DEPS,
|
|
165
|
+
},
|
|
114
166
|
],
|
|
115
|
-
defaultAction: RECOVERY_ACTION.INSTALL_DEPS
|
|
167
|
+
defaultAction: RECOVERY_ACTION.INSTALL_DEPS,
|
|
116
168
|
},
|
|
117
169
|
[ERROR_CATEGORY.CONFIGURATION_ERROR]: {
|
|
118
170
|
patterns: [
|
|
119
|
-
{
|
|
120
|
-
|
|
171
|
+
{
|
|
172
|
+
match: /invalid.*config/i,
|
|
173
|
+
cause: 'Invalid configuration',
|
|
174
|
+
action: RECOVERY_ACTION.UPDATE_CONFIG,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
match: /missing.*required/i,
|
|
178
|
+
cause: 'Missing required field',
|
|
179
|
+
action: RECOVERY_ACTION.UPDATE_CONFIG,
|
|
180
|
+
},
|
|
121
181
|
],
|
|
122
|
-
defaultAction: RECOVERY_ACTION.UPDATE_CONFIG
|
|
182
|
+
defaultAction: RECOVERY_ACTION.UPDATE_CONFIG,
|
|
123
183
|
},
|
|
124
184
|
[ERROR_CATEGORY.RUNTIME_ERROR]: {
|
|
125
185
|
patterns: [
|
|
126
186
|
{ match: /enoent/i, cause: 'File not found', action: RECOVERY_ACTION.FIX_CODE },
|
|
127
187
|
{ match: /eacces/i, cause: 'Permission denied', action: RECOVERY_ACTION.UPDATE_CONFIG },
|
|
128
|
-
{ match: /econnrefused/i, cause: 'Connection refused', action: RECOVERY_ACTION.RETRY }
|
|
188
|
+
{ match: /econnrefused/i, cause: 'Connection refused', action: RECOVERY_ACTION.RETRY },
|
|
129
189
|
],
|
|
130
|
-
defaultAction: RECOVERY_ACTION.MANUAL_REVIEW
|
|
131
|
-
}
|
|
190
|
+
defaultAction: RECOVERY_ACTION.MANUAL_REVIEW,
|
|
191
|
+
},
|
|
132
192
|
};
|
|
133
193
|
}
|
|
134
194
|
|
|
@@ -152,7 +212,7 @@ class ErrorRecoveryHandler {
|
|
|
152
212
|
rootCause,
|
|
153
213
|
remediation,
|
|
154
214
|
context,
|
|
155
|
-
confidence: this.calculateConfidence(rootCause)
|
|
215
|
+
confidence: this.calculateConfidence(rootCause),
|
|
156
216
|
};
|
|
157
217
|
|
|
158
218
|
// Record in history
|
|
@@ -172,14 +232,14 @@ class ErrorRecoveryHandler {
|
|
|
172
232
|
message: error.message,
|
|
173
233
|
name: error.name,
|
|
174
234
|
stack: error.stack,
|
|
175
|
-
code: error.code
|
|
235
|
+
code: error.code,
|
|
176
236
|
};
|
|
177
237
|
}
|
|
178
238
|
return {
|
|
179
239
|
message: error.message || String(error),
|
|
180
240
|
name: error.name || 'Error',
|
|
181
241
|
stack: error.stack || '',
|
|
182
|
-
code: error.code || ''
|
|
242
|
+
code: error.code || '',
|
|
183
243
|
};
|
|
184
244
|
}
|
|
185
245
|
|
|
@@ -221,24 +281,24 @@ class ErrorRecoveryHandler {
|
|
|
221
281
|
*/
|
|
222
282
|
identifyRootCause(errorInfo, category) {
|
|
223
283
|
const patterns = this.recoveryPatterns[category];
|
|
224
|
-
|
|
284
|
+
|
|
225
285
|
if (!patterns) {
|
|
226
286
|
return {
|
|
227
287
|
cause: 'Unknown error',
|
|
228
288
|
action: RECOVERY_ACTION.MANUAL_REVIEW,
|
|
229
|
-
matched: false
|
|
289
|
+
matched: false,
|
|
230
290
|
};
|
|
231
291
|
}
|
|
232
292
|
|
|
233
293
|
const message = errorInfo.message;
|
|
234
|
-
|
|
294
|
+
|
|
235
295
|
for (const pattern of patterns.patterns) {
|
|
236
296
|
if (pattern.match.test(message)) {
|
|
237
297
|
return {
|
|
238
298
|
cause: pattern.cause,
|
|
239
299
|
action: pattern.action,
|
|
240
300
|
matched: true,
|
|
241
|
-
pattern: pattern.match.toString()
|
|
301
|
+
pattern: pattern.match.toString(),
|
|
242
302
|
};
|
|
243
303
|
}
|
|
244
304
|
}
|
|
@@ -246,7 +306,7 @@ class ErrorRecoveryHandler {
|
|
|
246
306
|
return {
|
|
247
307
|
cause: 'Unrecognized error pattern',
|
|
248
308
|
action: patterns.defaultAction,
|
|
249
|
-
matched: false
|
|
309
|
+
matched: false,
|
|
250
310
|
};
|
|
251
311
|
}
|
|
252
312
|
|
|
@@ -319,7 +379,7 @@ class ErrorRecoveryHandler {
|
|
|
319
379
|
steps,
|
|
320
380
|
commands,
|
|
321
381
|
estimatedTime: this.estimateRecoveryTime(rootCause.action),
|
|
322
|
-
priority: this.determinePriority(category, context)
|
|
382
|
+
priority: this.determinePriority(category, context),
|
|
323
383
|
};
|
|
324
384
|
}
|
|
325
385
|
|
|
@@ -336,7 +396,7 @@ class ErrorRecoveryHandler {
|
|
|
336
396
|
[RECOVERY_ACTION.UPDATE_CONFIG]: '5-15 minutes',
|
|
337
397
|
[RECOVERY_ACTION.ROLLBACK]: '10-30 minutes',
|
|
338
398
|
[RECOVERY_ACTION.RETRY]: '1-5 minutes',
|
|
339
|
-
[RECOVERY_ACTION.MANUAL_REVIEW]: '30-60 minutes'
|
|
399
|
+
[RECOVERY_ACTION.MANUAL_REVIEW]: '30-60 minutes',
|
|
340
400
|
};
|
|
341
401
|
return estimates[action] || 'Unknown';
|
|
342
402
|
}
|
|
@@ -349,10 +409,10 @@ class ErrorRecoveryHandler {
|
|
|
349
409
|
*/
|
|
350
410
|
determinePriority(category, context) {
|
|
351
411
|
if (context.blocking) return 'critical';
|
|
352
|
-
|
|
412
|
+
|
|
353
413
|
const highPriority = [ERROR_CATEGORY.BUILD_ERROR, ERROR_CATEGORY.TEST_FAILURE];
|
|
354
414
|
const mediumPriority = [ERROR_CATEGORY.TYPE_ERROR, ERROR_CATEGORY.LINT_ERROR];
|
|
355
|
-
|
|
415
|
+
|
|
356
416
|
if (highPriority.includes(category)) return 'high';
|
|
357
417
|
if (mediumPriority.includes(category)) return 'medium';
|
|
358
418
|
return 'low';
|
|
@@ -374,7 +434,7 @@ class ErrorRecoveryHandler {
|
|
|
374
434
|
*/
|
|
375
435
|
recordError(analysis) {
|
|
376
436
|
this.errorHistory.unshift(analysis);
|
|
377
|
-
|
|
437
|
+
|
|
378
438
|
// Trim history
|
|
379
439
|
if (this.errorHistory.length > this.config.maxHistorySize) {
|
|
380
440
|
this.errorHistory = this.errorHistory.slice(0, this.config.maxHistorySize);
|
|
@@ -410,10 +470,10 @@ class ErrorRecoveryHandler {
|
|
|
410
470
|
*/
|
|
411
471
|
async saveAnalysis(analysis) {
|
|
412
472
|
await this.ensureStorageDir();
|
|
413
|
-
|
|
473
|
+
|
|
414
474
|
const fileName = `error-${analysis.id}.json`;
|
|
415
475
|
const filePath = path.join(this.config.storageDir, fileName);
|
|
416
|
-
|
|
476
|
+
|
|
417
477
|
await fs.writeFile(filePath, JSON.stringify(analysis, null, 2), 'utf-8');
|
|
418
478
|
return filePath;
|
|
419
479
|
}
|
|
@@ -426,7 +486,7 @@ class ErrorRecoveryHandler {
|
|
|
426
486
|
async loadAnalysis(id) {
|
|
427
487
|
const fileName = `error-${id}.json`;
|
|
428
488
|
const filePath = path.join(this.config.storageDir, fileName);
|
|
429
|
-
|
|
489
|
+
|
|
430
490
|
try {
|
|
431
491
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
432
492
|
return JSON.parse(content);
|
|
@@ -520,5 +580,5 @@ module.exports = {
|
|
|
520
580
|
ErrorRecoveryHandler,
|
|
521
581
|
createErrorRecoveryHandler,
|
|
522
582
|
ERROR_CATEGORY,
|
|
523
|
-
RECOVERY_ACTION
|
|
583
|
+
RECOVERY_ACTION,
|
|
524
584
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Experiment Report Generator
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Automatically generates experiment reports from test results.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Requirement: IMP-6.2-006-01
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @module enterprise/experiment-report
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ const path = require('path');
|
|
|
17
17
|
const REPORT_FORMAT = {
|
|
18
18
|
MARKDOWN: 'markdown',
|
|
19
19
|
HTML: 'html',
|
|
20
|
-
JSON: 'json'
|
|
20
|
+
JSON: 'json',
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -27,7 +27,7 @@ const TEST_STATUS = {
|
|
|
27
27
|
PASSED: 'passed',
|
|
28
28
|
FAILED: 'failed',
|
|
29
29
|
SKIPPED: 'skipped',
|
|
30
|
-
PENDING: 'pending'
|
|
30
|
+
PENDING: 'pending',
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -45,7 +45,7 @@ class ExperimentReportGenerator {
|
|
|
45
45
|
includeMetrics: config.includeMetrics !== false,
|
|
46
46
|
includeObservations: config.includeObservations !== false,
|
|
47
47
|
includeCodeSnippets: config.includeCodeSnippets !== false,
|
|
48
|
-
...config
|
|
48
|
+
...config,
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -66,23 +66,27 @@ class ExperimentReportGenerator {
|
|
|
66
66
|
version: options.version || '1.0.0',
|
|
67
67
|
generatedAt: new Date().toISOString(),
|
|
68
68
|
author: options.author || 'MUSUBI SDD',
|
|
69
|
-
environment: this.getEnvironment()
|
|
69
|
+
environment: this.getEnvironment(),
|
|
70
70
|
},
|
|
71
71
|
summary: reportData.summary,
|
|
72
72
|
testResults: reportData.tests,
|
|
73
73
|
metrics,
|
|
74
74
|
observations,
|
|
75
|
-
conclusions: options.conclusions || []
|
|
75
|
+
conclusions: options.conclusions || [],
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const formatted = this.formatReport(report, options.format || this.config.format);
|
|
79
|
-
const filePath = await this.saveReport(
|
|
79
|
+
const filePath = await this.saveReport(
|
|
80
|
+
formatted,
|
|
81
|
+
report.metadata,
|
|
82
|
+
options.format || this.config.format
|
|
83
|
+
);
|
|
80
84
|
|
|
81
85
|
return {
|
|
82
86
|
report,
|
|
83
87
|
formatted,
|
|
84
88
|
filePath,
|
|
85
|
-
format: options.format || this.config.format
|
|
89
|
+
format: options.format || this.config.format,
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -108,7 +112,7 @@ class ExperimentReportGenerator {
|
|
|
108
112
|
suite: suite.name || path.basename(suite.testFilePath || ''),
|
|
109
113
|
status,
|
|
110
114
|
duration: test.duration || 0,
|
|
111
|
-
failureMessages: test.failureMessages || []
|
|
115
|
+
failureMessages: test.failureMessages || [],
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
if (status === TEST_STATUS.PASSED) passed++;
|
|
@@ -128,7 +132,7 @@ class ExperimentReportGenerator {
|
|
|
128
132
|
suite: test.suite || 'Default',
|
|
129
133
|
status,
|
|
130
134
|
duration: test.duration || 0,
|
|
131
|
-
failureMessages: test.errors || []
|
|
135
|
+
failureMessages: test.errors || [],
|
|
132
136
|
});
|
|
133
137
|
|
|
134
138
|
if (status === TEST_STATUS.PASSED) passed++;
|
|
@@ -146,7 +150,7 @@ class ExperimentReportGenerator {
|
|
|
146
150
|
skipped,
|
|
147
151
|
pending,
|
|
148
152
|
passRate: tests.length > 0 ? ((passed / tests.length) * 100).toFixed(2) + '%' : '0%',
|
|
149
|
-
duration: testResults.duration || tests.reduce((sum, t) => sum + t.duration, 0)
|
|
153
|
+
duration: testResults.duration || tests.reduce((sum, t) => sum + t.duration, 0),
|
|
150
154
|
};
|
|
151
155
|
|
|
152
156
|
return { tests, summary };
|
|
@@ -175,7 +179,7 @@ class ExperimentReportGenerator {
|
|
|
175
179
|
const metrics = {
|
|
176
180
|
performance: {},
|
|
177
181
|
coverage: {},
|
|
178
|
-
quality: {}
|
|
182
|
+
quality: {},
|
|
179
183
|
};
|
|
180
184
|
|
|
181
185
|
// Performance metrics
|
|
@@ -190,7 +194,9 @@ class ExperimentReportGenerator {
|
|
|
190
194
|
.filter(d => d > 0);
|
|
191
195
|
|
|
192
196
|
if (durations.length > 0) {
|
|
193
|
-
metrics.performance.avgTestDuration = (
|
|
197
|
+
metrics.performance.avgTestDuration = (
|
|
198
|
+
durations.reduce((a, b) => a + b, 0) / durations.length
|
|
199
|
+
).toFixed(2);
|
|
194
200
|
metrics.performance.maxTestDuration = Math.max(...durations);
|
|
195
201
|
metrics.performance.minTestDuration = Math.min(...durations);
|
|
196
202
|
}
|
|
@@ -203,16 +209,15 @@ class ExperimentReportGenerator {
|
|
|
203
209
|
lines: coverage.lines || coverage.line || 'N/A',
|
|
204
210
|
branches: coverage.branches || coverage.branch || 'N/A',
|
|
205
211
|
functions: coverage.functions || coverage.function || 'N/A',
|
|
206
|
-
statements: coverage.statements || coverage.statement || 'N/A'
|
|
212
|
+
statements: coverage.statements || coverage.statement || 'N/A',
|
|
207
213
|
};
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
// Quality metrics
|
|
211
217
|
const summary = this.parseTestResults(testResults).summary;
|
|
212
218
|
metrics.quality.passRate = summary.passRate;
|
|
213
|
-
metrics.quality.failureRate =
|
|
214
|
-
? ((summary.failed / summary.total) * 100).toFixed(2) + '%'
|
|
215
|
-
: '0%';
|
|
219
|
+
metrics.quality.failureRate =
|
|
220
|
+
summary.total > 0 ? ((summary.failed / summary.total) * 100).toFixed(2) + '%' : '0%';
|
|
216
221
|
|
|
217
222
|
return metrics;
|
|
218
223
|
}
|
|
@@ -226,7 +231,7 @@ class ExperimentReportGenerator {
|
|
|
226
231
|
node: process.version,
|
|
227
232
|
platform: process.platform,
|
|
228
233
|
arch: process.arch,
|
|
229
|
-
cwd: process.cwd()
|
|
234
|
+
cwd: process.cwd(),
|
|
230
235
|
};
|
|
231
236
|
}
|
|
232
237
|
|
|
@@ -393,7 +398,7 @@ class ExperimentReportGenerator {
|
|
|
393
398
|
* @returns {string} HTML content
|
|
394
399
|
*/
|
|
395
400
|
formatHTML(report) {
|
|
396
|
-
const { metadata, summary, testResults,
|
|
401
|
+
const { metadata, summary, testResults, observations, conclusions } = report;
|
|
397
402
|
|
|
398
403
|
return `<!DOCTYPE html>
|
|
399
404
|
<html lang="en">
|
|
@@ -433,34 +438,50 @@ class ExperimentReportGenerator {
|
|
|
433
438
|
<tr><td>Duration</td><td>${this.formatDuration(summary.duration)}</td></tr>
|
|
434
439
|
</table>
|
|
435
440
|
|
|
436
|
-
${
|
|
441
|
+
${
|
|
442
|
+
testResults.length > 0
|
|
443
|
+
? `
|
|
437
444
|
<h2>Test Results</h2>
|
|
438
445
|
<table>
|
|
439
446
|
<tr><th>Suite</th><th>Test</th><th>Status</th><th>Duration</th></tr>
|
|
440
|
-
${testResults
|
|
447
|
+
${testResults
|
|
448
|
+
.map(
|
|
449
|
+
t => `
|
|
441
450
|
<tr>
|
|
442
451
|
<td>${t.suite}</td>
|
|
443
452
|
<td>${t.name}</td>
|
|
444
453
|
<td class="${t.status}">${t.status}</td>
|
|
445
454
|
<td>${t.duration}ms</td>
|
|
446
455
|
</tr>
|
|
447
|
-
`
|
|
456
|
+
`
|
|
457
|
+
)
|
|
458
|
+
.join('')}
|
|
448
459
|
</table>
|
|
449
|
-
`
|
|
460
|
+
`
|
|
461
|
+
: ''
|
|
462
|
+
}
|
|
450
463
|
|
|
451
|
-
${
|
|
464
|
+
${
|
|
465
|
+
observations.length > 0
|
|
466
|
+
? `
|
|
452
467
|
<h2>Observations</h2>
|
|
453
468
|
<ul>
|
|
454
469
|
${observations.map(o => `<li>${o}</li>`).join('')}
|
|
455
470
|
</ul>
|
|
456
|
-
`
|
|
471
|
+
`
|
|
472
|
+
: ''
|
|
473
|
+
}
|
|
457
474
|
|
|
458
|
-
${
|
|
475
|
+
${
|
|
476
|
+
conclusions.length > 0
|
|
477
|
+
? `
|
|
459
478
|
<h2>Conclusions</h2>
|
|
460
479
|
<ul>
|
|
461
480
|
${conclusions.map(c => `<li>${c}</li>`).join('')}
|
|
462
481
|
</ul>
|
|
463
|
-
`
|
|
482
|
+
`
|
|
483
|
+
: ''
|
|
484
|
+
}
|
|
464
485
|
|
|
465
486
|
<hr>
|
|
466
487
|
<p class="meta"><em>Generated by MUSUBI SDD Experiment Report Generator</em></p>
|
|
@@ -479,8 +500,8 @@ class ExperimentReportGenerator {
|
|
|
479
500
|
await this.ensureOutputDir();
|
|
480
501
|
|
|
481
502
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
482
|
-
const extension =
|
|
483
|
-
|
|
503
|
+
const extension =
|
|
504
|
+
format === REPORT_FORMAT.HTML ? 'html' : format === REPORT_FORMAT.JSON ? 'json' : 'md';
|
|
484
505
|
const fileName = `experiment-${timestamp}.${extension}`;
|
|
485
506
|
const filePath = path.join(this.config.outputDir, fileName);
|
|
486
507
|
|
|
@@ -503,11 +524,16 @@ class ExperimentReportGenerator {
|
|
|
503
524
|
*/
|
|
504
525
|
getStatusIcon(status) {
|
|
505
526
|
switch (status) {
|
|
506
|
-
case TEST_STATUS.PASSED:
|
|
507
|
-
|
|
508
|
-
case TEST_STATUS.
|
|
509
|
-
|
|
510
|
-
|
|
527
|
+
case TEST_STATUS.PASSED:
|
|
528
|
+
return '✅';
|
|
529
|
+
case TEST_STATUS.FAILED:
|
|
530
|
+
return '❌';
|
|
531
|
+
case TEST_STATUS.SKIPPED:
|
|
532
|
+
return '⏭️';
|
|
533
|
+
case TEST_STATUS.PENDING:
|
|
534
|
+
return '⏳';
|
|
535
|
+
default:
|
|
536
|
+
return '❓';
|
|
511
537
|
}
|
|
512
538
|
}
|
|
513
539
|
|
|
@@ -569,5 +595,5 @@ module.exports = {
|
|
|
569
595
|
ExperimentReportGenerator,
|
|
570
596
|
createExperimentReportGenerator,
|
|
571
597
|
REPORT_FORMAT,
|
|
572
|
-
TEST_STATUS
|
|
598
|
+
TEST_STATUS,
|
|
573
599
|
};
|
package/src/enterprise/index.js
CHANGED
|
@@ -26,21 +26,21 @@ const {
|
|
|
26
26
|
ExperimentReportGenerator,
|
|
27
27
|
createExperimentReportGenerator,
|
|
28
28
|
REPORT_FORMAT,
|
|
29
|
-
TEST_STATUS
|
|
29
|
+
TEST_STATUS,
|
|
30
30
|
} = require('./experiment-report');
|
|
31
31
|
|
|
32
32
|
const {
|
|
33
33
|
TechArticleGenerator,
|
|
34
34
|
createTechArticleGenerator,
|
|
35
35
|
PLATFORM,
|
|
36
|
-
ARTICLE_TYPE
|
|
36
|
+
ARTICLE_TYPE,
|
|
37
37
|
} = require('./tech-article');
|
|
38
38
|
|
|
39
39
|
const {
|
|
40
40
|
ErrorRecoveryHandler,
|
|
41
41
|
createErrorRecoveryHandler,
|
|
42
42
|
ERROR_CATEGORY,
|
|
43
|
-
RECOVERY_ACTION
|
|
43
|
+
RECOVERY_ACTION,
|
|
44
44
|
} = require('./error-recovery');
|
|
45
45
|
|
|
46
46
|
const {
|
|
@@ -48,7 +48,7 @@ const {
|
|
|
48
48
|
createRollbackManager,
|
|
49
49
|
ROLLBACK_LEVEL,
|
|
50
50
|
ROLLBACK_STATUS,
|
|
51
|
-
WORKFLOW_STAGE
|
|
51
|
+
WORKFLOW_STAGE,
|
|
52
52
|
} = require('./rollback-manager');
|
|
53
53
|
|
|
54
54
|
module.exports = {
|
|
@@ -89,5 +89,5 @@ module.exports = {
|
|
|
89
89
|
createRollbackManager,
|
|
90
90
|
ROLLBACK_LEVEL,
|
|
91
91
|
ROLLBACK_STATUS,
|
|
92
|
-
WORKFLOW_STAGE
|
|
92
|
+
WORKFLOW_STAGE,
|
|
93
93
|
};
|