qa360 1.4.5 → 2.0.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.md +1 -1
- package/dist/commands/ai.d.ts +41 -0
- package/dist/commands/ai.js +499 -0
- package/dist/commands/ask.js +12 -12
- package/dist/commands/coverage.d.ts +8 -0
- package/dist/commands/coverage.js +252 -0
- package/dist/commands/explain.d.ts +27 -0
- package/dist/commands/explain.js +630 -0
- package/dist/commands/flakiness.d.ts +73 -0
- package/dist/commands/flakiness.js +435 -0
- package/dist/commands/generate.d.ts +66 -0
- package/dist/commands/generate.js +438 -0
- package/dist/commands/init.d.ts +56 -9
- package/dist/commands/init.js +217 -10
- package/dist/commands/monitor.d.ts +27 -0
- package/dist/commands/monitor.js +225 -0
- package/dist/commands/ollama.d.ts +40 -0
- package/dist/commands/ollama.js +301 -0
- package/dist/commands/pack.d.ts +37 -9
- package/dist/commands/pack.js +240 -141
- package/dist/commands/regression.d.ts +8 -0
- package/dist/commands/regression.js +340 -0
- package/dist/commands/repair.d.ts +26 -0
- package/dist/commands/repair.js +307 -0
- package/dist/commands/retry.d.ts +43 -0
- package/dist/commands/retry.js +275 -0
- package/dist/commands/run.d.ts +8 -3
- package/dist/commands/run.js +45 -31
- package/dist/commands/slo.d.ts +8 -0
- package/dist/commands/slo.js +327 -0
- package/dist/core/adapters/playwright-native-api.d.ts +183 -0
- package/dist/core/adapters/playwright-native-api.js +461 -0
- package/dist/core/adapters/playwright-ui.d.ts +7 -0
- package/dist/core/adapters/playwright-ui.js +29 -1
- package/dist/core/ai/anthropic-provider.d.ts +50 -0
- package/dist/core/ai/anthropic-provider.js +211 -0
- package/dist/core/ai/deepseek-provider.d.ts +81 -0
- package/dist/core/ai/deepseek-provider.js +254 -0
- package/dist/core/ai/index.d.ts +60 -0
- package/dist/core/ai/index.js +18 -0
- package/dist/core/ai/llm-client.d.ts +45 -0
- package/dist/core/ai/llm-client.js +7 -0
- package/dist/core/ai/mock-provider.d.ts +49 -0
- package/dist/core/ai/mock-provider.js +121 -0
- package/dist/core/ai/ollama-provider.d.ts +78 -0
- package/dist/core/ai/ollama-provider.js +192 -0
- package/dist/core/ai/openai-provider.d.ts +48 -0
- package/dist/core/ai/openai-provider.js +188 -0
- package/dist/core/ai/provider-factory.d.ts +160 -0
- package/dist/core/ai/provider-factory.js +269 -0
- package/dist/core/auth/api-key-provider.d.ts +16 -0
- package/dist/core/auth/api-key-provider.js +63 -0
- package/dist/core/auth/aws-iam-provider.d.ts +35 -0
- package/dist/core/auth/aws-iam-provider.js +177 -0
- package/dist/core/auth/azure-ad-provider.d.ts +15 -0
- package/dist/core/auth/azure-ad-provider.js +99 -0
- package/dist/core/auth/basic-auth-provider.d.ts +26 -0
- package/dist/core/auth/basic-auth-provider.js +111 -0
- package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
- package/dist/core/auth/gcp-adc-provider.js +126 -0
- package/dist/core/auth/index.d.ts +238 -0
- package/dist/core/auth/index.js +82 -0
- package/dist/core/auth/jwt-provider.d.ts +19 -0
- package/dist/core/auth/jwt-provider.js +160 -0
- package/dist/core/auth/manager.d.ts +84 -0
- package/dist/core/auth/manager.js +230 -0
- package/dist/core/auth/oauth2-provider.d.ts +17 -0
- package/dist/core/auth/oauth2-provider.js +114 -0
- package/dist/core/auth/totp-provider.d.ts +31 -0
- package/dist/core/auth/totp-provider.js +134 -0
- package/dist/core/auth/ui-login-provider.d.ts +26 -0
- package/dist/core/auth/ui-login-provider.js +198 -0
- package/dist/core/cache/index.d.ts +7 -0
- package/dist/core/cache/index.js +6 -0
- package/dist/core/cache/lru-cache.d.ts +203 -0
- package/dist/core/cache/lru-cache.js +397 -0
- package/dist/core/coverage/analyzer.d.ts +101 -0
- package/dist/core/coverage/analyzer.js +415 -0
- package/dist/core/coverage/collector.d.ts +74 -0
- package/dist/core/coverage/collector.js +459 -0
- package/dist/core/coverage/config.d.ts +37 -0
- package/dist/core/coverage/config.js +156 -0
- package/dist/core/coverage/index.d.ts +11 -0
- package/dist/core/coverage/index.js +15 -0
- package/dist/core/coverage/types.d.ts +267 -0
- package/dist/core/coverage/types.js +6 -0
- package/dist/core/coverage/vault.d.ts +95 -0
- package/dist/core/coverage/vault.js +405 -0
- package/dist/core/dashboard/assets.d.ts +6 -0
- package/dist/core/dashboard/assets.js +690 -0
- package/dist/core/dashboard/index.d.ts +6 -0
- package/dist/core/dashboard/index.js +5 -0
- package/dist/core/dashboard/server.d.ts +72 -0
- package/dist/core/dashboard/server.js +354 -0
- package/dist/core/dashboard/types.d.ts +70 -0
- package/dist/core/dashboard/types.js +5 -0
- package/dist/core/discoverer/index.d.ts +115 -0
- package/dist/core/discoverer/index.js +250 -0
- package/dist/core/flakiness/index.d.ts +228 -0
- package/dist/core/flakiness/index.js +384 -0
- package/dist/core/generation/code-formatter.d.ts +111 -0
- package/dist/core/generation/code-formatter.js +307 -0
- package/dist/core/generation/code-generator.d.ts +144 -0
- package/dist/core/generation/code-generator.js +293 -0
- package/dist/core/generation/generator.d.ts +40 -0
- package/dist/core/generation/generator.js +76 -0
- package/dist/core/generation/index.d.ts +30 -0
- package/dist/core/generation/index.js +28 -0
- package/dist/core/generation/pack-generator.d.ts +107 -0
- package/dist/core/generation/pack-generator.js +416 -0
- package/dist/core/generation/prompt-builder.d.ts +132 -0
- package/dist/core/generation/prompt-builder.js +672 -0
- package/dist/core/generation/source-analyzer.d.ts +213 -0
- package/dist/core/generation/source-analyzer.js +657 -0
- package/dist/core/generation/test-optimizer.d.ts +117 -0
- package/dist/core/generation/test-optimizer.js +328 -0
- package/dist/core/generation/types.d.ts +214 -0
- package/dist/core/generation/types.js +4 -0
- package/dist/core/index.d.ts +23 -1
- package/dist/core/index.js +39 -0
- package/dist/core/pack/validator.js +31 -1
- package/dist/core/pack-v2/index.d.ts +9 -0
- package/dist/core/pack-v2/index.js +8 -0
- package/dist/core/pack-v2/loader.d.ts +62 -0
- package/dist/core/pack-v2/loader.js +231 -0
- package/dist/core/pack-v2/migrator.d.ts +56 -0
- package/dist/core/pack-v2/migrator.js +455 -0
- package/dist/core/pack-v2/validator.d.ts +61 -0
- package/dist/core/pack-v2/validator.js +577 -0
- package/dist/core/regression/detector.d.ts +107 -0
- package/dist/core/regression/detector.js +497 -0
- package/dist/core/regression/index.d.ts +9 -0
- package/dist/core/regression/index.js +11 -0
- package/dist/core/regression/trend-analyzer.d.ts +102 -0
- package/dist/core/regression/trend-analyzer.js +345 -0
- package/dist/core/regression/types.d.ts +222 -0
- package/dist/core/regression/types.js +7 -0
- package/dist/core/regression/vault.d.ts +87 -0
- package/dist/core/regression/vault.js +289 -0
- package/dist/core/repair/engine/fixer.d.ts +24 -0
- package/dist/core/repair/engine/fixer.js +226 -0
- package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
- package/dist/core/repair/engine/suggestion-engine.js +187 -0
- package/dist/core/repair/index.d.ts +10 -0
- package/dist/core/repair/index.js +13 -0
- package/dist/core/repair/repairer.d.ts +90 -0
- package/dist/core/repair/repairer.js +284 -0
- package/dist/core/repair/types.d.ts +91 -0
- package/dist/core/repair/types.js +6 -0
- package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
- package/dist/core/repair/utils/error-analyzer.js +264 -0
- package/dist/core/retry/flakiness-integration.d.ts +60 -0
- package/dist/core/retry/flakiness-integration.js +228 -0
- package/dist/core/retry/index.d.ts +14 -0
- package/dist/core/retry/index.js +16 -0
- package/dist/core/retry/retry-engine.d.ts +80 -0
- package/dist/core/retry/retry-engine.js +296 -0
- package/dist/core/retry/types.d.ts +178 -0
- package/dist/core/retry/types.js +52 -0
- package/dist/core/retry/vault.d.ts +77 -0
- package/dist/core/retry/vault.js +304 -0
- package/dist/core/runner/e2e-helpers.d.ts +102 -0
- package/dist/core/runner/e2e-helpers.js +153 -0
- package/dist/core/runner/phase3-runner.d.ts +101 -2
- package/dist/core/runner/phase3-runner.js +559 -24
- package/dist/core/self-healing/assertion-healer.d.ts +97 -0
- package/dist/core/self-healing/assertion-healer.js +371 -0
- package/dist/core/self-healing/engine.d.ts +122 -0
- package/dist/core/self-healing/engine.js +538 -0
- package/dist/core/self-healing/index.d.ts +10 -0
- package/dist/core/self-healing/index.js +11 -0
- package/dist/core/self-healing/selector-healer.d.ts +103 -0
- package/dist/core/self-healing/selector-healer.js +372 -0
- package/dist/core/self-healing/types.d.ts +152 -0
- package/dist/core/self-healing/types.js +6 -0
- package/dist/core/slo/config.d.ts +107 -0
- package/dist/core/slo/config.js +360 -0
- package/dist/core/slo/index.d.ts +11 -0
- package/dist/core/slo/index.js +15 -0
- package/dist/core/slo/sli-calculator.d.ts +92 -0
- package/dist/core/slo/sli-calculator.js +364 -0
- package/dist/core/slo/slo-tracker.d.ts +148 -0
- package/dist/core/slo/slo-tracker.js +379 -0
- package/dist/core/slo/types.d.ts +281 -0
- package/dist/core/slo/types.js +7 -0
- package/dist/core/slo/vault.d.ts +102 -0
- package/dist/core/slo/vault.js +427 -0
- package/dist/core/tui/index.d.ts +7 -0
- package/dist/core/tui/index.js +6 -0
- package/dist/core/tui/monitor.d.ts +92 -0
- package/dist/core/tui/monitor.js +271 -0
- package/dist/core/tui/renderer.d.ts +33 -0
- package/dist/core/tui/renderer.js +218 -0
- package/dist/core/tui/types.d.ts +63 -0
- package/dist/core/tui/types.js +5 -0
- package/dist/core/types/pack-v2.d.ts +425 -0
- package/dist/core/types/pack-v2.js +8 -0
- package/dist/core/vault/index.d.ts +116 -0
- package/dist/core/vault/index.js +400 -5
- package/dist/core/watch/index.d.ts +7 -0
- package/dist/core/watch/index.js +6 -0
- package/dist/core/watch/watch-mode.d.ts +213 -0
- package/dist/core/watch/watch-mode.js +389 -0
- package/dist/index.js +68 -68
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +136 -0
- package/package.json +5 -1
- package/dist/core/adapters/playwright-api.d.ts +0 -82
- package/dist/core/adapters/playwright-api.js +0 -264
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression Trend Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes metric trends to predict potential regressions.
|
|
5
|
+
* Uses linear regression and change point detection.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Regression Trend Analyzer class
|
|
9
|
+
*/
|
|
10
|
+
export class RegressionTrendAnalyzer {
|
|
11
|
+
/**
|
|
12
|
+
* Analyze trend for a metric
|
|
13
|
+
*/
|
|
14
|
+
analyzeTrend(data, windowMs) {
|
|
15
|
+
if (data.length < 3) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const analysis = this.linearRegression(data);
|
|
19
|
+
const direction = this.getTrendDirection(analysis.slope);
|
|
20
|
+
const strength = Math.abs(analysis.rSquared);
|
|
21
|
+
// Project future value
|
|
22
|
+
const projected = this.projectTrend(data, analysis);
|
|
23
|
+
return {
|
|
24
|
+
metric: data[0].metadata?.metric || 'unknown',
|
|
25
|
+
direction,
|
|
26
|
+
strength,
|
|
27
|
+
projected,
|
|
28
|
+
dataPoints: data.length,
|
|
29
|
+
regressions: 0 // Will be populated by detector
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Perform linear regression on time series
|
|
34
|
+
*/
|
|
35
|
+
linearRegression(data) {
|
|
36
|
+
const n = data.length;
|
|
37
|
+
if (n < 2) {
|
|
38
|
+
return {
|
|
39
|
+
slope: 0,
|
|
40
|
+
intercept: data[0]?.value || 0,
|
|
41
|
+
rSquared: 0,
|
|
42
|
+
significant: false,
|
|
43
|
+
direction: 'stable',
|
|
44
|
+
project: () => 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Normalize timestamps to start from 0
|
|
48
|
+
const baseTime = data[0].timestamp;
|
|
49
|
+
let sumX = 0;
|
|
50
|
+
let sumY = 0;
|
|
51
|
+
let sumXY = 0;
|
|
52
|
+
let sumXX = 0;
|
|
53
|
+
let sumYY = 0;
|
|
54
|
+
for (const point of data) {
|
|
55
|
+
const x = (point.timestamp - baseTime) / 1000; // Convert to seconds
|
|
56
|
+
const y = point.value;
|
|
57
|
+
sumX += x;
|
|
58
|
+
sumY += y;
|
|
59
|
+
sumXY += x * y;
|
|
60
|
+
sumXX += x * x;
|
|
61
|
+
sumYY += y * y;
|
|
62
|
+
}
|
|
63
|
+
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
|
64
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
65
|
+
// Calculate R²
|
|
66
|
+
const meanY = sumY / n;
|
|
67
|
+
let ssTotal = 0;
|
|
68
|
+
let ssResidual = 0;
|
|
69
|
+
for (const point of data) {
|
|
70
|
+
const x = (point.timestamp - baseTime) / 1000;
|
|
71
|
+
const y = point.value;
|
|
72
|
+
const predicted = slope * x + intercept;
|
|
73
|
+
ssTotal += Math.pow(y - meanY, 2);
|
|
74
|
+
ssResidual += Math.pow(y - predicted, 2);
|
|
75
|
+
}
|
|
76
|
+
const rSquared = ssTotal > 0 ? 1 - ssResidual / ssTotal : 0;
|
|
77
|
+
// Determine significance (simplified)
|
|
78
|
+
const significant = rSquared > 0.7 && Math.abs(slope) > 0.001;
|
|
79
|
+
const direction = Math.abs(slope) < 0.001 ? 'stable'
|
|
80
|
+
: slope > 0 ? 'increasing'
|
|
81
|
+
: 'decreasing';
|
|
82
|
+
return {
|
|
83
|
+
slope,
|
|
84
|
+
intercept,
|
|
85
|
+
rSquared,
|
|
86
|
+
significant,
|
|
87
|
+
direction,
|
|
88
|
+
project: (ms) => {
|
|
89
|
+
const x = ms / 1000;
|
|
90
|
+
return slope * x + intercept;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detect change points in time series
|
|
96
|
+
*/
|
|
97
|
+
detectChangePoints(data, minSegmentSize = 5) {
|
|
98
|
+
if (data.length < minSegmentSize * 2) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const changePoints = [];
|
|
102
|
+
const values = data.map(d => d.value);
|
|
103
|
+
// Use simplified CUSUM-like approach
|
|
104
|
+
let maxChange = 0;
|
|
105
|
+
let maxIndex = 0;
|
|
106
|
+
for (let i = minSegmentSize; i < values.length - minSegmentSize; i++) {
|
|
107
|
+
const before = values.slice(0, i);
|
|
108
|
+
const after = values.slice(i);
|
|
109
|
+
const beforeMean = before.reduce((a, b) => a + b, 0) / before.length;
|
|
110
|
+
const afterMean = after.reduce((a, b) => a + b, 0) / after.length;
|
|
111
|
+
const magnitude = Math.abs(afterMean - beforeMean);
|
|
112
|
+
if (magnitude > maxChange) {
|
|
113
|
+
maxChange = magnitude;
|
|
114
|
+
maxIndex = i;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (maxChange > 0) {
|
|
118
|
+
const splitIdx = maxIndex;
|
|
119
|
+
const before = values.slice(0, splitIdx);
|
|
120
|
+
const after = values.slice(splitIdx);
|
|
121
|
+
changePoints.push({
|
|
122
|
+
index: splitIdx,
|
|
123
|
+
timestamp: data[splitIdx].timestamp,
|
|
124
|
+
beforeMean: before.reduce((a, b) => a + b, 0) / before.length,
|
|
125
|
+
afterMean: after.reduce((a, b) => a + b, 0) / after.length,
|
|
126
|
+
magnitude: maxChange,
|
|
127
|
+
pValue: this.estimatePValue(maxChange, before.length, after.length)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return changePoints;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Estimate p-value for change point
|
|
134
|
+
*/
|
|
135
|
+
estimatePValue(magnitude, n1, n2) {
|
|
136
|
+
// Simplified estimation using t-distribution approximation
|
|
137
|
+
const pooledStd = Math.sqrt((magnitude * magnitude) / (1 / n1 + 1 / n2));
|
|
138
|
+
const tStat = magnitude / (pooledStd || 1);
|
|
139
|
+
// Rough p-value approximation
|
|
140
|
+
return Math.max(0.001, 1 - Math.min(1, tStat / 5));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Project trend forward
|
|
144
|
+
*/
|
|
145
|
+
projectTrend(data, analysis) {
|
|
146
|
+
if (!analysis.significant) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
// Project 7 days forward
|
|
150
|
+
const weekMs = 7 * 24 * 60 * 60 * 1000;
|
|
151
|
+
return analysis.project(weekMs);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get trend direction for reporting
|
|
155
|
+
*/
|
|
156
|
+
getTrendDirection(slope) {
|
|
157
|
+
if (Math.abs(slope) < 0.001) {
|
|
158
|
+
return 'stable';
|
|
159
|
+
}
|
|
160
|
+
// Note: For some metrics (like latency), increasing is bad
|
|
161
|
+
// For others (like coverage), increasing is good
|
|
162
|
+
// This is simplified - should consider metric type
|
|
163
|
+
return slope > 0 ? 'degrading' : 'improving';
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Calculate time until threshold breach
|
|
167
|
+
*/
|
|
168
|
+
calculateTimeToThreshold(data, threshold, isIncreasingBad) {
|
|
169
|
+
const analysis = this.linearRegression(data);
|
|
170
|
+
if (!analysis.significant || Math.abs(analysis.slope) < 0.00001) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const currentValue = data[data.length - 1].value;
|
|
174
|
+
// Determine if we're moving toward threshold
|
|
175
|
+
const movingTowardBad = isIncreasingBad
|
|
176
|
+
? analysis.slope > 0
|
|
177
|
+
: analysis.slope < 0;
|
|
178
|
+
if (!movingTowardBad) {
|
|
179
|
+
return null; // Not moving toward threshold
|
|
180
|
+
}
|
|
181
|
+
// Calculate time to reach threshold
|
|
182
|
+
// slope is in units per second, so we need to convert
|
|
183
|
+
const diff = isIncreasingBad
|
|
184
|
+
? threshold - currentValue
|
|
185
|
+
: currentValue - threshold;
|
|
186
|
+
if (diff <= 0) {
|
|
187
|
+
return 0; // Already at/beyond threshold
|
|
188
|
+
}
|
|
189
|
+
// Convert: slope (units/sec) to time needed (ms)
|
|
190
|
+
// time_ms = (diff / slope) * 1000
|
|
191
|
+
const timeToThreshold = (diff / Math.abs(analysis.slope)) * 1000;
|
|
192
|
+
return Math.max(0, timeToThreshold);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Compare two periods for regression
|
|
196
|
+
*/
|
|
197
|
+
comparePeriods(baseline, current, metric) {
|
|
198
|
+
if (baseline.length === 0 || current.length === 0) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const baselineMean = baseline.reduce((sum, p) => sum + p.value, 0) / baseline.length;
|
|
202
|
+
const currentMean = current.reduce((sum, p) => sum + p.value, 0) / current.length;
|
|
203
|
+
const absoluteChange = currentMean - baselineMean;
|
|
204
|
+
const percentChange = baselineMean !== 0
|
|
205
|
+
? (absoluteChange / baselineMean) * 100
|
|
206
|
+
: 0;
|
|
207
|
+
// Determine if regression based on metric type
|
|
208
|
+
const type = this.inferType(metric);
|
|
209
|
+
const direction = this.isRegressionDirection(type, absoluteChange);
|
|
210
|
+
if (!direction) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
// Calculate significance using t-test
|
|
214
|
+
const baselineVariance = baseline.reduce((sum, p) => sum + Math.pow(p.value - baselineMean, 2), 0) / baseline.length;
|
|
215
|
+
const currentVariance = current.reduce((sum, p) => sum + Math.pow(p.value - currentMean, 2), 0) / current.length;
|
|
216
|
+
const pooledStd = Math.sqrt(((baseline.length - 1) * baselineVariance + (current.length - 1) * currentVariance) /
|
|
217
|
+
(baseline.length + current.length - 2));
|
|
218
|
+
const tStat = absoluteChange / (pooledStd * Math.sqrt(1 / baseline.length + 1 / current.length) || 1);
|
|
219
|
+
const pValue = 2 * (1 - this.normalCDF(Math.abs(tStat)));
|
|
220
|
+
// Severity based on percent change and significance
|
|
221
|
+
let severity;
|
|
222
|
+
if (Math.abs(percentChange) > 50 && pValue < 0.01) {
|
|
223
|
+
severity = 'critical';
|
|
224
|
+
}
|
|
225
|
+
else if (Math.abs(percentChange) > 20 && pValue < 0.05) {
|
|
226
|
+
severity = 'major';
|
|
227
|
+
}
|
|
228
|
+
else if (Math.abs(percentChange) > 10) {
|
|
229
|
+
severity = 'moderate';
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
severity = 'minor';
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
id: `regression_${Date.now()}_${metric.replace(/\W/g, '_')}`,
|
|
236
|
+
runId: current[current.length - 1].runId,
|
|
237
|
+
baselineRunId: baseline[baseline.length - 1].runId,
|
|
238
|
+
type,
|
|
239
|
+
severity,
|
|
240
|
+
status: 'detected',
|
|
241
|
+
metricName: metric,
|
|
242
|
+
baselineValue: baselineMean,
|
|
243
|
+
currentValue: currentMean,
|
|
244
|
+
absoluteChange,
|
|
245
|
+
percentChange,
|
|
246
|
+
significance: pValue,
|
|
247
|
+
confidence: 1 - pValue,
|
|
248
|
+
direction: absoluteChange > 0 ? 'worse' : 'better',
|
|
249
|
+
affectedComponent: metric,
|
|
250
|
+
gate: 'unknown',
|
|
251
|
+
suggestions: [
|
|
252
|
+
`Review changes between ${new Date(baseline[0].timestamp).toISOString()} and ${new Date(current[0].timestamp).toISOString()}`,
|
|
253
|
+
`${percentChange > 0 ? 'Increased' : 'Decreased'} by ${Math.abs(percentChange).toFixed(1)}%`
|
|
254
|
+
],
|
|
255
|
+
timestamp: Date.now()
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Infer regression type from metric name
|
|
260
|
+
*/
|
|
261
|
+
inferType(metric) {
|
|
262
|
+
if (metric.includes('duration') || metric.includes('latency') || metric.includes('time')) {
|
|
263
|
+
return 'performance';
|
|
264
|
+
}
|
|
265
|
+
if (metric.includes('coverage')) {
|
|
266
|
+
return 'coverage';
|
|
267
|
+
}
|
|
268
|
+
if (metric.includes('flaky') || metric.includes('stability')) {
|
|
269
|
+
return 'reliability';
|
|
270
|
+
}
|
|
271
|
+
if (metric.includes('security') || metric.includes('vulnerability')) {
|
|
272
|
+
return 'security';
|
|
273
|
+
}
|
|
274
|
+
return 'quality';
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Check if change direction indicates regression
|
|
278
|
+
*/
|
|
279
|
+
isRegressionDirection(type, change) {
|
|
280
|
+
// For performance, reliability: increase is bad
|
|
281
|
+
if (type === 'performance' || type === 'reliability') {
|
|
282
|
+
return change > 0;
|
|
283
|
+
}
|
|
284
|
+
// For coverage: decrease is bad
|
|
285
|
+
if (type === 'coverage') {
|
|
286
|
+
return change < 0;
|
|
287
|
+
}
|
|
288
|
+
// Default: increase is bad
|
|
289
|
+
return change > 0;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Normal CDF approximation
|
|
293
|
+
*/
|
|
294
|
+
normalCDF(x) {
|
|
295
|
+
const a1 = 0.254829592;
|
|
296
|
+
const a2 = -0.284496736;
|
|
297
|
+
const a3 = 1.421413741;
|
|
298
|
+
const a4 = -1.453152027;
|
|
299
|
+
const a5 = 1.061405429;
|
|
300
|
+
const p = 0.3275911;
|
|
301
|
+
const sign = x < 0 ? -1 : 1;
|
|
302
|
+
x = Math.abs(x) / Math.sqrt(2);
|
|
303
|
+
const t = 1.0 / (1.0 + p * x);
|
|
304
|
+
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
|
|
305
|
+
return 0.5 * (1.0 + sign * y);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get trend description
|
|
309
|
+
*/
|
|
310
|
+
getTrendDescription(trend) {
|
|
311
|
+
const percentStrength = Math.round(trend.strength * 100);
|
|
312
|
+
switch (trend.direction) {
|
|
313
|
+
case 'degrading':
|
|
314
|
+
return `Degrading (${percentStrength}% confidence)`;
|
|
315
|
+
case 'improving':
|
|
316
|
+
return `Improving (${percentStrength}% confidence)`;
|
|
317
|
+
default:
|
|
318
|
+
return `Stable (${percentStrength}% confidence)`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Format time to threshold
|
|
323
|
+
*/
|
|
324
|
+
formatTimeToThreshold(ms) {
|
|
325
|
+
if (ms < 0)
|
|
326
|
+
return 'Already breached';
|
|
327
|
+
if (ms === 0)
|
|
328
|
+
return 'At threshold';
|
|
329
|
+
const days = Math.floor(ms / (24 * 60 * 60 * 1000));
|
|
330
|
+
const hours = Math.floor((ms % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
|
|
331
|
+
if (days > 0) {
|
|
332
|
+
return `${days} day${days > 1 ? 's' : ''}`;
|
|
333
|
+
}
|
|
334
|
+
if (hours > 0) {
|
|
335
|
+
return `${hours} hour${hours > 1 ? 's' : ''}`;
|
|
336
|
+
}
|
|
337
|
+
return '< 1 hour';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Create a regression trend analyzer
|
|
342
|
+
*/
|
|
343
|
+
export function createRegressionTrendAnalyzer() {
|
|
344
|
+
return new RegressionTrendAnalyzer();
|
|
345
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Regression Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Types for detecting performance and quality regressions.
|
|
5
|
+
* Uses statistical analysis to identify significant changes in metrics.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Regression types
|
|
9
|
+
*/
|
|
10
|
+
export type RegressionType = 'performance' | 'quality' | 'coverage' | 'reliability' | 'security' | 'api_breaking' | 'custom';
|
|
11
|
+
/**
|
|
12
|
+
* Regression severity
|
|
13
|
+
*/
|
|
14
|
+
export type RegressionSeverity = 'info' | 'minor' | 'moderate' | 'major' | 'critical';
|
|
15
|
+
/**
|
|
16
|
+
* Regression status
|
|
17
|
+
*/
|
|
18
|
+
export type RegressionStatus = 'detected' | 'investigating' | 'confirmed' | 'false_positive' | 'resolved' | 'ignored';
|
|
19
|
+
/**
|
|
20
|
+
* Regression detection result
|
|
21
|
+
*/
|
|
22
|
+
export interface RegressionDetection {
|
|
23
|
+
/** Unique identifier */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Run ID where regression was detected */
|
|
26
|
+
runId: string;
|
|
27
|
+
/** Baseline run ID for comparison */
|
|
28
|
+
baselineRunId: string;
|
|
29
|
+
/** Regression type */
|
|
30
|
+
type: RegressionType;
|
|
31
|
+
/** Severity level */
|
|
32
|
+
severity: RegressionSeverity;
|
|
33
|
+
/** Status */
|
|
34
|
+
status: RegressionStatus;
|
|
35
|
+
/** Metric name that regressed */
|
|
36
|
+
metricName: string;
|
|
37
|
+
/** Baseline value */
|
|
38
|
+
baselineValue: number;
|
|
39
|
+
/** Current value */
|
|
40
|
+
currentValue: number;
|
|
41
|
+
/** Absolute change */
|
|
42
|
+
absoluteChange: number;
|
|
43
|
+
/** Percentage change */
|
|
44
|
+
percentChange: number;
|
|
45
|
+
/** Statistical significance (p-value) */
|
|
46
|
+
significance: number;
|
|
47
|
+
/** Confidence (0-1) */
|
|
48
|
+
confidence: number;
|
|
49
|
+
/** Direction (worse = degradation, better = improvement) */
|
|
50
|
+
direction: 'worse' | 'better' | 'neutral';
|
|
51
|
+
/** Affected component/test */
|
|
52
|
+
affectedComponent: string;
|
|
53
|
+
/** Gate where detected */
|
|
54
|
+
gate: string;
|
|
55
|
+
/** Context information */
|
|
56
|
+
context?: {
|
|
57
|
+
file?: string;
|
|
58
|
+
line?: number;
|
|
59
|
+
test?: string;
|
|
60
|
+
endpoint?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
};
|
|
63
|
+
/** Suggested actions */
|
|
64
|
+
suggestions: string[];
|
|
65
|
+
/** Related commits */
|
|
66
|
+
commits?: string[];
|
|
67
|
+
/** Detection timestamp */
|
|
68
|
+
timestamp: number;
|
|
69
|
+
/** Notes */
|
|
70
|
+
notes?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Regression threshold configuration
|
|
74
|
+
*/
|
|
75
|
+
export interface RegressionThreshold {
|
|
76
|
+
/** Metric name */
|
|
77
|
+
metric: string;
|
|
78
|
+
/** Maximum allowed regression (percentage) */
|
|
79
|
+
maxRegression: number;
|
|
80
|
+
/** Minimum change to trigger alert (absolute) */
|
|
81
|
+
minDelta: number;
|
|
82
|
+
/** Statistical significance threshold (p-value) */
|
|
83
|
+
significanceLevel: number;
|
|
84
|
+
/** Direction to monitor */
|
|
85
|
+
direction: 'increase' | 'decrease' | 'both';
|
|
86
|
+
/** Regression severity if threshold exceeded */
|
|
87
|
+
severity: RegressionSeverity;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Regression configuration
|
|
91
|
+
*/
|
|
92
|
+
export interface RegressionConfig {
|
|
93
|
+
/** Enable regression detection */
|
|
94
|
+
enabled: boolean;
|
|
95
|
+
/** Automatic detection on each run */
|
|
96
|
+
autoDetect: boolean;
|
|
97
|
+
/** Baseline window (number of runs to average) */
|
|
98
|
+
baselineWindow: number;
|
|
99
|
+
/** Minimum data points for statistical analysis */
|
|
100
|
+
minDataPoints: number;
|
|
101
|
+
/** Statistical method */
|
|
102
|
+
method: 'zscore' | 'ttest' | 'mannwhitney' | 'percentile';
|
|
103
|
+
/** Thresholds by metric type */
|
|
104
|
+
thresholds: {
|
|
105
|
+
performance: RegressionThreshold[];
|
|
106
|
+
quality: RegressionThreshold[];
|
|
107
|
+
coverage: RegressionThreshold[];
|
|
108
|
+
reliability: RegressionThreshold[];
|
|
109
|
+
security: RegressionThreshold[];
|
|
110
|
+
};
|
|
111
|
+
/** Alert configuration */
|
|
112
|
+
alerts: {
|
|
113
|
+
enabled: boolean;
|
|
114
|
+
onNewRegression: boolean;
|
|
115
|
+
onSeverity: RegressionSeverity[];
|
|
116
|
+
};
|
|
117
|
+
/** Ignore patterns (metrics to ignore) */
|
|
118
|
+
ignorePatterns: string[];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Time series data point
|
|
122
|
+
*/
|
|
123
|
+
export interface TimeSeriesPoint {
|
|
124
|
+
timestamp: number;
|
|
125
|
+
value: number;
|
|
126
|
+
runId: string;
|
|
127
|
+
metadata?: Record<string, unknown>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Statistical test result
|
|
131
|
+
*/
|
|
132
|
+
export interface StatisticalTest {
|
|
133
|
+
/** Test name */
|
|
134
|
+
test: string;
|
|
135
|
+
/** P-value */
|
|
136
|
+
pValue: number;
|
|
137
|
+
/** Is significant? */
|
|
138
|
+
significant: boolean;
|
|
139
|
+
/** Test statistic */
|
|
140
|
+
statistic: number;
|
|
141
|
+
/** Confidence interval */
|
|
142
|
+
confidenceInterval?: {
|
|
143
|
+
lower: number;
|
|
144
|
+
upper: number;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Regression trend analysis
|
|
149
|
+
*/
|
|
150
|
+
export interface RegressionTrend {
|
|
151
|
+
/** Metric name */
|
|
152
|
+
metric: string;
|
|
153
|
+
/** Current trend direction */
|
|
154
|
+
direction: 'improving' | 'degrading' | 'stable';
|
|
155
|
+
/** Trend strength (0-1) */
|
|
156
|
+
strength: number;
|
|
157
|
+
/** Projected value (if trend continues) */
|
|
158
|
+
projected?: number;
|
|
159
|
+
/** Time to threshold (if degrading) */
|
|
160
|
+
timeToThreshold?: number;
|
|
161
|
+
/** Data points analyzed */
|
|
162
|
+
dataPoints: number;
|
|
163
|
+
/** Regression detections in trend */
|
|
164
|
+
regressions: number;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Regression report
|
|
168
|
+
*/
|
|
169
|
+
export interface RegressionReport {
|
|
170
|
+
/** Report ID */
|
|
171
|
+
id: string;
|
|
172
|
+
/** Report timestamp */
|
|
173
|
+
timestamp: number;
|
|
174
|
+
/** Comparison period */
|
|
175
|
+
period: {
|
|
176
|
+
start: number;
|
|
177
|
+
end: number;
|
|
178
|
+
baselineStart: number;
|
|
179
|
+
baselineEnd: number;
|
|
180
|
+
};
|
|
181
|
+
/** Detected regressions */
|
|
182
|
+
regressions: RegressionDetection[];
|
|
183
|
+
/** Summary statistics */
|
|
184
|
+
summary: {
|
|
185
|
+
total: number;
|
|
186
|
+
bySeverity: Record<RegressionSeverity, number>;
|
|
187
|
+
byType: Record<RegressionType, number>;
|
|
188
|
+
new: number;
|
|
189
|
+
resolved: number;
|
|
190
|
+
ongoing: number;
|
|
191
|
+
};
|
|
192
|
+
/** Overall health status */
|
|
193
|
+
healthStatus: 'healthy' | 'warning' | 'critical';
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Baseline data
|
|
197
|
+
*/
|
|
198
|
+
export interface Baseline {
|
|
199
|
+
/** Metric name */
|
|
200
|
+
metric: string;
|
|
201
|
+
/** Average value */
|
|
202
|
+
mean: number;
|
|
203
|
+
/** Standard deviation */
|
|
204
|
+
stdDev: number;
|
|
205
|
+
/** Minimum value */
|
|
206
|
+
min: number;
|
|
207
|
+
/** Maximum value */
|
|
208
|
+
max: number;
|
|
209
|
+
/** Percentiles */
|
|
210
|
+
percentiles: {
|
|
211
|
+
p50: number;
|
|
212
|
+
p90: number;
|
|
213
|
+
p95: number;
|
|
214
|
+
p99: number;
|
|
215
|
+
};
|
|
216
|
+
/** Sample size */
|
|
217
|
+
sampleSize: number;
|
|
218
|
+
/** Calculated from run IDs */
|
|
219
|
+
runIds: string[];
|
|
220
|
+
/** Baseline timestamp */
|
|
221
|
+
timestamp: number;
|
|
222
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression Vault Integration
|
|
3
|
+
*
|
|
4
|
+
* Integrates regression data storage and retrieval with the Evidence Vault.
|
|
5
|
+
*/
|
|
6
|
+
import type { Database } from 'sqlite3';
|
|
7
|
+
import type { RegressionDetection } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Regression Vault class
|
|
10
|
+
*/
|
|
11
|
+
export declare class RegressionVault {
|
|
12
|
+
private db;
|
|
13
|
+
private dbRun;
|
|
14
|
+
private dbAll;
|
|
15
|
+
private dbGet;
|
|
16
|
+
constructor(db: Database);
|
|
17
|
+
/**
|
|
18
|
+
* Initialize regression tables
|
|
19
|
+
*/
|
|
20
|
+
initialize(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Store a regression detection
|
|
23
|
+
*/
|
|
24
|
+
storeRegression(regression: RegressionDetection): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Store multiple regressions
|
|
27
|
+
*/
|
|
28
|
+
storeRegressions(regressions: RegressionDetection[]): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get regressions by run ID
|
|
31
|
+
*/
|
|
32
|
+
getRegressionsByRun(runId: string): Promise<RegressionDetection[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Get regressions by type
|
|
35
|
+
*/
|
|
36
|
+
getRegressionsByType(type: string, limit?: number): Promise<RegressionDetection[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Get regressions by severity
|
|
39
|
+
*/
|
|
40
|
+
getRegressionsBySeverity(severity: string, limit?: number): Promise<RegressionDetection[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Get regressions by status
|
|
43
|
+
*/
|
|
44
|
+
getRegressionsByStatus(status: string, limit?: number): Promise<RegressionDetection[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Get recent regressions
|
|
47
|
+
*/
|
|
48
|
+
getRecentRegressions(limit?: number): Promise<RegressionDetection[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Get regressions for a metric
|
|
51
|
+
*/
|
|
52
|
+
getRegressionsByMetric(metricName: string, limit?: number): Promise<RegressionDetection[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Update regression status
|
|
55
|
+
*/
|
|
56
|
+
updateRegressionStatus(id: string, status: RegressionDetection['status'], notes?: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Store regression snapshot
|
|
59
|
+
*/
|
|
60
|
+
storeSnapshot(runId: string, metricName: string, value: number, baselineValue?: number): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Get metric history for trend analysis
|
|
63
|
+
*/
|
|
64
|
+
getMetricHistory(metricName: string, limit?: number): Promise<Array<{
|
|
65
|
+
runId: string;
|
|
66
|
+
value: number;
|
|
67
|
+
timestamp: number;
|
|
68
|
+
}>>;
|
|
69
|
+
/**
|
|
70
|
+
* Get regression statistics
|
|
71
|
+
*/
|
|
72
|
+
getStatistics(): Promise<{
|
|
73
|
+
total: number;
|
|
74
|
+
bySeverity: Record<string, number>;
|
|
75
|
+
byType: Record<string, number>;
|
|
76
|
+
byStatus: Record<string, number>;
|
|
77
|
+
recent: number;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Delete old regressions
|
|
81
|
+
*/
|
|
82
|
+
deleteOldRegressions(olderThanMs: number): Promise<number>;
|
|
83
|
+
/**
|
|
84
|
+
* Map database rows to RegressionDetection objects
|
|
85
|
+
*/
|
|
86
|
+
private mapRowsToDetections;
|
|
87
|
+
}
|