Skip to content

Commit c1f907c

Browse files
authored
refactor: semantic function clustering in pkg/workflow (#21277)
1 parent e4dc9c6 commit c1f907c

File tree

7 files changed

+556
-557
lines changed

7 files changed

+556
-557
lines changed

pkg/workflow/frontmatter_extraction_metadata.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package workflow
33
import (
44
"fmt"
55
"maps"
6-
"math"
76
"strings"
87

98
"github.com/github/gh-aw/pkg/logger"
@@ -144,12 +143,7 @@ func buildSourceURL(source string) string {
144143
}
145144

146145
// safeUintToInt safely converts uint to int, returning 0 if overflow would occur
147-
func safeUintToInt(u uint) int {
148-
if u > math.MaxInt {
149-
return 0 // Return 0 (engine default) if value would overflow
150-
}
151-
return int(u)
152-
}
146+
func safeUintToInt(u uint) int { return safeUint64ToInt(uint64(u)) }
153147

154148
// extractToolsTimeout extracts the timeout setting from tools
155149
// Returns 0 if not set (engines will use their own defaults)

pkg/workflow/safe_outputs_env.go

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package workflow
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
8+
"github.com/github/gh-aw/pkg/logger"
9+
)
10+
11+
var safeOutputsEnvLog = logger.New("workflow:safe_outputs_env")
12+
13+
// ========================================
14+
// Safe Output Environment Variables
15+
// ========================================
16+
17+
// applySafeOutputEnvToMap adds safe-output related environment variables to an env map
18+
// This extracts the duplicated safe-output env setup logic across all engines (copilot, codex, claude, custom)
19+
func applySafeOutputEnvToMap(env map[string]string, data *WorkflowData) {
20+
if data.SafeOutputs == nil {
21+
return
22+
}
23+
24+
safeOutputsEnvLog.Printf("Applying safe output env vars: trial_mode=%t, staged=%t", data.TrialMode, data.SafeOutputs.Staged)
25+
26+
env["GH_AW_SAFE_OUTPUTS"] = "${{ env.GH_AW_SAFE_OUTPUTS }}"
27+
28+
// Add staged flag if specified
29+
if data.TrialMode || data.SafeOutputs.Staged {
30+
env["GH_AW_SAFE_OUTPUTS_STAGED"] = "true"
31+
}
32+
if data.TrialMode && data.TrialLogicalRepo != "" {
33+
env["GH_AW_TARGET_REPO_SLUG"] = data.TrialLogicalRepo
34+
}
35+
36+
// Add branch name if upload assets is configured
37+
if data.SafeOutputs.UploadAssets != nil {
38+
safeOutputsEnvLog.Printf("Adding upload assets env vars: branch=%s", data.SafeOutputs.UploadAssets.BranchName)
39+
env["GH_AW_ASSETS_BRANCH"] = fmt.Sprintf("%q", data.SafeOutputs.UploadAssets.BranchName)
40+
env["GH_AW_ASSETS_MAX_SIZE_KB"] = strconv.Itoa(data.SafeOutputs.UploadAssets.MaxSizeKB)
41+
env["GH_AW_ASSETS_ALLOWED_EXTS"] = fmt.Sprintf("%q", strings.Join(data.SafeOutputs.UploadAssets.AllowedExts, ","))
42+
}
43+
}
44+
45+
// buildWorkflowMetadataEnvVars builds workflow name and source environment variables
46+
// This extracts the duplicated workflow metadata setup logic from safe-output job builders
47+
func buildWorkflowMetadataEnvVars(workflowName string, workflowSource string) []string {
48+
var customEnvVars []string
49+
50+
// Add workflow name
51+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_WORKFLOW_NAME: %q\n", workflowName))
52+
53+
// Add workflow source and source URL if present
54+
if workflowSource != "" {
55+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_WORKFLOW_SOURCE: %q\n", workflowSource))
56+
sourceURL := buildSourceURL(workflowSource)
57+
if sourceURL != "" {
58+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_WORKFLOW_SOURCE_URL: %q\n", sourceURL))
59+
}
60+
}
61+
62+
return customEnvVars
63+
}
64+
65+
// buildWorkflowMetadataEnvVarsWithTrackerID builds workflow metadata env vars including tracker-id
66+
func buildWorkflowMetadataEnvVarsWithTrackerID(workflowName string, workflowSource string, trackerID string) []string {
67+
customEnvVars := buildWorkflowMetadataEnvVars(workflowName, workflowSource)
68+
69+
// Add tracker-id if present
70+
if trackerID != "" {
71+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_TRACKER_ID: %q\n", trackerID))
72+
}
73+
74+
return customEnvVars
75+
}
76+
77+
// buildSafeOutputJobEnvVars builds environment variables for safe-output jobs with staged/target repo handling
78+
// This extracts the duplicated env setup logic in safe-output job builders (create_issue, add_comment, etc.)
79+
func buildSafeOutputJobEnvVars(trialMode bool, trialLogicalRepoSlug string, staged bool, targetRepoSlug string) []string {
80+
var customEnvVars []string
81+
82+
// Pass the staged flag if it's set to true
83+
if trialMode || staged {
84+
safeOutputsEnvLog.Printf("Setting staged flag: trial_mode=%t, staged=%t", trialMode, staged)
85+
customEnvVars = append(customEnvVars, " GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\n")
86+
}
87+
88+
// Set GH_AW_TARGET_REPO_SLUG - prefer target-repo config over trial target repo
89+
if targetRepoSlug != "" {
90+
safeOutputsEnvLog.Printf("Setting target repo slug from config: %s", targetRepoSlug)
91+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_TARGET_REPO_SLUG: %q\n", targetRepoSlug))
92+
} else if trialMode && trialLogicalRepoSlug != "" {
93+
safeOutputsEnvLog.Printf("Setting target repo slug from trial mode: %s", trialLogicalRepoSlug)
94+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_TARGET_REPO_SLUG: %q\n", trialLogicalRepoSlug))
95+
}
96+
97+
return customEnvVars
98+
}
99+
100+
// buildStandardSafeOutputEnvVars builds the standard set of environment variables
101+
// that all safe-output job builders need: metadata + staged/target repo handling
102+
// This reduces duplication in safe-output job builders
103+
func (c *Compiler) buildStandardSafeOutputEnvVars(data *WorkflowData, targetRepoSlug string) []string {
104+
var customEnvVars []string
105+
106+
// Add workflow metadata (name, source, and tracker-id)
107+
customEnvVars = append(customEnvVars, buildWorkflowMetadataEnvVarsWithTrackerID(data.Name, data.Source, data.TrackerID)...)
108+
109+
// Add engine metadata (id, version, model) for XML comment marker
110+
customEnvVars = append(customEnvVars, buildEngineMetadataEnvVars(data.EngineConfig)...)
111+
112+
// Add common safe output job environment variables (staged/target repo)
113+
customEnvVars = append(customEnvVars, buildSafeOutputJobEnvVars(
114+
c.trialMode,
115+
c.trialLogicalRepoSlug,
116+
data.SafeOutputs.Staged,
117+
targetRepoSlug,
118+
)...)
119+
120+
// Add messages config if present
121+
if data.SafeOutputs.Messages != nil {
122+
messagesJSON, err := serializeMessagesConfig(data.SafeOutputs.Messages)
123+
if err != nil {
124+
safeOutputsEnvLog.Printf("Warning: failed to serialize messages config: %v", err)
125+
} else if messagesJSON != "" {
126+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_SAFE_OUTPUT_MESSAGES: %q\n", messagesJSON))
127+
}
128+
}
129+
130+
return customEnvVars
131+
}
132+
133+
// buildStepLevelSafeOutputEnvVars builds environment variables for consolidated safe output steps
134+
// This excludes variables that are already set at the job level in consolidated jobs
135+
func (c *Compiler) buildStepLevelSafeOutputEnvVars(data *WorkflowData, targetRepoSlug string) []string {
136+
var customEnvVars []string
137+
138+
// Only add target repo slug if it's different from the job-level setting
139+
// (i.e., this step has a specific target-repo config that overrides the global trial mode target)
140+
if targetRepoSlug != "" {
141+
// Step-specific target repo overrides job-level setting
142+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_TARGET_REPO_SLUG: %q\n", targetRepoSlug))
143+
} else if !c.trialMode && data.SafeOutputs.Staged {
144+
// Step needs staged flag but there's no job-level target repo (not in trial mode)
145+
// Job level only sets this if trialMode is true
146+
customEnvVars = append(customEnvVars, " GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\n")
147+
}
148+
149+
// Note: The following are now set at job level and should NOT be included here:
150+
// - GH_AW_WORKFLOW_NAME
151+
// - GH_AW_WORKFLOW_SOURCE
152+
// - GH_AW_WORKFLOW_SOURCE_URL
153+
// - GH_AW_TRACKER_ID
154+
// - GH_AW_ENGINE_ID
155+
// - GH_AW_ENGINE_VERSION
156+
// - GH_AW_ENGINE_MODEL
157+
// - GH_AW_SAFE_OUTPUTS_STAGED (if in trial mode)
158+
// - GH_AW_TARGET_REPO_SLUG (if in trial mode and no step override)
159+
// - GH_AW_SAFE_OUTPUT_MESSAGES
160+
161+
return customEnvVars
162+
}
163+
164+
// buildEngineMetadataEnvVars builds engine metadata environment variables (id, version, model)
165+
// These are used by the JavaScript footer generation to create XML comment markers for traceability
166+
func buildEngineMetadataEnvVars(engineConfig *EngineConfig) []string {
167+
var customEnvVars []string
168+
169+
if engineConfig == nil {
170+
return customEnvVars
171+
}
172+
173+
safeOutputsEnvLog.Printf("Building engine metadata env vars: id=%s, version=%s", engineConfig.ID, engineConfig.Version)
174+
175+
// Add engine ID if present
176+
if engineConfig.ID != "" {
177+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_ENGINE_ID: %q\n", engineConfig.ID))
178+
}
179+
180+
// Add engine version if present
181+
if engineConfig.Version != "" {
182+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_ENGINE_VERSION: %q\n", engineConfig.Version))
183+
}
184+
185+
// Add engine model if present
186+
if engineConfig.Model != "" {
187+
customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_ENGINE_MODEL: %q\n", engineConfig.Model))
188+
}
189+
190+
return customEnvVars
191+
}
192+
193+
// ========================================
194+
// Safe Output Environment Helpers
195+
// ========================================
196+
197+
// addCustomSafeOutputEnvVars adds custom environment variables to safe output job steps
198+
func (c *Compiler) addCustomSafeOutputEnvVars(steps *[]string, data *WorkflowData) {
199+
if data.SafeOutputs != nil && len(data.SafeOutputs.Env) > 0 {
200+
for key, value := range data.SafeOutputs.Env {
201+
*steps = append(*steps, fmt.Sprintf(" %s: %s\n", key, value))
202+
}
203+
}
204+
}
205+
206+
// addSafeOutputGitHubTokenForConfig adds github-token to the with section, preferring per-config token over global
207+
// Uses precedence: config token > safe-outputs global github-token > GH_AW_GITHUB_TOKEN || GITHUB_TOKEN
208+
func (c *Compiler) addSafeOutputGitHubTokenForConfig(steps *[]string, data *WorkflowData, configToken string) {
209+
var safeOutputsToken string
210+
if data.SafeOutputs != nil {
211+
safeOutputsToken = data.SafeOutputs.GitHubToken
212+
}
213+
214+
// If app is configured, use app token
215+
if data.SafeOutputs != nil && data.SafeOutputs.GitHubApp != nil {
216+
*steps = append(*steps, " github-token: ${{ steps.safe-outputs-app-token.outputs.token }}\n")
217+
return
218+
}
219+
220+
// Choose the first non-empty custom token for precedence
221+
effectiveCustomToken := configToken
222+
if effectiveCustomToken == "" {
223+
effectiveCustomToken = safeOutputsToken
224+
}
225+
226+
// Get effective token
227+
effectiveToken := getEffectiveSafeOutputGitHubToken(effectiveCustomToken)
228+
*steps = append(*steps, fmt.Sprintf(" github-token: %s\n", effectiveToken))
229+
}
230+
231+
// addSafeOutputCopilotGitHubTokenForConfig adds github-token to the with section for Copilot-related operations
232+
// Uses precedence: config token > safe-outputs global github-token > COPILOT_GITHUB_TOKEN
233+
func (c *Compiler) addSafeOutputCopilotGitHubTokenForConfig(steps *[]string, data *WorkflowData, configToken string) {
234+
var safeOutputsToken string
235+
if data.SafeOutputs != nil {
236+
safeOutputsToken = data.SafeOutputs.GitHubToken
237+
}
238+
239+
// If app is configured, use app token
240+
if data.SafeOutputs != nil && data.SafeOutputs.GitHubApp != nil {
241+
*steps = append(*steps, " github-token: ${{ steps.safe-outputs-app-token.outputs.token }}\n")
242+
return
243+
}
244+
245+
// Choose the first non-empty custom token for precedence
246+
effectiveCustomToken := configToken
247+
if effectiveCustomToken == "" {
248+
effectiveCustomToken = safeOutputsToken
249+
}
250+
251+
// Get effective token
252+
effectiveToken := getEffectiveCopilotRequestsToken(effectiveCustomToken)
253+
*steps = append(*steps, fmt.Sprintf(" github-token: %s\n", effectiveToken))
254+
}
255+
256+
// addSafeOutputAgentGitHubTokenForConfig adds github-token to the with section for agent assignment operations
257+
// Uses precedence: config token > safe-outputs token > GH_AW_AGENT_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN
258+
// This is specifically for assign-to-agent operations which require elevated permissions.
259+
//
260+
// Note: GitHub App tokens are intentionally NOT used here, even when github-app: is configured.
261+
// The Copilot assignment API only accepts PATs (fine-grained or classic), not GitHub App
262+
// installation tokens. Callers must provide an explicit github-token or rely on GH_AW_AGENT_TOKEN.
263+
func (c *Compiler) addSafeOutputAgentGitHubTokenForConfig(steps *[]string, data *WorkflowData, configToken string) {
264+
// Get safe-outputs level token
265+
var safeOutputsToken string
266+
if data.SafeOutputs != nil {
267+
safeOutputsToken = data.SafeOutputs.GitHubToken
268+
}
269+
270+
// Choose the first non-empty custom token for precedence
271+
effectiveCustomToken := configToken
272+
if effectiveCustomToken == "" {
273+
effectiveCustomToken = safeOutputsToken
274+
}
275+
276+
// Get effective token - falls back to ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
277+
// when no explicit token is provided. GitHub App tokens are never used here because the
278+
// Copilot assignment API rejects them.
279+
effectiveToken := getEffectiveCopilotCodingAgentGitHubToken(effectiveCustomToken)
280+
*steps = append(*steps, fmt.Sprintf(" github-token: %s\n", effectiveToken))
281+
}
282+
283+
// buildAllowedReposEnvVar builds an allowed-repos environment variable line for safe-output jobs.
284+
// envVarName should be the full env var name like "GH_AW_ALLOWED_REPOS".
285+
// Returns an empty slice if allowedRepos is empty.
286+
func buildAllowedReposEnvVar(envVarName string, allowedRepos []string) []string {
287+
if len(allowedRepos) == 0 {
288+
return nil
289+
}
290+
reposStr := strings.Join(allowedRepos, ",")
291+
return []string{fmt.Sprintf(" %s: %q\n", envVarName, reposStr)}
292+
}

pkg/workflow/safe_outputs_generation.go

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)