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,389 @@
1
+ /**
2
+ * QA360 Watch Mode
3
+ *
4
+ * Continuous testing mode that monitors file changes and re-runs tests.
5
+ * Features:
6
+ * - File watching with debounce
7
+ * - Smart test selection based on changed files
8
+ * - Benchmark mode for performance comparison
9
+ * - Statistics aggregation across runs
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const watcher = new WatchMode({
14
+ * workingDir: process.cwd(),
15
+ * debounceMs: 300,
16
+ * onRunStart: () => console.log('Running tests...'),
17
+ * onRunComplete: (result) => console.log('Done:', result)
18
+ * });
19
+ *
20
+ * await watcher.start();
21
+ * ```
22
+ */
23
+ import { watch } from 'fs';
24
+ import { relative, join } from 'path';
25
+ import { Phase3Runner } from '../runner/phase3-runner.js';
26
+ import { readFile } from 'fs/promises';
27
+ /**
28
+ * Watch Mode Manager
29
+ *
30
+ * Monitors file changes and runs tests automatically.
31
+ */
32
+ export class WatchMode {
33
+ options;
34
+ watchers = [];
35
+ runNumber = 0;
36
+ isRunning = false;
37
+ debounceTimer;
38
+ pendingChanges = new Set();
39
+ runResults = [];
40
+ stats = {
41
+ totalRuns: 0,
42
+ successfulRuns: 0,
43
+ failedRuns: 0,
44
+ avgDuration: 0,
45
+ fastestRun: Infinity,
46
+ slowestRun: 0,
47
+ totalDuration: 0,
48
+ avgTrustScore: 0,
49
+ totalTests: 0,
50
+ totalPassed: 0,
51
+ totalFailed: 0
52
+ };
53
+ packConfig;
54
+ constructor(options) {
55
+ this.options = {
56
+ workingDir: options.workingDir,
57
+ packPath: options.packPath ?? join(options.workingDir, 'pack.yml'),
58
+ debounceMs: options.debounceMs ?? 300,
59
+ ignore: options.ignore ?? ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/.qa360/**', '**/build/**'],
60
+ runOnStart: options.runOnStart ?? true,
61
+ clearScreen: options.clearScreen ?? true,
62
+ onRunStart: options.onRunStart ?? (() => { }),
63
+ onRunComplete: options.onRunComplete ?? (() => { }),
64
+ onFileChange: options.onFileChange ?? (() => { }),
65
+ onError: options.onError ?? ((err) => console.error('Watch error:', err))
66
+ };
67
+ }
68
+ /**
69
+ * Start watching files and running tests
70
+ */
71
+ async start() {
72
+ if (this.isRunning) {
73
+ throw new Error('Watch mode is already running');
74
+ }
75
+ this.isRunning = true;
76
+ try {
77
+ // Load pack configuration
78
+ await this.loadPackConfig();
79
+ // Setup file watcher
80
+ this.setupWatcher();
81
+ // Run initial test if configured
82
+ if (this.options.runOnStart) {
83
+ await this.runTests([]);
84
+ }
85
+ this.log('Watch mode started. Press Ctrl+C to stop.');
86
+ }
87
+ catch (error) {
88
+ this.isRunning = false;
89
+ throw error;
90
+ }
91
+ }
92
+ /**
93
+ * Stop watching files
94
+ */
95
+ async stop() {
96
+ if (!this.isRunning) {
97
+ return;
98
+ }
99
+ this.isRunning = false;
100
+ if (this.debounceTimer) {
101
+ clearTimeout(this.debounceTimer);
102
+ }
103
+ // Close all file watchers
104
+ for (const watcher of this.watchers) {
105
+ watcher.close();
106
+ }
107
+ this.watchers = [];
108
+ this.log('Watch mode stopped.');
109
+ this.log(this.formatStats());
110
+ }
111
+ /**
112
+ * Get current statistics
113
+ */
114
+ getStats() {
115
+ return { ...this.stats };
116
+ }
117
+ /**
118
+ * Get run history
119
+ */
120
+ getRunHistory() {
121
+ return [...this.runResults];
122
+ }
123
+ /**
124
+ * Run benchmark tests
125
+ */
126
+ async benchmark(config) {
127
+ this.log(`Starting benchmark: ${config.iterations} iterations (${config.warmupIterations} warmup)`);
128
+ const durations = [];
129
+ let successful = 0;
130
+ // Warmup runs
131
+ this.log('Warming up...');
132
+ for (let i = 0; i < config.warmupIterations; i++) {
133
+ await this.runTests([], { silent: true });
134
+ }
135
+ // Benchmark runs
136
+ this.log('Running benchmark...');
137
+ for (let i = 0; i < config.iterations; i++) {
138
+ const result = await this.runTests([], { silent: true });
139
+ durations.push(result.duration);
140
+ if (result.success) {
141
+ successful++;
142
+ }
143
+ // Progress indicator
144
+ if ((i + 1) % Math.max(1, Math.floor(config.iterations / 10)) === 0) {
145
+ this.log(` Progress: ${i + 1}/${config.iterations}`);
146
+ }
147
+ }
148
+ return this.calculateBenchmarkStats(durations, successful, config.iterations);
149
+ }
150
+ /**
151
+ * Load pack configuration
152
+ */
153
+ async loadPackConfig() {
154
+ try {
155
+ const content = await readFile(this.options.packPath, 'utf-8');
156
+ // Parse YAML (simple version, assumes YAML is preprocessed or uses JSON-compatible format)
157
+ // For full YAML support, you'd need a YAML parser
158
+ this.packConfig = JSON.parse(content);
159
+ }
160
+ catch (error) {
161
+ // If YAML parsing fails, try to use a simple config
162
+ this.packConfig = {
163
+ version: 2,
164
+ name: 'watch-tests',
165
+ gates: {},
166
+ execution: { on_failure: 'continue' }
167
+ };
168
+ }
169
+ }
170
+ /**
171
+ * Setup file watcher using native Node.js fs.watch
172
+ */
173
+ setupWatcher() {
174
+ const watchPath = this.options.workingDir;
175
+ try {
176
+ const watcher = watch(watchPath, { recursive: true }, (eventType, filename) => {
177
+ if (filename) {
178
+ this.handleFileChange(join(watchPath, filename));
179
+ }
180
+ });
181
+ watcher.on('error', (error) => this.options.onError(error));
182
+ this.watchers.push(watcher);
183
+ }
184
+ catch (error) {
185
+ this.options.onError(error);
186
+ }
187
+ }
188
+ /**
189
+ * Check if a path should be ignored
190
+ */
191
+ shouldIgnore(path) {
192
+ const normalizedPath = path.replace(/\\/g, '/');
193
+ for (const pattern of this.options.ignore) {
194
+ const regexPattern = pattern
195
+ .replace(/\*\*/g, '.*')
196
+ .replace(/\*/g, '[^/]*')
197
+ .replace(/\//g, '\\/');
198
+ try {
199
+ const regex = new RegExp(regexPattern);
200
+ if (regex.test(normalizedPath)) {
201
+ return true;
202
+ }
203
+ }
204
+ catch {
205
+ // Invalid regex pattern, skip
206
+ }
207
+ }
208
+ return false;
209
+ }
210
+ /**
211
+ * Handle file change with debouncing
212
+ */
213
+ handleFileChange(path) {
214
+ if (this.shouldIgnore(path)) {
215
+ return;
216
+ }
217
+ this.pendingChanges.add(path);
218
+ this.options.onFileChange(path);
219
+ if (this.debounceTimer) {
220
+ clearTimeout(this.debounceTimer);
221
+ }
222
+ this.debounceTimer = setTimeout(() => {
223
+ const changes = Array.from(this.pendingChanges);
224
+ this.pendingChanges.clear();
225
+ this.runTests(changes);
226
+ }, this.options.debounceMs);
227
+ }
228
+ /**
229
+ * Run tests and update statistics
230
+ */
231
+ async runTests(changedFiles, options = {}) {
232
+ const runNumber = ++this.runNumber;
233
+ const startedAt = new Date();
234
+ if (!options.silent) {
235
+ if (this.options.clearScreen) {
236
+ console.clear();
237
+ }
238
+ this.options.onRunStart(runNumber);
239
+ }
240
+ let result;
241
+ try {
242
+ const runner = new Phase3Runner({
243
+ workingDir: this.options.workingDir,
244
+ pack: this.packConfig
245
+ });
246
+ const phase3Result = await runner.run();
247
+ const completedAt = new Date();
248
+ const duration = completedAt.getTime() - startedAt.getTime();
249
+ const success = phase3Result.success;
250
+ result = {
251
+ runNumber,
252
+ startedAt,
253
+ completedAt,
254
+ duration,
255
+ result: phase3Result,
256
+ changedFiles,
257
+ success
258
+ };
259
+ this.runResults.push(result);
260
+ this.updateStats(result);
261
+ if (!options.silent) {
262
+ this.logRunResult(result);
263
+ }
264
+ }
265
+ catch (error) {
266
+ const completedAt = new Date();
267
+ const duration = completedAt.getTime() - startedAt.getTime();
268
+ result = {
269
+ runNumber,
270
+ startedAt,
271
+ completedAt,
272
+ duration,
273
+ result: {
274
+ success: false,
275
+ pack: this.packConfig,
276
+ duration: 0,
277
+ gates: [],
278
+ hooks: { beforeAll: [], beforeEach: [], afterEach: [], afterAll: [] },
279
+ summary: { total: 0, passed: 0, failed: 1, trustScore: 0 },
280
+ error: error instanceof Error ? error.message : String(error)
281
+ },
282
+ changedFiles,
283
+ success: false
284
+ };
285
+ this.runResults.push(result);
286
+ this.updateStats(result);
287
+ }
288
+ this.options.onRunComplete(result);
289
+ return result;
290
+ }
291
+ /**
292
+ * Update aggregated statistics
293
+ */
294
+ updateStats(result) {
295
+ this.stats.totalRuns++;
296
+ this.stats.totalDuration += result.duration;
297
+ if (result.success) {
298
+ this.stats.successfulRuns++;
299
+ this.stats.avgTrustScore = ((this.stats.avgTrustScore * (this.stats.successfulRuns - 1) + result.result.summary.trustScore) /
300
+ this.stats.successfulRuns);
301
+ }
302
+ else {
303
+ this.stats.failedRuns++;
304
+ }
305
+ this.stats.avgDuration = this.stats.totalDuration / this.stats.totalRuns;
306
+ this.stats.fastestRun = Math.min(this.stats.fastestRun, result.duration);
307
+ this.stats.slowestRun = Math.max(this.stats.slowestRun, result.duration);
308
+ this.stats.totalTests += result.result.summary.total;
309
+ this.stats.totalPassed += result.result.summary.passed;
310
+ this.stats.totalFailed += result.result.summary.failed;
311
+ }
312
+ /**
313
+ * Calculate benchmark statistics
314
+ */
315
+ calculateBenchmarkStats(durations, successful, total) {
316
+ const sorted = [...durations].sort((a, b) => a - b);
317
+ const sum = durations.reduce((a, b) => a + b, 0);
318
+ const avg = sum / durations.length;
319
+ const variance = durations.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / durations.length;
320
+ const stdDev = Math.sqrt(variance);
321
+ return {
322
+ avgDuration: avg,
323
+ minDuration: sorted[0] ?? 0,
324
+ maxDuration: sorted[sorted.length - 1] ?? 0,
325
+ stdDev,
326
+ median: sorted[Math.floor(sorted.length / 2)] ?? 0,
327
+ p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
328
+ p99: sorted[Math.floor(sorted.length * 0.99)] ?? 0,
329
+ successfulRuns: successful,
330
+ totalIterations: total,
331
+ throughput: 1000 / avg // runs per second
332
+ };
333
+ }
334
+ /**
335
+ * Log formatted run result
336
+ */
337
+ logRunResult(result) {
338
+ const { result: r, changedFiles, duration } = result;
339
+ console.log(`\n${result.success ? 'āœ…' : 'āŒ'} Run #${result.runNumber} (${duration}ms)`);
340
+ if (changedFiles.length > 0) {
341
+ console.log(` Changed files:`);
342
+ for (const file of changedFiles) {
343
+ console.log(` - ${relative(this.options.workingDir, file)}`);
344
+ }
345
+ }
346
+ console.log(` Tests: ${r.summary.passed}/${r.summary.total} passed`);
347
+ console.log(` Trust Score: ${r.summary.trustScore}`);
348
+ console.log(` Gates: ${r.gates.length}`);
349
+ if (r.gates.length > 0) {
350
+ for (const gate of r.gates) {
351
+ console.log(` ${gate.success ? 'āœ…' : 'āŒ'} ${gate.gate}`);
352
+ }
353
+ }
354
+ console.log(`\n${this.formatStats()}`);
355
+ }
356
+ /**
357
+ * Format statistics for display
358
+ */
359
+ formatStats() {
360
+ const s = this.stats;
361
+ if (s.totalRuns === 0) {
362
+ return 'No runs yet';
363
+ }
364
+ const successRate = ((s.successfulRuns / s.totalRuns) * 100).toFixed(1);
365
+ return [
366
+ '',
367
+ 'šŸ“Š Statistics:',
368
+ ` Runs: ${s.totalRuns} (${s.successfulRuns} passed, ${s.failedRuns} failed)`,
369
+ ` Success Rate: ${successRate}%`,
370
+ ` Avg Duration: ${Math.round(s.avgDuration)}ms`,
371
+ ` Fastest: ${s.fastestRun === Infinity ? 0 : s.fastestRun}ms | Slowest: ${s.slowestRun}ms`,
372
+ ` Avg Trust Score: ${Math.round(s.avgTrustScore)}`,
373
+ ` Total Tests: ${s.totalTests} (${s.totalPassed} passed, ${s.totalFailed} failed)`,
374
+ ''
375
+ ].join('\n');
376
+ }
377
+ /**
378
+ * Log message
379
+ */
380
+ log(message) {
381
+ console.log(`[Watch] ${message}`);
382
+ }
383
+ }
384
+ /**
385
+ * Factory function to create a watch mode instance
386
+ */
387
+ export function createWatchMode(options) {
388
+ return new WatchMode(options);
389
+ }
package/dist/index.js CHANGED
@@ -21,9 +21,21 @@ import { examplesListCommand, examplesCopyCommand, examplesShowCommand } from '.
21
21
  import { runCommand } from './commands/run.js';
22
22
  import { reportCommand } from './commands/report.js';
23
23
  import { verifyCommand } from './commands/verify.js';
24
- import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js';
24
+ import { explainCommand } from './commands/explain.js';
25
+ // import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js'; // TODO: fix pack v2 imports
25
26
  import { secretsAddCommand, secretsListCommand, secretsRemoveCommand, secretsDoctorCommand, secretsExportCommand } from './commands/secrets.js';
26
27
  import { createHistoryCommands } from './commands/history.js';
28
+ import { createAICommands } from './commands/ai.js';
29
+ import { createFlakinessCommands } from './commands/flakiness.js';
30
+ import { createDashboardCommand } from './commands/monitor.js';
31
+ import { coverageCommand } from './commands/coverage.js';
32
+ import { sloCommand } from './commands/slo.js';
33
+ import { regressionCommand } from './commands/regression.js';
34
+ import { createRetryCommands } from './commands/retry.js';
35
+ // import { createMonitorCommands } from './commands/monitor.js'; // TODO: Re-enable monitor imports
36
+ // import { createOllamaCommands } from './commands/ollama.js'; // TODO: Re-enable when Ollama exports from core are fixed
37
+ // import { createGenerateCommands } from './commands/generate.js'; // TODO: fix generation imports
38
+ // import { createRepairCommand } from './commands/repair.js'; // TODO: fix repair imports
27
39
  const program = new Command();
28
40
  program
29
41
  .name('qa360')
@@ -46,6 +58,7 @@ program
46
58
  .option('--output <file>', 'Output file path', 'qa360.yml')
47
59
  .option('--force', 'Overwrite existing file')
48
60
  .option('-y, --yes', 'Skip prompts and use defaults')
61
+ .option('--beginner', 'Guided mode for first-time users with step-by-step explanations')
49
62
  .action(async (options) => {
50
63
  await initCommand(options);
51
64
  });
@@ -114,80 +127,47 @@ program
114
127
  await verifyCommand(proof, options);
115
128
  });
116
129
  program
117
- .command('explain <code>')
118
- .description('Explain error code and provide solutions')
119
- .action(async (code) => {
120
- console.log(chalk.blue(`šŸ” QA360 Explain - Code ${code}`));
121
- const explanations = {
122
- 'QX001': {
123
- title: 'Node.js Version Incompatible',
124
- cause: 'QA360 Core requires Node.js 18.0.0 or higher',
125
- solution: 'Update Node.js: https://nodejs.org/ or use nvm/fnm'
126
- },
127
- 'QX002': {
128
- title: 'QA360 Directory Structure Missing',
129
- cause: 'Required .qa360 directories not found',
130
- solution: 'Run "qa360 doctor --fix" to create missing directories'
131
- },
132
- 'QX003': {
133
- title: 'Playwright Not Available',
134
- cause: 'Playwright is not installed or not in PATH',
135
- solution: 'Install: "npx playwright install" or use --docker flag'
136
- },
137
- 'QX004': {
138
- title: 'Puppeteer Not Available',
139
- cause: 'Puppeteer is not installed (required for PDF generation)',
140
- solution: 'Install: "npm install puppeteer" or check dependencies'
141
- },
142
- 'QX005': {
143
- title: 'Network Connectivity Issues',
144
- cause: 'Cannot reach external services (proxy/firewall)',
145
- solution: 'Check network settings, configure proxy, or use offline mode'
146
- },
147
- 'QX006': {
148
- title: 'Secrets Configuration Missing',
149
- cause: 'No secrets.json file configured',
150
- solution: 'Run "qa360 doctor --fix" to create default secrets file'
151
- },
152
- 'QX007': {
153
- title: 'Cryptographic Keys Missing',
154
- cause: 'Ed25519 key pair not found for signing',
155
- solution: 'Run "qa360 doctor --fix" to generate new key pair'
156
- }
157
- };
158
- const explanation = explanations[code];
159
- if (explanation) {
160
- console.log(chalk.red(`\nāŒ ${explanation.title}`));
161
- console.log(chalk.yellow(`\nšŸ” Cause: ${explanation.cause}`));
162
- console.log(chalk.green(`\nšŸ’” Solution: ${explanation.solution}`));
163
- }
164
- else {
165
- console.log(chalk.red(`\nāŒ Code d'erreur inconnu: ${code}`));
166
- console.log(chalk.blue('\nšŸ“š Codes disponibles: QX001-QX007'));
167
- }
130
+ .command('explain [input]')
131
+ .description('Explain errors, failures, and test results with AI')
132
+ .option('--ai', 'Use AI-powered explanation')
133
+ .option('-P, --provider <provider>', 'AI provider to use (deepseek, ollama, openai, anthropic)')
134
+ .option('--run <id>', 'Explain a specific test run')
135
+ .option('--gate <name>', 'Explain gate-specific failures')
136
+ .option('--json', 'Output as JSON')
137
+ .action(async (input, options) => {
138
+ await explainCommand(input, options);
168
139
  });
169
140
  // Pack commands (Phase 2)
141
+ /*
142
+ // TODO: Re-enable after fixing pack v2 imports
170
143
  const packCommand = program
171
- .command('pack')
172
- .description('Pack configuration management');
144
+ .command('pack')
145
+ .description('Pack configuration management');
146
+
173
147
  packCommand
174
- .command('validate [pack]')
175
- .description('Validate pack.yml against v1 schema')
176
- .action(async (pack) => {
177
- await packValidateCommand(pack);
178
- });
148
+ .command('validate [pack]')
149
+ .description('Validate pack.yml (auto-detects v1 or v2)')
150
+ .option('--check-files', 'Verify that referenced test files exist')
151
+ .action(async (pack, options) => {
152
+ await packValidateCommand(pack, options);
153
+ });
154
+
179
155
  packCommand
180
- .command('lint [pack]')
181
- .description('Auto-fix pack.yml formatting and structure')
182
- .action(async (pack) => {
156
+ .command('lint [pack]')
157
+ .description('Auto-fix pack.yml formatting and structure')
158
+ .action(async (pack) => {
183
159
  await packLintCommand(pack);
184
- });
160
+ });
161
+
185
162
  packCommand
186
- .command('migrate [pack]')
187
- .description('Migrate legacy pack to v1 format')
188
- .action(async (pack) => {
189
- await packMigrateCommand(pack);
190
- });
163
+ .command('migrate [pack]')
164
+ .description('Migrate pack v1 to v2 format')
165
+ .option('--output <file>', 'Output file path')
166
+ .option('--dry-run', 'Preview changes without writing files')
167
+ .action(async (pack, options) => {
168
+ await packMigrateCommand(pack, options);
169
+ });
170
+ */
191
171
  // Secrets commands (Phase 2)
192
172
  const secretsCommand = program
193
173
  .command('secrets')
@@ -232,6 +212,26 @@ program
232
212
  });
233
213
  // History commands (Evidence Vault)
234
214
  program.addCommand(createHistoryCommands());
215
+ // Flakiness commands (Test reliability tracking)
216
+ program.addCommand(createFlakinessCommands());
217
+ // AI commands (DeepSeek, Ollama, OpenAI, Anthropic)
218
+ program.addCommand(createAICommands());
219
+ // program.addCommand(createOllamaCommands()); // TODO: Re-enable when Ollama exports from core are fixed
220
+ // Generate AI commands (Phase 4)
221
+ // program.addCommand(createGenerateCommands()); // TODO: Re-enable after fixing imports
222
+ // Repair Commands (Phase 7 - Auto-Repair)
223
+ // program.addCommand(createRepairCommand()); // TODO: Re-enable after fixing imports
224
+ // Monitor Commands (Phase 8 - TUI & Dashboard)
225
+ // program.addCommand(createMonitorCommands()); // TODO: Re-enable after fixing imports
226
+ program.addCommand(createDashboardCommand());
227
+ // Coverage Commands (Vision 2.0 - Phase 2 - F11)
228
+ program.addCommand(coverageCommand);
229
+ // SLO/SLI Commands (Vision 2.0 - Phase 2)
230
+ program.addCommand(sloCommand);
231
+ // Regression Commands (Vision 2.0 - Phase 2 - F12)
232
+ program.addCommand(regressionCommand);
233
+ // Retry Commands (Vision 2.0 - Phase 2 - F8 Smart Retry)
234
+ program.addCommand(createRetryCommands());
235
235
  // Show banner
236
236
  console.log(chalk.bold.blue('QA360 Core v' + version));
237
237
  console.log(chalk.gray('Transform software testing into verifiable, signed, and traceable proofs\n'));
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Config loader utilities for CLI commands
3
+ */
4
+ import { PackConfigV1, type PackConfigV2 } from 'qa360-core';
5
+ export declare function loadPackConfig(packPath: string): Promise<PackConfigV1 | PackConfigV2>;