import { useState, useRef, useEffect } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Plus, Trash2, ChevronUp, ChevronDown, Upload, Pencil, X, FileText } from 'lucide-react'; import { toast } from 'sonner'; import type { PipelineConfig, PipelineStep } from '@automaker/types'; import { cn } from '@/lib/utils'; // Color options for pipeline columns const COLOR_OPTIONS = [ { value: 'bg-blue-500/20', label: 'Blue', preview: 'bg-blue-500' }, { value: 'bg-purple-500/20', label: 'Purple', preview: 'bg-purple-500' }, { value: 'bg-green-500/20', label: 'Green', preview: 'bg-green-500' }, { value: 'bg-orange-500/20', label: 'Orange', preview: 'bg-orange-500' }, { value: 'bg-red-500/20', label: 'Red', preview: 'bg-red-500' }, { value: 'bg-pink-500/20', label: 'Pink', preview: 'bg-pink-500' }, { value: 'bg-cyan-500/20', label: 'Cyan', preview: 'bg-cyan-500' }, { value: 'bg-amber-500/20', label: 'Amber', preview: 'bg-amber-500' }, { value: 'bg-indigo-500/20', label: 'Indigo', preview: 'bg-indigo-500' }, ]; // Pre-built step templates with well-designed prompts const STEP_TEMPLATES = [ { id: 'code-review', name: 'Code Review', colorClass: 'bg-blue-500/20', instructions: `## Code Review Please perform a thorough code review of the changes made in this feature. Focus on: ### Code Quality - **Readability**: Is the code easy to understand? Are variable/function names descriptive? - **Maintainability**: Will this code be easy to modify in the future? - **DRY Principle**: Is there any duplicated code that should be abstracted? - **Single Responsibility**: Do functions and classes have a single, clear purpose? ### Best Practices - Follow established patterns and conventions used in the codebase - Ensure proper error handling is in place - Check for appropriate logging where needed - Verify that magic numbers/strings are replaced with named constants ### Performance - Identify any potential performance bottlenecks - Check for unnecessary re-renders (React) or redundant computations - Ensure efficient data structures are used ### Testing - Verify that new code has appropriate test coverage - Check that edge cases are handled ### Action Required After reviewing, make any necessary improvements directly. If you find issues: 1. Fix them immediately if they are straightforward 2. For complex issues, document them clearly with suggested solutions Provide a brief summary of changes made or issues found.`, }, { id: 'security-review', name: 'Security Review', colorClass: 'bg-red-500/20', instructions: `## Security Review Perform a comprehensive security audit of the changes made in this feature. Check for vulnerabilities in the following areas: ### Input Validation & Sanitization - Verify all user inputs are properly validated and sanitized - Check for SQL injection vulnerabilities - Check for XSS (Cross-Site Scripting) vulnerabilities - Ensure proper encoding of output data ### Authentication & Authorization - Verify authentication checks are in place where needed - Ensure authorization logic correctly restricts access - Check for privilege escalation vulnerabilities - Verify session management is secure ### Data Protection - Ensure sensitive data is not logged or exposed - Check that secrets/credentials are not hardcoded - Verify proper encryption is used for sensitive data - Check for secure transmission of data (HTTPS, etc.) ### Common Vulnerabilities (OWASP Top 10) - Injection flaws - Broken authentication - Sensitive data exposure - XML External Entities (XXE) - Broken access control - Security misconfiguration - Cross-Site Scripting (XSS) - Insecure deserialization - Using components with known vulnerabilities - Insufficient logging & monitoring ### Action Required 1. Fix any security vulnerabilities immediately 2. For complex security issues, document them with severity levels 3. Add security-related comments where appropriate Provide a security assessment summary with any issues found and fixes applied.`, }, { id: 'testing', name: 'Testing', colorClass: 'bg-green-500/20', instructions: `## Testing Step Please ensure comprehensive test coverage for the changes made in this feature. ### Unit Tests - Write unit tests for all new functions and methods - Ensure edge cases are covered - Test error handling paths - Aim for high code coverage on new code ### Integration Tests - Test interactions between components/modules - Verify API endpoints work correctly - Test database operations if applicable ### Test Quality - Tests should be readable and well-documented - Each test should have a clear purpose - Use descriptive test names that explain the scenario - Follow the Arrange-Act-Assert pattern ### Run Tests After writing tests, run the full test suite and ensure: 1. All new tests pass 2. No existing tests are broken 3. Test coverage meets project standards Provide a summary of tests added and any issues found during testing.`, }, { id: 'documentation', name: 'Documentation', colorClass: 'bg-amber-500/20', instructions: `## Documentation Step Please ensure all changes are properly documented. ### Code Documentation - Add/update JSDoc or docstrings for new functions and classes - Document complex algorithms or business logic - Add inline comments for non-obvious code ### API Documentation - Document any new or modified API endpoints - Include request/response examples - Document error responses ### README Updates - Update README if new setup steps are required - Document any new environment variables - Update architecture diagrams if applicable ### Changelog - Document notable changes for the changelog - Include breaking changes if any Provide a summary of documentation added or updated.`, }, { id: 'optimization', name: 'Performance Optimization', colorClass: 'bg-cyan-500/20', instructions: `## Performance Optimization Step Review and optimize the performance of the changes made in this feature. ### Code Performance - Identify and optimize slow algorithms (O(n²) → O(n log n), etc.) - Remove unnecessary computations or redundant operations - Optimize loops and iterations - Use appropriate data structures ### Memory Usage - Check for memory leaks - Optimize memory-intensive operations - Ensure proper cleanup of resources ### Database/API - Optimize database queries (add indexes, reduce N+1 queries) - Implement caching where appropriate - Batch API calls when possible ### Frontend (if applicable) - Minimize bundle size - Optimize render performance - Implement lazy loading where appropriate - Use memoization for expensive computations ### Action Required 1. Profile the code to identify bottlenecks 2. Apply optimizations 3. Measure improvements Provide a summary of optimizations applied and performance improvements achieved.`, }, ]; // Helper to get template color class const getTemplateColorClass = (templateId: string): string => { const template = STEP_TEMPLATES.find((t) => t.id === templateId); return template?.colorClass || COLOR_OPTIONS[0].value; }; interface PipelineSettingsDialogProps { open: boolean; onClose: () => void; projectPath: string; pipelineConfig: PipelineConfig | null; onSave: (config: PipelineConfig) => Promise; } interface EditingStep { id?: string; name: string; instructions: string; colorClass: string; order: number; } export function PipelineSettingsDialog({ open, onClose, projectPath, pipelineConfig, onSave, }: PipelineSettingsDialogProps) { // Filter and validate steps to ensure all required properties exist const validateSteps = (steps: PipelineStep[] | undefined): PipelineStep[] => { if (!Array.isArray(steps)) return []; return steps.filter( (step): step is PipelineStep => step != null && typeof step.id === 'string' && typeof step.name === 'string' && typeof step.instructions === 'string' ); }; const [steps, setSteps] = useState(() => validateSteps(pipelineConfig?.steps)); const [editingStep, setEditingStep] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const fileInputRef = useRef(null); // Sync steps when dialog opens or pipelineConfig changes useEffect(() => { if (open) { setSteps(validateSteps(pipelineConfig?.steps)); } }, [open, pipelineConfig]); const sortedSteps = [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); const handleAddStep = () => { setEditingStep({ name: '', instructions: '', colorClass: COLOR_OPTIONS[steps.length % COLOR_OPTIONS.length].value, order: steps.length, }); }; const handleEditStep = (step: PipelineStep) => { setEditingStep({ id: step.id, name: step.name, instructions: step.instructions, colorClass: step.colorClass, order: step.order, }); }; const handleDeleteStep = (stepId: string) => { const newSteps = steps.filter((s) => s.id !== stepId); // Reorder remaining steps newSteps.forEach((s, index) => { s.order = index; }); setSteps(newSteps); }; const handleMoveStep = (stepId: string, direction: 'up' | 'down') => { const stepIndex = sortedSteps.findIndex((s) => s.id === stepId); if ( (direction === 'up' && stepIndex === 0) || (direction === 'down' && stepIndex === sortedSteps.length - 1) ) { return; } const newSteps = [...sortedSteps]; const targetIndex = direction === 'up' ? stepIndex - 1 : stepIndex + 1; // Swap orders const temp = newSteps[stepIndex].order; newSteps[stepIndex].order = newSteps[targetIndex].order; newSteps[targetIndex].order = temp; setSteps(newSteps); }; const handleFileUpload = () => { fileInputRef.current?.click(); }; const handleFileInputChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; try { const content = await file.text(); setEditingStep((prev) => (prev ? { ...prev, instructions: content } : null)); toast.success('Instructions loaded from file'); } catch (error) { toast.error('Failed to load file'); } // Reset the input so the same file can be selected again if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleSaveStep = () => { if (!editingStep) return; if (!editingStep.name.trim()) { toast.error('Step name is required'); return; } if (!editingStep.instructions.trim()) { toast.error('Step instructions are required'); return; } const now = new Date().toISOString(); if (editingStep.id) { // Update existing step setSteps((prev) => prev.map((s) => s.id === editingStep.id ? { ...s, name: editingStep.name, instructions: editingStep.instructions, colorClass: editingStep.colorClass, updatedAt: now, } : s ) ); } else { // Add new step const newStep: PipelineStep = { id: `step_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`, name: editingStep.name, instructions: editingStep.instructions, colorClass: editingStep.colorClass, order: steps.length, createdAt: now, updatedAt: now, }; setSteps((prev) => [...prev, newStep]); } setEditingStep(null); }; const handleSaveConfig = async () => { setIsSubmitting(true); try { // If the user is currently editing a step and clicks "Save Configuration", // include that step in the config (common expectation) instead of silently dropping it. let effectiveSteps = steps; if (editingStep) { if (!editingStep.name.trim()) { toast.error('Step name is required'); return; } if (!editingStep.instructions.trim()) { toast.error('Step instructions are required'); return; } const now = new Date().toISOString(); if (editingStep.id) { // Update existing (or add if missing for some reason) const existingIdx = effectiveSteps.findIndex((s) => s.id === editingStep.id); if (existingIdx >= 0) { effectiveSteps = effectiveSteps.map((s) => s.id === editingStep.id ? { ...s, name: editingStep.name, instructions: editingStep.instructions, colorClass: editingStep.colorClass, updatedAt: now, } : s ); } else { effectiveSteps = [ ...effectiveSteps, { id: editingStep.id, name: editingStep.name, instructions: editingStep.instructions, colorClass: editingStep.colorClass, order: effectiveSteps.length, createdAt: now, updatedAt: now, }, ]; } } else { // Add new step effectiveSteps = [ ...effectiveSteps, { id: `step_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`, name: editingStep.name, instructions: editingStep.instructions, colorClass: editingStep.colorClass, order: effectiveSteps.length, createdAt: now, updatedAt: now, }, ]; } // Keep local UI state consistent with what we are saving. setSteps(effectiveSteps); setEditingStep(null); } const sortedEffectiveSteps = [...effectiveSteps].sort( (a, b) => (a.order ?? 0) - (b.order ?? 0) ); const config: PipelineConfig = { version: 1, steps: sortedEffectiveSteps.map((s, index) => ({ ...s, order: index })), }; await onSave(config); toast.success('Pipeline configuration saved'); onClose(); } catch (error) { toast.error('Failed to save pipeline configuration'); } finally { setIsSubmitting(false); } }; return ( !open && onClose()}> {/* Hidden file input for loading instructions from .md files */} Pipeline Settings Configure custom pipeline steps that run after a feature completes "In Progress". Each step will automatically prompt the agent with its instructions.
{/* Steps List */} {sortedSteps.length > 0 ? (
{sortedSteps.map((step, index) => (
{step.name || 'Unnamed Step'}
{(step.instructions || '').substring(0, 100)} {(step.instructions || '').length > 100 ? '...' : ''}
))}
) : (

No pipeline steps configured.

Add steps to create a custom workflow after features complete.

)} {/* Add Step Button */} {!editingStep && ( )} {/* Edit/Add Step Form */} {editingStep && (

{editingStep.id ? 'Edit Step' : 'New Step'}

{/* Template Selector - only show for new steps */} {!editingStep.id && (

Select a pre-built template to populate the form, or create your own from scratch.

)}
setEditingStep((prev) => (prev ? { ...prev, name: e.target.value } : null)) } />
{COLOR_OPTIONS.map((color) => (