qa360 2.2.14 → 2.2.18

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 (50) hide show
  1. package/cli/dist/commands/ai.js +1 -1
  2. package/cli/dist/commands/ask.js +362 -36
  3. package/cli/dist/commands/coverage.js +1 -1
  4. package/cli/dist/commands/crawl.js +1 -1
  5. package/cli/dist/commands/doctor.js +2 -2
  6. package/cli/dist/commands/explain.js +2 -2
  7. package/cli/dist/commands/flakiness.js +1 -1
  8. package/cli/dist/commands/generate.js +1 -1
  9. package/cli/dist/commands/history.js +1 -1
  10. package/cli/dist/commands/monitor.js +3 -3
  11. package/cli/dist/commands/ollama.js +1 -1
  12. package/cli/dist/commands/pack.js +2 -2
  13. package/cli/dist/commands/regression.js +1 -1
  14. package/cli/dist/commands/repair.js +1 -1
  15. package/cli/dist/commands/retry.js +1 -1
  16. package/cli/dist/commands/run.d.ts +1 -1
  17. package/cli/dist/commands/run.js +2 -1
  18. package/cli/dist/commands/secrets.js +1 -1
  19. package/cli/dist/commands/serve.js +1 -1
  20. package/cli/dist/commands/slo.js +1 -1
  21. package/cli/dist/commands/verify.js +1 -1
  22. package/cli/dist/core/ai/ollama-provider.js +3 -15
  23. package/cli/dist/core/core/coverage/analyzer.d.ts +101 -0
  24. package/cli/dist/core/core/coverage/analyzer.js +415 -0
  25. package/cli/dist/core/core/coverage/collector.d.ts +74 -0
  26. package/cli/dist/core/core/coverage/collector.js +459 -0
  27. package/cli/dist/core/core/coverage/config.d.ts +37 -0
  28. package/cli/dist/core/core/coverage/config.js +156 -0
  29. package/cli/dist/core/core/coverage/index.d.ts +11 -0
  30. package/cli/dist/core/core/coverage/index.js +15 -0
  31. package/cli/dist/core/core/coverage/types.d.ts +267 -0
  32. package/cli/dist/core/core/coverage/types.js +6 -0
  33. package/cli/dist/core/core/coverage/vault.d.ts +95 -0
  34. package/cli/dist/core/core/coverage/vault.js +405 -0
  35. package/cli/dist/core/crawler/selector-generator.js +3 -72
  36. package/cli/dist/core/generation/crawler-pack-generator.d.ts +1 -1
  37. package/cli/dist/core/generation/crawler-pack-generator.js +143 -31
  38. package/cli/dist/core/pack/validator.js +2 -2
  39. package/cli/dist/core/pack-v2/migrator.d.ts +0 -5
  40. package/cli/dist/core/pack-v2/migrator.js +6 -74
  41. package/cli/dist/core/pack-v2/validator.js +3 -4
  42. package/cli/dist/core/runner/phase3-runner.js +1 -12
  43. package/cli/dist/utils/config.d.ts +1 -1
  44. package/cli/dist/utils/config.js +11 -5
  45. package/package.json +24 -30
  46. package/cli/CHANGELOG.md +0 -84
  47. package/cli/LICENSE +0 -24
  48. package/cli/package.json +0 -76
  49. package/core/LICENSE +0 -24
  50. package/core/package.json +0 -81
@@ -10,7 +10,7 @@ import { Command } from 'commander';
10
10
  import chalk from 'chalk';
11
11
  import ora from 'ora';
12
12
  import Table from 'cli-table3';
13
- import { createBest, getProviderInfo, getProviderStatus, createLLMProvider, DeepSeekProvider, DeepSeekError, OllamaProvider, OllamaError, OpenAIProvider, OpenAIError, AnthropicProvider, AnthropicError } from '../core/index.js';
13
+ import { createBest, getProviderInfo, getProviderStatus, createLLMProvider, DeepSeekProvider, DeepSeekError, OllamaProvider, OllamaError, OpenAIProvider, OpenAIError, AnthropicProvider, AnthropicError } from 'qa360-core';
14
14
  /**
15
15
  * List all available AI providers
16
16
  */
@@ -49,9 +49,136 @@ export class QA360Ask {
49
49
  name: 'target',
50
50
  message: 'URL ou endpoint cible:',
51
51
  validate: (input) => input.length > 0 || 'URL requise'
52
+ },
53
+ {
54
+ type: 'confirm',
55
+ name: 'needsAuth',
56
+ message: 'L\'API require-t-elle une authentification?',
57
+ default: false
52
58
  }
53
59
  ]);
54
- return this.generatePackFromAnswers(answers);
60
+ // Ask for authentication details if needed
61
+ let authConfig;
62
+ if (answers.needsAuth) {
63
+ authConfig = await inquirer.prompt([
64
+ {
65
+ type: 'list',
66
+ name: 'type',
67
+ message: 'Type d\'authentification:',
68
+ choices: [
69
+ { name: 'Bearer Token (JWT, OAuth2)', value: 'bearer' },
70
+ { name: 'API Key (x-api-key, etc.)', value: 'api_key' },
71
+ { name: 'Basic Auth (username/password)', value: 'basic' },
72
+ { name: 'Custom Header', value: 'custom' }
73
+ ]
74
+ },
75
+ {
76
+ type: 'input',
77
+ name: 'bearerToken',
78
+ message: 'Bearer Token (laissez vide pour utiliser variable):',
79
+ default: '${API_TOKEN}',
80
+ when: (answers) => answers.type === 'bearer'
81
+ },
82
+ {
83
+ type: 'input',
84
+ name: 'headerName',
85
+ message: 'Nom du header (ex: x-api-key):',
86
+ default: 'x-api-key',
87
+ when: (answers) => answers.type === 'api_key' || answers.type === 'custom'
88
+ },
89
+ {
90
+ type: 'input',
91
+ name: 'tokenPrefix',
92
+ message: 'Prefix du token (ex: "Bearer " pour "Bearer xxx"):',
93
+ default: 'Bearer ',
94
+ when: (answers) => answers.type === 'custom'
95
+ },
96
+ {
97
+ type: 'input',
98
+ name: 'apiKey',
99
+ message: 'Valeur de la cle API (laissez vide pour variable):',
100
+ default: '${API_KEY}',
101
+ when: (answers) => answers.type === 'api_key'
102
+ },
103
+ {
104
+ type: 'input',
105
+ name: 'username',
106
+ message: 'Nom d\'utilisateur:',
107
+ when: (answers) => answers.type === 'basic'
108
+ },
109
+ {
110
+ type: 'password',
111
+ name: 'password',
112
+ message: 'Mot de passe:',
113
+ mask: '*',
114
+ when: (answers) => answers.type === 'basic'
115
+ }
116
+ ]);
117
+ }
118
+ // Ask for specific endpoints if API type
119
+ let endpoints = [];
120
+ if (answers.type === 'api' || answers.type === 'complete') {
121
+ const addEndpoints = await inquirer.prompt([
122
+ {
123
+ type: 'confirm',
124
+ name: 'addEndpoints',
125
+ message: 'Voulez-vous specifier des endpoints specifiques a tester?',
126
+ default: true
127
+ }
128
+ ]);
129
+ if (addEndpoints.addEndpoints) {
130
+ let adding = true;
131
+ while (adding) {
132
+ const endpoint = await inquirer.prompt([
133
+ {
134
+ type: 'list',
135
+ name: 'method',
136
+ message: 'Methode HTTP:',
137
+ choices: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
138
+ default: 'GET'
139
+ },
140
+ {
141
+ type: 'input',
142
+ name: 'path',
143
+ message: 'Chemin (ex: /api/users):',
144
+ validate: (input) => input.length > 0 || 'Chemin requis'
145
+ },
146
+ {
147
+ type: 'number',
148
+ name: 'expectedStatus',
149
+ message: 'Code HTTP attendu:',
150
+ default: 200
151
+ },
152
+ {
153
+ type: 'confirm',
154
+ name: 'addBodyValidation',
155
+ message: 'Voulez-vous valider le corps de la reponse?',
156
+ default: false
157
+ },
158
+ {
159
+ type: 'input',
160
+ name: 'bodyValidation',
161
+ message: 'Champ JSON a valider (ex: data.users.0.email, ou "status:ok" pour valeur exacte):',
162
+ when: (answers) => answers.addBodyValidation
163
+ },
164
+ {
165
+ type: 'confirm',
166
+ name: 'addAnother',
167
+ message: 'Ajouter un autre endpoint?',
168
+ default: false
169
+ }
170
+ ]);
171
+ endpoints.push({
172
+ method: endpoint.method,
173
+ path: endpoint.path,
174
+ expectedStatus: endpoint.expectedStatus,
175
+ bodyValidation: endpoint.bodyValidation
176
+ });
177
+ adding = endpoint.addAnother;
178
+ }
179
+ }
180
+ }
181
+ return this.generatePackFromAnswers({ ...answers, authConfig, endpoints });
55
182
  }
56
183
  async parseNaturalLanguage(query) {
57
184
  console.log(chalk.gray(`Analyse: "${query}"`));
@@ -101,36 +228,77 @@ export class QA360Ask {
101
228
  const baseUrl = `\${BASE_URL:-${answers.target}}`;
102
229
  switch (answers.type) {
103
230
  case 'api':
104
- return this.generateApiPack(answers.name, answers.description, baseUrl);
231
+ return this.generateApiPack(answers.name, answers.description, baseUrl, answers.authConfig, answers.endpoints);
105
232
  case 'web':
106
- return this.generateWebPack(answers.name, answers.description, baseUrl);
233
+ return this.generateWebPack(answers.name, answers.description, baseUrl, answers.authConfig);
107
234
  case 'performance':
108
- return this.generatePerformancePack(answers.name, answers.description, baseUrl);
235
+ return this.generatePerformancePack(answers.name, answers.description, baseUrl, answers.authConfig);
109
236
  case 'security':
110
- return this.generateSecurityPack(answers.name, answers.description, baseUrl);
237
+ return this.generateSecurityPack(answers.name, answers.description, baseUrl, answers.authConfig);
111
238
  case 'accessibility':
112
- return this.generateAccessibilityPack(answers.name, answers.description, baseUrl);
239
+ return this.generateAccessibilityPack(answers.name, answers.description, baseUrl, answers.authConfig);
113
240
  case 'complete':
114
- return this.generateCompletePack(answers.name, answers.description, baseUrl);
241
+ return this.generateCompletePack(answers.name, answers.description, baseUrl, answers.authConfig, answers.endpoints);
115
242
  default:
116
- return this.generateApiPack(answers.name, answers.description, baseUrl);
243
+ return this.generateApiPack(answers.name, answers.description, baseUrl, answers.authConfig, answers.endpoints);
117
244
  }
118
245
  }
119
- generateApiPack(name, description, baseUrl) {
246
+ generateApiPack(name, description, baseUrl, authConfig, endpoints) {
247
+ // Build smoke tests from provided endpoints or use defaults
248
+ let smokeTests;
249
+ let requests;
250
+ if (endpoints && endpoints.length > 0) {
251
+ // User provided specific endpoints - use both smoke format and request format with validation
252
+ smokeTests = endpoints.map(e => `${e.method} ${e.path} -> ${e.expectedStatus}`);
253
+ // Also build request format with response validation
254
+ requests = endpoints.map(e => {
255
+ const request = {
256
+ method: e.method,
257
+ path: e.path,
258
+ expect: {
259
+ status: e.expectedStatus
260
+ }
261
+ };
262
+ // Add body validation if specified
263
+ if (e.bodyValidation) {
264
+ // Parse body validation - could be "field:value" or "field.nested.path:value"
265
+ const colonIndex = e.bodyValidation.indexOf(':');
266
+ if (colonIndex !== -1) {
267
+ const fieldPath = e.bodyValidation.substring(0, colonIndex);
268
+ const expectedValue = e.bodyValidation.substring(colonIndex + 1);
269
+ request.expect.body = parseBodyValidation(fieldPath, expectedValue);
270
+ }
271
+ else {
272
+ // If no colon, just check field exists
273
+ request.expect.body = parseBodyValidation(e.bodyValidation, null);
274
+ }
275
+ }
276
+ return request;
277
+ });
278
+ }
279
+ else {
280
+ // Use default generic endpoints
281
+ smokeTests = [
282
+ 'GET /health -> 200',
283
+ 'GET /status -> 200',
284
+ ];
285
+ }
286
+ // Build auth config
287
+ const auth = buildAuthConfig(authConfig);
120
288
  return {
121
289
  version: 2,
122
290
  name,
123
291
  description,
292
+ auth: auth.config,
124
293
  gates: {
125
294
  'api-health': {
126
295
  adapter: 'playwright-api',
127
296
  enabled: true,
297
+ auth: auth.profileName,
128
298
  config: {
129
299
  baseUrl,
130
- smoke: [
131
- 'GET /health -> 200',
132
- 'GET /status -> 200',
133
- ]
300
+ smoke: smokeTests,
301
+ ...(requests && requests.length > 0 ? { requests } : {})
134
302
  },
135
303
  options: {
136
304
  timeout: 10000,
@@ -146,15 +314,18 @@ export class QA360Ask {
146
314
  }
147
315
  };
148
316
  }
149
- generateWebPack(name, description, baseUrl) {
317
+ generateWebPack(name, description, baseUrl, authConfig) {
318
+ const auth = buildAuthConfig(authConfig);
150
319
  return {
151
320
  version: 2,
152
321
  name,
153
322
  description,
323
+ auth: auth.config,
154
324
  gates: {
155
325
  'ui-smoke': {
156
326
  adapter: 'playwright-ui',
157
327
  enabled: true,
328
+ auth: auth.profileName,
158
329
  config: {
159
330
  baseUrl,
160
331
  pages: [
@@ -190,15 +361,18 @@ export class QA360Ask {
190
361
  }
191
362
  };
192
363
  }
193
- generatePerformancePack(name, description, baseUrl) {
364
+ generatePerformancePack(name, description, baseUrl, authConfig) {
365
+ const auth = buildAuthConfig(authConfig);
194
366
  return {
195
367
  version: 2,
196
368
  name,
197
369
  description,
370
+ auth: auth.config,
198
371
  gates: {
199
372
  'api-health': {
200
373
  adapter: 'playwright-api',
201
374
  enabled: true,
375
+ auth: auth.profileName,
202
376
  config: {
203
377
  baseUrl,
204
378
  smoke: ['GET /health -> 200']
@@ -242,15 +416,18 @@ export default function () {
242
416
  }
243
417
  };
244
418
  }
245
- generateSecurityPack(name, description, baseUrl) {
419
+ generateSecurityPack(name, description, baseUrl, authConfig) {
420
+ const auth = buildAuthConfig(authConfig);
246
421
  return {
247
422
  version: 2,
248
423
  name,
249
424
  description,
425
+ auth: auth.config,
250
426
  gates: {
251
427
  'api-health': {
252
428
  adapter: 'playwright-api',
253
429
  enabled: true,
430
+ auth: auth.profileName,
254
431
  config: {
255
432
  baseUrl,
256
433
  smoke: ['GET / -> 200']
@@ -301,15 +478,18 @@ export default function () {
301
478
  }
302
479
  };
303
480
  }
304
- generateAccessibilityPack(name, description, baseUrl) {
481
+ generateAccessibilityPack(name, description, baseUrl, authConfig) {
482
+ const auth = buildAuthConfig(authConfig);
305
483
  return {
306
484
  version: 2,
307
485
  name,
308
486
  description,
487
+ auth: auth.config,
309
488
  gates: {
310
489
  'ui-smoke': {
311
490
  adapter: 'playwright-ui',
312
491
  enabled: true,
492
+ auth: auth.profileName,
313
493
  config: {
314
494
  baseUrl,
315
495
  pages: [
@@ -343,34 +523,64 @@ export default function () {
343
523
  }
344
524
  };
345
525
  }
346
- generateCompletePack(name, description, baseUrl) {
526
+ generateCompletePack(name, description, baseUrl, authConfig, endpoints) {
527
+ const auth = buildAuthConfig(authConfig);
528
+ // Build smoke tests from provided endpoints or use defaults
529
+ let apiSmokeTests;
530
+ let apiCrudTests;
531
+ let requests;
532
+ if (endpoints && endpoints.length > 0) {
533
+ // User provided specific endpoints
534
+ apiSmokeTests = endpoints.filter(e => e.method === 'GET').map(e => `${e.method} ${e.path} -> ${e.expectedStatus}`);
535
+ apiCrudTests = endpoints.map(e => `${e.method} ${e.path} -> ${e.expectedStatus}`);
536
+ // Build request format with response validation
537
+ requests = endpoints.map(e => {
538
+ const request = {
539
+ method: e.method,
540
+ path: e.path,
541
+ expect: {
542
+ status: e.expectedStatus
543
+ }
544
+ };
545
+ if (e.bodyValidation) {
546
+ const colonIndex = e.bodyValidation.indexOf(':');
547
+ if (colonIndex !== -1) {
548
+ const fieldPath = e.bodyValidation.substring(0, colonIndex);
549
+ const expectedValue = e.bodyValidation.substring(colonIndex + 1);
550
+ request.expect.body = parseBodyValidation(fieldPath, expectedValue);
551
+ }
552
+ else {
553
+ request.expect.body = parseBodyValidation(e.bodyValidation, null);
554
+ }
555
+ }
556
+ return request;
557
+ });
558
+ }
559
+ else {
560
+ // Use default generic endpoints
561
+ apiSmokeTests = ['GET /health -> 200', 'GET /api/v1/status -> 200'];
562
+ apiCrudTests = [
563
+ 'GET /api/v1/users -> 200',
564
+ 'POST /api/v1/users -> 201',
565
+ 'PUT /api/v1/users/1 -> 200',
566
+ 'DELETE /api/v1/users/1 -> 204'
567
+ ];
568
+ }
347
569
  return {
348
570
  version: 2,
349
571
  name,
350
572
  description,
351
- auth: {
352
- api: 'default-bearer',
353
- profiles: {
354
- 'default-bearer': {
355
- type: 'bearer',
356
- config: {
357
- token: '${API_TOKEN:-your-token-here}'
358
- }
359
- }
360
- }
361
- },
573
+ auth: auth.config,
362
574
  gates: {
363
575
  // API Health Check
364
576
  'api-health': {
365
577
  adapter: 'playwright-api',
366
578
  enabled: true,
367
- auth: 'default-bearer',
579
+ auth: auth.profileName,
368
580
  config: {
369
581
  baseUrl,
370
- smoke: [
371
- 'GET /health -> 200',
372
- 'GET /api/v1/status -> 200'
373
- ]
582
+ smoke: apiSmokeTests.length > 0 ? apiSmokeTests : ['GET /health -> 200', 'GET /api/v1/status -> 200'],
583
+ ...(requests && requests.length > 0 ? { requests } : {})
374
584
  },
375
585
  options: {
376
586
  timeout: 5000,
@@ -381,10 +591,10 @@ export default function () {
381
591
  'api-crud': {
382
592
  adapter: 'playwright-api',
383
593
  enabled: true,
384
- auth: 'default-bearer',
594
+ auth: auth.profileName,
385
595
  config: {
386
596
  baseUrl,
387
- smoke: [
597
+ smoke: apiCrudTests.length > 0 ? apiCrudTests : [
388
598
  'GET /api/v1/users -> 200',
389
599
  'POST /api/v1/users -> 201',
390
600
  'PUT /api/v1/users/1 -> 200',
@@ -566,6 +776,122 @@ export default function () {
566
776
  }
567
777
  }
568
778
  }
779
+ /**
780
+ * Build auth config from user answers
781
+ */
782
+ function buildAuthConfig(authConfig) {
783
+ if (!authConfig || !authConfig.type) {
784
+ return { config: undefined, profileName: undefined };
785
+ }
786
+ const profiles = {};
787
+ let profileName = 'default-auth';
788
+ switch (authConfig.type) {
789
+ case 'bearer':
790
+ profileName = 'bearer-auth';
791
+ profiles[profileName] = {
792
+ type: 'bearer',
793
+ config: {
794
+ token: authConfig.bearerToken || '${API_TOKEN}'
795
+ }
796
+ };
797
+ break;
798
+ case 'api_key':
799
+ profileName = 'api-key-auth';
800
+ profiles[profileName] = {
801
+ type: 'api_key',
802
+ config: {
803
+ header_name: authConfig.headerName || 'x-api-key',
804
+ value: authConfig.apiKey || '${API_KEY}'
805
+ }
806
+ };
807
+ break;
808
+ case 'basic':
809
+ profileName = 'basic-auth';
810
+ profiles[profileName] = {
811
+ type: 'basic',
812
+ config: {
813
+ username: authConfig.username || '${BASIC_USERNAME}',
814
+ password: authConfig.password || '${BASIC_PASSWORD}'
815
+ }
816
+ };
817
+ break;
818
+ case 'custom':
819
+ profileName = 'custom-auth';
820
+ profiles[profileName] = {
821
+ type: 'bearer',
822
+ config: {
823
+ token: authConfig.tokenPrefix ? `${authConfig.tokenPrefix}\${CUSTOM_TOKEN}` : '${CUSTOM_TOKEN}'
824
+ },
825
+ cache: {
826
+ enabled: true,
827
+ ttl: 300
828
+ }
829
+ };
830
+ break;
831
+ }
832
+ return {
833
+ config: {
834
+ api: profileName,
835
+ profiles
836
+ },
837
+ profileName
838
+ };
839
+ }
840
+ /**
841
+ * Parse body validation string into nested object
842
+ * Format: "field.nested.path:value" or just "field.nested.path"
843
+ *
844
+ * Examples:
845
+ * - "data.users.0.email" -> checks if field exists
846
+ * - "status:ok" -> checks if status == "ok"
847
+ * - "data.count:5" -> checks if data.count == 5
848
+ * - "data.items.length:>0" -> checks if data.items.length > 0
849
+ */
850
+ function parseBodyValidation(fieldPath, expectedValue) {
851
+ const parts = fieldPath.split('.');
852
+ const result = {};
853
+ // Build nested object from path
854
+ let current = result;
855
+ for (let i = 0; i < parts.length - 1; i++) {
856
+ current[parts[i]] = {};
857
+ current = current[parts[i]];
858
+ }
859
+ // Set the final value
860
+ const lastKey = parts[parts.length - 1];
861
+ if (expectedValue === null) {
862
+ // Just check field exists - use special sentinel
863
+ current[lastKey] = '__exists__';
864
+ }
865
+ else if (expectedValue.startsWith('>')) {
866
+ // Greater than comparison
867
+ const numValue = parseFloat(expectedValue.substring(1));
868
+ current[lastKey] = { $gt: isNaN(numValue) ? expectedValue.substring(1) : numValue };
869
+ }
870
+ else if (expectedValue.startsWith('<')) {
871
+ // Less than comparison
872
+ const numValue = parseFloat(expectedValue.substring(1));
873
+ current[lastKey] = { $lt: isNaN(numValue) ? expectedValue.substring(1) : numValue };
874
+ }
875
+ else if (expectedValue === 'true') {
876
+ current[lastKey] = true;
877
+ }
878
+ else if (expectedValue === 'false') {
879
+ current[lastKey] = false;
880
+ }
881
+ else if (/^\d+$/.test(expectedValue)) {
882
+ // Numeric value
883
+ current[lastKey] = parseInt(expectedValue, 10);
884
+ }
885
+ else if (/^\d+\.\d+$/.test(expectedValue)) {
886
+ // Float value
887
+ current[lastKey] = parseFloat(expectedValue);
888
+ }
889
+ else {
890
+ // String value
891
+ current[lastKey] = expectedValue;
892
+ }
893
+ return result;
894
+ }
569
895
  export async function askCommand(query) {
570
896
  const ask = new QA360Ask();
571
897
  try {
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Command } from 'commander';
7
7
  import { existsSync } from 'fs';
8
- import { CoverageCollector, CoverageAnalyzer, parseCoverageThreshold, formatCoverageThreshold } from '../core/index.js';
8
+ import { CoverageCollector, CoverageAnalyzer, parseCoverageThreshold, formatCoverageThreshold } from 'qa360-core';
9
9
  export const coverageCommand = new Command('coverage');
10
10
  coverageCommand.description('Coverage analytics and reporting');
11
11
  // Show coverage for a specific file or run
@@ -6,7 +6,7 @@
6
6
  import { Command } from 'commander';
7
7
  import chalk from 'chalk';
8
8
  import ora from 'ora';
9
- import { generatePackFromCrawl, quickCrawl } from '../core/index.js';
9
+ import { generatePackFromCrawl, quickCrawl } from 'qa360-core';
10
10
  /**
11
11
  * Create crawl command
12
12
  */
@@ -255,7 +255,7 @@ export class QA360Doctor {
255
255
  }
256
256
  async checkProofKeys() {
257
257
  try {
258
- const { ensureProofKeys } = await import('../core/index.js');
258
+ const { ensureProofKeys } = await import('qa360-core');
259
259
  const result = await ensureProofKeys(this.qa360Dir);
260
260
  // Validate result
261
261
  if (!result) {
@@ -300,7 +300,7 @@ export class QA360Doctor {
300
300
  }
301
301
  async checkProofRoundtrip() {
302
302
  try {
303
- const coreModule = await import('../core/index.js');
303
+ const coreModule = await import('qa360-core');
304
304
  const { generateKeys, createProofBundle, verifyProofBundle } = coreModule;
305
305
  // Guard: Verify exports are functions
306
306
  if (typeof generateKeys !== 'function') {
@@ -17,8 +17,8 @@ import chalk from 'chalk';
17
17
  import ora from 'ora';
18
18
  import { join } from 'path';
19
19
  import { existsSync } from 'fs';
20
- import { createBest, createLLMProvider } from '../core/index.js';
21
- import { EvidenceVault } from '../core/index.js';
20
+ import { createBest, createLLMProvider } from 'qa360-core';
21
+ import { EvidenceVault } from 'qa360-core';
22
22
  /**
23
23
  * Static error code database
24
24
  */
@@ -8,7 +8,7 @@ import chalk from 'chalk';
8
8
  import Table from 'cli-table3';
9
9
  import { join } from 'path';
10
10
  import { existsSync } from 'fs';
11
- import { EvidenceVault, FLAKINESS_CATEGORIES } from '../core/index.js';
11
+ import { EvidenceVault, FLAKINESS_CATEGORIES } from 'qa360-core';
12
12
  /**
13
13
  * Get the vault directory path
14
14
  */
@@ -11,7 +11,7 @@ import chalk from 'chalk';
11
11
  import ora from 'ora';
12
12
  import { writeFileSync, mkdirSync, existsSync } from 'fs';
13
13
  import { dirname, resolve } from 'path';
14
- import { generateTests, generateApiTestsFromOpenAPI, generateUiTestsFromUrl, generatePerfTests, generateUnitTests, checkGenerationAvailability, } from '../core/index.js';
14
+ import { generateTests, generateApiTestsFromOpenAPI, generateUiTestsFromUrl, generatePerfTests, generateUnitTests, checkGenerationAvailability, } from 'qa360-core';
15
15
  /**
16
16
  * Create generate commands group
17
17
  */
@@ -7,7 +7,7 @@ import { existsSync, createWriteStream, readFileSync, statSync, readdirSync, unl
7
7
  import { join } from 'path';
8
8
  import chalk from 'chalk';
9
9
  import AdmZip from 'adm-zip';
10
- import { EvidenceVault } from '../core/index.js';
10
+ import { EvidenceVault } from 'qa360-core';
11
11
  export class QA360History {
12
12
  vault;
13
13
  constructor() { }
@@ -4,9 +4,9 @@
4
4
  */
5
5
  import { Command } from 'commander';
6
6
  import chalk from 'chalk';
7
- import { TUIMonitor } from '../core/index.js';
8
- import { DashboardServer, EvidenceVault } from '../core/index.js';
9
- import { Phase3Runner } from '../core/index.js';
7
+ import { TUIMonitor } from 'qa360-core';
8
+ import { DashboardServer, EvidenceVault } from 'qa360-core';
9
+ import { Phase3Runner } from 'qa360-core';
10
10
  import { existsSync } from 'fs';
11
11
  import { join } from 'path';
12
12
  import { loadPackConfig } from '../utils/config.js';
@@ -7,7 +7,7 @@
7
7
  import { Command } from 'commander';
8
8
  import chalk from 'chalk';
9
9
  import ora from 'ora';
10
- import { checkOllamaSetup, OllamaProvider, OllamaError } from '../core/index.js';
10
+ import { checkOllamaSetup, OllamaProvider, OllamaError } from 'qa360-core';
11
11
  import { loadQA360Config, updateQA360Config, getConfigPath } from '../utils/config.js';
12
12
  /**
13
13
  * Test Ollama connection
@@ -9,8 +9,8 @@ import { resolve } from 'path';
9
9
  import chalk from 'chalk';
10
10
  import ora from 'ora';
11
11
  import * as yaml from 'js-yaml';
12
- import { PackValidator, PackMigrator as PackMigratorV1 } from '../core/index.js';
13
- import { PackValidatorV2, PackMigrator, PackLoaderV2 } from '../core/index.js';
12
+ import { PackValidator, PackMigrator as PackMigratorV1 } from 'qa360-core';
13
+ import { PackValidatorV2, PackMigrator, PackLoaderV2 } from 'qa360-core';
14
14
  export class QA360Pack {
15
15
  qa360Dir = resolve(process.cwd(), '.qa360');
16
16
  packPath = resolve(this.qa360Dir, 'pack.yml');
@@ -4,7 +4,7 @@
4
4
  * CLI command for regression detection and analysis.
5
5
  */
6
6
  import { Command } from 'commander';
7
- import { createRegressionDetector, createRegressionTrendAnalyzer } from '../core/index.js';
7
+ import { createRegressionDetector, createRegressionTrendAnalyzer } from 'qa360-core';
8
8
  export const regressionCommand = new Command('regression');
9
9
  regressionCommand.description('Regression detection and analysis');
10
10
  // Detect regressions
@@ -7,7 +7,7 @@ import { Command } from 'commander';
7
7
  import { readFile } from 'fs/promises';
8
8
  import { existsSync } from 'fs';
9
9
  import { resolve } from 'path';
10
- import { TestRepairer, analyzeTestErrors, getQuickFixes } from '../core/index.js';
10
+ import { TestRepairer, analyzeTestErrors, getQuickFixes } from 'qa360-core';
11
11
  /**
12
12
  * Parse test output to extract errors
13
13
  */