package task import ( "fmt" "strings" ) // ValidationError collects multiple validation failures. type ValidationError struct { Errors []string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed: %s", strings.Join(e.Errors, "; ")) } func (e *ValidationError) Add(msg string) { e.Errors = append(e.Errors, msg) } func (e *ValidationError) HasErrors() bool { return len(e.Errors) > 0 } // Validate checks a task for required fields and valid values. func Validate(t *Task) error { ve := &ValidationError{} if t.Name == "" { ve.Add("name is required") } if t.Claude.Instructions == "" { ve.Add("claude.instructions is required") } if t.Claude.MaxBudgetUSD < 0 { ve.Add("claude.max_budget_usd must be non-negative") } if t.Timeout.Duration < 0 { ve.Add("timeout must be non-negative") } if t.Retry.MaxAttempts < 1 { ve.Add("retry.max_attempts must be at least 1") } if t.Retry.Backoff != "" && t.Retry.Backoff != "linear" && t.Retry.Backoff != "exponential" { ve.Add("retry.backoff must be 'linear' or 'exponential'") } validPriorities := map[Priority]bool{PriorityHigh: true, PriorityNormal: true, PriorityLow: true} if t.Priority != "" && !validPriorities[t.Priority] { ve.Add(fmt.Sprintf("invalid priority %q; must be high, normal, or low", t.Priority)) } if t.Claude.PermissionMode != "" { validModes := map[string]bool{ "default": true, "acceptEdits": true, "bypassPermissions": true, "plan": true, "dontAsk": true, "delegate": true, } if !validModes[t.Claude.PermissionMode] { ve.Add(fmt.Sprintf("invalid permission_mode %q", t.Claude.PermissionMode)) } } if ve.HasErrors() { return ve } return nil }