meld
Version:
Meld: A template language for LLM prompts
597 lines (496 loc) • 18 kB
Markdown
# StateService Audit Notes
## Core Responsibilities (from ARCHITECTURE.md and PIPELINE.md)
1. Maintain both original and transformed node trees
2. Handle state inheritance correctly
3. Support immutable state operations
4. Manage transformation state through cloning/merging
## Critical State Operations
### 1. Node Management
- Original nodes array
- Transformed nodes array (optional)
- Node addition/removal
- Content appending
- Transformation tracking
### 2. State Lifecycle
```typescript
// Key operations that must preserve transformation state:
createChildState()
mergeChildState()
clone()
```
### 3. Transformation State
- Service level: `_transformationEnabled` flag
- State level: `transformedNodes` array
- Factory level: Transformation state copying
## StateFactory Implementation Findings
### 1. State Creation
```typescript
createState(options?: StateNodeOptions): StateNode {
// Creates new maps from parent if available
const state: StateNode = {
variables: {
text: new Map(options?.parentState?.variables.text ?? []),
data: new Map(options?.parentState?.variables.data ?? []),
path: new Map(options?.parentState?.variables.path ?? [])
},
commands: new Map(options?.parentState?.commands ?? []),
imports: new Set(options?.parentState?.imports ?? []),
nodes: [...(options?.parentState?.nodes ?? [])],
transformedNodes: options?.parentState?.transformedNodes ?
[...options.parentState.transformedNodes] : undefined,
filePath: options?.filePath ?? options?.parentState?.filePath,
parentState: options?.parentState
};
// ...
}
```
Key Observations:
- All maps and collections are properly cloned
- Parent state is preserved in new state
- Transformation nodes are conditionally copied
- Immutability is maintained through new Map/Set creation
### 2. State Merging
```typescript
mergeStates(parent: StateNode, child: StateNode): StateNode {
// Creates new maps with parent as base
const text = new Map(parent.variables.text);
const data = new Map(parent.variables.data);
const path = new Map(parent.variables.path);
const commands = new Map(parent.commands);
// Child values override parent values
for (const [key, value] of child.variables.text) {
text.set(key, value);
}
// ... similar for other maps ...
// Nodes are appended, not merged
nodes: [...parent.nodes, ...child.nodes],
// Transformed nodes from child take precedence
transformedNodes: child.transformedNodes !== undefined ?
[...child.transformedNodes] :
parent.transformedNodes !== undefined ?
[...parent.transformedNodes] : undefined,
}
```
Key Observations:
- Child state takes precedence over parent
- Collections are properly cloned
- Nodes are appended rather than replaced
- Transformed nodes follow child-first precedence
## Potential Issues Found
### 1. State Inheritance Chain
Verified Behavior:
- Parent state is properly referenced in child states
- Maps and collections are properly cloned during inheritance
- Child states can override parent values
- Transformation state is preserved in child creation
Potential Issues:
- No validation of parent/child relationship consistency
- No checks for circular parent references
- Transformation flags not explicitly copied in child creation
- No validation of transformed nodes matching original nodes
### 2. State Cloning Depth
Current clone implementation has proper deep cloning:
- Creates new state without parent reference
- Properly clones all collections
- Preserves transformation state
- Copies service-level flags
### 3. State Factory Operations
Verified operations:
```typescript
createState() - ✓ Proper cloning, parent handling
createChildState() - ✓ Inherits parent state correctly
mergeStates() - ✓ Child precedence, proper cloning
updateState() - ✓ Immutable updates, proper state copying
```
## Investigation Plan
1. **State Creation Flow**
- ✓ Transformation state initialization verified
- ✓ Parent state handling in factory verified
- ⚠️ Need to verify transformation flag inheritance
2. **State Modification**
- ✓ Methods properly clone state
- ✓ Immutability maintained
- ⚠️ Need to verify transformation consistency across operations
3. **State Inheritance**
- ✓ Parent/child relationship mapped
- ⚠️ Need to verify transformation state in complex scenarios
- ⚠️ Need to test circular reference handling
## Next Steps
1. Create test cases for:
- Circular parent references
- Deep transformation state inheritance
- Complex state merging scenarios
- Transformation flag consistency
2. Add validation for:
- Parent/child relationship consistency
- Transformed node validity
- Circular reference detection
3. Document transformation state lifecycle:
- When transformation flags should be inherited
- How transformed nodes should be validated
- Best practices for state merging
## Questions to Answer
1. **State Initialization**
- When should transformation be enabled?
- Should child states inherit transformation settings?
- How should transformed nodes be initialized?
2. **State Operations**
- What's the correct order for copying state?
- How should transformed nodes be merged?
- When should transformation state be preserved?
3. **Factory Behavior**
- Should factory be transformation-aware?
- How should it handle parent state?
- What's the correct state copying depth?
## Test Coverage Needed
1. **State Inheritance**
- Parent/child transformation inheritance
- Transformation state in merged states
- Cloned state consistency
2. **State Operations**
- Node transformation tracking
- State immutability with transformations
- Error handling in transformation operations
3. **Edge Cases**
- Partial transformations
- Nested state inheritance
- Complex state merging
## Test Coverage Analysis
### Existing Test Coverage
1. Basic State Operations
- ✓ State creation and initialization
- ✓ Variable management (text, data, path)
- ✓ Command handling
- ✓ Import management
- ✓ Node operations
2. Transformation State
- ✓ Basic transformation enabling/disabling
- ✓ Node transformation tracking
- ✓ Transformation state preservation in cloning
- ✓ Immutability checks with transformations
3. State Inheritance
- ✓ Basic parent/child state creation
- ✓ Simple state merging
- ✓ Variable inheritance and overriding
### Test Coverage Gaps
1. Complex Transformation Scenarios
```typescript
// Need tests for:
- Nested child states with transformations enabled/disabled at different levels
- Merging states with conflicting transformed nodes
- Transformation state preservation across multiple inheritance levels
```
2. Edge Cases
```typescript
// Missing tests for:
- Circular parent references
- Invalid state merges
- Incomplete or corrupted state
```
3. State Validation
```typescript
// Need validation tests for:
- Node array consistency
- Parent/child relationship integrity
- Transformation state validity
```
### Required Test Cases
1. **Transformation Inheritance**
```typescript
it('should handle transformation flags in nested states', () => {
const parent = new StateService();
parent.enableTransformation(true);
const child = parent.createChildState();
const grandchild = child.createChildState();
// Verify transformation state inheritance
expect(child.isTransformationEnabled()).toBe(true);
expect(grandchild.isTransformationEnabled()).toBe(true);
});
it('should merge transformed nodes correctly in complex hierarchies', () => {
const parent = new StateService();
const child1 = parent.createChildState();
const child2 = parent.createChildState();
// Setup different transformation states
parent.enableTransformation(true);
child1.enableTransformation(true);
child2.enableTransformation(false);
// Verify correct merging behavior
});
```
2. **State Validation**
```typescript
it('should detect circular parent references', () => {
const parent = new StateService();
const child = parent.createChildState();
// Attempt to create circular reference
expect(() => parent.mergeChildState(child))
.toThrow('Circular parent reference detected');
});
it('should validate transformed nodes match originals', () => {
const service = new StateService();
service.enableTransformation(true);
// Add original node
const original = createTestNode('original');
service.addNode(original);
// Attempt invalid transformation
const invalid = createTestNode('invalid');
expect(() => service.transformNode(invalid, invalid))
.toThrow('Cannot transform node: original node not found');
});
```
3. **Complex State Operations**
```typescript
it('should handle deep cloning with transformations', () => {
const original = new StateService();
original.enableTransformation(true);
// Setup complex state
const node1 = createTestNode('node1');
const node2 = createTestNode('node2');
original.addNode(node1);
original.addNode(node2);
// Transform nodes
const transformed1 = createTestNode('transformed1');
original.transformNode(node1, transformed1);
// Clone and verify
const cloned = original.clone();
expect(cloned.getTransformedNodes()).toEqual(original.getTransformedNodes());
expect(cloned.isTransformationEnabled()).toBe(true);
});
it('should preserve transformation state in complex merges', () => {
const parent = new StateService();
const child1 = parent.createChildState();
const child2 = parent.createChildState();
// Setup different states
parent.enableTransformation(true);
child1.enableTransformation(true);
child2.enableTransformation(false);
// Add and transform nodes
const parentNode = createTestNode('parent');
const child1Node = createTestNode('child1');
const child2Node = createTestNode('child2');
parent.addNode(parentNode);
child1.addNode(child1Node);
child2.addNode(child2Node);
// Transform some nodes
const transformed = createTestNode('transformed');
child1.transformNode(child1Node, transformed);
// Merge and verify
parent.mergeChildState(child1);
parent.mergeChildState(child2);
// Verify correct node ordering and transformation state
});
```
### Implementation Recommendations
1. Add Validation Layer
```typescript
class StateValidator {
static validateParentReference(state: StateNode): void {
// Check for circular references
let current = state;
const visited = new Set<StateNode>();
while (current.parentState) {
if (visited.has(current.parentState)) {
throw new Error('Circular parent reference detected');
}
visited.add(current);
current = current.parentState;
}
}
static validateTransformedNodes(state: StateNode): void {
if (!state.transformedNodes) return;
if (state.transformedNodes.length !== state.nodes.length) {
throw new Error('Transformed nodes array length mismatch');
}
// Add additional validation as needed
}
}
```
2. Enhance State Factory
```typescript
class StateFactory {
createState(options?: StateNodeOptions): StateNode {
const state = // ... existing creation code ...
// Add validation
StateValidator.validateParentReference(state);
StateValidator.validateTransformedNodes(state);
return state;
}
}
```
3. Improve Error Handling
```typescript
class StateService {
mergeChildState(childState: IStateService): void {
this.checkMutable();
const child = childState as StateService;
try {
// Validate before merge
StateValidator.validateParentReference(child.currentState);
StateValidator.validateTransformedNodes(child.currentState);
this.currentState = this.stateFactory.mergeStates(
this.currentState,
child.currentState
);
} catch (error) {
logger.error('State merge failed', { error });
throw new Error(`Invalid state merge: ${error.message}`);
}
}
}
```
## Next Steps
1. Implement test cases in priority order:
- Transformation inheritance tests
- State validation tests
- Complex merge tests
2. Add validation layer:
- Create StateValidator class
- Add validation to factory operations
- Enhance error handling
3. Document best practices:
- When to enable/disable transformations
- How to handle complex state merges
- Validation requirements
## Mock Implementation Analysis
### 1. Mock Service Variants
1. Legacy InterpreterState (`tests/mocks/state.ts`):
```typescript
export class InterpreterState {
private nodes: MeldNode[] = [];
private textVars: Map<string, string> = new Map();
private dataVars: Map<string, any> = new Map();
private commands: Map<string, string> = new Map();
private imports: Set<string> = new Set();
// Missing transformation state completely
}
```
Issues:
- No transformation support
- Doesn't implement full IStateService interface
- Used in older tests that may need updating
2. Factory Mock (`testFactories.ts`):
```typescript
export function createMockStateService(): IStateService {
const mockService = {
// Has all interface methods but behavior doesn't match real service
getTransformedNodes: vi.fn(),
setTransformedNodes: vi.fn(),
transformNode: vi.fn(),
isTransformationEnabled: vi.fn(),
enableTransformation: vi.fn(),
// ...
};
// Default implementations don't match real service behavior
mockService.isTransformationEnabled.mockImplementation(() => false);
mockService.getTransformedNodes.mockImplementation(() => []);
}
```
Issues:
- Transformation state not properly preserved
- Clone operation doesn't copy all state
- Child state creation doesn't inherit parent state
3. Real Service in TestContext:
```typescript
// In TestContext.ts
const state = new StateService();
state.setCurrentFilePath('test.meld');
state.enableTransformation(true); // Different default than mock
```
Issues:
- Inconsistent with mock defaults
- May hide problems in tests using mocks
### 2. Behavioral Mismatches
1. State Inheritance:
```typescript
// Real Service
createChildState(): IStateService {
const child = new StateService(this);
// Inherits transformation state and nodes
return child;
}
// Mock Service
createChildState: vi.fn().mockImplementation(() => createMockStateService())
// Creates fresh mock without inheritance
```
2. Transformation State:
```typescript
// Real Service
enableTransformation(enable: boolean): void {
if (enable) {
this.updateState({
transformedNodes: [...this.currentState.nodes]
}, 'enableTransformation');
}
this._transformationEnabled = enable;
}
// Mock Service
enableTransformation: vi.fn()
// No state initialization or preservation
```
3. State Cloning:
```typescript
// Real Service - Full state preservation
clone(): IStateService {
const cloned = new StateService();
cloned.currentState = this.stateFactory.createState({...});
cloned._transformationEnabled = this._transformationEnabled;
return cloned;
}
// Mock Service - Partial state copying
clone: vi.fn().mockImplementation(() => {
const newMock = createMockStateService();
newMock.getNodes.mockImplementation(() => [...mockService.getNodes()]);
return newMock;
})
```
### 3. Test Impact Analysis
1. Affected Test Types:
- Unit tests using legacy InterpreterState
- Integration tests mixing real and mock services
- API tests with transformation expectations
- Output service tests requiring transformed nodes
2. Failure Patterns:
- Missing transformation state in child states
- Inconsistent node arrays after cloning
- Lost state during service interactions
- Transformation flags not preserved
3. Risk Areas:
- Directive processing with transformation
- State inheritance chains
- Complex state merging operations
- Cross-service interactions
### 4. Required Alignments
1. Interface Compliance:
```typescript
interface IStateService {
// All mocks must implement these transformation methods
getTransformedNodes(): MeldNode[];
setTransformedNodes(nodes: MeldNode[]): void;
transformNode(original: MeldNode, transformed: MeldNode): void;
isTransformationEnabled(): boolean;
enableTransformation(enable: boolean): void;
}
```
2. Behavioral Consistency:
- State inheritance in child states
- Transformation state preservation
- Node array management
- Cloning and merging operations
3. Default Settings:
- Transformation enabled/disabled state
- Initial node arrays
- Parent/child relationships
- State immutability
### 5. Investigation Needed
1. Test Coverage:
- Which tests use which mock variants?
- Are transformation tests comprehensive?
- Do integration tests verify state preservation?
2. State Lifecycle:
- How is transformation state propagated?
- When should state be preserved vs reset?
- What are the valid state transitions?
3. Mock Migration:
- Can we deprecate legacy mocks?
- How to update affected tests?
- What's the migration timeline?