applyDefaults applies default values for missing workflow sections
(data *WorkflowData, markdownPath string)
| 19 | |
| 20 | // applyDefaults applies default values for missing workflow sections |
| 21 | func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error { |
| 22 | toolsLog.Printf("Applying defaults to workflow: name=%s, path=%s", data.Name, markdownPath) |
| 23 | |
| 24 | // Populate cached values after all mutations to Permissions and Concurrency have been applied. |
| 25 | // Using defer ensures the cache is always set on every return path, including early returns. |
| 26 | // applyDefaults is the final stage that mutates data.Permissions (setting defaults), so |
| 27 | // the values computed here represent the stable, |
| 28 | // final state that validateWorkflowData will use. These caches eliminate repeated |
| 29 | // YAML parsing, regex extraction, and expression parsing in the hot validateWorkflowData loop. |
| 30 | defer func() { |
| 31 | data.CachedPermissions = NewPermissionsParser(data.Permissions).ToPermissions() |
| 32 | data.CachedPermissionScopeNamesErr = ValidatePermissionScopeNames(data.Permissions) |
| 33 | data.CachedPermissionScopeNamesSet = true |
| 34 | data.ConcurrencyGroupExpr = extractConcurrencyGroupFromYAML(data.Concurrency) |
| 35 | // Pre-validate and cache the concurrency group expression so validateWorkflowData |
| 36 | // can short-circuit without re-running the expensive ExpressionParser on every call. |
| 37 | // CachedConcurrencyGroupExprSet is always true after applyDefaults regardless of whether |
| 38 | // a group expression exists, so callers can distinguish "already computed" from "not yet computed". |
| 39 | if data.ConcurrencyGroupExpr != "" { |
| 40 | data.CachedConcurrencyGroupExprErr = validateConcurrencyGroupExpression(data.ConcurrencyGroupExpr) |
| 41 | } |
| 42 | data.CachedConcurrencyGroupExprSet = true |
| 43 | // Cache the expanded + parsed toolsets for the GitHub tool so both |
| 44 | // ValidatePermissions and validateToolConfiguration reuse one result. |
| 45 | // Use GetToolsets() to stay aligned with the runtime normalization done by GitHubToolConfig. |
| 46 | if data.ParsedTools != nil && data.ParsedTools.GitHub != nil { |
| 47 | data.CachedParsedToolsets = ParseGitHubToolsets(data.ParsedTools.GitHub.GetToolsets()) |
| 48 | } |
| 49 | }() |
| 50 | |
| 51 | // Check if this is a command trigger workflow (by checking if user specified "on.command") |
| 52 | isCommandTrigger := false |
| 53 | isLabelCommandTrigger := false |
| 54 | if data.On == "" { |
| 55 | // parseOnSection may have already detected the command trigger and populated data.Command |
| 56 | // (this covers slash_command map format, slash_command shorthand "on: /name", and deprecated "command:") |
| 57 | if len(data.Command) > 0 { |
| 58 | isCommandTrigger = true |
| 59 | } else if len(data.LabelCommand) > 0 { |
| 60 | isLabelCommandTrigger = true |
| 61 | } else { |
| 62 | // Check the original frontmatter for command trigger |
| 63 | content, err := os.ReadFile(markdownPath) |
| 64 | if err == nil { |
| 65 | result, err := parser.ExtractFrontmatterFromContent(string(content)) |
| 66 | if err == nil { |
| 67 | if onValue, exists := result.Frontmatter["on"]; exists { |
| 68 | // Check for slash_command or command (deprecated) |
| 69 | if onMap, ok := onValue.(map[string]any); ok { |
| 70 | if _, hasSlashCommand := onMap["slash_command"]; hasSlashCommand { |
| 71 | isCommandTrigger = true |
| 72 | } else if _, hasCommand := onMap["command"]; hasCommand { |
| 73 | isCommandTrigger = true |
| 74 | } else if _, hasLabelCommand := onMap["label_command"]; hasLabelCommand { |
| 75 | isLabelCommandTrigger = true |
| 76 | } |
| 77 | } |
| 78 | } |