UNPKG

meld

Version:

Meld: A template language for LLM prompts

1,639 lines (1,417 loc) 111 kB
# Interface & Implementation Audit # Meld Codebase Audit This is part of a systematic audit of the Meld codebase, focusing on transformation issues, state management bugs, and service implementation mismatches. ## FORMATTING REQUIREMENTS - Use markdown tables for comparisons - Use code blocks with language tags - Include line numbers for all code references - Format method signatures consistently - Separate sections with clear headers - Include evidence for all findings ## ANALYSIS REQUIREMENTS - Base all findings on concrete evidence from the code - Do not make assumptions without supporting code - Highlight any contradictions found - Note any missing or incomplete implementations - Identify patterns across multiple files - Flag any potential architectural issues ## CODE ANALYSIS INSTRUCTIONS 1. INTERFACE ANALYSIS - Check method signatures match exactly - Verify parameter types and return types - Confirm optional parameters are consistent - Note any documentation mismatches 2. IMPLEMENTATION ANALYSIS - Verify all interface methods are implemented - Check for extra methods not in interface - Confirm implementation behavior matches docs - Note any partial or incomplete implementations 3. MOCK ANALYSIS - Compare mock methods to real implementations - Check mock return types match interface - Verify mock behavior in test scenarios - Note any missing or incomplete mock methods 4. TEST COVERAGE - Check which methods are actually tested - Note any untested code paths - Verify test assertions match requirements - Flag any inconsistent test behavior IMPORTANT: Always check both the interface definition AND its usage in the codebase. Methods may be used that aren't properly defined in the interface. ## CODE TO ANALYZE \=== STATE SERVICE INTERFACE AND IMPLEMENTATION === Processing...# IStateService.ts ## Content ```typescript import type { MeldNode } from 'meld-spec'; export interface IStateService { // Text variables getTextVar(name: string): string | undefined; setTextVar(name: string, value: string): void; getAllTextVars(): Map<string, string>; getLocalTextVars(): Map<string, string>; // Data variables getDataVar(name: string): any; setDataVar(name: string, value: any): void; getAllDataVars(): Map<string, any>; getLocalDataVars(): Map<string, any>; // Path variables getPathVar(name: string): string | undefined; setPathVar(name: string, value: string): void; getAllPathVars(): Map<string, string>; // Commands getCommand(name: string): { command: string; options?: Record<string, unknown> } | undefined; setCommand(name: string, command: string | { command: string; options?: Record<string, unknown> }): void; getAllCommands(): Map<string, { command: string; options?: Record<string, unknown> }>; // Nodes getNodes(): MeldNode[]; addNode(node: MeldNode): void; appendContent(content: string): void; // Node transformation (new) getTransformedNodes(): MeldNode[]; setTransformedNodes(nodes: MeldNode[]): void; transformNode(original: MeldNode, transformed: MeldNode): void; isTransformationEnabled(): boolean; enableTransformation(enable: boolean): void; // Imports addImport(path: string): void; removeImport(path: string): void; hasImport(path: string): boolean; getImports(): Set<string>; // File path getCurrentFilePath(): string | null; setCurrentFilePath(path: string): void; // State management hasLocalChanges(): boolean; getLocalChanges(): string[]; setImmutable(): void; readonly isImmutable: boolean; createChildState(): IStateService; mergeChildState(childState: IStateService): void; clone(): IStateService; } ``` # StateService.ts ## Functions - StateService - StateService.constructor - StateService.getTextVar - StateService.setTextVar - StateService.getAllTextVars - StateService.getLocalTextVars - StateService.getDataVar - StateService.setDataVar - StateService.getAllDataVars - StateService.getLocalDataVars - StateService.getPathVar - StateService.setPathVar - StateService.getAllPathVars - StateService.getCommand - StateService.setCommand - StateService.getAllCommands - StateService.getNodes - StateService.getTransformedNodes - StateService.setTransformedNodes - StateService.addNode - StateService.transformNode - StateService.isTransformationEnabled - StateService.enableTransformation - StateService.appendContent - StateService.addImport - StateService.removeImport - StateService.hasImport - StateService.getImports - StateService.getCurrentFilePath - StateService.setCurrentFilePath - StateService.hasLocalChanges - StateService.getLocalChanges - StateService.setImmutable - StateService.createChildState - StateService.mergeChildState - StateService.clone - StateService.checkMutable - StateService.updateState ## Content ```typescript import type { MeldNode, TextNode } from 'meld-spec'; import { stateLogger as logger } from '@core/utils/logger.js'; import type { IStateService } from './IStateService.js'; import type { StateNode, CommandDefinition } from './types.js'; import { StateFactory } from './StateFactory.js'; export class StateService implements IStateService { private stateFactory: StateFactory; private currentState: StateNode; private _isImmutable: boolean = false; private _transformationEnabled: boolean = false; constructor(parentState?: IStateService) { this.stateFactory = new StateFactory(); this.currentState = this.stateFactory.createState({ source: 'constructor', parentState: parentState ? (parentState as StateService).currentState : undefined }); } // Text variables getTextVar(name: string): string | undefined { return this.currentState.variables.text.get(name); } setTextVar(name: string, value: string): void { this.checkMutable(); const text = new Map(this.currentState.variables.text); text.set(name, value); this.updateState({ variables: { ...this.currentState.variables, text } }, `setTextVar:${name}`); } getAllTextVars(): Map<string, string> { return new Map(this.currentState.variables.text); } getLocalTextVars(): Map<string, string> { return new Map(this.currentState.variables.text); } // Data variables getDataVar(name: string): unknown { return this.currentState.variables.data.get(name); } setDataVar(name: string, value: unknown): void { this.checkMutable(); const data = new Map(this.currentState.variables.data); data.set(name, value); this.updateState({ variables: { ...this.currentState.variables, data } }, `setDataVar:${name}`); } getAllDataVars(): Map<string, unknown> { return new Map(this.currentState.variables.data); } getLocalDataVars(): Map<string, unknown> { return new Map(this.currentState.variables.data); } // Path variables getPathVar(name: string): string | undefined { return this.currentState.variables.path.get(name); } setPathVar(name: string, value: string): void { this.checkMutable(); const path = new Map(this.currentState.variables.path); path.set(name, value); this.updateState({ variables: { ...this.currentState.variables, path } }, `setPathVar:${name}`); } getAllPathVars(): Map<string, string> { return new Map(this.currentState.variables.path); } // Commands getCommand(name: string): { command: string; options?: Record<string, unknown> } | undefined { const cmd = this.currentState.commands.get(name); if (!cmd) return undefined; return { command: cmd.command, options: cmd.options ? { ...cmd.options } : undefined }; } setCommand(name: string, command: string | { command: string; options?: Record<string, unknown> }): void { this.checkMutable(); const commands = new Map(this.currentState.commands); const cmdDef: CommandDefinition = typeof command === 'string' ? { command } : { command: command.command, options: command.options }; commands.set(name, cmdDef); this.updateState({ commands }, `setCommand:${name}`); } getAllCommands(): Map<string, { command: string; options?: Record<string, unknown> }> { const commands = new Map<string, { command: string; options?: Record<string, unknown> }>(); for (const [name, cmd] of this.currentState.commands) { commands.set(name, { command: cmd.command, options: cmd.options ? { ...cmd.options } : undefined }); } return commands; } // Nodes getNodes(): MeldNode[] { return [...this.currentState.nodes]; } getTransformedNodes(): MeldNode[] { return this.currentState.transformedNodes ? [...this.currentState.transformedNodes] : [...this.currentState.nodes]; } setTransformedNodes(nodes: MeldNode[]): void { this.checkMutable(); this.updateState({ transformedNodes: [...nodes] }, 'setTransformedNodes'); } addNode(node: MeldNode): void { this.checkMutable(); const updates: Partial<StateNode> = { nodes: [...this.currentState.nodes, node] }; updates.transformedNodes = [ ...(this.currentState.transformedNodes || this.currentState.nodes), node ]; this.updateState(updates, 'addNode'); } transformNode(original: MeldNode, transformed: MeldNode): void { this.checkMutable(); if (!this._transformationEnabled) { return; } const transformedNodes = this.currentState.transformedNodes || this.currentState.nodes; const index = transformedNodes.findIndex(node => node === original); if (index === -1) { throw new Error('Cannot transform node: original node not found'); } const updatedNodes = [...transformedNodes]; updatedNodes[index] = transformed; this.updateState({ transformedNodes: updatedNodes }, 'transformNode'); } isTransformationEnabled(): boolean { return this._transformationEnabled; } enableTransformation(enable: boolean): void { if (this._transformationEnabled === enable) { return; } this._transformationEnabled = enable; // Initialize transformed nodes if enabling if (enable) { // Always initialize with a fresh copy of nodes, even if transformedNodes already exists this.updateState({ transformedNodes: [...this.currentState.nodes] }, 'enableTransformation'); } } appendContent(content: string): void { this.checkMutable(); // Create a text node and add it const node: MeldNode = { type: 'Text', content: content, location: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } } } as TextNode; this.addNode(node); } // Imports addImport(path: string): void { this.checkMutable(); const imports = new Set(this.currentState.imports); imports.add(path); this.updateState({ imports }, `addImport:${path}`); } removeImport(path: string): void { this.checkMutable(); const imports = new Set(this.currentState.imports); imports.delete(path); this.updateState({ imports }, `removeImport:${path}`); } hasImport(path: string): boolean { return this.currentState.imports.has(path); } getImports(): Set<string> { return new Set(this.currentState.imports); } // File path getCurrentFilePath(): string | null { return this.currentState.filePath ?? null; } setCurrentFilePath(path: string): void { this.checkMutable(); this.updateState({ filePath: path }, 'setCurrentFilePath'); } // State management hasLocalChanges(): boolean { return true; // In immutable model, any non-empty state has local changes } getLocalChanges(): string[] { return ['state']; // In immutable model, the entire state is considered changed } setImmutable(): void { this._isImmutable = true; } get isImmutable(): boolean { return this._isImmutable; } createChildState(): IStateService { const child = new StateService(this); logger.debug('Created child state', { parentPath: this.getCurrentFilePath(), childPath: child.getCurrentFilePath() }); return child; } mergeChildState(childState: IStateService): void { this.checkMutable(); const child = childState as StateService; this.currentState = this.stateFactory.mergeStates(this.currentState, child.currentState); } clone(): IStateService { const cloned = new StateService(); // Create a completely new state without parent reference cloned.currentState = this.stateFactory.createState({ source: 'clone', filePath: this.currentState.filePath }); // Copy all state cloned.updateState({ variables: { text: new Map(this.currentState.variables.text), data: new Map(this.currentState.variables.data), path: new Map(this.currentState.variables.path) }, commands: new Map(this.currentState.commands), nodes: [...this.currentState.nodes], transformedNodes: this.currentState.transformedNodes ? [...this.currentState.transformedNodes] : undefined, imports: new Set(this.currentState.imports) }, 'clone'); // Copy flags cloned._isImmutable = this._isImmutable; cloned._transformationEnabled = this._transformationEnabled; return cloned; } private checkMutable(): void { if (this._isImmutable) { throw new Error('Cannot modify immutable state'); } } private updateState(updates: Partial<StateNode>, source: string): void { this.currentState = this.stateFactory.updateState(this.currentState, updates); logger.debug('Updated state', { source, updates }); } } ``` \=== USAGE IN PRODUCTION CODE === Processing... Processing.... Processing..... Processing......# DirectiveService.ts ## Functions - MeldLLMXMLError - DirectiveService - MeldLLMXMLError.constructor - DirectiveService.constructor - DirectiveService.initialize - DirectiveService.registerDefaultHandlers - DirectiveService.registerHandler - DirectiveService.handleDirective - DirectiveService.processDirectives - DirectiveService.createContext - DirectiveService.updateInterpreterService - DirectiveService.hasHandler - DirectiveService.validateDirective - DirectiveService.createChildContext - DirectiveService.supportsDirective - DirectiveService.getSupportedDirectives - DirectiveService.ensureInitialized - DirectiveService.handleTextDirective - DirectiveService.handleDataDirective - DirectiveService.handleImportDirective - DirectiveService.extractSection - DirectiveService.calculateSimilarity - DirectiveService.handleEmbedDirective - DirectiveService.processDirective ## Content ```typescript import type { DirectiveNode, DirectiveKind, DirectiveData } from 'meld-spec'; import { directiveLogger } from '../../core/utils/logger.js'; import { IDirectiveService, IDirectiveHandler, DirectiveContext } from './IDirectiveService.js'; import { IValidationService } from '@services/ValidationService/IValidationService.js'; import { IStateService } from '@services/StateService/IStateService.js'; import { IPathService } from '@services/PathService/IPathService.js'; import { IFileSystemService } from '@services/FileSystemService/IFileSystemService.js'; import { IParserService } from '@services/ParserService/IParserService.js'; import { IInterpreterService } from '@services/InterpreterService/IInterpreterService.js'; import { MeldDirectiveError } from '@core/errors/MeldDirectiveError.js'; import { ICircularityService } from '@services/CircularityService/ICircularityService.js'; import { IResolutionService } from '@services/ResolutionService/IResolutionService.js'; import { DirectiveError, DirectiveErrorCode } from './errors/DirectiveError.js'; import type { ILogger } from './handlers/execution/EmbedDirectiveHandler.js'; // Import all handlers import { TextDirectiveHandler } from './handlers/definition/TextDirectiveHandler.js'; import { DataDirectiveHandler } from './handlers/definition/DataDirectiveHandler.js'; import { PathDirectiveHandler } from './handlers/definition/PathDirectiveHandler.js'; import { DefineDirectiveHandler } from './handlers/definition/DefineDirectiveHandler.js'; import { RunDirectiveHandler } from './handlers/execution/RunDirectiveHandler.js'; import { EmbedDirectiveHandler } from './handlers/execution/EmbedDirectiveHandler.js'; import { ImportDirectiveHandler } from './handlers/execution/ImportDirectiveHandler.js'; export class MeldLLMXMLError extends Error { constructor( message: string, public readonly code: string, public readonly details?: any ) { super(message); this.name = 'MeldLLMXMLError'; Object.setPrototypeOf(this, MeldLLMXMLError.prototype); } } /** * Service responsible for handling directives */ export class DirectiveService implements IDirectiveService { private validationService?: IValidationService; private stateService?: IStateService; private pathService?: IPathService; private fileSystemService?: IFileSystemService; private parserService?: IParserService; private interpreterService?: IInterpreterService; private circularityService?: ICircularityService; private resolutionService?: IResolutionService; private initialized = false; private logger: ILogger; private handlers: Map<string, IDirectiveHandler> = new Map(); constructor(logger?: ILogger) { this.logger = logger || directiveLogger; } initialize( validationService: IValidationService, stateService: IStateService, pathService: IPathService, fileSystemService: IFileSystemService, parserService: IParserService, interpreterService: IInterpreterService, circularityService: ICircularityService, resolutionService: IResolutionService ): void { this.validationService = validationService; this.stateService = stateService; this.pathService = pathService; this.fileSystemService = fileSystemService; this.parserService = parserService; this.interpreterService = interpreterService; this.circularityService = circularityService; this.resolutionService = resolutionService; this.initialized = true; // Register default handlers this.registerDefaultHandlers(); this.logger.debug('DirectiveService initialized', { handlers: Array.from(this.handlers.keys()) }); } /** * Register all default directive handlers */ public registerDefaultHandlers(): void { // Definition handlers this.registerHandler( new TextDirectiveHandler( this.validationService!, this.stateService!, this.resolutionService! ) ); this.registerHandler( new DataDirectiveHandler( this.validationService!, this.stateService!, this.resolutionService! ) ); this.registerHandler( new PathDirectiveHandler( this.validationService!, this.stateService!, this.resolutionService! ) ); this.registerHandler( new DefineDirectiveHandler( this.validationService!, this.stateService!, this.resolutionService! ) ); // Execution handlers this.registerHandler( new RunDirectiveHandler( this.validationService!, this.resolutionService!, this.stateService!, this.fileSystemService! ) ); this.registerHandler( new EmbedDirectiveHandler( this.validationService!, this.resolutionService!, this.stateService!, this.circularityService!, this.fileSystemService!, this.parserService!, this.interpreterService!, this.logger ) ); this.registerHandler( new ImportDirectiveHandler( this.validationService!, this.resolutionService!, this.stateService!, this.fileSystemService!, this.parserService!, this.interpreterService!, this.circularityService! ) ); } /** * Register a new directive handler */ registerHandler(handler: IDirectiveHandler): void { if (!this.initialized) { throw new Error('DirectiveService must be initialized before registering handlers'); } if (!handler.kind) { throw new Error('Handler must have a kind property'); } this.handlers.set(handler.kind, handler); this.logger.debug(`Registered handler for directive: ${handler.kind}`); } /** * Handle a directive node */ public async handleDirective(node: DirectiveNode, context: DirectiveContext): Promise<IStateService> { return this.processDirective(node, context); } /** * Process multiple directives in sequence */ async processDirectives(nodes: DirectiveNode[], parentContext?: DirectiveContext): Promise<IStateService> { let currentState = parentContext?.state?.clone() || this.stateService!.createChildState(); for (const node of nodes) { // Create a new context with the current state as parent and a new child state const nodeContext = { currentFilePath: parentContext?.currentFilePath || '', parentState: currentState, state: currentState.createChildState() }; // Process directive and get the updated state const updatedState = await this.processDirective(node, nodeContext); // Merge the updated state back into the current state currentState.mergeChildState(updatedState); } return currentState; } /** * Create execution context for a directive */ private createContext(node: DirectiveNode, parentContext?: DirectiveContext): DirectiveContext { return { currentFilePath: node.location?.start.line ? node.location.start.line.toString() : '', parentState: parentContext?.state, state: parentContext?.state?.clone() || this.stateService!.createChildState() }; } /** * Update the interpreter service reference */ updateInterpreterService(interpreterService: IInterpreterService): void { this.interpreterService = interpreterService; this.logger.debug('Updated interpreter service reference'); } /** * Check if a handler exists for a directive kind */ hasHandler(kind: string): boolean { return this.handlers.has(kind); } /** * Validate a directive node */ async validateDirective(node: DirectiveNode): Promise<void> { try { await this.validationService!.validate(node); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); const errorForLog = error instanceof Error ? error : new Error(String(error)); this.logger.error('Failed to validate directive', { kind: node.directive.kind, location: node.location, error: errorForLog }); throw new DirectiveError( errorMessage, node.directive.kind, DirectiveErrorCode.VALIDATION_FAILED, { node } ); } } /** * Create a child context for nested directives */ public createChildContext(parentContext: DirectiveContext, filePath: string): DirectiveContext { return { currentFilePath: filePath, state: parentContext.state.createChildState(), parentState: parentContext.state }; } supportsDirective(kind: string): boolean { return this.handlers.has(kind); } getSupportedDirectives(): string[] { return Array.from(this.handlers.keys()); } private ensureInitialized(): void { if (!this.initialized) { throw new Error('DirectiveService must be initialized before use'); } } private async handleTextDirective(node: DirectiveNode): Promise<void> { const directive = node.directive; this.logger.debug('Processing text directive', { identifier: directive.identifier, location: node.location }); try { // Value is already interpolated by meld-ast await this.stateService!.setTextVar(directive.identifier, directive.value); this.logger.debug('Text directive processed successfully', { identifier: directive.identifier, location: node.location }); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); const errorForLog = error instanceof Error ? error : new Error(String(error)); this.logger.error('Failed to process text directive', { identifier: directive.identifier, location: node.location, error: errorForLog }); throw new MeldDirectiveError( errorMessage, 'text', node.location?.start ); } } private async handleDataDirective(node: DirectiveNode): Promise<void> { const directive = node.directive; this.logger.debug('Processing data directive', { identifier: directive.identifier, location: node.location }); try { // Value is already interpolated by meld-ast let value = directive.value; if (typeof value === 'string') { value = JSON.parse(value); } await this.stateService!.setDataVar(directive.identifier, value); this.logger.debug('Data directive processed successfully', { identifier: directive.identifier, location: node.location }); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); const errorForLog = error instanceof Error ? error : new Error(String(error)); this.logger.error('Failed to process data directive', { identifier: directive.identifier, location: node.location, error: errorForLog }); throw new MeldDirectiveError( errorMessage, 'data', node.location?.start ); } } private async handleImportDirective(node: DirectiveNode): Promise<void> { const directive = node.directive; this.logger.debug('Processing import directive', { path: directive.path, section: directive.section, fuzzy: directive.fuzzy, location: node.location }); try { // Path is already interpolated by meld-ast const fullPath = await this.pathService!.resolvePath(directive.path); // Check for circular imports this.circularityService!.beginImport(fullPath); try { // Check if file exists if (!await this.fileSystemService!.exists(fullPath)) { throw new Error(`Import file not found: ${fullPath}`); } // Create a child state for the import const childState = await this.stateService!.createChildState(); // Read the file content const content = await this.fileSystemService!.readFile(fullPath); // If a section is specified, extract it (section name is already interpolated) let processedContent = content; if (directive.section) { processedContent = await this.extractSection( content, directive.section, directive.fuzzy || 0 ); } // Parse and interpret the content const parsedNodes = await this.parserService!.parse(processedContent); await this.interpreterService!.interpret(parsedNodes, { initialState: childState, filePath: fullPath, mergeState: true }); this.logger.debug('Import content processed', { path: fullPath, section: directive.section, location: node.location }); } finally { // Always end import tracking, even if there was an error this.circularityService!.endImport(fullPath); } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); const errorForLog = error instanceof Error ? error : new Error(String(error)); this.logger.error('Failed to process import directive', { path: directive.path, section: directive.section, location: node.location, error: errorForLog }); throw new MeldDirectiveError( errorMessage, 'import', node.location?.start ); } } private async extractSection( content: string, section: string, fuzzyMatch: number ): Promise<string> { try { // Split content into lines const lines = content.split('\n'); const headings: { title: string; line: number; level: number }[] = []; // Find all headings and their levels for (let i = 0; i < lines.length; i++) { const line = lines[i]; const match = line.match(/^(#{1,6})\s+(.+)$/); if (match) { headings.push({ title: match[2], line: i, level: match[1].length }); } } // Find best matching heading let bestMatch: typeof headings[0] | undefined; let bestScore = 0; for (const heading of headings) { const score = this.calculateSimilarity(heading.title, section); if (score > fuzzyMatch && score > bestScore) { bestScore = score; bestMatch = heading; } } if (!bestMatch) { // Find closest match for error message let closestMatch = ''; let closestScore = 0; for (const heading of headings) { const score = this.calculateSimilarity(heading.title, section); if (score > closestScore) { closestScore = score; closestMatch = heading.title; } } throw new MeldLLMXMLError( 'Section not found', 'SECTION_NOT_FOUND', { title: section, bestMatch: closestMatch } ); } // Find the end of the section (next heading of same or higher level) let endLine = lines.length; for (let i = bestMatch.line + 1; i < lines.length; i++) { const line = lines[i]; const match = line.match(/^(#{1,6})\s+/); if (match && match[1].length <= bestMatch.level) { endLine = i; break; } } // Extract the section content return lines.slice(bestMatch.line, endLine).join('\n'); } catch (error) { if (error instanceof MeldLLMXMLError) { throw error; } throw new MeldLLMXMLError( error instanceof Error ? error.message : 'Unknown error during section extraction', 'PARSE_ERROR', error ); } } private calculateSimilarity(str1: string, str2: string): number { // Convert strings to lowercase for case-insensitive comparison const s1 = str1.toLowerCase(); const s2 = str2.toLowerCase(); if (s1 === s2) return 1.0; // Calculate Levenshtein distance const len1 = s1.length; const len2 = s2.length; const matrix: number[][] = []; for (let i = 0; i <= len1; i++) { matrix[i] = [i]; } for (let j = 0; j <= len2; j++) { matrix[0][j] = j; } for (let i = 1; i <= len1; i++) { for (let j = 1; j <= len2; j++) { const cost = s1[i - 1] === s2[j - 1] ? 0 : 1; matrix[i][j] = Math.min( matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost ); } } // Convert distance to similarity score between 0 and 1 const maxLen = Math.max(len1, len2); return maxLen === 0 ? 1.0 : 1.0 - matrix[len1][len2] / maxLen; } private async handleEmbedDirective(node: DirectiveNode): Promise<void> { const directive = node.directive; this.logger.debug('Processing embed directive', { path: directive.path, section: directive.section, fuzzy: directive.fuzzy, names: directive.names, location: node.location }); try { // Path is already interpolated by meld-ast const fullPath = await this.pathService!.resolvePath(directive.path); // Check for circular imports this.circularityService!.beginImport(fullPath); try { // Check if file exists if (!await this.fileSystemService!.exists(fullPath)) { throw new Error(`Embed file not found: ${fullPath}`); } // Create a child state for the import const childState = await this.stateService!.createChildState(); // Read the file content const content = await this.fileSystemService!.readFile(fullPath); // If a section is specified, extract it (section name is already interpolated) let processedContent = content; if (directive.section) { processedContent = await this.extractSection( content, directive.section, directive.fuzzy || 0 ); } // Parse and interpret the content const parsedNodes = await this.parserService!.parse(processedContent); await this.interpreterService!.interpret(parsedNodes, { initialState: childState, filePath: fullPath, mergeState: true }); this.logger.debug('Embed content processed', { path: fullPath, section: directive.section, location: node.location }); } finally { // Always end import tracking, even if there was an error this.circularityService!.endImport(fullPath); } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); const errorForLog = error instanceof Error ? error : new Error(String(error)); this.logger.error('Failed to process embed directive', { path: directive.path, section: directive.section, location: node.location, error: errorForLog }); throw new MeldDirectiveError( errorMessage, 'embed', node.location?.start ); } } public async processDirective(node: DirectiveNode, context: DirectiveContext): Promise<IStateService> { this.ensureInitialized(); try { if (!node.directive || !node.directive.kind) { throw new DirectiveError( 'Invalid directive format', 'unknown', DirectiveErrorCode.VALIDATION_FAILED, { node } ); } const kind = node.directive.kind.toLowerCase(); const handler = this.handlers.get(kind); if (!handler) { throw new DirectiveError( `Unknown directive kind: ${kind}`, kind, DirectiveErrorCode.HANDLER_NOT_FOUND, { node } ); } if (typeof handler.execute !== 'function') { throw new DirectiveError( `Invalid handler for directive kind: ${kind}`, kind, DirectiveErrorCode.HANDLER_NOT_FOUND, { node } ); } // Validate directive before handling await this.validateDirective(node); // Execute the directive return await handler.execute(node, context); } catch (error) { if (error instanceof DirectiveError) { throw error; } // Simplify error messages for common cases let message = error instanceof Error ? error.message : String(error); let code = DirectiveErrorCode.EXECUTION_FAILED; if (message.includes('file not found') || message.includes('no such file')) { message = `Referenced file not found: ${node.directive.path || node.directive.value}`; code = DirectiveErrorCode.FILE_NOT_FOUND; } else if (message.includes('circular import') || message.includes('circular reference')) { message = 'Circular import detected'; code = DirectiveErrorCode.CIRCULAR_REFERENCE; } else if (message.includes('parameter count') || message.includes('wrong number of parameters')) { message = 'Invalid parameter count'; code = DirectiveErrorCode.VALIDATION_FAILED; } else if (message.includes('invalid path') || message.includes('path validation failed')) { message = 'Invalid path'; code = DirectiveErrorCode.VALIDATION_FAILED; } throw new DirectiveError( message, node.directive?.kind || 'unknown', code, { node, cause: error instanceof Error ? error : undefined } ); } } } ``` # IDirectiveService.ts ## Content ```typescript import { DirectiveNode } from 'meld-spec'; import { IStateService } from '@services/StateService/IStateService.js'; import type { IValidationService } from '@services/ValidationService/IValidationService.js'; import type { IPathService } from '@services/PathService/IPathService.js'; import type { IFileSystemService } from '@services/FileSystemService/IFileSystemService.js'; import type { IParserService } from '@services/ParserService/IParserService.js'; import type { IInterpreterService } from '@services/InterpreterService/IInterpreterService.js'; import type { ICircularityService } from '@services/CircularityService/ICircularityService.js'; import type { IResolutionService } from '@services/ResolutionService/IResolutionService.js'; /** * Context for directive execution */ export interface DirectiveContext { /** Current file being processed */ currentFilePath?: string; /** Parent state for nested contexts */ parentState?: IStateService; /** Current state for this directive */ state: IStateService; /** Working directory for command execution */ workingDirectory?: string; } /** * Interface for directive handlers */ export interface IDirectiveHandler { /** The directive kind this handler processes */ readonly kind: string; /** * Execute the directive * @returns The updated state after directive execution */ execute( node: DirectiveNode, context: DirectiveContext ): Promise<IStateService>; } /** * Service responsible for handling directives */ export interface IDirectiveService { /** * Initialize the DirectiveService with required dependencies */ initialize( validationService: IValidationService, stateService: IStateService, pathService: IPathService, fileSystemService: IFileSystemService, parserService: IParserService, interpreterService: IInterpreterService, circularityService: ICircularityService, resolutionService: IResolutionService ): void; /** * Update the interpreter service reference * This is needed to handle circular dependencies in initialization */ updateInterpreterService(interpreterService: IInterpreterService): void; /** * Handle a directive node * @returns The updated state after directive execution */ handleDirective( node: DirectiveNode, context: DirectiveContext ): Promise<IStateService>; /** * Register a new directive handler */ registerHandler(handler: IDirectiveHandler): void; /** * Check if a handler exists for a directive kind */ hasHandler(kind: string): boolean; /** * Validate a directive node */ validateDirective(node: DirectiveNode): Promise<void>; /** * Create a child context for nested directives */ createChildContext( parentContext: DirectiveContext, filePath: string ): DirectiveContext; /** * Process a directive node, validating and executing it * Values in the directive will already be interpolated by meld-ast * @returns The updated state after directive execution * @throws {MeldDirectiveError} If directive processing fails */ processDirective(node: DirectiveNode, parentContext?: DirectiveContext): Promise<IStateService>; /** * Process multiple directive nodes in sequence * @returns The final state after processing all directives * @throws {MeldDirectiveError} If any directive processing fails */ processDirectives(nodes: DirectiveNode[], parentContext?: DirectiveContext): Promise<IStateService>; /** * Check if a directive kind is supported */ supportsDirective(kind: string): boolean; /** * Get a list of all supported directive kinds */ getSupportedDirectives(): string[]; } ``` # DirectiveError.ts ## Functions - DirectiveError - DirectiveError.constructor - DirectiveError.toJSON - DirectiveError.getFullCauseMessage ## Content ```typescript import { DirectiveNode } from 'meld-spec'; import { DirectiveContext } from '@services/DirectiveService/IDirectiveService.js'; import type { Location } from '@core/types/index.js'; /** * Error codes for directive failures */ export enum DirectiveErrorCode { VALIDATION_FAILED = 'VALIDATION_FAILED', RESOLUTION_FAILED = 'RESOLUTION_FAILED', EXECUTION_FAILED = 'EXECUTION_FAILED', HANDLER_NOT_FOUND = 'HANDLER_NOT_FOUND', FILE_NOT_FOUND = 'FILE_NOT_FOUND', CIRCULAR_REFERENCE = 'CIRCULAR_REFERENCE', VARIABLE_NOT_FOUND = 'VARIABLE_NOT_FOUND', STATE_ERROR = 'STATE_ERROR', INVALID_CONTEXT = 'INVALID_CONTEXT' } interface SerializedDirectiveError { name: string; message: string; kind: string; code: DirectiveErrorCode; location?: Location; filePath?: string; cause?: string; fullCauseMessage?: string; } /** * Error thrown when directive handling fails */ export class DirectiveError extends Error { public readonly location?: Location; public readonly filePath?: string; private readonly errorCause?: Error; constructor( message: string, public readonly kind: string, public readonly code: DirectiveErrorCode, public readonly details?: { node?: DirectiveNode; context?: DirectiveContext; cause?: Error; location?: Location; details?: { node?: DirectiveNode; location?: Location; }; } ) { // Create message with location if available const loc = details?.location ?? details?.node?.location; const locationStr = loc ? ` at line ${loc.start.line}, column ${loc.start.column}` : ''; const filePathStr = details?.context?.currentFilePath ? ` in ${details.context.currentFilePath}` : ''; // Include cause message in the full error message if available const causeStr = details?.cause ? ` | Caused by: ${details.cause.message}` : ''; super(`Directive error (${kind}): ${message}${locationStr}${filePathStr}${causeStr}`); this.name = 'DirectiveError'; // Store essential properties this.location = details?.location ?? details?.node?.location; this.filePath = details?.context?.currentFilePath; this.errorCause = details?.cause; // Set cause property for standard error chaining if (details?.cause) { Object.defineProperty(this, 'cause', { value: details.cause, enumerable: true, configurable: true, writable: false }); } // Ensure proper prototype chain Object.setPrototypeOf(this, DirectiveError.prototype); } // Add public getter for cause that ensures we always return the full error public get cause(): Error | undefined { return this.errorCause; } /** * Custom serialization to avoid circular references and include only essential info */ toJSON(): SerializedDirectiveError { return { name: this.name, message: this.message, kind: this.kind, code: this.code, location: this.location, filePath: this.filePath, cause: this.errorCause?.message, fullCauseMessage: this.errorCause ? this.getFullCauseMessage(this.errorCause) : undefined }; } /** * Helper to get the full cause message chain */ private getFullCauseMessage(error: Error): string { let message = error.message; if ('cause' in error && error.cause instanceof Error) { message += ` | Caused by: ${this.getFullCauseMessage(error.cause)}`; } return message; } } ``` # DataDirectiveHandler.ts ## Functions - DataDirectiveHandler - DataDirectiveHandler.constructor - DataDirectiveHandler.execute - DataDirectiveHandler.resolveObjectFields - DataDirectiveHandler.validateSchema ## Content ```typescript import { DirectiveNode } from 'meld-spec'; // TODO: Use meld-ast nodes and types instead of meld-spec directly // TODO: Import MeldDirectiveError from core/errors for proper error hierarchy import { IDirectiveHandler, DirectiveContext } from '@services/DirectiveService/IDirectiveService.js'; import { IValidationService } from '@services/ValidationService/IValidationService.js'; import { IStateService } from '@services/StateService/IStateService.js'; import { IResolutionService, ResolutionContext } from '@services/ResolutionService/IResolutionService.js'; import { ResolutionContextFactory } from '@services/ResolutionService/ResolutionContextFactory.js'; import { directiveLogger as logger } from '@core/utils/logger.js'; import { DirectiveError, DirectiveErrorCode } from '@services/DirectiveService/errors/DirectiveError.js'; /** * Handler for @data directives * Stores data values in state after resolving variables and processing embedded content */ export class DataDirectiveHandler implements IDirectiveHandler { readonly kind = 'data'; constructor( private validationService: IValidationService, private stateService: IStateService, private resolutionService: IResolutionService ) {} public async execute(node: DirectiveNode, context: DirectiveContext): Promise<IStateService> { await this.validationService.validate(node); const { identifier, value } = node.directive; const resolutionContext: ResolutionContext = { allowedVariableTypes: { text: true, data: true, path: true, command: true }, currentFilePath: context.currentFilePath, state: context.state }; try { let parsedValue: unknown; // Handle both string and object values if (typeof value === 'string') { // First resolve any variables in the JSON string const resolvedJsonString = await this.resolutionService.resolveInContext(value, resolutionContext); // Then parse the JSON try { parsedValue = JSON.parse(resolvedJsonString); // Recursively resolve any variables in the parsed object parsedValue = await this.resolveObjectFields(parsedValue, resolutionContext); } catch (error) { if (error instanceof Error) { throw new DirectiveError( `Invalid JSON in data directive: ${error.message}`, 'data', DirectiveErrorCode.VALIDATION_FAILED, { node, context } ); } throw error; } } else { // Value is already an object, resolve variables in it parsedValue = await this.resolveObjectFields(value, resolutionContext); } // Store the resolved value in a new state const newState = context.state.clone(); newState.setDataVar(identifier, parsedValue); return newState; } catch (error) { if (error instanceof Error) { throw new DirectiveError( `Error processing data directive: ${error.message}`, 'data', DirectiveErrorCode.EXECUTION_FAILED, { node, context } ); } throw error; } } /** * Recursively resolve variables in object fields */ private async resolveObjectFields( obj: any, context: ResolutionContext ): Promise<any> { if (obj === null || obj === undefined) { return obj; } if (typeof obj === 'string') { // If the string contains any variable references, resolve them if (obj.includes('${') || obj.includes('#{') || obj.includes('$') || obj.includes('`')) { return this.resolutionService.resolveInContext(obj, context); } return obj; } if (Array.isArray(obj)) { return Promise.all( obj.map(item => this.resolveObjectFields(item, context)) ); } if (typeof obj === 'object') { const resolved: Record<string, any> = {}; for (const [key, value] of Object.entries(obj)) { // Keep original key, only resolve value resolved[key] = await this.resolveObjectFields(value, context); } return resolved; } // For other primitive types (number, boolean, etc), return as is return obj; } /** * Validate resolved value against schema */ private async validateSchema( value: any, schema: string, node: DirectiveNode ): Promise<void> { try { // TODO: Implement schema validation once schema system is defined // For now, just log that we would validate logger.debug('Schema validation requested', { schema, location: node.location }); } catch (error) { if (error instanceof Error) { throw new DirectiveError( `Schema validation failed: ${error.message}`, 'data', DirectiveErrorCode.VALIDATION_FAILED, { node } ); } throw error; } } } ``` # DefineDirectiveHandler.ts ## Functions - DefineDirectiveHandler - DefineDirectiveHandler.constructor - DefineDirectiveHandler.execute - DefineDirectiveHandler.parseIdentifier - DefineDirectiveHandler.processCommand - DefineDirectiveHandler.validateParameters - DefineDirectiveHandler.extractParameterReferences ## Content ```typescript import { IDirectiveHandler, DirectiveContext } from '../../IDirectiveService.js'; import { IValidationService } from '@services/ValidationService/IValidationService.js'; import { IStateService } from '@services/StateService/IStateService.js'; import { IResolutionService } from '@services/ResolutionService/IResolutionService.js'; import { DirectiveNode } from 'meld-spec'; import { DirectiveError, DirectiveErrorCode } from '../../errors/DirectiveError.js'; import { directiveLogger as logger } from '@core/utils/logger.js'; interface CommandDefinition { parameters: string[]; command: string; metadata?: { risk?: 'high' | 'med' | 'low'; about?: string; meta?: R