From 6893eb6080c88831f2f424dc3e59a549a0fc1877 Mon Sep 17 00:00:00 2001 From: beads/crew/emma Date: Wed, 31 Dec 2025 00:37:59 -0800 Subject: [PATCH] feat: add negation support for step conditions (!{{var}}) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds "!{{var}}" syntax for negated truthy checks in Step.Condition. Useful for "skip this step if feature is enabled" patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/formula/stepcondition.go | 12 +++++++++++ internal/formula/stepcondition_test.go | 29 ++++++++++++++++++++++++++ internal/formula/types.go | 2 +- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/internal/formula/stepcondition.go b/internal/formula/stepcondition.go index cc04e64c..66d13786 100644 --- a/internal/formula/stepcondition.go +++ b/internal/formula/stepcondition.go @@ -5,6 +5,7 @@ // // Supported formats: // - "{{var}}" - truthy check (non-empty, non-"false", non-"0") +// - "!{{var}}" - negated truthy check (include if var is falsy) // - "{{var}} == value" - equality check // - "{{var}} != value" - inequality check package formula @@ -20,6 +21,9 @@ var ( // {{var}} - simple variable reference for truthy check stepCondVarPattern = regexp.MustCompile(`^\{\{(\w+)\}\}$`) + // !{{var}} - negated truthy check + stepCondNegatedVarPattern = regexp.MustCompile(`^!\{\{(\w+)\}\}$`) + // {{var}} == value or {{var}} != value stepCondComparePattern = regexp.MustCompile(`^\{\{(\w+)\}\}\s*(==|!=)\s*(.+)$`) ) @@ -30,6 +34,7 @@ var ( // Condition formats: // - "" (empty) - always include // - "{{var}}" - include if var is truthy (non-empty, non-"false", non-"0") +// - "!{{var}}" - include if var is NOT truthy (negated) // - "{{var}} == value" - include if var equals value // - "{{var}} != value" - include if var does not equal value func EvaluateStepCondition(condition string, vars map[string]string) (bool, error) { @@ -47,6 +52,13 @@ func EvaluateStepCondition(condition string, vars map[string]string) (bool, erro return isTruthy(value), nil } + // Try negated truthy pattern: !{{var}} + if m := stepCondNegatedVarPattern.FindStringSubmatch(condition); m != nil { + varName := m[1] + value := vars[varName] + return !isTruthy(value), nil + } + // Try comparison pattern: {{var}} == value or {{var}} != value if m := stepCondComparePattern.FindStringSubmatch(condition); m != nil { varName := m[1] diff --git a/internal/formula/stepcondition_test.go b/internal/formula/stepcondition_test.go index 860236b5..8da2b361 100644 --- a/internal/formula/stepcondition_test.go +++ b/internal/formula/stepcondition_test.go @@ -84,6 +84,35 @@ func TestEvaluateStepCondition(t *testing.T) { want: true, wantErr: false, }, + // Negated truthy checks: !{{var}} + { + name: "negated - truthy value becomes false", + condition: "!{{enabled}}", + vars: map[string]string{"enabled": "true"}, + want: false, + wantErr: false, + }, + { + name: "negated - falsy value becomes true", + condition: "!{{enabled}}", + vars: map[string]string{"enabled": "false"}, + want: true, + wantErr: false, + }, + { + name: "negated - empty value becomes true", + condition: "!{{enabled}}", + vars: map[string]string{"enabled": ""}, + want: true, + wantErr: false, + }, + { + name: "negated - missing variable becomes true", + condition: "!{{enabled}}", + vars: map[string]string{}, + want: true, + wantErr: false, + }, // Equality checks: {{var}} == value { name: "equality - match", diff --git a/internal/formula/types.go b/internal/formula/types.go index ab2384d4..66dd106d 100644 --- a/internal/formula/types.go +++ b/internal/formula/types.go @@ -175,7 +175,7 @@ type Step struct { ExpandVars map[string]string `json:"expand_vars,omitempty"` // Condition makes this step optional based on a variable. - // Format: "{{var}}" (truthy) or "{{var}} == value" or "{{var}} != value". + // Format: "{{var}}" (truthy), "!{{var}}" (negated), "{{var}} == value", "{{var}} != value". // Evaluated at cook/pour time via FilterStepsByCondition. Condition string `json:"condition,omitempty"`