qa360 2.0.6 → 2.0.8

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.
@@ -51,8 +51,12 @@ export class QA360History {
51
51
  // Display table
52
52
  console.log(chalk.bold('\nšŸ“‹ QA360 Run History\n'));
53
53
  const table = runs.map((run) => {
54
- const duration = run.ended_at ? run.ended_at - run.started_at : null;
55
- const durationStr = duration ? `${Math.round(duration / 1000)}s` : 'running';
54
+ const duration = run.ended_at && run.started_at ? run.ended_at - run.started_at : null;
55
+ let durationStr = 'running';
56
+ if (duration !== null) {
57
+ const durationSec = Math.round(duration / 1000);
58
+ durationStr = durationSec > 0 ? `${durationSec}s` : '< 1s';
59
+ }
56
60
  // Format status without chalk for console.table compatibility
57
61
  const statusIcon = run.status === 'passed' ? 'āœ…' :
58
62
  run.status === 'failed' ? 'āŒ' :
@@ -425,8 +429,8 @@ export class QA360History {
425
429
  }
426
430
  // 1. Find runs to delete (beyond keepLast, not pinned)
427
431
  const allRuns = await vault.listRuns({ limit: 1000 });
428
- const pinnedRuns = allRuns.filter(r => r.pinned);
429
- const unpinnedRuns = allRuns.filter(r => !r.pinned);
432
+ const pinnedRuns = allRuns.filter((r) => r.pinned);
433
+ const unpinnedRuns = allRuns.filter((r) => !r.pinned);
430
434
  console.log(chalk.gray(`šŸ“Š Total runs: ${allRuns.length}`));
431
435
  console.log(chalk.gray(`šŸ“Œ Pinned runs: ${pinnedRuns.length}`));
432
436
  console.log(chalk.gray(`šŸ“¦ Unpinned runs: ${unpinnedRuns.length}`));
@@ -508,7 +512,20 @@ export class QA360History {
508
512
  */
509
513
  async pin(runId, unpin = false) {
510
514
  const vault = await this.getVault();
511
- await vault.pinRun(runId, !unpin);
515
+ // Resolve short ID to full ID if needed
516
+ let fullId = runId;
517
+ const run = await vault.getRun(runId);
518
+ if (!run && runId.length < 36) {
519
+ // Try prefix match for short IDs
520
+ const prefixMatch = await vault.getRunByPrefix(runId);
521
+ if (prefixMatch) {
522
+ fullId = prefixMatch.id;
523
+ }
524
+ }
525
+ else if (run) {
526
+ fullId = run.id;
527
+ }
528
+ await vault.pinRun(fullId, !unpin);
512
529
  const action = unpin ? 'unpinned' : 'pinned';
513
530
  console.log(chalk.green(`āœ… Run ${runId} ${action} successfully!`));
514
531
  }
@@ -11,7 +11,7 @@ export interface PackLoadResult {
11
11
  valid?: boolean;
12
12
  error?: Error;
13
13
  sourcePath?: string;
14
- format?: 'v1' | 'v2' | 'unknown';
14
+ format?: 'v1' | 'v2' | 'unknown' | 'legacy';
15
15
  migrated?: boolean;
16
16
  changes?: string[];
17
17
  warnings?: string[];
@@ -50,6 +50,20 @@ export class PackLoaderV2 {
50
50
  let changes = [];
51
51
  let migrationWarnings = [];
52
52
  if (detectedVersion === 1) {
53
+ // Check for legacy format before migrating
54
+ if (migrator.isLegacyFormat(parsed)) {
55
+ return {
56
+ success: false,
57
+ error: new Error(`Legacy pack format detected (version ${parsed.version || '0.9.x'}). ` +
58
+ `This format is no longer supported.\n\n` +
59
+ `To fix this:\n` +
60
+ ` 1. Delete the old pack file: ${resolvedPath}\n` +
61
+ ` 2. Create a new pack: qa360 init\n` +
62
+ ` 3. Or copy an example: qa360 examples copy api-basic`),
63
+ sourcePath: resolvedPath,
64
+ format: 'legacy'
65
+ };
66
+ }
53
67
  // Migrate v1 to v2
54
68
  const migrationResult = migrator.migrate(parsed);
55
69
  if (!migrationResult.success) {
@@ -69,6 +83,20 @@ export class PackLoaderV2 {
69
83
  pack = parsed;
70
84
  }
71
85
  else {
86
+ // Check for legacy format and provide helpful error
87
+ if (migrator.isLegacyFormat(parsed)) {
88
+ return {
89
+ success: false,
90
+ error: new Error(`Legacy pack format detected (version ${parsed.version || '0.9.x'}). ` +
91
+ `This format is no longer supported.\n\n` +
92
+ `To fix this:\n` +
93
+ ` 1. Delete the old pack file: ${resolvedPath}\n` +
94
+ ` 2. Create a new pack: qa360 init\n` +
95
+ ` 3. Or copy an example: qa360 examples copy api-basic`),
96
+ sourcePath: resolvedPath,
97
+ format: 'legacy'
98
+ };
99
+ }
72
100
  return {
73
101
  success: false,
74
102
  error: new Error(`Unknown pack format. Version: ${parsed.version}`),
@@ -51,6 +51,11 @@ export declare class PackMigrator {
51
51
  private sanitizeGateName;
52
52
  /**
53
53
  * Detect pack version
54
+ * Handles both integer versions (1, 2) and semver strings (1.0.0, 2.0.0)
54
55
  */
55
56
  detectVersion(pack: any): 1 | 2 | 'unknown';
57
+ /**
58
+ * Check if pack is in legacy format (v0.9.x or earlier)
59
+ */
60
+ isLegacyFormat(pack: any): boolean;
56
61
  }
@@ -435,12 +435,26 @@ export class PackMigrator {
435
435
  }
436
436
  /**
437
437
  * Detect pack version
438
+ * Handles both integer versions (1, 2) and semver strings (1.0.0, 2.0.0)
438
439
  */
439
440
  detectVersion(pack) {
440
- if (pack.version === 2)
441
- return 2;
442
- if (pack.version === 1)
443
- return 1;
441
+ // Handle semver format (e.g., "1.0.0", "2.0.0")
442
+ if (pack.version) {
443
+ if (typeof pack.version === 'number') {
444
+ if (pack.version === 2)
445
+ return 2;
446
+ if (pack.version === 1)
447
+ return 1;
448
+ }
449
+ else if (typeof pack.version === 'string') {
450
+ // Extract major version from semver string
451
+ const majorVersion = parseInt(pack.version.split('.')[0], 10);
452
+ if (majorVersion === 2)
453
+ return 2;
454
+ if (majorVersion === 1)
455
+ return 1;
456
+ }
457
+ }
444
458
  // Heuristic detection
445
459
  if (pack.gates && Array.isArray(pack.gates))
446
460
  return 1;
@@ -450,6 +464,17 @@ export class PackMigrator {
450
464
  if (firstGate?.test_files || firstGate?.adapter)
451
465
  return 2;
452
466
  }
467
+ // Check for legacy format (v0.9.x) with adapters/tests
468
+ if (pack.adapters || pack.tests) {
469
+ // Legacy format detected - treat as unknown to trigger helpful error
470
+ return 'unknown';
471
+ }
453
472
  return 'unknown';
454
473
  }
474
+ /**
475
+ * Check if pack is in legacy format (v0.9.x or earlier)
476
+ */
477
+ isLegacyFormat(pack) {
478
+ return !!(pack.adapters || (pack.tests && Array.isArray(pack.tests)));
479
+ }
455
480
  }
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import { runCommand } from './commands/run.js';
22
22
  import { reportCommand } from './commands/report.js';
23
23
  import { verifyCommand } from './commands/verify.js';
24
24
  import { explainCommand } from './commands/explain.js';
25
- // import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js'; // TODO: fix pack v2 imports
25
+ import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js';
26
26
  import { secretsAddCommand, secretsListCommand, secretsRemoveCommand, secretsDoctorCommand, secretsExportCommand } from './commands/secrets.js';
27
27
  import { createHistoryCommands } from './commands/history.js';
28
28
  import { createAICommands } from './commands/ai.js';
@@ -138,36 +138,30 @@ program
138
138
  await explainCommand(input, options);
139
139
  });
140
140
  // Pack commands (Phase 2)
141
- /*
142
- // TODO: Re-enable after fixing pack v2 imports
143
141
  const packCommand = program
144
- .command('pack')
145
- .description('Pack configuration management');
146
-
142
+ .command('pack')
143
+ .description('Pack configuration management');
147
144
  packCommand
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) => {
145
+ .command('validate [pack]')
146
+ .description('Validate pack.yml (auto-detects v1 or v2)')
147
+ .option('--check-files', 'Verify that referenced test files exist')
148
+ .action(async (pack, options) => {
152
149
  await packValidateCommand(pack, options);
153
- });
154
-
150
+ });
155
151
  packCommand
156
- .command('lint [pack]')
157
- .description('Auto-fix pack.yml formatting and structure')
158
- .action(async (pack) => {
152
+ .command('lint [pack]')
153
+ .description('Auto-fix pack.yml formatting and structure')
154
+ .action(async (pack) => {
159
155
  await packLintCommand(pack);
160
- });
161
-
156
+ });
162
157
  packCommand
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) => {
158
+ .command('migrate [pack]')
159
+ .description('Migrate pack v1 to v2 format')
160
+ .option('--output <file>', 'Output file path')
161
+ .option('--dry-run', 'Preview changes without writing files')
162
+ .action(async (pack, options) => {
168
163
  await packMigrateCommand(pack, options);
169
- });
170
- */
164
+ });
171
165
  // Secrets commands (Phase 2)
172
166
  const secretsCommand = program
173
167
  .command('secrets')
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Config loader utilities for CLI commands
3
3
  */
4
- import { PackConfigV1, type PackConfigV2 } from 'qa360-core';
4
+ import { PackConfigV1, type PackConfigV2 } from '../core/index.js';
5
5
  export declare function loadPackConfig(packPath: string): Promise<PackConfigV1 | PackConfigV2>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qa360",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "QA360 Proof CLI - Quality as Cryptographic Proof",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",