meld
Version:
Meld: A template language for LLM prompts
288 lines (240 loc) • 10.3 kB
text/typescript
/**
* @package
* Enhanced compact state visualization utilities.
*
* This module provides compact visualization formats for state data,
* optimized for normal test runs with minimal output.
*/
import type { IStateVisualizationService, VisualizationConfig, StateMetrics } from './IStateVisualizationService';
import type { IStateHistoryService, StateOperation, StateTransformation } from '../StateHistoryService/IStateHistoryService';
import type { IStateTrackingService, StateMetadata, StateRelationship } from '../StateTrackingService/IStateTrackingService';
import { serviceLogger } from '@core/utils/logger';
/**
* Generates compact, test-friendly visualizations of state data
*/
export class CompactStateVisualization {
constructor(
private historyService: IStateHistoryService,
private trackingService: IStateTrackingService,
private parentService: IStateVisualizationService
) {}
/**
* Generate a compact summary of a state and its hierarchy
* @param stateId - The state ID to visualize
* @returns A compact string representation
*/
public generateCompactStateSummary(stateId: string): string {
try {
// Get state metadata
const operations = this.historyService.getOperationHistory(stateId);
const createOp = operations.find(op => op.type === 'create' || op.type === 'merge');
if (!createOp || !createOp.metadata) {
return `State ${stateId}: No metadata available`;
}
const metadata = createOp.metadata;
const lineage = this.trackingService.getStateLineage(stateId);
const descendants = this.trackingService.getStateDescendants(stateId);
// Format a compact summary
const lines: string[] = [];
lines.push(`State ${stateId} (${metadata.source})`);
if (metadata.filePath) {
lines.push(` File: ${metadata.filePath}`);
}
// Add lineage info
if (lineage.length > 1) {
const lineageStr = lineage.slice(0, -1).join(' → ');
lines.push(` Ancestry: ${lineageStr} → ${stateId}`);
}
// Add descendant count
if (descendants.length > 0) {
lines.push(` Children: ${descendants.length}`);
}
// Add operation counts
const transformCount = operations.filter(op => op.type === 'transform').length;
if (transformCount > 0) {
lines.push(` Transforms: ${transformCount}`);
}
return lines.join('\n');
} catch (error) {
serviceLogger.error('Failed to generate compact state summary', { stateId, error });
return `State ${stateId}: Error generating summary`;
}
}
/**
* Generate a compact transformation summary
* @param stateId - The state ID to visualize transformations for
* @returns A compact string representation of transformations
*/
public generateCompactTransformSummary(stateId: string): string {
try {
const transformations = this.historyService.getTransformationChain(stateId);
if (transformations.length === 0) {
return `State ${stateId}: No transformations`;
}
const lines: string[] = [];
lines.push(`State ${stateId} transforms (${transformations.length}):`);
// Group transformations by operation type
const operationGroups = new Map<string, number>();
transformations.forEach(transform => {
const count = operationGroups.get(transform.operation) || 0;
operationGroups.set(transform.operation, count + 1);
});
// Add operation group summaries
Array.from(operationGroups.entries()).forEach(([operation, count]) => {
lines.push(` ${operation}: ${count}`);
});
// Add first and last transformation details
const first = transformations[0];
const last = transformations[transformations.length - 1];
lines.push(` First: ${this.formatCompactTransform(first)}`);
if (transformations.length > 1) {
lines.push(` Last: ${this.formatCompactTransform(last)}`);
}
return lines.join('\n');
} catch (error) {
serviceLogger.error('Failed to generate compact transform summary', { stateId, error });
return `State ${stateId}: Error generating transformation summary`;
}
}
/**
* Generate compact summary of all states in a hierarchy
* @param rootStateId - The root state to start from
* @returns A compact text representation
*/
public generateCompactHierarchySummary(rootStateId: string): string {
try {
const descendants = this.trackingService.getStateDescendants(rootStateId);
const allStates = [rootStateId, ...descendants];
if (allStates.length === 1) {
return this.generateCompactStateSummary(rootStateId);
}
const lines: string[] = [];
lines.push(`State hierarchy from ${rootStateId} (${allStates.length} states):`);
// Add root state
lines.push(this.generateCompactStateSummary(rootStateId));
// Add direct children only (for brevity)
const directChildren = descendants.filter(id => {
const lineage = this.trackingService.getStateLineage(id);
return lineage[lineage.length - 2] === rootStateId;
});
if (directChildren.length > 0) {
lines.push('\nDirect children:');
directChildren.forEach(childId => {
lines.push(`- ${this.generateBriefStateSummary(childId)}`);
});
}
// Add summary of remaining descendants
const remainingCount = descendants.length - directChildren.length;
if (remainingCount > 0) {
lines.push(`\nAdditional descendants: ${remainingCount}`);
}
return lines.join('\n');
} catch (error) {
serviceLogger.error('Failed to generate compact hierarchy summary', { rootStateId, error });
return `State ${rootStateId}: Error generating hierarchy summary`;
}
}
/**
* Generate compact metrics summary
* @param timeRange - Optional time range
* @returns A compact text representation of metrics
*/
public generateCompactMetricsSummary(timeRange?: { start: number; end: number }): string {
try {
const metrics = this.parentService.getMetrics(timeRange);
const lines: string[] = [];
lines.push('State metrics summary:');
lines.push(` Total states: ${metrics.totalStates}`);
// Add state type breakdown
if (Object.keys(metrics.statesByType).length > 0) {
lines.push(' State types:');
Object.entries(metrics.statesByType).forEach(([type, count]) => {
lines.push(` ${type}: ${count}`);
});
}
// Add transformation metrics
lines.push(` Avg transforms/state: ${metrics.averageTransformationsPerState.toFixed(1)}`);
lines.push(` Max transform chain: ${metrics.maxTransformationChainLength}`);
// Add structure metrics
lines.push(` Max tree depth: ${metrics.maxTreeDepth}`);
lines.push(` Avg children/state: ${metrics.averageChildrenPerState.toFixed(1)}`);
return lines.join('\n');
} catch (error) {
serviceLogger.error('Failed to generate compact metrics summary', { error });
return 'Error generating metrics summary';
}
}
/**
* Export a compact state graph summary to a file
* @param stateIds - The state IDs to include
* @param outputPath - The output file path
* @returns Success indicator
*/
public exportCompactStateGraphToFile(stateIds: string[], outputPath: string): boolean {
try {
const fs = require('fs');
const lines: string[] = [];
lines.push('# State Graph Summary');
lines.push(`Generated: ${new Date().toISOString()}`);
lines.push(`States: ${stateIds.length}`);
lines.push('');
// Add state summaries
stateIds.forEach(stateId => {
lines.push(this.generateCompactStateSummary(stateId));
lines.push('');
});
// Add transformation summaries
lines.push('# Transformation Summaries');
stateIds.forEach(stateId => {
const transformations = this.historyService.getTransformationChain(stateId);
if (transformations.length > 0) {
lines.push(this.generateCompactTransformSummary(stateId));
lines.push('');
}
});
// Add metrics
lines.push('# Metrics');
lines.push(this.generateCompactMetricsSummary());
// Write to file
fs.writeFileSync(outputPath, lines.join('\n'));
return true;
} catch (error) {
serviceLogger.error('Failed to export compact state graph', { stateIds, outputPath, error });
return false;
}
}
/**
* Format a transformation in a compact way
* @private
*/
private formatCompactTransform(transform: StateTransformation): string {
const beforeKeys = Object.keys(transform.before || {});
const afterKeys = Object.keys(transform.after || {});
const changedKeys = afterKeys.filter(key => {
const beforeVal = transform.before?.[key];
const afterVal = transform.after?.[key];
return JSON.stringify(beforeVal) !== JSON.stringify(afterVal);
});
const newKeys = afterKeys.filter(key => !beforeKeys.includes(key));
const removedKeys = beforeKeys.filter(key => !afterKeys.includes(key));
return `${transform.operation} (${changedKeys.length} changed, ${newKeys.length} added, ${removedKeys.length} removed)`;
}
/**
* Generate a very brief single-line summary of a state
* @private
*/
private generateBriefStateSummary(stateId: string): string {
try {
const operations = this.historyService.getOperationHistory(stateId);
const createOp = operations.find(op => op.type === 'create' || op.type === 'merge');
if (!createOp || !createOp.metadata) {
return `State ${stateId}`;
}
const metadata = createOp.metadata;
const transformCount = operations.filter(op => op.type === 'transform').length;
return `State ${stateId} (${metadata.source}, ${transformCount} transforms)`;
} catch (error) {
return `State ${stateId}`;
}
}
}