#!/usr/bin/env bun /** * Agent Pipeline Validator * * Validates pipeline manifest and agent definitions * Usage: ./validate-pipeline.ts [pipeline-name] */ import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; interface PipelineManifest { name: string; stages: Array<{ name: string; description: string }>; dataFormat?: string; } interface AgentDefinition { name: string; description: string; model?: string; } function parseFrontmatter(content: string): { frontmatter: any; content: string } { const match = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/); if (!match) { return { frontmatter: {}, content }; } const frontmatter: any = {}; const lines = match[1].split('\n'); for (const line of lines) { const [key, ...valueParts] = line.split(':'); if (key && valueParts.length > 0) { const value = valueParts.join(':').trim(); frontmatter[key.trim()] = value; } } return { frontmatter, content: match[2] }; } function validateAgentFile(agentPath: string): { valid: boolean; errors: string[] } { const errors: string[] = []; if (!existsSync(agentPath)) { return { valid: false, errors: [`Agent file not found: ${agentPath}`] }; } const content = readFileSync(agentPath, 'utf-8'); const { frontmatter } = parseFrontmatter(content); // Check required fields if (!frontmatter.name) { errors.push(`Missing 'name' in frontmatter`); } if (!frontmatter.description) { errors.push(`Missing 'description' in frontmatter`); } // Check for output markers const markerPattern = /<<<(\w+)>>>/g; const markers = content.match(markerPattern); if (!markers || markers.length < 2) { errors.push(`Missing output markers (expected <<>>...<<>>)`); } return { valid: errors.length === 0, errors }; } function validatePipeline(pipelineName: string): void { const basePath = join(process.cwd(), '.claude', 'agents', pipelineName); const manifestPath = join(basePath, 'pipeline.md'); console.log(`\nšŸ” Validating pipeline: ${pipelineName}\n`); // Check if pipeline directory exists if (!existsSync(basePath)) { console.error(`āŒ Pipeline directory not found: ${basePath}`); process.exit(1); } // Load and validate manifest let stages: string[] = []; if (existsSync(manifestPath)) { const manifestContent = readFileSync(manifestPath, 'utf-8'); const { frontmatter } = parseFrontmatter(manifestContent); stages = frontmatter.stages?.map((s: any) => typeof s === 'string' ? s : s.name) || []; } // If no manifest, auto-detect agents if (stages.length === 0) { const { readdirSync } = require('fs'); const files = readdirSync(basePath).filter((f: string) => f.endsWith('.md') && f !== 'pipeline.md'); stages = files.map((f: string) => f.replace('.md', '')); } console.log(`šŸ“‹ Stages: ${stages.join(' → ')}\n`); // Validate each agent let hasErrors = false; for (const stage of stages) { const agentPath = join(basePath, `${stage}.md`); const { valid, errors } = validateAgentFile(agentPath); if (valid) { console.log(` āœ… ${stage}`); } else { console.log(` āŒ ${stage}`); for (const error of errors) { console.log(` ${error}`); } hasErrors = true; } } // Check for scripts const scriptsPath = join(process.cwd(), 'scripts', `run-${pipelineName}.ts`); if (existsSync(scriptsPath)) { console.log(`\n āœ… Pipeline script: ${scriptsPath}`); } else { console.log(`\n āš ļø Missing pipeline script: ${scriptsPath}`); console.log(` Create this script to orchestrate the agents.`); } console.log(''); if (hasErrors) { console.log('āŒ Pipeline validation failed\n'); process.exit(1); } else { console.log('āœ… Pipeline validation passed!\n'); } } // Main const pipelineName = process.argv[2]; if (!pipelineName) { console.log('Usage: validate-pipeline.ts '); console.log('Example: validate-pipeline.ts ai-news-tweet'); process.exit(1); } validatePipeline(pipelineName);