generateWorkflowHeader generates the YAML header section including comments for description, source, imports/includes, frontmatter-hash, stop-time, and manual-approval. All ANSI escape codes are stripped from the output. The gh-aw-metadata line is placed first for easy machine parsing.
(yaml *strings.Builder, data *WorkflowData, frontmatterHash string, bodyHash string, secrets []string, actions []string)
| 82 | // All ANSI escape codes are stripped from the output. |
| 83 | // The gh-aw-metadata line is placed first for easy machine parsing. |
| 84 | func (c *Compiler) generateWorkflowHeader(yaml *strings.Builder, data *WorkflowData, frontmatterHash string, bodyHash string, secrets []string, actions []string) { |
| 85 | // Skip the ASCII art banner in wasm/editor mode — it takes up too much space |
| 86 | if c.skipHeader { |
| 87 | return |
| 88 | } |
| 89 | |
| 90 | // Add lock metadata as the very first line for easy machine parsing. |
| 91 | // Single-line JSON format to minimize merge conflicts. |
| 92 | if frontmatterHash != "" { |
| 93 | agentInfo := AgentMetadataInfo{} |
| 94 | // Agent ID: prefer EngineConfig.ID, fall back to legacy AI field |
| 95 | if data.EngineConfig != nil && data.EngineConfig.ID != "" { |
| 96 | agentInfo.AgentID = data.EngineConfig.ID |
| 97 | } else if data.AI != "" { |
| 98 | agentInfo.AgentID = data.AI |
| 99 | } |
| 100 | // Agent model: only include if statically configured |
| 101 | if data.EngineConfig != nil && data.EngineConfig.Model != "" { |
| 102 | agentInfo.AgentModel = data.EngineConfig.Model |
| 103 | } |
| 104 | // Detection agent info: only if threat detection has its own engine config |
| 105 | if data.SafeOutputs != nil && data.SafeOutputs.ThreatDetection != nil && data.SafeOutputs.ThreatDetection.EngineConfig != nil { |
| 106 | agentInfo.DetectionAgentID = data.SafeOutputs.ThreatDetection.EngineConfig.ID |
| 107 | agentInfo.DetectionAgentModel = data.SafeOutputs.ThreatDetection.EngineConfig.Model |
| 108 | } |
| 109 | agentInfo.EngineVersions = collectEngineVersionsForMetadata(data) |
| 110 | agentInfo.AgentImageRunner = resolveAgentImageRunnerIdentifier(data.RawFrontmatter) |
| 111 | metadata := GenerateLockMetadata(LockHashInfo{FrontmatterHash: frontmatterHash, BodyHash: bodyHash}, data.StopTime, c.effectiveStrictMode(data.RawFrontmatter), agentInfo) |
| 112 | if metadata.CompilerVersion == "" && c.GetActionTag() != "" { |
| 113 | metadata.CompilerVersion = c.GetVersion() |
| 114 | } |
| 115 | metadataJSON, err := metadata.ToJSON() |
| 116 | if err != nil { |
| 117 | // Fallback to legacy format if JSON serialization fails |
| 118 | fmt.Fprintf(yaml, "# frontmatter-hash: %s\n", frontmatterHash) |
| 119 | } else { |
| 120 | fmt.Fprintf(yaml, "# gh-aw-metadata: %s\n", metadataJSON) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | // Embed the gh-aw-manifest immediately after gh-aw-metadata for easy machine parsing. |
| 125 | // The manifest records all secrets, external actions, container images, and frontmatter |
| 126 | // skills detected at compile time so that subsequent compilations can perform safe update |
| 127 | // enforcement. |
| 128 | manifest := NewGHAWManifest(secrets, actions, data.ActionResolutionFailures, data.DockerImagePins, data.Redirect, data.Skills) |
| 129 | if manifestJSON, err := manifest.ToJSON(); err == nil { |
| 130 | fmt.Fprintf(yaml, "# gh-aw-manifest: %s\n", manifestJSON) |
| 131 | } else { |
| 132 | compilerYamlLog.Printf("Failed to serialize gh-aw-manifest: %v. Safe update mode will not be available for future compilations of this workflow.", err) |
| 133 | } |
| 134 | |
| 135 | // Add workflow header with logo and instructions |
| 136 | sourceFile := "the corresponding .md file" |
| 137 | if data.Source != "" { |
| 138 | sourceFile = data.Source |
| 139 | } |
| 140 | header := GenerateWorkflowHeader(sourceFile, "gh-aw", "") |
| 141 | yaml.WriteString(header) |