UNPKG

cmte

Version:

Design by Committee™ except it's just you and LLMs

281 lines (258 loc) 8.43 kB
/** * Test file for the execution engine */ import path from 'path'; import fs from 'fs/promises'; import { fileURLToPath } from 'url'; import { ComponentRegistry } from './components/registry.js'; import { WorkflowExecutor } from './execution/workflow-executor.js'; import { createEmptyResumeState, saveResumeState, loadResumeState, markFileAsReviewed } from './state/index.js'; import { logger } from '../utils/logger.js'; // Get dirname equivalent in ESM const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Create the output directory path const outputDir = path.resolve(__dirname, '../../_output/test-workflow'); // Create a test workflow const testWorkflow = { name: 'test-workflow', description: 'Test workflow for execution engine', outputPath: outputDir, phases: [{ usePhase: 'phase1', dependsOn: [] }, { usePhase: 'phase2', dependsOn: ['phase1'], humanInputRequired: [path.join(outputDir, 'phase1/example-output.o.md')] }] }; // Create test phases const phase1 = { name: 'phase1', description: 'Test phase 1', execution: 'sequential', set: [{ useSet: 'set1' }] }; const phase2 = { name: 'phase2', description: 'Test phase 2', execution: 'sequential', set: [{ useSet: 'set2' }] }; // Create test sets const set1 = { name: 'set1', description: 'Test set 1', execution: 'sequential', tasks: [{ description: 'Test task 1', useTask: 'task1' }] }; const set2 = { name: 'set2', description: 'Test set 2', execution: 'sequential', tasks: [{ description: 'Test task 2', useTask: 'task2' }] }; // Create test tasks const task1 = { name: 'task1', description: 'Test task 1' }; const task2 = { name: 'task2', description: 'Test task 2' }; /** * Run the test */ async function runTest() { try { // Create a mock registry const registry = new ComponentRegistry(outputDir); // Add test components to registry await registry.addTask('task1', task1, 'Test task 1 content'); await registry.addTask('task2', task2, 'Test task 2 content'); await registry.addSet('set1', set1); await registry.addSet('set2', set2); await registry.addPhase('phase1', phase1); await registry.addPhase('phase2', phase2); await registry.addWorkflow('test-workflow', testWorkflow); // Create the workflow executor const workflowPath = outputDir; const executor = new WorkflowExecutor({ registry, workflowPath, outputPath: testWorkflow.outputPath, initialContext: { testVar: 'test value' } }); // Create a test output file to simulate phase1 output const phase1Dir = path.join(workflowPath, 'phase1'); await fs.mkdir(phase1Dir, { recursive: true }); const exampleOutputPath = path.join(phase1Dir, 'example-output.o.md'); await fs.writeFile(exampleOutputPath, '# Example Output\n\nThis is a test output file.'); logger.info(`Created test file at: ${exampleOutputPath}`); // Execute the workflow logger.info('Starting workflow execution'); const completed = await executor.executeWorkflow(testWorkflow); if (completed) { logger.info('Workflow completed successfully'); } else { logger.info('Workflow awaiting human input'); // List the files awaiting review const state = await loadResumeState(workflowPath); logger.info('Files awaiting review:'); state.filesAwaitingReview.forEach(file => { logger.info(`- ${file}`); }); // Properly mark the file as reviewed, which will also add it to processedHumanInputPaths const fileToReview = state.filesAwaitingReview[0]; const updatedState = markFileAsReviewed(state, fileToReview); logger.info(`Updated state with markFileAsReviewed: ${JSON.stringify(updatedState)}`); // Save the updated state await saveResumeState(workflowPath, updatedState); // Resume the workflow logger.info('Resuming workflow execution'); const resumeCompleted = await executor.resumeWorkflow(testWorkflow); if (resumeCompleted) { logger.info('Workflow completed successfully after resuming'); } else { logger.error('Workflow still awaiting human input after resuming'); } } } catch (error) { logger.error(`Test failed: ${error}`); console.error(error); } } // Run the test if this file is executed directly if (import.meta.url.startsWith('file:')) { const modulePath = fileURLToPath(import.meta.url); if (process.argv[1] === modulePath) { runTest().catch(error => { logger.error(`Test failed: ${error}`); console.error(error); process.exit(1); }); } } export { runTest }; /** * Run a workflow from the beginning */ export async function runWorkflow(workflowFolder, options = {}) { logger.info('Starting workflow execution', { workflowFolder, options, cwd: process.cwd() }); try { // Create registry and load workflow const registry = new ComponentRegistry(process.cwd()); logger.info('Created component registry'); // Load the workflow logger.info(`Loading workflow from ${workflowFolder}`); const workflow = await registry.loadWorkflow(workflowFolder); logger.info('Loaded workflow', { name: workflow.name, phases: workflow.phases.length, outputPath: workflow.outputPath }); // Create the workflow executor const executor = new WorkflowExecutor({ registry, workflowPath: workflowFolder, outputPath: workflow.outputPath, savePrompts: options.savePrompts || options.dryRun, // Always save prompts in dry run mode dryRun: options.dryRun, apiDryRun: options.apiDryRun, useHaikuModel: options.useHaikuModel, useLocalLLM: options.useLocalLLM ?? false, initialContext: options.initialContext || {} }); logger.info('Created workflow executor', { savePrompts: options.savePrompts || options.dryRun, dryRun: options.dryRun, apiDryRun: options.apiDryRun, useHaikuModel: options.useHaikuModel, useLocalLLM: options.useLocalLLM ?? false }); // Execute the workflow const completed = await executor.executeWorkflow(workflow); if (!completed) { logger.info('Workflow paused for human input'); // Get the state to show what files need review const state = await loadResumeState(workflowFolder); if (state.filesAwaitingReview.length > 0) { logger.info('Files awaiting review:', { files: state.filesAwaitingReview }); } } else { logger.info('Workflow completed successfully'); } } catch (error) { logger.error('Error executing workflow', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); throw error; } } /** * Continue a workflow from where it left off */ export async function nextStep(workflowFolder, options = {}) { const registry = new ComponentRegistry(workflowFolder); const workflow = await registry.loadWorkflow('.'); const executor = new WorkflowExecutor({ registry, workflowPath: workflowFolder, outputPath: workflow.outputPath, savePrompts: options.savePrompts, dryRun: options.dryRun, apiDryRun: options.apiDryRun, useHaikuModel: options.useHaikuModel, useLocalLLM: options.useLocalLLM ?? false, initialContext: options.initialContext }); const completed = await executor.resumeWorkflow(workflow); if (!completed) { logger.info('Workflow still requires human input'); } } /** * Review a file in a workflow */ export async function reviewFile(workflowFolder, filePath) { const state = await loadResumeState(workflowFolder); if (!state) { throw new Error('No workflow state found'); } const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(workflowFolder, filePath); const updatedState = markFileAsReviewed(state, absolutePath); await saveResumeState(workflowFolder, updatedState); logger.info(`Marked ${filePath} as reviewed`); } /** * Initialize a new workflow */ export async function initWorkflow(workflowFolder, options = {}) { const state = createEmptyResumeState(); await saveResumeState(workflowFolder, state); logger.info(`Initialized workflow in ${workflowFolder}`); }