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,271 @@
1
+ /**
2
+ * TUI Monitor - Real-time test execution monitor
3
+ * Listens to Phase3Runner events and updates the terminal UI
4
+ */
5
+ import { TerminalRenderer } from './renderer.js';
6
+ export class TUIMonitor {
7
+ options;
8
+ renderer;
9
+ state;
10
+ refreshInterval;
11
+ eventBuffer = [];
12
+ gateStartTime = new Map();
13
+ constructor(options = {}) {
14
+ this.options = options;
15
+ this.renderer = new TerminalRenderer({
16
+ colors: options.colors ?? true,
17
+ width: options.width
18
+ });
19
+ this.state = {
20
+ status: 'idle',
21
+ currentPhase: 'Initializing',
22
+ progress: 0,
23
+ gates: [],
24
+ startTime: Date.now(),
25
+ elapsed: 0
26
+ };
27
+ // Hide cursor for cleaner UI
28
+ process.stdout.write('\x1b[?25l');
29
+ }
30
+ /**
31
+ * Start monitoring a test run
32
+ */
33
+ start(totalGates) {
34
+ this.state.status = 'running';
35
+ this.state.currentPhase = 'Setup';
36
+ this.state.gates = Array.from({ length: totalGates }, (_, i) => ({
37
+ name: `Gate ${i + 1}`,
38
+ status: 'pending'
39
+ }));
40
+ this.state.startTime = Date.now();
41
+ // Start refresh loop
42
+ const refreshRate = this.options.refreshRate ?? 500;
43
+ this.refreshInterval = setInterval(() => {
44
+ this.updateElapsed();
45
+ this.calculateETA();
46
+ this.renderer.render(this.state);
47
+ }, refreshRate);
48
+ // Handle exit signals
49
+ this.setupExitHandlers();
50
+ }
51
+ /**
52
+ * Emit an event during test execution
53
+ */
54
+ emit(event) {
55
+ this.eventBuffer.push(event);
56
+ this.processEvent(event);
57
+ }
58
+ /**
59
+ * Update gate status
60
+ */
61
+ updateGate(name, updates) {
62
+ const gate = this.state.gates.find(g => g.name === name);
63
+ if (gate) {
64
+ Object.assign(gate, updates);
65
+ }
66
+ }
67
+ /**
68
+ * Mark gate as started
69
+ */
70
+ startGate(name) {
71
+ // If gate doesn't exist, add it
72
+ if (!this.state.gates.find(g => g.name === name)) {
73
+ this.state.gates.push({ name, status: 'pending' });
74
+ }
75
+ this.gateStartTime.set(name, Date.now());
76
+ this.updateGate(name, {
77
+ status: 'running'
78
+ });
79
+ this.state.currentGate = name;
80
+ }
81
+ /**
82
+ * Mark gate as completed
83
+ */
84
+ completeGate(name, success, results) {
85
+ const startTime = this.gateStartTime.get(name);
86
+ const duration = startTime ? Date.now() - startTime : undefined;
87
+ this.updateGate(name, {
88
+ status: success ? 'passed' : 'failed',
89
+ duration,
90
+ testsTotal: results?.total,
91
+ testsPassed: results?.passed,
92
+ testsFailed: results?.failed
93
+ });
94
+ this.state.currentGate = undefined;
95
+ this.updateProgress();
96
+ }
97
+ /**
98
+ * Set current phase
99
+ */
100
+ setPhase(phase) {
101
+ this.state.currentPhase = phase;
102
+ }
103
+ /**
104
+ * Set current progress (0-100)
105
+ */
106
+ setProgress(progress) {
107
+ this.state.progress = Math.max(0, Math.min(100, progress));
108
+ }
109
+ /**
110
+ * Update test progress for current gate
111
+ */
112
+ updateTestProgress(gateName, completed, total) {
113
+ this.updateGate(gateName, {
114
+ testsTotal: total,
115
+ testsPassed: completed
116
+ });
117
+ }
118
+ /**
119
+ * Mark run as failed
120
+ */
121
+ fail(error) {
122
+ this.state.status = 'failed';
123
+ this.stop();
124
+ }
125
+ /**
126
+ * Mark run as completed
127
+ */
128
+ complete() {
129
+ this.state.status = 'completed';
130
+ this.state.progress = 100;
131
+ this.stop();
132
+ }
133
+ /**
134
+ * Stop monitoring and cleanup
135
+ */
136
+ stop() {
137
+ if (this.refreshInterval) {
138
+ clearInterval(this.refreshInterval);
139
+ this.refreshInterval = undefined;
140
+ }
141
+ // Final render
142
+ this.renderer.render(this.state);
143
+ // Wait a bit to show final state, then cleanup
144
+ setTimeout(() => {
145
+ this.cleanup();
146
+ }, 1000);
147
+ }
148
+ /**
149
+ * Cleanup and restore terminal
150
+ */
151
+ cleanup() {
152
+ this.renderer.cleanup();
153
+ this.removeExitHandlers();
154
+ // Print final summary
155
+ this.printSummary();
156
+ }
157
+ /**
158
+ * Get current state
159
+ */
160
+ getState() {
161
+ return this.state;
162
+ }
163
+ /**
164
+ * Get event buffer
165
+ */
166
+ getEvents() {
167
+ return this.eventBuffer;
168
+ }
169
+ // Private methods
170
+ processEvent(event) {
171
+ switch (event.type) {
172
+ case 'phase_start':
173
+ this.setPhase(String(event.data.phase));
174
+ break;
175
+ case 'gate_start':
176
+ this.startGate(String(event.data.gate));
177
+ break;
178
+ case 'gate_end':
179
+ this.completeGate(String(event.data.gate), Boolean(event.data.success), event.data.results);
180
+ break;
181
+ case 'test_progress':
182
+ this.updateTestProgress(String(event.data.gate), Number(event.data.completed), Number(event.data.total));
183
+ break;
184
+ case 'error':
185
+ this.fail(String(event.data.error));
186
+ break;
187
+ }
188
+ }
189
+ updateElapsed() {
190
+ this.state.elapsed = Date.now() - this.state.startTime;
191
+ }
192
+ calculateETA() {
193
+ if (this.state.progress <= 0) {
194
+ this.state.eta = undefined;
195
+ return;
196
+ }
197
+ const elapsed = this.state.elapsed;
198
+ const progress = this.state.progress / 100;
199
+ const estimatedTotal = elapsed / progress;
200
+ this.state.eta = estimatedTotal - elapsed;
201
+ }
202
+ updateProgress() {
203
+ const total = this.state.gates.length;
204
+ const completed = this.state.gates.filter(g => g.status === 'passed' || g.status === 'failed' || g.status === 'skipped').length;
205
+ this.state.progress = total > 0 ? (completed / total) * 100 : 0;
206
+ }
207
+ printSummary() {
208
+ const { status, gates, elapsed } = this.state;
209
+ console.log('\n' + '='.repeat(60));
210
+ console.log(` Test Run ${status === 'completed' ? 'Completed' : 'Failed'}`);
211
+ console.log('='.repeat(60));
212
+ const passed = gates.filter(g => g.status === 'passed').length;
213
+ const failed = gates.filter(g => g.status === 'failed').length;
214
+ const skipped = gates.filter(g => g.status === 'skipped').length;
215
+ console.log(` Total Gates: ${gates.length}`);
216
+ console.log(` Passed: ${this.formatCount(passed, 'green')}`);
217
+ console.log(` Failed: ${this.formatCount(failed, 'red')}`);
218
+ console.log(` Skipped: ${this.formatCount(skipped, 'dim')}`);
219
+ console.log(` Duration: ${this.formatDuration(elapsed)}`);
220
+ console.log('='.repeat(60) + '\n');
221
+ }
222
+ formatCount(count, color) {
223
+ const code = { green: '32', red: '31', dim: '2' }[color] || '0';
224
+ return `\x1b[${code}m${count}\x1b[0m`;
225
+ }
226
+ formatDuration(ms) {
227
+ if (ms < 1000)
228
+ return `${ms}ms`;
229
+ if (ms < 60000)
230
+ return `${(ms / 1000).toFixed(1)}s`;
231
+ const min = Math.floor(ms / 60000);
232
+ const sec = Math.floor((ms % 60000) / 1000);
233
+ return `${min}m ${sec}s`;
234
+ }
235
+ setupExitHandlers() {
236
+ process.on('SIGINT', this.handleExit.bind(this));
237
+ process.on('SIGTERM', this.handleExit.bind(this));
238
+ }
239
+ removeExitHandlers() {
240
+ process.off('SIGINT', this.handleExit.bind(this));
241
+ process.off('SIGTERM', this.handleExit.bind(this));
242
+ }
243
+ handleExit() {
244
+ this.cleanup();
245
+ process.exit(0);
246
+ }
247
+ }
248
+ /**
249
+ * Create a monitor instance for programmatic use
250
+ */
251
+ export function createMonitor(options) {
252
+ return new TUIMonitor(options);
253
+ }
254
+ /**
255
+ * Convenience function to monitor a Phase3Runner execution
256
+ */
257
+ export async function monitorExecution(execute, options) {
258
+ const monitor = new TUIMonitor(options);
259
+ try {
260
+ await execute(monitor.emit.bind(monitor));
261
+ monitor.complete();
262
+ }
263
+ catch (error) {
264
+ monitor.emit({
265
+ type: 'error',
266
+ timestamp: Date.now(),
267
+ data: { error: error instanceof Error ? error.message : String(error) }
268
+ });
269
+ }
270
+ return;
271
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * TUI Renderer - Renders UI components to terminal
3
+ * Pure ANSI/Unicode rendering with no external dependencies
4
+ */
5
+ import type { TUIState, TUIRenderer, TUIProgressBarOptions, TUITableOptions } from './types.js';
6
+ export declare class TerminalRenderer implements TUIRenderer {
7
+ private readonly colors;
8
+ private readonly width;
9
+ private lastRender;
10
+ private renderCount;
11
+ constructor(options?: {
12
+ colors?: boolean;
13
+ width?: number;
14
+ });
15
+ private getTerminalWidth;
16
+ render(state: TUIState): string;
17
+ cleanup(): void;
18
+ private renderLayout;
19
+ private renderHeader;
20
+ private renderProgress;
21
+ private createProgressBar;
22
+ private createGatesTable;
23
+ private renderFooter;
24
+ divider(width: number, char?: string): string;
25
+ progressBar(options: TUIProgressBarOptions): string;
26
+ table(options: TUITableOptions): string[];
27
+ private getStatusIcon;
28
+ private getGateStatusIcon;
29
+ private formatTestCounts;
30
+ private formatDuration;
31
+ private colorize;
32
+ getRenderCount(): number;
33
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * TUI Renderer - Renders UI components to terminal
3
+ * Pure ANSI/Unicode rendering with no external dependencies
4
+ */
5
+ export class TerminalRenderer {
6
+ colors;
7
+ width;
8
+ lastRender = '';
9
+ renderCount = 0;
10
+ constructor(options) {
11
+ this.colors = options?.colors ?? true;
12
+ this.width = options?.width ?? this.getTerminalWidth();
13
+ }
14
+ getTerminalWidth() {
15
+ return process.stdout.columns || 80;
16
+ }
17
+ render(state) {
18
+ this.renderCount++;
19
+ const layout = {
20
+ header: this.renderHeader(state),
21
+ sections: [
22
+ { type: 'divider' },
23
+ { type: 'text', content: this.renderProgress(state) },
24
+ { type: 'progress', options: this.createProgressBar(state) },
25
+ { type: 'divider' },
26
+ { type: 'table', options: this.createGatesTable(state) }
27
+ ],
28
+ footer: this.renderFooter(state)
29
+ };
30
+ const output = this.renderLayout(layout);
31
+ // Only output if changed (reduces flicker)
32
+ if (output !== this.lastRender) {
33
+ this.lastRender = output;
34
+ // Clear screen and move to top
35
+ process.stdout.write('\x1b[2J\x1b[H');
36
+ process.stdout.write(output);
37
+ }
38
+ return output;
39
+ }
40
+ cleanup() {
41
+ // Clear any remaining progress indicators
42
+ process.stdout.write('\x1b[2J\x1b[H');
43
+ process.stdout.write('\x1b[?25h'); // Show cursor
44
+ }
45
+ renderLayout(layout) {
46
+ const lines = [];
47
+ if (layout.header) {
48
+ lines.push(layout.header);
49
+ }
50
+ for (const section of layout.sections) {
51
+ switch (section.type) {
52
+ case 'text':
53
+ if (section.content)
54
+ lines.push(section.content);
55
+ break;
56
+ case 'divider':
57
+ lines.push(this.divider(this.width));
58
+ break;
59
+ case 'progress':
60
+ if (section.options)
61
+ lines.push(this.progressBar(section.options));
62
+ break;
63
+ case 'table':
64
+ if (section.options)
65
+ lines.push(...this.table(section.options));
66
+ break;
67
+ }
68
+ }
69
+ if (layout.footer) {
70
+ lines.push('');
71
+ lines.push(layout.footer);
72
+ }
73
+ return lines.join('\n');
74
+ }
75
+ renderHeader(state) {
76
+ const statusIcon = this.getStatusIcon(state.status);
77
+ const statusText = this.colorize(state.status.toUpperCase(), state.status);
78
+ const version = 'QA360 v1.4.7';
79
+ return [
80
+ this.colorize('╔════════════════════════════════════════════════════════════╗', 'dim'),
81
+ this.colorize(`║ ${version} - Test Execution Monitor${' '.repeat(Math.max(0, this.width - 60))}║`, 'bold'),
82
+ this.colorize('╠════════════════════════════════════════════════════════════╣', 'dim'),
83
+ this.colorize(`║ Status: ${statusIcon} ${statusText}${' '.repeat(Math.max(0, this.width - 65))}║`, 'reset'),
84
+ this.colorize('╚════════════════════════════════════════════════════════════╝', 'dim')
85
+ ].join('\n');
86
+ }
87
+ renderProgress(state) {
88
+ const elapsed = this.formatDuration(state.elapsed);
89
+ const eta = state.eta ? this.formatDuration(state.eta) : '--:--';
90
+ const progress = state.progress.toFixed(1);
91
+ return ` Phase: ${state.currentPhase} | Progress: ${progress}% | Elapsed: ${elapsed} | ETA: ${eta}`;
92
+ }
93
+ createProgressBar(state) {
94
+ return {
95
+ width: this.width - 10,
96
+ progress: state.progress,
97
+ label: state.currentGate ? `Running: ${state.currentGate}` : undefined,
98
+ showPercent: true
99
+ };
100
+ }
101
+ createGatesTable(state) {
102
+ return {
103
+ columns: [
104
+ { key: 'status', width: 3, label: '' },
105
+ { key: 'name', width: 25, label: 'Gate' },
106
+ { key: 'tests', width: 20, label: 'Tests' },
107
+ { key: 'duration', width: 10, label: 'Duration' }
108
+ ],
109
+ rows: state.gates.map(gate => ({
110
+ status: this.getGateStatusIcon(gate.status),
111
+ name: gate.name,
112
+ tests: this.formatTestCounts(gate),
113
+ duration: gate.duration ? this.formatDuration(gate.duration) : '--'
114
+ })),
115
+ showHeaders: true
116
+ };
117
+ }
118
+ renderFooter(state) {
119
+ const help = 'Press Ctrl+C to exit';
120
+ return this.colorize(` ${help}`, 'dim');
121
+ }
122
+ // UI Components
123
+ divider(width, char = '─') {
124
+ return this.colorize(char.repeat(Math.min(width, 80)), 'dim');
125
+ }
126
+ progressBar(options) {
127
+ const { width, progress, label, showPercent } = options;
128
+ const filled = Math.floor((progress / 100) * (width - 4));
129
+ const empty = (width - 4) - filled;
130
+ const bar = '├' +
131
+ '█'.repeat(filled) +
132
+ (empty > 0 ? '░' : '') +
133
+ '░'.repeat(Math.max(0, empty - 1)) +
134
+ '┤';
135
+ const percent = showPercent ? ` ${progress.toFixed(1)}%` : '';
136
+ const labelStr = label ? `${label} ` : '';
137
+ return ` ${labelStr}${bar}${percent}`;
138
+ }
139
+ table(options) {
140
+ const { columns, rows, showHeaders } = options;
141
+ const lines = [''];
142
+ // Header
143
+ if (showHeaders) {
144
+ const header = columns.map(col => (col.label || col.key).padEnd(col.width)).join(' ');
145
+ lines.push(` ${this.colorize(header, 'bold')}`);
146
+ lines.push(` ${'─'.repeat(header.length)}`);
147
+ }
148
+ // Rows
149
+ for (const row of rows) {
150
+ const line = columns.map(col => String(row[col.key] || '').padEnd(col.width)).join(' ');
151
+ lines.push(` ${line}`);
152
+ }
153
+ return lines;
154
+ }
155
+ // Utilities
156
+ getStatusIcon(status) {
157
+ const icons = {
158
+ idle: '○',
159
+ running: '●',
160
+ paused: '◐',
161
+ completed: '✓',
162
+ failed: '✗'
163
+ };
164
+ return this.colorize(icons[status] || '○', status);
165
+ }
166
+ getGateStatusIcon(status) {
167
+ const icons = {
168
+ pending: this.colorize('○', 'dim'),
169
+ running: this.colorize('●', 'yellow'),
170
+ passed: this.colorize('✓', 'green'),
171
+ failed: this.colorize('✗', 'red'),
172
+ skipped: this.colorize('○', 'dim')
173
+ };
174
+ return icons[status] || icons.pending;
175
+ }
176
+ formatTestCounts(gate) {
177
+ if (gate.testsTotal === undefined)
178
+ return '--';
179
+ const passed = gate.testsPassed ?? 0;
180
+ const failed = gate.testsFailed ?? 0;
181
+ return `${passed}/${gate.testsTotal} ${failed > 0 ? `(${failed} failed)` : ''}`;
182
+ }
183
+ formatDuration(ms) {
184
+ if (ms < 1000)
185
+ return `${ms}ms`;
186
+ if (ms < 60000)
187
+ return `${(ms / 1000).toFixed(1)}s`;
188
+ const min = Math.floor(ms / 60000);
189
+ const sec = Math.floor((ms % 60000) / 1000);
190
+ return `${min}m ${sec}s`;
191
+ }
192
+ colorize(text, type) {
193
+ if (!this.colors)
194
+ return text;
195
+ const codes = {
196
+ green: '32',
197
+ red: '31',
198
+ yellow: '33',
199
+ blue: '34',
200
+ dim: '2',
201
+ bold: '1',
202
+ reset: '0',
203
+ passed: '32',
204
+ failed: '31',
205
+ running: '33',
206
+ pending: '2',
207
+ skipped: '2',
208
+ idle: '34',
209
+ completed: '32',
210
+ paused: '33'
211
+ };
212
+ const code = codes[type] || '0';
213
+ return `\x1b[${code}m${text}\x1b[0m`;
214
+ }
215
+ getRenderCount() {
216
+ return this.renderCount;
217
+ }
218
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * QA360 TUI (Terminal UI) Module
3
+ * Phase 8: Terminal UI for local monitoring
4
+ */
5
+ export interface TUIOptions {
6
+ refreshRate?: number;
7
+ compact?: boolean;
8
+ verbose?: boolean;
9
+ colors?: boolean;
10
+ width?: number;
11
+ }
12
+ export interface TUIState {
13
+ status: 'idle' | 'running' | 'paused' | 'completed' | 'failed';
14
+ currentPhase: string;
15
+ progress: number;
16
+ currentGate?: string;
17
+ gates: TUIGateState[];
18
+ startTime: number;
19
+ elapsed: number;
20
+ eta?: number;
21
+ }
22
+ export interface TUIGateState {
23
+ name: string;
24
+ status: 'pending' | 'running' | 'passed' | 'failed' | 'skipped';
25
+ duration?: number;
26
+ testsTotal?: number;
27
+ testsPassed?: number;
28
+ testsFailed?: number;
29
+ }
30
+ export interface TUIEvent {
31
+ type: 'phase_start' | 'phase_end' | 'gate_start' | 'gate_end' | 'test_progress' | 'error';
32
+ timestamp: number;
33
+ data: Record<string, unknown>;
34
+ }
35
+ export interface TUIRenderer {
36
+ render(state: TUIState): string;
37
+ cleanup(): void;
38
+ }
39
+ export interface TUIProgressBarOptions {
40
+ width: number;
41
+ progress: number;
42
+ label?: string;
43
+ showPercent?: boolean;
44
+ }
45
+ export interface TUITableOptions {
46
+ columns: Array<{
47
+ key: string;
48
+ width: number;
49
+ label?: string;
50
+ }>;
51
+ rows: Array<Record<string, string | number>>;
52
+ showHeaders?: boolean;
53
+ }
54
+ export interface TUILayout {
55
+ header?: string;
56
+ sections: TUISection[];
57
+ footer?: string;
58
+ }
59
+ export interface TUISection {
60
+ type: 'text' | 'progress' | 'table' | 'divider';
61
+ content?: string;
62
+ options?: TUIProgressBarOptions | TUITableOptions;
63
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * QA360 TUI (Terminal UI) Module
3
+ * Phase 8: Terminal UI for local monitoring
4
+ */
5
+ export {};