musubi-sdd 3.0.1 → 3.5.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/bin/musubi-change.js +623 -10
- package/bin/musubi-orchestrate.js +456 -0
- package/bin/musubi-trace.js +393 -0
- package/package.json +3 -2
- package/src/analyzers/impact-analyzer.js +682 -0
- package/src/integrations/cicd.js +782 -0
- package/src/integrations/documentation.js +740 -0
- package/src/integrations/examples.js +789 -0
- package/src/integrations/index.js +23 -0
- package/src/integrations/platforms.js +929 -0
- package/src/managers/delta-spec.js +484 -0
- package/src/monitoring/incident-manager.js +890 -0
- package/src/monitoring/index.js +633 -0
- package/src/monitoring/observability.js +938 -0
- package/src/monitoring/release-manager.js +622 -0
- package/src/orchestration/index.js +168 -0
- package/src/orchestration/orchestration-engine.js +409 -0
- package/src/orchestration/pattern-registry.js +319 -0
- package/src/orchestration/patterns/auto.js +386 -0
- package/src/orchestration/patterns/group-chat.js +395 -0
- package/src/orchestration/patterns/human-in-loop.js +506 -0
- package/src/orchestration/patterns/nested.js +322 -0
- package/src/orchestration/patterns/sequential.js +278 -0
- package/src/orchestration/patterns/swarm.js +395 -0
- package/src/orchestration/workflow-orchestrator.js +738 -0
- package/src/reporters/coverage-report.js +452 -0
- package/src/reporters/traceability-matrix-report.js +684 -0
- package/src/steering/advanced-validation.js +812 -0
- package/src/steering/auto-updater.js +670 -0
- package/src/steering/index.js +119 -0
- package/src/steering/quality-metrics.js +650 -0
- package/src/steering/template-constraints.js +789 -0
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +22 -0
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +21 -0
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +90 -28
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +32 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +27 -0
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +30 -0
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +21 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +27 -0
- package/src/templates/agents/codex/AGENTS.md +36 -1
- package/src/templates/agents/cursor/AGENTS.md +36 -1
- package/src/templates/agents/gemini-cli/GEMINI.md +36 -1
- package/src/templates/agents/github-copilot/AGENTS.md +65 -1
- package/src/templates/agents/qwen-code/QWEN.md +36 -1
- package/src/templates/agents/windsurf/AGENTS.md +36 -1
- package/src/templates/shared/delta-spec-template.md +246 -0
- package/src/validators/delta-format.js +474 -0
- package/src/validators/traceability-validator.js +561 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Metrics Dashboard Module
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Coverage metrics (code, test, documentation)
|
|
6
|
+
* - Compliance scoring (steering, constitutional)
|
|
7
|
+
* - Analytics and trends
|
|
8
|
+
* - Health indicators
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const EventEmitter = require('events');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// Metric Categories
|
|
15
|
+
const MetricCategory = {
|
|
16
|
+
COVERAGE: 'coverage',
|
|
17
|
+
COMPLIANCE: 'compliance',
|
|
18
|
+
QUALITY: 'quality',
|
|
19
|
+
HEALTH: 'health',
|
|
20
|
+
TREND: 'trend'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Health Status
|
|
24
|
+
const HealthStatus = {
|
|
25
|
+
HEALTHY: 'healthy',
|
|
26
|
+
WARNING: 'warning',
|
|
27
|
+
CRITICAL: 'critical',
|
|
28
|
+
UNKNOWN: 'unknown'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Trend Direction
|
|
32
|
+
const TrendDirection = {
|
|
33
|
+
UP: 'up',
|
|
34
|
+
DOWN: 'down',
|
|
35
|
+
STABLE: 'stable',
|
|
36
|
+
UNKNOWN: 'unknown'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Base Metric
|
|
41
|
+
*/
|
|
42
|
+
class Metric {
|
|
43
|
+
constructor(name, options = {}) {
|
|
44
|
+
this.name = name;
|
|
45
|
+
this.category = options.category || MetricCategory.QUALITY;
|
|
46
|
+
this.unit = options.unit || 'percent';
|
|
47
|
+
this.value = options.value || 0;
|
|
48
|
+
this.target = options.target || null;
|
|
49
|
+
this.min = options.min || 0;
|
|
50
|
+
this.max = options.max || 100;
|
|
51
|
+
this.timestamp = options.timestamp || new Date();
|
|
52
|
+
this.metadata = options.metadata || {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
update(value) {
|
|
56
|
+
this.value = Math.max(this.min, Math.min(this.max, value));
|
|
57
|
+
this.timestamp = new Date();
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getProgress() {
|
|
62
|
+
if (this.target === null) return null;
|
|
63
|
+
return (this.value / this.target) * 100;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isOnTarget() {
|
|
67
|
+
if (this.target === null) return null;
|
|
68
|
+
return this.value >= this.target;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
toJSON() {
|
|
72
|
+
return {
|
|
73
|
+
name: this.name,
|
|
74
|
+
category: this.category,
|
|
75
|
+
value: this.value,
|
|
76
|
+
unit: this.unit,
|
|
77
|
+
target: this.target,
|
|
78
|
+
progress: this.getProgress(),
|
|
79
|
+
onTarget: this.isOnTarget(),
|
|
80
|
+
timestamp: this.timestamp.toISOString()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Coverage Metric
|
|
87
|
+
*/
|
|
88
|
+
class CoverageMetric extends Metric {
|
|
89
|
+
constructor(name, options = {}) {
|
|
90
|
+
super(name, { ...options, category: MetricCategory.COVERAGE });
|
|
91
|
+
this.coveredItems = options.coveredItems || 0;
|
|
92
|
+
this.totalItems = options.totalItems || 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setCoverage(covered, total) {
|
|
96
|
+
this.coveredItems = covered;
|
|
97
|
+
this.totalItems = total;
|
|
98
|
+
this.value = total > 0 ? (covered / total) * 100 : 0;
|
|
99
|
+
this.timestamp = new Date();
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
toJSON() {
|
|
104
|
+
return {
|
|
105
|
+
...super.toJSON(),
|
|
106
|
+
covered: this.coveredItems,
|
|
107
|
+
total: this.totalItems
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Compliance Metric
|
|
114
|
+
*/
|
|
115
|
+
class ComplianceMetric extends Metric {
|
|
116
|
+
constructor(name, options = {}) {
|
|
117
|
+
super(name, { ...options, category: MetricCategory.COMPLIANCE });
|
|
118
|
+
this.rules = [];
|
|
119
|
+
this.violations = [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
addRule(rule) {
|
|
123
|
+
this.rules.push(rule);
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
addViolation(violation) {
|
|
128
|
+
this.violations.push({
|
|
129
|
+
...violation,
|
|
130
|
+
timestamp: new Date()
|
|
131
|
+
});
|
|
132
|
+
this.recalculate();
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
clearViolations() {
|
|
137
|
+
this.violations = [];
|
|
138
|
+
this.recalculate();
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
recalculate() {
|
|
143
|
+
if (this.rules.length === 0) {
|
|
144
|
+
this.value = 100;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const violatedRules = new Set(this.violations.map(v => v.rule));
|
|
149
|
+
const compliantCount = this.rules.length - violatedRules.size;
|
|
150
|
+
this.value = (compliantCount / this.rules.length) * 100;
|
|
151
|
+
this.timestamp = new Date();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
toJSON() {
|
|
155
|
+
return {
|
|
156
|
+
...super.toJSON(),
|
|
157
|
+
rules: this.rules.length,
|
|
158
|
+
violations: this.violations.length,
|
|
159
|
+
violatedRules: [...new Set(this.violations.map(v => v.rule))]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Health Indicator
|
|
166
|
+
*/
|
|
167
|
+
class HealthIndicator {
|
|
168
|
+
constructor(name, options = {}) {
|
|
169
|
+
this.name = name;
|
|
170
|
+
this.status = options.status || HealthStatus.UNKNOWN;
|
|
171
|
+
this.message = options.message || '';
|
|
172
|
+
this.lastCheck = options.lastCheck || null;
|
|
173
|
+
this.checkInterval = options.checkInterval || 60000; // 1 minute
|
|
174
|
+
this.checker = options.checker || (() => ({ status: HealthStatus.UNKNOWN }));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async check() {
|
|
178
|
+
try {
|
|
179
|
+
const result = await this.checker();
|
|
180
|
+
this.status = result.status;
|
|
181
|
+
this.message = result.message || '';
|
|
182
|
+
this.lastCheck = new Date();
|
|
183
|
+
return result;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.status = HealthStatus.CRITICAL;
|
|
186
|
+
this.message = error.message;
|
|
187
|
+
this.lastCheck = new Date();
|
|
188
|
+
return { status: HealthStatus.CRITICAL, error };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
isHealthy() {
|
|
193
|
+
return this.status === HealthStatus.HEALTHY;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
needsCheck() {
|
|
197
|
+
if (!this.lastCheck) return true;
|
|
198
|
+
return Date.now() - this.lastCheck.getTime() > this.checkInterval;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
toJSON() {
|
|
202
|
+
return {
|
|
203
|
+
name: this.name,
|
|
204
|
+
status: this.status,
|
|
205
|
+
message: this.message,
|
|
206
|
+
lastCheck: this.lastCheck?.toISOString(),
|
|
207
|
+
healthy: this.isHealthy()
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Trend Analyzer
|
|
214
|
+
*/
|
|
215
|
+
class TrendAnalyzer {
|
|
216
|
+
constructor(options = {}) {
|
|
217
|
+
this.dataPoints = [];
|
|
218
|
+
this.maxPoints = options.maxPoints || 100;
|
|
219
|
+
this.trendWindow = options.trendWindow || 7; // days
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
addDataPoint(value, timestamp = new Date()) {
|
|
223
|
+
this.dataPoints.push({ value, timestamp });
|
|
224
|
+
|
|
225
|
+
// Keep only maxPoints
|
|
226
|
+
if (this.dataPoints.length > this.maxPoints) {
|
|
227
|
+
this.dataPoints = this.dataPoints.slice(-this.maxPoints);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getTrend() {
|
|
234
|
+
if (this.dataPoints.length < 2) {
|
|
235
|
+
return { direction: TrendDirection.UNKNOWN, change: 0 };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const recent = this.dataPoints.slice(-this.trendWindow);
|
|
239
|
+
const first = recent[0].value;
|
|
240
|
+
const last = recent[recent.length - 1].value;
|
|
241
|
+
const change = last - first;
|
|
242
|
+
const percentChange = first > 0 ? (change / first) * 100 : 0;
|
|
243
|
+
|
|
244
|
+
let direction;
|
|
245
|
+
if (Math.abs(percentChange) < 1) {
|
|
246
|
+
direction = TrendDirection.STABLE;
|
|
247
|
+
} else if (change > 0) {
|
|
248
|
+
direction = TrendDirection.UP;
|
|
249
|
+
} else {
|
|
250
|
+
direction = TrendDirection.DOWN;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
direction,
|
|
255
|
+
change,
|
|
256
|
+
percentChange,
|
|
257
|
+
first,
|
|
258
|
+
last,
|
|
259
|
+
dataPoints: recent.length
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getAverage() {
|
|
264
|
+
if (this.dataPoints.length === 0) return 0;
|
|
265
|
+
const sum = this.dataPoints.reduce((acc, dp) => acc + dp.value, 0);
|
|
266
|
+
return sum / this.dataPoints.length;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
getMin() {
|
|
270
|
+
if (this.dataPoints.length === 0) return null;
|
|
271
|
+
return Math.min(...this.dataPoints.map(dp => dp.value));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
getMax() {
|
|
275
|
+
if (this.dataPoints.length === 0) return null;
|
|
276
|
+
return Math.max(...this.dataPoints.map(dp => dp.value));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
toJSON() {
|
|
280
|
+
return {
|
|
281
|
+
trend: this.getTrend(),
|
|
282
|
+
average: this.getAverage(),
|
|
283
|
+
min: this.getMin(),
|
|
284
|
+
max: this.getMax(),
|
|
285
|
+
dataPoints: this.dataPoints.length
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Quality Score Calculator
|
|
292
|
+
*/
|
|
293
|
+
class QualityScoreCalculator {
|
|
294
|
+
constructor(options = {}) {
|
|
295
|
+
this.weights = options.weights || {
|
|
296
|
+
coverage: 0.3,
|
|
297
|
+
compliance: 0.3,
|
|
298
|
+
health: 0.2,
|
|
299
|
+
trends: 0.2
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
calculate(metrics) {
|
|
304
|
+
const scores = {
|
|
305
|
+
coverage: this.calculateCoverageScore(metrics.coverage || []),
|
|
306
|
+
compliance: this.calculateComplianceScore(metrics.compliance || []),
|
|
307
|
+
health: this.calculateHealthScore(metrics.health || []),
|
|
308
|
+
trends: this.calculateTrendScore(metrics.trends || [])
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const overall = Object.entries(this.weights).reduce((acc, [key, weight]) => {
|
|
312
|
+
return acc + (scores[key] * weight);
|
|
313
|
+
}, 0);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
overall: Math.round(overall * 100) / 100,
|
|
317
|
+
breakdown: scores,
|
|
318
|
+
grade: this.getGrade(overall),
|
|
319
|
+
weights: this.weights
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
calculateCoverageScore(coverageMetrics) {
|
|
324
|
+
if (coverageMetrics.length === 0) return 0;
|
|
325
|
+
const avg = coverageMetrics.reduce((acc, m) => acc + m.value, 0) / coverageMetrics.length;
|
|
326
|
+
return Math.min(100, avg);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
calculateComplianceScore(complianceMetrics) {
|
|
330
|
+
if (complianceMetrics.length === 0) return 100;
|
|
331
|
+
const avg = complianceMetrics.reduce((acc, m) => acc + m.value, 0) / complianceMetrics.length;
|
|
332
|
+
return avg;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
calculateHealthScore(healthIndicators) {
|
|
336
|
+
if (healthIndicators.length === 0) return 100;
|
|
337
|
+
|
|
338
|
+
const statusScores = {
|
|
339
|
+
[HealthStatus.HEALTHY]: 100,
|
|
340
|
+
[HealthStatus.WARNING]: 60,
|
|
341
|
+
[HealthStatus.CRITICAL]: 0,
|
|
342
|
+
[HealthStatus.UNKNOWN]: 50
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const avg = healthIndicators.reduce((acc, h) => acc + statusScores[h.status], 0) / healthIndicators.length;
|
|
346
|
+
return avg;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
calculateTrendScore(trends) {
|
|
350
|
+
if (trends.length === 0) return 50;
|
|
351
|
+
|
|
352
|
+
const directionScores = {
|
|
353
|
+
[TrendDirection.UP]: 100,
|
|
354
|
+
[TrendDirection.STABLE]: 70,
|
|
355
|
+
[TrendDirection.DOWN]: 30,
|
|
356
|
+
[TrendDirection.UNKNOWN]: 50
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const avg = trends.reduce((acc, t) => acc + directionScores[t.direction], 0) / trends.length;
|
|
360
|
+
return avg;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
getGrade(score) {
|
|
364
|
+
if (score >= 90) return 'A';
|
|
365
|
+
if (score >= 80) return 'B';
|
|
366
|
+
if (score >= 70) return 'C';
|
|
367
|
+
if (score >= 60) return 'D';
|
|
368
|
+
return 'F';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Quality Metrics Dashboard
|
|
374
|
+
*/
|
|
375
|
+
class QualityMetricsDashboard extends EventEmitter {
|
|
376
|
+
constructor(options = {}) {
|
|
377
|
+
super();
|
|
378
|
+
this.name = options.name || 'Quality Dashboard';
|
|
379
|
+
this.metrics = new Map();
|
|
380
|
+
this.healthIndicators = new Map();
|
|
381
|
+
this.trendAnalyzers = new Map();
|
|
382
|
+
this.scoreCalculator = new QualityScoreCalculator(options.weights);
|
|
383
|
+
this.history = [];
|
|
384
|
+
this.maxHistory = options.maxHistory || 1000;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Metric Management
|
|
388
|
+
addMetric(metric) {
|
|
389
|
+
this.metrics.set(metric.name, metric);
|
|
390
|
+
this.emit('metric:added', metric);
|
|
391
|
+
return this;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
getMetric(name) {
|
|
395
|
+
return this.metrics.get(name);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
updateMetric(name, value) {
|
|
399
|
+
const metric = this.metrics.get(name);
|
|
400
|
+
if (metric) {
|
|
401
|
+
metric.update(value);
|
|
402
|
+
this.recordHistory('metric', name, value);
|
|
403
|
+
this.emit('metric:updated', metric);
|
|
404
|
+
|
|
405
|
+
// Update trend if exists
|
|
406
|
+
const trend = this.trendAnalyzers.get(name);
|
|
407
|
+
if (trend) {
|
|
408
|
+
trend.addDataPoint(value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
getMetricsByCategory(category) {
|
|
415
|
+
return [...this.metrics.values()].filter(m => m.category === category);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Health Indicators
|
|
419
|
+
addHealthIndicator(indicator) {
|
|
420
|
+
this.healthIndicators.set(indicator.name, indicator);
|
|
421
|
+
this.emit('health:added', indicator);
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
getHealthIndicator(name) {
|
|
426
|
+
return this.healthIndicators.get(name);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async checkHealth(name) {
|
|
430
|
+
const indicator = this.healthIndicators.get(name);
|
|
431
|
+
if (indicator) {
|
|
432
|
+
const result = await indicator.check();
|
|
433
|
+
this.emit('health:checked', { name, result });
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async checkAllHealth() {
|
|
440
|
+
const results = {};
|
|
441
|
+
for (const [name, indicator] of this.healthIndicators) {
|
|
442
|
+
results[name] = await indicator.check();
|
|
443
|
+
}
|
|
444
|
+
return results;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Trend Analysis
|
|
448
|
+
addTrendAnalyzer(name, options = {}) {
|
|
449
|
+
const analyzer = new TrendAnalyzer(options);
|
|
450
|
+
this.trendAnalyzers.set(name, analyzer);
|
|
451
|
+
return analyzer;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
getTrendAnalyzer(name) {
|
|
455
|
+
return this.trendAnalyzers.get(name);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
getTrends() {
|
|
459
|
+
const trends = {};
|
|
460
|
+
for (const [name, analyzer] of this.trendAnalyzers) {
|
|
461
|
+
trends[name] = analyzer.getTrend();
|
|
462
|
+
}
|
|
463
|
+
return trends;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Score Calculation
|
|
467
|
+
calculateScore() {
|
|
468
|
+
const data = {
|
|
469
|
+
coverage: this.getMetricsByCategory(MetricCategory.COVERAGE),
|
|
470
|
+
compliance: this.getMetricsByCategory(MetricCategory.COMPLIANCE),
|
|
471
|
+
health: [...this.healthIndicators.values()],
|
|
472
|
+
trends: Object.values(this.getTrends())
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const score = this.scoreCalculator.calculate(data);
|
|
476
|
+
this.emit('score:calculated', score);
|
|
477
|
+
return score;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// History
|
|
481
|
+
recordHistory(type, name, value) {
|
|
482
|
+
this.history.push({
|
|
483
|
+
type,
|
|
484
|
+
name,
|
|
485
|
+
value,
|
|
486
|
+
timestamp: new Date()
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
if (this.history.length > this.maxHistory) {
|
|
490
|
+
this.history = this.history.slice(-this.maxHistory);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
getHistory(options = {}) {
|
|
495
|
+
let filtered = this.history;
|
|
496
|
+
|
|
497
|
+
if (options.type) {
|
|
498
|
+
filtered = filtered.filter(h => h.type === options.type);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (options.name) {
|
|
502
|
+
filtered = filtered.filter(h => h.name === options.name);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (options.since) {
|
|
506
|
+
const since = new Date(options.since);
|
|
507
|
+
filtered = filtered.filter(h => h.timestamp >= since);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (options.limit) {
|
|
511
|
+
filtered = filtered.slice(-options.limit);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return filtered;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Dashboard Generation
|
|
518
|
+
generateSummary() {
|
|
519
|
+
const score = this.calculateScore();
|
|
520
|
+
const metrics = [...this.metrics.values()].map(m => m.toJSON());
|
|
521
|
+
const health = [...this.healthIndicators.values()].map(h => h.toJSON());
|
|
522
|
+
const trends = this.getTrends();
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
name: this.name,
|
|
526
|
+
generatedAt: new Date().toISOString(),
|
|
527
|
+
score,
|
|
528
|
+
metrics,
|
|
529
|
+
health,
|
|
530
|
+
trends,
|
|
531
|
+
summary: {
|
|
532
|
+
totalMetrics: metrics.length,
|
|
533
|
+
healthyIndicators: health.filter(h => h.healthy).length,
|
|
534
|
+
totalIndicators: health.length,
|
|
535
|
+
metricsOnTarget: metrics.filter(m => m.onTarget).length
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
generateMarkdownReport() {
|
|
541
|
+
const summary = this.generateSummary();
|
|
542
|
+
const lines = [
|
|
543
|
+
`# ${this.name}`,
|
|
544
|
+
'',
|
|
545
|
+
`Generated: ${summary.generatedAt}`,
|
|
546
|
+
'',
|
|
547
|
+
'## Overall Score',
|
|
548
|
+
'',
|
|
549
|
+
`**Grade: ${summary.score.grade}** (${summary.score.overall}%)`,
|
|
550
|
+
'',
|
|
551
|
+
'### Breakdown',
|
|
552
|
+
'',
|
|
553
|
+
`| Category | Score |`,
|
|
554
|
+
`|----------|-------|`,
|
|
555
|
+
`| Coverage | ${summary.score.breakdown.coverage.toFixed(1)}% |`,
|
|
556
|
+
`| Compliance | ${summary.score.breakdown.compliance.toFixed(1)}% |`,
|
|
557
|
+
`| Health | ${summary.score.breakdown.health.toFixed(1)}% |`,
|
|
558
|
+
`| Trends | ${summary.score.breakdown.trends.toFixed(1)}% |`,
|
|
559
|
+
'',
|
|
560
|
+
'## Metrics',
|
|
561
|
+
''
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
for (const metric of summary.metrics) {
|
|
565
|
+
const status = metric.onTarget ? '✅' : (metric.onTarget === false ? '⚠️' : '➖');
|
|
566
|
+
lines.push(`- ${status} **${metric.name}**: ${metric.value.toFixed(1)}${metric.unit === 'percent' ? '%' : ` ${metric.unit}`}`);
|
|
567
|
+
if (metric.target) {
|
|
568
|
+
lines.push(` - Target: ${metric.target}%`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
lines.push('', '## Health Indicators', '');
|
|
573
|
+
|
|
574
|
+
for (const indicator of summary.health) {
|
|
575
|
+
const icon = indicator.healthy ? '🟢' : (indicator.status === 'warning' ? '🟡' : '🔴');
|
|
576
|
+
lines.push(`- ${icon} **${indicator.name}**: ${indicator.status}`);
|
|
577
|
+
if (indicator.message) {
|
|
578
|
+
lines.push(` - ${indicator.message}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
lines.push('', '## Trends', '');
|
|
583
|
+
|
|
584
|
+
for (const [name, trend] of Object.entries(summary.trends)) {
|
|
585
|
+
const icon = trend.direction === 'up' ? '📈' : (trend.direction === 'down' ? '📉' : '➡️');
|
|
586
|
+
lines.push(`- ${icon} **${name}**: ${trend.direction} (${trend.percentChange?.toFixed(1) || 0}%)`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return lines.join('\n');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Serialization
|
|
593
|
+
toJSON() {
|
|
594
|
+
return this.generateSummary();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Factory function
|
|
600
|
+
*/
|
|
601
|
+
function createQualityDashboard(options = {}) {
|
|
602
|
+
const dashboard = new QualityMetricsDashboard(options);
|
|
603
|
+
|
|
604
|
+
// Add default metrics if specified
|
|
605
|
+
if (options.includeDefaults !== false) {
|
|
606
|
+
// Coverage metrics
|
|
607
|
+
dashboard.addMetric(new CoverageMetric('Code Coverage', { target: 80 }));
|
|
608
|
+
dashboard.addMetric(new CoverageMetric('Test Coverage', { target: 80 }));
|
|
609
|
+
dashboard.addMetric(new CoverageMetric('Documentation Coverage', { target: 70 }));
|
|
610
|
+
|
|
611
|
+
// Compliance metrics
|
|
612
|
+
const steering = new ComplianceMetric('Steering Compliance', { target: 100 });
|
|
613
|
+
steering.addRule('structure-defined');
|
|
614
|
+
steering.addRule('tech-defined');
|
|
615
|
+
steering.addRule('product-defined');
|
|
616
|
+
dashboard.addMetric(steering);
|
|
617
|
+
|
|
618
|
+
const constitution = new ComplianceMetric('Constitutional Compliance', { target: 100 });
|
|
619
|
+
constitution.addRule('article-1');
|
|
620
|
+
constitution.addRule('article-2');
|
|
621
|
+
constitution.addRule('article-3');
|
|
622
|
+
dashboard.addMetric(constitution);
|
|
623
|
+
|
|
624
|
+
// Trend analyzers
|
|
625
|
+
dashboard.addTrendAnalyzer('Code Coverage');
|
|
626
|
+
dashboard.addTrendAnalyzer('Test Coverage');
|
|
627
|
+
dashboard.addTrendAnalyzer('Steering Compliance');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return dashboard;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
module.exports = {
|
|
634
|
+
// Constants
|
|
635
|
+
MetricCategory,
|
|
636
|
+
HealthStatus,
|
|
637
|
+
TrendDirection,
|
|
638
|
+
|
|
639
|
+
// Classes
|
|
640
|
+
Metric,
|
|
641
|
+
CoverageMetric,
|
|
642
|
+
ComplianceMetric,
|
|
643
|
+
HealthIndicator,
|
|
644
|
+
TrendAnalyzer,
|
|
645
|
+
QualityScoreCalculator,
|
|
646
|
+
QualityMetricsDashboard,
|
|
647
|
+
|
|
648
|
+
// Factory
|
|
649
|
+
createQualityDashboard
|
|
650
|
+
};
|