diff --git a/.changeset/patch-document-agentic-workflows.md b/.changeset/patch-document-agentic-workflows.md new file mode 100644 index 0000000000..a671f471bc --- /dev/null +++ b/.changeset/patch-document-agentic-workflows.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Document the two-file agentic workflow structure (separate `.github/agentics/.md` prompt file and `.github/workflows/.md` + runtime import) in the templates and docs, and teach the compiler to validate dispatch-workflow references while dynamically building the dispatch tools from compiled/yml files. diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index b3d0c4c2c9..ace00173c1 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -112,7 +112,6 @@ jobs: with: persist-credentials: false - name: Merge remote .github folder - if: ${{ always() }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_FILE: ".github/agents/technical-doc-writer.agent.md" diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index 713df92bb3..4526a3bf60 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -114,7 +114,6 @@ jobs: with: persist-credentials: false - name: Merge remote .github folder - if: ${{ always() }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_FILE: ".github/agents/ci-cleaner.agent.md" diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index e365b6da8f..75cf22a8e7 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -183,26 +183,6 @@ 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 - env: - GH_AW_REPOSITORY_IMPORTS: '["github/github-deep-research-agent@main"]' - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/merge_remote_agent_github_folder.cjs'); - await main(); - name: Create gh-aw temp directory run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh - name: Setup jq utilities directory diff --git a/.github/workflows/scout.md b/.github/workflows/scout.md index 92e76a875b..4022a3aa37 100644 --- a/.github/workflows/scout.md +++ b/.github/workflows/scout.md @@ -16,7 +16,6 @@ permissions: roles: [admin, maintainer, write] engine: claude imports: - - github/github-deep-research-agent@main - shared/reporting.md - shared/mcp/arxiv.md - shared/mcp/tavily.md diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 70c196cb22..4046aa1658 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -115,7 +115,6 @@ jobs: with: persist-credentials: false - name: Merge remote .github folder - if: ${{ always() }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_FILE: ".github/agents/technical-doc-writer.agent.md" diff --git a/actions/setup/js/merge_remote_agent_github_folder.cjs b/actions/setup/js/merge_remote_agent_github_folder.cjs index 1373b30881..d1673ee9a2 100644 --- a/actions/setup/js/merge_remote_agent_github_folder.cjs +++ b/actions/setup/js/merge_remote_agent_github_folder.cjs @@ -123,7 +123,7 @@ 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. + * that checkout repositories into .github/aw/imports/ (relative to GITHUB_WORKSPACE), and mergeRepositoryGithubFolder uses those. * @param {string} owner - Repository owner * @param {string} repo - Repository name * @param {string} ref - Git reference (branch, tag, or SHA) @@ -167,6 +167,7 @@ function sparseCheckoutGithubFolder(owner, repo, ref, tempDir) { /** * Merge .github folder from source to destination, failing on conflicts + * Only copies files from specific subfolders: agents, skills, prompts, instructions, plugins * @param {string} sourcePath - Source .github folder path * @param {string} destPath - Destination .github folder path * @returns {{merged: number, conflicts: string[]}} @@ -177,11 +178,23 @@ function mergeGithubFolder(sourcePath, destPath) { const conflicts = []; let mergedCount = 0; + // Only copy files from these specific subfolders + const allowedSubfolders = ["agents", "skills", "prompts", "instructions", "plugins"]; + // Get all files from source .github folder const sourceFiles = getAllFiles(sourcePath); coreObj.info(`Found ${sourceFiles.length} files in source .github folder`); for (const relativePath of sourceFiles) { + // Check if the file is in one of the allowed subfolders + const pathParts = relativePath.split(path.sep); + const topLevelFolder = pathParts[0]; + + if (!allowedSubfolders.includes(topLevelFolder)) { + coreObj.info(`Skipping file outside allowed subfolders: ${relativePath}`); + continue; + } + const sourceFile = path.join(sourcePath, relativePath); const destFile = path.join(destPath, relativePath); @@ -225,9 +238,11 @@ async function mergeRepositoryGithubFolder(owner, repo, ref, workspace) { coreObj.info(`Merging .github folder from ${owner}/${repo}@${ref} into workspace`); // Calculate the pre-checked-out folder path - // This matches the format generated by the compiler: /tmp/gh-aw/repo-imports/-- + // This matches the format generated by the compiler: .github/aw/imports/-- + // The path is relative to GITHUB_WORKSPACE, so we need to resolve it const sanitizedRef = ref.replace(/\//g, "-").replace(/:/g, "-").replace(/\\/g, "-"); - const checkoutPath = `/tmp/gh-aw/repo-imports/${owner}-${repo}-${sanitizedRef}`; + const relativePath = `.github/aw/imports/${owner}-${repo}-${sanitizedRef}`; + const checkoutPath = path.join(workspace, relativePath); coreObj.info(`Looking for pre-checked-out repository at: ${checkoutPath}`); diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go index bfa9266d02..154be044ba 100644 --- a/pkg/workflow/compiler_yaml_main_job.go +++ b/pkg/workflow/compiler_yaml_main_job.go @@ -56,7 +56,6 @@ func (c *Compiler) generateMainJobSteps(yaml *strings.Builder, data *WorkflowDat if needsGithubMerge { compilerYamlLog.Printf("Adding merge remote .github folder step") yaml.WriteString(" - name: Merge remote .github folder\n") - yaml.WriteString(" if: ${{ always() }}\n") fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/github-script")) yaml.WriteString(" env:\n") @@ -505,8 +504,8 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus } // 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 +// Each repository is checked out into a temporary folder at .github/aw/imports/-- +// relative to GITHUB_WORKSPACE. 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) @@ -521,8 +520,9 @@ func (c *Compiler) generateRepositoryImportCheckouts(yaml *strings.Builder, repo // Generate a sanitized directory name for the checkout // Use a consistent format: owner-repo-ref + // NOTE: Path must be relative to GITHUB_WORKSPACE for actions/checkout@v6 sanitizedRef := sanitizeRefForPath(ref) - checkoutPath := fmt.Sprintf("/tmp/gh-aw/repo-imports/%s-%s-%s", owner, repo, sanitizedRef) + checkoutPath := fmt.Sprintf(".github/aw/imports/%s-%s-%s", owner, repo, sanitizedRef) // Generate the checkout step fmt.Fprintf(yaml, " - name: Checkout repository import %s/%s@%s\n", owner, repo, ref) diff --git a/pkg/workflow/repository_import_checkout_test.go b/pkg/workflow/repository_import_checkout_test.go index e3640c5780..4e9a2fdd2e 100644 --- a/pkg/workflow/repository_import_checkout_test.go +++ b/pkg/workflow/repository_import_checkout_test.go @@ -57,8 +57,8 @@ imports: "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, "path: .github/aw/imports/github-test-repo-main", + "Should specify checkout path in .github/aw/imports directory") assert.Contains(t, lockContentStr, "sparse-checkout:", "Should use sparse-checkout") assert.Contains(t, lockContentStr, ".github/", @@ -118,13 +118,13 @@ imports: // 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", + assert.Contains(t, lockContentStr, "path: .github/aw/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", + assert.Contains(t, lockContentStr, "path: .github/aw/imports/github-repo2-v1.0.0", "Should use correct path for second import") // Verify merge step includes both imports @@ -169,7 +169,7 @@ imports: 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", + assert.Contains(t, lockContentStr, "path: .github/aw/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")