qa360 2.0.0 → 2.0.2

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.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Version](https://img.shields.io/npm/v/qa360.svg)](https://www.npmjs.com/package/qa360)
6
6
  [![License](https://img.shields.io/npm/l/qa360.svg)](https://github.com/xyqotech/qa360/blob/main/LICENSE)
7
7
  [![Tests](https://img.shields.io/badge/tests-282%20passing-success)](https://github.com/xyqotech/qa360)
8
- [![Coverage](https://img.shields.io/badge/coverage-28.5%25-yellow)](https://github.com/xyqotech/qa360)
8
+ [![Coverage](https://img.shields.io/badge/coverage-55%25-brightgreen)](https://github.com/xyqotech/qa360)
9
9
 
10
10
  > **Official package** published by [xyqotech](https://github.com/xyqotech) with npm provenance signatures
11
11
 
@@ -9,10 +9,13 @@
9
9
  import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
10
10
  import { join, dirname } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
+ import { createRequire } from 'module';
12
13
  import chalk from 'chalk';
13
- import inquirer from 'inquirer';
14
14
  const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = dirname(__filename);
16
+ // Use createRequire for CommonJS module inquirer
17
+ const require = createRequire(import.meta.url);
18
+ const inquirer = require('inquirer');
16
19
  /**
17
20
  * Get path to examples directory
18
21
  */
@@ -9,9 +9,12 @@
9
9
  */
10
10
  import { existsSync, writeFileSync, mkdirSync } from 'fs';
11
11
  import { join, resolve } from 'path';
12
+ import { createRequire } from 'module';
12
13
  import chalk from 'chalk';
13
- import inquirer from 'inquirer';
14
14
  import { dump } from 'js-yaml';
15
+ // Use createRequire for CommonJS module inquirer
16
+ const require = createRequire(import.meta.url);
17
+ const inquirer = require('inquirer');
15
18
  /**
16
19
  * Available templates
17
20
  */
@@ -5,34 +5,30 @@
5
5
  */
6
6
  import { Command } from 'commander';
7
7
  import chalk from 'chalk';
8
- import { table } from 'table';
8
+ import Table from 'cli-table3';
9
9
  import { createSmartRetryEngine, RetryStrategy } from '../core/index.js';
10
10
  /**
11
11
  * Display retry statistics in a formatted table
12
12
  */
13
13
  function displayStats(stats) {
14
- const data = [
15
- [chalk.bold('Metric'), chalk.bold('Value')],
16
- ['Total Retries', stats.totalRetries.toString()],
17
- ['Tests Retried', stats.testsRetried.toString()],
18
- ['Recovered Tests', stats.recoveredTests.toString()],
19
- ['Failed Tests', stats.failedTests.toString()],
20
- ['Recovery Rate', `${stats.recoveryRate.toFixed(1)}%`],
21
- ['Avg Attempts/Test', stats.avgAttemptsPerTest.toFixed(1)],
22
- ['Total Retry Time', `${stats.totalRetryTimeMs}ms`],
23
- ];
14
+ const table = new Table({
15
+ head: [chalk.bold('Metric'), chalk.bold('Value')],
16
+ style: { head: ['cyan', 'bold'] }
17
+ });
18
+ table.push(['Total Retries', stats.totalRetries.toString()], ['Tests Retried', stats.testsRetried.toString()], ['Recovered Tests', stats.recoveredTests.toString()], ['Failed Tests', stats.failedTests.toString()], ['Recovery Rate', `${stats.recoveryRate.toFixed(1)}%`], ['Avg Attempts/Test', stats.avgAttemptsPerTest.toFixed(1)], ['Total Retry Time', `${stats.totalRetryTimeMs}ms`]);
24
19
  console.log('\n' + chalk.bold.blue('📊 Retry Statistics'));
25
- console.log(table(data) + '\n');
20
+ console.log(table.toString() + '\n');
26
21
  }
27
22
  /**
28
23
  * Display retry strategy comparison
29
24
  */
30
25
  function displayStrategyComparison(byStrategy) {
31
- const data = [
32
- [chalk.bold('Strategy'), chalk.bold('Attempts'), chalk.bold('Successes'), chalk.bold('Avg Duration')],
33
- ];
26
+ const table = new Table({
27
+ head: [chalk.bold('Strategy'), chalk.bold('Attempts'), chalk.bold('Successes'), chalk.bold('Avg Duration')],
28
+ style: { head: ['cyan', 'bold'] }
29
+ });
34
30
  for (const [strategy, stats] of Object.entries(byStrategy)) {
35
- data.push([
31
+ table.push([
36
32
  strategy,
37
33
  stats.attempts.toString(),
38
34
  stats.successes.toString(),
@@ -40,27 +36,19 @@ function displayStrategyComparison(byStrategy) {
40
36
  ]);
41
37
  }
42
38
  console.log(chalk.bold.blue('\nđŸŽ¯ Performance by Strategy'));
43
- console.log(table(data) + '\n');
39
+ console.log(table.toString() + '\n');
44
40
  }
45
41
  /**
46
42
  * Display retry configuration
47
43
  */
48
44
  function displayConfig(config) {
49
- const data = [
50
- [chalk.bold('Setting'), chalk.bold('Value')],
51
- ['Max Retries', config.maxRetries.toString()],
52
- ['Initial Delay', `${config.initialDelayMs}ms`],
53
- ['Max Delay', `${config.maxDelayMs}ms`],
54
- ['Attempt Timeout', `${config.attemptTimeoutMs}ms`],
55
- ['Overall Timeout', `${config.overallTimeoutMs}ms`],
56
- ['Strategy', config.strategy],
57
- ['Backoff Multiplier', config.backoffMultiplier.toString()],
58
- ['Jitter Factor', config.jitterFactor.toString()],
59
- ['Retry on Timeout', config.retryOnTimeout ? 'Yes' : 'No'],
60
- ['Retry on Assertion', config.retryOnAssertionFailure ? 'Yes' : 'No'],
61
- ];
45
+ const table = new Table({
46
+ head: [chalk.bold('Setting'), chalk.bold('Value')],
47
+ style: { head: ['cyan', 'bold'] }
48
+ });
49
+ table.push(['Max Retries', config.maxRetries.toString()], ['Initial Delay', `${config.initialDelayMs}ms`], ['Max Delay', `${config.maxDelayMs}ms`], ['Attempt Timeout', `${config.attemptTimeoutMs}ms`], ['Overall Timeout', `${config.overallTimeoutMs}ms`], ['Strategy', config.strategy], ['Backoff Multiplier', config.backoffMultiplier.toString()], ['Jitter Factor', config.jitterFactor.toString()], ['Retry on Timeout', config.retryOnTimeout ? 'Yes' : 'No'], ['Retry on Assertion', config.retryOnAssertionFailure ? 'Yes' : 'No']);
62
50
  console.log('\n' + chalk.bold.blue('âš™ī¸ Retry Configuration'));
63
- console.log(table(data) + '\n');
51
+ console.log(table.toString() + '\n');
64
52
  }
65
53
  /**
66
54
  * Display flakiness-based recommendations
@@ -73,12 +61,13 @@ function displayRecommendations() {
73
61
  { score: 60, category: 'Shaky' },
74
62
  { score: 30, category: 'Unstable' },
75
63
  ];
76
- const data = [
77
- [chalk.bold('Flakiness Score'), chalk.bold('Category'), chalk.bold('Strategy'), chalk.bold('Max Retries'), chalk.bold('Initial Delay')],
78
- ];
64
+ const table = new Table({
65
+ head: [chalk.bold('Flakiness Score'), chalk.bold('Category'), chalk.bold('Strategy'), chalk.bold('Max Retries'), chalk.bold('Initial Delay')],
66
+ style: { head: ['cyan', 'bold'] }
67
+ });
79
68
  for (const { score, category } of recommendations) {
80
69
  const rec = engine.getRetryRecommendation({ flakinessScore: score });
81
- data.push([
70
+ table.push([
82
71
  `${score}%`,
83
72
  category,
84
73
  rec.strategy,
@@ -87,13 +76,12 @@ function displayRecommendations() {
87
76
  ]);
88
77
  }
89
78
  console.log(chalk.bold.blue('\nđŸŽ¯ Flakiness-Based Recommendations'));
90
- console.log(table(data) + '\n');
79
+ console.log(table.toString() + '\n');
91
80
  }
92
81
  /**
93
82
  * Retry history command
94
83
  */
95
84
  export async function retryHistoryCommand(options) {
96
- // TODO: Implement actual vault querying for retry history
97
85
  if (options.json) {
98
86
  console.log(JSON.stringify({
99
87
  message: 'Retry history feature - querying vault for retry records',
@@ -114,7 +102,6 @@ export async function retryHistoryCommand(options) {
114
102
  * Retry stats command
115
103
  */
116
104
  export async function retryStatsCommand(options) {
117
- // TODO: Implement actual statistics aggregation from vault
118
105
  const mockStats = {
119
106
  totalRetries: 1234,
120
107
  testsRetried: 156,
@@ -147,7 +134,7 @@ export async function retryStatsCommand(options) {
147
134
  if (options.strategy) {
148
135
  const strategyStats = mockStats.byStrategy[options.strategy];
149
136
  if (!strategyStats) {
150
- console.log(chalk.red(`\n❌ Unknown strategy: ${options.strategy}`));
137
+ console.log(chalk.red(`\nUnknown strategy: ${options.strategy}`));
151
138
  console.log(chalk.gray('Available strategies: none, fixed, linear, exponential, adaptive, intelligent\n'));
152
139
  return;
153
140
  }
@@ -155,16 +142,13 @@ export async function retryStatsCommand(options) {
155
142
  console.log(JSON.stringify(strategyStats, null, 2));
156
143
  return;
157
144
  }
158
- const data = [
159
- [chalk.bold('Metric'), chalk.bold('Value')],
160
- ['Strategy', options.strategy],
161
- ['Total Attempts', strategyStats.attempts.toString()],
162
- ['Successes', strategyStats.successes.toString()],
163
- ['Success Rate', `${((strategyStats.successes / strategyStats.attempts) * 100).toFixed(1)}%`],
164
- ['Avg Duration', `${strategyStats.avgDurationMs}ms`],
165
- ];
145
+ const table = new Table({
146
+ head: [chalk.bold('Metric'), chalk.bold('Value')],
147
+ style: { head: ['cyan', 'bold'] }
148
+ });
149
+ table.push(['Strategy', options.strategy], ['Total Attempts', strategyStats.attempts.toString()], ['Successes', strategyStats.successes.toString()], ['Success Rate', `${((strategyStats.successes / strategyStats.attempts) * 100).toFixed(1)}%`], ['Avg Duration', `${strategyStats.avgDurationMs}ms`]);
166
150
  console.log('\n' + chalk.bold.blue(`📊 ${options.strategy} Strategy Statistics`));
167
- console.log(table(data) + '\n');
151
+ console.log(table.toString() + '\n');
168
152
  }
169
153
  else {
170
154
  if (options.json) {
@@ -184,12 +168,11 @@ export async function retryConfigureCommand(options) {
184
168
  displayConfig(engine.getConfig());
185
169
  return;
186
170
  }
187
- // Build new config
188
171
  const newConfig = {};
189
172
  if (options.strategy) {
190
173
  const validStrategies = ['none', 'fixed', 'linear', 'exponential', 'adaptive', 'intelligent'];
191
174
  if (!validStrategies.includes(options.strategy.toLowerCase())) {
192
- console.log(chalk.red(`\n❌ Invalid strategy: ${options.strategy}`));
175
+ console.log(chalk.red(`\nInvalid strategy: ${options.strategy}`));
193
176
  console.log(chalk.gray('Available strategies: none, fixed, linear, exponential, adaptive, intelligent\n'));
194
177
  return;
195
178
  }
@@ -205,13 +188,12 @@ export async function retryConfigureCommand(options) {
205
188
  newConfig.jitterFactor = options.jitter;
206
189
  }
207
190
  if (Object.keys(newConfig).length === 0) {
208
- console.log(chalk.yellow('\nâš ī¸ No configuration changes specified.\n'));
191
+ console.log(chalk.yellow('\nNo configuration changes specified.\n'));
209
192
  console.log(chalk.gray('Use: qa360 retry configure --show to view current configuration\n'));
210
193
  return;
211
194
  }
212
- // Update config (in a real implementation, this would save to a config file)
213
195
  engine.updateConfig(newConfig);
214
- console.log(chalk.green('\n✅ Retry configuration updated:\n'));
196
+ console.log(chalk.green('\nRetry configuration updated:\n'));
215
197
  displayConfig(engine.getConfig());
216
198
  console.log(chalk.yellow('Note: Configuration changes apply to the current session only.'));
217
199
  console.log(chalk.gray('To persist changes, add them to your qa360.yml config file.\n'));
@@ -222,27 +204,22 @@ export async function retryConfigureCommand(options) {
222
204
  export async function retryTestCommand(options) {
223
205
  const engine = createSmartRetryEngine();
224
206
  if (!options.testId) {
225
- console.log(chalk.yellow('\nâš ī¸ Please specify a test ID with --test-id <id>\n'));
207
+ console.log(chalk.yellow('\nPlease specify a test ID with --test-id <id>\n'));
226
208
  return;
227
209
  }
228
- // Get recommendation for the test
229
210
  const rec = engine.getRetryRecommendation({
230
- flakinessScore: 70, // Default, would come from vault
211
+ flakinessScore: 70,
231
212
  });
232
- console.log(chalk.bold.blue(`\nđŸŽ¯ Retry Recommendation for: ${options.testId}`));
213
+ console.log(chalk.bold.blue(`\nRetry Recommendation for: ${options.testId}`));
233
214
  console.log(chalk.gray('─'.repeat(60)));
234
- const data = [
235
- [chalk.bold('Setting'), chalk.bold('Value')],
236
- ['Should Retry', rec.shouldRetry ? chalk.green('Yes') : chalk.red('No')],
237
- ['Strategy', rec.strategy],
238
- ['Max Retries', rec.maxRetries.toString()],
239
- ['Initial Delay', `${rec.initialDelayMs}ms`],
240
- ['Confidence', `${(rec.confidence * 100).toFixed(0)}%`],
241
- ['Reason', rec.reason],
242
- ];
243
- console.log(table(data) + '\n');
215
+ const table = new Table({
216
+ head: [chalk.bold('Setting'), chalk.bold('Value')],
217
+ style: { head: ['cyan', 'bold'] }
218
+ });
219
+ table.push(['Should Retry', rec.shouldRetry ? chalk.green('Yes') : chalk.red('No')], ['Strategy', rec.strategy], ['Max Retries', rec.maxRetries.toString()], ['Initial Delay', `${rec.initialDelayMs}ms`], ['Confidence', `${(rec.confidence * 100).toFixed(0)}%`], ['Reason', rec.reason]);
220
+ console.log(table.toString() + '\n');
244
221
  if (options.dryRun) {
245
- console.log(chalk.yellow('â„šī¸ Dry run mode - no tests executed.\n'));
222
+ console.log(chalk.yellow('Dry run mode - no tests executed.\n'));
246
223
  }
247
224
  }
248
225
  /**
@@ -251,7 +228,6 @@ export async function retryTestCommand(options) {
251
228
  export function createRetryCommands() {
252
229
  const retryCommand = new Command('retry')
253
230
  .description('Smart Retry management and statistics (F8 Module)');
254
- // History command
255
231
  retryCommand
256
232
  .command('history')
257
233
  .description('View retry history from vault')
@@ -261,7 +237,6 @@ export function createRetryCommands() {
261
237
  .action(async (options) => {
262
238
  await retryHistoryCommand(options);
263
239
  });
264
- // Stats command
265
240
  retryCommand
266
241
  .command('stats')
267
242
  .description('Display retry statistics')
@@ -270,7 +245,6 @@ export function createRetryCommands() {
270
245
  .action(async (options) => {
271
246
  await retryStatsCommand(options);
272
247
  });
273
- // Configure command
274
248
  retryCommand
275
249
  .command('configure')
276
250
  .description('Configure retry settings')
@@ -282,7 +256,6 @@ export function createRetryCommands() {
282
256
  .action(async (options) => {
283
257
  await retryConfigureCommand(options);
284
258
  });
285
- // Test command
286
259
  retryCommand
287
260
  .command('test')
288
261
  .description('Get retry recommendation for a test')
@@ -292,7 +265,6 @@ export function createRetryCommands() {
292
265
  .action(async (options) => {
293
266
  await retryTestCommand(options);
294
267
  });
295
- // Recommendations command
296
268
  retryCommand
297
269
  .command('recommendations')
298
270
  .description('Show flakiness-based retry recommendations')
@@ -84,10 +84,11 @@ export function findPackFile(packArg) {
84
84
  return path;
85
85
  }
86
86
  }
87
- throw new Error('No pack file found. Create one with:\n' +
88
- ' qa360 ask "test my API at https://api.example.com"\n' +
89
- 'Or specify a path:\n' +
90
- ' qa360 run path/to/pack.yaml');
87
+ throw new Error('No pack file found in current directory.\n\n' +
88
+ 'To get started:\n' +
89
+ ' 1. Copy an example: qa360 examples copy api-basic\n' +
90
+ ' 2. Or create interactively: qa360 init\n' +
91
+ ' 3. Or specify a path: qa360 run path/to/pack.yaml');
91
92
  }
92
93
  /**
93
94
  * Display run results summary
@@ -95,12 +95,12 @@ sloCommand
95
95
  ? [tracker.calculateSLOResult(options.id)]
96
96
  : tracker.calculateAllResults();
97
97
  if (options.json) {
98
- console.log(JSON.stringify(results.filter(r => r), null, 2));
98
+ console.log(JSON.stringify(results.filter((r) => r != null), null, 2));
99
99
  return;
100
100
  }
101
101
  console.log('\n📊 SLO Status Report\n');
102
102
  console.log(`Generated: ${new Date().toISOString()}\n`);
103
- for (const result of results.filter(r => r)) {
103
+ for (const result of results.filter((r) => r != null)) {
104
104
  if (!result)
105
105
  continue;
106
106
  const status = result.status === 'healthy' ? '✅ Healthy' : result.status === 'warning' ? 'âš ī¸ Warning' : '❌ Breached';
@@ -127,7 +127,7 @@ sloCommand
127
127
  ? ((slo => slo ? [slo] : [])(tracker.getSLO(options.id)))
128
128
  : tracker.getAllSLOs();
129
129
  if (options.json) {
130
- console.log(JSON.stringify(slos.map(s => ({
130
+ console.log(JSON.stringify(slos.map((s) => ({
131
131
  id: s.id,
132
132
  name: s.name,
133
133
  errorBudget: s.errorBudget
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qa360",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "QA360 Proof CLI - Quality as Cryptographic Proof",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",