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.
Files changed (209) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/ai.d.ts +41 -0
  3. package/dist/commands/ai.js +499 -0
  4. package/dist/commands/ask.js +12 -12
  5. package/dist/commands/coverage.d.ts +8 -0
  6. package/dist/commands/coverage.js +252 -0
  7. package/dist/commands/explain.d.ts +27 -0
  8. package/dist/commands/explain.js +630 -0
  9. package/dist/commands/flakiness.d.ts +73 -0
  10. package/dist/commands/flakiness.js +435 -0
  11. package/dist/commands/generate.d.ts +66 -0
  12. package/dist/commands/generate.js +438 -0
  13. package/dist/commands/init.d.ts +56 -9
  14. package/dist/commands/init.js +217 -10
  15. package/dist/commands/monitor.d.ts +27 -0
  16. package/dist/commands/monitor.js +225 -0
  17. package/dist/commands/ollama.d.ts +40 -0
  18. package/dist/commands/ollama.js +301 -0
  19. package/dist/commands/pack.d.ts +37 -9
  20. package/dist/commands/pack.js +240 -141
  21. package/dist/commands/regression.d.ts +8 -0
  22. package/dist/commands/regression.js +340 -0
  23. package/dist/commands/repair.d.ts +26 -0
  24. package/dist/commands/repair.js +307 -0
  25. package/dist/commands/retry.d.ts +43 -0
  26. package/dist/commands/retry.js +275 -0
  27. package/dist/commands/run.d.ts +8 -3
  28. package/dist/commands/run.js +45 -31
  29. package/dist/commands/slo.d.ts +8 -0
  30. package/dist/commands/slo.js +327 -0
  31. package/dist/core/adapters/playwright-native-api.d.ts +183 -0
  32. package/dist/core/adapters/playwright-native-api.js +461 -0
  33. package/dist/core/adapters/playwright-ui.d.ts +7 -0
  34. package/dist/core/adapters/playwright-ui.js +29 -1
  35. package/dist/core/ai/anthropic-provider.d.ts +50 -0
  36. package/dist/core/ai/anthropic-provider.js +211 -0
  37. package/dist/core/ai/deepseek-provider.d.ts +81 -0
  38. package/dist/core/ai/deepseek-provider.js +254 -0
  39. package/dist/core/ai/index.d.ts +60 -0
  40. package/dist/core/ai/index.js +18 -0
  41. package/dist/core/ai/llm-client.d.ts +45 -0
  42. package/dist/core/ai/llm-client.js +7 -0
  43. package/dist/core/ai/mock-provider.d.ts +49 -0
  44. package/dist/core/ai/mock-provider.js +121 -0
  45. package/dist/core/ai/ollama-provider.d.ts +78 -0
  46. package/dist/core/ai/ollama-provider.js +192 -0
  47. package/dist/core/ai/openai-provider.d.ts +48 -0
  48. package/dist/core/ai/openai-provider.js +188 -0
  49. package/dist/core/ai/provider-factory.d.ts +160 -0
  50. package/dist/core/ai/provider-factory.js +269 -0
  51. package/dist/core/auth/api-key-provider.d.ts +16 -0
  52. package/dist/core/auth/api-key-provider.js +63 -0
  53. package/dist/core/auth/aws-iam-provider.d.ts +35 -0
  54. package/dist/core/auth/aws-iam-provider.js +177 -0
  55. package/dist/core/auth/azure-ad-provider.d.ts +15 -0
  56. package/dist/core/auth/azure-ad-provider.js +99 -0
  57. package/dist/core/auth/basic-auth-provider.d.ts +26 -0
  58. package/dist/core/auth/basic-auth-provider.js +111 -0
  59. package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
  60. package/dist/core/auth/gcp-adc-provider.js +126 -0
  61. package/dist/core/auth/index.d.ts +238 -0
  62. package/dist/core/auth/index.js +82 -0
  63. package/dist/core/auth/jwt-provider.d.ts +19 -0
  64. package/dist/core/auth/jwt-provider.js +160 -0
  65. package/dist/core/auth/manager.d.ts +84 -0
  66. package/dist/core/auth/manager.js +230 -0
  67. package/dist/core/auth/oauth2-provider.d.ts +17 -0
  68. package/dist/core/auth/oauth2-provider.js +114 -0
  69. package/dist/core/auth/totp-provider.d.ts +31 -0
  70. package/dist/core/auth/totp-provider.js +134 -0
  71. package/dist/core/auth/ui-login-provider.d.ts +26 -0
  72. package/dist/core/auth/ui-login-provider.js +198 -0
  73. package/dist/core/cache/index.d.ts +7 -0
  74. package/dist/core/cache/index.js +6 -0
  75. package/dist/core/cache/lru-cache.d.ts +203 -0
  76. package/dist/core/cache/lru-cache.js +397 -0
  77. package/dist/core/coverage/analyzer.d.ts +101 -0
  78. package/dist/core/coverage/analyzer.js +415 -0
  79. package/dist/core/coverage/collector.d.ts +74 -0
  80. package/dist/core/coverage/collector.js +459 -0
  81. package/dist/core/coverage/config.d.ts +37 -0
  82. package/dist/core/coverage/config.js +156 -0
  83. package/dist/core/coverage/index.d.ts +11 -0
  84. package/dist/core/coverage/index.js +15 -0
  85. package/dist/core/coverage/types.d.ts +267 -0
  86. package/dist/core/coverage/types.js +6 -0
  87. package/dist/core/coverage/vault.d.ts +95 -0
  88. package/dist/core/coverage/vault.js +405 -0
  89. package/dist/core/dashboard/assets.d.ts +6 -0
  90. package/dist/core/dashboard/assets.js +690 -0
  91. package/dist/core/dashboard/index.d.ts +6 -0
  92. package/dist/core/dashboard/index.js +5 -0
  93. package/dist/core/dashboard/server.d.ts +72 -0
  94. package/dist/core/dashboard/server.js +354 -0
  95. package/dist/core/dashboard/types.d.ts +70 -0
  96. package/dist/core/dashboard/types.js +5 -0
  97. package/dist/core/discoverer/index.d.ts +115 -0
  98. package/dist/core/discoverer/index.js +250 -0
  99. package/dist/core/flakiness/index.d.ts +228 -0
  100. package/dist/core/flakiness/index.js +384 -0
  101. package/dist/core/generation/code-formatter.d.ts +111 -0
  102. package/dist/core/generation/code-formatter.js +307 -0
  103. package/dist/core/generation/code-generator.d.ts +144 -0
  104. package/dist/core/generation/code-generator.js +293 -0
  105. package/dist/core/generation/generator.d.ts +40 -0
  106. package/dist/core/generation/generator.js +76 -0
  107. package/dist/core/generation/index.d.ts +30 -0
  108. package/dist/core/generation/index.js +28 -0
  109. package/dist/core/generation/pack-generator.d.ts +107 -0
  110. package/dist/core/generation/pack-generator.js +416 -0
  111. package/dist/core/generation/prompt-builder.d.ts +132 -0
  112. package/dist/core/generation/prompt-builder.js +672 -0
  113. package/dist/core/generation/source-analyzer.d.ts +213 -0
  114. package/dist/core/generation/source-analyzer.js +657 -0
  115. package/dist/core/generation/test-optimizer.d.ts +117 -0
  116. package/dist/core/generation/test-optimizer.js +328 -0
  117. package/dist/core/generation/types.d.ts +214 -0
  118. package/dist/core/generation/types.js +4 -0
  119. package/dist/core/index.d.ts +23 -1
  120. package/dist/core/index.js +39 -0
  121. package/dist/core/pack/validator.js +31 -1
  122. package/dist/core/pack-v2/index.d.ts +9 -0
  123. package/dist/core/pack-v2/index.js +8 -0
  124. package/dist/core/pack-v2/loader.d.ts +62 -0
  125. package/dist/core/pack-v2/loader.js +231 -0
  126. package/dist/core/pack-v2/migrator.d.ts +56 -0
  127. package/dist/core/pack-v2/migrator.js +455 -0
  128. package/dist/core/pack-v2/validator.d.ts +61 -0
  129. package/dist/core/pack-v2/validator.js +577 -0
  130. package/dist/core/regression/detector.d.ts +107 -0
  131. package/dist/core/regression/detector.js +497 -0
  132. package/dist/core/regression/index.d.ts +9 -0
  133. package/dist/core/regression/index.js +11 -0
  134. package/dist/core/regression/trend-analyzer.d.ts +102 -0
  135. package/dist/core/regression/trend-analyzer.js +345 -0
  136. package/dist/core/regression/types.d.ts +222 -0
  137. package/dist/core/regression/types.js +7 -0
  138. package/dist/core/regression/vault.d.ts +87 -0
  139. package/dist/core/regression/vault.js +289 -0
  140. package/dist/core/repair/engine/fixer.d.ts +24 -0
  141. package/dist/core/repair/engine/fixer.js +226 -0
  142. package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
  143. package/dist/core/repair/engine/suggestion-engine.js +187 -0
  144. package/dist/core/repair/index.d.ts +10 -0
  145. package/dist/core/repair/index.js +13 -0
  146. package/dist/core/repair/repairer.d.ts +90 -0
  147. package/dist/core/repair/repairer.js +284 -0
  148. package/dist/core/repair/types.d.ts +91 -0
  149. package/dist/core/repair/types.js +6 -0
  150. package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
  151. package/dist/core/repair/utils/error-analyzer.js +264 -0
  152. package/dist/core/retry/flakiness-integration.d.ts +60 -0
  153. package/dist/core/retry/flakiness-integration.js +228 -0
  154. package/dist/core/retry/index.d.ts +14 -0
  155. package/dist/core/retry/index.js +16 -0
  156. package/dist/core/retry/retry-engine.d.ts +80 -0
  157. package/dist/core/retry/retry-engine.js +296 -0
  158. package/dist/core/retry/types.d.ts +178 -0
  159. package/dist/core/retry/types.js +52 -0
  160. package/dist/core/retry/vault.d.ts +77 -0
  161. package/dist/core/retry/vault.js +304 -0
  162. package/dist/core/runner/e2e-helpers.d.ts +102 -0
  163. package/dist/core/runner/e2e-helpers.js +153 -0
  164. package/dist/core/runner/phase3-runner.d.ts +101 -2
  165. package/dist/core/runner/phase3-runner.js +559 -24
  166. package/dist/core/self-healing/assertion-healer.d.ts +97 -0
  167. package/dist/core/self-healing/assertion-healer.js +371 -0
  168. package/dist/core/self-healing/engine.d.ts +122 -0
  169. package/dist/core/self-healing/engine.js +538 -0
  170. package/dist/core/self-healing/index.d.ts +10 -0
  171. package/dist/core/self-healing/index.js +11 -0
  172. package/dist/core/self-healing/selector-healer.d.ts +103 -0
  173. package/dist/core/self-healing/selector-healer.js +372 -0
  174. package/dist/core/self-healing/types.d.ts +152 -0
  175. package/dist/core/self-healing/types.js +6 -0
  176. package/dist/core/slo/config.d.ts +107 -0
  177. package/dist/core/slo/config.js +360 -0
  178. package/dist/core/slo/index.d.ts +11 -0
  179. package/dist/core/slo/index.js +15 -0
  180. package/dist/core/slo/sli-calculator.d.ts +92 -0
  181. package/dist/core/slo/sli-calculator.js +364 -0
  182. package/dist/core/slo/slo-tracker.d.ts +148 -0
  183. package/dist/core/slo/slo-tracker.js +379 -0
  184. package/dist/core/slo/types.d.ts +281 -0
  185. package/dist/core/slo/types.js +7 -0
  186. package/dist/core/slo/vault.d.ts +102 -0
  187. package/dist/core/slo/vault.js +427 -0
  188. package/dist/core/tui/index.d.ts +7 -0
  189. package/dist/core/tui/index.js +6 -0
  190. package/dist/core/tui/monitor.d.ts +92 -0
  191. package/dist/core/tui/monitor.js +271 -0
  192. package/dist/core/tui/renderer.d.ts +33 -0
  193. package/dist/core/tui/renderer.js +218 -0
  194. package/dist/core/tui/types.d.ts +63 -0
  195. package/dist/core/tui/types.js +5 -0
  196. package/dist/core/types/pack-v2.d.ts +425 -0
  197. package/dist/core/types/pack-v2.js +8 -0
  198. package/dist/core/vault/index.d.ts +116 -0
  199. package/dist/core/vault/index.js +400 -5
  200. package/dist/core/watch/index.d.ts +7 -0
  201. package/dist/core/watch/index.js +6 -0
  202. package/dist/core/watch/watch-mode.d.ts +213 -0
  203. package/dist/core/watch/watch-mode.js +389 -0
  204. package/dist/index.js +68 -68
  205. package/dist/utils/config.d.ts +5 -0
  206. package/dist/utils/config.js +136 -0
  207. package/package.json +5 -1
  208. package/dist/core/adapters/playwright-api.d.ts +0 -82
  209. package/dist/core/adapters/playwright-api.js +0 -264
@@ -0,0 +1,384 @@
1
+ /**
2
+ * QA360 Flakiness Detection Engine
3
+ *
4
+ * Detects and scores test flakiness using consecutive runs and pattern analysis.
5
+ * This is the core differentiator of QA360 - measuring test reliability.
6
+ *
7
+ * Scoring Categories:
8
+ * - Legendary (100): Never flaky - 100% reliable
9
+ * - Solid (90-99): Rarely flaky
10
+ * - Good (75-89): Occasionally flaky
11
+ * - Shaky (50-74): Often flaky - attention needed
12
+ * - Unstable (0-49): Very flaky - quarantine recommended
13
+ *
14
+ * @module flakiness
15
+ */
16
+ import { createHash } from 'crypto';
17
+ /**
18
+ * Flakiness score category
19
+ */
20
+ export var FlakinessCategory;
21
+ (function (FlakinessCategory) {
22
+ FlakinessCategory["LEGENDARY"] = "legendary";
23
+ FlakinessCategory["SOLID"] = "solid";
24
+ FlakinessCategory["GOOD"] = "good";
25
+ FlakinessCategory["SHAKY"] = "shaky";
26
+ FlakinessCategory["UNSTABLE"] = "unstable"; // 0-49
27
+ })(FlakinessCategory || (FlakinessCategory = {}));
28
+ /**
29
+ * Flakiness category metadata
30
+ */
31
+ export const FLAKINESS_CATEGORIES = {
32
+ [FlakinessCategory.LEGENDARY]: {
33
+ min: 100,
34
+ max: 100,
35
+ label: 'LEGENDARY',
36
+ emoji: '🟢',
37
+ color: '#10B981',
38
+ description: 'Jamais flaky - 100% fiable',
39
+ action: 'None - keep doing what you\'re doing'
40
+ },
41
+ [FlakinessCategory.SOLID]: {
42
+ min: 90,
43
+ max: 99,
44
+ label: 'SOLID',
45
+ emoji: '🟢',
46
+ color: '#34D399',
47
+ description: 'Rarement flaky',
48
+ action: 'Monitor - occasional hiccups'
49
+ },
50
+ [FlakinessCategory.GOOD]: {
51
+ min: 75,
52
+ max: 89,
53
+ label: 'GOOD',
54
+ emoji: '🟡',
55
+ color: '#FBBF24',
56
+ description: 'Occasionnellement flaky',
57
+ action: 'Watch - may need attention'
58
+ },
59
+ [FlakinessCategory.SHAKY]: {
60
+ min: 50,
61
+ max: 74,
62
+ label: 'SHAKY',
63
+ emoji: '🟠',
64
+ color: '#F97316',
65
+ description: 'Souvent flaky - attention requise',
66
+ action: 'Investigate - fix recommended'
67
+ },
68
+ [FlakinessCategory.UNSTABLE]: {
69
+ min: 0,
70
+ max: 49,
71
+ label: 'UNSTABLE',
72
+ emoji: '🔴',
73
+ color: '#EF4444',
74
+ description: 'Très flaky - quarantaine recommandée',
75
+ action: 'Quarantine - unblock the team'
76
+ }
77
+ };
78
+ /**
79
+ * Default flakiness detection options
80
+ */
81
+ export const DEFAULT_FLAKINESS_OPTIONS = {
82
+ minRuns: 3,
83
+ consecutiveRuns: 3,
84
+ timeWindowDays: 30,
85
+ enablePatternDetection: true
86
+ };
87
+ /**
88
+ * Flakiness pattern types
89
+ */
90
+ export var FlakinessPattern;
91
+ (function (FlakinessPattern) {
92
+ FlakinessPattern["TIMING"] = "timing";
93
+ FlakinessPattern["RACE_CONDITION"] = "race_condition";
94
+ FlakinessPattern["EXTERNAL_DEPENDENCY"] = "external_dependency";
95
+ FlakinessPattern["ENVIRONMENT_SPECIFIC"] = "environment_specific";
96
+ FlakinessPattern["SELECTOR_ISSUE"] = "selector_issue";
97
+ FlakinessPattern["UNKNOWN"] = "unknown";
98
+ })(FlakinessPattern || (FlakinessPattern = {}));
99
+ /**
100
+ * Calculate flakiness score from test results
101
+ * @param results Array of test results
102
+ * @returns Flakiness score (0-100)
103
+ */
104
+ export function calculateFlakinessScore(results) {
105
+ if (results.length === 0) {
106
+ return 100; // No failures means 100% by default
107
+ }
108
+ const successCount = results.filter(r => r.success).length;
109
+ const score = Math.round((successCount / results.length) * 100);
110
+ return Math.max(0, Math.min(100, score));
111
+ }
112
+ /**
113
+ * Get flakiness category from score
114
+ * @param score Flakiness score (0-100)
115
+ * @returns Flakiness category
116
+ */
117
+ export function getFlakinessCategory(score) {
118
+ if (score === 100)
119
+ return FlakinessCategory.LEGENDARY;
120
+ if (score >= 90)
121
+ return FlakinessCategory.SOLID;
122
+ if (score >= 75)
123
+ return FlakinessCategory.GOOD;
124
+ if (score >= 50)
125
+ return FlakinessCategory.SHAKY;
126
+ return FlakinessCategory.UNSTABLE;
127
+ }
128
+ /**
129
+ * Generate a unique test ID
130
+ * @param testName Test name
131
+ * @param filePath File path
132
+ * @returns Unique test ID
133
+ */
134
+ export function generateTestId(testName, filePath) {
135
+ const hash = createHash('sha256')
136
+ .update(`${filePath}:${testName}`)
137
+ .digest('hex')
138
+ .substring(0, 16);
139
+ return hash;
140
+ }
141
+ /**
142
+ * Analyze test results for flakiness patterns
143
+ * @param results Array of test results for the same test
144
+ * @returns Pattern detection result or undefined
145
+ */
146
+ export function detectFlakinessPattern(results) {
147
+ if (results.length < 3) {
148
+ return undefined;
149
+ }
150
+ const failures = results.filter(r => !r.success);
151
+ if (failures.length === 0) {
152
+ return undefined; // No failures to analyze
153
+ }
154
+ // Collect error messages as lowercase strings
155
+ const errorMessages = failures.map(f => (f.errorMessage || '').toLowerCase());
156
+ // Helper to check if any error contains a keyword
157
+ const anyErrorContains = (keywords) => {
158
+ return errorMessages.some(err => keywords.some(kw => err.includes(kw)));
159
+ };
160
+ // Check for timing issues
161
+ if (anyErrorContains(['timeout', 'timed out', 'waiting for', 'element not found', 'waiting for selector'])) {
162
+ return {
163
+ patternType: FlakinessPattern.TIMING,
164
+ description: 'Timing issues - test is too fast for the application',
165
+ suggestedFix: 'Increase timeout or add explicit wait (waitForSelector, waitForLoadState)',
166
+ confidence: 0.85,
167
+ autoFixPossible: true
168
+ };
169
+ }
170
+ // Check for selector issues
171
+ if (anyErrorContains(['selector', 'detached', 'stale element', 'element not found'])) {
172
+ return {
173
+ patternType: FlakinessPattern.SELECTOR_ISSUE,
174
+ description: 'Selector issues - element not consistently found',
175
+ suggestedFix: 'Use data-testid selectors instead of CSS selectors for better reliability',
176
+ confidence: 0.80,
177
+ autoFixPossible: true
178
+ };
179
+ }
180
+ // Check for external dependency issues
181
+ if (anyErrorContains(['econnrefused', 'connection refused', 'connection reset', 'network error', 'network unreachable', 'api timeout'])) {
182
+ return {
183
+ patternType: FlakinessPattern.EXTERNAL_DEPENDENCY,
184
+ description: 'External dependency issues - network or service unavailable',
185
+ suggestedFix: 'Add retry logic with exponential backoff or mock the external dependency',
186
+ confidence: 0.90,
187
+ autoFixPossible: true
188
+ };
189
+ }
190
+ // Check for race conditions
191
+ if (anyErrorContains(['race condition', 'state changed', 'unexpected state', 'assertion failed'])) {
192
+ return {
193
+ patternType: FlakinessPattern.RACE_CONDITION,
194
+ description: 'Possible race condition - state changes unexpectedly',
195
+ suggestedFix: 'Add proper synchronization/locks or use explicit waits',
196
+ confidence: 0.70,
197
+ autoFixPossible: false
198
+ };
199
+ }
200
+ // Check for environment-specific issues
201
+ const envs = new Set(results.map(r => r.environment));
202
+ if (envs.size > 1) {
203
+ const failEnvs = new Set(failures.map(r => r.environment));
204
+ const passEnvs = new Set(results.filter(r => r.success).map(r => r.environment));
205
+ // Check if failures are concentrated in one environment
206
+ for (const env of failEnvs) {
207
+ if (!passEnvs.has(env)) {
208
+ return {
209
+ patternType: FlakinessPattern.ENVIRONMENT_SPECIFIC,
210
+ description: `Environment-specific issue - fails on ${env} only`,
211
+ suggestedFix: 'Check environment-specific configuration (OS, timezone, locale)',
212
+ confidence: 0.75,
213
+ autoFixPossible: false
214
+ };
215
+ }
216
+ }
217
+ }
218
+ return {
219
+ patternType: FlakinessPattern.UNKNOWN,
220
+ description: 'Flaky behavior detected but pattern unclear',
221
+ suggestedFix: 'Manual investigation recommended - check logs for specific failure patterns',
222
+ confidence: 0.50,
223
+ autoFixPossible: false
224
+ };
225
+ }
226
+ /**
227
+ * Calculate consecutive streak (passes or failures)
228
+ * @param results Array of test results sorted by timestamp
229
+ * @returns Current streak info
230
+ */
231
+ export function calculateStreak(results) {
232
+ if (results.length === 0) {
233
+ return { currentStreak: 0, streakType: 'pass' };
234
+ }
235
+ // Sort by timestamp (newest first)
236
+ const sorted = [...results].sort((a, b) => b.timestamp - a.timestamp);
237
+ let streak = 0;
238
+ let streakType = sorted[0].success ? 'pass' : 'fail';
239
+ for (const result of sorted) {
240
+ if (result.success === (streakType === 'pass')) {
241
+ streak++;
242
+ }
243
+ else {
244
+ break;
245
+ }
246
+ }
247
+ return { currentStreak: streak, streakType };
248
+ }
249
+ /**
250
+ * Format flakiness score for display
251
+ * @param score Flakiness score (0-100)
252
+ * @returns Formatted string with emoji
253
+ */
254
+ export function formatFlakinessScore(score) {
255
+ const category = getFlakinessCategory(score);
256
+ const meta = FLAKINESS_CATEGORIES[category];
257
+ return `${meta.emoji} ${category.toUpperCase()} (${score}%)`;
258
+ }
259
+ /**
260
+ * Flakiness Detection Engine
261
+ *
262
+ * Main class for detecting and scoring test flakiness
263
+ */
264
+ export class FlakinessDetector {
265
+ options;
266
+ constructor(options = {}) {
267
+ this.options = { ...DEFAULT_FLAKINESS_OPTIONS, ...options };
268
+ }
269
+ /**
270
+ * Analyze a single test's flakiness from its run history
271
+ * @param testId Test identifier
272
+ * @param results Array of test results (must be from the same test)
273
+ * @returns Flakiness result
274
+ */
275
+ analyzeTest(testId, results) {
276
+ // Filter by time window
277
+ const timeWindow = this.options.timeWindowDays * 24 * 60 * 60 * 1000;
278
+ const now = Date.now();
279
+ const cutoff = now - timeWindow;
280
+ const filteredResults = results.filter(r => r.timestamp >= cutoff);
281
+ // Need minimum runs to calculate score
282
+ if (filteredResults.length < this.options.minRuns) {
283
+ return {
284
+ testId,
285
+ testName: results[0]?.testName || 'Unknown',
286
+ filePath: results[0]?.filePath || 'Unknown',
287
+ gate: results[0]?.gate || 'Unknown',
288
+ score: 100, // Default to 100 if not enough data
289
+ category: FlakinessCategory.LEGENDARY,
290
+ totalRuns: filteredResults.length,
291
+ successfulRuns: filteredResults.filter(r => r.success).length,
292
+ avgDurationMs: this.averageDuration(filteredResults)
293
+ };
294
+ }
295
+ // Calculate score
296
+ const score = calculateFlakinessScore(filteredResults);
297
+ const category = getFlakinessCategory(score);
298
+ // Calculate streak
299
+ const { currentStreak, streakType } = calculateStreak(filteredResults);
300
+ // Detect patterns if enabled
301
+ let patternType;
302
+ let suggestedFix;
303
+ let confidence;
304
+ if (this.options.enablePatternDetection) {
305
+ const pattern = detectFlakinessPattern(filteredResults);
306
+ if (pattern) {
307
+ patternType = pattern.patternType;
308
+ suggestedFix = pattern.suggestedFix;
309
+ confidence = pattern.confidence;
310
+ }
311
+ }
312
+ return {
313
+ testId,
314
+ testName: results[0].testName,
315
+ filePath: results[0].filePath,
316
+ gate: results[0].gate,
317
+ score,
318
+ category,
319
+ totalRuns: filteredResults.length,
320
+ successfulRuns: filteredResults.filter(r => r.success).length,
321
+ avgDurationMs: this.averageDuration(filteredResults),
322
+ patternType,
323
+ suggestedFix,
324
+ confidence,
325
+ firstSeen: Math.min(...filteredResults.map(r => r.timestamp)),
326
+ lastSeen: Math.max(...filteredResults.map(r => r.timestamp)),
327
+ currentStreak,
328
+ streakType
329
+ };
330
+ }
331
+ /**
332
+ * Analyze multiple tests and return flakiness results
333
+ * @param testResults Map of testId to array of results
334
+ * @returns Array of flakiness results
335
+ */
336
+ analyzeAll(testResults) {
337
+ const flakinessResults = [];
338
+ for (const [testId, testRuns] of testResults.entries()) {
339
+ if (testRuns.length > 0) {
340
+ flakinessResults.push(this.analyzeTest(testId, testRuns));
341
+ }
342
+ }
343
+ // Sort by score (lowest first) - most flaky first
344
+ flakinessResults.sort((a, b) => a.score - b.score);
345
+ return flakinessResults;
346
+ }
347
+ /**
348
+ * Check if a test should be quarantined based on flakiness
349
+ * @param result Flakiness result
350
+ * @returns Whether to quarantine
351
+ */
352
+ shouldQuarantine(result) {
353
+ // Quarantine if:
354
+ // 1. Score is below 50 (Unstable)
355
+ // 2. Score is below 75 (Shaky) AND has more than 5 runs
356
+ return (result.category === FlakinessCategory.UNSTABLE ||
357
+ (result.category === FlakinessCategory.SHAKY && result.totalRuns >= 5));
358
+ }
359
+ /**
360
+ * Get quarantine recommendation message
361
+ * @param result Flakiness result
362
+ * @returns Recommendation message
363
+ */
364
+ getQuarantineRecommendation(result) {
365
+ if (!this.shouldQuarantine(result)) {
366
+ return `Test is ${FLAKINESS_CATEGORIES[result.category].emoji} ${result.category} - no quarantine needed`;
367
+ }
368
+ return [
369
+ `❌ Test "${result.testName}" is ${FLAKINESS_CATEGORIES[result.category].emoji} ${FLAKINESS_CATEGORIES[result.category].label} (${result.score}% flaky)`,
370
+ ` Passed ${result.successfulRuns}/${result.totalRuns} times`,
371
+ result.suggestedFix ? ` 💡 Suggested: ${result.suggestedFix}` : '',
372
+ ` 📋 Action: ${FLAKINESS_CATEGORIES[result.category].action}`
373
+ ].filter(Boolean).join('\n');
374
+ }
375
+ /**
376
+ * Calculate average duration
377
+ */
378
+ averageDuration(results) {
379
+ if (results.length === 0)
380
+ return 0;
381
+ const total = results.reduce((sum, r) => sum + r.durationMs, 0);
382
+ return Math.round(total / results.length);
383
+ }
384
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * QA360 Code Formatter
3
+ *
4
+ * Formats and validates generated test code.
5
+ * Supports TypeScript, JavaScript, Go, and Python.
6
+ */
7
+ /**
8
+ * Formatter configuration
9
+ */
10
+ export interface FormatterConfig {
11
+ /**
12
+ * Indentation style
13
+ */
14
+ indentStyle?: 'spaces' | 'tabs';
15
+ /**
16
+ * Indentation size
17
+ */
18
+ indentSize?: number;
19
+ /**
20
+ * Line width
21
+ */
22
+ lineWidth?: number;
23
+ /**
24
+ * Semicolons (for JS/TS)
25
+ */
26
+ semicolons?: boolean;
27
+ /**
28
+ * Quotes style
29
+ */
30
+ quotes?: 'single' | 'double';
31
+ /**
32
+ * Trailing commas
33
+ */
34
+ trailingCommas?: boolean;
35
+ /**
36
+ * Sort imports
37
+ */
38
+ sortImports?: boolean;
39
+ }
40
+ /**
41
+ * Formatting result
42
+ */
43
+ export interface FormatResult {
44
+ formatted: string;
45
+ errors: string[];
46
+ warnings: string[];
47
+ }
48
+ /**
49
+ * Code Formatter class
50
+ */
51
+ export declare class CodeFormatter {
52
+ private readonly config;
53
+ private readonly defaultConfig;
54
+ constructor(config?: FormatterConfig);
55
+ /**
56
+ * Format code based on language
57
+ */
58
+ format(code: string, language: string): string;
59
+ /**
60
+ * Format TypeScript code
61
+ */
62
+ formatTypeScript(code: string): string;
63
+ /**
64
+ * Format JavaScript code
65
+ */
66
+ formatJavaScript(code: string): string;
67
+ /**
68
+ * Format Go code
69
+ */
70
+ formatGo(code: string): string;
71
+ /**
72
+ * Format Python code
73
+ */
74
+ formatPython(code: string): string;
75
+ /**
76
+ * Generic formatting
77
+ */
78
+ formatGeneric(code: string): string;
79
+ /**
80
+ * Validate code syntax
81
+ */
82
+ validate(code: string, language: string): FormatResult;
83
+ /**
84
+ * Sort imports
85
+ */
86
+ private sortImports;
87
+ /**
88
+ * Sort import lines
89
+ */
90
+ private sortImportLines;
91
+ /**
92
+ * Normalize quotes
93
+ */
94
+ private normalizeQuotes;
95
+ /**
96
+ * Normalize braces spacing
97
+ */
98
+ private normalizeBraces;
99
+ /**
100
+ * Fix common formatting issues
101
+ */
102
+ private fixCommonIssues;
103
+ /**
104
+ * Normalize Python indentation
105
+ */
106
+ private normalizePythonIndentation;
107
+ }
108
+ /**
109
+ * Create a formatter with default config
110
+ */
111
+ export declare function createFormatter(config?: FormatterConfig): CodeFormatter;