UNPKG

meld

Version:

Meld: A template language for LLM prompts

357 lines (301 loc) 13.3 kB
#!/usr/bin/env node import { container } from 'tsyringe'; import { IStateService } from '@services/state/StateService/IStateService.js'; import { IParserService } from '@services/pipeline/ParserService/IParserService.js'; import { IInterpreterService } from '@services/pipeline/InterpreterService/IInterpreterService.js'; import { IFileSystemService } from '@services/fs/FileSystemService/IFileSystemService.js'; import { IDirectiveService } from '@services/pipeline/DirectiveService/IDirectiveService.js'; import { MeldResolutionError } from '@core/errors/MeldResolutionError.js'; import path from 'path'; import chalk from 'chalk'; import fs from 'fs/promises'; import { initializeContextDebugger, StateVisualizationService } from '../../tests/utils/debug/index.js'; import { IPathService } from '@services/fs/PathService/IPathService.js'; // Import concrete classes for direct instantiation import { StateService } from '@services/state/StateService/StateService.js'; import { ParserService } from '@services/pipeline/ParserService/ParserService.js'; import { InterpreterService } from '@services/pipeline/InterpreterService/InterpreterService.js'; import { FileSystemService } from '@services/fs/FileSystemService/FileSystemService.js'; import { DirectiveService } from '@services/pipeline/DirectiveService/DirectiveService.js'; import { PathService } from '@services/fs/PathService/PathService.js'; import { NodeFileSystem } from '@services/fs/FileSystemService/NodeFileSystem.js'; import { PathOperationsService } from '@services/fs/FileSystemService/PathOperationsService.js'; import { ResolutionService } from '@services/resolution/ResolutionService/ResolutionService.js'; import { ValidationService } from '@services/resolution/ValidationService/ValidationService.js'; import { CircularityService } from '@services/resolution/CircularityService/CircularityService.js'; interface DebugTransformOptions { filePath: string; directiveType?: string; outputFormat?: 'text' | 'json' | 'mermaid'; outputFile?: string; includeContent?: boolean; } /** * Debug node transformations through the pipeline */ export async function debugTransformCommand(options: DebugTransformOptions): Promise<void> { const { filePath, directiveType, outputFormat = 'text', outputFile, includeContent = false } = options; try { // Try to get services from DI container (for tests) let stateService, fileSystemService, parserService, directiveService, interpreterService; try { // For tests, try to get services from container stateService = container.resolve('StateService'); fileSystemService = container.resolve('FileSystemService'); parserService = container.resolve('ParserService'); directiveService = container.resolve('DirectiveService'); interpreterService = container.resolve('InterpreterService'); console.log(chalk.blue('Using services from dependency injection container')); } catch (error) { // For runtime use direct instantiation console.log(chalk.blue('Creating services directly...')); // Create the path operations service (needed for FileSystemService) const pathOps = new PathOperationsService(); // Create the node file system implementation const nodeFs = new NodeFileSystem(); // Create the base services first stateService = new StateService(); fileSystemService = new FileSystemService(pathOps, nodeFs); parserService = new ParserService(); const pathService = new PathService(); // Initialize the path service pathService.initialize(fileSystemService, pathOps); // Create the resolution service const resolutionService = new ResolutionService( stateService, fileSystemService, parserService, pathService ); // Create the validation service const validationService = new ValidationService(); // Create the interpreter service first interpreterService = new InterpreterService(); // Create the circularity service const circularityService = new CircularityService(); // Create the directive service directiveService = new DirectiveService(); directiveService.initialize( validationService, stateService, pathService, fileSystemService, parserService, interpreterService, // InterpreterService circularityService, // CircularityService resolutionService ); // Initialize the interpreter service interpreterService.initialize(directiveService, stateService); // Register default handlers directiveService.registerDefaultHandlers(); } // Set up state with proper paths const resolvedPath = path.resolve(filePath); const projectPath = path.dirname(resolvedPath); console.log(chalk.blue('Project path:'), projectPath); stateService.setPathVar('PROJECTPATH', projectPath); stateService.setPathVar('.', projectPath); stateService.setCurrentFilePath(filePath); // Try to get the home directory try { const homePath = process.env.HOME || process.env.USERPROFILE; if (homePath) { stateService.setPathVar('HOMEPATH', homePath); stateService.setPathVar('~', homePath); } } catch (error) { console.warn(chalk.yellow('Could not set home path variables')); } // Enable transformation tracking if (!interpreterService.canHandleTransformations()) { console.error(chalk.red('This interpreter does not support transformations')); console.error(chalk.yellow('Make sure you have built the codebase with "npm run build" before running debug commands')); return; } console.log(chalk.blue(`Debugging transformations for ${filePath}`)); if (directiveType) { console.log(chalk.blue(`Focusing on directive type: ${directiveType}`)); } // Read and process the file if (!await fileSystemService.exists(filePath)) { console.error(chalk.red(`File not found: ${filePath}`)); return; } const fileContent = await fileSystemService.readFile(filePath); // Use parse instead of parseWithLocations to match test expectations const nodes = await parserService.parse(fileContent); // Create a tracking proxy for directiveService const transformations: Array<{ directiveType: string; nodeType: string; originalNode: any; transformedNode: any; timestamp: number; success: boolean; error?: string; }> = []; // Track the transformations by hooking into the processDirective method const originalProcessDirective = directiveService.processDirective.bind(directiveService); directiveService.processDirective = async (node, context) => { const startTime = Date.now(); let success = true; let error: string | undefined; let result; const nodeType = node.type; const directiveKind = node.directive?.kind || 'unknown'; // Skip if we're filtering by directive type and this doesn't match if (directiveType && directiveKind !== directiveType) { return originalProcessDirective(node, context); } try { result = await originalProcessDirective(node, context); } catch (e) { success = false; error = e instanceof Error ? e.message : String(e); throw e; } finally { // Record the transformation attempt transformations.push({ directiveType: directiveKind, nodeType, originalNode: includeContent ? node : { type: nodeType, kind: directiveKind }, transformedNode: includeContent && result && 'replacement' in result ? result.replacement : { type: 'unknown', replaced: 'replacement' in result }, timestamp: startTime, success, error }); } return result; }; console.log(chalk.blue('Processing file to track transformations...')); // Interpret the file with transformation enabled await interpreterService.interpret(nodes, { initialState: stateService, filePath, mergeState: true }); console.log(chalk.green(`Processing complete. Captured ${transformations.length} transformations.`)); // Generate output based on format let output: string; if (outputFormat === 'json') { output = JSON.stringify(transformations, null, 2); } else if (outputFormat === 'mermaid') { // Initialize the visualization service for Mermaid output try { const visualizationService = initializeContextDebugger().getVisualizationService(); output = visualizationService.transformToMermaid(transformations, { includeTimestamps: true, includeDirectiveTypes: true }); } catch (error) { console.warn(chalk.yellow('Could not initialize visualization service. Using fallback mermaid generator.')); output = generateMermaidDiagram(transformations); } } else { // Default text format output = generateTextReport(transformations); } // Output the results if (outputFile) { await fs.writeFile(outputFile, output); console.log(chalk.green(`Transformation results saved to ${outputFile}`)); } else { console.log(chalk.cyan('\nTransformation Report:\n')); console.log(output); } } catch (error) { if (error instanceof MeldResolutionError) { console.error(chalk.red(`Resolution error: ${error.message}`)); if (error.details) { console.error(chalk.red(`Details: ${JSON.stringify(error.details, null, 2)}`)); } } else { console.error(chalk.red(`Error debugging transformations: ${error instanceof Error ? error.message : String(error)}`)); if (error instanceof Error && error.stack) { console.error(chalk.dim(error.stack)); } } console.error(chalk.yellow('If this is a module resolution error, make sure you have built the codebase with "npm run build" before running debug commands')); } } /** * Generate a text report of the transformations */ function generateTextReport(transformations: Array<any>): string { if (transformations.length === 0) { return 'No transformations captured.'; } // Group by directive type const byDirectiveType = new Map<string, typeof transformations>(); for (const transform of transformations) { if (!byDirectiveType.has(transform.directiveType)) { byDirectiveType.set(transform.directiveType, []); } byDirectiveType.get(transform.directiveType)!.push(transform); } const lines: string[] = []; for (const [directiveType, transforms] of byDirectiveType.entries()) { const successCount = transforms.filter(t => t.success).length; const failCount = transforms.length - successCount; lines.push(chalk.cyan(`\nDirective Type: ${directiveType}`)); lines.push(chalk.cyan(`Total transformations: ${transforms.length} (${successCount} success, ${failCount} fail)`)); transforms.forEach((transform, i) => { const statusColor = transform.success ? chalk.green : chalk.red; const status = transform.success ? 'SUCCESS' : 'FAILED'; lines.push(chalk.dim(`\nTransformation #${i + 1}:`)); lines.push(`Status: ${statusColor(status)}`); lines.push(`Node Type: ${transform.nodeType}`); if (transform.success) { if (transform.transformedNode.type) { lines.push(`Transformed To: ${transform.transformedNode.type}`); } else if (transform.transformedNode.replaced) { lines.push(`Replaced: ${transform.transformedNode.replaced}`); } } else if (transform.error) { lines.push(`Error: ${chalk.red(transform.error)}`); } }); } return lines.join('\n'); } /** * Generate a Mermaid diagram of the transformations */ function generateMermaidDiagram(transformations: Array<any>): string { if (transformations.length === 0) { return 'graph TD\n A[No transformations] --> B[captured]'; } const lines = ['graph TD']; // Add nodes for each transformation transformations.forEach((transform, i) => { const nodeId = `node_${i}`; const resultId = `result_${i}`; const label = `${transform.directiveType}\\n${transform.nodeType}`; const resultLabel = transform.success ? 'Transformed' : `Failed\\n${transform.error ? transform.error.substring(0, 20) : 'Error'}`; lines.push(` ${nodeId}["${label}"]`); lines.push(` ${resultId}["${resultLabel}"]`); lines.push(` ${nodeId} --> ${resultId}`); // Add styling if (transform.success) { lines.push(` style ${resultId} fill:#90EE90`); } else { lines.push(` style ${resultId} fill:#FFCCCB`); } // Connect to next transformation if exists if (i < transformations.length - 1) { lines.push(` ${resultId} --> node_${i+1}`); } }); return lines.join('\n'); }