summaryrefslogtreecommitdiff
path: root/internal/task/validator.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-02-08 21:35:45 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-02-08 21:35:45 -1000
commit2e2b2187b957e9af78797a67ec5c6874615fae02 (patch)
tree1181dbb7e43f5d30cb025fa4d50fd4e7a2c893b3 /internal/task/validator.go
Initial project: task model, executor, API server, CLI, storage, reporter
Claudomator automation toolkit for Claude Code with: - Task model with YAML parsing, validation, state machine (49 tests, 0 races) - SQLite storage for tasks and executions - Executor pool with bounded concurrency, timeout, cancellation - REST API + WebSocket for mobile PWA integration - Webhook/multi-notifier system - CLI: init, run, serve, list, status commands - Console, JSON, HTML reporters with cost tracking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/task/validator.go')
-rw-r--r--internal/task/validator.go65
1 files changed, 65 insertions, 0 deletions
diff --git a/internal/task/validator.go b/internal/task/validator.go
new file mode 100644
index 0000000..ea0b1c2
--- /dev/null
+++ b/internal/task/validator.go
@@ -0,0 +1,65 @@
+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
+}