jest-test-lineage-reporter 2.0.1 → 2.1.0

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 (34) hide show
  1. package/README.md +252 -0
  2. package/bin/jest-lineage.js +20 -0
  3. package/package.json +14 -5
  4. package/src/MutationTester.js +1154 -0
  5. package/src/__tests__/assertion-test.test.ts +59 -0
  6. package/src/__tests__/calculator.test.ts +30 -0
  7. package/src/__tests__/depth-example.test.ts +237 -0
  8. package/src/__tests__/gc-pressure-example.test.ts +169 -0
  9. package/src/__tests__/performance-example.test.ts +83 -0
  10. package/src/__tests__/quality-example.test.ts +122 -0
  11. package/src/__tests__/survived-mutations-example.test.ts +32 -0
  12. package/src/__tests__/truly-weak-example.test.ts +90 -0
  13. package/src/__tests__/weak-test-example.test.ts +222 -0
  14. package/src/babel-plugin-mutation-tester.js +402 -0
  15. package/src/calculator.ts +12 -0
  16. package/src/cli/commands/analyze.js +91 -0
  17. package/src/cli/commands/mutate.js +89 -0
  18. package/src/cli/commands/query.js +107 -0
  19. package/src/cli/commands/report.js +65 -0
  20. package/src/cli/commands/test.js +56 -0
  21. package/src/cli/index.js +89 -0
  22. package/src/cli/utils/config-loader.js +114 -0
  23. package/src/cli/utils/data-loader.js +118 -0
  24. package/src/cli/utils/jest-runner.js +105 -0
  25. package/src/cli/utils/output-formatter.js +126 -0
  26. package/src/depth-example.ts +66 -0
  27. package/src/gc-pressure-example.ts +158 -0
  28. package/src/global.d.ts +7 -0
  29. package/src/mcp/server.js +469 -0
  30. package/src/performance-example.ts +82 -0
  31. package/src/quality-example.ts +79 -0
  32. package/src/survived-mutations-example.ts +19 -0
  33. package/src/truly-weak-example.ts +37 -0
  34. package/src/weak-test-example.ts +91 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Report Command
3
+ * Generate HTML report from existing lineage data
4
+ */
5
+
6
+ const TestCoverageReporter = require('../../TestCoverageReporter');
7
+ const { loadLineageData, processLineageDataForMutation } = require('../utils/data-loader');
8
+ const { loadFullConfig } = require('../utils/config-loader');
9
+ const { spinner, success, error, info } = require('../utils/output-formatter');
10
+ const open = require('open');
11
+ const chalk = require('chalk');
12
+ const path = require('path');
13
+
14
+ async function reportCommand(options) {
15
+ try {
16
+ // Load configuration
17
+ const config = loadFullConfig(options);
18
+
19
+ // Load lineage data
20
+ info(`Loading lineage data from: ${chalk.yellow(options.data)}`);
21
+ const rawData = loadLineageData(options.data);
22
+
23
+ const testCount = rawData.tests.length;
24
+ info(`Loaded ${chalk.cyan(testCount)} tests`);
25
+
26
+ // Process lineage data
27
+ const lineageData = processLineageDataForMutation(rawData);
28
+
29
+ // Create reporter instance with minimal config
30
+ const reporter = new TestCoverageReporter(
31
+ { rootDir: process.cwd() },
32
+ { outputFile: options.output, ...config }
33
+ );
34
+
35
+ // Load data into reporter
36
+ reporter.processLineageResults(lineageData, 'unknown');
37
+
38
+ // Generate HTML report
39
+ const spin = spinner('Generating HTML report...');
40
+ spin.start();
41
+
42
+ await reporter.generateHtmlReport();
43
+
44
+ spin.succeed('HTML report generated!');
45
+
46
+ const reportPath = path.resolve(process.cwd(), options.output);
47
+ success(`Report saved to: ${chalk.cyan(reportPath)}`);
48
+
49
+ // Open in browser if requested
50
+ if (options.open) {
51
+ info('Opening report in browser...');
52
+ await open(reportPath);
53
+ }
54
+
55
+ process.exit(0);
56
+ } catch (err) {
57
+ error(`Failed to generate report: ${err.message}`);
58
+ if (process.env.JEST_LINEAGE_DEBUG === 'true') {
59
+ console.error(err.stack);
60
+ }
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ module.exports = reportCommand;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Test Command
3
+ * Run Jest tests with lineage tracking
4
+ */
5
+
6
+ const { runJest } = require('../utils/jest-runner');
7
+ const { loadFullConfig } = require('../utils/config-loader');
8
+ const { lineageDataExists } = require('../utils/data-loader');
9
+ const { success, error, info } = require('../utils/output-formatter');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ async function testCommand(jestArgs, options) {
14
+ try {
15
+ // Load configuration
16
+ const config = loadFullConfig(options);
17
+
18
+ // Run Jest with lineage tracking
19
+ const result = await runJest({
20
+ args: jestArgs || [],
21
+ config: options.config,
22
+ enableLineage: options.lineage !== false,
23
+ enablePerformance: options.performance !== false,
24
+ enableQuality: options.quality !== false,
25
+ quiet: options.quiet
26
+ });
27
+
28
+ // Check if lineage data was generated
29
+ const dataPath = path.join(process.cwd(), '.jest-lineage-data.json');
30
+ if (result.success && fs.existsSync(dataPath)) {
31
+ const data = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
32
+ const testCount = data.tests ? data.tests.length : 0;
33
+ const fileCount = data.tests
34
+ ? new Set(data.tests.flatMap(t =>
35
+ Object.keys(t.coverage || {}).map(k => k.split(':')[0])
36
+ )).size
37
+ : 0;
38
+
39
+ if (!options.quiet) {
40
+ info(`Lineage data saved to: ${dataPath}`);
41
+ console.log(` - ${testCount} tests tracked`);
42
+ console.log(` - ${fileCount} files analyzed\n`);
43
+ }
44
+ } else if (!result.success) {
45
+ error('Tests failed. Lineage data may be incomplete.');
46
+ }
47
+
48
+ // Exit with Jest's exit code
49
+ process.exit(result.exitCode);
50
+ } catch (err) {
51
+ error(`Failed to run tests: ${err.message}`);
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ module.exports = testCommand;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * CLI Command Router
3
+ * Main CLI logic with commander.js
4
+ */
5
+
6
+ const { Command } = require('commander');
7
+ const testCommand = require('./commands/test');
8
+ const mutateCommand = require('./commands/mutate');
9
+ const reportCommand = require('./commands/report');
10
+ const queryCommand = require('./commands/query');
11
+ const analyzeCommand = require('./commands/analyze');
12
+ const pkg = require('../../package.json');
13
+
14
+ async function run(argv) {
15
+ const program = new Command();
16
+
17
+ program
18
+ .name('jest-lineage')
19
+ .description('Comprehensive test analytics with lineage tracking and mutation testing')
20
+ .version(pkg.version, '-v, --version', 'Display version number');
21
+
22
+ // Test command - Run Jest with lineage tracking
23
+ program
24
+ .command('test [jest-args...]')
25
+ .description('Run Jest tests with lineage tracking enabled')
26
+ .option('--no-lineage', 'Disable lineage tracking')
27
+ .option('--no-performance', 'Disable performance tracking')
28
+ .option('--no-quality', 'Disable quality analysis')
29
+ .option('--config <path>', 'Path to Jest config file')
30
+ .option('--quiet, -q', 'Suppress console output')
31
+ .action(testCommand);
32
+
33
+ // Mutate command - Run mutation testing standalone
34
+ program
35
+ .command('mutate')
36
+ .description('Run mutation testing on existing lineage data')
37
+ .option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
38
+ .option('--threshold <number>', 'Mutation score threshold (%)', '80')
39
+ .option('--timeout <ms>', 'Timeout per mutation (ms)', '5000')
40
+ .option('--debug', 'Create debug mutation files instead of running tests')
41
+ .option('--debug-dir <path>', 'Directory for debug files', './mutations-debug')
42
+ .option('--operators <list>', 'Comma-separated mutation operators to enable')
43
+ .option('--verbose', 'Enable debug logging')
44
+ .action(mutateCommand);
45
+
46
+ // Report command - Generate HTML report
47
+ program
48
+ .command('report')
49
+ .description('Generate HTML report from existing lineage data')
50
+ .option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
51
+ .option('--output <path>', 'Output HTML file path', 'test-lineage-report.html')
52
+ .option('--open', 'Open report in browser after generation')
53
+ .option('--format <type>', 'Report format (html, json)', 'html')
54
+ .action(reportCommand);
55
+
56
+ // Query command - Query test coverage
57
+ program
58
+ .command('query <file> [line]')
59
+ .description('Query which tests cover specific files or lines')
60
+ .option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
61
+ .option('--json', 'Output as JSON')
62
+ .option('--format <type>', 'Output format (table, list, json)', 'table')
63
+ .action(queryCommand);
64
+
65
+ // Analyze command - Full workflow
66
+ program
67
+ .command('analyze')
68
+ .description('Full workflow: test + mutation + report')
69
+ .option('--config <path>', 'Path to Jest config file')
70
+ .option('--threshold <number>', 'Mutation score threshold (%)', '80')
71
+ .option('--output <path>', 'Output HTML file path', 'test-lineage-report.html')
72
+ .option('--open', 'Open report in browser')
73
+ .option('--skip-tests', 'Skip running tests (use existing data)')
74
+ .option('--skip-mutation', 'Skip mutation testing')
75
+ .option('--no-lineage', 'Disable lineage tracking')
76
+ .option('--no-performance', 'Disable performance tracking')
77
+ .option('--no-quality', 'Disable quality analysis')
78
+ .action(analyzeCommand);
79
+
80
+ // Show help if no command provided
81
+ if (argv.length <= 2) {
82
+ program.help();
83
+ }
84
+
85
+ // Parse and execute
86
+ await program.parseAsync(argv);
87
+ }
88
+
89
+ module.exports = { run };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Configuration Loader
3
+ * Load and merge configuration from multiple sources
4
+ * Priority: CLI args > env vars > config file > package.json > defaults
5
+ */
6
+
7
+ const { loadConfig } = require('../../config');
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+
11
+ /**
12
+ * Load full configuration with proper priority
13
+ * @param {object} cliOptions - Options from CLI arguments
14
+ * @returns {object} Merged configuration
15
+ */
16
+ function loadFullConfig(cliOptions = {}) {
17
+ // Start with defaults from config.js (includes env var processing)
18
+ let config = loadConfig();
19
+
20
+ // Load from package.json if available
21
+ const pkgConfig = loadPackageJsonConfig();
22
+ if (pkgConfig) {
23
+ config = { ...config, ...pkgConfig };
24
+ }
25
+
26
+ // Override with CLI options (highest priority)
27
+ const cliConfig = mapCliOptionsToConfig(cliOptions);
28
+ config = { ...config, ...cliConfig };
29
+
30
+ return config;
31
+ }
32
+
33
+ /**
34
+ * Map CLI options to config object
35
+ * @param {object} cliOptions - CLI options
36
+ * @returns {object} Config object
37
+ */
38
+ function mapCliOptionsToConfig(cliOptions) {
39
+ const config = {};
40
+
41
+ // Feature toggles
42
+ if (cliOptions.lineage === false) config.enableLineageTracking = false;
43
+ if (cliOptions.performance === false) config.enablePerformanceTracking = false;
44
+ if (cliOptions.quality === false) config.enableQualityAnalysis = false;
45
+
46
+ // Mutation testing settings
47
+ if (cliOptions.threshold !== undefined) {
48
+ config.mutationThreshold = parseInt(cliOptions.threshold);
49
+ }
50
+ if (cliOptions.timeout !== undefined) {
51
+ config.mutationTimeout = parseInt(cliOptions.timeout);
52
+ }
53
+ if (cliOptions.debug === true) {
54
+ config.debugMutations = true;
55
+ }
56
+ if (cliOptions.debugDir !== undefined) {
57
+ config.debugMutationDir = cliOptions.debugDir;
58
+ }
59
+ if (cliOptions.operators !== undefined) {
60
+ // Parse comma-separated operators
61
+ const operators = cliOptions.operators.split(',').map(o => o.trim());
62
+ config.mutationOperators = {
63
+ arithmetic: operators.includes('arithmetic'),
64
+ comparison: operators.includes('comparison'),
65
+ logical: operators.includes('logical'),
66
+ conditional: operators.includes('conditional'),
67
+ assignment: operators.includes('assignment'),
68
+ literals: operators.includes('literals'),
69
+ returns: operators.includes('returns'),
70
+ increments: operators.includes('increments')
71
+ };
72
+ }
73
+
74
+ // Output settings
75
+ if (cliOptions.output !== undefined) {
76
+ config.outputFile = cliOptions.output;
77
+ }
78
+ if (cliOptions.verbose === true) {
79
+ config.enableDebugLogging = true;
80
+ }
81
+ if (cliOptions.quiet === true) {
82
+ config.enableConsoleOutput = false;
83
+ }
84
+
85
+ // Skip options
86
+ if (cliOptions.skipMutation === true) {
87
+ config.enableMutationTesting = false;
88
+ }
89
+
90
+ return config;
91
+ }
92
+
93
+ /**
94
+ * Load config from package.json "jest-lineage" field
95
+ * @returns {object|null} Config from package.json or null
96
+ */
97
+ function loadPackageJsonConfig() {
98
+ try {
99
+ const pkgPath = path.join(process.cwd(), 'package.json');
100
+ if (fs.existsSync(pkgPath)) {
101
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
102
+ return pkg['jest-lineage'] || null;
103
+ }
104
+ } catch (error) {
105
+ // Silently ignore errors
106
+ }
107
+ return null;
108
+ }
109
+
110
+ module.exports = {
111
+ loadFullConfig,
112
+ mapCliOptionsToConfig,
113
+ loadPackageJsonConfig
114
+ };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Lineage Data Loader
3
+ * Load and validate .jest-lineage-data.json
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+
10
+ /**
11
+ * Load lineage data from file
12
+ * @param {string} dataPath - Path to lineage data file
13
+ * @returns {object} Parsed lineage data
14
+ */
15
+ function loadLineageData(dataPath = '.jest-lineage-data.json') {
16
+ const resolvedPath = path.resolve(process.cwd(), dataPath);
17
+
18
+ if (!fs.existsSync(resolvedPath)) {
19
+ throw new Error(
20
+ `Lineage data file not found: ${chalk.yellow(resolvedPath)}\n\n` +
21
+ `${chalk.cyan('Hint:')} Run ${chalk.green('jest-lineage test')} first to generate lineage data.`
22
+ );
23
+ }
24
+
25
+ try {
26
+ const content = fs.readFileSync(resolvedPath, 'utf8');
27
+ const data = JSON.parse(content);
28
+
29
+ // Validate structure
30
+ if (!data.timestamp || !Array.isArray(data.tests)) {
31
+ throw new Error(
32
+ `Invalid lineage data format in ${chalk.yellow(resolvedPath)}\n\n` +
33
+ `Expected format: { timestamp: number, tests: array }`
34
+ );
35
+ }
36
+
37
+ if (data.tests.length === 0) {
38
+ throw new Error(
39
+ `No test data found in ${chalk.yellow(resolvedPath)}\n\n` +
40
+ `The file exists but contains no test results. Run ${chalk.green('jest-lineage test')} to generate data.`
41
+ );
42
+ }
43
+
44
+ return data;
45
+ } catch (error) {
46
+ if (error.name === 'SyntaxError') {
47
+ throw new Error(
48
+ `Failed to parse lineage data file: ${chalk.yellow(resolvedPath)}\n\n` +
49
+ `The file contains invalid JSON. Error: ${error.message}`
50
+ );
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Process raw lineage data into format needed by MutationTester
58
+ * @param {object} rawData - Raw data from .jest-lineage-data.json
59
+ * @returns {object} Processed lineage data { filePath: { lineNumber: [testInfo, ...] } }
60
+ */
61
+ function processLineageDataForMutation(rawData) {
62
+ const processed = {};
63
+
64
+ if (!rawData.tests) {
65
+ return processed;
66
+ }
67
+
68
+ rawData.tests.forEach((test) => {
69
+ if (!test.coverage) {
70
+ return;
71
+ }
72
+
73
+ const coverageKeys = Object.keys(test.coverage);
74
+
75
+ coverageKeys.forEach((lineKey) => {
76
+ // Parse line key: "file.ts:lineNumber"
77
+ const [filePath, lineNumber, ...suffixes] = lineKey.split(':');
78
+
79
+ // Skip metadata entries (depth, performance, meta) - only process basic line coverage
80
+ if (!lineNumber || suffixes.length > 0) {
81
+ return;
82
+ }
83
+
84
+ if (!processed[filePath]) {
85
+ processed[filePath] = {};
86
+ }
87
+
88
+ if (!processed[filePath][lineNumber]) {
89
+ processed[filePath][lineNumber] = [];
90
+ }
91
+
92
+ processed[filePath][lineNumber].push({
93
+ testName: test.name,
94
+ testType: test.type || 'it',
95
+ testFile: test.testFile || 'unknown',
96
+ executionCount: test.coverage[lineKey] || 1
97
+ });
98
+ });
99
+ });
100
+
101
+ return processed;
102
+ }
103
+
104
+ /**
105
+ * Check if lineage data file exists
106
+ * @param {string} dataPath - Path to check
107
+ * @returns {boolean} True if file exists
108
+ */
109
+ function lineageDataExists(dataPath = '.jest-lineage-data.json') {
110
+ const resolvedPath = path.resolve(process.cwd(), dataPath);
111
+ return fs.existsSync(resolvedPath);
112
+ }
113
+
114
+ module.exports = {
115
+ loadLineageData,
116
+ processLineageDataForMutation,
117
+ lineageDataExists
118
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Jest Runner
3
+ * Orchestrate Jest execution with proper environment variables
4
+ */
5
+
6
+ const { spawn } = require('child_process');
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+
10
+ /**
11
+ * Run Jest with lineage tracking enabled
12
+ * @param {object} options - Jest run options
13
+ * @returns {Promise<object>} Result object with success status and exit code
14
+ */
15
+ async function runJest(options = {}) {
16
+ const {
17
+ args = [], // Jest arguments
18
+ config = null, // Path to Jest config
19
+ enableLineage = true, // Enable lineage tracking
20
+ enablePerformance = true, // Enable performance tracking
21
+ enableQuality = true, // Enable quality analysis
22
+ enableMutation = false, // Enable mutation mode
23
+ cwd = process.cwd(), // Working directory
24
+ stdio = 'inherit', // Stdio handling
25
+ quiet = false // Suppress output
26
+ } = options;
27
+
28
+ // Build Jest command
29
+ const jestPath = 'jest'; // Use npx/global jest
30
+ const jestArgs = [...args];
31
+
32
+ // Add config if specified
33
+ if (config) {
34
+ jestArgs.push('--config', config);
35
+ }
36
+
37
+ // Ensure coverage is collected (required for lineage tracking)
38
+ if (!jestArgs.includes('--coverage') && !jestArgs.includes('--no-coverage')) {
39
+ jestArgs.push('--coverage');
40
+ }
41
+
42
+ // Set environment variables for lineage tracking
43
+ const env = {
44
+ ...process.env,
45
+ JEST_LINEAGE_ENABLED: enableLineage ? 'true' : 'false',
46
+ JEST_LINEAGE_TRACKING: enableLineage ? 'true' : 'false',
47
+ JEST_LINEAGE_PERFORMANCE: enablePerformance ? 'true' : 'false',
48
+ JEST_LINEAGE_QUALITY: enableQuality ? 'true' : 'false',
49
+ JEST_LINEAGE_MUTATION: enableMutation ? 'true' : 'false',
50
+ JEST_LINEAGE_MUTATION_TESTING: 'false', // Not in mutation testing mode
51
+ };
52
+
53
+ if (!quiet) {
54
+ console.log(chalk.cyan('\n🧪 Running Jest with lineage tracking...\n'));
55
+ console.log(chalk.gray(`Command: ${jestPath} ${jestArgs.join(' ')}\n`));
56
+ }
57
+
58
+ return new Promise((resolve, reject) => {
59
+ const jest = spawn(jestPath, jestArgs, {
60
+ cwd,
61
+ env,
62
+ stdio,
63
+ shell: true
64
+ });
65
+
66
+ jest.on('close', (code) => {
67
+ if (code === 0) {
68
+ if (!quiet) {
69
+ console.log(chalk.green('\n✅ Tests completed successfully'));
70
+ }
71
+ resolve({ success: true, exitCode: code });
72
+ } else {
73
+ if (!quiet) {
74
+ console.log(chalk.red(`\n❌ Tests failed with exit code ${code}`));
75
+ }
76
+ resolve({ success: false, exitCode: code });
77
+ }
78
+ });
79
+
80
+ jest.on('error', (error) => {
81
+ console.error(chalk.red('\n❌ Failed to run Jest:'), error.message);
82
+ console.error(chalk.yellow('\nMake sure Jest is installed:'));
83
+ console.error(chalk.gray(' npm install --save-dev jest\n'));
84
+ reject(error);
85
+ });
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Validate that Jest is available
91
+ * @returns {boolean} True if Jest is available
92
+ */
93
+ function isJestAvailable() {
94
+ try {
95
+ require.resolve('jest');
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ module.exports = {
103
+ runJest,
104
+ isJestAvailable
105
+ };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Output Formatter
3
+ * Format console output with colors and formatting
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const ora = require('ora');
8
+
9
+ /**
10
+ * Print success message
11
+ * @param {string} message - Success message
12
+ */
13
+ function success(message) {
14
+ console.log(chalk.green(`✅ ${message}`));
15
+ }
16
+
17
+ /**
18
+ * Print error message
19
+ * @param {string} message - Error message
20
+ */
21
+ function error(message) {
22
+ console.error(chalk.red(`❌ ${message}`));
23
+ }
24
+
25
+ /**
26
+ * Print warning message
27
+ * @param {string} message - Warning message
28
+ */
29
+ function warning(message) {
30
+ console.log(chalk.yellow(`⚠️ ${message}`));
31
+ }
32
+
33
+ /**
34
+ * Print info message
35
+ * @param {string} message - Info message
36
+ */
37
+ function info(message) {
38
+ console.log(chalk.cyan(`ℹ️ ${message}`));
39
+ }
40
+
41
+ /**
42
+ * Print section header
43
+ * @param {string} title - Section title
44
+ */
45
+ function section(title) {
46
+ console.log(chalk.bold.cyan(`\n${title}`));
47
+ console.log(chalk.gray('═'.repeat(title.length + 2)));
48
+ }
49
+
50
+ /**
51
+ * Create a spinner for long operations
52
+ * @param {string} text - Spinner text
53
+ * @returns {object} Ora spinner instance
54
+ */
55
+ function spinner(text) {
56
+ return ora({
57
+ text,
58
+ color: 'cyan',
59
+ spinner: 'dots'
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Print mutation results summary
65
+ * @param {object} results - Mutation test results
66
+ */
67
+ function printMutationSummary(results) {
68
+ section('🧬 Mutation Testing Results');
69
+
70
+ console.log(`📊 ${chalk.bold('Total Mutations:')} ${results.totalMutations}`);
71
+ console.log(`${chalk.green('✅ Killed:')} ${results.killedMutations}`);
72
+ console.log(`${chalk.red('🔴 Survived:')} ${results.survivedMutations}`);
73
+ console.log(`${chalk.yellow('⏰ Timeout:')} ${results.timeoutMutations || 0}`);
74
+ console.log(`${chalk.gray('❌ Error:')} ${results.errorMutations || 0}`);
75
+ console.log(`${chalk.bold.cyan('🎯 Mutation Score:')} ${chalk.bold(results.mutationScore.toFixed(1))}%`);
76
+
77
+ if (results.mutationScore >= 80) {
78
+ console.log(chalk.green('\n✅ Excellent mutation score!'));
79
+ } else if (results.mutationScore >= 60) {
80
+ console.log(chalk.yellow('\n⚠️ Good mutation score, but room for improvement'));
81
+ } else {
82
+ console.log(chalk.red('\n❌ Low mutation score - consider improving test quality'));
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Print lineage data summary
88
+ * @param {object} data - Lineage data
89
+ */
90
+ function printLineageDataSummary(data) {
91
+ const testCount = data.tests.length;
92
+ const failedTests = data.tests.filter(t => t.failed).length;
93
+ const passedTests = testCount - failedTests;
94
+
95
+ console.log(chalk.cyan(`\n📊 Lineage data loaded:`));
96
+ console.log(` ${chalk.green('✓')} ${passedTests} tests passed`);
97
+ if (failedTests > 0) {
98
+ console.log(` ${chalk.red('✗')} ${failedTests} tests failed`);
99
+ }
100
+ console.log(` ${chalk.gray('📅')} Generated: ${new Date(data.timestamp).toLocaleString()}`);
101
+ }
102
+
103
+ /**
104
+ * Format file path for display
105
+ * @param {string} filePath - File path
106
+ * @returns {string} Formatted path
107
+ */
108
+ function formatPath(filePath) {
109
+ const cwd = process.cwd();
110
+ if (filePath.startsWith(cwd)) {
111
+ return chalk.gray(filePath.replace(cwd, '.'));
112
+ }
113
+ return chalk.gray(filePath);
114
+ }
115
+
116
+ module.exports = {
117
+ success,
118
+ error,
119
+ warning,
120
+ info,
121
+ section,
122
+ spinner,
123
+ printMutationSummary,
124
+ printLineageDataSummary,
125
+ formatPath
126
+ };