meld
Version:
Meld: A template language for LLM prompts
265 lines (200 loc) • 8.06 kB
Markdown
You are the architect of this codebase. It has gone through some refactors:
- We've fully abstracted out the import and md/xml handling with another library we built called llmxml.
- We've create PathService and centralized our path/fs mocks in testing.
But as we've been revisiting the remaining directives, we've discovered a lot of brittleness to our test configuration and it feels like we still need to do more in order to make our codebase more SOLID, maintainable, readable, and testable.
We are working on designing a new architecture which you have been leading the way on. (I will share this below.)
Here's our intended UX:
====== UX
@import[docs/UX.md]
====== / end UX
Here's a repo we used as the spec for meld-ast which we use in this codebase. Our types presumably align with the spec as well.
====== MELD SPEC SRC
@cmd[cpai ../meld-spec/src]
====== / end MELD SPEC SRC
Here's our current code and tests:
====== CODE AND TESTS
@cmd[cpai src tests --stdout]
====== / end CODE AND TESTS
Here's your design for our new architecture:
====== YOUR ARCHITECTURAL DESIGN
@import[dev/arch-1.md]
====== / end YOUR ARCHITECTURAL DESIGN
====== TEST SETUP GOALS
Here are the key goals for an ideal test setup for file/path operations:
1. **Zero Path String Manipulation in Tests**
- Tests should never deal with raw paths
- No manual joining of path segments
- No need to understand $PROJECTPATH/$HOMEPATH resolution
- Path normalization should be invisible to tests
2. **Declarative Project Structure**
- Define test files and directories as simple data structures
- Easy creation of nested directory structures
- Ability to set up multiple related files in one operation
- Simple snapshot/restore of file trees
3. **Intuitive File Operations**
- Read/write files without path manipulation
- Clear distinction between project and home paths
- Automatic parent directory creation
- Standardized error handling
4. **Test-Friendly Assertions**
- Check file existence without resolving paths
- Compare file contents easily
- Verify directory structures
- Match file patterns and wildcards
5. **Isolation and Reset**
- Clean separation between tests
- Easy cleanup of test state
- No leakage between test cases
- Simple "start fresh" mechanism
6. **Minimal Test Setup Code**
- Reusable project templates/fixtures
- Helper methods for common patterns
- Chainable setup operations
- Default reasonable test structures
7. **Clear Error Messages**
- Meaningful errors when files don't exist
- Clear reporting of path resolution failures
- Stack traces that point to test code
- Validation of test setup operations
8. **Mock File System Inspection**
- Easy debugging of file system state
- Clear view of what files exist
- Simple diffing of file system changes
- Visualization of directory structure
The specific test utilities I believe would be needed to implement this vision effectively:
1. **Project Builder Utility**
```typescript
interface TestProject {
// Create project structure declaratively
create(structure: {
files: Record<string, string> // filename -> content
dirs?: string[]
}): Promise<void>
// Add individual files with smart path handling
addFile(filename: string, content: string): Promise<void>
// Get file content without path manipulation
getFile(filename: string): Promise<string>
// Check file existence
hasFile(filename: string): Promise<boolean>
// Clear all test files
reset(): Promise<void>
}
```
2. **Directory Structure Validator**
```typescript
interface DirectoryValidator {
// Verify directory has expected files
verifyDirectory(dir: string, expected: string[]): Promise<void>
// Get directory listing
listFiles(dir: string): Promise<string[]>
// Compare directory state with snapshot
matchSnapshot(dir: string): Promise<void>
}
```
3. **Test Context Builder**
```typescript
interface TestContextBuilder {
// Set up standard test environment
withBasicProject(): TestContext
// Set up project with specific files
withFiles(files: Record<string, string>): TestContext
// Load predefined fixture
fromFixture(fixtureName: string): TestContext
}
```
4. **Path Handling Utilities**
```typescript
interface PathUtils {
// Convert between relative and absolute without knowing implementation
toProjectPath(filename: string): string
toHomePath(filename: string): string
// Get parent directory
getParentDir(path: string): string
}
```
5. **File System State Inspector**
```typescript
interface FSInspector {
// Get current file system state
getSnapshot(): Map<string, string>
// Compare states
getDiff(before: Map<string, string>): {
added: string[]
removed: string[]
modified: string[]
}
// Debug view of file system
printTree(): string
}
```
6. **Custom Test Matchers**
```typescript
interface CustomMatchers {
toHaveFile(filename: string): void
toHaveDirectory(dirname: string): void
toMatchFileContent(filename: string, content: string): void
toHaveFileStructure(expected: Record<string, string>): void
}
```
7. **Fixture Manager**
```typescript
interface FixtureManager {
// Load predefined test fixtures
load(fixtureName: string): Promise<void>
// Save current state as fixture
save(fixtureName: string): Promise<void>
// List available fixtures
list(): string[]
}
```
8. **Error Wrapper**
```typescript
interface ErrorWrapper {
// Convert fs errors to test-friendly errors
wrapFsError(error: Error): TestError
// Create validation errors
createError(message: string, path: string): TestError
}
```
Each of these would work together to enable tests like:
```typescript
// Example test usage
it('should process imported files', async () => {
const context = await TestContext.builder()
.withBasicProject()
.withFiles({
'main.meld': '@import [other.meld]',
'other.meld': '@text greeting = "Hello"'
})
.build()
await runMeld('main.meld')
await expect(context).toHaveFile('main.meld')
await expect(context.file('other.meld')).toHaveContent('@text greeting = "Hello"')
})
```
The key is that these utilities should:
1. Work together cohesively
2. Hide implementation details
3. Provide clear error messages
4. Be easy to use in common test scenarios
5. Support debugging when things go wrong
====== / end TEST SETUP GOALS
====== YOUR TASK
You are designing the utilities and systems for our tests. We already have some existing utilities we can build on, but it still needs a lot of work and we need an approach that abstracts away all complexity for our tests so they can focus on the logic rather than the inner workings.
Your test setup design should:
- align with, build on, and enhance your architectural design
- build on (and, if necessary, improve on) our test setup goals
- adhere strictly to the spec and target UX (double check this!)
- focus first and foremost on isolating and controlling complexity with centralized utilities -- which are themselves tested
- reference patterns and libraries that will help us approach this using well-established and reliable methods
Assume:
- vitest for tests, memfs for in-mem fs testing
- we will completely rewrite the directives and their tests
- no sunk cost. this codebase needs NO backward compatibility for anything because we haven't even shipped it yet. we can delete anything and just move forward with a clean approach.
- we should eschew performance for 'working' and maintainability/readability. we need to first make it work and make it clear before we make it fast.
You should deliver a test design aligned with target UX and spec which includes:
- file structure of the tests
- specific code for the core test utilities
- patterns demonstrating how tests will use the utilities
The end result of the design should be a test setup you are proud of and which aligns with your passion for SOLID, testable, maintainable architecture that is well-tested.
This is YOUR codebase so DO NOT be hand-wavy. Be specific, and decisive in your guidance.