diff --git a/.github/workflows/validate-agentic-workflows.yml b/.github/workflows/validate-agentic-workflows.yml index 7c8ab391..9cdab41d 100644 --- a/.github/workflows/validate-agentic-workflows.yml +++ b/.github/workflows/validate-agentic-workflows.yml @@ -27,11 +27,9 @@ jobs: exit_code=0 found=0 - # Find all .md files in workflows/ subfolders (excluding README.md) - for workflow_file in workflows/*/*.md; do + # Find all .md files directly in workflows/ + for workflow_file in workflows/*.md; do [ -f "$workflow_file" ] || continue - basename=$(basename "$workflow_file") - [ "$basename" = "README.md" ] && continue found=$((found + 1)) echo "::group::Compiling $workflow_file" @@ -45,7 +43,7 @@ jobs: done if [ "$found" -eq 0 ]; then - echo "No workflow .md files found to validate (README.md files are excluded)." + echo "No workflow .md files found to validate." else echo "Validated $found workflow file(s)." fi diff --git a/AGENTS.md b/AGENTS.md index faed7127..6bda5203 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ The Awesome GitHub Copilot repository is a community-driven collection of custom ├── instructions/ # Coding standards and guidelines (.instructions.md files) ├── skills/ # Agent Skills folders (each with SKILL.md and optional bundled assets) ├── hooks/ # Automated workflow hooks (folders with README.md + hooks.json) -├── workflows/ # Agentic Workflows (folders with README.md + workflow .md files) +├── workflows/ # Agentic Workflows (.md files for GitHub Actions automation) ├── plugins/ # Installable plugin packages (folders with plugin.json) ├── docs/ # Documentation for different resource types ├── eng/ # Build and automation scripts @@ -98,14 +98,14 @@ All agent files (`*.agent.md`), prompt files (`*.prompt.md`), and instruction fi - Follow the [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks) - Optionally includes `tags` field for categorization -#### Workflow Folders (workflows/*/README.md) -- Each workflow is a folder containing a `README.md` file with frontmatter and one or more `.md` workflow files -- README.md must have `name` field (human-readable name) -- README.md must have `description` field (wrapped in single quotes, not empty) -- README.md should have `triggers` field (array of trigger types, e.g., `['schedule', 'issues']`) -- Workflow `.md` files contain YAML frontmatter (`on`, `permissions`, `safe-outputs`) and natural language instructions -- Folder names should be lower case with words separated by hyphens -- Can include bundled assets (scripts, configuration files) +#### Workflow Files (workflows/*.md) +- Each workflow is a standalone `.md` file in the `workflows/` directory +- Must have `name` field (human-readable name) +- Must have `description` field (wrapped in single quotes, not empty) +- Should have `triggers` field (array of trigger types, e.g., `['schedule', 'issues']`) +- Contains agentic workflow frontmatter (`on`, `permissions`, `safe-outputs`) and natural language instructions +- File names should be lower case with words separated by hyphens +- Only `.md` files are accepted — `.yml`, `.yaml`, and `.lock.yml` files are blocked by CI - Optionally includes `tags` field for categorization - Follow the [GitHub Agentic Workflows specification](https://github.github.com/gh-aw) @@ -139,12 +139,11 @@ When adding a new agent, prompt, instruction, skill, hook, workflow, or plugin: **For Workflows:** -1. Create a new folder in `workflows/` with a descriptive name -2. Create `README.md` with proper frontmatter (name, description, triggers, tags) -3. Add one or more `.md` workflow files with `on`, `permissions`, and `safe-outputs` frontmatter -4. Add any bundled scripts or assets to the folder -5. Update the README.md by running: `npm run build` -6. Verify the workflow appears in the generated README +1. Create a new `.md` file in `workflows/` with a descriptive name (e.g., `daily-issues-report.md`) +2. Include frontmatter with `name`, `description`, `triggers`, plus agentic workflow fields (`on`, `permissions`, `safe-outputs`) +3. Compile with `gh aw compile --validate` to verify it's valid +4. Update the README.md by running: `npm run build` +5. Verify the workflow appears in the generated README **For Skills:** @@ -263,14 +262,15 @@ For hook folders (hooks/*/): - [ ] Follows [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks) - [ ] Optionally includes `tags` array field for categorization -For workflow folders (workflows/*/): -- [ ] Folder contains a README.md file with markdown front matter +For workflow files (workflows/*.md): +- [ ] File has markdown front matter - [ ] Has `name` field with human-readable name - [ ] Has non-empty `description` field wrapped in single quotes - [ ] Has `triggers` array field listing workflow trigger types -- [ ] Folder name is lower case with hyphens -- [ ] Contains at least one `.md` workflow file with `on` and `permissions` in frontmatter +- [ ] File name is lower case with hyphens +- [ ] Contains `on` and `permissions` in frontmatter - [ ] Workflow uses least-privilege permissions and safe outputs +- [ ] No `.yml`, `.yaml`, or `.lock.yml` files included - [ ] Follows [GitHub Agentic Workflows specification](https://github.github.com/gh-aw) - [ ] Optionally includes `tags` array field for categorization diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da0b2d5d..63e7630c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -165,21 +165,14 @@ plugins/my-plugin-id/ [Agentic Workflows](https://github.github.com/gh-aw) are AI-powered repository automations that run coding agents in GitHub Actions. Defined in markdown with natural language instructions, they enable scheduled and event-triggered automation with built-in guardrails. -1. **Create a new workflow folder**: Add a new folder in the `workflows/` directory with a descriptive name (e.g., `daily-issues-report`) -2. **Create a `README.md`**: Add a `README.md` with frontmatter containing `name`, `description`, `triggers`, and optionally `tags` -3. **Add workflow files**: Include one or more `.md` workflow files with YAML frontmatter (`on`, `permissions`, `safe-outputs`) and natural language instructions -4. **Add optional assets**: Include any helper scripts or configuration files referenced by the workflow -5. **Update the README**: Run `npm run build` to update the generated README tables +1. **Create your workflow file**: Add a new `.md` file in the `workflows/` directory (e.g., `daily-issues-report.md`) +2. **Include frontmatter**: Add `name`, `description`, `triggers`, and optionally `tags` at the top, followed by agentic workflow frontmatter (`on`, `permissions`, `safe-outputs`) and natural language instructions +3. **Test locally**: Compile with `gh aw compile --validate` to verify it's valid +4. **Update the README**: Run `npm run build` to update the generated README tables -#### Workflow folder structure +> **Note:** Only `.md` files are accepted — do not include compiled `.lock.yml` or `.yml` files. CI will block them. -``` -workflows/daily-issues-report/ -├── README.md # Workflow documentation with frontmatter -└── daily-issues-report.md # Agentic workflow file -``` - -#### README.md frontmatter example +#### Workflow file example ```markdown --- @@ -187,13 +180,6 @@ name: 'Daily Issues Report' description: 'Generates a daily summary of open issues and recent activity as a GitHub issue' triggers: ['schedule'] tags: ['reporting', 'issues', 'automation'] ---- -``` - -#### Workflow file example - -```markdown ---- on: schedule: daily on weekdays permissions: @@ -220,9 +206,9 @@ Create a daily summary of open issues for the team. - **Security first**: Use least-privilege permissions and safe outputs instead of direct write access - **Clear instructions**: Write clear natural language instructions in the workflow body -- **Descriptive names**: Use lowercase folder names with hyphens -- **Test locally**: Use `gh aw run` to test workflows before contributing -- **Documentation**: Include a thorough README explaining what the workflow does and how to use it +- **Descriptive names**: Use lowercase filenames with hyphens (e.g., `daily-issues-report.md`) +- **Test locally**: Use `gh aw compile --validate` to verify your workflow compiles +- **No compiled files**: Only submit the `.md` source — `.lock.yml` and `.yml` files are not accepted - Learn more at the [Agentic Workflows documentation](https://github.github.com/gh-aw) ## Submitting Your Contribution diff --git a/docs/README.workflows.md b/docs/README.workflows.md index 026b7058..93bdfdca 100644 --- a/docs/README.workflows.md +++ b/docs/README.workflows.md @@ -5,7 +5,7 @@ ### How to Use Agentic Workflows **What's Included:** -- Each workflow is a folder containing a `README.md` and one or more `.md` workflow files +- Each workflow is a single `.md` file with YAML frontmatter and natural language instructions - Workflows are compiled to `.lock.yml` GitHub Actions files via `gh aw compile` - Workflows follow the [GitHub Agentic Workflows specification](https://github.github.com/gh-aw) diff --git a/eng/constants.mjs b/eng/constants.mjs index bf5a8904..6736c243 100644 --- a/eng/constants.mjs +++ b/eng/constants.mjs @@ -135,7 +135,7 @@ Hooks enable automated workflows triggered by specific events during GitHub Copi workflowsUsage: `### How to Use Agentic Workflows **What's Included:** -- Each workflow is a folder containing a \`README.md\` and one or more \`.md\` workflow files +- Each workflow is a single \`.md\` file with YAML frontmatter and natural language instructions - Workflows are compiled to \`.lock.yml\` GitHub Actions files via \`gh aw compile\` - Workflows follow the [GitHub Agentic Workflows specification](https://github.github.com/gh-aw) diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index 28b20fad..9eb3da00 100644 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -195,7 +195,7 @@ function generateHooksData(gitDates) { } /** - * Generate workflows metadata (folder-based, similar to hooks) + * Generate workflows metadata (flat .md files) */ function generateWorkflowsData(gitDates) { const workflows = []; @@ -210,37 +210,34 @@ function generateWorkflowsData(gitDates) { }; } - const workflowFolders = fs.readdirSync(WORKFLOWS_DIR).filter((file) => { - const filePath = path.join(WORKFLOWS_DIR, file); - return fs.statSync(filePath).isDirectory(); + const workflowFiles = fs.readdirSync(WORKFLOWS_DIR).filter((file) => { + return file.endsWith(".md") && file !== ".gitkeep"; }); const allTriggers = new Set(); const allTags = new Set(); - for (const folder of workflowFolders) { - const workflowPath = path.join(WORKFLOWS_DIR, folder); - const metadata = parseWorkflowMetadata(workflowPath); + for (const file of workflowFiles) { + const filePath = path.join(WORKFLOWS_DIR, file); + const metadata = parseWorkflowMetadata(filePath); if (!metadata) continue; const relativePath = path - .relative(ROOT_FOLDER, workflowPath) + .relative(ROOT_FOLDER, filePath) .replace(/\\/g, "/"); - const readmeRelativePath = `${relativePath}/README.md`; (metadata.triggers || []).forEach((t) => allTriggers.add(t)); (metadata.tags || []).forEach((t) => allTags.add(t)); + const id = path.basename(file, ".md"); workflows.push({ - id: folder, + id, title: metadata.name, description: metadata.description, triggers: metadata.triggers || [], tags: metadata.tags || [], - assets: metadata.assets || [], path: relativePath, - readmeFile: readmeRelativePath, - lastUpdated: gitDates.get(readmeRelativePath) || null, + lastUpdated: gitDates.get(relativePath) || null, }); } @@ -735,7 +732,7 @@ function generateSearchIndex( id: workflow.id, title: workflow.title, description: workflow.description, - path: workflow.readmeFile, + path: workflow.path, lastUpdated: workflow.lastUpdated, searchText: `${workflow.title} ${workflow.description} ${workflow.triggers.join( " " diff --git a/eng/update-readme.mjs b/eng/update-readme.mjs index 534a591a..3456d5c6 100644 --- a/eng/update-readme.mjs +++ b/eng/update-readme.mjs @@ -588,26 +588,24 @@ function generateWorkflowsSection(workflowsDir) { return ""; } - // Get all workflow folders (directories) - const workflowFolders = fs.readdirSync(workflowsDir).filter((file) => { - const filePath = path.join(workflowsDir, file); - return fs.statSync(filePath).isDirectory(); + // Get all .md workflow files (flat, no subfolders) + const workflowFiles = fs.readdirSync(workflowsDir).filter((file) => { + return file.endsWith(".md") && file !== ".gitkeep"; }); - // Parse each workflow folder - const workflowEntries = workflowFolders - .map((folder) => { - const workflowPath = path.join(workflowsDir, folder); - const metadata = parseWorkflowMetadata(workflowPath); + // Parse each workflow file + const workflowEntries = workflowFiles + .map((file) => { + const filePath = path.join(workflowsDir, file); + const metadata = parseWorkflowMetadata(filePath); if (!metadata) return null; return { - folder, + file, name: metadata.name, description: metadata.description, triggers: metadata.triggers, tags: metadata.tags, - assets: metadata.assets, }; }) .filter((entry) => entry !== null) @@ -621,20 +619,16 @@ function generateWorkflowsSection(workflowsDir) { // Create table header let content = - "| Name | Description | Triggers | Bundled Assets |\n| ---- | ----------- | -------- | -------------- |\n"; + "| Name | Description | Triggers |\n| ---- | ----------- | -------- |\n"; // Generate table rows for each workflow for (const workflow of workflowEntries) { - const link = `../workflows/${workflow.folder}/README.md`; + const link = `../workflows/${workflow.file}`; const triggers = workflow.triggers.length > 0 ? workflow.triggers.join(", ") : "N/A"; - const assetsList = - workflow.assets.length > 0 - ? workflow.assets.map((a) => `\`${a}\``).join("
") - : "None"; content += `| [${workflow.name}](${link}) | ${formatTableCell( workflow.description - )} | ${triggers} | ${assetsList} |\n`; + )} | ${triggers} |\n`; } return `${TEMPLATES.workflowsSection}\n${TEMPLATES.workflowsUsage}\n\n${content}`; diff --git a/eng/yaml-parser.mjs b/eng/yaml-parser.mjs index ded43269..88d16582 100644 --- a/eng/yaml-parser.mjs +++ b/eng/yaml-parser.mjs @@ -254,24 +254,23 @@ function parseHookMetadata(hookPath) { } /** - * Parse workflow metadata from a workflow folder - * @param {string} workflowPath - Path to the workflow folder + * Parse workflow metadata from a standalone .md workflow file + * @param {string} filePath - Path to the workflow .md file * @returns {object|null} Workflow metadata or null on error */ -function parseWorkflowMetadata(workflowPath) { +function parseWorkflowMetadata(filePath) { return safeFileOperation( () => { - const readmeFile = path.join(workflowPath, "README.md"); - if (!fs.existsSync(readmeFile)) { + if (!fs.existsSync(filePath)) { return null; } - const frontmatter = parseFrontmatter(readmeFile); + const frontmatter = parseFrontmatter(filePath); // Validate required fields if (!frontmatter?.name || !frontmatter?.description) { console.warn( - `Invalid workflow at ${workflowPath}: missing name or description in frontmatter` + `Invalid workflow at ${filePath}: missing name or description in frontmatter` ); return null; } @@ -279,37 +278,15 @@ function parseWorkflowMetadata(workflowPath) { // Extract triggers from frontmatter if present const triggers = frontmatter.triggers || []; - // List bundled assets (all files except README.md), recursing through subdirectories - const getAllFiles = (dirPath, arrayOfFiles = []) => { - const files = fs.readdirSync(dirPath); - - files.forEach((file) => { - const filePath = path.join(dirPath, file); - if (fs.statSync(filePath).isDirectory()) { - arrayOfFiles = getAllFiles(filePath, arrayOfFiles); - } else { - const relativePath = path.relative(workflowPath, filePath); - if (relativePath !== "README.md") { - arrayOfFiles.push(relativePath.replace(/\\/g, "/")); - } - } - }); - - return arrayOfFiles; - }; - - const assets = getAllFiles(workflowPath).sort(); - return { name: frontmatter.name, description: frontmatter.description, triggers, tags: frontmatter.tags || [], - assets, - path: workflowPath, + path: filePath, }; }, - workflowPath, + filePath, null ); }