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,73 @@
1
+ /**
2
+ * Flakiness CLI Commands
3
+ *
4
+ * Provides commands for managing test flakiness detection and quarantine.
5
+ */
6
+ import { Command } from 'commander';
7
+ /**
8
+ * List flakiness history for a test
9
+ */
10
+ export declare function flakinessHistoryAction(testId: string, options: {
11
+ limit?: number;
12
+ json?: boolean;
13
+ workingDir?: string;
14
+ }): Promise<void>;
15
+ /**
16
+ * Show flakiness trends for a test
17
+ */
18
+ export declare function flakinessTrendsAction(testId: string, options: {
19
+ days?: number;
20
+ json?: boolean;
21
+ workingDir?: string;
22
+ }): Promise<void>;
23
+ /**
24
+ * List all flaky tests from recent runs
25
+ */
26
+ export declare function flakinessListAction(options: {
27
+ runId?: string;
28
+ threshold?: number;
29
+ json?: boolean;
30
+ workingDir?: string;
31
+ }): Promise<void>;
32
+ /**
33
+ * List quarantined tests
34
+ */
35
+ export declare function flakinessQuarantineListAction(options: {
36
+ all?: boolean;
37
+ json?: boolean;
38
+ workingDir?: string;
39
+ }): Promise<void>;
40
+ /**
41
+ * Add a test to quarantine
42
+ */
43
+ export declare function flakinessQuarantineAddAction(testId: string, options: {
44
+ reason?: string;
45
+ score?: number;
46
+ category?: string;
47
+ workingDir?: string;
48
+ }): Promise<void>;
49
+ /**
50
+ * Remove a test from quarantine
51
+ */
52
+ export declare function flakinessQuarantineRemoveAction(testId: string, options: {
53
+ notes?: string;
54
+ workingDir?: string;
55
+ }): Promise<void>;
56
+ /**
57
+ * Show patterns for a test
58
+ */
59
+ export declare function flakinessPatternsAction(testId: string, options: {
60
+ json?: boolean;
61
+ workingDir?: string;
62
+ }): Promise<void>;
63
+ /**
64
+ * Analyze flakiness for a specific run
65
+ */
66
+ export declare function flakinessAnalyzeAction(runId: string, options: {
67
+ json?: boolean;
68
+ workingDir?: string;
69
+ }): Promise<void>;
70
+ /**
71
+ * Create all flakiness commands
72
+ */
73
+ export declare function createFlakinessCommands(): Command;
@@ -0,0 +1,435 @@
1
+ /**
2
+ * Flakiness CLI Commands
3
+ *
4
+ * Provides commands for managing test flakiness detection and quarantine.
5
+ */
6
+ import { Command } from 'commander';
7
+ import chalk from 'chalk';
8
+ import Table from 'cli-table3';
9
+ import { join } from 'path';
10
+ import { existsSync } from 'fs';
11
+ import { EvidenceVault, FLAKINESS_CATEGORIES } from '../core/index.js';
12
+ /**
13
+ * Get the vault directory path
14
+ */
15
+ function getVaultPath(workingDir = process.cwd()) {
16
+ return join(workingDir, '.qa360');
17
+ }
18
+ /**
19
+ * Open the vault or exit with error
20
+ */
21
+ async function openVault(workingDir) {
22
+ const vaultPath = getVaultPath(workingDir);
23
+ const vaultDbPath = join(vaultPath, 'vault.db');
24
+ if (!existsSync(vaultDbPath)) {
25
+ console.error(chalk.red(`❌ Vault not found at ${vaultDbPath}`));
26
+ console.error(chalk.yellow('💡 Run "qa360 run" first to create a vault'));
27
+ process.exit(1);
28
+ }
29
+ return await EvidenceVault.open(vaultPath);
30
+ }
31
+ /**
32
+ * Format flakiness score with emoji
33
+ */
34
+ function formatScore(score, category) {
35
+ const meta = FLAKINESS_CATEGORIES[category];
36
+ const color = score >= 75 ? 'green' : score >= 50 ? 'yellow' : 'red';
37
+ return chalk[color](`${meta.emoji} ${score}%`);
38
+ }
39
+ /**
40
+ * Format category with color
41
+ */
42
+ function formatCategory(category) {
43
+ const meta = FLAKINESS_CATEGORIES[category];
44
+ const color = category === 'legendary' || category === 'solid' ? 'green' :
45
+ category === 'good' ? 'yellow' :
46
+ category === 'shaky' ? 'yellow' : 'red';
47
+ return chalk[color](meta.label);
48
+ }
49
+ /**
50
+ * List flakiness history for a test
51
+ */
52
+ export async function flakinessHistoryAction(testId, options) {
53
+ const vault = await openVault(options.workingDir);
54
+ const history = await vault.getFlakinessHistory(testId, options.limit || 50);
55
+ if (options.json) {
56
+ console.log(JSON.stringify(history, null, 2));
57
+ await vault.close();
58
+ return;
59
+ }
60
+ if (history.length === 0) {
61
+ console.log(chalk.yellow(`\n⚠️ No flakiness history found for test: ${testId}`));
62
+ await vault.close();
63
+ return;
64
+ }
65
+ console.log(chalk.blue(`\n📊 Flakiness History: ${history[0].test_name}`));
66
+ console.log(chalk.gray(`Test ID: ${testId}`));
67
+ console.log(chalk.gray(`File: ${history[0].file_path}\n`));
68
+ const table = new Table({
69
+ head: [chalk.gray('Score'), chalk.gray('Category'), chalk.gray('Runs'), chalk.gray('Duration'), chalk.gray('Date')],
70
+ colWidths: [12, 15, 10, 12, 20],
71
+ style: { head: [], border: ['gray'] }
72
+ });
73
+ for (const record of history) {
74
+ const date = new Date(record.created_at).toLocaleDateString();
75
+ table.push([
76
+ formatScore(record.score, record.category),
77
+ formatCategory(record.category),
78
+ `${record.successful_runs}/${record.total_runs}`,
79
+ `${record.avg_duration_ms}ms`,
80
+ date
81
+ ]);
82
+ }
83
+ console.log(table.toString());
84
+ await vault.close();
85
+ }
86
+ /**
87
+ * Show flakiness trends for a test
88
+ */
89
+ export async function flakinessTrendsAction(testId, options) {
90
+ const vault = await openVault(options.workingDir);
91
+ const trends = await vault.getFlakinessTrends(testId, options.days || 30);
92
+ if (options.json) {
93
+ console.log(JSON.stringify(trends, null, 2));
94
+ await vault.close();
95
+ return;
96
+ }
97
+ // Get test name from history
98
+ const history = await vault.getFlakinessHistory(testId, 1);
99
+ const testName = history[0]?.test_name || testId;
100
+ console.log(chalk.blue(`\n📈 Flakiness Trends: ${testName}`));
101
+ console.log(chalk.gray(`Test ID: ${testId}`));
102
+ console.log(chalk.gray(`Time Window: Last ${options.days || 30} days\n`));
103
+ const trendEmoji = trends.trend === 'improving' ? '📈' : trends.trend === 'degrading' ? '📉' : '➡️';
104
+ const trendColor = trends.trend === 'improving' ? 'green' : trends.trend === 'degrading' ? 'red' : 'gray';
105
+ const trendText = trends.trend === 'improving' ? 'Improving' : trends.trend === 'degrading' ? 'Degrading' : 'Stable';
106
+ const currentCategory = trends.currentScore >= 75 ? 'good' : trends.currentScore >= 50 ? 'shaky' : 'unstable';
107
+ console.log(`${chalk.gray('Current Score:')} ${formatScore(trends.currentScore, currentCategory)}`);
108
+ console.log(`${chalk.gray('Average Score:')} ${formatScore(Math.round(trends.averageScore), currentCategory)}`);
109
+ console.log(`${chalk.gray('Trend:')} ${chalk[trendColor](`${trendEmoji} ${trendText}`)}`);
110
+ console.log(`${chalk.gray('Data Points:')} ${trends.dataPoints.length}\n`);
111
+ await vault.close();
112
+ }
113
+ /**
114
+ * List all flaky tests from recent runs
115
+ */
116
+ export async function flakinessListAction(options) {
117
+ const vault = await openVault(options.workingDir);
118
+ let flakinessRecords = [];
119
+ if (options.runId) {
120
+ flakinessRecords = await vault.getRunFlakiness(options.runId);
121
+ }
122
+ else {
123
+ // Get recent runs and their flakiness
124
+ const runs = await vault.listRuns({ limit: 10 });
125
+ for (const run of runs) {
126
+ const runFlakiness = await vault.getRunFlakiness(run.id);
127
+ flakinessRecords.push(...runFlakiness);
128
+ }
129
+ }
130
+ // Filter by threshold if specified
131
+ if (options.threshold !== undefined) {
132
+ flakinessRecords = flakinessRecords.filter(r => r.score < options.threshold);
133
+ }
134
+ // Sort by score (most flaky first)
135
+ flakinessRecords.sort((a, b) => a.score - b.score);
136
+ if (options.json) {
137
+ console.log(JSON.stringify(flakinessRecords, null, 2));
138
+ await vault.close();
139
+ return;
140
+ }
141
+ if (flakinessRecords.length === 0) {
142
+ console.log(chalk.yellow('\n✅ No flaky tests found!'));
143
+ await vault.close();
144
+ return;
145
+ }
146
+ console.log(chalk.blue(`\n🎲 Flakiness Report (${flakinessRecords.length} tests)\n`));
147
+ const table = new Table({
148
+ head: [chalk.gray('Test'), chalk.gray('Gate'), chalk.gray('Score'), chalk.gray('Category'), chalk.gray('Runs'), chalk.gray('Pattern')],
149
+ colWidths: [30, 12, 10, 15, 8, 20],
150
+ wordWrap: true,
151
+ style: { head: [], border: ['gray'] }
152
+ });
153
+ for (const record of flakinessRecords) {
154
+ table.push([
155
+ record.test_name,
156
+ record.gate,
157
+ formatScore(record.score, record.category),
158
+ formatCategory(record.category),
159
+ `${record.successful_runs}/${record.total_runs}`,
160
+ record.pattern_type || '-'
161
+ ]);
162
+ }
163
+ console.log(table.toString());
164
+ await vault.close();
165
+ }
166
+ /**
167
+ * List quarantined tests
168
+ */
169
+ export async function flakinessQuarantineListAction(options) {
170
+ const vault = await openVault(options.workingDir);
171
+ const quarantined = await vault.getQuarantinedTests(options.all !== false);
172
+ if (options.json) {
173
+ console.log(JSON.stringify(quarantined, null, 2));
174
+ await vault.close();
175
+ return;
176
+ }
177
+ if (quarantined.length === 0) {
178
+ console.log(chalk.yellow('\n✅ No tests in quarantine!'));
179
+ await vault.close();
180
+ return;
181
+ }
182
+ console.log(chalk.blue(`\n🚫 Quarantined Tests (${quarantined.length} total)\n`));
183
+ const table = new Table({
184
+ head: [chalk.gray('Test'), chalk.gray('Gate'), chalk.gray('Category'), chalk.gray('Score'), chalk.gray('Reason'), chalk.gray('Status')],
185
+ colWidths: [25, 10, 12, 8, 30, 12],
186
+ wordWrap: true,
187
+ style: { head: [], border: ['gray'] }
188
+ });
189
+ for (const test of quarantined) {
190
+ const status = test.resolved_at ?
191
+ chalk.green('✅ Resolved') :
192
+ chalk.red('🚫 Active');
193
+ table.push([
194
+ test.test_name,
195
+ test.gate,
196
+ formatCategory(test.category),
197
+ `${test.score}%`,
198
+ test.reason,
199
+ status
200
+ ]);
201
+ }
202
+ console.log(table.toString());
203
+ await vault.close();
204
+ }
205
+ /**
206
+ * Add a test to quarantine
207
+ */
208
+ export async function flakinessQuarantineAddAction(testId, options) {
209
+ const vault = await openVault(options.workingDir);
210
+ // Check if already in quarantine
211
+ const existing = await vault.getQuarantine(testId);
212
+ const alreadyQuarantined = existing && !existing.resolved_at;
213
+ if (alreadyQuarantined) {
214
+ await vault.close();
215
+ console.log(chalk.yellow(`\n⚠️ Test "${existing.test_name}" is already in quarantine\n`));
216
+ return;
217
+ }
218
+ // Try to get test info from history
219
+ const history = await vault.getFlakinessHistory(testId, 1);
220
+ const testName = history[0]?.test_name || testId;
221
+ const gate = history[0]?.gate || 'unknown';
222
+ const filePath = history[0]?.file_path || 'unknown';
223
+ const score = options.score || history[0]?.score || 0;
224
+ const category = (options.category || history[0]?.category || 'unstable');
225
+ const reason = options.reason || `Manually quarantined by user`;
226
+ const quarantineId = await vault.addToQuarantine({
227
+ test_id: testId,
228
+ test_name: testName,
229
+ gate,
230
+ file_path: filePath,
231
+ reason,
232
+ score,
233
+ category,
234
+ quarantined_at: Date.now(),
235
+ quarantined_by: 'user'
236
+ });
237
+ console.log(chalk.green(`\n✅ Test "${testName}" added to quarantine (ID: ${quarantineId})`));
238
+ console.log(chalk.gray(` Reason: ${reason}`));
239
+ console.log(chalk.gray(` Category: ${formatCategory(category)} (${score}%)\n`));
240
+ await vault.close();
241
+ }
242
+ /**
243
+ * Remove a test from quarantine
244
+ */
245
+ export async function flakinessQuarantineRemoveAction(testId, options) {
246
+ const vault = await openVault(options.workingDir);
247
+ const quarantine = await vault.getQuarantine(testId);
248
+ if (!quarantine) {
249
+ console.log(chalk.yellow(`\n⚠️ Test "${testId}" is not in quarantine\n`));
250
+ await vault.close();
251
+ return;
252
+ }
253
+ if (quarantine.resolved_at) {
254
+ console.log(chalk.yellow(`\n⚠️ Test "${testId}" was already resolved on ${new Date(quarantine.resolved_at).toLocaleString()}\n`));
255
+ await vault.close();
256
+ return;
257
+ }
258
+ const notes = options.notes || 'Resolved by user';
259
+ await vault.removeFromQuarantine(testId, 'user', notes);
260
+ console.log(chalk.green(`\n✅ Test "${quarantine.test_name}" removed from quarantine`));
261
+ console.log(chalk.gray(` Notes: ${notes}\n`));
262
+ await vault.close();
263
+ }
264
+ /**
265
+ * Show patterns for a test
266
+ */
267
+ export async function flakinessPatternsAction(testId, options) {
268
+ const vault = await openVault(options.workingDir);
269
+ // Get all pattern types and check each
270
+ const patternTypes = ['timing', 'race_condition', 'external_dependency', 'environment_specific', 'selector_issue', 'unknown'];
271
+ const patterns = [];
272
+ for (const type of patternTypes) {
273
+ const pattern = await vault.getFlakinessPattern(testId, type);
274
+ if (pattern) {
275
+ patterns.push(pattern);
276
+ }
277
+ }
278
+ if (options.json) {
279
+ console.log(JSON.stringify(patterns, null, 2));
280
+ await vault.close();
281
+ return;
282
+ }
283
+ if (patterns.length === 0) {
284
+ console.log(chalk.yellow(`\n⚠️ No patterns detected for test: ${testId}\n`));
285
+ await vault.close();
286
+ return;
287
+ }
288
+ console.log(chalk.blue(`\n🔍 Detected Patterns: ${patterns[0].test_name || testId}\n`));
289
+ const table = new Table({
290
+ head: [chalk.gray('Pattern'), chalk.gray('Description'), chalk.gray('Confidence'), chalk.gray('Fix'), chalk.gray('Detections')],
291
+ colWidths: [18, 30, 12, 30, 10],
292
+ wordWrap: true,
293
+ style: { head: [], border: ['gray'] }
294
+ });
295
+ for (const pattern of patterns) {
296
+ table.push([
297
+ chalk.cyan(pattern.pattern_type),
298
+ pattern.description,
299
+ `${Math.round(pattern.confidence * 100)}%`,
300
+ pattern.suggested_fix,
301
+ pattern.detection_count.toString()
302
+ ]);
303
+ }
304
+ console.log(table.toString());
305
+ await vault.close();
306
+ }
307
+ /**
308
+ * Analyze flakiness for a specific run
309
+ */
310
+ export async function flakinessAnalyzeAction(runId, options) {
311
+ const vault = await openVault(options.workingDir);
312
+ const flakiness = await vault.getRunFlakiness(runId);
313
+ if (options.json) {
314
+ console.log(JSON.stringify(flakiness, null, 2));
315
+ await vault.close();
316
+ return;
317
+ }
318
+ if (flakiness.length === 0) {
319
+ console.log(chalk.yellow(`\n⚠️ No flakiness data found for run: ${runId}\n`));
320
+ await vault.close();
321
+ return;
322
+ }
323
+ // Calculate stats
324
+ const legendary = flakiness.filter((f) => f.category === 'legendary').length;
325
+ const solid = flakiness.filter((f) => f.category === 'solid').length;
326
+ const good = flakiness.filter((f) => f.category === 'good').length;
327
+ const shaky = flakiness.filter((f) => f.category === 'shaky').length;
328
+ const unstable = flakiness.filter((f) => f.category === 'unstable').length;
329
+ const avgScore = Math.round(flakiness.reduce((sum, f) => sum + f.score, 0) / flakiness.length);
330
+ console.log(chalk.blue(`\n📊 Flakiness Analysis for Run: ${runId}\n`));
331
+ console.log(chalk.gray(`Total Tests: ${flakiness.length}`));
332
+ console.log(chalk.gray(`Average Score: ${formatScore(avgScore, avgScore >= 75 ? 'good' : avgScore >= 50 ? 'shaky' : 'unstable')}\n`));
333
+ console.log(chalk.gray('Distribution:'));
334
+ console.log(` ${chalk.green('🟢')} Legendary: ${legendary}`);
335
+ console.log(` ${chalk.green('🟢')} Solid: ${solid}`);
336
+ console.log(` ${chalk.yellow('🟡')} Good: ${good}`);
337
+ console.log(` ${chalk.yellow('🟠')} Shaky: ${shaky}`);
338
+ console.log(` ${chalk.red('🔴')} Unstable: ${unstable}\n`);
339
+ // Show tests needing attention
340
+ const needsAttention = flakiness.filter((f) => f.score < 75);
341
+ if (needsAttention.length > 0) {
342
+ console.log(chalk.yellow(`⚠️ ${needsAttention.length} test(s) need attention:\n`));
343
+ const table = new Table({
344
+ head: [chalk.gray('Test'), chalk.gray('Score'), chalk.gray('Pattern'), chalk.gray('Suggested Fix')],
345
+ colWidths: [25, 10, 15, 40],
346
+ wordWrap: true,
347
+ style: { head: [], border: ['gray'] }
348
+ });
349
+ for (const test of needsAttention.slice(0, 10)) {
350
+ table.push([
351
+ test.test_name,
352
+ formatScore(test.score, test.category),
353
+ test.pattern_type || '-',
354
+ test.suggested_fix || '-'
355
+ ]);
356
+ }
357
+ console.log(table.toString());
358
+ if (needsAttention.length > 10) {
359
+ console.log(chalk.gray(`\n... and ${needsAttention.length - 10} more`));
360
+ }
361
+ }
362
+ await vault.close();
363
+ }
364
+ /**
365
+ * Create all flakiness commands
366
+ */
367
+ export function createFlakinessCommands() {
368
+ const flakinessCommand = new Command('flakiness')
369
+ .description('Test flakiness detection and quarantine management');
370
+ // History command
371
+ flakinessCommand
372
+ .command('history <testId>')
373
+ .description('Show flakiness history for a test')
374
+ .option('-l, --limit <number>', 'Number of records to show', '50')
375
+ .option('--json', 'Output as JSON')
376
+ .option('-w, --working-dir <path>', 'Working directory (default: current directory)')
377
+ .action(flakinessHistoryAction);
378
+ // Trends command
379
+ flakinessCommand
380
+ .command('trends <testId>')
381
+ .description('Show flakiness trends over time')
382
+ .option('-d, --days <number>', 'Time window in days', '30')
383
+ .option('--json', 'Output as JSON')
384
+ .option('-w, --working-dir <path>', 'Working directory')
385
+ .action(flakinessTrendsAction);
386
+ // List command
387
+ flakinessCommand
388
+ .command('list')
389
+ .description('List all flaky tests from recent runs')
390
+ .option('-r, --run-id <id>', 'Specific run ID')
391
+ .option('-t, --threshold <number>', 'Only show tests below this score')
392
+ .option('--json', 'Output as JSON')
393
+ .option('-w, --working-dir <path>', 'Working directory')
394
+ .action(flakinessListAction);
395
+ // Analyze command
396
+ flakinessCommand
397
+ .command('analyze <runId>')
398
+ .description('Analyze flakiness for a specific run')
399
+ .option('--json', 'Output as JSON')
400
+ .option('-w, --working-dir <path>', 'Working directory')
401
+ .action(flakinessAnalyzeAction);
402
+ // Patterns command
403
+ flakinessCommand
404
+ .command('patterns <testId>')
405
+ .description('Show detected patterns for a test')
406
+ .option('--json', 'Output as JSON')
407
+ .option('-w, --working-dir <path>', 'Working directory')
408
+ .action(flakinessPatternsAction);
409
+ // Quarantine subcommand
410
+ const quarantineCommand = flakinessCommand
411
+ .command('quarantine')
412
+ .description('Manage quarantined tests');
413
+ quarantineCommand
414
+ .command('list')
415
+ .description('List quarantined tests')
416
+ .option('-a, --all', 'Include resolved quarantines', false)
417
+ .option('--json', 'Output as JSON')
418
+ .option('-w, --working-dir <path>', 'Working directory')
419
+ .action(flakinessQuarantineListAction);
420
+ quarantineCommand
421
+ .command('add <testId>')
422
+ .description('Add a test to quarantine')
423
+ .option('-r, --reason <text>', 'Reason for quarantine')
424
+ .option('-s, --score <number>', 'Flakiness score (0-100)')
425
+ .option('-c, --category <category>', 'Category: shaky, unstable')
426
+ .option('-w, --working-dir <path>', 'Working directory')
427
+ .action(flakinessQuarantineAddAction);
428
+ quarantineCommand
429
+ .command('remove <testId>')
430
+ .description('Remove a test from quarantine')
431
+ .option('-n, --notes <text>', 'Resolution notes')
432
+ .option('-w, --working-dir <path>', 'Working directory')
433
+ .action(flakinessQuarantineRemoveAction);
434
+ return flakinessCommand;
435
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * QA360 Generate Command
3
+ *
4
+ * AI-powered test generation using Ollama.
5
+ * Generates tests from OpenAPI specs, HAR files, URLs, or code.
6
+ *
7
+ * Phase 4 - AI Test Generation
8
+ */
9
+ import { Command } from 'commander';
10
+ /**
11
+ * Create generate commands group
12
+ */
13
+ export declare function createGenerateCommands(): Command;
14
+ /**
15
+ * Generate tests command handler
16
+ */
17
+ export declare function generateCommand(source: string, options?: {
18
+ type?: 'openapi' | 'har' | 'url' | 'code';
19
+ output?: string;
20
+ framework?: 'playwright' | 'k6' | 'vitest' | 'jest';
21
+ optimize?: boolean;
22
+ model?: string;
23
+ format?: boolean;
24
+ }): Promise<void>;
25
+ /**
26
+ * Generate API tests from OpenAPI spec
27
+ */
28
+ export declare function generateApiCommand(specPath: string, options?: {
29
+ output?: string;
30
+ model?: string;
31
+ optimize?: boolean;
32
+ }): Promise<void>;
33
+ /**
34
+ * Generate UI tests from URL
35
+ */
36
+ export declare function generateUiCommand(url: string, options?: {
37
+ output?: string;
38
+ model?: string;
39
+ optimize?: boolean;
40
+ }): Promise<void>;
41
+ /**
42
+ * Generate performance tests
43
+ */
44
+ export declare function generatePerfCommand(specPath: string, options?: {
45
+ output?: string;
46
+ model?: string;
47
+ duration?: string;
48
+ vus?: number;
49
+ }): Promise<void>;
50
+ /**
51
+ * Generate unit tests from code
52
+ */
53
+ export declare function generateUnitCommand(filePath: string, options?: {
54
+ language?: string;
55
+ output?: string;
56
+ model?: string;
57
+ optimize?: boolean;
58
+ }): Promise<void>;
59
+ /**
60
+ * Build system prompt for test type
61
+ */
62
+ export declare function buildSystemPrompt(type: string): string;
63
+ /**
64
+ * Check if generation is available
65
+ */
66
+ export declare function checkCommand(): Promise<void>;