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
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Coverage Collector
3
+ *
4
+ * Collects coverage data from various test sources.
5
+ * Supports Jest, Vitest, Cypress, Playwright, and LCOV formats.
6
+ */
7
+ import type { FileCoverage, CoverageMetrics, CoverageResult, CoverageConfig } from './types.js';
8
+ /**
9
+ * Coverage Collector class
10
+ */
11
+ export declare class CoverageCollector {
12
+ private config;
13
+ constructor(config?: Partial<CoverageConfig>);
14
+ /**
15
+ * Collect coverage from a file path (auto-detect format)
16
+ */
17
+ collectFromFile(filePath: string): Promise<CoverageResult | null>;
18
+ /**
19
+ * Collect coverage from LCOV format
20
+ */
21
+ collectFromLcov(filePath: string): Promise<CoverageResult | null>;
22
+ /**
23
+ * Collect coverage from JSON format (Vitest/Istanbul)
24
+ */
25
+ collectFromJson(filePath: string): Promise<CoverageResult | null>;
26
+ /**
27
+ * Collect coverage from Istanbul format
28
+ */
29
+ collectFromIstanbul(filePath: string): Promise<CoverageResult | null>;
30
+ /**
31
+ * Parse LCOV format content
32
+ */
33
+ private parseLcov;
34
+ /**
35
+ * Convert LCOV record to FileCoverage
36
+ */
37
+ private convertLcovToFileCoverage;
38
+ /**
39
+ * Convert Vitest coverage data to FileCoverage
40
+ */
41
+ private convertVitestToFileCoverage;
42
+ /**
43
+ * Convert Istanbul coverage data to FileCoverage
44
+ */
45
+ private convertIstanbulToFileCoverage;
46
+ /**
47
+ * Calculate aggregate metrics from file coverage data
48
+ */
49
+ calculateMetrics(files: Record<string, FileCoverage>): CoverageMetrics;
50
+ /**
51
+ * Detect coverage format from file path
52
+ */
53
+ private detectFormat;
54
+ /**
55
+ * Filter files based on include/exclude patterns
56
+ */
57
+ filterFiles(files: Record<string, FileCoverage>): Record<string, FileCoverage>;
58
+ /**
59
+ * Match path against glob pattern
60
+ */
61
+ private matchPattern;
62
+ /**
63
+ * Merge multiple coverage results
64
+ */
65
+ mergeCoverageResults(results: CoverageResult[]): CoverageResult;
66
+ /**
67
+ * Update configuration
68
+ */
69
+ updateConfig(updates: Partial<CoverageConfig>): void;
70
+ }
71
+ /**
72
+ * Create a coverage collector with default config
73
+ */
74
+ export declare function createCoverageCollector(config?: Partial<CoverageConfig>): CoverageCollector;
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Coverage Collector
3
+ *
4
+ * Collects coverage data from various test sources.
5
+ * Supports Jest, Vitest, Cypress, Playwright, and LCOV formats.
6
+ */
7
+ import { createDefaultCoverageConfig } from './config.js';
8
+ /**
9
+ * Coverage Collector class
10
+ */
11
+ export class CoverageCollector {
12
+ config;
13
+ constructor(config) {
14
+ this.config = { ...createDefaultCoverageConfig(), ...config };
15
+ }
16
+ /**
17
+ * Collect coverage from a file path (auto-detect format)
18
+ */
19
+ async collectFromFile(filePath) {
20
+ const format = this.detectFormat(filePath);
21
+ switch (format) {
22
+ case 'lcov':
23
+ return this.collectFromLcov(filePath);
24
+ case 'json':
25
+ return this.collectFromJson(filePath);
26
+ case 'istanbul':
27
+ return this.collectFromIstanbul(filePath);
28
+ default:
29
+ return null;
30
+ }
31
+ }
32
+ /**
33
+ * Collect coverage from LCOV format
34
+ */
35
+ async collectFromLcov(filePath) {
36
+ try {
37
+ const fs = await import('node:fs/promises');
38
+ const content = await fs.readFile(filePath, 'utf-8');
39
+ const records = this.parseLcov(content);
40
+ if (records.length === 0) {
41
+ return null;
42
+ }
43
+ const files = {};
44
+ for (const record of records) {
45
+ files[record.file] = this.convertLcovToFileCoverage(record);
46
+ }
47
+ return {
48
+ source: 'lcov',
49
+ gate: 'unknown',
50
+ files,
51
+ metrics: this.calculateMetrics(files),
52
+ format: 'lcov',
53
+ timestamp: Date.now()
54
+ };
55
+ }
56
+ catch (error) {
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Collect coverage from JSON format (Vitest/Istanbul)
62
+ */
63
+ async collectFromJson(filePath) {
64
+ try {
65
+ const fs = await import('node:fs/promises');
66
+ const content = await fs.readFile(filePath, 'utf-8');
67
+ const data = JSON.parse(content);
68
+ const files = {};
69
+ // Handle Vitest format
70
+ if (data.result && typeof data.result === 'object') {
71
+ for (const [filePath, coverageData] of Object.entries(data.result)) {
72
+ if (typeof coverageData === 'object' && coverageData !== null) {
73
+ files[filePath] = this.convertVitestToFileCoverage(filePath, coverageData);
74
+ }
75
+ }
76
+ return {
77
+ source: 'vitest',
78
+ gate: 'unknown',
79
+ files,
80
+ metrics: this.calculateMetrics(files),
81
+ format: 'json',
82
+ timestamp: Date.now()
83
+ };
84
+ }
85
+ // Handle Istanbul format
86
+ if (typeof data === 'object' && !data.result) {
87
+ for (const [filePath, coverageData] of Object.entries(data)) {
88
+ if (typeof coverageData === 'object' && coverageData !== null) {
89
+ files[filePath] = this.convertIstanbulToFileCoverage(filePath, coverageData);
90
+ }
91
+ }
92
+ return {
93
+ source: 'istanbul',
94
+ gate: 'unknown',
95
+ files,
96
+ metrics: this.calculateMetrics(files),
97
+ format: 'istanbul',
98
+ timestamp: Date.now()
99
+ };
100
+ }
101
+ return null;
102
+ }
103
+ catch (error) {
104
+ return null;
105
+ }
106
+ }
107
+ /**
108
+ * Collect coverage from Istanbul format
109
+ */
110
+ async collectFromIstanbul(filePath) {
111
+ return this.collectFromJson(filePath);
112
+ }
113
+ /**
114
+ * Parse LCOV format content
115
+ */
116
+ parseLcov(content) {
117
+ const records = [];
118
+ const lines = content.split('\n');
119
+ let currentRecord = null;
120
+ for (const line of lines) {
121
+ if (line.startsWith('SF:')) {
122
+ // Start of new file record
123
+ currentRecord = {
124
+ file: line.substring(3),
125
+ lines: {},
126
+ branches: {},
127
+ functions: {}
128
+ };
129
+ records.push(currentRecord);
130
+ }
131
+ else if (currentRecord) {
132
+ if (line.startsWith('DA:')) {
133
+ // Line data: DA:<line>,<count>
134
+ const [, lineNum, count] = line.match(/^DA:(\d+),(\d+)$/) || [];
135
+ if (lineNum) {
136
+ currentRecord.lines[parseInt(lineNum, 10)] = parseInt(count, 10);
137
+ }
138
+ }
139
+ else if (line.startsWith('BRDA:')) {
140
+ // Branch data: BRDA:<line>,<block>,<branch>,<count>
141
+ const parts = line.substring(5).split(',');
142
+ if (parts.length >= 4) {
143
+ const lineNum = parts[0];
144
+ const branchId = `${lineNum}-${parts[1]}-${parts[2]}`;
145
+ const count = parts[3] === '-' ? 0 : parseInt(parts[3], 10);
146
+ if (!currentRecord.branches[lineNum]) {
147
+ currentRecord.branches[lineNum] = [];
148
+ }
149
+ currentRecord.branches[lineNum].push(count);
150
+ }
151
+ }
152
+ else if (line.startsWith('FN:')) {
153
+ // Function data: FN:<line>,<function name>
154
+ const parts = line.substring(3).split(',');
155
+ if (parts.length >= 2) {
156
+ currentRecord.functions[parts[1]] = 0;
157
+ }
158
+ }
159
+ else if (line.startsWith('FNDA:')) {
160
+ // Function execution data: FNDA:<count>,<function name>
161
+ const parts = line.substring(5).split(',');
162
+ if (parts.length >= 2) {
163
+ currentRecord.functions[parts[1]] = parseInt(parts[0], 10);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ return records;
169
+ }
170
+ /**
171
+ * Convert LCOV record to FileCoverage
172
+ */
173
+ convertLcovToFileCoverage(record) {
174
+ const lineNumbers = Object.keys(record.lines).map(Number);
175
+ const totalLines = lineNumbers.length > 0 ? Math.max(...lineNumbers) : 0;
176
+ const coveredLines = Object.values(record.lines).filter(c => c > 0).length;
177
+ // Count branches
178
+ let totalBranches = 0;
179
+ let coveredBranches = 0;
180
+ const branchesByLine = {};
181
+ for (const [lineNum, branchCounts] of Object.entries(record.branches)) {
182
+ const line = parseInt(lineNum, 10);
183
+ const branches = branchCounts;
184
+ totalBranches += branches.length;
185
+ coveredBranches += branches.filter(c => c > 0).length;
186
+ branchesByLine[line] = branches.map((count, idx) => ({
187
+ index: idx,
188
+ count,
189
+ location: { start: { line, column: 0 }, end: { line, column: 0 } }
190
+ }));
191
+ }
192
+ // Count functions
193
+ const totalFunctions = Object.keys(record.functions).length;
194
+ const coveredFunctions = Object.values(record.functions).filter(f => f > 0).length;
195
+ // Uncovered lines
196
+ const uncoveredLines = Object.entries(record.lines)
197
+ .filter(([, count]) => count === 0)
198
+ .map(([line]) => parseInt(line, 10));
199
+ const partiallyCoveredLines = Object.entries(record.lines)
200
+ .filter(([, count]) => count > 0 && count < 10) // Low execution count
201
+ .map(([line]) => parseInt(line, 10));
202
+ return {
203
+ path: record.file,
204
+ totalLines,
205
+ coveredLines,
206
+ lineCoverage: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
207
+ totalBranches,
208
+ coveredBranches,
209
+ branchCoverage: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
210
+ totalFunctions,
211
+ coveredFunctions,
212
+ functionCoverage: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
213
+ totalStatements: totalLines, // Approximation
214
+ coveredStatements: coveredLines,
215
+ statementCoverage: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
216
+ uncoveredLines,
217
+ partiallyCoveredLines,
218
+ branchesByLine
219
+ };
220
+ }
221
+ /**
222
+ * Convert Vitest coverage data to FileCoverage
223
+ */
224
+ convertVitestToFileCoverage(filePath, data) {
225
+ const s = data.s || { total: 0, covered: 0, pct: 0 };
226
+ const b = data.b || {};
227
+ const f = data.f || {};
228
+ const stmt = data.statementMap || {};
229
+ const branchMap = data.branchMap || {};
230
+ // Calculate line coverage from statement map
231
+ const lines = new Set();
232
+ const coveredLines = new Set();
233
+ for (const [idx, stmtInfo] of Object.entries(stmt)) {
234
+ if (stmtInfo && typeof stmtInfo === 'object') {
235
+ const start = stmtInfo.start;
236
+ const end = stmtInfo.end;
237
+ if (start && end) {
238
+ for (let i = start.line; i <= end.line; i++) {
239
+ lines.add(i);
240
+ }
241
+ }
242
+ }
243
+ const count = s[parseInt(idx)];
244
+ if (count > 0 && stmtInfo) {
245
+ const start = stmtInfo.start;
246
+ const end = stmtInfo.end;
247
+ if (start && end) {
248
+ for (let i = start.line; i <= end.line; i++) {
249
+ coveredLines.add(i);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ // Calculate branch coverage
255
+ let totalBranches = 0;
256
+ let coveredBranches = 0;
257
+ const branchesByLine = {};
258
+ for (const [idx, branches] of Object.entries(b)) {
259
+ if (Array.isArray(branches)) {
260
+ const branchInfo = branchMap[parseInt(idx)];
261
+ const line = branchInfo?.line || 0;
262
+ totalBranches += branches.length;
263
+ coveredBranches += branches.filter((c) => c > 0).length;
264
+ branchesByLine[line] = branches.map((count, idx) => ({
265
+ index: idx,
266
+ count,
267
+ location: { start: { line, column: 0 }, end: { line, column: 0 } }
268
+ }));
269
+ }
270
+ }
271
+ // Calculate function coverage
272
+ const totalFunctions = Object.keys(f).length;
273
+ const coveredFunctions = Object.values(f).filter((c) => typeof c === 'number' && c > 0).length;
274
+ const totalLines = lines.size;
275
+ const coveredLinesCount = coveredLines.size;
276
+ const uncoveredLines = Array.from(lines).filter(l => !coveredLines.has(l));
277
+ return {
278
+ path: filePath,
279
+ totalLines,
280
+ coveredLines: coveredLinesCount,
281
+ lineCoverage: s.pct || 0,
282
+ totalBranches,
283
+ coveredBranches,
284
+ branchCoverage: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
285
+ totalFunctions,
286
+ coveredFunctions,
287
+ functionCoverage: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
288
+ totalStatements: s.total || 0,
289
+ coveredStatements: s.covered || 0,
290
+ statementCoverage: s.pct || 0,
291
+ uncoveredLines,
292
+ partiallyCoveredLines: [],
293
+ branchesByLine
294
+ };
295
+ }
296
+ /**
297
+ * Convert Istanbul coverage data to FileCoverage
298
+ */
299
+ convertIstanbulToFileCoverage(filePath, data) {
300
+ return this.convertVitestToFileCoverage(filePath, data);
301
+ }
302
+ /**
303
+ * Calculate aggregate metrics from file coverage data
304
+ */
305
+ calculateMetrics(files) {
306
+ const fileArray = Object.values(files);
307
+ const threshold = this.config.thresholds.line || 80;
308
+ if (fileArray.length === 0) {
309
+ return {
310
+ lineCoverage: 0,
311
+ branchCoverage: 0,
312
+ functionCoverage: 0,
313
+ statementCoverage: 0,
314
+ totalFiles: 0,
315
+ filesWithCoverage: 0,
316
+ filesMeetingThreshold: 0,
317
+ totalLines: 0,
318
+ coveredLines: 0,
319
+ timestamp: Date.now()
320
+ };
321
+ }
322
+ let totalLines = 0;
323
+ let coveredLines = 0;
324
+ let totalBranches = 0;
325
+ let coveredBranches = 0;
326
+ let totalFunctions = 0;
327
+ let coveredFunctions = 0;
328
+ let totalStatements = 0;
329
+ let coveredStatements = 0;
330
+ let filesMeetingThreshold = 0;
331
+ for (const file of fileArray) {
332
+ totalLines += file.totalLines;
333
+ coveredLines += file.coveredLines;
334
+ totalBranches += file.totalBranches;
335
+ coveredBranches += file.coveredBranches;
336
+ totalFunctions += file.totalFunctions;
337
+ coveredFunctions += file.coveredFunctions;
338
+ totalStatements += file.totalStatements;
339
+ coveredStatements += file.coveredStatements;
340
+ if (file.lineCoverage >= threshold) {
341
+ filesMeetingThreshold++;
342
+ }
343
+ }
344
+ return {
345
+ lineCoverage: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
346
+ branchCoverage: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
347
+ functionCoverage: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
348
+ statementCoverage: totalStatements > 0 ? (coveredStatements / totalStatements) * 100 : 0,
349
+ totalFiles: fileArray.length,
350
+ filesWithCoverage: fileArray.filter(f => f.totalLines > 0).length,
351
+ filesMeetingThreshold,
352
+ totalLines,
353
+ coveredLines,
354
+ timestamp: Date.now()
355
+ };
356
+ }
357
+ /**
358
+ * Detect coverage format from file path
359
+ */
360
+ detectFormat(filePath) {
361
+ if (filePath.endsWith('.json')) {
362
+ return 'json';
363
+ }
364
+ if (filePath.endsWith('.lcov') || filePath.endsWith('.info')) {
365
+ return 'lcov';
366
+ }
367
+ if (filePath.includes('coverage') && filePath.endsWith('.json')) {
368
+ return 'istanbul';
369
+ }
370
+ return 'unknown';
371
+ }
372
+ /**
373
+ * Filter files based on include/exclude patterns
374
+ */
375
+ filterFiles(files) {
376
+ const filtered = {};
377
+ const isIncluded = (path) => {
378
+ // Check exclude patterns first
379
+ for (const pattern of this.config.exclude) {
380
+ if (this.matchPattern(path, pattern)) {
381
+ return false;
382
+ }
383
+ }
384
+ // Check include patterns
385
+ if (this.config.include.length === 0) {
386
+ return true; // No include filters means all
387
+ }
388
+ for (const pattern of this.config.include) {
389
+ if (this.matchPattern(path, pattern)) {
390
+ return true;
391
+ }
392
+ }
393
+ return false;
394
+ };
395
+ for (const [path, coverage] of Object.entries(files)) {
396
+ if (isIncluded(path)) {
397
+ filtered[path] = coverage;
398
+ }
399
+ }
400
+ return filtered;
401
+ }
402
+ /**
403
+ * Match path against glob pattern
404
+ */
405
+ matchPattern(path, pattern) {
406
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.').replace(/\//g, '\\/') + '$');
407
+ return regex.test(path);
408
+ }
409
+ /**
410
+ * Merge multiple coverage results
411
+ */
412
+ mergeCoverageResults(results) {
413
+ const mergedFiles = {};
414
+ for (const result of results) {
415
+ for (const [path, file] of Object.entries(result.files)) {
416
+ if (!mergedFiles[path]) {
417
+ mergedFiles[path] = { ...file };
418
+ }
419
+ else {
420
+ // Merge coverage data
421
+ const existing = mergedFiles[path];
422
+ // For lines, take the max execution count
423
+ const uncoveredLines = new Set([
424
+ ...existing.uncoveredLines,
425
+ ...file.uncoveredLines
426
+ ]);
427
+ // Update totals (approximate merging)
428
+ mergedFiles[path] = {
429
+ ...existing,
430
+ uncoveredLines: Array.from(uncoveredLines),
431
+ lineCoverage: Math.max(existing.lineCoverage, file.lineCoverage),
432
+ branchCoverage: Math.max(existing.branchCoverage, file.branchCoverage),
433
+ functionCoverage: Math.max(existing.functionCoverage, file.functionCoverage)
434
+ };
435
+ }
436
+ }
437
+ }
438
+ return {
439
+ source: 'custom',
440
+ gate: 'merged',
441
+ files: mergedFiles,
442
+ metrics: this.calculateMetrics(mergedFiles),
443
+ format: 'json',
444
+ timestamp: Date.now()
445
+ };
446
+ }
447
+ /**
448
+ * Update configuration
449
+ */
450
+ updateConfig(updates) {
451
+ this.config = { ...this.config, ...updates };
452
+ }
453
+ }
454
+ /**
455
+ * Create a coverage collector with default config
456
+ */
457
+ export function createCoverageCollector(config) {
458
+ return new CoverageCollector(config);
459
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Coverage Configuration
3
+ *
4
+ * Default configuration and factory functions for coverage analytics.
5
+ */
6
+ import type { CoverageConfig, CoverageThreshold } from './types.js';
7
+ /**
8
+ * Create default coverage configuration
9
+ */
10
+ export declare function createDefaultCoverageConfig(): CoverageConfig;
11
+ /**
12
+ * Create strict coverage configuration (enterprise/production)
13
+ */
14
+ export declare function createStrictCoverageConfig(): CoverageConfig;
15
+ /**
16
+ * Create relaxed coverage configuration (development/prototyping)
17
+ */
18
+ export declare function createRelaxedCoverageConfig(): CoverageConfig;
19
+ /**
20
+ * Parse coverage threshold from string format
21
+ * Supported formats:
22
+ * - "80" (line coverage only)
23
+ * - "line:80,branch:70"
24
+ * - "{line:80,branch:70,function:75}"
25
+ */
26
+ export declare function parseCoverageThreshold(input: string): CoverageThreshold;
27
+ /**
28
+ * Format coverage threshold as string
29
+ */
30
+ export declare function formatCoverageThreshold(threshold: CoverageThreshold): string;
31
+ /**
32
+ * Validate coverage configuration
33
+ */
34
+ export declare function validateCoverageConfig(config: CoverageConfig): {
35
+ valid: boolean;
36
+ errors: string[];
37
+ };