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,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects regressions using statistical methods.
|
|
5
|
+
* Supports Z-score, T-test, Mann-Whitney U, and percentile-based detection.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Regression Detector class
|
|
9
|
+
*/
|
|
10
|
+
export class RegressionDetector {
|
|
11
|
+
config;
|
|
12
|
+
baselines = new Map();
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = {
|
|
15
|
+
...this.createDefaultConfig(),
|
|
16
|
+
...config
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Detect regressions by comparing current values to baseline
|
|
21
|
+
*/
|
|
22
|
+
detectRegressions(currentData, currentRunId, gate, context) {
|
|
23
|
+
const regressions = [];
|
|
24
|
+
for (const [metricName, currentValue] of Object.entries(currentData)) {
|
|
25
|
+
if (this.shouldIgnore(metricName)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const baseline = this.baselines.get(metricName);
|
|
29
|
+
if (!baseline || baseline.sampleSize < this.config.minDataPoints) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const regression = this.detectMetricRegression(metricName, currentValue, baseline, currentRunId, gate, context);
|
|
33
|
+
if (regression) {
|
|
34
|
+
regressions.push(regression);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return regressions;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Detect regression for a single metric
|
|
41
|
+
*/
|
|
42
|
+
detectMetricRegression(metricName, currentValue, baseline, runId, gate, context) {
|
|
43
|
+
const threshold = this.getThresholdForMetric(metricName);
|
|
44
|
+
if (!threshold) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Calculate statistical significance
|
|
48
|
+
const test = this.performStatisticalTest(currentValue, baseline);
|
|
49
|
+
// Calculate change
|
|
50
|
+
const absoluteChange = currentValue - baseline.mean;
|
|
51
|
+
const percentChange = baseline.mean !== 0
|
|
52
|
+
? (absoluteChange / baseline.mean) * 100
|
|
53
|
+
: 0;
|
|
54
|
+
// Determine direction
|
|
55
|
+
const direction = this.getDIRECTION(metricName, absoluteChange, threshold);
|
|
56
|
+
// Check if regression detected
|
|
57
|
+
const isRegression = this.isRegression(currentValue, baseline, threshold, test, direction);
|
|
58
|
+
if (!isRegression) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Determine severity
|
|
62
|
+
const severity = this.calculateSeverity(absoluteChange, percentChange, threshold, test.pValue);
|
|
63
|
+
// Generate suggestions
|
|
64
|
+
const suggestions = this.generateSuggestions(metricName, direction, severity, currentValue, baseline.mean);
|
|
65
|
+
return {
|
|
66
|
+
id: `regression_${Date.now()}_${metricName.replace(/\W/g, '_')}`,
|
|
67
|
+
runId,
|
|
68
|
+
baselineRunId: baseline.runIds[baseline.runIds.length - 1],
|
|
69
|
+
type: this.getTypeForMetric(metricName),
|
|
70
|
+
severity,
|
|
71
|
+
status: 'detected',
|
|
72
|
+
metricName,
|
|
73
|
+
baselineValue: baseline.mean,
|
|
74
|
+
currentValue,
|
|
75
|
+
absoluteChange,
|
|
76
|
+
percentChange,
|
|
77
|
+
significance: test.pValue,
|
|
78
|
+
confidence: 1 - test.pValue,
|
|
79
|
+
direction,
|
|
80
|
+
affectedComponent: this.extractComponent(metricName, context),
|
|
81
|
+
gate,
|
|
82
|
+
context,
|
|
83
|
+
suggestions,
|
|
84
|
+
timestamp: Date.now()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Perform statistical test
|
|
89
|
+
*/
|
|
90
|
+
performStatisticalTest(currentValue, baseline) {
|
|
91
|
+
let test;
|
|
92
|
+
switch (this.config.method) {
|
|
93
|
+
case 'zscore':
|
|
94
|
+
test = this.zScoreTest(currentValue, baseline);
|
|
95
|
+
break;
|
|
96
|
+
case 'ttest':
|
|
97
|
+
test = this.tTest(currentValue, baseline);
|
|
98
|
+
break;
|
|
99
|
+
case 'percentile':
|
|
100
|
+
test = this.percentileTest(currentValue, baseline);
|
|
101
|
+
break;
|
|
102
|
+
case 'mannwhitney':
|
|
103
|
+
default:
|
|
104
|
+
test = this.zScoreTest(currentValue, baseline); // Default to z-score for single value
|
|
105
|
+
}
|
|
106
|
+
return test;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Z-score test
|
|
110
|
+
*/
|
|
111
|
+
zScoreTest(value, baseline) {
|
|
112
|
+
if (baseline.stdDev === 0) {
|
|
113
|
+
// When baseline has no variance, any change is highly significant
|
|
114
|
+
if (value !== baseline.mean) {
|
|
115
|
+
return {
|
|
116
|
+
test: 'zscore',
|
|
117
|
+
pValue: 0.0001, // Very small p-value for high significance
|
|
118
|
+
significant: true,
|
|
119
|
+
statistic: value > baseline.mean ? Infinity : -Infinity
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
test: 'zscore',
|
|
124
|
+
pValue: 1,
|
|
125
|
+
significant: false,
|
|
126
|
+
statistic: 0
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const zScore = (value - baseline.mean) / baseline.stdDev;
|
|
130
|
+
// Two-tailed p-value
|
|
131
|
+
const pValue = 2 * (1 - this.normalCDF(Math.abs(zScore)));
|
|
132
|
+
return {
|
|
133
|
+
test: 'zscore',
|
|
134
|
+
pValue,
|
|
135
|
+
significant: pValue < 0.05,
|
|
136
|
+
statistic: zScore,
|
|
137
|
+
confidenceInterval: {
|
|
138
|
+
lower: baseline.mean - 1.96 * baseline.stdDev,
|
|
139
|
+
upper: baseline.mean + 1.96 * baseline.stdDev
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* T-test (simplified for single value vs baseline)
|
|
145
|
+
*/
|
|
146
|
+
tTest(value, baseline) {
|
|
147
|
+
// For single value, use z-score approximation with n-1 degrees of freedom
|
|
148
|
+
const df = baseline.sampleSize - 1;
|
|
149
|
+
const tScore = (value - baseline.mean) / (baseline.stdDev / Math.sqrt(baseline.sampleSize));
|
|
150
|
+
// Approximate p-value (simplified)
|
|
151
|
+
const pValue = 2 * (1 - this.studentTCDF(Math.abs(tScore), df));
|
|
152
|
+
return {
|
|
153
|
+
test: 'ttest',
|
|
154
|
+
pValue,
|
|
155
|
+
significant: pValue < 0.05,
|
|
156
|
+
statistic: tScore
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Percentile test
|
|
161
|
+
*/
|
|
162
|
+
percentileTest(value, baseline) {
|
|
163
|
+
const percentile = this.calculatePercentile(value, baseline);
|
|
164
|
+
// P-value based on percentile (extreme values have low p-value)
|
|
165
|
+
const pValue = percentile > 95
|
|
166
|
+
? (100 - percentile) / 100
|
|
167
|
+
: percentile < 5
|
|
168
|
+
? percentile / 100
|
|
169
|
+
: 0.5;
|
|
170
|
+
return {
|
|
171
|
+
test: 'percentile',
|
|
172
|
+
pValue,
|
|
173
|
+
significant: pValue < 0.05,
|
|
174
|
+
statistic: percentile
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Calculate percentile for a value in baseline
|
|
179
|
+
*/
|
|
180
|
+
calculatePercentile(value, baseline) {
|
|
181
|
+
if (value < baseline.min)
|
|
182
|
+
return 0;
|
|
183
|
+
if (value > baseline.max)
|
|
184
|
+
return 100;
|
|
185
|
+
// Estimate percentile using mean and stdDev (assuming normal distribution)
|
|
186
|
+
const zScore = (value - baseline.mean) / baseline.stdDev;
|
|
187
|
+
return this.normalCDF(zScore) * 100;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Normal CDF (cumulative distribution function)
|
|
191
|
+
*/
|
|
192
|
+
normalCDF(x) {
|
|
193
|
+
// Approximation of normal CDF
|
|
194
|
+
const a1 = 0.254829592;
|
|
195
|
+
const a2 = -0.284496736;
|
|
196
|
+
const a3 = 1.421413741;
|
|
197
|
+
const a4 = -1.453152027;
|
|
198
|
+
const a5 = 1.061405429;
|
|
199
|
+
const p = 0.3275911;
|
|
200
|
+
const sign = x < 0 ? -1 : 1;
|
|
201
|
+
x = Math.abs(x) / Math.sqrt(2);
|
|
202
|
+
const t = 1.0 / (1.0 + p * x);
|
|
203
|
+
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
|
|
204
|
+
return 0.5 * (1.0 + sign * y);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Student's t CDF approximation
|
|
208
|
+
*/
|
|
209
|
+
studentTCDF(t, df) {
|
|
210
|
+
// Simplified approximation for large df
|
|
211
|
+
if (df > 30) {
|
|
212
|
+
return this.normalCDF(t);
|
|
213
|
+
}
|
|
214
|
+
// For small df, use approximation
|
|
215
|
+
return this.normalCDF(t * 0.9); // Conservative approximation
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Check if change constitutes a regression
|
|
219
|
+
*/
|
|
220
|
+
isRegression(currentValue, baseline, threshold, test, direction) {
|
|
221
|
+
// Must be statistically significant
|
|
222
|
+
if (!test.significant && test.pValue > threshold.significanceLevel) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
// Must exceed minimum delta
|
|
226
|
+
const absChange = Math.abs(currentValue - baseline.mean);
|
|
227
|
+
if (absChange < threshold.minDelta) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
// Must be in the "worse" direction
|
|
231
|
+
if (direction !== 'worse') {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
// For percentage-based thresholds
|
|
235
|
+
const percentChange = baseline.mean !== 0
|
|
236
|
+
? (absChange / baseline.mean) * 100
|
|
237
|
+
: 0;
|
|
238
|
+
return percentChange > threshold.maxRegression;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get direction of change (worse/better/neutral)
|
|
242
|
+
*/
|
|
243
|
+
getDIRECTION(metricName, change, threshold) {
|
|
244
|
+
// For 'increase' direction metrics (like latency): higher value is worse
|
|
245
|
+
// For 'decrease' direction metrics (like coverage): lower value is worse
|
|
246
|
+
const isWorse = (threshold.direction === 'increase' && change > 0) ||
|
|
247
|
+
(threshold.direction === 'decrease' && change < 0);
|
|
248
|
+
const isBetter = (threshold.direction === 'increase' && change < 0) ||
|
|
249
|
+
(threshold.direction === 'decrease' && change > 0);
|
|
250
|
+
if (isWorse) {
|
|
251
|
+
return 'worse';
|
|
252
|
+
}
|
|
253
|
+
if (isBetter) {
|
|
254
|
+
return 'better';
|
|
255
|
+
}
|
|
256
|
+
return 'neutral';
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Calculate regression severity
|
|
260
|
+
*/
|
|
261
|
+
calculateSeverity(absoluteChange, percentChange, threshold, pValue) {
|
|
262
|
+
// Critical: extreme regression or very high significance
|
|
263
|
+
if (percentChange > threshold.maxRegression * 5 || pValue < 0.001) {
|
|
264
|
+
return 'critical';
|
|
265
|
+
}
|
|
266
|
+
// Major: severe regression
|
|
267
|
+
if (percentChange > threshold.maxRegression * 3 || pValue < 0.01) {
|
|
268
|
+
return 'major';
|
|
269
|
+
}
|
|
270
|
+
// Moderate: notable regression
|
|
271
|
+
if (percentChange > threshold.maxRegression * 2 || pValue < 0.05) {
|
|
272
|
+
return 'moderate';
|
|
273
|
+
}
|
|
274
|
+
// Minor: slight regression
|
|
275
|
+
return 'minor';
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Generate suggestions for regression
|
|
279
|
+
*/
|
|
280
|
+
generateSuggestions(metricName, direction, severity, currentValue, baselineValue) {
|
|
281
|
+
const suggestions = [];
|
|
282
|
+
if (direction === 'worse') {
|
|
283
|
+
suggestions.push(`Investigate recent changes to ${metricName}`);
|
|
284
|
+
suggestions.push(`Compare with baseline: ${baselineValue.toFixed(2)} → ${currentValue.toFixed(2)}`);
|
|
285
|
+
if (severity === 'critical' || severity === 'major') {
|
|
286
|
+
suggestions.push('Consider rolling back recent changes');
|
|
287
|
+
suggestions.push('Review test environment for external factors');
|
|
288
|
+
}
|
|
289
|
+
if (metricName.includes('duration') || metricName.includes('latency')) {
|
|
290
|
+
suggestions.push('Check for infinite loops or blocking operations');
|
|
291
|
+
suggestions.push('Profile the code to identify bottlenecks');
|
|
292
|
+
}
|
|
293
|
+
if (metricName.includes('memory') || metricName.includes('heap')) {
|
|
294
|
+
suggestions.push('Check for memory leaks');
|
|
295
|
+
suggestions.push('Review allocation patterns');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return suggestions;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Update baseline with new data
|
|
302
|
+
*/
|
|
303
|
+
updateBaseline(metricName, data) {
|
|
304
|
+
if (data.length === 0)
|
|
305
|
+
return;
|
|
306
|
+
const values = data.map(d => d.value);
|
|
307
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
308
|
+
const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
309
|
+
const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length;
|
|
310
|
+
const stdDev = Math.sqrt(variance);
|
|
311
|
+
this.baselines.set(metricName, {
|
|
312
|
+
metric: metricName,
|
|
313
|
+
mean,
|
|
314
|
+
stdDev,
|
|
315
|
+
min: sorted[0],
|
|
316
|
+
max: sorted[sorted.length - 1],
|
|
317
|
+
percentiles: {
|
|
318
|
+
p50: sorted[Math.floor(sorted.length * 0.5)],
|
|
319
|
+
p90: sorted[Math.floor(sorted.length * 0.9)],
|
|
320
|
+
p95: sorted[Math.floor(sorted.length * 0.95)],
|
|
321
|
+
p99: sorted[Math.floor(sorted.length * 0.99)]
|
|
322
|
+
},
|
|
323
|
+
sampleSize: values.length,
|
|
324
|
+
runIds: data.map(d => d.runId),
|
|
325
|
+
timestamp: Date.now()
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get baseline for a metric
|
|
330
|
+
*/
|
|
331
|
+
getBaseline(metricName) {
|
|
332
|
+
return this.baselines.get(metricName);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get all baselines
|
|
336
|
+
*/
|
|
337
|
+
getAllBaselines() {
|
|
338
|
+
return Array.from(this.baselines.values());
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Get threshold for a metric
|
|
342
|
+
*/
|
|
343
|
+
getThresholdForMetric(metricName) {
|
|
344
|
+
// Check all threshold categories
|
|
345
|
+
for (const thresholds of Object.values(this.config.thresholds)) {
|
|
346
|
+
const match = thresholds.find(t => this.metricMatchesPattern(metricName, t.metric));
|
|
347
|
+
if (match)
|
|
348
|
+
return match;
|
|
349
|
+
}
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Check if metric matches pattern
|
|
354
|
+
*/
|
|
355
|
+
metricMatchesPattern(metricName, pattern) {
|
|
356
|
+
if (pattern === '*')
|
|
357
|
+
return true;
|
|
358
|
+
if (pattern.endsWith('*')) {
|
|
359
|
+
const prefix = pattern.slice(0, -1);
|
|
360
|
+
return metricName.startsWith(prefix);
|
|
361
|
+
}
|
|
362
|
+
return metricName === pattern;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get regression type for metric
|
|
366
|
+
*/
|
|
367
|
+
getTypeForMetric(metricName) {
|
|
368
|
+
if (metricName.includes('duration') || metricName.includes('latency') || metricName.includes('time')) {
|
|
369
|
+
return 'performance';
|
|
370
|
+
}
|
|
371
|
+
if (metricName.includes('coverage')) {
|
|
372
|
+
return 'coverage';
|
|
373
|
+
}
|
|
374
|
+
if (metricName.includes('flaky') || metricName.includes('stability')) {
|
|
375
|
+
return 'reliability';
|
|
376
|
+
}
|
|
377
|
+
if (metricName.includes('vulnerability') || metricName.includes('security')) {
|
|
378
|
+
return 'security';
|
|
379
|
+
}
|
|
380
|
+
if (metricName.includes('api') || metricName.includes('endpoint')) {
|
|
381
|
+
return 'api_breaking';
|
|
382
|
+
}
|
|
383
|
+
return 'quality';
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Extract component name from metric and context
|
|
387
|
+
*/
|
|
388
|
+
extractComponent(metricName, context) {
|
|
389
|
+
if (context?.file)
|
|
390
|
+
return String(context.file);
|
|
391
|
+
if (context?.test)
|
|
392
|
+
return String(context.test);
|
|
393
|
+
if (context?.endpoint)
|
|
394
|
+
return String(context.endpoint);
|
|
395
|
+
// Extract from metric name
|
|
396
|
+
const parts = metricName.split(/[._/]/);
|
|
397
|
+
return parts[0] || metricName;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Check if metric should be ignored
|
|
401
|
+
*/
|
|
402
|
+
shouldIgnore(metricName) {
|
|
403
|
+
return this.config.ignorePatterns.some(pattern => this.metricMatchesPattern(metricName, pattern));
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Create default configuration
|
|
407
|
+
*/
|
|
408
|
+
createDefaultConfig() {
|
|
409
|
+
return {
|
|
410
|
+
enabled: true,
|
|
411
|
+
autoDetect: true,
|
|
412
|
+
baselineWindow: 30,
|
|
413
|
+
minDataPoints: 5,
|
|
414
|
+
method: 'zscore',
|
|
415
|
+
thresholds: {
|
|
416
|
+
performance: [
|
|
417
|
+
{
|
|
418
|
+
metric: '*_duration',
|
|
419
|
+
maxRegression: 20, // 20% slower
|
|
420
|
+
minDelta: 100, // 100ms minimum change
|
|
421
|
+
significanceLevel: 0.05,
|
|
422
|
+
direction: 'increase',
|
|
423
|
+
severity: 'moderate'
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
metric: '*_latency',
|
|
427
|
+
maxRegression: 15,
|
|
428
|
+
minDelta: 50,
|
|
429
|
+
significanceLevel: 0.05,
|
|
430
|
+
direction: 'increase',
|
|
431
|
+
severity: 'moderate'
|
|
432
|
+
}
|
|
433
|
+
],
|
|
434
|
+
quality: [
|
|
435
|
+
{
|
|
436
|
+
metric: 'pass_rate',
|
|
437
|
+
maxRegression: 5, // 5% decrease
|
|
438
|
+
minDelta: 1,
|
|
439
|
+
significanceLevel: 0.05,
|
|
440
|
+
direction: 'decrease',
|
|
441
|
+
severity: 'major'
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
metric: 'failure_count',
|
|
445
|
+
maxRegression: 10, // 10% increase
|
|
446
|
+
minDelta: 1,
|
|
447
|
+
significanceLevel: 0.05,
|
|
448
|
+
direction: 'increase',
|
|
449
|
+
severity: 'moderate'
|
|
450
|
+
}
|
|
451
|
+
],
|
|
452
|
+
coverage: [
|
|
453
|
+
{
|
|
454
|
+
metric: 'line_coverage',
|
|
455
|
+
maxRegression: 2, // 2% decrease
|
|
456
|
+
minDelta: 0.5,
|
|
457
|
+
significanceLevel: 0.1,
|
|
458
|
+
direction: 'decrease',
|
|
459
|
+
severity: 'minor'
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
reliability: [
|
|
463
|
+
{
|
|
464
|
+
metric: 'flakiness_rate',
|
|
465
|
+
maxRegression: 5, // 5% increase
|
|
466
|
+
minDelta: 1,
|
|
467
|
+
significanceLevel: 0.05,
|
|
468
|
+
direction: 'increase',
|
|
469
|
+
severity: 'major'
|
|
470
|
+
}
|
|
471
|
+
],
|
|
472
|
+
security: [
|
|
473
|
+
{
|
|
474
|
+
metric: 'vulnerability_count',
|
|
475
|
+
maxRegression: 0, // Any increase is critical
|
|
476
|
+
minDelta: 1,
|
|
477
|
+
significanceLevel: 0.01,
|
|
478
|
+
direction: 'increase',
|
|
479
|
+
severity: 'critical'
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
},
|
|
483
|
+
alerts: {
|
|
484
|
+
enabled: true,
|
|
485
|
+
onNewRegression: true,
|
|
486
|
+
onSeverity: ['major', 'critical']
|
|
487
|
+
},
|
|
488
|
+
ignorePatterns: ['*_timestamp', '*_version', 'build_*']
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Create a regression detector
|
|
494
|
+
*/
|
|
495
|
+
export function createRegressionDetector(config) {
|
|
496
|
+
return new RegressionDetector(config);
|
|
497
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Regression Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Exports all regression detection functionality.
|
|
5
|
+
*/
|
|
6
|
+
export type { RegressionType, RegressionSeverity, RegressionStatus, RegressionDetection, RegressionThreshold, RegressionConfig, TimeSeriesPoint, StatisticalTest, RegressionTrend, RegressionReport, Baseline } from './types.js';
|
|
7
|
+
export { RegressionDetector, createRegressionDetector } from './detector.js';
|
|
8
|
+
export { RegressionTrendAnalyzer, createRegressionTrendAnalyzer, type TrendAnalysis, type ChangePoint } from './trend-analyzer.js';
|
|
9
|
+
export { RegressionVault } from './vault.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Regression Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Exports all regression detection functionality.
|
|
5
|
+
*/
|
|
6
|
+
// Detector
|
|
7
|
+
export { RegressionDetector, createRegressionDetector } from './detector.js';
|
|
8
|
+
// Trend Analyzer
|
|
9
|
+
export { RegressionTrendAnalyzer, createRegressionTrendAnalyzer } from './trend-analyzer.js';
|
|
10
|
+
// Vault integration
|
|
11
|
+
export { RegressionVault } from './vault.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression Trend Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes metric trends to predict potential regressions.
|
|
5
|
+
* Uses linear regression and change point detection.
|
|
6
|
+
*/
|
|
7
|
+
import type { TimeSeriesPoint, RegressionTrend, RegressionDetection } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Trend analysis result
|
|
10
|
+
*/
|
|
11
|
+
export interface TrendAnalysis {
|
|
12
|
+
/** Slope of trend */
|
|
13
|
+
slope: number;
|
|
14
|
+
/** Intercept */
|
|
15
|
+
intercept: number;
|
|
16
|
+
/** Correlation coefficient (R²) */
|
|
17
|
+
rSquared: number;
|
|
18
|
+
/** Is trend significant? */
|
|
19
|
+
significant: boolean;
|
|
20
|
+
/** Direction */
|
|
21
|
+
direction: 'increasing' | 'decreasing' | 'stable';
|
|
22
|
+
/** Projected value at future timestamp */
|
|
23
|
+
project(ms: number): number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Change point detection result
|
|
27
|
+
*/
|
|
28
|
+
export interface ChangePoint {
|
|
29
|
+
/** Index of change point */
|
|
30
|
+
index: number;
|
|
31
|
+
/** Timestamp of change */
|
|
32
|
+
timestamp: number;
|
|
33
|
+
/** Mean before change */
|
|
34
|
+
beforeMean: number;
|
|
35
|
+
/** Mean after change */
|
|
36
|
+
afterMean: number;
|
|
37
|
+
/** Magnitude of change */
|
|
38
|
+
magnitude: number;
|
|
39
|
+
/** Statistical significance */
|
|
40
|
+
pValue: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Regression Trend Analyzer class
|
|
44
|
+
*/
|
|
45
|
+
export declare class RegressionTrendAnalyzer {
|
|
46
|
+
/**
|
|
47
|
+
* Analyze trend for a metric
|
|
48
|
+
*/
|
|
49
|
+
analyzeTrend(data: TimeSeriesPoint[], windowMs?: number): RegressionTrend | null;
|
|
50
|
+
/**
|
|
51
|
+
* Perform linear regression on time series
|
|
52
|
+
*/
|
|
53
|
+
linearRegression(data: TimeSeriesPoint[]): TrendAnalysis;
|
|
54
|
+
/**
|
|
55
|
+
* Detect change points in time series
|
|
56
|
+
*/
|
|
57
|
+
detectChangePoints(data: TimeSeriesPoint[], minSegmentSize?: number): ChangePoint[];
|
|
58
|
+
/**
|
|
59
|
+
* Estimate p-value for change point
|
|
60
|
+
*/
|
|
61
|
+
private estimatePValue;
|
|
62
|
+
/**
|
|
63
|
+
* Project trend forward
|
|
64
|
+
*/
|
|
65
|
+
private projectTrend;
|
|
66
|
+
/**
|
|
67
|
+
* Get trend direction for reporting
|
|
68
|
+
*/
|
|
69
|
+
private getTrendDirection;
|
|
70
|
+
/**
|
|
71
|
+
* Calculate time until threshold breach
|
|
72
|
+
*/
|
|
73
|
+
calculateTimeToThreshold(data: TimeSeriesPoint[], threshold: number, isIncreasingBad: boolean): number | null;
|
|
74
|
+
/**
|
|
75
|
+
* Compare two periods for regression
|
|
76
|
+
*/
|
|
77
|
+
comparePeriods(baseline: TimeSeriesPoint[], current: TimeSeriesPoint[], metric: string): RegressionDetection | null;
|
|
78
|
+
/**
|
|
79
|
+
* Infer regression type from metric name
|
|
80
|
+
*/
|
|
81
|
+
private inferType;
|
|
82
|
+
/**
|
|
83
|
+
* Check if change direction indicates regression
|
|
84
|
+
*/
|
|
85
|
+
private isRegressionDirection;
|
|
86
|
+
/**
|
|
87
|
+
* Normal CDF approximation
|
|
88
|
+
*/
|
|
89
|
+
private normalCDF;
|
|
90
|
+
/**
|
|
91
|
+
* Get trend description
|
|
92
|
+
*/
|
|
93
|
+
getTrendDescription(trend: RegressionTrend): string;
|
|
94
|
+
/**
|
|
95
|
+
* Format time to threshold
|
|
96
|
+
*/
|
|
97
|
+
formatTimeToThreshold(ms: number): string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create a regression trend analyzer
|
|
101
|
+
*/
|
|
102
|
+
export declare function createRegressionTrendAnalyzer(): RegressionTrendAnalyzer;
|