package templates import ( "encoding/json" "fmt" "os" "path/filepath" "sync" "time" "gopkg.in/yaml.v3" ) // WorkflowTemplate defines the structure of a workflow template type WorkflowTemplate struct { // Metadata Name string `yaml:"name" json:"name"` Version string `yaml:"version" json:"version"` Description string `yaml:"description" json:"description"` Author string `yaml:"author" json:"author"` CreatedAt time.Time `yaml:"created_at" json:"created_at"` UpdatedAt time.Time `yaml:"updated_at" json:"updated_at"` // Orchestrator Configuration Orchestrator OrchestratorConfig `yaml:"orchestrator" json:"orchestrator"` // Task Configuration Tasks []TaskConfig `yaml:"tasks" json:"tasks"` // Variable Defaults Defaults map[string]interface{} `yaml:"defaults" json:"defaults,omitempty"` // Tags and Metadata Tags map[string]string `yaml:"tags" json:"tags,omitempty"` } // OrchestratorConfig defines orchestrator settings type OrchestratorConfig struct { PlannerURL string `yaml:"planner_url" json:"planner_url"` JudgeURL string `yaml:"judge_url" json:"judge_url"` ImplementerURL string `yaml:"implementer_url" json:"implementer_url"` TimeoutSeconds int `yaml:"timeout_seconds" json:"timeout_seconds"` RetryPolicy string `yaml:"retry_policy" json:"retry_policy"` MaxConcurrency int `yaml:"max_concurrency" json:"max_concurrency"` Variables map[string]interface{} `yaml:"variables" json:"variables,omitempty"` } // TaskConfig defines task template type TaskConfig struct { ID string `yaml:"id" json:"id"` Title string `yaml:"title" json:"title"` Description string `yaml:"description" json:"description"` Type string `yaml:"type" json:"type"` // feature, bugfix, refactor, etc. Priority string `yaml:"priority" json:"priority"` // high, medium, low DependsOn []string `yaml:"depends_on" json:"depends_on,omitempty"` Config map[string]interface{} `yaml:"config" json:"config,omitempty"` } // WorkflowTemplateManager manages workflow templates type WorkflowTemplateManager struct { mu sync.RWMutex templates map[string]*WorkflowTemplate templatePath string loadedFrom map[string]string // template name -> file path lastModified map[string]time.Time stats *TemplateStats } // TemplateStats tracks template usage type TemplateStats struct { TotalTemplates int LoadedTemplates int ExecutedTemplates int LastUsed map[string]time.Time } // NewWorkflowTemplateManager creates a new template manager func NewWorkflowTemplateManager(templatePath string) *WorkflowTemplateManager { return &WorkflowTemplateManager{ templates: make(map[string]*WorkflowTemplate), templatePath: templatePath, loadedFrom: make(map[string]string), lastModified: make(map[string]time.Time), stats: &TemplateStats{ LastUsed: make(map[string]time.Time), }, } } // SaveTemplate saves a template to file func (wtm *WorkflowTemplateManager) SaveTemplate(template *WorkflowTemplate) error { if template == nil { return fmt.Errorf("template cannot be nil") } if template.Name == "" { return fmt.Errorf("template name cannot be empty") } wtm.mu.Lock() defer wtm.mu.Unlock() // Update timestamps now := time.Now() template.UpdatedAt = now if template.CreatedAt.IsZero() { template.CreatedAt = now } // Create directory if needed if err := os.MkdirAll(wtm.templatePath, 0755); err != nil { return err } // Save to YAML file filePath := filepath.Join(wtm.templatePath, template.Name+".yaml") data, err := yaml.Marshal(template) if err != nil { return err } if err := os.WriteFile(filePath, data, 0644); err != nil { return err } // Update tracking wtm.templates[template.Name] = template wtm.loadedFrom[template.Name] = filePath wtm.lastModified[template.Name] = now wtm.stats.TotalTemplates++ wtm.stats.LoadedTemplates++ return nil } // LoadTemplate loads a template from file func (wtm *WorkflowTemplateManager) LoadTemplate(name string) (*WorkflowTemplate, error) { wtm.mu.Lock() defer wtm.mu.Unlock() // Check if already loaded if template, exists := wtm.templates[name]; exists { return template, nil } // Try to load from file filePath := filepath.Join(wtm.templatePath, name+".yaml") data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read template: %w", err) } var template WorkflowTemplate if err := yaml.Unmarshal(data, &template); err != nil { return nil, fmt.Errorf("failed to parse template: %w", err) } // Cache the template wtm.templates[name] = &template wtm.loadedFrom[name] = filePath wtm.lastModified[name] = time.Now() wtm.stats.LoadedTemplates++ wtm.stats.TotalTemplates++ return &template, nil } // LoadTemplateDirectory loads all templates from a directory func (wtm *WorkflowTemplateManager) LoadTemplateDirectory() error { entries, err := os.ReadDir(wtm.templatePath) if err != nil { if os.IsNotExist(err) { return nil // Directory doesn't exist yet } return err } for _, entry := range entries { if !entry.IsDir() && filepath.Ext(entry.Name()) == ".yaml" { name := entry.Name()[:len(entry.Name())-5] // Remove .yaml _, _ = wtm.LoadTemplate(name) } } return nil } // GetTemplate retrieves a cached template func (wtm *WorkflowTemplateManager) GetTemplate(name string) (*WorkflowTemplate, bool) { wtm.mu.RLock() defer wtm.mu.RUnlock() template, exists := wtm.templates[name] return template, exists } // ListTemplates returns all loaded templates func (wtm *WorkflowTemplateManager) ListTemplates() map[string]*WorkflowTemplate { wtm.mu.RLock() defer wtm.mu.RUnlock() result := make(map[string]*WorkflowTemplate) for name, template := range wtm.templates { result[name] = template } return result } // DeleteTemplate deletes a template func (wtm *WorkflowTemplateManager) DeleteTemplate(name string) error { wtm.mu.Lock() defer wtm.mu.Unlock() filePath, exists := wtm.loadedFrom[name] if !exists { return fmt.Errorf("template not found: %s", name) } if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) { return err } delete(wtm.templates, name) delete(wtm.loadedFrom, name) delete(wtm.lastModified, name) return nil } // ExportTemplateJSON exports a template as JSON func (wtm *WorkflowTemplateManager) ExportTemplateJSON(name string) (string, error) { wtm.mu.RLock() template, exists := wtm.templates[name] wtm.mu.RUnlock() if !exists { return "", fmt.Errorf("template not found: %s", name) } data, err := json.MarshalIndent(template, "", " ") if err != nil { return "", err } return string(data), nil } // ValidateTemplate validates a template func (wtm *WorkflowTemplateManager) ValidateTemplate(template *WorkflowTemplate) error { if template.Name == "" { return fmt.Errorf("template name is required") } if template.Orchestrator.PlannerURL == "" { return fmt.Errorf("planner_url is required") } if len(template.Tasks) == 0 { return fmt.Errorf("at least one task is required") } // Validate task dependencies taskIDs := make(map[string]bool) for _, task := range template.Tasks { if task.ID == "" { return fmt.Errorf("task ID is required") } taskIDs[task.ID] = true } for _, task := range template.Tasks { for _, dep := range task.DependsOn { if !taskIDs[dep] { return fmt.Errorf("task %s depends on non-existent task %s", task.ID, dep) } } } return nil } // RecordUsage records template usage func (wtm *WorkflowTemplateManager) RecordUsage(name string) error { wtm.mu.Lock() defer wtm.mu.Unlock() if _, exists := wtm.templates[name]; !exists { return fmt.Errorf("template not found: %s", name) } wtm.stats.ExecutedTemplates++ wtm.stats.LastUsed[name] = time.Now() return nil } // GetStats returns template manager statistics func (wtm *WorkflowTemplateManager) GetStats() *TemplateStats { wtm.mu.RLock() defer wtm.mu.RUnlock() stats := *wtm.stats return &stats } // ClearCache clears all cached templates func (wtm *WorkflowTemplateManager) ClearCache() { wtm.mu.Lock() defer wtm.mu.Unlock() wtm.templates = make(map[string]*WorkflowTemplate) wtm.loadedFrom = make(map[string]string) wtm.lastModified = make(map[string]time.Time) }