diff --git a/pkg/workflow/compiler_yaml_helpers.go b/pkg/workflow/compiler_yaml_helpers.go index 0baa723d23..ce5f49466b 100644 --- a/pkg/workflow/compiler_yaml_helpers.go +++ b/pkg/workflow/compiler_yaml_helpers.go @@ -145,9 +145,9 @@ func (c *Compiler) generateCheckoutActionsFolder(data *WorkflowData) []string { return nil } -// generateCheckoutGitHubFolder generates the checkout step for the .github folder -// for the agent job. This ensures workflows have access to workflow configurations -// and runtime imports even when they don't do a full repository checkout. +// generateCheckoutGitHubFolder generates the checkout step for the .github and .agents folders +// for the agent job. This ensures workflows have access to workflow configurations, +// runtime imports, and skills even when they don't do a full repository checkout. // // This checkout works in all modes (dev, script, release) and uses shallow clone // for minimal overhead. It should only be called in the main agent job. @@ -171,32 +171,33 @@ func (c *Compiler) generateCheckoutGitHubFolder(data *WorkflowData) []string { // Check if we have contents permission - without it, checkout is not possible permParser := NewPermissionsParser(data.Permissions) if !permParser.HasContentsReadAccess() { - compilerYamlLog.Print("Skipping .github checkout: no contents read access") + compilerYamlLog.Print("Skipping .github and .agents checkout: no contents read access") return nil } - // Skip .github checkout if custom steps already contain a full repository checkout - // The full checkout already includes the .github folder, making sparse checkout redundant + // Skip .github and .agents checkout if custom steps already contain a full repository checkout + // The full checkout already includes these folders, making sparse checkout redundant if data.CustomSteps != "" && ContainsCheckout(data.CustomSteps) { - compilerYamlLog.Print("Skipping .github sparse checkout: custom steps contain full repository checkout") + compilerYamlLog.Print("Skipping .github and .agents sparse checkout: custom steps contain full repository checkout") return nil } - // Skip .github checkout if an automatic full repository checkout will be added + // Skip .github and .agents checkout if an automatic full repository checkout will be added // The shouldAddCheckoutStep function returns true when a checkout step will be automatically added if c.shouldAddCheckoutStep(data) { - compilerYamlLog.Print("Skipping .github sparse checkout: full repository checkout will be added automatically") + compilerYamlLog.Print("Skipping .github and .agents sparse checkout: full repository checkout will be added automatically") return nil } - // For all modes (dev, script, release), checkout .github folder + // For all modes (dev, script, release), checkout .github and .agents folders // This works in release mode where actions aren't checked out return []string{ - " - name: Checkout .github folder\n", + " - name: Checkout .github and .agents folders\n", fmt.Sprintf(" uses: %s\n", GetActionPin("actions/checkout")), " with:\n", " sparse-checkout: |\n", " .github\n", + " .agents\n", " depth: 1\n", " persist-credentials: false\n", } diff --git a/pkg/workflow/compiler_yaml_helpers_test.go b/pkg/workflow/compiler_yaml_helpers_test.go index 7dee1f656a..33e036b063 100644 --- a/pkg/workflow/compiler_yaml_helpers_test.go +++ b/pkg/workflow/compiler_yaml_helpers_test.go @@ -340,3 +340,54 @@ func TestGenerateYAMLRefactored(t *testing.T) { }) } } + +// TestGenerateCheckoutGitHubFolder verifies that when sparse checkout is generated, +// it includes both .github and .agents folders +func TestGenerateCheckoutGitHubFolder(t *testing.T) { + compiler := NewCompiler() + + // Test that when checkout is generated, it includes both folders + // Note: Due to complex logic in shouldAddCheckoutStep, the sparse checkout + // may not be generated in simple test scenarios. This test verifies the + // output format when it is generated. + workflowData := &WorkflowData{ + Permissions: "permissions:\n contents: read", + } + + result := compiler.generateCheckoutGitHubFolder(workflowData) + + // If result is generated, verify it includes both .github and .agents + if result != nil { + checkoutStr := strings.Join(result, "") + + if !strings.Contains(checkoutStr, "Checkout .github and .agents folders") { + t.Errorf("Step name should mention both .github and .agents folders, got: %s", checkoutStr) + } + + if !strings.Contains(checkoutStr, ".github") || !strings.Contains(checkoutStr, ".agents") { + t.Errorf("Sparse checkout should include both .github and .agents folders, got: %s", checkoutStr) + } + + t.Log("✓ Sparse checkout includes both .github and .agents folders") + } else { + t.Log("Sparse checkout not generated (expected with default logic)") + } + + // Test negative cases + t.Run("without_contents_permission", func(t *testing.T) { + data := &WorkflowData{Permissions: "permissions: {}"} + if compiler.generateCheckoutGitHubFolder(data) != nil { + t.Error("Should not generate checkout without contents permission") + } + }) + + t.Run("with_action_tag", func(t *testing.T) { + data := &WorkflowData{ + Permissions: "permissions:\n contents: read", + Features: map[string]any{"action-tag": "abc123"}, + } + if compiler.generateCheckoutGitHubFolder(data) != nil { + t.Error("Should not generate checkout with action-tag") + } + }) +} diff --git a/pkg/workflow/github_folder_checkout_optimization_test.go b/pkg/workflow/github_folder_checkout_optimization_test.go index 45dd7aaf49..b4d42e27b7 100644 --- a/pkg/workflow/github_folder_checkout_optimization_test.go +++ b/pkg/workflow/github_folder_checkout_optimization_test.go @@ -145,10 +145,10 @@ This workflow uses runtime imports: {{runtime-import:shared/example.md}} agentJobSection = lockStr[agentJobStart : agentJobStart+10+nextJobStart] } - // Check for .github folder checkout - hasGitHubCheckout := strings.Contains(agentJobSection, "Checkout .github folder") + // Check for .github and .agents folders checkout + hasGitHubCheckout := strings.Contains(agentJobSection, "Checkout .github and .agents folders") assert.Equal(t, tt.expectGitHubCheckout, hasGitHubCheckout, - "Test case: %s - Expected .github checkout: %t, got: %t\nDescription: %s", + "Test case: %s - Expected .github and .agents checkout: %t, got: %t\nDescription: %s", tt.name, tt.expectGitHubCheckout, hasGitHubCheckout, tt.description) // Check for full repository checkout @@ -163,6 +163,14 @@ This workflow uses runtime imports: {{runtime-import:shared/example.md}} tt.name, tt.description) } + // If .github checkout is expected, verify that .agents folder is also included + if tt.expectGitHubCheckout { + assert.Contains(t, agentJobSection, ".github", + "Test case: %s - Sparse checkout should include .github folder", tt.name) + assert.Contains(t, agentJobSection, ".agents", + "Test case: %s - Sparse checkout should include .agents folder", tt.name) + } + t.Logf("✓ Test case passed: %s", tt.description) }) }