meld
Version:
Meld: A template language for LLM prompts
321 lines (272 loc) • 12.2 kB
text/typescript
import { container } from 'tsyringe';
import { IResolutionService } from '@services/resolution/ResolutionService/IResolutionService.js';
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 { MeldResolutionError } from '@core/errors/MeldResolutionError.js';
import path from 'path';
import chalk from 'chalk';
import fs from 'fs/promises';
import { initializeContextDebugger, VariableResolutionTracker } from '../../tests/utils/debug/index.js';
import { IPathService } from '@services/fs/PathService/IPathService.js';
// Import concrete classes for direct instantiation
import { ResolutionService } from '@services/resolution/ResolutionService/ResolutionService.js';
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 { PathService } from '@services/fs/PathService/PathService.js';
import { NodeFileSystem } from '@services/fs/FileSystemService/NodeFileSystem.js';
import { PathOperationsService } from '@services/fs/FileSystemService/PathOperationsService.js';
import { DirectiveService } from '@services/pipeline/DirectiveService/DirectiveService.js';
import { StateEventService } from '@services/state/StateEventService/StateEventService.js';
import { ValidationService } from '@services/resolution/ValidationService/ValidationService.js';
import { CircularityService } from '@services/resolution/CircularityService/CircularityService.js';
interface DebugContextOptions {
filePath: string;
variableName?: string;
visualizationType: 'hierarchy' | 'variable-propagation' | 'combined' | 'timeline';
rootStateId?: string;
outputFormat?: 'mermaid' | 'dot' | 'json';
outputFile?: string;
includeVars?: boolean;
includeTimestamps?: boolean;
includeFilePaths?: boolean;
}
/**
* Debug context boundaries and variable propagation in Meld
*/
export async function debugContextCommand(options: DebugContextOptions): Promise<void> {
const {
filePath,
variableName,
visualizationType,
rootStateId,
outputFormat = 'mermaid',
outputFile,
includeVars = true,
includeTimestamps = true,
includeFilePaths = true
} = options;
try {
// Try to get services from DI container (for tests)
let stateService, fileSystemService, parserService, directiveService, interpreterService, resolutionService, pathService;
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');
resolutionService = container.resolve('ResolutionService');
pathService = container.resolve('PathService');
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(pathOps);
// Create the base services first
stateService = new StateService();
fileSystemService = new FileSystemService(pathOps, nodeFs);
parserService = new ParserService();
pathService = new PathService();
// Create the state event service
const stateEventService = new StateEventService();
// Register the state event service in the container
container.register('StateEventService', { useValue: stateEventService });
// Initialize the path service
pathService.initialize(fileSystemService, pathOps);
// 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'));
}
// Create resolution service
resolutionService = new ResolutionService(
stateService,
fileSystemService,
parserService,
pathService
);
// Create the directive service
directiveService = new DirectiveService();
// Create validation service
const validationService = new ValidationService();
// Create circularity service
const circularityService = new CircularityService();
// Create the interpreter service
interpreterService = new InterpreterService();
// Initialize directive service with the interpreter service
directiveService.initialize(
validationService, // Add ValidationService instance
stateService,
pathService,
fileSystemService,
parserService,
interpreterService, // Pass the interpreter service directly
circularityService, // Add CircularityService instance
resolutionService
);
// Initialize the interpreter service
interpreterService.initialize(directiveService, stateService);
// Register default handlers
directiveService.registerDefaultHandlers();
}
// Initialize the context debugger
let contextDebugger;
try {
contextDebugger = initializeContextDebugger();
if (!contextDebugger) {
throw new Error('Context debugger returned undefined');
}
// Make sure the debugger is enabled with tracking options
contextDebugger.enable({
trackStates: true,
trackTimestamps: includeTimestamps,
trackOperations: true,
trackVariables: includeVars
});
console.log(chalk.green('Successfully initialized context debugger'));
} catch (error) {
console.error(chalk.red('Failed to initialize context debugger:'), error);
console.error(chalk.yellow('Make sure you have built the codebase with "npm run build" before running debug commands'));
return;
}
// Enable resolution tracking if a variable name is provided
if (variableName && typeof resolutionService.enableResolutionTracking === 'function') {
resolutionService.enableResolutionTracking({
watchVariables: [variableName]
});
} else if (variableName) {
console.warn(chalk.yellow('Resolution tracking is not available - enableResolutionTracking method missing'));
console.warn(chalk.yellow('Variable propagation visualization may be limited'));
}
console.log(chalk.blue(`Debugging context boundaries for ${filePath}`));
console.log(chalk.blue(`Visualization type: ${visualizationType}`));
// 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 root state - StateService doesn't have createState method
// Instead, we'll use the existing stateService instance which already has a state
const rootState = stateService;
rootState.setCurrentFilePath(filePath);
// Process the file
await interpreterService.interpret(nodes, {
initialState: rootState,
filePath,
mergeState: true
});
// Generate visualization
let visualization = '';
const effectiveRootStateId = rootStateId || rootState.getStateId();
console.log(chalk.blue('Generating visualization...'));
try {
switch (visualizationType) {
case 'hierarchy':
visualization = contextDebugger.visualizeContextHierarchy(
effectiveRootStateId,
outputFormat,
{
includeVars,
includeTimestamps,
includeFilePaths
}
);
break;
case 'variable-propagation':
if (!variableName) {
console.error(chalk.red('Variable name is required for variable-propagation visualization'));
return;
}
visualization = contextDebugger.visualizeVariablePropagation(
variableName,
effectiveRootStateId,
outputFormat,
{
includeTimestamps,
includeFilePaths
}
);
break;
case 'combined':
visualization = contextDebugger.visualizeContextsAndVariableFlow(
effectiveRootStateId,
outputFormat
);
break;
case 'timeline':
if (!variableName) {
console.error(chalk.red('Variable name is required for timeline visualization'));
return;
}
visualization = contextDebugger.visualizeResolutionTimeline(
variableName,
effectiveRootStateId,
outputFormat
);
break;
default:
console.error(chalk.red(`Unknown visualization type: ${visualizationType}`));
return;
}
if (!visualization) {
throw new Error('Visualization generation returned empty result');
}
console.log(chalk.green('Visualization generated successfully'));
} catch (error) {
console.error(chalk.red('Failed to generate visualization:'), error);
console.error(chalk.yellow('This may be due to missing state tracking data or unsupported visualization type'));
// Provide helpful debugging information
console.log(chalk.blue('\nDebug information:'));
console.log(`Root State ID: ${effectiveRootStateId}`);
console.log(`Visualization Type: ${visualizationType}`);
console.log(`Output Format: ${outputFormat}`);
if (variableName) {
console.log(`Variable Name: ${variableName}`);
}
return;
}
// Output the visualization
if (outputFile) {
await fs.writeFile(outputFile, visualization);
console.log(chalk.green(`Visualization saved to ${outputFile}`));
} else {
console.log(visualization);
}
} 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 context boundaries: ${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'));
}
}