musubi-sdd 5.0.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 +164 -145
- package/src/analyzers/codegraph-auto-update.js +858 -0
- package/src/analyzers/complexity-analyzer.js +536 -0
- package/src/analyzers/context-optimizer.js +247 -125
- package/src/analyzers/impact-analyzer.js +1 -1
- package/src/analyzers/large-project-analyzer.js +766 -0
- package/src/analyzers/repository-map.js +83 -80
- 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 +53 -44
- package/src/monitoring/incident-manager.js +123 -103
- package/src/monitoring/index.js +144 -134
- package/src/monitoring/observability.js +82 -59
- package/src/monitoring/quality-dashboard.js +51 -39
- package/src/monitoring/release-manager.js +70 -50
- 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,11 +1,21 @@
|
|
|
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
|
+
*
|
|
10
|
+
* Part of MUSUBI v5.0.0 - Production Readiness
|
|
11
|
+
*
|
|
12
|
+
* @module monitoring/incident-manager
|
|
13
|
+
* @version 1.0.0
|
|
14
|
+
*
|
|
15
|
+
* @traceability
|
|
16
|
+
* - Requirement: REQ-P5-002 (Incident Management)
|
|
17
|
+
* - Design: docs/design/tdd-musubi-v5.0.0.md#3.2
|
|
18
|
+
* - Test: tests/monitoring/incident-manager.test.js
|
|
9
19
|
*/
|
|
10
20
|
|
|
11
21
|
const { EventEmitter } = require('events');
|
|
@@ -18,7 +28,7 @@ const IncidentSeverity = {
|
|
|
18
28
|
SEV2: 'sev2', // High - significant impact
|
|
19
29
|
SEV3: 'sev3', // Medium - limited impact
|
|
20
30
|
SEV4: 'sev4', // Low - minimal impact
|
|
21
|
-
SEV5: 'sev5'
|
|
31
|
+
SEV5: 'sev5', // Informational
|
|
22
32
|
};
|
|
23
33
|
|
|
24
34
|
/**
|
|
@@ -32,7 +42,7 @@ const IncidentStatus = {
|
|
|
32
42
|
MITIGATING: 'mitigating',
|
|
33
43
|
MONITORING: 'monitoring',
|
|
34
44
|
RESOLVED: 'resolved',
|
|
35
|
-
CLOSED: 'closed'
|
|
45
|
+
CLOSED: 'closed',
|
|
36
46
|
};
|
|
37
47
|
|
|
38
48
|
/**
|
|
@@ -43,7 +53,7 @@ const StepStatus = {
|
|
|
43
53
|
IN_PROGRESS: 'in-progress',
|
|
44
54
|
COMPLETED: 'completed',
|
|
45
55
|
FAILED: 'failed',
|
|
46
|
-
SKIPPED: 'skipped'
|
|
56
|
+
SKIPPED: 'skipped',
|
|
47
57
|
};
|
|
48
58
|
|
|
49
59
|
/**
|
|
@@ -56,35 +66,37 @@ class Incident {
|
|
|
56
66
|
this.description = options.description || '';
|
|
57
67
|
this.severity = options.severity || IncidentSeverity.SEV3;
|
|
58
68
|
this.status = options.status || IncidentStatus.DETECTED;
|
|
59
|
-
|
|
69
|
+
|
|
60
70
|
this.detectedAt = options.detectedAt || new Date();
|
|
61
71
|
this.acknowledgedAt = null;
|
|
62
72
|
this.mitigatedAt = null;
|
|
63
73
|
this.resolvedAt = null;
|
|
64
74
|
this.closedAt = null;
|
|
65
|
-
|
|
75
|
+
|
|
66
76
|
this.affectedServices = options.affectedServices || [];
|
|
67
77
|
this.impactSummary = options.impactSummary || '';
|
|
68
78
|
this.customerImpact = options.customerImpact || {
|
|
69
79
|
affected: 0,
|
|
70
|
-
percentage: 0
|
|
80
|
+
percentage: 0,
|
|
71
81
|
};
|
|
72
|
-
|
|
82
|
+
|
|
73
83
|
this.assignee = options.assignee || null;
|
|
74
84
|
this.responders = options.responders || [];
|
|
75
85
|
this.commander = options.commander || null;
|
|
76
|
-
|
|
77
|
-
this.timeline = [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
|
|
87
|
+
this.timeline = [
|
|
88
|
+
{
|
|
89
|
+
timestamp: this.detectedAt,
|
|
90
|
+
action: 'detected',
|
|
91
|
+
description: 'Incident detected',
|
|
92
|
+
actor: 'system',
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
84
96
|
this.rootCause = null;
|
|
85
97
|
this.resolution = null;
|
|
86
98
|
this.postMortem = null;
|
|
87
|
-
|
|
99
|
+
|
|
88
100
|
this.relatedIncidents = options.relatedIncidents || [];
|
|
89
101
|
this.tags = options.tags || [];
|
|
90
102
|
this.metadata = options.metadata || {};
|
|
@@ -97,15 +109,15 @@ class Incident {
|
|
|
97
109
|
if (this.acknowledgedAt) {
|
|
98
110
|
throw new Error('Incident already acknowledged');
|
|
99
111
|
}
|
|
100
|
-
|
|
112
|
+
|
|
101
113
|
this.acknowledgedAt = new Date();
|
|
102
114
|
this.status = IncidentStatus.TRIAGING;
|
|
103
115
|
this.assignee = responder;
|
|
104
|
-
|
|
116
|
+
|
|
105
117
|
if (!this.responders.includes(responder)) {
|
|
106
118
|
this.responders.push(responder);
|
|
107
119
|
}
|
|
108
|
-
|
|
120
|
+
|
|
109
121
|
this._addTimelineEntry('acknowledged', `Acknowledged by ${responder}`, responder);
|
|
110
122
|
return this;
|
|
111
123
|
}
|
|
@@ -127,7 +139,11 @@ class Incident {
|
|
|
127
139
|
setCommander(commander) {
|
|
128
140
|
this.commander = commander;
|
|
129
141
|
this.addResponder(commander, 'commander');
|
|
130
|
-
this._addTimelineEntry(
|
|
142
|
+
this._addTimelineEntry(
|
|
143
|
+
'commander_assigned',
|
|
144
|
+
`${commander} assigned as incident commander`,
|
|
145
|
+
commander
|
|
146
|
+
);
|
|
131
147
|
return this;
|
|
132
148
|
}
|
|
133
149
|
|
|
@@ -137,9 +153,13 @@ class Incident {
|
|
|
137
153
|
updateStatus(newStatus, note = '', actor = 'system') {
|
|
138
154
|
const previousStatus = this.status;
|
|
139
155
|
this.status = newStatus;
|
|
140
|
-
|
|
141
|
-
this._addTimelineEntry(
|
|
142
|
-
|
|
156
|
+
|
|
157
|
+
this._addTimelineEntry(
|
|
158
|
+
'status_change',
|
|
159
|
+
`Status changed from ${previousStatus} to ${newStatus}. ${note}`,
|
|
160
|
+
actor
|
|
161
|
+
);
|
|
162
|
+
|
|
143
163
|
// Update timestamps
|
|
144
164
|
if (newStatus === IncidentStatus.MITIGATING && !this.mitigatedAt) {
|
|
145
165
|
// Record when mitigation started
|
|
@@ -150,7 +170,7 @@ class Incident {
|
|
|
150
170
|
if (newStatus === IncidentStatus.CLOSED) {
|
|
151
171
|
this.closedAt = new Date();
|
|
152
172
|
}
|
|
153
|
-
|
|
173
|
+
|
|
154
174
|
return this;
|
|
155
175
|
}
|
|
156
176
|
|
|
@@ -160,7 +180,11 @@ class Incident {
|
|
|
160
180
|
updateSeverity(newSeverity, reason = '', actor = 'system') {
|
|
161
181
|
const previousSeverity = this.severity;
|
|
162
182
|
this.severity = newSeverity;
|
|
163
|
-
this._addTimelineEntry(
|
|
183
|
+
this._addTimelineEntry(
|
|
184
|
+
'severity_change',
|
|
185
|
+
`Severity changed from ${previousSeverity} to ${newSeverity}. ${reason}`,
|
|
186
|
+
actor
|
|
187
|
+
);
|
|
164
188
|
return this;
|
|
165
189
|
}
|
|
166
190
|
|
|
@@ -198,21 +222,17 @@ class Incident {
|
|
|
198
222
|
*/
|
|
199
223
|
getMetrics() {
|
|
200
224
|
const now = new Date();
|
|
201
|
-
|
|
225
|
+
|
|
202
226
|
return {
|
|
203
|
-
timeToAcknowledge: this.acknowledgedAt
|
|
204
|
-
? (this.acknowledgedAt - this.detectedAt) / 1000
|
|
205
|
-
: null,
|
|
206
|
-
timeToMitigate: this.mitigatedAt
|
|
207
|
-
? (this.mitigatedAt - this.detectedAt) / 1000
|
|
208
|
-
: null,
|
|
209
|
-
timeToResolve: this.resolvedAt
|
|
210
|
-
? (this.resolvedAt - this.detectedAt) / 1000
|
|
227
|
+
timeToAcknowledge: this.acknowledgedAt
|
|
228
|
+
? (this.acknowledgedAt - this.detectedAt) / 1000
|
|
211
229
|
: null,
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
214
234
|
: (now - this.detectedAt) / 1000,
|
|
215
|
-
isOpen: !this.closedAt
|
|
235
|
+
isOpen: !this.closedAt,
|
|
216
236
|
};
|
|
217
237
|
}
|
|
218
238
|
|
|
@@ -225,7 +245,7 @@ class Incident {
|
|
|
225
245
|
timestamp: new Date(),
|
|
226
246
|
action,
|
|
227
247
|
description,
|
|
228
|
-
actor
|
|
248
|
+
actor,
|
|
229
249
|
});
|
|
230
250
|
}
|
|
231
251
|
|
|
@@ -260,7 +280,7 @@ class Incident {
|
|
|
260
280
|
rootCause: this.rootCause,
|
|
261
281
|
resolution: this.resolution,
|
|
262
282
|
tags: this.tags,
|
|
263
|
-
metrics: this.getMetrics()
|
|
283
|
+
metrics: this.getMetrics(),
|
|
264
284
|
};
|
|
265
285
|
}
|
|
266
286
|
}
|
|
@@ -277,7 +297,7 @@ class Runbook {
|
|
|
277
297
|
this.category = options.category || 'general';
|
|
278
298
|
this.tags = options.tags || [];
|
|
279
299
|
this.estimatedDuration = options.estimatedDuration || '15 minutes';
|
|
280
|
-
|
|
300
|
+
|
|
281
301
|
this.steps = (options.steps || []).map((step, index) => ({
|
|
282
302
|
id: step.id || `step-${index + 1}`,
|
|
283
303
|
order: step.order || index + 1,
|
|
@@ -287,9 +307,9 @@ class Runbook {
|
|
|
287
307
|
expectedOutput: step.expectedOutput || null,
|
|
288
308
|
onFailure: step.onFailure || 'abort', // abort, continue, retry
|
|
289
309
|
timeout: step.timeout || 300, // seconds
|
|
290
|
-
requiresConfirmation: step.requiresConfirmation || false
|
|
310
|
+
requiresConfirmation: step.requiresConfirmation || false,
|
|
291
311
|
}));
|
|
292
|
-
|
|
312
|
+
|
|
293
313
|
this.metadata = options.metadata || {};
|
|
294
314
|
}
|
|
295
315
|
|
|
@@ -303,7 +323,7 @@ class Runbook {
|
|
|
303
323
|
tags: this.tags,
|
|
304
324
|
estimatedDuration: this.estimatedDuration,
|
|
305
325
|
steps: this.steps,
|
|
306
|
-
metadata: this.metadata
|
|
326
|
+
metadata: this.metadata,
|
|
307
327
|
};
|
|
308
328
|
}
|
|
309
329
|
}
|
|
@@ -319,16 +339,16 @@ class RunbookExecution {
|
|
|
319
339
|
this.startedAt = new Date();
|
|
320
340
|
this.completedAt = null;
|
|
321
341
|
this.status = 'running';
|
|
322
|
-
|
|
342
|
+
|
|
323
343
|
this.stepResults = runbook.steps.map(step => ({
|
|
324
344
|
stepId: step.id,
|
|
325
345
|
status: StepStatus.PENDING,
|
|
326
346
|
startedAt: null,
|
|
327
347
|
completedAt: null,
|
|
328
348
|
output: null,
|
|
329
|
-
error: null
|
|
349
|
+
error: null,
|
|
330
350
|
}));
|
|
331
|
-
|
|
351
|
+
|
|
332
352
|
this.currentStepIndex = 0;
|
|
333
353
|
}
|
|
334
354
|
|
|
@@ -355,13 +375,13 @@ class RunbookExecution {
|
|
|
355
375
|
result.output = output;
|
|
356
376
|
this.currentStepIndex++;
|
|
357
377
|
}
|
|
358
|
-
|
|
378
|
+
|
|
359
379
|
// Check if all steps completed
|
|
360
380
|
if (this.currentStepIndex >= this.runbook.steps.length) {
|
|
361
381
|
this.status = 'completed';
|
|
362
382
|
this.completedAt = new Date();
|
|
363
383
|
}
|
|
364
|
-
|
|
384
|
+
|
|
365
385
|
return this;
|
|
366
386
|
}
|
|
367
387
|
|
|
@@ -375,7 +395,7 @@ class RunbookExecution {
|
|
|
375
395
|
result.completedAt = new Date();
|
|
376
396
|
result.error = error;
|
|
377
397
|
}
|
|
378
|
-
|
|
398
|
+
|
|
379
399
|
// Get step config to determine action
|
|
380
400
|
const step = this.runbook.steps.find(s => s.id === stepId);
|
|
381
401
|
if (step && step.onFailure === 'abort') {
|
|
@@ -384,7 +404,7 @@ class RunbookExecution {
|
|
|
384
404
|
} else if (step && step.onFailure === 'continue') {
|
|
385
405
|
this.currentStepIndex++;
|
|
386
406
|
}
|
|
387
|
-
|
|
407
|
+
|
|
388
408
|
return this;
|
|
389
409
|
}
|
|
390
410
|
|
|
@@ -416,16 +436,15 @@ class RunbookExecution {
|
|
|
416
436
|
* Get execution progress
|
|
417
437
|
*/
|
|
418
438
|
getProgress() {
|
|
419
|
-
const completed = this.stepResults.filter(
|
|
420
|
-
r.status === StepStatus.COMPLETED ||
|
|
421
|
-
r.status === StepStatus.SKIPPED
|
|
439
|
+
const completed = this.stepResults.filter(
|
|
440
|
+
r => r.status === StepStatus.COMPLETED || r.status === StepStatus.SKIPPED
|
|
422
441
|
).length;
|
|
423
|
-
|
|
442
|
+
|
|
424
443
|
return {
|
|
425
444
|
total: this.runbook.steps.length,
|
|
426
445
|
completed,
|
|
427
446
|
percentage: Math.round((completed / this.runbook.steps.length) * 100),
|
|
428
|
-
currentStep: this.getCurrentStep()
|
|
447
|
+
currentStep: this.getCurrentStep(),
|
|
429
448
|
};
|
|
430
449
|
}
|
|
431
450
|
|
|
@@ -439,7 +458,7 @@ class RunbookExecution {
|
|
|
439
458
|
completedAt: this.completedAt,
|
|
440
459
|
status: this.status,
|
|
441
460
|
stepResults: this.stepResults,
|
|
442
|
-
progress: this.getProgress()
|
|
461
|
+
progress: this.getProgress(),
|
|
443
462
|
};
|
|
444
463
|
}
|
|
445
464
|
}
|
|
@@ -454,30 +473,30 @@ class PostMortem {
|
|
|
454
473
|
this.title = `Post-Mortem: ${incident.title}`;
|
|
455
474
|
this.createdAt = new Date();
|
|
456
475
|
this.status = 'draft';
|
|
457
|
-
|
|
476
|
+
|
|
458
477
|
// Auto-populate from incident
|
|
459
478
|
this.summary = {
|
|
460
479
|
severity: incident.severity,
|
|
461
480
|
duration: incident.getMetrics().totalDuration,
|
|
462
481
|
affectedServices: incident.affectedServices,
|
|
463
|
-
customerImpact: incident.customerImpact
|
|
482
|
+
customerImpact: incident.customerImpact,
|
|
464
483
|
};
|
|
465
|
-
|
|
484
|
+
|
|
466
485
|
this.timeline = incident.timeline;
|
|
467
486
|
this.rootCause = incident.rootCause || 'TBD';
|
|
468
487
|
this.resolution = incident.resolution || 'TBD';
|
|
469
|
-
|
|
488
|
+
|
|
470
489
|
this.detection = {
|
|
471
490
|
method: 'TBD',
|
|
472
|
-
timeToDetect: incident.getMetrics().timeToAcknowledge
|
|
491
|
+
timeToDetect: incident.getMetrics().timeToAcknowledge,
|
|
473
492
|
};
|
|
474
|
-
|
|
493
|
+
|
|
475
494
|
this.response = {
|
|
476
495
|
responders: incident.responders,
|
|
477
496
|
commander: incident.commander,
|
|
478
|
-
timeToMitigate: incident.getMetrics().timeToMitigate
|
|
497
|
+
timeToMitigate: incident.getMetrics().timeToMitigate,
|
|
479
498
|
};
|
|
480
|
-
|
|
499
|
+
|
|
481
500
|
this.actionItems = [];
|
|
482
501
|
this.lessonsLearned = [];
|
|
483
502
|
this.whatWentWell = [];
|
|
@@ -496,7 +515,7 @@ class PostMortem {
|
|
|
496
515
|
priority: item.priority || 'medium',
|
|
497
516
|
dueDate: item.dueDate || null,
|
|
498
517
|
status: 'open',
|
|
499
|
-
createdAt: new Date()
|
|
518
|
+
createdAt: new Date(),
|
|
500
519
|
});
|
|
501
520
|
return this;
|
|
502
521
|
}
|
|
@@ -611,7 +630,7 @@ class PostMortem {
|
|
|
611
630
|
whatWentWell: this.whatWentWell,
|
|
612
631
|
whatWentPoorly: this.whatWentPoorly,
|
|
613
632
|
lessonsLearned: this.lessonsLearned,
|
|
614
|
-
actionItems: this.actionItems
|
|
633
|
+
actionItems: this.actionItems,
|
|
615
634
|
};
|
|
616
635
|
}
|
|
617
636
|
}
|
|
@@ -626,16 +645,16 @@ class IncidentManager extends EventEmitter {
|
|
|
626
645
|
this.runbooks = new Map();
|
|
627
646
|
this.executions = new Map();
|
|
628
647
|
this.postMortems = new Map();
|
|
629
|
-
|
|
648
|
+
|
|
630
649
|
this.oncall = {
|
|
631
650
|
primary: options.primaryOncall || null,
|
|
632
651
|
secondary: options.secondaryOncall || null,
|
|
633
|
-
escalation: options.escalation || []
|
|
652
|
+
escalation: options.escalation || [],
|
|
634
653
|
};
|
|
635
|
-
|
|
654
|
+
|
|
636
655
|
this.options = {
|
|
637
656
|
autoAcknowledgeTimeout: options.autoAcknowledgeTimeout || 300, // 5 minutes
|
|
638
|
-
...options
|
|
657
|
+
...options,
|
|
639
658
|
};
|
|
640
659
|
}
|
|
641
660
|
|
|
@@ -646,16 +665,16 @@ class IncidentManager extends EventEmitter {
|
|
|
646
665
|
const incident = options instanceof Incident ? options : new Incident(options);
|
|
647
666
|
this.incidents.set(incident.id, incident);
|
|
648
667
|
this.emit('incidentCreated', incident);
|
|
649
|
-
|
|
668
|
+
|
|
650
669
|
// Auto-notify on-call
|
|
651
670
|
if (this.oncall.primary) {
|
|
652
671
|
this.emit('notify', {
|
|
653
672
|
type: 'incident',
|
|
654
673
|
incident,
|
|
655
|
-
recipient: this.oncall.primary
|
|
674
|
+
recipient: this.oncall.primary,
|
|
656
675
|
});
|
|
657
676
|
}
|
|
658
|
-
|
|
677
|
+
|
|
659
678
|
return incident;
|
|
660
679
|
}
|
|
661
680
|
|
|
@@ -671,7 +690,7 @@ class IncidentManager extends EventEmitter {
|
|
|
671
690
|
*/
|
|
672
691
|
listIncidents(filter = {}) {
|
|
673
692
|
let incidents = [...this.incidents.values()];
|
|
674
|
-
|
|
693
|
+
|
|
675
694
|
if (filter.status) {
|
|
676
695
|
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
677
696
|
incidents = incidents.filter(i => statuses.includes(i.status));
|
|
@@ -681,12 +700,11 @@ class IncidentManager extends EventEmitter {
|
|
|
681
700
|
incidents = incidents.filter(i => severities.includes(i.severity));
|
|
682
701
|
}
|
|
683
702
|
if (filter.open) {
|
|
684
|
-
incidents = incidents.filter(
|
|
685
|
-
i.status !== IncidentStatus.RESOLVED &&
|
|
686
|
-
i.status !== IncidentStatus.CLOSED
|
|
703
|
+
incidents = incidents.filter(
|
|
704
|
+
i => i.status !== IncidentStatus.RESOLVED && i.status !== IncidentStatus.CLOSED
|
|
687
705
|
);
|
|
688
706
|
}
|
|
689
|
-
|
|
707
|
+
|
|
690
708
|
return incidents.map(i => i.toJSON());
|
|
691
709
|
}
|
|
692
710
|
|
|
@@ -696,7 +714,7 @@ class IncidentManager extends EventEmitter {
|
|
|
696
714
|
acknowledgeIncident(incidentId, responder) {
|
|
697
715
|
const incident = this.incidents.get(incidentId);
|
|
698
716
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
699
|
-
|
|
717
|
+
|
|
700
718
|
incident.acknowledge(responder);
|
|
701
719
|
this.emit('incidentAcknowledged', incident);
|
|
702
720
|
return incident;
|
|
@@ -708,7 +726,7 @@ class IncidentManager extends EventEmitter {
|
|
|
708
726
|
updateIncidentStatus(incidentId, newStatus, note = '', actor = 'system') {
|
|
709
727
|
const incident = this.incidents.get(incidentId);
|
|
710
728
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
711
|
-
|
|
729
|
+
|
|
712
730
|
incident.updateStatus(newStatus, note, actor);
|
|
713
731
|
this.emit('incidentStatusChanged', { incident, newStatus });
|
|
714
732
|
return incident;
|
|
@@ -720,7 +738,7 @@ class IncidentManager extends EventEmitter {
|
|
|
720
738
|
resolveIncident(incidentId, resolution, actor = 'system') {
|
|
721
739
|
const incident = this.incidents.get(incidentId);
|
|
722
740
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
723
|
-
|
|
741
|
+
|
|
724
742
|
incident.setResolution(resolution, actor);
|
|
725
743
|
this.emit('incidentResolved', incident);
|
|
726
744
|
return incident;
|
|
@@ -748,14 +766,14 @@ class IncidentManager extends EventEmitter {
|
|
|
748
766
|
*/
|
|
749
767
|
listRunbooks(filter = {}) {
|
|
750
768
|
let runbooks = [...this.runbooks.values()];
|
|
751
|
-
|
|
769
|
+
|
|
752
770
|
if (filter.category) {
|
|
753
771
|
runbooks = runbooks.filter(r => r.category === filter.category);
|
|
754
772
|
}
|
|
755
773
|
if (filter.tag) {
|
|
756
774
|
runbooks = runbooks.filter(r => r.tags.includes(filter.tag));
|
|
757
775
|
}
|
|
758
|
-
|
|
776
|
+
|
|
759
777
|
return runbooks.map(r => r.toJSON());
|
|
760
778
|
}
|
|
761
779
|
|
|
@@ -765,16 +783,16 @@ class IncidentManager extends EventEmitter {
|
|
|
765
783
|
executeRunbook(runbookId, incident = null) {
|
|
766
784
|
const runbook = this.runbooks.get(runbookId);
|
|
767
785
|
if (!runbook) throw new Error(`Runbook not found: ${runbookId}`);
|
|
768
|
-
|
|
786
|
+
|
|
769
787
|
const execution = new RunbookExecution(runbook, incident);
|
|
770
788
|
this.executions.set(execution.id, execution);
|
|
771
789
|
this.emit('runbookExecutionStarted', execution);
|
|
772
|
-
|
|
790
|
+
|
|
773
791
|
// Link to incident if provided
|
|
774
792
|
if (incident) {
|
|
775
793
|
incident.addUpdate(`Runbook "${runbook.name}" execution started`, 'system');
|
|
776
794
|
}
|
|
777
|
-
|
|
795
|
+
|
|
778
796
|
return execution;
|
|
779
797
|
}
|
|
780
798
|
|
|
@@ -791,11 +809,11 @@ class IncidentManager extends EventEmitter {
|
|
|
791
809
|
createPostMortem(incidentId) {
|
|
792
810
|
const incident = this.incidents.get(incidentId);
|
|
793
811
|
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
794
|
-
|
|
812
|
+
|
|
795
813
|
const postMortem = new PostMortem(incident);
|
|
796
814
|
this.postMortems.set(postMortem.id, postMortem);
|
|
797
815
|
incident.postMortem = postMortem.id;
|
|
798
|
-
|
|
816
|
+
|
|
799
817
|
this.emit('postMortemCreated', postMortem);
|
|
800
818
|
return postMortem;
|
|
801
819
|
}
|
|
@@ -830,10 +848,8 @@ class IncidentManager extends EventEmitter {
|
|
|
830
848
|
getStatistics(options = {}) {
|
|
831
849
|
const incidents = [...this.incidents.values()];
|
|
832
850
|
const { since } = options;
|
|
833
|
-
|
|
834
|
-
const filtered = since
|
|
835
|
-
? incidents.filter(i => i.detectedAt >= since)
|
|
836
|
-
: incidents;
|
|
851
|
+
|
|
852
|
+
const filtered = since ? incidents.filter(i => i.detectedAt >= since) : incidents;
|
|
837
853
|
|
|
838
854
|
const metrics = filtered.map(i => i.getMetrics());
|
|
839
855
|
const acknowledged = metrics.filter(m => m.timeToAcknowledge !== null);
|
|
@@ -841,15 +857,19 @@ class IncidentManager extends EventEmitter {
|
|
|
841
857
|
|
|
842
858
|
return {
|
|
843
859
|
total: filtered.length,
|
|
844
|
-
open: filtered.filter(
|
|
860
|
+
open: filtered.filter(
|
|
861
|
+
i => i.status !== IncidentStatus.CLOSED && i.status !== IncidentStatus.RESOLVED
|
|
862
|
+
).length,
|
|
845
863
|
bySeverity: this._countBy(filtered, 'severity'),
|
|
846
864
|
byStatus: this._countBy(filtered, 'status'),
|
|
847
|
-
mttr:
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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,
|
|
853
873
|
};
|
|
854
874
|
}
|
|
855
875
|
|
|
@@ -879,12 +899,12 @@ module.exports = {
|
|
|
879
899
|
RunbookExecution,
|
|
880
900
|
PostMortem,
|
|
881
901
|
IncidentManager,
|
|
882
|
-
|
|
902
|
+
|
|
883
903
|
// Constants
|
|
884
904
|
IncidentSeverity,
|
|
885
905
|
IncidentStatus,
|
|
886
906
|
StepStatus,
|
|
887
|
-
|
|
907
|
+
|
|
888
908
|
// Factory
|
|
889
|
-
createIncidentManager
|
|
909
|
+
createIncidentManager,
|
|
890
910
|
};
|