qa360 1.4.5 → 2.0.1

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 (209) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/ai.d.ts +41 -0
  3. package/dist/commands/ai.js +499 -0
  4. package/dist/commands/ask.js +12 -12
  5. package/dist/commands/coverage.d.ts +8 -0
  6. package/dist/commands/coverage.js +252 -0
  7. package/dist/commands/explain.d.ts +27 -0
  8. package/dist/commands/explain.js +630 -0
  9. package/dist/commands/flakiness.d.ts +73 -0
  10. package/dist/commands/flakiness.js +435 -0
  11. package/dist/commands/generate.d.ts +66 -0
  12. package/dist/commands/generate.js +438 -0
  13. package/dist/commands/init.d.ts +56 -9
  14. package/dist/commands/init.js +217 -10
  15. package/dist/commands/monitor.d.ts +27 -0
  16. package/dist/commands/monitor.js +225 -0
  17. package/dist/commands/ollama.d.ts +40 -0
  18. package/dist/commands/ollama.js +301 -0
  19. package/dist/commands/pack.d.ts +37 -9
  20. package/dist/commands/pack.js +240 -141
  21. package/dist/commands/regression.d.ts +8 -0
  22. package/dist/commands/regression.js +340 -0
  23. package/dist/commands/repair.d.ts +26 -0
  24. package/dist/commands/repair.js +307 -0
  25. package/dist/commands/retry.d.ts +43 -0
  26. package/dist/commands/retry.js +275 -0
  27. package/dist/commands/run.d.ts +8 -3
  28. package/dist/commands/run.js +45 -31
  29. package/dist/commands/slo.d.ts +8 -0
  30. package/dist/commands/slo.js +327 -0
  31. package/dist/core/adapters/playwright-native-api.d.ts +183 -0
  32. package/dist/core/adapters/playwright-native-api.js +461 -0
  33. package/dist/core/adapters/playwright-ui.d.ts +7 -0
  34. package/dist/core/adapters/playwright-ui.js +29 -1
  35. package/dist/core/ai/anthropic-provider.d.ts +50 -0
  36. package/dist/core/ai/anthropic-provider.js +211 -0
  37. package/dist/core/ai/deepseek-provider.d.ts +81 -0
  38. package/dist/core/ai/deepseek-provider.js +254 -0
  39. package/dist/core/ai/index.d.ts +60 -0
  40. package/dist/core/ai/index.js +18 -0
  41. package/dist/core/ai/llm-client.d.ts +45 -0
  42. package/dist/core/ai/llm-client.js +7 -0
  43. package/dist/core/ai/mock-provider.d.ts +49 -0
  44. package/dist/core/ai/mock-provider.js +121 -0
  45. package/dist/core/ai/ollama-provider.d.ts +78 -0
  46. package/dist/core/ai/ollama-provider.js +192 -0
  47. package/dist/core/ai/openai-provider.d.ts +48 -0
  48. package/dist/core/ai/openai-provider.js +188 -0
  49. package/dist/core/ai/provider-factory.d.ts +160 -0
  50. package/dist/core/ai/provider-factory.js +269 -0
  51. package/dist/core/auth/api-key-provider.d.ts +16 -0
  52. package/dist/core/auth/api-key-provider.js +63 -0
  53. package/dist/core/auth/aws-iam-provider.d.ts +35 -0
  54. package/dist/core/auth/aws-iam-provider.js +177 -0
  55. package/dist/core/auth/azure-ad-provider.d.ts +15 -0
  56. package/dist/core/auth/azure-ad-provider.js +99 -0
  57. package/dist/core/auth/basic-auth-provider.d.ts +26 -0
  58. package/dist/core/auth/basic-auth-provider.js +111 -0
  59. package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
  60. package/dist/core/auth/gcp-adc-provider.js +126 -0
  61. package/dist/core/auth/index.d.ts +238 -0
  62. package/dist/core/auth/index.js +82 -0
  63. package/dist/core/auth/jwt-provider.d.ts +19 -0
  64. package/dist/core/auth/jwt-provider.js +160 -0
  65. package/dist/core/auth/manager.d.ts +84 -0
  66. package/dist/core/auth/manager.js +230 -0
  67. package/dist/core/auth/oauth2-provider.d.ts +17 -0
  68. package/dist/core/auth/oauth2-provider.js +114 -0
  69. package/dist/core/auth/totp-provider.d.ts +31 -0
  70. package/dist/core/auth/totp-provider.js +134 -0
  71. package/dist/core/auth/ui-login-provider.d.ts +26 -0
  72. package/dist/core/auth/ui-login-provider.js +198 -0
  73. package/dist/core/cache/index.d.ts +7 -0
  74. package/dist/core/cache/index.js +6 -0
  75. package/dist/core/cache/lru-cache.d.ts +203 -0
  76. package/dist/core/cache/lru-cache.js +397 -0
  77. package/dist/core/coverage/analyzer.d.ts +101 -0
  78. package/dist/core/coverage/analyzer.js +415 -0
  79. package/dist/core/coverage/collector.d.ts +74 -0
  80. package/dist/core/coverage/collector.js +459 -0
  81. package/dist/core/coverage/config.d.ts +37 -0
  82. package/dist/core/coverage/config.js +156 -0
  83. package/dist/core/coverage/index.d.ts +11 -0
  84. package/dist/core/coverage/index.js +15 -0
  85. package/dist/core/coverage/types.d.ts +267 -0
  86. package/dist/core/coverage/types.js +6 -0
  87. package/dist/core/coverage/vault.d.ts +95 -0
  88. package/dist/core/coverage/vault.js +405 -0
  89. package/dist/core/dashboard/assets.d.ts +6 -0
  90. package/dist/core/dashboard/assets.js +690 -0
  91. package/dist/core/dashboard/index.d.ts +6 -0
  92. package/dist/core/dashboard/index.js +5 -0
  93. package/dist/core/dashboard/server.d.ts +72 -0
  94. package/dist/core/dashboard/server.js +354 -0
  95. package/dist/core/dashboard/types.d.ts +70 -0
  96. package/dist/core/dashboard/types.js +5 -0
  97. package/dist/core/discoverer/index.d.ts +115 -0
  98. package/dist/core/discoverer/index.js +250 -0
  99. package/dist/core/flakiness/index.d.ts +228 -0
  100. package/dist/core/flakiness/index.js +384 -0
  101. package/dist/core/generation/code-formatter.d.ts +111 -0
  102. package/dist/core/generation/code-formatter.js +307 -0
  103. package/dist/core/generation/code-generator.d.ts +144 -0
  104. package/dist/core/generation/code-generator.js +293 -0
  105. package/dist/core/generation/generator.d.ts +40 -0
  106. package/dist/core/generation/generator.js +76 -0
  107. package/dist/core/generation/index.d.ts +30 -0
  108. package/dist/core/generation/index.js +28 -0
  109. package/dist/core/generation/pack-generator.d.ts +107 -0
  110. package/dist/core/generation/pack-generator.js +416 -0
  111. package/dist/core/generation/prompt-builder.d.ts +132 -0
  112. package/dist/core/generation/prompt-builder.js +672 -0
  113. package/dist/core/generation/source-analyzer.d.ts +213 -0
  114. package/dist/core/generation/source-analyzer.js +657 -0
  115. package/dist/core/generation/test-optimizer.d.ts +117 -0
  116. package/dist/core/generation/test-optimizer.js +328 -0
  117. package/dist/core/generation/types.d.ts +214 -0
  118. package/dist/core/generation/types.js +4 -0
  119. package/dist/core/index.d.ts +23 -1
  120. package/dist/core/index.js +39 -0
  121. package/dist/core/pack/validator.js +31 -1
  122. package/dist/core/pack-v2/index.d.ts +9 -0
  123. package/dist/core/pack-v2/index.js +8 -0
  124. package/dist/core/pack-v2/loader.d.ts +62 -0
  125. package/dist/core/pack-v2/loader.js +231 -0
  126. package/dist/core/pack-v2/migrator.d.ts +56 -0
  127. package/dist/core/pack-v2/migrator.js +455 -0
  128. package/dist/core/pack-v2/validator.d.ts +61 -0
  129. package/dist/core/pack-v2/validator.js +577 -0
  130. package/dist/core/regression/detector.d.ts +107 -0
  131. package/dist/core/regression/detector.js +497 -0
  132. package/dist/core/regression/index.d.ts +9 -0
  133. package/dist/core/regression/index.js +11 -0
  134. package/dist/core/regression/trend-analyzer.d.ts +102 -0
  135. package/dist/core/regression/trend-analyzer.js +345 -0
  136. package/dist/core/regression/types.d.ts +222 -0
  137. package/dist/core/regression/types.js +7 -0
  138. package/dist/core/regression/vault.d.ts +87 -0
  139. package/dist/core/regression/vault.js +289 -0
  140. package/dist/core/repair/engine/fixer.d.ts +24 -0
  141. package/dist/core/repair/engine/fixer.js +226 -0
  142. package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
  143. package/dist/core/repair/engine/suggestion-engine.js +187 -0
  144. package/dist/core/repair/index.d.ts +10 -0
  145. package/dist/core/repair/index.js +13 -0
  146. package/dist/core/repair/repairer.d.ts +90 -0
  147. package/dist/core/repair/repairer.js +284 -0
  148. package/dist/core/repair/types.d.ts +91 -0
  149. package/dist/core/repair/types.js +6 -0
  150. package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
  151. package/dist/core/repair/utils/error-analyzer.js +264 -0
  152. package/dist/core/retry/flakiness-integration.d.ts +60 -0
  153. package/dist/core/retry/flakiness-integration.js +228 -0
  154. package/dist/core/retry/index.d.ts +14 -0
  155. package/dist/core/retry/index.js +16 -0
  156. package/dist/core/retry/retry-engine.d.ts +80 -0
  157. package/dist/core/retry/retry-engine.js +296 -0
  158. package/dist/core/retry/types.d.ts +178 -0
  159. package/dist/core/retry/types.js +52 -0
  160. package/dist/core/retry/vault.d.ts +77 -0
  161. package/dist/core/retry/vault.js +304 -0
  162. package/dist/core/runner/e2e-helpers.d.ts +102 -0
  163. package/dist/core/runner/e2e-helpers.js +153 -0
  164. package/dist/core/runner/phase3-runner.d.ts +101 -2
  165. package/dist/core/runner/phase3-runner.js +559 -24
  166. package/dist/core/self-healing/assertion-healer.d.ts +97 -0
  167. package/dist/core/self-healing/assertion-healer.js +371 -0
  168. package/dist/core/self-healing/engine.d.ts +122 -0
  169. package/dist/core/self-healing/engine.js +538 -0
  170. package/dist/core/self-healing/index.d.ts +10 -0
  171. package/dist/core/self-healing/index.js +11 -0
  172. package/dist/core/self-healing/selector-healer.d.ts +103 -0
  173. package/dist/core/self-healing/selector-healer.js +372 -0
  174. package/dist/core/self-healing/types.d.ts +152 -0
  175. package/dist/core/self-healing/types.js +6 -0
  176. package/dist/core/slo/config.d.ts +107 -0
  177. package/dist/core/slo/config.js +360 -0
  178. package/dist/core/slo/index.d.ts +11 -0
  179. package/dist/core/slo/index.js +15 -0
  180. package/dist/core/slo/sli-calculator.d.ts +92 -0
  181. package/dist/core/slo/sli-calculator.js +364 -0
  182. package/dist/core/slo/slo-tracker.d.ts +148 -0
  183. package/dist/core/slo/slo-tracker.js +379 -0
  184. package/dist/core/slo/types.d.ts +281 -0
  185. package/dist/core/slo/types.js +7 -0
  186. package/dist/core/slo/vault.d.ts +102 -0
  187. package/dist/core/slo/vault.js +427 -0
  188. package/dist/core/tui/index.d.ts +7 -0
  189. package/dist/core/tui/index.js +6 -0
  190. package/dist/core/tui/monitor.d.ts +92 -0
  191. package/dist/core/tui/monitor.js +271 -0
  192. package/dist/core/tui/renderer.d.ts +33 -0
  193. package/dist/core/tui/renderer.js +218 -0
  194. package/dist/core/tui/types.d.ts +63 -0
  195. package/dist/core/tui/types.js +5 -0
  196. package/dist/core/types/pack-v2.d.ts +425 -0
  197. package/dist/core/types/pack-v2.js +8 -0
  198. package/dist/core/vault/index.d.ts +116 -0
  199. package/dist/core/vault/index.js +400 -5
  200. package/dist/core/watch/index.d.ts +7 -0
  201. package/dist/core/watch/index.js +6 -0
  202. package/dist/core/watch/watch-mode.d.ts +213 -0
  203. package/dist/core/watch/watch-mode.js +389 -0
  204. package/dist/index.js +68 -68
  205. package/dist/utils/config.d.ts +5 -0
  206. package/dist/utils/config.js +136 -0
  207. package/package.json +5 -1
  208. package/dist/core/adapters/playwright-api.d.ts +0 -82
  209. package/dist/core/adapters/playwright-api.js +0 -264
@@ -0,0 +1,577 @@
1
+ /**
2
+ * QA360 Pack v2 Validator
3
+ *
4
+ * Validates pack.yml v2 files with test_files patterns and auth profiles.
5
+ */
6
+ import { glob } from 'glob';
7
+ import { resolve, dirname } from 'path';
8
+ export class PackValidatorV2 {
9
+ packPath;
10
+ baseDir;
11
+ constructor(packPath) {
12
+ this.packPath = packPath;
13
+ this.baseDir = dirname(resolve(packPath));
14
+ }
15
+ /**
16
+ * Validate a Pack v2 configuration
17
+ */
18
+ async validate(pack, options) {
19
+ const errors = [];
20
+ const warnings = [];
21
+ const info = {
22
+ gatesCount: 0,
23
+ totalTests: 0,
24
+ frameworks: []
25
+ };
26
+ // 1. Validate structure
27
+ const structureErrors = this.validateStructure(pack);
28
+ errors.push(...structureErrors);
29
+ if (errors.length > 0) {
30
+ return {
31
+ valid: false,
32
+ version: 2,
33
+ errors,
34
+ warnings,
35
+ info
36
+ };
37
+ }
38
+ // 2. Validate version
39
+ if (pack.version !== 2) {
40
+ errors.push({
41
+ code: 'QP2V001',
42
+ message: `Invalid version: expected 2, got ${pack.version}`,
43
+ path: 'version'
44
+ });
45
+ return { valid: false, version: 2, errors, warnings, info };
46
+ }
47
+ // 3. Validate required fields
48
+ const requiredErrors = this.validateRequiredFields(pack);
49
+ errors.push(...requiredErrors);
50
+ // 4. Validate auth configuration
51
+ const authErrors = await this.validateAuthConfig(pack.auth);
52
+ errors.push(...authErrors.errors);
53
+ warnings.push(...authErrors.warnings);
54
+ // 5. Validate gates
55
+ const gatesResult = await this.validateGates(pack.gates, options?.checkFilesExist ?? false);
56
+ errors.push(...gatesResult.errors);
57
+ warnings.push(...gatesResult.warnings);
58
+ info.gatesCount = Object.keys(pack.gates || {}).length;
59
+ info.totalTests = gatesResult.totalTests;
60
+ info.frameworks = gatesResult.frameworks;
61
+ // 6. Validate hooks
62
+ const hooksResult = this.validateHooks(pack.hooks);
63
+ errors.push(...hooksResult.errors);
64
+ warnings.push(...hooksResult.warnings);
65
+ // 7. Validate execution config
66
+ const execErrors = this.validateExecutionConfig(pack.execution);
67
+ errors.push(...execErrors.errors);
68
+ warnings.push(...execErrors.warnings);
69
+ // 8. Business logic validation
70
+ const businessErrors = this.validateBusinessRules(pack);
71
+ errors.push(...businessErrors.errors);
72
+ warnings.push(...businessErrors.warnings);
73
+ return {
74
+ valid: errors.length === 0,
75
+ version: 2,
76
+ errors,
77
+ warnings,
78
+ info
79
+ };
80
+ }
81
+ /**
82
+ * Validate basic structure
83
+ */
84
+ validateStructure(pack) {
85
+ const errors = [];
86
+ if (!pack || typeof pack !== 'object') {
87
+ errors.push({
88
+ code: 'QP2V002',
89
+ message: 'Pack configuration must be an object',
90
+ path: 'root'
91
+ });
92
+ }
93
+ return errors;
94
+ }
95
+ /**
96
+ * Validate required fields
97
+ */
98
+ validateRequiredFields(pack) {
99
+ const errors = [];
100
+ if (!pack.name || typeof pack.name !== 'string') {
101
+ errors.push({
102
+ code: 'QP2V003',
103
+ message: 'Pack name is required and must be a string',
104
+ path: 'name',
105
+ suggestion: 'Add: name: "my-pack"'
106
+ });
107
+ }
108
+ if (!pack.gates || typeof pack.gates !== 'object') {
109
+ errors.push({
110
+ code: 'QP2V004',
111
+ message: 'Gates configuration is required and must be an object',
112
+ path: 'gates',
113
+ suggestion: 'Add: gates: { smoke: { adapter: "playwright-api", test_files: ["tests/**/*.spec.ts"] } }'
114
+ });
115
+ }
116
+ if (pack.gates && Object.keys(pack.gates).length === 0) {
117
+ errors.push({
118
+ code: 'QP2V005',
119
+ message: 'At least one gate must be defined',
120
+ path: 'gates',
121
+ suggestion: 'Add at least one gate configuration'
122
+ });
123
+ }
124
+ return errors;
125
+ }
126
+ /**
127
+ * Validate auth configuration
128
+ */
129
+ async validateAuthConfig(auth) {
130
+ const errors = [];
131
+ const warnings = [];
132
+ if (!auth) {
133
+ return { errors, warnings };
134
+ }
135
+ // Validate default auth profiles reference existing profiles
136
+ const profiles = auth.profiles || {};
137
+ const validAuthTypes = ['none', 'jwt', 'oauth2', 'api_key', 'bearer', 'basic', 'totp', 'ui_login', 'gcp_adc', 'aws_iam', 'azure_ad'];
138
+ if (auth.api && !profiles[auth.api]) {
139
+ errors.push({
140
+ code: 'QP2V006',
141
+ message: `Auth profile "${auth.api}" referenced in auth.api but not defined in profiles`,
142
+ path: 'auth.api',
143
+ suggestion: `Define the profile or use an existing one: ${Object.keys(profiles).join(', ') || 'none defined'}`
144
+ });
145
+ }
146
+ if (auth.ui && !profiles[auth.ui]) {
147
+ errors.push({
148
+ code: 'QP2V007',
149
+ message: `Auth profile "${auth.ui}" referenced in auth.ui but not defined in profiles`,
150
+ path: 'auth.ui',
151
+ suggestion: `Define the profile or use an existing one: ${Object.keys(profiles).join(', ') || 'none defined'}`
152
+ });
153
+ }
154
+ // Validate each profile
155
+ for (const [name, profile] of Object.entries(profiles)) {
156
+ const profileErrors = this.validateAuthProfile(name, profile, validAuthTypes);
157
+ errors.push(...profileErrors.errors);
158
+ warnings.push(...profileErrors.warnings);
159
+ }
160
+ return { errors, warnings };
161
+ }
162
+ /**
163
+ * Validate a single auth profile
164
+ */
165
+ validateAuthProfile(name, profile, validTypes) {
166
+ const errors = [];
167
+ const warnings = [];
168
+ if (!validTypes.includes(profile.type)) {
169
+ errors.push({
170
+ code: 'QP2V008',
171
+ message: `Invalid auth type: ${profile.type}`,
172
+ path: `auth.profiles.${name}.type`,
173
+ suggestion: `Use one of: ${validTypes.join(', ')}`
174
+ });
175
+ }
176
+ // Type-specific validation
177
+ switch (profile.type) {
178
+ case 'jwt':
179
+ if (!profile.config?.client_id && !profile.config?.token_endpoint) {
180
+ warnings.push({
181
+ code: 'QP2V009',
182
+ message: 'JWT profile should have client_id or token_endpoint',
183
+ path: `auth.profiles.${name}.config`,
184
+ suggestion: 'Add client_id or token_endpoint for token retrieval'
185
+ });
186
+ }
187
+ break;
188
+ case 'oauth2':
189
+ if (!profile.config?.token_url) {
190
+ errors.push({
191
+ code: 'QP2V010',
192
+ message: 'OAuth2 profile requires token_url',
193
+ path: `auth.profiles.${name}.config.token_url`,
194
+ suggestion: 'Add token_url: "https://auth.example.com/oauth2/token"'
195
+ });
196
+ }
197
+ if (!profile.config?.client_id) {
198
+ errors.push({
199
+ code: 'QP2V011',
200
+ message: 'OAuth2 profile requires client_id',
201
+ path: `auth.profiles.${name}.config.client_id`,
202
+ suggestion: 'Add client_id: "your-client-id"'
203
+ });
204
+ }
205
+ break;
206
+ case 'api_key':
207
+ if (!profile.config?.key) {
208
+ errors.push({
209
+ code: 'QP2V012',
210
+ message: 'API key profile requires key',
211
+ path: `auth.profiles.${name}.config.key`,
212
+ suggestion: 'Add key or use secret reference: ${{ secrets.API_KEY }}'
213
+ });
214
+ }
215
+ break;
216
+ case 'bearer':
217
+ if (!profile.config?.token) {
218
+ errors.push({
219
+ code: 'QP2V013',
220
+ message: 'Bearer token profile requires token',
221
+ path: `auth.profiles.${name}.config.token`,
222
+ suggestion: 'Add token or use secret reference: ${{ secrets.BEARER_TOKEN }}'
223
+ });
224
+ }
225
+ break;
226
+ case 'basic':
227
+ if (!profile.config?.username || !profile.config?.password) {
228
+ warnings.push({
229
+ code: 'QP2V014',
230
+ message: 'Basic auth profile should have username and password',
231
+ path: `auth.profiles.${name}.config`,
232
+ suggestion: 'Add username and password or use secret references'
233
+ });
234
+ }
235
+ break;
236
+ case 'ui_login':
237
+ if (!profile.config?.url) {
238
+ errors.push({
239
+ code: 'QP2V015',
240
+ message: 'UI login profile requires url',
241
+ path: `auth.profiles.${name}.config.url`,
242
+ suggestion: 'Add url: "https://example.com/login"'
243
+ });
244
+ }
245
+ break;
246
+ case 'totp':
247
+ if (!profile.config?.secret) {
248
+ errors.push({
249
+ code: 'QP2V016',
250
+ message: 'TOTP profile requires secret',
251
+ path: `auth.profiles.${name}.config.secret`,
252
+ suggestion: 'Add TOTP secret or use secret reference'
253
+ });
254
+ }
255
+ break;
256
+ }
257
+ return { errors, warnings };
258
+ }
259
+ /**
260
+ * Validate gates configuration
261
+ */
262
+ async validateGates(gates, checkFilesExist) {
263
+ const errors = [];
264
+ const warnings = [];
265
+ const frameworks = new Set();
266
+ let totalTests = 0;
267
+ if (!gates) {
268
+ return { errors, warnings, totalTests, frameworks: [] };
269
+ }
270
+ const validAdapters = [
271
+ 'playwright-api',
272
+ 'playwright-ui',
273
+ 'k6-perf',
274
+ 'semgrep-sast',
275
+ 'zap-dast',
276
+ 'gitleaks-secrets',
277
+ 'osv-deps'
278
+ ];
279
+ for (const [gateName, gate] of Object.entries(gates)) {
280
+ // Validate adapter
281
+ if (gate.adapter && !validAdapters.includes(gate.adapter)) {
282
+ warnings.push({
283
+ code: 'QP2V017',
284
+ message: `Unknown adapter: ${gate.adapter}`,
285
+ path: `gates.${gateName}.adapter`,
286
+ suggestion: `Use one of: ${validAdapters.join(', ')}`
287
+ });
288
+ }
289
+ // Collect adapter type
290
+ if (gate.adapter) {
291
+ frameworks.add(gate.adapter);
292
+ }
293
+ // Validate test_files
294
+ if (gate.test_files && gate.test_files.length > 0) {
295
+ for (const pattern of gate.test_files) {
296
+ const patternErrors = this.validateTestPattern(gateName, pattern, checkFilesExist);
297
+ errors.push(...patternErrors.errors);
298
+ warnings.push(...patternErrors.warnings);
299
+ // Count matching files
300
+ if (checkFilesExist) {
301
+ try {
302
+ const matches = await glob(pattern, { cwd: this.baseDir, absolute: false });
303
+ totalTests += matches.length;
304
+ }
305
+ catch {
306
+ // Ignore glob errors
307
+ }
308
+ }
309
+ }
310
+ }
311
+ else if (!gate.config && !gate.test_files) {
312
+ warnings.push({
313
+ code: 'QP2V018',
314
+ message: `Gate "${gateName}" has neither test_files nor config`,
315
+ path: `gates.${gateName}`,
316
+ suggestion: 'Add test_files: ["tests/**/*.spec.ts"] or config with inline configuration'
317
+ });
318
+ }
319
+ // Validate auth profile reference
320
+ if (gate.auth) {
321
+ // Will be validated against profiles in auth section
322
+ if (!gate.adapter?.includes('api') && !gate.adapter?.includes('ui')) {
323
+ warnings.push({
324
+ code: 'QP2V019',
325
+ message: `Auth profile specified but adapter may not support auth`,
326
+ path: `gates.${gateName}.auth`,
327
+ suggestion: 'Ensure adapter supports authentication'
328
+ });
329
+ }
330
+ }
331
+ // Validate budgets
332
+ if (gate.budgets) {
333
+ const budgetErrors = this.validateGateBudgets(gateName, gate.budgets);
334
+ errors.push(...budgetErrors);
335
+ }
336
+ // Validate dependencies
337
+ if (gate.depends_on) {
338
+ for (const dep of gate.depends_on) {
339
+ if (!gates[dep]) {
340
+ errors.push({
341
+ code: 'QP2V020',
342
+ message: `Gate depends on non-existent gate: ${dep}`,
343
+ path: `gates.${gateName}.depends_on`,
344
+ suggestion: `Remove ${dep} from dependencies or define the gate`
345
+ });
346
+ }
347
+ }
348
+ }
349
+ }
350
+ return { errors, warnings, totalTests, frameworks: Array.from(frameworks) };
351
+ }
352
+ /**
353
+ * Validate a test file pattern
354
+ */
355
+ validateTestPattern(gateName, pattern, checkExistence) {
356
+ const errors = [];
357
+ const warnings = [];
358
+ // Check for invalid patterns
359
+ if (pattern.includes('..')) {
360
+ errors.push({
361
+ code: 'QP2V021',
362
+ message: 'Test pattern cannot contain parent directory reference ".."',
363
+ path: `gates.${gateName}.test_files`,
364
+ suggestion: 'Use relative patterns without ..'
365
+ });
366
+ }
367
+ if (pattern.startsWith('/')) {
368
+ warnings.push({
369
+ code: 'QP2V022',
370
+ message: 'Absolute path may not work across environments',
371
+ path: `gates.${gateName}.test_files`,
372
+ suggestion: 'Use relative pattern from pack file location'
373
+ });
374
+ }
375
+ return { errors, warnings };
376
+ }
377
+ /**
378
+ * Validate gate budgets
379
+ */
380
+ validateGateBudgets(gateName, budgets) {
381
+ const errors = [];
382
+ for (const [key, value] of Object.entries(budgets)) {
383
+ if (typeof value !== 'number' || value < 0) {
384
+ errors.push({
385
+ code: 'QP2V023',
386
+ message: `Budget value must be a positive number: ${key}`,
387
+ path: `gates.${gateName}.budgets.${key}`,
388
+ suggestion: 'Use a positive number value'
389
+ });
390
+ }
391
+ }
392
+ return errors;
393
+ }
394
+ /**
395
+ * Validate hooks configuration
396
+ */
397
+ validateHooks(hooks) {
398
+ const errors = [];
399
+ const warnings = [];
400
+ if (!hooks) {
401
+ return { errors, warnings };
402
+ }
403
+ const validHookTypes = ['run', 'wait_on', 'script', 'docker'];
404
+ const validPhases = ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'];
405
+ for (const [phase, hookList] of Object.entries(hooks)) {
406
+ if (!validPhases.includes(phase)) {
407
+ warnings.push({
408
+ code: 'QP2V024',
409
+ message: `Unknown hook phase: ${phase}`,
410
+ path: `hooks.${phase}`,
411
+ suggestion: `Use one of: ${validPhases.join(', ')}`
412
+ });
413
+ continue;
414
+ }
415
+ if (!Array.isArray(hookList)) {
416
+ errors.push({
417
+ code: 'QP2V025',
418
+ message: `Hook phase must be an array: ${phase}`,
419
+ path: `hooks.${phase}`,
420
+ suggestion: 'Use array format: hooks: { beforeAll: [{ run: "command" }] }'
421
+ });
422
+ continue;
423
+ }
424
+ for (let i = 0; i < hookList.length; i++) {
425
+ const hook = hookList[i];
426
+ const hookErrors = this.validateHook(phase, i, hook, validHookTypes);
427
+ errors.push(...hookErrors.errors);
428
+ warnings.push(...hookErrors.warnings);
429
+ }
430
+ }
431
+ return { errors, warnings };
432
+ }
433
+ /**
434
+ * Validate a single hook
435
+ */
436
+ validateHook(phase, index, hook, validTypes) {
437
+ const errors = [];
438
+ const warnings = [];
439
+ if (!hook.type || !validTypes.includes(hook.type)) {
440
+ errors.push({
441
+ code: 'QP2V026',
442
+ message: `Invalid or missing hook type: ${hook.type}`,
443
+ path: `hooks.${phase}[${index}].type`,
444
+ suggestion: `Use one of: ${validTypes.join(', ')}`
445
+ });
446
+ return { errors, warnings };
447
+ }
448
+ // Type-specific validation
449
+ switch (hook.type) {
450
+ case 'run':
451
+ case 'script':
452
+ if (!hook.command) {
453
+ errors.push({
454
+ code: 'QP2V027',
455
+ message: `${hook.type} hook requires command`,
456
+ path: `hooks.${phase}[${index}].command`,
457
+ suggestion: 'Add command to execute'
458
+ });
459
+ }
460
+ break;
461
+ case 'wait_on':
462
+ if (!hook.wait_for?.resource) {
463
+ errors.push({
464
+ code: 'QP2V028',
465
+ message: 'wait_on hook requires resource',
466
+ path: `hooks.${phase}[${index}].wait_for.resource`,
467
+ suggestion: 'Add resource: "http://localhost:3000" or "tcp://localhost:8080"'
468
+ });
469
+ }
470
+ break;
471
+ case 'docker':
472
+ if (!hook.compose) {
473
+ warnings.push({
474
+ code: 'QP2V029',
475
+ message: 'Docker hook should have compose configuration',
476
+ path: `hooks.${phase}[${index}]`,
477
+ suggestion: 'Add compose: { services: ["app"] }'
478
+ });
479
+ }
480
+ break;
481
+ }
482
+ // Check for dangerous commands
483
+ if (hook.command) {
484
+ const dangerous = ['rm -rf', 'sudo rm', 'del /f', 'format', 'mkfs'];
485
+ if (dangerous.some(d => hook.command.toLowerCase().includes(d))) {
486
+ warnings.push({
487
+ code: 'QP2V030',
488
+ message: 'Potentially dangerous command in hook',
489
+ path: `hooks.${phase}[${index}].command`,
490
+ suggestion: 'Review command for safety'
491
+ });
492
+ }
493
+ }
494
+ return { errors, warnings };
495
+ }
496
+ /**
497
+ * Validate execution configuration
498
+ */
499
+ validateExecutionConfig(execution) {
500
+ const errors = [];
501
+ const warnings = [];
502
+ if (!execution) {
503
+ // Add default if missing
504
+ return { errors, warnings };
505
+ }
506
+ // Validate on_failure
507
+ if (execution.on_failure && !['stop', 'continue', 'proceed'].includes(execution.on_failure)) {
508
+ errors.push({
509
+ code: 'QP2V031',
510
+ message: `Invalid on_failure value: ${execution.on_failure}`,
511
+ path: 'execution.on_failure',
512
+ suggestion: 'Use one of: stop, continue, proceed'
513
+ });
514
+ }
515
+ // Validate numeric values
516
+ if (execution.default_timeout !== undefined && (typeof execution.default_timeout !== 'number' || execution.default_timeout <= 0)) {
517
+ errors.push({
518
+ code: 'QP2V032',
519
+ message: 'default_timeout must be a positive number',
520
+ path: 'execution.default_timeout',
521
+ suggestion: 'Use timeout in milliseconds (e.g., 30000 for 30s)'
522
+ });
523
+ }
524
+ if (execution.max_concurrency !== undefined && (typeof execution.max_concurrency !== 'number' || execution.max_concurrency <= 0)) {
525
+ errors.push({
526
+ code: 'QP2V033',
527
+ message: 'max_concurrency must be a positive number',
528
+ path: 'execution.max_concurrency',
529
+ suggestion: 'Use a value >= 1'
530
+ });
531
+ }
532
+ return { errors, warnings };
533
+ }
534
+ /**
535
+ * Validate business rules
536
+ */
537
+ validateBusinessRules(pack) {
538
+ const errors = [];
539
+ const warnings = [];
540
+ // Early return if no gates (already caught by required validation)
541
+ if (!pack.gates || Object.keys(pack.gates).length === 0) {
542
+ return { errors, warnings };
543
+ }
544
+ // Check for API gate with api auth but no auth profile
545
+ const apiGates = Object.entries(pack.gates).filter(([_, g]) => g.adapter?.includes('api'));
546
+ if (apiGates.length > 0 && !pack.auth?.api && !pack.auth?.profiles) {
547
+ warnings.push({
548
+ code: 'QP2V034',
549
+ message: 'API gates defined but no default auth profile configured',
550
+ path: 'auth.api',
551
+ suggestion: 'Add auth.api or define profiles for API authentication'
552
+ });
553
+ }
554
+ // Check for UI gate with ui auth but no auth profile
555
+ const uiGates = Object.entries(pack.gates).filter(([_, g]) => g.adapter?.includes('ui'));
556
+ if (uiGates.length > 0 && !pack.auth?.ui && !pack.auth?.profiles) {
557
+ warnings.push({
558
+ code: 'QP2V035',
559
+ message: 'UI gates defined but no default auth profile configured',
560
+ path: 'auth.ui',
561
+ suggestion: 'Add auth.ui or define profiles for UI authentication'
562
+ });
563
+ }
564
+ // Check for gates without adapter
565
+ const gatesWithoutAdapter = Object.entries(pack.gates).filter(([_, g]) => !g.adapter);
566
+ if (gatesWithoutAdapter.length > 0) {
567
+ const names = gatesWithoutAdapter.map(([name]) => name).join(', ');
568
+ warnings.push({
569
+ code: 'QP2V036',
570
+ message: `Gates without adapter: ${names}`,
571
+ path: 'gates',
572
+ suggestion: 'Add adapter to each gate for proper execution'
573
+ });
574
+ }
575
+ return { errors, warnings };
576
+ }
577
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Regression Detector
3
+ *
4
+ * Detects regressions using statistical methods.
5
+ * Supports Z-score, T-test, Mann-Whitney U, and percentile-based detection.
6
+ */
7
+ import type { RegressionDetection, RegressionConfig, TimeSeriesPoint, Baseline } from './types.js';
8
+ /**
9
+ * Regression Detector class
10
+ */
11
+ export declare class RegressionDetector {
12
+ private config;
13
+ private baselines;
14
+ constructor(config?: Partial<RegressionConfig>);
15
+ /**
16
+ * Detect regressions by comparing current values to baseline
17
+ */
18
+ detectRegressions(currentData: Record<string, number>, currentRunId: string, gate: string, context?: Record<string, unknown>): RegressionDetection[];
19
+ /**
20
+ * Detect regression for a single metric
21
+ */
22
+ private detectMetricRegression;
23
+ /**
24
+ * Perform statistical test
25
+ */
26
+ private performStatisticalTest;
27
+ /**
28
+ * Z-score test
29
+ */
30
+ private zScoreTest;
31
+ /**
32
+ * T-test (simplified for single value vs baseline)
33
+ */
34
+ private tTest;
35
+ /**
36
+ * Percentile test
37
+ */
38
+ private percentileTest;
39
+ /**
40
+ * Calculate percentile for a value in baseline
41
+ */
42
+ private calculatePercentile;
43
+ /**
44
+ * Normal CDF (cumulative distribution function)
45
+ */
46
+ private normalCDF;
47
+ /**
48
+ * Student's t CDF approximation
49
+ */
50
+ private studentTCDF;
51
+ /**
52
+ * Check if change constitutes a regression
53
+ */
54
+ private isRegression;
55
+ /**
56
+ * Get direction of change (worse/better/neutral)
57
+ */
58
+ private getDIRECTION;
59
+ /**
60
+ * Calculate regression severity
61
+ */
62
+ private calculateSeverity;
63
+ /**
64
+ * Generate suggestions for regression
65
+ */
66
+ private generateSuggestions;
67
+ /**
68
+ * Update baseline with new data
69
+ */
70
+ updateBaseline(metricName: string, data: TimeSeriesPoint[]): void;
71
+ /**
72
+ * Get baseline for a metric
73
+ */
74
+ getBaseline(metricName: string): Baseline | undefined;
75
+ /**
76
+ * Get all baselines
77
+ */
78
+ getAllBaselines(): Baseline[];
79
+ /**
80
+ * Get threshold for a metric
81
+ */
82
+ private getThresholdForMetric;
83
+ /**
84
+ * Check if metric matches pattern
85
+ */
86
+ private metricMatchesPattern;
87
+ /**
88
+ * Get regression type for metric
89
+ */
90
+ private getTypeForMetric;
91
+ /**
92
+ * Extract component name from metric and context
93
+ */
94
+ private extractComponent;
95
+ /**
96
+ * Check if metric should be ignored
97
+ */
98
+ private shouldIgnore;
99
+ /**
100
+ * Create default configuration
101
+ */
102
+ private createDefaultConfig;
103
+ }
104
+ /**
105
+ * Create a regression detector
106
+ */
107
+ export declare function createRegressionDetector(config?: Partial<RegressionConfig>): RegressionDetector;