qa360 1.4.3 → 1.4.5

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.
@@ -23,7 +23,6 @@ export interface DoctorResult {
23
23
  }
24
24
  export declare class QA360Doctor {
25
25
  private qa360Dir;
26
- private toolsDir;
27
26
  private keysDir;
28
27
  private runsDir;
29
28
  diagnose(options?: {
@@ -40,7 +39,6 @@ export declare class QA360Doctor {
40
39
  private checkSecrets;
41
40
  private checkCryptoKeys;
42
41
  private checkProofKeys;
43
- private checkProofsDirectory;
44
42
  private checkProofRoundtrip;
45
43
  private performFixes;
46
44
  private generateResult;
@@ -2,13 +2,12 @@
2
2
  * QA360 Doctor Command - Environment diagnostic and auto-fix
3
3
  */
4
4
  import { execSync } from 'child_process';
5
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
5
+ import { existsSync, mkdirSync } from 'fs';
6
6
  import { join } from 'path';
7
7
  import chalk from 'chalk';
8
8
  import ora from 'ora';
9
9
  export class QA360Doctor {
10
10
  qa360Dir = process.env.QA360_HOME || join(process.cwd(), '.qa360');
11
- toolsDir = join(this.qa360Dir, 'tools');
12
11
  keysDir = join(this.qa360Dir, 'keys');
13
12
  runsDir = join(this.qa360Dir, 'runs');
14
13
  async diagnose(options = {}) {
@@ -74,7 +73,6 @@ export class QA360Doctor {
74
73
  checks.push(await this.checkCryptoKeys());
75
74
  // 7. Proof System (Phase 2)
76
75
  checks.push(await this.checkProofKeys());
77
- checks.push(await this.checkProofsDirectory());
78
76
  checks.push(await this.checkProofRoundtrip());
79
77
  return checks;
80
78
  }
@@ -111,7 +109,8 @@ export class QA360Doctor {
111
109
  }
112
110
  }
113
111
  async checkDirectories() {
114
- const dirs = [this.qa360Dir, this.toolsDir, this.keysDir, this.runsDir];
112
+ // Only check essential directories - tools/ is deprecated
113
+ const dirs = [this.qa360Dir, this.keysDir, this.runsDir];
115
114
  const missing = dirs.filter(dir => !existsSync(dir));
116
115
  if (missing.length === 0) {
117
116
  return {
@@ -193,7 +192,9 @@ export class QA360Doctor {
193
192
  }
194
193
  }
195
194
  async checkSecrets() {
196
- const secretsFile = join(this.qa360Dir, 'secrets.json');
195
+ const secretsFile = join(this.qa360Dir, 'secrets.enc');
196
+ const oldSecretsFile = join(this.qa360Dir, 'secrets.json');
197
+ // Secrets are optional - this is informational only
197
198
  if (existsSync(secretsFile)) {
198
199
  return {
199
200
  id: 'gestion_secrets',
@@ -202,26 +203,33 @@ export class QA360Doctor {
202
203
  message: 'Fichier secrets configuré ✅'
203
204
  };
204
205
  }
206
+ // Old secrets file exists but not new one
207
+ if (existsSync(oldSecretsFile)) {
208
+ return {
209
+ id: 'gestion_secrets',
210
+ name: 'Gestion Secrets',
211
+ status: 'ok',
212
+ message: 'Fichier secrets (ancien format) configuré ℹ️'
213
+ };
214
+ }
215
+ // No secrets file is OK - secrets are optional
205
216
  return {
206
217
  id: 'gestion_secrets',
207
218
  name: 'Gestion Secrets',
208
- status: 'warning',
209
- message: 'Aucun fichier secrets configuré',
210
- code: 'QX006',
211
- fix: async () => {
212
- const defaultSecrets = {
213
- version: '1.0.0',
214
- encrypted: false,
215
- secrets: {}
216
- };
217
- writeFileSync(secretsFile, JSON.stringify(defaultSecrets, null, 2));
218
- }
219
+ status: 'ok',
220
+ message: 'Aucun fichier secrets (optionnel) ℹ️'
219
221
  };
220
222
  }
221
223
  async checkCryptoKeys() {
222
- const privateKeyFile = join(this.keysDir, 'qa360.key');
223
- const publicKeyFile = join(this.keysDir, 'qa360.pub');
224
- if (existsSync(privateKeyFile) && existsSync(publicKeyFile)) {
224
+ // Updated to use new key filenames (ed25519.key / ed25519.pub)
225
+ const privateKeyFile = join(this.keysDir, 'ed25519.key');
226
+ const publicKeyFile = join(this.keysDir, 'ed25519.pub');
227
+ // Also check for old filenames for backward compatibility
228
+ const oldPrivateKeyFile = join(this.keysDir, 'qa360.key');
229
+ const oldPublicKeyFile = join(this.keysDir, 'qa360.pub');
230
+ const hasNewKeys = existsSync(privateKeyFile) && existsSync(publicKeyFile);
231
+ const hasOldKeys = existsSync(oldPrivateKeyFile) && existsSync(oldPublicKeyFile);
232
+ if (hasNewKeys) {
225
233
  return {
226
234
  id: 'crypto_keys',
227
235
  name: 'Clés Cryptographiques',
@@ -229,24 +237,20 @@ export class QA360Doctor {
229
237
  message: 'Paire de clés Ed25519 présente ✅'
230
238
  };
231
239
  }
240
+ if (hasOldKeys) {
241
+ return {
242
+ id: 'crypto_keys',
243
+ name: 'Clés Cryptographiques',
244
+ status: 'ok',
245
+ message: 'Paire de clés Ed25519 présente (ancien format) ℹ️'
246
+ };
247
+ }
248
+ // Note: Keys are auto-generated by ensureProofKeys, so this is just informational
232
249
  return {
233
250
  id: 'crypto_keys',
234
251
  name: 'Clés Cryptographiques',
235
- status: 'warning',
236
- message: 'Paire de clés Ed25519 manquante',
237
- code: 'QX007',
238
- fix: async () => {
239
- const { generateKeyPair } = await import('crypto');
240
- generateKeyPair('ed25519', {
241
- publicKeyEncoding: { type: 'spki', format: 'pem' },
242
- privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
243
- }, (err, publicKey, privateKey) => {
244
- if (err)
245
- throw err;
246
- writeFileSync(publicKeyFile, publicKey);
247
- writeFileSync(privateKeyFile, privateKey);
248
- });
249
- }
252
+ status: 'ok',
253
+ message: 'Clés Ed25519 seront générées automatiquement ℹ️'
250
254
  };
251
255
  }
252
256
  async checkProofKeys() {
@@ -294,38 +298,6 @@ export class QA360Doctor {
294
298
  };
295
299
  }
296
300
  }
297
- async checkProofsDirectory() {
298
- const proofsDir = join(this.qa360Dir, 'proofs');
299
- try {
300
- if (existsSync(proofsDir)) {
301
- return {
302
- id: 'proofs_directory',
303
- name: 'Proofs Directory',
304
- status: 'ok',
305
- message: `Proofs directory exists (${proofsDir}) ✅`
306
- };
307
- }
308
- return {
309
- id: 'proofs_directory',
310
- name: 'Proofs Directory',
311
- status: 'warning',
312
- message: 'Proofs directory missing',
313
- code: 'D004',
314
- fix: async () => {
315
- mkdirSync(proofsDir, { recursive: true, mode: 0o755 });
316
- }
317
- };
318
- }
319
- catch (error) {
320
- return {
321
- id: 'proofs_directory',
322
- name: 'Proofs Directory',
323
- status: 'error',
324
- message: `Cannot create proofs directory: ${error instanceof Error ? error.message : 'Unknown error'}`,
325
- code: 'D004'
326
- };
327
- }
328
- }
329
301
  async checkProofRoundtrip() {
330
302
  try {
331
303
  const coreModule = await import('../core/index.js');
@@ -9,6 +9,7 @@ export interface HistoryListOptions {
9
9
  gate?: string;
10
10
  since?: string;
11
11
  json?: boolean;
12
+ format?: string;
12
13
  }
13
14
  export interface HistoryGetOptions {
14
15
  json?: boolean;
@@ -39,7 +39,8 @@ export class QA360History {
39
39
  filters.since = new Date(options.since);
40
40
  }
41
41
  const runs = await vault.listRuns(filters);
42
- if (options.json) {
42
+ // Support both --json and --format json
43
+ if (options.json || options.format === 'json') {
43
44
  console.log(JSON.stringify(runs, null, 2));
44
45
  return;
45
46
  }
@@ -72,14 +73,21 @@ export class QA360History {
72
73
  */
73
74
  async get(runId, options) {
74
75
  const vault = await this.getVault();
75
- const run = await vault.getRun(runId);
76
+ // Try exact match first, then prefix match for shortened IDs
77
+ let run = await vault.getRun(runId);
78
+ if (!run && runId.length < 36) {
79
+ // Try prefix search for shortened IDs (e.g., "6ce422f7")
80
+ run = await vault.getRunByPrefix(runId);
81
+ }
76
82
  if (!run) {
77
- throw new Error(`Run not found: ${runId}`);
83
+ throw new Error(`Run not found: ${runId}${runId.length < 36 ? ' (try using the full UUID from "qa360 history list")' : ''}`);
78
84
  }
85
+ // Use the full UUID from the found run for subsequent lookups
86
+ const fullRunId = run.id;
79
87
  const [gates, findings, artifacts] = await Promise.all([
80
- vault.getGates(runId),
81
- vault.getFindings(runId),
82
- vault.getRunArtifacts(runId)
88
+ vault.getGates(fullRunId),
89
+ vault.getFindings(fullRunId),
90
+ vault.getRunArtifacts(fullRunId)
83
91
  ]);
84
92
  const runDetails = {
85
93
  run,
@@ -98,7 +106,7 @@ export class QA360History {
98
106
  return;
99
107
  }
100
108
  // Display formatted output
101
- console.log(chalk.bold(`\n🔍 Run Details: ${runId}\n`));
109
+ console.log(chalk.bold(`\n🔍 Run Details: ${fullRunId}\n`));
102
110
  console.log(chalk.blue('📊 Overview:'));
103
111
  console.log(` Status: ${this.formatStatus(run.status)}`);
104
112
  console.log(` Trust Score: ${run.trust_score ? `${run.trust_score}%` : 'Not calculated'}`);
@@ -560,7 +568,8 @@ export function createHistoryCommands() {
560
568
  .option('-s, --status <status>', 'Filter by status (passed|failed|cancelled|error)')
561
569
  .option('-g, --gate <gate>', 'Filter by gate name')
562
570
  .option('--since <date>', 'Show runs since date (ISO format)')
563
- .option('--json', 'Output as JSON')
571
+ .option('--json', 'Output as JSON (alias for --format json)')
572
+ .option('--format <format>', 'Output format (json|table)', 'table')
564
573
  .action(async (options) => {
565
574
  try {
566
575
  await history.list(options);
@@ -140,6 +140,11 @@ export declare class EvidenceVault {
140
140
  * Get run by ID
141
141
  */
142
142
  getRun(runId: string): Promise<RunRecord | null>;
143
+ /**
144
+ * Get run by ID prefix (e.g., "6ce422f7" for shortened display)
145
+ * Returns null if multiple or no runs match the prefix
146
+ */
147
+ getRunByPrefix(prefix: string): Promise<RunRecord | null>;
143
148
  /**
144
149
  * Get run by run_key
145
150
  */
@@ -169,6 +169,22 @@ export class EvidenceVault {
169
169
  });
170
170
  });
171
171
  }
172
+ /**
173
+ * Get run by ID prefix (e.g., "6ce422f7" for shortened display)
174
+ * Returns null if multiple or no runs match the prefix
175
+ */
176
+ async getRunByPrefix(prefix) {
177
+ const allRuns = await this.listRuns({});
178
+ const matching = allRuns.filter(r => r.id.startsWith(prefix));
179
+ if (matching.length === 0) {
180
+ return null;
181
+ }
182
+ if (matching.length > 1) {
183
+ // Ambiguous prefix - return null to indicate error
184
+ return null;
185
+ }
186
+ return matching[0];
187
+ }
172
188
  /**
173
189
  * Get run by run_key
174
190
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qa360",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "QA360 Proof CLI - Quality as Cryptographic Proof",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@playwright/test": "^1.49.0",
35
+ "adm-zip": "^0.5.16",
35
36
  "ajv": "^8.17.1",
36
37
  "ajv-draft-04": "^1.0.0",
37
38
  "ajv-formats": "^2.1.1",
@@ -48,7 +49,6 @@
48
49
  "@types/adm-zip": "^0.5.7",
49
50
  "@types/inquirer": "^9.0.9",
50
51
  "@vitest/coverage-v8": "1.6.0",
51
- "adm-zip": "^0.5.16",
52
52
  "tsx": "^4.0.0",
53
53
  "vitest": "1.6.0"
54
54
  },