From 5c7f2f7112e2c8eb22317bf732f681c63cfb7136 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:26:31 +0000 Subject: [PATCH 1/3] Initial plan From 60f01140c7bbf19093225ec994caa8a14641e283 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:37:10 +0000 Subject: [PATCH 2/3] Generate actions/checkout steps for repository imports instead of manual git operations Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/scout.lock.yml | 9 + .../js/merge_remote_agent_github_folder.cjs | 78 ++++---- pkg/workflow/compiler_yaml_main_job.go | 122 ++++++++++++ .../repository_import_checkout_test.go | 176 ++++++++++++++++++ 4 files changed, 345 insertions(+), 40 deletions(-) create mode 100644 pkg/workflow/repository_import_checkout_test.go diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index dcd0b03641..92c3c33739 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -183,6 +183,15 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 with: persist-credentials: false + - name: Checkout repository import github/github-deep-research-agent@main + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + with: + repository: github/github-deep-research-agent + ref: main + path: /tmp/gh-aw/repo-imports/github-github-deep-research-agent-main + sparse-checkout: | + .github/ + persist-credentials: false - name: Merge remote .github folder if: ${{ always() }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 diff --git a/actions/setup/js/merge_remote_agent_github_folder.cjs b/actions/setup/js/merge_remote_agent_github_folder.cjs index e8dbb903b4..0abc89dd14 100644 --- a/actions/setup/js/merge_remote_agent_github_folder.cjs +++ b/actions/setup/js/merge_remote_agent_github_folder.cjs @@ -122,6 +122,8 @@ function getAllFiles(dir, baseDir = dir) { /** * Sparse checkout the .github folder from a remote repository + * @deprecated This function is no longer used. The compiler now generates actions/checkout steps + * that checkout repositories into /tmp/gh-aw/repo-imports/, and mergeRepositoryGithubFolder uses those. * @param {string} owner - Repository owner * @param {string} repo - Repository name * @param {string} ref - Git reference (branch, tag, or SHA) @@ -213,7 +215,7 @@ function mergeGithubFolder(sourcePath, destPath) { } /** - * Merge a repository's .github folder into the workspace + * Merge a repository's .github folder into the workspace using pre-checked-out folder * @param {string} owner - Repository owner * @param {string} repo - Repository name * @param {string} ref - Git reference @@ -222,53 +224,49 @@ function mergeGithubFolder(sourcePath, destPath) { async function mergeRepositoryGithubFolder(owner, repo, ref, workspace) { coreObj.info(`Merging .github folder from ${owner}/${repo}@${ref} into workspace`); - // Create temporary directory for sparse checkout - const tempDir = path.join("/tmp", `gh-aw-repo-merge-${Date.now()}`); - fs.mkdirSync(tempDir, { recursive: true }); - coreObj.info(`Created temporary directory: ${tempDir}`); + // Calculate the pre-checked-out folder path + // This matches the format generated by the compiler: /tmp/gh-aw/repo-imports/-- + const sanitizedRef = ref.replace(/\//g, "-").replace(/:/g, "-").replace(/\\/g, "-"); + const checkoutPath = `/tmp/gh-aw/repo-imports/${owner}-${repo}-${sanitizedRef}`; + + coreObj.info(`Looking for pre-checked-out repository at: ${checkoutPath}`); - try { - // Sparse checkout .github folder from remote repository - sparseCheckoutGithubFolder(owner, repo, ref, tempDir); + // Check if the pre-checked-out folder exists + if (!pathExists(checkoutPath)) { + throw new Error(`Pre-checked-out repository not found at ${checkoutPath}. The actions/checkout step may have failed.`); + } - // Check if .github folder exists in remote repository - const sourceGithubFolder = path.join(tempDir, ".github"); - if (!pathExists(sourceGithubFolder)) { - coreObj.warning(`Remote repository ${owner}/${repo}@${ref} does not contain a .github folder`); - return; - } + // Check if .github folder exists in the checked-out repository + const sourceGithubFolder = path.join(checkoutPath, ".github"); + if (!pathExists(sourceGithubFolder)) { + coreObj.warning(`Remote repository ${owner}/${repo}@${ref} does not contain a .github folder`); + return; + } - // Merge .github folder into current repository - const destGithubFolder = path.join(workspace, ".github"); + // Merge .github folder into current repository + const destGithubFolder = path.join(workspace, ".github"); - // Ensure destination .github folder exists - if (!pathExists(destGithubFolder)) { - fs.mkdirSync(destGithubFolder, { recursive: true }); - coreObj.info("Created .github folder in workspace"); - } + // Ensure destination .github folder exists + if (!pathExists(destGithubFolder)) { + fs.mkdirSync(destGithubFolder, { recursive: true }); + coreObj.info("Created .github folder in workspace"); + } - const { merged, conflicts } = mergeGithubFolder(sourceGithubFolder, destGithubFolder); + const { merged, conflicts } = mergeGithubFolder(sourceGithubFolder, destGithubFolder); - // Report results - if (conflicts.length > 0) { - coreObj.error(`Found ${conflicts.length} file conflicts:`); - for (const conflict of conflicts) { - coreObj.error(` - ${conflict}`); - } - throw new Error(`Cannot merge .github folder from ${owner}/${repo}@${ref}: ${conflicts.length} file(s) conflict with existing files`); + // Report results + if (conflicts.length > 0) { + coreObj.error(`Found ${conflicts.length} file conflicts:`); + for (const conflict of conflicts) { + coreObj.error(` - ${conflict}`); } + throw new Error(`Cannot merge .github folder from ${owner}/${repo}@${ref}: ${conflicts.length} file(s) conflict with existing files`); + } - if (merged > 0) { - coreObj.info(`Successfully merged ${merged} file(s) from ${owner}/${repo}@${ref}`); - } else { - coreObj.info("No new files to merge"); - } - } finally { - // Clean up temporary directory - if (pathExists(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - coreObj.info("Cleaned up temporary directory"); - } + if (merged > 0) { + coreObj.info(`Successfully merged ${merged} file(s) from ${owner}/${repo}@${ref}`); + } else { + coreObj.info("No new files to merge"); } } diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go index 6bd9071a35..230cf3cb3b 100644 --- a/pkg/workflow/compiler_yaml_main_job.go +++ b/pkg/workflow/compiler_yaml_main_job.go @@ -36,6 +36,21 @@ func (c *Compiler) generateMainJobSteps(yaml *strings.Builder, data *WorkflowDat } } + // Add checkout steps for repository imports + // Each repository import needs to be checked out into a temporary folder + // so the merge script can copy files from it + if len(data.RepositoryImports) > 0 { + compilerYamlLog.Printf("Adding checkout steps for %d repository imports", len(data.RepositoryImports)) + c.generateRepositoryImportCheckouts(yaml, data.RepositoryImports) + } + + // Add checkout step for legacy agent import (if present) + // This handles the older import format where a specific agent file is imported + if data.AgentFile != "" && data.AgentImportSpec != "" { + compilerYamlLog.Printf("Adding checkout step for legacy agent import: %s", data.AgentImportSpec) + c.generateLegacyAgentImportCheckout(yaml, data.AgentImportSpec) + } + // Add merge remote .github folder step for repository imports or agent imports needsGithubMerge := (len(data.RepositoryImports) > 0) || (data.AgentFile != "" && data.AgentImportSpec != "") if needsGithubMerge { @@ -488,3 +503,110 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus i++ } } + +// generateRepositoryImportCheckouts generates checkout steps for repository imports +// Each repository is checked out into a temporary folder at /tmp/gh-aw/repo-imports/-- +// This allows the merge script to copy files from pre-checked-out folders instead of doing git operations +func (c *Compiler) generateRepositoryImportCheckouts(yaml *strings.Builder, repositoryImports []string) { + for _, repoImport := range repositoryImports { + compilerYamlLog.Printf("Generating checkout step for repository import: %s", repoImport) + + // Parse the import spec to extract owner, repo, and ref + // Format: owner/repo@ref or owner/repo + owner, repo, ref := parseRepositoryImportSpec(repoImport) + if owner == "" || repo == "" { + compilerYamlLog.Printf("Warning: failed to parse repository import: %s", repoImport) + continue + } + + // Generate a sanitized directory name for the checkout + // Use a consistent format: owner-repo-ref + sanitizedRef := sanitizeRefForPath(ref) + checkoutPath := fmt.Sprintf("/tmp/gh-aw/repo-imports/%s-%s-%s", owner, repo, sanitizedRef) + + // Generate the checkout step + yaml.WriteString(fmt.Sprintf(" - name: Checkout repository import %s/%s@%s\n", owner, repo, ref)) + fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/checkout")) + yaml.WriteString(" with:\n") + yaml.WriteString(fmt.Sprintf(" repository: %s/%s\n", owner, repo)) + yaml.WriteString(fmt.Sprintf(" ref: %s\n", ref)) + yaml.WriteString(fmt.Sprintf(" path: %s\n", checkoutPath)) + yaml.WriteString(" sparse-checkout: |\n") + yaml.WriteString(" .github/\n") + yaml.WriteString(" persist-credentials: false\n") + + compilerYamlLog.Printf("Added checkout step: %s/%s@%s -> %s", owner, repo, ref, checkoutPath) + } +} + +// parseRepositoryImportSpec parses a repository import specification +// Format: owner/repo@ref or owner/repo (defaults to "main" if no ref) +// Returns: owner, repo, ref +func parseRepositoryImportSpec(importSpec string) (owner, repo, ref string) { + // Remove section reference if present (file.md#Section) + cleanSpec := importSpec + if idx := strings.Index(importSpec, "#"); idx != -1 { + cleanSpec = importSpec[:idx] + } + + // Split on @ to get path and ref + parts := strings.Split(cleanSpec, "@") + pathPart := parts[0] + ref = "main" // default ref + if len(parts) > 1 { + ref = parts[1] + } + + // Parse path: owner/repo + slashParts := strings.Split(pathPart, "/") + if len(slashParts) != 2 { + return "", "", "" + } + + owner = slashParts[0] + repo = slashParts[1] + + return owner, repo, ref +} + +// generateLegacyAgentImportCheckout generates a checkout step for legacy agent imports +// Legacy format: owner/repo/path/to/file.md@ref +// This checks out the entire repository (not just .github folder) since the file could be anywhere +func (c *Compiler) generateLegacyAgentImportCheckout(yaml *strings.Builder, agentImportSpec string) { + compilerYamlLog.Printf("Generating checkout step for legacy agent import: %s", agentImportSpec) + + // Parse the import spec to extract owner, repo, and ref + owner, repo, ref := parseRepositoryImportSpec(agentImportSpec) + if owner == "" || repo == "" { + compilerYamlLog.Printf("Warning: failed to parse legacy agent import spec: %s", agentImportSpec) + return + } + + // Generate a sanitized directory name for the checkout + sanitizedRef := sanitizeRefForPath(ref) + checkoutPath := fmt.Sprintf("/tmp/gh-aw/repo-imports/%s-%s-%s", owner, repo, sanitizedRef) + + // Generate the checkout step + yaml.WriteString(fmt.Sprintf(" - name: Checkout agent import %s/%s@%s\n", owner, repo, ref)) + fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/checkout")) + yaml.WriteString(" with:\n") + yaml.WriteString(fmt.Sprintf(" repository: %s/%s\n", owner, repo)) + yaml.WriteString(fmt.Sprintf(" ref: %s\n", ref)) + yaml.WriteString(fmt.Sprintf(" path: %s\n", checkoutPath)) + yaml.WriteString(" sparse-checkout: |\n") + yaml.WriteString(" .github/\n") + yaml.WriteString(" persist-credentials: false\n") + + compilerYamlLog.Printf("Added legacy agent checkout step: %s/%s@%s -> %s", owner, repo, ref, checkoutPath) +} + +// sanitizeRefForPath sanitizes a git ref for use in a file path +// Replaces characters that are problematic in file paths with safe alternatives +func sanitizeRefForPath(ref string) string { + // Replace slashes with dashes (for refs like "feature/my-branch") + sanitized := strings.ReplaceAll(ref, "/", "-") + // Replace other problematic characters + sanitized = strings.ReplaceAll(sanitized, ":", "-") + sanitized = strings.ReplaceAll(sanitized, "\\", "-") + return sanitized +} diff --git a/pkg/workflow/repository_import_checkout_test.go b/pkg/workflow/repository_import_checkout_test.go new file mode 100644 index 0000000000..e3640c5780 --- /dev/null +++ b/pkg/workflow/repository_import_checkout_test.go @@ -0,0 +1,176 @@ +//go:build integration + +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/githubnext/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestRepositoryImportCheckout verifies that workflows with repository imports +// get actions/checkout steps generated before the merge step +func TestRepositoryImportCheckout(t *testing.T) { + frontmatter := `--- +on: + issues: + types: [opened] +permissions: + contents: read + issues: read +engine: copilot +strict: false +imports: + - github/test-repo@main +---` + markdown := "# Agent\n\nComplete the task." + + tmpDir := testutil.TempDir(t, "repository-import-checkout-test") + + // Create workflow file + workflowPath := filepath.Join(tmpDir, "test.md") + content := frontmatter + "\n\n" + markdown + require.NoError(t, os.WriteFile(workflowPath, []byte(content), 0644), "Failed to write workflow file") + + // Compile the workflow + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(workflowPath), "Failed to compile workflow") + + // Calculate the lock file path + lockFile := strings.TrimSuffix(workflowPath, ".md") + ".lock.yml" + + // Read the generated lock file + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "Failed to read lock file") + + lockContentStr := string(lockContent) + + // Verify checkout step for repository import is present + assert.Contains(t, lockContentStr, "name: Checkout repository import github/test-repo@main", + "Should contain checkout step for repository import") + assert.Contains(t, lockContentStr, "repository: github/test-repo", + "Should specify repository in checkout step") + assert.Contains(t, lockContentStr, "ref: main", + "Should specify ref in checkout step") + assert.Contains(t, lockContentStr, "path: /tmp/gh-aw/repo-imports/github-test-repo-main", + "Should specify checkout path in temp directory") + assert.Contains(t, lockContentStr, "sparse-checkout:", + "Should use sparse-checkout") + assert.Contains(t, lockContentStr, ".github/", + "Should checkout .github folder") + + // Verify merge step is present + assert.Contains(t, lockContentStr, "name: Merge remote .github folder", + "Should contain merge step") + assert.Contains(t, lockContentStr, "GH_AW_REPOSITORY_IMPORTS", + "Should pass repository imports to merge script") + + // Verify checkout step comes before merge step + checkoutIndex := strings.Index(lockContentStr, "name: Checkout repository import") + mergeIndex := strings.Index(lockContentStr, "name: Merge remote .github folder") + assert.Greater(t, mergeIndex, checkoutIndex, + "Merge step should come after checkout step") +} + +// TestMultipleRepositoryImportCheckouts verifies that workflows with multiple repository imports +// get separate checkout steps for each import +func TestMultipleRepositoryImportCheckouts(t *testing.T) { + frontmatter := `--- +on: + issues: + types: [opened] +permissions: + contents: read + issues: read +engine: copilot +strict: false +imports: + - github/repo1@main + - github/repo2@v1.0.0 +---` + markdown := "# Agent\n\nComplete the task." + + tmpDir := testutil.TempDir(t, "multiple-repository-imports-test") + + // Create workflow file + workflowPath := filepath.Join(tmpDir, "test.md") + content := frontmatter + "\n\n" + markdown + require.NoError(t, os.WriteFile(workflowPath, []byte(content), 0644), "Failed to write workflow file") + + // Compile the workflow + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(workflowPath), "Failed to compile workflow") + + // Calculate the lock file path + lockFile := strings.TrimSuffix(workflowPath, ".md") + ".lock.yml" + + // Read the generated lock file + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "Failed to read lock file") + + lockContentStr := string(lockContent) + + // Verify checkout step for first repository import + assert.Contains(t, lockContentStr, "name: Checkout repository import github/repo1@main", + "Should contain checkout step for first repository import") + assert.Contains(t, lockContentStr, "path: /tmp/gh-aw/repo-imports/github-repo1-main", + "Should use correct path for first import") + + // Verify checkout step for second repository import + assert.Contains(t, lockContentStr, "name: Checkout repository import github/repo2@v1.0.0", + "Should contain checkout step for second repository import") + assert.Contains(t, lockContentStr, "path: /tmp/gh-aw/repo-imports/github-repo2-v1.0.0", + "Should use correct path for second import") + + // Verify merge step includes both imports + assert.Contains(t, lockContentStr, `GH_AW_REPOSITORY_IMPORTS: '["github/repo1@main","github/repo2@v1.0.0"]'`, + "Should pass all repository imports to merge script") +} + +// TestRefSanitization verifies that git refs with special characters are sanitized for paths +func TestRefSanitization(t *testing.T) { + frontmatter := `--- +on: + issues: + types: [opened] +permissions: + contents: read + issues: read +engine: copilot +strict: false +imports: + - github/test-repo@feature/my-branch +---` + markdown := "# Agent\n\nComplete the task." + + tmpDir := testutil.TempDir(t, "ref-sanitization-test") + + // Create workflow file + workflowPath := filepath.Join(tmpDir, "test.md") + content := frontmatter + "\n\n" + markdown + require.NoError(t, os.WriteFile(workflowPath, []byte(content), 0644), "Failed to write workflow file") + + // Compile the workflow + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(workflowPath), "Failed to compile workflow") + + // Calculate the lock file path + lockFile := strings.TrimSuffix(workflowPath, ".md") + ".lock.yml" + + // Read the generated lock file + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "Failed to read lock file") + + lockContentStr := string(lockContent) + + // Verify ref is sanitized in path (/ replaced with -) + assert.Contains(t, lockContentStr, "path: /tmp/gh-aw/repo-imports/github-test-repo-feature-my-branch", + "Should sanitize slashes in ref for path") + assert.Contains(t, lockContentStr, "ref: feature/my-branch", + "Should keep original ref in checkout step") +} From 3c5f987d756733c3a78472216b9515d53e7143ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:40:23 +0000 Subject: [PATCH 3/3] Fix linting issues and recompile workflows Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../js/merge_remote_agent_github_folder.cjs | 2 +- pkg/workflow/compiler_yaml_main_job.go | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/actions/setup/js/merge_remote_agent_github_folder.cjs b/actions/setup/js/merge_remote_agent_github_folder.cjs index 0abc89dd14..1373b30881 100644 --- a/actions/setup/js/merge_remote_agent_github_folder.cjs +++ b/actions/setup/js/merge_remote_agent_github_folder.cjs @@ -228,7 +228,7 @@ async function mergeRepositoryGithubFolder(owner, repo, ref, workspace) { // This matches the format generated by the compiler: /tmp/gh-aw/repo-imports/-- const sanitizedRef = ref.replace(/\//g, "-").replace(/:/g, "-").replace(/\\/g, "-"); const checkoutPath = `/tmp/gh-aw/repo-imports/${owner}-${repo}-${sanitizedRef}`; - + coreObj.info(`Looking for pre-checked-out repository at: ${checkoutPath}`); // Check if the pre-checked-out folder exists diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go index 230cf3cb3b..bfa9266d02 100644 --- a/pkg/workflow/compiler_yaml_main_job.go +++ b/pkg/workflow/compiler_yaml_main_job.go @@ -510,7 +510,7 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus func (c *Compiler) generateRepositoryImportCheckouts(yaml *strings.Builder, repositoryImports []string) { for _, repoImport := range repositoryImports { compilerYamlLog.Printf("Generating checkout step for repository import: %s", repoImport) - + // Parse the import spec to extract owner, repo, and ref // Format: owner/repo@ref or owner/repo owner, repo, ref := parseRepositoryImportSpec(repoImport) @@ -518,23 +518,23 @@ func (c *Compiler) generateRepositoryImportCheckouts(yaml *strings.Builder, repo compilerYamlLog.Printf("Warning: failed to parse repository import: %s", repoImport) continue } - + // Generate a sanitized directory name for the checkout // Use a consistent format: owner-repo-ref sanitizedRef := sanitizeRefForPath(ref) checkoutPath := fmt.Sprintf("/tmp/gh-aw/repo-imports/%s-%s-%s", owner, repo, sanitizedRef) - + // Generate the checkout step - yaml.WriteString(fmt.Sprintf(" - name: Checkout repository import %s/%s@%s\n", owner, repo, ref)) + fmt.Fprintf(yaml, " - name: Checkout repository import %s/%s@%s\n", owner, repo, ref) fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/checkout")) yaml.WriteString(" with:\n") - yaml.WriteString(fmt.Sprintf(" repository: %s/%s\n", owner, repo)) - yaml.WriteString(fmt.Sprintf(" ref: %s\n", ref)) - yaml.WriteString(fmt.Sprintf(" path: %s\n", checkoutPath)) + fmt.Fprintf(yaml, " repository: %s/%s\n", owner, repo) + fmt.Fprintf(yaml, " ref: %s\n", ref) + fmt.Fprintf(yaml, " path: %s\n", checkoutPath) yaml.WriteString(" sparse-checkout: |\n") yaml.WriteString(" .github/\n") yaml.WriteString(" persist-credentials: false\n") - + compilerYamlLog.Printf("Added checkout step: %s/%s@%s -> %s", owner, repo, ref, checkoutPath) } } @@ -548,7 +548,7 @@ func parseRepositoryImportSpec(importSpec string) (owner, repo, ref string) { if idx := strings.Index(importSpec, "#"); idx != -1 { cleanSpec = importSpec[:idx] } - + // Split on @ to get path and ref parts := strings.Split(cleanSpec, "@") pathPart := parts[0] @@ -556,16 +556,16 @@ func parseRepositoryImportSpec(importSpec string) (owner, repo, ref string) { if len(parts) > 1 { ref = parts[1] } - + // Parse path: owner/repo slashParts := strings.Split(pathPart, "/") if len(slashParts) != 2 { return "", "", "" } - + owner = slashParts[0] repo = slashParts[1] - + return owner, repo, ref } @@ -574,29 +574,29 @@ func parseRepositoryImportSpec(importSpec string) (owner, repo, ref string) { // This checks out the entire repository (not just .github folder) since the file could be anywhere func (c *Compiler) generateLegacyAgentImportCheckout(yaml *strings.Builder, agentImportSpec string) { compilerYamlLog.Printf("Generating checkout step for legacy agent import: %s", agentImportSpec) - + // Parse the import spec to extract owner, repo, and ref owner, repo, ref := parseRepositoryImportSpec(agentImportSpec) if owner == "" || repo == "" { compilerYamlLog.Printf("Warning: failed to parse legacy agent import spec: %s", agentImportSpec) return } - + // Generate a sanitized directory name for the checkout sanitizedRef := sanitizeRefForPath(ref) checkoutPath := fmt.Sprintf("/tmp/gh-aw/repo-imports/%s-%s-%s", owner, repo, sanitizedRef) - + // Generate the checkout step - yaml.WriteString(fmt.Sprintf(" - name: Checkout agent import %s/%s@%s\n", owner, repo, ref)) + fmt.Fprintf(yaml, " - name: Checkout agent import %s/%s@%s\n", owner, repo, ref) fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/checkout")) yaml.WriteString(" with:\n") - yaml.WriteString(fmt.Sprintf(" repository: %s/%s\n", owner, repo)) - yaml.WriteString(fmt.Sprintf(" ref: %s\n", ref)) - yaml.WriteString(fmt.Sprintf(" path: %s\n", checkoutPath)) + fmt.Fprintf(yaml, " repository: %s/%s\n", owner, repo) + fmt.Fprintf(yaml, " ref: %s\n", ref) + fmt.Fprintf(yaml, " path: %s\n", checkoutPath) yaml.WriteString(" sparse-checkout: |\n") yaml.WriteString(" .github/\n") yaml.WriteString(" persist-credentials: false\n") - + compilerYamlLog.Printf("Added legacy agent checkout step: %s/%s@%s -> %s", owner, repo, ref, checkoutPath) }