musubi-sdd 3.10.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Metrics Dashboard
|
|
3
|
+
* カバレッジメトリクス、Constitutional準拠メトリクス、プロジェクトヘルス指標
|
|
4
|
+
*
|
|
5
|
+
* Part of MUSUBI v5.0.0 - Production Readiness
|
|
6
|
+
*
|
|
7
|
+
* @module monitoring/quality-dashboard
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*
|
|
10
|
+
* @traceability
|
|
11
|
+
* - Requirement: REQ-P5-001 (Quality Dashboard)
|
|
12
|
+
* - Design: docs/design/tdd-musubi-v5.0.0.md#3.1
|
|
13
|
+
* - Test: tests/monitoring/quality-dashboard.test.js
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const EventEmitter = require('events');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Metric categories
|
|
20
|
+
*/
|
|
21
|
+
const METRIC_CATEGORY = {
|
|
22
|
+
COVERAGE: 'coverage',
|
|
23
|
+
CONSTITUTIONAL: 'constitutional',
|
|
24
|
+
QUALITY: 'quality',
|
|
25
|
+
HEALTH: 'health',
|
|
26
|
+
PERFORMANCE: 'performance',
|
|
27
|
+
CUSTOM: 'custom'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Health status levels
|
|
32
|
+
*/
|
|
33
|
+
const HEALTH_STATUS = {
|
|
34
|
+
HEALTHY: 'healthy', // 80-100%
|
|
35
|
+
WARNING: 'warning', // 50-79%
|
|
36
|
+
CRITICAL: 'critical', // 20-49%
|
|
37
|
+
FAILING: 'failing' // <20%
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Constitutional articles
|
|
42
|
+
*/
|
|
43
|
+
const CONSTITUTIONAL_ARTICLES = {
|
|
44
|
+
SINGLE_SOURCE_OF_TRUTH: 'article-1',
|
|
45
|
+
EXPLICIT_CONTRACTS: 'article-2',
|
|
46
|
+
TRACEABILITY: 'article-3',
|
|
47
|
+
AUTOMATED_VALIDATION: 'article-4',
|
|
48
|
+
MACHINE_READABLE: 'article-5',
|
|
49
|
+
INCREMENTAL_ADOPTION: 'article-6',
|
|
50
|
+
SEPARATION_OF_CONCERNS: 'article-7',
|
|
51
|
+
FEEDBACK_LOOPS: 'article-8',
|
|
52
|
+
GOVERNANCE: 'article-9'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Quality Dashboard Engine
|
|
57
|
+
*/
|
|
58
|
+
class QualityDashboard extends EventEmitter {
|
|
59
|
+
/**
|
|
60
|
+
* @param {Object} options
|
|
61
|
+
* @param {Object} options.thresholds - Custom thresholds
|
|
62
|
+
* @param {boolean} options.autoCollect - Auto-collect metrics
|
|
63
|
+
* @param {number} options.collectInterval - Collection interval (ms)
|
|
64
|
+
*/
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
super();
|
|
67
|
+
|
|
68
|
+
this.thresholds = {
|
|
69
|
+
coverage: { healthy: 80, warning: 50, critical: 20 },
|
|
70
|
+
constitutional: { healthy: 90, warning: 70, critical: 50 },
|
|
71
|
+
quality: { healthy: 80, warning: 60, critical: 40 },
|
|
72
|
+
...options.thresholds
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.autoCollect = options.autoCollect ?? false;
|
|
76
|
+
this.collectInterval = options.collectInterval ?? 60000;
|
|
77
|
+
|
|
78
|
+
this.metrics = new Map();
|
|
79
|
+
this.history = [];
|
|
80
|
+
this.collectors = new Map();
|
|
81
|
+
this.intervalId = null;
|
|
82
|
+
|
|
83
|
+
// Initialize default collectors
|
|
84
|
+
this.initializeDefaultCollectors();
|
|
85
|
+
|
|
86
|
+
if (this.autoCollect) {
|
|
87
|
+
this.startAutoCollection();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Initialize default metric collectors
|
|
93
|
+
*/
|
|
94
|
+
initializeDefaultCollectors() {
|
|
95
|
+
// Coverage metrics collector
|
|
96
|
+
this.registerCollector('coverage', async (context) => ({
|
|
97
|
+
lines: context?.coverage?.lines ?? 0,
|
|
98
|
+
branches: context?.coverage?.branches ?? 0,
|
|
99
|
+
functions: context?.coverage?.functions ?? 0,
|
|
100
|
+
statements: context?.coverage?.statements ?? 0,
|
|
101
|
+
overall: context?.coverage?.overall ??
|
|
102
|
+
((context?.coverage?.lines ?? 0) +
|
|
103
|
+
(context?.coverage?.branches ?? 0) +
|
|
104
|
+
(context?.coverage?.functions ?? 0) +
|
|
105
|
+
(context?.coverage?.statements ?? 0)) / 4
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
// Constitutional compliance collector
|
|
109
|
+
this.registerCollector('constitutional', async (context) => {
|
|
110
|
+
const articles = context?.constitutional ?? {};
|
|
111
|
+
const scores = Object.values(CONSTITUTIONAL_ARTICLES).map(id => ({
|
|
112
|
+
id,
|
|
113
|
+
score: articles[id]?.score ?? 0,
|
|
114
|
+
compliant: articles[id]?.compliant ?? false
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
const totalScore = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
|
|
118
|
+
const compliantCount = scores.filter(s => s.compliant).length;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
articles: scores,
|
|
122
|
+
totalScore,
|
|
123
|
+
compliantCount,
|
|
124
|
+
totalArticles: scores.length,
|
|
125
|
+
complianceRate: (compliantCount / scores.length) * 100
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Quality metrics collector
|
|
130
|
+
this.registerCollector('quality', async (context) => ({
|
|
131
|
+
codeComplexity: context?.quality?.complexity ?? 0,
|
|
132
|
+
maintainability: context?.quality?.maintainability ?? 0,
|
|
133
|
+
documentation: context?.quality?.documentation ?? 0,
|
|
134
|
+
testQuality: context?.quality?.testQuality ?? 0,
|
|
135
|
+
overall: context?.quality?.overall ??
|
|
136
|
+
((context?.quality?.complexity ?? 0) +
|
|
137
|
+
(context?.quality?.maintainability ?? 0) +
|
|
138
|
+
(context?.quality?.documentation ?? 0) +
|
|
139
|
+
(context?.quality?.testQuality ?? 0)) / 4
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
// Health metrics collector
|
|
143
|
+
this.registerCollector('health', async (context) => {
|
|
144
|
+
const coverage = await this.getMetric('coverage');
|
|
145
|
+
const constitutional = await this.getMetric('constitutional');
|
|
146
|
+
const quality = await this.getMetric('quality');
|
|
147
|
+
|
|
148
|
+
const coverageScore = coverage?.overall ?? 0;
|
|
149
|
+
const constitutionalScore = constitutional?.totalScore ?? 0;
|
|
150
|
+
const qualityScore = quality?.overall ?? 0;
|
|
151
|
+
|
|
152
|
+
const overall = (coverageScore + constitutionalScore + qualityScore) / 3;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
coverageScore,
|
|
156
|
+
constitutionalScore,
|
|
157
|
+
qualityScore,
|
|
158
|
+
overall,
|
|
159
|
+
status: this.calculateStatus(overall, 'quality'),
|
|
160
|
+
timestamp: new Date().toISOString()
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Register a metric collector
|
|
167
|
+
* @param {string} name
|
|
168
|
+
* @param {Function} collector
|
|
169
|
+
*/
|
|
170
|
+
registerCollector(name, collector) {
|
|
171
|
+
if (typeof collector !== 'function') {
|
|
172
|
+
throw new Error('Collector must be a function');
|
|
173
|
+
}
|
|
174
|
+
this.collectors.set(name, collector);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Unregister a collector
|
|
179
|
+
* @param {string} name
|
|
180
|
+
*/
|
|
181
|
+
unregisterCollector(name) {
|
|
182
|
+
this.collectors.delete(name);
|
|
183
|
+
this.metrics.delete(name);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Collect metrics
|
|
188
|
+
* @param {Object} context - External context data
|
|
189
|
+
* @returns {Object}
|
|
190
|
+
*/
|
|
191
|
+
async collect(context = {}) {
|
|
192
|
+
const results = {};
|
|
193
|
+
const timestamp = new Date().toISOString();
|
|
194
|
+
|
|
195
|
+
for (const [name, collector] of this.collectors) {
|
|
196
|
+
try {
|
|
197
|
+
const data = await collector(context);
|
|
198
|
+
results[name] = {
|
|
199
|
+
...data,
|
|
200
|
+
collectedAt: timestamp
|
|
201
|
+
};
|
|
202
|
+
this.metrics.set(name, results[name]);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
results[name] = {
|
|
205
|
+
error: error.message,
|
|
206
|
+
collectedAt: timestamp
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const snapshot = {
|
|
212
|
+
timestamp,
|
|
213
|
+
metrics: { ...results }
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
this.history.push(snapshot);
|
|
217
|
+
this.emit('collected', snapshot);
|
|
218
|
+
|
|
219
|
+
return results;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get a specific metric
|
|
224
|
+
* @param {string} name
|
|
225
|
+
* @returns {Object|null}
|
|
226
|
+
*/
|
|
227
|
+
getMetric(name) {
|
|
228
|
+
return this.metrics.get(name) || null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get all metrics
|
|
233
|
+
* @returns {Object}
|
|
234
|
+
*/
|
|
235
|
+
getAllMetrics() {
|
|
236
|
+
const result = {};
|
|
237
|
+
for (const [name, data] of this.metrics) {
|
|
238
|
+
result[name] = data;
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Calculate health status from score
|
|
245
|
+
* @param {number} score
|
|
246
|
+
* @param {string} category
|
|
247
|
+
* @returns {string}
|
|
248
|
+
*/
|
|
249
|
+
calculateStatus(score, category = 'quality') {
|
|
250
|
+
const thresholds = this.thresholds[category] || this.thresholds.quality;
|
|
251
|
+
|
|
252
|
+
if (score >= thresholds.healthy) return HEALTH_STATUS.HEALTHY;
|
|
253
|
+
if (score >= thresholds.warning) return HEALTH_STATUS.WARNING;
|
|
254
|
+
if (score >= thresholds.critical) return HEALTH_STATUS.CRITICAL;
|
|
255
|
+
return HEALTH_STATUS.FAILING;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get project health summary
|
|
260
|
+
* @returns {Object}
|
|
261
|
+
*/
|
|
262
|
+
getHealthSummary() {
|
|
263
|
+
const coverage = this.metrics.get('coverage');
|
|
264
|
+
const constitutional = this.metrics.get('constitutional');
|
|
265
|
+
const quality = this.metrics.get('quality');
|
|
266
|
+
const health = this.metrics.get('health');
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
status: health?.status ?? HEALTH_STATUS.FAILING,
|
|
270
|
+
overall: health?.overall ?? 0,
|
|
271
|
+
breakdown: {
|
|
272
|
+
coverage: {
|
|
273
|
+
score: coverage?.overall ?? 0,
|
|
274
|
+
status: this.calculateStatus(coverage?.overall ?? 0, 'coverage')
|
|
275
|
+
},
|
|
276
|
+
constitutional: {
|
|
277
|
+
score: constitutional?.totalScore ?? 0,
|
|
278
|
+
status: this.calculateStatus(constitutional?.totalScore ?? 0, 'constitutional'),
|
|
279
|
+
compliantArticles: constitutional?.compliantCount ?? 0,
|
|
280
|
+
totalArticles: constitutional?.totalArticles ?? 9
|
|
281
|
+
},
|
|
282
|
+
quality: {
|
|
283
|
+
score: quality?.overall ?? 0,
|
|
284
|
+
status: this.calculateStatus(quality?.overall ?? 0, 'quality')
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
lastUpdated: health?.timestamp ?? null
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get trend data
|
|
293
|
+
* @param {string} metricName
|
|
294
|
+
* @param {number} limit
|
|
295
|
+
* @returns {Array}
|
|
296
|
+
*/
|
|
297
|
+
getTrend(metricName, limit = 10) {
|
|
298
|
+
const data = this.history
|
|
299
|
+
.filter(h => h.metrics[metricName])
|
|
300
|
+
.slice(-limit)
|
|
301
|
+
.map(h => ({
|
|
302
|
+
timestamp: h.timestamp,
|
|
303
|
+
value: h.metrics[metricName]
|
|
304
|
+
}));
|
|
305
|
+
|
|
306
|
+
if (data.length < 2) {
|
|
307
|
+
return { data, trend: 'stable', change: 0 };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const first = this.extractValue(data[0].value);
|
|
311
|
+
const last = this.extractValue(data[data.length - 1].value);
|
|
312
|
+
const change = last - first;
|
|
313
|
+
|
|
314
|
+
let trend = 'stable';
|
|
315
|
+
if (change > 5) trend = 'improving';
|
|
316
|
+
else if (change < -5) trend = 'declining';
|
|
317
|
+
|
|
318
|
+
return { data, trend, change };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Extract numeric value from metric
|
|
323
|
+
*/
|
|
324
|
+
extractValue(metric) {
|
|
325
|
+
if (typeof metric === 'number') return metric;
|
|
326
|
+
if (metric?.overall !== undefined) return metric.overall;
|
|
327
|
+
if (metric?.totalScore !== undefined) return metric.totalScore;
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get history
|
|
333
|
+
* @param {Object} filter
|
|
334
|
+
* @returns {Array}
|
|
335
|
+
*/
|
|
336
|
+
getHistory(filter = {}) {
|
|
337
|
+
let history = [...this.history];
|
|
338
|
+
|
|
339
|
+
if (filter.since) {
|
|
340
|
+
history = history.filter(h => new Date(h.timestamp) >= new Date(filter.since));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (filter.limit) {
|
|
344
|
+
history = history.slice(-filter.limit);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return history;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Export dashboard as markdown report
|
|
352
|
+
* @returns {string}
|
|
353
|
+
*/
|
|
354
|
+
exportReport() {
|
|
355
|
+
const summary = this.getHealthSummary();
|
|
356
|
+
let report = `# Quality Dashboard Report\n\n`;
|
|
357
|
+
report += `Generated: ${new Date().toISOString()}\n\n`;
|
|
358
|
+
|
|
359
|
+
// Overall Health
|
|
360
|
+
report += `## Overall Health\n\n`;
|
|
361
|
+
report += `| Metric | Score | Status |\n`;
|
|
362
|
+
report += `|--------|-------|--------|\n`;
|
|
363
|
+
report += `| **Overall** | ${summary.overall.toFixed(1)}% | ${this.statusEmoji(summary.status)} ${summary.status} |\n`;
|
|
364
|
+
report += `| Coverage | ${summary.breakdown.coverage.score.toFixed(1)}% | ${this.statusEmoji(summary.breakdown.coverage.status)} |\n`;
|
|
365
|
+
report += `| Constitutional | ${summary.breakdown.constitutional.score.toFixed(1)}% | ${this.statusEmoji(summary.breakdown.constitutional.status)} |\n`;
|
|
366
|
+
report += `| Quality | ${summary.breakdown.quality.score.toFixed(1)}% | ${this.statusEmoji(summary.breakdown.quality.status)} |\n\n`;
|
|
367
|
+
|
|
368
|
+
// Constitutional Compliance
|
|
369
|
+
const constitutional = this.metrics.get('constitutional');
|
|
370
|
+
if (constitutional?.articles) {
|
|
371
|
+
report += `## Constitutional Compliance\n\n`;
|
|
372
|
+
report += `Articles Compliant: ${summary.breakdown.constitutional.compliantArticles}/${summary.breakdown.constitutional.totalArticles}\n\n`;
|
|
373
|
+
report += `| Article | Score | Compliant |\n`;
|
|
374
|
+
report += `|---------|-------|----------|\n`;
|
|
375
|
+
for (const article of constitutional.articles) {
|
|
376
|
+
const check = article.compliant ? '✅' : '❌';
|
|
377
|
+
report += `| ${article.id} | ${article.score.toFixed(1)}% | ${check} |\n`;
|
|
378
|
+
}
|
|
379
|
+
report += '\n';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Coverage Details
|
|
383
|
+
const coverage = this.metrics.get('coverage');
|
|
384
|
+
if (coverage) {
|
|
385
|
+
report += `## Coverage Details\n\n`;
|
|
386
|
+
report += `| Type | Coverage |\n`;
|
|
387
|
+
report += `|------|----------|\n`;
|
|
388
|
+
report += `| Lines | ${coverage.lines.toFixed(1)}% |\n`;
|
|
389
|
+
report += `| Branches | ${coverage.branches.toFixed(1)}% |\n`;
|
|
390
|
+
report += `| Functions | ${coverage.functions.toFixed(1)}% |\n`;
|
|
391
|
+
report += `| Statements | ${coverage.statements.toFixed(1)}% |\n\n`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Quality Details
|
|
395
|
+
const quality = this.metrics.get('quality');
|
|
396
|
+
if (quality) {
|
|
397
|
+
report += `## Quality Metrics\n\n`;
|
|
398
|
+
report += `| Metric | Score |\n`;
|
|
399
|
+
report += `|--------|-------|\n`;
|
|
400
|
+
report += `| Complexity | ${quality.codeComplexity.toFixed(1)} |\n`;
|
|
401
|
+
report += `| Maintainability | ${quality.maintainability.toFixed(1)} |\n`;
|
|
402
|
+
report += `| Documentation | ${quality.documentation.toFixed(1)} |\n`;
|
|
403
|
+
report += `| Test Quality | ${quality.testQuality.toFixed(1)} |\n\n`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return report;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get emoji for status
|
|
411
|
+
*/
|
|
412
|
+
statusEmoji(status) {
|
|
413
|
+
const emojis = {
|
|
414
|
+
[HEALTH_STATUS.HEALTHY]: '🟢',
|
|
415
|
+
[HEALTH_STATUS.WARNING]: '🟡',
|
|
416
|
+
[HEALTH_STATUS.CRITICAL]: '🟠',
|
|
417
|
+
[HEALTH_STATUS.FAILING]: '🔴'
|
|
418
|
+
};
|
|
419
|
+
return emojis[status] || '⚪';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Start auto-collection
|
|
424
|
+
*/
|
|
425
|
+
startAutoCollection() {
|
|
426
|
+
if (this.intervalId) return;
|
|
427
|
+
this.intervalId = setInterval(() => {
|
|
428
|
+
this.collect().catch(err => this.emit('error', err));
|
|
429
|
+
}, this.collectInterval);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Stop auto-collection
|
|
434
|
+
*/
|
|
435
|
+
stopAutoCollection() {
|
|
436
|
+
if (this.intervalId) {
|
|
437
|
+
clearInterval(this.intervalId);
|
|
438
|
+
this.intervalId = null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Clear all data
|
|
444
|
+
*/
|
|
445
|
+
clear() {
|
|
446
|
+
this.metrics.clear();
|
|
447
|
+
this.history = [];
|
|
448
|
+
this.emit('cleared');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Set thresholds
|
|
453
|
+
* @param {string} category
|
|
454
|
+
* @param {Object} thresholds
|
|
455
|
+
*/
|
|
456
|
+
setThresholds(category, thresholds) {
|
|
457
|
+
this.thresholds[category] = {
|
|
458
|
+
...this.thresholds[category],
|
|
459
|
+
...thresholds
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get stats
|
|
465
|
+
* @returns {Object}
|
|
466
|
+
*/
|
|
467
|
+
getStats() {
|
|
468
|
+
return {
|
|
469
|
+
metricsCount: this.metrics.size,
|
|
470
|
+
collectorsCount: this.collectors.size,
|
|
471
|
+
historyCount: this.history.length,
|
|
472
|
+
autoCollecting: this.intervalId !== null,
|
|
473
|
+
thresholds: { ...this.thresholds }
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Factory function
|
|
480
|
+
*/
|
|
481
|
+
function createQualityDashboard(options = {}) {
|
|
482
|
+
return new QualityDashboard(options);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
module.exports = {
|
|
486
|
+
QualityDashboard,
|
|
487
|
+
createQualityDashboard,
|
|
488
|
+
METRIC_CATEGORY,
|
|
489
|
+
HEALTH_STATUS,
|
|
490
|
+
CONSTITUTIONAL_ARTICLES
|
|
491
|
+
};
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* - Feature flag management
|
|
7
7
|
* - Rollback procedures
|
|
8
8
|
* - Release notes generation
|
|
9
|
+
*
|
|
10
|
+
* Part of MUSUBI v5.0.0 - Production Readiness
|
|
11
|
+
*
|
|
12
|
+
* @module monitoring/release-manager
|
|
13
|
+
* @version 1.0.0
|
|
14
|
+
*
|
|
15
|
+
* @traceability
|
|
16
|
+
* - Requirement: REQ-P5-003 (Release Automation)
|
|
17
|
+
* - Design: docs/design/tdd-musubi-v5.0.0.md#3.3
|
|
18
|
+
* - Test: tests/monitoring/release-manager.test.js
|
|
9
19
|
*/
|
|
10
20
|
|
|
11
21
|
const { EventEmitter } = require('events');
|