@aplaytest/flaky 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * The quarantine codemod.
3
+ *
4
+ * Writes `@quarantine` into a test's own tag array rather than maintaining a
5
+ * side-list of quarantined tests. A suite that already excludes that tag needs
6
+ * no new runtime machinery, the state is greppable, it travels with the code
7
+ * in review, and deleting the test deletes the quarantine.
8
+ *
9
+ * WHY THIS LIVES IN @aplaytest/flaky AND NOT @aplaytest/core: ts-morph bundles the
10
+ * TypeScript compiler. `core` is imported by the reporter, which runs inside
11
+ * every test worker — loading a compiler there would be exactly the overhead
12
+ * that makes people switch the tool off. `flaky` is never on that path. When
13
+ * the heal engine needs the same plumbing, extract it to its own package
14
+ * rather than pushing it down into core.
15
+ */
16
+ import { Node, Project, SyntaxKind } from 'ts-morph';
17
+ export const QUARANTINE_TAG = '@quarantine';
18
+ /** `test(...)`, `test.only(...)`, `test.fail(...)` — but never `test.describe(...)`. */
19
+ function isTestCall(call) {
20
+ const expression = call.getExpression();
21
+ if (Node.isIdentifier(expression))
22
+ return expression.getText() === 'test';
23
+ if (Node.isPropertyAccessExpression(expression)) {
24
+ const object = expression.getExpression().getText();
25
+ const property = expression.getName();
26
+ // describe/beforeEach/afterEach take a title too; tagging those would
27
+ // quarantine an entire suite from a request to quarantine one test.
28
+ const NON_TEST = new Set(['describe', 'beforeEach', 'afterEach', 'beforeAll', 'afterAll', 'step']);
29
+ return object === 'test' && !NON_TEST.has(property);
30
+ }
31
+ return false;
32
+ }
33
+ function titleOf(call) {
34
+ const first = call.getArguments()[0];
35
+ if (first === undefined)
36
+ return null;
37
+ if (Node.isStringLiteral(first) || Node.isNoSubstitutionTemplateLiteral(first)) {
38
+ return first.getLiteralText();
39
+ }
40
+ return null;
41
+ }
42
+ function findTestCalls(source, title) {
43
+ return source
44
+ .getDescendantsOfKind(SyntaxKind.CallExpression)
45
+ .filter(call => isTestCall(call) && titleOf(call) === title);
46
+ }
47
+ /**
48
+ * The static fragments of a template-literal title, in order.
49
+ *
50
+ * `` `…selects "${name}", then ${path} is opened` `` yields
51
+ * `['…selects "', '", then ', ' is opened']`.
52
+ */
53
+ function templateQuasis(call) {
54
+ const first = call.getArguments()[0];
55
+ if (first === undefined || !Node.isTemplateExpression(first))
56
+ return null;
57
+ const parts = [first.getHead().getLiteralText()];
58
+ for (const span of first.getTemplateSpans()) {
59
+ parts.push(span.getLiteral().getLiteralText());
60
+ }
61
+ return parts;
62
+ }
63
+ /** Do the template's static fragments appear, in order, in this concrete title? */
64
+ function templateMatches(quasis, title) {
65
+ let cursor = 0;
66
+ for (const part of quasis) {
67
+ if (part === '')
68
+ continue;
69
+ const index = title.indexOf(part, cursor);
70
+ if (index === -1)
71
+ return false;
72
+ cursor = index + part.length;
73
+ }
74
+ return true;
75
+ }
76
+ /**
77
+ * Find a loop-generated test whose runtime title would match.
78
+ *
79
+ * Real suites parameterise: one `test()` call inside a `for` produces N tests
80
+ * with computed titles. Reporting a plain "not found" for those reads as "your
81
+ * title is wrong" and sends someone hunting a typo that is not there — so this
82
+ * case gets its own status and its own explanation.
83
+ */
84
+ function findParameterisedTest(source, title) {
85
+ for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
86
+ if (!isTestCall(call))
87
+ continue;
88
+ const quasis = templateQuasis(call);
89
+ if (quasis !== null && templateMatches(quasis, title))
90
+ return call;
91
+ }
92
+ return null;
93
+ }
94
+ /**
95
+ * Add the tag to the test's options object, creating one if absent.
96
+ *
97
+ * Three shapes exist in the wild and all three must round-trip:
98
+ * test('…', async () => {}) → insert `{ tag: [...] }`
99
+ * test('…', { tag: '@acceptance' }, async () => {}) → widen the string to an array
100
+ * test('…', { tag: ['@smoke'] }, async () => {}) → push onto the array
101
+ */
102
+ function addTagToCall(call, tag) {
103
+ const args = call.getArguments();
104
+ const options = args[1];
105
+ if (options !== undefined && Node.isObjectLiteralExpression(options)) {
106
+ const tagProperty = options.getProperty('tag');
107
+ if (tagProperty === undefined) {
108
+ options.addPropertyAssignment({ name: 'tag', initializer: `['${tag}']` });
109
+ return 'applied';
110
+ }
111
+ if (Node.isPropertyAssignment(tagProperty)) {
112
+ const initializer = tagProperty.getInitializer();
113
+ if (initializer !== undefined && Node.isArrayLiteralExpression(initializer)) {
114
+ const existing = initializer.getElements().map(e => e.getText().replace(/['"`]/g, ''));
115
+ if (existing.includes(tag))
116
+ return 'already-tagged';
117
+ initializer.addElement(`'${tag}'`);
118
+ return 'applied';
119
+ }
120
+ if (initializer !== undefined &&
121
+ (Node.isStringLiteral(initializer) || Node.isNoSubstitutionTemplateLiteral(initializer))) {
122
+ const existing = initializer.getLiteralText();
123
+ if (existing === tag)
124
+ return 'already-tagged';
125
+ tagProperty.setInitializer(`['${existing}', '${tag}']`);
126
+ return 'applied';
127
+ }
128
+ }
129
+ // A computed or spread tag value: append rather than rewrite, so we never
130
+ // discard an expression we do not understand.
131
+ options.addPropertyAssignment({ name: `/* atest */ tag`, initializer: `['${tag}']` });
132
+ return 'applied';
133
+ }
134
+ // No options object — insert one between the title and the body.
135
+ call.insertArgument(1, `{ tag: ['${tag}'] }`);
136
+ return 'applied';
137
+ }
138
+ function statementOf(call) {
139
+ return call.getFirstAncestorByKind(SyntaxKind.ExpressionStatement) ?? call;
140
+ }
141
+ /**
142
+ * Build the comment for insertion at `statement.getStart()`.
143
+ *
144
+ * That position sits AFTER the statement's existing leading whitespace, which
145
+ * drives both details here: the first line takes no indent prefix (the
146
+ * whitespace already in the file provides it), and the text ends with the
147
+ * indent so the statement it precedes keeps its own indentation instead of
148
+ * being left at column zero.
149
+ */
150
+ function renderComment(lines, indent) {
151
+ const body = lines.map(line => `${indent} * ${line}`).join('\n');
152
+ return `/**\n${body}\n${indent} */\n${indent}`;
153
+ }
154
+ /**
155
+ * Apply the quarantine tag. Pure with respect to disk — the caller decides
156
+ * whether to write `result.after`, so a dry run and a real run take exactly
157
+ * the same code path.
158
+ */
159
+ export function quarantineCodemod(sourceText, input) {
160
+ const tag = input.tag ?? QUARANTINE_TAG;
161
+ const project = new Project({ useInMemoryFileSystem: true });
162
+ const source = project.createSourceFile(input.file, sourceText, { overwrite: true });
163
+ let matches = findTestCalls(source, input.testTitle);
164
+ if (matches.length === 0) {
165
+ const parameterised = findParameterisedTest(source, input.testTitle);
166
+ if (parameterised !== null) {
167
+ const line = source.getLineAndColumnAtPos(parameterised.getStart()).line;
168
+ return {
169
+ status: 'parameterised',
170
+ file: input.file,
171
+ before: sourceText,
172
+ after: null,
173
+ line,
174
+ message: `"${input.testTitle}" is generated at runtime by the parameterised test at ` +
175
+ `${input.file}:${line}. Its title does not exist as a literal in the source, and the ` +
176
+ `single test() call there produces every case in the loop — tagging it would ` +
177
+ `quarantine all of them, not just this one.\n` +
178
+ `Narrow the loop's data, extract this case into its own test(), or fix it directly; ` +
179
+ `quarantine is not the right lever here.`,
180
+ };
181
+ }
182
+ return {
183
+ status: 'not-found',
184
+ file: input.file,
185
+ before: sourceText,
186
+ after: null,
187
+ line: null,
188
+ message: `No test titled "${input.testTitle}" in ${input.file}.`,
189
+ };
190
+ }
191
+ if (matches.length > 1 && input.line !== undefined) {
192
+ const onLine = matches.filter(call => source.getLineAndColumnAtPos(call.getStart()).line === input.line);
193
+ if (onLine.length === 1)
194
+ matches = onLine;
195
+ }
196
+ if (matches.length > 1) {
197
+ return {
198
+ status: 'ambiguous',
199
+ file: input.file,
200
+ before: sourceText,
201
+ after: null,
202
+ line: null,
203
+ message: `${matches.length} tests in ${input.file} share the title "${input.testTitle}". ` +
204
+ 'Pass the line number to disambiguate.',
205
+ };
206
+ }
207
+ const call = matches[0];
208
+ if (call === undefined) {
209
+ return {
210
+ status: 'not-found',
211
+ file: input.file,
212
+ before: sourceText,
213
+ after: null,
214
+ line: null,
215
+ message: `No test titled "${input.testTitle}" in ${input.file}.`,
216
+ };
217
+ }
218
+ const line = source.getLineAndColumnAtPos(call.getStart()).line;
219
+ const tagResult = addTagToCall(call, tag);
220
+ if (tagResult === 'already-tagged') {
221
+ return {
222
+ status: 'already-tagged',
223
+ file: input.file,
224
+ before: sourceText,
225
+ after: null,
226
+ line,
227
+ message: `"${input.testTitle}" already carries ${tag}.`,
228
+ };
229
+ }
230
+ // The comment is inserted LAST and by text position, because insertText
231
+ // invalidates every node handle obtained before it.
232
+ if (input.comment !== undefined && input.comment.length > 0) {
233
+ const statement = statementOf(call);
234
+ const start = statement.getStart();
235
+ const column = source.getLineAndColumnAtPos(start).column;
236
+ const indent = ' '.repeat(Math.max(0, column - 1));
237
+ source.insertText(start, renderComment(input.comment, indent));
238
+ }
239
+ return {
240
+ status: 'applied',
241
+ file: input.file,
242
+ before: sourceText,
243
+ after: source.getFullText(),
244
+ line,
245
+ message: `Tagged "${input.testTitle}" with ${tag}.`,
246
+ };
247
+ }
248
+ /** Remove the tag again — the release path. */
249
+ export function releaseCodemod(sourceText, input) {
250
+ const tag = input.tag ?? QUARANTINE_TAG;
251
+ const project = new Project({ useInMemoryFileSystem: true });
252
+ const source = project.createSourceFile(input.file, sourceText, { overwrite: true });
253
+ const matches = findTestCalls(source, input.testTitle);
254
+ const call = matches[0];
255
+ if (call === undefined) {
256
+ return {
257
+ status: 'not-found',
258
+ file: input.file,
259
+ before: sourceText,
260
+ after: null,
261
+ line: null,
262
+ message: `No test titled "${input.testTitle}" in ${input.file}.`,
263
+ };
264
+ }
265
+ const line = source.getLineAndColumnAtPos(call.getStart()).line;
266
+ const options = call.getArguments()[1];
267
+ if (options === undefined || !Node.isObjectLiteralExpression(options)) {
268
+ return {
269
+ status: 'not-found',
270
+ file: input.file,
271
+ before: sourceText,
272
+ after: null,
273
+ line,
274
+ message: `"${input.testTitle}" has no tags to remove.`,
275
+ };
276
+ }
277
+ const tagProperty = options.getProperty('tag');
278
+ if (tagProperty === undefined || !Node.isPropertyAssignment(tagProperty)) {
279
+ return {
280
+ status: 'not-found',
281
+ file: input.file,
282
+ before: sourceText,
283
+ after: null,
284
+ line,
285
+ message: `"${input.testTitle}" has no tags to remove.`,
286
+ };
287
+ }
288
+ const initializer = tagProperty.getInitializer();
289
+ if (initializer === undefined || !Node.isArrayLiteralExpression(initializer)) {
290
+ return {
291
+ status: 'not-found',
292
+ file: input.file,
293
+ before: sourceText,
294
+ after: null,
295
+ line,
296
+ message: `"${input.testTitle}" does not carry ${tag}.`,
297
+ };
298
+ }
299
+ const index = initializer
300
+ .getElements()
301
+ .findIndex(e => e.getText().replace(/['"`]/g, '') === tag);
302
+ if (index === -1) {
303
+ return {
304
+ status: 'not-found',
305
+ file: input.file,
306
+ before: sourceText,
307
+ after: null,
308
+ line,
309
+ message: `"${input.testTitle}" does not carry ${tag}.`,
310
+ };
311
+ }
312
+ initializer.removeElement(index);
313
+ return {
314
+ status: 'applied',
315
+ file: input.file,
316
+ before: sourceText,
317
+ after: source.getFullText(),
318
+ line,
319
+ message: `Removed ${tag} from "${input.testTitle}".`,
320
+ };
321
+ }
322
+ //# sourceMappingURL=codemod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codemod.js","sourceRoot":"","sources":["../src/codemod.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Deterministic feature extraction.
3
+ *
4
+ * Every value here is measured from recorded attempts. Classification consumes
5
+ * these features; a model is invited only to write prose *after* the class has
6
+ * already been decided. That ordering is what makes a flake verdict
7
+ * reproducible and arguable rather than an opinion.
8
+ */
9
+ import { type HistoricalAttempt } from '@aplaytest/core';
10
+ export interface FlakeFeatures {
11
+ /** Share of this test's failures landing in its single worst project. */
12
+ readonly projectConcentration: number;
13
+ /**
14
+ * Point-biserial correlation between worker count and failing. −1..1.
15
+ *
16
+ * Reported for context, but NOT used as the load test: with a rare outcome
17
+ * its ceiling collapses. A test that never fails at one worker and fails a
18
+ * third of the time at eight — an unmistakable load dependency — scores only
19
+ * ~0.41, because a 20% base rate caps how high r can reach. Use
20
+ * `workerLoadDelta` to decide; use this to describe.
21
+ */
22
+ readonly workerCorrelation: number;
23
+ /**
24
+ * failureRate(above median workers) − failureRate(at or below).
25
+ *
26
+ * Directly measures "does load make this fail?", is bounded 0..1, and is
27
+ * interpretable without knowing what point-biserial means.
28
+ */
29
+ readonly workerLoadDelta: number;
30
+ /** False when every attempt ran at the same worker count — nothing to compare. */
31
+ readonly workerCountVaried: boolean;
32
+ /** max P(fail | X co-scheduled) / P(fail). >1 means co-scheduling hurts. */
33
+ readonly coScheduleLift: number;
34
+ readonly coScheduleSuspects: readonly string[];
35
+ /** Failing duration ÷ median passing duration. High implies waiting, then giving up. */
36
+ readonly durationRatio: number;
37
+ readonly failureKinds: Readonly<Record<string, number>>;
38
+ /** Failures begin sharply at one commit and persist — a regression, not flake. */
39
+ readonly commitBoundary: boolean;
40
+ /** Distinct commits observed; below 2 the boundary test cannot fire. */
41
+ readonly commitsObserved: number;
42
+ /** Failed then passed inside a single run at one commit. The strongest signal. */
43
+ readonly retryFlips: number;
44
+ readonly totalFailures: number;
45
+ readonly totalAttempts: number;
46
+ }
47
+ /** Pearson correlation; with a 0/1 second variable this is point-biserial. */
48
+ export declare function correlation(xs: readonly number[], ys: readonly number[]): number;
49
+ /**
50
+ * @param scoped attempts for the (test, project) under analysis
51
+ * @param allProjects attempts for the same test across every project, used
52
+ * only for the concentration signal
53
+ */
54
+ export declare function extractFeatures(scoped: readonly HistoricalAttempt[], allProjects?: readonly HistoricalAttempt[]): FlakeFeatures;
55
+ //# sourceMappingURL=features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAA+B,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,wFAAwF;IACxF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AA0BD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsBhF;AAkHD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,WAAW,GAAE,SAAS,iBAAiB,EAAW,GACjD,aAAa,CAiDf"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Deterministic feature extraction.
3
+ *
4
+ * Every value here is measured from recorded attempts. Classification consumes
5
+ * these features; a model is invited only to write prose *after* the class has
6
+ * already been decided. That ordering is what makes a flake verdict
7
+ * reproducible and arguable rather than an opinion.
8
+ */
9
+ import { isFailure } from '@aplaytest/core';
10
+ const EMPTY_FEATURES = {
11
+ projectConcentration: 0,
12
+ workerCorrelation: 0,
13
+ workerLoadDelta: 0,
14
+ workerCountVaried: false,
15
+ coScheduleLift: 1,
16
+ coScheduleSuspects: [],
17
+ durationRatio: 1,
18
+ failureKinds: {},
19
+ commitBoundary: false,
20
+ commitsObserved: 0,
21
+ retryFlips: 0,
22
+ totalFailures: 0,
23
+ totalAttempts: 0,
24
+ };
25
+ function median(values) {
26
+ if (values.length === 0)
27
+ return 0;
28
+ const sorted = [...values].sort((a, b) => a - b);
29
+ const mid = Math.floor(sorted.length / 2);
30
+ if (sorted.length % 2 === 1)
31
+ return sorted[mid] ?? 0;
32
+ return ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2;
33
+ }
34
+ /** Pearson correlation; with a 0/1 second variable this is point-biserial. */
35
+ export function correlation(xs, ys) {
36
+ const n = Math.min(xs.length, ys.length);
37
+ if (n < 2)
38
+ return 0;
39
+ const meanX = xs.reduce((a, b) => a + b, 0) / n;
40
+ const meanY = ys.reduce((a, b) => a + b, 0) / n;
41
+ let covariance = 0;
42
+ let varianceX = 0;
43
+ let varianceY = 0;
44
+ for (let i = 0; i < n; i++) {
45
+ const dx = (xs[i] ?? 0) - meanX;
46
+ const dy = (ys[i] ?? 0) - meanY;
47
+ covariance += dx * dy;
48
+ varianceX += dx * dx;
49
+ varianceY += dy * dy;
50
+ }
51
+ // Zero variance means the variable never moved — no relationship is
52
+ // observable, which is honestly reported as 0 rather than NaN.
53
+ if (varianceX === 0 || varianceY === 0)
54
+ return 0;
55
+ return covariance / Math.sqrt(varianceX * varianceY);
56
+ }
57
+ /**
58
+ * Did failures start at a commit and stay?
59
+ *
60
+ * This is the guard that stops a genuine regression being filed as flake. It
61
+ * requires more than one commit in the window: with a single commit there is
62
+ * no boundary to detect, and treating that as a regression would silence every
63
+ * flake on a quiet branch.
64
+ */
65
+ function detectCommitBoundary(ordered) {
66
+ const withCommit = ordered.filter(a => a.commit !== null && a.commit !== '');
67
+ const commits = new Set(withCommit.map(a => a.commit));
68
+ if (withCommit.length < 4 || commits.size < 2)
69
+ return false;
70
+ const firstFailureIndex = withCommit.findIndex(a => isFailure(a.outcome));
71
+ if (firstFailureIndex <= 0)
72
+ return false;
73
+ const before = withCommit.slice(0, firstFailureIndex);
74
+ const after = withCommit.slice(firstFailureIndex);
75
+ if (before.length === 0 || after.length < 2)
76
+ return false;
77
+ const beforeFailureRate = before.filter(a => isFailure(a.outcome)).length / before.length;
78
+ const afterFailureRate = after.filter(a => isFailure(a.outcome)).length / after.length;
79
+ return beforeFailureRate === 0 && afterFailureRate >= 0.7;
80
+ }
81
+ /** Failed then passed within the same run — non-determinism at one commit. */
82
+ function countRetryFlips(attempts) {
83
+ const byRun = new Map();
84
+ for (const attempt of attempts) {
85
+ const existing = byRun.get(attempt.runId) ?? [];
86
+ existing.push(attempt);
87
+ byRun.set(attempt.runId, existing);
88
+ }
89
+ let flips = 0;
90
+ for (const group of byRun.values()) {
91
+ if (group.length < 2)
92
+ continue;
93
+ const sorted = [...group].sort((a, b) => a.retry - b.retry);
94
+ const failedEarly = sorted.slice(0, -1).some(a => isFailure(a.outcome));
95
+ const passedLast = sorted[sorted.length - 1]?.outcome === 'passed';
96
+ if (failedEarly && passedLast)
97
+ flips += 1;
98
+ }
99
+ return flips;
100
+ }
101
+ /**
102
+ * Compare the failure rate at the LOWEST observed worker count against the
103
+ * HIGHEST.
104
+ *
105
+ * Deliberately not a median split. Worker counts are heavily skewed in
106
+ * practice — a suite that mostly runs at full parallelism and occasionally in
107
+ * isolation puts the median at the top of the range, leaving the "high" group
108
+ * empty and the signal silently zero. Comparing the extremes answers the
109
+ * actual question ("does more parallelism make this fail?") and cannot be
110
+ * defeated by the shape of the distribution.
111
+ */
112
+ function workerLoadSignal(attempts) {
113
+ const distinct = [...new Set(attempts.map(a => a.workers))].sort((a, b) => a - b);
114
+ if (distinct.length < 2)
115
+ return { delta: 0, varied: false };
116
+ const lowest = distinct[0];
117
+ const highest = distinct[distinct.length - 1];
118
+ if (lowest === undefined || highest === undefined)
119
+ return { delta: 0, varied: false };
120
+ const low = attempts.filter(a => a.workers === lowest);
121
+ const high = attempts.filter(a => a.workers === highest);
122
+ if (low.length === 0 || high.length === 0)
123
+ return { delta: 0, varied: false };
124
+ const rate = (group) => group.filter(a => isFailure(a.outcome)).length / group.length;
125
+ return { delta: rate(high) - rate(low), varied: true };
126
+ }
127
+ function coScheduleSignal(attempts) {
128
+ const total = attempts.length;
129
+ const failures = attempts.filter(a => isFailure(a.outcome)).length;
130
+ if (total === 0 || failures === 0)
131
+ return { lift: 1, suspects: [] };
132
+ const baseRate = failures / total;
133
+ const withPeer = new Map();
134
+ for (const attempt of attempts) {
135
+ for (const peer of attempt.coScheduled) {
136
+ const stat = withPeer.get(peer) ?? { runs: 0, failures: 0 };
137
+ stat.runs += 1;
138
+ if (isFailure(attempt.outcome))
139
+ stat.failures += 1;
140
+ withPeer.set(peer, stat);
141
+ }
142
+ }
143
+ let bestLift = 1;
144
+ const suspects = [];
145
+ for (const [peer, stat] of withPeer) {
146
+ // Require a few co-occurrences before believing a lift; two runs can make
147
+ // anything look causal.
148
+ if (stat.runs < 3)
149
+ continue;
150
+ const lift = stat.failures / stat.runs / baseRate;
151
+ if (lift > 1.5)
152
+ suspects.push(peer);
153
+ if (lift > bestLift)
154
+ bestLift = lift;
155
+ }
156
+ return { lift: bestLift, suspects: suspects.slice(0, 5) };
157
+ }
158
+ /**
159
+ * @param scoped attempts for the (test, project) under analysis
160
+ * @param allProjects attempts for the same test across every project, used
161
+ * only for the concentration signal
162
+ */
163
+ export function extractFeatures(scoped, allProjects = scoped) {
164
+ if (scoped.length === 0)
165
+ return EMPTY_FEATURES;
166
+ const ordered = [...scoped].sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
167
+ const failures = ordered.filter(a => isFailure(a.outcome));
168
+ const passes = ordered.filter(a => a.outcome === 'passed');
169
+ const failuresByProject = new Map();
170
+ for (const attempt of allProjects.filter(a => isFailure(a.outcome))) {
171
+ failuresByProject.set(attempt.project, (failuresByProject.get(attempt.project) ?? 0) + 1);
172
+ }
173
+ const totalCrossProjectFailures = [...failuresByProject.values()].reduce((a, b) => a + b, 0);
174
+ const projectConcentration = totalCrossProjectFailures === 0
175
+ ? 0
176
+ : Math.max(...failuresByProject.values()) / totalCrossProjectFailures;
177
+ const failureKinds = {};
178
+ for (const attempt of failures) {
179
+ const kind = attempt.failureKind ?? 'unclassified';
180
+ failureKinds[kind] = (failureKinds[kind] ?? 0) + 1;
181
+ }
182
+ const medianPassDuration = median(passes.map(a => a.durationMs));
183
+ const medianFailDuration = median(failures.map(a => a.durationMs));
184
+ const durationRatio = medianPassDuration > 0 ? medianFailDuration / medianPassDuration : failures.length > 0 ? 2 : 1;
185
+ const { lift, suspects } = coScheduleSignal(ordered);
186
+ const load = workerLoadSignal(ordered);
187
+ return {
188
+ projectConcentration,
189
+ workerCorrelation: correlation(ordered.map(a => a.workers), ordered.map(a => (isFailure(a.outcome) ? 1 : 0))),
190
+ workerLoadDelta: load.delta,
191
+ workerCountVaried: load.varied,
192
+ coScheduleLift: lift,
193
+ coScheduleSuspects: suspects,
194
+ durationRatio,
195
+ failureKinds,
196
+ commitBoundary: detectCommitBoundary(ordered),
197
+ commitsObserved: new Set(ordered.map(a => a.commit).filter(c => c !== null && c !== '')).size,
198
+ retryFlips: countRetryFlips(ordered),
199
+ totalFailures: failures.length,
200
+ totalAttempts: ordered.length,
201
+ };
202
+ }
203
+ //# sourceMappingURL=features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.js","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @aplaytest/flaky — statistical flake detection and root-cause classification.
3
+ *
4
+ * Entirely deterministic. Every number here is measured from recorded
5
+ * attempts; no model is called anywhere in this package. A model may later be
6
+ * asked to phrase a verdict, never to reach one — a flake score that cannot be
7
+ * reproduced is a flake score nobody will act on.
8
+ */
9
+ export { scoreTest, isFlaky, usableAttempts, wilsonLowerBound, DEFAULT_SCORE_CONFIG, } from './score.js';
10
+ export type { FlakeScore, ScoreConfig, ScoreConfidence } from './score.js';
11
+ export { extractFeatures, correlation } from './features.js';
12
+ export type { FlakeFeatures } from './features.js';
13
+ export { classifyFlake, shouldRetry, FLAKE_CLASSES } from './classify.js';
14
+ export type { Classification, FlakeClass, Prescription } from './classify.js';
15
+ export { analyzeTest, analyzeAll, groupByTestAndProject, DEFAULT_ANALYZE_CONFIG } from './analyze.js';
16
+ export type { AnalyzeConfig, FlakyVerdict, FlakyReport } from './analyze.js';
17
+ export { evaluateQuarantinePolicy, effectiveBudget, daysUntilExpiry, expiryFor, renderQuarantineComment, buildQuarantineEntry, DEFAULT_QUARANTINE_POLICY, DEFAULT_QUARANTINE_REASON, UNCLASSIFIED_CAUSE, } from './quarantine.js';
18
+ export type { QuarantineEntry, QuarantinePolicy, PolicyResult, PolicyViolation, BuildQuarantineEntryInput, } from './quarantine.js';
19
+ export { quarantineCodemod, releaseCodemod, QUARANTINE_TAG } from './codemod.js';
20
+ export type { CodemodResult, CodemodStatus, QuarantineCodemodInput } from './codemod.js';
21
+ export { bisect, interpret, DEFAULT_BISECT_OPTIONS } from './bisect.js';
22
+ export type { BisectOptions, BisectResult, BisectProbe, BisectVerdict, BisectDimension } from './bisect.js';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,SAAS,EACT,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE7E,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,SAAS,EACT,uBAAuB,EACvB,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEzF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}