meld
Version:
Meld: A template language for LLM prompts
1,639 lines (1,417 loc) • 111 kB
Markdown
# 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