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,340 @@
1
+ /**
2
+ * QA360 Regression Command
3
+ *
4
+ * CLI command for regression detection and analysis.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { createRegressionDetector, createRegressionTrendAnalyzer } from '../core/index.js';
8
+ export const regressionCommand = new Command('regression');
9
+ regressionCommand.description('Regression detection and analysis');
10
+ // Detect regressions
11
+ regressionCommand
12
+ .command('detect')
13
+ .description('Detect regressions by comparing current metrics to baseline')
14
+ .option('--baseline <file>', 'Baseline metrics JSON file')
15
+ .option('--current <file>', 'Current metrics JSON file')
16
+ .option('--run-id <id>', 'Current run ID')
17
+ .option('--gate <name>', 'Gate name')
18
+ .option('--json', 'Output as JSON')
19
+ .action(async (options) => {
20
+ const detector = createRegressionDetector();
21
+ // Demo: Create baseline from simulated data
22
+ const baselineData = [];
23
+ for (let i = 0; i < 30; i++) {
24
+ baselineData.push({
25
+ timestamp: Date.now() - (30 - i) * 3600000,
26
+ value: 100 + Math.random() * 10,
27
+ runId: `run_${i}`
28
+ });
29
+ }
30
+ detector.updateBaseline('api_latency_p95', baselineData);
31
+ detector.updateBaseline('pass_rate', baselineData.map(d => ({
32
+ ...d,
33
+ value: 95 + Math.random() * 3
34
+ })));
35
+ detector.updateBaseline('line_coverage', baselineData.map(d => ({
36
+ ...d,
37
+ value: 80 + Math.random() * 2
38
+ })));
39
+ // Simulate current metrics with one regression
40
+ const currentMetrics = {
41
+ api_latency_p95: 145, // 45% increase - regression!
42
+ pass_rate: 94,
43
+ line_coverage: 79.5
44
+ };
45
+ const regressions = detector.detectRegressions(currentMetrics, options.runId || 'current', options.gate || 'api_smoke');
46
+ if (options.json) {
47
+ console.log(JSON.stringify(regressions, null, 2));
48
+ return;
49
+ }
50
+ console.log('\n🔍 Regression Detection Results\n');
51
+ console.log(`Run ID: ${options.runId || 'current'}`);
52
+ console.log(`Gate: ${options.gate || 'api_smoke'}\n`);
53
+ if (regressions.length === 0) {
54
+ console.log('✅ No regressions detected!\n');
55
+ return;
56
+ }
57
+ console.log(`Detected ${regressions.length} regression(s):\n`);
58
+ for (const reg of regressions) {
59
+ const icon = getSeverityIcon(reg.severity);
60
+ const arrow = reg.direction === 'worse' ? '↑' : '↓';
61
+ const changeStr = reg.percentChange > 0 ? '+' : '';
62
+ console.log(` ${icon} ${reg.metricName}`);
63
+ console.log(` Type: ${reg.type}`);
64
+ console.log(` Severity: ${reg.severity.toUpperCase()}`);
65
+ console.log(` Baseline: ${reg.baselineValue.toFixed(2)} → Current: ${reg.currentValue.toFixed(2)}`);
66
+ console.log(` Change: ${arrow} ${changeStr}${reg.percentChange.toFixed(1)}% (${reg.absoluteChange.toFixed(2)})`);
67
+ console.log(` Confidence: ${(reg.confidence * 100).toFixed(1)}%`);
68
+ console.log(` Component: ${reg.affectedComponent}`);
69
+ console.log(` Suggestions:`);
70
+ for (const suggestion of reg.suggestions) {
71
+ console.log(` - ${suggestion}`);
72
+ }
73
+ console.log('');
74
+ }
75
+ });
76
+ // Analyze trends
77
+ regressionCommand
78
+ .command('trend')
79
+ .description('Analyze metric trends for potential future regressions')
80
+ .option('--metric <name>', 'Metric name to analyze')
81
+ .option('--points <n>', 'Number of data points to use', '30')
82
+ .option('--json', 'Output as JSON')
83
+ .action(async (options) => {
84
+ const analyzer = createRegressionTrendAnalyzer();
85
+ const metricName = options.metric || 'api_latency_p95';
86
+ // Generate demo trend data
87
+ const data = [];
88
+ for (let i = 0; i < parseInt(options.points); i++) {
89
+ // Slight upward trend (degrading)
90
+ const trend = i * 0.5;
91
+ const noise = (Math.random() - 0.5) * 5;
92
+ data.push({
93
+ timestamp: Date.now() - (parseInt(options.points) - i) * 3600000,
94
+ value: 100 + trend + noise,
95
+ runId: `run_${i}`,
96
+ metadata: { metric: metricName }
97
+ });
98
+ }
99
+ const trend = analyzer.analyzeTrend(data);
100
+ if (options.json) {
101
+ console.log(JSON.stringify(trend, null, 2));
102
+ return;
103
+ }
104
+ console.log('\n📈 Trend Analysis\n');
105
+ console.log(`Metric: ${metricName}`);
106
+ console.log(`Data Points: ${data.length}\n`);
107
+ if (!trend) {
108
+ console.log('⚠️ Insufficient data for trend analysis\n');
109
+ return;
110
+ }
111
+ const directionIcon = trend.direction === 'degrading' ? '📉' : trend.direction === 'improving' ? '📈' : '➡️';
112
+ const strengthPercent = Math.round(trend.strength * 100);
113
+ console.log(` ${directionIcon} Direction: ${trend.direction.toUpperCase()}`);
114
+ console.log(` Strength: ${strengthPercent}% confidence`);
115
+ if (trend.projected !== undefined) {
116
+ console.log(` Projected (7 days): ${trend.projected.toFixed(2)}`);
117
+ }
118
+ console.log('');
119
+ });
120
+ // List recent regressions
121
+ regressionCommand
122
+ .command('list')
123
+ .description('List recent regressions')
124
+ .option('--severity <level>', 'Filter by severity')
125
+ .option('--type <type>', 'Filter by type')
126
+ .option('--limit <n>', 'Max results', '20')
127
+ .option('--json', 'Output as JSON')
128
+ .action(async (options) => {
129
+ // Demo: Generate some regressions
130
+ const regressions = [
131
+ {
132
+ id: 'reg_1',
133
+ runId: 'run_123',
134
+ baselineRunId: 'run_100',
135
+ type: 'performance',
136
+ severity: 'major',
137
+ status: 'detected',
138
+ metricName: 'api_latency_p95',
139
+ baselineValue: 100,
140
+ currentValue: 145,
141
+ absoluteChange: 45,
142
+ percentChange: 45,
143
+ significance: 0.001,
144
+ confidence: 0.999,
145
+ direction: 'worse',
146
+ affectedComponent: '/api/users',
147
+ gate: 'api_smoke',
148
+ suggestions: ['Investigate recent API changes', 'Profile the endpoint'],
149
+ timestamp: Date.now() - 3600000
150
+ },
151
+ {
152
+ id: 'reg_2',
153
+ runId: 'run_123',
154
+ baselineRunId: 'run_100',
155
+ type: 'coverage',
156
+ severity: 'minor',
157
+ status: 'detected',
158
+ metricName: 'line_coverage',
159
+ baselineValue: 82,
160
+ currentValue: 79.5,
161
+ absoluteChange: -2.5,
162
+ percentChange: -3.05,
163
+ significance: 0.03,
164
+ confidence: 0.97,
165
+ direction: 'worse',
166
+ affectedComponent: 'src/components/',
167
+ gate: 'ui',
168
+ suggestions: ['Review deleted test cases'],
169
+ timestamp: Date.now() - 3600000
170
+ },
171
+ {
172
+ id: 'reg_3',
173
+ runId: 'run_122',
174
+ baselineRunId: 'run_99',
175
+ type: 'quality',
176
+ severity: 'critical',
177
+ status: 'resolved',
178
+ metricName: 'pass_rate',
179
+ baselineValue: 98,
180
+ currentValue: 85,
181
+ absoluteChange: -13,
182
+ percentChange: -13.27,
183
+ significance: 0.0001,
184
+ confidence: 0.9999,
185
+ direction: 'worse',
186
+ affectedComponent: 'test/auth',
187
+ gate: 'unit',
188
+ suggestions: ['Fix authentication tests'],
189
+ timestamp: Date.now() - 86400000
190
+ }
191
+ ];
192
+ // Filter
193
+ let filtered = regressions;
194
+ if (options.severity) {
195
+ filtered = filtered.filter(r => r.severity === options.severity);
196
+ }
197
+ if (options.type) {
198
+ filtered = filtered.filter(r => r.type === options.type);
199
+ }
200
+ filtered = filtered.slice(0, parseInt(options.limit));
201
+ if (options.json) {
202
+ console.log(JSON.stringify(filtered, null, 2));
203
+ return;
204
+ }
205
+ console.log('\n🔍 Recent Regressions\n');
206
+ if (filtered.length === 0) {
207
+ console.log('No regressions found.\n');
208
+ return;
209
+ }
210
+ for (const reg of filtered) {
211
+ const icon = getSeverityIcon(reg.severity);
212
+ const statusIcon = reg.status === 'resolved' ? '✅' : reg.status === 'investigating' ? '🔬' : '❗';
213
+ console.log(` ${icon} ${statusIcon} ${reg.metricName}`);
214
+ console.log(` Severity: ${reg.severity.toUpperCase()} | Status: ${reg.status}`);
215
+ console.log(` Type: ${reg.type} | Gate: ${reg.gate}`);
216
+ console.log(` ${new Date(reg.timestamp).toISOString()}`);
217
+ console.log('');
218
+ }
219
+ });
220
+ // Compare two runs
221
+ regressionCommand
222
+ .command('compare')
223
+ .description('Compare two runs for regressions')
224
+ .option('--baseline <runId>', 'Baseline run ID')
225
+ .option('--current <runId>', 'Current run ID')
226
+ .option('--metric <name>', 'Metric to compare')
227
+ .option('--json', 'Output as JSON')
228
+ .action(async (options) => {
229
+ const analyzer = createRegressionTrendAnalyzer();
230
+ const metricName = options.metric || 'api_latency';
231
+ // Generate demo data
232
+ const baseline = [];
233
+ const current = [];
234
+ for (let i = 0; i < 10; i++) {
235
+ baseline.push({
236
+ timestamp: Date.now() - (20 - i) * 3600000,
237
+ value: 100 + Math.random() * 5,
238
+ runId: options.baseline || `baseline_${i}`
239
+ });
240
+ }
241
+ for (let i = 0; i < 10; i++) {
242
+ current.push({
243
+ timestamp: Date.now() - (10 - i) * 3600000,
244
+ value: 120 + Math.random() * 5, // 20% higher
245
+ runId: options.current || `current_${i}`
246
+ });
247
+ }
248
+ const regression = analyzer.comparePeriods(baseline, current, metricName);
249
+ if (options.json) {
250
+ console.log(JSON.stringify(regression, null, 2));
251
+ return;
252
+ }
253
+ console.log('\n🔍 Regression Comparison\n');
254
+ console.log(`Metric: ${metricName}`);
255
+ console.log(`Baseline: ${options.baseline || 'baseline_*'}`);
256
+ console.log(`Current: ${options.current || 'current_*'}\n`);
257
+ if (!regression) {
258
+ console.log('✅ No regression detected!\n');
259
+ return;
260
+ }
261
+ const icon = getSeverityIcon(regression.severity);
262
+ console.log(` ${icon} Regression Detected`);
263
+ console.log(` Type: ${regression.type}`);
264
+ console.log(` Severity: ${regression.severity.toUpperCase()}`);
265
+ console.log(` Baseline: ${regression.baselineValue.toFixed(2)} → Current: ${regression.currentValue.toFixed(2)}`);
266
+ console.log(` Change: ${regression.percentChange > 0 ? '+' : ''}${regression.percentChange.toFixed(1)}%`);
267
+ console.log(` Confidence: ${(regression.confidence * 100).toFixed(1)}%\n`);
268
+ });
269
+ // Show regression statistics
270
+ regressionCommand
271
+ .command('stats')
272
+ .description('Show regression statistics')
273
+ .option('--json', 'Output as JSON')
274
+ .action(async (_options) => {
275
+ // Demo statistics
276
+ const stats = {
277
+ total: 15,
278
+ bySeverity: {
279
+ critical: 2,
280
+ major: 3,
281
+ moderate: 5,
282
+ minor: 3,
283
+ info: 2
284
+ },
285
+ byType: {
286
+ performance: 5,
287
+ quality: 4,
288
+ coverage: 2,
289
+ reliability: 2,
290
+ security: 1,
291
+ api_breaking: 1
292
+ },
293
+ byStatus: {
294
+ detected: 5,
295
+ investigating: 3,
296
+ confirmed: 2,
297
+ resolved: 4,
298
+ ignored: 1
299
+ },
300
+ recent: 3 // Last 7 days
301
+ };
302
+ console.log('\n📊 Regression Statistics\n');
303
+ console.log(`Total Regressions: ${stats.total}`);
304
+ console.log(`Recent (7 days): ${stats.recent}\n`);
305
+ console.log('By Severity:');
306
+ for (const [severity, count] of Object.entries(stats.bySeverity)) {
307
+ if (count > 0) {
308
+ const icon = getSeverityIcon(severity);
309
+ console.log(` ${icon} ${severity.toUpperCase()}: ${count}`);
310
+ }
311
+ }
312
+ console.log('\nBy Type:');
313
+ for (const [type, count] of Object.entries(stats.byType)) {
314
+ if (count > 0) {
315
+ console.log(` • ${type}: ${count}`);
316
+ }
317
+ }
318
+ console.log('\nBy Status:');
319
+ for (const [status, count] of Object.entries(stats.byStatus)) {
320
+ if (count > 0) {
321
+ console.log(` • ${status}: ${count}`);
322
+ }
323
+ }
324
+ console.log('');
325
+ });
326
+ function getSeverityIcon(severity) {
327
+ switch (severity) {
328
+ case 'critical':
329
+ return '🔴';
330
+ case 'major':
331
+ return '🟠';
332
+ case 'moderate':
333
+ return '🟡';
334
+ case 'minor':
335
+ return '🟢';
336
+ default:
337
+ return '⚪';
338
+ }
339
+ }
340
+ export default regressionCommand;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Repair Commands
3
+ *
4
+ * CLI commands for automatic test repair.
5
+ */
6
+ import { Command } from 'commander';
7
+ /**
8
+ * Repair analyze command
9
+ */
10
+ export declare function createRepairAnalyzeCommand(): Command;
11
+ /**
12
+ * Repair suggest command
13
+ */
14
+ export declare function createRepairSuggestCommand(): Command;
15
+ /**
16
+ * Repair fix command
17
+ */
18
+ export declare function createRepairFixCommand(): Command;
19
+ /**
20
+ * Repair interactive command
21
+ */
22
+ export declare function createRepairInteractiveCommand(): Command;
23
+ /**
24
+ * Create main repair command group
25
+ */
26
+ export declare function createRepairCommand(): Command;
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Repair Commands
3
+ *
4
+ * CLI commands for automatic test repair.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { readFile } from 'fs/promises';
8
+ import { existsSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { TestRepairer, analyzeTestErrors, getQuickFixes } from '../core/index.js';
11
+ /**
12
+ * Parse test output to extract errors
13
+ */
14
+ async function parseTestOutput(outputPath) {
15
+ if (!existsSync(outputPath)) {
16
+ return { errors: [] };
17
+ }
18
+ try {
19
+ const content = await readFile(outputPath, 'utf-8');
20
+ const errors = await analyzeTestErrors('', content);
21
+ return { errors };
22
+ }
23
+ catch {
24
+ return { errors: [] };
25
+ }
26
+ }
27
+ /**
28
+ * Simulate running tests and extracting errors
29
+ */
30
+ async function extractErrorsFromFile(testFile) {
31
+ if (!existsSync(testFile)) {
32
+ return [];
33
+ }
34
+ try {
35
+ const content = await readFile(testFile, 'utf-8');
36
+ // Look for common error patterns in test files
37
+ const errors = [];
38
+ // This is a simplified extraction - in production would parse actual test output
39
+ if (content.includes('expect(') && content.includes('.toBe(')) {
40
+ // Find assertions that might fail
41
+ const lines = content.split('\n');
42
+ lines.forEach((line, index) => {
43
+ if (line.includes('expect(') && line.includes('.toBe(')) {
44
+ // Create a simulated error for demonstration
45
+ const match = line.match(/expect\(([^)]+)\)\.toBe\(([^)]+)\)/);
46
+ if (match) {
47
+ errors.push({
48
+ testFile,
49
+ testName: `Test at line ${index + 1}`,
50
+ line: index + 1,
51
+ type: 'assertion_error',
52
+ message: `Expected ${match[2]} but received different value`,
53
+ context: 'Assertion failed'
54
+ });
55
+ }
56
+ }
57
+ });
58
+ }
59
+ return errors;
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ }
65
+ /**
66
+ * Repair analyze command
67
+ */
68
+ export function createRepairAnalyzeCommand() {
69
+ const cmd = new Command('analyze');
70
+ cmd
71
+ .description('Analyze test failures and generate repair suggestions')
72
+ .argument('<test-file>', 'Path to the test file')
73
+ .option('-o, --output <path>', 'Path to test output file')
74
+ .option('-p, --provider <provider>', 'LLM provider (ollama, openai, anthropic, mock)', 'ollama')
75
+ .option('-j, --json', 'Output in JSON format')
76
+ .option('-v, --verbose', 'Verbose output')
77
+ .action(async (testFile, options) => {
78
+ const filePath = resolve(testFile);
79
+ if (!existsSync(filePath)) {
80
+ console.error(`Error: Test file not found: ${filePath}`);
81
+ process.exit(1);
82
+ }
83
+ try {
84
+ // Extract errors from test output or simulate
85
+ let errors = [];
86
+ if (options.output) {
87
+ const result = await parseTestOutput(options.output);
88
+ errors = result.errors;
89
+ }
90
+ if (errors.length === 0) {
91
+ errors = await extractErrorsFromFile(filePath);
92
+ }
93
+ if (errors.length === 0) {
94
+ console.log('No test errors found to analyze.');
95
+ console.log('Tip: Provide test output with --output option or run tests first.');
96
+ return;
97
+ }
98
+ // Create repairer
99
+ const repairer = new TestRepairer({
100
+ llmProvider: options.provider,
101
+ verbose: options.verbose
102
+ });
103
+ // Analyze
104
+ const testRunResult = {
105
+ passed: 0,
106
+ failed: errors.length,
107
+ errors,
108
+ duration: 0
109
+ };
110
+ const analysis = await repairer.analyze(filePath, testRunResult);
111
+ if (options.json) {
112
+ console.log(JSON.stringify({
113
+ testFile: filePath,
114
+ errorsFound: errors.length,
115
+ autoFixable: analysis.autoFixable,
116
+ suggestions: analysis.suggestions,
117
+ priority: analysis.priority,
118
+ estimatedTime: analysis.estimatedTime
119
+ }, null, 2));
120
+ }
121
+ else {
122
+ console.log(`\n=== Test Repair Analysis ===`);
123
+ console.log(`File: ${filePath}`);
124
+ console.log(`Errors found: ${errors.length}`);
125
+ console.log(`Auto-fixable: ${analysis.autoFixable}`);
126
+ console.log(`Priority: ${analysis.priority}`);
127
+ console.log(`Estimated repair time: ${analysis.estimatedTime}s`);
128
+ console.log(`\nSuggestions:`);
129
+ for (const suggestion of analysis.suggestions) {
130
+ console.log(`\n ${suggestion.description}`);
131
+ console.log(` Type: ${suggestion.type}`);
132
+ console.log(` Confidence: ${(suggestion.confidence * 100).toFixed(0)}%`);
133
+ console.log(` Effort: ${suggestion.estimatedEffort}`);
134
+ console.log(` Reason: ${suggestion.reason}`);
135
+ if (options.verbose && suggestion.code) {
136
+ console.log(` Code:\n ${suggestion.code}`);
137
+ }
138
+ }
139
+ }
140
+ }
141
+ catch (error) {
142
+ console.error(`Analysis failed: ${error instanceof Error ? error.message : String(error)}`);
143
+ process.exit(1);
144
+ }
145
+ });
146
+ return cmd;
147
+ }
148
+ /**
149
+ * Repair suggest command
150
+ */
151
+ export function createRepairSuggestCommand() {
152
+ const cmd = new Command('suggest');
153
+ cmd
154
+ .description('Get quick fix suggestions for a test error')
155
+ .argument('<test-file>', 'Path to the test file')
156
+ .option('-m, --message <message>', 'Error message')
157
+ .option('-t, --type <type>', 'Error type (assertion_error, timeout_error, etc.)')
158
+ .option('-l, --line <number>', 'Line number', '0')
159
+ .option('-j, --json', 'Output in JSON format')
160
+ .action(async (testFile, options) => {
161
+ const filePath = resolve(testFile);
162
+ if (!options.message) {
163
+ console.error('Error: --message is required');
164
+ process.exit(1);
165
+ }
166
+ try {
167
+ const error = {
168
+ testFile: filePath,
169
+ testName: 'unknown',
170
+ line: parseInt(options.line, 10),
171
+ type: options.type || 'unknown',
172
+ message: options.message
173
+ };
174
+ const suggestions = await getQuickFixes(error);
175
+ if (options.json) {
176
+ console.log(JSON.stringify(suggestions, null, 2));
177
+ }
178
+ else {
179
+ console.log(`\n=== Quick Fix Suggestions ===`);
180
+ console.log(`Error: ${error.message}`);
181
+ console.log(`Type: ${error.type}\n`);
182
+ if (suggestions.length === 0) {
183
+ console.log('No quick fixes available for this error type.');
184
+ }
185
+ else {
186
+ for (const suggestion of suggestions) {
187
+ console.log(`\n${suggestion.description}`);
188
+ console.log(` Type: ${suggestion.type}`);
189
+ console.log(` Confidence: ${(suggestion.confidence * 100).toFixed(0)}%`);
190
+ console.log(` Code: ${suggestion.code}`);
191
+ }
192
+ }
193
+ }
194
+ }
195
+ catch (error) {
196
+ console.error(`Failed to generate suggestions: ${error instanceof Error ? error.message : String(error)}`);
197
+ process.exit(1);
198
+ }
199
+ });
200
+ return cmd;
201
+ }
202
+ /**
203
+ * Repair fix command
204
+ */
205
+ export function createRepairFixCommand() {
206
+ const cmd = new Command('fix');
207
+ cmd
208
+ .description('Apply automatic fixes to failing tests')
209
+ .argument('<test-file>', 'Path to the test file')
210
+ .option('-o, --output <path>', 'Path to test output file')
211
+ .option('-p, --provider <provider>', 'LLM provider (ollama, openai, anthropic, mock)', 'ollama')
212
+ .option('--apply', 'Actually apply fixes (default: dry-run)')
213
+ .option('--no-backup', 'Do not create backup before fixing')
214
+ .option('-j, --json', 'Output in JSON format')
215
+ .option('-v, --verbose', 'Verbose output')
216
+ .action(async (testFile, options) => {
217
+ const filePath = resolve(testFile);
218
+ if (!existsSync(filePath)) {
219
+ console.error(`Error: Test file not found: ${filePath}`);
220
+ process.exit(1);
221
+ }
222
+ try {
223
+ // Extract errors
224
+ let errors = [];
225
+ if (options.output) {
226
+ const result = await parseTestOutput(options.output);
227
+ errors = result.errors;
228
+ }
229
+ if (errors.length === 0) {
230
+ errors = await extractErrorsFromFile(filePath);
231
+ }
232
+ if (errors.length === 0) {
233
+ console.log('No test errors found to fix.');
234
+ return;
235
+ }
236
+ // Create repairer
237
+ const repairer = new TestRepairer({
238
+ llmProvider: options.provider,
239
+ autoApply: options.apply,
240
+ backup: options.backup !== false,
241
+ verbose: options.verbose
242
+ });
243
+ const testRunResult = {
244
+ passed: 0,
245
+ failed: errors.length,
246
+ errors,
247
+ duration: 0
248
+ };
249
+ console.log(`\n${options.apply ? 'Applying' : 'Previewing'} fixes for ${errors.length} error(s)...`);
250
+ const report = await repairer.repair(filePath, testRunResult);
251
+ if (options.json) {
252
+ console.log(JSON.stringify({
253
+ duration: report.duration,
254
+ success: report.success,
255
+ originalErrors: report.result.originalErrors.length,
256
+ remainingErrors: report.result.remainingErrors.length,
257
+ fixesApplied: report.result.fixes.length,
258
+ iterations: report.result.iterations
259
+ }, null, 2));
260
+ }
261
+ else {
262
+ console.log(repairer.generateReport(report));
263
+ if (!options.apply) {
264
+ console.log('\n[Dry run mode] Use --apply to actually apply fixes.');
265
+ }
266
+ }
267
+ }
268
+ catch (error) {
269
+ console.error(`Fix failed: ${error instanceof Error ? error.message : String(error)}`);
270
+ process.exit(1);
271
+ }
272
+ });
273
+ return cmd;
274
+ }
275
+ /**
276
+ * Repair interactive command
277
+ */
278
+ export function createRepairInteractiveCommand() {
279
+ const cmd = new Command('interactive');
280
+ cmd
281
+ .description('Interactive repair mode')
282
+ .argument('[test-file]', 'Path to the test file (optional)')
283
+ .option('-p, --provider <provider>', 'LLM provider (ollama, openai, anthropic, mock)', 'ollama')
284
+ .action(async (testFile, options) => {
285
+ console.log('\n=== Interactive Repair Mode ===');
286
+ console.log('This feature will be available in a future version.');
287
+ console.log('For now, use: qa360 repair analyze <file> && qa360 repair fix <file> --apply');
288
+ console.log('\nAvailable commands:');
289
+ console.log(' qa360 repair analyze <file> - Analyze test failures');
290
+ console.log(' qa360 repair suggest <file> - Get quick fix suggestions');
291
+ console.log(' qa360 repair fix <file> - Apply automatic fixes');
292
+ });
293
+ return cmd;
294
+ }
295
+ /**
296
+ * Create main repair command group
297
+ */
298
+ export function createRepairCommand() {
299
+ const cmd = new Command('repair');
300
+ cmd
301
+ .description('Automatic test repair commands (Phase 7)');
302
+ cmd.addCommand(createRepairAnalyzeCommand());
303
+ cmd.addCommand(createRepairSuggestCommand());
304
+ cmd.addCommand(createRepairFixCommand());
305
+ cmd.addCommand(createRepairInteractiveCommand());
306
+ return cmd;
307
+ }