@ooples/token-optimizer-mcp
Version:
Intelligent context window optimization for Claude Code - store content externally via caching and compression, freeing up your context window for what matters
443 lines โข 16.3 kB
JavaScript
/**
* Smart Test Tool - 80% Token Reduction
*
* Wraps Jest to provide:
* - Incremental test runs (only affected tests)
* - Cached test results
* - Failure summarization (not full logs)
* - Coverage delta tracking
*/
import { spawn } from 'child_process';
import { CacheEngine } from '../../core/cache-engine.js';
import { TokenCounter } from '../../core/token-counter.js';
import { MetricsCollector } from '../../core/metrics.js';
import { createHash } from 'crypto';
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
export class SmartTest {
cache;
cacheNamespace = 'smart_test';
projectRoot;
constructor(cache, _tokenCounter, _metrics, projectRoot) {
this.cache = cache;
this.projectRoot = projectRoot || process.cwd();
}
/**
* Run tests with smart caching and output reduction
*/
async run(options = {}) {
const { pattern, onlyChanged = false, force = false, coverage = false, watch = false, maxCacheAge = 3600, } = options;
// Generate cache key based on test files and their content
const cacheKey = await this.generateCacheKey(pattern);
// Check cache first (unless force or watch mode)
if (!force && !watch) {
const cached = this.getCachedResult(cacheKey, maxCacheAge);
if (cached) {
return this.formatCachedOutput(cached);
}
}
// Run Jest
const result = await this.runJest({
pattern,
onlyChanged,
coverage,
watch,
});
// Cache the result
if (!watch) {
this.cacheResult(cacheKey, result);
}
// Transform to smart output
return this.transformOutput(result);
}
/**
* Run Jest and capture results
*/
async runJest(options) {
const args = ['--json'];
if (options.pattern) {
// Convert Windows backslashes to forward slashes
let normalizedPattern = options.pattern.replace(/\\/g, '/');
// Escape regex special characters for Jest pattern matching
// Preserve wildcards (*) as .* for regex, but escape other special chars
normalizedPattern = normalizedPattern
.replace(/\./g, '\\.') // Escape dots
.replace(/\+/g, '\\+') // Escape plus
.replace(/\?/g, '\\?') // Escape question mark
.replace(/\[/g, '\\[') // Escape square brackets
.replace(/\]/g, '\\]')
.replace(/\(/g, '\\(') // Escape parentheses
.replace(/\)/g, '\\)')
.replace(/\{/g, '\\{') // Escape curly braces
.replace(/\}/g, '\\}')
.replace(/\^/g, '\\^') // Escape caret
.replace(/\$/g, '\\$') // Escape dollar
.replace(/\|/g, '\\|') // Escape pipe
.replace(/\*/g, '.*'); // Convert wildcard * to .* for regex
args.push('--testPathPattern=' + normalizedPattern);
}
if (options.onlyChanged) {
args.push('--onlyChanged');
}
if (options.coverage) {
args.push('--coverage', '--coverageReporters=json-summary');
}
if (options.watch) {
args.push('--watch');
}
args.push('--no-colors');
return new Promise((resolve, reject) => {
let stdout = '';
let stderr = '';
const jest = spawn('npm', ['run', 'test', '--', ...args], {
cwd: this.projectRoot,
shell: true,
});
jest.stdout.on('data', (data) => {
stdout += data.toString();
});
jest.stderr.on('data', (data) => {
stderr += data.toString();
});
jest.on('close', (_code) => {
try {
// Jest writes JSON to stdout even on failure
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const result = JSON.parse(jsonMatch[0]);
resolve(result);
}
else {
reject(new Error(`Failed to parse Jest output: ${stderr || stdout}`));
}
}
catch (err) {
reject(new Error(`Failed to parse Jest output: ${err instanceof Error ? err.message : String(err)}`));
}
});
jest.on('error', (err) => {
reject(err);
});
});
}
/**
* Generate cache key based on test file contents
*/
async generateCacheKey(pattern) {
const hash = createHash('sha256');
hash.update(this.cacheNamespace);
hash.update(pattern || 'all');
// Hash package.json to detect dependency changes
const packageJsonPath = join(this.projectRoot, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = readFileSync(packageJsonPath, 'utf-8');
hash.update(packageJson);
}
// Hash jest config to detect config changes
const jestConfigPath = join(this.projectRoot, 'jest.config.js');
if (existsSync(jestConfigPath)) {
const jestConfig = readFileSync(jestConfigPath, 'utf-8');
hash.update(jestConfig);
}
return `${this.cacheNamespace}:${hash.digest('hex')}`;
}
/**
* Get cached result if available and fresh
*/
getCachedResult(key, maxAge) {
const cached = this.cache.get(key);
if (!cached) {
return null;
}
try {
const result = JSON.parse(cached);
const age = (Date.now() - result.cachedAt) / 1000;
if (age <= maxAge) {
return result;
}
}
catch (err) {
// Invalid cache entry
return null;
}
return null;
}
/**
* Cache test result
*/
cacheResult(key, result) {
const toCache = {
...result,
cachedAt: Date.now(),
};
const dataToCache = JSON.stringify(toCache);
const originalSize = JSON.stringify(result).length;
const compactSize = this.estimateCompactSize(result);
this.cache.set(key, dataToCache, originalSize, compactSize);
}
/**
* Transform full Jest output to smart output
*/
transformOutput(result, fromCache = false) {
const failures = this.extractFailures(result);
const newTests = this.detectNewTests(result);
const coverageDelta = this.calculateCoverageDelta(result);
const originalSize = JSON.stringify(result).length;
const compactSize = this.estimateCompactSize(result);
return {
summary: {
total: result.numTotalTests,
passed: result.numPassedTests,
failed: result.numFailedTests,
skipped: result.numPendingTests,
duration: result.endTime - result.startTime,
fromCache,
},
failures,
coverageDelta,
newTests,
metrics: {
originalTokens: Math.ceil(originalSize / 4),
compactedTokens: Math.ceil(compactSize / 4),
reductionPercentage: Math.round(((originalSize - compactSize) / originalSize) * 100),
},
};
}
/**
* Format cached output
*/
formatCachedOutput(result) {
return this.transformOutput(result, true);
}
/**
* Extract only failures with concise error messages
*/
extractFailures(result) {
const failures = [];
for (const testFile of result.testResults || []) {
if (testFile.status === 'failed') {
for (const assertion of testFile.assertionResults || []) {
if (assertion.status === 'failed') {
// Extract concise error message
const error = this.extractConciseError(assertion.failureMessages);
failures.push({
testFile: testFile.name,
testName: assertion.title,
error,
location: this.extractErrorLocation(assertion.failureMessages),
});
}
}
}
}
return failures;
}
/**
* Extract concise error message from Jest failure
*/
extractConciseError(messages) {
if (!messages || messages.length === 0) {
return 'Unknown error';
}
// Join all messages
const fullMessage = messages.join('\n');
// Extract the most important lines
const lines = fullMessage.split('\n');
const importantLines = lines.filter((line) => {
// Keep expect() lines, received/expected, and error messages
return (line.includes('expect') ||
line.includes('Received:') ||
line.includes('Expected:') ||
line.includes('Error:') ||
line.includes('at '));
});
// Limit to first 5 important lines
return (importantLines.slice(0, 5).join('\n').trim() || fullMessage.slice(0, 200));
}
/**
* Extract error location from stack trace
*/
extractErrorLocation(messages) {
const fullMessage = messages.join('\n');
const lines = fullMessage.split('\n');
for (const line of lines) {
if (line.trim().startsWith('at ') && !line.includes('node_modules')) {
// Extract file:line:column
const match = line.match(/\(([^)]+):(\d+):(\d+)\)/);
if (match) {
return `${match[1]}:${match[2]}:${match[3]}`;
}
}
}
return undefined;
}
/**
* Detect new tests (simplified version - would need test history)
*/
detectNewTests(_result) {
// In a real implementation, we'd compare with previous run
// For now, return empty array
return [];
}
/**
* Calculate coverage delta (simplified version - would need previous coverage)
*/
calculateCoverageDelta(result) {
// Guard against missing coverage data
if (!result.coverageMap || !result.coverageMap.total) {
return undefined;
}
const total = result.coverageMap.total;
// Verify all coverage metrics exist
if (!total.statements ||
!total.branches ||
!total.functions ||
!total.lines) {
return undefined;
}
// In a real implementation, we'd compare with previous run
// For now, return current coverage as delta
return {
statements: total.statements.pct,
branches: total.branches.pct,
functions: total.functions.pct,
lines: total.lines.pct,
};
}
/**
* Estimate compact output size for token calculation
*/
estimateCompactSize(result) {
// Count only summary and failures, not full test results
const summary = {
total: result.numTotalTests,
passed: result.numPassedTests,
failed: result.numFailedTests,
skipped: result.numPendingTests,
};
const failures = this.extractFailures(result);
return JSON.stringify({ summary, failures }).length;
}
/**
* Close cache connection
*/
close() {
this.cache.close();
}
}
/**
* Factory function for creating SmartTest with shared resources (benchmark usage)
*/
export function getSmartTestTool(cache, tokenCounter, metrics, projectRoot) {
return new SmartTest(cache, tokenCounter, metrics, projectRoot);
}
/**
* CLI-friendly function for running smart tests
*/
export async function runSmartTest(options = {}) {
// Create standalone resources for CLI usage
const cache = new CacheEngine(join(homedir(), '.token-optimizer-cache', 'cache.db'));
const tokenCounter = new TokenCounter();
const metrics = new MetricsCollector();
const smartTest = new SmartTest(cache, tokenCounter, metrics, options.projectRoot);
try {
const result = await smartTest.run(options);
// Format as human-readable output
let output = `\n๐งช Smart Test Results ${result.summary.fromCache ? '(cached)' : ''}\n`;
output += `${'='.repeat(50)}\n\n`;
// Summary
output += `Summary:\n`;
output += ` Total: ${result.summary.total}\n`;
output += ` โ Passed: ${result.summary.passed}\n`;
output += ` โ Failed: ${result.summary.failed}\n`;
output += ` โ Skipped: ${result.summary.skipped}\n`;
output += ` Duration: ${(result.summary.duration / 1000).toFixed(2)}s\n\n`;
// Failures
if (result.failures.length > 0) {
output += `Failures:\n`;
for (const failure of result.failures) {
output += `\n โ ${failure.testName}\n`;
output += ` File: ${failure.testFile}\n`;
if (failure.location) {
output += ` Location: ${failure.location}\n`;
}
output += ` Error:\n`;
const errorLines = failure.error.split('\n');
for (const line of errorLines) {
output += ` ${line}\n`;
}
}
output += '\n';
}
// Coverage delta
if (result.coverageDelta) {
output += `Coverage:\n`;
output += ` Statements: ${result.coverageDelta.statements.toFixed(2)}%\n`;
output += ` Branches: ${result.coverageDelta.branches.toFixed(2)}%\n`;
output += ` Functions: ${result.coverageDelta.functions.toFixed(2)}%\n`;
output += ` Lines: ${result.coverageDelta.lines.toFixed(2)}%\n\n`;
}
// New tests
if (result.newTests.length > 0) {
output += `New Tests:\n`;
for (const test of result.newTests) {
output += ` + ${test}\n`;
}
output += '\n';
}
// Metrics
output += `Token Reduction:\n`;
output += ` Original: ${result.metrics.originalTokens} tokens\n`;
output += ` Compacted: ${result.metrics.compactedTokens} tokens\n`;
output += ` Reduction: ${result.metrics.reductionPercentage}%\n`;
return output;
}
finally {
smartTest.close();
}
}
// MCP Tool definition
export const SMART_TEST_TOOL_DEFINITION = {
name: 'smart_test',
description: 'Run tests with intelligent caching, coverage tracking, and incremental test execution',
inputSchema: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'Pattern to match test files',
},
onlyChanged: {
type: 'boolean',
description: 'Run only tests that changed since last run',
default: false,
},
force: {
type: 'boolean',
description: 'Force full test run (ignore cache)',
default: false,
},
coverage: {
type: 'boolean',
description: 'Collect coverage information',
default: false,
},
watch: {
type: 'boolean',
description: 'Watch mode for continuous testing',
default: false,
},
projectRoot: {
type: 'string',
description: 'Project root directory',
},
maxCacheAge: {
type: 'number',
description: 'Maximum cache age in seconds (default: 300)',
default: 300,
},
},
},
};
//# sourceMappingURL=smart-test.js.map