musubi-sdd 5.1.0 → 5.6.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 +106 -48
- package/README.md +110 -32
- package/bin/musubi-analyze.js +74 -67
- package/bin/musubi-browser.js +27 -26
- package/bin/musubi-change.js +48 -47
- package/bin/musubi-checkpoint.js +10 -7
- package/bin/musubi-convert.js +25 -25
- package/bin/musubi-costs.js +27 -10
- package/bin/musubi-gui.js +52 -46
- package/bin/musubi-init.js +1952 -10
- package/bin/musubi-orchestrate.js +327 -239
- package/bin/musubi-remember.js +69 -56
- package/bin/musubi-resolve.js +53 -45
- package/bin/musubi-trace.js +51 -22
- package/bin/musubi-validate.js +39 -30
- package/bin/musubi-workflow.js +33 -34
- package/bin/musubi.js +39 -2
- package/package.json +1 -1
- package/src/agents/agent-loop.js +94 -95
- package/src/agents/agentic/code-generator.js +119 -109
- package/src/agents/agentic/code-reviewer.js +105 -108
- package/src/agents/agentic/index.js +4 -4
- package/src/agents/browser/action-executor.js +13 -13
- package/src/agents/browser/ai-comparator.js +11 -10
- package/src/agents/browser/context-manager.js +6 -6
- package/src/agents/browser/index.js +5 -5
- package/src/agents/browser/nl-parser.js +31 -46
- package/src/agents/browser/screenshot.js +2 -2
- package/src/agents/browser/test-generator.js +6 -4
- package/src/agents/function-tool.js +71 -65
- package/src/agents/index.js +7 -7
- package/src/agents/schema-generator.js +98 -94
- package/src/analyzers/ast-extractor.js +158 -146
- package/src/analyzers/codegraph-auto-update.js +858 -0
- package/src/analyzers/complexity-analyzer.js +536 -0
- package/src/analyzers/context-optimizer.js +241 -126
- package/src/analyzers/impact-analyzer.js +1 -1
- package/src/analyzers/large-project-analyzer.js +766 -0
- package/src/analyzers/repository-map.js +77 -81
- package/src/analyzers/security-analyzer.js +19 -11
- package/src/analyzers/stuck-detector.js +19 -17
- package/src/converters/index.js +78 -57
- package/src/converters/ir/types.js +12 -12
- package/src/converters/parsers/musubi-parser.js +134 -126
- package/src/converters/parsers/openapi-parser.js +70 -53
- package/src/converters/parsers/speckit-parser.js +239 -175
- package/src/converters/writers/musubi-writer.js +123 -118
- package/src/converters/writers/speckit-writer.js +124 -113
- package/src/generators/rust-migration-generator.js +512 -0
- package/src/gui/public/index.html +1365 -1211
- package/src/gui/server.js +41 -40
- package/src/gui/services/file-watcher.js +23 -8
- package/src/gui/services/project-scanner.js +26 -20
- package/src/gui/services/replanning-service.js +27 -23
- package/src/gui/services/traceability-service.js +8 -8
- package/src/gui/services/workflow-service.js +14 -7
- package/src/index.js +151 -0
- package/src/integrations/cicd.js +90 -104
- package/src/integrations/codegraph-mcp.js +643 -0
- package/src/integrations/documentation.js +142 -103
- package/src/integrations/examples.js +95 -80
- package/src/integrations/github-client.js +17 -17
- package/src/integrations/index.js +5 -5
- package/src/integrations/mcp/index.js +21 -21
- package/src/integrations/mcp/mcp-context-provider.js +76 -78
- package/src/integrations/mcp/mcp-discovery.js +74 -72
- package/src/integrations/mcp/mcp-tool-registry.js +99 -94
- package/src/integrations/mcp-connector.js +70 -66
- package/src/integrations/platforms.js +50 -49
- package/src/integrations/tool-discovery.js +37 -31
- package/src/llm-providers/anthropic-provider.js +11 -11
- package/src/llm-providers/base-provider.js +16 -18
- package/src/llm-providers/copilot-provider.js +22 -19
- package/src/llm-providers/index.js +26 -25
- package/src/llm-providers/ollama-provider.js +11 -11
- package/src/llm-providers/openai-provider.js +12 -12
- package/src/managers/agent-memory.js +36 -24
- package/src/managers/checkpoint-manager.js +4 -8
- package/src/managers/delta-spec.js +19 -19
- package/src/managers/index.js +13 -4
- package/src/managers/memory-condenser.js +35 -45
- package/src/managers/repo-skill-manager.js +57 -31
- package/src/managers/skill-loader.js +25 -22
- package/src/managers/skill-tools.js +36 -72
- package/src/managers/workflow.js +30 -22
- package/src/monitoring/cost-tracker.js +48 -46
- package/src/monitoring/incident-manager.js +116 -106
- package/src/monitoring/index.js +144 -134
- package/src/monitoring/observability.js +75 -62
- package/src/monitoring/quality-dashboard.js +45 -41
- package/src/monitoring/release-manager.js +63 -53
- package/src/orchestration/agent-skill-binding.js +39 -47
- package/src/orchestration/error-handler.js +65 -107
- package/src/orchestration/guardrails/base-guardrail.js +26 -24
- package/src/orchestration/guardrails/guardrail-rules.js +50 -64
- package/src/orchestration/guardrails/index.js +5 -5
- package/src/orchestration/guardrails/input-guardrail.js +58 -45
- package/src/orchestration/guardrails/output-guardrail.js +104 -81
- package/src/orchestration/guardrails/safety-check.js +79 -79
- package/src/orchestration/index.js +38 -55
- package/src/orchestration/mcp-tool-adapters.js +96 -99
- package/src/orchestration/orchestration-engine.js +21 -21
- package/src/orchestration/pattern-registry.js +60 -45
- package/src/orchestration/patterns/auto.js +34 -47
- package/src/orchestration/patterns/group-chat.js +59 -65
- package/src/orchestration/patterns/handoff.js +67 -65
- package/src/orchestration/patterns/human-in-loop.js +51 -72
- package/src/orchestration/patterns/nested.js +25 -40
- package/src/orchestration/patterns/sequential.js +35 -34
- package/src/orchestration/patterns/swarm.js +63 -56
- package/src/orchestration/patterns/triage.js +150 -109
- package/src/orchestration/reasoning/index.js +9 -9
- package/src/orchestration/reasoning/planning-engine.js +143 -140
- package/src/orchestration/reasoning/reasoning-engine.js +206 -144
- package/src/orchestration/reasoning/self-correction.js +121 -128
- package/src/orchestration/replanning/adaptive-goal-modifier.js +107 -112
- package/src/orchestration/replanning/alternative-generator.js +37 -42
- package/src/orchestration/replanning/config.js +63 -59
- package/src/orchestration/replanning/goal-progress-tracker.js +98 -100
- package/src/orchestration/replanning/index.js +24 -20
- package/src/orchestration/replanning/plan-evaluator.js +49 -50
- package/src/orchestration/replanning/plan-monitor.js +32 -28
- package/src/orchestration/replanning/proactive-path-optimizer.js +175 -178
- package/src/orchestration/replanning/replan-history.js +33 -26
- package/src/orchestration/replanning/replanning-engine.js +106 -108
- package/src/orchestration/skill-executor.js +107 -109
- package/src/orchestration/skill-registry.js +85 -89
- package/src/orchestration/workflow-examples.js +228 -231
- package/src/orchestration/workflow-executor.js +65 -68
- package/src/orchestration/workflow-orchestrator.js +72 -73
- package/src/phase4-integration.js +47 -40
- package/src/phase5-integration.js +89 -30
- package/src/reporters/coverage-report.js +82 -30
- package/src/reporters/hierarchical-reporter.js +498 -0
- package/src/reporters/traceability-matrix-report.js +29 -20
- package/src/resolvers/issue-resolver.js +43 -31
- package/src/steering/advanced-validation.js +133 -124
- package/src/steering/auto-updater.js +60 -73
- package/src/steering/index.js +6 -6
- package/src/steering/quality-metrics.js +41 -35
- package/src/steering/steering-auto-update.js +83 -86
- package/src/steering/steering-validator.js +98 -106
- package/src/steering/template-constraints.js +53 -54
- package/src/templates/agents/claude-code/CLAUDE.md +32 -32
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +13 -5
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/mlops-guide.md +23 -23
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/model-card-template.md +60 -41
- package/src/templates/agents/claude-code/skills/api-designer/api-patterns.md +27 -19
- package/src/templates/agents/claude-code/skills/api-designer/openapi-template.md +11 -7
- package/src/templates/agents/claude-code/skills/bug-hunter/SKILL.md +4 -3
- package/src/templates/agents/claude-code/skills/bug-hunter/root-cause-analysis.md +37 -15
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/dependency-graph-patterns.md +36 -42
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/impact-analysis-template.md +69 -60
- package/src/templates/agents/claude-code/skills/cloud-architect/aws-patterns.md +31 -38
- package/src/templates/agents/claude-code/skills/cloud-architect/azure-patterns.md +28 -23
- package/src/templates/agents/claude-code/skills/code-reviewer/SKILL.md +61 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/best-practices.md +27 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/review-checklist.md +29 -10
- package/src/templates/agents/claude-code/skills/code-reviewer/review-standards.md +29 -24
- package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +8 -6
- package/src/templates/agents/claude-code/skills/constitution-enforcer/constitutional-articles.md +62 -26
- package/src/templates/agents/claude-code/skills/constitution-enforcer/phase-minus-one-gates.md +35 -16
- package/src/templates/agents/claude-code/skills/database-administrator/backup-recovery.md +27 -17
- package/src/templates/agents/claude-code/skills/database-administrator/tuning-guide.md +25 -20
- package/src/templates/agents/claude-code/skills/database-schema-designer/schema-patterns.md +39 -22
- package/src/templates/agents/claude-code/skills/devops-engineer/ci-cd-templates.md +25 -22
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +24 -21
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +148 -63
- package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +35 -16
- package/src/templates/agents/claude-code/skills/orchestrator/selection-matrix.md +69 -64
- package/src/templates/agents/claude-code/skills/performance-engineer/optimization-playbook.md +47 -47
- package/src/templates/agents/claude-code/skills/performance-optimizer/SKILL.md +69 -0
- package/src/templates/agents/claude-code/skills/performance-optimizer/benchmark-template.md +63 -45
- package/src/templates/agents/claude-code/skills/performance-optimizer/optimization-patterns.md +33 -35
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +7 -6
- package/src/templates/agents/claude-code/skills/project-manager/agile-ceremonies.md +47 -28
- package/src/templates/agents/claude-code/skills/project-manager/project-templates.md +94 -78
- package/src/templates/agents/claude-code/skills/quality-assurance/SKILL.md +20 -17
- package/src/templates/agents/claude-code/skills/quality-assurance/qa-plan-template.md +63 -49
- package/src/templates/agents/claude-code/skills/release-coordinator/SKILL.md +5 -5
- package/src/templates/agents/claude-code/skills/release-coordinator/feature-flag-guide.md +30 -26
- package/src/templates/agents/claude-code/skills/release-coordinator/release-plan-template.md +67 -35
- package/src/templates/agents/claude-code/skills/requirements-analyst/ears-format.md +54 -42
- package/src/templates/agents/claude-code/skills/requirements-analyst/validation-rules.md +36 -33
- package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +77 -19
- package/src/templates/agents/claude-code/skills/security-auditor/audit-checklists.md +24 -24
- package/src/templates/agents/claude-code/skills/security-auditor/owasp-top-10.md +61 -20
- package/src/templates/agents/claude-code/skills/security-auditor/vulnerability-patterns.md +43 -11
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/incident-response-template.md +55 -25
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/observability-patterns.md +78 -68
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/slo-sli-guide.md +73 -53
- package/src/templates/agents/claude-code/skills/software-developer/solid-principles.md +83 -37
- package/src/templates/agents/claude-code/skills/software-developer/test-first-workflow.md +38 -31
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/steering/auto-update-rules.md +31 -0
- package/src/templates/agents/claude-code/skills/system-architect/adr-template.md +25 -7
- package/src/templates/agents/claude-code/skills/system-architect/c4-model-guide.md +74 -61
- package/src/templates/agents/claude-code/skills/technical-writer/doc-templates/documentation-templates.md +70 -52
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +2 -0
- package/src/templates/agents/claude-code/skills/test-engineer/ears-test-mapping.md +75 -71
- package/src/templates/agents/claude-code/skills/test-engineer/test-types.md +85 -63
- package/src/templates/agents/claude-code/skills/traceability-auditor/coverage-matrix-template.md +39 -36
- package/src/templates/agents/claude-code/skills/traceability-auditor/gap-detection-rules.md +22 -17
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/accessibility-guidelines.md +49 -75
- package/src/templates/agents/claude-code/skills/ui-ux-designer/design-system-components.md +71 -59
- package/src/templates/agents/codex/AGENTS.md +74 -42
- package/src/templates/agents/cursor/AGENTS.md +74 -42
- package/src/templates/agents/gemini-cli/GEMINI.md +74 -42
- package/src/templates/agents/github-copilot/AGENTS.md +83 -51
- package/src/templates/agents/qwen-code/QWEN.md +74 -42
- package/src/templates/agents/windsurf/AGENTS.md +74 -42
- package/src/templates/architectures/README.md +41 -0
- package/src/templates/architectures/clean-architecture/README.md +113 -0
- package/src/templates/architectures/event-driven/README.md +162 -0
- package/src/templates/architectures/hexagonal/README.md +130 -0
- package/src/templates/index.js +6 -1
- package/src/templates/locale-manager.js +16 -16
- package/src/templates/shared/delta-spec-template.md +20 -13
- package/src/templates/shared/github-actions/musubi-issue-resolver.yml +5 -5
- package/src/templates/shared/github-actions/musubi-security-check.yml +3 -3
- package/src/templates/shared/github-actions/musubi-validate.yml +4 -4
- package/src/templates/shared/steering/structure.md +95 -0
- package/src/templates/skills/browser-agent.md +21 -16
- package/src/templates/skills/web-gui.md +8 -0
- package/src/templates/template-constraints.js +50 -53
- package/src/validators/advanced-validation.js +30 -36
- package/src/validators/constitutional-validator.js +77 -73
- package/src/validators/critic-system.js +49 -59
- package/src/validators/delta-format.js +59 -55
- package/src/validators/traceability-validator.js +7 -11
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Incident Manager - Incident response and management
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Provides incident management capabilities:
|
|
5
5
|
* - Incident lifecycle management
|
|
6
6
|
* - Runbook execution
|
|
7
7
|
* - Post-mortem generation
|
|
8
8
|
* - On-call management
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* Part of MUSUBI v5.0.0 - Production Readiness
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* @module monitoring/incident-manager
|
|
13
13
|
* @version 1.0.0
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* @traceability
|
|
16
16
|
* - Requirement: REQ-P5-002 (Incident Management)
|
|
17
17
|
* - Design: docs/design/tdd-musubi-v5.0.0.md#3.2
|
|
@@ -28,7 +28,7 @@ const IncidentSeverity = {
|
|
|
28
28
|
SEV2: 'sev2', // High - significant impact
|
|
29
29
|
SEV3: 'sev3', // Medium - limited impact
|
|
30
30
|
SEV4: 'sev4', // Low - minimal impact
|
|
31
|
-
SEV5: 'sev5'
|
|
31
|
+
SEV5: 'sev5', // Informational
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -42,7 +42,7 @@ const IncidentStatus = {
|
|
|
42
42
|
MITIGATING: 'mitigating',
|
|
43
43
|
MONITORING: 'monitoring',
|
|
44
44
|
RESOLVED: 'resolved',
|
|
45
|
-
CLOSED: 'closed'
|
|
45
|
+
CLOSED: 'closed',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -53,7 +53,7 @@ const StepStatus = {
|
|
|
53
53
|
IN_PROGRESS: 'in-progress',
|
|
54
54
|
COMPLETED: 'completed',
|
|
55
55
|
FAILED: 'failed',
|
|
56
|
-
SKIPPED: 'skipped'
|
|
56
|
+
SKIPPED: 'skipped',
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -66,35 +66,37 @@ class Incident {
|
|
|
66
66
|
this.description = options.description || '';
|
|
67
67
|
this.severity = options.severity || IncidentSeverity.SEV3;
|
|
68
68
|
this.status = options.status || IncidentStatus.DETECTED;
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
this.detectedAt = options.detectedAt || new Date();
|
|
71
71
|
this.acknowledgedAt = null;
|
|
72
72
|
this.mitigatedAt = null;
|
|
73
73
|
this.resolvedAt = null;
|
|
74
74
|
this.closedAt = null;
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
this.affectedServices = options.affectedServices || [];
|
|
77
77
|
this.impactSummary = options.impactSummary || '';
|
|
78
78
|
this.customerImpact = options.customerImpact || {
|
|
79
79
|
affected: 0,
|
|
80
|
-
percentage: 0
|
|
80
|
+
percentage: 0,
|
|
81
81
|
};
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
this.assignee = options.assignee || null;
|
|
84
84
|
this.responders = options.responders || [];
|
|
85
85
|
this.commander = options.commander || null;
|
|
86
|
-
|
|
87
|
-
this.timeline = [
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
|
|
87
|
+
this.timeline = [
|
|
88
|
+
{
|
|
89
|
+
timestamp: this.detectedAt,
|
|
90
|
+
action: 'detected',
|
|
91
|
+
description: 'Incident detected',
|
|
92
|
+
actor: 'system',
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
94
96
|
this.rootCause = null;
|
|
95
97
|
this.resolution = null;
|
|
96
98
|
this.postMortem = null;
|
|
97
|
-
|
|
99
|
+
|
|
98
100
|
this.relatedIncidents = options.relatedIncidents || [];
|
|
99
101
|
this.tags = options.tags || [];
|
|
100
102
|
this.metadata = options.metadata || {};
|
|
@@ -107,15 +109,15 @@ class Incident {
|
|
|
107
109
|
if (this.acknowledgedAt) {
|
|
108
110
|
throw new Error('Incident already acknowledged');
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
this.acknowledgedAt = new Date();
|
|
112
114
|
this.status = IncidentStatus.TRIAGING;
|
|
113
115
|
this.assignee = responder;
|
|
114
|
-
|
|
116
|
+
|
|
115
117
|
if (!this.responders.includes(responder)) {
|
|
116
118
|
this.responders.push(responder);
|
|
117
119
|
}
|
|
118
|
-
|
|
120
|
+
|
|
119
121
|
this._addTimelineEntry('acknowledged', `Acknowledged by ${responder}`, responder);
|
|
120
122
|
return this;
|
|
121
123
|
}
|
|
@@ -137,7 +139,11 @@ class Incident {
|
|
|
137
139
|
setCommander(commander) {
|
|
138
140
|
this.commander = commander;
|
|
139
141
|
this.addResponder(commander, 'commander');
|
|
140
|
-
this._addTimelineEntry(
|
|
142
|
+
this._addTimelineEntry(
|
|
143
|
+
'commander_assigned',
|
|
144
|
+
`${commander} assigned as incident commander`,
|
|
145
|
+
commander
|
|
146
|
+
);
|
|
141
147
|
return this;
|
|
142
148
|
}
|
|
143
149
|
|
|
@@ -147,9 +153,13 @@ class Incident {
|
|
|
147
153
|
updateStatus(newStatus, note = '', actor = 'system') {
|
|
148
154
|
const previousStatus = this.status;
|
|
149
155
|
this.status = newStatus;
|
|
150
|
-
|
|
151
|
-
this._addTimelineEntry(
|
|
152
|
-
|
|
156
|
+
|
|
157
|
+
this._addTimelineEntry(
|
|
158
|
+
'status_change',
|
|
159
|
+
`Status changed from ${previousStatus} to ${newStatus}. ${note}`,
|
|
160
|
+
actor
|
|
161
|
+
);
|
|
162
|
+
|
|
153
163
|
// Update timestamps
|
|
154
164
|
if (newStatus === IncidentStatus.MITIGATING && !this.mitigatedAt) {
|
|
155
165
|
// Record when mitigation started
|
|
@@ -160,7 +170,7 @@ class Incident {
|
|
|
160
170
|
if (newStatus === IncidentStatus.CLOSED) {
|
|
161
171
|
this.closedAt = new Date();
|
|
162
172
|
}
|
|
163
|
-
|
|
173
|
+
|
|
164
174
|
return this;
|
|
165
175
|
}
|
|
166
176
|
|
|
@@ -170,7 +180,11 @@ class Incident {
|
|
|
170
180
|
updateSeverity(newSeverity, reason = '', actor = 'system') {
|
|
171
181
|
const previousSeverity = this.severity;
|
|
172
182
|
this.severity = newSeverity;
|
|
173
|
-
this._addTimelineEntry(
|
|
183
|
+
this._addTimelineEntry(
|
|
184
|
+
'severity_change',
|
|
185
|
+
`Severity changed from ${previousSeverity} to ${newSeverity}. ${reason}`,
|
|
186
|
+
actor
|
|
187
|
+
);
|
|
174
188
|
return this;
|
|
175
189
|
}
|
|
176
190
|
|
|
@@ -208,21 +222,17 @@ class Incident {
|
|
|
208
222
|
*/
|
|
209
223
|
getMetrics() {
|
|
210
224
|
const now = new Date();
|
|
211
|
-
|
|
225
|
+
|
|
212
226
|
return {
|
|
213
|
-
timeToAcknowledge: this.acknowledgedAt
|
|
214
|
-
? (this.acknowledgedAt - this.detectedAt) / 1000
|
|
215
|
-
: null,
|
|
216
|
-
timeToMitigate: this.mitigatedAt
|
|
217
|
-
? (this.mitigatedAt - this.detectedAt) / 1000
|
|
218
|
-
: null,
|
|
219
|
-
timeToResolve: this.resolvedAt
|
|
220
|
-
? (this.resolvedAt - this.detectedAt) / 1000
|
|
227
|
+
timeToAcknowledge: this.acknowledgedAt
|
|
228
|
+
? (this.acknowledgedAt - this.detectedAt) / 1000
|
|
221
229
|
: null,
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
timeToMitigate: this.mitigatedAt ? (this.mitigatedAt - this.detectedAt) / 1000 : null,
|
|
231
|
+
timeToResolve: this.resolvedAt ? (this.resolvedAt - this.detectedAt) / 1000 : null,
|
|
232
|
+
totalDuration: this.closedAt
|
|
233
|
+
? (this.closedAt - this.detectedAt) / 1000
|
|
224
234
|
: (now - this.detectedAt) / 1000,
|
|
225
|
-
isOpen: !this.closedAt
|
|
235
|
+
isOpen: !this.closedAt,
|
|
226
236
|
};
|
|
227
237
|
}
|
|
228
238
|
|
|
@@ -235,7 +245,7 @@ class Incident {
|
|
|
235
245
|
timestamp: new Date(),
|
|
236
246
|
action,
|
|
237
247
|
description,
|
|
238
|
-
actor
|
|
248
|
+
actor,
|
|
239
249
|
});
|
|
240
250
|
}
|
|
241
251
|
|
|
@@ -270,7 +280,7 @@ class Incident {
|
|
|
270
280
|
rootCause: this.rootCause,
|
|
271
281
|
resolution: this.resolution,
|
|
272
282
|
tags: this.tags,
|
|
273
|
-
metrics: this.getMetrics()
|
|
283
|
+
metrics: this.getMetrics(),
|
|
274
284
|
};
|
|
275
285
|
}
|
|
276
286
|
}
|
|
@@ -287,7 +297,7 @@ class Runbook {
|
|
|
287
297
|
this.category = options.category || 'general';
|
|
288
298
|
this.tags = options.tags || [];
|
|
289
299
|
this.estimatedDuration = options.estimatedDuration || '15 minutes';
|
|
290
|
-
|
|
300
|
+
|
|
291
301
|
this.steps = (options.steps || []).map((step, index) => ({
|
|
292
302
|
id: step.id || `step-${index + 1}`,
|
|
293
303
|
order: step.order || index + 1,
|
|
@@ -297,9 +307,9 @@ class Runbook {
|
|
|
297
307
|
expectedOutput: step.expectedOutput || null,
|
|
298
308
|
onFailure: step.onFailure || 'abort', // abort, continue, retry
|
|
299
309
|
timeout: step.timeout || 300, // seconds
|
|
300
|
-
requiresConfirmation: step.requiresConfirmation || false
|
|
310
|
+
requiresConfirmation: step.requiresConfirmation || false,
|
|
301
311
|
}));
|
|
302
|
-
|
|
312
|
+
|
|
303
313
|
this.metadata = options.metadata || {};
|
|
304
314
|
}
|
|
305
315
|
|
|
@@ -313,7 +323,7 @@ class Runbook {
|
|
|
313
323
|
tags: this.tags,
|
|
314
324
|
estimatedDuration: this.estimatedDuration,
|
|
315
325
|
steps: this.steps,
|
|
316
|
-
metadata: this.metadata
|
|
326
|
+
metadata: this.metadata,
|
|
317
327
|
};
|
|
318
328
|
}
|
|
319
329
|
}
|
|
@@ -329,16 +339,16 @@ class RunbookExecution {
|
|
|
329
339
|
this.startedAt = new Date();
|
|
330
340
|
this.completedAt = null;
|
|
331
341
|
this.status = 'running';
|
|
332
|
-
|
|
342
|
+
|
|
333
343
|
this.stepResults = runbook.steps.map(step => ({
|
|
334
344
|
stepId: step.id,
|
|
335
345
|
status: StepStatus.PENDING,
|
|
336
346
|
startedAt: null,
|
|
337
347
|
completedAt: null,
|
|
338
348
|
output: null,
|
|
339
|
-
error: null
|
|
349
|
+
error: null,
|
|
340
350
|
}));
|
|
341
|
-
|
|
351
|
+
|
|
342
352
|
this.currentStepIndex = 0;
|
|
343
353
|
}
|
|
344
354
|
|
|
@@ -365,13 +375,13 @@ class RunbookExecution {
|
|
|
365
375
|
result.output = output;
|
|
366
376
|
this.currentStepIndex++;
|
|
367
377
|
}
|
|
368
|
-
|
|
378
|
+
|
|
369
379
|
// Check if all steps completed
|
|
370
380
|
if (this.currentStepIndex >= this.runbook.steps.length) {
|
|
371
381
|
this.status = 'completed';
|
|
372
382
|
this.completedAt = new Date();
|
|
373
383
|
}
|
|
374
|
-
|
|
384
|
+
|
|
375
385
|
return this;
|
|
376
386
|
}
|
|
377
387
|
|
|
@@ -385,7 +395,7 @@ class RunbookExecution {
|
|
|
385
395
|
result.completedAt = new Date();
|
|
386
396
|
result.error = error;
|
|
387
397
|
}
|
|
388
|
-
|
|
398
|
+
|
|
389
399
|
// Get step config to determine action
|
|
390
400
|
const step = this.runbook.steps.find(s => s.id === stepId);
|
|
391
401
|
if (step && step.onFailure === 'abort') {
|
|
@@ -394,7 +404,7 @@ class RunbookExecution {
|
|
|
394
404
|
} else if (step && step.onFailure === 'continue') {
|
|
395
405
|
this.currentStepIndex++;
|
|
396
406
|
}
|
|
397
|
-
|
|
407
|
+
|
|
398
408
|
return this;
|
|
399
409
|
}
|
|
400
410
|
|
|
@@ -426,16 +436,15 @@ class RunbookExecution {
|
|
|
426
436
|
* Get execution progress
|
|
427
437
|
*/
|
|
428
438
|
getProgress() {
|
|
429
|
-
const completed = this.stepResults.filter(
|
|
430
|
-
r.status === StepStatus.COMPLETED ||
|
|
431
|
-
r.status === StepStatus.SKIPPED
|
|
439
|
+
const completed = this.stepResults.filter(
|
|
440
|
+
r => r.status === StepStatus.COMPLETED || r.status === StepStatus.SKIPPED
|
|
432
441
|
).length;
|
|
433
|
-
|
|
442
|
+
|
|
434
443
|
return {
|
|
435
444
|
total: this.runbook.steps.length,
|
|
436
445
|
completed,
|
|
437
446
|
percentage: Math.round((completed / this.runbook.steps.length) * 100),
|
|
438
|
-
currentStep: this.getCurrentStep()
|
|
447
|
+
currentStep: this.getCurrentStep(),
|
|
439
448
|
};
|
|
440
449
|
}
|
|
441
450
|
|
|
@@ -449,7 +458,7 @@ class RunbookExecution {
|
|
|
449
458
|
completedAt: this.completedAt,
|
|
450
459
|
status: this.status,
|
|
451
460
|
stepResults: this.stepResults,
|
|
452
|
-
progress: this.getProgress()
|
|
461
|
+
progress: this.getProgress(),
|
|
453
462
|
};
|
|
454
463
|
}
|
|
455
464
|
}
|
|
@@ -464,30 +473,30 @@ class PostMortem {
|
|
|
464
473
|
this.title = `Post-Mortem: ${incident.title}`;
|
|
465
474
|
this.createdAt = new Date();
|
|
466
475
|
this.status = 'draft';
|
|
467
|
-
|
|
476
|
+
|
|
468
477
|
// Auto-populate from incident
|
|
469
478
|
this.summary = {
|
|
470
479
|
severity: incident.severity,
|
|
471
480
|
duration: incident.getMetrics().totalDuration,
|
|
472
481
|
affectedServices: incident.affectedServices,
|
|
473
|
-
customerImpact: incident.customerImpact
|
|
482
|
+
customerImpact: incident.customerImpact,
|
|
474
483
|
};
|
|
475
|
-
|
|
484
|
+
|
|
476
485
|
this.timeline = incident.timeline;
|
|
477
486
|
this.rootCause = incident.rootCause || 'TBD';
|
|
478
487
|
this.resolution = incident.resolution || 'TBD';
|
|
479
|
-
|
|
488
|
+
|
|
480
489
|
this.detection = {
|
|
481
490
|
method: 'TBD',
|
|
482
|
-
timeToDetect: incident.getMetrics().timeToAcknowledge
|
|
491
|
+
timeToDetect: incident.getMetrics().timeToAcknowledge,
|
|
483
492
|
};
|
|
484
|
-
|
|
493
|
+
|
|
485
494
|
this.response = {
|
|
486
495
|
responders: incident.responders,
|
|
487
496
|
commander: incident.commander,
|
|
488
|
-
timeToMitigate: incident.getMetrics().timeToMitigate
|
|
497
|
+
timeToMitigate: incident.getMetrics().timeToMitigate,
|
|
489
498
|
};
|
|
490
|
-
|
|
499
|
+
|
|
491
500
|
this.actionItems = [];
|
|
492
501
|
this.lessonsLearned = [];
|
|
493
502
|
this.whatWentWell = [];
|
|
@@ -506,7 +515,7 @@ class PostMortem {
|
|
|
506
515
|
priority: item.priority || 'medium',
|
|
507
516
|
dueDate: item.dueDate || null,
|
|
508
517
|
status: 'open',
|
|
509
|
-
createdAt: new Date()
|
|
518
|
+
createdAt: new Date(),
|
|
510
519
|
});
|
|
511
520
|
return this;
|
|
512
521
|
}
|
|
@@ -621,7 +630,7 @@ class PostMortem {
|
|
|
621
630
|
whatWentWell: this.whatWentWell,
|
|
622
631
|
whatWentPoorly: this.whatWentPoorly,
|
|
623
632
|
lessonsLearned: this.lessonsLearned,
|
|
624
|
-
actionItems: this.actionItems
|
|
633
|
+
actionItems: this.actionItems,
|
|
625
634
|
};
|
|
626
635
|
}
|
|
627
636
|
}
|
|
@@ -636,16 +645,16 @@ class IncidentManager extends EventEmitter {
|
|
|
636
645
|
this.runbooks = new Map();
|
|
637
646
|
this.executions = new Map();
|
|
638
647
|
this.postMortems = new Map();
|
|
639
|
-
|
|
648
|
+
|
|
640
649
|
this.oncall = {
|
|
641
650
|
primary: options.primaryOncall || null,
|
|
642
651
|
secondary: options.secondaryOncall || null,
|
|
643
|
-
escalation: options.escalation || []
|
|
652
|
+
escalation: options.escalation || [],
|
|
644
653
|
};
|
|
645
|
-
|
|
654
|
+
|
|
646
655
|
this.options = {
|
|
647
656
|
autoAcknowledgeTimeout: options.autoAcknowledgeTimeout || 300, // 5 minutes
|
|
648
|
-
...options
|
|
657
|
+
...options,
|
|
649
658
|
};
|
|
650
659
|
}
|
|
651
660
|
|
|
@@ -656,16 +665,16 @@ class IncidentManager extends EventEmitter {
|
|
|
656
665
|
const incident = options instanceof Incident ? options : new Incident(options);
|
|
657
666
|
this.incidents.set(incident.id, incident);
|
|
658
667
|
this.emit('incidentCreated', incident);
|
|
659
|
-
|
|
668
|
+
|
|
660
669
|
// Auto-notify on-call
|
|
661
670
|
if (this.oncall.primary) {
|
|
662
671
|
this.emit('notify', {
|
|
663
672
|
type: 'incident',
|
|
664
673
|
incident,
|
|
665
|
-
recipient: this.oncall.primary
|
|
674
|
+
recipient: this.oncall.primary,
|
|
666
675
|
});
|
|
667
676
|
}
|
|
668
|
-
|
|
677
|
+
|
|
669
678
|
return incident;
|
|
670
679
|
}
|
|
671
680
|
|
|
@@ -681,7 +690,7 @@ class IncidentManager extends EventEmitter {
|
|
|
681
690
|
*/
|
|
682
691
|
listIncidents(filter = {}) {
|
|
683
692
|
let incidents = [...this.incidents.values()];
|
|
684
|
-
|
|
693
|
+
|
|
685
694
|
if (filter.status) {
|
|
686
695
|
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
687
696
|
incidents = incidents.filter(i => statuses.includes(i.status));
|
|
@@ -691,12 +700,11 @@ class IncidentManager extends EventEmitter {
|
|
|
691
700
|
incidents = incidents.filter(i => severities.includes(i.severity));
|
|
692
701
|
}
|
|
693
702
|
if (filter.open) {
|
|
694
|
-
incidents = incidents.filter(
|
|
695
|
-
i.status !== IncidentStatus.RESOLVED &&
|
|
696
|
-
i.status !== IncidentStatus.CLOSED
|
|
703
|
+
incidents = incidents.filter(
|
|
704
|
+
i => i.status !== IncidentStatus.RESOLVED && i.status !== IncidentStatus.CLOSED
|
|
697
705
|
);
|
|
698
706
|
}
|
|
699
|
-
|
|
707
|
+
|
|
700
708
|
return incidents.map(i => i.toJSON());
|
|
701
709
|
}
|
|
702
710
|
|
|
@@ -706,7 +714,7 @@ class IncidentManager extends EventEmitter {
|
|
|
706
714
|
acknowledgeIncident(incidentId, responder) {
|
|
707
715
|
const incident = this.incidents.get(incidentId);
|
|
708
716
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
709
|
-
|
|
717
|
+
|
|
710
718
|
incident.acknowledge(responder);
|
|
711
719
|
this.emit('incidentAcknowledged', incident);
|
|
712
720
|
return incident;
|
|
@@ -718,7 +726,7 @@ class IncidentManager extends EventEmitter {
|
|
|
718
726
|
updateIncidentStatus(incidentId, newStatus, note = '', actor = 'system') {
|
|
719
727
|
const incident = this.incidents.get(incidentId);
|
|
720
728
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
721
|
-
|
|
729
|
+
|
|
722
730
|
incident.updateStatus(newStatus, note, actor);
|
|
723
731
|
this.emit('incidentStatusChanged', { incident, newStatus });
|
|
724
732
|
return incident;
|
|
@@ -730,7 +738,7 @@ class IncidentManager extends EventEmitter {
|
|
|
730
738
|
resolveIncident(incidentId, resolution, actor = 'system') {
|
|
731
739
|
const incident = this.incidents.get(incidentId);
|
|
732
740
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
733
|
-
|
|
741
|
+
|
|
734
742
|
incident.setResolution(resolution, actor);
|
|
735
743
|
this.emit('incidentResolved', incident);
|
|
736
744
|
return incident;
|
|
@@ -758,14 +766,14 @@ class IncidentManager extends EventEmitter {
|
|
|
758
766
|
*/
|
|
759
767
|
listRunbooks(filter = {}) {
|
|
760
768
|
let runbooks = [...this.runbooks.values()];
|
|
761
|
-
|
|
769
|
+
|
|
762
770
|
if (filter.category) {
|
|
763
771
|
runbooks = runbooks.filter(r => r.category === filter.category);
|
|
764
772
|
}
|
|
765
773
|
if (filter.tag) {
|
|
766
774
|
runbooks = runbooks.filter(r => r.tags.includes(filter.tag));
|
|
767
775
|
}
|
|
768
|
-
|
|
776
|
+
|
|
769
777
|
return runbooks.map(r => r.toJSON());
|
|
770
778
|
}
|
|
771
779
|
|
|
@@ -775,16 +783,16 @@ class IncidentManager extends EventEmitter {
|
|
|
775
783
|
executeRunbook(runbookId, incident = null) {
|
|
776
784
|
const runbook = this.runbooks.get(runbookId);
|
|
777
785
|
if (!runbook) throw new Error(`Runbook not found: ${runbookId}`);
|
|
778
|
-
|
|
786
|
+
|
|
779
787
|
const execution = new RunbookExecution(runbook, incident);
|
|
780
788
|
this.executions.set(execution.id, execution);
|
|
781
789
|
this.emit('runbookExecutionStarted', execution);
|
|
782
|
-
|
|
790
|
+
|
|
783
791
|
// Link to incident if provided
|
|
784
792
|
if (incident) {
|
|
785
793
|
incident.addUpdate(`Runbook "${runbook.name}" execution started`, 'system');
|
|
786
794
|
}
|
|
787
|
-
|
|
795
|
+
|
|
788
796
|
return execution;
|
|
789
797
|
}
|
|
790
798
|
|
|
@@ -801,11 +809,11 @@ class IncidentManager extends EventEmitter {
|
|
|
801
809
|
createPostMortem(incidentId) {
|
|
802
810
|
const incident = this.incidents.get(incidentId);
|
|
803
811
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
804
|
-
|
|
812
|
+
|
|
805
813
|
const postMortem = new PostMortem(incident);
|
|
806
814
|
this.postMortems.set(postMortem.id, postMortem);
|
|
807
815
|
incident.postMortem = postMortem.id;
|
|
808
|
-
|
|
816
|
+
|
|
809
817
|
this.emit('postMortemCreated', postMortem);
|
|
810
818
|
return postMortem;
|
|
811
819
|
}
|
|
@@ -840,10 +848,8 @@ class IncidentManager extends EventEmitter {
|
|
|
840
848
|
getStatistics(options = {}) {
|
|
841
849
|
const incidents = [...this.incidents.values()];
|
|
842
850
|
const { since } = options;
|
|
843
|
-
|
|
844
|
-
const filtered = since
|
|
845
|
-
? incidents.filter(i => i.detectedAt >= since)
|
|
846
|
-
: incidents;
|
|
851
|
+
|
|
852
|
+
const filtered = since ? incidents.filter(i => i.detectedAt >= since) : incidents;
|
|
847
853
|
|
|
848
854
|
const metrics = filtered.map(i => i.getMetrics());
|
|
849
855
|
const acknowledged = metrics.filter(m => m.timeToAcknowledge !== null);
|
|
@@ -851,15 +857,19 @@ class IncidentManager extends EventEmitter {
|
|
|
851
857
|
|
|
852
858
|
return {
|
|
853
859
|
total: filtered.length,
|
|
854
|
-
open: filtered.filter(
|
|
860
|
+
open: filtered.filter(
|
|
861
|
+
i => i.status !== IncidentStatus.CLOSED && i.status !== IncidentStatus.RESOLVED
|
|
862
|
+
).length,
|
|
855
863
|
bySeverity: this._countBy(filtered, 'severity'),
|
|
856
864
|
byStatus: this._countBy(filtered, 'status'),
|
|
857
|
-
mttr:
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
865
|
+
mttr:
|
|
866
|
+
resolved.length > 0
|
|
867
|
+
? resolved.reduce((sum, m) => sum + m.timeToResolve, 0) / resolved.length
|
|
868
|
+
: null,
|
|
869
|
+
mtta:
|
|
870
|
+
acknowledged.length > 0
|
|
871
|
+
? acknowledged.reduce((sum, m) => sum + m.timeToAcknowledge, 0) / acknowledged.length
|
|
872
|
+
: null,
|
|
863
873
|
};
|
|
864
874
|
}
|
|
865
875
|
|
|
@@ -889,12 +899,12 @@ module.exports = {
|
|
|
889
899
|
RunbookExecution,
|
|
890
900
|
PostMortem,
|
|
891
901
|
IncidentManager,
|
|
892
|
-
|
|
902
|
+
|
|
893
903
|
// Constants
|
|
894
904
|
IncidentSeverity,
|
|
895
905
|
IncidentStatus,
|
|
896
906
|
StepStatus,
|
|
897
|
-
|
|
907
|
+
|
|
898
908
|
// Factory
|
|
899
|
-
createIncidentManager
|
|
909
|
+
createIncidentManager,
|
|
900
910
|
};
|