flakewatch-core 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.
Files changed (53) hide show
  1. package/dist/__tests__/config.test.d.ts +2 -0
  2. package/dist/__tests__/config.test.d.ts.map +1 -0
  3. package/dist/__tests__/config.test.js +65 -0
  4. package/dist/__tests__/config.test.js.map +1 -0
  5. package/dist/__tests__/context.test.d.ts +2 -0
  6. package/dist/__tests__/context.test.d.ts.map +1 -0
  7. package/dist/__tests__/context.test.js +76 -0
  8. package/dist/__tests__/context.test.js.map +1 -0
  9. package/dist/__tests__/grouping.test.d.ts +2 -0
  10. package/dist/__tests__/grouping.test.d.ts.map +1 -0
  11. package/dist/__tests__/grouping.test.js +124 -0
  12. package/dist/__tests__/grouping.test.js.map +1 -0
  13. package/dist/__tests__/report.test.d.ts +2 -0
  14. package/dist/__tests__/report.test.d.ts.map +1 -0
  15. package/dist/__tests__/report.test.js +102 -0
  16. package/dist/__tests__/report.test.js.map +1 -0
  17. package/dist/config.d.ts +41 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +91 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/context.d.ts +60 -0
  22. package/dist/context.d.ts.map +1 -0
  23. package/dist/context.js +175 -0
  24. package/dist/context.js.map +1 -0
  25. package/dist/grouping.d.ts +23 -0
  26. package/dist/grouping.d.ts.map +1 -0
  27. package/dist/grouping.js +88 -0
  28. package/dist/grouping.js.map +1 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +8 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/llm.d.ts +12 -0
  34. package/dist/llm.d.ts.map +1 -0
  35. package/dist/llm.js +188 -0
  36. package/dist/llm.js.map +1 -0
  37. package/dist/playwright-json.d.ts +46 -0
  38. package/dist/playwright-json.d.ts.map +1 -0
  39. package/dist/playwright-json.js +46 -0
  40. package/dist/playwright-json.js.map +1 -0
  41. package/dist/report.d.ts +7 -0
  42. package/dist/report.d.ts.map +1 -0
  43. package/dist/report.js +121 -0
  44. package/dist/report.js.map +1 -0
  45. package/dist/triage.d.ts +25 -0
  46. package/dist/triage.d.ts.map +1 -0
  47. package/dist/triage.js +91 -0
  48. package/dist/triage.js.map +1 -0
  49. package/dist/verdicts.d.ts +56 -0
  50. package/dist/verdicts.d.ts.map +1 -0
  51. package/dist/verdicts.js +2 -0
  52. package/dist/verdicts.js.map +1 -0
  53. package/package.json +35 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateConfig } from '../config.js';
3
+ function makeConfig(overrides = {}) {
4
+ return {
5
+ llm: {
6
+ provider: 'anthropic',
7
+ model: 'claude-sonnet-4-20250514',
8
+ apiKey: 'test-key',
9
+ },
10
+ browser: {
11
+ method: 'mcp',
12
+ mcpServerCommand: 'npx @anthropic/chrome-devtools-mcp',
13
+ },
14
+ investigation: {
15
+ mode: 'screenshot',
16
+ maxConcurrent: 3,
17
+ maxTotal: 10,
18
+ massFailureThreshold: 0.5,
19
+ },
20
+ auth: {},
21
+ context: {
22
+ includeTrace: true,
23
+ includeConsole: true,
24
+ includeNetwork: true,
25
+ maxTestSourceTokens: 6000,
26
+ },
27
+ output: {
28
+ format: 'markdown',
29
+ dir: './flakewatch-reports',
30
+ },
31
+ ...overrides,
32
+ };
33
+ }
34
+ describe('validateConfig', () => {
35
+ it('returns no errors for valid config', () => {
36
+ const errors = validateConfig(makeConfig());
37
+ expect(errors).toHaveLength(0);
38
+ });
39
+ it('errors on missing API key', () => {
40
+ const config = makeConfig();
41
+ config.llm.apiKey = undefined;
42
+ const errors = validateConfig(config);
43
+ expect(errors).toHaveLength(1);
44
+ expect(errors[0]).toContain('API key');
45
+ });
46
+ it('errors on invalid maxConcurrent', () => {
47
+ const config = makeConfig();
48
+ config.investigation.maxConcurrent = 0;
49
+ const errors = validateConfig(config);
50
+ expect(errors.some((e) => e.includes('maxConcurrent'))).toBe(true);
51
+ });
52
+ it('errors on invalid massFailureThreshold', () => {
53
+ const config = makeConfig();
54
+ config.investigation.massFailureThreshold = 1.5;
55
+ const errors = validateConfig(config);
56
+ expect(errors.some((e) => e.includes('massFailureThreshold'))).toBe(true);
57
+ });
58
+ it('errors on zero massFailureThreshold', () => {
59
+ const config = makeConfig();
60
+ config.investigation.massFailureThreshold = 0;
61
+ const errors = validateConfig(config);
62
+ expect(errors.some((e) => e.includes('massFailureThreshold'))).toBe(true);
63
+ });
64
+ });
65
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/context.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { formatContextForLLM } from '../context.js';
3
+ function makeContext(overrides = {}) {
4
+ return {
5
+ testId: 'test-1',
6
+ testTitle: 'should display user profile',
7
+ testFile: 'tests/profile.spec.ts',
8
+ testSource: `test('should display user profile', async ({ page }) => {
9
+ await page.goto('/profile');
10
+ await expect(page.locator('.user-name')).toBeVisible();
11
+ });`,
12
+ pageObjectSources: new Map(),
13
+ errorMessage: "locator.click: Error: Element '.user-name' not found",
14
+ errorStack: 'at tests/profile.spec.ts:3:15',
15
+ ...overrides,
16
+ };
17
+ }
18
+ describe('formatContextForLLM', () => {
19
+ it('includes test title and file', () => {
20
+ const output = formatContextForLLM(makeContext());
21
+ expect(output).toContain('should display user profile');
22
+ expect(output).toContain('tests/profile.spec.ts');
23
+ });
24
+ it('includes error message and stack', () => {
25
+ const output = formatContextForLLM(makeContext());
26
+ expect(output).toContain("Element '.user-name' not found");
27
+ expect(output).toContain('tests/profile.spec.ts:3:15');
28
+ });
29
+ it('includes test source code', () => {
30
+ const output = formatContextForLLM(makeContext());
31
+ expect(output).toContain("page.goto('/profile')");
32
+ expect(output).toContain('.user-name');
33
+ });
34
+ it('includes page object sources', () => {
35
+ const context = makeContext({
36
+ pageObjectSources: new Map([
37
+ ['pages/profile.ts', 'export class ProfilePage { nameSelector = ".user-name"; }'],
38
+ ]),
39
+ });
40
+ const output = formatContextForLLM(context);
41
+ expect(output).toContain('pages/profile.ts');
42
+ expect(output).toContain('ProfilePage');
43
+ });
44
+ it('includes intent annotation when present', () => {
45
+ const context = makeContext({
46
+ intentAnnotation: 'Verify the user name is visible on the profile page',
47
+ });
48
+ const output = formatContextForLLM(context);
49
+ expect(output).toContain('Verify the user name is visible');
50
+ });
51
+ it('includes screenshot path', () => {
52
+ const context = makeContext({
53
+ screenshotPath: './test-results/profile-chromium/screenshot.png',
54
+ });
55
+ const output = formatContextForLLM(context);
56
+ expect(output).toContain('screenshot.png');
57
+ });
58
+ it('includes network requests when present', () => {
59
+ const context = makeContext({
60
+ networkRequests: [
61
+ { url: '/api/user', method: 'GET', status: 500, duration: 120 },
62
+ ],
63
+ });
64
+ const output = formatContextForLLM(context);
65
+ expect(output).toContain('GET /api/user');
66
+ expect(output).toContain('500');
67
+ });
68
+ it('includes console messages when present', () => {
69
+ const context = makeContext({
70
+ consoleMessages: ['Error: Failed to fetch user data'],
71
+ });
72
+ const output = formatContextForLLM(context);
73
+ expect(output).toContain('Failed to fetch user data');
74
+ });
75
+ });
76
+ //# sourceMappingURL=context.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.test.js","sourceRoot":"","sources":["../../src/__tests__/context.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=grouping.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grouping.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/grouping.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { groupFailures, selectGroupsForInvestigation } from '../grouping.js';
3
+ function makeFailure(overrides = {}) {
4
+ return {
5
+ testId: 'test-1',
6
+ testTitle: 'test one',
7
+ testFile: 'tests/example.spec.ts',
8
+ error: {
9
+ message: 'locator.click: Error: Element not found',
10
+ stack: 'at tests/example.spec.ts:10:5',
11
+ },
12
+ ...overrides,
13
+ };
14
+ }
15
+ describe('groupFailures', () => {
16
+ it('groups failures with identical error messages', () => {
17
+ const failures = [
18
+ makeFailure({ testId: '1', testFile: 'tests/a.spec.ts' }),
19
+ makeFailure({ testId: '2', testFile: 'tests/b.spec.ts' }),
20
+ makeFailure({
21
+ testId: '3',
22
+ error: { message: 'Different error', stack: 'at tests/c.spec.ts:5:1' },
23
+ }),
24
+ ];
25
+ const groups = groupFailures(failures);
26
+ expect(groups).toHaveLength(2);
27
+ const largeGroup = groups.find((g) => g.failures.length === 2);
28
+ expect(largeGroup).toBeDefined();
29
+ expect(largeGroup.failures.map((f) => f.testId)).toEqual(['1', '2']);
30
+ });
31
+ it('picks the simplest test (shortest path) as sample', () => {
32
+ const failures = [
33
+ makeFailure({ testId: '1', testFile: 'tests/very/deep/nested/test.spec.ts' }),
34
+ makeFailure({ testId: '2', testFile: 'tests/a.spec.ts' }),
35
+ ];
36
+ const groups = groupFailures(failures);
37
+ expect(groups).toHaveLength(1);
38
+ expect(groups[0].sample.testId).toBe('2');
39
+ });
40
+ it('normalizes dynamic values in error messages', () => {
41
+ const failures = [
42
+ makeFailure({
43
+ testId: '1',
44
+ error: {
45
+ message: 'Timeout 30000ms waiting for element #user-123',
46
+ stack: 'at tests/a.spec.ts:10:5',
47
+ },
48
+ }),
49
+ makeFailure({
50
+ testId: '2',
51
+ error: {
52
+ message: 'Timeout 5000ms waiting for element #user-456',
53
+ stack: 'at tests/a.spec.ts:10:5',
54
+ },
55
+ }),
56
+ ];
57
+ const groups = groupFailures(failures);
58
+ expect(groups).toHaveLength(1);
59
+ });
60
+ it('separates errors from different stack locations', () => {
61
+ const failures = [
62
+ makeFailure({
63
+ testId: '1',
64
+ error: {
65
+ message: 'Element not found',
66
+ stack: 'at tests/login.spec.ts:10:5',
67
+ },
68
+ }),
69
+ makeFailure({
70
+ testId: '2',
71
+ error: {
72
+ message: 'Element not found',
73
+ stack: 'at tests/dashboard.spec.ts:25:3',
74
+ },
75
+ }),
76
+ ];
77
+ const groups = groupFailures(failures);
78
+ expect(groups).toHaveLength(2);
79
+ });
80
+ it('handles failures with no stack trace', () => {
81
+ const failures = [
82
+ makeFailure({ testId: '1', error: { message: 'Error' } }),
83
+ makeFailure({ testId: '2', error: { message: 'Error' } }),
84
+ ];
85
+ const groups = groupFailures(failures);
86
+ expect(groups).toHaveLength(1);
87
+ });
88
+ });
89
+ describe('selectGroupsForInvestigation', () => {
90
+ it('returns all groups when under limits', () => {
91
+ const groups = [
92
+ { id: 'g1', signature: 'err1', failures: [makeFailure()], sample: makeFailure() },
93
+ { id: 'g2', signature: 'err2', failures: [makeFailure()], sample: makeFailure() },
94
+ ];
95
+ const { selected, circuitBroken } = selectGroupsForInvestigation(groups, 100, { maxTotal: 10, massFailureThreshold: 0.5 });
96
+ expect(selected).toHaveLength(2);
97
+ expect(circuitBroken).toBe(false);
98
+ });
99
+ it('triggers circuit breaker when failure rate exceeds threshold', () => {
100
+ const bigGroup = {
101
+ id: 'g1',
102
+ signature: 'err1',
103
+ failures: Array.from({ length: 60 }, (_, i) => makeFailure({ testId: `test-${i}` })),
104
+ sample: makeFailure(),
105
+ };
106
+ const { selected, circuitBroken } = selectGroupsForInvestigation([bigGroup], 100, { maxTotal: 10, massFailureThreshold: 0.5 });
107
+ expect(circuitBroken).toBe(true);
108
+ expect(selected).toHaveLength(1);
109
+ });
110
+ it('respects maxTotal limit', () => {
111
+ const groups = Array.from({ length: 20 }, (_, i) => ({
112
+ id: `g${i}`,
113
+ signature: `err${i}`,
114
+ failures: [makeFailure({ testId: `test-${i}` })],
115
+ sample: makeFailure({ testId: `test-${i}` }),
116
+ }));
117
+ const { selected } = selectGroupsForInvestigation(groups, 1000, {
118
+ maxTotal: 5,
119
+ massFailureThreshold: 0.5,
120
+ });
121
+ expect(selected).toHaveLength(5);
122
+ });
123
+ });
124
+ //# sourceMappingURL=grouping.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grouping.test.js","sourceRoot":"","sources":["../../src/__tests__/grouping.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=report.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/report.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { formatMarkdownReport, formatGitHubComment } from '../report.js';
3
+ function makeReport(overrides = {}) {
4
+ return {
5
+ timestamp: '2026-03-13T12:00:00Z',
6
+ totalFailures: 3,
7
+ investigated: 2,
8
+ skippedByCircuitBreaker: false,
9
+ groups: [
10
+ {
11
+ groupId: 'group-0',
12
+ errorSignature: 'Element not found',
13
+ count: 2,
14
+ sampleTestId: 'test-1',
15
+ verdict: 'stale_test',
16
+ },
17
+ {
18
+ groupId: 'group-1',
19
+ errorSignature: 'API returned 500',
20
+ count: 1,
21
+ sampleTestId: 'test-3',
22
+ verdict: 'likely_bug',
23
+ },
24
+ ],
25
+ results: [
26
+ {
27
+ testId: 'test-1',
28
+ testTitle: 'should show user name',
29
+ testFile: 'tests/profile.spec.ts',
30
+ groupId: 'group-0',
31
+ verdict: {
32
+ type: 'stale_test',
33
+ confidence: 0.9,
34
+ summary: 'The selector .user-name was renamed to .profile-name',
35
+ evidence: {
36
+ reasoning: 'The page loads correctly but uses .profile-name instead of .user-name',
37
+ },
38
+ proposedFix: {
39
+ filePath: 'tests/profile.spec.ts',
40
+ originalCode: "page.locator('.user-name')",
41
+ fixedCode: "page.locator('.profile-name')",
42
+ explanation: 'Update selector to match current DOM',
43
+ },
44
+ },
45
+ investigationDurationMs: 2500,
46
+ tokensUsed: 1200,
47
+ },
48
+ {
49
+ testId: 'test-3',
50
+ testTitle: 'should load dashboard data',
51
+ testFile: 'tests/dashboard.spec.ts',
52
+ groupId: 'group-1',
53
+ verdict: {
54
+ type: 'likely_bug',
55
+ confidence: 0.85,
56
+ summary: 'The /api/dashboard endpoint returns 500',
57
+ evidence: {
58
+ reasoning: 'Network request to /api/dashboard fails with Internal Server Error',
59
+ networkErrors: [{ url: '/api/dashboard', status: 500, method: 'GET' }],
60
+ },
61
+ },
62
+ investigationDurationMs: 3200,
63
+ tokensUsed: 1500,
64
+ },
65
+ ],
66
+ ...overrides,
67
+ };
68
+ }
69
+ describe('formatMarkdownReport', () => {
70
+ it('includes the header with counts', () => {
71
+ const md = formatMarkdownReport(makeReport());
72
+ expect(md).toContain('Flakewatch Analysis');
73
+ expect(md).toContain('3');
74
+ expect(md).toContain('2');
75
+ });
76
+ it('groups results by verdict type', () => {
77
+ const md = formatMarkdownReport(makeReport());
78
+ expect(md).toContain('Likely Bugs (1)');
79
+ expect(md).toContain('Stale Tests (fix proposed) (1)');
80
+ });
81
+ it('includes proposed fix as diff', () => {
82
+ const md = formatMarkdownReport(makeReport());
83
+ expect(md).toContain('```diff');
84
+ expect(md).toContain("- page.locator('.user-name')");
85
+ expect(md).toContain("+ page.locator('.profile-name')");
86
+ });
87
+ it('shows circuit breaker warning when triggered', () => {
88
+ const md = formatMarkdownReport(makeReport({ skippedByCircuitBreaker: true }));
89
+ expect(md).toContain('Circuit breaker');
90
+ });
91
+ it('includes token and duration stats', () => {
92
+ const md = formatMarkdownReport(makeReport());
93
+ expect(md).toContain('tokens used');
94
+ });
95
+ });
96
+ describe('formatGitHubComment', () => {
97
+ it('includes the flakewatch footer', () => {
98
+ const comment = formatGitHubComment(makeReport());
99
+ expect(comment).toContain('flakewatch');
100
+ });
101
+ });
102
+ //# sourceMappingURL=report.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.test.js","sourceRoot":"","sources":["../../src/__tests__/report.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export interface LLMConfig {
2
+ provider: 'anthropic';
3
+ model: string;
4
+ apiKey?: string;
5
+ }
6
+ export interface BrowserConfig {
7
+ method: 'mcp';
8
+ mcpServerCommand: string;
9
+ }
10
+ export type InvestigationMode = 'screenshot' | 'full';
11
+ export interface InvestigationConfig {
12
+ mode: InvestigationMode;
13
+ maxConcurrent: number;
14
+ maxTotal: number;
15
+ massFailureThreshold: number;
16
+ baseURL?: string;
17
+ }
18
+ export interface AuthConfig {
19
+ storageStatePath?: string;
20
+ }
21
+ export interface ContextConfig {
22
+ includeTrace: boolean;
23
+ includeConsole: boolean;
24
+ includeNetwork: boolean;
25
+ maxTestSourceTokens: number;
26
+ }
27
+ export interface OutputConfig {
28
+ format: 'markdown' | 'json' | 'github-comment';
29
+ dir: string;
30
+ }
31
+ export interface SmartRetryConfig {
32
+ llm: LLMConfig;
33
+ browser: BrowserConfig;
34
+ investigation: InvestigationConfig;
35
+ auth: AuthConfig;
36
+ context: ContextConfig;
37
+ output: OutputConfig;
38
+ }
39
+ export declare function loadConfig(configPath?: string, overrides?: Partial<SmartRetryConfig>): Promise<SmartRetryConfig>;
40
+ export declare function validateConfig(config: SmartRetryConfig): string[];
41
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,gBAAgB,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,aAAa,CAAC;IACvB,aAAa,EAAE,mBAAmB,CAAC;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AA+BD,wBAAsB,UAAU,CAC9B,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AA0BD,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAyBjE"}
package/dist/config.js ADDED
@@ -0,0 +1,91 @@
1
+ import { resolve } from 'node:path';
2
+ const DEFAULT_CONFIG = {
3
+ llm: {
4
+ provider: 'anthropic',
5
+ model: 'claude-sonnet-4-20250514',
6
+ apiKey: process.env['ANTHROPIC_API_KEY'],
7
+ },
8
+ browser: {
9
+ method: 'mcp',
10
+ mcpServerCommand: 'npx chrome-devtools-mcp --headless --isolated',
11
+ },
12
+ investigation: {
13
+ mode: 'screenshot',
14
+ maxConcurrent: 3,
15
+ maxTotal: 10,
16
+ massFailureThreshold: 0.5,
17
+ },
18
+ auth: {},
19
+ context: {
20
+ includeTrace: true,
21
+ includeConsole: true,
22
+ includeNetwork: true,
23
+ maxTestSourceTokens: 6000,
24
+ },
25
+ output: {
26
+ format: 'markdown',
27
+ dir: './flakewatch-reports',
28
+ },
29
+ };
30
+ export async function loadConfig(configPath, overrides) {
31
+ let fileConfig = {};
32
+ if (configPath) {
33
+ const absolutePath = resolve(configPath);
34
+ try {
35
+ const module = await import(absolutePath);
36
+ fileConfig = module.default ?? module;
37
+ }
38
+ catch {
39
+ throw new Error(`Failed to load config from ${absolutePath}`);
40
+ }
41
+ }
42
+ else {
43
+ // Try default config locations
44
+ for (const name of ['flakewatch.config.ts', 'flakewatch.config.js']) {
45
+ try {
46
+ const module = await import(resolve(name));
47
+ fileConfig = module.default ?? module;
48
+ break;
49
+ }
50
+ catch {
51
+ // Try next
52
+ }
53
+ }
54
+ }
55
+ return deepMerge(DEFAULT_CONFIG, fileConfig, (overrides ?? {}));
56
+ }
57
+ function deepMerge(...sources) {
58
+ const result = {};
59
+ for (const source of sources) {
60
+ for (const [key, value] of Object.entries(source)) {
61
+ if (value !== undefined &&
62
+ typeof value === 'object' &&
63
+ value !== null &&
64
+ !Array.isArray(value)) {
65
+ result[key] = deepMerge(result[key] ?? {}, value);
66
+ }
67
+ else if (value !== undefined) {
68
+ result[key] = value;
69
+ }
70
+ }
71
+ }
72
+ return result;
73
+ }
74
+ export function validateConfig(config) {
75
+ const errors = [];
76
+ if (!config.llm.apiKey) {
77
+ errors.push('Missing LLM API key. Set ANTHROPIC_API_KEY env var or configure llm.apiKey.');
78
+ }
79
+ if (config.investigation.maxConcurrent < 1) {
80
+ errors.push('investigation.maxConcurrent must be >= 1');
81
+ }
82
+ if (config.investigation.maxTotal < 1) {
83
+ errors.push('investigation.maxTotal must be >= 1');
84
+ }
85
+ if (config.investigation.massFailureThreshold <= 0 ||
86
+ config.investigation.massFailureThreshold > 1) {
87
+ errors.push('investigation.massFailureThreshold must be between 0 and 1');
88
+ }
89
+ return errors;
90
+ }
91
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { ContextConfig } from './config.js';
2
+ export interface TestFailure {
3
+ testId: string;
4
+ testTitle: string;
5
+ testFile: string;
6
+ line?: number;
7
+ error: {
8
+ message: string;
9
+ stack?: string;
10
+ };
11
+ duration?: number;
12
+ retry?: number;
13
+ screenshotPath?: string;
14
+ tracePath?: string;
15
+ annotations?: string[];
16
+ }
17
+ export interface FailureContext {
18
+ testId: string;
19
+ testTitle: string;
20
+ testFile: string;
21
+ testSource: string;
22
+ pageObjectSources: Map<string, string>;
23
+ intentAnnotation?: string;
24
+ errorMessage: string;
25
+ errorStack?: string;
26
+ failingLine?: FailingLine;
27
+ screenshotPath?: string;
28
+ traceActions?: TraceAction[];
29
+ consoleMessages?: string[];
30
+ networkRequests?: NetworkRequest[];
31
+ }
32
+ export interface FailingLine {
33
+ file: string;
34
+ line: number;
35
+ column?: number;
36
+ code: string;
37
+ context: string;
38
+ }
39
+ export interface TraceAction {
40
+ action: string;
41
+ selector?: string;
42
+ timestamp: number;
43
+ duration: number;
44
+ error?: string;
45
+ }
46
+ export interface NetworkRequest {
47
+ url: string;
48
+ method: string;
49
+ status?: number;
50
+ duration?: number;
51
+ }
52
+ /**
53
+ * Package a test failure with all relevant context for LLM analysis.
54
+ */
55
+ export declare function packageFailureContext(failure: TestFailure, config: ContextConfig): Promise<FailureContext>;
56
+ /**
57
+ * Format failure context as a prompt for LLM analysis.
58
+ */
59
+ export declare function formatContextForLLM(context: FailureContext): string;
60
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IAGjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;IAG1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAwFD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CA+EnE"}