Skip to main content

Inspection Consistency

Overview​

This document explains how the MCP JetBrains Code Inspections server ensures consistent, deterministic results across different runs, environments, and configurations.

Consistency Principles​

1. Deterministic Execution​

Every inspection run with the same inputs should produce identical outputs:

// From InspectionExecutor.ts
async inspect(params: InspectionParams): Promise<InspectionResult> {
// Always use the same execution flow
const ide = await this.ideSelector.select();
const profile = await this.profileManager.resolve(params);
const command = this.strategy.buildCommand(params);
const result = await this.execute(command);
return this.processor.process(result);
}

2. Isolated Execution Environment​

Each inspection runs in an isolated environment:

// From InspectionStrategy.ts
private createIsolatedEnvironment(): EnvironmentConfig {
return {
// Separate config directory to avoid conflicts
configPath: path.join(os.tmpdir(), 'idea-config', process.pid.toString()),
// Separate system directory for caches
systemPath: path.join(os.tmpdir(), 'idea-system', process.pid.toString()),
// Consistent JVM options
jvmOptions: [
'-Xmx2048m',
'-XX:+UseG1GC',
'-Djava.awt.headless=true'
]
};
}

3. Stable Output Ordering​

Results are consistently ordered:

// From ResultParser.ts
private sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
return diagnostics.sort((a, b) => {
// First by file path
const fileCompare = a.file.localeCompare(b.file);
if (fileCompare !== 0) return fileCompare;

// Then by line number
const lineCompare = a.line - b.line;
if (lineCompare !== 0) return lineCompare;

// Finally by column
return (a.column || 0) - (b.column || 0);
});
}

Configuration Management​

Environment Variable Consistency​

All configuration is managed through environment variables:

# Consistent configuration across runs
export FORCE_INSPECT_PATH="/Applications/WebStorm.app/Contents/bin/inspect.sh"
export FORCE_PROJECT_ROOT="/path/to/project"
export FORCE_PROFILE_PATH="/path/to/profile.xml"
export INSPECTION_TIMEOUT="120000"
export EXCLUDE_INSPECTIONS="SpellCheckingInspection"
export RESPONSE_FORMAT="json"

Profile Management​

The InspectionProfileManager ensures consistent profile usage:

export class InspectionProfileManager {
private profileCache = new Map<string, string>();

async resolveProfile(options: ProfileOptions): Promise<string> {
const cacheKey = this.getCacheKey(options);

// Return cached profile for consistency
if (this.profileCache.has(cacheKey)) {
return this.profileCache.get(cacheKey)!;
}

const profile = await this.findProfile(options);
this.profileCache.set(cacheKey, profile);
return profile;
}
}

Handling Non-Deterministic Elements​

1. Timestamps​

Remove or normalize timestamps from output:

class DiagnosticNormalizer {
normalize(diagnostic: RawDiagnostic): NormalizedDiagnostic {
// Remove timestamp fields
const { timestamp, ...rest } = diagnostic;

return {
...rest,
// Use stable identifiers
id: this.generateStableId(rest),
};
}

private generateStableId(diagnostic: any): string {
// Create deterministic ID from content
const content = `${diagnostic.file}:${diagnostic.line}:${diagnostic.column}:${diagnostic.inspection}`;
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 8);
}
}

2. File System Paths​

Normalize all paths to be relative:

class PathNormalizer {
normalize(absolutePath: string, projectRoot: string): string {
// Always use forward slashes
const normalized = absolutePath.replace(/\\/g, '/');

// Make relative to project root
if (normalized.startsWith(projectRoot)) {
return path.relative(projectRoot, normalized).replace(/\\/g, '/');
}

return normalized;
}
}

3. Random Inspection Order​

Force consistent inspection execution order:

class InspectionOrdering {
orderInspections(inspections: Inspection[]): Inspection[] {
// Sort by inspection ID for consistency
return inspections.sort((a, b) => a.id.localeCompare(b.id));
}
}

Version Compatibility​

IDE Version Management​

Track and handle IDE version differences:

interface IDEVersion {
major: number;
minor: number;
patch: number;
build: string;
}

class VersionCompatibility {
ensureCompatibility(ide: IDE): void {
const version = this.parseVersion(ide.version);

// Warn about version differences
if (this.hasBreakingChanges(version)) {
this.logger.warn(`IDE version ${ide.version} may produce different results`);
}

// Apply version-specific adjustments
this.applyVersionAdjustments(version);
}
}

Profile Version Compatibility​

Handle profile format changes:

<!-- Profile with version information -->
<profile version="1.0">
<option name="myName" value="Unified" />
<option name="PROFILE_VERSION" value="2023.3" />

<!-- Version-specific settings -->
<inspection_tool class="ModernInspection" enabled="true" level="WARNING">
<!-- Only in 2023.3+ -->
<option name="requiresVersion" value="2023.3" />
</inspection_tool>
</profile>

Caching Strategy​

Result Caching​

Implement deterministic caching:

class InspectionCache {
private cache = new Map<string, CachedResult>();

getCacheKey(params: InspectionParams): string {
// Create deterministic cache key
const elements = [
params.targetPath,
params.profilePath || 'default',
params.filter?.only?.sort().join(',') || '',
params.filter?.exclude?.sort().join(',') || '',
this.getFileHash(params.targetPath),
];

return crypto.createHash('sha256').update(elements.join('|')).digest('hex');
}

private getFileHash(targetPath: string): string {
// Hash file contents for cache invalidation
const content = fs.readFileSync(targetPath, 'utf8');
return crypto.createHash('md5').update(content).digest('hex');
}
}

Cache Invalidation​

Properly invalidate caches:

class CacheInvalidator {
shouldInvalidate(cached: CachedResult, current: FileInfo): boolean {
return (
// File modified
cached.fileHash !== current.hash ||
// Profile changed
cached.profileHash !== current.profileHash ||
// IDE version changed
cached.ideVersion !== current.ideVersion ||
// Cache expired
Date.now() - cached.timestamp > this.maxAge
);
}
}

Testing Consistency​

Regression Testing​

Ensure consistent results across changes:

describe('Inspection Consistency', () => {
it('should produce identical results for multiple runs', async () => {
const params = {
targetPath: 'test/fixtures/sample.ts',
profilePath: 'test/fixtures/profile.xml',
};

const result1 = await executor.inspect(params);
const result2 = await executor.inspect(params);

expect(result1).toEqual(result2);
});

it('should maintain consistency across IDE restarts', async () => {
const result1 = await executor.inspect(params);

// Simulate IDE restart
await executor.restart();

const result2 = await executor.inspect(params);

expect(normalizeResults(result1)).toEqual(normalizeResults(result2));
});
});

Consistency Metrics​

Track consistency over time:

class ConsistencyMonitor {
async checkConsistency(params: InspectionParams): Promise<ConsistencyReport> {
const results: InspectionResult[] = [];

// Run multiple times
for (let i = 0; i < 5; i++) {
results.push(await this.executor.inspect(params));
}

return {
consistent: this.areResultsIdentical(results),
variance: this.calculateVariance(results),
differences: this.findDifferences(results),
};
}
}

Common Consistency Issues​

1. Timing-Dependent Results​

Problem: Some inspections depend on timing or external resources.

Solution:

class TimingNormalizer {
normalize(diagnostic: Diagnostic): Diagnostic {
// Remove timing-sensitive information
if (diagnostic.message.includes('took')) {
diagnostic.message = diagnostic.message.replace(/took \d+ms/, 'took Xms');
}
return diagnostic;
}
}

2. Plugin Loading Order​

Problem: Plugins may load in different orders.

Solution:

class PluginManager {
async loadPlugins(): Promise<Plugin[]> {
const plugins = await this.discoverPlugins();
// Always load in alphabetical order
return plugins.sort((a, b) => a.id.localeCompare(b.id));
}
}

3. Locale Differences​

Problem: Different system locales affect messages.

Solution:

# Force consistent locale
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

Best Practices for Consistency​

1. Use Explicit Configuration​

Always specify configuration explicitly:

{
"env": {
"FORCE_INSPECT_PATH": "/path/to/inspect.sh",
"FORCE_PROJECT_ROOT": "/path/to/project",
"FORCE_PROFILE_PATH": "/path/to/profile.xml",
"INSPECTION_TIMEOUT": "120000"
}
}

2. Version Lock​

Lock IDE and plugin versions:

{
"requirements": {
"ide": "WebStorm 2023.3.2",
"plugins": {
"eslint": "2.4.0",
"prettier": "1.2.3"
}
}
}

3. Isolated Execution​

Run inspections in isolated environments:

# Use Docker for complete isolation
docker run -v $(pwd):/project \
mcp-jetbrains-inspections \
inspect /project/src

4. Result Verification​

Implement result verification:

class ResultVerifier {
verify(result: InspectionResult): boolean {
return (
this.hasExpectedStructure(result) && this.hasValidSeverities(result) && this.hasConsistentPaths(result) && this.hasNoDuplicates(result)
);
}
}

Monitoring and Debugging​

Consistency Logging​

Enable detailed logging for debugging:

class ConsistencyLogger {
logInspectionRun(params: InspectionParams, result: InspectionResult): void {
const entry = {
timestamp: new Date().toISOString(),
params: this.sanitizeParams(params),
result: {
totalProblems: result.totalProblems,
checksum: this.calculateChecksum(result),
},
environment: {
ide: process.env.FORCE_INSPECT_PATH,
profile: process.env.FORCE_PROFILE_PATH,
nodeVersion: process.version,
},
};

this.logger.info('Inspection run', entry);
}
}

Diff Analysis​

Analyze differences between runs:

class DiffAnalyzer {
analyze(result1: InspectionResult, result2: InspectionResult): DiffReport {
return {
added: this.findAdded(result1, result2),
removed: this.findRemoved(result1, result2),
changed: this.findChanged(result1, result2),
summary: this.generateSummary(result1, result2),
};
}
}

Performance vs Consistency Trade-offs​

Caching Strategies​

Balance performance with consistency:

enum CacheStrategy {
NONE = 'none', // Always fresh, slowest
CONSERVATIVE = 'conservative', // Cache with strict invalidation
AGGRESSIVE = 'aggressive', // Cache aggressively, fastest
}

class CacheManager {
constructor(private strategy: CacheStrategy) {}

async get(key: string): Promise<InspectionResult | null> {
switch (this.strategy) {
case CacheStrategy.NONE:
return null;
case CacheStrategy.CONSERVATIVE:
return this.getIfValid(key);
case CacheStrategy.AGGRESSIVE:
return this.getRegardless(key);
}
}
}

Parallel Execution​

Maintain consistency with parallel processing:

class ParallelExecutor {
async inspectParallel(files: string[]): Promise<InspectionResult[]> {
// Sort files for consistent ordering
const sortedFiles = files.sort();

// Process in deterministic batches
const batchSize = 4;
const results: InspectionResult[] = [];

for (let i = 0; i < sortedFiles.length; i += batchSize) {
const batch = sortedFiles.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map((file) => this.inspect(file)));
results.push(...batchResults);
}

return results;
}
}

Future Improvements​

Planned Enhancements​

  1. Checksum Verification: Verify result integrity with checksums
  2. Reproducible Builds: Support fully reproducible inspection runs
  3. Consistency Testing: Automated consistency regression tests
  4. Drift Detection: Detect when results start to drift

Research Areas​

  1. Machine Learning: Detect and correct inconsistencies automatically
  2. Distributed Inspection: Consistent results across distributed systems
  3. Version Migration: Automatic migration between IDE versions

References​