Skip to main content
Glama
terminal-quality-analyzer.ts23.9 kB
/** * Terminal Quality Analyzer - CLAUDE.md Foundation #6 compliant * * Focused component for validating terminal display quality, echo patterns, and formatting * Part of Production Scenario Validator refactoring to follow KISS principle */ import { WorkflowResult } from '../../tests/integration/terminal-history-framework/comprehensive-response-collector'; // Reuse the same constants for consistency export class TerminalQualityAnalyzer { // Named constants for all threshold values - CLAUDE.md Foundation #8 compliance private static readonly QUALITY_THRESHOLDS = { EXCELLENT: 0.95, GOOD: 0.8, POOR: 0.5, MINIMUM_ACCEPTABLE: 0.7 }; private static readonly VALIDATION_PATTERNS = { OLD_PROMPT_FORMAT: /[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+:[^$]*\$/, BRACKET_PROMPT_FORMAT: /\[[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\s+[^\]]+\]\$/, DUPLICATED_PROMPT: /(\$\s*){2,}/, DUPLICATED_COMMAND: /echo.*echo/, ANSI_COLOR_CODES: /\x1b\[[0-9;]*m/g, PROBLEMATIC_CONTROL_CHARS: /[\x00-\x08\x0B\x0C\x0E-\x1A\x1C-\x1F\x7F]/, CRLF_LINE_ENDINGS: /\r\n/g, LF_LINE_ENDINGS: /\n/g }; /** * Analyze command echo quality with real validation logic */ analyzeCommandEchoQuality(output: string): number { // REAL VALIDATION LOGIC: Analyze command echo quality const lines = output.split('\n'); let totalCommands = 0; let properEchoPatterns = 0; let duplicatedEchoes = 0; let cleanEchoes = 0; for (let i = 0; i < lines.length - 1; i++) { const line = lines[i]; const nextLine = lines[i + 1]; // Detect command patterns (lines ending with $) if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(line) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(line)) { totalCommands++; // Check if next line contains the command echo const commandMatch = line.match(/\$\s*(.+)$/); if (commandMatch && nextLine.includes(commandMatch[1])) { properEchoPatterns++; // Check for clean echo (no duplication) const commandText = commandMatch[1].trim(); if (commandText && nextLine.split(commandText).length === 2) { cleanEchoes++; } else { duplicatedEchoes++; } } } } // Calculate echo quality score if (totalCommands === 0) return TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT; const echoQualityRatio = properEchoPatterns / totalCommands; const duplicationPenalty = duplicatedEchoes / totalCommands; const cleanlinessBonus = cleanEchoes / totalCommands; return Math.max(0, echoQualityRatio - duplicationPenalty + (cleanlinessBonus * 0.1)); } /** * Analyze how well command results are separated */ analyzeResultSeparationQuality(output: string): number { // REAL VALIDATION LOGIC: Analyze how well command results are separated const lines = output.split('\n'); let commandResultPairs = 0; let properSeparation = 0; let blendedResults = 0; for (let i = 0; i < lines.length - 2; i++) { const line = lines[i]; // Detect command execution patterns if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(line) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(line)) { const commandMatch = line.match(/\$\s*(.+)$/); if (commandMatch) { commandResultPairs++; // Look for proper separation between command and result let separationFound = false; let resultStart = -1; // Check next few lines for result patterns for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) { const resultLine = lines[j]; // Skip empty lines and command echoes if (resultLine.trim() === '' || resultLine.includes(commandMatch[1])) { continue; } // Found potential result line if (resultStart === -1) { resultStart = j; } // Check if result is clearly separated from next prompt if (j < lines.length - 1 && (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(lines[j + 1]) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(lines[j + 1]))) { separationFound = true; break; } } if (separationFound) { properSeparation++; } else { blendedResults++; } } } } // Calculate separation quality score if (commandResultPairs === 0) return TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT; const separationRatio = properSeparation / commandResultPairs; const blendingPenalty = blendedResults / commandResultPairs; return Math.max(0, separationRatio - (blendingPenalty * 0.5)); } /** * Analyze overall terminal output cleanliness */ analyzeOverallCleanliness(output: string): number { // REAL VALIDATION LOGIC: Analyze overall terminal output cleanliness let cleanlinessScore = 1.0; let issues = 0; let totalChecks = 0; // Check CRLF line ending consistency (critical for xterm.js) totalChecks++; const crlfMatches = output.match(TerminalQualityAnalyzer.VALIDATION_PATTERNS.CRLF_LINE_ENDINGS); const lfMatches = output.match(TerminalQualityAnalyzer.VALIDATION_PATTERNS.LF_LINE_ENDINGS); const crlfRatio = crlfMatches ? crlfMatches.length / (lfMatches ? lfMatches.length : 1) : 0; if (crlfRatio < 0.8) { issues++; cleanlinessScore -= 0.3; // Heavy penalty for CRLF inconsistency } // Check for problematic control characters totalChecks++; const cleanOutput = output.replace(TerminalQualityAnalyzer.VALIDATION_PATTERNS.ANSI_COLOR_CODES, ''); if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.PROBLEMATIC_CONTROL_CHARS.test(cleanOutput)) { issues++; cleanlinessScore -= 0.2; } // Check for prompt duplication patterns totalChecks++; if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.DUPLICATED_PROMPT.test(output)) { issues++; cleanlinessScore -= 0.15; } // Check for command echo duplication totalChecks++; if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.DUPLICATED_COMMAND.test(output)) { issues++; cleanlinessScore -= 0.15; } // Check for excessive whitespace or formatting issues totalChecks++; const lines = output.split('\n'); let excessiveWhitespace = 0; let emptyLineStreaks = 0; let currentEmptyStreak = 0; for (const line of lines) { if (line.trim() === '') { currentEmptyStreak++; if (currentEmptyStreak > 3) { excessiveWhitespace++; } } else { if (currentEmptyStreak > 2) { emptyLineStreaks++; } currentEmptyStreak = 0; // Check for lines with excessive trailing whitespace if (line !== line.trimEnd() && line.trimEnd().length > 0) { excessiveWhitespace++; } } } const whitespaceIssueRatio = (excessiveWhitespace + emptyLineStreaks) / Math.max(1, lines.length / 10); if (whitespaceIssueRatio > 0.1) { issues++; cleanlinessScore -= Math.min(0.1, whitespaceIssueRatio * 0.05); } // Check for proper encoding (no garbled characters) totalChecks++; const encoding = /[\uFFFD\u00C2\u00A0]/.test(output); // Common encoding issues if (encoding) { issues++; cleanlinessScore -= 0.1; } return Math.max(0, Math.min(1, cleanlinessScore)); } /** * Validate line ending consistency */ validateLineEndingConsistency(output: string): number { const crlfCount = (output.match(TerminalQualityAnalyzer.VALIDATION_PATTERNS.CRLF_LINE_ENDINGS) || []).length; const lfCount = (output.match(TerminalQualityAnalyzer.VALIDATION_PATTERNS.LF_LINE_ENDINGS) || []).length; return crlfCount > 0 ? crlfCount / lfCount : 0; } /** * Validate prompt consistency throughout output */ validatePromptConsistency(output: string): number { // REAL VALIDATION LOGIC: Check for consistent prompt formatting throughout the output const lines = output.split('\n'); const promptPatterns = new Map<string, number>(); let totalPrompts = 0; for (const line of lines) { if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(line) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(line)) { totalPrompts++; // Extract prompt pattern (everything before the command) const promptMatch = line.match(/^(.+\$)\s*(.*)$/); if (promptMatch) { const promptPart = promptMatch[1]; const currentCount = promptPatterns.get(promptPart) || 0; promptPatterns.set(promptPart, currentCount + 1); } } } if (totalPrompts === 0) return TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT; // Calculate consistency - higher score for fewer distinct patterns const uniquePatterns = promptPatterns.size; const dominantPattern = Math.max(...promptPatterns.values()); const consistency = dominantPattern / totalPrompts; // Penalty for too many different prompt patterns const varietyPenalty = Math.max(0, (uniquePatterns - 2) * 0.1); return Math.max(0, consistency - varietyPenalty); } /** * Validate overall output structure quality */ validateOutputStructure(output: string): number { // REAL VALIDATION LOGIC: Validate overall output structure const lines = output.split('\n'); let structureScore = 1.0; let structuralIssues = 0; // Check for proper command-response structure let expectedPromptNext = false; let commandsWithoutResults = 0; let orphanedResults = 0; let properCommandResponsePairs = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(line) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(line)) { const commandMatch = line.match(/\$\s*(.+)$/); if (commandMatch && commandMatch[1].trim()) { // Found a command - look for corresponding result let foundResult = false; for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) { const nextLine = lines[j]; // Skip empty lines and direct command echoes if (nextLine.trim() === '' || nextLine === commandMatch[1]) { continue; } // If we hit another prompt before finding results, this is problematic if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(nextLine) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(nextLine)) { break; } // Found result content if (nextLine.trim().length > 0) { foundResult = true; properCommandResponsePairs++; break; } } if (!foundResult) { commandsWithoutResults++; structuralIssues++; } } } else if (line.trim().length > 0) { // Non-prompt content - check if it's an orphaned result if (expectedPromptNext) { orphanedResults++; structuralIssues++; } } } // Calculate structural quality const totalCommands = properCommandResponsePairs + commandsWithoutResults; if (totalCommands > 0) { const structureRatio = properCommandResponsePairs / totalCommands; structureScore = structureRatio; } // Apply penalties for structural issues const issueRatio = structuralIssues / Math.max(1, lines.length / 5); structureScore -= Math.min(0.5, issueRatio * 0.1); // Check for excessive fragmentation (too many short lines) const shortLines = lines.filter(line => line.trim().length > 0 && line.trim().length < 3).length; const fragmentationRatio = shortLines / Math.max(1, lines.length); if (fragmentationRatio > 0.3) { structureScore -= 0.1; } return Math.max(0, structureScore); } /** * Validate professional display standards */ validateProfessionalDisplay(workflowResult: WorkflowResult): { professionalDisplay: boolean; warnings: string[]; } { const warnings: string[] = []; const output = workflowResult.concatenatedResponses; // Check for professional terminal formatting const hasCRLF = output.includes('\r\n'); const hasProperPrompts = this.validatePromptFormatting(output); const noEchoDuplication = this.validateNoEchoDuplication(output); const cleanFormatting = this.validateCleanFormatting(output); const professionalDisplay = hasCRLF && hasProperPrompts && noEchoDuplication && cleanFormatting; if (!hasCRLF) warnings.push('Missing CRLF line endings for xterm.js compatibility'); if (!hasProperPrompts) warnings.push('Improper prompt formatting detected'); if (!noEchoDuplication) warnings.push('Echo duplication detected'); if (!cleanFormatting) warnings.push('Terminal formatting issues detected'); return { professionalDisplay, warnings }; } /** * Validate column alignment for tabular data */ validateColumnAlignment(output: string): number { // REAL VALIDATION LOGIC: Validate tabular data column alignment const lines = output.split('\n').filter(line => line.trim().length > 0); let tabularBlocks = 0; let wellAlignedBlocks = 0; // Define tabular patterns commonly seen in command output const tabularIndicators = [ /\s+PID\s+USER\s+/, // ps aux /Filesystem\s+Size\s+Used\s+Avail\s+Use%/, // df -h /Proto\s+Recv-Q\s+Send-Q/, // netstat /\s+USER\s+TTY\s+/, // who /\w+\s+\w+\s+\w+\s+\d+/ // Generic columnar pattern ]; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Check if this line indicates tabular data if (tabularIndicators.some(pattern => pattern.test(line))) { tabularBlocks++; // Analyze the next few lines for alignment consistency let alignmentScore = 0; let columnarLines = 0; for (let j = i; j < Math.min(i + 10, lines.length); j++) { const currentLine = lines[j]; const nextLine = lines[j + 1]; if (!nextLine) break; // Split by multiple spaces to identify columns const currentColumns = currentLine.split(/\s{2,}/); const nextColumns = nextLine.split(/\s{2,}/); if (currentColumns.length > 1 && nextColumns.length > 1) { columnarLines++; // Check column alignment by comparing starting positions let alignedColumns = 0; const currentPositions = this.getColumnPositions(currentLine); const nextPositions = this.getColumnPositions(nextLine); const minColumns = Math.min(currentPositions.length, nextPositions.length); for (let k = 0; k < minColumns; k++) { // Allow 2-character tolerance for alignment if (Math.abs(currentPositions[k] - nextPositions[k]) <= 2) { alignedColumns++; } } if (minColumns > 0) { alignmentScore += alignedColumns / minColumns; } } } // Calculate average alignment for this block if (columnarLines > 0) { const blockAlignment = alignmentScore / columnarLines; if (blockAlignment >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.MINIMUM_ACCEPTABLE) { wellAlignedBlocks++; } } // Skip ahead to avoid re-processing the same block i += Math.min(10, columnarLines); } } return tabularBlocks === 0 ? TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT : (wellAlignedBlocks / tabularBlocks); } /** * Analyze text processing command output quality */ analyzeTextProcessingOutput(output: string): number { // REAL VALIDATION LOGIC: Analyze text processing command output quality // Remove unused variable let qualityChecks = 0; let passedChecks = 0; // Check for common text processing command patterns const textProcessingPatterns = [ /grep.*:/, // grep with line numbers/filenames /\d+\s+\d+\s+\d+\s+\S+/, // wc output (lines words chars filename) /\|\s*head/, // piped head /\|\s*tail/, // piped tail /\|\s*sort/, // piped sort /\|\s*uniq/, // piped uniq /\|\s*awk/, // piped awk /\|\s*sed/ // piped sed ]; const lines = output.split('\n'); let textProcessingFound = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Detect text processing commands if (textProcessingPatterns.some(pattern => pattern.test(line))) { textProcessingFound = true; qualityChecks++; // Analyze result quality in subsequent lines let hasResults = false; let resultsWellFormatted = true; for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) { const resultLine = lines[j]; // Skip prompts and command echoes if (TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT.test(resultLine) || TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT.test(resultLine)) { break; } if (resultLine.trim().length > 0) { hasResults = true; // Check for formatting issues if (resultLine.includes('\u0000') || // null characters resultLine.includes('\uFFFD') || // replacement characters /[\x00-\x08\x0E-\x1F\x7F]/.test(resultLine)) { // control characters resultsWellFormatted = false; } } } if (hasResults && resultsWellFormatted) { passedChecks++; } } } // If no text processing found, assume excellent (not applicable) if (!textProcessingFound) { return TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT; } // Calculate quality based on well-formatted text processing results // const basicQuality = qualityChecks > 0 ? passedChecks / qualityChecks : 1.0; // Additional checks for text processing quality // Check for proper encoding handling qualityChecks++; const hasEncodingIssues = /[\uFFFD\u00C2\u00A0]/.test(output); if (!hasEncodingIssues) { passedChecks++; } // Check for proper line handling (no excessive truncation) qualityChecks++; const excessiveTruncation = output.includes('...') && (output.match(/\.\.\./g) || []).length > lines.length * 0.1; if (!excessiveTruncation) { passedChecks++; } // Check for proper whitespace handling qualityChecks++; const properWhitespace = !(/\t{5,}/.test(output) || /\s{20,}/.test(output)); if (properWhitespace) { passedChecks++; } return Math.max(0, passedChecks / qualityChecks); } /** * Helper method to find column starting positions in a line */ private getColumnPositions(line: string): number[] { const positions: number[] = []; const columns = line.split(/\s{2,}/); let currentPos = 0; for (let i = 0; i < columns.length; i++) { const columnIndex = line.indexOf(columns[i], currentPos); if (columnIndex !== -1) { positions.push(columnIndex); currentPos = columnIndex + columns[i].length; } } return positions; } /** * Helper methods for validation */ private validatePromptFormatting(output: string): boolean { // Check for both old and new bracket format prompts const oldFormatPattern = TerminalQualityAnalyzer.VALIDATION_PATTERNS.OLD_PROMPT_FORMAT; const bracketFormatPattern = TerminalQualityAnalyzer.VALIDATION_PATTERNS.BRACKET_PROMPT_FORMAT; return oldFormatPattern.test(output) || bracketFormatPattern.test(output); } private validateNoEchoDuplication(output: string): boolean { // Check for common echo duplication patterns const duplicatedPromptPattern = TerminalQualityAnalyzer.VALIDATION_PATTERNS.DUPLICATED_PROMPT; const duplicatedCommandPattern = TerminalQualityAnalyzer.VALIDATION_PATTERNS.DUPLICATED_COMMAND; return !duplicatedPromptPattern.test(output) && !duplicatedCommandPattern.test(output); } private validateCleanFormatting(output: string): boolean { // Allow ANSI escape sequences (color codes) but check for other problematic control characters // ANSI sequences start with \x1b[ (ESC[) and are common in terminal output const cleanOutput = output.replace(TerminalQualityAnalyzer.VALIDATION_PATTERNS.ANSI_COLOR_CODES, ''); // Remove ANSI color codes // Check for problematic control characters (excluding ANSI sequences, CR, LF, and Tab) const hasProblematicControlChars = TerminalQualityAnalyzer.VALIDATION_PATTERNS.PROBLEMATIC_CONTROL_CHARS.test(cleanOutput); return !hasProblematicControlChars; } /** * Calculate echo quality grade */ calculateEchoQuality(output: string): 'excellent' | 'good' | 'poor' { const commandEchoQuality = this.analyzeCommandEchoQuality(output); const resultSeparationQuality = this.analyzeResultSeparationQuality(output); const overallCleanliness = this.analyzeOverallCleanliness(output); if (commandEchoQuality >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT && resultSeparationQuality >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT && overallCleanliness >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT) { return 'excellent'; } else if (commandEchoQuality >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.GOOD && resultSeparationQuality >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.GOOD && overallCleanliness >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.GOOD) { return 'good'; } else { return 'poor'; } } /** * Calculate terminal formatting grade */ calculateTerminalFormatting(output: string): 'clean' | 'acceptable' | 'poor' { const lineEndingConsistency = this.validateLineEndingConsistency(output); const promptConsistency = this.validatePromptConsistency(output); const outputStructure = this.validateOutputStructure(output); if (lineEndingConsistency >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT && promptConsistency >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT && outputStructure >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.EXCELLENT) { return 'clean'; } else if (lineEndingConsistency >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.GOOD && promptConsistency >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.GOOD && outputStructure >= TerminalQualityAnalyzer.QUALITY_THRESHOLDS.GOOD) { return 'acceptable'; } else { return 'poor'; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LightspeedDMS/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server