qa360 1.0.1 → 1.0.3

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.
@@ -1,55 +1,36 @@
1
1
  /**
2
- * QA360 Run Command - Test execution engine
2
+ * QA360 Run Command - Execute quality gates
3
+ *
4
+ * Usage:
5
+ * qa360 run [pack.yaml]
6
+ * qa360 run --output ./results
7
+ * qa360 run --verbose
3
8
  */
4
- import { PackConfig } from './ask';
5
- export interface RunResult {
6
- pack: PackConfig;
7
- results: TestResult[];
8
- summary: RunSummary;
9
- timestamp: string;
10
- duration: number;
11
- }
12
- export interface TestResult {
13
- name: string;
14
- adapter: string;
15
- status: 'passed' | 'failed' | 'skipped';
16
- duration: number;
9
+ import { type Phase3RunResult, type PackConfigV1 } from 'qa360-core';
10
+ /**
11
+ * CLI options for run command
12
+ */
13
+ export interface RunOptions {
17
14
  output?: string;
18
- error?: string;
19
- artifacts?: string[];
20
- }
21
- export interface RunSummary {
22
- total: number;
23
- passed: number;
24
- failed: number;
25
- skipped: number;
26
- successRate: number;
15
+ verbose?: boolean;
16
+ dryRun?: boolean;
17
+ strict?: boolean;
27
18
  }
28
- export declare class QA360Runner {
29
- private qa360Dir;
30
- private toolsDir;
31
- private runsDir;
32
- private currentRunDir;
33
- run(packPath?: string, options?: {
34
- mode?: 'soft' | 'strict';
35
- retries?: number;
36
- timeout?: number;
37
- docker?: boolean;
38
- }): Promise<RunResult>;
39
- private loadPack;
40
- private executeHooks;
41
- private executeTests;
42
- private executeTest;
43
- private executePlaywrightTest;
44
- private executeK6Test;
45
- private executeLighthouseTest;
46
- private executeSemgrepTest;
47
- private executeZapTest;
48
- private executeInDocker;
49
- private isToolAvailable;
50
- private generateSummary;
51
- private saveRunResults;
52
- private displayResults;
53
- }
54
- export declare function runCommand(packPath?: string, options?: any): Promise<void>;
19
+ /**
20
+ * Load and validate pack configuration
21
+ */
22
+ export declare function loadPack(packPath: string): Promise<PackConfigV1>;
23
+ /**
24
+ * Find pack file (search order: argument, .qa360/pack.yml, pack.yaml, pack.yml)
25
+ */
26
+ export declare function findPackFile(packArg?: string): string;
27
+ /**
28
+ * Display run results summary
29
+ */
30
+ export declare function displayResults(result: Phase3RunResult): void;
31
+ /**
32
+ * Main run command
33
+ */
34
+ export declare function runCommand(packArg?: string, options?: RunOptions): Promise<void>;
35
+ export default runCommand;
55
36
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EAAE,UAAU,EAAY,MAAM,OAAO,CAAC;AAE7C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,aAAa,CAAM;IAErB,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;QACpC,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KACb,GAAG,OAAO,CAAC,SAAS,CAAC;YAkDb,QAAQ;YAuBR,YAAY;YAqBZ,YAAY;YA+CZ,WAAW;YAyBX,qBAAqB;YAmDrB,aAAa;YAgCb,qBAAqB;YAMrB,kBAAkB;YAMlB,cAAc;YAMd,eAAe;IAM7B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,eAAe;YAUT,cAAc;IAO5B,OAAO,CAAC,cAAc;CAoBvB;AAED,wBAAsB,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,GAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CASpF"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAgB,KAAK,eAAe,EAAkC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBtE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BrD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA8B5D;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,IAAI,CAAC,CAiDf;AAGD,eAAe,UAAU,CAAC"}
@@ -1,293 +1,137 @@
1
1
  /**
2
- * QA360 Run Command - Test execution engine
2
+ * QA360 Run Command - Execute quality gates
3
+ *
4
+ * Usage:
5
+ * qa360 run [pack.yaml]
6
+ * qa360 run --output ./results
7
+ * qa360 run --verbose
3
8
  */
4
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
5
- import { join } from 'path';
6
- import { execSync } from 'child_process';
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { join, resolve } from 'path';
7
11
  import chalk from 'chalk';
8
- import ora from 'ora';
9
12
  import { load } from 'js-yaml';
10
- export class QA360Runner {
11
- qa360Dir = join(process.cwd(), '.qa360');
12
- toolsDir = join(this.qa360Dir, 'tools');
13
- runsDir = join(this.qa360Dir, 'runs');
14
- currentRunDir = '';
15
- async run(packPath, options = {}) {
16
- const startTime = Date.now();
17
- const timestamp = new Date().toISOString();
18
- // Setup run directory
19
- this.currentRunDir = join(this.runsDir, timestamp.replace(/[:.]/g, '-'));
20
- mkdirSync(this.currentRunDir, { recursive: true });
21
- console.log(chalk.blue('🚀 QA360 Run - Exécution des tests\n'));
22
- try {
23
- // 1. Load pack configuration
24
- const pack = await this.loadPack(packPath);
25
- console.log(chalk.green(`📦 Pack chargé: ${pack.name} v${pack.version}`));
26
- // 2. Execute hooks: beforeAll
27
- await this.executeHooks(pack.hooks?.beforeAll, 'beforeAll');
28
- // 3. Execute tests
29
- const results = await this.executeTests(pack.tests, options);
30
- // 4. Execute hooks: afterAll
31
- await this.executeHooks(pack.hooks?.afterAll, 'afterAll');
32
- // 5. Generate summary
33
- const summary = this.generateSummary(results);
34
- const duration = Date.now() - startTime;
35
- const runResult = {
36
- pack,
37
- results,
38
- summary,
39
- timestamp,
40
- duration
41
- };
42
- // 6. Save run results
43
- await this.saveRunResults(runResult);
44
- // 7. Display results
45
- this.displayResults(runResult);
46
- return runResult;
47
- }
48
- catch (error) {
49
- console.error(chalk.red('❌ Erreur lors de l\'exécution:'), error);
50
- throw error;
51
- }
52
- }
53
- async loadPack(packPath) {
54
- const defaultPath = join(this.qa360Dir, 'pack.yml');
55
- const targetPath = packPath || defaultPath;
56
- if (!existsSync(targetPath)) {
57
- throw new Error(`Pack non trouvé: ${targetPath}\n💡 Utilisez 'qa360 ask' pour générer un pack`);
58
- }
59
- try {
60
- const content = readFileSync(targetPath, 'utf8');
61
- const pack = load(content);
62
- // Validation basique
63
- if (!pack.name || !pack.adapters || !pack.tests) {
64
- throw new Error('Pack invalide: champs requis manquants (name, adapters, tests)');
65
- }
66
- return pack;
67
- }
68
- catch (error) {
69
- throw new Error(`Erreur de parsing du pack: ${error}`);
70
- }
71
- }
72
- async executeHooks(hooks, phase) {
73
- if (!hooks || hooks.length === 0)
74
- return;
75
- console.log(chalk.yellow(`🔗 Exécution hooks ${phase}:`));
76
- for (const hook of hooks) {
77
- const spinner = ora(`Hook: ${hook}`).start();
78
- try {
79
- execSync(hook, {
80
- stdio: 'pipe',
81
- cwd: process.cwd(),
82
- timeout: 30000
83
- });
84
- spinner.succeed();
85
- }
86
- catch (error) {
87
- spinner.fail(`Hook échoué: ${error}`);
88
- throw error;
89
- }
90
- }
91
- }
92
- async executeTests(tests, options) {
93
- const results = [];
94
- console.log(chalk.blue(`\n🧪 Exécution de ${tests.length} test(s):\n`));
95
- for (let i = 0; i < tests.length; i++) {
96
- const test = tests[i];
97
- const spinner = ora(`[${i + 1}/${tests.length}] ${test.name}`).start();
98
- const startTime = Date.now();
99
- try {
100
- const result = await this.executeTest(test, options);
101
- result.duration = Date.now() - startTime;
102
- if (result.status === 'passed') {
103
- spinner.succeed(chalk.green(`${test.name} (${result.duration}ms)`));
104
- }
105
- else if (result.status === 'skipped') {
106
- spinner.warn(chalk.yellow(`${test.name} - Ignoré`));
107
- }
108
- else {
109
- spinner.fail(chalk.red(`${test.name} - ${result.error}`));
110
- }
111
- results.push(result);
112
- }
113
- catch (error) {
114
- const result = {
115
- name: test.name,
116
- adapter: test.adapter,
117
- status: 'failed',
118
- duration: Date.now() - startTime,
119
- error: error instanceof Error ? error.message : String(error)
120
- };
121
- spinner.fail(chalk.red(`${test.name} - Erreur: ${result.error}`));
122
- results.push(result);
123
- // En mode strict, arrêter à la première erreur
124
- if (options.mode === 'strict') {
125
- throw new Error(`Test échoué en mode strict: ${test.name}`);
126
- }
127
- }
128
- }
129
- return results;
130
- }
131
- async executeTest(test, options) {
132
- const baseResult = {
133
- name: test.name,
134
- adapter: test.adapter,
135
- status: 'failed',
136
- duration: 0,
137
- artifacts: []
138
- };
139
- switch (test.adapter) {
140
- case 'playwright':
141
- return await this.executePlaywrightTest(test, baseResult, options);
142
- case 'k6':
143
- return await this.executeK6Test(test, baseResult, options);
144
- case 'lighthouse':
145
- return await this.executeLighthouseTest(test, baseResult, options);
146
- case 'semgrep':
147
- return await this.executeSemgrepTest(test, baseResult, options);
148
- case 'zap':
149
- return await this.executeZapTest(test, baseResult, options);
150
- default:
151
- throw new Error(`Adapter non supporté: ${test.adapter}`);
152
- }
153
- }
154
- async executePlaywrightTest(test, result, options) {
155
- try {
156
- // Check if Playwright is available
157
- if (!this.isToolAvailable('playwright')) {
158
- if (options.docker) {
159
- return await this.executeInDocker(test, result);
160
- }
161
- throw new Error('Playwright non disponible. Utilisez --docker ou installez Playwright');
162
- }
163
- const config = test.config;
164
- // Simple HTTP test for API endpoints
165
- if (config.url && config.method) {
166
- // Use native fetch (Node 18+)
167
- const response = await fetch(config.url, {
168
- method: config.method || 'GET',
169
- signal: AbortSignal.timeout(test.timeout || 10000)
170
- });
171
- // Check expectations
172
- if (config.expect?.status && response.status !== config.expect.status) {
173
- throw new Error(`Status attendu: ${config.expect.status}, reçu: ${response.status}`);
174
- }
175
- if (config.expect?.responseTime) {
176
- const maxTime = parseInt(config.expect.responseTime.replace(/[^\d]/g, ''));
177
- if (result.duration > maxTime) {
178
- throw new Error(`Temps de réponse trop élevé: ${result.duration}ms > ${maxTime}ms`);
179
- }
180
- }
181
- result.status = 'passed';
182
- result.output = `HTTP ${response.status} - ${config.url}`;
183
- }
184
- else if (config.actions) {
185
- // Browser automation test (simplified)
186
- result.status = 'skipped';
187
- result.output = 'Test browser ignoré (implémentation Phase 2)';
188
- }
189
- else {
190
- throw new Error('Configuration Playwright invalide');
191
- }
192
- return result;
193
- }
194
- catch (error) {
195
- result.error = error instanceof Error ? error.message : String(error);
196
- return result;
197
- }
198
- }
199
- async executeK6Test(test, result, options) {
200
- try {
201
- if (!this.isToolAvailable('k6')) {
202
- result.status = 'skipped';
203
- result.output = 'k6 non disponible - test ignoré';
204
- return result;
205
- }
206
- // Simplified k6 test execution
207
- const config = test.config;
208
- // Basic load test simulation (using native fetch for Node 18+)
209
- const vus = config.scenarios?.load_test?.vus || 1;
210
- const duration = config.scenarios?.load_test?.duration || '10s';
211
- const promises = [];
212
- for (let i = 0; i < vus; i++) {
213
- promises.push(fetch(config.url));
214
- }
215
- await Promise.all(promises);
216
- result.status = 'passed';
217
- result.output = `Load test: ${vus} VUs pendant ${duration}`;
218
- }
219
- catch (error) {
220
- result.error = error instanceof Error ? error.message : String(error);
221
- }
222
- return result;
223
- }
224
- async executeLighthouseTest(test, result, options) {
225
- result.status = 'skipped';
226
- result.output = 'Lighthouse test ignoré (implémentation Phase 2)';
227
- return result;
228
- }
229
- async executeSemgrepTest(test, result, options) {
230
- result.status = 'skipped';
231
- result.output = 'Semgrep test ignoré (implémentation Phase 2)';
232
- return result;
233
- }
234
- async executeZapTest(test, result, options) {
235
- result.status = 'skipped';
236
- result.output = 'ZAP test ignoré (implémentation Phase 2)';
237
- return result;
238
- }
239
- async executeInDocker(test, result) {
240
- result.status = 'skipped';
241
- result.output = 'Exécution Docker ignorée (implémentation Phase 2)';
242
- return result;
243
- }
244
- isToolAvailable(tool) {
245
- try {
246
- execSync(`which ${tool}`, { stdio: 'pipe' });
247
- return true;
248
- }
249
- catch {
250
- return false;
13
+ import { Phase3Runner, PackValidator } from 'qa360-core';
14
+ /**
15
+ * Load and validate pack configuration
16
+ */
17
+ export async function loadPack(packPath) {
18
+ if (!existsSync(packPath)) {
19
+ throw new Error(`Pack file not found: ${packPath}`);
20
+ }
21
+ const content = readFileSync(packPath, 'utf-8');
22
+ const pack = load(content);
23
+ // Validate pack schema
24
+ const validator = new PackValidator();
25
+ const validation = await validator.validate(pack);
26
+ if (!validation.valid) {
27
+ const errors = validation.errors?.map(e => ` • ${e}`).join('\n');
28
+ throw new Error(`Invalid pack configuration:\n${errors}`);
29
+ }
30
+ // Show warnings if any
31
+ if (validation.warnings && validation.warnings.length > 0) {
32
+ console.log(chalk.yellow('\n⚠️ Pack warnings:'));
33
+ validation.warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
34
+ }
35
+ return pack;
36
+ }
37
+ /**
38
+ * Find pack file (search order: argument, .qa360/pack.yml, pack.yaml, pack.yml)
39
+ */
40
+ export function findPackFile(packArg) {
41
+ if (packArg) {
42
+ return resolve(packArg);
43
+ }
44
+ const searchPaths = [
45
+ join(process.cwd(), '.qa360', 'pack.yml'),
46
+ join(process.cwd(), '.qa360', 'pack.yaml'),
47
+ join(process.cwd(), 'pack.yml'),
48
+ join(process.cwd(), 'pack.yaml'),
49
+ join(process.cwd(), 'qa360.yml'),
50
+ join(process.cwd(), 'qa360.yaml'),
51
+ ];
52
+ for (const path of searchPaths) {
53
+ if (existsSync(path)) {
54
+ return path;
251
55
  }
252
56
  }
253
- generateSummary(results) {
254
- const total = results.length;
255
- const passed = results.filter(r => r.status === 'passed').length;
256
- const failed = results.filter(r => r.status === 'failed').length;
257
- const skipped = results.filter(r => r.status === 'skipped').length;
258
- const successRate = total > 0 ? Math.round((passed / total) * 100) : 0;
259
- return { total, passed, failed, skipped, successRate };
260
- }
261
- async saveRunResults(runResult) {
262
- const resultsPath = join(this.currentRunDir, 'results.json');
263
- writeFileSync(resultsPath, JSON.stringify(runResult, null, 2));
264
- console.log(chalk.gray(`\n💾 Résultats sauvegardés: ${resultsPath}`));
265
- }
266
- displayResults(runResult) {
267
- const { summary, duration } = runResult;
268
- console.log(chalk.bold('\n📊 Résumé de l\'exécution:\n'));
269
- console.log(`⏱️ Durée: ${duration}ms`);
270
- console.log(`📈 Taux de succès: ${summary.successRate}%`);
271
- console.log(chalk.green(`✅ Passés: ${summary.passed}`));
272
- console.log(chalk.red(`❌ Échoués: ${summary.failed}`));
273
- console.log(chalk.yellow(`⏭️ Ignorés: ${summary.skipped}`));
274
- console.log(`📊 Total: ${summary.total}`);
275
- if (summary.failed > 0) {
276
- console.log(chalk.red('\n❌ Tests échoués:'));
277
- runResult.results
278
- .filter(r => r.status === 'failed')
279
- .forEach(r => console.log(chalk.red(` • ${r.name}: ${r.error}`)));
57
+ throw new Error('No pack file found. Create one with:\n' +
58
+ ' qa360 ask "test my API at https://api.example.com"\n' +
59
+ 'Or specify a path:\n' +
60
+ ' qa360 run path/to/pack.yaml');
61
+ }
62
+ /**
63
+ * Display run results summary
64
+ */
65
+ export function displayResults(result) {
66
+ console.log(chalk.bold('\n' + '═'.repeat(50)));
67
+ console.log(chalk.bold('📊 EXECUTION SUMMARY'));
68
+ console.log('═'.repeat(50));
69
+ console.log(`\n⏱️ Duration: ${result.duration}ms`);
70
+ console.log(`🎯 Trust Score: ${result.summary.trustScore}%`);
71
+ console.log(`📋 Gates: ${result.summary.passed}/${result.summary.total} passed`);
72
+ if (result.proofPath) {
73
+ console.log(`\n📄 Proof: ${result.proofPath}`);
74
+ console.log(chalk.gray(' Verify with: qa360 verify ' + result.proofPath));
75
+ }
76
+ if (result.success) {
77
+ console.log(chalk.green.bold('\n✅ All quality gates passed!'));
78
+ }
79
+ else {
80
+ console.log(chalk.red.bold(`\n❌ ${result.summary.failed} gate(s) failed`));
81
+ // Show failed gates
82
+ const failed = result.gates.filter((g) => !g.success);
83
+ if (failed.length > 0) {
84
+ console.log(chalk.red('\nFailed gates:'));
85
+ failed.forEach((g) => {
86
+ console.log(chalk.red(` • ${g.gate}: ${g.error || 'Unknown error'}`));
87
+ });
280
88
  }
281
- console.log(chalk.blue('\n💡 Prochaine étape: qa360 report --sign'));
282
89
  }
90
+ console.log('═'.repeat(50) + '\n');
283
91
  }
284
- export async function runCommand(packPath, options = {}) {
285
- const runner = new QA360Runner();
92
+ /**
93
+ * Main run command
94
+ */
95
+ export async function runCommand(packArg, options = {}) {
286
96
  try {
287
- await runner.run(packPath, options);
97
+ // Step 1: Find pack file
98
+ const packPath = findPackFile(packArg);
99
+ console.log(chalk.blue(`\n📦 Loading pack: ${packPath}`));
100
+ // Step 2: Load and validate pack
101
+ const pack = await loadPack(packPath);
102
+ console.log(chalk.green(` ✅ Pack loaded: ${pack.name} v${pack.version}`));
103
+ console.log(chalk.gray(` Gates: ${pack.gates.join(', ')}`));
104
+ // Step 3: Dry run check
105
+ if (options.dryRun) {
106
+ console.log(chalk.yellow('\n🔍 Dry run mode - no tests executed'));
107
+ console.log(chalk.gray(' Remove --dry-run to execute tests'));
108
+ return;
109
+ }
110
+ // Step 4: Create and run Phase3Runner
111
+ const workingDir = process.cwd();
112
+ const outputDir = options.output
113
+ ? resolve(options.output)
114
+ : join(workingDir, '.qa360', 'runs');
115
+ const runner = new Phase3Runner({
116
+ workingDir,
117
+ pack,
118
+ outputDir,
119
+ });
120
+ const result = await runner.run();
121
+ // Step 5: Display results
122
+ displayResults(result);
123
+ // Step 6: Exit with appropriate code
124
+ process.exit(result.success ? 0 : 1);
288
125
  }
289
126
  catch (error) {
290
- console.error(chalk.red('❌ Échec de l\'exécution:'), error);
127
+ console.error(chalk.red('\nExecution failed:'));
128
+ console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
129
+ if (options.verbose && error instanceof Error && error.stack) {
130
+ console.error(chalk.gray('\nStack trace:'));
131
+ console.error(chalk.gray(error.stack));
132
+ }
291
133
  process.exit(1);
292
134
  }
293
135
  }
136
+ // Export for CLI integration
137
+ export default runCommand;
@@ -130,7 +130,7 @@ export class QA360Secrets {
130
130
  type: 'list',
131
131
  name: 'secretName',
132
132
  message: 'Secret à supprimer:',
133
- choices: secrets.map((s) => s.name)
133
+ choices: secrets.map(s => s.name)
134
134
  }]);
135
135
  name = selectAnswer.secretName;
136
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAiGD;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6EjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqGD;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAgDjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6EjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
@@ -14,7 +14,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
14
14
  import { join, resolve, basename } from 'path';
15
15
  import chalk from 'chalk';
16
16
  // Import from core
17
- import { verifyProofFile, VerificationCode, } from 'qa360-core';
17
+ import { verifyProofFile, verifyPhase3Proof, VerificationCode, } from 'qa360-core';
18
18
  /**
19
19
  * Format verification result for human-readable output
20
20
  */
@@ -83,12 +83,17 @@ function findProofFiles(dirPath) {
83
83
  for (const entry of entries) {
84
84
  const fullPath = join(dirPath, entry);
85
85
  const stat = statSync(fullPath);
86
- if (stat.isFile() && entry.endsWith('.json')) {
87
- // Check if it looks like a proof bundle
86
+ if (stat.isFile() && entry.endsWith('-proof.json')) {
87
+ files.push(fullPath);
88
+ }
89
+ else if (stat.isFile() && entry.endsWith('.json')) {
90
+ // Check if it looks like a proof bundle (either format)
88
91
  try {
89
92
  const content = readFileSync(fullPath, 'utf-8');
90
93
  const json = JSON.parse(content);
91
- if (json.spec === 'qa360.proof.v1') {
94
+ // Support both RFC format (spec) and Phase3 format (version + signature)
95
+ if (json.spec === 'qa360.proof.v1' ||
96
+ (json.version && json.signature && json.runId)) {
92
97
  files.push(fullPath);
93
98
  }
94
99
  }
@@ -113,14 +118,32 @@ export async function verifySingle(filePath, options = {}) {
113
118
  return 1;
114
119
  }
115
120
  try {
116
- const result = await verifyProofFile(absolutePath);
121
+ // Detect proof format
122
+ const content = readFileSync(absolutePath, 'utf-8');
123
+ const json = JSON.parse(content);
124
+ let result;
125
+ if (json.spec === 'qa360.proof.v1') {
126
+ // RFC format
127
+ result = await verifyProofFile(absolutePath);
128
+ }
129
+ else if (json.version && json.signature && json.runId) {
130
+ // Phase3 format
131
+ result = await verifyPhase3Proof(absolutePath);
132
+ }
133
+ else {
134
+ result = {
135
+ valid: false,
136
+ code: VerificationCode.PROOF_MALFORMED,
137
+ message: 'Unknown proof format',
138
+ };
139
+ }
117
140
  if (options.json) {
118
141
  formatJSON(result);
119
142
  }
120
143
  else {
121
144
  formatResult(result, filePath);
122
145
  }
123
- return result.code;
146
+ return result.valid ? 0 : result.code;
124
147
  }
125
148
  catch (error) {
126
149
  if (options.json) {
package/dist/index.js CHANGED
@@ -7,14 +7,21 @@
7
7
  */
8
8
  import { Command } from 'commander';
9
9
  import chalk from 'chalk';
10
- import { version } from '../package.json';
11
- import { doctorCommand } from './commands/doctor';
12
- import { askCommand } from './commands/ask';
13
- import { runCommand } from './commands/run';
14
- import { reportCommand } from './commands/report';
15
- import { verifyCommand } from './commands/verify';
16
- import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack';
17
- import { secretsAddCommand, secretsListCommand, secretsRemoveCommand, secretsDoctorCommand, secretsExportCommand } from './commands/secrets';
10
+ import { readFileSync } from 'fs';
11
+ import { join, dirname } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
16
+ const version = packageJson.version;
17
+ import { doctorCommand } from './commands/doctor.js';
18
+ import { askCommand } from './commands/ask.js';
19
+ import { runCommand } from './commands/run.js';
20
+ import { reportCommand } from './commands/report.js';
21
+ import { verifyCommand } from './commands/verify.js';
22
+ import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js';
23
+ import { secretsAddCommand, secretsListCommand, secretsRemoveCommand, secretsDoctorCommand, secretsExportCommand } from './commands/secrets.js';
24
+ import { createHistoryCommands } from './commands/history.js';
18
25
  const program = new Command();
19
26
  program
20
27
  .name('qa360')
@@ -187,6 +194,8 @@ program
187
194
  console.log(chalk.blue('📦 QA360 Proof Bundle'));
188
195
  console.log(chalk.yellow('⚠️ Phase 3 implementation coming soon...'));
189
196
  });
197
+ // History commands (Evidence Vault)
198
+ program.addCommand(createHistoryCommands());
190
199
  // Show banner
191
200
  console.log(chalk.bold.blue('QA360 Core v' + version));
192
201
  console.log(chalk.gray('Transform software testing into verifiable, signed, and traceable proofs\n'));